mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net-next v1 0/3] dinghai: firmware handshake, MSI-X interrupts and async event queue
@ 2026-08-23 19:32 han.junyang
  2026-08-23 19:35 ` [PATCH net-next v1 1/3] dinghai: add firmware version check and RISC-V readiness polling han.junyang
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: han.junyang @ 2026-08-23 19:32 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 PF driver bring-up on top of the
probe skeleton and the BAR/PCI capability access already merged in
net-next ("dinghai: add ZTE network driver support" and "dinghai:
add hardware register access and PCI capability scanning").

Patch 1 makes the probe talk to the firmware for the first time: it
waits for the version compatibility block to appear in BAR 0, checks
the driver/firmware version contract and then waits for the RISC-V
management core to finish booting, so later steps can talk to it.
Firmware images predating the protocol are detected and skipped.

Patch 2 allocates the fixed MSI-X vector layout of the device and
manages the vectors in per-purpose pools. IRQs are reference counted
so that several event queues can share one vector; delivery fans out
through an atomic notifier chain attached to each IRQ.

Patch 3 adds the event queue table and the first async event queue:
it takes one vector from the async pool, reads the event id from the
PF receive sub-channel of the BAR message area on interrupt and
dispatches it to the per-event-type notifier chains of the table.

Design note: struct zxdh_core_dev is shared by several core device
types (PF, VF, SF, MPF) whose IRQ pool and event queue layouts
differ, so the generic tables in the core only carry a private
pointer, and each device type keeps its concrete layout local to its
own source file. This series wires up the PF async part only; the
remaining pools and queues grow with the series that use them.

The BAR message channel handshake that tells the firmware which
vectors to signal, the vq queue pairs and the remaining event queues
follow in later series.

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    | 221 ++++++++++
 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 | 424 ++++++++++++++++++++
 drivers/net/ethernet/zte/dinghai/zxdh_irq.h |  71 ++++
 7 files changed, 980 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] 8+ messages in thread

* [PATCH net-next v1 1/3] dinghai: add firmware version check and  RISC-V readiness polling
  2026-08-23 19:32 [PATCH net-next v1 0/3] dinghai: firmware handshake, MSI-X interrupts and async event queue han.junyang
@ 2026-08-23 19:35 ` han.junyang
  2026-08-23 21:55   ` [PATCH net-next v1 1/3] dinghai: add firmware version check and? " Andrew Lunn
  2026-08-24 17:59   ` Andrew Lunn
  2026-08-23 19:37 ` [PATCH net-next v1 2/3] dinghai: add MSI-X interrupt pools han.junyang
                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 8+ messages in thread
From: han.junyang @ 2026-08-23 19:35 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 | 103 +++++++++++++++++++++++
 drivers/net/ethernet/zte/dinghai/en_pf.h |  46 ++++++++++
 2 files changed, 149 insertions(+)

diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c
index 86d437408820..232d95420d2e 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/delay.h>
+#include <linux/io.h>
 #include <net/devlink.h>
 #include <linux/dma-mapping.h>
 #include "en_pf.h"
@@ -369,6 +371,95 @@ 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 *fw_compat;
+	void __iomem *compat_base;
+	int i;
+
+	fw_compat = &pf_dev->fw_compat;
+	compat_base = 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.
+	 */
+	for (i = 0; i < ZXDH_FW_COMPAT_TIMEOUT_SEC; i++) {
+		if (ioread32(compat_base) != 0xffffffffU)
+			break;
+		msleep(MSEC_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.
+	 */
+	ioread32_rep(compat_base, fw_compat, sizeof(*fw_compat) / 4);
+
+	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;
+	int i;
+
+	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;
+
+	for (i = 0; i < ZXDH_RISCV_READY_TIMEOUT_SEC; i++) {
+		if (ioread8(&hb->riscv_power_on) == 1)
+			return 0;
+		msleep(MSEC_PER_SEC);
+	}
+
+	dev_err(zxdh_dev->device, "timed out waiting for riscv power on\n");
+	return -ETIMEDOUT;
+}
+
 static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 {
 	struct zxdh_core_dev *zxdh_dev;
@@ -405,6 +496,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..a1e2b24d861d 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.
+ * Copied out with ioread32_rep(), 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] 8+ messages in thread

* [PATCH net-next v1 2/3] dinghai: add MSI-X interrupt pools
  2026-08-23 19:32 [PATCH net-next v1 0/3] dinghai: firmware handshake, MSI-X interrupts and async event queue han.junyang
  2026-08-23 19:35 ` [PATCH net-next v1 1/3] dinghai: add firmware version check and RISC-V readiness polling han.junyang
@ 2026-08-23 19:37 ` han.junyang
  2026-08-24 23:55   ` Andrew Lunn
  2026-08-23 19:40 ` [PATCH net-next v1 3/3] dinghai: add async event queue for firmware notifications han.junyang
  2026-08-24 17:00 ` [PATCH net-next v1 0/3] dinghai: firmware handshake, MSI-X interrupts and async event queue Jakub Kicinski
  3 siblings, 1 reply; 8+ messages in thread
