From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 6C36E43F12; Fri, 26 Apr 2024 09:50:07 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 6FDBA43CA9; Fri, 26 Apr 2024 09:49:15 +0200 (CEST) Received: from NAM04-BN8-obe.outbound.protection.outlook.com (mail-bn8nam04on2109.outbound.protection.outlook.com [40.107.100.109]) by mails.dpdk.org (Postfix) with ESMTP id F287443CA0 for ; Fri, 26 Apr 2024 09:49:11 +0200 (CEST) ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=eHJzUeMaGksPCwzJjIy21/boja5XiU+fvR7Q4Bol8OeeRrG1HFs2DBoHNYoJpP+FPv5tMjOxWaBtzauGMkYLfRXCmblUyXCxQCjmYRz0MAViGTqXQixbrZWwMBUmAJZ7vfmpbqWtAuJUq4uE0Zc1ftTGJJTgAIgJ2/vkRJ3mDvi37yh34T1kFXrfX7Be4gzJurYa6nYKbWZ/hX7BNfxuc6U1VWN4zFEye2c2SiL85k/0V9tcpz0HjFM0RJd2gp6G1HW5QNmeijNQMDNUTk1R9PVsfg2uggfBYATDNqbYaRhv/DQvI43kiM8+vrYVnfD5cwiwfpBHqnKIfXmjioHLSg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=xMxuQt0p9O/CFuebIu4mFZrN+7slowqspnyCc6Y2VBw=; b=OR9DMha04bKyhp63VHHGL3iPSS0jek+J8Z9lpYe77Sknn1HhZ477npiStS4kxiix/jHhvTt7Rku/0mYGcfgpH+23VrDUzgmHHE2HhitC8BnTgiAIoXDpiVIChwpJCPQECS3yaKHH8PL5u/lTows229yi1TaIikiBpxAGbVR0d4jNzr1zgGoQLm1cx46LLGWyZoIU+SyibcH0NQY9/srbM8DuqdTB8M5jON21a5MI0LRHM/FTLyxlaondGuKbqdE77jB36GhOOiNt3OBokz0veBRa14HA9ycidhALNkKMAFVc3hMTzYWLCJ7pnIBSDmeBwzQ8KHcA2l0P1P+otVytrg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=corigine.com; dmarc=pass action=none header.from=corigine.com; dkim=pass header.d=corigine.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=corigine.onmicrosoft.com; s=selector2-corigine-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=xMxuQt0p9O/CFuebIu4mFZrN+7slowqspnyCc6Y2VBw=; b=lZJFr4/AhaffxorcBLuqVAaOGcI5DBe+naali6JrGzdL5WB76+if6fDMXMSC2SzL/4xrxVzwYVU/oDylUHN0ptGeWjg/Ra15TKNPRVt7mTxknwAfff6iUG9tK0g8VCAcJL7mtnSOmfmiF0+Duh9709dwZbIiDVYNocSMSR03Sjg= Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=corigine.com; Received: from SJ0PR13MB5545.namprd13.prod.outlook.com (2603:10b6:a03:424::5) by MN0PR13MB6618.namprd13.prod.outlook.com (2603:10b6:208:4c3::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7544.11; Fri, 26 Apr 2024 07:49:10 +0000 Received: from SJ0PR13MB5545.namprd13.prod.outlook.com ([fe80::ec12:7411:559a:850e]) by SJ0PR13MB5545.namprd13.prod.outlook.com ([fe80::ec12:7411:559a:850e%5]) with mapi id 15.20.7544.010; Fri, 26 Apr 2024 07:49:10 +0000 From: Chaoyong He To: dev@dpdk.org Cc: oss-drivers@corigine.com, Xinying Yu , Chaoyong He , Long Wu , Peng Zhang Subject: [PATCH 09/10] vdpa/nfp: setup vring relay thread Date: Fri, 26 Apr 2024 15:48:30 +0800 Message-Id: <20240426074831.1729792-10-chaoyong.he@corigine.com> X-Mailer: git-send-email 2.39.1 In-Reply-To: <20240426074831.1729792-1-chaoyong.he@corigine.com> References: <20240426074831.1729792-1-chaoyong.he@corigine.com> Content-Transfer-Encoding: 8bit Content-Type: text/plain X-ClientProxiedBy: BYAPR02CA0007.namprd02.prod.outlook.com (2603:10b6:a02:ee::20) To SJ0PR13MB5545.namprd13.prod.outlook.com (2603:10b6:a03:424::5) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: SJ0PR13MB5545:EE_|MN0PR13MB6618:EE_ X-MS-Office365-Filtering-Correlation-Id: 2ba9b761-b6e1-4d6f-b877-08dc65c55bd3 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230031|366007|376005|52116005|1800799015|38350700005; X-Microsoft-Antispam-Message-Info: =?us-ascii?Q?aNxu3NySWW5GQDyPL+2O7FN6X65CFKdJacLJfkT/PxUpV5Vu2FnH7bPuGhmv?= =?us-ascii?Q?eH3HMRB237NrfPXDB66ssOfxaW1QexeYYpR+jtMvkS48f4dtm3H4oIDBbihf?= =?us-ascii?Q?qoPTpihj3Y0jHKYhHTrNYGNTiHgfCP5bWVfDlKCBbuBTcr556lnbHCW8xv3g?= =?us-ascii?Q?jRAs0Xq1rymOqi/w2SxAkOx7lthrqTR3D87GIschLGVBtbCEoKGTuUVkfBgV?= =?us-ascii?Q?kUMDYOXt5QqtZS5Tm2RFnB5HcdsE0pi4ZzYuALOlRHjTMMBA0f/IhfnVUh7O?= =?us-ascii?Q?j65FeEnW7gsfJIObgxpkHK5Rp9PblfwolR2AvcxKF0EoPEehUU/zmg7qPvIq?= =?us-ascii?Q?OKMqkSB1WsosTrExS7L8sw/67OFDYXSr7UCd63+w7ZGecWikgNLqeSEv+qg2?= =?us-ascii?Q?m8gKF5AwkfCX/MVoXwaEP6aUgHR4UYm9a4wjaNKYO3nlRPYbCBoHTFzpC20U?= =?us-ascii?Q?fhbr18+vH/0NxbBv6mItYw9cuaV2sTvMJp3NHAMZCZIXJnaaiRJrk5XzD58s?= =?us-ascii?Q?gxqSoekeajNYgK3tBke//k2ZTQNf+H8PYNj7YQQNSSETcKlgd1t0F5slnvW1?= =?us-ascii?Q?Nxm2XfftCnY9HGbKfi6vcjaqHiXAU6pI1gTw0+xn6h6u9ct0ts3iQNfbgU/8?= =?us-ascii?Q?4m1li6ZRmHyCw6QfHcpSxBf1dxJWDEnp7yzCBDljq2Y33Tw0z+mfkEfzQyq6?= =?us-ascii?Q?zsyp65sHPjMfCFqRHpTf11oBc5WjtdHbORuUS26oiBgxJWpwAfIu/xZUs/zD?= =?us-ascii?Q?YCPlRRZM+YvZApf4Zjl487SbF9cYor90220L2MOVLuOO/qj++iGnSYr7WDnI?= =?us-ascii?Q?4QRLhAb5TnZO6k2DAhOmAsepOwAQyjRVfkh4DC7Pxe5tecRhAD5o2D1iWD/H?= =?us-ascii?Q?AzM3pMwM9WAHo7WbjneY3R6nffXLSqy7JfH6tVi0xMPMvJ8VJ+6kYrGUfXuV?= =?us-ascii?Q?feeV3wT+DbLHCF98DMnU2liLgS7Agr3UHryI20Mx87Rc7CaTdcn6+RKJyJWJ?= =?us-ascii?Q?KKlxCbTZrcPR3bzE2Yv+fgivkgNuQQIMqeX+ehlbtrbN2bcizbV6Q1N5cHys?= =?us-ascii?Q?kkLbwTJ7/C/cBBvS2tT4R5KzRaS2sMEaz+TJTnDOItiMj6qwx5VeepXL/Igf?= =?us-ascii?Q?M1fAgqNlTyqfyWsCls38v/ZWVMyYnIv0v43XpNehU0eCIsUovzAQd1djoLDd?= =?us-ascii?Q?rbSlV/Z404i6+JuzLiJ1d5KRbFbqYNYqgy/Lzbrbzn8l74SPXS/VowzR2ffQ?= =?us-ascii?Q?D/fpquIsRCZpYpyVVQ7UL7nMKkpQcHdGhFuhCLPbZQuosWz+fFCYG58++4N8?= =?us-ascii?Q?h6ibQdFvRpCF4fx4IRWndAUcFbrB4y83QiNAosD+upg3vA=3D=3D?= X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:SJ0PR13MB5545.namprd13.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230031)(366007)(376005)(52116005)(1800799015)(38350700005); DIR:OUT; SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?us-ascii?Q?khHpFnjqwYXerjcpZci7cvr3SiRjeGG419dnovvSpLe3swpSKzFErmohSxt/?= =?us-ascii?Q?tNgRE4wNYtqBdgGa0cQu0fSxKmG0SSQ+vB2Jw91bXOKPnMHxkGInPHaIw5v1?= =?us-ascii?Q?ALBWlsXtpTUIrjpBEEIznuoCbpmgSQeoPY3tC6NqYsiNX59VUNIxtnqNmL5c?= =?us-ascii?Q?sYwReiwW8O2sF4CyVZ0YTrjgmz4UD2If+nKkZnadtxn9ojpmCmif6LMMteeT?= =?us-ascii?Q?9cumsA0NMDnurZqQyZEKyJ5PrFk/0Z9EsvIAd+Ems3ThE1yRKAPlfZUzclo5?= =?us-ascii?Q?nctgdZAS/tyejz0DPZPyhIVTMTMqono3TiE6HjaqnLtU3DiaCwRR30HRUNJP?= =?us-ascii?Q?G38gKwseP7VbTavPDkTGYUJUUZ5mFJHws9y5hOjOSaQ/Jc2kab2UApgZLV5I?= =?us-ascii?Q?9uXCSK0wpJTQLOm+Edl0aQiUJkoBgZBAn/TuXx8/IbshCEDHlMwCyMo9iwbh?= =?us-ascii?Q?QuO11cLuwyMAlN/CJacwBDL/llSNjPv/xg10qs2RA25ariG9+BWP4xB/PKxt?= =?us-ascii?Q?H4EyqRBQEpWlDhPEf38n0aVkrtOhP9TUx4CS7DGe1c6nFuNLe1M3OKDA2rXV?= =?us-ascii?Q?v31+hhPGz0hiei9R22fMfftDEDJF2by8iK6p8w/JCKNbVMb2iNiJDF2i26Do?= =?us-ascii?Q?srBJPbasq//gUIAX9jhWZ+L6fAvy3inMAxragfTqi8WfVrMHM7TyW1xS/B91?= =?us-ascii?Q?BOwxD/Y4dt67NBhU9SLA04ZmJ7va9mAChEulmrII2/7HiXctfzQCFOaFJIu6?= =?us-ascii?Q?AptZ6wN1gkOfYksKOHFu6gGJ6+lJHkYzjafXwyafhpdOaP+LrxM+WU3ETafQ?= =?us-ascii?Q?OpuDQBthO05ERKElnrIMJWeyMUoADEkty7zn65N6d/0G4BkHTxlLOvFNkXuP?= =?us-ascii?Q?F2DLTjs+87SaYx7cxAHfMrTJoGb/d4hI917AbuosLuZTHjfWfTbvSIPUhEKW?= =?us-ascii?Q?JwPRQmXRisJZs/krS0IECyTKbqC57KfUUQRznGG1MuVeWt9vcr1WHqVYccP4?= =?us-ascii?Q?McZWJ+3jvsdjnZKSGB0HUQcZWgKUinQMAO/LkvyxnV0WMxiheSqe7j4P6ls7?= =?us-ascii?Q?Euji6miV0IJ4Q0pBgCXLAGsj7Ri33rKy2IcrhLu3sS0anEiEHC3rheFeDyEM?= =?us-ascii?Q?gv6eHaAZH4GSf3blN5QY/cYsoZYIJ51DUn3tLsODKWk/nguAlg9pzuwhaF+c?= =?us-ascii?Q?Y/7fhpluj8wLAyaS4y3k80p6T+u+pOW8XIRSFrgL6DNYJzsvjOKWKZh2SG7g?= =?us-ascii?Q?tnSVm3rFRmUukf5ymn94QvFtrovs26Ibc95w6Yvj3QydmnlqLw4yefbgkfIY?= =?us-ascii?Q?+hLUX3CAsaGmM/M3ocvs2fflmU0Nev1lbfcQedPHBW/mZAqg88HIZFcQQ4P4?= =?us-ascii?Q?jWqLW0DtYssJn/xQacy9qN9h6o3LOZWEV8ePM/SYirhesI5/N7T+cB6JggN2?= =?us-ascii?Q?ZeLJml6lZj7ROpl5h60sFI5B5+6Wu7mpBU4KWKXmvw3f/j6UyiwXtvvsDBJ2?= =?us-ascii?Q?OOryIO+7CzVzbLpO7pP/QI4pToVmCfn3GZLjFWC/H6iGdaoJEzDH1hzbyNvI?= =?us-ascii?Q?NimF+xfD1ArZlfgsw75CEdusr8LxzMJSdhSDJ8nhqNVGA8uy8hGsLzpLQzeS?= =?us-ascii?Q?9Q=3D=3D?= X-OriginatorOrg: corigine.com X-MS-Exchange-CrossTenant-Network-Message-Id: 2ba9b761-b6e1-4d6f-b877-08dc65c55bd3 X-MS-Exchange-CrossTenant-AuthSource: SJ0PR13MB5545.namprd13.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 26 Apr 2024 07:49:10.6045 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: fe128f2c-073b-4c20-818e-7246a585940c X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: aeq+ZzydjisJnry7rmvM9RN9JEIPtlEpw/fUkEyqz34aH4ymosx+0HPEIWF4bME+yZqSVbSpx/iQze74jwCaM80+y/g4L1fus+v59EMUHSY= X-MS-Exchange-Transport-CrossTenantHeadersStamped: MN0PR13MB6618 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org From: Xinying Yu Setup the vring relay thread to monitor the interruption from device. And do the dirty page logging or notify device according to event data. Signed-off-by: Xinying Yu Reviewed-by: Chaoyong He Reviewed-by: Long Wu Reviewed-by: Peng Zhang --- drivers/vdpa/nfp/nfp_vdpa.c | 148 +++++++++++++++++++++++++++++++ drivers/vdpa/nfp/nfp_vdpa_core.c | 9 ++ drivers/vdpa/nfp/nfp_vdpa_core.h | 2 + 3 files changed, 159 insertions(+) diff --git a/drivers/vdpa/nfp/nfp_vdpa.c b/drivers/vdpa/nfp/nfp_vdpa.c index 65f7144671..e57765eb1a 100644 --- a/drivers/vdpa/nfp/nfp_vdpa.c +++ b/drivers/vdpa/nfp/nfp_vdpa.c @@ -26,6 +26,8 @@ #define NFP_VDPA_USED_RING_LEN(size) \ ((size) * sizeof(struct vring_used_elem) + sizeof(struct vring_used)) +#define EPOLL_DATA_INTR 1 + struct nfp_vdpa_dev { struct rte_pci_device *pci_dev; struct rte_vdpa_device *vdev; @@ -776,6 +778,139 @@ update_datapath(struct nfp_vdpa_dev *device) return ret; } +static int +nfp_vdpa_vring_epoll_ctl(uint32_t queue_num, + struct nfp_vdpa_dev *device) +{ + int ret; + uint32_t qid; + struct epoll_event ev; + struct rte_vhost_vring vring; + + for (qid = 0; qid < queue_num; qid++) { + ev.events = EPOLLIN | EPOLLPRI; + rte_vhost_get_vhost_vring(device->vid, qid, &vring); + ev.data.u64 = qid << 1 | (uint64_t)vring.kickfd << 32; + ret = epoll_ctl(device->epoll_fd, EPOLL_CTL_ADD, vring.kickfd, &ev); + if (ret < 0) { + DRV_VDPA_LOG(ERR, "Epoll add error for queue %u", qid); + return ret; + } + } + + /* vDPA driver interrupt */ + for (qid = 0; qid < queue_num; qid += 2) { + ev.events = EPOLLIN | EPOLLPRI; + /* Leave a flag to mark it's for interrupt */ + ev.data.u64 = EPOLL_DATA_INTR | qid << 1 | + (uint64_t)device->intr_fd[qid] << 32; + ret = epoll_ctl(device->epoll_fd, EPOLL_CTL_ADD, + device->intr_fd[qid], &ev); + if (ret < 0) { + DRV_VDPA_LOG(ERR, "Epoll add error for queue %u", qid); + return ret; + } + + nfp_vdpa_update_used_ring(device, qid); + } + + return 0; +} + +static int +nfp_vdpa_vring_epoll_wait(uint32_t queue_num, + struct nfp_vdpa_dev *device) +{ + int i; + int fds; + int kickfd; + uint32_t qid; + struct epoll_event events[NFP_VDPA_MAX_QUEUES * 2]; + + for (;;) { + fds = epoll_wait(device->epoll_fd, events, queue_num * 2, -1); + if (fds < 0) { + if (errno == EINTR) + continue; + + DRV_VDPA_LOG(ERR, "Epoll wait fail"); + return -EACCES; + } + + for (i = 0; i < fds; i++) { + qid = events[i].data.u32 >> 1; + kickfd = (uint32_t)(events[i].data.u64 >> 32); + + nfp_vdpa_read_kickfd(kickfd); + if ((events[i].data.u32 & EPOLL_DATA_INTR) != 0) { + nfp_vdpa_update_used_ring(device, qid); + nfp_vdpa_irq_unmask(&device->hw); + } else { + nfp_vdpa_notify_queue(&device->hw, qid); + } + } + } + + return 0; +} + +static uint32_t +nfp_vdpa_vring_relay(void *arg) +{ + int ret; + int epoll_fd; + uint16_t queue_id; + uint32_t queue_num; + struct nfp_vdpa_dev *device = arg; + + epoll_fd = epoll_create(NFP_VDPA_MAX_QUEUES * 2); + if (epoll_fd < 0) { + DRV_VDPA_LOG(ERR, "failed to create epoll instance."); + return 1; + } + + device->epoll_fd = epoll_fd; + + queue_num = rte_vhost_get_vring_num(device->vid); + + ret = nfp_vdpa_vring_epoll_ctl(queue_num, device); + if (ret != 0) + goto notify_exit; + + /* Start relay with a first kick */ + for (queue_id = 0; queue_id < queue_num; queue_id++) + nfp_vdpa_notify_queue(&device->hw, queue_id); + + ret = nfp_vdpa_vring_epoll_wait(queue_num, device); + if (ret != 0) + goto notify_exit; + + return 0; + +notify_exit: + close(device->epoll_fd); + device->epoll_fd = -1; + + return 1; +} + +static int +nfp_vdpa_setup_vring_relay(struct nfp_vdpa_dev *device) +{ + int ret; + char name[RTE_THREAD_INTERNAL_NAME_SIZE]; + + snprintf(name, sizeof(name), "nfp_vring%d", device->vid); + ret = rte_thread_create_internal_control(&device->tid, name, + nfp_vdpa_vring_relay, (void *)device); + if (ret != 0) { + DRV_VDPA_LOG(ERR, "Failed to create vring relay pthread."); + return -EPERM; + } + + return 0; +} + static int nfp_vdpa_sw_fallback(struct nfp_vdpa_dev *device) { @@ -802,10 +937,17 @@ nfp_vdpa_sw_fallback(struct nfp_vdpa_dev *device) if (ret != 0) goto unset_intr; + /* Setup vring relay thread */ + ret = nfp_vdpa_setup_vring_relay(device); + if (ret != 0) + goto stop_vf; + device->hw.sw_fallback_running = true; return 0; +stop_vf: + nfp_vdpa_stop(device, true); unset_intr: nfp_vdpa_disable_vfio_intr(device); error: @@ -859,6 +1001,12 @@ nfp_vdpa_dev_close(int vid) /* Reset VF */ nfp_vdpa_stop(device, true); + /* Remove interrupt setting */ + nfp_vdpa_disable_vfio_intr(device); + + /* Unset DMA map for guest memory */ + nfp_vdpa_dma_map(device, false); + device->hw.sw_fallback_running = false; rte_atomic_store_explicit(&device->dev_attached, 0, diff --git a/drivers/vdpa/nfp/nfp_vdpa_core.c b/drivers/vdpa/nfp/nfp_vdpa_core.c index 8f9aba9519..70aeb4a3ac 100644 --- a/drivers/vdpa/nfp/nfp_vdpa_core.c +++ b/drivers/vdpa/nfp/nfp_vdpa_core.c @@ -271,3 +271,12 @@ nfp_vdpa_notify_queue(struct nfp_vdpa_hw *vdpa_hw, nfp_qcp_notify_ptr_add(vdpa_hw->notify_addr[qid], NFP_QCP_NOTIFY_WRITE_PTR, qid); } + +void nfp_vdpa_irq_unmask(struct nfp_vdpa_hw *vdpa_hw) +{ + struct nfp_hw *hw = &vdpa_hw->super; + + /* Make sure all updates are written before un-masking */ + rte_wmb(); + nn_cfg_writeb(hw, NFP_NET_CFG_ICR(1), NFP_NET_CFG_ICR_UNMASKED); +} diff --git a/drivers/vdpa/nfp/nfp_vdpa_core.h b/drivers/vdpa/nfp/nfp_vdpa_core.h index a339ace601..bc4db556a2 100644 --- a/drivers/vdpa/nfp/nfp_vdpa_core.h +++ b/drivers/vdpa/nfp/nfp_vdpa_core.h @@ -60,4 +60,6 @@ void nfp_vdpa_notify_queue(struct nfp_vdpa_hw *vdpa_hw, uint16_t qid); uint64_t nfp_vdpa_get_queue_notify_offset(struct nfp_vdpa_hw *vdpa_hw, int qid); +void nfp_vdpa_irq_unmask(struct nfp_vdpa_hw *vdpa_hw); + #endif /* __NFP_VDPA_CORE_H__ */ -- 2.39.1