AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/11 05:03 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch series modifies the ZTE DingHai Ethernet controller driver (drivers/net/ethernet/zte/dinghai/), adding IRQ pool management and event queue plumbing for ZTE DingHai PCIe adapters (PCI vendor ID 0x1cf2). This driver requires vendor-specific physical PCIe hardware that is not emulated in standard virtualized fuzzing environments (QEMU/GCE), making the modified code structurally unreachable for syzkaller.",
  "WorthFuzzing": false
}

1/1 2026/09/11 05:03 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit ee3ef496a935d4fcdc37410b970e473d8b6b9c00\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Sep 11 05:03:10 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/ethernet/zte/dinghai/Makefile b/drivers/net/ethernet/zte/dinghai/Makefile\nindex e2f4da33df593..2dc6d68cc0e10 100644\n--- a/drivers/net/ethernet/zte/dinghai/Makefile\n+++ b/drivers/net/ethernet/zte/dinghai/Makefile\n@@ -4,4 +4,4 @@\n #\n \n obj-$(CONFIG_DINGHAI_PF) += dinghai10e.o\n-dinghai10e-y := en_pf.o\n+dinghai10e-y := en_pf.o zxdh_irq.o zxdh_eq.o\ndiff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c\nindex 86d4374088203..fe3924b890d17 100644\n--- a/drivers/net/ethernet/zte/dinghai/en_pf.c\n+++ b/drivers/net/ethernet/zte/dinghai/en_pf.c\n@@ -6,6 +6,10 @@\n \n #include \u003clinux/module.h\u003e\n #include \u003clinux/pci.h\u003e\n+#include \u003clinux/io.h\u003e\n+#include \u003clinux/iopoll.h\u003e\n+#include \u003clinux/mm.h\u003e\n+#include \u003clinux/slab.h\u003e\n #include \u003cnet/devlink.h\u003e\n #include \u003clinux/dma-mapping.h\u003e\n #include \"en_pf.h\"\n@@ -25,6 +29,14 @@ static const struct pci_device_id zxdh_pf_pci_table[] = {\n \n MODULE_DEVICE_TABLE(pci, zxdh_pf_pci_table);\n \n+struct zxdh_pf_irq_table {\n+\tstruct zxdh_irq_pool *async_pool;\n+};\n+\n+/* IRQ compaction thresholds of the async pool, in number of queues. */\n+#define ZXDH_PF_ASYNC_IRQ_MIN_COMP\t0\n+#define ZXDH_PF_ASYNC_IRQ_MAX_COMP\t7\n+\n void *zxdh_core_alloc_priv(struct zxdh_core_dev *zxdh_dev, size_t size)\n {\n \tvoid *priv = kzalloc(size, GFP_KERNEL);\n@@ -369,6 +381,177 @@ int zxdh_pf_modern_cfg_init(struct zxdh_core_dev *zxdh_dev)\n \treturn ret;\n }\n \n+/* Read the firmware version block and verify the driver/firmware\n+ * version contract.\n+ */\n+static int zxdh_pf_fw_compat_check(struct zxdh_core_dev *zxdh_dev)\n+{\n+\tstruct zxdh_pf_dev *pf_dev = zxdh_dev-\u003epriv;\n+\tstruct zxdh_fw_compat __iomem *compat;\n+\tstruct zxdh_fw_compat *fw_compat;\n+\tu32 erased;\n+\n+\tfw_compat = \u0026pf_dev-\u003efw_compat;\n+\tcompat = pf_dev-\u003epci_ioremap_addr[0] + ZXDH_FW_COMPAT_OFFSET;\n+\n+\t/* The region reads as all ones until the firmware populates it at\n+\t * the end of its boot; allow up to 200 s for a cold boot.\n+\t */\n+\treadx_poll_timeout(ioread32, compat, erased, erased != 0xffffffffU,\n+\t\t\t   USEC_PER_SEC,\n+\t\t\t   ZXDH_FW_COMPAT_TIMEOUT_SEC * USEC_PER_SEC);\n+\n+\t/* Firmware predating the compatibility region keeps the erased\n+\t * pattern, which fails the module id check below and defers the\n+\t * decision to the readiness wait.\n+\t */\n+\tfw_compat-\u003emodule_id = ioread8(\u0026compat-\u003emodule_id);\n+\tfw_compat-\u003emajor = ioread8(\u0026compat-\u003emajor);\n+\tfw_compat-\u003efw_minor = ioread8(\u0026compat-\u003efw_minor);\n+\tfw_compat-\u003edrv_minor = ioread8(\u0026compat-\u003edrv_minor);\n+\tfw_compat-\u003epatch = ioread16(\u0026compat-\u003epatch);\n+\n+\tif (fw_compat-\u003emodule_id != ZXDH_MODULE_ID) {\n+\t\tdev_info(zxdh_dev-\u003edevice,\n+\t\t\t \"unknown module id %u, skip fw compat check\\n\",\n+\t\t\t fw_compat-\u003emodule_id);\n+\t\treturn 0;\n+\t}\n+\n+\tif (fw_compat-\u003emajor != ZXDH_MAJOR) {\n+\t\tdev_err(zxdh_dev-\u003edevice,\n+\t\t\t\"driver major %u incompatible with firmware major %u\\n\",\n+\t\t\tZXDH_MAJOR, fw_compat-\u003emajor);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (fw_compat-\u003efw_minor \u003c ZXDH_FW_MINOR) {\n+\t\tdev_err(zxdh_dev-\u003edevice,\n+\t\t\t\"firmware fw_minor %d older than required %u\\n\",\n+\t\t\tfw_compat-\u003efw_minor, ZXDH_FW_MINOR);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (fw_compat-\u003edrv_minor \u003e ZXDH_DRV_MINOR) {\n+\t\tdev_err(zxdh_dev-\u003edevice,\n+\t\t\t\"driver drv_minor %u older than required by firmware %u\\n\",\n+\t\t\tZXDH_DRV_MINOR, fw_compat-\u003edrv_minor);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* Wait for the RISC-V management core of the firmware to finish\n+ * booting, so that later probe steps can talk to it.\n+ */\n+static int zxdh_pf_wait_riscv_ready(struct zxdh_core_dev *zxdh_dev)\n+{\n+\tstruct zxdh_pf_dev *pf_dev = zxdh_dev-\u003epriv;\n+\tstruct zxdh_health_buffer __iomem *hb;\n+\tu8 health_version;\n+\tu8 power_on;\n+\tint err;\n+\n+\thb = pf_dev-\u003epci_ioremap_addr[0] + ZXDH_RISCV_HB_OFFSET;\n+\thealth_version = ioread8(\u0026hb-\u003ehealth_version);\n+\n+\t/* Firmware predating the health buffer protocol has neither a\n+\t * valid version byte nor a power-on flag to wait for.\n+\t */\n+\tif (health_version != 1 \u0026\u0026\n+\t    pf_dev-\u003efw_compat.patch \u003c ZXDH_HPIRQ_PATCH)\n+\t\treturn 0;\n+\n+\terr = readx_poll_timeout(ioread8, \u0026hb-\u003eriscv_power_on, power_on,\n+\t\t\t\t power_on == 1, USEC_PER_SEC,\n+\t\t\t\t ZXDH_RISCV_READY_TIMEOUT_SEC * USEC_PER_SEC);\n+\tif (err) {\n+\t\tdev_err(zxdh_dev-\u003edevice, \"timed out waiting for riscv power on\\n\");\n+\t\treturn err;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int zxdh_pf_irq_pools_init(struct zxdh_core_dev *zxdh_dev)\n+{\n+\tstruct zxdh_pf_irq_table *pf_irq_table = zxdh_dev-\u003eirq_table.priv;\n+\tstruct zxdh_irq_pool *pool;\n+\n+\tpool = zxdh_irq_pool_alloc(zxdh_dev, 0, ZXDH_ASYNC_CHANNELS_NUM,\n+\t\t\t\t   \"zxdh_pf_async\", ZXDH_PF_ASYNC_IRQ_MIN_COMP,\n+\t\t\t\t   ZXDH_PF_ASYNC_IRQ_MAX_COMP);\n+\tif (IS_ERR(pool))\n+\t\treturn PTR_ERR(pool);\n+\n+\tpf_irq_table-\u003easync_pool = pool;\n+\n+\treturn 0;\n+}\n+\n+static void zxdh_pf_irq_pools_destroy(struct zxdh_pf_irq_table *pf_irq_table)\n+{\n+\tif (pf_irq_table-\u003easync_pool)\n+\t\tzxdh_irq_pool_free(pf_irq_table-\u003easync_pool);\n+}\n+\n+int zxdh_pf_irq_table_init(struct zxdh_core_dev *zxdh_dev)\n+{\n+\tstruct zxdh_irq_table *table = \u0026zxdh_dev-\u003eirq_table;\n+\tstruct zxdh_pf_irq_table *priv;\n+\n+\tpriv = kvzalloc_obj(*priv, GFP_KERNEL);\n+\tif (!priv)\n+\t\treturn -ENOMEM;\n+\ttable-\u003epriv = priv;\n+\n+\treturn 0;\n+}\n+\n+int zxdh_pf_irq_table_create(struct zxdh_core_dev *zxdh_dev)\n+{\n+\tint total_vec = ZXDH_VQS_CHANNELS_NUM + ZXDH_ASYNC_CHANNELS_NUM +\n+\t\t\tZXDH_RDMA_CHANNELS_NUM;\n+\tint err;\n+\n+\ttotal_vec = pci_alloc_irq_vectors(zxdh_dev-\u003epdev, total_vec, total_vec,\n+\t\t\t\t\t  PCI_IRQ_MSIX);\n+\tif (total_vec \u003c 0) {\n+\t\tdev_err(zxdh_dev-\u003edevice, \"pci_alloc_irq_vectors failed: %d\\n\",\n+\t\t\ttotal_vec);\n+\t\treturn total_vec;\n+\t}\n+\n+\terr = zxdh_pf_irq_pools_init(zxdh_dev);\n+\tif (err) {\n+\t\tdev_err(zxdh_dev-\u003edevice, \"zxdh_pf_irq_pools_init failed: %d\\n\",\n+\t\t\terr);\n+\t\tpci_free_irq_vectors(zxdh_dev-\u003epdev);\n+\t}\n+\n+\treturn err;\n+}\n+\n+void zxdh_pf_irq_table_destroy(struct zxdh_core_dev *zxdh_dev)\n+{\n+\tstruct zxdh_irq_table *table = \u0026zxdh_dev-\u003eirq_table;\n+\n+\tif (table-\u003epriv)\n+\t\tzxdh_pf_irq_pools_destroy(table-\u003epriv);\n+\tkvfree(table-\u003epriv);\n+\ttable-\u003epriv = NULL;\n+\tpci_free_irq_vectors(zxdh_dev-\u003epdev);\n+}\n+\n+/* Fetch a vector from the async pool for one async event queue. */\n+struct zxdh_irq *zxdh_pf_async_irq_request(struct zxdh_core_dev *zxdh_dev)\n+{\n+\tstruct zxdh_pf_irq_table *pf_irq_table = zxdh_dev-\u003eirq_table.priv;\n+\n+\treturn zxdh_get_irq_of_pool(pf_irq_table-\u003easync_pool);\n+}\n+\n static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)\n {\n \tstruct zxdh_core_dev *zxdh_dev;\n@@ -405,10 +588,50 @@ static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)\n \t\tgoto err_cfg_init;\n \t}\n \n+\tret = zxdh_pf_fw_compat_check(zxdh_dev);\n+\tif (ret) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"zxdh_pf_fw_compat_check failed: %d\\n\", ret);\n+\t\tgoto err_cfg_init;\n+\t}\n+\n+\tret = zxdh_pf_wait_riscv_ready(zxdh_dev);\n+\tif (ret) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"zxdh_pf_wait_riscv_ready failed: %d\\n\", ret);\n+\t\tgoto err_cfg_init;\n+\t}\n+\n+\tret = zxdh_pf_irq_table_init(zxdh_dev);\n+\tif (ret) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"zxdh_pf_irq_table_init failed: %d\\n\", ret);\n+\t\tgoto err_cfg_init;\n+\t}\n+\n+\tret = zxdh_pf_irq_table_create(zxdh_dev);\n+\tif (ret) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"zxdh_pf_irq_table_create failed: %d\\n\", ret);\n+\t\tgoto err_irq_table;\n+\t}\n+\n+\tret = zxdh_pf_eq_table_init(zxdh_dev);\n+\tif (ret) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"zxdh_pf_eq_table_init failed: %d\\n\", ret);\n+\t\tgoto err_eq_table;\n+\t}\n+\n+\tret = zxdh_pf_eq_table_create(zxdh_dev);\n+\tif (ret) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"zxdh_pf_eq_table_create failed: %d\\n\", ret);\n+\t\tgoto err_eq_table;\n+\t}\n+\n \tdevlink_register(devlink);\n \n \treturn 0;\n \n+err_eq_table:\n+\tzxdh_pf_eq_table_destroy(zxdh_dev);\n+err_irq_table:\n+\tzxdh_pf_irq_table_destroy(zxdh_dev);\n err_cfg_init:\n \tzxdh_pf_pci_close(zxdh_dev);\n err_pci_init:\n@@ -424,6 +647,8 @@ static void zxdh_pf_remove(struct pci_dev *pdev)\n \tstruct devlink *devlink = priv_to_devlink(zxdh_dev);\n \n \tdevlink_unregister(devlink);\n+\tzxdh_pf_eq_table_destroy(zxdh_dev);\n+\tzxdh_pf_irq_table_destroy(zxdh_dev);\n \tzxdh_pf_modern_cfg_uninit(zxdh_dev);\n \tzxdh_pf_pci_close(zxdh_dev);\n \tzxdh_core_free_priv(zxdh_dev);\ndiff --git a/drivers/net/ethernet/zte/dinghai/en_pf.h b/drivers/net/ethernet/zte/dinghai/en_pf.h\nindex 7373dee8d1a9f..894a05a09d961 100644\n--- a/drivers/net/ethernet/zte/dinghai/en_pf.h\n+++ b/drivers/net/ethernet/zte/dinghai/en_pf.h\n@@ -9,6 +9,9 @@\n \n #include \u003clinux/types.h\u003e\n \n+#include \"zxdh_eq.h\"\n+#include \"zxdh_irq.h\"\n+\n #define ZXDH_PF_VENDOR_ID\t0x1cf2\n #define ZXDH_PF_DEVICE_ID\t0x8040\n #define ZXDH_VF_DEVICE_ID\t0x8041\n@@ -29,10 +32,57 @@\n #define ZXDH_PF_ALIGN2\t\t\t2\n #define ZXDH_PF_MAP_MINLEN2\t\t2\n \n+/* Fixed offsets of the firmware interface regions within BAR 0. */\n+#define ZXDH_RISCV_HB_OFFSET\t0x5300\n+#define ZXDH_FW_COMPAT_OFFSET\t0x5400\n+\n+/* Driver/firmware version contract. The firmware publishes its side of\n+ * the contract in the region at ZXDH_FW_COMPAT_OFFSET.\n+ */\n+#define ZXDH_MODULE_ID\t\t1\n+#define ZXDH_MAJOR\t\t1\n+#define ZXDH_FW_MINOR\t\t0\n+#define ZXDH_DRV_MINOR\t\t0\n+/* Firmware patch level that introduced the health buffer protocol. */\n+#define ZXDH_HPIRQ_PATCH\t4\n+\n+#define ZXDH_FW_COMPAT_TIMEOUT_SEC\t200\n+#define ZXDH_RISCV_READY_TIMEOUT_SEC\t40\n+\n+/* Firmware version compatibility block at ZXDH_FW_COMPAT_OFFSET.\n+ * Fields are read through ioread*(), which converts from little-endian.\n+ */\n+struct zxdh_fw_compat {\n+\tu8 module_id;\n+\tu8 major;\n+\ts8 fw_minor;\n+\tu8 drv_minor;\n+\tu16 patch;\n+\tu16 rsv;\n+} __packed;\n+\n+/* Health buffer at ZXDH_RISCV_HB_OFFSET, maintained by the RISC-V\n+ * management core of the firmware. Fields are read through ioread*().\n+ */\n+struct zxdh_health_buffer {\n+\tu32 synd;\t\t/* Bitmask of active syndrome flags. */\n+\tu32 health_counter;\t/* Incremented heartbeat counter. */\n+\tu8 status;\n+\tu8 rfr;\n+\tu8 fw_exception;\n+\tu8 riscv_power_on;\t/* Set to 1 once the core finished booting. */\n+\tu8 fw_version[32];\n+\tu8 pf_status[5];\n+\tu8 health_version;\t/* Health buffer protocol version. */\n+\tu8 rsv1[30];\n+} __packed;\n+\n struct zxdh_core_dev {\n \tstruct device *device;\n \tstruct pci_dev *pdev;\n \tstruct devlink *devlink;\n+\tstruct zxdh_irq_table irq_table;\n+\tstruct zxdh_eq_table eq_table;\n \tvoid *priv;\n };\n \n@@ -54,6 +104,7 @@ struct zxdh_pf_dev {\n \ts32 modern_bars;\n \tvoid __iomem *pci_ioremap_addr[6];\n \tu32 dev_cfg_bar_off;\n+\tstruct zxdh_fw_compat fw_compat;\n };\n \n void *zxdh_core_alloc_priv(struct zxdh_core_dev *zxdh_dev, size_t size);\n@@ -72,4 +123,10 @@ int zxdh_pf_device_cfg_init(struct zxdh_core_dev *zxdh_dev);\n void zxdh_pf_modern_cfg_uninit(struct zxdh_core_dev *zxdh_dev);\n int zxdh_pf_modern_cfg_init(struct zxdh_core_dev *zxdh_dev);\n \n+/* PF IRQ table (en_pf.c) */\n+int zxdh_pf_irq_table_init(struct zxdh_core_dev *zxdh_dev);\n+int zxdh_pf_irq_table_create(struct zxdh_core_dev *zxdh_dev);\n+void zxdh_pf_irq_table_destroy(struct zxdh_core_dev *zxdh_dev);\n+struct zxdh_irq *zxdh_pf_async_irq_request(struct zxdh_core_dev *zxdh_dev);\n+\n #endif /* __ZXDH_EN_PF_H__ */\ndiff --git a/drivers/net/ethernet/zte/dinghai/zxdh_eq.c b/drivers/net/ethernet/zte/dinghai/zxdh_eq.c\nnew file mode 100644\nindex 0000000000000..f1d7eeec1ae4e\n--- /dev/null\n+++ b/drivers/net/ethernet/zte/dinghai/zxdh_eq.c\n@@ -0,0 +1,145 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * ZTE DingHai Ethernet driver - event queue plumbing\n+ * Copyright (c) 2022-2026, ZTE Corporation.\n+ *\n+ * Async event queues are the delivery path for firmware notifications:\n+ * a queue owns one MSI-X vector of the async pool and fans interrupts\n+ * out to the per-event-type notifier chains of the event queue table.\n+ * The BAR message channel handshake that tells the firmware which\n+ * vectors to signal comes with the BAR message subsystem in a later\n+ * series; the PF side wired here is live as soon as that channel is\n+ * enabled.\n+ */\n+\n+#include \u003clinux/io.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/notifier.h\u003e\n+#include \u003clinux/slab.h\u003e\n+\n+#include \"en_pf.h\"\n+#include \"zxdh_eq.h\"\n+#include \"zxdh_irq.h\"\n+\n+/* PF side of the event queue table: one async EQ fed by the RISC-V\n+ * management core.\n+ */\n+struct zxdh_pf_eq_table {\n+\tstruct zxdh_eq_async riscv_eq;\n+};\n+\n+/* Fetch the event_id the RISC-V core posted in the PF receive\n+ * sub-channel of the BAR message area. The field lives at byte offset\n+ * 2 of the 12-byte message header, so a single 32-bit read of the\n+ * sub-channel start covers it.\n+ */\n+static u16 zxdh_eq_event_id_get(struct zxdh_core_dev *zxdh_dev)\n+{\n+\tstruct zxdh_pf_dev *pf_dev = zxdh_dev-\u003epriv;\n+\tvoid __iomem *subchan;\n+\n+\tsubchan = pf_dev-\u003epci_ioremap_addr[0] + ZXDH_BAR_MSG_SUBCHAN_RECV;\n+\n+\treturn ioread32(subchan) \u003e\u003e 16;\n+}\n+\n+static enum zxdh_eq_event_type zxdh_eq_event_type_get(u16 event_id)\n+{\n+\tif (event_id == ZXDH_MSG_MODULE_RISC_READY)\n+\t\treturn ZXDH_EQ_EVENT_TYPE_RISCV_READY;\n+\n+\t/* Only the RISC-V ready notification is dispatched in this\n+\t * series; the remaining firmware events arrive with the BAR\n+\t * message subsystem.\n+\t */\n+\treturn ZXDH_EQ_EVENT_TYPE_MAX;\n+}\n+\n+static int zxdh_eq_async_riscv_int(struct notifier_block *nb,\n+\t\t\t\t   unsigned long action, void *data)\n+{\n+\tstruct zxdh_eq_async *eq = container_of(nb, struct zxdh_eq_async, irq_nb);\n+\tstruct zxdh_core_dev *zxdh_dev = eq-\u003epriv;\n+\tenum zxdh_eq_event_type event_type;\n+\n+\tevent_type = zxdh_eq_event_type_get(zxdh_eq_event_id_get(zxdh_dev));\n+\tif (event_type == ZXDH_EQ_EVENT_TYPE_MAX)\n+\t\treturn NOTIFY_DONE;\n+\n+\tatomic_notifier_call_chain(\u0026zxdh_dev-\u003eeq_table.nh[event_type],\n+\t\t\t\t   event_type, NULL);\n+\n+\treturn NOTIFY_OK;\n+}\n+\n+int zxdh_pf_eq_table_init(struct zxdh_core_dev *zxdh_dev)\n+{\n+\tstruct zxdh_eq_table *table = \u0026zxdh_dev-\u003eeq_table;\n+\tstruct zxdh_pf_eq_table *priv;\n+\tint i;\n+\n+\tpriv = kvzalloc_obj(*priv, GFP_KERNEL);\n+\tif (!priv)\n+\t\treturn -ENOMEM;\n+\ttable-\u003epriv = priv;\n+\n+\tmutex_init(\u0026table-\u003elock);\n+\tfor (i = 0; i \u003c ZXDH_EQ_EVENT_TYPE_MAX; i++)\n+\t\tATOMIC_INIT_NOTIFIER_HEAD(\u0026table-\u003enh[i]);\n+\n+\treturn 0;\n+}\n+\n+int zxdh_pf_eq_table_create(struct zxdh_core_dev *zxdh_dev)\n+{\n+\tstruct zxdh_pf_eq_table *pf_eq_table = zxdh_dev-\u003eeq_table.priv;\n+\tstruct zxdh_eq_table *table = \u0026zxdh_dev-\u003eeq_table;\n+\tstruct zxdh_eq_async *eq = \u0026pf_eq_table-\u003eriscv_eq;\n+\tint err;\n+\n+\tmutex_lock(\u0026table-\u003elock);\n+\n+\teq-\u003epriv = zxdh_dev;\n+\teq-\u003eirq = zxdh_pf_async_irq_request(zxdh_dev);\n+\tif (IS_ERR(eq-\u003eirq)) {\n+\t\terr = PTR_ERR(eq-\u003eirq);\n+\t\tgoto unlock;\n+\t}\n+\n+\teq-\u003eirq_nb.notifier_call = zxdh_eq_async_riscv_int;\n+\terr = zxdh_irq_attach_nb(eq-\u003eirq, \u0026eq-\u003eirq_nb);\n+\tif (err) {\n+\t\tzxdh_irqs_release_vectors(\u0026eq-\u003eirq, 1);\n+\t\teq-\u003eirq = NULL;\n+\t}\n+\n+unlock:\n+\tmutex_unlock(\u0026table-\u003elock);\n+\n+\treturn err;\n+}\n+\n+void zxdh_pf_eq_table_destroy(struct zxdh_core_dev *zxdh_dev)\n+{\n+\tstruct zxdh_pf_eq_table *pf_eq_table = zxdh_dev-\u003eeq_table.priv;\n+\tstruct zxdh_eq_table *table = \u0026zxdh_dev-\u003eeq_table;\n+\tstruct zxdh_eq_async *eq;\n+\n+\tif (!pf_eq_table)\n+\t\treturn;\n+\n+\teq = \u0026pf_eq_table-\u003eriscv_eq;\n+\n+\tmutex_lock(\u0026table-\u003elock);\n+\tif (eq-\u003eirq) {\n+\t\tzxdh_irq_detach_nb(eq-\u003eirq, \u0026eq-\u003eirq_nb);\n+\t\tzxdh_irqs_release_vectors(\u0026eq-\u003eirq, 1);\n+\t\teq-\u003eirq = NULL;\n+\t}\n+\tmutex_unlock(\u0026table-\u003elock);\n+\n+\tmutex_destroy(\u0026table-\u003elock);\n+\tkvfree(table-\u003epriv);\n+\ttable-\u003epriv = NULL;\n+}\ndiff --git a/drivers/net/ethernet/zte/dinghai/zxdh_eq.h b/drivers/net/ethernet/zte/dinghai/zxdh_eq.h\nnew file mode 100644\nindex 0000000000000..4965ca15912ac\n--- /dev/null\n+++ b/drivers/net/ethernet/zte/dinghai/zxdh_eq.h\n@@ -0,0 +1,61 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * ZTE DingHai Ethernet driver - event queue abstraction\n+ * Copyright (c) 2022-2026, ZTE Corporation.\n+ */\n+\n+#ifndef __ZXDH_EQ_H__\n+#define __ZXDH_EQ_H__\n+\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/notifier.h\u003e\n+\n+struct zxdh_core_dev;\n+struct zxdh_irq;\n+\n+/* BAR message area: shared-memory mailbox between the driver and the\n+ * firmware, split into sub-channels of 2048 bytes. Notifications from\n+ * the RISC-V management core are posted to the PF receive sub-channel,\n+ * one interval above the area base.\n+ */\n+#define ZXDH_BAR_MSG_OFFSET\t\t0x2000\n+#define ZXDH_BAR_MSG_SUBCHAN_INTERVAL\t2048\n+#define ZXDH_BAR_MSG_SUBCHAN_RECV\t(ZXDH_BAR_MSG_OFFSET +\t\\\n+\t\t\t\t\t ZXDH_BAR_MSG_SUBCHAN_INTERVAL)\n+\n+/* event_id of the \"RISC-V core ready\" firmware notification, carried\n+ * at byte offset 2 of the BAR message header.\n+ */\n+#define ZXDH_MSG_MODULE_RISC_READY\t13\n+\n+/* Event types the upper layers can subscribe to over the per-type\n+ * notifier chains of the event queue table. The full catalogue lives\n+ * in the firmware; only the ones dispatched in this series are named.\n+ */\n+enum zxdh_eq_event_type {\n+\tZXDH_EQ_EVENT_TYPE_RISCV_READY = 0,\n+\tZXDH_EQ_EVENT_TYPE_MAX,\n+};\n+\n+/* Asynchronous event queue delivering firmware notifications to the\n+ * driver without a request/reply handshake.\n+ */\n+struct zxdh_eq_async {\n+\tstruct zxdh_irq *irq;\n+\tstruct notifier_block irq_nb;\n+\tvoid *priv;\n+};\n+\n+struct zxdh_eq_table {\n+\t/* Notifier chains fanned out by event type. */\n+\tstruct atomic_notifier_head nh[ZXDH_EQ_EVENT_TYPE_MAX];\n+\t/* Serializes async EQ create/destroy. */\n+\tstruct mutex lock;\n+\tvoid *priv;\n+};\n+\n+int zxdh_pf_eq_table_init(struct zxdh_core_dev *zxdh_dev);\n+int zxdh_pf_eq_table_create(struct zxdh_core_dev *zxdh_dev);\n+void zxdh_pf_eq_table_destroy(struct zxdh_core_dev *zxdh_dev);\n+\n+#endif /* __ZXDH_EQ_H__ */\ndiff --git a/drivers/net/ethernet/zte/dinghai/zxdh_irq.c b/drivers/net/ethernet/zte/dinghai/zxdh_irq.c\nnew file mode 100644\nindex 0000000000000..437344329c7e8\n--- /dev/null\n+++ b/drivers/net/ethernet/zte/dinghai/zxdh_irq.c\n@@ -0,0 +1,412 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * ZTE DingHai Ethernet driver - IRQ pool management\n+ * Copyright (c) 2022-2026, ZTE Corporation.\n+ *\n+ * Device vectors are managed in per-purpose pools. An IRQ carries a\n+ * refcount so that several event queues can share one vector; on\n+ * raise, the interrupt is fanned out through an atomic notifier chain\n+ * attached to the IRQ.\n+ */\n+\n+#include \u003clinux/cpumask.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/interrupt.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/pci.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/xarray.h\u003e\n+\n+#include \"en_pf.h\"\n+#include \"zxdh_irq.h\"\n+\n+/* Release callback of the IRQ kref, called with pool-\u003elock held. */\n+static void zxdh_irq_release(struct kref *kref)\n+{\n+\tstruct zxdh_irq *irq = container_of(kref, struct zxdh_irq, refcount);\n+\tstruct zxdh_irq_pool *pool = irq-\u003epool;\n+\n+\tlockdep_assert_held(\u0026pool-\u003elock);\n+\txa_erase(\u0026pool-\u003eirqs, irq-\u003eindex);\n+\t/* free_irq() requires the affinity hint to be cleared before it is\n+\t * called; the asymmetry with the set path in zxdh_irq_alloc() is\n+\t * intentional.\n+\t */\n+\tirq_update_affinity_hint(irq-\u003eirqn, NULL);\n+\tfree_cpumask_var(irq-\u003emask);\n+\tfree_irq(irq-\u003eirqn, irq);\n+\tkfree(irq);\n+}\n+\n+/* Drop one reference; returns 1 if the IRQ was released. */\n+static int zxdh_irq_put(struct zxdh_irq *irq)\n+{\n+\tstruct zxdh_irq_pool *pool = irq-\u003epool;\n+\tint released;\n+\n+\tmutex_lock(\u0026pool-\u003elock);\n+\treleased = kref_put(\u0026irq-\u003erefcount, zxdh_irq_release);\n+\tmutex_unlock(\u0026pool-\u003elock);\n+\n+\treturn released;\n+}\n+\n+static int zxdh_irq_get(struct zxdh_irq *irq)\n+{\n+\treturn kref_get_unless_zero(\u0026irq-\u003erefcount);\n+}\n+\n+static irqreturn_t zxdh_irq_int_handler(int irq, void *data)\n+{\n+\tstruct zxdh_irq *zxdh_irq = data;\n+\n+\tatomic_notifier_call_chain(\u0026zxdh_irq-\u003enh, 0, NULL);\n+\n+\treturn IRQ_HANDLED;\n+}\n+\n+static struct zxdh_irq *zxdh_irq_alloc(struct zxdh_irq_pool *pool, int vecidx,\n+\t\t\t\t       const struct cpumask *affinity)\n+{\n+\tstruct zxdh_core_dev *zxdh_dev = pool-\u003edev;\n+\tstruct zxdh_irq *irq;\n+\tint err;\n+\tint cpu;\n+\n+\tirq = kzalloc_obj(*irq, GFP_KERNEL);\n+\tif (!irq)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tirq-\u003epool = pool;\n+\tirq-\u003eirqn = pci_irq_vector(zxdh_dev-\u003epdev, vecidx);\n+\tif (irq-\u003eirqn \u003c 0) {\n+\t\terr = irq-\u003eirqn;\n+\t\tgoto err_irqn;\n+\t}\n+\n+\tATOMIC_INIT_NOTIFIER_HEAD(\u0026irq-\u003enh);\n+\tsnprintf(irq-\u003ename, ZXDH_MAX_IRQ_NAME, \"async_%d@pci:%s\", vecidx,\n+\t\t pci_name(zxdh_dev-\u003epdev));\n+\n+\terr = request_irq(irq-\u003eirqn, zxdh_irq_int_handler, 0, irq-\u003ename, irq);\n+\tif (err) {\n+\t\tdev_err(zxdh_dev-\u003edevice, \"request_irq failed: %d\\n\", err);\n+\t\tgoto err_irqn;\n+\t}\n+\n+\tif (!zalloc_cpumask_var(\u0026irq-\u003emask, GFP_KERNEL)) {\n+\t\tdev_err(zxdh_dev-\u003edevice, \"zalloc_cpumask_var failed\\n\");\n+\t\terr = -ENOMEM;\n+\t\tgoto err_cpumask;\n+\t}\n+\n+\tif (affinity) {\n+\t\tcpumask_copy(irq-\u003emask, affinity);\n+\t} else {\n+\t\t/* No preference requested; spread over all online CPUs. */\n+\t\tfor_each_online_cpu(cpu)\n+\t\t\tcpumask_set_cpu(cpu, irq-\u003emask);\n+\t}\n+\tirq_update_affinity_hint(irq-\u003eirqn, irq-\u003emask);\n+\n+\tkref_init(\u0026irq-\u003erefcount);\n+\tirq-\u003eindex = vecidx;\n+\terr = xa_err(xa_store(\u0026pool-\u003eirqs, irq-\u003eindex, irq, GFP_KERNEL));\n+\tif (err) {\n+\t\tdev_err(zxdh_dev-\u003edevice, \"xa_store failed for irq %u: %d\\n\",\n+\t\t\tirq-\u003eindex, err);\n+\t\tgoto err_xa;\n+\t}\n+\n+\treturn irq;\n+\n+err_xa:\n+\tirq_update_affinity_hint(irq-\u003eirqn, NULL);\n+\tfree_cpumask_var(irq-\u003emask);\n+err_cpumask:\n+\tfree_irq(irq-\u003eirqn, irq);\n+err_irqn:\n+\tkfree(irq);\n+\treturn ERR_PTR(err);\n+}\n+\n+int zxdh_irq_attach_nb(struct zxdh_irq *irq, struct notifier_block *nb)\n+{\n+\tint err;\n+\n+\tif (!zxdh_irq_get(irq))\n+\t\treturn -ENOENT;\n+\n+\terr = atomic_notifier_chain_register(\u0026irq-\u003enh, nb);\n+\tif (err)\n+\t\tzxdh_irq_put(irq);\n+\n+\treturn err;\n+}\n+\n+int zxdh_irq_detach_nb(struct zxdh_irq *irq, struct notifier_block *nb)\n+{\n+\tint err;\n+\n+\terr = atomic_notifier_chain_unregister(\u0026irq-\u003enh, nb);\n+\tzxdh_irq_put(irq);\n+\n+\treturn err;\n+}\n+\n+/* Release one or more IRQs back to their pool. */\n+void zxdh_irqs_release_vectors(struct zxdh_irq **irqs, int nirqs)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c nirqs; i++) {\n+\t\tsynchronize_irq(irqs[i]-\u003eirqn);\n+\t\tzxdh_irq_put(irqs[i]);\n+\t}\n+}\n+\n+static void zxdh_cpu_put(struct zxdh_irq_pool *pool, int cpu)\n+{\n+\tpool-\u003eirqs_per_cpu[cpu]--;\n+}\n+\n+static void zxdh_cpu_get(struct zxdh_irq_pool *pool, int cpu)\n+{\n+\tpool-\u003eirqs_per_cpu[cpu]++;\n+}\n+\n+/* Find the least loaded CPU, i.e. the CPU with the fewest IRQs bound\n+ * to it, within the requested mask.\n+ */\n+static int zxdh_cpu_get_least_loaded(struct zxdh_irq_pool *pool,\n+\t\t\t\t     const struct cpumask *req_mask)\n+{\n+\tint best_cpu = -1;\n+\tint cpu;\n+\n+\tfor_each_cpu_and(cpu, req_mask, cpu_online_mask) {\n+\t\t/* This CPU has no IRQs yet, no need to look further. */\n+\t\tif (!pool-\u003eirqs_per_cpu[cpu]) {\n+\t\t\tbest_cpu = cpu;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (best_cpu \u003c 0)\n+\t\t\tbest_cpu = cpu;\n+\t\tif (pool-\u003eirqs_per_cpu[cpu] \u003c pool-\u003eirqs_per_cpu[best_cpu])\n+\t\t\tbest_cpu = cpu;\n+\t}\n+\n+\tif (best_cpu \u003c 0) {\n+\t\tdev_err(pool-\u003edev-\u003edevice,\n+\t\t\t\"no online CPU in affinity mask (%*pbl)\\n\",\n+\t\t\tcpumask_pr_args(req_mask));\n+\t\tbest_cpu = cpumask_first(cpu_online_mask);\n+\t}\n+\tpool-\u003eirqs_per_cpu[best_cpu]++;\n+\n+\treturn best_cpu;\n+}\n+\n+/* Create a new IRQ from the pool and bind it to the requested mask. */\n+static struct zxdh_irq *zxdh_irq_pool_request_irq(struct zxdh_irq_pool *pool,\n+\t\t\t\t\t\t  const struct cpumask *req_mask)\n+{\n+\tconst struct cpumask *affinity = req_mask;\n+\tcpumask_var_t auto_mask;\n+\tstruct zxdh_irq *irq;\n+\tu32 irq_index;\n+\tint err;\n+\n+\tif (!zalloc_cpumask_var(\u0026auto_mask, GFP_KERNEL))\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\terr = xa_alloc(\u0026pool-\u003eirqs, \u0026irq_index, NULL, pool-\u003exa_num_irqs,\n+\t\t       GFP_KERNEL);\n+\tif (err) {\n+\t\tif (err == -EBUSY)\n+\t\t\terr = -EUSERS;\n+\t\tgoto err_xa;\n+\t}\n+\n+\tif (cpumask_weight(req_mask) \u003e 1) {\n+\t\t/* Bind to the least loaded CPU of the request. */\n+\t\tcpumask_set_cpu(zxdh_cpu_get_least_loaded(pool, req_mask),\n+\t\t\t\tauto_mask);\n+\t\taffinity = auto_mask;\n+\t} else {\n+\t\tzxdh_cpu_get(pool, cpumask_first(req_mask));\n+\t}\n+\n+\tirq = zxdh_irq_alloc(pool, irq_index, affinity);\n+\tif (IS_ERR(irq))\n+\t\tgoto err_alloc;\n+\n+\tfree_cpumask_var(auto_mask);\n+\n+\treturn irq;\n+\n+err_alloc:\n+\t/* Undo the per-CPU accounting done above. */\n+\tzxdh_cpu_put(pool, cpumask_first(affinity));\n+\txa_erase(\u0026pool-\u003eirqs, irq_index);\n+err_xa:\n+\tfree_cpumask_var(auto_mask);\n+\treturn err ? ERR_PTR(err) : irq;\n+}\n+\n+/* Look for the IRQ with the smallest refcount whose affinity mask is a\n+ * subset of the requested mask.\n+ */\n+static struct zxdh_irq *zxdh_irq_pool_find_least_loaded(struct zxdh_irq_pool *pool,\n+\t\t\t\t\t\t\tconst struct cpumask *req_mask)\n+{\n+\tstruct zxdh_irq *irq = NULL;\n+\tstruct zxdh_irq *iter;\n+\tunsigned long index;\n+\tint iter_refcount;\n+\n+\tlockdep_assert_held(\u0026pool-\u003elock);\n+\txa_for_each_range(\u0026pool-\u003eirqs, index, iter,\n+\t\t\t  pool-\u003exa_num_irqs.min, pool-\u003exa_num_irqs.max) {\n+\t\titer_refcount = refcount_read(\u0026iter-\u003erefcount.refcount);\n+\n+\t\t/* Skip IRQs whose mask is not a subset of the request. */\n+\t\tif (!cpumask_subset(iter-\u003emask, req_mask))\n+\t\t\tcontinue;\n+\t\t/* IRQ below the minimum threshold found, take it. */\n+\t\tif (iter_refcount \u003c pool-\u003emin_threshold)\n+\t\t\treturn iter;\n+\t\tif (!irq ||\n+\t\t    iter_refcount \u003c refcount_read(\u0026irq-\u003erefcount.refcount))\n+\t\t\tirq = iter;\n+\t}\n+\n+\treturn irq;\n+}\n+\n+/* Request an IRQ for the given mask: share the least loaded existing\n+ * matching IRQ once it passes the pool minimum threshold, otherwise\n+ * create a new one.\n+ */\n+static struct zxdh_irq *zxdh_irq_affinity_request(struct zxdh_irq_pool *pool,\n+\t\t\t\t\t\t  const struct cpumask *req_mask)\n+{\n+\tstruct zxdh_irq *least_loaded_irq;\n+\tstruct zxdh_irq *new_irq;\n+\n+\tmutex_lock(\u0026pool-\u003elock);\n+\n+\tleast_loaded_irq = zxdh_irq_pool_find_least_loaded(pool, req_mask);\n+\tif (least_loaded_irq \u0026\u0026\n+\t    refcount_read(\u0026least_loaded_irq-\u003erefcount.refcount) \u003c pool-\u003emin_threshold)\n+\t\tgoto out;\n+\n+\t/* No IRQ below the minimum threshold, try to create a new one. */\n+\tnew_irq = zxdh_irq_pool_request_irq(pool, req_mask);\n+\tif (IS_ERR(new_irq)) {\n+\t\tif (!least_loaded_irq) {\n+\t\t\tmutex_unlock(\u0026pool-\u003elock);\n+\t\t\treturn new_irq;\n+\t\t}\n+\t\t/* Could not create a new IRQ for the requested affinity,\n+\t\t * share the existing one.\n+\t\t */\n+\t\tdev_warn(pool-\u003edev-\u003edevice,\n+\t\t\t \"irq pool %s exhausted, sharing irqs\\n\",\n+\t\t\t pool-\u003ename);\n+\t\tgoto out;\n+\t}\n+\n+\tleast_loaded_irq = new_irq;\n+\tgoto unlock;\n+\n+out:\n+\t/* Holding pool-\u003elock keeps the IRQ alive, so this cannot fail. */\n+\tzxdh_irq_get(least_loaded_irq);\n+\tif (refcount_read(\u0026least_loaded_irq-\u003erefcount.refcount) \u003e pool-\u003emax_threshold)\n+\t\tdev_warn(pool-\u003edev-\u003edevice,\n+\t\t\t \"irq %u overloaded, pool %s, %u refs on this irq\\n\",\n+\t\t\t pci_irq_vector(pool-\u003edev-\u003epdev,\n+\t\t\t\t\tleast_loaded_irq-\u003eindex),\n+\t\t\t pool-\u003ename,\n+\t\t\t refcount_read(\u0026least_loaded_irq-\u003erefcount.refcount) /\n+\t\t\t ZXDH_EQ_REFS_PER_IRQ);\n+unlock:\n+\tmutex_unlock(\u0026pool-\u003elock);\n+\n+\treturn least_loaded_irq;\n+}\n+\n+/* Request an IRQ from the pool, spread over the online CPUs. */\n+struct zxdh_irq *zxdh_get_irq_of_pool(struct zxdh_irq_pool *pool)\n+{\n+\tcpumask_var_t req_mask;\n+\tstruct zxdh_irq *irq;\n+\n+\tif (!zalloc_cpumask_var(\u0026req_mask, GFP_KERNEL))\n+\t\treturn ERR_PTR(-ENOMEM);\n+\tcpumask_copy(req_mask, cpu_online_mask);\n+\n+\tirq = zxdh_irq_affinity_request(pool, req_mask);\n+\n+\tfree_cpumask_var(req_mask);\n+\n+\treturn irq;\n+}\n+\n+struct zxdh_irq_pool *zxdh_irq_pool_alloc(struct zxdh_core_dev *zxdh_dev,\n+\t\t\t\t\t  int start, int size, const char *name,\n+\t\t\t\t\t  u32 min_threshold, u32 max_threshold)\n+{\n+\tstruct zxdh_irq_pool *pool;\n+\tu16 *irqs_per_cpu;\n+\n+\tirqs_per_cpu = kcalloc(nr_cpu_ids, sizeof(*irqs_per_cpu), GFP_KERNEL);\n+\tif (!irqs_per_cpu)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tpool = kvzalloc_obj(*pool, GFP_KERNEL);\n+\tif (!pool) {\n+\t\tkfree(irqs_per_cpu);\n+\t\treturn ERR_PTR(-ENOMEM);\n+\t}\n+\n+\tpool-\u003edev = zxdh_dev;\n+\tpool-\u003eirqs_per_cpu = irqs_per_cpu;\n+\tmutex_init(\u0026pool-\u003elock);\n+\txa_init_flags(\u0026pool-\u003eirqs, XA_FLAGS_ALLOC);\n+\tpool-\u003exa_num_irqs.min = start;\n+\tpool-\u003exa_num_irqs.max = start + size - 1;\n+\tsnprintf(pool-\u003ename, ZXDH_MAX_IRQ_NAME, \"%s\", name);\n+\n+\t/* Thresholds are expressed in queue references, two per IRQ. */\n+\tpool-\u003emin_threshold = min_threshold * ZXDH_EQ_REFS_PER_IRQ;\n+\tpool-\u003emax_threshold = max_threshold * ZXDH_EQ_REFS_PER_IRQ;\n+\n+\treturn pool;\n+}\n+\n+void zxdh_irq_pool_free(struct zxdh_irq_pool *pool)\n+{\n+\tstruct zxdh_irq *irq;\n+\tunsigned long index;\n+\tu32 cpu;\n+\n+\t/* On a fast teardown the table may still hold IRQs; release\n+\t * whatever is left.\n+\t */\n+\tmutex_lock(\u0026pool-\u003elock);\n+\txa_for_each(\u0026pool-\u003eirqs, index, irq)\n+\t\tzxdh_irq_release(\u0026irq-\u003erefcount);\n+\tmutex_unlock(\u0026pool-\u003elock);\n+\txa_destroy(\u0026pool-\u003eirqs);\n+\tmutex_destroy(\u0026pool-\u003elock);\n+\n+\tif (pool-\u003eirqs_per_cpu) {\n+\t\tfor_each_online_cpu(cpu)\n+\t\t\tWARN_ON(pool-\u003eirqs_per_cpu[cpu]);\n+\t\tkfree(pool-\u003eirqs_per_cpu);\n+\t}\n+\n+\tkvfree(pool);\n+}\ndiff --git a/drivers/net/ethernet/zte/dinghai/zxdh_irq.h b/drivers/net/ethernet/zte/dinghai/zxdh_irq.h\nnew file mode 100644\nindex 0000000000000..89f46d7b11c9c\n--- /dev/null\n+++ b/drivers/net/ethernet/zte/dinghai/zxdh_irq.h\n@@ -0,0 +1,72 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * ZTE DingHai Ethernet driver - IRQ pool management\n+ * Copyright (c) 2022-2026, ZTE Corporation.\n+ */\n+\n+#ifndef __ZXDH_IRQ_H__\n+#define __ZXDH_IRQ_H__\n+\n+#include \u003clinux/cpumask.h\u003e\n+#include \u003clinux/kref.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/notifier.h\u003e\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/xarray.h\u003e\n+\n+struct zxdh_core_dev;\n+\n+/* Number of MSI-X vectors per purpose. The layout of the vector space\n+ * is a hardware interface:\n+ *   [0, 8)    async event queues\n+ *   [8, 14)   reserved for RDMA (unused for now)\n+ *   [14, 78)  vq rx/tx queue pairs\n+ */\n+#define ZXDH_ASYNC_CHANNELS_NUM\t\t8\n+#define ZXDH_RDMA_CHANNELS_NUM\t\t6\n+#define ZXDH_VQS_CHANNELS_NUM\t\t64\n+\n+#define ZXDH_MAX_IRQ_NAME\t100\n+#define ZXDH_EQ_REFS_PER_IRQ\t2\n+\n+/* Reference counted interrupt. Event queues share an IRQ through the\n+ * notifier chain, which is fired from hard IRQ context.\n+ */\n+struct zxdh_irq {\n+\tstruct atomic_notifier_head nh;\n+\tcpumask_var_t mask;\t\t/* interrupt affinity */\n+\tchar name[ZXDH_MAX_IRQ_NAME];\n+\tstruct zxdh_irq_pool *pool;\n+\tu32 index;\t\t\t/* vector index */\n+\tint irqn;\t\t\t/* Linux IRQ number */\n+\tstruct kref refcount;\n+};\n+\n+/* Pool of vectors dedicated to one purpose, identified by the\n+ * [xa_num_irqs.min, xa_num_irqs.max] vector range.\n+ */\n+struct zxdh_irq_pool {\n+\tchar name[ZXDH_MAX_IRQ_NAME];\n+\tstruct xa_limit xa_num_irqs;\n+\tstruct mutex lock;\t\t/* serializes IRQ creation */\n+\tstruct xarray irqs;\n+\tu32 min_threshold;\t\t/* in queue references */\n+\tu32 max_threshold;\t\t/* in queue references */\n+\tu16 *irqs_per_cpu;\t\t/* number of IRQs bound per CPU */\n+\tstruct zxdh_core_dev *dev;\n+};\n+\n+struct zxdh_irq_table {\n+\tvoid *priv;\n+};\n+\n+struct zxdh_irq *zxdh_get_irq_of_pool(struct zxdh_irq_pool *pool);\n+int zxdh_irq_attach_nb(struct zxdh_irq *irq, struct notifier_block *nb);\n+int zxdh_irq_detach_nb(struct zxdh_irq *irq, struct notifier_block *nb);\n+void zxdh_irqs_release_vectors(struct zxdh_irq **irqs, int nirqs);\n+struct zxdh_irq_pool *zxdh_irq_pool_alloc(struct zxdh_core_dev *zxdh_dev,\n+\t\t\t\t\t  int start, int size, const char *name,\n+\t\t\t\t\t  u32 min_threshold, u32 max_threshold);\n+void zxdh_irq_pool_free(struct zxdh_irq_pool *pool);\n+\n+#endif /* __ZXDH_IRQ_H__ */\n"
}

