AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/07 12:44 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces a new driver for the Nebula-matrix m18110-NIC/m18000-NIC family of network interface cards. This is a vendor-specific PCIe hardware driver that requires physical hardware not emulated in standard QEMU environments. Therefore, the code paths are impossible to reach and fuzz in virtualized environments.",
  "WorthFuzzing": false
}

1/1 2026/08/07 12:44 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 79ccd39435076bc81fd59eb19a6bafc0ea057744\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Aug 7 12:44:09 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/networking/device_drivers/ethernet/index.rst b/Documentation/networking/device_drivers/ethernet/index.rst\nindex 786a23c84b901..4d74b954b0ba8 100644\n--- a/Documentation/networking/device_drivers/ethernet/index.rst\n+++ b/Documentation/networking/device_drivers/ethernet/index.rst\n@@ -47,6 +47,7 @@ Contents:\n    meta/fbnic\n    microsoft/netvsc\n    mucse/rnpgbe\n+   nebula-matrix/nbl\n    netronome/nfp\n    pensando/ionic\n    pensando/ionic_rdma\ndiff --git a/Documentation/networking/device_drivers/ethernet/nebula-matrix/nbl.rst b/Documentation/networking/device_drivers/ethernet/nebula-matrix/nbl.rst\nnew file mode 100644\nindex 0000000000000..ff38302968c51\n--- /dev/null\n+++ b/Documentation/networking/device_drivers/ethernet/nebula-matrix/nbl.rst\n@@ -0,0 +1,28 @@\n+.. SPDX-License-Identifier: GPL-2.0\n+\n+================================================================\n+Linux Base Driver for Nebula-matrix m18110-NIC/m18000-NIC family\n+================================================================\n+\n+Overview:\n+=========\n+The m18110-NIC/m18000-NIC is a series of network interface cards for the Data\n+Center Area.\n+\n+The driver supports link-speed 100GbE/25GE/10GE.\n+\n+m18110-NIC/m18000-NIC devices support MSI-X interrupt vector for each Tx/Rx\n+queue and interrupt moderation.\n+\n+m18110-NIC/m18000-NIC devices support also various offload features such as\n+checksum offload, Receive-Side Scaling(RSS).\n+\n+Support\n+=======\n+\n+For more information about m18110-NIC/m18000-NIC, please visit the following URL:\n+https://www.nebula-matrix.com/snic_s1000_en\n+\n+If an issue is identified with the released source code on the supported kernel\n+with a supported adapter, email the specific information related to the issue to\n+open@nebula-matrix.com.\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 00e7b26e0a23f..ab0910a6836de 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -18597,6 +18597,16 @@ F:\tDocumentation/devicetree/bindings/hwmon/nuvoton,nct7363.yaml\n F:\tDocumentation/hwmon/nct7363.rst\n F:\tdrivers/hwmon/nct7363.c\n \n+NEBULA-MATRIX ETHERNET DRIVER (nebula-matrix)\n+M:\tIllusion Wang \u003cillusion.wang@nebula-matrix.com\u003e\n+M:\tDimon Zhao \u003cdimon.zhao@nebula-matrix.com\u003e\n+M:\tAlvin Wang \u003calvin.wang@nebula-matrix.com\u003e\n+M:\tSam Chen \u003csam.chen@nebula-matrix.com\u003e\n+L:\tnetdev@vger.kernel.org\n+S:\tMaintained\n+F:\tDocumentation/networking/device_drivers/ethernet/nebula-matrix/\n+F:\tdrivers/net/ethernet/nebula-matrix/\n+\n NETCONSOLE\n M:\tBreno Leitao \u003cleitao@debian.org\u003e\n S:\tMaintained\ndiff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig\nindex 78c79ad7bba55..5c21d02c4e11a 100644\n--- a/drivers/net/ethernet/Kconfig\n+++ b/drivers/net/ethernet/Kconfig\n@@ -130,6 +130,7 @@ config FEALNX\n \n source \"drivers/net/ethernet/ni/Kconfig\"\n source \"drivers/net/ethernet/natsemi/Kconfig\"\n+source \"drivers/net/ethernet/nebula-matrix/Kconfig\"\n source \"drivers/net/ethernet/netronome/Kconfig\"\n source \"drivers/net/ethernet/8390/Kconfig\"\n source \"drivers/net/ethernet/nvidia/Kconfig\"\ndiff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile\nindex bba55d9af3877..accce62a79a69 100644\n--- a/drivers/net/ethernet/Makefile\n+++ b/drivers/net/ethernet/Makefile\n@@ -67,6 +67,7 @@ obj-$(CONFIG_NET_VENDOR_MUCSE) += mucse/\n obj-$(CONFIG_NET_VENDOR_MYRI) += myricom/\n obj-$(CONFIG_FEALNX) += fealnx.o\n obj-$(CONFIG_NET_VENDOR_NATSEMI) += natsemi/\n+obj-$(CONFIG_NET_VENDOR_NEBULA_MATRIX) += nebula-matrix/\n obj-$(CONFIG_NET_VENDOR_NETRONOME) += netronome/\n obj-$(CONFIG_NET_VENDOR_NI) += ni/\n obj-$(CONFIG_NET_VENDOR_NVIDIA) += nvidia/\ndiff --git a/drivers/net/ethernet/nebula-matrix/Kconfig b/drivers/net/ethernet/nebula-matrix/Kconfig\nnew file mode 100644\nindex 0000000000000..f16e9663eaedf\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/Kconfig\n@@ -0,0 +1,31 @@\n+# SPDX-License-Identifier: GPL-2.0\n+#\n+# Nebula-matrix network device configuration\n+#\n+\n+config NET_VENDOR_NEBULA_MATRIX\n+\tbool \"Nebula-matrix devices\"\n+\tdefault y\n+\thelp\n+\t  If you have a network (Ethernet) card belonging to this class, say Y.\n+\t  Note that the answer to this question doesn't directly affect the\n+\t  kernel: saying N will just cause the configurator to skip all\n+\t  the questions about Nebula-matrix cards. If you say Y, you will be asked\n+\t  for your specific card in the following questions.\n+\n+if NET_VENDOR_NEBULA_MATRIX\n+\n+config NBL\n+\ttristate \"Nebula-matrix Ethernet Controller m18110/m18000 support\"\n+\tdepends on PCI \u0026\u0026 (64BIT || COMPILE_TEST) \u0026\u0026 !CPU_BIG_ENDIAN\n+\thelp\n+\t  This driver supports Nebula-matrix Ethernet Controller m18110/m18000\n+\t  Family of devices.\n+\n+\t  More specific information on configuring the driver is in\n+\t  \u003cfile:Documentation/networking/device_drivers/ethernet/nebula-matrix/nbl.rst\u003e.\n+\n+\t  To compile this driver as a module, choose M here. The module\n+\t  will be called nbl.\n+\n+endif # NET_VENDOR_NEBULA_MATRIX\ndiff --git a/drivers/net/ethernet/nebula-matrix/Makefile b/drivers/net/ethernet/nebula-matrix/Makefile\nnew file mode 100644\nindex 0000000000000..42cdf2db8f0cf\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/Makefile\n@@ -0,0 +1,6 @@\n+# SPDX-License-Identifier: GPL-2.0\n+#\n+# Makefile for the Nebula-matrix network device drivers.\n+#\n+\n+obj-$(CONFIG_NBL) += nbl/\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/Makefile b/drivers/net/ethernet/nebula-matrix/nbl/Makefile\nnew file mode 100644\nindex 0000000000000..0febda2098659\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/Makefile\n@@ -0,0 +1,15 @@\n+# SPDX-License-Identifier: GPL-2.0\n+# Copyright (c) 2025 Nebula Matrix Limited.\n+\n+obj-$(CONFIG_NBL) := nbl.o\n+\n+nbl-objs +=       nbl_common/nbl_common.o \\\n+\t\t\t\tnbl_channel/nbl_channel.o \\\n+\t\t\t\tnbl_hw/nbl_hw_leonis/nbl_hw_leonis.o \\\n+\t\t\t\tnbl_hw/nbl_hw_leonis/nbl_resource_leonis.o \\\n+\t\t\t\tnbl_hw/nbl_resource.o \\\n+\t\t\t\tnbl_hw/nbl_interrupt.o \\\n+\t\t\t\tnbl_hw/nbl_chip.o \\\n+\t\t\t\tnbl_core/nbl_dispatch.o \\\n+\t\t\t\tnbl_core/nbl_dev.o \\\n+\t\t\t\tnbl_main.o\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.c\nnew file mode 100644\nindex 0000000000000..3b7f3876a075d\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.c\n@@ -0,0 +1,1142 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/pci.h\u003e\n+#include \u003clinux/bits.h\u003e\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/atomic.h\u003e\n+#include \u003clinux/wait.h\u003e\n+#include \"nbl_channel.h\"\n+\n+static int nbl_chan_add_msg_handler(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t    u16 msg_type, nbl_chan_resp func,\n+\t\t\t\t    void *priv)\n+{\n+\tstruct nbl_chan_msg_node_data handler = { 0 };\n+\tint ret;\n+\n+\thandler.func = func;\n+\thandler.priv = priv;\n+\tret = nbl_common_alloc_hash_node(chan_mgt-\u003ehandle_hash_tbl, \u0026msg_type,\n+\t\t\t\t\t \u0026handler, NULL);\n+\n+\treturn ret;\n+}\n+\n+static int nbl_chan_init_msg_handler(struct nbl_channel_mgt *chan_mgt)\n+{\n+\tstruct nbl_common_info *common = chan_mgt-\u003ecommon;\n+\tstruct nbl_hash_tbl_key tbl_key = { 0 };\n+\n+\ttbl_key.dev = common-\u003edev;\n+\ttbl_key.key_size = sizeof(u16);\n+\ttbl_key.data_size = sizeof(struct nbl_chan_msg_node_data);\n+\ttbl_key.bucket_size = NBL_CHAN_HANDLER_TBL_BUCKET_SIZE;\n+\n+\tchan_mgt-\u003ehandle_hash_tbl = nbl_common_init_hash_table(\u0026tbl_key);\n+\tif (!chan_mgt-\u003ehandle_hash_tbl)\n+\t\treturn -ENOMEM;\n+\n+\treturn 0;\n+}\n+\n+static void nbl_chan_remove_msg_handler(struct nbl_channel_mgt *chan_mgt)\n+{\n+\tif (!chan_mgt-\u003ehandle_hash_tbl)\n+\t\treturn;\n+\tnbl_common_remove_hash_table(chan_mgt-\u003ehandle_hash_tbl);\n+\tchan_mgt-\u003ehandle_hash_tbl = NULL;\n+}\n+\n+static void nbl_chan_init_queue_param(struct nbl_chan_info *chan_info,\n+\t\t\t\t      u16 num_txq_entries, u16 num_rxq_entries,\n+\t\t\t\t      u16 txq_buf_size, u16 rxq_buf_size)\n+{\n+\tchan_info-\u003enum_txq_entries = num_txq_entries;\n+\tchan_info-\u003enum_rxq_entries = num_rxq_entries;\n+\tchan_info-\u003etxq_buf_size = txq_buf_size;\n+\tchan_info-\u003erxq_buf_size = rxq_buf_size;\n+\tatomic_set(\u0026chan_info-\u003einflight_tx_cnt, 0);\n+\tWRITE_ONCE(chan_info-\u003eshutdown, false);\n+}\n+\n+static int nbl_chan_init_tx_queue(struct nbl_common_info *common,\n+\t\t\t\t  struct nbl_chan_info *chan_info)\n+{\n+\tstruct nbl_chan_ring *txq = \u0026chan_info-\u003etxq;\n+\tstruct device *dev = common-\u003edev;\n+\tsize_t size =\n+\t\tchan_info-\u003enum_txq_entries * sizeof(struct nbl_chan_tx_desc);\n+\tu16 i;\n+\n+\ttxq-\u003edesc.tx_desc =\n+\t\tdmam_alloc_coherent(dev, size, \u0026txq-\u003edma, GFP_KERNEL);\n+\tif (!txq-\u003edesc.tx_desc)\n+\t\treturn -ENOMEM;\n+\n+\tchan_info-\u003ewait = devm_kcalloc(dev, chan_info-\u003enum_txq_entries,\n+\t\t\t\t       sizeof(*chan_info-\u003ewait), GFP_KERNEL);\n+\tif (!chan_info-\u003ewait)\n+\t\treturn -ENOMEM;\n+\tfor (i = 0; i \u003c chan_info-\u003enum_txq_entries; i++) {\n+\t\tinit_waitqueue_head(\u0026chan_info-\u003ewait[i].wait_queue);\n+\t\tchan_info-\u003ewait[i].status = NBL_MBX_STATUS_IDLE;\n+\t\tspin_lock_init(\u0026chan_info-\u003ewait[i].status_lock);\n+\t}\n+\n+\ttxq-\u003ebuf = devm_kcalloc(dev, chan_info-\u003enum_txq_entries,\n+\t\t\t\tsizeof(*txq-\u003ebuf), GFP_KERNEL);\n+\tif (!txq-\u003ebuf)\n+\t\treturn -ENOMEM;\n+\n+\treturn 0;\n+}\n+\n+static int nbl_chan_init_rx_queue(struct nbl_common_info *common,\n+\t\t\t\t  struct nbl_chan_info *chan_info)\n+{\n+\tstruct nbl_chan_ring *rxq = \u0026chan_info-\u003erxq;\n+\tstruct device *dev = common-\u003edev;\n+\tsize_t size =\n+\t\tchan_info-\u003enum_rxq_entries * sizeof(struct nbl_chan_rx_desc);\n+\n+\trxq-\u003edesc.rx_desc =\n+\t\tdmam_alloc_coherent(dev, size, \u0026rxq-\u003edma, GFP_KERNEL);\n+\tif (!rxq-\u003edesc.rx_desc) {\n+\t\tdev_err_ratelimited(dev,\n+\t\t\t\t    \"Allocate DMA for chan rx descriptor ring failed\\n\");\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\trxq-\u003ebuf = devm_kcalloc(dev, chan_info-\u003enum_rxq_entries,\n+\t\t\t\tsizeof(*rxq-\u003ebuf), GFP_KERNEL);\n+\tif (!rxq-\u003ebuf)\n+\t\treturn -ENOMEM;\n+\n+\treturn 0;\n+}\n+\n+static int nbl_chan_init_queue(struct nbl_common_info *common,\n+\t\t\t       struct nbl_chan_info *chan_info)\n+{\n+\tint err;\n+\n+\terr = nbl_chan_init_tx_queue(common, chan_info);\n+\tif (err)\n+\t\treturn err;\n+\n+\terr = nbl_chan_init_rx_queue(common, chan_info);\n+\n+\treturn err;\n+}\n+\n+static void nbl_chan_config_queue(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t  struct nbl_chan_info *chan_info, bool tx)\n+{\n+\tstruct nbl_hw_ops *hw_ops = chan_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tstruct nbl_hw_mgt *p = chan_mgt-\u003ehw_ops_tbl-\u003epriv;\n+\tstruct nbl_chan_ring *ring;\n+\tdma_addr_t addr;\n+\tint size_bwid;\n+\n+\tif (tx)\n+\t\tring = \u0026chan_info-\u003etxq;\n+\telse\n+\t\tring = \u0026chan_info-\u003erxq;\n+\taddr = ring-\u003edma;\n+\tif (tx) {\n+\t\tsize_bwid = ilog2(chan_info-\u003enum_txq_entries);\n+\t\thw_ops-\u003econfig_mailbox_txq(p, addr, size_bwid);\n+\t} else {\n+\t\tsize_bwid = ilog2(chan_info-\u003enum_rxq_entries);\n+\t\thw_ops-\u003econfig_mailbox_rxq(p, addr, size_bwid);\n+\t}\n+}\n+\n+static int nbl_chan_alloc_all_tx_bufs(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t      struct nbl_chan_info *chan_info)\n+{\n+\tstruct nbl_chan_ring *txq = \u0026chan_info-\u003etxq;\n+\tstruct device *dev = chan_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_chan_buf *buf;\n+\tu16 i;\n+\n+\tfor (i = 0; i \u003c chan_info-\u003enum_txq_entries; i++) {\n+\t\tbuf = \u0026txq-\u003ebuf[i];\n+\t\tbuf-\u003eva = dmam_alloc_coherent(dev, chan_info-\u003etxq_buf_size,\n+\t\t\t\t\t      \u0026buf-\u003epa, GFP_KERNEL);\n+\t\tif (!buf-\u003eva) {\n+\t\t\tdev_err_ratelimited(dev,\n+\t\t\t\t\t    \"Allocate buffer for chan tx queue failed\\n\");\n+\t\t\treturn -ENOMEM;\n+\t\t}\n+\t}\n+\n+\ttxq-\u003enext_to_clean = 0;\n+\ttxq-\u003enext_to_use = 0;\n+\ttxq-\u003etail_ptr = 0;\n+\n+\treturn 0;\n+}\n+\n+static void nbl_chan_cfg_qinfo_map_table(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t\t u8 bus, u8 devid)\n+{\n+\tstruct nbl_hw_ops *hw_ops = chan_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tstruct nbl_hw_mgt *p = chan_mgt-\u003ehw_ops_tbl-\u003epriv;\n+\tu32 pf_mask = 0;\n+\tu8 func_id;\n+\n+\thw_ops-\u003eget_host_pf_mask(p, \u0026pf_mask);\n+\tfor (func_id = 0; func_id \u003c NBL_MAX_PF; func_id++) {\n+\t\tif (!(pf_mask \u0026 (1 \u003c\u003c func_id)))\n+\t\t\thw_ops-\u003ecfg_mailbox_qinfo(p, func_id, bus,\n+\t\t\t\t\t\t  devid, func_id);\n+\t}\n+}\n+\n+static int nbl_chan_alloc_all_rx_bufs(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t      struct nbl_chan_info *chan_info)\n+{\n+\tstruct nbl_chan_ring *rxq = \u0026chan_info-\u003erxq;\n+\tstruct device *dev = chan_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_chan_rx_desc *desc;\n+\tstruct nbl_chan_buf *buf;\n+\tu16 i;\n+\n+\tfor (i = 0; i \u003c chan_info-\u003enum_rxq_entries; i++) {\n+\t\tbuf = \u0026rxq-\u003ebuf[i];\n+\t\tbuf-\u003eva = dmam_alloc_coherent(dev, chan_info-\u003erxq_buf_size,\n+\t\t\t\t\t      \u0026buf-\u003epa, GFP_KERNEL);\n+\t\tif (!buf-\u003eva) {\n+\t\t\tdev_err_ratelimited(dev,\n+\t\t\t\t\t    \"Allocate buffer for chan rx queue failed\\n\");\n+\t\t\tgoto err;\n+\t\t}\n+\t}\n+\n+\tdesc = rxq-\u003edesc.rx_desc;\n+\t/*\n+\t * RX ring adopts empty-ring convention: reserve one unused entry\n+\t * to distinguish queue FULL vs queue EMPTY.\n+\t * We initialize entries [0 ... num_rxq_entries - 2],\n+\t * leave the last entry uninitialized as the hole marker.\n+\t */\n+\tfor (i = 0; i \u003c chan_info-\u003enum_rxq_entries - 1; i++) {\n+\t\tbuf = \u0026rxq-\u003ebuf[i];\n+\t\tdesc[i].buf_addr = cpu_to_le64(buf-\u003epa);\n+\t\tdesc[i].buf_len = cpu_to_le32(chan_info-\u003erxq_buf_size);\n+\t\tdesc[i].flags = cpu_to_le16(BIT(NBL_CHAN_RX_DESC_AVAIL));\n+\t}\n+\n+\trxq-\u003enext_to_clean = 0;\n+\t/* Reserve last slot as hole to differentiate full/empty status */\n+\trxq-\u003enext_to_use = chan_info-\u003enum_rxq_entries - 1;\n+\trxq-\u003etail_ptr = chan_info-\u003enum_rxq_entries - 1;\n+\n+\treturn 0;\n+err:\n+\treturn -ENOMEM;\n+}\n+\n+static int nbl_chan_alloc_all_bufs(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t   struct nbl_chan_info *chan_info)\n+{\n+\tint err;\n+\n+\terr = nbl_chan_alloc_all_tx_bufs(chan_mgt, chan_info);\n+\tif (err)\n+\t\treturn err;\n+\terr = nbl_chan_alloc_all_rx_bufs(chan_mgt, chan_info);\n+\n+\treturn err;\n+}\n+\n+static void nbl_chan_stop_queue(struct nbl_channel_mgt *chan_mgt)\n+{\n+\tstruct nbl_hw_ops *hw_ops = chan_mgt-\u003ehw_ops_tbl-\u003eops;\n+\n+\thw_ops-\u003estop_mailbox_rxq(chan_mgt-\u003ehw_ops_tbl-\u003epriv);\n+\thw_ops-\u003estop_mailbox_txq(chan_mgt-\u003ehw_ops_tbl-\u003epriv);\n+}\n+\n+static int nbl_chan_teardown_queue(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t   u8 chan_type)\n+{\n+\tstruct nbl_chan_info *chan_info = chan_mgt-\u003echan_info[chan_type];\n+\tstruct nbl_chan_waitqueue_head *wait_head;\n+\tunsigned long wait_start, wait_timeout;\n+\tint ret = 0;\n+\tu16 i;\n+\n+\t/* Step 1: Mark shutdown flag, reject all new send requests */\n+\tWRITE_ONCE(chan_info-\u003eshutdown, true);\n+\t/*\n+\t * Full memory barrier: Ensure the write to 'shutdown' becomes visible\n+\t * to all other CPUs BEFORE we start waiting on inflight_tx_cnt.\n+\t * Peer send_msg threads check shutdown flag prior to taking new work;\n+\t * this barrier prevents reordering that would let send paths observe\n+\t * inflight_tx_cnt == 0 but still accept new messages.\n+\t */\n+\tsmp_mb();\n+\n+\tfor (i = 0; i \u003c chan_info-\u003enum_txq_entries; i++) {\n+\t\twait_head = \u0026chan_info-\u003ewait[i];\n+\t\tspin_lock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\t/* Only wake threads that are actually waiting */\n+\t\tif (READ_ONCE(wait_head-\u003estatus) == NBL_MBX_STATUS_WAITING) {\n+\t\t\t/* Mark as timeout so waking threads know to abort */\n+\t\t\tWRITE_ONCE(wait_head-\u003estatus, NBL_MBX_STATUS_TIMEOUT);\n+\t\t\tWRITE_ONCE(wait_head-\u003eacked, 1);\n+\t\t\tWRITE_ONCE(wait_head-\u003eack_err, (s32)-EIO);\n+\t\t\twake_up(\u0026wait_head-\u003ewait_queue);\n+\t\t}\n+\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t}\n+\t/* Stop hardware queues */\n+\tnbl_chan_stop_queue(chan_mgt);\n+\n+\t/*\n+\t * Step 2: Wait all in-flight send_msg threads exit FIRST.\n+\t * Guarantee no further queue_work() can be triggered during cleanup.\n+\t * Add timeout to avoid permanent blocking.\n+\t */\n+\twait_start = jiffies;\n+\twait_timeout = msecs_to_jiffies(5000);\n+\twhile (atomic_read(\u0026chan_info-\u003einflight_tx_cnt) != 0) {\n+\t\tif (time_after(jiffies, wait_start + wait_timeout)) {\n+\t\t\tdev_warn(chan_mgt-\u003ecommon-\u003edev,\n+\t\t\t\t \"teardown: wait inflight_tx_cnt timeout, force continue\\n\");\n+\t\t\tret = -ETIMEDOUT;\n+\t\t\tbreak;\n+\t\t}\n+\t\tcpu_relax();\n+\t\tusleep_range(1000, 2000);\n+\t}\n+\n+\t/* Step3: All send paths drained, safely cancel cleanup work */\n+\tif (chan_info-\u003eclean_task) {\n+\t\tcancel_work_sync(chan_info-\u003eclean_task);\n+\t\tchan_info-\u003eclean_task = NULL;\n+\t}\n+\treturn ret;\n+}\n+\n+static int nbl_chan_setup_queue(struct nbl_channel_mgt *chan_mgt, u8 chan_type)\n+{\n+\tstruct nbl_chan_info *chan_info = chan_mgt-\u003echan_info[chan_type];\n+\tstruct nbl_hw_ops *hw_ops = chan_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tstruct nbl_common_info *common = chan_mgt-\u003ecommon;\n+\tstruct nbl_chan_ring *rxq = \u0026chan_info-\u003erxq;\n+\tint err;\n+\n+\tif (READ_ONCE(chan_info-\u003einit_done))\n+\t\treturn 0;\n+\terr = devm_mutex_init(common-\u003edev, \u0026chan_info-\u003etxq_lock);\n+\tif (err)\n+\t\treturn err;\n+\tnbl_chan_init_queue_param(chan_info, NBL_CHAN_QUEUE_LEN,\n+\t\t\t\t  NBL_CHAN_QUEUE_LEN, NBL_CHAN_BUF_LEN,\n+\t\t\t\t  NBL_CHAN_BUF_LEN);\n+\terr = nbl_chan_init_queue(common, chan_info);\n+\tif (err)\n+\t\treturn err;\n+\terr = nbl_chan_alloc_all_bufs(chan_mgt, chan_info);\n+\tif (err)\n+\t\treturn err;\n+\tnbl_chan_config_queue(chan_mgt, chan_info, true); /* tx */\n+\tnbl_chan_config_queue(chan_mgt, chan_info, false); /* rx */\n+\tnbl_chan_update_tail_ptr(hw_ops, chan_mgt-\u003ehw_ops_tbl-\u003epriv,\n+\t\t\t\t rxq-\u003etail_ptr, NBL_MB_RX_QID);\n+\tWRITE_ONCE(chan_info-\u003einit_done, true);\n+\treturn 0;\n+}\n+\n+static int nbl_chan_update_txqueue(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t   struct nbl_chan_info *chan_info,\n+\t\t\t\t   struct nbl_chan_tx_param *param)\n+{\n+\tstruct nbl_chan_ring *txq = \u0026chan_info-\u003etxq;\n+\tstruct nbl_chan_tx_desc *tx_desc =\n+\t\tNBL_CHAN_TX_RING_TO_DESC(txq, txq-\u003enext_to_use);\n+\tstruct nbl_chan_buf *tx_buf =\n+\t\tNBL_CHAN_TX_RING_TO_BUF(txq, txq-\u003enext_to_use);\n+\n+\tif (param-\u003earg_len \u003e NBL_CHAN_BUF_LEN - sizeof(*tx_desc))\n+\t\treturn -EINVAL;\n+\n+\ttx_desc-\u003edstid = cpu_to_le16(param-\u003edstid);\n+\ttx_desc-\u003emsg_type = cpu_to_le16(param-\u003emsg_type);\n+\ttx_desc-\u003emsgid = cpu_to_le16(param-\u003emsgid);\n+\n+\tif (param-\u003earg_len \u003e NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN) {\n+\t\tmemcpy(tx_buf-\u003eva, param-\u003earg, param-\u003earg_len);\n+\t\ttx_desc-\u003ebuf_addr = cpu_to_le64(tx_buf-\u003epa);\n+\t\ttx_desc-\u003ebuf_len = cpu_to_le16(param-\u003earg_len);\n+\t\ttx_desc-\u003edata_len = 0;\n+\t\tmemset(tx_desc-\u003edata, 0, sizeof(tx_desc-\u003edata));\n+\t} else {\n+\t\tmemset(tx_desc-\u003edata, 0, sizeof(tx_desc-\u003edata));\n+\t\tmemset(\u0026tx_desc-\u003ebuf_addr, 0, sizeof(tx_desc-\u003ebuf_addr));\n+\t\tmemcpy(tx_desc-\u003edata, param-\u003earg, param-\u003earg_len);\n+\t\ttx_desc-\u003ebuf_len = 0;\n+\t\ttx_desc-\u003edata_len = cpu_to_le16(param-\u003earg_len);\n+\t}\n+\tdma_wmb();\n+\ttx_desc-\u003eflags = cpu_to_le16(BIT(NBL_CHAN_TX_DESC_AVAIL));\n+\n+\ttxq-\u003enext_to_use =\n+\t\tNBL_NEXT_ID(txq-\u003enext_to_use, chan_info-\u003enum_txq_entries - 1);\n+\ttxq-\u003etail_ptr++;\n+\n+\treturn 0;\n+}\n+\n+static int nbl_chan_kick_tx_ring(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t struct nbl_chan_info *chan_info)\n+{\n+\tstruct nbl_hw_ops *hw_ops = chan_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tstruct nbl_chan_ring *txq = \u0026chan_info-\u003etxq;\n+\tstruct device *dev = chan_mgt-\u003ecommon-\u003edev;\n+\tint max_retries = NBL_CHAN_TX_WAIT_TIMES;\n+\tstruct nbl_chan_tx_desc *tx_desc;\n+\tint retry_count = 0;\n+\tu16 msg_type;\n+\n+\tnbl_chan_update_tail_ptr(hw_ops, chan_mgt-\u003ehw_ops_tbl-\u003epriv,\n+\t\t\t\t txq-\u003etail_ptr, NBL_MB_TX_QID);\n+\n+\ttx_desc = NBL_CHAN_TX_RING_TO_DESC(txq, txq-\u003enext_to_clean);\n+\twhile (retry_count \u003c max_retries) {\n+\t\tif (READ_ONCE(chan_info-\u003eshutdown))\n+\t\t\treturn -ESHUTDOWN;\n+\n+\t\t/* Ensure HW written descriptor visible to CPU */\n+\t\tsmp_rmb();\n+\t\tif (le16_to_cpu(READ_ONCE(tx_desc-\u003eflags)) \u0026\n+\t\t    BIT(NBL_CHAN_TX_DESC_USED)) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tretry_count++;\n+\t\tif (retry_count == max_retries) {\n+\t\t\tmsg_type = le16_to_cpu(READ_ONCE(tx_desc-\u003emsg_type));\n+\t\t\tdev_err_ratelimited(dev, \"chan send msg type: %d timeout\\n\",\n+\t\t\t\t\t    msg_type);\n+\t\t\ttxq-\u003enext_to_clean = txq-\u003enext_to_use;\n+\t\t\treturn -ETIMEDOUT;\n+\t\t}\n+\t\tusleep_range(NBL_CHAN_TX_WAIT_US, NBL_CHAN_TX_WAIT_US_MAX);\n+\t}\n+\n+\ttxq-\u003enext_to_clean = txq-\u003enext_to_use;\n+\n+\treturn 0;\n+}\n+\n+static void nbl_chan_recv_ack_msg(void *priv, u16 srcid, u16 msgid, void *data,\n+\t\t\t\t  u32 data_len)\n+{\n+\tstruct nbl_channel_mgt *chan_mgt = (struct nbl_channel_mgt *)priv;\n+\tstruct nbl_chan_waitqueue_head *wait_head = NULL;\n+\tstruct device *dev = chan_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_chan_info *chan_info =\n+\t\tchan_mgt-\u003echan_info[NBL_CHAN_TYPE_MAILBOX];\n+\tu32 ack_datalen, ack_msgtype = 0;\n+\tu32 *payload = data;\n+\tu16 ack_msgid = 0;\n+\tu32 copy_len;\n+\n+\tif (READ_ONCE(chan_info-\u003eshutdown))\n+\t\treturn;\n+\tif (data_len \u003e NBL_CHAN_BUF_LEN ||\n+\t    data_len \u003c NBL_CHAN_ACK_HEAD_LEN * sizeof(u32)) {\n+\t\tdev_err_ratelimited(dev, \"Invalid ACK data_len: %u\\n\",\n+\t\t\t\t    data_len);\n+\t\treturn;\n+\t}\n+\tack_datalen = data_len - NBL_CHAN_ACK_HEAD_LEN * sizeof(u32);\n+\tack_msgtype = le16_to_cpu(*(__le16 *)(payload + NBL_CHAN_MSG_TYPE_POS));\n+\tack_msgid = le16_to_cpu(*(__le16 *)(payload + NBL_CHAN_MSG_ID_POS));\n+\tif (FIELD_GET(NBL_CHAN_MSGID_LOC_MASK, ack_msgid) \u003e=\n+\t    chan_info-\u003enum_txq_entries) {\n+\t\tdev_err_ratelimited(dev, \"chan recv msg id: %d err\\n\",\n+\t\t\t\t    ack_msgid);\n+\t\treturn;\n+\t}\n+\twait_head =\n+\t\t\u0026chan_info-\u003ewait[FIELD_GET(NBL_CHAN_MSGID_LOC_MASK, ack_msgid)];\n+\tspin_lock_irq(\u0026wait_head-\u003estatus_lock);\n+\tif (srcid != READ_ONCE(wait_head-\u003edstid)) {\n+\t\t/* Do not modify the status; the slot remains WAITING,\n+\t\t * and the sender will time out normally\n+\t\t */\n+\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\tdev_err_ratelimited(dev, \"ACK srcid=%u != dstid=%u, rejecting\\n\",\n+\t\t\t\t    srcid, READ_ONCE(wait_head-\u003edstid));\n+\t\treturn;\n+\t}\n+\tif (READ_ONCE(wait_head-\u003estatus) != NBL_MBX_STATUS_WAITING) {\n+\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\tdev_err_ratelimited(dev,\n+\t\t\t\t    \"Skip ack with invalid status, wait msgtype:%u msg_index:%u status:%d ack_data_len:%d, ack msgtype:%u msgid:%u datalen:%d\\n\",\n+\t\t\t\t    READ_ONCE(wait_head-\u003emsg_type),\n+\t\t\t\t    READ_ONCE(wait_head-\u003emsg_index),\n+\t\t\t\t    READ_ONCE(wait_head-\u003estatus),\n+\t\t\t\t    READ_ONCE(wait_head-\u003eack_data_len),\n+\t\t\t\t    ack_msgtype, ack_msgid, ack_datalen);\n+\t\treturn;\n+\t}\n+\n+\tif (READ_ONCE(wait_head-\u003emsg_type) != ack_msgtype) {\n+\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\tdev_err_ratelimited(dev,\n+\t\t\t\t    \"Skip ack msg type mismatch, wait msgtype:%u msg_index:%u status:%d ack_data_len:%d, ack msgtype:%u msgid:%u datalen:%d\\n\",\n+\t\t\t\t    READ_ONCE(wait_head-\u003emsg_type),\n+\t\t\t\t    READ_ONCE(wait_head-\u003emsg_index),\n+\t\t\t\t    READ_ONCE(wait_head-\u003estatus),\n+\t\t\t\t    READ_ONCE(wait_head-\u003eack_data_len),\n+\t\t\t\t    ack_msgtype, ack_msgid, ack_datalen);\n+\t\treturn;\n+\t}\n+\tif (FIELD_GET(NBL_CHAN_MSGID_INDEX_MASK, ack_msgid) !=\n+\t    READ_ONCE(wait_head-\u003emsg_index)) {\n+\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\tdev_err_ratelimited(dev,\n+\t\t\t\t    \"Stale ACK: expected index=%u, got msgid %u\\n\",\n+\t\t\t\t    READ_ONCE(wait_head-\u003emsg_index), ack_msgid);\n+\t\treturn;\n+\t}\n+\n+\twait_head-\u003eack_err =\n+\t\t(s32)le32_to_cpu(*(__le32 *)(payload + NBL_CHAN_ACK_RET_POS));\n+\n+\tcopy_len = min_t(u32, READ_ONCE(wait_head-\u003eack_data_len), ack_datalen);\n+\tif (wait_head-\u003eack_err \u003e= 0 \u0026\u0026 copy_len \u003e 0) {\n+\t\tif (!READ_ONCE(wait_head-\u003eack_data)) {\n+\t\t\tdev_err_ratelimited(dev, \"ACK payload dropped: ack_data is NULL\\n\");\n+\t\t\twait_head-\u003eack_data_len = 0;\n+\t\t\tgoto ack_done;\n+\t\t}\n+\t\tmemcpy((char *)wait_head-\u003eack_data,\n+\t\t       payload + NBL_CHAN_ACK_HEAD_LEN, copy_len);\n+\t\twait_head-\u003eack_data_len = (u16)copy_len;\n+\t} else {\n+\t\twait_head-\u003eack_data_len = 0;\n+\t}\n+ack_done:\n+\tWRITE_ONCE(wait_head-\u003eacked, 1);\n+\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\tif (READ_ONCE(wait_head-\u003eacked))\n+\t\twake_up(\u0026wait_head-\u003ewait_queue);\n+}\n+\n+static void nbl_chan_recv_msg(struct nbl_channel_mgt *chan_mgt, void *data)\n+{\n+\tstruct device *dev = chan_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_chan_msg_node_data *msg_handler;\n+\tu16 msg_type, payload_len, srcid, msgid;\n+\tstruct nbl_chan_info *chan_info =\n+\t\tchan_mgt-\u003echan_info[NBL_CHAN_TYPE_MAILBOX];\n+\tstruct nbl_chan_tx_desc *tx_desc;\n+\tvoid *payload;\n+\tsize_t size;\n+\n+\tif (READ_ONCE(chan_info-\u003eshutdown))\n+\t\treturn;\n+\ttx_desc = data;\n+\tmsg_type = le16_to_cpu(READ_ONCE(tx_desc-\u003emsg_type));\n+\tdev_dbg(dev, \"recv msg_type: %d\\n\", msg_type);\n+\n+\tsrcid = le16_to_cpu(READ_ONCE(tx_desc-\u003esrcid));\n+\tmsgid = le16_to_cpu(READ_ONCE(tx_desc-\u003emsgid));\n+\t/* Only check if the value exceeds the maximum, relying on the hash\n+\t * table to filter invalid message IDs.\n+\t * The gap values are reserved for future protocol extensions.\n+\t */\n+\tif (msg_type \u003e= NBL_CHAN_MSG_MAILBOX_MAX)\n+\t\treturn;\n+\n+\tif (le16_to_cpu(READ_ONCE(tx_desc-\u003edata_len))) {\n+\t\tpayload_len = le16_to_cpu(READ_ONCE(tx_desc-\u003edata_len));\n+\t\tif (payload_len \u003e NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN) {\n+\t\t\tdev_err_ratelimited(dev,\n+\t\t\t\t\t    \"data_len=%u exceeds embedded buffer size=%u\\n\",\n+\t\t\t\t\t    payload_len,\n+\t\t\t\t\t    NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN);\n+\t\t\treturn;\n+\t\t}\n+\t\tpayload = tx_desc-\u003edata;\n+\t} else {\n+\t\tpayload_len = le16_to_cpu(READ_ONCE(tx_desc-\u003ebuf_len));\n+\t\tsize = NBL_CHAN_BUF_LEN - sizeof(*tx_desc);\n+\t\tif (payload_len \u003e size) {\n+\t\t\tdev_err_ratelimited(dev,\n+\t\t\t\t\t    \"buf_len=%u exceeds external buffer size=%zu\\n\",\n+\t\t\t\t\t    payload_len,\n+\t\t\t\t\t    size);\n+\t\t\treturn;\n+\t\t}\n+\t\tpayload = tx_desc + 1;\n+\t}\n+\n+\tmsg_handler =\n+\t\tnbl_common_get_hash_node(chan_mgt-\u003ehandle_hash_tbl, \u0026msg_type);\n+\tif (!msg_handler || !msg_handler-\u003efunc) {\n+\t\tdev_err_ratelimited(dev,\n+\t\t\t\t    \"No handler for msg_type: %u (srcid=%u, msgid=%u)\\n\",\n+\t\t\t\t    msg_type, srcid, msgid);\n+\t\treturn;\n+\t}\n+\tmsg_handler-\u003efunc(msg_handler-\u003epriv, srcid, msgid, payload,\n+\t\t\t  payload_len);\n+}\n+\n+static void nbl_chan_advance_rx_ring(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t     struct nbl_chan_info *chan_info,\n+\t\t\t\t     struct nbl_chan_ring *rxq)\n+{\n+\tstruct nbl_hw_ops *hw_ops = chan_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tstruct nbl_chan_rx_desc *rx_desc;\n+\tstruct nbl_chan_buf *rx_buf;\n+\tu16 next_to_use;\n+\n+\tnext_to_use = rxq-\u003enext_to_use;\n+\trx_desc = NBL_CHAN_RX_RING_TO_DESC(rxq, next_to_use);\n+\trx_buf = NBL_CHAN_RX_RING_TO_BUF(rxq, next_to_use);\n+\n+\trx_desc-\u003ebuf_addr = cpu_to_le64(rx_buf-\u003epa);\n+\trx_desc-\u003ebuf_len = cpu_to_le32(chan_info-\u003erxq_buf_size);\n+\n+\t/*\n+\t * DMA Write Memory Barrier:\n+\t * Ensures all previous DMA-mapped writes (buffer address/length)\n+\t * are completed before the descriptor flags are updated.\n+\t * This prevents hardware from seeing a partially updated descriptor\n+\t * where flags are set but buffer info isn't ready yet.\n+\t */\n+\tdma_wmb();\n+\n+\trx_desc-\u003eflags = cpu_to_le16(BIT(NBL_CHAN_RX_DESC_AVAIL));\n+\n+\trxq-\u003enext_to_use++;\n+\tif (rxq-\u003enext_to_use == chan_info-\u003enum_rxq_entries)\n+\t\trxq-\u003enext_to_use = 0;\n+\trxq-\u003etail_ptr++;\n+\n+\tnbl_chan_update_tail_ptr(hw_ops, chan_mgt-\u003ehw_ops_tbl-\u003epriv,\n+\t\t\t\t rxq-\u003etail_ptr, NBL_MB_RX_QID);\n+}\n+\n+static void nbl_chan_clean_queue(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t struct nbl_chan_info *chan_info)\n+{\n+\tstruct nbl_common_info *common = chan_mgt-\u003ecommon;\n+\tstruct nbl_chan_ring *rxq = \u0026chan_info-\u003erxq;\n+\tstruct device *dev = chan_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_chan_rx_desc *rx_desc;\n+\tstruct nbl_chan_buf *rx_buf;\n+\tstruct work_struct *task;\n+\tbool more_work = false;\n+\tu16 next_to_clean;\n+\tu32 budget = 64;\n+\tu16 flags;\n+\n+\tnext_to_clean = rxq-\u003enext_to_clean;\n+\trx_desc = NBL_CHAN_RX_RING_TO_DESC(rxq, next_to_clean);\n+\trx_buf = NBL_CHAN_RX_RING_TO_BUF(rxq, next_to_clean);\n+\twhile (le16_to_cpu(READ_ONCE(rx_desc-\u003eflags)) \u0026\n+\t       BIT(NBL_CHAN_RX_DESC_USED)) {\n+\t\tflags = le16_to_cpu(READ_ONCE(rx_desc-\u003eflags));\n+\n+\t\tif (READ_ONCE(chan_info-\u003eshutdown))\n+\t\t\tbreak;\n+\t\tif (!(flags \u0026 BIT(NBL_CHAN_RX_DESC_WRITE)))\n+\t\t\tdev_dbg(dev,\n+\t\t\t\t\"mailbox rx flag 0x%x missing NBL_CHAN_RX_DESC_WRITE\\n\",\n+\t\t\t\tflags);\n+\n+\t\tdma_rmb();\n+\t\tnbl_chan_recv_msg(chan_mgt, rx_buf-\u003eva);\n+\t\tnbl_chan_advance_rx_ring(chan_mgt, chan_info, rxq);\n+\t\tnext_to_clean++;\n+\t\tif (next_to_clean == chan_info-\u003enum_rxq_entries)\n+\t\t\tnext_to_clean = 0;\n+\t\trx_desc = NBL_CHAN_RX_RING_TO_DESC(rxq, next_to_clean);\n+\t\trx_buf = NBL_CHAN_RX_RING_TO_BUF(rxq, next_to_clean);\n+\t\tif (--budget == 0) {\n+\t\t\tmore_work = true;\n+\t\t\tbreak;\n+\t\t}\n+\t\tcond_resched();\n+\t}\n+\trxq-\u003enext_to_clean = next_to_clean;\n+\n+\t/* Prevent queue_work after teardown clears clean_task */\n+\tif (READ_ONCE(chan_info-\u003eshutdown))\n+\t\treturn;\n+\tif (common-\u003ewq \u0026\u0026 more_work) {\n+\t\ttask = READ_ONCE(chan_info-\u003eclean_task);\n+\t\tif (task)\n+\t\t\tqueue_work(common-\u003ewq, task);\n+\t}\n+}\n+\n+static void nbl_chan_clean_queue_subtask(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t\t u8 chan_type)\n+{\n+\tstruct nbl_chan_info *chan_info = chan_mgt-\u003echan_info[chan_type];\n+\n+\tnbl_chan_clean_queue(chan_mgt, chan_info);\n+}\n+\n+static int nbl_chan_get_msg_id(struct nbl_chan_info *chan_info,\n+\t\t\t       u16 *msgid)\n+{\n+\tint search_loc = chan_info-\u003ewait_head_index, i;\n+\tstruct nbl_chan_waitqueue_head *wait = NULL;\n+\tint status;\n+\n+\tfor (i = 0; i \u003c chan_info-\u003enum_txq_entries; i++) {\n+\t\twait = \u0026chan_info-\u003ewait[search_loc];\n+\n+\t\tspin_lock_irq(\u0026wait-\u003estatus_lock);\n+\t\tstatus = READ_ONCE(wait-\u003estatus);\n+\t\tif (status == NBL_MBX_STATUS_IDLE ||\n+\t\t    status == NBL_MBX_STATUS_TIMEOUT) {\n+\t\t\tWRITE_ONCE(wait-\u003emsg_index,\n+\t\t\t\t   NBL_NEXT_ID(wait-\u003emsg_index,\n+\t\t\t\t\t       NBL_CHAN_MSG_INDEX_MAX));\n+\n+\t\t\t*msgid = FIELD_PREP(NBL_CHAN_MSGID_INDEX_MASK,\n+\t\t\t\t\t    wait-\u003emsg_index) |\n+\t\t\t\t FIELD_PREP(NBL_CHAN_MSGID_LOC_MASK,\n+\t\t\t\t\t    search_loc);\n+\t\t\tspin_unlock_irq(\u0026wait-\u003estatus_lock);\n+\n+\t\t\t/* Advance starting search position for next caller */\n+\t\t\tWRITE_ONCE(chan_info-\u003ewait_head_index, search_loc);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tspin_unlock_irq(\u0026wait-\u003estatus_lock);\n+\n+\t\tsearch_loc = NBL_NEXT_ID(search_loc,\n+\t\t\t\t\t chan_info-\u003enum_txq_entries - 1);\n+\t}\n+\n+\t/*\n+\t * All tx slots are occupied. May happen under high transmit load\n+\t * or delayed remote ACK responses. Caller should retry later.\n+\t */\n+\treturn -EAGAIN;\n+}\n+\n+static int nbl_chan_send_msg(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t     struct nbl_chan_send_info *chan_send)\n+{\n+\tstruct nbl_common_info *common = chan_mgt-\u003ecommon;\n+\tstruct nbl_chan_waitqueue_head *wait_head;\n+\tstruct nbl_chan_tx_param tx_param = { 0 };\n+\tint i = NBL_CHAN_TX_WAIT_ACK_TIMES;\n+\tstruct nbl_chan_info *chan_info =\n+\t\tchan_mgt-\u003echan_info[NBL_CHAN_TYPE_MAILBOX];\n+\tstruct device *dev = common-\u003edev;\n+\tbool inflight_inc = false;\n+\tstruct work_struct *task;\n+\tu16 msgid = 0;\n+\tint ret;\n+\tu16 tmp;\n+\n+\tif (chan_send-\u003eresp_len \u003e NBL_CHAN_BUF_LEN) {\n+\t\tdev_err_ratelimited(dev, \"resp_len %zu exceeds max %d\\n\",\n+\t\t\t\t    chan_send-\u003eresp_len, NBL_CHAN_BUF_LEN);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tatomic_inc(\u0026chan_info-\u003einflight_tx_cnt);\n+\tinflight_inc = true;\n+\t/*\n+\t * Ensure reading chan_info-\u003eshutdown happens\n+\t * strictly after inflight_tx_cnt increment above on this CPU.\n+\t * Prevent compiler/cpu reordering: read shutdown before incrementing\n+\t * inflight counter, which would cause missing shutdown check and leak\n+\t * tx inflight count.\n+\t */\n+\tsmp_rmb();\n+\tif (READ_ONCE(chan_info-\u003eshutdown)) {\n+\t\tatomic_dec(\u0026chan_info-\u003einflight_tx_cnt);\n+\t\treturn -ESHUTDOWN;\n+\t}\n+\n+\tmutex_lock(\u0026chan_info-\u003etxq_lock);\n+\tif (test_bit(NBL_CHAN_ABNORMAL, chan_info-\u003estate)) {\n+\t\tret = -EIO;\n+\t\tgoto unlock_out;\n+\t}\n+\tret = nbl_chan_get_msg_id(chan_info, \u0026msgid);\n+\tif (ret) {\n+\t\tdev_err_ratelimited(dev,\n+\t\t\t\t    \"Channel tx wait head full, send msgtype:%u to dstid:%u failed\\n\",\n+\t\t\t\t    chan_send-\u003emsg_type, chan_send-\u003edstid);\n+\t\tgoto unlock_out;\n+\t}\n+\n+\ttx_param.msg_type = chan_send-\u003emsg_type;\n+\ttx_param.arg = chan_send-\u003earg;\n+\ttx_param.arg_len = chan_send-\u003earg_len;\n+\ttx_param.dstid = chan_send-\u003edstid;\n+\ttx_param.msgid = msgid;\n+\n+\tret = nbl_chan_update_txqueue(chan_mgt, chan_info, \u0026tx_param);\n+\tif (ret) {\n+\t\tdev_err_ratelimited(dev,\n+\t\t\t\t    \"Channel tx queue full, send msgtype:%u to dstid:%u failed\\n\",\n+\t\t\t\t    chan_send-\u003emsg_type, chan_send-\u003edstid);\n+\t\tgoto unlock_out;\n+\t}\n+\n+\twait_head =\n+\t\t\u0026chan_info-\u003ewait[FIELD_GET(NBL_CHAN_MSGID_LOC_MASK, msgid)];\n+\tspin_lock_irq(\u0026wait_head-\u003estatus_lock);\n+\tWRITE_ONCE(wait_head-\u003eacked, 0);\n+\tWRITE_ONCE(wait_head-\u003eack_data, chan_send-\u003eresp);\n+\tWRITE_ONCE(wait_head-\u003eack_data_len, chan_send-\u003eresp_len);\n+\tWRITE_ONCE(wait_head-\u003emsg_type, chan_send-\u003emsg_type);\n+\tWRITE_ONCE(wait_head-\u003emsg_index,\n+\t\t   FIELD_GET(NBL_CHAN_MSGID_INDEX_MASK, msgid));\n+\tWRITE_ONCE(wait_head-\u003edstid, chan_send-\u003edstid);\n+\n+\tWRITE_ONCE(wait_head-\u003estatus, chan_send-\u003eack ?\n+\t\t   NBL_MBX_STATUS_WAITING : NBL_MBX_STATUS_IDLE);\n+\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\n+\tret = nbl_chan_kick_tx_ring(chan_mgt, chan_info);\n+\tif (ret) {\n+\t\tspin_lock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\tWRITE_ONCE(wait_head-\u003estatus, NBL_MBX_STATUS_TIMEOUT);\n+\t\tWRITE_ONCE(wait_head-\u003eacked, 0);\n+\t\tWRITE_ONCE(wait_head-\u003eack_data, NULL);\n+\t\tWRITE_ONCE(wait_head-\u003eack_data_len, 0);\n+\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\tgoto unlock_out;\n+\t}\n+\tmutex_unlock(\u0026chan_info-\u003etxq_lock);\n+\tif (!chan_send-\u003eack) {\n+\t\ttmp = FIELD_GET(NBL_CHAN_MSGID_LOC_MASK, msgid);\n+\t\twait_head = \u0026chan_info-\u003ewait[tmp];\n+\t\tspin_lock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\tWRITE_ONCE(wait_head-\u003estatus, NBL_MBX_STATUS_IDLE);\n+\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\tatomic_dec(\u0026chan_info-\u003einflight_tx_cnt);\n+\t\treturn 0;\n+\t}\n+\n+\t/* choose wait strategy based on interrupt state */\n+\tif (test_bit(NBL_CHAN_INTERRUPT_READY, chan_info-\u003estate)) {\n+\t\tspin_lock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\twhile (!READ_ONCE(wait_head-\u003eacked)) {\n+\t\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\t\tret = wait_event_timeout(wait_head-\u003ewait_queue,\n+\t\t\t\t\t\t READ_ONCE(wait_head-\u003eacked) ||\n+\t\t\t\t\t\t READ_ONCE(chan_info-\u003eshutdown),\n+\t\t\t\t\t\t NBL_CHAN_ACK_WAIT_TIME);\n+\t\t\tspin_lock_irq(\u0026wait_head-\u003estatus_lock);\n+\n+\t\t\tif (READ_ONCE(chan_info-\u003eshutdown)) {\n+\t\t\t\tret = -ESHUTDOWN;\n+\t\t\t\tWRITE_ONCE(wait_head-\u003eack_data, NULL);\n+\t\t\t\tWRITE_ONCE(wait_head-\u003eack_data_len, 0);\n+\t\t\t\tWRITE_ONCE(wait_head-\u003estatus,\n+\t\t\t\t\t   NBL_MBX_STATUS_IDLE);\n+\t\t\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\t\t\tgoto inflight_dec_out;\n+\t\t\t}\n+\t\t\tif (ret == 0) {\n+\t\t\t\tif (READ_ONCE(wait_head-\u003estatus) ==\n+\t\t\t\t    NBL_MBX_STATUS_WAITING) {\n+\t\t\t\t\tWRITE_ONCE(wait_head-\u003estatus,\n+\t\t\t\t\t\t   NBL_MBX_STATUS_TIMEOUT);\n+\t\t\t\t\tWRITE_ONCE(wait_head-\u003eacked, 0);\n+\t\t\t\t\tWRITE_ONCE(wait_head-\u003eack_data, NULL);\n+\t\t\t\t\tWRITE_ONCE(wait_head-\u003eack_data_len, 0);\n+\t\t\t\t}\n+\t\t\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\t\t\tdev_err_ratelimited(dev,\n+\t\t\t\t\t\t    \"Channel waiting ack failed, message type: %d, msg id: %u\\n\",\n+\t\t\t\t\t\t    chan_send-\u003emsg_type, msgid);\n+\t\t\t\tret = -ETIMEDOUT;\n+\t\t\t\tgoto inflight_dec_out;\n+\t\t\t}\n+\n+\t\t\tif (READ_ONCE(wait_head-\u003eacked))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tchan_send-\u003eack_len = wait_head-\u003eack_data_len;\n+\t\tret = wait_head-\u003eack_err;\n+\t\tWRITE_ONCE(wait_head-\u003eacked, 0);\n+\t\tWRITE_ONCE(wait_head-\u003estatus, NBL_MBX_STATUS_IDLE);\n+\t\tWRITE_ONCE(wait_head-\u003eack_data, NULL);\n+\t\tWRITE_ONCE(wait_head-\u003eack_data_len, 0);\n+\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\tgoto inflight_dec_out;\n+\t} else {\n+\t\t/* Polling wait path for synchronous ACK */\n+\t\twhile (i--) {\n+\t\t\tif (READ_ONCE(chan_info-\u003eshutdown)) {\n+\t\t\t\tret = -ESHUTDOWN;\n+\t\t\t\t/*\n+\t\t\t\t * Shutdown happens during polling wait,\n+\t\t\t\t * reclaim slot\n+\t\t\t\t */\n+\t\t\t\tspin_lock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\t\t\tWRITE_ONCE(wait_head-\u003eacked, 0);\n+\t\t\t\tWRITE_ONCE(wait_head-\u003eack_data, NULL);\n+\t\t\t\tWRITE_ONCE(wait_head-\u003eack_data_len, 0);\n+\t\t\t\tWRITE_ONCE(wait_head-\u003estatus,\n+\t\t\t\t\t   NBL_MBX_STATUS_TIMEOUT);\n+\t\t\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\t\t\tgoto inflight_dec_out;\n+\t\t\t}\n+\n+\t\t\ttask = READ_ONCE(chan_info-\u003eclean_task);\n+\t\t\tif (common-\u003ewq \u0026\u0026 task \u0026\u0026\n+\t\t\t    !READ_ONCE(chan_info-\u003eshutdown))\n+\t\t\t\tqueue_work(common-\u003ewq, task);\n+\n+\t\t\tspin_lock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\t\tif (READ_ONCE(wait_head-\u003eacked)) {\n+\t\t\t\tchan_send-\u003eack_len = wait_head-\u003eack_data_len;\n+\t\t\t\tret = wait_head-\u003eack_err;\n+\t\t\t\tWRITE_ONCE(wait_head-\u003eacked, 0);\n+\t\t\t\tWRITE_ONCE(wait_head-\u003estatus,\n+\t\t\t\t\t   NBL_MBX_STATUS_IDLE);\n+\t\t\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\t\t\tgoto inflight_dec_out;\n+\t\t\t}\n+\t\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\n+\t\t\tusleep_range(NBL_CHAN_TX_WAIT_ACK_US_MIN,\n+\t\t\t\t     NBL_CHAN_TX_WAIT_ACK_US_MAX);\n+\t\t\tcond_resched();\n+\t\t}\n+\n+\t\t/* Polling loop exhausted, reclaim slot */\n+\t\tspin_lock_irq(\u0026wait_head-\u003estatus_lock);\n+\t\tWRITE_ONCE(wait_head-\u003eacked, 0);\n+\t\tWRITE_ONCE(wait_head-\u003eack_data, NULL);\n+\t\tWRITE_ONCE(wait_head-\u003eack_data_len, 0);\n+\t\tWRITE_ONCE(wait_head-\u003estatus, NBL_MBX_STATUS_TIMEOUT);\n+\t\tspin_unlock_irq(\u0026wait_head-\u003estatus_lock);\n+\n+\t\tdev_err_ratelimited(dev,\n+\t\t\t\t    \"Channel polling ack failed, message type: %d msg id: %u\\n\",\n+\t\t\t\t    chan_send-\u003emsg_type, msgid);\n+\t\tret = -ETIMEDOUT;\n+\t\tgoto inflight_dec_out;\n+\t}\n+\n+inflight_dec_out:\n+\tatomic_dec(\u0026chan_info-\u003einflight_tx_cnt);\n+\treturn ret;\n+\n+unlock_out:\n+\tmutex_unlock(\u0026chan_info-\u003etxq_lock);\n+\tif (inflight_inc)\n+\t\tatomic_dec(\u0026chan_info-\u003einflight_tx_cnt);\n+\treturn ret;\n+}\n+\n+static int nbl_chan_send_ack(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t     struct nbl_chan_ack_info *chan_ack)\n+{\n+\tsize_t head_len = NBL_CHAN_ACK_HEAD_LEN * sizeof(u32);\n+\tsize_t data_len = chan_ack-\u003edata_len;\n+\tstruct nbl_chan_send_info chan_send;\n+\t__le32 *tmp;\n+\tsize_t len;\n+\tint ret;\n+\n+\tif (data_len \u003e\n+\t    NBL_CHAN_BUF_LEN - sizeof(struct nbl_chan_tx_desc) - head_len)\n+\t\treturn -EINVAL;\n+\n+\tlen = head_len + data_len;\n+\ttmp = kzalloc(len, GFP_ATOMIC);\n+\tif (!tmp)\n+\t\treturn -ENOMEM;\n+\n+\t*(__le16 *)\u0026tmp[NBL_CHAN_MSG_TYPE_POS] =\n+\t\tcpu_to_le16(chan_ack-\u003emsg_type);\n+\t*(__le16 *)\u0026tmp[NBL_CHAN_MSG_ID_POS] = cpu_to_le16(chan_ack-\u003emsgid);\n+\ttmp[NBL_CHAN_ACK_RET_POS] = cpu_to_le32(chan_ack-\u003eerr);\n+\tif (chan_ack-\u003edata \u0026\u0026 chan_ack-\u003edata_len)\n+\t\tmemcpy(\u0026tmp[NBL_CHAN_ACK_HEAD_LEN], chan_ack-\u003edata,\n+\t\t       chan_ack-\u003edata_len);\n+\n+\tnbl_chan_fill_send_info(\u0026chan_send, chan_ack-\u003edstid, NBL_CHAN_MSG_ACK,\n+\t\t\t\ttmp, len, NULL, 0, 0);\n+\tret = nbl_chan_send_msg(chan_mgt, \u0026chan_send);\n+\tkfree(tmp);\n+\n+\treturn ret;\n+}\n+\n+static int nbl_chan_register_msg(struct nbl_channel_mgt *chan_mgt, u16 msg_type,\n+\t\t\t\t nbl_chan_resp func, void *callback)\n+{\n+\treturn nbl_chan_add_msg_handler(chan_mgt, msg_type, func, callback);\n+}\n+\n+static bool nbl_chan_check_queue_exist(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t       u8 chan_type)\n+{\n+\tstruct nbl_chan_info *chan_info = chan_mgt-\u003echan_info[chan_type];\n+\n+\treturn chan_info ? true : false;\n+}\n+\n+static void nbl_chan_register_chan_task(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t\tu8 chan_type, struct work_struct *task)\n+{\n+\tstruct nbl_chan_info *chan_info = chan_mgt-\u003echan_info[chan_type];\n+\n+\tchan_info-\u003eclean_task = task;\n+}\n+\n+static void nbl_chan_set_queue_state(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t     enum nbl_chan_state state, u8 chan_type,\n+\t\t\t\t     u8 set)\n+{\n+\tstruct nbl_chan_info *chan_info = chan_mgt-\u003echan_info[chan_type];\n+\n+\tif (set)\n+\t\tset_bit(state, chan_info-\u003estate);\n+\telse\n+\t\tclear_bit(state, chan_info-\u003estate);\n+}\n+\n+static struct nbl_channel_ops chan_ops = {\n+\t.send_msg\t\t\t= nbl_chan_send_msg,\n+\t.send_ack\t\t\t= nbl_chan_send_ack,\n+\t.register_msg\t\t\t= nbl_chan_register_msg,\n+\t.unregister_all_msg\t\t= nbl_chan_remove_msg_handler,\n+\t.cfg_chan_qinfo_map_table\t= nbl_chan_cfg_qinfo_map_table,\n+\t.check_queue_exist\t\t= nbl_chan_check_queue_exist,\n+\t.setup_queue\t\t\t= nbl_chan_setup_queue,\n+\t.teardown_queue\t\t\t= nbl_chan_teardown_queue,\n+\t.clean_queue_subtask\t\t= nbl_chan_clean_queue_subtask,\n+\t.register_chan_task\t\t= nbl_chan_register_chan_task,\n+\t.set_queue_state\t\t= nbl_chan_set_queue_state,\n+};\n+\n+static struct nbl_channel_mgt *\n+nbl_chan_setup_chan_mgt(struct nbl_adapter *adapter)\n+{\n+\tstruct nbl_hw_ops_tbl *hw_ops_tbl = adapter-\u003eintf.hw_ops_tbl;\n+\tstruct nbl_common_info *common = \u0026adapter-\u003ecommon;\n+\tstruct device *dev = \u0026adapter-\u003epdev-\u003edev;\n+\tstruct nbl_channel_mgt *chan_mgt;\n+\tstruct nbl_chan_info *mailbox;\n+\tint ret;\n+\n+\tchan_mgt = devm_kzalloc(dev, sizeof(*chan_mgt), GFP_KERNEL);\n+\tif (!chan_mgt)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tchan_mgt-\u003ecommon = common;\n+\tchan_mgt-\u003ehw_ops_tbl = hw_ops_tbl;\n+\n+\tmailbox = devm_kzalloc(dev, sizeof(*mailbox), GFP_KERNEL);\n+\tif (!mailbox)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\tmailbox-\u003echan_type = NBL_CHAN_TYPE_MAILBOX;\n+\tchan_mgt-\u003echan_info[NBL_CHAN_TYPE_MAILBOX] = mailbox;\n+\n+\tret = nbl_chan_init_msg_handler(chan_mgt);\n+\tif (ret)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\treturn chan_mgt;\n+}\n+\n+static struct nbl_channel_ops_tbl *\n+nbl_chan_setup_ops(struct device *dev, struct nbl_channel_mgt *chan_mgt)\n+{\n+\tstruct nbl_channel_ops_tbl *chan_ops_tbl;\n+\tint ret;\n+\n+\tchan_ops_tbl = devm_kzalloc(dev, sizeof(*chan_ops_tbl), GFP_KERNEL);\n+\tif (!chan_ops_tbl)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\tif (!chan_ops.send_msg || !chan_ops.send_ack ||\n+\t    !chan_ops.register_msg || !chan_ops.unregister_all_msg ||\n+\t    !chan_ops.cfg_chan_qinfo_map_table ||\n+\t    !chan_ops.check_queue_exist || !chan_ops.setup_queue ||\n+\t    !chan_ops.teardown_queue || !chan_ops.clean_queue_subtask ||\n+\t    !chan_ops.register_chan_task || !chan_ops.set_queue_state)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tchan_ops_tbl-\u003eops = \u0026chan_ops;\n+\tchan_ops_tbl-\u003epriv = chan_mgt;\n+\n+\tret = nbl_chan_register_msg(chan_mgt, NBL_CHAN_MSG_ACK,\n+\t\t\t\t    nbl_chan_recv_ack_msg, chan_mgt);\n+\tif (ret)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\treturn chan_ops_tbl;\n+}\n+\n+int nbl_chan_init_common(struct nbl_adapter *adap)\n+{\n+\tstruct nbl_channel_ops_tbl *chan_ops_tbl;\n+\tstruct device *dev = \u0026adap-\u003epdev-\u003edev;\n+\tstruct nbl_channel_mgt *chan_mgt;\n+\tint ret;\n+\n+\tBUILD_BUG_ON(sizeof(struct nbl_chan_param_cfg_msix_map) != 8);\n+\tBUILD_BUG_ON(sizeof(struct nbl_chan_param_set_mailbox_irq) != 4);\n+\tBUILD_BUG_ON(sizeof(struct nbl_chan_param_get_vsi_id) != 4);\n+\tBUILD_BUG_ON(sizeof(struct nbl_chan_param_get_eth_id) != 8);\n+\tchan_mgt = nbl_chan_setup_chan_mgt(adap);\n+\tif (IS_ERR(chan_mgt)) {\n+\t\tret = PTR_ERR(chan_mgt);\n+\t\tgoto exit;\n+\t}\n+\n+\tchan_ops_tbl = nbl_chan_setup_ops(dev, chan_mgt);\n+\tif (IS_ERR(chan_ops_tbl)) {\n+\t\tret = PTR_ERR(chan_ops_tbl);\n+\t\tgoto cleanup_mgt;\n+\t}\n+\tadap-\u003eintf.channel_ops_tbl = chan_ops_tbl;\n+\tadap-\u003ecore.chan_mgt = chan_mgt;\n+\treturn 0;\n+\n+cleanup_mgt:\n+\tnbl_chan_remove_msg_handler(chan_mgt);\n+exit:\n+\treturn ret;\n+}\n+\n+void nbl_chan_remove_common(struct nbl_adapter *adap)\n+{\n+\tstruct nbl_channel_mgt *chan_mgt = adap-\u003ecore.chan_mgt;\n+\n+\tif (!chan_mgt)\n+\t\treturn;\n+\n+\t/*\n+\t * All channel queues shall be torn down earlier in remove path\n+\t * to drain inflight tx workers and stop hardware before destroying\n+\t * message handler hash table.\n+\t */\n+\tnbl_chan_remove_msg_handler(chan_mgt);\n+\tadap-\u003ecore.chan_mgt = NULL;\n+}\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.h\nnew file mode 100644\nindex 0000000000000..d59e660be9c79\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.h\n@@ -0,0 +1,173 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_CHANNEL_H_\n+#define _NBL_CHANNEL_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+#include \"../nbl_include/nbl_include.h\"\n+#include \"../nbl_include/nbl_def_channel.h\"\n+#include \"../nbl_include/nbl_def_hw.h\"\n+#include \"../nbl_include/nbl_def_common.h\"\n+#include \"../nbl_core.h\"\n+\n+#define NBL_CHAN_TX_RING_TO_DESC(tx_ring, i) \\\n+\t(\u0026((((tx_ring)-\u003edesc.tx_desc))[i]))\n+#define NBL_CHAN_RX_RING_TO_DESC(rx_ring, i) \\\n+\t(\u0026((((rx_ring)-\u003edesc.rx_desc))[i]))\n+#define NBL_CHAN_TX_RING_TO_BUF(tx_ring, i) (\u0026(((tx_ring)-\u003ebuf)[i]))\n+#define NBL_CHAN_RX_RING_TO_BUF(rx_ring, i) (\u0026(((rx_ring)-\u003ebuf)[i]))\n+\n+#define NBL_CHAN_TX_WAIT_US\t\t\t100\n+#define NBL_CHAN_TX_WAIT_US_MAX\t\t\t120\n+#define NBL_CHAN_TX_WAIT_TIMES\t\t\t100\n+#define NBL_CHAN_TX_WAIT_ACK_US_MIN\t\t1000\n+#define NBL_CHAN_TX_WAIT_ACK_US_MAX\t\t1200\n+#define NBL_CHAN_TX_WAIT_ACK_TIMES\t\t5000\n+#define NBL_CHAN_QUEUE_LEN\t\t\t256\n+#define NBL_CHAN_BUF_LEN\t\t\t4096\n+#define NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN\t16\n+\n+#define NBL_CHAN_TX_DESC_AVAIL\t\t\t0\n+#define NBL_CHAN_TX_DESC_USED\t\t\t1\n+#define NBL_CHAN_RX_DESC_WRITE\t\t\t1\n+#define NBL_CHAN_RX_DESC_AVAIL\t\t\t3\n+#define NBL_CHAN_RX_DESC_USED\t\t\t4\n+\n+#define NBL_CHAN_ACK_HEAD_LEN\t\t\t3\n+#define NBL_CHAN_ACK_RET_POS\t\t\t2\n+#define NBL_CHAN_MSG_ID_POS\t\t\t1\n+#define NBL_CHAN_MSG_TYPE_POS\t\t\t0\n+\n+#define NBL_CHAN_ACK_WAIT_TIME\t\t\t(3 * HZ)\n+\n+#define NBL_CHAN_HANDLER_TBL_BUCKET_SIZE\t512\n+\n+enum {\n+\tNBL_MB_RX_QID = 0,\n+\tNBL_MB_TX_QID = 1,\n+};\n+\n+enum {\n+\tNBL_MBX_STATUS_IDLE = 0,\n+\tNBL_MBX_STATUS_WAITING,\n+\tNBL_MBX_STATUS_TIMEOUT,\n+};\n+\n+struct nbl_chan_tx_param {\n+\tenum nbl_chan_msg_type msg_type;\n+\tvoid *arg;\n+\tsize_t arg_len;\n+\tu16 dstid;\n+\tu16 msgid;\n+};\n+\n+struct nbl_chan_buf {\n+\tvoid *va;\n+\tdma_addr_t pa;\n+\tsize_t size;\n+};\n+\n+struct nbl_chan_tx_desc {\n+\t__le16 flags;\n+\t__le16 srcid;\n+\t__le16 dstid;\n+\t__le16 data_len;\n+\t__le16 buf_len;\n+\t__le64 buf_addr;\n+\t__le16 msg_type;\n+\tu8 data[16];\n+\t__le16 msgid;\n+\tu8 rsv[26];\n+} __packed;\n+\n+struct nbl_chan_rx_desc {\n+\t__le16 flags;\n+\t__le32 buf_len;\n+\t__le16 buf_id;\n+\t__le64 buf_addr;\n+} __packed;\n+\n+union nbl_chan_desc_ptr {\n+\tstruct nbl_chan_tx_desc *tx_desc;\n+\tstruct nbl_chan_rx_desc *rx_desc;\n+};\n+\n+struct nbl_chan_ring {\n+\tunion nbl_chan_desc_ptr desc;\n+\tstruct nbl_chan_buf *buf;\n+\tu16 next_to_use;\n+\tu16 tail_ptr; /* hardware does modulo ring size internally */\n+\tu16 next_to_clean;\n+\tdma_addr_t dma;\n+};\n+\n+#define NBL_CHAN_MSG_INDEX_MAX 63\n+\n+#define NBL_CHAN_MSGID_INDEX_MASK GENMASK(5, 0)\n+#define NBL_CHAN_MSGID_LOC_MASK GENMASK(13, 6)\n+\n+static inline void nbl_chan_update_tail_ptr(struct nbl_hw_ops *hw_ops,\n+\t\t\t\t\t    void *hw_priv, u32 tail_ptr, u8 qid)\n+{\n+\thw_ops-\u003eupdate_mailbox_queue_tail_ptr(hw_priv, tail_ptr, qid);\n+}\n+\n+struct nbl_chan_waitqueue_head {\n+\tstruct wait_queue_head wait_queue;\n+\tchar *ack_data;\n+\tint acked;\n+\ts32 ack_err;\n+\tu16 ack_data_len;\n+\tu16 msg_type;\n+\t/*\n+\t * Spinlock protecting all fields.\n+\t * Must be held when reading/writing: status, acked, ack_err,\n+\t * ack_data_len, etc.\n+\t * The lock ensures atomic updates of these fields and\n+\t * proper memory ordering with smp_wmb()/smp_rmb().\n+\t */\n+\tspinlock_t status_lock;\n+\tint status;\n+\tu8 msg_index;\n+\tu16 dstid;\n+};\n+\n+struct nbl_chan_info {\n+\tstruct nbl_chan_ring txq;\n+\tstruct nbl_chan_ring rxq;\n+\tstruct nbl_chan_waitqueue_head *wait;\n+\t/*\n+\t *Protects access to the TX queue (txq) and related metadata.\n+\t *This mutex ensures exclusive access when updating the TX queue\n+\t */\n+\tstruct mutex txq_lock;\n+\tstruct work_struct *clean_task;\n+\tu16 wait_head_index;\n+\tu16 num_txq_entries;\n+\tu16 num_rxq_entries;\n+\tu16 txq_buf_size;\n+\tu16 rxq_buf_size;\n+\tDECLARE_BITMAP(state, NBL_CHAN_STATE_NBITS);\n+\tu8 chan_type;\n+\tatomic_t inflight_tx_cnt;\n+\tbool shutdown;\n+\tbool init_done;\n+};\n+\n+struct nbl_chan_msg_node_data {\n+\tnbl_chan_resp func;\n+\tvoid *priv;\n+};\n+\n+struct nbl_channel_mgt {\n+\tstruct nbl_common_info *common;\n+\tstruct nbl_hw_ops_tbl *hw_ops_tbl;\n+\tstruct nbl_chan_info *chan_info[NBL_CHAN_TYPE_MAX];\n+\tstruct nbl_hash_tbl_mgt *handle_hash_tbl;\n+};\n+\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c\nnew file mode 100644\nindex 0000000000000..f10ef78ee7f64\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c\n@@ -0,0 +1,206 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/jhash.h\u003e\n+#include \"nbl_common.h\"\n+\n+void nbl_common_destroy_wq(struct nbl_common_info *common)\n+{\n+\tdestroy_workqueue(common-\u003ewq);\n+}\n+\n+int nbl_common_create_wq(struct nbl_common_info *common)\n+{\n+\tchar wq_name[32];\n+\n+\tsnprintf(wq_name, sizeof(wq_name), \"nbl_wq_%s\", pci_name(common-\u003epdev));\n+\tcommon-\u003ewq = alloc_workqueue(\"%s\", WQ_UNBOUND, 0, wq_name);\n+\tif (!common-\u003ewq) {\n+\t\tpr_err(\"Failed to create workqueue nbl_wq\\n\");\n+\t\tgoto alloc_wq_failed;\n+\t}\n+\n+\treturn 0;\n+alloc_wq_failed:\n+\treturn -ENOMEM;\n+}\n+\n+/*\n+ * nbl_common_func_id_to_rel_pf_id - convert absolute PF id to relative PF id\n+ *\n+ * Leonis currently uses fixed mgt_pf = 0. This helper maintains unified\n+ * conversion logic for future platforms supporting non-zero management PF.\n+ * Return: 0 on success, -EINVAL if pf_id \u003c common-\u003emgt_pf or rel_pf_id is NULL.\n+ */\n+int nbl_common_func_id_to_rel_pf_id(struct nbl_common_info *common, u32 pf_id,\n+\t\t\t\t    u32 *rel_pf_id)\n+{\n+\tif (!rel_pf_id)\n+\t\treturn -EINVAL;\n+\n+\tif (pf_id \u003c common-\u003emgt_pf)\n+\t\treturn -EINVAL;\n+\t*rel_pf_id = pf_id - common-\u003emgt_pf;\n+\treturn 0;\n+}\n+\n+static u32 nbl_common_calc_hash_key(void *key, u32 key_size, u32 bucket_size)\n+{\n+\tu32 hash;\n+\n+\tif (bucket_size == 0 || bucket_size == NBL_HASH_TBL_LIST_BUCKET_SIZE)\n+\t\treturn 0;\n+\n+\thash = jhash(key, key_size, 0);\n+\n+\t/* Use bitmask if bucket_size is a power of 2 */\n+\tif ((bucket_size \u0026 (bucket_size - 1)) == 0)\n+\t\treturn hash \u0026 (bucket_size - 1);\n+\telse\n+\t\treturn hash % bucket_size;\n+}\n+\n+/*\n+ * alloc a hash table\n+ * the table support multi thread\n+ */\n+struct nbl_hash_tbl_mgt *\n+nbl_common_init_hash_table(struct nbl_hash_tbl_key *key)\n+{\n+\tstruct nbl_hash_tbl_mgt *tbl_mgt;\n+\tint bucket_size;\n+\tint i;\n+\n+\ttbl_mgt = devm_kzalloc(key-\u003edev, sizeof(*tbl_mgt), GFP_KERNEL);\n+\tif (!tbl_mgt)\n+\t\treturn NULL;\n+\n+\tbucket_size = key-\u003ebucket_size;\n+\ttbl_mgt-\u003ehash = devm_kcalloc(key-\u003edev, bucket_size,\n+\t\t\t\t     sizeof(struct hlist_head), GFP_KERNEL);\n+\tif (!tbl_mgt-\u003ehash)\n+\t\tgoto alloc_hash_failed;\n+\n+\tfor (i = 0; i \u003c bucket_size; i++)\n+\t\tINIT_HLIST_HEAD(tbl_mgt-\u003ehash + i);\n+\n+\tmemcpy(\u0026tbl_mgt-\u003etbl_key, key, sizeof(struct nbl_hash_tbl_key));\n+\n+\treturn tbl_mgt;\n+\n+alloc_hash_failed:\n+\treturn NULL;\n+}\n+\n+/*\n+ * Allocate a hash node and add to the hash table.\n+ *\n+ * Note: The hash table is protected by the caller's mutex (txq_lock),\n+ * not lock-free. The \"single context\" comment refers to: this function\n+ * is only called during init from nbl_chan_init_msg_handler, so there\n+ * is no concurrent mutation during init.\n+ * After init, the table is read-only (no unregister API), so no lock is needed\n+ * for lookups either.\n+ *\n+ * The tbl_mgt and bucket array are devm-allocated, so they are automatically\n+ * freed on device detach. Only the hash nodes themselves need explicit cleanup.\n+ */\n+int nbl_common_alloc_hash_node(struct nbl_hash_tbl_mgt *tbl_mgt, void *key,\n+\t\t\t       void *data, void **out_data)\n+{\n+\tstruct nbl_hash_entry_node *hash_node;\n+\tu16 data_size;\n+\tu16 node_size;\n+\tu32 hash_val;\n+\tu16 key_size;\n+\n+\tnode_size = sizeof(*hash_node);\n+\thash_node = kzalloc(node_size, GFP_KERNEL);\n+\tif (!hash_node)\n+\t\treturn -ENOMEM;\n+\n+\tkey_size = tbl_mgt-\u003etbl_key.key_size;\n+\thash_node-\u003ekey = kzalloc(key_size, GFP_KERNEL);\n+\tif (!hash_node-\u003ekey)\n+\t\tgoto alloc_key_failed;\n+\n+\tdata_size = tbl_mgt-\u003etbl_key.data_size;\n+\thash_node-\u003edata = kzalloc(data_size, GFP_KERNEL);\n+\tif (!hash_node-\u003edata)\n+\t\tgoto alloc_data_failed;\n+\n+\tmemcpy(hash_node-\u003ekey, key, key_size);\n+\tmemcpy(hash_node-\u003edata, data, data_size);\n+\n+\thash_val = nbl_common_calc_hash_key(key, key_size,\n+\t\t\t\t\t    tbl_mgt-\u003etbl_key.bucket_size);\n+\n+\thlist_add_head(\u0026hash_node-\u003enode, tbl_mgt-\u003ehash + hash_val);\n+\ttbl_mgt-\u003enode_num++;\n+\tif (out_data)\n+\t\t*out_data = hash_node-\u003edata;\n+\n+\treturn 0;\n+\n+alloc_data_failed:\n+\tkfree(hash_node-\u003ekey);\n+alloc_key_failed:\n+\tkfree(hash_node);\n+\treturn -ENOMEM;\n+}\n+\n+/*\n+ * get a hash node, return the data if node exist\n+ */\n+void *nbl_common_get_hash_node(struct nbl_hash_tbl_mgt *tbl_mgt, void *key)\n+{\n+\tstruct nbl_hash_entry_node *hash_node;\n+\tstruct hlist_head *head;\n+\tvoid *data = NULL;\n+\tu32 hash_val;\n+\tu16 key_size;\n+\n+\tkey_size = tbl_mgt-\u003etbl_key.key_size;\n+\thash_val = nbl_common_calc_hash_key(key, key_size,\n+\t\t\t\t\t    tbl_mgt-\u003etbl_key.bucket_size);\n+\thead = tbl_mgt-\u003ehash + hash_val;\n+\n+\thlist_for_each_entry(hash_node, head, node)\n+\t\tif (!memcmp(hash_node-\u003ekey, key, key_size)) {\n+\t\t\tdata = hash_node-\u003edata;\n+\t\t\tbreak;\n+\t\t}\n+\n+\treturn data;\n+}\n+\n+static void nbl_common_detach_hash_node(struct nbl_hash_tbl_mgt *tbl_mgt,\n+\t\t\t\t\tstruct nbl_hash_entry_node *hash_node)\n+{\n+\thlist_del(\u0026hash_node-\u003enode);\n+\tkfree(hash_node-\u003ekey);\n+\tkfree(hash_node-\u003edata);\n+\tkfree(hash_node);\n+\ttbl_mgt-\u003enode_num--;\n+}\n+\n+/*\n+ * Free all hash nodes in the table.\n+ */\n+void nbl_common_remove_hash_table(struct nbl_hash_tbl_mgt *tbl_mgt)\n+{\n+\tstruct nbl_hash_entry_node *hash_node;\n+\tstruct hlist_node *safe_node;\n+\tstruct hlist_head *head;\n+\tu32 i;\n+\n+\tfor (i = 0; i \u003c tbl_mgt-\u003etbl_key.bucket_size; i++) {\n+\t\thead = tbl_mgt-\u003ehash + i;\n+\t\thlist_for_each_entry_safe(hash_node, safe_node, head, node) {\n+\t\t\tnbl_common_detach_hash_node(tbl_mgt, hash_node);\n+\t\t}\n+\t}\n+}\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.h\nnew file mode 100644\nindex 0000000000000..ad8fb7ec9f45d\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.h\n@@ -0,0 +1,29 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_COMMON_H_\n+#define _NBL_COMMON_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+#include \"../nbl_include/nbl_include.h\"\n+#include \"../nbl_include/nbl_def_common.h\"\n+\n+/* list only need one bucket size */\n+#define NBL_HASH_TBL_LIST_BUCKET_SIZE 1\n+\n+struct nbl_hash_tbl_mgt {\n+\tstruct nbl_hash_tbl_key tbl_key;\n+\tstruct hlist_head *hash;\n+\tu16 node_num;\n+};\n+\n+struct nbl_hash_entry_node {\n+\tstruct hlist_node node;\n+\tvoid *key;\n+\tvoid *data;\n+};\n+\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h\nnew file mode 100644\nindex 0000000000000..a48bd9a0ef1ec\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h\n@@ -0,0 +1,45 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_CORE_H_\n+#define _NBL_CORE_H_\n+\n+#include \u003clinux/pci.h\u003e\n+#include \"nbl_include/nbl_include.h\"\n+#include \"nbl_include/nbl_def_common.h\"\n+\n+enum {\n+\tNBL_CAP_HAS_NET_BIT,\n+};\n+\n+struct nbl_interface {\n+\tstruct nbl_hw_ops_tbl *hw_ops_tbl;\n+\tstruct nbl_resource_ops_tbl *resource_ops_tbl;\n+\tstruct nbl_dispatch_ops_tbl *dispatch_ops_tbl;\n+\tstruct nbl_channel_ops_tbl *channel_ops_tbl;\n+};\n+\n+struct nbl_core {\n+\tstruct nbl_hw_mgt *hw_mgt;\n+\tstruct nbl_resource_mgt *res_mgt;\n+\tstruct nbl_dispatch_mgt *disp_mgt;\n+\tstruct nbl_dev_mgt *dev_mgt;\n+\tstruct nbl_channel_mgt *chan_mgt;\n+};\n+\n+struct nbl_adapter {\n+\tstruct pci_dev *pdev;\n+\tstruct nbl_core core;\n+\tstruct nbl_interface intf;\n+\tstruct nbl_common_info common;\n+};\n+\n+struct nbl_adapter *nbl_core_init(struct pci_dev *pdev,\n+\t\t\t\t  struct nbl_init_param *param);\n+void nbl_core_remove(struct nbl_adapter *adapter);\n+int nbl_core_start(struct nbl_adapter *adapter);\n+void nbl_core_stop(struct nbl_adapter *adapter);\n+\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c\nnew file mode 100644\nindex 0000000000000..3a8b7ee2e4752\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c\n@@ -0,0 +1,499 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/pci.h\u003e\n+#include \"nbl_dev.h\"\n+\n+static void nbl_dev_clean_mailbox_schedule(struct nbl_dev_mgt *dev_mgt);\n+\n+/* ----------  Interrupt config  ---------- */\n+static irqreturn_t nbl_dev_clean_mailbox(int __always_unused irq, void *data)\n+{\n+\tstruct nbl_dev_mgt *dev_mgt = (struct nbl_dev_mgt *)data;\n+\n+\tnbl_dev_clean_mailbox_schedule(dev_mgt);\n+\treturn IRQ_HANDLED;\n+}\n+\n+static void nbl_dev_init_msix_cnt(struct nbl_dev_mgt *dev_mgt)\n+{\n+\tstruct nbl_dev_common *dev_common = dev_mgt-\u003ecommon_dev;\n+\tstruct nbl_msix_info *msix_info = \u0026dev_common-\u003emsix_info;\n+\n+\tmsix_info-\u003eserv_info[NBL_MSIX_MAILBOX_TYPE].num = 1;\n+}\n+\n+static int nbl_dev_request_mailbox_irq(struct nbl_dev_mgt *dev_mgt)\n+{\n+\tstruct nbl_dev_common *dev_common = dev_mgt-\u003ecommon_dev;\n+\tstruct nbl_msix_info *msix_info = \u0026dev_common-\u003emsix_info;\n+\tstruct nbl_common_info *common = dev_mgt-\u003ecommon;\n+\tu16 local_vec_id;\n+\tint irq_num;\n+\tint err;\n+\n+\tif (!msix_info-\u003eserv_info[NBL_MSIX_MAILBOX_TYPE].num)\n+\t\treturn 0;\n+\n+\tlocal_vec_id =\n+\t\tmsix_info-\u003eserv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id;\n+\tirq_num = pci_irq_vector(common-\u003epdev, local_vec_id);\n+\tif (irq_num \u003c 0) {\n+\t\tdev_err(common-\u003edev, \"Failed to get mailbox IRQ vector: %d\\n\",\n+\t\t\tirq_num);\n+\t\treturn irq_num;\n+\t}\n+\n+\tsnprintf(dev_common-\u003emailbox_name, sizeof(dev_common-\u003emailbox_name),\n+\t\t \"nbl_mailbox@pci:%s\", pci_name(common-\u003epdev));\n+\terr = request_irq(irq_num, nbl_dev_clean_mailbox, 0,\n+\t\t\t  dev_common-\u003emailbox_name, dev_mgt);\n+\tif (err)\n+\t\treturn err;\n+\n+\treturn 0;\n+}\n+\n+static void nbl_dev_free_mailbox_irq(struct nbl_dev_mgt *dev_mgt)\n+{\n+\tstruct nbl_dev_common *dev_common = dev_mgt-\u003ecommon_dev;\n+\tstruct nbl_msix_info *msix_info = \u0026dev_common-\u003emsix_info;\n+\tstruct nbl_common_info *common = dev_mgt-\u003ecommon;\n+\tu16 local_vec_id;\n+\tint irq_num;\n+\n+\tif (!msix_info-\u003eserv_info[NBL_MSIX_MAILBOX_TYPE].num)\n+\t\treturn;\n+\n+\tlocal_vec_id =\n+\t\tmsix_info-\u003eserv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id;\n+\tirq_num = pci_irq_vector(common-\u003epdev, local_vec_id);\n+\tif (irq_num \u003e= 0)\n+\t\tfree_irq(irq_num, dev_mgt);\n+}\n+\n+static int nbl_dev_enable_mailbox_irq(struct nbl_dev_mgt *dev_mgt)\n+{\n+\tstruct nbl_dispatch_ops *disp_ops = dev_mgt-\u003edisp_ops_tbl-\u003eops;\n+\tstruct nbl_channel_ops *chan_ops = dev_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_dev_common *dev_common = dev_mgt-\u003ecommon_dev;\n+\tstruct nbl_msix_info *msix_info = \u0026dev_common-\u003emsix_info;\n+\tu16 local_vec_id;\n+\n+\tif (!msix_info-\u003eserv_info[NBL_MSIX_MAILBOX_TYPE].num)\n+\t\treturn 0;\n+\n+\tlocal_vec_id =\n+\t\tmsix_info-\u003eserv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id;\n+\tchan_ops-\u003eset_queue_state(dev_mgt-\u003echan_ops_tbl-\u003epriv,\n+\t\t\t\t  NBL_CHAN_INTERRUPT_READY,\n+\t\t\t\t  NBL_CHAN_TYPE_MAILBOX, true);\n+\n+\treturn disp_ops-\u003eset_mailbox_irq(dev_mgt-\u003edisp_ops_tbl-\u003epriv,\n+\t\t\t\t\t    local_vec_id, true);\n+}\n+\n+static int nbl_dev_disable_mailbox_irq(struct nbl_dev_mgt *dev_mgt)\n+{\n+\tstruct nbl_dispatch_ops *disp_ops = dev_mgt-\u003edisp_ops_tbl-\u003eops;\n+\tstruct nbl_channel_ops *chan_ops = dev_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_dev_common *dev_common = dev_mgt-\u003ecommon_dev;\n+\tstruct nbl_msix_info *msix_info = \u0026dev_common-\u003emsix_info;\n+\tu16 local_vec_id;\n+\n+\tif (!msix_info-\u003eserv_info[NBL_MSIX_MAILBOX_TYPE].num)\n+\t\treturn 0;\n+\n+\tlocal_vec_id =\n+\t\tmsix_info-\u003eserv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id;\n+\t/*\n+\t * Disable sequence invariant: update software state first, then mask\n+\t * hardware interrupt. Must not reverse the order.\n+\t *\n+\t * If hardware interrupt is masked before clearing INTERRUPT_READY,\n+\t * the hardware may still transmit outstanding ACK packets for in-flight\n+\t * messages. Subsequent switch to polling mode discards pending ACK\n+\t * processing, triggering \"Channel waiting ack failed\" and \"Skip ack\n+\t * with invalid status\" errors.\n+\t *\n+\t * By entering polling mode first, any late hardware interrupts are\n+\t * ignored without pending ACK expectations, then hardware interrupt\n+\t * can be safely disabled.\n+\t *\n+\t * This helper is invoked in two paths:\n+\t * 1. Error unwind path of nbl_dev_start(): immediately followed by\n+\t *    nbl_dev_free_mailbox_irq(), channel resources\n+\t *    are fully torn down afterwards, no stale descriptors remain.\n+\t * 2. Normal device stop path nbl_dev_stop(): free_irq() synchronously\n+\t *    waits for any in-flight threaded irq bottom-half handler to\n+\t *    finish execution, all pending mailbox cleanup work completes\n+\t *    before channel teardown in late remove stage. No stuck\n+\t *    descriptors will linger in either scenario.\n+\t */\n+\tchan_ops-\u003eset_queue_state(dev_mgt-\u003echan_ops_tbl-\u003epriv,\n+\t\t\t\t  NBL_CHAN_INTERRUPT_READY,\n+\t\t\t\t  NBL_CHAN_TYPE_MAILBOX, false);\n+\n+\treturn disp_ops-\u003eset_mailbox_irq(dev_mgt-\u003edisp_ops_tbl-\u003epriv,\n+\t\t\t\t\t    local_vec_id, false);\n+}\n+\n+static int nbl_dev_cfg_msix_map(struct nbl_dev_mgt *dev_mgt)\n+{\n+\tstruct nbl_dispatch_ops *disp_ops = dev_mgt-\u003edisp_ops_tbl-\u003eops;\n+\tstruct nbl_dev_common *dev_common = dev_mgt-\u003ecommon_dev;\n+\tstruct nbl_msix_info *msix_info = \u0026dev_common-\u003emsix_info;\n+\tbool mask_en = msix_info-\u003eserv_info[NBL_MSIX_NET_TYPE].hw_self_mask_en;\n+\tu16 msix_net_num = msix_info-\u003eserv_info[NBL_MSIX_NET_TYPE].num;\n+\tu16 msix_not_net_num = 0;\n+\tint err, i;\n+\n+\tmsix_info-\u003eserv_info[NBL_MSIX_VIRTIO_TYPE].base_vector_id = 0;\n+\tfor (i = NBL_MSIX_NET_TYPE; i \u003c NBL_MSIX_TYPE_MAX; i++)\n+\t\tmsix_info-\u003eserv_info[i].base_vector_id =\n+\t\t\tmsix_info-\u003eserv_info[i - 1].base_vector_id +\n+\t\t\tmsix_info-\u003eserv_info[i - 1].num;\n+\n+\tfor (i = 0; i \u003c NBL_MSIX_TYPE_MAX; i++) {\n+\t\tif (i == NBL_MSIX_NET_TYPE)\n+\t\t\tcontinue;\n+\t\tmsix_not_net_num += msix_info-\u003eserv_info[i].num;\n+\t}\n+\n+\terr = disp_ops-\u003ecfg_msix_map(dev_mgt-\u003edisp_ops_tbl-\u003epriv,\n+\t\t\t\t\t   msix_net_num, msix_not_net_num,\n+\t\t\t\t\t   mask_en);\n+\n+\treturn err;\n+}\n+\n+static int nbl_dev_destroy_msix_map(struct nbl_dev_mgt *dev_mgt)\n+{\n+\tstruct nbl_dispatch_ops *disp_ops = dev_mgt-\u003edisp_ops_tbl-\u003eops;\n+\n+\treturn disp_ops-\u003edestroy_msix_map(dev_mgt-\u003edisp_ops_tbl-\u003epriv);\n+}\n+\n+static int nbl_dev_init_interrupt_scheme(struct nbl_dev_mgt *dev_mgt)\n+{\n+\tstruct nbl_dev_common *dev_common = dev_mgt-\u003ecommon_dev;\n+\tstruct nbl_msix_info *msix_info = \u0026dev_common-\u003emsix_info;\n+\tstruct nbl_common_info *common = dev_mgt-\u003ecommon;\n+\tint needed = 0;\n+\tint err;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c NBL_MSIX_TYPE_MAX; i++)\n+\t\tneeded += msix_info-\u003eserv_info[i].num;\n+\n+\terr = pci_alloc_irq_vectors(common-\u003epdev, needed, needed,\n+\t\t\t\t    PCI_IRQ_MSIX | PCI_IRQ_AFFINITY);\n+\tif (err \u003c 0) {\n+\t\tdev_err(common-\u003edev,\n+\t\t\t\"pci_alloc_irq_vectors failed, err = %d\\n\", err);\n+\t\treturn err;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void nbl_dev_clear_interrupt_scheme(struct nbl_dev_mgt *dev_mgt)\n+{\n+\tstruct nbl_common_info *common = dev_mgt-\u003ecommon;\n+\n+\tpci_free_irq_vectors(common-\u003epdev);\n+}\n+\n+/* ----------  Channel config  ---------- */\n+static void nbl_dev_setup_chan_qinfo(struct nbl_dev_mgt *dev_mgt, u8 chan_type)\n+{\n+\tstruct nbl_channel_ops *chan_ops = dev_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_channel_mgt *priv = dev_mgt-\u003echan_ops_tbl-\u003epriv;\n+\tstruct nbl_common_info *common = dev_mgt-\u003ecommon;\n+\n+\tif (!chan_ops-\u003echeck_queue_exist(priv, chan_type))\n+\t\treturn;\n+\n+\tchan_ops-\u003ecfg_chan_qinfo_map_table(priv, common-\u003ehw_bus, common-\u003edevid);\n+}\n+\n+static int nbl_dev_setup_chan_queue(struct nbl_dev_mgt *dev_mgt, u8 chan_type)\n+{\n+\tstruct nbl_channel_ops *chan_ops = dev_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_channel_mgt *priv = dev_mgt-\u003echan_ops_tbl-\u003epriv;\n+\tint ret = 0;\n+\n+\tif (chan_ops-\u003echeck_queue_exist(priv, chan_type))\n+\t\tret = chan_ops-\u003esetup_queue(priv, chan_type);\n+\n+\treturn ret;\n+}\n+\n+static int nbl_dev_remove_chan_queue(struct nbl_dev_mgt *dev_mgt, u8 chan_type)\n+{\n+\tstruct nbl_channel_ops *chan_ops = dev_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_channel_mgt *priv = dev_mgt-\u003echan_ops_tbl-\u003epriv;\n+\tint ret = 0;\n+\n+\tif (chan_ops-\u003echeck_queue_exist(priv, chan_type))\n+\t\tret = chan_ops-\u003eteardown_queue(priv, chan_type);\n+\n+\treturn ret;\n+}\n+\n+static void nbl_dev_register_chan_task(struct nbl_dev_mgt *dev_mgt,\n+\t\t\t\t       u8 chan_type, struct work_struct *task)\n+{\n+\tstruct nbl_channel_ops *chan_ops = dev_mgt-\u003echan_ops_tbl-\u003eops;\n+\n+\tif (chan_ops-\u003echeck_queue_exist(dev_mgt-\u003echan_ops_tbl-\u003epriv, chan_type))\n+\t\tchan_ops-\u003eregister_chan_task(dev_mgt-\u003echan_ops_tbl-\u003epriv,\n+\t\t\t\t\t     chan_type, task);\n+}\n+\n+/* ----------  Tasks config  ---------- */\n+static void nbl_dev_clean_mailbox_task(struct work_struct *work)\n+{\n+\tstruct nbl_dev_common *common_dev =\n+\t\tcontainer_of(work, struct nbl_dev_common, clean_mbx_task);\n+\tstruct nbl_dev_mgt *dev_mgt = common_dev-\u003edev_mgt;\n+\tstruct nbl_channel_ops *chan_ops = dev_mgt-\u003echan_ops_tbl-\u003eops;\n+\n+\tchan_ops-\u003eclean_queue_subtask(dev_mgt-\u003echan_ops_tbl-\u003epriv,\n+\t\t\t\t      NBL_CHAN_TYPE_MAILBOX);\n+}\n+\n+static void nbl_dev_clean_mailbox_schedule(struct nbl_dev_mgt *dev_mgt)\n+{\n+\tstruct nbl_dev_common *common_dev = dev_mgt-\u003ecommon_dev;\n+\tstruct nbl_common_info *common = dev_mgt-\u003ecommon;\n+\n+\tqueue_work(common-\u003ewq, \u0026common_dev-\u003eclean_mbx_task);\n+}\n+\n+/* ----------  Dev init process  ---------- */\n+static int nbl_dev_setup_common_dev(struct nbl_adapter *adapter)\n+{\n+\tstruct nbl_dev_mgt *dev_mgt = adapter-\u003ecore.dev_mgt;\n+\tstruct nbl_dispatch_ops *disp_ops = dev_mgt-\u003edisp_ops_tbl-\u003eops;\n+\tstruct nbl_dispatch_mgt *priv = dev_mgt-\u003edisp_ops_tbl-\u003epriv;\n+\tstruct nbl_common_info *common = dev_mgt-\u003ecommon;\n+\tstruct nbl_dev_common *common_dev;\n+\tint ret;\n+\n+\tcommon_dev = devm_kzalloc(\u0026adapter-\u003epdev-\u003edev, sizeof(*common_dev),\n+\t\t\t\t  GFP_KERNEL);\n+\tif (!common_dev)\n+\t\treturn -ENOMEM;\n+\tcommon_dev-\u003edev_mgt = dev_mgt;\n+\n+\tret = nbl_dev_setup_chan_queue(dev_mgt, NBL_CHAN_TYPE_MAILBOX);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tINIT_WORK(\u0026common_dev-\u003eclean_mbx_task, nbl_dev_clean_mailbox_task);\n+\tnbl_dev_register_chan_task(dev_mgt, NBL_CHAN_TYPE_MAILBOX,\n+\t\t\t\t   \u0026common_dev-\u003eclean_mbx_task);\n+\t/*\n+\t * Even if has_ctrl=false (no dedicated control PF channel), we fetch\n+\t * VSI/ETH info via regular mailbox message  instead of\n+\t * dedicated control command.\n+\t */\n+\tret = disp_ops-\u003eget_vsi_id(priv, NBL_VSI_DATA, \u0026common-\u003evsi_id);\n+\tif (ret)\n+\t\tgoto err_cleanup;\n+\tret = disp_ops-\u003eget_eth_id(priv, common-\u003evsi_id, \u0026common-\u003eeth_num,\n+\t\t\t     \u0026common-\u003eeth_id, \u0026common-\u003elogic_eth_id);\n+\tif (ret)\n+\t\tgoto err_cleanup;\n+\n+\tdev_mgt-\u003ecommon_dev = common_dev;\n+\tnbl_dev_init_msix_cnt(dev_mgt);\n+\n+\treturn 0;\n+err_cleanup:\n+\tcancel_work_sync(\u0026common_dev-\u003eclean_mbx_task);\n+\tnbl_dev_register_chan_task(dev_mgt, NBL_CHAN_TYPE_MAILBOX, NULL);\n+\tnbl_dev_remove_chan_queue(dev_mgt, NBL_CHAN_TYPE_MAILBOX);\n+\treturn ret;\n+}\n+\n+static void nbl_dev_remove_common_dev(struct nbl_adapter *adapter)\n+{\n+\tstruct nbl_dev_mgt *dev_mgt = adapter-\u003ecore.dev_mgt;\n+\tstruct nbl_dev_common *common_dev = dev_mgt-\u003ecommon_dev;\n+\n+\tif (!common_dev)\n+\t\treturn;\n+\tnbl_dev_remove_chan_queue(dev_mgt, NBL_CHAN_TYPE_MAILBOX);\n+\tcancel_work_sync(\u0026common_dev-\u003eclean_mbx_task);\n+\tnbl_dev_register_chan_task(dev_mgt, NBL_CHAN_TYPE_MAILBOX, NULL);\n+}\n+\n+static int nbl_dev_setup_ctrl_dev(struct nbl_adapter *adapter)\n+{\n+\tstruct nbl_dev_mgt *dev_mgt = adapter-\u003ecore.dev_mgt;\n+\tstruct nbl_dispatch_ops *disp_ops = dev_mgt-\u003edisp_ops_tbl-\u003eops;\n+\tint ret;\n+\n+\tret = disp_ops-\u003einit_module(dev_mgt-\u003edisp_ops_tbl-\u003epriv);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tnbl_dev_setup_chan_qinfo(dev_mgt, NBL_CHAN_TYPE_MAILBOX);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * This is intentional.  The qinfo registers are managed by the chip\n+ * firmware, not by the driver.  Setting driver status to false is the\n+ * designed teardown mechanism — it notifies the firmware, which then\n+ * performs its own cleanup of all per-PF state including the qinfo\n+ * registers.\n+ * An inverse helper would duplicate work that the firmware already\n+ * does, and would add error-path complexity for no benefit.  We keep\n+ * the deinit path minimal and rely on the firmware cleanup for\n+ * correctness, including in abnormal reset scenarios.\n+ */\n+static void nbl_dev_remove_ctrl_dev(struct nbl_adapter *adapter)\n+{\n+\tstruct nbl_dev_mgt *dev_mgt = adapter-\u003ecore.dev_mgt;\n+\tstruct nbl_dispatch_ops *disp_ops = dev_mgt-\u003edisp_ops_tbl-\u003eops;\n+\n+\tdisp_ops-\u003edeinit_module(dev_mgt-\u003edisp_ops_tbl-\u003epriv);\n+}\n+\n+static struct nbl_dev_mgt *nbl_dev_setup_dev_mgt(struct nbl_common_info *common)\n+{\n+\tstruct nbl_dev_mgt *dev_mgt;\n+\n+\tdev_mgt = devm_kzalloc(common-\u003edev, sizeof(*dev_mgt), GFP_KERNEL);\n+\tif (!dev_mgt)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tdev_mgt-\u003ecommon = common;\n+\treturn dev_mgt;\n+}\n+\n+int nbl_dev_init(struct nbl_adapter *adapter)\n+{\n+\tstruct nbl_common_info *common = \u0026adapter-\u003ecommon;\n+\tstruct nbl_dispatch_ops_tbl *disp_ops_tbl =\n+\t\tadapter-\u003eintf.dispatch_ops_tbl;\n+\tstruct nbl_channel_ops_tbl *chan_ops_tbl =\n+\t\tadapter-\u003eintf.channel_ops_tbl;\n+\tstruct nbl_dev_mgt *dev_mgt;\n+\tint ret;\n+\n+\tdev_mgt = nbl_dev_setup_dev_mgt(common);\n+\tif (IS_ERR(dev_mgt)) {\n+\t\tret = PTR_ERR(dev_mgt);\n+\t\treturn ret;\n+\t}\n+\n+\tdev_mgt-\u003edisp_ops_tbl = disp_ops_tbl;\n+\tdev_mgt-\u003echan_ops_tbl = chan_ops_tbl;\n+\tadapter-\u003ecore.dev_mgt = dev_mgt;\n+\tret = nbl_common_create_wq(common);\n+\tif (ret)\n+\t\treturn ret;\n+\t/*\n+\t * Chip hardware initialization is completed by firmware at power-up.\n+\t * Only driver functional table/register config follows here, safe to\n+\t * access hardware registers before ctrl dev setup.\n+\t */\n+\tret = nbl_dev_setup_common_dev(adapter);\n+\tif (ret)\n+\t\tgoto setup_err;\n+\n+\tif (common-\u003ehas_ctrl) {\n+\t\tret = nbl_dev_setup_ctrl_dev(adapter);\n+\t\tif (ret)\n+\t\t\tgoto setup_ctrl_dev_fail;\n+\t}\n+\n+\treturn 0;\n+setup_ctrl_dev_fail:\n+\tnbl_dev_remove_common_dev(adapter);\n+setup_err:\n+\tnbl_common_destroy_wq(common);\n+\treturn ret;\n+}\n+\n+/*\n+ * Teardown order: ctrl dev first, then common dev.\n+ * nbl_dev_remove_ctrl_dev() notifies firmware to clean all per-PF state\n+ * (including qinfo registers), so subsequent common dev queue cleanup\n+ * will not trigger PCIe master abort or invalid register access.\n+ */\n+void nbl_dev_remove(struct nbl_adapter *adapter)\n+{\n+\tstruct nbl_common_info *common = \u0026adapter-\u003ecommon;\n+\n+\tif (common-\u003ehas_ctrl)\n+\t\tnbl_dev_remove_ctrl_dev(adapter);\n+\tnbl_dev_remove_common_dev(adapter);\n+\tnbl_common_destroy_wq(common);\n+}\n+\n+/* ----------  Dev start process  ---------- */\n+int nbl_dev_start(struct nbl_adapter *adapter)\n+{\n+\tstruct nbl_dev_mgt *dev_mgt = adapter-\u003ecore.dev_mgt;\n+\tstruct nbl_dev_common *common_dev = dev_mgt-\u003ecommon_dev;\n+\tint cleanup_ret;\n+\tint ret;\n+\n+\tret = nbl_dev_cfg_msix_map(dev_mgt);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = nbl_dev_init_interrupt_scheme(dev_mgt);\n+\tif (ret)\n+\t\tgoto init_interrupt_scheme_err;\n+\tret = nbl_dev_request_mailbox_irq(dev_mgt);\n+\tif (ret)\n+\t\tgoto mailbox_request_irq_err;\n+\tret = nbl_dev_enable_mailbox_irq(dev_mgt);\n+\tif (ret)\n+\t\tgoto enable_mailbox_irq_err;\n+\n+\treturn 0;\n+enable_mailbox_irq_err:\n+\tcleanup_ret = nbl_dev_disable_mailbox_irq(dev_mgt);\n+\tif (cleanup_ret)\n+\t\tdev_err(dev_mgt-\u003ecommon-\u003edev,\n+\t\t\t\"Failed to disable mailbox IRQ: %d\\n\", cleanup_ret);\n+\tnbl_dev_free_mailbox_irq(dev_mgt);\n+\tcancel_work_sync(\u0026common_dev-\u003eclean_mbx_task);\n+mailbox_request_irq_err:\n+\tnbl_dev_clear_interrupt_scheme(dev_mgt);\n+init_interrupt_scheme_err:\n+\tcleanup_ret =  nbl_dev_destroy_msix_map(dev_mgt);\n+\tif (cleanup_ret)\n+\t\tdev_err(dev_mgt-\u003ecommon-\u003edev,\n+\t\t\t\"Failed to destroy MSI-X map: %d\\n\", cleanup_ret);\n+\treturn ret;\n+}\n+\n+void nbl_dev_stop(struct nbl_adapter *adapter)\n+{\n+\tstruct nbl_dev_mgt *dev_mgt = adapter-\u003ecore.dev_mgt;\n+\tstruct nbl_dev_common *common_dev = dev_mgt-\u003ecommon_dev;\n+\tint ret;\n+\n+\tret = nbl_dev_disable_mailbox_irq(dev_mgt);\n+\tif (ret)\n+\t\tdev_err(dev_mgt-\u003ecommon-\u003edev,\n+\t\t\t\"Failed to disable mailbox IRQ: %d\\n\", ret);\n+\tnbl_dev_free_mailbox_irq(dev_mgt);\n+\tcancel_work_sync(\u0026common_dev-\u003eclean_mbx_task);\n+\tnbl_dev_clear_interrupt_scheme(dev_mgt);\n+\tret = nbl_dev_destroy_msix_map(dev_mgt);\n+\tif (ret)\n+\t\tdev_err(dev_mgt-\u003ecommon-\u003edev,\n+\t\t\t\"Failed to destroy MSI-X map: %d\\n\", ret);\n+}\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.h\nnew file mode 100644\nindex 0000000000000..7666ab9a73f3f\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.h\n@@ -0,0 +1,57 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_DEV_H_\n+#define _NBL_DEV_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+#include \"../nbl_include/nbl_include.h\"\n+#include \"../nbl_include/nbl_def_channel.h\"\n+#include \"../nbl_include/nbl_def_hw.h\"\n+#include \"../nbl_include/nbl_def_resource.h\"\n+#include \"../nbl_include/nbl_def_dispatch.h\"\n+#include \"../nbl_include/nbl_def_dev.h\"\n+#include \"../nbl_include/nbl_def_common.h\"\n+#include \"../nbl_core.h\"\n+\n+#define NBL_STRING_NAME_LEN\t\t\t32\n+\n+enum nbl_msix_serv_type {\n+\t/* virtio_dev has a config vector_id, and the vector_id need is 0 */\n+\tNBL_MSIX_VIRTIO_TYPE = 0,\n+\tNBL_MSIX_NET_TYPE,\n+\tNBL_MSIX_MAILBOX_TYPE,\n+\tNBL_MSIX_TYPE_MAX\n+};\n+\n+struct nbl_msix_serv_info {\n+\tchar irq_name[NBL_STRING_NAME_LEN];\n+\tu16 num;\n+\tu16 base_vector_id;\n+\t/* true: hw report msix, hw need to mask actively */\n+\tbool hw_self_mask_en;\n+};\n+\n+struct nbl_msix_info {\n+\tstruct nbl_msix_serv_info serv_info[NBL_MSIX_TYPE_MAX];\n+};\n+\n+struct nbl_dev_common {\n+\tstruct nbl_dev_mgt *dev_mgt;\n+\tstruct nbl_msix_info msix_info;\n+\tchar mailbox_name[NBL_STRING_NAME_LEN];\n+\t/* for ctrl-dev/net-dev mailbox recv msg */\n+\tstruct work_struct clean_mbx_task;\n+};\n+\n+struct nbl_dev_mgt {\n+\tstruct nbl_common_info *common;\n+\tstruct nbl_dispatch_ops_tbl *disp_ops_tbl;\n+\tstruct nbl_channel_ops_tbl *chan_ops_tbl;\n+\tstruct nbl_dev_common *common_dev;\n+};\n+\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c\nnew file mode 100644\nindex 0000000000000..3fef901c8e25f\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c\n@@ -0,0 +1,633 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/pci.h\u003e\n+#include \"nbl_dispatch.h\"\n+\n+static int nbl_disp_chan_get_vsi_id_req(struct nbl_dispatch_mgt *disp_mgt,\n+\t\t\t\t\tu16 type, u16 *vsi_id)\n+{\n+\tstruct nbl_channel_ops *chan_ops = disp_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_common_info *common = disp_mgt-\u003ecommon;\n+\tstruct nbl_chan_param_get_vsi_id result = { 0 };\n+\tstruct nbl_chan_param_get_vsi_id param = { 0 };\n+\tstruct nbl_chan_send_info chan_send;\n+\tint ret;\n+\n+\tparam.type = cpu_to_le16(type);\n+\n+\tnbl_chan_fill_send_info(\u0026chan_send, common-\u003emgt_pf,\n+\t\t\t\tNBL_CHAN_MSG_GET_VSI_ID,\n+\t\t\t\t\u0026param, sizeof(param), \u0026result,\n+\t\t\t\tsizeof(result), 1);\n+\tret = chan_ops-\u003esend_msg(disp_mgt-\u003echan_ops_tbl-\u003epriv, \u0026chan_send);\n+\tswitch (ret) {\n+\tcase NBL_CHAN_RESP_UNIMPLEMENTED:\n+\t\treturn -EOPNOTSUPP;\n+\tcase NBL_CHAN_RESP_ERR:\n+\t\treturn -EREMOTEIO;\n+\tcase NBL_CHAN_RESP_OK:\n+\t\tbreak;\n+\tdefault:\n+\t\treturn ret;\n+\t}\n+\t*vsi_id = le16_to_cpu(result.vsi_id);\n+\treturn 0;\n+}\n+\n+static void nbl_disp_chan_get_vsi_id_resp(void *priv, u16 src_id, u16 msg_id,\n+\t\t\t\t\t  void *data, u32 data_len)\n+{\n+\tstruct nbl_dispatch_mgt *disp_mgt = (struct nbl_dispatch_mgt *)priv;\n+\tstruct nbl_channel_ops *chan_ops = disp_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\tstruct device *dev = disp_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_chan_param_get_vsi_id result = { 0 };\n+\tstruct nbl_chan_param_get_vsi_id param = { 0 };\n+\tstruct nbl_chan_ack_info chan_ack;\n+\tint err = NBL_CHAN_RESP_OK;\n+\tu16 vsi_id = 0;\n+\tint copy_len;\n+\tint ret;\n+\n+\tif (data_len == 0) {\n+\t\terr = NBL_CHAN_RESP_ERR;\n+\t\tgoto ack_out;\n+\t}\n+\tcopy_len = min_t(size_t, data_len, sizeof(param));\n+\tmemcpy(\u0026param, data, copy_len);\n+\n+\tif (res_ops-\u003eget_vsi_id) {\n+\t\tret = res_ops-\u003eget_vsi_id(p, src_id, le16_to_cpu(param.type),\n+\t\t\t\t\t  \u0026vsi_id);\n+\t\tif (ret)\n+\t\t\terr = NBL_CHAN_RESP_ERR;\n+\t} else {\n+\t\terr = NBL_CHAN_RESP_UNIMPLEMENTED;\n+\t}\n+\n+\tresult.vsi_id = cpu_to_le16(vsi_id);\n+ack_out:\n+\tnbl_chan_fill_ack_info(\u0026chan_ack, src_id,\n+\t\t\t       NBL_CHAN_MSG_GET_VSI_ID, msg_id, err,\n+\t\t\t       \u0026result, sizeof(result));\n+\tret = chan_ops-\u003esend_ack(disp_mgt-\u003echan_ops_tbl-\u003epriv, \u0026chan_ack);\n+\tif (ret)\n+\t\tdev_err(dev,\n+\t\t\t\"channel send ack failed with ret: %d, msg_type: %d\\n\",\n+\t\t\tret, NBL_CHAN_MSG_GET_VSI_ID);\n+}\n+\n+static int nbl_disp_chan_get_eth_id_req(struct nbl_dispatch_mgt *disp_mgt,\n+\t\t\t\t\tu16 vsi_id, u8 *eth_num, u8 *eth_id,\n+\t\t\t\t\tu8 *logic_eth_id)\n+{\n+\tstruct nbl_channel_ops *chan_ops = disp_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_common_info *common = disp_mgt-\u003ecommon;\n+\tstruct nbl_chan_param_get_eth_id result = { 0 };\n+\tstruct nbl_chan_param_get_eth_id param = { 0 };\n+\tstruct nbl_chan_send_info chan_send;\n+\tint ret;\n+\n+\tparam.vsi_id = cpu_to_le16(vsi_id);\n+\n+\tnbl_chan_fill_send_info(\u0026chan_send, common-\u003emgt_pf,\n+\t\t\t\tNBL_CHAN_MSG_GET_ETH_ID,\n+\t\t\t\t\u0026param, sizeof(param), \u0026result,\n+\t\t\t\tsizeof(result), 1);\n+\tret = chan_ops-\u003esend_msg(disp_mgt-\u003echan_ops_tbl-\u003epriv, \u0026chan_send);\n+\tswitch (ret) {\n+\tcase NBL_CHAN_RESP_UNIMPLEMENTED:\n+\t\treturn -EOPNOTSUPP;\n+\tcase NBL_CHAN_RESP_ERR:\n+\t\treturn -EREMOTEIO;\n+\tcase NBL_CHAN_RESP_OK:\n+\t\tbreak;\n+\tdefault:\n+\t\treturn ret;\n+\t}\n+\t*eth_num = result.eth_num;\n+\t*eth_id = result.eth_id;\n+\t*logic_eth_id = result.logic_eth_id;\n+\n+\treturn 0;\n+}\n+\n+static void nbl_disp_chan_get_eth_id_resp(void *priv, u16 src_id, u16 msg_id,\n+\t\t\t\t\t  void *data, u32 data_len)\n+{\n+\tstruct nbl_dispatch_mgt *disp_mgt = (struct nbl_dispatch_mgt *)priv;\n+\tstruct nbl_channel_ops *chan_ops = disp_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\tstruct nbl_chan_param_get_eth_id result = { 0 };\n+\tstruct nbl_chan_param_get_eth_id param = { 0 };\n+\tstruct device *dev = disp_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_chan_ack_info chan_ack;\n+\tint err = NBL_CHAN_RESP_OK;\n+\tint copy_len;\n+\tint ret;\n+\n+\tif (data_len == 0) {\n+\t\terr = NBL_CHAN_RESP_ERR;\n+\t\tgoto ack_out;\n+\t}\n+\tcopy_len = min_t(size_t, data_len, sizeof(param));\n+\tmemcpy(\u0026param, data, copy_len);\n+\n+\tif (res_ops-\u003eget_eth_id) {\n+\t\tret = res_ops-\u003eget_eth_id(p, src_id, le16_to_cpu(param.vsi_id),\n+\t\t\t\t\t  \u0026result.eth_num, \u0026result.eth_id,\n+\t\t\t\t\t  \u0026result.logic_eth_id);\n+\t\tif (ret)\n+\t\t\terr = NBL_CHAN_RESP_ERR;\n+\t} else {\n+\t\terr = NBL_CHAN_RESP_UNIMPLEMENTED;\n+\t}\n+ack_out:\n+\tnbl_chan_fill_ack_info(\u0026chan_ack, src_id,\n+\t\t\t       NBL_CHAN_MSG_GET_ETH_ID, msg_id, err,\n+\t\t\t       \u0026result, sizeof(result));\n+\tret = chan_ops-\u003esend_ack(disp_mgt-\u003echan_ops_tbl-\u003epriv, \u0026chan_ack);\n+\tif (ret)\n+\t\tdev_err(dev,\n+\t\t\t\"channel send ack failed with ret: %d, msg_type: %d\\n\",\n+\t\t\tret, NBL_CHAN_MSG_GET_ETH_ID);\n+}\n+\n+static void nbl_disp_deinit_module(struct nbl_dispatch_mgt *disp_mgt)\n+{\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\n+\tif (res_ops-\u003edeinit_module)\n+\t\tres_ops-\u003edeinit_module(p);\n+}\n+\n+static int nbl_disp_init_module(struct nbl_dispatch_mgt *disp_mgt)\n+{\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\n+\tif (res_ops-\u003einit_module)\n+\t\treturn res_ops-\u003einit_module(p);\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static int nbl_disp_cfg_msix_map(struct nbl_dispatch_mgt *disp_mgt,\n+\t\t\t\t u16 num_net_msix, u16 num_others_msix,\n+\t\t\t\t bool net_msix_mask_en)\n+{\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\tstruct nbl_common_info *common = disp_mgt-\u003ecommon;\n+\tint ret;\n+\n+\tif (!res_ops-\u003ecfg_msix_map)\n+\t\treturn -EOPNOTSUPP;\n+\tmutex_lock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\tret = res_ops-\u003ecfg_msix_map(p, common-\u003emgt_pf, num_net_msix,\n+\t\t\t\t\t  num_others_msix, net_msix_mask_en);\n+\tmutex_unlock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\treturn ret;\n+}\n+\n+static int\n+nbl_disp_chan_cfg_msix_map_req(struct nbl_dispatch_mgt *disp_mgt,\n+\t\t\t       u16 num_net_msix, u16 num_others_msix,\n+\t\t\t       bool net_msix_mask_en)\n+{\n+\tstruct nbl_channel_ops *chan_ops = disp_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_common_info *common = disp_mgt-\u003ecommon;\n+\tstruct nbl_chan_param_cfg_msix_map param = { 0 };\n+\tstruct nbl_chan_send_info chan_send;\n+\tint ret;\n+\n+\tparam.num_net_msix = cpu_to_le16(num_net_msix);\n+\tparam.num_others_msix = cpu_to_le16(num_others_msix);\n+\tparam.msix_mask_en = cpu_to_le16(!!net_msix_mask_en);\n+\n+\tnbl_chan_fill_send_info(\u0026chan_send, common-\u003emgt_pf,\n+\t\t\t\tNBL_CHAN_MSG_CONFIGURE_MSIX_MAP,\n+\t\t\t\t\u0026param, sizeof(param),\n+\t\t\t\tNULL, 0, 1);\n+\tret = chan_ops-\u003esend_msg(disp_mgt-\u003echan_ops_tbl-\u003epriv, \u0026chan_send);\n+\tswitch (ret) {\n+\tcase NBL_CHAN_RESP_UNIMPLEMENTED:\n+\t\treturn -EOPNOTSUPP;\n+\tcase NBL_CHAN_RESP_PERM_DENY:\n+\t\treturn -EPERM;\n+\tcase NBL_CHAN_RESP_ERR:\n+\t\treturn -EREMOTEIO;\n+\tcase NBL_CHAN_RESP_OK:\n+\t\treturn ret;\n+\tdefault:\n+\t\treturn ret;\n+\t}\n+}\n+\n+static void nbl_disp_chan_cfg_msix_map_resp(void *priv, u16 src_id, u16 msg_id,\n+\t\t\t\t\t    void *data, u32 data_len)\n+{\n+\tstruct nbl_dispatch_mgt *disp_mgt = (struct nbl_dispatch_mgt *)priv;\n+\tstruct nbl_channel_ops *chan_ops = disp_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\tstruct device *dev = disp_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_chan_param_cfg_msix_map param = { 0 };\n+\tstruct nbl_chan_ack_info chan_ack;\n+\tint err = NBL_CHAN_RESP_OK;\n+\tint copy_len;\n+\tint ret;\n+\n+\tif (src_id \u003e NBL_MAX_PF_SRC_ID) {\n+\t\terr = NBL_CHAN_RESP_PERM_DENY;\n+\t\tgoto ack_out;\n+\t}\n+\tif (data_len == 0) {\n+\t\terr = NBL_CHAN_RESP_ERR;\n+\t\tgoto ack_out;\n+\t}\n+\tcopy_len = min_t(size_t, data_len, sizeof(param));\n+\tmemcpy(\u0026param, data, copy_len);\n+\n+\tif (res_ops-\u003ecfg_msix_map) {\n+\t\tmutex_lock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\t\tret = res_ops-\u003ecfg_msix_map(p, src_id,\n+\t\t\t\t\t    le16_to_cpu(param.num_net_msix),\n+\t\t\t\t\t    le16_to_cpu(param.num_others_msix),\n+\t\t\t\t\t    !!le16_to_cpu(param.msix_mask_en));\n+\t\tmutex_unlock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\t\tif (ret)\n+\t\t\terr = NBL_CHAN_RESP_ERR;\n+\t} else {\n+\t\terr = NBL_CHAN_RESP_UNIMPLEMENTED;\n+\t}\n+ack_out:\n+\tnbl_chan_fill_ack_info(\u0026chan_ack, src_id,\n+\t\t\t       NBL_CHAN_MSG_CONFIGURE_MSIX_MAP, msg_id,\n+\t\t\t       err, NULL, 0);\n+\tret = chan_ops-\u003esend_ack(disp_mgt-\u003echan_ops_tbl-\u003epriv, \u0026chan_ack);\n+\tif (ret)\n+\t\tdev_err(dev,\n+\t\t\t\"channel send ack failed with ret: %d, msg_type: %d\\n\",\n+\t\t\tret, NBL_CHAN_MSG_CONFIGURE_MSIX_MAP);\n+}\n+\n+static int nbl_disp_chan_destroy_msix_map_req(struct nbl_dispatch_mgt *disp_mgt)\n+{\n+\tstruct nbl_channel_ops *chan_ops = disp_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_common_info *common = disp_mgt-\u003ecommon;\n+\tstruct nbl_chan_send_info chan_send;\n+\tint ret;\n+\n+\tnbl_chan_fill_send_info(\u0026chan_send, common-\u003emgt_pf,\n+\t\t\t\tNBL_CHAN_MSG_DESTROY_MSIX_MAP,\n+\t\t\t\tNULL, 0, NULL, 0, 1);\n+\tret = chan_ops-\u003esend_msg(disp_mgt-\u003echan_ops_tbl-\u003epriv, \u0026chan_send);\n+\tswitch (ret) {\n+\tcase NBL_CHAN_RESP_UNIMPLEMENTED:\n+\t\treturn -EOPNOTSUPP;\n+\tcase NBL_CHAN_RESP_PERM_DENY:\n+\t\treturn -EPERM;\n+\tcase NBL_CHAN_RESP_ERR:\n+\t\treturn -EREMOTEIO;\n+\tcase NBL_CHAN_RESP_OK:\n+\t\treturn ret;\n+\tdefault:\n+\t\treturn ret;\n+\t}\n+}\n+\n+static void nbl_disp_chan_destroy_msix_map_resp(void *priv, u16 src_id,\n+\t\t\t\t\t\tu16 msg_id, void *data,\n+\t\t\t\t\t\tu32 data_len)\n+{\n+\tstruct nbl_dispatch_mgt *disp_mgt = (struct nbl_dispatch_mgt *)priv;\n+\tstruct nbl_channel_ops *chan_ops = disp_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\tstruct device *dev = disp_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_chan_ack_info chan_ack;\n+\tint err = NBL_CHAN_RESP_OK;\n+\tint ret;\n+\n+\tif (src_id \u003e NBL_MAX_PF_SRC_ID) {\n+\t\terr = NBL_CHAN_RESP_PERM_DENY;\n+\t\tgoto ack_out;\n+\t}\n+\tif (res_ops-\u003edestroy_msix_map) {\n+\t\tmutex_lock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\t\tret = res_ops-\u003edestroy_msix_map(p, src_id);\n+\t\tmutex_unlock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\t\tif (ret)\n+\t\t\terr = NBL_CHAN_RESP_ERR;\n+\t} else {\n+\t\terr = NBL_CHAN_RESP_UNIMPLEMENTED;\n+\t}\n+ack_out:\n+\tnbl_chan_fill_ack_info(\u0026chan_ack, src_id,\n+\t\t\t       NBL_CHAN_MSG_DESTROY_MSIX_MAP, msg_id,\n+\t\t\t       err, NULL, 0);\n+\tret = chan_ops-\u003esend_ack(disp_mgt-\u003echan_ops_tbl-\u003epriv, \u0026chan_ack);\n+\tif (ret)\n+\t\tdev_err(dev,\n+\t\t\t\"channel send ack failed with ret: %d, msg_type: %d\\n\",\n+\t\t\tret, NBL_CHAN_MSG_DESTROY_MSIX_MAP);\n+}\n+\n+static int nbl_disp_chan_set_mailbox_irq_req(struct nbl_dispatch_mgt *disp_mgt,\n+\t\t\t\t\t     u16 vector_id, bool en_msix)\n+{\n+\tstruct nbl_channel_ops *chan_ops = disp_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_chan_param_set_mailbox_irq param = { 0 };\n+\tstruct nbl_common_info *common = disp_mgt-\u003ecommon;\n+\tstruct nbl_chan_send_info chan_send;\n+\tint ret;\n+\n+\tparam.vector_id = cpu_to_le16(vector_id);\n+\tparam.en_msix = !!en_msix;\n+\n+\tnbl_chan_fill_send_info(\u0026chan_send, common-\u003emgt_pf,\n+\t\t\t\tNBL_CHAN_MSG_MAILBOX_SET_IRQ,\n+\t\t\t\t\u0026param, sizeof(param), NULL, 0, 1);\n+\tret = chan_ops-\u003esend_msg(disp_mgt-\u003echan_ops_tbl-\u003epriv, \u0026chan_send);\n+\tswitch (ret) {\n+\tcase NBL_CHAN_RESP_UNIMPLEMENTED:\n+\t\treturn -EOPNOTSUPP;\n+\tcase NBL_CHAN_RESP_ERR:\n+\t\treturn -EREMOTEIO;\n+\tcase NBL_CHAN_RESP_OK:\n+\t\treturn ret;\n+\tdefault:\n+\t\treturn ret;\n+\t}\n+}\n+\n+static void nbl_disp_chan_set_mailbox_irq_resp(void *priv, u16 src_id,\n+\t\t\t\t\t       u16 msg_id, void *data,\n+\t\t\t\t\t       u32 data_len)\n+{\n+\tstruct nbl_dispatch_mgt *disp_mgt = (struct nbl_dispatch_mgt *)priv;\n+\tstruct nbl_channel_ops *chan_ops = disp_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\tstruct nbl_chan_param_set_mailbox_irq param = { 0 };\n+\tstruct device *dev = disp_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_chan_ack_info chan_ack;\n+\tint err = NBL_CHAN_RESP_OK;\n+\tbool en_msix;\n+\tu16 vector_id;\n+\tint copy_len;\n+\tint ret;\n+\n+\tif (data_len == 0) {\n+\t\terr = NBL_CHAN_RESP_ERR;\n+\t\tgoto ack_out;\n+\t}\n+\tcopy_len = min_t(size_t, data_len, sizeof(param));\n+\tmemcpy(\u0026param, data, copy_len);\n+\tvector_id = le16_to_cpu(param.vector_id);\n+\ten_msix = !!param.en_msix;\n+\n+\tif (res_ops-\u003eset_mailbox_irq) {\n+\t\tmutex_lock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\t\tret = res_ops-\u003eset_mailbox_irq(p, src_id, vector_id, en_msix);\n+\t\tmutex_unlock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\t\tif (ret)\n+\t\t\terr = NBL_CHAN_RESP_ERR;\n+\t} else {\n+\t\terr = NBL_CHAN_RESP_UNIMPLEMENTED;\n+\t}\n+\n+ack_out:\n+\tnbl_chan_fill_ack_info(\u0026chan_ack, src_id,\n+\t\t\t       NBL_CHAN_MSG_MAILBOX_SET_IRQ, msg_id,\n+\t\t\t       err, NULL, 0);\n+\tret = chan_ops-\u003esend_ack(disp_mgt-\u003echan_ops_tbl-\u003epriv, \u0026chan_ack);\n+\tif (ret)\n+\t\tdev_err(dev,\n+\t\t\t\"channel send ack failed with ret: %d, msg_type: %d\\n\",\n+\t\t\tret, NBL_CHAN_MSG_MAILBOX_SET_IRQ);\n+}\n+\n+static int nbl_disp_destroy_msix_map(struct nbl_dispatch_mgt *disp_mgt)\n+{\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\tstruct nbl_common_info *common = disp_mgt-\u003ecommon;\n+\tint ret;\n+\n+\tif (!res_ops-\u003edestroy_msix_map)\n+\t\treturn -EOPNOTSUPP;\n+\tmutex_lock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\tret = res_ops-\u003edestroy_msix_map(p, common-\u003emgt_pf);\n+\tmutex_unlock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\treturn ret;\n+}\n+\n+static int nbl_disp_set_mailbox_irq(struct nbl_dispatch_mgt *disp_mgt,\n+\t\t\t\t    u16 vector_id, bool en_msix)\n+{\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\tstruct nbl_common_info *common = disp_mgt-\u003ecommon;\n+\tint ret;\n+\n+\tif (!res_ops-\u003eset_mailbox_irq)\n+\t\treturn -EOPNOTSUPP;\n+\tmutex_lock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\tret = res_ops-\u003eset_mailbox_irq(p, common-\u003emgt_pf, vector_id, en_msix);\n+\tmutex_unlock(\u0026disp_mgt-\u003eops_mutex_lock);\n+\treturn ret;\n+}\n+\n+static int nbl_disp_get_vsi_id(struct nbl_dispatch_mgt *disp_mgt, u16 type,\n+\t\t\t       u16 *vsi_id)\n+{\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\tstruct nbl_common_info *common = disp_mgt-\u003ecommon;\n+\n+\tif (res_ops-\u003eget_vsi_id)\n+\t\treturn res_ops-\u003eget_vsi_id(p, common-\u003emgt_pf, type, vsi_id);\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static int nbl_disp_get_eth_id(struct nbl_dispatch_mgt *disp_mgt, u16 vsi_id,\n+\t\t\t       u8 *eth_num, u8 *eth_id, u8 *logic_eth_id)\n+{\n+\tstruct nbl_resource_ops *res_ops = disp_mgt-\u003eres_ops_tbl-\u003eops;\n+\tstruct nbl_resource_mgt *p = disp_mgt-\u003eres_ops_tbl-\u003epriv;\n+\tstruct nbl_common_info *common = disp_mgt-\u003ecommon;\n+\n+\tif (res_ops-\u003eget_eth_id)\n+\t\treturn res_ops-\u003eget_eth_id(p, common-\u003emgt_pf, vsi_id,\n+\t\t\t\t\t   eth_num, eth_id, logic_eth_id);\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static int nbl_disp_setup_msg(struct nbl_dispatch_mgt *disp_mgt)\n+{\n+\tstruct nbl_channel_ops *chan_ops = disp_mgt-\u003echan_ops_tbl-\u003eops;\n+\tstruct nbl_channel_mgt *p = disp_mgt-\u003echan_ops_tbl-\u003epriv;\n+\tint ret = 0;\n+\tint _ret;\n+\n+\t_ret = chan_ops-\u003eregister_msg(p, NBL_CHAN_MSG_CONFIGURE_MSIX_MAP,\n+\t\t\t\t      nbl_disp_chan_cfg_msix_map_resp,\n+\t\t\t\t      disp_mgt);\n+\tif (_ret \u003c 0 \u0026\u0026 !ret)\n+\t\tret = _ret;\n+\n+\t_ret = chan_ops-\u003eregister_msg(p, NBL_CHAN_MSG_DESTROY_MSIX_MAP,\n+\t\t\t\t      nbl_disp_chan_destroy_msix_map_resp,\n+\t\t\t\t      disp_mgt);\n+\tif (_ret \u003c 0 \u0026\u0026 !ret)\n+\t\tret = _ret;\n+\n+\t_ret = chan_ops-\u003eregister_msg(p, NBL_CHAN_MSG_MAILBOX_SET_IRQ,\n+\t\t\t\t      nbl_disp_chan_set_mailbox_irq_resp,\n+\t\t\t\t      disp_mgt);\n+\tif (_ret \u003c 0 \u0026\u0026 !ret)\n+\t\tret = _ret;\n+\n+\t_ret = chan_ops-\u003eregister_msg(p, NBL_CHAN_MSG_GET_VSI_ID,\n+\t\t\t\t      nbl_disp_chan_get_vsi_id_resp,\n+\t\t\t\t      disp_mgt);\n+\tif (_ret \u003c 0 \u0026\u0026 !ret)\n+\t\tret = _ret;\n+\n+\t_ret = chan_ops-\u003eregister_msg(p, NBL_CHAN_MSG_GET_ETH_ID,\n+\t\t\t\t      nbl_disp_chan_get_eth_id_resp,\n+\t\t\t\t      disp_mgt);\n+\tif (_ret \u003c 0 \u0026\u0026 !ret)\n+\t\tret = _ret;\n+\n+\tif (ret)\n+\t\tchan_ops-\u003eunregister_all_msg(p);\n+\treturn ret;\n+}\n+\n+static void nbl_disp_setup_ctrl_lvl(struct nbl_dispatch_mgt *disp_mgt, u32 lvl)\n+{\n+\tstruct nbl_dispatch_ops *disp_ops = disp_mgt-\u003edisp_ops_tbl-\u003eops;\n+\n+\tset_bit(lvl, disp_mgt-\u003ectrl_lvl);\n+\n+\tif (test_bit(NBL_DISP_CTRL_LVL_MGT, disp_mgt-\u003ectrl_lvl)) {\n+\t\tdisp_ops-\u003einit_module = nbl_disp_init_module;\n+\t\tdisp_ops-\u003edeinit_module = nbl_disp_deinit_module;\n+\t\tdisp_ops-\u003ecfg_msix_map = nbl_disp_cfg_msix_map;\n+\t\tdisp_ops-\u003edestroy_msix_map = nbl_disp_destroy_msix_map;\n+\t\tdisp_ops-\u003eset_mailbox_irq = nbl_disp_set_mailbox_irq;\n+\t\tdisp_ops-\u003eget_vsi_id = nbl_disp_get_vsi_id;\n+\t\tdisp_ops-\u003eget_eth_id = nbl_disp_get_eth_id;\n+\t} else {\n+\t\tdisp_ops-\u003ecfg_msix_map =\n+\t\t\tnbl_disp_chan_cfg_msix_map_req;\n+\t\tdisp_ops-\u003edestroy_msix_map = nbl_disp_chan_destroy_msix_map_req;\n+\t\tdisp_ops-\u003eset_mailbox_irq = nbl_disp_chan_set_mailbox_irq_req;\n+\t\tdisp_ops-\u003eget_vsi_id = nbl_disp_chan_get_vsi_id_req;\n+\t\tdisp_ops-\u003eget_eth_id = nbl_disp_chan_get_eth_id_req;\n+\t}\n+}\n+\n+static struct nbl_dispatch_mgt *\n+nbl_disp_setup_disp_mgt(struct nbl_common_info *common)\n+{\n+\tstruct nbl_dispatch_mgt *disp_mgt;\n+\tstruct device *dev = common-\u003edev;\n+\tint err;\n+\n+\tdisp_mgt = devm_kzalloc(dev, sizeof(*disp_mgt), GFP_KERNEL);\n+\tif (!disp_mgt)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tdisp_mgt-\u003ecommon = common;\n+\terr = devm_mutex_init(common-\u003edev, \u0026disp_mgt-\u003eops_mutex_lock);\n+\tif (err)\n+\t\treturn ERR_PTR(err);\n+\treturn disp_mgt;\n+}\n+\n+static struct nbl_dispatch_ops_tbl *\n+nbl_disp_setup_ops(struct device *dev, struct nbl_dispatch_mgt *disp_mgt)\n+{\n+\tstruct nbl_dispatch_ops_tbl *disp_ops_tbl;\n+\tstruct nbl_dispatch_ops *disp_ops;\n+\n+\tdisp_ops_tbl = devm_kzalloc(dev, sizeof(*disp_ops_tbl), GFP_KERNEL);\n+\tif (!disp_ops_tbl)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tdisp_ops = devm_kzalloc(dev, sizeof(*disp_ops), GFP_KERNEL);\n+\tif (!disp_ops)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tdisp_ops_tbl-\u003eops = disp_ops;\n+\tdisp_ops_tbl-\u003epriv = disp_mgt;\n+\n+\treturn disp_ops_tbl;\n+}\n+\n+int nbl_disp_init(struct nbl_adapter *adapter)\n+{\n+\tstruct nbl_common_info *common = \u0026adapter-\u003ecommon;\n+\tstruct nbl_dispatch_ops_tbl *disp_ops_tbl;\n+\tstruct nbl_resource_ops_tbl *res_ops_tbl =\n+\t\tadapter-\u003eintf.resource_ops_tbl;\n+\tstruct nbl_channel_ops_tbl *chan_ops_tbl =\n+\t\tadapter-\u003eintf.channel_ops_tbl;\n+\tstruct device *dev = \u0026adapter-\u003epdev-\u003edev;\n+\tstruct nbl_dispatch_mgt *disp_mgt;\n+\tint ret;\n+\n+\tdisp_mgt = nbl_disp_setup_disp_mgt(common);\n+\tif (IS_ERR(disp_mgt)) {\n+\t\tret = PTR_ERR(disp_mgt);\n+\t\treturn ret;\n+\t}\n+\n+\tdisp_ops_tbl = nbl_disp_setup_ops(dev, disp_mgt);\n+\tif (IS_ERR(disp_ops_tbl)) {\n+\t\tret = PTR_ERR(disp_ops_tbl);\n+\t\treturn ret;\n+\t}\n+\n+\tdisp_mgt-\u003eres_ops_tbl = res_ops_tbl;\n+\tdisp_mgt-\u003echan_ops_tbl = chan_ops_tbl;\n+\tdisp_mgt-\u003edisp_ops_tbl = disp_ops_tbl;\n+\tadapter-\u003ecore.disp_mgt = disp_mgt;\n+\tadapter-\u003eintf.dispatch_ops_tbl = disp_ops_tbl;\n+\n+\tret = nbl_disp_setup_msg(disp_mgt);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (common-\u003ehas_ctrl)\n+\t\tnbl_disp_setup_ctrl_lvl(disp_mgt, NBL_DISP_CTRL_LVL_MGT);\n+\n+\t/*\n+\t * For non-control PF with network capability, enable net control\n+\t * level.\n+\t * All dispatch ops declared with NBL_DISP_CTRL_LVL_MGT fall back\n+\t * to remote mailbox msg_req handlers when MGT bit is not set.\n+\t */\n+\tif (common-\u003ehas_net)\n+\t\tnbl_disp_setup_ctrl_lvl(disp_mgt, NBL_DISP_CTRL_LVL_NET);\n+\n+\treturn 0;\n+}\n+\n+void nbl_disp_remove(struct nbl_adapter *adapter)\n+{\n+\t/*\n+\t * All message handlers will be cleaned up inside channel layer\n+\t * nbl_chan_remove_common() at final device tear-down\n+\t */\n+}\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h\nnew file mode 100644\nindex 0000000000000..644094c9dd8eb\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h\n@@ -0,0 +1,26 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_DISPATCH_H_\n+#define _NBL_DISPATCH_H_\n+#include \"../nbl_include/nbl_include.h\"\n+#include \"../nbl_include/nbl_def_channel.h\"\n+#include \"../nbl_include/nbl_def_hw.h\"\n+#include \"../nbl_include/nbl_def_resource.h\"\n+#include \"../nbl_include/nbl_def_dispatch.h\"\n+#include \"../nbl_include/nbl_def_common.h\"\n+#include \"../nbl_core.h\"\n+\n+struct nbl_dispatch_mgt {\n+\tstruct nbl_common_info *common;\n+\tstruct nbl_resource_ops_tbl *res_ops_tbl;\n+\tstruct nbl_channel_ops_tbl *chan_ops_tbl;\n+\tstruct nbl_dispatch_ops_tbl *disp_ops_tbl;\n+\tDECLARE_BITMAP(ctrl_lvl, NBL_DISP_CTRL_LVL_MAX);\n+\t/* use for the caller not in interrupt */\n+\tstruct mutex ops_mutex_lock;\n+};\n+\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.c\nnew file mode 100644\nindex 0000000000000..c1e6180a70106\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.c\n@@ -0,0 +1,23 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+#include \u003clinux/device.h\u003e\n+#include \"nbl_chip.h\"\n+\n+void nbl_res_chip_deinit_module(struct nbl_resource_mgt *res_mgt)\n+{\n+\tstruct nbl_hw_ops *hw_ops = res_mgt-\u003ehw_ops_tbl-\u003eops;\n+\n+\thw_ops-\u003edeinit_module(res_mgt-\u003ehw_ops_tbl-\u003epriv);\n+}\n+\n+int nbl_res_chip_init_module(struct nbl_resource_mgt *res_mgt)\n+{\n+\tu8 eth_speed = res_mgt-\u003eresource_info-\u003eboard_info.eth_speed;\n+\tu8 eth_num = res_mgt-\u003eresource_info-\u003eboard_info.eth_num;\n+\tstruct nbl_hw_ops *hw_ops = res_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tstruct nbl_hw_mgt *p = res_mgt-\u003ehw_ops_tbl-\u003epriv;\n+\n+\treturn hw_ops-\u003einit_module(p, eth_speed, eth_num);\n+}\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.h\nnew file mode 100644\nindex 0000000000000..d8ec8ea63d040\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.h\n@@ -0,0 +1,12 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_CHIP_H_\n+#define _NBL_CHIP_H_\n+\n+#include \"nbl_resource.h\"\n+int nbl_res_chip_init_module(struct nbl_resource_mgt *res_mgt);\n+void nbl_res_chip_deinit_module(struct nbl_resource_mgt *res_mgt);\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.c\nnew file mode 100644\nindex 0000000000000..f36d400e6db01\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.c\n@@ -0,0 +1,1086 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/pci.h\u003e\n+#include \u003clinux/bits.h\u003e\n+#include \u003clinux/io.h\u003e\n+#include \u003clinux/spinlock.h\u003e\n+#include \u003clinux/bitfield.h\u003e\n+#include \"nbl_hw_leonis.h\"\n+\n+static void nbl_hw_read_mbx_regs(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 *data,\n+\t\t\t\t u32 len)\n+{\n+\tu32 i;\n+\n+\tif (len % 4)\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c len / 4; i++)\n+\t\tdata[i] = nbl_mbx_rd32(hw_mgt, reg + i * sizeof(u32));\n+}\n+\n+static void nbl_hw_write_mbx_regs(struct nbl_hw_mgt *hw_mgt, u64 reg,\n+\t\t\t\t  const u32 *data, u32 len)\n+{\n+\tu32 i;\n+\n+\tif (len % 4)\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c len / 4; i++)\n+\t\tnbl_mbx_wr32(hw_mgt, reg + i * sizeof(u32), data[i]);\n+}\n+\n+static void nbl_hw_rd_regs(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 *data,\n+\t\t\t   u32 len)\n+{\n+\tu32 size = len / 4;\n+\tu32 i;\n+\n+\tif (len % 4)\n+\t\treturn;\n+\tfor (i = 0; i \u003c size; i++)\n+\t\tdata[i] = rd32(hw_mgt-\u003ehw_addr, reg + i * sizeof(u32));\n+}\n+\n+static void nbl_hw_wr_regs(struct nbl_hw_mgt *hw_mgt, u64 reg, const u32 *data,\n+\t\t\t   u32 len)\n+{\n+\tu32 size = len / 4;\n+\tu32 i;\n+\n+\tif (len % 4)\n+\t\treturn;\n+\tfor (i = 0; i \u003c size; i++)\n+\t\twr32(hw_mgt-\u003ehw_addr, reg + i * sizeof(u32), data[i]);\n+}\n+\n+static void nbl_hw_rd_regs_lock(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 *data,\n+\t\t\t\tu32 len)\n+{\n+\tu32 size = len / 4;\n+\tu32 i;\n+\n+\tif (len % 4)\n+\t\treturn;\n+\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\n+\tfor (i = 0; i \u003c size; i++)\n+\t\tdata[i] = rd32(hw_mgt-\u003ehw_addr, reg + i * sizeof(u32));\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+}\n+\n+static void nbl_hw_wr_regs_lock(struct nbl_hw_mgt *hw_mgt, u64 reg,\n+\t\t\t\tconst u32 *data, u32 len)\n+{\n+\tu32 size = len / 4;\n+\tu32 i;\n+\n+\tif (len % 4)\n+\t\treturn;\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tfor (i = 0; i \u003c size; i++)\n+\t\twr32(hw_mgt-\u003ehw_addr, reg + i * sizeof(u32), data[i]);\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+}\n+\n+/*\n+ * Per hardware spec, registers reset to zero after cold boot, FLR and bus\n+ * reset.Zero value indicates firmware not initialized; upper layer retries\n+ * setup. No stale valid configuration can persist.\n+ */\n+static void nbl_hw_get_fw_eth_map(struct nbl_hw_mgt *hw_mgt, u32 *eth_map)\n+{\n+\tu32 data;\n+\n+\tnbl_hw_read_mbx_regs(hw_mgt, NBL_FW_BOARD_DW6_OFFSET, \u0026data,\n+\t\t\t     sizeof(data));\n+\t*eth_map = FIELD_GET(NBL_FW_BOARD_DW6_ETH_BITMAP_MASK, data);\n+}\n+\n+static u32 nbl_hw_get_quirks(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tu32 quirks;\n+\n+\tnbl_hw_read_mbx_regs(hw_mgt, NBL_LEONIS_QUIRKS_OFFSET, \u0026quirks,\n+\t\t\t     sizeof(u32));\n+\n+\tif (quirks == ~0u)\n+\t\treturn 0;\n+\n+\treturn quirks;\n+}\n+\n+static void nbl_configure_dped_checksum(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tu32 data;\n+\n+\t/* DPED dped_l4_ck_cmd_40 for sctp */\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_rd_regs(hw_mgt, NBL_DPED_L4_CK_CMD_40_ADDR, \u0026data, sizeof(data));\n+\tdata |= FIELD_PREP(NBL_DPED_L4_CK_CMD_40_EN_MASK, 1);\n+\tnbl_hw_wr_regs(hw_mgt, NBL_DPED_L4_CK_CMD_40_ADDR, \u0026data, sizeof(data));\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+}\n+\n+static void nbl_dped_init(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_wr32(hw_mgt, NBL_DPED_VLAN_OFFSET, 0xC);\n+\tnbl_hw_wr32(hw_mgt, NBL_DPED_DSCP_OFFSET_0, 0x8);\n+\tnbl_hw_wr32(hw_mgt, NBL_DPED_DSCP_OFFSET_1, 0x4);\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+\t/* dped checksum offload */\n+\tnbl_configure_dped_checksum(hw_mgt);\n+}\n+\n+static void nbl_uped_init(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tu32 hw_edit;\n+\n+\t/* V4 TCP: l3_len = 0 */\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_rd_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V4_TCP_IDX),\n+\t\t       \u0026hw_edit, sizeof(hw_edit));\n+\thw_edit \u0026= ~NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK;\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V4_TCP_IDX),\n+\t\t       \u0026hw_edit, sizeof(hw_edit));\n+\n+\t/* V6 TCP: l3_len = 1 */\n+\tnbl_hw_rd_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V6_TCP_IDX),\n+\t\t       \u0026hw_edit, sizeof(hw_edit));\n+\thw_edit = (hw_edit \u0026 ~NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK) |\n+\t\t  FIELD_PREP(NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK, 1);\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V6_TCP_IDX),\n+\t\t       \u0026hw_edit, sizeof(hw_edit));\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+}\n+\n+static int nbl_shaping_eth_init(struct nbl_hw_mgt *hw_mgt, u8 eth_id, u8 speed)\n+{\n+\tstruct nbl_shaping_dvn_dport_u dvn_dport = { 0 };\n+\tstruct nbl_shaping_dport_u dport = { 0 };\n+\tu32 rate, half_rate;\n+\tu32 depth;\n+\tu64 low_val, high_val;\n+\n+\tswitch (speed) {\n+\tcase NBL_FW_PORT_SPEED_100G:\n+\t\trate = 100000;\n+\t\tbreak;\n+\tcase NBL_FW_PORT_SPEED_50G:\n+\t\trate = 50000;\n+\t\tbreak;\n+\tcase NBL_FW_PORT_SPEED_25G:\n+\t\trate = 25000;\n+\t\tbreak;\n+\tcase NBL_FW_PORT_SPEED_10G:\n+\t\trate = 10000;\n+\t\tbreak;\n+\tdefault:\n+\t\tdev_err(hw_mgt-\u003ecommon-\u003edev,\n+\t\t\t\"Unsupported port speed %u for eth%u\\n\", speed, eth_id);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\thalf_rate = rate / 2;\n+\tdepth = max_t(u32, rate * 2, NBL_LR_LEONIS_NET_BUCKET_DEPTH);\n+\n+\t/* 1. clear valid first\n+\t * dport and dvn_dport are zero-initialised above, so VALID=0 already\n+\t */\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DPORT_REG(eth_id), dport.data,\n+\t\t       sizeof(dport));\n+\tnbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DVN_DPORT_REG(eth_id),\n+\t\t       dvn_dport.data, sizeof(dvn_dport));\n+\n+\t/* 2. write config words (valid=0, safe) */\n+\tlow_val = FIELD_PREP(DPORT_CIR_MASK, rate) |\n+\t\t  FIELD_PREP(DPORT_PIR_MASK, rate) |\n+\t\t  FIELD_PREP(DPORT_DEPTH_MASK, depth) |\n+\t\t  FIELD_PREP(DPORT_CBS_MASK_LOW, depth \u0026 0x3F);\n+\thigh_val = FIELD_PREP(DPORT_CBS_MASK_HIGH, depth \u003e\u003e 6) |\n+\t\t   FIELD_PREP(DPORT_PBS_MASK, depth);\n+\t/* Fixed split, independent of host endian */\n+\tdport.data[0] = lower_32_bits(low_val);\n+\tdport.data[1] = upper_32_bits(low_val);\n+\tdport.data[2] = lower_32_bits(high_val);\n+\tdport.data[3] = upper_32_bits(high_val);\n+\n+\tlow_val = FIELD_PREP(DPORT_CIR_MASK, half_rate) |\n+\t\t  FIELD_PREP(DPORT_PIR_MASK, rate) |\n+\t\t  FIELD_PREP(DPORT_DEPTH_MASK, depth) |\n+\t\t  FIELD_PREP(DPORT_CBS_MASK_LOW, depth \u0026 0x3F);\n+\thigh_val = FIELD_PREP(DPORT_CBS_MASK_HIGH, depth \u003e\u003e 6) |\n+\t\t   FIELD_PREP(DPORT_PBS_MASK, depth);\n+\tdvn_dport.data[0] = lower_32_bits(low_val);\n+\tdvn_dport.data[1] = upper_32_bits(low_val);\n+\tdvn_dport.data[2] = lower_32_bits(high_val);\n+\tdvn_dport.data[3] = upper_32_bits(high_val);\n+\n+\tnbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DPORT_REG(eth_id), dport.data,\n+\t\t       sizeof(dport));\n+\tnbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DVN_DPORT_REG(eth_id),\n+\t\t       dvn_dport.data, sizeof(dvn_dport));\n+\n+\t/* 3. commit: set valid last */\n+\tlow_val = FIELD_PREP(DPORT_VALID_MASK, 1);\n+\tdport.data[0] |= lower_32_bits(low_val);\n+\n+\tlow_val = FIELD_PREP(DPORT_VALID_MASK, 1);\n+\tdvn_dport.data[0] |= lower_32_bits(low_val);\n+\n+\tnbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DPORT_REG(eth_id), dport.data,\n+\t\t       sizeof(dport));\n+\tnbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DVN_DPORT_REG(eth_id),\n+\t\t       dvn_dport.data, sizeof(dvn_dport));\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+\treturn 0;\n+}\n+\n+static int nbl_shaping_init(struct nbl_hw_mgt *hw_mgt, u8 speed)\n+{\n+#define NBL_SHAPING_FLUSH_INTERVAL 128\n+\tstruct nbl_shaping_net_u net_shaping = { 0 };\n+\tu32 eth_bitmap = 0;\n+\tu32 psha_en = 0;\n+\tint ret;\n+\tint i;\n+\n+\tnbl_hw_get_fw_eth_map(hw_mgt, \u0026eth_bitmap);\n+\tfor (i = 0; i \u003c NBL_MAX_ETHERNET; i++) {\n+\t\tif (!(eth_bitmap \u0026 BIT(i)))\n+\t\t\tcontinue;\n+\t\tret = nbl_shaping_eth_init(hw_mgt, i, speed);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\tpsha_en = FIELD_PREP(NBL_DSCH_PSHA_EN_MASK, 0xF);\n+\tnbl_hw_wr_regs_lock(hw_mgt, NBL_DSCH_PSHA_EN_ADDR, \u0026psha_en,\n+\t\t\t    sizeof(psha_en));\n+\n+\tfor (i = 0; i \u003c NBL_MAX_FUNC; i++) {\n+\t\tnbl_hw_wr_regs_lock(hw_mgt, NBL_SHAPING_NET_REG(i),\n+\t\t\t\t    net_shaping.data,\n+\t\t\t\t    sizeof(net_shaping));\n+\t\tif ((i + 1) % NBL_SHAPING_FLUSH_INTERVAL == 0)\n+\t\t\tnbl_flush_writes(hw_mgt);\n+\t}\n+\tnbl_flush_writes(hw_mgt);\n+\treturn 0;\n+}\n+\n+static void nbl_dsch_qid_max_init(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tu32 quanta = 0;\n+\n+\tquanta = FIELD_PREP(NBL_DSCH_VN_QUANTA_H_QUA_MASK, NBL_HOST_QUANTA) |\n+\t\t FIELD_PREP(NBL_DSCH_VN_QUANTA_E_QUA_MASK, NBL_ECPU_QUANTA);\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_wr_regs(hw_mgt, NBL_DSCH_VN_QUANTA_ADDR, \u0026quanta,\n+\t\t       sizeof(quanta));\n+\tnbl_hw_wr32(hw_mgt, NBL_DSCH_HOST_QID_MAX, NBL_MAX_QUEUE_ID);\n+\n+\tnbl_hw_wr32(hw_mgt, NBL_DVN_ECPU_QUEUE_NUM, 0);\n+\tnbl_hw_wr32(hw_mgt, NBL_UVN_ECPU_QUEUE_NUM, 0);\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+}\n+\n+static int nbl_ustore_init(struct nbl_hw_mgt *hw_mgt, u8 eth_num)\n+{\n+\tu32 eth_bitmap = 0;\n+\tu32 drop_th = 0;\n+\tu32 pkt_len;\n+\tint i;\n+\n+\tif (eth_num != 1 \u0026\u0026 eth_num != 2 \u0026\u0026 eth_num != 4)\n+\t\treturn -EINVAL;\n+\t/* Read current packet length config\n+\t *(to preserve other fields while updating 'min')\n+\t */\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_rd_regs(hw_mgt, NBL_USTORE_PKT_LEN_ADDR, \u0026pkt_len,\n+\t\t       sizeof(pkt_len));\n+\t/* min arp packet length 42 (14 + 28) */\n+\tpkt_len \u0026= ~NBL_USTORE_PKT_LEN_MIN_MASK;\n+\tpkt_len |= FIELD_PREP(NBL_USTORE_PKT_LEN_MIN_MASK, 42);\n+\tnbl_hw_wr_regs(hw_mgt, NBL_USTORE_PKT_LEN_ADDR, \u0026pkt_len,\n+\t\t       sizeof(pkt_len));\n+\n+\tdrop_th |= FIELD_PREP(NBL_USTORE_PORT_DROP_TH_EN_MASK, 1);\n+\tif (eth_num == 1)\n+\t\tdrop_th |= FIELD_PREP(NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK,\n+\t\t\t\t      NBL_USTORE_SINGLE_ETH_DROP_TH);\n+\telse if (eth_num == 2)\n+\t\tdrop_th |= FIELD_PREP(NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK,\n+\t\t\t\t      NBL_USTORE_DUAL_ETH_DROP_TH);\n+\telse\n+\t\tdrop_th |= FIELD_PREP(NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK,\n+\t\t\t\t      NBL_USTORE_QUAD_ETH_DROP_TH);\n+\tnbl_hw_get_fw_eth_map(hw_mgt, \u0026eth_bitmap);\n+\tfor (i = 0; i \u003c NBL_MAX_ETHERNET; i++) {\n+\t\tif (!(eth_bitmap \u0026 BIT(i)))\n+\t\t\tcontinue;\n+\t\tnbl_hw_wr_regs(hw_mgt, NBL_USTORE_PORT_DROP_TH_REG_ARR(i),\n+\t\t\t       \u0026drop_th, sizeof(drop_th));\n+\t}\n+\n+\t/* Clear port drop/truncate counters by reading them\n+\t * (hardware has read-to-clear behavior for these registers)\n+\t */\n+\tfor (i = 0; i \u003c NBL_MAX_ETHERNET; i++) {\n+\t\tif (!(eth_bitmap \u0026 BIT(i)))\n+\t\t\tcontinue;\n+\t\tnbl_hw_rd32(hw_mgt, NBL_USTORE_BUF_PORT_DROP_PKT(i));\n+\t\tnbl_hw_rd32(hw_mgt, NBL_USTORE_BUF_PORT_TRUN_PKT(i));\n+\t}\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+\treturn 0;\n+}\n+\n+static void nbl_dstore_init(struct nbl_hw_mgt *hw_mgt, u8 speed)\n+{\n+\tu32 eth_bitmap = 0;\n+\tu32 drop_th;\n+\tu32 fc_th;\n+\tu32 bp_th;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c NBL_DSTORE_PORT_DROP_TH_DEPTH; i++) {\n+\t\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\t\tnbl_hw_rd_regs(hw_mgt, NBL_DSTORE_PORT_DROP_TH_REG(i), \u0026drop_th,\n+\t\t\t       sizeof(drop_th));\n+\t\tdrop_th \u0026= ~NBL_DSTORE_PORT_DROP_EN_MASK;\n+\t\tnbl_hw_wr_regs(hw_mgt, NBL_DSTORE_PORT_DROP_TH_REG(i), \u0026drop_th,\n+\t\t\t       sizeof(drop_th));\n+\t\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+\t}\n+\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_rd_regs(hw_mgt, NBL_DSTORE_DISC_BP_TH, \u0026bp_th, sizeof(bp_th));\n+\tbp_th |= FIELD_PREP(NBL_DSTORE_DISC_BP_TH_EN_MASK, 1);\n+\tnbl_hw_wr_regs(hw_mgt, NBL_DSTORE_DISC_BP_TH, \u0026bp_th, sizeof(bp_th));\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+\n+\tnbl_hw_get_fw_eth_map(hw_mgt, \u0026eth_bitmap);\n+\tfor (i = 0; i \u003c NBL_MAX_ETHERNET; i++) {\n+\t\tif (!(eth_bitmap \u0026 BIT(i)))\n+\t\t\tcontinue;\n+\t\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\t\tnbl_hw_rd_regs(hw_mgt, NBL_DSTORE_D_DPORT_FC_TH_REG(i), \u0026fc_th,\n+\t\t\t       sizeof(fc_th));\n+\t\tfc_th \u0026= ~(NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK |\n+\t\t\t   NBL_DSTORE_D_DPORT_FC_XON_TH_MASK);\n+\t\tif (speed == NBL_FW_PORT_SPEED_100G) {\n+\t\t\tfc_th |=\n+\t\t\t\tFIELD_PREP(NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK,\n+\t\t\t\t\t   NBL_DSTORE_DROP_XOFF_TH_100G) |\n+\t\t\t\tFIELD_PREP(NBL_DSTORE_D_DPORT_FC_XON_TH_MASK,\n+\t\t\t\t\t   NBL_DSTORE_DROP_XON_TH_100G);\n+\t\t} else {\n+\t\t\tfc_th |=\n+\t\t\t\tFIELD_PREP(NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK,\n+\t\t\t\t\t   NBL_DSTORE_DROP_XOFF_TH) |\n+\t\t\t\tFIELD_PREP(NBL_DSTORE_D_DPORT_FC_XON_TH_MASK,\n+\t\t\t\t\t   NBL_DSTORE_DROP_XON_TH);\n+\t\t}\n+\n+\t\tfc_th |= FIELD_PREP(NBL_DSTORE_D_DPORT_FC_FC_EN_MASK, 1);\n+\t\tnbl_hw_wr_regs(hw_mgt, NBL_DSTORE_D_DPORT_FC_TH_REG(i), \u0026fc_th,\n+\t\t\t       sizeof(fc_th));\n+\t\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+\t}\n+}\n+\n+static void nbl_dvn_descreq_num_cfg(struct nbl_hw_mgt *hw_mgt, u8 descreq_num)\n+{\n+\tu8 split_ring_num = (descreq_num \u003e\u003e 3) \u0026 0x1;\n+\tu8 ring_num = descreq_num \u0026 0x7;\n+\tu32 num_cfg = 0;\n+\n+\tnum_cfg = FIELD_PREP(NBL_DVN_DESCREQ_NUM_CFG_AVRING_DESREQ_NUM_CFG_MASK,\n+\t\t\t     split_ring_num) |\n+\t\t  FIELD_PREP(NBL_DVN_DESCREQ_NUM_CFG_PACKED_L1_NUM_MASK,\n+\t\t\t     ring_num);\n+\n+\tnbl_hw_wr_regs_lock(hw_mgt, NBL_DVN_DESCREQ_NUM_CFG, \u0026num_cfg,\n+\t\t\t    sizeof(num_cfg));\n+}\n+\n+static void nbl_dvn_init(struct nbl_hw_mgt *hw_mgt, u8 speed)\n+{\n+\tu32 timeout = 0;\n+\tu32 ro_flag = 0;\n+\n+\ttimeout = FIELD_PREP(NBL_DVN_DESC_WR_MERGE_TIMEOUT_CFG_CYCLE_MASK,\n+\t\t\t     DEFAULT_DVN_DESC_WR_MERGE_TIMEOUT_MAX);\n+\tnbl_hw_wr_regs_lock(hw_mgt, NBL_DVN_DESC_WR_MERGE_TIMEOUT, \u0026timeout,\n+\t\t\t    sizeof(timeout));\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_rd_regs(hw_mgt, NBL_DVN_DIF_REQ_RD_RO_FLAG, \u0026ro_flag,\n+\t\t       sizeof(ro_flag));\n+\tif (pcie_relaxed_ordering_enabled(hw_mgt-\u003ecommon-\u003epdev)) {\n+\t\tro_flag |=\n+\t\t\tFIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DESC_RO_EN_MASK,\n+\t\t\t\t   1) |\n+\t\t\tFIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DATA_RO_EN_MASK,\n+\t\t\t\t   1) |\n+\t\t\tFIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_AVRING_RO_EN_MASK,\n+\t\t\t\t   1);\n+\t} else {\n+\t\tro_flag \u0026=\n+\t\t\t~(FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DESC_RO_EN_MASK,\n+\t\t\t\t     1) |\n+\t\t\tFIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DATA_RO_EN_MASK,\n+\t\t\t\t   1) |\n+\t\t\tFIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_AVRING_RO_EN_MASK,\n+\t\t\t\t   1));\n+\t}\n+\tnbl_hw_wr_regs(hw_mgt, NBL_DVN_DIF_REQ_RD_RO_FLAG, \u0026ro_flag,\n+\t\t       sizeof(ro_flag));\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+\tif (speed == NBL_FW_PORT_SPEED_100G)\n+\t\tnbl_dvn_descreq_num_cfg(hw_mgt,\n+\t\t\t\t\tDEFAULT_DVN_100G_DESCREQ_NUMCFG);\n+\telse\n+\t\tnbl_dvn_descreq_num_cfg(hw_mgt, DEFAULT_DVN_DESCREQ_NUMCFG);\n+}\n+\n+static void nbl_uvn_init(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tu16 wr_timeout = NBL_UVN_DESC_WR_TIMEOUT_VAL;\n+\tu32 timeout = NBL_UVN_DESC_RD_WAIT_TICKS;\n+\tu32 desc_wr_timeout = 0;\n+\tu32 prefetch_init = 0;\n+\tbool ro_enabled;\n+\tu32 flag = 0;\n+\tu32 mask = 0;\n+\tu32 quirks;\n+\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_wr32(hw_mgt, NBL_UVN_DESC_RD_WAIT, timeout);\n+\tdesc_wr_timeout =\n+\t\tFIELD_PREP(NBL_UVN_DESC_WR_TIMEOUT_NUM_MASK, wr_timeout);\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UVN_DESC_WR_TIMEOUT, \u0026desc_wr_timeout,\n+\t\t       sizeof(desc_wr_timeout));\n+\tro_enabled = pcie_relaxed_ordering_enabled(hw_mgt-\u003ecommon-\u003epdev);\n+\n+\tnbl_hw_rd_regs(hw_mgt, NBL_UVN_DIF_REQ_RO_FLAG, \u0026flag, sizeof(flag));\n+\tif (ro_enabled) {\n+\t\tflag |= FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_AVAIL_RD_MASK, 1) |\n+\t\tFIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_RD_MASK, 1) |\n+\t\tFIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_PKT_WR_MASK, 1);\n+\t\tflag \u0026= ~FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_WR_MASK, 1);\n+\t} else {\n+\t\tflag \u0026= ~(FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_AVAIL_RD_MASK, 1) |\n+\t\t\t  FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_RD_MASK, 1) |\n+\t\t\t  FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_PKT_WR_MASK, 1) |\n+\t\t\t  FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_WR_MASK, 1));\n+\t}\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UVN_DIF_REQ_RO_FLAG, \u0026flag, sizeof(flag));\n+\n+\tnbl_hw_rd_regs(hw_mgt, NBL_UVN_QUEUE_ERR_MASK, \u0026mask, sizeof(mask));\n+\tmask |= FIELD_PREP(NBL_UVN_QUEUE_ERR_MASK_DIF_ERR_MASK, 1);\n+\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UVN_QUEUE_ERR_MASK, \u0026mask, sizeof(mask));\n+\n+\tquirks = nbl_hw_get_quirks(hw_mgt);\n+\t/*\n+\t * sel=0: use configured num; sel=1: use internal calc (max 32)\n+\t * Default is sel=1, unless NBL_QUIRKS_UVN_PREFETCH_ALIGN is set,\n+\t * in which case override to sel=0.\n+\t */\n+\tprefetch_init =\n+\t\tFIELD_PREP(NBL_UVN_DESC_PREFETCH_INIT_NUM_MASK,\n+\t\t\t   NBL_UVN_DESC_PREFETCH_NUM) |\n+\t\tFIELD_PREP(NBL_UVN_DESC_PREFETCH_INIT_SEL_MASK,\n+\t\t\t   (quirks \u0026 NBL_QUIRK_UVN_PREFETCH_ALIGN) ? 0 :\n+\t\t\t\t\t\t\t\t\t   1);\n+\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UVN_DESC_PREFETCH_INIT, \u0026prefetch_init,\n+\t\t       sizeof(prefetch_init));\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+}\n+\n+static void nbl_uqm_init(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tu32 que_type = 0;\n+\tu32 cnt = 0;\n+\tint i;\n+\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UQM_FWD_DROP_CNT, \u0026cnt, sizeof(cnt));\n+\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_PKT_CNT, \u0026cnt, sizeof(cnt));\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_PKT_SLICE_CNT, \u0026cnt, sizeof(cnt));\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_PKT_LEN_ADD_CNT, \u0026cnt, sizeof(cnt));\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_HEAD_PNTR_ADD_CNT, \u0026cnt,\n+\t\t       sizeof(cnt));\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_WEIGHT_ADD_CNT, \u0026cnt, sizeof(cnt));\n+\n+\tfor (i = 0; i \u003c NBL_UQM_PORT_DROP_DEPTH; i++) {\n+\t\tnbl_hw_wr_regs(hw_mgt,\n+\t\t\t       NBL_UQM_PORT_DROP_PKT_CNT + (sizeof(cnt) * i),\n+\t\t\t       \u0026cnt, sizeof(cnt));\n+\t\tnbl_hw_wr_regs(hw_mgt,\n+\t\t\t       NBL_UQM_PORT_DROP_PKT_SLICE_CNT +\n+\t\t\t\t       (sizeof(cnt) * i),\n+\t\t\t       \u0026cnt, sizeof(cnt));\n+\t\tnbl_hw_wr_regs(hw_mgt,\n+\t\t\t       NBL_UQM_PORT_DROP_PKT_LEN_ADD_CNT +\n+\t\t\t\t       (sizeof(cnt) * i),\n+\t\t\t       \u0026cnt, sizeof(cnt));\n+\t\tnbl_hw_wr_regs(hw_mgt,\n+\t\t\t       NBL_UQM_PORT_DROP_HEAD_PNTR_ADD_CNT +\n+\t\t\t\t       (sizeof(cnt) * i),\n+\t\t\t       \u0026cnt, sizeof(cnt));\n+\t\tnbl_hw_wr_regs(hw_mgt,\n+\t\t\t       NBL_UQM_PORT_DROP_WEIGHT_ADD_CNT +\n+\t\t\t\t       (sizeof(cnt) * i),\n+\t\t\t       \u0026cnt, sizeof(cnt));\n+\t}\n+\n+\tfor (i = 0; i \u003c NBL_UQM_DPORT_DROP_DEPTH; i++)\n+\t\tnbl_hw_wr_regs(hw_mgt,\n+\t\t\t       NBL_UQM_DPORT_DROP_CNT + (sizeof(cnt) * i), \u0026cnt,\n+\t\t\t       sizeof(cnt));\n+\t/* bit 0: bp mode , bit1: drop mode, resv bit1-31 */\n+\tnbl_hw_wr_regs(hw_mgt, NBL_UQM_QUE_TYPE, \u0026que_type, sizeof(que_type));\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+}\n+\n+static int nbl_dp_init(struct nbl_hw_mgt *hw_mgt, u8 speed, u8 eth_num)\n+{\n+\tint ret;\n+\n+\tnbl_dped_init(hw_mgt);\n+\tnbl_uped_init(hw_mgt);\n+\tret = nbl_shaping_init(hw_mgt, speed);\n+\tif (ret)\n+\t\treturn ret;\n+\tnbl_dsch_qid_max_init(hw_mgt);\n+\tret = nbl_ustore_init(hw_mgt, eth_num);\n+\tif (ret)\n+\t\treturn ret;\n+\tnbl_dstore_init(hw_mgt, speed);\n+\tnbl_dvn_init(hw_mgt, speed);\n+\tnbl_uvn_init(hw_mgt);\n+\tnbl_uqm_init(hw_mgt);\n+\treturn 0;\n+}\n+\n+static void nbl_host_padpt_init(struct nbl_hw_mgt *hw_mgt)\n+{\n+\t/* padpt flow  control register */\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_CPLH_UP,\n+\t\t    NBL_HOST_PADPT_CFG_FC_CPLH_UP_VAL);\n+\tnbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_PD_DN,\n+\t\t    NBL_HOST_PADPT_CFG_FC_PD_DN_VAL);\n+\tnbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_PH_DN,\n+\t\t    NBL_HOST_PADPT_CFG_FC_PH_DN_VAL);\n+\tnbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_NPH_DN,\n+\t\t    NBL_HOST_PADPT_CFG_FC_NPH_DN_VAL);\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+}\n+\n+static void nbl_intf_init(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tnbl_host_padpt_init(hw_mgt);\n+}\n+\n+static void nbl_hw_set_driver_status(struct nbl_hw_mgt *hw_mgt, bool active)\n+{\n+\tu32 status;\n+\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tstatus = nbl_hw_rd32(hw_mgt, NBL_DRIVER_STATUS_REG);\n+\n+\tstatus \u0026= ~BIT(NBL_DRIVER_STATUS_BIT);\n+\tstatus |= FIELD_PREP(BIT(NBL_DRIVER_STATUS_BIT), active);\n+\n+\tnbl_hw_wr32(hw_mgt, NBL_DRIVER_STATUS_REG, status);\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+}\n+\n+/*\n+ * This design is intentional. Setting driver status to false is the\n+ * official teardown mechanism: it notifies firmware to perform full\n+ * cleanup of all per-PF hardware state, including qinfo registers.\n+ * An inverse helper would duplicate work that the firmware already\n+ * does, and would add error-path complexity for no benefit. We keep\n+ * the deinit path minimal and rely on firmware cleanup for correctness,\n+ * including abnormal hardware reset scenarios.\n+ */\n+static void nbl_hw_deinit_module(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tnbl_hw_set_driver_status(hw_mgt, false);\n+\t/* ensure registers written */\n+\tnbl_flush_writes(hw_mgt);\n+}\n+\n+/*\n+ * Full chip hardware initialization is handled by firmware.\n+ * This function only configures driver-level table entries and registers.\n+ */\n+static int nbl_hw_init_module(struct nbl_hw_mgt *hw_mgt, u8 eth_speed,\n+\t\t\t      u8 eth_num)\n+{\n+\tint ret;\n+\n+\tret = nbl_dp_init(hw_mgt, eth_speed, eth_num);\n+\tif (ret)\n+\t\tgoto notify_fw;\n+\tnbl_intf_init(hw_mgt);\n+\tnbl_hw_set_driver_status(hw_mgt, true);\n+\t/* ensure registers written */\n+\tnbl_flush_writes(hw_mgt);\n+\n+\treturn 0;\n+notify_fw:\n+\t/*\n+\t * On failure path we set driver_status=false directly.\n+\t * The FW checks this flag and cleans up partial state,\n+\t * so no explicit rollback is needed.\n+\t */\n+\tnbl_hw_set_driver_status(hw_mgt, false);\n+\tnbl_flush_writes(hw_mgt);\n+\treturn ret;\n+}\n+\n+/*\n+ * nbl_hw_set_mailbox_irq - read-modify-write of NBL_MAILBOX_QINFO_MAP_REG_ARR\n+ *\n+ * Note: This RMW is currently safe because the two callers are strictly\n+ * sequential: nbl_hw_cfg_mailbox_qinfo() runs at init, nbl_hw_set_mailbox_irq()\n+ * runs at start. Future reset/hot-add/VF-reinit paths must preserve this\n+ * ordering; do not introduce concurrent access to this register without\n+ * making the RMW atomic (e.g., hold reg_lock across read+write, or use\n+ * cached state instead of read).\n+ */\n+static void nbl_hw_set_mailbox_irq(struct nbl_hw_mgt *hw_mgt, u16 func_id,\n+\t\t\t\t   bool en_msix, u16 global_vec_id)\n+{\n+\tu32 data = 0;\n+\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_hw_rd_regs(hw_mgt, NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id), \u0026data,\n+\t\t       sizeof(data));\n+\tdata \u0026= ~(NBL_MAILBOX_QINFO_MAP_MSIX_IDX_MASK |\n+\t\t  NBL_MAILBOX_QINFO_MAP_MSIX_IDX_VALID_MASK);\n+\tif (en_msix)\n+\t\tdata |= FIELD_PREP(NBL_MAILBOX_QINFO_MAP_MSIX_IDX_MASK,\n+\t\t\t\t   global_vec_id) |\n+\t\t\tFIELD_PREP(NBL_MAILBOX_QINFO_MAP_MSIX_IDX_VALID_MASK,\n+\t\t\t\t   1);\n+\n+\tnbl_hw_wr_regs(hw_mgt, NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id), \u0026data,\n+\t\t       sizeof(data));\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+\tnbl_flush_writes(hw_mgt);\n+}\n+\n+static void nbl_hw_cfg_msix_map(struct nbl_hw_mgt *hw_mgt, u16 func_id,\n+\t\t\t\tbool valid, dma_addr_t dma_addr, u8 bus,\n+\t\t\t\tu8 devid, u8 function)\n+{\n+\tstruct nbl_function_msix_map function_msix_map;\n+\n+\tmemset(\u0026function_msix_map, 0, sizeof(function_msix_map));\n+\tif (valid) {\n+\t\tfunction_msix_map.data[0] = lower_32_bits(dma_addr);\n+\t\tfunction_msix_map.data[1] = upper_32_bits(dma_addr);\n+\t\t/* use ctrl dev's bdf, because the dma memory was\n+\t\t * allocated by it\n+\t\t */\n+\t\tfunction_msix_map.data[2] =\n+\t\t\tFIELD_PREP(NBL_FUNCTION_MSIX_MAP_FUNCTION_MASK,\n+\t\t\t\t   function) |\n+\t\t\tFIELD_PREP(NBL_FUNCTION_MSIX_MAP_DEVID_MASK, devid) |\n+\t\t\tFIELD_PREP(NBL_FUNCTION_MSIX_MAP_BUS_MASK, bus) |\n+\t\t\tFIELD_PREP(NBL_FUNCTION_MSIX_MAP_VALID_MASK, 1);\n+\t} else {\n+\t\t/* Write full MSIX entry atomically under reg_lock,\n+\t\t * hardware cannot read partial updated entry\n+\t\t */\n+\t\tfunction_msix_map.data[0] = lower_32_bits(dma_addr);\n+\t\tfunction_msix_map.data[1] = upper_32_bits(dma_addr);\n+\t\tfunction_msix_map.data[2] = 0;\n+\t}\n+\n+\tnbl_hw_wr_regs_lock(hw_mgt,\n+\t\t\t    NBL_PCOMPLETER_FUNCTION_MSIX_MAP_REG_ARR(func_id),\n+\t\t\t    function_msix_map.data, sizeof(function_msix_map));\n+}\n+\n+static void nbl_hw_cfg_msix_info(struct nbl_hw_mgt *hw_mgt, u16 func_id,\n+\t\t\t\t bool valid, u16 interrupt_id, u8 bus,\n+\t\t\t\t u8 devid, u8 function, bool msix_mask_en)\n+{\n+\tu32 host_msix_fid = 0;\n+\tstruct nbl_host_msix_info msix_info;\n+\n+\tmemset(\u0026msix_info, 0, sizeof(msix_info));\n+\tif (valid) {\n+\t\thost_msix_fid =\n+\t\t\tFIELD_PREP(NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_FID_MASK,\n+\t\t\t\t   func_id) |\n+\t\t\tFIELD_PREP(NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_VLD_MASK,\n+\t\t\t\t   1);\n+\n+\t\tmsix_info.data[1] =\n+\t\t\tFIELD_PREP(NBL_HOST_MSIX_INFO_FUNCTION_MASK, function) |\n+\t\t\tFIELD_PREP(NBL_HOST_MSIX_INFO_DEVID_MASK, devid) |\n+\t\t\tFIELD_PREP(NBL_HOST_MSIX_INFO_BUS_MASK, bus) |\n+\t\t\tFIELD_PREP(NBL_HOST_MSIX_INFO_VALID_MASK, 1);\n+\n+\t\tif (msix_mask_en)\n+\t\t\tmsix_info.data[1] |=\n+\t\t\tFIELD_PREP(NBL_HOST_MSIX_INFO_MSIX_MASK_EN_MASK, 1);\n+\t}\n+\tspin_lock(\u0026hw_mgt-\u003ereg_lock);\n+\t/*\n+\t * Programming order rule:\n+\t * Enable: PADPT_HOST_MSIX_INFO -\u003e PCOMPLETER_HOST_MSIX_FID_TABLE\n+\t * Teardown: reverse order, clear FID VLD first to avoid inconsistent\n+\t * state\n+\t */\n+\tif (valid) {\n+\t\tnbl_hw_wr_regs(hw_mgt,\n+\t\t\t       NBL_PADPT_HOST_MSIX_INFO_REG_ARR(interrupt_id),\n+\t\t\t       msix_info.data, sizeof(msix_info));\n+\t\tnbl_hw_wr_regs(hw_mgt,\n+\t\t\t       NBL_PCOMPLETER_HOST_MSIX_FID_TABLE(interrupt_id),\n+\t\t\t       \u0026host_msix_fid, sizeof(host_msix_fid));\n+\t} else {\n+\t\tnbl_hw_wr_regs(hw_mgt,\n+\t\t\t       NBL_PCOMPLETER_HOST_MSIX_FID_TABLE(interrupt_id),\n+\t\t\t       \u0026host_msix_fid, sizeof(host_msix_fid));\n+\t\tnbl_hw_wr_regs(hw_mgt,\n+\t\t\t       NBL_PADPT_HOST_MSIX_INFO_REG_ARR(interrupt_id),\n+\t\t\t       msix_info.data, sizeof(msix_info));\n+\t}\n+\tspin_unlock(\u0026hw_mgt-\u003ereg_lock);\n+}\n+\n+static void nbl_hw_update_mailbox_queue_tail_ptr(struct nbl_hw_mgt *hw_mgt,\n+\t\t\t\t\t\t u16 tail_ptr, u8 txrx)\n+{\n+\t/* local_qid 0 and 1 denote rx and tx queue respectively */\n+\tu32 local_qid = txrx;\n+\tu32 value = ((u32)tail_ptr \u003c\u003c 16) | local_qid;\n+\n+\t/* wmb for doorbell */\n+\twmb();\n+\tnbl_mbx_wr32(hw_mgt, NBL_MAILBOX_NOTIFY_ADDR, value);\n+}\n+\n+static void nbl_hw_config_mailbox_rxq(struct nbl_hw_mgt *hw_mgt,\n+\t\t\t\t      dma_addr_t dma_addr, int size_bwid)\n+{\n+\tstruct nbl_mailbox_qinfo_cfg_table cfg_tbl;\n+\n+\tmemset(\u0026cfg_tbl, 0, sizeof(cfg_tbl));\n+\tcfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1);\n+\tnbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR,\n+\t\t\t      cfg_tbl.data, sizeof(cfg_tbl));\n+\n+\tcfg_tbl.data[0] = lower_32_bits(dma_addr);\n+\tcfg_tbl.data[1] = upper_32_bits(dma_addr);\n+\tcfg_tbl.data[2] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_SIZE_BWID_MASK,\n+\t\t\t\t     size_bwid);\n+\tcfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 0) |\n+\t\t\t  FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_EN_MASK, 1);\n+\tnbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR,\n+\t\t\t      cfg_tbl.data, sizeof(cfg_tbl));\n+}\n+\n+static void nbl_hw_config_mailbox_txq(struct nbl_hw_mgt *hw_mgt,\n+\t\t\t\t      dma_addr_t dma_addr, int size_bwid)\n+{\n+\tstruct nbl_mailbox_qinfo_cfg_table cfg_tbl;\n+\n+\tmemset(\u0026cfg_tbl, 0, sizeof(cfg_tbl));\n+\tcfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1);\n+\tnbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR,\n+\t\t\t      cfg_tbl.data, sizeof(cfg_tbl));\n+\n+\tcfg_tbl.data[0] = lower_32_bits(dma_addr);\n+\tcfg_tbl.data[1] = upper_32_bits(dma_addr);\n+\tcfg_tbl.data[2] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_SIZE_BWID_MASK,\n+\t\t\t\t     size_bwid);\n+\tcfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 0) |\n+\t\t\t  FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_EN_MASK, 1);\n+\tnbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR,\n+\t\t\t      cfg_tbl.data, sizeof(cfg_tbl));\n+}\n+\n+static void nbl_hw_stop_mailbox_rxq(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tstruct nbl_mailbox_qinfo_cfg_table cfg_tbl;\n+\n+\tmemset(\u0026cfg_tbl, 0, sizeof(cfg_tbl));\n+\tcfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1);\n+\tnbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR,\n+\t\t\t      cfg_tbl.data, sizeof(cfg_tbl));\n+}\n+\n+static void nbl_hw_stop_mailbox_txq(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tstruct nbl_mailbox_qinfo_cfg_table cfg_tbl;\n+\n+\tmemset(\u0026cfg_tbl, 0, sizeof(cfg_tbl));\n+\tcfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1);\n+\tnbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR,\n+\t\t\t      cfg_tbl.data, sizeof(cfg_tbl));\n+}\n+\n+static void nbl_hw_get_host_pf_mask(struct nbl_hw_mgt *hw_mgt, u32 *pf_mask)\n+{\n+\tnbl_hw_rd_regs_lock(hw_mgt, NBL_PCIE_HOST_K_PF_MASK_REG, pf_mask,\n+\t\t\t    sizeof(*pf_mask));\n+}\n+\n+static void nbl_hw_get_real_bus(struct nbl_hw_mgt *hw_mgt, u8 *bus)\n+{\n+\tu32 data;\n+\n+\tnbl_hw_rd_regs_lock(hw_mgt, NBL_PCIE_HOST_TL_CFG_BUSDEV, \u0026data,\n+\t\t\t    sizeof(data));\n+\t*bus = FIELD_GET(NBL_PCIE_BUS_MASK, data);\n+}\n+\n+static void nbl_hw_cfg_mailbox_qinfo(struct nbl_hw_mgt *hw_mgt, u16 func_id,\n+\t\t\t\t     u8 bus, u8 devid, u8 function)\n+{\n+\tu32 data = 0;\n+\n+\tdata = FIELD_PREP(NBL_MAILBOX_QINFO_MAP_FUNCTION_MASK, function) |\n+\t       FIELD_PREP(NBL_MAILBOX_QINFO_MAP_DEVID_MASK, devid) |\n+\t       FIELD_PREP(NBL_MAILBOX_QINFO_MAP_BUS_MASK, bus);\n+\tnbl_hw_wr_regs_lock(hw_mgt, NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id),\n+\t\t\t    \u0026data,\n+\t\t\t    sizeof(data));\n+}\n+\n+/*\n+ * Per hardware spec, registers reset to zero after cold boot, FLR and bus\n+ * reset.Zero value indicates firmware not initialized; upper layer retries\n+ * setup. No stale valid configuration can persist.\n+ */\n+static void nbl_hw_get_board_info(struct nbl_hw_mgt *hw_mgt,\n+\t\t\t\t  struct nbl_board_port_info *board_info)\n+{\n+\tu32 data = 0;\n+\n+\tnbl_hw_read_mbx_regs(hw_mgt, NBL_FW_BOARD_DW3_OFFSET, \u0026data,\n+\t\t\t     sizeof(data));\n+\tboard_info-\u003eeth_num = FIELD_GET(NBL_FW_BOARD_DW3_PORT_NUM_MASK, data);\n+\tboard_info-\u003eeth_speed =\n+\t\tFIELD_GET(NBL_FW_BOARD_DW3_PORT_SPEED_MASK, data);\n+\tboard_info-\u003ep4_version =\n+\t\tFIELD_GET(NBL_FW_BOARD_DW3_P4_VERSION_MASK, data);\n+}\n+\n+static struct nbl_hw_ops hw_ops = {\n+\t.init_module = nbl_hw_init_module,\n+\t.deinit_module = nbl_hw_deinit_module,\n+\n+\t.cfg_msix_map = nbl_hw_cfg_msix_map,\n+\t.cfg_msix_info = nbl_hw_cfg_msix_info,\n+\t.flush_write = nbl_flush_writes,\n+\n+\t.update_mailbox_queue_tail_ptr = nbl_hw_update_mailbox_queue_tail_ptr,\n+\t.config_mailbox_rxq = nbl_hw_config_mailbox_rxq,\n+\t.config_mailbox_txq = nbl_hw_config_mailbox_txq,\n+\t.stop_mailbox_rxq = nbl_hw_stop_mailbox_rxq,\n+\t.stop_mailbox_txq = nbl_hw_stop_mailbox_txq,\n+\t.get_host_pf_mask = nbl_hw_get_host_pf_mask,\n+\t.get_real_bus = nbl_hw_get_real_bus,\n+\n+\t.cfg_mailbox_qinfo = nbl_hw_cfg_mailbox_qinfo,\n+\t.set_mailbox_irq = nbl_hw_set_mailbox_irq,\n+\n+\t.get_fw_eth_map = nbl_hw_get_fw_eth_map,\n+\t.get_board_info = nbl_hw_get_board_info,\n+};\n+\n+/* Structure starts here, adding an op should not modify anything below */\n+static struct nbl_hw_mgt *nbl_hw_setup_hw_mgt(struct nbl_common_info *common)\n+{\n+\tstruct device *dev = common-\u003edev;\n+\tstruct nbl_hw_mgt *hw_mgt;\n+\n+\thw_mgt = devm_kzalloc(dev, sizeof(*hw_mgt), GFP_KERNEL);\n+\tif (!hw_mgt)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\thw_mgt-\u003ecommon = common;\n+\n+\treturn hw_mgt;\n+}\n+\n+static struct nbl_hw_ops_tbl *nbl_hw_setup_ops(struct nbl_common_info *common,\n+\t\t\t\t\t       struct nbl_hw_mgt *hw_mgt)\n+{\n+\tstruct nbl_hw_ops_tbl *hw_ops_tbl;\n+\tstruct device *dev;\n+\n+\tdev = common-\u003edev;\n+\thw_ops_tbl = devm_kzalloc(dev, sizeof(*hw_ops_tbl), GFP_KERNEL);\n+\tif (!hw_ops_tbl)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\tif (!hw_ops.init_module || !hw_ops.deinit_module ||\n+\t    !hw_ops.cfg_msix_map || !hw_ops.cfg_msix_info ||\n+\t    !hw_ops.flush_write || !hw_ops.update_mailbox_queue_tail_ptr ||\n+\t    !hw_ops.config_mailbox_rxq || !hw_ops.config_mailbox_txq ||\n+\t    !hw_ops.stop_mailbox_rxq || !hw_ops.stop_mailbox_txq ||\n+\t    !hw_ops.get_host_pf_mask || !hw_ops.get_real_bus ||\n+\t    !hw_ops.cfg_mailbox_qinfo || !hw_ops.set_mailbox_irq ||\n+\t    !hw_ops.get_fw_eth_map || !hw_ops.get_board_info)\n+\t\treturn ERR_PTR(-EINVAL);\n+\thw_ops_tbl-\u003eops = \u0026hw_ops;\n+\thw_ops_tbl-\u003epriv = hw_mgt;\n+\n+\treturn hw_ops_tbl;\n+}\n+\n+static int nbl_pcim_request_selected_bars(struct pci_dev *pdev, u32 mask,\n+\t\t\t\t\t  const char *name)\n+{\n+\tint bar;\n+\tint ret;\n+\n+\tfor (bar = 0; bar \u003c PCI_STD_NUM_BARS; bar++) {\n+\t\tif (!(mask \u0026 BIT(bar)))\n+\t\t\tcontinue;\n+\t\tret = pcim_request_region(pdev, bar, name);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\treturn 0;\n+}\n+\n+int nbl_hw_init_leonis(struct nbl_adapter *adapter)\n+{\n+\tstruct nbl_common_info *common = \u0026adapter-\u003ecommon;\n+\tstruct pci_dev *pdev = common-\u003epdev;\n+\tstruct nbl_hw_ops_tbl *hw_ops_tbl = NULL;\n+\tstruct nbl_hw_mgt *hw_mgt = NULL;\n+\tresource_size_t bar_len;\n+\tu32 bar_mask;\n+\tint ret;\n+\n+\thw_mgt = nbl_hw_setup_hw_mgt(common);\n+\tif (IS_ERR(hw_mgt)) {\n+\t\tret = PTR_ERR(hw_mgt);\n+\t\tgoto setup_mgt_fail;\n+\t}\n+\tbar_mask = BIT(NBL_MEMORY_BAR) | BIT(NBL_MAILBOX_BAR);\n+\tret = nbl_pcim_request_selected_bars(pdev, bar_mask, NBL_DRIVER_NAME);\n+\tif (ret) {\n+\t\tdev_err(\u0026pdev-\u003edev,\n+\t\t\t\"Request memory bar failed, err = %d\\n\",\n+\t\t\tret);\n+\t\tgoto setup_mgt_fail;\n+\t}\n+\n+\tbar_len = pci_resource_len(pdev, NBL_MEMORY_BAR);\n+\tif (!(pci_resource_flags(pdev, NBL_MEMORY_BAR) \u0026 IORESOURCE_MEM)) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"MEMORY BAR is not memory resource\\n\");\n+\t\tret = -EINVAL;\n+\t\tgoto setup_mgt_fail;\n+\t}\n+\tif (common-\u003ehas_ctrl) {\n+\t\t/*\n+\t\t * Hardware layout: MEMORY BAR total size is 64M.\n+\t\t * The front NBL_HW_REG_SPACE_SIZE (32M) holds module\n+\t\t * registers and table entries. The trailing NBL_RDMA_NOTIFY_LEN\n+\t\t * bytes within this 32M region are reserved exclusively for\n+\t\t * RDMA notification hardware logic.\n+\t\t * Ethernet driver must avoid mapping this reserved range to\n+\t\t * prevent x86 PAT attribute aliasing conflict between eth UC\n+\t\t * mapping and RDMA driver WC mapping.\n+\t\t * Mapping range starts at offset 0, length equals\n+\t\t * NBL_HW_REG_SPACE_SIZE - NBL_RDMA_NOTIFY_LEN.\n+\t\t */\n+\t\tif (bar_len \u003c NBL_HW_REG_SPACE_SIZE) {\n+\t\t\tdev_err(\u0026pdev-\u003edev,\n+\t\t\t\t\"MEMORY BAR len %pa too small for ctrl reg space\\n\",\n+\t\t\t\t\u0026bar_len);\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto setup_mgt_fail;\n+\t\t}\n+\t\t/*\n+\t\t * Spec: Front 32MiB of 64M MEM BAR contains register space.\n+\t\t * Skip trailing NBL_RDMA_NOTIFY_LEN bytes inside 32M boundary\n+\t\t * never map that RDMA exclusive partition to avoid PAT conflict\n+\t\t */\n+\t\thw_mgt-\u003ehw_size = NBL_HW_REG_SPACE_SIZE  - NBL_RDMA_NOTIFY_LEN;\n+\t\thw_mgt-\u003ehw_addr =\n+\t\t\tpcim_iomap(pdev, NBL_MEMORY_BAR,\n+\t\t\t\t   hw_mgt-\u003ehw_size);\n+\t} else {\n+\t\tif (bar_len \u003c NBL_REG_NET_ONLY_LEN) {\n+\t\t\tdev_err(\u0026pdev-\u003edev,\n+\t\t\t\t\"MEMORY BAR len %pa too small for net only reg space\\n\",\n+\t\t\t\t\u0026bar_len);\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto setup_mgt_fail;\n+\t\t}\n+\t\thw_mgt-\u003ehw_size = NBL_REG_NET_ONLY_LEN;\n+\t\thw_mgt-\u003ehw_addr = pcim_iomap(pdev, NBL_MEMORY_BAR,\n+\t\t\t\t\t     hw_mgt-\u003ehw_size);\n+\t}\n+\tif (!hw_mgt-\u003ehw_addr) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"MEMORY BAR pcim_iomap failed\\n\");\n+\t\tret = -EIO;\n+\t\tgoto setup_mgt_fail;\n+\t}\n+\n+\tbar_len = pci_resource_len(pdev, NBL_MAILBOX_BAR);\n+\tif (!(pci_resource_flags(pdev, NBL_MAILBOX_BAR) \u0026 IORESOURCE_MEM)) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"MAILBOX BAR is not memory resource\\n\");\n+\t\tret = -EINVAL;\n+\t\tgoto setup_mgt_fail;\n+\t}\n+\tif (bar_len == 0) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"MAILBOX BAR length is zero\\n\");\n+\t\tret = -EINVAL;\n+\t\tgoto setup_mgt_fail;\n+\t}\n+\thw_mgt-\u003emailbox_bar_hw_addr = pcim_iomap(pdev, NBL_MAILBOX_BAR,\n+\t\t\t\t\t\t bar_len);\n+\tif (!hw_mgt-\u003emailbox_bar_hw_addr) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"MAILBOX BAR pcim_iomap failed\\n\");\n+\t\tret = -EIO;\n+\t\tgoto setup_mgt_fail;\n+\t}\n+\n+\tspin_lock_init(\u0026hw_mgt-\u003ereg_lock);\n+\n+\thw_ops_tbl = nbl_hw_setup_ops(common, hw_mgt);\n+\tif (IS_ERR(hw_ops_tbl)) {\n+\t\tret = PTR_ERR(hw_ops_tbl);\n+\t\tgoto setup_mgt_fail;\n+\t}\n+\tadapter-\u003eintf.hw_ops_tbl = hw_ops_tbl;\n+\tadapter-\u003ecore.hw_mgt = hw_mgt;\n+\n+\treturn 0;\n+\n+setup_mgt_fail:\n+\treturn ret;\n+}\n+\n+void nbl_hw_remove_leonis(struct nbl_adapter *adapter)\n+{\n+\t/* All BAR mappings \u0026 PCI regions are managed by pcim/devres,\n+\t * no manual iounmap / release required\n+\t */\n+}\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.h\nnew file mode 100644\nindex 0000000000000..e096f2169180b\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.h\n@@ -0,0 +1,355 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_HW_LEONIS_H_\n+#define _NBL_HW_LEONIS_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+#include \"../../nbl_include/nbl_include.h\"\n+#include \"../nbl_hw_reg.h\"\n+\n+#define NBL_DRIVER_STATUS_REG\t\t\t0x1300444\n+#define NBL_DRIVER_STATUS_BIT\t\t\t16\n+\n+/*  ----------  REG BASE ADDR  ----------  */\n+/* Interface modules base addr */\n+#define NBL_INTF_HOST_PCOMPLETER_BASE\t\t0x00f08000\n+#define NBL_INTF_HOST_PADPT_BASE\t\t0x00f4c000\n+#define NBL_INTF_HOST_MAILBOX_BASE\t\t0x00fb0000\n+#define NBL_INTF_HOST_PCIE_BASE\t\t\t0X01504000\n+/* DP modules base addr */\n+#define NBL_DP_USTORE_BASE\t\t\t0x00104000\n+#define NBL_DP_UQM_BASE\t\t\t\t0x00114000\n+#define NBL_DP_UPED_BASE\t\t\t0x0015c000\n+#define NBL_DP_UVN_BASE\t\t\t\t0x00244000\n+#define NBL_DP_DSCH_BASE\t\t\t0x00404000\n+#define NBL_DP_SHAPING_BASE\t\t\t0x00504000\n+#define NBL_DP_DVN_BASE\t\t\t\t0x00514000\n+#define NBL_DP_DSTORE_BASE\t\t\t0x00704000\n+#define NBL_DP_DQM_BASE\t\t\t\t0x00714000\n+#define NBL_DP_DPED_BASE\t\t\t0x0075c000\n+#define NBL_DP_DDMUX_BASE\t\t\t0x00984000\n+/*  --------  MAILBOX BAR2 -----  */\n+#define NBL_MAILBOX_NOTIFY_ADDR\t\t\t0x00000000\n+#define NBL_MAILBOX_BAR_REG\t\t\t0x00000000\n+#define NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR\t0x10\n+#define NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR\t0x20\n+#define NBL_MAILBOX_QINFO_CFG_DBG_TABLE_ADDR\t0x30\n+\n+/*  --------  MAILBOX  --------  */\n+\n+/* mailbox BAR qinfo_cfg_table */\n+#define MAILBOX_QINFO_CFG_TABLE_DWLEN\t4\n+/* data[2] */\n+#define NBL_MAILBOX_QINFO_CFG_QUEUE_SIZE_BWID_MASK\tGENMASK(3, 0)\n+/* data[3] */\n+#define NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK\t\tBIT(0)\n+#define NBL_MAILBOX_QINFO_CFG_QUEUE_EN_MASK\t\tBIT(1)\n+#define NBL_MAILBOX_QINFO_CFG_DIF_ERR_MASK\t\tBIT(2)\n+#define NBL_MAILBOX_QINFO_CFG_PTR_ERR_MASK\t\tBIT(3)\n+struct nbl_mailbox_qinfo_cfg_table {\n+\tu32 data[MAILBOX_QINFO_CFG_TABLE_DWLEN];\n+};\n+\n+/*  --------  MAILBOX BAR0 -----  */\n+/* mailbox qinfo_map_table */\n+#define NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id) \\\n+\t(NBL_INTF_HOST_MAILBOX_BASE + 0x00001000 + (func_id) * sizeof(u32))\n+\n+/* MAILBOX qinfo_map_table */\n+#define NBL_MAILBOX_QINFO_MAP_FUNCTION_MASK\t\tGENMASK(2, 0)\n+#define NBL_MAILBOX_QINFO_MAP_DEVID_MASK\t\tGENMASK(7, 3)\n+#define NBL_MAILBOX_QINFO_MAP_BUS_MASK\t\t\tGENMASK(15, 8)\n+#define NBL_MAILBOX_QINFO_MAP_MSIX_IDX_MASK\t\tGENMASK(28, 16)\n+#define NBL_MAILBOX_QINFO_MAP_MSIX_IDX_VALID_MASK\tBIT(29)\n+\n+/*  --------  HOST_PCIE  --------  */\n+#define NBL_PCIE_HOST_K_PF_MASK_REG (NBL_INTF_HOST_PCIE_BASE + 0x00001004)\n+#define NBL_PCIE_HOST_TL_CFG_BUSDEV (NBL_INTF_HOST_PCIE_BASE + 0x11040)\n+\n+#define NBL_PCIE_BUS_MASK\tGENMASK(12, 5)\n+\n+/*  --------  HOST_PADPT  --------  */\n+#define NBL_HOST_PADPT_HOST_CFG_FC_PD_DN (NBL_INTF_HOST_PADPT_BASE + 0x00000160)\n+#define NBL_HOST_PADPT_HOST_CFG_FC_PH_DN (NBL_INTF_HOST_PADPT_BASE + 0x00000164)\n+#define NBL_HOST_PADPT_HOST_CFG_FC_NPH_DN \\\n+\t(NBL_INTF_HOST_PADPT_BASE + 0x0000016C)\n+#define NBL_HOST_PADPT_HOST_CFG_FC_CPLH_UP \\\n+\t(NBL_INTF_HOST_PADPT_BASE + 0x00000170)\n+\n+#define NBL_HOST_PADPT_CFG_FC_CPLH_UP_VAL      0x10400\n+#define NBL_HOST_PADPT_CFG_FC_PD_DN_VAL        0x10080\n+#define NBL_HOST_PADPT_CFG_FC_PH_DN_VAL        0x10010\n+#define NBL_HOST_PADPT_CFG_FC_NPH_DN_VAL       0x10010\n+/* host_padpt host_msix_info */\n+#define NBL_PADPT_HOST_MSIX_INFO_REG_ARR(vector_id) \\\n+\t(NBL_INTF_HOST_PADPT_BASE + 0x00010000 +    \\\n+\t (vector_id) * sizeof(struct nbl_host_msix_info))\n+\n+#define NBL_HOST_MSIX_INFO_DWLEN\t2\n+/* data[0] */\n+#define NBL_HOST_MSIX_INFO_INTRL_PNUM_MASK GENMASK(15, 0)\n+#define NBL_HOST_MSIX_INFO_INTRL_RATE_MASK GENMASK(31, 16)\n+/* data[1] */\n+#define NBL_HOST_MSIX_INFO_FUNCTION_MASK GENMASK(2, 0)\n+#define NBL_HOST_MSIX_INFO_DEVID_MASK GENMASK(7, 3)\n+#define NBL_HOST_MSIX_INFO_BUS_MASK GENMASK(15, 8)\n+#define NBL_HOST_MSIX_INFO_VALID_MASK BIT(16)\n+#define NBL_HOST_MSIX_INFO_MSIX_MASK_EN_MASK BIT(17)\n+struct nbl_host_msix_info {\n+\tu32 data[NBL_HOST_MSIX_INFO_DWLEN];\n+};\n+\n+/*  --------  HOST_PCOMPLETER  --------  */\n+/* pcompleter_host pcompleter_host_virtio_qid_map_table */\n+#define NBL_PCOMPLETER_FUNCTION_MSIX_MAP_REG_ARR(i)   \\\n+\t(NBL_INTF_HOST_PCOMPLETER_BASE + 0x00004000 + \\\n+\t (i) * sizeof(struct nbl_function_msix_map))\n+#define NBL_PCOMPLETER_HOST_MSIX_FID_TABLE(i) \\\n+\t(NBL_INTF_HOST_PCOMPLETER_BASE + 0x0003a000 + (i) * sizeof(u32))\n+\n+#define NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_FID_MASK  GENMASK(9, 0)\n+#define NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_VLD_MASK  BIT(10)\n+\n+#define NBL_FUNC_MSIX_MAP_DWLEN\t\t4\n+/* data[2] */\n+#define NBL_FUNCTION_MSIX_MAP_FUNCTION_MASK GENMASK(2, 0)\n+#define NBL_FUNCTION_MSIX_MAP_DEVID_MASK GENMASK(7, 3)\n+#define NBL_FUNCTION_MSIX_MAP_BUS_MASK GENMASK(15, 8)\n+#define NBL_FUNCTION_MSIX_MAP_VALID_MASK BIT(16)\n+struct nbl_function_msix_map {\n+\tu32 data[NBL_FUNC_MSIX_MAP_DWLEN];\n+};\n+\n+/*  ----------  DPED  ----------  */\n+#define NBL_DPED_VLAN_OFFSET\t\t(NBL_DP_DPED_BASE + 0x000003F4)\n+#define NBL_DPED_DSCP_OFFSET_0\t\t(NBL_DP_DPED_BASE + 0x000003F8)\n+#define NBL_DPED_DSCP_OFFSET_1\t\t(NBL_DP_DPED_BASE + 0x000003FC)\n+/* DPED hw_edt_prof/ UPED hw_edt_prof */\n+\n+#define NBL_DPED_L4_CK_CMD_40_ADDR 0x75c338\n+#define NBL_DPED_L4_CK_CMD_40_DEPTH 1\n+#define NBL_DPED_L4_CK_CMD_40_WIDTH 32\n+#define NBL_DPED_L4_CK_CMD_40_DWLEN 1\n+\n+#define NBL_DPED_L4_CK_CMD_40_VALUE_MASK\tGENMASK(7, 0)\n+#define NBL_DPED_L4_CK_CMD_40_LEN_IN_OFT_MASK\tGENMASK(14, 8)\n+#define NBL_DPED_L4_CK_CMD_40_LEN_PHID_MASK\tGENMASK(16, 15)\n+#define NBL_DPED_L4_CK_CMD_40_LEN_VLD_MASK\tBIT(17)\n+#define NBL_DPED_L4_CK_CMD_40_DATA_VLD_MASK\tGENMASK(18, 18)\n+#define NBL_DPED_L4_CK_CMD_40_IN_OFT_MASK\tGENMASK(25, 19)\n+#define NBL_DPED_L4_CK_CMD_40_PHID_MASK\t\tGENMASK(27, 26)\n+#define NBL_DPED_L4_CK_CMD_40_FLAG_MASK\t\tBIT(28)\n+#define NBL_DPED_L4_CK_CMD_40_MODE_MASK\t\tBIT(29)\n+#define NBL_DPED_L4_CK_CMD_40_RSV_MASK\t\tBIT(30)\n+#define NBL_DPED_L4_CK_CMD_40_EN_MASK\t\tBIT(31)\n+\n+/*  ----------  UPED  ----------  */\n+/* UPED uped_hw_edt_prof */\n+#define NBL_UPED_HW_EDT_PROF_TABLE(i) \\\n+\t(NBL_DP_UPED_BASE + 0x00001000 + (i) * sizeof(u32))\n+#define NBL_UPED_V4_TCP_IDX\t\t5\n+#define NBL_UPED_V6_TCP_IDX\t\t6\n+#define NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK GENMASK(3, 2)\n+\n+/*  ----------  DSCH  ----------  */\n+#define NBL_DSCH_PSHA_EN_MASK  GENMASK(3, 0)\n+/* DSCH dsch maxqid */\n+#define NBL_DSCH_HOST_QID_MAX (NBL_DP_DSCH_BASE + 0x00000118)\n+#define NBL_DSCH_VN_QUANTA_ADDR (NBL_DP_DSCH_BASE + 0x00000134)\n+\n+#define NBL_MAX_QUEUE_ID\t0x7ff\n+#define NBL_HOST_QUANTA\t\t0x8000\n+#define NBL_ECPU_QUANTA\t\t0x1000\n+\n+#define NBL_DSCH_VN_QUANTA_H_QUA_MASK GENMASK(15, 0)\n+#define NBL_DSCH_VN_QUANTA_E_QUA_MASK GENMASK(31, 16)\n+\n+/*  ----------  DVN  ----------  */\n+/* DVN dvn_queue_table */\n+#define NBL_DVN_ECPU_QUEUE_NUM\t\t\t(NBL_DP_DVN_BASE + 0x0000041C)\n+#define NBL_DVN_DESCREQ_NUM_CFG\t\t\t(NBL_DP_DVN_BASE + 0x00000430)\n+#define NBL_DVN_DESC_WR_MERGE_TIMEOUT\t\t(NBL_DP_DVN_BASE + 0x00000480)\n+#define NBL_DVN_DIF_REQ_RD_RO_FLAG\t\t(NBL_DP_DVN_BASE + 0x0000045C)\n+\n+#define DEFAULT_DVN_DESCREQ_NUMCFG\t\t0x03\n+#define DEFAULT_DVN_100G_DESCREQ_NUMCFG\t\t0x07\n+\n+#define DEFAULT_DVN_DESC_WR_MERGE_TIMEOUT_MAX\t0x3FF\n+\n+/* spilit ring descreq_num 0:8,1:16 */\n+#define NBL_DVN_DESCREQ_NUM_CFG_AVRING_DESREQ_NUM_CFG_MASK BIT(0)\n+/* packet ring descreq_num\n+ * 0:8,1:12,2:16;3:20,4:24,5:26;6:32,7:32\n+ */\n+#define NBL_DVN_DESCREQ_NUM_CFG_PACKED_L1_NUM_MASK GENMASK(6, 4)\n+\n+#define NBL_DVN_DESC_WR_MERGE_TIMEOUT_CFG_CYCLE_MASK GENMASK(9, 0)\n+\n+#define NBL_DVN_DIF_REQ_RD_RO_FLAG_DESC_RO_EN_MASK BIT(0)\n+#define NBL_DVN_DIF_REQ_RD_RO_FLAG_DATA_RO_EN_MASK BIT(1)\n+#define NBL_DVN_DIF_REQ_RD_RO_FLAG_AVRING_RO_EN_MASK BIT(2)\n+\n+/*  ----------  UVN  ----------  */\n+/* UVN uvn_queue_table */\n+\n+#define NBL_UVN_DESC_RD_WAIT\t\t\t(NBL_DP_UVN_BASE + 0x0000020C)\n+#define NBL_UVN_QUEUE_ERR_MASK\t\t\t(NBL_DP_UVN_BASE + 0x00000224)\n+#define NBL_UVN_ECPU_QUEUE_NUM\t\t\t(NBL_DP_UVN_BASE + 0x0000023C)\n+#define NBL_UVN_DESC_WR_TIMEOUT\t\t\t(NBL_DP_UVN_BASE + 0x00000214)\n+#define NBL_UVN_DIF_REQ_RO_FLAG\t\t\t(NBL_DP_UVN_BASE + 0x00000250)\n+#define NBL_UVN_DESC_PREFETCH_INIT\t\t(NBL_DP_UVN_BASE + 0x00000204)\n+#define NBL_UVN_DESC_PREFETCH_NUM\t\t4\n+\n+#define NBL_UVN_DIF_REQ_RO_FLAG_AVAIL_RD_MASK BIT(0)\n+#define NBL_UVN_DIF_REQ_RO_FLAG_DESC_RD_MASK BIT(1)\n+#define NBL_UVN_DIF_REQ_RO_FLAG_PKT_WR_MASK BIT(2)\n+#define NBL_UVN_DIF_REQ_RO_FLAG_DESC_WR_MASK BIT(3)\n+\n+#define NBL_UVN_DESC_WR_TIMEOUT_NUM_MASK GENMASK(14, 0)\n+#define NBL_UVN_DESC_WR_TIMEOUT_MASK_MASK BIT(15)\n+\n+#define NBL_UVN_QUEUE_ERR_MASK_DIF_ERR_MASK BIT(5)\n+\n+#define NBL_UVN_DESC_PREFETCH_INIT_NUM_MASK GENMASK(7, 0)\n+#define NBL_UVN_DESC_PREFETCH_INIT_SEL_MASK BIT(16)\n+\n+#define NBL_UVN_DESC_WR_TIMEOUT_VAL 0x12c\n+/* 200us = 200000ns / 1.67ns per tick = 119760 ticks */\n+#define NBL_UVN_DESC_RD_WAIT_TICKS 119760\n+\n+/*  --------  USTORE  --------  */\n+#define NBL_USTORE_PKT_LEN_ADDR (NBL_DP_USTORE_BASE + 0x00000108)\n+#define NBL_USTORE_PORT_DROP_TH_REG_ARR(port_id) \\\n+\t(NBL_DP_USTORE_BASE + 0x00000150 + (port_id) * sizeof(u32))\n+#define NBL_USTORE_BUF_PORT_DROP_PKT(eth_id) \\\n+\t(NBL_DP_USTORE_BASE + 0x00002500 + (eth_id) * sizeof(u32))\n+#define NBL_USTORE_BUF_PORT_TRUN_PKT(eth_id) \\\n+\t(NBL_DP_USTORE_BASE + 0x00002540 + (eth_id) * sizeof(u32))\n+\n+#define NBL_USTORE_SINGLE_ETH_DROP_TH\t\t0xC80\n+#define NBL_USTORE_DUAL_ETH_DROP_TH\t\t0x640\n+#define NBL_USTORE_QUAD_ETH_DROP_TH\t\t0x320\n+\n+/* USTORE pkt_len */\n+#define NBL_USTORE_PKT_LEN_MIN_MASK GENMASK(6, 0)\n+\n+/* USTORE port_drop_th */\n+#define NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK GENMASK(11, 0)\n+#define NBL_USTORE_PORT_DROP_TH_EN_MASK BIT(31)\n+\n+/* UQM*/\n+#define NBL_UQM_QUE_TYPE\t\t\t(NBL_DP_UQM_BASE + 0x0000013c)\n+#define NBL_UQM_DROP_PKT_CNT\t\t\t(NBL_DP_UQM_BASE + 0x000009C0)\n+#define NBL_UQM_DROP_PKT_SLICE_CNT\t\t(NBL_DP_UQM_BASE + 0x000009C4)\n+#define NBL_UQM_DROP_PKT_LEN_ADD_CNT\t\t(NBL_DP_UQM_BASE + 0x000009C8)\n+#define NBL_UQM_DROP_HEAD_PNTR_ADD_CNT\t\t(NBL_DP_UQM_BASE + 0x000009CC)\n+#define NBL_UQM_DROP_WEIGHT_ADD_CNT\t\t(NBL_DP_UQM_BASE + 0x000009D0)\n+#define NBL_UQM_PORT_DROP_PKT_CNT\t\t(NBL_DP_UQM_BASE + 0x000009D4)\n+#define NBL_UQM_PORT_DROP_PKT_SLICE_CNT\t\t(NBL_DP_UQM_BASE + 0x000009F4)\n+#define NBL_UQM_PORT_DROP_PKT_LEN_ADD_CNT\t(NBL_DP_UQM_BASE + 0x00000A14)\n+#define NBL_UQM_PORT_DROP_HEAD_PNTR_ADD_CNT\t(NBL_DP_UQM_BASE + 0x00000A34)\n+#define NBL_UQM_PORT_DROP_WEIGHT_ADD_CNT\t(NBL_DP_UQM_BASE + 0x00000A54)\n+#define NBL_UQM_FWD_DROP_CNT\t\t\t(NBL_DP_UQM_BASE + 0x00000A80)\n+#define NBL_UQM_DPORT_DROP_CNT\t\t\t(NBL_DP_UQM_BASE + 0x00000B74)\n+\n+#define NBL_UQM_PORT_DROP_DEPTH\t\t\t6\n+#define NBL_UQM_DPORT_DROP_DEPTH\t\t16\n+\n+/*  ---------  SHAPING  ---------  */\n+#define NBL_SHAPING_NET(i)                  \\\n+\t(NBL_DP_SHAPING_BASE + 0x00001800 + \\\n+\t (i) * sizeof(struct nbl_shaping_net))\n+\n+/* cir 1, bandwidth 1kB/s in protol environment */\n+/* cir 1, bandwidth 1Mb/s */\n+#define NBL_LR_LEONIS_NET_BUCKET_DEPTH\t\t9600\n+#define NBL_SHAPING_DPORT_ADDR 0x504700\n+#define NBL_SHAPING_DPORT_DWLEN 4\n+#define NBL_SHAPING_DPORT_REG(r) \\\n+\t(NBL_SHAPING_DPORT_ADDR + (NBL_SHAPING_DPORT_DWLEN * 4) * (r))\n+#define NBL_SHAPING_DVN_DPORT_ADDR 0x504750\n+#define NBL_SHAPING_DVN_DPORT_DWLEN 4\n+#define NBL_SHAPING_DVN_DPORT_REG(r) \\\n+\t(NBL_SHAPING_DVN_DPORT_ADDR + (NBL_SHAPING_DVN_DPORT_DWLEN * 4) * (r))\n+#define NBL_DSCH_PSHA_EN_ADDR 0x404314\n+#define NBL_SHAPING_NET_ADDR 0x505800\n+#define NBL_SHAPING_NET_DWLEN 4\n+#define NBL_SHAPING_NET_REG(r) \\\n+\t(NBL_SHAPING_NET_ADDR + (NBL_SHAPING_NET_DWLEN * 4) * (r))\n+\n+#define DPORT_VALID_MASK (0x1ULL \u003c\u003c 0)\n+#define DPORT_DEPTH_MASK (0x7FFFFULL \u003c\u003c 1)\t// [19:1]\n+#define DPORT_CIR_MASK (0x7FFFFULL \u003c\u003c 20)\t// [38:20]\n+#define DPORT_PIR_MASK (0x7FFFFULL \u003c\u003c 39)\t// [57:39]\n+#define DPORT_CBS_MASK_LOW (0x3FULL \u003c\u003c 58)\t\t// [63:58]\n+#define DPORT_CBS_MASK_HIGH (0x7FFFULL \u003c\u003c (0)) // [78:64] -\u003e high[14:0]\n+#define DPORT_PBS_MASK (0x1FFFFFULL \u003c\u003c (79 - 64)) // [99:79] -\u003e high[35:15]\n+\n+/* SHAPING shaping_net */\n+struct nbl_shaping_net_u {\n+\tu32 data[NBL_SHAPING_NET_DWLEN];\n+};\n+\n+struct nbl_shaping_dport_u {\n+\tu32 data[NBL_SHAPING_DPORT_DWLEN];\n+};\n+\n+struct nbl_shaping_dvn_dport_u {\n+\tu32 data[NBL_SHAPING_DVN_DPORT_DWLEN];\n+};\n+\n+/*  --------  DSTORE  --------  */\n+#define NBL_DSTORE_D_DPORT_FC_TH_ADDR  0x704600\n+#define NBL_DSTORE_D_DPORT_FC_TH_DEPTH 5\n+#define NBL_DSTORE_D_DPORT_FC_TH_WIDTH 32\n+#define NBL_DSTORE_D_DPORT_FC_TH_DWLEN 1\n+\n+#define NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK GENMASK(10, 0)\n+#define NBL_DSTORE_D_DPORT_FC_XON_TH_MASK GENMASK(26, 16)\n+#define NBL_DSTORE_D_DPORT_FC_FC_EN_MASK BIT(31)\n+\n+#define NBL_DSTORE_D_DPORT_FC_TH_REG(r)  \\\n+\t(NBL_DSTORE_D_DPORT_FC_TH_ADDR + \\\n+\t (NBL_DSTORE_D_DPORT_FC_TH_DWLEN * 4) * (r))\n+#define NBL_DSTORE_PORT_DROP_TH_ADDR 0x704150\n+#define NBL_DSTORE_PORT_DROP_TH_DEPTH 6\n+#define NBL_DSTORE_PORT_DROP_TH_WIDTH 32\n+#define NBL_DSTORE_PORT_DROP_TH_DWLEN 1\n+\n+#define NBL_DSTORE_PORT_DROP_DISC_TH_MASK GENMASK(9, 0)\n+#define NBL_DSTORE_PORT_DROP_EN_MASK BIT(31)\n+\n+#define NBL_DSTORE_DROP_XOFF_TH\t\t\t0xC8\n+#define NBL_DSTORE_DROP_XON_TH\t\t\t0x64\n+\n+#define NBL_DSTORE_DROP_XOFF_TH_100G\t\t0x1F4\n+#define NBL_DSTORE_DROP_XON_TH_100G\t\t0x12C\n+\n+#define NBL_DSTORE_DISC_BP_TH (NBL_DP_DSTORE_BASE + 0x00000630)\n+\n+#define NBL_DSTORE_DISC_BP_TH_EN_MASK BIT(31)\n+\n+#define NBL_DSTORE_PORT_DROP_TH_REG(r)  \\\n+\t(NBL_DSTORE_PORT_DROP_TH_ADDR + \\\n+\t (NBL_DSTORE_PORT_DROP_TH_DWLEN * 4) * (r))\n+\n+#define NBL_FW_BOARD_CONFIG\t\t\t0x200\n+#define NBL_FW_BOARD_DW3_OFFSET\t\t\t(NBL_FW_BOARD_CONFIG + 12)\n+#define NBL_FW_BOARD_DW6_OFFSET\t\t\t(NBL_FW_BOARD_CONFIG + 24)\n+\n+#define NBL_FW_BOARD_DW3_PORT_TYPE_MASK BIT(0)\n+#define NBL_FW_BOARD_DW3_PORT_NUM_MASK GENMASK(7, 1)\n+#define NBL_FW_BOARD_DW3_PORT_SPEED_MASK GENMASK(9, 8)\n+#define NBL_FW_BOARD_DW3_GPIO_TYPE_MASK GENMASK(12, 10)\n+#define NBL_FW_BOARD_DW3_P4_VERSION_MASK GENMASK(13, 13)\n+\n+#define NBL_FW_BOARD_DW6_LANE_BITMAP_MASK GENMASK(7, 0)\n+#define NBL_FW_BOARD_DW6_ETH_BITMAP_MASK GENMASK(15, 8)\n+\n+#define NBL_LEONIS_QUIRKS_OFFSET\t0x00000140\n+\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.c\nnew file mode 100644\nindex 0000000000000..dadd74f054eee\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.c\n@@ -0,0 +1,321 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/pci.h\u003e\n+#include \u003clinux/bits.h\u003e\n+#include \"nbl_resource_leonis.h\"\n+\n+static struct nbl_resource_ops res_ops = {\n+\t.get_vsi_id = nbl_res_func_id_to_vsi_id,\n+\t.get_eth_id = nbl_res_get_eth_id,\n+\t.cfg_msix_map = nbl_res_intr_cfg_msix_map,\n+\t.destroy_msix_map = nbl_res_intr_destroy_msix_map,\n+\t.set_mailbox_irq = nbl_res_intr_set_mailbox_irq,\n+\t.init_module = nbl_res_chip_init_module,\n+\t.deinit_module = nbl_res_chip_deinit_module,\n+};\n+\n+static struct nbl_resource_mgt *\n+nbl_res_setup_res_mgt(struct nbl_common_info *common)\n+{\n+\tstruct nbl_resource_info *resource_info;\n+\tstruct nbl_resource_mgt *res_mgt;\n+\tstruct device *dev = common-\u003edev;\n+\n+\tres_mgt = devm_kzalloc(dev, sizeof(*res_mgt), GFP_KERNEL);\n+\tif (!res_mgt)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\tres_mgt-\u003ecommon = common;\n+\n+\tresource_info =\n+\t\tdevm_kzalloc(dev, sizeof(*resource_info), GFP_KERNEL);\n+\tif (!resource_info)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\tres_mgt-\u003eresource_info = resource_info;\n+\n+\treturn res_mgt;\n+}\n+\n+static struct nbl_resource_ops_tbl *\n+nbl_res_setup_ops(struct device *dev, struct nbl_resource_mgt *res_mgt)\n+{\n+\tstruct nbl_resource_ops_tbl *res_ops_tbl;\n+\n+\tres_ops_tbl = devm_kzalloc(dev, sizeof(*res_ops_tbl), GFP_KERNEL);\n+\tif (!res_ops_tbl)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\tif (!res_ops.get_vsi_id || !res_ops.get_eth_id  ||\n+\t    !res_ops.cfg_msix_map || !res_ops.destroy_msix_map ||\n+\t    !res_ops.set_mailbox_irq || !res_ops.init_module ||\n+\t    !res_ops.deinit_module)\n+\t\treturn ERR_PTR(-EINVAL);\n+\tres_ops_tbl-\u003eops = \u0026res_ops;\n+\tres_ops_tbl-\u003epriv = res_mgt;\n+\n+\treturn res_ops_tbl;\n+}\n+\n+static int nbl_res_ctrl_dev_setup_eth_info(struct nbl_resource_mgt *res_mgt)\n+{\n+\tstruct nbl_hw_ops *hw_ops = res_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tstruct device *dev = res_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_eth_info *eth_info;\n+\tu32 eth_bitmap = 0, eth_id;\n+\tu32 eth_num = 0;\n+\tu32 fw_port_num;\n+\tint i;\n+\n+\teth_info = devm_kzalloc(dev, sizeof(*eth_info), GFP_KERNEL);\n+\tif (!eth_info)\n+\t\treturn -ENOMEM;\n+\n+\tres_mgt-\u003eresource_info-\u003eeth_info = eth_info;\n+\n+\tfw_port_num = res_mgt-\u003eresource_info-\u003eboard_info.eth_num;\n+\thw_ops-\u003eget_fw_eth_map(res_mgt-\u003ehw_ops_tbl-\u003epriv, \u0026eth_bitmap);\n+\tif (eth_bitmap \u0026 ~((1 \u003c\u003c NBL_MAX_ETHERNET) - 1)) {\n+\t\tdev_err(dev, \"FW reported invalid eth_bitmap 0x%x\\n\",\n+\t\t\teth_bitmap);\n+\t\treturn -EINVAL;\n+\t}\n+\tif (fw_port_num != hweight32(eth_bitmap)) {\n+\t\tdev_err(dev, \"FW inconsistency: port_num=%u, bitmap=0x%x\\n\",\n+\t\t\tfw_port_num, eth_bitmap);\n+\t\treturn -EINVAL;\n+\t}\n+\t/* Reject zero ports early to avoid delayed topology mismatch error */\n+\tif (fw_port_num == 0) {\n+\t\tdev_warn(dev, \"FW reports zero ethernet ports, defer probe waiting FW ready\\n\");\n+\t\treturn -EPROBE_DEFER;\n+\t}\n+\tif (fw_port_num \u003e NBL_MAX_ETHERNET || fw_port_num == 3) {\n+\t\tdev_err(dev, \"FW reports %u Ethernet ports, not supported\\n\",\n+\t\t\tfw_port_num);\n+\t\treturn -EINVAL;\n+\t}\n+\teth_info-\u003eeth_num = fw_port_num;\n+\t/* Intentional design constraint: each PF maps to exactly one\n+\t * Ethernet port. This couples PF identity to port identity\n+\t * and is required by nbl_res_get_eth_id() which indexes\n+\t * eth_info-\u003eeth_id[] by relative PF id.\n+\t */\n+\tif (res_mgt-\u003eresource_info-\u003emax_pf != eth_info-\u003eeth_num) {\n+\t\tdev_err(dev, \"Invalid PF-to-port topology: max_pf=%u, eth_num=%u\\n\",\n+\t\t\tres_mgt-\u003eresource_info-\u003emax_pf, eth_info-\u003eeth_num);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/*\n+\t * Original comment said dual-port board eth_id fixed to 0,2;\n+\t * Code accepts any contiguous valid bitmap bits (0/1 or 0/2 etc).\n+\t * Firmware only needs to report correct count of active ports,\n+\t * no hard-coded fixed bit positions required.\n+\t */\n+\tfor (i = 0; i \u003c NBL_MAX_ETHERNET; i++) {\n+\t\tif ((1 \u003c\u003c i) \u0026 eth_bitmap) {\n+\t\t\tset_bit(i, eth_info-\u003eeth_bitmap);\n+\t\t\teth_info-\u003eeth_id[eth_num] = i;\n+\t\t\teth_info-\u003elogic_eth_id[i] = eth_num;\n+\t\t\teth_num++;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i \u003c res_mgt-\u003eresource_info-\u003emax_pf; i++) {\n+\t\teth_id = eth_info-\u003eeth_id[i];\n+\t\teth_info-\u003epf_bitmap[eth_id] |= BIT(i);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int nbl_res_ctrl_dev_sriov_info_init(struct nbl_resource_mgt *res_mgt)\n+{\n+\tstruct nbl_hw_ops *hw_ops = res_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tstruct nbl_hw_mgt *p = res_mgt-\u003ehw_ops_tbl-\u003epriv;\n+\tstruct nbl_common_info *common = res_mgt-\u003ecommon;\n+\tstruct nbl_sriov_info *sriov_info;\n+\tstruct device *dev = common-\u003edev;\n+\tu8 hw_bus = 0;\n+\tu16 function;\n+\tu16 func_id;\n+\n+\thw_ops-\u003eget_real_bus(p, \u0026hw_bus);\n+\tif (common-\u003efunction + res_mgt-\u003eresource_info-\u003emax_pf \u003e NBL_MAX_PF) {\n+\t\tdev_err(dev, \"PF count exceeds available function space\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\tsriov_info = devm_kcalloc(dev, res_mgt-\u003eresource_info-\u003emax_pf,\n+\t\t\t\t  sizeof(*sriov_info), GFP_KERNEL);\n+\tif (!sriov_info)\n+\t\treturn -ENOMEM;\n+\n+\tres_mgt-\u003eresource_info-\u003esriov_info = sriov_info;\n+\t/*\n+\t * common-\u003ehw_bus supplies bus number for channel mailbox QINFO mapping.\n+\t * Execution order guarantee: this assignment runs before\n+\t * cfg_chan_qinfo_map_table() in nbl_dev_start(), only executed\n+\t * on control PF path.\n+\t */\n+\tcommon-\u003ehw_bus = hw_bus;\n+\n+\tfor (func_id = 0; func_id \u003c res_mgt-\u003eresource_info-\u003emax_pf; func_id++) {\n+\t\tsriov_info = res_mgt-\u003eresource_info-\u003esriov_info + func_id;\n+\t\tfunction = common-\u003efunction + func_id;\n+\t\tsriov_info-\u003ebdf = PCI_DEVID(common-\u003ehw_bus,\n+\t\t\t\t\t    PCI_DEVFN(common-\u003edevid, function));\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int nbl_res_ctrl_dev_vsi_info_init(struct nbl_resource_mgt *res_mgt)\n+{\n+\tstruct nbl_eth_info *eth_info = res_mgt-\u003eresource_info-\u003eeth_info;\n+\tstruct nbl_common_info *common = res_mgt-\u003ecommon;\n+\tstruct device *dev = common-\u003edev;\n+\tstruct nbl_vsi_info *vsi_info;\n+\tint i;\n+\n+\tvsi_info = devm_kzalloc(dev, sizeof(*vsi_info), GFP_KERNEL);\n+\tif (!vsi_info)\n+\t\treturn -ENOMEM;\n+\n+\tres_mgt-\u003eresource_info-\u003evsi_info = vsi_info;\n+\t/*\n+\t * case 1 one port(1pf)\n+\t * pf0 (NBL_VSI_SERV_PF_DATA_TYPE) vsi is 0\n+\t * case 2 two port(2pf)\n+\t * pf0,pf1(NBL_VSI_SERV_PF_DATA_TYPE) vsi is 0,512\n+\t * case 3 four port(4pf)\n+\t * pf0,pf1,pf2,pf3(NBL_VSI_SERV_PF_DATA_TYPE) vsi is 0,256,512,768\n+\t */\n+\n+\tvsi_info-\u003enum = eth_info-\u003eeth_num;\n+\t/*\n+\t * eth_num can be 1/2/4:\n+\t * - 2/4 ports use dedicated gap constants;\n+\t * - 1 port falls back to NBL_DEFAULT_VSI_ID_GAP (1024).\n+\t * All three values produce valid base_id offsets.\n+\t */\n+\tfor (i = 0; i \u003c vsi_info-\u003enum; i++) {\n+\t\tvsi_info-\u003eserv_info[i][NBL_VSI_SERV_PF_DATA_TYPE].base_id =\n+\t\t\ti * nbl_vsi_id_gap(vsi_info-\u003enum);\n+\t\tvsi_info-\u003eserv_info[i][NBL_VSI_SERV_PF_DATA_TYPE].num = 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int nbl_res_init_pf_num(struct nbl_resource_mgt *res_mgt)\n+{\n+\tstruct nbl_hw_ops *hw_ops = res_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tu32 pf_mask = 0;\n+\tu32 pf_num = 0;\n+\tint i;\n+\n+\thw_ops-\u003eget_host_pf_mask(res_mgt-\u003ehw_ops_tbl-\u003epriv, \u0026pf_mask);\n+\tif (pf_mask == 0) {\n+\t\tdev_warn(res_mgt-\u003ecommon-\u003edev, \"FW not ready, defer probe\\n\");\n+\t\treturn -EPROBE_DEFER;\n+\t}\n+\t/*\n+\t * Hardware guarantees pf_mask has contiguous cleared bits\n+\t * starting from bit 0 (e.g., 0b11111100, not 0b01010101).\n+\t * This allows us to stop at the first set bit.\n+\t */\n+\tfor (i = 0; i \u003c NBL_MAX_PF; i++) {\n+\t\tif (!(pf_mask \u0026 (1 \u003c\u003c i)))\n+\t\t\tpf_num++;\n+\t}\n+\tif (pf_num == 0 || pf_num \u003e NBL_MAX_ETHERNET || pf_num == 3) {\n+\t\tdev_err(res_mgt-\u003ecommon-\u003edev, \"Invalid pf_num=%u, reject resource init\\n\",\n+\t\t\tpf_num);\n+\t\treturn -EINVAL;\n+\t}\n+\tres_mgt-\u003eresource_info-\u003emax_pf = pf_num;\n+\n+\treturn 0;\n+}\n+\n+static void nbl_res_init_board_info(struct nbl_resource_mgt *res_mgt)\n+{\n+\tstruct nbl_hw_ops *hw_ops = res_mgt-\u003ehw_ops_tbl-\u003eops;\n+\n+\thw_ops-\u003eget_board_info(res_mgt-\u003ehw_ops_tbl-\u003epriv,\n+\t\t\t       \u0026res_mgt-\u003eresource_info-\u003eboard_info);\n+}\n+\n+static int nbl_res_start(struct nbl_resource_mgt *res_mgt)\n+{\n+\tstruct nbl_common_info *common = res_mgt-\u003ecommon;\n+\tint ret = 0;\n+\n+\tif (common-\u003ehas_ctrl) {\n+\t\tnbl_res_init_board_info(res_mgt);\n+\n+\t\tret = nbl_res_init_pf_num(res_mgt);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tret = nbl_res_ctrl_dev_sriov_info_init(res_mgt);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tret = nbl_res_ctrl_dev_setup_eth_info(res_mgt);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tret = nbl_res_ctrl_dev_vsi_info_init(res_mgt);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tret = nbl_intr_mgt_start(res_mgt);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int nbl_res_init_leonis(struct nbl_adapter *adap)\n+{\n+\tstruct nbl_channel_ops_tbl *chan_ops_tbl = adap-\u003eintf.channel_ops_tbl;\n+\tstruct nbl_hw_ops_tbl *hw_ops_tbl = adap-\u003eintf.hw_ops_tbl;\n+\tstruct nbl_common_info *common = \u0026adap-\u003ecommon;\n+\tstruct nbl_resource_ops_tbl *res_ops_tbl;\n+\tstruct device *dev = \u0026adap-\u003epdev-\u003edev;\n+\tstruct nbl_resource_mgt *res_mgt;\n+\tint ret;\n+\n+\tres_mgt = nbl_res_setup_res_mgt(common);\n+\tif (IS_ERR(res_mgt)) {\n+\t\tret = PTR_ERR(res_mgt);\n+\t\treturn ret;\n+\t}\n+\tres_mgt-\u003echan_ops_tbl = chan_ops_tbl;\n+\tres_mgt-\u003ehw_ops_tbl = hw_ops_tbl;\n+\n+\tret = nbl_res_start(res_mgt);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tres_ops_tbl = nbl_res_setup_ops(dev, res_mgt);\n+\tif (IS_ERR(res_ops_tbl)) {\n+\t\tret = PTR_ERR(res_ops_tbl);\n+\t\treturn ret;\n+\t}\n+\tadap-\u003eintf.resource_ops_tbl = res_ops_tbl;\n+\tadap-\u003ecore.res_mgt = res_mgt;\n+\n+\treturn 0;\n+}\n+\n+void nbl_res_remove_leonis(struct nbl_adapter *adap)\n+{\n+\t/*\n+\t * No resource release here because all memory uses devm managed\n+\t * allocation\n+\t */\n+}\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.h\nnew file mode 100644\nindex 0000000000000..f9a53b5d4627f\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.h\n@@ -0,0 +1,12 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_RESOURCE_LEONIS_H_\n+#define _NBL_RESOURCE_LEONIS_H_\n+\n+#include \"../nbl_resource.h\"\n+#include \"../nbl_interrupt.h\"\n+#include \"../nbl_chip.h\"\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_reg.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_reg.h\nnew file mode 100644\nindex 0000000000000..0026cee101aa9\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_reg.h\n@@ -0,0 +1,78 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_HW_REG_H_\n+#define _NBL_HW_REG_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+#include \"../nbl_include/nbl_def_channel.h\"\n+#include \"../nbl_include/nbl_def_hw.h\"\n+#include \"../nbl_include/nbl_def_common.h\"\n+#include \"../nbl_core.h\"\n+\n+#define NBL_MEMORY_BAR\t\t\t\t0\n+#define NBL_MAILBOX_BAR\t\t\t\t2\n+#define NBL_RDMA_NOTIFY_LEN\t\t\t(8ULL \u003c\u003c 10)\n+#define NBL_REG_NET_ONLY_LEN\t\t\t(8ULL \u003c\u003c 10)\n+#define NBL_HW_DUMMY_REG\t\t\t0x1300904\n+/*\n+ * PCI MEMORY BAR total size: 64MiB.\n+ * First 32MiB: module registers + table entry address range.\n+ * Region after 32MiB is unused for ethernet driver.\n+ */\n+#define NBL_HW_REG_SPACE_SIZE\t\t\t(32ULL \u003c\u003c 20)\n+\n+struct nbl_hw_mgt {\n+\tstruct nbl_common_info *common;\n+\tu8 __iomem *hw_addr;\n+\tu8 __iomem *mailbox_bar_hw_addr;\n+\tresource_size_t hw_size;\n+\tspinlock_t reg_lock; /* Protect reg access */\n+};\n+\n+static inline u32 rd32(u8 __iomem *addr, u64 reg)\n+{\n+\treturn readl(addr + reg);\n+}\n+\n+static inline void wr32(u8 __iomem *addr, u64 reg, u32 value)\n+{\n+\twritel(value, addr + reg);\n+}\n+\n+static inline void nbl_hw_wr32(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 value)\n+{\n+\t/* Used for emu, make sure that we won't write too frequently */\n+\twr32(hw_mgt-\u003ehw_addr, reg, value);\n+}\n+\n+static inline u32 nbl_hw_rd32(struct nbl_hw_mgt *hw_mgt, u64 reg)\n+{\n+\treturn rd32(hw_mgt-\u003ehw_addr, reg);\n+}\n+\n+static inline void nbl_mbx_wr32(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 value)\n+{\n+\twritel(value, hw_mgt-\u003emailbox_bar_hw_addr + reg);\n+}\n+\n+/*\n+ * Only call this when has_ctrl=true, which maps enough space\n+ * (bar_len - 8192) to cover NBL_HW_DUMMY_REG (0x1300904).\n+ * The flow/design guarantees this is only called in the\n+ * has_ctrl path.\n+ */\n+static inline void nbl_flush_writes(struct nbl_hw_mgt *hw_mgt)\n+{\n+\tnbl_hw_rd32(hw_mgt, NBL_HW_DUMMY_REG);\n+}\n+\n+static inline u32 nbl_mbx_rd32(struct nbl_hw_mgt *hw_mgt, u64 reg)\n+{\n+\treturn readl(hw_mgt-\u003emailbox_bar_hw_addr + reg);\n+}\n+\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.c\nnew file mode 100644\nindex 0000000000000..34410d0d90224\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.c\n@@ -0,0 +1,405 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/bitfield.h\u003e\n+#include \"nbl_interrupt.h\"\n+\n+#define NBL_MSIX_DMA_SYNC_MIN_US\t100\n+#define NBL_MSIX_DMA_SYNC_MAX_US\t120\n+\n+static void nbl_intr_release_bitmap(struct nbl_resource_mgt *res_mgt,\n+\t\t\t\t    u16 *vec_buf, u16 cnt)\n+{\n+\tstruct nbl_interrupt_mgt *intr_mgt = res_mgt-\u003eintr_mgt;\n+\tu16 bit;\n+\tu16 i;\n+\n+\tif (!vec_buf || cnt == 0)\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c cnt; i++) {\n+\t\tu16 intr_index = vec_buf[i];\n+\n+\t\tif (intr_index \u003e= NBL_NET_INTR_BASE) {\n+\t\t\tbit = intr_index - NBL_NET_INTR_BASE;\n+\t\t\tif (bit \u003c NBL_MAX_NET_INTERRUPT)\n+\t\t\t\tclear_bit(bit, intr_mgt-\u003eintr_net_bmap);\n+\t\t\telse\n+\t\t\t\tdev_warn(res_mgt-\u003ecommon-\u003edev,\n+\t\t\t\t\t \"invalid net intr index %u\\n\",\n+\t\t\t\t\t intr_index);\n+\t\t} else {\n+\t\t\tif (intr_index \u003c NBL_MAX_OTHER_INTERRUPT)\n+\t\t\t\tclear_bit(intr_index,\n+\t\t\t\t\t  intr_mgt-\u003eintr_other_bmap);\n+\t\t\telse\n+\t\t\t\tdev_warn(res_mgt-\u003ecommon-\u003edev,\n+\t\t\t\t\t \"invalid other intr index %u\\n\",\n+\t\t\t\t\t intr_index);\n+\t\t}\n+\t}\n+}\n+\n+/**\n+ * nbl_res_intr_destroy_msix_map - tear down MSI-X mapping for specified PF\n+ * @res_mgt: resource management instance\n+ * @func_id: target function identifier\n+ *\n+ * Clear interrupt bitmaps and disable MSI-X via hardware callbacks.\n+ * DMA coherent table buffer uses devm allocation and remains valid\n+ * until device removal to avoid IOMMU faults caused by in-flight hardware DMA.\n+ *\n+ * Caller note: this function has no internal locking. Serialization\n+ * must be guaranteed at upper dispatch layer.\n+ * Caller must ensure all concurrent resource operations are excluded.\n+ *\n+ * Caller guarantee: hardware cannot issue outstanding DMA to these regions,\n+ * all relevant IRQs have been released or not yet configured.\n+ *\n+ * Return: 0 on success, negative errno on failure\n+ */\n+int nbl_res_intr_destroy_msix_map(struct nbl_resource_mgt *res_mgt,\n+\t\t\t\t  u16 func_id)\n+{\n+\tstruct nbl_interrupt_mgt *intr_mgt = res_mgt-\u003eintr_mgt;\n+\tstruct nbl_hw_ops *hw_ops = res_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tstruct device *dev = res_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_msix_map_table *msix_map_table;\n+\tu16 *interrupts;\n+\tu16 intr_num, i;\n+\n+\tif (!res_mgt-\u003ecommon-\u003ehas_ctrl)\n+\t\treturn -EINVAL;\n+\tif (func_id \u003e= NBL_MAX_FUNC) {\n+\t\tdev_err(dev, \"Invalid func_id %u\\n\", func_id);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tintr_num = intr_mgt-\u003efunc_intr_res[func_id].num_interrupts;\n+\tinterrupts = intr_mgt-\u003efunc_intr_res[func_id].interrupts;\n+\tmsix_map_table = \u0026intr_mgt-\u003efunc_intr_res[func_id].msix_map_table;\n+\n+\tif (!interrupts || !msix_map_table-\u003ebase_addr) {\n+\t\tdev_dbg(dev, \"No interrupt resources for func %u\\n\", func_id);\n+\t\treturn 0;\n+\t}\n+\n+\tfor (i = 0; i \u003c intr_num; i++) {\n+\t\thw_ops-\u003ecfg_msix_info(res_mgt-\u003ehw_ops_tbl-\u003epriv,\n+\t\t\t\t      func_id, false, interrupts[i],\n+\t\t\t\t      0, 0, 0, false);\n+\t}\n+\n+\tnbl_intr_release_bitmap(res_mgt, interrupts, intr_num);\n+\n+\thw_ops-\u003ecfg_msix_map(res_mgt-\u003ehw_ops_tbl-\u003epriv, func_id,\n+\t\t\t     false, 0, 0, 0, 0);\n+\thw_ops-\u003eflush_write(res_mgt-\u003ehw_ops_tbl-\u003epriv);\n+\n+\t/*\n+\t * Hardware provides no idle status register for MSIX map DMA engine.\n+\t * Use bounded sleep to mitigate race between posted MMIO disable writes\n+\t * and ongoing in-flight table read DMA access.\n+\t * After sleep, hardware no longer performs DMA access to MSIX table,\n+\t * safe to free DMA memory now.\n+\t */\n+\tusleep_range(NBL_MSIX_DMA_SYNC_MIN_US, NBL_MSIX_DMA_SYNC_MAX_US);\n+\n+\t/*\n+\t * Now safe to release old MSIX DMA memory, prevents devres accumulation\n+\t * leak Since hardware DMA has quiesced after sleep, no IOMMU fault risk\n+\t * anymore.\n+\t */\n+\tif (msix_map_table-\u003ebase_addr) {\n+\t\tdmam_free_coherent(dev, msix_map_table-\u003esize,\n+\t\t\t\t   msix_map_table-\u003ebase_addr,\n+\t\t\t\t   msix_map_table-\u003edma);\n+\t}\n+\n+\t/* Release runtime allocated interrupt vector buffer */\n+\tkfree(intr_mgt-\u003efunc_intr_res[func_id].interrupts);\n+\tintr_mgt-\u003efunc_intr_res[func_id].interrupts = NULL;\n+\tintr_mgt-\u003efunc_intr_res[func_id].num_interrupts = 0;\n+\tintr_mgt-\u003efunc_intr_res[func_id].num_net_interrupts = 0;\n+\n+\t/* Clear stale MSIX table pointers for safety */\n+\tmsix_map_table-\u003ebase_addr = NULL;\n+\tmsix_map_table-\u003edma = 0;\n+\tmsix_map_table-\u003esize = 0;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * nbl_res_intr_cfg_msix_map - allocate \u0026 program MSI-X mapping table\n+ * @res_mgt: resource management instance\n+ * @func_id: target function identifier\n+ * @num_net_msix: required net data interrupt vectors\n+ * @num_others_msix: required control interrupt vectors\n+ * @net_msix_mask_en: enable mask for net interrupt entries\n+ *\n+ * Allocate interrupt vectors and coherent DMA table in advance;\n+ * only destroy old configuration once all allocations succeed.\n+ *\n+ * Note: There exists a transient window after tearing down old MSI-X hardware\n+ * state before programming new mapping. Atomic table swap is unsupported on\n+ * current silicon, this gap is accepted as hardware limitation.\n+ *\n+ * Caller note: this function has no internal locking. Serialization\n+ * must be guaranteed at upper dispatch layer.\n+ *\n+ * Old MSIX table memory will be explicitly freed inside destroy_msix_map()\n+ * after waiting for hardware DMA quiesce, so repeated reconfiguration will not\n+ * accumulate devres-managed DMA memory.\n+ * Return: 0 on success, negative errno on failure\n+ */\n+int nbl_res_intr_cfg_msix_map(struct nbl_resource_mgt *res_mgt,\n+\t\t\t      u16 func_id, u16 num_net_msix,\n+\t\t\t      u16 num_others_msix,\n+\t\t\t      bool net_msix_mask_en)\n+{\n+\tstruct nbl_interrupt_mgt *intr_mgt = res_mgt-\u003eintr_mgt;\n+\tstruct nbl_hw_ops *hw_ops = res_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tstruct nbl_common_info *common = res_mgt-\u003ecommon;\n+\tstruct nbl_msix_map_table *tmp_msix_tbl = NULL;\n+\tstruct nbl_msix_map_table *official_tbl;\n+\tstruct nbl_msix_map *msix_map_entries;\n+\tstruct device *dev = common-\u003edev;\n+\tu16 requested, intr_index;\n+\tu8 bus, devid, function;\n+\tbool msix_mask_en = false;\n+\tu16 *tmp_interrupts = NULL;\n+\tu16 global_vec;\n+\tint ret = 0;\n+\tu16 i;\n+\n+\tif (!common-\u003ehas_ctrl)\n+\t\treturn -EINVAL;\n+\tif (func_id \u003e= NBL_MAX_FUNC) {\n+\t\tdev_err(dev, \"Invalid func_id %u\\n\", func_id);\n+\t\treturn -EINVAL;\n+\t}\n+\tif (num_net_msix == 0 \u0026\u0026 num_others_msix == 0) {\n+\t\tdev_err(dev, \"MSI-X vector count cannot both be zero\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\tif (num_net_msix \u003e NBL_MSIX_MAP_TABLE_MAX_ENTRIES ||\n+\t    num_others_msix \u003e NBL_MSIX_MAP_TABLE_MAX_ENTRIES) {\n+\t\tdev_err(dev, \"MSI-X count out of limit: net=%u, others=%u\\n\",\n+\t\t\tnum_net_msix, num_others_msix);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (check_add_overflow(num_net_msix, num_others_msix, \u0026requested) ||\n+\t    requested \u003e NBL_MSIX_MAP_TABLE_MAX_ENTRIES) {\n+\t\tdev_err(dev, \"Total MSI-X vectors %u exceeds maximum %u\\n\",\n+\t\t\trequested, NBL_MSIX_MAP_TABLE_MAX_ENTRIES);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tret = nbl_res_func_id_to_bdf(res_mgt, func_id, \u0026bus, \u0026devid, \u0026function);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Phase1: Pre-allocate ALL new resources first.\n+\t * Do NOT destroy old configuration before all allocations succeed.\n+\t */\n+\ttmp_msix_tbl = kzalloc_obj(*tmp_msix_tbl);\n+\tif (!tmp_msix_tbl) {\n+\t\tret = -ENOMEM;\n+\t\tgoto out;\n+\t}\n+\n+\ttmp_msix_tbl-\u003esize =\n+\t\tsizeof(struct nbl_msix_map) * NBL_MSIX_MAP_TABLE_MAX_ENTRIES;\n+\t/*\n+\t * Hardware requires fixed stride table layout; allocate full size\n+\t * even when only partial entries are used. Memory managed by devm.\n+\t */\n+\ttmp_msix_tbl-\u003ebase_addr = dmam_alloc_coherent(dev, tmp_msix_tbl-\u003esize,\n+\t\t\t\t\t\t      \u0026tmp_msix_tbl-\u003edma,\n+\t\t\t\t\t\t      GFP_KERNEL);\n+\tif (!tmp_msix_tbl-\u003ebase_addr) {\n+\t\tdev_err(dev, \"Failed to allocate DMA memory for MSIX table\\n\");\n+\t\tret = -ENOMEM;\n+\t\tgoto free_tmp_tbl;\n+\t}\n+\n+\ttmp_interrupts = kcalloc(requested, sizeof(tmp_interrupts[0]),\n+\t\t\t\t GFP_KERNEL);\n+\tif (!tmp_interrupts) {\n+\t\tret = -ENOMEM;\n+\t\tgoto free_tmp_tbl;\n+\t}\n+\n+\t/* Allocate net interrupt vectors */\n+\tfor (i = 0; i \u003c num_net_msix; i++) {\n+\t\tintr_index = find_first_zero_bit(intr_mgt-\u003eintr_net_bmap,\n+\t\t\t\t\t\t NBL_MAX_NET_INTERRUPT);\n+\t\tif (intr_index == NBL_MAX_NET_INTERRUPT) {\n+\t\t\tdev_err(dev, \"No free net interrupt vectors left\\n\");\n+\t\t\tret = -EAGAIN;\n+\t\t\tgoto release_vecs;\n+\t\t}\n+\t\ttmp_interrupts[i] = intr_index + NBL_NET_INTR_BASE;\n+\t\tset_bit(intr_index, intr_mgt-\u003eintr_net_bmap);\n+\t}\n+\n+\t/* Allocate other interrupt vectors */\n+\tfor (; i \u003c requested; i++) {\n+\t\tintr_index =\n+\t\t\tfind_first_zero_bit(intr_mgt-\u003eintr_other_bmap,\n+\t\t\t\t\t    NBL_MAX_OTHER_INTERRUPT);\n+\t\tif (intr_index == NBL_MAX_OTHER_INTERRUPT) {\n+\t\t\tdev_err(dev, \"No free control interrupt vectors left\\n\");\n+\t\t\tret = -EAGAIN;\n+\t\t\tgoto release_vecs;\n+\t\t}\n+\t\ttmp_interrupts[i] = intr_index;\n+\t\tset_bit(intr_index, intr_mgt-\u003eintr_other_bmap);\n+\t}\n+\n+\t/*\n+\t * Phase2: All new resource allocation succeeded.\n+\t * Now tear down old MSIX hardware configuration.\n+\t */\n+\tret = nbl_res_intr_destroy_msix_map(res_mgt, func_id);\n+\tif (ret)\n+\t\tgoto release_vecs;\n+\n+\t/* Swap temporary resources into official entry */\n+\tofficial_tbl = \u0026intr_mgt-\u003efunc_intr_res[func_id].msix_map_table;\n+\tofficial_tbl-\u003ebase_addr = tmp_msix_tbl-\u003ebase_addr;\n+\tofficial_tbl-\u003edma = tmp_msix_tbl-\u003edma;\n+\tofficial_tbl-\u003esize = tmp_msix_tbl-\u003esize;\n+\tkfree(tmp_msix_tbl);\n+\ttmp_msix_tbl = NULL;\n+\n+\tintr_mgt-\u003efunc_intr_res[func_id].interrupts = tmp_interrupts;\n+\tintr_mgt-\u003efunc_intr_res[func_id].num_interrupts = requested;\n+\tintr_mgt-\u003efunc_intr_res[func_id].num_net_interrupts = num_net_msix;\n+\ttmp_interrupts = NULL;\n+\n+\t/* Fill MSIX map table and program hardware */\n+\tmsix_map_entries = official_tbl-\u003ebase_addr;\n+\tfor (i = 0; i \u003c requested; i++) {\n+\t\tglobal_vec = intr_mgt-\u003efunc_intr_res[func_id].interrupts[i];\n+\t\tmsix_map_entries[i].data =\n+\t\t\tcpu_to_le16(FIELD_PREP(NBL_MSIX_MAP_VALID_MASK, 1) |\n+\t\t\t\t    FIELD_PREP(NBL_MSIX_MAP_INDEX_MASK,\n+\t\t\t\t\t       global_vec));\n+\n+\t\tmsix_mask_en = (i \u003c num_net_msix \u0026\u0026 net_msix_mask_en) ? true :\n+\t\t\t\t\t\t\t\t\tfalse;\n+\t\thw_ops-\u003ecfg_msix_info(res_mgt-\u003ehw_ops_tbl-\u003epriv,\n+\t\t\t\t      func_id, true, global_vec,\n+\t\t\t\t      bus, devid, function,\n+\t\t\t\t      msix_mask_en);\n+\t}\n+\n+\t/* Flush CPU writes to coherent memory before hardware DMA access */\n+\tdma_wmb();\n+\n+\thw_ops-\u003ecfg_msix_map(res_mgt-\u003ehw_ops_tbl-\u003epriv, func_id,\n+\t\t\t     true, official_tbl-\u003edma, common-\u003ehw_bus,\n+\t\t\t     common-\u003edevid, common-\u003efunction);\n+\thw_ops-\u003eflush_write(res_mgt-\u003ehw_ops_tbl-\u003epriv);\n+\n+out:\n+\treturn ret;\n+\n+release_vecs:\n+\tnbl_intr_release_bitmap(res_mgt, tmp_interrupts, i);\n+free_tmp_tbl:\n+\t/* Release DMA buffer allocated by dmam_alloc_coherent first */\n+\tif (tmp_msix_tbl \u0026\u0026 tmp_msix_tbl-\u003ebase_addr) {\n+\t\tdmam_free_coherent(dev, tmp_msix_tbl-\u003esize,\n+\t\t\t\t   tmp_msix_tbl-\u003ebase_addr,\n+\t\t\t\t   tmp_msix_tbl-\u003edma);\n+\t}\n+\tkfree(tmp_msix_tbl);\n+\tkfree(tmp_interrupts);\n+\ttmp_msix_tbl = NULL;\n+\ttmp_interrupts = NULL;\n+\tgoto out;\n+}\n+\n+/**\n+ * nbl_res_intr_set_mailbox_irq - bind mailbox IRQ to specified vector\n+ * @res_mgt: resource management instance\n+ * @func_id: target function identifier\n+ * @vector_id: index inside local interrupt array\n+ * @en_msix: enable/disable mailbox interrupt\n+ *\n+ * Caller note: this function has no internal locking. Serialization\n+ * must be guaranteed at upper dispatch layer.\n+ *\n+ * Return: 0 on success, negative errno on parameter check or hw failure\n+ */\n+int nbl_res_intr_set_mailbox_irq(struct nbl_resource_mgt *res_mgt,\n+\t\t\t\t u16 func_id, u16 vector_id,\n+\t\t\t\t bool en_msix)\n+{\n+\tstruct nbl_interrupt_mgt *intr_mgt = res_mgt-\u003eintr_mgt;\n+\tstruct nbl_hw_ops *hw_ops = res_mgt-\u003ehw_ops_tbl-\u003eops;\n+\tstruct nbl_common_info *common = res_mgt-\u003ecommon;\n+\tstruct device *dev = common-\u003edev;\n+\tu16 global_vec_id;\n+\n+\tif (!common-\u003ehas_ctrl)\n+\t\treturn -EINVAL;\n+\tif (func_id \u003e= NBL_MAX_FUNC) {\n+\t\tdev_err(dev, \"func_id %u out of range\\n\", func_id);\n+\t\treturn -EINVAL;\n+\t}\n+\tif (!intr_mgt-\u003efunc_intr_res[func_id].interrupts) {\n+\t\tdev_err(dev, \"func %u MSIX map not configured\\n\", func_id);\n+\t\treturn -ENODEV;\n+\t}\n+\tif (vector_id \u003e= intr_mgt-\u003efunc_intr_res[func_id].num_interrupts) {\n+\t\tdev_err(dev, \"vector_id %u out of range (max %u)\\n\",\n+\t\t\tvector_id,\n+\t\t\tintr_mgt-\u003efunc_intr_res[func_id].num_interrupts - 1);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tglobal_vec_id = intr_mgt-\u003efunc_intr_res[func_id].interrupts[vector_id];\n+\thw_ops-\u003eset_mailbox_irq(res_mgt-\u003ehw_ops_tbl-\u003epriv, func_id,\n+\t\t\t\ten_msix, global_vec_id);\n+\n+\treturn 0;\n+}\n+\n+static struct nbl_interrupt_mgt *nbl_intr_setup_mgt(struct device *dev)\n+{\n+\tstruct nbl_interrupt_mgt *intr_mgt;\n+\n+\tintr_mgt = devm_kzalloc(dev, sizeof(*intr_mgt), GFP_KERNEL);\n+\tif (!intr_mgt)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tbitmap_zero(intr_mgt-\u003eintr_net_bmap, NBL_MAX_NET_INTERRUPT);\n+\tbitmap_zero(intr_mgt-\u003eintr_other_bmap, NBL_MAX_OTHER_INTERRUPT);\n+\n+\treturn intr_mgt;\n+}\n+\n+int nbl_intr_mgt_start(struct nbl_resource_mgt *res_mgt)\n+{\n+\tstruct device *dev = res_mgt-\u003ecommon-\u003edev;\n+\tstruct nbl_interrupt_mgt *intr_mgt;\n+\tint ret;\n+\n+\tintr_mgt = nbl_intr_setup_mgt(dev);\n+\tif (IS_ERR(intr_mgt)) {\n+\t\tret = PTR_ERR(intr_mgt);\n+\t\treturn ret;\n+\t}\n+\tres_mgt-\u003eintr_mgt = intr_mgt;\n+\treturn 0;\n+}\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.h\nnew file mode 100644\nindex 0000000000000..a4b2de5b2351f\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.h\n@@ -0,0 +1,21 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_INTERRUPT_H_\n+#define _NBL_INTERRUPT_H_\n+\n+#include \"nbl_resource.h\"\n+\n+#define NBL_MSIX_MAP_TABLE_MAX_ENTRIES\t1024\n+int nbl_res_intr_destroy_msix_map(struct nbl_resource_mgt *res_mgt,\n+\t\t\t\t  u16 func_id);\n+int nbl_res_intr_cfg_msix_map(struct nbl_resource_mgt *res_mgt,\n+\t\t\t      u16 func_id, u16 num_net_msix,\n+\t\t\t      u16 num_others_msix,\n+\t\t\t      bool net_msix_mask_en);\n+int nbl_res_intr_set_mailbox_irq(struct nbl_resource_mgt *res_mgt,\n+\t\t\t\t u16 func_id, u16 vector_id,\n+\t\t\t\t bool en_msix);\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c\nnew file mode 100644\nindex 0000000000000..a6e2c7eeed0e3\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c\n@@ -0,0 +1,150 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#include \u003clinux/pci.h\u003e\n+#include \"nbl_resource.h\"\n+\n+int nbl_res_func_id_to_vsi_id(struct nbl_resource_mgt *res_mgt, u16 func_id,\n+\t\t\t      u16 type, u16 *vsi_id)\n+{\n+\tstruct nbl_vsi_info *vsi_info = res_mgt-\u003eresource_info-\u003evsi_info;\n+\tenum nbl_vsi_serv_type dst_type = NBL_VSI_SERV_PF_DATA_TYPE;\n+\tstruct nbl_common_info *common = res_mgt-\u003ecommon;\n+\tstruct device *dev = res_mgt-\u003ecommon-\u003edev;\n+\tint pfid = func_id;\n+\tu32 rel_pf_id;\n+\tint ret;\n+\n+\tif (!common-\u003ehas_ctrl || !vsi_id) {\n+\t\tdev_dbg(dev, \"No control plane or null vsi output ptr\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\tret = nbl_common_func_id_to_rel_pf_id(common, pfid, \u0026rel_pf_id);\n+\tif (ret)\n+\t\treturn ret;\n+\tif (rel_pf_id \u003e= vsi_info-\u003enum) {\n+\t\tdev_err(dev, \"PF %d (diff=%u) exceeds vsi_info-\u003enum (%u)\\n\",\n+\t\t\tpfid, rel_pf_id, vsi_info-\u003enum);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tret = nbl_res_pf_dev_vsi_type_to_hw_vsi_type(res_mgt, type, \u0026dst_type);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Invalid vsi type %u func_id %u\\n\", type, func_id);\n+\t\treturn ret;\n+\t}\n+\t*vsi_id = vsi_info-\u003eserv_info[rel_pf_id][dst_type].base_id;\n+\treturn 0;\n+}\n+\n+int nbl_res_vsi_id_to_pf_id(struct nbl_resource_mgt *res_mgt, u16 vsi_id)\n+{\n+\tstruct nbl_vsi_info *vsi_info = res_mgt-\u003eresource_info-\u003evsi_info;\n+\tstruct nbl_common_info *common = res_mgt-\u003ecommon;\n+\tstruct device *dev = res_mgt-\u003ecommon-\u003edev;\n+\tint j = NBL_VSI_SERV_PF_DATA_TYPE;\n+\tint pf_id, i;\n+\n+\tif (!common-\u003ehas_ctrl) {\n+\t\tdev_dbg(dev, \"No control plane available\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\tfor (i = 0; i \u003c vsi_info-\u003enum; i++) {\n+\t\tif (vsi_id \u003e= vsi_info-\u003eserv_info[i][j].base_id \u0026\u0026\n+\t\t    (vsi_id \u003c vsi_info-\u003eserv_info[i][j].base_id +\n+\t\t\t\t\tvsi_info-\u003eserv_info[i][j].num)) {\n+\t\t\tpf_id = i + common-\u003emgt_pf;\n+\t\t\tif (pf_id \u003e= NBL_MAX_PF) {\n+\t\t\t\tdev_err(dev, \"PF ID overflow\\n\");\n+\t\t\t\treturn -ERANGE;\n+\t\t\t}\n+\t\t\treturn pf_id;\n+\t\t}\n+\t}\n+\n+\tdev_dbg(dev, \"VSI ID %u not found\\n\", vsi_id);\n+\treturn -ENOENT;\n+}\n+\n+int nbl_res_func_id_to_bdf(struct nbl_resource_mgt *res_mgt, u16 func_id,\n+\t\t\t   u8 *bus, u8 *dev, u8 *function)\n+{\n+\tstruct nbl_common_info *common = res_mgt-\u003ecommon;\n+\tstruct nbl_sriov_info *sriov_info;\n+\tint pfid = func_id;\n+\tu8 pf_bus, devfn;\n+\tu32 rel_pf_id;\n+\tint ret;\n+\n+\tif (!common-\u003ehas_ctrl || !bus || !dev || !function)\n+\t\treturn -EINVAL;\n+\tret = nbl_common_func_id_to_rel_pf_id(common, pfid, \u0026rel_pf_id);\n+\tif (ret)\n+\t\treturn ret;\n+\tif (rel_pf_id \u003e= res_mgt-\u003eresource_info-\u003emax_pf) {\n+\t\tdev_err(common-\u003edev, \"PF ID %u exceeds maximum supported PF count %u\\n\",\n+\t\t\tpfid, res_mgt-\u003eresource_info-\u003emax_pf);\n+\t\treturn -ERANGE;\n+\t}\n+\tsriov_info = res_mgt-\u003eresource_info-\u003esriov_info + rel_pf_id;\n+\tpf_bus = PCI_BUS_NUM(sriov_info-\u003ebdf);\n+\tdevfn = sriov_info-\u003ebdf \u0026 0xff;\n+\t*bus = pf_bus;\n+\t*dev = PCI_SLOT(devfn);\n+\t*function = PCI_FUNC(devfn);\n+\n+\treturn 0;\n+}\n+\n+int nbl_res_get_eth_id(struct nbl_resource_mgt *res_mgt, u16 func_id,\n+\t\t       u16 vsi_id, u8 *eth_num, u8 *eth_id, u8 *logic_eth_id)\n+{\n+\tstruct nbl_eth_info *eth_info = res_mgt-\u003eresource_info-\u003eeth_info;\n+\tstruct nbl_common_info *common = res_mgt-\u003ecommon;\n+\tstruct device *dev = res_mgt-\u003ecommon-\u003edev;\n+\tint pfid = func_id;\n+\tint rel_pf_id;\n+\tint abs_pf_id;\n+\n+\tif (!common-\u003ehas_ctrl || !eth_num || !eth_id || !logic_eth_id)\n+\t\treturn -EINVAL;\n+\tabs_pf_id = nbl_res_vsi_id_to_pf_id(res_mgt, vsi_id);\n+\tif (abs_pf_id \u003c 0) {\n+\t\tdev_err(dev, \"Failed to get PF ID from VSI ID %u\\n\", vsi_id);\n+\t\treturn -EINVAL;\n+\t}\n+\tif (abs_pf_id != pfid) {\n+\t\tdev_err(dev, \"func_id %u does not match pf derived from vsi_id %u\\n\",\n+\t\t\tpfid, vsi_id);\n+\t\treturn -EINVAL;\n+\t}\n+\trel_pf_id = abs_pf_id - common-\u003emgt_pf;\n+\n+\tif (rel_pf_id \u003e= eth_info-\u003eeth_num) {\n+\t\tdev_err(dev, \"rel_pf_id %d out of range [0, %u)\\n\",\n+\t\t\trel_pf_id, eth_info-\u003eeth_num);\n+\t\treturn -ERANGE;\n+\t}\n+\n+\t*eth_num = eth_info-\u003eeth_num;\n+\t*eth_id = eth_info-\u003eeth_id[rel_pf_id];\n+\t*logic_eth_id = rel_pf_id;\n+\treturn 0;\n+}\n+\n+int nbl_res_pf_dev_vsi_type_to_hw_vsi_type(struct nbl_resource_mgt *res_mgt,\n+\t\t\t\t\t   u16 src_type,\n+\t\t\t\t\t   enum nbl_vsi_serv_type *dst_type)\n+{\n+\tswitch (src_type) {\n+\tcase NBL_VSI_DATA:\n+\t\t*dst_type = NBL_VSI_SERV_PF_DATA_TYPE;\n+\t\treturn 0;\n+\tdefault:\n+\t\tdev_err_once(res_mgt-\u003ecommon-\u003edev,\n+\t\t\t     \"Unsupported vsi src_type %u\\n\", src_type);\n+\t\treturn -EINVAL;\n+\t}\n+}\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h\nnew file mode 100644\nindex 0000000000000..7f7624e9215db\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h\n@@ -0,0 +1,109 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_RESOURCE_H_\n+#define _NBL_RESOURCE_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+#include \"../nbl_include/nbl_include.h\"\n+#include \"../nbl_include/nbl_def_channel.h\"\n+#include \"../nbl_include/nbl_def_hw.h\"\n+#include \"../nbl_include/nbl_def_resource.h\"\n+#include \"../nbl_include/nbl_def_common.h\"\n+#include \"../nbl_core.h\"\n+\n+struct nbl_resource_mgt;\n+\n+/* --------- INTERRUPT ---------- */\n+#define NBL_MAX_OTHER_INTERRUPT\t\t\t1024\n+#define NBL_MAX_NET_INTERRUPT\t\t\t4096\n+#define NBL_NET_INTR_BASE\t\tNBL_MAX_OTHER_INTERRUPT\n+\n+#define NBL_MSIX_MAP_VALID_MASK\t\tBIT(0)\n+#define NBL_MSIX_MAP_INDEX_MASK\t\tGENMASK(13, 1)\n+#define NBL_MSIX_MAP_RSV_MASK\t\tGENMASK(15, 14)\n+\n+struct nbl_msix_map {\n+\t__le16 data;\n+};\n+\n+struct nbl_msix_map_table {\n+\tstruct nbl_msix_map *base_addr;\n+\tdma_addr_t dma;\n+\tsize_t size;\n+};\n+\n+struct nbl_func_interrupt_resource_mng {\n+\tu16 num_interrupts;\n+\tu16 num_net_interrupts;\n+\tu16 *interrupts;\n+\tstruct nbl_msix_map_table msix_map_table;\n+};\n+\n+struct nbl_interrupt_mgt {\n+\tDECLARE_BITMAP(intr_net_bmap, NBL_MAX_NET_INTERRUPT);\n+\tDECLARE_BITMAP(intr_other_bmap, NBL_MAX_OTHER_INTERRUPT);\n+\tstruct nbl_func_interrupt_resource_mng func_intr_res[NBL_MAX_FUNC];\n+};\n+\n+/* --------- INFO ---------- */\n+struct nbl_sriov_info {\n+\tunsigned int bdf;\n+};\n+\n+struct nbl_eth_info {\n+\tDECLARE_BITMAP(eth_bitmap, NBL_MAX_ETHERNET);\n+\tu8 pf_bitmap[NBL_MAX_ETHERNET];\n+\tu8 eth_num;\n+\tu8 resv[3];\n+\tu8 eth_id[NBL_MAX_PF];\n+\tu8 logic_eth_id[NBL_MAX_PF];\n+};\n+\n+enum nbl_vsi_serv_type {\n+\tNBL_VSI_SERV_PF_DATA_TYPE,\n+\tNBL_VSI_SERV_MAX_TYPE,\n+};\n+\n+struct nbl_vsi_serv_info {\n+\tu16 base_id;\n+\tu16 num;\n+};\n+\n+struct nbl_vsi_info {\n+\tu16 num;\n+\tstruct nbl_vsi_serv_info serv_info[NBL_MAX_ETHERNET]\n+\t\t\t\t\t  [NBL_VSI_SERV_MAX_TYPE];\n+};\n+\n+struct nbl_resource_info {\n+\tstruct nbl_sriov_info *sriov_info;\n+\tstruct nbl_eth_info *eth_info;\n+\tstruct nbl_vsi_info *vsi_info;\n+\tu8 max_pf;\n+\tstruct nbl_board_port_info board_info;\n+};\n+\n+struct nbl_resource_mgt {\n+\tstruct nbl_common_info *common;\n+\tstruct nbl_resource_info *resource_info;\n+\tstruct nbl_channel_ops_tbl *chan_ops_tbl;\n+\tstruct nbl_hw_ops_tbl *hw_ops_tbl;\n+\tstruct nbl_interrupt_mgt *intr_mgt;\n+};\n+\n+int nbl_res_vsi_id_to_pf_id(struct nbl_resource_mgt *res_mgt, u16 vsi_id);\n+int nbl_res_func_id_to_vsi_id(struct nbl_resource_mgt *res_mgt, u16 func_id,\n+\t\t\t      u16 type, u16 *vsi_id);\n+int nbl_res_func_id_to_bdf(struct nbl_resource_mgt *res_mgt, u16 func_id,\n+\t\t\t   u8 *bus, u8 *dev, u8 *function);\n+int nbl_res_get_eth_id(struct nbl_resource_mgt *res_mgt, u16 func_id,\n+\t\t       u16 vsi_id, u8 *eth_num, u8 *eth_id, u8 *logic_eth_id);\n+int nbl_intr_mgt_start(struct nbl_resource_mgt *res_mgt);\n+int nbl_res_pf_dev_vsi_type_to_hw_vsi_type(struct nbl_resource_mgt *res_mgt,\n+\t\t\t\t\t   u16 src_type,\n+\t\t\t\t\t   enum nbl_vsi_serv_type *dst_type);\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h\nnew file mode 100644\nindex 0000000000000..4db33a6e01512\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h\n@@ -0,0 +1,379 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_DEF_CHANNEL_H_\n+#define _NBL_DEF_CHANNEL_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+struct nbl_channel_mgt;\n+struct nbl_adapter;\n+\n+typedef void (*nbl_chan_resp)(void *, u16, u16, void *, u32);\n+\n+enum {\n+\tNBL_CHAN_RESP_OK = 0,\n+\tNBL_CHAN_RESP_ERR = 1,\n+\tNBL_CHAN_RESP_UNIMPLEMENTED = 2,\n+\tNBL_CHAN_RESP_PERM_DENY = 3,\n+};\n+\n+/*\n+ * Mailbox wire opcodes\n+ * Every opcode is assigned explicit fixed numeric value, stable wire ABI\n+ * shared between driver and firmware.\n+ * Firmware/driver wire compatibility rule:\n+ * New entries must only be appended before NBL_CHAN_MSG_MAILBOX_MAX.\n+ * Reordering / inserting / deleting existing items will break cross-version\n+ * interoperability. Any change to existing enumerators requires matching\n+ * firmware ABI adjustment.\n+ */\n+enum nbl_chan_msg_type {\n+\tNBL_CHAN_MSG_ACK = 0,\n+\tNBL_CHAN_MSG_ADD_MACVLAN = 1,\n+\tNBL_CHAN_MSG_DEL_MACVLAN = 2,\n+\tNBL_CHAN_MSG_ADD_MULTI_RULE = 3,\n+\tNBL_CHAN_MSG_DEL_MULTI_RULE = 4,\n+\tNBL_CHAN_MSG_SETUP_MULTI_GROUP = 5,\n+\tNBL_CHAN_MSG_REMOVE_MULTI_GROUP = 6,\n+\tNBL_CHAN_MSG_REGISTER_NET = 7,\n+\tNBL_CHAN_MSG_UNREGISTER_NET = 8,\n+\tNBL_CHAN_MSG_ALLOC_TXRX_QUEUES = 9,\n+\tNBL_CHAN_MSG_FREE_TXRX_QUEUES = 10,\n+\tNBL_CHAN_MSG_SETUP_QUEUE = 11,\n+\tNBL_CHAN_MSG_REMOVE_ALL_QUEUES = 12,\n+\tNBL_CHAN_MSG_CFG_DSCH = 13,\n+\tNBL_CHAN_MSG_SETUP_CQS = 14,\n+\tNBL_CHAN_MSG_REMOVE_CQS = 15,\n+\tNBL_CHAN_MSG_CFG_QDISC_MQPRIO = 16,\n+\tNBL_CHAN_MSG_CONFIGURE_MSIX_MAP = 17,\n+\tNBL_CHAN_MSG_DESTROY_MSIX_MAP = 18,\n+\tNBL_CHAN_MSG_MAILBOX_SET_IRQ = 19,\n+\tNBL_CHAN_MSG_GET_GLOBAL_VECTOR = 20,\n+\tNBL_CHAN_MSG_GET_VSI_ID = 21,\n+\tNBL_CHAN_MSG_SET_PROMISC_MODE = 22,\n+\tNBL_CHAN_MSG_GET_FIRMWARE_VERSION = 23,\n+\tNBL_CHAN_MSG_GET_QUEUE_ERR_STATS = 24,\n+\tNBL_CHAN_MSG_GET_COALESCE = 25,\n+\tNBL_CHAN_MSG_SET_COALESCE = 26,\n+\tNBL_CHAN_MSG_SET_SPOOF_CHECK_ADDR = 27,\n+\tNBL_CHAN_MSG_SET_VF_SPOOF_CHECK = 28,\n+\tNBL_CHAN_MSG_GET_RXFH_INDIR_SIZE = 29,\n+\tNBL_CHAN_MSG_GET_RXFH_INDIR = 30,\n+\tNBL_CHAN_MSG_GET_RXFH_RSS_KEY = 31,\n+\tNBL_CHAN_MSG_GET_RXFH_RSS_ALG_SEL = 32,\n+\tNBL_CHAN_MSG_GET_HW_CAPS = 33,\n+\tNBL_CHAN_MSG_GET_HW_STATE = 34,\n+\tNBL_CHAN_MSG_REGISTER_RDMA = 35,\n+\tNBL_CHAN_MSG_UNREGISTER_RDMA = 36,\n+\tNBL_CHAN_MSG_GET_REAL_HW_ADDR = 37,\n+\tNBL_CHAN_MSG_GET_REAL_BDF = 38,\n+\tNBL_CHAN_MSG_GRC_PROCESS = 39,\n+\tNBL_CHAN_MSG_SET_SFP_STATE = 40,\n+\tNBL_CHAN_MSG_SET_ETH_LOOPBACK = 41,\n+\tNBL_CHAN_MSG_CHECK_ACTIVE_VF = 42,\n+\tNBL_CHAN_MSG_GET_PRODUCT_FLEX_CAP = 43,\n+\tNBL_CHAN_MSG_ALLOC_KTLS_TX_INDEX = 44,\n+\tNBL_CHAN_MSG_FREE_KTLS_TX_INDEX = 45,\n+\tNBL_CHAN_MSG_CFG_KTLS_TX_KEYMAT = 46,\n+\tNBL_CHAN_MSG_ALLOC_KTLS_RX_INDEX = 47,\n+\tNBL_CHAN_MSG_FREE_KTLS_RX_INDEX = 48,\n+\tNBL_CHAN_MSG_CFG_KTLS_RX_KEYMAT = 49,\n+\tNBL_CHAN_MSG_CFG_KTLS_RX_RECORD = 50,\n+\tNBL_CHAN_MSG_ADD_KTLS_RX_FLOW = 51,\n+\tNBL_CHAN_MSG_DEL_KTLS_RX_FLOW = 52,\n+\tNBL_CHAN_MSG_ALLOC_IPSEC_TX_INDEX = 53,\n+\tNBL_CHAN_MSG_FREE_IPSEC_TX_INDEX = 54,\n+\tNBL_CHAN_MSG_ALLOC_IPSEC_RX_INDEX = 55,\n+\tNBL_CHAN_MSG_FREE_IPSEC_RX_INDEX = 56,\n+\tNBL_CHAN_MSG_CFG_IPSEC_TX_SAD = 57,\n+\tNBL_CHAN_MSG_CFG_IPSEC_RX_SAD = 58,\n+\tNBL_CHAN_MSG_ADD_IPSEC_TX_FLOW = 59,\n+\tNBL_CHAN_MSG_DEL_IPSEC_TX_FLOW = 60,\n+\tNBL_CHAN_MSG_ADD_IPSEC_RX_FLOW = 61,\n+\tNBL_CHAN_MSG_DEL_IPSEC_RX_FLOW = 62,\n+\tNBL_CHAN_MSG_NOTIFY_IPSEC_HARD_EXPIRE = 63,\n+\tNBL_CHAN_MSG_GET_MBX_IRQ_NUM = 64,\n+\tNBL_CHAN_MSG_CLEAR_FLOW = 65,\n+\tNBL_CHAN_MSG_CLEAR_QUEUE = 66,\n+\tNBL_CHAN_MSG_GET_ETH_ID = 67,\n+\tNBL_CHAN_MSG_SET_OFFLOAD_STATUS = 68,\n+\tNBL_CHAN_MSG_INIT_OFLD = 69,\n+\tNBL_CHAN_MSG_INIT_CMDQ = 70,\n+\tNBL_CHAN_MSG_DESTROY_CMDQ = 71,\n+\tNBL_CHAN_MSG_RESET_CMDQ = 72,\n+\tNBL_CHAN_MSG_INIT_FLOW = 73,\n+\tNBL_CHAN_MSG_DEINIT_FLOW = 74,\n+\tNBL_CHAN_MSG_OFFLOAD_FLOW_RULE = 75,\n+\tNBL_CHAN_MSG_GET_ACL_SWITCH = 76,\n+\tNBL_CHAN_MSG_GET_VSI_GLOBAL_QUEUE_ID = 77,\n+\tNBL_CHAN_MSG_INIT_REP = 78,\n+\tNBL_CHAN_MSG_GET_LINE_RATE_INFO = 79,\n+\tNBL_CHAN_MSG_REGISTER_NET_REP = 80,\n+\tNBL_CHAN_MSG_UNREGISTER_NET_REP = 81,\n+\tNBL_CHAN_MSG_REGISTER_ETH_REP = 82,\n+\tNBL_CHAN_MSG_UNREGISTER_ETH_REP = 83,\n+\tNBL_CHAN_MSG_REGISTER_UPCALL_PORT = 84,\n+\tNBL_CHAN_MSG_UNREGISTER_UPCALL_PORT = 85,\n+\tNBL_CHAN_MSG_GET_PORT_STATE = 86,\n+\tNBL_CHAN_MSG_SET_PORT_ADVERTISING = 87,\n+\tNBL_CHAN_MSG_GET_MODULE_INFO = 88,\n+\tNBL_CHAN_MSG_GET_MODULE_EEPROM = 89,\n+\tNBL_CHAN_MSG_GET_LINK_STATE = 90,\n+\tNBL_CHAN_MSG_NOTIFY_LINK_STATE = 91,\n+\tNBL_CHAN_MSG_GET_QUEUE_CXT = 92,\n+\tNBL_CHAN_MSG_CFG_LOG = 93,\n+\tNBL_CHAN_MSG_INIT_VDPAQ = 94,\n+\tNBL_CHAN_MSG_DESTROY_VDPAQ = 95,\n+\tNBL_CHAN_MSG_GET_UPCALL_PORT = 96,\n+\tNBL_CHAN_MSG_NOTIFY_ETH_REP_LINK_STATE = 97,\n+\tNBL_CHAN_MSG_SET_ETH_MAC_ADDR = 98,\n+\tNBL_CHAN_MSG_GET_FUNCTION_ID = 99,\n+\tNBL_CHAN_MSG_GET_CHIP_TEMPERATURE = 100,\n+\tNBL_CHAN_MSG_DISABLE_HW_FLOW = 101,\n+\tNBL_CHAN_MSG_ENABLE_HW_FLOW = 102,\n+\tNBL_CHAN_MSG_SET_UPCALL_RULE = 103,\n+\tNBL_CHAN_MSG_UNSET_UPCALL_RULE = 104,\n+\tNBL_CHAN_MSG_GET_REG_DUMP = 105,\n+\tNBL_CHAN_MSG_GET_REG_DUMP_LEN = 106,\n+\tNBL_CHAN_MSG_CFG_LAG_HASH_ALGORITHM = 107,\n+\tNBL_CHAN_MSG_CFG_LAG_MEMBER_FWD = 108,\n+\tNBL_CHAN_MSG_CFG_LAG_MEMBER_LIST = 109,\n+\tNBL_CHAN_MSG_CFG_LAG_MEMBER_UP_ATTR = 110,\n+\tNBL_CHAN_MSG_ADD_LAG_FLOW = 111,\n+\tNBL_CHAN_MSG_DEL_LAG_FLOW = 112,\n+\tNBL_CHAN_MSG_SWITCHDEV_INIT_CMDQ = 113,\n+\tNBL_CHAN_MSG_SWITCHDEV_DEINIT_CMDQ = 114,\n+\tNBL_CHAN_MSG_SET_TC_FLOW_INFO = 115,\n+\tNBL_CHAN_MSG_UNSET_TC_FLOW_INFO = 116,\n+\tNBL_CHAN_MSG_INIT_ACL = 117,\n+\tNBL_CHAN_MSG_UNINIT_ACL = 118,\n+\tNBL_CHAN_MSG_CFG_LAG_MCC = 119,\n+\tNBL_CHAN_MSG_REGISTER_VSI2Q = 120,\n+\tNBL_CHAN_MSG_SETUP_Q2VSI = 121,\n+\tNBL_CHAN_MSG_REMOVE_Q2VSI = 122,\n+\tNBL_CHAN_MSG_SETUP_RSS = 123,\n+\tNBL_CHAN_MSG_REMOVE_RSS = 124,\n+\tNBL_CHAN_MSG_GET_REP_QUEUE_INFO = 125,\n+\tNBL_CHAN_MSG_CTRL_PORT_LED = 126,\n+\tNBL_CHAN_MSG_NWAY_RESET = 127,\n+\tNBL_CHAN_MSG_SET_INTL_SUPPRESS_LEVEL = 128,\n+\tNBL_CHAN_MSG_GET_ETH_STATS = 129,\n+\tNBL_CHAN_MSG_GET_MODULE_TEMPERATURE = 130,\n+\tNBL_CHAN_MSG_GET_BOARD_INFO = 131,\n+\tNBL_CHAN_MSG_GET_P4_USED = 132,\n+\tNBL_CHAN_MSG_GET_VF_BASE_VSI_ID = 133,\n+\tNBL_CHAN_MSG_ADD_LLDP_FLOW = 134,\n+\tNBL_CHAN_MSG_DEL_LLDP_FLOW = 135,\n+\tNBL_CHAN_MSG_CFG_ETH_BOND_INFO = 136,\n+\tNBL_CHAN_MSG_CFG_DUPPKT_MCC = 137,\n+\tNBL_CHAN_MSG_ADD_ND_UPCALL_FLOW = 138,\n+\tNBL_CHAN_MSG_DEL_ND_UPCALL_FLOW = 139,\n+\tNBL_CHAN_MSG_GET_BOARD_ID = 140,\n+\tNBL_CHAN_MSG_SET_SHAPING_DPORT_VLD = 141,\n+\tNBL_CHAN_MSG_SET_DPORT_FC_TH_VLD = 142,\n+\tNBL_CHAN_MSG_REGISTER_RDMA_BOND = 143,\n+\tNBL_CHAN_MSG_UNREGISTER_RDMA_BOND = 144,\n+\tNBL_CHAN_MSG_RESTORE_NETDEV_QUEUE = 145,\n+\tNBL_CHAN_MSG_RESTART_NETDEV_QUEUE = 146,\n+\tNBL_CHAN_MSG_RESTORE_HW_QUEUE = 147,\n+\tNBL_CHAN_MSG_KEEP_ALIVE = 148,\n+\tNBL_CHAN_MSG_GET_BASE_MAC_ADDR = 149,\n+\tNBL_CHAN_MSG_CFG_BOND_SHAPING = 150,\n+\tNBL_CHAN_MSG_CFG_BGID_BACK_PRESSURE = 151,\n+\tNBL_CHAN_MSG_ALLOC_KT_BLOCK = 152,\n+\tNBL_CHAN_MSG_FREE_KT_BLOCK = 153,\n+\tNBL_CHAN_MSG_GET_USER_QUEUE_INFO = 154,\n+\tNBL_CHAN_MSG_GET_ETH_BOND_INFO = 155,\n+\tNBL_CHAN_MSG_CLEAR_ACCEL_FLOW = 156,\n+\tNBL_CHAN_MSG_SET_BRIDGE_MODE = 157,\n+\tNBL_CHAN_MSG_GET_VF_FUNCTION_ID = 158,\n+\tNBL_CHAN_MSG_NOTIFY_LINK_FORCED = 159,\n+\tNBL_CHAN_MSG_SET_PMD_DEBUG = 160,\n+\tNBL_CHAN_MSG_REGISTER_FUNC_MAC = 161,\n+\tNBL_CHAN_MSG_SET_TX_RATE = 162,\n+\tNBL_CHAN_MSG_REGISTER_FUNC_LINK_FORCED = 163,\n+\tNBL_CHAN_MSG_GET_LINK_FORCED = 164,\n+\tNBL_CHAN_MSG_REGISTER_FUNC_VLAN = 165,\n+\tNBL_CHAN_MSG_GET_FD_FLOW = 166,\n+\tNBL_CHAN_MSG_GET_FD_FLOW_CNT = 167,\n+\tNBL_CHAN_MSG_GET_FD_FLOW_ALL = 168,\n+\tNBL_CHAN_MSG_GET_FD_FLOW_MAX = 169,\n+\tNBL_CHAN_MSG_REPLACE_FD_FLOW = 170,\n+\tNBL_CHAN_MSG_REMOVE_FD_FLOW = 171,\n+\tNBL_CHAN_MSG_CFG_FD_FLOW_STATE = 172,\n+\tNBL_CHAN_MSG_REGISTER_FUNC_RATE = 173,\n+\tNBL_CHAN_MSG_NOTIFY_VLAN = 174,\n+\tNBL_CHAN_MSG_GET_XDP_QUEUE_INFO = 175,\n+\tNBL_CHAN_MSG_STOP_ABNORMAL_SW_QUEUE = 176,\n+\tNBL_CHAN_MSG_STOP_ABNORMAL_HW_QUEUE = 177,\n+\tNBL_CHAN_MSG_NOTIFY_RESET_EVENT = 178,\n+\tNBL_CHAN_MSG_ACK_RESET_EVENT = 179,\n+\tNBL_CHAN_MSG_GET_VF_VSI_ID = 180,\n+\tNBL_CHAN_MSG_CONFIGURE_QOS = 181,\n+\tNBL_CHAN_MSG_GET_PFC_BUFFER_SIZE = 182,\n+\tNBL_CHAN_MSG_SET_PFC_BUFFER_SIZE = 183,\n+\tNBL_CHAN_MSG_GET_VF_STATS = 184,\n+\tNBL_CHAN_MSG_REGISTER_FUNC_TRUST = 185,\n+\tNBL_CHAN_MSG_NOTIFY_TRUST = 186,\n+\tNBL_CHAN_MSG_CHECK_VF_IS_ACTIVE = 187,\n+\tNBL_CHAN_MSG_GET_ETH_ABNORMAL_STATS = 188,\n+\tNBL_CHAN_MSG_GET_ETH_CTRL_STATS = 189,\n+\tNBL_CHAN_MSG_GET_PAUSE_STATS = 190,\n+\tNBL_CHAN_MSG_GET_ETH_MAC_STATS = 191,\n+\tNBL_CHAN_MSG_GET_FEC_STATS = 192,\n+\tNBL_CHAN_MSG_CFG_MULTI_MCAST_RULE = 193,\n+\tNBL_CHAN_MSG_GET_LINK_DOWN_COUNT = 194,\n+\tNBL_CHAN_MSG_GET_LINK_STATUS_OPCODE = 195,\n+\tNBL_CHAN_MSG_GET_RMON_STATS = 196,\n+\tNBL_CHAN_MSG_REGISTER_PF_NAME = 197,\n+\tNBL_CHAN_MSG_GET_PF_NAME = 198,\n+\tNBL_CHAN_MSG_CONFIGURE_RDMA_BW = 199,\n+\tNBL_CHAN_MSG_SET_RATE_LIMIT = 200,\n+\tNBL_CHAN_MSG_SET_TC_WGT = 201,\n+\tNBL_CHAN_MSG_REMOVE_QUEUE = 202,\n+\tNBL_CHAN_MSG_GET_MIRROR_TABLE_ID = 203,\n+\tNBL_CHAN_MSG_CONFIGURE_MIRROR = 204,\n+\tNBL_CHAN_MSG_CONFIGURE_MIRROR_TABLE = 205,\n+\tNBL_CHAN_MSG_CLEAR_MIRROR_CFG = 206,\n+\tNBL_CHAN_MSG_MIRROR_OUTPUTPORT_NOTIFY = 207,\n+\tNBL_CHAN_MSG_CHECK_FLOWTABLE_SPEC = 208,\n+\tNBL_CHAN_MSG_CHECK_VF_IS_VDPA = 209,\n+\tNBL_CHAN_MSG_GET_VDPA_VF_STATS = 210,\n+\tNBL_CHAN_MSG_SET_RX_RATE = 211,\n+\tNBL_CHAN_MSG_GET_UVN_PKT_DROP_STATS = 212,\n+\tNBL_CHAN_MSG_GET_USTORE_PKT_DROP_STATS = 213,\n+\tNBL_CHAN_MSG_GET_USTORE_TOTAL_PKT_DROP_STATS = 214,\n+\tNBL_CHAN_MSG_SET_WOL = 215,\n+\tNBL_CHAN_MSG_INIT_VF_MSIX_MAP = 216,\n+\tNBL_CHAN_MSG_GET_ST_NAME = 217,\n+\t/* mailbox msg end */\n+\tNBL_CHAN_MSG_MAILBOX_MAX,\n+};\n+\n+enum nbl_chan_state {\n+\tNBL_CHAN_INTERRUPT_READY,\n+\tNBL_CHAN_ABNORMAL,\n+\tNBL_CHAN_STATE_NBITS\n+};\n+\n+struct nbl_chan_param_cfg_msix_map {\n+\t__le16 num_net_msix;\n+\t__le16 num_others_msix;\n+\t__le16 msix_mask_en;\n+\t__le16 rsvd;\n+};\n+\n+struct nbl_chan_param_set_mailbox_irq {\n+\t__le16 vector_id;\n+\tu8 en_msix;\n+\tu8 rsvd;\n+};\n+\n+struct nbl_chan_param_get_vsi_id {\n+\t__le16 vsi_id;\n+\t__le16 type;\n+};\n+\n+struct nbl_chan_param_get_eth_id {\n+\t__le16 vsi_id;\n+\tu8 eth_num;\n+\tu8 eth_id;\n+\tu8 logic_eth_id;\n+\tu8 rsvd[3];\n+};\n+\n+struct nbl_board_port_info {\n+\tu8 eth_num;\n+\tu8 eth_speed;\n+\tu8 p4_version;\n+\tu8 rsv[5];\n+};\n+\n+struct nbl_chan_send_info {\n+\tvoid *arg;\n+\tsize_t arg_len;\n+\tvoid *resp;\n+\tsize_t resp_len;\n+\tu16 dstid;\n+\tu16 msg_type;\n+\tu16 ack;\n+\tu16 ack_len;\n+};\n+\n+struct nbl_chan_ack_info {\n+\tvoid *data;\n+\tint err;\n+\tu32 data_len;\n+\tu16 dstid;\n+\tu16 msg_type;\n+\tu16 msgid;\n+};\n+\n+enum nbl_channel_type {\n+\tNBL_CHAN_TYPE_MAILBOX,\n+\tNBL_CHAN_TYPE_MAX\n+};\n+\n+static inline void\n+nbl_chan_fill_send_info(struct nbl_chan_send_info *info,\n+\t\t\tu16 dst_id, u16 msg_type,\n+\t\t\tvoid *argument, u32 arg_length,\n+\t\t\tvoid *response, u32 resp_length,\n+\t\t\tbool need_ack)\n+{\n+\tinfo-\u003edstid = dst_id;\n+\tinfo-\u003emsg_type = msg_type;\n+\tinfo-\u003earg = argument;\n+\tinfo-\u003earg_len = arg_length;\n+\tinfo-\u003eresp = response;\n+\tinfo-\u003eresp_len = resp_length;\n+\tinfo-\u003eack = need_ack;\n+}\n+\n+static inline void\n+nbl_chan_fill_ack_info(struct nbl_chan_ack_info *info,\n+\t\t       u16 dst_id, u16 msg_type, u16 msg_id,\n+\t\t       int err_code, void *ack_data, u32 data_length)\n+{\n+\tinfo-\u003edstid = dst_id;\n+\tinfo-\u003emsg_type = msg_type;\n+\tinfo-\u003emsgid = msg_id;\n+\tinfo-\u003eerr = err_code;\n+\tinfo-\u003edata = ack_data;\n+\tinfo-\u003edata_len = data_length;\n+}\n+\n+struct nbl_channel_ops {\n+\tint (*send_msg)(struct nbl_channel_mgt *chan_mgt,\n+\t\t\tstruct nbl_chan_send_info *chan_send);\n+\tint (*send_ack)(struct nbl_channel_mgt *chan_mgt,\n+\t\t\tstruct nbl_chan_ack_info *chan_ack);\n+\tint (*register_msg)(struct nbl_channel_mgt *chan_mgt, u16 msg_type,\n+\t\t\t    nbl_chan_resp func, void *callback_priv);\n+\tvoid (*unregister_all_msg)(struct nbl_channel_mgt *chan_mgt);\n+\tvoid (*cfg_chan_qinfo_map_table)(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t\t u8 bus, u8 devid);\n+\tbool (*check_queue_exist)(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t  u8 chan_type);\n+\tint (*setup_queue)(struct nbl_channel_mgt *chan_mgt, u8 chan_type);\n+\tint (*teardown_queue)(struct nbl_channel_mgt *chan_mgt, u8 chan_type);\n+\tvoid (*clean_queue_subtask)(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t    u8 chan_type);\n+\tvoid (*register_chan_task)(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\t   u8 chan_type, struct work_struct *task);\n+\tvoid (*set_queue_state)(struct nbl_channel_mgt *chan_mgt,\n+\t\t\t\tenum nbl_chan_state state, u8 chan_type,\n+\t\t\t\tu8 set);\n+};\n+\n+struct nbl_channel_ops_tbl {\n+\tstruct nbl_channel_ops *ops;\n+\tstruct nbl_channel_mgt *priv;\n+};\n+\n+int nbl_chan_init_common(struct nbl_adapter *adapter);\n+void nbl_chan_remove_common(struct nbl_adapter *adapter);\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h\nnew file mode 100644\nindex 0000000000000..75882bfe4c936\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h\n@@ -0,0 +1,69 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_DEF_COMMON_H_\n+#define _NBL_DEF_COMMON_H_\n+\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/pci.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \"nbl_include.h\"\n+\n+#define NBL_TWO_ETHERNET_PORT\t\t\t2\n+#define NBL_FOUR_ETHERNET_PORT\t\t\t4\n+#define NBL_DEFAULT_VSI_ID_GAP\t\t\t1024\n+#define NBL_TWO_ETHERNET_VSI_ID_GAP\t\t512\n+#define NBL_FOUR_ETHERNET_VSI_ID_GAP\t\t256\n+\n+static inline u32 nbl_vsi_id_gap(u32 m)\n+{\n+\tif (m == NBL_FOUR_ETHERNET_PORT)\n+\t\treturn NBL_FOUR_ETHERNET_VSI_ID_GAP;\n+\telse if (m == NBL_TWO_ETHERNET_PORT)\n+\t\treturn NBL_TWO_ETHERNET_VSI_ID_GAP;\n+\n+\treturn NBL_DEFAULT_VSI_ID_GAP;\n+}\n+\n+struct nbl_common_info {\n+\tstruct workqueue_struct *wq;\n+\tstruct pci_dev *pdev;\n+\tstruct device *dev;\n+\tu32 msg_enable;\n+\tu16 vsi_id;\n+\tu8 eth_id;\n+\tu8 logic_eth_id;\n+\tu8 eth_num;\n+\n+\tu8 function;\n+\tu8 devid;\n+\tu8 bus;\n+\tu8 hw_bus;\n+\tu16 mgt_pf;\n+\n+\tu8 has_ctrl;\n+\tu8 has_net;\n+};\n+\n+struct nbl_hash_tbl_key {\n+\tstruct device *dev;\n+\tu16 key_size;\n+\tu16 data_size; /* no include key or node member */\n+\tu16 bucket_size;\n+\tu16 resv;\n+};\n+\n+void nbl_common_destroy_wq(struct nbl_common_info *common);\n+int nbl_common_create_wq(struct nbl_common_info *common);\n+int nbl_common_func_id_to_rel_pf_id(struct nbl_common_info *common, u32 pf_id,\n+\t\t\t\t    u32 *rel_pf_id);\n+struct nbl_hash_tbl_mgt *\n+nbl_common_init_hash_table(struct nbl_hash_tbl_key *key);\n+void nbl_common_remove_hash_table(struct nbl_hash_tbl_mgt *tbl_mgt);\n+int nbl_common_alloc_hash_node(struct nbl_hash_tbl_mgt *tbl_mgt, void *key,\n+\t\t\t       void *data, void **out_data);\n+void *nbl_common_get_hash_node(struct nbl_hash_tbl_mgt *tbl_mgt, void *key);\n+\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev.h\nnew file mode 100644\nindex 0000000000000..32e6cce38d397\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev.h\n@@ -0,0 +1,16 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_DEF_DEV_H_\n+#define _NBL_DEF_DEV_H_\n+\n+struct nbl_adapter;\n+\n+int nbl_dev_init(struct nbl_adapter *adapter);\n+void nbl_dev_remove(struct nbl_adapter *adapter);\n+int nbl_dev_start(struct nbl_adapter *adapter);\n+void nbl_dev_stop(struct nbl_adapter *adapter);\n+\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dispatch.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dispatch.h\nnew file mode 100644\nindex 0000000000000..b26425e0112b8\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dispatch.h\n@@ -0,0 +1,42 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_DEF_DISPATCH_H_\n+#define _NBL_DEF_DISPATCH_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+struct nbl_dispatch_mgt;\n+struct nbl_adapter;\n+enum {\n+\tNBL_DISP_CTRL_LVL_NEVER = 0,\n+\tNBL_DISP_CTRL_LVL_MGT,\n+\tNBL_DISP_CTRL_LVL_NET,\n+\tNBL_DISP_CTRL_LVL_MAX,\n+};\n+\n+struct nbl_dispatch_ops {\n+\tint (*init_module)(struct nbl_dispatch_mgt *disp_mgt);\n+\tvoid (*deinit_module)(struct nbl_dispatch_mgt *disp_mgt);\n+\tint (*cfg_msix_map)(struct nbl_dispatch_mgt *disp_mgt,\n+\t\t\t    u16 num_net_msix, u16 num_others_msix,\n+\t\t\t    bool net_msix_mask_en);\n+\tint (*destroy_msix_map)(struct nbl_dispatch_mgt *disp_mgt);\n+\tint (*set_mailbox_irq)(struct nbl_dispatch_mgt *disp_mgt,\n+\t\t\t       u16 vector_id, bool en_msix);\n+\tint (*get_vsi_id)(struct nbl_dispatch_mgt *disp_mgt, u16 type,\n+\t\t\t  u16 *vsi_id);\n+\tint (*get_eth_id)(struct nbl_dispatch_mgt *disp_mgt, u16 vsi_id,\n+\t\t\t  u8 *eth_num, u8 *eth_id, u8 *logic_eth_id);\n+};\n+\n+struct nbl_dispatch_ops_tbl {\n+\tstruct nbl_dispatch_ops *ops;\n+\tstruct nbl_dispatch_mgt *priv;\n+};\n+\n+int nbl_disp_init(struct nbl_adapter *adapter);\n+void nbl_disp_remove(struct nbl_adapter *adapter);\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h\nnew file mode 100644\nindex 0000000000000..31647d3abcbea\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h\n@@ -0,0 +1,53 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_DEF_HW_H_\n+#define _NBL_DEF_HW_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+struct nbl_hw_mgt;\n+struct nbl_adapter;\n+struct nbl_hw_ops {\n+\tint (*init_module)(struct nbl_hw_mgt *hw_mgt, u8 eth_speed, u8 eth_num);\n+\tvoid (*deinit_module)(struct nbl_hw_mgt *hw_mgt);\n+\n+\tvoid (*cfg_msix_map)(struct nbl_hw_mgt *hw_mgt, u16 func_id,\n+\t\t\t     bool valid, dma_addr_t dma_addr, u8 bus,\n+\t\t\t     u8 devid, u8 function);\n+\tvoid (*cfg_msix_info)(struct nbl_hw_mgt *hw_mgt, u16 func_id,\n+\t\t\t      bool valid, u16 interrupt_id, u8 bus,\n+\t\t\t      u8 devid, u8 function,\n+\t\t\t      bool net_msix_mask_en);\n+\tvoid (*flush_write)(struct nbl_hw_mgt *hw_mgt);\n+\tvoid (*update_mailbox_queue_tail_ptr)(struct nbl_hw_mgt *hw_mgt,\n+\t\t\t\t\t      u16 tail_ptr, u8 txrx);\n+\tvoid (*config_mailbox_rxq)(struct nbl_hw_mgt *hw_mgt,\n+\t\t\t\t   dma_addr_t dma_addr, int size_bwid);\n+\tvoid (*config_mailbox_txq)(struct nbl_hw_mgt *hw_mgt,\n+\t\t\t\t   dma_addr_t dma_addr, int size_bwid);\n+\tvoid (*stop_mailbox_rxq)(struct nbl_hw_mgt *hw_mgt);\n+\tvoid (*stop_mailbox_txq)(struct nbl_hw_mgt *hw_mgt);\n+\tvoid (*get_host_pf_mask)(struct nbl_hw_mgt *hw_mgt, u32 *pf_mask);\n+\tvoid (*get_real_bus)(struct nbl_hw_mgt *hw_mgt, u8 *bus);\n+\n+\tvoid (*cfg_mailbox_qinfo)(struct nbl_hw_mgt *hw_mgt, u16 func_id,\n+\t\t\t\t  u8 bus, u8 devid, u8 function);\n+\tvoid (*set_mailbox_irq)(struct nbl_hw_mgt *hw_mgt, u16 func_id,\n+\t\t\t\tbool en_msix, u16 global_vec_id);\n+\tvoid (*get_fw_eth_map)(struct nbl_hw_mgt *hw_mgt, u32 *eth_map);\n+\tvoid (*get_board_info)(struct nbl_hw_mgt *hw_mgt,\n+\t\t\t       struct nbl_board_port_info *board);\n+};\n+\n+struct nbl_hw_ops_tbl {\n+\tstruct nbl_hw_ops *ops;\n+\tstruct nbl_hw_mgt *priv;\n+};\n+\n+int nbl_hw_init_leonis(struct nbl_adapter *adapter);\n+void nbl_hw_remove_leonis(struct nbl_adapter *adapter);\n+\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resource.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resource.h\nnew file mode 100644\nindex 0000000000000..58be5262ec844\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resource.h\n@@ -0,0 +1,38 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_DEF_RESOURCE_H_\n+#define _NBL_DEF_RESOURCE_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+struct nbl_resource_mgt;\n+struct nbl_adapter;\n+\n+struct nbl_resource_ops {\n+\tint (*init_module)(struct nbl_resource_mgt *res_mgt);\n+\tvoid (*deinit_module)(struct nbl_resource_mgt *res_mgt);\n+\n+\tint (*cfg_msix_map)(struct nbl_resource_mgt *res_mgt, u16 func_id,\n+\t\t\t    u16 num_net_msix, u16 num_others_msix,\n+\t\t\t    bool net_msix_mask_en);\n+\tint (*destroy_msix_map)(struct nbl_resource_mgt *res_mgt, u16 func_id);\n+\tint (*set_mailbox_irq)(struct nbl_resource_mgt *res_mgt, u16 func_id,\n+\t\t\t       u16 vector_id, bool en_msix);\n+\tint (*get_vsi_id)(struct nbl_resource_mgt *res_mgt, u16 func_id,\n+\t\t\t  u16 type, u16 *vsi_id);\n+\tint (*get_eth_id)(struct nbl_resource_mgt *res_mgt, u16 func_id,\n+\t\t\t  u16 vsi_id, u8 *eth_num, u8 *eth_id,\n+\t\t\t  u8 *logic_eth_id);\n+};\n+\n+struct nbl_resource_ops_tbl {\n+\tstruct nbl_resource_ops *ops;\n+\tstruct nbl_resource_mgt *priv;\n+};\n+\n+int nbl_res_init_leonis(struct nbl_adapter *adapter);\n+void nbl_res_remove_leonis(struct nbl_adapter *adapter);\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h\nnew file mode 100644\nindex 0000000000000..aa61051c80345\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h\n@@ -0,0 +1,49 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#ifndef _NBL_INCLUDE_H_\n+#define _NBL_INCLUDE_H_\n+\n+#include \u003clinux/types.h\u003e\n+\n+/*  ------  Basic definitions  -------  */\n+#define NBL_DRIVER_NAME\t\t\t\t\t\"nbl\"\n+#define NBL_MAX_PF\t\t\t\t\t8\n+#define NBL_NEXT_ID(id, max) (((id) + 1) % ((max) + 1))\n+\n+#define NBL_MAX_FUNC\t\t\t\t\t520\n+#define NBL_MAX_ETHERNET\t\t\t\t4\n+#define NBL_MAX_PF_SRC_ID\t\t\t\t3\n+/* Used for macros to pass checkpatch */\n+#define NBL_NAME(x)\t\t\t\t\tx\n+\n+enum {\n+\tNBL_VSI_DATA = 0,\n+};\n+\n+struct nbl_func_caps {\n+\tu32 has_ctrl:1;\n+\tu32 has_net:1;\n+\tu32 rsv:30;\n+};\n+\n+struct nbl_init_param {\n+\tstruct nbl_func_caps caps;\n+};\n+\n+/*\n+ * Firmware ABI defines port speed enum fixed, value 0 represents 10G, cannot\n+ * reassign 0 to INVALID for compatibility\n+ */\n+enum nbl_fw_port_speed {\n+\tNBL_FW_PORT_SPEED_10G,\n+\tNBL_FW_PORT_SPEED_25G,\n+\tNBL_FW_PORT_SPEED_50G,\n+\tNBL_FW_PORT_SPEED_100G,\n+};\n+\n+#define NBL_QUIRK_UVN_PREFETCH_ALIGN  BIT(1)\n+\n+#endif\ndiff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c\nnew file mode 100644\nindex 0000000000000..c12a53da6a9b3\n--- /dev/null\n+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c\n@@ -0,0 +1,247 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Copyright (c) 2025 Nebula Matrix Limited.\n+ */\n+\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/pci.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/bits.h\u003e\n+#include \"nbl_include/nbl_include.h\"\n+#include \"nbl_include/nbl_def_channel.h\"\n+#include \"nbl_include/nbl_def_hw.h\"\n+#include \"nbl_include/nbl_def_resource.h\"\n+#include \"nbl_include/nbl_def_dispatch.h\"\n+#include \"nbl_include/nbl_def_dev.h\"\n+#include \"nbl_include/nbl_def_common.h\"\n+#include \"nbl_core.h\"\n+\n+int nbl_core_start(struct nbl_adapter *adapter)\n+{\n+\treturn nbl_dev_start(adapter);\n+}\n+\n+void nbl_core_stop(struct nbl_adapter *adapter)\n+{\n+\tnbl_dev_stop(adapter);\n+}\n+\n+struct nbl_adapter *nbl_core_init(struct pci_dev *pdev,\n+\t\t\t\t  struct nbl_init_param *param)\n+{\n+\tstruct nbl_common_info *common;\n+\tstruct nbl_adapter *adapter;\n+\tint ret;\n+\n+\tadapter = devm_kzalloc(\u0026pdev-\u003edev, sizeof(*adapter), GFP_KERNEL);\n+\tif (!adapter)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tadapter-\u003epdev = pdev;\n+\tcommon = \u0026adapter-\u003ecommon;\n+\n+\tcommon-\u003emgt_pf = 0;\n+\tcommon-\u003epdev = pdev;\n+\tcommon-\u003edev = \u0026pdev-\u003edev;\n+\tcommon-\u003ehas_ctrl = param-\u003ecaps.has_ctrl;\n+\tcommon-\u003ehas_net = param-\u003ecaps.has_net;\n+\tcommon-\u003efunction = PCI_FUNC(pdev-\u003edevfn);\n+\tcommon-\u003edevid = PCI_SLOT(pdev-\u003edevfn);\n+\tcommon-\u003ebus = pdev-\u003ebus-\u003enumber;\n+\n+\tret = nbl_hw_init_leonis(adapter);\n+\tif (ret)\n+\t\tgoto hw_init_fail;\n+\n+\tret = nbl_chan_init_common(adapter);\n+\tif (ret)\n+\t\tgoto chan_init_fail;\n+\n+\tret = nbl_res_init_leonis(adapter);\n+\tif (ret)\n+\t\tgoto res_init_fail;\n+\n+\tret = nbl_disp_init(adapter);\n+\tif (ret)\n+\t\tgoto disp_init_fail;\n+\n+\tret = nbl_dev_init(adapter);\n+\tif (ret)\n+\t\tgoto dev_init_fail;\n+\treturn adapter;\n+\n+dev_init_fail:\n+\tnbl_disp_remove(adapter);\n+disp_init_fail:\n+\tnbl_res_remove_leonis(adapter);\n+res_init_fail:\n+\tnbl_chan_remove_common(adapter);\n+chan_init_fail:\n+\tnbl_hw_remove_leonis(adapter);\n+hw_init_fail:\n+\treturn ERR_PTR(ret);\n+}\n+\n+void nbl_core_remove(struct nbl_adapter *adapter)\n+{\n+\tnbl_dev_remove(adapter);\n+\tnbl_disp_remove(adapter);\n+\tnbl_res_remove_leonis(adapter);\n+\tnbl_chan_remove_common(adapter);\n+\tnbl_hw_remove_leonis(adapter);\n+}\n+\n+static void nbl_get_func_param(struct pci_dev *pdev, kernel_ulong_t driver_data,\n+\t\t\t       struct nbl_init_param *param)\n+{\n+\tparam-\u003ecaps.has_net = !!(driver_data \u0026 BIT(NBL_CAP_HAS_NET_BIT));\n+\n+\t/*\n+\t * Only PF0 possesses ctrl capability; all PFs share identical PCI\n+\t * device ID, so we handle this via special function ID judgement.\n+\t */\n+\tif ((PCI_FUNC(pdev-\u003edevfn) == 0) \u0026\u0026 !pdev-\u003eis_virtfn)\n+\t\tparam-\u003ecaps.has_ctrl = 1;\n+}\n+\n+static int nbl_probe(struct pci_dev *pdev,\n+\t\t     const struct pci_device_id *id)\n+{\n+\tstruct nbl_init_param param = { { 0 } };\n+\tstruct device *dev = \u0026pdev-\u003edev;\n+\tstruct nbl_adapter *adapter;\n+\tint err;\n+\n+\terr = pcim_enable_device(pdev);\n+\tif (err) {\n+\t\tdev_err(\u0026pdev-\u003edev, \"Failed to enable PCI dev, err=%d\\n\", err);\n+\t\treturn err;\n+\t}\n+\n+\tnbl_get_func_param(pdev, id-\u003edriver_data, \u0026param);\n+\n+\terr = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));\n+\tif (err) {\n+\t\tdev_dbg(dev, \"Configure DMA 64 bit mask failed, err = %d\\n\",\n+\t\t\terr);\n+\t\terr = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));\n+\t\tif (err) {\n+\t\t\tdev_err(dev,\n+\t\t\t\t\"Configure DMA 32 bit mask failed, err = %d\\n\",\n+\t\t\t\terr);\n+\t\t\tgoto configure_dma_err;\n+\t\t}\n+\t}\n+\tpci_set_master(pdev);\n+\n+\tadapter = nbl_core_init(pdev, \u0026param);\n+\tif (IS_ERR(adapter)) {\n+\t\tdev_err(dev, \"Nbl adapter init fail: %pe\\n\", adapter);\n+\t\terr = PTR_ERR(adapter);\n+\t\tgoto adapter_init_err;\n+\t}\n+\tpci_set_drvdata(pdev, adapter);\n+\terr = nbl_core_start(adapter);\n+\tif (err)\n+\t\tgoto core_start_err;\n+\n+\treturn 0;\n+core_start_err:\n+\tpci_set_drvdata(pdev, NULL);\n+\tnbl_core_remove(adapter);\n+adapter_init_err:\n+\tpci_clear_master(pdev);\n+configure_dma_err:\n+\treturn err;\n+}\n+\n+static void nbl_remove(struct pci_dev *pdev)\n+{\n+\tstruct nbl_adapter *adapter = pci_get_drvdata(pdev);\n+\n+\tif (adapter) {\n+\t\tnbl_core_stop(adapter);\n+\t\tnbl_core_remove(adapter);\n+\t}\n+\n+\tpci_clear_master(pdev);\n+}\n+\n+/*\n+ * PCI Device IDs for Leonis/NBL Network Controllers\n+ *\n+ * Vendor ID: 0x1F0F\n+ * SNIC v3r1 product Device IDs range: 0x3403-0x3412\n+ */\n+#define NBL_VENDOR_ID\t\t\t\t0x1F0F\n+\n+#define NBL_DEVICE_ID_M18110\t\t\t0x3403\n+#define NBL_DEVICE_ID_M18110_LX\t\t\t0x3404\n+#define NBL_DEVICE_ID_M18110_BASE_T\t\t0x3405\n+#define NBL_DEVICE_ID_M18110_LX_BASE_T\t\t0x3406\n+#define NBL_DEVICE_ID_M18110_OCP\t\t0x3407\n+#define NBL_DEVICE_ID_M18110_LX_OCP\t\t0x3408\n+#define NBL_DEVICE_ID_M18110_BASE_T_OCP\t\t0x3409\n+#define NBL_DEVICE_ID_M18110_LX_BASE_T_OCP\t0x340a\n+#define NBL_DEVICE_ID_M18000\t\t\t0x340b\n+#define NBL_DEVICE_ID_M18000_LX\t\t\t0x340c\n+#define NBL_DEVICE_ID_M18000_BASE_T\t\t0x340d\n+#define NBL_DEVICE_ID_M18000_LX_BASE_T\t\t0x340e\n+#define NBL_DEVICE_ID_M18000_OCP\t\t0x340f\n+#define NBL_DEVICE_ID_M18000_LX_OCP\t\t0x3410\n+#define NBL_DEVICE_ID_M18000_BASE_T_OCP\t\t0x3411\n+#define NBL_DEVICE_ID_M18000_LX_BASE_T_OCP\t0x3412\n+\n+/* All below IDs belong to Leonis ASIC family, different form-factor variants,\n+ * share the same hardware initialization flow without differentiated ops.\n+ */\n+static const struct pci_device_id nbl_id_table[] = {\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_LX),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_BASE_T),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_LX_BASE_T),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_OCP),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_LX_OCP),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_BASE_T_OCP),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_LX_BASE_T_OCP),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_LX),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_BASE_T),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_LX_BASE_T),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_OCP),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_LX_OCP),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_BASE_T_OCP),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_LX_BASE_T_OCP),\n+\t  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },\n+\t/* required as sentinel */\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(pci, nbl_id_table);\n+\n+static struct pci_driver nbl_driver = {\n+\t.name = NBL_DRIVER_NAME,\n+\t.id_table = nbl_id_table,\n+\t.probe = nbl_probe,\n+\t.remove = nbl_remove,\n+};\n+\n+module_pci_driver(nbl_driver);\n+\n+MODULE_LICENSE(\"GPL\");\n+MODULE_DESCRIPTION(\"Nebula Matrix Network Driver\");\n+MODULE_AUTHOR(\"Illusion Wang \u003cillusion.wang@nebula-matrix.com\u003e\");\n"
}

2/1 2026/08/07 12:44 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new driver for the Nebula-matrix m18110-NIC/m18000-NIC family of network interface cards. This is a vendor-specific PCIe hardware driver that requires physical hardware not emulated in standard QEMU environments. Therefore, the code paths are impossible to reach and fuzz in virtualized environments.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a '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:
For your convenience, here is the diff of the changes:
commit 79ccd39435076bc81fd59eb19a6bafc0ea057744
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Aug 7 12:44:09 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/networking/device_drivers/ethernet/index.rst b/Documentation/networking/device_drivers/ethernet/index.rst
index 786a23c84b901..4d74b954b0ba8 100644
--- a/Documentation/networking/device_drivers/ethernet/index.rst
+++ b/Documentation/networking/device_drivers/ethernet/index.rst
@@ -47,6 +47,7 @@ Contents:
    meta/fbnic
    microsoft/netvsc
    mucse/rnpgbe
+   nebula-matrix/nbl
    netronome/nfp
    pensando/ionic
    pensando/ionic_rdma
diff --git a/Documentation/networking/device_drivers/ethernet/nebula-matrix/nbl.rst b/Documentation/networking/device_drivers/ethernet/nebula-matrix/nbl.rst
new file mode 100644
index 0000000000000..ff38302968c51
--- /dev/null
+++ b/Documentation/networking/device_drivers/ethernet/nebula-matrix/nbl.rst
@@ -0,0 +1,28 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+================================================================
+Linux Base Driver for Nebula-matrix m18110-NIC/m18000-NIC family
+================================================================
+
+Overview:
+=========
+The m18110-NIC/m18000-NIC is a series of network interface cards for the Data
+Center Area.
+
+The driver supports link-speed 100GbE/25GE/10GE.
+
+m18110-NIC/m18000-NIC devices support MSI-X interrupt vector for each Tx/Rx
+queue and interrupt moderation.
+
+m18110-NIC/m18000-NIC devices support also various offload features such as
+checksum offload, Receive-Side Scaling(RSS).
+
+Support
+=======
+
+For more information about m18110-NIC/m18000-NIC, please visit the following URL:
+https://www.nebula-matrix.com/snic_s1000_en
+
+If an issue is identified with the released source code on the supported kernel
+with a supported adapter, email the specific information related to the issue to
+open@nebula-matrix.com.
diff --git a/MAINTAINERS b/MAINTAINERS
index 00e7b26e0a23f..ab0910a6836de 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18597,6 +18597,16 @@ F:	Documentation/devicetree/bindings/hwmon/nuvoton,nct7363.yaml
 F:	Documentation/hwmon/nct7363.rst
 F:	drivers/hwmon/nct7363.c
 
+NEBULA-MATRIX ETHERNET DRIVER (nebula-matrix)
+M:	Illusion Wang <illusion.wang@nebula-matrix.com>
+M:	Dimon Zhao <dimon.zhao@nebula-matrix.com>
+M:	Alvin Wang <alvin.wang@nebula-matrix.com>
+M:	Sam Chen <sam.chen@nebula-matrix.com>
+L:	netdev@vger.kernel.org
+S:	Maintained
+F:	Documentation/networking/device_drivers/ethernet/nebula-matrix/
+F:	drivers/net/ethernet/nebula-matrix/
+
 NETCONSOLE
 M:	Breno Leitao <leitao@debian.org>
 S:	Maintained
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index 78c79ad7bba55..5c21d02c4e11a 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -130,6 +130,7 @@ config FEALNX
 
 source "drivers/net/ethernet/ni/Kconfig"
 source "drivers/net/ethernet/natsemi/Kconfig"
+source "drivers/net/ethernet/nebula-matrix/Kconfig"
 source "drivers/net/ethernet/netronome/Kconfig"
 source "drivers/net/ethernet/8390/Kconfig"
 source "drivers/net/ethernet/nvidia/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index bba55d9af3877..accce62a79a69 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_NET_VENDOR_MUCSE) += mucse/
 obj-$(CONFIG_NET_VENDOR_MYRI) += myricom/
 obj-$(CONFIG_FEALNX) += fealnx.o
 obj-$(CONFIG_NET_VENDOR_NATSEMI) += natsemi/
+obj-$(CONFIG_NET_VENDOR_NEBULA_MATRIX) += nebula-matrix/
 obj-$(CONFIG_NET_VENDOR_NETRONOME) += netronome/
 obj-$(CONFIG_NET_VENDOR_NI) += ni/
 obj-$(CONFIG_NET_VENDOR_NVIDIA) += nvidia/
diff --git a/drivers/net/ethernet/nebula-matrix/Kconfig b/drivers/net/ethernet/nebula-matrix/Kconfig
new file mode 100644
index 0000000000000..f16e9663eaedf
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/Kconfig
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Nebula-matrix network device configuration
+#
+
+config NET_VENDOR_NEBULA_MATRIX
+	bool "Nebula-matrix devices"
+	default y
+	help
+	  If you have a network (Ethernet) card belonging to this class, say Y.
+	  Note that the answer to this question doesn't directly affect the
+	  kernel: saying N will just cause the configurator to skip all
+	  the questions about Nebula-matrix cards. If you say Y, you will be asked
+	  for your specific card in the following questions.
+
+if NET_VENDOR_NEBULA_MATRIX
+
+config NBL
+	tristate "Nebula-matrix Ethernet Controller m18110/m18000 support"
+	depends on PCI && (64BIT || COMPILE_TEST) && !CPU_BIG_ENDIAN
+	help
+	  This driver supports Nebula-matrix Ethernet Controller m18110/m18000
+	  Family of devices.
+
+	  More specific information on configuring the driver is in
+	  <file:Documentation/networking/device_drivers/ethernet/nebula-matrix/nbl.rst>.
+
+	  To compile this driver as a module, choose M here. The module
+	  will be called nbl.
+
+endif # NET_VENDOR_NEBULA_MATRIX
diff --git a/drivers/net/ethernet/nebula-matrix/Makefile b/drivers/net/ethernet/nebula-matrix/Makefile
new file mode 100644
index 0000000000000..42cdf2db8f0cf
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the Nebula-matrix network device drivers.
+#
+
+obj-$(CONFIG_NBL) += nbl/
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/Makefile b/drivers/net/ethernet/nebula-matrix/nbl/Makefile
new file mode 100644
index 0000000000000..0febda2098659
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 Nebula Matrix Limited.
+
+obj-$(CONFIG_NBL) := nbl.o
+
+nbl-objs +=       nbl_common/nbl_common.o \
+				nbl_channel/nbl_channel.o \
+				nbl_hw/nbl_hw_leonis/nbl_hw_leonis.o \
+				nbl_hw/nbl_hw_leonis/nbl_resource_leonis.o \
+				nbl_hw/nbl_resource.o \
+				nbl_hw/nbl_interrupt.o \
+				nbl_hw/nbl_chip.o \
+				nbl_core/nbl_dispatch.o \
+				nbl_core/nbl_dev.o \
+				nbl_main.o
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.c
new file mode 100644
index 0000000000000..3b7f3876a075d
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.c
@@ -0,0 +1,1142 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/bitfield.h>
+#include <linux/pci.h>
+#include <linux/bits.h>
+#include <linux/dma-mapping.h>
+#include <linux/atomic.h>
+#include <linux/wait.h>
+#include "nbl_channel.h"
+
+static int nbl_chan_add_msg_handler(struct nbl_channel_mgt *chan_mgt,
+				    u16 msg_type, nbl_chan_resp func,
+				    void *priv)
+{
+	struct nbl_chan_msg_node_data handler = { 0 };
+	int ret;
+
+	handler.func = func;
+	handler.priv = priv;
+	ret = nbl_common_alloc_hash_node(chan_mgt->handle_hash_tbl, &msg_type,
+					 &handler, NULL);
+
+	return ret;
+}
+
+static int nbl_chan_init_msg_handler(struct nbl_channel_mgt *chan_mgt)
+{
+	struct nbl_common_info *common = chan_mgt->common;
+	struct nbl_hash_tbl_key tbl_key = { 0 };
+
+	tbl_key.dev = common->dev;
+	tbl_key.key_size = sizeof(u16);
+	tbl_key.data_size = sizeof(struct nbl_chan_msg_node_data);
+	tbl_key.bucket_size = NBL_CHAN_HANDLER_TBL_BUCKET_SIZE;
+
+	chan_mgt->handle_hash_tbl = nbl_common_init_hash_table(&tbl_key);
+	if (!chan_mgt->handle_hash_tbl)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void nbl_chan_remove_msg_handler(struct nbl_channel_mgt *chan_mgt)
+{
+	if (!chan_mgt->handle_hash_tbl)
+		return;
+	nbl_common_remove_hash_table(chan_mgt->handle_hash_tbl);
+	chan_mgt->handle_hash_tbl = NULL;
+}
+
+static void nbl_chan_init_queue_param(struct nbl_chan_info *chan_info,
+				      u16 num_txq_entries, u16 num_rxq_entries,
+				      u16 txq_buf_size, u16 rxq_buf_size)
+{
+	chan_info->num_txq_entries = num_txq_entries;
+	chan_info->num_rxq_entries = num_rxq_entries;
+	chan_info->txq_buf_size = txq_buf_size;
+	chan_info->rxq_buf_size = rxq_buf_size;
+	atomic_set(&chan_info->inflight_tx_cnt, 0);
+	WRITE_ONCE(chan_info->shutdown, false);
+}
+
+static int nbl_chan_init_tx_queue(struct nbl_common_info *common,
+				  struct nbl_chan_info *chan_info)
+{
+	struct nbl_chan_ring *txq = &chan_info->txq;
+	struct device *dev = common->dev;
+	size_t size =
+		chan_info->num_txq_entries * sizeof(struct nbl_chan_tx_desc);
+	u16 i;
+
+	txq->desc.tx_desc =
+		dmam_alloc_coherent(dev, size, &txq->dma, GFP_KERNEL);
+	if (!txq->desc.tx_desc)
+		return -ENOMEM;
+
+	chan_info->wait = devm_kcalloc(dev, chan_info->num_txq_entries,
+				       sizeof(*chan_info->wait), GFP_KERNEL);
+	if (!chan_info->wait)
+		return -ENOMEM;
+	for (i = 0; i < chan_info->num_txq_entries; i++) {
+		init_waitqueue_head(&chan_info->wait[i].wait_queue);
+		chan_info->wait[i].status = NBL_MBX_STATUS_IDLE;
+		spin_lock_init(&chan_info->wait[i].status_lock);
+	}
+
+	txq->buf = devm_kcalloc(dev, chan_info->num_txq_entries,
+				sizeof(*txq->buf), GFP_KERNEL);
+	if (!txq->buf)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int nbl_chan_init_rx_queue(struct nbl_common_info *common,
+				  struct nbl_chan_info *chan_info)
+{
+	struct nbl_chan_ring *rxq = &chan_info->rxq;
+	struct device *dev = common->dev;
+	size_t size =
+		chan_info->num_rxq_entries * sizeof(struct nbl_chan_rx_desc);
+
+	rxq->desc.rx_desc =
+		dmam_alloc_coherent(dev, size, &rxq->dma, GFP_KERNEL);
+	if (!rxq->desc.rx_desc) {
+		dev_err_ratelimited(dev,
+				    "Allocate DMA for chan rx descriptor ring failed\n");
+		return -ENOMEM;
+	}
+
+	rxq->buf = devm_kcalloc(dev, chan_info->num_rxq_entries,
+				sizeof(*rxq->buf), GFP_KERNEL);
+	if (!rxq->buf)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int nbl_chan_init_queue(struct nbl_common_info *common,
+			       struct nbl_chan_info *chan_info)
+{
+	int err;
+
+	err = nbl_chan_init_tx_queue(common, chan_info);
+	if (err)
+		return err;
+
+	err = nbl_chan_init_rx_queue(common, chan_info);
+
+	return err;
+}
+
+static void nbl_chan_config_queue(struct nbl_channel_mgt *chan_mgt,
+				  struct nbl_chan_info *chan_info, bool tx)
+{
+	struct nbl_hw_ops *hw_ops = chan_mgt->hw_ops_tbl->ops;
+	struct nbl_hw_mgt *p = chan_mgt->hw_ops_tbl->priv;
+	struct nbl_chan_ring *ring;
+	dma_addr_t addr;
+	int size_bwid;
+
+	if (tx)
+		ring = &chan_info->txq;
+	else
+		ring = &chan_info->rxq;
+	addr = ring->dma;
+	if (tx) {
+		size_bwid = ilog2(chan_info->num_txq_entries);
+		hw_ops->config_mailbox_txq(p, addr, size_bwid);
+	} else {
+		size_bwid = ilog2(chan_info->num_rxq_entries);
+		hw_ops->config_mailbox_rxq(p, addr, size_bwid);
+	}
+}
+
+static int nbl_chan_alloc_all_tx_bufs(struct nbl_channel_mgt *chan_mgt,
+				      struct nbl_chan_info *chan_info)
+{
+	struct nbl_chan_ring *txq = &chan_info->txq;
+	struct device *dev = chan_mgt->common->dev;
+	struct nbl_chan_buf *buf;
+	u16 i;
+
+	for (i = 0; i < chan_info->num_txq_entries; i++) {
+		buf = &txq->buf[i];
+		buf->va = dmam_alloc_coherent(dev, chan_info->txq_buf_size,
+					      &buf->pa, GFP_KERNEL);
+		if (!buf->va) {
+			dev_err_ratelimited(dev,
+					    "Allocate buffer for chan tx queue failed\n");
+			return -ENOMEM;
+		}
+	}
+
+	txq->next_to_clean = 0;
+	txq->next_to_use = 0;
+	txq->tail_ptr = 0;
+
+	return 0;
+}
+
+static void nbl_chan_cfg_qinfo_map_table(struct nbl_channel_mgt *chan_mgt,
+					 u8 bus, u8 devid)
+{
+	struct nbl_hw_ops *hw_ops = chan_mgt->hw_ops_tbl->ops;
+	struct nbl_hw_mgt *p = chan_mgt->hw_ops_tbl->priv;
+	u32 pf_mask = 0;
+	u8 func_id;
+
+	hw_ops->get_host_pf_mask(p, &pf_mask);
+	for (func_id = 0; func_id < NBL_MAX_PF; func_id++) {
+		if (!(pf_mask & (1 << func_id)))
+			hw_ops->cfg_mailbox_qinfo(p, func_id, bus,
+						  devid, func_id);
+	}
+}
+
+static int nbl_chan_alloc_all_rx_bufs(struct nbl_channel_mgt *chan_mgt,
+				      struct nbl_chan_info *chan_info)
+{
+	struct nbl_chan_ring *rxq = &chan_info->rxq;
+	struct device *dev = chan_mgt->common->dev;
+	struct nbl_chan_rx_desc *desc;
+	struct nbl_chan_buf *buf;
+	u16 i;
+
+	for (i = 0; i < chan_info->num_rxq_entries; i++) {
+		buf = &rxq->buf[i];
+		buf->va = dmam_alloc_coherent(dev, chan_info->rxq_buf_size,
+					      &buf->pa, GFP_KERNEL);
+		if (!buf->va) {
+			dev_err_ratelimited(dev,
+					    "Allocate buffer for chan rx queue failed\n");
+			goto err;
+		}
+	}
+
+	desc = rxq->desc.rx_desc;
+	/*
+	 * RX ring adopts empty-ring convention: reserve one unused entry
+	 * to distinguish queue FULL vs queue EMPTY.
+	 * We initialize entries [0 ... num_rxq_entries - 2],
+	 * leave the last entry uninitialized as the hole marker.
+	 */
+	for (i = 0; i < chan_info->num_rxq_entries - 1; i++) {
+		buf = &rxq->buf[i];
+		desc[i].buf_addr = cpu_to_le64(buf->pa);
+		desc[i].buf_len = cpu_to_le32(chan_info->rxq_buf_size);
+		desc[i].flags = cpu_to_le16(BIT(NBL_CHAN_RX_DESC_AVAIL));
+	}
+
+	rxq->next_to_clean = 0;
+	/* Reserve last slot as hole to differentiate full/empty status */
+	rxq->next_to_use = chan_info->num_rxq_entries - 1;
+	rxq->tail_ptr = chan_info->num_rxq_entries - 1;
+
+	return 0;
+err:
+	return -ENOMEM;
+}
+
+static int nbl_chan_alloc_all_bufs(struct nbl_channel_mgt *chan_mgt,
+				   struct nbl_chan_info *chan_info)
+{
+	int err;
+
+	err = nbl_chan_alloc_all_tx_bufs(chan_mgt, chan_info);
+	if (err)
+		return err;
+	err = nbl_chan_alloc_all_rx_bufs(chan_mgt, chan_info);
+
+	return err;
+}
+
+static void nbl_chan_stop_queue(struct nbl_channel_mgt *chan_mgt)
+{
+	struct nbl_hw_ops *hw_ops = chan_mgt->hw_ops_tbl->ops;
+
+	hw_ops->stop_mailbox_rxq(chan_mgt->hw_ops_tbl->priv);
+	hw_ops->stop_mailbox_txq(chan_mgt->hw_ops_tbl->priv);
+}
+
+static int nbl_chan_teardown_queue(struct nbl_channel_mgt *chan_mgt,
+				   u8 chan_type)
+{
+	struct nbl_chan_info *chan_info = chan_mgt->chan_info[chan_type];
+	struct nbl_chan_waitqueue_head *wait_head;
+	unsigned long wait_start, wait_timeout;
+	int ret = 0;
+	u16 i;
+
+	/* Step 1: Mark shutdown flag, reject all new send requests */
+	WRITE_ONCE(chan_info->shutdown, true);
+	/*
+	 * Full memory barrier: Ensure the write to 'shutdown' becomes visible
+	 * to all other CPUs BEFORE we start waiting on inflight_tx_cnt.
+	 * Peer send_msg threads check shutdown flag prior to taking new work;
+	 * this barrier prevents reordering that would let send paths observe
+	 * inflight_tx_cnt == 0 but still accept new messages.
+	 */
+	smp_mb();
+
+	for (i = 0; i < chan_info->num_txq_entries; i++) {
+		wait_head = &chan_info->wait[i];
+		spin_lock_irq(&wait_head->status_lock);
+		/* Only wake threads that are actually waiting */
+		if (READ_ONCE(wait_head->status) == NBL_MBX_STATUS_WAITING) {
+			/* Mark as timeout so waking threads know to abort */
+			WRITE_ONCE(wait_head->status, NBL_MBX_STATUS_TIMEOUT);
+			WRITE_ONCE(wait_head->acked, 1);
+			WRITE_ONCE(wait_head->ack_err, (s32)-EIO);
+			wake_up(&wait_head->wait_queue);
+		}
+		spin_unlock_irq(&wait_head->status_lock);
+	}
+	/* Stop hardware queues */
+	nbl_chan_stop_queue(chan_mgt);
+
+	/*
+	 * Step 2: Wait all in-flight send_msg threads exit FIRST.
+	 * Guarantee no further queue_work() can be triggered during cleanup.
+	 * Add timeout to avoid permanent blocking.
+	 */
+	wait_start = jiffies;
+	wait_timeout = msecs_to_jiffies(5000);
+	while (atomic_read(&chan_info->inflight_tx_cnt) != 0) {
+		if (time_after(jiffies, wait_start + wait_timeout)) {
+			dev_warn(chan_mgt->common->dev,
+				 "teardown: wait inflight_tx_cnt timeout, force continue\n");
+			ret = -ETIMEDOUT;
+			break;
+		}
+		cpu_relax();
+		usleep_range(1000, 2000);
+	}
+
+	/* Step3: All send paths drained, safely cancel cleanup work */
+	if (chan_info->clean_task) {
+		cancel_work_sync(chan_info->clean_task);
+		chan_info->clean_task = NULL;
+	}
+	return ret;
+}
+
+static int nbl_chan_setup_queue(struct nbl_channel_mgt *chan_mgt, u8 chan_type)
+{
+	struct nbl_chan_info *chan_info = chan_mgt->chan_info[chan_type];
+	struct nbl_hw_ops *hw_ops = chan_mgt->hw_ops_tbl->ops;
+	struct nbl_common_info *common = chan_mgt->common;
+	struct nbl_chan_ring *rxq = &chan_info->rxq;
+	int err;
+
+	if (READ_ONCE(chan_info->init_done))
+		return 0;
+	err = devm_mutex_init(common->dev, &chan_info->txq_lock);
+	if (err)
+		return err;
+	nbl_chan_init_queue_param(chan_info, NBL_CHAN_QUEUE_LEN,
+				  NBL_CHAN_QUEUE_LEN, NBL_CHAN_BUF_LEN,
+				  NBL_CHAN_BUF_LEN);
+	err = nbl_chan_init_queue(common, chan_info);
+	if (err)
+		return err;
+	err = nbl_chan_alloc_all_bufs(chan_mgt, chan_info);
+	if (err)
+		return err;
+	nbl_chan_config_queue(chan_mgt, chan_info, true); /* tx */
+	nbl_chan_config_queue(chan_mgt, chan_info, false); /* rx */
+	nbl_chan_update_tail_ptr(hw_ops, chan_mgt->hw_ops_tbl->priv,
+				 rxq->tail_ptr, NBL_MB_RX_QID);
+	WRITE_ONCE(chan_info->init_done, true);
+	return 0;
+}
+
+static int nbl_chan_update_txqueue(struct nbl_channel_mgt *chan_mgt,
+				   struct nbl_chan_info *chan_info,
+				   struct nbl_chan_tx_param *param)
+{
+	struct nbl_chan_ring *txq = &chan_info->txq;
+	struct nbl_chan_tx_desc *tx_desc =
+		NBL_CHAN_TX_RING_TO_DESC(txq, txq->next_to_use);
+	struct nbl_chan_buf *tx_buf =
+		NBL_CHAN_TX_RING_TO_BUF(txq, txq->next_to_use);
+
+	if (param->arg_len > NBL_CHAN_BUF_LEN - sizeof(*tx_desc))
+		return -EINVAL;
+
+	tx_desc->dstid = cpu_to_le16(param->dstid);
+	tx_desc->msg_type = cpu_to_le16(param->msg_type);
+	tx_desc->msgid = cpu_to_le16(param->msgid);
+
+	if (param->arg_len > NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN) {
+		memcpy(tx_buf->va, param->arg, param->arg_len);
+		tx_desc->buf_addr = cpu_to_le64(tx_buf->pa);
+		tx_desc->buf_len = cpu_to_le16(param->arg_len);
+		tx_desc->data_len = 0;
+		memset(tx_desc->data, 0, sizeof(tx_desc->data));
+	} else {
+		memset(tx_desc->data, 0, sizeof(tx_desc->data));
+		memset(&tx_desc->buf_addr, 0, sizeof(tx_desc->buf_addr));
+		memcpy(tx_desc->data, param->arg, param->arg_len);
+		tx_desc->buf_len = 0;
+		tx_desc->data_len = cpu_to_le16(param->arg_len);
+	}
+	dma_wmb();
+	tx_desc->flags = cpu_to_le16(BIT(NBL_CHAN_TX_DESC_AVAIL));
+
+	txq->next_to_use =
+		NBL_NEXT_ID(txq->next_to_use, chan_info->num_txq_entries - 1);
+	txq->tail_ptr++;
+
+	return 0;
+}
+
+static int nbl_chan_kick_tx_ring(struct nbl_channel_mgt *chan_mgt,
+				 struct nbl_chan_info *chan_info)
+{
+	struct nbl_hw_ops *hw_ops = chan_mgt->hw_ops_tbl->ops;
+	struct nbl_chan_ring *txq = &chan_info->txq;
+	struct device *dev = chan_mgt->common->dev;
+	int max_retries = NBL_CHAN_TX_WAIT_TIMES;
+	struct nbl_chan_tx_desc *tx_desc;
+	int retry_count = 0;
+	u16 msg_type;
+
+	nbl_chan_update_tail_ptr(hw_ops, chan_mgt->hw_ops_tbl->priv,
+				 txq->tail_ptr, NBL_MB_TX_QID);
+
+	tx_desc = NBL_CHAN_TX_RING_TO_DESC(txq, txq->next_to_clean);
+	while (retry_count < max_retries) {
+		if (READ_ONCE(chan_info->shutdown))
+			return -ESHUTDOWN;
+
+		/* Ensure HW written descriptor visible to CPU */
+		smp_rmb();
+		if (le16_to_cpu(READ_ONCE(tx_desc->flags)) &
+		    BIT(NBL_CHAN_TX_DESC_USED)) {
+			break;
+		}
+
+		retry_count++;
+		if (retry_count == max_retries) {
+			msg_type = le16_to_cpu(READ_ONCE(tx_desc->msg_type));
+			dev_err_ratelimited(dev, "chan send msg type: %d timeout\n",
+					    msg_type);
+			txq->next_to_clean = txq->next_to_use;
+			return -ETIMEDOUT;
+		}
+		usleep_range(NBL_CHAN_TX_WAIT_US, NBL_CHAN_TX_WAIT_US_MAX);
+	}
+
+	txq->next_to_clean = txq->next_to_use;
+
+	return 0;
+}
+
+static void nbl_chan_recv_ack_msg(void *priv, u16 srcid, u16 msgid, void *data,
+				  u32 data_len)
+{
+	struct nbl_channel_mgt *chan_mgt = (struct nbl_channel_mgt *)priv;
+	struct nbl_chan_waitqueue_head *wait_head = NULL;
+	struct device *dev = chan_mgt->common->dev;
+	struct nbl_chan_info *chan_info =
+		chan_mgt->chan_info[NBL_CHAN_TYPE_MAILBOX];
+	u32 ack_datalen, ack_msgtype = 0;
+	u32 *payload = data;
+	u16 ack_msgid = 0;
+	u32 copy_len;
+
+	if (READ_ONCE(chan_info->shutdown))
+		return;
+	if (data_len > NBL_CHAN_BUF_LEN ||
+	    data_len < NBL_CHAN_ACK_HEAD_LEN * sizeof(u32)) {
+		dev_err_ratelimited(dev, "Invalid ACK data_len: %u\n",
+				    data_len);
+		return;
+	}
+	ack_datalen = data_len - NBL_CHAN_ACK_HEAD_LEN * sizeof(u32);
+	ack_msgtype = le16_to_cpu(*(__le16 *)(payload + NBL_CHAN_MSG_TYPE_POS));
+	ack_msgid = le16_to_cpu(*(__le16 *)(payload + NBL_CHAN_MSG_ID_POS));
+	if (FIELD_GET(NBL_CHAN_MSGID_LOC_MASK, ack_msgid) >=
+	    chan_info->num_txq_entries) {
+		dev_err_ratelimited(dev, "chan recv msg id: %d err\n",
+				    ack_msgid);
+		return;
+	}
+	wait_head =
+		&chan_info->wait[FIELD_GET(NBL_CHAN_MSGID_LOC_MASK, ack_msgid)];
+	spin_lock_irq(&wait_head->status_lock);
+	if (srcid != READ_ONCE(wait_head->dstid)) {
+		/* Do not modify the status; the slot remains WAITING,
+		 * and the sender will time out normally
+		 */
+		spin_unlock_irq(&wait_head->status_lock);
+		dev_err_ratelimited(dev, "ACK srcid=%u != dstid=%u, rejecting\n",
+				    srcid, READ_ONCE(wait_head->dstid));
+		return;
+	}
+	if (READ_ONCE(wait_head->status) != NBL_MBX_STATUS_WAITING) {
+		spin_unlock_irq(&wait_head->status_lock);
+		dev_err_ratelimited(dev,
+				    "Skip ack with invalid status, wait msgtype:%u msg_index:%u status:%d ack_data_len:%d, ack msgtype:%u msgid:%u datalen:%d\n",
+				    READ_ONCE(wait_head->msg_type),
+				    READ_ONCE(wait_head->msg_index),
+				    READ_ONCE(wait_head->status),
+				    READ_ONCE(wait_head->ack_data_len),
+				    ack_msgtype, ack_msgid, ack_datalen);
+		return;
+	}
+
+	if (READ_ONCE(wait_head->msg_type) != ack_msgtype) {
+		spin_unlock_irq(&wait_head->status_lock);
+		dev_err_ratelimited(dev,
+				    "Skip ack msg type mismatch, wait msgtype:%u msg_index:%u status:%d ack_data_len:%d, ack msgtype:%u msgid:%u datalen:%d\n",
+				    READ_ONCE(wait_head->msg_type),
+				    READ_ONCE(wait_head->msg_index),
+				    READ_ONCE(wait_head->status),
+				    READ_ONCE(wait_head->ack_data_len),
+				    ack_msgtype, ack_msgid, ack_datalen);
+		return;
+	}
+	if (FIELD_GET(NBL_CHAN_MSGID_INDEX_MASK, ack_msgid) !=
+	    READ_ONCE(wait_head->msg_index)) {
+		spin_unlock_irq(&wait_head->status_lock);
+		dev_err_ratelimited(dev,
+				    "Stale ACK: expected index=%u, got msgid %u\n",
+				    READ_ONCE(wait_head->msg_index), ack_msgid);
+		return;
+	}
+
+	wait_head->ack_err =
+		(s32)le32_to_cpu(*(__le32 *)(payload + NBL_CHAN_ACK_RET_POS));
+
+	copy_len = min_t(u32, READ_ONCE(wait_head->ack_data_len), ack_datalen);
+	if (wait_head->ack_err >= 0 && copy_len > 0) {
+		if (!READ_ONCE(wait_head->ack_data)) {
+			dev_err_ratelimited(dev, "ACK payload dropped: ack_data is NULL\n");
+			wait_head->ack_data_len = 0;
+			goto ack_done;
+		}
+		memcpy((char *)wait_head->ack_data,
+		       payload + NBL_CHAN_ACK_HEAD_LEN, copy_len);
+		wait_head->ack_data_len = (u16)copy_len;
+	} else {
+		wait_head->ack_data_len = 0;
+	}
+ack_done:
+	WRITE_ONCE(wait_head->acked, 1);
+	spin_unlock_irq(&wait_head->status_lock);
+	if (READ_ONCE(wait_head->acked))
+		wake_up(&wait_head->wait_queue);
+}
+
+static void nbl_chan_recv_msg(struct nbl_channel_mgt *chan_mgt, void *data)
+{
+	struct device *dev = chan_mgt->common->dev;
+	struct nbl_chan_msg_node_data *msg_handler;
+	u16 msg_type, payload_len, srcid, msgid;
+	struct nbl_chan_info *chan_info =
+		chan_mgt->chan_info[NBL_CHAN_TYPE_MAILBOX];
+	struct nbl_chan_tx_desc *tx_desc;
+	void *payload;
+	size_t size;
+
+	if (READ_ONCE(chan_info->shutdown))
+		return;
+	tx_desc = data;
+	msg_type = le16_to_cpu(READ_ONCE(tx_desc->msg_type));
+	dev_dbg(dev, "recv msg_type: %d\n", msg_type);
+
+	srcid = le16_to_cpu(READ_ONCE(tx_desc->srcid));
+	msgid = le16_to_cpu(READ_ONCE(tx_desc->msgid));
+	/* Only check if the value exceeds the maximum, relying on the hash
+	 * table to filter invalid message IDs.
+	 * The gap values are reserved for future protocol extensions.
+	 */
+	if (msg_type >= NBL_CHAN_MSG_MAILBOX_MAX)
+		return;
+
+	if (le16_to_cpu(READ_ONCE(tx_desc->data_len))) {
+		payload_len = le16_to_cpu(READ_ONCE(tx_desc->data_len));
+		if (payload_len > NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN) {
+			dev_err_ratelimited(dev,
+					    "data_len=%u exceeds embedded buffer size=%u\n",
+					    payload_len,
+					    NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN);
+			return;
+		}
+		payload = tx_desc->data;
+	} else {
+		payload_len = le16_to_cpu(READ_ONCE(tx_desc->buf_len));
+		size = NBL_CHAN_BUF_LEN - sizeof(*tx_desc);
+		if (payload_len > size) {
+			dev_err_ratelimited(dev,
+					    "buf_len=%u exceeds external buffer size=%zu\n",
+					    payload_len,
+					    size);
+			return;
+		}
+		payload = tx_desc + 1;
+	}
+
+	msg_handler =
+		nbl_common_get_hash_node(chan_mgt->handle_hash_tbl, &msg_type);
+	if (!msg_handler || !msg_handler->func) {
+		dev_err_ratelimited(dev,
+				    "No handler for msg_type: %u (srcid=%u, msgid=%u)\n",
+				    msg_type, srcid, msgid);
+		return;
+	}
+	msg_handler->func(msg_handler->priv, srcid, msgid, payload,
+			  payload_len);
+}
+
+static void nbl_chan_advance_rx_ring(struct nbl_channel_mgt *chan_mgt,
+				     struct nbl_chan_info *chan_info,
+				     struct nbl_chan_ring *rxq)
+{
+	struct nbl_hw_ops *hw_ops = chan_mgt->hw_ops_tbl->ops;
+	struct nbl_chan_rx_desc *rx_desc;
+	struct nbl_chan_buf *rx_buf;
+	u16 next_to_use;
+
+	next_to_use = rxq->next_to_use;
+	rx_desc = NBL_CHAN_RX_RING_TO_DESC(rxq, next_to_use);
+	rx_buf = NBL_CHAN_RX_RING_TO_BUF(rxq, next_to_use);
+
+	rx_desc->buf_addr = cpu_to_le64(rx_buf->pa);
+	rx_desc->buf_len = cpu_to_le32(chan_info->rxq_buf_size);
+
+	/*
+	 * DMA Write Memory Barrier:
+	 * Ensures all previous DMA-mapped writes (buffer address/length)
+	 * are completed before the descriptor flags are updated.
+	 * This prevents hardware from seeing a partially updated descriptor
+	 * where flags are set but buffer info isn't ready yet.
+	 */
+	dma_wmb();
+
+	rx_desc->flags = cpu_to_le16(BIT(NBL_CHAN_RX_DESC_AVAIL));
+
+	rxq->next_to_use++;
+	if (rxq->next_to_use == chan_info->num_rxq_entries)
+		rxq->next_to_use = 0;
+	rxq->tail_ptr++;
+
+	nbl_chan_update_tail_ptr(hw_ops, chan_mgt->hw_ops_tbl->priv,
+				 rxq->tail_ptr, NBL_MB_RX_QID);
+}
+
+static void nbl_chan_clean_queue(struct nbl_channel_mgt *chan_mgt,
+				 struct nbl_chan_info *chan_info)
+{
+	struct nbl_common_info *common = chan_mgt->common;
+	struct nbl_chan_ring *rxq = &chan_info->rxq;
+	struct device *dev = chan_mgt->common->dev;
+	struct nbl_chan_rx_desc *rx_desc;
+	struct nbl_chan_buf *rx_buf;
+	struct work_struct *task;
+	bool more_work = false;
+	u16 next_to_clean;
+	u32 budget = 64;
+	u16 flags;
+
+	next_to_clean = rxq->next_to_clean;
+	rx_desc = NBL_CHAN_RX_RING_TO_DESC(rxq, next_to_clean);
+	rx_buf = NBL_CHAN_RX_RING_TO_BUF(rxq, next_to_clean);
+	while (le16_to_cpu(READ_ONCE(rx_desc->flags)) &
+	       BIT(NBL_CHAN_RX_DESC_USED)) {
+		flags = le16_to_cpu(READ_ONCE(rx_desc->flags));
+
+		if (READ_ONCE(chan_info->shutdown))
+			break;
+		if (!(flags & BIT(NBL_CHAN_RX_DESC_WRITE)))
+			dev_dbg(dev,
+				"mailbox rx flag 0x%x missing NBL_CHAN_RX_DESC_WRITE\n",
+				flags);
+
+		dma_rmb();
+		nbl_chan_recv_msg(chan_mgt, rx_buf->va);
+		nbl_chan_advance_rx_ring(chan_mgt, chan_info, rxq);
+		next_to_clean++;
+		if (next_to_clean == chan_info->num_rxq_entries)
+			next_to_clean = 0;
+		rx_desc = NBL_CHAN_RX_RING_TO_DESC(rxq, next_to_clean);
+		rx_buf = NBL_CHAN_RX_RING_TO_BUF(rxq, next_to_clean);
+		if (--budget == 0) {
+			more_work = true;
+			break;
+		}
+		cond_resched();
+	}
+	rxq->next_to_clean = next_to_clean;
+
+	/* Prevent queue_work after teardown clears clean_task */
+	if (READ_ONCE(chan_info->shutdown))
+		return;
+	if (common->wq && more_work) {
+		task = READ_ONCE(chan_info->clean_task);
+		if (task)
+			queue_work(common->wq, task);
+	}
+}
+
+static void nbl_chan_clean_queue_subtask(struct nbl_channel_mgt *chan_mgt,
+					 u8 chan_type)
+{
+	struct nbl_chan_info *chan_info = chan_mgt->chan_info[chan_type];
+
+	nbl_chan_clean_queue(chan_mgt, chan_info);
+}
+
+static int nbl_chan_get_msg_id(struct nbl_chan_info *chan_info,
+			       u16 *msgid)
+{
+	int search_loc = chan_info->wait_head_index, i;
+	struct nbl_chan_waitqueue_head *wait = NULL;
+	int status;
+
+	for (i = 0; i < chan_info->num_txq_entries; i++) {
+		wait = &chan_info->wait[search_loc];
+
+		spin_lock_irq(&wait->status_lock);
+		status = READ_ONCE(wait->status);
+		if (status == NBL_MBX_STATUS_IDLE ||
+		    status == NBL_MBX_STATUS_TIMEOUT) {
+			WRITE_ONCE(wait->msg_index,
+				   NBL_NEXT_ID(wait->msg_index,
+					       NBL_CHAN_MSG_INDEX_MAX));
+
+			*msgid = FIELD_PREP(NBL_CHAN_MSGID_INDEX_MASK,
+					    wait->msg_index) |
+				 FIELD_PREP(NBL_CHAN_MSGID_LOC_MASK,
+					    search_loc);
+			spin_unlock_irq(&wait->status_lock);
+
+			/* Advance starting search position for next caller */
+			WRITE_ONCE(chan_info->wait_head_index, search_loc);
+			return 0;
+		}
+		spin_unlock_irq(&wait->status_lock);
+
+		search_loc = NBL_NEXT_ID(search_loc,
+					 chan_info->num_txq_entries - 1);
+	}
+
+	/*
+	 * All tx slots are occupied. May happen under high transmit load
+	 * or delayed remote ACK responses. Caller should retry later.
+	 */
+	return -EAGAIN;
+}
+
+static int nbl_chan_send_msg(struct nbl_channel_mgt *chan_mgt,
+			     struct nbl_chan_send_info *chan_send)
+{
+	struct nbl_common_info *common = chan_mgt->common;
+	struct nbl_chan_waitqueue_head *wait_head;
+	struct nbl_chan_tx_param tx_param = { 0 };
+	int i = NBL_CHAN_TX_WAIT_ACK_TIMES;
+	struct nbl_chan_info *chan_info =
+		chan_mgt->chan_info[NBL_CHAN_TYPE_MAILBOX];
+	struct device *dev = common->dev;
+	bool inflight_inc = false;
+	struct work_struct *task;
+	u16 msgid = 0;
+	int ret;
+	u16 tmp;
+
+	if (chan_send->resp_len > NBL_CHAN_BUF_LEN) {
+		dev_err_ratelimited(dev, "resp_len %zu exceeds max %d\n",
+				    chan_send->resp_len, NBL_CHAN_BUF_LEN);
+		return -EINVAL;
+	}
+
+	atomic_inc(&chan_info->inflight_tx_cnt);
+	inflight_inc = true;
+	/*
+	 * Ensure reading chan_info->shutdown happens
+	 * strictly after inflight_tx_cnt increment above on this CPU.
+	 * Prevent compiler/cpu reordering: read shutdown before incrementing
+	 * inflight counter, which would cause missing shutdown check and leak
+	 * tx inflight count.
+	 */
+	smp_rmb();
+	if (READ_ONCE(chan_info->shutdown)) {
+		atomic_dec(&chan_info->inflight_tx_cnt);
+		return -ESHUTDOWN;
+	}
+
+	mutex_lock(&chan_info->txq_lock);
+	if (test_bit(NBL_CHAN_ABNORMAL, chan_info->state)) {
+		ret = -EIO;
+		goto unlock_out;
+	}
+	ret = nbl_chan_get_msg_id(chan_info, &msgid);
+	if (ret) {
+		dev_err_ratelimited(dev,
+				    "Channel tx wait head full, send msgtype:%u to dstid:%u failed\n",
+				    chan_send->msg_type, chan_send->dstid);
+		goto unlock_out;
+	}
+
+	tx_param.msg_type = chan_send->msg_type;
+	tx_param.arg = chan_send->arg;
+	tx_param.arg_len = chan_send->arg_len;
+	tx_param.dstid = chan_send->dstid;
+	tx_param.msgid = msgid;
+
+	ret = nbl_chan_update_txqueue(chan_mgt, chan_info, &tx_param);
+	if (ret) {
+		dev_err_ratelimited(dev,
+				    "Channel tx queue full, send msgtype:%u to dstid:%u failed\n",
+				    chan_send->msg_type, chan_send->dstid);
+		goto unlock_out;
+	}
+
+	wait_head =
+		&chan_info->wait[FIELD_GET(NBL_CHAN_MSGID_LOC_MASK, msgid)];
+	spin_lock_irq(&wait_head->status_lock);
+	WRITE_ONCE(wait_head->acked, 0);
+	WRITE_ONCE(wait_head->ack_data, chan_send->resp);
+	WRITE_ONCE(wait_head->ack_data_len, chan_send->resp_len);
+	WRITE_ONCE(wait_head->msg_type, chan_send->msg_type);
+	WRITE_ONCE(wait_head->msg_index,
+		   FIELD_GET(NBL_CHAN_MSGID_INDEX_MASK, msgid));
+	WRITE_ONCE(wait_head->dstid, chan_send->dstid);
+
+	WRITE_ONCE(wait_head->status, chan_send->ack ?
+		   NBL_MBX_STATUS_WAITING : NBL_MBX_STATUS_IDLE);
+	spin_unlock_irq(&wait_head->status_lock);
+
+	ret = nbl_chan_kick_tx_ring(chan_mgt, chan_info);
+	if (ret) {
+		spin_lock_irq(&wait_head->status_lock);
+		WRITE_ONCE(wait_head->status, NBL_MBX_STATUS_TIMEOUT);
+		WRITE_ONCE(wait_head->acked, 0);
+		WRITE_ONCE(wait_head->ack_data, NULL);
+		WRITE_ONCE(wait_head->ack_data_len, 0);
+		spin_unlock_irq(&wait_head->status_lock);
+		goto unlock_out;
+	}
+	mutex_unlock(&chan_info->txq_lock);
+	if (!chan_send->ack) {
+		tmp = FIELD_GET(NBL_CHAN_MSGID_LOC_MASK, msgid);
+		wait_head = &chan_info->wait[tmp];
+		spin_lock_irq(&wait_head->status_lock);
+		WRITE_ONCE(wait_head->status, NBL_MBX_STATUS_IDLE);
+		spin_unlock_irq(&wait_head->status_lock);
+		atomic_dec(&chan_info->inflight_tx_cnt);
+		return 0;
+	}
+
+	/* choose wait strategy based on interrupt state */
+	if (test_bit(NBL_CHAN_INTERRUPT_READY, chan_info->state)) {
+		spin_lock_irq(&wait_head->status_lock);
+		while (!READ_ONCE(wait_head->acked)) {
+			spin_unlock_irq(&wait_head->status_lock);
+			ret = wait_event_timeout(wait_head->wait_queue,
+						 READ_ONCE(wait_head->acked) ||
+						 READ_ONCE(chan_info->shutdown),
+						 NBL_CHAN_ACK_WAIT_TIME);
+			spin_lock_irq(&wait_head->status_lock);
+
+			if (READ_ONCE(chan_info->shutdown)) {
+				ret = -ESHUTDOWN;
+				WRITE_ONCE(wait_head->ack_data, NULL);
+				WRITE_ONCE(wait_head->ack_data_len, 0);
+				WRITE_ONCE(wait_head->status,
+					   NBL_MBX_STATUS_IDLE);
+				spin_unlock_irq(&wait_head->status_lock);
+				goto inflight_dec_out;
+			}
+			if (ret == 0) {
+				if (READ_ONCE(wait_head->status) ==
+				    NBL_MBX_STATUS_WAITING) {
+					WRITE_ONCE(wait_head->status,
+						   NBL_MBX_STATUS_TIMEOUT);
+					WRITE_ONCE(wait_head->acked, 0);
+					WRITE_ONCE(wait_head->ack_data, NULL);
+					WRITE_ONCE(wait_head->ack_data_len, 0);
+				}
+				spin_unlock_irq(&wait_head->status_lock);
+				dev_err_ratelimited(dev,
+						    "Channel waiting ack failed, message type: %d, msg id: %u\n",
+						    chan_send->msg_type, msgid);
+				ret = -ETIMEDOUT;
+				goto inflight_dec_out;
+			}
+
+			if (READ_ONCE(wait_head->acked))
+				break;
+		}
+
+		chan_send->ack_len = wait_head->ack_data_len;
+		ret = wait_head->ack_err;
+		WRITE_ONCE(wait_head->acked, 0);
+		WRITE_ONCE(wait_head->status, NBL_MBX_STATUS_IDLE);
+		WRITE_ONCE(wait_head->ack_data, NULL);
+		WRITE_ONCE(wait_head->ack_data_len, 0);
+		spin_unlock_irq(&wait_head->status_lock);
+		goto inflight_dec_out;
+	} else {
+		/* Polling wait path for synchronous ACK */
+		while (i--) {
+			if (READ_ONCE(chan_info->shutdown)) {
+				ret = -ESHUTDOWN;
+				/*
+				 * Shutdown happens during polling wait,
+				 * reclaim slot
+				 */
+				spin_lock_irq(&wait_head->status_lock);
+				WRITE_ONCE(wait_head->acked, 0);
+				WRITE_ONCE(wait_head->ack_data, NULL);
+				WRITE_ONCE(wait_head->ack_data_len, 0);
+				WRITE_ONCE(wait_head->status,
+					   NBL_MBX_STATUS_TIMEOUT);
+				spin_unlock_irq(&wait_head->status_lock);
+				goto inflight_dec_out;
+			}
+
+			task = READ_ONCE(chan_info->clean_task);
+			if (common->wq && task &&
+			    !READ_ONCE(chan_info->shutdown))
+				queue_work(common->wq, task);
+
+			spin_lock_irq(&wait_head->status_lock);
+			if (READ_ONCE(wait_head->acked)) {
+				chan_send->ack_len = wait_head->ack_data_len;
+				ret = wait_head->ack_err;
+				WRITE_ONCE(wait_head->acked, 0);
+				WRITE_ONCE(wait_head->status,
+					   NBL_MBX_STATUS_IDLE);
+				spin_unlock_irq(&wait_head->status_lock);
+				goto inflight_dec_out;
+			}
+			spin_unlock_irq(&wait_head->status_lock);
+
+			usleep_range(NBL_CHAN_TX_WAIT_ACK_US_MIN,
+				     NBL_CHAN_TX_WAIT_ACK_US_MAX);
+			cond_resched();
+		}
+
+		/* Polling loop exhausted, reclaim slot */
+		spin_lock_irq(&wait_head->status_lock);
+		WRITE_ONCE(wait_head->acked, 0);
+		WRITE_ONCE(wait_head->ack_data, NULL);
+		WRITE_ONCE(wait_head->ack_data_len, 0);
+		WRITE_ONCE(wait_head->status, NBL_MBX_STATUS_TIMEOUT);
+		spin_unlock_irq(&wait_head->status_lock);
+
+		dev_err_ratelimited(dev,
+				    "Channel polling ack failed, message type: %d msg id: %u\n",
+				    chan_send->msg_type, msgid);
+		ret = -ETIMEDOUT;
+		goto inflight_dec_out;
+	}
+
+inflight_dec_out:
+	atomic_dec(&chan_info->inflight_tx_cnt);
+	return ret;
+
+unlock_out:
+	mutex_unlock(&chan_info->txq_lock);
+	if (inflight_inc)
+		atomic_dec(&chan_info->inflight_tx_cnt);
+	return ret;
+}
+
+static int nbl_chan_send_ack(struct nbl_channel_mgt *chan_mgt,
+			     struct nbl_chan_ack_info *chan_ack)
+{
+	size_t head_len = NBL_CHAN_ACK_HEAD_LEN * sizeof(u32);
+	size_t data_len = chan_ack->data_len;
+	struct nbl_chan_send_info chan_send;
+	__le32 *tmp;
+	size_t len;
+	int ret;
+
+	if (data_len >
+	    NBL_CHAN_BUF_LEN - sizeof(struct nbl_chan_tx_desc) - head_len)
+		return -EINVAL;
+
+	len = head_len + data_len;
+	tmp = kzalloc(len, GFP_ATOMIC);
+	if (!tmp)
+		return -ENOMEM;
+
+	*(__le16 *)&tmp[NBL_CHAN_MSG_TYPE_POS] =
+		cpu_to_le16(chan_ack->msg_type);
+	*(__le16 *)&tmp[NBL_CHAN_MSG_ID_POS] = cpu_to_le16(chan_ack->msgid);
+	tmp[NBL_CHAN_ACK_RET_POS] = cpu_to_le32(chan_ack->err);
+	if (chan_ack->data && chan_ack->data_len)
+		memcpy(&tmp[NBL_CHAN_ACK_HEAD_LEN], chan_ack->data,
+		       chan_ack->data_len);
+
+	nbl_chan_fill_send_info(&chan_send, chan_ack->dstid, NBL_CHAN_MSG_ACK,
+				tmp, len, NULL, 0, 0);
+	ret = nbl_chan_send_msg(chan_mgt, &chan_send);
+	kfree(tmp);
+
+	return ret;
+}
+
+static int nbl_chan_register_msg(struct nbl_channel_mgt *chan_mgt, u16 msg_type,
+				 nbl_chan_resp func, void *callback)
+{
+	return nbl_chan_add_msg_handler(chan_mgt, msg_type, func, callback);
+}
+
+static bool nbl_chan_check_queue_exist(struct nbl_channel_mgt *chan_mgt,
+				       u8 chan_type)
+{
+	struct nbl_chan_info *chan_info = chan_mgt->chan_info[chan_type];
+
+	return chan_info ? true : false;
+}
+
+static void nbl_chan_register_chan_task(struct nbl_channel_mgt *chan_mgt,
+					u8 chan_type, struct work_struct *task)
+{
+	struct nbl_chan_info *chan_info = chan_mgt->chan_info[chan_type];
+
+	chan_info->clean_task = task;
+}
+
+static void nbl_chan_set_queue_state(struct nbl_channel_mgt *chan_mgt,
+				     enum nbl_chan_state state, u8 chan_type,
+				     u8 set)
+{
+	struct nbl_chan_info *chan_info = chan_mgt->chan_info[chan_type];
+
+	if (set)
+		set_bit(state, chan_info->state);
+	else
+		clear_bit(state, chan_info->state);
+}
+
+static struct nbl_channel_ops chan_ops = {
+	.send_msg			= nbl_chan_send_msg,
+	.send_ack			= nbl_chan_send_ack,
+	.register_msg			= nbl_chan_register_msg,
+	.unregister_all_msg		= nbl_chan_remove_msg_handler,
+	.cfg_chan_qinfo_map_table	= nbl_chan_cfg_qinfo_map_table,
+	.check_queue_exist		= nbl_chan_check_queue_exist,
+	.setup_queue			= nbl_chan_setup_queue,
+	.teardown_queue			= nbl_chan_teardown_queue,
+	.clean_queue_subtask		= nbl_chan_clean_queue_subtask,
+	.register_chan_task		= nbl_chan_register_chan_task,
+	.set_queue_state		= nbl_chan_set_queue_state,
+};
+
+static struct nbl_channel_mgt *
+nbl_chan_setup_chan_mgt(struct nbl_adapter *adapter)
+{
+	struct nbl_hw_ops_tbl *hw_ops_tbl = adapter->intf.hw_ops_tbl;
+	struct nbl_common_info *common = &adapter->common;
+	struct device *dev = &adapter->pdev->dev;
+	struct nbl_channel_mgt *chan_mgt;
+	struct nbl_chan_info *mailbox;
+	int ret;
+
+	chan_mgt = devm_kzalloc(dev, sizeof(*chan_mgt), GFP_KERNEL);
+	if (!chan_mgt)
+		return ERR_PTR(-ENOMEM);
+
+	chan_mgt->common = common;
+	chan_mgt->hw_ops_tbl = hw_ops_tbl;
+
+	mailbox = devm_kzalloc(dev, sizeof(*mailbox), GFP_KERNEL);
+	if (!mailbox)
+		return ERR_PTR(-ENOMEM);
+	mailbox->chan_type = NBL_CHAN_TYPE_MAILBOX;
+	chan_mgt->chan_info[NBL_CHAN_TYPE_MAILBOX] = mailbox;
+
+	ret = nbl_chan_init_msg_handler(chan_mgt);
+	if (ret)
+		return ERR_PTR(-ENOMEM);
+
+	return chan_mgt;
+}
+
+static struct nbl_channel_ops_tbl *
+nbl_chan_setup_ops(struct device *dev, struct nbl_channel_mgt *chan_mgt)
+{
+	struct nbl_channel_ops_tbl *chan_ops_tbl;
+	int ret;
+
+	chan_ops_tbl = devm_kzalloc(dev, sizeof(*chan_ops_tbl), GFP_KERNEL);
+	if (!chan_ops_tbl)
+		return ERR_PTR(-ENOMEM);
+	if (!chan_ops.send_msg || !chan_ops.send_ack ||
+	    !chan_ops.register_msg || !chan_ops.unregister_all_msg ||
+	    !chan_ops.cfg_chan_qinfo_map_table ||
+	    !chan_ops.check_queue_exist || !chan_ops.setup_queue ||
+	    !chan_ops.teardown_queue || !chan_ops.clean_queue_subtask ||
+	    !chan_ops.register_chan_task || !chan_ops.set_queue_state)
+		return ERR_PTR(-EINVAL);
+
+	chan_ops_tbl->ops = &chan_ops;
+	chan_ops_tbl->priv = chan_mgt;
+
+	ret = nbl_chan_register_msg(chan_mgt, NBL_CHAN_MSG_ACK,
+				    nbl_chan_recv_ack_msg, chan_mgt);
+	if (ret)
+		return ERR_PTR(-ENOMEM);
+
+	return chan_ops_tbl;
+}
+
+int nbl_chan_init_common(struct nbl_adapter *adap)
+{
+	struct nbl_channel_ops_tbl *chan_ops_tbl;
+	struct device *dev = &adap->pdev->dev;
+	struct nbl_channel_mgt *chan_mgt;
+	int ret;
+
+	BUILD_BUG_ON(sizeof(struct nbl_chan_param_cfg_msix_map) != 8);
+	BUILD_BUG_ON(sizeof(struct nbl_chan_param_set_mailbox_irq) != 4);
+	BUILD_BUG_ON(sizeof(struct nbl_chan_param_get_vsi_id) != 4);
+	BUILD_BUG_ON(sizeof(struct nbl_chan_param_get_eth_id) != 8);
+	chan_mgt = nbl_chan_setup_chan_mgt(adap);
+	if (IS_ERR(chan_mgt)) {
+		ret = PTR_ERR(chan_mgt);
+		goto exit;
+	}
+
+	chan_ops_tbl = nbl_chan_setup_ops(dev, chan_mgt);
+	if (IS_ERR(chan_ops_tbl)) {
+		ret = PTR_ERR(chan_ops_tbl);
+		goto cleanup_mgt;
+	}
+	adap->intf.channel_ops_tbl = chan_ops_tbl;
+	adap->core.chan_mgt = chan_mgt;
+	return 0;
+
+cleanup_mgt:
+	nbl_chan_remove_msg_handler(chan_mgt);
+exit:
+	return ret;
+}
+
+void nbl_chan_remove_common(struct nbl_adapter *adap)
+{
+	struct nbl_channel_mgt *chan_mgt = adap->core.chan_mgt;
+
+	if (!chan_mgt)
+		return;
+
+	/*
+	 * All channel queues shall be torn down earlier in remove path
+	 * to drain inflight tx workers and stop hardware before destroying
+	 * message handler hash table.
+	 */
+	nbl_chan_remove_msg_handler(chan_mgt);
+	adap->core.chan_mgt = NULL;
+}
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.h
new file mode 100644
index 0000000000000..d59e660be9c79
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.h
@@ -0,0 +1,173 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_CHANNEL_H_
+#define _NBL_CHANNEL_H_
+
+#include <linux/types.h>
+
+#include "../nbl_include/nbl_include.h"
+#include "../nbl_include/nbl_def_channel.h"
+#include "../nbl_include/nbl_def_hw.h"
+#include "../nbl_include/nbl_def_common.h"
+#include "../nbl_core.h"
+
+#define NBL_CHAN_TX_RING_TO_DESC(tx_ring, i) \
+	(&((((tx_ring)->desc.tx_desc))[i]))
+#define NBL_CHAN_RX_RING_TO_DESC(rx_ring, i) \
+	(&((((rx_ring)->desc.rx_desc))[i]))
+#define NBL_CHAN_TX_RING_TO_BUF(tx_ring, i) (&(((tx_ring)->buf)[i]))
+#define NBL_CHAN_RX_RING_TO_BUF(rx_ring, i) (&(((rx_ring)->buf)[i]))
+
+#define NBL_CHAN_TX_WAIT_US			100
+#define NBL_CHAN_TX_WAIT_US_MAX			120
+#define NBL_CHAN_TX_WAIT_TIMES			100
+#define NBL_CHAN_TX_WAIT_ACK_US_MIN		1000
+#define NBL_CHAN_TX_WAIT_ACK_US_MAX		1200
+#define NBL_CHAN_TX_WAIT_ACK_TIMES		5000
+#define NBL_CHAN_QUEUE_LEN			256
+#define NBL_CHAN_BUF_LEN			4096
+#define NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN	16
+
+#define NBL_CHAN_TX_DESC_AVAIL			0
+#define NBL_CHAN_TX_DESC_USED			1
+#define NBL_CHAN_RX_DESC_WRITE			1
+#define NBL_CHAN_RX_DESC_AVAIL			3
+#define NBL_CHAN_RX_DESC_USED			4
+
+#define NBL_CHAN_ACK_HEAD_LEN			3
+#define NBL_CHAN_ACK_RET_POS			2
+#define NBL_CHAN_MSG_ID_POS			1
+#define NBL_CHAN_MSG_TYPE_POS			0
+
+#define NBL_CHAN_ACK_WAIT_TIME			(3 * HZ)
+
+#define NBL_CHAN_HANDLER_TBL_BUCKET_SIZE	512
+
+enum {
+	NBL_MB_RX_QID = 0,
+	NBL_MB_TX_QID = 1,
+};
+
+enum {
+	NBL_MBX_STATUS_IDLE = 0,
+	NBL_MBX_STATUS_WAITING,
+	NBL_MBX_STATUS_TIMEOUT,
+};
+
+struct nbl_chan_tx_param {
+	enum nbl_chan_msg_type msg_type;
+	void *arg;
+	size_t arg_len;
+	u16 dstid;
+	u16 msgid;
+};
+
+struct nbl_chan_buf {
+	void *va;
+	dma_addr_t pa;
+	size_t size;
+};
+
+struct nbl_chan_tx_desc {
+	__le16 flags;
+	__le16 srcid;
+	__le16 dstid;
+	__le16 data_len;
+	__le16 buf_len;
+	__le64 buf_addr;
+	__le16 msg_type;
+	u8 data[16];
+	__le16 msgid;
+	u8 rsv[26];
+} __packed;
+
+struct nbl_chan_rx_desc {
+	__le16 flags;
+	__le32 buf_len;
+	__le16 buf_id;
+	__le64 buf_addr;
+} __packed;
+
+union nbl_chan_desc_ptr {
+	struct nbl_chan_tx_desc *tx_desc;
+	struct nbl_chan_rx_desc *rx_desc;
+};
+
+struct nbl_chan_ring {
+	union nbl_chan_desc_ptr desc;
+	struct nbl_chan_buf *buf;
+	u16 next_to_use;
+	u16 tail_ptr; /* hardware does modulo ring size internally */
+	u16 next_to_clean;
+	dma_addr_t dma;
+};
+
+#define NBL_CHAN_MSG_INDEX_MAX 63
+
+#define NBL_CHAN_MSGID_INDEX_MASK GENMASK(5, 0)
+#define NBL_CHAN_MSGID_LOC_MASK GENMASK(13, 6)
+
+static inline void nbl_chan_update_tail_ptr(struct nbl_hw_ops *hw_ops,
+					    void *hw_priv, u32 tail_ptr, u8 qid)
+{
+	hw_ops->update_mailbox_queue_tail_ptr(hw_priv, tail_ptr, qid);
+}
+
+struct nbl_chan_waitqueue_head {
+	struct wait_queue_head wait_queue;
+	char *ack_data;
+	int acked;
+	s32 ack_err;
+	u16 ack_data_len;
+	u16 msg_type;
+	/*
+	 * Spinlock protecting all fields.
+	 * Must be held when reading/writing: status, acked, ack_err,
+	 * ack_data_len, etc.
+	 * The lock ensures atomic updates of these fields and
+	 * proper memory ordering with smp_wmb()/smp_rmb().
+	 */
+	spinlock_t status_lock;
+	int status;
+	u8 msg_index;
+	u16 dstid;
+};
+
+struct nbl_chan_info {
+	struct nbl_chan_ring txq;
+	struct nbl_chan_ring rxq;
+	struct nbl_chan_waitqueue_head *wait;
+	/*
+	 *Protects access to the TX queue (txq) and related metadata.
+	 *This mutex ensures exclusive access when updating the TX queue
+	 */
+	struct mutex txq_lock;
+	struct work_struct *clean_task;
+	u16 wait_head_index;
+	u16 num_txq_entries;
+	u16 num_rxq_entries;
+	u16 txq_buf_size;
+	u16 rxq_buf_size;
+	DECLARE_BITMAP(state, NBL_CHAN_STATE_NBITS);
+	u8 chan_type;
+	atomic_t inflight_tx_cnt;
+	bool shutdown;
+	bool init_done;
+};
+
+struct nbl_chan_msg_node_data {
+	nbl_chan_resp func;
+	void *priv;
+};
+
+struct nbl_channel_mgt {
+	struct nbl_common_info *common;
+	struct nbl_hw_ops_tbl *hw_ops_tbl;
+	struct nbl_chan_info *chan_info[NBL_CHAN_TYPE_MAX];
+	struct nbl_hash_tbl_mgt *handle_hash_tbl;
+};
+
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c
new file mode 100644
index 0000000000000..f10ef78ee7f64
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#include <linux/device.h>
+#include <linux/jhash.h>
+#include "nbl_common.h"
+
+void nbl_common_destroy_wq(struct nbl_common_info *common)
+{
+	destroy_workqueue(common->wq);
+}
+
+int nbl_common_create_wq(struct nbl_common_info *common)
+{
+	char wq_name[32];
+
+	snprintf(wq_name, sizeof(wq_name), "nbl_wq_%s", pci_name(common->pdev));
+	common->wq = alloc_workqueue("%s", WQ_UNBOUND, 0, wq_name);
+	if (!common->wq) {
+		pr_err("Failed to create workqueue nbl_wq\n");
+		goto alloc_wq_failed;
+	}
+
+	return 0;
+alloc_wq_failed:
+	return -ENOMEM;
+}
+
+/*
+ * nbl_common_func_id_to_rel_pf_id - convert absolute PF id to relative PF id
+ *
+ * Leonis currently uses fixed mgt_pf = 0. This helper maintains unified
+ * conversion logic for future platforms supporting non-zero management PF.
+ * Return: 0 on success, -EINVAL if pf_id < common->mgt_pf or rel_pf_id is NULL.
+ */
+int nbl_common_func_id_to_rel_pf_id(struct nbl_common_info *common, u32 pf_id,
+				    u32 *rel_pf_id)
+{
+	if (!rel_pf_id)
+		return -EINVAL;
+
+	if (pf_id < common->mgt_pf)
+		return -EINVAL;
+	*rel_pf_id = pf_id - common->mgt_pf;
+	return 0;
+}
+
+static u32 nbl_common_calc_hash_key(void *key, u32 key_size, u32 bucket_size)
+{
+	u32 hash;
+
+	if (bucket_size == 0 || bucket_size == NBL_HASH_TBL_LIST_BUCKET_SIZE)
+		return 0;
+
+	hash = jhash(key, key_size, 0);
+
+	/* Use bitmask if bucket_size is a power of 2 */
+	if ((bucket_size & (bucket_size - 1)) == 0)
+		return hash & (bucket_size - 1);
+	else
+		return hash % bucket_size;
+}
+
+/*
+ * alloc a hash table
+ * the table support multi thread
+ */
+struct nbl_hash_tbl_mgt *
+nbl_common_init_hash_table(struct nbl_hash_tbl_key *key)
+{
+	struct nbl_hash_tbl_mgt *tbl_mgt;
+	int bucket_size;
+	int i;
+
+	tbl_mgt = devm_kzalloc(key->dev, sizeof(*tbl_mgt), GFP_KERNEL);
+	if (!tbl_mgt)
+		return NULL;
+
+	bucket_size = key->bucket_size;
+	tbl_mgt->hash = devm_kcalloc(key->dev, bucket_size,
+				     sizeof(struct hlist_head), GFP_KERNEL);
+	if (!tbl_mgt->hash)
+		goto alloc_hash_failed;
+
+	for (i = 0; i < bucket_size; i++)
+		INIT_HLIST_HEAD(tbl_mgt->hash + i);
+
+	memcpy(&tbl_mgt->tbl_key, key, sizeof(struct nbl_hash_tbl_key));
+
+	return tbl_mgt;
+
+alloc_hash_failed:
+	return NULL;
+}
+
+/*
+ * Allocate a hash node and add to the hash table.
+ *
+ * Note: The hash table is protected by the caller's mutex (txq_lock),
+ * not lock-free. The "single context" comment refers to: this function
+ * is only called during init from nbl_chan_init_msg_handler, so there
+ * is no concurrent mutation during init.
+ * After init, the table is read-only (no unregister API), so no lock is needed
+ * for lookups either.
+ *
+ * The tbl_mgt and bucket array are devm-allocated, so they are automatically
+ * freed on device detach. Only the hash nodes themselves need explicit cleanup.
+ */
+int nbl_common_alloc_hash_node(struct nbl_hash_tbl_mgt *tbl_mgt, void *key,
+			       void *data, void **out_data)
+{
+	struct nbl_hash_entry_node *hash_node;
+	u16 data_size;
+	u16 node_size;
+	u32 hash_val;
+	u16 key_size;
+
+	node_size = sizeof(*hash_node);
+	hash_node = kzalloc(node_size, GFP_KERNEL);
+	if (!hash_node)
+		return -ENOMEM;
+
+	key_size = tbl_mgt->tbl_key.key_size;
+	hash_node->key = kzalloc(key_size, GFP_KERNEL);
+	if (!hash_node->key)
+		goto alloc_key_failed;
+
+	data_size = tbl_mgt->tbl_key.data_size;
+	hash_node->data = kzalloc(data_size, GFP_KERNEL);
+	if (!hash_node->data)
+		goto alloc_data_failed;
+
+	memcpy(hash_node->key, key, key_size);
+	memcpy(hash_node->data, data, data_size);
+
+	hash_val = nbl_common_calc_hash_key(key, key_size,
+					    tbl_mgt->tbl_key.bucket_size);
+
+	hlist_add_head(&hash_node->node, tbl_mgt->hash + hash_val);
+	tbl_mgt->node_num++;
+	if (out_data)
+		*out_data = hash_node->data;
+
+	return 0;
+
+alloc_data_failed:
+	kfree(hash_node->key);
+alloc_key_failed:
+	kfree(hash_node);
+	return -ENOMEM;
+}
+
+/*
+ * get a hash node, return the data if node exist
+ */
+void *nbl_common_get_hash_node(struct nbl_hash_tbl_mgt *tbl_mgt, void *key)
+{
+	struct nbl_hash_entry_node *hash_node;
+	struct hlist_head *head;
+	void *data = NULL;
+	u32 hash_val;
+	u16 key_size;
+
+	key_size = tbl_mgt->tbl_key.key_size;
+	hash_val = nbl_common_calc_hash_key(key, key_size,
+					    tbl_mgt->tbl_key.bucket_size);
+	head = tbl_mgt->hash + hash_val;
+
+	hlist_for_each_entry(hash_node, head, node)
+		if (!memcmp(hash_node->key, key, key_size)) {
+			data = hash_node->data;
+			break;
+		}
+
+	return data;
+}
+
+static void nbl_common_detach_hash_node(struct nbl_hash_tbl_mgt *tbl_mgt,
+					struct nbl_hash_entry_node *hash_node)
+{
+	hlist_del(&hash_node->node);
+	kfree(hash_node->key);
+	kfree(hash_node->data);
+	kfree(hash_node);
+	tbl_mgt->node_num--;
+}
+
+/*
+ * Free all hash nodes in the table.
+ */
+void nbl_common_remove_hash_table(struct nbl_hash_tbl_mgt *tbl_mgt)
+{
+	struct nbl_hash_entry_node *hash_node;
+	struct hlist_node *safe_node;
+	struct hlist_head *head;
+	u32 i;
+
+	for (i = 0; i < tbl_mgt->tbl_key.bucket_size; i++) {
+		head = tbl_mgt->hash + i;
+		hlist_for_each_entry_safe(hash_node, safe_node, head, node) {
+			nbl_common_detach_hash_node(tbl_mgt, hash_node);
+		}
+	}
+}
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.h
new file mode 100644
index 0000000000000..ad8fb7ec9f45d
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_COMMON_H_
+#define _NBL_COMMON_H_
+
+#include <linux/types.h>
+
+#include "../nbl_include/nbl_include.h"
+#include "../nbl_include/nbl_def_common.h"
+
+/* list only need one bucket size */
+#define NBL_HASH_TBL_LIST_BUCKET_SIZE 1
+
+struct nbl_hash_tbl_mgt {
+	struct nbl_hash_tbl_key tbl_key;
+	struct hlist_head *hash;
+	u16 node_num;
+};
+
+struct nbl_hash_entry_node {
+	struct hlist_node node;
+	void *key;
+	void *data;
+};
+
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h
new file mode 100644
index 0000000000000..a48bd9a0ef1ec
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_CORE_H_
+#define _NBL_CORE_H_
+
+#include <linux/pci.h>
+#include "nbl_include/nbl_include.h"
+#include "nbl_include/nbl_def_common.h"
+
+enum {
+	NBL_CAP_HAS_NET_BIT,
+};
+
+struct nbl_interface {
+	struct nbl_hw_ops_tbl *hw_ops_tbl;
+	struct nbl_resource_ops_tbl *resource_ops_tbl;
+	struct nbl_dispatch_ops_tbl *dispatch_ops_tbl;
+	struct nbl_channel_ops_tbl *channel_ops_tbl;
+};
+
+struct nbl_core {
+	struct nbl_hw_mgt *hw_mgt;
+	struct nbl_resource_mgt *res_mgt;
+	struct nbl_dispatch_mgt *disp_mgt;
+	struct nbl_dev_mgt *dev_mgt;
+	struct nbl_channel_mgt *chan_mgt;
+};
+
+struct nbl_adapter {
+	struct pci_dev *pdev;
+	struct nbl_core core;
+	struct nbl_interface intf;
+	struct nbl_common_info common;
+};
+
+struct nbl_adapter *nbl_core_init(struct pci_dev *pdev,
+				  struct nbl_init_param *param);
+void nbl_core_remove(struct nbl_adapter *adapter);
+int nbl_core_start(struct nbl_adapter *adapter);
+void nbl_core_stop(struct nbl_adapter *adapter);
+
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c
new file mode 100644
index 0000000000000..3a8b7ee2e4752
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c
@@ -0,0 +1,499 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+#include <linux/device.h>
+#include <linux/pci.h>
+#include "nbl_dev.h"
+
+static void nbl_dev_clean_mailbox_schedule(struct nbl_dev_mgt *dev_mgt);
+
+/* ----------  Interrupt config  ---------- */
+static irqreturn_t nbl_dev_clean_mailbox(int __always_unused irq, void *data)
+{
+	struct nbl_dev_mgt *dev_mgt = (struct nbl_dev_mgt *)data;
+
+	nbl_dev_clean_mailbox_schedule(dev_mgt);
+	return IRQ_HANDLED;
+}
+
+static void nbl_dev_init_msix_cnt(struct nbl_dev_mgt *dev_mgt)
+{
+	struct nbl_dev_common *dev_common = dev_mgt->common_dev;
+	struct nbl_msix_info *msix_info = &dev_common->msix_info;
+
+	msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].num = 1;
+}
+
+static int nbl_dev_request_mailbox_irq(struct nbl_dev_mgt *dev_mgt)
+{
+	struct nbl_dev_common *dev_common = dev_mgt->common_dev;
+	struct nbl_msix_info *msix_info = &dev_common->msix_info;
+	struct nbl_common_info *common = dev_mgt->common;
+	u16 local_vec_id;
+	int irq_num;
+	int err;
+
+	if (!msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].num)
+		return 0;
+
+	local_vec_id =
+		msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id;
+	irq_num = pci_irq_vector(common->pdev, local_vec_id);
+	if (irq_num < 0) {
+		dev_err(common->dev, "Failed to get mailbox IRQ vector: %d\n",
+			irq_num);
+		return irq_num;
+	}
+
+	snprintf(dev_common->mailbox_name, sizeof(dev_common->mailbox_name),
+		 "nbl_mailbox@pci:%s", pci_name(common->pdev));
+	err = request_irq(irq_num, nbl_dev_clean_mailbox, 0,
+			  dev_common->mailbox_name, dev_mgt);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static void nbl_dev_free_mailbox_irq(struct nbl_dev_mgt *dev_mgt)
+{
+	struct nbl_dev_common *dev_common = dev_mgt->common_dev;
+	struct nbl_msix_info *msix_info = &dev_common->msix_info;
+	struct nbl_common_info *common = dev_mgt->common;
+	u16 local_vec_id;
+	int irq_num;
+
+	if (!msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].num)
+		return;
+
+	local_vec_id =
+		msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id;
+	irq_num = pci_irq_vector(common->pdev, local_vec_id);
+	if (irq_num >= 0)
+		free_irq(irq_num, dev_mgt);
+}
+
+static int nbl_dev_enable_mailbox_irq(struct nbl_dev_mgt *dev_mgt)
+{
+	struct nbl_dispatch_ops *disp_ops = dev_mgt->disp_ops_tbl->ops;
+	struct nbl_channel_ops *chan_ops = dev_mgt->chan_ops_tbl->ops;
+	struct nbl_dev_common *dev_common = dev_mgt->common_dev;
+	struct nbl_msix_info *msix_info = &dev_common->msix_info;
+	u16 local_vec_id;
+
+	if (!msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].num)
+		return 0;
+
+	local_vec_id =
+		msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id;
+	chan_ops->set_queue_state(dev_mgt->chan_ops_tbl->priv,
+				  NBL_CHAN_INTERRUPT_READY,
+				  NBL_CHAN_TYPE_MAILBOX, true);
+
+	return disp_ops->set_mailbox_irq(dev_mgt->disp_ops_tbl->priv,
+					    local_vec_id, true);
+}
+
+static int nbl_dev_disable_mailbox_irq(struct nbl_dev_mgt *dev_mgt)
+{
+	struct nbl_dispatch_ops *disp_ops = dev_mgt->disp_ops_tbl->ops;
+	struct nbl_channel_ops *chan_ops = dev_mgt->chan_ops_tbl->ops;
+	struct nbl_dev_common *dev_common = dev_mgt->common_dev;
+	struct nbl_msix_info *msix_info = &dev_common->msix_info;
+	u16 local_vec_id;
+
+	if (!msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].num)
+		return 0;
+
+	local_vec_id =
+		msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id;
+	/*
+	 * Disable sequence invariant: update software state first, then mask
+	 * hardware interrupt. Must not reverse the order.
+	 *
+	 * If hardware interrupt is masked before clearing INTERRUPT_READY,
+	 * the hardware may still transmit outstanding ACK packets for in-flight
+	 * messages. Subsequent switch to polling mode discards pending ACK
+	 * processing, triggering "Channel waiting ack failed" and "Skip ack
+	 * with invalid status" errors.
+	 *
+	 * By entering polling mode first, any late hardware interrupts are
+	 * ignored without pending ACK expectations, then hardware interrupt
+	 * can be safely disabled.
+	 *
+	 * This helper is invoked in two paths:
+	 * 1. Error unwind path of nbl_dev_start(): immediately followed by
+	 *    nbl_dev_free_mailbox_irq(), channel resources
+	 *    are fully torn down afterwards, no stale descriptors remain.
+	 * 2. Normal device stop path nbl_dev_stop(): free_irq() synchronously
+	 *    waits for any in-flight threaded irq bottom-half handler to
+	 *    finish execution, all pending mailbox cleanup work completes
+	 *    before channel teardown in late remove stage. No stuck
+	 *    descriptors will linger in either scenario.
+	 */
+	chan_ops->set_queue_state(dev_mgt->chan_ops_tbl->priv,
+				  NBL_CHAN_INTERRUPT_READY,
+				  NBL_CHAN_TYPE_MAILBOX, false);
+
+	return disp_ops->set_mailbox_irq(dev_mgt->disp_ops_tbl->priv,
+					    local_vec_id, false);
+}
+
+static int nbl_dev_cfg_msix_map(struct nbl_dev_mgt *dev_mgt)
+{
+	struct nbl_dispatch_ops *disp_ops = dev_mgt->disp_ops_tbl->ops;
+	struct nbl_dev_common *dev_common = dev_mgt->common_dev;
+	struct nbl_msix_info *msix_info = &dev_common->msix_info;
+	bool mask_en = msix_info->serv_info[NBL_MSIX_NET_TYPE].hw_self_mask_en;
+	u16 msix_net_num = msix_info->serv_info[NBL_MSIX_NET_TYPE].num;
+	u16 msix_not_net_num = 0;
+	int err, i;
+
+	msix_info->serv_info[NBL_MSIX_VIRTIO_TYPE].base_vector_id = 0;
+	for (i = NBL_MSIX_NET_TYPE; i < NBL_MSIX_TYPE_MAX; i++)
+		msix_info->serv_info[i].base_vector_id =
+			msix_info->serv_info[i - 1].base_vector_id +
+			msix_info->serv_info[i - 1].num;
+
+	for (i = 0; i < NBL_MSIX_TYPE_MAX; i++) {
+		if (i == NBL_MSIX_NET_TYPE)
+			continue;
+		msix_not_net_num += msix_info->serv_info[i].num;
+	}
+
+	err = disp_ops->cfg_msix_map(dev_mgt->disp_ops_tbl->priv,
+					   msix_net_num, msix_not_net_num,
+					   mask_en);
+
+	return err;
+}
+
+static int nbl_dev_destroy_msix_map(struct nbl_dev_mgt *dev_mgt)
+{
+	struct nbl_dispatch_ops *disp_ops = dev_mgt->disp_ops_tbl->ops;
+
+	return disp_ops->destroy_msix_map(dev_mgt->disp_ops_tbl->priv);
+}
+
+static int nbl_dev_init_interrupt_scheme(struct nbl_dev_mgt *dev_mgt)
+{
+	struct nbl_dev_common *dev_common = dev_mgt->common_dev;
+	struct nbl_msix_info *msix_info = &dev_common->msix_info;
+	struct nbl_common_info *common = dev_mgt->common;
+	int needed = 0;
+	int err;
+	int i;
+
+	for (i = 0; i < NBL_MSIX_TYPE_MAX; i++)
+		needed += msix_info->serv_info[i].num;
+
+	err = pci_alloc_irq_vectors(common->pdev, needed, needed,
+				    PCI_IRQ_MSIX | PCI_IRQ_AFFINITY);
+	if (err < 0) {
+		dev_err(common->dev,
+			"pci_alloc_irq_vectors failed, err = %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static void nbl_dev_clear_interrupt_scheme(struct nbl_dev_mgt *dev_mgt)
+{
+	struct nbl_common_info *common = dev_mgt->common;
+
+	pci_free_irq_vectors(common->pdev);
+}
+
+/* ----------  Channel config  ---------- */
+static void nbl_dev_setup_chan_qinfo(struct nbl_dev_mgt *dev_mgt, u8 chan_type)
+{
+	struct nbl_channel_ops *chan_ops = dev_mgt->chan_ops_tbl->ops;
+	struct nbl_channel_mgt *priv = dev_mgt->chan_ops_tbl->priv;
+	struct nbl_common_info *common = dev_mgt->common;
+
+	if (!chan_ops->check_queue_exist(priv, chan_type))
+		return;
+
+	chan_ops->cfg_chan_qinfo_map_table(priv, common->hw_bus, common->devid);
+}
+
+static int nbl_dev_setup_chan_queue(struct nbl_dev_mgt *dev_mgt, u8 chan_type)
+{
+	struct nbl_channel_ops *chan_ops = dev_mgt->chan_ops_tbl->ops;
+	struct nbl_channel_mgt *priv = dev_mgt->chan_ops_tbl->priv;
+	int ret = 0;
+
+	if (chan_ops->check_queue_exist(priv, chan_type))
+		ret = chan_ops->setup_queue(priv, chan_type);
+
+	return ret;
+}
+
+static int nbl_dev_remove_chan_queue(struct nbl_dev_mgt *dev_mgt, u8 chan_type)
+{
+	struct nbl_channel_ops *chan_ops = dev_mgt->chan_ops_tbl->ops;
+	struct nbl_channel_mgt *priv = dev_mgt->chan_ops_tbl->priv;
+	int ret = 0;
+
+	if (chan_ops->check_queue_exist(priv, chan_type))
+		ret = chan_ops->teardown_queue(priv, chan_type);
+
+	return ret;
+}
+
+static void nbl_dev_register_chan_task(struct nbl_dev_mgt *dev_mgt,
+				       u8 chan_type, struct work_struct *task)
+{
+	struct nbl_channel_ops *chan_ops = dev_mgt->chan_ops_tbl->ops;
+
+	if (chan_ops->check_queue_exist(dev_mgt->chan_ops_tbl->priv, chan_type))
+		chan_ops->register_chan_task(dev_mgt->chan_ops_tbl->priv,
+					     chan_type, task);
+}
+
+/* ----------  Tasks config  ---------- */
+static void nbl_dev_clean_mailbox_task(struct work_struct *work)
+{
+	struct nbl_dev_common *common_dev =
+		container_of(work, struct nbl_dev_common, clean_mbx_task);
+	struct nbl_dev_mgt *dev_mgt = common_dev->dev_mgt;
+	struct nbl_channel_ops *chan_ops = dev_mgt->chan_ops_tbl->ops;
+
+	chan_ops->clean_queue_subtask(dev_mgt->chan_ops_tbl->priv,
+				      NBL_CHAN_TYPE_MAILBOX);
+}
+
+static void nbl_dev_clean_mailbox_schedule(struct nbl_dev_mgt *dev_mgt)
+{
+	struct nbl_dev_common *common_dev = dev_mgt->common_dev;
+	struct nbl_common_info *common = dev_mgt->common;
+
+	queue_work(common->wq, &common_dev->clean_mbx_task);
+}
+
+/* ----------  Dev init process  ---------- */
+static int nbl_dev_setup_common_dev(struct nbl_adapter *adapter)
+{
+	struct nbl_dev_mgt *dev_mgt = adapter->core.dev_mgt;
+	struct nbl_dispatch_ops *disp_ops = dev_mgt->disp_ops_tbl->ops;
+	struct nbl_dispatch_mgt *priv = dev_mgt->disp_ops_tbl->priv;
+	struct nbl_common_info *common = dev_mgt->common;
+	struct nbl_dev_common *common_dev;
+	int ret;
+
+	common_dev = devm_kzalloc(&adapter->pdev->dev, sizeof(*common_dev),
+				  GFP_KERNEL);
+	if (!common_dev)
+		return -ENOMEM;
+	common_dev->dev_mgt = dev_mgt;
+
+	ret = nbl_dev_setup_chan_queue(dev_mgt, NBL_CHAN_TYPE_MAILBOX);
+	if (ret)
+		return ret;
+
+	INIT_WORK(&common_dev->clean_mbx_task, nbl_dev_clean_mailbox_task);
+	nbl_dev_register_chan_task(dev_mgt, NBL_CHAN_TYPE_MAILBOX,
+				   &common_dev->clean_mbx_task);
+	/*
+	 * Even if has_ctrl=false (no dedicated control PF channel), we fetch
+	 * VSI/ETH info via regular mailbox message  instead of
+	 * dedicated control command.
+	 */
+	ret = disp_ops->get_vsi_id(priv, NBL_VSI_DATA, &common->vsi_id);
+	if (ret)
+		goto err_cleanup;
+	ret = disp_ops->get_eth_id(priv, common->vsi_id, &common->eth_num,
+			     &common->eth_id, &common->logic_eth_id);
+	if (ret)
+		goto err_cleanup;
+
+	dev_mgt->common_dev = common_dev;
+	nbl_dev_init_msix_cnt(dev_mgt);
+
+	return 0;
+err_cleanup:
+	cancel_work_sync(&common_dev->clean_mbx_task);
+	nbl_dev_register_chan_task(dev_mgt, NBL_CHAN_TYPE_MAILBOX, NULL);
+	nbl_dev_remove_chan_queue(dev_mgt, NBL_CHAN_TYPE_MAILBOX);
+	return ret;
+}
+
+static void nbl_dev_remove_common_dev(struct nbl_adapter *adapter)
+{
+	struct nbl_dev_mgt *dev_mgt = adapter->core.dev_mgt;
+	struct nbl_dev_common *common_dev = dev_mgt->common_dev;
+
+	if (!common_dev)
+		return;
+	nbl_dev_remove_chan_queue(dev_mgt, NBL_CHAN_TYPE_MAILBOX);
+	cancel_work_sync(&common_dev->clean_mbx_task);
+	nbl_dev_register_chan_task(dev_mgt, NBL_CHAN_TYPE_MAILBOX, NULL);
+}
+
+static int nbl_dev_setup_ctrl_dev(struct nbl_adapter *adapter)
+{
+	struct nbl_dev_mgt *dev_mgt = adapter->core.dev_mgt;
+	struct nbl_dispatch_ops *disp_ops = dev_mgt->disp_ops_tbl->ops;
+	int ret;
+
+	ret = disp_ops->init_module(dev_mgt->disp_ops_tbl->priv);
+	if (ret)
+		return ret;
+
+	nbl_dev_setup_chan_qinfo(dev_mgt, NBL_CHAN_TYPE_MAILBOX);
+
+	return 0;
+}
+
+/*
+ * This is intentional.  The qinfo registers are managed by the chip
+ * firmware, not by the driver.  Setting driver status to false is the
+ * designed teardown mechanism — it notifies the firmware, which then
+ * performs its own cleanup of all per-PF state including the qinfo
+ * registers.
+ * An inverse helper would duplicate work that the firmware already
+ * does, and would add error-path complexity for no benefit.  We keep
+ * the deinit path minimal and rely on the firmware cleanup for
+ * correctness, including in abnormal reset scenarios.
+ */
+static void nbl_dev_remove_ctrl_dev(struct nbl_adapter *adapter)
+{
+	struct nbl_dev_mgt *dev_mgt = adapter->core.dev_mgt;
+	struct nbl_dispatch_ops *disp_ops = dev_mgt->disp_ops_tbl->ops;
+
+	disp_ops->deinit_module(dev_mgt->disp_ops_tbl->priv);
+}
+
+static struct nbl_dev_mgt *nbl_dev_setup_dev_mgt(struct nbl_common_info *common)
+{
+	struct nbl_dev_mgt *dev_mgt;
+
+	dev_mgt = devm_kzalloc(common->dev, sizeof(*dev_mgt), GFP_KERNEL);
+	if (!dev_mgt)
+		return ERR_PTR(-ENOMEM);
+
+	dev_mgt->common = common;
+	return dev_mgt;
+}
+
+int nbl_dev_init(struct nbl_adapter *adapter)
+{
+	struct nbl_common_info *common = &adapter->common;
+	struct nbl_dispatch_ops_tbl *disp_ops_tbl =
+		adapter->intf.dispatch_ops_tbl;
+	struct nbl_channel_ops_tbl *chan_ops_tbl =
+		adapter->intf.channel_ops_tbl;
+	struct nbl_dev_mgt *dev_mgt;
+	int ret;
+
+	dev_mgt = nbl_dev_setup_dev_mgt(common);
+	if (IS_ERR(dev_mgt)) {
+		ret = PTR_ERR(dev_mgt);
+		return ret;
+	}
+
+	dev_mgt->disp_ops_tbl = disp_ops_tbl;
+	dev_mgt->chan_ops_tbl = chan_ops_tbl;
+	adapter->core.dev_mgt = dev_mgt;
+	ret = nbl_common_create_wq(common);
+	if (ret)
+		return ret;
+	/*
+	 * Chip hardware initialization is completed by firmware at power-up.
+	 * Only driver functional table/register config follows here, safe to
+	 * access hardware registers before ctrl dev setup.
+	 */
+	ret = nbl_dev_setup_common_dev(adapter);
+	if (ret)
+		goto setup_err;
+
+	if (common->has_ctrl) {
+		ret = nbl_dev_setup_ctrl_dev(adapter);
+		if (ret)
+			goto setup_ctrl_dev_fail;
+	}
+
+	return 0;
+setup_ctrl_dev_fail:
+	nbl_dev_remove_common_dev(adapter);
+setup_err:
+	nbl_common_destroy_wq(common);
+	return ret;
+}
+
+/*
+ * Teardown order: ctrl dev first, then common dev.
+ * nbl_dev_remove_ctrl_dev() notifies firmware to clean all per-PF state
+ * (including qinfo registers), so subsequent common dev queue cleanup
+ * will not trigger PCIe master abort or invalid register access.
+ */
+void nbl_dev_remove(struct nbl_adapter *adapter)
+{
+	struct nbl_common_info *common = &adapter->common;
+
+	if (common->has_ctrl)
+		nbl_dev_remove_ctrl_dev(adapter);
+	nbl_dev_remove_common_dev(adapter);
+	nbl_common_destroy_wq(common);
+}
+
+/* ----------  Dev start process  ---------- */
+int nbl_dev_start(struct nbl_adapter *adapter)
+{
+	struct nbl_dev_mgt *dev_mgt = adapter->core.dev_mgt;
+	struct nbl_dev_common *common_dev = dev_mgt->common_dev;
+	int cleanup_ret;
+	int ret;
+
+	ret = nbl_dev_cfg_msix_map(dev_mgt);
+	if (ret)
+		return ret;
+
+	ret = nbl_dev_init_interrupt_scheme(dev_mgt);
+	if (ret)
+		goto init_interrupt_scheme_err;
+	ret = nbl_dev_request_mailbox_irq(dev_mgt);
+	if (ret)
+		goto mailbox_request_irq_err;
+	ret = nbl_dev_enable_mailbox_irq(dev_mgt);
+	if (ret)
+		goto enable_mailbox_irq_err;
+
+	return 0;
+enable_mailbox_irq_err:
+	cleanup_ret = nbl_dev_disable_mailbox_irq(dev_mgt);
+	if (cleanup_ret)
+		dev_err(dev_mgt->common->dev,
+			"Failed to disable mailbox IRQ: %d\n", cleanup_ret);
+	nbl_dev_free_mailbox_irq(dev_mgt);
+	cancel_work_sync(&common_dev->clean_mbx_task);
+mailbox_request_irq_err:
+	nbl_dev_clear_interrupt_scheme(dev_mgt);
+init_interrupt_scheme_err:
+	cleanup_ret =  nbl_dev_destroy_msix_map(dev_mgt);
+	if (cleanup_ret)
+		dev_err(dev_mgt->common->dev,
+			"Failed to destroy MSI-X map: %d\n", cleanup_ret);
+	return ret;
+}
+
+void nbl_dev_stop(struct nbl_adapter *adapter)
+{
+	struct nbl_dev_mgt *dev_mgt = adapter->core.dev_mgt;
+	struct nbl_dev_common *common_dev = dev_mgt->common_dev;
+	int ret;
+
+	ret = nbl_dev_disable_mailbox_irq(dev_mgt);
+	if (ret)
+		dev_err(dev_mgt->common->dev,
+			"Failed to disable mailbox IRQ: %d\n", ret);
+	nbl_dev_free_mailbox_irq(dev_mgt);
+	cancel_work_sync(&common_dev->clean_mbx_task);
+	nbl_dev_clear_interrupt_scheme(dev_mgt);
+	ret = nbl_dev_destroy_msix_map(dev_mgt);
+	if (ret)
+		dev_err(dev_mgt->common->dev,
+			"Failed to destroy MSI-X map: %d\n", ret);
+}
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.h
new file mode 100644
index 0000000000000..7666ab9a73f3f
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_DEV_H_
+#define _NBL_DEV_H_
+
+#include <linux/types.h>
+
+#include "../nbl_include/nbl_include.h"
+#include "../nbl_include/nbl_def_channel.h"
+#include "../nbl_include/nbl_def_hw.h"
+#include "../nbl_include/nbl_def_resource.h"
+#include "../nbl_include/nbl_def_dispatch.h"
+#include "../nbl_include/nbl_def_dev.h"
+#include "../nbl_include/nbl_def_common.h"
+#include "../nbl_core.h"
+
+#define NBL_STRING_NAME_LEN			32
+
+enum nbl_msix_serv_type {
+	/* virtio_dev has a config vector_id, and the vector_id need is 0 */
+	NBL_MSIX_VIRTIO_TYPE = 0,
+	NBL_MSIX_NET_TYPE,
+	NBL_MSIX_MAILBOX_TYPE,
+	NBL_MSIX_TYPE_MAX
+};
+
+struct nbl_msix_serv_info {
+	char irq_name[NBL_STRING_NAME_LEN];
+	u16 num;
+	u16 base_vector_id;
+	/* true: hw report msix, hw need to mask actively */
+	bool hw_self_mask_en;
+};
+
+struct nbl_msix_info {
+	struct nbl_msix_serv_info serv_info[NBL_MSIX_TYPE_MAX];
+};
+
+struct nbl_dev_common {
+	struct nbl_dev_mgt *dev_mgt;
+	struct nbl_msix_info msix_info;
+	char mailbox_name[NBL_STRING_NAME_LEN];
+	/* for ctrl-dev/net-dev mailbox recv msg */
+	struct work_struct clean_mbx_task;
+};
+
+struct nbl_dev_mgt {
+	struct nbl_common_info *common;
+	struct nbl_dispatch_ops_tbl *disp_ops_tbl;
+	struct nbl_channel_ops_tbl *chan_ops_tbl;
+	struct nbl_dev_common *common_dev;
+};
+
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c
new file mode 100644
index 0000000000000..3fef901c8e25f
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c
@@ -0,0 +1,633 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include "nbl_dispatch.h"
+
+static int nbl_disp_chan_get_vsi_id_req(struct nbl_dispatch_mgt *disp_mgt,
+					u16 type, u16 *vsi_id)
+{
+	struct nbl_channel_ops *chan_ops = disp_mgt->chan_ops_tbl->ops;
+	struct nbl_common_info *common = disp_mgt->common;
+	struct nbl_chan_param_get_vsi_id result = { 0 };
+	struct nbl_chan_param_get_vsi_id param = { 0 };
+	struct nbl_chan_send_info chan_send;
+	int ret;
+
+	param.type = cpu_to_le16(type);
+
+	nbl_chan_fill_send_info(&chan_send, common->mgt_pf,
+				NBL_CHAN_MSG_GET_VSI_ID,
+				&param, sizeof(param), &result,
+				sizeof(result), 1);
+	ret = chan_ops->send_msg(disp_mgt->chan_ops_tbl->priv, &chan_send);
+	switch (ret) {
+	case NBL_CHAN_RESP_UNIMPLEMENTED:
+		return -EOPNOTSUPP;
+	case NBL_CHAN_RESP_ERR:
+		return -EREMOTEIO;
+	case NBL_CHAN_RESP_OK:
+		break;
+	default:
+		return ret;
+	}
+	*vsi_id = le16_to_cpu(result.vsi_id);
+	return 0;
+}
+
+static void nbl_disp_chan_get_vsi_id_resp(void *priv, u16 src_id, u16 msg_id,
+					  void *data, u32 data_len)
+{
+	struct nbl_dispatch_mgt *disp_mgt = (struct nbl_dispatch_mgt *)priv;
+	struct nbl_channel_ops *chan_ops = disp_mgt->chan_ops_tbl->ops;
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+	struct device *dev = disp_mgt->common->dev;
+	struct nbl_chan_param_get_vsi_id result = { 0 };
+	struct nbl_chan_param_get_vsi_id param = { 0 };
+	struct nbl_chan_ack_info chan_ack;
+	int err = NBL_CHAN_RESP_OK;
+	u16 vsi_id = 0;
+	int copy_len;
+	int ret;
+
+	if (data_len == 0) {
+		err = NBL_CHAN_RESP_ERR;
+		goto ack_out;
+	}
+	copy_len = min_t(size_t, data_len, sizeof(param));
+	memcpy(&param, data, copy_len);
+
+	if (res_ops->get_vsi_id) {
+		ret = res_ops->get_vsi_id(p, src_id, le16_to_cpu(param.type),
+					  &vsi_id);
+		if (ret)
+			err = NBL_CHAN_RESP_ERR;
+	} else {
+		err = NBL_CHAN_RESP_UNIMPLEMENTED;
+	}
+
+	result.vsi_id = cpu_to_le16(vsi_id);
+ack_out:
+	nbl_chan_fill_ack_info(&chan_ack, src_id,
+			       NBL_CHAN_MSG_GET_VSI_ID, msg_id, err,
+			       &result, sizeof(result));
+	ret = chan_ops->send_ack(disp_mgt->chan_ops_tbl->priv, &chan_ack);
+	if (ret)
+		dev_err(dev,
+			"channel send ack failed with ret: %d, msg_type: %d\n",
+			ret, NBL_CHAN_MSG_GET_VSI_ID);
+}
+
+static int nbl_disp_chan_get_eth_id_req(struct nbl_dispatch_mgt *disp_mgt,
+					u16 vsi_id, u8 *eth_num, u8 *eth_id,
+					u8 *logic_eth_id)
+{
+	struct nbl_channel_ops *chan_ops = disp_mgt->chan_ops_tbl->ops;
+	struct nbl_common_info *common = disp_mgt->common;
+	struct nbl_chan_param_get_eth_id result = { 0 };
+	struct nbl_chan_param_get_eth_id param = { 0 };
+	struct nbl_chan_send_info chan_send;
+	int ret;
+
+	param.vsi_id = cpu_to_le16(vsi_id);
+
+	nbl_chan_fill_send_info(&chan_send, common->mgt_pf,
+				NBL_CHAN_MSG_GET_ETH_ID,
+				&param, sizeof(param), &result,
+				sizeof(result), 1);
+	ret = chan_ops->send_msg(disp_mgt->chan_ops_tbl->priv, &chan_send);
+	switch (ret) {
+	case NBL_CHAN_RESP_UNIMPLEMENTED:
+		return -EOPNOTSUPP;
+	case NBL_CHAN_RESP_ERR:
+		return -EREMOTEIO;
+	case NBL_CHAN_RESP_OK:
+		break;
+	default:
+		return ret;
+	}
+	*eth_num = result.eth_num;
+	*eth_id = result.eth_id;
+	*logic_eth_id = result.logic_eth_id;
+
+	return 0;
+}
+
+static void nbl_disp_chan_get_eth_id_resp(void *priv, u16 src_id, u16 msg_id,
+					  void *data, u32 data_len)
+{
+	struct nbl_dispatch_mgt *disp_mgt = (struct nbl_dispatch_mgt *)priv;
+	struct nbl_channel_ops *chan_ops = disp_mgt->chan_ops_tbl->ops;
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+	struct nbl_chan_param_get_eth_id result = { 0 };
+	struct nbl_chan_param_get_eth_id param = { 0 };
+	struct device *dev = disp_mgt->common->dev;
+	struct nbl_chan_ack_info chan_ack;
+	int err = NBL_CHAN_RESP_OK;
+	int copy_len;
+	int ret;
+
+	if (data_len == 0) {
+		err = NBL_CHAN_RESP_ERR;
+		goto ack_out;
+	}
+	copy_len = min_t(size_t, data_len, sizeof(param));
+	memcpy(&param, data, copy_len);
+
+	if (res_ops->get_eth_id) {
+		ret = res_ops->get_eth_id(p, src_id, le16_to_cpu(param.vsi_id),
+					  &result.eth_num, &result.eth_id,
+					  &result.logic_eth_id);
+		if (ret)
+			err = NBL_CHAN_RESP_ERR;
+	} else {
+		err = NBL_CHAN_RESP_UNIMPLEMENTED;
+	}
+ack_out:
+	nbl_chan_fill_ack_info(&chan_ack, src_id,
+			       NBL_CHAN_MSG_GET_ETH_ID, msg_id, err,
+			       &result, sizeof(result));
+	ret = chan_ops->send_ack(disp_mgt->chan_ops_tbl->priv, &chan_ack);
+	if (ret)
+		dev_err(dev,
+			"channel send ack failed with ret: %d, msg_type: %d\n",
+			ret, NBL_CHAN_MSG_GET_ETH_ID);
+}
+
+static void nbl_disp_deinit_module(struct nbl_dispatch_mgt *disp_mgt)
+{
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+
+	if (res_ops->deinit_module)
+		res_ops->deinit_module(p);
+}
+
+static int nbl_disp_init_module(struct nbl_dispatch_mgt *disp_mgt)
+{
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+
+	if (res_ops->init_module)
+		return res_ops->init_module(p);
+	return -EOPNOTSUPP;
+}
+
+static int nbl_disp_cfg_msix_map(struct nbl_dispatch_mgt *disp_mgt,
+				 u16 num_net_msix, u16 num_others_msix,
+				 bool net_msix_mask_en)
+{
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+	struct nbl_common_info *common = disp_mgt->common;
+	int ret;
+
+	if (!res_ops->cfg_msix_map)
+		return -EOPNOTSUPP;
+	mutex_lock(&disp_mgt->ops_mutex_lock);
+	ret = res_ops->cfg_msix_map(p, common->mgt_pf, num_net_msix,
+					  num_others_msix, net_msix_mask_en);
+	mutex_unlock(&disp_mgt->ops_mutex_lock);
+	return ret;
+}
+
+static int
+nbl_disp_chan_cfg_msix_map_req(struct nbl_dispatch_mgt *disp_mgt,
+			       u16 num_net_msix, u16 num_others_msix,
+			       bool net_msix_mask_en)
+{
+	struct nbl_channel_ops *chan_ops = disp_mgt->chan_ops_tbl->ops;
+	struct nbl_common_info *common = disp_mgt->common;
+	struct nbl_chan_param_cfg_msix_map param = { 0 };
+	struct nbl_chan_send_info chan_send;
+	int ret;
+
+	param.num_net_msix = cpu_to_le16(num_net_msix);
+	param.num_others_msix = cpu_to_le16(num_others_msix);
+	param.msix_mask_en = cpu_to_le16(!!net_msix_mask_en);
+
+	nbl_chan_fill_send_info(&chan_send, common->mgt_pf,
+				NBL_CHAN_MSG_CONFIGURE_MSIX_MAP,
+				&param, sizeof(param),
+				NULL, 0, 1);
+	ret = chan_ops->send_msg(disp_mgt->chan_ops_tbl->priv, &chan_send);
+	switch (ret) {
+	case NBL_CHAN_RESP_UNIMPLEMENTED:
+		return -EOPNOTSUPP;
+	case NBL_CHAN_RESP_PERM_DENY:
+		return -EPERM;
+	case NBL_CHAN_RESP_ERR:
+		return -EREMOTEIO;
+	case NBL_CHAN_RESP_OK:
+		return ret;
+	default:
+		return ret;
+	}
+}
+
+static void nbl_disp_chan_cfg_msix_map_resp(void *priv, u16 src_id, u16 msg_id,
+					    void *data, u32 data_len)
+{
+	struct nbl_dispatch_mgt *disp_mgt = (struct nbl_dispatch_mgt *)priv;
+	struct nbl_channel_ops *chan_ops = disp_mgt->chan_ops_tbl->ops;
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+	struct device *dev = disp_mgt->common->dev;
+	struct nbl_chan_param_cfg_msix_map param = { 0 };
+	struct nbl_chan_ack_info chan_ack;
+	int err = NBL_CHAN_RESP_OK;
+	int copy_len;
+	int ret;
+
+	if (src_id > NBL_MAX_PF_SRC_ID) {
+		err = NBL_CHAN_RESP_PERM_DENY;
+		goto ack_out;
+	}
+	if (data_len == 0) {
+		err = NBL_CHAN_RESP_ERR;
+		goto ack_out;
+	}
+	copy_len = min_t(size_t, data_len, sizeof(param));
+	memcpy(&param, data, copy_len);
+
+	if (res_ops->cfg_msix_map) {
+		mutex_lock(&disp_mgt->ops_mutex_lock);
+		ret = res_ops->cfg_msix_map(p, src_id,
+					    le16_to_cpu(param.num_net_msix),
+					    le16_to_cpu(param.num_others_msix),
+					    !!le16_to_cpu(param.msix_mask_en));
+		mutex_unlock(&disp_mgt->ops_mutex_lock);
+		if (ret)
+			err = NBL_CHAN_RESP_ERR;
+	} else {
+		err = NBL_CHAN_RESP_UNIMPLEMENTED;
+	}
+ack_out:
+	nbl_chan_fill_ack_info(&chan_ack, src_id,
+			       NBL_CHAN_MSG_CONFIGURE_MSIX_MAP, msg_id,
+			       err, NULL, 0);
+	ret = chan_ops->send_ack(disp_mgt->chan_ops_tbl->priv, &chan_ack);
+	if (ret)
+		dev_err(dev,
+			"channel send ack failed with ret: %d, msg_type: %d\n",
+			ret, NBL_CHAN_MSG_CONFIGURE_MSIX_MAP);
+}
+
+static int nbl_disp_chan_destroy_msix_map_req(struct nbl_dispatch_mgt *disp_mgt)
+{
+	struct nbl_channel_ops *chan_ops = disp_mgt->chan_ops_tbl->ops;
+	struct nbl_common_info *common = disp_mgt->common;
+	struct nbl_chan_send_info chan_send;
+	int ret;
+
+	nbl_chan_fill_send_info(&chan_send, common->mgt_pf,
+				NBL_CHAN_MSG_DESTROY_MSIX_MAP,
+				NULL, 0, NULL, 0, 1);
+	ret = chan_ops->send_msg(disp_mgt->chan_ops_tbl->priv, &chan_send);
+	switch (ret) {
+	case NBL_CHAN_RESP_UNIMPLEMENTED:
+		return -EOPNOTSUPP;
+	case NBL_CHAN_RESP_PERM_DENY:
+		return -EPERM;
+	case NBL_CHAN_RESP_ERR:
+		return -EREMOTEIO;
+	case NBL_CHAN_RESP_OK:
+		return ret;
+	default:
+		return ret;
+	}
+}
+
+static void nbl_disp_chan_destroy_msix_map_resp(void *priv, u16 src_id,
+						u16 msg_id, void *data,
+						u32 data_len)
+{
+	struct nbl_dispatch_mgt *disp_mgt = (struct nbl_dispatch_mgt *)priv;
+	struct nbl_channel_ops *chan_ops = disp_mgt->chan_ops_tbl->ops;
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+	struct device *dev = disp_mgt->common->dev;
+	struct nbl_chan_ack_info chan_ack;
+	int err = NBL_CHAN_RESP_OK;
+	int ret;
+
+	if (src_id > NBL_MAX_PF_SRC_ID) {
+		err = NBL_CHAN_RESP_PERM_DENY;
+		goto ack_out;
+	}
+	if (res_ops->destroy_msix_map) {
+		mutex_lock(&disp_mgt->ops_mutex_lock);
+		ret = res_ops->destroy_msix_map(p, src_id);
+		mutex_unlock(&disp_mgt->ops_mutex_lock);
+		if (ret)
+			err = NBL_CHAN_RESP_ERR;
+	} else {
+		err = NBL_CHAN_RESP_UNIMPLEMENTED;
+	}
+ack_out:
+	nbl_chan_fill_ack_info(&chan_ack, src_id,
+			       NBL_CHAN_MSG_DESTROY_MSIX_MAP, msg_id,
+			       err, NULL, 0);
+	ret = chan_ops->send_ack(disp_mgt->chan_ops_tbl->priv, &chan_ack);
+	if (ret)
+		dev_err(dev,
+			"channel send ack failed with ret: %d, msg_type: %d\n",
+			ret, NBL_CHAN_MSG_DESTROY_MSIX_MAP);
+}
+
+static int nbl_disp_chan_set_mailbox_irq_req(struct nbl_dispatch_mgt *disp_mgt,
+					     u16 vector_id, bool en_msix)
+{
+	struct nbl_channel_ops *chan_ops = disp_mgt->chan_ops_tbl->ops;
+	struct nbl_chan_param_set_mailbox_irq param = { 0 };
+	struct nbl_common_info *common = disp_mgt->common;
+	struct nbl_chan_send_info chan_send;
+	int ret;
+
+	param.vector_id = cpu_to_le16(vector_id);
+	param.en_msix = !!en_msix;
+
+	nbl_chan_fill_send_info(&chan_send, common->mgt_pf,
+				NBL_CHAN_MSG_MAILBOX_SET_IRQ,
+				&param, sizeof(param), NULL, 0, 1);
+	ret = chan_ops->send_msg(disp_mgt->chan_ops_tbl->priv, &chan_send);
+	switch (ret) {
+	case NBL_CHAN_RESP_UNIMPLEMENTED:
+		return -EOPNOTSUPP;
+	case NBL_CHAN_RESP_ERR:
+		return -EREMOTEIO;
+	case NBL_CHAN_RESP_OK:
+		return ret;
+	default:
+		return ret;
+	}
+}
+
+static void nbl_disp_chan_set_mailbox_irq_resp(void *priv, u16 src_id,
+					       u16 msg_id, void *data,
+					       u32 data_len)
+{
+	struct nbl_dispatch_mgt *disp_mgt = (struct nbl_dispatch_mgt *)priv;
+	struct nbl_channel_ops *chan_ops = disp_mgt->chan_ops_tbl->ops;
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+	struct nbl_chan_param_set_mailbox_irq param = { 0 };
+	struct device *dev = disp_mgt->common->dev;
+	struct nbl_chan_ack_info chan_ack;
+	int err = NBL_CHAN_RESP_OK;
+	bool en_msix;
+	u16 vector_id;
+	int copy_len;
+	int ret;
+
+	if (data_len == 0) {
+		err = NBL_CHAN_RESP_ERR;
+		goto ack_out;
+	}
+	copy_len = min_t(size_t, data_len, sizeof(param));
+	memcpy(&param, data, copy_len);
+	vector_id = le16_to_cpu(param.vector_id);
+	en_msix = !!param.en_msix;
+
+	if (res_ops->set_mailbox_irq) {
+		mutex_lock(&disp_mgt->ops_mutex_lock);
+		ret = res_ops->set_mailbox_irq(p, src_id, vector_id, en_msix);
+		mutex_unlock(&disp_mgt->ops_mutex_lock);
+		if (ret)
+			err = NBL_CHAN_RESP_ERR;
+	} else {
+		err = NBL_CHAN_RESP_UNIMPLEMENTED;
+	}
+
+ack_out:
+	nbl_chan_fill_ack_info(&chan_ack, src_id,
+			       NBL_CHAN_MSG_MAILBOX_SET_IRQ, msg_id,
+			       err, NULL, 0);
+	ret = chan_ops->send_ack(disp_mgt->chan_ops_tbl->priv, &chan_ack);
+	if (ret)
+		dev_err(dev,
+			"channel send ack failed with ret: %d, msg_type: %d\n",
+			ret, NBL_CHAN_MSG_MAILBOX_SET_IRQ);
+}
+
+static int nbl_disp_destroy_msix_map(struct nbl_dispatch_mgt *disp_mgt)
+{
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+	struct nbl_common_info *common = disp_mgt->common;
+	int ret;
+
+	if (!res_ops->destroy_msix_map)
+		return -EOPNOTSUPP;
+	mutex_lock(&disp_mgt->ops_mutex_lock);
+	ret = res_ops->destroy_msix_map(p, common->mgt_pf);
+	mutex_unlock(&disp_mgt->ops_mutex_lock);
+	return ret;
+}
+
+static int nbl_disp_set_mailbox_irq(struct nbl_dispatch_mgt *disp_mgt,
+				    u16 vector_id, bool en_msix)
+{
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+	struct nbl_common_info *common = disp_mgt->common;
+	int ret;
+
+	if (!res_ops->set_mailbox_irq)
+		return -EOPNOTSUPP;
+	mutex_lock(&disp_mgt->ops_mutex_lock);
+	ret = res_ops->set_mailbox_irq(p, common->mgt_pf, vector_id, en_msix);
+	mutex_unlock(&disp_mgt->ops_mutex_lock);
+	return ret;
+}
+
+static int nbl_disp_get_vsi_id(struct nbl_dispatch_mgt *disp_mgt, u16 type,
+			       u16 *vsi_id)
+{
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+	struct nbl_common_info *common = disp_mgt->common;
+
+	if (res_ops->get_vsi_id)
+		return res_ops->get_vsi_id(p, common->mgt_pf, type, vsi_id);
+	return -EOPNOTSUPP;
+}
+
+static int nbl_disp_get_eth_id(struct nbl_dispatch_mgt *disp_mgt, u16 vsi_id,
+			       u8 *eth_num, u8 *eth_id, u8 *logic_eth_id)
+{
+	struct nbl_resource_ops *res_ops = disp_mgt->res_ops_tbl->ops;
+	struct nbl_resource_mgt *p = disp_mgt->res_ops_tbl->priv;
+	struct nbl_common_info *common = disp_mgt->common;
+
+	if (res_ops->get_eth_id)
+		return res_ops->get_eth_id(p, common->mgt_pf, vsi_id,
+					   eth_num, eth_id, logic_eth_id);
+	return -EOPNOTSUPP;
+}
+
+static int nbl_disp_setup_msg(struct nbl_dispatch_mgt *disp_mgt)
+{
+	struct nbl_channel_ops *chan_ops = disp_mgt->chan_ops_tbl->ops;
+	struct nbl_channel_mgt *p = disp_mgt->chan_ops_tbl->priv;
+	int ret = 0;
+	int _ret;
+
+	_ret = chan_ops->register_msg(p, NBL_CHAN_MSG_CONFIGURE_MSIX_MAP,
+				      nbl_disp_chan_cfg_msix_map_resp,
+				      disp_mgt);
+	if (_ret < 0 && !ret)
+		ret = _ret;
+
+	_ret = chan_ops->register_msg(p, NBL_CHAN_MSG_DESTROY_MSIX_MAP,
+				      nbl_disp_chan_destroy_msix_map_resp,
+				      disp_mgt);
+	if (_ret < 0 && !ret)
+		ret = _ret;
+
+	_ret = chan_ops->register_msg(p, NBL_CHAN_MSG_MAILBOX_SET_IRQ,
+				      nbl_disp_chan_set_mailbox_irq_resp,
+				      disp_mgt);
+	if (_ret < 0 && !ret)
+		ret = _ret;
+
+	_ret = chan_ops->register_msg(p, NBL_CHAN_MSG_GET_VSI_ID,
+				      nbl_disp_chan_get_vsi_id_resp,
+				      disp_mgt);
+	if (_ret < 0 && !ret)
+		ret = _ret;
+
+	_ret = chan_ops->register_msg(p, NBL_CHAN_MSG_GET_ETH_ID,
+				      nbl_disp_chan_get_eth_id_resp,
+				      disp_mgt);
+	if (_ret < 0 && !ret)
+		ret = _ret;
+
+	if (ret)
+		chan_ops->unregister_all_msg(p);
+	return ret;
+}
+
+static void nbl_disp_setup_ctrl_lvl(struct nbl_dispatch_mgt *disp_mgt, u32 lvl)
+{
+	struct nbl_dispatch_ops *disp_ops = disp_mgt->disp_ops_tbl->ops;
+
+	set_bit(lvl, disp_mgt->ctrl_lvl);
+
+	if (test_bit(NBL_DISP_CTRL_LVL_MGT, disp_mgt->ctrl_lvl)) {
+		disp_ops->init_module = nbl_disp_init_module;
+		disp_ops->deinit_module = nbl_disp_deinit_module;
+		disp_ops->cfg_msix_map = nbl_disp_cfg_msix_map;
+		disp_ops->destroy_msix_map = nbl_disp_destroy_msix_map;
+		disp_ops->set_mailbox_irq = nbl_disp_set_mailbox_irq;
+		disp_ops->get_vsi_id = nbl_disp_get_vsi_id;
+		disp_ops->get_eth_id = nbl_disp_get_eth_id;
+	} else {
+		disp_ops->cfg_msix_map =
+			nbl_disp_chan_cfg_msix_map_req;
+		disp_ops->destroy_msix_map = nbl_disp_chan_destroy_msix_map_req;
+		disp_ops->set_mailbox_irq = nbl_disp_chan_set_mailbox_irq_req;
+		disp_ops->get_vsi_id = nbl_disp_chan_get_vsi_id_req;
+		disp_ops->get_eth_id = nbl_disp_chan_get_eth_id_req;
+	}
+}
+
+static struct nbl_dispatch_mgt *
+nbl_disp_setup_disp_mgt(struct nbl_common_info *common)
+{
+	struct nbl_dispatch_mgt *disp_mgt;
+	struct device *dev = common->dev;
+	int err;
+
+	disp_mgt = devm_kzalloc(dev, sizeof(*disp_mgt), GFP_KERNEL);
+	if (!disp_mgt)
+		return ERR_PTR(-ENOMEM);
+
+	disp_mgt->common = common;
+	err = devm_mutex_init(common->dev, &disp_mgt->ops_mutex_lock);
+	if (err)
+		return ERR_PTR(err);
+	return disp_mgt;
+}
+
+static struct nbl_dispatch_ops_tbl *
+nbl_disp_setup_ops(struct device *dev, struct nbl_dispatch_mgt *disp_mgt)
+{
+	struct nbl_dispatch_ops_tbl *disp_ops_tbl;
+	struct nbl_dispatch_ops *disp_ops;
+
+	disp_ops_tbl = devm_kzalloc(dev, sizeof(*disp_ops_tbl), GFP_KERNEL);
+	if (!disp_ops_tbl)
+		return ERR_PTR(-ENOMEM);
+
+	disp_ops = devm_kzalloc(dev, sizeof(*disp_ops), GFP_KERNEL);
+	if (!disp_ops)
+		return ERR_PTR(-ENOMEM);
+
+	disp_ops_tbl->ops = disp_ops;
+	disp_ops_tbl->priv = disp_mgt;
+
+	return disp_ops_tbl;
+}
+
+int nbl_disp_init(struct nbl_adapter *adapter)
+{
+	struct nbl_common_info *common = &adapter->common;
+	struct nbl_dispatch_ops_tbl *disp_ops_tbl;
+	struct nbl_resource_ops_tbl *res_ops_tbl =
+		adapter->intf.resource_ops_tbl;
+	struct nbl_channel_ops_tbl *chan_ops_tbl =
+		adapter->intf.channel_ops_tbl;
+	struct device *dev = &adapter->pdev->dev;
+	struct nbl_dispatch_mgt *disp_mgt;
+	int ret;
+
+	disp_mgt = nbl_disp_setup_disp_mgt(common);
+	if (IS_ERR(disp_mgt)) {
+		ret = PTR_ERR(disp_mgt);
+		return ret;
+	}
+
+	disp_ops_tbl = nbl_disp_setup_ops(dev, disp_mgt);
+	if (IS_ERR(disp_ops_tbl)) {
+		ret = PTR_ERR(disp_ops_tbl);
+		return ret;
+	}
+
+	disp_mgt->res_ops_tbl = res_ops_tbl;
+	disp_mgt->chan_ops_tbl = chan_ops_tbl;
+	disp_mgt->disp_ops_tbl = disp_ops_tbl;
+	adapter->core.disp_mgt = disp_mgt;
+	adapter->intf.dispatch_ops_tbl = disp_ops_tbl;
+
+	ret = nbl_disp_setup_msg(disp_mgt);
+	if (ret)
+		return ret;
+
+	if (common->has_ctrl)
+		nbl_disp_setup_ctrl_lvl(disp_mgt, NBL_DISP_CTRL_LVL_MGT);
+
+	/*
+	 * For non-control PF with network capability, enable net control
+	 * level.
+	 * All dispatch ops declared with NBL_DISP_CTRL_LVL_MGT fall back
+	 * to remote mailbox msg_req handlers when MGT bit is not set.
+	 */
+	if (common->has_net)
+		nbl_disp_setup_ctrl_lvl(disp_mgt, NBL_DISP_CTRL_LVL_NET);
+
+	return 0;
+}
+
+void nbl_disp_remove(struct nbl_adapter *adapter)
+{
+	/*
+	 * All message handlers will be cleaned up inside channel layer
+	 * nbl_chan_remove_common() at final device tear-down
+	 */
+}
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h
new file mode 100644
index 0000000000000..644094c9dd8eb
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_DISPATCH_H_
+#define _NBL_DISPATCH_H_
+#include "../nbl_include/nbl_include.h"
+#include "../nbl_include/nbl_def_channel.h"
+#include "../nbl_include/nbl_def_hw.h"
+#include "../nbl_include/nbl_def_resource.h"
+#include "../nbl_include/nbl_def_dispatch.h"
+#include "../nbl_include/nbl_def_common.h"
+#include "../nbl_core.h"
+
+struct nbl_dispatch_mgt {
+	struct nbl_common_info *common;
+	struct nbl_resource_ops_tbl *res_ops_tbl;
+	struct nbl_channel_ops_tbl *chan_ops_tbl;
+	struct nbl_dispatch_ops_tbl *disp_ops_tbl;
+	DECLARE_BITMAP(ctrl_lvl, NBL_DISP_CTRL_LVL_MAX);
+	/* use for the caller not in interrupt */
+	struct mutex ops_mutex_lock;
+};
+
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.c
new file mode 100644
index 0000000000000..c1e6180a70106
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+#include <linux/device.h>
+#include "nbl_chip.h"
+
+void nbl_res_chip_deinit_module(struct nbl_resource_mgt *res_mgt)
+{
+	struct nbl_hw_ops *hw_ops = res_mgt->hw_ops_tbl->ops;
+
+	hw_ops->deinit_module(res_mgt->hw_ops_tbl->priv);
+}
+
+int nbl_res_chip_init_module(struct nbl_resource_mgt *res_mgt)
+{
+	u8 eth_speed = res_mgt->resource_info->board_info.eth_speed;
+	u8 eth_num = res_mgt->resource_info->board_info.eth_num;
+	struct nbl_hw_ops *hw_ops = res_mgt->hw_ops_tbl->ops;
+	struct nbl_hw_mgt *p = res_mgt->hw_ops_tbl->priv;
+
+	return hw_ops->init_module(p, eth_speed, eth_num);
+}
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.h
new file mode 100644
index 0000000000000..d8ec8ea63d040
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_CHIP_H_
+#define _NBL_CHIP_H_
+
+#include "nbl_resource.h"
+int nbl_res_chip_init_module(struct nbl_resource_mgt *res_mgt);
+void nbl_res_chip_deinit_module(struct nbl_resource_mgt *res_mgt);
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.c
new file mode 100644
index 0000000000000..f36d400e6db01
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.c
@@ -0,0 +1,1086 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+#include <linux/device.h>
+#include <linux/pci.h>
+#include <linux/bits.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/bitfield.h>
+#include "nbl_hw_leonis.h"
+
+static void nbl_hw_read_mbx_regs(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 *data,
+				 u32 len)
+{
+	u32 i;
+
+	if (len % 4)
+		return;
+
+	for (i = 0; i < len / 4; i++)
+		data[i] = nbl_mbx_rd32(hw_mgt, reg + i * sizeof(u32));
+}
+
+static void nbl_hw_write_mbx_regs(struct nbl_hw_mgt *hw_mgt, u64 reg,
+				  const u32 *data, u32 len)
+{
+	u32 i;
+
+	if (len % 4)
+		return;
+
+	for (i = 0; i < len / 4; i++)
+		nbl_mbx_wr32(hw_mgt, reg + i * sizeof(u32), data[i]);
+}
+
+static void nbl_hw_rd_regs(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 *data,
+			   u32 len)
+{
+	u32 size = len / 4;
+	u32 i;
+
+	if (len % 4)
+		return;
+	for (i = 0; i < size; i++)
+		data[i] = rd32(hw_mgt->hw_addr, reg + i * sizeof(u32));
+}
+
+static void nbl_hw_wr_regs(struct nbl_hw_mgt *hw_mgt, u64 reg, const u32 *data,
+			   u32 len)
+{
+	u32 size = len / 4;
+	u32 i;
+
+	if (len % 4)
+		return;
+	for (i = 0; i < size; i++)
+		wr32(hw_mgt->hw_addr, reg + i * sizeof(u32), data[i]);
+}
+
+static void nbl_hw_rd_regs_lock(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 *data,
+				u32 len)
+{
+	u32 size = len / 4;
+	u32 i;
+
+	if (len % 4)
+		return;
+
+	spin_lock(&hw_mgt->reg_lock);
+
+	for (i = 0; i < size; i++)
+		data[i] = rd32(hw_mgt->hw_addr, reg + i * sizeof(u32));
+	spin_unlock(&hw_mgt->reg_lock);
+}
+
+static void nbl_hw_wr_regs_lock(struct nbl_hw_mgt *hw_mgt, u64 reg,
+				const u32 *data, u32 len)
+{
+	u32 size = len / 4;
+	u32 i;
+
+	if (len % 4)
+		return;
+	spin_lock(&hw_mgt->reg_lock);
+	for (i = 0; i < size; i++)
+		wr32(hw_mgt->hw_addr, reg + i * sizeof(u32), data[i]);
+	spin_unlock(&hw_mgt->reg_lock);
+}
+
+/*
+ * Per hardware spec, registers reset to zero after cold boot, FLR and bus
+ * reset.Zero value indicates firmware not initialized; upper layer retries
+ * setup. No stale valid configuration can persist.
+ */
+static void nbl_hw_get_fw_eth_map(struct nbl_hw_mgt *hw_mgt, u32 *eth_map)
+{
+	u32 data;
+
+	nbl_hw_read_mbx_regs(hw_mgt, NBL_FW_BOARD_DW6_OFFSET, &data,
+			     sizeof(data));
+	*eth_map = FIELD_GET(NBL_FW_BOARD_DW6_ETH_BITMAP_MASK, data);
+}
+
+static u32 nbl_hw_get_quirks(struct nbl_hw_mgt *hw_mgt)
+{
+	u32 quirks;
+
+	nbl_hw_read_mbx_regs(hw_mgt, NBL_LEONIS_QUIRKS_OFFSET, &quirks,
+			     sizeof(u32));
+
+	if (quirks == ~0u)
+		return 0;
+
+	return quirks;
+}
+
+static void nbl_configure_dped_checksum(struct nbl_hw_mgt *hw_mgt)
+{
+	u32 data;
+
+	/* DPED dped_l4_ck_cmd_40 for sctp */
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_rd_regs(hw_mgt, NBL_DPED_L4_CK_CMD_40_ADDR, &data, sizeof(data));
+	data |= FIELD_PREP(NBL_DPED_L4_CK_CMD_40_EN_MASK, 1);
+	nbl_hw_wr_regs(hw_mgt, NBL_DPED_L4_CK_CMD_40_ADDR, &data, sizeof(data));
+	spin_unlock(&hw_mgt->reg_lock);
+}
+
+static void nbl_dped_init(struct nbl_hw_mgt *hw_mgt)
+{
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_wr32(hw_mgt, NBL_DPED_VLAN_OFFSET, 0xC);
+	nbl_hw_wr32(hw_mgt, NBL_DPED_DSCP_OFFSET_0, 0x8);
+	nbl_hw_wr32(hw_mgt, NBL_DPED_DSCP_OFFSET_1, 0x4);
+	spin_unlock(&hw_mgt->reg_lock);
+	/* dped checksum offload */
+	nbl_configure_dped_checksum(hw_mgt);
+}
+
+static void nbl_uped_init(struct nbl_hw_mgt *hw_mgt)
+{
+	u32 hw_edit;
+
+	/* V4 TCP: l3_len = 0 */
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_rd_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V4_TCP_IDX),
+		       &hw_edit, sizeof(hw_edit));
+	hw_edit &= ~NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK;
+	nbl_hw_wr_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V4_TCP_IDX),
+		       &hw_edit, sizeof(hw_edit));
+
+	/* V6 TCP: l3_len = 1 */
+	nbl_hw_rd_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V6_TCP_IDX),
+		       &hw_edit, sizeof(hw_edit));
+	hw_edit = (hw_edit & ~NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK) |
+		  FIELD_PREP(NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK, 1);
+	nbl_hw_wr_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V6_TCP_IDX),
+		       &hw_edit, sizeof(hw_edit));
+	spin_unlock(&hw_mgt->reg_lock);
+}
+
+static int nbl_shaping_eth_init(struct nbl_hw_mgt *hw_mgt, u8 eth_id, u8 speed)
+{
+	struct nbl_shaping_dvn_dport_u dvn_dport = { 0 };
+	struct nbl_shaping_dport_u dport = { 0 };
+	u32 rate, half_rate;
+	u32 depth;
+	u64 low_val, high_val;
+
+	switch (speed) {
+	case NBL_FW_PORT_SPEED_100G:
+		rate = 100000;
+		break;
+	case NBL_FW_PORT_SPEED_50G:
+		rate = 50000;
+		break;
+	case NBL_FW_PORT_SPEED_25G:
+		rate = 25000;
+		break;
+	case NBL_FW_PORT_SPEED_10G:
+		rate = 10000;
+		break;
+	default:
+		dev_err(hw_mgt->common->dev,
+			"Unsupported port speed %u for eth%u\n", speed, eth_id);
+		return -EINVAL;
+	}
+
+	half_rate = rate / 2;
+	depth = max_t(u32, rate * 2, NBL_LR_LEONIS_NET_BUCKET_DEPTH);
+
+	/* 1. clear valid first
+	 * dport and dvn_dport are zero-initialised above, so VALID=0 already
+	 */
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DPORT_REG(eth_id), dport.data,
+		       sizeof(dport));
+	nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DVN_DPORT_REG(eth_id),
+		       dvn_dport.data, sizeof(dvn_dport));
+
+	/* 2. write config words (valid=0, safe) */
+	low_val = FIELD_PREP(DPORT_CIR_MASK, rate) |
+		  FIELD_PREP(DPORT_PIR_MASK, rate) |
+		  FIELD_PREP(DPORT_DEPTH_MASK, depth) |
+		  FIELD_PREP(DPORT_CBS_MASK_LOW, depth & 0x3F);
+	high_val = FIELD_PREP(DPORT_CBS_MASK_HIGH, depth >> 6) |
+		   FIELD_PREP(DPORT_PBS_MASK, depth);
+	/* Fixed split, independent of host endian */
+	dport.data[0] = lower_32_bits(low_val);
+	dport.data[1] = upper_32_bits(low_val);
+	dport.data[2] = lower_32_bits(high_val);
+	dport.data[3] = upper_32_bits(high_val);
+
+	low_val = FIELD_PREP(DPORT_CIR_MASK, half_rate) |
+		  FIELD_PREP(DPORT_PIR_MASK, rate) |
+		  FIELD_PREP(DPORT_DEPTH_MASK, depth) |
+		  FIELD_PREP(DPORT_CBS_MASK_LOW, depth & 0x3F);
+	high_val = FIELD_PREP(DPORT_CBS_MASK_HIGH, depth >> 6) |
+		   FIELD_PREP(DPORT_PBS_MASK, depth);
+	dvn_dport.data[0] = lower_32_bits(low_val);
+	dvn_dport.data[1] = upper_32_bits(low_val);
+	dvn_dport.data[2] = lower_32_bits(high_val);
+	dvn_dport.data[3] = upper_32_bits(high_val);
+
+	nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DPORT_REG(eth_id), dport.data,
+		       sizeof(dport));
+	nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DVN_DPORT_REG(eth_id),
+		       dvn_dport.data, sizeof(dvn_dport));
+
+	/* 3. commit: set valid last */
+	low_val = FIELD_PREP(DPORT_VALID_MASK, 1);
+	dport.data[0] |= lower_32_bits(low_val);
+
+	low_val = FIELD_PREP(DPORT_VALID_MASK, 1);
+	dvn_dport.data[0] |= lower_32_bits(low_val);
+
+	nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DPORT_REG(eth_id), dport.data,
+		       sizeof(dport));
+	nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DVN_DPORT_REG(eth_id),
+		       dvn_dport.data, sizeof(dvn_dport));
+	spin_unlock(&hw_mgt->reg_lock);
+	return 0;
+}
+
+static int nbl_shaping_init(struct nbl_hw_mgt *hw_mgt, u8 speed)
+{
+#define NBL_SHAPING_FLUSH_INTERVAL 128
+	struct nbl_shaping_net_u net_shaping = { 0 };
+	u32 eth_bitmap = 0;
+	u32 psha_en = 0;
+	int ret;
+	int i;
+
+	nbl_hw_get_fw_eth_map(hw_mgt, &eth_bitmap);
+	for (i = 0; i < NBL_MAX_ETHERNET; i++) {
+		if (!(eth_bitmap & BIT(i)))
+			continue;
+		ret = nbl_shaping_eth_init(hw_mgt, i, speed);
+		if (ret)
+			return ret;
+	}
+	psha_en = FIELD_PREP(NBL_DSCH_PSHA_EN_MASK, 0xF);
+	nbl_hw_wr_regs_lock(hw_mgt, NBL_DSCH_PSHA_EN_ADDR, &psha_en,
+			    sizeof(psha_en));
+
+	for (i = 0; i < NBL_MAX_FUNC; i++) {
+		nbl_hw_wr_regs_lock(hw_mgt, NBL_SHAPING_NET_REG(i),
+				    net_shaping.data,
+				    sizeof(net_shaping));
+		if ((i + 1) % NBL_SHAPING_FLUSH_INTERVAL == 0)
+			nbl_flush_writes(hw_mgt);
+	}
+	nbl_flush_writes(hw_mgt);
+	return 0;
+}
+
+static void nbl_dsch_qid_max_init(struct nbl_hw_mgt *hw_mgt)
+{
+	u32 quanta = 0;
+
+	quanta = FIELD_PREP(NBL_DSCH_VN_QUANTA_H_QUA_MASK, NBL_HOST_QUANTA) |
+		 FIELD_PREP(NBL_DSCH_VN_QUANTA_E_QUA_MASK, NBL_ECPU_QUANTA);
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_wr_regs(hw_mgt, NBL_DSCH_VN_QUANTA_ADDR, &quanta,
+		       sizeof(quanta));
+	nbl_hw_wr32(hw_mgt, NBL_DSCH_HOST_QID_MAX, NBL_MAX_QUEUE_ID);
+
+	nbl_hw_wr32(hw_mgt, NBL_DVN_ECPU_QUEUE_NUM, 0);
+	nbl_hw_wr32(hw_mgt, NBL_UVN_ECPU_QUEUE_NUM, 0);
+	spin_unlock(&hw_mgt->reg_lock);
+}
+
+static int nbl_ustore_init(struct nbl_hw_mgt *hw_mgt, u8 eth_num)
+{
+	u32 eth_bitmap = 0;
+	u32 drop_th = 0;
+	u32 pkt_len;
+	int i;
+
+	if (eth_num != 1 && eth_num != 2 && eth_num != 4)
+		return -EINVAL;
+	/* Read current packet length config
+	 *(to preserve other fields while updating 'min')
+	 */
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_rd_regs(hw_mgt, NBL_USTORE_PKT_LEN_ADDR, &pkt_len,
+		       sizeof(pkt_len));
+	/* min arp packet length 42 (14 + 28) */
+	pkt_len &= ~NBL_USTORE_PKT_LEN_MIN_MASK;
+	pkt_len |= FIELD_PREP(NBL_USTORE_PKT_LEN_MIN_MASK, 42);
+	nbl_hw_wr_regs(hw_mgt, NBL_USTORE_PKT_LEN_ADDR, &pkt_len,
+		       sizeof(pkt_len));
+
+	drop_th |= FIELD_PREP(NBL_USTORE_PORT_DROP_TH_EN_MASK, 1);
+	if (eth_num == 1)
+		drop_th |= FIELD_PREP(NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK,
+				      NBL_USTORE_SINGLE_ETH_DROP_TH);
+	else if (eth_num == 2)
+		drop_th |= FIELD_PREP(NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK,
+				      NBL_USTORE_DUAL_ETH_DROP_TH);
+	else
+		drop_th |= FIELD_PREP(NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK,
+				      NBL_USTORE_QUAD_ETH_DROP_TH);
+	nbl_hw_get_fw_eth_map(hw_mgt, &eth_bitmap);
+	for (i = 0; i < NBL_MAX_ETHERNET; i++) {
+		if (!(eth_bitmap & BIT(i)))
+			continue;
+		nbl_hw_wr_regs(hw_mgt, NBL_USTORE_PORT_DROP_TH_REG_ARR(i),
+			       &drop_th, sizeof(drop_th));
+	}
+
+	/* Clear port drop/truncate counters by reading them
+	 * (hardware has read-to-clear behavior for these registers)
+	 */
+	for (i = 0; i < NBL_MAX_ETHERNET; i++) {
+		if (!(eth_bitmap & BIT(i)))
+			continue;
+		nbl_hw_rd32(hw_mgt, NBL_USTORE_BUF_PORT_DROP_PKT(i));
+		nbl_hw_rd32(hw_mgt, NBL_USTORE_BUF_PORT_TRUN_PKT(i));
+	}
+	spin_unlock(&hw_mgt->reg_lock);
+	return 0;
+}
+
+static void nbl_dstore_init(struct nbl_hw_mgt *hw_mgt, u8 speed)
+{
+	u32 eth_bitmap = 0;
+	u32 drop_th;
+	u32 fc_th;
+	u32 bp_th;
+	int i;
+
+	for (i = 0; i < NBL_DSTORE_PORT_DROP_TH_DEPTH; i++) {
+		spin_lock(&hw_mgt->reg_lock);
+		nbl_hw_rd_regs(hw_mgt, NBL_DSTORE_PORT_DROP_TH_REG(i), &drop_th,
+			       sizeof(drop_th));
+		drop_th &= ~NBL_DSTORE_PORT_DROP_EN_MASK;
+		nbl_hw_wr_regs(hw_mgt, NBL_DSTORE_PORT_DROP_TH_REG(i), &drop_th,
+			       sizeof(drop_th));
+		spin_unlock(&hw_mgt->reg_lock);
+	}
+
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_rd_regs(hw_mgt, NBL_DSTORE_DISC_BP_TH, &bp_th, sizeof(bp_th));
+	bp_th |= FIELD_PREP(NBL_DSTORE_DISC_BP_TH_EN_MASK, 1);
+	nbl_hw_wr_regs(hw_mgt, NBL_DSTORE_DISC_BP_TH, &bp_th, sizeof(bp_th));
+	spin_unlock(&hw_mgt->reg_lock);
+
+	nbl_hw_get_fw_eth_map(hw_mgt, &eth_bitmap);
+	for (i = 0; i < NBL_MAX_ETHERNET; i++) {
+		if (!(eth_bitmap & BIT(i)))
+			continue;
+		spin_lock(&hw_mgt->reg_lock);
+		nbl_hw_rd_regs(hw_mgt, NBL_DSTORE_D_DPORT_FC_TH_REG(i), &fc_th,
+			       sizeof(fc_th));
+		fc_th &= ~(NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK |
+			   NBL_DSTORE_D_DPORT_FC_XON_TH_MASK);
+		if (speed == NBL_FW_PORT_SPEED_100G) {
+			fc_th |=
+				FIELD_PREP(NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK,
+					   NBL_DSTORE_DROP_XOFF_TH_100G) |
+				FIELD_PREP(NBL_DSTORE_D_DPORT_FC_XON_TH_MASK,
+					   NBL_DSTORE_DROP_XON_TH_100G);
+		} else {
+			fc_th |=
+				FIELD_PREP(NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK,
+					   NBL_DSTORE_DROP_XOFF_TH) |
+				FIELD_PREP(NBL_DSTORE_D_DPORT_FC_XON_TH_MASK,
+					   NBL_DSTORE_DROP_XON_TH);
+		}
+
+		fc_th |= FIELD_PREP(NBL_DSTORE_D_DPORT_FC_FC_EN_MASK, 1);
+		nbl_hw_wr_regs(hw_mgt, NBL_DSTORE_D_DPORT_FC_TH_REG(i), &fc_th,
+			       sizeof(fc_th));
+		spin_unlock(&hw_mgt->reg_lock);
+	}
+}
+
+static void nbl_dvn_descreq_num_cfg(struct nbl_hw_mgt *hw_mgt, u8 descreq_num)
+{
+	u8 split_ring_num = (descreq_num >> 3) & 0x1;
+	u8 ring_num = descreq_num & 0x7;
+	u32 num_cfg = 0;
+
+	num_cfg = FIELD_PREP(NBL_DVN_DESCREQ_NUM_CFG_AVRING_DESREQ_NUM_CFG_MASK,
+			     split_ring_num) |
+		  FIELD_PREP(NBL_DVN_DESCREQ_NUM_CFG_PACKED_L1_NUM_MASK,
+			     ring_num);
+
+	nbl_hw_wr_regs_lock(hw_mgt, NBL_DVN_DESCREQ_NUM_CFG, &num_cfg,
+			    sizeof(num_cfg));
+}
+
+static void nbl_dvn_init(struct nbl_hw_mgt *hw_mgt, u8 speed)
+{
+	u32 timeout = 0;
+	u32 ro_flag = 0;
+
+	timeout = FIELD_PREP(NBL_DVN_DESC_WR_MERGE_TIMEOUT_CFG_CYCLE_MASK,
+			     DEFAULT_DVN_DESC_WR_MERGE_TIMEOUT_MAX);
+	nbl_hw_wr_regs_lock(hw_mgt, NBL_DVN_DESC_WR_MERGE_TIMEOUT, &timeout,
+			    sizeof(timeout));
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_rd_regs(hw_mgt, NBL_DVN_DIF_REQ_RD_RO_FLAG, &ro_flag,
+		       sizeof(ro_flag));
+	if (pcie_relaxed_ordering_enabled(hw_mgt->common->pdev)) {
+		ro_flag |=
+			FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DESC_RO_EN_MASK,
+				   1) |
+			FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DATA_RO_EN_MASK,
+				   1) |
+			FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_AVRING_RO_EN_MASK,
+				   1);
+	} else {
+		ro_flag &=
+			~(FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DESC_RO_EN_MASK,
+				     1) |
+			FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DATA_RO_EN_MASK,
+				   1) |
+			FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_AVRING_RO_EN_MASK,
+				   1));
+	}
+	nbl_hw_wr_regs(hw_mgt, NBL_DVN_DIF_REQ_RD_RO_FLAG, &ro_flag,
+		       sizeof(ro_flag));
+	spin_unlock(&hw_mgt->reg_lock);
+	if (speed == NBL_FW_PORT_SPEED_100G)
+		nbl_dvn_descreq_num_cfg(hw_mgt,
+					DEFAULT_DVN_100G_DESCREQ_NUMCFG);
+	else
+		nbl_dvn_descreq_num_cfg(hw_mgt, DEFAULT_DVN_DESCREQ_NUMCFG);
+}
+
+static void nbl_uvn_init(struct nbl_hw_mgt *hw_mgt)
+{
+	u16 wr_timeout = NBL_UVN_DESC_WR_TIMEOUT_VAL;
+	u32 timeout = NBL_UVN_DESC_RD_WAIT_TICKS;
+	u32 desc_wr_timeout = 0;
+	u32 prefetch_init = 0;
+	bool ro_enabled;
+	u32 flag = 0;
+	u32 mask = 0;
+	u32 quirks;
+
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_wr32(hw_mgt, NBL_UVN_DESC_RD_WAIT, timeout);
+	desc_wr_timeout =
+		FIELD_PREP(NBL_UVN_DESC_WR_TIMEOUT_NUM_MASK, wr_timeout);
+	nbl_hw_wr_regs(hw_mgt, NBL_UVN_DESC_WR_TIMEOUT, &desc_wr_timeout,
+		       sizeof(desc_wr_timeout));
+	ro_enabled = pcie_relaxed_ordering_enabled(hw_mgt->common->pdev);
+
+	nbl_hw_rd_regs(hw_mgt, NBL_UVN_DIF_REQ_RO_FLAG, &flag, sizeof(flag));
+	if (ro_enabled) {
+		flag |= FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_AVAIL_RD_MASK, 1) |
+		FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_RD_MASK, 1) |
+		FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_PKT_WR_MASK, 1);
+		flag &= ~FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_WR_MASK, 1);
+	} else {
+		flag &= ~(FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_AVAIL_RD_MASK, 1) |
+			  FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_RD_MASK, 1) |
+			  FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_PKT_WR_MASK, 1) |
+			  FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_WR_MASK, 1));
+	}
+	nbl_hw_wr_regs(hw_mgt, NBL_UVN_DIF_REQ_RO_FLAG, &flag, sizeof(flag));
+
+	nbl_hw_rd_regs(hw_mgt, NBL_UVN_QUEUE_ERR_MASK, &mask, sizeof(mask));
+	mask |= FIELD_PREP(NBL_UVN_QUEUE_ERR_MASK_DIF_ERR_MASK, 1);
+
+	nbl_hw_wr_regs(hw_mgt, NBL_UVN_QUEUE_ERR_MASK, &mask, sizeof(mask));
+
+	quirks = nbl_hw_get_quirks(hw_mgt);
+	/*
+	 * sel=0: use configured num; sel=1: use internal calc (max 32)
+	 * Default is sel=1, unless NBL_QUIRKS_UVN_PREFETCH_ALIGN is set,
+	 * in which case override to sel=0.
+	 */
+	prefetch_init =
+		FIELD_PREP(NBL_UVN_DESC_PREFETCH_INIT_NUM_MASK,
+			   NBL_UVN_DESC_PREFETCH_NUM) |
+		FIELD_PREP(NBL_UVN_DESC_PREFETCH_INIT_SEL_MASK,
+			   (quirks & NBL_QUIRK_UVN_PREFETCH_ALIGN) ? 0 :
+									   1);
+
+	nbl_hw_wr_regs(hw_mgt, NBL_UVN_DESC_PREFETCH_INIT, &prefetch_init,
+		       sizeof(prefetch_init));
+	spin_unlock(&hw_mgt->reg_lock);
+}
+
+static void nbl_uqm_init(struct nbl_hw_mgt *hw_mgt)
+{
+	u32 que_type = 0;
+	u32 cnt = 0;
+	int i;
+
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_wr_regs(hw_mgt, NBL_UQM_FWD_DROP_CNT, &cnt, sizeof(cnt));
+
+	nbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_PKT_CNT, &cnt, sizeof(cnt));
+	nbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_PKT_SLICE_CNT, &cnt, sizeof(cnt));
+	nbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_PKT_LEN_ADD_CNT, &cnt, sizeof(cnt));
+	nbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_HEAD_PNTR_ADD_CNT, &cnt,
+		       sizeof(cnt));
+	nbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_WEIGHT_ADD_CNT, &cnt, sizeof(cnt));
+
+	for (i = 0; i < NBL_UQM_PORT_DROP_DEPTH; i++) {
+		nbl_hw_wr_regs(hw_mgt,
+			       NBL_UQM_PORT_DROP_PKT_CNT + (sizeof(cnt) * i),
+			       &cnt, sizeof(cnt));
+		nbl_hw_wr_regs(hw_mgt,
+			       NBL_UQM_PORT_DROP_PKT_SLICE_CNT +
+				       (sizeof(cnt) * i),
+			       &cnt, sizeof(cnt));
+		nbl_hw_wr_regs(hw_mgt,
+			       NBL_UQM_PORT_DROP_PKT_LEN_ADD_CNT +
+				       (sizeof(cnt) * i),
+			       &cnt, sizeof(cnt));
+		nbl_hw_wr_regs(hw_mgt,
+			       NBL_UQM_PORT_DROP_HEAD_PNTR_ADD_CNT +
+				       (sizeof(cnt) * i),
+			       &cnt, sizeof(cnt));
+		nbl_hw_wr_regs(hw_mgt,
+			       NBL_UQM_PORT_DROP_WEIGHT_ADD_CNT +
+				       (sizeof(cnt) * i),
+			       &cnt, sizeof(cnt));
+	}
+
+	for (i = 0; i < NBL_UQM_DPORT_DROP_DEPTH; i++)
+		nbl_hw_wr_regs(hw_mgt,
+			       NBL_UQM_DPORT_DROP_CNT + (sizeof(cnt) * i), &cnt,
+			       sizeof(cnt));
+	/* bit 0: bp mode , bit1: drop mode, resv bit1-31 */
+	nbl_hw_wr_regs(hw_mgt, NBL_UQM_QUE_TYPE, &que_type, sizeof(que_type));
+	spin_unlock(&hw_mgt->reg_lock);
+}
+
+static int nbl_dp_init(struct nbl_hw_mgt *hw_mgt, u8 speed, u8 eth_num)
+{
+	int ret;
+
+	nbl_dped_init(hw_mgt);
+	nbl_uped_init(hw_mgt);
+	ret = nbl_shaping_init(hw_mgt, speed);
+	if (ret)
+		return ret;
+	nbl_dsch_qid_max_init(hw_mgt);
+	ret = nbl_ustore_init(hw_mgt, eth_num);
+	if (ret)
+		return ret;
+	nbl_dstore_init(hw_mgt, speed);
+	nbl_dvn_init(hw_mgt, speed);
+	nbl_uvn_init(hw_mgt);
+	nbl_uqm_init(hw_mgt);
+	return 0;
+}
+
+static void nbl_host_padpt_init(struct nbl_hw_mgt *hw_mgt)
+{
+	/* padpt flow  control register */
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_CPLH_UP,
+		    NBL_HOST_PADPT_CFG_FC_CPLH_UP_VAL);
+	nbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_PD_DN,
+		    NBL_HOST_PADPT_CFG_FC_PD_DN_VAL);
+	nbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_PH_DN,
+		    NBL_HOST_PADPT_CFG_FC_PH_DN_VAL);
+	nbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_NPH_DN,
+		    NBL_HOST_PADPT_CFG_FC_NPH_DN_VAL);
+	spin_unlock(&hw_mgt->reg_lock);
+}
+
+static void nbl_intf_init(struct nbl_hw_mgt *hw_mgt)
+{
+	nbl_host_padpt_init(hw_mgt);
+}
+
+static void nbl_hw_set_driver_status(struct nbl_hw_mgt *hw_mgt, bool active)
+{
+	u32 status;
+
+	spin_lock(&hw_mgt->reg_lock);
+	status = nbl_hw_rd32(hw_mgt, NBL_DRIVER_STATUS_REG);
+
+	status &= ~BIT(NBL_DRIVER_STATUS_BIT);
+	status |= FIELD_PREP(BIT(NBL_DRIVER_STATUS_BIT), active);
+
+	nbl_hw_wr32(hw_mgt, NBL_DRIVER_STATUS_REG, status);
+	spin_unlock(&hw_mgt->reg_lock);
+}
+
+/*
+ * This design is intentional. Setting driver status to false is the
+ * official teardown mechanism: it notifies firmware to perform full
+ * cleanup of all per-PF hardware state, including qinfo registers.
+ * An inverse helper would duplicate work that the firmware already
+ * does, and would add error-path complexity for no benefit. We keep
+ * the deinit path minimal and rely on firmware cleanup for correctness,
+ * including abnormal hardware reset scenarios.
+ */
+static void nbl_hw_deinit_module(struct nbl_hw_mgt *hw_mgt)
+{
+	nbl_hw_set_driver_status(hw_mgt, false);
+	/* ensure registers written */
+	nbl_flush_writes(hw_mgt);
+}
+
+/*
+ * Full chip hardware initialization is handled by firmware.
+ * This function only configures driver-level table entries and registers.
+ */
+static int nbl_hw_init_module(struct nbl_hw_mgt *hw_mgt, u8 eth_speed,
+			      u8 eth_num)
+{
+	int ret;
+
+	ret = nbl_dp_init(hw_mgt, eth_speed, eth_num);
+	if (ret)
+		goto notify_fw;
+	nbl_intf_init(hw_mgt);
+	nbl_hw_set_driver_status(hw_mgt, true);
+	/* ensure registers written */
+	nbl_flush_writes(hw_mgt);
+
+	return 0;
+notify_fw:
+	/*
+	 * On failure path we set driver_status=false directly.
+	 * The FW checks this flag and cleans up partial state,
+	 * so no explicit rollback is needed.
+	 */
+	nbl_hw_set_driver_status(hw_mgt, false);
+	nbl_flush_writes(hw_mgt);
+	return ret;
+}
+
+/*
+ * nbl_hw_set_mailbox_irq - read-modify-write of NBL_MAILBOX_QINFO_MAP_REG_ARR
+ *
+ * Note: This RMW is currently safe because the two callers are strictly
+ * sequential: nbl_hw_cfg_mailbox_qinfo() runs at init, nbl_hw_set_mailbox_irq()
+ * runs at start. Future reset/hot-add/VF-reinit paths must preserve this
+ * ordering; do not introduce concurrent access to this register without
+ * making the RMW atomic (e.g., hold reg_lock across read+write, or use
+ * cached state instead of read).
+ */
+static void nbl_hw_set_mailbox_irq(struct nbl_hw_mgt *hw_mgt, u16 func_id,
+				   bool en_msix, u16 global_vec_id)
+{
+	u32 data = 0;
+
+	spin_lock(&hw_mgt->reg_lock);
+	nbl_hw_rd_regs(hw_mgt, NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id), &data,
+		       sizeof(data));
+	data &= ~(NBL_MAILBOX_QINFO_MAP_MSIX_IDX_MASK |
+		  NBL_MAILBOX_QINFO_MAP_MSIX_IDX_VALID_MASK);
+	if (en_msix)
+		data |= FIELD_PREP(NBL_MAILBOX_QINFO_MAP_MSIX_IDX_MASK,
+				   global_vec_id) |
+			FIELD_PREP(NBL_MAILBOX_QINFO_MAP_MSIX_IDX_VALID_MASK,
+				   1);
+
+	nbl_hw_wr_regs(hw_mgt, NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id), &data,
+		       sizeof(data));
+	spin_unlock(&hw_mgt->reg_lock);
+	nbl_flush_writes(hw_mgt);
+}
+
+static void nbl_hw_cfg_msix_map(struct nbl_hw_mgt *hw_mgt, u16 func_id,
+				bool valid, dma_addr_t dma_addr, u8 bus,
+				u8 devid, u8 function)
+{
+	struct nbl_function_msix_map function_msix_map;
+
+	memset(&function_msix_map, 0, sizeof(function_msix_map));
+	if (valid) {
+		function_msix_map.data[0] = lower_32_bits(dma_addr);
+		function_msix_map.data[1] = upper_32_bits(dma_addr);
+		/* use ctrl dev's bdf, because the dma memory was
+		 * allocated by it
+		 */
+		function_msix_map.data[2] =
+			FIELD_PREP(NBL_FUNCTION_MSIX_MAP_FUNCTION_MASK,
+				   function) |
+			FIELD_PREP(NBL_FUNCTION_MSIX_MAP_DEVID_MASK, devid) |
+			FIELD_PREP(NBL_FUNCTION_MSIX_MAP_BUS_MASK, bus) |
+			FIELD_PREP(NBL_FUNCTION_MSIX_MAP_VALID_MASK, 1);
+	} else {
+		/* Write full MSIX entry atomically under reg_lock,
+		 * hardware cannot read partial updated entry
+		 */
+		function_msix_map.data[0] = lower_32_bits(dma_addr);
+		function_msix_map.data[1] = upper_32_bits(dma_addr);
+		function_msix_map.data[2] = 0;
+	}
+
+	nbl_hw_wr_regs_lock(hw_mgt,
+			    NBL_PCOMPLETER_FUNCTION_MSIX_MAP_REG_ARR(func_id),
+			    function_msix_map.data, sizeof(function_msix_map));
+}
+
+static void nbl_hw_cfg_msix_info(struct nbl_hw_mgt *hw_mgt, u16 func_id,
+				 bool valid, u16 interrupt_id, u8 bus,
+				 u8 devid, u8 function, bool msix_mask_en)
+{
+	u32 host_msix_fid = 0;
+	struct nbl_host_msix_info msix_info;
+
+	memset(&msix_info, 0, sizeof(msix_info));
+	if (valid) {
+		host_msix_fid =
+			FIELD_PREP(NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_FID_MASK,
+				   func_id) |
+			FIELD_PREP(NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_VLD_MASK,
+				   1);
+
+		msix_info.data[1] =
+			FIELD_PREP(NBL_HOST_MSIX_INFO_FUNCTION_MASK, function) |
+			FIELD_PREP(NBL_HOST_MSIX_INFO_DEVID_MASK, devid) |
+			FIELD_PREP(NBL_HOST_MSIX_INFO_BUS_MASK, bus) |
+			FIELD_PREP(NBL_HOST_MSIX_INFO_VALID_MASK, 1);
+
+		if (msix_mask_en)
+			msix_info.data[1] |=
+			FIELD_PREP(NBL_HOST_MSIX_INFO_MSIX_MASK_EN_MASK, 1);
+	}
+	spin_lock(&hw_mgt->reg_lock);
+	/*
+	 * Programming order rule:
+	 * Enable: PADPT_HOST_MSIX_INFO -> PCOMPLETER_HOST_MSIX_FID_TABLE
+	 * Teardown: reverse order, clear FID VLD first to avoid inconsistent
+	 * state
+	 */
+	if (valid) {
+		nbl_hw_wr_regs(hw_mgt,
+			       NBL_PADPT_HOST_MSIX_INFO_REG_ARR(interrupt_id),
+			       msix_info.data, sizeof(msix_info));
+		nbl_hw_wr_regs(hw_mgt,
+			       NBL_PCOMPLETER_HOST_MSIX_FID_TABLE(interrupt_id),
+			       &host_msix_fid, sizeof(host_msix_fid));
+	} else {
+		nbl_hw_wr_regs(hw_mgt,
+			       NBL_PCOMPLETER_HOST_MSIX_FID_TABLE(interrupt_id),
+			       &host_msix_fid, sizeof(host_msix_fid));
+		nbl_hw_wr_regs(hw_mgt,
+			       NBL_PADPT_HOST_MSIX_INFO_REG_ARR(interrupt_id),
+			       msix_info.data, sizeof(msix_info));
+	}
+	spin_unlock(&hw_mgt->reg_lock);
+}
+
+static void nbl_hw_update_mailbox_queue_tail_ptr(struct nbl_hw_mgt *hw_mgt,
+						 u16 tail_ptr, u8 txrx)
+{
+	/* local_qid 0 and 1 denote rx and tx queue respectively */
+	u32 local_qid = txrx;
+	u32 value = ((u32)tail_ptr << 16) | local_qid;
+
+	/* wmb for doorbell */
+	wmb();
+	nbl_mbx_wr32(hw_mgt, NBL_MAILBOX_NOTIFY_ADDR, value);
+}
+
+static void nbl_hw_config_mailbox_rxq(struct nbl_hw_mgt *hw_mgt,
+				      dma_addr_t dma_addr, int size_bwid)
+{
+	struct nbl_mailbox_qinfo_cfg_table cfg_tbl;
+
+	memset(&cfg_tbl, 0, sizeof(cfg_tbl));
+	cfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1);
+	nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR,
+			      cfg_tbl.data, sizeof(cfg_tbl));
+
+	cfg_tbl.data[0] = lower_32_bits(dma_addr);
+	cfg_tbl.data[1] = upper_32_bits(dma_addr);
+	cfg_tbl.data[2] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_SIZE_BWID_MASK,
+				     size_bwid);
+	cfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 0) |
+			  FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_EN_MASK, 1);
+	nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR,
+			      cfg_tbl.data, sizeof(cfg_tbl));
+}
+
+static void nbl_hw_config_mailbox_txq(struct nbl_hw_mgt *hw_mgt,
+				      dma_addr_t dma_addr, int size_bwid)
+{
+	struct nbl_mailbox_qinfo_cfg_table cfg_tbl;
+
+	memset(&cfg_tbl, 0, sizeof(cfg_tbl));
+	cfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1);
+	nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR,
+			      cfg_tbl.data, sizeof(cfg_tbl));
+
+	cfg_tbl.data[0] = lower_32_bits(dma_addr);
+	cfg_tbl.data[1] = upper_32_bits(dma_addr);
+	cfg_tbl.data[2] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_SIZE_BWID_MASK,
+				     size_bwid);
+	cfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 0) |
+			  FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_EN_MASK, 1);
+	nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR,
+			      cfg_tbl.data, sizeof(cfg_tbl));
+}
+
+static void nbl_hw_stop_mailbox_rxq(struct nbl_hw_mgt *hw_mgt)
+{
+	struct nbl_mailbox_qinfo_cfg_table cfg_tbl;
+
+	memset(&cfg_tbl, 0, sizeof(cfg_tbl));
+	cfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1);
+	nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR,
+			      cfg_tbl.data, sizeof(cfg_tbl));
+}
+
+static void nbl_hw_stop_mailbox_txq(struct nbl_hw_mgt *hw_mgt)
+{
+	struct nbl_mailbox_qinfo_cfg_table cfg_tbl;
+
+	memset(&cfg_tbl, 0, sizeof(cfg_tbl));
+	cfg_tbl.data[3] = FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1);
+	nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR,
+			      cfg_tbl.data, sizeof(cfg_tbl));
+}
+
+static void nbl_hw_get_host_pf_mask(struct nbl_hw_mgt *hw_mgt, u32 *pf_mask)
+{
+	nbl_hw_rd_regs_lock(hw_mgt, NBL_PCIE_HOST_K_PF_MASK_REG, pf_mask,
+			    sizeof(*pf_mask));
+}
+
+static void nbl_hw_get_real_bus(struct nbl_hw_mgt *hw_mgt, u8 *bus)
+{
+	u32 data;
+
+	nbl_hw_rd_regs_lock(hw_mgt, NBL_PCIE_HOST_TL_CFG_BUSDEV, &data,
+			    sizeof(data));
+	*bus = FIELD_GET(NBL_PCIE_BUS_MASK, data);
+}
+
+static void nbl_hw_cfg_mailbox_qinfo(struct nbl_hw_mgt *hw_mgt, u16 func_id,
+				     u8 bus, u8 devid, u8 function)
+{
+	u32 data = 0;
+
+	data = FIELD_PREP(NBL_MAILBOX_QINFO_MAP_FUNCTION_MASK, function) |
+	       FIELD_PREP(NBL_MAILBOX_QINFO_MAP_DEVID_MASK, devid) |
+	       FIELD_PREP(NBL_MAILBOX_QINFO_MAP_BUS_MASK, bus);
+	nbl_hw_wr_regs_lock(hw_mgt, NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id),
+			    &data,
+			    sizeof(data));
+}
+
+/*
+ * Per hardware spec, registers reset to zero after cold boot, FLR and bus
+ * reset.Zero value indicates firmware not initialized; upper layer retries
+ * setup. No stale valid configuration can persist.
+ */
+static void nbl_hw_get_board_info(struct nbl_hw_mgt *hw_mgt,
+				  struct nbl_board_port_info *board_info)
+{
+	u32 data = 0;
+
+	nbl_hw_read_mbx_regs(hw_mgt, NBL_FW_BOARD_DW3_OFFSET, &data,
+			     sizeof(data));
+	board_info->eth_num = FIELD_GET(NBL_FW_BOARD_DW3_PORT_NUM_MASK, data);
+	board_info->eth_speed =
+		FIELD_GET(NBL_FW_BOARD_DW3_PORT_SPEED_MASK, data);
+	board_info->p4_version =
+		FIELD_GET(NBL_FW_BOARD_DW3_P4_VERSION_MASK, data);
+}
+
+static struct nbl_hw_ops hw_ops = {
+	.init_module = nbl_hw_init_module,
+	.deinit_module = nbl_hw_deinit_module,
+
+	.cfg_msix_map = nbl_hw_cfg_msix_map,
+	.cfg_msix_info = nbl_hw_cfg_msix_info,
+	.flush_write = nbl_flush_writes,
+
+	.update_mailbox_queue_tail_ptr = nbl_hw_update_mailbox_queue_tail_ptr,
+	.config_mailbox_rxq = nbl_hw_config_mailbox_rxq,
+	.config_mailbox_txq = nbl_hw_config_mailbox_txq,
+	.stop_mailbox_rxq = nbl_hw_stop_mailbox_rxq,
+	.stop_mailbox_txq = nbl_hw_stop_mailbox_txq,
+	.get_host_pf_mask = nbl_hw_get_host_pf_mask,
+	.get_real_bus = nbl_hw_get_real_bus,
+
+	.cfg_mailbox_qinfo = nbl_hw_cfg_mailbox_qinfo,
+	.set_mailbox_irq = nbl_hw_set_mailbox_irq,
+
+	.get_fw_eth_map = nbl_hw_get_fw_eth_map,
+	.get_board_info = nbl_hw_get_board_info,
+};
+
+/* Structure starts here, adding an op should not modify anything below */
+static struct nbl_hw_mgt *nbl_hw_setup_hw_mgt(struct nbl_common_info *common)
+{
+	struct device *dev = common->dev;
+	struct nbl_hw_mgt *hw_mgt;
+
+	hw_mgt = devm_kzalloc(dev, sizeof(*hw_mgt), GFP_KERNEL);
+	if (!hw_mgt)
+		return ERR_PTR(-ENOMEM);
+
+	hw_mgt->common = common;
+
+	return hw_mgt;
+}
+
+static struct nbl_hw_ops_tbl *nbl_hw_setup_ops(struct nbl_common_info *common,
+					       struct nbl_hw_mgt *hw_mgt)
+{
+	struct nbl_hw_ops_tbl *hw_ops_tbl;
+	struct device *dev;
+
+	dev = common->dev;
+	hw_ops_tbl = devm_kzalloc(dev, sizeof(*hw_ops_tbl), GFP_KERNEL);
+	if (!hw_ops_tbl)
+		return ERR_PTR(-ENOMEM);
+	if (!hw_ops.init_module || !hw_ops.deinit_module ||
+	    !hw_ops.cfg_msix_map || !hw_ops.cfg_msix_info ||
+	    !hw_ops.flush_write || !hw_ops.update_mailbox_queue_tail_ptr ||
+	    !hw_ops.config_mailbox_rxq || !hw_ops.config_mailbox_txq ||
+	    !hw_ops.stop_mailbox_rxq || !hw_ops.stop_mailbox_txq ||
+	    !hw_ops.get_host_pf_mask || !hw_ops.get_real_bus ||
+	    !hw_ops.cfg_mailbox_qinfo || !hw_ops.set_mailbox_irq ||
+	    !hw_ops.get_fw_eth_map || !hw_ops.get_board_info)
+		return ERR_PTR(-EINVAL);
+	hw_ops_tbl->ops = &hw_ops;
+	hw_ops_tbl->priv = hw_mgt;
+
+	return hw_ops_tbl;
+}
+
+static int nbl_pcim_request_selected_bars(struct pci_dev *pdev, u32 mask,
+					  const char *name)
+{
+	int bar;
+	int ret;
+
+	for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
+		if (!(mask & BIT(bar)))
+			continue;
+		ret = pcim_request_region(pdev, bar, name);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+int nbl_hw_init_leonis(struct nbl_adapter *adapter)
+{
+	struct nbl_common_info *common = &adapter->common;
+	struct pci_dev *pdev = common->pdev;
+	struct nbl_hw_ops_tbl *hw_ops_tbl = NULL;
+	struct nbl_hw_mgt *hw_mgt = NULL;
+	resource_size_t bar_len;
+	u32 bar_mask;
+	int ret;
+
+	hw_mgt = nbl_hw_setup_hw_mgt(common);
+	if (IS_ERR(hw_mgt)) {
+		ret = PTR_ERR(hw_mgt);
+		goto setup_mgt_fail;
+	}
+	bar_mask = BIT(NBL_MEMORY_BAR) | BIT(NBL_MAILBOX_BAR);
+	ret = nbl_pcim_request_selected_bars(pdev, bar_mask, NBL_DRIVER_NAME);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"Request memory bar failed, err = %d\n",
+			ret);
+		goto setup_mgt_fail;
+	}
+
+	bar_len = pci_resource_len(pdev, NBL_MEMORY_BAR);
+	if (!(pci_resource_flags(pdev, NBL_MEMORY_BAR) & IORESOURCE_MEM)) {
+		dev_err(&pdev->dev, "MEMORY BAR is not memory resource\n");
+		ret = -EINVAL;
+		goto setup_mgt_fail;
+	}
+	if (common->has_ctrl) {
+		/*
+		 * Hardware layout: MEMORY BAR total size is 64M.
+		 * The front NBL_HW_REG_SPACE_SIZE (32M) holds module
+		 * registers and table entries. The trailing NBL_RDMA_NOTIFY_LEN
+		 * bytes within this 32M region are reserved exclusively for
+		 * RDMA notification hardware logic.
+		 * Ethernet driver must avoid mapping this reserved range to
+		 * prevent x86 PAT attribute aliasing conflict between eth UC
+		 * mapping and RDMA driver WC mapping.
+		 * Mapping range starts at offset 0, length equals
+		 * NBL_HW_REG_SPACE_SIZE - NBL_RDMA_NOTIFY_LEN.
+		 */
+		if (bar_len < NBL_HW_REG_SPACE_SIZE) {
+			dev_err(&pdev->dev,
+				"MEMORY BAR len %pa too small for ctrl reg space\n",
+				&bar_len);
+			ret = -EINVAL;
+			goto setup_mgt_fail;
+		}
+		/*
+		 * Spec: Front 32MiB of 64M MEM BAR contains register space.
+		 * Skip trailing NBL_RDMA_NOTIFY_LEN bytes inside 32M boundary
+		 * never map that RDMA exclusive partition to avoid PAT conflict
+		 */
+		hw_mgt->hw_size = NBL_HW_REG_SPACE_SIZE  - NBL_RDMA_NOTIFY_LEN;
+		hw_mgt->hw_addr =
+			pcim_iomap(pdev, NBL_MEMORY_BAR,
+				   hw_mgt->hw_size);
+	} else {
+		if (bar_len < NBL_REG_NET_ONLY_LEN) {
+			dev_err(&pdev->dev,
+				"MEMORY BAR len %pa too small for net only reg space\n",
+				&bar_len);
+			ret = -EINVAL;
+			goto setup_mgt_fail;
+		}
+		hw_mgt->hw_size = NBL_REG_NET_ONLY_LEN;
+		hw_mgt->hw_addr = pcim_iomap(pdev, NBL_MEMORY_BAR,
+					     hw_mgt->hw_size);
+	}
+	if (!hw_mgt->hw_addr) {
+		dev_err(&pdev->dev, "MEMORY BAR pcim_iomap failed\n");
+		ret = -EIO;
+		goto setup_mgt_fail;
+	}
+
+	bar_len = pci_resource_len(pdev, NBL_MAILBOX_BAR);
+	if (!(pci_resource_flags(pdev, NBL_MAILBOX_BAR) & IORESOURCE_MEM)) {
+		dev_err(&pdev->dev, "MAILBOX BAR is not memory resource\n");
+		ret = -EINVAL;
+		goto setup_mgt_fail;
+	}
+	if (bar_len == 0) {
+		dev_err(&pdev->dev, "MAILBOX BAR length is zero\n");
+		ret = -EINVAL;
+		goto setup_mgt_fail;
+	}
+	hw_mgt->mailbox_bar_hw_addr = pcim_iomap(pdev, NBL_MAILBOX_BAR,
+						 bar_len);
+	if (!hw_mgt->mailbox_bar_hw_addr) {
+		dev_err(&pdev->dev, "MAILBOX BAR pcim_iomap failed\n");
+		ret = -EIO;
+		goto setup_mgt_fail;
+	}
+
+	spin_lock_init(&hw_mgt->reg_lock);
+
+	hw_ops_tbl = nbl_hw_setup_ops(common, hw_mgt);
+	if (IS_ERR(hw_ops_tbl)) {
+		ret = PTR_ERR(hw_ops_tbl);
+		goto setup_mgt_fail;
+	}
+	adapter->intf.hw_ops_tbl = hw_ops_tbl;
+	adapter->core.hw_mgt = hw_mgt;
+
+	return 0;
+
+setup_mgt_fail:
+	return ret;
+}
+
+void nbl_hw_remove_leonis(struct nbl_adapter *adapter)
+{
+	/* All BAR mappings & PCI regions are managed by pcim/devres,
+	 * no manual iounmap / release required
+	 */
+}
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.h
new file mode 100644
index 0000000000000..e096f2169180b
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.h
@@ -0,0 +1,355 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_HW_LEONIS_H_
+#define _NBL_HW_LEONIS_H_
+
+#include <linux/types.h>
+
+#include "../../nbl_include/nbl_include.h"
+#include "../nbl_hw_reg.h"
+
+#define NBL_DRIVER_STATUS_REG			0x1300444
+#define NBL_DRIVER_STATUS_BIT			16
+
+/*  ----------  REG BASE ADDR  ----------  */
+/* Interface modules base addr */
+#define NBL_INTF_HOST_PCOMPLETER_BASE		0x00f08000
+#define NBL_INTF_HOST_PADPT_BASE		0x00f4c000
+#define NBL_INTF_HOST_MAILBOX_BASE		0x00fb0000
+#define NBL_INTF_HOST_PCIE_BASE			0X01504000
+/* DP modules base addr */
+#define NBL_DP_USTORE_BASE			0x00104000
+#define NBL_DP_UQM_BASE				0x00114000
+#define NBL_DP_UPED_BASE			0x0015c000
+#define NBL_DP_UVN_BASE				0x00244000
+#define NBL_DP_DSCH_BASE			0x00404000
+#define NBL_DP_SHAPING_BASE			0x00504000
+#define NBL_DP_DVN_BASE				0x00514000
+#define NBL_DP_DSTORE_BASE			0x00704000
+#define NBL_DP_DQM_BASE				0x00714000
+#define NBL_DP_DPED_BASE			0x0075c000
+#define NBL_DP_DDMUX_BASE			0x00984000
+/*  --------  MAILBOX BAR2 -----  */
+#define NBL_MAILBOX_NOTIFY_ADDR			0x00000000
+#define NBL_MAILBOX_BAR_REG			0x00000000
+#define NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR	0x10
+#define NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR	0x20
+#define NBL_MAILBOX_QINFO_CFG_DBG_TABLE_ADDR	0x30
+
+/*  --------  MAILBOX  --------  */
+
+/* mailbox BAR qinfo_cfg_table */
+#define MAILBOX_QINFO_CFG_TABLE_DWLEN	4
+/* data[2] */
+#define NBL_MAILBOX_QINFO_CFG_QUEUE_SIZE_BWID_MASK	GENMASK(3, 0)
+/* data[3] */
+#define NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK		BIT(0)
+#define NBL_MAILBOX_QINFO_CFG_QUEUE_EN_MASK		BIT(1)
+#define NBL_MAILBOX_QINFO_CFG_DIF_ERR_MASK		BIT(2)
+#define NBL_MAILBOX_QINFO_CFG_PTR_ERR_MASK		BIT(3)
+struct nbl_mailbox_qinfo_cfg_table {
+	u32 data[MAILBOX_QINFO_CFG_TABLE_DWLEN];
+};
+
+/*  --------  MAILBOX BAR0 -----  */
+/* mailbox qinfo_map_table */
+#define NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id) \
+	(NBL_INTF_HOST_MAILBOX_BASE + 0x00001000 + (func_id) * sizeof(u32))
+
+/* MAILBOX qinfo_map_table */
+#define NBL_MAILBOX_QINFO_MAP_FUNCTION_MASK		GENMASK(2, 0)
+#define NBL_MAILBOX_QINFO_MAP_DEVID_MASK		GENMASK(7, 3)
+#define NBL_MAILBOX_QINFO_MAP_BUS_MASK			GENMASK(15, 8)
+#define NBL_MAILBOX_QINFO_MAP_MSIX_IDX_MASK		GENMASK(28, 16)
+#define NBL_MAILBOX_QINFO_MAP_MSIX_IDX_VALID_MASK	BIT(29)
+
+/*  --------  HOST_PCIE  --------  */
+#define NBL_PCIE_HOST_K_PF_MASK_REG (NBL_INTF_HOST_PCIE_BASE + 0x00001004)
+#define NBL_PCIE_HOST_TL_CFG_BUSDEV (NBL_INTF_HOST_PCIE_BASE + 0x11040)
+
+#define NBL_PCIE_BUS_MASK	GENMASK(12, 5)
+
+/*  --------  HOST_PADPT  --------  */
+#define NBL_HOST_PADPT_HOST_CFG_FC_PD_DN (NBL_INTF_HOST_PADPT_BASE + 0x00000160)
+#define NBL_HOST_PADPT_HOST_CFG_FC_PH_DN (NBL_INTF_HOST_PADPT_BASE + 0x00000164)
+#define NBL_HOST_PADPT_HOST_CFG_FC_NPH_DN \
+	(NBL_INTF_HOST_PADPT_BASE + 0x0000016C)
+#define NBL_HOST_PADPT_HOST_CFG_FC_CPLH_UP \
+	(NBL_INTF_HOST_PADPT_BASE + 0x00000170)
+
+#define NBL_HOST_PADPT_CFG_FC_CPLH_UP_VAL      0x10400
+#define NBL_HOST_PADPT_CFG_FC_PD_DN_VAL        0x10080
+#define NBL_HOST_PADPT_CFG_FC_PH_DN_VAL        0x10010
+#define NBL_HOST_PADPT_CFG_FC_NPH_DN_VAL       0x10010
+/* host_padpt host_msix_info */
+#define NBL_PADPT_HOST_MSIX_INFO_REG_ARR(vector_id) \
+	(NBL_INTF_HOST_PADPT_BASE + 0x00010000 +    \
+	 (vector_id) * sizeof(struct nbl_host_msix_info))
+
+#define NBL_HOST_MSIX_INFO_DWLEN	2
+/* data[0] */
+#define NBL_HOST_MSIX_INFO_INTRL_PNUM_MASK GENMASK(15, 0)
+#define NBL_HOST_MSIX_INFO_INTRL_RATE_MASK GENMASK(31, 16)
+/* data[1] */
+#define NBL_HOST_MSIX_INFO_FUNCTION_MASK GENMASK(2, 0)
+#define NBL_HOST_MSIX_INFO_DEVID_MASK GENMASK(7, 3)
+#define NBL_HOST_MSIX_INFO_BUS_MASK GENMASK(15, 8)
+#define NBL_HOST_MSIX_INFO_VALID_MASK BIT(16)
+#define NBL_HOST_MSIX_INFO_MSIX_MASK_EN_MASK BIT(17)
+struct nbl_host_msix_info {
+	u32 data[NBL_HOST_MSIX_INFO_DWLEN];
+};
+
+/*  --------  HOST_PCOMPLETER  --------  */
+/* pcompleter_host pcompleter_host_virtio_qid_map_table */
+#define NBL_PCOMPLETER_FUNCTION_MSIX_MAP_REG_ARR(i)   \
+	(NBL_INTF_HOST_PCOMPLETER_BASE + 0x00004000 + \
+	 (i) * sizeof(struct nbl_function_msix_map))
+#define NBL_PCOMPLETER_HOST_MSIX_FID_TABLE(i) \
+	(NBL_INTF_HOST_PCOMPLETER_BASE + 0x0003a000 + (i) * sizeof(u32))
+
+#define NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_FID_MASK  GENMASK(9, 0)
+#define NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_VLD_MASK  BIT(10)
+
+#define NBL_FUNC_MSIX_MAP_DWLEN		4
+/* data[2] */
+#define NBL_FUNCTION_MSIX_MAP_FUNCTION_MASK GENMASK(2, 0)
+#define NBL_FUNCTION_MSIX_MAP_DEVID_MASK GENMASK(7, 3)
+#define NBL_FUNCTION_MSIX_MAP_BUS_MASK GENMASK(15, 8)
+#define NBL_FUNCTION_MSIX_MAP_VALID_MASK BIT(16)
+struct nbl_function_msix_map {
+	u32 data[NBL_FUNC_MSIX_MAP_DWLEN];
+};
+
+/*  ----------  DPED  ----------  */
+#define NBL_DPED_VLAN_OFFSET		(NBL_DP_DPED_BASE + 0x000003F4)
+#define NBL_DPED_DSCP_OFFSET_0		(NBL_DP_DPED_BASE + 0x000003F8)
+#define NBL_DPED_DSCP_OFFSET_1		(NBL_DP_DPED_BASE + 0x000003FC)
+/* DPED hw_edt_prof/ UPED hw_edt_prof */
+
+#define NBL_DPED_L4_CK_CMD_40_ADDR 0x75c338
+#define NBL_DPED_L4_CK_CMD_40_DEPTH 1
+#define NBL_DPED_L4_CK_CMD_40_WIDTH 32
+#define NBL_DPED_L4_CK_CMD_40_DWLEN 1
+
+#define NBL_DPED_L4_CK_CMD_40_VALUE_MASK	GENMASK(7, 0)
+#define NBL_DPED_L4_CK_CMD_40_LEN_IN_OFT_MASK	GENMASK(14, 8)
+#define NBL_DPED_L4_CK_CMD_40_LEN_PHID_MASK	GENMASK(16, 15)
+#define NBL_DPED_L4_CK_CMD_40_LEN_VLD_MASK	BIT(17)
+#define NBL_DPED_L4_CK_CMD_40_DATA_VLD_MASK	GENMASK(18, 18)
+#define NBL_DPED_L4_CK_CMD_40_IN_OFT_MASK	GENMASK(25, 19)
+#define NBL_DPED_L4_CK_CMD_40_PHID_MASK		GENMASK(27, 26)
+#define NBL_DPED_L4_CK_CMD_40_FLAG_MASK		BIT(28)
+#define NBL_DPED_L4_CK_CMD_40_MODE_MASK		BIT(29)
+#define NBL_DPED_L4_CK_CMD_40_RSV_MASK		BIT(30)
+#define NBL_DPED_L4_CK_CMD_40_EN_MASK		BIT(31)
+
+/*  ----------  UPED  ----------  */
+/* UPED uped_hw_edt_prof */
+#define NBL_UPED_HW_EDT_PROF_TABLE(i) \
+	(NBL_DP_UPED_BASE + 0x00001000 + (i) * sizeof(u32))
+#define NBL_UPED_V4_TCP_IDX		5
+#define NBL_UPED_V6_TCP_IDX		6
+#define NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK GENMASK(3, 2)
+
+/*  ----------  DSCH  ----------  */
+#define NBL_DSCH_PSHA_EN_MASK  GENMASK(3, 0)
+/* DSCH dsch maxqid */
+#define NBL_DSCH_HOST_QID_MAX (NBL_DP_DSCH_BASE + 0x00000118)
+#define NBL_DSCH_VN_QUANTA_ADDR (NBL_DP_DSCH_BASE + 0x00000134)
+
+#define NBL_MAX_QUEUE_ID	0x7ff
+#define NBL_HOST_QUANTA		0x8000
+#define NBL_ECPU_QUANTA		0x1000
+
+#define NBL_DSCH_VN_QUANTA_H_QUA_MASK GENMASK(15, 0)
+#define NBL_DSCH_VN_QUANTA_E_QUA_MASK GENMASK(31, 16)
+
+/*  ----------  DVN  ----------  */
+/* DVN dvn_queue_table */
+#define NBL_DVN_ECPU_QUEUE_NUM			(NBL_DP_DVN_BASE + 0x0000041C)
+#define NBL_DVN_DESCREQ_NUM_CFG			(NBL_DP_DVN_BASE + 0x00000430)
+#define NBL_DVN_DESC_WR_MERGE_TIMEOUT		(NBL_DP_DVN_BASE + 0x00000480)
+#define NBL_DVN_DIF_REQ_RD_RO_FLAG		(NBL_DP_DVN_BASE + 0x0000045C)
+
+#define DEFAULT_DVN_DESCREQ_NUMCFG		0x03
+#define DEFAULT_DVN_100G_DESCREQ_NUMCFG		0x07
+
+#define DEFAULT_DVN_DESC_WR_MERGE_TIMEOUT_MAX	0x3FF
+
+/* spilit ring descreq_num 0:8,1:16 */
+#define NBL_DVN_DESCREQ_NUM_CFG_AVRING_DESREQ_NUM_CFG_MASK BIT(0)
+/* packet ring descreq_num
+ * 0:8,1:12,2:16;3:20,4:24,5:26;6:32,7:32
+ */
+#define NBL_DVN_DESCREQ_NUM_CFG_PACKED_L1_NUM_MASK GENMASK(6, 4)
+
+#define NBL_DVN_DESC_WR_MERGE_TIMEOUT_CFG_CYCLE_MASK GENMASK(9, 0)
+
+#define NBL_DVN_DIF_REQ_RD_RO_FLAG_DESC_RO_EN_MASK BIT(0)
+#define NBL_DVN_DIF_REQ_RD_RO_FLAG_DATA_RO_EN_MASK BIT(1)
+#define NBL_DVN_DIF_REQ_RD_RO_FLAG_AVRING_RO_EN_MASK BIT(2)
+
+/*  ----------  UVN  ----------  */
+/* UVN uvn_queue_table */
+
+#define NBL_UVN_DESC_RD_WAIT			(NBL_DP_UVN_BASE + 0x0000020C)
+#define NBL_UVN_QUEUE_ERR_MASK			(NBL_DP_UVN_BASE + 0x00000224)
+#define NBL_UVN_ECPU_QUEUE_NUM			(NBL_DP_UVN_BASE + 0x0000023C)
+#define NBL_UVN_DESC_WR_TIMEOUT			(NBL_DP_UVN_BASE + 0x00000214)
+#define NBL_UVN_DIF_REQ_RO_FLAG			(NBL_DP_UVN_BASE + 0x00000250)
+#define NBL_UVN_DESC_PREFETCH_INIT		(NBL_DP_UVN_BASE + 0x00000204)
+#define NBL_UVN_DESC_PREFETCH_NUM		4
+
+#define NBL_UVN_DIF_REQ_RO_FLAG_AVAIL_RD_MASK BIT(0)
+#define NBL_UVN_DIF_REQ_RO_FLAG_DESC_RD_MASK BIT(1)
+#define NBL_UVN_DIF_REQ_RO_FLAG_PKT_WR_MASK BIT(2)
+#define NBL_UVN_DIF_REQ_RO_FLAG_DESC_WR_MASK BIT(3)
+
+#define NBL_UVN_DESC_WR_TIMEOUT_NUM_MASK GENMASK(14, 0)
+#define NBL_UVN_DESC_WR_TIMEOUT_MASK_MASK BIT(15)
+
+#define NBL_UVN_QUEUE_ERR_MASK_DIF_ERR_MASK BIT(5)
+
+#define NBL_UVN_DESC_PREFETCH_INIT_NUM_MASK GENMASK(7, 0)
+#define NBL_UVN_DESC_PREFETCH_INIT_SEL_MASK BIT(16)
+
+#define NBL_UVN_DESC_WR_TIMEOUT_VAL 0x12c
+/* 200us = 200000ns / 1.67ns per tick = 119760 ticks */
+#define NBL_UVN_DESC_RD_WAIT_TICKS 119760
+
+/*  --------  USTORE  --------  */
+#define NBL_USTORE_PKT_LEN_ADDR (NBL_DP_USTORE_BASE + 0x00000108)
+#define NBL_USTORE_PORT_DROP_TH_REG_ARR(port_id) \
+	(NBL_DP_USTORE_BASE + 0x00000150 + (port_id) * sizeof(u32))
+#define NBL_USTORE_BUF_PORT_DROP_PKT(eth_id) \
+	(NBL_DP_USTORE_BASE + 0x00002500 + (eth_id) * sizeof(u32))
+#define NBL_USTORE_BUF_PORT_TRUN_PKT(eth_id) \
+	(NBL_DP_USTORE_BASE + 0x00002540 + (eth_id) * sizeof(u32))
+
+#define NBL_USTORE_SINGLE_ETH_DROP_TH		0xC80
+#define NBL_USTORE_DUAL_ETH_DROP_TH		0x640
+#define NBL_USTORE_QUAD_ETH_DROP_TH		0x320
+
+/* USTORE pkt_len */
+#define NBL_USTORE_PKT_LEN_MIN_MASK GENMASK(6, 0)
+
+/* USTORE port_drop_th */
+#define NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK GENMASK(11, 0)
+#define NBL_USTORE_PORT_DROP_TH_EN_MASK BIT(31)
+
+/* UQM*/
+#define NBL_UQM_QUE_TYPE			(NBL_DP_UQM_BASE + 0x0000013c)
+#define NBL_UQM_DROP_PKT_CNT			(NBL_DP_UQM_BASE + 0x000009C0)
+#define NBL_UQM_DROP_PKT_SLICE_CNT		(NBL_DP_UQM_BASE + 0x000009C4)
+#define NBL_UQM_DROP_PKT_LEN_ADD_CNT		(NBL_DP_UQM_BASE + 0x000009C8)
+#define NBL_UQM_DROP_HEAD_PNTR_ADD_CNT		(NBL_DP_UQM_BASE + 0x000009CC)
+#define NBL_UQM_DROP_WEIGHT_ADD_CNT		(NBL_DP_UQM_BASE + 0x000009D0)
+#define NBL_UQM_PORT_DROP_PKT_CNT		(NBL_DP_UQM_BASE + 0x000009D4)
+#define NBL_UQM_PORT_DROP_PKT_SLICE_CNT		(NBL_DP_UQM_BASE + 0x000009F4)
+#define NBL_UQM_PORT_DROP_PKT_LEN_ADD_CNT	(NBL_DP_UQM_BASE + 0x00000A14)
+#define NBL_UQM_PORT_DROP_HEAD_PNTR_ADD_CNT	(NBL_DP_UQM_BASE + 0x00000A34)
+#define NBL_UQM_PORT_DROP_WEIGHT_ADD_CNT	(NBL_DP_UQM_BASE + 0x00000A54)
+#define NBL_UQM_FWD_DROP_CNT			(NBL_DP_UQM_BASE + 0x00000A80)
+#define NBL_UQM_DPORT_DROP_CNT			(NBL_DP_UQM_BASE + 0x00000B74)
+
+#define NBL_UQM_PORT_DROP_DEPTH			6
+#define NBL_UQM_DPORT_DROP_DEPTH		16
+
+/*  ---------  SHAPING  ---------  */
+#define NBL_SHAPING_NET(i)                  \
+	(NBL_DP_SHAPING_BASE + 0x00001800 + \
+	 (i) * sizeof(struct nbl_shaping_net))
+
+/* cir 1, bandwidth 1kB/s in protol environment */
+/* cir 1, bandwidth 1Mb/s */
+#define NBL_LR_LEONIS_NET_BUCKET_DEPTH		9600
+#define NBL_SHAPING_DPORT_ADDR 0x504700
+#define NBL_SHAPING_DPORT_DWLEN 4
+#define NBL_SHAPING_DPORT_REG(r) \
+	(NBL_SHAPING_DPORT_ADDR + (NBL_SHAPING_DPORT_DWLEN * 4) * (r))
+#define NBL_SHAPING_DVN_DPORT_ADDR 0x504750
+#define NBL_SHAPING_DVN_DPORT_DWLEN 4
+#define NBL_SHAPING_DVN_DPORT_REG(r) \
+	(NBL_SHAPING_DVN_DPORT_ADDR + (NBL_SHAPING_DVN_DPORT_DWLEN * 4) * (r))
+#define NBL_DSCH_PSHA_EN_ADDR 0x404314
+#define NBL_SHAPING_NET_ADDR 0x505800
+#define NBL_SHAPING_NET_DWLEN 4
+#define NBL_SHAPING_NET_REG(r) \
+	(NBL_SHAPING_NET_ADDR + (NBL_SHAPING_NET_DWLEN * 4) * (r))
+
+#define DPORT_VALID_MASK (0x1ULL << 0)
+#define DPORT_DEPTH_MASK (0x7FFFFULL << 1)	// [19:1]
+#define DPORT_CIR_MASK (0x7FFFFULL << 20)	// [38:20]
+#define DPORT_PIR_MASK (0x7FFFFULL << 39)	// [57:39]
+#define DPORT_CBS_MASK_LOW (0x3FULL << 58)		// [63:58]
+#define DPORT_CBS_MASK_HIGH (0x7FFFULL << (0)) // [78:64] -> high[14:0]
+#define DPORT_PBS_MASK (0x1FFFFFULL << (79 - 64)) // [99:79] -> high[35:15]
+
+/* SHAPING shaping_net */
+struct nbl_shaping_net_u {
+	u32 data[NBL_SHAPING_NET_DWLEN];
+};
+
+struct nbl_shaping_dport_u {
+	u32 data[NBL_SHAPING_DPORT_DWLEN];
+};
+
+struct nbl_shaping_dvn_dport_u {
+	u32 data[NBL_SHAPING_DVN_DPORT_DWLEN];
+};
+
+/*  --------  DSTORE  --------  */
+#define NBL_DSTORE_D_DPORT_FC_TH_ADDR  0x704600
+#define NBL_DSTORE_D_DPORT_FC_TH_DEPTH 5
+#define NBL_DSTORE_D_DPORT_FC_TH_WIDTH 32
+#define NBL_DSTORE_D_DPORT_FC_TH_DWLEN 1
+
+#define NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK GENMASK(10, 0)
+#define NBL_DSTORE_D_DPORT_FC_XON_TH_MASK GENMASK(26, 16)
+#define NBL_DSTORE_D_DPORT_FC_FC_EN_MASK BIT(31)
+
+#define NBL_DSTORE_D_DPORT_FC_TH_REG(r)  \
+	(NBL_DSTORE_D_DPORT_FC_TH_ADDR + \
+	 (NBL_DSTORE_D_DPORT_FC_TH_DWLEN * 4) * (r))
+#define NBL_DSTORE_PORT_DROP_TH_ADDR 0x704150
+#define NBL_DSTORE_PORT_DROP_TH_DEPTH 6
+#define NBL_DSTORE_PORT_DROP_TH_WIDTH 32
+#define NBL_DSTORE_PORT_DROP_TH_DWLEN 1
+
+#define NBL_DSTORE_PORT_DROP_DISC_TH_MASK GENMASK(9, 0)
+#define NBL_DSTORE_PORT_DROP_EN_MASK BIT(31)
+
+#define NBL_DSTORE_DROP_XOFF_TH			0xC8
+#define NBL_DSTORE_DROP_XON_TH			0x64
+
+#define NBL_DSTORE_DROP_XOFF_TH_100G		0x1F4
+#define NBL_DSTORE_DROP_XON_TH_100G		0x12C
+
+#define NBL_DSTORE_DISC_BP_TH (NBL_DP_DSTORE_BASE + 0x00000630)
+
+#define NBL_DSTORE_DISC_BP_TH_EN_MASK BIT(31)
+
+#define NBL_DSTORE_PORT_DROP_TH_REG(r)  \
+	(NBL_DSTORE_PORT_DROP_TH_ADDR + \
+	 (NBL_DSTORE_PORT_DROP_TH_DWLEN * 4) * (r))
+
+#define NBL_FW_BOARD_CONFIG			0x200
+#define NBL_FW_BOARD_DW3_OFFSET			(NBL_FW_BOARD_CONFIG + 12)
+#define NBL_FW_BOARD_DW6_OFFSET			(NBL_FW_BOARD_CONFIG + 24)
+
+#define NBL_FW_BOARD_DW3_PORT_TYPE_MASK BIT(0)
+#define NBL_FW_BOARD_DW3_PORT_NUM_MASK GENMASK(7, 1)
+#define NBL_FW_BOARD_DW3_PORT_SPEED_MASK GENMASK(9, 8)
+#define NBL_FW_BOARD_DW3_GPIO_TYPE_MASK GENMASK(12, 10)
+#define NBL_FW_BOARD_DW3_P4_VERSION_MASK GENMASK(13, 13)
+
+#define NBL_FW_BOARD_DW6_LANE_BITMAP_MASK GENMASK(7, 0)
+#define NBL_FW_BOARD_DW6_ETH_BITMAP_MASK GENMASK(15, 8)
+
+#define NBL_LEONIS_QUIRKS_OFFSET	0x00000140
+
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.c
new file mode 100644
index 0000000000000..dadd74f054eee
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.c
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+#include <linux/device.h>
+#include <linux/pci.h>
+#include <linux/bits.h>
+#include "nbl_resource_leonis.h"
+
+static struct nbl_resource_ops res_ops = {
+	.get_vsi_id = nbl_res_func_id_to_vsi_id,
+	.get_eth_id = nbl_res_get_eth_id,
+	.cfg_msix_map = nbl_res_intr_cfg_msix_map,
+	.destroy_msix_map = nbl_res_intr_destroy_msix_map,
+	.set_mailbox_irq = nbl_res_intr_set_mailbox_irq,
+	.init_module = nbl_res_chip_init_module,
+	.deinit_module = nbl_res_chip_deinit_module,
+};
+
+static struct nbl_resource_mgt *
+nbl_res_setup_res_mgt(struct nbl_common_info *common)
+{
+	struct nbl_resource_info *resource_info;
+	struct nbl_resource_mgt *res_mgt;
+	struct device *dev = common->dev;
+
+	res_mgt = devm_kzalloc(dev, sizeof(*res_mgt), GFP_KERNEL);
+	if (!res_mgt)
+		return ERR_PTR(-ENOMEM);
+	res_mgt->common = common;
+
+	resource_info =
+		devm_kzalloc(dev, sizeof(*resource_info), GFP_KERNEL);
+	if (!resource_info)
+		return ERR_PTR(-ENOMEM);
+	res_mgt->resource_info = resource_info;
+
+	return res_mgt;
+}
+
+static struct nbl_resource_ops_tbl *
+nbl_res_setup_ops(struct device *dev, struct nbl_resource_mgt *res_mgt)
+{
+	struct nbl_resource_ops_tbl *res_ops_tbl;
+
+	res_ops_tbl = devm_kzalloc(dev, sizeof(*res_ops_tbl), GFP_KERNEL);
+	if (!res_ops_tbl)
+		return ERR_PTR(-ENOMEM);
+	if (!res_ops.get_vsi_id || !res_ops.get_eth_id  ||
+	    !res_ops.cfg_msix_map || !res_ops.destroy_msix_map ||
+	    !res_ops.set_mailbox_irq || !res_ops.init_module ||
+	    !res_ops.deinit_module)
+		return ERR_PTR(-EINVAL);
+	res_ops_tbl->ops = &res_ops;
+	res_ops_tbl->priv = res_mgt;
+
+	return res_ops_tbl;
+}
+
+static int nbl_res_ctrl_dev_setup_eth_info(struct nbl_resource_mgt *res_mgt)
+{
+	struct nbl_hw_ops *hw_ops = res_mgt->hw_ops_tbl->ops;
+	struct device *dev = res_mgt->common->dev;
+	struct nbl_eth_info *eth_info;
+	u32 eth_bitmap = 0, eth_id;
+	u32 eth_num = 0;
+	u32 fw_port_num;
+	int i;
+
+	eth_info = devm_kzalloc(dev, sizeof(*eth_info), GFP_KERNEL);
+	if (!eth_info)
+		return -ENOMEM;
+
+	res_mgt->resource_info->eth_info = eth_info;
+
+	fw_port_num = res_mgt->resource_info->board_info.eth_num;
+	hw_ops->get_fw_eth_map(res_mgt->hw_ops_tbl->priv, &eth_bitmap);
+	if (eth_bitmap & ~((1 << NBL_MAX_ETHERNET) - 1)) {
+		dev_err(dev, "FW reported invalid eth_bitmap 0x%x\n",
+			eth_bitmap);
+		return -EINVAL;
+	}
+	if (fw_port_num != hweight32(eth_bitmap)) {
+		dev_err(dev, "FW inconsistency: port_num=%u, bitmap=0x%x\n",
+			fw_port_num, eth_bitmap);
+		return -EINVAL;
+	}
+	/* Reject zero ports early to avoid delayed topology mismatch error */
+	if (fw_port_num == 0) {
+		dev_warn(dev, "FW reports zero ethernet ports, defer probe waiting FW ready\n");
+		return -EPROBE_DEFER;
+	}
+	if (fw_port_num > NBL_MAX_ETHERNET || fw_port_num == 3) {
+		dev_err(dev, "FW reports %u Ethernet ports, not supported\n",
+			fw_port_num);
+		return -EINVAL;
+	}
+	eth_info->eth_num = fw_port_num;
+	/* Intentional design constraint: each PF maps to exactly one
+	 * Ethernet port. This couples PF identity to port identity
+	 * and is required by nbl_res_get_eth_id() which indexes
+	 * eth_info->eth_id[] by relative PF id.
+	 */
+	if (res_mgt->resource_info->max_pf != eth_info->eth_num) {
+		dev_err(dev, "Invalid PF-to-port topology: max_pf=%u, eth_num=%u\n",
+			res_mgt->resource_info->max_pf, eth_info->eth_num);
+		return -EINVAL;
+	}
+
+	/*
+	 * Original comment said dual-port board eth_id fixed to 0,2;
+	 * Code accepts any contiguous valid bitmap bits (0/1 or 0/2 etc).
+	 * Firmware only needs to report correct count of active ports,
+	 * no hard-coded fixed bit positions required.
+	 */
+	for (i = 0; i < NBL_MAX_ETHERNET; i++) {
+		if ((1 << i) & eth_bitmap) {
+			set_bit(i, eth_info->eth_bitmap);
+			eth_info->eth_id[eth_num] = i;
+			eth_info->logic_eth_id[i] = eth_num;
+			eth_num++;
+		}
+	}
+
+	for (i = 0; i < res_mgt->resource_info->max_pf; i++) {
+		eth_id = eth_info->eth_id[i];
+		eth_info->pf_bitmap[eth_id] |= BIT(i);
+	}
+
+	return 0;
+}
+
+static int nbl_res_ctrl_dev_sriov_info_init(struct nbl_resource_mgt *res_mgt)
+{
+	struct nbl_hw_ops *hw_ops = res_mgt->hw_ops_tbl->ops;
+	struct nbl_hw_mgt *p = res_mgt->hw_ops_tbl->priv;
+	struct nbl_common_info *common = res_mgt->common;
+	struct nbl_sriov_info *sriov_info;
+	struct device *dev = common->dev;
+	u8 hw_bus = 0;
+	u16 function;
+	u16 func_id;
+
+	hw_ops->get_real_bus(p, &hw_bus);
+	if (common->function + res_mgt->resource_info->max_pf > NBL_MAX_PF) {
+		dev_err(dev, "PF count exceeds available function space\n");
+		return -EINVAL;
+	}
+	sriov_info = devm_kcalloc(dev, res_mgt->resource_info->max_pf,
+				  sizeof(*sriov_info), GFP_KERNEL);
+	if (!sriov_info)
+		return -ENOMEM;
+
+	res_mgt->resource_info->sriov_info = sriov_info;
+	/*
+	 * common->hw_bus supplies bus number for channel mailbox QINFO mapping.
+	 * Execution order guarantee: this assignment runs before
+	 * cfg_chan_qinfo_map_table() in nbl_dev_start(), only executed
+	 * on control PF path.
+	 */
+	common->hw_bus = hw_bus;
+
+	for (func_id = 0; func_id < res_mgt->resource_info->max_pf; func_id++) {
+		sriov_info = res_mgt->resource_info->sriov_info + func_id;
+		function = common->function + func_id;
+		sriov_info->bdf = PCI_DEVID(common->hw_bus,
+					    PCI_DEVFN(common->devid, function));
+	}
+
+	return 0;
+}
+
+static int nbl_res_ctrl_dev_vsi_info_init(struct nbl_resource_mgt *res_mgt)
+{
+	struct nbl_eth_info *eth_info = res_mgt->resource_info->eth_info;
+	struct nbl_common_info *common = res_mgt->common;
+	struct device *dev = common->dev;
+	struct nbl_vsi_info *vsi_info;
+	int i;
+
+	vsi_info = devm_kzalloc(dev, sizeof(*vsi_info), GFP_KERNEL);
+	if (!vsi_info)
+		return -ENOMEM;
+
+	res_mgt->resource_info->vsi_info = vsi_info;
+	/*
+	 * case 1 one port(1pf)
+	 * pf0 (NBL_VSI_SERV_PF_DATA_TYPE) vsi is 0
+	 * case 2 two port(2pf)
+	 * pf0,pf1(NBL_VSI_SERV_PF_DATA_TYPE) vsi is 0,512
+	 * case 3 four port(4pf)
+	 * pf0,pf1,pf2,pf3(NBL_VSI_SERV_PF_DATA_TYPE) vsi is 0,256,512,768
+	 */
+
+	vsi_info->num = eth_info->eth_num;
+	/*
+	 * eth_num can be 1/2/4:
+	 * - 2/4 ports use dedicated gap constants;
+	 * - 1 port falls back to NBL_DEFAULT_VSI_ID_GAP (1024).
+	 * All three values produce valid base_id offsets.
+	 */
+	for (i = 0; i < vsi_info->num; i++) {
+		vsi_info->serv_info[i][NBL_VSI_SERV_PF_DATA_TYPE].base_id =
+			i * nbl_vsi_id_gap(vsi_info->num);
+		vsi_info->serv_info[i][NBL_VSI_SERV_PF_DATA_TYPE].num = 1;
+	}
+
+	return 0;
+}
+
+static int nbl_res_init_pf_num(struct nbl_resource_mgt *res_mgt)
+{
+	struct nbl_hw_ops *hw_ops = res_mgt->hw_ops_tbl->ops;
+	u32 pf_mask = 0;
+	u32 pf_num = 0;
+	int i;
+
+	hw_ops->get_host_pf_mask(res_mgt->hw_ops_tbl->priv, &pf_mask);
+	if (pf_mask == 0) {
+		dev_warn(res_mgt->common->dev, "FW not ready, defer probe\n");
+		return -EPROBE_DEFER;
+	}
+	/*
+	 * Hardware guarantees pf_mask has contiguous cleared bits
+	 * starting from bit 0 (e.g., 0b11111100, not 0b01010101).
+	 * This allows us to stop at the first set bit.
+	 */
+	for (i = 0; i < NBL_MAX_PF; i++) {
+		if (!(pf_mask & (1 << i)))
+			pf_num++;
+	}
+	if (pf_num == 0 || pf_num > NBL_MAX_ETHERNET || pf_num == 3) {
+		dev_err(res_mgt->common->dev, "Invalid pf_num=%u, reject resource init\n",
+			pf_num);
+		return -EINVAL;
+	}
+	res_mgt->resource_info->max_pf = pf_num;
+
+	return 0;
+}
+
+static void nbl_res_init_board_info(struct nbl_resource_mgt *res_mgt)
+{
+	struct nbl_hw_ops *hw_ops = res_mgt->hw_ops_tbl->ops;
+
+	hw_ops->get_board_info(res_mgt->hw_ops_tbl->priv,
+			       &res_mgt->resource_info->board_info);
+}
+
+static int nbl_res_start(struct nbl_resource_mgt *res_mgt)
+{
+	struct nbl_common_info *common = res_mgt->common;
+	int ret = 0;
+
+	if (common->has_ctrl) {
+		nbl_res_init_board_info(res_mgt);
+
+		ret = nbl_res_init_pf_num(res_mgt);
+		if (ret)
+			return ret;
+
+		ret = nbl_res_ctrl_dev_sriov_info_init(res_mgt);
+		if (ret)
+			return ret;
+
+		ret = nbl_res_ctrl_dev_setup_eth_info(res_mgt);
+		if (ret)
+			return ret;
+
+		ret = nbl_res_ctrl_dev_vsi_info_init(res_mgt);
+		if (ret)
+			return ret;
+
+		ret = nbl_intr_mgt_start(res_mgt);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int nbl_res_init_leonis(struct nbl_adapter *adap)
+{
+	struct nbl_channel_ops_tbl *chan_ops_tbl = adap->intf.channel_ops_tbl;
+	struct nbl_hw_ops_tbl *hw_ops_tbl = adap->intf.hw_ops_tbl;
+	struct nbl_common_info *common = &adap->common;
+	struct nbl_resource_ops_tbl *res_ops_tbl;
+	struct device *dev = &adap->pdev->dev;
+	struct nbl_resource_mgt *res_mgt;
+	int ret;
+
+	res_mgt = nbl_res_setup_res_mgt(common);
+	if (IS_ERR(res_mgt)) {
+		ret = PTR_ERR(res_mgt);
+		return ret;
+	}
+	res_mgt->chan_ops_tbl = chan_ops_tbl;
+	res_mgt->hw_ops_tbl = hw_ops_tbl;
+
+	ret = nbl_res_start(res_mgt);
+	if (ret)
+		return ret;
+
+	res_ops_tbl = nbl_res_setup_ops(dev, res_mgt);
+	if (IS_ERR(res_ops_tbl)) {
+		ret = PTR_ERR(res_ops_tbl);
+		return ret;
+	}
+	adap->intf.resource_ops_tbl = res_ops_tbl;
+	adap->core.res_mgt = res_mgt;
+
+	return 0;
+}
+
+void nbl_res_remove_leonis(struct nbl_adapter *adap)
+{
+	/*
+	 * No resource release here because all memory uses devm managed
+	 * allocation
+	 */
+}
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.h
new file mode 100644
index 0000000000000..f9a53b5d4627f
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resource_leonis.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_RESOURCE_LEONIS_H_
+#define _NBL_RESOURCE_LEONIS_H_
+
+#include "../nbl_resource.h"
+#include "../nbl_interrupt.h"
+#include "../nbl_chip.h"
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_reg.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_reg.h
new file mode 100644
index 0000000000000..0026cee101aa9
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_reg.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_HW_REG_H_
+#define _NBL_HW_REG_H_
+
+#include <linux/types.h>
+
+#include "../nbl_include/nbl_def_channel.h"
+#include "../nbl_include/nbl_def_hw.h"
+#include "../nbl_include/nbl_def_common.h"
+#include "../nbl_core.h"
+
+#define NBL_MEMORY_BAR				0
+#define NBL_MAILBOX_BAR				2
+#define NBL_RDMA_NOTIFY_LEN			(8ULL << 10)
+#define NBL_REG_NET_ONLY_LEN			(8ULL << 10)
+#define NBL_HW_DUMMY_REG			0x1300904
+/*
+ * PCI MEMORY BAR total size: 64MiB.
+ * First 32MiB: module registers + table entry address range.
+ * Region after 32MiB is unused for ethernet driver.
+ */
+#define NBL_HW_REG_SPACE_SIZE			(32ULL << 20)
+
+struct nbl_hw_mgt {
+	struct nbl_common_info *common;
+	u8 __iomem *hw_addr;
+	u8 __iomem *mailbox_bar_hw_addr;
+	resource_size_t hw_size;
+	spinlock_t reg_lock; /* Protect reg access */
+};
+
+static inline u32 rd32(u8 __iomem *addr, u64 reg)
+{
+	return readl(addr + reg);
+}
+
+static inline void wr32(u8 __iomem *addr, u64 reg, u32 value)
+{
+	writel(value, addr + reg);
+}
+
+static inline void nbl_hw_wr32(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 value)
+{
+	/* Used for emu, make sure that we won't write too frequently */
+	wr32(hw_mgt->hw_addr, reg, value);
+}
+
+static inline u32 nbl_hw_rd32(struct nbl_hw_mgt *hw_mgt, u64 reg)
+{
+	return rd32(hw_mgt->hw_addr, reg);
+}
+
+static inline void nbl_mbx_wr32(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 value)
+{
+	writel(value, hw_mgt->mailbox_bar_hw_addr + reg);
+}
+
+/*
+ * Only call this when has_ctrl=true, which maps enough space
+ * (bar_len - 8192) to cover NBL_HW_DUMMY_REG (0x1300904).
+ * The flow/design guarantees this is only called in the
+ * has_ctrl path.
+ */
+static inline void nbl_flush_writes(struct nbl_hw_mgt *hw_mgt)
+{
+	nbl_hw_rd32(hw_mgt, NBL_HW_DUMMY_REG);
+}
+
+static inline u32 nbl_mbx_rd32(struct nbl_hw_mgt *hw_mgt, u64 reg)
+{
+	return readl(hw_mgt->mailbox_bar_hw_addr + reg);
+}
+
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.c
new file mode 100644
index 0000000000000..34410d0d90224
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.c
@@ -0,0 +1,405 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/bitfield.h>
+#include "nbl_interrupt.h"
+
+#define NBL_MSIX_DMA_SYNC_MIN_US	100
+#define NBL_MSIX_DMA_SYNC_MAX_US	120
+
+static void nbl_intr_release_bitmap(struct nbl_resource_mgt *res_mgt,
+				    u16 *vec_buf, u16 cnt)
+{
+	struct nbl_interrupt_mgt *intr_mgt = res_mgt->intr_mgt;
+	u16 bit;
+	u16 i;
+
+	if (!vec_buf || cnt == 0)
+		return;
+
+	for (i = 0; i < cnt; i++) {
+		u16 intr_index = vec_buf[i];
+
+		if (intr_index >= NBL_NET_INTR_BASE) {
+			bit = intr_index - NBL_NET_INTR_BASE;
+			if (bit < NBL_MAX_NET_INTERRUPT)
+				clear_bit(bit, intr_mgt->intr_net_bmap);
+			else
+				dev_warn(res_mgt->common->dev,
+					 "invalid net intr index %u\n",
+					 intr_index);
+		} else {
+			if (intr_index < NBL_MAX_OTHER_INTERRUPT)
+				clear_bit(intr_index,
+					  intr_mgt->intr_other_bmap);
+			else
+				dev_warn(res_mgt->common->dev,
+					 "invalid other intr index %u\n",
+					 intr_index);
+		}
+	}
+}
+
+/**
+ * nbl_res_intr_destroy_msix_map - tear down MSI-X mapping for specified PF
+ * @res_mgt: resource management instance
+ * @func_id: target function identifier
+ *
+ * Clear interrupt bitmaps and disable MSI-X via hardware callbacks.
+ * DMA coherent table buffer uses devm allocation and remains valid
+ * until device removal to avoid IOMMU faults caused by in-flight hardware DMA.
+ *
+ * Caller note: this function has no internal locking. Serialization
+ * must be guaranteed at upper dispatch layer.
+ * Caller must ensure all concurrent resource operations are excluded.
+ *
+ * Caller guarantee: hardware cannot issue outstanding DMA to these regions,
+ * all relevant IRQs have been released or not yet configured.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+int nbl_res_intr_destroy_msix_map(struct nbl_resource_mgt *res_mgt,
+				  u16 func_id)
+{
+	struct nbl_interrupt_mgt *intr_mgt = res_mgt->intr_mgt;
+	struct nbl_hw_ops *hw_ops = res_mgt->hw_ops_tbl->ops;
+	struct device *dev = res_mgt->common->dev;
+	struct nbl_msix_map_table *msix_map_table;
+	u16 *interrupts;
+	u16 intr_num, i;
+
+	if (!res_mgt->common->has_ctrl)
+		return -EINVAL;
+	if (func_id >= NBL_MAX_FUNC) {
+		dev_err(dev, "Invalid func_id %u\n", func_id);
+		return -EINVAL;
+	}
+
+	intr_num = intr_mgt->func_intr_res[func_id].num_interrupts;
+	interrupts = intr_mgt->func_intr_res[func_id].interrupts;
+	msix_map_table = &intr_mgt->func_intr_res[func_id].msix_map_table;
+
+	if (!interrupts || !msix_map_table->base_addr) {
+		dev_dbg(dev, "No interrupt resources for func %u\n", func_id);
+		return 0;
+	}
+
+	for (i = 0; i < intr_num; i++) {
+		hw_ops->cfg_msix_info(res_mgt->hw_ops_tbl->priv,
+				      func_id, false, interrupts[i],
+				      0, 0, 0, false);
+	}
+
+	nbl_intr_release_bitmap(res_mgt, interrupts, intr_num);
+
+	hw_ops->cfg_msix_map(res_mgt->hw_ops_tbl->priv, func_id,
+			     false, 0, 0, 0, 0);
+	hw_ops->flush_write(res_mgt->hw_ops_tbl->priv);
+
+	/*
+	 * Hardware provides no idle status register for MSIX map DMA engine.
+	 * Use bounded sleep to mitigate race between posted MMIO disable writes
+	 * and ongoing in-flight table read DMA access.
+	 * After sleep, hardware no longer performs DMA access to MSIX table,
+	 * safe to free DMA memory now.
+	 */
+	usleep_range(NBL_MSIX_DMA_SYNC_MIN_US, NBL_MSIX_DMA_SYNC_MAX_US);
+
+	/*
+	 * Now safe to release old MSIX DMA memory, prevents devres accumulation
+	 * leak Since hardware DMA has quiesced after sleep, no IOMMU fault risk
+	 * anymore.
+	 */
+	if (msix_map_table->base_addr) {
+		dmam_free_coherent(dev, msix_map_table->size,
+				   msix_map_table->base_addr,
+				   msix_map_table->dma);
+	}
+
+	/* Release runtime allocated interrupt vector buffer */
+	kfree(intr_mgt->func_intr_res[func_id].interrupts);
+	intr_mgt->func_intr_res[func_id].interrupts = NULL;
+	intr_mgt->func_intr_res[func_id].num_interrupts = 0;
+	intr_mgt->func_intr_res[func_id].num_net_interrupts = 0;
+
+	/* Clear stale MSIX table pointers for safety */
+	msix_map_table->base_addr = NULL;
+	msix_map_table->dma = 0;
+	msix_map_table->size = 0;
+
+	return 0;
+}
+
+/**
+ * nbl_res_intr_cfg_msix_map - allocate & program MSI-X mapping table
+ * @res_mgt: resource management instance
+ * @func_id: target function identifier
+ * @num_net_msix: required net data interrupt vectors
+ * @num_others_msix: required control interrupt vectors
+ * @net_msix_mask_en: enable mask for net interrupt entries
+ *
+ * Allocate interrupt vectors and coherent DMA table in advance;
+ * only destroy old configuration once all allocations succeed.
+ *
+ * Note: There exists a transient window after tearing down old MSI-X hardware
+ * state before programming new mapping. Atomic table swap is unsupported on
+ * current silicon, this gap is accepted as hardware limitation.
+ *
+ * Caller note: this function has no internal locking. Serialization
+ * must be guaranteed at upper dispatch layer.
+ *
+ * Old MSIX table memory will be explicitly freed inside destroy_msix_map()
+ * after waiting for hardware DMA quiesce, so repeated reconfiguration will not
+ * accumulate devres-managed DMA memory.
+ * Return: 0 on success, negative errno on failure
+ */
+int nbl_res_intr_cfg_msix_map(struct nbl_resource_mgt *res_mgt,
+			      u16 func_id, u16 num_net_msix,
+			      u16 num_others_msix,
+			      bool net_msix_mask_en)
+{
+	struct nbl_interrupt_mgt *intr_mgt = res_mgt->intr_mgt;
+	struct nbl_hw_ops *hw_ops = res_mgt->hw_ops_tbl->ops;
+	struct nbl_common_info *common = res_mgt->common;
+	struct nbl_msix_map_table *tmp_msix_tbl = NULL;
+	struct nbl_msix_map_table *official_tbl;
+	struct nbl_msix_map *msix_map_entries;
+	struct device *dev = common->dev;
+	u16 requested, intr_index;
+	u8 bus, devid, function;
+	bool msix_mask_en = false;
+	u16 *tmp_interrupts = NULL;
+	u16 global_vec;
+	int ret = 0;
+	u16 i;
+
+	if (!common->has_ctrl)
+		return -EINVAL;
+	if (func_id >= NBL_MAX_FUNC) {
+		dev_err(dev, "Invalid func_id %u\n", func_id);
+		return -EINVAL;
+	}
+	if (num_net_msix == 0 && num_others_msix == 0) {
+		dev_err(dev, "MSI-X vector count cannot both be zero\n");
+		return -EINVAL;
+	}
+	if (num_net_msix > NBL_MSIX_MAP_TABLE_MAX_ENTRIES ||
+	    num_others_msix > NBL_MSIX_MAP_TABLE_MAX_ENTRIES) {
+		dev_err(dev, "MSI-X count out of limit: net=%u, others=%u\n",
+			num_net_msix, num_others_msix);
+		return -EINVAL;
+	}
+
+	if (check_add_overflow(num_net_msix, num_others_msix, &requested) ||
+	    requested > NBL_MSIX_MAP_TABLE_MAX_ENTRIES) {
+		dev_err(dev, "Total MSI-X vectors %u exceeds maximum %u\n",
+			requested, NBL_MSIX_MAP_TABLE_MAX_ENTRIES);
+		return -EINVAL;
+	}
+
+	ret = nbl_res_func_id_to_bdf(res_mgt, func_id, &bus, &devid, &function);
+	if (ret)
+		return ret;
+
+	/*
+	 * Phase1: Pre-allocate ALL new resources first.
+	 * Do NOT destroy old configuration before all allocations succeed.
+	 */
+	tmp_msix_tbl = kzalloc_obj(*tmp_msix_tbl);
+	if (!tmp_msix_tbl) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	tmp_msix_tbl->size =
+		sizeof(struct nbl_msix_map) * NBL_MSIX_MAP_TABLE_MAX_ENTRIES;
+	/*
+	 * Hardware requires fixed stride table layout; allocate full size
+	 * even when only partial entries are used. Memory managed by devm.
+	 */
+	tmp_msix_tbl->base_addr = dmam_alloc_coherent(dev, tmp_msix_tbl->size,
+						      &tmp_msix_tbl->dma,
+						      GFP_KERNEL);
+	if (!tmp_msix_tbl->base_addr) {
+		dev_err(dev, "Failed to allocate DMA memory for MSIX table\n");
+		ret = -ENOMEM;
+		goto free_tmp_tbl;
+	}
+
+	tmp_interrupts = kcalloc(requested, sizeof(tmp_interrupts[0]),
+				 GFP_KERNEL);
+	if (!tmp_interrupts) {
+		ret = -ENOMEM;
+		goto free_tmp_tbl;
+	}
+
+	/* Allocate net interrupt vectors */
+	for (i = 0; i < num_net_msix; i++) {
+		intr_index = find_first_zero_bit(intr_mgt->intr_net_bmap,
+						 NBL_MAX_NET_INTERRUPT);
+		if (intr_index == NBL_MAX_NET_INTERRUPT) {
+			dev_err(dev, "No free net interrupt vectors left\n");
+			ret = -EAGAIN;
+			goto release_vecs;
+		}
+		tmp_interrupts[i] = intr_index + NBL_NET_INTR_BASE;
+		set_bit(intr_index, intr_mgt->intr_net_bmap);
+	}
+
+	/* Allocate other interrupt vectors */
+	for (; i < requested; i++) {
+		intr_index =
+			find_first_zero_bit(intr_mgt->intr_other_bmap,
+					    NBL_MAX_OTHER_INTERRUPT);
+		if (intr_index == NBL_MAX_OTHER_INTERRUPT) {
+			dev_err(dev, "No free control interrupt vectors left\n");
+			ret = -EAGAIN;
+			goto release_vecs;
+		}
+		tmp_interrupts[i] = intr_index;
+		set_bit(intr_index, intr_mgt->intr_other_bmap);
+	}
+
+	/*
+	 * Phase2: All new resource allocation succeeded.
+	 * Now tear down old MSIX hardware configuration.
+	 */
+	ret = nbl_res_intr_destroy_msix_map(res_mgt, func_id);
+	if (ret)
+		goto release_vecs;
+
+	/* Swap temporary resources into official entry */
+	official_tbl = &intr_mgt->func_intr_res[func_id].msix_map_table;
+	official_tbl->base_addr = tmp_msix_tbl->base_addr;
+	official_tbl->dma = tmp_msix_tbl->dma;
+	official_tbl->size = tmp_msix_tbl->size;
+	kfree(tmp_msix_tbl);
+	tmp_msix_tbl = NULL;
+
+	intr_mgt->func_intr_res[func_id].interrupts = tmp_interrupts;
+	intr_mgt->func_intr_res[func_id].num_interrupts = requested;
+	intr_mgt->func_intr_res[func_id].num_net_interrupts = num_net_msix;
+	tmp_interrupts = NULL;
+
+	/* Fill MSIX map table and program hardware */
+	msix_map_entries = official_tbl->base_addr;
+	for (i = 0; i < requested; i++) {
+		global_vec = intr_mgt->func_intr_res[func_id].interrupts[i];
+		msix_map_entries[i].data =
+			cpu_to_le16(FIELD_PREP(NBL_MSIX_MAP_VALID_MASK, 1) |
+				    FIELD_PREP(NBL_MSIX_MAP_INDEX_MASK,
+					       global_vec));
+
+		msix_mask_en = (i < num_net_msix && net_msix_mask_en) ? true :
+									false;
+		hw_ops->cfg_msix_info(res_mgt->hw_ops_tbl->priv,
+				      func_id, true, global_vec,
+				      bus, devid, function,
+				      msix_mask_en);
+	}
+
+	/* Flush CPU writes to coherent memory before hardware DMA access */
+	dma_wmb();
+
+	hw_ops->cfg_msix_map(res_mgt->hw_ops_tbl->priv, func_id,
+			     true, official_tbl->dma, common->hw_bus,
+			     common->devid, common->function);
+	hw_ops->flush_write(res_mgt->hw_ops_tbl->priv);
+
+out:
+	return ret;
+
+release_vecs:
+	nbl_intr_release_bitmap(res_mgt, tmp_interrupts, i);
+free_tmp_tbl:
+	/* Release DMA buffer allocated by dmam_alloc_coherent first */
+	if (tmp_msix_tbl && tmp_msix_tbl->base_addr) {
+		dmam_free_coherent(dev, tmp_msix_tbl->size,
+				   tmp_msix_tbl->base_addr,
+				   tmp_msix_tbl->dma);
+	}
+	kfree(tmp_msix_tbl);
+	kfree(tmp_interrupts);
+	tmp_msix_tbl = NULL;
+	tmp_interrupts = NULL;
+	goto out;
+}
+
+/**
+ * nbl_res_intr_set_mailbox_irq - bind mailbox IRQ to specified vector
+ * @res_mgt: resource management instance
+ * @func_id: target function identifier
+ * @vector_id: index inside local interrupt array
+ * @en_msix: enable/disable mailbox interrupt
+ *
+ * Caller note: this function has no internal locking. Serialization
+ * must be guaranteed at upper dispatch layer.
+ *
+ * Return: 0 on success, negative errno on parameter check or hw failure
+ */
+int nbl_res_intr_set_mailbox_irq(struct nbl_resource_mgt *res_mgt,
+				 u16 func_id, u16 vector_id,
+				 bool en_msix)
+{
+	struct nbl_interrupt_mgt *intr_mgt = res_mgt->intr_mgt;
+	struct nbl_hw_ops *hw_ops = res_mgt->hw_ops_tbl->ops;
+	struct nbl_common_info *common = res_mgt->common;
+	struct device *dev = common->dev;
+	u16 global_vec_id;
+
+	if (!common->has_ctrl)
+		return -EINVAL;
+	if (func_id >= NBL_MAX_FUNC) {
+		dev_err(dev, "func_id %u out of range\n", func_id);
+		return -EINVAL;
+	}
+	if (!intr_mgt->func_intr_res[func_id].interrupts) {
+		dev_err(dev, "func %u MSIX map not configured\n", func_id);
+		return -ENODEV;
+	}
+	if (vector_id >= intr_mgt->func_intr_res[func_id].num_interrupts) {
+		dev_err(dev, "vector_id %u out of range (max %u)\n",
+			vector_id,
+			intr_mgt->func_intr_res[func_id].num_interrupts - 1);
+		return -EINVAL;
+	}
+
+	global_vec_id = intr_mgt->func_intr_res[func_id].interrupts[vector_id];
+	hw_ops->set_mailbox_irq(res_mgt->hw_ops_tbl->priv, func_id,
+				en_msix, global_vec_id);
+
+	return 0;
+}
+
+static struct nbl_interrupt_mgt *nbl_intr_setup_mgt(struct device *dev)
+{
+	struct nbl_interrupt_mgt *intr_mgt;
+
+	intr_mgt = devm_kzalloc(dev, sizeof(*intr_mgt), GFP_KERNEL);
+	if (!intr_mgt)
+		return ERR_PTR(-ENOMEM);
+
+	bitmap_zero(intr_mgt->intr_net_bmap, NBL_MAX_NET_INTERRUPT);
+	bitmap_zero(intr_mgt->intr_other_bmap, NBL_MAX_OTHER_INTERRUPT);
+
+	return intr_mgt;
+}
+
+int nbl_intr_mgt_start(struct nbl_resource_mgt *res_mgt)
+{
+	struct device *dev = res_mgt->common->dev;
+	struct nbl_interrupt_mgt *intr_mgt;
+	int ret;
+
+	intr_mgt = nbl_intr_setup_mgt(dev);
+	if (IS_ERR(intr_mgt)) {
+		ret = PTR_ERR(intr_mgt);
+		return ret;
+	}
+	res_mgt->intr_mgt = intr_mgt;
+	return 0;
+}
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.h
new file mode 100644
index 0000000000000..a4b2de5b2351f
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_INTERRUPT_H_
+#define _NBL_INTERRUPT_H_
+
+#include "nbl_resource.h"
+
+#define NBL_MSIX_MAP_TABLE_MAX_ENTRIES	1024
+int nbl_res_intr_destroy_msix_map(struct nbl_resource_mgt *res_mgt,
+				  u16 func_id);
+int nbl_res_intr_cfg_msix_map(struct nbl_resource_mgt *res_mgt,
+			      u16 func_id, u16 num_net_msix,
+			      u16 num_others_msix,
+			      bool net_msix_mask_en);
+int nbl_res_intr_set_mailbox_irq(struct nbl_resource_mgt *res_mgt,
+				 u16 func_id, u16 vector_id,
+				 bool en_msix);
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c
new file mode 100644
index 0000000000000..a6e2c7eeed0e3
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#include <linux/pci.h>
+#include "nbl_resource.h"
+
+int nbl_res_func_id_to_vsi_id(struct nbl_resource_mgt *res_mgt, u16 func_id,
+			      u16 type, u16 *vsi_id)
+{
+	struct nbl_vsi_info *vsi_info = res_mgt->resource_info->vsi_info;
+	enum nbl_vsi_serv_type dst_type = NBL_VSI_SERV_PF_DATA_TYPE;
+	struct nbl_common_info *common = res_mgt->common;
+	struct device *dev = res_mgt->common->dev;
+	int pfid = func_id;
+	u32 rel_pf_id;
+	int ret;
+
+	if (!common->has_ctrl || !vsi_id) {
+		dev_dbg(dev, "No control plane or null vsi output ptr\n");
+		return -EINVAL;
+	}
+	ret = nbl_common_func_id_to_rel_pf_id(common, pfid, &rel_pf_id);
+	if (ret)
+		return ret;
+	if (rel_pf_id >= vsi_info->num) {
+		dev_err(dev, "PF %d (diff=%u) exceeds vsi_info->num (%u)\n",
+			pfid, rel_pf_id, vsi_info->num);
+		return -EINVAL;
+	}
+
+	ret = nbl_res_pf_dev_vsi_type_to_hw_vsi_type(res_mgt, type, &dst_type);
+	if (ret) {
+		dev_err(dev, "Invalid vsi type %u func_id %u\n", type, func_id);
+		return ret;
+	}
+	*vsi_id = vsi_info->serv_info[rel_pf_id][dst_type].base_id;
+	return 0;
+}
+
+int nbl_res_vsi_id_to_pf_id(struct nbl_resource_mgt *res_mgt, u16 vsi_id)
+{
+	struct nbl_vsi_info *vsi_info = res_mgt->resource_info->vsi_info;
+	struct nbl_common_info *common = res_mgt->common;
+	struct device *dev = res_mgt->common->dev;
+	int j = NBL_VSI_SERV_PF_DATA_TYPE;
+	int pf_id, i;
+
+	if (!common->has_ctrl) {
+		dev_dbg(dev, "No control plane available\n");
+		return -EINVAL;
+	}
+	for (i = 0; i < vsi_info->num; i++) {
+		if (vsi_id >= vsi_info->serv_info[i][j].base_id &&
+		    (vsi_id < vsi_info->serv_info[i][j].base_id +
+					vsi_info->serv_info[i][j].num)) {
+			pf_id = i + common->mgt_pf;
+			if (pf_id >= NBL_MAX_PF) {
+				dev_err(dev, "PF ID overflow\n");
+				return -ERANGE;
+			}
+			return pf_id;
+		}
+	}
+
+	dev_dbg(dev, "VSI ID %u not found\n", vsi_id);
+	return -ENOENT;
+}
+
+int nbl_res_func_id_to_bdf(struct nbl_resource_mgt *res_mgt, u16 func_id,
+			   u8 *bus, u8 *dev, u8 *function)
+{
+	struct nbl_common_info *common = res_mgt->common;
+	struct nbl_sriov_info *sriov_info;
+	int pfid = func_id;
+	u8 pf_bus, devfn;
+	u32 rel_pf_id;
+	int ret;
+
+	if (!common->has_ctrl || !bus || !dev || !function)
+		return -EINVAL;
+	ret = nbl_common_func_id_to_rel_pf_id(common, pfid, &rel_pf_id);
+	if (ret)
+		return ret;
+	if (rel_pf_id >= res_mgt->resource_info->max_pf) {
+		dev_err(common->dev, "PF ID %u exceeds maximum supported PF count %u\n",
+			pfid, res_mgt->resource_info->max_pf);
+		return -ERANGE;
+	}
+	sriov_info = res_mgt->resource_info->sriov_info + rel_pf_id;
+	pf_bus = PCI_BUS_NUM(sriov_info->bdf);
+	devfn = sriov_info->bdf & 0xff;
+	*bus = pf_bus;
+	*dev = PCI_SLOT(devfn);
+	*function = PCI_FUNC(devfn);
+
+	return 0;
+}
+
+int nbl_res_get_eth_id(struct nbl_resource_mgt *res_mgt, u16 func_id,
+		       u16 vsi_id, u8 *eth_num, u8 *eth_id, u8 *logic_eth_id)
+{
+	struct nbl_eth_info *eth_info = res_mgt->resource_info->eth_info;
+	struct nbl_common_info *common = res_mgt->common;
+	struct device *dev = res_mgt->common->dev;
+	int pfid = func_id;
+	int rel_pf_id;
+	int abs_pf_id;
+
+	if (!common->has_ctrl || !eth_num || !eth_id || !logic_eth_id)
+		return -EINVAL;
+	abs_pf_id = nbl_res_vsi_id_to_pf_id(res_mgt, vsi_id);
+	if (abs_pf_id < 0) {
+		dev_err(dev, "Failed to get PF ID from VSI ID %u\n", vsi_id);
+		return -EINVAL;
+	}
+	if (abs_pf_id != pfid) {
+		dev_err(dev, "func_id %u does not match pf derived from vsi_id %u\n",
+			pfid, vsi_id);
+		return -EINVAL;
+	}
+	rel_pf_id = abs_pf_id - common->mgt_pf;
+
+	if (rel_pf_id >= eth_info->eth_num) {
+		dev_err(dev, "rel_pf_id %d out of range [0, %u)\n",
+			rel_pf_id, eth_info->eth_num);
+		return -ERANGE;
+	}
+
+	*eth_num = eth_info->eth_num;
+	*eth_id = eth_info->eth_id[rel_pf_id];
+	*logic_eth_id = rel_pf_id;
+	return 0;
+}
+
+int nbl_res_pf_dev_vsi_type_to_hw_vsi_type(struct nbl_resource_mgt *res_mgt,
+					   u16 src_type,
+					   enum nbl_vsi_serv_type *dst_type)
+{
+	switch (src_type) {
+	case NBL_VSI_DATA:
+		*dst_type = NBL_VSI_SERV_PF_DATA_TYPE;
+		return 0;
+	default:
+		dev_err_once(res_mgt->common->dev,
+			     "Unsupported vsi src_type %u\n", src_type);
+		return -EINVAL;
+	}
+}
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h
new file mode 100644
index 0000000000000..7f7624e9215db
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_RESOURCE_H_
+#define _NBL_RESOURCE_H_
+
+#include <linux/types.h>
+
+#include "../nbl_include/nbl_include.h"
+#include "../nbl_include/nbl_def_channel.h"
+#include "../nbl_include/nbl_def_hw.h"
+#include "../nbl_include/nbl_def_resource.h"
+#include "../nbl_include/nbl_def_common.h"
+#include "../nbl_core.h"
+
+struct nbl_resource_mgt;
+
+/* --------- INTERRUPT ---------- */
+#define NBL_MAX_OTHER_INTERRUPT			1024
+#define NBL_MAX_NET_INTERRUPT			4096
+#define NBL_NET_INTR_BASE		NBL_MAX_OTHER_INTERRUPT
+
+#define NBL_MSIX_MAP_VALID_MASK		BIT(0)
+#define NBL_MSIX_MAP_INDEX_MASK		GENMASK(13, 1)
+#define NBL_MSIX_MAP_RSV_MASK		GENMASK(15, 14)
+
+struct nbl_msix_map {
+	__le16 data;
+};
+
+struct nbl_msix_map_table {
+	struct nbl_msix_map *base_addr;
+	dma_addr_t dma;
+	size_t size;
+};
+
+struct nbl_func_interrupt_resource_mng {
+	u16 num_interrupts;
+	u16 num_net_interrupts;
+	u16 *interrupts;
+	struct nbl_msix_map_table msix_map_table;
+};
+
+struct nbl_interrupt_mgt {
+	DECLARE_BITMAP(intr_net_bmap, NBL_MAX_NET_INTERRUPT);
+	DECLARE_BITMAP(intr_other_bmap, NBL_MAX_OTHER_INTERRUPT);
+	struct nbl_func_interrupt_resource_mng func_intr_res[NBL_MAX_FUNC];
+};
+
+/* --------- INFO ---------- */
+struct nbl_sriov_info {
+	unsigned int bdf;
+};
+
+struct nbl_eth_info {
+	DECLARE_BITMAP(eth_bitmap, NBL_MAX_ETHERNET);
+	u8 pf_bitmap[NBL_MAX_ETHERNET];
+	u8 eth_num;
+	u8 resv[3];
+	u8 eth_id[NBL_MAX_PF];
+	u8 logic_eth_id[NBL_MAX_PF];
+};
+
+enum nbl_vsi_serv_type {
+	NBL_VSI_SERV_PF_DATA_TYPE,
+	NBL_VSI_SERV_MAX_TYPE,
+};
+
+struct nbl_vsi_serv_info {
+	u16 base_id;
+	u16 num;
+};
+
+struct nbl_vsi_info {
+	u16 num;
+	struct nbl_vsi_serv_info serv_info[NBL_MAX_ETHERNET]
+					  [NBL_VSI_SERV_MAX_TYPE];
+};
+
+struct nbl_resource_info {
+	struct nbl_sriov_info *sriov_info;
+	struct nbl_eth_info *eth_info;
+	struct nbl_vsi_info *vsi_info;
+	u8 max_pf;
+	struct nbl_board_port_info board_info;
+};
+
+struct nbl_resource_mgt {
+	struct nbl_common_info *common;
+	struct nbl_resource_info *resource_info;
+	struct nbl_channel_ops_tbl *chan_ops_tbl;
+	struct nbl_hw_ops_tbl *hw_ops_tbl;
+	struct nbl_interrupt_mgt *intr_mgt;
+};
+
+int nbl_res_vsi_id_to_pf_id(struct nbl_resource_mgt *res_mgt, u16 vsi_id);
+int nbl_res_func_id_to_vsi_id(struct nbl_resource_mgt *res_mgt, u16 func_id,
+			      u16 type, u16 *vsi_id);
+int nbl_res_func_id_to_bdf(struct nbl_resource_mgt *res_mgt, u16 func_id,
+			   u8 *bus, u8 *dev, u8 *function);
+int nbl_res_get_eth_id(struct nbl_resource_mgt *res_mgt, u16 func_id,
+		       u16 vsi_id, u8 *eth_num, u8 *eth_id, u8 *logic_eth_id);
+int nbl_intr_mgt_start(struct nbl_resource_mgt *res_mgt);
+int nbl_res_pf_dev_vsi_type_to_hw_vsi_type(struct nbl_resource_mgt *res_mgt,
+					   u16 src_type,
+					   enum nbl_vsi_serv_type *dst_type);
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h
new file mode 100644
index 0000000000000..4db33a6e01512
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h
@@ -0,0 +1,379 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_DEF_CHANNEL_H_
+#define _NBL_DEF_CHANNEL_H_
+
+#include <linux/types.h>
+
+struct nbl_channel_mgt;
+struct nbl_adapter;
+
+typedef void (*nbl_chan_resp)(void *, u16, u16, void *, u32);
+
+enum {
+	NBL_CHAN_RESP_OK = 0,
+	NBL_CHAN_RESP_ERR = 1,
+	NBL_CHAN_RESP_UNIMPLEMENTED = 2,
+	NBL_CHAN_RESP_PERM_DENY = 3,
+};
+
+/*
+ * Mailbox wire opcodes
+ * Every opcode is assigned explicit fixed numeric value, stable wire ABI
+ * shared between driver and firmware.
+ * Firmware/driver wire compatibility rule:
+ * New entries must only be appended before NBL_CHAN_MSG_MAILBOX_MAX.
+ * Reordering / inserting / deleting existing items will break cross-version
+ * interoperability. Any change to existing enumerators requires matching
+ * firmware ABI adjustment.
+ */
+enum nbl_chan_msg_type {
+	NBL_CHAN_MSG_ACK = 0,
+	NBL_CHAN_MSG_ADD_MACVLAN = 1,
+	NBL_CHAN_MSG_DEL_MACVLAN = 2,
+	NBL_CHAN_MSG_ADD_MULTI_RULE = 3,
+	NBL_CHAN_MSG_DEL_MULTI_RULE = 4,
+	NBL_CHAN_MSG_SETUP_MULTI_GROUP = 5,
+	NBL_CHAN_MSG_REMOVE_MULTI_GROUP = 6,
+	NBL_CHAN_MSG_REGISTER_NET = 7,
+	NBL_CHAN_MSG_UNREGISTER_NET = 8,
+	NBL_CHAN_MSG_ALLOC_TXRX_QUEUES = 9,
+	NBL_CHAN_MSG_FREE_TXRX_QUEUES = 10,
+	NBL_CHAN_MSG_SETUP_QUEUE = 11,
+	NBL_CHAN_MSG_REMOVE_ALL_QUEUES = 12,
+	NBL_CHAN_MSG_CFG_DSCH = 13,
+	NBL_CHAN_MSG_SETUP_CQS = 14,
+	NBL_CHAN_MSG_REMOVE_CQS = 15,
+	NBL_CHAN_MSG_CFG_QDISC_MQPRIO = 16,
+	NBL_CHAN_MSG_CONFIGURE_MSIX_MAP = 17,
+	NBL_CHAN_MSG_DESTROY_MSIX_MAP = 18,
+	NBL_CHAN_MSG_MAILBOX_SET_IRQ = 19,
+	NBL_CHAN_MSG_GET_GLOBAL_VECTOR = 20,
+	NBL_CHAN_MSG_GET_VSI_ID = 21,
+	NBL_CHAN_MSG_SET_PROMISC_MODE = 22,
+	NBL_CHAN_MSG_GET_FIRMWARE_VERSION = 23,
+	NBL_CHAN_MSG_GET_QUEUE_ERR_STATS = 24,
+	NBL_CHAN_MSG_GET_COALESCE = 25,
+	NBL_CHAN_MSG_SET_COALESCE = 26,
+	NBL_CHAN_MSG_SET_SPOOF_CHECK_ADDR = 27,
+	NBL_CHAN_MSG_SET_VF_SPOOF_CHECK = 28,
+	NBL_CHAN_MSG_GET_RXFH_INDIR_SIZE = 29,
+	NBL_CHAN_MSG_GET_RXFH_INDIR = 30,
+	NBL_CHAN_MSG_GET_RXFH_RSS_KEY = 31,
+	NBL_CHAN_MSG_GET_RXFH_RSS_ALG_SEL = 32,
+	NBL_CHAN_MSG_GET_HW_CAPS = 33,
+	NBL_CHAN_MSG_GET_HW_STATE = 34,
+	NBL_CHAN_MSG_REGISTER_RDMA = 35,
+	NBL_CHAN_MSG_UNREGISTER_RDMA = 36,
+	NBL_CHAN_MSG_GET_REAL_HW_ADDR = 37,
+	NBL_CHAN_MSG_GET_REAL_BDF = 38,
+	NBL_CHAN_MSG_GRC_PROCESS = 39,
+	NBL_CHAN_MSG_SET_SFP_STATE = 40,
+	NBL_CHAN_MSG_SET_ETH_LOOPBACK = 41,
+	NBL_CHAN_MSG_CHECK_ACTIVE_VF = 42,
+	NBL_CHAN_MSG_GET_PRODUCT_FLEX_CAP = 43,
+	NBL_CHAN_MSG_ALLOC_KTLS_TX_INDEX = 44,
+	NBL_CHAN_MSG_FREE_KTLS_TX_INDEX = 45,
+	NBL_CHAN_MSG_CFG_KTLS_TX_KEYMAT = 46,
+	NBL_CHAN_MSG_ALLOC_KTLS_RX_INDEX = 47,
+	NBL_CHAN_MSG_FREE_KTLS_RX_INDEX = 48,
+	NBL_CHAN_MSG_CFG_KTLS_RX_KEYMAT = 49,
+	NBL_CHAN_MSG_CFG_KTLS_RX_RECORD = 50,
+	NBL_CHAN_MSG_ADD_KTLS_RX_FLOW = 51,
+	NBL_CHAN_MSG_DEL_KTLS_RX_FLOW = 52,
+	NBL_CHAN_MSG_ALLOC_IPSEC_TX_INDEX = 53,
+	NBL_CHAN_MSG_FREE_IPSEC_TX_INDEX = 54,
+	NBL_CHAN_MSG_ALLOC_IPSEC_RX_INDEX = 55,
+	NBL_CHAN_MSG_FREE_IPSEC_RX_INDEX = 56,
+	NBL_CHAN_MSG_CFG_IPSEC_TX_SAD = 57,
+	NBL_CHAN_MSG_CFG_IPSEC_RX_SAD = 58,
+	NBL_CHAN_MSG_ADD_IPSEC_TX_FLOW = 59,
+	NBL_CHAN_MSG_DEL_IPSEC_TX_FLOW = 60,
+	NBL_CHAN_MSG_ADD_IPSEC_RX_FLOW = 61,
+	NBL_CHAN_MSG_DEL_IPSEC_RX_FLOW = 62,
+	NBL_CHAN_MSG_NOTIFY_IPSEC_HARD_EXPIRE = 63,
+	NBL_CHAN_MSG_GET_MBX_IRQ_NUM = 64,
+	NBL_CHAN_MSG_CLEAR_FLOW = 65,
+	NBL_CHAN_MSG_CLEAR_QUEUE = 66,
+	NBL_CHAN_MSG_GET_ETH_ID = 67,
+	NBL_CHAN_MSG_SET_OFFLOAD_STATUS = 68,
+	NBL_CHAN_MSG_INIT_OFLD = 69,
+	NBL_CHAN_MSG_INIT_CMDQ = 70,
+	NBL_CHAN_MSG_DESTROY_CMDQ = 71,
+	NBL_CHAN_MSG_RESET_CMDQ = 72,
+	NBL_CHAN_MSG_INIT_FLOW = 73,
+	NBL_CHAN_MSG_DEINIT_FLOW = 74,
+	NBL_CHAN_MSG_OFFLOAD_FLOW_RULE = 75,
+	NBL_CHAN_MSG_GET_ACL_SWITCH = 76,
+	NBL_CHAN_MSG_GET_VSI_GLOBAL_QUEUE_ID = 77,
+	NBL_CHAN_MSG_INIT_REP = 78,
+	NBL_CHAN_MSG_GET_LINE_RATE_INFO = 79,
+	NBL_CHAN_MSG_REGISTER_NET_REP = 80,
+	NBL_CHAN_MSG_UNREGISTER_NET_REP = 81,
+	NBL_CHAN_MSG_REGISTER_ETH_REP = 82,
+	NBL_CHAN_MSG_UNREGISTER_ETH_REP = 83,
+	NBL_CHAN_MSG_REGISTER_UPCALL_PORT = 84,
+	NBL_CHAN_MSG_UNREGISTER_UPCALL_PORT = 85,
+	NBL_CHAN_MSG_GET_PORT_STATE = 86,
+	NBL_CHAN_MSG_SET_PORT_ADVERTISING = 87,
+	NBL_CHAN_MSG_GET_MODULE_INFO = 88,
+	NBL_CHAN_MSG_GET_MODULE_EEPROM = 89,
+	NBL_CHAN_MSG_GET_LINK_STATE = 90,
+	NBL_CHAN_MSG_NOTIFY_LINK_STATE = 91,
+	NBL_CHAN_MSG_GET_QUEUE_CXT = 92,
+	NBL_CHAN_MSG_CFG_LOG = 93,
+	NBL_CHAN_MSG_INIT_VDPAQ = 94,
+	NBL_CHAN_MSG_DESTROY_VDPAQ = 95,
+	NBL_CHAN_MSG_GET_UPCALL_PORT = 96,
+	NBL_CHAN_MSG_NOTIFY_ETH_REP_LINK_STATE = 97,
+	NBL_CHAN_MSG_SET_ETH_MAC_ADDR = 98,
+	NBL_CHAN_MSG_GET_FUNCTION_ID = 99,
+	NBL_CHAN_MSG_GET_CHIP_TEMPERATURE = 100,
+	NBL_CHAN_MSG_DISABLE_HW_FLOW = 101,
+	NBL_CHAN_MSG_ENABLE_HW_FLOW = 102,
+	NBL_CHAN_MSG_SET_UPCALL_RULE = 103,
+	NBL_CHAN_MSG_UNSET_UPCALL_RULE = 104,
+	NBL_CHAN_MSG_GET_REG_DUMP = 105,
+	NBL_CHAN_MSG_GET_REG_DUMP_LEN = 106,
+	NBL_CHAN_MSG_CFG_LAG_HASH_ALGORITHM = 107,
+	NBL_CHAN_MSG_CFG_LAG_MEMBER_FWD = 108,
+	NBL_CHAN_MSG_CFG_LAG_MEMBER_LIST = 109,
+	NBL_CHAN_MSG_CFG_LAG_MEMBER_UP_ATTR = 110,
+	NBL_CHAN_MSG_ADD_LAG_FLOW = 111,
+	NBL_CHAN_MSG_DEL_LAG_FLOW = 112,
+	NBL_CHAN_MSG_SWITCHDEV_INIT_CMDQ = 113,
+	NBL_CHAN_MSG_SWITCHDEV_DEINIT_CMDQ = 114,
+	NBL_CHAN_MSG_SET_TC_FLOW_INFO = 115,
+	NBL_CHAN_MSG_UNSET_TC_FLOW_INFO = 116,
+	NBL_CHAN_MSG_INIT_ACL = 117,
+	NBL_CHAN_MSG_UNINIT_ACL = 118,
+	NBL_CHAN_MSG_CFG_LAG_MCC = 119,
+	NBL_CHAN_MSG_REGISTER_VSI2Q = 120,
+	NBL_CHAN_MSG_SETUP_Q2VSI = 121,
+	NBL_CHAN_MSG_REMOVE_Q2VSI = 122,
+	NBL_CHAN_MSG_SETUP_RSS = 123,
+	NBL_CHAN_MSG_REMOVE_RSS = 124,
+	NBL_CHAN_MSG_GET_REP_QUEUE_INFO = 125,
+	NBL_CHAN_MSG_CTRL_PORT_LED = 126,
+	NBL_CHAN_MSG_NWAY_RESET = 127,
+	NBL_CHAN_MSG_SET_INTL_SUPPRESS_LEVEL = 128,
+	NBL_CHAN_MSG_GET_ETH_STATS = 129,
+	NBL_CHAN_MSG_GET_MODULE_TEMPERATURE = 130,
+	NBL_CHAN_MSG_GET_BOARD_INFO = 131,
+	NBL_CHAN_MSG_GET_P4_USED = 132,
+	NBL_CHAN_MSG_GET_VF_BASE_VSI_ID = 133,
+	NBL_CHAN_MSG_ADD_LLDP_FLOW = 134,
+	NBL_CHAN_MSG_DEL_LLDP_FLOW = 135,
+	NBL_CHAN_MSG_CFG_ETH_BOND_INFO = 136,
+	NBL_CHAN_MSG_CFG_DUPPKT_MCC = 137,
+	NBL_CHAN_MSG_ADD_ND_UPCALL_FLOW = 138,
+	NBL_CHAN_MSG_DEL_ND_UPCALL_FLOW = 139,
+	NBL_CHAN_MSG_GET_BOARD_ID = 140,
+	NBL_CHAN_MSG_SET_SHAPING_DPORT_VLD = 141,
+	NBL_CHAN_MSG_SET_DPORT_FC_TH_VLD = 142,
+	NBL_CHAN_MSG_REGISTER_RDMA_BOND = 143,
+	NBL_CHAN_MSG_UNREGISTER_RDMA_BOND = 144,
+	NBL_CHAN_MSG_RESTORE_NETDEV_QUEUE = 145,
+	NBL_CHAN_MSG_RESTART_NETDEV_QUEUE = 146,
+	NBL_CHAN_MSG_RESTORE_HW_QUEUE = 147,
+	NBL_CHAN_MSG_KEEP_ALIVE = 148,
+	NBL_CHAN_MSG_GET_BASE_MAC_ADDR = 149,
+	NBL_CHAN_MSG_CFG_BOND_SHAPING = 150,
+	NBL_CHAN_MSG_CFG_BGID_BACK_PRESSURE = 151,
+	NBL_CHAN_MSG_ALLOC_KT_BLOCK = 152,
+	NBL_CHAN_MSG_FREE_KT_BLOCK = 153,
+	NBL_CHAN_MSG_GET_USER_QUEUE_INFO = 154,
+	NBL_CHAN_MSG_GET_ETH_BOND_INFO = 155,
+	NBL_CHAN_MSG_CLEAR_ACCEL_FLOW = 156,
+	NBL_CHAN_MSG_SET_BRIDGE_MODE = 157,
+	NBL_CHAN_MSG_GET_VF_FUNCTION_ID = 158,
+	NBL_CHAN_MSG_NOTIFY_LINK_FORCED = 159,
+	NBL_CHAN_MSG_SET_PMD_DEBUG = 160,
+	NBL_CHAN_MSG_REGISTER_FUNC_MAC = 161,
+	NBL_CHAN_MSG_SET_TX_RATE = 162,
+	NBL_CHAN_MSG_REGISTER_FUNC_LINK_FORCED = 163,
+	NBL_CHAN_MSG_GET_LINK_FORCED = 164,
+	NBL_CHAN_MSG_REGISTER_FUNC_VLAN = 165,
+	NBL_CHAN_MSG_GET_FD_FLOW = 166,
+	NBL_CHAN_MSG_GET_FD_FLOW_CNT = 167,
+	NBL_CHAN_MSG_GET_FD_FLOW_ALL = 168,
+	NBL_CHAN_MSG_GET_FD_FLOW_MAX = 169,
+	NBL_CHAN_MSG_REPLACE_FD_FLOW = 170,
+	NBL_CHAN_MSG_REMOVE_FD_FLOW = 171,
+	NBL_CHAN_MSG_CFG_FD_FLOW_STATE = 172,
+	NBL_CHAN_MSG_REGISTER_FUNC_RATE = 173,
+	NBL_CHAN_MSG_NOTIFY_VLAN = 174,
+	NBL_CHAN_MSG_GET_XDP_QUEUE_INFO = 175,
+	NBL_CHAN_MSG_STOP_ABNORMAL_SW_QUEUE = 176,
+	NBL_CHAN_MSG_STOP_ABNORMAL_HW_QUEUE = 177,
+	NBL_CHAN_MSG_NOTIFY_RESET_EVENT = 178,
+	NBL_CHAN_MSG_ACK_RESET_EVENT = 179,
+	NBL_CHAN_MSG_GET_VF_VSI_ID = 180,
+	NBL_CHAN_MSG_CONFIGURE_QOS = 181,
+	NBL_CHAN_MSG_GET_PFC_BUFFER_SIZE = 182,
+	NBL_CHAN_MSG_SET_PFC_BUFFER_SIZE = 183,
+	NBL_CHAN_MSG_GET_VF_STATS = 184,
+	NBL_CHAN_MSG_REGISTER_FUNC_TRUST = 185,
+	NBL_CHAN_MSG_NOTIFY_TRUST = 186,
+	NBL_CHAN_MSG_CHECK_VF_IS_ACTIVE = 187,
+	NBL_CHAN_MSG_GET_ETH_ABNORMAL_STATS = 188,
+	NBL_CHAN_MSG_GET_ETH_CTRL_STATS = 189,
+	NBL_CHAN_MSG_GET_PAUSE_STATS = 190,
+	NBL_CHAN_MSG_GET_ETH_MAC_STATS = 191,
+	NBL_CHAN_MSG_GET_FEC_STATS = 192,
+	NBL_CHAN_MSG_CFG_MULTI_MCAST_RULE = 193,
+	NBL_CHAN_MSG_GET_LINK_DOWN_COUNT = 194,
+	NBL_CHAN_MSG_GET_LINK_STATUS_OPCODE = 195,
+	NBL_CHAN_MSG_GET_RMON_STATS = 196,
+	NBL_CHAN_MSG_REGISTER_PF_NAME = 197,
+	NBL_CHAN_MSG_GET_PF_NAME = 198,
+	NBL_CHAN_MSG_CONFIGURE_RDMA_BW = 199,
+	NBL_CHAN_MSG_SET_RATE_LIMIT = 200,
+	NBL_CHAN_MSG_SET_TC_WGT = 201,
+	NBL_CHAN_MSG_REMOVE_QUEUE = 202,
+	NBL_CHAN_MSG_GET_MIRROR_TABLE_ID = 203,
+	NBL_CHAN_MSG_CONFIGURE_MIRROR = 204,
+	NBL_CHAN_MSG_CONFIGURE_MIRROR_TABLE = 205,
+	NBL_CHAN_MSG_CLEAR_MIRROR_CFG = 206,
+	NBL_CHAN_MSG_MIRROR_OUTPUTPORT_NOTIFY = 207,
+	NBL_CHAN_MSG_CHECK_FLOWTABLE_SPEC = 208,
+	NBL_CHAN_MSG_CHECK_VF_IS_VDPA = 209,
+	NBL_CHAN_MSG_GET_VDPA_VF_STATS = 210,
+	NBL_CHAN_MSG_SET_RX_RATE = 211,
+	NBL_CHAN_MSG_GET_UVN_PKT_DROP_STATS = 212,
+	NBL_CHAN_MSG_GET_USTORE_PKT_DROP_STATS = 213,
+	NBL_CHAN_MSG_GET_USTORE_TOTAL_PKT_DROP_STATS = 214,
+	NBL_CHAN_MSG_SET_WOL = 215,
+	NBL_CHAN_MSG_INIT_VF_MSIX_MAP = 216,
+	NBL_CHAN_MSG_GET_ST_NAME = 217,
+	/* mailbox msg end */
+	NBL_CHAN_MSG_MAILBOX_MAX,
+};
+
+enum nbl_chan_state {
+	NBL_CHAN_INTERRUPT_READY,
+	NBL_CHAN_ABNORMAL,
+	NBL_CHAN_STATE_NBITS
+};
+
+struct nbl_chan_param_cfg_msix_map {
+	__le16 num_net_msix;
+	__le16 num_others_msix;
+	__le16 msix_mask_en;
+	__le16 rsvd;
+};
+
+struct nbl_chan_param_set_mailbox_irq {
+	__le16 vector_id;
+	u8 en_msix;
+	u8 rsvd;
+};
+
+struct nbl_chan_param_get_vsi_id {
+	__le16 vsi_id;
+	__le16 type;
+};
+
+struct nbl_chan_param_get_eth_id {
+	__le16 vsi_id;
+	u8 eth_num;
+	u8 eth_id;
+	u8 logic_eth_id;
+	u8 rsvd[3];
+};
+
+struct nbl_board_port_info {
+	u8 eth_num;
+	u8 eth_speed;
+	u8 p4_version;
+	u8 rsv[5];
+};
+
+struct nbl_chan_send_info {
+	void *arg;
+	size_t arg_len;
+	void *resp;
+	size_t resp_len;
+	u16 dstid;
+	u16 msg_type;
+	u16 ack;
+	u16 ack_len;
+};
+
+struct nbl_chan_ack_info {
+	void *data;
+	int err;
+	u32 data_len;
+	u16 dstid;
+	u16 msg_type;
+	u16 msgid;
+};
+
+enum nbl_channel_type {
+	NBL_CHAN_TYPE_MAILBOX,
+	NBL_CHAN_TYPE_MAX
+};
+
+static inline void
+nbl_chan_fill_send_info(struct nbl_chan_send_info *info,
+			u16 dst_id, u16 msg_type,
+			void *argument, u32 arg_length,
+			void *response, u32 resp_length,
+			bool need_ack)
+{
+	info->dstid = dst_id;
+	info->msg_type = msg_type;
+	info->arg = argument;
+	info->arg_len = arg_length;
+	info->resp = response;
+	info->resp_len = resp_length;
+	info->ack = need_ack;
+}
+
+static inline void
+nbl_chan_fill_ack_info(struct nbl_chan_ack_info *info,
+		       u16 dst_id, u16 msg_type, u16 msg_id,
+		       int err_code, void *ack_data, u32 data_length)
+{
+	info->dstid = dst_id;
+	info->msg_type = msg_type;
+	info->msgid = msg_id;
+	info->err = err_code;
+	info->data = ack_data;
+	info->data_len = data_length;
+}
+
+struct nbl_channel_ops {
+	int (*send_msg)(struct nbl_channel_mgt *chan_mgt,
+			struct nbl_chan_send_info *chan_send);
+	int (*send_ack)(struct nbl_channel_mgt *chan_mgt,
+			struct nbl_chan_ack_info *chan_ack);
+	int (*register_msg)(struct nbl_channel_mgt *chan_mgt, u16 msg_type,
+			    nbl_chan_resp func, void *callback_priv);
+	void (*unregister_all_msg)(struct nbl_channel_mgt *chan_mgt);
+	void (*cfg_chan_qinfo_map_table)(struct nbl_channel_mgt *chan_mgt,
+					 u8 bus, u8 devid);
+	bool (*check_queue_exist)(struct nbl_channel_mgt *chan_mgt,
+				  u8 chan_type);
+	int (*setup_queue)(struct nbl_channel_mgt *chan_mgt, u8 chan_type);
+	int (*teardown_queue)(struct nbl_channel_mgt *chan_mgt, u8 chan_type);
+	void (*clean_queue_subtask)(struct nbl_channel_mgt *chan_mgt,
+				    u8 chan_type);
+	void (*register_chan_task)(struct nbl_channel_mgt *chan_mgt,
+				   u8 chan_type, struct work_struct *task);
+	void (*set_queue_state)(struct nbl_channel_mgt *chan_mgt,
+				enum nbl_chan_state state, u8 chan_type,
+				u8 set);
+};
+
+struct nbl_channel_ops_tbl {
+	struct nbl_channel_ops *ops;
+	struct nbl_channel_mgt *priv;
+};
+
+int nbl_chan_init_common(struct nbl_adapter *adapter);
+void nbl_chan_remove_common(struct nbl_adapter *adapter);
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h
new file mode 100644
index 0000000000000..75882bfe4c936
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_DEF_COMMON_H_
+#define _NBL_DEF_COMMON_H_
+
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/device.h>
+#include "nbl_include.h"
+
+#define NBL_TWO_ETHERNET_PORT			2
+#define NBL_FOUR_ETHERNET_PORT			4
+#define NBL_DEFAULT_VSI_ID_GAP			1024
+#define NBL_TWO_ETHERNET_VSI_ID_GAP		512
+#define NBL_FOUR_ETHERNET_VSI_ID_GAP		256
+
+static inline u32 nbl_vsi_id_gap(u32 m)
+{
+	if (m == NBL_FOUR_ETHERNET_PORT)
+		return NBL_FOUR_ETHERNET_VSI_ID_GAP;
+	else if (m == NBL_TWO_ETHERNET_PORT)
+		return NBL_TWO_ETHERNET_VSI_ID_GAP;
+
+	return NBL_DEFAULT_VSI_ID_GAP;
+}
+
+struct nbl_common_info {
+	struct workqueue_struct *wq;
+	struct pci_dev *pdev;
+	struct device *dev;
+	u32 msg_enable;
+	u16 vsi_id;
+	u8 eth_id;
+	u8 logic_eth_id;
+	u8 eth_num;
+
+	u8 function;
+	u8 devid;
+	u8 bus;
+	u8 hw_bus;
+	u16 mgt_pf;
+
+	u8 has_ctrl;
+	u8 has_net;
+};
+
+struct nbl_hash_tbl_key {
+	struct device *dev;
+	u16 key_size;
+	u16 data_size; /* no include key or node member */
+	u16 bucket_size;
+	u16 resv;
+};
+
+void nbl_common_destroy_wq(struct nbl_common_info *common);
+int nbl_common_create_wq(struct nbl_common_info *common);
+int nbl_common_func_id_to_rel_pf_id(struct nbl_common_info *common, u32 pf_id,
+				    u32 *rel_pf_id);
+struct nbl_hash_tbl_mgt *
+nbl_common_init_hash_table(struct nbl_hash_tbl_key *key);
+void nbl_common_remove_hash_table(struct nbl_hash_tbl_mgt *tbl_mgt);
+int nbl_common_alloc_hash_node(struct nbl_hash_tbl_mgt *tbl_mgt, void *key,
+			       void *data, void **out_data);
+void *nbl_common_get_hash_node(struct nbl_hash_tbl_mgt *tbl_mgt, void *key);
+
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev.h
new file mode 100644
index 0000000000000..32e6cce38d397
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_DEF_DEV_H_
+#define _NBL_DEF_DEV_H_
+
+struct nbl_adapter;
+
+int nbl_dev_init(struct nbl_adapter *adapter);
+void nbl_dev_remove(struct nbl_adapter *adapter);
+int nbl_dev_start(struct nbl_adapter *adapter);
+void nbl_dev_stop(struct nbl_adapter *adapter);
+
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dispatch.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dispatch.h
new file mode 100644
index 0000000000000..b26425e0112b8
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dispatch.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_DEF_DISPATCH_H_
+#define _NBL_DEF_DISPATCH_H_
+
+#include <linux/types.h>
+
+struct nbl_dispatch_mgt;
+struct nbl_adapter;
+enum {
+	NBL_DISP_CTRL_LVL_NEVER = 0,
+	NBL_DISP_CTRL_LVL_MGT,
+	NBL_DISP_CTRL_LVL_NET,
+	NBL_DISP_CTRL_LVL_MAX,
+};
+
+struct nbl_dispatch_ops {
+	int (*init_module)(struct nbl_dispatch_mgt *disp_mgt);
+	void (*deinit_module)(struct nbl_dispatch_mgt *disp_mgt);
+	int (*cfg_msix_map)(struct nbl_dispatch_mgt *disp_mgt,
+			    u16 num_net_msix, u16 num_others_msix,
+			    bool net_msix_mask_en);
+	int (*destroy_msix_map)(struct nbl_dispatch_mgt *disp_mgt);
+	int (*set_mailbox_irq)(struct nbl_dispatch_mgt *disp_mgt,
+			       u16 vector_id, bool en_msix);
+	int (*get_vsi_id)(struct nbl_dispatch_mgt *disp_mgt, u16 type,
+			  u16 *vsi_id);
+	int (*get_eth_id)(struct nbl_dispatch_mgt *disp_mgt, u16 vsi_id,
+			  u8 *eth_num, u8 *eth_id, u8 *logic_eth_id);
+};
+
+struct nbl_dispatch_ops_tbl {
+	struct nbl_dispatch_ops *ops;
+	struct nbl_dispatch_mgt *priv;
+};
+
+int nbl_disp_init(struct nbl_adapter *adapter);
+void nbl_disp_remove(struct nbl_adapter *adapter);
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h
new file mode 100644
index 0000000000000..31647d3abcbea
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_DEF_HW_H_
+#define _NBL_DEF_HW_H_
+
+#include <linux/types.h>
+
+struct nbl_hw_mgt;
+struct nbl_adapter;
+struct nbl_hw_ops {
+	int (*init_module)(struct nbl_hw_mgt *hw_mgt, u8 eth_speed, u8 eth_num);
+	void (*deinit_module)(struct nbl_hw_mgt *hw_mgt);
+
+	void (*cfg_msix_map)(struct nbl_hw_mgt *hw_mgt, u16 func_id,
+			     bool valid, dma_addr_t dma_addr, u8 bus,
+			     u8 devid, u8 function);
+	void (*cfg_msix_info)(struct nbl_hw_mgt *hw_mgt, u16 func_id,
+			      bool valid, u16 interrupt_id, u8 bus,
+			      u8 devid, u8 function,
+			      bool net_msix_mask_en);
+	void (*flush_write)(struct nbl_hw_mgt *hw_mgt);
+	void (*update_mailbox_queue_tail_ptr)(struct nbl_hw_mgt *hw_mgt,
+					      u16 tail_ptr, u8 txrx);
+	void (*config_mailbox_rxq)(struct nbl_hw_mgt *hw_mgt,
+				   dma_addr_t dma_addr, int size_bwid);
+	void (*config_mailbox_txq)(struct nbl_hw_mgt *hw_mgt,
+				   dma_addr_t dma_addr, int size_bwid);
+	void (*stop_mailbox_rxq)(struct nbl_hw_mgt *hw_mgt);
+	void (*stop_mailbox_txq)(struct nbl_hw_mgt *hw_mgt);
+	void (*get_host_pf_mask)(struct nbl_hw_mgt *hw_mgt, u32 *pf_mask);
+	void (*get_real_bus)(struct nbl_hw_mgt *hw_mgt, u8 *bus);
+
+	void (*cfg_mailbox_qinfo)(struct nbl_hw_mgt *hw_mgt, u16 func_id,
+				  u8 bus, u8 devid, u8 function);
+	void (*set_mailbox_irq)(struct nbl_hw_mgt *hw_mgt, u16 func_id,
+				bool en_msix, u16 global_vec_id);
+	void (*get_fw_eth_map)(struct nbl_hw_mgt *hw_mgt, u32 *eth_map);
+	void (*get_board_info)(struct nbl_hw_mgt *hw_mgt,
+			       struct nbl_board_port_info *board);
+};
+
+struct nbl_hw_ops_tbl {
+	struct nbl_hw_ops *ops;
+	struct nbl_hw_mgt *priv;
+};
+
+int nbl_hw_init_leonis(struct nbl_adapter *adapter);
+void nbl_hw_remove_leonis(struct nbl_adapter *adapter);
+
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resource.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resource.h
new file mode 100644
index 0000000000000..58be5262ec844
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resource.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_DEF_RESOURCE_H_
+#define _NBL_DEF_RESOURCE_H_
+
+#include <linux/types.h>
+
+struct nbl_resource_mgt;
+struct nbl_adapter;
+
+struct nbl_resource_ops {
+	int (*init_module)(struct nbl_resource_mgt *res_mgt);
+	void (*deinit_module)(struct nbl_resource_mgt *res_mgt);
+
+	int (*cfg_msix_map)(struct nbl_resource_mgt *res_mgt, u16 func_id,
+			    u16 num_net_msix, u16 num_others_msix,
+			    bool net_msix_mask_en);
+	int (*destroy_msix_map)(struct nbl_resource_mgt *res_mgt, u16 func_id);
+	int (*set_mailbox_irq)(struct nbl_resource_mgt *res_mgt, u16 func_id,
+			       u16 vector_id, bool en_msix);
+	int (*get_vsi_id)(struct nbl_resource_mgt *res_mgt, u16 func_id,
+			  u16 type, u16 *vsi_id);
+	int (*get_eth_id)(struct nbl_resource_mgt *res_mgt, u16 func_id,
+			  u16 vsi_id, u8 *eth_num, u8 *eth_id,
+			  u8 *logic_eth_id);
+};
+
+struct nbl_resource_ops_tbl {
+	struct nbl_resource_ops *ops;
+	struct nbl_resource_mgt *priv;
+};
+
+int nbl_res_init_leonis(struct nbl_adapter *adapter);
+void nbl_res_remove_leonis(struct nbl_adapter *adapter);
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h
new file mode 100644
index 0000000000000..aa61051c80345
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#ifndef _NBL_INCLUDE_H_
+#define _NBL_INCLUDE_H_
+
+#include <linux/types.h>
+
+/*  ------  Basic definitions  -------  */
+#define NBL_DRIVER_NAME					"nbl"
+#define NBL_MAX_PF					8
+#define NBL_NEXT_ID(id, max) (((id) + 1) % ((max) + 1))
+
+#define NBL_MAX_FUNC					520
+#define NBL_MAX_ETHERNET				4
+#define NBL_MAX_PF_SRC_ID				3
+/* Used for macros to pass checkpatch */
+#define NBL_NAME(x)					x
+
+enum {
+	NBL_VSI_DATA = 0,
+};
+
+struct nbl_func_caps {
+	u32 has_ctrl:1;
+	u32 has_net:1;
+	u32 rsv:30;
+};
+
+struct nbl_init_param {
+	struct nbl_func_caps caps;
+};
+
+/*
+ * Firmware ABI defines port speed enum fixed, value 0 represents 10G, cannot
+ * reassign 0 to INVALID for compatibility
+ */
+enum nbl_fw_port_speed {
+	NBL_FW_PORT_SPEED_10G,
+	NBL_FW_PORT_SPEED_25G,
+	NBL_FW_PORT_SPEED_50G,
+	NBL_FW_PORT_SPEED_100G,
+};
+
+#define NBL_QUIRK_UVN_PREFETCH_ALIGN  BIT(1)
+
+#endif
diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c
new file mode 100644
index 0000000000000..c12a53da6a9b3
--- /dev/null
+++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Nebula Matrix Limited.
+ */
+
+#include <linux/device.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/bits.h>
+#include "nbl_include/nbl_include.h"
+#include "nbl_include/nbl_def_channel.h"
+#include "nbl_include/nbl_def_hw.h"
+#include "nbl_include/nbl_def_resource.h"
+#include "nbl_include/nbl_def_dispatch.h"
+#include "nbl_include/nbl_def_dev.h"
+#include "nbl_include/nbl_def_common.h"
+#include "nbl_core.h"
+
+int nbl_core_start(struct nbl_adapter *adapter)
+{
+	return nbl_dev_start(adapter);
+}
+
+void nbl_core_stop(struct nbl_adapter *adapter)
+{
+	nbl_dev_stop(adapter);
+}
+
+struct nbl_adapter *nbl_core_init(struct pci_dev *pdev,
+				  struct nbl_init_param *param)
+{
+	struct nbl_common_info *common;
+	struct nbl_adapter *adapter;
+	int ret;
+
+	adapter = devm_kzalloc(&pdev->dev, sizeof(*adapter), GFP_KERNEL);
+	if (!adapter)
+		return ERR_PTR(-ENOMEM);
+
+	adapter->pdev = pdev;
+	common = &adapter->common;
+
+	common->mgt_pf = 0;
+	common->pdev = pdev;
+	common->dev = &pdev->dev;
+	common->has_ctrl = param->caps.has_ctrl;
+	common->has_net = param->caps.has_net;
+	common->function = PCI_FUNC(pdev->devfn);
+	common->devid = PCI_SLOT(pdev->devfn);
+	common->bus = pdev->bus->number;
+
+	ret = nbl_hw_init_leonis(adapter);
+	if (ret)
+		goto hw_init_fail;
+
+	ret = nbl_chan_init_common(adapter);
+	if (ret)
+		goto chan_init_fail;
+
+	ret = nbl_res_init_leonis(adapter);
+	if (ret)
+		goto res_init_fail;
+
+	ret = nbl_disp_init(adapter);
+	if (ret)
+		goto disp_init_fail;
+
+	ret = nbl_dev_init(adapter);
+	if (ret)
+		goto dev_init_fail;
+	return adapter;
+
+dev_init_fail:
+	nbl_disp_remove(adapter);
+disp_init_fail:
+	nbl_res_remove_leonis(adapter);
+res_init_fail:
+	nbl_chan_remove_common(adapter);
+chan_init_fail:
+	nbl_hw_remove_leonis(adapter);
+hw_init_fail:
+	return ERR_PTR(ret);
+}
+
+void nbl_core_remove(struct nbl_adapter *adapter)
+{
+	nbl_dev_remove(adapter);
+	nbl_disp_remove(adapter);
+	nbl_res_remove_leonis(adapter);
+	nbl_chan_remove_common(adapter);
+	nbl_hw_remove_leonis(adapter);
+}
+
+static void nbl_get_func_param(struct pci_dev *pdev, kernel_ulong_t driver_data,
+			       struct nbl_init_param *param)
+{
+	param->caps.has_net = !!(driver_data & BIT(NBL_CAP_HAS_NET_BIT));
+
+	/*
+	 * Only PF0 possesses ctrl capability; all PFs share identical PCI
+	 * device ID, so we handle this via special function ID judgement.
+	 */
+	if ((PCI_FUNC(pdev->devfn) == 0) && !pdev->is_virtfn)
+		param->caps.has_ctrl = 1;
+}
+
+static int nbl_probe(struct pci_dev *pdev,
+		     const struct pci_device_id *id)
+{
+	struct nbl_init_param param = { { 0 } };
+	struct device *dev = &pdev->dev;
+	struct nbl_adapter *adapter;
+	int err;
+
+	err = pcim_enable_device(pdev);
+	if (err) {
+		dev_err(&pdev->dev, "Failed to enable PCI dev, err=%d\n", err);
+		return err;
+	}
+
+	nbl_get_func_param(pdev, id->driver_data, &param);
+
+	err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+	if (err) {
+		dev_dbg(dev, "Configure DMA 64 bit mask failed, err = %d\n",
+			err);
+		err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
+		if (err) {
+			dev_err(dev,
+				"Configure DMA 32 bit mask failed, err = %d\n",
+				err);
+			goto configure_dma_err;
+		}
+	}
+	pci_set_master(pdev);
+
+	adapter = nbl_core_init(pdev, &param);
+	if (IS_ERR(adapter)) {
+		dev_err(dev, "Nbl adapter init fail: %pe\n", adapter);
+		err = PTR_ERR(adapter);
+		goto adapter_init_err;
+	}
+	pci_set_drvdata(pdev, adapter);
+	err = nbl_core_start(adapter);
+	if (err)
+		goto core_start_err;
+
+	return 0;
+core_start_err:
+	pci_set_drvdata(pdev, NULL);
+	nbl_core_remove(adapter);
+adapter_init_err:
+	pci_clear_master(pdev);
+configure_dma_err:
+	return err;
+}
+
+static void nbl_remove(struct pci_dev *pdev)
+{
+	struct nbl_adapter *adapter = pci_get_drvdata(pdev);
+
+	if (adapter) {
+		nbl_core_stop(adapter);
+		nbl_core_remove(adapter);
+	}
+
+	pci_clear_master(pdev);
+}
+
+/*
+ * PCI Device IDs for Leonis/NBL Network Controllers
+ *
+ * Vendor ID: 0x1F0F
+ * SNIC v3r1 product Device IDs range: 0x3403-0x3412
+ */
+#define NBL_VENDOR_ID				0x1F0F
+
+#define NBL_DEVICE_ID_M18110			0x3403
+#define NBL_DEVICE_ID_M18110_LX			0x3404
+#define NBL_DEVICE_ID_M18110_BASE_T		0x3405
+#define NBL_DEVICE_ID_M18110_LX_BASE_T		0x3406
+#define NBL_DEVICE_ID_M18110_OCP		0x3407
+#define NBL_DEVICE_ID_M18110_LX_OCP		0x3408
+#define NBL_DEVICE_ID_M18110_BASE_T_OCP		0x3409
+#define NBL_DEVICE_ID_M18110_LX_BASE_T_OCP	0x340a
+#define NBL_DEVICE_ID_M18000			0x340b
+#define NBL_DEVICE_ID_M18000_LX			0x340c
+#define NBL_DEVICE_ID_M18000_BASE_T		0x340d
+#define NBL_DEVICE_ID_M18000_LX_BASE_T		0x340e
+#define NBL_DEVICE_ID_M18000_OCP		0x340f
+#define NBL_DEVICE_ID_M18000_LX_OCP		0x3410
+#define NBL_DEVICE_ID_M18000_BASE_T_OCP		0x3411
+#define NBL_DEVICE_ID_M18000_LX_BASE_T_OCP	0x3412
+
+/* All below IDs belong to Leonis ASIC family, different form-factor variants,
+ * share the same hardware initialization flow without differentiated ops.
+ */
+static const struct pci_device_id nbl_id_table[] = {
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_LX),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_BASE_T),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_LX_BASE_T),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_OCP),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_LX_OCP),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_BASE_T_OCP),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18110_LX_BASE_T_OCP),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_LX),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_BASE_T),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_LX_BASE_T),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_OCP),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_LX_OCP),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_BASE_T_OCP),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	{ PCI_DEVICE(NBL_VENDOR_ID, NBL_DEVICE_ID_M18000_LX_BASE_T_OCP),
+	  .driver_data = BIT(NBL_CAP_HAS_NET_BIT) },
+	/* required as sentinel */
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, nbl_id_table);
+
+static struct pci_driver nbl_driver = {
+	.name = NBL_DRIVER_NAME,
+	.id_table = nbl_id_table,
+	.probe = nbl_probe,
+	.remove = nbl_remove,
+};
+
+module_pci_driver(nbl_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Nebula Matrix Network Driver");
+MODULE_AUTHOR("Illusion Wang <illusion.wang@nebula-matrix.com>");

3/2 2026/08/07 12:44 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 96872
							output: 104
							thoughts: 1554
						

Thoughts:
**WorthFuzzing Decision for Nebula-matrix NIC Driver**

Okay, so I've got this patch series introducing a new Ethernet driver for the Nebula-matrix m18110-NIC/m18000-NIC family. It's adding code under `drivers/net/ethernet/nebula-matrix/nbl/`, and the driver registers a PCI driver called `nbl_driver`.  It's clear this is intended for a physical PCIe card, a SmartNIC/NIC specifically designed for Data Center applications. It utilizes MSI-X, mailboxes, and all the usual hardware-specific interfaces.

The instructions are very specific: I need to return `WorthFuzzing=false` if the patch *only* contains code in vendor-specific PCIe switch, SmartNIC, or GPU drivers that need *physical* hardware and aren't emulated by QEMU.  Let's break this down. Is this driver for a SmartNIC? Yes, it's for the Nebula-matrix m18110/m18000 NICs, clearly a NIC designed for data centers.  Does it require physical hardware? Absolutely, it's a PCIe NIC.  Is it emulated in standard QEMU? No. Therefore, the Nebula-matrix NIC is a vendor-specific NIC that requires a physical PCIe card, and isn't emulated in QEMU.

The instructions say that if all code added falls under this category, `WorthFuzzing=false`. The listed files: documentation, Kconfig, Makefiles, the driver source itself, common includes, and the hardware-specific code, all belong to the Nebula-matrix NIC driver.  This is a self-contained driver that isn't dependent on any other code to work.  So, I'm confident that all the added code here falls into the category of vendor-specific SmartNIC drivers that need a physical card and aren't emulated.  

Therefore, I'll return `WorthFuzzing=false`.



4/2 2026/08/07 12:44 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new driver for the Nebula-matrix m18110-NIC/m18000-NIC family of network interface cards. This is a vendor-specific PCIe hardware driver that requires physical hardware not emulated in standard QEMU environments. Therefore, the code paths are impossible to reach and fuzz in virtualized environments.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new driver for the Nebula-matrix m18110-NIC/m18000-NIC family of network interface cards. This is a vendor-specific PCIe hardware driver that requires physical hardware not emulated in standard QEMU environments. Therefore, the code paths are impossible to reach and fuzz in virtualized environments.",
  "WorthFuzzing": false
}

5/1 2026/08/07 12:44 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)