From: han.junyang @ 2026-08-23 19:37 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 | 424 ++++++++++++++++++++
 drivers/net/ethernet/zte/dinghai/zxdh_irq.h |  71 ++++
 5 files changed, 608 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 232d95420d2e..8e76a1905fd2 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/delay.h>
 #include <linux/io.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);
@@ -460,6 +470,84 @@ static int zxdh_pf_wait_riscv_ready(struct zxdh_core_dev *zxdh_dev)
 	return -ETIMEDOUT;
 }

+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;
@@ -508,10 +596,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:
@@ -527,6 +629,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 a1e2b24d861d..09592b9de6cf 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..101f141068c1
--- /dev/null
+++ b/drivers/net/ethernet/zte/dinghai/zxdh_irq.c
@@ -0,0 +1,424 @@
+// 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"
+
+static void zxdh_irq_release(struct zxdh_irq *irq)
+{
+	struct zxdh_irq_pool *pool = irq->pool;
+
+	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 = 0;
+
+	mutex_lock(&pool->lock);
+	if (!--irq->refcount) {
+		zxdh_irq_release(irq);
+		released = 1;
+	}
+	mutex_unlock(&pool->lock);
+
+	return released;
+}
+
+static int zxdh_irq_get_locked(struct zxdh_irq *irq)
+{
+	lockdep_assert_held(&irq->pool->lock);
+	if (WARN_ON_ONCE(!irq->refcount))
+		return 0;
+
+	irq->refcount++;
+
+	return 1;
+}
+
+static int zxdh_irq_get(struct zxdh_irq *irq)
+{
+	int err;
+
+	mutex_lock(&irq->pool->lock);
+	err = zxdh_irq_get_locked(irq);
+	mutex_unlock(&irq->pool->lock);
+
+	return err;
+}
+
+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);
+
+	irq->refcount = 1;
+	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 = iter->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 < irq->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 &&
+	    least_loaded_irq->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:
+	zxdh_irq_get_locked(least_loaded_irq);
+	if (least_loaded_irq->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,
+			 least_loaded_irq->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.
+	 */
+	xa_for_each(&pool->irqs, index, irq)
+		zxdh_irq_release(irq);
+	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..d5d21f2fcb0f
--- /dev/null
+++ b/drivers/net/ethernet/zte/dinghai/zxdh_irq.h
@@ -0,0 +1,71 @@
+/* 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/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 */
+	int 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] 8+ messages in thread

* [PATCH net-next v1 3/3] dinghai: add async event queue for firmware  notifications
  2026-08-23 19:32 [PATCH net-next v1 0/3] dinghai: firmware handshake, MSI-X interrupts and async event queue han.junyang
  2026-08-23 19:35 ` [PATCH net-next v1 1/3] dinghai: add firmware version check and RISC-V readiness polling han.junyang
  2026-08-23 19:37 ` [PATCH net-next v1 2/3] dinghai: add MSI-X interrupt pools han.junyang
@ 2026-08-23 19:40 ` han.junyang
  2026-08-24 17:00 ` [PATCH net-next v1 0/3] dinghai: firmware handshake, MSI-X interrupts and async event queue Jakub Kicinski
  3 siblings, 0 replies; 8+ messages in thread
From: han.junyang @ 2026-08-23 19:40 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 8e76a1905fd2..74c79d39ea8a 100644
--- a/drivers/net/ethernet/zte/dinghai/en_pf.c
+++ b/drivers/net/ethernet/zte/dinghai/en_pf.c
@@ -488,7 +488,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)
@@ -532,18 +533,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);
 }
@@ -608,12 +608,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:
@@ -629,6 +643,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 09592b9de6cf..49e1ee9ed8b9 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] 8+ messages in thread

* Re: [PATCH net-next v1 1/3] dinghai: add firmware version check and? RISC-V readiness polling
  2026-08-23 19:35 ` [PATCH net-next v1 1/3] dinghai: add firmware version check and RISC-V readiness polling han.junyang
@ 2026-08-23 21:55   ` Andrew Lunn
  2026-08-24 17:59   ` Andrew Lunn
  1 sibling, 0 replies; 8+ messages in thread
From: Andrew Lunn @ 2026-08-23 21:55 UTC (permalink / raw)
  To: han.junyang
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms,
	linux-kernel, netdev, ran.ming, han.chengfei, zhang.yanze

> +	for (i = 0; i < ZXDH_RISCV_READY_TIMEOUT_SEC; i++) {
> +		if (ioread8(&hb->riscv_power_on) == 1)
> +			return 0;
> +		msleep(MSEC_PER_SEC);
> +	}

Please use one of the macros in iopoll.h

	Andrew

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next v1 0/3] dinghai: firmware handshake, MSI-X interrupts and async event queue
  2026-08-23 19:32 [PATCH net-next v1 0/3] dinghai: firmware handshake, MSI-X interrupts and async event queue han.junyang
                   ` (2 preceding siblings ...)
  2026-08-23 19:40 ` [PATCH net-next v1 3/3] dinghai: add async event queue for firmware notifications han.junyang
@ 2026-08-24 17:00 ` Jakub Kicinski
  3 siblings, 0 replies; 8+ messages in thread