2/1 2026/09/11 05:03 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series modifies the ZTE DingHai Ethernet controller driver (drivers/net/ethernet/zte/dinghai/), adding IRQ pool management and event queue plumbing for ZTE DingHai PCIe adapters (PCI vendor ID 0x1cf2). This driver requires vendor-specific physical PCIe hardware that is not emulated in standard virtualized fuzzing environments (QEMU/GCE), making the modified code structurally unreachable for syzkaller.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit ee3ef496a935d4fcdc37410b970e473d8b6b9c00
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Sep 11 05:03:10 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/net/ethernet/zte/dinghai/Makefile b/drivers/net/ethernet/zte/dinghai/Makefile
index e2f4da33df593..2dc6d68cc0e10 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 zxdh_eq.o
diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c
index 86d4374088203..fe3924b890d17 100644
--- a/drivers/net/ethernet/zte/dinghai/en_pf.c
+++ b/drivers/net/ethernet/zte/dinghai/en_pf.c
@@ -6,6 +6,10 @@
 
 #include <linux/module.h>
 #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"
@@ -25,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);
@@ -369,6 +381,177 @@ 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_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)
+{
+	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)
+{
+	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;
+
+	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_pf_irq_table *pf_irq_table = zxdh_dev->irq_table.priv;
+
+	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;
@@ -405,10 +588,50 @@ 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;
+	}
+
+	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;
+	}
+
+	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:
+	zxdh_pf_irq_table_destroy(zxdh_dev);
 err_cfg_init:
 	zxdh_pf_pci_close(zxdh_dev);
 err_pci_init:
