get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/2218458/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "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(&eth_addr, 0x0, sizeof(eth_addr));\n+    rte_eth_macaddr_get(common->port_id, &eth_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=&lt;\n-                  user-shared-mempool-mtu-list&gt;</code>\n-              </p>\n-              <p>where</p>\n-              <p>\n-                <ul>\n-                  <li>\n-                    &lt;user-shared-mempool-mtu-list&gt; ::=\n-                    NULL | &lt;non-empty-list&gt;\n-                  </li>\n-                  <li>\n-                    &lt;non-empty-list&gt; ::= &lt;user-mtus&gt; |\n-                                               &lt;user-mtus&gt; ,\n-                                               &lt;non-empty-list&gt;\n-                  </li>\n-                  <li>\n-                    &lt;user-mtus&gt; ::= &lt;mtu-all-socket&gt; |\n-                                          &lt;mtu-socket-pair&gt;\n-                  </li>\n-                  <li>\n-                    &lt;mtu-all-socket&gt; ::= &lt;mtu&gt;\n-                  </li>\n-                  <li>\n-                    &lt;mtu-socket-pair&gt; ::= &lt;mtu&gt; : &lt;socket-id&gt;\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=&lt;\n+            user-shared-mempool-mtu-list&gt;</code>\n+        </p>\n+        <p>where</p>\n+        <p>\n+          <ul>\n+            <li>\n+              &lt;user-shared-mempool-mtu-list&gt; ::=\n+              NULL | &lt;non-empty-list&gt;\n+            </li>\n+            <li>\n+              &lt;non-empty-list&gt; ::= &lt;user-mtus&gt; |\n+                                         &lt;user-mtus&gt; ,\n+                                         &lt;non-empty-list&gt;\n+            </li>\n+            <li>\n+              &lt;user-mtus&gt; ::= &lt;mtu-all-socket&gt; |\n+                                    &lt;mtu-socket-pair&gt;\n+            </li>\n+            <li>\n+              &lt;mtu-all-socket&gt; ::= &lt;mtu&gt;\n+            </li>\n+            <li>\n+              &lt;mtu-socket-pair&gt; ::= &lt;mtu&gt; : &lt;socket-id&gt;\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"
    ]
}