Got to the adaption of the existing representor plugin code to the
framework as the the last thing prior to submitting to reach the
soft-freeze, it could use this:

--- a/lib/plug_providers/representor/plug-representor.c
+++ b/lib/plug_providers/representor/plug-representor.c
@@ -228,11 +228,16 @@ plug_representor_port_prepare(const struct
plug_port_ctx_in *ctx_in,

     char *rep_port;
     rep_port = shash_find_data(&devlink_ports, keybuf);
-    VLOG_INFO("plug_representor %s (%s) -> %s",
-              ctx_in->lport_name, rep_port, ctx_in->br_int->name);
+    if (!rep_port) {
+        VLOG_INFO("No representor port found for "
+                  "lport: %s pf-mac: '%s' vf-num: '%s'",
+                  ctx_in->lport_name, pf_mac, vf_num);
+        return false;
+    }
     ctx_out->name = rep_port;
     ctx_out->type = NULL;
     ctx_out->iface_options = NULL;
+
     return true;
 }

Otherwise I'm working on the runtime update part of this module and
will submit once I've received any other feedback on this series.

On Fri, Sep 3, 2021 at 9:28 PM Frode Nordahl
<frode.nord...@canonical.com> wrote:
>
> Add the first in-tree plug provider plugin and its dependencies.
> The representor plugin can be used with multiple NIC vendors
> supporting Open vSwitch hardware offload and the devlink-port
> infrastructure[0].
>
> It is particularly useful for use with NICs connected to multiple
> distinct CPUs where the instance runs on one host and Open
> vSwitch and OVN runs on a different host, the smartnic CPU.
>
> Extend the build system with macros from the OVS build system to
> allow checking for dependencies of the plugin as well as providing
> kernel header files that may not be available at build time.
>
> The plugin will only be built when enabled and when building on
> a Linux system.
>
> 0: https://www.kernel.org/doc/html/latest/networking/devlink/devlink-port.html
> Signed-off-by: Frode Nordahl <frode.nord...@canonical.com>
> ---
>  Documentation/automake.mk                     |   1 +
>  Documentation/topics/plug_providers/index.rst |   1 +
>  .../topics/plug_providers/plug-providers.rst  |   5 +
>  .../plug_providers/plug-representor.rst       |  45 ++
>  build-aux/initial-tab-whitelist               |   1 +
>  configure.ac                                  |   2 +
>  include/automake.mk                           |   4 +
>  include/linux/automake.mk                     |   2 +
>  include/linux/devlink.h                       | 625 ++++++++++++++++++
>  lib/automake.mk                               |  11 +
>  lib/plug-provider.h                           |   6 +-
>  lib/plug.c                                    |   1 +
>  .../representor/netlink-devlink.c             | 499 ++++++++++++++
>  .../representor/netlink-devlink.h             | 115 ++++
>  .../representor/plug-representor.c            | 307 +++++++++
>  m4/ovn.m4                                     |  26 +
>  16 files changed, 1650 insertions(+), 1 deletion(-)
>  create mode 100644 Documentation/topics/plug_providers/plug-representor.rst
>  create mode 100644 include/linux/automake.mk
>  create mode 100644 include/linux/devlink.h
>  create mode 100644 lib/plug_providers/representor/netlink-devlink.c
>  create mode 100644 lib/plug_providers/representor/netlink-devlink.h
>  create mode 100644 lib/plug_providers/representor/plug-representor.c
>
> diff --git a/Documentation/automake.mk b/Documentation/automake.mk
> index 92a843d76..2a3483bc0 100644
> --- a/Documentation/automake.mk
> +++ b/Documentation/automake.mk
> @@ -29,6 +29,7 @@ DOC_SOURCE = \
>         Documentation/topics/role-based-access-control.rst \
>         Documentation/topics/debugging-ddlog.rst \
>         Documentation/topics/plug_providers/plug-providers.rst \
> +       Documentation/topics/plug_providers/plug-representor.rst \
>         Documentation/howto/index.rst \
>         Documentation/howto/docker.rst \
>         Documentation/howto/firewalld.rst \
> diff --git a/Documentation/topics/plug_providers/index.rst 
> b/Documentation/topics/plug_providers/index.rst
> index 837eeae15..3d16458a2 100644
> --- a/Documentation/topics/plug_providers/index.rst
> +++ b/Documentation/topics/plug_providers/index.rst
> @@ -30,3 +30,4 @@ Plug Providers
>     :maxdepth: 2
>
>     plug-providers
> +   plug-representor
> diff --git a/Documentation/topics/plug_providers/plug-providers.rst 
> b/Documentation/topics/plug_providers/plug-providers.rst
> index 7b891156c..5f0089ed9 100644
> --- a/Documentation/topics/plug_providers/plug-providers.rst
> +++ b/Documentation/topics/plug_providers/plug-providers.rst
> @@ -163,6 +163,11 @@ Building with in-tree plugging providers
>  Plugging providers hosted in the OVN repository living under
>  `lib/plug_providers`:
>
> +* :doc:`representor <plug-representor>`
> +
> +  - Representor port lookup making use of the Linux kernel devlink-port
> +    infrastructure.
> +
>  To enable them, provide the `--enable-plug-providers` command line option to
>  the configure script when building OVN.
>
> diff --git a/Documentation/topics/plug_providers/plug-representor.rst 
> b/Documentation/topics/plug_providers/plug-representor.rst
> new file mode 100644
> index 000000000..c301a6cd2
> --- /dev/null
> +++ b/Documentation/topics/plug_providers/plug-representor.rst
> @@ -0,0 +1,45 @@
> +..
> +      Licensed under the Apache License, Version 2.0 (the "License"); you may
> +      not use this file except in compliance with the License. You may obtain
> +      a copy of the License at
> +
> +          http://www.apache.org/licenses/LICENSE-2.0
> +
> +      Unless required by applicable law or agreed to in writing, software
> +      distributed under the License is distributed on an "AS IS" BASIS, 
> WITHOUT
> +      WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See 
> the
> +      License for the specific language governing permissions and limitations
> +      under the License.
> +
> +      Convention for heading levels in OVN documentation:
> +
> +      =======  Heading 0 (reserved for the title in a document)
> +      -------  Heading 1
> +      ~~~~~~~  Heading 2
> +      +++++++  Heading 3
> +      '''''''  Heading 4
> +
> +      Avoid deeper levels because they do not render well.
> +
> +=============================
> +The Representor Plug Provider
> +=============================
> +
> +Logical Switch Port Options
> +---------------------------
> +
> +plug:representor:pf-mac
> +~~~~~~~~~~~~~~~~~~~~~~~
> +
> +MAC address for identifying PF device.  When
> +`OVN_Northbound:Logical_Switch_Port:options` key `plug:representor:vf-num` is
> +also set, this option is used to identify PF to use as base to locate the
> +correct VF representor port.  When 
> `OVN_Northbound:Logical_Switch_Port:options`
> +key `plug:representor:vf-num` is not set this option is used to locate a PF
> +representor port.
> +
> +plug:representor:vf-num
> +~~~~~~~~~~~~~~~~~~~~~~~
> +
> +Logical VF number relative to PF device specified in
> +`OVN_Northbound:Logical_Switch_Port:options` key `plug-pf-mac`.
> diff --git a/build-aux/initial-tab-whitelist b/build-aux/initial-tab-whitelist
> index b2f5a0791..c70f93891 100644
> --- a/build-aux/initial-tab-whitelist
> +++ b/build-aux/initial-tab-whitelist
> @@ -3,6 +3,7 @@
>  \.mk$
>  \.png$
>  \.sln$
> +^include/linux/
>  ^ovs/
>  ^third-party/
>  ^xenserver/
> diff --git a/configure.ac b/configure.ac
> index 7f3274e59..5b542bfd1 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -174,6 +174,8 @@ OVS_CHECK_PRAGMA_MESSAGE
>  OVN_CHECK_OVS
>  OVN_CHECK_PLUG_PROVIDER
>  OVN_ENABLE_PLUG
> +OVS_CHECK_NETLINK
> +OVS_CHECK_LINUX_NETLINK
>  OVS_CTAGS_IDENTIFIERS
>  AC_SUBST([OVS_CFLAGS])
>  AC_SUBST([OVS_LDFLAGS])
> diff --git a/include/automake.mk b/include/automake.mk
> index 9e8403f8d..75638bd9a 100644
> --- a/include/automake.mk
> +++ b/include/automake.mk
> @@ -1,2 +1,6 @@
>  include include/ovn/automake.mk
>
> +if LINUX
> +include include/linux/automake.mk
> +endif
> +
> diff --git a/include/linux/automake.mk b/include/linux/automake.mk
> new file mode 100644
> index 000000000..5b53597eb
> --- /dev/null
> +++ b/include/linux/automake.mk
> @@ -0,0 +1,2 @@
> +noinst_HEADERS += \
> +       include/linux/devlink.h
> diff --git a/include/linux/devlink.h b/include/linux/devlink.h
> new file mode 100644
> index 000000000..28ea92b62
> --- /dev/null
> +++ b/include/linux/devlink.h
> @@ -0,0 +1,625 @@
> +/*
> + * The kernel devlink interface has gained a number of additions in recent
> + * kernel versions. To allow Open vSwitch to consume these interfaces in its
> + * runtime environment regardless of what kernel version was available at 
> build
> + * time, and also avoiding an elaborate set of autoconf macros to check for
> + * presence of individual pieces, we include the entire file here.
> + *
> + * Source:
> + * 
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/include/uapi/linux/devlink.h
>  @ a556dded9c23c51c82654f1ebe389cbc0bc22057 */
> +#if !defined(__KERNEL__)
> +#ifndef __UAPI_LINUX_DEVLINK_WRAPPER_H
> +#define __UAPI_LINUX_DEVLINK_WRAPPER_H 1
> +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
> +/*
> + * include/uapi/linux/devlink.h - Network physical device Netlink interface
> + * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
> + * Copyright (c) 2016 Jiri Pirko <j...@mellanox.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#ifndef _UAPI_LINUX_DEVLINK_H_
> +#define _UAPI_LINUX_DEVLINK_H_
> +
> +#include <linux/const.h>
> +
> +#define DEVLINK_GENL_NAME "devlink"
> +#define DEVLINK_GENL_VERSION 0x1
> +#define DEVLINK_GENL_MCGRP_CONFIG_NAME "config"
> +
> +enum devlink_command {
> +       /* don't change the order or add anything between, this is ABI! */
> +       DEVLINK_CMD_UNSPEC,
> +
> +       DEVLINK_CMD_GET,                /* can dump */
> +       DEVLINK_CMD_SET,
> +       DEVLINK_CMD_NEW,
> +       DEVLINK_CMD_DEL,
> +
> +       DEVLINK_CMD_PORT_GET,           /* can dump */
> +       DEVLINK_CMD_PORT_SET,
> +       DEVLINK_CMD_PORT_NEW,
> +       DEVLINK_CMD_PORT_DEL,
> +
> +       DEVLINK_CMD_PORT_SPLIT,
> +       DEVLINK_CMD_PORT_UNSPLIT,
> +
> +       DEVLINK_CMD_SB_GET,             /* can dump */
> +       DEVLINK_CMD_SB_SET,
> +       DEVLINK_CMD_SB_NEW,
> +       DEVLINK_CMD_SB_DEL,
> +
> +       DEVLINK_CMD_SB_POOL_GET,        /* can dump */
> +       DEVLINK_CMD_SB_POOL_SET,
> +       DEVLINK_CMD_SB_POOL_NEW,
> +       DEVLINK_CMD_SB_POOL_DEL,
> +
> +       DEVLINK_CMD_SB_PORT_POOL_GET,   /* can dump */
> +       DEVLINK_CMD_SB_PORT_POOL_SET,
> +       DEVLINK_CMD_SB_PORT_POOL_NEW,
> +       DEVLINK_CMD_SB_PORT_POOL_DEL,
> +
> +       DEVLINK_CMD_SB_TC_POOL_BIND_GET,        /* can dump */
> +       DEVLINK_CMD_SB_TC_POOL_BIND_SET,
> +       DEVLINK_CMD_SB_TC_POOL_BIND_NEW,
> +       DEVLINK_CMD_SB_TC_POOL_BIND_DEL,
> +
> +       /* Shared buffer occupancy monitoring commands */
> +       DEVLINK_CMD_SB_OCC_SNAPSHOT,
> +       DEVLINK_CMD_SB_OCC_MAX_CLEAR,
> +
> +       DEVLINK_CMD_ESWITCH_GET,
> +#define DEVLINK_CMD_ESWITCH_MODE_GET /* obsolete, never use this! */ \
> +       DEVLINK_CMD_ESWITCH_GET
> +
> +       DEVLINK_CMD_ESWITCH_SET,
> +#define DEVLINK_CMD_ESWITCH_MODE_SET /* obsolete, never use this! */ \
> +       DEVLINK_CMD_ESWITCH_SET
> +
> +       DEVLINK_CMD_DPIPE_TABLE_GET,
> +       DEVLINK_CMD_DPIPE_ENTRIES_GET,
> +       DEVLINK_CMD_DPIPE_HEADERS_GET,
> +       DEVLINK_CMD_DPIPE_TABLE_COUNTERS_SET,
> +       DEVLINK_CMD_RESOURCE_SET,
> +       DEVLINK_CMD_RESOURCE_DUMP,
> +
> +       /* Hot driver reload, makes configuration changes take place. The
> +        * devlink instance is not released during the process.
> +        */
> +       DEVLINK_CMD_RELOAD,
> +
> +       DEVLINK_CMD_PARAM_GET,          /* can dump */
> +       DEVLINK_CMD_PARAM_SET,
> +       DEVLINK_CMD_PARAM_NEW,
> +       DEVLINK_CMD_PARAM_DEL,
> +
> +       DEVLINK_CMD_REGION_GET,
> +       DEVLINK_CMD_REGION_SET,
> +       DEVLINK_CMD_REGION_NEW,
> +       DEVLINK_CMD_REGION_DEL,
> +       DEVLINK_CMD_REGION_READ,
> +
> +       DEVLINK_CMD_PORT_PARAM_GET,     /* can dump */
> +       DEVLINK_CMD_PORT_PARAM_SET,
> +       DEVLINK_CMD_PORT_PARAM_NEW,
> +       DEVLINK_CMD_PORT_PARAM_DEL,
> +
> +       DEVLINK_CMD_INFO_GET,           /* can dump */
> +
> +       DEVLINK_CMD_HEALTH_REPORTER_GET,
> +       DEVLINK_CMD_HEALTH_REPORTER_SET,
> +       DEVLINK_CMD_HEALTH_REPORTER_RECOVER,
> +       DEVLINK_CMD_HEALTH_REPORTER_DIAGNOSE,
> +       DEVLINK_CMD_HEALTH_REPORTER_DUMP_GET,
> +       DEVLINK_CMD_HEALTH_REPORTER_DUMP_CLEAR,
> +
> +       DEVLINK_CMD_FLASH_UPDATE,
> +       DEVLINK_CMD_FLASH_UPDATE_END,           /* notification only */
> +       DEVLINK_CMD_FLASH_UPDATE_STATUS,        /* notification only */
> +
> +       DEVLINK_CMD_TRAP_GET,           /* can dump */
> +       DEVLINK_CMD_TRAP_SET,
> +       DEVLINK_CMD_TRAP_NEW,
> +       DEVLINK_CMD_TRAP_DEL,
> +
> +       DEVLINK_CMD_TRAP_GROUP_GET,     /* can dump */
> +       DEVLINK_CMD_TRAP_GROUP_SET,
> +       DEVLINK_CMD_TRAP_GROUP_NEW,
> +       DEVLINK_CMD_TRAP_GROUP_DEL,
> +
> +       DEVLINK_CMD_TRAP_POLICER_GET,   /* can dump */
> +       DEVLINK_CMD_TRAP_POLICER_SET,
> +       DEVLINK_CMD_TRAP_POLICER_NEW,
> +       DEVLINK_CMD_TRAP_POLICER_DEL,
> +
> +       DEVLINK_CMD_HEALTH_REPORTER_TEST,
> +
> +       /* add new commands above here */
> +       __DEVLINK_CMD_MAX,
> +       DEVLINK_CMD_MAX = __DEVLINK_CMD_MAX - 1
> +};
> +
> +enum devlink_port_type {
> +       DEVLINK_PORT_TYPE_NOTSET,
> +       DEVLINK_PORT_TYPE_AUTO,
> +       DEVLINK_PORT_TYPE_ETH,
> +       DEVLINK_PORT_TYPE_IB,
> +};
> +
> +enum devlink_sb_pool_type {
> +       DEVLINK_SB_POOL_TYPE_INGRESS,
> +       DEVLINK_SB_POOL_TYPE_EGRESS,
> +};
> +
> +/* static threshold - limiting the maximum number of bytes.
> + * dynamic threshold - limiting the maximum number of bytes
> + *   based on the currently available free space in the shared buffer pool.
> + *   In this mode, the maximum quota is calculated based
> + *   on the following formula:
> + *     max_quota = alpha / (1 + alpha) * Free_Buffer
> + *   While Free_Buffer is the amount of none-occupied buffer associated to
> + *   the relevant pool.
> + *   The value range which can be passed is 0-20 and serves
> + *   for computation of alpha by following formula:
> + *     alpha = 2 ^ (passed_value - 10)
> + */
> +
> +enum devlink_sb_threshold_type {
> +       DEVLINK_SB_THRESHOLD_TYPE_STATIC,
> +       DEVLINK_SB_THRESHOLD_TYPE_DYNAMIC,
> +};
> +
> +#define DEVLINK_SB_THRESHOLD_TO_ALPHA_MAX 20
> +
> +enum devlink_eswitch_mode {
> +       DEVLINK_ESWITCH_MODE_LEGACY,
> +       DEVLINK_ESWITCH_MODE_SWITCHDEV,
> +};
> +
> +enum devlink_eswitch_inline_mode {
> +       DEVLINK_ESWITCH_INLINE_MODE_NONE,
> +       DEVLINK_ESWITCH_INLINE_MODE_LINK,
> +       DEVLINK_ESWITCH_INLINE_MODE_NETWORK,
> +       DEVLINK_ESWITCH_INLINE_MODE_TRANSPORT,
> +};
> +
> +enum devlink_eswitch_encap_mode {
> +       DEVLINK_ESWITCH_ENCAP_MODE_NONE,
> +       DEVLINK_ESWITCH_ENCAP_MODE_BASIC,
> +};
> +
> +enum devlink_port_flavour {
> +       DEVLINK_PORT_FLAVOUR_PHYSICAL, /* Any kind of a port physically
> +                                       * facing the user.
> +                                       */
> +       DEVLINK_PORT_FLAVOUR_CPU, /* CPU port */
> +       DEVLINK_PORT_FLAVOUR_DSA, /* Distributed switch architecture
> +                                  * interconnect port.
> +                                  */
> +       DEVLINK_PORT_FLAVOUR_PCI_PF, /* Represents eswitch port for
> +                                     * the PCI PF. It is an internal
> +                                     * port that faces the PCI PF.
> +                                     */
> +       DEVLINK_PORT_FLAVOUR_PCI_VF, /* Represents eswitch port
> +                                     * for the PCI VF. It is an internal
> +                                     * port that faces the PCI VF.
> +                                     */
> +       DEVLINK_PORT_FLAVOUR_VIRTUAL, /* Any virtual port facing the user. */
> +       DEVLINK_PORT_FLAVOUR_UNUSED, /* Port which exists in the switch, but
> +                                     * is not used in any way.
> +                                     */
> +       DEVLINK_PORT_FLAVOUR_PCI_SF, /* Represents eswitch port
> +                                     * for the PCI SF. It is an internal
> +                                     * port that faces the PCI SF.
> +                                     */
> +};
> +
> +enum devlink_param_cmode {
> +       DEVLINK_PARAM_CMODE_RUNTIME,
> +       DEVLINK_PARAM_CMODE_DRIVERINIT,
> +       DEVLINK_PARAM_CMODE_PERMANENT,
> +
> +       /* Add new configuration modes above */
> +       __DEVLINK_PARAM_CMODE_MAX,
> +       DEVLINK_PARAM_CMODE_MAX = __DEVLINK_PARAM_CMODE_MAX - 1
> +};
> +
> +enum devlink_param_fw_load_policy_value {
> +       DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DRIVER,
> +       DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_FLASH,
> +       DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DISK,
> +       DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_UNKNOWN,
> +};
> +
> +enum devlink_param_reset_dev_on_drv_probe_value {
> +       DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_UNKNOWN,
> +       DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_ALWAYS,
> +       DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_NEVER,
> +       DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_DISK,
> +};
> +
> +enum {
> +       DEVLINK_ATTR_STATS_RX_PACKETS,          /* u64 */
> +       DEVLINK_ATTR_STATS_RX_BYTES,            /* u64 */
> +       DEVLINK_ATTR_STATS_RX_DROPPED,          /* u64 */
> +
> +       __DEVLINK_ATTR_STATS_MAX,
> +       DEVLINK_ATTR_STATS_MAX = __DEVLINK_ATTR_STATS_MAX - 1
> +};
> +
> +/* Specify what sections of a flash component can be overwritten when
> + * performing an update. Overwriting of firmware binary sections is always
> + * implicitly assumed to be allowed.
> + *
> + * Each section must be documented in
> + * Documentation/networking/devlink/devlink-flash.rst
> + *
> + */
> +enum {
> +       DEVLINK_FLASH_OVERWRITE_SETTINGS_BIT,
> +       DEVLINK_FLASH_OVERWRITE_IDENTIFIERS_BIT,
> +
> +       __DEVLINK_FLASH_OVERWRITE_MAX_BIT,
> +       DEVLINK_FLASH_OVERWRITE_MAX_BIT = __DEVLINK_FLASH_OVERWRITE_MAX_BIT - 
> 1
> +};
> +
> +#define DEVLINK_FLASH_OVERWRITE_SETTINGS 
> _BITUL(DEVLINK_FLASH_OVERWRITE_SETTINGS_BIT)
> +#define DEVLINK_FLASH_OVERWRITE_IDENTIFIERS 
> _BITUL(DEVLINK_FLASH_OVERWRITE_IDENTIFIERS_BIT)
> +
> +#define DEVLINK_SUPPORTED_FLASH_OVERWRITE_SECTIONS \
> +       (_BITUL(__DEVLINK_FLASH_OVERWRITE_MAX_BIT) - 1)
> +
> +/**
> + * enum devlink_trap_action - Packet trap action.
> + * @DEVLINK_TRAP_ACTION_DROP: Packet is dropped by the device and a copy is 
> not
> + *                            sent to the CPU.
> + * @DEVLINK_TRAP_ACTION_TRAP: The sole copy of the packet is sent to the CPU.
> + * @DEVLINK_TRAP_ACTION_MIRROR: Packet is forwarded by the device and a copy 
> is
> + *                              sent to the CPU.
> + */
> +enum devlink_trap_action {
> +       DEVLINK_TRAP_ACTION_DROP,
> +       DEVLINK_TRAP_ACTION_TRAP,
> +       DEVLINK_TRAP_ACTION_MIRROR,
> +};
> +
> +/**
> + * enum devlink_trap_type - Packet trap type.
> + * @DEVLINK_TRAP_TYPE_DROP: Trap reason is a drop. Trapped packets are only
> + *                          processed by devlink and not injected to the
> + *                          kernel's Rx path.
> + * @DEVLINK_TRAP_TYPE_EXCEPTION: Trap reason is an exception. Packet was not
> + *                               forwarded as intended due to an exception
> + *                               (e.g., missing neighbour entry) and trapped 
> to
> + *                               control plane for resolution. Trapped 
> packets
> + *                               are processed by devlink and injected to
> + *                               the kernel's Rx path.
> + * @DEVLINK_TRAP_TYPE_CONTROL: Packet was trapped because it is required for
> + *                             the correct functioning of the control plane.
> + *                             For example, an ARP request packet. Trapped
> + *                             packets are injected to the kernel's Rx path,
> + *                             but not reported to drop monitor.
> + */
> +enum devlink_trap_type {
> +       DEVLINK_TRAP_TYPE_DROP,
> +       DEVLINK_TRAP_TYPE_EXCEPTION,
> +       DEVLINK_TRAP_TYPE_CONTROL,
> +};
> +
> +enum {
> +       /* Trap can report input port as metadata */
> +       DEVLINK_ATTR_TRAP_METADATA_TYPE_IN_PORT,
> +       /* Trap can report flow action cookie as metadata */
> +       DEVLINK_ATTR_TRAP_METADATA_TYPE_FA_COOKIE,
> +};
> +
> +enum devlink_reload_action {
> +       DEVLINK_RELOAD_ACTION_UNSPEC,
> +       DEVLINK_RELOAD_ACTION_DRIVER_REINIT,    /* Driver entities 
> re-instantiation */
> +       DEVLINK_RELOAD_ACTION_FW_ACTIVATE,      /* FW activate */
> +
> +       /* Add new reload actions above */
> +       __DEVLINK_RELOAD_ACTION_MAX,
> +       DEVLINK_RELOAD_ACTION_MAX = __DEVLINK_RELOAD_ACTION_MAX - 1
> +};
> +
> +enum devlink_reload_limit {
> +       DEVLINK_RELOAD_LIMIT_UNSPEC,    /* unspecified, no constraints */
> +       DEVLINK_RELOAD_LIMIT_NO_RESET,  /* No reset allowed, no down time 
> allowed,
> +                                        * no link flap and no configuration 
> is lost.
> +                                        */
> +
> +       /* Add new reload limit above */
> +       __DEVLINK_RELOAD_LIMIT_MAX,
> +       DEVLINK_RELOAD_LIMIT_MAX = __DEVLINK_RELOAD_LIMIT_MAX - 1
> +};
> +
> +#define DEVLINK_RELOAD_LIMITS_VALID_MASK (_BITUL(__DEVLINK_RELOAD_LIMIT_MAX) 
> - 1)
> +
> +enum devlink_attr {
> +       /* don't change the order or add anything between, this is ABI! */
> +       DEVLINK_ATTR_UNSPEC,
> +
> +       /* bus name + dev name together are a handle for devlink entity */
> +       DEVLINK_ATTR_BUS_NAME,                  /* string */
> +       DEVLINK_ATTR_DEV_NAME,                  /* string */
> +
> +       DEVLINK_ATTR_PORT_INDEX,                /* u32 */
> +       DEVLINK_ATTR_PORT_TYPE,                 /* u16 */
> +       DEVLINK_ATTR_PORT_DESIRED_TYPE,         /* u16 */
> +       DEVLINK_ATTR_PORT_NETDEV_IFINDEX,       /* u32 */
> +       DEVLINK_ATTR_PORT_NETDEV_NAME,          /* string */
> +       DEVLINK_ATTR_PORT_IBDEV_NAME,           /* string */
> +       DEVLINK_ATTR_PORT_SPLIT_COUNT,          /* u32 */
> +       DEVLINK_ATTR_PORT_SPLIT_GROUP,          /* u32 */
> +       DEVLINK_ATTR_SB_INDEX,                  /* u32 */
> +       DEVLINK_ATTR_SB_SIZE,                   /* u32 */
> +       DEVLINK_ATTR_SB_INGRESS_POOL_COUNT,     /* u16 */
> +       DEVLINK_ATTR_SB_EGRESS_POOL_COUNT,      /* u16 */
> +       DEVLINK_ATTR_SB_INGRESS_TC_COUNT,       /* u16 */
> +       DEVLINK_ATTR_SB_EGRESS_TC_COUNT,        /* u16 */
> +       DEVLINK_ATTR_SB_POOL_INDEX,             /* u16 */
> +       DEVLINK_ATTR_SB_POOL_TYPE,              /* u8 */
> +       DEVLINK_ATTR_SB_POOL_SIZE,              /* u32 */
> +       DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE,    /* u8 */
> +       DEVLINK_ATTR_SB_THRESHOLD,              /* u32 */
> +       DEVLINK_ATTR_SB_TC_INDEX,               /* u16 */
> +       DEVLINK_ATTR_SB_OCC_CUR,                /* u32 */
> +       DEVLINK_ATTR_SB_OCC_MAX,                /* u32 */
> +       DEVLINK_ATTR_ESWITCH_MODE,              /* u16 */
> +       DEVLINK_ATTR_ESWITCH_INLINE_MODE,       /* u8 */
> +
> +       DEVLINK_ATTR_DPIPE_TABLES,              /* nested */
> +       DEVLINK_ATTR_DPIPE_TABLE,               /* nested */
> +       DEVLINK_ATTR_DPIPE_TABLE_NAME,          /* string */
> +       DEVLINK_ATTR_DPIPE_TABLE_SIZE,          /* u64 */
> +       DEVLINK_ATTR_DPIPE_TABLE_MATCHES,       /* nested */
> +       DEVLINK_ATTR_DPIPE_TABLE_ACTIONS,       /* nested */
> +       DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED,      /* u8 */
> +
> +       DEVLINK_ATTR_DPIPE_ENTRIES,             /* nested */
> +       DEVLINK_ATTR_DPIPE_ENTRY,               /* nested */
> +       DEVLINK_ATTR_DPIPE_ENTRY_INDEX,         /* u64 */
> +       DEVLINK_ATTR_DPIPE_ENTRY_MATCH_VALUES,  /* nested */
> +       DEVLINK_ATTR_DPIPE_ENTRY_ACTION_VALUES, /* nested */
> +       DEVLINK_ATTR_DPIPE_ENTRY_COUNTER,       /* u64 */
> +
> +       DEVLINK_ATTR_DPIPE_MATCH,               /* nested */
> +       DEVLINK_ATTR_DPIPE_MATCH_VALUE,         /* nested */
> +       DEVLINK_ATTR_DPIPE_MATCH_TYPE,          /* u32 */
> +
> +       DEVLINK_ATTR_DPIPE_ACTION,              /* nested */
> +       DEVLINK_ATTR_DPIPE_ACTION_VALUE,        /* nested */
> +       DEVLINK_ATTR_DPIPE_ACTION_TYPE,         /* u32 */
> +
> +       DEVLINK_ATTR_DPIPE_VALUE,
> +       DEVLINK_ATTR_DPIPE_VALUE_MASK,
> +       DEVLINK_ATTR_DPIPE_VALUE_MAPPING,       /* u32 */
> +
> +       DEVLINK_ATTR_DPIPE_HEADERS,             /* nested */
> +       DEVLINK_ATTR_DPIPE_HEADER,              /* nested */
> +       DEVLINK_ATTR_DPIPE_HEADER_NAME,         /* string */
> +       DEVLINK_ATTR_DPIPE_HEADER_ID,           /* u32 */
> +       DEVLINK_ATTR_DPIPE_HEADER_FIELDS,       /* nested */
> +       DEVLINK_ATTR_DPIPE_HEADER_GLOBAL,       /* u8 */
> +       DEVLINK_ATTR_DPIPE_HEADER_INDEX,        /* u32 */
> +
> +       DEVLINK_ATTR_DPIPE_FIELD,               /* nested */
> +       DEVLINK_ATTR_DPIPE_FIELD_NAME,          /* string */
> +       DEVLINK_ATTR_DPIPE_FIELD_ID,            /* u32 */
> +       DEVLINK_ATTR_DPIPE_FIELD_BITWIDTH,      /* u32 */
> +       DEVLINK_ATTR_DPIPE_FIELD_MAPPING_TYPE,  /* u32 */
> +
> +       DEVLINK_ATTR_PAD,
> +
> +       DEVLINK_ATTR_ESWITCH_ENCAP_MODE,        /* u8 */
> +       DEVLINK_ATTR_RESOURCE_LIST,             /* nested */
> +       DEVLINK_ATTR_RESOURCE,                  /* nested */
> +       DEVLINK_ATTR_RESOURCE_NAME,             /* string */
> +       DEVLINK_ATTR_RESOURCE_ID,               /* u64 */
> +       DEVLINK_ATTR_RESOURCE_SIZE,             /* u64 */
> +       DEVLINK_ATTR_RESOURCE_SIZE_NEW,         /* u64 */
> +       DEVLINK_ATTR_RESOURCE_SIZE_VALID,       /* u8 */
> +       DEVLINK_ATTR_RESOURCE_SIZE_MIN,         /* u64 */
> +       DEVLINK_ATTR_RESOURCE_SIZE_MAX,         /* u64 */
> +       DEVLINK_ATTR_RESOURCE_SIZE_GRAN,        /* u64 */
> +       DEVLINK_ATTR_RESOURCE_UNIT,             /* u8 */
> +       DEVLINK_ATTR_RESOURCE_OCC,              /* u64 */
> +       DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_ID,   /* u64 */
> +       DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_UNITS,/* u64 */
> +
> +       DEVLINK_ATTR_PORT_FLAVOUR,              /* u16 */
> +       DEVLINK_ATTR_PORT_NUMBER,               /* u32 */
> +       DEVLINK_ATTR_PORT_SPLIT_SUBPORT_NUMBER, /* u32 */
> +
> +       DEVLINK_ATTR_PARAM,                     /* nested */
> +       DEVLINK_ATTR_PARAM_NAME,                /* string */
> +       DEVLINK_ATTR_PARAM_GENERIC,             /* flag */
> +       DEVLINK_ATTR_PARAM_TYPE,                /* u8 */
> +       DEVLINK_ATTR_PARAM_VALUES_LIST,         /* nested */
> +       DEVLINK_ATTR_PARAM_VALUE,               /* nested */
> +       DEVLINK_ATTR_PARAM_VALUE_DATA,          /* dynamic */
> +       DEVLINK_ATTR_PARAM_VALUE_CMODE,         /* u8 */
> +
> +       DEVLINK_ATTR_REGION_NAME,               /* string */
> +       DEVLINK_ATTR_REGION_SIZE,               /* u64 */
> +       DEVLINK_ATTR_REGION_SNAPSHOTS,          /* nested */
> +       DEVLINK_ATTR_REGION_SNAPSHOT,           /* nested */
> +       DEVLINK_ATTR_REGION_SNAPSHOT_ID,        /* u32 */
> +
> +       DEVLINK_ATTR_REGION_CHUNKS,             /* nested */
> +       DEVLINK_ATTR_REGION_CHUNK,              /* nested */
> +       DEVLINK_ATTR_REGION_CHUNK_DATA,         /* binary */
> +       DEVLINK_ATTR_REGION_CHUNK_ADDR,         /* u64 */
> +       DEVLINK_ATTR_REGION_CHUNK_LEN,          /* u64 */
> +
> +       DEVLINK_ATTR_INFO_DRIVER_NAME,          /* string */
> +       DEVLINK_ATTR_INFO_SERIAL_NUMBER,        /* string */
> +       DEVLINK_ATTR_INFO_VERSION_FIXED,        /* nested */
> +       DEVLINK_ATTR_INFO_VERSION_RUNNING,      /* nested */
> +       DEVLINK_ATTR_INFO_VERSION_STORED,       /* nested */
> +       DEVLINK_ATTR_INFO_VERSION_NAME,         /* string */
> +       DEVLINK_ATTR_INFO_VERSION_VALUE,        /* string */
> +
> +       DEVLINK_ATTR_SB_POOL_CELL_SIZE,         /* u32 */
> +
> +       DEVLINK_ATTR_FMSG,                      /* nested */
> +       DEVLINK_ATTR_FMSG_OBJ_NEST_START,       /* flag */
> +       DEVLINK_ATTR_FMSG_PAIR_NEST_START,      /* flag */
> +       DEVLINK_ATTR_FMSG_ARR_NEST_START,       /* flag */
> +       DEVLINK_ATTR_FMSG_NEST_END,             /* flag */
> +       DEVLINK_ATTR_FMSG_OBJ_NAME,             /* string */
> +       DEVLINK_ATTR_FMSG_OBJ_VALUE_TYPE,       /* u8 */
> +       DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA,       /* dynamic */
> +
> +       DEVLINK_ATTR_HEALTH_REPORTER,                   /* nested */
> +       DEVLINK_ATTR_HEALTH_REPORTER_NAME,              /* string */
> +       DEVLINK_ATTR_HEALTH_REPORTER_STATE,             /* u8 */
> +       DEVLINK_ATTR_HEALTH_REPORTER_ERR_COUNT,         /* u64 */
> +       DEVLINK_ATTR_HEALTH_REPORTER_RECOVER_COUNT,     /* u64 */
> +       DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS,           /* u64 */
> +       DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD,   /* u64 */
> +       DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER,      /* u8 */
> +
> +       DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME,    /* string */
> +       DEVLINK_ATTR_FLASH_UPDATE_COMPONENT,    /* string */
> +       DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG,   /* string */
> +       DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE,  /* u64 */
> +       DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL, /* u64 */
> +
> +       DEVLINK_ATTR_PORT_PCI_PF_NUMBER,        /* u16 */
> +       DEVLINK_ATTR_PORT_PCI_VF_NUMBER,        /* u16 */
> +
> +       DEVLINK_ATTR_STATS,                             /* nested */
> +
> +       DEVLINK_ATTR_TRAP_NAME,                         /* string */
> +       /* enum devlink_trap_action */
> +       DEVLINK_ATTR_TRAP_ACTION,                       /* u8 */
> +       /* enum devlink_trap_type */
> +       DEVLINK_ATTR_TRAP_TYPE,                         /* u8 */
> +       DEVLINK_ATTR_TRAP_GENERIC,                      /* flag */
> +       DEVLINK_ATTR_TRAP_METADATA,                     /* nested */
> +       DEVLINK_ATTR_TRAP_GROUP_NAME,                   /* string */
> +
> +       DEVLINK_ATTR_RELOAD_FAILED,                     /* u8 0 or 1 */
> +
> +       DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS_NS,        /* u64 */
> +
> +       DEVLINK_ATTR_NETNS_FD,                  /* u32 */
> +       DEVLINK_ATTR_NETNS_PID,                 /* u32 */
> +       DEVLINK_ATTR_NETNS_ID,                  /* u32 */
> +
> +       DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP, /* u8 */
> +
> +       DEVLINK_ATTR_TRAP_POLICER_ID,                   /* u32 */
> +       DEVLINK_ATTR_TRAP_POLICER_RATE,                 /* u64 */
> +       DEVLINK_ATTR_TRAP_POLICER_BURST,                /* u64 */
> +
> +       DEVLINK_ATTR_PORT_FUNCTION,                     /* nested */
> +
> +       DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER,  /* string */
> +
> +       DEVLINK_ATTR_PORT_LANES,                        /* u32 */
> +       DEVLINK_ATTR_PORT_SPLITTABLE,                   /* u8 */
> +
> +       DEVLINK_ATTR_PORT_EXTERNAL,             /* u8 */
> +       DEVLINK_ATTR_PORT_CONTROLLER_NUMBER,    /* u32 */
> +
> +       DEVLINK_ATTR_FLASH_UPDATE_STATUS_TIMEOUT,       /* u64 */
> +       DEVLINK_ATTR_FLASH_UPDATE_OVERWRITE_MASK,       /* bitfield32 */
> +
> +       DEVLINK_ATTR_RELOAD_ACTION,             /* u8 */
> +       DEVLINK_ATTR_RELOAD_ACTIONS_PERFORMED,  /* bitfield32 */
> +       DEVLINK_ATTR_RELOAD_LIMITS,             /* bitfield32 */
> +
> +       DEVLINK_ATTR_DEV_STATS,                 /* nested */
> +       DEVLINK_ATTR_RELOAD_STATS,              /* nested */
> +       DEVLINK_ATTR_RELOAD_STATS_ENTRY,        /* nested */
> +       DEVLINK_ATTR_RELOAD_STATS_LIMIT,        /* u8 */
> +       DEVLINK_ATTR_RELOAD_STATS_VALUE,        /* u32 */
> +       DEVLINK_ATTR_REMOTE_RELOAD_STATS,       /* nested */
> +       DEVLINK_ATTR_RELOAD_ACTION_INFO,        /* nested */
> +       DEVLINK_ATTR_RELOAD_ACTION_STATS,       /* nested */
> +
> +       DEVLINK_ATTR_PORT_PCI_SF_NUMBER,        /* u32 */
> +       /* add new attributes above here, update the policy in devlink.c */
> +
> +       __DEVLINK_ATTR_MAX,
> +       DEVLINK_ATTR_MAX = __DEVLINK_ATTR_MAX - 1
> +};
> +
> +/* Mapping between internal resource described by the field and system
> + * structure
> + */
> +enum devlink_dpipe_field_mapping_type {
> +       DEVLINK_DPIPE_FIELD_MAPPING_TYPE_NONE,
> +       DEVLINK_DPIPE_FIELD_MAPPING_TYPE_IFINDEX,
> +};
> +
> +/* Match type - specify the type of the match */
> +enum devlink_dpipe_match_type {
> +       DEVLINK_DPIPE_MATCH_TYPE_FIELD_EXACT,
> +};
> +
> +/* Action type - specify the action type */
> +enum devlink_dpipe_action_type {
> +       DEVLINK_DPIPE_ACTION_TYPE_FIELD_MODIFY,
> +};
> +
> +enum devlink_dpipe_field_ethernet_id {
> +       DEVLINK_DPIPE_FIELD_ETHERNET_DST_MAC,
> +};
> +
> +enum devlink_dpipe_field_ipv4_id {
> +       DEVLINK_DPIPE_FIELD_IPV4_DST_IP,
> +};
> +
> +enum devlink_dpipe_field_ipv6_id {
> +       DEVLINK_DPIPE_FIELD_IPV6_DST_IP,
> +};
> +
> +enum devlink_dpipe_header_id {
> +       DEVLINK_DPIPE_HEADER_ETHERNET,
> +       DEVLINK_DPIPE_HEADER_IPV4,
> +       DEVLINK_DPIPE_HEADER_IPV6,
> +};
> +
> +enum devlink_resource_unit {
> +       DEVLINK_RESOURCE_UNIT_ENTRY,
> +};
> +
> +enum devlink_port_function_attr {
> +       DEVLINK_PORT_FUNCTION_ATTR_UNSPEC,
> +       DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR,     /* binary */
> +       DEVLINK_PORT_FN_ATTR_STATE,     /* u8 */
> +       DEVLINK_PORT_FN_ATTR_OPSTATE,   /* u8 */
> +
> +       __DEVLINK_PORT_FUNCTION_ATTR_MAX,
> +       DEVLINK_PORT_FUNCTION_ATTR_MAX = __DEVLINK_PORT_FUNCTION_ATTR_MAX - 1
> +};
> +
> +enum devlink_port_fn_state {
> +       DEVLINK_PORT_FN_STATE_INACTIVE,
> +       DEVLINK_PORT_FN_STATE_ACTIVE,
> +};
> +
> +/**
> + * enum devlink_port_fn_opstate - indicates operational state of the function
> + * @DEVLINK_PORT_FN_OPSTATE_ATTACHED: Driver is attached to the function.
> + * For graceful tear down of the function, after inactivation of the
> + * function, user should wait for operational state to turn DETACHED.
> + * @DEVLINK_PORT_FN_OPSTATE_DETACHED: Driver is detached from the function.
> + * It is safe to delete the port.
> + */
> +enum devlink_port_fn_opstate {
> +       DEVLINK_PORT_FN_OPSTATE_DETACHED,
> +       DEVLINK_PORT_FN_OPSTATE_ATTACHED,
> +};
> +
> +#endif /* _UAPI_LINUX_DEVLINK_H_ */
> +#endif /* __UAPI_LINUX_DEVLINK_WRAPPER_H */
> +#endif /* !__KERNEL__ */
> diff --git a/lib/automake.mk b/lib/automake.mk
> index 086fbd62d..1057504d1 100644
> --- a/lib/automake.mk
> +++ b/lib/automake.mk
> @@ -43,6 +43,17 @@ lib_libovn_la_SOURCES = \
>         lib/plug.c \
>         lib/plug-dummy.h \
>         lib/plug-dummy.c
> +
> +# in-tree plug providers
> +if ENABLE_PLUG
> +if LINUX
> +lib_libovn_la_SOURCES += \
> +       lib/plug_providers/representor/netlink-devlink.h \
> +       lib/plug_providers/representor/netlink-devlink.c \
> +       lib/plug_providers/representor/plug-representor.c
> +endif
> +endif
> +
>  nodist_lib_libovn_la_SOURCES = \
>         lib/ovn-dirs.c \
>         lib/ovn-nb-idl.c \
> diff --git a/lib/plug-provider.h b/lib/plug-provider.h
> index 487534ee5..6587be8dc 100644
> --- a/lib/plug-provider.h
> +++ b/lib/plug-provider.h
> @@ -87,9 +87,13 @@ struct plug_class {
>  };
>
>  extern const struct plug_class plug_dummy_class;
> +#ifdef ENABLE_PLUG
> +/* in-tree plug classes */
> +extern const struct plug_class plug_representor;
> +#endif /* ENABLE_PLUG */
>  #ifdef HAVE_PLUG_PROVIDER
>  extern const struct plug_class *plug_provider_classes[];
> -#endif
> +#endif /* HAVE_PLUG_PROVIDER */
>
>  #ifdef  __cplusplus
>  }
> diff --git a/lib/plug.c b/lib/plug.c
> index c0c34214e..dab06713a 100644
> --- a/lib/plug.c
> +++ b/lib/plug.c
> @@ -32,6 +32,7 @@ VLOG_DEFINE_THIS_MODULE(plug);
>
>  #ifdef ENABLE_PLUG
>  static const struct plug_class *base_plug_classes[] = {
> +    &plug_representor,
>  };
>  #endif
>
> diff --git a/lib/plug_providers/representor/netlink-devlink.c 
> b/lib/plug_providers/representor/netlink-devlink.c
> new file mode 100644
> index 000000000..82e9c71f3
> --- /dev/null
> +++ b/lib/plug_providers/representor/netlink-devlink.c
> @@ -0,0 +1,499 @@
> +/*
> + * Copyright (c) 2021 Canonical
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +#include <config.h>
> +#include <errno.h>
> +#include <inttypes.h>
> +#include <linux/devlink.h>
> +#include <linux/genetlink.h>
> +#include "netlink.h"
> +#include "netlink-socket.h"
> +#include "netlink-devlink.h"
> +#include "openvswitch/vlog.h"
> +#include "packets.h"
> +
> +VLOG_DEFINE_THIS_MODULE(netlink_devlink);
> +
> +/* Initialized by nl_devlink_init() */
> +static int ovs_devlink_family;
> +
> +struct nl_dl_dump_state {
> +    struct nl_dump dump;
> +    struct ofpbuf buf;
> +    int error;
> +};
> +
> +static int nl_devlink_init(void);
> +
> +const char *dl_str_not_present = "";
> +
> +/* Allocates memory for and returns a pointer to devlink dump state object.
> + *
> + * One-time initialization and lookup of the devlink generic netlink family 
> is
> + * also performed, and the caller should check for error condition with a 
> call
> + * to nl_dl_dump_init_error before attempting to dump devlink data.
> + *
> + * The caller owns the allocated object and is responsible for freeing the
> + * allocated memory with a call to nl_dl_dump_destroy when done. */
> +struct nl_dl_dump_state *
> +nl_dl_dump_init(void)
> +{
> +    struct nl_dl_dump_state *dump_state;
> +
> +    dump_state = xmalloc(sizeof(*dump_state));
> +    dump_state->error = nl_devlink_init();
> +    return dump_state;
> +}
> +
> +/* Get error indicator from the devlink initialization process. */
> +int
> +nl_dl_dump_init_error(struct nl_dl_dump_state *dump_state)
> +{
> +    return dump_state->error;
> +}
> +
> +/* Free memory previously allocated by call to nl_dl_dump_init.
> + *
> + * Note that the caller is responsible for making a call to nl_dl_dump_finish
> + * to free up resources associated with any in-flight dump process prior to
> + * destroying the dump state object. */
> +void
> +nl_dl_dump_destroy(struct nl_dl_dump_state *dump_state)
> +{
> +    free(dump_state);
> +}
> +
> +void
> +nl_msg_put_dlgenmsg(struct ofpbuf *msg, size_t expected_payload,
> +                    int family, uint8_t cmd, uint32_t flags)
> +{
> +    nl_msg_put_genlmsghdr(msg, expected_payload, family,
> +                          flags, cmd, DEVLINK_GENL_VERSION);
> +}
> +
> +/* Starts a Netlink-devlink "dump" operation, by sending devlink request with
> + * command 'cmd' to the kernel on a Netlink socket, and initializes 'state'
> + * with buffer and dump state. */
> +void
> +nl_dl_dump_start(uint8_t cmd, struct nl_dl_dump_state *state)
> +{
> +    struct ofpbuf *request;
> +
> +    request = ofpbuf_new(NLMSG_HDRLEN + GENL_HDRLEN);
> +    nl_msg_put_dlgenmsg(request, 0, ovs_devlink_family, cmd,
> +                        NLM_F_REQUEST);
> +    nl_dump_start(&state->dump, NETLINK_GENERIC, request);
> +    ofpbuf_delete(request);
> +
> +    ofpbuf_init(&state->buf, NL_DUMP_BUFSIZE);
> +}
> +
> +static bool
> +nl_dl_dump_next__(struct nl_dl_dump_state *state,
> +                  bool (*parse_function)(struct ofpbuf *, void *),
> +                  void *entry)
> +{
> +    struct ofpbuf msg;
> +
> +    if (!nl_dump_next(&state->dump, &msg, &state->buf)) {
> +        return false;
> +    }
> +    if (!parse_function(&msg, entry)) {
> +        ovs_mutex_lock(&state->dump.mutex);
> +        state->dump.status = EPROTO;
> +        ovs_mutex_unlock(&state->dump.mutex);
> +        return false;
> +    }
> +    return true;
> +}
> +
> +/* Attempts to retrieve and parse another reply in on-going dump operation.
> + *
> + * If successful, returns true and assignes values or pointers to data in
> + * 'port_entry'.  The caller must not modify 'port_entry' (because it may
> + * contain pointers to data within the buffer which will be used by future
> + * calls to this function.
> + *
> + * On failure, returns false.  Failure might indicate an actual error or 
> merely
> + * the end of replies.  An error status for the entire dump operation is
> + * provided when it is completed by calling nl_dl_dump_finish()
> + */
> +bool
> +nl_dl_port_dump_next(struct nl_dl_dump_state *state,
> +                     struct dl_port *port_entry)
> +{
> +    return nl_dl_dump_next__(
> +        state,
> +        (bool ( * )(struct ofpbuf *, void *)) &nl_dl_parse_port_policy,
> +        (void *) port_entry);
> +}
> +
> +bool
> +nl_dl_info_dump_next(struct nl_dl_dump_state *state,
> +                     struct dl_info *info_entry)
> +{
> +    return nl_dl_dump_next__(
> +        state,
> +        (bool ( * )(struct ofpbuf *, void *)) &nl_dl_parse_info_policy,
> +        (void *) info_entry);
> +}
> +
> +int
> +nl_dl_dump_finish(struct nl_dl_dump_state *state)
> +{
> +    ofpbuf_uninit(&state->buf);
> +    return nl_dump_done(&state->dump);
> +}
> +
> +static uint64_t
> +attr_get_up_to_u64(size_t attr_idx, struct nlattr *attrs[],
> +                   const struct nl_policy policy[],
> +                   size_t policy_len)
> +{
> +    if (attr_idx < policy_len && attrs[attr_idx]) {
> +        switch (policy[attr_idx].type) {
> +        case NL_A_U8:
> +            return nl_attr_get_u8(attrs[attr_idx]);
> +            break;
> +        case NL_A_U16:
> +            return nl_attr_get_u16(attrs[attr_idx]);
> +            break;
> +        case NL_A_U32:
> +            return nl_attr_get_u32(attrs[attr_idx]);
> +            break;
> +        case NL_A_U64:
> +            return nl_attr_get_u64(attrs[attr_idx]);
> +            break;
> +        case NL_A_U128:
> +        case NL_A_STRING:
> +        case NL_A_NO_ATTR:
> +        case NL_A_UNSPEC:
> +        case NL_A_FLAG:
> +        case NL_A_IPV6:
> +        case NL_A_NESTED:
> +        case NL_A_LL_ADDR:
> +        case N_NL_ATTR_TYPES: default: OVS_NOT_REACHED();
> +        };
> +    }
> +    return -1;
> +}
> +
> +static const char *
> +attr_get_str(size_t attr_idx, struct nlattr *attrs[],
> +             const struct nl_policy policy[],
> +             size_t policy_len)
> +{
> +    if (attr_idx < policy_len && attrs[attr_idx]) {
> +        ovs_assert(policy[attr_idx].type == NL_A_STRING);
> +        return nl_attr_get_string(attrs[attr_idx]);
> +    }
> +    return dl_str_not_present;
> +}
> +
> +bool
> +nl_dl_parse_port_function(struct nlattr *nla, struct dl_port_function 
> *port_fn)
> +{
> +    static const struct nl_policy policy[] = {
> +        /* Appeared in Linux v5.9 */
> +        [DEVLINK_PORT_FUNCTION_ATTR_UNSPEC] = { .type = NL_A_UNSPEC,
> +                                                .optional = true, },
> +        [DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR] = { .type = NL_A_LL_ADDR,
> +                                                 .optional = true, },
> +
> +        /* Appeared in Linnux v5.12 */
> +        [DEVLINK_PORT_FN_ATTR_STATE] = { .type = NL_A_U8, .optional = true, 
> },
> +        [DEVLINK_PORT_FN_ATTR_OPSTATE] = { .type = NL_A_U8,
> +                                           .optional = true, },
> +    };
> +    struct nlattr *attrs[ARRAY_SIZE(policy)];
> +    bool parsed;
> +
> +    parsed = nl_parse_nested(nla, policy, attrs, ARRAY_SIZE(policy));
> +
> +    if (parsed) {
> +        if (attrs[DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR]) {
> +            size_t hw_addr_size = nl_attr_get_size(
> +                            attrs[DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR]);
> +            if (hw_addr_size == sizeof(struct eth_addr)) {
> +                port_fn->eth_addr = nl_attr_get_eth_addr(
> +                                attrs[DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR]);
> +            } else if (hw_addr_size == sizeof(struct ib_addr)) {
> +                port_fn->ib_addr = nl_attr_get_ib_addr(
> +                                attrs[DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR]);
> +            } else {
> +                return false;
> +            }
> +        } else {
> +            memset(&port_fn->eth_addr, 0, sizeof(port_fn->eth_addr));
> +            memset(&port_fn->ib_addr, 0, sizeof(port_fn->ib_addr));
> +        }
> +        port_fn->state = attr_get_up_to_u64(
> +                        DEVLINK_PORT_FN_ATTR_STATE,
> +                        attrs, policy, ARRAY_SIZE(policy));
> +        port_fn->opstate = attr_get_up_to_u64(
> +                        DEVLINK_PORT_FN_ATTR_OPSTATE,
> +                        attrs, policy, ARRAY_SIZE(policy));
> +    }
> +
> +    return parsed;
> +}
> +
> +bool
> +nl_dl_parse_port_policy(struct ofpbuf *msg, struct dl_port *port)
> +{
> +    static const struct nl_policy policy[] = {
> +        /* Appeared in Linux v4.6 */
> +        [DEVLINK_ATTR_BUS_NAME] = { .type = NL_A_STRING, .optional = false, 
> },
> +        [DEVLINK_ATTR_DEV_NAME] = { .type = NL_A_STRING, .optional = false, 
> },
> +        [DEVLINK_ATTR_PORT_INDEX] = { .type = NL_A_U32, .optional = false, },
> +
> +        [DEVLINK_ATTR_PORT_TYPE] = { .type = NL_A_U16, .optional = true, },
> +        [DEVLINK_ATTR_PORT_DESIRED_TYPE] = { .type = NL_A_U16,
> +                                            .optional = true, },
> +        [DEVLINK_ATTR_PORT_NETDEV_IFINDEX] = { .type = NL_A_U32,
> +                                               .optional = true, },
> +        [DEVLINK_ATTR_PORT_NETDEV_NAME] = { .type = NL_A_STRING,
> +                                            .optional = true, },
> +        [DEVLINK_ATTR_PORT_IBDEV_NAME] = { .type = NL_A_STRING,
> +                                           .optional = true, },
> +        [DEVLINK_ATTR_PORT_SPLIT_COUNT] = { .type = NL_A_U32,
> +                                            .optional = true, },
> +        [DEVLINK_ATTR_PORT_SPLIT_GROUP] = { .type = NL_A_U32,
> +                                            .optional = true, },
> +
> +        /* Appeared in Linux v4.18 */
> +        [DEVLINK_ATTR_PORT_FLAVOUR] = { .type = NL_A_U16, .optional = true, 
> },
> +        [DEVLINK_ATTR_PORT_NUMBER] = { .type = NL_A_U32, .optional = true, },
> +        [DEVLINK_ATTR_PORT_SPLIT_SUBPORT_NUMBER] = { .type = NL_A_U32,
> +                                                     .optional = true, },
> +
> +        /* Appeared in Linux v5.3 */
> +        [DEVLINK_ATTR_PORT_PCI_PF_NUMBER] = { .type = NL_A_U16,
> +                                              .optional = true, },
> +        [DEVLINK_ATTR_PORT_PCI_VF_NUMBER] = { .type = NL_A_U16,
> +                                              .optional = true, },
> +
> +        /* Appeared in Linux v5.9 */
> +        [DEVLINK_ATTR_PORT_FUNCTION] = { .type = NL_A_NESTED,
> +                                         .optional = true, },
> +        [DEVLINK_ATTR_PORT_LANES] = { .type = NL_A_U32, .optional = true, },
> +        [DEVLINK_ATTR_PORT_SPLITTABLE] = { .type = NL_A_U8,
> +                                           .optional = true, },
> +
> +        /* Appeared in Linux v5.10 */
> +        [DEVLINK_ATTR_PORT_EXTERNAL] = { .type = NL_A_U8, .optional = true },
> +        [DEVLINK_ATTR_PORT_CONTROLLER_NUMBER] = { .type = NL_A_U32,
> +                                                  .optional = true},
> +
> +        /* Appeared in Linux v5.12 */
> +        [DEVLINK_ATTR_PORT_PCI_SF_NUMBER] = { .type = NL_A_U32,
> +                                              .optional = true },
> +    };
> +    struct nlattr *attrs[ARRAY_SIZE(policy)];
> +
> +    if (!nl_policy_parse(msg, NLMSG_HDRLEN + GENL_HDRLEN,
> +                         policy, attrs,
> +                         ARRAY_SIZE(policy)))
> +    {
> +        return false;
> +    }
> +    port->bus_name = nl_attr_get_string(attrs[DEVLINK_ATTR_BUS_NAME]);
> +    port->dev_name = nl_attr_get_string(attrs[DEVLINK_ATTR_DEV_NAME]);
> +    port->index = nl_attr_get_u32(attrs[DEVLINK_ATTR_PORT_INDEX]);
> +
> +    port->type = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_TYPE,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->desired_type = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_DESIRED_TYPE,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->netdev_ifindex = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_NETDEV_IFINDEX,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    if (port->type == DEVLINK_PORT_TYPE_ETH &&
> +            attrs[DEVLINK_ATTR_PORT_NETDEV_NAME]) {
> +        port->netdev_name = nl_attr_get_string(
> +            attrs[DEVLINK_ATTR_PORT_NETDEV_NAME]);
> +    } else if (port->type == DEVLINK_PORT_TYPE_IB &&
> +            attrs[DEVLINK_ATTR_PORT_IBDEV_NAME]) {
> +        port->ibdev_name = nl_attr_get_string(
> +            attrs[DEVLINK_ATTR_PORT_IBDEV_NAME]);
> +    } else {
> +        port->netdev_name = dl_str_not_present;
> +    }
> +    port->split_count = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_SPLIT_COUNT,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->split_group = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_SPLIT_GROUP,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->flavour = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_FLAVOUR,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->number = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_NUMBER,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->split_subport_number = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_SPLIT_SUBPORT_NUMBER,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->pci_pf_number = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_PCI_PF_NUMBER,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->pci_vf_number = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_PCI_VF_NUMBER,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->lanes = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_LANES,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->splittable = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_SPLITTABLE,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->external = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_EXTERNAL,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->controller_number = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_CONTROLLER_NUMBER,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    port->pci_sf_number = attr_get_up_to_u64(
> +                    DEVLINK_ATTR_PORT_PCI_SF_NUMBER,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +
> +    if (attrs[DEVLINK_ATTR_PORT_FUNCTION]) {
> +        if (!nl_dl_parse_port_function(attrs[DEVLINK_ATTR_PORT_FUNCTION],
> +                                       &port->function))
> +        {
> +            return false;
> +        }
> +    } else {
> +        memset(&port->function, 0, sizeof(port->function));
> +        port->function.state = UINT8_MAX;
> +        port->function.opstate = UINT8_MAX;
> +    }
> +
> +    return true;
> +}
> +
> +bool
> +nl_dl_parse_info_version(struct nlattr *nla, struct dl_info_version 
> *info_ver)
> +{
> +    static const struct nl_policy policy[] = {
> +        /* Appeared in Linux v5.1 */
> +        [DEVLINK_ATTR_INFO_VERSION_NAME] = { .type = NL_A_STRING,
> +                                             .optional = true, },
> +        [DEVLINK_ATTR_INFO_VERSION_VALUE] = { .type = NL_A_STRING,
> +                                              .optional = true, },
> +    };
> +    struct nlattr *attrs[ARRAY_SIZE(policy)];
> +    bool parsed;
> +
> +    parsed = nl_parse_nested(nla, policy, attrs, ARRAY_SIZE(policy));
> +
> +    if (parsed) {
> +        info_ver->name = attr_get_str(
> +                        DEVLINK_ATTR_INFO_VERSION_NAME,
> +                        attrs, policy, ARRAY_SIZE(policy));
> +        info_ver->value = attr_get_str(
> +                        DEVLINK_ATTR_INFO_VERSION_NAME,
> +                        attrs, policy, ARRAY_SIZE(policy));
> +    }
> +
> +    return parsed;
> +}
> +
> +static bool
> +attr_fill_version(size_t attr_idx, struct nlattr *attrs[],
> +                  size_t attrs_len,
> +                  struct dl_info_version *version)
> +{
> +    if (attr_idx < attrs_len && attrs[attr_idx]) {
> +        if (!nl_dl_parse_info_version(attrs[attr_idx],
> +                                      version))
> +        {
> +            return false;
> +        }
> +    } else {
> +        version->name = dl_str_not_present;
> +        version->value = dl_str_not_present;
> +    }
> +    return true;
> +}
> +
> +bool
> +nl_dl_parse_info_policy(struct ofpbuf *msg, struct dl_info *info)
> +{
> +    static const struct nl_policy policy[] = {
> +        /* Appeared in Linux v5.1 */
> +        [DEVLINK_ATTR_INFO_DRIVER_NAME] = { .type = NL_A_STRING,
> +                                            .optional = false, },
> +        [DEVLINK_ATTR_INFO_SERIAL_NUMBER] = { .type = NL_A_STRING,
> +                                              .optional = true, },
> +        [DEVLINK_ATTR_INFO_VERSION_FIXED] = { .type = NL_A_NESTED,
> +                                              .optional = true, },
> +        [DEVLINK_ATTR_INFO_VERSION_RUNNING] = { .type = NL_A_NESTED,
> +                                                .optional = true, },
> +        [DEVLINK_ATTR_INFO_VERSION_STORED] = { .type = NL_A_NESTED,
> +                                               .optional = true, },
> +
> +        /* Appeared in Linux v5.9 */
> +        [DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER] = { .type = NL_A_STRING,
> +                                                    .optional = true, },
> +    };
> +    struct nlattr *attrs[ARRAY_SIZE(policy)];
> +
> +    if (!nl_policy_parse(msg, NLMSG_HDRLEN + GENL_HDRLEN,
> +                         policy, attrs,
> +                         ARRAY_SIZE(policy)))
> +    {
> +        return false;
> +    }
> +    info->driver_name = attr_get_str(
> +                    DEVLINK_ATTR_INFO_DRIVER_NAME,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    info->serial_number = attr_get_str(
> +                    DEVLINK_ATTR_INFO_SERIAL_NUMBER,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    info->board_serial_number = attr_get_str(
> +                    DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER,
> +                    attrs, policy, ARRAY_SIZE(policy));
> +    if (!attr_fill_version(DEVLINK_ATTR_INFO_VERSION_FIXED, attrs,
> +                           ARRAY_SIZE(policy), &info->version_fixed)
> +        || !attr_fill_version(DEVLINK_ATTR_INFO_VERSION_RUNNING, attrs,
> +                              ARRAY_SIZE(policy), &info->version_running)
> +        || !attr_fill_version(DEVLINK_ATTR_INFO_VERSION_STORED, attrs,
> +                              ARRAY_SIZE(policy), &info->version_stored))
> +    {
> +        return false;
> +    }
> +
> +    return true;
> +}
> +
> +static int
> +nl_devlink_init(void)
> +{
> +    static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
> +    static int error;
> +
> +    if (ovsthread_once_start(&once)) {
> +        error = nl_lookup_genl_family(DEVLINK_GENL_NAME, 
> &ovs_devlink_family);
> +        if (error) {
> +            VLOG_INFO("Generic Netlink family '%s' does not exist. "
> +                      "Linux version 4.6 or newer required.",
> +                      DEVLINK_GENL_NAME);
> +        }
> +        ovsthread_once_done(&once);
> +    }
> +    return error;
> +}
> diff --git a/lib/plug_providers/representor/netlink-devlink.h 
> b/lib/plug_providers/representor/netlink-devlink.h
> new file mode 100644
> index 000000000..a7b108435
> --- /dev/null
> +++ b/lib/plug_providers/representor/netlink-devlink.h
> @@ -0,0 +1,115 @@
> +/*
> + * Copyright (c) 2021 Canonical
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef NETLINK_DEVLINK_H
> +#define NETLINK_DEVLINK_H 1
> +
> +/* Presence of each individual value in these structs is determined at 
> runtime
> + * and depends on which kernel version we are communicating with as well as
> + * which driver implementation is filling in the information for each
> + * individual device or port.
> + *
> + * To signal non-presence of values the library follows the following
> + * convention:
> + *
> + * - integer type values will be set to their maximum value
> + *   (e.g. UNIT8_MAX for a unit8_t)
> + *
> + * - hardware address type values will be set to all zero
> + *
> + * - string type values will be set to a pointer to dl_str_not_present
> + *   (an empty string).
> + */
> +
> +extern const char *dl_str_not_present;
> +
> +struct dl_port_function {
> +    struct eth_addr eth_addr;
> +    struct ib_addr ib_addr;
> +    uint8_t state;
> +    uint8_t opstate;
> +};
> +
> +struct dl_port {
> +    const char *bus_name;
> +    const char *dev_name;
> +    uint32_t index;
> +    uint16_t type;
> +    uint16_t desired_type;
> +    uint32_t netdev_ifindex;
> +    union {
> +        const char *netdev_name; /* type DEVLINK_PORT_TYPE_ETH */
> +        const char *ibdev_name;  /* type DEVLINK_PORT_TYPE_IB */
> +    };
> +    uint32_t split_count;
> +    uint32_t split_group;
> +    uint16_t flavour;
> +    uint32_t number;
> +    uint32_t split_subport_number;
> +    uint16_t pci_pf_number;
> +    uint16_t pci_vf_number;
> +    struct dl_port_function function;
> +    uint32_t lanes;
> +    uint8_t splittable;
> +    uint8_t external;
> +    uint32_t controller_number;
> +    uint32_t pci_sf_number;
> +};
> +
> +struct dl_info_version {
> +    const char *name;
> +    const char *value;
> +};
> +
> +struct dl_info {
> +    const char *driver_name;
> +    const char *serial_number;
> +    const char *board_serial_number;
> +    struct dl_info_version version_fixed;
> +    struct dl_info_version version_running;
> +    struct dl_info_version version_stored;
> +};
> +
> +struct eth_addr nl_attr_get_eth_addr(const struct nlattr *nla);
> +struct ib_addr nl_attr_get_ib_addr(const struct nlattr *nla);
> +
> +/* The nl_dl_dump_state record declaration refers to types declared in
> + * netlink-socket.h, which requires OVS internal autoconf macros and
> + * definitions to be present for successful compilation.
> + *
> + * To enable friction free consumtion of these interfaces from programs
> + * external to Open vSwitch, such as OVN, we keep the declaration of
> + * nl_dl_dump_state private.
> + *
> + * Use the nl_dl_dump_init function to allocate memory for and get a pointer 
> to
> + * a devlink dump state object. The caller owns the allocated object and is
> + * responsible for freeing the allocated memory when done. */
> +struct nl_dl_dump_state;
> +
> +struct nl_dl_dump_state * nl_dl_dump_init(void);
> +int nl_dl_dump_init_error(struct nl_dl_dump_state *);
> +void nl_dl_dump_destroy(struct nl_dl_dump_state *);
> +void nl_msg_put_dlgenmsg(struct ofpbuf *, size_t, int, uint8_t, uint32_t);
> +void nl_dl_dump_start(uint8_t, struct nl_dl_dump_state *);
> +bool nl_dl_port_dump_next(struct nl_dl_dump_state *, struct dl_port *);
> +bool nl_dl_info_dump_next(struct nl_dl_dump_state *, struct dl_info *);
> +int nl_dl_dump_finish(struct nl_dl_dump_state *);
> +bool nl_dl_parse_port_policy(struct ofpbuf *, struct dl_port *);
> +bool nl_dl_parse_port_function(struct nlattr *, struct dl_port_function *);
> +bool nl_dl_parse_info_policy(struct ofpbuf *, struct dl_info *);
> +bool nl_dl_parse_info_version(struct nlattr *, struct dl_info_version *);
> +
> +#endif /* NETLINK_DEVLINK_H */
> diff --git a/lib/plug_providers/representor/plug-representor.c 
> b/lib/plug_providers/representor/plug-representor.c
> new file mode 100644
> index 000000000..4c4187114
> --- /dev/null
> +++ b/lib/plug_providers/representor/plug-representor.c
> @@ -0,0 +1,307 @@
> +/* Copyright (c) 2021 Canonical
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +#include <errno.h>
> +#include <inttypes.h>
> +#include <linux/devlink.h>
> +#include <net/if.h>
> +
> +#include "plug-provider.h"
> +#include "plug.h"
> +
> +#include "hash.h"
> +#include "lib/vswitch-idl.h"
> +#include "openvswitch/hmap.h"
> +#include "openvswitch/vlog.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "netlink-devlink.h"
> +#include "openvswitch/shash.h"
> +#include "packets.h"
> +
> +VLOG_DEFINE_THIS_MODULE(plug_representor);
> +
> +/* Contains netdev name of ports known to devlink indexed by PF MAC
> + * address and logical function number (if applicable).
> + *
> + * Examples:
> + *     SR-IOV Physical Function: key "00:53:00:00:00:42"    value "pf0hpf"
> + *     SR-IOV Virtual Function:  key "00:53:00:00:00:42-42" value "pf0vf42"
> + */
> +static struct shash devlink_ports;
> +
> +/* Max number of physical ports connected to a single NIC SoC. */
> +#define MAX_NIC_PHY_PORTS 64
> +/* string repr of eth MAC, '-', logical function number (uint32_t) */
> +#define MAX_KEY_LEN 17+1+10+1
> +
> +static bool compat_get_host_pf_mac(const char *, struct eth_addr *);
> +
> +static bool
> +fill_devlink_ports_key_from_strs(char *buf, size_t bufsiz,
> +                                const char *host_pf_mac,
> +                                const char *function)
> +{
> +    return snprintf(buf, bufsiz,
> +                    function != NULL ? "%s-%s": "%s",
> +                    host_pf_mac, function) < bufsiz;
> +}
> +
> +/* We deliberately pass the struct eth_addr by value as we would have to copy
> + * the data either way to make use of the ETH_ADDR_ARGS macro */
> +static bool
> +fill_devlink_ports_key_from_typed(char *buf, size_t bufsiz,
> +                    struct eth_addr host_pf_mac,
> +                    uint32_t function)
> +{
> +    return snprintf(
> +        buf, bufsiz,
> +        function < UINT32_MAX ? ETH_ADDR_FMT"-%"PRIu32 : ETH_ADDR_FMT,
> +        ETH_ADDR_ARGS(host_pf_mac), function) < bufsiz;
> +}
> +
> +static void
> +devlink_port_add_function(struct dl_port *port_entry,
> +                          struct eth_addr *host_pf_mac)
> +{
> +    char keybuf[MAX_KEY_LEN];
> +    uint32_t function_number;
> +
> +    switch (port_entry->flavour) {
> +    case DEVLINK_PORT_FLAVOUR_PCI_PF:
> +        /* for Physical Function representor ports we only add the MAC 
> address
> +         * and no logical function number */
> +        function_number = -1;
> +        break;
> +    case DEVLINK_PORT_FLAVOUR_PCI_VF:
> +        function_number = port_entry->pci_vf_number;
> +        break;
> +    default:
> +        VLOG_WARN("Unsupported flavour for port '%s': %s",
> +            port_entry->netdev_name,
> +            port_entry->flavour == DEVLINK_PORT_FLAVOUR_PHYSICAL ? 
> "PHYSICAL" :
> +            port_entry->flavour == DEVLINK_PORT_FLAVOUR_CPU ? "CPU" :
> +            port_entry->flavour == DEVLINK_PORT_FLAVOUR_DSA ? "DSA" :
> +            port_entry->flavour == DEVLINK_PORT_FLAVOUR_PCI_PF ? "PCI_PF":
> +            port_entry->flavour == DEVLINK_PORT_FLAVOUR_PCI_VF ? "PCI_VF":
> +            port_entry->flavour == DEVLINK_PORT_FLAVOUR_VIRTUAL ? "VIRTUAL":
> +            port_entry->flavour == DEVLINK_PORT_FLAVOUR_UNUSED ? "UNUSED":
> +            port_entry->flavour == DEVLINK_PORT_FLAVOUR_PCI_SF ? "PCI_SF":
> +            "UNKNOWN");
> +        return;
> +    };
> +    /* Failure to fill key from typed values means calculation of the max key
> +     * length is wrong, i.e. a bug. */
> +    ovs_assert(fill_devlink_ports_key_from_typed(
> +                            keybuf, sizeof(keybuf),
> +                            *host_pf_mac, function_number));
> +    shash_add(&devlink_ports, keybuf, xstrdup(port_entry->netdev_name));
> +}
> +
> +
> +static int
> +plug_representor_init(void)
> +{
> +    struct nl_dl_dump_state *port_dump;
> +    struct dl_port port_entry;
> +    int error;
> +    struct eth_addr host_pf_macs[MAX_NIC_PHY_PORTS];
> +
> +    shash_init(&devlink_ports);
> +
> +    port_dump = nl_dl_dump_init();
> +    if ((error = nl_dl_dump_init_error(port_dump))) {
> +        VLOG_WARN(
> +            "unable to start dump of ports from devlink-port interface");
> +        return error;
> +    }
> +    /* The core devlink infrastructure in the kernel keeps a linked list of
> +     * the devices and each of those has a linked list of ports. These are
> +     * populated by each device driver as devices are enumerated, and as such
> +     * we can rely on ports being dumped in a consistent order on a device
> +     * by device basis with logical numbering for each port flavour starting
> +     * on 0 for each new device.
> +     */
> +    nl_dl_dump_start(DEVLINK_CMD_PORT_GET, port_dump);
> +    while (nl_dl_port_dump_next(port_dump, &port_entry)) {
> +        switch (port_entry.flavour) {
> +        case DEVLINK_PORT_FLAVOUR_PHYSICAL:
> +            /* The PHYSICAL flavoured port represent a network facing port on
> +             * the NIC.
> +             *
> +             * For kernel versions where the devlink-port infrastructure does
> +             * not provide MAC address for PCI_PF flavoured ports, there 
> exist
> +             * a interface in sysfs which is relative to the name of the
> +             * PHYSICAL port netdev name.
> +             *
> +             * Since we at this point in the dump do not know if the MAC will
> +             * be provided for the PCI_PF or not, proactively store the MAC
> +             * address by looking up through the sysfs interface.
> +             *
> +             * If MAC address is available once we get to the PCI_PF we will
> +             * overwrite the stored value.
> +             */
> +            if (port_entry.number > MAX_NIC_PHY_PORTS) {
> +                VLOG_WARN("physical port number out of range for port '%s': "
> +                          "%"PRIu32,
> +                          port_entry.netdev_name, port_entry.number);
> +                continue;
> +            }
> +            compat_get_host_pf_mac(port_entry.netdev_name,
> +                                   &host_pf_macs[port_entry.number]);
> +            break;
> +        case DEVLINK_PORT_FLAVOUR_PCI_PF: /* FALL THROUGH */
> +            /* The PCI_PF flavoured port represent a host facing port.
> +             *
> +             * For function flavours other than PHYSICAL pci_pf_number will 
> be
> +             * set to the logical number of which physical port the function
> +             * belongs.
> +             */
> +            if (!eth_addr_is_zero(port_entry.function.eth_addr)) {
> +                host_pf_macs[port_entry.pci_pf_number] =
> +                    port_entry.function.eth_addr;
> +            }
> +            /* FALL THROUGH */
> +        case DEVLINK_PORT_FLAVOUR_PCI_VF:
> +            /* The PCI_VF flavoured port represent a host facing
> +             * PCI Virtual Function.
> +             *
> +             * For function flavours other than PHYSICAL pci_pf_number will 
> be
> +             * set to the logical number of which physical port the function
> +             * belongs.
> +             */
> +            if (port_entry.pci_pf_number > MAX_NIC_PHY_PORTS) {
> +                VLOG_WARN("physical port number out of range for port '%s': "
> +                          "%"PRIu32,
> +                          port_entry.netdev_name, port_entry.pci_pf_number);
> +                continue;
> +            }
> +            devlink_port_add_function(&port_entry,
> +                                      
> &host_pf_macs[port_entry.pci_pf_number]);
> +            break;
> +        };
> +    }
> +    nl_dl_dump_finish(port_dump);
> +    nl_dl_dump_destroy(port_dump);
> +
> +    return 0;
> +}
> +
> +static int
> +plug_representor_destroy(void)
> +{
> +    shash_destroy_free_data(&devlink_ports);
> +
> +    return 0;
> +}
> +
> +static bool
> +plug_representor_port_prepare(const struct plug_port_ctx_in *ctx_in,
> +                              struct plug_port_ctx_out *ctx_out)
> +{
> +    char keybuf[MAX_KEY_LEN];
> +    const char *pf_mac = smap_get(ctx_in->lport_options,
> +                                  "plug:representor:pf-mac");
> +    const char *vf_num = smap_get(ctx_in->lport_options,
> +                                  "plug:representor:vf-num");
> +    if (!fill_devlink_ports_key_from_strs(keybuf, sizeof(keybuf),
> +                                          pf_mac, vf_num))
> +    {
> +        /* Overflow, most likely incorrect input data from database */
> +        VLOG_WARN("Southbound DB port plugging options out of range for "
> +                  "lport: %s pf-mac: '%s' vf-num: '%s'",
> +                  ctx_in->lport_name, pf_mac, vf_num);
> +        return false;
> +    }
> +
> +    char *rep_port;
> +    rep_port = shash_find_data(&devlink_ports, keybuf);
> +    VLOG_INFO("plug_representor %s (%s) -> %s",
> +              ctx_in->lport_name, rep_port, ctx_in->br_int->name);
> +    ctx_out->name = rep_port;
> +    ctx_out->type = NULL;
> +    ctx_out->iface_options = NULL;
> +    return true;
> +}
> +
> +static void
> +plug_representor_port_finish(const struct plug_port_ctx_in *ctx_in 
> OVS_UNUSED,
> +                             struct plug_port_ctx_out *ctx_out OVS_UNUSED)
> +{
> +    /* Nothing to be done here for now */
> +}
> +
> +static void
> +plug_representor_port_ctx_destroy(
> +                const struct plug_port_ctx_in *ctx_in OVS_UNUSED,
> +                struct plug_port_ctx_out *ctx_out OVS_UNUSED)
> +{
> +    /* Noting to be done here for now */
> +}
> +
> +const struct plug_class plug_representor = {
> +    .type = "representor",
> +    .init = plug_representor_init,
> +    .destroy = plug_representor_destroy,
> +    .plug_get_maintained_iface_options = NULL, /* TODO */
> +    .run = NULL, /* TODO */
> +    .plug_port_prepare = plug_representor_port_prepare,
> +    .plug_port_finish = plug_representor_port_finish,
> +    .plug_port_ctx_destroy = plug_representor_port_ctx_destroy,
> +};
> +
> +/* The kernel devlink-port interface provides a vendor neutral and standard 
> way
> + * of discovering host visible resources such as MAC address of interfaces 
> from
> + * a program running on the NIC SoC side.
> + *
> + * However a fairly recent kernel version is required for it to work, so 
> until
> + * this is widely available we provide this helper to retrieve the same
> + * information from the interim sysfs solution. */
> +static bool
> +compat_get_host_pf_mac(const char *netdev_name, struct eth_addr *ea)
> +{
> +    char file_name[IFNAMSIZ + 35 + 1];
> +    FILE *stream;
> +    char line[128];
> +    bool retval = false;
> +
> +    snprintf(file_name, sizeof(file_name),
> +             "/sys/class/net/%s/smart_nic/pf/config", netdev_name);
> +    stream = fopen(file_name, "r");
> +    if (!stream) {
> +        VLOG_WARN("%s: open failed (%s)",
> +                  file_name, ovs_strerror(errno));
> +        *ea = eth_addr_zero;
> +        return false;
> +    }
> +    while (fgets(line, sizeof(line), stream)) {
> +        char key[16];
> +        char *cp;
> +        if (ovs_scan(line, "%15[^:]: ", key)
> +            && key[0] == 'M' && key[1] == 'A' && key[2] == 'C')
> +        {
> +            /* strip any newline character */
> +            if ((cp = strchr(line, '\n')) != NULL) {
> +                *cp = '\0';
> +            }
> +            /* point cp at end of key + ': ', i.e. start of MAC address */
> +            cp = line + strnlen(key, sizeof(key)) + 2;
> +            retval = eth_addr_from_string(cp, ea);
> +            break;
> +        }
> +    }
> +    fclose(stream);
> +    return retval;
> +}
> diff --git a/m4/ovn.m4 b/m4/ovn.m4
> index 2909914fb..2f274fc65 100644
> --- a/m4/ovn.m4
> +++ b/m4/ovn.m4
> @@ -592,3 +592,29 @@ AC_DEFUN([OVS_CHECK_DDLOG_FAST_BUILD],
>     if $ddlog_fast_build; then
>        DDLOG_EXTRA_RUSTFLAGS="-C opt-level=z"
>     fi])
> +
> +dnl Checks for Netlink support.
> +AC_DEFUN([OVS_CHECK_NETLINK],
> +  [AC_CHECK_HEADER([linux/netlink.h],
> +                   [HAVE_NETLINK=yes],
> +                   [HAVE_NETLINK=no],
> +                   [#include <sys/socket.h>
> +   ])
> +   AM_CONDITIONAL([HAVE_NETLINK], [test "$HAVE_NETLINK" = yes])
> +   if test "$HAVE_NETLINK" = yes; then
> +      AC_DEFINE([HAVE_NETLINK], [1],
> +                [Define to 1 if Netlink protocol is available.])
> +   fi])
> +
> +dnl OVS_CHECK_LINUX_NETLINK
> +dnl
> +dnl Configure Linux netlink compat.
> +AC_DEFUN([OVS_CHECK_LINUX_NETLINK], [
> +  AC_COMPILE_IFELSE([
> +    AC_LANG_PROGRAM([#include <linux/netlink.h>], [
> +        struct nla_bitfield32 x =  { 0 };
> +    ])],
> +    [AC_DEFINE([HAVE_NLA_BITFIELD32], [1],
> +    [Define to 1 if struct nla_bitfield32 is available.])])
> +])
> +
> --
> 2.32.0
>
> _______________________________________________
> dev mailing list
> d...@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev



-- 
Frode Nordahl
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to