@@ -424,6 +647,8 @@ 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);
 	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 7373dee8d1a9f..894a05a09d961 100644
--- a/drivers/net/ethernet/zte/dinghai/en_pf.h
+++ b/drivers/net/ethernet/zte/dinghai/en_pf.h
@@ -9,6 +9,9 @@
 
 #include <linux/types.h>
 
+#include "zxdh_eq.h"
+#include "zxdh_irq.h"
+
 #define ZXDH_PF_VENDOR_ID	0x1cf2
 #define ZXDH_PF_DEVICE_ID	0x8040
 #define ZXDH_VF_DEVICE_ID	0x8041
@@ -29,10 +32,57 @@
 #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;
 	struct devlink *devlink;
+	struct zxdh_irq_table irq_table;
+	struct zxdh_eq_table eq_table;
 	void *priv;
 };
 
@@ -54,6 +104,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);
@@ -72,4 +123,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_eq.c b/drivers/net/ethernet/zte/dinghai/zxdh_eq.c
new file mode 100644
index 0000000000000..f1d7eeec1ae4e
--- /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 0000000000000..4965ca15912ac
--- /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__ */
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
@@ -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 0000000000000..89f46d7b11c9c
--- /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__ */

3/2 2026/09/11 05:03 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 16164
							output: 16
							thoughts: 270
						

