Add a devlink instance for the idpf adapter and implement the .info_get
callback, reporting:
  - serial_number: PCI Device Serial Number, for device identification
  - fw.mgmt.api: running version of the driver-device communication
    channel (virtchnl)

This enables NIPA CI and other tools to uniquely identify devices under
test and track device capabilities. This phased implementation initially
focuses on stable, readily available information. Firmware version
information will be added in future patches as support is exposed
through virtchnl and the driver architecture allows reliable retrieval.

The adapter structure is carved out of the devlink private area, so
devlink_alloc() replaces the plain kzalloc_obj() in idpf_probe() and
devlink_free() replaces the corresponding kfree() on both the probe
error path and in idpf_remove(). The instance is registered in
idpf_probe(), before the reset worker that performs the virtchnl
handshake is queued, and unregistered in idpf_remove(), so it stays
available for the whole time the driver is bound.

The serial number is omitted on devices that do not implement the PCI
Device Serial Number extended capability, where pci_get_dsn() returns 0,
rather than reporting an all-zero serial number. The virtchnl version is
omitted until it has been negotiated, as the instance is registered
before the handshake runs.

$ devlink dev show
pci/0000:85:00.0

$ devlink dev info pci/0000:85:00.0
pci/0000:85:00.0:
  driver idpf
  serial_number 00-a0-c9-ff-ff-23-45-67
  versions:
      running:
        fw.mgmt.api 2.0

Link: 
https://github.com/linux-netdev/nipa/wiki/Netdev-CI-system#device-information
Signed-off-by: Paul Greenwalt <[email protected]>
---
 Documentation/networking/devlink/idpf.rst     | 30 ++++++
 Documentation/networking/devlink/index.rst    |  1 +
 drivers/net/ethernet/intel/idpf/Kconfig       |  1 +
 drivers/net/ethernet/intel/idpf/Makefile      |  1 +
 .../net/ethernet/intel/idpf/idpf_devlink.c    | 98 +++++++++++++++++++
 .../net/ethernet/intel/idpf/idpf_devlink.h    | 45 +++++++++
 drivers/net/ethernet/intel/idpf/idpf_main.c   | 13 ++-
 7 files changed, 186 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/networking/devlink/idpf.rst
 create mode 100644 drivers/net/ethernet/intel/idpf/idpf_devlink.c
 create mode 100644 drivers/net/ethernet/intel/idpf/idpf_devlink.h

diff --git a/Documentation/networking/devlink/idpf.rst 
b/Documentation/networking/devlink/idpf.rst
new file mode 100644
index 0000000000000..5121cffd8f476
--- /dev/null
+++ b/Documentation/networking/devlink/idpf.rst
@@ -0,0 +1,30 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================
+idpf devlink support
+====================
+
+This document describes the devlink features implemented by the ``idpf``
+device driver.
+
+Info versions
+=============
+
+The ``idpf`` driver reports the following versions
+
+.. list-table:: devlink info versions implemented
+    :widths: 5 5 5 90
+
+    * - Name
+      - Type
+      - Example
+      - Description
+    * - ``fw.mgmt.api``
+      - running
+      - 2.0
+      - 2-digit version number (major.minor) of the communication channel
+        (virtchnl) used by the device.
+
+The driver also reports the PCI Device Serial Number through the
+``serial_number`` attribute, on devices that implement the PCI Device Serial
+Number extended capability.
diff --git a/Documentation/networking/devlink/index.rst 
b/Documentation/networking/devlink/index.rst
index d4a83fdcff7fe..538494b1051bd 100644
--- a/Documentation/networking/devlink/index.rst
+++ b/Documentation/networking/devlink/index.rst
@@ -85,6 +85,7 @@ parameters, info versions, and other features it supports.
    hns3
    i40e
    ice
+   idpf
    ionic
    iosm
    ixd
diff --git a/drivers/net/ethernet/intel/idpf/Kconfig 
b/drivers/net/ethernet/intel/idpf/Kconfig
index 586df3a4afe90..4ee8bbc401fe6 100644
--- a/drivers/net/ethernet/intel/idpf/Kconfig
+++ b/drivers/net/ethernet/intel/idpf/Kconfig
@@ -6,6 +6,7 @@ config IDPF
        depends on PCI_MSI
        depends on PTP_1588_CLOCK_OPTIONAL
        select DIMLIB
