Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2218458/?format=api
{ "id": 2218458, "url": "http://patchwork.ozlabs.org/api/patches/2218458/?format=api", "web_url": "http://patchwork.ozlabs.org/project/openvswitch/patch/20260401091318.2671624-12-elibr@nvidia.com/", "project": { "id": 47, "url": "http://patchwork.ozlabs.org/api/projects/47/?format=api", "name": "Open vSwitch", "link_name": "openvswitch", "list_id": "ovs-dev.openvswitch.org", "list_email": "ovs-dev@openvswitch.org", "web_url": "http://openvswitch.org/", "scm_url": "git@github.com:openvswitch/ovs.git", "webscm_url": "https://github.com/openvswitch/ovs", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20260401091318.2671624-12-elibr@nvidia.com>", "list_archive_url": null, "date": "2026-04-01T09:13:18", "name": "[ovs-dev,v3,11/11] netdev-doca: Introduce doca netdev.", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "bab1f03588edb4fb8d78554d363560bc73c917db", "submitter": { "id": 79848, "url": "http://patchwork.ozlabs.org/api/people/79848/?format=api", "name": "Eli Britstein", "email": "elibr@nvidia.com" }, "delegate": { "id": 75123, "url": "http://patchwork.ozlabs.org/api/users/75123/?format=api", "username": "echaudron", "first_name": "Eelco", "last_name": "Chaudron", "email": "echaudro@redhat.com" }, "mbox": "http://patchwork.ozlabs.org/project/openvswitch/patch/20260401091318.2671624-12-elibr@nvidia.com/mbox/", "series": [ { "id": 498297, "url": "http://patchwork.ozlabs.org/api/series/498297/?format=api", "web_url": "http://patchwork.ozlabs.org/project/openvswitch/list/?series=498297", "date": "2026-04-01T09:13:07", "name": "netdev-doca", "version": 3, "mbox": "http://patchwork.ozlabs.org/series/498297/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2218458/comments/", "check": "success", "checks": "http://patchwork.ozlabs.org/api/patches/2218458/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<ovs-dev-bounces@openvswitch.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "dev@openvswitch.org" ], "Delivered-To": [ "patchwork-incoming@legolas.ozlabs.org", "ovs-dev@lists.linuxfoundation.org" ], "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n unprotected) header.d=Nvidia.com header.i=@Nvidia.com header.a=rsa-sha256\n header.s=selector2 header.b=hOjWcZw3;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::137; helo=smtp4.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)", "smtp4.osuosl.org;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key,\n unprotected) header.d=Nvidia.com header.i=@Nvidia.com header.a=rsa-sha256\n header.s=selector2 header.b=hOjWcZw3", "smtp4.osuosl.org;\n dmarc=pass (p=reject dis=none) header.from=nvidia.com" ], "Received": [ "from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4flzrd57fTz1yGH\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 01 Apr 2026 20:16:49 +1100 (AEDT)", "from localhost (localhost [127.0.0.1])\n\tby smtp4.osuosl.org (Postfix) with ESMTP id 5CB1841031;\n\tWed, 1 Apr 2026 09:16:48 +0000 (UTC)", "from smtp4.osuosl.org ([127.0.0.1])\n by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id pubMoLfPS5H4; Wed, 1 Apr 2026 09:16:45 +0000 (UTC)", "from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])\n\tby smtp4.osuosl.org (Postfix) with ESMTPS id B290141114;\n\tWed, 1 Apr 2026 09:16:45 +0000 (UTC)", "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id 7271BC0070;\n\tWed, 1 Apr 2026 09:16:45 +0000 (UTC)", "from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 8BF58C003D\n for <dev@openvswitch.org>; Wed, 1 Apr 2026 09:16:43 +0000 (UTC)", "from localhost (localhost [127.0.0.1])\n by smtp4.osuosl.org (Postfix) with ESMTP id 14FCB41112\n for <dev@openvswitch.org>; Wed, 1 Apr 2026 09:15:41 +0000 (UTC)", "from smtp4.osuosl.org ([127.0.0.1])\n by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id pdtJcno00xOn for <dev@openvswitch.org>;\n Wed, 1 Apr 2026 09:15:38 +0000 (UTC)", "from CH5PR02CU005.outbound.protection.outlook.com\n (mail-northcentralusazlp170120005.outbound.protection.outlook.com\n [IPv6:2a01:111:f403:c105::5])\n by smtp4.osuosl.org (Postfix) with ESMTPS id 800714110B\n for <dev@openvswitch.org>; Wed, 1 Apr 2026 09:15:37 +0000 (UTC)", "from PH7P220CA0048.NAMP220.PROD.OUTLOOK.COM (2603:10b6:510:32b::19)\n by SJ2PR12MB8876.namprd12.prod.outlook.com (2603:10b6:a03:539::18)\n with Microsoft SMTP Server (version=TLS1_2,\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9769.10; Wed, 1 Apr\n 2026 09:15:16 +0000", "from CY4PEPF0000E9DA.namprd05.prod.outlook.com\n (2603:10b6:510:32b:cafe::83) by PH7P220CA0048.outlook.office365.com\n (2603:10b6:510:32b::19) with Microsoft SMTP Server (version=TLS1_3,\n cipher=TLS_AES_256_GCM_SHA384) id 15.20.9745.29 via Frontend Transport; Wed,\n 1 Apr 2026 09:15:08 +0000", "from mail.nvidia.com (216.228.117.160) by\n CY4PEPF0000E9DA.mail.protection.outlook.com (10.167.241.73) with Microsoft\n SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id\n 15.20.9769.17 via Frontend Transport; Wed, 1 Apr 2026 09:15:15 +0000", "from rnnvmail201.nvidia.com (10.129.68.8) by mail.nvidia.com\n (10.129.200.66) with Microsoft SMTP Server (version=TLS1_2,\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.20; Wed, 1 Apr\n 2026 02:14:59 -0700", "from nvidia.com (10.126.231.35) by rnnvmail201.nvidia.com\n (10.129.68.8) with Microsoft SMTP Server (version=TLS1_2,\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.20; Wed, 1 Apr\n 2026 02:14:55 -0700" ], "X-Virus-Scanned": [ "amavis at osuosl.org", "amavis at osuosl.org" ], "X-Comment": "SPF check N/A for local connections - client-ip=140.211.9.56;\n helo=lists.linuxfoundation.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN> ", "DKIM-Filter": [ "OpenDKIM Filter v2.11.0 smtp4.osuosl.org B290141114", "OpenDKIM Filter v2.11.0 smtp4.osuosl.org 800714110B" ], "Received-SPF": [ "Pass (mailfrom) identity=mailfrom;\n client-ip=2a01:111:f403:c105::5;\n helo=ch5pr02cu005.outbound.protection.outlook.com;\n envelope-from=elibr@nvidia.com; receiver=<UNKNOWN>", "Pass (protection.outlook.com: domain of nvidia.com designates\n 216.228.117.160 as permitted sender) receiver=protection.outlook.com;\n client-ip=216.228.117.160; helo=mail.nvidia.com; pr=C" ], "DMARC-Filter": "OpenDMARC Filter v1.4.2 smtp4.osuosl.org 800714110B", "ARC-Seal": "i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none;\n b=njH6lmCLucv34+cxIKBrtugqcWeAPXy5qn3WFuHeXdtUz/wy0XapAezWchKz/A7oOZi5wbdT+z7w13YVerrxEli8dNEgQIgiQVFSi84tXY5YcY1SzSjyGfk0YeSK4WHYl2sz8IaNQRNLNzAefi1b1L3McNsKA09Titw8wwvtzTashOrkzaxaUGl1qwiJz0pJIl/YDs5CgWO6MgtBnUHWhHMDdz+TmEYl73CQ/v4jXRZXG2nzFdcEKeZDQkpA6eMRYyl1FqaEybsnykqKAwDmQccY7ddNmRKCgzd95ZBZQjqVtskzxx0BhuY2tZAaF8uVxWvgK9USQBiotMKg/DhkEw==", "ARC-Message-Signature": "i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;\n s=arcselector10001;\n 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;\n bh=YVb8U5Hn9qFq0B4onD4x6FXiKL9aGUkUlKn59dV4FJ0=;\n b=qsYA5o7ughe2vZK5ElFQhXqdZsfnVSiGzKpSAyjHTj3Rs6tPhLq8ZZHShv7Xr7rSa3Gy28tHR72iQe7SrGAiNkG9Ao6cj1mmIRQEvDS+1KA7WKkluRX0nSDUhA8CnSdIt4co5bEYwtmbfB8zsKxcf3RpMwPg8VWe6/xJFYJgM8feFI8OTWerIVeD/WvgT1voTv03E7jNyg4VH1UWIEOAP6Ezpi4qEhMb0EjybFC2rGXwZAn8RGfk78CZMB9nw2LjkZh1SJzytW+BLtBsIyAHYzFF84eAZmiDOa5GvPw315hSJqKiw7jLYb+OE0zUXM7shuxgZF4lsl35WNdFuMx9fg==", "ARC-Authentication-Results": "i=1; mx.microsoft.com 1; spf=pass (sender ip is\n 216.228.117.160) smtp.rcpttodomain=openvswitch.org smtp.mailfrom=nvidia.com;\n dmarc=pass (p=reject sp=reject pct=100) action=none header.from=nvidia.com;\n dkim=none (message not signed); arc=none (0)", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=Nvidia.com;\n s=selector2;\n h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;\n bh=YVb8U5Hn9qFq0B4onD4x6FXiKL9aGUkUlKn59dV4FJ0=;\n b=hOjWcZw3CIYuSZCJykEy3Xao1wqizZzIK19j6bl7oQSlT9XuAQj3dYjdfARY3J2OTYIYB5+f7ubdOf4XkDKUSGXc/pEGAYuaVwhVpGHHYtox+V67I8kjEgJ6NczCIrE9pL4Bn+wXmtBkezMrTFX4PN+bBLiTbOKa1trPVjUXJ2VPMvcVKjbGhi31FpF4ro+mZ2CScribde7RZkOD+UV6c81FDfu9PRyZIwFhFrkl0Bgp+NpYuMAFqYu8GD4LMVbRp6QEHlWn1iDzV+81QOrLWb5/82oQeLp7EHM11gFPrs82eO3hbT6yr2i6AlYyZQDf6aedm6LXO4WII6UHFj0RYg==", "X-MS-Exchange-Authentication-Results": "spf=pass (sender IP is 216.228.117.160)\n smtp.mailfrom=nvidia.com;\n dkim=none (message not signed)\n header.d=none;dmarc=pass action=none header.from=nvidia.com;", "From": "Eli Britstein <elibr@nvidia.com>", "To": "<dev@openvswitch.org>", "Date": "Wed, 1 Apr 2026 12:13:18 +0300", "Message-ID": "<20260401091318.2671624-12-elibr@nvidia.com>", "X-Mailer": "git-send-email 2.34.1", "In-Reply-To": "<20260401091318.2671624-1-elibr@nvidia.com>", "References": "<20260401091318.2671624-1-elibr@nvidia.com>", "MIME-Version": "1.0", "X-Originating-IP": "[10.126.231.35]", "X-ClientProxiedBy": "rnnvmail203.nvidia.com (10.129.68.9) To\n rnnvmail201.nvidia.com (10.129.68.8)", "X-EOPAttributedMessage": "0", "X-MS-PublicTrafficType": "Email", "X-MS-TrafficTypeDiagnostic": "CY4PEPF0000E9DA:EE_|SJ2PR12MB8876:EE_", "X-MS-Office365-Filtering-Correlation-Id": "30c7b794-5cbf-437e-abb9-08de8fcf2fe1", "X-MS-Exchange-SenderADCheck": "1", "X-MS-Exchange-AntiSpam-Relay": "0", "X-Microsoft-Antispam": "BCL:0;\n ARA:13230040|376014|82310400026|1800799024|36860700016|13003099007|18092099006|22082099003|18002099003|56012099003;", "X-Microsoft-Antispam-Message-Info": "\n 52UPJXPrMfRT8h8ZL4raSWvUxpUje/GRQHWsTgM+j1tR0GYeanDStdbOQOr+IeM7UE3daL6D8Ayggue6csD8drXTphcAaZSwCMuTLgepkVbvPRbh+X99DtI4iI2GfX1Tx3vTjWE9nV/TC5M9N/zkU78YFAFMjXqXC6ATLbnLGTDG0mRbqoIW1X9JmPv8NsLSP1pqpRvqUSMBUbcyV59+ghHLBxNViapn7JcUr/0vheJmXwQkB0vc9IWrUL0IgS+NqoDXDEFWLHjzTPjsWEEbiORXWkEagwuXwlunkoDN31hewqYUz+AkIUIBdD9eRnnAd2Vc2XUX9UiPHNLDfizo+TL/9PYXdPRvHE4DRKZqIs+D8CZVS4v2lAtXOMrhtysK0ZkcM9Q5KNyhTvYAb8pH1wCX4+z87G99JmPCo863BTuSqOM8UQNmW9KPlq62h351ovkF84fTXomJevMEOvvAjRgQ38u8/y+ks/9BEpBYBlSlfLTIiFV3UsfPx9Voa3sABF/cTAYrZw6UH/jGNHBy2cEzfiY0YSe9i+J0weJDPVOcFBPQX/1em3Gecl9mmiT9Qx8SDm7i82uhwhsiSB1AVNAfExrcRvnSVkc6bnK0VPYLyAWMQeM+Z9M2v9YeOdGEw0JNUGkAdeRU+Xc7NI8UxyZ5fvsKvhWTBEYNrggaexCJUYQj5VKg+b/k+6bBm6UAKQky5kfBLqfuYiZ0YrOSSb62w7MhMqg9+h/0V1TEWkkrgboECAR0de+aAzfHtVNOvg6pjHY89oVD3dXKrJdeWQ==", "X-Forefront-Antispam-Report": "CIP:216.228.117.160; CTRY:US; LANG:en; SCL:1;\n SRV:;\n IPV:NLI; SFV:NSPM; H:mail.nvidia.com; PTR:dc6edge1.nvidia.com; CAT:NONE;\n SFS:(13230040)(376014)(82310400026)(1800799024)(36860700016)(13003099007)(18092099006)(22082099003)(18002099003)(56012099003);\n DIR:OUT; SFP:1101;", "X-MS-Exchange-AntiSpam-MessageData-ChunkCount": "1", "X-MS-Exchange-AntiSpam-MessageData-0": "\n nko/ATWKXCFmutU59YAwGdUU39gxTZ6aarlh0x/PtdJi4PLL90Q8NkYtt6L3YaSTSVrA3RylfE3Dqpo77P7Asr+9n9xg2usp+UERrdHkgDzcAdLoRdXhir8QBvuSA2SLxqgTJC9F0a8A1saVpJvfHqKmeuB7G43FvnqrYVcmU1Dg2YhMOcT/Ff5ifbuleXqQol/Xw2DTArocVRa8gjHLy8aasanZ0X2I7S3Jznrj40j1bNL+gEodLnLCHd1qyZ5ExQd79lYYhkfE8L6oh/zGGHM1kJRqArVhBy0Fi9qcLYf8vhsH79U+864yk4BnPxPhX8ZiZLOtHIw3us9VItW0apEB7ZZrKuS5kEoMjPHWnEX2CLsr+NVMwjc1Kszu8PTyrRXGnpDfgi1/nqIIrLnZ73v7abDQ547kxoxrRxit1amU94L2Yq64aOBV5PDu7PK1", "X-OriginatorOrg": "Nvidia.com", "X-MS-Exchange-CrossTenant-OriginalArrivalTime": "01 Apr 2026 09:15:15.6070 (UTC)", "X-MS-Exchange-CrossTenant-Network-Message-Id": "\n 30c7b794-5cbf-437e-abb9-08de8fcf2fe1", "X-MS-Exchange-CrossTenant-Id": "43083d15-7273-40c1-b7db-39efd9ccc17a", "X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp": "\n TenantId=43083d15-7273-40c1-b7db-39efd9ccc17a; Ip=[216.228.117.160];\n Helo=[mail.nvidia.com]", "X-MS-Exchange-CrossTenant-AuthSource": "\n CY4PEPF0000E9DA.namprd05.prod.outlook.com", "X-MS-Exchange-CrossTenant-AuthAs": "Anonymous", "X-MS-Exchange-CrossTenant-FromEntityHeader": "HybridOnPrem", "X-MS-Exchange-Transport-CrossTenantHeadersStamped": "SJ2PR12MB8876", "Subject": "[ovs-dev] [PATCH v3 11/11] netdev-doca: Introduce doca netdev.", "X-BeenThere": "ovs-dev@openvswitch.org", "X-Mailman-Version": "2.1.30", "Precedence": "list", "List-Id": "<ovs-dev.openvswitch.org>", "List-Unsubscribe": "<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>", "List-Archive": "<http://mail.openvswitch.org/pipermail/ovs-dev/>", "List-Post": "<mailto:ovs-dev@openvswitch.org>", "List-Help": "<mailto:ovs-dev-request@openvswitch.org?subject=help>", "List-Subscribe": "<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=subscribe>", "Cc": "Eli Britstein <elibr@nvidia.com>, Ilya Maximets <i.maximets@ovn.org>,\n David Marchand <david.marchand@redhat.com>, Maor Dickman <maord@nvidia.com>", "Content-Type": "text/plain; charset=\"us-ascii\"", "Content-Transfer-Encoding": "7bit", "Errors-To": "ovs-dev-bounces@openvswitch.org", "Sender": "\"dev\" <ovs-dev-bounces@openvswitch.org>" }, "content": "Introduce a new netdev type - \"doca\".\nThe code is placed in new files.\n- ovs-doca: initialization of doca library and utility functions that\n are used currently by netdev-doca and also will be used for future\n hw-offload code.\n- netdev-doca: implementation of the new netdev.\n\nSupported ports are mlx5 ports in switch-dev mode only that with a NIC\nthat supports hw-steering.\n\nThe netdev has the concept of ESW manager. A representor port is\nfunctional only if its ESW manager is attached to OVS. In case it is\nnot, the representor appears as functional in ovs-vsctl show, but it is\nnot. Upon initializing of an ESW manager port, each representor is\nreconfigured to be functional, and upon destruction, they are first stopped.\n\nSteering infrastructure:\n- RX packets of all ports are steered to a common queue. This queue is\n polled using dpdk API and the packets are classified to a per-port\n memory structure.\n- TX packets are marked with the target port as metadata and sent to a\n common queue. The egress pipe matches on the metadata and forwards the\n packets accordingly.\n\nSigned-off-by: Eli Britstein <elibr@nvidia.com>\n---\n Documentation/automake.mk | 2 +\n Documentation/howto/doca.rst | 143 ++\n Documentation/howto/index.rst | 1 +\n Documentation/intro/install/doca.rst | 104 +\n Documentation/intro/install/index.rst | 1 +\n NEWS | 4 +\n lib/automake.mk | 6 +\n lib/netdev-doca.c | 2898 +++++++++++++++++++++++++\n lib/netdev-doca.h | 159 ++\n lib/ovs-doca.c | 732 ++++++-\n lib/ovs-doca.h | 82 +\n tests/ofproto-macros.at | 1 +\n utilities/checkpatch_dict.txt | 1 +\n vswitchd/vswitch.xml | 87 +-\n 14 files changed, 4187 insertions(+), 34 deletions(-)\n create mode 100644 Documentation/howto/doca.rst\n create mode 100644 Documentation/intro/install/doca.rst\n create mode 100644 lib/netdev-doca.c\n create mode 100644 lib/netdev-doca.h", "diff": "diff --git a/Documentation/automake.mk b/Documentation/automake.mk\nindex ea9459b55..230128efb 100644\n--- a/Documentation/automake.mk\n+++ b/Documentation/automake.mk\n@@ -14,6 +14,7 @@ DOC_SOURCE = \\\n \tDocumentation/intro/install/debian.rst \\\n \tDocumentation/intro/install/documentation.rst \\\n \tDocumentation/intro/install/distributions.rst \\\n+\tDocumentation/intro/install/doca.rst \\\n \tDocumentation/intro/install/dpdk.rst \\\n \tDocumentation/intro/install/fedora.rst \\\n \tDocumentation/intro/install/general.rst \\\n@@ -63,6 +64,7 @@ DOC_SOURCE = \\\n \tDocumentation/topics/userspace-tx-steering.rst \\\n \tDocumentation/topics/windows.rst \\\n \tDocumentation/howto/index.rst \\\n+\tDocumentation/howto/doca.rst \\\n \tDocumentation/howto/dpdk.rst \\\n \tDocumentation/howto/ipsec.rst \\\n \tDocumentation/howto/kvm.rst \\\ndiff --git a/Documentation/howto/doca.rst b/Documentation/howto/doca.rst\nnew file mode 100644\nindex 000000000..4afb749d1\n--- /dev/null\n+++ b/Documentation/howto/doca.rst\n@@ -0,0 +1,143 @@\n+..\n+ Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n+ not use this file except in compliance with the License. You may obtain\n+ a copy of the License at\n+\n+ http://www.apache.org/licenses/LICENSE-2.0\n+\n+ Unless required by applicable law or agreed to in writing, software\n+ distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n+ License for the specific language governing permissions and limitations\n+ under the License.\n+\n+ Convention for heading levels in Open vSwitch documentation:\n+\n+ ======= Heading 0 (reserved for the title in a document)\n+ ------- Heading 1\n+ ~~~~~~~ Heading 2\n+ +++++++ Heading 3\n+ ''''''' Heading 4\n+\n+ Avoid deeper levels because they do not render well.\n+\n+============================\n+Using Open vSwitch with DOCA\n+============================\n+\n+This document describes how to use Open vSwitch with DOCA on NVIDIA\n+BlueField DPUs and ConnectX NICs.\n+\n+.. important::\n+\n+ Using DOCA with OVS requires building OVS with both DPDK and DOCA\n+ support. For build instructions refer to :doc:`/intro/install/doca`.\n+\n+Prerequisites\n+-------------\n+\n+Enabling DOCA\n+~~~~~~~~~~~~~\n+\n+The ``doca-init`` option must be set to ``true`` before starting\n+``ovs-vswitchd``. If DOCA cannot be initialized, the process will abort::\n+\n+ $ ovs-vsctl --no-wait set Open_vSwitch . other_config:doca-init=true\n+\n+DOCA also requires DPDK, so ``dpdk-init`` must be enabled as well::\n+\n+ $ ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true\n+\n+.. note::\n+ Changing either value requires restarting ``ovs-vswitchd``.\n+\n+DOCA initialization can be confirmed by checking the ``doca_initialized``\n+value::\n+\n+ $ ovs-vsctl get Open_vSwitch . doca_initialized\n+ true\n+\n+E-Switch Configuration\n+~~~~~~~~~~~~~~~~~~~~~~\n+\n+The NIC embedded switch (E-Switch) must be set to ``switchdev`` mode.\n+\n+Set the E-Switch to switchdev mode using the PF PCI address::\n+\n+ $ sudo devlink dev eswitch set pci/0000:08:00.0 mode switchdev\n+\n+DPDK PCI Device Probing\n+~~~~~~~~~~~~~~~~~~~~~~~\n+\n+DPDK must not automatically probe PCI devices when using DOCA ports. Disable\n+automatic probing by passing a dummy allow-list address via ``dpdk-extra``::\n+\n+ $ ovs-vsctl set Open_vSwitch . \\\n+ other_config:dpdk-extra=\"-a pci:0000:00:00.0\"\n+\n+Device Capabilities\n+~~~~~~~~~~~~~~~~~~~\n+\n+DOCA requires ``CAP_SYS_RAWIO`` to configure the E-Switch manager. Without\n+it, OVS fails to detect the ESW manager port and all DOCA ports are\n+non-functional. The ``ovs-vswitchd`` process must be started with the\n+``--hw-rawio-access`` command line option.\n+\n+On RHEL/Fedora systems, edit ``/etc/sysconfig/openvswitch``::\n+\n+ OPTIONS=\"--ovs-vswitchd-options='--hw-rawio-access'\"\n+\n+On Debian/Ubuntu systems, ``ovs-vswitchd`` runs as root by default and\n+already has all capabilities, so this step is not required. If running as\n+a non-root user, edit ``/etc/default/openvswitch-switch``::\n+\n+ OVS_CTL_OPTS=\"--ovs-vswitchd-options='--hw-rawio-access'\"\n+\n+Restart ``ovs-vswitchd`` after making the change.\n+\n+Ports and Bridges\n+-----------------\n+\n+Bridges and ports are configured with ``ovs-vsctl``. Bridges should be\n+created with ``datapath_type=netdev``::\n+\n+ $ ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev\n+\n+DOCA ports are added by referencing the Linux network interface name of the\n+port representor and setting the interface type to ``doca``. For example,\n+given a NIC where ``enp8s0f0`` is the E-Switch uplink and ``enp8s0f0_0``,\n+``enp8s0f0_1`` are VF representors::\n+\n+ $ ovs-vsctl add-port br0 enp8s0f0 -- set Interface enp8s0f0 type=doca\n+ $ ovs-vsctl add-port br0 enp8s0f0_0 -- set Interface enp8s0f0_0 type=doca\n+ $ ovs-vsctl add-port br0 enp8s0f0_1 -- set Interface enp8s0f0_1 type=doca\n+\n+.. important::\n+\n+ The E-Switch uplink representor (e.g. ``enp8s0f0``) must be attached to\n+ OVS. Without it, VF representor ports are silently non-functional.\n+\n+.. important::\n+\n+ DOCA ports and mlx5 DPDK ports (``type=dpdk``) cannot coexist in the\n+ same OVS instance. NVIDIA NIC ports must be either all ``type=doca`` or\n+ all ``type=dpdk``. Other (non-mlx5) DPDK port types and kernel ports\n+ are not affected by this restriction and can be used alongside DOCA ports.\n+\n+Configuration Notes\n+-------------------\n+\n+The ``other_config:flow-limit`` value is read during DOCA initialization and\n+cannot be changed dynamically. Modifying ``flow-limit`` requires restarting\n+``ovs-vswitchd`` for the new value to take effect with DOCA::\n+\n+ $ ovs-vsctl set Open_vSwitch . other_config:flow-limit=100000\n+\n+Further Reading\n+---------------\n+\n+- :doc:`/intro/install/doca` -- Build and installation instructions.\n+- :doc:`/intro/install/dpdk` -- DPDK build prerequisites.\n+- :doc:`dpdk` -- General DPDK usage with OVS.\n+- `NVIDIA DOCA Documentation <https://docs.nvidia.com/doca/>`_ -- Upstream\n+ DOCA SDK reference.\ndiff --git a/Documentation/howto/index.rst b/Documentation/howto/index.rst\nindex 1491de3f3..9e083361e 100644\n--- a/Documentation/howto/index.rst\n+++ b/Documentation/howto/index.rst\n@@ -48,5 +48,6 @@ OVS\n vtep\n sflow\n dpdk\n+ doca\n tc-offload\n \ndiff --git a/Documentation/intro/install/doca.rst b/Documentation/intro/install/doca.rst\nnew file mode 100644\nindex 000000000..a3393077f\n--- /dev/null\n+++ b/Documentation/intro/install/doca.rst\n@@ -0,0 +1,104 @@\n+..\n+ Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n+ not use this file except in compliance with the License. You may obtain\n+ a copy of the License at\n+\n+ http://www.apache.org/licenses/LICENSE-2.0\n+\n+ Unless required by applicable law or agreed to in writing, software\n+ distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n+ License for the specific language governing permissions and limitations\n+ under the License.\n+\n+ Convention for heading levels in Open vSwitch documentation:\n+\n+ ======= Heading 0 (reserved for the title in a document)\n+ ------- Heading 1\n+ ~~~~~~~ Heading 2\n+ +++++++ Heading 3\n+ ''''''' Heading 4\n+\n+ Avoid deeper levels because they do not render well.\n+\n+======================\n+Open vSwitch with DOCA\n+======================\n+\n+This document describes how to build and install Open vSwitch with DOCA\n+support on NVIDIA BlueField and ConnectX network platforms.\n+\n+.. important::\n+\n+ Building OVS with DOCA requires a working DPDK build first. Refer to\n+ :doc:`dpdk` for DPDK build and installation instructions.\n+\n+Build Requirements\n+------------------\n+\n+In addition to the requirements described in :doc:`general` and :doc:`dpdk`,\n+building Open vSwitch with DOCA requires the following:\n+\n+- DPDK with mlx5 PMD driver enabled (see :doc:`dpdk`). The DOCA SDK\n+ includes a compatible DPDK build (``dpdk-community-dev``); alternatively,\n+ DPDK can be built from source with ``-Denable_drivers=net/mlx5``.\n+\n+- DOCA SDK packages (``libdoca-sdk-flow-dev``, ``libdoca-sdk-dpdk-bridge-dev``)\n+\n+- An NVIDIA BlueField DPU or ConnectX NIC with a supported firmware version\n+\n+.. _doca-install:\n+\n+Installing\n+----------\n+\n+Install DOCA SDK\n+~~~~~~~~~~~~~~~~\n+\n+The DOCA SDK can be installed from the NVIDIA package repository.\n+\n+#. Download the DOCA host repo package from the `NVIDIA DOCA Downloads`_ page:\n+\n+ Select *Host-Server* deployment platform, *DOCA-Host* deployment package,\n+ *Linux* target OS, and *x86_64* architecture.\n+\n+#. Install the repository and SDK packages::\n+\n+ $ sudo dpkg -i doca-repo.deb\n+ $ sudo apt-get update\n+ $ sudo apt-get install -y dpdk-community-dev \\\n+ libdoca-sdk-flow-dev libdoca-sdk-dpdk-bridge-dev\n+\n+ On RPM-based distributions::\n+\n+ $ sudo rpm -i doca-repo.rpm\n+ $ sudo dnf install -y dpdk-community-devel \\\n+ libdoca-sdk-flow-devel libdoca-sdk-dpdk-bridge-devel\n+\n+.. _NVIDIA DOCA Downloads: https://developer.nvidia.com/doca-downloads\n+\n+Install OVS\n+~~~~~~~~~~~~\n+\n+OVS must be configured with both ``--with-dpdk`` and ``--with-doca`` flags.\n+\n+#. Ensure the standard OVS requirements, described in\n+ :ref:`general-build-reqs`, are installed\n+\n+#. Bootstrap, if required, as described in :ref:`general-bootstrapping`\n+\n+#. Configure the package with DPDK and DOCA support::\n+\n+ $ ./configure --with-dpdk=static --with-doca=static\n+\n+ .. note::\n+ ``--with-doca`` requires ``--with-dpdk``. The configure step will fail\n+ if DPDK is not enabled.\n+\n+ .. note::\n+ While ``--with-dpdk`` and ``--with-doca`` are required, you can pass\n+ any other configuration option described in :ref:`general-configuring`.\n+\n+#. Build and install OVS, as described in :ref:`general-building`\n+\n+Additional information can be found in :doc:`general`.\ndiff --git a/Documentation/intro/install/index.rst b/Documentation/intro/install/index.rst\nindex 885a65d6e..767b4afd3 100644\n--- a/Documentation/intro/install/index.rst\n+++ b/Documentation/intro/install/index.rst\n@@ -44,6 +44,7 @@ Installation from Source\n windows\n userspace\n dpdk\n+ doca\n afxdp\n \n Installation from Packages\ndiff --git a/NEWS b/NEWS\nindex 1a3044cbf..2ccb6ea39 100644\n--- a/NEWS\n+++ b/NEWS\n@@ -3,6 +3,10 @@ Post-v3.7.0\n - Userspace datapath:\n * ARP/ND lookups for native tunnel are now rate limited. The holdout\n timer can be configured with 'tnl/neigh/retrans_time'.\n+ - DOCA:\n+ * New netdev type \"doca\", available under \"netdev\" datapath,\n+ using the DOCA API for NVIDIA ConnectX and BlueField NICs.\n+ See Documentation/howto/doca.rst.\n \n \n v3.7.0 - 16 Feb 2026\ndiff --git a/lib/automake.mk b/lib/automake.mk\nindex 66c5c3d93..09a2936a9 100644\n--- a/lib/automake.mk\n+++ b/lib/automake.mk\n@@ -521,6 +521,12 @@ lib_libopenvswitch_la_SOURCES += \\\n \tlib/ovs-doca.c \\\n \tlib/ovs-doca.h\n \n+if DOCA_NETDEV\n+lib_libopenvswitch_la_SOURCES += \\\n+\tlib/netdev-doca.c \\\n+\tlib/netdev-doca.h\n+endif\n+\n if WIN32\n lib_libopenvswitch_la_SOURCES += \\\n \tlib/dpif-netlink.c \\\ndiff --git a/lib/netdev-doca.c b/lib/netdev-doca.c\nnew file mode 100644\nindex 000000000..c3b2fdc95\n--- /dev/null\n+++ b/lib/netdev-doca.c\n@@ -0,0 +1,2898 @@\n+/*\n+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.\n+ * All rights reserved.\n+ * SPDX-License-Identifier: Apache-2.0\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at\n+ *\n+ * http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#include <config.h>\n+\n+#include <dirent.h>\n+#include <errno.h>\n+#include <infiniband/verbs.h>\n+#include <net/if.h>\n+#include <unistd.h>\n+\n+#include <rte_bus.h>\n+#include <rte_config.h>\n+#include <rte_dev.h>\n+#include <rte_ethdev.h>\n+#include <rte_malloc.h>\n+#include <rte_mempool.h>\n+#include <rte_pci.h>\n+#include <rte_pmd_mlx5.h>\n+#include <rte_ring.h>\n+#include <rte_version.h>\n+\n+#include <doca_bitfield.h>\n+#include <doca_dev.h>\n+#include <doca_dpdk.h>\n+#include <doca_flow.h>\n+#include <doca_rdma_bridge.h>\n+\n+#include \"coverage.h\"\n+#include \"dp-packet.h\"\n+#include \"dpif-netdev.h\"\n+#include \"netdev-doca.h\"\n+#include \"netdev-provider.h\"\n+#include \"ovs-doca.h\"\n+#include \"ovs-thread.h\"\n+#include \"refmap.h\"\n+#include \"rtnetlink.h\"\n+#include \"unixctl.h\"\n+#include \"userspace-tso.h\"\n+#include \"util.h\"\n+\n+#include \"openvswitch/vlog.h\"\n+\n+VLOG_DEFINE_THIS_MODULE(netdev_doca);\n+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(600, 600);\n+\n+COVERAGE_DEFINE(netdev_doca_drop_ring_full);\n+COVERAGE_DEFINE(netdev_doca_invalid_classify_port);\n+COVERAGE_DEFINE(netdev_doca_no_mark);\n+\n+#define NETDEV_DOCA_MAX_MEGAFLOWS_COUNTERS (1 << 19)\n+#define NETDEV_DOCA_ACTIONS_MEM_SIZE \\\n+ (64 * 2 * NETDEV_DOCA_MAX_MEGAFLOWS_COUNTERS)\n+\n+#define MAX_PHYS_ITEM_ID_LEN 32\n+\n+struct netdev_doca_esw_key {\n+ struct rte_pci_addr rte_pci;\n+};\n+\n+struct netdev_doca_esw_ctx_arg {\n+ struct netdev_doca_esw_key *esw_key;\n+ struct netdev_doca *dev;\n+};\n+\n+struct rss_match_type {\n+ enum doca_flow_l3_meta l3_type;\n+ enum doca_flow_l4_meta l4_type;\n+};\n+\n+static uint16_t pre_miss_mapping[NUM_SEND_TO_KERNEL] = {\n+ [SEND_TO_KERNEL_LACP] = ETH_TYPE_LACP,\n+ [SEND_TO_KERNEL_LLDP] = ETH_TYPE_LLDP,\n+};\n+\n+static struct refmap *netdev_doca_esw_rfm;\n+static struct atomic_count n_doca_ports = ATOMIC_COUNT_INIT(0);\n+struct ovs_mutex doca_mutex = OVS_MUTEX_INITIALIZER;\n+/* Contains all 'struct doca_dev's. */\n+static struct ovs_list doca_list OVS_GUARDED_BY(doca_mutex)\n+ = OVS_LIST_INITIALIZER(&doca_list);\n+\n+static void\n+netdev_doca_destruct(struct netdev *netdev);\n+\n+static int\n+netdev_doca_port_stop(struct netdev *netdev)\n+ OVS_REQUIRES(doca_mutex);\n+\n+static dpdk_port_t\n+netdev_doca_get_esw_mgr_port_id(const struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+\n+ if (!rte_eth_dev_is_valid_port(dev->common.port_id) ||\n+ !rte_eth_dev_is_valid_port(dev->esw_mgr_port_id)) {\n+ return DPDK_ETH_PORT_ID_INVALID;\n+ }\n+\n+ return dev->esw_mgr_port_id;\n+}\n+\n+static dpdk_port_t\n+netdev_doca_get_port_id(const struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+\n+ if (!rte_eth_dev_is_valid_port(dev->common.port_id)) {\n+ return DPDK_ETH_PORT_ID_INVALID;\n+ }\n+\n+ return dev->common.port_id;\n+}\n+\n+static bool\n+netdev_doca_is_esw_mgr(const struct netdev *netdev)\n+{\n+ dpdk_port_t esw_mgr_id = netdev_doca_get_esw_mgr_port_id(netdev);\n+\n+ return esw_mgr_id == netdev_doca_get_port_id(netdev) &&\n+ esw_mgr_id != DPDK_ETH_PORT_ID_INVALID;\n+}\n+\n+static int\n+netdev_doca_egress_pipe_init(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct ovs_doca_flow_match match;\n+ struct doca_flow_monitor monitor;\n+ struct doca_flow_fwd fwd;\n+\n+ memset(&match, 0, sizeof match);\n+ memset(&fwd, 0, sizeof fwd);\n+ memset(&monitor, 0, sizeof monitor);\n+\n+ /* Meta to match on is defined per entry. */\n+ memset(&match.d.meta.pkt_meta, 0xFF, sizeof match.d.meta.pkt_meta);\n+\n+ /* Port ID to forward to is defined per entry. */\n+ fwd.type = DOCA_FLOW_FWD_PORT;\n+ memset(&fwd.port_id, 0xFF, sizeof fwd.port_id);\n+ monitor.counter_type = DOCA_FLOW_RESOURCE_TYPE_NON_SHARED;\n+\n+ return ovs_doca_pipe_create(&dev->common.up, &match, NULL, &monitor, NULL,\n+ NULL, NULL, &fwd, NULL, RTE_MAX_ETHPORTS, true,\n+ true, UINT64_C(1) << AUX_QUEUE, \"EGRESS\",\n+ &dev->esw_ctx->egress_pipe);\n+}\n+\n+static void\n+netdev_doca_egress_pipe_uninit(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+\n+ ovs_doca_destroy_pipe(&esw->egress_pipe);\n+}\n+\n+static int\n+netdev_doca_rss_pipe_init(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct ovs_doca_flow_actions actions;\n+ struct ovs_doca_flow_match match;\n+ struct doca_flow_monitor monitor;\n+ struct doca_flow_fwd fwd;\n+ int rv;\n+\n+ memset(&match, 0, sizeof match);\n+ memset(&fwd, 0, sizeof fwd);\n+ memset(&actions, 0, sizeof actions);\n+ memset(&monitor, 0, sizeof monitor);\n+\n+ memset(&match.d.parser_meta.port_id, 0xFF,\n+ sizeof match.d.parser_meta.port_id);\n+ memset(&match.d.parser_meta.outer_l3_type, 0xFF,\n+ sizeof match.d.parser_meta.outer_l3_type);\n+ memset(&match.d.parser_meta.outer_l4_type, 0xFF,\n+ sizeof match.d.parser_meta.outer_l4_type);\n+\n+ memset(&actions.mark, 0xFF, sizeof actions.mark);\n+\n+ monitor.counter_type = DOCA_FLOW_RESOURCE_TYPE_NON_SHARED;\n+\n+ fwd.type = DOCA_FLOW_FWD_RSS;\n+ fwd.rss_type = DOCA_FLOW_RESOURCE_TYPE_NON_SHARED;\n+ memset(&fwd.rss.nr_queues, 0xFF, sizeof fwd.rss.nr_queues);\n+\n+ rv = ovs_doca_pipe_create(&dev->common.up, &match, NULL, &monitor,\n+ &actions, &actions, NULL, &fwd, NULL,\n+ NETDEV_DOCA_RSS_NUM_ENTRIES * RTE_MAX_ETHPORTS,\n+ false, false, UINT64_C(1) << AUX_QUEUE, \"RSS\",\n+ &dev->esw_ctx->rss_pipe);\n+ return rv;\n+}\n+\n+static void\n+netdev_doca_rss_pipe_uninit(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+\n+ ovs_doca_destroy_pipe(&esw->rss_pipe);\n+}\n+\n+static uint32_t\n+netdev_doca_rss_flags(enum netdev_doca_rss_type type)\n+{\n+ switch (type) {\n+ case NETDEV_DOCA_RSS_IPV4_TCP:\n+ return DOCA_FLOW_RSS_IPV4 | DOCA_FLOW_RSS_TCP;\n+ case NETDEV_DOCA_RSS_IPV4_UDP:\n+ return DOCA_FLOW_RSS_IPV4 | DOCA_FLOW_RSS_UDP;\n+ case NETDEV_DOCA_RSS_IPV4_ICMP:\n+ return DOCA_FLOW_RSS_IPV4;\n+ case NETDEV_DOCA_RSS_IPV4_ESP:\n+ return DOCA_FLOW_RSS_IPV4;\n+ case NETDEV_DOCA_RSS_IPV4_OTHER:\n+ return DOCA_FLOW_RSS_IPV4;\n+ case NETDEV_DOCA_RSS_IPV6_TCP:\n+ return DOCA_FLOW_RSS_IPV6 | DOCA_FLOW_RSS_TCP;\n+ case NETDEV_DOCA_RSS_IPV6_UDP:\n+ return DOCA_FLOW_RSS_IPV6 | DOCA_FLOW_RSS_UDP;\n+ case NETDEV_DOCA_RSS_IPV6_ICMP:\n+ return DOCA_FLOW_RSS_IPV6;\n+ case NETDEV_DOCA_RSS_IPV6_ESP:\n+ return DOCA_FLOW_RSS_IPV6;\n+ case NETDEV_DOCA_RSS_IPV6_OTHER:\n+ return DOCA_FLOW_RSS_IPV6;\n+ case NETDEV_DOCA_RSS_OTHER:\n+ return 0;\n+ }\n+\n+ OVS_NOT_REACHED();\n+ return 0;\n+}\n+\n+static struct rss_match_type\n+netdev_doca_rss_match_type(enum netdev_doca_rss_type type)\n+{\n+ switch (type) {\n+ case NETDEV_DOCA_RSS_IPV4_TCP:\n+ return (struct rss_match_type) {\n+ .l3_type = DOCA_FLOW_L3_META_IPV4,\n+ .l4_type = DOCA_FLOW_L4_META_TCP,\n+ };\n+ case NETDEV_DOCA_RSS_IPV4_UDP:\n+ return (struct rss_match_type) {\n+ .l3_type = DOCA_FLOW_L3_META_IPV4,\n+ .l4_type = DOCA_FLOW_L4_META_UDP,\n+ };\n+ case NETDEV_DOCA_RSS_IPV4_ICMP:\n+ return (struct rss_match_type) {\n+ .l3_type = DOCA_FLOW_L3_META_IPV4,\n+ .l4_type = DOCA_FLOW_L4_META_ICMP,\n+ };\n+ case NETDEV_DOCA_RSS_IPV4_ESP:\n+ return (struct rss_match_type) {\n+ .l3_type = DOCA_FLOW_L3_META_IPV4,\n+ .l4_type = DOCA_FLOW_L4_META_ESP,\n+ };\n+ case NETDEV_DOCA_RSS_IPV4_OTHER:\n+ return (struct rss_match_type) {\n+ .l3_type = DOCA_FLOW_L3_META_IPV4,\n+ .l4_type = DOCA_FLOW_L4_META_NONE,\n+ };\n+ case NETDEV_DOCA_RSS_IPV6_TCP:\n+ return (struct rss_match_type) {\n+ .l3_type = DOCA_FLOW_L3_META_IPV6,\n+ .l4_type = DOCA_FLOW_L4_META_TCP,\n+ };\n+ case NETDEV_DOCA_RSS_IPV6_UDP:\n+ return (struct rss_match_type) {\n+ .l3_type = DOCA_FLOW_L3_META_IPV6,\n+ .l4_type = DOCA_FLOW_L4_META_UDP,\n+ };\n+ case NETDEV_DOCA_RSS_IPV6_ICMP:\n+ return (struct rss_match_type) {\n+ .l3_type = DOCA_FLOW_L3_META_IPV6,\n+ .l4_type = DOCA_FLOW_L4_META_ICMP,\n+ };\n+ case NETDEV_DOCA_RSS_IPV6_ESP:\n+ return (struct rss_match_type) {\n+ .l3_type = DOCA_FLOW_L3_META_IPV6,\n+ .l4_type = DOCA_FLOW_L4_META_ESP,\n+ };\n+ case NETDEV_DOCA_RSS_IPV6_OTHER:\n+ return (struct rss_match_type) {\n+ .l3_type = DOCA_FLOW_L3_META_IPV6,\n+ .l4_type = DOCA_FLOW_L4_META_NONE,\n+ };\n+ case NETDEV_DOCA_RSS_OTHER:\n+ return (struct rss_match_type) {\n+ .l3_type = DOCA_FLOW_L3_META_NONE,\n+ .l4_type = DOCA_FLOW_L4_META_NONE,\n+ };\n+ }\n+\n+ OVS_NOT_REACHED();\n+ return (struct rss_match_type) {};\n+}\n+\n+static const char *\n+netdev_doca_stats_name(enum netdev_doca_rss_type type)\n+{\n+ switch (type) {\n+ case NETDEV_DOCA_RSS_IPV4_TCP:\n+ return \"rx_ipv4_tcp\";\n+ case NETDEV_DOCA_RSS_IPV4_UDP:\n+ return \"rx_ipv4_udp\";\n+ case NETDEV_DOCA_RSS_IPV4_ICMP:\n+ return \"rx_ipv4_icmp\";\n+ case NETDEV_DOCA_RSS_IPV4_ESP:\n+ return \"rx_ipv4_esp\";\n+ case NETDEV_DOCA_RSS_IPV4_OTHER:\n+ return \"rx_ipv4_other\";\n+ case NETDEV_DOCA_RSS_IPV6_TCP:\n+ return \"rx_ipv6_tcp\";\n+ case NETDEV_DOCA_RSS_IPV6_UDP:\n+ return \"rx_ipv6_udp\";\n+ case NETDEV_DOCA_RSS_IPV6_ICMP:\n+ return \"rx_ipv6_icmp\";\n+ case NETDEV_DOCA_RSS_IPV6_ESP:\n+ return \"rx_ipv6_esp\";\n+ case NETDEV_DOCA_RSS_IPV6_OTHER:\n+ return \"rx_ipv6_other\";\n+ case NETDEV_DOCA_RSS_OTHER:\n+ return \"rx_other\";\n+ }\n+\n+ OVS_NOT_REACHED();\n+ return \"ERR\";\n+}\n+\n+static int\n+netdev_doca_rss_entries_init(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_dpdk_common *common = &dev->common;\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+ dpdk_port_t port_id = common->port_id;\n+ struct ovs_doca_flow_actions actions;\n+ struct doca_flow_pipe_entry *entry;\n+ struct ovs_doca_flow_match match;\n+ unsigned int num_of_queues;\n+ struct doca_flow_fwd fwd;\n+ uint16_t *rss_queues;\n+ int ret;\n+ int i;\n+\n+ num_of_queues = esw->n_rxq;\n+\n+ rss_queues = xcalloc(num_of_queues, sizeof *rss_queues);\n+ for (i = 0; i < num_of_queues; i++) {\n+ rss_queues[i] = i;\n+ }\n+\n+ memset(&match, 0, sizeof match);\n+ memset(&actions, 0, sizeof actions);\n+ memset(&fwd, 0, sizeof fwd);\n+\n+ fwd.type = DOCA_FLOW_FWD_RSS;\n+ fwd.rss.queues_array = rss_queues;\n+ fwd.rss.nr_queues = num_of_queues;\n+ fwd.rss_type = DOCA_FLOW_RESOURCE_TYPE_NON_SHARED;\n+\n+ match.d.parser_meta.port_id = port_id;\n+ actions.mark = (OVS_FORCE doca_be32_t) DOCA_HTOBE32(port_id);\n+\n+ for (i = 0; i < NETDEV_DOCA_RSS_NUM_ENTRIES; i++) {\n+ struct rss_match_type match_type = netdev_doca_rss_match_type(i);\n+\n+ match.d.parser_meta.outer_l3_type = match_type.l3_type;\n+ match.d.parser_meta.outer_l4_type = match_type.l4_type;\n+ fwd.rss.outer_flags = netdev_doca_rss_flags(i);\n+\n+ ret = ovs_doca_add_entry(&common->up, AUX_QUEUE, esw->rss_pipe, &match,\n+ &actions, NULL, &fwd,\n+ DOCA_FLOW_ENTRY_FLAGS_NO_WAIT, &entry);\n+ if (ret) {\n+ VLOG_ERR(\"%s: Failed to create '%s' rss entry. Error: %d (%s)\",\n+ netdev_get_name(&common->up), netdev_doca_stats_name(i),\n+ ret, doca_error_get_descr(ret));\n+ break;\n+ }\n+\n+ dev->rss_entries[i] = entry;\n+ }\n+\n+ free(rss_queues);\n+\n+ return ret;\n+}\n+\n+static void\n+netdev_doca_rss_entries_uninit(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+\n+ for (int i = 0; i < NETDEV_DOCA_RSS_NUM_ENTRIES; i++) {\n+ ovs_doca_remove_entry(esw, AUX_QUEUE, DOCA_FLOW_ENTRY_FLAGS_NO_WAIT,\n+ &dev->rss_entries[i]);\n+ }\n+}\n+\n+static int\n+netdev_doca_meta_tag0_pipe_init(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct ovs_doca_flow_actions actions_masks;\n+ struct ovs_doca_flow_actions actions;\n+ struct ovs_doca_flow_match match;\n+ struct doca_flow_fwd fwd = {\n+ .type = DOCA_FLOW_FWD_PIPE,\n+ .next_pipe = dev->esw_ctx->rss_pipe,\n+ };\n+\n+ memset(&match, 0, sizeof match);\n+ memset(&actions, 0, sizeof actions);\n+ memset(&actions_masks, 0, sizeof actions_masks);\n+\n+ memset(&actions_masks.d.meta.u32[0], 0xFF,\n+ sizeof actions_masks.d.meta.u32[0]);\n+\n+ return ovs_doca_pipe_create(netdev, &match, NULL, NULL, &actions,\n+ &actions_masks, NULL, &fwd, NULL, 1, false,\n+ false, UINT64_C(1) << AUX_QUEUE, \"META_TAG0\",\n+ &dev->esw_ctx->meta_tag0_pipe);\n+}\n+\n+static void\n+netdev_doca_meta_tag0_pipe_uninit(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+\n+ ovs_doca_destroy_pipe(&esw->meta_tag0_pipe);\n+}\n+\n+static int\n+netdev_doca_meta_tag0_rule_init(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct doca_flow_pipe_entry **pentry;\n+ struct doca_flow_pipe *pipe;\n+ int ret;\n+\n+ pentry = &dev->esw_ctx->meta_tag0_entry;\n+ pipe = dev->esw_ctx->meta_tag0_pipe;\n+\n+ ret = ovs_doca_add_entry(netdev, AUX_QUEUE, pipe, NULL, NULL, NULL, NULL,\n+ DOCA_FLOW_ENTRY_FLAGS_NO_WAIT, pentry);\n+ if (ret) {\n+ VLOG_ERR(\"%s: Failed to create meta-tag0 rule. Error: %d (%s)\",\n+ netdev_get_name(netdev), ret, doca_error_get_descr(ret));\n+ }\n+\n+ return ret;\n+}\n+\n+static void\n+netdev_doca_meta_tag0_rule_uninit(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+\n+ ovs_doca_remove_entry(esw, AUX_QUEUE, DOCA_FLOW_ENTRY_FLAGS_NO_WAIT,\n+ &dev->esw_ctx->meta_tag0_entry);\n+}\n+\n+static int\n+netdev_doca_pre_miss_pipe_init(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct ovs_doca_flow_match match = { .d = {\n+ .parser_meta.outer_l2_type = DOCA_FLOW_L2_META_NO_VLAN,\n+ .outer.eth.type = UINT16_MAX,\n+ }, };\n+ struct doca_flow_target *kernel_target;\n+ struct doca_flow_fwd fwd, miss;\n+ int err;\n+\n+ memset(&miss, 0, sizeof miss);\n+ memset(&fwd, 0, sizeof fwd);\n+\n+ miss.type = DOCA_FLOW_FWD_PIPE;\n+ miss.next_pipe = dev->esw_ctx->meta_tag0_pipe;\n+\n+ err = doca_flow_get_target(DOCA_FLOW_TARGET_KERNEL, &kernel_target);\n+ if (err) {\n+ VLOG_ERR(\"%s: Could not get miss to kernel target. Error: %d (%s)\",\n+ netdev_get_name(netdev), err, doca_error_get_descr(err));\n+ return err;\n+ }\n+\n+ fwd.type = DOCA_FLOW_FWD_TARGET;\n+ fwd.target = kernel_target;\n+\n+ return ovs_doca_pipe_create(netdev, &match, NULL, NULL, NULL, NULL, NULL,\n+ &fwd, &miss, NUM_SEND_TO_KERNEL, false,\n+ false, UINT64_C(1) << AUX_QUEUE, \"PRE_MISS\",\n+ &dev->esw_ctx->pre_miss_pipe);\n+}\n+\n+static void\n+netdev_doca_pre_miss_pipe_uninit(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+\n+ ovs_doca_destroy_pipe(&esw->pre_miss_pipe);\n+}\n+\n+static int\n+netdev_doca_pre_miss_rules_init(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct doca_flow_pipe_entry **pentry;\n+ struct ovs_doca_flow_match match;\n+ int ret;\n+\n+ memset(&match, 0, sizeof match);\n+\n+ for (int i = 0 ; i < NUM_SEND_TO_KERNEL ; i++) {\n+ pentry = &dev->esw_ctx->pre_miss_entries[i];\n+\n+ match.d.outer.eth.type = htons(pre_miss_mapping[i]);\n+ ret = ovs_doca_add_entry(netdev, AUX_QUEUE,\n+ dev->esw_ctx->pre_miss_pipe, &match, NULL,\n+ NULL, NULL, DOCA_FLOW_ENTRY_FLAGS_NO_WAIT,\n+ pentry);\n+ if (ret) {\n+ VLOG_ERR(\"%s: Failed to create pre_miss %x rule. Error: %d (%s)\",\n+ netdev_get_name(netdev), pre_miss_mapping[i],\n+ ret, doca_error_get_descr(ret));\n+ break;\n+ }\n+ }\n+\n+ return ret;\n+}\n+\n+static void\n+netdev_doca_pre_miss_rules_uninit(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+\n+ for (int i = 0 ; i < NUM_SEND_TO_KERNEL ; i++) {\n+ ovs_doca_remove_entry(esw, AUX_QUEUE, DOCA_FLOW_ENTRY_FLAGS_NO_WAIT,\n+ &esw->pre_miss_entries[i]);\n+ }\n+}\n+\n+static int\n+netdev_doca_root_pipe_init(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct doca_flow_fwd miss;\n+\n+ memset(&miss, 0, sizeof miss);\n+ miss.type = DOCA_FLOW_FWD_PIPE;\n+ miss.next_pipe = dev->esw_ctx->pre_miss_pipe;\n+\n+ return ovs_doca_pipe_create(netdev, NULL, NULL, NULL, NULL, NULL, NULL,\n+ NULL, &miss, 0, false, true, 0, \"ROOT\",\n+ &dev->esw_ctx->root_pipe);\n+}\n+\n+static void\n+netdev_doca_root_pipe_uninit(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+\n+ ovs_doca_destroy_pipe(&esw->root_pipe);\n+}\n+\n+static int\n+netdev_doca_egress_entry_init(struct netdev_doca *dev)\n+{\n+ struct doca_flow_pipe *pipe = dev->esw_ctx->egress_pipe;\n+ struct netdev_dpdk_common *common = &dev->common;\n+ dpdk_port_t port_id = common->port_id;\n+ struct ovs_doca_flow_match match;\n+ struct doca_flow_fwd fwd;\n+ int ret;\n+\n+ memset(&match, 0, sizeof match);\n+ memset(&fwd, 0, sizeof fwd);\n+\n+ match.d.meta.pkt_meta = (OVS_FORCE doca_be32_t) DOCA_HTOBE32(port_id);\n+\n+ fwd.type = DOCA_FLOW_FWD_PORT;\n+ fwd.port_id = port_id;\n+\n+ ret = ovs_doca_add_entry(&common->up, AUX_QUEUE, pipe, &match, NULL, NULL,\n+ &fwd, DOCA_FLOW_ENTRY_FLAGS_NO_WAIT,\n+ &dev->egress_entry);\n+ if (ret) {\n+ VLOG_ERR(\"Failed to create egress pipe entry. Error: %d (%s)\", ret,\n+ doca_error_get_descr(ret));\n+ }\n+\n+ return ret;\n+}\n+\n+static void\n+netdev_doca_egress_entry_uninit(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+\n+ ovs_doca_remove_entry(esw, AUX_QUEUE, DOCA_FLOW_ENTRY_FLAGS_NO_WAIT,\n+ &dev->egress_entry);\n+}\n+\n+static void\n+netdev_doca_slowpath_esw_uninit(struct netdev *netdev)\n+{\n+ netdev_doca_root_pipe_uninit(netdev);\n+ netdev_doca_pre_miss_rules_uninit(netdev);\n+ netdev_doca_pre_miss_pipe_uninit(netdev);\n+ netdev_doca_meta_tag0_rule_uninit(netdev);\n+ netdev_doca_meta_tag0_pipe_uninit(netdev);\n+ netdev_doca_rss_pipe_uninit(netdev);\n+ netdev_doca_egress_pipe_uninit(netdev);\n+}\n+\n+static int\n+netdev_doca_slowpath_esw_init(struct netdev *netdev)\n+{\n+ int rv;\n+\n+#define ESW_INIT_CMD(func) \\\n+ do { \\\n+ rv = (func)(netdev); \\\n+ if (!rv) { \\\n+ break; \\\n+ } \\\n+ VLOG_ERR(\"%s: %s failed: %d\", netdev_get_name(netdev), \\\n+ #func, rv); \\\n+ return rv; \\\n+ } while (0)\n+\n+ ESW_INIT_CMD(netdev_doca_egress_pipe_init);\n+ ESW_INIT_CMD(netdev_doca_rss_pipe_init);\n+ ESW_INIT_CMD(netdev_doca_meta_tag0_pipe_init);\n+ ESW_INIT_CMD(netdev_doca_meta_tag0_rule_init);\n+ ESW_INIT_CMD(netdev_doca_pre_miss_pipe_init);\n+ ESW_INIT_CMD(netdev_doca_pre_miss_rules_init);\n+ ESW_INIT_CMD(netdev_doca_root_pipe_init);\n+\n+ return 0;\n+}\n+\n+static void\n+netdev_doca_esw_port_uninit(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+ uint16_t pid;\n+\n+ if (!esw) {\n+ return;\n+ }\n+\n+ for (pid = 0; pid < RTE_MAX_ETHPORTS; pid++) {\n+ if (esw->port_queues[pid]) {\n+ for (uint16_t qid = 0; qid < esw->n_rxq; qid++) {\n+ struct rte_ring **pring = &esw->port_queues[pid][qid].ring;\n+ struct dp_packet *pkt;\n+ int deq;\n+\n+ if (!*pring) {\n+ continue;\n+ }\n+\n+ while (1) {\n+ deq = rte_ring_dequeue(*pring, (void **) &pkt);\n+ if (deq) {\n+ break;\n+ }\n+ dp_packet_delete(pkt);\n+ }\n+ rte_ring_free(*pring);\n+ *pring = NULL;\n+ }\n+\n+ rte_free(esw->port_queues[pid]);\n+ esw->port_queues[pid] = NULL;\n+ }\n+ }\n+\n+ netdev_doca_slowpath_esw_uninit(netdev);\n+}\n+\n+static int\n+netdev_doca_esw_init(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_dpdk_common *common = &dev->common;\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+ uint16_t pid;\n+ int rv;\n+\n+ esw->esw_port = dev->port;\n+ esw->esw_netdev = netdev;\n+ esw->port_id = common->port_id;\n+ esw->n_rxq = netdev->n_rxq;\n+\n+ rv = netdev_doca_slowpath_esw_init(netdev);\n+ if (rv) {\n+ return rv;\n+ }\n+\n+ for (pid = 0; pid < RTE_MAX_ETHPORTS; pid++) {\n+ uint16_t qid;\n+\n+ esw->port_queues[pid] =\n+ rte_calloc_socket(\"port_queues\", esw->n_rxq,\n+ sizeof(struct netdev_doca_port_queue),\n+ RTE_CACHE_LINE_SIZE,\n+ common->socket_id);\n+ if (!esw->port_queues[pid]) {\n+ VLOG_ERR(\"%s: port_queues alloc failed for pid=%d\",\n+ netdev_get_name(netdev), pid);\n+ rv = ENOMEM;\n+ goto err;\n+ }\n+\n+ for (qid = 0; qid < esw->n_rxq; qid++) {\n+ char *ring_name;\n+\n+ ring_name = xasprintf(\"%s-%d-%d\", netdev_get_name(netdev), pid,\n+ qid);\n+ if (!ring_name) {\n+ VLOG_ERR(\"%s: ring_name alloc failed for pid=%d qid=%d\",\n+ netdev_get_name(netdev), pid, qid);\n+ rv = ENOMEM;\n+ goto err;\n+ }\n+\n+ if (strlen(ring_name) >= RTE_RING_NAMESIZE) {\n+ VLOG_ERR(\"%s: ring_name too long for pid=%d qid=%d\",\n+ netdev_get_name(netdev), pid, qid);\n+ free(ring_name);\n+ rv = ENAMETOOLONG;\n+ goto err;\n+ }\n+\n+ esw->port_queues[pid][qid].ring =\n+ rte_ring_create(ring_name, NETDEV_MAX_BURST * 2,\n+ common->socket_id,\n+ RING_F_SC_DEQ | RING_F_SP_ENQ);\n+ free(ring_name);\n+ if (!esw->port_queues[pid][qid].ring) {\n+ VLOG_ERR(\"%s: ring creation failed for pid=%d qid=%d\",\n+ netdev_get_name(netdev), pid, qid);\n+ rv = ENOMEM;\n+ goto err;\n+ }\n+\n+ atomic_init(&esw->port_queues[pid][qid].n_packets, 0);\n+ atomic_init(&esw->port_queues[pid][qid].n_bytes, 0);\n+ }\n+ }\n+\n+ return 0;\n+err:\n+ netdev_doca_esw_port_uninit(netdev);\n+ return rv;\n+}\n+\n+static int\n+get_sys(const char *prefix, const char *devname, const char *suffix,\n+ char *outp, size_t maxlen)\n+{\n+ char str[PATH_MAX];\n+ size_t len;\n+ FILE *fp;\n+ char *p;\n+ int n;\n+\n+ n = snprintf(str, sizeof str, \"/sys/%s/%s/%s\", prefix, devname, suffix);\n+ if (!(n >= 0 && n < sizeof str)) {\n+ VLOG_DBG(\"%s: snprintf overflow for %s/%s/%s\", OVS_SOURCE_LOCATOR,\n+ prefix, devname, suffix);\n+ return ENOSPC;\n+ }\n+\n+ fp = fopen(str, \"r\");\n+ if (!fp) {\n+ VLOG_DBG(\"%s: fopen failed for %s\", OVS_SOURCE_LOCATOR, str);\n+ return errno;\n+ }\n+\n+ p = fgets(str, sizeof str, fp);\n+ fclose(fp);\n+\n+ if (!p) {\n+ VLOG_DBG(\"%s: fgets failed for %s\", OVS_SOURCE_LOCATOR, str);\n+ return EIO;\n+ }\n+\n+ /* The string is terminated by \\n. Drop it. */\n+ if (outp) {\n+ len = strnlen(str, maxlen);\n+ if (maxlen <= len) {\n+ VLOG_DBG(\"%s: maxlen exceeded for %s/%s/%s\", OVS_SOURCE_LOCATOR,\n+ prefix, devname, suffix);\n+ return ERANGE;\n+ }\n+ ovs_strlcpy(outp, str, len);\n+ }\n+\n+ return 0;\n+}\n+\n+static int\n+get_phys_port_name(const char *devname, char *outp, size_t maxlen)\n+{\n+ return get_sys(\"class/net\", devname, \"phys_port_name\", outp, maxlen);\n+}\n+\n+static int\n+get_bonding_slaves(const char *devname, char *outp, size_t maxlen)\n+{\n+ return get_sys(\"class/net\", devname, \"bonding/slaves\", outp, maxlen);\n+}\n+\n+static doca_error_t\n+dev_get_rep(const char *name, struct doca_devinfo *devinfo, bool *found)\n+{\n+ char dev_name[DOCA_DEVINFO_IFACE_NAME_SIZE];\n+ struct doca_devinfo_rep **dev_list_rep;\n+ struct doca_dev *ddev;\n+ uint32_t nb_devs_rep;\n+ doca_error_t ret;\n+ int i;\n+\n+ ret = doca_dev_open(devinfo, &ddev);\n+ if (ret != DOCA_SUCCESS) {\n+ VLOG_ERR(\"%s: Failed to open device. Error: %d (%s)\", name, ret,\n+ doca_error_get_descr(ret));\n+ return ret;\n+ }\n+\n+ ret = doca_devinfo_rep_create_list(ddev, DOCA_DEVINFO_REP_FILTER_NET,\n+ &dev_list_rep, &nb_devs_rep);\n+ if (ret != DOCA_SUCCESS) {\n+ VLOG_ERR(\"%s: Failed to create a rep list. Error: %d (%s)\", name, ret,\n+ doca_error_get_descr(ret));\n+ goto err_list;\n+ }\n+\n+ for (i = 0; i < nb_devs_rep; i++) {\n+ ret = doca_devinfo_rep_get_iface_name(dev_list_rep[i], dev_name,\n+ sizeof dev_name);\n+ if (ret != DOCA_SUCCESS) {\n+ VLOG_ERR(\"%s: Failed to get rep iface name. Error: %d (%s)\", name,\n+ ret, doca_error_get_descr(ret));\n+ goto out;\n+ }\n+\n+ if (!strcmp(name, dev_name)) {\n+ *found = true;\n+ break;\n+ }\n+ }\n+\n+out:\n+ ret = doca_devinfo_rep_destroy_list(dev_list_rep);\n+ if (ret != DOCA_SUCCESS) {\n+ VLOG_ERR(\"%s: Failed to destroy rep list. Error: %d (%s)\", name, ret,\n+ doca_error_get_descr(ret));\n+ }\n+\n+err_list:\n+ ret = doca_dev_close(ddev);\n+ if (ret != DOCA_SUCCESS) {\n+ VLOG_ERR(\"%s: Failed to close dev. Error: %d (%s)\", name, ret,\n+ doca_error_get_descr(ret));\n+ }\n+\n+ return ret;\n+}\n+\n+static int\n+get_pci(const char *name, char *pci, size_t maxlen, bool *is_rep)\n+{\n+ struct doca_devinfo **dev_list;\n+ bool found = false;\n+ uint32_t nb_devs;\n+ doca_error_t ret;\n+ int i;\n+\n+ if (maxlen <= PCI_PRI_STR_SIZE) {\n+ return DOCA_ERROR_INVALID_VALUE;\n+ }\n+\n+ ret = doca_devinfo_create_list(&dev_list, &nb_devs);\n+ if (ret != DOCA_SUCCESS) {\n+ VLOG_ERR(\"%s: Failed to create a dev list. Error: %d (%s)\", name, ret,\n+ doca_error_get_descr(ret));\n+ return ret;\n+ }\n+\n+ /* Traverse the list of devices.\n+ * 1. If the device is not an ESW, continue.\n+ * 2. If the device name is what we look for, done.\n+ * 3. If not, try to find in the representors of this ESW.\n+ */\n+ for (i = 0; i < nb_devs; i++) {\n+ char dev_name[DOCA_DEVINFO_IFACE_NAME_SIZE];\n+ uint8_t net_supported;\n+\n+ /* If not an ESW, continue. */\n+ ret = doca_devinfo_rep_cap_is_filter_net_supported(\n+ dev_list[i], &net_supported);\n+ if (ret != DOCA_SUCCESS) {\n+ VLOG_ERR(\"%s: Failed to check rep_cap. Error: %d (%s)\", name, ret,\n+ doca_error_get_descr(ret));\n+ goto out;\n+ }\n+\n+ if (!net_supported) {\n+ continue;\n+ }\n+\n+ ret = doca_devinfo_get_pci_addr_str(dev_list[i], pci);\n+ if (ret != DOCA_SUCCESS) {\n+ VLOG_ERR(\"%s: Failed to get pci. Error: %d (%s)\", name, ret,\n+ doca_error_get_descr(ret));\n+ goto out;\n+ }\n+\n+ ret = doca_devinfo_get_iface_name(dev_list[i], dev_name,\n+ sizeof dev_name);\n+ if (ret != DOCA_SUCCESS) {\n+ VLOG_ERR(\"%s: Failed to get iface name. Error: %d (%s)\", name, ret,\n+ doca_error_get_descr(ret));\n+ goto out;\n+ }\n+\n+ if (!strcmp(name, dev_name)) {\n+ found = true;\n+ *is_rep = false;\n+ break;\n+ }\n+\n+ /* Search in its representor devices. */\n+ ret = dev_get_rep(name, dev_list[i], &found);\n+ if (ret != DOCA_SUCCESS) {\n+ goto out;\n+ }\n+\n+ if (found) {\n+ *is_rep = true;\n+ break;\n+ }\n+ }\n+\n+ if (!found) {\n+ ret = DOCA_ERROR_NOT_FOUND;\n+ VLOG_ERR(\"%s: Not found. Error: %d (%s)\", name, ret,\n+ doca_error_get_descr(ret));\n+ }\n+\n+out:\n+ doca_devinfo_destroy_list(dev_list);\n+ return ret;\n+}\n+\n+static int\n+get_dpdk_iface_name(const char *name, char iface[IFNAMSIZ])\n+{\n+ char phys_port_name[IFNAMSIZ];\n+ char slaves[PATH_MAX];\n+ char *save_ptr;\n+ char *lower;\n+\n+ /* In case the device is a bond, there is a lower_p0 symbolic link, with\n+ * the format of ../../.../<lower-dev>. Extract the lower device.\n+ */\n+\n+ if (get_bonding_slaves(name, slaves, sizeof slaves)) {\n+ goto fallback;\n+ }\n+\n+ lower = strtok_r(slaves, \" \", &save_ptr);\n+ while (lower) {\n+ if (!get_phys_port_name(lower, phys_port_name,\n+ sizeof phys_port_name) &&\n+ !strcmp(phys_port_name, \"p0\")) {\n+ break;\n+ }\n+ lower = strtok_r(NULL, \" \", &save_ptr);\n+ }\n+\n+ if (!lower) {\n+ goto fallback;\n+ }\n+\n+ /* Reached here if found a lower device p0. */\n+ ovs_strlcpy(iface, lower, IFNAMSIZ);\n+ goto out;\n+\n+fallback:\n+ ovs_strlcpy(iface, name, IFNAMSIZ);\n+out:\n+ return 0;\n+}\n+\n+struct netdev_doca *\n+netdev_doca_cast(const struct netdev *netdev)\n+{\n+ struct netdev_dpdk_common *common = netdev_dpdk_common_cast(netdev);\n+\n+ return CONTAINER_OF(common, struct netdev_doca, common);\n+}\n+\n+/* Allocates an area of 'sz' bytes from DPDK. The memory is zero'ed.\n+ *\n+ * Unlike xmalloc(), this function can return NULL on failure. */\n+static void *\n+doca_rte_mzalloc(const char *type, size_t sz)\n+{\n+ return rte_zmalloc(type, sz, CACHE_LINE_SIZE);\n+}\n+\n+static struct netdev *\n+netdev_doca_alloc(void)\n+{\n+ struct netdev_doca *dev;\n+\n+ dev = doca_rte_mzalloc(\"ovs_doca_netdev\", sizeof *dev);\n+ if (!dev) {\n+ return NULL;\n+ }\n+\n+ /* Upon the first port disable dpdk steering to allow doca to work. */\n+ if (!atomic_count_inc(&n_doca_ports)) {\n+ rte_pmd_mlx5_disable_steering();\n+ }\n+\n+ return &dev->common.up;\n+}\n+\n+static void\n+netdev_doca_dealloc(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+\n+ /* Upon the last doca port going down, enable back dpdk steering. */\n+ if (atomic_count_dec(&n_doca_ports) == 1) {\n+ rte_pmd_mlx5_enable_steering();\n+ }\n+\n+ rte_free(dev);\n+}\n+\n+static int\n+netdev_doca_set_mtu(struct netdev *netdev, int mtu)\n+{\n+ struct netdev_dpdk_common *common = netdev_dpdk_common_cast(netdev);\n+\n+ ovs_mutex_lock(&common->mutex);\n+ if (common->requested_mtu != mtu) {\n+ if (!netdev_doca_is_esw_mgr(netdev)) {\n+ VLOG_WARN(\"%s: setting requested MTU %d is ignored for \"\n+ \"representor\", netdev_get_name(netdev), mtu);\n+ goto out;\n+ }\n+\n+ common->requested_mtu = mtu;\n+ netdev_request_reconfigure(netdev);\n+ }\n+out:\n+ ovs_mutex_unlock(&common->mutex);\n+\n+ return 0;\n+}\n+\n+\n+static int\n+netdev_doca_dev_open_pci(struct rte_pci_addr *rte_pci, struct doca_dev **pdev)\n+{\n+ struct doca_devinfo **dev_list;\n+ char pci[PCI_PRI_STR_SIZE];\n+ uint8_t is_esw_manager = 0;\n+ uint8_t is_addr_equal = 0;\n+ uint32_t nb_devs;\n+ size_t i;\n+ int res;\n+\n+ /* Set default return value. */\n+ *pdev = NULL;\n+\n+ res = doca_devinfo_create_list(&dev_list, &nb_devs);\n+ if (res != DOCA_SUCCESS) {\n+ VLOG_ERR(\"Failed to load doca devices list. Error: %d (%s)\",\n+ res, doca_error_get_descr(res));\n+ return res;\n+ }\n+\n+ rte_pci_device_name(rte_pci, pci, sizeof pci);\n+ /* Search. */\n+ for (i = 0; i < nb_devs; i++) {\n+ res = doca_devinfo_is_equal_pci_addr(dev_list[i], pci, &is_addr_equal);\n+ if (res != DOCA_SUCCESS || !is_addr_equal) {\n+ continue;\n+ }\n+\n+ res = doca_dpdk_cap_is_rep_port_supported(dev_list[i],\n+ &is_esw_manager);\n+ if (res != DOCA_SUCCESS || !is_esw_manager) {\n+ continue;\n+ }\n+\n+ VLOG_DBG(\"Opening '%s'\", pci);\n+ res = doca_dev_open(dev_list[i], pdev);\n+ if (res != DOCA_SUCCESS) {\n+ VLOG_ERR(\"Failed to open DOCA device. Error: %d (%s)\",\n+ res, doca_error_get_descr(res));\n+ }\n+\n+ goto out;\n+ }\n+\n+ VLOG_WARN(\"No matching doca device found\");\n+ res = DOCA_ERROR_NOT_FOUND;\n+\n+out:\n+ doca_devinfo_destroy_list(dev_list);\n+ return res;\n+}\n+\n+static int\n+netdev_doca_esw_ctx_init(void *ctx_, void *arg_)\n+{\n+ struct netdev_doca_esw_ctx_arg *arg = arg_;\n+ struct netdev_doca_esw_ctx *ctx = ctx_;\n+\n+ if (netdev_doca_dev_open_pci(&arg->esw_key->rte_pci, &ctx->dev)) {\n+ return ENODEV;\n+ }\n+\n+ rte_pci_device_name(&arg->esw_key->rte_pci, ctx->pci_addr,\n+ sizeof ctx->pci_addr);\n+ ctx->cmd_fd = -1;\n+ memset(ctx->offload_queues, 0, sizeof ctx->offload_queues);\n+\n+ return 0;\n+}\n+\n+static void\n+netdev_doca_esw_ctx_uninit(void *ctx_)\n+{\n+ struct netdev_doca_esw_ctx *ctx = ctx_;\n+\n+ memset(ctx->pci_addr, 0, sizeof ctx->pci_addr);\n+}\n+\n+static struct ds *\n+netdev_doca_esw_ctx_dump(struct ds *s, void *key_, void *ctx OVS_UNUSED)\n+{\n+ struct netdev_doca_esw_key *key = key_;\n+ char pci_addr[PCI_PRI_STR_SIZE];\n+\n+ rte_pci_device_name(&key->rte_pci, pci_addr, sizeof pci_addr);\n+ ds_put_format(s, \"pci=%s\", pci_addr);\n+\n+ return s;\n+}\n+\n+static int\n+netdev_doca_class_init(void)\n+{\n+ static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;\n+ static struct netdev_dpdk_watchdog_params watchdog_params = {\n+ .mutex = &doca_mutex,\n+ .list = &doca_list,\n+ };\n+\n+ if (!ovsthread_once_start(&once)) {\n+ return 0;\n+ }\n+\n+ ovs_thread_create(\"doca_watchdog\", netdev_dpdk_watchdog, &watchdog_params);\n+ netdev_doca_esw_rfm = refmap_create(\"netdev-doca-esw\",\n+ sizeof(struct netdev_doca_esw_key),\n+ sizeof(struct netdev_doca_esw_ctx),\n+ netdev_doca_esw_ctx_init,\n+ netdev_doca_esw_ctx_uninit,\n+ netdev_doca_esw_ctx_dump);\n+\n+ ovsthread_once_done(&once);\n+ return 0;\n+}\n+\n+/* Extract the PCI part from 'devargs' to rte_pci.\n+ * Return -EINVAL for error or 0 for success.\n+ */\n+static int\n+netdev_doca_parse_dpdk_devargs_pci(const char *devargs,\n+ struct rte_pci_addr *rte_pci)\n+{\n+ struct rte_devargs da;\n+ int rv = 0;\n+\n+ if (rte_devargs_parse(&da, devargs)) {\n+ VLOG_ERR(\"%s: rte_devargs_parse failed for %s\",\n+ OVS_SOURCE_LOCATOR, devargs);\n+ return EINVAL;\n+ }\n+\n+ if (rte_pci_addr_parse(da.name, rte_pci)) {\n+ VLOG_ERR(\"%s: rte_pci_addr_parse failed for %s\",\n+ OVS_SOURCE_LOCATOR, da.name);\n+ rv = EINVAL;\n+ goto out;\n+ }\n+\n+out:\n+ rte_devargs_reset(&da);\n+ return rv;\n+}\n+\n+/* Changing the netdev of the ESW require changes of its representor ports.\n+ * This helper traverses them with a callback to run on each representor.\n+ * For each representor, request a reconfigure of it.\n+ */\n+\n+static void\n+netdev_doca_do_foreach_representor(struct netdev_doca *esw_dev,\n+ bool (*cb)(struct netdev_doca *))\n+ OVS_REQUIRES(doca_mutex)\n+{\n+ bool need_reconfigure = false;\n+ struct rte_pci_addr esw_pci;\n+ struct rte_pci_addr rep_pci;\n+ struct netdev_doca *dev;\n+\n+ if (netdev_doca_parse_dpdk_devargs_pci(esw_dev->common.devargs,\n+ &esw_pci)) {\n+ return;\n+ }\n+\n+ LIST_FOR_EACH (dev, common.list_node, &doca_list) {\n+ if (esw_dev == dev) {\n+ continue;\n+ }\n+\n+ if (!dev->common.devargs ||\n+ netdev_doca_parse_dpdk_devargs_pci(dev->common.devargs,\n+ &rep_pci)) {\n+ continue;\n+ }\n+\n+ if (rte_pci_addr_cmp(&rep_pci, &esw_pci)) {\n+ continue;\n+ }\n+\n+ ovs_mutex_lock(&dev->common.mutex);\n+ need_reconfigure |= cb(dev);\n+ ovs_mutex_unlock(&dev->common.mutex);\n+ netdev_request_reconfigure(&dev->common.up);\n+ }\n+\n+ if (need_reconfigure) {\n+ /* If a representor is reconfigured a result of its ESW manager\n+ * change, it might not be synced in the bridge's database. Signal it\n+ * to reconfigure, to make it right.\n+ */\n+ rtnetlink_report_link();\n+ }\n+}\n+\n+static void\n+netdev_doca_dev_close(struct netdev_doca *dev)\n+{\n+ struct netdev_dpdk_common *common = &dev->common;\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+ struct rte_eth_dev_info dev_info;\n+ char *pci_addr;\n+ bool last;\n+ int err;\n+\n+ memset(&dev_info, 0, sizeof dev_info);\n+\n+ if (rte_eth_dev_is_valid_port(common->port_id)) {\n+ err = rte_eth_dev_info_get(common->port_id, &dev_info);\n+ if (err) {\n+ VLOG_ERR(\"Failed to get info of port \"DPDK_PORT_ID_FMT\": %s\",\n+ common->port_id, rte_strerror(-err));\n+ }\n+\n+ err = rte_eth_dev_close(common->port_id);\n+ if (err) {\n+ VLOG_ERR(\"Failed to close port \"DPDK_PORT_ID_FMT\": %s\",\n+ common->port_id, rte_strerror(-err));\n+ }\n+ }\n+\n+ if (!esw) {\n+ return;\n+ }\n+\n+ pci_addr = xstrdup(esw->pci_addr);\n+ if (common->port_id != dev->esw_mgr_port_id && dev->dev_rep) {\n+ VLOG_DBG(\"%s: Closing doca dev_rep for port_id \"DPDK_PORT_ID_FMT\n+ \". %p\", netdev_get_name(&common->up),\n+ common->port_id, dev->dev_rep);\n+ err = doca_dev_rep_close(dev->dev_rep);\n+ if (err) {\n+ VLOG_ERR(\"Failed to close doca dev_rep with port id \"\n+ DPDK_PORT_ID_FMT\". Error: %d (%s)\",\n+ common->port_id, err, doca_error_get_descr(err));\n+ }\n+\n+ dev->dev_rep = NULL;\n+ }\n+\n+ last = refmap_unref(netdev_doca_esw_rfm, esw);\n+ /* The last is the ESW. */\n+ if (last && esw->dev) {\n+ /* The esw->cmd_fd is closed inside. */\n+ if (dev_info.device) {\n+ err = rte_dev_remove(dev_info.device);\n+ if (err) {\n+ VLOG_ERR(\"Failed to remove device %s: %s\", common->devargs,\n+ rte_strerror(-err));\n+ }\n+ }\n+\n+ VLOG_DBG(\"Closing '%s'\", pci_addr);\n+ err = doca_dev_close(esw->dev);\n+ if (err) {\n+ VLOG_ERR(\"Failed to close doca dev %s. Error: %d (%s)\", pci_addr,\n+ err, doca_error_get_descr(err));\n+ }\n+\n+ esw->dev = NULL;\n+ esw->cmd_fd = -1;\n+ }\n+\n+ dev->esw_ctx = NULL;\n+ free(pci_addr);\n+}\n+\n+static bool\n+netdev_doca_rep_stop(struct netdev_doca *dev)\n+ OVS_REQUIRES(doca_mutex)\n+{\n+ struct netdev_dpdk_common *common = &dev->common;\n+\n+ if (!dpdk_dev_is_started(common)) {\n+ return false;\n+ }\n+\n+ netdev_doca_port_stop(&common->up);\n+ netdev_doca_dev_close(dev);\n+ common->port_id = DPDK_ETH_PORT_ID_INVALID;\n+ dev->esw_mgr_port_id = DPDK_ETH_PORT_ID_INVALID;\n+ common->attached = false;\n+\n+ return true;\n+}\n+\n+static int\n+netdev_doca_port_stop(struct netdev *netdev)\n+ OVS_REQUIRES(doca_mutex)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_dpdk_common *common = &dev->common;\n+ bool started = dpdk_dev_is_started(common);\n+ int err = 0;\n+\n+ if (started) {\n+ if (netdev_doca_get_esw_mgr_port_id(netdev) == common->port_id) {\n+ netdev_doca_do_foreach_representor(dev, netdev_doca_rep_stop);\n+ }\n+\n+ VLOG_INFO(\"%s: Stopping '%s', port_id=\"DPDK_PORT_ID_FMT,\n+ netdev_get_name(netdev), common->devargs,\n+ common->port_id);\n+ atomic_store(&common->started, false);\n+ }\n+\n+ netdev_doca_rss_entries_uninit(netdev);\n+ netdev_doca_egress_entry_uninit(netdev);\n+\n+ if (common->port_id == dev->esw_mgr_port_id) {\n+ netdev_doca_esw_port_uninit(netdev);\n+ }\n+\n+ if (dev->port) {\n+ err = doca_flow_port_stop(dev->port);\n+ dev->port = NULL;\n+ }\n+\n+ rte_eth_dev_stop(common->port_id);\n+\n+ return err;\n+}\n+\n+static void\n+common_destruct(struct netdev_doca *dev)\n+ OVS_REQUIRES(doca_mutex)\n+ OVS_EXCLUDED(dev->common.mutex)\n+{\n+ struct netdev_dpdk_common *common = &dev->common;\n+\n+ rte_free(common->tx_q);\n+ ovs_list_remove(&common->list_node);\n+ free(dev->sw_tx_stats);\n+ free(common->sw_stats);\n+ ovs_mutex_destroy(&common->mutex);\n+}\n+\n+static void\n+netdev_doca_destruct(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_dpdk_common *common = &dev->common;\n+ bool is_esw_mgr = netdev_doca_is_esw_mgr(netdev);\n+\n+ ovs_mutex_lock(&doca_mutex);\n+\n+ netdev_doca_port_stop(netdev);\n+\n+ if (common->attached) {\n+ netdev_doca_dev_close(dev);\n+ common->port_id = DPDK_ETH_PORT_ID_INVALID;\n+\n+ VLOG_INFO(\"Device '%s' has been removed\", common->devargs);\n+ }\n+\n+ ovs_mutex_lock(&common->mutex);\n+ netdev_dpdk_clear_xstats(common);\n+ ovs_mutex_unlock(&common->mutex);\n+ free(common->devargs);\n+ common_destruct(dev);\n+ if (is_esw_mgr) {\n+ rte_mempool_free(common->mp);\n+ }\n+\n+ common->mp = NULL;\n+\n+ ovs_mutex_unlock(&doca_mutex);\n+}\n+\n+static int\n+netdev_doca_get_sw_custom_stats(const struct netdev *netdev,\n+ struct netdev_custom_stats *custom_stats)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_dpdk_common *common = &dev->common;\n+ int i, n;\n+\n+#define SW_CSTATS \\\n+ SW_CSTAT(tx_retries) \\\n+ SW_CSTAT(tx_failure_drops) \\\n+ SW_CSTAT(tx_mtu_exceeded_drops) \\\n+ SW_CSTAT(tx_invalid_hwol_drops)\n+\n+#define SW_CSTAT(NAME) + 1\n+ custom_stats->size = SW_CSTATS;\n+#undef SW_CSTAT\n+ custom_stats->counters = xcalloc(custom_stats->size,\n+ sizeof *custom_stats->counters);\n+\n+ ovs_mutex_lock(&common->mutex);\n+\n+ rte_spinlock_lock(&common->stats_lock);\n+ i = 0;\n+#define SW_CSTAT(NAME) \\\n+ custom_stats->counters[i++].value = common->sw_stats->NAME;\n+ SW_CSTATS;\n+#undef SW_CSTAT\n+ rte_spinlock_unlock(&common->stats_lock);\n+\n+ ovs_mutex_unlock(&common->mutex);\n+\n+ i = 0;\n+ n = 0;\n+#define SW_CSTAT(NAME) \\\n+ if (custom_stats->counters[i].value != UINT64_MAX) { \\\n+ ovs_strlcpy(custom_stats->counters[n].name, \\\n+ \"ovs_\"#NAME, NETDEV_CUSTOM_STATS_NAME_SIZE); \\\n+ custom_stats->counters[n].value = custom_stats->counters[i].value; \\\n+ n++; \\\n+ } \\\n+ i++;\n+ SW_CSTATS;\n+#undef SW_CSTAT\n+\n+ custom_stats->size = n;\n+ return 0;\n+}\n+\n+static int\n+netdev_doca_get_custom_stats(const struct netdev *netdev,\n+ struct netdev_custom_stats *custom_stats)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw_ctx = dev->esw_ctx;\n+ struct netdev_dpdk_common *common = &dev->common;\n+ dpdk_port_t port_id = common->port_id;\n+ struct doca_flow_resource_query stats;\n+ struct netdev_custom_counter *counter;\n+ uint64_t n_sw_packets, n_sw_bytes;\n+ uint64_t n_packets, n_bytes;\n+ int n_txq = netdev->n_txq;\n+ unsigned int n_rxq;\n+ int sw_stats_size;\n+ enum {\n+ PACKETS,\n+ BYTES,\n+ };\n+ int err;\n+ int i;\n+\n+ if (!dpdk_dev_is_started(common)) {\n+ return EAGAIN;\n+ }\n+\n+ netdev_doca_get_sw_custom_stats(netdev, custom_stats);\n+\n+ sw_stats_size = custom_stats->size;\n+ n_rxq = dev->esw_ctx->n_rxq;\n+ custom_stats->size += 2 * (NETDEV_DOCA_RSS_NUM_ENTRIES + n_rxq + n_txq +\n+ 1);\n+\n+ custom_stats->counters = xrealloc(custom_stats->counters,\n+ custom_stats->size *\n+ sizeof *custom_stats->counters);\n+ counter = &custom_stats->counters[sw_stats_size];\n+\n+ for (i = 0; i < NETDEV_DOCA_RSS_NUM_ENTRIES; i++, counter += 2) {\n+ const char *stats_name = netdev_doca_stats_name(i);\n+\n+ err = doca_flow_resource_query_entry(dev->rss_entries[i], &stats);\n+ if (err) {\n+ VLOG_ERR(\"%s: Failed to query '%s' RSS entry. Error: %d (%s)\",\n+ common->devargs, stats_name, err,\n+ doca_error_get_descr(err));\n+ return err;\n+ }\n+\n+ counter[PACKETS].value = stats.counter.total_pkts;\n+ snprintf(counter[PACKETS].name, NETDEV_CUSTOM_STATS_NAME_SIZE,\n+ \"%s_packets\", stats_name);\n+ counter[BYTES].value = stats.counter.total_bytes;\n+ snprintf(counter[BYTES].name, NETDEV_CUSTOM_STATS_NAME_SIZE,\n+ \"%s_bytes\", stats_name);\n+ }\n+\n+ n_sw_packets = 0;\n+ n_sw_bytes = 0;\n+\n+ for (i = 0; i < n_rxq; i++, counter += 2) {\n+ atomic_read_relaxed(&esw_ctx->port_queues[port_id][i].n_packets,\n+ &n_packets);\n+ atomic_read_relaxed(&esw_ctx->port_queues[port_id][i].n_bytes,\n+ &n_bytes);\n+\n+ n_sw_packets += n_packets;\n+ n_sw_bytes += n_bytes;\n+\n+ counter[PACKETS].value = n_packets;\n+ snprintf(counter[PACKETS].name, NETDEV_CUSTOM_STATS_NAME_SIZE,\n+ \"rx_q%d_packets\", i);\n+ counter[BYTES].value = n_bytes;\n+ snprintf(counter[BYTES].name, NETDEV_CUSTOM_STATS_NAME_SIZE,\n+ \"rx_q%d_bytes\", i);\n+ }\n+\n+ counter[PACKETS].value = n_sw_packets;\n+ snprintf(counter[PACKETS].name, NETDEV_CUSTOM_STATS_NAME_SIZE,\n+ \"sw_rx_packets\");\n+ counter[BYTES].value = n_sw_bytes;\n+ snprintf(counter[BYTES].name, NETDEV_CUSTOM_STATS_NAME_SIZE,\n+ \"sw_rx_bytes\");\n+ counter += 2;\n+\n+ for (i = 0; i < n_txq; i++, counter += 2) {\n+ atomic_read_relaxed(&dev->sw_tx_stats[i].n_packets, &n_packets);\n+ atomic_read_relaxed(&dev->sw_tx_stats[i].n_bytes, &n_bytes);\n+\n+ counter[PACKETS].value = n_packets;\n+ snprintf(counter[PACKETS].name, NETDEV_CUSTOM_STATS_NAME_SIZE,\n+ \"tx_q%d_packets\", i);\n+ counter[BYTES].value = n_bytes;\n+ snprintf(counter[BYTES].name, NETDEV_CUSTOM_STATS_NAME_SIZE,\n+ \"tx_q%d_bytes\", i);\n+ }\n+\n+ return 0;\n+}\n+\n+static int\n+netdev_doca_get_status(const struct netdev *netdev, struct smap *args)\n+{\n+ return netdev_dpdk_get_status__(netdev, &doca_mutex, args);\n+}\n+\n+/* Mempools are allocated for ESW managers only.\n+ * Estimation of number of mbufs required for this port:\n+ * (<packets required to fill the device rxqs>\n+ * + <packets that could be stuck on other ports txqs>\n+ * + <packets in the pmd threads>\n+ * + <headroom for per-lcore mempool caches>)\n+ */\n+static uint32_t\n+doca_calculate_mbufs(struct netdev_doca *dev)\n+{\n+ struct netdev_dpdk_common *common = &dev->common;\n+ uint32_t n_mbufs;\n+\n+ n_mbufs = common->requested_n_rxq * common->requested_rxq_size\n+ + common->requested_n_txq * common->requested_txq_size\n+ + MIN(RTE_MAX_LCORE, common->requested_n_rxq) * NETDEV_MAX_BURST\n+ + MIN(RTE_MAX_LCORE, 1 + common->requested_n_rxq) * MP_CACHE_SZ;\n+\n+ return n_mbufs;\n+}\n+\n+static int\n+netdev_doca_mempool_configure(struct netdev_doca *dev)\n+ OVS_REQUIRES(dev->common.mutex)\n+{\n+ struct netdev_dpdk_common *common = &dev->common;\n+ uint32_t buf_size = netdev_dpdk_buf_size(common->requested_mtu);\n+ const char *netdev_name = netdev_get_name(&common->up);\n+ int socket_id = common->requested_socket_id;\n+ struct rte_mempool *mp;\n+ uint32_t mbuf_size;\n+ uint32_t n_mbufs;\n+ int mtu;\n+\n+\n+ if (!netdev_doca_is_esw_mgr(&common->up)) {\n+ struct netdev_doca *esw_dev;\n+\n+ esw_dev = netdev_doca_cast(dev->esw_ctx->esw_netdev);\n+\n+ common->mp = esw_dev->common.mp;\n+ common->mtu = esw_dev->common.mtu;\n+ common->requested_mtu = common->mtu;\n+ common->max_packet_len = esw_dev->common.max_packet_len;\n+ return 0;\n+ }\n+\n+ mtu = FRAME_LEN_TO_MTU(buf_size);\n+ mbuf_size = MTU_TO_FRAME_LEN(mtu);\n+ n_mbufs = doca_calculate_mbufs(dev);\n+\n+ mp = netdev_dpdk_mp_create_pool(netdev_name, n_mbufs, mbuf_size,\n+ socket_id);\n+ if (!mp) {\n+ VLOG_ERR(\"%s: Failed to create mempool\", netdev_name);\n+ return ENOMEM;\n+ }\n+\n+ VLOG_DBG(\"%s: Allocated a mempool of %u mbufs of size %u \"\n+ \"on socket %d with %d Rx and %d Tx queues, \"\n+ \"cache line size of %u\",\n+ netdev_name, n_mbufs, mbuf_size, socket_id,\n+ common->requested_n_rxq, common->requested_n_txq,\n+ RTE_CACHE_LINE_SIZE);\n+ common->mp = mp;\n+ common->mtu = common->requested_mtu;\n+ common->socket_id = common->requested_socket_id;\n+ common->max_packet_len = MTU_TO_FRAME_LEN(common->mtu);\n+\n+ return 0;\n+}\n+\n+static int\n+dpdk_eth_dev_port_config_complete(struct netdev_doca *dev,\n+ int n_rxq, int n_txq)\n+{\n+ struct netdev_dpdk_common *common = &dev->common;\n+ uint16_t conf_mtu;\n+ int diag;\n+\n+ free(dev->sw_tx_stats);\n+ dev->sw_tx_stats = xcalloc(n_txq, sizeof *dev->sw_tx_stats);\n+ for (int i = 0; i < n_txq; i++) {\n+ atomic_init(&dev->sw_tx_stats[i].n_packets, 0);\n+ atomic_init(&dev->sw_tx_stats[i].n_bytes, 0);\n+ }\n+\n+ common->up.n_rxq = n_rxq;\n+ common->up.n_txq = n_txq;\n+\n+ diag = rte_eth_dev_set_mtu(common->port_id, common->mtu);\n+ if (diag) {\n+ /* A device may not support rte_eth_dev_set_mtu, in this case\n+ * flag a warning to the user and include the devices configured\n+ * MTU value that will be used instead. */\n+ if (-ENOTSUP == diag) {\n+ rte_eth_dev_get_mtu(common->port_id, &conf_mtu);\n+ VLOG_WARN(\"Interface %s does not support MTU configuration, \"\n+ \"max packet size supported is %\"PRIu16\".\",\n+ common->up.name, conf_mtu);\n+ } else {\n+ VLOG_ERR(\"Interface %s MTU (%d) setup error: %s\",\n+ common->up.name, common->mtu, rte_strerror(-diag));\n+ }\n+ }\n+\n+ return diag;\n+}\n+\n+static int\n+dpdk_eth_dev_port_config(struct netdev_doca *dev,\n+ const struct rte_eth_dev_info *info,\n+ int n_rxq, int n_txq)\n+{\n+ struct netdev_dpdk_common *common = &dev->common;\n+ struct rte_eth_conf conf = port_conf;\n+ int diag = 0;\n+ int i;\n+\n+ netdev_dpdk_build_port_conf(common, info, &conf);\n+\n+ if (!netdev_doca_is_esw_mgr(&common->up)) {\n+ rte_eth_dev_configure(common->port_id, 0, 0, &conf);\n+ return dpdk_eth_dev_port_config_complete(dev, n_rxq, n_txq);\n+ }\n+\n+ /* A device may report more queues than it makes available (this has\n+ * been observed for Intel xl710, which reserves some of them for\n+ * SRIOV): rte_eth_*_queue_setup will fail if a queue is not\n+ * available. When this happens we can retry the configuration\n+ * and request less queues. */\n+ while (n_rxq && n_txq) {\n+ if (diag) {\n+ VLOG_INFO(\"Retrying setup with (rxq:%d txq:%d)\", n_rxq, n_txq);\n+ }\n+\n+ diag = rte_eth_dev_configure(common->port_id, n_rxq,\n+ n_txq, &conf);\n+ if (diag) {\n+ VLOG_WARN(\"Interface %s eth_dev setup error %s\\n\",\n+ common->up.name, rte_strerror(-diag));\n+ break;\n+ }\n+\n+ for (i = 0; i < n_txq; i++) {\n+ diag = rte_eth_tx_queue_setup(common->port_id, i, common->txq_size,\n+ common->socket_id, NULL);\n+ if (diag) {\n+ VLOG_INFO(\"Interface %s unable to setup txq(%d): %s\",\n+ common->up.name, i, rte_strerror(-diag));\n+ break;\n+ }\n+ }\n+\n+ if (i != n_txq) {\n+ /* Retry with less tx queues. */\n+ n_txq = i;\n+ continue;\n+ }\n+\n+ for (i = 0; i < n_rxq; i++) {\n+ diag = rte_eth_rx_queue_setup(common->port_id, i, common->rxq_size,\n+ common->socket_id, NULL,\n+ common->mp);\n+ if (diag) {\n+ VLOG_INFO(\"Interface %s unable to setup rxq(%d): %s\",\n+ common->up.name, i, rte_strerror(-diag));\n+ break;\n+ }\n+ }\n+\n+ if (i != n_rxq) {\n+ /* Retry with less rx queues. */\n+ n_rxq = i;\n+ continue;\n+ }\n+\n+ return dpdk_eth_dev_port_config_complete(dev, n_rxq, n_txq);\n+ }\n+\n+ return diag;\n+}\n+\n+static int\n+netdev_doca_esw_key_parse(const char *devargs,\n+ struct netdev_doca_esw_key *esw_key)\n+{\n+ struct rte_pci_addr *rte_pci = &esw_key->rte_pci;\n+\n+ memset(esw_key, 0, sizeof *esw_key);\n+ return netdev_doca_parse_dpdk_devargs_pci(devargs, rte_pci);\n+}\n+\n+static int\n+netdev_doca_dev_probe(struct netdev_doca *dev, const char *devargs)\n+{\n+ struct ds rte_devargs = DS_EMPTY_INITIALIZER;\n+ struct netdev_doca_esw_ctx_arg ctx_arg;\n+ struct netdev_doca_esw_key esw_key;\n+ struct ibv_pd *pd;\n+ int rv = 0;\n+\n+ ovs_assert(!dev->esw_ctx);\n+\n+ if (netdev_doca_esw_key_parse(devargs, &esw_key)) {\n+ VLOG_ERR(\"%s: esw_key_parse failed for %s\",\n+ OVS_SOURCE_LOCATOR, devargs);\n+ return EINVAL;\n+ }\n+\n+ ctx_arg = (struct netdev_doca_esw_ctx_arg) {\n+ .esw_key = &esw_key,\n+ .dev = dev,\n+ };\n+\n+ dev->esw_ctx = refmap_ref(netdev_doca_esw_rfm, &esw_key, &ctx_arg);\n+ if (!dev->esw_ctx) {\n+ VLOG_ERR(\"Could not get esw context for %s\", devargs);\n+ return EINVAL;\n+ }\n+\n+ if (doca_rdma_bridge_get_dev_pd(dev->esw_ctx->dev, &pd)) {\n+ VLOG_ERR(\"Could not get pd for %s\", devargs);\n+ rv = EINVAL;\n+ goto out;\n+ }\n+\n+ if (dev->esw_ctx->cmd_fd == -1) {\n+ dev->esw_ctx->cmd_fd = dup(pd->context->cmd_fd);\n+ if (dev->esw_ctx->cmd_fd == -1) {\n+ VLOG_ERR(\"Could not dup fd for %s. Error %s\", devargs,\n+ ovs_strerror(errno));\n+ rv = EBADF;\n+ goto out;\n+ }\n+ }\n+\n+ ds_put_format(&rte_devargs, \"%s,cmd_fd=%d,pd_handle=%u\", devargs,\n+ dev->esw_ctx->cmd_fd, pd->handle);\n+\n+ VLOG_DBG(\"Probing '%s'\", ds_cstr(&rte_devargs));\n+ if (rte_dev_probe(ds_cstr(&rte_devargs))) {\n+ VLOG_ERR(\"%s: rte_dev_probe failed for %s\", OVS_SOURCE_LOCATOR,\n+ ds_cstr(&rte_devargs));\n+ close(dev->esw_ctx->cmd_fd);\n+ dev->esw_ctx->cmd_fd = -1;\n+ rv = ENODEV;\n+ goto out;\n+ }\n+\n+out:\n+ ds_destroy(&rte_devargs);\n+ if (rv) {\n+ netdev_doca_dev_close(dev);\n+ }\n+ return rv;\n+}\n+\n+static int\n+netdev_doca_port_start(struct netdev *netdev)\n+ OVS_REQUIRES(doca_mutex)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_dpdk_common *common = &dev->common;\n+ const char *devargs = common->devargs;\n+ dpdk_port_t port_id = common->port_id;\n+ struct doca_flow_port_cfg *port_cfg;\n+ struct netdev_doca_esw_ctx *esw;\n+ int err;\n+\n+ if (!rte_eth_dev_is_valid_port(dev->esw_mgr_port_id)) {\n+ VLOG_ERR(\"Cannot start port \"DPDK_PORT_ID_FMT\" '%s', invalid proxy \"\n+ \"port\", port_id,\n+ devargs);\n+ return DOCA_ERROR_NOT_FOUND;\n+ }\n+\n+ err = doca_flow_port_cfg_create(&port_cfg);\n+ if (err) {\n+ VLOG_ERR(\"Failed to create doca flow port_cfg. Error: %d (%s)\",\n+ err, doca_error_get_descr(err));\n+ return err;\n+ }\n+\n+ esw = dev->esw_ctx;\n+ if (!esw) {\n+ err = DOCA_ERROR_INVALID_VALUE;\n+ goto out;\n+ }\n+\n+ err = doca_flow_port_cfg_set_port_id(port_cfg, port_id);\n+ if (err) {\n+ VLOG_ERR(\"%s: Failed to set doca flow port_cfg port_id \"\n+ DPDK_PORT_ID_FMT\". Error: %d (%s)\",\n+ netdev_get_name(netdev), port_id, err,\n+ doca_error_get_descr(err));\n+ goto out;\n+ }\n+\n+ if (!netdev_doca_is_esw_mgr(netdev)) {\n+ err = doca_dpdk_open_dev_rep_by_port_id(port_id, esw->dev,\n+ &dev->dev_rep);\n+ if (err) {\n+ VLOG_ERR(\"%s: Failed to open doca dev_rep for port_id \"\n+ DPDK_PORT_ID_FMT\". Error: %d (%s)\",\n+ netdev_get_name(netdev), port_id, err,\n+ doca_error_get_descr(err));\n+ goto out;\n+ }\n+\n+ VLOG_DBG(\"%s: Opening doca dev_rep for port_id \"DPDK_PORT_ID_FMT\n+ \". %p\", netdev_get_name(netdev), port_id, dev->dev_rep);\n+\n+ err = doca_flow_port_cfg_set_dev_rep(port_cfg, dev->dev_rep);\n+ if (err) {\n+ VLOG_ERR(\"%s: Failed to set doca flow port_cfg dev_rep. \"\n+ \"Error: %d (%s)\", netdev_get_name(netdev), err,\n+ doca_error_get_descr(err));\n+ goto out;\n+ }\n+ }\n+\n+ err = doca_flow_port_cfg_set_dev(port_cfg, esw->dev);\n+ if (err) {\n+ VLOG_ERR(\"%s: Failed to set doca flow port_cfg dev. Error: %d (%s)\",\n+ netdev_get_name(netdev), err, doca_error_get_descr(err));\n+ goto out;\n+ }\n+\n+ VLOG_INFO(\"%s: Starting '%s', port_id=\"DPDK_PORT_ID_FMT,\n+ netdev_get_name(netdev), devargs, port_id);\n+ if (common->port_id == dev->esw_mgr_port_id) {\n+ err = doca_flow_port_cfg_set_actions_mem_size(\n+ port_cfg, NETDEV_DOCA_ACTIONS_MEM_SIZE);\n+ if (err) {\n+ VLOG_ERR(\"Failed set_actions_mem_size for port_id \"\n+ DPDK_PORT_ID_FMT\". Error: %d (%s)\",\n+ common->port_id, err,\n+ doca_error_get_descr(err));\n+ goto out;\n+ }\n+\n+ err = rte_eth_dev_start(common->port_id);\n+ if (err) {\n+ VLOG_ERR(\"Failed to start dpdk port_id \"DPDK_PORT_ID_FMT\n+ \". Error: %s\", common->port_id,\n+ rte_strerror(-err));\n+ err = DOCA_ERROR_DRIVER;\n+ goto out;\n+ }\n+\n+ err = doca_flow_port_cfg_set_nr_resources(port_cfg,\n+ DOCA_FLOW_RESOURCE_COUNTER,\n+ ovs_doca_max_counters());\n+ if (err) {\n+ VLOG_ERR(\"Failed set_nr_resources counters for port_id \"\n+ DPDK_PORT_ID_FMT\". Error: %d (%s)\",\n+ common->port_id, err,\n+ doca_error_get_descr(err));\n+ goto out;\n+ }\n+ }\n+\n+ err = doca_flow_port_start(port_cfg, &dev->port);\n+ if (err) {\n+ VLOG_ERR(\"Failed to start doca flow port_id \"DPDK_PORT_ID_FMT\n+ \". Error: %d (%s)\", port_id, err,\n+ doca_error_get_descr(err));\n+ goto out;\n+ }\n+\n+ if (common->port_id == dev->esw_mgr_port_id) {\n+ err = netdev_doca_esw_init(netdev);\n+ if (err) {\n+ goto out;\n+ }\n+ }\n+\n+ err = netdev_doca_egress_entry_init(dev);\n+ if (err) {\n+ goto out;\n+ }\n+\n+ err = netdev_doca_rss_entries_init(netdev);\n+ if (err) {\n+ goto out;\n+ }\n+\n+out:\n+ doca_flow_port_cfg_destroy(port_cfg);\n+ if (err) {\n+ netdev_doca_port_stop(netdev);\n+ }\n+ return err;\n+}\n+\n+static bool\n+netdev_doca_rep_reconfigure(struct netdev_doca *dev OVS_UNUSED)\n+{\n+ return true;\n+}\n+\n+static int\n+dpdk_eth_dev_init(struct netdev_doca *dev)\n+ OVS_REQUIRES(doca_mutex)\n+ OVS_REQUIRES(dev->common.mutex)\n+{\n+ struct netdev_dpdk_common *common = &dev->common;\n+ struct netdev *netdev = &common->up;\n+ struct rte_ether_addr eth_addr;\n+ struct rte_eth_dev_info info;\n+ int n_rxq, n_txq;\n+ int diag;\n+\n+ diag = rte_eth_dev_info_get(common->port_id, &info);\n+ if (diag < 0) {\n+ VLOG_ERR(\"Interface %s rte_eth_dev_info_get error: %s\",\n+ common->up.name, rte_strerror(-diag));\n+ return -diag;\n+ }\n+\n+ common->is_representor = common->devargs &&\n+ strstr(common->devargs, \"representor=\");\n+\n+ netdev_dpdk_detect_hw_ol_features(common, &info);\n+\n+ n_rxq = MIN(info.max_rx_queues, common->up.n_rxq);\n+ n_txq = MIN(info.max_tx_queues, common->up.n_txq);\n+\n+ diag = dpdk_eth_dev_port_config(dev, &info, n_rxq, n_txq);\n+ if (diag) {\n+ VLOG_ERR(\"Interface %s(rxq:%d txq:%d lsc interrupt mode:%s) \"\n+ \"configure error: %s\",\n+ common->up.name, n_rxq, n_txq,\n+ common->lsc_interrupt_mode ? \"true\" : \"false\",\n+ rte_strerror(-diag));\n+ return -diag;\n+ }\n+\n+ /* When a representor is probed before its ESW, dpdk implicitly\n+ * probes the latter, thus probe is not called from\n+ * netdev_doca_process_devargs(). In this case we call probe at\n+ * netdev_doca_port_start(), and make sure the device is marked as\n+ * \"attached\".\n+ */\n+ common->attached = true;\n+ diag = netdev_doca_port_start(netdev);\n+ if (diag) {\n+ VLOG_ERR(\"Failed to init DOCA port %s port_id \"DPDK_PORT_ID_FMT\n+ \". Error: %d (%s)\", netdev_get_name(netdev),\n+ common->port_id, diag, doca_error_get_descr(diag));\n+ return diag;\n+ }\n+\n+ atomic_store(&common->started, true);\n+\n+ netdev_dpdk_configure_xstats(common);\n+\n+ memset(ð_addr, 0x0, sizeof(eth_addr));\n+ rte_eth_macaddr_get(common->port_id, ð_addr);\n+ VLOG_INFO_RL(&rl, \"Port %d: \"ETH_ADDR_FMT,\n+ common->port_id, ETH_ADDR_BYTES_ARGS(eth_addr.addr_bytes));\n+\n+ memcpy(common->hwaddr.ea, eth_addr.addr_bytes, ETH_ADDR_LEN);\n+ if (rte_eth_link_get_nowait(common->port_id, &common->link) < 0) {\n+ memset(&common->link, 0, sizeof common->link);\n+ }\n+\n+ /* Upon success of esw_mgr port, update the representor's field of it. */\n+ if (netdev_doca_get_esw_mgr_port_id(netdev) == common->port_id) {\n+ netdev_doca_do_foreach_representor(dev, netdev_doca_rep_reconfigure);\n+ }\n+\n+ return 0;\n+}\n+\n+static struct doca_tx_queue *\n+netdev_doca_alloc_txq(unsigned int n_txqs)\n+{\n+ struct doca_tx_queue *txqs;\n+ unsigned i;\n+\n+ txqs = doca_rte_mzalloc(\"ovs_doca_txq\", n_txqs * sizeof *txqs);\n+ if (txqs) {\n+ for (i = 0; i < n_txqs; i++) {\n+ rte_spinlock_init(&txqs[i].tx_lock);\n+ }\n+ }\n+\n+ return txqs;\n+}\n+\n+static int\n+netdev_doca_reconfigure(struct netdev *netdev)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_dpdk_common *common = &dev->common;\n+ int err = 0;\n+\n+ /* If an ESW manager is not attached to OVS, a representor cannot be\n+ * configured.\n+ */\n+ if (!netdev_doca_is_esw_mgr(netdev) &&\n+ netdev_doca_get_esw_mgr_port_id(netdev) ==\n+ DPDK_ETH_PORT_ID_INVALID) {\n+ return EOPNOTSUPP;\n+ }\n+\n+ ovs_mutex_lock(&doca_mutex);\n+ ovs_mutex_lock(&dev->common.mutex);\n+\n+ common->requested_n_rxq = common->user_n_rxq;\n+\n+ if (netdev->n_txq == common->requested_n_txq\n+ && netdev->n_rxq == common->requested_n_rxq\n+ && common->mtu == common->requested_mtu\n+ && common->lsc_interrupt_mode == common->requested_lsc_interrupt_mode\n+ && common->rxq_size == common->requested_rxq_size\n+ && common->txq_size == common->requested_txq_size\n+ && eth_addr_equals(common->hwaddr, common->requested_hwaddr)\n+ && common->socket_id == common->requested_socket_id\n+ && dpdk_dev_is_started(common)) {\n+ /* Reconfiguration is unnecessary. */\n+ goto out;\n+ }\n+\n+ netdev_doca_port_stop(netdev);\n+\n+ err = netdev_doca_mempool_configure(dev);\n+ if (err) {\n+ goto out;\n+ }\n+\n+ common->lsc_interrupt_mode = common->requested_lsc_interrupt_mode;\n+\n+ netdev->n_txq = common->requested_n_txq;\n+ netdev->n_rxq = common->requested_n_rxq;\n+ if (!netdev_doca_is_esw_mgr(netdev)) {\n+ int esw_n_rxq;\n+\n+ esw_n_rxq = dev->esw_ctx->n_rxq;\n+ if (esw_n_rxq < 0) {\n+ err = -1;\n+ goto out;\n+ }\n+ if (common->requested_n_rxq != esw_n_rxq) {\n+ VLOG_WARN(\"%s: requested_n_rxq=%d is ignored. DOCA binds the \"\n+ \"number of rx queues to the esw's n_rxq=%d\",\n+ netdev_get_name(netdev), common->requested_n_rxq,\n+ esw_n_rxq);\n+ }\n+ netdev->n_rxq = esw_n_rxq;\n+ }\n+\n+ common->rxq_size = common->requested_rxq_size;\n+ common->txq_size = common->requested_txq_size;\n+\n+ rte_free(common->tx_q);\n+ common->tx_q = NULL;\n+\n+ if (!eth_addr_equals(common->hwaddr, common->requested_hwaddr)) {\n+ err = netdev_dpdk_set_etheraddr__(common,\n+ common->requested_hwaddr);\n+ if (err) {\n+ goto out;\n+ }\n+ }\n+\n+ err = dpdk_eth_dev_init(dev);\n+ if (err) {\n+ goto out;\n+ }\n+ netdev_dpdk_update_netdev_flags(common);\n+\n+ /* If both requested and actual hw-addr were previously\n+ * unset (initialized to 0), then first device init above\n+ * will have set actual hw-addr to something new.\n+ * This would trigger spurious MAC reconfiguration unless\n+ * the requested MAC is kept in sync.\n+ *\n+ * This is harmless in case requested_hwaddr was\n+ * configured by the user, as netdev_dpdk_set_etheraddr__()\n+ * will have succeeded to get to this point.\n+ */\n+ common->requested_hwaddr = common->hwaddr;\n+\n+ common->tx_q = netdev_doca_alloc_txq(netdev->n_txq);\n+ if (!common->tx_q) {\n+ err = ENOMEM;\n+ }\n+\n+ netdev_change_seq_changed(netdev);\n+\n+out:\n+ ovs_mutex_unlock(&dev->common.mutex);\n+ ovs_mutex_unlock(&doca_mutex);\n+ return err;\n+}\n+\n+static int\n+common_construct(struct netdev *netdev, dpdk_port_t port_no, int socket_id)\n+ OVS_REQUIRES(doca_mutex)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_dpdk_common *common = &dev->common;\n+\n+ ovs_mutex_init(&common->mutex);\n+\n+ rte_spinlock_init(&common->stats_lock);\n+\n+ /* If the 'sid' is negative, it means that the kernel fails\n+ * to obtain the pci numa info. In that situation, always\n+ * use 'SOCKET0'. */\n+ common->socket_id = socket_id < 0 ? SOCKET0 : socket_id;\n+ common->requested_socket_id = common->socket_id;\n+ common->port_id = port_no;\n+ dev->esw_mgr_port_id = port_no;\n+ common->flags = 0;\n+ common->requested_mtu = RTE_ETHER_MTU;\n+ common->max_packet_len = MTU_TO_FRAME_LEN(common->mtu);\n+ common->requested_lsc_interrupt_mode = 0;\n+ common->attached = false;\n+ atomic_store(&common->started, false);\n+\n+ netdev->n_rxq = 0;\n+ netdev->n_txq = 0;\n+ common->user_n_rxq = NR_QUEUE;\n+ common->requested_n_rxq = NR_QUEUE;\n+ common->requested_n_txq = NR_QUEUE;\n+ common->requested_rxq_size = NIC_PORT_DEFAULT_RXQ_SIZE;\n+ common->requested_txq_size = NIC_PORT_DEFAULT_TXQ_SIZE;\n+\n+ /* Initialize the flow control to NULL. */\n+ memset(&common->fc_conf, 0, sizeof common->fc_conf);\n+\n+ /* Initialize the hardware offload flags to 0. */\n+ common->hw_ol_features = 0;\n+\n+ common->rx_metadata_delivery_configured = false;\n+\n+ common->flags = NETDEV_UP | NETDEV_PROMISC;\n+\n+ ovs_list_push_back(&doca_list, &common->list_node);\n+\n+ netdev_request_reconfigure(netdev);\n+\n+ common->rte_xstats_names = NULL;\n+ common->rte_xstats_names_size = 0;\n+\n+ common->rte_xstats_ids = NULL;\n+ common->rte_xstats_ids_size = 0;\n+\n+ common->sw_stats = xzalloc(sizeof *common->sw_stats);\n+ common->sw_stats->tx_retries = UINT64_MAX;\n+\n+ return 0;\n+}\n+\n+static int\n+netdev_doca_construct(struct netdev *netdev)\n+{\n+ int err;\n+\n+ ovs_mutex_lock(&doca_mutex);\n+ err = common_construct(netdev, DPDK_ETH_PORT_ID_INVALID, SOCKET0);\n+ ovs_mutex_unlock(&doca_mutex);\n+\n+ return err;\n+}\n+\n+static int\n+netdev_doca_get_config(const struct netdev *netdev, struct smap *args)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_dpdk_common *common = &dev->common;\n+\n+ ovs_mutex_lock(&common->mutex);\n+ netdev_dpdk_get_config_common(common, args);\n+ ovs_mutex_unlock(&common->mutex);\n+\n+ return 0;\n+}\n+\n+static char *\n+netdev_doca_generate_devargs(const char *name, char *devargs, size_t maxlen,\n+ char iface[IFNAMSIZ])\n+{\n+ char phys_port_name_[IFNAMSIZ], *phys_port_name = phys_port_name_;\n+ char iface_tmp[IFNAMSIZ];\n+ char device[PATH_MAX];\n+ char *mlx5_devargs;\n+ char *rep_part;\n+ bool is_rep;\n+ bool is_pf;\n+ char *pci;\n+ int port;\n+ int len;\n+\n+ if (get_dpdk_iface_name(name, iface_tmp)) {\n+ VLOG_ERR(\"%s: get_dpdk_iface_name failed for %s\",\n+ OVS_SOURCE_LOCATOR, name);\n+ return NULL;\n+ }\n+\n+ name = iface_tmp;\n+ ovs_strlcpy(iface, name, IFNAMSIZ);\n+\n+ if (get_pci(name, device, sizeof device, &is_rep)) {\n+ VLOG_ERR(\"%s: get_pci failed for %s\", OVS_SOURCE_LOCATOR, name);\n+ return NULL;\n+ }\n+\n+ pci = device;\n+\n+ if (get_phys_port_name(name, phys_port_name_, sizeof phys_port_name_)) {\n+ VLOG_ERR(\"%s: get_phys_port_name failed for %s\",\n+ OVS_SOURCE_LOCATOR, name);\n+ return NULL;\n+ }\n+\n+ /* In some kernels, there is a controller prefix, like \"c1\". Ignore it. */\n+ if (sscanf(phys_port_name, \"c%d\", &port) == 1) {\n+ phys_port_name += 2;\n+ }\n+\n+ is_pf = false;\n+\n+ if (sscanf(phys_port_name, \"p%d\", &port) == 1) {\n+ is_pf = true;\n+ } else if (sscanf(phys_port_name, \"pf%d\", &port) != 1) {\n+ VLOG_ERR(\"%s: unrecognized phys_port_name %s\",\n+ OVS_SOURCE_LOCATOR, phys_port_name);\n+ return NULL;\n+ }\n+\n+ mlx5_devargs =\n+ \"dv_xmeta_en=4,\"\n+ \"dv_flow_en=2,\"\n+ \"probe_opt_en=1\";\n+\n+ len = strlen(phys_port_name);\n+ /* HPF's phys_port_name is pf0/pf1. */\n+ if (len == 3 && !strncmp(phys_port_name, \"pf\", 2)) {\n+ /* \"\" to workaround a false positive checkpatch issue. */\n+ if (snprintf(devargs, maxlen, \"%s,%s,representor=(pf%d)\"\"vf65535\", pci,\n+ mlx5_devargs, port) < 0) {\n+ VLOG_ERR(\"%s: snprintf failed for HPF devargs\",\n+ OVS_SOURCE_LOCATOR);\n+ return NULL;\n+ }\n+\n+ return devargs;\n+ }\n+\n+ /* PF ports. */\n+ if (is_pf) {\n+ if (!is_rep) {\n+ len = snprintf(devargs, maxlen, \"%s,%s\", pci, mlx5_devargs);\n+ } else {\n+ len = snprintf(devargs, maxlen, \"%s,%s,representor=pf%d\", pci,\n+ mlx5_devargs, port);\n+ }\n+\n+ if (len < 0) {\n+ VLOG_ERR(\"%s: snprintf failed for PF devargs\", OVS_SOURCE_LOCATOR);\n+ return NULL;\n+ }\n+\n+ return devargs;\n+ }\n+\n+ /* Representors. */\n+ rep_part = strstr(phys_port_name, \"vf\");\n+ if (!rep_part) {\n+ rep_part = strstr(phys_port_name, \"sf\");\n+ }\n+\n+ if (!rep_part) {\n+ VLOG_ERR(\"%s: no vf/sf in phys_port_name %s\",\n+ OVS_SOURCE_LOCATOR, phys_port_name);\n+ return NULL;\n+ }\n+\n+ /* Format as (pfX)vfY or (pfX)sfY. */\n+ if (snprintf(devargs, maxlen, \"%s,%s,representor=(%.*s)%s\", pci,\n+ mlx5_devargs, (int) (rep_part - phys_port_name),\n+ phys_port_name, rep_part) < 0) {\n+ VLOG_ERR(\"%s: snprintf failed for representor devargs\",\n+ OVS_SOURCE_LOCATOR);\n+ return NULL;\n+ }\n+\n+ return devargs;\n+}\n+\n+static dpdk_port_t\n+netdev_doca_process_devargs(struct netdev_doca *dev,\n+ const char *devargs, char **errp)\n+ OVS_REQUIRES(doca_mutex)\n+{\n+ dpdk_port_t new_port_id;\n+\n+ new_port_id = netdev_dpdk_get_port_by_devargs(devargs);\n+ if (!rte_eth_dev_is_valid_port(new_port_id)) {\n+ int err;\n+\n+ /* Device not found in DPDK, attempt to attach it. */\n+ err = netdev_doca_dev_probe(dev, devargs);\n+ if (err) {\n+ new_port_id = DPDK_ETH_PORT_ID_INVALID;\n+ } else {\n+ new_port_id = netdev_dpdk_get_port_by_devargs(devargs);\n+ if (rte_eth_dev_is_valid_port(new_port_id)) {\n+ /* Attach successful. */\n+ dev->common.attached = true;\n+ VLOG_INFO(\"Device '%s' attached to DPDK\", devargs);\n+ } else {\n+ /* Attach unsuccessful. */\n+ new_port_id = DPDK_ETH_PORT_ID_INVALID;\n+ }\n+ }\n+ }\n+\n+ if (new_port_id == DPDK_ETH_PORT_ID_INVALID) {\n+ VLOG_WARN_BUF(errp, \"Error attaching device '%s' to DPDK\", devargs);\n+ }\n+\n+ return new_port_id;\n+}\n+\n+static struct netdev_doca *\n+netdev_doca_lookup_by_port_id(dpdk_port_t port_id)\n+ OVS_REQUIRES(doca_mutex)\n+{\n+ struct netdev_dpdk_common *common;\n+\n+ common = netdev_dpdk_lookup_by_port_id__(port_id, &doca_list);\n+ if (common) {\n+ return CONTAINER_OF(common, struct netdev_doca, common);\n+ }\n+\n+ return NULL;\n+}\n+\n+static dpdk_port_t\n+netdev_doca_find_esw_mgr_port_id(dpdk_port_t dev_port_id)\n+ OVS_REQUIRES(doca_mutex)\n+{\n+ struct rte_eth_dev_info info;\n+ struct netdev_doca *dev;\n+ uint16_t domain_id;\n+\n+ if (!rte_eth_dev_is_valid_port(dev_port_id)) {\n+ return DPDK_ETH_PORT_ID_INVALID;\n+ }\n+\n+ if (rte_eth_dev_info_get(dev_port_id, &info) < 0) {\n+ VLOG_DBG_RL(&rl, \"Failed to retrieve device info for port \"\n+ DPDK_PORT_ID_FMT, dev_port_id);\n+ return DPDK_ETH_PORT_ID_INVALID;\n+ }\n+\n+ domain_id = info.switch_info.domain_id;\n+ LIST_FOR_EACH (dev, common.list_node, &doca_list) {\n+ if (!rte_eth_dev_is_valid_port(dev->common.port_id)) {\n+ continue;\n+ }\n+\n+ if (rte_eth_dev_info_get(dev->common.port_id, &info) < 0) {\n+ VLOG_DBG_RL(&rl, \"Failed to retrieve device info for port \"\n+ DPDK_PORT_ID_FMT, dev->common.port_id);\n+ continue;\n+ }\n+\n+ if (info.switch_info.domain_id == domain_id &&\n+ !(*info.dev_flags & RTE_ETH_DEV_REPRESENTOR)) {\n+ VLOG_INFO(\"Found ESW manager port \"DPDK_PORT_ID_FMT\" for \"\n+ \"device \"DPDK_PORT_ID_FMT, dev->common.port_id,\n+ dev_port_id);\n+ return dev->common.port_id;\n+ }\n+ }\n+\n+ return DPDK_ETH_PORT_ID_INVALID;\n+}\n+\n+static int\n+netdev_doca_set_config(struct netdev *netdev, const struct smap *args,\n+ char **errp)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_dpdk_common *common = &dev->common;\n+ char generated[PATH_MAX];\n+ bool lsc_interrupt_mode;\n+ const char *new_devargs;\n+ char iface[IFNAMSIZ];\n+ const char *dev_name;\n+ const char *vf_mac;\n+ int err = 0;\n+ bool is_rep;\n+\n+ ovs_mutex_lock(&doca_mutex);\n+ ovs_mutex_lock(&common->mutex);\n+\n+ memset(iface, 0, sizeof iface);\n+ if (!common->devargs) {\n+ dev_name = netdev_get_name(netdev);\n+ new_devargs = netdev_doca_generate_devargs(dev_name, generated,\n+ sizeof generated, iface);\n+ if (!new_devargs) {\n+ VLOG_WARN(\"%s: Could not generate DPDK devargs\",\n+ netdev_get_name(netdev));\n+ err = ENODEV;\n+ goto out;\n+ }\n+\n+ common->devargs = xstrdup(new_devargs);\n+ }\n+\n+ is_rep = strstr(common->devargs, \"representor=\");\n+ if (is_rep) {\n+ struct netdev_doca_esw_key esw_key;\n+ struct netdev_doca_esw_ctx *esw;\n+\n+ if (netdev_doca_esw_key_parse(common->devargs, &esw_key)) {\n+ VLOG_ERR(\"%s: esw_key_parse failed for %s\",\n+ OVS_SOURCE_LOCATOR, common->devargs);\n+ err = EINVAL;\n+ goto out;\n+ }\n+\n+ esw = refmap_try_ref(netdev_doca_esw_rfm, &esw_key);\n+ if (!esw) {\n+ goto out;\n+ }\n+\n+ refmap_unref(netdev_doca_esw_rfm, esw);\n+ }\n+\n+ netdev_dpdk_set_rxq_config(common, args);\n+\n+ /* Don't process dpdk-devargs if value is unchanged and port id\n+ * is valid. */\n+ if (!(rte_eth_dev_is_valid_port(common->port_id) && common->attached)) {\n+ dpdk_port_t new_port_id =\n+ netdev_doca_process_devargs(dev, common->devargs, errp);\n+\n+ if (!rte_eth_dev_is_valid_port(new_port_id)) {\n+ err = EINVAL;\n+ } else if (new_port_id == common->port_id) {\n+ /* Already configured, do not reconfigure again. */\n+ err = 0;\n+ } else {\n+ struct netdev_doca *dup_dev;\n+\n+ dup_dev = netdev_doca_lookup_by_port_id(new_port_id);\n+ if (dup_dev) {\n+ VLOG_WARN_BUF(errp, \"'%s' is trying to use device '%s' \"\n+ \"which is already in use by '%s'\",\n+ netdev_get_name(netdev), common->devargs,\n+ netdev_get_name(&dup_dev->common.up));\n+ err = EADDRINUSE;\n+ } else {\n+ int sid = rte_eth_dev_socket_id(new_port_id);\n+\n+ common->requested_socket_id = sid < 0 ? SOCKET0 : sid;\n+ common->port_id = new_port_id;\n+ dev->esw_mgr_port_id =\n+ netdev_doca_find_esw_mgr_port_id(new_port_id);\n+ netdev_request_reconfigure(&common->up);\n+ err = 0;\n+ }\n+ }\n+ }\n+\n+ if (err) {\n+ goto out;\n+ }\n+\n+ vf_mac = smap_get(args, \"dpdk-vf-mac\");\n+ if (vf_mac) {\n+ struct eth_addr mac;\n+\n+ if (!common->is_representor) {\n+ VLOG_WARN(\"'%s' is trying to set the VF MAC '%s' \"\n+ \"but 'options:dpdk-vf-mac' is only supported for \"\n+ \"VF representors.\",\n+ netdev_get_name(netdev), vf_mac);\n+ } else if (!eth_addr_from_string(vf_mac, &mac)) {\n+ VLOG_WARN(\"interface '%s': cannot parse VF MAC '%s'.\",\n+ netdev_get_name(netdev), vf_mac);\n+ } else if (eth_addr_is_multicast(mac)) {\n+ VLOG_WARN(\"interface '%s': cannot set VF MAC to multicast \"\n+ \"address '%s'.\", netdev_get_name(netdev), vf_mac);\n+ } else if (!eth_addr_equals(common->requested_hwaddr, mac)) {\n+ common->requested_hwaddr = mac;\n+ netdev_request_reconfigure(netdev);\n+ }\n+ }\n+\n+ lsc_interrupt_mode = smap_get_bool(args, \"dpdk-lsc-interrupt\", false);\n+ if (common->requested_lsc_interrupt_mode != lsc_interrupt_mode) {\n+ common->requested_lsc_interrupt_mode = lsc_interrupt_mode;\n+ netdev_request_reconfigure(netdev);\n+ }\n+\n+out:\n+ ovs_mutex_unlock(&common->mutex);\n+ ovs_mutex_unlock(&doca_mutex);\n+\n+ return err;\n+}\n+\n+static void\n+classify_in_port(struct dp_packet_batch *rx_batch,\n+ struct netdev_doca_port_queue *pq[RTE_MAX_ETHPORTS],\n+ uint16_t queue_id)\n+{\n+ struct dp_packet *pkt;\n+ uint64_t old_count;\n+ uint32_t pkt_size;\n+ uint32_t port_id;\n+ int rv;\n+\n+ DP_PACKET_BATCH_FOR_EACH (i, pkt, rx_batch) {\n+ dp_packet_reset_cutlen(pkt);\n+ pkt->packet_type = htonl(PT_ETH);\n+ pkt->has_hash = !!(pkt->mbuf.ol_flags & RTE_MBUF_F_RX_RSS_HASH);\n+ pkt->has_mark = !!(pkt->mbuf.ol_flags & RTE_MBUF_F_RX_FDIR_ID);\n+ pkt->offloads =\n+ pkt->mbuf.ol_flags & (RTE_MBUF_F_RX_IP_CKSUM_BAD\n+ | RTE_MBUF_F_RX_IP_CKSUM_GOOD\n+ | RTE_MBUF_F_RX_L4_CKSUM_BAD\n+ | RTE_MBUF_F_RX_L4_CKSUM_GOOD);\n+\n+ if (!dp_packet_has_flow_mark(pkt, &port_id)) {\n+ COVERAGE_INC(netdev_doca_no_mark);\n+ dp_packet_delete(pkt);\n+ continue;\n+ }\n+\n+ pkt->has_mark = false;\n+ if (!rte_eth_dev_is_valid_port(port_id)) {\n+ COVERAGE_INC(netdev_doca_invalid_classify_port);\n+ dp_packet_delete(pkt);\n+ continue;\n+ }\n+\n+ pkt_size = dp_packet_size(pkt);\n+ rv = rte_ring_sp_enqueue(pq[port_id][queue_id].ring, pkt);\n+ if (rv) {\n+ COVERAGE_INC(netdev_doca_drop_ring_full);\n+ dp_packet_delete(pkt);\n+ continue;\n+ }\n+\n+ atomic_add_relaxed(&pq[port_id][queue_id].n_bytes, pkt_size,\n+ &old_count);\n+ }\n+}\n+\n+static int\n+netdev_doca_rxq_recv(struct netdev_rxq *rxq, struct dp_packet_batch *batch,\n+ int *qfill)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(rxq->netdev);\n+ struct netdev_rxq_dpdk *rx = netdev_rxq_dpdk_cast(rxq);\n+ struct netdev_dpdk_common *common = &dev->common;\n+ struct netdev_doca_port_queue *pq;\n+ struct dp_packet_batch rx_batch;\n+ dpdk_port_t esw_mgr_port_id;\n+ dpdk_port_t port_id;\n+ uint64_t old_count;\n+ int nb_rx;\n+\n+ if (OVS_UNLIKELY(!(common->flags & NETDEV_UP) ||\n+ !dpdk_dev_is_started(common))) {\n+ return EAGAIN;\n+ }\n+\n+ esw_mgr_port_id = dev->esw_ctx->port_id;\n+ port_id = common->port_id;\n+\n+ if (port_id == esw_mgr_port_id) {\n+ rx_batch.count =\n+ rte_eth_rx_burst(esw_mgr_port_id, rxq->queue_id,\n+ (struct rte_mbuf **) rx_batch.packets,\n+ NETDEV_MAX_BURST);\n+ if (rx_batch.count == 0) {\n+ return 0;\n+ }\n+\n+ classify_in_port(&rx_batch, dev->esw_ctx->port_queues, rxq->queue_id);\n+ }\n+\n+ pq = &dev->esw_ctx->port_queues[port_id][rxq->queue_id];\n+ batch->count =\n+ rte_ring_sc_dequeue_burst(pq->ring, (void **) batch->packets,\n+ NETDEV_MAX_BURST, NULL);\n+ atomic_add_relaxed(&pq->n_packets, batch->count, &old_count);\n+\n+ nb_rx = batch->count;\n+\n+ if (!nb_rx) {\n+ return EAGAIN;\n+ }\n+\n+ if (qfill) {\n+ if (nb_rx == NETDEV_MAX_BURST) {\n+ *qfill = rte_eth_rx_queue_count(rx->port_id, rxq->queue_id);\n+ } else {\n+ *qfill = 0;\n+ }\n+ }\n+\n+ return 0;\n+}\n+\n+static size_t\n+netdev_doca_common_send(struct netdev *netdev, struct dp_packet_batch *batch,\n+ struct netdev_doca_sw_stats *stats)\n+{\n+ struct rte_mbuf **pkts = (struct rte_mbuf **) batch->packets;\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ size_t cnt, pkt_cnt = dp_packet_batch_size(batch);\n+ struct netdev_dpdk_common *common = &dev->common;\n+ struct dp_packet *packet;\n+ bool need_copy = false;\n+\n+ memset(stats, 0, sizeof *stats);\n+\n+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {\n+ if (packet->source != DPBUF_DPDK) {\n+ need_copy = true;\n+ break;\n+ }\n+ }\n+\n+ /* Copy dp-packets to mbufs. */\n+ if (OVS_UNLIKELY(need_copy)) {\n+ cnt = netdev_dpdk_copy_batch_to_mbuf(common, batch);\n+ stats->tx_failure_drops += pkt_cnt - cnt;\n+ pkt_cnt = cnt;\n+ }\n+\n+ /* Drop over-sized packets. */\n+ cnt = netdev_dpdk_filter_packet_len(common, pkts, pkt_cnt);\n+ stats->tx_mtu_exceeded_drops += pkt_cnt - cnt;\n+ pkt_cnt = cnt;\n+\n+ /* Prepare each mbuf for hardware offloading. */\n+ cnt = netdev_dpdk_prep_hwol_batch(common, pkts, pkt_cnt);\n+ stats->tx_invalid_hwol_drops += pkt_cnt - cnt;\n+ pkt_cnt = cnt;\n+\n+ return cnt;\n+}\n+\n+static inline void\n+packet_set_meta(struct dp_packet *p, uint32_t meta)\n+{\n+ *RTE_MBUF_DYNFIELD(&p->mbuf, rte_flow_dynf_metadata_offs,\n+ uint32_t *) = meta;\n+ p->mbuf.ol_flags |= RTE_MBUF_DYNFLAG_TX_METADATA;\n+}\n+\n+static int\n+netdev_doca_eth_send(struct netdev *netdev, int qid,\n+ struct dp_packet_batch *batch, bool concurrent_txq)\n+{\n+ struct rte_mbuf **pkts = (struct rte_mbuf **) batch->packets;\n+ uint32_t port_id_meta = netdev_doca_get_port_id(netdev);\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_dpdk_common *common = &dev->common;\n+ int batch_cnt = dp_packet_batch_size(batch);\n+ struct netdev_doca_sw_stats stats;\n+ struct dp_packet *packet;\n+ uint64_t n_bytes = 0;\n+ uint64_t old_count;\n+ int cnt, dropped;\n+\n+ if (OVS_UNLIKELY(!(common->flags & NETDEV_UP))) {\n+ rte_spinlock_lock(&common->stats_lock);\n+ common->stats.tx_dropped += dp_packet_batch_size(batch);\n+ rte_spinlock_unlock(&common->stats_lock);\n+ dp_packet_delete_batch(batch, true);\n+ return 0;\n+ }\n+\n+ if (OVS_UNLIKELY(concurrent_txq)) {\n+ qid = qid % common->up.n_txq;\n+ rte_spinlock_lock(&common->tx_q[qid].tx_lock);\n+ }\n+\n+ cnt = netdev_doca_common_send(netdev, batch, &stats);\n+\n+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {\n+ /* Set metadata for egress pipe rules to match on. */\n+ packet_set_meta(packet, port_id_meta);\n+ n_bytes += dp_packet_size(packet);\n+ }\n+\n+ atomic_add_relaxed(&dev->sw_tx_stats[qid].n_packets, batch->count,\n+ &old_count);\n+ atomic_add_relaxed(&dev->sw_tx_stats[qid].n_bytes, n_bytes, &old_count);\n+\n+ dropped = netdev_dpdk_eth_tx_burst(common, dev->esw_mgr_port_id,\n+ qid, pkts, cnt);\n+ stats.tx_failure_drops += dropped;\n+ dropped += batch_cnt - cnt;\n+ if (OVS_UNLIKELY(dropped)) {\n+ struct netdev_doca_sw_stats *sw_stats = common->sw_stats;\n+\n+ rte_spinlock_lock(&common->stats_lock);\n+ common->stats.tx_dropped += dropped;\n+ sw_stats->tx_failure_drops += stats.tx_failure_drops;\n+ sw_stats->tx_mtu_exceeded_drops += stats.tx_mtu_exceeded_drops;\n+ sw_stats->tx_invalid_hwol_drops += stats.tx_invalid_hwol_drops;\n+ rte_spinlock_unlock(&common->stats_lock);\n+ }\n+\n+ if (OVS_UNLIKELY(concurrent_txq)) {\n+ rte_spinlock_unlock(&common->tx_q[qid].tx_lock);\n+ }\n+\n+ return 0;\n+}\n+\n+#define NETDEV_DOCA_CLASS_COMMON \\\n+ .is_pmd = true, \\\n+ .alloc = netdev_doca_alloc, \\\n+ .dealloc = netdev_doca_dealloc, \\\n+ .get_numa_id = netdev_dpdk_get_numa_id, \\\n+ .set_etheraddr = netdev_dpdk_set_etheraddr, \\\n+ .get_etheraddr = netdev_dpdk_get_etheraddr, \\\n+ .get_mtu = netdev_dpdk_get_mtu, \\\n+ .set_mtu = netdev_doca_set_mtu, \\\n+ .get_ifindex = netdev_dpdk_get_ifindex, \\\n+ .get_carrier_resets = netdev_dpdk_get_carrier_resets, \\\n+ .set_miimon_interval = netdev_dpdk_set_miimon, \\\n+ .update_flags = netdev_dpdk_update_flags, \\\n+ .rxq_alloc = netdev_dpdk_rxq_alloc, \\\n+ .rxq_construct = netdev_dpdk_rxq_construct, \\\n+ .rxq_destruct = netdev_dpdk_rxq_destruct, \\\n+ .rxq_dealloc = netdev_dpdk_rxq_dealloc\n+\n+#define NETDEV_DOCA_CLASS_BASE \\\n+ NETDEV_DOCA_CLASS_COMMON, \\\n+ .init = netdev_doca_class_init, \\\n+ .destruct = netdev_doca_destruct, \\\n+ .set_tx_multiq = netdev_dpdk_set_tx_multiq, \\\n+ .get_carrier = netdev_dpdk_get_carrier, \\\n+ .get_stats = netdev_dpdk_get_stats, \\\n+ .get_custom_stats = netdev_doca_get_custom_stats, \\\n+ .get_features = netdev_dpdk_get_features, \\\n+ .get_speed = netdev_dpdk_get_speed, \\\n+ .get_status = netdev_doca_get_status, \\\n+ .reconfigure = netdev_doca_reconfigure, \\\n+ .rxq_recv = netdev_doca_rxq_recv\n+\n+static const struct netdev_class netdev_doca_class = {\n+ .type = \"doca\",\n+ NETDEV_DOCA_CLASS_BASE,\n+ .construct = netdev_doca_construct,\n+ .get_config = netdev_doca_get_config,\n+ .set_config = netdev_doca_set_config,\n+ .send = netdev_doca_eth_send,\n+};\n+\n+void\n+netdev_doca_register(void)\n+{\n+ netdev_register_provider(&netdev_doca_class);\n+}\ndiff --git a/lib/netdev-doca.h b/lib/netdev-doca.h\nnew file mode 100644\nindex 000000000..b774318fa\n--- /dev/null\n+++ b/lib/netdev-doca.h\n@@ -0,0 +1,159 @@\n+/*\n+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.\n+ * All rights reserved.\n+ * SPDX-License-Identifier: Apache-2.0\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at\n+ *\n+ * http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#ifndef NETDEV_DOCA_H\n+#define NETDEV_DOCA_H\n+\n+#include <config.h>\n+\n+#include <rte_ethdev.h>\n+#include <rte_mempool.h>\n+#include <rte_pci.h>\n+\n+#include <doca_flow.h>\n+\n+#include \"netdev-provider.h\"\n+#include \"ovs-doca.h\"\n+#include \"util.h\"\n+\n+#include \"openvswitch/list.h\"\n+\n+struct doca_tx_queue;\n+struct netdev_doca_sw_stats;\n+extern struct ovs_mutex doca_mutex;\n+#define NETDEV_DPDK_TX_Q_TYPE struct doca_tx_queue\n+#define NETDEV_DPDK_SW_STATS_TYPE struct netdev_doca_sw_stats\n+#define NETDEV_DPDK_GLOBAL_MUTEX doca_mutex\n+#include \"netdev-dpdk-private.h\"\n+\n+struct rte_ring;\n+\n+enum netdev_doca_rss_type {\n+ NETDEV_DOCA_RSS_IPV4_TCP,\n+ NETDEV_DOCA_RSS_IPV4_UDP,\n+ NETDEV_DOCA_RSS_IPV4_ICMP,\n+ NETDEV_DOCA_RSS_IPV4_ESP,\n+ NETDEV_DOCA_RSS_IPV4_OTHER,\n+ NETDEV_DOCA_RSS_IPV6_TCP,\n+ NETDEV_DOCA_RSS_IPV6_UDP,\n+ NETDEV_DOCA_RSS_IPV6_ICMP,\n+ NETDEV_DOCA_RSS_IPV6_ESP,\n+ NETDEV_DOCA_RSS_IPV6_OTHER,\n+ NETDEV_DOCA_RSS_OTHER,\n+};\n+/* Must be the last enum type. */\n+#define NETDEV_DOCA_RSS_NUM_ENTRIES (NETDEV_DOCA_RSS_OTHER + 1)\n+\n+/* Custom software stats for dpdk ports */\n+struct netdev_doca_sw_stats {\n+ /* No. of retries when unable to transmit. */\n+ uint64_t tx_retries;\n+ /* Packet drops when unable to transmit; Probably Tx queue is full. */\n+ uint64_t tx_failure_drops;\n+ /* Packet length greater than device MTU. */\n+ uint64_t tx_mtu_exceeded_drops;\n+ /* Packet drops in HWOL processing. */\n+ uint64_t tx_invalid_hwol_drops;\n+};\n+\n+struct netdev_doca_tx_stats {\n+ PADDED_MEMBERS(CACHE_LINE_SIZE,\n+ atomic_uint64_t n_packets;\n+ atomic_uint64_t n_bytes;\n+ );\n+};\n+\n+enum netdev_doca_port_dir {\n+ NETDEV_DOCA_PORT_DIR_RX,\n+ NETDEV_DOCA_PORT_DIR_TX,\n+ NUM_NETDEV_DOCA_PORT_DIR,\n+};\n+\n+enum pre_miss_types {\n+ SEND_TO_KERNEL_LACP,\n+ SEND_TO_KERNEL_LLDP,\n+ NUM_SEND_TO_KERNEL,\n+};\n+\n+struct netdev_doca_port_queue {\n+ PADDED_MEMBERS(CACHE_LINE_SIZE,\n+ struct rte_ring *ring;\n+ atomic_uint64_t n_packets;\n+ atomic_uint64_t n_bytes;\n+ );\n+};\n+\n+struct netdev_doca_esw_ctx {\n+ struct netdev_doca_port_queue *port_queues[RTE_MAX_ETHPORTS];\n+ dpdk_port_t port_id;\n+ struct ovs_doca_offload_queue\n+ offload_queues[OVS_DOCA_MAX_OFFLOAD_QUEUES];\n+ struct doca_flow_port *esw_port;\n+ struct netdev *esw_netdev;\n+ /* miss-path */\n+ struct {\n+ struct doca_flow_pipe *egress_pipe;\n+ struct doca_flow_pipe *rss_pipe;\n+ struct doca_flow_pipe *meta_tag0_pipe;\n+ struct doca_flow_pipe_entry *meta_tag0_entry;\n+ struct doca_flow_pipe *pre_miss_pipe;\n+ struct doca_flow_pipe_entry *pre_miss_entries[NUM_SEND_TO_KERNEL];\n+ struct doca_flow_pipe *root_pipe;\n+ };\n+ unsigned int n_rxq;\n+ char pci_addr[PCI_PRI_STR_SIZE];\n+ struct doca_dev *dev;\n+ uint32_t op_state;\n+ int cmd_fd;\n+};\n+\n+/* There should be one 'struct doca_tx_queue' created for\n+ * each netdev tx queue. */\n+struct doca_tx_queue {\n+ /* Padding to make doca_tx_queue exactly one cache line long. */\n+ PADDED_MEMBERS(CACHE_LINE_SIZE,\n+ /* Protects the members and the NIC queue from concurrent access.\n+ * It is used only if the queue is shared among different pmd threads\n+ * (see 'concurrent_txq'). */\n+ rte_spinlock_t tx_lock;\n+ );\n+};\n+\n+struct netdev_doca {\n+ struct netdev_dpdk_common common; /* Must be first (offset 0). */\n+\n+ dpdk_port_t esw_mgr_port_id;\n+ struct netdev_doca_tx_stats *sw_tx_stats;\n+\n+ PADDED_MEMBERS_CACHELINE_MARKER(CACHE_LINE_SIZE, cacheline10,\n+ struct doca_flow_port *port;\n+ struct netdev_doca_esw_ctx *esw_ctx;\n+ struct doca_flow_pipe_entry *rss_entries[NETDEV_DOCA_RSS_NUM_ENTRIES];\n+ struct doca_flow_pipe_entry *egress_entry;\n+ char *peer_name;\n+ enum netdev_doca_port_dir port_dir;\n+ struct doca_dev_rep *dev_rep;\n+ );\n+};\n+\n+void netdev_doca_register(void);\n+\n+struct netdev_doca *\n+netdev_doca_cast(const struct netdev *netdev);\n+\n+#endif /* NETDEV_DOCA_H */\ndiff --git a/lib/ovs-doca.c b/lib/ovs-doca.c\nindex eae361a21..f78d0efdd 100644\n--- a/lib/ovs-doca.c\n+++ b/lib/ovs-doca.c\n@@ -24,13 +24,34 @@\n \n #ifdef DOCA_NETDEV\n \n+#include <errno.h>\n+#include <infiniband/verbs.h>\n+#include <string.h>\n+#include <unistd.h>\n+\n #include <rte_common.h>\n+#include <rte_flow.h>\n+#include <rte_pci.h>\n #include <rte_pmd_mlx5.h>\n \n+#include <doca_flow.h>\n+#include <doca_flow_definitions.h>\n+#include <doca_log.h>\n #include <doca_version.h>\n \n+#include \"coverage.h\"\n+#include \"dpdk.h\"\n+#include \"netdev.h\"\n+#include \"netdev-doca.h\"\n+#include \"smap.h\"\n+#include \"unixctl.h\"\n+#include \"util.h\"\n+\n+#include \"openvswitch/list.h\"\n+#include \"openvswitch/vlog.h\"\n+\n /* DOCA disables dpdk steering as a constructor in higher priority.\n- * Set a lower priority one to enable it back. Disable it only upon using\n+ * Set a lower priority one to enable it back. Disable it only upon using\n * doca ports.\n */\n RTE_INIT(dpdk_steering_enable)\n@@ -38,9 +59,714 @@ RTE_INIT(dpdk_steering_enable)\n rte_pmd_mlx5_enable_steering();\n }\n \n+VLOG_DEFINE_THIS_MODULE(ovs_doca);\n+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(600, 600);\n+\n+#define OVS_DOCA_SLOWPATH_COUNTERS \\\n+ ((NETDEV_DOCA_RSS_NUM_ENTRIES + 1) * RTE_MAX_ETHPORTS)\n+\n+COVERAGE_DEFINE(ovs_doca_queue_block);\n+COVERAGE_DEFINE(ovs_doca_queue_empty);\n+COVERAGE_DEFINE(ovs_doca_queue_none_processed);\n+\n+static atomic_bool doca_initialized = false;\n+static unsigned int ovs_doca_max_megaflows_counters;\n+static FILE *log_stream = NULL; /* Stream for DOCA log redirection */\n+static struct doca_log_backend *ovs_doca_log = NULL;\n+\n+static const char * const levels[] = {\n+ [DOCA_LOG_LEVEL_CRIT] = \"CRT\",\n+ [DOCA_LOG_LEVEL_ERROR] = \"ERR\",\n+ [DOCA_LOG_LEVEL_WARNING] = \"WRN\",\n+ [DOCA_LOG_LEVEL_INFO] = \"INF\",\n+ [DOCA_LOG_LEVEL_DEBUG] = \"DBG\",\n+ [DOCA_LOG_LEVEL_TRACE] = \"TRC\",\n+};\n+\n+static int\n+ovs_doca_parse_log_level(const char *s)\n+{\n+ for (int i = 0; i < ARRAY_SIZE(levels); ++i) {\n+ if (levels[i] && !strncmp(s, levels[i], strlen(levels[i]))) {\n+ return i;\n+ }\n+ }\n+\n+ return -1;\n+}\n+\n+static const char *\n+ovs_doca_log_level_to_str(uint32_t log_level)\n+{\n+ for (int i = 0; i < ARRAY_SIZE(levels); ++i) {\n+ if (i == log_level && levels[i]) {\n+ return levels[i];\n+ }\n+ }\n+\n+ OVS_NOT_REACHED();\n+ return \"UNKONWN\";\n+}\n+\n+static enum doca_log_level\n+get_buf_log_level(const char *buf, size_t size)\n+{\n+ const char *p = buf;\n+ int level;\n+\n+ for (int i = 0; i < 4; i++) {\n+ while (size && *p && *p != '[') {\n+ size--;\n+ p++;\n+ }\n+\n+ if (!size || !*p) {\n+ return DOCA_LOG_LEVEL_DISABLE;\n+ }\n+\n+ size--;\n+ p++;\n+ }\n+\n+ level = ovs_doca_parse_log_level(p);\n+ if (level < 0) {\n+ return DOCA_LOG_LEVEL_DISABLE;\n+ }\n+\n+ return level;\n+}\n+\n+static ssize_t\n+ovs_doca_log_write(void *c OVS_UNUSED, const char *buf, size_t size)\n+{\n+ static struct vlog_rate_limit dbg_rl = VLOG_RATE_LIMIT_INIT(600, 600);\n+ enum doca_log_level level = get_buf_log_level(buf, size);\n+\n+ switch (level) {\n+ case DOCA_LOG_LEVEL_DISABLE:\n+ VLOG_EMER(\"(Failed to parse level): %.*s\", (int) size, buf);\n+ break;\n+ case DOCA_LOG_LEVEL_TRACE:\n+ case DOCA_LOG_LEVEL_DEBUG:\n+ VLOG_DBG_RL(&dbg_rl, \"%.*s\", (int) size, buf);\n+ break;\n+ case DOCA_LOG_LEVEL_INFO:\n+ VLOG_INFO_RL(&rl, \"%.*s\", (int) size, buf);\n+ break;\n+ case DOCA_LOG_LEVEL_WARNING:\n+ VLOG_WARN_RL(&rl, \"%.*s\", (int) size, buf);\n+ break;\n+ case DOCA_LOG_LEVEL_ERROR:\n+ VLOG_ERR_RL(&rl, \"%.*s\", (int) size, buf);\n+ break;\n+ case DOCA_LOG_LEVEL_CRIT:\n+ VLOG_EMER(\"%.*s\", (int) size, buf);\n+ break;\n+ default:\n+ OVS_NOT_REACHED();\n+ }\n+\n+ return size;\n+}\n+\n+static cookie_io_functions_t ovs_doca_log_func = {\n+ .write = ovs_doca_log_write,\n+};\n+\n+static void\n+ovs_doca_unixctl_log_set(struct unixctl_conn *conn, int argc,\n+ const char *argv[], void *aux OVS_UNUSED)\n+{\n+ int level = DOCA_LOG_LEVEL_DEBUG;\n+\n+ /* With no argument, level is set to 'debug'. */\n+\n+ if (argc == 2) {\n+ const char *level_string;\n+\n+ level_string = argv[1];\n+ level = ovs_doca_parse_log_level(level_string);\n+ if (level < 0) {\n+ char *err_msg = xasprintf(\"invalid log level: '%s'\", level_string);\n+\n+ unixctl_command_reply_error(conn, err_msg);\n+ free(err_msg);\n+ return;\n+ }\n+ }\n+\n+ doca_log_level_set_global_sdk_limit(level);\n+ unixctl_command_reply(conn, NULL);\n+}\n+\n+static void\n+ovs_doca_log_get(FILE *stream)\n+{\n+ uint32_t log_level;\n+\n+ log_level = doca_log_level_get_global_sdk_limit();\n+ fprintf(stream, \"DOCA log level is %s\",\n+ ovs_doca_log_level_to_str(log_level));\n+}\n+\n+static void\n+ovs_doca_destroy_defs(struct doca_flow_definitions *defs,\n+ struct doca_flow_definitions_cfg *defs_cfg)\n+{\n+ if (defs) {\n+ doca_flow_definitions_destroy(defs);\n+ }\n+\n+ if (defs_cfg) {\n+ doca_flow_definitions_cfg_destroy(defs_cfg);\n+ }\n+}\n+\n+static doca_error_t\n+ovs_doca_init_defs(struct doca_flow_cfg *cfg,\n+ struct doca_flow_definitions **defs,\n+ struct doca_flow_definitions_cfg **defs_cfg)\n+{\n+#define DEF_FIELD(str_val, struct_name, field_name) { \\\n+ .str = str_val, \\\n+ .offset = offsetof(struct struct_name, field_name), \\\n+ .size = MEMBER_SIZEOF(struct struct_name, field_name) \\\n+}\n+ struct def_field {\n+ const char *str;\n+ size_t offset;\n+ size_t size;\n+ } def_fields[] = {\n+ DEF_FIELD(\"actions.packet.meta.mark\", ovs_doca_flow_actions, mark),\n+ };\n+ doca_error_t result;\n+\n+ result = doca_flow_definitions_cfg_create(defs_cfg);\n+ if (result != DOCA_SUCCESS) {\n+ VLOG_ERR(\"Failed to create defs cfg. Error: %d (%s)\", result,\n+ doca_error_get_descr(result));\n+ return result;\n+ }\n+\n+ result = doca_flow_definitions_create(*defs_cfg, defs);\n+ if (result != DOCA_SUCCESS) {\n+ VLOG_ERR(\"Failed to create definitions. Error: %d (%s)\", result,\n+ doca_error_get_descr(result));\n+ goto out;\n+ }\n+\n+ for (int i = 0; i < ARRAY_SIZE(def_fields); i++) {\n+ result = doca_flow_definitions_add_field(*defs, def_fields[i].str,\n+ def_fields[i].offset,\n+ def_fields[i].size);\n+ if (result != DOCA_SUCCESS) {\n+ VLOG_ERR(\"Failed to add definition field '%s'. Error: %d (%s)\",\n+ def_fields[i].str, result, doca_error_get_descr(result));\n+ goto out;\n+ }\n+ }\n+\n+ result = doca_flow_cfg_set_definitions(cfg, *defs);\n+ if (result != DOCA_SUCCESS) {\n+ VLOG_ERR(\"Failed to set doca_flow_cfg defs. Error: %d (%s)\", result,\n+ doca_error_get_descr(result));\n+ goto out;\n+ }\n+\n+out:\n+ if (result) {\n+ ovs_doca_destroy_defs(*defs, *defs_cfg);\n+ }\n+\n+ return result;\n+}\n+\n+static void\n+ovs_doca_offload_entry_process(struct doca_flow_pipe_entry *entry,\n+ uint16_t qid,\n+ enum doca_flow_entry_status status,\n+ enum doca_flow_entry_op op,\n+ void *aux)\n+{\n+ static const char *status_desc[] = {\n+ [DOCA_FLOW_ENTRY_STATUS_IN_PROCESS] = \"in-process\",\n+ [DOCA_FLOW_ENTRY_STATUS_SUCCESS] = \"success\",\n+ [DOCA_FLOW_ENTRY_STATUS_ERROR] = \"failure\",\n+ };\n+ static const char *op_desc[] = {\n+ [DOCA_FLOW_ENTRY_OP_ADD] = \"add\",\n+ [DOCA_FLOW_ENTRY_OP_DEL] = \"del\",\n+ [DOCA_FLOW_ENTRY_OP_UPD] = \"mod\",\n+ [DOCA_FLOW_ENTRY_OP_AGED] = \"age\",\n+ };\n+ bool error = status == DOCA_FLOW_ENTRY_STATUS_ERROR;\n+ struct ovs_doca_offload_queue *queues = aux;\n+\n+ ovs_assert(status < ARRAY_SIZE(status_desc));\n+ ovs_assert(op < ARRAY_SIZE(op_desc));\n+\n+ VLOG_RL(&rl, error ? VLL_ERR : VLL_DBG,\n+ \"%s: [qid:%\" PRIu16 \"] %s aux=%p entry %p %s\",\n+ __func__, qid, op_desc[op], aux, entry, status_desc[status]);\n+\n+ if (queues && status != DOCA_FLOW_ENTRY_STATUS_IN_PROCESS) {\n+ queues[qid].n_waiting_entries--;\n+ }\n+}\n+\n+static int\n+ovs_doca_init__(const struct smap *ovs_other_config)\n+{\n+ struct doca_flow_definitions_cfg *defs_cfg = NULL;\n+ struct doca_flow_definitions *defs = NULL;\n+ struct doca_flow_cfg *cfg;\n+ doca_error_t err;\n+\n+ if (!dpdk_available()) {\n+ VLOG_ERR(\"DOCA requires DPDK. Set other_config:dpdk-init=true.\");\n+ return ENODEV;\n+ }\n+\n+ if (rte_flow_dynf_metadata_register() < 0) {\n+ VLOG_ERR(\"Failed to register dynamic metadata, err: %s.\",\n+ rte_strerror(rte_errno));\n+ return ENOTSUP;\n+ }\n+\n+ log_stream = fopencookie(NULL, \"w+\", ovs_doca_log_func);\n+ if (!log_stream) {\n+ VLOG_ERR(\"Can't redirect DOCA log: %s.\", ovs_strerror(errno));\n+ } else {\n+ /* Create a logger back-end that prints to the redirected log */\n+ err = doca_log_backend_create_with_file_sdk(log_stream,\n+ &ovs_doca_log);\n+ if (err != DOCA_SUCCESS) {\n+ VLOG_ERR(\"%s: doca_log_backend_create_with_file_sdk failed.\"\n+ \" Error: %d (%s)\",\n+ OVS_SOURCE_LOCATOR, err, doca_error_get_descr(err));\n+ return ENODEV;\n+ }\n+\n+ doca_log_level_set_global_sdk_limit(DOCA_LOG_LEVEL_WARNING);\n+ }\n+\n+ unixctl_command_register(\"doca/log-set\", \"{level}. \"\n+ \"level=CRT/ERR/WRN/INF/DBG/TRC\", 0, 1,\n+ ovs_doca_unixctl_log_set, NULL);\n+ unixctl_command_register(\"doca/log-get\", \"\", 0, 0,\n+ unixctl_mem_stream, ovs_doca_log_get);\n+\n+ /* DOCA configuration happens earlier than dpif-netdev's.\n+ * To avoid reorganizing them, read the relevant item directly. */\n+ ovs_doca_max_megaflows_counters =\n+ smap_get_uint(ovs_other_config, \"flow-limit\",\n+ OVS_DOCA_MAX_MEGAFLOWS_COUNTERS);\n+\n+#define RV_TEST(call) \\\n+ do { \\\n+ err = (call); \\\n+ if (err) { \\\n+ VLOG_ERR(\"%s failed. Error: %d (%s)\", \\\n+ #call, err, doca_error_get_descr(err)); \\\n+ return ENODEV; \\\n+ } \\\n+ } while (0)\n+\n+ RV_TEST(doca_flow_cfg_create(&cfg));\n+ RV_TEST(doca_flow_cfg_set_pipe_queues(cfg,\n+ OVS_DOCA_MAX_OFFLOAD_QUEUES));\n+ RV_TEST(doca_flow_cfg_set_resource_mode(cfg,\n+ DOCA_FLOW_RESOURCE_MODE_PORT));\n+ RV_TEST(doca_flow_cfg_set_mode_args(cfg,\n+ \"switch\"\n+ \",hws\"\n+ \",isolated\"\n+ \",expert\"\n+ \"\"));\n+ RV_TEST(doca_flow_cfg_set_queue_depth(cfg, OVS_DOCA_QUEUE_DEPTH));\n+ RV_TEST(doca_flow_cfg_set_cb_entry_process(\n+ cfg, ovs_doca_offload_entry_process));\n+ RV_TEST(ovs_doca_init_defs(cfg, &defs, &defs_cfg));\n+\n+ VLOG_INFO(\"DOCA Enabled - initializing...\");\n+ RV_TEST(doca_flow_init(cfg));\n+ ovs_doca_destroy_defs(defs, defs_cfg);\n+ RV_TEST(doca_flow_cfg_destroy(cfg));\n+\n+#undef RV_TEST\n+\n+ netdev_doca_register();\n+ return 0;\n+}\n+\n+static bool\n+ovs_doca_available(void)\n+{\n+ bool available;\n+\n+ atomic_read_relaxed(&doca_initialized, &available);\n+ return available;\n+}\n+\n+/* Complete the queue 'qid' on the netdev's ESW until OVS_DOCA_QUEUE_DEPTH\n+ * entries are available.\n+ */\n+static doca_error_t\n+ovs_doca_complete_queue_esw(struct netdev_doca_esw_ctx *esw,\n+ unsigned int qid)\n+{\n+ struct ovs_doca_offload_queue *queue;\n+ long long int timeout_ms;\n+ unsigned int n_waiting;\n+ doca_error_t err;\n+ uint32_t room;\n+ int retries;\n+\n+ queue = &esw->offload_queues[qid];\n+ n_waiting = queue->n_waiting_entries;\n+\n+ if (n_waiting == 0) {\n+ COVERAGE_INC(ovs_doca_queue_empty);\n+ return DOCA_SUCCESS;\n+ }\n+\n+ /* 1 second timeout. */\n+ timeout_ms = time_msec() + 1 * 1000;\n+ retries = 100;\n+ do {\n+ unsigned int n_processed;\n+\n+ /* Use 'max_processed_entries' == 0 to always attempt processing\n+ * the full length of the queue. */\n+ err = doca_flow_entries_process(esw->esw_port, qid,\n+ OVS_DOCA_ENTRY_PROCESS_TIMEOUT_US, 0);\n+ if (err) {\n+ VLOG_WARN_RL(&rl, \"%s: Failed to process entries in queue \"\n+ \"%u. Error: %d (%s)\",\n+ netdev_get_name(esw->esw_netdev), qid,\n+ err, doca_error_get_descr(err));\n+ return err;\n+ }\n+\n+ n_processed = n_waiting - queue->n_waiting_entries;\n+ if (n_processed == 0) {\n+ COVERAGE_INC(ovs_doca_queue_none_processed);\n+ }\n+ n_waiting = queue->n_waiting_entries;\n+\n+ room = OVS_DOCA_QUEUE_DEPTH - n_waiting;\n+ if (n_processed == 0 && retries-- <= 0) {\n+ COVERAGE_INC(ovs_doca_queue_block);\n+ break;\n+ }\n+\n+ if (timeout_ms && time_msec() > timeout_ms) {\n+ ovs_abort(0, \"Timeout reached trying to complete queue %u: \"\n+ \"%u remaining entries\", qid, n_waiting);\n+ }\n+ } while (err == DOCA_SUCCESS && room < OVS_DOCA_QUEUE_DEPTH);\n+\n+ return err;\n+}\n+\n+static doca_error_t\n+ovs_doca_add_generic(unsigned int qid,\n+ uint32_t hash_index,\n+ struct doca_flow_pipe *pipe,\n+ enum doca_flow_pipe_type pipe_type,\n+ const struct ovs_doca_flow_match *omatch,\n+ const struct ovs_doca_flow_actions *oactions,\n+ const struct doca_flow_monitor *monitor,\n+ const struct doca_flow_fwd *fwd,\n+ uint32_t flags,\n+ struct netdev_doca_esw_ctx *esw,\n+ struct doca_flow_pipe_entry **pentry)\n+ OVS_NO_THREAD_SAFETY_ANALYSIS\n+{\n+ const struct doca_flow_actions *actions;\n+ struct ovs_doca_offload_queue *queues;\n+ const struct doca_flow_match *match;\n+ doca_error_t err;\n+\n+ ovs_assert(qid < OVS_DOCA_MAX_OFFLOAD_QUEUES);\n+\n+ ovs_assert(esw);\n+ queues = esw->offload_queues;\n+ match = omatch ? &omatch->d : NULL;\n+ actions = oactions ? &oactions->d : NULL;\n+\n+ ovs_assert(queues);\n+\n+ switch (pipe_type) {\n+ case DOCA_FLOW_PIPE_BASIC:\n+ err = doca_flow_pipe_basic_add_entry(qid, pipe, match, 0, actions,\n+ monitor, fwd, flags, queues,\n+ pentry);\n+ break;\n+ case DOCA_FLOW_PIPE_HASH:\n+ err = doca_flow_pipe_hash_add_entry(qid, pipe, hash_index, 0, actions,\n+ monitor, fwd, flags, queues,\n+ pentry);\n+ break;\n+ case DOCA_FLOW_PIPE_CONTROL:\n+ case DOCA_FLOW_PIPE_LPM:\n+ case DOCA_FLOW_PIPE_CT:\n+ case DOCA_FLOW_PIPE_ACL:\n+ case DOCA_FLOW_PIPE_ORDERED_LIST:\n+ OVS_NOT_REACHED();\n+ }\n+\n+ if (err == DOCA_SUCCESS) {\n+ queues[qid].n_waiting_entries++;\n+ }\n+\n+ return err;\n+}\n+\n+doca_error_t\n+ovs_doca_add_entry(struct netdev *netdev,\n+ unsigned int qid,\n+ struct doca_flow_pipe *pipe,\n+ const struct ovs_doca_flow_match *match,\n+ const struct ovs_doca_flow_actions *actions,\n+ const struct doca_flow_monitor *monitor,\n+ const struct doca_flow_fwd *fwd,\n+ uint32_t flags,\n+ struct doca_flow_pipe_entry **pentry)\n+{\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct netdev_doca_esw_ctx *esw = dev->esw_ctx;\n+ doca_error_t err;\n+\n+ err = ovs_doca_add_generic(qid, 0, pipe, DOCA_FLOW_PIPE_BASIC, match,\n+ actions, monitor, fwd, flags, esw, pentry);\n+ if (err) {\n+ VLOG_WARN_RL(&rl, \"%s: Failed to create basic pipe entry. \"\n+ \"Error: %d (%s)\", netdev_get_name(netdev), err,\n+ doca_error_get_descr(err));\n+ return err;\n+ }\n+\n+ if (DOCA_FLOW_FLAGS_IS_SET(flags, DOCA_FLOW_ENTRY_FLAGS_NO_WAIT)) {\n+ err = ovs_doca_complete_queue_esw(esw, qid);\n+ if (err != DOCA_SUCCESS) {\n+ VLOG_ERR(\"%s: ovs_doca_complete_queue_esw failed.\"\n+ \" Error: %d (%s)\",\n+ OVS_SOURCE_LOCATOR, err, doca_error_get_descr(err));\n+ return err;\n+ }\n+ }\n+\n+ return err;\n+}\n+\n+doca_error_t\n+ovs_doca_remove_entry(struct netdev_doca_esw_ctx *esw,\n+ unsigned int qid, uint32_t flags,\n+ struct doca_flow_pipe_entry **entry)\n+{\n+ doca_error_t err;\n+\n+ if (!*entry) {\n+ return DOCA_SUCCESS;\n+ }\n+\n+ ovs_assert(qid < OVS_DOCA_MAX_OFFLOAD_QUEUES);\n+\n+ err = doca_flow_pipe_remove_entry(qid, flags, *entry);\n+ if (err == DOCA_SUCCESS) {\n+ esw->offload_queues[qid].n_waiting_entries++;\n+ if (DOCA_FLOW_FLAGS_IS_SET(flags, DOCA_FLOW_ENTRY_FLAGS_NO_WAIT)) {\n+ /* Ignore potential errors here, as even if the queue completion\n+ * failed, the entry removal would still be issued. The caller\n+ * requires knowing so. */\n+ ovs_doca_complete_queue_esw(esw, qid);\n+ }\n+ *entry = NULL;\n+ } else {\n+ VLOG_ERR(\"Failed to remove entry %p qid=%d. Error: %d (%s)\",\n+ *entry, qid, err, doca_error_get_descr(err));\n+ }\n+\n+ return err;\n+}\n+\n+doca_error_t\n+ovs_doca_pipe_cfg_allow_queues(struct doca_flow_pipe_cfg *cfg,\n+ uint64_t queues_bitmap)\n+{\n+ ovs_assert(cfg);\n+\n+ for (unsigned int qid = 0; qid < OVS_DOCA_MAX_OFFLOAD_QUEUES; qid++) {\n+ doca_error_t err;\n+\n+ if ((UINT64_C(1) << qid) & queues_bitmap) {\n+ continue;\n+ }\n+\n+ err = doca_flow_pipe_cfg_set_excluded_queue(cfg, qid);\n+ if (DOCA_IS_ERROR(err)) {\n+ VLOG_ERR(\"Failed to exclude queue %u in pipe configuration.\"\n+ \" Error: %d (%s)\", qid, err, doca_error_get_descr(err));\n+ return err;\n+ }\n+ }\n+\n+ return DOCA_SUCCESS;\n+}\n+\n void\n-ovs_doca_init(const struct smap *ovs_other_config OVS_UNUSED)\n+ovs_doca_destroy_pipe(struct doca_flow_pipe **ppipe)\n {\n+ if (!ppipe || !*ppipe) {\n+ return;\n+ }\n+\n+ doca_flow_pipe_destroy(*ppipe);\n+ *ppipe = NULL;\n+}\n+\n+doca_error_t\n+ovs_doca_pipe_create(struct netdev *netdev,\n+ struct ovs_doca_flow_match *match,\n+ struct ovs_doca_flow_match *match_mask,\n+ struct doca_flow_monitor *monitor,\n+ struct ovs_doca_flow_actions *actions,\n+ struct ovs_doca_flow_actions *actions_mask,\n+ struct doca_flow_action_desc *desc,\n+ struct doca_flow_fwd *fwd,\n+ struct doca_flow_fwd *fwd_miss,\n+ uint32_t nr_entries,\n+ bool is_egress, bool is_root,\n+ uint64_t queues_bitmap,\n+ const char *pipe_str,\n+ struct doca_flow_pipe **pipe)\n+{\n+ struct doca_flow_actions *actions_arr[1], *actions_masks_arr[1];\n+ struct netdev_doca *dev = netdev_doca_cast(netdev);\n+ struct doca_flow_action_descs descs, *descs_arr[1];\n+ char pipe_name[OVS_DOCA_MAX_PIPE_NAME_LEN];\n+ struct doca_flow_port *doca_port;\n+ struct doca_flow_pipe_cfg *cfg;\n+ int ret;\n+\n+ ovs_assert(!*pipe);\n+\n+ doca_port = doca_flow_port_switch_get(dev->port);\n+ ovs_assert(doca_port);\n+\n+ snprintf(pipe_name, sizeof pipe_name, \"%s: %s\", netdev_get_name(netdev),\n+ pipe_str);\n+\n+ ret = doca_flow_pipe_cfg_create(&cfg, doca_port);\n+ if (ret) {\n+ VLOG_ERR(\"%s: Could not create doca_flow_pipe_cfg for %s.\"\n+ \" Error: %d (%s)\", netdev_get_name(netdev), pipe_name,\n+ ret, doca_error_get_descr(ret));\n+ return ret;\n+ }\n+\n+ actions_arr[0] = actions ? &actions->d : NULL;\n+ actions_masks_arr[0] = actions_mask ? &actions_mask->d : NULL;\n+ descs.desc_array = desc;\n+ descs.nb_action_desc = 1;\n+ descs_arr[0] = &descs;\n+\n+#define PIPE_CFG_SET(call) \\\n+ do { \\\n+ ret = (call); \\\n+ if (ret) { \\\n+ VLOG_ERR(\"%s: %s failed for %s. Error: %d (%s)\", \\\n+ netdev_get_name(netdev), #call, pipe_name, \\\n+ ret, doca_error_get_descr(ret)); \\\n+ goto error; \\\n+ } \\\n+ } while (0)\n+\n+ PIPE_CFG_SET(doca_flow_pipe_cfg_set_name(cfg, pipe_name));\n+ PIPE_CFG_SET(doca_flow_pipe_cfg_set_type(cfg, DOCA_FLOW_PIPE_BASIC));\n+ PIPE_CFG_SET(doca_flow_pipe_cfg_set_nr_entries(cfg, nr_entries));\n+ PIPE_CFG_SET(ovs_doca_pipe_cfg_allow_queues(cfg, queues_bitmap));\n+ if (is_egress) {\n+ PIPE_CFG_SET(doca_flow_pipe_cfg_set_domain(\n+ cfg, DOCA_FLOW_PIPE_DOMAIN_EGRESS));\n+ }\n+\n+ PIPE_CFG_SET(doca_flow_pipe_cfg_set_is_root(cfg, is_root));\n+ if (match) {\n+ PIPE_CFG_SET(doca_flow_pipe_cfg_set_match(cfg, &match->d,\n+ match_mask\n+ ? &match_mask->d\n+ : &match->d));\n+ }\n+\n+ if (monitor) {\n+ PIPE_CFG_SET(doca_flow_pipe_cfg_set_monitor(cfg, monitor));\n+ }\n+\n+ if (actions) {\n+ PIPE_CFG_SET(doca_flow_pipe_cfg_set_actions(cfg, actions_arr,\n+ actions_mask\n+ ? actions_masks_arr\n+ : actions_arr,\n+ desc\n+ ? descs_arr\n+ : NULL, 1));\n+ }\n+\n+#undef PIPE_CFG_SET\n+\n+ ret = doca_flow_pipe_create(cfg, fwd, fwd_miss, pipe);\n+ if (ret) {\n+ VLOG_ERR(\"%s: Failed to create basic pipe '%s'. Error: %d (%s)\",\n+ netdev_get_name(netdev), pipe_name, ret,\n+ doca_error_get_descr(ret));\n+ }\n+\n+error:\n+ doca_flow_pipe_cfg_destroy(cfg);\n+ return ret;\n+}\n+\n+unsigned int\n+ovs_doca_max_counters(void)\n+{\n+ return ovs_doca_max_megaflows_counters + OVS_DOCA_SLOWPATH_COUNTERS;\n+}\n+\n+void\n+ovs_doca_init(const struct smap *ovs_other_config)\n+{\n+ static bool enabled = false;\n+ int rv;\n+\n+ if (enabled || !ovs_other_config) {\n+ return;\n+ }\n+\n+ if (smap_get_bool(ovs_other_config, \"doca-init\", false)) {\n+ static struct ovsthread_once once_enable = OVSTHREAD_ONCE_INITIALIZER;\n+\n+ if (!ovsthread_once_start(&once_enable)) {\n+ return;\n+ }\n+\n+ VLOG_INFO(\"Using DOCA %s\", doca_version_runtime());\n+ VLOG_INFO(\"DOCA Enabled - initializing...\");\n+ rv = ovs_doca_init__(ovs_other_config);\n+ if (!rv) {\n+ VLOG_INFO(\"DOCA Enabled - initialized\");\n+ enabled = true;\n+ } else {\n+ ovs_abort(rv, \"DOCA Initialization Failed.\");\n+ }\n+\n+ ovsthread_once_done(&once_enable);\n+ } else {\n+ VLOG_INFO_ONCE(\"DOCA Disabled - Use other_config:doca-init to enable\");\n+ }\n+\n+ atomic_store_relaxed(&doca_initialized, enabled);\n }\n \n void\n@@ -56,7 +782,7 @@ ovs_doca_status(const struct ovsrec_open_vswitch *cfg)\n return;\n }\n \n- ovsrec_open_vswitch_set_doca_initialized(cfg, false);\n+ ovsrec_open_vswitch_set_doca_initialized(cfg, ovs_doca_available());\n ovsrec_open_vswitch_set_doca_version(cfg, doca_version_runtime());\n }\n \ndiff --git a/lib/ovs-doca.h b/lib/ovs-doca.h\nindex 9bd96c941..8a66572ef 100644\n--- a/lib/ovs-doca.h\n+++ b/lib/ovs-doca.h\n@@ -24,6 +24,88 @@\n struct ovsrec_open_vswitch;\n struct smap;\n \n+#ifdef DOCA_NETDEV\n+\n+#include <doca_dev.h>\n+#include <doca_flow.h>\n+\n+#include \"dp-packet.h\"\n+#include \"ovs-thread.h\"\n+#include \"util.h\"\n+\n+#define AUX_QUEUE 0\n+#define OVS_DOCA_MAX_OFFLOAD_QUEUES 1\n+#define OVS_DOCA_QUEUE_DEPTH 32\n+#define OVS_DOCA_ENTRY_PROCESS_TIMEOUT_US 1000\n+\n+/* Estimated maximum number of megaflows */\n+#define OVS_DOCA_MAX_MEGAFLOWS_COUNTERS (1 << 19)\n+\n+#define OVS_DOCA_MAX_PIPE_NAME_LEN 128\n+\n+struct netdev_doca_esw_ctx;\n+\n+struct ovs_doca_offload_queue {\n+ PADDED_MEMBERS(CACHE_LINE_SIZE,\n+ unsigned int n_waiting_entries;\n+ );\n+};\n+\n+struct ovs_doca_flow_actions {\n+ struct doca_flow_actions d;\n+ uint32_t mark;\n+};\n+BUILD_ASSERT_DECL(offsetof(struct ovs_doca_flow_actions, d) == 0);\n+\n+struct ovs_doca_flow_match {\n+ struct doca_flow_match d;\n+};\n+BUILD_ASSERT_DECL(offsetof(struct ovs_doca_flow_match, d) == 0);\n+\n+doca_error_t\n+ovs_doca_add_entry(struct netdev *netdev,\n+ unsigned int qid,\n+ struct doca_flow_pipe *pipe,\n+ const struct ovs_doca_flow_match *match,\n+ const struct ovs_doca_flow_actions *actions,\n+ const struct doca_flow_monitor *monitor,\n+ const struct doca_flow_fwd *fwd,\n+ uint32_t flags,\n+ struct doca_flow_pipe_entry **pentry);\n+\n+doca_error_t\n+ovs_doca_remove_entry(struct netdev_doca_esw_ctx *esw,\n+ unsigned int qid, uint32_t flags,\n+ struct doca_flow_pipe_entry **entry);\n+\n+void\n+ovs_doca_destroy_pipe(struct doca_flow_pipe **ppipe);\n+\n+doca_error_t\n+ovs_doca_pipe_create(struct netdev *netdev,\n+ struct ovs_doca_flow_match *match,\n+ struct ovs_doca_flow_match *match_mask,\n+ struct doca_flow_monitor *monitor,\n+ struct ovs_doca_flow_actions *actions,\n+ struct ovs_doca_flow_actions *actions_mask,\n+ struct doca_flow_action_desc *desc,\n+ struct doca_flow_fwd *fwd,\n+ struct doca_flow_fwd *fwd_miss,\n+ uint32_t nr_entries,\n+ bool is_egress, bool is_root,\n+ uint64_t queues_bitmap,\n+ const char *pipe_str,\n+ struct doca_flow_pipe **pipe);\n+\n+doca_error_t\n+ovs_doca_pipe_cfg_allow_queues(struct doca_flow_pipe_cfg *cfg,\n+ uint64_t queues_bitmap);\n+\n+unsigned int\n+ovs_doca_max_counters(void);\n+\n+#endif /* DOCA_NETDEV */\n+\n void ovs_doca_init(const struct smap *ovs_other_config);\n void print_doca_version(void);\n void ovs_doca_status(const struct ovsrec_open_vswitch *);\ndiff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at\nindex 7f6ab8904..e19f7a2d0 100644\n--- a/tests/ofproto-macros.at\n+++ b/tests/ofproto-macros.at\n@@ -223,6 +223,7 @@ m4_define([_OVS_VSWITCHD_START],\n /netdev_linux|INFO|.*device has unknown hardware address family/d\n /ofproto|INFO|datapath ID changed to fedcba9876543210/d\n /dpdk|INFO|DPDK Disabled - Use other_config:dpdk-init to enable/d\n+/ovs_doca|INFO|DOCA Disabled - Use other_config:doca-init to enable/d\n /netlink_socket|INFO|netlink: could not enable listening to all nsid/d\n /dpif_offload|INFO|Flow HW offload is enabled/d\n /probe tc:/d\ndiff --git a/utilities/checkpatch_dict.txt b/utilities/checkpatch_dict.txt\nindex 6a454bcf8..37a30077f 100644\n--- a/utilities/checkpatch_dict.txt\n+++ b/utilities/checkpatch_dict.txt\n@@ -235,6 +235,7 @@ recirc\n recirculation\n recirculations\n refmap\n+representor\n revalidate\n revalidation\n revalidator\ndiff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml\nindex 9edd1027e..df0819f26 100644\n--- a/vswitchd/vswitch.xml\n+++ b/vswitchd/vswitch.xml\n@@ -337,6 +337,22 @@\n </p>\n </column>\n \n+ <column name=\"other_config\" key=\"doca-init\"\n+ type='{\"type\": \"boolean\"}'>\n+ <p>\n+ A value of <code>true</code> will cause the ovs-vswitchd process to\n+ abort if DOCA cannot be initialized.\n+ </p>\n+ <p>\n+ The default value is <code>false</code>. Changing this value\n+ requires restarting the daemon\n+ </p>\n+ <p>\n+ If this value is <code>false</code> at startup, any doca ports which\n+ are configured in the bridge will fail as an unknown port type.\n+ </p>\n+ </column>\n+\n <column name=\"other_config\" key=\"dpdk-lcore-mask\"\n type='{\"type\": \"integer\", \"minInteger\": 1}'>\n <p>\n@@ -510,6 +526,10 @@\n \n <column name=\"other_config\" key=\"per-port-memory\"\n type='{\"type\": \"boolean\"}'>\n+ <p>\n+ NOTE: For DOCA, there is a mempool per ESW manager, which is shared\n+ with all its representors. This configuration is N/A for doca.\n+ </p>\n <p>\n By default OVS DPDK uses a shared memory model wherein devices\n that have the same MTU and socket values can share the same\n@@ -526,36 +546,40 @@\n </column>\n \n <column name=\"other_config\" key=\"shared-mempool-config\">\n- <p>Specifies dpdk shared mempool config.</p>\n- <p>Value should be set in the following form:</p>\n- <p>\n- <code>other_config:shared-mempool-config=<\n- user-shared-mempool-mtu-list></code>\n- </p>\n- <p>where</p>\n- <p>\n- <ul>\n- <li>\n- <user-shared-mempool-mtu-list> ::=\n- NULL | <non-empty-list>\n- </li>\n- <li>\n- <non-empty-list> ::= <user-mtus> |\n- <user-mtus> ,\n- <non-empty-list>\n- </li>\n- <li>\n- <user-mtus> ::= <mtu-all-socket> |\n- <mtu-socket-pair>\n- </li>\n- <li>\n- <mtu-all-socket> ::= <mtu>\n- </li>\n- <li>\n- <mtu-socket-pair> ::= <mtu> : <socket-id>\n- </li>\n- </ul>\n- </p>\n+ <p>\n+ NOTE: For DOCA, there is a mempool per ESW manager, which is shared\n+ with all its representors. This configuration is N/A for doca.\n+ </p>\n+ <p>Specifies dpdk shared mempool config.</p>\n+ <p>Value should be set in the following form:</p>\n+ <p>\n+ <code>other_config:shared-mempool-config=<\n+ user-shared-mempool-mtu-list></code>\n+ </p>\n+ <p>where</p>\n+ <p>\n+ <ul>\n+ <li>\n+ <user-shared-mempool-mtu-list> ::=\n+ NULL | <non-empty-list>\n+ </li>\n+ <li>\n+ <non-empty-list> ::= <user-mtus> |\n+ <user-mtus> ,\n+ <non-empty-list>\n+ </li>\n+ <li>\n+ <user-mtus> ::= <mtu-all-socket> |\n+ <mtu-socket-pair>\n+ </li>\n+ <li>\n+ <mtu-all-socket> ::= <mtu>\n+ </li>\n+ <li>\n+ <mtu-socket-pair> ::= <mtu> : <socket-id>\n+ </li>\n+ </ul>\n+ </p>\n <p>\n Changing this value requires restarting the daemon if dpdk-init has\n already been set to true.\n@@ -942,7 +966,8 @@\n </column>\n \n <column name=\"doca_initialized\">\n- Always false.\n+ True if <ref column=\"other_config\" key=\"doca-init\"/> is set to\n+ true and the DOCA library is successfully initialized.\n </column>\n \n <group title=\"Statistics\">\n", "prefixes": [ "ovs-dev", "v3", "11/11" ] }