Thoughts:
**Analyzing the ZTE DingHai Ethernet Driver Patch for Syzkaller Potential**

Okay, so the task is to figure out if this kernel patch series warrants fuzzing with syzkaller. The patch adds a bunch of files under `drivers/net/ethernet/zte/dinghai`.  Specifically:  `Makefile`, `en_pf.c`, `en_pf.h`, `zxdh_eq.c`, `zxdh_eq.h`, `zxdh_irq.c`, and `zxdh_irq.h`.  First things first, let's establish the context of this driver.  It looks like this is a ZTE DingHai Ethernet driver. I need to quickly determine whether this is a physical PCI device, an ASIC, or something that's emulated or accessible in some way.  The deciding factor is whether it is a physical device, and if so, how accessible it is.  I need to dig into the code and the Kconfig options to get a good handle on that. That will dictate the fuzzing approach and the overall value of fuzzing this particular code.



4/2 2026/09/11 05:03 tool grepper
0m Args:
{
  "Expression": "DINGHAI"
}

Results:
{
  "Output": "MAINTAINERS=30195=F:\tsound/hda/codecs/senarytech.c\nMAINTAINERS-30196-\nMAINTAINERS:30197:ZTE DINGHAI ETHERNET DRIVER\nMAINTAINERS-30198-M:\tJunyang Han \u003chan.junyang@zte.com.cn\u003e\n--\ndrivers/net/ethernet/zte/Makefile-5-\ndrivers/net/ethernet/zte/Makefile:6:obj-$(CONFIG_DINGHAI) += dinghai/\n--\ndrivers/net/ethernet/zte/dinghai/Kconfig-5-\ndrivers/net/ethernet/zte/dinghai/Kconfig:6:config DINGHAI\ndrivers/net/ethernet/zte/dinghai/Kconfig-7-    bool \"ZTE DingHai Ethernet driver\"\n--\ndrivers/net/ethernet/zte/dinghai/Kconfig-20-\ndrivers/net/ethernet/zte/dinghai/Kconfig:21:if DINGHAI\ndrivers/net/ethernet/zte/dinghai/Kconfig-22-\ndrivers/net/ethernet/zte/dinghai/Kconfig:23:config DINGHAI_PF\ndrivers/net/ethernet/zte/dinghai/Kconfig-24-    tristate \"ZTE DingHai PF (Physical Function) driver\"\n--\ndrivers/net/ethernet/zte/dinghai/Kconfig-33-\ndrivers/net/ethernet/zte/dinghai/Kconfig:34:endif # DINGHAI\n--\ndrivers/net/ethernet/zte/dinghai/Makefile-5-\ndrivers/net/ethernet/zte/dinghai/Makefile:6:obj-$(CONFIG_DINGHAI_PF) += dinghai10e.o\ndrivers/net/ethernet/zte/dinghai/Makefile-7-dinghai10e-y := en_pf.o zxdh_irq.o zxdh_eq.o\n"
}