+       select NET_DEVLINK
        select LIBIE_CP
        select LIBETH_XDP
        help
diff --git a/drivers/net/ethernet/intel/idpf/Makefile 
b/drivers/net/ethernet/intel/idpf/Makefile
index 4aaafa175ec37..81b27a4067478 100644
--- a/drivers/net/ethernet/intel/idpf/Makefile
+++ b/drivers/net/ethernet/intel/idpf/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_IDPF) += idpf.o
 
 idpf-y := \
        idpf_dev.o              \
+       idpf_devlink.o          \
        idpf_ethtool.o          \
        idpf_idc.o              \
        idpf_lib.o              \
diff --git a/drivers/net/ethernet/intel/idpf/idpf_devlink.c 
b/drivers/net/ethernet/intel/idpf/idpf_devlink.c
new file mode 100644
index 0000000000000..afdb5bc2e002c
--- /dev/null
+++ b/drivers/net/ethernet/intel/idpf/idpf_devlink.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2026 Intel Corporation */
+
+#include <linux/pci.h>
+#include <linux/unaligned.h>
+
+#include "idpf.h"
+#include "idpf_devlink.h"
+
+#define IDPF_DEVLINK_INFO_LEN          128
+
+/**
+ * idpf_info_get_dsn - format the PCI DSN as the device serial number
+ * @adapter: the idpf adapter structure
+ * @buf: buffer to store the formatted serial number
+ * @buf_size: size of @buf
+ *
+ * Return: true if the device reports a DSN, false otherwise.
+ */
+static bool idpf_info_get_dsn(struct idpf_adapter *adapter, char *buf,
+                             size_t buf_size)
+{
+       u64 dsn = pci_get_dsn(adapter->pdev);
+       u8 dsn_be[sizeof(dsn)];
+
+       if (!dsn)
+               return false;
+
+       /* Copy the DSN into an array in Big Endian format */
+       put_unaligned_be64(dsn, dsn_be);
+       snprintf(buf, buf_size, "%8phD", dsn_be);
+
+       return true;
+}
+
+/**
+ * idpf_devlink_info_get - .info_get devlink handler
+ * @devlink: devlink instance structure
+ * @req: the devlink info request
+ * @extack: extended netlink ack structure
+ *
+ * Callback for the devlink .info_get operation. Reports information about the
+ * device. The virtchnl version is only reported once it has been negotiated,
+ * as the instance is registered before the handshake runs.
+ *
+ * Return: zero on success or a negative error code on failure.
+ */
+static int idpf_devlink_info_get(struct devlink *devlink,
+                                struct devlink_info_req *req,
+                                struct netlink_ext_ack *extack)
+{
+       struct idpf_adapter *adapter = devlink_priv(devlink);
+       char buf[IDPF_DEVLINK_INFO_LEN];
+       u32 maj, min;
+       int err;
+
+       if (idpf_info_get_dsn(adapter, buf, sizeof(buf))) {
+               err = devlink_info_serial_number_put(req, buf);
+               if (err)
+                       return err;
+       }
+
+       maj = READ_ONCE(adapter->virt_ver_maj);
+       min = READ_ONCE(adapter->virt_ver_min);
+       if (!maj && !min)
+               return 0;
+
+       snprintf(buf, sizeof(buf), "%u.%u", maj, min);
+
+       return devlink_info_version_running_put(req,
+                                       
DEVLINK_INFO_VERSION_GENERIC_FW_MGMT_API,
+                                       buf);
+}
+
+static const struct devlink_ops idpf_devlink_ops = {
+       .info_get = idpf_devlink_info_get,
+};
+
+/**
+ * idpf_adapter_alloc - allocate devlink and return adapter
+ * @dev: IDPF device to allocate for
+ *
+ * Allocate a devlink instance for this device and return the private area as
+ * the adapter structure.
+ *
+ * Return: adapter structure on success, NULL on failure
+ */
+struct idpf_adapter *idpf_adapter_alloc(struct device *dev)
+{
+       struct devlink *devlink;
+
+       devlink = devlink_alloc(&idpf_devlink_ops, sizeof(struct idpf_adapter),
+                               dev);
+       if (!devlink)
+               return NULL;
+
+       return devlink_priv(devlink);
+}
diff --git a/drivers/net/ethernet/intel/idpf/idpf_devlink.h 
b/drivers/net/ethernet/intel/idpf/idpf_devlink.h
new file mode 100644
index 0000000000000..3ace09be8dddf
--- /dev/null
+++ b/drivers/net/ethernet/intel/idpf/idpf_devlink.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) 2026 Intel Corporation */
+
+#ifndef _IDPF_DEVLINK_H_
+#define _IDPF_DEVLINK_H_
+#include <net/devlink.h>
+
+struct idpf_adapter;
+
+struct idpf_adapter *idpf_adapter_alloc(struct device *dev);
+
+/**
+ * idpf_devlink_free - teardown the devlink
+ * @adapter: IDPF adapter structure to free
+ */
+static inline void idpf_devlink_free(struct idpf_adapter *adapter)
+{
+       struct devlink *devlink = priv_to_devlink(adapter);
+
+       devlink_free(devlink);
+}
+
+/**
+ * idpf_devlink_register - register the devlink
+ * @adapter: IDPF adapter structure
+ */
+static inline void idpf_devlink_register(struct idpf_adapter *adapter)
+{
+       struct devlink *devlink = priv_to_devlink(adapter);
+
+       devlink_register(devlink);
+}
+
+/**
+ * idpf_devlink_unregister - unregister the devlink
+ * @adapter: IDPF adapter structure
+ */
+static inline void idpf_devlink_unregister(struct idpf_adapter *adapter)
+{
+       struct devlink *devlink = priv_to_devlink(adapter);
+
+       devlink_unregister(devlink);
+}
+
+#endif /* _IDPF_DEVLINK_H_ */
diff --git a/drivers/net/ethernet/intel/idpf/idpf_main.c 
b/drivers/net/ethernet/intel/idpf/idpf_main.c
index 3dbbe21459990..34a69bb2e36bc 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_main.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_main.c
@@ -3,6 +3,7 @@
 
 #include "idpf.h"
 #include "idpf_devids.h"