From: Jakub Kicinski @ 2026-08-24 17:00 UTC (permalink / raw)
  To: han.junyang
  Cc: andrew+netdev, davem, edumazet, pabeni, horms, linux-kernel,
	netdev, ran.ming, han.chengfei, zhang.yanze

On Mon, 24 Aug 2026 03:32:49 +0800 (CST) han.junyang@zte.com.cn wrote:
> Subject: [PATCH net-next v1 0/3] dinghai: firmware handshake, MSI-X interrupts and async event queue

## Form letter - net-next-closed

net-next pull request for v7.3 has already been merged, and therefore
the net-next tree is closed for new drivers, features, code refactoring
and optimizations. We are currently accepting bug fixes only.

Please repost when net-next reopens after Aug 31st.

RFC patches sent for review only are obviously welcome at any time.

See: https://www.kernel.org/doc/html/next/process/maintainer-netdev.html#development-cycle
-- 
pw-bot: defer
pv-bot: closed

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next v1 1/3] dinghai: add firmware version check and? RISC-V readiness polling
  2026-08-23 19:35 ` [PATCH net-next v1 1/3] dinghai: add firmware version check and RISC-V readiness polling han.junyang
  2026-08-23 21:55   ` [PATCH net-next v1 1/3] dinghai: add firmware version check and? " Andrew Lunn
@ 2026-08-24 17:59   ` Andrew Lunn
  1 sibling, 0 replies; 8+ messages in thread
From: Andrew Lunn @ 2026-08-24 17:59 UTC (permalink / raw)
  To: han.junyang
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms,
	linux-kernel, netdev, ran.ming, han.chengfei, zhang.yanze

> +	struct zxdh_fw_compat *fw_compat;

> +	/* Firmware predating the compatibility region keeps the erased
> +	 * pattern, which fails the module id check below and defers the
> +	 * decision to the readiness wait.
> +	 */
> +	ioread32_rep(compat_base, fw_compat, sizeof(*fw_compat) / 4);

> +/* Firmware version compatibility block at ZXDH_FW_COMPAT_OFFSET.
> + * Copied out with ioread32_rep(), which converts from little-endian.
> + */
> +struct zxdh_fw_compat {
> +	u8 module_id;
> +	u8 major;
> +	s8 fw_minor;
> +	u8 drv_minor;
> +	u16 patch;
> +	u16 rsv;

What are the endinness of these two u16? Should they be marked as
little endian?

> +} __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. */

Same question here.

     Andrew

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next v1 2/3] dinghai: add MSI-X interrupt pools
  2026-08-23 19:37 ` [PATCH net-next v1 2/3] dinghai: add MSI-X interrupt pools han.junyang
@ 2026-08-24 23:55   ` Andrew Lunn
  0 siblings, 0 replies; 8+ messages in thread
From: Andrew Lunn @ 2026-08-24 23:55 UTC (permalink / raw)
  To: han.junyang
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms,
	linux-kernel, netdev, ran.ming, han.chengfei, zhang.yanze

> + * 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.


> +static int zxdh_irq_get_locked(struct zxdh_irq *irq)
> +{
> +	lockdep_assert_held(&irq->pool->lock);
> +	if (WARN_ON_ONCE(!irq->refcount))
> +		return 0;
> +
> +	irq->refcount++;

Why not use kref?

    Andrew

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-08-24 23:56 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-23 19:32 [PATCH net-next v1 0/3] dinghai: firmware handshake, MSI-X interrupts and async event queue han.junyang
2026-08-23 19:35 ` [PATCH net-next v1 1/3] dinghai: add firmware version check and RISC-V readiness polling han.junyang
2026-08-23 21:55   ` [PATCH net-next v1 1/3] dinghai: add firmware version check and? " Andrew Lunn
2026-08-24 17:59   ` Andrew Lunn
2026-08-23 19:37 ` [PATCH net-next v1 2/3] dinghai: add MSI-X interrupt pools han.junyang
2026-08-24 23:55   ` Andrew Lunn
2026-08-23 19:40 ` [PATCH net-next v1 3/3] dinghai: add async event queue for firmware notifications han.junyang
2026-08-24 17:00 ` [PATCH net-next v1 0/3] dinghai: firmware handshake, MSI-X interrupts and async event queue Jakub Kicinski

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®