5/2 2026/09/11 05:03 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 17170
							output: 40
							thoughts: 20
						

Thoughts:
Here's a summary of my thought process, as if I were analyzing that Kconfig file:

**Diving into the ZTE Dinghai Kconfig**

Alright, let's crack open `drivers/net/ethernet/zte/dinghai/Kconfig`. Right away, I'm expecting this to define the configuration options for a network driver specific to ZTE's Dinghai platform. Given the path, I'm looking for the usual suspects: module enable flags, hardware-specific features like DMA settings or PHY configurations, perhaps even platform dependencies. I'll need to understand the relationship between this Kconfig and the other files in the directory. Are there any dependencies on other drivers or subsystems? Are there any conditional includes based on specific board revisions or hardware revisions within the Dinghai family? I'll be looking for things like `config ZTE_DINGHAI` as a top-level symbol, and then nested options detailing things like different MAC addresses, specific PHY support (like `CONFIG_PHY_XXXX`), any device-tree-related settings, or interrupt configurations. Any use of `select` statements to pull in other supporting modules will be important to note. And of course, I'll be scanning for any unusual or vendor-specific options that might give me insight into ZTE's design choices or customizations. This should be a quick scan, but I need to make sure I grasp the key configurable elements that control this driver's behavior and the conditions under which it can be enabled. Ultimately, the goal is to fully grasp what parameters can be set via this config file and how they impact the functionality of the ethernet device.



