* [PATCH net-next v2 0/3] dinghai: firmware handshake, MSI-X pools and async event queues
@ 2026-09-11 3:56 han.junyang
2026-09-11 3:59 ` [PATCH net-next v2 1/3] dinghai: add firmware version check and RISC-V readiness polling han.junyang
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: han.junyang @ 2026-09-11 3:56 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, horms
Cc: linux-kernel, netdev, han.junyang, ran.ming, han.chengfei, zhang.yanze
From: Junyang Han <han.junyang@zte.com.cn>
This series continues the DingHai (ZXDH) PF driver bring-up: after PCI
probing, it verifies the firmware version contract, waits for the
RISC-V management core to become ready, sets up the MSI-X interrupt
pools, and creates the async event queues through which the firmware
will report events.
Some notes on the IRQ design:
The vector space is partitioned into per-purpose pools (async, RDMA,
vq). Event queues share a vector through an atomic notifier chain
attached to the IRQ, and the sharing degree is governed by pool
thresholds. The IRQ table hangs off a void *priv in the shared core
device: the PF, MPF and SF core devices each carry a different pool
layout behind that pointer, so a type-specific struct in en_pf.c keeps
the shared header free of PF-only details.
Changes in v2:
- Convert both firmware readiness polls to readx_poll_timeout()
(Andrew Lunn).
- Read the fw compat block field by field through ioread*() accessors
instead of an ioread32_rep() bulk copy, which misplaces the u8/u16
fields on big-endian; no __le annotations are needed since each
accessor converts from little-endian (Andrew Lunn).
- Use kref for the IRQ reference count (Andrew Lunn).
Junyang Han (3):
dinghai: add firmware version check and RISC-V readiness polling
dinghai: add MSI-X interrupt pools
dinghai: add async event queue for firmware notifications
drivers/net/ethernet/zte/dinghai/Makefile | 2 +-
drivers/net/ethernet/zte/dinghai/en_pf.c | 225 +++++++++++
drivers/net/ethernet/zte/dinghai/en_pf.h | 57 +++
drivers/net/ethernet/zte/dinghai/zxdh_eq.c | 145 +++++++
drivers/net/ethernet/zte/dinghai/zxdh_eq.h | 61 +++
drivers/net/ethernet/zte/dinghai/zxdh_irq.c | 412 ++++++++++++++++++++
drivers/net/ethernet/zte/dinghai/zxdh_irq.h | 72 ++++
7 files changed, 973 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/zte/dinghai/zxdh_eq.c
create mode 100644 drivers/net/ethernet/zte/dinghai/zxdh_eq.h
create mode 100644 drivers/net/ethernet/zte/dinghai/zxdh_irq.c
create mode 100644 drivers/net/ethernet/zte/dinghai/zxdh_irq.h
--
2.27.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v2 1/3] dinghai: add firmware version check and RISC-V readiness polling
2026-09-11 3:56 [PATCH net-next v2 0/3] dinghai: firmware handshake, MSI-X pools and async event queues han.junyang
@ 2026-09-11 3:59 ` han.junyang
2026-09-14 7:20 ` Simon Horman
2026-09-11 4:02 ` [PATCH net-next v2 2/3] dinghai: add MSI-X interrupt pools han.junyang
2026-09-11 4:05 ` [PATCH net-next v2 3/3] dinghai: add async event queue for firmware notifications han.junyang
2 siblings, 1 reply; 7+ messages in thread
From: han.junyang @ 2026-09-11 3:59 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, horms
Cc: linux-kernel, netdev, han.junyang, ran.ming, han.chengfei, zhang.yanze
From: Junyang Han <han.junyang@zte.com.cn>
The DingHai firmware publishes a version compatibility block and a
RISC-V health buffer at fixed offsets within BAR 0.
After the PCI capabilities are mapped, poll the compatibility block
until the firmware populates it (the region reads as all ones until
then) and verify the driver/firmware version contract. Then wait for
the RISC-V management core to set its power-on flag in the health
buffer before the rest of the probe continues.
Firmware images predating the health buffer protocol (health version
other than 1 and patch level below ZXDH_HPIRQ_PATCH) skip the
readiness wait.
Signed-off-by: Junyang Han <han.junyang@zte.com.cn>
---
drivers/net/ethernet/zte/dinghai/en_pf.c | 107 +++++++++++++++++++++++
drivers/net/ethernet/zte/dinghai/en_pf.h | 46 ++++++++++
2 files changed, 153 insertions(+)
diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c
index 86d437408820..d257d8e9b612 100644
--- a/drivers/net/ethernet/zte/dinghai/en_pf.c
+++ b/drivers/net/ethernet/zte/dinghai/en_pf.c
@@ -6,6 +6,8 @@
#include <linux/module.h>
#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
#include <net/devlink.h>
#include <linux/dma-mapping.h>
#include "en_pf.h"
@@ -369,6 +371,99 @@ int zxdh_pf_modern_cfg_init(struct zxdh_core_dev *zxdh_dev)
return ret;
}
+/* Read the firmware version block and verify the driver/firmware
+ * version contract.
+ */
+static int zxdh_pf_fw_compat_check(struct zxdh_core_dev *zxdh_dev)
+{
+ struct zxdh_pf_dev *pf_dev = zxdh_dev->priv;
+ struct zxdh_fw_compat __iomem *compat;
+ struct zxdh_fw_compat *fw_compat;
+ u32 erased;
+
+ fw_compat = &pf_dev->fw_compat;
+ compat = pf_dev->pci_ioremap_addr[0] + ZXDH_FW_COMPAT_OFFSET;
+
+ /* The region reads as all ones until the firmware populates it at
+ * the end of its boot; allow up to 200 s for a cold boot.
+ */
+ readx_poll_timeout(ioread32, compat, erased, erased != 0xffffffffU,
+ USEC_PER_SEC,
+ ZXDH_FW_COMPAT_TIMEOUT_SEC * USEC_PER_SEC);
+
+ /* Firmware predating the compatibility region keeps the erased
+ * pattern, which fails the module id check below and defers the
+ * decision to the readiness wait.
+ */
+ fw_compat->module_id = ioread8(&compat->module_id);
+ fw_compat->major = ioread8(&compat->major);
+ fw_compat->fw_minor = ioread8(&compat->fw_minor);
+ fw_compat->drv_minor = ioread8(&compat->drv_minor);
+ fw_compat->patch = ioread16(&compat->patch);
+
+ if (fw_compat->module_id != ZXDH_MODULE_ID) {
+ dev_info(zxdh_dev->device,
+ "unknown module id %u, skip fw compat check\n",
+ fw_compat->module_id);
+ return 0;
+ }
+
+ if (fw_compat->major != ZXDH_MAJOR) {
+ dev_err(zxdh_dev->device,
+ "driver major %u incompatible with firmware major %u\n",
+ ZXDH_MAJOR, fw_compat->major);
+ return -EINVAL;
+ }
+
+ if (fw_compat->fw_minor < ZXDH_FW_MINOR) {
+ dev_err(zxdh_dev->device,
+ "firmware fw_minor %d older than required %u\n",
+ fw_compat->fw_minor, ZXDH_FW_MINOR);
+ return -EINVAL;
+ }
+
+ if (fw_compat->drv_minor > ZXDH_DRV_MINOR) {
+ dev_err(zxdh_dev->device,
+ "driver drv_minor %u older than required by firmware %u\n",
+ ZXDH_DRV_MINOR, fw_compat->drv_minor);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Wait for the RISC-V management core of the firmware to finish
+ * booting, so that later probe steps can talk to it.
+ */
+static int zxdh_pf_wait_riscv_ready(struct zxdh_core_dev *zxdh_dev)
+{
+ struct zxdh_pf_dev *pf_dev = zxdh_dev->priv;
+ struct zxdh_health_buffer __iomem *hb;
+ u8 health_version;
+ u8 power_on;
+ int err;
+
+ hb = pf_dev->pci_ioremap_addr[0] + ZXDH_RISCV_HB_OFFSET;
+ health_version = ioread8(&hb->health_version);
+
+ /* Firmware predating the health buffer protocol has neither a
+ * valid version byte nor a power-on flag to wait for.
+ */
+ if (health_version != 1 &&
+ pf_dev->fw_compat.patch < ZXDH_HPIRQ_PATCH)
+ return 0;
+
+ err = readx_poll_timeout(ioread8, &hb->riscv_power_on, power_on,
+ power_on == 1, USEC_PER_SEC,
+ ZXDH_RISCV_READY_TIMEOUT_SEC * USEC_PER_SEC);
+ if (err) {
+ dev_err(zxdh_dev->device, "timed out waiting for riscv power on\n");
+ return err;
+ }
+
+ return 0;
+}
+
static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct zxdh_core_dev *zxdh_dev;
@@ -405,6 +500,18 @@ static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto err_cfg_init;
}
+ ret = zxdh_pf_fw_compat_check(zxdh_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "zxdh_pf_fw_compat_check failed: %d\n", ret);
+ goto err_cfg_init;
+ }
+
+ ret = zxdh_pf_wait_riscv_ready(zxdh_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "zxdh_pf_wait_riscv_ready failed: %d\n", ret);
+ goto err_cfg_init;
+ }
+
devlink_register(devlink);
return 0;
diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.h b/drivers/net/ethernet/zte/dinghai/en_pf.h
index 7373dee8d1a9..bc275ddd75b8 100644
--- a/drivers/net/ethernet/zte/dinghai/en_pf.h
+++ b/drivers/net/ethernet/zte/dinghai/en_pf.h
@@ -29,6 +29,51 @@
#define ZXDH_PF_ALIGN2 2
#define ZXDH_PF_MAP_MINLEN2 2
+/* Fixed offsets of the firmware interface regions within BAR 0. */
+#define ZXDH_RISCV_HB_OFFSET 0x5300
+#define ZXDH_FW_COMPAT_OFFSET 0x5400
+
+/* Driver/firmware version contract. The firmware publishes its side of
+ * the contract in the region at ZXDH_FW_COMPAT_OFFSET.
+ */
+#define ZXDH_MODULE_ID 1
+#define ZXDH_MAJOR 1
+#define ZXDH_FW_MINOR 0
+#define ZXDH_DRV_MINOR 0
+/* Firmware patch level that introduced the health buffer protocol. */
+#define ZXDH_HPIRQ_PATCH 4
+
+#define ZXDH_FW_COMPAT_TIMEOUT_SEC 200
+#define ZXDH_RISCV_READY_TIMEOUT_SEC 40
+
+/* Firmware version compatibility block at ZXDH_FW_COMPAT_OFFSET.
+ * Fields are read through ioread*(), which converts from little-endian.
+ */
+struct zxdh_fw_compat {
+ u8 module_id;
+ u8 major;
+ s8 fw_minor;
+ u8 drv_minor;
+ u16 patch;
+ u16 rsv;
+} __packed;
+
+/* Health buffer at ZXDH_RISCV_HB_OFFSET, maintained by the RISC-V
+ * management core of the firmware. Fields are read through ioread*().
+ */
+struct zxdh_health_buffer {
+ u32 synd; /* Bitmask of active syndrome flags. */
+ u32 health_counter; /* Incremented heartbeat counter. */
+ u8 status;
+ u8 rfr;
+ u8 fw_exception;
+ u8 riscv_power_on; /* Set to 1 once the core finished booting. */
+ u8 fw_version[32];
+ u8 pf_status[5];
+ u8 health_version; /* Health buffer protocol version. */
+ u8 rsv1[30];
+} __packed;
+
struct zxdh_core_dev {
struct device *device;
struct pci_dev *pdev;
@@ -54,6 +99,7 @@ struct zxdh_pf_dev {
s32 modern_bars;
void __iomem *pci_ioremap_addr[6];
u32 dev_cfg_bar_off;
+ struct zxdh_fw_compat fw_compat;
};
void *zxdh_core_alloc_priv(struct zxdh_core_dev *zxdh_dev, size_t size);
--
2.27.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v2 2/3] dinghai: add MSI-X interrupt pools
2026-09-11 3:56 [PATCH net-next v2 0/3] dinghai: firmware handshake, MSI-X pools and async event queues han.junyang
2026-09-11 3:59 ` [PATCH net-next v2 1/3] dinghai: add firmware version check and RISC-V readiness polling han.junyang
@ 2026-09-11 4:02 ` han.junyang
2026-09-14 7:20 ` Simon Horman
2026-09-11 4:05 ` [PATCH net-next v2 3/3] dinghai: add async event queue for firmware notifications han.junyang
2 siblings, 1 reply; 7+ messages in thread
From: han.junyang @ 2026-09-11 4:02 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, horms
Cc: linux-kernel, netdev, han.junyang, ran.ming, han.chengfei, zhang.yanze
From: Junyang Han <han.junyang@zte.com.cn>
Allocate the fixed MSI-X vector layout of the device and manage the
vectors in per-purpose pools: one pool for the async event queues and
one for the vq queue pairs, with a range reserved for RDMA in between.
IRQs are reference counted so that several event queues can share one
vector. The pool hands out the least loaded matching IRQ once it passes
the pool minimum threshold, and binds newly created IRQs to the least
loaded CPU of the requested affinity mask. Interrupt delivery fans out
through an atomic notifier chain attached to each IRQ, which the async
event queue setup posted later in this series hooks into.
Signed-off-by: Junyang Han <han.junyang@zte.com.cn>
---
drivers/net/ethernet/zte/dinghai/Makefile | 2 +-
drivers/net/ethernet/zte/dinghai/en_pf.c | 103 +++++
drivers/net/ethernet/zte/dinghai/en_pf.h | 9 +
drivers/net/ethernet/zte/dinghai/zxdh_irq.c | 412 ++++++++++++++++++++
drivers/net/ethernet/zte/dinghai/zxdh_irq.h | 72 ++++
5 files changed, 597 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/zte/dinghai/zxdh_irq.c
create mode 100644 drivers/net/ethernet/zte/dinghai/zxdh_irq.h
diff --git a/drivers/net/ethernet/zte/dinghai/Makefile b/drivers/net/ethernet/zte/dinghai/Makefile
index e2f4da33df59..f4a8f9480291 100644
--- a/drivers/net/ethernet/zte/dinghai/Makefile
+++ b/drivers/net/ethernet/zte/dinghai/Makefile
@@ -4,4 +4,4 @@
#
obj-$(CONFIG_DINGHAI_PF) += dinghai10e.o
-dinghai10e-y := en_pf.o
+dinghai10e-y := en_pf.o zxdh_irq.o
diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c
index d257d8e9b612..923d688e7ec5 100644
--- a/drivers/net/ethernet/zte/dinghai/en_pf.c
+++ b/drivers/net/ethernet/zte/dinghai/en_pf.c
@@ -8,6 +8,8 @@
#include <linux/pci.h>
#include <linux/io.h>
#include <linux/iopoll.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
#include <net/devlink.h>
#include <linux/dma-mapping.h>
#include "en_pf.h"
@@ -27,6 +29,14 @@ static const struct pci_device_id zxdh_pf_pci_table[] = {
MODULE_DEVICE_TABLE(pci, zxdh_pf_pci_table);
+struct zxdh_pf_irq_table {
+ struct zxdh_irq_pool *async_pool;
+};
+
+/* IRQ compaction thresholds of the async pool, in number of queues. */
+#define ZXDH_PF_ASYNC_IRQ_MIN_COMP 0
+#define ZXDH_PF_ASYNC_IRQ_MAX_COMP 7
+
void *zxdh_core_alloc_priv(struct zxdh_core_dev *zxdh_dev, size_t size)
{
void *priv = kzalloc(size, GFP_KERNEL);
@@ -464,6 +474,84 @@ static int zxdh_pf_wait_riscv_ready(struct zxdh_core_dev *zxdh_dev)
return 0;
}
+static int zxdh_pf_irq_pools_init(struct zxdh_core_dev *zxdh_dev)
+{
+ struct zxdh_pf_irq_table *pf_irq_table = zxdh_dev->irq_table.priv;
+ struct zxdh_irq_pool *pool;
+
+ pool = zxdh_irq_pool_alloc(zxdh_dev, 0, ZXDH_ASYNC_CHANNELS_NUM,
+ "zxdh_pf_async", ZXDH_PF_ASYNC_IRQ_MIN_COMP,
+ ZXDH_PF_ASYNC_IRQ_MAX_COMP);
+ if (IS_ERR(pool))
+ return PTR_ERR(pool);
+
+ pf_irq_table->async_pool = pool;
+
+ return 0;
+}
+
+static void zxdh_pf_irq_pools_destroy(struct zxdh_pf_irq_table *pf_irq_table)
+{
+ zxdh_irq_pool_free(pf_irq_table->async_pool);
+}
+
+int zxdh_pf_irq_table_init(struct zxdh_core_dev *zxdh_dev)
+{
+ struct zxdh_irq_table *table = &zxdh_dev->irq_table;
+ struct zxdh_pf_irq_table *priv;
+
+ priv = kvzalloc_obj(*priv, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ table->priv = priv;
+
+ return 0;
+}
+
+int zxdh_pf_irq_table_create(struct zxdh_core_dev *zxdh_dev)
+{
+ int total_vec = ZXDH_VQS_CHANNELS_NUM + ZXDH_ASYNC_CHANNELS_NUM +
+ ZXDH_RDMA_CHANNELS_NUM;
+ int err;
+
+ total_vec = pci_alloc_irq_vectors(zxdh_dev->pdev, total_vec, total_vec,
+ PCI_IRQ_MSIX);
+ if (total_vec < 0) {
+ dev_err(zxdh_dev->device, "pci_alloc_irq_vectors failed: %d\n",
+ total_vec);
+ return total_vec;
+ }
+
+ err = zxdh_pf_irq_pools_init(zxdh_dev);
+ if (err) {
+ dev_err(zxdh_dev->device, "zxdh_pf_irq_pools_init failed: %d\n",
+ err);
+ pci_free_irq_vectors(zxdh_dev->pdev);
+ }
+
+ return err;
+}
+
+void zxdh_pf_irq_table_destroy(struct zxdh_core_dev *zxdh_dev)
+{
+ struct zxdh_irq_table *table = &zxdh_dev->irq_table;
+
+ zxdh_pf_irq_pools_destroy(table->priv);
+ kvfree(table->priv);
+ pci_free_irq_vectors(zxdh_dev->pdev);
+}
+
+struct zxdh_irq *zxdh_pf_async_irq_request(struct zxdh_core_dev *zxdh_dev)
+{
+ struct zxdh_irq_table *table = &zxdh_dev->irq_table;
+ struct zxdh_pf_irq_table *pf_irq_table = table->priv;
+
+ if (!pf_irq_table->async_pool)
+ return NULL;
+
+ return zxdh_get_irq_of_pool(pf_irq_table->async_pool);
+}
+
static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct zxdh_core_dev *zxdh_dev;
@@ -512,10 +600,24 @@ static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto err_cfg_init;
}
+ ret = zxdh_pf_irq_table_init(zxdh_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "zxdh_pf_irq_table_init failed: %d\n", ret);
+ goto err_cfg_init;
+ }
+
+ ret = zxdh_pf_irq_table_create(zxdh_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "zxdh_pf_irq_table_create failed: %d\n", ret);
+ goto err_irq_table;
+ }
+
devlink_register(devlink);
return 0;
+err_irq_table:
+ kvfree(zxdh_dev->irq_table.priv);
err_cfg_init:
zxdh_pf_pci_close(zxdh_dev);
err_pci_init:
@@ -531,6 +633,7 @@ static void zxdh_pf_remove(struct pci_dev *pdev)
struct devlink *devlink = priv_to_devlink(zxdh_dev);
devlink_unregister(devlink);
+ zxdh_pf_irq_table_destroy(zxdh_dev);
zxdh_pf_modern_cfg_uninit(zxdh_dev);
zxdh_pf_pci_close(zxdh_dev);
zxdh_core_free_priv(zxdh_dev);
diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.h b/drivers/net/ethernet/zte/dinghai/en_pf.h
index bc275ddd75b8..f60a237c61c4 100644
--- a/drivers/net/ethernet/zte/dinghai/en_pf.h
+++ b/drivers/net/ethernet/zte/dinghai/en_pf.h
@@ -9,6 +9,8 @@
#include <linux/types.h>
+#include "zxdh_irq.h"
+
#define ZXDH_PF_VENDOR_ID 0x1cf2
#define ZXDH_PF_DEVICE_ID 0x8040
#define ZXDH_VF_DEVICE_ID 0x8041
@@ -78,6 +80,7 @@ struct zxdh_core_dev {
struct device *device;
struct pci_dev *pdev;
struct devlink *devlink;
+ struct zxdh_irq_table irq_table;
void *priv;
};
@@ -118,4 +121,10 @@ int zxdh_pf_device_cfg_init(struct zxdh_core_dev *zxdh_dev);
void zxdh_pf_modern_cfg_uninit(struct zxdh_core_dev *zxdh_dev);
int zxdh_pf_modern_cfg_init(struct zxdh_core_dev *zxdh_dev);
+/* PF IRQ table (en_pf.c) */
+int zxdh_pf_irq_table_init(struct zxdh_core_dev *zxdh_dev);
+int zxdh_pf_irq_table_create(struct zxdh_core_dev *zxdh_dev);
+void zxdh_pf_irq_table_destroy(struct zxdh_core_dev *zxdh_dev);
+struct zxdh_irq *zxdh_pf_async_irq_request(struct zxdh_core_dev *zxdh_dev);
+
#endif /* __ZXDH_EN_PF_H__ */
diff --git a/drivers/net/ethernet/zte/dinghai/zxdh_irq.c b/drivers/net/ethernet/zte/dinghai/zxdh_irq.c
new file mode 100644
index 000000000000..437344329c7e
--- /dev/null
+++ b/drivers/net/ethernet/zte/dinghai/zxdh_irq.c
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ZTE DingHai Ethernet driver - IRQ pool management
+ * Copyright (c) 2022-2026, ZTE Corporation.
+ *
+ * Device vectors are managed in per-purpose pools. An IRQ carries a
+ * refcount so that several event queues can share one vector; on
+ * raise, the interrupt is fanned out through an atomic notifier chain
+ * attached to the IRQ.
+ */
+
+#include <linux/cpumask.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/xarray.h>
+
+#include "en_pf.h"
+#include "zxdh_irq.h"
+
+/* Release callback of the IRQ kref, called with pool->lock held. */
+static void zxdh_irq_release(struct kref *kref)
+{
+ struct zxdh_irq *irq = container_of(kref, struct zxdh_irq, refcount);
+ struct zxdh_irq_pool *pool = irq->pool;
+
+ lockdep_assert_held(&pool->lock);
+ xa_erase(&pool->irqs, irq->index);
+ /* free_irq() requires the affinity hint to be cleared before it is
+ * called; the asymmetry with the set path in zxdh_irq_alloc() is
+ * intentional.
+ */
+ irq_update_affinity_hint(irq->irqn, NULL);
+ free_cpumask_var(irq->mask);
+ free_irq(irq->irqn, irq);
+ kfree(irq);
+}
+
+/* Drop one reference; returns 1 if the IRQ was released. */
+static int zxdh_irq_put(struct zxdh_irq *irq)
+{
+ struct zxdh_irq_pool *pool = irq->pool;
+ int released;
+
+ mutex_lock(&pool->lock);
+ released = kref_put(&irq->refcount, zxdh_irq_release);
+ mutex_unlock(&pool->lock);
+
+ return released;
+}
+
+static int zxdh_irq_get(struct zxdh_irq *irq)
+{
+ return kref_get_unless_zero(&irq->refcount);
+}
+
+static irqreturn_t zxdh_irq_int_handler(int irq, void *data)
+{
+ struct zxdh_irq *zxdh_irq = data;
+
+ atomic_notifier_call_chain(&zxdh_irq->nh, 0, NULL);
+
+ return IRQ_HANDLED;
+}
+
+static struct zxdh_irq *zxdh_irq_alloc(struct zxdh_irq_pool *pool, int vecidx,
+ const struct cpumask *affinity)
+{
+ struct zxdh_core_dev *zxdh_dev = pool->dev;
+ struct zxdh_irq *irq;
+ int err;
+ int cpu;
+
+ irq = kzalloc_obj(*irq, GFP_KERNEL);
+ if (!irq)
+ return ERR_PTR(-ENOMEM);
+
+ irq->pool = pool;
+ irq->irqn = pci_irq_vector(zxdh_dev->pdev, vecidx);
+ if (irq->irqn < 0) {
+ err = irq->irqn;
+ goto err_irqn;
+ }
+
+ ATOMIC_INIT_NOTIFIER_HEAD(&irq->nh);
+ snprintf(irq->name, ZXDH_MAX_IRQ_NAME, "async_%d@pci:%s", vecidx,
+ pci_name(zxdh_dev->pdev));
+
+ err = request_irq(irq->irqn, zxdh_irq_int_handler, 0, irq->name, irq);
+ if (err) {
+ dev_err(zxdh_dev->device, "request_irq failed: %d\n", err);
+ goto err_irqn;
+ }
+
+ if (!zalloc_cpumask_var(&irq->mask, GFP_KERNEL)) {
+ dev_err(zxdh_dev->device, "zalloc_cpumask_var failed\n");
+ err = -ENOMEM;
+ goto err_cpumask;
+ }
+
+ if (affinity) {
+ cpumask_copy(irq->mask, affinity);
+ } else {
+ /* No preference requested; spread over all online CPUs. */
+ for_each_online_cpu(cpu)
+ cpumask_set_cpu(cpu, irq->mask);
+ }
+ irq_update_affinity_hint(irq->irqn, irq->mask);
+
+ kref_init(&irq->refcount);
+ irq->index = vecidx;
+ err = xa_err(xa_store(&pool->irqs, irq->index, irq, GFP_KERNEL));
+ if (err) {
+ dev_err(zxdh_dev->device, "xa_store failed for irq %u: %d\n",
+ irq->index, err);
+ goto err_xa;
+ }
+
+ return irq;
+
+err_xa:
+ irq_update_affinity_hint(irq->irqn, NULL);
+ free_cpumask_var(irq->mask);
+err_cpumask:
+ free_irq(irq->irqn, irq);
+err_irqn:
+ kfree(irq);
+ return ERR_PTR(err);
+}
+
+int zxdh_irq_attach_nb(struct zxdh_irq *irq, struct notifier_block *nb)
+{
+ int err;
+
+ if (!zxdh_irq_get(irq))
+ return -ENOENT;
+
+ err = atomic_notifier_chain_register(&irq->nh, nb);
+ if (err)
+ zxdh_irq_put(irq);
+
+ return err;
+}
+
+int zxdh_irq_detach_nb(struct zxdh_irq *irq, struct notifier_block *nb)
+{
+ int err;
+
+ err = atomic_notifier_chain_unregister(&irq->nh, nb);
+ zxdh_irq_put(irq);
+
+ return err;
+}
+
+/* Release one or more IRQs back to their pool. */
+void zxdh_irqs_release_vectors(struct zxdh_irq **irqs, int nirqs)
+{
+ int i;
+
+ for (i = 0; i < nirqs; i++) {
+ synchronize_irq(irqs[i]->irqn);
+ zxdh_irq_put(irqs[i]);
+ }
+}
+
+static void zxdh_cpu_put(struct zxdh_irq_pool *pool, int cpu)
+{
+ pool->irqs_per_cpu[cpu]--;
+}
+
+static void zxdh_cpu_get(struct zxdh_irq_pool *pool, int cpu)
+{
+ pool->irqs_per_cpu[cpu]++;
+}
+
+/* Find the least loaded CPU, i.e. the CPU with the fewest IRQs bound
+ * to it, within the requested mask.
+ */
+static int zxdh_cpu_get_least_loaded(struct zxdh_irq_pool *pool,
+ const struct cpumask *req_mask)
+{
+ int best_cpu = -1;
+ int cpu;
+
+ for_each_cpu_and(cpu, req_mask, cpu_online_mask) {
+ /* This CPU has no IRQs yet, no need to look further. */
+ if (!pool->irqs_per_cpu[cpu]) {
+ best_cpu = cpu;
+ break;
+ }
+ if (best_cpu < 0)
+ best_cpu = cpu;
+ if (pool->irqs_per_cpu[cpu] < pool->irqs_per_cpu[best_cpu])
+ best_cpu = cpu;
+ }
+
+ if (best_cpu < 0) {
+ dev_err(pool->dev->device,
+ "no online CPU in affinity mask (%*pbl)\n",
+ cpumask_pr_args(req_mask));
+ best_cpu = cpumask_first(cpu_online_mask);
+ }
+ pool->irqs_per_cpu[best_cpu]++;
+
+ return best_cpu;
+}
+
+/* Create a new IRQ from the pool and bind it to the requested mask. */
+static struct zxdh_irq *zxdh_irq_pool_request_irq(struct zxdh_irq_pool *pool,
+ const struct cpumask *req_mask)
+{
+ const struct cpumask *affinity = req_mask;
+ cpumask_var_t auto_mask;
+ struct zxdh_irq *irq;
+ u32 irq_index;
+ int err;
+
+ if (!zalloc_cpumask_var(&auto_mask, GFP_KERNEL))
+ return ERR_PTR(-ENOMEM);
+
+ err = xa_alloc(&pool->irqs, &irq_index, NULL, pool->xa_num_irqs,
+ GFP_KERNEL);
+ if (err) {
+ if (err == -EBUSY)
+ err = -EUSERS;
+ goto err_xa;
+ }
+
+ if (cpumask_weight(req_mask) > 1) {
+ /* Bind to the least loaded CPU of the request. */
+ cpumask_set_cpu(zxdh_cpu_get_least_loaded(pool, req_mask),
+ auto_mask);
+ affinity = auto_mask;
+ } else {
+ zxdh_cpu_get(pool, cpumask_first(req_mask));
+ }
+
+ irq = zxdh_irq_alloc(pool, irq_index, affinity);
+ if (IS_ERR(irq))
+ goto err_alloc;
+
+ free_cpumask_var(auto_mask);
+
+ return irq;
+
+err_alloc:
+ /* Undo the per-CPU accounting done above. */
+ zxdh_cpu_put(pool, cpumask_first(affinity));
+ xa_erase(&pool->irqs, irq_index);
+err_xa:
+ free_cpumask_var(auto_mask);
+ return err ? ERR_PTR(err) : irq;
+}
+
+/* Look for the IRQ with the smallest refcount whose affinity mask is a
+ * subset of the requested mask.
+ */
+static struct zxdh_irq *zxdh_irq_pool_find_least_loaded(struct zxdh_irq_pool *pool,
+ const struct cpumask *req_mask)
+{
+ struct zxdh_irq *irq = NULL;
+ struct zxdh_irq *iter;
+ unsigned long index;
+ int iter_refcount;
+
+ lockdep_assert_held(&pool->lock);
+ xa_for_each_range(&pool->irqs, index, iter,
+ pool->xa_num_irqs.min, pool->xa_num_irqs.max) {
+ iter_refcount = refcount_read(&iter->refcount.refcount);
+
+ /* Skip IRQs whose mask is not a subset of the request. */
+ if (!cpumask_subset(iter->mask, req_mask))
+ continue;
+ /* IRQ below the minimum threshold found, take it. */
+ if (iter_refcount < pool->min_threshold)
+ return iter;
+ if (!irq ||
+ iter_refcount < refcount_read(&irq->refcount.refcount))
+ irq = iter;
+ }
+
+ return irq;
+}
+
+/* Request an IRQ for the given mask: share the least loaded existing
+ * matching IRQ once it passes the pool minimum threshold, otherwise
+ * create a new one.
+ */
+static struct zxdh_irq *zxdh_irq_affinity_request(struct zxdh_irq_pool *pool,
+ const struct cpumask *req_mask)
+{
+ struct zxdh_irq *least_loaded_irq;
+ struct zxdh_irq *new_irq;
+
+ mutex_lock(&pool->lock);
+
+ least_loaded_irq = zxdh_irq_pool_find_least_loaded(pool, req_mask);
+ if (least_loaded_irq &&
+ refcount_read(&least_loaded_irq->refcount.refcount) < pool->min_threshold)
+ goto out;
+
+ /* No IRQ below the minimum threshold, try to create a new one. */
+ new_irq = zxdh_irq_pool_request_irq(pool, req_mask);
+ if (IS_ERR(new_irq)) {
+ if (!least_loaded_irq) {
+ mutex_unlock(&pool->lock);
+ return new_irq;
+ }
+ /* Could not create a new IRQ for the requested affinity,
+ * share the existing one.
+ */
+ dev_warn(pool->dev->device,
+ "irq pool %s exhausted, sharing irqs\n",
+ pool->name);
+ goto out;
+ }
+
+ least_loaded_irq = new_irq;
+ goto unlock;
+
+out:
+ /* Holding pool->lock keeps the IRQ alive, so this cannot fail. */
+ zxdh_irq_get(least_loaded_irq);
+ if (refcount_read(&least_loaded_irq->refcount.refcount) > pool->max_threshold)
+ dev_warn(pool->dev->device,
+ "irq %u overloaded, pool %s, %u refs on this irq\n",
+ pci_irq_vector(pool->dev->pdev,
+ least_loaded_irq->index),
+ pool->name,
+ refcount_read(&least_loaded_irq->refcount.refcount) /
+ ZXDH_EQ_REFS_PER_IRQ);
+unlock:
+ mutex_unlock(&pool->lock);
+
+ return least_loaded_irq;
+}
+
+/* Request an IRQ from the pool, spread over the online CPUs. */
+struct zxdh_irq *zxdh_get_irq_of_pool(struct zxdh_irq_pool *pool)
+{
+ cpumask_var_t req_mask;
+ struct zxdh_irq *irq;
+
+ if (!zalloc_cpumask_var(&req_mask, GFP_KERNEL))
+ return ERR_PTR(-ENOMEM);
+ cpumask_copy(req_mask, cpu_online_mask);
+
+ irq = zxdh_irq_affinity_request(pool, req_mask);
+
+ free_cpumask_var(req_mask);
+
+ return irq;
+}
+
+struct zxdh_irq_pool *zxdh_irq_pool_alloc(struct zxdh_core_dev *zxdh_dev,
+ int start, int size, const char *name,
+ u32 min_threshold, u32 max_threshold)
+{
+ struct zxdh_irq_pool *pool;
+ u16 *irqs_per_cpu;
+
+ irqs_per_cpu = kcalloc(nr_cpu_ids, sizeof(*irqs_per_cpu), GFP_KERNEL);
+ if (!irqs_per_cpu)
+ return ERR_PTR(-ENOMEM);
+
+ pool = kvzalloc_obj(*pool, GFP_KERNEL);
+ if (!pool) {
+ kfree(irqs_per_cpu);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ pool->dev = zxdh_dev;
+ pool->irqs_per_cpu = irqs_per_cpu;
+ mutex_init(&pool->lock);
+ xa_init_flags(&pool->irqs, XA_FLAGS_ALLOC);
+ pool->xa_num_irqs.min = start;
+ pool->xa_num_irqs.max = start + size - 1;
+ snprintf(pool->name, ZXDH_MAX_IRQ_NAME, "%s", name);
+
+ /* Thresholds are expressed in queue references, two per IRQ. */
+ pool->min_threshold = min_threshold * ZXDH_EQ_REFS_PER_IRQ;
+ pool->max_threshold = max_threshold * ZXDH_EQ_REFS_PER_IRQ;
+
+ return pool;
+}
+
+void zxdh_irq_pool_free(struct zxdh_irq_pool *pool)
+{
+ struct zxdh_irq *irq;
+ unsigned long index;
+ u32 cpu;
+
+ /* On a fast teardown the table may still hold IRQs; release
+ * whatever is left.
+ */
+ mutex_lock(&pool->lock);
+ xa_for_each(&pool->irqs, index, irq)
+ zxdh_irq_release(&irq->refcount);
+ mutex_unlock(&pool->lock);
+ xa_destroy(&pool->irqs);
+ mutex_destroy(&pool->lock);
+
+ if (pool->irqs_per_cpu) {
+ for_each_online_cpu(cpu)
+ WARN_ON(pool->irqs_per_cpu[cpu]);
+ kfree(pool->irqs_per_cpu);
+ }
+
+ kvfree(pool);
+}
diff --git a/drivers/net/ethernet/zte/dinghai/zxdh_irq.h b/drivers/net/ethernet/zte/dinghai/zxdh_irq.h
new file mode 100644
index 000000000000..89f46d7b11c9
--- /dev/null
+++ b/drivers/net/ethernet/zte/dinghai/zxdh_irq.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * ZTE DingHai Ethernet driver - IRQ pool management
+ * Copyright (c) 2022-2026, ZTE Corporation.
+ */
+
+#ifndef __ZXDH_IRQ_H__
+#define __ZXDH_IRQ_H__
+
+#include <linux/cpumask.h>
+#include <linux/kref.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/types.h>
+#include <linux/xarray.h>
+
+struct zxdh_core_dev;
+
+/* Number of MSI-X vectors per purpose. The layout of the vector space
+ * is a hardware interface:
+ * [0, 8) async event queues
+ * [8, 14) reserved for RDMA (unused for now)
+ * [14, 78) vq rx/tx queue pairs
+ */
+#define ZXDH_ASYNC_CHANNELS_NUM 8
+#define ZXDH_RDMA_CHANNELS_NUM 6
+#define ZXDH_VQS_CHANNELS_NUM 64
+
+#define ZXDH_MAX_IRQ_NAME 100
+#define ZXDH_EQ_REFS_PER_IRQ 2
+
+/* Reference counted interrupt. Event queues share an IRQ through the
+ * notifier chain, which is fired from hard IRQ context.
+ */
+struct zxdh_irq {
+ struct atomic_notifier_head nh;
+ cpumask_var_t mask; /* interrupt affinity */
+ char name[ZXDH_MAX_IRQ_NAME];
+ struct zxdh_irq_pool *pool;
+ u32 index; /* vector index */
+ int irqn; /* Linux IRQ number */
+ struct kref refcount;
+};
+
+/* Pool of vectors dedicated to one purpose, identified by the
+ * [xa_num_irqs.min, xa_num_irqs.max] vector range.
+ */
+struct zxdh_irq_pool {
+ char name[ZXDH_MAX_IRQ_NAME];
+ struct xa_limit xa_num_irqs;
+ struct mutex lock; /* serializes IRQ creation */
+ struct xarray irqs;
+ u32 min_threshold; /* in queue references */
+ u32 max_threshold; /* in queue references */
+ u16 *irqs_per_cpu; /* number of IRQs bound per CPU */
+ struct zxdh_core_dev *dev;
+};
+
+struct zxdh_irq_table {
+ void *priv;
+};
+
+struct zxdh_irq *zxdh_get_irq_of_pool(struct zxdh_irq_pool *pool);
+int zxdh_irq_attach_nb(struct zxdh_irq *irq, struct notifier_block *nb);
+int zxdh_irq_detach_nb(struct zxdh_irq *irq, struct notifier_block *nb);
+void zxdh_irqs_release_vectors(struct zxdh_irq **irqs, int nirqs);
+struct zxdh_irq_pool *zxdh_irq_pool_alloc(struct zxdh_core_dev *zxdh_dev,
+ int start, int size, const char *name,
+ u32 min_threshold, u32 max_threshold);
+void zxdh_irq_pool_free(struct zxdh_irq_pool *pool);
+
+#endif /* __ZXDH_IRQ_H__ */
--
2.27.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v2 3/3] dinghai: add async event queue for firmware notifications
2026-09-11 3:56 [PATCH net-next v2 0/3] dinghai: firmware handshake, MSI-X pools and async event queues han.junyang
2026-09-11 3:59 ` [PATCH net-next v2 1/3] dinghai: add firmware version check and RISC-V readiness polling han.junyang
2026-09-11 4:02 ` [PATCH net-next v2 2/3] dinghai: add MSI-X interrupt pools han.junyang
@ 2026-09-11 4:05 ` han.junyang
2026-09-14 7:20 ` Simon Horman
2 siblings, 1 reply; 7+ messages in thread
From: han.junyang @ 2026-09-11 4:05 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, horms
Cc: linux-kernel, netdev, han.junyang, ran.ming, han.chengfei, zhang.yanze
From: Junyang Han <han.junyang@zte.com.cn>
Add the event queue table and the first async event queue, fed by the
RISC-V management core of the firmware. The queue owns one MSI-X
vector taken from the async pool and, on interrupt, reads the event_id
from the PF receive sub-channel of the BAR message area, maps it to
an event type and fans it out through the per-event-type notifier
chains of the table. The PF probe creates and tears down the queue
through the existing IRQ pool interfaces.
The BAR message channel handshake that tells the firmware which
vectors to signal comes with the BAR message subsystem in a later
series; the PF side wired here is live as soon as that channel is
enabled. Subscribers for the RISCV_READY event register on the
notifier chains in later series as well.
Signed-off-by: Junyang Han <han.junyang@zte.com.cn>
---
drivers/net/ethernet/zte/dinghai/Makefile | 2 +-
drivers/net/ethernet/zte/dinghai/en_pf.c | 31 +++--
drivers/net/ethernet/zte/dinghai/en_pf.h | 2 +
drivers/net/ethernet/zte/dinghai/zxdh_eq.c | 145 +++++++++++++++++++++
drivers/net/ethernet/zte/dinghai/zxdh_eq.h | 61 +++++++++
5 files changed, 232 insertions(+), 9 deletions(-)
create mode 100644 drivers/net/ethernet/zte/dinghai/zxdh_eq.c
create mode 100644 drivers/net/ethernet/zte/dinghai/zxdh_eq.h
diff --git a/drivers/net/ethernet/zte/dinghai/Makefile b/drivers/net/ethernet/zte/dinghai/Makefile
index f4a8f9480291..2dc6d68cc0e1 100644
--- a/drivers/net/ethernet/zte/dinghai/Makefile
+++ b/drivers/net/ethernet/zte/dinghai/Makefile
@@ -4,4 +4,4 @@
#
obj-$(CONFIG_DINGHAI_PF) += dinghai10e.o
-dinghai10e-y := en_pf.o zxdh_irq.o
+dinghai10e-y := en_pf.o zxdh_irq.o zxdh_eq.o
diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c
index 923d688e7ec5..fe3924b890d1 100644
--- a/drivers/net/ethernet/zte/dinghai/en_pf.c
+++ b/drivers/net/ethernet/zte/dinghai/en_pf.c
@@ -492,7 +492,8 @@ static int zxdh_pf_irq_pools_init(struct zxdh_core_dev *zxdh_dev)
static void zxdh_pf_irq_pools_destroy(struct zxdh_pf_irq_table *pf_irq_table)
{
- zxdh_irq_pool_free(pf_irq_table->async_pool);
+ if (pf_irq_table->async_pool)
+ zxdh_irq_pool_free(pf_irq_table->async_pool);
}
int zxdh_pf_irq_table_init(struct zxdh_core_dev *zxdh_dev)
@@ -536,18 +537,17 @@ void zxdh_pf_irq_table_destroy(struct zxdh_core_dev *zxdh_dev)
{
struct zxdh_irq_table *table = &zxdh_dev->irq_table;
- zxdh_pf_irq_pools_destroy(table->priv);
+ if (table->priv)
+ zxdh_pf_irq_pools_destroy(table->priv);
kvfree(table->priv);
+ table->priv = NULL;
pci_free_irq_vectors(zxdh_dev->pdev);
}
+/* Fetch a vector from the async pool for one async event queue. */
struct zxdh_irq *zxdh_pf_async_irq_request(struct zxdh_core_dev *zxdh_dev)
{
- struct zxdh_irq_table *table = &zxdh_dev->irq_table;
- struct zxdh_pf_irq_table *pf_irq_table = table->priv;
-
- if (!pf_irq_table->async_pool)
- return NULL;
+ struct zxdh_pf_irq_table *pf_irq_table = zxdh_dev->irq_table.priv;
return zxdh_get_irq_of_pool(pf_irq_table->async_pool);
}
@@ -612,12 +612,26 @@ static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto err_irq_table;
}
+ ret = zxdh_pf_eq_table_init(zxdh_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "zxdh_pf_eq_table_init failed: %d\n", ret);
+ goto err_eq_table;
+ }
+
+ ret = zxdh_pf_eq_table_create(zxdh_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "zxdh_pf_eq_table_create failed: %d\n", ret);
+ goto err_eq_table;
+ }
+
devlink_register(devlink);
return 0;
+err_eq_table:
+ zxdh_pf_eq_table_destroy(zxdh_dev);
err_irq_table:
- kvfree(zxdh_dev->irq_table.priv);
+ zxdh_pf_irq_table_destroy(zxdh_dev);
err_cfg_init:
zxdh_pf_pci_close(zxdh_dev);
err_pci_init:
@@ -633,6 +647,7 @@ static void zxdh_pf_remove(struct pci_dev *pdev)
struct devlink *devlink = priv_to_devlink(zxdh_dev);
devlink_unregister(devlink);
+ zxdh_pf_eq_table_destroy(zxdh_dev);
zxdh_pf_irq_table_destroy(zxdh_dev);
zxdh_pf_modern_cfg_uninit(zxdh_dev);
zxdh_pf_pci_close(zxdh_dev);
diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.h b/drivers/net/ethernet/zte/dinghai/en_pf.h
index f60a237c61c4..894a05a09d96 100644
--- a/drivers/net/ethernet/zte/dinghai/en_pf.h
+++ b/drivers/net/ethernet/zte/dinghai/en_pf.h
@@ -9,6 +9,7 @@
#include <linux/types.h>
+#include "zxdh_eq.h"
#include "zxdh_irq.h"
#define ZXDH_PF_VENDOR_ID 0x1cf2
@@ -81,6 +82,7 @@ struct zxdh_core_dev {
struct pci_dev *pdev;
struct devlink *devlink;
struct zxdh_irq_table irq_table;
+ struct zxdh_eq_table eq_table;
void *priv;
};
diff --git a/drivers/net/ethernet/zte/dinghai/zxdh_eq.c b/drivers/net/ethernet/zte/dinghai/zxdh_eq.c
new file mode 100644
index 000000000000..f1d7eeec1ae4
--- /dev/null
+++ b/drivers/net/ethernet/zte/dinghai/zxdh_eq.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ZTE DingHai Ethernet driver - event queue plumbing
+ * Copyright (c) 2022-2026, ZTE Corporation.
+ *
+ * Async event queues are the delivery path for firmware notifications:
+ * a queue owns one MSI-X vector of the async pool and fans interrupts
+ * out to the per-event-type notifier chains of the event queue table.
+ * The BAR message channel handshake that tells the firmware which
+ * vectors to signal comes with the BAR message subsystem in a later
+ * series; the PF side wired here is live as soon as that channel is
+ * enabled.
+ */
+
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+
+#include "en_pf.h"
+#include "zxdh_eq.h"
+#include "zxdh_irq.h"
+
+/* PF side of the event queue table: one async EQ fed by the RISC-V
+ * management core.
+ */
+struct zxdh_pf_eq_table {
+ struct zxdh_eq_async riscv_eq;
+};
+
+/* Fetch the event_id the RISC-V core posted in the PF receive
+ * sub-channel of the BAR message area. The field lives at byte offset
+ * 2 of the 12-byte message header, so a single 32-bit read of the
+ * sub-channel start covers it.
+ */
+static u16 zxdh_eq_event_id_get(struct zxdh_core_dev *zxdh_dev)
+{
+ struct zxdh_pf_dev *pf_dev = zxdh_dev->priv;
+ void __iomem *subchan;
+
+ subchan = pf_dev->pci_ioremap_addr[0] + ZXDH_BAR_MSG_SUBCHAN_RECV;
+
+ return ioread32(subchan) >> 16;
+}
+
+static enum zxdh_eq_event_type zxdh_eq_event_type_get(u16 event_id)
+{
+ if (event_id == ZXDH_MSG_MODULE_RISC_READY)
+ return ZXDH_EQ_EVENT_TYPE_RISCV_READY;
+
+ /* Only the RISC-V ready notification is dispatched in this
+ * series; the remaining firmware events arrive with the BAR
+ * message subsystem.
+ */
+ return ZXDH_EQ_EVENT_TYPE_MAX;
+}
+
+static int zxdh_eq_async_riscv_int(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct zxdh_eq_async *eq = container_of(nb, struct zxdh_eq_async, irq_nb);
+ struct zxdh_core_dev *zxdh_dev = eq->priv;
+ enum zxdh_eq_event_type event_type;
+
+ event_type = zxdh_eq_event_type_get(zxdh_eq_event_id_get(zxdh_dev));
+ if (event_type == ZXDH_EQ_EVENT_TYPE_MAX)
+ return NOTIFY_DONE;
+
+ atomic_notifier_call_chain(&zxdh_dev->eq_table.nh[event_type],
+ event_type, NULL);
+
+ return NOTIFY_OK;
+}
+
+int zxdh_pf_eq_table_init(struct zxdh_core_dev *zxdh_dev)
+{
+ struct zxdh_eq_table *table = &zxdh_dev->eq_table;
+ struct zxdh_pf_eq_table *priv;
+ int i;
+
+ priv = kvzalloc_obj(*priv, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ table->priv = priv;
+
+ mutex_init(&table->lock);
+ for (i = 0; i < ZXDH_EQ_EVENT_TYPE_MAX; i++)
+ ATOMIC_INIT_NOTIFIER_HEAD(&table->nh[i]);
+
+ return 0;
+}
+
+int zxdh_pf_eq_table_create(struct zxdh_core_dev *zxdh_dev)
+{
+ struct zxdh_pf_eq_table *pf_eq_table = zxdh_dev->eq_table.priv;
+ struct zxdh_eq_table *table = &zxdh_dev->eq_table;
+ struct zxdh_eq_async *eq = &pf_eq_table->riscv_eq;
+ int err;
+
+ mutex_lock(&table->lock);
+
+ eq->priv = zxdh_dev;
+ eq->irq = zxdh_pf_async_irq_request(zxdh_dev);
+ if (IS_ERR(eq->irq)) {
+ err = PTR_ERR(eq->irq);
+ goto unlock;
+ }
+
+ eq->irq_nb.notifier_call = zxdh_eq_async_riscv_int;
+ err = zxdh_irq_attach_nb(eq->irq, &eq->irq_nb);
+ if (err) {
+ zxdh_irqs_release_vectors(&eq->irq, 1);
+ eq->irq = NULL;
+ }
+
+unlock:
+ mutex_unlock(&table->lock);
+
+ return err;
+}
+
+void zxdh_pf_eq_table_destroy(struct zxdh_core_dev *zxdh_dev)
+{
+ struct zxdh_pf_eq_table *pf_eq_table = zxdh_dev->eq_table.priv;
+ struct zxdh_eq_table *table = &zxdh_dev->eq_table;
+ struct zxdh_eq_async *eq;
+
+ if (!pf_eq_table)
+ return;
+
+ eq = &pf_eq_table->riscv_eq;
+
+ mutex_lock(&table->lock);
+ if (eq->irq) {
+ zxdh_irq_detach_nb(eq->irq, &eq->irq_nb);
+ zxdh_irqs_release_vectors(&eq->irq, 1);
+ eq->irq = NULL;
+ }
+ mutex_unlock(&table->lock);
+
+ mutex_destroy(&table->lock);
+ kvfree(table->priv);
+ table->priv = NULL;
+}
diff --git a/drivers/net/ethernet/zte/dinghai/zxdh_eq.h b/drivers/net/ethernet/zte/dinghai/zxdh_eq.h
new file mode 100644
index 000000000000..4965ca15912a
--- /dev/null
+++ b/drivers/net/ethernet/zte/dinghai/zxdh_eq.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * ZTE DingHai Ethernet driver - event queue abstraction
+ * Copyright (c) 2022-2026, ZTE Corporation.
+ */
+
+#ifndef __ZXDH_EQ_H__
+#define __ZXDH_EQ_H__
+
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+
+struct zxdh_core_dev;
+struct zxdh_irq;
+
+/* BAR message area: shared-memory mailbox between the driver and the
+ * firmware, split into sub-channels of 2048 bytes. Notifications from
+ * the RISC-V management core are posted to the PF receive sub-channel,
+ * one interval above the area base.
+ */
+#define ZXDH_BAR_MSG_OFFSET 0x2000
+#define ZXDH_BAR_MSG_SUBCHAN_INTERVAL 2048
+#define ZXDH_BAR_MSG_SUBCHAN_RECV (ZXDH_BAR_MSG_OFFSET + \
+ ZXDH_BAR_MSG_SUBCHAN_INTERVAL)
+
+/* event_id of the "RISC-V core ready" firmware notification, carried
+ * at byte offset 2 of the BAR message header.
+ */
+#define ZXDH_MSG_MODULE_RISC_READY 13
+
+/* Event types the upper layers can subscribe to over the per-type
+ * notifier chains of the event queue table. The full catalogue lives
+ * in the firmware; only the ones dispatched in this series are named.
+ */
+enum zxdh_eq_event_type {
+ ZXDH_EQ_EVENT_TYPE_RISCV_READY = 0,
+ ZXDH_EQ_EVENT_TYPE_MAX,
+};
+
+/* Asynchronous event queue delivering firmware notifications to the
+ * driver without a request/reply handshake.
+ */
+struct zxdh_eq_async {
+ struct zxdh_irq *irq;
+ struct notifier_block irq_nb;
+ void *priv;
+};
+
+struct zxdh_eq_table {
+ /* Notifier chains fanned out by event type. */
+ struct atomic_notifier_head nh[ZXDH_EQ_EVENT_TYPE_MAX];
+ /* Serializes async EQ create/destroy. */
+ struct mutex lock;
+ void *priv;
+};
+
+int zxdh_pf_eq_table_init(struct zxdh_core_dev *zxdh_dev);
+int zxdh_pf_eq_table_create(struct zxdh_core_dev *zxdh_dev);
+void zxdh_pf_eq_table_destroy(struct zxdh_core_dev *zxdh_dev);
+
+#endif /* __ZXDH_EQ_H__ */
--
2.27.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net-next v2 1/3] dinghai: add firmware version check and RISC-V readiness polling
2026-09-11 3:59 ` [PATCH net-next v2 1/3] dinghai: add firmware version check and RISC-V readiness polling han.junyang
@ 2026-09-14 7:20 ` Simon Horman
0 siblings, 0 replies; 7+ messages in thread
From: Simon Horman @ 2026-09-14 7:20 UTC (permalink / raw)
To: han.junyang
Cc: Simon Horman, andrew+netdev, davem, edumazet, kuba, pabeni,
linux-kernel, netdev, ran.ming, han.chengfei, zhang.yanze
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev/#/patchset/20260911115929435mXJzcTF08xlUAdjnT8qqm@zte.com.cn?part=1
---
dinghai: add firmware version check and RISC-V readiness polling
This patch introduces firmware version compatibility checks and RISC-V health
buffer readiness polling during device probe. It reads version information from
a fixed offset in BAR 0 and waits for the firmware's RISC-V management core
to set a power-on flag.
> diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c
> index 86d4374088203..d257d8e9b612e 100644
> --- a/drivers/net/ethernet/zte/dinghai/en_pf.c
> +++ b/drivers/net/ethernet/zte/dinghai/en_pf.c
[ ... ]
> @@ -369,6 +371,99 @@ int zxdh_pf_modern_cfg_init(struct zxdh_core_dev *zxdh_dev)
> return ret;
> }
>
> +/* Read the firmware version block and verify the driver/firmware
> + * version contract.
> + */
> +static int zxdh_pf_fw_compat_check(struct zxdh_core_dev *zxdh_dev)
> +{
> + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv;
> + struct zxdh_fw_compat __iomem *compat;
> + struct zxdh_fw_compat *fw_compat;
> + u32 erased;
> +
> + fw_compat = &pf_dev->fw_compat;
> + compat = pf_dev->pci_ioremap_addr[0] + ZXDH_FW_COMPAT_OFFSET;
> +
> + /* The region reads as all ones until the firmware populates it at
> + * the end of its boot; allow up to 200 s for a cold boot.
> + */
> + readx_poll_timeout(ioread32, compat, erased, erased != 0xffffffffU,
> + USEC_PER_SEC,
> + ZXDH_FW_COMPAT_TIMEOUT_SEC * USEC_PER_SEC);
[Severity: High]
Is it intended to ignore the return value of readx_poll_timeout() here?
When running on older firmware that does not populate the compatibility
region, the memory remains 0xffffffff, which causes the driver to spin
synchronously for the full 200 seconds during device initialization.
> +
> + /* Firmware predating the compatibility region keeps the erased
> + * pattern, which fails the module id check below and defers the
> + * decision to the readiness wait.
> + */
> + fw_compat->module_id = ioread8(&compat->module_id);
> + fw_compat->major = ioread8(&compat->major);
> + fw_compat->fw_minor = ioread8(&compat->fw_minor);
> + fw_compat->drv_minor = ioread8(&compat->drv_minor);
> + fw_compat->patch = ioread16(&compat->patch);
[Severity: Medium]
Could there be a race condition between the firmware writing the
compatibility block and the driver reading it?
The readx_poll_timeout() loop polls the first 4 bytes using ioread32() and
proceeds as soon as they change from 0xffffffff. If the firmware writes
the data sequentially, the patch field might still be 0xFFFF when read by
the driver immediately afterward, potentially triggering false version
mismatches.
[Severity: High]
Can an erased compatibility region cause the probe to fail on older
firmware, despite the intended backward compatibility?
When the region is erased, fw_compat->patch is populated with 0xFFFF.
Later in zxdh_pf_wait_riscv_ready(), the driver attempts to skip the wait:
if (health_version != 1 &&
pf_dev->fw_compat.patch < ZXDH_HPIRQ_PATCH)
return 0;
Since 0xFFFF < 4 evaluates to false, the driver falls through, waits an
additional 40 seconds, and fails the probe.
[ ... ]
> @@ -405,6 +500,18 @@ static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> goto err_cfg_init;
> }
>
> + ret = zxdh_pf_fw_compat_check(zxdh_dev);
> + if (ret) {
> + dev_err(&pdev->dev, "zxdh_pf_fw_compat_check failed: %d\n", ret);
> + goto err_cfg_init;
> + }
> +
> + ret = zxdh_pf_wait_riscv_ready(zxdh_dev);
> + if (ret) {
> + dev_err(&pdev->dev, "zxdh_pf_wait_riscv_ready failed: %d\n", ret);
> + goto err_cfg_init;
> + }
[Severity: High]
Do these error paths leak the PCI I/O regions acquired earlier?
The error paths jump to err_cfg_init when these checks fail, which only
calls zxdh_pf_pci_close(). However, the mappings created in
zxdh_pf_modern_cfg_init() require a call to zxdh_pf_modern_cfg_uninit() to
be properly cleaned up.
> +
> devlink_register(devlink);
>
> return 0;
> diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.h b/drivers/net/ethernet/zte/dinghai/en_pf.h
> index 7373dee8d1a9f..bc275ddd75b81 100644
> --- a/drivers/net/ethernet/zte/dinghai/en_pf.h
> +++ b/drivers/net/ethernet/zte/dinghai/en_pf.h
[ ... ]
> @@ -29,6 +29,51 @@
> #define ZXDH_PF_ALIGN2 2
> #define ZXDH_PF_MAP_MINLEN2 2
>
> +/* Fixed offsets of the firmware interface regions within BAR 0. */
> +#define ZXDH_RISCV_HB_OFFSET 0x5300
> +#define ZXDH_FW_COMPAT_OFFSET 0x5400
> +
> +/* Driver/firmware version contract. The firmware publishes its side of
> + * the contract in the region at ZXDH_FW_COMPAT_OFFSET.
> + */
> +#define ZXDH_MODULE_ID 1
> +#define ZXDH_MAJOR 1
> +#define ZXDH_FW_MINOR 0
> +#define ZXDH_DRV_MINOR 0
> +/* Firmware patch level that introduced the health buffer protocol. */
> +#define ZXDH_HPIRQ_PATCH 4
> +
> +#define ZXDH_FW_COMPAT_TIMEOUT_SEC 200
> +#define ZXDH_RISCV_READY_TIMEOUT_SEC 40
> +
> +/* Firmware version compatibility block at ZXDH_FW_COMPAT_OFFSET.
> + * Fields are read through ioread*(), which converts from little-endian.
> + */
> +struct zxdh_fw_compat {
> + u8 module_id;
> + u8 major;
> + s8 fw_minor;
[Severity: Medium]
Will firmware minor versions 128 or greater be improperly rejected due to
the signedness of fw_minor?
When fw_minor is read into an s8, values 128 or greater will wrap to
negative numbers. The validation check in zxdh_pf_fw_compat_check():
if (fw_compat->fw_minor < ZXDH_FW_MINOR) {
...
(where ZXDH_FW_MINOR is 0) evaluates to true for these negative values,
incorrectly triggering a version incompatibility error.
> + u8 drv_minor;
> + u16 patch;
> + u16 rsv;
> +} __packed;
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net-next v2 2/3] dinghai: add MSI-X interrupt pools
2026-09-11 4:02 ` [PATCH net-next v2 2/3] dinghai: add MSI-X interrupt pools han.junyang
@ 2026-09-14 7:20 ` Simon Horman
0 siblings, 0 replies; 7+ messages in thread
From: Simon Horman @ 2026-09-14 7:20 UTC (permalink / raw)
To: han.junyang
Cc: Simon Horman, andrew+netdev, davem, edumazet, kuba, pabeni,
linux-kernel, netdev, ran.ming, han.chengfei, zhang.yanze
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev/#/patchset/20260911115929435mXJzcTF08xlUAdjnT8qqm@zte.com.cn?part=2
---
dinghai: add MSI-X interrupt pools
This commit introduces a driver for ZTE DingHai Ethernet, specifically
allocating the fixed MSI-X vector layout of the device. It manages the
vectors in per-purpose pools for async event queues and vq queue pairs.
[Severity: Medium]
The commit message states:
manage the vectors in per-purpose pools: one pool for the async event queues
and one for the vq queue pairs, with a range reserved for RDMA in between.
Was the VQ pool allocation omitted from this patch by mistake? Looking at
zxdh_pf_irq_pools_init(), only the async pool is allocated.
> diff --git a/drivers/net/ethernet/zte/dinghai/zxdh_irq.c b/drivers/net/ethernet/zte/dinghai/zxdh_irq.c
> new file mode 100644
> index 0000000000000..437344329c7e8
> --- /dev/null
> +++ b/drivers/net/ethernet/zte/dinghai/zxdh_irq.c
[ ... ]
> +static void zxdh_irq_release(struct kref *kref)
> +{
> + struct zxdh_irq *irq = container_of(kref, struct zxdh_irq, refcount);
> + struct zxdh_irq_pool *pool = irq->pool;
> +
> + lockdep_assert_held(&pool->lock);
> + xa_erase(&pool->irqs, irq->index);
> + /* free_irq() requires the affinity hint to be cleared before it is
> + * called; the asymmetry with the set path in zxdh_irq_alloc() is
> + * intentional.
> + */
> + irq_update_affinity_hint(irq->irqn, NULL);
> + free_cpumask_var(irq->mask);
> + free_irq(irq->irqn, irq);
> + kfree(irq);
> +}
[Severity: High]
Does this code leak the logical counters in pool->irqs_per_cpu?
When an IRQ is allocated, zxdh_irq_pool_request_irq() increments
pool->irqs_per_cpu[cpu]. It seems zxdh_irq_release() misses a call to
zxdh_cpu_put(), which will permanently skew load balancing and trigger the
WARN_ON(pool->irqs_per_cpu[cpu]) during zxdh_irq_pool_free().
[ ... ]
> +static struct zxdh_irq *zxdh_irq_alloc(struct zxdh_irq_pool *pool, int vecidx,
> + const struct cpumask *affinity)
> +{
> + struct zxdh_core_dev *zxdh_dev = pool->dev;
> + struct zxdh_irq *irq;
> + int err;
> + int cpu;
> +
> + irq = kzalloc_obj(*irq, GFP_KERNEL);
> + if (!irq)
> + return ERR_PTR(-ENOMEM);
> +
> + irq->pool = pool;
> + irq->irqn = pci_irq_vector(zxdh_dev->pdev, vecidx);
> + if (irq->irqn < 0) {
> + err = irq->irqn;
> + goto err_irqn;
> + }
> +
> + ATOMIC_INIT_NOTIFIER_HEAD(&irq->nh);
> + snprintf(irq->name, ZXDH_MAX_IRQ_NAME, "async_%d@pci:%s", vecidx,
> + pci_name(zxdh_dev->pdev));
[Severity: Low]
Is this hardcoded name prefix intentional?
This ignores the configured pool->name. If this generic library is reused for
other pools, such as the VQ pool, they will all show up as "async_" in
/proc/interrupts.
> +
> + err = request_irq(irq->irqn, zxdh_irq_int_handler, 0, irq->name, irq);
> + if (err) {
> + dev_err(zxdh_dev->device, "request_irq failed: %d\n", err);
> + goto err_irqn;
> + }
> +
> + if (!zalloc_cpumask_var(&irq->mask, GFP_KERNEL)) {
> + dev_err(zxdh_dev->device, "zalloc_cpumask_var failed\n");
> + err = -ENOMEM;
> + goto err_cpumask;
> + }
> +
> + if (affinity) {
> + cpumask_copy(irq->mask, affinity);
> + } else {
> + /* No preference requested; spread over all online CPUs. */
> + for_each_online_cpu(cpu)
> + cpumask_set_cpu(cpu, irq->mask);
> + }
> + irq_update_affinity_hint(irq->irqn, irq->mask);
[Severity: Medium]
Will this actually configure the SMP hardware routing for the IRQ?
The driver uses irq_update_affinity_hint() which only sets a userspace hint
via procfs. Should this be irq_set_affinity_and_hint() instead to actually
enforce the intended hardware IRQ load balancing?
[ ... ]
> +static struct zxdh_irq *zxdh_irq_pool_request_irq(struct zxdh_irq_pool *pool,
> + const struct cpumask *req_mask)
> +{
> + const struct cpumask *affinity = req_mask;
> + cpumask_var_t auto_mask;
> + struct zxdh_irq *irq;
> + u32 irq_index;
> + int err;
> +
> + if (!zalloc_cpumask_var(&auto_mask, GFP_KERNEL))
> + return ERR_PTR(-ENOMEM);
> +
> + err = xa_alloc(&pool->irqs, &irq_index, NULL, pool->xa_num_irqs,
> + GFP_KERNEL);
[Severity: High]
Will this xa_alloc() call fail unconditionally?
The XArray API strictly forbids NULL entries for allocation because NULL
represents an unallocated slot. Passing NULL as the entry triggers a
WARN_ON_ONCE(!entry) and returns -EINVAL.
[ ... ]
> +void zxdh_irq_pool_free(struct zxdh_irq_pool *pool)
> +{
> + struct zxdh_irq *irq;
> + unsigned long index;
> + u32 cpu;
> +
> + /* On a fast teardown the table may still hold IRQs; release
> + * whatever is left.
> + */
> + mutex_lock(&pool->lock);
> + xa_for_each(&pool->irqs, index, irq)
> + zxdh_irq_release(&irq->refcount);
> + mutex_unlock(&pool->lock);
[Severity: High]
Can this manual iteration bypass the reference counting and cause a
use-after-free?
If zxdh_irq_pool_free() executes while active consumers still hold an IRQ
reference during a fast teardown, it will forcefully execute
zxdh_irq_release() on the irq object. When the consumer eventually calls
zxdh_irq_put() and accesses the freed irq and its freed pool->lock, will
this trigger a use-after-free?
[ ... ]
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net-next v2 3/3] dinghai: add async event queue for firmware notifications
2026-09-11 4:05 ` [PATCH net-next v2 3/3] dinghai: add async event queue for firmware notifications han.junyang
@ 2026-09-14 7:20 ` Simon Horman
0 siblings, 0 replies; 7+ messages in thread
From: Simon Horman @ 2026-09-14 7:20 UTC (permalink / raw)
To: han.junyang
Cc: Simon Horman, andrew+netdev, davem, edumazet, kuba, pabeni,
linux-kernel, netdev, ran.ming, han.chengfei, zhang.yanze
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev/#/patchset/20260911115929435mXJzcTF08xlUAdjnT8qqm@zte.com.cn?part=3
---
dinghai: add async event queue for firmware notifications
This commit adds an event queue table and an async event queue for firmware
notifications. The queue claims an MSI-X vector from the async pool and maps
firmware events to notifier chains.
> diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c
> --- a/drivers/net/ethernet/zte/dinghai/en_pf.c
> +++ b/drivers/net/ethernet/zte/dinghai/en_pf.c
[ ... ]
> @@ -612,12 +612,26 @@ static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> goto err_irq_table;
> }
>
> + ret = zxdh_pf_eq_table_init(zxdh_dev);
> + if (ret) {
> + dev_err(&pdev->dev, "zxdh_pf_eq_table_init failed: %d\n", ret);
> + goto err_eq_table;
> + }
> +
> + ret = zxdh_pf_eq_table_create(zxdh_dev);
> + if (ret) {
> + dev_err(&pdev->dev, "zxdh_pf_eq_table_create failed: %d\n", ret);
> + goto err_eq_table;
> + }
> +
> devlink_register(devlink);
>
> return 0;
>
> +err_eq_table:
> + zxdh_pf_eq_table_destroy(zxdh_dev);
> err_irq_table:
> - kvfree(zxdh_dev->irq_table.priv);
> + zxdh_pf_irq_table_destroy(zxdh_dev);
> err_cfg_init:
> zxdh_pf_pci_close(zxdh_dev);
[Severity: Medium]
Does this error path leak the modern configuration MMIO mappings?
The zxdh_pf_modern_cfg_init() function successfully maps several capabilities.
If a subsequent step fails (such as zxdh_pf_eq_table_init() or other device
initialization checks), the code jumps to err_eq_table and falls through to
err_cfg_init. However, this label only calls zxdh_pf_pci_close() and misses
a call to zxdh_pf_modern_cfg_uninit(), leaving the memory mapped.
> err_pci_init:
[ ... ]
> diff --git a/drivers/net/ethernet/zte/dinghai/zxdh_eq.c b/drivers/net/ethernet/zte/dinghai/zxdh_eq.c
> --- /dev/null
> +++ b/drivers/net/ethernet/zte/dinghai/zxdh_eq.c
[ ... ]
> +static u16 zxdh_eq_event_id_get(struct zxdh_core_dev *zxdh_dev)
> +{
> + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv;
> + void __iomem *subchan;
> +
> + subchan = pf_dev->pci_ioremap_addr[0] + ZXDH_BAR_MSG_SUBCHAN_RECV;
> +
> + return ioread32(subchan) >> 16;
> +}
[Severity: High]
Is there a risk of an out-of-bounds MMIO read here?
The device blindly maps BAR 0 based on pci_resource_len() during
zxdh_pf_pci_init(). If the hardware or an emulated environment exposes a
truncated BAR 0 length that is smaller than 0x2804 bytes, reading from
ZXDH_BAR_MSG_SUBCHAN_RECV (offset 0x2800) could exceed the mapped boundaries
and cause a page fault or oops.
[ ... ]
> +int zxdh_pf_eq_table_create(struct zxdh_core_dev *zxdh_dev)
> +{
> + struct zxdh_pf_eq_table *pf_eq_table = zxdh_dev->eq_table.priv;
> + struct zxdh_eq_table *table = &zxdh_dev->eq_table;
> + struct zxdh_eq_async *eq = &pf_eq_table->riscv_eq;
> + int err;
> +
> + mutex_lock(&table->lock);
> +
> + eq->priv = zxdh_dev;
> + eq->irq = zxdh_pf_async_irq_request(zxdh_dev);
> + if (IS_ERR(eq->irq)) {
> + err = PTR_ERR(eq->irq);
> + goto unlock;
> + }
[Severity: High]
Could this lead to an invalid pointer dereference on the cleanup path?
If zxdh_pf_async_irq_request() fails (e.g., due to memory exhaustion), it
returns an error pointer like ERR_PTR(-ENOMEM). This error pointer is
assigned to eq->irq but is not cleared to NULL before jumping to unlock.
When the error propagates, zxdh_pf_probe() jumps to the err_eq_table label
and calls zxdh_pf_eq_table_destroy().
Because eq->irq contains an error pointer, the cleanup check in
zxdh_pf_eq_table_destroy():
if (eq->irq) {
zxdh_irq_detach_nb(eq->irq, &eq->irq_nb);
...
will evaluate to true, and zxdh_irq_detach_nb() will attempt to dereference
the error pointer, causing a kernel panic.
> +
> + eq->irq_nb.notifier_call = zxdh_eq_async_riscv_int;
> + err = zxdh_irq_attach_nb(eq->irq, &eq->irq_nb);
[ ... ]
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-14 7:21 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-11 3:56 [PATCH net-next v2 0/3] dinghai: firmware handshake, MSI-X pools and async event queues han.junyang
2026-09-11 3:59 ` [PATCH net-next v2 1/3] dinghai: add firmware version check and RISC-V readiness polling han.junyang
2026-09-14 7:20 ` Simon Horman
2026-09-11 4:02 ` [PATCH net-next v2 2/3] dinghai: add MSI-X interrupt pools han.junyang
2026-09-14 7:20 ` Simon Horman
2026-09-11 4:05 ` [PATCH net-next v2 3/3] dinghai: add async event queue for firmware notifications han.junyang
2026-09-14 7:20 ` Simon Horman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®