From nobody Fri Dec 12 15:15:14 2025 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1761650310; cv=none; d=zohomail.com; s=zohoarc; b=YNgGIziz+pOaIeos3OXoIQhpObhDfAUlFBXsQQhUei9hLboABd9R9kpuy4caLCBVfP1gOYgcB60PP0KT45s6Cj+Br5kL74EN9LnL8DhYr5HRUk2TVJYQcSIbZAl2aFGftOPB1E3N4JwTik9ReuI1gy5lP9Ky8gH5vXT6bgbmFiM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1761650310; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Owner:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=jQ51FAtMWH9oz3zQUoBZSDG8VBgzV7k6n5p0JbcLz6s=; b=HgvDJCw5/J2SX8b4IW957RF22UAHme/MOpZWy3IFr/Ye/Z6qkGO1GKb9KFrtwEU/zM8sZNU48v/8wrCXkAO+yF4EbIdHGoRh2kxKB/MEzQoWZini9iqxERBT0yJgk0aaFovS/B+tsZGAcR9Kp4y9kmowucb6oe5tqHvDByM0ics= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 1761650310512836.0482123544945; Tue, 28 Oct 2025 04:18:30 -0700 (PDT) Received: by lists.libvirt.org (Postfix, from userid 993) id 8CC29417E8; Tue, 28 Oct 2025 07:18:17 -0400 (EDT) Received: from [172.19.199.25] (lists.libvirt.org [8.43.85.245]) by lists.libvirt.org (Postfix) with ESMTP id B844F3F969; Tue, 28 Oct 2025 07:16:44 -0400 (EDT) Received: by lists.libvirt.org (Postfix, from userid 993) id 1A5CA4655D; Sat, 25 Oct 2025 04:18:02 -0400 (EDT) Received: from mail-ej1-f48.google.com (mail-ej1-f48.google.com [209.85.218.48]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id 29F7A4655E for ; Sat, 25 Oct 2025 04:18:01 -0400 (EDT) Received: by mail-ej1-f48.google.com with SMTP id a640c23a62f3a-b6d78062424so352123366b.1 for ; Sat, 25 Oct 2025 01:18:01 -0700 (PDT) Received: from tulp.my.domain (84-25-144-101.cable.dynamic.v4.ziggo.nl. [84.25.144.101]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-b6d85398463sm139688766b.34.2025.10.25.01.17.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 25 Oct 2025 01:17:58 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-5.3 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED,SPF_PASS autolearn=unavailable autolearn_force=no version=4.0.1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1761380279; x=1761985079; darn=lists.libvirt.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=jQ51FAtMWH9oz3zQUoBZSDG8VBgzV7k6n5p0JbcLz6s=; b=YUdQycaYomeaDfw2BUHpwaZbHxYXZYOed9HHKaOSR3BrLRWJmy8yxj03YJcxhKUzzk ZVwJD9i5lESpWVIsB7AAYDKKaImTTWscApfuk9A1wvRdTT+fdKnE9KUeKl0BuABR69WJ BehJc2oH7cuY4ilpBRUWK1wDGCRLZkoQ0jkawFst0jP7kphxo2GEf/rY0D71gYSAPnv0 vYXmM4X7Yl8XHaNu8ITG3nEVKdB1t2Z8x15i+EEM8ObSetRPfHP6esfYhj9Bq7KheP64 bDFlJmgYCzRIQDVrU5tvVCRV+rxJmrWQWGTTF4RkxjQf5Cz+ETAHqMrxmrvH7qZdVvBq 9dPg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1761380279; x=1761985079; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=jQ51FAtMWH9oz3zQUoBZSDG8VBgzV7k6n5p0JbcLz6s=; b=anJ/AXY9l7yT/iLmpNjrbLTVe7SjoSdtXF3gdM85FqcRvNzOP4qCPEolfS+aMzbiDt 5fTCfwP9lRYPz/8ZSmxVzzwNGAwPd73FMsaL6e8BaK1SuuqncYrQ8Ggm4UeJ+IgH1tAT bzj3LqkhxYiJhUaYc2vgZTuN1qRDs1CD6pa+l1Uvt8UajZrx49ABNxuJq/CMQfxMTYxv XlKPT2wPdk0Ut1X41MkB7OPZeGisk9HwJ2XrZ7HhFq9USpHGnDqOPZxA/Qg8uLYq5pAQ ntJMqvS3jnFhfqvP6E41s+kRy74wHmxcb74tbf5jwwjINAnPa4DEizyO4nXtpGDbBuRx 9sIg== X-Gm-Message-State: AOJu0YxbHyVm4M80hWJECU8tgK6nt/AGQfmWO/R9xEOL2Fw8F859EVsp aX8GTRxbvjE1vqR93OofZzFUA+foFeK/plgrKhibReL0f1bemBuJl0vkMrtijTnDD92mVA== X-Gm-Gg: ASbGncvyxZLXH5PggoiPLsniJfhqs6zvMPMeIAFYMX0weE3QR7SOZOP28hEWFOcWD+o pCdmRPg0xHTedA20EPFp+JF+ZHYhvkkEPHDMFRDBmM/PWlJk+zVLF8pz5GQerBgDsn2NJdUS1Po FXzm6EbGgJRwEEe3Ak89sZO4idN5Rl3heEETktxlqxOTIlUHiRt56d2VBuJY9YEH+a8jzvZbF13 o/DYV4FVq4kbmgNkYeAou/KNjjG2Ymk155yhKbzECuLHat/hcDoAh7RPncAOhEXs0g+9JRuHpya 6RsNiaM+XMLmU9w0DUje1sgioC+hAXTb0YHUzk91Q7kfU6FYdXVEivYayq7utjju/Bf4xmdSGYt FMG7eTAHnGXt3Gdet8HQqXPzLajIQ3fFcsJsovBG+RTEy27rKM7fWG00TxOn7tsGbw/85BDnFg6 EBPrf308REYPsH/kZO4zF0Rc3/qQRj4X1WrP24H3x7W0cPNg== X-Google-Smtp-Source: AGHT+IEgPTbmBPp1RpcSg3kWHRT2glY2rTUPbbcFsbs/Le03y5cCEon3xc7HDyjqcDTv78pXD54jxA== X-Received: by 2002:a17:907:25c5:b0:b40:da21:bf38 with SMTP id a640c23a62f3a-b6474b36a51mr3588100466b.36.1761380279102; Sat, 25 Oct 2025 01:17:59 -0700 (PDT) From: Roman Bogorodskiy To: devel@lists.libvirt.org Subject: [PATCH 1/4] bhyve: implement NVMe device support Date: Sat, 25 Oct 2025 10:15:55 +0200 Message-ID: <20251025081558.18715-2-bogorodskiy@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251025081558.18715-1-bogorodskiy@gmail.com> References: <20251025081558.18715-1-bogorodskiy@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-ID-Hash: PEM6MR23AFVX7GMWPNZOQP3EUBGGM3TH X-Message-ID-Hash: PEM6MR23AFVX7GMWPNZOQP3EUBGGM3TH X-MailFrom: bogorodskiy@gmail.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-devel.lists.libvirt.org-0; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Roman Bogorodskiy X-Mailman-Version: 3.3.10 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1761650313388154100 Content-Type: text/plain; charset="utf-8" NVMe devices in bhyve are modeled this way: -s $pciaddr,nvme,devpath[,opts] devpath can be a path to the image or the block device. It also can be "ram=3Dsize_in_MiB", but this is not covered by this series. There could be only a single device per PCI address. Optional configuration options (such as max number of queues, concurrent I/O requests, etc) are also not covered by this series. Signed-off-by: Roman Bogorodskiy --- src/bhyve/bhyve_capabilities.c | 14 +++++++ src/bhyve/bhyve_capabilities.h | 1 + src/bhyve/bhyve_command.c | 41 ++++++++++++++++++- src/bhyve/bhyve_device.c | 1 + src/bhyve/bhyve_domain.c | 15 +++++-- .../bhyvexml2argvdata/bhyvexml2argv-nvme.args | 9 ++++ .../bhyvexml2argv-nvme.ldargs | 4 ++ .../bhyvexml2argvdata/bhyvexml2argv-nvme.xml | 16 ++++++++ tests/bhyvexml2argvtest.c | 4 +- .../bhyvexml2xmlout-nvme.xml | 27 ++++++++++++ tests/bhyvexml2xmltest.c | 1 + 11 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 tests/bhyvexml2argvdata/bhyvexml2argv-nvme.args create mode 100644 tests/bhyvexml2argvdata/bhyvexml2argv-nvme.ldargs create mode 100644 tests/bhyvexml2argvdata/bhyvexml2argv-nvme.xml create mode 100644 tests/bhyvexml2xmloutdata/bhyvexml2xmlout-nvme.xml diff --git a/src/bhyve/bhyve_capabilities.c b/src/bhyve/bhyve_capabilities.c index 04a5a4cf29..aad757e801 100644 --- a/src/bhyve/bhyve_capabilities.c +++ b/src/bhyve/bhyve_capabilities.c @@ -355,6 +355,17 @@ bhyveProbeCapsVirtioRnd(unsigned int *caps, char *bina= ry) } =20 =20 +static int +bhyveProbeCapsNvme(unsigned int *caps, char *binary) +{ + return bhyveProbeCapsDeviceHelper(caps, binary, + "-s", + "0,nvme", + "pci slot 0:0: unknown device \"nvme= \"", + BHYVE_CAP_NVME); +} + + int virBhyveProbeCaps(unsigned int *caps) { @@ -395,6 +406,9 @@ virBhyveProbeCaps(unsigned int *caps) if ((ret =3D bhyveProbeCapsVirtioRnd(caps, binary))) goto out; =20 + if ((ret =3D bhyveProbeCapsNvme(caps, binary))) + goto out; + out: VIR_FREE(binary); return ret; diff --git a/src/bhyve/bhyve_capabilities.h b/src/bhyve/bhyve_capabilities.h index 9b24241dc1..500b235397 100644 --- a/src/bhyve/bhyve_capabilities.h +++ b/src/bhyve/bhyve_capabilities.h @@ -54,6 +54,7 @@ typedef enum { BHYVE_CAP_VNC_PASSWORD =3D 1 << 8, BHYVE_CAP_VIRTIO_9P =3D 1 << 9, BHYVE_CAP_VIRTIO_RND =3D 1 << 10, + BHYVE_CAP_NVME =3D 1 << 11, } virBhyveCapsFlags; =20 int virBhyveProbeGrubCaps(virBhyveGrubCapsFlags *caps); diff --git a/src/bhyve/bhyve_command.c b/src/bhyve/bhyve_command.c index ab6d6e92e4..9f2d02b484 100644 --- a/src/bhyve/bhyve_command.c +++ b/src/bhyve/bhyve_command.c @@ -328,6 +328,38 @@ bhyveBuildUSBControllerArgStr(const virDomainDef *def, return 0; } =20 +static int +bhyveBuildNVMeControllerArgStr(const virDomainDef *def, + virDomainControllerDef *controller, + struct _bhyveConn *driver G_GNUC_UNUSED, + virCommand *cmd) +{ + g_auto(virBuffer) buf =3D VIR_BUFFER_INITIALIZER; + const char *disk_source; + size_t i; + + for (i =3D 0; i < def->ndisks; i++) { + virDomainDiskDef *disk =3D def->disks[i]; + + if (disk->bus !=3D VIR_DOMAIN_DISK_BUS_NVME) + continue; + + if (disk->info.addr.drive.controller !=3D controller->idx) + continue; + + VIR_DEBUG("disk %zu controller %d", i, controller->idx); + + disk_source =3D virDomainDiskGetSource(disk); + + virCommandAddArg(cmd, "-s"); + virCommandAddArgFormat(cmd, "%d:0,nvme,%s", + controller->info.addr.pci.slot, + disk_source); + } + + return 0; +} + static int bhyveBuildVirtIODiskArgStr(const virDomainDef *def G_GNUC_UNUSED, virDomainDiskDef *disk, @@ -370,6 +402,9 @@ bhyveBuildDiskArgStr(const virDomainDef *def, case VIR_DOMAIN_DISK_BUS_SATA: /* Handled by bhyveBuildAHCIControllerArgStr() */ break; + case VIR_DOMAIN_DISK_BUS_NVME: + /* Handled by bhyveBuildNVMeControllerArgStr() */ + break; case VIR_DOMAIN_DISK_BUS_VIRTIO: if (bhyveBuildVirtIODiskArgStr(def, disk, cmd) < 0) return -1; @@ -382,7 +417,6 @@ bhyveBuildDiskArgStr(const virDomainDef *def, case VIR_DOMAIN_DISK_BUS_USB: case VIR_DOMAIN_DISK_BUS_UML: case VIR_DOMAIN_DISK_BUS_SD: - case VIR_DOMAIN_DISK_BUS_NVME: case VIR_DOMAIN_DISK_BUS_LAST: default: virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", @@ -432,13 +466,16 @@ bhyveBuildControllerArgStr(const virDomainDef *def, virCommandAddArgFormat(cmd, "%d:0,lpc", controller->info.addr.pci.slot); break; + case VIR_DOMAIN_CONTROLLER_TYPE_NVME: + if (bhyveBuildNVMeControllerArgStr(def, controller, driver, cmd) <= 0) + return -1; + break; case VIR_DOMAIN_CONTROLLER_TYPE_IDE: case VIR_DOMAIN_CONTROLLER_TYPE_FDC: case VIR_DOMAIN_CONTROLLER_TYPE_SCSI: case VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL: case VIR_DOMAIN_CONTROLLER_TYPE_CCID: case VIR_DOMAIN_CONTROLLER_TYPE_XENBUS: - case VIR_DOMAIN_CONTROLLER_TYPE_NVME: case VIR_DOMAIN_CONTROLLER_TYPE_LAST: default: virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", diff --git a/src/bhyve/bhyve_device.c b/src/bhyve/bhyve_device.c index 68983d5bc4..49cfccaeba 100644 --- a/src/bhyve/bhyve_device.c +++ b/src/bhyve/bhyve_device.c @@ -114,6 +114,7 @@ bhyveAssignDevicePCISlots(virDomainDef *def, for (i =3D 0; i < def->ncontrollers; i++) { if ((def->controllers[i]->type =3D=3D VIR_DOMAIN_CONTROLLER_TYPE_P= CI) || (def->controllers[i]->type =3D=3D VIR_DOMAIN_CONTROLLER_TYPE_S= ATA) || + (def->controllers[i]->type =3D=3D VIR_DOMAIN_CONTROLLER_TYPE_N= VME) || ((def->controllers[i]->type =3D=3D VIR_DOMAIN_CONTROLLER_TYPE_= USB) && (def->controllers[i]->model =3D=3D VIR_DOMAIN_CONTROLLER_MODE= L_USB_NEC_XHCI)) || def->controllers[i]->type =3D=3D VIR_DOMAIN_CONTROLLER_TYPE_IS= A) { diff --git a/src/bhyve/bhyve_domain.c b/src/bhyve/bhyve_domain.c index 9dec300a99..79ac336430 100644 --- a/src/bhyve/bhyve_domain.c +++ b/src/bhyve/bhyve_domain.c @@ -111,9 +111,10 @@ bhyveDomainDiskDefAssignAddress(struct _bhyveConn *dri= ver, virDomainDiskDef *def, const virDomainDef *vmdef G_GNUC_UNUSED) { - int idx =3D virDiskNameToIndex(def->dst); + int idx =3D -1; + int nvme_ctrl =3D 0; =20 - if (idx < 0) { + if (virDiskNameParse(def->dst, &nvme_ctrl, &idx, NULL) < 0) { virReportError(VIR_ERR_XML_ERROR, _("Unknown disk name '%1$s' and no address specifie= d"), def->dst); @@ -134,6 +135,15 @@ bhyveDomainDiskDefAssignAddress(struct _bhyveConn *dri= ver, =20 def->info.addr.drive.bus =3D 0; break; + + case VIR_DOMAIN_DISK_BUS_NVME: + def->info.type =3D VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE; + + def->info.addr.drive.controller =3D nvme_ctrl; + def->info.addr.drive.unit =3D 0; + def->info.addr.drive.bus =3D idx; + break; + case VIR_DOMAIN_DISK_BUS_SCSI: case VIR_DOMAIN_DISK_BUS_IDE: case VIR_DOMAIN_DISK_BUS_FDC: @@ -143,7 +153,6 @@ bhyveDomainDiskDefAssignAddress(struct _bhyveConn *driv= er, case VIR_DOMAIN_DISK_BUS_USB: case VIR_DOMAIN_DISK_BUS_UML: case VIR_DOMAIN_DISK_BUS_SD: - case VIR_DOMAIN_DISK_BUS_NVME: case VIR_DOMAIN_DISK_BUS_LAST: default: break; diff --git a/tests/bhyvexml2argvdata/bhyvexml2argv-nvme.args b/tests/bhyvex= ml2argvdata/bhyvexml2argv-nvme.args new file mode 100644 index 0000000000..bd39db1fe6 --- /dev/null +++ b/tests/bhyvexml2argvdata/bhyvexml2argv-nvme.args @@ -0,0 +1,9 @@ +bhyve \ +-c 1 \ +-m 214 \ +-u \ +-H \ +-P \ +-s 0:0,hostbridge \ +-s 2:0,nvme,/tmp/freebsd.img \ +bhyve diff --git a/tests/bhyvexml2argvdata/bhyvexml2argv-nvme.ldargs b/tests/bhyv= exml2argvdata/bhyvexml2argv-nvme.ldargs new file mode 100644 index 0000000000..5905f4b3e6 --- /dev/null +++ b/tests/bhyvexml2argvdata/bhyvexml2argv-nvme.ldargs @@ -0,0 +1,4 @@ +bhyveload \ +-m 214 \ +-d /tmp/freebsd.img \ +bhyve diff --git a/tests/bhyvexml2argvdata/bhyvexml2argv-nvme.xml b/tests/bhyvexm= l2argvdata/bhyvexml2argv-nvme.xml new file mode 100644 index 0000000000..8daaa11e85 --- /dev/null +++ b/tests/bhyvexml2argvdata/bhyvexml2argv-nvme.xml @@ -0,0 +1,16 @@ + + bhyve + df3be7e7-a104-11e3-aeb0-50e5492bd3dc + 219136 + 1 + + hvm + + + + + + + + + diff --git a/tests/bhyvexml2argvtest.c b/tests/bhyvexml2argvtest.c index cc6b17233d..0a5202e425 100644 --- a/tests/bhyvexml2argvtest.c +++ b/tests/bhyvexml2argvtest.c @@ -195,7 +195,8 @@ mymain(void) BHYVE_CAP_NET_E1000 | BHYVE_CAP_LPC_BOOTROM | \ BHYVE_CAP_FBUF | BHYVE_CAP_XHCI | \ BHYVE_CAP_CPUTOPOLOGY | BHYVE_CAP_SOUND_HDA | \ - BHYVE_CAP_VNC_PASSWORD | BHYVE_CAP_VIRTIO_9P; + BHYVE_CAP_VNC_PASSWORD | BHYVE_CAP_VIRTIO_9P | \ + BHYVE_CAP_NVME; =20 DO_TEST("base"); DO_TEST("wired"); @@ -259,6 +260,7 @@ mymain(void) DO_TEST("serial-tcp"); DO_TEST("4-consoles"); DO_TEST_FAILURE("serial-invalid-port"); + DO_TEST("nvme"); =20 /* Address allocation tests */ DO_TEST("addr-single-sata-disk"); diff --git a/tests/bhyvexml2xmloutdata/bhyvexml2xmlout-nvme.xml b/tests/bhy= vexml2xmloutdata/bhyvexml2xmlout-nvme.xml new file mode 100644 index 0000000000..d4ab69b15f --- /dev/null +++ b/tests/bhyvexml2xmloutdata/bhyvexml2xmlout-nvme.xml @@ -0,0 +1,27 @@ + + bhyve + df3be7e7-a104-11e3-aeb0-50e5492bd3dc + 219136 + 219136 + 1 + + hvm + + + + destroy + restart + destroy + + + + + +
+ + + +
+ + + diff --git a/tests/bhyvexml2xmltest.c b/tests/bhyvexml2xmltest.c index df093a5539..be69e21484 100644 --- a/tests/bhyvexml2xmltest.c +++ b/tests/bhyvexml2xmltest.c @@ -117,6 +117,7 @@ mymain(void) DO_TEST_DIFFERENT("virtio-rnd"); DO_TEST_DIFFERENT("serial-tcp"); DO_TEST_DIFFERENT("4-consoles"); + DO_TEST_DIFFERENT("nvme"); =20 /* Address allocation tests */ DO_TEST_DIFFERENT("addr-single-sata-disk"); --=20 2.51.0