6/2 2026/09/11 05:03 tool read-file
0m Args:
{
  "File": "drivers/net/ethernet/zte/dinghai/Kconfig",
  "FirstLine": 1,
  "LineCount": 40
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\t#\n   3:\t# ZTE DingHai Ethernet driver configuration\n   4:\t#\n   5:\t\n   6:\tconfig DINGHAI\n   7:\t    bool \"ZTE DingHai Ethernet driver\"\n   8:\t    depends on PCI\n   9:\t    select NET_DEVLINK\n  10:\t    help\n  11:\t      This driver supports ZTE DingHai Ethernet devices.\n  12:\t\n  13:\t      DingHai is a high-performance Ethernet controller that supports\n  14:\t      multiple features including hardware offloading, SR-IOV, and\n  15:\t      advanced virtualization capabilities.\n  16:\t\n  17:\t      If you say Y here, you can select specific driver variants below.\n  18:\t\n  19:\t      If unsure, say N.\n  20:\t\n  21:\tif DINGHAI\n  22:\t\n  23:\tconfig DINGHAI_PF\n  24:\t    tristate \"ZTE DingHai PF (Physical Function) driver\"\n  25:\t    help\n  26:\t      This driver supports ZTE DingHai PCI Express Ethernet\n  27:\t      adapters (PF).\n  28:\t\n  29:\t      To compile this driver as a module, choose M here. The module\n  30:\t      will be named dinghai10e.\n  31:\t\n  32:\t      If unsure, say N.\n  33:\t\n  34:\tendif # DINGHAI\n"
}