+#include "idpf_devlink.h"
 #include "idpf_lan_vf_regs.h"
 #include "idpf_virtchnl.h"
 
@@ -127,12 +128,15 @@ static void idpf_remove(struct pci_dev *pdev)
 
        set_bit(IDPF_REMOVE_IN_PROG, adapter->flags);
 
+       idpf_devlink_unregister(adapter);
+
        /* Wait until vc_event_task is done to consider if any hard reset is
         * in progress else we may go ahead and release the resources but the
         * thread doing the hard reset might continue the init path and
         * end up in bad state.
         */
        cancel_delayed_work_sync(&adapter->vc_event_task);
+
        if (adapter->num_vfs)
                idpf_sriov_configure(pdev, 0);
 
@@ -184,7 +188,8 @@ static void idpf_remove(struct pci_dev *pdev)
        mutex_destroy(&adapter->vc_buf_lock);
 
        idpf_decfg_device(adapter);
-       kfree(adapter);
+
+       idpf_devlink_free(adapter);
 }
 
 /**
@@ -263,7 +268,7 @@ static int idpf_probe(struct pci_dev *pdev, const struct 
pci_device_id *ent)
        struct idpf_adapter *adapter;
        int err;
 
-       adapter = kzalloc_obj(*adapter);
+       adapter = idpf_adapter_alloc(dev);
        if (!adapter)
                return -ENOMEM;
 
@@ -349,6 +354,8 @@ static int idpf_probe(struct pci_dev *pdev, const struct 
pci_device_id *ent)
        INIT_DELAYED_WORK(&adapter->stats_task, idpf_statistics_task);
        INIT_DELAYED_WORK(&adapter->vc_event_task, idpf_vc_event_task);
 
+       idpf_devlink_register(adapter);
+
        adapter->dev_ops.reg_ops.reset_reg_init(adapter);
        set_bit(IDPF_HR_DRV_LOAD, adapter->flags);
        queue_delayed_work(adapter->vc_event_wq, &adapter->vc_event_task,
@@ -367,7 +374,7 @@ static int idpf_probe(struct pci_dev *pdev, const struct 
pci_device_id *ent)
 err_init_wq:
        idpf_decfg_device(adapter);
 err_free:
-       kfree(adapter);
+       idpf_devlink_free(adapter);
        return err;
 }
 
-- 
2.52.0

Reply via email to