7/2 2026/09/11 05:03 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 17966
							output: 44
							thoughts: 419
						

Thoughts:
**My Assessment of WorthFuzzing for the ZTE DingHai Driver**

Okay, let's break this down. The prompt tells me to consider returning `WorthFuzzing=false` if the modified code falls into certain negative criteria, specifically Section 2. Looking at that section, I need to see if the changes touch structurally unreachable hardware or unreachable execution paths.

First, I need to determine if ZTE DingHai falls under the category of "Structurally unreachable hardware." The criteria specify vendor-specific PCIe switches, SmartNICs, or GPU drivers requiring physical hardware that standard QEMU doesn't emulate. Based on the provided patch and my knowledge, it looks like:

*   The driver uses PCI vendor ID `0x1cf2` (ZTE Corporation), which is a clear indication of a vendor-specific device.
*   It's registered as a PCI driver via `module_pci_driver`, communicating via BAR registers, MSI-X interrupts, and likely with on-chip firmware.
*   Crucially, the prompt states that standard QEMU doesn't emulate ZTE DingHai PCI NICs.

Therefore, *prima facie*, this ZTE DingHai driver appears to be a vendor-specific PCI SmartNIC / Ethernet controller hardware driver that requires the physical hardware to function. It satisfies the "Structurally unreachable hardware" criteria.

Now, I'll quickly check for any virtual bus, software mock, or standard virtual device that could instantiate or reach this driver without the physical hardware. I'll take a quick look at `en_pf.c`, but based on the initial assessment, I'm confident that the answer is no. Unless I find something unexpected, my initial assessment stands.



8/2 2026/09/11 05:03 tool grepper
0m Args:
{
  "Expression": "pci_register_driver|module_pci_driver",
  "PathPrefix": "drivers/net/ethernet/zte/dinghai"
}

Results:
{
  "Output": "drivers/net/ethernet/zte/dinghai/en_pf.c=666=static struct pci_driver zxdh_pf_driver = {\n--\ndrivers/net/ethernet/zte/dinghai/en_pf.c-673-\ndrivers/net/ethernet/zte/dinghai/en_pf.c:674:module_pci_driver(zxdh_pf_driver);\n"
}

9/2 2026/09/11 05:03 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 18900
							output: 44
							thoughts: 24
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Diving into the PCI Table**

Okay, time to get to the heart of this. My initial thought is to zero in on the `zxdh_pf_pci_table` within `en_pf.c`. That table is usually where I find the core mappings and configurations related to PCI devices within the PF (Physical Function) context. I'm expecting to see the relevant entries that define how the system interacts with the underlying hardware, how interrupts are routed, device IDs and maybe even DMA settings. I need to understand exactly how the PCI devices are being initialized and managed here. Let's see if this table is the starting point for whatever I'm troubleshooting.



10/2 2026/09/11 05:03 tool read-file
0m Args:
{
  "File": "drivers/net/ethernet/zte/dinghai/en_pf.c",
  "FirstLine": 15,
  "LineCount": 30
}

Results:
{
  "Contents": "  15:\t#include \"en_pf.h\"\n  16:\t#include \"dh_queue.h\"\n  17:\t\n  18:\tMODULE_AUTHOR(\"Junyang Han \u003chan.junyang@zte.com.cn\u003e\");\n  19:\tMODULE_DESCRIPTION(\"ZTE DingHai series Ethernet driver\");\n  20:\tMODULE_LICENSE(\"GPL\");\n  21:\t\n  22:\tstatic const struct devlink_ops zxdh_pf_devlink_ops = {};\n  23:\t\n  24:\tstatic const struct pci_device_id zxdh_pf_pci_table[] = {\n  25:\t\t{ PCI_DEVICE(ZXDH_PF_VENDOR_ID, ZXDH_PF_DEVICE_ID) },\n  26:\t\t{ PCI_DEVICE(ZXDH_PF_VENDOR_ID, ZXDH_VF_DEVICE_ID) },\n  27:\t\t{ }\n  28:\t};\n  29:\t\n  30:\tMODULE_DEVICE_TABLE(pci, zxdh_pf_pci_table);\n  31:\t\n  32:\tstruct zxdh_pf_irq_table {\n  33:\t\tstruct zxdh_irq_pool *async_pool;\n  34:\t};\n  35:\t\n  36:\t/* IRQ compaction thresholds of the async pool, in number of queues. */\n  37:\t#define ZXDH_PF_ASYNC_IRQ_MIN_COMP\t0\n  38:\t#define ZXDH_PF_ASYNC_IRQ_MAX_COMP\t7\n  39:\t\n  40:\tvoid *zxdh_core_alloc_priv(struct zxdh_core_dev *zxdh_dev, size_t size)\n  41:\t{\n  42:\t\tvoid *priv = kzalloc(size, GFP_KERNEL);\n  43:\t\n  44:\t\tif (priv)\n"
}

11/2 2026/09/11 05:03 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 19616
							output: 121
							thoughts: 361
						

Thoughts:
**My Assessment of This Patch**

Okay, so I've got a patch here affecting files within `drivers/net/ethernet/zte/dinghai/`. Looking at the filenames, this appears to be the ZTE DingHai Ethernet driver. My immediate understanding is that this is for a specific PCIe NIC, and from experience, I know this specific hardware isn't something that's emulated in your standard fuzzing setups like QEMU or GCE.

Given that, I can confidently say this code is *structurally unreachable* during our typical fuzzing processes. Therefore, based on that hard fact, there is no value in making any fuzzing adjustments here. I'm going to set `WorthFuzzing` to `false`. There's no point in creating fuzzing symbols or enabling configurations specifically for this patch, as it is. Therefore, both `FocusSymbols` and `EnableConfigs` will be set to empty arrays. I've got my reasoning laid out pretty clearly: the changes are confined to this DingHai driver for specific PCIe hardware that isn't accessible in fuzzing. I will call `set-results`.



12/2 2026/09/11 05:03 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch series modifies the ZTE DingHai Ethernet controller driver (drivers/net/ethernet/zte/dinghai/), adding IRQ pool management and event queue plumbing for ZTE DingHai PCIe adapters (PCI vendor ID 0x1cf2). This driver requires vendor-specific physical PCIe hardware that is not emulated in standard virtualized fuzzing environments (QEMU/GCE), making the modified code structurally unreachable for syzkaller.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series modifies the ZTE DingHai Ethernet controller driver (drivers/net/ethernet/zte/dinghai/), adding IRQ pool management and event queue plumbing for ZTE DingHai PCIe adapters (PCI vendor ID 0x1cf2). This driver requires vendor-specific physical PCIe hardware that is not emulated in standard virtualized fuzzing environments (QEMU/GCE), making the modified code structurally unreachable for syzkaller.",
  "WorthFuzzing": false
}

13/1 2026/09/11 05:03 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)