All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 01/15] yunsilicon: add PCI core driver skeleton
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
@ 2026-10-12 11:43 ` Xin Tian
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:43 ` [PATCH 02/15] yunsilicon: add the register HAL Xin Tian
                   ` (14 subsequent siblings)
  15 siblings, 1 reply; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:43 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the Yunsilicon XSC PCI core driver skeleton: Kconfig and Makefile
plumbing, the register definitions and the PCI probe/remove path that
enables the device, maps BAR 0 and allocates the per-function resource
structure.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 drivers/net/ethernet/Kconfig                  |    1 +
 drivers/net/ethernet/Makefile                 |    1 +
 drivers/net/ethernet/yunsilicon/Kconfig       |   26 +
 drivers/net/ethernet/yunsilicon/Makefile      |    8 +
 .../ethernet/yunsilicon/xsc/common/xsc_cmd.h  | 1022 +++++++++++++++++
 .../yunsilicon/xsc/common/xsc_cmd_api.h       |   25 +
 .../ethernet/yunsilicon/xsc/common/xsc_cmdq.h |  192 ++++
 .../ethernet/yunsilicon/xsc/common/xsc_core.h |  707 ++++++++++++
 .../yunsilicon/xsc/common/xsc_device.h        |   94 ++
 .../ethernet/yunsilicon/xsc/common/xsc_hsi.h  |  123 ++
 .../ethernet/yunsilicon/xsc/common/xsc_reg.h  |   28 +
 .../net/ethernet/yunsilicon/xsc/pci/Kconfig   |   15 +
 .../net/ethernet/yunsilicon/xsc/pci/Makefile  |    9 +
 .../net/ethernet/yunsilicon/xsc/pci/main.c    |  253 ++++
 14 files changed, 2504 insertions(+)
 create mode 100644 drivers/net/ethernet/yunsilicon/Kconfig
 create mode 100644 drivers/net/ethernet/yunsilicon/Makefile
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd_api.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmdq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_core.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_device.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_hsi.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_reg.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/Kconfig
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/main.c

diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index 8581ccba1..3f357b773 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -70,6 +70,7 @@ source "drivers/net/ethernet/i825xx/Kconfig"
 source "drivers/net/ethernet/ibm/Kconfig"
 source "drivers/net/ethernet/intel/Kconfig"
 source "drivers/net/ethernet/xscale/Kconfig"
+source "drivers/net/ethernet/yunsilicon/Kconfig"
 
 config JME
 	tristate "JMicron(R) PCI-Express Gigabit Ethernet support"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index 2b1153d35..4ad89b36b 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_NET_VENDOR_INTEL) += intel/
 obj-$(CONFIG_NET_VENDOR_I825XX) += i825xx/
 obj-$(CONFIG_NET_VENDOR_MICROSOFT) += microsoft/
 obj-$(CONFIG_NET_VENDOR_XSCALE) += xscale/
+obj-$(CONFIG_NET_VENDOR_YUNSILICON) += yunsilicon/
 obj-$(CONFIG_JME) += jme.o
 obj-$(CONFIG_KORINA) += korina.o
 obj-$(CONFIG_LANTIQ_ETOP) += lantiq_etop.o
diff --git a/drivers/net/ethernet/yunsilicon/Kconfig b/drivers/net/ethernet/yunsilicon/Kconfig
new file mode 100644
index 000000000..ffdfdf4bf
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/Kconfig
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+# All rights reserved.
+#
+# Yunsilicon driver configuration
+#
+
+config NET_VENDOR_YUNSILICON
+	bool "Yunsilicon devices"
+	default y
+	depends on PCI
+	depends on 64BIT || COMPILE_TEST
+	help
+	  If you have a network (Ethernet) device 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 Yunsilicon cards. If you say Y, you will be asked
+	  for your specific card in the following questions.
+
+if NET_VENDOR_YUNSILICON
+
+source "drivers/net/ethernet/yunsilicon/xsc/pci/Kconfig"
+
+endif # NET_VENDOR_YUNSILICON
diff --git a/drivers/net/ethernet/yunsilicon/Makefile b/drivers/net/ethernet/yunsilicon/Makefile
new file mode 100644
index 000000000..b2924ccaf
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+# All rights reserved.
+#
+# Makefile for the Yunsilicon device drivers.
+#
+
+obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc/pci/
diff --git a/drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd.h b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd.h
new file mode 100644
index 000000000..0b8f669b5
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd.h
@@ -0,0 +1,1022 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_CMD_H
+#define __XSC_CMD_H
+
+#include <linux/if_ether.h>
+
+#define XSC_CMDQ_VERSION	0x32
+
+#define XSC_BOARD_SN_LEN	32
+#define DSCP_PCP_UNSET		255
+#define XSC_MAX_NUM_PCIE_INTF		2
+#define XSC_MAX_PF_NUM_PER_PCIE		8
+
+/* hca_cap mbox version */
+#define CMD_QUERY_HCA_CAP_V1	1
+#define CMD_QUERY_HCA_CAP_V2	2
+#define CMD_QUERY_HCA_CAP_V3	3
+#define CMD_QUERY_HCA_CAP_V4	4
+#define CMD_QUERY_HCA_CAP_V5	5
+#define CMD_QUERY_HCA_CAP_V6	6
+#define CMD_QUERY_HCA_CAP_V7	7
+#define CMD_QUERY_HCA_CAP_V8	8
+#define CMD_QUERY_HCA_CAP_V9	9
+
+#define CMD_SET_CQ_CONTEXT_V1	1
+
+enum {
+	XSC_CMDQ_GET_HW_STATS_RDMA_V0 = 0,
+	XSC_CMDQ_GET_HW_STATS_RDMA_V1 = 1,
+
+	XSC_CMDQ_GET_HW_STATS_RDMA_UNKNOW,
+};
+
+enum {
+	XSC_CMD_STAT_OK				= 0x0,
+	XSC_CMD_STAT_INT_ERR			= 0x1,
+	XSC_CMD_STAT_BAD_OP_ERR			= 0x2,
+	XSC_CMD_STAT_BAD_PARAM_ERR		= 0x3,
+	XSC_CMD_STAT_BAD_SYS_STATE_ERR		= 0x4,
+	XSC_CMD_STAT_BAD_RES_ERR		= 0x5,
+	XSC_CMD_STAT_RES_BUSY			= 0x6,
+	XSC_CMD_STAT_LIM_ERR			= 0x8,
+	XSC_CMD_STAT_BAD_RES_STATE_ERR		= 0x9,
+	XSC_CMD_STAT_IX_ERR			= 0xa,
+	XSC_CMD_STAT_NO_RES_ERR			= 0xf,
+	XSC_CMD_STAT_BAD_QP_STATE_ERR		= 0x10,
+	XSC_CMD_STAT_BAD_PKT_ERR		= 0x30,
+	XSC_CMD_STAT_BAD_SIZE_OUTS_CQES_ERR	= 0x40,
+	XSC_CMD_STAT_BAD_INP_LEN_ERR		= 0x50,
+	XSC_CMD_STAT_BAD_OUTP_LEN_ERR		= 0x51,
+};
+
+/* status aliases used by the RDMA driver (same protocol values) */
+#define XSC_CMD_STATUS_OK			XSC_CMD_STAT_OK
+#define XSC_CMD_STATUS_FAIL			XSC_CMD_STAT_INT_ERR
+#define XSC_CMD_STATUS_NOT_SUPPORTED		XSC_CMD_STAT_BAD_OP_ERR
+#define XSC_CMD_STATUS_BAD_PARAM		XSC_CMD_STAT_BAD_PARAM_ERR
+#define XSC_CMD_STATUS_INVAL_RES		XSC_CMD_STAT_BAD_RES_ERR
+#define XSC_CMD_STATUS_BUSY			XSC_CMD_STAT_RES_BUSY
+#define XSC_CMD_STATUS_INVAL_DATA		XSC_CMD_STAT_LIM_ERR
+#define XSC_CMD_STATUS_NOT_FOUND		XSC_CMD_STAT_IX_ERR
+#define XSC_CMD_STATUS_NO_RES			XSC_CMD_STAT_NO_RES_ERR
+#define XSC_CMD_STATUS_BAD_INBUF		XSC_CMD_STAT_BAD_INP_LEN_ERR
+#define XSC_CMD_STATUS_BAD_OUTBUF		XSC_CMD_STAT_BAD_OUTP_LEN_ERR
+
+/* extended status codes */
+#define XSC_CMD_STATUS_INVAL_FUNC		0x41
+#define XSC_CMD_STATUS_NO_MPT_RES		0x42
+#define XSC_CMD_STATUS_NO_MTT_RES		0x43
+#define XSC_CMD_STATUS_NO_EQN_RES		0x44
+#define XSC_CMD_STATUS_NO_EQ_PA_RES		0x45
+#define XSC_CMD_STATUS_NO_CQN_RES		0x46
+#define XSC_CMD_STATUS_NO_CQ_PA_RES		0x47
+#define XSC_CMD_STATUS_NO_QPN_RES		0x48
+#define XSC_CMD_STATUS_NO_QP_PA_RES		0x49
+#define XSC_CMD_STATUS_NO_PDN_RES		0x4a
+#define XSC_CMD_STATUS_QP_FLUSH_BUSY		0x4b
+#define XSC_CMD_STATUS_QP_FLUSH_PENDING		0x4c
+#define XSC_CMD_STATUS_INVAL_OPCODE		0x52
+
+enum {
+	XSC_CMD_OP_QUERY_HCA_CAP		= 0x100,
+	XSC_CMD_OP_QUERY_CMDQ_VERSION		= 0x10a,
+	XSC_CMD_OP_FUNCTION_RESET		= 0x10c,
+	XSC_CMD_OP_DUMMY			= 0x10d,
+	XSC_CMD_OP_QUERY_GUID			= 0x113,
+	XSC_CMD_OP_ACTIVATE_HW_CONFIG		= 0x114,
+
+	XSC_CMD_OP_QUERY_HW_STATS		= 0x1200,
+	XSC_CMD_OP_QUERY_HW_STATS_RDMA		= 0x1203,
+	XSC_CMD_OP_QUERY_HW_GLOBAL_STATS	= 0x1210,
+
+	XSC_CMD_OP_CREATE_MKEY			= 0x200,
+	XSC_CMD_OP_DESTROY_MKEY			= 0x202,
+	XSC_CMD_OP_REG_MR			= 0x204,
+	XSC_CMD_OP_DEREG_MR			= 0x205,
+	XSC_CMD_OP_SET_MPT			= 0x206,
+	XSC_CMD_OP_SET_MTT			= 0x207,
+
+	XSC_CMD_OP_CREATE_EQ			= 0x301,
+	XSC_CMD_OP_DESTROY_EQ			= 0x302,
+
+	XSC_CMD_OP_CREATE_CQ			= 0x400,
+	XSC_CMD_OP_DESTROY_CQ			= 0x401,
+	XSC_CMD_OP_SET_CQ_CONTEXT		= 0x406,
+	XSC_CMD_OP_SET_CQ_BUF_PA		= 0x407,
+	XSC_CMD_OP_CREATE_CQ_EX			= 0x408,
+
+	XSC_CMD_OP_CREATE_QP			= 0x500,
+	XSC_CMD_OP_DESTROY_QP			= 0x501,
+	XSC_CMD_OP_RST2INIT_QP			= 0x502,
+	XSC_CMD_OP_INIT2RTR_QP			= 0x503,
+	XSC_CMD_OP_RTR2RTS_QP			= 0x504,
+	XSC_CMD_OP_RTS2RTS_QP			= 0x505,
+	XSC_CMD_OP_SQERR2RTS_QP			= 0x506,
+	XSC_CMD_OP_2ERR_QP			= 0x507,
+	XSC_CMD_OP_RTS2SQD_QP			= 0x508,
+	XSC_CMD_OP_SQD2RTS_QP			= 0x509,
+	XSC_CMD_OP_2RST_QP			= 0x50a,
+	XSC_CMD_OP_QUERY_QP			= 0x50b,
+	XSC_CMD_OP_INIT2INIT_QP			= 0x50e,
+	XSC_CMD_OP_SQD2SQD_QP			= 0x511,
+	XSC_CMD_OP_CREATE_MULTI_QP		= 0x515,
+
+	XSC_CMD_OP_ALLOC_PD			= 0x800,
+	XSC_CMD_OP_DEALLOC_PD			= 0x801,
+
+	XSC_CMD_OP_MODIFY_RAW_QP		= 0x81f,
+
+	XSC_CMD_OP_ENABLE_NIC_HCA		= 0x810,
+	XSC_CMD_OP_DISABLE_NIC_HCA		= 0x811,
+
+	XSC_CMD_OP_QUERY_VPORT_STATE		= 0x822,
+	XSC_CMD_OP_QUERY_LINK_INFO		= 0x832,
+	XSC_CMD_OP_MODIFY_LINK_INFO		= 0x834,
+	XSC_CMD_OP_QUERY_EVENT_TYPE		= 0x831,
+
+	XSC_CMD_OP_ENABLE_MSIX			= 0x850,
+
+	XSC_CMD_OP_SET_MTU			= 0x1100,
+	XSC_CMD_OP_QUERY_ETH_MAC		= 0X1101,
+
+	XSC_CMD_OP_SET_PORT_ADMIN_STATUS	= 0x1801,
+
+	XSC_CMD_OP_MAX
+};
+
+enum xsc_dma_direct {
+	XSC_DMA_DIR_TO_MAC,
+	XSC_DMA_DIR_READ,
+	XSC_DMA_DIR_WRITE,
+	XSC_DMA_DIR_LOOPBACK,
+	XSC_DMA_DIR_MAX
+};
+
+/* hw feature bitmap, 32bit */
+enum xsc_hw_feature_flag {
+	XSC_HW_RDMA_SUPPORT			= BIT(0),
+	XSC_HW_PFC_PRIO_STATISTIC_SUPPORT	= BIT(1),
+	XSC_HW_PFC_STALL_STATS_SUPPORT		= BIT(3),
+	XSC_HW_RDMA_CM_SUPPORT			= BIT(5),
+	XSC_HW_MTU_8192_SUPPORT			= BIT(11),
+	XSC_HW_RDMA_ATOMIC_SUPPORT		= BIT(17),
+	XSC_HW_MULTIDOORBELL_SUPPORT		= BIT(18),
+	XSC_HW_CQE64_SUPPORT			= BIT(21),
+
+	XSC_HW_LAST_FEATURE			= BIT(31)
+};
+
+struct xsc_inbox_hdr {
+	__be16		opcode;
+	u8		rsvd[4];
+	__be16		ver; /* cmd version */
+};
+
+struct xsc_outbox_hdr {
+	u8		status;
+	u8		rsvd[5];
+	__be16		ver;
+};
+
+/*CQ mbox*/
+struct xsc_cq_context {
+	__be16		eqn; /* event queue number */
+	__be16		pa_num; /* physical address count in the ctx */
+	__be16		glb_func_id;
+	u8		log_cq_sz;
+	u8		cq_type;
+};
+
+struct xsc_cq_context_ex {
+	struct xsc_cq_context ctx;
+	u8		page_shift;
+	u8		overrun_ignore;
+	u8		rsvd[2];
+	u32		pa_num_ex;
+};
+
+struct xsc_create_cq_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	struct xsc_cq_context	ctx;
+	__be64			pas[]; /* physical address list */
+};
+
+struct xsc_create_cq_ex_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	struct xsc_cq_context_ex	ctx_ex;
+	__be64			pas[];
+};
+
+struct xsc_create_cq_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be32			cqn; /* completion queue number */
+	u8			rsvd[4];
+};
+
+struct xsc_destroy_cq_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be32			cqn;
+	u8			rsvd[4];
+};
+
+struct xsc_destroy_cq_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+struct xsc_set_cq_context_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	struct xsc_cq_context_ex	ctx_ex;
+};
+
+struct xsc_set_cq_context_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be32			cqn;
+	__be32			cq_pa_list_base;
+};
+
+struct xsc_set_cq_buf_pa_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be32			pa_list_start;
+	__be32			pa_num;
+	__be64			pas[];
+};
+
+struct xsc_set_cq_buf_pa_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+/* MKey / MPT / MTT mbox */
+struct xsc_create_mkey_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	u8			rsvd[4];
+};
+
+struct xsc_create_mkey_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be32			mkey;
+};
+
+struct xsc_destroy_mkey_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be32			mkey;
+};
+
+struct xsc_destroy_mkey_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd;
+};
+
+struct xsc_mpt_item {
+	__be32		pdn;
+	__be32		pa_num;
+	__be32		len;
+	__be32		mkey;
+	__be32		len_h;
+	u8		is_gpu;
+	u8		acc;
+	u8		page_mode;
+	u8		map_en;
+	__be64		va_base;
+};
+
+struct xsc_set_mpt_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	struct xsc_mpt_item	mpt_item;
+};
+
+struct xsc_set_mpt_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be32			mtt_base;
+	u8			rsvd[4];
+};
+
+struct xsc_mtt_setting {
+	__be32		mtt_base;
+	__be32		pa_num;
+	__be64		pas[];
+};
+
+struct xsc_set_mtt_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	struct xsc_mtt_setting	mtt_setting;
+};
+
+struct xsc_set_mtt_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+/*QP mbox*/
+enum {
+	MODULE_SPEED_UNKNOWN,
+	MODULE_SPEED_10G,
+	MODULE_SPEED_25G,
+	MODULE_SPEED_40G_R4,
+	MODULE_SPEED_50G_R,
+	MODULE_SPEED_50G_R2,
+	MODULE_SPEED_100G_R2,
+	MODULE_SPEED_100G_R4,
+	MODULE_SPEED_200G_R4,
+	MODULE_SPEED_200G_R8,
+	MODULE_SPEED_400G_R8,
+	MODULE_SPEED_400G_R4,
+};
+
+struct xsc_create_qp_request {
+	__be16		input_qpn;
+	__be16		pa_num;
+	u8		qp_type;
+	u8		log_sq_sz;
+	u8		log_rq_sz;
+	u8		dma_direct;
+	__be32		pdn; /* protect domain number */
+	__be16		cqn_send;
+	__be16		cqn_recv;
+	__be16		glb_funcid;
+	/*rsvd, the old logic_port */
+	u8		page_shift;
+	u8		rsvd;
+	__be64		pas[];
+};
+
+struct xsc_create_qp_mbox_in {
+	struct xsc_inbox_hdr		hdr;
+	struct xsc_create_qp_request	req;
+};
+
+struct xsc_create_qp_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be32			qpn; /* queue pair number */
+	u8			rsvd[4];
+};
+
+struct xsc_destroy_qp_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be32			qpn;
+	u8			rsvd[4];
+};
+
+struct xsc_destroy_qp_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+/* MR mbox */
+struct xsc_register_mr_request {
+	__be32		pdn;
+	__be32		pa_num;
+	__be64		len;
+	__be32		mkey;
+	u8		is_gpu;
+	u8		acc;
+	u8		page_mode;
+	u8		map_en;
+	__be64		va_base;
+	__be64		pas[];
+};
+
+struct xsc_register_mr_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	struct xsc_register_mr_request	req;
+};
+
+struct xsc_unregister_mr_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be32			mkey;
+	u8			rsvd[4];
+};
+
+struct xsc_unregister_mr_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+enum qp_access_flag {
+	QP_ACCESS_REMOTE_READ = (1 << 0),
+	QP_ACCESS_REMOTE_WRITE = (1 << 1),
+	QP_ACCESS_REMOTE_ATOMIC = (1 << 2),
+	QP_ACCESS_MODIFY_QP_TYPE = (1 << 3),
+};
+
+#define	XSC_QP_CONTEXT_V1	1
+#define XSC_QP_CONTEXT_V2	2
+
+struct xsc_qp_context {
+	__be32		remote_qpn;
+	__be32		cqn_send;
+	__be32		cqn_recv;
+	__be32		next_send_psn;
+	__be32		next_recv_psn;
+	__be32		pdn;
+	__be16		src_udp_port;
+	__be16		path_id;
+	u8		mtu_mode;
+	u8		lag_sel;
+	u8		lag_sel_en;
+	u8		retry_cnt;
+	u8		rnr_retry;
+	u8		dscp;
+	u8		state;
+	u8		hop_limit;
+	u8		dmac[6];
+	u8		smac[6];
+	__be32		dip[4];
+	__be32		sip[4];
+	__be16		ip_type;
+	__be16		grp_id;
+	u8		vlan_valid;
+	u8		dci_cfi_prio_sl;
+	__be16		vlan_id;
+	u8		qp_out_port;
+	u8		pcie_no;
+	__be16		lag_id;
+	__be16		func_id;
+	u8		max_sra;
+	u8		max_rra;
+	u8		no_need_wait;
+	u8		swith_qp_type;
+	u8		rsvd0[2];
+	__be32		qp_access_flags;
+};
+
+struct xsc_modify_qp_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be32			qpn;
+	struct xsc_qp_context	ctx;
+	u8			no_need_wait;
+};
+
+struct xsc_modify_qp_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+struct xsc_query_qp_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be32			qpn;
+	u8			rsvd[4];
+};
+
+struct xsc_query_qp_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	struct xsc_qp_context	ctx;
+};
+
+struct xsc_create_multiqp_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be16			qp_num;
+	u8			qp_type;
+	u8			rsvd;
+	__be32			req_len;
+	u8			data[];
+};
+
+struct xsc_create_multiqp_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be32			qpn_base;
+};
+
+/* PD mbox */
+struct xsc_alloc_pd_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+struct xsc_alloc_pd_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be32			pdn;
+	u8			rsvd[4];
+};
+
+struct xsc_dealloc_pd_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be32			pdn;
+	u8			rsvd[4];
+};
+
+struct xsc_dealloc_pd_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+/* MSIX TABLE mbox */
+struct xsc_msix_table_info_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be16			index;
+	u8			rsvd[6];
+};
+
+struct xsc_msix_table_info_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be32			addr_lo;
+	__be32			addr_hi;
+	__be32			data;
+};
+
+/*EQ mbox*/
+struct xsc_eq_context {
+	__be16			vecidx;
+	__be16			pa_num;
+	u8			log_eq_sz;
+	__be16			glb_func_id;
+	u8			is_async_eq;
+	u8			rsvd;
+};
+
+struct xsc_create_eq_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	struct xsc_eq_context	ctx;
+	__be64			pas[];
+};
+
+struct xsc_create_eq_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be32			eqn;
+	u8			rsvd[4];
+};
+
+struct xsc_destroy_eq_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be32			eqn;
+	u8			rsvd[4];
+};
+
+struct xsc_destroy_eq_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+struct xsc_cmd_query_cmdq_ver_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+struct xsc_cmd_query_cmdq_ver_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be16			cmdq_ver;
+	u8			rsvd[6];
+};
+
+struct xsc_fw_version {
+	u8		fw_version_major;
+	u8		fw_version_minor;
+	__be16		fw_version_patch;
+	__be32		fw_version_tweak;
+	u8		fw_version_extra_flag;
+	u8		rsvd[7];
+};
+
+struct xsc_hca_cap {
+	u8		rsvd1[12];
+	u8		send_seg_num;
+	u8		send_wqe_shift;
+	u8		recv_seg_num;
+	u8		recv_wqe_shift;
+	u8		log_max_srq_sz;
+	u8		log_max_qp_sz;
+	u8		log_max_mtt;
+	u8		log_max_qp;
+	u8		log_max_strq_sz;
+	u8		log_max_srqs;
+	u8		rsvd4[2];
+	u8		log_max_tso;
+	u8		log_max_cq_sz;
+	u8		rsvd6;
+	u8		log_max_cq;
+	u8		log_max_eq_sz;
+	u8		log_max_mkey;
+	u8		log_max_msix;
+	u8		log_max_eq;
+	u8		max_indirection;
+	u8		log_max_mrw_sz;
+	u8		log_max_bsf_list_sz;
+	u8		log_max_klm_list_sz;
+	u8		rsvd_8_0;
+	u8		log_max_ra_req_dc;
+	u8		rsvd_8_1;
+	u8		log_max_ra_res_dc;
+	u8		rsvd9;
+	u8		log_max_ra_req_qp;
+	u8		log_max_qp_depth;
+	u8		log_max_ra_res_qp;
+	__be16		max_vfs;
+	__be16		raweth_qp_id_end;
+	__be16		raw_tpe_qp_num;
+	__be16		max_qp_count;
+	__be16		raweth_qp_id_base;
+	u8		rsvd13;
+	u8		local_ca_ack_delay;
+	u8		max_num_eqs;
+	u8		num_ports;
+	u8		log_max_msg;
+	u8		mac_port;
+	__be16		raweth_rss_qp_id_base;
+	__be16		stat_rate_support;
+	u8		rsvd16[2];
+	__be64		flags;
+	u8		rsvd17;
+	u8		uar_sz;
+	u8		rsvd18;
+	u8		log_pg_sz;
+	__be16		bf_log_bf_reg_size;
+	__be16		msix_base;
+	__be16		msix_num;
+	__be16		max_desc_sz_sq;
+	u8		rsvd20[2];
+	__be16		max_desc_sz_rq;
+	u8		rsvd21[2];
+	__be16		max_desc_sz_sq_dc;
+	u8		rsvd22[4];
+	__be16		max_qp_mcg;
+	u8		rsvd23;
+	u8		log_max_mcg;
+	u8		rsvd24;
+	u8		log_max_pd;
+	u8		rsvd25;
+	u8		log_max_xrcd;
+	u8		rsvd26[40];
+	__be32		uar_page_sz;
+	u8		rsvd27[8];
+	__be32		hw_feature_flag; /* enum xsc_hw_feature_flag */
+	__be16		pf0_vf_funcid_base;
+	__be16		pf0_vf_funcid_top;
+	__be16		pf1_vf_funcid_base;
+	__be16		pf1_vf_funcid_top;
+	__be16		pcie0_pf_funcid_base;
+	__be16		pcie0_pf_funcid_top;
+	__be16		pcie1_pf_funcid_base;
+	__be16		pcie1_pf_funcid_top;
+	u8		log_msx_atomic_size_qp;
+	u8		pcie_host;
+	u8		rsvd28;
+	u8		log_msx_atomic_size_dc;
+	u8		board_sn[XSC_BOARD_SN_LEN];
+	u8		max_tc;
+	u8		mac_bit;
+	__be16		funcid_to_logic_port;
+	u8		rsvd29[6];
+	u8		nif_port_num;
+	u8		reg_mr_via_cmdq;
+	__be32		hca_core_clock;
+	__be32		max_rwq_indirection_tables; /* rss_caps */
+	__be32		max_rwq_indirection_table_size; /* rss_caps */
+	__be32		chip_ver_h;
+	__be32		chip_ver_m;
+	__be32		chip_ver_l;
+	__be32		hotfix_num;
+	__be32		feature_flag;
+	__be32		rx_pkt_len_max;
+	__be32		glb_func_id;
+	__be64		tx_db;
+	__be64		rx_db;
+	__be64		complete_db;
+	__be64		complete_reg;
+	__be64		event_db;
+	__be32		qp_rate_limit_min;
+	__be32		qp_rate_limit_max;
+	struct xsc_fw_version	fw_ver;
+	u8		lag_logic_port_ofst;
+	/* V1 */
+	__be64		max_mr_size;
+	__be16		max_cmd_in_len;
+	__be16		max_cmd_out_len;
+	/* V2 */
+	__be32		max_qp;
+	__be32		max_cq;
+	__be32		max_pd;
+	__be32		max_mtt;
+	/* V3 */
+	__be32		mpt_tbl_addr;
+	__be32		mpt_tbl_depth;
+	__be32		mpt_tbl_width;
+	__be32		mtt_inst_base_addr;
+	__be32		mtt_inst_stride;
+	__be32		mtt_inst_num_log;
+	__be32		mtt_inst_depth;
+	/* V4 */
+	__be16		vf_funcid_base[XSC_MAX_NUM_PCIE_INTF][XSC_MAX_PF_NUM_PER_PCIE];
+	__be16		vf_funcid_top[XSC_MAX_NUM_PCIE_INTF][XSC_MAX_PF_NUM_PER_PCIE];
+	__be16		pf_funcid_base[XSC_MAX_NUM_PCIE_INTF];
+	__be16		pf_funcid_top[XSC_MAX_NUM_PCIE_INTF];
+	u8		pcie_no;
+	u8		pf_id;
+	__be16		vf_id;
+	u8		pcie_host_num;
+	u8		pf_num_per_pcie;
+	/* V6 */
+	u16		ft_support;
+	/* V7 */
+	__be16		total_dynamic_vf_msix;
+	__be16		min_dynamic_vf_msix;
+	__be16		max_dynamic_vf_msix;
+	/* V8 */
+	u8		max_sgl_rd;
+	u8		rsvd30;
+	/* V9 */
+	__be32		mdb_num;
+	u8		rsvd31[4];
+	__be64		mdb_base;
+};
+
+struct xsc_cmd_query_hca_cap_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be16			cpu_num;
+	u8			rsvd[6];
+};
+
+struct xsc_cmd_query_hca_cap_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd0[8];
+	struct xsc_hca_cap	hca_cap;
+};
+
+struct xsc_query_vport_state_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			admin_state:4;
+	u8			state:4;
+};
+
+struct xsc_query_vport_state_in {
+	struct xsc_inbox_hdr	hdr;
+	__be32			data0;
+#define XSC_QUERY_VPORT_OTHER_VPORT	BIT(0)
+#define XSC_QUERY_VPORT_VPORT_NUM_MASK	GENMASK(16, 1)
+};
+
+enum {
+	XSC_CMD_EVENT_RESP_CHANGE_LINK		= BIT(0),
+	XSC_CMD_EVENT_RESP_TEMP_WARN		= BIT(1),
+	XSC_CMD_EVENT_RESP_OVER_TEMP_PROTECTION	= BIT(2),
+};
+
+struct xsc_event_resp {
+	u8			resp_cmd_type;
+};
+
+struct xsc_event_query_type_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	u8			rsvd[2];
+};
+
+struct xsc_event_query_type_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	struct xsc_event_resp	ctx;
+};
+
+struct xsc_modify_raw_qp_request {
+	__be16		qpn;
+	__be16		lag_id;
+	__be16		func_id;
+	u8		dma_direct;
+	u8		prio;
+	u8		qp_out_port;
+	u8		rsvd[7];
+};
+
+struct xsc_modify_raw_qp_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	u8			pcie_no;
+	u8			rsvd[7];
+	struct xsc_modify_raw_qp_request	req;
+};
+
+struct xsc_modify_raw_qp_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+struct xsc_set_mtu_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be16			mtu;
+	__be16			rx_buf_sz_min;
+	u8			mac_port;
+	u8			rsvd;
+};
+
+struct xsc_set_mtu_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+};
+
+struct xsc_query_eth_mac_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	u8			index;
+};
+
+struct xsc_query_eth_mac_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			mac[ETH_ALEN];
+};
+
+enum {
+	XSC_TBM_CAP_HASH_PPH = 0,
+	XSC_TBM_CAP_RSS,
+	XSC_TBM_CAP_PP_BYPASS,
+	XSC_TBM_CAP_PCT_DROP_CONFIG,
+};
+
+struct xsc_nic_attr {
+	__be16	caps;
+	__be16	caps_mask;
+	u8	mac_addr[ETH_ALEN];
+};
+
+struct xsc_rss_attr {
+	u8	rss_en;
+	u8	hfunc;
+	__be16	rqn_base;
+	__be16	rqn_num;
+	__be32	hash_tmpl;
+};
+
+struct xsc_cmd_enable_nic_hca_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	struct xsc_nic_attr	nic;
+	struct xsc_rss_attr	rss;
+};
+
+struct xsc_cmd_enable_nic_hca_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd0[2];
+};
+
+struct xsc_nic_dis_attr {
+	__be16	caps;
+};
+
+struct xsc_cmd_disable_nic_hca_mbox_in {
+	struct xsc_inbox_hdr		hdr;
+	struct xsc_nic_dis_attr		nic;
+};
+
+struct xsc_cmd_disable_nic_hca_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd0[4];
+};
+
+struct xsc_function_reset_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be16			glb_func_id;
+	u8			rsvd[6];
+};
+
+struct xsc_function_reset_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+struct xsc_cmd_query_guid_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+struct xsc_cmd_query_guid_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be64			guid;
+};
+
+struct xsc_cmd_activate_hw_config_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+struct xsc_cmd_activate_hw_config_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	u8			rsvd[8];
+};
+
+struct xsc_event_linkinfo {
+	u8 linkstatus; /*0:down, 1:up*/
+	u8 port;
+	u8 duplex;
+	u8 autoneg;
+	u32 linkspeed;
+	u64 supported;
+	u64 advertising;
+	u64 supported_fec;	/* reserved, not support currently */
+	u64 advertised_fec;	/* reserved, not support currently */
+	u64 supported_speed[2];
+	u64 advertising_speed[2];
+};
+
+struct xsc_event_query_linkinfo_mbox_out {
+	struct xsc_outbox_hdr		hdr;
+	struct xsc_event_linkinfo	ctx;
+};
+
+struct xsc_event_set_port_admin_status_mbox_in {
+	struct xsc_inbox_hdr	hdr;
+	__be16			status;
+};
+
+struct xsc_event_set_port_admin_status_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	__be32			status;
+};
+
+/* HW stats mbox */
+struct xsc_hw_stats_rdma_pf {
+	/*by mac port*/
+	u64 rdma_tx_pkts;
+	u64 rdma_tx_bytes;
+	u64 rdma_rx_pkts;
+	u64 rdma_rx_bytes;
+	u64 np_cnp_sent;
+	u64 rp_cnp_handled;
+	u64 np_ecn_marked_roce_packets;
+	u64 rp_cnp_ignored;
+	u64 read_rsp_out_of_seq;
+	u64 implied_nak_seq_err;
+	/*by function*/
+	u64 out_of_sequence;
+	u64 packet_seq_err;
+	u64 out_of_buffer;
+	u64 rnr_nak_retry_err;
+	u64 local_ack_timeout_err;
+	u64 rx_read_requests;
+	u64 rx_write_requests;
+	u64 duplicate_requests;
+	u64 rdma_tx_pkts_func;
+	u64 rdma_tx_payload_bytes;
+	u64 rdma_rx_pkts_func;
+	u64 rdma_rx_payload_bytes;
+	/*global*/
+	u64 rdma_loopback_pkts;
+	u64 rdma_loopback_bytes;
+	/*for diamond*/
+	u64 out_of_sequence_sr;
+	u64 packet_seq_err_sr;
+	u64 rdma_ndp_rx_pkts;
+	u64 rdma_ndp_rx_trimmed_pkts;
+	u64 rdma_ndp_trimmed_pkts_sr;
+	u64 rx_icrc_encapsulated;
+};
+
+struct xsc_hw_stats_rdma_vf {
+	/*by function*/
+	u64 rdma_tx_pkts_func;
+	u64 rdma_tx_payload_bytes;
+	u64 rdma_rx_pkts_func;
+	u64 rdma_rx_payload_bytes;
+
+	u64 out_of_sequence;
+	u64 packet_seq_err;
+	u64 out_of_buffer;
+	u64 rnr_nak_retry_err;
+	u64 local_ack_timeout_err;
+	u64 rx_read_requests;
+	u64 rx_write_requests;
+	u64 duplicate_requests;
+};
+
+struct xsc_hw_stats_rdma {
+	u8 is_pf;
+	u8 rsv;
+	__be16 ver;
+	union {
+		struct xsc_hw_stats_rdma_pf pf_stats;
+		struct xsc_hw_stats_rdma_vf vf_stats;
+	} stats;
+};
+
+#define CMD_QUERY_HW_STATS_V1 1
+struct xsc_hw_stats_mbox_in {
+	struct xsc_inbox_hdr hdr;
+	u8 mac_port;
+	u8 is_lag;
+	u8 lag_member_num;
+	u8 member_port[];
+};
+
+struct xsc_hw_stats_rdma_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	struct xsc_hw_stats_rdma	hw_stats;
+};
+
+struct xsc_hw_global_stats_rdma {
+	/*by global*/
+	u64 rdma_loopback_pkts;
+	u64 rdma_loopback_bytes;
+	u64 rx_icrc_encapsulated;
+	u64 req_cqe_error;
+	u64 resp_cqe_error;
+	u64 cqe_msg_code_error;
+};
+
+struct xsc_hw_global_stats_mbox_in {
+	struct xsc_inbox_hdr hdr;
+	u8 rsv[4];
+};
+
+struct xsc_hw_global_stats_mbox_out {
+	struct xsc_outbox_hdr	hdr;
+	struct xsc_hw_global_stats_rdma	hw_stats;
+};
+
+#endif /* __XSC_CMD_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd_api.h b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd_api.h
new file mode 100644
index 000000000..3ef6080ae
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd_api.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_DRIVER_H
+#define __XSC_DRIVER_H
+
+#include "common/xsc_core.h"
+#include "common/xsc_cmd.h"
+
+int xsc_cmd_init(struct xsc_core_device *xdev);
+void xsc_cmd_cleanup(struct xsc_core_device *xdev);
+void xsc_cmd_use_events(struct xsc_core_device *xdev);
+void xsc_cmd_use_polling(struct xsc_core_device *xdev);
+int xsc_cmd_err_handler(struct xsc_core_device *xdev);
+void xsc_cmd_resp_handler(struct xsc_core_device *xdev);
+int xsc_cmd_exec(struct xsc_core_device *xdev,
+		 void *in, unsigned int in_size,
+		 void *out, unsigned int out_size);
+int xsc_cmd_version_check(struct xsc_core_device *xdev);
+
+int xsc_cmd_status_to_err(struct xsc_outbox_hdr *hdr);
+
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmdq.h b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmdq.h
new file mode 100644
index 000000000..004611146
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmdq.h
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_CMDQ_H
+#define __XSC_CMDQ_H
+
+#include <linux/if_ether.h>
+
+#include "common/xsc_cmd.h"
+
+#define XSC_CMD_MAX_RETRY_CNT	3
+
+enum {
+	XSC_CMD_TIMEOUT_MSEC	= 10 * 1000,
+	XSC_CMD_WQ_MAX_NAME	= 32,
+};
+
+enum {
+	XSC_CMD_DATA, /* print command payload only */
+	XSC_CMD_TIME, /* print command execution time */
+};
+
+enum {
+	XSC_MAX_COMMANDS		= 32,
+	XSC_CMD_DATA_BLOCK_SIZE	= 512,
+	XSC_PCI_CMD_XPORT		= 7,
+};
+
+struct xsc_cmd_prot_block {
+	u8		data[XSC_CMD_DATA_BLOCK_SIZE];
+	u8		rsvd0[48];
+	__be64		next;
+	__be32		block_num;
+	/* init to 0, dma user should change this val to 1 */
+	u8		owner_status;
+	u8		token;
+	u8		ctrl_sig;
+	u8		sig;
+};
+
+struct cache_ent {
+	/* protect block chain allocations */
+	spinlock_t		lock;
+	struct list_head	head;
+};
+
+struct cmd_msg_cache {
+	struct cache_ent	large;
+	struct cache_ent	med;
+
+};
+
+#define CMD_FIRST_SIZE 8
+struct xsc_cmd_first {
+	__be32		data[CMD_FIRST_SIZE];
+};
+
+struct xsc_cmd_mailbox {
+	void			*buf;
+	dma_addr_t		dma;
+	struct xsc_cmd_mailbox *next;
+};
+
+struct xsc_cmd_msg {
+	struct list_head	list;
+	struct cache_ent	*cache;
+	u32			len;
+	struct xsc_cmd_first	first;
+	struct xsc_cmd_mailbox	*next;
+};
+
+#define RSP_FIRST_SIZE 14
+struct xsc_rsp_first {
+	/* can be larger, xsc_rsp_layout */
+	__be32		data[RSP_FIRST_SIZE];
+};
+
+struct xsc_rsp_msg {
+	struct list_head		list;
+	struct cache_ent		*cache;
+	u32				len;
+	struct xsc_rsp_first		first;
+	struct xsc_cmd_mailbox		*next;
+};
+
+typedef void (*xsc_cmd_cbk_t)(int status, void *context);
+
+/* hw will use this for some records(e.g. vf_id) */
+struct cmdq_rsv {
+	u16	vf_id;
+	u8	rsv[2];
+};
+
+/* related with hw, won't change */
+
+struct xsc_cmd_layout {
+	struct cmdq_rsv	rsv0;
+	__be32		inlen;
+	__be64		in_ptr;
+	__be32		in[CMD_FIRST_SIZE];
+	__be64		out_ptr;
+	__be32		outlen;
+	u8		token;
+	u8		sig;
+	u8		idx;
+	u8		type: 7;
+	/* rsv for hw, arm will check this bit to make sure mem written */
+	u8		owner_bit: 1;
+};
+
+struct xsc_rsp_layout {
+	struct cmdq_rsv	rsv0;
+	__be32		out[RSP_FIRST_SIZE];
+	u8		token;
+	u8		sig;
+	u8		idx;
+	u8		type: 7;
+	/* rsv for hw, driver will check this bit to make sure mem written */
+	u8		owner_bit: 1;
+};
+
+struct xsc_cmd_work_ent {
+	struct xsc_cmd_msg	*in;
+	struct xsc_rsp_msg	*out;
+	int			idx;
+	struct completion	done;
+	struct xsc_cmd		*cmd;
+	struct work_struct	work;
+	struct xsc_cmd_layout	*lay;
+	struct xsc_rsp_layout	*rsp_lay;
+	int			ret;
+	u8			status;
+	u8			token;
+};
+
+enum xsc_cmd_status {
+	XSC_CMD_STATUS_NORMAL,
+	XSC_CMD_STATUS_TIMEDOUT,
+};
+
+enum xsc_cmd_mode {
+	XSC_CMD_MODE_POLLING,
+	XSC_CMD_MODE_EVENTS,
+};
+
+struct xsc_cmd {
+	void			*cmd_buf;
+	void			*cq_buf;
+	dma_addr_t		dma;
+	dma_addr_t		cq_dma;
+	u16			cmd_pid;
+	u16			cq_cid;
+	u8			owner_bit;
+	u8			retry_cnt;
+	u8			cmdif_rev;
+	u8			log_sz;
+	u8			log_stride;
+	int			max_reg_cmds;
+	int			events;
+	u32 __iomem		*vector;
+
+	/* protect command queue allocations */
+	spinlock_t		alloc_lock;
+	/* protect token allocations */
+	spinlock_t		token_lock;
+	/* protect cmdq req pid doorbell */
+	spinlock_t		doorbell_lock;
+	u8			token;
+	unsigned long		cmd_entry_mask;
+	char			wq_name[XSC_CMD_WQ_MAX_NAME];
+	struct workqueue_struct	*wq;
+	struct task_struct	*cq_task;
+	/* The semaphore limits the number of working commands
+	 * to max_reg_cmds. Each exec_cmd call does a down
+	 * to acquire the semaphore before execution and an up
+	 * after completion, ensuring no more than max_reg_cmds
+	 * commands run at the same time.
+	 */
+	struct semaphore	sem;
+	enum xsc_cmd_mode	mode;
+	struct xsc_cmd_work_ent	*ent_arr[XSC_MAX_COMMANDS];
+	struct dma_pool		*pool;
+	struct cmd_msg_cache	cache;
+	int			checksum_disabled;
+	unsigned int		irqn;
+	u8			ownerbit_learned;
+	u8			cmd_status;
+};
+
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/common/xsc_core.h b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_core.h
new file mode 100644
index 000000000..f491b9d8d
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_core.h
@@ -0,0 +1,707 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_CORE_H
+#define __XSC_CORE_H
+
+#include <linux/pci.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/if_vlan.h>
+#include <linux/reboot.h>
+
+#include "common/xsc_reg.h"
+#include "common/xsc_cmdq.h"
+
+#define XSC_PCI_VENDOR_ID		0x1f67
+
+#define XSC_MC_PF_DEV_ID		0x1011
+#define XSC_MC_VF_DEV_ID		0x1012
+
+#define XSC_MF_HOST_PF_DEV_ID		0x1051
+#define XSC_MF_HOST_VF_DEV_ID		0x1052
+#define XSC_MF_SOC_PF_DEV_ID		0x1053
+
+#define XSC_MS_PF_DEV_ID		0x1111
+#define XSC_MS_VF_DEV_ID		0x1112
+
+#define XSC_MV_HOST_PF_DEV_ID		0x1151
+#define XSC_MV_HOST_VF_DEV_ID		0x1152
+#define XSC_MV_SOC_PF_DEV_ID		0x1153
+
+#define PAGE_SHIFT_4K			12
+#define PAGE_SIZE_4K			(_AC(1, UL) << PAGE_SHIFT_4K)
+
+#define XSC_ORIGIN_PF_BAR_SIZE		(256 * 1024 * 1024)
+
+enum xsc_coredev_type {
+	XSC_COREDEV_PF,
+	XSC_COREDEV_VF,
+	XSC_COREDEV_SF
+};
+
+enum {
+	XSC_MAX_EQ_NAME	= 20
+};
+
+enum {
+	XSC_MAX_IRQ_NAME	= 32
+};
+
+enum {
+	XSC_MAX_FW_PORTS	= 1,
+};
+
+enum {
+	XSC_BF_REGS_PER_PAGE	= 4,
+	XSC_MAX_UAR_PAGES	= 1 << 8,
+	XSC_MAX_UUARS		= XSC_MAX_UAR_PAGES * XSC_BF_REGS_PER_PAGE,
+};
+
+/* alloc */
+struct xsc_buf_list {
+	void			*buf;
+	dma_addr_t		map;
+};
+
+struct xsc_buf {
+	struct xsc_buf_list	direct;
+	struct xsc_buf_list	*page_list;
+	unsigned long		nbufs;
+	unsigned long		npages;
+	unsigned long		page_shift;
+	unsigned long		size;
+};
+
+struct xsc_frag_buf {
+	struct xsc_buf_list	*frags;
+	unsigned long		npages;
+	unsigned long		size;
+	u8			page_shift;
+};
+
+struct xsc_frag_buf_ctrl {
+	struct xsc_buf_list	*frags;
+	u32			sz_m1;
+	u16			frag_sz_m1;
+	u16			strides_offset;
+	u8			log_sz;
+	u8			log_stride;
+	u8			log_frag_strides;
+};
+
+/* qp */
+struct xsc_send_wqe_ctrl_seg {
+	union {
+		__le32		data0;
+		struct {
+			__le32		msg_opcode:8;
+			__le32		with_immdt:1;
+			__le32		csum_en:2;
+			__le32		ds_data_num:5;
+			__le32		wqe_id:16;
+		};
+	};
+#define XSC_WQE_CTRL_SEG_MSG_OPCODE_MASK	GENMASK(7, 0)
+#define XSC_WQE_CTRL_SEG_WITH_IMM		BIT(8)
+#define XSC_WQE_CTRL_SEG_CSUM_EN_MASK		GENMASK(10, 9)
+#define XSC_WQE_CTRL_SEG_DS_DATA_NUM_MASK	GENMASK(15, 11)
+#define XSC_WQE_CTRL_SEG_WQE_ID_MASK		GENMASK(31, 16)
+
+	__le32		msg_len;
+
+	union {
+		__le32		data2;
+		__le32		opcode_data;
+		struct {
+			u8		has_pph:1;
+			u8		so_type:1;
+			__le16		so_data_size:14;
+			u8:8;
+			u8		so_hdr_len:8;
+		};
+	};
+#define XSC_WQE_CTRL_SEG_HAS_PPH		BIT(0)
+#define XSC_WQE_CTRL_SEG_SO_TYPE		BIT(1)
+#define XSC_WQE_CTRL_SEG_SO_DATA_SIZE_MASK	GENMASK(15, 2)
+#define XSC_WQE_CTRL_SEG_SO_HDR_LEN_MASK	GENMASK(31, 24)
+
+	union {
+		__le32		data3;
+		struct {
+			__le32		se:1;
+			__le32		ce:1;
+			__le32:30;
+		};
+	};
+#define XSC_WQE_CTRL_SEG_SE			BIT(0)
+#define XSC_WQE_CTRL_SEG_CE			BIT(1)
+};
+
+struct xsc_wqe_data_seg {
+	union {
+		__le32		data0;
+		__le32		in_line:1;
+		struct {
+			__le32:1;
+			__le32		seg_len:31;
+			__le32		mkey;
+			__le64		va;
+		};
+		struct {
+			__le32:1;
+			__le32		len:7;
+			u8		in_line_data[15];
+		};
+	};
+#define XSC_WQE_DATA_SEG_SEG_LEN_MASK		GENMASK(31, 1)
+};
+
+struct xsc_core_qp {
+	void			(*event)(struct xsc_core_qp *qp, int type);
+	u32			qpn;
+	atomic_t		refcount;
+	struct completion	free;
+	int			pid;
+	u16			qp_type;
+	u16			eth_queue_type;
+	u16			qp_type_internal;
+	u16			grp_id;
+	u8			mac_id;
+	u8			err_occurred;
+	u32			qp_protocol_mode;
+	u32			qp_access_flags;
+};
+
+struct xsc_qp_table {
+	spinlock_t		lock; /* protect radix tree */
+	struct radix_tree_root	tree;
+};
+
+/* cq */
+#define XSC_MAX_NAME_LEN	32
+#define XSC_MAX_IBDEV_NAME_LEN	64
+
+struct xsc_priv {
+	char			name[XSC_MAX_NAME_LEN];
+	char			ibdev_name[XSC_MAX_IBDEV_NAME_LEN];
+	int			cm_pcp;
+	int			cm_dscp;
+	int			force_pcp;
+	int			force_dscp;
+};
+
+enum xsc_dev_event {
+	XSC_DEV_EVENT_SYS_ERROR,
+	XSC_DEV_EVENT_PORT_UP,
+	XSC_DEV_EVENT_PORT_DOWN,
+	XSC_DEV_EVENT_PORT_INITIALIZED,
+	XSC_DEV_EVENT_LID_CHANGE,
+	XSC_DEV_EVENT_PKEY_CHANGE,
+	XSC_DEV_EVENT_GUID_CHANGE,
+	XSC_DEV_EVENT_CLIENT_REREG,
+};
+
+enum {
+	RDMA_PROTO_ROCEV2,
+	RDMA_PROTO_VEROCE,
+};
+
+#define VEROCE_UDP_DPORT	4794
+#define MAX_MR_CACHE_ENTRIES	5000
+
+enum xsc_event {
+	XSC_EVENT_TYPE_COMP			= 0x0,
+	/* mad */
+	XSC_EVENT_TYPE_COMM_EST			= 0x02,
+	XSC_EVENT_TYPE_CQ_ERROR			= 0x04,
+	XSC_EVENT_TYPE_WQ_CATAS_ERROR		= 0x05,
+	XSC_EVENT_TYPE_INTERNAL_ERROR		= 0x08,
+	/* IBV_EVENT_QP_REQ_ERR */
+	XSC_EVENT_TYPE_WQ_INVAL_REQ_ERROR	= 0x10,
+	/* IBV_EVENT_QP_ACCESS_ERR */
+	XSC_EVENT_TYPE_WQ_ACCESS_ERROR		= 0x11,
+};
+
+struct xsc_cqe {
+	union {
+		__le32		data0;
+		struct {
+			u8		placeholder1;
+#define XSC_CQE_MSG_OPCD_MASK		GENMASK(7, 0)
+#define XSC_CQE_ERR_CODE_MASK		GENMASK(6, 0)
+#define XSC_CQE_IS_ERR			BIT(7)
+#define XSC_CQE_QP_ID_MASK		GENMASK(22, 8)
+#define XSC_CQE_SE			BIT(24)
+#define XSC_CQE_HAS_PPH			BIT(25)
+#define XSC_CQE_TYPE			BIT(26)
+#define XSC_CQE_WITH_IMM		BIT(27)
+#define XSC_CQE_CSUM_ERR_MASK		GENMASK(31, 28)
+			__le32		qp_id:15;
+			u8		rsv1:1;
+			u8		se:1;
+			u8		has_pph:1;
+			u8		type:1;
+			u8		with_immdt:1;
+			u8		csum_err:4;
+		};
+	};
+
+	__le32		imm_data;
+	__le32		msg_len;
+	__le32		vni;
+
+	__le64		ts:48;
+
+	__le16		wqe_id;
+	u8		placeholder2;
+	u8		rsv3;
+	__le16		rsv[1];
+
+	union {
+		__le32		data1;
+		struct {
+			__le16		rsv0;
+			__le16		rsv2:15;
+#define XSC_CQE_OWNER			BIT(31)
+			u8		owner:1;
+		};
+	};
+};
+
+struct xsc_cqe64 {
+	struct xsc_cqe	cqe;
+	u8		padding[32];
+};
+
+struct xsc_core_cq {
+	u32			cqn;
+	u32			cqe_sz;
+	struct xsc_core_device	*xdev;
+	struct xsc_core_device	*dev;
+	atomic_t		refcount;
+	struct completion	free;
+	unsigned int		vector;
+	unsigned int		irqn;
+	void			(*comp)(struct xsc_core_cq *cq);
+	void			(*event)(struct xsc_core_cq *cq,
+					 enum xsc_event);
+	u32			cons_index;
+	unsigned int		arm_sn;
+	int			pid;
+	u32			reg_next_cid;
+	u32			reg_done_pid;
+	struct xsc_eq		*eq;
+};
+
+struct xsc_cq_table {
+	spinlock_t		lock; /* protect radix tree */
+	struct radix_tree_root	tree;
+};
+
+/* mr */
+struct xsc_core_mr {
+	u64			iova;
+	u64			size;
+	u32			key;
+	u32			pd;
+	u32			access;
+};
+
+/* eq */
+struct xsc_eq {
+	struct xsc_core_device	*dev;
+	struct xsc_cq_table	cq_table;
+	u32			cons_index;
+	struct xsc_buf		buf;
+	unsigned int		irqn;
+	u32			eqn;
+	u32			nent;
+	cpumask_var_t		mask;
+	char			name[XSC_MAX_EQ_NAME];
+	struct list_head	list;
+	u16			index;
+};
+
+struct xsc_eq_table {
+	void __iomem		*update_ci;
+	void __iomem		*update_arm_ci;
+	struct list_head	comp_eqs_list;
+	struct xsc_eq		pages_eq;
+	struct xsc_eq		async_eq;
+	struct xsc_eq		cmd_eq;
+	int			num_comp_vectors;
+	int			eq_vec_comp_base;
+	/* protect EQs list */
+	spinlock_t		lock;
+};
+
+/* irq */
+struct xsc_irq_info {
+	cpumask_var_t mask;
+	char name[XSC_MAX_IRQ_NAME];
+};
+
+/* adev */
+#define XSC_PCI_DRV_NAME "xsc_pci"
+#define XSC_ETH_ADEV_NAME "eth"
+#define XSC_IB_ADEV_NAME "ib"
+
+enum xsc_adev_idx {
+	XSC_ADEV_IDX_ETH,
+	XSC_ADEV_IDX_IB,
+	XSC_ADEV_IDX_MAX,
+};
+
+struct xsc_adev {
+	struct auxiliary_device	adev;
+	struct xsc_core_device	*xdev;
+
+	int			idx;
+};
+
+struct xsc_board_info {
+	u32			board_id;
+	char			board_sn[XSC_BOARD_SN_LEN];
+	__be64			guid;
+	u8			guid_valid;
+	u8			hw_config_activated;
+	struct xsc_msg_opcode	*msg_opcode;
+};
+
+struct xsc_caps {
+	u8		log_max_eq;
+	u8		log_max_cq;
+	u8		log_max_qp;
+	u8		log_max_mkey;
+	u8		log_max_pd;
+	u8		log_max_srq;
+	u8		log_max_msix;
+	u32		max_cq;
+	u32		max_qp;
+	u32		max_pd;
+	u32		max_cqes;
+	u32		max_wqes;
+	u32		max_sq_desc_sz;
+	u32		max_rq_desc_sz;
+	u64		flags;
+	u16		stat_rate_support;
+	u32		log_max_msg;
+	u32		num_ports;
+	u32		max_ra_res_qp;
+	u32		max_ra_req_qp;
+	u32		max_srq_wqes;
+	u32		bf_reg_size;
+	u32		bf_regs_per_page;
+	u32		reserved_lkey;
+	u8		local_ca_ack_delay;
+	u8		log_max_mcg;
+	u16		max_qp_mcg;
+	u32		min_page_sz;
+	u32		send_ds_num;
+	u32		send_wqe_shift;
+	u32		recv_ds_num;
+	u32		recv_wqe_shift;
+	u32		rx_pkt_len_max;
+
+	u32		msix_enable:1;
+	u32		port_type:1;
+	u32		embedded_cpu:1;
+	u32		eswitch_manager:1;
+	u32		ecpf_vport_exists:1;
+	u32		vport_group_manager:1;
+	u32		sf:1;
+	u32		wqe_inline_mode:3;
+	u32		raweth_qp_id_base:15;
+	u32		rsvd0:7;
+
+	u16		max_vfs;
+	u8		log_max_qp_depth;
+	u8		log_max_current_uc_list;
+	u8		log_max_current_mc_list;
+	u16		log_max_vlan_list;
+	u8		fdb_multi_path_to_table;
+	u8		log_esw_max_sched_depth;
+
+	u8		max_num_sf_partitions;
+	u8		log_max_esw_sf;
+	u16		sf_base_id;
+
+	u32		max_tc:8;
+	u32		ets:1;
+	u32		dcbx:1;
+	u32		dscp:1;
+	u32		sbcam_reg:1;
+	u32		qos:1;
+	u32		port_buf:1;
+	u32		rsvd1:2;
+	u32		raw_tpe_qp_num:16;
+	u32		max_num_eqs:8;
+	u32		mac_port:8;
+	u32		raweth_rss_qp_id_base:16;
+	u16		msix_base;
+	u16		msix_num;
+	u8		log_max_mtt;
+	u8		log_max_tso;
+	u32		hca_core_clock;
+	u32		max_rwq_indirection_tables; /* rss_caps */
+	u32		max_rwq_indirection_table_size; /* rss_caps */
+	u16		raweth_qp_id_end;
+	u32		qp_rate_limit_min;
+	u32		qp_rate_limit_max;
+	u32		hw_feature_flag;
+	u16		pf0_vf_funcid_base;
+	u16		pf0_vf_funcid_top;
+	u16		pf1_vf_funcid_base;
+	u16		pf1_vf_funcid_top;
+	u16		pcie0_pf_funcid_base;
+	u16		pcie0_pf_funcid_top;
+	u16		pcie1_pf_funcid_base;
+	u16		pcie1_pf_funcid_top;
+	u8		nif_port_num;
+	u8		pcie_host;
+	u8		mac_bit;
+	u16		funcid_to_logic_port;
+	u8		lag_logic_port_ofst;
+
+	/* RDMA / MR resources (populated from hca_cap V1~V9) */
+	u64		max_mr_size;
+	u16		max_cmd_in_len;
+	u16		max_cmd_out_len;
+	u32		max_mtt;
+	u32		mpt_tbl_addr;
+	u32		mpt_tbl_depth;
+	u32		mpt_tbl_width;
+	u32		mtt_inst_base_addr;
+	u32		mtt_inst_stride;
+	u32		mtt_inst_num_log;
+	u32		mtt_inst_depth;
+	u32		mdb_num;
+	u64		mdb_base;
+};
+
+struct xsc_srq_cache_wr;
+
+/* xsc_core */
+struct xsc_dev_resource {
+	struct xsc_qp_table	qp_table;
+	struct xsc_cq_table	cq_table;
+	struct xsc_eq_table	eq_table;
+	struct xsc_irq_info	*irq_info;
+	/* protect buffer allocation according to numa node */
+	struct mutex		alloc_mutex;
+	/* protect mkey key allocation */
+	spinlock_t		mkey_lock;
+	u8			mkey_key;
+
+	/* srq number allocation, srqn is a software index only */
+	spinlock_t		srq_lock;
+	unsigned long		*srq_tbl;
+	struct xsc_srq_cache_wr	*srq_cache_wr;
+	u32			max_srq_num;
+};
+
+struct xsc_core_device {
+	struct pci_dev		*pdev;
+	struct device		*device;
+	int			adev_id;
+	struct xsc_adev		**xsc_adev_list;
+	void			*eth_priv;
+	struct xsc_dev_resource	*dev_res;
+	int			numa_node;
+
+	/* async event plumbing, owned by the PCI layer */
+	struct work_struct	event_work;
+	struct workqueue_struct	*event_wq;
+	void (*link_event_handler)(void *xdev);
+
+	void __iomem		*bar;
+	int			bar_num;
+	enum xsc_coredev_type	coredev_type;
+	struct xsc_hw_reg	*hal_regs;
+
+	u8			mac_port;
+	u8			pcie_no;
+	u8			pf_id;
+	u16			glb_func_id;
+	u16			gsi_qpn;
+	atomic_t		num_qps;
+
+	u16			msix_vec_base;
+
+	void			*netdev;
+
+	u32			rdma_proto_mode;
+	u16			cm_udp_dst_port;
+	u16			veroce_udp_dst_port;
+	u8			read_flush;
+	u8			bql_thresh;
+
+	struct xsc_priv		priv;
+
+	struct xsc_cmd		cmd;
+	u16			cmdq_ver;
+
+	struct xsc_caps		caps;
+	struct xsc_board_info	*board_info;
+
+	u32			chip_ver_h;
+	u32			chip_ver_m;
+	u32			chip_ver_l;
+	u32			hotfix_num;
+	u32			feature_flag;
+
+	u8			fw_version_major;
+	u8			fw_version_minor;
+	u16			fw_version_patch;
+	u32			fw_version_tweak;
+	u8			fw_version_extra_flag;
+	cpumask_var_t		xps_cpumask;
+};
+
+int xsc_core_create_resource_common(struct xsc_core_device *xdev,
+				    struct xsc_core_qp *qp);
+void xsc_core_destroy_resource_common(struct xsc_core_device *xdev,
+				      struct xsc_core_qp *qp);
+int xsc_core_eth_create_qp(struct xsc_core_device *xdev,
+			   struct xsc_create_qp_mbox_in *in,
+			   int insize, u32 *p_qpn);
+int xsc_core_eth_modify_qp_status(struct xsc_core_device *xdev,
+				  u32 qpn, u16 status);
+int xsc_core_eth_destroy_qp(struct xsc_core_device *xdev, u32 qpn);
+int xsc_core_eth_create_rss_qp_rqs(struct xsc_core_device *xdev,
+				   struct xsc_create_multiqp_mbox_in *in,
+				   int insize,
+				   u32 *p_qpn_base);
+int xsc_core_eth_modify_raw_qp(struct xsc_core_device *xdev,
+			       struct xsc_modify_raw_qp_mbox_in *in);
+int xsc_core_eth_create_cq(struct xsc_core_device *xdev,
+			   struct xsc_core_cq *xcq,
+			   struct xsc_create_cq_mbox_in *in,
+			   int insize);
+int xsc_core_eth_destroy_cq(struct xsc_core_device *xdev,
+			    struct xsc_core_cq *xcq);
+
+struct xsc_eq *xsc_core_eq_get(struct xsc_core_device *xdev, int i);
+int xsc_core_vector2eqn(struct xsc_core_device *xdev, int vector, u32 *eqn,
+			unsigned int *irqn);
+
+/*
+ * Link event callback registration. The events are dispatched from the PCI
+ * layer's own workqueue; unregister detaches the callback and waits for one
+ * already in flight, so it must precede freeing what the handler touches.
+ */
+void xsc_irq_event_register(struct xsc_core_device *xdev,
+			    void (*handler)(void *xdev));
+void xsc_irq_event_unregister(struct xsc_core_device *xdev);
+void xsc_core_fill_page_frag_array(struct xsc_frag_buf *buf,
+				   __be64 *pas, unsigned int npages);
+int xsc_core_frag_buf_alloc_node(struct xsc_core_device *xdev,
+				 unsigned long size,
+				 struct xsc_frag_buf *buf,
+				 int node);
+void xsc_core_frag_buf_free(struct xsc_core_device *xdev,
+			    struct xsc_frag_buf *buf);
+
+u8 xsc_core_query_vport_state(struct xsc_core_device *xdev, u16 vport);
+
+static inline void *xsc_buf_offset(struct xsc_buf *buf, unsigned long offset)
+{
+	if (likely(buf->nbufs == 1))
+		return buf->direct.buf + offset;
+	else
+		return buf->page_list[offset >> PAGE_SHIFT].buf +
+			(offset & (PAGE_SIZE - 1));
+}
+
+static inline bool xsc_fw_is_available(struct xsc_core_device *xdev)
+{
+	return xdev->cmd.cmd_status == XSC_CMD_STATUS_NORMAL;
+}
+
+static inline u8 get_cqe_opcode(struct xsc_cqe *cqe)
+{
+	return FIELD_GET(XSC_CQE_MSG_OPCD_MASK, le32_to_cpu(cqe->data0));
+}
+
+static inline bool xsc_core_is_pf(const struct xsc_core_device *dev)
+{
+	return dev->coredev_type == XSC_COREDEV_PF;
+}
+
+static inline bool is_support_hw_pf_stats(struct xsc_core_device *dev)
+{
+	return xsc_core_is_pf(dev);
+}
+
+static inline bool is_support_cqe64(struct xsc_core_device *dev)
+{
+	return dev->caps.hw_feature_flag & XSC_HW_CQE64_SUPPORT;
+}
+
+static inline bool is_support_rdma_cm(struct xsc_core_device *dev)
+{
+	return dev->caps.hw_feature_flag & XSC_HW_RDMA_CM_SUPPORT;
+}
+
+static inline bool is_support_rdma_atomic(struct xsc_core_device *dev)
+{
+	return dev->caps.hw_feature_flag & XSC_HW_RDMA_ATOMIC_SUPPORT;
+}
+
+static inline bool is_support_rdma(struct xsc_core_device *dev)
+{
+	return dev->caps.hw_feature_flag & XSC_HW_RDMA_SUPPORT;
+}
+
+static inline bool is_support_8k_mtu(struct xsc_core_device *dev)
+{
+	return dev->caps.hw_feature_flag & XSC_HW_MTU_8192_SUPPORT;
+}
+
+static inline bool is_pf_bar_compressed(struct xsc_core_device *dev)
+{
+	return pci_resource_len(dev->pdev, 0) != XSC_ORIGIN_PF_BAR_SIZE;
+}
+
+static inline bool is_support_multi_doorbell(struct xsc_core_device *dev)
+{
+	return dev->caps.hw_feature_flag & XSC_HW_MULTIDOORBELL_SUPPORT;
+}
+
+void xsc_remove_rdma_driver(void);
+int xsc_counters_init(struct ib_device *ib_dev, struct xsc_core_device *xdev);
+void xsc_counters_fini(struct ib_device *ib_dev, struct xsc_core_device *xdev);
+void xsc_ib_sysfs_init(struct ib_device *ib_dev, struct xsc_core_device *xdev);
+void xsc_ib_sysfs_fini(struct ib_device *ib_dev, struct xsc_core_device *xdev);
+
+void xsc_init_hal(struct xsc_core_device *xdev, u32 device_id);
+
+u8 xsc_get_mr_page_mode(struct xsc_core_device *xdev, u8 page_shift);
+u64 xsc_get_max_mr_size(struct xsc_core_device *xdev);
+void xsc_set_data_seg(struct xsc_core_device *xdev, void *data_seg, u64 addr,
+		      u32 key, u32 length);
+u64 xsc_get_rdma_stat_mask(struct xsc_core_device *xdev, u16 ver);
+u32 xsc_get_max_qp_depth(struct xsc_core_device *xdev);
+bool xsc_check_max_qp_depth(struct xsc_core_device *xdev, u32 *wqe_cnt,
+			    u32 max_qp_depth);
+void xsc_set_wqe_id(struct xsc_core_device *xdev, void *cseg, u32 wqe_id);
+int xsc_get_cqe_wqe_id(struct xsc_core_device *xdev, void *cqe);
+void xsc_get_multidb_info(struct xsc_core_device *xdev, u32 *num, u32 *base);
+u8 xsc_get_cqe_opcode(struct xsc_core_device *xdev, void *cqe);
+u32 xsc_get_recv_ds_num(struct xsc_core_device *xdev);
+int xsc_get_link_speed(struct xsc_core_device *xdev);
+void xsc_set_mtt_info(struct xsc_core_device *xdev);
+void xsc_set_pf_db_addr(struct xsc_core_device *xdev,
+			u64 tx_db, u64 rx_db, u64 cq_db, u64 cq_reg, u64 eq_db);
+void xsc_get_db_addr(struct xsc_core_device *xdev,
+		     u64 *tx_db, u64 *rx_db, u64 *cq_db, u64 *cq_reg, u64 *eq_db);
+
+void xsc_update_tx_db(struct xsc_core_device *xdev, u32 sqn, u32 next_pid);
+void xsc_update_rx_db(struct xsc_core_device *xdev, u32 rqn, u32 next_pid);
+void xsc_arm_cq(struct xsc_core_device *xdev, u32 cqn, u32 next_cid, u8 solicited);
+void xsc_update_cq_ci(struct xsc_core_device *xdev, u32 cqn, u32 next_cid);
+
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/common/xsc_device.h b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_device.h
new file mode 100644
index 000000000..302534839
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_device.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_DEVICE_H
+#define __XSC_DEVICE_H
+
+#include <linux/types.h>
+
+/* QP type */
+enum {
+	XSC_QUEUE_TYPE_RDMA_RC		= 0,
+	XSC_QUEUE_TYPE_RDMA_MAD		= 1,
+	XSC_QUEUE_TYPE_RAW		= 2,
+	XSC_QUEUE_TYPE_VIRTIO_NET	= 3,
+	XSC_QUEUE_TYPE_VIRTIO_BLK	= 4,
+	XSC_QUEUE_TYPE_RAW_TPE		= 5,
+	XSC_QUEUE_TYPE_RAW_TSO		= 6,
+	XSC_QUEUE_TYPE_RAW_TX		= 7,
+	XSC_QUEUE_TYPE_SNIFFER		= 8,
+	XSC_QUEUE_TYPE_WORC		= 9,
+	XSC_QUEUE_TYPE_INVALID		= 0xFF,
+};
+
+enum {
+	XSC_PERM_LOCAL_READ	= 1 << 0,
+	XSC_PERM_LOCAL_WRITE	= 1 << 1,
+	XSC_PERM_REMOTE_READ	= 1 << 2,
+	XSC_PERM_REMOTE_WRITE	= 1 << 3,
+	XSC_PERM_ATOMIC		= 1 << 4,
+	XSC_PERM_UMR_EN		= 1 << 7,
+};
+
+enum {
+	XSC_DEV_CAP_FLAG_RC		= 1LL <<  0,
+	XSC_DEV_CAP_FLAG_UC		= 1LL <<  1,
+	XSC_DEV_CAP_FLAG_UD		= 1LL <<  2,
+	XSC_DEV_CAP_FLAG_XRC		= 1LL <<  3,
+	XSC_DEV_CAP_FLAG_SRQ		= 1LL <<  6,
+	XSC_DEV_CAP_FLAG_BAD_PKEY_CNTR	= 1LL <<  8,
+	XSC_DEV_CAP_FLAG_BAD_QKEY_CNTR	= 1LL <<  9,
+	XSC_DEV_CAP_FLAG_APM		= 1LL << 17,
+	XSC_DEV_CAP_FLAG_ATOMIC		= 1LL << 18,
+	XSC_DEV_CAP_FLAG_ON_DMND_PG	= 1LL << 24,
+	XSC_DEV_CAP_FLAG_RESIZE_SRQ	= 1LL << 32,
+	XSC_DEV_CAP_FLAG_REMOTE_FENCE	= 1LL << 38,
+	XSC_DEV_CAP_FLAG_TLP_HINTS	= 1LL << 39,
+	XSC_DEV_CAP_FLAG_SIG_HAND_OVER	= 1LL << 40,
+	XSC_DEV_CAP_FLAG_DCT		= 1LL << 41,
+	XSC_DEV_CAP_FLAG_CMDIF_CSUM	= 1LL << 46,
+};
+
+enum xsc_traffic_types {
+	XSC_TT_IPV4,
+	XSC_TT_IPV4_TCP,
+	XSC_TT_IPV4_UDP,
+	XSC_TT_IPV6,
+	XSC_TT_IPV6_TCP,
+	XSC_TT_IPV6_UDP,
+	XSC_TT_IPV4_IPSEC_AH,
+	XSC_TT_IPV6_IPSEC_AH,
+	XSC_TT_IPV4_IPSEC_ESP,
+	XSC_TT_IPV6_IPSEC_ESP,
+	XSC_TT_ANY,
+	XSC_NUM_TT,
+};
+
+#define XSC_NUM_INDIR_TIRS XSC_NUM_TT
+
+enum {
+	XSC_L3_PROT_TYPE_IPV4	= BIT(0),
+	XSC_L3_PROT_TYPE_IPV6	= BIT(1),
+};
+
+enum {
+	XSC_L4_PROT_TYPE_TCP	= BIT(0),
+	XSC_L4_PROT_TYPE_UDP	= BIT(1),
+};
+
+struct xsc_tirc_config {
+	u8 l3_prot_type;
+	u8 l4_prot_type;
+	u32 rx_hash_fields;
+};
+
+enum {
+	XSC_HASH_FUNC_XOR		= 0,
+	XSC_HASH_FUNC_TOP		= 1,
+	XSC_HASH_FUNC_TOP_SYM	= 2,
+	XSC_HASH_FUNC_RSV		= 3,
+};
+
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/common/xsc_hsi.h b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_hsi.h
new file mode 100644
index 000000000..aa2049f89
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_hsi.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef XSC_HSI_H
+#define XSC_HSI_H
+
+#include <asm/byteorder.h>
+
+#include <linux/types.h>
+#include <linux/bitops.h>
+
+#define WR_LE_32(x, val)	(x = __cpu_to_le32(val))
+#define WR_LE_64(x, val)	(x = __cpu_to_le64(val))
+
+#define WR_BE_32(x, val)	(x = __cpu_to_be32(val))
+
+#define RD_LE_32(x)		__le32_to_cpu(x)
+#define RD_BE_32(x)		__be32_to_cpu(x)
+
+struct xsc_msg_opcode {
+	u32	send;
+	u32	send_with_imm;
+	u32	rdma_write;
+	u32	rdma_write_with_imm;
+	u32	rdma_read;
+	u32	atomic_cmp_and_swp;
+	u32	atomic_fetch_and_add;
+	u32	masked_atomic_cmp_and_swp;
+	u32	masked_atomic_fetch_and_add;
+	u32	mad;
+};
+
+/* sw cqe opcode */
+enum {
+	XSC_OPCODE_RDMA_REQ_SEND	= 0,
+	XSC_OPCODE_RDMA_REQ_SEND_IMMDT	= 1,
+	XSC_OPCODE_RDMA_RSP_RECV	= 2,
+	XSC_OPCODE_RDMA_RSP_RECV_IMMDT	= 3,
+	XSC_OPCODE_RDMA_REQ_WRITE	= 4,
+	XSC_OPCODE_RDMA_REQ_WRITE_IMMDT	= 5,
+	XSC_OPCODE_RDMA_RSP_WRITE_IMMDT	= 6,
+	XSC_OPCODE_RDMA_REQ_READ	= 7,
+	XSC_OPCODE_RDMA_REQ_ERROR	= 8,
+	XSC_OPCODE_RDMA_RSP_ERROR	= 9,
+	XSC_OPCODE_RDMA_CQE_ERROR	= 10,
+	XSC_OPCODE_RDMA_MAD_REQ_SEND    = 11,
+	XSC_OPCODE_RDMA_MAD_RSP_RECV    = 12,
+	XSC_OPCODE_RDMA_CQE_RAW_SNF     = 13,
+};
+
+enum {
+	XSC_SEND_SEG_MAX		= 32,
+	XSC_BASE_WQE_SHIFT		= 4,
+	XSC_SEND_SEG_NUM		= 4,
+	XSC_SEND_WQE_SHIFT		= 6,
+	XSC_CTRL_SEG_NUM		= 1,
+	XSC_RADDR_SEG_NUM		= 1,
+};
+
+enum {
+	XSC_RECV_SEG_MAX		= 4,
+	XSC_RECV_SEG_NUM		= 1,
+	XSC_RECV_WQE_SHIFT		= 4,
+};
+
+enum {
+	XSC_INLINE_SIZE_MAX		= 15,
+};
+
+#define XSC_MPT_MAP_EN		0
+
+enum {
+	XSC_REQ		= 0,
+	XSC_RSP		= 1,
+};
+
+enum {
+	XSC_WITHOUT_IMMDT	= 0,
+	XSC_WITH_IMMDT		= 1,
+};
+
+/* CQE error codes */
+enum {
+	XSC_ERR_CODE_NAK_RETRY			= 0x40,
+	XSC_ERR_CODE_NAK_OPCODE			= 0x41,
+	XSC_ERR_CODE_NAK_MR			= 0x42,
+	XSC_ERR_CODE_NAK_OPERATION		= 0x43,
+	XSC_ERR_CODE_NAK_RNR			= 0x44,
+	XSC_ERR_CODE_LOCAL_MR			= 0x45,
+	XSC_ERR_CODE_LOCAL_LEN			= 0x46,
+	XSC_ERR_CODE_LOCAL_OPCODE		= 0x47,
+	XSC_ERR_CODE_CQ_OVER_FLOW		= 0x48,
+	XSC_ERR_CODE_STRG_ACC_GEN_CQE		= 0x4c,
+	XSC_ERR_CODE_CQE_ACC			= 0x4d,
+	XSC_ERR_CODE_FLUSH			= 0x4e,
+	XSC_ERR_CODE_MALF_WQE_HOST		= 0x50,
+	XSC_ERR_CODE_MALF_WQE_INFO		= 0x51,
+	XSC_ERR_CODE_MR_NON_NAK			= 0x52,
+	XSC_ERR_CODE_OPCODE_GEN_CQE		= 0x61,
+	XSC_ERR_CODE_MANY_READ			= 0x62,
+	XSC_ERR_CODE_LEN_GEN_CQE		= 0x63,
+	XSC_ERR_CODE_MR				= 0x65,
+	XSC_ERR_CODE_MR_GEN_CQE			= 0x66,
+	XSC_ERR_CODE_OPERATION			= 0x67,
+	XSC_ERR_CODE_MALF_WQE_INFO_GEN_NAK	= 0x68,
+};
+
+enum xsc_qp_state {
+	XSC_QP_STATE_RST		= 0,
+	XSC_QP_STATE_INIT		= 1,
+	XSC_QP_STATE_RTR		= 2,
+	XSC_QP_STATE_RTS		= 3,
+	XSC_QP_STATE_SQER		= 4,
+	XSC_QP_STATE_SQD		= 5,
+	XSC_QP_STATE_ERR		= 6,
+	XSC_QP_STATE_SQ_DRAINING	= 7,
+	XSC_QP_STATE_SUSPENDED		= 9,
+	XSC_QP_NUM_STATE
+};
+
+#endif /* XSC_HSI_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/common/xsc_reg.h b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_reg.h
new file mode 100644
index 000000000..babdb5802
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/common/xsc_reg.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef XSC_REG_H
+#define XSC_REG_H
+#define CMDQM_HOST_REQ_BUF_BASE_H_ADDR_MEM_ADDR 0x0
+#define CMDQM_HOST_REQ_BUF_BASE_L_ADDR_MEM_ADDR 0x4
+#define CMDQM_HOST_REQ_PID_MEM_ADDR 0x8
+#define CMDQM_HOST_REQ_CID_MEM_ADDR 0xc
+#define CMDQM_HOST_RSP_BUF_BASE_H_ADDR_MEM_ADDR 0x10
+#define CMDQM_HOST_RSP_BUF_BASE_L_ADDR_MEM_ADDR 0x14
+#define CMDQM_HOST_RSP_PID_MEM_ADDR 0x18
+#define CMDQM_HOST_RSP_CID_MEM_ADDR 0x1c
+#define CMDQM_HOST_VF_ERR_STS_MEM_ADDR 0x20
+#define CMDQM_VECTOR_ID_MEM_ADDR 0x24
+
+#define DB_CQ_FUNC_MEM_ADDR 0x8c4
+#define DB_EQ_FUNC_MEM_ADDR 0x8c8
+#define DB_CQ_CID_DIRECT_MEM_ADDR 0x8cc
+#define TX_DB_FUNC_MEM_ADDR 0x8d0
+#define RX_DB_FUNC_MEM_ADDR 0x8d4
+
+#define MDB_NUM	64
+#define QPM_TX_MDB_BASE_REG_ADDR 0xd00
+
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Kconfig b/drivers/net/ethernet/yunsilicon/xsc/pci/Kconfig
new file mode 100644
index 000000000..1cc3f7b49
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+# All rights reserved.
+# Yunsilicon PCI configuration
+#
+
+config YUNSILICON_XSC_PCI
+	tristate "Yunsilicon XSC PCI driver"
+	depends on PCI
+	help
+	  This driver is common for Yunsilicon XSC
+	  ethernet and RDMA drivers.
+
+	  To compile this driver as a module, choose M here. The module
+	  will be called xsc_pci.
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
new file mode 100644
index 000000000..c163aa950
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+# All rights reserved.
+
+ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
+
+obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
+
+xsc_pci-y := main.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/main.c b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
new file mode 100644
index 000000000..734d48825
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "common/xsc_core.h"
+
+static const struct pci_device_id xsc_pci_id_table[] = {
+	{ PCI_DEVICE(XSC_PCI_VENDOR_ID, XSC_MC_PF_DEV_ID) },
+	{ PCI_DEVICE(XSC_PCI_VENDOR_ID, XSC_MF_HOST_PF_DEV_ID) },
+	{ PCI_DEVICE(XSC_PCI_VENDOR_ID, XSC_MF_SOC_PF_DEV_ID) },
+	{ PCI_DEVICE(XSC_PCI_VENDOR_ID, XSC_MS_PF_DEV_ID) },
+	{ PCI_DEVICE(XSC_PCI_VENDOR_ID, XSC_MV_HOST_PF_DEV_ID) },
+	{ PCI_DEVICE(XSC_PCI_VENDOR_ID, XSC_MV_SOC_PF_DEV_ID) },
+	{ 0 }
+};
+
+static int xsc_set_dma_caps(struct pci_dev *pdev)
+{
+	int err;
+
+	err = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64));
+	if (!err)
+		dma_set_max_seg_size(&pdev->dev, SZ_2G);
+
+	return err;
+}
+
+static int xsc_pci_init(struct xsc_core_device *xdev,
+			const struct pci_device_id *id)
+{
+	struct pci_dev *pdev = xdev->pdev;
+	void __iomem *bar_base;
+	int bar_num = 0;
+	int err;
+
+	xdev->numa_node = dev_to_node(&pdev->dev);
+
+	err = pci_enable_device(pdev);
+	if (err) {
+		pci_err(pdev, "failed to enable PCI device: err=%d\n", err);
+		goto err_out;
+	}
+
+	err = pci_request_region(pdev, bar_num, KBUILD_MODNAME);
+	if (err) {
+		pci_err(pdev, "failed to request %s pci_region=%d: err=%d\n",
+			KBUILD_MODNAME, bar_num, err);
+		goto err_disable;
+	}
+
+	pci_set_master(pdev);
+
+	err = xsc_set_dma_caps(pdev);
+	if (err) {
+		pci_err(pdev, "failed to set DMA capabilities mask: err=%d\n",
+			err);
+		goto err_clr_master;
+	}
+
+	bar_base = pci_ioremap_bar(pdev, bar_num);
+	if (!bar_base) {
+		pci_err(pdev, "failed to ioremap %s bar%d\n", KBUILD_MODNAME,
+			bar_num);
+		err = -ENOMEM;
+		goto err_clr_master;
+	}
+
+	err = pci_save_state(pdev);
+	if (err) {
+		pci_err(pdev, "pci_save_state failed: err=%d\n", err);
+		goto err_io_unmap;
+	}
+
+	xdev->bar_num = bar_num;
+	xdev->bar = bar_base;
+
+	return 0;
+
+err_io_unmap:
+	pci_iounmap(pdev, bar_base);
+err_clr_master:
+	pci_clear_master(pdev);
+	pci_release_region(pdev, bar_num);
+err_disable:
+	pci_disable_device(pdev);
+err_out:
+	return err;
+}
+
+static void xsc_pci_fini(struct xsc_core_device *xdev)
+{
+	struct pci_dev *pdev = xdev->pdev;
+
+	pci_iounmap(pdev, xdev->bar);
+	pci_clear_master(pdev);
+	pci_release_region(pdev, xdev->bar_num);
+	pci_disable_device(pdev);
+}
+
+static int xsc_dev_res_init(struct xsc_core_device *xdev)
+{
+	struct xsc_dev_resource *dev_res;
+
+	dev_res = kvzalloc_obj(*dev_res, GFP_KERNEL);
+	if (!dev_res)
+		return -ENOMEM;
+
+	xdev->dev_res = dev_res;
+	mutex_init(&dev_res->alloc_mutex);
+	spin_lock_init(&dev_res->mkey_lock);
+
+	return 0;
+}
+
+static void xsc_dev_res_cleanup(struct xsc_core_device *xdev)
+{
+	kfree(xdev->dev_res);
+}
+
+static int xsc_core_dev_init(struct xsc_core_device *xdev)
+{
+	int err;
+
+	err = xsc_dev_res_init(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "xsc dev res init failed %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static void xsc_core_dev_cleanup(struct xsc_core_device *xdev)
+{
+	xsc_dev_res_cleanup(xdev);
+}
+
+static int xsc_hw_setup(struct xsc_core_device *xdev)
+{
+	return 0;
+}
+
+static void xsc_hw_cleanup(struct xsc_core_device *xdev)
+{
+}
+
+static int xsc_load(struct xsc_core_device *xdev)
+{
+	int err = 0;
+
+	err = xsc_hw_setup(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "xsc_hw_setup failed %d\n", err);
+		goto err_out;
+	}
+
+	return 0;
+err_out:
+	return err;
+}
+
+static void xsc_unload(struct xsc_core_device *xdev)
+{
+	xsc_hw_cleanup(xdev);
+}
+
+static int xsc_pci_probe(struct pci_dev *pci_dev,
+			 const struct pci_device_id *id)
+{
+	struct xsc_core_device *xdev;
+	int err;
+
+	xdev = kzalloc_obj(*xdev, GFP_KERNEL);
+	if (!xdev)
+		return -ENOMEM;
+
+	xdev->pdev = pci_dev;
+	xdev->device = &pci_dev->dev;
+	xdev->coredev_type = pci_dev->is_virtfn ? XSC_COREDEV_VF : XSC_COREDEV_PF;
+
+	pci_set_drvdata(pci_dev, xdev);
+	err = xsc_pci_init(xdev, id);
+	if (err) {
+		pci_err(pci_dev, "xsc_pci_init failed %d\n", err);
+		goto err_unset_pci_drvdata;
+	}
+
+	err = xsc_core_dev_init(xdev);
+	if (err) {
+		pci_err(pci_dev, "xsc_core_dev_init failed %d\n", err);
+		goto err_pci_fini;
+	}
+
+	err = xsc_load(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "xsc_load failed %d\n", err);
+		goto err_core_dev_cleanup;
+	}
+
+	return 0;
+err_core_dev_cleanup:
+	xsc_core_dev_cleanup(xdev);
+err_pci_fini:
+	xsc_pci_fini(xdev);
+err_unset_pci_drvdata:
+	pci_set_drvdata(pci_dev, NULL);
+	kfree(xdev);
+
+	return err;
+}
+
+static void xsc_pci_remove(struct pci_dev *pci_dev)
+{
+	struct xsc_core_device *xdev = pci_get_drvdata(pci_dev);
+
+	xsc_unload(xdev);
+	xsc_core_dev_cleanup(xdev);
+	xsc_pci_fini(xdev);
+	pci_set_drvdata(pci_dev, NULL);
+	kfree(xdev);
+}
+
+static struct pci_driver xsc_pci_driver = {
+	.name		= "xsc-pci",
+	.id_table	= xsc_pci_id_table,
+	.probe		= xsc_pci_probe,
+	.remove		= xsc_pci_remove,
+};
+
+static int __init xsc_init(void)
+{
+	int err;
+
+	err = pci_register_driver(&xsc_pci_driver);
+	if (err) {
+		pr_err("failed to register pci driver\n");
+		return err;
+	}
+
+	return 0;
+}
+
+static void __exit xsc_fini(void)
+{
+	pci_unregister_driver(&xsc_pci_driver);
+}
+
+module_init(xsc_init);
+module_exit(xsc_fini);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Yunsilicon XSC PCI driver");

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

* [PATCH 02/15] yunsilicon: add the register HAL
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
  2026-10-12 11:43 ` [PATCH 01/15] yunsilicon: add PCI core driver skeleton Xin Tian
@ 2026-10-12 11:43 ` Xin Tian
  2026-10-12 15:21   ` Andrew Lunn
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:43 ` [PATCH 03/15] yunsilicon: add the command queue Xin Tian
                   ` (13 subsequent siblings)
  15 siblings, 2 replies; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:43 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the register access layer and the per-device register map: doorbell
and interrupt registers, the command queue pointers and the device
capability queries that the upper layers call.  The register map is
described by xsc_hal.h and implemented for the Andes device.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 .../net/ethernet/yunsilicon/xsc/pci/Makefile  |   2 +-
 .../yunsilicon/xsc/pci/hal/andes_impl.c       | 708 ++++++++++++++++++
 .../yunsilicon/xsc/pci/hal/andes_reg.h        |  43 ++
 .../ethernet/yunsilicon/xsc/pci/hal/xsc_hal.h |  85 +++
 .../net/ethernet/yunsilicon/xsc/pci/main.c    |   3 +
 5 files changed, 840 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_reg.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hal/xsc_hal.h

diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
index c163aa950..81b65232e 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
@@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
 
-xsc_pci-y := main.o
+xsc_pci-y := main.o hal/andes_impl.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c b/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c
new file mode 100644
index 000000000..46fc045f9
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c
@@ -0,0 +1,708 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2024 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include "andes_reg.h"
+#include "common/xsc_core.h"
+#include "common/xsc_cmd.h"
+#include "common/xsc_hsi.h"
+#include "common/xsc_reg.h"
+#include "xsc_hal.h"
+
+/* message opcode */
+enum {
+	XSC_MSG_OPCODE_SEND		= 0,
+	XSC_MSG_OPCODE_RDMA_WRITE	= 1,
+	XSC_MSG_OPCODE_RDMA_READ	= 2,
+	XSC_MSG_OPCODE_MAD		= 3,
+	XSC_MSG_OPCODE_RDMA_ACK		= 4,
+	XSC_MSG_OPCODE_RDMA_ACK_READ	= 5,
+	XSC_MSG_OPCODE_RDMA_CNP		= 6,
+	XSC_MSG_OPCODE_RAW		= 7,
+	XSC_MSG_OPCODE_VIRTIO_NET	= 8,
+	XSC_MSG_OPCODE_VIRTIO_BLK	= 9,
+	XSC_MSG_OPCODE_RAW_TPE		= 10,
+	XSC_MSG_OPCODE_INIT_QP_REQ	= 11,
+	XSC_MSG_OPCODE_INIT_QP_RSP	= 12,
+	XSC_MSG_OPCODE_INIT_PATH_REQ	= 13,
+	XSC_MSG_OPCODE_INIT_PATH_RSP	= 14,
+
+	XSC_MSG_OPCODE_UNSUPPORT	= -1,
+};
+
+#define REG_ADDR(bp, offset)	((bp) + (offset))
+
+static void xsc_read32(void __iomem *bar, u32 off, void *data, int len)
+{
+	u32 val = readl(REG_ADDR(bar, off));
+
+	memcpy(data, &val, len);
+}
+
+static void xsc_write32(void __iomem *bar, u32 off, void *data)
+{
+	writel(*(u32 *)data, REG_ADDR(bar, off));
+}
+
+static void andes_ring_tx_doorbell(struct xsc_core_device *xdev,
+				   struct xsc_hw_reg *regs,
+				   void __iomem *bar, u32 sqn, u32 next_pid,
+				   bool mdb)
+{
+	union xsc_send_doorbell {
+		struct{
+			u32  next_pid : 16;
+			u32 qp_id : 15;
+		};
+		u32 raw;
+	} db;
+	u32 db_reg = mdb ? QPM_TX_MDB_BASE_REG_ADDR + (sqn & (MDB_NUM - 1)) * sizeof(u32) :
+		regs->tx_db;
+
+	db.next_pid = next_pid;
+	db.qp_id = sqn;
+
+	/* Make sure that descriptors are written before
+	 * updating doorbell record and ringing the doorbell
+	 */
+	wmb();
+
+	writel(db.raw, REG_ADDR(bar, db_reg));
+}
+
+static void andes_ring_rx_doorbell(struct xsc_hw_reg *regs,
+				   void __iomem *bar, u32 rqn, u32 next_pid)
+{
+	union xsc_recv_doorbell {
+		struct{
+			u32  next_pid : 13;
+			u32 qp_id : 15;
+		};
+		u32 raw;
+	} db;
+
+	db.next_pid = next_pid;
+	db.qp_id = rqn;
+
+	/* Make sure that descriptors are written before
+	 * updating doorbell record and ringing the doorbell
+	 */
+	wmb();
+
+	writel(db.raw, REG_ADDR(bar, regs->rx_db));
+}
+
+static void andes_update_cq_db(struct xsc_hw_reg *regs,
+			       void __iomem *bar, u32 cqn, u32 next_cid,
+			       u8 solicited)
+{
+	union andes_cq_doorbell {
+		struct{
+			u32     cq_next_cid:16;
+			u32     cq_id:15;
+			u32     arm:1;
+		};
+		u32     val;
+	} db;
+
+	db.cq_next_cid = next_cid;
+	db.cq_id = cqn;
+	db.arm = solicited;
+
+	/* Make sure that the doorbell record in host memory is
+	 * written before ringing the doorbell via PCI MMIO.
+	 */
+	wmb();
+	writel(db.val, REG_ADDR(bar, regs->complete_db));
+}
+
+static void andes_set_cq_ci(struct xsc_hw_reg *regs,
+			    void __iomem *bar, u32 cqn, u32 next_cid)
+{
+	union andes_cq_doorbell {
+		struct{
+			u32     cq_next_cid:16;
+			u32     cq_id:15;
+			u32     arm:1;
+		};
+		u32     val;
+	} db;
+
+	db.val = 0;
+	db.cq_next_cid = next_cid;
+	db.cq_id = cqn;
+	/* make sure val write to memory done */
+	wmb();
+	writel(db.val, REG_ADDR(bar, regs->complete_reg));
+}
+
+static void andes_set_eq_ci(struct xsc_hw_reg *regs,
+			    void __iomem *bar, u32 eqn, u32 next_cid,
+			    u8 arm)
+{
+	union andes_eq_doorbell {
+		struct{
+			u32 eq_next_cid : 11;
+			u32 eq_id : 11;
+			u32 arm : 1;
+		};
+		u32 val;
+	} db;
+
+	db.eq_next_cid = next_cid;
+	db.eq_id = eqn;
+	db.arm = !!arm;
+
+	/* make sure val write to memory done */
+	wmb();
+	writel(db.val, REG_ADDR(bar, regs->event_db));
+}
+
+static u8 andes_get_mr_page_mode(u8 page_shift)
+{
+enum {
+	XSC_PAGE_SHIFT_4K	= 12,
+	XSC_PAGE_SHIFT_64K	= 16,
+	XSC_PAGE_SHIFT_2M	= 21,
+	XSC_PAGE_SHIFT_1G	= 30,
+};
+
+enum {
+	XSC_PAGE_MODE_4K	= 0,
+	XSC_PAGE_MODE_64K	= 1,
+	XSC_PAGE_MODE_2M	= 2,
+	XSC_PAGE_MODE_1G	= 3,
+};
+
+	return (page_shift == XSC_PAGE_SHIFT_4K ? XSC_PAGE_MODE_4K :
+		(page_shift == XSC_PAGE_SHIFT_64K ? XSC_PAGE_MODE_64K :
+		(page_shift == XSC_PAGE_SHIFT_2M ? XSC_PAGE_MODE_2M : XSC_PAGE_MODE_1G)));
+}
+
+static inline u32 andes_mkey_to_idx(u32 mkey)
+{
+	return mkey >> 17;
+}
+
+static inline u32 andes_idx_to_mkey(u32 mkey_idx)
+{
+	return mkey_idx << 17;
+}
+
+static const u32 xsc_msg_opcode[][2][2] = {
+	[XSC_MSG_OPCODE_SEND][XSC_REQ][XSC_WITHOUT_IMMDT] = XSC_OPCODE_RDMA_REQ_SEND,
+	[XSC_MSG_OPCODE_SEND][XSC_REQ][XSC_WITH_IMMDT] = XSC_OPCODE_RDMA_REQ_SEND_IMMDT,
+	[XSC_MSG_OPCODE_SEND][XSC_RSP][XSC_WITHOUT_IMMDT] = XSC_OPCODE_RDMA_RSP_RECV,
+	[XSC_MSG_OPCODE_SEND][XSC_RSP][XSC_WITH_IMMDT] = XSC_OPCODE_RDMA_RSP_RECV_IMMDT,
+	[XSC_MSG_OPCODE_RDMA_WRITE][XSC_REQ][XSC_WITHOUT_IMMDT] = XSC_OPCODE_RDMA_REQ_WRITE,
+	[XSC_MSG_OPCODE_RDMA_WRITE][XSC_REQ][XSC_WITH_IMMDT] = XSC_OPCODE_RDMA_REQ_WRITE_IMMDT,
+	[XSC_MSG_OPCODE_RDMA_WRITE][XSC_RSP][XSC_WITHOUT_IMMDT] = XSC_OPCODE_RDMA_CQE_ERROR,
+	[XSC_MSG_OPCODE_RDMA_WRITE][XSC_RSP][XSC_WITH_IMMDT] = XSC_OPCODE_RDMA_RSP_WRITE_IMMDT,
+	[XSC_MSG_OPCODE_RDMA_READ][XSC_REQ][XSC_WITHOUT_IMMDT] = XSC_OPCODE_RDMA_REQ_READ,
+	[XSC_MSG_OPCODE_RDMA_READ][XSC_REQ][XSC_WITH_IMMDT] = XSC_OPCODE_RDMA_CQE_ERROR,
+	[XSC_MSG_OPCODE_RDMA_READ][XSC_RSP][XSC_WITHOUT_IMMDT] = XSC_OPCODE_RDMA_CQE_ERROR,
+	[XSC_MSG_OPCODE_RDMA_READ][XSC_RSP][XSC_WITH_IMMDT] = XSC_OPCODE_RDMA_CQE_ERROR,
+	[XSC_MSG_OPCODE_MAD][XSC_REQ][XSC_WITHOUT_IMMDT] = XSC_OPCODE_RDMA_MAD_REQ_SEND,
+	[XSC_MSG_OPCODE_MAD][XSC_RSP][XSC_WITHOUT_IMMDT] = XSC_OPCODE_RDMA_MAD_RSP_RECV,
+};
+
+struct andes_cqe {
+	union {
+		u8		msg_opcode;
+		struct {
+			u8		error_code:7;
+			u8		is_error:1;
+		};
+	};
+	__le32		qp_id:15;
+	u8		rsv1:1;
+	u8		se:1;
+	u8		has_pph:1;
+	u8		type:1;
+	u8		with_immdt:1;
+	u8		csum_err:4;
+	__le32		imm_data;
+	__le32		msg_len;
+	__le32		vni;
+	__le64		ts:48;
+	__le16		wqe_id;
+	__le16		rsv[3];
+	__le16		rsv2:15;
+	u8		owner:1;
+};
+
+static int andes_get_cqe_wqe_id(void *cqe)
+{
+	return ((struct andes_cqe *)cqe)->wqe_id;
+}
+
+static u8 andes_get_cqe_opcode(void *cqe)
+{
+	struct andes_cqe *_cqe = cqe;
+	u8 msg_opcode = _cqe->msg_opcode;
+
+	if (_cqe->is_error)
+		return _cqe->type ? XSC_OPCODE_RDMA_RSP_ERROR : XSC_OPCODE_RDMA_REQ_ERROR;
+	if (msg_opcode > XSC_MSG_OPCODE_MAD)
+		return XSC_OPCODE_RDMA_CQE_ERROR;
+	return xsc_msg_opcode[msg_opcode][_cqe->type][_cqe->with_immdt];
+}
+
+struct andes_data_seg {
+	u32	in_line:1;
+	u32	length:31;
+	u32	key;
+	u64	addr;
+};
+
+static void andes_set_data_seg(void *data_seg, u32 length, u32 key, u64 addr)
+{
+	struct andes_data_seg *seg = data_seg;
+
+	seg->length = length;
+	seg->key = key;
+	seg->addr = addr;
+}
+
+struct andes_send_wqe_ctrl_seg {
+	__le32		msg_opcode:8;
+	__le32		with_immdt:1;
+	__le32		csum_en:2;
+	__le32		ds_data_num:5;
+	__le32		wqe_id:16;
+	__le32		msg_len;
+	union {
+		__le32		opcode_data;
+		struct {
+			u8		has_pph:1;
+			u8		so_type:1;
+			__le16		so_data_size:14;
+			u8:8;
+			u8		so_hdr_len:8;
+		};
+		struct {
+			__le16		desc_id;
+			__le16		is_last_wqe:1;
+			__le16		dst_qp_id:15;
+		};
+	};
+	__le32		se:1;
+	__le32		ce:1;
+	__le32:30;
+};
+
+static void andes_set_wqe_id(void *cseg, u32 wqe_id)
+{
+	u16 _wqe_id = wqe_id;
+
+	((struct andes_send_wqe_ctrl_seg *)cseg)->wqe_id = cpu_to_le16(_wqe_id);
+}
+
+static struct xsc_msg_opcode xsc_ib_opcode = {
+	.send = XSC_MSG_OPCODE_SEND,
+	.send_with_imm = XSC_MSG_OPCODE_SEND,
+	.rdma_write = XSC_MSG_OPCODE_RDMA_WRITE,
+	.rdma_write_with_imm = XSC_MSG_OPCODE_RDMA_WRITE,
+	.rdma_read = XSC_MSG_OPCODE_RDMA_READ,
+	.atomic_cmp_and_swp = XSC_MSG_OPCODE_UNSUPPORT,
+	.atomic_fetch_and_add = XSC_MSG_OPCODE_UNSUPPORT,
+	.masked_atomic_cmp_and_swp = XSC_MSG_OPCODE_UNSUPPORT,
+	.masked_atomic_fetch_and_add = XSC_MSG_OPCODE_UNSUPPORT,
+	.mad = XSC_MSG_OPCODE_MAD,
+};
+
+static struct xsc_msg_opcode *andes_get_msg_opcode(void)
+{
+	return &xsc_ib_opcode;
+}
+
+static struct xsc_hw_reg andes_pf_regs = {
+	.req_pid_addr = HIF_CMDQM_HOST_REQ_PID_MEM_ADDR - 0xa0000000,
+	.req_cid_addr = HIF_CMDQM_HOST_REQ_CID_MEM_ADDR - 0xa0000000,
+	.rsp_pid_addr = HIF_CMDQM_HOST_RSP_PID_MEM_ADDR - 0xa0000000,
+	.rsp_cid_addr = HIF_CMDQM_HOST_RSP_CID_MEM_ADDR - 0xa0000000,
+	.req_buf_h_addr = HIF_CMDQM_HOST_REQ_BUF_BASE_H_ADDR_MEM_ADDR - 0xa0000000,
+	.req_buf_l_addr = HIF_CMDQM_HOST_REQ_BUF_BASE_L_ADDR_MEM_ADDR - 0xa0000000,
+	.rsp_buf_h_addr = HIF_CMDQM_HOST_RSP_BUF_BASE_H_ADDR_MEM_ADDR - 0xa0000000,
+	.rsp_buf_l_addr = HIF_CMDQM_HOST_RSP_BUF_BASE_L_ADDR_MEM_ADDR - 0xa0000000,
+	.msix_vec_addr = HIF_CMDQM_VECTOR_ID_MEM_ADDR - 0xa0000000,
+	.interrupt_stat_addr = HIF_CMDQM_HOST_VF_ERR_STS_MEM_ADDR - 0xa0000000,
+	.mpt_tbl_addr = MMC_MPT_TBL_MEM_ADDR - 0xa0000000,
+	.mpt_tbl_depth = MMC_MPT_TBL_MEM_DEPTH,
+	.mpt_tbl_width = MMC_MPT_TBL_MEM_WIDTH,
+	.mtt_inst_base_addr = MMC_MTT_TBL_MEM_ADDR - 0xa0000000,
+	.mtt_inst_stride = 0,
+	.mtt_inst_num_log = 0,
+	.mtt_inst_depth = MMC_MTT_TBL_MEM_DEPTH,
+};
+
+/* BAR-relative window shared by bar-compressed PF and VF */
+static struct xsc_hw_reg andes_compressed_regs = {
+	.tx_db = TX_DB_FUNC_MEM_ADDR,
+	.rx_db = RX_DB_FUNC_MEM_ADDR,
+	.complete_db = DB_CQ_FUNC_MEM_ADDR,
+	.complete_reg = DB_CQ_CID_DIRECT_MEM_ADDR,
+	.event_db = DB_EQ_FUNC_MEM_ADDR,
+	.req_pid_addr = CMDQM_HOST_REQ_PID_MEM_ADDR,
+	.req_cid_addr = CMDQM_HOST_REQ_CID_MEM_ADDR,
+	.rsp_pid_addr = CMDQM_HOST_RSP_PID_MEM_ADDR,
+	.rsp_cid_addr = CMDQM_HOST_RSP_CID_MEM_ADDR,
+	.req_buf_h_addr = CMDQM_HOST_REQ_BUF_BASE_H_ADDR_MEM_ADDR,
+	.req_buf_l_addr = CMDQM_HOST_REQ_BUF_BASE_L_ADDR_MEM_ADDR,
+	.rsp_buf_h_addr = CMDQM_HOST_RSP_BUF_BASE_H_ADDR_MEM_ADDR,
+	.rsp_buf_l_addr = CMDQM_HOST_RSP_BUF_BASE_L_ADDR_MEM_ADDR,
+	.msix_vec_addr = CMDQM_VECTOR_ID_MEM_ADDR,
+	.interrupt_stat_addr = CMDQM_HOST_VF_ERR_STS_MEM_ADDR,
+	.mpt_tbl_addr = MMC_MPT_TBL_MEM_ADDR - 0xa0000000,
+	.mpt_tbl_depth = MMC_MPT_TBL_MEM_DEPTH,
+	.mpt_tbl_width = MMC_MPT_TBL_MEM_WIDTH,
+	.mtt_inst_base_addr = MMC_MTT_TBL_MEM_ADDR - 0xa0000000,
+	.mtt_inst_stride = 0,
+	.mtt_inst_num_log = 0,
+	.mtt_inst_depth = MMC_MTT_TBL_MEM_DEPTH,
+};
+
+void xsc_init_hal(struct xsc_core_device *xdev, u32 device_id)
+{
+	switch (device_id) {
+	case XSC_MC_VF_DEV_ID:
+	case XSC_MF_HOST_VF_DEV_ID:
+	case XSC_MS_VF_DEV_ID:
+	case XSC_MV_HOST_VF_DEV_ID:
+		/* VFs expose the same BAR-relative window as a bar-compressed PF */
+		xdev->hal_regs = &andes_compressed_regs;
+		break;
+	default:
+		if (is_pf_bar_compressed(xdev))
+			xdev->hal_regs = &andes_compressed_regs;
+		else
+			xdev->hal_regs = &andes_pf_regs;
+		break;
+	}
+}
+
+void xsc_set_mtt_info(struct xsc_core_device *xdev)
+{
+	struct xsc_hw_reg *regs = xdev->hal_regs;
+
+	regs->mpt_tbl_addr = xdev->caps.mpt_tbl_addr - 0xa0000000;
+	regs->mpt_tbl_depth = xdev->caps.mpt_tbl_depth;
+	regs->mpt_tbl_width = xdev->caps.mpt_tbl_width;
+	regs->mtt_inst_base_addr = xdev->caps.mtt_inst_base_addr - 0xa0000000;
+	regs->mtt_inst_stride = xdev->caps.mtt_inst_stride;
+	regs->mtt_inst_num_log = xdev->caps.mtt_inst_num_log;
+	regs->mtt_inst_depth = xdev->caps.mtt_inst_depth;
+}
+
+void xsc_set_pf_db_addr(struct xsc_core_device *xdev,
+			u64 tx_db, u64 rx_db, u64 cq_db, u64 cq_reg, u64 eq_db)
+{
+	struct xsc_hw_reg *regs = xdev->hal_regs;
+
+	if (is_pf_bar_compressed(xdev))
+		return;
+
+	if (xsc_core_is_pf(xdev)) {
+		regs->tx_db = tx_db - 0xa0000000;
+		regs->rx_db = rx_db - 0xa0000000;
+		regs->complete_db = cq_db - 0xa0000000;
+		regs->complete_reg = cq_reg - 0xa0000000;
+		regs->event_db = eq_db - 0xa0000000;
+	}
+}
+
+void xsc_get_db_addr(struct xsc_core_device *xdev,
+		     u64 *tx_db, u64 *rx_db, u64 *cq_db, u64 *cq_reg, u64 *eq_db)
+{
+	struct xsc_hw_reg *regs = xdev->hal_regs;
+
+	if (!xsc_core_is_pf(xdev) || is_pf_bar_compressed(xdev)) {
+		if (tx_db)
+			*tx_db = regs->tx_db;
+		if (rx_db)
+			*rx_db = regs->rx_db;
+		if (cq_db)
+			*cq_db = regs->complete_db;
+		if (cq_reg)
+			*cq_reg = regs->complete_reg;
+		if (eq_db)
+			*eq_db = regs->event_db;
+	} else {
+		if (tx_db)
+			*tx_db = regs->tx_db + 0xa0000000;
+		if (rx_db)
+			*rx_db = regs->rx_db + 0xa0000000;
+		if (cq_db)
+			*cq_db = regs->complete_db + 0xa0000000;
+		if (cq_reg)
+			*cq_reg = regs->complete_reg + 0xa0000000;
+		if (eq_db)
+			*eq_db = regs->event_db + 0xa0000000;
+	}
+}
+EXPORT_SYMBOL_GPL(xsc_get_db_addr);
+
+void xsc_update_tx_db(struct xsc_core_device *xdev, u32 sqn, u32 next_pid)
+{
+	bool mdb = is_support_multi_doorbell(xdev);
+
+	andes_ring_tx_doorbell(xdev, xdev->hal_regs, xdev->bar, sqn,
+			       next_pid, mdb);
+}
+EXPORT_SYMBOL_GPL(xsc_update_tx_db);
+
+void xsc_update_rx_db(struct xsc_core_device *xdev, u32 rqn, u32 next_pid)
+{
+	andes_ring_rx_doorbell(xdev->hal_regs, xdev->bar, rqn, next_pid);
+}
+EXPORT_SYMBOL_GPL(xsc_update_rx_db);
+
+void xsc_arm_cq(struct xsc_core_device *xdev, u32 cqn, u32 next_cid, u8 solicited)
+{
+	andes_update_cq_db(xdev->hal_regs, xdev->bar, cqn, next_cid, solicited);
+}
+EXPORT_SYMBOL_GPL(xsc_arm_cq);
+
+void xsc_update_cq_ci(struct xsc_core_device *xdev, u32 cqn, u32 next_cid)
+{
+	andes_set_cq_ci(xdev->hal_regs, xdev->bar, cqn, next_cid);
+}
+EXPORT_SYMBOL_GPL(xsc_update_cq_ci);
+
+void xsc_update_eq_ci(struct xsc_core_device *xdev, u32 eqn, u32 next_cid, u8 arm)
+{
+	andes_set_eq_ci(xdev->hal_regs, xdev->bar, eqn, next_cid, arm);
+}
+
+void xsc_update_cmdq_req_pid(struct xsc_core_device *xdev, u32 req_pid)
+{
+	xsc_write32(xdev->bar, xdev->hal_regs->req_pid_addr, &req_pid);
+}
+
+void xsc_update_cmdq_rsp_cid(struct xsc_core_device *xdev, u32 rsp_cid)
+{
+	xsc_write32(xdev->bar, xdev->hal_regs->rsp_cid_addr, &rsp_cid);
+}
+
+u32 xsc_get_cmdq_req_pid(struct xsc_core_device *xdev)
+{
+	u32 val;
+
+	xsc_read32(xdev->bar, xdev->hal_regs->req_pid_addr, &val, sizeof(val));
+	return val;
+}
+
+u32 xsc_get_cmdq_req_cid(struct xsc_core_device *xdev)
+{
+	u32 val;
+
+	xsc_read32(xdev->bar, xdev->hal_regs->req_cid_addr, &val, sizeof(val));
+	return val;
+}
+
+u32 xsc_get_cmdq_rsp_pid(struct xsc_core_device *xdev)
+{
+	u32 val;
+
+	xsc_read32(xdev->bar, xdev->hal_regs->rsp_pid_addr, &val, sizeof(val));
+	return val;
+}
+
+u32 xsc_get_cmdq_rsp_cid(struct xsc_core_device *xdev)
+{
+	u32 val;
+
+	xsc_read32(xdev->bar, xdev->hal_regs->rsp_cid_addr, &val, sizeof(val));
+	return val;
+}
+
+void xsc_set_cmdq_req_buf_addr(struct xsc_core_device *xdev, u32 haddr, u32 laddr)
+{
+	xsc_write32(xdev->bar, xdev->hal_regs->req_buf_h_addr, &haddr);
+	xsc_write32(xdev->bar, xdev->hal_regs->req_buf_l_addr, &laddr);
+}
+
+void xsc_set_cmdq_rsp_buf_addr(struct xsc_core_device *xdev, u32 haddr, u32 laddr)
+{
+	xsc_write32(xdev->bar, xdev->hal_regs->rsp_buf_h_addr, &haddr);
+	xsc_write32(xdev->bar, xdev->hal_regs->rsp_buf_l_addr, &laddr);
+}
+
+void xsc_set_cmdq_msix_vector(struct xsc_core_device *xdev, u32 vector)
+{
+	xsc_write32(xdev->bar, xdev->hal_regs->msix_vec_addr, &vector);
+}
+
+void xsc_check_cmdq_status(struct xsc_core_device *xdev)
+{
+	u32 status;
+
+	xsc_read32(xdev->bar, xdev->hal_regs->interrupt_stat_addr, &status,
+		   sizeof(status));
+	if (status) {
+		status = 0xf;
+		xsc_write32(xdev->bar, xdev->hal_regs->interrupt_stat_addr, &status);
+	}
+}
+
+int xsc_handle_cmdq_interrupt(struct xsc_core_device *xdev)
+{
+	union interrupt_stat {
+		struct {
+			u32	hw_read_req_err:1;
+			u32	hw_write_req_err:1;
+			u32	req_pid_err:1;
+			u32	rsp_cid_err:1;
+		};
+		u32	raw;
+	} stat;
+
+	int err = 0;
+	int retry = 0;
+
+	xsc_read32(xdev->bar, xdev->hal_regs->interrupt_stat_addr, &stat.raw,
+		   sizeof(stat.raw));
+	while (stat.raw != 0) {
+		err++;
+		if (stat.hw_read_req_err) {
+			retry = 1;
+			stat.hw_read_req_err = 0;
+			pci_err(xdev->pdev, "hw report read req from host failed!\n");
+		} else if (stat.hw_write_req_err) {
+			retry = 1;
+			stat.hw_write_req_err = 0;
+			pci_err(xdev->pdev, "hw report write req to fw failed!\n");
+		} else if (stat.req_pid_err) {
+			stat.req_pid_err = 0;
+			pci_err(xdev->pdev, "hw report unexpected req pid!\n");
+		} else if (stat.rsp_cid_err) {
+			stat.rsp_cid_err = 0;
+			pci_err(xdev->pdev, "hw report unexpected rsp cid!\n");
+		} else {
+			stat.raw = 0;
+			pci_err(xdev->pdev, "ignore unknown interrupt!\n");
+		}
+	}
+
+	if (retry) {
+		if (xdev->cmd.retry_cnt >= XSC_CMD_MAX_RETRY_CNT) {
+			pci_warn(xdev->pdev, "err handler have retried for %d times, stop retry\n",
+				 xdev->cmd.retry_cnt);
+			goto clear_err_int;
+		}
+		xsc_write32(xdev->bar, xdev->hal_regs->req_pid_addr,
+			    &xdev->cmd.cmd_pid);
+		xdev->cmd.retry_cnt++;
+	}
+
+clear_err_int:
+	if (err) {
+		stat.raw = 0xf;
+		xsc_write32(xdev->bar, xdev->hal_regs->interrupt_stat_addr, &stat.raw);
+	}
+
+	return err;
+}
+
+u8 xsc_get_mr_page_mode(struct xsc_core_device *xdev, u8 page_shift)
+{
+	return andes_get_mr_page_mode(page_shift);
+}
+EXPORT_SYMBOL_GPL(xsc_get_mr_page_mode);
+
+u32 xsc_mkey_to_idx(struct xsc_core_device *xdev, u32 mkey)
+{
+	return andes_mkey_to_idx(mkey);
+}
+
+u32 xsc_idx_to_mkey(struct xsc_core_device *xdev, u32 mkey_idx)
+{
+	return andes_idx_to_mkey(mkey_idx);
+}
+
+u8 xsc_get_cqe_opcode(struct xsc_core_device *xdev, void *cqe)
+{
+	return andes_get_cqe_opcode(cqe);
+}
+EXPORT_SYMBOL_GPL(xsc_get_cqe_opcode);
+
+u64 xsc_get_rdma_stat_mask(struct xsc_core_device *xdev, u16 ver)
+{
+	u64 mask;
+
+	if (xsc_core_is_pf(xdev))
+		mask = 0xFFFFFF;
+	else
+		mask = 0xfff;
+
+	return mask;
+}
+EXPORT_SYMBOL_GPL(xsc_get_rdma_stat_mask);
+
+void xsc_set_data_seg(struct xsc_core_device *xdev, void *data_seg, u64 addr, u32 key, u32 length)
+{
+	andes_set_data_seg(data_seg, length, key, addr);
+}
+EXPORT_SYMBOL_GPL(xsc_set_data_seg);
+
+u32 xsc_get_max_qp_depth(struct xsc_core_device *xdev)
+{
+	return 32 * 1024;
+}
+EXPORT_SYMBOL_GPL(xsc_get_max_qp_depth);
+
+bool xsc_check_max_qp_depth(struct xsc_core_device *xdev, u32 *wqe_cnt, u32 max_qp_depth)
+{
+	if (*wqe_cnt > max_qp_depth)
+		*wqe_cnt = max_qp_depth;
+
+	return false;
+}
+EXPORT_SYMBOL_GPL(xsc_check_max_qp_depth);
+
+u32 xsc_get_recv_ds_num(struct xsc_core_device *xdev)
+{
+	return xdev->caps.recv_ds_num;
+}
+EXPORT_SYMBOL_GPL(xsc_get_recv_ds_num);
+
+void *xsc_get_msg_opcode(struct xsc_core_device *xdev)
+{
+	return andes_get_msg_opcode();
+}
+
+u64 xsc_get_max_mr_size(struct xsc_core_device *xdev)
+{
+	u64 max_mpt_mr_size = xdev->caps.max_mr_size;
+	u64 max_mtt_mr_size = xdev->caps.max_mtt * PAGE_SIZE;
+
+	return min_t(u64, max_mpt_mr_size, max_mtt_mr_size);
+}
+EXPORT_SYMBOL_GPL(xsc_get_max_mr_size);
+
+void xsc_get_multidb_info(struct xsc_core_device *xdev, u32 *num, u32 *base)
+{
+	if (is_support_multi_doorbell(xdev)) {
+		*num = MDB_NUM;
+		*base = QPM_TX_MDB_BASE_REG_ADDR;
+	} else {
+		*num = 0;
+		*base = 0;
+	}
+}
+EXPORT_SYMBOL_GPL(xsc_get_multidb_info);
+
+void xsc_set_wqe_id(struct xsc_core_device *dev, void *cseg, u32 wqe_id)
+{
+	andes_set_wqe_id(cseg, wqe_id);
+}
+EXPORT_SYMBOL_GPL(xsc_set_wqe_id);
+
+int xsc_get_cqe_wqe_id(struct xsc_core_device *dev, void *cqe)
+{
+	return andes_get_cqe_wqe_id(cqe);
+}
+EXPORT_SYMBOL_GPL(xsc_get_cqe_wqe_id);
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_reg.h b/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_reg.h
new file mode 100644
index 000000000..cd25b64a5
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_reg.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+/* generated time:
+ * Thu Feb 29 15:33:50 CST 2024
+ */
+
+#ifndef ANDES_REG_H
+#define ANDES_REG_H
+
+//hif_irq_csr_defines.h
+
+//hif_cpm_csr_defines.h
+
+//mmc_csr_defines.h
+#define MMC_MPT_TBL_MEM_DEPTH  32768
+#define MMC_MTT_TBL_MEM_DEPTH  262144
+#define MMC_MPT_TBL_MEM_WIDTH  256
+#define MMC_MPT_TBL_MEM_ADDR   0xa4100000
+#define MMC_MTT_TBL_MEM_ADDR   0xa4200000
+
+//clsf_dma_csr_defines.h
+
+//hif_tbl_csr_defines.h
+
+//hif_cmdqm_csr_defines.h
+#define HIF_CMDQM_HOST_REQ_PID_MEM_ADDR   0xa1026000
+#define HIF_CMDQM_HOST_REQ_CID_MEM_ADDR   0xa1028000
+#define HIF_CMDQM_HOST_RSP_PID_MEM_ADDR   0xa102e000
+#define HIF_CMDQM_HOST_RSP_CID_MEM_ADDR   0xa1030000
+#define HIF_CMDQM_HOST_REQ_BUF_BASE_H_ADDR_MEM_ADDR   0xa1022000
+#define HIF_CMDQM_HOST_REQ_BUF_BASE_L_ADDR_MEM_ADDR   0xa1024000
+#define HIF_CMDQM_HOST_RSP_BUF_BASE_H_ADDR_MEM_ADDR   0xa102a000
+#define HIF_CMDQM_HOST_RSP_BUF_BASE_L_ADDR_MEM_ADDR   0xa102c000
+#define HIF_CMDQM_VECTOR_ID_MEM_ADDR   0xa1034000
+#define HIF_CMDQM_HOST_VF_ERR_STS_MEM_ADDR   0xa1032000
+
+//PSV use
+//hif_irq_csr_defines.h
+
+#endif /* XSC_HW_H  */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/hal/xsc_hal.h b/drivers/net/ethernet/yunsilicon/xsc/pci/hal/xsc_hal.h
new file mode 100644
index 000000000..5a3a7e27f
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/hal/xsc_hal.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2024 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef XSC_HAL_H
+#define XSC_HAL_H
+
+struct xsc_core_device;
+
+struct xsc_hw_reg {
+	u32 tx_db;
+	u32 rx_db;
+	u32 complete_db;
+	u32 complete_reg;
+	u32 event_db;
+	u32 req_pid_addr;
+	u32 req_cid_addr;
+	u32 rsp_pid_addr;
+	u32 rsp_cid_addr;
+	u32 req_buf_h_addr;
+	u32 req_buf_l_addr;
+	u32 rsp_buf_h_addr;
+	u32 rsp_buf_l_addr;
+	u32 msix_vec_addr;
+	u32 interrupt_stat_addr;
+	u32 mpt_tbl_addr;
+	u32 mpt_tbl_depth;
+	u32 mpt_tbl_width;
+	u32 mtt_inst_base_addr;
+	u32 mtt_inst_stride;
+	u32 mtt_inst_num_log;
+	u32 mtt_inst_depth;
+};
+
+/* HAL entry points exported to the PCI/RDMA/ETH layers */
+void xsc_init_hal(struct xsc_core_device *xdev, u32 device_id);
+void xsc_set_mtt_info(struct xsc_core_device *xdev);
+void xsc_set_pf_db_addr(struct xsc_core_device *xdev,
+			u64 tx_db, u64 rx_db, u64 cq_db, u64 cq_reg, u64 eq_db);
+void xsc_get_db_addr(struct xsc_core_device *xdev,
+		     u64 *tx_db, u64 *rx_db, u64 *cq_db, u64 *cq_reg, u64 *eq_db);
+
+void xsc_update_tx_db(struct xsc_core_device *xdev, u32 sqn, u32 next_pid);
+void xsc_update_rx_db(struct xsc_core_device *xdev, u32 rqn, u32 next_pid);
+void xsc_arm_cq(struct xsc_core_device *xdev, u32 cqn, u32 next_cid, u8 solicited);
+void xsc_update_cq_ci(struct xsc_core_device *xdev, u32 cqn, u32 next_cid);
+void xsc_update_eq_ci(struct xsc_core_device *xdev, u32 eqn, u32 next_cid, u8 arm);
+
+void xsc_update_cmdq_req_pid(struct xsc_core_device *xdev, u32 req_pid);
+void xsc_update_cmdq_rsp_cid(struct xsc_core_device *xdev, u32 rsp_cid);
+u32 xsc_get_cmdq_req_pid(struct xsc_core_device *xdev);
+u32 xsc_get_cmdq_req_cid(struct xsc_core_device *xdev);
+u32 xsc_get_cmdq_rsp_pid(struct xsc_core_device *xdev);
+u32 xsc_get_cmdq_rsp_cid(struct xsc_core_device *xdev);
+void xsc_set_cmdq_req_buf_addr(struct xsc_core_device *xdev, u32 haddr, u32 laddr);
+void xsc_set_cmdq_rsp_buf_addr(struct xsc_core_device *xdev, u32 haddr, u32 laddr);
+void xsc_set_cmdq_msix_vector(struct xsc_core_device *xdev, u32 vector);
+void xsc_check_cmdq_status(struct xsc_core_device *xdev);
+int xsc_handle_cmdq_interrupt(struct xsc_core_device *xdev);
+
+u8 xsc_get_mr_page_mode(struct xsc_core_device *xdev, u8 page_shift);
+u32 xsc_mkey_to_idx(struct xsc_core_device *xdev, u32 mkey);
+u32 xsc_idx_to_mkey(struct xsc_core_device *xdev, u32 mkey_idx);
+
+u8 xsc_get_cqe_opcode(struct xsc_core_device *xdev, void *cqe);
+u64 xsc_get_rdma_stat_mask(struct xsc_core_device *xdev, u16 ver);
+void xsc_set_data_seg(struct xsc_core_device *xdev, void *data_seg, u64 addr, u32 key, u32 length);
+u32 xsc_get_max_qp_depth(struct xsc_core_device *xdev);
+bool xsc_check_max_qp_depth(struct xsc_core_device *xdev, u32 *wqe_cnt, u32 max_qp_depth);
+u32 xsc_get_recv_ds_num(struct xsc_core_device *xdev);
+void *xsc_get_msg_opcode(struct xsc_core_device *xdev);
+u64 xsc_get_max_mr_size(struct xsc_core_device *xdev);
+void xsc_get_multidb_info(struct xsc_core_device *xdev, u32 *num, u32 *base);
+void xsc_set_wqe_id(struct xsc_core_device *dev, void *cseg, u32 wqe_id);
+int xsc_get_cqe_wqe_id(struct xsc_core_device *dev, void *cqe);
+
+enum {
+	CQ_STAT_FIRED,
+	CQ_STAT_KEEP,
+	CQ_STAT_ARM_NEXT,
+	CQ_STAT_ARM_SOLICITED,
+};
+
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/main.c b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
index 734d48825..35dff401a 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
@@ -4,6 +4,7 @@
  */
 
 #include "common/xsc_core.h"
+#include "hal/xsc_hal.h"
 
 static const struct pci_device_id xsc_pci_id_table[] = {
 	{ PCI_DEVICE(XSC_PCI_VENDOR_ID, XSC_MC_PF_DEV_ID) },
@@ -75,6 +76,8 @@ static int xsc_pci_init(struct xsc_core_device *xdev,
 	xdev->bar_num = bar_num;
 	xdev->bar = bar_base;
 
+	xsc_init_hal(xdev, pdev->device);
+
 	return 0;
 
 err_io_unmap:

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

* [PATCH 03/15] yunsilicon: add the command queue
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
  2026-10-12 11:43 ` [PATCH 01/15] yunsilicon: add PCI core driver skeleton Xin Tian
  2026-10-12 11:43 ` [PATCH 02/15] yunsilicon: add the register HAL Xin Tian
@ 2026-10-12 11:43 ` Xin Tian
  2026-10-13  3:11   ` Junxian Huang
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 04/15] yunsilicon: add event queues, interrupts and the Xin Tian
                   ` (12 subsequent siblings)
  15 siblings, 2 replies; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:43 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the command queue that is shared by the ethernet and RDMA functions:
the request/response rings, the command work handling and the device
initialisation sequence that queries the HCA capabilities, the GUID and
the message opcode table.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 .../net/ethernet/yunsilicon/xsc/pci/Makefile  |    2 +-
 .../net/ethernet/yunsilicon/xsc/pci/cmdq.c    | 1247 +++++++++++++++++
 drivers/net/ethernet/yunsilicon/xsc/pci/hw.c  |  310 ++++
 drivers/net/ethernet/yunsilicon/xsc/pci/hw.h  |   18 +
 .../net/ethernet/yunsilicon/xsc/pci/main.c    |   55 +
 5 files changed, 1631 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hw.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hw.h

diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
index 81b65232e..656cdffa1 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
@@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
 
-xsc_pci-y := main.o hal/andes_impl.o
+xsc_pci-y := main.o cmdq.o hw.o hal/andes_impl.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c b/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
new file mode 100644
index 000000000..48832b26d
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
@@ -0,0 +1,1247 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ * Copyright (c) 2013-2016, Mellanox Technologies. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/random.h>
+#include <linux/kthread.h>
+#include <linux/io-mapping.h>
+
+#include "common/xsc_cmd_api.h"
+#include "common/xsc_cmd.h"
+#include "common/xsc_core.h"
+#include "hal/xsc_hal.h"
+
+enum {
+	CMD_IF_REV = 3,
+};
+
+enum {
+	NUM_LONG_LISTS	  = 2,
+	NUM_MED_LISTS	  = 64,
+	LONG_LIST_SIZE	  = (2ULL * 1024 * 1024 * 1024 / PAGE_SIZE) * 8 + 16 +
+				XSC_CMD_DATA_BLOCK_SIZE,
+	MED_LIST_SIZE	  = 16 + XSC_CMD_DATA_BLOCK_SIZE,
+};
+
+enum {
+	XSC_CMD_DELIVERY_STAT_OK			= 0x0,
+	XSC_CMD_DELIVERY_STAT_SIGNAT_ERR		= 0x1,
+	XSC_CMD_DELIVERY_STAT_TOK_ERR			= 0x2,
+	XSC_CMD_DELIVERY_STAT_BAD_BLK_NUM_ERR		= 0x3,
+	XSC_CMD_DELIVERY_STAT_OUT_PTR_ALIGN_ERR		= 0x4,
+	XSC_CMD_DELIVERY_STAT_IN_PTR_ALIGN_ERR		= 0x5,
+	XSC_CMD_DELIVERY_STAT_FW_ERR			= 0x6,
+	XSC_CMD_DELIVERY_STAT_IN_LENGTH_ERR		= 0x7,
+	XSC_CMD_DELIVERY_STAT_OUT_LENGTH_ERR		= 0x8,
+	XSC_CMD_DELIVERY_STAT_RES_FLD_NOT_CLR_ERR	= 0x9,
+	XSC_CMD_DELIVERY_STAT_CMD_DESCR_ERR		= 0x10,
+};
+
+static struct xsc_cmd_work_ent *xsc_alloc_cmd(struct xsc_cmd *cmd,
+					      struct xsc_cmd_msg *in,
+					      struct xsc_rsp_msg *out)
+{
+	struct xsc_cmd_work_ent *ent;
+
+	ent = kzalloc_obj(*ent, GFP_KERNEL);
+	if (!ent)
+		return ERR_PTR(-ENOMEM);
+
+	ent->in		= in;
+	ent->out	= out;
+	ent->cmd	= cmd;
+
+	return ent;
+}
+
+static void xsc_free_cmd(struct xsc_cmd_work_ent *ent)
+{
+	kfree(ent);
+}
+
+static u8 xsc_alloc_token(struct xsc_cmd *cmd)
+{
+	u8 token;
+
+	spin_lock(&cmd->token_lock);
+	token = cmd->token++ % 255 + 1;
+	spin_unlock(&cmd->token_lock);
+
+	return token;
+}
+
+static int xsc_alloc_ent(struct xsc_cmd *cmd)
+{
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&cmd->alloc_lock, flags);
+	ret = find_first_bit(&cmd->cmd_entry_mask, cmd->max_reg_cmds);
+	if (ret < cmd->max_reg_cmds)
+		clear_bit(ret, &cmd->cmd_entry_mask);
+	spin_unlock_irqrestore(&cmd->alloc_lock, flags);
+
+	return ret < cmd->max_reg_cmds ? ret : -ENOSPC;
+}
+
+static void xsc_free_ent(struct xsc_cmd *cmd, int idx)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&cmd->alloc_lock, flags);
+	set_bit(idx, &cmd->cmd_entry_mask);
+	spin_unlock_irqrestore(&cmd->alloc_lock, flags);
+}
+
+static struct xsc_cmd_layout *xsc_get_inst(struct xsc_cmd *cmd, int idx)
+{
+	return cmd->cmd_buf + (idx << cmd->log_stride);
+}
+
+static struct xsc_rsp_layout *xsc_get_cq_inst(struct xsc_cmd *cmd, int idx)
+{
+	return cmd->cq_buf + (idx << cmd->log_stride);
+}
+
+static u8 xsc_xor8_buf(void *buf, int len)
+{
+	u8 *ptr = buf;
+	u8 sum = 0;
+	int i;
+
+	for (i = 0; i < len; i++)
+		sum ^= ptr[i];
+
+	return sum;
+}
+
+static int xsc_verify_block_sig(struct xsc_cmd_prot_block *block)
+{
+	/* rsvd0 was set to 0xFF by fw */
+	if (xsc_xor8_buf(block->rsvd0,
+			 sizeof(*block) - sizeof(block->data) - 1) != 0xff)
+		return -EINVAL;
+
+	if (xsc_xor8_buf(block, sizeof(*block)) != 0xff)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void xsc_calc_block_sig(struct xsc_cmd_prot_block *block, u8 token)
+{
+	block->token = token;
+	block->ctrl_sig = ~xsc_xor8_buf(block->rsvd0,
+				    sizeof(*block) - sizeof(block->data) - 2);
+	block->sig = ~xsc_xor8_buf(block, sizeof(*block) - 1);
+}
+
+static void xsc_calc_chain_sig(struct xsc_cmd_mailbox *head, u8 token)
+{
+	struct xsc_cmd_mailbox *next = head;
+
+	while (next) {
+		xsc_calc_block_sig(next->buf, token);
+		next = next->next;
+	}
+}
+
+static void xsc_set_signature(struct xsc_cmd_work_ent *ent)
+{
+	ent->lay->sig = ~xsc_xor8_buf(ent->lay, sizeof(*ent->lay));
+	xsc_calc_chain_sig(ent->in->next, ent->token);
+	xsc_calc_chain_sig(ent->out->next, ent->token);
+}
+
+static int xsc_verify_signature(struct xsc_cmd_work_ent *ent)
+{
+	struct xsc_cmd_mailbox *next = ent->out->next;
+	int err;
+	u8 sig;
+
+	sig = xsc_xor8_buf(ent->rsp_lay, sizeof(*ent->rsp_lay));
+	if (sig != 0xff)
+		return -EINVAL;
+
+	while (next) {
+		err = xsc_verify_block_sig(next->buf);
+		if (err)
+			return err;
+
+		next = next->next;
+	}
+
+	return 0;
+}
+
+static const char *xsc_command_str(int command)
+{
+	switch (command) {
+	case XSC_CMD_OP_QUERY_HCA_CAP:
+		return "QUERY_HCA_CAP";
+
+	case XSC_CMD_OP_QUERY_CMDQ_VERSION:
+		return "QUERY_CMDQ_VERSION";
+
+	case XSC_CMD_OP_FUNCTION_RESET:
+		return "FUNCTION_RESET";
+
+	case XSC_CMD_OP_DUMMY:
+		return "DUMMY_CMD";
+
+	case XSC_CMD_OP_CREATE_EQ:
+		return "CREATE_EQ";
+
+	case XSC_CMD_OP_DESTROY_EQ:
+		return "DESTROY_EQ";
+
+	case XSC_CMD_OP_CREATE_CQ:
+		return "CREATE_CQ";
+
+	case XSC_CMD_OP_DESTROY_CQ:
+		return "DESTROY_CQ";
+
+	case XSC_CMD_OP_CREATE_QP:
+		return "CREATE_QP";
+
+	case XSC_CMD_OP_DESTROY_QP:
+		return "DESTROY_QP";
+
+	case XSC_CMD_OP_RST2INIT_QP:
+		return "RST2INIT_QP";
+
+	case XSC_CMD_OP_INIT2RTR_QP:
+		return "INIT2RTR_QP";
+
+	case XSC_CMD_OP_RTR2RTS_QP:
+		return "RTR2RTS_QP";
+
+	case XSC_CMD_OP_RTS2RTS_QP:
+		return "RTS2RTS_QP";
+
+	case XSC_CMD_OP_SQERR2RTS_QP:
+		return "SQERR2RTS_QP";
+
+	case XSC_CMD_OP_2ERR_QP:
+		return "2ERR_QP";
+
+	case XSC_CMD_OP_RTS2SQD_QP:
+		return "RTS2SQD_QP";
+
+	case XSC_CMD_OP_SQD2RTS_QP:
+		return "SQD2RTS_QP";
+
+	case XSC_CMD_OP_2RST_QP:
+		return "2RST_QP";
+
+	case XSC_CMD_OP_INIT2INIT_QP:
+		return "INIT2INIT_QP";
+
+	case XSC_CMD_OP_MODIFY_RAW_QP:
+		return "MODIFY_RAW_QP";
+
+	case XSC_CMD_OP_ENABLE_NIC_HCA:
+		return "ENABLE_NIC_HCA";
+
+	case XSC_CMD_OP_DISABLE_NIC_HCA:
+		return "DISABLE_NIC_HCA";
+
+	case XSC_CMD_OP_QUERY_VPORT_STATE:
+		return "QUERY_VPORT_STATE";
+
+	case XSC_CMD_OP_QUERY_EVENT_TYPE:
+		return "QUERY_EVENT_TYPE";
+
+	case XSC_CMD_OP_ENABLE_MSIX:
+		return "ENABLE_MSIX";
+
+	case XSC_CMD_OP_SET_MTU:
+		return "SET_MTU";
+
+	case XSC_CMD_OP_QUERY_ETH_MAC:
+		return "QUERY_ETH_MAC";
+
+	default: return "unknown command opcode";
+	}
+}
+
+static void cmd_work_handler(struct work_struct *work)
+{
+	struct xsc_cmd_work_ent *ent;
+	struct xsc_core_device *xdev;
+	struct xsc_cmd_layout *lay;
+	struct semaphore *sem;
+	struct xsc_cmd *cmd;
+	unsigned long flags;
+
+	ent = container_of(work, struct xsc_cmd_work_ent, work);
+	cmd = ent->cmd;
+	xdev = container_of(cmd, struct xsc_core_device, cmd);
+	sem = &cmd->sem;
+	down(sem);
+	ent->idx = xsc_alloc_ent(cmd);
+	if (ent->idx < 0) {
+		pci_err(xdev->pdev, "failed to allocate command entry\n");
+		up(sem);
+		return;
+	}
+
+	ent->token = xsc_alloc_token(cmd);
+	cmd->ent_arr[ent->idx] = ent;
+
+	spin_lock_irqsave(&cmd->doorbell_lock, flags);
+	lay = xsc_get_inst(cmd, cmd->cmd_pid);
+	ent->lay = lay;
+	memset(lay, 0, sizeof(*lay));
+	memcpy(lay->in, ent->in->first.data, sizeof(lay->in));
+	if (ent->in->next)
+		lay->in_ptr = cpu_to_be64(ent->in->next->dma);
+	lay->inlen = cpu_to_be32(ent->in->len);
+	if (ent->out->next)
+		lay->out_ptr = cpu_to_be64(ent->out->next->dma);
+	lay->outlen = cpu_to_be32(ent->out->len);
+	lay->type = XSC_PCI_CMD_XPORT;
+	lay->token = ent->token;
+	lay->idx = ent->idx;
+	if (!cmd->checksum_disabled)
+		xsc_set_signature(ent);
+	else
+		lay->sig = 0xff;
+
+	/* ring doorbell after the descriptor is valid */
+	wmb();
+
+	cmd->cmd_pid = (cmd->cmd_pid + 1) % (1 << cmd->log_sz);
+	xsc_update_cmdq_req_pid(xdev, cmd->cmd_pid);
+	spin_unlock_irqrestore(&cmd->doorbell_lock, flags);
+}
+
+static const char *xsc_deliv_status_to_str(u8 status)
+{
+	switch (status) {
+	case XSC_CMD_DELIVERY_STAT_OK:
+		return "no errors";
+	case XSC_CMD_DELIVERY_STAT_SIGNAT_ERR:
+		return "signature error";
+	case XSC_CMD_DELIVERY_STAT_TOK_ERR:
+		return "token error";
+	case XSC_CMD_DELIVERY_STAT_BAD_BLK_NUM_ERR:
+		return "bad block number";
+	case XSC_CMD_DELIVERY_STAT_OUT_PTR_ALIGN_ERR:
+		return "output pointer not aligned to block size";
+	case XSC_CMD_DELIVERY_STAT_IN_PTR_ALIGN_ERR:
+		return "input pointer not aligned to block size";
+	case XSC_CMD_DELIVERY_STAT_FW_ERR:
+		return "firmware internal error";
+	case XSC_CMD_DELIVERY_STAT_IN_LENGTH_ERR:
+		return "command input length error";
+	case XSC_CMD_DELIVERY_STAT_OUT_LENGTH_ERR:
+		return "command output length error";
+	case XSC_CMD_DELIVERY_STAT_RES_FLD_NOT_CLR_ERR:
+		return "reserved fields not cleared";
+	case XSC_CMD_DELIVERY_STAT_CMD_DESCR_ERR:
+		return "bad command descriptor type";
+	default:
+		return "unknown status code";
+	}
+}
+
+static u16 xsc_msg_to_opcode(struct xsc_cmd_msg *in)
+{
+	struct xsc_inbox_hdr *hdr = (struct xsc_inbox_hdr *)(in->first.data);
+
+	return be16_to_cpu(hdr->opcode);
+}
+
+static int xsc_wait_func(struct xsc_core_device *xdev,
+			 struct xsc_cmd_work_ent *ent)
+{
+	unsigned long timeout = msecs_to_jiffies(XSC_CMD_TIMEOUT_MSEC);
+	struct xsc_cmd *cmd = &xdev->cmd;
+	int err;
+
+	if (!wait_for_completion_timeout(&ent->done, timeout))
+		err = -ETIMEDOUT;
+	else
+		err = ent->ret;
+
+	if (err == -ETIMEDOUT) {
+		cmd->cmd_status = XSC_CMD_STATUS_TIMEDOUT;
+		pci_err(xdev->pdev, "wait for %s(0x%x) response timeout!\n",
+			xsc_command_str(xsc_msg_to_opcode(ent->in)),
+			xsc_msg_to_opcode(ent->in));
+	} else if (err) {
+		pci_err(xdev->pdev, "err %d, delivery status %s(%d)\n", err,
+			xsc_deliv_status_to_str(ent->status), ent->status);
+	}
+
+	return err;
+}
+
+/*  Notes:
+ *    1. Callback functions may not sleep
+ *    2. page queue commands do not support asynchrous completion
+ */
+static int xsc_cmd_invoke(struct xsc_core_device *xdev, struct xsc_cmd_msg *in,
+			  struct xsc_rsp_msg *out, u8 *status)
+{
+	struct xsc_cmd *cmd = &xdev->cmd;
+	struct xsc_cmd_work_ent *ent;
+	struct semaphore *sem;
+	int err = 0;
+
+	ent = xsc_alloc_cmd(cmd, in, out);
+	if (IS_ERR(ent))
+		return PTR_ERR(ent);
+
+	init_completion(&ent->done);
+	INIT_WORK(&ent->work, cmd_work_handler);
+	if (!queue_work(cmd->wq, &ent->work)) {
+		pci_err(xdev->pdev, "failed to queue work\n");
+		err = -ENOMEM;
+		goto err_free;
+	}
+
+	err = xsc_wait_func(xdev, ent);
+	if (err == -ETIMEDOUT)
+		goto err_sem_up;
+	*status = ent->status;
+	xsc_free_cmd(ent);
+
+	return err;
+
+err_sem_up:
+	sem = &cmd->sem;
+	up(sem);
+err_free:
+	xsc_free_cmd(ent);
+	return err;
+}
+
+static int xsc_copy_to_cmd_msg(struct xsc_cmd_msg *to, void *from,
+			       unsigned int size)
+{
+	struct xsc_cmd_prot_block *block;
+	struct xsc_cmd_mailbox *next;
+	unsigned int copy;
+
+	if (!to || !from)
+		return -ENOMEM;
+
+	copy = min(size, sizeof(to->first.data));
+	memcpy(to->first.data, from, copy);
+	size -= copy;
+	from += copy;
+
+	next = to->next;
+	while (size) {
+		if (!next) {
+			WARN_ONCE(1, "Mail box not enough\n");
+			return -ENOMEM;
+		}
+
+		copy = min(size, XSC_CMD_DATA_BLOCK_SIZE);
+		block = next->buf;
+		memcpy(block->data, from, copy);
+		block->owner_status = 0;
+		from += copy;
+		size -= copy;
+		next = next->next;
+	}
+
+	return 0;
+}
+
+static int xsc_copy_from_rsp_msg(void *to, struct xsc_rsp_msg *from,
+				 unsigned int size)
+{
+	struct xsc_cmd_prot_block *block;
+	struct xsc_cmd_mailbox *next;
+	unsigned int copy;
+
+	if (!to || !from)
+		return -ENOMEM;
+
+	copy = min(size, sizeof(from->first.data));
+	memcpy(to, from->first.data, copy);
+	size -= copy;
+	to += copy;
+
+	next = from->next;
+	while (size) {
+		if (!next) {
+			WARN_ONCE(1, "Mail too short\n");
+			return -ENOMEM;
+		}
+
+		copy = min(size, XSC_CMD_DATA_BLOCK_SIZE);
+		block = next->buf;
+		if (!block->owner_status)
+			pr_err("block ownership check failed\n");
+
+		memcpy(to, block->data, copy);
+		to += copy;
+		size -= copy;
+		next = next->next;
+	}
+
+	return 0;
+}
+
+static struct xsc_cmd_mailbox *xsc_alloc_cmd_box(struct xsc_core_device *xdev,
+						 gfp_t flags)
+{
+	struct xsc_cmd_mailbox *mailbox;
+
+	mailbox = kmalloc_obj(*mailbox, flags);
+	if (!mailbox)
+		return ERR_PTR(-ENOMEM);
+
+	mailbox->buf = dma_pool_zalloc(xdev->cmd.pool, flags,
+				       &mailbox->dma);
+	if (!mailbox->buf) {
+		kfree(mailbox);
+		return ERR_PTR(-ENOMEM);
+	}
+	mailbox->next = NULL;
+
+	return mailbox;
+}
+
+static void xsc_free_cmd_box(struct xsc_core_device *xdev,
+			     struct xsc_cmd_mailbox *mailbox)
+{
+	dma_pool_free(xdev->cmd.pool, mailbox->buf, mailbox->dma);
+
+	kfree(mailbox);
+}
+
+static struct xsc_cmd_msg *xsc_alloc_cmd_msg(struct xsc_core_device *xdev,
+					     gfp_t flags, unsigned int size)
+{
+	struct xsc_cmd_mailbox *tmp, *head = NULL;
+	struct xsc_cmd_prot_block *block;
+	struct xsc_cmd_msg *msg;
+	int blen;
+	int err;
+	int n;
+	int i;
+
+	msg = kzalloc_obj(*msg, GFP_KERNEL);
+	if (!msg)
+		return ERR_PTR(-ENOMEM);
+
+	blen = size - min(sizeof(msg->first.data), size);
+	n = (blen + XSC_CMD_DATA_BLOCK_SIZE - 1) / XSC_CMD_DATA_BLOCK_SIZE;
+
+	for (i = 0; i < n; i++) {
+		tmp = xsc_alloc_cmd_box(xdev, flags);
+		if (IS_ERR(tmp)) {
+			pci_err(xdev->pdev, "failed allocating block\n");
+			err = PTR_ERR(tmp);
+			goto err_free;
+		}
+
+		block = tmp->buf;
+		tmp->next = head;
+		block->next = cpu_to_be64(tmp->next ? tmp->next->dma : 0);
+		block->block_num = cpu_to_be32(n - i - 1);
+		head = tmp;
+	}
+	msg->next = head;
+	msg->len = size;
+	return msg;
+
+err_free:
+	while (head) {
+		tmp = head->next;
+		xsc_free_cmd_box(xdev, head);
+		head = tmp;
+	}
+	kfree(msg);
+
+	return ERR_PTR(err);
+}
+
+static void xsc_free_cmd_msg(struct xsc_core_device *xdev,
+			     struct xsc_cmd_msg *msg)
+{
+	struct xsc_cmd_mailbox *head = msg->next;
+	struct xsc_cmd_mailbox *next;
+
+	while (head) {
+		next = head->next;
+		xsc_free_cmd_box(xdev, head);
+		head = next;
+	}
+	kfree(msg);
+}
+
+static struct xsc_rsp_msg *xsc_alloc_rsp_msg(struct xsc_core_device *xdev,
+					     gfp_t flags, unsigned int size)
+{
+	struct xsc_cmd_mailbox *tmp, *head = NULL;
+	struct xsc_cmd_prot_block *block;
+	struct xsc_rsp_msg *msg;
+	int blen;
+	int err;
+	int n;
+	int i;
+
+	msg = kzalloc_obj(*msg, GFP_KERNEL);
+	if (!msg)
+		return ERR_PTR(-ENOMEM);
+
+	blen = size - min_t(int, sizeof(msg->first.data), size);
+	n = (blen + XSC_CMD_DATA_BLOCK_SIZE - 1) / XSC_CMD_DATA_BLOCK_SIZE;
+
+	for (i = 0; i < n; i++) {
+		tmp = xsc_alloc_cmd_box(xdev, flags);
+		if (IS_ERR(tmp)) {
+			pci_err(xdev->pdev, "failed allocating block\n");
+			err = PTR_ERR(tmp);
+			goto err_free;
+		}
+
+		block = tmp->buf;
+		tmp->next = head;
+		block->next = cpu_to_be64(tmp->next ? tmp->next->dma : 0);
+		block->block_num = cpu_to_be32(n - i - 1);
+		head = tmp;
+	}
+	msg->next = head;
+	msg->len = size;
+	return msg;
+
+err_free:
+	while (head) {
+		tmp = head->next;
+		xsc_free_cmd_box(xdev, head);
+		head = tmp;
+	}
+	kfree(msg);
+
+	return ERR_PTR(err);
+}
+
+static void xsc_free_rsp_msg(struct xsc_core_device *xdev,
+			     struct xsc_rsp_msg *msg)
+{
+	struct xsc_cmd_mailbox *head = msg->next;
+	struct xsc_cmd_mailbox *next;
+
+	while (head) {
+		next = head->next;
+		xsc_free_cmd_box(xdev, head);
+		head = next;
+	}
+	kfree(msg);
+}
+
+static void xsc_set_wqname(struct xsc_core_device *xdev)
+{
+	struct xsc_cmd *cmd = &xdev->cmd;
+
+	snprintf(cmd->wq_name, sizeof(cmd->wq_name), "xsc_cmd_%s",
+		 dev_name(&xdev->pdev->dev));
+}
+
+void xsc_cmd_use_events(struct xsc_core_device *xdev)
+{
+	struct xsc_cmd *cmd = &xdev->cmd;
+	int i;
+
+	for (i = 0; i < cmd->max_reg_cmds; i++)
+		down(&cmd->sem);
+
+	flush_workqueue(cmd->wq);
+
+	cmd->mode = XSC_CMD_MODE_EVENTS;
+
+	while (cmd->cmd_pid != cmd->cq_cid)
+		msleep(20);
+	kthread_stop(cmd->cq_task);
+	cmd->cq_task = NULL;
+
+	for (i = 0; i < cmd->max_reg_cmds; i++)
+		up(&cmd->sem);
+}
+
+static int xsc_cmd_cq_polling(void *data);
+void xsc_cmd_use_polling(struct xsc_core_device *xdev)
+{
+	struct xsc_cmd *cmd = &xdev->cmd;
+	int i;
+
+	for (i = 0; i < cmd->max_reg_cmds; i++)
+		down(&cmd->sem);
+
+	flush_workqueue(cmd->wq);
+	cmd->mode = XSC_CMD_MODE_POLLING;
+	cmd->cq_task = kthread_create(xsc_cmd_cq_polling,
+				      (void *)xdev,
+				      "xsc_cmd_cq_polling");
+	if (cmd->cq_task)
+		wake_up_process(cmd->cq_task);
+
+	for (i = 0; i < cmd->max_reg_cmds; i++)
+		up(&cmd->sem);
+}
+
+int xsc_cmd_status_to_err(struct xsc_outbox_hdr *hdr)
+{
+	switch (hdr->status) {
+	case XSC_CMD_STATUS_OK:
+		return 0;
+	case XSC_CMD_STATUS_FAIL:
+		return -EIO;
+	case XSC_CMD_STATUS_NOT_SUPPORTED:
+		return -EOPNOTSUPP;
+	case XSC_CMD_STATUS_BAD_PARAM:
+		return -EINVAL;
+	case XSC_CMD_STATUS_INVAL_RES:
+	case XSC_CMD_STATUS_INVAL_DATA:
+	case XSC_CMD_STATUS_NO_RES:
+	case XSC_CMD_STATUS_NO_MPT_RES:
+	case XSC_CMD_STATUS_NO_MTT_RES:
+	case XSC_CMD_STATUS_NO_EQN_RES:
+	case XSC_CMD_STATUS_NO_EQ_PA_RES:
+	case XSC_CMD_STATUS_NO_CQN_RES:
+	case XSC_CMD_STATUS_NO_CQ_PA_RES:
+	case XSC_CMD_STATUS_NO_QPN_RES:
+	case XSC_CMD_STATUS_NO_QP_PA_RES:
+	case XSC_CMD_STATUS_NO_PDN_RES:
+	case XSC_CMD_STATUS_BAD_INBUF:
+	case XSC_CMD_STATUS_BAD_OUTBUF:
+		return -EIO;
+	case XSC_CMD_STATUS_BUSY:
+	case XSC_CMD_STATUS_QP_FLUSH_BUSY:
+		return -EBUSY;
+	case XSC_CMD_STATUS_QP_FLUSH_PENDING:
+		return -EAGAIN;
+	case XSC_CMD_STATUS_NOT_FOUND:
+	case XSC_CMD_STATUS_INVAL_FUNC:
+		return -ENODEV;
+	default:
+		return -EIO;
+	}
+}
+
+static int xsc_status_to_err(u8 status)
+{
+	struct xsc_outbox_hdr hdr = { .status = status };
+
+	return xsc_cmd_status_to_err(&hdr);
+}
+
+static struct xsc_cmd_msg *xsc_alloc_msg(struct xsc_core_device *xdev,
+					 unsigned int in_size)
+{
+	struct xsc_cmd_msg *msg = ERR_PTR(-ENOMEM);
+	struct xsc_cmd *cmd = &xdev->cmd;
+	struct cache_ent *ent = NULL;
+
+	if (in_size > MED_LIST_SIZE && in_size <= LONG_LIST_SIZE)
+		ent = &cmd->cache.large;
+	else if (in_size > 16 && in_size <= MED_LIST_SIZE)
+		ent = &cmd->cache.med;
+
+	if (ent) {
+		spin_lock(&ent->lock);
+		if (!list_empty(&ent->head)) {
+			msg = list_entry(ent->head.next, typeof(*msg), list);
+			/* For cached lists, we must explicitly state what is
+			 * the real size
+			 */
+			msg->len = in_size;
+			list_del(&msg->list);
+		}
+		spin_unlock(&ent->lock);
+	}
+
+	if (IS_ERR(msg))
+		msg = xsc_alloc_cmd_msg(xdev, GFP_KERNEL, in_size);
+
+	return msg;
+}
+
+static void xsc_free_msg(struct xsc_core_device *xdev, struct xsc_cmd_msg *msg)
+{
+	if (msg->cache) {
+		spin_lock(&msg->cache->lock);
+		list_add_tail(&msg->list, &msg->cache->head);
+		spin_unlock(&msg->cache->lock);
+	} else {
+		xsc_free_cmd_msg(xdev, msg);
+	}
+}
+
+static int _xsc_cmd_exec(struct xsc_core_device *xdev,
+			 void *in, unsigned int in_size,
+			 void *out, unsigned int out_size)
+{
+	struct xsc_cmd *cmd = &xdev->cmd;
+	struct xsc_rsp_msg *outb;
+	struct xsc_cmd_msg *inb;
+	u8 status = 0;
+	int err;
+
+	if (cmd->cmd_status == XSC_CMD_STATUS_TIMEDOUT)
+		return -ETIMEDOUT;
+
+	inb = xsc_alloc_msg(xdev, in_size);
+	if (IS_ERR(inb)) {
+		err = PTR_ERR(inb);
+		return err;
+	}
+
+	err = xsc_copy_to_cmd_msg(inb, in, in_size);
+	if (err) {
+		pci_err(xdev->pdev, "copy to cmd_msg err %d\n", err);
+		goto err_free_msg;
+	}
+
+	outb = xsc_alloc_rsp_msg(xdev, GFP_KERNEL, out_size);
+	if (IS_ERR(outb)) {
+		err = PTR_ERR(outb);
+		goto err_free_msg;
+	}
+
+	err = xsc_cmd_invoke(xdev, inb, outb, &status);
+	if (err)
+		goto err_free_rsp;
+
+	if (status) {
+		pci_err(xdev->pdev, "opcode:%#x, err %d, status %d\n",
+			xsc_msg_to_opcode(inb), err, status);
+		err = xsc_status_to_err(status);
+		goto err_free_rsp;
+	}
+
+	err = xsc_copy_from_rsp_msg(out, outb, out_size);
+
+err_free_rsp:
+	xsc_free_rsp_msg(xdev, outb);
+
+err_free_msg:
+	xsc_free_msg(xdev, inb);
+	return err;
+}
+
+int xsc_cmd_exec(struct xsc_core_device *xdev,
+		 void *in, unsigned int in_size,
+		 void *out, unsigned int out_size)
+{
+	struct xsc_outbox_hdr *out_hdr = (struct xsc_outbox_hdr *)out;
+	struct xsc_inbox_hdr *in_hdr = (struct xsc_inbox_hdr *)in;
+	int err;
+
+	in_hdr->ver = 0;
+	if (in_hdr->ver != 0) {
+		pci_err(xdev->pdev, "recv an unexpected cmd ver = %d, opcode = %d\n",
+			be16_to_cpu(in_hdr->ver), be16_to_cpu(in_hdr->opcode));
+		WARN_ON(in_hdr->ver != 0);
+	}
+
+	err = _xsc_cmd_exec(xdev, in, in_size, out, out_size);
+	if (err)
+		return err;
+	if (out_hdr->status) {
+		err = -EIO;
+		return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_cmd_exec);
+
+static void xsc_destroy_msg_cache(struct xsc_core_device *xdev)
+{
+	struct xsc_cmd *cmd = &xdev->cmd;
+	struct xsc_cmd_msg *msg;
+	struct xsc_cmd_msg *n;
+
+	list_for_each_entry_safe(msg, n, &cmd->cache.large.head, list) {
+		list_del(&msg->list);
+		xsc_free_cmd_msg(xdev, msg);
+	}
+
+	list_for_each_entry_safe(msg, n, &cmd->cache.med.head, list) {
+		list_del(&msg->list);
+		xsc_free_cmd_msg(xdev, msg);
+	}
+}
+
+static int xsc_create_msg_cache(struct xsc_core_device *xdev)
+{
+	struct xsc_cmd *cmd = &xdev->cmd;
+	struct xsc_cmd_msg *msg;
+	int err;
+	int i;
+
+	spin_lock_init(&cmd->cache.large.lock);
+	INIT_LIST_HEAD(&cmd->cache.large.head);
+	spin_lock_init(&cmd->cache.med.lock);
+	INIT_LIST_HEAD(&cmd->cache.med.head);
+
+	for (i = 0; i < NUM_LONG_LISTS; i++) {
+		msg = xsc_alloc_cmd_msg(xdev, GFP_KERNEL, LONG_LIST_SIZE);
+		if (IS_ERR(msg)) {
+			err = PTR_ERR(msg);
+			goto err_destroy;
+		}
+		msg->cache = &cmd->cache.large;
+		list_add_tail(&msg->list, &cmd->cache.large.head);
+	}
+
+	for (i = 0; i < NUM_MED_LISTS; i++) {
+		msg = xsc_alloc_cmd_msg(xdev, GFP_KERNEL, MED_LIST_SIZE);
+		if (IS_ERR(msg)) {
+			err = PTR_ERR(msg);
+			goto err_destroy;
+		}
+		msg->cache = &cmd->cache.med;
+		list_add_tail(&msg->list, &cmd->cache.med.head);
+	}
+
+	return 0;
+
+err_destroy:
+	xsc_destroy_msg_cache(xdev);
+	return err;
+}
+
+static void xsc_cmd_comp_handler(struct xsc_core_device *xdev, u8 idx,
+				 struct xsc_rsp_layout *rsp)
+{
+	struct xsc_cmd *cmd = &xdev->cmd;
+	struct xsc_cmd_work_ent *ent;
+
+	if (idx > cmd->max_reg_cmds || (cmd->cmd_entry_mask & (1 << idx))) {
+		pci_err(xdev->pdev, "idx[%d] exceed max cmds, or has no relative request.\n",
+			idx);
+		return;
+	}
+	ent = cmd->ent_arr[idx];
+	ent->rsp_lay = rsp;
+
+	memcpy(ent->out->first.data,
+	       ent->rsp_lay->out,
+	       sizeof(ent->rsp_lay->out));
+	if (!cmd->checksum_disabled)
+		ent->ret = xsc_verify_signature(ent);
+	else
+		ent->ret = 0;
+	ent->status = 0;
+
+	xsc_free_ent(cmd, ent->idx);
+	complete(&ent->done);
+	up(&cmd->sem);
+}
+
+static int xsc_cmd_cq_polling(void *data)
+{
+	struct xsc_core_device *xdev = data;
+	struct xsc_cmd *cmd = &xdev->cmd;
+	struct xsc_rsp_layout *rsp;
+	u32 cq_pid;
+
+	while (!kthread_should_stop()) {
+		if (need_resched())
+			schedule();
+		cq_pid = xsc_get_cmdq_rsp_pid(xdev);
+		if (cmd->cq_cid == cq_pid) {
+			mdelay(3);
+			continue;
+		}
+
+		rsp = xsc_get_cq_inst(cmd, cmd->cq_cid);
+		if (!cmd->ownerbit_learned) {
+			cmd->ownerbit_learned = 1;
+			cmd->owner_bit = rsp->owner_bit;
+		}
+		if (cmd->owner_bit != rsp->owner_bit) {
+			pci_err(xdev->pdev, "hw update cq doorbell but buf not ready %u %u\n",
+				cmd->cq_cid, cq_pid);
+			continue;
+		}
+
+		xsc_cmd_comp_handler(xdev, rsp->idx, rsp);
+
+		cmd->cq_cid = (cmd->cq_cid + 1) % (1 << cmd->log_sz);
+
+		xsc_update_cmdq_rsp_cid(xdev, cmd->cq_cid);
+		if (cmd->cq_cid == 0)
+			cmd->owner_bit = !cmd->owner_bit;
+	}
+
+	return 0;
+}
+
+int xsc_cmd_err_handler(struct xsc_core_device *xdev)
+{
+	return xsc_handle_cmdq_interrupt(xdev);
+}
+
+void xsc_cmd_resp_handler(struct xsc_core_device *xdev)
+{
+	struct xsc_cmd *cmd = &xdev->cmd;
+	struct xsc_rsp_layout *rsp;
+	const int budget = 32;
+	int count = 0;
+	u32 cq_pid;
+
+	while (count < budget) {
+		cq_pid = xsc_get_cmdq_rsp_pid(xdev);
+		if (cq_pid == cmd->cq_cid)
+			return;
+
+		rsp = xsc_get_cq_inst(cmd, cmd->cq_cid);
+		if (!cmd->ownerbit_learned) {
+			cmd->ownerbit_learned = 1;
+			cmd->owner_bit = rsp->owner_bit;
+		}
+		if (cmd->owner_bit != rsp->owner_bit) {
+			pci_err(xdev->pdev, "hw update cq doorbell but buf not ready %u %u\n",
+				cmd->cq_cid, cq_pid);
+			return;
+		}
+
+		xsc_cmd_comp_handler(xdev, rsp->idx, rsp);
+
+		cmd->cq_cid = (cmd->cq_cid + 1) % (1 << cmd->log_sz);
+		xsc_update_cmdq_rsp_cid(xdev, cmd->cq_cid);
+		if (cmd->cq_cid == 0)
+			cmd->owner_bit = !cmd->owner_bit;
+
+		count++;
+	}
+}
+
+static void xsc_cmd_handle_rsp_before_reload
+(struct xsc_cmd *cmd, struct xsc_core_device *xdev)
+{
+	u32 rsp_pid, rsp_cid;
+
+	rsp_pid = xsc_get_cmdq_rsp_pid(xdev);
+	rsp_cid = xsc_get_cmdq_rsp_cid(xdev);
+	if (rsp_pid == rsp_cid)
+		return;
+
+	cmd->cq_cid = rsp_pid;
+
+	xsc_update_cmdq_rsp_cid(xdev, cmd->cq_cid);
+}
+
+int xsc_cmd_init(struct xsc_core_device *xdev)
+{
+	unsigned int size = sizeof(struct xsc_cmd_prot_block);
+	int align = roundup_pow_of_two(size);
+	struct xsc_cmd *cmd = &xdev->cmd;
+	u32 cmd_h, cmd_l;
+	int err;
+
+	cmd->pool = dma_pool_create("xsc_cmd",
+				    &xdev->pdev->dev,
+				    size, align, 0);
+	if (!cmd->pool)
+		return -ENOMEM;
+
+	cmd->cmd_buf = (void *)__get_free_pages(GFP_ATOMIC, 0);
+	if (!cmd->cmd_buf) {
+		err = -ENOMEM;
+		goto err_free_pool;
+	}
+	cmd->cq_buf = (void *)__get_free_pages(GFP_ATOMIC, 0);
+	if (!cmd->cq_buf) {
+		err = -ENOMEM;
+		goto err_free_cmd;
+	}
+
+	memset(cmd->cmd_buf, 0, PAGE_SIZE);
+	memset(cmd->cq_buf, 0, PAGE_SIZE);
+
+	cmd->dma = dma_map_single(&xdev->pdev->dev, cmd->cmd_buf, PAGE_SIZE,
+				  DMA_BIDIRECTIONAL);
+	if (dma_mapping_error(&xdev->pdev->dev, cmd->dma)) {
+		err = -ENOMEM;
+		goto err_free_cq;
+	}
+
+	cmd->cq_dma = dma_map_single(&xdev->pdev->dev, cmd->cq_buf, PAGE_SIZE,
+				     DMA_BIDIRECTIONAL);
+	if (dma_mapping_error(&xdev->pdev->dev, cmd->cq_dma)) {
+		err = -ENOMEM;
+		goto err_unmap_cmd;
+	}
+
+	cmd->cmd_pid = xsc_get_cmdq_req_pid(xdev);
+	cmd->cq_cid = xsc_get_cmdq_rsp_cid(xdev);
+	cmd->ownerbit_learned = 0;
+
+	xsc_cmd_handle_rsp_before_reload(cmd, xdev);
+
+#define ELEMENT_SIZE_LOG	6 /* 64B */
+#define Q_DEPTH_LOG		5 /* 32 */
+
+	cmd->log_sz = Q_DEPTH_LOG;
+	cmd->log_stride = ELEMENT_SIZE_LOG;
+
+	if (1 << cmd->log_sz > XSC_MAX_COMMANDS) {
+		dev_err(&xdev->pdev->dev, "firmware reports too many outstanding commands %d\n",
+			1 << cmd->log_sz);
+		err = -EINVAL;
+		goto err_unmap_cq;
+	}
+
+	if (cmd->log_sz + cmd->log_stride > PAGE_SHIFT) {
+		dev_err(&xdev->pdev->dev, "command queue size overflow\n");
+		err = -EINVAL;
+		goto err_unmap_cq;
+	}
+
+	cmd->checksum_disabled = 1;
+	cmd->max_reg_cmds = (1 << cmd->log_sz) - 1;
+	cmd->cmd_entry_mask = (1 << cmd->max_reg_cmds) - 1;
+
+	spin_lock_init(&cmd->alloc_lock);
+	spin_lock_init(&cmd->token_lock);
+	spin_lock_init(&cmd->doorbell_lock);
+
+	sema_init(&cmd->sem, cmd->max_reg_cmds);
+
+	cmd_h = upper_32_bits(cmd->dma);
+	cmd_l = lower_32_bits(cmd->dma);
+	if (cmd_l & 0xfff) {
+		dev_err(&xdev->pdev->dev, "invalid command queue address\n");
+		err = -ENOMEM;
+		goto err_unmap_cq;
+	}
+
+	xsc_set_cmdq_req_buf_addr(xdev, cmd_h, cmd_l);
+
+	cmd_h = upper_32_bits(cmd->cq_dma);
+	cmd_l = lower_32_bits(cmd->cq_dma);
+	if (cmd_l & 0xfff) {
+		dev_err(&xdev->pdev->dev, "invalid command queue address\n");
+		err = -ENOMEM;
+		goto err_unmap_cq;
+	}
+	xsc_set_cmdq_rsp_buf_addr(xdev, cmd_h, cmd_l);
+
+	/* Make sure firmware sees the complete address before we proceed */
+	wmb();
+
+	cmd->mode = XSC_CMD_MODE_POLLING;
+	cmd->cmd_status = XSC_CMD_STATUS_NORMAL;
+
+	err = xsc_create_msg_cache(xdev);
+	if (err) {
+		dev_err(&xdev->pdev->dev, "failed to create command cache\n");
+		goto err_unmap_cq;
+	}
+
+	xsc_set_wqname(xdev);
+	cmd->wq = create_singlethread_workqueue(cmd->wq_name);
+	if (!cmd->wq) {
+		dev_err(&xdev->pdev->dev, "failed to create command workqueue\n");
+		err = -ENOMEM;
+		goto err_destroy_cache;
+	}
+
+	cmd->cq_task = kthread_create(xsc_cmd_cq_polling,
+				      (void *)xdev,
+				      "xsc_cmd_cq_polling");
+	if (!cmd->cq_task) {
+		dev_err(&xdev->pdev->dev, "failed to create cq task\n");
+		err = -ENOMEM;
+		goto err_destroy_wq;
+	}
+	wake_up_process(cmd->cq_task);
+
+	/* clear abnormal state to avoid the impact of previous error */
+	xsc_check_cmdq_status(xdev);
+
+	return 0;
+
+err_destroy_wq:
+	destroy_workqueue(cmd->wq);
+
+err_destroy_cache:
+	xsc_destroy_msg_cache(xdev);
+
+err_unmap_cq:
+	dma_unmap_single(&xdev->pdev->dev, cmd->cq_dma, PAGE_SIZE,
+			 DMA_BIDIRECTIONAL);
+
+err_unmap_cmd:
+	dma_unmap_single(&xdev->pdev->dev, cmd->dma, PAGE_SIZE,
+			 DMA_BIDIRECTIONAL);
+err_free_cq:
+	free_pages((unsigned long)cmd->cq_buf, 0);
+
+err_free_cmd:
+	free_pages((unsigned long)cmd->cmd_buf, 0);
+
+err_free_pool:
+	dma_pool_destroy(cmd->pool);
+
+	return err;
+}
+
+void xsc_cmd_cleanup(struct xsc_core_device *xdev)
+{
+	struct xsc_cmd *cmd = &xdev->cmd;
+
+	destroy_workqueue(cmd->wq);
+	if (cmd->cq_task)
+		kthread_stop(cmd->cq_task);
+	xsc_destroy_msg_cache(xdev);
+	dma_unmap_single(&xdev->pdev->dev, cmd->dma, PAGE_SIZE,
+			 DMA_BIDIRECTIONAL);
+	free_pages((unsigned long)cmd->cq_buf, 0);
+	dma_unmap_single(&xdev->pdev->dev, cmd->cq_dma, PAGE_SIZE,
+			 DMA_BIDIRECTIONAL);
+	free_pages((unsigned long)cmd->cmd_buf, 0);
+	dma_pool_destroy(cmd->pool);
+}
+
+int xsc_cmd_version_check(struct xsc_core_device *xdev)
+{
+	struct xsc_cmd_query_cmdq_ver_mbox_out *out;
+	struct xsc_cmd_query_cmdq_ver_mbox_in in;
+
+	int err;
+
+	out = kzalloc_obj(*out, GFP_KERNEL);
+	if (!out)
+		return -ENOMEM;
+
+	memset(&in, 0, sizeof(in));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_QUERY_CMDQ_VERSION);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), out, sizeof(*out));
+	if (err)
+		goto err_free;
+
+	if (be16_to_cpu(out->cmdq_ver) != XSC_CMDQ_VERSION) {
+		pci_err(xdev->pdev, "cmdq version check failed, expecting %d, actual %d\n",
+			XSC_CMDQ_VERSION, be16_to_cpu(out->cmdq_ver));
+		err = -EINVAL;
+		goto err_free;
+	}
+	xdev->cmdq_ver = XSC_CMDQ_VERSION;
+
+err_free:
+	kfree(out);
+	return err;
+}
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/hw.c b/drivers/net/ethernet/yunsilicon/xsc/pci/hw.c
new file mode 100644
index 000000000..321e236e8
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/hw.c
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+
+#include "common/xsc_cmd_api.h"
+#include "hw.h"
+#include "hal/xsc_hal.h"
+
+#define MAX_BOARD_NUM	32
+
+static struct xsc_board_info *board_info[MAX_BOARD_NUM];
+
+static struct xsc_board_info *xsc_get_board_info(char *board_sn)
+{
+	int i;
+
+	for (i = 0; i < MAX_BOARD_NUM; i++) {
+		if (!board_info[i])
+			continue;
+		if (!strncmp(board_info[i]->board_sn, board_sn,
+			     XSC_BOARD_SN_LEN))
+			return board_info[i];
+	}
+
+	return NULL;
+}
+
+static struct xsc_board_info *xsc_alloc_board_info(void)
+{
+	int i;
+
+	for (i = 0; i < MAX_BOARD_NUM; i++) {
+		if (!board_info[i])
+			break;
+	}
+	if (i == MAX_BOARD_NUM)
+		return NULL;
+	board_info[i] = vzalloc(sizeof(*board_info[i]));
+	if (!board_info[i])
+		return NULL;
+	board_info[i]->board_id = i;
+	return board_info[i];
+}
+
+void xsc_free_board_info(void)
+{
+	int i;
+
+	for (i = 0; i < MAX_BOARD_NUM; i++)
+		vfree(board_info[i]);
+}
+
+int xsc_cmd_query_hca_cap(struct xsc_core_device *xdev,
+			  struct xsc_caps *caps)
+{
+	struct xsc_cmd_query_hca_cap_mbox_out *out;
+	struct xsc_cmd_query_hca_cap_mbox_in in;
+	struct xsc_board_info *board_info;
+	int err;
+	u16 t16;
+
+	out = kzalloc_obj(*out, GFP_KERNEL);
+	if (!out)
+		return -ENOMEM;
+
+	memset(&in, 0, sizeof(in));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_QUERY_HCA_CAP);
+	in.cpu_num = cpu_to_be16(num_online_cpus());
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), out, sizeof(*out));
+	if (err)
+		goto err_free;
+
+	xdev->glb_func_id = be32_to_cpu(out->hca_cap.glb_func_id);
+	caps->pcie0_pf_funcid_base =
+		be16_to_cpu(out->hca_cap.pcie0_pf_funcid_base);
+	caps->pcie0_pf_funcid_top =
+		be16_to_cpu(out->hca_cap.pcie0_pf_funcid_top);
+	caps->pcie1_pf_funcid_base =
+		be16_to_cpu(out->hca_cap.pcie1_pf_funcid_base);
+	caps->pcie1_pf_funcid_top =
+		be16_to_cpu(out->hca_cap.pcie1_pf_funcid_top);
+	caps->funcid_to_logic_port =
+		be16_to_cpu(out->hca_cap.funcid_to_logic_port);
+
+	caps->pcie_host = out->hca_cap.pcie_host;
+	caps->nif_port_num = out->hca_cap.nif_port_num;
+	caps->hw_feature_flag = be32_to_cpu(out->hca_cap.hw_feature_flag);
+
+	caps->raweth_qp_id_base = be16_to_cpu(out->hca_cap.raweth_qp_id_base);
+	caps->raweth_qp_id_end = be16_to_cpu(out->hca_cap.raweth_qp_id_end);
+	caps->raweth_rss_qp_id_base =
+		be16_to_cpu(out->hca_cap.raweth_rss_qp_id_base);
+	caps->raw_tpe_qp_num = be16_to_cpu(out->hca_cap.raw_tpe_qp_num);
+	caps->max_cqes = 1 << out->hca_cap.log_max_cq_sz;
+	caps->max_wqes = 1 << out->hca_cap.log_max_qp_sz;
+	caps->max_sq_desc_sz = be16_to_cpu(out->hca_cap.max_desc_sz_sq);
+	caps->max_rq_desc_sz = be16_to_cpu(out->hca_cap.max_desc_sz_rq);
+	caps->flags = be64_to_cpu(out->hca_cap.flags);
+	caps->stat_rate_support = be16_to_cpu(out->hca_cap.stat_rate_support);
+	caps->log_max_msg = out->hca_cap.log_max_msg & 0x1f;
+	caps->num_ports = out->hca_cap.num_ports & 0xf;
+	caps->log_max_cq = out->hca_cap.log_max_cq & 0x1f;
+	caps->log_max_eq = out->hca_cap.log_max_eq & 0xf;
+	caps->log_max_msix = out->hca_cap.log_max_msix & 0xf;
+	caps->mac_port = out->hca_cap.mac_port & 0xff;
+	xdev->mac_port = caps->mac_port;
+	if (caps->num_ports > XSC_MAX_FW_PORTS) {
+		pci_err(xdev->pdev, "device has %d ports while the driver supports max %d ports\n",
+			caps->num_ports, XSC_MAX_FW_PORTS);
+		err = -EINVAL;
+		goto err_free;
+	}
+	caps->send_ds_num = out->hca_cap.send_seg_num;
+	caps->send_wqe_shift = out->hca_cap.send_wqe_shift;
+	caps->recv_ds_num = out->hca_cap.recv_seg_num;
+	caps->recv_wqe_shift = out->hca_cap.recv_wqe_shift;
+
+	caps->embedded_cpu = 0;
+	caps->ecpf_vport_exists = 0;
+	caps->log_max_current_uc_list = 0;
+	caps->log_max_current_mc_list = 0;
+	caps->log_max_vlan_list = 8;
+	caps->log_max_qp = out->hca_cap.log_max_qp & 0x1f;
+	caps->log_max_mkey = out->hca_cap.log_max_mkey & 0x3f;
+	caps->log_max_pd = out->hca_cap.log_max_pd & 0x1f;
+	caps->log_max_srq = out->hca_cap.log_max_srqs & 0x1f;
+	caps->local_ca_ack_delay = out->hca_cap.local_ca_ack_delay & 0x1f;
+	caps->log_max_mcg = out->hca_cap.log_max_mcg;
+	caps->log_max_mtt = out->hca_cap.log_max_mtt;
+	caps->log_max_tso = out->hca_cap.log_max_tso;
+	caps->hca_core_clock = be32_to_cpu(out->hca_cap.hca_core_clock);
+	caps->max_rwq_indirection_tables =
+		be32_to_cpu(out->hca_cap.max_rwq_indirection_tables);
+	caps->max_rwq_indirection_table_size =
+		be32_to_cpu(out->hca_cap.max_rwq_indirection_table_size);
+	caps->max_qp_mcg = be16_to_cpu(out->hca_cap.max_qp_mcg);
+	caps->max_ra_res_qp = 1 << (out->hca_cap.log_max_ra_res_qp & 0x3f);
+	caps->max_ra_req_qp = 1 << (out->hca_cap.log_max_ra_req_qp & 0x3f);
+	caps->max_srq_wqes = 1 << out->hca_cap.log_max_srq_sz;
+	caps->rx_pkt_len_max = be32_to_cpu(out->hca_cap.rx_pkt_len_max);
+	caps->max_vfs = be16_to_cpu(out->hca_cap.max_vfs);
+	caps->qp_rate_limit_min = be32_to_cpu(out->hca_cap.qp_rate_limit_min);
+	caps->qp_rate_limit_max = be32_to_cpu(out->hca_cap.qp_rate_limit_max);
+
+	caps->msix_enable = 1;
+	caps->msix_base = be16_to_cpu(out->hca_cap.msix_base);
+	caps->msix_num = be16_to_cpu(out->hca_cap.msix_num);
+
+	t16 = be16_to_cpu(out->hca_cap.bf_log_bf_reg_size);
+	if (t16 & 0x8000) {
+		caps->bf_reg_size = 1 << (t16 & 0x1f);
+		caps->bf_regs_per_page = XSC_BF_REGS_PER_PAGE;
+	} else {
+		caps->bf_reg_size = 0;
+		caps->bf_regs_per_page = 0;
+	}
+	caps->min_page_sz = ~(u32)((1 << PAGE_SHIFT) - 1);
+
+	caps->dcbx = 1;
+	caps->qos = 1;
+	caps->ets = 1;
+	caps->dscp = 1;
+	caps->max_tc = out->hca_cap.max_tc;
+	caps->log_max_qp_depth = out->hca_cap.log_max_qp_depth & 0xff;
+	caps->mac_bit = out->hca_cap.mac_bit;
+	caps->lag_logic_port_ofst = out->hca_cap.lag_logic_port_ofst;
+
+	caps->max_qp = be32_to_cpu(out->hca_cap.max_qp);
+	caps->max_cq = be32_to_cpu(out->hca_cap.max_cq);
+	caps->max_pd = be32_to_cpu(out->hca_cap.max_pd);
+
+	if (be16_to_cpu(out->hdr.ver) >= CMD_QUERY_HCA_CAP_V2)
+		caps->max_mtt = be32_to_cpu(out->hca_cap.max_mtt);
+	else
+		caps->max_mtt = 1 << (out->hca_cap.log_max_mtt & 0x3f);
+
+	if (be16_to_cpu(out->hdr.ver) >= CMD_QUERY_HCA_CAP_V1) {
+		caps->max_mr_size = be64_to_cpu(out->hca_cap.max_mr_size);
+		caps->max_cmd_in_len = be16_to_cpu(out->hca_cap.max_cmd_in_len);
+		caps->max_cmd_out_len = be16_to_cpu(out->hca_cap.max_cmd_out_len);
+	} else {
+		caps->max_mr_size = (1ull << 32) - 1;
+		caps->max_cmd_in_len = 10240;
+		caps->max_cmd_out_len = 2048;
+	}
+
+	if (be16_to_cpu(out->hdr.ver) >= CMD_QUERY_HCA_CAP_V3) {
+		caps->mpt_tbl_addr = be32_to_cpu(out->hca_cap.mpt_tbl_addr);
+		caps->mpt_tbl_depth = be32_to_cpu(out->hca_cap.mpt_tbl_depth);
+		caps->mpt_tbl_width = be32_to_cpu(out->hca_cap.mpt_tbl_width);
+		caps->mtt_inst_base_addr = be32_to_cpu(out->hca_cap.mtt_inst_base_addr);
+		caps->mtt_inst_stride = be32_to_cpu(out->hca_cap.mtt_inst_stride);
+		caps->mtt_inst_num_log = be32_to_cpu(out->hca_cap.mtt_inst_num_log);
+		caps->mtt_inst_depth = be32_to_cpu(out->hca_cap.mtt_inst_depth);
+		xsc_set_mtt_info(xdev);
+	}
+
+	xdev->pcie_no = out->hca_cap.pcie_no;
+	xdev->pf_id = out->hca_cap.pf_id;
+
+	if (be16_to_cpu(out->hdr.ver) >= CMD_QUERY_HCA_CAP_V9) {
+		caps->mdb_base = be64_to_cpu(out->hca_cap.mdb_base);
+		caps->mdb_num = be32_to_cpu(out->hca_cap.mdb_num);
+	}
+
+	xdev->chip_ver_h = be32_to_cpu(out->hca_cap.chip_ver_h);
+	xdev->chip_ver_m = be32_to_cpu(out->hca_cap.chip_ver_m);
+	xdev->chip_ver_l = be32_to_cpu(out->hca_cap.chip_ver_l);
+	xdev->hotfix_num = be32_to_cpu(out->hca_cap.hotfix_num);
+	xdev->feature_flag = be32_to_cpu(out->hca_cap.feature_flag);
+
+	board_info = xsc_get_board_info(out->hca_cap.board_sn);
+	if (!board_info) {
+		board_info = xsc_alloc_board_info();
+		if (!board_info) {
+			err = -ENOMEM;
+			goto err_free;
+		}
+
+		memcpy(board_info->board_sn,
+		       out->hca_cap.board_sn,
+		       sizeof(out->hca_cap.board_sn));
+	}
+
+	xdev->board_info = board_info;
+
+	xsc_set_pf_db_addr(xdev, be64_to_cpu(out->hca_cap.tx_db),
+			   be64_to_cpu(out->hca_cap.rx_db),
+			   be64_to_cpu(out->hca_cap.complete_db),
+			   be64_to_cpu(out->hca_cap.complete_reg),
+			   be64_to_cpu(out->hca_cap.event_db));
+
+	xdev->fw_version_major = out->hca_cap.fw_ver.fw_version_major;
+	xdev->fw_version_minor = out->hca_cap.fw_ver.fw_version_minor;
+	xdev->fw_version_patch =
+		be16_to_cpu(out->hca_cap.fw_ver.fw_version_patch);
+	xdev->fw_version_tweak =
+		be32_to_cpu(out->hca_cap.fw_ver.fw_version_tweak);
+	xdev->fw_version_extra_flag = out->hca_cap.fw_ver.fw_version_extra_flag;
+
+	kfree(out);
+	return 0;
+err_free:
+	kfree(out);
+	return err;
+}
+
+static int xsc_cmd_query_guid(struct xsc_core_device *xdev)
+{
+	struct xsc_cmd_query_guid_mbox_out out;
+	struct xsc_cmd_query_guid_mbox_in in = {};
+	int err;
+
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_QUERY_GUID);
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err)
+		return err;
+
+	xdev->board_info->guid = out.guid;
+	xdev->board_info->guid_valid = 1;
+	return 0;
+}
+
+int xsc_query_guid(struct xsc_core_device *xdev)
+{
+	if (xdev->board_info->guid_valid)
+		return 0;
+
+	return xsc_cmd_query_guid(xdev);
+}
+
+static int xsc_cmd_activate_hw_config(struct xsc_core_device *xdev)
+{
+	struct xsc_cmd_activate_hw_config_mbox_out out;
+	struct xsc_cmd_activate_hw_config_mbox_in in = {};
+	int err = 0;
+
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_ACTIVATE_HW_CONFIG);
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err)
+		return err;
+
+	xdev->board_info->hw_config_activated = 1;
+	return 0;
+}
+
+int xsc_activate_hw_config(struct xsc_core_device *xdev)
+{
+	if (xdev->board_info->hw_config_activated)
+		return 0;
+
+	return xsc_cmd_activate_hw_config(xdev);
+}
+
+int xsc_reset_function_resource(struct xsc_core_device *xdev)
+{
+	struct xsc_function_reset_mbox_out out;
+	struct xsc_function_reset_mbox_in in;
+
+	memset(&in, 0, sizeof(in));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_FUNCTION_RESET);
+	in.glb_func_id = cpu_to_be16(xdev->glb_func_id);
+
+	return xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+}
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/hw.h b/drivers/net/ethernet/yunsilicon/xsc/pci/hw.h
new file mode 100644
index 000000000..5843a72c8
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/hw.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_HW_H
+#define __XSC_HW_H
+
+#include "common/xsc_core.h"
+
+void xsc_free_board_info(void);
+int xsc_cmd_query_hca_cap(struct xsc_core_device *xdev,
+			  struct xsc_caps *caps);
+int xsc_query_guid(struct xsc_core_device *xdev);
+int xsc_activate_hw_config(struct xsc_core_device *xdev);
+int xsc_reset_function_resource(struct xsc_core_device *xdev);
+
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/main.c b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
index 35dff401a..79acd36df 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
@@ -4,6 +4,8 @@
  */
 
 #include "common/xsc_core.h"
+#include "common/xsc_cmd_api.h"
+#include "hw.h"
 #include "hal/xsc_hal.h"
 
 static const struct pci_device_id xsc_pci_id_table[] = {
@@ -141,11 +143,63 @@ static void xsc_core_dev_cleanup(struct xsc_core_device *xdev)
 
 static int xsc_hw_setup(struct xsc_core_device *xdev)
 {
+	int err;
+
+	err = xsc_cmd_init(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "Failed initializing command interface, aborting\n");
+		goto err_out;
+	}
+
+	err = xsc_cmd_version_check(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "Failed to check cmdq version\n");
+		goto err_cmd_cleanup;
+	}
+
+	err = xsc_cmd_query_hca_cap(xdev, &xdev->caps);
+	if (err) {
+		pci_err(xdev->pdev, "Failed to query hca, err=%d\n", err);
+		goto err_cmd_cleanup;
+	}
+
+	err = xsc_query_guid(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "failed to query guid, err=%d\n", err);
+		goto err_cmd_cleanup;
+	}
+
+	err = xsc_activate_hw_config(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "failed to activate hw config, err=%d\n",
+			err);
+		goto err_cmd_cleanup;
+	}
+
+	err = xsc_reset_function_resource(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "Failed to reset function resource\n");
+		goto err_cmd_cleanup;
+	}
+
+	/* Required by the rdma client, which is probed right after this. */
+	xdev->board_info->msg_opcode = xsc_get_msg_opcode(xdev);
+	if (!xdev->board_info->msg_opcode) {
+		pci_err(xdev->pdev, "failed to get msg opcode table\n");
+		err = -ENODEV;
+		goto err_cmd_cleanup;
+	}
+
 	return 0;
+err_cmd_cleanup:
+	xsc_cmd_cleanup(xdev);
+err_out:
+	return err;
 }
 
 static void xsc_hw_cleanup(struct xsc_core_device *xdev)
 {
+	xsc_cmd_cleanup(xdev);
 }
 
 static int xsc_load(struct xsc_core_device *xdev)
@@ -247,6 +301,7 @@ static int __init xsc_init(void)
 static void __exit xsc_fini(void)
 {
 	pci_unregister_driver(&xsc_pci_driver);
+	xsc_free_board_info();
 }
 
 module_init(xsc_init);

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

* [PATCH 04/15] yunsilicon: add event queues, interrupts and the
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (2 preceding siblings ...)
  2026-10-12 11:43 ` [PATCH 03/15] yunsilicon: add the command queue Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 05/15] yunsilicon: add the CQ and QP resource pools Xin Tian
                   ` (11 subsequent siblings)
  15 siblings, 1 reply; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

auxiliary bus

Add the event queues, the MSI-X vector handling and the auxiliary bus
glue: the PCI core requests its vectors, creates one EQ per completion
vector and publishes one auxiliary device per function so that the
ethernet and RDMA drivers can bind to it.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 .../net/ethernet/yunsilicon/xsc/pci/Makefile  |   2 +-
 .../net/ethernet/yunsilicon/xsc/pci/adev.c    | 142 +++++
 .../net/ethernet/yunsilicon/xsc/pci/adev.h    |  17 +
 .../net/ethernet/yunsilicon/xsc/pci/alloc.c   | 237 +++++++++
 .../net/ethernet/yunsilicon/xsc/pci/alloc.h   |  17 +
 drivers/net/ethernet/yunsilicon/xsc/pci/cq.h  |  17 +
 drivers/net/ethernet/yunsilicon/xsc/pci/eq.c  | 269 ++++++++++
 drivers/net/ethernet/yunsilicon/xsc/pci/eq.h  |  39 ++
 .../net/ethernet/yunsilicon/xsc/pci/main.c    |  25 +
 .../net/ethernet/yunsilicon/xsc/pci/pci_irq.c | 498 ++++++++++++++++++
 .../net/ethernet/yunsilicon/xsc/pci/pci_irq.h |  18 +
 drivers/net/ethernet/yunsilicon/xsc/pci/qp.h  |  28 +
 12 files changed, 1308 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/adev.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/adev.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/alloc.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/alloc.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/cq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/eq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/eq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/qp.h

diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
index 656cdffa1..e264990f9 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
@@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
 
-xsc_pci-y := main.o cmdq.o hw.o hal/andes_impl.o
+xsc_pci-y := main.o cmdq.o hw.o alloc.o eq.o pci_irq.o adev.o hal/andes_impl.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/adev.c b/drivers/net/ethernet/yunsilicon/xsc/pci/adev.c
new file mode 100644
index 000000000..c8e8f1a9f
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/adev.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/idr.h>
+
+#include "adev.h"
+
+static DEFINE_IDA(xsc_adev_ida);
+
+static const char * const xsc_adev_name[] = {
+	[XSC_ADEV_IDX_ETH] = XSC_ETH_ADEV_NAME,
+	[XSC_ADEV_IDX_IB]  = XSC_IB_ADEV_NAME,
+};
+
+static void xsc_release_adev(struct device *dev)
+{
+	struct xsc_adev *xsc_adev =
+		container_of(dev, struct xsc_adev, adev.dev);
+
+	kfree(xsc_adev);
+}
+
+int xsc_adev_add(struct xsc_core_device *xdev, int idx)
+{
+	struct auxiliary_device	*adev;
+	struct xsc_adev *xsc_adev;
+	int ret;
+
+	if (!xdev->xsc_adev_list)
+		return -ENODEV;
+
+	if (xdev->xsc_adev_list[idx])
+		return 0;
+
+	xsc_adev = kzalloc_obj(*xsc_adev, GFP_KERNEL);
+	if (!xsc_adev)
+		return -ENOMEM;
+
+	adev = &xsc_adev->adev;
+	adev->name = xsc_adev_name[idx];
+	adev->id = xdev->adev_id;
+	adev->dev.parent = &xdev->pdev->dev;
+	adev->dev.release = xsc_release_adev;
+	xsc_adev->xdev = xdev;
+	xsc_adev->idx = idx;
+
+	ret = auxiliary_device_init(adev);
+	if (ret) {
+		kfree(xsc_adev);
+		return ret;
+	}
+
+	ret = auxiliary_device_add(adev);
+	if (ret) {
+		auxiliary_device_uninit(adev);
+		return ret;
+	}
+
+	xdev->xsc_adev_list[idx] = xsc_adev;
+
+	return 0;
+}
+
+void xsc_adev_del(struct xsc_core_device *xdev, int idx)
+{
+	struct xsc_adev *xsc_adev;
+	struct auxiliary_device *adev;
+
+	if (!xdev->xsc_adev_list)
+		return;
+
+	xsc_adev = xdev->xsc_adev_list[idx];
+	if (!xsc_adev)
+		return;
+
+	/* Clear the slot before teardown: the driver's remove callback runs
+	 * synchronously inside auxiliary_device_delete() and may re-enter
+	 * xsc_adev_add() for this index.
+	 */
+	xdev->xsc_adev_list[idx] = NULL;
+
+	adev = &xsc_adev->adev;
+	auxiliary_device_delete(adev);
+	auxiliary_device_uninit(adev);
+}
+
+int xsc_adev_init(struct xsc_core_device *xdev)
+{
+	struct xsc_adev **xsc_adev_list;
+	int adev_id;
+	int ret;
+
+	xsc_adev_list = kcalloc(ARRAY_SIZE(xsc_adev_name),
+				sizeof(struct xsc_adev *),
+				GFP_KERNEL);
+	if (!xsc_adev_list)
+		return -ENOMEM;
+	xdev->xsc_adev_list = xsc_adev_list;
+
+	adev_id = ida_alloc(&xsc_adev_ida, GFP_KERNEL);
+	if (adev_id < 0) {
+		ret = adev_id;
+		goto err_free_adev_list;
+	}
+	xdev->adev_id = adev_id;
+
+	ret = xsc_adev_add(xdev, XSC_ADEV_IDX_ETH);
+	if (ret)
+		goto err_dalloc_adev_id;
+
+	ret = xsc_adev_add(xdev, XSC_ADEV_IDX_IB);
+	if (ret)
+		goto err_unreg_eth_adev;
+
+	return 0;
+
+err_unreg_eth_adev:
+	xsc_adev_del(xdev, XSC_ADEV_IDX_ETH);
+err_dalloc_adev_id:
+	ida_free(&xsc_adev_ida, xdev->adev_id);
+err_free_adev_list:
+	kfree(xsc_adev_list);
+	xdev->xsc_adev_list = NULL;
+
+	return ret;
+}
+
+void xsc_adev_uninit(struct xsc_core_device *xdev)
+{
+	if (!xdev->xsc_adev_list)
+		return;
+
+	xsc_adev_del(xdev, XSC_ADEV_IDX_IB);
+	xsc_adev_del(xdev, XSC_ADEV_IDX_ETH);
+	ida_free(&xsc_adev_ida, xdev->adev_id);
+	kfree(xdev->xsc_adev_list);
+	xdev->xsc_adev_list = NULL;
+}
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/adev.h b/drivers/net/ethernet/yunsilicon/xsc/pci/adev.h
new file mode 100644
index 000000000..fb5789ec4
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/adev.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_ADEV_H
+#define __XSC_ADEV_H
+
+#include "common/xsc_core.h"
+
+int xsc_adev_init(struct xsc_core_device *xdev);
+void xsc_adev_uninit(struct xsc_core_device *xdev);
+
+int xsc_adev_add(struct xsc_core_device *xdev, int idx);
+void xsc_adev_del(struct xsc_core_device *xdev, int idx);
+
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/alloc.c b/drivers/net/ethernet/yunsilicon/xsc/pci/alloc.c
new file mode 100644
index 000000000..70176797a
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/alloc.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/export.h>
+#include <linux/bitmap.h>
+#include <linux/dma-mapping.h>
+#include <linux/vmalloc.h>
+
+#include "alloc.h"
+
+/* Handling for queue buffers -- we allocate a bunch of memory and
+ * register it in a memory region at HCA virtual address 0.  If the
+ * requested size is > max_direct, we split the allocation into
+ * multiple pages, so we don't require too much contiguous memory.
+ */
+int xsc_buf_alloc(struct xsc_core_device *xdev,
+		  unsigned long size,
+		  unsigned long max_direct,
+		  struct xsc_buf *buf)
+{
+	dma_addr_t t;
+
+	buf->size = size;
+	if (size <= max_direct) {
+		buf->nbufs        = 1;
+		buf->npages       = 1;
+		buf->page_shift   = get_order(size) + PAGE_SHIFT;
+		buf->direct.buf   = dma_alloc_coherent(&xdev->pdev->dev,
+						       size,
+						       &t,
+						       GFP_KERNEL);
+		if (!buf->direct.buf)
+			return -ENOMEM;
+
+		buf->direct.map = t;
+
+		while (t & GENMASK(buf->page_shift - 1, 0)) {
+			--buf->page_shift;
+			buf->npages *= 2;
+		}
+	} else {
+		struct page **pages;
+		int i;
+
+		buf->direct.buf  = NULL;
+		buf->nbufs       = DIV_ROUND_UP(size, PAGE_SIZE);
+		buf->npages      = buf->nbufs;
+		buf->page_shift  = PAGE_SHIFT;
+		buf->page_list   = kcalloc(buf->nbufs, sizeof(*buf->page_list),
+					   GFP_KERNEL);
+		if (!buf->page_list)
+			return -ENOMEM;
+
+		for (i = 0; i < buf->nbufs; i++) {
+			buf->page_list[i].buf =
+				dma_alloc_coherent(&xdev->pdev->dev, PAGE_SIZE,
+						   &t, GFP_KERNEL);
+			if (!buf->page_list[i].buf)
+				goto err_free;
+
+			buf->page_list[i].map = t;
+		}
+
+		pages = kmalloc_array(buf->nbufs, sizeof(*pages), GFP_KERNEL);
+		if (!pages)
+			goto err_free;
+		for (i = 0; i < buf->nbufs; i++) {
+			void *addr = buf->page_list[i].buf;
+
+			if (is_vmalloc_addr(addr))
+				pages[i] = vmalloc_to_page(addr);
+			else
+				pages[i] = virt_to_page(addr);
+		}
+		buf->direct.buf = vmap(pages, buf->nbufs, VM_MAP, PAGE_KERNEL);
+		kfree(pages);
+		if (!buf->direct.buf)
+			goto err_free;
+	}
+
+	return 0;
+
+err_free:
+	xsc_buf_free(xdev, buf);
+
+	return -ENOMEM;
+}
+EXPORT_SYMBOL_GPL(xsc_buf_alloc);
+
+void xsc_buf_free(struct xsc_core_device *xdev, struct xsc_buf *buf)
+{
+	int i;
+
+	if (buf->nbufs == 1) {
+		dma_free_coherent(&xdev->pdev->dev, buf->size, buf->direct.buf,
+				  buf->direct.map);
+	} else {
+		if (BITS_PER_LONG == 64 && buf->direct.buf)
+			vunmap(buf->direct.buf);
+
+		for (i = 0; i < buf->nbufs; i++)
+			if (buf->page_list[i].buf)
+				dma_free_coherent(&xdev->pdev->dev, PAGE_SIZE,
+						  buf->page_list[i].buf,
+						  buf->page_list[i].map);
+		kfree(buf->page_list);
+	}
+}
+EXPORT_SYMBOL_GPL(xsc_buf_free);
+
+void xsc_fill_page_array(struct xsc_buf *buf, __be64 *pas, unsigned int npages)
+{
+	unsigned int shift = PAGE_SHIFT - PAGE_SHIFT_4K;
+	unsigned int mask = (1 << shift) - 1;
+	u64 addr;
+	int i;
+
+	for (i = 0; i < npages; i++) {
+		if (buf->nbufs == 1)
+			addr = buf->direct.map + (i << PAGE_SHIFT_4K);
+		else
+			addr = buf->page_list[i >> shift].map +
+			       ((i & mask) << PAGE_SHIFT_4K);
+
+		pas[i] = cpu_to_be64(addr);
+	}
+}
+EXPORT_SYMBOL_GPL(xsc_fill_page_array);
+
+void xsc_core_fill_page_frag_array(struct xsc_frag_buf *buf,
+				   __be64 *pas,
+				   unsigned int npages)
+{
+	int i;
+	dma_addr_t addr;
+	unsigned int shift = PAGE_SHIFT - PAGE_SHIFT_4K;
+	unsigned int mask = (1 << shift) - 1;
+
+	for (i = 0; i < npages; i++) {
+		addr = buf->frags[i >> shift].map +
+			((i & mask) << PAGE_SHIFT_4K);
+		pas[i] = cpu_to_be64(addr);
+	}
+}
+EXPORT_SYMBOL_GPL(xsc_core_fill_page_frag_array);
+
+static void *xsc_dma_zalloc_coherent_node(struct xsc_core_device *xdev,
+					  size_t size, dma_addr_t *dma_handle,
+					  int node)
+{
+	struct xsc_dev_resource *dev_res = xdev->dev_res;
+	struct device *device = &xdev->pdev->dev;
+	int original_node;
+	void *cpu_handle;
+
+	/* WA for kernels that don't use numa_mem_id in alloc_pages_node */
+	if (node == NUMA_NO_NODE)
+		node = numa_mem_id();
+
+	mutex_lock(&dev_res->alloc_mutex);
+	original_node = dev_to_node(device);
+	set_dev_node(device, node);
+	cpu_handle = dma_alloc_coherent(device, size, dma_handle,
+					GFP_KERNEL);
+	set_dev_node(device, original_node);
+	mutex_unlock(&dev_res->alloc_mutex);
+	return cpu_handle;
+}
+
+int xsc_core_frag_buf_alloc_node(struct xsc_core_device *xdev,
+				 unsigned long size,
+				 struct xsc_frag_buf *buf,
+				 int node)
+{
+	int i;
+
+	buf->size = size;
+	buf->npages = DIV_ROUND_UP(size, PAGE_SIZE);
+	buf->page_shift = PAGE_SHIFT;
+	buf->frags = kcalloc(buf->npages, sizeof(struct xsc_buf_list),
+			     GFP_KERNEL);
+	if (!buf->frags)
+		goto err_out;
+
+	for (i = 0; i < buf->npages; i++) {
+		unsigned long frag_sz = min_t(unsigned long, size, PAGE_SIZE);
+		struct xsc_buf_list *frag = &buf->frags[i];
+
+		frag->buf = xsc_dma_zalloc_coherent_node(xdev, frag_sz,
+							 &frag->map, node);
+		if (!frag->buf)
+			goto err_free_buf;
+		if (frag->map & ((1 << buf->page_shift) - 1)) {
+			dma_free_coherent(&xdev->pdev->dev, frag_sz,
+					  buf->frags[i].buf, buf->frags[i].map);
+			pci_err(xdev->pdev, "unexpected map alignment: %pad, page_shift=%d\n",
+				&frag->map, buf->page_shift);
+			goto err_free_buf;
+		}
+		size -= frag_sz;
+	}
+
+	return 0;
+
+err_free_buf:
+	while (i--)
+		dma_free_coherent(&xdev->pdev->dev,
+				  PAGE_SIZE,
+				  buf->frags[i].buf,
+				  buf->frags[i].map);
+	kfree(buf->frags);
+err_out:
+	return -ENOMEM;
+}
+EXPORT_SYMBOL_GPL(xsc_core_frag_buf_alloc_node);
+
+void xsc_core_frag_buf_free(struct xsc_core_device *xdev,
+			    struct xsc_frag_buf *buf)
+{
+	unsigned long size = buf->size;
+	int i;
+
+	for (i = 0; i < buf->npages; i++) {
+		unsigned long frag_sz = min_t(unsigned long, size, PAGE_SIZE);
+
+		dma_free_coherent(&xdev->pdev->dev, frag_sz, buf->frags[i].buf,
+				  buf->frags[i].map);
+		size -= frag_sz;
+	}
+	kfree(buf->frags);
+}
+EXPORT_SYMBOL_GPL(xsc_core_frag_buf_free);
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/alloc.h b/drivers/net/ethernet/yunsilicon/xsc/pci/alloc.h
new file mode 100644
index 000000000..aef39e632
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/alloc.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_ALLOC_H
+#define __XSC_ALLOC_H
+
+#include "common/xsc_core.h"
+
+int xsc_buf_alloc(struct xsc_core_device *xdev,
+		  unsigned long size,
+		  unsigned long max_direct,
+		  struct xsc_buf *buf);
+void xsc_buf_free(struct xsc_core_device *xdev, struct xsc_buf *buf);
+void xsc_fill_page_array(struct xsc_buf *buf, __be64 *pas, unsigned int npages);
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/cq.h b/drivers/net/ethernet/yunsilicon/xsc/pci/cq.h
new file mode 100644
index 000000000..afe6cbb0d
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/cq.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_CQ_H
+#define __XSC_CQ_H
+
+#include "common/xsc_core.h"
+
+void xsc_init_cq_table(struct xsc_core_device *xdev);
+
+int xsc_core_create_cq(struct xsc_core_device *xdev, struct xsc_core_cq *cq,
+		       struct xsc_create_cq_ex_mbox_in *in, int inlen);
+int xsc_core_destroy_cq(struct xsc_core_device *xdev, struct xsc_core_cq *cq);
+
+#endif /* __XSC_CQ_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/eq.c b/drivers/net/ethernet/yunsilicon/xsc/pci/eq.c
new file mode 100644
index 000000000..d273a42d6
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/eq.c
@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+
+#include "common/xsc_cmd_api.h"
+#include "common/xsc_core.h"
+#include "qp.h"
+#include "alloc.h"
+#include "eq.h"
+#include "hal/xsc_hal.h"
+
+enum {
+	XSC_EQE_SIZE		= sizeof(struct xsc_eqe),
+};
+
+enum {
+	XSC_NUM_SPARE_EQE	= 0x80,
+	XSC_NUM_ASYNC_EQE	= 0x100,
+};
+
+static int xsc_cmd_destroy_eq(struct xsc_core_device *xdev, u32 eqn)
+{
+	struct xsc_destroy_eq_mbox_out out = {0};
+	struct xsc_destroy_eq_mbox_in in = {0};
+
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DESTROY_EQ);
+	in.eqn = cpu_to_be32(eqn);
+
+	return xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+}
+
+static struct xsc_eqe *get_eqe(struct xsc_eq *eq, u32 entry)
+{
+	return xsc_buf_offset(&eq->buf, entry * XSC_EQE_SIZE);
+}
+
+static struct xsc_eqe *next_eqe_sw(struct xsc_eq *eq)
+{
+	struct xsc_eqe *eqe = get_eqe(eq, eq->cons_index & (eq->nent - 1));
+
+	return ((eqe->owner_data & XSC_EQE_OWNER) ^
+		!!(eq->cons_index & eq->nent)) ? NULL : eqe;
+}
+
+static void eq_update_ci(struct xsc_eq *eq, int arm)
+{
+	xsc_update_eq_ci(eq->dev, eq->eqn, eq->cons_index, arm);
+}
+
+static void xsc_cq_completion(struct xsc_core_device *xdev, u32 cqn)
+{
+}
+
+static void xsc_eq_cq_event(struct xsc_core_device *xdev,
+			    u32 cqn, int event_type)
+{
+}
+
+static int xsc_eq_int(struct xsc_core_device *xdev, struct xsc_eq *eq)
+{
+	u32 cqn, queue_id;
+	struct xsc_eqe *eqe;
+	int eqes_found = 0;
+	int set_ci = 0;
+
+	while ((eqe = next_eqe_sw(eq))) {
+		/* Make sure we read EQ entry contents after we've
+		 * checked the ownership bit.
+		 */
+		rmb();
+		switch (eqe->type) {
+		case XSC_EVENT_TYPE_COMP:
+		case XSC_EVENT_TYPE_INTERNAL_ERROR:
+			/* eqe is changing */
+			queue_id = FIELD_GET(XSC_EQE_QUEUE_ID_MASK,
+					     le16_to_cpu(eqe->queue_id_data));
+			cqn = queue_id;
+			xsc_cq_completion(xdev, cqn);
+			break;
+
+		case XSC_EVENT_TYPE_CQ_ERROR:
+			queue_id = FIELD_GET(XSC_EQE_QUEUE_ID_MASK,
+					     le16_to_cpu(eqe->queue_id_data));
+			cqn = queue_id;
+			xsc_eq_cq_event(xdev, cqn, eqe->type);
+			break;
+		default:
+			break;
+		}
+
+		++eq->cons_index;
+		eqes_found = 1;
+		++set_ci;
+
+		/* The HCA will think the queue has overflowed if we
+		 * don't tell it we've been processing events.  We
+		 * create our EQs with XSC_NUM_SPARE_EQE extra
+		 * entries, so we must update our consumer index at
+		 * least that often.
+		 */
+		if (unlikely(set_ci >= XSC_NUM_SPARE_EQE)) {
+			eq_update_ci(eq, 0);
+			set_ci = 0;
+		}
+	}
+
+	eq_update_ci(eq, 1);
+
+	return eqes_found;
+}
+
+static irqreturn_t xsc_msix_handler(int irq, void *eq_ptr)
+{
+	struct xsc_core_device *xdev;
+	struct xsc_eq *eq = eq_ptr;
+
+	xdev = eq->dev;
+	xsc_eq_int(xdev, eq);
+
+	/* MSI-X vectors always belong to us */
+	return IRQ_HANDLED;
+}
+
+static void init_eq_buf(struct xsc_eq *eq)
+{
+	struct xsc_eqe *eqe;
+	int i;
+
+	for (i = 0; i < eq->nent; i++) {
+		eqe = get_eqe(eq, i);
+		eqe->owner_data |= XSC_EQE_OWNER;
+	}
+}
+
+int xsc_create_map_eq(struct xsc_core_device *xdev,
+		      struct xsc_eq *eq, u16 vecidx,
+		      u32 nent, const char *name)
+{
+	u16 msix_vec_offset = xdev->msix_vec_base + vecidx;
+	struct xsc_dev_resource *dev_res = xdev->dev_res;
+	struct xsc_create_eq_mbox_out out;
+	struct xsc_create_eq_mbox_in *in;
+	unsigned int hw_npages;
+	u32 inlen;
+	int err;
+
+	eq->nent = roundup_pow_of_two(roundup(nent, XSC_NUM_SPARE_EQE));
+	err = xsc_buf_alloc(xdev, eq->nent * XSC_EQE_SIZE, PAGE_SIZE, &eq->buf);
+	if (err)
+		return err;
+
+	init_eq_buf(eq);
+
+	hw_npages = DIV_ROUND_UP(eq->nent * XSC_EQE_SIZE, PAGE_SIZE_4K);
+	inlen = sizeof(*in) + sizeof(in->pas[0]) * hw_npages;
+	in = kvzalloc(inlen, GFP_KERNEL);
+	if (!in) {
+		err = -ENOMEM;
+		goto err_free_eq_buf;
+	}
+	memset(&out, 0, sizeof(out));
+
+	xsc_fill_page_array(&eq->buf, in->pas, hw_npages);
+
+	in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_CREATE_EQ);
+	in->ctx.log_eq_sz = ilog2(eq->nent);
+	in->ctx.vecidx = cpu_to_be16(msix_vec_offset);
+	in->ctx.pa_num = cpu_to_be16(hw_npages);
+	in->ctx.glb_func_id = cpu_to_be16(xdev->glb_func_id);
+	in->ctx.is_async_eq = (vecidx == XSC_EQ_VEC_ASYNC ? 1 : 0);
+
+	err = xsc_cmd_exec(xdev, in, inlen, &out, sizeof(out));
+	if (err)
+		goto err_free_in;
+
+	snprintf(dev_res->irq_info[vecidx].name, XSC_MAX_IRQ_NAME, "%s@pci:%s",
+		 name, pci_name(xdev->pdev));
+
+	eq->eqn = be32_to_cpu(out.eqn);
+	eq->irqn = pci_irq_vector(xdev->pdev, vecidx);
+	eq->dev = xdev;
+	eq->index = vecidx;
+
+	err = request_irq(eq->irqn, xsc_msix_handler, 0,
+			  dev_res->irq_info[vecidx].name, eq);
+	if (err)
+		goto err_destroy_eq;
+
+	/* EQs are created in ARMED state
+	 */
+	eq_update_ci(eq, 1);
+	kvfree(in);
+	return 0;
+
+err_destroy_eq:
+	xsc_cmd_destroy_eq(xdev, eq->eqn);
+
+err_free_in:
+	kvfree(in);
+
+err_free_eq_buf:
+	xsc_buf_free(xdev, &eq->buf);
+	return err;
+}
+
+int xsc_destroy_unmap_eq(struct xsc_core_device *xdev, struct xsc_eq *eq)
+{
+	int err;
+
+	if (!xsc_fw_is_available(xdev))
+		return 0;
+
+	free_irq(eq->irqn, eq);
+	err = xsc_cmd_destroy_eq(xdev, eq->eqn);
+	if (err)
+		pci_err(xdev->pdev, "failed to destroy a previously created eq: eqn %d\n",
+			eq->eqn);
+	xsc_buf_free(xdev, &eq->buf);
+
+	return err;
+}
+
+void xsc_eq_init(struct xsc_core_device *xdev)
+{
+	spin_lock_init(&xdev->dev_res->eq_table.lock);
+}
+
+int xsc_start_eqs(struct xsc_core_device *xdev)
+{
+	struct xsc_eq_table *table = &xdev->dev_res->eq_table;
+	int err;
+
+	err = xsc_create_map_eq(xdev, &table->async_eq, XSC_EQ_VEC_ASYNC,
+				XSC_NUM_ASYNC_EQE, "xsc_async_eq");
+	if (err)
+		pci_err(xdev->pdev, "failed to create async EQ %d\n", err);
+
+	return err;
+}
+
+void xsc_stop_eqs(struct xsc_core_device *xdev)
+{
+	struct xsc_eq_table *table = &xdev->dev_res->eq_table;
+
+	xsc_destroy_unmap_eq(xdev, &table->async_eq);
+}
+
+struct xsc_eq *xsc_core_eq_get(struct xsc_core_device *xdev, int i)
+{
+	struct xsc_eq_table *table = &xdev->dev_res->eq_table;
+	struct xsc_eq *eq_ret = NULL;
+	struct xsc_eq *eq, *n;
+
+	spin_lock(&table->lock);
+	list_for_each_entry_safe(eq, n, &table->comp_eqs_list, list) {
+		if (eq->index == i) {
+			eq_ret = eq;
+			break;
+		}
+	}
+	spin_unlock(&table->lock);
+
+	return eq_ret;
+}
+EXPORT_SYMBOL_GPL(xsc_core_eq_get);
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/eq.h b/drivers/net/ethernet/yunsilicon/xsc/pci/eq.h
new file mode 100644
index 000000000..3e7869e12
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/eq.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __EQ_H
+#define __EQ_H
+
+#include "common/xsc_core.h"
+
+enum {
+	XSC_EQ_VEC_ASYNC		= 0,
+	XSC_VEC_CMD			= 1,
+	XSC_VEC_CMD_EVENT		= 2,
+	XSC_DMA_READ_DONE_VEC		= 3,
+	XSC_EQ_VEC_COMP_BASE,
+};
+
+struct xsc_eqe {
+	u8 type;
+	u8 sub_type;
+	__le16 queue_id_data;
+#define XSC_EQE_QUEUE_ID_MASK	GENMASK(14, 0)
+
+	u8 err_code;
+	u8 rsvd[2];
+	u8 owner_data;
+#define XSC_EQE_OWNER		BIT(7)
+};
+
+int xsc_create_map_eq(struct xsc_core_device *xdev,
+		      struct xsc_eq *eq, u16 vecidx,
+		      u32 nent, const char *name);
+int xsc_destroy_unmap_eq(struct xsc_core_device *xdev, struct xsc_eq *eq);
+void xsc_eq_init(struct xsc_core_device *xdev);
+int xsc_start_eqs(struct xsc_core_device *xdev);
+void xsc_stop_eqs(struct xsc_core_device *xdev);
+
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/main.c b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
index 79acd36df..e1f7f178f 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
@@ -6,6 +6,11 @@
 #include "common/xsc_core.h"
 #include "common/xsc_cmd_api.h"
 #include "hw.h"
+#include "qp.h"
+#include "cq.h"
+#include "eq.h"
+#include "pci_irq.h"
+#include "adev.h"
 #include "hal/xsc_hal.h"
 
 static const struct pci_device_id xsc_pci_id_table[] = {
@@ -190,6 +195,8 @@ static int xsc_hw_setup(struct xsc_core_device *xdev)
 		goto err_cmd_cleanup;
 	}
 
+	xsc_eq_init(xdev);
+
 	return 0;
 err_cmd_cleanup:
 	xsc_cmd_cleanup(xdev);
@@ -212,13 +219,31 @@ static int xsc_load(struct xsc_core_device *xdev)
 		goto err_out;
 	}
 
+	err = xsc_irq_eq_create(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "xsc_irq_eq_create failed %d\n", err);
+		goto err_hw_cleanup;
+	}
+
+	err = xsc_adev_init(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "xsc_adev_init failed %d\n", err);
+		goto err_irq_eq_destroy;
+	}
+
 	return 0;
+err_irq_eq_destroy:
+	xsc_irq_eq_destroy(xdev);
+err_hw_cleanup:
+	xsc_hw_cleanup(xdev);
 err_out:
 	return err;
 }
 
 static void xsc_unload(struct xsc_core_device *xdev)
 {
+	xsc_adev_uninit(xdev);
+	xsc_irq_eq_destroy(xdev);
 	xsc_hw_cleanup(xdev);
 }
 
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.c b/drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.c
new file mode 100644
index 000000000..c0f0b9d1f
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.c
@@ -0,0 +1,498 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/irqdomain.h>
+#include <linux/msi.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/module.h>
+
+#include "common/xsc_cmd_api.h"
+#include "common/xsc_core.h"
+#include "eq.h"
+#include "pci_irq.h"
+#include "hal/xsc_hal.h"
+
+enum {
+	XSC_COMP_EQ_SIZE = 1024,
+};
+
+static void xsc_free_irq(struct xsc_core_device *xdev, unsigned int vector)
+{
+	unsigned int irqn = 0;
+
+	irqn = pci_irq_vector(xdev->pdev, vector);
+	disable_irq(irqn);
+
+	if (xsc_fw_is_available(xdev))
+		free_irq(irqn, xdev);
+}
+
+static int set_comp_irq_affinity_hint(struct xsc_core_device *xdev, int i)
+{
+	struct xsc_eq_table *table = &xdev->dev_res->eq_table;
+	struct xsc_eq *eq = xsc_core_eq_get(xdev, i);
+	int vecidx = table->eq_vec_comp_base + i;
+	unsigned int irqn;
+	int ret;
+
+	irqn = pci_irq_vector(xdev->pdev, vecidx);
+	if (!zalloc_cpumask_var(&eq->mask, GFP_KERNEL)) {
+		pci_err(xdev->pdev, "zalloc_cpumask_var rx cpumask failed");
+		return -ENOMEM;
+	}
+
+	if (!zalloc_cpumask_var(&xdev->xps_cpumask, GFP_KERNEL)) {
+		pci_err(xdev->pdev, "zalloc_cpumask_var tx cpumask failed");
+		return -ENOMEM;
+	}
+
+	cpumask_set_cpu(cpumask_local_spread(i, xdev->numa_node),
+			xdev->xps_cpumask);
+	ret = irq_set_affinity_hint(irqn, eq->mask);
+
+	return ret;
+}
+
+static void clear_comp_irq_affinity_hint(struct xsc_core_device *xdev, int i)
+{
+	struct xsc_eq_table *table = &xdev->dev_res->eq_table;
+	struct xsc_eq *eq = xsc_core_eq_get(xdev, i);
+	int vecidx = table->eq_vec_comp_base + i;
+	int irqn;
+
+	irqn = pci_irq_vector(xdev->pdev, vecidx);
+	irq_set_affinity_hint(irqn, NULL);
+	free_cpumask_var(eq->mask);
+}
+
+static int set_comp_irq_affinity_hints(struct xsc_core_device *xdev)
+{
+	struct xsc_eq_table *table = &xdev->dev_res->eq_table;
+	int nvec = table->num_comp_vectors;
+	int err;
+	int i;
+
+	for (i = 0; i < nvec; i++) {
+		err = set_comp_irq_affinity_hint(xdev, i);
+		if (err)
+			goto err_out;
+	}
+
+	return 0;
+
+err_out:
+	for (i--; i >= 0; i--)
+		clear_comp_irq_affinity_hint(xdev, i);
+	free_cpumask_var(xdev->xps_cpumask);
+
+	return err;
+}
+
+static void clear_comp_irq_affinity_hints(struct xsc_core_device *xdev)
+{
+	struct xsc_eq_table *table = &xdev->dev_res->eq_table;
+	int nvec = table->num_comp_vectors;
+	int i;
+
+	for (i = 0; i < nvec; i++)
+		clear_comp_irq_affinity_hint(xdev, i);
+	free_cpumask_var(xdev->xps_cpumask);
+}
+
+static int xsc_alloc_irq_vectors(struct xsc_core_device *xdev)
+{
+	struct xsc_dev_resource *dev_res = xdev->dev_res;
+	struct xsc_eq_table *table = &dev_res->eq_table;
+	int nvec = xdev->caps.msix_num;
+	int nvec_base;
+	int err;
+
+	nvec_base = XSC_EQ_VEC_COMP_BASE;
+	if (nvec <= nvec_base) {
+		pci_err(xdev->pdev, "failed to alloc irq vector(%d)\n", nvec);
+		return -ENOMEM;
+	}
+
+	dev_res->irq_info = kcalloc(nvec, sizeof(*dev_res->irq_info),
+				    GFP_KERNEL);
+	if (!dev_res->irq_info)
+		return -ENOMEM;
+
+	nvec = pci_alloc_irq_vectors(xdev->pdev, nvec_base + 1, nvec,
+				     PCI_IRQ_MSIX);
+	if (nvec < 0) {
+		err = nvec;
+		goto err_free_irq_info;
+	}
+
+	table->eq_vec_comp_base = nvec_base;
+	table->num_comp_vectors = nvec - nvec_base;
+	xdev->msix_vec_base = xdev->caps.msix_base;
+
+	return 0;
+
+err_free_irq_info:
+	kfree(dev_res->irq_info);
+	return err;
+}
+
+static void xsc_free_irq_vectors(struct xsc_core_device *xdev)
+{
+	struct xsc_dev_resource *dev_res = xdev->dev_res;
+
+	if (!xsc_fw_is_available(xdev))
+		return;
+
+	pci_free_irq_vectors(xdev->pdev);
+	kfree(dev_res->irq_info);
+}
+
+int xsc_core_vector2eqn(struct xsc_core_device *xdev, int vector, u32 *eqn,
+			unsigned int *irqn)
+{
+	struct xsc_eq_table *table = &xdev->dev_res->eq_table;
+	struct xsc_eq *eq, *n;
+	int err = -ENOENT;
+
+	if (!xdev->caps.msix_enable)
+		return 0;
+
+	spin_lock(&table->lock);
+	list_for_each_entry_safe(eq, n, &table->comp_eqs_list, list) {
+		if (eq->index == vector) {
+			*eqn = eq->eqn;
+			*irqn = eq->irqn;
+			err = 0;
+			break;
+		}
+	}
+	spin_unlock(&table->lock);
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(xsc_core_vector2eqn);
+
+int xsc_vector2eqn(struct xsc_core_device *xdev, int vector, int *eqn,
+		   unsigned int *irqn)
+{
+	u32 cqn = 0;
+	int err;
+
+	err = xsc_core_vector2eqn(xdev, vector, &cqn, irqn);
+	if (!err)
+		*eqn = (int)cqn;
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(xsc_vector2eqn);
+
+static void free_comp_eqs(struct xsc_core_device *xdev)
+{
+	struct xsc_eq_table *table = &xdev->dev_res->eq_table;
+	struct xsc_eq *eq, *n;
+
+	spin_lock(&table->lock);
+	list_for_each_entry_safe(eq, n, &table->comp_eqs_list, list) {
+		list_del(&eq->list);
+		spin_unlock(&table->lock);
+		if (xsc_destroy_unmap_eq(xdev, eq))
+			pci_err(xdev->pdev, "failed to destroy EQ 0x%x\n",
+				eq->eqn);
+		kfree(eq);
+		spin_lock(&table->lock);
+	}
+	spin_unlock(&table->lock);
+}
+
+static int alloc_comp_eqs(struct xsc_core_device *xdev)
+{
+	struct xsc_eq_table *table = &xdev->dev_res->eq_table;
+	char name[XSC_MAX_IRQ_NAME];
+	struct xsc_eq *eq;
+	int ncomp_vec;
+	u32 nent;
+	int err;
+	int i;
+
+	INIT_LIST_HEAD(&table->comp_eqs_list);
+	ncomp_vec = table->num_comp_vectors;
+	nent = XSC_COMP_EQ_SIZE;
+
+	for (i = 0; i < ncomp_vec; i++) {
+		eq = kzalloc_obj(*eq, GFP_KERNEL);
+		if (!eq) {
+			err = -ENOMEM;
+			goto err_free;
+		}
+
+		snprintf(name, XSC_MAX_IRQ_NAME, "xsc_comp%d", i);
+		err = xsc_create_map_eq(xdev, eq,
+					i + table->eq_vec_comp_base,
+					nent, name);
+		if (err) {
+			kfree(eq);
+			goto err_free;
+		}
+
+		eq->index = i;
+		spin_lock(&table->lock);
+		list_add_tail(&eq->list, &table->comp_eqs_list);
+		spin_unlock(&table->lock);
+	}
+
+	return 0;
+
+err_free:
+	free_comp_eqs(xdev);
+	return err;
+}
+
+static irqreturn_t xsc_cmd_handler(int irq, void *arg)
+{
+	struct xsc_core_device *xdev = (struct xsc_core_device *)arg;
+	int err;
+
+	disable_irq_nosync(xdev->cmd.irqn);
+	err = xsc_cmd_err_handler(xdev);
+	if (!err)
+		xsc_cmd_resp_handler(xdev);
+	enable_irq(xdev->cmd.irqn);
+
+	return IRQ_HANDLED;
+}
+
+static int xsc_request_irq_for_cmdq(struct xsc_core_device *xdev, u8 vecidx)
+{
+	struct xsc_dev_resource *dev_res = xdev->dev_res;
+
+	xsc_set_cmdq_msix_vector(xdev, xdev->msix_vec_base + vecidx);
+
+	snprintf(dev_res->irq_info[vecidx].name, XSC_MAX_IRQ_NAME, "%s@pci:%s",
+		 "xsc_cmd", pci_name(xdev->pdev));
+	xdev->cmd.irqn = pci_irq_vector(xdev->pdev, vecidx);
+	return request_irq(xdev->cmd.irqn, xsc_cmd_handler, 0,
+		dev_res->irq_info[vecidx].name, xdev);
+}
+
+static void xsc_free_irq_for_cmdq(struct xsc_core_device *xdev)
+{
+	xsc_free_irq(xdev, XSC_VEC_CMD);
+}
+
+/*
+ * Handle an asynchronous event. This runs on the PCI layer's own workqueue,
+ * so it outlives any auxiliary driver: the driver only ever registers a
+ * callback here, and the memory this touches belongs to xsc_core_device
+ * rather than to the eth or RDMA private area.
+ */
+static void xsc_event_work(struct work_struct *work)
+{
+	struct xsc_core_device *xdev = container_of(work, struct xsc_core_device,
+						    event_work);
+	struct xsc_event_query_type_mbox_out out;
+	struct xsc_event_query_type_mbox_in in = {};
+	int err;
+
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_QUERY_EVENT_TYPE);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err || out.hdr.status) {
+		pci_err(xdev->pdev, "failed to query event type, err=%d, status=%d\n",
+			err, out.hdr.status);
+		return;
+	}
+
+	switch (out.ctx.resp_cmd_type) {
+	case XSC_CMD_EVENT_RESP_CHANGE_LINK:
+		if (xdev->link_event_handler)
+			xdev->link_event_handler(xdev);
+		break;
+	case XSC_CMD_EVENT_RESP_TEMP_WARN:
+		pci_warn(xdev->pdev, "[Minor]nic chip temperature high warning\n");
+		break;
+	case XSC_CMD_EVENT_RESP_OVER_TEMP_PROTECTION:
+		pci_warn(xdev->pdev, "[Critical]nic chip was over-temperature\n");
+		break;
+	default:
+		pci_dbg(xdev->pdev, "unknown event cmdtype=0x%x\n",
+			out.ctx.resp_cmd_type);
+		break;
+	}
+}
+
+static irqreturn_t xsc_event_handler(int irq, void *arg)
+{
+	struct xsc_core_device *xdev = (struct xsc_core_device *)arg;
+
+	queue_work(xdev->event_wq, &xdev->event_work);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Register the link event callback.
+ *
+ * The events are dispatched from the PCI layer's own workqueue, whose
+ * contents are not the client's business (it also carries the temperature
+ * warnings, which are logged here), so the client must not touch
+ * xdev->event_wq itself.
+ */
+void xsc_irq_event_register(struct xsc_core_device *xdev,
+			    void (*handler)(void *xdev))
+{
+	xdev->link_event_handler = handler;
+}
+EXPORT_SYMBOL_GPL(xsc_irq_event_register);
+
+/*
+ * Detach the callback and wait for an event that is already in flight to
+ * finish with it, in the same way free_irq() both unregisters and waits.
+ * Must be called before the caller frees whatever its handler dereferences.
+ * Safe to call when no handler is registered.
+ */
+void xsc_irq_event_unregister(struct xsc_core_device *xdev)
+{
+	xdev->link_event_handler = NULL;
+
+	/* Only exists once MSI-X, which requests the event IRQ, is up. */
+	if (xdev->event_wq)
+		flush_workqueue(xdev->event_wq);
+}
+EXPORT_SYMBOL_GPL(xsc_irq_event_unregister);
+
+static int xsc_request_irq_for_event(struct xsc_core_device *xdev)
+{
+	struct xsc_dev_resource *dev_res = xdev->dev_res;
+
+	snprintf(dev_res->irq_info[XSC_VEC_CMD_EVENT].name,
+		 XSC_MAX_IRQ_NAME, "%s@pci:%s",
+		 "xsc_eth_event", pci_name(xdev->pdev));
+	return request_irq(pci_irq_vector(xdev->pdev, XSC_VEC_CMD_EVENT),
+			   xsc_event_handler, 0,
+			   dev_res->irq_info[XSC_VEC_CMD_EVENT].name, xdev);
+}
+
+static void xsc_free_irq_for_event(struct xsc_core_device *xdev)
+{
+	xsc_free_irq(xdev, XSC_VEC_CMD_EVENT);
+}
+
+static int xsc_cmd_enable_msix(struct xsc_core_device *xdev)
+{
+	struct xsc_msix_table_info_mbox_out out;
+	struct xsc_msix_table_info_mbox_in in;
+	int err;
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_ENABLE_MSIX);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err) {
+		pci_err(xdev->pdev, "xsc_cmd_exec enable msix failed %d\n",
+			err);
+		return err;
+	}
+
+	return 0;
+}
+
+int xsc_irq_eq_create(struct xsc_core_device *xdev)
+{
+	int err;
+
+	if (xdev->caps.msix_enable == 0)
+		return 0;
+
+	err = xsc_alloc_irq_vectors(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "enable msix failed, err=%d\n", err);
+		goto err_out;
+	}
+
+	err = xsc_start_eqs(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "failed to start EQs, err=%d\n", err);
+		goto err_free_irq_vectors;
+	}
+
+	err = alloc_comp_eqs(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "failed to alloc comp EQs, err=%d\n", err);
+		goto err_stop_eqs;
+	}
+
+	err = xsc_request_irq_for_cmdq(xdev, XSC_VEC_CMD);
+	if (err) {
+		pci_err(xdev->pdev, "failed to request irq for cmdq, err=%d\n",
+			err);
+		goto err_free_comp_eqs;
+	}
+
+	xdev->event_wq = create_singlethread_workqueue("xsc_event");
+	if (!xdev->event_wq) {
+		pci_err(xdev->pdev, "failed to create event workqueue\n");
+		err = -ENOMEM;
+		goto err_free_irq_cmdq;
+	}
+	INIT_WORK(&xdev->event_work, xsc_event_work);
+
+	err = xsc_request_irq_for_event(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "failed to request irq for event, err=%d\n",
+			err);
+		goto err_destroy_event_wq;
+	}
+
+	err = set_comp_irq_affinity_hints(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "failed to alloc affinity hint cpumask, err=%d\n",
+			err);
+		goto err_free_irq_evnt;
+	}
+
+	xsc_cmd_use_events(xdev);
+	err = xsc_cmd_enable_msix(xdev);
+	if (err) {
+		pci_err(xdev->pdev, "xsc_cmd_enable_msix failed %d.\n", err);
+		xsc_cmd_use_polling(xdev);
+		goto err_free_irq_evnt;
+	}
+	return 0;
+
+err_free_irq_evnt:
+	xsc_free_irq_for_event(xdev);
+err_destroy_event_wq:
+	destroy_workqueue(xdev->event_wq);
+err_free_irq_cmdq:
+	xsc_free_irq_for_cmdq(xdev);
+err_free_comp_eqs:
+	free_comp_eqs(xdev);
+err_stop_eqs:
+	xsc_stop_eqs(xdev);
+err_free_irq_vectors:
+	xsc_free_irq_vectors(xdev);
+err_out:
+	return err;
+}
+
+void xsc_irq_eq_destroy(struct xsc_core_device *xdev)
+{
+	if (xdev->caps.msix_enable == 0)
+		return;
+
+	xsc_stop_eqs(xdev);
+	clear_comp_irq_affinity_hints(xdev);
+	free_comp_eqs(xdev);
+
+	/* xsc_free_irq() disables and synchronises the event IRQ, so no new
+	 * work can be queued once this returns.
+	 */
+	xsc_free_irq_for_event(xdev);
+	destroy_workqueue(xdev->event_wq);
+	xsc_free_irq_for_cmdq(xdev);
+	xsc_free_irq_vectors(xdev);
+}
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.h b/drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.h
new file mode 100644
index 000000000..750251d1b
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_PCI_IRQ_H
+#define __XSC_PCI_IRQ_H
+
+#include "common/xsc_core.h"
+
+int xsc_irq_eq_create(struct xsc_core_device *xdev);
+void xsc_irq_eq_destroy(struct xsc_core_device *xdev);
+int xsc_core_vector2eqn(struct xsc_core_device *xdev, int vector, u32 *eqn,
+			unsigned int *irqn);
+int xsc_vector2eqn(struct xsc_core_device *xdev, int vector, int *eqn,
+		   unsigned int *irqn);
+
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/qp.h b/drivers/net/ethernet/yunsilicon/xsc/pci/qp.h
new file mode 100644
index 000000000..08af2008a
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/qp.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_QP_H
+#define __XSC_QP_H
+
+#include "common/xsc_core.h"
+#include "common/xsc_hsi.h"
+
+void xsc_init_qp_table(struct xsc_core_device *xdev);
+void xsc_qp_event(struct xsc_core_device *xdev, u32 qpn, int event_type);
+
+int xsc_core_create_qp(struct xsc_core_device *xdev,
+		       struct xsc_core_qp *qp,
+		       struct xsc_create_qp_mbox_in *in,
+		       int inlen);
+int xsc_core_destroy_qp(struct xsc_core_device *xdev,
+			struct xsc_core_qp *qp);
+int xsc_core_qp_modify(struct xsc_core_device *xdev, enum xsc_qp_state cur_state,
+		       enum xsc_qp_state new_state,
+		       struct xsc_modify_qp_mbox_in *in, int sqd_event,
+		       struct xsc_core_qp *qp);
+int xsc_core_qp_query(struct xsc_core_device *xdev, struct xsc_core_qp *qp,
+		      struct xsc_query_qp_mbox_out *out, int outlen);
+
+#endif /* __XSC_QP_H */

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

* [PATCH 05/15] yunsilicon: add the CQ and QP resource pools
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (3 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 04/15] yunsilicon: add event queues, interrupts and the Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 06/15] yunsilicon: add ethernet netdev registration Xin Tian
                   ` (10 subsequent siblings)
  15 siblings, 1 reply; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the core-side completion queue and queue pair pools: the CQ/QP
table management, the create/destroy/drain helpers and the EQ dispatch
of CQ completion, CQ error and QP events.

The pools are shared by the ethernet and RDMA functions, which create
their queues on top of them.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 .../net/ethernet/yunsilicon/xsc/pci/Makefile  |   2 +-
 drivers/net/ethernet/yunsilicon/xsc/pci/cq.c  | 316 ++++++++++++++++
 drivers/net/ethernet/yunsilicon/xsc/pci/eq.c  |  53 ++-
 .../net/ethernet/yunsilicon/xsc/pci/main.c    |   2 +
 drivers/net/ethernet/yunsilicon/xsc/pci/qp.c  | 356 ++++++++++++++++++
 5 files changed, 727 insertions(+), 2 deletions(-)
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/cq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/qp.c

diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
index e264990f9..5c947da2a 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
@@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
 
-xsc_pci-y := main.o cmdq.o hw.o alloc.o eq.o pci_irq.o adev.o hal/andes_impl.o
+xsc_pci-y := main.o cmdq.o hw.o qp.o cq.o alloc.o eq.o pci_irq.o adev.o hal/andes_impl.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/cq.c b/drivers/net/ethernet/yunsilicon/xsc/pci/cq.c
new file mode 100644
index 000000000..39715682f
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/cq.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "common/xsc_core.h"
+#include "common/xsc_cmd_api.h"
+#include "cq.h"
+
+void xsc_init_cq_table(struct xsc_core_device *xdev)
+{
+	struct xsc_cq_table *table = &xdev->dev_res->cq_table;
+
+	spin_lock_init(&table->lock);
+	INIT_RADIX_TREE(&table->tree, GFP_ATOMIC);
+}
+
+static int xsc_create_cq(struct xsc_core_device *xdev, u32 *p_cqn,
+			 struct xsc_create_cq_mbox_in *in, int insize)
+{
+	struct xsc_create_cq_mbox_out out;
+	int ret;
+
+	memset(&out, 0, sizeof(out));
+	in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_CREATE_CQ);
+	ret = xsc_cmd_exec(xdev, in, insize, &out, sizeof(out));
+	if (ret) {
+		pci_err(xdev->pdev, "failed to create cq, err=%d out.status=%u\n",
+			ret, out.hdr.status);
+		return ret;
+	}
+
+	*p_cqn = be32_to_cpu(out.cqn) & 0xffffff;
+	return 0;
+}
+
+static int xsc_destroy_cq(struct xsc_core_device *xdev, u32 cqn)
+{
+	struct xsc_destroy_cq_mbox_out out;
+	struct xsc_destroy_cq_mbox_in in;
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DESTROY_CQ);
+	in.cqn = cpu_to_be32(cqn);
+
+	return xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+}
+
+int xsc_core_eth_create_cq(struct xsc_core_device *xdev,
+			   struct xsc_core_cq *xcq,
+			   struct xsc_create_cq_mbox_in *in,
+			   int insize)
+{
+	struct xsc_cq_table *table = &xdev->dev_res->cq_table;
+	u32 cqn;
+	int ret;
+	int err;
+
+	ret = xsc_create_cq(xdev, &cqn, in, insize);
+	if (ret) {
+		pci_err(xdev->pdev, "xsc_create_cq failed\n");
+		return -ENOEXEC;
+	}
+	xcq->cqn = cqn;
+	xcq->cons_index = 0;
+	xcq->arm_sn = 0;
+	atomic_set(&xcq->refcount, 1);
+	init_completion(&xcq->free);
+
+	spin_lock_irq(&table->lock);
+	ret = radix_tree_insert(&table->tree, xcq->cqn, xcq);
+	spin_unlock_irq(&table->lock);
+	if (ret)
+		goto err_insert_cq;
+	return 0;
+err_insert_cq:
+	err = xsc_destroy_cq(xdev, cqn);
+	if (err)
+		pci_err(xdev->pdev, "failed to destroy cqn=%d, err=%d\n",
+			xcq->cqn, err);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(xsc_core_eth_create_cq);
+
+int xsc_core_eth_destroy_cq(struct xsc_core_device *xdev,
+			    struct xsc_core_cq *xcq)
+{
+	struct xsc_cq_table *table = &xdev->dev_res->cq_table;
+	struct xsc_core_cq *tmp;
+	int err;
+
+	spin_lock_irq(&table->lock);
+	tmp = radix_tree_delete(&table->tree, xcq->cqn);
+	spin_unlock_irq(&table->lock);
+	if (!tmp) {
+		err = -ENOENT;
+		goto err_delete_cq;
+	}
+
+	if (tmp != xcq) {
+		err = -EINVAL;
+		goto err_delete_cq;
+	}
+
+	err = xsc_destroy_cq(xdev, xcq->cqn);
+	if (err)
+		goto err_destroy_cq;
+
+	if (atomic_dec_and_test(&xcq->refcount))
+		complete(&xcq->free);
+	wait_for_completion(&xcq->free);
+	return 0;
+
+err_destroy_cq:
+	pci_err(xdev->pdev, "failed to destroy cqn=%d, err=%d\n",
+		xcq->cqn, err);
+	return err;
+err_delete_cq:
+	pci_err(xdev->pdev, "cqn=%d not found in tree, err=%d\n",
+		xcq->cqn, err);
+	return err;
+}
+EXPORT_SYMBOL_GPL(xsc_core_eth_destroy_cq);
+
+static int xsc_set_cq_context(struct xsc_core_device *xdev,
+			      struct xsc_cq_context_ex *ctx_ex,
+			      u32 *pa_list_base, u32 *cqn)
+{
+	struct xsc_set_cq_context_mbox_in in;
+	struct xsc_set_cq_context_mbox_out out;
+	int err;
+
+	memset(&in, 0, sizeof(in));
+	memcpy(&in.ctx_ex, ctx_ex, sizeof(*ctx_ex));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_SET_CQ_CONTEXT);
+	in.hdr.ver = cpu_to_be16(CMD_SET_CQ_CONTEXT_V1);
+
+	memset(&out, 0, sizeof(out));
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err || out.hdr.status) {
+		pci_err(xdev->pdev, "failed to set cq context\n");
+		return -EIO;
+	}
+
+	*pa_list_base = be32_to_cpu(out.cq_pa_list_base);
+	*cqn = be32_to_cpu(out.cqn);
+	return 0;
+}
+
+static int xsc_set_cq_buf_pa(struct xsc_core_device *xdev,
+			     struct xsc_create_cq_ex_mbox_in *req,
+			     u32 pa_list_base, u32 cqn)
+{
+	struct xsc_set_cq_buf_pa_mbox_in *in;
+	struct xsc_set_cq_buf_pa_mbox_out out;
+	u32 pa_num_total = be32_to_cpu(req->ctx_ex.pa_num_ex);
+	u32 pa_num_max = (xdev->caps.max_cmd_in_len - sizeof(*in)) / sizeof(__be64);
+	u32 pa_num_left = pa_num_total;
+	u32 pa_num, copy_len;
+	int in_len, err;
+
+	while (pa_num_left) {
+		pa_num = min(pa_num_max, pa_num_left);
+		copy_len = pa_num * sizeof(__be64);
+		in_len = sizeof(*in) + copy_len;
+
+		in = kvzalloc(in_len, GFP_KERNEL);
+		if (!in)
+			return -ENOMEM;
+
+		in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_SET_CQ_BUF_PA);
+		in->pa_list_start = cpu_to_be32(pa_list_base);
+		in->pa_num = cpu_to_be32(pa_num);
+		memcpy(in->pas, &req->pas[pa_num_total - pa_num_left], copy_len);
+		pa_num_left -= pa_num;
+		pa_list_base += pa_num;
+		memset(&out, 0, sizeof(out));
+
+		err = xsc_cmd_exec(xdev, in, in_len, &out, sizeof(out));
+		kvfree(in);
+
+		if (err || out.hdr.status) {
+			pci_err(xdev->pdev, "failed to set cq buf pa, cqn=%d\n", cqn);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int xsc_create_cq_compat(struct xsc_core_device *xdev,
+				struct xsc_create_cq_ex_mbox_in *in,
+				struct xsc_create_cq_mbox_out *out)
+{
+	struct xsc_create_cq_mbox_in *_in;
+	int _inlen = sizeof(*_in) +
+		be16_to_cpu(in->ctx_ex.ctx.pa_num) * sizeof(__be64);
+	int err;
+
+	_in = kvzalloc(_inlen, GFP_KERNEL);
+	if (!_in)
+		return -ENOMEM;
+
+	_in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_CREATE_CQ);
+	memcpy(&_in->ctx, &in->ctx_ex.ctx, sizeof(_in->ctx));
+	memcpy(&_in->pas, &in->pas, _inlen - sizeof(*_in));
+	memset(out, 0, sizeof(*out));
+
+	err = xsc_cmd_exec(xdev, _in, _inlen, out, sizeof(*out));
+	kvfree(_in);
+
+	if (err)
+		return err;
+	if (out->hdr.status)
+		return xsc_cmd_status_to_err(&out->hdr);
+
+	return 0;
+}
+
+int xsc_core_create_cq(struct xsc_core_device *xdev, struct xsc_core_cq *cq,
+		       struct xsc_create_cq_ex_mbox_in *in, int inlen)
+{
+	struct xsc_cq_table *table = &xdev->dev_res->cq_table;
+	struct xsc_create_cq_mbox_out out;
+	u32 pa_list_base = 0;
+	u32 cqn = 0;
+	int err;
+
+	if (inlen < xdev->caps.max_cmd_in_len) {
+		in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_CREATE_CQ_EX);
+		memset(&out, 0, sizeof(out));
+		err = xsc_cmd_exec(xdev, in, inlen, &out, sizeof(out));
+		if (err)
+			return err;
+
+		if (out.hdr.status && out.hdr.status != XSC_CMD_STATUS_NOT_SUPPORTED)
+			return xsc_cmd_status_to_err(&out.hdr);
+
+		if (out.hdr.status == XSC_CMD_STATUS_NOT_SUPPORTED) {
+			err = xsc_create_cq_compat(xdev, in, &out);
+			if (err)
+				return err;
+		}
+
+		cqn = be32_to_cpu(out.cqn);
+	} else {
+		err = xsc_set_cq_context(xdev, &in->ctx_ex, &pa_list_base, &cqn);
+		if (err)
+			return err;
+
+		err = xsc_set_cq_buf_pa(xdev, in, pa_list_base, cqn);
+		if (err)
+			goto err_cmd;
+	}
+
+	cq->cqn = cqn;
+	cq->cons_index = 0;
+	cq->arm_sn = 0;
+	cq->xdev = xdev;
+	cq->dev = xdev;
+	atomic_set(&cq->refcount, 1);
+	init_completion(&cq->free);
+
+	xsc_arm_cq(xdev, cq->cqn, 0, 0);
+
+	spin_lock_irq(&table->lock);
+	err = radix_tree_insert(&table->tree, cq->cqn, cq);
+	spin_unlock_irq(&table->lock);
+	if (err)
+		goto err_cmd;
+
+	cq->pid = current->pid;
+	return 0;
+
+err_cmd:
+	xsc_destroy_cq(xdev, cqn);
+	return err;
+}
+EXPORT_SYMBOL_GPL(xsc_core_create_cq);
+
+int xsc_core_destroy_cq(struct xsc_core_device *xdev, struct xsc_core_cq *cq)
+{
+	struct xsc_cq_table *table = &xdev->dev_res->cq_table;
+	struct xsc_destroy_cq_mbox_in in;
+	struct xsc_destroy_cq_mbox_out out;
+	struct xsc_core_cq *tmp;
+	int err;
+
+	spin_lock_irq(&table->lock);
+	tmp = radix_tree_delete(&table->tree, cq->cqn);
+	spin_unlock_irq(&table->lock);
+	if (!tmp || tmp != cq) {
+		pci_warn(xdev->pdev, "cq 0x%x not found in tree\n", cq->cqn);
+		return -EINVAL;
+	}
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DESTROY_CQ);
+	in.cqn = cpu_to_be32(cq->cqn);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err)
+		return err;
+
+	if (out.hdr.status)
+		return xsc_cmd_status_to_err(&out.hdr);
+
+	if (atomic_dec_and_test(&cq->refcount))
+		complete(&cq->free);
+	wait_for_completion(&cq->free);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_destroy_cq);
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/eq.c b/drivers/net/ethernet/yunsilicon/xsc/pci/eq.c
index d273a42d6..ff4bcb42b 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/eq.c
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/eq.c
@@ -53,16 +53,59 @@ static void eq_update_ci(struct xsc_eq *eq, int arm)
 
 static void xsc_cq_completion(struct xsc_core_device *xdev, u32 cqn)
 {
+	struct xsc_cq_table *table = &xdev->dev_res->cq_table;
+	struct xsc_core_cq *cq;
+
+	rcu_read_lock();
+	cq = radix_tree_lookup(&table->tree, cqn);
+	if (likely(cq))
+		atomic_inc(&cq->refcount);
+	rcu_read_unlock();
+
+	if (!cq) {
+		pci_err(xdev->pdev, "Completion event for bogus CQ, cqn=%d\n",
+			cqn);
+		return;
+	}
+
+	++cq->arm_sn;
+
+	if (!cq->comp)
+		pci_err(xdev->pdev, "cq->comp is NULL\n");
+	else
+		cq->comp(cq);
+
+	if (atomic_dec_and_test(&cq->refcount))
+		complete(&cq->free);
 }
 
 static void xsc_eq_cq_event(struct xsc_core_device *xdev,
 			    u32 cqn, int event_type)
 {
+	struct xsc_cq_table *table = &xdev->dev_res->cq_table;
+	struct xsc_core_cq *cq;
+
+	spin_lock(&table->lock);
+	cq = radix_tree_lookup(&table->tree, cqn);
+	if (likely(cq))
+		atomic_inc(&cq->refcount);
+	spin_unlock(&table->lock);
+
+	if (unlikely(!cq)) {
+		pci_err(xdev->pdev, "Async event for bogus CQ, cqn=%d\n",
+			cqn);
+		return;
+	}
+
+	cq->event(cq, event_type);
+
+	if (atomic_dec_and_test(&cq->refcount))
+		complete(&cq->free);
 }
 
 static int xsc_eq_int(struct xsc_core_device *xdev, struct xsc_eq *eq)
 {
-	u32 cqn, queue_id;
+	u32 cqn, qpn, queue_id;
 	struct xsc_eqe *eqe;
 	int eqes_found = 0;
 	int set_ci = 0;
@@ -88,6 +131,14 @@ static int xsc_eq_int(struct xsc_core_device *xdev, struct xsc_eq *eq)
 			cqn = queue_id;
 			xsc_eq_cq_event(xdev, cqn, eqe->type);
 			break;
+		case XSC_EVENT_TYPE_WQ_CATAS_ERROR:
+		case XSC_EVENT_TYPE_WQ_INVAL_REQ_ERROR:
+		case XSC_EVENT_TYPE_WQ_ACCESS_ERROR:
+			queue_id = FIELD_GET(XSC_EQE_QUEUE_ID_MASK,
+					     le16_to_cpu(eqe->queue_id_data));
+			qpn = queue_id;
+			xsc_qp_event(xdev, qpn, eqe->type);
+			break;
 		default:
 			break;
 		}
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/main.c b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
index e1f7f178f..c4416a78b 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
@@ -195,6 +195,8 @@ static int xsc_hw_setup(struct xsc_core_device *xdev)
 		goto err_cmd_cleanup;
 	}
 
+	xsc_init_cq_table(xdev);
+	xsc_init_qp_table(xdev);
 	xsc_eq_init(xdev);
 
 	return 0;
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/qp.c b/drivers/net/ethernet/yunsilicon/xsc/pci/qp.c
new file mode 100644
index 000000000..8ea57a09f
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/qp.c
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/gfp.h>
+#include <linux/time.h>
+#include <linux/export.h>
+#include <linux/kthread.h>
+
+#include "common/xsc_core.h"
+#include "common/xsc_cmd_api.h"
+#include "qp.h"
+
+int xsc_core_create_resource_common(struct xsc_core_device *xdev,
+				    struct xsc_core_qp *qp)
+{
+	struct xsc_qp_table *table = &xdev->dev_res->qp_table;
+	int err;
+
+	spin_lock_irq(&table->lock);
+	err = radix_tree_insert(&table->tree, qp->qpn, qp);
+	spin_unlock_irq(&table->lock);
+	if (err)
+		return err;
+
+	atomic_set(&qp->refcount, 1);
+	init_completion(&qp->free);
+	qp->pid = current->pid;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_create_resource_common);
+
+void xsc_core_destroy_resource_common(struct xsc_core_device *xdev,
+				      struct xsc_core_qp *qp)
+{
+	struct xsc_qp_table *table = &xdev->dev_res->qp_table;
+	unsigned long flags;
+
+	spin_lock_irqsave(&table->lock, flags);
+	radix_tree_delete(&table->tree, qp->qpn);
+	spin_unlock_irqrestore(&table->lock, flags);
+
+	if (atomic_dec_and_test(&qp->refcount))
+		complete(&qp->free);
+	wait_for_completion(&qp->free);
+}
+EXPORT_SYMBOL_GPL(xsc_core_destroy_resource_common);
+
+void xsc_qp_event(struct xsc_core_device *xdev, u32 qpn, int event_type)
+{
+	struct xsc_qp_table *table = &xdev->dev_res->qp_table;
+	struct xsc_core_qp *qp;
+
+	spin_lock(&table->lock);
+
+	qp = radix_tree_lookup(&table->tree, qpn);
+	if (qp)
+		atomic_inc(&qp->refcount);
+
+	spin_unlock(&table->lock);
+
+	if (!qp) {
+		pci_err(xdev->pdev, "Async event for bogus QP 0x%x\n", qpn);
+		return;
+	}
+
+	qp->event(qp, event_type);
+
+	if (atomic_dec_and_test(&qp->refcount))
+		complete(&qp->free);
+}
+
+void xsc_init_qp_table(struct xsc_core_device *xdev)
+{
+	struct xsc_qp_table *table = &xdev->dev_res->qp_table;
+
+	spin_lock_init(&table->lock);
+	INIT_RADIX_TREE(&table->tree, GFP_ATOMIC);
+}
+
+int xsc_core_eth_create_qp(struct xsc_core_device *xdev,
+			   struct xsc_create_qp_mbox_in *in,
+			   int insize, u32 *p_qpn)
+{
+	struct xsc_create_qp_mbox_out out;
+	int ret;
+
+	in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_CREATE_QP);
+	ret = xsc_cmd_exec(xdev, in, insize, &out, sizeof(out));
+	if (ret) {
+		pci_err(xdev->pdev, "failed to create sq, err=%d out.status=%u\n",
+			ret, out.hdr.status);
+		return ret;
+	}
+
+	*p_qpn = be32_to_cpu(out.qpn) & 0xffffff;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_eth_create_qp);
+
+int xsc_core_eth_modify_qp_status(struct xsc_core_device *xdev,
+				  u32 qpn, u16 status)
+{
+	struct xsc_modify_qp_mbox_out out;
+	struct xsc_modify_qp_mbox_in in = {};
+	int ret = 0;
+
+	in.hdr.opcode = cpu_to_be16(status);
+	in.qpn = cpu_to_be32(qpn);
+	in.no_need_wait = 1;
+
+	ret = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (ret) {
+		pci_err(xdev->pdev, "failed to modify qp %u status=%u, err=%d out.status %u\n",
+			qpn, status, ret, out.hdr.status);
+		return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_eth_modify_qp_status);
+
+int xsc_core_eth_destroy_qp(struct xsc_core_device *xdev, u32 qpn)
+{
+	struct xsc_destroy_qp_mbox_out out;
+	struct xsc_destroy_qp_mbox_in in;
+	int err;
+
+	err = xsc_core_eth_modify_qp_status(xdev, qpn, XSC_CMD_OP_2RST_QP);
+	if (err) {
+		pci_err(xdev->pdev, "failed to set sq%d status=rst, err=%d\n",
+			qpn, err);
+		return err;
+	}
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DESTROY_QP);
+	in.qpn = cpu_to_be32(qpn);
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err) {
+		pci_err(xdev->pdev, "failed to destroy sq%d, err=%d out.status=%u\n",
+			qpn, err, out.hdr.status);
+		return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_eth_destroy_qp);
+
+int xsc_core_eth_modify_raw_qp(struct xsc_core_device *xdev,
+			       struct xsc_modify_raw_qp_mbox_in *in)
+{
+	struct xsc_modify_raw_qp_mbox_out out;
+	int err;
+
+	in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_MODIFY_RAW_QP);
+
+	err = xsc_cmd_exec(xdev, in, sizeof(struct xsc_modify_raw_qp_mbox_in),
+			   &out, sizeof(struct xsc_modify_raw_qp_mbox_out));
+	if (err) {
+		pci_err(xdev->pdev, "failed to modify sq, err=%d out.status=%u\n",
+			err, out.hdr.status);
+		return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_eth_modify_raw_qp);
+
+int xsc_core_eth_create_rss_qp_rqs(struct xsc_core_device *xdev,
+				   struct xsc_create_multiqp_mbox_in *in,
+				   int insize,
+				   u32 *p_qpn_base)
+{
+	struct xsc_create_multiqp_mbox_out out;
+	int err;
+
+	in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_CREATE_MULTI_QP);
+	err = xsc_cmd_exec(xdev, in, insize, &out, sizeof(out));
+	if (err) {
+		pci_err(xdev->pdev,
+			"failed to create rss rq, qp_num=%d, type=%d, err=%d out.status=%u\n",
+			in->qp_num, in->qp_type, err, out.hdr.status);
+		return err;
+	}
+
+	*p_qpn_base = be32_to_cpu(out.qpn_base) & 0xffffff;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_eth_create_rss_qp_rqs);
+
+int xsc_core_create_qp(struct xsc_core_device *xdev,
+		       struct xsc_core_qp *qp,
+		       struct xsc_create_qp_mbox_in *in,
+		       int inlen)
+{
+	struct xsc_create_qp_mbox_out out;
+	struct xsc_destroy_qp_mbox_in din;
+	struct xsc_destroy_qp_mbox_out dout;
+	int err;
+
+	memset(&out, 0, sizeof(out));
+	in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_CREATE_QP);
+
+	err = xsc_cmd_exec(xdev, in, inlen, &out, sizeof(out));
+	if (err)
+		return err;
+
+	if (out.hdr.status)
+		return xsc_cmd_status_to_err(&out.hdr);
+
+	qp->qpn = be32_to_cpu(out.qpn) & 0xffffff;
+
+	err = xsc_core_create_resource_common(xdev, qp);
+	if (err)
+		goto err_cmd;
+
+	atomic_inc(&xdev->num_qps);
+	return 0;
+
+err_cmd:
+	memset(&din, 0, sizeof(din));
+	memset(&dout, 0, sizeof(dout));
+	din.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DESTROY_QP);
+	din.qpn = cpu_to_be32(qp->qpn);
+	xsc_cmd_exec(xdev, &din, sizeof(din), &dout, sizeof(dout));
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(xsc_core_create_qp);
+
+int xsc_core_destroy_qp(struct xsc_core_device *xdev,
+			struct xsc_core_qp *qp)
+{
+	struct xsc_destroy_qp_mbox_in in;
+	struct xsc_destroy_qp_mbox_out out;
+	int err;
+
+	xsc_core_destroy_resource_common(xdev, qp);
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DESTROY_QP);
+	in.qpn = cpu_to_be32(qp->qpn);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err)
+		return err;
+
+	if (out.hdr.status)
+		return xsc_cmd_status_to_err(&out.hdr);
+
+	atomic_dec(&xdev->num_qps);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_destroy_qp);
+
+int xsc_core_qp_modify(struct xsc_core_device *xdev, enum xsc_qp_state cur_state,
+		       enum xsc_qp_state new_state,
+		       struct xsc_modify_qp_mbox_in *in, int sqd_event,
+		       struct xsc_core_qp *qp)
+{
+	static const u16 optab[XSC_QP_NUM_STATE][XSC_QP_NUM_STATE] = {
+		[XSC_QP_STATE_RST] = {
+			[XSC_QP_STATE_RST]	= XSC_CMD_OP_2RST_QP,
+			[XSC_QP_STATE_ERR]	= XSC_CMD_OP_2ERR_QP,
+			[XSC_QP_STATE_INIT]	= XSC_CMD_OP_RST2INIT_QP,
+		},
+		[XSC_QP_STATE_INIT] = {
+			[XSC_QP_STATE_RST]	= XSC_CMD_OP_2RST_QP,
+			[XSC_QP_STATE_ERR]	= XSC_CMD_OP_2ERR_QP,
+			[XSC_QP_STATE_INIT]	= XSC_CMD_OP_INIT2INIT_QP,
+			[XSC_QP_STATE_RTR]	= XSC_CMD_OP_INIT2RTR_QP,
+		},
+		[XSC_QP_STATE_RTR] = {
+			[XSC_QP_STATE_RST]	= XSC_CMD_OP_2RST_QP,
+			[XSC_QP_STATE_ERR]	= XSC_CMD_OP_2ERR_QP,
+			[XSC_QP_STATE_RTS]	= XSC_CMD_OP_RTR2RTS_QP,
+		},
+		[XSC_QP_STATE_RTS] = {
+			[XSC_QP_STATE_RST]	= XSC_CMD_OP_2RST_QP,
+			[XSC_QP_STATE_ERR]	= XSC_CMD_OP_2ERR_QP,
+			[XSC_QP_STATE_RTS]	= XSC_CMD_OP_RTS2RTS_QP,
+			[XSC_QP_STATE_SQD]	= XSC_CMD_OP_RTS2SQD_QP,
+		},
+		[XSC_QP_STATE_SQD] = {
+			[XSC_QP_STATE_RST]	= XSC_CMD_OP_2RST_QP,
+			[XSC_QP_STATE_ERR]	= XSC_CMD_OP_2ERR_QP,
+			[XSC_QP_STATE_RTS]	= XSC_CMD_OP_SQD2RTS_QP,
+			[XSC_QP_STATE_SQD]	= XSC_CMD_OP_SQD2SQD_QP,
+		},
+		[XSC_QP_STATE_SQER] = {
+			[XSC_QP_STATE_RST]	= XSC_CMD_OP_2RST_QP,
+			[XSC_QP_STATE_ERR]	= XSC_CMD_OP_2ERR_QP,
+			[XSC_QP_STATE_RTS]	= XSC_CMD_OP_SQERR2RTS_QP,
+		},
+		[XSC_QP_STATE_ERR] = {
+			[XSC_QP_STATE_RST]	= XSC_CMD_OP_2RST_QP,
+			[XSC_QP_STATE_ERR]	= XSC_CMD_OP_2ERR_QP,
+		}
+	};
+
+	struct xsc_modify_qp_mbox_out out;
+	u16 op;
+
+	if (cur_state >= XSC_QP_NUM_STATE || new_state >= XSC_QP_NUM_STATE ||
+	    !optab[cur_state][new_state])
+		return -EINVAL;
+
+	memset(&out, 0, sizeof(out));
+	op = optab[cur_state][new_state];
+
+	if (new_state == XSC_QP_STATE_RTR) {
+		in->ctx.pcie_no = xdev->pcie_no;
+		in->ctx.func_id = cpu_to_be16(xdev->glb_func_id);
+	}
+
+	in->hdr.opcode = cpu_to_be16(op);
+	in->qpn = cpu_to_be32(qp->qpn);
+	in->no_need_wait = 1;
+
+	if (xsc_cmd_exec(xdev, in, sizeof(*in), &out, sizeof(out)))
+		return -ENOEXEC;
+
+	if (out.hdr.status)
+		return xsc_cmd_status_to_err(&out.hdr);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_qp_modify);
+
+int xsc_core_qp_query(struct xsc_core_device *xdev, struct xsc_core_qp *qp,
+		      struct xsc_query_qp_mbox_out *out, int outlen)
+{
+	struct xsc_query_qp_mbox_in in;
+	int err;
+
+	memset(&in, 0, sizeof(in));
+	memset(out, 0, outlen);
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_QUERY_QP);
+	in.qpn = cpu_to_be32(qp->qpn);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), out, outlen);
+	if (err)
+		return err;
+
+	if (out->hdr.status)
+		return xsc_cmd_status_to_err(&out->hdr);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_qp_query);

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

* [PATCH 06/15] yunsilicon: add ethernet netdev registration
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (4 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 05/15] yunsilicon: add the CQ and QP resource pools Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 07/15] yunsilicon: add ethernet TX path Xin Tian
                   ` (9 subsequent siblings)
  15 siblings, 1 reply; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the ethernet auxiliary driver: allocate the netdev, program the MAC
address and the MTU, register it, and implement ndo_open/ndo_stop which
flip the port admin status and register the link event handler.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 drivers/net/ethernet/yunsilicon/Kconfig       |   2 +-
 drivers/net/ethernet/yunsilicon/Makefile      |   2 +-
 .../net/ethernet/yunsilicon/xsc/net/Kconfig   |  17 +
 .../net/ethernet/yunsilicon/xsc/net/Makefile  |   9 +
 .../net/ethernet/yunsilicon/xsc/net/main.c    | 464 ++++++++++++++++++
 .../net/ethernet/yunsilicon/xsc/net/xsc_eth.h |  45 ++
 .../yunsilicon/xsc/net/xsc_eth_common.h       | 124 +++++
 .../yunsilicon/xsc/net/xsc_eth_stats.h        |  34 ++
 .../yunsilicon/xsc/net/xsc_eth_txrx.h         |  91 ++++
 .../ethernet/yunsilicon/xsc/net/xsc_eth_wq.c  |  80 +++
 .../ethernet/yunsilicon/xsc/net/xsc_eth_wq.h  | 185 +++++++
 .../net/ethernet/yunsilicon/xsc/net/xsc_pp.h  |  38 ++
 .../net/ethernet/yunsilicon/xsc/net/xsc_pph.h |  42 ++
 .../ethernet/yunsilicon/xsc/net/xsc_queue.h   | 185 +++++++
 .../net/ethernet/yunsilicon/xsc/pci/Makefile  |   2 +-
 .../net/ethernet/yunsilicon/xsc/pci/vport.c   |  34 ++
 16 files changed, 1351 insertions(+), 3 deletions(-)
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/Kconfig
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/Makefile
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/main.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_common.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_wq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_wq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_pp.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_pph.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_queue.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/vport.c

diff --git a/drivers/net/ethernet/yunsilicon/Kconfig b/drivers/net/ethernet/yunsilicon/Kconfig
index ffdfdf4bf..46af1fdd0 100644
--- a/drivers/net/ethernet/yunsilicon/Kconfig
+++ b/drivers/net/ethernet/yunsilicon/Kconfig
@@ -1,7 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 # Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
 # All rights reserved.
-#
 # Yunsilicon driver configuration
 #
 
@@ -21,6 +20,7 @@ config NET_VENDOR_YUNSILICON
 
 if NET_VENDOR_YUNSILICON
 
+source "drivers/net/ethernet/yunsilicon/xsc/net/Kconfig"
 source "drivers/net/ethernet/yunsilicon/xsc/pci/Kconfig"
 
 endif # NET_VENDOR_YUNSILICON
diff --git a/drivers/net/ethernet/yunsilicon/Makefile b/drivers/net/ethernet/yunsilicon/Makefile
index b2924ccaf..a89a5ce24 100644
--- a/drivers/net/ethernet/yunsilicon/Makefile
+++ b/drivers/net/ethernet/yunsilicon/Makefile
@@ -1,8 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0
 # Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
 # All rights reserved.
-#
 # Makefile for the Yunsilicon device drivers.
 #
 
+obj-$(CONFIG_YUNSILICON_XSC_ETH) += xsc/net/
 obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc/pci/
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/Kconfig b/drivers/net/ethernet/yunsilicon/xsc/net/Kconfig
new file mode 100644
index 000000000..7428b2629
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+# All rights reserved.
+# Yunsilicon driver configuration
+#
+
+config YUNSILICON_XSC_ETH
+	tristate "Yunsilicon XSC ethernet driver"
+	depends on YUNSILICON_XSC_PCI
+	depends on NET
+	select PAGE_POOL
+	help
+	  This driver provides ethernet support for
+	  Yunsilicon XSC devices.
+
+	  To compile this driver as a module, choose M here. The module
+	  will be called xsc_eth.
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/Makefile b/drivers/net/ethernet/yunsilicon/xsc/net/Makefile
new file mode 100644
index 000000000..0e6249cec
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+# All rights reserved.
+
+ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
+
+obj-$(CONFIG_YUNSILICON_XSC_ETH) += xsc_eth.o
+
+xsc_eth-y := main.o xsc_eth_wq.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/main.c b/drivers/net/ethernet/yunsilicon/xsc/net/main.c
new file mode 100644
index 000000000..b92c3ed19
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/main.c
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/irq.h>
+
+#include "common/xsc_core.h"
+#include "common/xsc_cmd_api.h"
+#include "common/xsc_device.h"
+#include "xsc_pp.h"
+#include "xsc_eth_common.h"
+#include "xsc_eth.h"
+#include "xsc_eth_txrx.h"
+
+static const struct xsc_tirc_config tirc_default_config[XSC_NUM_INDIR_TIRS] = {
+	[XSC_TT_IPV4] = {
+				.l3_prot_type = XSC_L3_PROT_TYPE_IPV4,
+				.l4_prot_type = 0,
+				.rx_hash_fields = XSC_HASH_IP,
+	},
+	[XSC_TT_IPV4_TCP] = {
+				.l3_prot_type = XSC_L3_PROT_TYPE_IPV4,
+				.l4_prot_type = XSC_L4_PROT_TYPE_TCP,
+				.rx_hash_fields = XSC_HASH_IP_PORTS,
+	},
+	[XSC_TT_IPV4_UDP] = {
+				.l3_prot_type = XSC_L3_PROT_TYPE_IPV4,
+				.l4_prot_type = XSC_L4_PROT_TYPE_UDP,
+				.rx_hash_fields = XSC_HASH_IP_PORTS,
+	},
+	[XSC_TT_IPV6] = {
+				.l3_prot_type = XSC_L3_PROT_TYPE_IPV6,
+				.l4_prot_type = 0,
+				.rx_hash_fields = XSC_HASH_IP6,
+	},
+	[XSC_TT_IPV6_TCP] = {
+				.l3_prot_type = XSC_L3_PROT_TYPE_IPV6,
+				.l4_prot_type = XSC_L4_PROT_TYPE_TCP,
+				.rx_hash_fields = XSC_HASH_IP6_PORTS,
+	},
+	[XSC_TT_IPV6_UDP] = {
+				.l3_prot_type = XSC_L3_PROT_TYPE_IPV6,
+				.l4_prot_type = XSC_L4_PROT_TYPE_UDP,
+				.rx_hash_fields = XSC_HASH_IP6_PORTS,
+	},
+};
+
+static int xsc_get_max_num_channels(struct xsc_core_device *xdev)
+{
+	return min_t(int, xdev->dev_res->eq_table.num_comp_vectors,
+		     XSC_ETH_MAX_NUM_CHANNELS);
+}
+
+static void xsc_build_rss_param(struct xsc_rss_params *rss_param)
+{
+	enum xsc_traffic_types tt;
+
+	for (tt = 0; tt < XSC_NUM_INDIR_TIRS; tt++) {
+		rss_param->rx_hash_fields[tt] =
+			tirc_default_config[tt].rx_hash_fields;
+	}
+	rss_param->rss_hash_tmpl = XSC_HASH_IP_PORTS | XSC_HASH_IP6_PORTS;
+}
+
+static void xsc_eth_build_nic_params(struct xsc_adapter *adapter, u32 ch_num)
+{
+	struct xsc_eth_params *params = &adapter->nic_param;
+	struct xsc_core_device *xdev = adapter->xdev;
+
+	params->mtu = SW_DEFAULT_MTU;
+
+	params->comp_vectors = xdev->dev_res->eq_table.num_comp_vectors;
+	params->max_num_ch = ch_num;
+	params->num_channels = ch_num;
+
+	xsc_build_rss_param(&adapter->rss_param);
+}
+
+static int xsc_eth_netdev_init(struct xsc_adapter *adapter)
+{
+	unsigned int node, nch;
+
+	nch = adapter->nic_param.max_num_ch;
+	node = dev_to_node(adapter->dev);
+	adapter->txq2sq = kcalloc_node(nch,
+				       sizeof(*adapter->txq2sq),
+				       GFP_KERNEL, node);
+	if (!adapter->txq2sq)
+		return -ENOMEM;
+
+	netif_carrier_off(adapter->netdev);
+
+	return 0;
+}
+
+static bool xsc_eth_get_link_status(struct xsc_adapter *adapter)
+{
+	struct xsc_core_device *xdev = adapter->xdev;
+	u16 vport = 0;
+	bool link_up;
+
+	link_up = xsc_core_query_vport_state(xdev, vport);
+
+	return link_up ? true : false;
+}
+
+static int xsc_eth_change_link_status(struct xsc_adapter *adapter)
+{
+	bool link_up;
+
+	link_up = xsc_eth_get_link_status(adapter);
+
+	if (link_up && !netif_carrier_ok(adapter->netdev)) {
+		netdev_info(adapter->netdev, "Link up\n");
+		netif_carrier_on(adapter->netdev);
+	} else if (!link_up && netif_carrier_ok(adapter->netdev)) {
+		netdev_info(adapter->netdev, "Link down\n");
+		netif_carrier_off(adapter->netdev);
+	}
+
+	return 0;
+}
+
+/*
+ * Registered with the PCI layer, which runs it on its own workqueue for
+ * every XSC_CMD_EVENT_RESP_CHANGE_LINK event.
+ */
+static void xsc_eth_link_event_handler(void *arg)
+{
+	struct xsc_core_device *xdev = arg;
+	struct xsc_adapter *adapter = xdev->eth_priv;
+	int err;
+
+	if (!adapter || !adapter->opened)
+		return;
+
+	err = xsc_eth_change_link_status(adapter);
+	if (err)
+		netdev_err(adapter->netdev, "failed to change linkstatus, err=%d\n",
+			   err);
+}
+
+static void xsc_set_default_xps_cpumasks(struct xsc_adapter *adapter)
+{
+	struct xsc_core_device *xdev = adapter->xdev;
+	int num_comp_vectors, irq;
+
+	num_comp_vectors = adapter->nic_param.comp_vectors;
+	cpumask_clear(xdev->xps_cpumask);
+
+	for (irq = 0; irq < num_comp_vectors; irq++) {
+		cpumask_set_cpu(cpumask_local_spread(irq, xdev->numa_node),
+				xdev->xps_cpumask);
+		netif_set_xps_queue(adapter->netdev, xdev->xps_cpumask, irq);
+	}
+}
+
+static int xsc_set_port_admin_status(struct xsc_adapter *adapter,
+				     enum xsc_port_status status)
+{
+	struct xsc_event_set_port_admin_status_mbox_out out;
+	struct xsc_event_set_port_admin_status_mbox_in in = {};
+
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_SET_PORT_ADMIN_STATUS);
+	in.status = cpu_to_be16(status);
+
+	return xsc_cmd_exec(adapter->xdev, &in, sizeof(in), &out, sizeof(out));
+}
+
+static int xsc_eth_open(struct net_device *netdev)
+{
+	struct xsc_adapter *adapter = netdev_priv(netdev);
+	struct xsc_core_device *xdev = adapter->xdev;
+
+	if (adapter->opened) {
+		netdev_err(adapter->netdev, "ndo_open on an already opened device\n");
+		return 0;
+	}
+	xsc_irq_event_register(xdev, xsc_eth_link_event_handler);
+
+	if (xsc_eth_get_link_status(adapter))	{
+		netdev_info(netdev, "Link up\n");
+		netif_carrier_on(adapter->netdev);
+	} else {
+		netdev_info(netdev, "Link down\n");
+	}
+
+	adapter->opened = true;
+
+	xsc_set_default_xps_cpumasks(adapter);
+
+	xsc_set_port_admin_status(adapter, XSC_PORT_UP);
+
+	return 0;
+}
+
+static int xsc_eth_close(struct net_device *netdev)
+{
+	struct xsc_adapter *adapter = netdev_priv(netdev);
+	struct xsc_core_device *xdev = adapter->xdev;
+
+	if (!adapter->opened)
+		return 0;
+	adapter->opened = false;
+
+	netif_carrier_off(adapter->netdev);
+
+	xsc_irq_event_unregister(xdev);
+
+	xsc_set_port_admin_status(adapter, XSC_PORT_DOWN);
+
+	return 0;
+}
+
+static int xsc_eth_set_hw_mtu(struct xsc_core_device *xdev,
+			      u16 mtu, u16 rx_buf_sz)
+{
+	struct xsc_set_mtu_mbox_out out;
+	struct xsc_set_mtu_mbox_in in;
+	int err;
+
+	memset(&in, 0, sizeof(struct xsc_set_mtu_mbox_in));
+	memset(&out, 0, sizeof(struct xsc_set_mtu_mbox_out));
+
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_SET_MTU);
+	in.mtu = cpu_to_be16(mtu);
+	in.rx_buf_sz_min = cpu_to_be16(rx_buf_sz);
+	in.mac_port = xdev->mac_port;
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(struct xsc_set_mtu_mbox_in), &out,
+			   sizeof(struct xsc_set_mtu_mbox_out));
+	if (err) {
+		netdev_err(xsc_dev_to_netdev(xdev),
+			   "failed to set hw_mtu=%u rx_buf_sz=%u, err=%d, status=%d\n",
+			   mtu, rx_buf_sz, err, out.hdr.status);
+		return err;
+	}
+
+	return 0;
+}
+
+static const struct net_device_ops xsc_netdev_ops = {
+	.ndo_open		= xsc_eth_open,
+	.ndo_stop		= xsc_eth_close,
+};
+
+static void xsc_eth_build_nic_netdev(struct xsc_adapter *adapter)
+{
+	struct net_device *netdev = adapter->netdev;
+
+	/* Set up network device as normal. */
+	netdev->netdev_ops = &xsc_netdev_ops;
+
+	netdev->min_mtu = SW_DEFAULT_MTU;
+	netdev->max_mtu = SW_DEFAULT_MTU;
+	/*mtu - macheaderlen - ipheaderlen should be aligned in 8B*/
+	netdev->mtu = SW_DEFAULT_MTU;
+
+	netdev->features = NETIF_F_HIGHDMA;
+}
+
+static int xsc_eth_nic_init(struct xsc_adapter *adapter, u32 ch_num)
+{
+	int err;
+
+	xsc_eth_build_nic_params(adapter, ch_num);
+
+	err = xsc_eth_netdev_init(adapter);
+	if (err)
+		return err;
+
+	xsc_eth_build_nic_netdev(adapter);
+
+	return 0;
+}
+
+static void xsc_eth_nic_cleanup(struct xsc_adapter *adapter)
+{
+	kfree(adapter->txq2sq);
+}
+
+static int xsc_eth_get_mac(struct xsc_core_device *xdev, char *mac)
+{
+	struct xsc_query_eth_mac_mbox_out *out;
+	struct xsc_query_eth_mac_mbox_in in;
+	int err;
+
+	out = kzalloc_obj(*out, GFP_KERNEL);
+	if (!out)
+		return -ENOMEM;
+
+	memset(&in, 0, sizeof(in));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_QUERY_ETH_MAC);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), out, sizeof(*out));
+	if (err || out->hdr.status) {
+		netdev_warn(xsc_dev_to_netdev(xdev),
+			    "get mac failed! err=%d, out.status=%u\n",
+			    err, out->hdr.status);
+		err = -ENOEXEC;
+		goto exit;
+	}
+
+	memcpy(mac, out->mac, ETH_ALEN);
+	netdev_dbg(xsc_dev_to_netdev(xdev),
+		   "get mac %02x:%02x:%02x:%02x:%02x:%02x\n",
+		   mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+
+exit:
+	kfree(out);
+	return err;
+}
+
+static void xsc_eth_l2_addr_init(struct xsc_adapter *adapter)
+{
+	struct net_device *netdev = adapter->netdev;
+	char mac[6] = {0};
+	int ret = 0;
+
+	ret = xsc_eth_get_mac(adapter->xdev, mac);
+	if (ret) {
+		netdev_warn(netdev, "get mac failed %d, generate random mac...",
+			    ret);
+		eth_random_addr(mac);
+	}
+	eth_hw_addr_set(netdev, mac);
+
+	if (!is_valid_ether_addr(netdev->perm_addr))
+		memcpy(netdev->perm_addr, netdev->dev_addr, netdev->addr_len);
+}
+
+/*
+ * Program the MAC address and the MTU into the hardware. Called once from
+ * probe(), before the netdev is registered: the netdev layer marks the
+ * device present itself in register_netdevice(), so there is nothing to
+ * attach here and no need to take the rtnl lock.
+ */
+static int xsc_eth_nic_enable(struct xsc_adapter *adapter)
+{
+	struct xsc_core_device *xdev = adapter->xdev;
+
+	xsc_eth_l2_addr_init(adapter);
+
+	return xsc_eth_set_hw_mtu(xdev, XSC_SW2HW_MTU(adapter->nic_param.mtu),
+				  XSC_SW2HW_RX_PKT_LEN(adapter->nic_param.mtu));
+}
+
+static int xsc_eth_probe(struct auxiliary_device *adev,
+			 const struct auxiliary_device_id *adev_id)
+{
+	struct xsc_adev *xsc_adev = container_of(adev, struct xsc_adev, adev);
+	struct xsc_core_device *xdev = xsc_adev->xdev;
+	struct xsc_adapter *adapter;
+	struct net_device *netdev;
+	int num_chl;
+	int err;
+
+	num_chl = xsc_get_max_num_channels(xdev);
+
+	netdev = alloc_etherdev_mqs(sizeof(struct xsc_adapter),
+				    num_chl, num_chl);
+	if (!netdev) {
+		pr_err("alloc_etherdev_mqs failed, txq=%d, rxq=%d\n",
+		       num_chl, num_chl);
+		return -ENOMEM;
+	}
+
+	netdev->dev.parent = &xdev->pdev->dev;
+	adapter = netdev_priv(netdev);
+	adapter->netdev = netdev;
+	adapter->pdev = xdev->pdev;
+	adapter->dev = &adapter->pdev->dev;
+	adapter->xdev = xdev;
+	xdev->eth_priv = adapter;
+
+	err = xsc_eth_nic_init(adapter, num_chl);
+	if (err) {
+		netdev_err(netdev, "xsc_eth_nic_init failed, err=%d\n", err);
+		goto err_free_netdev;
+	}
+
+	err = xsc_eth_nic_enable(adapter);
+	if (err) {
+		netdev_err(netdev, "xsc_eth_nic_enable failed, err=%d\n", err);
+		goto err_nic_cleanup;
+	}
+
+	adapter->stats = kvzalloc_obj(*adapter->stats, GFP_KERNEL);
+	if (!adapter->stats) {
+		err = -ENOMEM;
+		goto err_nic_cleanup;
+	}
+
+	err = register_netdev(netdev);
+	if (err) {
+		netdev_err(netdev, "register_netdev failed, err=%d\n", err);
+		goto err_free_stats;
+	}
+
+	xdev->netdev = netdev;
+
+	return 0;
+
+err_free_stats:
+	kvfree(adapter->stats);
+err_nic_cleanup:
+	xsc_eth_nic_cleanup(adapter);
+err_free_netdev:
+	xdev->eth_priv = NULL;
+	free_netdev(netdev);
+
+	return err;
+}
+
+static void xsc_eth_remove(struct auxiliary_device *adev)
+{
+	struct xsc_adev *xsc_adev = container_of(adev, struct xsc_adev, adev);
+	struct xsc_core_device *xdev = xsc_adev->xdev;
+	struct xsc_adapter *adapter;
+
+	if (!xdev)
+		return;
+
+	adapter = xdev->eth_priv;
+	if (!adapter)
+		return;
+
+	/* Stop link events reaching this adapter and wait out one already in
+	 * flight, which would otherwise touch the memory freed below.
+	 */
+	xsc_irq_event_unregister(xdev);
+
+	/* Runs ndo_stop(), so the queues and the HCA are shut down first. */
+	unregister_netdev(adapter->netdev);
+
+	/* Must precede free_netdev(): the adapter lives in the netdev priv. */
+	xsc_eth_nic_cleanup(adapter);
+	kvfree(adapter->stats);
+	free_netdev(adapter->netdev);
+
+	xdev->netdev = NULL;
+	xdev->eth_priv = NULL;
+}
+
+static const struct auxiliary_device_id xsc_eth_id_table[] = {
+	{ .name = XSC_PCI_DRV_NAME "." XSC_ETH_ADEV_NAME },
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, xsc_eth_id_table);
+
+static struct auxiliary_driver xsc_eth_driver = {
+	.name = "eth",
+	.probe = xsc_eth_probe,
+	.remove = xsc_eth_remove,
+	.id_table = xsc_eth_id_table,
+};
+module_auxiliary_driver(xsc_eth_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Yunsilicon XSC ethernet driver");
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth.h b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth.h
new file mode 100644
index 000000000..4d9f92e09
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_ETH_H
+#define __XSC_ETH_H
+
+#include <linux/udp.h>
+
+#include "common/xsc_device.h"
+#include "xsc_eth_common.h"
+#include "xsc_eth_stats.h"
+
+#define XSC_INVALID_LKEY	0x100
+
+struct xsc_rss_params {
+	u32	rx_hash_fields[XSC_NUM_INDIR_TIRS];
+	u32	rss_hash_tmpl;
+};
+
+struct xsc_adapter {
+	struct net_device	*netdev;
+	struct pci_dev		*pdev;
+	struct device		*dev;
+	struct xsc_core_device	*xdev;
+
+	struct xsc_eth_params	nic_param;
+	struct xsc_rss_params	rss_param;
+
+	struct xsc_eth_channels	channels;
+	struct xsc_sq		**txq2sq;
+
+	/* true between ndo_open and ndo_stop */
+	bool			opened;
+
+	struct xsc_stats	*stats;
+};
+
+static inline struct net_device *xsc_dev_to_netdev(struct xsc_core_device *xdev)
+{
+	return ((struct xsc_adapter *)xdev->eth_priv)->netdev;
+}
+
+#endif /* __XSC_ETH_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_common.h b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_common.h
new file mode 100644
index 000000000..be476f31f
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_common.h
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_ETH_COMMON_H
+#define __XSC_ETH_COMMON_H
+
+#include "xsc_queue.h"
+#include "xsc_pph.h"
+
+#define SW_DEFAULT_MTU		ETH_DATA_LEN
+
+#define XSC_ETH_HW_MTU_SEND	9800
+#define XSC_ETH_HARD_MTU	(ETH_HLEN + VLAN_HLEN * 2 + ETH_FCS_LEN)
+#define XSC_SW2HW_MTU(mtu)	((mtu) + XSC_ETH_HARD_MTU)
+#define XSC_SW2HW_HLEN		(ETH_HLEN + VLAN_HLEN * 2 + ETH_FCS_LEN + \
+				 XSC_PPH_HEAD_LEN)
+#define XSC_SW2HW_FRAG_SIZE(mtu)	((mtu) + XSC_SW2HW_HLEN)
+#define XSC_ETH_RX_MAX_HEAD_ROOM	256
+#define XSC_SW2HW_RX_PKT_LEN(mtu)	\
+	((mtu) + ETH_HLEN + XSC_ETH_RX_MAX_HEAD_ROOM)
+
+#define XSC_RX_MAX_HEAD			(256)
+
+#define XSC_QPN_SQN_STUB		1025
+
+#define XSC_ETH_MAX_NUM_CHANNELS	256
+
+#define XSC_SKB_FRAG_SZ(len)	(SKB_DATA_ALIGN(len) +	\
+				SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
+
+#define XSC_RQCQ_ELE_SZ		32
+#define XSC_SQCQ_ELE_SZ		32
+
+#define XSC_LOG_RQCQ_SZ		0xb
+#define XSC_LOG_SQCQ_SZ		0xa
+
+#define XSC_RQCQ_ELE_NUM	BIT(XSC_LOG_RQCQ_SZ)
+#define XSC_SQCQ_ELE_NUM	BIT(XSC_LOG_SQCQ_SZ)
+
+enum xsc_port_status {
+	XSC_PORT_DOWN      = 0,
+	XSC_PORT_UP        = 1,
+};
+
+enum xsc_queue_type {
+	XSC_QUEUE_TYPE_RQCQ,
+	XSC_QUEUE_TYPE_SQCQ,
+	XSC_QUEUE_TYPE_RQ,
+	XSC_QUEUE_TYPE_SQ,
+};
+
+struct xsc_queue_attr {
+	u32 ele_num;
+	u32 ele_size;
+	u8  ele_log_size;
+	u8  q_log_size;
+};
+
+struct xsc_eth_rx_wqe_cyc {
+	DECLARE_FLEX_ARRAY(struct xsc_wqe_data_seg, data);
+};
+
+struct xsc_cq_param {
+	struct xsc_wq_param wq;
+	struct xsc_queue_attr cq_attr;
+};
+
+struct xsc_rq_param {
+	struct xsc_wq_param wq;
+	struct xsc_queue_attr rq_attr;
+	struct xsc_rq_frags_info frags_info;
+};
+
+struct xsc_sq_param {
+	struct xsc_wq_param wq;
+	struct xsc_queue_attr sq_attr;
+};
+
+struct xsc_channel_param {
+	struct xsc_cq_param rqcq_param;
+	struct xsc_cq_param sqcq_param;
+	struct xsc_rq_param rq_param;
+	struct xsc_sq_param sq_param;
+};
+
+struct xsc_eth_qp {
+	struct xsc_rq rq;
+	struct xsc_sq sq;
+};
+
+struct xsc_eth_params {
+	u16	num_channels;
+	u16	max_num_ch;
+	u32	mtu;
+	u32	comp_vectors;
+	u32	sq_size;
+	u32	rq_size;
+	u32	rq_frags_size;
+};
+
+struct xsc_channel {
+	/* data path */
+	struct xsc_eth_qp	qp;
+	struct napi_struct	napi;
+	int			chl_idx;
+
+	/*relationship*/
+	struct xsc_adapter	*adapter;
+	struct net_device	*netdev;
+	int			cpu;
+
+	/* data path - accessed per napi poll */
+	const struct cpumask	*aff_mask;
+} ____cacheline_aligned_in_smp;
+
+struct xsc_eth_channels {
+	struct xsc_channel	*c;
+	unsigned int		num_chl;
+	u32			rqn_base;
+};
+
+#endif
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.h b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.h
new file mode 100644
index 000000000..a391afe97
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_EN_STATS_H
+#define __XSC_EN_STATS_H
+
+#include "xsc_eth_common.h"
+
+struct xsc_rq_stats {
+	u64 packets;
+	u64 bytes;
+};
+
+struct xsc_sq_stats {
+	u64 packets;
+	u64 bytes;
+	u64 dropped;
+};
+
+struct xsc_channel_stats {
+	struct xsc_sq_stats sq;
+	struct xsc_rq_stats rq;
+} ____cacheline_aligned_in_smp;
+
+struct xsc_stats {
+	struct xsc_channel_stats channel_stats[XSC_ETH_MAX_NUM_CHANNELS];
+};
+
+void xsc_eth_fold_sw_stats64(struct xsc_adapter *adapter,
+			     struct rtnl_link_stats64 *s);
+
+#endif /* XSC_EN_STATS_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.h b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.h
new file mode 100644
index 000000000..8d14f943e
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_RXTX_H
+#define __XSC_RXTX_H
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+
+#include "xsc_eth.h"
+
+enum {
+	XSC_ETH_WQE_NONE_CSUM,
+	XSC_ETH_WQE_INNER_CSUM,
+	XSC_ETH_WQE_OUTER_CSUM,
+	XSC_ETH_WQE_INNER_AND_OUTER_CSUM,
+};
+
+void xsc_cq_notify_hw_rearm(struct xsc_cq *cq);
+void xsc_cq_notify_hw(struct xsc_cq *cq);
+int xsc_eth_napi_poll(struct napi_struct *napi, int budget);
+bool xsc_eth_post_rx_wqes(struct xsc_rq *rq);
+void xsc_eth_handle_rx_cqe(struct xsc_cqwq *cqwq,
+			   struct xsc_rq *rq, struct xsc_cqe *cqe);
+void xsc_eth_dealloc_rx_wqe(struct xsc_rq *rq, u16 ix);
+struct sk_buff *xsc_skb_from_cqe_linear(struct xsc_rq *rq,
+					struct xsc_wqe_frag_info *wi,
+					u32 cqe_bcnt, u8 has_pph);
+struct sk_buff *xsc_skb_from_cqe_nonlinear(struct xsc_rq *rq,
+					   struct xsc_wqe_frag_info *wi,
+					   u32 cqe_bcnt, u8 has_pph);
+int xsc_poll_rx_cq(struct xsc_cq *cq, int budget);
+
+netdev_tx_t xsc_eth_xmit_start(struct sk_buff *skb, struct net_device *netdev);
+
+static inline void xsc_tx_dma_unmap(struct device *dev, struct xsc_sq_dma *dma)
+{
+	switch (dma->type) {
+	case XSC_DMA_MAP_SINGLE:
+		dma_unmap_single(dev, dma->addr, dma->size, DMA_TO_DEVICE);
+		break;
+	case XSC_DMA_MAP_PAGE:
+		dma_unmap_page(dev, dma->addr, dma->size, DMA_TO_DEVICE);
+		break;
+	default:
+		break;
+	}
+}
+
+static inline struct xsc_sq_dma *xsc_dma_get(struct xsc_sq *sq, u32 i)
+{
+	return &sq->db.dma_fifo[i & sq->dma_fifo_mask];
+}
+
+static inline bool xsc_wqc_has_room_for(struct xsc_wq_cyc *wq,
+					u16 cc, u16 pc, u16 n)
+{
+	return (xsc_wq_cyc_ctr2ix(wq, cc - pc) >= n) || (cc == pc);
+}
+
+static inline struct xsc_cqe *xsc_cqwq_get_cqe_buff(struct xsc_cqwq *wq, u32 ix)
+{
+	struct xsc_cqe *cqe = xsc_frag_buf_get_wqe(&wq->fbc, ix);
+
+	return cqe;
+}
+
+static inline struct xsc_cqe *xsc_cqwq_get_cqe(struct xsc_cqwq *wq)
+{
+	u32 ci = xsc_cqwq_get_ci(wq);
+	u8 cqe_ownership_bit;
+	struct xsc_cqe *cqe;
+	u8 sw_ownership_val;
+
+	cqe = xsc_cqwq_get_cqe_buff(wq, ci);
+
+	cqe_ownership_bit = !!(le32_to_cpu(cqe->data1) & XSC_CQE_OWNER);
+	sw_ownership_val = xsc_cqwq_get_wrap_cnt(wq) & 1;
+
+	if (cqe_ownership_bit != sw_ownership_val)
+		return NULL;
+
+	/* ensure cqe content is read after cqe ownership bit */
+	dma_rmb();
+
+	return cqe;
+}
+
+#endif /* XSC_RXTX_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_wq.c b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_wq.c
new file mode 100644
index 000000000..b69a4e373
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_wq.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd. All
+ * rights reserved.
+ * Copyright (c) 2013-2015, Mellanox Technologies, Ltd.  All rights reserved.
+ */
+
+#include "xsc_eth_wq.h"
+#include "xsc_eth.h"
+
+u32 xsc_wq_cyc_get_size(struct xsc_wq_cyc *wq)
+{
+	return (u32)wq->fbc.sz_m1 + 1;
+}
+
+static u32 wq_get_byte_sz(u8 log_sz, u8 log_stride)
+{
+	return ((u32)1 << log_sz) << log_stride;
+}
+
+int xsc_eth_cqwq_create(struct xsc_core_device *xdev,
+			struct xsc_wq_param *param,
+			u8 q_log_size,
+			u8 ele_log_size,
+			struct xsc_cqwq *wq,
+			struct xsc_wq_ctrl *wq_ctrl)
+{
+	u8 log_wq_stride = ele_log_size;
+	u8 log_wq_sz     = q_log_size;
+	int err;
+
+	err = xsc_core_frag_buf_alloc_node(xdev,
+					   wq_get_byte_sz(log_wq_sz,
+							  log_wq_stride),
+					   &wq_ctrl->buf,
+					   param->buf_numa_node);
+	if (err) {
+		netdev_err(xsc_dev_to_netdev(xdev),
+			   "xsc_core_frag_buf_alloc_node failed, %d\n", err);
+		return err;
+	}
+
+	xsc_init_fbc(wq_ctrl->buf.frags, log_wq_stride, log_wq_sz, &wq->fbc);
+	wq_ctrl->xdev = xdev;
+	return 0;
+}
+
+int xsc_eth_wq_cyc_create(struct xsc_core_device *xdev,
+			  struct xsc_wq_param *param,
+			  u8 q_log_size,
+			  u8 ele_log_size,
+			  struct xsc_wq_cyc *wq,
+			  struct xsc_wq_ctrl *wq_ctrl)
+{
+	struct xsc_frag_buf_ctrl *fbc = &wq->fbc;
+	u8 log_wq_stride = ele_log_size;
+	u8 log_wq_sz = q_log_size;
+	int err;
+
+	err = xsc_core_frag_buf_alloc_node(xdev,
+					   wq_get_byte_sz(log_wq_sz,
+							  log_wq_stride),
+					   &wq_ctrl->buf,
+					   param->buf_numa_node);
+	if (err) {
+		netdev_err(xsc_dev_to_netdev(xdev),
+			   "xsc_core_frag_buf_alloc_node failed, %d\n", err);
+		return err;
+	}
+
+	xsc_init_fbc(wq_ctrl->buf.frags, log_wq_stride, log_wq_sz, fbc);
+	wq->sz = xsc_wq_cyc_get_size(wq);
+	wq_ctrl->xdev = xdev;
+	return 0;
+}
+
+void xsc_eth_wq_destroy(struct xsc_wq_ctrl *wq_ctrl)
+{
+	xsc_core_frag_buf_free(wq_ctrl->xdev, &wq_ctrl->buf);
+}
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_wq.h b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_wq.h
new file mode 100644
index 000000000..01cee9fd1
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_wq.h
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd. All
+ * rights reserved.
+ * Copyright (c) 2013-2015, Mellanox Technologies, Ltd.  All rights reserved.
+ */
+
+#ifndef __XSC_WQ_H
+#define __XSC_WQ_H
+
+#include "common/xsc_core.h"
+
+struct xsc_wq_param {
+	int		buf_numa_node;
+	int		db_numa_node;
+};
+
+struct xsc_wq_ctrl {
+	struct xsc_core_device	*xdev;
+	struct xsc_frag_buf	buf;
+};
+
+struct xsc_wq_cyc {
+	struct xsc_frag_buf_ctrl fbc;
+	u16			sz;
+	u16			wqe_ctr;
+	u16			cur_sz;
+};
+
+struct xsc_cqwq {
+	struct xsc_frag_buf_ctrl fbc;
+	__be32			  *db;
+	u32			  cc; /* consumer counter */
+};
+
+enum xsc_res_type {
+	XSC_RES_RQ,
+	XSC_RES_SQ,
+};
+
+u32 xsc_wq_cyc_get_size(struct xsc_wq_cyc *wq);
+
+/*api for eth driver*/
+int xsc_eth_cqwq_create(struct xsc_core_device *xdev,
+			struct xsc_wq_param *param,
+			u8 q_log_size,
+			u8 ele_log_size,
+			struct xsc_cqwq *wq,
+			struct xsc_wq_ctrl *wq_ctrl);
+
+int xsc_eth_wq_cyc_create(struct xsc_core_device *xdev,
+			  struct xsc_wq_param *param,
+			  u8 q_log_size,
+			  u8 ele_log_size,
+			  struct xsc_wq_cyc *wq,
+			  struct xsc_wq_ctrl *wq_ctrl);
+void xsc_eth_wq_destroy(struct xsc_wq_ctrl *wq_ctrl);
+
+static inline void xsc_init_fbc_offset(struct xsc_buf_list *frags,
+				       u8 log_stride, u8 log_sz,
+				       u16 strides_offset,
+				       struct xsc_frag_buf_ctrl *fbc)
+{
+	fbc->frags      = frags;
+	fbc->log_stride = log_stride;
+	fbc->log_sz     = log_sz;
+	fbc->sz_m1	= (1 << fbc->log_sz) - 1;
+	fbc->log_frag_strides = PAGE_SHIFT - fbc->log_stride;
+	fbc->frag_sz_m1	= (1 << fbc->log_frag_strides) - 1;
+	fbc->strides_offset = strides_offset;
+}
+
+static inline void xsc_init_fbc(struct xsc_buf_list *frags,
+				u8 log_stride, u8 log_sz,
+				struct xsc_frag_buf_ctrl *fbc)
+{
+	xsc_init_fbc_offset(frags, log_stride, log_sz, 0, fbc);
+}
+
+static inline void *xsc_frag_buf_get_wqe(struct xsc_frag_buf_ctrl *fbc,
+					 u32 ix)
+{
+	unsigned int frag;
+
+	ix  += fbc->strides_offset;
+	frag = ix >> fbc->log_frag_strides;
+
+	return fbc->frags[frag].buf +
+		((fbc->frag_sz_m1 & ix) << fbc->log_stride);
+}
+
+static inline u32
+xsc_frag_buf_get_idx_last_contig_stride(struct xsc_frag_buf_ctrl *fbc, u32 ix)
+{
+	u32 last_frag_stride_idx = (ix + fbc->strides_offset) | fbc->frag_sz_m1;
+
+	return min_t(u32, last_frag_stride_idx - fbc->strides_offset,
+		     fbc->sz_m1);
+}
+
+static inline int xsc_wq_cyc_missing(struct xsc_wq_cyc *wq)
+{
+	return wq->sz - wq->cur_sz;
+}
+
+static inline int xsc_wq_cyc_is_empty(struct xsc_wq_cyc *wq)
+{
+	return !wq->cur_sz;
+}
+
+static inline void xsc_wq_cyc_push(struct xsc_wq_cyc *wq)
+{
+	wq->wqe_ctr++;
+	wq->cur_sz++;
+}
+
+static inline void xsc_wq_cyc_push_n(struct xsc_wq_cyc *wq, u8 n)
+{
+	wq->wqe_ctr += n;
+	wq->cur_sz += n;
+}
+
+static inline void xsc_wq_cyc_pop(struct xsc_wq_cyc *wq)
+{
+	wq->cur_sz--;
+}
+
+static inline u16 xsc_wq_cyc_ctr2ix(struct xsc_wq_cyc *wq, u16 ctr)
+{
+	return ctr & wq->fbc.sz_m1;
+}
+
+static inline u16 xsc_wq_cyc_get_head(struct xsc_wq_cyc *wq)
+{
+	return xsc_wq_cyc_ctr2ix(wq, wq->wqe_ctr);
+}
+
+static inline u16 xsc_wq_cyc_get_tail(struct xsc_wq_cyc *wq)
+{
+	return xsc_wq_cyc_ctr2ix(wq, wq->wqe_ctr - wq->cur_sz);
+}
+
+static inline void *xsc_wq_cyc_get_wqe(struct xsc_wq_cyc *wq, u16 ix)
+{
+	return xsc_frag_buf_get_wqe(&wq->fbc, ix);
+}
+
+static inline u32 xsc_cqwq_ctr2ix(struct xsc_cqwq *wq, u32 ctr)
+{
+	return ctr & wq->fbc.sz_m1;
+}
+
+static inline u32 xsc_cqwq_get_ci(struct xsc_cqwq *wq)
+{
+	return xsc_cqwq_ctr2ix(wq, wq->cc);
+}
+
+static inline u32 xsc_cqwq_get_ctr_wrap_cnt(struct xsc_cqwq *wq, u32 ctr)
+{
+	return ctr >> wq->fbc.log_sz;
+}
+
+static inline u32 xsc_cqwq_get_wrap_cnt(struct xsc_cqwq *wq)
+{
+	return xsc_cqwq_get_ctr_wrap_cnt(wq, wq->cc);
+}
+
+static inline void xsc_cqwq_pop(struct xsc_cqwq *wq)
+{
+	wq->cc++;
+}
+
+static inline u32 xsc_cqwq_get_size(struct xsc_cqwq *wq)
+{
+	return wq->fbc.sz_m1 + 1;
+}
+
+static inline struct xsc_cqe *xsc_cqwq_get_wqe(struct xsc_cqwq *wq, u32 ix)
+{
+	struct xsc_cqe *cqe = xsc_frag_buf_get_wqe(&wq->fbc, ix);
+
+	return cqe;
+}
+
+#endif /* __XSC_WQ_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_pp.h b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_pp.h
new file mode 100644
index 000000000..cc9eec866
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_pp.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_PP_H
+#define __XSC_PP_H
+
+enum {
+	XSC_HASH_FIELD_SEL_SRC_IP	= BIT(0),
+	XSC_HASH_FIELD_SEL_PROTO	= BIT(1),
+	XSC_HASH_FIELD_SEL_DST_IP	= BIT(2),
+	XSC_HASH_FIELD_SEL_SPORT	= BIT(3),
+	XSC_HASH_FIELD_SEL_DPORT	= BIT(4),
+	XSC_HASH_FIELD_SEL_SRC_IPV6	= BIT(5),
+	XSC_HASH_FIELD_SEL_DST_IPV6	= BIT(6),
+	XSC_HASH_FIELD_SEL_SPORT_V6	= BIT(7),
+	XSC_HASH_FIELD_SEL_DPORT_V6	= BIT(8),
+};
+
+#define XSC_HASH_IP		(XSC_HASH_FIELD_SEL_SRC_IP	|\
+				XSC_HASH_FIELD_SEL_DST_IP	|\
+				XSC_HASH_FIELD_SEL_PROTO)
+#define XSC_HASH_IP_PORTS	(XSC_HASH_FIELD_SEL_SRC_IP	|\
+				XSC_HASH_FIELD_SEL_DST_IP	|\
+				XSC_HASH_FIELD_SEL_SPORT	|\
+				XSC_HASH_FIELD_SEL_DPORT	|\
+				XSC_HASH_FIELD_SEL_PROTO)
+#define XSC_HASH_IP6		(XSC_HASH_FIELD_SEL_SRC_IPV6	|\
+				XSC_HASH_FIELD_SEL_DST_IPV6	|\
+				XSC_HASH_FIELD_SEL_PROTO)
+#define XSC_HASH_IP6_PORTS	(XSC_HASH_FIELD_SEL_SRC_IPV6	|\
+				XSC_HASH_FIELD_SEL_DST_IPV6	|\
+				XSC_HASH_FIELD_SEL_SPORT_V6	|\
+				XSC_HASH_FIELD_SEL_DPORT_V6	|\
+				XSC_HASH_FIELD_SEL_PROTO)
+
+#endif /* __XSC_PP_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_pph.h b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_pph.h
new file mode 100644
index 000000000..cc544df2c
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_pph.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_PPH_H
+#define __XSC_PPH_H
+
+#define XSC_PPH_HEAD_LEN	64
+
+#define OUTER_L3_BIT	BIT(3)
+#define OUTER_L4_BIT	BIT(2)
+#define INNER_L3_BIT	BIT(1)
+#define INNER_L4_BIT	BIT(0)
+#define OUTER_BIT		(OUTER_L3_BIT | OUTER_L4_BIT)
+#define INNER_BIT		(INNER_L3_BIT | INNER_L4_BIT)
+#define OUTER_AND_INNER	(OUTER_BIT | INNER_BIT)
+
+#define PACKET_UNKNOWN	BIT(4)
+
+#define EPP2SOC_PPH_EXT_TUNNEL_TYPE_OFFSET (6UL)
+#define EPP2SOC_PPH_EXT_TUNNEL_TYPE_BIT_MASK (0XF00)
+#define EPP2SOC_PPH_EXT_TUNNEL_TYPE_BIT_OFFSET (8)
+
+#define EPP2SOC_PPH_EXT_ERROR_BITMAP_OFFSET (20UL)
+#define EPP2SOC_PPH_EXT_ERROR_BITMAP_BIT_MASK (0XFF)
+#define EPP2SOC_PPH_EXT_ERROR_BITMAP_BIT_OFFSET (0)
+
+#define XSC_GET_EPP2SOC_PPH_EXT_TUNNEL_TYPE(PPH_BASE_ADDR)	\
+	((*(u16 *)((u8 *)(PPH_BASE_ADDR) +	\
+	EPP2SOC_PPH_EXT_TUNNEL_TYPE_OFFSET) &	\
+	EPP2SOC_PPH_EXT_TUNNEL_TYPE_BIT_MASK) >>	\
+	EPP2SOC_PPH_EXT_TUNNEL_TYPE_BIT_OFFSET)
+
+#define XSC_GET_EPP2SOC_PPH_ERROR_BITMAP(PPH_BASE_ADDR)	\
+	((*(u8 *)((u8 *)(PPH_BASE_ADDR) +	\
+	EPP2SOC_PPH_EXT_ERROR_BITMAP_OFFSET) &	\
+	EPP2SOC_PPH_EXT_ERROR_BITMAP_BIT_MASK) >>	\
+	EPP2SOC_PPH_EXT_ERROR_BITMAP_BIT_OFFSET)
+
+#endif /* __XSC_PPH_H */
+
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_queue.h b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_queue.h
new file mode 100644
index 000000000..49c3229e8
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_queue.h
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ * Copyright (c) 2015-2016, Mellanox Technologies. All rights reserved.
+ */
+
+#ifndef __XSC_QUEUE_H
+#define __XSC_QUEUE_H
+
+#include <net/page_pool/types.h>
+#include <net/page_pool/helpers.h>
+
+#include "common/xsc_core.h"
+#include "xsc_eth_wq.h"
+
+enum {
+	XSC_SEND_WQE_DS	= 16,
+	XSC_SEND_WQE_BB	= 64,
+};
+
+enum {
+	XSC_RECV_WQE_DS	= 16,
+	XSC_RECV_WQE_BB	= 16,
+};
+
+enum {
+	XSC_ETH_RQ_STATE_ENABLED,
+};
+
+#define XSC_SEND_WQEBB_CTRL_NUM_DS	1
+
+/* each ds holds one fragment in skb */
+#define XSC_MAX_RX_FRAGS        4
+#define XSC_RX_FRAG_SZ_ORDER    0
+#define XSC_RX_FRAG_SZ          (PAGE_SIZE << XSC_RX_FRAG_SZ_ORDER)
+#define DEFAULT_FRAG_SIZE       (2048)
+
+enum {
+	XSC_ETH_SQ_STATE_ENABLED,
+};
+
+struct xsc_cq {
+	/* data path - accessed per cqe */
+	struct xsc_cqwq	wq;
+
+	/* data path - accessed per napi poll */
+	u16			event_ctr;
+	struct napi_struct	*napi;
+	struct xsc_core_cq	xcq;
+	struct xsc_channel	*channel;
+
+	/* control */
+	struct xsc_core_device	*xdev;
+	struct xsc_wq_ctrl	wq_ctrl;
+} ____cacheline_aligned_in_smp;
+
+struct xsc_wqe_frag_info {
+	struct xsc_dma_info *di;
+	u32 offset;
+	u8 last_in_page;
+	u8 is_available;
+};
+
+struct xsc_dma_info {
+	struct page	*page;
+	dma_addr_t	addr;
+};
+
+struct xsc_rq_frag_info {
+	int frag_size;
+	int frag_stride;
+};
+
+struct xsc_rq_frags_info {
+	struct xsc_rq_frag_info arr[XSC_MAX_RX_FRAGS];
+	u8 num_frags;
+	u8 log_num_frags;
+	u8 wqe_bulk;
+	u8 wqe_bulk_min;
+	u8 frags_max_num;
+};
+
+struct xsc_rq;
+typedef void (*xsc_fp_handle_rx_cqe)(struct xsc_cqwq *cqwq, struct xsc_rq *rq,
+				     struct xsc_cqe *cqe);
+typedef bool (*xsc_fp_post_rx_wqes)(struct xsc_rq *rq);
+typedef void (*xsc_fp_dealloc_wqe)(struct xsc_rq *rq, u16 ix);
+typedef struct sk_buff * (*xsc_fp_skb_from_cqe)(struct xsc_rq *rq,
+						struct xsc_wqe_frag_info *wi,
+						u32 cqe_bcnt,
+						u8 has_pph);
+
+struct xsc_rq {
+	struct xsc_core_qp		cqp;
+	struct {
+		struct xsc_wq_cyc	wq;
+		struct xsc_wqe_frag_info	*frags;
+		struct xsc_dma_info	*di;
+		struct xsc_rq_frags_info	info;
+		xsc_fp_skb_from_cqe	skb_from_cqe;
+	} wqe;
+
+	struct {
+		u16	headroom;
+		u8	map_dir;	/* dma map direction */
+	} buff;
+
+	struct page_pool	*page_pool;
+	struct xsc_wq_ctrl	wq_ctrl;
+	struct xsc_cq		cq;
+	u32	rqn;
+	int	ix;
+
+	unsigned long	state;
+	struct xsc_rq_stats *stats;
+
+	u32 hw_mtu;
+	u32 frags_sz;
+
+	xsc_fp_handle_rx_cqe	handle_rx_cqe;
+	xsc_fp_post_rx_wqes	post_wqes;
+	xsc_fp_dealloc_wqe	dealloc_wqe;
+} ____cacheline_aligned_in_smp;
+
+enum xsc_dma_map_type {
+	XSC_DMA_MAP_SINGLE,
+	XSC_DMA_MAP_PAGE
+};
+
+struct xsc_sq_dma {
+	dma_addr_t	addr;
+	u32		size;
+	enum xsc_dma_map_type	type;
+};
+
+struct xsc_tx_wqe_info {
+	struct sk_buff *skb;
+	u32 num_bytes;
+	u8  num_wqebbs;
+	u8  num_dma;
+};
+
+struct xsc_tx_wqe {
+	struct xsc_send_wqe_ctrl_seg ctrl;
+	struct xsc_wqe_data_seg data[];
+};
+
+struct xsc_sq {
+	struct xsc_core_qp		cqp;
+	/* dirtied @completion */
+	u16                        cc;
+	u32                        dma_fifo_cc;
+
+	/* dirtied @xmit */
+	u16                        pc ____cacheline_aligned_in_smp;
+	u32                        dma_fifo_pc;
+
+	struct xsc_cq            cq;
+
+	/* read only */
+	struct xsc_wq_cyc         wq;
+	u32                        dma_fifo_mask;
+	struct xsc_sq_stats     *stats;
+	struct {
+		struct xsc_sq_dma         *dma_fifo;
+		struct xsc_tx_wqe_info    *wqe_info;
+	} db;
+	void __iomem              *uar_map;
+	struct netdev_queue       *txq;
+	u32                        sqn;
+	u16                        stop_room;
+
+	__be32                     mkey_be;
+	unsigned long              state;
+	unsigned int               hw_mtu;
+
+	/* control path */
+	struct xsc_wq_ctrl        wq_ctrl;
+	struct xsc_channel         *channel;
+	int                        ch_ix;
+	int                        txq_ix;
+} ____cacheline_aligned_in_smp;
+
+#endif /* __XSC_QUEUE_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
index 5c947da2a..b01fca3ac 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
@@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
 
-xsc_pci-y := main.o cmdq.o hw.o qp.o cq.o alloc.o eq.o pci_irq.o adev.o hal/andes_impl.o
+xsc_pci-y := main.o cmdq.o hw.o qp.o cq.o alloc.o eq.o pci_irq.o adev.o vport.o hal/andes_impl.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/vport.c b/drivers/net/ethernet/yunsilicon/xsc/pci/vport.c
new file mode 100644
index 000000000..5118ff36b
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/vport.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "common/xsc_core.h"
+#include "common/xsc_cmd_api.h"
+
+u8 xsc_core_query_vport_state(struct xsc_core_device *xdev, u16 vport)
+{
+	struct xsc_query_vport_state_out out;
+	struct xsc_query_vport_state_in in;
+	u32 val = 0;
+	int err;
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_QUERY_VPORT_STATE);
+	val |= FIELD_PREP(XSC_QUERY_VPORT_VPORT_NUM_MASK, vport);
+	if (vport)
+		val |= XSC_QUERY_VPORT_OTHER_VPORT;
+	in.data0 = cpu_to_be32(val);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err) {
+		pci_err(xdev->pdev, "failed to query vport state, err=%d, status=%d\n",
+			err, out.hdr.status);
+		return err;
+	}
+
+	return out.state;
+}
+EXPORT_SYMBOL_GPL(xsc_core_query_vport_state);

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

* [PATCH 07/15] yunsilicon: add ethernet TX path
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (5 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 06/15] yunsilicon: add ethernet netdev registration Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 08/15] yunsilicon: add ethernet RX path and statistics Xin Tian
                   ` (8 subsequent siblings)
  15 siblings, 1 reply; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the transmit path: WQE construction, completion handling and
ndo_start_xmit.  The channel layer opens its SQ/CQ pairs, enables NAPI
and advertises the TX offloads the driver implements.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 .../net/ethernet/yunsilicon/xsc/net/Makefile  |   2 +-
 .../net/ethernet/yunsilicon/xsc/net/main.c    | 905 +++++++++++++++++-
 .../ethernet/yunsilicon/xsc/net/xsc_eth_tx.c  | 309 ++++++
 .../yunsilicon/xsc/net/xsc_eth_txrx.c         | 146 +++
 4 files changed, 1357 insertions(+), 5 deletions(-)
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_tx.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c

diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/Makefile b/drivers/net/ethernet/yunsilicon/xsc/net/Makefile
index 0e6249cec..ffb819884 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/net/Makefile
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/Makefile
@@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_YUNSILICON_XSC_ETH) += xsc_eth.o
 
-xsc_eth-y := main.o xsc_eth_wq.o
+xsc_eth-y := main.o xsc_eth_wq.o xsc_eth_txrx.o xsc_eth_tx.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/main.c b/drivers/net/ethernet/yunsilicon/xsc/net/main.c
index b92c3ed19..a20d01618 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/net/main.c
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/main.c
@@ -97,6 +97,800 @@ static int xsc_eth_netdev_init(struct xsc_adapter *adapter)
 	return 0;
 }
 
+static void xsc_eth_build_queue_param(struct xsc_adapter *adapter,
+				      struct xsc_queue_attr *attr, u8 type)
+{
+	struct xsc_core_device *xdev = adapter->xdev;
+
+	if (adapter->nic_param.sq_size == 0)
+		adapter->nic_param.sq_size = BIT(xdev->caps.log_max_qp_depth);
+	if (adapter->nic_param.rq_size == 0)
+		adapter->nic_param.rq_size = BIT(xdev->caps.log_max_qp_depth);
+
+	if (type == XSC_QUEUE_TYPE_RQCQ) {
+		attr->ele_num = min_t(int, XSC_RQCQ_ELE_NUM,
+				      xdev->caps.max_cqes);
+		attr->ele_size = XSC_RQCQ_ELE_SZ;
+		attr->ele_log_size = order_base_2(XSC_RQCQ_ELE_SZ);
+		attr->q_log_size = order_base_2(attr->ele_num);
+	} else if (type == XSC_QUEUE_TYPE_SQCQ) {
+		attr->ele_num = min_t(int, XSC_SQCQ_ELE_NUM,
+				      xdev->caps.max_cqes);
+		attr->ele_size = XSC_SQCQ_ELE_SZ;
+		attr->ele_log_size = order_base_2(XSC_SQCQ_ELE_SZ);
+		attr->q_log_size = order_base_2(attr->ele_num);
+	} else if (type == XSC_QUEUE_TYPE_RQ) {
+		attr->ele_num = adapter->nic_param.rq_size;
+		attr->ele_size = xdev->caps.recv_ds_num * XSC_RECV_WQE_DS;
+		attr->ele_log_size = order_base_2(attr->ele_size);
+		attr->q_log_size = order_base_2(attr->ele_num);
+	} else if (type == XSC_QUEUE_TYPE_SQ) {
+		attr->ele_num = adapter->nic_param.sq_size;
+		attr->ele_size = xdev->caps.send_ds_num * XSC_SEND_WQE_DS;
+		attr->ele_log_size = order_base_2(attr->ele_size);
+		attr->q_log_size = order_base_2(attr->ele_num);
+	}
+}
+
+static u32 xsc_rx_get_linear_frag_sz(u32 mtu)
+{
+	u32 byte_count = XSC_SW2HW_FRAG_SIZE(mtu);
+
+	return XSC_SKB_FRAG_SZ(byte_count);
+}
+
+static bool xsc_rx_is_linear_skb(u32 mtu)
+{
+	u32 linear_frag_sz = xsc_rx_get_linear_frag_sz(mtu);
+
+	return linear_frag_sz <= PAGE_SIZE;
+}
+
+static u32 xsc_get_rq_frag_info(struct xsc_rq_frags_info *frags_info, u32 mtu)
+{
+	u32 byte_count = XSC_SW2HW_FRAG_SIZE(mtu);
+	int frag_stride;
+	int i = 0;
+
+	if (xsc_rx_is_linear_skb(mtu)) {
+		frag_stride = xsc_rx_get_linear_frag_sz(mtu);
+		frag_stride = roundup_pow_of_two(frag_stride);
+
+		frags_info->arr[0].frag_size = byte_count;
+		frags_info->arr[0].frag_stride = frag_stride;
+		frags_info->num_frags = 1;
+		frags_info->wqe_bulk = PAGE_SIZE / frag_stride;
+		frags_info->wqe_bulk_min = frags_info->wqe_bulk;
+		goto out;
+	}
+
+	if (byte_count <= DEFAULT_FRAG_SIZE) {
+		frags_info->arr[0].frag_size = DEFAULT_FRAG_SIZE;
+		frags_info->arr[0].frag_stride = DEFAULT_FRAG_SIZE;
+		frags_info->num_frags = 1;
+	} else if (byte_count <= PAGE_SIZE_4K) {
+		frags_info->arr[0].frag_size = PAGE_SIZE_4K;
+		frags_info->arr[0].frag_stride = PAGE_SIZE_4K;
+		frags_info->num_frags = 1;
+	} else if (byte_count <= (PAGE_SIZE_4K + DEFAULT_FRAG_SIZE)) {
+		if (PAGE_SIZE < 2 * PAGE_SIZE_4K) {
+			frags_info->arr[0].frag_size = PAGE_SIZE_4K;
+			frags_info->arr[0].frag_stride = PAGE_SIZE_4K;
+			frags_info->arr[1].frag_size = PAGE_SIZE_4K;
+			frags_info->arr[1].frag_stride = PAGE_SIZE_4K;
+			frags_info->num_frags = 2;
+		} else {
+			frags_info->arr[0].frag_size = 2 * PAGE_SIZE_4K;
+			frags_info->arr[0].frag_stride = 2 * PAGE_SIZE_4K;
+			frags_info->num_frags = 1;
+		}
+	} else if (byte_count <= 2 * PAGE_SIZE_4K) {
+		if (PAGE_SIZE < 2 * PAGE_SIZE_4K) {
+			frags_info->arr[0].frag_size = PAGE_SIZE_4K;
+			frags_info->arr[0].frag_stride = PAGE_SIZE_4K;
+			frags_info->arr[1].frag_size = PAGE_SIZE_4K;
+			frags_info->arr[1].frag_stride = PAGE_SIZE_4K;
+			frags_info->num_frags = 2;
+		} else {
+			frags_info->arr[0].frag_size = 2 * PAGE_SIZE_4K;
+			frags_info->arr[0].frag_stride = 2 * PAGE_SIZE_4K;
+			frags_info->num_frags = 1;
+		}
+	} else {
+		if (PAGE_SIZE < 4 * PAGE_SIZE_4K) {
+			frags_info->num_frags =
+				roundup(byte_count,
+					PAGE_SIZE_4K) / PAGE_SIZE_4K;
+			for (i = 0; i < frags_info->num_frags; i++) {
+				frags_info->arr[i].frag_size = PAGE_SIZE_4K;
+				frags_info->arr[i].frag_stride = PAGE_SIZE_4K;
+			}
+		} else {
+			frags_info->arr[0].frag_size = 4 * PAGE_SIZE_4K;
+			frags_info->arr[0].frag_stride = 4 * PAGE_SIZE_4K;
+			frags_info->num_frags = 1;
+		}
+	}
+
+	if (PAGE_SIZE <= PAGE_SIZE_4K) {
+		frags_info->wqe_bulk_min = 4;
+		frags_info->wqe_bulk = max_t(u8, frags_info->wqe_bulk_min, 8);
+	} else if (PAGE_SIZE <= 2 * PAGE_SIZE_4K) {
+		frags_info->wqe_bulk = 2;
+		frags_info->wqe_bulk_min = frags_info->wqe_bulk;
+	} else {
+		frags_info->wqe_bulk =
+			PAGE_SIZE /
+			(frags_info->num_frags * frags_info->arr[0].frag_size);
+		frags_info->wqe_bulk_min = frags_info->wqe_bulk;
+	}
+
+out:
+	frags_info->log_num_frags = order_base_2(frags_info->num_frags);
+
+	return frags_info->num_frags * frags_info->arr[0].frag_size;
+}
+
+static void xsc_build_rq_frags_info(struct xsc_queue_attr *attr,
+				    struct xsc_rq_frags_info *frags_info,
+				    struct xsc_eth_params *params)
+{
+	params->rq_frags_size = xsc_get_rq_frag_info(frags_info, params->mtu);
+	frags_info->frags_max_num = attr->ele_size / XSC_RECV_WQE_DS;
+}
+
+static void xsc_eth_build_channel_param(struct xsc_adapter *adapter,
+					struct xsc_channel_param *chl_param)
+{
+	xsc_eth_build_queue_param(adapter, &chl_param->rqcq_param.cq_attr,
+				  XSC_QUEUE_TYPE_RQCQ);
+	chl_param->rqcq_param.wq.buf_numa_node = dev_to_node(adapter->dev);
+
+	xsc_eth_build_queue_param(adapter, &chl_param->sqcq_param.cq_attr,
+				  XSC_QUEUE_TYPE_SQCQ);
+	chl_param->sqcq_param.wq.buf_numa_node = dev_to_node(adapter->dev);
+
+	xsc_eth_build_queue_param(adapter, &chl_param->sq_param.sq_attr,
+				  XSC_QUEUE_TYPE_SQ);
+	chl_param->sq_param.wq.buf_numa_node = dev_to_node(adapter->dev);
+
+	xsc_eth_build_queue_param(adapter, &chl_param->rq_param.rq_attr,
+				  XSC_QUEUE_TYPE_RQ);
+	chl_param->rq_param.wq.buf_numa_node = dev_to_node(adapter->dev);
+
+	xsc_build_rq_frags_info(&chl_param->rq_param.rq_attr,
+				&chl_param->rq_param.frags_info,
+				&adapter->nic_param);
+}
+
+static void xsc_eth_cq_error_event(struct xsc_core_cq *xcq,
+				   enum xsc_event event)
+{
+	struct xsc_cq *xsc_cq = container_of(xcq, struct xsc_cq, xcq);
+	struct xsc_core_device *xdev = xsc_cq->xdev;
+
+	if (event != XSC_EVENT_TYPE_CQ_ERROR) {
+		netdev_err(xsc_dev_to_netdev(xdev),
+			   "Unexpected event type %d on CQ %06x\n",
+			   event, xcq->cqn);
+		return;
+	}
+
+	netdev_err(xsc_dev_to_netdev(xdev),
+		   "Eth catch CQ ERROR: %x, cqn: %d\n", event, xcq->cqn);
+}
+
+static void xsc_eth_completion_event(struct xsc_core_cq *xcq)
+{
+	struct xsc_cq *cq = container_of(xcq, struct xsc_cq, xcq);
+	struct xsc_core_device *xdev = cq->xdev;
+	struct xsc_rq *rq = NULL;
+
+	if (unlikely(!cq->channel)) {
+		netdev_err(xsc_dev_to_netdev(xdev), "cq%d->channel is null\n",
+			   xcq->cqn);
+		return;
+	}
+
+	rq = &cq->channel->qp.rq;
+
+	if (!test_bit(XSC_ETH_RQ_STATE_ENABLED, &rq->state))
+		netdev_err(xsc_dev_to_netdev(xdev),
+			   "ch%d_cq%d, napi_flag=0x%lx\n",
+			   cq->channel->chl_idx, xcq->cqn,
+			   cq->napi->state);
+
+	napi_schedule(cq->napi);
+	cq->event_ctr++;
+}
+
+static int xsc_eth_alloc_cq(struct xsc_channel *c, struct xsc_cq *pcq,
+			    struct xsc_cq_param *pcq_param)
+{
+	u8 ele_log_size = pcq_param->cq_attr.ele_log_size;
+	struct xsc_core_device *xdev = c->adapter->xdev;
+	u8 q_log_size = pcq_param->cq_attr.q_log_size;
+	struct xsc_core_cq *core_cq = &pcq->xcq;
+	int ret;
+	u32 i;
+
+	pcq_param->wq.db_numa_node = cpu_to_node(c->cpu);
+	pcq_param->wq.buf_numa_node = cpu_to_node(c->cpu);
+
+	ret = xsc_eth_cqwq_create(xdev, &pcq_param->wq,
+				  q_log_size, ele_log_size, &pcq->wq,
+				  &pcq->wq_ctrl);
+	if (ret)
+		return ret;
+
+	core_cq->cqe_sz = pcq_param->cq_attr.ele_num;
+	core_cq->comp = xsc_eth_completion_event;
+	core_cq->event = xsc_eth_cq_error_event;
+	core_cq->vector = c->chl_idx;
+
+	for (i = 0; i < xsc_cqwq_get_size(&pcq->wq); i++) {
+		struct xsc_cqe *cqe = xsc_cqwq_get_wqe(&pcq->wq, i);
+
+		cqe->data1 |= cpu_to_le32(XSC_CQE_OWNER);
+	}
+	pcq->xdev = xdev;
+
+	return ret;
+}
+
+static int xsc_eth_set_cq(struct xsc_channel *c,
+			  struct xsc_cq *pcq,
+			  struct xsc_cq_param *pcq_param)
+{
+	struct xsc_core_device *xdev = c->adapter->xdev;
+	struct xsc_create_cq_mbox_in *in;
+	unsigned int hw_npages;
+	unsigned int irqn;
+	int inlen;
+	u32 eqn;
+	int ret;
+
+	hw_npages = DIV_ROUND_UP(pcq->wq_ctrl.buf.size, PAGE_SIZE_4K);
+	/*mbox size + pas size*/
+	inlen = sizeof(struct xsc_create_cq_mbox_in) +
+		sizeof(__be64) * hw_npages;
+
+	in = kvzalloc(inlen, GFP_KERNEL);
+	if (!in)
+		return -ENOMEM;
+
+	/*construct param of in struct*/
+	ret = xsc_core_vector2eqn(xdev, c->chl_idx, &eqn, &irqn);
+	if (ret)
+		goto err_free;
+
+	in->ctx.eqn = cpu_to_be16(eqn);
+	in->ctx.log_cq_sz = pcq_param->cq_attr.q_log_size;
+	in->ctx.pa_num = cpu_to_be16(hw_npages);
+	in->ctx.glb_func_id = cpu_to_be16(xdev->glb_func_id);
+
+	xsc_core_fill_page_frag_array(&pcq->wq_ctrl.buf,
+				      &in->pas[0],
+				      hw_npages);
+
+	ret = xsc_core_eth_create_cq(c->adapter->xdev, &pcq->xcq, in, inlen);
+	if (ret)
+		goto err_free;
+	pcq->xcq.irqn = irqn;
+	pcq->xcq.eq = xsc_core_eq_get(xdev, pcq->xcq.vector);
+	return 0;
+
+err_free:
+	kvfree(in);
+	return ret;
+}
+
+static void xsc_eth_free_cq(struct xsc_cq *cq)
+{
+	xsc_eth_wq_destroy(&cq->wq_ctrl);
+}
+
+static int xsc_eth_open_cq(struct xsc_channel *c,
+			   struct xsc_cq *pcq,
+			   struct xsc_cq_param *pcq_param)
+{
+	int ret;
+
+	ret = xsc_eth_alloc_cq(c, pcq, pcq_param);
+	if (ret)
+		return ret;
+
+	ret = xsc_eth_set_cq(c, pcq, pcq_param);
+	if (ret)
+		goto err_free_cq;
+
+	xsc_cq_notify_hw_rearm(pcq);
+
+	pcq->napi = &c->napi;
+	pcq->channel = c;
+
+	return 0;
+
+err_free_cq:
+	xsc_eth_free_cq(pcq);
+	return ret;
+}
+
+static int xsc_eth_close_cq(struct xsc_channel *c, struct xsc_cq *pcq)
+{
+	struct xsc_core_device *xdev = c->adapter->xdev;
+	int ret;
+
+	ret = xsc_core_eth_destroy_cq(xdev, &pcq->xcq);
+	if (ret) {
+		netdev_err(xsc_dev_to_netdev(xdev), "failed to close ch%d cq%d, ret=%d\n",
+			   c->chl_idx, pcq->xcq.cqn, ret);
+		return ret;
+	}
+
+	xsc_eth_free_cq(pcq);
+
+	return 0;
+}
+
+static void xsc_free_qp_sq_db(struct xsc_sq *sq)
+{
+	kvfree(sq->db.wqe_info);
+	kvfree(sq->db.dma_fifo);
+}
+
+static void xsc_free_qp_sq(struct xsc_sq *sq)
+{
+	xsc_free_qp_sq_db(sq);
+	xsc_eth_wq_destroy(&sq->wq_ctrl);
+}
+
+static int xsc_eth_alloc_qp_sq_db(struct xsc_sq *sq, int numa)
+{
+	struct xsc_core_device *xdev = sq->cq.xdev;
+	int wq_sz;
+	int df_sz;
+
+	wq_sz = xsc_wq_cyc_get_size(&sq->wq);
+	df_sz = wq_sz * xdev->caps.send_ds_num;
+	sq->db.dma_fifo = kvzalloc_node(array_size(df_sz,
+						   sizeof(*sq->db.dma_fifo)),
+					GFP_KERNEL,
+					numa);
+	sq->db.wqe_info = kvzalloc_node(array_size(wq_sz,
+						   sizeof(*sq->db.wqe_info)),
+					GFP_KERNEL,
+					numa);
+
+	if (!sq->db.dma_fifo || !sq->db.wqe_info) {
+		xsc_free_qp_sq_db(sq);
+		return -ENOMEM;
+	}
+
+	sq->dma_fifo_mask = df_sz - 1;
+
+	return 0;
+}
+
+static void xsc_eth_qp_event(struct xsc_core_qp *qp, int type)
+{
+	struct xsc_core_device *xdev;
+	struct xsc_rq *rq;
+	struct xsc_sq *sq;
+
+	if (qp->eth_queue_type == XSC_RES_RQ) {
+		rq = container_of(qp, struct xsc_rq, cqp);
+		xdev = rq->cq.xdev;
+	} else if (qp->eth_queue_type == XSC_RES_SQ) {
+		sq = container_of(qp, struct xsc_sq, cqp);
+		xdev = sq->cq.xdev;
+	} else {
+		pr_err("%s:Unknown eth qp type %d\n", __func__, type);
+		return;
+	}
+
+	switch (type) {
+	case XSC_EVENT_TYPE_WQ_CATAS_ERROR:
+	case XSC_EVENT_TYPE_WQ_INVAL_REQ_ERROR:
+	case XSC_EVENT_TYPE_WQ_ACCESS_ERROR:
+		netdev_err(xsc_dev_to_netdev(xdev),
+			   "%s:Async event %x on QP %d\n",
+			   __func__, type, qp->qpn);
+		break;
+	default:
+		netdev_err(xsc_dev_to_netdev(xdev),
+			   "%s: Unexpected event type %d on QP %d\n",
+			   __func__, type, qp->qpn);
+		return;
+	}
+}
+
+static int xsc_eth_open_qp_sq(struct xsc_channel *c,
+			      struct xsc_sq *psq,
+			      struct xsc_sq_param *psq_param)
+{
+	u8 ele_log_size = psq_param->sq_attr.ele_log_size;
+	u8 q_log_size = psq_param->sq_attr.q_log_size;
+	struct xsc_modify_raw_qp_mbox_in *modify_in;
+	struct xsc_channel_stats *channel_stats;
+	struct xsc_create_qp_mbox_in *in;
+	struct xsc_core_device *xdev;
+	struct xsc_adapter *adapter;
+	struct xsc_stats *stats;
+	unsigned int hw_npages;
+	int inlen;
+	int ret;
+
+	adapter = c->adapter;
+	xdev  = adapter->xdev;
+	stats = adapter->stats;
+	channel_stats = &stats->channel_stats[c->chl_idx];
+	psq->stats = &channel_stats->sq;
+	psq_param->wq.db_numa_node = cpu_to_node(c->cpu);
+
+	ret = xsc_eth_wq_cyc_create(xdev, &psq_param->wq,
+				    q_log_size, ele_log_size, &psq->wq,
+				    &psq->wq_ctrl);
+	if (ret)
+		return ret;
+
+	hw_npages = DIV_ROUND_UP(psq->wq_ctrl.buf.size, PAGE_SIZE_4K);
+	inlen = sizeof(struct xsc_create_qp_mbox_in) +
+		sizeof(__be64) * hw_npages;
+
+	in = kvzalloc(inlen, GFP_KERNEL);
+	if (!in) {
+		ret = -ENOMEM;
+		goto err_sq_wq_destroy;
+	}
+	in->req.input_qpn = cpu_to_be16(XSC_QPN_SQN_STUB); /*no use for eth*/
+	in->req.qp_type = XSC_QUEUE_TYPE_RAW_TSO; /*default sq is tso qp*/
+	in->req.log_sq_sz = ilog2(xdev->caps.send_ds_num) + q_log_size;
+	in->req.pa_num = cpu_to_be16(hw_npages);
+	in->req.cqn_send = cpu_to_be16(psq->cq.xcq.cqn);
+	in->req.cqn_recv = in->req.cqn_send;
+	in->req.glb_funcid = cpu_to_be16(xdev->glb_func_id);
+
+	xsc_core_fill_page_frag_array(&psq->wq_ctrl.buf,
+				      &in->req.pas[0], hw_npages);
+
+	ret = xsc_core_eth_create_qp(xdev, in, inlen, &psq->sqn);
+	if (ret)
+		goto err_sq_in_destroy;
+
+	psq->cqp.qpn = psq->sqn;
+	psq->cqp.event = xsc_eth_qp_event;
+	psq->cqp.eth_queue_type = XSC_RES_SQ;
+
+	ret = xsc_core_create_resource_common(xdev, &psq->cqp);
+	if (ret) {
+		netdev_err(adapter->netdev, "%s:error qp:%d errno:%d\n",
+			   __func__, psq->sqn, ret);
+		goto err_sq_destroy;
+	}
+
+	psq->channel = c;
+	psq->ch_ix = c->chl_idx;
+	psq->txq_ix = psq->ch_ix;
+
+	/*need to querify from hardware*/
+	psq->hw_mtu = XSC_ETH_HW_MTU_SEND;
+	psq->stop_room = 1;
+
+	ret = xsc_eth_alloc_qp_sq_db(psq, psq_param->wq.db_numa_node);
+	if (ret)
+		goto err_sq_common_destroy;
+
+	inlen = sizeof(struct xsc_modify_raw_qp_mbox_in);
+	modify_in = kvzalloc(inlen, GFP_KERNEL);
+	if (!modify_in) {
+		ret = -ENOMEM;
+		goto err_sq_common_destroy;
+	}
+
+	modify_in->req.qp_out_port = xdev->pf_id;
+	modify_in->pcie_no = xdev->pcie_no;
+	modify_in->req.qpn = cpu_to_be16((u16)(psq->sqn));
+	modify_in->req.func_id = cpu_to_be16(xdev->glb_func_id);
+	modify_in->req.dma_direct = XSC_DMA_DIR_TO_MAC;
+	modify_in->req.prio = 0; /* single traffic class */
+	ret = xsc_core_eth_modify_raw_qp(xdev, modify_in);
+	if (ret)
+		goto err_sq_modify_in_destroy;
+
+	kvfree(modify_in);
+	kvfree(in);
+
+	return 0;
+
+err_sq_modify_in_destroy:
+	kvfree(modify_in);
+
+err_sq_common_destroy:
+	xsc_core_destroy_resource_common(xdev, &psq->cqp);
+
+err_sq_destroy:
+	xsc_core_eth_destroy_qp(xdev, psq->cqp.qpn);
+
+err_sq_in_destroy:
+	kvfree(in);
+
+err_sq_wq_destroy:
+	xsc_eth_wq_destroy(&psq->wq_ctrl);
+	return ret;
+}
+
+static int xsc_eth_close_qp_sq(struct xsc_channel *c, struct xsc_sq *psq)
+{
+	struct xsc_core_device *xdev = c->adapter->xdev;
+	int ret;
+
+	xsc_core_destroy_resource_common(xdev, &psq->cqp);
+
+	ret = xsc_core_eth_destroy_qp(xdev, psq->cqp.qpn);
+	if (ret)
+		return ret;
+
+	xsc_free_qp_sq(psq);
+
+	return 0;
+}
+
+static int xsc_eth_open_channel(struct xsc_adapter *adapter,
+				int idx,
+				struct xsc_channel *c,
+				struct xsc_channel_param *chl_param)
+{
+	struct xsc_core_device *xdev = adapter->xdev;
+	struct net_device *netdev = adapter->netdev;
+	const struct cpumask *aff;
+	unsigned int irqn;
+	u32 eqn;
+	int ret;
+
+	c->adapter = adapter;
+	c->netdev = adapter->netdev;
+	c->chl_idx = idx;
+
+	if (xdev->caps.msix_enable) {
+		ret = xsc_core_vector2eqn(xdev, c->chl_idx, &eqn, &irqn);
+		if (ret)
+			goto err_out;
+		aff = irq_get_affinity_mask(irqn);
+		c->aff_mask = aff;
+		c->cpu = cpumask_first(aff);
+	}
+
+	ret = xsc_eth_open_cq(c, &c->qp.rq.cq, &chl_param->rqcq_param);
+	if (ret)
+		goto err_out;
+
+	ret = xsc_eth_open_cq(c, &c->qp.sq.cq, &chl_param->sqcq_param);
+	if (ret)
+		goto err_close_rq_cq;
+
+	ret = xsc_eth_open_qp_sq(c, &c->qp.sq, &chl_param->sq_param);
+	if (ret)
+		goto err_close_sq_cq;
+
+	netif_napi_add(netdev, &c->napi, xsc_eth_napi_poll);
+
+	netdev_dbg(adapter->netdev, "open channel%d ok\n", idx);
+	return 0;
+
+err_close_sq_cq:
+	xsc_eth_close_cq(c, &c->qp.sq.cq);
+err_close_rq_cq:
+	xsc_eth_close_cq(c, &c->qp.rq.cq);
+err_out:
+	netdev_err(adapter->netdev, "failed to open channel: ch%d, err=%d\n",
+		   idx, ret);
+	return ret;
+}
+
+static int xsc_eth_modify_qps_channel(struct xsc_adapter *adapter,
+				      struct xsc_channel *c)
+{
+	return xsc_core_eth_modify_qp_status(adapter->xdev, c->qp.sq.sqn,
+					     XSC_CMD_OP_RTR2RTS_QP);
+}
+
+static int xsc_eth_modify_qps(struct xsc_adapter *adapter,
+			      struct xsc_eth_channels *chls)
+{
+	int ret;
+	int i;
+
+	for (i = 0; i < chls->num_chl; i++) {
+		struct xsc_channel *c = &chls->c[i];
+
+		ret = xsc_eth_modify_qps_channel(adapter, c);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static void xsc_eth_close_channel(struct xsc_channel *c, bool free_rq)
+{
+	xsc_eth_close_qp_sq(c, &c->qp.sq);
+	xsc_eth_close_cq(c, &c->qp.sq.cq);
+
+	netif_napi_del(&c->napi);
+}
+
+static int xsc_eth_open_channels(struct xsc_adapter *adapter)
+{
+	struct xsc_eth_channels *chls = &adapter->channels;
+	struct xsc_core_device *xdev = adapter->xdev;
+	struct xsc_channel_param *chl_param;
+	int ret = 0;
+	int i;
+
+	chls->num_chl = adapter->nic_param.num_channels;
+	chls->c = kcalloc_node(chls->num_chl, sizeof(struct xsc_channel),
+			       GFP_KERNEL, xdev->numa_node);
+	if (!chls->c) {
+		ret = -ENOMEM;
+		goto err_out;
+	}
+
+	chl_param = kvzalloc_obj(*chl_param, GFP_KERNEL);
+	if (!chl_param) {
+		ret = -ENOMEM;
+		goto err_free_ch;
+	}
+
+	xsc_eth_build_channel_param(adapter, chl_param);
+
+	for (i = 0; i < chls->num_chl; i++) {
+		ret = xsc_eth_open_channel(adapter, i, &chls->c[i], chl_param);
+		if (ret)
+			goto err_close_channel;
+	}
+
+	for (i = 0; i < chls->num_chl; i++)
+		napi_enable(&chls->c[i].napi);
+
+	/* flush cache to memory before interrupt and napi_poll running */
+	smp_wmb();
+
+	ret = xsc_eth_modify_qps(adapter, chls);
+	if (ret)
+		goto err_close_channel;
+
+	kvfree(chl_param);
+	return 0;
+
+err_close_channel:
+	for (--i; i >= 0; i--)
+		xsc_eth_close_channel(&chls->c[i], false);
+
+	kvfree(chl_param);
+err_free_ch:
+	kfree(chls->c);
+err_out:
+	chls->num_chl = 0;
+	netdev_err(adapter->netdev, "failed to open %d channels, err=%d\n",
+		   chls->num_chl, ret);
+	return ret;
+}
+
+static void xsc_eth_close_channels(struct xsc_adapter *adapter)
+{
+	struct xsc_channel *c;
+	int i;
+
+	for (i = 0; i < adapter->channels.num_chl; i++) {
+		c = &adapter->channels.c[i];
+
+		xsc_eth_close_channel(c, true);
+	}
+
+	kfree(adapter->channels.c);
+	adapter->channels.num_chl = 0;
+}
+
+static void xsc_eth_build_tx2sq_maps(struct xsc_adapter *adapter)
+{
+	struct xsc_channel *c;
+	struct xsc_sq *psq;
+	int i;
+
+	for (i = 0; i < adapter->channels.num_chl; i++) {
+		c = &adapter->channels.c[i];
+		psq = &c->qp.sq;
+		adapter->txq2sq[psq->txq_ix] = psq;
+	}
+}
+
+static void xsc_eth_activate_txqsq(struct xsc_channel *c)
+{
+	struct xsc_sq *psq = &c->qp.sq;
+
+	psq->txq = netdev_get_tx_queue(psq->channel->netdev, psq->txq_ix);
+	set_bit(XSC_ETH_SQ_STATE_ENABLED, &psq->state);
+	netdev_tx_reset_queue(psq->txq);
+	netif_tx_start_queue(psq->txq);
+}
+
+static void xsc_eth_deactivate_txqsq(struct xsc_channel *c)
+{
+	clear_bit(XSC_ETH_SQ_STATE_ENABLED, &c->qp.sq.state);
+}
+
+static void xsc_eth_activate_channel(struct xsc_channel *c)
+{
+	xsc_eth_activate_txqsq(c);
+}
+
+static void xsc_eth_deactivate_channel(struct xsc_channel *c)
+{
+	xsc_eth_deactivate_txqsq(c);
+}
+
+static void xsc_eth_activate_channels(struct xsc_eth_channels *chs)
+{
+	int i;
+
+	for (i = 0; i < chs->num_chl; i++)
+		xsc_eth_activate_channel(&chs->c[i]);
+}
+
+static void xsc_eth_deactivate_channels(struct xsc_eth_channels *chs)
+{
+	int i;
+
+	for (i = 0; i < chs->num_chl; i++)
+		xsc_eth_deactivate_channel(&chs->c[i]);
+
+	/* Sync with all NAPIs to wait until they stop using queues. */
+	synchronize_net();
+
+	for (i = 0; i < chs->num_chl; i++)
+		/* last doorbell out */
+		napi_disable(&chs->c[i].napi);
+}
+
+static void xsc_eth_activate_priv_channels(struct xsc_adapter *adapter)
+{
+	struct net_device *netdev = adapter->netdev;
+
+	netif_set_real_num_tx_queues(netdev, adapter->channels.num_chl);
+	netif_set_real_num_rx_queues(netdev, adapter->channels.num_chl);
+
+	xsc_eth_build_tx2sq_maps(adapter);
+	xsc_eth_activate_channels(&adapter->channels);
+	netif_tx_start_all_queues(adapter->netdev);
+}
+
+static void xsc_eth_deactivate_priv_channels(struct xsc_adapter *adapter)
+{
+	netif_tx_disable(adapter->netdev);
+	xsc_eth_deactivate_channels(&adapter->channels);
+}
+
+static int xsc_eth_sw_init(struct xsc_adapter *adapter)
+{
+	int ret;
+
+	ret = xsc_eth_open_channels(adapter);
+	if (ret)
+		return ret;
+
+	xsc_eth_activate_priv_channels(adapter);
+
+	return 0;
+}
+
+static void xsc_eth_sw_deinit(struct xsc_adapter *adapter)
+{
+	xsc_eth_deactivate_priv_channels(adapter);
+
+	return xsc_eth_close_channels(adapter);
+}
+
 static bool xsc_eth_get_link_status(struct xsc_adapter *adapter)
 {
 	struct xsc_core_device *xdev = adapter->xdev;
@@ -144,6 +938,84 @@ static void xsc_eth_link_event_handler(void *arg)
 			   err);
 }
 
+static bool xsc_get_pct_drop_config(struct xsc_core_device *xdev)
+{
+	return (xdev->pdev->device == XSC_MC_PF_DEV_ID) ||
+		(xdev->pdev->device == XSC_MF_SOC_PF_DEV_ID) ||
+		(xdev->pdev->device == XSC_MS_PF_DEV_ID) ||
+		(xdev->pdev->device == XSC_MV_SOC_PF_DEV_ID);
+}
+
+static int xsc_eth_enable_nic_hca(struct xsc_adapter *adapter)
+{
+	struct xsc_cmd_enable_nic_hca_mbox_out out = {};
+	struct xsc_cmd_enable_nic_hca_mbox_in in = {};
+	struct xsc_core_device *xdev = adapter->xdev;
+	struct net_device *netdev = adapter->netdev;
+	u16 caps_mask = 0;
+	u16 caps = 0;
+	int err;
+
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_ENABLE_NIC_HCA);
+
+	in.rss.rss_en = 1;
+	in.rss.rqn_base = cpu_to_be16(adapter->channels.rqn_base -
+				xdev->caps.raweth_rss_qp_id_base);
+	in.rss.rqn_num = cpu_to_be16(adapter->channels.num_chl);
+	/* hfunc is a constant: the driver never negotiates anything else */
+	in.rss.hfunc = XSC_HASH_FUNC_TOP;
+	in.rss.hash_tmpl = cpu_to_be32(adapter->rss_param.rss_hash_tmpl);
+	caps_mask |= BIT(XSC_TBM_CAP_RSS);
+
+	if (netdev->features & NETIF_F_RXCSUM)
+		caps |= BIT(XSC_TBM_CAP_HASH_PPH);
+	caps_mask |= BIT(XSC_TBM_CAP_HASH_PPH);
+
+	if (xsc_get_pct_drop_config(xdev) && !(netdev->flags & IFF_SLAVE))
+		caps |= BIT(XSC_TBM_CAP_PCT_DROP_CONFIG);
+	caps_mask |= BIT(XSC_TBM_CAP_PCT_DROP_CONFIG);
+
+	memcpy(in.nic.mac_addr, netdev->dev_addr, ETH_ALEN);
+
+	in.nic.caps = cpu_to_be16(caps);
+	in.nic.caps_mask = cpu_to_be16(caps_mask);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err) {
+		netdev_err(netdev, "failed!! err=%d, status=%d\n",
+			   err, out.hdr.status);
+		return -ENOEXEC;
+	}
+
+	return 0;
+}
+
+static int xsc_eth_disable_nic_hca(struct xsc_adapter *adapter)
+{
+	struct xsc_cmd_disable_nic_hca_mbox_out out = {};
+	struct xsc_cmd_disable_nic_hca_mbox_in in = {};
+	struct xsc_core_device *xdev = adapter->xdev;
+	struct net_device *netdev = adapter->netdev;
+	u16 caps = 0;
+	int err;
+
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DISABLE_NIC_HCA);
+
+	if (xsc_get_pct_drop_config(xdev) &&
+	    !(netdev->priv_flags & IFF_BONDING))
+		caps |= BIT(XSC_TBM_CAP_PCT_DROP_CONFIG);
+
+	in.nic.caps = cpu_to_be16(caps);
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err) {
+		netdev_err(netdev, "failed!! err=%d, status=%d\n",
+			   err, out.hdr.status);
+		return -ENOEXEC;
+	}
+
+	return 0;
+}
+
 static void xsc_set_default_xps_cpumasks(struct xsc_adapter *adapter)
 {
 	struct xsc_core_device *xdev = adapter->xdev;
@@ -175,11 +1047,20 @@ static int xsc_eth_open(struct net_device *netdev)
 {
 	struct xsc_adapter *adapter = netdev_priv(netdev);
 	struct xsc_core_device *xdev = adapter->xdev;
+	int ret;
 
 	if (adapter->opened) {
 		netdev_err(adapter->netdev, "ndo_open on an already opened device\n");
 		return 0;
 	}
+	ret = xsc_eth_sw_init(adapter);
+	if (ret)
+		return ret;
+
+	ret = xsc_eth_enable_nic_hca(adapter);
+	if (ret)
+		goto err_sw_deinit;
+
 	xsc_irq_event_register(xdev, xsc_eth_link_event_handler);
 
 	if (xsc_eth_get_link_status(adapter))	{
@@ -196,12 +1077,16 @@ static int xsc_eth_open(struct net_device *netdev)
 	xsc_set_port_admin_status(adapter, XSC_PORT_UP);
 
 	return 0;
+
+err_sw_deinit:
+	xsc_eth_sw_deinit(adapter);
+	return ret;
 }
 
 static int xsc_eth_close(struct net_device *netdev)
 {
 	struct xsc_adapter *adapter = netdev_priv(netdev);
-	struct xsc_core_device *xdev = adapter->xdev;
+	int ret;
 
 	if (!adapter->opened)
 		return 0;
@@ -209,11 +1094,16 @@ static int xsc_eth_close(struct net_device *netdev)
 
 	netif_carrier_off(adapter->netdev);
 
-	xsc_irq_event_unregister(xdev);
+	xsc_eth_sw_deinit(adapter);
+
+	ret = xsc_eth_disable_nic_hca(adapter);
+	if (ret)
+		netdev_err(adapter->netdev, "failed to disable nic hca, err=%d\n",
+			   ret);
 
 	xsc_set_port_admin_status(adapter, XSC_PORT_DOWN);
 
-	return 0;
+	return ret;
 }
 
 static int xsc_eth_set_hw_mtu(struct xsc_core_device *xdev,
@@ -246,6 +1136,7 @@ static int xsc_eth_set_hw_mtu(struct xsc_core_device *xdev,
 static const struct net_device_ops xsc_netdev_ops = {
 	.ndo_open		= xsc_eth_open,
 	.ndo_stop		= xsc_eth_close,
+	.ndo_start_xmit		= xsc_eth_xmit_start,
 };
 
 static void xsc_eth_build_nic_netdev(struct xsc_adapter *adapter)
@@ -260,7 +1151,13 @@ static void xsc_eth_build_nic_netdev(struct xsc_adapter *adapter)
 	/*mtu - macheaderlen - ipheaderlen should be aligned in 8B*/
 	netdev->mtu = SW_DEFAULT_MTU;
 
-	netdev->features = NETIF_F_HIGHDMA;
+	netdev->vlan_features |= NETIF_F_SG |
+				 NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
+				 NETIF_F_TSO | NETIF_F_TSO6;
+
+	netdev->hw_features = netdev->vlan_features;
+
+	netdev->features = netdev->hw_features | NETIF_F_HIGHDMA;
 }
 
 static int xsc_eth_nic_init(struct xsc_adapter *adapter, u32 ch_num)
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_tx.c b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_tx.c
new file mode 100644
index 000000000..7495b12b7
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_tx.c
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/tcp.h>
+
+#include "xsc_eth.h"
+#include "xsc_eth_txrx.h"
+
+#define XSC_OPCODE_RAW 7
+
+static void xsc_dma_push(struct xsc_sq *sq, dma_addr_t addr, u32 size,
+			 enum xsc_dma_map_type map_type)
+{
+	struct xsc_sq_dma *dma = xsc_dma_get(sq, sq->dma_fifo_pc++);
+
+	dma->addr = addr;
+	dma->size = size;
+	dma->type = map_type;
+}
+
+static void xsc_dma_unmap_wqe(struct xsc_sq *sq, u8 num_dma)
+{
+	struct xsc_adapter *adapter = sq->channel->adapter;
+	struct device *dev  = adapter->dev;
+	int i;
+
+	for (i = 0; i < num_dma; i++) {
+		struct xsc_sq_dma *last_pushed_dma;
+
+		last_pushed_dma = xsc_dma_get(sq, --sq->dma_fifo_pc);
+		xsc_tx_dma_unmap(dev, last_pushed_dma);
+	}
+}
+
+static void *xsc_sq_fetch_wqe(struct xsc_sq *sq, size_t size, u16 *pi)
+{
+	struct xsc_wq_cyc *wq = &sq->wq;
+	void *wqe;
+
+	/*caution, sp->pc is default to be zero*/
+	*pi  = xsc_wq_cyc_ctr2ix(wq, sq->pc);
+	wqe = xsc_wq_cyc_get_wqe(wq, *pi);
+	memset(wqe, 0, size);
+
+	return wqe;
+}
+
+static u16 xsc_tx_get_gso_ihs(struct xsc_sq *sq, struct sk_buff *skb)
+{
+	u16 ihs;
+
+	if (skb->encapsulation) {
+		ihs = skb_inner_transport_offset(skb) + inner_tcp_hdrlen(skb);
+	} else {
+		if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4)
+			ihs = skb_transport_offset(skb) + sizeof(struct udphdr);
+		else
+			ihs = skb_transport_offset(skb) + tcp_hdrlen(skb);
+	}
+
+	return ihs;
+}
+
+static void xsc_txwqe_build_cseg_csum(struct xsc_sq *sq,
+				      struct sk_buff *skb,
+				      struct xsc_send_wqe_ctrl_seg *cseg)
+{
+	u32 val = le32_to_cpu(cseg->data0);
+
+	if (likely(skb->ip_summed == CHECKSUM_PARTIAL)) {
+		if (skb->encapsulation)
+			val |= FIELD_PREP(XSC_WQE_CTRL_SEG_CSUM_EN_MASK,
+					  XSC_ETH_WQE_INNER_AND_OUTER_CSUM);
+		else
+			val |= FIELD_PREP(XSC_WQE_CTRL_SEG_CSUM_EN_MASK,
+					  XSC_ETH_WQE_OUTER_CSUM);
+	} else {
+		val |= FIELD_PREP(XSC_WQE_CTRL_SEG_CSUM_EN_MASK,
+				  XSC_ETH_WQE_NONE_CSUM);
+	}
+	cseg->data0 = cpu_to_le32(val);
+}
+
+static void xsc_txwqe_build_csegs(struct xsc_sq *sq, struct sk_buff *skb,
+				  u16 mss, u16 ihs, u16 headlen,
+				  u8 opcode, u16 ds_cnt, u32 msglen,
+				  struct xsc_send_wqe_ctrl_seg *cseg)
+{
+	struct xsc_core_device *xdev = sq->cq.xdev;
+	int send_wqe_ds_num_log;
+	u32 val = 0;
+
+	send_wqe_ds_num_log = ilog2(xdev->caps.send_ds_num);
+	xsc_txwqe_build_cseg_csum(sq, skb, cseg);
+
+	if (mss != 0) {
+		val |= XSC_WQE_CTRL_SEG_HAS_PPH |
+		       XSC_WQE_CTRL_SEG_SO_TYPE |
+		       FIELD_PREP(XSC_WQE_CTRL_SEG_SO_HDR_LEN_MASK, ihs) |
+		       FIELD_PREP(XSC_WQE_CTRL_SEG_SO_DATA_SIZE_MASK, mss);
+		cseg->data2 = cpu_to_le32(val);
+	}
+
+	val = le32_to_cpu(cseg->data0);
+	val |= FIELD_PREP(XSC_WQE_CTRL_SEG_MSG_OPCODE_MASK, opcode) |
+	       FIELD_PREP(XSC_WQE_CTRL_SEG_WQE_ID_MASK,
+			  sq->pc << send_wqe_ds_num_log) |
+	       FIELD_PREP(XSC_WQE_CTRL_SEG_DS_DATA_NUM_MASK,
+			  ds_cnt - XSC_SEND_WQEBB_CTRL_NUM_DS);
+	cseg->data0 = cpu_to_le32(val);
+	cseg->msg_len = cpu_to_le32(msglen);
+	cseg->data3 = cpu_to_le32(XSC_WQE_CTRL_SEG_CE);
+}
+
+static int xsc_txwqe_build_dsegs(struct xsc_sq *sq, struct sk_buff *skb,
+				 u16 ihs, u16 headlen,
+				 struct xsc_wqe_data_seg *dseg)
+{
+	struct xsc_adapter *adapter = sq->channel->adapter;
+	struct device *dev = adapter->dev;
+	dma_addr_t dma_addr = 0;
+	u8 num_dma = 0;
+	int i;
+
+	if (headlen) {
+		dma_addr = dma_map_single(dev,
+					  skb->data,
+					  headlen,
+					  DMA_TO_DEVICE);
+		if (unlikely(dma_mapping_error(dev, dma_addr)))
+			goto err_dma_unmap_wqe;
+
+		dseg->va = cpu_to_le64(dma_addr);
+		dseg->mkey  = cpu_to_le32(be32_to_cpu(sq->mkey_be));
+		dseg->data0 |=
+			cpu_to_le32(FIELD_PREP(XSC_WQE_DATA_SEG_SEG_LEN_MASK,
+					       headlen));
+
+		xsc_dma_push(sq, dma_addr, headlen, XSC_DMA_MAP_SINGLE);
+		num_dma++;
+		dseg++;
+	}
+
+	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+		skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+		int fsz = skb_frag_size(frag);
+
+		dma_addr = skb_frag_dma_map(dev, frag, 0, fsz, DMA_TO_DEVICE);
+		if (unlikely(dma_mapping_error(dev, dma_addr)))
+			goto err_dma_unmap_wqe;
+
+		dseg->va = cpu_to_le64(dma_addr);
+		dseg->mkey = cpu_to_le32(be32_to_cpu(sq->mkey_be));
+		dseg->data0 |=
+			cpu_to_le32(FIELD_PREP(XSC_WQE_DATA_SEG_SEG_LEN_MASK,
+					       fsz));
+
+		xsc_dma_push(sq, dma_addr, fsz, XSC_DMA_MAP_PAGE);
+		num_dma++;
+		dseg++;
+	}
+
+	return num_dma;
+
+err_dma_unmap_wqe:
+	xsc_dma_unmap_wqe(sq, num_dma);
+	return -ENOMEM;
+}
+
+static void xsc_sq_notify_hw(struct xsc_wq_cyc *wq, u16 pc,
+			     struct xsc_sq *sq)
+{
+	struct xsc_adapter *adapter = sq->channel->adapter;
+	struct xsc_core_device *xdev  = adapter->xdev;
+	int send_ds_num_log = ilog2(xdev->caps.send_ds_num);
+
+	/* reverse wqe index to ds index */
+	xsc_update_tx_db(xdev, sq->sqn, pc << send_ds_num_log);
+}
+
+static void xsc_txwqe_complete(struct xsc_sq *sq, struct sk_buff *skb,
+			       u8 opcode, u16 ds_cnt,
+			       u8 num_wqebbs, u32 num_bytes, u8 num_dma,
+			       struct xsc_tx_wqe_info *wi)
+{
+	struct xsc_wq_cyc *wq = &sq->wq;
+
+	wi->num_bytes = num_bytes;
+	wi->num_dma = num_dma;
+	wi->num_wqebbs = num_wqebbs;
+	wi->skb = skb;
+
+	netdev_tx_sent_queue(sq->txq, num_bytes);
+
+	sq->pc += wi->num_wqebbs;
+
+	if (unlikely(!xsc_wqc_has_room_for(wq, sq->cc, sq->pc, sq->stop_room)))
+		netif_tx_stop_queue(sq->txq);
+
+	if (!netdev_xmit_more() || netif_xmit_stopped(sq->txq))
+		xsc_sq_notify_hw(wq, sq->pc, sq);
+}
+
+static uint32_t xsc_eth_xmit_frame(struct sk_buff *skb,
+				   struct xsc_sq *sq,
+				   struct xsc_tx_wqe *wqe,
+				   u16 pi)
+{
+	struct xsc_core_device *xdev = sq->cq.xdev;
+	struct xsc_sq_stats *stats = sq->stats;
+	struct xsc_send_wqe_ctrl_seg *cseg;
+	struct xsc_wqe_data_seg *dseg;
+	struct xsc_tx_wqe_info *wi;
+	u16 mss, ihs, headlen;
+	u32 num_bytes;
+	u8 num_wqebbs;
+	int num_dma;
+	u16 ds_cnt;
+	u8 opcode;
+
+retry_send:
+	/* Calc ihs and ds cnt, no writes to wqe yet; ctrl-ds is reduced by
+	 * ds_data_num.
+	 */
+	ds_cnt = XSC_SEND_WQEBB_CTRL_NUM_DS;
+
+	if (skb_is_gso(skb)) {
+		opcode		= XSC_OPCODE_RAW;
+		mss		= skb_shinfo(skb)->gso_size;
+		ihs		= xsc_tx_get_gso_ihs(sq, skb);
+		num_bytes	= skb->len +
+				  (skb_shinfo(skb)->gso_segs - 1) * ihs;
+		stats->packets	+= skb_shinfo(skb)->gso_segs;
+	} else {
+		opcode		= XSC_OPCODE_RAW;
+		mss		= 0;
+		ihs		= 0;
+		num_bytes	= skb->len;
+		stats->packets++;
+	}
+
+	/*linear data in skb*/
+	headlen = skb->len - skb->data_len;
+	ds_cnt += !!headlen;
+	ds_cnt += skb_shinfo(skb)->nr_frags;
+
+	/* Check packet size. */
+	if (unlikely(mss == 0 && skb->len > sq->hw_mtu))
+		goto err_drop;
+
+	num_wqebbs = DIV_ROUND_UP(ds_cnt, xdev->caps.send_ds_num);
+	/*if ds_cnt exceed one wqe, drop it*/
+	if (num_wqebbs != 1) {
+		if (skb_linearize(skb))
+			goto err_drop;
+		goto retry_send;
+	}
+
+	/* fill wqe */
+	wi   = (struct xsc_tx_wqe_info *)&sq->db.wqe_info[pi];
+	cseg = &wqe->ctrl;
+	dseg = &wqe->data[0];
+
+	if (unlikely(skb->len == 0))
+		goto err_drop;
+
+	xsc_txwqe_build_csegs(sq, skb, mss, ihs, headlen,
+			      opcode, ds_cnt, skb->len, cseg);
+
+	/*inline header is also use dma to transport*/
+	num_dma = xsc_txwqe_build_dsegs(sq, skb, ihs, headlen, dseg);
+	if (unlikely(num_dma < 0))
+		goto err_drop;
+
+	xsc_txwqe_complete(sq, skb, opcode, ds_cnt, num_wqebbs, num_bytes,
+			   num_dma, wi);
+	stats->bytes     += num_bytes;
+
+	return NETDEV_TX_OK;
+
+err_drop:
+	stats->dropped++;
+	dev_kfree_skb_any(skb);
+
+	return NETDEV_TX_OK;
+}
+
+netdev_tx_t xsc_eth_xmit_start(struct sk_buff *skb, struct net_device *netdev)
+{
+	struct xsc_adapter *adapter = netdev_priv(netdev);
+	struct xsc_tx_wqe *wqe;
+	struct xsc_sq *sq;
+	u32 ds_num;
+	u16 pi;
+
+	if (!adapter->opened)
+		return NETDEV_TX_BUSY;
+
+	sq = adapter->txq2sq[skb_get_queue_mapping(skb)];
+	if (unlikely(!sq))
+		return NETDEV_TX_BUSY;
+
+	ds_num = adapter->xdev->caps.send_ds_num;
+	wqe = xsc_sq_fetch_wqe(sq, ds_num * XSC_SEND_WQE_DS, &pi);
+
+	return xsc_eth_xmit_frame(skb, sq, wqe, pi);
+}
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c
new file mode 100644
index 000000000..228070851
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "xsc_eth_common.h"
+#include "xsc_eth_txrx.h"
+
+void xsc_cq_notify_hw_rearm(struct xsc_cq *cq)
+{
+	xsc_arm_cq(cq->xdev, cq->xcq.cqn, cq->wq.cc, 0);
+}
+
+void xsc_cq_notify_hw(struct xsc_cq *cq)
+{
+	xsc_update_cq_ci(cq->xdev, cq->xcq.cqn, cq->wq.cc);
+}
+
+static bool xsc_channel_no_affinity_change(struct xsc_channel *c)
+{
+	int current_cpu = smp_processor_id();
+
+	return cpumask_test_cpu(current_cpu, c->aff_mask);
+}
+
+static void xsc_dump_error_sqcqe(struct xsc_sq *sq,
+				 struct xsc_cqe *cqe)
+{
+	struct net_device *netdev = sq->channel->netdev;
+	u32 ci = xsc_cqwq_get_ci(&sq->cq.wq);
+
+	net_err_ratelimited("Err cqe on dev %s cqn=0x%x ci=0x%x sqn=0x%x err_code=0x%x qpid=0x%lx\n",
+			    netdev->name, sq->cq.xcq.cqn, ci,
+			    sq->sqn, get_cqe_opcode(cqe),
+			    FIELD_GET(XSC_CQE_QP_ID_MASK,
+				      le32_to_cpu(cqe->data0)));
+}
+
+static bool xsc_poll_tx_cq(struct xsc_cq *cq, int napi_budget)
+{
+	struct xsc_adapter *adapter;
+	struct xsc_cqe *cqe;
+	struct device *dev;
+	struct xsc_sq *sq;
+	u32 dma_fifo_cc;
+	u32 nbytes = 0;
+	u16 npkts = 0;
+	int i = 0;
+	u16 sqcc;
+
+	sq = container_of(cq, struct xsc_sq, cq);
+	if (!test_bit(XSC_ETH_SQ_STATE_ENABLED, &sq->state))
+		return false;
+
+	adapter = sq->channel->adapter;
+	dev = adapter->dev;
+
+	cqe = xsc_cqwq_get_cqe(&cq->wq);
+	if (!cqe)
+		goto out;
+
+	if (unlikely(get_cqe_opcode(cqe) & BIT(7))) {
+		xsc_dump_error_sqcqe(sq, cqe);
+		return false;
+	}
+
+	sqcc = sq->cc;
+
+	/* avoid dirtying sq cache line every cqe */
+	dma_fifo_cc = sq->dma_fifo_cc;
+	i = 0;
+	do {
+		struct xsc_tx_wqe_info *wi;
+		struct sk_buff *skb;
+		int j;
+		u16 ci;
+
+		xsc_cqwq_pop(&cq->wq);
+
+		ci = xsc_wq_cyc_ctr2ix(&sq->wq, sqcc);
+		wi = &sq->db.wqe_info[ci];
+		skb = wi->skb;
+
+		/*cqe may be overstanding in real test, not by nop in other*/
+		if (unlikely(!skb))
+			continue;
+
+		for (j = 0; j < wi->num_dma; j++) {
+			struct xsc_sq_dma *dma = xsc_dma_get(sq, dma_fifo_cc++);
+
+			xsc_tx_dma_unmap(dev, dma);
+		}
+
+		npkts++;
+		nbytes += wi->num_bytes;
+		sqcc += wi->num_wqebbs;
+		napi_consume_skb(skb, 0);
+
+	} while ((++i <= napi_budget) && (cqe = xsc_cqwq_get_cqe(&cq->wq)));
+
+	xsc_cq_notify_hw(cq);
+
+	/* ensure cq space is freed before enabling more cqes */
+	wmb();
+
+	sq->dma_fifo_cc = dma_fifo_cc;
+	sq->cc = sqcc;
+
+	netdev_tx_completed_queue(sq->txq, npkts, nbytes);
+
+	if (netif_tx_queue_stopped(sq->txq) &&
+	    xsc_wqc_has_room_for(&sq->wq, sq->cc, sq->pc, sq->stop_room)) {
+		netif_tx_wake_queue(sq->txq);
+	}
+
+out:
+	return (i == napi_budget);
+}
+
+int xsc_eth_napi_poll(struct napi_struct *napi, int budget)
+{
+	struct xsc_channel *c = container_of(napi, struct xsc_channel, napi);
+	struct xsc_eth_params *params = &c->adapter->nic_param;
+	bool busy = false;
+	int tx_budget;
+
+	rcu_read_lock();
+
+	tx_budget = params->sq_size >> 2;
+	busy |= xsc_poll_tx_cq(&c->qp.sq.cq, tx_budget);
+
+	if (busy) {
+		if (likely(xsc_channel_no_affinity_change(c))) {
+			rcu_read_unlock();
+			return budget;
+		}
+	}
+
+	if (unlikely(!napi_complete_done(napi, 0)))
+		goto err_out;
+
+	xsc_cq_notify_hw_rearm(&c->qp.sq.cq);
+err_out:
+	rcu_read_unlock();
+	return 0;
+}

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

* [PATCH 08/15] yunsilicon: add ethernet RX path and statistics
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (6 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 07/15] yunsilicon: add ethernet TX path Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 09/15] yunsilicon: add RDMA device support Xin Tian
                   ` (7 subsequent siblings)
  15 siblings, 1 reply; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the receive path: RX queues backed by page_pool, the NAPI poll loop,
CQ processing and the RSS/checksum handling.  The channel layer now
opens the RQ/CQ pairs as well, and the driver advertises RX checksum and
hash offloads.  Also report the per-channel software counters through
ndo_get_stats64.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 .../net/ethernet/yunsilicon/xsc/net/Makefile  |   2 +-
 .../net/ethernet/yunsilicon/xsc/net/main.c    | 367 +++++++++++-
 .../ethernet/yunsilicon/xsc/net/xsc_eth_rx.c  | 551 ++++++++++++++++++
 .../yunsilicon/xsc/net/xsc_eth_stats.c        |  39 ++
 .../yunsilicon/xsc/net/xsc_eth_txrx.c         |  17 +-
 5 files changed, 971 insertions(+), 5 deletions(-)
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.c

diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/Makefile b/drivers/net/ethernet/yunsilicon/xsc/net/Makefile
index ffb819884..43d049e42 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/net/Makefile
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/Makefile
@@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_YUNSILICON_XSC_ETH) += xsc_eth.o
 
-xsc_eth-y := main.o xsc_eth_wq.o xsc_eth_txrx.o xsc_eth_tx.o
+xsc_eth-y := main.o xsc_eth_wq.o xsc_eth_txrx.o xsc_eth_tx.o xsc_eth_rx.o xsc_eth_stats.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/main.c b/drivers/net/ethernet/yunsilicon/xsc/net/main.c
index a20d01618..d1f72043f 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/net/main.c
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/main.c
@@ -691,6 +691,14 @@ static int xsc_eth_open_channel(struct xsc_adapter *adapter,
 static int xsc_eth_modify_qps_channel(struct xsc_adapter *adapter,
 				      struct xsc_channel *c)
 {
+	int ret;
+
+	c->qp.rq.post_wqes(&c->qp.rq);
+	ret = xsc_core_eth_modify_qp_status(adapter->xdev, c->qp.rq.rqn,
+					    XSC_CMD_OP_RTR2RTS_QP);
+	if (ret)
+		return ret;
+
 	return xsc_core_eth_modify_qp_status(adapter->xdev, c->qp.sq.sqn,
 					     XSC_CMD_OP_RTR2RTS_QP);
 }
@@ -712,8 +720,332 @@ static int xsc_eth_modify_qps(struct xsc_adapter *adapter,
 	return 0;
 }
 
+static void xsc_eth_init_frags_partition(struct xsc_rq *rq)
+{
+	struct xsc_wqe_frag_info next_frag = {};
+	struct xsc_wqe_frag_info *prev;
+	int i;
+
+	next_frag.di = &rq->wqe.di[0];
+	next_frag.offset = 0;
+	prev = NULL;
+
+	for (i = 0; i < xsc_wq_cyc_get_size(&rq->wqe.wq); i++) {
+		struct xsc_rq_frag_info *frag_info = &rq->wqe.info.arr[0];
+		struct xsc_wqe_frag_info *frag =
+			&rq->wqe.frags[i << rq->wqe.info.log_num_frags];
+		int f;
+
+		for (f = 0; f < rq->wqe.info.num_frags; f++, frag++) {
+			if (next_frag.offset + frag_info[f].frag_stride >
+				XSC_RX_FRAG_SZ) {
+				next_frag.di++;
+				next_frag.offset = 0;
+				if (prev)
+					prev->last_in_page = 1;
+			}
+			*frag = next_frag;
+
+			/* prepare next */
+			next_frag.offset += frag_info[f].frag_stride;
+			prev = frag;
+		}
+	}
+
+	if (prev)
+		prev->last_in_page = 1;
+}
+
+static int xsc_eth_init_di_list(struct xsc_rq *rq, u32 wq_sz, int cpu)
+{
+	int len = wq_sz << rq->wqe.info.log_num_frags;
+
+	rq->wqe.di = kvzalloc_node(array_size(len, sizeof(*rq->wqe.di)),
+				   GFP_KERNEL, cpu_to_node(cpu));
+	if (!rq->wqe.di)
+		return -ENOMEM;
+
+	xsc_eth_init_frags_partition(rq);
+
+	return 0;
+}
+
+static void xsc_eth_free_di_list(struct xsc_rq *rq)
+{
+	kvfree(rq->wqe.di);
+}
+
+static int xsc_eth_alloc_rq(struct xsc_channel *c,
+			    struct xsc_rq *prq,
+			    struct xsc_rq_param *prq_param)
+{
+	u8 ele_log_size = prq_param->rq_attr.ele_log_size;
+	u8 q_log_size = prq_param->rq_attr.q_log_size;
+	struct page_pool_params pagepool_params = {0};
+	struct xsc_adapter *adapter = c->adapter;
+	struct xsc_channel_stats *channel_stats;
+	u32 pool_size = 1 << q_log_size;
+	struct xsc_stats *stats;
+	int ret = 0;
+	u32 wq_sz;
+	int i, f;
+
+	stats = c->adapter->stats;
+	channel_stats = &stats->channel_stats[c->chl_idx];
+	prq->stats = &channel_stats->rq;
+	prq_param->wq.db_numa_node = cpu_to_node(c->cpu);
+
+	ret = xsc_eth_wq_cyc_create(c->adapter->xdev, &prq_param->wq,
+				    q_log_size, ele_log_size, &prq->wqe.wq,
+				    &prq->wq_ctrl);
+	if (ret)
+		return ret;
+
+	wq_sz = xsc_wq_cyc_get_size(&prq->wqe.wq);
+
+	prq->wqe.info = prq_param->frags_info;
+	prq->wqe.frags =
+		kvzalloc_node(array_size((wq_sz << prq->wqe.info.log_num_frags),
+					 sizeof(*prq->wqe.frags)),
+			      GFP_KERNEL,
+			      cpu_to_node(c->cpu));
+	if (!prq->wqe.frags) {
+		ret = -ENOMEM;
+		goto err_destroy_wq;
+	}
+
+	ret = xsc_eth_init_di_list(prq, wq_sz, c->cpu);
+	if (ret)
+		goto err_free_frags;
+
+	prq->buff.map_dir = DMA_FROM_DEVICE;
+
+	/* Create a page_pool and register it with rxq */
+	pool_size = wq_sz << prq->wqe.info.log_num_frags;
+	pagepool_params.order = XSC_RX_FRAG_SZ_ORDER;
+	pagepool_params.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
+	pagepool_params.pool_size = pool_size;
+	pagepool_params.nid = cpu_to_node(c->cpu);
+	pagepool_params.dev = c->adapter->dev;
+	pagepool_params.dma_dir	= prq->buff.map_dir;
+	pagepool_params.max_len = PAGE_SIZE;
+
+	prq->page_pool = page_pool_create(&pagepool_params);
+	if (IS_ERR(prq->page_pool)) {
+		ret = PTR_ERR(prq->page_pool);
+		prq->page_pool = NULL;
+		goto err_free_di_list;
+	}
+
+	if (c->chl_idx == 0)
+		netdev_dbg(adapter->netdev,
+			   "page pool: size=%d, cpu=%d, pool_numa=%d, mtu=%d, wqe_numa=%d\n",
+			   pool_size, c->cpu, pagepool_params.nid,
+			   adapter->nic_param.mtu,
+			   prq_param->wq.buf_numa_node);
+
+	for (i = 0; i < wq_sz; i++) {
+		struct xsc_eth_rx_wqe_cyc *wqe =
+			xsc_wq_cyc_get_wqe(&prq->wqe.wq, i);
+
+		for (f = 0; f < prq->wqe.info.num_frags; f++) {
+			u32 frag_size = prq->wqe.info.arr[f].frag_size;
+			u32 val = 0;
+
+			val |= FIELD_PREP(XSC_WQE_DATA_SEG_SEG_LEN_MASK,
+					  frag_size);
+			wqe->data[f].data0 = cpu_to_le32(val);
+			wqe->data[f].mkey = cpu_to_le32(XSC_INVALID_LKEY);
+		}
+
+		for (; f < prq->wqe.info.frags_max_num; f++) {
+			wqe->data[f].data0 = 0;
+			wqe->data[f].mkey = cpu_to_le32(XSC_INVALID_LKEY);
+			wqe->data[f].va = 0;
+		}
+	}
+
+	prq->post_wqes = xsc_eth_post_rx_wqes;
+	prq->handle_rx_cqe = xsc_eth_handle_rx_cqe;
+	prq->dealloc_wqe = xsc_eth_dealloc_rx_wqe;
+	prq->wqe.skb_from_cqe = xsc_rx_is_linear_skb(adapter->nic_param.mtu) ?
+					xsc_skb_from_cqe_linear :
+					xsc_skb_from_cqe_nonlinear;
+	prq->ix = c->chl_idx;
+	prq->frags_sz = adapter->nic_param.rq_frags_size;
+
+	return 0;
+
+err_free_di_list:
+	xsc_eth_free_di_list(prq);
+err_free_frags:
+	kvfree(prq->wqe.frags);
+err_destroy_wq:
+	xsc_eth_wq_destroy(&prq->wq_ctrl);
+	return ret;
+}
+
+static void xsc_free_qp_rq(struct xsc_rq *rq)
+{
+	kvfree(rq->wqe.frags);
+	kvfree(rq->wqe.di);
+
+	if (rq->page_pool)
+		page_pool_destroy(rq->page_pool);
+
+	xsc_eth_wq_destroy(&rq->wq_ctrl);
+}
+
+static int xsc_eth_open_rss_qp_rqs(struct xsc_adapter *adapter,
+				   struct xsc_rq_param *prq_param,
+				   struct xsc_eth_channels *chls,
+				   unsigned int num_chl)
+{
+	u8 q_log_size = prq_param->rq_attr.q_log_size;
+	struct xsc_create_multiqp_mbox_in *in;
+	struct xsc_create_qp_request *req;
+	unsigned int hw_npages;
+	struct xsc_channel *c;
+	int ret = 0, err = 0;
+	struct xsc_rq *prq;
+	int paslen = 0;
+	int entry_len;
+	u32 rqn_base;
+	int i, n;
+	int inlen;
+
+	for (i = 0; i < num_chl; i++) {
+		c = &chls->c[i];
+		prq = &c->qp.rq;
+
+		ret = xsc_eth_alloc_rq(c, prq, prq_param);
+		if (ret)
+			goto err_alloc_rqs;
+
+		hw_npages = DIV_ROUND_UP(prq->wq_ctrl.buf.size, PAGE_SIZE_4K);
+		/*support different npages number smoothly*/
+		entry_len = sizeof(struct xsc_create_qp_request) +
+			sizeof(__be64) * hw_npages;
+
+		paslen += entry_len;
+	}
+
+	inlen = sizeof(struct xsc_create_multiqp_mbox_in) + paslen;
+	in = kvzalloc(inlen, GFP_KERNEL);
+	if (!in) {
+		ret = -ENOMEM;
+		goto err_create_rss_rqs;
+	}
+
+	in->qp_num = cpu_to_be16(num_chl);
+	in->qp_type = XSC_QUEUE_TYPE_RAW;
+	in->req_len = cpu_to_be32(inlen);
+
+	req = (struct xsc_create_qp_request *)&in->data[0];
+	n = 0;
+	for (i = 0; i < num_chl; i++) {
+		c = &chls->c[i];
+		prq = &c->qp.rq;
+
+		hw_npages = DIV_ROUND_UP(prq->wq_ctrl.buf.size, PAGE_SIZE_4K);
+		/* no use for eth */
+		req->input_qpn = cpu_to_be16(0);
+		req->qp_type = XSC_QUEUE_TYPE_RAW;
+		req->log_rq_sz = ilog2(adapter->xdev->caps.recv_ds_num)
+					+ q_log_size;
+		req->pa_num = cpu_to_be16(hw_npages);
+		req->cqn_recv = cpu_to_be16(prq->cq.xcq.cqn);
+		req->cqn_send = req->cqn_recv;
+		req->glb_funcid = cpu_to_be16(adapter->xdev->glb_func_id);
+
+		xsc_core_fill_page_frag_array(&prq->wq_ctrl.buf,
+					      &req->pas[0], hw_npages);
+		n++;
+		entry_len = sizeof(struct xsc_create_qp_request) +
+			sizeof(__be64) * hw_npages;
+		req = (struct xsc_create_qp_request *)
+			((u8 *)req + entry_len);
+	}
+
+	ret = xsc_core_eth_create_rss_qp_rqs(adapter->xdev, in, inlen,
+					     &rqn_base);
+	kvfree(in);
+	if (ret)
+		goto err_create_rss_rqs;
+
+	n = 0;
+	for (i = 0; i < num_chl; i++) {
+		c = &chls->c[i];
+		prq = &c->qp.rq;
+
+		prq->rqn = rqn_base + n;
+		prq->cqp.qpn = prq->rqn;
+		prq->cqp.event = xsc_eth_qp_event;
+		prq->cqp.eth_queue_type = XSC_RES_RQ;
+		ret = xsc_core_create_resource_common(adapter->xdev,
+						      &prq->cqp);
+		if (ret) {
+			err = ret;
+			netdev_err(adapter->netdev,
+				   "create resource common error qp:%d errno:%d\n",
+				   prq->rqn, ret);
+			continue;
+		}
+
+		n++;
+	}
+	if (err)
+		return err;
+
+	adapter->channels.rqn_base = rqn_base;
+	return 0;
+
+err_create_rss_rqs:
+	i = num_chl;
+err_alloc_rqs:
+	for (--i; i >= 0; i--) {
+		c = &chls->c[i];
+		xsc_free_qp_rq(&c->qp.rq);
+	}
+	return ret;
+}
+
+static void xsc_eth_free_rx_wqe(struct xsc_rq *rq)
+{
+	struct xsc_wq_cyc *wq = &rq->wqe.wq;
+	u16 wqe_ix;
+
+	while (!xsc_wq_cyc_is_empty(wq)) {
+		wqe_ix = xsc_wq_cyc_get_tail(wq);
+		rq->dealloc_wqe(rq, wqe_ix);
+		xsc_wq_cyc_pop(wq);
+	}
+}
+
+static int xsc_eth_close_qp_rq(struct xsc_channel *c, struct xsc_rq *prq)
+{
+	struct xsc_core_device *xdev = c->adapter->xdev;
+	int ret;
+
+	xsc_core_destroy_resource_common(xdev, &prq->cqp);
+
+	ret = xsc_core_eth_destroy_qp(xdev, prq->cqp.qpn);
+	if (ret)
+		return ret;
+
+	xsc_eth_free_rx_wqe(prq);
+	xsc_free_qp_rq(prq);
+
+	return 0;
+}
+
 static void xsc_eth_close_channel(struct xsc_channel *c, bool free_rq)
 {
+	if (free_rq)
+		xsc_eth_close_qp_rq(c, &c->qp.rq);
+	xsc_eth_close_cq(c, &c->qp.rq.cq);
+	memset(&c->qp.rq, 0, sizeof(struct xsc_rq));
+
 	xsc_eth_close_qp_sq(c, &c->qp.sq);
 	xsc_eth_close_cq(c, &c->qp.sq.cq);
 
@@ -725,6 +1057,7 @@ static int xsc_eth_open_channels(struct xsc_adapter *adapter)
 	struct xsc_eth_channels *chls = &adapter->channels;
 	struct xsc_core_device *xdev = adapter->xdev;
 	struct xsc_channel_param *chl_param;
+	bool free_rq = false;
 	int ret = 0;
 	int i;
 
@@ -750,6 +1083,14 @@ static int xsc_eth_open_channels(struct xsc_adapter *adapter)
 			goto err_close_channel;
 	}
 
+	ret = xsc_eth_open_rss_qp_rqs(adapter,
+				      &chl_param->rq_param,
+				      chls,
+				      chls->num_chl);
+	if (ret)
+		goto err_close_channel;
+	free_rq = true;
+
 	for (i = 0; i < chls->num_chl; i++)
 		napi_enable(&chls->c[i].napi);
 
@@ -765,7 +1106,7 @@ static int xsc_eth_open_channels(struct xsc_adapter *adapter)
 
 err_close_channel:
 	for (--i; i >= 0; i--)
-		xsc_eth_close_channel(&chls->c[i], false);
+		xsc_eth_close_channel(&chls->c[i], free_rq);
 
 	kvfree(chl_param);
 err_free_ch:
@@ -820,13 +1161,25 @@ static void xsc_eth_deactivate_txqsq(struct xsc_channel *c)
 	clear_bit(XSC_ETH_SQ_STATE_ENABLED, &c->qp.sq.state);
 }
 
+static void xsc_activate_rq(struct xsc_channel *c)
+{
+	set_bit(XSC_ETH_RQ_STATE_ENABLED, &c->qp.rq.state);
+}
+
+static void xsc_deactivate_rq(struct xsc_channel *c)
+{
+	clear_bit(XSC_ETH_RQ_STATE_ENABLED, &c->qp.rq.state);
+}
+
 static void xsc_eth_activate_channel(struct xsc_channel *c)
 {
 	xsc_eth_activate_txqsq(c);
+	xsc_activate_rq(c);
 }
 
 static void xsc_eth_deactivate_channel(struct xsc_channel *c)
 {
+	xsc_deactivate_rq(c);
 	xsc_eth_deactivate_txqsq(c);
 }
 
@@ -1106,6 +1459,14 @@ static int xsc_eth_close(struct net_device *netdev)
 	return ret;
 }
 
+static void xsc_eth_get_stats(struct net_device *netdev,
+			      struct rtnl_link_stats64 *stats)
+{
+	struct xsc_adapter *adapter = netdev_priv(netdev);
+
+	xsc_eth_fold_sw_stats64(adapter, stats);
+}
+
 static int xsc_eth_set_hw_mtu(struct xsc_core_device *xdev,
 			      u16 mtu, u16 rx_buf_sz)
 {
@@ -1137,6 +1498,7 @@ static const struct net_device_ops xsc_netdev_ops = {
 	.ndo_open		= xsc_eth_open,
 	.ndo_stop		= xsc_eth_close,
 	.ndo_start_xmit		= xsc_eth_xmit_start,
+	.ndo_get_stats64	= xsc_eth_get_stats,
 };
 
 static void xsc_eth_build_nic_netdev(struct xsc_adapter *adapter)
@@ -1153,7 +1515,8 @@ static void xsc_eth_build_nic_netdev(struct xsc_adapter *adapter)
 
 	netdev->vlan_features |= NETIF_F_SG |
 				 NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
-				 NETIF_F_TSO | NETIF_F_TSO6;
+				 NETIF_F_TSO | NETIF_F_TSO6 |
+				 NETIF_F_RXCSUM | NETIF_F_RXHASH;
 
 	netdev->hw_features = netdev->vlan_features;
 
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c
new file mode 100644
index 000000000..3913a9671
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c
@@ -0,0 +1,551 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd. All
+ * rights reserved.
+ * Copyright (c) 2015-2016, Mellanox Technologies. All rights reserved.
+ */
+
+#include <linux/net_tstamp.h>
+#include <linux/device.h>
+
+#include "xsc_pp.h"
+#include "xsc_eth.h"
+#include "xsc_eth_txrx.h"
+#include "xsc_eth_common.h"
+#include "xsc_pph.h"
+
+static void xsc_rq_notify_hw(struct xsc_rq *rq)
+{
+	struct xsc_core_device *xdev = rq->cq.xdev;
+	struct xsc_wq_cyc *wq = &rq->wqe.wq;
+	u64 rqwqe_id;
+
+	rqwqe_id = wq->wqe_ctr << (ilog2(xdev->caps.recv_ds_num));
+	/* reverse wqe index to ds index */
+	xsc_update_rx_db(xdev, rq->rqn, rqwqe_id);
+}
+
+static void xsc_skb_set_hash(struct xsc_adapter *adapter,
+			     struct xsc_cqe *cqe,
+			     struct sk_buff *skb)
+{
+	struct xsc_rss_params *rss = &adapter->rss_param;
+	bool l3_hash = false;
+	bool l4_hash = false;
+	u32 hash_field;
+	int ht = 0;
+
+	if (adapter->netdev->features & NETIF_F_RXHASH) {
+		if (skb->protocol == htons(ETH_P_IP)) {
+			hash_field = rss->rx_hash_fields[XSC_TT_IPV4_TCP];
+			if (hash_field & XSC_HASH_FIELD_SEL_SRC_IP ||
+			    hash_field & XSC_HASH_FIELD_SEL_DST_IP)
+				l3_hash = true;
+
+			if (hash_field & XSC_HASH_FIELD_SEL_SPORT ||
+			    hash_field & XSC_HASH_FIELD_SEL_DPORT)
+				l4_hash = true;
+		} else if (skb->protocol == htons(ETH_P_IPV6)) {
+			hash_field = rss->rx_hash_fields[XSC_TT_IPV6_TCP];
+			if (hash_field & XSC_HASH_FIELD_SEL_SRC_IPV6 ||
+			    hash_field & XSC_HASH_FIELD_SEL_DST_IPV6)
+				l3_hash = true;
+
+			if (hash_field & XSC_HASH_FIELD_SEL_SPORT_V6 ||
+			    hash_field & XSC_HASH_FIELD_SEL_DPORT_V6)
+				l4_hash = true;
+		}
+
+		if (l3_hash && l4_hash)
+			ht = PKT_HASH_TYPE_L4;
+		else if (l3_hash)
+			ht = PKT_HASH_TYPE_L3;
+		if (ht)
+			skb_set_hash(skb, le32_to_cpu(cqe->vni), ht);
+	}
+}
+
+static void xsc_handle_csum(struct xsc_cqe *cqe, struct xsc_rq *rq,
+			    struct sk_buff *skb, struct xsc_wqe_frag_info *wi)
+{
+	struct xsc_dma_info *dma_info;
+	struct net_device *netdev;
+	void *hw_pph;
+	struct xsc_channel *c;
+	int offset_from;
+
+	c = rq->cq.channel;
+	netdev = c->adapter->netdev;
+	dma_info = wi->di;
+	offset_from = wi->offset;
+	hw_pph = page_address(dma_info->page) + offset_from;
+
+	if (unlikely((netdev->features & NETIF_F_RXCSUM) == 0))
+		goto csum_none;
+
+	if (unlikely(XSC_GET_EPP2SOC_PPH_ERROR_BITMAP(hw_pph) & PACKET_UNKNOWN))
+		goto csum_none;
+
+	if (XSC_GET_EPP2SOC_PPH_EXT_TUNNEL_TYPE(hw_pph) &&
+	    (!(FIELD_GET(XSC_CQE_CSUM_ERR_MASK, le32_to_cpu(cqe->data0)) &
+	       OUTER_AND_INNER))) {
+		skb->ip_summed = CHECKSUM_UNNECESSARY;
+		skb->csum_level = 1;
+		skb->encapsulation = 1;
+	} else if (XSC_GET_EPP2SOC_PPH_EXT_TUNNEL_TYPE(hw_pph) &&
+		   (!(FIELD_GET(XSC_CQE_CSUM_ERR_MASK,
+				le32_to_cpu(cqe->data0)) &
+		      OUTER_BIT) &&
+		    (FIELD_GET(XSC_CQE_CSUM_ERR_MASK,
+			       le32_to_cpu(cqe->data0)) &
+		     INNER_BIT))) {
+		skb->ip_summed = CHECKSUM_UNNECESSARY;
+		skb->csum_level = 0;
+		skb->encapsulation = 1;
+	} else if (!XSC_GET_EPP2SOC_PPH_EXT_TUNNEL_TYPE(hw_pph) &&
+		   (!(FIELD_GET(XSC_CQE_CSUM_ERR_MASK,
+				le32_to_cpu(cqe->data0)) &
+		      OUTER_BIT))) {
+		skb->ip_summed = CHECKSUM_UNNECESSARY;
+	}
+
+	goto out;
+
+csum_none:
+	skb->csum = 0;
+	skb->ip_summed = CHECKSUM_NONE;
+out:
+	return;
+}
+
+static void xsc_build_rx_skb(struct xsc_cqe *cqe,
+			     u32 cqe_bcnt,
+			     struct xsc_rq *rq,
+			     struct sk_buff *skb,
+			     struct xsc_wqe_frag_info *wi)
+{
+	struct xsc_adapter *adapter;
+	struct net_device *netdev;
+	struct xsc_channel *c;
+
+	c = rq->cq.channel;
+	adapter = c->adapter;
+	netdev = c->netdev;
+
+	skb->mac_len = ETH_HLEN;
+
+	skb_record_rx_queue(skb, rq->ix);
+	xsc_handle_csum(cqe, rq, skb, wi);
+
+	skb->protocol = eth_type_trans(skb, netdev);
+	xsc_skb_set_hash(adapter, cqe, skb);
+}
+
+static void xsc_complete_rx_cqe(struct xsc_rq *rq,
+				struct xsc_cqe *cqe,
+				u32 cqe_bcnt,
+				struct sk_buff *skb,
+				struct xsc_wqe_frag_info *wi)
+{
+	struct xsc_rq_stats *stats = rq->stats;
+
+	stats->packets++;
+	stats->bytes += cqe_bcnt;
+	xsc_build_rx_skb(cqe, cqe_bcnt, rq, skb, wi);
+}
+
+static void xsc_add_skb_frag(struct xsc_rq *rq,
+			     struct sk_buff *skb,
+			     struct xsc_dma_info *di,
+			     u32 frag_offset, u32 len,
+			     unsigned int truesize)
+{
+	struct xsc_channel *c = rq->cq.channel;
+	struct device *dev = c->adapter->dev;
+
+	dma_sync_single_for_cpu(dev, di->addr + frag_offset,
+				len, DMA_FROM_DEVICE);
+	page_ref_inc(di->page);
+	skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
+			di->page, frag_offset, len, truesize);
+}
+
+static void xsc_copy_skb_header(struct device *dev,
+				struct sk_buff *skb,
+				struct xsc_dma_info *dma_info,
+				int offset_from, u32 headlen)
+{
+	void *from = page_address(dma_info->page) + offset_from;
+	/* Aligning len to sizeof(long) optimizes memcpy performance */
+	unsigned int len = ALIGN(headlen, sizeof(long));
+
+	dma_sync_single_for_cpu(dev, dma_info->addr + offset_from, len,
+				DMA_FROM_DEVICE);
+	skb_copy_to_linear_data(skb, from, len);
+}
+
+static struct sk_buff *xsc_build_linear_skb(struct xsc_rq *rq, void *va,
+					    u32 frag_size, u16 headroom,
+					    u32 cqe_bcnt)
+{
+	struct sk_buff *skb = build_skb(va, frag_size);
+
+	if (unlikely(!skb))
+		return NULL;
+
+	skb_reserve(skb, headroom);
+	skb_put(skb, cqe_bcnt);
+
+	return skb;
+}
+
+struct sk_buff *xsc_skb_from_cqe_linear(struct xsc_rq *rq,
+					struct xsc_wqe_frag_info *wi,
+					u32 cqe_bcnt, u8 has_pph)
+{
+	int pph_len = has_pph ? XSC_PPH_HEAD_LEN : 0;
+	u16 rx_headroom = rq->buff.headroom;
+	struct xsc_dma_info *di = wi->di;
+	struct sk_buff *skb;
+	void *va, *data;
+	u32 frag_size;
+
+	va = page_address(di->page) + wi->offset;
+	data = va + rx_headroom + pph_len;
+	frag_size = XSC_SKB_FRAG_SZ(rx_headroom + cqe_bcnt);
+
+	dma_sync_single_range_for_cpu(rq->cq.xdev->device, di->addr, wi->offset,
+				      frag_size, DMA_FROM_DEVICE);
+	net_prefetchw(va); /* xdp_frame data area */
+	net_prefetch(data);
+
+	skb = xsc_build_linear_skb(rq, va, frag_size, (rx_headroom + pph_len),
+				   (cqe_bcnt - pph_len));
+	if (unlikely(!skb))
+		return NULL;
+
+	/* queue up for recycling/reuse */
+	page_ref_inc(di->page);
+
+	return skb;
+}
+
+struct sk_buff *xsc_skb_from_cqe_nonlinear(struct xsc_rq *rq,
+					   struct xsc_wqe_frag_info *wi,
+					   u32 cqe_bcnt, u8 has_pph)
+{
+	struct xsc_rq_frag_info *frag_info = &rq->wqe.info.arr[0];
+	u16 headlen  = min_t(u32, XSC_RX_MAX_HEAD, cqe_bcnt);
+	struct xsc_wqe_frag_info *head_wi = wi;
+	struct xsc_wqe_frag_info *rx_wi = wi;
+	u16 head_offset = head_wi->offset;
+	u16 byte_cnt = cqe_bcnt - headlen;
+	u16 frag_consumed_bytes = 0;
+	u16 frag_headlen = headlen;
+	struct net_device *netdev;
+	struct xsc_channel *c;
+	struct sk_buff *skb;
+	struct device *dev;
+	u8 fragcnt = 0;
+	int i = 0;
+
+	c = rq->cq.channel;
+	dev = c->adapter->dev;
+	netdev = c->adapter->netdev;
+
+	skb = napi_alloc_skb(rq->cq.napi, ALIGN(XSC_RX_MAX_HEAD, sizeof(long)));
+	if (unlikely(!skb))
+		return NULL;
+
+	net_prefetchw(skb->data);
+
+	if (likely(has_pph)) {
+		headlen = min_t(u32, XSC_RX_MAX_HEAD,
+				(cqe_bcnt - XSC_PPH_HEAD_LEN));
+		frag_headlen = headlen + XSC_PPH_HEAD_LEN;
+		byte_cnt = cqe_bcnt - headlen - XSC_PPH_HEAD_LEN;
+		head_offset += XSC_PPH_HEAD_LEN;
+	}
+
+	for (i = 0; i < rq->wqe.info.num_frags; i++, rx_wi++)
+		rx_wi->is_available = 0;
+
+	while (byte_cnt) {
+		/*figure out whether the first fragment can be a page ?*/
+		frag_consumed_bytes =
+			min_t(u16, frag_info->frag_size - frag_headlen,
+			      byte_cnt);
+
+		xsc_add_skb_frag(rq, skb, wi->di, wi->offset + frag_headlen,
+				 frag_consumed_bytes, frag_info->frag_stride);
+		byte_cnt -= frag_consumed_bytes;
+
+		/*to protect extend wqe read, drop exceed bytes*/
+		frag_headlen = 0;
+		fragcnt++;
+		if (fragcnt == rq->wqe.info.num_frags) {
+			if (byte_cnt) {
+				netdev_warn(netdev,
+					    "large packet reach the maximum rev-wqe num.\n");
+				netdev_warn(netdev,
+					    "%u bytes dropped: frag_num=%d, headlen=%d, cqe_cnt=%d, frag0_bytes=%d, frag_size=%d\n",
+					    byte_cnt, fragcnt,
+					    headlen, cqe_bcnt,
+					    frag_consumed_bytes,
+					    frag_info->frag_size);
+			}
+			break;
+		}
+
+		frag_info++;
+		wi++;
+	}
+
+	/* copy header */
+	xsc_copy_skb_header(dev, skb, head_wi->di, head_offset, headlen);
+
+	/* skb linear part was allocated with headlen and aligned to long */
+	skb->tail += headlen;
+	skb->len += headlen;
+
+	return skb;
+}
+
+static void xsc_put_rx_frag(struct xsc_rq *rq,
+			    struct xsc_wqe_frag_info *frag, bool recycle)
+{
+	if (frag->last_in_page)
+		page_pool_recycle_direct(rq->page_pool, frag->di->page);
+}
+
+static struct xsc_wqe_frag_info *get_frag(struct xsc_rq *rq, u16 ix)
+{
+	return &rq->wqe.frags[ix << rq->wqe.info.log_num_frags];
+}
+
+static void xsc_free_rx_wqe(struct xsc_rq *rq,
+			    struct xsc_wqe_frag_info *wi, bool recycle)
+{
+	int i;
+
+	for (i = 0; i < rq->wqe.info.num_frags; i++, wi++) {
+		if (wi->is_available && recycle)
+			continue;
+		xsc_put_rx_frag(rq, wi, recycle);
+	}
+}
+
+static void xsc_dump_error_rqcqe(struct xsc_rq *rq,
+				 struct xsc_cqe *cqe)
+{
+	struct net_device *netdev;
+	struct xsc_channel *c;
+	u32 ci;
+
+	c = rq->cq.channel;
+	netdev  = c->adapter->netdev;
+	ci = xsc_cqwq_get_ci(&rq->cq.wq);
+
+	net_err_ratelimited("Error cqe on dev=%s, cqn=%d, ci=%d, rqn=%d, qpn=%ld, error_code=0x%x\n",
+			    netdev->name, rq->cq.xcq.cqn, ci,
+			    rq->rqn,
+			    FIELD_GET(XSC_CQE_QP_ID_MASK,
+				      le32_to_cpu(cqe->data0)),
+			    get_cqe_opcode(cqe));
+}
+
+void xsc_eth_handle_rx_cqe(struct xsc_cqwq *cqwq,
+			   struct xsc_rq *rq, struct xsc_cqe *cqe)
+{
+	struct xsc_wq_cyc *wq = &rq->wqe.wq;
+	u8 cqe_opcode = get_cqe_opcode(cqe);
+	struct xsc_wqe_frag_info *wi;
+	struct sk_buff *skb;
+	u32 cqe_bcnt;
+	u16 ci;
+
+	ci = xsc_wq_cyc_ctr2ix(wq, cqwq->cc);
+	wi = get_frag(rq, ci);
+	if (unlikely(cqe_opcode & BIT(7))) {
+		xsc_dump_error_rqcqe(rq, cqe);
+		goto free_wqe;
+	}
+
+	cqe_bcnt = le32_to_cpu(cqe->msg_len);
+	if ((le32_to_cpu(cqe->data0) & XSC_CQE_HAS_PPH) &&
+	    cqe_bcnt <= XSC_PPH_HEAD_LEN)
+		goto free_wqe;
+
+	if (unlikely(cqe_bcnt > rq->frags_sz))
+		goto free_wqe;
+
+	cqe_bcnt = min_t(u32, cqe_bcnt, rq->frags_sz);
+	skb = rq->wqe.skb_from_cqe(rq, wi, cqe_bcnt,
+				   !!(le32_to_cpu(cqe->data0) &
+				      XSC_CQE_HAS_PPH));
+	if (!skb)
+		goto free_wqe;
+
+	xsc_complete_rx_cqe(rq, cqe,
+			    (le32_to_cpu(cqe->data0) & XSC_CQE_HAS_PPH) ?
+			    cqe_bcnt - XSC_PPH_HEAD_LEN : cqe_bcnt,
+			    skb, wi);
+
+	napi_gro_receive(rq->cq.napi, skb);
+
+free_wqe:
+	xsc_free_rx_wqe(rq, wi, true);
+	xsc_wq_cyc_pop(wq);
+}
+
+int xsc_poll_rx_cq(struct xsc_cq *cq, int budget)
+{
+	struct xsc_rq *rq = container_of(cq, struct xsc_rq, cq);
+	struct xsc_cqwq *cqwq = &cq->wq;
+	struct xsc_cqe *cqe;
+	int work_done = 0;
+
+	if (!test_bit(XSC_ETH_RQ_STATE_ENABLED, &rq->state))
+		return 0;
+
+	while ((work_done < budget) && (cqe = xsc_cqwq_get_cqe(cqwq))) {
+		rq->handle_rx_cqe(cqwq, rq, cqe);
+		++work_done;
+
+		xsc_cqwq_pop(cqwq);
+	}
+
+	if (!work_done)
+		goto out;
+
+	xsc_cq_notify_hw(cq);
+	/* ensure cq space is freed before enabling more cqes */
+	wmb();
+
+out:
+	return work_done;
+}
+
+static int xsc_page_alloc_pool(struct xsc_rq *rq,
+			       struct xsc_dma_info *dma_info)
+{
+	dma_info->page = page_pool_dev_alloc_pages(rq->page_pool);
+	if (unlikely(!dma_info->page))
+		return -ENOMEM;
+	dma_info->addr = page_pool_get_dma_addr(dma_info->page);
+
+	return 0;
+}
+
+static int xsc_get_rx_frag(struct xsc_rq *rq,
+			   struct xsc_wqe_frag_info *frag)
+{
+	int err = 0;
+
+	if (!frag->offset && !frag->is_available)
+		/* On first frag (offset == 0), replenish page (dma_info
+		 * actually). Other frags that point to the same dma_info
+		 * (with a different offset) should just use the new one
+		 * without replenishing again by themselves.
+		 */
+		err = xsc_page_alloc_pool(rq, frag->di);
+
+	return err;
+}
+
+static int xsc_alloc_rx_wqe(struct xsc_rq *rq,
+			    struct xsc_eth_rx_wqe_cyc *wqe,
+			    u16 ix)
+{
+	struct xsc_wqe_frag_info *frag = get_frag(rq, ix);
+	u64 addr;
+	int err;
+	int i;
+
+	for (i = 0; i < rq->wqe.info.num_frags; i++, frag++) {
+		err = xsc_get_rx_frag(rq, frag);
+		if (unlikely(err))
+			goto err_free_frags;
+
+		addr = frag->di->addr + frag->offset + rq->buff.headroom;
+		wqe->data[i].va = cpu_to_le64(addr);
+	}
+
+	return 0;
+
+err_free_frags:
+	while (--i >= 0)
+		xsc_put_rx_frag(rq, --frag, true);
+
+	return err;
+}
+
+void xsc_eth_dealloc_rx_wqe(struct xsc_rq *rq, u16 ix)
+{
+	struct xsc_wqe_frag_info *wi = get_frag(rq, ix);
+
+	xsc_free_rx_wqe(rq, wi, false);
+}
+
+static int xsc_alloc_rx_wqes(struct xsc_rq *rq, u16 ix, u8 wqe_bulk)
+{
+	struct xsc_wq_cyc *wq = &rq->wqe.wq;
+	struct xsc_eth_rx_wqe_cyc *wqe;
+	int err;
+	int idx;
+	int i;
+
+	for (i = 0; i < wqe_bulk; i++) {
+		idx = xsc_wq_cyc_ctr2ix(wq, (ix + i));
+		wqe = xsc_wq_cyc_get_wqe(wq, idx);
+
+		err = xsc_alloc_rx_wqe(rq, wqe, idx);
+		if (unlikely(err))
+			goto err_free_wqes;
+	}
+
+	return 0;
+
+err_free_wqes:
+	while (--i >= 0)
+		xsc_eth_dealloc_rx_wqe(rq, ix + i);
+
+	return err;
+}
+
+bool xsc_eth_post_rx_wqes(struct xsc_rq *rq)
+{
+	struct xsc_wq_cyc *wq = &rq->wqe.wq;
+	u8 wqe_bulk, wqe_bulk_min;
+	int err = 0;
+	int alloc;
+	u16 head;
+
+	wqe_bulk = rq->wqe.info.wqe_bulk;
+	wqe_bulk_min = rq->wqe.info.wqe_bulk_min;
+	if (xsc_wq_cyc_missing(wq) < wqe_bulk)
+		return false;
+
+	do {
+		head = xsc_wq_cyc_get_head(wq);
+
+		alloc = min_t(int, wqe_bulk, xsc_wq_cyc_missing(wq));
+		if (alloc < wqe_bulk && alloc >= wqe_bulk_min)
+			alloc = alloc & 0xfffffffe;
+
+		if (alloc > 0) {
+			err = xsc_alloc_rx_wqes(rq, head, alloc);
+			if (unlikely(err))
+				break;
+
+			xsc_wq_cyc_push_n(wq, alloc);
+		}
+	} while (xsc_wq_cyc_missing(wq) >= wqe_bulk_min);
+
+	dma_wmb();
+
+	/* ensure wqes are visible to device before updating doorbell record */
+	xsc_rq_notify_hw(rq);
+
+	return !!err;
+}
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.c b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.c
new file mode 100644
index 000000000..b466fc1f0
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include "xsc_eth_stats.h"
+#include "xsc_eth.h"
+
+static int xsc_get_netdev_max_channels(struct xsc_adapter *adapter)
+{
+	struct net_device *netdev = adapter->netdev;
+
+	return min_t(unsigned int, netdev->num_rx_queues,
+		     netdev->num_tx_queues);
+}
+
+void xsc_eth_fold_sw_stats64(struct xsc_adapter *adapter,
+			     struct rtnl_link_stats64 *s)
+{
+	int i;
+
+	for (i = 0; i < xsc_get_netdev_max_channels(adapter); i++) {
+		struct xsc_channel_stats *channel_stats;
+		struct xsc_rq_stats *rq_stats;
+		struct xsc_sq_stats *sq_stats;
+
+		channel_stats = &adapter->stats->channel_stats[i];
+		rq_stats = &channel_stats->rq;
+		sq_stats = &channel_stats->sq;
+
+		s->rx_packets   += rq_stats->packets;
+		s->rx_bytes     += rq_stats->bytes;
+
+		s->tx_packets    += sq_stats->packets;
+		s->tx_bytes      += sq_stats->bytes;
+		s->tx_dropped    += sq_stats->dropped;
+	}
+}
+
diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c
index 228070851..3effa79ef 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c
+++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c
@@ -121,7 +121,9 @@ int xsc_eth_napi_poll(struct napi_struct *napi, int budget)
 {
 	struct xsc_channel *c = container_of(napi, struct xsc_channel, napi);
 	struct xsc_eth_params *params = &c->adapter->nic_param;
+	struct xsc_rq *rq = &c->qp.rq;
 	bool busy = false;
+	int work_done = 0;
 	int tx_budget;
 
 	rcu_read_lock();
@@ -129,18 +131,29 @@ int xsc_eth_napi_poll(struct napi_struct *napi, int budget)
 	tx_budget = params->sq_size >> 2;
 	busy |= xsc_poll_tx_cq(&c->qp.sq.cq, tx_budget);
 
+	/* budget=0 means: don't poll rx rings */
+	if (likely(budget)) {
+		work_done = xsc_poll_rx_cq(&rq->cq, budget);
+		busy |= work_done == budget;
+	}
+
+	busy |= rq->post_wqes(rq);
+
 	if (busy) {
 		if (likely(xsc_channel_no_affinity_change(c))) {
 			rcu_read_unlock();
 			return budget;
 		}
+		if (budget && work_done == budget)
+			work_done--;
 	}
 
-	if (unlikely(!napi_complete_done(napi, 0)))
+	if (unlikely(!napi_complete_done(napi, work_done)))
 		goto err_out;
 
 	xsc_cq_notify_hw_rearm(&c->qp.sq.cq);
+	xsc_cq_notify_hw_rearm(&rq->cq);
 err_out:
 	rcu_read_unlock();
-	return 0;
+	return work_done;
 }

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

* [PATCH 09/15] yunsilicon: add RDMA device support
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (7 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 08/15] yunsilicon: add ethernet RX path and statistics Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13  6:17   ` Junxian Huang
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 10/15] yunsilicon: add RDMA PD/MR Xin Tian
                   ` (6 subsequent siblings)
  15 siblings, 2 replies; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the RDMA driver's uverbs ABI header (include/uapi/rdma/xsc-abi.h),
the ib_device scaffolding and device registration: query_device,
query_port, query_pkey, ucontext allocation and mmap, port attributes and
the netdev notifier that tracks the port state.

The driver reports RDMA_DRIVER_XSC, which is added to the rdma_driver_id
enum at the end so existing IDs are untouched.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 drivers/infiniband/Kconfig                    |   1 +
 drivers/infiniband/hw/Makefile                |   1 +
 drivers/infiniband/hw/xsc/Kconfig             |  17 +
 drivers/infiniband/hw/xsc/Makefile            |   9 +
 drivers/infiniband/hw/xsc/main.c              | 797 ++++++++++++++++++
 drivers/infiniband/hw/xsc/xsc_ib.h            | 542 ++++++++++++
 .../net/ethernet/yunsilicon/xsc/common/cq.h   |  30 +
 .../ethernet/yunsilicon/xsc/common/driver.h   |  95 +++
 .../net/ethernet/yunsilicon/xsc/common/qp.h   | 104 +++
 .../net/ethernet/yunsilicon/xsc/common/srq.h  |  54 ++
 .../net/ethernet/yunsilicon/xsc/pci/Makefile  |   2 +-
 .../net/ethernet/yunsilicon/xsc/pci/link.c    |  35 +
 include/uapi/rdma/ib_user_ioctl_verbs.h       |   1 +
 include/uapi/rdma/xsc-abi.h                   | 295 +++++++
 14 files changed, 1982 insertions(+), 1 deletion(-)
 create mode 100644 drivers/infiniband/hw/xsc/Kconfig
 create mode 100644 drivers/infiniband/hw/xsc/Makefile
 create mode 100644 drivers/infiniband/hw/xsc/main.c
 create mode 100644 drivers/infiniband/hw/xsc/xsc_ib.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/cq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/driver.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/qp.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/srq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/link.c
 create mode 100644 include/uapi/rdma/xsc-abi.h

diff --git a/drivers/infiniband/Kconfig b/drivers/infiniband/Kconfig
index 086195758..b3cc506db 100644
--- a/drivers/infiniband/Kconfig
+++ b/drivers/infiniband/Kconfig
@@ -100,6 +100,7 @@ source "drivers/infiniband/hw/ocrdma/Kconfig"
 source "drivers/infiniband/hw/qedr/Kconfig"
 source "drivers/infiniband/hw/usnic/Kconfig"
 source "drivers/infiniband/hw/vmw_pvrdma/Kconfig"
+source "drivers/infiniband/hw/xsc/Kconfig"
 source "drivers/infiniband/sw/rdmavt/Kconfig"
 endif # !UML
 source "drivers/infiniband/sw/rxe/Kconfig"
diff --git a/drivers/infiniband/hw/Makefile b/drivers/infiniband/hw/Makefile
index c42b22ac3..1bcd2d50a 100644
--- a/drivers/infiniband/hw/Makefile
+++ b/drivers/infiniband/hw/Makefile
@@ -16,3 +16,4 @@ obj-$(CONFIG_INFINIBAND_BNXT_RE)	+= bnxt_re/
 obj-$(CONFIG_INFINIBAND_BNG_RE)		+= bng_re/
 obj-$(CONFIG_INFINIBAND_ERDMA)		+= erdma/
 obj-$(CONFIG_INFINIBAND_IONIC)		+= ionic/
+obj-$(CONFIG_INFINIBAND_XSC)	+= xsc/
diff --git a/drivers/infiniband/hw/xsc/Kconfig b/drivers/infiniband/hw/xsc/Kconfig
new file mode 100644
index 000000000..5cf46df2d
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+# All rights reserved.
+
+config INFINIBAND_XSC
+	tristate "Yunsilicon XSC RDMA driver"
+	depends on INFINIBAND && PCI && INET
+	depends on YUNSILICON_XSC_PCI && YUNSILICON_XSC_ETH
+	help
+	  This driver provides RDMA support for Yunsilicon XSC devices.
+	  It uses the auxiliary device published by the XSC PCI core and,
+	  for now, the netdev registered by the XSC ethernet driver to
+	  derive the port state and GID.  Because of that netdev
+	  dependency it also depends on YUNSILICON_XSC_ETH.
+
+	  To compile this driver as a module, choose M here. The module
+	  will be called xsc_ib.
diff --git a/drivers/infiniband/hw/xsc/Makefile b/drivers/infiniband/hw/xsc/Makefile
new file mode 100644
index 000000000..6c4ec1ca0
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+# All rights reserved.
+
+ccflags-y := -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
+
+obj-$(CONFIG_INFINIBAND_XSC) += xsc_ib.o
+
+xsc_ib-y := main.o
diff --git a/drivers/infiniband/hw/xsc/main.c b/drivers/infiniband/hw/xsc/main.c
new file mode 100644
index 000000000..9d53a89be
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/main.c
@@ -0,0 +1,797 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/namei.h>
+#include <linux/kobject.h>
+#include <linux/kernfs.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/io-mapping.h>
+#include <linux/sched.h>
+#include <linux/netdevice.h>
+#include "common/xsc_core.h"
+#include "common/xsc_hsi.h"
+#include "common/xsc_cmd.h"
+#include "common/driver.h"
+
+#include <rdma/ib_user_verbs.h>
+#include <rdma/ib_smi.h>
+#include <rdma/ib_umem.h>
+
+#include <rdma/xsc-abi.h>
+#include "xsc_ib.h"
+
+#define DRIVER_NAME "xsc_ib"
+
+#define XSC_IB_GID_TBL_LEN	256
+#define XSC_IB_MAX_PKEYS	0x80
+
+MODULE_DESCRIPTION("Yunsilicon HCA IB driver");
+MODULE_LICENSE("GPL");
+
+static int xsc_ib_query_device(struct ib_device *ibdev,
+			       struct ib_device_attr *props,
+			       struct ib_udata *udata)
+{
+	size_t udata_outlen = (udata) ? udata->outlen : 0;
+	struct xsc_ib_dev *dev = to_xdev(ibdev);
+	struct xsc_ib_query_device_resp resp;
+	union xsc_ib_fw_ver fw_ver;
+	int err = -ENOMEM;
+	size_t resp_len;
+	int max_rq_sg;
+	u64 max_tso;
+	u64 flags;
+
+	err = ib_is_udata_in_empty(udata);
+	if (err)
+		return err;
+
+	memset(&resp, 0, sizeof(resp));
+	memset(props, 0, sizeof(*props));
+
+	resp_len = sizeof(resp.comp_mask) + sizeof(resp.response_length);
+	/*check param*/
+	if (udata_outlen && udata_outlen < resp_len)
+		return -EINVAL;
+
+	resp.response_length = resp_len;
+
+	fw_ver.data = 0;
+	fw_ver.s.ver_major = dev->xdev->fw_version_major;
+	fw_ver.s.ver_minor = dev->xdev->fw_version_minor;
+	fw_ver.s.ver_patch = dev->xdev->fw_version_patch;
+	fw_ver.s.ver_tweak = dev->xdev->fw_version_tweak;
+	props->fw_ver = fw_ver.data;
+
+	props->device_cap_flags    = IB_DEVICE_CHANGE_PHY_PORT |
+		IB_DEVICE_PORT_ACTIVE_EVENT		|
+		IB_DEVICE_SYS_IMAGE_GUID		|
+		IB_DEVICE_RC_RNR_NAK_GEN;
+	props->kernel_cap_flags =  IBK_BLOCK_MULTICAST_LOOPBACK;
+	props->kernel_cap_flags |= IBK_LOCAL_DMA_LKEY;
+
+	flags = dev->xdev->caps.flags;
+	if (flags & XSC_DEV_CAP_FLAG_BAD_PKEY_CNTR)
+		props->device_cap_flags |= IB_DEVICE_BAD_PKEY_CNTR;
+	if (flags & XSC_DEV_CAP_FLAG_BAD_QKEY_CNTR)
+		props->device_cap_flags |= IB_DEVICE_BAD_QKEY_CNTR;
+	if (flags & XSC_DEV_CAP_FLAG_APM)
+		props->device_cap_flags |= IB_DEVICE_AUTO_PATH_MIG;
+	props->page_size_cap	   = dev->xdev->caps.min_page_sz;
+	props->max_mr_size	   = xsc_get_max_mr_size(dev->xdev);
+	props->max_qp		   = dev->xdev->caps.max_qp;
+	props->max_qp_wr	   = xsc_get_max_qp_depth(dev->xdev);
+	max_rq_sg = dev->xdev->caps.max_rq_desc_sz / sizeof(struct xsc_wqe_data_seg);
+
+	props->max_send_sge = dev->xdev->caps.send_ds_num - XSC_CTRL_SEG_NUM -
+		XSC_RADDR_SEG_NUM;
+	props->max_recv_sge = dev->xdev->caps.recv_ds_num;
+	props->max_sge_rd	   = 1;/*max sge per read wqe*/
+	props->max_cq		   = dev->xdev->caps.max_cq;
+	props->max_cqe		   = dev->xdev->caps.max_cqes;
+	props->max_mr		   = (1 << dev->xdev->caps.log_max_mkey) - 1;
+	props->max_pd		   = dev->xdev->caps.max_pd;
+	props->max_qp_rd_atom	   = dev->xdev->caps.max_ra_res_qp;
+	props->max_qp_init_rd_atom = dev->xdev->caps.max_ra_req_qp;
+	props->max_res_rd_atom	   = props->max_qp_rd_atom * props->max_qp;
+	props->max_srq		   =
+		dev->xdev->caps.log_max_srq ? (1 << dev->xdev->caps.log_max_srq) : 0;
+	props->max_srq_wr	   = dev->xdev->caps.max_srq_wqes - 1;
+	props->max_srq_sge	   = dev->xdev->caps.log_max_srq ? (max_rq_sg - 1) : 0;
+	props->local_ca_ack_delay  = dev->xdev->caps.local_ca_ack_delay;
+	props->atomic_cap	   = is_support_rdma_atomic(dev->xdev) ?
+		IB_ATOMIC_HCA : IB_ATOMIC_NONE;
+	props->masked_atomic_cap   = IB_ATOMIC_HCA;
+	props->max_mcast_grp	   =
+		dev->xdev->caps.log_max_mcg ? (1 << dev->xdev->caps.log_max_mcg) : 0;
+	props->max_mcast_qp_attach = dev->xdev->caps.max_qp_mcg;
+	props->max_total_mcast_qp_attach = props->max_mcast_qp_attach *
+					   props->max_mcast_grp;
+
+	props->sys_image_guid = dev->xdev->board_info->guid;
+	props->vendor_id = dev->xdev->pdev->vendor;
+	props->vendor_part_id = dev->xdev->pdev->device;
+	props->hw_ver = ((dev->xdev->chip_ver_l & 0xffff) << 16) |
+		(dev->xdev->hotfix_num & 0xffff);
+	props->max_pkeys = XSC_IB_MAX_PKEYS;
+	props->max_wq_type_rq = dev->xdev->caps.max_qp;
+	props->hca_core_clock = dev->xdev->caps.hca_core_clock * 1000; /* KHz */
+	props->rss_caps.max_rwq_indirection_tables =
+		dev->xdev->caps.max_rwq_indirection_tables;
+	props->rss_caps.max_rwq_indirection_table_size =
+		dev->xdev->caps.max_rwq_indirection_table_size;
+	props->rss_caps.supported_qpts = 1 << IB_QPT_RAW_PACKET;
+
+	/*response tso_caps extend param*/
+	if (offsetofend(typeof(resp), tso_caps) <= udata_outlen) {
+		max_tso = dev->xdev->caps.log_max_tso ? (1 << dev->xdev->caps.log_max_tso) : 0;
+		if (max_tso) {
+			resp.tso_caps.max_tso = max_tso;
+			resp.tso_caps.supported_qpts |= 1 << IB_QPT_RAW_PACKET;
+			resp.response_length += sizeof(resp.tso_caps);
+		}
+	}
+
+	/*response rss_caps extend param*/
+	if (offsetofend(typeof(resp), rss_caps) <= udata_outlen) {
+		resp.rss_caps.rx_hash_function = XSC_RX_HASH_FUNC_TOEPLITZ;
+		resp.rss_caps.rx_hash_fields_mask =
+			XSC_RX_HASH_SRC_IPV4 |
+			XSC_RX_HASH_DST_IPV4 |
+			XSC_RX_HASH_SRC_IPV6 |
+			XSC_RX_HASH_DST_IPV6 |
+			XSC_RX_HASH_SRC_PORT_TCP |
+			XSC_RX_HASH_DST_PORT_TCP |
+			XSC_RX_HASH_SRC_PORT_UDP |
+			XSC_RX_HASH_DST_PORT_UDP |
+			XSC_RX_HASH_INNER;
+		resp.response_length += sizeof(resp.rss_caps);
+	}
+
+	/*response packet pacing caps*/
+	if (offsetofend(typeof(resp), packet_pacing_caps) <= udata_outlen) {
+		resp.packet_pacing_caps.qp_rate_limit_max =
+			dev->xdev->caps.qp_rate_limit_max;
+		resp.packet_pacing_caps.qp_rate_limit_min =
+			dev->xdev->caps.qp_rate_limit_min;
+		resp.packet_pacing_caps.supported_qpts |= 1 << IB_QPT_RAW_PACKET;
+
+		resp.response_length += sizeof(resp.packet_pacing_caps);
+	}
+
+	/*copy response data to user*/
+	if (udata_outlen) {
+		err = ib_copy_to_udata(udata, &resp, resp.response_length);
+		if (err) {
+			ibdev_err(&dev->ib_dev, "copy response info to udata fail,err=%d\n", err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static void xsc_calc_link_info(struct xsc_core_device *xdev,
+			       struct ib_port_attr *props)
+{
+	switch (xsc_get_link_speed(xdev)) {
+	case MODULE_SPEED_10G:
+		props->active_speed = XSC_RDMA_LINK_SPEED_10GB;
+		props->active_width = 1;
+		break;
+	case MODULE_SPEED_25G:
+		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
+		props->active_width = 1;
+		break;
+	case MODULE_SPEED_40G_R4:
+		props->active_speed = XSC_RDMA_LINK_SPEED_10GB;
+		props->active_width = 2;
+		break;
+	case MODULE_SPEED_50G_R:
+		props->active_speed = XSC_RDMA_LINK_SPEED_50GB;
+		props->active_width = 1;
+		break;
+	case MODULE_SPEED_50G_R2:
+		props->active_speed = XSC_RDMA_LINK_SPEED_50GB;
+		props->active_width = 1;
+		break;
+	case MODULE_SPEED_100G_R2:
+		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
+		props->active_width = 2;
+		break;
+	case MODULE_SPEED_100G_R4:
+		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
+		props->active_width = 2;
+		break;
+	case MODULE_SPEED_200G_R4:
+		props->active_speed = XSC_RDMA_LINK_SPEED_50GB;
+		props->active_width = 2;
+		break;
+	case MODULE_SPEED_200G_R8:
+		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
+		props->active_width = 4;
+		break;
+	case MODULE_SPEED_400G_R8:
+		props->active_speed = XSC_RDMA_LINK_SPEED_50GB;
+		props->active_width = 4;
+		break;
+	case MODULE_SPEED_400G_R4:
+		props->active_speed = XSC_RDMA_LINK_SPEED_100GB;
+		props->active_width = 2;
+		break;
+	default:
+		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
+		props->active_width = 1;
+		break;
+	}
+}
+
+static enum rdma_link_layer xsc_ib_port_link_layer(struct ib_device *ibdev, u32 port)
+{
+	return IB_LINK_LAYER_ETHERNET;
+}
+
+int xsc_ib_query_port(struct ib_device *ibdev, u32 port,
+		      struct ib_port_attr *props)
+{
+	struct xsc_ib_dev *dev = to_xdev(ibdev);
+	struct net_device *ndev = dev->netdev;
+	struct xsc_core_device *xdev = dev->xdev;
+
+	if (port < 1 || port > xdev->caps.num_ports) {
+		ibdev_warn(&dev->ib_dev, "invalid port number %d\n", port);
+		return -EINVAL;
+	}
+
+	memset(props, 0, sizeof(*props));
+
+	if (netif_running(ndev) && netif_carrier_ok(ndev))
+		props->state = IB_PORT_ACTIVE;
+	else
+		props->state = IB_PORT_DOWN;
+
+	props->max_mtu = is_support_8k_mtu(xdev) ? IB_MTU_8192 : IB_MTU_4096;
+	props->active_mtu = min(props->max_mtu, xsc_net_to_ib_mtu(ndev->mtu));
+	props->gid_tbl_len = XSC_IB_GID_TBL_LEN;
+	props->port_cap_flags = IB_PORT_CM_SUP | IB_PORT_OTHER_LOCAL_CHANGES_SUP;
+	props->max_msg_sz = SZ_1G;
+	props->bad_pkey_cntr = 0;
+	props->qkey_viol_cntr = 0;
+	props->pkey_tbl_len = 1;
+	props->lid = 0;
+	props->sm_lid = 0;
+	props->lmc = 0;
+	props->max_vl_num = 0;
+	props->sm_sl = 0;
+	props->subnet_timeout = 0;
+	props->init_type_reply = 0;
+	if (!is_support_rdma(xdev)) {
+		props->active_width = 1;
+		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
+	} else {
+		xsc_calc_link_info(xdev, props);
+	}
+
+	props->phys_state = netif_carrier_ok(ndev) ? XSC_RDMA_PHY_STATE_LINK_UP :
+				XSC_RDMA_PHY_STATE_DISABLED;
+	return 0;
+}
+
+static int xsc_ib_query_pkey(struct ib_device *ibdev, u32 port, u16 index,
+			     u16 *pkey)
+{
+	*pkey = 0xffff;
+	return 0;
+}
+
+int xsc_ib_alloc_ucontext(struct ib_ucontext *uctx,
+			  struct ib_udata *udata)
+{
+	struct ib_device *ibdev = uctx->device;
+	struct xsc_ib_dev *dev = to_xdev(ibdev);
+	struct xsc_ib_alloc_ucontext_req req;
+	struct xsc_ib_alloc_ucontext_resp resp;
+	int err;
+
+	if (!dev->ib_active)
+		return -EAGAIN;
+
+	err = ib_copy_from_udata(&req, udata, sizeof(req));
+	if (err)
+		return err;
+
+	memset(&resp, 0, sizeof(resp));
+	resp.qp_tab_size      = dev->xdev->caps.max_qp;
+	resp.cache_line_size  = L1_CACHE_BYTES;
+	resp.max_sq_desc_sz = dev->xdev->caps.max_sq_desc_sz;
+	resp.max_rq_desc_sz = dev->xdev->caps.max_rq_desc_sz;
+	resp.max_send_wqebb = dev->xdev->caps.max_wqes;
+	resp.max_recv_wr = dev->xdev->caps.max_wqes;
+	xsc_get_db_addr(dev->xdev, &resp.qpm_tx_db, &resp.qpm_rx_db,
+			&resp.cqm_armdb, &resp.cqm_next_cid_reg, NULL);
+	resp.send_ds_num = dev->xdev->caps.send_ds_num;
+	resp.recv_ds_num = dev->xdev->caps.recv_ds_num;
+	resp.cmds_supp_uhw |= XSC_USER_CMDS_SUPP_UHW_QUERY_DEVICE;
+	resp.device_id = dev->xdev->pdev->device;
+
+	if (req.abi_ver >= ALLOC_UCONTEXT_VER_V1) {
+		xsc_get_multidb_info(dev->xdev, &resp.multidb_num, &resp.tx_multidb_base);
+		resp.rdma_proto_mode = dev->xdev->rdma_proto_mode;
+	}
+
+	if (is_support_cqe64(dev->xdev))
+		resp.hw_feature_flag |= XSC_HW_FEATURE_FLAG_SUPPORT_CQE64;
+
+	resp.num_ports = dev->xdev->caps.num_ports;
+
+	return ib_copy_to_udata(udata, &resp, min_t(int, udata->outlen, sizeof(resp)));
+}
+
+void xsc_ib_dealloc_ucontext(struct ib_ucontext *ibcontext)
+{
+}
+
+static int xsc_ib_mmap(struct ib_ucontext *ibcontext, struct vm_area_struct *vma)
+{
+	struct xsc_ib_dev *dev = to_xdev(ibcontext->device);
+	struct xsc_core_device *xdev = dev->xdev;
+	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+	resource_size_t reg_base;
+	resource_size_t reg_size = vma->vm_end - vma->vm_start;
+	u64 tx_db = 0;
+	u64 rx_db = 0;
+	u64 cq_db = 0;
+	u64 cq_reg = 0;
+
+	xsc_get_db_addr(xdev, &tx_db, &rx_db, &cq_db, &cq_reg, NULL);
+	dev_dbg(&xdev->pdev->dev, "offset:0x%lx\n", offset);
+
+	if (offset == (tx_db & PAGE_MASK))
+		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) + (tx_db & PAGE_MASK);
+	else if (offset == (rx_db & PAGE_MASK))
+		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) + (rx_db & PAGE_MASK);
+	else if (offset == (cq_reg & PAGE_MASK))
+		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) + (cq_reg & PAGE_MASK);
+	else if (offset == (cq_db & PAGE_MASK))
+		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) + (cq_db & PAGE_MASK);
+	else if (offset == (QPM_TX_MDB_BASE_REG_ADDR & PAGE_MASK))
+		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) +
+			(QPM_TX_MDB_BASE_REG_ADDR & PAGE_MASK);
+	else if (offset == (xdev->caps.mdb_base & PAGE_MASK))
+		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) +
+			(xdev->caps.mdb_base & PAGE_MASK);
+	else
+		return -EINVAL;
+
+	dev_dbg(&xdev->pdev->dev, "regbase:0x%llx", reg_base);
+
+	reg_base = (xsc_core_is_pf(xdev) && !is_pf_bar_compressed(xdev)) ?
+		reg_base - 0xA0000000 : reg_base;
+
+	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+	return remap_pfn_range(vma, vma->vm_start, reg_base >> PAGE_SHIFT,
+			       reg_size, vma->vm_page_prot);
+}
+
+static int xsc_port_immutable(struct ib_device *ibdev, u32 port_num,
+			      struct ib_port_immutable *immutable)
+{
+	struct ib_port_attr attr;
+	int err;
+
+	err = ib_query_port(ibdev, port_num, &attr);
+	if (err)
+		return err;
+
+	immutable->pkey_tbl_len = attr.pkey_tbl_len;
+	immutable->gid_tbl_len = attr.gid_tbl_len;
+	immutable->core_cap_flags = RDMA_CORE_PORT_IBA_ROCE |
+				    RDMA_CORE_PORT_IBA_ROCE_UDP_ENCAP;
+	immutable->max_mad_size = IB_MGMT_MAD_SIZE * 2;
+
+	return 0;
+}
+
+static struct net_device *xsc_get_netdev(struct ib_device *ibdev, u32 port_num)
+{
+	struct xsc_ib_dev *xsc_ib_dev = to_xdev(ibdev);
+	struct net_device *dev = xsc_ib_dev->netdev;
+
+	if (dev)
+		dev_hold(dev);
+
+	return dev;
+}
+
+static void xsc_get_guid(const u8 *dev_addr, u8 *guid)
+{
+	u8 mac[ETH_ALEN];
+
+	/* MAC-48 to EUI-64 mapping */
+	memcpy(mac, dev_addr, ETH_ALEN);
+	guid[0] = mac[0] ^ 2;
+	guid[1] = mac[1];
+	guid[2] = mac[2];
+	guid[3] = 0xff;
+	guid[4] = 0xfe;
+	guid[5] = mac[3];
+	guid[6] = mac[4];
+	guid[7] = mac[5];
+}
+
+static void init_node_data(struct xsc_ib_dev *dev)
+{
+	strscpy(dev->ib_dev.node_desc, "xsc_node_desc",
+		sizeof(dev->ib_dev.node_desc));
+	xsc_get_guid(dev->netdev->dev_addr, (u8 *)&dev->ib_dev.node_guid);
+}
+
+static void xsc_ib_event(struct xsc_core_device *dev, void *context,
+			 enum xsc_dev_event event, unsigned long data)
+{
+	struct xsc_ib_dev *ibdev = (struct xsc_ib_dev *)context;
+	struct ib_event ibev;
+	u8 port = 0;
+
+	switch (event) {
+	case XSC_DEV_EVENT_SYS_ERROR:
+		ibdev->ib_active = false;
+		ibev.event = IB_EVENT_DEVICE_FATAL;
+		break;
+
+	case XSC_DEV_EVENT_PORT_UP:
+		ibev.event = IB_EVENT_PORT_ACTIVE;
+		port = *(u8 *)data;
+		break;
+
+	case XSC_DEV_EVENT_PORT_DOWN:
+		ibev.event = IB_EVENT_PORT_ERR;
+		port = *(u8 *)data;
+		break;
+
+	case XSC_DEV_EVENT_PORT_INITIALIZED:
+		/* not used by ULPs */
+		return;
+
+	case XSC_DEV_EVENT_LID_CHANGE:
+		ibev.event = IB_EVENT_LID_CHANGE;
+		port = *(u8 *)data;
+		break;
+
+	case XSC_DEV_EVENT_PKEY_CHANGE:
+		ibev.event = IB_EVENT_PKEY_CHANGE;
+		port = *(u8 *)data;
+		break;
+
+	case XSC_DEV_EVENT_GUID_CHANGE:
+		ibev.event = IB_EVENT_GID_CHANGE;
+		port = *(u8 *)data;
+		break;
+
+	case XSC_DEV_EVENT_CLIENT_REREG:
+		ibev.event = IB_EVENT_CLIENT_REREGISTER;
+		port = *(u8 *)data;
+		break;
+	}
+
+	ibev.device	      = &ibdev->ib_dev;
+	ibev.element.port_num = port;
+
+	if (ibdev->ib_active)
+		ib_dispatch_event(&ibev);
+}
+
+static void xsc_ib_get_dev_fw_str(struct ib_device *ibdev, char *str)
+{
+	struct xsc_core_device *dev = to_xdev(ibdev)->xdev;
+	u8 ver_major = dev->fw_version_major;
+	u8 ver_minor = dev->fw_version_minor;
+	u16 ver_patch = dev->fw_version_patch;
+	u32 ver_tweak = dev->fw_version_tweak;
+
+	if (ver_tweak == 0) {
+		snprintf(str, IB_FW_VERSION_NAME_MAX, "v%u.%u.%u",
+			 ver_major,  ver_minor, ver_patch);
+	} else {
+		snprintf(str, IB_FW_VERSION_NAME_MAX, "v%u.%u.%u+%u",
+			 ver_major, ver_minor, ver_patch, ver_tweak);
+	}
+}
+
+static void xsc_ib_disassociate_ucontext(struct ib_ucontext *ibcontext)
+{
+}
+
+static void xsc_ib_dev_setting(struct xsc_ib_dev *dev)
+{
+	dev->ib_dev.ops.owner		= THIS_MODULE;
+	dev->ib_dev.ops.uverbs_abi_ver	= XSC_IB_UVERBS_ABI_VERSION;
+	dev->ib_dev.ops.driver_id = RDMA_DRIVER_XSC;
+	dev->ib_dev.ops.disassociate_ucontext = xsc_ib_disassociate_ucontext;
+	dev->ib_dev.ops.query_device	= xsc_ib_query_device;
+	dev->ib_dev.ops.query_port	= xsc_ib_query_port;
+	dev->ib_dev.ops.query_pkey	= xsc_ib_query_pkey;
+
+	dev->ib_dev.ops.alloc_ucontext	= xsc_ib_alloc_ucontext;
+	dev->ib_dev.ops.dealloc_ucontext = xsc_ib_dealloc_ucontext;
+	dev->ib_dev.ops.mmap		= xsc_ib_mmap;
+
+
+	dev->ib_dev.ops.get_link_layer	= xsc_ib_port_link_layer;
+	dev->ib_dev.ops.get_netdev		= xsc_get_netdev;
+
+
+	dev->ib_dev.ops.get_port_immutable		= xsc_port_immutable;
+
+	dev->ib_dev.ops.get_dev_fw_str	= xsc_ib_get_dev_fw_str;
+
+	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_ucontext, xsc_ib_ucontext, ibucontext);
+}
+
+static void xsc_get_port_state(struct net_device *ndev, enum xsc_dev_event *ev)
+{
+	*ev = XSC_DEV_EVENT_PORT_DOWN;
+	if (netif_running(ndev) && netif_carrier_ok(ndev))
+		*ev = XSC_DEV_EVENT_PORT_UP;
+}
+
+static int xsc_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+	struct xsc_ib_dev *ibdev = container_of(this, struct xsc_ib_dev, nb);
+	struct net_device *ndev = netdev_notifier_info_to_dev(ptr);
+	enum xsc_dev_event ev;
+	u8 port = 1;
+
+	if (ndev != ibdev->netdev)
+		goto done;
+
+	ibdev_dbg(&ibdev->ib_dev, "netdev notfiy event:%ld\n", event);
+	switch (event) {
+	case NETDEV_CHANGE:
+	case NETDEV_UP:
+	case NETDEV_DOWN:
+		xsc_get_port_state(ibdev->netdev, &ev);
+		xsc_ib_event(ibdev->xdev, ibdev, ev, (unsigned long)&port);
+		break;
+	default:
+		break;
+	}
+done:
+	return NOTIFY_DONE;
+}
+
+static int xsc_register_netdev_notifier(struct xsc_ib_dev *ibdev)
+{
+	ibdev->nb.notifier_call = xsc_netdev_event;
+	return register_netdevice_notifier(&ibdev->nb);
+}
+
+static int xsc_unregister_netdev_notifier(struct xsc_ib_dev *ibdev)
+{
+	return unregister_netdevice_notifier(&ibdev->nb);
+}
+
+static int init_one(struct xsc_core_device *xdev,
+		    struct xsc_ib_dev **ibdev)
+{
+	struct xsc_ib_dev *dev;
+	int err;
+
+	dev = (struct xsc_ib_dev *)ib_alloc_device(xsc_ib_dev, ib_dev);
+	if (!dev)
+		return -ENOMEM;
+
+	dev->xdev = xdev;
+	dev->netdev = xdev->netdev;
+	strscpy(dev->ib_dev.name, "xscale_%d", IB_DEVICE_NAME_MAX);
+
+	memcpy(dev->xdev->priv.ibdev_name, dev->ib_dev.name, IB_DEVICE_NAME_MAX);
+
+	if (!xdev->caps.msix_enable)
+		dev->num_comp_vectors = 1;
+	else
+		dev->num_comp_vectors = xdev->dev_res->eq_table.num_comp_vectors;
+
+	dev->ib_dev.node_type		= RDMA_NODE_IB_CA;
+	dev->ib_dev.local_dma_lkey	= 0xFF;
+	dev->num_ports		= xdev->caps.num_ports;
+	dev->ib_dev.phys_port_cnt     = dev->num_ports;
+	dev->ib_dev.num_comp_vectors	= dev->num_comp_vectors;
+	dev->ib_dev.dev.parent = &xdev->pdev->dev;
+	xsc_ib_dev_setting(dev);
+	dev->xdev->priv.cm_dscp = DSCP_PCP_UNSET;
+	dev->xdev->priv.cm_pcp = DSCP_PCP_UNSET;
+	dev->xdev->priv.force_pcp = DSCP_PCP_UNSET;
+	dev->xdev->priv.force_dscp = DSCP_PCP_UNSET;
+	dev->xdev->rdma_proto_mode = RDMA_PROTO_ROCEV2;
+	dev->xdev->cm_udp_dst_port = ROCE_V2_UDP_DPORT;
+	dev->xdev->veroce_udp_dst_port = VEROCE_UDP_DPORT;
+
+	dev->ib_dev.uverbs_cmd_mask	=
+		(1ull << IB_USER_VERBS_CMD_GET_CONTEXT)		|
+		(1ull << IB_USER_VERBS_CMD_QUERY_DEVICE)	|
+		(1ull << IB_USER_VERBS_CMD_QUERY_PORT)		|
+		(1ull << IB_USER_VERBS_CMD_ALLOC_PD)		|
+		(1ull << IB_USER_VERBS_CMD_DEALLOC_PD)		|
+		(1ull << IB_USER_VERBS_CMD_CREATE_AH)		|
+		(1ull << IB_USER_VERBS_CMD_DESTROY_AH)		|
+		(1ull << IB_USER_VERBS_CMD_REG_MR)		|
+		(1ull << IB_USER_VERBS_CMD_REREG_MR)		|
+		(1ull << IB_USER_VERBS_CMD_DEREG_MR)		|
+		(1ull << IB_USER_VERBS_CMD_CREATE_COMP_CHANNEL)	|
+		(1ull << IB_USER_VERBS_CMD_CREATE_CQ)		|
+		(1ull << IB_USER_VERBS_CMD_RESIZE_CQ)		|
+		(1ull << IB_USER_VERBS_CMD_DESTROY_CQ)		|
+		(1ull << IB_USER_VERBS_CMD_CREATE_QP)		|
+		(1ull << IB_USER_VERBS_CMD_MODIFY_QP)		|
+		(1ull << IB_USER_VERBS_CMD_QUERY_QP)		|
+		(1ull << IB_USER_VERBS_CMD_DESTROY_QP)		|
+		(1ull << IB_USER_VERBS_CMD_ATTACH_MCAST)	|
+		(1ull << IB_USER_VERBS_CMD_DETACH_MCAST)	|
+		(1ull << IB_USER_VERBS_CMD_CREATE_SRQ)		|
+		(1ull << IB_USER_VERBS_CMD_MODIFY_SRQ)		|
+		(1ull << IB_USER_VERBS_CMD_QUERY_SRQ)		|
+		(1ull << IB_USER_VERBS_CMD_DESTROY_SRQ)		|
+		(1ull << IB_USER_VERBS_CMD_OPEN_QP);
+
+	init_node_data(dev);
+
+
+
+
+	err = ib_register_device(&dev->ib_dev, dev->ib_dev.name,
+				 dev->xdev->device);
+	if (err)
+		goto err_free;
+
+	rdma_roce_rescan_device(&dev->ib_dev);
+	dev->ib_active = true;
+	*ibdev = dev;
+
+	xsc_register_netdev_notifier(dev);
+
+
+
+	return 0;
+
+err_free:
+	ib_dealloc_device((struct ib_device *)dev);
+
+	return err;
+}
+
+static void remove_one(struct xsc_core_device *xdev, void *intf_ctx)
+{
+	struct xsc_ib_dev *dev = (struct xsc_ib_dev *)intf_ctx;
+
+	xsc_unregister_netdev_notifier(dev);
+	ib_unregister_device(&dev->ib_dev);
+	ib_dealloc_device(&dev->ib_dev);
+}
+
+static void init_iommu_state(struct xsc_ib_dev *dev)
+{
+	struct iommu_domain *domain;
+
+	dev->iommu_state = XSC_IB_IOMMU_MAP_DISABLE;
+	domain = iommu_get_domain_for_dev(dev->ib_dev.dma_device);
+	if (domain) {
+		if (domain->type & __IOMMU_DOMAIN_DMA_API)
+			dev->iommu_state = XSC_IB_IOMMU_MAP_NORMAL;
+	} else {
+		/* Try to allocate DMA memory; if the DMA address differs from
+		 * the physical address, an IOMMU maps it through an unknown
+		 * domain.
+		 */
+		dma_addr_t dma_addr;
+		void *tmp;
+
+		tmp = dma_alloc_coherent(dev->ib_dev.dma_device, PAGE_SIZE,
+					 &dma_addr, GFP_KERNEL);
+		if (tmp) {
+			if (virt_to_phys(tmp) != dma_addr)
+				dev->iommu_state = XSC_IB_IOMMU_MAP_UNKNOWN_DOMAIN;
+			dma_free_coherent(dev->ib_dev.dma_device, PAGE_SIZE,
+					  tmp, dma_addr);
+		}
+	}
+
+	ibdev_dbg(&dev->ib_dev, "iommu dma map state=%d\n", dev->iommu_state);
+}
+
+static int xsc_ib_probe(struct auxiliary_device *adev,
+			const struct auxiliary_device_id *adev_id)
+{
+	struct xsc_adev *xsc_adev = container_of(adev, struct xsc_adev, adev);
+	struct xsc_core_device *xdev = xsc_adev->xdev;
+	struct xsc_ib_dev *ibdev = NULL;
+	int ret;
+
+	ret = init_one(xdev, &ibdev);
+	if (ret) {
+		dev_err(&adev->dev, "failed to add RDMA device, ret = %d\n",
+			ret);
+		return ret;
+	}
+
+	init_iommu_state(ibdev);
+	auxiliary_set_drvdata(adev, ibdev);
+
+	return 0;
+}
+
+static void xsc_ib_remove(struct auxiliary_device *adev)
+{
+	struct xsc_ib_dev *ibdev = auxiliary_get_drvdata(adev);
+
+	if (!ibdev)
+		return;
+
+	remove_one(ibdev->xdev, ibdev);
+}
+
+static const struct auxiliary_device_id xsc_ib_id_table[] = {
+	{ .name = XSC_PCI_DRV_NAME "." XSC_IB_ADEV_NAME },
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, xsc_ib_id_table);
+
+static struct auxiliary_driver xsc_ib_driver = {
+	.name = XSC_IB_ADEV_NAME,
+	.probe = xsc_ib_probe,
+	.remove = xsc_ib_remove,
+	.id_table = xsc_ib_id_table,
+};
+
+static int xsc_ib_reboot_event_handler(struct notifier_block *nb, unsigned long action, void *data)
+{
+	pr_info("xsc ib driver recv %lu event\n", action);
+
+	xsc_remove_rdma_driver();
+
+	return NOTIFY_OK;
+}
+
+struct notifier_block xsc_ib_nb = {
+	.notifier_call = xsc_ib_reboot_event_handler,
+	.next = NULL,
+	.priority = 2,
+};
+
+void xsc_remove_rdma_driver(void)
+{
+	auxiliary_driver_unregister(&xsc_ib_driver);
+}
+
+static int __init xsc_ib_init(void)
+{
+	int ret;
+
+	ret = auxiliary_driver_register(&xsc_ib_driver);
+	if (ret) {
+		pr_err("failed to register auxiliary driver\n");
+		return ret;
+	}
+
+	register_reboot_notifier(&xsc_ib_nb);
+
+	return 0;
+}
+
+static void __exit xsc_ib_cleanup(void)
+{
+	unregister_reboot_notifier(&xsc_ib_nb);
+	xsc_remove_rdma_driver();
+}
+
+module_init(xsc_ib_init);
+module_exit(xsc_ib_cleanup);
diff --git a/drivers/infiniband/hw/xsc/xsc_ib.h b/drivers/infiniband/hw/xsc/xsc_ib.h
new file mode 100644
index 000000000..a2ae382e2
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/xsc_ib.h
@@ -0,0 +1,542 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef XSC_IB_H
+#define XSC_IB_H
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <rdma/ib_verbs.h>
+#include <rdma/ib_smi.h>
+#include <rdma/ib_pack.h>
+#include <rdma/ib_mad.h>
+#include "common/xsc_core.h"
+#include "common/driver.h"
+#include "common/cq.h"
+#include "common/srq.h"
+#include "common/qp.h"
+#include <linux/types.h>
+#include <linux/iommu.h>
+#include <linux/dma-direct.h>
+#include <linux/dma-mapping.h>
+#include <crypto/hash.h>
+#include <rdma/uverbs_ioctl.h>
+
+#define	XSC_PAGE_SHIFT_4K	12
+#define	XSC_PAGE_SHIFT_64K	16
+#define	XSC_PAGE_SHIFT_2M	21
+#define	XSC_PAGE_SHIFT_1G	30
+#define XSC_PAGE_SZ_4K		BIT(XSC_PAGE_SHIFT_4K)
+#define XSC_PAGE_SZ_64K		BIT(XSC_PAGE_SHIFT_64K)
+#define XSC_PAGE_SZ_2M		BIT(XSC_PAGE_SHIFT_2M)
+#define XSC_PAGE_SZ_1G		BIT(XSC_PAGE_SHIFT_1G)
+#define XSC_MR_PAGE_CAP_MASK	(XSC_PAGE_SZ_4K |	\
+				XSC_PAGE_SZ_64K |	\
+				XSC_PAGE_SZ_2M |	\
+				XSC_PAGE_SZ_1G)
+
+
+struct xsc_ib_ucontext {
+	struct ib_ucontext	ibucontext;
+};
+
+struct xsc_ib_pd {
+	struct ib_pd		ibpd;
+	u32			pdn;
+	u32			pa_lkey;
+};
+
+struct wr_list {
+	u16	opcode;
+	u16	next;
+};
+
+struct xsc_ib_wq {
+	u64		       *wrid;
+	u32		       *wr_data;
+	struct wr_list	       *w_list;
+	unsigned long	       *wqe_head;
+
+	/* serialize post to the work queue
+	 */
+	spinlock_t		lock;
+	int			wqe_cnt;
+	int			ds_cnt;
+	int			max_post;
+	int			max_gs;
+	int			offset;
+	int			wqe_shift;
+	unsigned int		head;
+	unsigned int		tail;
+	u32			cur_post;
+	u32			last_poll;
+	void		       *qend;
+	void		*hdr_buf;
+	u32			hdr_size;
+	dma_addr_t	hdr_dma;
+	int			mad_queue_depth;
+	int			mad_index;
+	u32			*wr_opcode;
+	u32			*need_flush;
+	atomic_t	flush_wqe_cnt;
+	int			*prev_ce_wqe;
+	int			last_comp_ce_wqe;
+	int			last_post_ce_wqe;
+};
+
+enum {
+	XSC_QP_USER,
+	XSC_QP_KERNEL,
+};
+
+struct xsc_ib_qp {
+	struct ib_qp		ibqp;
+	struct xsc_core_qp	xqp;
+	struct xsc_buf		buf;
+
+	struct xsc_ib_wq	rq;
+
+	u32			doorbell_qpn;
+	u8			sq_signal_bits;
+	struct xsc_ib_wq	sq;
+
+	struct ib_umem	       *umem;
+	int			buf_size;
+
+	/* serialize qp state modifications
+	 */
+	struct mutex		mutex;
+	u32			flags;
+	u8			port;
+	u8			alt_port;
+	u8			atomic_rd_en;
+	u8			resp_depth;
+	u8			state;
+	int			max_inline_data;
+	int			has_rq;
+
+	int			create_type;
+	u32			pa_lkey;
+	/* For QP1 */
+	struct ib_ud_header	qp1_hdr;
+	u32			send_psn;
+	struct xsc_qp_context	ctx;
+	struct ib_cq		*send_cq;
+	struct ib_cq		*recv_cq;
+	/* For qp resources */
+	spinlock_t		lock;
+
+	struct list_head	cq_recv_list;
+	struct list_head	cq_send_list;
+	bool		post_send_invalid;
+	bool		post_recv_invalid;
+	struct ib_recv_wr *srq_head_wr;
+	struct ib_recv_wr *srq_tail_wr;
+	bool has_trig_cq_comp_handle;
+};
+
+struct xsc_ib_cq_buf {
+	struct xsc_buf		buf;
+	struct ib_umem		*umem;
+	int			cqe_size;
+};
+
+enum xsc_ib_qp_flags {
+	XSC_IB_QP_BLOCK_MULTICAST_LOOPBACK     = 1 << 0,
+	XSC_IB_QP_SIGNATURE_HANDLING           = 1 << 1,
+};
+
+struct xsc_ib_cq {
+	struct ib_cq		ibcq;
+	struct xsc_core_cq	xcq;
+	struct xsc_ib_cq_buf	buf;
+
+	/* serialize access to the CQ
+	 */
+	spinlock_t		lock;
+
+	/* protect resize cq
+	 */
+	struct mutex		resize_mutex;
+	struct xsc_ib_cq_resize *resize_buf;
+	struct ib_umem	       *resize_umem;
+	int			cqe_size;
+	struct list_head	send_qp_list;
+	struct list_head	recv_qp_list;
+	struct list_head	err_state_qp_list;
+	u32			create_flags;
+	int			invalid;
+};
+
+enum xsc_srq_wr_per_qp {
+	XSC_SRQ_MIN_WR_NUM = 32,
+	XSC_SRQ_MAX_WR_NUM = 128,
+};
+
+enum {
+	XSC_SRQ_USER,
+	XSC_SRQ_KERNEL,
+};
+
+struct xsc_ib_srq {
+	struct ib_srq		ibsrq;
+	struct xsc_core_srq	xsrq;
+	u64		       *wrid;
+	/* protect SRQ hanlding
+	 */
+	spinlock_t		lock;
+	int			head;
+	int			tail;
+	struct ib_umem	       *umem;
+	/* serialize arming a SRQ
+	 */
+	struct mutex		mutex;
+	int			qp_num;
+	int			create_type;
+};
+
+void xsc_restore_cache_srq_wrs(struct xsc_ib_srq *srq, struct xsc_ib_qp *qp);
+void xsc_free_one_cache_srq_wr(struct xsc_ib_srq *srq, struct xsc_ib_qp *qp, u64 wr_id);
+void xsc_free_all_cache_wrs(struct ib_recv_wr *wr);
+void xsc_post_cache_wrs(struct xsc_ib_srq *srq, struct xsc_ib_qp *qp, int wr_num, bool init);
+
+struct xsc_ib_mr {
+	struct ib_mr		ibmr;
+	struct xsc_core_mr	xmr;
+	struct ib_umem	       *umem;
+	int			npages;
+};
+
+#define MAX_OPCODE	IB_WR_MASKED_ATOMIC_FETCH_AND_ADD
+
+struct xsc_ib_dev {
+	struct ib_device		ib_dev;
+	struct net_device	*netdev;
+	struct xsc_core_device *xdev;
+	int				num_ports;
+	int				num_comp_vectors;
+	u8				ib_active;
+	u32				crc_32_table[256];
+	int iommu_state;
+	struct notifier_block nb;
+	u32				opcode_map[MAX_OPCODE + 1];
+};
+
+union xsc_ib_fw_ver {
+	u64 data;
+	struct {
+		u8	ver_major;
+		u8	ver_minor;
+		u16	ver_patch;
+		u32	ver_tweak;
+	} s;
+};
+
+struct xsc_err_state_qp_node {
+	struct list_head	entry;
+	u32					qp_id;
+	bool				is_sq;
+};
+
+struct xsc_ib_ah {
+	struct ib_ah		ibah;
+	struct xsc_av		av;
+};
+
+/*
+ * Conversion helpers.
+ *
+ * The first group goes from a uverbs object to the private structure that
+ * embeds it; the second goes from the core object to the private structure
+ * that embeds that one.  Both directions exist for cq, qp and srq, so the
+ * names spell out which object is being converted.
+ */
+static inline struct xsc_ib_dev *to_xdev(struct ib_device *ibdev)
+{
+	return container_of(ibdev, struct xsc_ib_dev, ib_dev);
+}
+
+static inline struct xsc_ib_pd *to_xpd(struct ib_pd *ibpd)
+{
+	return container_of(ibpd, struct xsc_ib_pd, ibpd);
+}
+
+static inline struct xsc_ib_mr *to_xmr(struct ib_mr *ibmr)
+{
+	return container_of(ibmr, struct xsc_ib_mr, ibmr);
+}
+
+static inline struct xsc_ib_ah *to_xah(struct ib_ah *ibah)
+{
+	return container_of(ibah, struct xsc_ib_ah, ibah);
+}
+
+static inline struct xsc_ib_cq *to_xcq(struct ib_cq *ibcq)
+{
+	return container_of(ibcq, struct xsc_ib_cq, ibcq);
+}
+
+static inline struct xsc_ib_qp *to_xqp(struct ib_qp *ibqp)
+{
+	return container_of(ibqp, struct xsc_ib_qp, ibqp);
+}
+
+static inline struct xsc_ib_srq *to_xsrq(struct ib_srq *ibsrq)
+{
+	return container_of(ibsrq, struct xsc_ib_srq, ibsrq);
+}
+
+static inline struct xsc_ib_cq *core_to_xcq(struct xsc_core_cq *xcq)
+{
+	return container_of(xcq, struct xsc_ib_cq, xcq);
+}
+
+static inline struct xsc_ib_qp *core_to_xqp(struct xsc_core_qp *xqp)
+{
+	return container_of(xqp, struct xsc_ib_qp, xqp);
+}
+
+static inline struct xsc_ib_srq *core_to_xsrq(struct xsc_core_srq *xsrq)
+{
+	return container_of(xsrq, struct xsc_ib_srq, xsrq);
+}
+
+void build_opcode_map(struct xsc_ib_dev *dev);
+
+int xsc_ib_create_ah(struct ib_ah *ibah,
+		     struct rdma_ah_init_attr *init_attr,
+		     struct ib_udata *udata);
+int xsc_ib_destroy_ah(struct ib_ah *ibah, u32 flags);
+int xsc_ib_create_cq(struct ib_cq *ibcq,
+		     const struct ib_cq_init_attr *attr,
+		     struct uverbs_attr_bundle *attrs);
+int xsc_ib_destroy_cq(struct ib_cq *cq, struct ib_udata *udata);
+int xsc_ib_destroy_qp(struct ib_qp *qp, struct ib_udata *udata);
+int xsc_ib_dereg_mr(struct ib_mr *ibmr, struct ib_udata *udata);
+int xsc_ib_alloc_ucontext(struct ib_ucontext *uctx, struct ib_udata *udata);
+void xsc_ib_dealloc_ucontext(struct ib_ucontext *ibcontext);
+int xsc_ib_alloc_pd(struct ib_pd *ibpd, struct ib_udata *udata);
+int xsc_ib_dealloc_pd(struct ib_pd *pd, struct ib_udata *udata);
+
+int xsc_ib_create_srq(struct ib_srq *ib_srq,
+		      struct ib_srq_init_attr *init_attr,
+		      struct ib_udata *udata);
+int xsc_ib_modify_srq(struct ib_srq *ibsrq, struct ib_srq_attr *attr,
+		      enum ib_srq_attr_mask attr_mask, struct ib_udata *udata);
+int xsc_ib_query_srq(struct ib_srq *ibsrq, struct ib_srq_attr *srq_attr);
+int xsc_ib_post_srq_recv(struct ib_srq *ibsrq, const struct ib_recv_wr *wr,
+			 const struct ib_recv_wr **bad_wr);
+int xsc_ib_destroy_srq(struct ib_srq *srq, struct ib_udata *udata);
+
+int xsc_ib_query_port(struct ib_device *ibdev, u32 port,
+		      struct ib_port_attr *props);
+
+int xsc_ib_create_qp(struct ib_qp *ibqp,
+		     struct ib_qp_init_attr *init_attr,
+		     struct ib_udata *udata);
+
+void __xsc_ib_cq_clean(struct xsc_ib_cq *cq, u32 qpn);
+void xsc_ib_cq_clean(struct xsc_ib_cq *cq, u32 qpn);
+
+int xsc_ib_query_ah(struct ib_ah *ibah, struct rdma_ah_attr *ah_attr);
+int xsc_ib_err_state_qp(struct xsc_ib_dev *dev, struct xsc_ib_qp *qp,
+			enum xsc_qp_state cur_state, enum xsc_qp_state state);
+int xsc_ib_modify_qp(struct ib_qp *ibqp, struct ib_qp_attr *attr,
+		     int attr_mask, struct ib_udata *udata);
+int xsc_ib_query_qp(struct ib_qp *ibqp, struct ib_qp_attr *qp_attr, int qp_attr_mask,
+		    struct ib_qp_init_attr *qp_init_attr);
+
+int xsc_ib_post_send(struct ib_qp *ibqp, const struct ib_send_wr *wr,
+		     const struct ib_send_wr **bad_wr);
+int xsc_ib_post_recv(struct ib_qp *ibqp, const struct ib_recv_wr *wr,
+		     const struct ib_recv_wr **bad_wr);
+
+void *xsc_get_send_wqe(struct xsc_ib_qp *qp, int n);
+int xsc_ib_poll_cq(struct ib_cq *ibcq, int num_entries, struct ib_wc *wc);
+int xsc_ib_arm_cq(struct ib_cq *ibcq, enum ib_cq_notify_flags flags);
+struct ib_mr *xsc_ib_get_dma_mr(struct ib_pd *pd, int acc);
+struct ib_mr *xsc_ib_reg_user_mr(struct ib_pd *pd, u64 start, u64 length,
+				 u64 virt_addr, int access_flags,
+				 struct ib_dmah *dmah,
+				 struct ib_udata *udata);
+int xsc_ib_get_buf_offset(u64 addr, int page_shift, u32 *offset);
+void xsc_ib_cont_pages(struct ib_umem *umem, u64 addr, int *count, int *shift,
+		       int *ncont);
+void xsc_ib_populate_pas(struct xsc_ib_dev *dev, struct ib_umem *umem,
+			 int page_shift, __be64 *pas, int npages, bool need_to_devide);
+struct ib_mr *xsc_ib_reg_user_mr_dmabuf(struct ib_pd *pd, u64 offset,
+					u64 length, u64 virt_addr,
+					int fd, int access_flags,
+					struct ib_dmah *dmah,
+					struct uverbs_attr_bundle *attrs);
+
+void xsc_ib_drain_rq(struct ib_qp *qp);
+void xsc_ib_drain_sq(struct ib_qp *qp);
+
+static inline u8 convert_access(int acc)
+{
+	return (acc & IB_ACCESS_REMOTE_ATOMIC ? XSC_PERM_ATOMIC       : 0) |
+	       (acc & IB_ACCESS_REMOTE_WRITE  ? XSC_PERM_REMOTE_WRITE : 0) |
+	       (acc & IB_ACCESS_REMOTE_READ   ? XSC_PERM_REMOTE_READ  : 0) |
+	       (acc & IB_ACCESS_LOCAL_WRITE   ? XSC_PERM_LOCAL_WRITE  : 0) |
+	       XSC_PERM_LOCAL_READ;
+}
+
+#define IB_MTU_8192	6
+
+static inline enum ib_mtu xsc_net_to_ib_mtu(unsigned int mtu)
+{
+	mtu = mtu - (IB_GRH_BYTES + IB_UDP_BYTES + IB_BTH_BYTES +
+		     IB_EXT_XRC_BYTES + IB_EXT_ATOMICETH_BYTES +
+		     IB_ICRC_BYTES);
+	if (mtu >= 8192)
+		return IB_MTU_8192;
+	else if (mtu >= ib_mtu_enum_to_int(IB_MTU_4096))
+		return IB_MTU_4096;
+	else if (mtu >= ib_mtu_enum_to_int(IB_MTU_1024))
+		return IB_MTU_1024;
+	else
+		return 0;
+}
+
+#define XSC_IB_IOMMU_MAP_DISABLE 0
+#define XSC_IB_IOMMU_MAP_UNKNOWN_DOMAIN 1
+#define XSC_IB_IOMMU_MAP_NORMAL 2
+
+static inline int xsc_ib_iommu_dma_map(struct ib_device *ibdev)
+{
+	return to_xdev(ibdev)->iommu_state;
+}
+
+static inline void *xsc_ib_iova_to_virt(struct ib_device *ibdev, dma_addr_t iova)
+{
+	phys_addr_t phyaddr;
+	struct iommu_domain *domain;
+
+	domain = iommu_get_domain_for_dev(ibdev->dma_device);
+	if (likely(domain)) {
+		phyaddr = iommu_iova_to_phys(domain, iova);
+		phyaddr |= iova & (PAGE_SIZE - 1);
+	} else {
+		phyaddr = dma_to_phys(ibdev->dma_device, iova);
+	}
+
+	return phys_to_virt(phyaddr);
+}
+
+struct ib_mad_list_head {
+	struct list_head list;
+	struct ib_cqe cqe;
+	struct ib_mad_queue *mad_queue;
+};
+
+#define IB_MAD_SEND_REQ_MAX_SG 2
+struct ib_mad_send_wr_private {
+	struct ib_mad_list_head mad_list;
+	struct list_head agent_list;
+	struct ib_mad_agent_private *mad_agent_priv;
+	struct ib_mad_send_buf send_buf;
+	u64 header_mapping;
+	u64 payload_mapping;
+	struct ib_ud_wr send_wr;
+	struct ib_sge sg_list[IB_MAD_SEND_REQ_MAX_SG];
+	__be64 tid;
+	unsigned long timeout;
+	int max_retries;
+	int retries_left;
+	int retry;
+	int refcount;
+	enum ib_wc_status status;
+
+	/* RMPP control */
+	struct list_head rmpp_list;
+	struct ib_rmpp_segment *last_ack_seg;
+	struct ib_rmpp_segment *cur_seg;
+	int last_ack;
+	int seg_num;
+	int newwin;
+	int pad;
+};
+
+struct ib_mad_private_header {
+	struct ib_mad_list_head mad_list;
+	struct ib_mad_recv_wc recv_wc;
+	struct ib_wc wc;
+	u64 mapping;
+} __packed;
+
+struct ib_mad_private {
+	struct ib_mad_private_header header;
+	size_t mad_size;
+	struct ib_grh grh;
+	DECLARE_FLEX_ARRAY(u8, mad);
+} __packed;
+
+static inline void *xsc_ib_send_mad_sg_virt_addr(struct ib_device *ibdev,
+						 const struct ib_send_wr *wr,
+						 int sg)
+{
+	struct ib_mad_send_wr_private *mad_send_wr;
+	struct ib_mad_list_head *mad_list;
+	int iommu_state = xsc_ib_iommu_dma_map(ibdev);
+
+	/* direct dma mapping */
+	if (!iommu_state)
+		return phys_to_virt(dma_to_phys(ibdev->dma_device, wr->sg_list[sg].addr));
+
+	if (iommu_state == XSC_IB_IOMMU_MAP_NORMAL)
+		return xsc_ib_iova_to_virt(ibdev, wr->sg_list[sg].addr);
+
+	mad_list = container_of(wr->wr_cqe, struct ib_mad_list_head, cqe);
+	mad_send_wr = container_of(mad_list, struct ib_mad_send_wr_private,
+				   mad_list);
+
+	/* sg_list[0] */
+	if (sg == 0)
+		return mad_send_wr->send_buf.mad;
+
+	/* sg_list[1] */
+	if (mad_send_wr->send_buf.seg_count)
+		return ib_get_rmpp_segment(&mad_send_wr->send_buf,
+					   mad_send_wr->seg_num);
+	return mad_send_wr->send_buf.mad + mad_send_wr->send_buf.hdr_len;
+}
+
+static inline void *xsc_ib_recv_mad_sg_virt_addr(struct ib_device *ibdev,
+						 struct ib_wc *wc,
+						 u64 sg_addr)
+{
+	struct ib_mad_private_header *mad_priv_hdr;
+	struct ib_mad_private *recv;
+	struct ib_mad_list_head *mad_list;
+	int iommu_state = xsc_ib_iommu_dma_map(ibdev);
+
+	/* direct dma mapping */
+	if (!iommu_state)
+		return phys_to_virt(dma_to_phys(ibdev->dma_device, sg_addr));
+
+	if (iommu_state == XSC_IB_IOMMU_MAP_NORMAL)
+		return xsc_ib_iova_to_virt(ibdev, sg_addr);
+
+	mad_list = container_of(wc->wr_cqe, struct ib_mad_list_head, cqe);
+	mad_priv_hdr = container_of(mad_list, struct ib_mad_private_header, mad_list);
+	recv = container_of(mad_priv_hdr, struct ib_mad_private, header);
+	return &recv->grh;
+}
+
+struct xsc_ib_burst_info {
+	__u32       max_burst_sz;
+	__u16       typical_pkt_sz;
+	__u16       reserved;
+};
+
+struct xsc_ib_modify_qp {
+	__u32		reserved;
+	struct xsc_ib_burst_info  burst_info;
+	__u32		profile;
+};
+
+#endif /* XSC_IB_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/common/cq.h b/drivers/net/ethernet/yunsilicon/xsc/common/cq.h
new file mode 100644
index 000000000..5217000c5
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/common/cq.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef XSC_CORE_CQ_H
+#define XSC_CORE_CQ_H
+
+#include "common/xsc_core.h"
+
+enum {
+	XSC_CQE_OWNER_MASK	= 1,
+};
+
+enum {
+	CQE_SIZE_64 = 0,
+	CQE_SIZE_128 = 1,
+};
+
+enum {
+	XSC_CQ_DB_REQ_NOT_SOL		= 1,
+	XSC_CQ_DB_REQ_NOT		= 0,
+};
+
+void xsc_init_cq_table(struct xsc_core_device *xdev);
+int xsc_core_create_cq(struct xsc_core_device *xdev, struct xsc_core_cq *cq,
+		       struct xsc_create_cq_ex_mbox_in *in, int inlen);
+int xsc_core_destroy_cq(struct xsc_core_device *xdev, struct xsc_core_cq *cq);
+
+#endif /* XSC_CORE_CQ_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/common/driver.h b/drivers/net/ethernet/yunsilicon/xsc/common/driver.h
new file mode 100644
index 000000000..b3e91c868
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/common/driver.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef XSC_DRIVER_H
+#define XSC_DRIVER_H
+
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/pci.h>
+#include <linux/spinlock_types.h>
+#include <linux/semaphore.h>
+#include <linux/vmalloc.h>
+#include <linux/radix-tree.h>
+#include <rdma/ib_verbs.h>
+
+#include "common/xsc_device.h"
+#include "common/xsc_core.h"
+#include "common/xsc_cmd_api.h"
+#include "common/xsc_cmd.h"
+#include "common/xsc_hsi.h"
+#include "common/srq.h"
+
+struct rxe_bth {
+	u8			opcode;
+	u8			flags;
+	__be16			pkey;
+	__be32			qpn;
+	__be32			apsn;
+};
+
+struct rxe_deth {
+	__be32			qkey;
+	__be32			src_qp;
+};
+
+static inline void *xsc_vzalloc(unsigned long size)
+{
+	void *rtn;
+
+	rtn = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
+	if (!rtn)
+		rtn = vzalloc(size);
+	return rtn;
+}
+
+static inline void xsc_vfree(const void *addr)
+{
+	if (addr && is_vmalloc_addr(addr))
+		vfree(addr);
+	else
+		kfree(addr);
+}
+
+int xsc_buf_alloc(struct xsc_core_device *xdev, unsigned long size,
+		  unsigned long max_direct, struct xsc_buf *buf);
+void xsc_buf_free(struct xsc_core_device *xdev, struct xsc_buf *buf);
+void xsc_fill_page_array(struct xsc_buf *buf, __be64 *pas, unsigned int npages);
+
+int xsc_core_create_mkey(struct xsc_core_device *xdev, struct xsc_core_mr *mr);
+int xsc_core_destroy_mkey(struct xsc_core_device *xdev, struct xsc_core_mr *mr);
+int xsc_core_register_mr(struct xsc_core_device *xdev, struct xsc_core_mr *mr,
+			 struct xsc_register_mr_mbox_in *in, int inlen);
+int xsc_core_dereg_mr(struct xsc_core_device *xdev, struct xsc_core_mr *mr);
+void xsc_reg_local_dma_mr(struct xsc_core_device *xdev);
+
+int xsc_core_alloc_pd(struct xsc_core_device *xdev, u32 *pdn);
+int xsc_core_dealloc_pd(struct xsc_core_device *xdev, u32 pdn);
+
+int xsc_core_create_srq(struct xsc_core_device *xdev, struct xsc_core_srq *srq);
+int xsc_core_destroy_srq(struct xsc_core_device *xdev, struct xsc_core_srq *srq);
+
+int xsc_core_create_cq(struct xsc_core_device *xdev, struct xsc_core_cq *cq,
+		       struct xsc_create_cq_ex_mbox_in *in, int inlen);
+int xsc_core_destroy_cq(struct xsc_core_device *xdev, struct xsc_core_cq *cq);
+
+int xsc_core_create_qp(struct xsc_core_device *xdev,
+		       struct xsc_core_qp *qp,
+		       struct xsc_create_qp_mbox_in *in,
+		       int inlen);
+int xsc_core_destroy_qp(struct xsc_core_device *xdev,
+			struct xsc_core_qp *qp);
+int xsc_core_qp_modify(struct xsc_core_device *xdev, enum xsc_qp_state cur_state,
+		       enum xsc_qp_state new_state,
+		       struct xsc_modify_qp_mbox_in *in, int sqd_event,
+		       struct xsc_core_qp *qp);
+int xsc_core_qp_query(struct xsc_core_device *xdev, struct xsc_core_qp *qp,
+		      struct xsc_query_qp_mbox_out *out, int outlen);
+void xsc_qp_event(struct xsc_core_device *xdev, u32 qpn, int event_type);
+
+int xsc_vector2eqn(struct xsc_core_device *xdev, int vector, int *eqn,
+		   unsigned int *irqn);
+
+#endif /* XSC_DRIVER_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/common/qp.h b/drivers/net/ethernet/yunsilicon/xsc/common/qp.h
new file mode 100644
index 000000000..4855c97e4
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/common/qp.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef XSC_QP_H
+#define XSC_QP_H
+
+#include <linux/radix-tree.h>
+
+#include "common/xsc_core.h"
+#include "common/xsc_cmd.h"
+#include "common/xsc_hsi.h"
+
+enum {
+	XSC_QP_PM_MIGRATED		= 0x3,
+	XSC_QP_PM_ARMED			= 0x0,
+	XSC_QP_PM_REARM			= 0x1
+};
+
+enum {
+	XSC_WQE_CTRL_CQ_UPDATE		= 2 << 2,
+	XSC_WQE_CTRL_SOLICITED		= 1 << 1,
+};
+
+struct xsc_av {
+	union {
+		struct {
+			__be32	qkey;
+			__be32	reserved;
+		} qkey;
+		__be64	dc_key;
+	} key;
+	__be32	dqp_dct;
+	u8	stat_rate_sl;
+	u8	fl_mlid;
+	union {
+		__be16	rlid;
+		__be16  udp_sport;
+	};
+	u8	reserved0[4];
+	u8	rmac[6];
+	u8	tclass;
+	u8	hop_limit;
+	__be32	grh_gid_fl;
+	u8	rgid[16];
+};
+
+struct xsc_qp_path {
+	u8			fl;
+	u8			rsvd3;
+	u8			free_ar;
+	u8			pkey_index;
+	u8			rsvd0;
+	u8			grh_mlid;
+	__be16			rlid;
+	u8			ackto_lt;
+	u8			mgid_index;
+	u8			static_rate;
+	u8			hop_limit;
+	__be32			tclass_flowlabel;
+	u8			rgid[16];
+	u8			rsvd1[4];
+	u8			sl;
+	u8			port;
+	u8			rsvd2[6];
+	u8			dmac[6];
+	u8			smac[6];
+	__be16		af_type;
+	__be32		sip[4];
+	__be32		dip[4];
+	__be16			sport;
+	u8			ecn_dscp;
+	u8			vlan_valid;
+	__be16			vlan_id;
+	u8			dci_cfi_prio_sl; /* not left moved yet. */
+};
+
+static inline struct xsc_core_qp *__xsc_qp_lookup(struct xsc_core_device *xdev, u32 qpn)
+{
+	return radix_tree_lookup(&xdev->dev_res->qp_table.tree, qpn);
+}
+
+int xsc_core_create_resource_common(struct xsc_core_device *xdev,
+				    struct xsc_core_qp *qp);
+void xsc_core_destroy_resource_common(struct xsc_core_device *xdev,
+				      struct xsc_core_qp *qp);
+void xsc_init_qp_table(struct xsc_core_device *xdev);
+void xsc_qp_event(struct xsc_core_device *xdev, u32 qpn, int event_type);
+
+int xsc_core_create_qp(struct xsc_core_device *xdev,
+		       struct xsc_core_qp *qp,
+		       struct xsc_create_qp_mbox_in *in,
+		       int inlen);
+int xsc_core_destroy_qp(struct xsc_core_device *xdev,
+			struct xsc_core_qp *qp);
+int xsc_core_qp_modify(struct xsc_core_device *xdev, enum xsc_qp_state cur_state,
+		       enum xsc_qp_state new_state,
+		       struct xsc_modify_qp_mbox_in *in, int sqd_event,
+		       struct xsc_core_qp *qp);
+int xsc_core_qp_query(struct xsc_core_device *xdev, struct xsc_core_qp *qp,
+		      struct xsc_query_qp_mbox_out *out, int outlen);
+
+#endif /* XSC_QP_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/common/srq.h b/drivers/net/ethernet/yunsilicon/xsc/common/srq.h
new file mode 100644
index 000000000..0a2e660e1
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/common/srq.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef XSC_SRQ_H
+#define XSC_SRQ_H
+
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "common/xsc_core.h"
+
+struct ib_qp;
+struct ib_recv_wr;
+
+#define XSC_MAX_SRQ_NUM 8192
+#define XSC_MAX_SRQ_LOG_SIZE 13
+
+struct xsc_srq_qp_node {
+	struct list_head	entry;
+	u32			qp_id;
+	struct ib_qp		*ibqp;
+};
+
+struct xsc_srq_cache_wr {
+	int wr_num;
+	int max_wr_num;
+	struct ib_recv_wr *head_wr;
+	struct ib_recv_wr *tail_wr;
+};
+
+struct xsc_core_srq {
+	u32 srqn;
+	int max;
+	size_t max_gs;
+	size_t max_avail_gather;
+	int wqe_shift;
+	void (*event)(struct xsc_core_srq *srq, enum xsc_event e);
+
+	/* protect ctrl list */
+	spinlock_t		lock;
+	struct list_head	ctrl_list;
+	u16 uid;
+
+	struct list_head	srq_qp_list;
+	struct xsc_srq_cache_wr *cache_wr;
+};
+
+int xsc_core_create_srq(struct xsc_core_device *xdev, struct xsc_core_srq *srq);
+int xsc_core_destroy_srq(struct xsc_core_device *xdev, struct xsc_core_srq *srq);
+
+#endif /* XSC_SRQ_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
index b01fca3ac..87481db3f 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
@@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
 
-xsc_pci-y := main.o cmdq.o hw.o qp.o cq.o alloc.o eq.o pci_irq.o adev.o vport.o hal/andes_impl.o
+xsc_pci-y := main.o cmdq.o hw.o qp.o cq.o link.o alloc.o eq.o pci_irq.o adev.o vport.o hal/andes_impl.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/link.c b/drivers/net/ethernet/yunsilicon/xsc/pci/link.c
new file mode 100644
index 000000000..b991eddae
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/link.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "common/xsc_core.h"
+#include "common/xsc_cmd_api.h"
+
+int xsc_get_link_speed(struct xsc_core_device *xdev)
+{
+	struct xsc_event_query_linkinfo_mbox_out out;
+	struct xsc_event_linkinfo linkinfo;
+	struct xsc_inbox_hdr in;
+	int err;
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+	in.opcode = cpu_to_be16(XSC_CMD_OP_QUERY_LINK_INFO);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err || out.hdr.status) {
+		pci_err(xdev->pdev, "failed to get link info, err=%d, status=%d\n",
+			err, out.hdr.status);
+		return MODULE_SPEED_25G;
+	}
+
+	linkinfo = out.ctx;
+	linkinfo.linkspeed = be32_to_cpu(linkinfo.linkspeed);
+
+	return linkinfo.linkspeed;
+}
+EXPORT_SYMBOL_GPL(xsc_get_link_speed);
diff --git a/include/uapi/rdma/ib_user_ioctl_verbs.h b/include/uapi/rdma/ib_user_ioctl_verbs.h
index 21f86cc7b..53fc99057 100644
--- a/include/uapi/rdma/ib_user_ioctl_verbs.h
+++ b/include/uapi/rdma/ib_user_ioctl_verbs.h
@@ -257,6 +257,7 @@ enum rdma_driver_id {
 	RDMA_DRIVER_ERDMA,
 	RDMA_DRIVER_MANA,
 	RDMA_DRIVER_IONIC,
+	RDMA_DRIVER_XSC,
 };
 
 enum ib_uverbs_gid_type {
diff --git a/include/uapi/rdma/xsc-abi.h b/include/uapi/rdma/xsc-abi.h
new file mode 100644
index 000000000..c4d0db30e
--- /dev/null
+++ b/include/uapi/rdma/xsc-abi.h
@@ -0,0 +1,295 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * Copyright (c) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_ABI_H__
+#define __XSC_ABI_H__
+
+#include <linux/types.h>
+#include <linux/if_ether.h>	/* For ETH_ALEN. */
+
+/* Increment this value if any changes that break userspace ABI
+ * compatibility are made.
+ */
+#define XSC_IB_UVERBS_ABI_VERSION	1
+
+/* Make sure that all structs defined in this file remain laid out so
+ * that they pack the same way on 32-bit and 64-bit architectures (to
+ * avoid incompatibility between 32-bit userspace and 64-bit kernels).
+ * In particular do not use pointer types -- pass pointers in __u64
+ * instead.
+ */
+
+enum {
+	XSC_QP_FLAG_SIGNATURE		= 1 << 0,
+	XSC_QP_FLAG_SCATTER_CQE	= 1 << 1,
+	XSC_QP_FLAG_TUNNEL_OFFLOADS	= 1 << 2,
+	XSC_QP_FLAG_BFREG_INDEX	= 1 << 3,
+	XSC_QP_FLAG_TYPE_DCT		= 1 << 4,
+	XSC_QP_FLAG_TYPE_DCI		= 1 << 5,
+	XSC_QP_FLAG_TIR_ALLOW_SELF_LB_UC = 1 << 6,
+	XSC_QP_FLAG_TIR_ALLOW_SELF_LB_MC = 1 << 7,
+	XSC_QP_FLAG_ALLOW_SCATTER_CQE	= 1 << 8,
+	XSC_QP_FLAG_RAWPACKET_TSO       = 1 << 9,
+	XSC_QP_FLAG_RAWPACKET_TX	= 1 << 10,
+	XSC_QP_FLAG_RAWPACKET_SNIFFER	= 1 << 11,
+	XSC_QP_FLAG_WORC		= 1 << 12,
+};
+
+#define ALLOC_UCONTEXT_VER_V1 1
+#define ALLOC_UCONTEXT_VER_V2 2
+
+#define CURRENT_UCONTEXT_ABI_VER	ALLOC_UCONTEXT_VER_V2
+
+struct xsc_ib_alloc_ucontext_req {
+	__u32	abi_ver;
+	__u32	rsvd1;
+};
+
+enum xsc_user_cmds_supp_uhw {
+	XSC_USER_CMDS_SUPP_UHW_QUERY_DEVICE = 1 << 0,
+	XSC_USER_CMDS_SUPP_UHW_CREATE_AH    = 1 << 1,
+};
+
+enum {
+	XSC_HW_FEATURE_FLAG_SUPPORT_CQE64 = 1 << 0,
+};
+
+struct xsc_ib_alloc_ucontext_resp {
+	__u32	qp_tab_size;
+	__u32	cache_line_size;
+	__u16	max_sq_desc_sz;
+	__u16	max_rq_desc_sz;
+	__u32	max_send_wqebb;
+	__u32	max_recv_wr;
+	__u16	num_ports;
+	__u16	device_id;
+	__u64	qpm_tx_db;
+	__u64	qpm_rx_db;
+	__u64	cqm_next_cid_reg;
+	__u64	cqm_armdb;
+	__u32	send_ds_num;
+	__u32	recv_ds_num;
+	__u32   cmds_supp_uhw;
+	__u32	pad;
+	__u32	multidb_num;
+	__u32	tx_multidb_base;
+	__u32	rdma_proto_mode;
+	__u32	hw_feature_flag;
+	__u32	reserved[32];
+};
+
+struct xsc_ib_create_qp {
+	__u64	buf_addr;
+	__u64	db_addr;
+	__u32	sq_wqe_count;
+	__u32	rq_wqe_count;
+	__u32	rq_wqe_shift;
+	__u32	flags;
+
+	int	dmabuf_fd;
+	size_t	dmabuf_sz;
+};
+
+struct xsc_ib_create_qp_resp {
+	__u32	uuar_index;
+	__u32 reserved;
+};
+
+struct xsc_ib_create_srq {
+	__u64	buf_addr;
+	__u64	db_addr;
+	__u32	flags;
+	__u32	reserved0;
+	__u32	uidx;
+	__u32	reserved1;
+};
+
+struct xsc_ib_create_srq_resp {
+	__u32	srqn;
+	__u32	reserved;
+};
+
+struct xsc_ib_create_cq {
+	__u64 buf_addr;
+	__u64 db_addr;
+	__u32	cqe_size;
+
+	int	dmabuf_fd;
+	size_t	dmabuf_sz;
+};
+
+struct xsc_ib_create_cq_resp {
+	__u32	cqn;
+	__u32	reserved;
+};
+
+struct xsc_ib_create_ah_resp {
+	__u32	response_length;
+	__u8	dmac[ETH_ALEN];
+	__u8	reserved[6];
+};
+
+struct xsc_ib_alloc_pd_resp {
+	__u32	pdn;
+};
+
+struct xsc_ib_tso_caps {
+	__u32 max_tso; /* Maximum tso payload size in bytes */
+
+	/* Corresponding bit will be set if qp type from
+	 * 'enum ib_qp_type' is supported, e.g.
+	 * supported_qpts |= 1 << IB_QPT_UD
+	 */
+	__u32 supported_qpts;
+};
+
+/* RX Hash function flags */
+enum xsc_rx_hash_function_flags {
+	XSC_RX_HASH_FUNC_TOEPLITZ	= 1 << 0,
+};
+
+enum xsc_rdma_link_speed {
+	XSC_RDMA_LINK_SPEED_2_5GB	= 1 << 0,
+	XSC_RDMA_LINK_SPEED_5GB		= 1 << 1,
+	XSC_RDMA_LINK_SPEED_10GB	= 1 << 3,
+	XSC_RDMA_LINK_SPEED_14GB	= 1 << 4,
+	XSC_RDMA_LINK_SPEED_25GB	= 1 << 5,
+	XSC_RDMA_LINK_SPEED_50GB	= 1 << 6,
+	XSC_RDMA_LINK_SPEED_100GB	= 1 << 7,
+};
+
+enum xsc_rdma_phys_state {
+	XSC_RDMA_PHY_STATE_SLEEP	= 1,
+	XSC_RDMA_PHY_STATE_POLLING,
+	XSC_RDMA_PHY_STATE_DISABLED,
+	XSC_RDMA_PHY_STATE_PORT_CONFIGURATION_TRAINNING,
+	XSC_RDMA_PHY_STATE_LINK_UP,
+	XSC_RDMA_PHY_STATE_LINK_ERROR_RECOVERY,
+	XSC_RDMA_PHY_STATE_PHY_TEST,
+};
+
+/*
+ * RX Hash flags, these flags allows to set which incoming packet's field should
+ * participates in RX Hash. Each flag represent certain packet's field,
+ * when the flag is set the field that is represented by the flag will
+ * participate in RX Hash calculation.
+ * Note: *IPV4 and *IPV6 flags can't be enabled together on the same QP
+ * and *TCP and *UDP flags can't be enabled together on the same QP.
+ */
+enum xsc_rx_hash_fields {
+	XSC_RX_HASH_SRC_IPV4	= 1 << 0,
+	XSC_RX_HASH_DST_IPV4	= 1 << 1,
+	XSC_RX_HASH_SRC_IPV6	= 1 << 2,
+	XSC_RX_HASH_DST_IPV6	= 1 << 3,
+	XSC_RX_HASH_SRC_PORT_TCP	= 1 << 4,
+	XSC_RX_HASH_DST_PORT_TCP	= 1 << 5,
+	XSC_RX_HASH_SRC_PORT_UDP	= 1 << 6,
+	XSC_RX_HASH_DST_PORT_UDP	= 1 << 7,
+	XSC_RX_HASH_IPSEC_SPI		= 1 << 8,
+	/* Save bits for future fields */
+	XSC_RX_HASH_INNER		= (1UL << 31),
+};
+
+struct xsc_ib_rss_caps {
+	__aligned_u64 rx_hash_fields_mask; /* enum xsc_rx_hash_fields */
+	__u8 rx_hash_function; /* enum xsc_rx_hash_function_flags */
+	__u8 reserved[7];
+};
+
+enum xsc_ib_cqe_comp_res_format {
+	XSC_IB_CQE_RES_FORMAT_HASH	= 1 << 0,
+	XSC_IB_CQE_RES_FORMAT_CSUM	= 1 << 1,
+	XSC_IB_CQE_RES_FORMAT_CSUM_STRIDX = 1 << 2,
+};
+
+struct xsc_ib_cqe_comp_caps {
+	__u32 max_num;
+	__u32 supported_format; /* enum xsc_ib_cqe_comp_res_format */
+};
+
+enum xsc_ib_packet_pacing_cap_flags {
+	XSC_IB_PP_SUPPORT_BURST	= 1 << 0,
+};
+
+struct xsc_packet_pacing_caps {
+	__u32 qp_rate_limit_min;
+	__u32 qp_rate_limit_max; /* In kpbs */
+
+	/* Corresponding bit will be set if qp type from
+	 * 'enum ib_qp_type' is supported, e.g.
+	 * supported_qpts |= 1 << IB_QPT_RAW_PACKET
+	 */
+	__u32 supported_qpts;
+	__u8  cap_flags; /* enum xsc_ib_packet_pacing_cap_flags */
+	__u8  reserved[3];
+};
+
+enum xsc_ib_mpw_caps {
+	MPW_RESERVED		= 1 << 0,
+	XSC_IB_ALLOW_MPW	= 1 << 1,
+	XSC_IB_SUPPORT_EMPW	= 1 << 2,
+};
+
+enum xsc_ib_sw_parsing_offloads {
+	XSC_IB_SW_PARSING = 1 << 0,
+	XSC_IB_SW_PARSING_CSUM = 1 << 1,
+	XSC_IB_SW_PARSING_LSO = 1 << 2,
+};
+
+struct xsc_ib_sw_parsing_caps {
+	__u32 sw_parsing_offloads; /* enum xsc_ib_sw_parsing_offloads */
+
+	/* Corresponding bit will be set if qp type from
+	 * 'enum ib_qp_type' is supported, e.g.
+	 * supported_qpts |= 1 << IB_QPT_RAW_PACKET
+	 */
+	__u32 supported_qpts;
+};
+
+struct xsc_ib_striding_rq_caps {
+	__u32 min_single_stride_log_num_of_bytes;
+	__u32 max_single_stride_log_num_of_bytes;
+	__u32 min_single_wqe_log_num_of_strides;
+	__u32 max_single_wqe_log_num_of_strides;
+
+	/* Corresponding bit will be set if qp type from
+	 * 'enum ib_qp_type' is supported, e.g.
+	 * supported_qpts |= 1 << IB_QPT_RAW_PACKET
+	 */
+	__u32 supported_qpts;
+	__u32 reserved;
+};
+
+enum xsc_ib_query_dev_resp_flags {
+	/* Support 128B CQE compression */
+	XSC_IB_QUERY_DEV_RESP_FLAGS_CQE_128B_COMP = 1 << 0,
+	XSC_IB_QUERY_DEV_RESP_FLAGS_CQE_128B_PAD  = 1 << 1,
+};
+
+enum xsc_ib_tunnel_offloads {
+	XSC_IB_TUNNELED_OFFLOADS_VXLAN  = 1 << 0,
+	XSC_IB_TUNNELED_OFFLOADS_GRE    = 1 << 1,
+	XSC_IB_TUNNELED_OFFLOADS_GENEVE = 1 << 2,
+	XSC_IB_TUNNELED_OFFLOADS_MPLS_GRE = 1 << 3,
+	XSC_IB_TUNNELED_OFFLOADS_MPLS_UDP = 1 << 4,
+};
+
+struct xsc_ib_query_device_resp {
+	__u32	comp_mask;
+	__u32	response_length;
+	struct	xsc_ib_tso_caps tso_caps;
+	struct	xsc_ib_rss_caps rss_caps;
+	struct	xsc_ib_cqe_comp_caps cqe_comp_caps;
+	struct	xsc_packet_pacing_caps packet_pacing_caps;
+	__u32	xsc_ib_support_multi_pkt_send_wqes;
+	__u32	flags; /* Use enum xsc_ib_query_dev_resp_flags */
+	struct xsc_ib_sw_parsing_caps sw_parsing_caps;
+	struct xsc_ib_striding_rq_caps striding_rq_caps;
+	__u32	tunnel_offloads_caps; /* enum xsc_ib_tunnel_offloads */
+	__u32	reserved;
+};
+
+#endif /* __XSC_ABI_H__ */

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

* [PATCH 10/15] yunsilicon: add RDMA PD/MR
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (8 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 09/15] yunsilicon: add RDMA device support Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 11/15] yunsilicon: add RDMA CQ/SRQ/AH Xin Tian
                   ` (5 subsequent siblings)
  15 siblings, 1 reply; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add protection domains and memory regions: the PD allocation verbs, the
kernel and user MR registration paths (including dmabuf), and the DMA
address translation helpers they build on.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 drivers/infiniband/hw/xsc/Makefile            |   2 +-
 drivers/infiniband/hw/xsc/main.c              |  44 +++
 drivers/infiniband/hw/xsc/mem.c               | 161 +++++++++
 drivers/infiniband/hw/xsc/mr.c                | 313 ++++++++++++++++++
 .../net/ethernet/yunsilicon/xsc/pci/Makefile  |   2 +-
 drivers/net/ethernet/yunsilicon/xsc/pci/mr.c  | 212 ++++++++++++
 drivers/net/ethernet/yunsilicon/xsc/pci/mr.h  |  18 +
 drivers/net/ethernet/yunsilicon/xsc/pci/pd.c  |  55 +++
 drivers/net/ethernet/yunsilicon/xsc/pci/pd.h  |  14 +
 9 files changed, 819 insertions(+), 2 deletions(-)
 create mode 100644 drivers/infiniband/hw/xsc/mem.c
 create mode 100644 drivers/infiniband/hw/xsc/mr.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/mr.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/mr.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/pd.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/pd.h

diff --git a/drivers/infiniband/hw/xsc/Makefile b/drivers/infiniband/hw/xsc/Makefile
index 6c4ec1ca0..b535a6653 100644
--- a/drivers/infiniband/hw/xsc/Makefile
+++ b/drivers/infiniband/hw/xsc/Makefile
@@ -6,4 +6,4 @@ ccflags-y := -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_INFINIBAND_XSC) += xsc_ib.o
 
-xsc_ib-y := main.o
+xsc_ib-y := main.o mem.o mr.o
diff --git a/drivers/infiniband/hw/xsc/main.c b/drivers/infiniband/hw/xsc/main.c
index 9d53a89be..bd2c23b51 100644
--- a/drivers/infiniband/hw/xsc/main.c
+++ b/drivers/infiniband/hw/xsc/main.c
@@ -382,6 +382,42 @@ static int xsc_ib_mmap(struct ib_ucontext *ibcontext, struct vm_area_struct *vma
 			       reg_size, vma->vm_page_prot);
 }
 
+int xsc_ib_alloc_pd(struct ib_pd *ibpd, struct ib_udata *udata)
+{
+	struct ib_device *ibdev = ibpd->device;
+	struct xsc_ib_alloc_pd_resp resp;
+	struct xsc_ib_pd *pd;
+	int err;
+
+	pd = to_xpd(ibpd);
+
+	err = xsc_core_alloc_pd(to_xdev(ibdev)->xdev, &pd->pdn);
+	if (err)
+		return err;
+
+	if (udata) {
+		resp.pdn = pd->pdn;
+		if (ib_copy_to_udata(udata, &resp, sizeof(resp))) {
+			xsc_core_dealloc_pd(to_xdev(ibdev)->xdev, pd->pdn);
+			return -EFAULT;
+		}
+	} else {
+		pd->pa_lkey = 0;
+	}
+
+	return 0;
+}
+
+int xsc_ib_dealloc_pd(struct ib_pd *pd, struct ib_udata *udata)
+{
+	struct xsc_ib_dev *dev = to_xdev(pd->device);
+	struct xsc_ib_pd *xpd = to_xpd(pd);
+
+	xsc_core_dealloc_pd(dev->xdev, xpd->pdn);
+
+	return 0;
+}
+
 static int xsc_port_immutable(struct ib_device *ibdev, u32 port_num,
 			      struct ib_port_immutable *immutable)
 {
@@ -525,16 +561,23 @@ static void xsc_ib_dev_setting(struct xsc_ib_dev *dev)
 	dev->ib_dev.ops.dealloc_ucontext = xsc_ib_dealloc_ucontext;
 	dev->ib_dev.ops.mmap		= xsc_ib_mmap;
 
+	dev->ib_dev.ops.alloc_pd		= xsc_ib_alloc_pd;
+	dev->ib_dev.ops.dealloc_pd		= xsc_ib_dealloc_pd;
 
 	dev->ib_dev.ops.get_link_layer	= xsc_ib_port_link_layer;
 	dev->ib_dev.ops.get_netdev		= xsc_get_netdev;
 
+	dev->ib_dev.ops.get_dma_mr		= xsc_ib_get_dma_mr;
+	dev->ib_dev.ops.reg_user_mr		= xsc_ib_reg_user_mr; /* optional */
+	dev->ib_dev.ops.reg_user_mr_dmabuf	= xsc_ib_reg_user_mr_dmabuf;
+	dev->ib_dev.ops.dereg_mr		= xsc_ib_dereg_mr;
 
 	dev->ib_dev.ops.get_port_immutable		= xsc_port_immutable;
 
 	dev->ib_dev.ops.get_dev_fw_str	= xsc_ib_get_dev_fw_str;
 
 	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_ucontext, xsc_ib_ucontext, ibucontext);
+	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_pd, xsc_ib_pd, ibpd);
 }
 
 static void xsc_get_port_state(struct net_device *ndev, enum xsc_dev_event *ev)
@@ -647,6 +690,7 @@ static int init_one(struct xsc_core_device *xdev,
 
 
 
+	xsc_reg_local_dma_mr(xdev);
 
 	err = ib_register_device(&dev->ib_dev, dev->ib_dev.name,
 				 dev->xdev->device);
diff --git a/drivers/infiniband/hw/xsc/mem.c b/drivers/infiniband/hw/xsc/mem.c
new file mode 100644
index 000000000..2fbce9943
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/mem.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <rdma/ib_umem.h>
+#include "xsc_ib.h"
+
+/**
+ * __xsc_ib_cont_pages - scan a umem for compound pages
+ * @umem: umem object to scan
+ * @addr: ib virtual address requested by the user
+ * @max_page_shift: upper limit for the detected page shift, 0 for none
+ * @count: number of PAGE_SIZE pages covered by umem
+ * @shift: page shift for the compound pages found in the region
+ * @ncont: number of compound pages
+ */
+static void __xsc_ib_cont_pages(struct ib_umem *umem, u64 addr,
+				unsigned long max_page_shift,
+				int *count, int *shift, int *ncont)
+{
+	unsigned long tmp;
+	unsigned long m;
+	u64 base = ~0, p = 0;
+	u64 len, pfn;
+	int i = 0;
+	struct scatterlist *sg;
+	int entry;
+	/* TODO: need peer mem support */
+	unsigned long page_shift = PAGE_SHIFT;
+
+	addr = addr >> page_shift;
+	tmp = (unsigned long)addr;
+	m = find_first_bit(&tmp, BITS_PER_LONG);
+	if (max_page_shift)
+		m = min_t(unsigned long, max_page_shift - page_shift, m);
+	for_each_sgtable_dma_sg(&umem->sgt_append.sgt, sg, entry) {
+		len = sg_dma_len(sg) >> page_shift;
+		pfn = sg_dma_address(sg) >> page_shift;
+		if (base + p != pfn) {
+			/* If either the offset or the new
+			 * base are unaligned update m
+			 */
+			tmp = (unsigned long)(pfn | p);
+			if (!IS_ALIGNED(tmp, 1 << m))
+				m = find_first_bit(&tmp, BITS_PER_LONG);
+
+			base = pfn;
+			p = 0;
+		}
+
+		p += len;
+		i += len;
+	}
+
+	if (i) {
+		m = min_t(unsigned long, ilog2(roundup_pow_of_two(i)), m);
+		*ncont = DIV_ROUND_UP(i, (1 << m));
+	} else {
+		m = 0;
+		*ncont = 0;
+	}
+	*shift = page_shift + m;
+	*count = i;
+}
+
+void xsc_ib_cont_pages(struct ib_umem *umem, u64 addr,
+		       int *count, int *shift, int *ncont)
+{
+	/* no limit for page_shift */
+	__xsc_ib_cont_pages(umem, addr, 0, count, shift, ncont);
+}
+
+static void __xsc_ib_populate_pas(struct xsc_ib_dev *dev, struct ib_umem *umem,
+				  int page_shift, size_t offset, size_t num_pages,
+				  __be64 *pas, int access_flags, bool need_to_devide)
+{
+	unsigned long umem_page_shift = PAGE_SHIFT;
+	int shift = page_shift - umem_page_shift;
+	int mask = (1 << shift) - 1;
+	int i = 0;
+	int k, idx;
+	u64 cur = 0;
+	u64 base;
+	int len;
+	struct scatterlist *sg;
+	int entry;
+
+	for_each_sgtable_dma_sg(&umem->sgt_append.sgt, sg, entry) {
+		len = sg_dma_len(sg) >> umem_page_shift;
+		if (need_to_devide)
+			len = sg_dma_len(sg) >> PAGE_SHIFT_4K;
+		else
+			len = sg_dma_len(sg) >> umem_page_shift;
+		base = sg_dma_address(sg);
+
+		/* Skip elements below offset */
+		if (i + len < offset << shift) {
+			i += len;
+			continue;
+		}
+
+		/* Skip pages below offset */
+		if (i < offset << shift) {
+			k = (offset << shift) - i;
+			i = offset << shift;
+		} else {
+			k = 0;
+		}
+
+		for (; k < len; k++) {
+			if (!(i & mask)) {
+				if (need_to_devide)
+					cur = base + (k << PAGE_SHIFT_4K);
+				else
+					cur = base + (k << umem_page_shift);
+				cur |= access_flags;
+				idx = (i >> shift) - offset;
+
+				pas[idx] = cpu_to_be64(cur);
+				ibdev_dbg(&dev->ib_dev, "pas[%d] 0x%llx\n",
+					  i >> shift, be64_to_cpu(pas[idx]));
+			}
+			i++;
+
+			/* Stop after num_pages reached */
+			if (i >> shift >= offset + num_pages)
+				return;
+		}
+	}
+}
+
+void xsc_ib_populate_pas(struct xsc_ib_dev *dev, struct ib_umem *umem,
+			 int page_shift, __be64 *pas, int npages, bool need_to_devide)
+{
+	return __xsc_ib_populate_pas(dev, umem, page_shift, 0,
+				     npages, pas, 0, need_to_devide);
+}
+
+int xsc_ib_get_buf_offset(u64 addr, int page_shift, u32 *offset)
+{
+	u64 page_size;
+	u64 page_mask;
+	u64 off_size;
+	u64 off_mask;
+	u64 buf_off;
+
+	page_size = 1 << page_shift;
+	page_mask = page_size - 1;
+	buf_off = addr & page_mask;
+	off_size = page_size >> 6;
+	off_mask = off_size - 1;
+
+	if (buf_off & off_mask)
+		return -EINVAL;
+
+	*offset = buf_off >> ilog2(off_size);
+	return 0;
+}
diff --git a/drivers/infiniband/hw/xsc/mr.c b/drivers/infiniband/hw/xsc/mr.c
new file mode 100644
index 000000000..efd685e57
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/mr.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/kref.h>
+#include <linux/random.h>
+#include <linux/debugfs.h>
+#include <linux/export.h>
+#include <linux/dma-buf.h>
+#include <linux/dma-resv.h>
+#include <rdma/ib_umem.h>
+#include <rdma/iter.h>
+#include "common/xsc_cmd.h"
+#include <linux/dma-direct.h>
+#include "xsc_ib.h"
+
+enum {
+	DEF_CACHE_SIZE	= 10,
+};
+
+struct ib_mr *xsc_ib_get_dma_mr(struct ib_pd *pd, int acc)
+{
+	struct xsc_ib_dev *dev = to_xdev(pd->device);
+	struct xsc_core_device *xdev = dev->xdev;
+	struct xsc_register_mr_mbox_in *in;
+	struct xsc_register_mr_request *req;
+	struct xsc_ib_mr *mr;
+	int err;
+
+	mr = kzalloc_obj(*mr, GFP_KERNEL);
+	if (!mr)
+		return ERR_PTR(-ENOMEM);
+
+	in = kzalloc_obj(*in, GFP_KERNEL);
+	if (!in) {
+		err = -ENOMEM;
+		goto err_free;
+	}
+
+	req = &in->req;
+	req->acc = convert_access(acc);
+	req->va_base = 0;
+	req->map_en = !(XSC_MPT_MAP_EN);
+
+	err = xsc_core_create_mkey(xdev, &mr->xmr);
+	if (err)
+		goto err_in;
+	req->mkey = cpu_to_be32(mr->xmr.key);
+	err = xsc_core_register_mr(xdev, &mr->xmr, in, sizeof(*in));
+	if (err)
+		goto err_reg_mr;
+	kfree(in);
+	mr->ibmr.lkey = mr->xmr.key;
+	mr->ibmr.rkey = mr->xmr.key;
+	mr->umem = NULL;
+
+	return &mr->ibmr;
+err_reg_mr:
+	xsc_core_destroy_mkey(xdev, &mr->xmr);
+err_in:
+	kfree(in);
+
+err_free:
+	kfree(mr);
+
+	return ERR_PTR(err);
+}
+
+static struct xsc_ib_mr *reg_create(struct ib_pd *pd, u64 virt_addr,
+				    u64 length, struct ib_umem *umem,
+				    int npages, int page_shift,
+				    int access_flags)
+{
+	struct xsc_ib_dev *dev = to_xdev(pd->device);
+	struct xsc_register_mr_mbox_in *in;
+	struct xsc_ib_mr *mr;
+	struct ib_block_iter biter;
+	__be64 *pas;
+	int inlen;
+	int err;
+
+	mr = kzalloc_obj(*mr, GFP_KERNEL);
+	if (!mr) {
+		err = -ENOMEM;
+		goto err_0;
+	}
+
+	inlen = sizeof(*in) + sizeof(*in->req.pas) * npages;
+	in = xsc_vzalloc(inlen);
+	if (!in) {
+		err = -ENOMEM;
+		goto err_1;
+	}
+	err = xsc_core_create_mkey(dev->xdev, &mr->xmr);
+	if (err) {
+		ibdev_warn(&dev->ib_dev, "create mkey failed\n");
+		goto err_2;
+	}
+
+	pas = in->req.pas;
+	rdma_umem_for_each_dma_block(umem, &biter, 1UL << page_shift) {
+		*pas = cpu_to_be64(rdma_block_iter_dma_address(&biter));
+		pas++;
+	}
+
+	in->req.acc = convert_access(access_flags);
+	in->req.pa_num = cpu_to_be32(npages);
+	in->req.pdn = cpu_to_be32(to_xpd(pd)->pdn);
+	in->req.va_base = cpu_to_be64(virt_addr);
+	in->req.map_en = XSC_MPT_MAP_EN;
+	in->req.len = cpu_to_be64(length);
+	in->req.page_mode = xsc_get_mr_page_mode(dev->xdev, page_shift);
+	ibdev_dbg(&dev->ib_dev, "read_flush hwconfig %s\n",
+		  dev->xdev->read_flush ? "enable" : "disable");
+	in->req.is_gpu = 0;
+	in->req.mkey = cpu_to_be32(mr->xmr.key);
+	err = xsc_core_register_mr(dev->xdev, &mr->xmr, in, inlen);
+	if (err) {
+		ibdev_warn(&dev->ib_dev, "register mr failed, err = %d\n", err);
+		goto err_reg_mr;
+	}
+	mr->umem = umem;
+	xsc_vfree(in);
+
+	ibdev_dbg(&dev->ib_dev, "mkey = 0x%x\n", mr->xmr.key);
+
+	return mr;
+err_reg_mr:
+	xsc_core_destroy_mkey(dev->xdev, &mr->xmr);
+err_2:
+	xsc_vfree(in);
+err_1:
+	kfree(mr);
+err_0:
+	return ERR_PTR(err);
+}
+
+struct ib_mr *xsc_ib_reg_user_mr(struct ib_pd *pd, u64 start, u64 length,
+				 u64 virt_addr, int access_flags,
+				 struct ib_dmah *dmah,
+				 struct ib_udata *udata)
+{
+	struct xsc_ib_dev *dev = to_xdev(pd->device);
+	struct xsc_ib_mr *mr = NULL;
+	struct ib_umem *umem;
+	unsigned long pgsz;
+	int page_shift;
+	int npages;
+	int err;
+
+	if (dmah)
+		return ERR_PTR(-EOPNOTSUPP);
+
+	if (length > dev->xdev->caps.max_mr_size) {
+		ibdev_err(&dev->ib_dev, "reg user mr length(%llu) exceeded.\n", length);
+		return ERR_PTR(-EINVAL);
+	}
+
+	ibdev_dbg(&dev->ib_dev, "start 0x%llx, virt_addr 0x%llx, length 0x%llx\n",
+		  start, virt_addr, length);
+
+	umem = ib_umem_get_va(&dev->ib_dev, start, length, access_flags);
+	if (IS_ERR(umem)) {
+		ibdev_dbg(&dev->ib_dev, "umem get failed\n");
+		return (void *)umem;
+	}
+
+	pgsz = ib_umem_find_best_pgsz(umem, XSC_MR_PAGE_CAP_MASK, virt_addr);
+	if (!pgsz) {
+		ibdev_warn(&dev->ib_dev, "find best page size failed\n");
+		err = -EINVAL;
+		goto error;
+	}
+
+	page_shift = order_base_2(pgsz);
+	npages = ib_umem_num_dma_blocks(umem, pgsz);
+
+	ibdev_dbg(&dev->ib_dev, "npages %d, page_shift %d\n", npages, page_shift);
+
+	mr = reg_create(pd, virt_addr, length, umem, npages, page_shift,
+			access_flags);
+	if (IS_ERR(mr)) {
+		err = PTR_ERR(mr);
+		goto error;
+	}
+
+	ibdev_dbg(&dev->ib_dev, "mkey 0x%x\n", mr->xmr.key);
+
+	mr->umem = umem;
+	mr->npages = npages;
+	mr->ibmr.lkey = mr->xmr.key;
+	mr->ibmr.rkey = mr->xmr.key;
+	mr->ibmr.length = length;
+	return &mr->ibmr;
+
+error:
+	ib_umem_release(umem);
+	return ERR_PTR(err);
+}
+
+struct ib_mr *xsc_ib_reg_user_mr_dmabuf(struct ib_pd *pd, u64 offset,
+					u64 length, u64 virt_addr,
+					int fd, int access_flags,
+					struct ib_dmah *dmah,
+					struct uverbs_attr_bundle *attrs)
+{
+	struct xsc_ib_dev *dev = to_xdev(pd->device);
+	struct ib_umem_dmabuf *umem_dmabuf;
+	struct ib_umem *umem;
+	struct xsc_ib_mr *mr;
+	unsigned long pgsz;
+	int page_shift;
+	int npages;
+	int err;
+
+	if (!IS_ENABLED(CONFIG_INFINIBAND_USER_MEM))
+		return ERR_PTR(-EOPNOTSUPP);
+
+	if (dmah)
+		return ERR_PTR(-EOPNOTSUPP);
+
+	umem_dmabuf = ib_umem_dmabuf_get_pinned(&dev->ib_dev,
+						offset, length,
+						fd, access_flags);
+	if (IS_ERR(umem_dmabuf)) {
+		ibdev_dbg(&dev->ib_dev, "umem_dmabuf get failed (%ld)\n",
+			  PTR_ERR(umem_dmabuf));
+		return ERR_CAST(umem_dmabuf);
+	}
+	umem = &umem_dmabuf->umem;
+
+	pgsz = ib_umem_find_best_pgsz(umem, XSC_MR_PAGE_CAP_MASK, virt_addr);
+	if (!pgsz) {
+		ibdev_warn(&dev->ib_dev, "find best page size failed\n");
+		err = -EINVAL;
+		goto err_release;
+	}
+
+	page_shift = order_base_2(pgsz);
+	npages = ib_umem_num_dma_blocks(umem, pgsz);
+
+	mr = reg_create(pd, virt_addr, length, umem, npages, page_shift,
+			access_flags);
+	if (IS_ERR(mr)) {
+		err = PTR_ERR(mr);
+		goto err_release;
+	}
+
+	mr->umem = umem;
+	mr->npages = npages;
+	mr->ibmr.lkey = mr->xmr.key;
+	mr->ibmr.rkey = mr->xmr.key;
+	mr->ibmr.length = length;
+
+	return &mr->ibmr;
+err_release:
+	ib_umem_release(umem);
+	return ERR_PTR(err);
+}
+
+int xsc_ib_dereg_mr(struct ib_mr *ibmr, struct ib_udata *udata)
+{
+	struct xsc_ib_dev *dev = to_xdev(ibmr->device);
+	struct xsc_ib_mr *mr = to_xmr(ibmr);
+	struct ib_umem *umem = mr->umem;
+	int err;
+
+	ibdev_dbg(&dev->ib_dev, "dereg mkey = 0x%x\n", mr->xmr.key);
+
+	if (mr->npages) {
+		err = xsc_core_dereg_mr(dev->xdev, &mr->xmr);
+		if (err) {
+			ibdev_warn(&dev->ib_dev, "failed to dereg mr 0x%x (%d)\n",
+				   mr->xmr.key, err);
+			return err;
+		}
+	}
+	err = xsc_core_destroy_mkey(dev->xdev, &mr->xmr);
+	if (err) {
+		ibdev_warn(&dev->ib_dev, "failed to destroy mkey 0x%x (%d)\n",
+			   mr->xmr.key, err);
+		return err;
+	}
+
+	if (umem)
+		ib_umem_release(umem);
+
+	kfree(mr);
+
+	return 0;
+}
+
+void xsc_reg_local_dma_mr(struct xsc_core_device *dev)
+{
+	struct xsc_register_mr_mbox_in in;
+	int err = 0;
+
+	in.req.pdn = 0;
+	in.req.pa_num = 0;
+	in.req.len = 0;
+	in.req.mkey = cpu_to_be32(0xFF);
+	in.req.acc = XSC_PERM_LOCAL_WRITE | XSC_PERM_LOCAL_READ;
+	in.req.page_mode = xsc_get_mr_page_mode(dev, PAGE_SHIFT_4K);
+	in.req.is_gpu = 0;
+	in.req.map_en = !(XSC_MPT_MAP_EN);
+	in.req.va_base = 0;
+
+	err = xsc_core_register_mr(dev, NULL, &in, sizeof(in));
+	if (err)
+		dev_err(&dev->pdev->dev, "\n");
+}
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
index 87481db3f..065d52ebf 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
@@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
 
-xsc_pci-y := main.o cmdq.o hw.o qp.o cq.o link.o alloc.o eq.o pci_irq.o adev.o vport.o hal/andes_impl.o
+xsc_pci-y := main.o cmdq.o hw.o qp.o cq.o pd.o mr.o link.o alloc.o eq.o pci_irq.o adev.o vport.o hal/andes_impl.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/mr.c b/drivers/net/ethernet/yunsilicon/xsc/pci/mr.c
new file mode 100644
index 000000000..5c68f0fd8
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/mr.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "common/xsc_core.h"
+#include "common/xsc_cmd_api.h"
+#include "hal/xsc_hal.h"
+#include "mr.h"
+
+int xsc_core_create_mkey(struct xsc_core_device *xdev, struct xsc_core_mr *mr)
+{
+	struct xsc_create_mkey_mbox_in in;
+	struct xsc_create_mkey_mbox_out out;
+	u8 key;
+	int err;
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+
+	spin_lock(&xdev->dev_res->mkey_lock);
+	key = 0x80 + xdev->dev_res->mkey_key++;
+	spin_unlock(&xdev->dev_res->mkey_lock);
+
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_CREATE_MKEY);
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err) {
+		pci_err(xdev->pdev, "failed to create mkey, err = %d\n", err);
+		return err;
+	}
+
+	if (out.hdr.status) {
+		pci_err(xdev->pdev, "failed to create mkey, status = %d\n", out.hdr.status);
+		return xsc_cmd_status_to_err(&out.hdr);
+	}
+
+	mr->key = xsc_idx_to_mkey(xdev, be32_to_cpu(out.mkey) & 0xffffff) | key;
+	pci_dbg(xdev->pdev, "out 0x%x, key 0x%x, mkey 0x%x\n",
+		be32_to_cpu(out.mkey), key, mr->key);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_create_mkey);
+
+int xsc_core_destroy_mkey(struct xsc_core_device *xdev, struct xsc_core_mr *mr)
+{
+	struct xsc_destroy_mkey_mbox_in in;
+	struct xsc_destroy_mkey_mbox_out out;
+	int err;
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DESTROY_MKEY);
+	in.mkey = cpu_to_be32(mr->key);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err)
+		return err;
+
+	if (out.hdr.status)
+		return xsc_cmd_status_to_err(&out.hdr);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_destroy_mkey);
+
+static int xsc_set_mpt_via_cmdq(struct xsc_core_device *xdev,
+				struct xsc_register_mr_mbox_in *in_cmd,
+				u32 *mtt_base)
+{
+	struct xsc_register_mr_request *req = &in_cmd->req;
+	struct xsc_set_mpt_mbox_in *in;
+	struct xsc_set_mpt_mbox_out out;
+	u64 mem_size;
+	int err;
+
+	in = kzalloc_obj(*in, GFP_KERNEL);
+	if (!in)
+		return -ENOMEM;
+
+	mem_size = be64_to_cpu(req->len);
+	in->mpt_item.pdn = req->pdn;
+	in->mpt_item.pa_num = req->pa_num;
+	in->mpt_item.len = cpu_to_be32((u32)mem_size);
+	in->mpt_item.len_h = cpu_to_be32((u32)(mem_size >> 32));
+	in->mpt_item.mkey = req->mkey;
+	in->mpt_item.is_gpu = req->is_gpu;
+	in->mpt_item.acc = req->acc;
+	in->mpt_item.page_mode = req->page_mode;
+	in->mpt_item.map_en = req->map_en;
+	in->mpt_item.va_base = req->va_base;
+	in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_SET_MPT);
+
+	memset(&out, 0, sizeof(out));
+	err = xsc_cmd_exec(xdev, in, sizeof(*in), &out, sizeof(out));
+	kfree(in);
+	if (err || out.hdr.status) {
+		pci_err(xdev->pdev, "failed to set mpt, err = %d, status = %d\n",
+			err, out.hdr.status);
+		return -EINVAL;
+	}
+
+	*mtt_base = be32_to_cpu(out.mtt_base);
+	return 0;
+}
+
+#define PA_NUM_PER_CMD 1024
+
+static int xsc_set_mtt_via_cmdq(struct xsc_core_device *xdev,
+				struct xsc_register_mr_mbox_in *in_cmd,
+				u32 mtt_base)
+{
+	struct xsc_register_mr_request *req = &in_cmd->req;
+	struct xsc_set_mtt_mbox_in *seg_in;
+	struct xsc_set_mtt_mbox_out seg_out;
+	int tot_pg_num = be32_to_cpu(req->pa_num);
+	int tot_seg_num, seg_pa_num, seg_idx;
+	int pa_idx_base = 0;
+	int in_len, err, i;
+
+	tot_seg_num = (tot_pg_num & (PA_NUM_PER_CMD - 1)) ?
+		      ((tot_pg_num >> 10) + 1) : (tot_pg_num >> 10);
+
+	for (seg_idx = 0; seg_idx < tot_seg_num; seg_idx++) {
+		seg_pa_num = (seg_idx != tot_seg_num - 1) ? PA_NUM_PER_CMD :
+			     (tot_pg_num - ((tot_seg_num - 1) << 10));
+		in_len = (seg_pa_num << 3) + sizeof(*seg_in);
+		seg_in = kzalloc(in_len, GFP_KERNEL);
+		if (!seg_in)
+			return -ENOMEM;
+
+		seg_in->mtt_setting.mtt_base = cpu_to_be32(mtt_base);
+		seg_in->mtt_setting.pa_num = cpu_to_be32(seg_pa_num);
+		for (i = 0; i < seg_pa_num; i++)
+			seg_in->mtt_setting.pas[i] = req->pas[pa_idx_base + i];
+		seg_in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_SET_MTT);
+
+		memset(&seg_out, 0, sizeof(seg_out));
+		pci_dbg(xdev->pdev,
+			"set mtt seg %d, pa_num %d, pa_idx_base %d, tot_seg %d\n",
+			seg_idx, seg_pa_num, pa_idx_base, tot_seg_num);
+		err = xsc_cmd_exec(xdev, seg_in, in_len, &seg_out, sizeof(seg_out));
+		kfree(seg_in);
+		if (err || seg_out.hdr.status) {
+			pci_err(xdev->pdev, "failed to set mtt seg %d, err = %d, status = %d\n",
+				seg_idx, err, seg_out.hdr.status);
+			return -EINVAL;
+		}
+
+		pa_idx_base += seg_pa_num;
+		mtt_base += seg_pa_num;
+	}
+
+	return 0;
+}
+
+static int xsc_dereg_mr_via_cmdq(struct xsc_core_device *xdev, u32 mkey)
+{
+	struct xsc_unregister_mr_mbox_in in;
+	struct xsc_unregister_mr_mbox_out out;
+	int err;
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DEREG_MR);
+	in.mkey = cpu_to_be32(xsc_mkey_to_idx(xdev, mkey));
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err || out.hdr.status) {
+		pci_err(xdev->pdev, "failed to dereg mr, err = %d, status = %d\n",
+			err, out.hdr.status);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int xsc_core_register_mr(struct xsc_core_device *xdev, struct xsc_core_mr *mr,
+			 struct xsc_register_mr_mbox_in *in, int inlen)
+{
+	u32 mtt_base;
+	int err;
+
+	in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_REG_MR);
+
+	err = xsc_set_mpt_via_cmdq(xdev, in, &mtt_base);
+	if (err) {
+		pci_err(xdev->pdev, "failed to set mpt via cmdq\n");
+		return err;
+	}
+
+	err = xsc_set_mtt_via_cmdq(xdev, in, mtt_base);
+	if (err) {
+		pci_err(xdev->pdev, "failed to set mtt via cmdq\n");
+		xsc_dereg_mr_via_cmdq(xdev, be32_to_cpu(in->req.mkey));
+		return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_register_mr);
+
+int xsc_core_dereg_mr(struct xsc_core_device *xdev, struct xsc_core_mr *mr)
+{
+	return xsc_dereg_mr_via_cmdq(xdev, mr->key);
+}
+EXPORT_SYMBOL_GPL(xsc_core_dereg_mr);
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/mr.h b/drivers/net/ethernet/yunsilicon/xsc/pci/mr.h
new file mode 100644
index 000000000..edafecda6
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/mr.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_MR_H
+#define __XSC_MR_H
+
+#include "common/xsc_core.h"
+#include "common/xsc_cmd.h"
+
+int xsc_core_create_mkey(struct xsc_core_device *xdev, struct xsc_core_mr *mr);
+int xsc_core_destroy_mkey(struct xsc_core_device *xdev, struct xsc_core_mr *mr);
+int xsc_core_register_mr(struct xsc_core_device *xdev, struct xsc_core_mr *mr,
+			 struct xsc_register_mr_mbox_in *in, int inlen);
+int xsc_core_dereg_mr(struct xsc_core_device *xdev, struct xsc_core_mr *mr);
+
+#endif /* __XSC_MR_H */
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/pd.c b/drivers/net/ethernet/yunsilicon/xsc/pci/pd.c
new file mode 100644
index 000000000..7969dfefc
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/pd.c
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "common/xsc_core.h"
+#include "common/xsc_cmd_api.h"
+#include "pd.h"
+
+int xsc_core_alloc_pd(struct xsc_core_device *xdev, u32 *pdn)
+{
+	struct xsc_alloc_pd_mbox_in in;
+	struct xsc_alloc_pd_mbox_out out;
+	int err;
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_ALLOC_PD);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err)
+		return err;
+
+	if (out.hdr.status)
+		return xsc_cmd_status_to_err(&out.hdr);
+
+	*pdn = be32_to_cpu(out.pdn) & 0xffffff;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_alloc_pd);
+
+int xsc_core_dealloc_pd(struct xsc_core_device *xdev, u32 pdn)
+{
+	struct xsc_dealloc_pd_mbox_in in;
+	struct xsc_dealloc_pd_mbox_out out;
+	int err;
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DEALLOC_PD);
+	in.pdn = cpu_to_be32(pdn);
+
+	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
+	if (err)
+		return err;
+
+	if (out.hdr.status)
+		return xsc_cmd_status_to_err(&out.hdr);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_dealloc_pd);
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/pd.h b/drivers/net/ethernet/yunsilicon/xsc/pci/pd.h
new file mode 100644
index 000000000..d9a99baea
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/pd.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_PD_H
+#define __XSC_PD_H
+
+#include "common/xsc_core.h"
+
+int xsc_core_alloc_pd(struct xsc_core_device *xdev, u32 *pdn);
+int xsc_core_dealloc_pd(struct xsc_core_device *xdev, u32 pdn);
+
+#endif /* __XSC_PD_H */

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

* [PATCH 11/15] yunsilicon: add RDMA CQ/SRQ/AH
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (9 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 10/15] yunsilicon: add RDMA PD/MR Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 12/15] yunsilicon: add RDMA QP verbs Xin Tian
                   ` (4 subsequent siblings)
  15 siblings, 1 reply; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the completion queues, shared receive queues and address handles: the
create/destroy/query/modify verbs and the CQ poll and completion
processing.

The QP receive helper and error-state tracking are included here
because the CQ and SRQ paths reference them.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 drivers/infiniband/hw/xsc/Makefile            |   2 +-
 drivers/infiniband/hw/xsc/ah.c                | 144 +++
 drivers/infiniband/hw/xsc/cq.c                | 895 ++++++++++++++++++
 drivers/infiniband/hw/xsc/main.c              |  15 +
 drivers/infiniband/hw/xsc/qp.c                | 239 +++++
 drivers/infiniband/hw/xsc/srq.c               | 426 +++++++++
 .../net/ethernet/yunsilicon/xsc/pci/Makefile  |   3 +-
 .../net/ethernet/yunsilicon/xsc/pci/main.c    |  10 +
 drivers/net/ethernet/yunsilicon/xsc/pci/srq.c |  92 ++
 drivers/net/ethernet/yunsilicon/xsc/pci/srq.h |  16 +
 10 files changed, 1840 insertions(+), 2 deletions(-)
 create mode 100644 drivers/infiniband/hw/xsc/ah.c
 create mode 100644 drivers/infiniband/hw/xsc/cq.c
 create mode 100644 drivers/infiniband/hw/xsc/qp.c
 create mode 100644 drivers/infiniband/hw/xsc/srq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/srq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/srq.h

diff --git a/drivers/infiniband/hw/xsc/Makefile b/drivers/infiniband/hw/xsc/Makefile
index b535a6653..d5ad0e573 100644
--- a/drivers/infiniband/hw/xsc/Makefile
+++ b/drivers/infiniband/hw/xsc/Makefile
@@ -6,4 +6,4 @@ ccflags-y := -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_INFINIBAND_XSC) += xsc_ib.o
 
-xsc_ib-y := main.o mem.o mr.o
+xsc_ib-y := main.o cq.o qp.o mem.o mr.o srq.o ah.o
diff --git a/drivers/infiniband/hw/xsc/ah.c b/drivers/infiniband/hw/xsc/ah.c
new file mode 100644
index 000000000..4d916dbe7
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/ah.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/random.h>
+#include "xsc_ib.h"
+#include <rdma/xsc-abi.h>
+
+static u32 xsc_calc_roce_udp_flow_label(void)
+{
+	/* Generate a non-zero 20 bit flow label. */
+	return get_random_u32_below(IB_GRH_FLOWLABEL_MASK) + 1;
+}
+
+static u16 xsc_ah_get_udp_sport(const struct xsc_ib_dev *dev,
+				struct rdma_ah_attr *ah_attr)
+{
+	enum ib_gid_type gid_type = ah_attr->grh.sgid_attr->gid_type;
+	u16 sport = 0;
+	u32 fl = 0;
+
+	if (gid_type == IB_GID_TYPE_ROCE_UDP_ENCAP &&
+	    (rdma_ah_get_ah_flags(ah_attr) & IB_AH_GRH) &&
+	    (ah_attr->grh.flow_label & IB_GRH_FLOWLABEL_MASK)) {
+		fl = ah_attr->grh.flow_label;
+	} else {
+		/*generate a 20bit flow_label and output to user layer*/
+		fl = xsc_calc_roce_udp_flow_label();
+		ah_attr->grh.flow_label = fl;
+	}
+
+	sport = rdma_flow_label_to_udp_sport(fl);
+	ibdev_dbg(&dev->ib_dev, "fl=0x%x,sport=0x%x\n", fl, sport);
+	return sport;
+}
+
+static struct ib_ah *create_ib_ah(struct xsc_ib_dev *dev,
+				  struct xsc_ib_ah *ah,
+				  struct rdma_ah_attr *ah_attr)
+{
+	enum ib_gid_type gid_type;
+
+	if (rdma_ah_get_ah_flags(ah_attr) & IB_AH_GRH) {
+		const struct ib_global_route *grh = rdma_ah_read_grh(ah_attr);
+
+		memcpy(ah->av.rgid, &grh->dgid, 16);
+		ah->av.grh_gid_fl = cpu_to_be32(grh->flow_label |
+						(1 << 30) |
+						grh->sgid_index << 20);
+		ah->av.hop_limit = grh->hop_limit;
+		ah->av.tclass = grh->traffic_class;
+	}
+
+	ah->av.stat_rate_sl = (rdma_ah_get_static_rate(ah_attr) << 4);
+
+	if (ah_attr->type == RDMA_AH_ATTR_TYPE_ROCE) {
+		gid_type = ah_attr->grh.sgid_attr->gid_type;
+
+		memcpy(ah->av.rmac, ah_attr->roce.dmac,
+		       sizeof(ah_attr->roce.dmac));
+
+		ah->av.udp_sport = xsc_ah_get_udp_sport(dev, ah_attr);
+		ah->av.stat_rate_sl |= (rdma_ah_get_sl(ah_attr) & 0x7) << 1;
+		if (gid_type == IB_GID_TYPE_ROCE_UDP_ENCAP)
+#define XSC_ECN_ENABLED BIT(1)
+			ah->av.tclass |= XSC_ECN_ENABLED;
+	} else {
+		ah->av.rlid = cpu_to_be16(rdma_ah_get_dlid(ah_attr));
+		ah->av.fl_mlid = rdma_ah_get_path_bits(ah_attr) & 0x7f;
+		ah->av.stat_rate_sl |= (rdma_ah_get_sl(ah_attr) & 0xf);
+	}
+
+	return &ah->ibah;
+}
+
+int xsc_ib_create_ah(struct ib_ah *ibah,
+		     struct rdma_ah_init_attr *init_attr,
+		      struct ib_udata *udata)
+{
+	struct xsc_ib_ah *ah = to_xah(ibah);
+	struct xsc_ib_dev *dev = to_xdev(ibah->device);
+	struct rdma_ah_attr *ah_attr = init_attr->ah_attr;
+	enum rdma_ah_attr_type ah_type = ah_attr->type;
+
+	if (ah_type == RDMA_AH_ATTR_TYPE_ROCE &&
+	    !(rdma_ah_get_ah_flags(ah_attr) & IB_AH_GRH))
+		return -EINVAL;
+
+	if (ah_type == RDMA_AH_ATTR_TYPE_ROCE && udata) {
+		int err;
+		struct xsc_ib_create_ah_resp resp = {};
+		u32 min_resp_len = offsetof(typeof(resp), dmac) +
+				   sizeof(resp.dmac);
+
+		if (udata->outlen < min_resp_len)
+			return -EINVAL;
+
+		resp.response_length = min_resp_len;
+		memcpy(resp.dmac, ah_attr->roce.dmac, ETH_ALEN);
+		err = ib_copy_to_udata(udata, &resp, resp.response_length);
+		if (err)
+			return err;
+	}
+
+	create_ib_ah(dev, ah, ah_attr); /* never fails */
+	return 0;
+}
+
+int xsc_ib_query_ah(struct ib_ah *ibah, struct rdma_ah_attr *ah_attr)
+{
+	struct xsc_ib_ah *ah = to_xah(ibah);
+	u32 tmp;
+
+	memset(ah_attr, 0, sizeof(*ah_attr));
+	ah_attr->type = ibah->type;
+
+	tmp = be32_to_cpu(ah->av.grh_gid_fl);
+	if (tmp & (1 << 30)) {
+		rdma_ah_set_grh(ah_attr, NULL, tmp & IB_GRH_FLOWLABEL_MASK,
+				(tmp >> 20) & 0xff, ah->av.hop_limit,
+				ah->av.tclass);
+		rdma_ah_set_dgid_raw(ah_attr, ah->av.rgid);
+	}
+
+	rdma_ah_set_static_rate(ah_attr, ah->av.stat_rate_sl >> 4);
+	if (ah_attr->type == RDMA_AH_ATTR_TYPE_ROCE) {
+		rdma_ah_set_sl(ah_attr, (ah->av.stat_rate_sl >> 1) & 0x7);
+	} else {
+		rdma_ah_set_dlid(ah_attr, be16_to_cpu(ah->av.rlid));
+		rdma_ah_set_path_bits(ah_attr, ah->av.fl_mlid & 0x7f);
+		rdma_ah_set_sl(ah_attr, ah->av.stat_rate_sl & 0xf);
+	}
+
+	return 0;
+}
+
+int xsc_ib_destroy_ah(struct ib_ah *ibah, u32 flags)
+{
+	return 0;
+}
diff --git a/drivers/infiniband/hw/xsc/cq.c b/drivers/infiniband/hw/xsc/cq.c
new file mode 100644
index 000000000..04820bf3a
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/cq.c
@@ -0,0 +1,895 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/kref.h>
+#include <rdma/ib_umem.h>
+#include "xsc_ib.h"
+#include <rdma/xsc-abi.h>
+#include "common/xsc_hsi.h"
+#include <linux/dma-direct.h>
+
+#ifndef IB_UVERBS_CQ_FLAGS_IGNORE_OVERRUN
+#define IB_UVERBS_CQ_FLAGS_IGNORE_OVERRUN BIT(1)
+#endif
+
+enum {
+	CQ_OK					=  0,
+	CQ_EMPTY				= -1,
+	CQ_POLL_ERR				= -2
+};
+
+enum {
+	XSC_CQE_APP_TAG_MATCHING = 1,
+};
+
+enum {
+	XSC_CQE_APP_OP_TM_CONSUMED = 0x1,
+	XSC_CQE_APP_OP_TM_EXPECTED = 0x2,
+	XSC_CQE_APP_OP_TM_UNEXPECTED = 0x3,
+	XSC_CQE_APP_OP_TM_NO_TAG = 0x4,
+	XSC_CQE_APP_OP_TM_APPEND = 0x5,
+	XSC_CQE_APP_OP_TM_REMOVE = 0x6,
+	XSC_CQE_APP_OP_TM_NOOP = 0x7,
+	XSC_CQE_APP_OP_TM_CONSUMED_SW_RDNV = 0x9,
+	XSC_CQE_APP_OP_TM_CONSUMED_MSG = 0xA,
+	XSC_CQE_APP_OP_TM_CONSUMED_MSG_SW_RDNV = 0xB,
+	XSC_CQE_APP_OP_TM_MSG_COMPLETION_CANCELED = 0xC,
+};
+
+static const u32 xsc_cqe_opcode[] = {
+	[XSC_OPCODE_RDMA_REQ_SEND]		= IB_WC_SEND,
+	[XSC_OPCODE_RDMA_REQ_SEND_IMMDT]	= IB_WC_SEND,
+	[XSC_OPCODE_RDMA_RSP_RECV]		= IB_WC_RECV,
+	[XSC_OPCODE_RDMA_RSP_RECV_IMMDT]	= IB_WC_RECV,
+	[XSC_OPCODE_RDMA_REQ_WRITE]		= IB_WC_RDMA_WRITE,
+	[XSC_OPCODE_RDMA_REQ_WRITE_IMMDT]	= IB_WC_RDMA_WRITE,
+	[XSC_OPCODE_RDMA_RSP_WRITE_IMMDT]	= IB_WC_RECV_RDMA_WITH_IMM,
+	[XSC_OPCODE_RDMA_REQ_READ]		= IB_WC_RDMA_READ,
+	[XSC_OPCODE_RDMA_MAD_REQ_SEND]		= IB_WC_SEND,
+	[XSC_OPCODE_RDMA_MAD_RSP_RECV]		= IB_WC_RECV,
+};
+
+static void xsc_ib_cq_comp(struct xsc_core_cq *cq)
+{
+	struct ib_cq *ibcq = &core_to_xcq(cq)->ibcq;
+
+	ibcq->comp_handler(ibcq, ibcq->cq_context);
+}
+
+static void xsc_ib_cq_event(struct xsc_core_cq *xcq, enum xsc_event type)
+{
+	struct xsc_ib_cq *cq = container_of(xcq, struct xsc_ib_cq, xcq);
+	struct xsc_ib_dev *dev = to_xdev(cq->ibcq.device);
+	struct ib_cq *ibcq = &cq->ibcq;
+	struct ib_event event;
+
+	if (type != XSC_EVENT_TYPE_CQ_ERROR) {
+		ibdev_err(&dev->ib_dev, "Unexpected event type %d on CQ %06x\n",
+			  type, xcq->cqn);
+		return;
+	}
+
+	if (ibcq->event_handler) {
+		event.device     = &dev->ib_dev;
+		event.event      = IB_EVENT_CQ_ERR;
+		event.element.cq = ibcq;
+		ibcq->event_handler(&event, ibcq->cq_context);
+		pr_info("xsc_ib: xsc async event 0x%x, ib event %d\n",
+			XSC_EVENT_TYPE_CQ_ERROR, IB_EVENT_CQ_ERR);
+	}
+}
+
+static void *get_cqe_from_buf(struct xsc_ib_cq_buf *buf, int n, int size)
+{
+	return xsc_buf_offset(&buf->buf, n * size);
+}
+
+static void *get_cqe(struct xsc_ib_cq *cq, int n)
+{
+	return get_cqe_from_buf(&cq->buf, n, cq->xcq.cqe_sz);
+}
+
+static void *get_sw_cqe(struct xsc_ib_cq *cq, int n)
+{
+	struct xsc_cqe *cqe;
+
+	cqe = (struct xsc_cqe *)get_cqe(cq, n & (cq->ibcq.cqe - 1));
+
+	return ((cqe->owner & XSC_CQE_OWNER_MASK) ^
+		!!(n & cq->ibcq.cqe)) ? NULL : cqe;
+}
+
+static inline void handle_good_req(struct ib_wc *wc,
+				   struct xsc_cqe *cqe,
+				   u8 opcode)
+{
+	wc->opcode = xsc_cqe_opcode[opcode];
+	if (opcode == XSC_OPCODE_RDMA_REQ_READ)
+		wc->byte_len  = RD_LE_32(cqe->msg_len);
+	wc->status = IB_WC_SUCCESS;
+}
+
+static void handle_responder(struct ib_wc *wc, struct xsc_cqe *cqe,
+			     struct xsc_ib_qp *qp, u8 opcode)
+{
+	struct xsc_ib_wq *wq = &qp->rq;
+	u16 idx;
+
+	wc->byte_len = RD_LE_32(cqe->msg_len);
+	wc->opcode = xsc_cqe_opcode[opcode];
+	wc->status = IB_WC_SUCCESS;
+
+	idx = wq->tail & (wq->wqe_cnt - 1);
+	wc->wr_id = wq->wrid[idx];
+	atomic_dec(&wq->flush_wqe_cnt);
+	++wq->tail;
+}
+
+static void *get_wqe(struct xsc_ib_qp *qp, int offset)
+{
+	return xsc_buf_offset(&qp->buf, offset);
+}
+
+static void *get_recv_wqe(struct xsc_ib_qp *qp, int n)
+{
+	return get_wqe(qp, qp->rq.offset + (n << qp->rq.wqe_shift));
+}
+
+static void *get_seg_wqe(void *first, int n)
+{
+	return first + (n << XSC_BASE_WQE_SHIFT);
+}
+
+static void xsc_handle_rdma_mad_resp_recv(struct xsc_ib_cq *cq,
+					  struct xsc_ib_qp **cur_qp,
+					  struct ib_wc *wc,
+					  struct xsc_cqe *cqe,
+					  u8 opcode)
+{
+	struct xsc_ib_dev *dev = to_xdev(cq->ibcq.device);
+	void *recv;
+	struct xsc_wqe_data_seg *data_seg;
+	struct iphdr *ip4h = NULL;
+	struct ipv6hdr *ip6h;
+	struct udphdr *udph;
+	struct ib_unpacked_eth *eth;
+	struct ib_unpacked_vlan *vlan;
+	struct ib_grh *grh;
+	struct ib_mad *mad;
+	struct rxe_bth *bth;
+	struct rxe_deth *deth;
+	unsigned int pading_sz = 0;
+	struct xsc_ib_wq *wq;
+	int idx;
+	u16 eth_type;
+	void *l3_start;
+
+	wq = &(*cur_qp)->rq;
+	idx = wq->tail & (wq->wqe_cnt - 1);
+
+	handle_responder(wc, cqe, *cur_qp, opcode);
+
+	data_seg = get_seg_wqe(get_recv_wqe(*cur_qp, idx), 0);
+	recv = xsc_ib_recv_mad_sg_virt_addr(&dev->ib_dev, wc, data_seg->va);
+
+	eth = (struct ib_unpacked_eth *)recv;
+	grh = (struct ib_grh *)recv;
+	if (eth->type == htons(ETH_P_8021Q)) {
+		vlan = (struct ib_unpacked_vlan *)(eth + 1);
+		eth_type = ntohs(vlan->type);
+		l3_start = vlan + 1;
+
+		wc->vlan_id = ntohs(vlan->tag) & 0x0fff;
+		wc->sl = (ntohs(vlan->tag) >> 13) & 0x7;
+		wc->wc_flags |= IB_WC_WITH_VLAN;
+	} else {
+		eth_type = ntohs(eth->type);
+		l3_start = eth + 1;
+	}
+
+	if (eth_type == ETH_P_IP) {
+		ip4h = (struct iphdr *)l3_start;
+		udph = (struct udphdr *)(ip4h + 1);
+	} else {
+		ip6h = (struct ipv6hdr *)l3_start;
+		udph = (struct udphdr *)(ip6h + 1);
+	}
+	bth = (struct rxe_bth *)(udph + 1);
+	deth = (struct rxe_deth *)(bth + 1);
+	mad = (struct ib_mad *)(deth + 1);
+
+	if (eth_type == ETH_P_IP) {
+		pading_sz = sizeof(*grh) - sizeof(*ip4h);
+		memmove((u8 *)(grh + 1) - sizeof(*ip4h), ip4h, sizeof(*ip4h));
+		memset(grh, 0, pading_sz);
+	} else {
+		memmove(grh, ip6h, sizeof(*ip6h));
+	}
+	memmove(grh + 1, mad, sizeof(*mad));
+
+	wc->wc_flags |= IB_WC_GRH;
+
+	ibdev_dbg(&dev->ib_dev, "recv cqe idx:%u, len:%u\n", wq->tail, wc->byte_len);
+	ibdev_dbg(&dev->ib_dev, "qp[%d] recv MAD packet, msg_len=%d\n",
+		  (*cur_qp)->xqp.qpn, wc->byte_len);
+	wc->status = IB_WC_SUCCESS;
+}
+
+static void xsc_generate_lost_cqe(struct xsc_ib_dev *dev, struct xsc_ib_wq *wq,
+				  int qpn, int idx, struct ib_wc *wc)
+{
+	int lost_cqe_idx = wq->prev_ce_wqe[idx];
+
+	ibdev_err(&dev->ib_dev, "detected lost cqe, cur wqe idx of qp%d is %d\n", qpn, idx);
+
+	while (wq->prev_ce_wqe[lost_cqe_idx] != wq->last_comp_ce_wqe)
+		lost_cqe_idx = wq->prev_ce_wqe[lost_cqe_idx];
+	ibdev_err(&dev->ib_dev, "generate lost cqe idx of qp%d is %d\n", qpn, lost_cqe_idx);
+
+	wq->last_comp_ce_wqe = lost_cqe_idx;
+	wc->wr_id = wq->wrid[lost_cqe_idx];
+	wq->tail = wq->wqe_head[lost_cqe_idx] + 1;
+	atomic_dec(&wq->flush_wqe_cnt);
+	wq->need_flush[lost_cqe_idx] = 0;
+	wc->status = IB_WC_GENERAL_ERR;
+}
+
+static int xsc_poll_one(struct xsc_ib_cq *cq,
+			struct xsc_ib_qp **cur_qp,
+			struct ib_wc *wc)
+{
+	struct xsc_ib_dev *dev = to_xdev(cq->ibcq.device);
+	struct xsc_core_qp *xqp;
+	struct xsc_ib_wq *wq;
+	u8 opcode;
+	u32 qpn;
+	int idx;
+	struct xsc_cqe *cqe;
+	u32 *p = NULL;
+	struct xsc_ib_srq *xsrq;
+
+	cqe = get_sw_cqe(cq, cq->xcq.cons_index);
+	if (!cqe)
+		return -EAGAIN;
+
+	++cq->xcq.cons_index;
+
+	/* Make sure we read CQ entry contents after we've checked the
+	 * ownership bit.
+	 */
+	rmb();
+
+	p = (u32 *)cqe;
+
+	qpn = cqe->qp_id;
+	qpn = le32_to_cpu(qpn);
+	if (!*cur_qp || (qpn != (*cur_qp)->ibqp.qp_num)) {
+		/* We do not have to take the QP table lock here,
+		 * because CQs will be locked while QPs are removed
+		 * from the table.
+		 */
+		xqp = __xsc_qp_lookup(dev->xdev, qpn);
+		if (unlikely(!xqp)) {
+			ibdev_warn(&dev->ib_dev, "CQE@CQ %d for unknown QPN %d\n",
+				   cq->xcq.cqn, qpn);
+			return -EINVAL;
+		}
+
+		*cur_qp = core_to_xqp(xqp);
+	}
+
+	memset(wc, 0, sizeof(*wc));
+	wc->qp = &(*cur_qp)->ibqp;
+	opcode = xsc_get_cqe_opcode(dev->xdev, cqe);
+	switch (opcode) {
+	case XSC_OPCODE_RDMA_REQ_SEND_IMMDT:
+	case XSC_OPCODE_RDMA_REQ_WRITE_IMMDT:
+		wc->wc_flags |= IB_WC_WITH_IMM;
+		fallthrough;
+	case XSC_OPCODE_RDMA_REQ_SEND:
+	case XSC_OPCODE_RDMA_REQ_WRITE:
+	case XSC_OPCODE_RDMA_REQ_READ:
+	case XSC_OPCODE_RDMA_MAD_REQ_SEND:
+		wq = &(*cur_qp)->sq;
+		idx = xsc_get_cqe_wqe_id(dev->xdev, cqe) >> (wq->wqe_shift - XSC_BASE_WQE_SHIFT);
+		idx &= (wq->wqe_cnt - 1);
+		if (wq->prev_ce_wqe[idx] != wq->last_comp_ce_wqe) {
+			xsc_generate_lost_cqe(dev, wq, qpn, idx, wc);
+			cq->invalid = 1;
+			break;
+		}
+		wq->last_comp_ce_wqe = idx;
+		handle_good_req(wc, cqe, opcode);
+		wc->wr_id = wq->wrid[idx];
+		wq->tail = wq->wqe_head[idx] + 1;
+		if (opcode != XSC_OPCODE_RDMA_MAD_REQ_SEND)
+			atomic_dec(&wq->flush_wqe_cnt);
+		wq->need_flush[idx] = 0;
+		ibdev_dbg(&dev->ib_dev, "wqeid:%u, wq tail:%u qpn:%u\n", idx, wq->tail, qpn);
+		wc->status = IB_WC_SUCCESS;
+		break;
+	case XSC_OPCODE_RDMA_RSP_RECV_IMMDT:
+	case XSC_OPCODE_RDMA_RSP_WRITE_IMMDT:
+		wc->wc_flags |= IB_WC_WITH_IMM;
+		WR_BE_32(wc->ex.imm_data, RD_LE_32(cqe->imm_data));
+		fallthrough;
+	case XSC_OPCODE_RDMA_RSP_RECV:
+		wq = &(*cur_qp)->rq;
+		handle_responder(wc, cqe, *cur_qp, opcode);
+		if ((*cur_qp)->ibqp.srq) {
+			xsrq = to_xsrq((*cur_qp)->ibqp.srq);
+			xsc_free_one_cache_srq_wr(xsrq, *cur_qp, wc->wr_id);
+		}
+		ibdev_dbg(&dev->ib_dev, "recv cqe idx:%u, len:%u, qpn:%u\n",
+			  wq->tail, wc->byte_len, qpn);
+		wc->status = IB_WC_SUCCESS;
+		break;
+
+	case XSC_OPCODE_RDMA_MAD_RSP_RECV:
+		ibdev_dbg(&dev->ib_dev, "recv MAD, qpn:%u\n", qpn);
+		xsc_handle_rdma_mad_resp_recv(cq, cur_qp, wc, cqe, opcode);
+		break;
+
+	case XSC_OPCODE_RDMA_REQ_ERROR:
+		wq = &(*cur_qp)->sq;
+		idx = xsc_get_cqe_wqe_id(dev->xdev, cqe) >> (wq->wqe_shift - XSC_BASE_WQE_SHIFT);
+		idx &= (wq->wqe_cnt - 1);
+
+		if (!wq->need_flush[idx]) {
+			unsigned int i;
+			unsigned int tmp;
+			unsigned int prev = -1;
+
+			for (i = wq->tail; i != wq->head; i++) {
+				tmp = i & (wq->wqe_cnt - 1);
+				if (tmp == idx)
+					break;
+				if (wq->need_flush[tmp])
+					prev = tmp;
+			}
+
+			if (prev != -1) {
+				if (wq->prev_ce_wqe[prev] != wq->last_comp_ce_wqe) {
+					xsc_generate_lost_cqe(dev, wq, qpn, prev, wc);
+					cq->invalid = 1;
+					break;
+				}
+			}
+		} else {
+			if (wq->prev_ce_wqe[idx] != wq->last_comp_ce_wqe) {
+				xsc_generate_lost_cqe(dev, wq, qpn, idx, wc);
+				cq->invalid = 1;
+				break;
+			}
+		}
+
+		wc->wr_id = wq->wrid[idx];
+		wq->tail = wq->wqe_head[idx] + 1;
+		if (wq->need_flush[idx])
+			atomic_dec(&wq->flush_wqe_cnt);
+		wq->need_flush[idx] = 0;
+		ibdev_err(&dev->ib_dev, "req error\n%08x %08x %08x %08x %08x %08x\n",
+			  p[0], p[1], p[2], p[3], p[5], p[6]);
+		wc->status = IB_WC_GENERAL_ERR;
+		xsc_ib_err_state_qp(dev, *cur_qp, (*cur_qp)->state, XSC_QP_STATE_ERR);
+		break;
+	case XSC_OPCODE_RDMA_RSP_ERROR:
+		wq = &(*cur_qp)->rq;
+		idx = wq->tail & (wq->wqe_cnt - 1);
+		wc->wr_id = wq->wrid[idx];
+		wq->tail++;
+		atomic_dec(&wq->flush_wqe_cnt);
+		if ((*cur_qp)->ibqp.srq) {
+			xsrq = to_xsrq((*cur_qp)->ibqp.srq);
+			xsc_free_one_cache_srq_wr(xsrq, *cur_qp, wc->wr_id);
+		}
+		ibdev_err(&dev->ib_dev, "rsp error\n%08x %08x %08x %08x %08x %08x\n",
+			  p[0], p[1], p[2], p[3], p[5], p[6]);
+		wc->status = IB_WC_GENERAL_ERR;
+		xsc_ib_err_state_qp(dev, *cur_qp, (*cur_qp)->state, XSC_QP_STATE_ERR);
+		break;
+
+	default:
+		ibdev_err(&dev->ib_dev, "completion error\n%08x %08x %08x %08x %08x %08x\n",
+			  p[0], p[1], p[2], p[3], p[5], p[6]);
+		wc->status = IB_WC_GENERAL_ERR;
+		wc->wr_id = 0;
+		break;
+	}
+
+	return 0;
+}
+
+static inline void gen_flush_err_cqe(struct xsc_err_state_qp_node *err_node,
+				     struct ib_qp *ibqp, struct xsc_ib_wq *wq, u32 idx,
+				     struct ib_wc *wc)
+{
+	memset(wc, 0, sizeof(*wc));
+	if (err_node->is_sq) {
+		switch (wq->wr_opcode[idx]) {
+		case IB_WR_SEND:
+		case IB_WR_SEND_WITH_IMM:
+			wc->opcode = IB_WC_SEND;
+			break;
+		case IB_WR_RDMA_WRITE:
+		case IB_WR_RDMA_WRITE_WITH_IMM:
+			wc->opcode = IB_WC_RDMA_WRITE;
+			break;
+		case IB_WR_RDMA_READ:
+			wc->opcode = IB_WC_RDMA_READ;
+		}
+	} else {
+		wc->opcode = IB_WC_RECV;
+	}
+
+	wc->qp = ibqp;
+	wc->status = IB_WC_WR_FLUSH_ERR;
+	wc->vendor_err = XSC_ERR_CODE_FLUSH;
+	wc->wr_id = wq->wrid[idx];
+	wq->tail++;
+	atomic_dec(&wq->flush_wqe_cnt);
+	if (err_node->is_sq)
+		wq->need_flush[idx] = 0;
+}
+
+static inline int xsc_generate_flush_err_cqe(struct ib_cq *ibcq,
+					     int ne, int *npolled, struct ib_wc *wc)
+{
+	u32 qp_id = 0;
+	int flush_wqe_cnt = 0;
+	int sw_npolled = 0;
+	u32 idx = 0;
+	struct xsc_err_state_qp_node *err_qp_node;
+	struct xsc_core_qp *xqp;
+	struct xsc_ib_cq *cq = to_xcq(ibcq);
+	struct xsc_ib_wq *wq;
+	struct xsc_ib_dev *dev = to_xdev(cq->ibcq.device);
+
+	list_for_each_entry(err_qp_node, &cq->err_state_qp_list, entry) {
+		if (!err_qp_node)
+			break;
+
+		sw_npolled = 0;
+		qp_id = err_qp_node->qp_id;
+		xqp = __xsc_qp_lookup(dev->xdev, qp_id);
+		if (unlikely(!xqp)) {
+			ibdev_warn(&dev->ib_dev, "CQE@CQ %d for unknown QPN %d\n",
+				   cq->xcq.cqn, qp_id);
+			continue;
+		}
+		wq = err_qp_node->is_sq ? &(core_to_xqp(xqp)->sq) : &(core_to_xqp(xqp)->rq);
+		flush_wqe_cnt = atomic_read(&wq->flush_wqe_cnt);
+		ibdev_dbg(&dev->ib_dev, "is_sq %d, flush_wq_cnt %d, ne %d, npolled %d, qp_id %d, cqn %d\n",
+			  err_qp_node->is_sq, flush_wqe_cnt, ne, *npolled, qp_id, cq->xcq.cqn);
+
+		while (wq->tail != wq->head) {
+			idx = wq->tail & (wq->wqe_cnt - 1);
+			if (err_qp_node->is_sq && !wq->need_flush[idx]) {
+				wq->tail++;
+				continue;
+			} else {
+				gen_flush_err_cqe(err_qp_node, &(core_to_xqp(xqp)->ibqp), wq,
+						  idx, wc + *npolled + sw_npolled);
+				++sw_npolled;
+			}
+
+			if (sw_npolled == ne - *npolled)
+				break;
+		}
+		*npolled += sw_npolled;
+		if (*npolled == ne)
+			break;
+	}
+
+	return 0;
+}
+
+int xsc_ib_poll_cq(struct ib_cq *ibcq, int num_entries, struct ib_wc *wc)
+{
+	struct xsc_ib_cq *cq = to_xcq(ibcq);
+	struct xsc_core_cq *xcq = &cq->xcq;
+	struct xsc_ib_qp *cur_qp = NULL;
+	int npolled = 0;
+	int err = 0;
+	unsigned long flags;
+	u32 next_cid;
+
+	spin_lock_irqsave(&cq->lock, flags);
+	next_cid = xcq->cons_index;
+
+	for (npolled = 0; npolled < num_entries; npolled++) {
+		if (cq->invalid) {
+			err = -EINVAL;
+			break;
+		}
+		err = xsc_poll_one(cq, &cur_qp, wc + npolled);
+		if (err)
+			break;
+	}
+
+	if (err) {
+		if (npolled < num_entries && !(list_empty(&cq->err_state_qp_list)))
+			xsc_generate_flush_err_cqe(ibcq, num_entries, &npolled, wc);
+	}
+
+	if (next_cid != xcq->cons_index)
+		xsc_update_cq_ci(xcq->dev, xcq->cqn, xcq->cons_index);
+
+	spin_unlock_irqrestore(&cq->lock, flags);
+
+	return npolled;
+}
+
+static int xsc_cqe_is_empty(struct xsc_ib_cq *cq)
+{
+	struct xsc_cqe *cqe = get_sw_cqe(cq, cq->xcq.cons_index);
+
+	if (cq->invalid)
+		return 1;
+
+	if (!cqe)
+		return 1;
+
+	return 0;
+}
+
+int xsc_ib_arm_cq(struct ib_cq *ibcq, enum ib_cq_notify_flags flags)
+{
+	struct xsc_ib_cq *xcq = to_xcq(ibcq);
+	struct xsc_core_cq *cq = &xcq->xcq;
+	int ret = 0;
+	unsigned long irq_flags;
+	u8 solicited = 0;
+
+	spin_lock_irqsave(&xcq->lock, irq_flags);
+	if (flags & IB_CQ_NEXT_COMP)
+		solicited = 0;
+	else if (flags & IB_CQ_SOLICITED)
+		solicited = 1;/* arm next:0 arm solicited:1 */
+
+	if ((flags & IB_CQ_REPORT_MISSED_EVENTS) && (!xsc_cqe_is_empty(xcq))) {
+		ret = 1;
+		goto out;
+	}
+
+	xsc_arm_cq(cq->dev, cq->cqn, cq->cons_index, solicited);
+out:
+	spin_unlock_irqrestore(&xcq->lock, irq_flags);
+	return ret;
+}
+
+static int alloc_cq_buf(struct xsc_ib_dev *dev, struct xsc_ib_cq_buf *buf,
+			int nent, int cqe_size)
+{
+	int err;
+
+	err = xsc_buf_alloc(dev->xdev, nent * cqe_size,
+			    PAGE_SIZE, &buf->buf);
+	if (err)
+		return err;
+
+	buf->cqe_size = cqe_size;
+
+	return 0;
+}
+
+static void free_cq_buf(struct xsc_ib_dev *dev, struct xsc_ib_cq_buf *buf)
+{
+	xsc_buf_free(dev->xdev, &buf->buf);
+}
+
+static struct ib_umem *cq_umem_get(struct xsc_ib_dev *dev,
+				   struct ib_udata *udata, struct ib_ucontext *context,
+	int entries, struct xsc_ib_create_cq *ucmd)
+{
+	struct ib_umem *umem;
+
+	umem = ib_umem_get_va(&dev->ib_dev, ucmd->buf_addr,
+			      entries * ucmd->cqe_size,
+			      IB_ACCESS_LOCAL_WRITE);
+	return umem;
+}
+
+static int create_cq_user(struct xsc_ib_dev *dev, struct ib_udata *udata,
+			  struct ib_ucontext *context, struct xsc_ib_cq *cq,
+			  int entries, struct xsc_create_cq_ex_mbox_in **cqb,
+			  int *cqe_size, int *index, int *inlen)
+{
+	struct xsc_ib_create_cq ucmd;
+	int page_shift;
+	int npages;
+	int ncont;
+	int err;
+	int log_cq_sz;
+	int hw_npages;
+	struct ib_umem_dmabuf *umem_dmabuf;
+
+	if (ib_copy_from_udata(&ucmd, udata, sizeof(ucmd)))
+		return -EFAULT;
+
+	*cqe_size = ucmd.cqe_size;
+
+	if (ucmd.dmabuf_fd != -1) {
+		umem_dmabuf = ib_umem_dmabuf_get_pinned(&dev->ib_dev, 0, ucmd.dmabuf_sz,
+							ucmd.dmabuf_fd, IB_ACCESS_LOCAL_WRITE);
+
+		if (IS_ERR(umem_dmabuf)) {
+			ibdev_warn(&dev->ib_dev, "umem_dmabuf get failed by fd %d (%ld)\n",
+				   ucmd.dmabuf_fd, PTR_ERR(umem_dmabuf));
+			return -ENOMEM;
+		}
+
+		cq->buf.umem = &umem_dmabuf->umem;
+		ibdev_dbg(&dev->ib_dev, "get umem_dmabuf success by fd %d, umem_dmabuf_addr %p\n",
+			  ucmd.dmabuf_fd, umem_dmabuf);
+	} else {
+		cq->buf.umem = cq_umem_get(dev, udata, context, entries, &ucmd);
+	}
+
+	if (IS_ERR(cq->buf.umem)) {
+		err = PTR_ERR(cq->buf.umem);
+		return err;
+	}
+
+	xsc_ib_cont_pages(cq->buf.umem, ucmd.buf_addr, &npages, &page_shift,
+			  &ncont);
+	if (ncont != npages) {
+		ibdev_dbg(&dev->ib_dev, "bad page_shift:%d, ncont:%d\n", page_shift, ncont);
+		/* amber doesn't support compound pages */
+		page_shift = PAGE_SHIFT;
+		ncont = npages;
+		ibdev_dbg(&dev->ib_dev, "overwrite to page_shift:%d, ncont:%d\n",
+			  page_shift, ncont);
+	}
+	log_cq_sz = ilog2(entries);
+	hw_npages = DIV_ROUND_UP((1 << log_cq_sz) * (*cqe_size), PAGE_SIZE_4K);
+	ibdev_dbg(&dev->ib_dev, "addr 0x%llx, entries %d, npages %d, hw_npages %d\n",
+		  ucmd.buf_addr, entries, npages, hw_npages);
+
+	*inlen = sizeof(**cqb) + sizeof(*(*cqb)->pas) * hw_npages;
+	*cqb = xsc_vzalloc(*inlen);
+	if (!*cqb) {
+		err = -ENOMEM;
+		goto err_umem;
+	}
+	xsc_ib_populate_pas(dev, cq->buf.umem, page_shift, (*cqb)->pas, hw_npages, true);
+	(*cqb)->ctx_ex.ctx.pa_num = cpu_to_be16(hw_npages);
+	(*cqb)->ctx_ex.pa_num_ex = cpu_to_be32(hw_npages);
+
+	return 0;
+
+err_umem:
+	ib_umem_release(cq->buf.umem);
+	return err;
+}
+
+static void destroy_cq_user(struct xsc_ib_cq *cq, struct ib_udata *udata)
+{
+	ib_umem_release(cq->buf.umem);
+}
+
+static int create_cq_kernel(struct xsc_ib_dev *dev, struct xsc_ib_cq *cq,
+			    int entries, int cqe_size,
+			    struct xsc_create_cq_ex_mbox_in **cqb,
+			    int *index, int *inlen)
+{
+	int err;
+	int i = 0;
+	struct xsc_cqe *cqe = NULL;
+	int hw_npages;
+
+	cq->xcq.cqe_sz = cqe_size;
+
+	err = alloc_cq_buf(dev, &cq->buf, entries, cqe_size);
+	if (err)
+		return err;
+
+	for (i = 0; i < entries; i++) {
+		cqe = (struct xsc_cqe *)get_cqe(cq, i);
+		cqe->owner = 1;
+	}
+
+	hw_npages = DIV_ROUND_UP(entries * cqe_size, PAGE_SIZE_4K);
+	*inlen = sizeof(**cqb) + sizeof(*(*cqb)->pas) * hw_npages;
+	*cqb = xsc_vzalloc(*inlen);
+	if (!*cqb) {
+		err = -ENOMEM;
+		goto err_buf;
+	}
+	xsc_fill_page_array(&cq->buf.buf, (*cqb)->pas, hw_npages);
+	(*cqb)->ctx_ex.ctx.pa_num = cpu_to_be16(hw_npages);
+	(*cqb)->ctx_ex.pa_num_ex = cpu_to_be32(hw_npages);
+
+	return 0;
+
+err_buf:
+	free_cq_buf(dev, &cq->buf);
+	return err;
+}
+
+static void destroy_cq_kernel(struct xsc_ib_dev *dev, struct xsc_ib_cq *cq)
+{
+	free_cq_buf(dev, &cq->buf);
+}
+
+int xsc_ib_create_cq(struct ib_cq *ibcq,
+		     const struct ib_cq_init_attr *attr,
+		     struct uverbs_attr_bundle *attrs)
+{
+	struct ib_device *ibdev = ibcq->device;
+	struct ib_udata *udata = attrs ? &attrs->driver_udata : NULL;
+	int entries = attr->cqe;
+	int vector = attr->comp_vector;
+	struct xsc_create_cq_ex_mbox_in *cqb = NULL;
+	struct xsc_ib_dev *dev = to_xdev(ibdev);
+	struct xsc_ib_cq *cq;
+	int index;
+	int inlen = 0;
+	int cqe_size;
+	int irqn;
+	int err;
+	unsigned int eqn;
+
+	entries = roundup_pow_of_two(entries);
+
+	ibdev_dbg(&dev->ib_dev, "entries:%d, vector:%d, max_cqes:%d\n", entries, vector,
+		  dev->xdev->caps.max_cqes);
+
+	if (entries > dev->xdev->caps.max_cqes)
+		entries = dev->xdev->caps.max_cqes;
+	cq = to_xcq(ibcq);
+	cq->ibcq.cqe = entries;
+	mutex_init(&cq->resize_mutex);
+	spin_lock_init(&cq->lock);
+	cq->resize_buf = NULL;
+	cq->resize_umem = NULL;
+	cq->create_flags = attr->flags;
+	cq->invalid = 0;
+
+	if (udata) {
+		err = create_cq_user(dev, udata, NULL, cq, entries,
+				     &cqb, &cqe_size, &index, &inlen);
+		if (err)
+			goto err_create;
+	} else {
+		cqe_size = is_support_cqe64(dev->xdev) ?
+			sizeof(struct xsc_cqe64) : sizeof(struct xsc_cqe);
+		err = create_cq_kernel(dev, cq, entries, cqe_size, &cqb, &index, &inlen);
+		if (err)
+			goto err_create;
+	}
+
+	cq->cqe_size = cqe_size;
+	cqb->ctx_ex.ctx.log_cq_sz = ilog2(entries);
+	cqb->ctx_ex.ctx.glb_func_id = cpu_to_be16(dev->xdev->glb_func_id);
+
+	err = xsc_vector2eqn(dev->xdev, vector, &eqn, &irqn);
+	if (err)
+		goto err_cqb;
+
+	cqb->ctx_ex.ctx.eqn = eqn;
+	cqb->ctx_ex.ctx.eqn = cpu_to_be16(cqb->ctx_ex.ctx.eqn);
+	cqb->ctx_ex.page_shift = PAGE_SHIFT;
+	if (cq->create_flags & IB_UVERBS_CQ_FLAGS_IGNORE_OVERRUN)
+		cqb->ctx_ex.overrun_ignore = 1;
+
+	err = xsc_core_create_cq(dev->xdev, &cq->xcq, cqb, inlen);
+	if (err)
+		goto err_cqb;
+
+	ibdev_dbg(&dev->ib_dev, "succeeded to create cqn %d, vector=%d, cq_sz=%d, eqn=%d\n",
+		  cq->xcq.cqn, vector, entries, eqn);
+	cq->xcq.irqn = irqn;
+	cq->xcq.comp  = xsc_ib_cq_comp;
+	cq->xcq.event = xsc_ib_cq_event;
+
+	if (udata) {
+		if (ib_copy_to_udata(udata, &cq->xcq.cqn, sizeof(__u32))) {
+			err = -EFAULT;
+			goto err_cmd;
+		}
+	}
+
+	INIT_LIST_HEAD(&cq->send_qp_list);
+	INIT_LIST_HEAD(&cq->recv_qp_list);
+	INIT_LIST_HEAD(&cq->err_state_qp_list);
+	xsc_vfree(cqb);
+
+	return 0;
+
+err_cmd:
+	xsc_core_destroy_cq(dev->xdev, &cq->xcq);
+
+err_cqb:
+	xsc_vfree(cqb);
+	if (udata)
+		destroy_cq_user(cq, udata);
+	else
+		destroy_cq_kernel(dev, cq);
+
+err_create:
+
+	return err;
+}
+
+int xsc_ib_destroy_cq(struct ib_cq *cq, struct ib_udata *udata)
+{
+	struct xsc_ib_dev *dev = to_xdev(cq->device);
+	struct xsc_ib_cq *xcq = to_xcq(cq);
+	struct xsc_err_state_qp_node *tmp = NULL, *err_qp_node = NULL;
+
+	list_for_each_entry_safe(err_qp_node, tmp, &xcq->err_state_qp_list, entry) {
+		list_del(&err_qp_node->entry);
+		kfree(err_qp_node);
+	}
+
+	xsc_core_destroy_cq(dev->xdev, &xcq->xcq);
+	if (udata)
+		destroy_cq_user(xcq, udata);
+	else
+		destroy_cq_kernel(dev, xcq);
+
+	return 0;
+}
+
+static int is_equal_rsn(struct xsc_cqe *cqe, u32 rsn)
+{
+	u32 qpn = le32_to_cpu(cqe->qp_id);
+	return rsn == qpn;
+}
+
+void __xsc_ib_cq_clean(struct xsc_ib_cq *cq, u32 rsn)
+{
+	struct xsc_cqe *cqe, *dest;
+	u32 prod_index;
+	int nfreed = 0;
+	u8 owner_bit;
+
+	if (!cq)
+		return;
+
+	/* First we need to find the current producer index, so we
+	 * know where to start cleaning from.  It doesn't matter if HW
+	 * adds new entries after this loop -- the QP we're worried
+	 * about is already in RESET, so the new entries won't come
+	 * from our QP and therefore don't need to be checked.
+	 */
+	for (prod_index = cq->xcq.cons_index; get_sw_cqe(cq, prod_index); prod_index++)
+		if (prod_index == cq->xcq.cons_index + cq->ibcq.cqe)
+			break;
+
+	/* Now sweep backwards through the CQ, removing CQ entries
+	 * that match our QP by copying older entries on top of them.
+	 */
+	while ((int)(--prod_index) - (int)cq->xcq.cons_index >= 0) {
+		cqe = (struct xsc_cqe *)get_cqe(cq, prod_index & (cq->ibcq.cqe - 1));
+		if (is_equal_rsn(cqe, rsn)) {
+			++nfreed;
+		} else if (nfreed) {
+			dest = (struct xsc_cqe *)get_cqe(cq, (prod_index + nfreed) &
+							 (cq->ibcq.cqe - 1));
+			owner_bit = dest->owner & XSC_CQE_OWNER_MASK;
+			memcpy(dest, cqe, cq->xcq.cqe_sz);
+			dest->owner = owner_bit |
+				(dest->owner & ~XSC_CQE_OWNER_MASK);
+		}
+	}
+
+	if (nfreed) {
+		cq->xcq.cons_index += nfreed;
+		xsc_update_cq_ci(cq->xcq.dev, cq->xcq.cqn, cq->xcq.cons_index);
+	}
+}
+
+void xsc_ib_cq_clean(struct xsc_ib_cq *cq, u32 qpn)
+{
+	if (!cq)
+		return;
+
+	spin_lock_irq(&cq->lock);
+	__xsc_ib_cq_clean(cq, qpn);
+	spin_unlock_irq(&cq->lock);
+}
diff --git a/drivers/infiniband/hw/xsc/main.c b/drivers/infiniband/hw/xsc/main.c
index bd2c23b51..e9a9ba114 100644
--- a/drivers/infiniband/hw/xsc/main.c
+++ b/drivers/infiniband/hw/xsc/main.c
@@ -563,10 +563,17 @@ static void xsc_ib_dev_setting(struct xsc_ib_dev *dev)
 
 	dev->ib_dev.ops.alloc_pd		= xsc_ib_alloc_pd;
 	dev->ib_dev.ops.dealloc_pd		= xsc_ib_dealloc_pd;
+	dev->ib_dev.ops.create_ah		= xsc_ib_create_ah;
+	dev->ib_dev.ops.query_ah		= xsc_ib_query_ah;
+	dev->ib_dev.ops.destroy_ah		= xsc_ib_destroy_ah;
 
 	dev->ib_dev.ops.get_link_layer	= xsc_ib_port_link_layer;
 	dev->ib_dev.ops.get_netdev		= xsc_get_netdev;
 
+	dev->ib_dev.ops.create_cq		= xsc_ib_create_cq;
+	dev->ib_dev.ops.destroy_cq		= xsc_ib_destroy_cq;
+	dev->ib_dev.ops.poll_cq		= xsc_ib_poll_cq;
+	dev->ib_dev.ops.req_notify_cq	= xsc_ib_arm_cq;
 	dev->ib_dev.ops.get_dma_mr		= xsc_ib_get_dma_mr;
 	dev->ib_dev.ops.reg_user_mr		= xsc_ib_reg_user_mr; /* optional */
 	dev->ib_dev.ops.reg_user_mr_dmabuf	= xsc_ib_reg_user_mr_dmabuf;
@@ -575,9 +582,17 @@ static void xsc_ib_dev_setting(struct xsc_ib_dev *dev)
 	dev->ib_dev.ops.get_port_immutable		= xsc_port_immutable;
 
 	dev->ib_dev.ops.get_dev_fw_str	= xsc_ib_get_dev_fw_str;
+	dev->ib_dev.ops.create_srq		= xsc_ib_create_srq;
+	dev->ib_dev.ops.destroy_srq		= xsc_ib_destroy_srq;
+	dev->ib_dev.ops.query_srq		= xsc_ib_query_srq;
+	dev->ib_dev.ops.modify_srq		= xsc_ib_modify_srq;
+	dev->ib_dev.ops.post_srq_recv	= xsc_ib_post_srq_recv;
 
 	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_ucontext, xsc_ib_ucontext, ibucontext);
+	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_ah, xsc_ib_ah, ibah);
+	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_cq, xsc_ib_cq, ibcq);
 	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_pd, xsc_ib_pd, ibpd);
+	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_srq, xsc_ib_srq, ibsrq);
 }
 
 static void xsc_get_port_state(struct net_device *ndev, enum xsc_dev_event *ev)
diff --git a/drivers/infiniband/hw/xsc/qp.c b/drivers/infiniband/hw/xsc/qp.c
new file mode 100644
index 000000000..28c8a1246
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/qp.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <rdma/ib_umem.h>
+#include "xsc_ib.h"
+#include <rdma/xsc-abi.h>
+#include "common/xsc_hsi.h"
+#include <linux/crc32.h>
+#include <linux/dma-direct.h>
+#include <rdma/ib_addr.h>
+
+#ifndef ETH_P_IBOE
+#define ETH_P_IBOE	0x8915
+#endif
+
+#define MAD_QUEUE_DEPTH        128
+
+enum {
+	XSC_IB_CACHE_LINE_SIZE	= 64,
+};
+
+#define UDP_SPORT_MASK_EN		0x40000000
+#define UDP_SPORT_MASK_EN_OFFSET	30
+#define UDP_SPORT_MASK			0xffff
+#define UDP_SPORT_OFFSET		0
+
+static int is_qp1(enum ib_qp_type qp_type)
+{
+	return qp_type == IB_QPT_GSI;
+}
+
+static void *get_wqe(struct xsc_ib_qp *qp, int offset)
+{
+	return xsc_buf_offset(&qp->buf, offset);
+}
+
+static void *get_recv_wqe(struct xsc_ib_qp *qp, int n)
+{
+	return get_wqe(qp, qp->rq.offset + (n << qp->rq.wqe_shift));
+}
+
+static void *get_seg_wqe(void *first, int n)
+{
+	return first + (n << XSC_BASE_WQE_SHIFT);
+}
+
+#define MAX_QP1_SQ_HDR_SIZE_V2	512
+#define MAX_QP1_SQ_HDR_SIZE	86
+	/* Ethernet header	=  14 */
+	/* ib_grh		=  40 (provided by MAD) */
+	/* ib_bth + ib_deth	=  20 */
+	/* MAD			= 256 (provided by MAD) */
+	/* iCRC			=   4 */
+#define MAX_QP1_RQ_HDR_SIZE_V2	512
+
+static inline void xsc_ib_trigger_cq_comp_handler(struct ib_cq *send_cq, struct ib_cq *recv_cq)
+{
+	if (send_cq)
+		send_cq->comp_handler(send_cq, send_cq->cq_context);
+
+	if (recv_cq && send_cq != recv_cq)
+		recv_cq->comp_handler(recv_cq, recv_cq->cq_context);
+}
+
+int xsc_ib_err_state_qp(struct xsc_ib_dev *dev, struct xsc_ib_qp *qp,
+			enum xsc_qp_state cur_state, enum xsc_qp_state state)
+{
+	struct xsc_err_state_qp_node *tmp = NULL, *err_rq_node = NULL, *err_sq_node = NULL;
+	struct xsc_ib_cq *send_xcq, *recv_xcq;
+
+	ibdev_dbg(&dev->ib_dev, "modify qp: qpid %d, cur_qp_state %d, qp_state %d\n",
+		  qp->xqp.qpn, cur_state, state);
+	if (cur_state == XSC_QP_STATE_ERR && state != XSC_QP_STATE_ERR) {
+		if (!qp->ibqp.srq && qp->recv_cq) {
+			recv_xcq = to_xcq(qp->recv_cq);
+			list_for_each_entry_safe(err_rq_node, tmp,
+						 &recv_xcq->err_state_qp_list, entry) {
+				if (err_rq_node->qp_id == qp->xqp.qpn && !err_rq_node->is_sq) {
+					list_del(&err_rq_node->entry);
+					kfree(err_rq_node);
+				}
+			}
+		}
+
+		if (qp->send_cq) {
+			send_xcq = to_xcq(qp->send_cq);
+			list_for_each_entry_safe(err_sq_node, tmp,
+						 &send_xcq->err_state_qp_list, entry) {
+				if (err_sq_node->qp_id == qp->xqp.qpn && err_sq_node->is_sq) {
+					list_del(&err_sq_node->entry);
+					kfree(err_sq_node);
+				}
+			}
+		}
+		return 0;
+	}
+
+	if (cur_state != XSC_QP_STATE_ERR && state == XSC_QP_STATE_ERR &&
+	    !qp->has_trig_cq_comp_handle) {
+		if (!qp->ibqp.srq && qp->recv_cq) {
+			err_rq_node = kzalloc_obj(*err_rq_node, GFP_KERNEL);
+			if (!err_rq_node)
+				return -ENOMEM;
+			err_rq_node->qp_id = qp->xqp.qpn;
+			err_rq_node->is_sq = false;
+			recv_xcq = to_xcq(qp->recv_cq);
+			list_add_tail(&err_rq_node->entry, &recv_xcq->err_state_qp_list);
+		}
+
+		if (qp->send_cq) {
+			err_sq_node = kzalloc_obj(*err_sq_node, GFP_KERNEL);
+			if (!err_sq_node)
+				return -ENOMEM;
+			err_sq_node->qp_id = qp->xqp.qpn;
+			err_sq_node->is_sq = true;
+			send_xcq = to_xcq(qp->send_cq);
+			list_add_tail(&err_sq_node->entry, &send_xcq->err_state_qp_list);
+		}
+
+		xsc_ib_trigger_cq_comp_handler(qp->send_cq, qp->recv_cq);
+		qp->has_trig_cq_comp_handle = true;
+	}
+	return 0;
+}
+
+enum xsc_ib_qp_ex_attr_mask {
+	XSC_IB_QP_FLUSH		= 1 << 21,
+};
+
+static int xsc_wq_overflow(struct xsc_ib_wq *wq, int nreq, struct xsc_ib_cq *cq)
+{
+	unsigned int cur;
+
+	cur = wq->head - wq->tail;
+	if (likely(cur + nreq < wq->max_post))
+		return 0;
+
+	spin_lock(&cq->lock);
+	cur = wq->head - wq->tail;
+	spin_unlock(&cq->lock);
+
+	return cur + nreq >= wq->max_post;
+}
+
+#define BTH_QPN_MASK		(0x00ffffff)
+#define BTH_PSN_MASK		(0x00ffffff)
+
+/* Compute a partial ICRC for all the IB transport headers. */
+
+/* Routine for sending QP1 packets for RoCE V1 an V2 */
+/* TODO: sq hdr buf should be created dynamically for mult entry */
+
+int xsc_ib_post_recv(struct ib_qp *ibqp, const struct ib_recv_wr *wr,
+		     const struct ib_recv_wr **bad_wr)
+{
+	struct xsc_ib_qp *qp = to_xqp(ibqp);
+	struct xsc_ib_dev *dev = to_xdev(ibqp->device);
+	struct xsc_core_device *xdev = dev->xdev;
+	struct xsc_wqe_data_seg *recv_head;
+	struct xsc_wqe_data_seg *data_seg;
+	unsigned long flags = 0;
+	int err = 0;
+	u32 next_pid = 0;
+	int nreq = 0;
+	u32 idx;
+	int i;
+
+	spin_lock_irqsave(&qp->rq.lock, flags);
+
+	idx = qp->rq.head & (qp->rq.wqe_cnt - 1);
+
+	for (nreq = 0; wr; ++nreq, wr = wr->next) {
+		if (unlikely(xsc_wq_overflow(&qp->rq, nreq, to_xcq(qp->ibqp.recv_cq)))) {
+			ibdev_err(&dev->ib_dev, "recv work queue overflow\n");
+			err = -ENOMEM;
+			*bad_wr = wr;
+			goto out;
+		}
+
+		if (unlikely(wr->num_sge > qp->rq.max_gs)) {
+			ibdev_err(&dev->ib_dev, "max gs exceeded %d (max = %d)\n",
+				  wr->num_sge, qp->rq.max_gs);
+			err = -EINVAL;
+			*bad_wr = wr;
+			goto out;
+		}
+
+		if (unlikely(qp->post_recv_invalid)) {
+			ibdev_dbg(&dev->ib_dev, "qp %d has been draind, post recv invalid...\n",
+				  qp->xqp.qpn);
+			err = -EIO;
+			*bad_wr = wr;
+			goto out;
+		}
+
+		recv_head = get_recv_wqe(qp, idx);
+
+		for (i = 0; i < wr->num_sge; ++i) {
+			if (unlikely(!wr->sg_list[i].length))
+				continue;
+			data_seg = get_seg_wqe(recv_head, i);
+			data_seg->in_line = 0;
+			WR_LE_64(data_seg->va, wr->sg_list[i].addr);
+			WR_LE_32(data_seg->mkey, wr->sg_list[i].lkey);
+			if (is_qp1(qp->xqp.qp_type))
+				WR_LE_32(data_seg->seg_len, xdev->caps.rx_pkt_len_max);
+			else
+				WR_LE_32(data_seg->seg_len, wr->sg_list[i].length);
+		}
+
+		qp->rq.wrid[idx] = wr->wr_id;
+
+		idx = (idx + 1) & (qp->rq.wqe_cnt - 1);
+		atomic_inc(&qp->rq.flush_wqe_cnt);
+	}
+
+out:
+	if (likely(nreq)) {
+		qp->rq.head += nreq;
+		next_pid = qp->rq.head << (qp->rq.wqe_shift - XSC_BASE_WQE_SHIFT);
+
+		xsc_update_rx_db(xdev, qp->doorbell_qpn, next_pid);
+	}
+
+	spin_unlock_irqrestore(&qp->rq.lock, flags);
+
+	return err;
+}
+
+struct xsc_ib_drain_cqe {
+	struct ib_cqe cqe;
+	struct completion done;
+	int qpn;
+};
+
diff --git a/drivers/infiniband/hw/xsc/srq.c b/drivers/infiniband/hw/xsc/srq.c
new file mode 100644
index 000000000..911820fa1
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/srq.c
@@ -0,0 +1,426 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/slab.h>
+#include <rdma/ib_umem.h>
+#include <rdma/ib_user_verbs.h>
+#include "xsc_ib.h"
+#include "common/srq.h"
+#include <rdma/xsc-abi.h>
+#include <linux/list_sort.h>
+
+static int xsc_init_cache_wr(struct ib_srq_init_attr *attr, struct xsc_ib_srq *srq)
+{
+	int srqn = srq->xsrq.srqn;
+	struct xsc_srq_cache_wr *cache_wr = srq->xsrq.cache_wr;
+
+	if (srqn >= XSC_MAX_SRQ_NUM)
+		return -EINVAL;
+
+	memset(&cache_wr[srqn], 0, sizeof(struct xsc_srq_cache_wr));
+	cache_wr[srqn].max_wr_num = attr->attr.max_wr;
+	return 0;
+}
+
+void xsc_restore_cache_srq_wrs(struct xsc_ib_srq *srq, struct xsc_ib_qp *qp)
+{
+	int srqn = srq->xsrq.srqn;
+	struct xsc_srq_cache_wr *cache_wr = srq->xsrq.cache_wr;
+
+	if (qp->srq_tail_wr)
+		qp->srq_tail_wr->next = cache_wr[srqn].head_wr;
+	if (cache_wr[srqn].head_wr && qp->srq_head_wr)
+		cache_wr[srqn].head_wr = qp->srq_head_wr;
+}
+
+void xsc_free_one_cache_srq_wr(struct xsc_ib_srq *srq, struct xsc_ib_qp *qp, u64 wr_id)
+{
+	struct ib_recv_wr *tmp_wr, *prev_wr = NULL;
+	int srqn = srq->xsrq.srqn;
+	struct xsc_srq_cache_wr *cache_wr = srq->xsrq.cache_wr;
+	unsigned long flags;
+
+	spin_lock_irqsave(&srq->lock, flags);
+	tmp_wr = qp->srq_head_wr;
+	for (; tmp_wr; tmp_wr = tmp_wr->next) {
+		if (tmp_wr->wr_id == wr_id) {
+			if (prev_wr)
+				prev_wr->next = tmp_wr->next;
+			else
+				qp->srq_head_wr = tmp_wr->next;
+
+			kfree(tmp_wr->sg_list);
+			kfree(tmp_wr);
+			break;
+		}
+		prev_wr = tmp_wr;
+	}
+	cache_wr[srqn].wr_num--;
+	spin_unlock_irqrestore(&srq->lock, flags);
+}
+
+void xsc_free_all_cache_wrs(struct ib_recv_wr *wr)
+{
+	struct ib_recv_wr *prev_wr = NULL;
+
+	for (; wr; wr = wr->next) {
+		kfree(prev_wr);
+		kfree(wr->sg_list);
+		prev_wr = wr;
+	}
+	kfree(prev_wr);
+}
+
+static void xsc_destroy_cache_wr(struct xsc_ib_srq *srq)
+{
+	int srqn = srq->xsrq.srqn;
+	struct xsc_srq_cache_wr *cache_wr = srq->xsrq.cache_wr;
+
+	xsc_free_all_cache_wrs(cache_wr[srqn].head_wr);
+	memset(&cache_wr[srqn], 0, sizeof(struct xsc_srq_cache_wr));
+}
+
+static void xsc_ib_srq_event(struct xsc_core_srq *srq, enum xsc_event type)
+{
+	struct ib_event event;
+	struct ib_srq *ibsrq = &core_to_xsrq(srq)->ibsrq;
+
+	if (ibsrq->event_handler) {
+		event.device      = ibsrq->device;
+		event.element.srq = ibsrq;
+		switch (type) {
+		default:
+			pr_warn("xsc_ib: Unexpected event type %d on SRQ %06x\n",
+				type, srq->srqn);
+			return;
+		}
+
+		ibsrq->event_handler(&event, ibsrq->srq_context);
+	}
+}
+
+static int create_srq_user(struct ib_pd *pd, struct xsc_ib_srq *srq,
+			   struct ib_srq_init_attr *init_attr,
+			   struct ib_udata *udata, int buf_size)
+{
+	struct xsc_ib_dev *dev = to_xdev(pd->device);
+	struct xsc_ib_create_srq ucmd = {};
+	size_t ucmdlen;
+
+	ucmdlen = min(udata->inlen, sizeof(ucmd));
+
+	if (ib_copy_from_udata(&ucmd, udata, ucmdlen)) {
+		ibdev_dbg(&dev->ib_dev, "failed copy udata\n");
+		return -EFAULT;
+	}
+
+	if (ucmd.reserved0 || ucmd.reserved1)
+		return -EINVAL;
+
+	if (udata->inlen > sizeof(ucmd) &&
+	    !ib_is_udata_cleared(udata, sizeof(ucmd),
+				 udata->inlen - sizeof(ucmd)))
+		return -EINVAL;
+
+	srq->create_type = XSC_SRQ_USER;
+
+	return 0;
+}
+
+static int create_srq_kernel(struct xsc_ib_dev *dev, struct xsc_ib_srq *srq,
+			     struct ib_srq_init_attr *init_attr, int buf_size)
+{
+	srq->create_type = XSC_SRQ_KERNEL;
+	return 0;
+}
+
+static void destroy_srq_user(struct xsc_ib_srq *srq, struct ib_udata *udata)
+{
+}
+
+static void destroy_srq_kernel(struct xsc_ib_dev *dev, struct xsc_ib_srq *srq)
+{
+}
+
+int xsc_ib_create_srq(struct ib_srq *ib_srq,
+		      struct ib_srq_init_attr *init_attr,
+		      struct ib_udata *udata)
+{
+	struct xsc_ib_dev *dev;
+	struct xsc_ib_srq *srq;
+	int err;
+	u32 max_srq_wqes = 1 << XSC_MAX_SRQ_LOG_SIZE;
+	struct ib_pd *pd = ib_srq->pd;
+
+	if (pd) {
+		dev = to_xdev(pd->device);
+	} else {
+		pr_warn("%s: no PD for SRQ\n", __func__);
+		err = -EINVAL;
+		goto err_ret;
+	}
+
+	if (init_attr->srq_type != IB_SRQT_BASIC) {
+		err = -EOPNOTSUPP;
+		goto err_ret;
+	}
+
+	/* Sanity check SRQ size before proceeding */
+	if (init_attr->attr.max_wr >= max_srq_wqes) {
+		ibdev_dbg(&dev->ib_dev, "max_wr %d, cap %d\n",
+			  init_attr->attr.max_wr,
+			   max_srq_wqes);
+		err = -EINVAL;
+		goto err_ret;
+	}
+
+	srq = to_xsrq(ib_srq);
+
+	mutex_init(&srq->mutex);
+	spin_lock_init(&srq->lock);
+	srq->xsrq.max    = roundup_pow_of_two(init_attr->attr.max_wr + 1);
+	srq->xsrq.max_gs = init_attr->attr.max_sge;
+
+	if (udata)
+		err = create_srq_user(pd, srq, NULL, udata, 0);
+	else
+		err = create_srq_kernel(dev, srq, init_attr, 0);
+
+	if (err) {
+		ibdev_dbg(&dev->ib_dev, "create srq rsc failed, ret %d", err);
+		goto err_create_rsc;
+	}
+
+	err = xsc_core_create_srq(dev->xdev, &srq->xsrq);
+	if (err) {
+		ibdev_err(&dev->ib_dev, "create srq failed, err=%d\n", err);
+		goto err_create;
+	}
+	ibdev_dbg(&dev->ib_dev, "create SRQ with srqn 0x%x, max_wr %d\n",
+		  srq->xsrq.srqn, init_attr->attr.max_wr);
+
+	srq->xsrq.event = xsc_ib_srq_event;
+	srq->ibsrq.ext.xrc.srq_num = srq->xsrq.srqn;
+
+	if (udata) {
+		struct xsc_ib_create_srq_resp resp = {
+			.srqn = srq->xsrq.srqn,
+		};
+
+		if (ib_copy_to_udata(udata, &resp,
+				     min(udata->outlen, sizeof(resp)))) {
+			ibdev_dbg(&dev->ib_dev, "copy to user failed\n");
+			err = -EFAULT;
+			goto err_rsp;
+		}
+	} else {
+		err = xsc_init_cache_wr(init_attr, srq);
+		if (err)
+			goto err_rsp;
+		INIT_LIST_HEAD(&srq->xsrq.srq_qp_list);
+	}
+
+	init_attr->attr.max_wr = srq->xsrq.max - 1;
+
+	return 0;
+err_rsp:
+	xsc_core_destroy_srq(dev->xdev, &srq->xsrq);
+err_create:
+	if (udata)
+		destroy_srq_user(srq, udata);
+	else
+		destroy_srq_kernel(dev, srq);
+err_create_rsc:
+err_ret:
+	return err;
+}
+
+int xsc_ib_modify_srq(struct ib_srq *ibsrq, struct ib_srq_attr *attr,
+		      enum ib_srq_attr_mask attr_mask, struct ib_udata *udata)
+{
+	/* We don't support resizing SRQs yet */
+	if (attr_mask & IB_SRQ_MAX_WR)
+		return -EINVAL;
+
+	if (attr_mask & IB_SRQ_LIMIT)
+		return -EOPNOTSUPP;
+
+	return 0;
+}
+
+int xsc_ib_query_srq(struct ib_srq *ibsrq, struct ib_srq_attr *srq_attr)
+{
+	struct xsc_ib_srq *srq = to_xsrq(ibsrq);
+
+	srq_attr->srq_limit = 0;
+	srq_attr->max_wr    = srq->xsrq.max - 1;
+	srq_attr->max_sge   = srq->xsrq.max_gs;
+
+	return 0;
+}
+
+int xsc_ib_destroy_srq(struct ib_srq *srq, struct ib_udata *udata)
+{
+	struct xsc_ib_srq *xsrq = to_xsrq(srq);
+	struct xsc_ib_dev *dev = to_xdev(srq->device);
+
+	xsc_core_destroy_srq(dev->xdev, &xsrq->xsrq);
+	if (xsrq->create_type == XSC_SRQ_USER) {
+		destroy_srq_user(xsrq, NULL);
+	} else {
+		xsc_destroy_cache_wr(xsrq);
+		destroy_srq_kernel(dev, xsrq);
+	}
+
+	ibdev_dbg(&dev->ib_dev, "destroy srq %d successfully.\n", xsrq->xsrq.srqn);
+	return 0;
+}
+
+static int cmp_flush_wqe_cnt(void *priv,
+			     const struct list_head *a,
+			     const struct list_head *b)
+{
+	const struct xsc_srq_qp_node *na =
+		list_entry(a, struct xsc_srq_qp_node, entry);
+	const struct xsc_srq_qp_node *nb =
+		list_entry(b, struct xsc_srq_qp_node, entry);
+
+	int fa = atomic_read(&(to_xqp(na->ibqp)->rq.flush_wqe_cnt));
+	int fb = atomic_read(&(to_xqp(nb->ibqp)->rq.flush_wqe_cnt));
+
+	return fa - fb;
+}
+
+static int xsc_cache_srq_wr(struct xsc_ib_srq *srq, const struct ib_recv_wr *wr, int *nreq)
+{
+	int i, n;
+	int srqn = srq->xsrq.srqn;
+	struct xsc_srq_cache_wr *cache_wr = srq->xsrq.cache_wr;
+	struct ib_recv_wr *head_wr = NULL, *new_wr = NULL, *prev_wr = NULL;
+
+	for (n = 0; wr; wr = wr->next, n++) {
+		new_wr = kcalloc(1, sizeof(struct ib_recv_wr), GFP_KERNEL);
+		if (prev_wr)
+			prev_wr->next = new_wr;
+		else
+			head_wr = new_wr;
+		new_wr->wr_id = wr->wr_id;
+		new_wr->sg_list = kcalloc(wr->num_sge, sizeof(struct ib_sge), GFP_KERNEL);
+		for (i = 0; i < wr->num_sge; i++)
+			memcpy(&new_wr->sg_list[i], &wr->sg_list[i], sizeof(struct ib_sge));
+		new_wr->num_sge = wr->num_sge;
+		prev_wr = new_wr;
+	};
+
+	if (!cache_wr[srqn].head_wr)
+		cache_wr[srqn].head_wr = head_wr;
+	else
+		cache_wr[srqn].tail_wr->next = head_wr;
+
+	cache_wr[srqn].tail_wr = new_wr;
+	cache_wr[srqn].wr_num += n;
+	*nreq = n;
+
+	if (cache_wr[srqn].wr_num > cache_wr[srqn].max_wr_num)
+		return -1;
+	else
+		return 0;
+}
+
+void xsc_post_cache_wrs(struct xsc_ib_srq *srq, struct xsc_ib_qp *qp, int wr_num, bool init)
+{
+	int srqn = srq->xsrq.srqn;
+	struct xsc_srq_cache_wr *cache_wr = srq->xsrq.cache_wr;
+	int err = 0;
+	int i;
+	const struct ib_recv_wr *tmp_wr;
+	const struct ib_recv_wr *bad_wr;
+	struct ib_recv_wr *wr_head, *next_wr;
+
+	if (!cache_wr[srqn].head_wr)
+		return;
+
+	wr_head = cache_wr[srqn].head_wr;
+	next_wr = cache_wr[srqn].head_wr;
+
+	if (init)
+		qp->srq_head_wr = wr_head;
+	else
+		qp->srq_tail_wr->next = wr_head;
+
+	qp->srq_tail_wr = wr_head;
+	for (i = 0; next_wr && i < (wr_num - 1); i++) {
+		next_wr = next_wr->next;
+		if (next_wr)
+			qp->srq_tail_wr = next_wr;
+	}
+
+	cache_wr[srqn].head_wr = qp->srq_tail_wr->next;
+	qp->srq_tail_wr->next = NULL;
+	tmp_wr = wr_head;
+	err = xsc_ib_post_recv(&qp->ibqp, tmp_wr, &bad_wr);
+	if (err) {
+		qp->srq_tail_wr->next = cache_wr[srqn].head_wr;
+		qp->srq_tail_wr = NULL;
+		for (; wr_head && (bad_wr->wr_id != wr_head->wr_id); wr_head = wr_head->next)
+			qp->srq_tail_wr = wr_head;
+
+		if (!qp->srq_tail_wr) {
+			cache_wr[srqn].head_wr = wr_head;
+			qp->srq_head_wr = NULL;
+		} else {
+			cache_wr[srq->xsrq.srqn].head_wr = qp->srq_tail_wr->next;
+			qp->srq_tail_wr->next = NULL;
+		}
+	}
+}
+
+int xsc_ib_post_srq_recv(struct ib_srq *ibsrq, const struct ib_recv_wr *wr,
+			 const struct ib_recv_wr **bad_wr)
+{
+	struct xsc_ib_srq *srq = to_xsrq(ibsrq);
+	struct xsc_ib_dev *dev = to_xdev(ibsrq->device);
+	struct xsc_srq_qp_node *qp_node;
+	struct xsc_ib_qp *qp;
+	unsigned long flags;
+	int err = 0;
+	int flush_wqe_cnt = 0;
+	int nreq = 0;
+	int wr_num = 0;
+
+	spin_lock_irqsave(&srq->lock, flags);
+
+	err = xsc_cache_srq_wr(srq, wr, &nreq);
+	if (err) {
+		ibdev_err(&dev->ib_dev, "No more space to cache SRQ WRs...");
+		err = -EINVAL;
+		*bad_wr = wr;
+		goto out;
+	}
+
+	if (list_empty(&srq->xsrq.srq_qp_list))
+		goto out;
+
+	list_sort(NULL, &srq->xsrq.srq_qp_list, cmp_flush_wqe_cnt);
+
+	list_for_each_entry(qp_node, &srq->xsrq.srq_qp_list, entry) {
+		if (!qp_node || nreq <= 0)
+			break;
+
+		qp = to_xqp(qp_node->ibqp);
+		flush_wqe_cnt = atomic_read(&qp->rq.flush_wqe_cnt);
+		wr_num = min_t(int, nreq, (XSC_SRQ_MIN_WR_NUM - flush_wqe_cnt));
+		nreq -= wr_num;
+		ibdev_dbg(&dev->ib_dev, "srq_qp_list qp node qp %d, flush cnt %d, nreq %d, wr_num %d\n",
+			  qp_node->qp_id, flush_wqe_cnt, nreq, wr_num);
+		xsc_post_cache_wrs(srq, qp, wr_num, false);
+	}
+
+out:
+	spin_unlock_irqrestore(&srq->lock, flags);
+
+	return err;
+}
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
index 065d52ebf..fe0f9d175 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
@@ -6,4 +6,5 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
 
-xsc_pci-y := main.o cmdq.o hw.o qp.o cq.o pd.o mr.o link.o alloc.o eq.o pci_irq.o adev.o vport.o hal/andes_impl.o
+xsc_pci-y := main.o cmdq.o hw.o qp.o cq.o pd.o mr.o srq.o link.o alloc.o eq.o pci_irq.o adev.o vport.o \
+		hal/andes_impl.o
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/main.c b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
index c4416a78b..c2a9f4863 100644
--- a/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/main.c
@@ -8,6 +8,7 @@
 #include "hw.h"
 #include "qp.h"
 #include "cq.h"
+#include "srq.h"
 #include "eq.h"
 #include "pci_irq.h"
 #include "adev.h"
@@ -120,11 +121,20 @@ static int xsc_dev_res_init(struct xsc_core_device *xdev)
 	mutex_init(&dev_res->alloc_mutex);
 	spin_lock_init(&dev_res->mkey_lock);
 
+	if (xsc_create_srq_res(xdev))
+		goto err_free;
+
 	return 0;
+
+err_free:
+	kfree(dev_res);
+	xdev->dev_res = NULL;
+	return -ENOMEM;
 }
 
 static void xsc_dev_res_cleanup(struct xsc_core_device *xdev)
 {
+	xsc_destroy_srq_res(xdev);
 	kfree(xdev->dev_res);
 }
 
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/srq.c b/drivers/net/ethernet/yunsilicon/xsc/pci/srq.c
new file mode 100644
index 000000000..c2f9c1a88
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/srq.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+#include "common/xsc_core.h"
+#include "common/srq.h"
+#include "srq.h"
+
+int xsc_create_srq_res(struct xsc_core_device *xdev)
+{
+	struct xsc_dev_resource *dev_res = xdev->dev_res;
+	u32 max_srq_num = XSC_MAX_SRQ_NUM;
+
+	dev_res->srq_tbl = bitmap_zalloc(max_srq_num, GFP_KERNEL);
+	if (!dev_res->srq_tbl)
+		return -ENOMEM;
+
+	dev_res->srq_cache_wr = vzalloc(max_srq_num *
+					sizeof(*dev_res->srq_cache_wr));
+	if (!dev_res->srq_cache_wr) {
+		bitmap_free(dev_res->srq_tbl);
+		dev_res->srq_tbl = NULL;
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&dev_res->srq_lock);
+	dev_res->max_srq_num = max_srq_num;
+
+	return 0;
+}
+
+void xsc_destroy_srq_res(struct xsc_core_device *xdev)
+{
+	struct xsc_dev_resource *dev_res = xdev->dev_res;
+
+	vfree(dev_res->srq_cache_wr);
+	dev_res->srq_cache_wr = NULL;
+	bitmap_free(dev_res->srq_tbl);
+	dev_res->srq_tbl = NULL;
+	dev_res->max_srq_num = 0;
+}
+
+int xsc_core_create_srq(struct xsc_core_device *xdev, struct xsc_core_srq *srq)
+{
+	struct xsc_dev_resource *dev_res = xdev->dev_res;
+	u32 srqn;
+
+	spin_lock(&dev_res->srq_lock);
+	srqn = find_first_zero_bit(dev_res->srq_tbl, dev_res->max_srq_num);
+	if (srqn == dev_res->max_srq_num) {
+		spin_unlock(&dev_res->srq_lock);
+		pci_err(xdev->pdev, "no free srq number\n");
+		return -ENOMEM;
+	}
+	set_bit(srqn, dev_res->srq_tbl);
+	spin_unlock(&dev_res->srq_lock);
+
+	srq->srqn = srqn;
+	/* array base: callers index it by srqn */
+	srq->cache_wr = dev_res->srq_cache_wr;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_create_srq);
+
+int xsc_core_destroy_srq(struct xsc_core_device *xdev, struct xsc_core_srq *srq)
+{
+	struct xsc_dev_resource *dev_res = xdev->dev_res;
+
+	if (srq->srqn >= dev_res->max_srq_num) {
+		pci_err(xdev->pdev, "invalid srqn %u\n", srq->srqn);
+		return -EINVAL;
+	}
+
+	spin_lock(&dev_res->srq_lock);
+	clear_bit(srq->srqn, dev_res->srq_tbl);
+	spin_unlock(&dev_res->srq_lock);
+
+	/*
+	 * The caller still owns the cached recv WRs in cache_wr[srqn] and
+	 * releases them right after this call, so the slot must be left
+	 * untouched here.
+	 */
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsc_core_destroy_srq);
diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/srq.h b/drivers/net/ethernet/yunsilicon/xsc/pci/srq.h
new file mode 100644
index 000000000..a70faf9e7
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/srq.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __XSC_SRQ_H
+#define __XSC_SRQ_H
+
+#include "common/xsc_core.h"
+#include "common/srq.h"
+
+/* return 0 on success, negative errno otherwise */
+int xsc_create_srq_res(struct xsc_core_device *xdev);
+void xsc_destroy_srq_res(struct xsc_core_device *xdev);
+
+#endif /* __XSC_SRQ_H */

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

* [PATCH 12/15] yunsilicon: add RDMA QP verbs
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (10 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 11/15] yunsilicon: add RDMA CQ/SRQ/AH Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 13/15] yunsilicon: add the RDMA send path Xin Tian
                   ` (3 subsequent siblings)
  15 siblings, 1 reply; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the queue pair control path: create, destroy, modify and query, for
both kernel and user queue pairs, including the address path setup for
RoCE.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 drivers/infiniband/hw/xsc/main.c |    6 +
 drivers/infiniband/hw/xsc/qp.c   | 1321 ++++++++++++++++++++++++++++++
 2 files changed, 1327 insertions(+)

diff --git a/drivers/infiniband/hw/xsc/main.c b/drivers/infiniband/hw/xsc/main.c
index e9a9ba114..c4f306318 100644
--- a/drivers/infiniband/hw/xsc/main.c
+++ b/drivers/infiniband/hw/xsc/main.c
@@ -570,6 +570,11 @@ static void xsc_ib_dev_setting(struct xsc_ib_dev *dev)
 	dev->ib_dev.ops.get_link_layer	= xsc_ib_port_link_layer;
 	dev->ib_dev.ops.get_netdev		= xsc_get_netdev;
 
+	dev->ib_dev.ops.create_qp		= xsc_ib_create_qp;
+	dev->ib_dev.ops.modify_qp		= xsc_ib_modify_qp;
+	dev->ib_dev.ops.query_qp		= xsc_ib_query_qp;
+	dev->ib_dev.ops.destroy_qp		= xsc_ib_destroy_qp;
+	dev->ib_dev.ops.post_recv		= xsc_ib_post_recv;
 	dev->ib_dev.ops.create_cq		= xsc_ib_create_cq;
 	dev->ib_dev.ops.destroy_cq		= xsc_ib_destroy_cq;
 	dev->ib_dev.ops.poll_cq		= xsc_ib_poll_cq;
@@ -592,6 +597,7 @@ static void xsc_ib_dev_setting(struct xsc_ib_dev *dev)
 	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_ah, xsc_ib_ah, ibah);
 	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_cq, xsc_ib_cq, ibcq);
 	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_pd, xsc_ib_pd, ibpd);
+	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_qp, xsc_ib_qp, ibqp);
 	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_srq, xsc_ib_srq, ibsrq);
 }
 
diff --git a/drivers/infiniband/hw/xsc/qp.c b/drivers/infiniband/hw/xsc/qp.c
index 28c8a1246..6a83adc9c 100644
--- a/drivers/infiniband/hw/xsc/qp.c
+++ b/drivers/infiniband/hw/xsc/qp.c
@@ -28,11 +28,21 @@ enum {
 #define UDP_SPORT_MASK			0xffff
 #define UDP_SPORT_OFFSET		0
 
+static int is_qp0(enum ib_qp_type qp_type)
+{
+	return qp_type == IB_QPT_SMI;
+}
+
 static int is_qp1(enum ib_qp_type qp_type)
 {
 	return qp_type == IB_QPT_GSI;
 }
 
+static int is_sqp(enum ib_qp_type qp_type)
+{
+	return is_qp0(qp_type) || is_qp1(qp_type);
+}
+
 static void *get_wqe(struct xsc_ib_qp *qp, int offset)
 {
 	return xsc_buf_offset(&qp->buf, offset);
@@ -48,6 +58,258 @@ static void *get_seg_wqe(void *first, int n)
 	return first + (n << XSC_BASE_WQE_SHIFT);
 }
 
+static void xsc_ib_qp_event(struct xsc_core_qp *qp, int type)
+{
+	struct ib_qp *ibqp = &core_to_xqp(qp)->ibqp;
+	struct ib_event event;
+
+	if (ibqp->event_handler) {
+		event.device     = ibqp->device;
+		event.element.qp = ibqp;
+		switch (type) {
+		case XSC_EVENT_TYPE_WQ_CATAS_ERROR:
+			event.event = IB_EVENT_QP_FATAL;
+			break;
+		case XSC_EVENT_TYPE_WQ_INVAL_REQ_ERROR:
+			event.event = IB_EVENT_QP_REQ_ERR;
+			break;
+		case XSC_EVENT_TYPE_WQ_ACCESS_ERROR:
+			event.event = IB_EVENT_QP_ACCESS_ERR;
+			break;
+		default:
+			pr_warn("xsc_ib: Unexpected event type %d on QP %06x\n", type, qp->qpn);
+			return;
+		}
+
+		ibqp->event_handler(&event, ibqp->qp_context);
+		pr_info("xsc_ib: xsc async event 0x%x, ib event %d\n", type, event.event);
+	}
+}
+
+static int set_rq_size(struct xsc_ib_dev *dev, struct ib_qp_init_attr *init_attr,
+		       int has_rq, struct xsc_ib_qp *qp, struct xsc_ib_create_qp *ucmd)
+{
+	bool check_res = false;
+	u32 wqe_cnt = init_attr->cap.max_recv_wr ?
+		roundup_pow_of_two(init_attr->cap.max_recv_wr) : 0;
+
+	if (init_attr->srq)
+		wqe_cnt = to_xsrq(init_attr->srq)->xsrq.max;
+
+	/* Sanity check RQ size before proceeding */
+	check_res = xsc_check_max_qp_depth(dev->xdev, &wqe_cnt, dev->xdev->caps.max_wqes);
+	if (check_res) {
+		ibdev_err(&dev->ib_dev, "max_recv_wr:%d exceed max rq depth\n",
+			  init_attr->cap.max_recv_wr);
+		return -EINVAL;
+	}
+
+	if (!has_rq) {
+		qp->rq.max_gs = 0;
+		qp->rq.wqe_cnt = 0;
+		qp->rq.wqe_shift = 0;
+	} else {
+		if (ucmd) {
+			qp->rq.wqe_cnt = ucmd->rq_wqe_count;
+			qp->rq.wqe_shift = ucmd->rq_wqe_shift;
+			qp->rq.max_gs = 1;
+			qp->rq.max_post = qp->rq.wqe_cnt;
+		} else {
+			qp->rq.wqe_cnt = wqe_cnt;
+			if (qp->xqp.qp_type == IB_QPT_GSI)
+				qp->rq.wqe_shift = ilog2(xsc_get_recv_ds_num(dev->xdev))
+					+ XSC_RECV_WQE_SHIFT;
+			else
+				qp->rq.wqe_shift = dev->xdev->caps.recv_wqe_shift;
+			qp->rq.max_gs = dev->xdev->caps.recv_ds_num;
+			qp->rq.max_post = qp->rq.wqe_cnt;
+		}
+	}
+
+	return 0;
+}
+
+static int calc_sq_size(struct xsc_ib_dev *dev, struct ib_qp_init_attr *attr,
+			struct xsc_ib_qp *qp)
+{
+	int wqe_size;
+	int wq_size;
+	bool check_res = false;
+
+	if (!attr->cap.max_send_wr) {
+		ibdev_err(&dev->ib_dev, "invalid max_send_wr:%d\n", attr->cap.max_send_wr);
+		return -1;
+	}
+
+	wqe_size = 1 << dev->xdev->caps.send_wqe_shift;
+	qp->max_inline_data = (dev->xdev->caps.send_ds_num - 2) * sizeof(struct xsc_wqe_data_seg);
+	attr->cap.max_inline_data = qp->max_inline_data;
+
+	attr->cap.max_send_wr = min_t(u32, attr->cap.max_send_wr, dev->xdev->caps.max_wqes);
+	qp->sq.wqe_cnt = roundup_pow_of_two(attr->cap.max_send_wr);
+
+	check_res = xsc_check_max_qp_depth(dev->xdev, &qp->sq.wqe_cnt, dev->xdev->caps.max_wqes);
+	if (check_res) {
+		ibdev_err(&dev->ib_dev, "max_send_wr:%d exceed max sq depth\n",
+			  attr->cap.max_send_wr);
+		return -EINVAL;
+	}
+	qp->sq.ds_cnt = qp->sq.wqe_cnt << (dev->xdev->caps.send_wqe_shift - XSC_BASE_WQE_SHIFT);
+	wq_size = qp->sq.wqe_cnt * wqe_size;
+	qp->sq.wqe_shift = ilog2(wqe_size);
+	qp->sq.max_gs = dev->xdev->caps.send_ds_num - XSC_CTRL_SEG_NUM - XSC_RADDR_SEG_NUM;
+	qp->sq.max_post = qp->sq.wqe_cnt;
+
+	return wq_size;
+}
+
+static int qp_has_rq(struct ib_qp_init_attr *attr)
+{
+	if (!attr->srq && !attr->cap.max_recv_wr)
+		return 0;
+
+	return 1;
+}
+
+static enum xsc_qp_state to_xsc_state(enum ib_qp_state state)
+{
+	switch (state) {
+	case IB_QPS_RESET:	return XSC_QP_STATE_RST;
+	case IB_QPS_INIT:	return XSC_QP_STATE_INIT;
+	case IB_QPS_RTR:	return XSC_QP_STATE_RTR;
+	case IB_QPS_RTS:	return XSC_QP_STATE_RTS;
+	case IB_QPS_SQD:	return XSC_QP_STATE_SQD;
+	case IB_QPS_SQE:	return XSC_QP_STATE_SQER;
+	case IB_QPS_ERR:	return XSC_QP_STATE_ERR;
+	default:		return -1;
+	}
+}
+
+static char *qp_state_to_str(enum ib_qp_state state)
+{
+	switch (state) {
+	case IB_QPS_RESET:	return "RST";
+	case IB_QPS_INIT:	return "INIT";
+	case IB_QPS_RTR:	return "RTR";
+	case IB_QPS_RTS:	return "RTS";
+	case IB_QPS_SQD:	return "SQD";
+	case IB_QPS_SQE:	return "SQE";
+	case IB_QPS_ERR:	return "ERR";
+	default:		return "UNKNOWN";
+	}
+}
+
+static struct ib_umem *qp_umem_get(struct xsc_ib_dev *dev, struct xsc_ib_qp *qp,
+				   struct ib_udata *udata, struct ib_pd *pd,
+	struct xsc_ib_create_qp *ucmd)
+{
+	struct ib_umem *umem;
+
+	umem = ib_umem_get_va(&dev->ib_dev, ucmd->buf_addr, qp->buf_size,
+			      IB_ACCESS_LOCAL_WRITE);
+	return umem;
+}
+
+static int create_user_qp(struct xsc_ib_dev *dev, struct ib_pd *pd,
+			  struct xsc_ib_qp *qp, struct ib_udata *udata,
+			  struct xsc_create_qp_mbox_in **in,
+			  struct xsc_ib_create_qp_resp *resp, int *inlen)
+{
+	struct xsc_ib_create_qp ucmd;
+	int page_shift;
+	int npages;
+	u32 offset;
+	int ncont;
+	int err;
+	int hw_npages;
+	struct ib_umem_dmabuf *umem_dmabuf;
+
+	err = ib_copy_from_udata(&ucmd, udata, sizeof(ucmd));
+	if (err) {
+		ibdev_err(&dev->ib_dev, "failed to copy from udata, err=%d\n", err);
+		return err;
+	}
+	ibdev_dbg(&dev->ib_dev, "buf_addr:0x%lx db_addr:0x%lx sq cnt:%u, rq cnt:%u, rq shift:%u\n",
+		  (uintptr_t)ucmd.buf_addr, (uintptr_t)ucmd.db_addr,
+		    ucmd.sq_wqe_count, ucmd.rq_wqe_count, ucmd.rq_wqe_shift);
+
+	qp->sq.ds_cnt = ucmd.sq_wqe_count;
+	qp->sq.wqe_cnt = ucmd.sq_wqe_count;
+	qp->sq.wqe_shift = XSC_BASE_WQE_SHIFT;
+	qp->rq.ds_cnt = ucmd.rq_wqe_count;
+	qp->rq.wqe_cnt = ucmd.rq_wqe_count;
+	qp->rq.wqe_shift = XSC_BASE_WQE_SHIFT;
+
+	qp->buf_size = (qp->sq.wqe_cnt << qp->sq.wqe_shift) + (qp->rq.wqe_cnt << qp->rq.wqe_shift);
+
+	if (ucmd.dmabuf_fd != -1) {
+		umem_dmabuf = ib_umem_dmabuf_get_pinned(&dev->ib_dev, 0, ucmd.dmabuf_sz,
+							ucmd.dmabuf_fd, IB_ACCESS_LOCAL_WRITE);
+
+		if (IS_ERR(umem_dmabuf)) {
+			ibdev_warn(&dev->ib_dev, "qp umem_dmabuf get failed (%ld)\n",
+				   PTR_ERR(umem_dmabuf));
+			return -ENOMEM;
+		}
+
+		qp->umem = &umem_dmabuf->umem;
+	} else {
+		qp->umem = qp_umem_get(dev, qp, udata, pd, &ucmd);
+	}
+
+	if (IS_ERR(qp->umem)) {
+		ibdev_err(&dev->ib_dev, "umem_get failed\n");
+		err = PTR_ERR(qp->umem);
+		goto err_uuar;
+	}
+
+	xsc_ib_cont_pages(qp->umem, ucmd.buf_addr, &npages, &page_shift,
+			  &ncont);
+	if (ncont != npages) {
+		/* TODO: peer memory support failed */
+		page_shift = PAGE_SHIFT;
+		ncont = npages;
+	}
+
+	hw_npages = DIV_ROUND_UP(qp->buf_size, PAGE_SIZE_4K);
+	err = xsc_ib_get_buf_offset(ucmd.buf_addr, page_shift, &offset);
+	if (err) {
+		ibdev_err(&dev->ib_dev, "bad offset:%d\n", offset);
+		goto err_umem;
+	}
+	ibdev_dbg(&dev->ib_dev, "npage:%d, page_shift:%d, ncont:%d, offset:%d, hw_npages %d\n",
+		  npages, page_shift, ncont, offset, hw_npages);
+
+	*inlen = sizeof(**in) + sizeof(*((*in)->req.pas)) * hw_npages;
+	*in = xsc_vzalloc(*inlen);
+	if (!*in) {
+		err = -ENOMEM;
+		goto err_umem;
+	}
+	xsc_ib_populate_pas(dev, qp->umem, page_shift, (*in)->req.pas, hw_npages, true);
+	(*in)->req.pa_num = cpu_to_be16(hw_npages);
+
+	err = ib_copy_to_udata(udata, resp, sizeof(*resp));
+	if (err) {
+		ibdev_err(&dev->ib_dev, "failed to copy to udata, err=%d\n", err);
+		goto err_umem;
+	}
+	qp->create_type = XSC_QP_USER;
+
+	return 0;
+
+err_umem:
+	ib_umem_release(qp->umem);
+
+err_uuar:
+	return err;
+}
+
+static void destroy_qp_user(struct ib_pd *__maybe_unused pd, struct xsc_ib_qp *qp)
+{
+	ib_umem_release(qp->umem);
+}
+
 #define MAX_QP1_SQ_HDR_SIZE_V2	512
 #define MAX_QP1_SQ_HDR_SIZE	86
 	/* Ethernet header	=  14 */
@@ -57,6 +319,690 @@ static void *get_seg_wqe(void *first, int n)
 	/* iCRC			=   4 */
 #define MAX_QP1_RQ_HDR_SIZE_V2	512
 
+static int create_kernel_qp(struct xsc_ib_dev *dev,
+			    struct ib_qp_init_attr *init_attr,
+			    struct xsc_ib_qp *qp,
+			    struct xsc_create_qp_mbox_in **in, int *inlen)
+{
+	int err;
+	int sq_size;
+	int hw_npages;
+
+	sq_size = calc_sq_size(dev, init_attr, qp);
+	if (sq_size < 0) {
+		err = -EINVAL;
+		ibdev_err(&dev->ib_dev, "err %d\n", err);
+		return err;
+	}
+
+	qp->rq.ds_cnt = qp->rq.wqe_cnt << (qp->rq.wqe_shift - XSC_BASE_WQE_SHIFT);
+	qp->rq.offset = 0;
+	qp->sq.offset = qp->rq.wqe_cnt << qp->rq.wqe_shift;
+	qp->buf_size = qp->sq.offset + sq_size;
+	qp->send_psn = 0;
+
+	err = xsc_buf_alloc(dev->xdev, qp->buf_size, PAGE_SIZE, &qp->buf);
+	if (err) {
+		ibdev_err(&dev->ib_dev, "failed to alloc qp buffer,err=%d\n", err);
+		return err;
+	}
+
+	qp->sq.qend = qp->buf.direct.buf + qp->sq.offset + sq_size;
+	hw_npages = DIV_ROUND_UP(qp->buf_size, PAGE_SIZE_4K);
+	*inlen = sizeof(**in) + sizeof(*(*in)->req.pas) * hw_npages;
+	*in = xsc_vzalloc(*inlen);
+	if (!*in) {
+		err = -ENOMEM;
+		goto err_buf;
+	}
+
+	xsc_fill_page_array(&qp->buf, (*in)->req.pas, hw_npages);
+	(*in)->req.pa_num = cpu_to_be16(hw_npages);
+
+	qp->sq.wrid = kcalloc(qp->sq.wqe_cnt, sizeof(*qp->sq.wrid), GFP_KERNEL);
+	qp->sq.wr_data = kcalloc(qp->sq.wqe_cnt, sizeof(*qp->sq.wr_data), GFP_KERNEL);
+	qp->rq.wrid = kcalloc(qp->rq.wqe_cnt, sizeof(*qp->rq.wrid), GFP_KERNEL);
+	qp->sq.w_list = kcalloc(qp->sq.wqe_cnt, sizeof(*qp->sq.w_list), GFP_KERNEL);
+	qp->sq.wqe_head = kcalloc(qp->sq.wqe_cnt, sizeof(*qp->sq.wqe_head), GFP_KERNEL);
+	qp->sq.wr_opcode = kcalloc(qp->sq.wqe_cnt, sizeof(*qp->sq.wr_opcode), GFP_KERNEL);
+	qp->sq.need_flush = kcalloc(qp->sq.wqe_cnt, sizeof(*qp->sq.need_flush), GFP_KERNEL);
+	qp->sq.prev_ce_wqe = kcalloc(qp->sq.wqe_cnt, sizeof(*qp->sq.prev_ce_wqe), GFP_KERNEL);
+	qp->sq.last_comp_ce_wqe = -1;
+	qp->sq.last_post_ce_wqe = -1;
+
+	if (!qp->sq.wrid || !qp->sq.wr_data || !qp->rq.wrid || !qp->sq.prev_ce_wqe ||
+	    !qp->sq.w_list || !qp->sq.wqe_head || !qp->sq.wr_opcode || !qp->sq.need_flush) {
+		err = -ENOMEM;
+		goto err_wrid;
+	}
+	qp->create_type = XSC_QP_KERNEL;
+
+	if (init_attr->qp_type == IB_QPT_GSI) {
+		qp->sq.mad_index = 0;
+		qp->sq.mad_queue_depth = MAD_QUEUE_DEPTH;
+		qp->sq.hdr_size = MAX_QP1_SQ_HDR_SIZE_V2 * MAD_QUEUE_DEPTH;
+		qp->sq.hdr_buf = dma_alloc_coherent(dev->ib_dev.dma_device,
+						    qp->sq.hdr_size,
+						    &qp->sq.hdr_dma,
+						    GFP_KERNEL);
+		if (!qp->sq.hdr_buf) {
+			err = -ENOMEM;
+			ibdev_err(&dev->ib_dev, "Failed to create sq_hdr_buf");
+			goto err_wrid;
+		}
+	}
+
+	return 0;
+
+err_wrid:
+	kfree(qp->sq.prev_ce_wqe);
+	kfree(qp->sq.need_flush);
+	kfree(qp->sq.wr_opcode);
+	kfree(qp->sq.wqe_head);
+	kfree(qp->sq.w_list);
+	kfree(qp->sq.wrid);
+	kfree(qp->sq.wr_data);
+	kfree(qp->rq.wrid);
+
+err_buf:
+	xsc_buf_free(dev->xdev, &qp->buf);
+	return err;
+}
+
+static void destroy_qp_kernel(struct xsc_ib_dev *dev, struct xsc_ib_qp *qp)
+{
+	if (qp->sq.hdr_buf)
+		dma_free_coherent(dev->ib_dev.dma_device, qp->sq.hdr_size,
+				  qp->sq.hdr_buf, qp->sq.hdr_dma);
+	kfree(qp->sq.prev_ce_wqe);
+	kfree(qp->sq.need_flush);
+	kfree(qp->sq.wr_opcode);
+	kfree(qp->sq.wqe_head);
+	kfree(qp->sq.w_list);
+	kfree(qp->sq.wrid);
+	kfree(qp->sq.wr_data);
+	kfree(qp->rq.wrid);
+	xsc_buf_free(dev->xdev, &qp->buf);
+}
+
+static u8 ib_to_xsc_qp_type(enum ib_qp_type qp_type, __u32 flags)
+{
+	if (qp_type == IB_QPT_RC) {
+		if (flags & XSC_QP_FLAG_WORC)
+			return XSC_QUEUE_TYPE_WORC;
+		else
+			return XSC_QUEUE_TYPE_RDMA_RC;
+	} else if ((qp_type == IB_QPT_GSI) || (qp_type == IB_QPT_SMI)) {
+		return XSC_QUEUE_TYPE_RDMA_MAD;
+	} else if (qp_type == IB_QPT_RAW_PACKET) {
+		if (flags & XSC_QP_FLAG_RAWPACKET_TSO)
+			return XSC_QUEUE_TYPE_RAW_TSO;
+		else if (flags & XSC_QP_FLAG_RAWPACKET_TX)
+			return XSC_QUEUE_TYPE_RAW_TX;
+		else if (flags & XSC_QP_FLAG_RAWPACKET_SNIFFER)
+			return XSC_QUEUE_TYPE_SNIFFER;
+		else
+			return XSC_QUEUE_TYPE_RAW;
+	} else {
+		return XSC_QUEUE_TYPE_INVALID;
+	}
+}
+
+static void get_cqs(enum ib_qp_type qp_type,
+		    struct ib_cq *ib_send_cq, struct ib_cq *ib_recv_cq,
+		    struct xsc_ib_cq **send_cq, struct xsc_ib_cq **recv_cq)
+{
+	switch (qp_type) {
+	case IB_QPT_SMI:
+	case IB_QPT_GSI:
+	case IB_QPT_RC:
+	case IB_QPT_UC:
+	case IB_QPT_UD:
+	case IB_QPT_RAW_PACKET:
+		*send_cq = ib_send_cq ? to_xcq(ib_send_cq) : NULL;
+		*recv_cq = ib_recv_cq ? to_xcq(ib_recv_cq) : NULL;
+		break;
+	default:
+		*send_cq = NULL;
+		*recv_cq = NULL;
+		break;
+	}
+}
+
+static void xsc_ib_lock_cqs(struct xsc_ib_cq *send_cq, struct xsc_ib_cq *recv_cq)
+	__acquires(&send_cq->lock) __acquires(&recv_cq->lock)
+{
+	if (send_cq) {
+		if (recv_cq) {
+			if (send_cq->xcq.cqn < recv_cq->xcq.cqn)  {
+				spin_lock_irq(&send_cq->lock);
+				spin_lock_nested(&recv_cq->lock,
+						 SINGLE_DEPTH_NESTING);
+			} else if (send_cq->xcq.cqn == recv_cq->xcq.cqn) {
+				spin_lock_irq(&send_cq->lock);
+				__acquire(&recv_cq->lock);
+			} else {
+				spin_lock_irq(&recv_cq->lock);
+				spin_lock_nested(&send_cq->lock,
+						 SINGLE_DEPTH_NESTING);
+			}
+		} else {
+			spin_lock_irq(&send_cq->lock);
+		}
+	} else if (recv_cq) {
+		spin_lock_irq(&recv_cq->lock);
+	}
+}
+
+static void xsc_ib_unlock_cqs(struct xsc_ib_cq *send_cq, struct xsc_ib_cq *recv_cq)
+	__releases(&send_cq->lock) __releases(&recv_cq->lock)
+{
+	if (send_cq) {
+		if (recv_cq) {
+			if (send_cq->xcq.cqn < recv_cq->xcq.cqn)  {
+				spin_unlock(&recv_cq->lock);
+				spin_unlock_irq(&send_cq->lock);
+			} else if (send_cq->xcq.cqn == recv_cq->xcq.cqn) {
+				__release(&recv_cq->lock);
+				spin_unlock_irq(&send_cq->lock);
+			} else {
+				spin_unlock(&send_cq->lock);
+				spin_unlock_irq(&recv_cq->lock);
+			}
+		} else {
+			spin_unlock_irq(&send_cq->lock);
+		}
+	} else if (recv_cq) {
+		spin_unlock_irq(&recv_cq->lock);
+	}
+}
+
+static int create_qp_common(struct xsc_ib_dev *dev, struct ib_pd *pd,
+			    struct ib_qp_init_attr *init_attr,
+			    struct ib_udata *udata, struct xsc_ib_qp *qp)
+{
+	struct xsc_ib_create_qp_resp resp;
+	struct xsc_create_qp_mbox_in *in = NULL;
+	struct xsc_ib_create_qp ucmd;
+	struct xsc_ib_cq *send_cq;
+	struct xsc_ib_cq *recv_cq;
+	struct xsc_srq_qp_node *qp_node;
+	int inlen = sizeof(*in);
+	int err;
+	char buf[256];
+	char *ptr = buf;
+	int ret = 0;
+
+	mutex_init(&qp->mutex);
+	spin_lock_init(&qp->sq.lock);
+	spin_lock_init(&qp->rq.lock);
+	spin_lock_init(&qp->lock);
+
+	if (init_attr->sq_sig_type == IB_SIGNAL_ALL_WR)
+		qp->sq_signal_bits = XSC_WQE_CTRL_CQ_UPDATE;
+
+	if (pd->uobject) {
+		if (ib_copy_from_udata(&ucmd, udata, sizeof(ucmd))) {
+			ibdev_err(&dev->ib_dev, "failed to copy from udata\n");
+			return -EFAULT;
+		}
+	}
+
+	qp->has_rq = qp_has_rq(init_attr);
+
+	err = set_rq_size(dev, init_attr, qp->has_rq,
+			  qp, pd->uobject ? &ucmd : NULL);
+	if (err) {
+		ibdev_err(&dev->ib_dev, "failed to set rq size %d\n", err);
+		return err;
+	}
+
+	if (pd->uobject) {
+		err = create_user_qp(dev, pd, qp, udata, &in, &resp, &inlen);
+		if (err)
+			ibdev_err(&dev->ib_dev, "failed to create user qp, err = %d\n", err);
+	} else {
+		err = create_kernel_qp(dev, init_attr, qp, &in, &inlen);
+		if (err)
+			ibdev_err(&dev->ib_dev, "failed to create kernel qp, err = %d\n", err);
+		else
+			qp->pa_lkey = to_xpd(pd)->pa_lkey;
+	}
+
+	if (err)
+		return err;
+
+	if (is_sqp(init_attr->qp_type))
+		qp->port = init_attr->port_num;
+
+	if (is_qp1(init_attr->qp_type))
+		in->req.input_qpn = cpu_to_be16(1);
+
+	in->req.pdn = cpu_to_be32(to_xpd(pd)->pdn);
+
+	if (qp->rq.ds_cnt)
+		in->req.log_rq_sz = ilog2(qp->rq.ds_cnt);
+
+	if (qp->sq.ds_cnt)
+		in->req.log_sq_sz = ilog2(qp->sq.ds_cnt);
+	else
+		in->req.log_sq_sz = ilog2(0x80);
+
+	if (init_attr->send_cq) {
+		qp->send_cq = init_attr->send_cq;
+		in->req.cqn_send = to_xcq(init_attr->send_cq)->xcq.cqn;
+		in->req.cqn_send = cpu_to_be16(in->req.cqn_send);
+	}
+
+	if (init_attr->recv_cq) {
+		qp->recv_cq = init_attr->recv_cq;
+		in->req.cqn_recv = to_xcq(init_attr->recv_cq)->xcq.cqn;
+		in->req.cqn_recv = cpu_to_be16(in->req.cqn_recv);
+	}
+
+	in->req.qp_type = ib_to_xsc_qp_type(init_attr->qp_type, pd->uobject ?
+					    ucmd.flags : 0);
+
+	if (in->req.qp_type == XSC_QUEUE_TYPE_INVALID) {
+		ibdev_err(&dev->ib_dev, "invalid qp type:%d\n", init_attr->qp_type);
+		goto err_create;
+	}
+	in->req.glb_funcid = cpu_to_be16(dev->xdev->glb_func_id);
+	in->req.page_shift = PAGE_SHIFT;
+
+	qp->xqp.qp_type_internal = in->req.qp_type;
+
+	err = xsc_core_create_qp(dev->xdev, &qp->xqp, in, inlen);
+	if (err) {
+		ibdev_err(&dev->ib_dev, "create qp failed, err=%d\n", err);
+		goto err_create;
+	}
+
+	qp->doorbell_qpn = qp->xqp.qpn;
+
+	qp->xqp.event = xsc_ib_qp_event;
+	qp->xqp.qp_type = init_attr->qp_type;
+	ret += snprintf(ptr + ret, 256 - ret, "pdn=%d,", to_xpd(pd)->pdn);
+	ret += snprintf(ptr + ret, 256 - ret, "log_rq_sz=%d,", in->req.log_rq_sz);
+	ret += snprintf(ptr + ret, 256 - ret, "log_sq_sz=%d,", in->req.log_sq_sz);
+	ret += snprintf(ptr + ret, 256 - ret, "scqn=%d,", to_xcq(init_attr->send_cq)->xcq.cqn);
+	ret += snprintf(ptr + ret, 256 - ret, "rcqn=%d", to_xcq(init_attr->recv_cq)->xcq.cqn);
+
+	get_cqs(init_attr->qp_type, init_attr->send_cq, init_attr->recv_cq, &send_cq, &recv_cq);
+	xsc_ib_lock_cqs(send_cq, recv_cq);
+	if (send_cq)
+		list_add_tail(&qp->cq_send_list, &send_cq->send_qp_list);
+	if (recv_cq)
+		list_add_tail(&qp->cq_recv_list, &recv_cq->recv_qp_list);
+	xsc_ib_unlock_cqs(send_cq, recv_cq);
+
+	if (!pd->uobject && init_attr->srq) {
+		qp_node = kvzalloc_obj(*qp_node, GFP_KERNEL);
+		if (!qp_node)
+			goto err_create;
+		qp_node->ibqp = &qp->ibqp;
+		qp_node->qp_id = qp->xqp.qpn;
+		ibdev_dbg(&dev->ib_dev, "qpn:%u add into srq_qp_list\n", qp_node->qp_id);
+		list_add_tail(&qp_node->entry, &to_xsrq(init_attr->srq)->xsrq.srq_qp_list);
+	}
+
+	qp->xqp.qp_protocol_mode = dev->xdev->rdma_proto_mode;
+
+	ibdev_dbg(&dev->ib_dev, "succeeded to create qp:%d, %s\n", qp->xqp.qpn, buf);
+
+	xsc_vfree(in);
+
+	return 0;
+
+err_create:
+	if (qp->create_type == XSC_QP_USER)
+		destroy_qp_user(pd, qp);
+	else if (qp->create_type == XSC_QP_KERNEL)
+		destroy_qp_kernel(dev, qp);
+
+	xsc_vfree(in);
+	return err;
+}
+
+static struct xsc_ib_pd *get_pd(struct xsc_ib_qp *qp)
+{
+	return to_xpd(qp->ibqp.pd);
+}
+
+static void destroy_qp_common(struct xsc_ib_dev *dev, struct xsc_ib_qp *qp)
+{
+	struct xsc_ib_cq *send_cq = NULL;
+	struct xsc_ib_cq *recv_cq = NULL;
+	struct xsc_modify_qp_mbox_in *in;
+	int err;
+
+	in = kzalloc_obj(*in, GFP_KERNEL);
+	if (!in)
+		return;
+
+	if (qp->xqp.qp_type_internal == XSC_QUEUE_TYPE_RAW ||
+	    qp->xqp.qp_type_internal == XSC_QUEUE_TYPE_RAW_TSO ||
+	    qp->xqp.qp_type_internal == XSC_QUEUE_TYPE_RAW_TX ||
+	    qp->state != IB_QPS_RESET)
+		if (xsc_core_qp_modify(dev->xdev, to_xsc_state(qp->state),
+				       XSC_QP_STATE_RST, in, sizeof(*in), &qp->xqp))
+			ibdev_warn(&dev->ib_dev, "modify QP %06x to RESET failed\n", qp->xqp.qpn);
+
+	get_cqs(qp->xqp.qp_type, qp->ibqp.send_cq, qp->ibqp.recv_cq, &send_cq, &recv_cq);
+	xsc_ib_lock_cqs(send_cq, recv_cq);
+	if (send_cq)
+		list_del(&qp->cq_send_list);
+
+	if (recv_cq)
+		list_del(&qp->cq_recv_list);
+
+	if (qp->create_type == XSC_QP_KERNEL) {
+		__xsc_ib_cq_clean(recv_cq, qp->xqp.qpn);
+		if (send_cq != recv_cq)
+			__xsc_ib_cq_clean(send_cq, qp->xqp.qpn);
+	}
+	xsc_ib_unlock_cqs(send_cq, recv_cq);
+
+	err = xsc_core_destroy_qp(dev->xdev, &qp->xqp);
+	if (err)
+		ibdev_warn(&dev->ib_dev, "failed to destroy QP 0x%x\n", qp->xqp.qpn);
+	kfree(in);
+
+	if (qp->create_type == XSC_QP_KERNEL)
+		destroy_qp_kernel(dev, qp);
+	else if (qp->create_type == XSC_QP_USER)
+		destroy_qp_user(&get_pd(qp)->ibpd, qp);
+}
+
+static const char *ib_qp_type_str(enum ib_qp_type type)
+{
+	switch (type) {
+	case IB_QPT_SMI:
+		return "IB_QPT_SMI";
+	case IB_QPT_GSI:
+		return "IB_QPT_GSI";
+	case IB_QPT_RC:
+		return "IB_QPT_RC";
+	case IB_QPT_UC:
+		return "IB_QPT_UC";
+	case IB_QPT_UD:
+		return "IB_QPT_UD";
+	case IB_QPT_RAW_IPV6:
+		return "IB_QPT_RAW_IPV6";
+	case IB_QPT_RAW_ETHERTYPE:
+		return "IB_QPT_RAW_ETHERTYPE";
+	case IB_QPT_RAW_PACKET:
+		return "IB_QPT_RAW_PACKET";
+	case IB_QPT_MAX:
+	default:
+		return "Invalid QP type";
+	}
+}
+
+int xsc_ib_create_qp(struct ib_qp *ibqp,
+		     struct ib_qp_init_attr *init_attr,
+		     struct ib_udata *udata)
+{
+	struct xsc_ib_dev *dev;
+	struct xsc_ib_qp *qp;
+	struct ib_pd *pd = ibqp->pd;
+	int err;
+
+	if (pd) {
+		dev = to_xdev(pd->device);
+	} else {
+		pr_warn("%s: no PD for transport %s\n", __func__,
+			ib_qp_type_str(init_attr->qp_type));
+		return -EINVAL;
+	}
+
+	if (init_attr->qp_type != IB_QPT_RAW_PACKET) {
+		if (!is_support_rdma(dev->xdev) ||
+		    (is_qp1(init_attr->qp_type) && !is_support_rdma_cm(dev->xdev))) {
+			return -EPROTONOSUPPORT;
+		}
+	}
+
+	qp = to_xqp(ibqp);
+
+	qp->xqp.qp_type = init_attr->qp_type;
+
+	switch (init_attr->qp_type) {
+	case IB_QPT_RC:
+	case IB_QPT_SMI:
+	case IB_QPT_GSI:
+	case IB_QPT_RAW_PACKET:
+		err = create_qp_common(dev, pd, init_attr, udata, qp);
+		if (err) {
+			ibdev_err(&dev->ib_dev, "create_qp_common failed\n");
+			return err;
+		}
+
+		if (is_qp0(init_attr->qp_type)) {
+			qp->ibqp.qp_num = 0;
+		} else if (is_qp1(init_attr->qp_type)) {
+			qp->ibqp.qp_num = 1;
+			dev->xdev->gsi_qpn = qp->xqp.qpn;
+		} else {
+			qp->ibqp.qp_num = qp->xqp.qpn;
+		}
+
+		break;
+
+	case IB_QPT_RAW_IPV6:
+	case IB_QPT_RAW_ETHERTYPE:
+	case IB_QPT_MAX:
+	default:
+		ibdev_err(&dev->ib_dev, "unsupported qp type %d\n",
+			  init_attr->qp_type);
+		/* Don't support raw QPs */
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int xsc_ib_destroy_qp(struct ib_qp *qp, struct ib_udata *udata)
+{
+	struct xsc_ib_dev *dev = to_xdev(qp->device);
+	struct xsc_ib_qp *xqp = to_xqp(qp);
+	struct xsc_err_state_qp_node *tmp = NULL, *err_rq_node = NULL, *err_sq_node = NULL;
+	struct xsc_srq_qp_node *qp_node = NULL, *tmp_qp_node = NULL;
+	unsigned long flags;
+	struct xsc_ib_srq *xsrq;
+
+	if (qp->qp_type == IB_QPT_RC) {
+		if (xqp->recv_cq) {
+			spin_lock_irq(&to_xcq(xqp->recv_cq)->lock);
+			list_for_each_entry_safe(err_rq_node, tmp,
+						 &to_xcq(xqp->recv_cq)->err_state_qp_list, entry) {
+				if (err_rq_node->qp_id == xqp->xqp.qpn && !err_rq_node->is_sq) {
+					list_del(&err_rq_node->entry);
+					kfree(err_rq_node);
+				}
+			}
+			spin_unlock_irq(&to_xcq(xqp->recv_cq)->lock);
+		}
+
+		if (xqp->send_cq) {
+			spin_lock_irq(&to_xcq(xqp->send_cq)->lock);
+			list_for_each_entry_safe(err_sq_node, tmp,
+						 &to_xcq(xqp->send_cq)->err_state_qp_list, entry) {
+				if (err_sq_node->qp_id == xqp->xqp.qpn && err_sq_node->is_sq) {
+					list_del(&err_sq_node->entry);
+					kfree(err_sq_node);
+				}
+			}
+			spin_unlock_irq(&to_xcq(xqp->send_cq)->lock);
+		}
+	}
+
+	if (xqp->create_type == XSC_QP_KERNEL && qp->srq) {
+		if (!to_xsrq(qp->srq) || list_empty(&to_xsrq(qp->srq)->xsrq.srq_qp_list))
+			ibdev_warn(&dev->ib_dev, "srq_qp_list is empty!\n");
+
+		xsrq = to_xsrq(qp->srq);
+		spin_lock_irqsave(&xsrq->lock, flags);
+		xsc_restore_cache_srq_wrs(xsrq, xqp);
+		list_for_each_entry_safe(qp_node, tmp_qp_node,
+					 &to_xsrq(qp->srq)->xsrq.srq_qp_list, entry) {
+			if (qp_node->qp_id == xqp->xqp.qpn) {
+				to_xsrq(qp->srq)->qp_num--;
+				ibdev_dbg(&dev->ib_dev, "srq_qp_list del node qpn:%u qp_num:%d\n",
+					  qp_node->qp_id, to_xsrq(qp->srq)->qp_num);
+				list_del_init(&qp_node->entry);
+				kvfree(qp_node);
+				break;
+			}
+		}
+		spin_unlock_irqrestore(&xsrq->lock, flags);
+	}
+
+	destroy_qp_common(dev, xqp);
+
+	return 0;
+}
+
+static inline u16 xsc_calc_udp_sport(u32 lqpn, u32 rqpn)
+{
+	unsigned char *p;
+	u8 ports[2];
+	u16 sport;
+	u64 tqpn;
+
+	tqpn = ((u64)(lqpn & 0xffffff)) * ((u64)(rqpn & 0xffffff));
+	p = (unsigned char *)&tqpn;
+	ports[0] = p[0] ^ p[2] ^ p[4];
+	ports[1] = p[1] ^ p[3] ^ p[5];
+	sport = *((u16 *)ports) | 0xC000;
+
+	return sport;
+}
+
+static inline void xsc_path_set_udp_sport(struct xsc_qp_path *path,
+					  const struct rdma_ah_attr *ah,
+					  u32 lqpn, u32 rqpn)
+{
+	if ((ah->grh.flow_label & UDP_SPORT_MASK) != 0) {
+		if ((ah->grh.flow_label & UDP_SPORT_MASK_EN) == 0)
+			path->sport = cpu_to_be16(rdma_flow_label_to_udp_sport(ah->grh.flow_label));
+		else
+			path->sport = cpu_to_be16((ah->grh.flow_label & UDP_SPORT_MASK) >>
+							UDP_SPORT_OFFSET);
+	} else {
+		path->sport = cpu_to_be16(xsc_calc_udp_sport(lqpn, rqpn));
+	}
+}
+
+static int xsc_set_path(struct xsc_ib_dev *dev, const struct rdma_ah_attr *ah,
+			struct xsc_qp_path *path, u8 port, int attr_mask,
+			u32 path_flags, const struct ib_qp_attr *attr, struct xsc_ib_qp *qp)
+{
+	struct ib_global_route *grh = rdma_ah_retrieve_grh((struct rdma_ah_attr *)ah);
+	union ib_gid *dgid = &grh->dgid;
+	const struct ib_gid_attr *sgid_attr = grh->sgid_attr;
+	union ib_gid *sgid = &((struct ib_gid_attr *)sgid_attr)->gid;
+	union {
+		struct sockaddr     _sockaddr;
+		struct sockaddr_in  _sockaddr_in;
+		struct sockaddr_in6 _sockaddr_in6;
+	} sgid_addr, dgid_addr;
+	int force_pcp, force_dscp;
+	char buf[256] = {0};
+	char *ptr = buf;
+	int ret = 0;
+
+	if (ah->type == RDMA_AH_ATTR_TYPE_ROCE) {
+		if (!(rdma_ah_get_ah_flags(ah) & IB_AH_GRH))
+			return -EINVAL;
+
+		if (qp->ibqp.qp_type == IB_QPT_RC ||
+		    qp->ibqp.qp_type == IB_QPT_UC) {
+			if (((ah->grh.flow_label & UDP_SPORT_MASK_EN) != 0) &&
+			    qp->xqp.qp_protocol_mode == RDMA_PROTO_VEROCE) {
+				ibdev_err(&dev->ib_dev,
+					  "qp type is  RDMA_PROTO_VEROCE, using flow_label to set the UDP sport %d is not allowed.",
+					   (ah->grh.flow_label & UDP_SPORT_MASK) >>
+					   UDP_SPORT_OFFSET);
+				return -EINVAL;
+			}
+			xsc_path_set_udp_sport(path, ah, qp->ibqp.qp_num, attr->dest_qp_num);
+		}
+
+		if (sgid_attr->gid_type != IB_GID_TYPE_ROCE_UDP_ENCAP) {
+			ibdev_err(&dev->ib_dev, "gid type not ROCEv2\n");
+			return -EINVAL;
+		}
+
+		force_dscp = dev->xdev->priv.force_dscp;
+		if (force_dscp == DSCP_PCP_UNSET)
+			path->ecn_dscp = (grh->traffic_class >> 2) & 0x3f;
+		else
+			path->ecn_dscp = force_dscp;
+		path->hop_limit = grh->hop_limit;
+
+		rdma_gid2ip((struct sockaddr *)&sgid_addr, sgid);
+		rdma_gid2ip((struct sockaddr *)&dgid_addr, dgid);
+
+		if (sgid_addr._sockaddr.sa_family == AF_INET &&
+		    dgid_addr._sockaddr.sa_family == AF_INET) {
+			memcpy(path->sip, &sgid_addr._sockaddr_in.sin_addr.s_addr,
+			       sizeof(struct in_addr));
+			memcpy(path->dip, &dgid_addr._sockaddr_in.sin_addr.s_addr,
+			       sizeof(struct in_addr));
+			path->af_type = AF_INET;
+			ret += snprintf(ptr + ret, 256 - ret, "sip=%#x,",
+					be32_to_cpu(path->sip[0]));
+			ret += snprintf(ptr + ret, 256 - ret, "dip=%#x,",
+					be32_to_cpu(path->dip[0]));
+		} else if (sgid_addr._sockaddr.sa_family == AF_INET6 &&
+			   dgid_addr._sockaddr.sa_family == AF_INET6) {
+			memcpy(path->sip, &sgid_addr._sockaddr_in6.sin6_addr.s6_addr,
+			       sizeof(path->sip));
+			memcpy(path->dip, &dgid_addr._sockaddr_in6.sin6_addr.s6_addr,
+			       sizeof(path->dip));
+			path->af_type = AF_INET6;
+			ret += snprintf(ptr + ret, 256 - ret, "sip=%08x%08x%08x%08x,",
+					be32_to_cpu(path->sip[0]), be32_to_cpu(path->sip[1]),
+					be32_to_cpu(path->sip[2]), be32_to_cpu(path->sip[3]));
+			ret += snprintf(ptr + ret, 256 - ret, "dip=%08x%08x%08x%08x,",
+					be32_to_cpu(path->dip[0]), be32_to_cpu(path->dip[1]),
+					be32_to_cpu(path->dip[2]), be32_to_cpu(path->dip[3]));
+		} else {
+			return -EINVAL;
+		}
+
+		ether_addr_copy(path->smac, dev->netdev->dev_addr);
+
+		memcpy(path->dmac, ah->roce.dmac, sizeof(ah->roce.dmac));
+		ret += snprintf(ptr + ret, 256 - ret, "smac=%02x%02x%02x%02x%02x%02x,",
+				path->smac[0], path->smac[1], path->smac[2],
+				path->smac[3], path->smac[4], path->smac[5]);
+		ret += snprintf(ptr + ret, 256 - ret, "dmac=%02x%02x%02x%02x%02x%02x",
+				path->dmac[0], path->dmac[1], path->dmac[2],
+				path->dmac[3], path->dmac[4], path->dmac[5]);
+		if (is_zero_ether_addr(path->dmac))
+			ibdev_err(&dev->ib_dev, "path dmac is zero!\n");
+		else
+			ibdev_dbg(&dev->ib_dev, "ib path info:%s\n", buf);
+
+		if (is_vlan_dev(sgid_attr->ndev)) {
+			path->vlan_valid = 1;
+			path->vlan_id = cpu_to_be16(vlan_dev_vlan_id(sgid_attr->ndev));
+
+			force_pcp = dev->xdev->priv.force_pcp;
+			if (force_pcp == DSCP_PCP_UNSET)
+				path->dci_cfi_prio_sl = (ah->sl & 0x7);
+			else
+				path->dci_cfi_prio_sl = force_pcp;
+		} else {
+			path->vlan_valid = 0;
+		}
+	}
+	ibdev_dbg(&dev->ib_dev, "path dscp %d pcp %d\n", path->ecn_dscp, path->dci_cfi_prio_sl);
+	return 0;
+}
+
 static inline void xsc_ib_trigger_cq_comp_handler(struct ib_cq *send_cq, struct ib_cq *recv_cq)
 {
 	if (send_cq)
@@ -127,10 +1073,285 @@ int xsc_ib_err_state_qp(struct xsc_ib_dev *dev, struct xsc_ib_qp *qp,
 	return 0;
 }
 
+static inline void xsc_set_qp_access_flag(struct xsc_modify_qp_mbox_in *in,
+					  struct xsc_qp_context *context, int access_flags)
+{
+	in->hdr.ver = cpu_to_be16(XSC_QP_CONTEXT_V1);
+	context->qp_access_flags = 0;
+
+	if (access_flags & IB_ACCESS_REMOTE_READ)
+		context->qp_access_flags |= QP_ACCESS_REMOTE_READ;
+	if (access_flags & IB_ACCESS_REMOTE_WRITE)
+		context->qp_access_flags |= QP_ACCESS_REMOTE_WRITE;
+	if (access_flags & IB_ACCESS_REMOTE_ATOMIC)
+		context->qp_access_flags |= QP_ACCESS_REMOTE_ATOMIC;
+
+	context->qp_access_flags = cpu_to_be32(context->qp_access_flags);
+}
+
+static int __xsc_ib_modify_qp(struct ib_qp *ibqp,
+			      const struct ib_qp_attr *attr, int attr_mask,
+			      enum ib_qp_state cur_state, enum ib_qp_state new_state,
+			      const struct xsc_ib_modify_qp *ucmd,
+			      struct ib_udata *udata)
+{
+	struct xsc_ib_dev *dev = to_xdev(ibqp->device);
+	struct xsc_ib_qp *qp = to_xqp(ibqp);
+	struct xsc_ib_cq *send_cq, *recv_cq;
+	struct xsc_qp_context *context;
+	struct xsc_modify_qp_mbox_in *in;
+	struct xsc_qp_path path;
+	int sqd_event;
+	int err;
+	char buf[256] = {0};
+	char *ptr = buf;
+	int ret = 0;
+	struct xsc_core_device *xdev = dev->xdev;
+
+	in = kzalloc_obj(*in, GFP_KERNEL);
+	if (!in)
+		return -ENOMEM;
+
+	context = &qp->ctx;
+
+	if (attr_mask & IB_QP_PATH_MTU) {
+		if (attr->path_mtu != IB_MTU_1024 &&
+		    attr->path_mtu != IB_MTU_4096 &&
+		    attr->path_mtu != IB_MTU_8192) {
+			ibdev_warn(&dev->ib_dev, "invalid mtu %d\n", attr->path_mtu);
+		}
+
+		if (attr->path_mtu < IB_MTU_4096)
+			context->mtu_mode = 0;
+		else if (attr->path_mtu < IB_MTU_8192)
+			context->mtu_mode = 1;
+		else
+			context->mtu_mode = 2;
+		ret = snprintf(ptr, 256, "path_mtu=%d,", attr->path_mtu);
+	}
+
+	if (attr_mask & IB_QP_DEST_QPN) {
+		context->remote_qpn = cpu_to_be32(attr->dest_qp_num);
+		ret += snprintf(ptr + ret, 256 - ret, "dest_qp_num=%d,", attr->dest_qp_num);
+	}
+
+	if (attr_mask & IB_QP_AV) {
+		err = xsc_set_path(dev, &attr->ah_attr, &path,
+				   attr_mask & IB_QP_PORT ? attr->port_num : qp->port,
+				   attr_mask, 0, attr, qp);
+		if (err)
+			goto out;
+
+		context->src_udp_port = path.sport;
+		context->dscp = path.ecn_dscp;
+		context->hop_limit = path.hop_limit;
+		context->ip_type = (path.af_type == AF_INET ? 0 : 1);
+		context->ip_type = cpu_to_be16(context->ip_type);
+		memcpy(context->dip, path.dip, sizeof(context->dip));
+		memcpy(context->sip, path.sip, sizeof(context->sip));
+		memcpy(context->dmac, path.dmac, sizeof(path.dmac));
+		memcpy(context->smac, path.smac, sizeof(path.smac));
+
+		context->vlan_valid = path.vlan_valid;
+		context->dci_cfi_prio_sl = path.dci_cfi_prio_sl;
+		context->vlan_id = path.vlan_id;
+	}
+
+	if (attr_mask & IB_QP_RNR_RETRY) {
+		context->rnr_retry = attr->rnr_retry;
+		ret += snprintf(ptr + ret, 256 - ret, "rnr_retry=%d,", attr->rnr_retry);
+	}
+
+	if (attr_mask & IB_QP_RETRY_CNT) {
+		context->retry_cnt = attr->retry_cnt;
+		ret += snprintf(ptr + ret, 256 - ret, "retry_cnt=%d,", attr->retry_cnt);
+	}
+
+	if (attr_mask & IB_QP_MAX_QP_RD_ATOMIC && attr->max_rd_atomic)
+		context->max_sra = min_t(u32, attr->max_rd_atomic, dev->xdev->caps.max_ra_req_qp);
+
+	if (attr_mask & IB_QP_MAX_DEST_RD_ATOMIC && attr->max_dest_rd_atomic)
+		context->max_rra = attr->max_dest_rd_atomic;
+
+	if (attr_mask & IB_QP_SQ_PSN) {
+		context->next_send_psn = cpu_to_be32(attr->sq_psn);
+		ret += snprintf(ptr + ret, 256 - ret, "sq_psn=%#x,", attr->sq_psn);
+	}
+
+	if (attr_mask & IB_QP_RQ_PSN) {
+		context->next_recv_psn = cpu_to_be32(attr->rq_psn);
+		ret += snprintf(ptr + ret, 256 - ret, "rq_psn=%#x,", attr->rq_psn);
+	}
+
+	if (attr_mask & IB_QP_ACCESS_FLAGS) {
+		xsc_set_qp_access_flag(in, context, attr->qp_access_flags);
+		ret += snprintf(ptr + ret, 256 - ret, "qp_access_flags=%#x,\n",
+				attr->qp_access_flags);
+	}
+
+	if (udata && udata->inlen && attr_mask & IB_QP_STATE && new_state == IB_QPS_RTR) {
+		if (!ucmd->profile &&
+		    xdev->rdma_proto_mode == RDMA_PROTO_VEROCE) {
+			context->swith_qp_type = 1;
+			context->qp_access_flags |= QP_ACCESS_MODIFY_QP_TYPE;
+			context->qp_access_flags = cpu_to_be32(context->qp_access_flags);
+			qp->xqp.qp_protocol_mode =
+				ucmd->profile ? RDMA_PROTO_VEROCE : RDMA_PROTO_ROCEV2;
+			in->hdr.ver = cpu_to_be16(XSC_QP_CONTEXT_V2);
+			ibdev_dbg(&dev->ib_dev,
+				  "local is VEROCE,remote is RoCEv2, switch qp_type from VEROCE to RoCEv2!\n");
+		}
+	}
+
+	if (attr_mask & IB_QP_STATE && new_state == IB_QPS_RESET)
+		qp->xqp.qp_protocol_mode = xdev->rdma_proto_mode;
+
+	if (cur_state == IB_QPS_RTS && new_state == IB_QPS_SQD	&&
+	    attr_mask & IB_QP_EN_SQD_ASYNC_NOTIFY && attr->en_sqd_async_notify)
+		sqd_event = 1;
+	else
+		sqd_event = 0;
+
+	memcpy(&in->ctx, context, sizeof(*context));
+	err = xsc_core_qp_modify(xdev, to_xsc_state(cur_state),
+				 to_xsc_state(new_state), in, sqd_event,
+				 &qp->xqp);
+	if (err) {
+		ibdev_err(&dev->ib_dev, "failed to modify qp[%d] from %s to %s\n",
+			  qp->xqp.qpn, qp_state_to_str(cur_state), qp_state_to_str(new_state));
+		goto out;
+	}
+
+	qp->state = new_state;
+	ibdev_dbg(&dev->ib_dev, "succeeded to modify qp[%d] from %s to %s with attr_mask=%#x, %s\n",
+		  qp->xqp.qpn, qp_state_to_str(cur_state), qp_state_to_str(new_state),
+		    attr_mask, buf);
+
+	if (qp->state == IB_QPS_INIT) {
+		qp->post_recv_invalid = false;
+		qp->post_send_invalid = false;
+	}
+
+	if (attr_mask & IB_QP_ACCESS_FLAGS)
+		qp->atomic_rd_en = attr->qp_access_flags;
+	if (attr_mask & IB_QP_MAX_DEST_RD_ATOMIC)
+		qp->resp_depth = attr->max_dest_rd_atomic;
+	if (attr_mask & IB_QP_PORT)
+		qp->port = attr->port_num;
+	if (attr_mask & IB_QP_ALT_PATH)
+		qp->alt_port = attr->alt_port_num;
+
+	/*
+	 * If we moved a kernel QP to RESET, clean up all old CQ
+	 * entries and reinitialize the QP.
+	 */
+	if (new_state == IB_QPS_RESET && !ibqp->uobject) {
+		get_cqs(qp->xqp.qp_type, qp->ibqp.send_cq, qp->ibqp.recv_cq, &send_cq, &recv_cq);
+		xsc_ib_cq_clean(recv_cq, qp->xqp.qpn);
+		if (send_cq != recv_cq)
+			xsc_ib_cq_clean(send_cq, qp->xqp.qpn);
+
+		qp->rq.head = 0;
+		qp->rq.tail = 0;
+		qp->sq.head = 0;
+		qp->sq.tail = 0;
+		qp->sq.cur_post = 0;
+		qp->sq.last_poll = 0;
+		qp->sq.last_comp_ce_wqe = -1;
+		qp->sq.last_post_ce_wqe = -1;
+		qp->has_trig_cq_comp_handle = false;
+	}
+
+out:
+	kfree(in);
+	return err;
+}
+
 enum xsc_ib_qp_ex_attr_mask {
 	XSC_IB_QP_FLUSH		= 1 << 21,
 };
 
+int xsc_ib_modify_qp(struct ib_qp *ibqp, struct ib_qp_attr *attr,
+		     int attr_mask, struct ib_udata *udata)
+{
+	struct xsc_ib_dev *dev = to_xdev(ibqp->device);
+	struct xsc_ib_qp *qp = to_xqp(ibqp);
+	enum ib_qp_state cur_state, new_state;
+	int err = -EINVAL;
+	int wr_num;
+	struct xsc_ib_srq *srq;
+	unsigned long flags;
+	struct xsc_ib_cq *send_cq;
+	struct xsc_ib_cq *recv_cq;
+	struct xsc_ib_modify_qp ucmd = {};
+
+	if (udata && udata->inlen) {
+		if (udata->inlen < offsetofend(typeof(ucmd), profile))
+			return -EINVAL;
+
+		if (ib_copy_from_udata(&ucmd, udata,
+				       min(udata->inlen, sizeof(ucmd))))
+			return -EFAULT;
+	}
+
+	mutex_lock(&qp->mutex);
+
+	cur_state = attr_mask & IB_QP_CUR_STATE ? attr->cur_qp_state : qp->state;
+	new_state = attr_mask & IB_QP_STATE ? attr->qp_state : cur_state;
+
+	if ((attr_mask & IB_QP_PORT) &&
+	    (attr->port_num == 0 || attr->port_num > dev->xdev->caps.num_ports)) {
+		ibdev_err(&dev->ib_dev, "error port num\n");
+		goto out;
+	}
+
+	if (attr_mask & IB_QP_MAX_DEST_RD_ATOMIC &&
+	    attr->max_dest_rd_atomic > dev->xdev->caps.max_ra_res_qp) {
+		ibdev_err(&dev->ib_dev, "dest rd atomic:%u exeeded", attr->max_dest_rd_atomic);
+		goto out;
+	}
+
+	if (cur_state == new_state && cur_state == IB_QPS_RESET) {
+		err = 0;
+		goto out;
+	}
+
+	if (!ib_modify_qp_is_ok(cur_state, new_state, ibqp->qp_type, attr_mask)) {
+		ibdev_err(&dev->ib_dev, "ib_modify_qp not ok, type=%d mask=0x%x state from%d to%d\n",
+			  ibqp->qp_type, attr_mask, cur_state, new_state);
+		goto out;
+	}
+
+	err = __xsc_ib_modify_qp(ibqp, attr, attr_mask, cur_state, new_state, &ucmd, udata);
+
+	if (!err && (attr_mask & IB_QP_STATE) && !udata) {
+		get_cqs(qp->xqp.qp_type, qp->send_cq, qp->recv_cq, &send_cq, &recv_cq);
+		xsc_ib_lock_cqs(send_cq, recv_cq);
+		err = xsc_ib_err_state_qp(dev, qp, to_xsc_state(cur_state),
+					  to_xsc_state(new_state));
+		xsc_ib_unlock_cqs(send_cq, recv_cq);
+
+		if (!ibqp->srq)
+			goto out;
+
+		if (cur_state == IB_QPS_RESET && new_state == IB_QPS_INIT) {
+			srq = to_xsrq(ibqp->srq);
+			spin_lock_irqsave(&srq->lock, flags);
+			srq->qp_num++;
+			wr_num = XSC_SRQ_MIN_WR_NUM;
+			ibdev_dbg(&dev->ib_dev,
+				  "SRQ %d: qp %d to INIT, post %d srq wrs, qp_num %d\n",
+				   srq->xsrq.srqn, qp->xqp.qpn, wr_num, srq->qp_num);
+			xsc_post_cache_wrs(srq, qp, wr_num, true);
+			spin_unlock_irqrestore(&srq->lock, flags);
+		}
+	}
+
+out:
+	mutex_unlock(&qp->mutex);
+	return err;
+}
+
 static int xsc_wq_overflow(struct xsc_ib_wq *wq, int nreq, struct xsc_ib_cq *cq)
 {
 	unsigned int cur;
@@ -231,6 +1452,106 @@ int xsc_ib_post_recv(struct ib_qp *ibqp, const struct ib_recv_wr *wr,
 	return err;
 }
 
+static inline enum ib_qp_state to_ib_qp_state(enum xsc_qp_state xsc_state)
+{
+	switch (xsc_state) {
+	case XSC_QP_STATE_RST:      return IB_QPS_RESET;
+	case XSC_QP_STATE_INIT:     return IB_QPS_INIT;
+	case XSC_QP_STATE_RTR:      return IB_QPS_RTR;
+	case XSC_QP_STATE_RTS:      return IB_QPS_RTS;
+	case XSC_QP_STATE_SQ_DRAINING:
+	case XSC_QP_STATE_SQD:      return IB_QPS_SQD;
+	case XSC_QP_STATE_SQER:     return IB_QPS_SQE;
+	case XSC_QP_STATE_ERR:      return IB_QPS_ERR;
+	default:		     return -1;
+	}
+}
+
+static inline enum ib_mig_state to_ib_mig_state(int xsc_mig_state)
+{
+	switch (xsc_mig_state) {
+	case XSC_QP_PM_ARMED:		return IB_MIG_ARMED;
+	case XSC_QP_PM_REARM:		return IB_MIG_REARM;
+	case XSC_QP_PM_MIGRATED:	return IB_MIG_MIGRATED;
+	default: return -1;
+	}
+}
+
+int xsc_ib_query_qp(struct ib_qp *ibqp, struct ib_qp_attr *qp_attr, int qp_attr_mask,
+		    struct ib_qp_init_attr *qp_init_attr)
+{
+	struct xsc_ib_dev *dev = to_xdev(ibqp->device);
+	struct xsc_ib_qp *qp = to_xqp(ibqp);
+	struct xsc_query_qp_mbox_out *outb;
+	struct xsc_qp_context *context;
+	int err = 0;
+
+	mutex_lock(&qp->mutex);
+
+	if (qp_attr_mask & XSC_IB_QP_FLUSH) {
+		xsc_ib_trigger_cq_comp_handler(qp->send_cq, qp->recv_cq);
+		goto out;
+	}
+
+	outb = kzalloc_obj(*outb, GFP_KERNEL);
+	if (!outb) {
+		err = -ENOMEM;
+		goto out;
+	}
+	context = &outb->ctx;
+	err = xsc_core_qp_query(dev->xdev, &qp->xqp, outb, sizeof(*outb));
+	if (err)
+		goto out_free;
+
+	if (qp->xqp.err_occurred) {
+		qp->state = IB_QPS_ERR;
+		qp->xqp.err_occurred = 0;
+	}
+	qp_attr->qp_state	     = qp->state;
+	qp_attr->path_mtu	     = context->mtu_mode ? IB_MTU_4096 : IB_MTU_1024;
+	qp_attr->rq_psn		     = be32_to_cpu(context->next_recv_psn) & 0xffffff;
+	qp_attr->sq_psn		     = be32_to_cpu(context->next_send_psn) & 0xffffff;
+	qp_attr->dest_qp_num	     = be32_to_cpu(context->remote_qpn) & 0xffffff;
+	qp_attr->sq_draining = 0;
+	qp_attr->retry_cnt	    = context->retry_cnt;
+	qp_attr->rnr_retry	    = context->rnr_retry;
+	qp_attr->cur_qp_state	     = qp_attr->qp_state;
+	qp_attr->cap.max_recv_wr     = qp->rq.wqe_cnt;
+	qp_attr->cap.max_recv_sge    = qp->rq.max_gs;
+	qp_attr->max_rd_atomic = context->max_sra;
+	qp_attr->max_dest_rd_atomic = context->max_rra;
+	qp_attr->port_num = 1;
+
+	if (!ibqp->uobject) {
+		qp_attr->cap.max_send_wr  = qp->sq.wqe_cnt;
+		qp_attr->cap.max_send_sge = qp->sq.max_gs;
+	} else {
+		qp_attr->cap.max_send_wr  = qp->sq.wqe_cnt;
+		qp_attr->cap.max_send_sge = qp->sq.max_gs;
+	}
+
+	/* We don't support inline sends for kernel QPs (yet), and we
+	 * don't know what userspace's value should be.
+	 */
+	qp_attr->cap.max_inline_data = 0;
+
+	qp_init_attr->cap	     = qp_attr->cap;
+
+	qp_init_attr->create_flags = 0;
+	if (qp->flags & XSC_IB_QP_BLOCK_MULTICAST_LOOPBACK)
+		qp_init_attr->create_flags |= IB_QP_CREATE_BLOCK_MULTICAST_LOOPBACK;
+
+	qp_init_attr->sq_sig_type = qp->sq_signal_bits & XSC_WQE_CTRL_CQ_UPDATE ?
+		IB_SIGNAL_ALL_WR : IB_SIGNAL_REQ_WR;
+
+out_free:
+	kfree(outb);
+
+out:
+	mutex_unlock(&qp->mutex);
+	return err;
+}
+
 struct xsc_ib_drain_cqe {
 	struct ib_cqe cqe;
 	struct completion done;

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

* [PATCH 13/15] yunsilicon: add the RDMA send path
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (11 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 12/15] yunsilicon: add RDMA QP verbs Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 14/15] yunsilicon: add RDMA counters and sysfs Xin Tian
                   ` (2 subsequent siblings)
  15 siblings, 1 reply; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the post send path: WQE construction for the supported work request
types (including the UD QP1 header build), the doorbell update and the
send queue drain helpers.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 drivers/infiniband/hw/xsc/main.c |  21 +
 drivers/infiniband/hw/xsc/qp.c   | 691 +++++++++++++++++++++++++++++++
 2 files changed, 712 insertions(+)

diff --git a/drivers/infiniband/hw/xsc/main.c b/drivers/infiniband/hw/xsc/main.c
index c4f306318..30927006b 100644
--- a/drivers/infiniband/hw/xsc/main.c
+++ b/drivers/infiniband/hw/xsc/main.c
@@ -526,6 +526,22 @@ static void xsc_ib_event(struct xsc_core_device *dev, void *context,
 		ib_dispatch_event(&ibev);
 }
 
+static void crc_table_init(struct xsc_ib_dev *dev)
+{
+	u32 c, i, j;
+
+	for (i = 0; i < 256; i++) {
+		c = i;
+		for (j = 0; j < 8; j++) {
+			if (c & 1)
+				c = 0xedb88320L ^ (c >> 1);
+			else
+				c = c >> 1;
+		}
+		dev->crc_32_table[i] = c;
+	}
+}
+
 static void xsc_ib_get_dev_fw_str(struct ib_device *ibdev, char *str)
 {
 	struct xsc_core_device *dev = to_xdev(ibdev)->xdev;
@@ -574,6 +590,7 @@ static void xsc_ib_dev_setting(struct xsc_ib_dev *dev)
 	dev->ib_dev.ops.modify_qp		= xsc_ib_modify_qp;
 	dev->ib_dev.ops.query_qp		= xsc_ib_query_qp;
 	dev->ib_dev.ops.destroy_qp		= xsc_ib_destroy_qp;
+	dev->ib_dev.ops.post_send		= xsc_ib_post_send;
 	dev->ib_dev.ops.post_recv		= xsc_ib_post_recv;
 	dev->ib_dev.ops.create_cq		= xsc_ib_create_cq;
 	dev->ib_dev.ops.destroy_cq		= xsc_ib_destroy_cq;
@@ -586,6 +603,8 @@ static void xsc_ib_dev_setting(struct xsc_ib_dev *dev)
 
 	dev->ib_dev.ops.get_port_immutable		= xsc_port_immutable;
 
+	dev->ib_dev.ops.drain_sq		= xsc_ib_drain_sq;
+	dev->ib_dev.ops.drain_rq		= xsc_ib_drain_rq;
 	dev->ib_dev.ops.get_dev_fw_str	= xsc_ib_get_dev_fw_str;
 	dev->ib_dev.ops.create_srq		= xsc_ib_create_srq;
 	dev->ib_dev.ops.destroy_srq		= xsc_ib_destroy_srq;
@@ -709,7 +728,9 @@ static int init_one(struct xsc_core_device *xdev,
 
 	init_node_data(dev);
 
+	crc_table_init(dev);
 
+	build_opcode_map(dev);
 
 	xsc_reg_local_dma_mr(xdev);
 
diff --git a/drivers/infiniband/hw/xsc/qp.c b/drivers/infiniband/hw/xsc/qp.c
index 6a83adc9c..9c79e09a7 100644
--- a/drivers/infiniband/hw/xsc/qp.c
+++ b/drivers/infiniband/hw/xsc/qp.c
@@ -28,6 +28,23 @@ enum {
 #define UDP_SPORT_MASK			0xffff
 #define UDP_SPORT_OFFSET		0
 
+void build_opcode_map(struct xsc_ib_dev *dev)
+{
+	struct xsc_msg_opcode *xsc_ib_opcode = dev->xdev->board_info->msg_opcode;
+
+	dev->opcode_map[IB_WR_SEND] = xsc_ib_opcode->send;
+	dev->opcode_map[IB_WR_SEND_WITH_IMM] = xsc_ib_opcode->send_with_imm;
+	dev->opcode_map[IB_WR_RDMA_WRITE] = xsc_ib_opcode->rdma_write;
+	dev->opcode_map[IB_WR_RDMA_WRITE_WITH_IMM] = xsc_ib_opcode->rdma_write_with_imm;
+	dev->opcode_map[IB_WR_RDMA_READ] = xsc_ib_opcode->rdma_read;
+	dev->opcode_map[IB_WR_ATOMIC_CMP_AND_SWP] = xsc_ib_opcode->atomic_cmp_and_swp;
+	dev->opcode_map[IB_WR_ATOMIC_FETCH_AND_ADD] = xsc_ib_opcode->atomic_fetch_and_add;
+	dev->opcode_map[IB_WR_MASKED_ATOMIC_CMP_AND_SWP] =
+		xsc_ib_opcode->masked_atomic_cmp_and_swp;
+	dev->opcode_map[IB_WR_MASKED_ATOMIC_FETCH_AND_ADD] =
+		xsc_ib_opcode->masked_atomic_fetch_and_add;
+}
+
 static int is_qp0(enum ib_qp_type qp_type)
 {
 	return qp_type == IB_QPT_SMI;
@@ -58,6 +75,79 @@ static void *get_seg_wqe(void *first, int n)
 	return first + (n << XSC_BASE_WQE_SHIFT);
 }
 
+void *xsc_get_send_wqe(struct xsc_ib_qp *qp, int n)
+{
+	return get_wqe(qp, qp->sq.offset + (n << qp->sq.wqe_shift));
+}
+
+static int iboe_tos_to_sl(struct net_device *ndev, int tos)
+{
+	int prio;
+	struct net_device *dev;
+
+	prio = rt_tos2priority(tos);
+	dev = is_vlan_dev(ndev) ? vlan_dev_real_dev(ndev) : ndev;
+	if (dev->num_tc)
+		return netdev_get_prio_tc_map(dev, prio);
+
+#if IS_ENABLED(CONFIG_VLAN_8021Q)
+	if (is_vlan_dev(ndev))
+		return (vlan_dev_get_egress_qos_mask(ndev, prio) &
+				VLAN_PRIO_MASK) >> VLAN_PRIO_SHIFT;
+#endif
+	return 0;
+}
+
+static inline void set_remote_addr_seg(struct xsc_core_device *xdev,
+				       struct xsc_wqe_data_seg *remote_seg,
+				       u32 msg_len, u64 remote_addr, u32 rkey)
+{
+	xsc_set_data_seg(xdev, remote_seg, remote_addr, rkey, msg_len);
+}
+
+static void set_local_data_seg(struct xsc_core_device *xdev,
+			       struct xsc_wqe_data_seg *data_seg, struct ib_sge *sg)
+{
+	xsc_set_data_seg(xdev, data_seg, sg->addr, sg->lkey, sg->length);
+}
+
+static int set_data_inl_seg(struct xsc_ib_qp *qp, const struct ib_send_wr *wr, void *ctrl)
+{
+	struct xsc_wqe_data_seg *data_seg;
+	unsigned int seg_index;
+	void *addr;
+	int len;
+	int i;
+
+	for (i = 0, seg_index = 1; i < wr->num_sge; ++i, ++seg_index) {
+		if (likely(wr->sg_list[i].length)) {
+			addr = (void *)wr->sg_list[i].addr;
+			len  = wr->sg_list[i].length;
+
+			if (unlikely(len > qp->max_inline_data))
+				return -ENOMEM;
+
+			data_seg = get_seg_wqe(ctrl, seg_index);
+			data_seg->in_line = 1;
+			data_seg->len = len;
+			memcpy(data_seg->in_line_data, addr, len);
+		}
+	}
+
+	return 0;
+}
+
+static __be32 send_ieth(const struct ib_send_wr *wr)
+{
+	switch (wr->opcode) {
+	case IB_WR_SEND_WITH_IMM:
+	case IB_WR_RDMA_WRITE_WITH_IMM:
+		return wr->ex.imm_data;
+	default:
+		return 0;
+	}
+}
+
 static void xsc_ib_qp_event(struct xsc_core_qp *qp, int type)
 {
 	struct ib_qp *ibqp = &core_to_xqp(qp)->ibqp;
@@ -1367,13 +1457,521 @@ static int xsc_wq_overflow(struct xsc_ib_wq *wq, int nreq, struct xsc_ib_cq *cq)
 	return cur + nreq >= wq->max_post;
 }
 
+static inline void xsc_post_send_db(struct xsc_ib_qp *qp,
+				    struct xsc_core_device *xdev,
+				    int nreq)
+{
+	u32 next_pid;
+
+	if (unlikely(!nreq))
+		return;
+
+	qp->sq.head += nreq;
+
+	next_pid = qp->sq.head << (qp->sq.wqe_shift - XSC_BASE_WQE_SHIFT);
+	xsc_update_tx_db(xdev, qp->doorbell_qpn, next_pid);
+}
+
+static inline u32 xsc_crc32(struct xsc_ib_dev *dev, u32 crc, u8 *buf, size_t len)
+{
+	u32 i;
+
+	for (i = 0; i < len; i++)
+		crc = dev->crc_32_table[(crc ^ buf[i]) & 0xff] ^ (crc >> 8);
+
+	return crc;
+}
+
 #define BTH_QPN_MASK		(0x00ffffff)
 #define BTH_PSN_MASK		(0x00ffffff)
 
 /* Compute a partial ICRC for all the IB transport headers. */
+static u32 xsc_icrc_hdr(struct xsc_ib_dev *dev, void *pkt, u32 size, u32 *icrc)
+{
+	struct iphdr *ip4h = NULL;
+	struct ipv6hdr *ip6h = NULL;
+	struct udphdr *udph;
+	struct ib_unpacked_eth *eth;
+	struct rxe_bth *bth;
+	struct ib_unpacked_deth *deth;
+	struct ib_unpacked_vlan *vlan;
+	int crc;
+	int crc_field_len;
+	__be16 l3_type;
+	u8 *l3_start;
+
+	int hdr_size;
+
+	/* pseudo header buffer size is calculate using ipv6 header size since
+	 * it is bigger than ipv4
+	 */
+	u8 pshdr[sizeof(struct udphdr) +
+		sizeof(struct ipv6hdr) +
+		sizeof(*bth) + sizeof(*deth)];
+
+	eth = pkt;
+
+	if (eth->type == htons(ETH_P_8021Q)) {
+		vlan = (struct ib_unpacked_vlan *)(eth + 1);
+		l3_type = vlan->type;
+		l3_start = (u8 *)(vlan + 1);
+		size -= 4;
+	} else {
+		l3_type = eth->type;
+		l3_start = (u8 *)(eth + 1);
+	}
+
+	hdr_size = sizeof(struct udphdr) +
+		(l3_type == htons(ETH_P_IP) ?
+		sizeof(struct iphdr) : sizeof(struct ipv6hdr));
+
+	crc_field_len = hdr_size + sizeof(*bth) + sizeof(*deth);
+
+	if (crc_field_len != size) {
+		ibdev_err(&dev->ib_dev, "Unmatched hdr: expect %d actual %d\n",
+			  crc_field_len, size);
+		return -EINVAL;
+	}
+
+	ip4h = (struct iphdr *)(l3_start);
+	ip6h = (struct ipv6hdr *)(l3_start);
+	udph = (struct udphdr *)(ip4h + 1);
+	bth = (struct rxe_bth *)(udph + 1);
+
+	memcpy(pshdr, l3_start, crc_field_len);
+
+	/* This seed is the result of computing a CRC with a seed of
+	 * 0xfffffff and 8 bytes of 0xff representing a masked LRH.
+	 */
+	crc = 0xdebb20e3;
+
+	if (l3_type == htons(ETH_P_IP)) { /* IPv4 */
+		memcpy(pshdr, ip4h, hdr_size);
+		ip4h = (struct iphdr *)pshdr;
+		udph = (struct udphdr *)(ip4h + 1);
+
+		ip4h->ttl = 0xff;
+		ip4h->check = CSUM_MANGLED_0;
+		ip4h->tos = 0xff;
+	} else {				/* IPv6 */
+		memcpy(pshdr, ip6h, hdr_size);
+		ip6h = (struct ipv6hdr *)pshdr;
+		udph = (struct udphdr *)(ip6h + 1);
+
+		memset(ip6h->flow_lbl, 0xff, sizeof(ip6h->flow_lbl));
+		ip6h->priority = 0xf;
+		ip6h->hop_limit = 0xff;
+	}
+	udph->check = CSUM_MANGLED_0;
+
+	bth = (struct rxe_bth *)(udph + 1);
+	/* exclude bth.resv8a */
+	bth->qpn |= cpu_to_be32(~BTH_QPN_MASK);
+
+	*icrc = xsc_crc32(dev, crc, pshdr, crc_field_len);
+
+	return 0;
+}
 
 /* Routine for sending QP1 packets for RoCE V1 an V2 */
 /* TODO: sq hdr buf should be created dynamically for mult entry */
+static int build_qp1_send_v2(struct xsc_ib_dev *dev,
+			     struct xsc_ib_qp *qp,
+			     const struct ib_send_wr *wr,
+			     struct ib_sge *sge,
+			     int payload_size, u32 *crc)
+{
+	struct xsc_ib_ah *ah = container_of(ud_wr((struct ib_send_wr *)wr)->ah, struct xsc_ib_ah,
+					    ibah);
+	const struct ib_gid_attr *sgid_attr = ah->ibah.sgid_attr;
+	u16 ether_type;
+	union ib_gid dgid;
+	bool is_eth = false;
+	bool is_vlan = false;
+	bool is_grh = false;
+	bool is_udp = false;
+	u8 ip_version = 0;
+	u16 vlan_id = 0xFFFF;
+	int rc = 0;
+	int cm_pcp = 0;
+	void *hdr_buf;
+
+	memset(&qp->qp1_hdr, 0, sizeof(qp->qp1_hdr));
+
+	if (!qp->sq.hdr_buf) {
+		ibdev_err(&dev->ib_dev, "QP1 buffer is empty!");
+		return -ENOMEM;
+	}
+	hdr_buf = (u8 *)qp->sq.hdr_buf + MAX_QP1_SQ_HDR_SIZE_V2 * qp->sq.mad_index;
+
+	if (!sgid_attr || !sgid_attr->ndev) {
+		ibdev_err(&dev->ib_dev, "sgid_addr or ndev is null\n");
+		return -ENXIO;
+	}
+
+	if (is_vlan_dev(sgid_attr->ndev))
+		vlan_id = vlan_dev_vlan_id(sgid_attr->ndev);
+
+	is_udp = sgid_attr->gid_type == IB_GID_TYPE_ROCE_UDP_ENCAP;
+	memcpy(&dgid.raw, &ah->av.rgid, 16);
+	if (is_udp) {
+		if (ipv6_addr_v4mapped((struct in6_addr *)&sgid_attr->gid)) {
+			ip_version = 4;
+			ether_type = ETH_P_IP;
+		} else {
+			ip_version = 6;
+			ether_type = ETH_P_IPV6;
+		}
+		is_grh = false;
+	} else {
+		ether_type = ETH_P_IBOE;
+		is_grh = true;
+	}
+
+	is_eth = true;
+	is_vlan = (vlan_id && (vlan_id < 0x1000)) ? true : false;
+
+	ib_ud_header_init(payload_size, !is_eth, is_eth, is_vlan, is_grh,
+			  ip_version, is_udp, 0, &qp->qp1_hdr);
+
+	/* ETH */
+	ether_addr_copy(qp->qp1_hdr.eth.dmac_h, ah->av.rmac);
+	ether_addr_copy(qp->qp1_hdr.eth.smac_h, dev->netdev->dev_addr);
+
+	/* For vlan, check the sgid for vlan existence */
+	if (!is_vlan) {
+		qp->qp1_hdr.eth.type = cpu_to_be16(ether_type);
+	} else {
+		if (dev->xdev->priv.cm_pcp != DSCP_PCP_UNSET)
+			cm_pcp = dev->xdev->priv.cm_pcp << 13;
+		else
+			cm_pcp = (iboe_tos_to_sl(sgid_attr->ndev, ah->av.tclass) << 13);
+		qp->qp1_hdr.vlan.type = cpu_to_be16(ether_type);
+		qp->qp1_hdr.vlan.tag = cpu_to_be16(vlan_id | cm_pcp);
+	}
+
+#define ECN_CAPABLE_TRANSPORT 0x2
+	if (is_grh || ip_version == 6) {
+		memcpy(qp->qp1_hdr.grh.source_gid.raw, sgid_attr->gid.raw,
+		       sizeof(sgid_attr->gid));
+		memcpy(qp->qp1_hdr.grh.destination_gid.raw, ah->av.rgid,
+		       sizeof(ah->av.rgid));
+		qp->qp1_hdr.grh.hop_limit     = ah->av.hop_limit;
+
+		if (dev->xdev->priv.cm_dscp != DSCP_PCP_UNSET)
+			qp->qp1_hdr.grh.traffic_class =
+				(dev->xdev->priv.cm_dscp << 2) | ECN_CAPABLE_TRANSPORT;
+		else
+			qp->qp1_hdr.grh.traffic_class = ECN_CAPABLE_TRANSPORT;
+	}
+
+	if (ip_version == 4) {
+		if (dev->xdev->priv.cm_dscp != DSCP_PCP_UNSET)
+			qp->qp1_hdr.ip4.tos =
+				(dev->xdev->priv.cm_dscp << 2) | ECN_CAPABLE_TRANSPORT;
+		else
+			qp->qp1_hdr.ip4.tos = ECN_CAPABLE_TRANSPORT;
+		qp->qp1_hdr.ip4.id = 0;
+		qp->qp1_hdr.ip4.frag_off = htons(IP_DF);
+		qp->qp1_hdr.ip4.ttl = ah->av.hop_limit;
+
+		memcpy(&qp->qp1_hdr.ip4.saddr, sgid_attr->gid.raw + 12, 4);
+		memcpy(&qp->qp1_hdr.ip4.daddr, ah->av.rgid + 12, 4);
+		qp->qp1_hdr.ip4.check = ib_ud_ip4_csum(&qp->qp1_hdr);
+	}
+
+	if (is_udp) {
+		qp->qp1_hdr.udp.dport = htons(dev->xdev->cm_udp_dst_port);
+		qp->qp1_hdr.udp.sport = htons(ah->av.udp_sport);
+		qp->qp1_hdr.udp.csum = 0;
+		ibdev_dbg(&dev->ib_dev, "CM packet used udp_sport=%d\n", ah->av.udp_sport);
+	}
+
+	/* BTH */
+	if (wr->opcode == IB_WR_SEND_WITH_IMM) {
+		qp->qp1_hdr.bth.opcode = IB_OPCODE_UD_SEND_ONLY_WITH_IMMEDIATE;
+		qp->qp1_hdr.immediate_present = 1;
+	} else {
+		qp->qp1_hdr.bth.opcode = IB_OPCODE_UD_SEND_ONLY;
+	}
+	if (wr->send_flags & IB_SEND_SOLICITED)
+		qp->qp1_hdr.bth.solicited_event = 1;
+	/* pad_count */
+	qp->qp1_hdr.bth.pad_count = (4 - payload_size) & 3;
+
+	/* P_key for QP1 is for all members */
+	qp->qp1_hdr.bth.pkey = cpu_to_be16(0xFFFF);
+	qp->qp1_hdr.bth.destination_qpn = IB_QP1;
+	qp->qp1_hdr.bth.ack_req = 0;
+	qp->send_psn++;
+	qp->send_psn &= BTH_PSN_MASK;
+	qp->qp1_hdr.bth.psn = cpu_to_be32(qp->send_psn);
+	/* DETH */
+	/* Use the privileged Q_Key for QP1 */
+	qp->qp1_hdr.deth.qkey = cpu_to_be32(IB_QP1_QKEY);
+	qp->qp1_hdr.deth.source_qpn = IB_QP1;
+
+	/* Pack the QP1 to the transmit buffer */
+	sge->addr = (dma_addr_t)(qp->sq.hdr_dma + MAX_QP1_SQ_HDR_SIZE_V2 * qp->sq.mad_index);
+	sge->lkey = 0xFFFFFFFF;
+	sge->length = MAX_QP1_SQ_HDR_SIZE;
+
+	ib_ud_header_pack(&qp->qp1_hdr, hdr_buf);
+	/*
+	 * Max Header buf size for IPV6 RoCE V2 is 86,
+	 * which is same as the QP1 SQ header buffer.
+	 * Header buf size for IPV4 RoCE V2 can be 66.
+	 * ETH(14) + VLAN(4)+ IP(20) + UDP (8) + BTH(20).
+	 * Subtract 20 bytes from QP1 SQ header buf size
+	 */
+	if (is_udp && ip_version == 4)
+		sge->length -= 20;
+	/*
+	 * Max Header buf size for RoCE V1 is 78.
+	 * ETH(14) + VLAN(4) + GRH(40) + BTH(20).
+	 * Subtract 8 bytes from QP1 SQ header buf size
+	 */
+	if (!is_udp)
+		sge->length -= 8;
+
+	/* Subtract 4 bytes for non vlan packets */
+	if (!is_vlan)
+		sge->length -= 4;
+
+	rc = xsc_icrc_hdr(dev, hdr_buf, sge->length - sizeof(struct ib_unpacked_eth), crc);
+	if (rc) {
+		ibdev_err(&dev->ib_dev, "CRC error: hdr size %ld\n",
+			  sge->length - sizeof(struct ib_unpacked_eth));
+	}
+	return rc;
+}
+
+static void zero_send_ds(struct xsc_ib_qp *qp, int idx)
+{
+	void *seg;
+	int i;
+	int ds_num;
+	u64 *p;
+
+	ds_num = XSC_SEND_SEG_NUM << (qp->sq.wqe_shift - XSC_SEND_WQE_SHIFT);
+	seg = (void *)xsc_get_send_wqe(qp, idx);
+	for (i = 1; i < ds_num; i++) {
+		p = get_seg_wqe(seg, i);
+		p[0] = 0;
+		p[1] = 0;
+	}
+}
+
+int xsc_ib_post_send(struct ib_qp *ibqp, const struct ib_send_wr *wr,
+		     const struct ib_send_wr **bad_wr)
+{
+	struct xsc_ib_qp *qp = to_xqp(ibqp);
+	struct xsc_ib_dev *dev = to_xdev(ibqp->device);
+	void *seg;
+	struct xsc_send_wqe_ctrl_seg *ctrl;
+	struct xsc_wqe_data_seg *data_seg;
+	u32 crc;
+	int nreq = 0;
+	int err = 0;
+	int i;
+	unsigned int idx;
+	unsigned long irqflag = 0;
+	struct ib_sge sg;
+	u8 *cur_p = NULL;
+	u8 *mad_send_base = NULL;
+	void *vaddr;
+	struct xsc_msg_opcode *xsc_ib_opcode = dev->xdev->board_info->msg_opcode;
+
+	spin_lock_irqsave(&qp->sq.lock, irqflag);
+
+	for (nreq = 0; wr; ++nreq, wr = wr->next) {
+		unsigned int seg_index = 1;
+		unsigned int msg_len = 0;
+		struct ib_sge *sgl = &wr->sg_list[0];
+		int sg_n = wr->num_sge;
+
+		if (unlikely(wr->opcode == IB_WR_REG_MR ||
+			     wr->opcode == IB_WR_LOCAL_INV ||
+			     wr->opcode == IB_WR_SEND_WITH_INV)) {
+			ibdev_err(&dev->ib_dev, "opcode %d not supported\n",
+				  wr->opcode);
+			err = -EOPNOTSUPP;
+			*bad_wr = wr;
+			goto out;
+		}
+
+		if (unlikely(wr->opcode < 0 || wr->opcode >= ARRAY_SIZE(dev->opcode_map))) {
+			ibdev_err(&dev->ib_dev, "bad opcode %d\n", wr->opcode);
+			err = -EINVAL;
+			*bad_wr = wr;
+			goto out;
+		}
+
+		if (unlikely(xsc_wq_overflow(&qp->sq, nreq,
+					     to_xcq(qp->ibqp.send_cq)))) {
+			ibdev_err(&dev->ib_dev, "send work queue overflow\n");
+			err = -ENOMEM;
+			*bad_wr = wr;
+			goto out;
+		}
+
+		if (unlikely(wr->num_sge > qp->sq.max_gs)) {
+			ibdev_err(&dev->ib_dev, "max gs exceeded %d (max = %d)\n",
+				  wr->num_sge, qp->sq.max_gs);
+			err = -ENOMEM;
+			*bad_wr = wr;
+			goto out;
+		}
+
+		if (unlikely(wr->opcode == IB_WR_RDMA_READ && wr->num_sge > 1)) {
+			ibdev_err(&dev->ib_dev, "rdma read, max gs exceeded %d (max = 1)\n",
+				  wr->num_sge);
+			err = -ENOMEM;
+			*bad_wr = wr;
+			goto out;
+		}
+
+		if (unlikely(qp->post_send_invalid)) {
+			ibdev_dbg(&dev->ib_dev, "qp %d has been draind, post send invalid...\n",
+				  qp->xqp.qpn);
+			err = -EIO;
+			*bad_wr = wr;
+			goto out;
+		}
+
+		idx = qp->sq.cur_post & (qp->sq.wqe_cnt - 1);
+		zero_send_ds(qp, idx);
+		seg = xsc_get_send_wqe(qp, idx);
+		ctrl = seg;
+		xsc_set_wqe_id(dev->xdev, ctrl, qp->sq.cur_post <<
+			       (qp->sq.wqe_shift - XSC_BASE_WQE_SHIFT));
+		ctrl->ds_data_num = 0;
+		ctrl->se = wr->send_flags & IB_SEND_SOLICITED ? 1 : 0;
+		ctrl->ce = wr->send_flags & IB_SEND_SIGNALED ? 1 : 0;
+		for (i = 0; i < wr->num_sge; ++i) {
+			if (likely(wr->sg_list[i].length))
+				msg_len += wr->sg_list[i].length;
+		}
+		ctrl->msg_len = msg_len;
+		ctrl->with_immdt = 0;
+
+		if (unlikely(wr->opcode == IB_WR_RDMA_READ && msg_len == 0)) {
+			ibdev_err(&dev->ib_dev, "rdma read, msg len should not be 0\n");
+			/* workaround, return success for posting zero-length read */
+			err = 0;
+			goto out;
+		}
+		switch (ibqp->qp_type) {
+		case IB_QPT_RC:
+			ctrl->ds_data_num = wr->num_sge;
+			switch (wr->opcode) {
+			case IB_WR_SEND:
+				break;
+			case IB_WR_SEND_WITH_IMM:
+				ctrl->with_immdt = 1;
+				WR_LE_32(ctrl->opcode_data, RD_BE_32(send_ieth(wr)));
+				break;
+			case IB_WR_RDMA_WRITE_WITH_IMM:
+				ctrl->with_immdt = 1;
+				WR_LE_32(ctrl->opcode_data, RD_BE_32(send_ieth(wr)));
+				fallthrough;
+			case IB_WR_RDMA_READ:
+			case IB_WR_RDMA_WRITE:
+				if (ctrl->msg_len == 0)
+					break;
+				ctrl->ds_data_num++;
+				data_seg = get_seg_wqe(ctrl, seg_index);
+				set_remote_addr_seg(dev->xdev, data_seg,
+						    msg_len,
+						    rdma_wr(wr)->remote_addr,
+						    rdma_wr(wr)->rkey);
+				seg_index++;
+				break;
+			default:
+				ibdev_err(&dev->ib_dev, "unsupported send opcode %u\n",
+					  wr->opcode);
+				err = -EPERM;
+				*bad_wr = wr;
+				goto out;
+			}
+			ctrl->msg_opcode = dev->opcode_map[wr->opcode];
+			break;
+		case IB_QPT_UD:
+		case IB_QPT_GSI:
+			ctrl->msg_opcode = xsc_ib_opcode->mad;
+			ctrl->ds_data_num++;
+			data_seg = get_seg_wqe(ctrl, seg_index);
+			mad_send_base = (u8 *)qp->sq.hdr_buf +
+				MAX_QP1_SQ_HDR_SIZE_V2 * qp->sq.mad_index;
+
+			err = build_qp1_send_v2(dev, qp, wr, &sg, msg_len, &crc);
+			if (err) {
+				*bad_wr = wr;
+				goto out;
+			}
+
+			cur_p = mad_send_base + sg.length;
+			for (i = 0; i < wr->num_sge; ++i) {
+				if (likely(wr->sg_list[i].length)) {
+					vaddr = xsc_ib_send_mad_sg_virt_addr(&dev->ib_dev, wr, i);
+					memcpy(cur_p, vaddr, wr->sg_list[i].length);
+				}
+				cur_p += wr->sg_list[i].length;
+			}
+			crc = xsc_crc32(dev, crc, mad_send_base + sg.length, ctrl->msg_len);
+			ctrl->msg_len += sg.length;
+			seg_index++;
+
+			*(u32 *)&mad_send_base[ctrl->msg_len] = ~crc;
+			ctrl->msg_len += sizeof(crc);
+			sg.length = ctrl->msg_len;
+			set_local_data_seg(dev->xdev, data_seg, &sg);
+			ibdev_dbg(&dev->ib_dev, "qp[%d] send MAD packet, msg_len:%d\n",
+				  qp->xqp.qpn, ctrl->msg_len);
+			qp->sq.mad_index = (qp->sq.mad_index + 1) % MAD_QUEUE_DEPTH;
+
+			sg_n = 0;
+			break;
+		default:
+			ibdev_err(&dev->ib_dev, "qp type:%u NOT supported\n", ibqp->qp_type);
+			err = -EPERM;
+			*bad_wr = wr;
+			goto out;
+		}
+
+		if (wr->send_flags & IB_SEND_INLINE && wr->num_sge) {
+			err = set_data_inl_seg(qp, wr, ctrl);
+			if (unlikely(err)) {
+				*bad_wr = wr;
+				ibdev_err(&dev->ib_dev, "inline layout failed, err %d\n", err);
+				goto out;
+			}
+		} else {
+			for (i = 0; i < sg_n; ++i, ++seg_index) {
+				if (likely(sgl[i].length)) {
+					data_seg = get_seg_wqe(ctrl, seg_index);
+					set_local_data_seg(dev->xdev, data_seg, &sgl[i]);
+				}
+			}
+		}
+		qp->sq.wrid[idx] = wr->wr_id;
+		qp->sq.wqe_head[idx] = qp->sq.head + nreq;
+		qp->sq.cur_post += 1;
+		if (ctrl->ce) {
+			atomic_inc(&qp->sq.flush_wqe_cnt);
+			qp->sq.need_flush[idx] = 1;
+			qp->sq.prev_ce_wqe[idx] = qp->sq.last_post_ce_wqe;
+			qp->sq.last_post_ce_wqe = idx;
+		}
+		qp->sq.wr_opcode[idx] = wr->opcode;
+	}
+out:
+	ibdev_dbg(&dev->ib_dev, "nreq:%d\n", nreq);
+	xsc_post_send_db(qp, dev->xdev, nreq);
+	spin_unlock_irqrestore(&qp->sq.lock, irqflag);
+
+	return err;
+}
 
 int xsc_ib_post_recv(struct ib_qp *ibqp, const struct ib_recv_wr *wr,
 		     const struct ib_recv_wr **bad_wr)
@@ -1558,3 +2156,96 @@ struct xsc_ib_drain_cqe {
 	int qpn;
 };
 
+static void xsc_ib_drain_rq_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+	struct xsc_ib_drain_cqe *cqe = container_of(wc->wr_cqe,
+						     struct xsc_ib_drain_cqe, cqe);
+	struct xsc_ib_dev *dev = to_xdev(cq->device);
+
+	complete(&cqe->done);
+	ibdev_dbg(&dev->ib_dev, "QP %d ib_drain_rq done!", cqe->qpn);
+}
+
+static void xsc_ib_drain_sq_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+	struct xsc_ib_drain_cqe *cqe = container_of(wc->wr_cqe,
+						     struct xsc_ib_drain_cqe, cqe);
+	struct xsc_ib_dev *dev = to_xdev(cq->device);
+
+	complete(&cqe->done);
+	ibdev_dbg(&dev->ib_dev, "QP %d ib_drain_sq done!", cqe->qpn);
+}
+
+void xsc_ib_drain_rq(struct ib_qp *qp)
+{
+	struct ib_cq *cq = qp->recv_cq;
+	struct xsc_ib_cq *xcq = to_xcq(cq);
+	struct xsc_ib_qp *xqp = to_xqp(qp);
+	struct ib_qp_attr attr = { .qp_state = IB_QPS_ERR };
+	struct xsc_ib_drain_cqe rdrain;
+	const struct ib_recv_wr rwr = {
+		.next = NULL,
+		{.wr_cqe = &rdrain.cqe,},
+	};
+	const struct ib_recv_wr *bad_rwr;
+	int ret;
+	struct xsc_ib_dev *dev = to_xdev(qp->device);
+
+	ret = ib_modify_qp(qp, &attr, IB_QP_STATE);
+	if (ret) {
+		ibdev_err(&dev->ib_dev, "failed to drain recv queue: %d\n", ret);
+		return;
+	}
+
+	rdrain.qpn = xqp->xqp.qpn;
+	rdrain.cqe.done = xsc_ib_drain_rq_done;
+	init_completion(&rdrain.done);
+
+	ret = xsc_ib_post_recv(qp, &rwr, &bad_rwr);
+	if (ret) {
+		ibdev_err(&dev->ib_dev, "failed to drain recv queue: %d\n", ret);
+		return;
+	}
+	xqp->post_recv_invalid = true;
+
+	xcq->xcq.comp(&xcq->xcq);
+	wait_for_completion(&rdrain.done);
+}
+
+void xsc_ib_drain_sq(struct ib_qp *qp)
+{
+	struct ib_cq *cq = qp->send_cq;
+	struct xsc_ib_cq *xcq = to_xcq(cq);
+	struct xsc_ib_qp *xqp = to_xqp(qp);
+	struct ib_qp_attr attr = { .qp_state = IB_QPS_ERR };
+	struct xsc_ib_drain_cqe sdrain;
+	const struct ib_send_wr *bad_swr;
+	const struct ib_send_wr swr = {
+		.next = NULL,
+		{ .wr_cqe	= &sdrain.cqe, },
+		.opcode	= IB_WR_RDMA_WRITE,
+		.send_flags = IB_SEND_SIGNALED,
+	};
+	int ret;
+	struct xsc_ib_dev *dev = to_xdev(qp->device);
+
+	ret = ib_modify_qp(qp, &attr, IB_QP_STATE);
+	if (ret) {
+		ibdev_err(&dev->ib_dev, "failed to drain send queue: %d\n", ret);
+		return;
+	}
+
+	sdrain.qpn = xqp->xqp.qpn;
+	sdrain.cqe.done = xsc_ib_drain_sq_done;
+	init_completion(&sdrain.done);
+
+	ret = xsc_ib_post_send(qp, &swr, &bad_swr);
+	if (ret) {
+		ibdev_err(&dev->ib_dev, "failed to drain send queue: %d\n", ret);
+		return;
+	}
+	xqp->post_send_invalid = true;
+
+	xcq->xcq.comp(&xcq->xcq);
+	wait_for_completion(&sdrain.done);
+}

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

* [PATCH 14/15] yunsilicon: add RDMA counters and sysfs
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (12 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 13/15] yunsilicon: add the RDMA send path Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13 11:46   ` sashiko-bot
  2026-10-12 11:44 ` [PATCH 15/15] MAINTAINERS: add the Yunsilicon XSC drivers Xin Tian
  2026-10-13 14:34 ` [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Leon Romanovsky
  15 siblings, 1 reply; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add the hardware counter reporting through the RDMA counter interface,
the global counter sysfs attributes and the hca_type/hw_rev sysfs
attributes.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 drivers/infiniband/hw/xsc/Makefile       |   3 +-
 drivers/infiniband/hw/xsc/counters.c     | 422 +++++++++++++++++++++++
 drivers/infiniband/hw/xsc/counters.h     |  31 ++
 drivers/infiniband/hw/xsc/main.c         |   4 +
 drivers/infiniband/hw/xsc/xsc_ib_sysfs.c |  64 ++++
 5 files changed, 523 insertions(+), 1 deletion(-)
 create mode 100644 drivers/infiniband/hw/xsc/counters.c
 create mode 100644 drivers/infiniband/hw/xsc/counters.h
 create mode 100644 drivers/infiniband/hw/xsc/xsc_ib_sysfs.c

diff --git a/drivers/infiniband/hw/xsc/Makefile b/drivers/infiniband/hw/xsc/Makefile
index d5ad0e573..48a6d2b0a 100644
--- a/drivers/infiniband/hw/xsc/Makefile
+++ b/drivers/infiniband/hw/xsc/Makefile
@@ -6,4 +6,5 @@ ccflags-y := -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
 
 obj-$(CONFIG_INFINIBAND_XSC) += xsc_ib.o
 
-xsc_ib-y := main.o cq.o qp.o mem.o mr.o srq.o ah.o
+xsc_ib-y := main.o cq.o qp.o mem.o mr.o ah.o counters.o \
+		xsc_ib_sysfs.o srq.o
diff --git a/drivers/infiniband/hw/xsc/counters.c b/drivers/infiniband/hw/xsc/counters.c
new file mode 100644
index 000000000..135df9595
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/counters.c
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include "common/xsc_core.h"
+#include "common/xsc_hsi.h"
+#include "common/driver.h"
+#include "common/xsc_cmd.h"
+#include "counters.h"
+#include "xsc_ib.h"
+
+#define COUNTERS_FILE_NAME         "counters"
+#define COUNTERS_NAMES_FILE_NAME   "counters_names"
+#define COUNTERS_VALUE_FILE_NAME   "counters_value"
+#define COUNTERS_ATTER_GROUP_NAME  "counters"
+#define GLOBAL_COUNTERS_GROUP_NAME "global_counters"
+#define GLOBAL_COUNTERS_FILE_NAME  "counters"
+
+static const struct counter_desc hw_rdma_stats_pf_desc[] = {
+	/*by mac port*/
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_tx_pkts) },//0
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_tx_bytes) },//1
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_rx_pkts) },//2
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_rx_bytes) },//3
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  np_cnp_sent) },//4
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rp_cnp_handled) },//5
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  np_ecn_marked_roce_packets) },//6
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rp_cnp_ignored) },//7
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  read_rsp_out_of_seq) },//8
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  implied_nak_seq_err) },//9
+	/*by function*/
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  out_of_sequence) },//10
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  packet_seq_err) },//11
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  out_of_buffer) },//12
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rnr_nak_retry_err) },//13
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  local_ack_timeout_err) },//14
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rx_read_requests) },//15
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rx_write_requests) },//16
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  duplicate_requests) },//17
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_tx_pkts_func) },//18
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_tx_payload_bytes) },//19
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_rx_pkts_func) },//20
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_rx_payload_bytes) },//21
+	/*global*/
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_loopback_pkts) },//22
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_loopback_bytes) },//23
+	/*for diamond*/
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  out_of_sequence_sr) },//24
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  packet_seq_err_sr) },//25
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_ndp_rx_pkts) },//26
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_ndp_rx_trimmed_pkts) },//27
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rdma_ndp_trimmed_pkts_sr) },//28
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_pf,  rx_icrc_encapsulated) },//29
+	/*XSC_CMDQ_GET_HW_STATS_RDMA_V1*/
+};
+
+static const struct counter_desc hw_rdma_stats_vf_desc[] = {
+	/*by function*/
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  rdma_tx_pkts_func) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  rdma_tx_payload_bytes) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  rdma_rx_pkts_func) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  rdma_rx_payload_bytes) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  out_of_sequence) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  packet_seq_err) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  out_of_buffer) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  rnr_nak_retry_err) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  local_ack_timeout_err) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  rx_read_requests) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  rx_write_requests) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_stats_rdma_vf,  duplicate_requests) },
+};
+
+static const struct counter_desc hw_global_rdma_stats_desc[] = {
+	{ XSC_DECLARE_STAT(struct xsc_hw_global_stats_rdma,  rdma_loopback_pkts) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_global_stats_rdma,  rdma_loopback_bytes) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_global_stats_rdma,  rx_icrc_encapsulated) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_global_stats_rdma,  req_cqe_error) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_global_stats_rdma,  resp_cqe_error) },
+	{ XSC_DECLARE_STAT(struct xsc_hw_global_stats_rdma,  cqe_msg_code_error) },
+};
+
+static int get_hw_stats_rdma(struct xsc_core_device *dev, struct xsc_hw_stats_rdma *stats_rdma)
+{
+	int ret;
+	struct xsc_hw_stats_mbox_in in;
+	struct xsc_hw_stats_rdma_mbox_out out;
+
+	memset(stats_rdma, 0, sizeof(*stats_rdma));
+
+	if (!dev)
+		return -1;
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+	in.mac_port = dev->mac_port;
+	in.hdr.ver = cpu_to_be16(XSC_CMDQ_GET_HW_STATS_RDMA_V1);
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_QUERY_HW_STATS_RDMA);
+
+	ret = xsc_cmd_exec(dev, (void *)&in, sizeof(in), (void *)&out, sizeof(out));
+	if (ret || out.hdr.status)
+		return -1;
+
+	memcpy(stats_rdma, &out.hw_stats, sizeof(*stats_rdma));
+
+	return 0;
+}
+
+static ssize_t counters_names_show(struct device *device,
+				   struct device_attribute *attr, char *buf)
+{
+	struct xsc_ib_dev *ibdev = container_of(device, struct xsc_ib_dev, ib_dev.dev);
+	struct xsc_core_device *xdev = ibdev->xdev;
+	struct xsc_hw_stats_rdma stats_rdma;
+	const struct counter_desc *desc;
+	int desc_size;
+	ssize_t count = 0;
+	u64 mask;
+	int i;
+
+	if (get_hw_stats_rdma(xdev, &stats_rdma))
+		return 0;
+
+	if (is_support_hw_pf_stats(xdev)) {
+		desc = hw_rdma_stats_pf_desc;
+		desc_size = ARRAY_SIZE(hw_rdma_stats_pf_desc);
+	} else {
+		desc = hw_rdma_stats_vf_desc;
+		desc_size = ARRAY_SIZE(hw_rdma_stats_vf_desc);
+	}
+
+	mask = xsc_get_rdma_stat_mask(xdev, be16_to_cpu(stats_rdma.ver));
+	for (i = 0; i < desc_size; i++) {
+		if (!((1ULL << i) & mask))
+			continue;
+		count += sysfs_emit_at(buf, count, "%s\n", desc[i].format);
+	}
+
+	return count;
+}
+static DEVICE_ATTR_RO(counters_names);
+
+static ssize_t counters_show(struct device *device,
+			     struct device_attribute *attr, char *buf)
+{
+	struct xsc_ib_dev *ibdev = container_of(device, struct xsc_ib_dev, ib_dev.dev);
+	struct xsc_core_device *xdev = ibdev->xdev;
+	struct xsc_hw_stats_rdma stats_rdma;
+	const struct counter_desc *desc;
+	int desc_size;
+	ssize_t count = 0;
+	u8 *stats;
+	u64 mask;
+	int i;
+
+	if (get_hw_stats_rdma(xdev, &stats_rdma) ||
+	    is_support_hw_pf_stats(xdev) != stats_rdma.is_pf)
+		return 0;
+
+	if (is_support_hw_pf_stats(xdev)) {
+		desc = hw_rdma_stats_pf_desc;
+		desc_size = ARRAY_SIZE(hw_rdma_stats_pf_desc);
+		stats = (u8 *)&stats_rdma.stats.pf_stats;
+	} else {
+		desc = hw_rdma_stats_vf_desc;
+		desc_size = ARRAY_SIZE(hw_rdma_stats_vf_desc);
+		stats = (u8 *)&stats_rdma.stats.vf_stats;
+	}
+
+	mask = xsc_get_rdma_stat_mask(xdev, be16_to_cpu(stats_rdma.ver));
+	for (i = 0; i < desc_size; i++) {
+		u64 value;
+
+		if (!((1ULL << i) & mask))
+			continue;
+		value = be64_to_cpu(*(u64 *)(stats + desc[i].offset));
+		count += sysfs_emit_at(buf, count, "%-26s    %-20llu\n",
+				       desc[i].format, value);
+	}
+
+	return count;
+}
+static DEVICE_ATTR_RO(counters);
+
+static ssize_t counters_value_read(struct file *file, struct kobject *kob,
+				   const struct bin_attribute *bin_attr,
+				   char *buf, loff_t loff, size_t size)
+{
+	struct xsc_ib_dev *ibdev = container_of(kob, struct xsc_ib_dev, ib_dev.dev.kobj);
+	struct xsc_core_device *xdev = ibdev->xdev;
+	struct xsc_hw_stats_rdma stats_rdma;
+	const struct counter_desc *desc;
+	int desc_size;
+	int bin_size;
+	u64 *tmp_value;
+	u8 *stats;
+	u64 mask;
+	int i, j;
+
+	if (bin_attr->size > size || bin_attr->size == 0)
+		return 0;
+
+	if (get_hw_stats_rdma(xdev, &stats_rdma) ||
+	    is_support_hw_pf_stats(xdev) != stats_rdma.is_pf)
+		return 0;
+
+	if (is_support_hw_pf_stats(xdev)) {
+		desc = hw_rdma_stats_pf_desc;
+		desc_size = ARRAY_SIZE(hw_rdma_stats_pf_desc);
+		stats = (u8 *)&stats_rdma.stats.pf_stats;
+	} else {
+		desc = hw_rdma_stats_vf_desc;
+		desc_size = ARRAY_SIZE(hw_rdma_stats_vf_desc);
+		stats = (u8 *)&stats_rdma.stats.vf_stats;
+	}
+
+	bin_size = desc_size * sizeof(u64);
+	if (bin_attr->size < bin_size)
+		return 0;
+
+	tmp_value = kzalloc(bin_attr->size, GFP_KERNEL);
+	if (!tmp_value)
+		return 0;
+
+	mask = xsc_get_rdma_stat_mask(xdev, be16_to_cpu(stats_rdma.ver));
+	j = 0;
+	for (i = 0; i < desc_size; i++) {
+		if (!((1ULL << i) & mask))
+			continue;
+		tmp_value[j] = be64_to_cpu(*(u64 *)(stats + desc[i].offset));
+		j++;
+	}
+
+	memcpy(buf, tmp_value, bin_attr->size);
+
+	kfree(tmp_value);
+	return bin_attr->size;
+}
+static BIN_ATTR_RO(counters_value, sizeof(struct xsc_hw_stats_rdma));
+
+static struct attribute *counters_attrs[] = {
+	&dev_attr_counters_names.attr,
+	&dev_attr_counters.attr,
+	NULL,
+};
+
+static const struct bin_attribute *const counters_bin_attrs[] = {
+	&bin_attr_counters_value,
+	NULL,
+};
+
+static const struct attribute_group counters_group = {
+	.name = "counters",
+	.attrs = counters_attrs,
+	.bin_attrs = counters_bin_attrs,
+};
+
+static int counters_sysfs_init(struct ib_device *ib_dev)
+{
+	return sysfs_create_group(&ib_dev->dev.kobj, &counters_group);
+}
+
+static void counters_sysfs_fini(struct ib_device *ib_dev)
+{
+	sysfs_remove_group(&ib_dev->dev.kobj, &counters_group);
+}
+
+static ssize_t global_cnt_attr_show(struct kobject *kobj,
+				    struct attribute *attr, char *buf)
+{
+	struct xsc_global_cnt_attributes *a =
+		container_of(attr, struct xsc_global_cnt_attributes, attr);
+	struct xsc_global_cnt_interface *g =
+		container_of(kobj, struct xsc_global_cnt_interface, kobj);
+
+	if (!a->show)
+		return -EIO;
+
+	return a->show(g, a, buf);
+}
+
+static ssize_t global_cnt_attr_store(struct kobject *kobj,
+				     struct attribute *attr,
+				     const char *buf, size_t size)
+{
+	struct xsc_global_cnt_attributes *a =
+		container_of(attr, struct xsc_global_cnt_attributes, attr);
+	struct xsc_global_cnt_interface *g =
+		container_of(kobj, struct xsc_global_cnt_interface, kobj);
+
+	if (!a->store)
+		return -EIO;
+
+	return a->store(g, a, buf, size);
+}
+
+static ssize_t global_counters_show(struct xsc_global_cnt_interface *g,
+				    struct xsc_global_cnt_attributes *a, char *buf)
+{
+	int i;
+	int ret;
+	u8 *stats;
+	u64 value;
+	int desc_size;
+	ssize_t count = 0;
+	const struct counter_desc *desc;
+	struct xsc_hw_global_stats_mbox_in in;
+	struct xsc_hw_global_stats_mbox_out out;
+
+	memset(&in, 0, sizeof(in));
+	memset(&out, 0, sizeof(out));
+	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_QUERY_HW_GLOBAL_STATS);
+	ret = xsc_cmd_exec(g->xdev, (void *)&in, sizeof(in),
+			   (void *)&out, sizeof(out));
+	if (ret || out.hdr.status)
+		return 0;
+
+	desc = &hw_global_rdma_stats_desc[0];
+	desc_size = ARRAY_SIZE(hw_global_rdma_stats_desc);
+	stats = (u8 *)&out.hw_stats;
+
+	for (i = 0 ; i < desc_size; i++) {
+		value = *(u64 *)(stats + desc[i].offset);
+		value = be64_to_cpu(value);
+		count += sprintf(&buf[count], "%-26s    %-20llu\n",
+				desc[i].format, value);
+	}
+
+	return count;
+}
+
+static ssize_t global_counters_store(struct xsc_global_cnt_interface *g,
+				     struct xsc_global_cnt_attributes *a,
+				     const char *buf, size_t count)
+{
+	return -EOPNOTSUPP;
+}
+
+#define GLOBAL_CNT_ATTR(_name) struct xsc_global_cnt_attributes xsc_global_cnt_attr_##_name = \
+	__ATTR(_name, 0444, global_##_name##_show, global_##_name##_store)
+
+GLOBAL_CNT_ATTR(counters);
+
+static const struct sysfs_ops global_cnt_sysfs_ops = {
+	.show = global_cnt_attr_show,
+	.store = global_cnt_attr_store,
+};
+
+static struct attribute *global_cnt_attrs[] = {
+	&xsc_global_cnt_attr_counters.attr,
+	NULL
+};
+
+ATTRIBUTE_GROUPS(global_cnt);
+
+static const struct kobj_type global_cnt_ktype = {
+	.sysfs_ops     = &global_cnt_sysfs_ops,
+	.default_groups = global_cnt_groups,
+};
+
+static struct xsc_global_cnt_interface *g_global_cnt_interface;
+
+static int global_cnt_sysfs_init(struct ib_device *ib_dev, struct xsc_core_device *xdev)
+{
+	struct xsc_global_cnt_interface *tmp;
+	int err;
+
+	if (!xdev || !xsc_core_is_pf(xdev) || xdev->pf_id != 0)
+		return 0;
+
+	tmp = kzalloc_obj(*tmp, GFP_KERNEL);
+	if (!tmp)
+		return -ENOMEM;
+
+	err = kobject_init_and_add(&tmp->kobj, &global_cnt_ktype,
+				   &ib_dev->dev.kobj, GLOBAL_COUNTERS_GROUP_NAME);
+	if (err)
+		goto error_return;
+
+	g_global_cnt_interface = tmp;
+	tmp->xdev = xdev;
+	return 0;
+
+error_return:
+	kobject_put(&tmp->kobj);
+	kfree(tmp);
+	return err;
+}
+
+static void global_cnt_sysfs_fini(struct xsc_core_device *xdev)
+{
+	if (!g_global_cnt_interface || !xdev || !xsc_core_is_pf(xdev) || xdev->pf_id != 0)
+		return;
+
+	kobject_put(&g_global_cnt_interface->kobj);
+	kfree(g_global_cnt_interface);
+	g_global_cnt_interface = NULL;
+}
+
+int xsc_counters_init(struct ib_device *ib_dev, struct xsc_core_device *xdev)
+{
+	int ret;
+
+	ret = counters_sysfs_init(ib_dev);
+	if (ret)
+		return ret;
+
+	ret = global_cnt_sysfs_init(ib_dev, xdev);
+	if (ret)
+		counters_sysfs_fini(ib_dev);
+
+	return ret;
+}
+
+void xsc_counters_fini(struct ib_device *ib_dev, struct xsc_core_device *xdev)
+{
+	counters_sysfs_fini(ib_dev);
+	global_cnt_sysfs_fini(xdev);
+}
diff --git a/drivers/infiniband/hw/xsc/counters.h b/drivers/infiniband/hw/xsc/counters.h
new file mode 100644
index 000000000..944dbafb6
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/counters.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#ifndef __COUNTERS_H__
+#define __COUNTERS_H__
+
+#define STRING_LEN		32
+#define XSC_DECLARE_STAT(type, fld)	""#fld, offsetof(type, fld)
+
+struct counter_desc {
+	char		format[STRING_LEN];
+	size_t		offset; /* Byte offset */
+};
+
+struct xsc_global_cnt_interface {
+	struct xsc_core_device  *xdev;
+	struct kobject		kobj;
+};
+
+struct xsc_global_cnt_attributes {
+	struct attribute attr;
+	ssize_t (*show)(struct xsc_global_cnt_interface *g, struct xsc_global_cnt_attributes *a,
+			char *buf);
+	ssize_t (*store)(struct xsc_global_cnt_interface *g, struct xsc_global_cnt_attributes *a,
+			 const char *buf, size_t count);
+};
+
+#endif
diff --git a/drivers/infiniband/hw/xsc/main.c b/drivers/infiniband/hw/xsc/main.c
index 30927006b..38c8b4f06 100644
--- a/drivers/infiniband/hw/xsc/main.c
+++ b/drivers/infiniband/hw/xsc/main.c
@@ -745,7 +745,9 @@ static int init_one(struct xsc_core_device *xdev,
 
 	xsc_register_netdev_notifier(dev);
 
+	xsc_counters_init(&dev->ib_dev, xdev);
 
+	xsc_ib_sysfs_init(&dev->ib_dev, xdev);
 
 	return 0;
 
@@ -759,6 +761,8 @@ static void remove_one(struct xsc_core_device *xdev, void *intf_ctx)
 {
 	struct xsc_ib_dev *dev = (struct xsc_ib_dev *)intf_ctx;
 
+	xsc_ib_sysfs_fini(&dev->ib_dev, xdev);
+	xsc_counters_fini(&dev->ib_dev, xdev);
 	xsc_unregister_netdev_notifier(dev);
 	ib_unregister_device(&dev->ib_dev);
 	ib_dealloc_device(&dev->ib_dev);
diff --git a/drivers/infiniband/hw/xsc/xsc_ib_sysfs.c b/drivers/infiniband/hw/xsc/xsc_ib_sysfs.c
new file mode 100644
index 000000000..a0b577bde
--- /dev/null
+++ b/drivers/infiniband/hw/xsc/xsc_ib_sysfs.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/time.h>
+#include "common/xsc_core.h"
+#include "common/xsc_hsi.h"
+#include "common/driver.h"
+#include "common/xsc_cmd.h"
+#include "xsc_ib.h"
+
+static ssize_t hca_type_show(struct device *device, struct device_attribute *attr, char *buf)
+{
+	struct ib_device *ib_dev = container_of(device, struct ib_device, dev);
+	struct xsc_core_device *dev = to_xdev(ib_dev)->xdev;
+	struct pci_dev *pdev = dev->pdev;
+
+	return sprintf(buf, "%x\n", pdev->subsystem_device);
+}
+
+static DEVICE_ATTR_RO(hca_type);
+
+static ssize_t hw_rev_show(struct device *device, struct device_attribute *attr, char *buf)
+{
+	struct ib_device *ib_dev = container_of(device, struct ib_device, dev);
+	struct xsc_core_device *dev = to_xdev(ib_dev)->xdev;
+	u32 hw_ver = 0;
+
+	hw_ver = ((dev->chip_ver_l & 0xffff) << 16) |
+		(dev->hotfix_num & 0xffff);
+	return sprintf(buf, "0x%x\n", hw_ver);
+}
+
+static DEVICE_ATTR_RO(hw_rev);
+
+static struct device_attribute *xsc_ib_attributes[] = {
+	&dev_attr_hca_type,
+	&dev_attr_hw_rev,
+};
+
+void xsc_ib_sysfs_init(struct ib_device *ib_dev, struct xsc_core_device *xdev)
+{
+	int err = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(xsc_ib_attributes); i++) {
+		err = device_create_file(&ib_dev->dev, xsc_ib_attributes[i]);
+		if (err)
+			dev_err(&xdev->pdev->dev, "Create sysfs file for %s failed.\n",
+				xsc_ib_attributes[i]->attr.name);
+	}
+}
+
+void xsc_ib_sysfs_fini(struct ib_device *ib_dev, struct xsc_core_device *xdev)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(xsc_ib_attributes); i++)
+		device_remove_file(&ib_dev->dev, xsc_ib_attributes[i]);
+}

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

* [PATCH 15/15] MAINTAINERS: add the Yunsilicon XSC drivers
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (13 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 14/15] yunsilicon: add RDMA counters and sysfs Xin Tian
@ 2026-10-12 11:44 ` Xin Tian
  2026-10-13 14:34 ` [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Leon Romanovsky
  15 siblings, 0 replies; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Add an entry covering the Yunsilicon XSC ethernet and RDMA drivers, with
the netdev and linux-rdma lists on Cc.

Signed-off-by: Xin Tian <tianx@yunsilicon.com>
---
 MAINTAINERS | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index c24144478..34902a130 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -30069,6 +30069,15 @@ S:	Maintained
 F:	Documentation/input/devices/yealink.rst
 F:	drivers/input/misc/yealink.*
 
+YUNSILICON XSC DRIVERS
+M:	Honggang Wei <weihg@yunsilicon.com>
+M:	Xin Tian <tianx@yunsilicon.com>
+L:	netdev@vger.kernel.org
+L:	linux-rdma@vger.kernel.org
+S:	Maintained
+F:	drivers/infiniband/hw/xsc
+F:	drivers/net/ethernet/yunsilicon/xsc
+
 ZD1211RW WIRELESS DRIVER
 L:	linux-wireless@vger.kernel.org
 S:	Orphan

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

* [PATCH 00/15] Yunsilicon ethernet and RDMA drivers
@ 2026-10-12 11:44 Xin Tian
  2026-10-12 11:43 ` [PATCH 01/15] yunsilicon: add PCI core driver skeleton Xin Tian
                   ` (15 more replies)
  0 siblings, 16 replies; 35+ messages in thread
From: Xin Tian @ 2026-10-12 11:44 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma

Hi all,

This is a new submission from Yunsilicon to upstream our converged
network adapter driver.  The hardware is a dual-function PCIe device
(PCI vendor 0x1f67) that supports both ethernet and RDMA operations.

The series adds three drivers, which share the xsc prefix.  A shared PCI
core driver binds to the physical function, brings up the command queue,
the event queues and the interrupt handling, and publishes one auxiliary
device per function.  The ethernet and RDMA drivers bind to that
auxiliary device instead of driving the PCI function themselves, so the
two personalities share a single probe and resource set.

  1. xsc_pci - the PCI core driver (CONFIG_YUNSILICON_XSC_PCI).
     Owns the PCI function, the command queue, the event queues, the
     interrupt vectors, the register access layer and the CQ/QP/SRQ
     resource pools.  Patches 01-05.

  2. xsc_eth - the ethernet driver (CONFIG_YUNSILICON_XSC_ETH).
     Registers the netdev, implements the TX/RX packet paths with NAPI
     and page_pool, RSS, checksum offloads and statistics.  Patches
     06-08, on top of the core.

  3. xsc_ib - the RDMA driver (CONFIG_INFINIBAND_XSC).
     Registers the ib_device and provides the PD/MR, CQ/SRQ/AH and QP
     verbs including the send/receive data paths, plus RDMA counters and
     sysfs attributes.  Patches 09-14, on top of the core and the
     ethernet driver (the netdev supplies port state and GID).

Patch 15 adds the MAINTAINERS entry.

The series is split so that every commit builds: the core patches
introduce the register access, the command queue, the event queues and
the resource pools incrementally, and the ethernet and RDMA patches only
start creating queues once their dependencies are in place.  Patch 05
(core CQ/QP pools) is deliberately shared by both upper drivers; that is
why it precedes the ethernet patches.

The RDMA device is registered at patch 09, before all verbs exist, so
kverbs_provider stays false until the mandatory verbs appear in patches
10-13.  This keeps each intermediate tree compilable.

Please review.

Thanks,
Xin Tian

Xin Tian (15):
  yunsilicon: add PCI core driver skeleton
  yunsilicon: add the register HAL
  yunsilicon: add the command queue
  yunsilicon: add event queues, interrupts and the auxiliary bus
  yunsilicon: add the CQ and QP resource pools
  yunsilicon: add ethernet netdev registration
  yunsilicon: add ethernet TX path
  yunsilicon: add ethernet RX path and statistics
  yunsilicon: add RDMA device support
  yunsilicon: add RDMA PD/MR
  yunsilicon: add RDMA CQ/SRQ/AH
  yunsilicon: add RDMA QP verbs
  yunsilicon: add the RDMA send path
  yunsilicon: add RDMA counters and sysfs
  MAINTAINERS: add the Yunsilicon XSC drivers

 MAINTAINERS                                   |    9 +
 drivers/infiniband/Kconfig                    |    1 +
 drivers/infiniband/hw/Makefile                |    1 +
 drivers/infiniband/hw/xsc/Kconfig             |   17 +
 drivers/infiniband/hw/xsc/Makefile            |   10 +
 drivers/infiniband/hw/xsc/ah.c                |  144 ++
 drivers/infiniband/hw/xsc/counters.c          |  422 +++
 drivers/infiniband/hw/xsc/counters.h          |   31 +
 drivers/infiniband/hw/xsc/cq.c                |  895 +++++++
 drivers/infiniband/hw/xsc/main.c              |  887 +++++++
 drivers/infiniband/hw/xsc/mem.c               |  161 ++
 drivers/infiniband/hw/xsc/mr.c                |  313 +++
 drivers/infiniband/hw/xsc/qp.c                | 2251 +++++++++++++++++
 drivers/infiniband/hw/xsc/srq.c               |  426 ++++
 drivers/infiniband/hw/xsc/xsc_ib.h            |  542 ++++
 drivers/infiniband/hw/xsc/xsc_ib_sysfs.c      |   64 +
 drivers/net/ethernet/Kconfig                  |    1 +
 drivers/net/ethernet/Makefile                 |    1 +
 drivers/net/ethernet/yunsilicon/Kconfig       |   26 +
 drivers/net/ethernet/yunsilicon/Makefile      |    8 +
 .../net/ethernet/yunsilicon/xsc/common/cq.h   |   30 +
 .../ethernet/yunsilicon/xsc/common/driver.h   |   95 +
 .../net/ethernet/yunsilicon/xsc/common/qp.h   |  104 +
 .../net/ethernet/yunsilicon/xsc/common/srq.h  |   54 +
 .../ethernet/yunsilicon/xsc/common/xsc_cmd.h  | 1022 ++++++++
 .../yunsilicon/xsc/common/xsc_cmd_api.h       |   25 +
 .../ethernet/yunsilicon/xsc/common/xsc_cmdq.h |  192 ++
 .../ethernet/yunsilicon/xsc/common/xsc_core.h |  707 ++++++
 .../yunsilicon/xsc/common/xsc_device.h        |   94 +
 .../ethernet/yunsilicon/xsc/common/xsc_hsi.h  |  123 +
 .../ethernet/yunsilicon/xsc/common/xsc_reg.h  |   28 +
 .../net/ethernet/yunsilicon/xsc/net/Kconfig   |   17 +
 .../net/ethernet/yunsilicon/xsc/net/Makefile  |    9 +
 .../net/ethernet/yunsilicon/xsc/net/main.c    | 1724 +++++++++++++
 .../net/ethernet/yunsilicon/xsc/net/xsc_eth.h |   45 +
 .../yunsilicon/xsc/net/xsc_eth_common.h       |  124 +
 .../ethernet/yunsilicon/xsc/net/xsc_eth_rx.c  |  551 ++++
 .../yunsilicon/xsc/net/xsc_eth_stats.c        |   39 +
 .../yunsilicon/xsc/net/xsc_eth_stats.h        |   34 +
 .../ethernet/yunsilicon/xsc/net/xsc_eth_tx.c  |  309 +++
 .../yunsilicon/xsc/net/xsc_eth_txrx.c         |  159 ++
 .../yunsilicon/xsc/net/xsc_eth_txrx.h         |   91 +
 .../ethernet/yunsilicon/xsc/net/xsc_eth_wq.c  |   80 +
 .../ethernet/yunsilicon/xsc/net/xsc_eth_wq.h  |  185 ++
 .../net/ethernet/yunsilicon/xsc/net/xsc_pp.h  |   38 +
 .../net/ethernet/yunsilicon/xsc/net/xsc_pph.h |   42 +
 .../ethernet/yunsilicon/xsc/net/xsc_queue.h   |  185 ++
 .../net/ethernet/yunsilicon/xsc/pci/Kconfig   |   15 +
 .../net/ethernet/yunsilicon/xsc/pci/Makefile  |   10 +
 .../net/ethernet/yunsilicon/xsc/pci/adev.c    |  142 ++
 .../net/ethernet/yunsilicon/xsc/pci/adev.h    |   17 +
 .../net/ethernet/yunsilicon/xsc/pci/alloc.c   |  237 ++
 .../net/ethernet/yunsilicon/xsc/pci/alloc.h   |   17 +
 .../net/ethernet/yunsilicon/xsc/pci/cmdq.c    | 1247 +++++++++
 drivers/net/ethernet/yunsilicon/xsc/pci/cq.c  |  316 +++
 drivers/net/ethernet/yunsilicon/xsc/pci/cq.h  |   17 +
 drivers/net/ethernet/yunsilicon/xsc/pci/eq.c  |  320 +++
 drivers/net/ethernet/yunsilicon/xsc/pci/eq.h  |   39 +
 .../yunsilicon/xsc/pci/hal/andes_impl.c       |  708 ++++++
 .../yunsilicon/xsc/pci/hal/andes_reg.h        |   43 +
 .../ethernet/yunsilicon/xsc/pci/hal/xsc_hal.h |   85 +
 drivers/net/ethernet/yunsilicon/xsc/pci/hw.c  |  310 +++
 drivers/net/ethernet/yunsilicon/xsc/pci/hw.h  |   18 +
 .../net/ethernet/yunsilicon/xsc/pci/link.c    |   35 +
 .../net/ethernet/yunsilicon/xsc/pci/main.c    |  348 +++
 drivers/net/ethernet/yunsilicon/xsc/pci/mr.c  |  212 ++
 drivers/net/ethernet/yunsilicon/xsc/pci/mr.h  |   18 +
 .../net/ethernet/yunsilicon/xsc/pci/pci_irq.c |  498 ++++
 .../net/ethernet/yunsilicon/xsc/pci/pci_irq.h |   18 +
 drivers/net/ethernet/yunsilicon/xsc/pci/pd.c  |   55 +
 drivers/net/ethernet/yunsilicon/xsc/pci/pd.h  |   14 +
 drivers/net/ethernet/yunsilicon/xsc/pci/qp.c  |  356 +++
 drivers/net/ethernet/yunsilicon/xsc/pci/qp.h  |   28 +
 drivers/net/ethernet/yunsilicon/xsc/pci/srq.c |   92 +
 drivers/net/ethernet/yunsilicon/xsc/pci/srq.h |   16 +
 .../net/ethernet/yunsilicon/xsc/pci/vport.c   |   34 +
 include/uapi/rdma/ib_user_ioctl_verbs.h       |    1 +
 include/uapi/rdma/xsc-abi.h                   |  295 +++
 78 files changed, 17877 insertions(+)
 create mode 100644 drivers/infiniband/hw/xsc/Kconfig
 create mode 100644 drivers/infiniband/hw/xsc/Makefile
 create mode 100644 drivers/infiniband/hw/xsc/ah.c
 create mode 100644 drivers/infiniband/hw/xsc/counters.c
 create mode 100644 drivers/infiniband/hw/xsc/counters.h
 create mode 100644 drivers/infiniband/hw/xsc/cq.c
 create mode 100644 drivers/infiniband/hw/xsc/main.c
 create mode 100644 drivers/infiniband/hw/xsc/mem.c
 create mode 100644 drivers/infiniband/hw/xsc/mr.c
 create mode 100644 drivers/infiniband/hw/xsc/qp.c
 create mode 100644 drivers/infiniband/hw/xsc/srq.c
 create mode 100644 drivers/infiniband/hw/xsc/xsc_ib.h
 create mode 100644 drivers/infiniband/hw/xsc/xsc_ib_sysfs.c
 create mode 100644 drivers/net/ethernet/yunsilicon/Kconfig
 create mode 100644 drivers/net/ethernet/yunsilicon/Makefile
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/cq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/driver.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/qp.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/srq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd_api.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmdq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_core.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_device.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_hsi.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/xsc_reg.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/Kconfig
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/Makefile
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/main.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_common.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_tx.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_wq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_wq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_pp.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_pph.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/net/xsc_queue.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/Kconfig
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/adev.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/adev.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/alloc.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/alloc.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/cq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/cq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/eq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/eq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_reg.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hal/xsc_hal.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hw.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hw.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/link.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/main.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/mr.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/mr.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/pd.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/pd.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/qp.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/qp.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/srq.c
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/srq.h
 create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/vport.c
 create mode 100644 include/uapi/rdma/xsc-abi.h

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

* Re: [PATCH 02/15] yunsilicon: add the register HAL
  2026-10-12 11:43 ` [PATCH 02/15] yunsilicon: add the register HAL Xin Tian
@ 2026-10-12 15:21   ` Andrew Lunn
  2026-10-13 11:46   ` sashiko-bot
  1 sibling, 0 replies; 35+ messages in thread
From: Andrew Lunn @ 2026-10-12 15:21 UTC (permalink / raw)
  To: Xin Tian
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, Leon Romanovsky, weihg,
	linux-kernel, netdev, linux-rdma

On Mon, Oct 12, 2026 at 07:43:55PM +0800, Xin Tian wrote:
> Add the register access layer and the per-device register map: doorbell
> and interrupt registers, the command queue pointers and the device
> capability queries that the upper layers call.  The register map is
> described by xsc_hal.h and implemented for the Andes device.
> 
> Signed-off-by: Xin Tian <tianx@yunsilicon.com>
> ---
>  .../net/ethernet/yunsilicon/xsc/pci/Makefile  |   2 +-
>  .../yunsilicon/xsc/pci/hal/andes_impl.c       | 708 ++++++++++++++++++
>  .../yunsilicon/xsc/pci/hal/andes_reg.h        |  43 ++
>  .../ethernet/yunsilicon/xsc/pci/hal/xsc_hal.h |  85 +++
>  .../net/ethernet/yunsilicon/xsc/pci/main.c    |   3 +
>  5 files changed, 840 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c
>  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_reg.h
>  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hal/xsc_hal.h
> 
> diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
> index c163aa950..81b65232e 100644
> --- a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
> +++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
> @@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
>  
>  obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
>  
> -xsc_pci-y := main.o
> +xsc_pci-y := main.o hal/andes_impl.o
> diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c b/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c
> new file mode 100644
> index 000000000..46fc045f9
> --- /dev/null
> +++ b/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c
> @@ -0,0 +1,708 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (C) 2024 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/pci.h>
> +#include "andes_reg.h"
> +#include "common/xsc_core.h"
> +#include "common/xsc_cmd.h"
> +#include "common/xsc_hsi.h"
> +#include "common/xsc_reg.h"
> +#include "xsc_hal.h"
> +
> +/* message opcode */
> +enum {
> +	XSC_MSG_OPCODE_SEND		= 0,
> +	XSC_MSG_OPCODE_RDMA_WRITE	= 1,
> +	XSC_MSG_OPCODE_RDMA_READ	= 2,
> +	XSC_MSG_OPCODE_MAD		= 3,
> +	XSC_MSG_OPCODE_RDMA_ACK		= 4,
> +	XSC_MSG_OPCODE_RDMA_ACK_READ	= 5,
> +	XSC_MSG_OPCODE_RDMA_CNP		= 6,
> +	XSC_MSG_OPCODE_RAW		= 7,
> +	XSC_MSG_OPCODE_VIRTIO_NET	= 8,
> +	XSC_MSG_OPCODE_VIRTIO_BLK	= 9,
> +	XSC_MSG_OPCODE_RAW_TPE		= 10,
> +	XSC_MSG_OPCODE_INIT_QP_REQ	= 11,
> +	XSC_MSG_OPCODE_INIT_QP_RSP	= 12,
> +	XSC_MSG_OPCODE_INIT_PATH_REQ	= 13,
> +	XSC_MSG_OPCODE_INIT_PATH_RSP	= 14,
> +
> +	XSC_MSG_OPCODE_UNSUPPORT	= -1,
> +};
> +
> +#define REG_ADDR(bp, offset)	((bp) + (offset))
> +
> +static void xsc_read32(void __iomem *bar, u32 off, void *data, int len)
> +{
> +	u32 val = readl(REG_ADDR(bar, off));
> +
> +	memcpy(data, &val, len);

Why memcpy. Probably because the types are messed up.

> +}
> +
> +static void xsc_write32(void __iomem *bar, u32 off, void *data)
> +{
> +	writel(*(u32 *)data, REG_ADDR(bar, off));
> +}

Please sort out your types. You should not be passing a void *. Pass
u32 *. Casts are a red flag, they are generally not needed and
normally indicate bad design.

	Andrew

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

* Re: [PATCH 03/15] yunsilicon: add the command queue
  2026-10-12 11:43 ` [PATCH 03/15] yunsilicon: add the command queue Xin Tian
@ 2026-10-13  3:11   ` Junxian Huang
  2026-10-13 14:31     ` Leon Romanovsky
  2026-10-13 11:46   ` sashiko-bot
  1 sibling, 1 reply; 35+ messages in thread
From: Junxian Huang @ 2026-10-13  3:11 UTC (permalink / raw)
  To: Xin Tian, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma



On 2026/10/12 19:43, Xin Tian wrote:
> Add the command queue that is shared by the ethernet and RDMA functions:
> the request/response rings, the command work handling and the device
> initialisation sequence that queries the HCA capabilities, the GUID and
> the message opcode table.
> 
> Signed-off-by: Xin Tian <tianx@yunsilicon.com>
> ---
>  .../net/ethernet/yunsilicon/xsc/pci/Makefile  |    2 +-
>  .../net/ethernet/yunsilicon/xsc/pci/cmdq.c    | 1247 +++++++++++++++++
>  drivers/net/ethernet/yunsilicon/xsc/pci/hw.c  |  310 ++++
>  drivers/net/ethernet/yunsilicon/xsc/pci/hw.h  |   18 +
>  .../net/ethernet/yunsilicon/xsc/pci/main.c    |   55 +
>  5 files changed, 1631 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
>  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hw.c
>  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hw.h
> 
> diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
> index 81b65232e..656cdffa1 100644
> --- a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
> +++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
> @@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
>  
>  obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
>  
> -xsc_pci-y := main.o hal/andes_impl.o
> +xsc_pci-y := main.o cmdq.o hw.o hal/andes_impl.o
> diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c b/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
> new file mode 100644
> index 000000000..48832b26d
> --- /dev/null
> +++ b/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
> @@ -0,0 +1,1247 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
> + * All rights reserved.
> + * Copyright (c) 2013-2016, Mellanox Technologies. All rights reserved.

Why Mellanox?

> + */
> +
> +#include <linux/module.h>
> +#include <linux/errno.h>
> +#include <linux/pci.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/random.h>
> +#include <linux/kthread.h>
> +#include <linux/io-mapping.h>
> +
> +#include "common/xsc_cmd_api.h"
> +#include "common/xsc_cmd.h"
> +#include "common/xsc_core.h"
> +#include "hal/xsc_hal.h"
> +
> +enum {
> +	CMD_IF_REV = 3,
> +};
> +
> +enum {
> +	NUM_LONG_LISTS	  = 2,
> +	NUM_MED_LISTS	  = 64,
> +	LONG_LIST_SIZE	  = (2ULL * 1024 * 1024 * 1024 / PAGE_SIZE) * 8 + 16 +
> +				XSC_CMD_DATA_BLOCK_SIZE,
> +	MED_LIST_SIZE	  = 16 + XSC_CMD_DATA_BLOCK_SIZE,
> +};
> +
> +enum {
> +	XSC_CMD_DELIVERY_STAT_OK			= 0x0,
> +	XSC_CMD_DELIVERY_STAT_SIGNAT_ERR		= 0x1,
> +	XSC_CMD_DELIVERY_STAT_TOK_ERR			= 0x2,
> +	XSC_CMD_DELIVERY_STAT_BAD_BLK_NUM_ERR		= 0x3,
> +	XSC_CMD_DELIVERY_STAT_OUT_PTR_ALIGN_ERR		= 0x4,
> +	XSC_CMD_DELIVERY_STAT_IN_PTR_ALIGN_ERR		= 0x5,
> +	XSC_CMD_DELIVERY_STAT_FW_ERR			= 0x6,
> +	XSC_CMD_DELIVERY_STAT_IN_LENGTH_ERR		= 0x7,
> +	XSC_CMD_DELIVERY_STAT_OUT_LENGTH_ERR		= 0x8,
> +	XSC_CMD_DELIVERY_STAT_RES_FLD_NOT_CLR_ERR	= 0x9,
> +	XSC_CMD_DELIVERY_STAT_CMD_DESCR_ERR		= 0x10,
> +};
> +
> +static struct xsc_cmd_work_ent *xsc_alloc_cmd(struct xsc_cmd *cmd,
> +					      struct xsc_cmd_msg *in,
> +					      struct xsc_rsp_msg *out)
> +{
> +	struct xsc_cmd_work_ent *ent;
> +
> +	ent = kzalloc_obj(*ent, GFP_KERNEL);
> +	if (!ent)
> +		return ERR_PTR(-ENOMEM);
> +
> +	ent->in		= in;
> +	ent->out	= out;
> +	ent->cmd	= cmd;
> +
> +	return ent;
> +}
> +
> +static void xsc_free_cmd(struct xsc_cmd_work_ent *ent)
> +{
> +	kfree(ent);

One line function is unnecessary.

Junxian

> +}

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

* Re: [PATCH 09/15] yunsilicon: add RDMA device support
  2026-10-12 11:44 ` [PATCH 09/15] yunsilicon: add RDMA device support Xin Tian
@ 2026-10-13  6:17   ` Junxian Huang
  2026-10-13 11:46   ` sashiko-bot
  1 sibling, 0 replies; 35+ messages in thread
From: Junxian Huang @ 2026-10-13  6:17 UTC (permalink / raw)
  To: Xin Tian, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Jason Gunthorpe, Leon Romanovsky
  Cc: weihg, linux-kernel, netdev, linux-rdma



On 2026/10/12 19:44, Xin Tian wrote:
> Add the RDMA driver's uverbs ABI header (include/uapi/rdma/xsc-abi.h),
> the ib_device scaffolding and device registration: query_device,
> query_port, query_pkey, ucontext allocation and mmap, port attributes and
> the netdev notifier that tracks the port state.
> 
> The driver reports RDMA_DRIVER_XSC, which is added to the rdma_driver_id
> enum at the end so existing IDs are untouched.
> 
> Signed-off-by: Xin Tian <tianx@yunsilicon.com>
> ---
>  drivers/infiniband/Kconfig                    |   1 +
>  drivers/infiniband/hw/Makefile                |   1 +
>  drivers/infiniband/hw/xsc/Kconfig             |  17 +
>  drivers/infiniband/hw/xsc/Makefile            |   9 +
>  drivers/infiniband/hw/xsc/main.c              | 797 ++++++++++++++++++
>  drivers/infiniband/hw/xsc/xsc_ib.h            | 542 ++++++++++++
>  .../net/ethernet/yunsilicon/xsc/common/cq.h   |  30 +
>  .../ethernet/yunsilicon/xsc/common/driver.h   |  95 +++
>  .../net/ethernet/yunsilicon/xsc/common/qp.h   | 104 +++
>  .../net/ethernet/yunsilicon/xsc/common/srq.h  |  54 ++
>  .../net/ethernet/yunsilicon/xsc/pci/Makefile  |   2 +-
>  .../net/ethernet/yunsilicon/xsc/pci/link.c    |  35 +
>  include/uapi/rdma/ib_user_ioctl_verbs.h       |   1 +
>  include/uapi/rdma/xsc-abi.h                   | 295 +++++++
>  14 files changed, 1982 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/infiniband/hw/xsc/Kconfig
>  create mode 100644 drivers/infiniband/hw/xsc/Makefile
>  create mode 100644 drivers/infiniband/hw/xsc/main.c
>  create mode 100644 drivers/infiniband/hw/xsc/xsc_ib.h
>  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/cq.h
>  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/driver.h
>  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/qp.h
>  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/common/srq.h
>  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/link.c
>  create mode 100644 include/uapi/rdma/xsc-abi.h
> 
> diff --git a/drivers/infiniband/Kconfig b/drivers/infiniband/Kconfig
> index 086195758..b3cc506db 100644
> --- a/drivers/infiniband/Kconfig
> +++ b/drivers/infiniband/Kconfig
> @@ -100,6 +100,7 @@ source "drivers/infiniband/hw/ocrdma/Kconfig"
>  source "drivers/infiniband/hw/qedr/Kconfig"
>  source "drivers/infiniband/hw/usnic/Kconfig"
>  source "drivers/infiniband/hw/vmw_pvrdma/Kconfig"
> +source "drivers/infiniband/hw/xsc/Kconfig"
>  source "drivers/infiniband/sw/rdmavt/Kconfig"
>  endif # !UML
>  source "drivers/infiniband/sw/rxe/Kconfig"
> diff --git a/drivers/infiniband/hw/Makefile b/drivers/infiniband/hw/Makefile
> index c42b22ac3..1bcd2d50a 100644
> --- a/drivers/infiniband/hw/Makefile
> +++ b/drivers/infiniband/hw/Makefile
> @@ -16,3 +16,4 @@ obj-$(CONFIG_INFINIBAND_BNXT_RE)	+= bnxt_re/
>  obj-$(CONFIG_INFINIBAND_BNG_RE)		+= bng_re/
>  obj-$(CONFIG_INFINIBAND_ERDMA)		+= erdma/
>  obj-$(CONFIG_INFINIBAND_IONIC)		+= ionic/
> +obj-$(CONFIG_INFINIBAND_XSC)	+= xsc/
> diff --git a/drivers/infiniband/hw/xsc/Kconfig b/drivers/infiniband/hw/xsc/Kconfig
> new file mode 100644
> index 000000000..5cf46df2d
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/Kconfig
> @@ -0,0 +1,17 @@
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
> +# All rights reserved.
> +
> +config INFINIBAND_XSC
> +	tristate "Yunsilicon XSC RDMA driver"
> +	depends on INFINIBAND && PCI && INET
> +	depends on YUNSILICON_XSC_PCI && YUNSILICON_XSC_ETH
> +	help
> +	  This driver provides RDMA support for Yunsilicon XSC devices.
> +	  It uses the auxiliary device published by the XSC PCI core and,
> +	  for now, the netdev registered by the XSC ethernet driver to
> +	  derive the port state and GID.  Because of that netdev
> +	  dependency it also depends on YUNSILICON_XSC_ETH.
> +
> +	  To compile this driver as a module, choose M here. The module
> +	  will be called xsc_ib.
> diff --git a/drivers/infiniband/hw/xsc/Makefile b/drivers/infiniband/hw/xsc/Makefile
> new file mode 100644
> index 000000000..6c4ec1ca0
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
> +# All rights reserved.
> +
> +ccflags-y := -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
> +
> +obj-$(CONFIG_INFINIBAND_XSC) += xsc_ib.o
> +
> +xsc_ib-y := main.o
> diff --git a/drivers/infiniband/hw/xsc/main.c b/drivers/infiniband/hw/xsc/main.c
> new file mode 100644
> index 000000000..9d53a89be
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/main.c
> @@ -0,0 +1,797 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2021 - 2026, Shanghai Yunsilicon Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/auxiliary_bus.h>
> +#include <linux/init.h>
> +#include <linux/errno.h>
> +#include <linux/pci.h>
> +#include <linux/namei.h>
> +#include <linux/kobject.h>
> +#include <linux/kernfs.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/slab.h>
> +#include <linux/io-mapping.h>
> +#include <linux/sched.h>
> +#include <linux/netdevice.h>
> +#include "common/xsc_core.h"
> +#include "common/xsc_hsi.h"
> +#include "common/xsc_cmd.h"
> +#include "common/driver.h"
> +
> +#include <rdma/ib_user_verbs.h>
> +#include <rdma/ib_smi.h>
> +#include <rdma/ib_umem.h>
> +
> +#include <rdma/xsc-abi.h>
> +#include "xsc_ib.h"
> +
> +#define DRIVER_NAME "xsc_ib"
> +
> +#define XSC_IB_GID_TBL_LEN	256
> +#define XSC_IB_MAX_PKEYS	0x80
> +
> +MODULE_DESCRIPTION("Yunsilicon HCA IB driver");
> +MODULE_LICENSE("GPL");
> +
> +static int xsc_ib_query_device(struct ib_device *ibdev,
> +			       struct ib_device_attr *props,
> +			       struct ib_udata *udata)

Are the comments in this function really needed? I think the codes
are self-explainable enough.

> +{
> +	size_t udata_outlen = (udata) ? udata->outlen : 0;
> +	struct xsc_ib_dev *dev = to_xdev(ibdev);
> +	struct xsc_ib_query_device_resp resp;
> +	union xsc_ib_fw_ver fw_ver;
> +	int err = -ENOMEM;
> +	size_t resp_len;
> +	int max_rq_sg;
> +	u64 max_tso;
> +	u64 flags;
> +
> +	err = ib_is_udata_in_empty(udata);
> +	if (err)
> +		return err;
> +
> +	memset(&resp, 0, sizeof(resp));
> +	memset(props, 0, sizeof(*props));

The core already handles the zeroing, no need to do this in driver.

> +
> +	resp_len = sizeof(resp.comp_mask) + sizeof(resp.response_length);
> +	/*check param*/
> +	if (udata_outlen && udata_outlen < resp_len)
> +		return -EINVAL;
> +
> +	resp.response_length = resp_len;
> +
> +	fw_ver.data = 0;
> +	fw_ver.s.ver_major = dev->xdev->fw_version_major;
> +	fw_ver.s.ver_minor = dev->xdev->fw_version_minor;
> +	fw_ver.s.ver_patch = dev->xdev->fw_version_patch;
> +	fw_ver.s.ver_tweak = dev->xdev->fw_version_tweak;
> +	props->fw_ver = fw_ver.data;
> +
> +	props->device_cap_flags    = IB_DEVICE_CHANGE_PHY_PORT |
> +		IB_DEVICE_PORT_ACTIVE_EVENT		|
> +		IB_DEVICE_SYS_IMAGE_GUID		|
> +		IB_DEVICE_RC_RNR_NAK_GEN;
> +	props->kernel_cap_flags =  IBK_BLOCK_MULTICAST_LOOPBACK;
> +	props->kernel_cap_flags |= IBK_LOCAL_DMA_LKEY;
> +
> +	flags = dev->xdev->caps.flags;
> +	if (flags & XSC_DEV_CAP_FLAG_BAD_PKEY_CNTR)
> +		props->device_cap_flags |= IB_DEVICE_BAD_PKEY_CNTR;
> +	if (flags & XSC_DEV_CAP_FLAG_BAD_QKEY_CNTR)
> +		props->device_cap_flags |= IB_DEVICE_BAD_QKEY_CNTR;
> +	if (flags & XSC_DEV_CAP_FLAG_APM)
> +		props->device_cap_flags |= IB_DEVICE_AUTO_PATH_MIG;
> +	props->page_size_cap	   = dev->xdev->caps.min_page_sz;
> +	props->max_mr_size	   = xsc_get_max_mr_size(dev->xdev);
> +	props->max_qp		   = dev->xdev->caps.max_qp;
> +	props->max_qp_wr	   = xsc_get_max_qp_depth(dev->xdev);
> +	max_rq_sg = dev->xdev->caps.max_rq_desc_sz / sizeof(struct xsc_wqe_data_seg);
> +
> +	props->max_send_sge = dev->xdev->caps.send_ds_num - XSC_CTRL_SEG_NUM -
> +		XSC_RADDR_SEG_NUM;
> +	props->max_recv_sge = dev->xdev->caps.recv_ds_num;
> +	props->max_sge_rd	   = 1;/*max sge per read wqe*/
> +	props->max_cq		   = dev->xdev->caps.max_cq;
> +	props->max_cqe		   = dev->xdev->caps.max_cqes;
> +	props->max_mr		   = (1 << dev->xdev->caps.log_max_mkey) - 1;
> +	props->max_pd		   = dev->xdev->caps.max_pd;
> +	props->max_qp_rd_atom	   = dev->xdev->caps.max_ra_res_qp;
> +	props->max_qp_init_rd_atom = dev->xdev->caps.max_ra_req_qp;
> +	props->max_res_rd_atom	   = props->max_qp_rd_atom * props->max_qp;
> +	props->max_srq		   =
> +		dev->xdev->caps.log_max_srq ? (1 << dev->xdev->caps.log_max_srq) : 0;
> +	props->max_srq_wr	   = dev->xdev->caps.max_srq_wqes - 1;
> +	props->max_srq_sge	   = dev->xdev->caps.log_max_srq ? (max_rq_sg - 1) : 0;
> +	props->local_ca_ack_delay  = dev->xdev->caps.local_ca_ack_delay;
> +	props->atomic_cap	   = is_support_rdma_atomic(dev->xdev) ?
> +		IB_ATOMIC_HCA : IB_ATOMIC_NONE;
> +	props->masked_atomic_cap   = IB_ATOMIC_HCA;
> +	props->max_mcast_grp	   =
> +		dev->xdev->caps.log_max_mcg ? (1 << dev->xdev->caps.log_max_mcg) : 0;
> +	props->max_mcast_qp_attach = dev->xdev->caps.max_qp_mcg;
> +	props->max_total_mcast_qp_attach = props->max_mcast_qp_attach *
> +					   props->max_mcast_grp;
> +
> +	props->sys_image_guid = dev->xdev->board_info->guid;
> +	props->vendor_id = dev->xdev->pdev->vendor;
> +	props->vendor_part_id = dev->xdev->pdev->device;
> +	props->hw_ver = ((dev->xdev->chip_ver_l & 0xffff) << 16) |
> +		(dev->xdev->hotfix_num & 0xffff);
> +	props->max_pkeys = XSC_IB_MAX_PKEYS;
> +	props->max_wq_type_rq = dev->xdev->caps.max_qp;
> +	props->hca_core_clock = dev->xdev->caps.hca_core_clock * 1000; /* KHz */
> +	props->rss_caps.max_rwq_indirection_tables =
> +		dev->xdev->caps.max_rwq_indirection_tables;
> +	props->rss_caps.max_rwq_indirection_table_size =
> +		dev->xdev->caps.max_rwq_indirection_table_size;
> +	props->rss_caps.supported_qpts = 1 << IB_QPT_RAW_PACKET;
> +
> +	/*response tso_caps extend param*/
> +	if (offsetofend(typeof(resp), tso_caps) <= udata_outlen) {
> +		max_tso = dev->xdev->caps.log_max_tso ? (1 << dev->xdev->caps.log_max_tso) : 0;
> +		if (max_tso) {
> +			resp.tso_caps.max_tso = max_tso;
> +			resp.tso_caps.supported_qpts |= 1 << IB_QPT_RAW_PACKET;
> +			resp.response_length += sizeof(resp.tso_caps);
> +		}
> +	}
> +
> +	/*response rss_caps extend param*/
> +	if (offsetofend(typeof(resp), rss_caps) <= udata_outlen) {
> +		resp.rss_caps.rx_hash_function = XSC_RX_HASH_FUNC_TOEPLITZ;
> +		resp.rss_caps.rx_hash_fields_mask =
> +			XSC_RX_HASH_SRC_IPV4 |
> +			XSC_RX_HASH_DST_IPV4 |
> +			XSC_RX_HASH_SRC_IPV6 |
> +			XSC_RX_HASH_DST_IPV6 |
> +			XSC_RX_HASH_SRC_PORT_TCP |
> +			XSC_RX_HASH_DST_PORT_TCP |
> +			XSC_RX_HASH_SRC_PORT_UDP |
> +			XSC_RX_HASH_DST_PORT_UDP |
> +			XSC_RX_HASH_INNER;
> +		resp.response_length += sizeof(resp.rss_caps);
> +	}
> +
> +	/*response packet pacing caps*/
> +	if (offsetofend(typeof(resp), packet_pacing_caps) <= udata_outlen) {
> +		resp.packet_pacing_caps.qp_rate_limit_max =
> +			dev->xdev->caps.qp_rate_limit_max;
> +		resp.packet_pacing_caps.qp_rate_limit_min =
> +			dev->xdev->caps.qp_rate_limit_min;
> +		resp.packet_pacing_caps.supported_qpts |= 1 << IB_QPT_RAW_PACKET;
> +
> +		resp.response_length += sizeof(resp.packet_pacing_caps);
> +	}
> +
> +	/*copy response data to user*/
> +	if (udata_outlen) {
> +		err = ib_copy_to_udata(udata, &resp, resp.response_length);
> +		if (err) {
> +			ibdev_err(&dev->ib_dev, "copy response info to udata fail,err=%d\n", err);
> +			return err;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void xsc_calc_link_info(struct xsc_core_device *xdev,
> +			       struct ib_port_attr *props)
> +{
> +	switch (xsc_get_link_speed(xdev)) {
> +	case MODULE_SPEED_10G:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_10GB;
> +		props->active_width = 1;
> +		break;
> +	case MODULE_SPEED_25G:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
> +		props->active_width = 1;
> +		break;
> +	case MODULE_SPEED_40G_R4:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_10GB;
> +		props->active_width = 2;
> +		break;
> +	case MODULE_SPEED_50G_R:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_50GB;
> +		props->active_width = 1;
> +		break;
> +	case MODULE_SPEED_50G_R2:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_50GB;
> +		props->active_width = 1;
> +		break;
> +	case MODULE_SPEED_100G_R2:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
> +		props->active_width = 2;
> +		break;
> +	case MODULE_SPEED_100G_R4:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
> +		props->active_width = 2;
> +		break;
> +	case MODULE_SPEED_200G_R4:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_50GB;
> +		props->active_width = 2;
> +		break;
> +	case MODULE_SPEED_200G_R8:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
> +		props->active_width = 4;
> +		break;
> +	case MODULE_SPEED_400G_R8:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_50GB;
> +		props->active_width = 4;
> +		break;
> +	case MODULE_SPEED_400G_R4:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_100GB;
> +		props->active_width = 2;
> +		break;
> +	default:
> +		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
> +		props->active_width = 1;
> +		break;
> +	}
> +}
> +
> +static enum rdma_link_layer xsc_ib_port_link_layer(struct ib_device *ibdev, u32 port)
> +{
> +	return IB_LINK_LAYER_ETHERNET;
> +}
> +
> +int xsc_ib_query_port(struct ib_device *ibdev, u32 port,
> +		      struct ib_port_attr *props)
> +{
> +	struct xsc_ib_dev *dev = to_xdev(ibdev);
> +	struct net_device *ndev = dev->netdev;
> +	struct xsc_core_device *xdev = dev->xdev;
> +
> +	if (port < 1 || port > xdev->caps.num_ports) {
> +		ibdev_warn(&dev->ib_dev, "invalid port number %d\n", port);
> +		return -EINVAL;
> +	}
> +
> +	memset(props, 0, sizeof(*props));
> +
> +	if (netif_running(ndev) && netif_carrier_ok(ndev))
> +		props->state = IB_PORT_ACTIVE;
> +	else
> +		props->state = IB_PORT_DOWN;

You can use ib_get_curr_port_state() instead of open-coding.

> +
> +	props->max_mtu = is_support_8k_mtu(xdev) ? IB_MTU_8192 : IB_MTU_4096;
> +	props->active_mtu = min(props->max_mtu, xsc_net_to_ib_mtu(ndev->mtu));

Similarly, use iboe_get_mtu().

> +	props->gid_tbl_len = XSC_IB_GID_TBL_LEN;
> +	props->port_cap_flags = IB_PORT_CM_SUP | IB_PORT_OTHER_LOCAL_CHANGES_SUP;
> +	props->max_msg_sz = SZ_1G;
> +	props->bad_pkey_cntr = 0;
> +	props->qkey_viol_cntr = 0;
> +	props->pkey_tbl_len = 1;
> +	props->lid = 0;
> +	props->sm_lid = 0;
> +	props->lmc = 0;
> +	props->max_vl_num = 0;
> +	props->sm_sl = 0;
> +	props->subnet_timeout = 0;
> +	props->init_type_reply = 0;
> +	if (!is_support_rdma(xdev)) {
> +		props->active_width = 1;
> +		props->active_speed = XSC_RDMA_LINK_SPEED_25GB;
> +	} else {
> +		xsc_calc_link_info(xdev, props);
> +	}
> +
> +	props->phys_state = netif_carrier_ok(ndev) ? XSC_RDMA_PHY_STATE_LINK_UP :
> +				XSC_RDMA_PHY_STATE_DISABLED;
> +	return 0;
> +}
> +
> +static int xsc_ib_query_pkey(struct ib_device *ibdev, u32 port, u16 index,
> +			     u16 *pkey)
> +{
> +	*pkey = 0xffff;
> +	return 0;
> +}
> +
> +int xsc_ib_alloc_ucontext(struct ib_ucontext *uctx,
> +			  struct ib_udata *udata)
> +{
> +	struct ib_device *ibdev = uctx->device;
> +	struct xsc_ib_dev *dev = to_xdev(ibdev);
> +	struct xsc_ib_alloc_ucontext_req req;
> +	struct xsc_ib_alloc_ucontext_resp resp;
> +	int err;
> +
> +	if (!dev->ib_active)
> +		return -EAGAIN;
> +
> +	err = ib_copy_from_udata(&req, udata, sizeof(req));
> +	if (err)
> +		return err;
> +
> +	memset(&resp, 0, sizeof(resp));
> +	resp.qp_tab_size      = dev->xdev->caps.max_qp;
> +	resp.cache_line_size  = L1_CACHE_BYTES;
> +	resp.max_sq_desc_sz = dev->xdev->caps.max_sq_desc_sz;
> +	resp.max_rq_desc_sz = dev->xdev->caps.max_rq_desc_sz;
> +	resp.max_send_wqebb = dev->xdev->caps.max_wqes;
> +	resp.max_recv_wr = dev->xdev->caps.max_wqes;
> +	xsc_get_db_addr(dev->xdev, &resp.qpm_tx_db, &resp.qpm_rx_db,
> +			&resp.cqm_armdb, &resp.cqm_next_cid_reg, NULL);
> +	resp.send_ds_num = dev->xdev->caps.send_ds_num;
> +	resp.recv_ds_num = dev->xdev->caps.recv_ds_num;
> +	resp.cmds_supp_uhw |= XSC_USER_CMDS_SUPP_UHW_QUERY_DEVICE;
> +	resp.device_id = dev->xdev->pdev->device;
> +
> +	if (req.abi_ver >= ALLOC_UCONTEXT_VER_V1) {
> +		xsc_get_multidb_info(dev->xdev, &resp.multidb_num, &resp.tx_multidb_base);
> +		resp.rdma_proto_mode = dev->xdev->rdma_proto_mode;
> +	}
> +
> +	if (is_support_cqe64(dev->xdev))
> +		resp.hw_feature_flag |= XSC_HW_FEATURE_FLAG_SUPPORT_CQE64;
> +
> +	resp.num_ports = dev->xdev->caps.num_ports;
> +
> +	return ib_copy_to_udata(udata, &resp, min_t(int, udata->outlen, sizeof(resp)));
> +}
> +
> +void xsc_ib_dealloc_ucontext(struct ib_ucontext *ibcontext)
> +{
> +}
> +
> +static int xsc_ib_mmap(struct ib_ucontext *ibcontext, struct vm_area_struct *vma)
> +{
> +	struct xsc_ib_dev *dev = to_xdev(ibcontext->device);
> +	struct xsc_core_device *xdev = dev->xdev;
> +	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
> +	resource_size_t reg_base;
> +	resource_size_t reg_size = vma->vm_end - vma->vm_start;
> +	u64 tx_db = 0;
> +	u64 rx_db = 0;
> +	u64 cq_db = 0;
> +	u64 cq_reg = 0;
> +
> +	xsc_get_db_addr(xdev, &tx_db, &rx_db, &cq_db, &cq_reg, NULL);
> +	dev_dbg(&xdev->pdev->dev, "offset:0x%lx\n", offset);
> +
> +	if (offset == (tx_db & PAGE_MASK))
> +		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) + (tx_db & PAGE_MASK);
> +	else if (offset == (rx_db & PAGE_MASK))
> +		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) + (rx_db & PAGE_MASK);
> +	else if (offset == (cq_reg & PAGE_MASK))
> +		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) + (cq_reg & PAGE_MASK);
> +	else if (offset == (cq_db & PAGE_MASK))
> +		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) + (cq_db & PAGE_MASK);
> +	else if (offset == (QPM_TX_MDB_BASE_REG_ADDR & PAGE_MASK))
> +		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) +
> +			(QPM_TX_MDB_BASE_REG_ADDR & PAGE_MASK);
> +	else if (offset == (xdev->caps.mdb_base & PAGE_MASK))
> +		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) +
> +			(xdev->caps.mdb_base & PAGE_MASK);
> +	else
> +		return -EINVAL;
> +
> +	dev_dbg(&xdev->pdev->dev, "regbase:0x%llx", reg_base);
> +
> +	reg_base = (xsc_core_is_pf(xdev) && !is_pf_bar_compressed(xdev)) ?
> +		reg_base - 0xA0000000 : reg_base;
> +
> +	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
> +	return remap_pfn_range(vma, vma->vm_start, reg_base >> PAGE_SHIFT,
> +			       reg_size, vma->vm_page_prot);
> +}
> +
> +static int xsc_port_immutable(struct ib_device *ibdev, u32 port_num,
> +			      struct ib_port_immutable *immutable)
> +{
> +	struct ib_port_attr attr;
> +	int err;
> +
> +	err = ib_query_port(ibdev, port_num, &attr);
> +	if (err)
> +		return err;
> +
> +	immutable->pkey_tbl_len = attr.pkey_tbl_len;
> +	immutable->gid_tbl_len = attr.gid_tbl_len;
> +	immutable->core_cap_flags = RDMA_CORE_PORT_IBA_ROCE |
> +				    RDMA_CORE_PORT_IBA_ROCE_UDP_ENCAP;
> +	immutable->max_mad_size = IB_MGMT_MAD_SIZE * 2;
> +
> +	return 0;
> +}
> +
> +static struct net_device *xsc_get_netdev(struct ib_device *ibdev, u32 port_num)

You should use ib_device_set_netdev() instead of implementing this op.

> +{
> +	struct xsc_ib_dev *xsc_ib_dev = to_xdev(ibdev);
> +	struct net_device *dev = xsc_ib_dev->netdev;
> +
> +	if (dev)
> +		dev_hold(dev);
> +
> +	return dev;
> +}
> +
> +static void xsc_get_guid(const u8 *dev_addr, u8 *guid)
> +{
> +	u8 mac[ETH_ALEN];
> +
> +	/* MAC-48 to EUI-64 mapping */
> +	memcpy(mac, dev_addr, ETH_ALEN);
> +	guid[0] = mac[0] ^ 2;
> +	guid[1] = mac[1];
> +	guid[2] = mac[2];
> +	guid[3] = 0xff;
> +	guid[4] = 0xfe;
> +	guid[5] = mac[3];
> +	guid[6] = mac[4];
> +	guid[7] = mac[5];
> +}
> +
> +static void init_node_data(struct xsc_ib_dev *dev)
> +{
> +	strscpy(dev->ib_dev.node_desc, "xsc_node_desc",
> +		sizeof(dev->ib_dev.node_desc));
> +	xsc_get_guid(dev->netdev->dev_addr, (u8 *)&dev->ib_dev.node_guid);
> +}
> +
> +static void xsc_ib_event(struct xsc_core_device *dev, void *context,
> +			 enum xsc_dev_event event, unsigned long data)
> +{
> +	struct xsc_ib_dev *ibdev = (struct xsc_ib_dev *)context;
> +	struct ib_event ibev;
> +	u8 port = 0;
> +
> +	switch (event) {
> +	case XSC_DEV_EVENT_SYS_ERROR:
> +		ibdev->ib_active = false;
> +		ibev.event = IB_EVENT_DEVICE_FATAL;
> +		break;
> +
> +	case XSC_DEV_EVENT_PORT_UP:
> +		ibev.event = IB_EVENT_PORT_ACTIVE;
> +		port = *(u8 *)data;
> +		break;
> +
> +	case XSC_DEV_EVENT_PORT_DOWN:
> +		ibev.event = IB_EVENT_PORT_ERR;
> +		port = *(u8 *)data;
> +		break;
> +
> +	case XSC_DEV_EVENT_PORT_INITIALIZED:
> +		/* not used by ULPs */
> +		return;
> +
> +	case XSC_DEV_EVENT_LID_CHANGE:
> +		ibev.event = IB_EVENT_LID_CHANGE;
> +		port = *(u8 *)data;
> +		break;
> +
> +	case XSC_DEV_EVENT_PKEY_CHANGE:
> +		ibev.event = IB_EVENT_PKEY_CHANGE;
> +		port = *(u8 *)data;
> +		break;
> +
> +	case XSC_DEV_EVENT_GUID_CHANGE:
> +		ibev.event = IB_EVENT_GID_CHANGE;
> +		port = *(u8 *)data;
> +		break;
> +
> +	case XSC_DEV_EVENT_CLIENT_REREG:
> +		ibev.event = IB_EVENT_CLIENT_REREGISTER;
> +		port = *(u8 *)data;
> +		break;
> +	}
> +
> +	ibev.device	      = &ibdev->ib_dev;
> +	ibev.element.port_num = port;
> +
> +	if (ibdev->ib_active)
> +		ib_dispatch_event(&ibev);
> +}
> +
> +static void xsc_ib_get_dev_fw_str(struct ib_device *ibdev, char *str)
> +{
> +	struct xsc_core_device *dev = to_xdev(ibdev)->xdev;
> +	u8 ver_major = dev->fw_version_major;
> +	u8 ver_minor = dev->fw_version_minor;
> +	u16 ver_patch = dev->fw_version_patch;
> +	u32 ver_tweak = dev->fw_version_tweak;
> +
> +	if (ver_tweak == 0) {
> +		snprintf(str, IB_FW_VERSION_NAME_MAX, "v%u.%u.%u",
> +			 ver_major,  ver_minor, ver_patch);
> +	} else {
> +		snprintf(str, IB_FW_VERSION_NAME_MAX, "v%u.%u.%u+%u",
> +			 ver_major, ver_minor, ver_patch, ver_tweak);
> +	}
> +}
> +
> +static void xsc_ib_disassociate_ucontext(struct ib_ucontext *ibcontext)
> +{
> +}
> +
> +static void xsc_ib_dev_setting(struct xsc_ib_dev *dev)
> +{
> +	dev->ib_dev.ops.owner		= THIS_MODULE;
> +	dev->ib_dev.ops.uverbs_abi_ver	= XSC_IB_UVERBS_ABI_VERSION;
> +	dev->ib_dev.ops.driver_id = RDMA_DRIVER_XSC;
> +	dev->ib_dev.ops.disassociate_ucontext = xsc_ib_disassociate_ucontext;
> +	dev->ib_dev.ops.query_device	= xsc_ib_query_device;
> +	dev->ib_dev.ops.query_port	= xsc_ib_query_port;
> +	dev->ib_dev.ops.query_pkey	= xsc_ib_query_pkey;
> +
> +	dev->ib_dev.ops.alloc_ucontext	= xsc_ib_alloc_ucontext;
> +	dev->ib_dev.ops.dealloc_ucontext = xsc_ib_dealloc_ucontext;
> +	dev->ib_dev.ops.mmap		= xsc_ib_mmap;
> +
> +
> +	dev->ib_dev.ops.get_link_layer	= xsc_ib_port_link_layer;
> +	dev->ib_dev.ops.get_netdev		= xsc_get_netdev;
> +
> +
> +	dev->ib_dev.ops.get_port_immutable		= xsc_port_immutable;
> +
> +	dev->ib_dev.ops.get_dev_fw_str	= xsc_ib_get_dev_fw_str;
> +
> +	dev->ib_dev.ops INIT_RDMA_OBJ_SIZE(ib_ucontext, xsc_ib_ucontext, ibucontext);
> +}
> +
> +static void xsc_get_port_state(struct net_device *ndev, enum xsc_dev_event *ev)
> +{
> +	*ev = XSC_DEV_EVENT_PORT_DOWN;
> +	if (netif_running(ndev) && netif_carrier_ok(ndev))
> +		*ev = XSC_DEV_EVENT_PORT_UP;
> +}
> +
> +static int xsc_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
> +{
> +	struct xsc_ib_dev *ibdev = container_of(this, struct xsc_ib_dev, nb);
> +	struct net_device *ndev = netdev_notifier_info_to_dev(ptr);
> +	enum xsc_dev_event ev;
> +	u8 port = 1;
> +
> +	if (ndev != ibdev->netdev)
> +		goto done;
> +
> +	ibdev_dbg(&ibdev->ib_dev, "netdev notfiy event:%ld\n", event);
> +	switch (event) {
> +	case NETDEV_CHANGE:
> +	case NETDEV_UP:
> +	case NETDEV_DOWN:
> +		xsc_get_port_state(ibdev->netdev, &ev);
> +		xsc_ib_event(ibdev->xdev, ibdev, ev, (unsigned long)&port);
> +		break;
> +	default:
> +		break;
> +	}
> +done:
> +	return NOTIFY_DONE;
> +}
> +
> +static int xsc_register_netdev_notifier(struct xsc_ib_dev *ibdev)
> +{
> +	ibdev->nb.notifier_call = xsc_netdev_event;
> +	return register_netdevice_notifier(&ibdev->nb);
ib_netdevice_event() already handles these events. You can also implement
.report_port_event() if needed. Anyhow you probably don't need this
notifier in driver.

> +}
> +
> +static int xsc_unregister_netdev_notifier(struct xsc_ib_dev *ibdev)
> +{
> +	return unregister_netdevice_notifier(&ibdev->nb);
> +}
> +
> +static int init_one(struct xsc_core_device *xdev,
> +		    struct xsc_ib_dev **ibdev)
> +{
> +	struct xsc_ib_dev *dev;
> +	int err;
> +
> +	dev = (struct xsc_ib_dev *)ib_alloc_device(xsc_ib_dev, ib_dev);
> +	if (!dev)
> +		return -ENOMEM;
> +
> +	dev->xdev = xdev;
> +	dev->netdev = xdev->netdev;
> +	strscpy(dev->ib_dev.name, "xscale_%d", IB_DEVICE_NAME_MAX);
> +
> +	memcpy(dev->xdev->priv.ibdev_name, dev->ib_dev.name, IB_DEVICE_NAME_MAX);
> +
> +	if (!xdev->caps.msix_enable)
> +		dev->num_comp_vectors = 1;
> +	else
> +		dev->num_comp_vectors = xdev->dev_res->eq_table.num_comp_vectors;
> +
> +	dev->ib_dev.node_type		= RDMA_NODE_IB_CA;
> +	dev->ib_dev.local_dma_lkey	= 0xFF;
> +	dev->num_ports		= xdev->caps.num_ports;
> +	dev->ib_dev.phys_port_cnt     = dev->num_ports;
> +	dev->ib_dev.num_comp_vectors	= dev->num_comp_vectors;
> +	dev->ib_dev.dev.parent = &xdev->pdev->dev;
> +	xsc_ib_dev_setting(dev);
> +	dev->xdev->priv.cm_dscp = DSCP_PCP_UNSET;
> +	dev->xdev->priv.cm_pcp = DSCP_PCP_UNSET;
> +	dev->xdev->priv.force_pcp = DSCP_PCP_UNSET;
> +	dev->xdev->priv.force_dscp = DSCP_PCP_UNSET;
> +	dev->xdev->rdma_proto_mode = RDMA_PROTO_ROCEV2;
> +	dev->xdev->cm_udp_dst_port = ROCE_V2_UDP_DPORT;
> +	dev->xdev->veroce_udp_dst_port = VEROCE_UDP_DPORT;
> +
> +	dev->ib_dev.uverbs_cmd_mask	=
> +		(1ull << IB_USER_VERBS_CMD_GET_CONTEXT)		|
> +		(1ull << IB_USER_VERBS_CMD_QUERY_DEVICE)	|
> +		(1ull << IB_USER_VERBS_CMD_QUERY_PORT)		|
> +		(1ull << IB_USER_VERBS_CMD_ALLOC_PD)		|
> +		(1ull << IB_USER_VERBS_CMD_DEALLOC_PD)		|
> +		(1ull << IB_USER_VERBS_CMD_CREATE_AH)		|
> +		(1ull << IB_USER_VERBS_CMD_DESTROY_AH)		|
> +		(1ull << IB_USER_VERBS_CMD_REG_MR)		|
> +		(1ull << IB_USER_VERBS_CMD_REREG_MR)		|
> +		(1ull << IB_USER_VERBS_CMD_DEREG_MR)		|
> +		(1ull << IB_USER_VERBS_CMD_CREATE_COMP_CHANNEL)	|
> +		(1ull << IB_USER_VERBS_CMD_CREATE_CQ)		|
> +		(1ull << IB_USER_VERBS_CMD_RESIZE_CQ)		|
> +		(1ull << IB_USER_VERBS_CMD_DESTROY_CQ)		|
> +		(1ull << IB_USER_VERBS_CMD_CREATE_QP)		|
> +		(1ull << IB_USER_VERBS_CMD_MODIFY_QP)		|
> +		(1ull << IB_USER_VERBS_CMD_QUERY_QP)		|
> +		(1ull << IB_USER_VERBS_CMD_DESTROY_QP)		|
> +		(1ull << IB_USER_VERBS_CMD_ATTACH_MCAST)	|
> +		(1ull << IB_USER_VERBS_CMD_DETACH_MCAST)	|
> +		(1ull << IB_USER_VERBS_CMD_CREATE_SRQ)		|
> +		(1ull << IB_USER_VERBS_CMD_MODIFY_SRQ)		|
> +		(1ull << IB_USER_VERBS_CMD_QUERY_SRQ)		|
> +		(1ull << IB_USER_VERBS_CMD_DESTROY_SRQ)		|
> +		(1ull << IB_USER_VERBS_CMD_OPEN_QP);
> +
> +	init_node_data(dev);
> +
> +
> +
> +

Too many blank lines.

> +	err = ib_register_device(&dev->ib_dev, dev->ib_dev.name,
> +				 dev->xdev->device);
> +	if (err)
> +		goto err_free;
> +
> +	rdma_roce_rescan_device(&dev->ib_dev);

This is already called in
ib_register_device → ib_cache_setup_one → gid_table_setup_one

> +	dev->ib_active = true;
> +	*ibdev = dev;
> +
> +	xsc_register_netdev_notifier(dev);
> +
> +
> +

Too many blank lines.

> +	return 0;
> +
> +err_free:
> +	ib_dealloc_device((struct ib_device *)dev);
> +
> +	return err;
> +}
> +
> diff --git a/drivers/infiniband/hw/xsc/xsc_ib.h b/drivers/infiniband/hw/xsc/xsc_ib.h
> new file mode 100644
> index 000000000..a2ae382e2
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/xsc_ib.h
> @@ -0,0 +1,542 @@
> +static inline void *xsc_ib_iova_to_virt(struct ib_device *ibdev, dma_addr_t iova)
> +{
> +	phys_addr_t phyaddr;
> +	struct iommu_domain *domain;
> +
> +	domain = iommu_get_domain_for_dev(ibdev->dma_device);
> +	if (likely(domain)) {
> +		phyaddr = iommu_iova_to_phys(domain, iova);
> +		phyaddr |= iova & (PAGE_SIZE - 1);
> +	} else {
> +		phyaddr = dma_to_phys(ibdev->dma_device, iova);
> +	}
> +
> +	return phys_to_virt(phyaddr);
> +}
> +
> +struct ib_mad_list_head {

Why are these ib_mad structures defined again in the driver's header?

Junxian

> +	struct list_head list;
> +	struct ib_cqe cqe;
> +	struct ib_mad_queue *mad_queue;
> +};
> +
> +#define IB_MAD_SEND_REQ_MAX_SG 2
> +struct ib_mad_send_wr_private {
> +	struct ib_mad_list_head mad_list;
> +	struct list_head agent_list;
> +	struct ib_mad_agent_private *mad_agent_priv;
> +	struct ib_mad_send_buf send_buf;
> +	u64 header_mapping;
> +	u64 payload_mapping;
> +	struct ib_ud_wr send_wr;
> +	struct ib_sge sg_list[IB_MAD_SEND_REQ_MAX_SG];
> +	__be64 tid;
> +	unsigned long timeout;
> +	int max_retries;
> +	int retries_left;
> +	int retry;
> +	int refcount;
> +	enum ib_wc_status status;
> +
> +	/* RMPP control */
> +	struct list_head rmpp_list;
> +	struct ib_rmpp_segment *last_ack_seg;
> +	struct ib_rmpp_segment *cur_seg;
> +	int last_ack;
> +	int seg_num;
> +	int newwin;
> +	int pad;
> +};
> +
> +struct ib_mad_private_header {
> +	struct ib_mad_list_head mad_list;
> +	struct ib_mad_recv_wc recv_wc;
> +	struct ib_wc wc;
> +	u64 mapping;
> +} __packed;
> +
> +struct ib_mad_private {
> +	struct ib_mad_private_header header;
> +	size_t mad_size;
> +	struct ib_grh grh;
> +	DECLARE_FLEX_ARRAY(u8, mad);
> +} __packed;
> +
> +static inline void *xsc_ib_send_mad_sg_virt_addr(struct ib_device *ibdev,
> +						 const struct ib_send_wr *wr,
> +						 int sg)
> +{
> +	struct ib_mad_send_wr_private *mad_send_wr;
> +	struct ib_mad_list_head *mad_list;
> +	int iommu_state = xsc_ib_iommu_dma_map(ibdev);
> +
> +	/* direct dma mapping */
> +	if (!iommu_state)
> +		return phys_to_virt(dma_to_phys(ibdev->dma_device, wr->sg_list[sg].addr));
> +
> +	if (iommu_state == XSC_IB_IOMMU_MAP_NORMAL)
> +		return xsc_ib_iova_to_virt(ibdev, wr->sg_list[sg].addr);
> +
> +	mad_list = container_of(wr->wr_cqe, struct ib_mad_list_head, cqe);
> +	mad_send_wr = container_of(mad_list, struct ib_mad_send_wr_private,
> +				   mad_list);
> +
> +	/* sg_list[0] */
> +	if (sg == 0)
> +		return mad_send_wr->send_buf.mad;
> +
> +	/* sg_list[1] */
> +	if (mad_send_wr->send_buf.seg_count)
> +		return ib_get_rmpp_segment(&mad_send_wr->send_buf,
> +					   mad_send_wr->seg_num);
> +	return mad_send_wr->send_buf.mad + mad_send_wr->send_buf.hdr_len;
> +}
> +
> +static inline void *xsc_ib_recv_mad_sg_virt_addr(struct ib_device *ibdev,
> +						 struct ib_wc *wc,
> +						 u64 sg_addr)
> +{
> +	struct ib_mad_private_header *mad_priv_hdr;
> +	struct ib_mad_private *recv;
> +	struct ib_mad_list_head *mad_list;
> +	int iommu_state = xsc_ib_iommu_dma_map(ibdev);
> +
> +	/* direct dma mapping */
> +	if (!iommu_state)
> +		return phys_to_virt(dma_to_phys(ibdev->dma_device, sg_addr));
> +
> +	if (iommu_state == XSC_IB_IOMMU_MAP_NORMAL)
> +		return xsc_ib_iova_to_virt(ibdev, sg_addr);
> +
> +	mad_list = container_of(wc->wr_cqe, struct ib_mad_list_head, cqe);
> +	mad_priv_hdr = container_of(mad_list, struct ib_mad_private_header, mad_list);
> +	recv = container_of(mad_priv_hdr, struct ib_mad_private, header);
> +	return &recv->grh;
> +}
> +
> +struct xsc_ib_burst_info {
> +	__u32       max_burst_sz;
> +	__u16       typical_pkt_sz;
> +	__u16       reserved;
> +};
> +
> +struct xsc_ib_modify_qp {
> +	__u32		reserved;
> +	struct xsc_ib_burst_info  burst_info;
> +	__u32		profile;
> +};
> +
> +#endif /* XSC_IB_H */

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

* Re: [PATCH 04/15] yunsilicon: add event queues, interrupts and the
  2026-10-12 11:44 ` [PATCH 04/15] yunsilicon: add event queues, interrupts and the Xin Tian
@ 2026-10-13 11:46   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] net: xsc: bitwise XOR logic error in next_eqe_sw() halts event queue processing
- [High] net: xsc: skipping teardown when firmware is offline causes Use-After-Free and resource leaks
- [High] net: xsc: spoofing device->numa_node during DMA allocation causes data races
- [High] yunsilicon: vmap() on dma_alloc_coherent() memory creates illegal cache aliases
- [Medium] net: xsc: multiple memory leaks and incorrect affinity setup in cpumask management
- [Medium] net: xsc: vunmap skipped on 32-bit architectures causes vmalloc memory leak
- [Medium] net: xsc: data race on link_event_handler can lead to NULL pointer dereference
- [Medium] net: xsc: xsc_core_vector2eqn leaves irqn uninitialized when MSI-X is disabled
--

Make it readable and polite without reproducing the patch diff context.

[Severity: High]
File: ..., Function: ..., Line: ...
Your question here...

Do not use '> diff' or any direct source recitation. Respond only with the prose descriptions.commit a872f21fb08a814fac5b1a6b91049b643f0a1ed7
Author: Xin Tian <tianx@yunsilicon.com>

yunsilicon: add event queues, interrupts and the auxiliary bus

This commit adds event queues, MSI-X vector handling, and auxiliary bus
glue for the yunsilicon driver. It sets up the PCI core to request vectors,
create completion queues, and publish auxiliary devices for the ethernet and
RDMA drivers.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/pci/eq.c, within the next_eqe_sw()
function, the logic for advancing the consumer index performs a bitwise XOR
against !!(eq->cons_index & eq->nent) with (eqe->owner_data & XSC_EQE_OWNER).
Because XSC_EQE_OWNER is BIT(7) (128), when the hardware sets the owner bit,
this evaluates to 128 ^ 1, which equals 129. Since 129 is non-zero, it returns
NULL instead of the event queue entry. Will this cause the event queue to halt
completely after exactly one cycle? Would it be better to normalize the left
side to a boolean first?

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/pci/eq.c, within xsc_destroy_unmap_eq(),
and in drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.c within both
xsc_free_irq_vectors() and xsc_free_irq(), the functions check if the firmware
is available and return early if it is not. By returning early, critical
cleanup operations like free_irq(), pci_free_irq_vectors(), and
xsc_buf_free() are skipped. Does skipping free_irq() leave the interrupt
handler active to dereference the subsequently freed `eq` object, causing a
Use-After-Free? Will this also leak PCI MSI-X allocations?

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.c, the function
set_comp_irq_affinity_hint() is called in a loop over the number of vectors.
Inside this loop, it unconditionally allocates xdev->xps_cpumask using
zalloc_cpumask_var(). Does this repeatedly overwrite the pointer and
permanently leak memory on every iteration when CONFIG_CPUMASK_OFFSTACK=y?
Additionally, if the allocation fails, it returns without freeing eq->mask.
Also, when setting the affinity hint, the code passes the newly allocated
and empty eq->mask to irq_set_affinity_hint() instead of the populated
xdev->xps_cpumask. Finally, if xsc_cmd_enable_msix() fails in
xsc_irq_eq_create(), the error path jumps to err_free_irq_evnt, which skips
clear_comp_irq_affinity_hints(). Will this leak all successfully allocated
cpumasks?

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/pci/alloc.c, within xsc_buf_free(),
there is a check that only calls vunmap() if BITS_PER_LONG == 64. However, in
xsc_buf_alloc(), the memory is unconditionally mapped using vmap() when the
requested size exceeds the direct mapping threshold. Will guarding the vunmap()
with an architecture check permanently leak the mapping on 32-bit platforms
every time a multi-page buffer is freed?

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.c, within xsc_event_work(),
the link event handler is checked for NULL before being called. However,
xsc_irq_event_unregister() can concurrently set xdev->link_event_handler to
NULL. Can the compiler issue two separate memory loads for the check and the
call, leading to a NULL pointer dereference if the pointer is cleared between
the two operations? Should this use READ_ONCE() to load the pointer into a
local variable first?

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/pci/alloc.c, the function
xsc_dma_zalloc_coherent_node() calls set_dev_node() to temporarily modify
the global device->numa_node field in order to force node-local DMA
allocations. While this is protected locally by dev_res->alloc_mutex, other
subsystems might call dev_to_node() concurrently. Can this data race cause
other parallel threads to read the spoofed value and allocate memory on the
wrong physical NUMA node?

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.c, the function
xsc_core_vector2eqn() returns 0 (success) early if xdev->caps.msix_enable is
false, without initializing the *irqn parameter. The caller, xsc_vector2eqn(),
passes this success along, which could lead to undefined behavior when it
uses the uninitialized local variable. Should *irqn be initialized before
returning on this path?

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/pci/alloc.c, within xsc_buf_alloc(),
memory allocated via dma_alloc_coherent() is converted to struct page pointers
using virt_to_page() or vmalloc_to_page(), and then remapped contiguously
using vmap(..., PAGE_KERNEL). Because DMA coherent memory is not guaranteed
to be backed by standard linear map pages, and applying PAGE_KERNEL to
potentially uncached or write-combined memory creates conflicting cache
aliases, is this architecturally illegal? Does this bypass DMA API safety
constraints and risk memory corruption or kernel panics?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=4

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

* Re: [PATCH 07/15] yunsilicon: add ethernet TX path
  2026-10-12 11:44 ` [PATCH 07/15] yunsilicon: add ethernet TX path Xin Tian
@ 2026-10-13 11:46   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 12 potential issue(s) to consider:
- [Critical] net: stale wi->skb pointer causes infinite loop and double-free on spurious CQE
- [High] net: unpopped error CQE causes infinite NAPI polling loop
- [High] net: inverted return logic in xsc_poll_tx_cq causes NAPI interrupt storms
- [High] net: TX queue can permanently stall due to missing smp_mb() after netif_tx_stop_queue
- [High] net: NAPI instances are deleted without being disabled in xsc_eth_open_channels error path
- [High] net: NULL pointer dereference of aff_mask in xsc_channel_no_affinity_change
- [High] yunsilicon: memory and DMA leak on TX queue teardown
- [Medium] net: unconditional memory leak of mailbox buffer in xsc_eth_set_cq
- [Medium] net: SQ DB arrays leaked on error path in xsc_eth_open_qp_sq
- [Medium] net: firmware teardown failures cause software memory leaks
- [Medium] net: TX packets are double-counted on retry and miscounted on drop
- [Medium] net: missing u64_stats_sync for 64-bit per-queue network statistics
--

commit 68f71bfadd8545a6a72866ea79110d42b134cfcb
Author: Xin Tian <tianx@yunsilicon.com>
Subject: yunsilicon: add ethernet TX path

This commit adds the transmit path for the yunsilicon ethernet driver.
It introduces WQE construction, completion handling, and ndo_start_xmit.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c, inside the
xsc_poll_tx_cq function around line 62, the code detects an error CQE
and returns false without calling xsc_cqwq_pop. Will this cause the NAPI
polling loop to wedge permanently as subsequent polls re-fetch the exact
same CQE? Additionally, the CQEs fetched inside the do-while loop around
line 99 bypass the error check entirely, which might result in error
CQEs being processed as successful completions.

[Severity: High]
Also in xsc_poll_tx_cq around line 99, the polling loop uses the condition
++i <= napi_budget. If the budget is fully exhausted, the loop breaks with
i equal to napi_budget + 1. The function then returns i == napi_budget,
which evaluates to false. Can this inverted return logic cause NAPI to
prematurely end the polling cycle while the queue is still full, leading
to interrupt storms?

[Severity: Critical]
Further down in xsc_poll_tx_cq around lines 84 and 96, napi_consume_skb
frees the SKB but leaves the wi->skb pointer intact. If the cyclic buffer
wraps around and a spurious CQE is fetched, this dangling pointer will
falsely pass the !skb condition check, resulting in a double-free of the
SKB and double DMA unmap. Furthermore, the handling for spurious CQEs uses
continue to skip the rest of the loop without advancing sqcc and
dma_fifo_cc. Does this result in an infinite loop by re-processing the
same SW WQE?

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_tx.c, within the
xsc_txwqe_complete function around line 185, netif_tx_stop_queue is
called if the queue lacks room. Should there be an smp_mb barrier and a
space re-check immediately after stopping the queue? Without an explicit
barrier, concurrent NAPI updates could race with the TX path, permanently
stalling the TX queue.

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/net/main.c, inside the
xsc_eth_set_cq function around line 427, the mailbox buffer in is
allocated with kvzalloc. On successful completion of xsc_core_eth_create_cq,
the function returns 0 but skips freeing the buffer. Can we add a kvfree
for the buffer on the success path to avoid permanently leaking this memory?

[Severity: Medium]
Similarly, in xsc_eth_open_qp_sq around lines 560 and 586, if a setup step
like modify_in allocation fails, the error path jumps to
err_sq_common_destroy. This path destroys the WQ and QP but does not call
xsc_free_qp_sq_db. Does this leak the dma_fifo and wqe_info arrays
allocated earlier in the function?

[Severity: High]
When tearing down channels in xsc_eth_open_channels (error path around line
639) and xsc_eth_close_channel (around line 605), netif_napi_del is
called directly. Because napi_enable was already called, should
napi_disable be executed prior to deletion to prevent a use-after-free if
the NAPI instance is scheduled concurrently?

[Severity: Medium]
In xsc_eth_close_cq and xsc_eth_close_qp_sq around lines 349 and 492, the
functions return early if xsc_core_eth_destroy_cq or
xsc_core_eth_destroy_qp report a firmware error. Bypassing xsc_eth_free_cq
and xsc_free_qp_sq on these errors appears to indefinitely leak the OS
level queue memory if the firmware is unresponsive. Is it intentional to
skip freeing software memory on a firmware timeout?

[Severity: Medium]
In xsc_eth_xmit_frame around lines 234 and 262, stats->packets is
incremented preemptively. If the packet exceeds a single WQE and is passed
to skb_linearize, a successful linearization jumps back to retry_send,
causing the packet to be double-counted. If linearization fails and jumps
to err_drop, the counter is not reverted. Should the statistics update be
moved after we guarantee the packet will not be dropped or retried?

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c, the function
xsc_channel_no_affinity_change around line 23 directly dereferences
c->aff_mask in cpumask_test_cpu. Since c->aff_mask is only assigned when
xdev->caps.msix_enable is true, and irq_get_affinity_mask can return
NULL, will this cause a NULL pointer dereference during NAPI polling on
systems without MSI-X?

[Severity: Medium]
In xsc_eth_xmit_frame around line 290, 64-bit statistic counters like
stats->bytes and stats->packets are updated directly. To prevent torn
reads on 32-bit architectures, should these per-queue statistics be updated
using the u64_stats_update_begin and u64_stats_update_end macros?

[Severity: High]
In xsc_free_qp_sq_db around line 436, the tracking arrays sq->db.wqe_info
and sq->db.dma_fifo are freed. It appears the function does not drain and
unmap active elements between sq->cc and sq->pc first. Can this lead to
a silent leak of SKBs and DMA mappings when the interface is stopped or
reconfigured while traffic is active?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=7

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

* Re: [PATCH 08/15] yunsilicon: add ethernet RX path and statistics
  2026-10-12 11:44 ` [PATCH 08/15] yunsilicon: add ethernet RX path and statistics Xin Tian
@ 2026-10-13 11:46   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 16 potential issue(s) to consider:
- [High] net: memory and resource leak in xsc_eth_open_rss_qp_rqs error path
- [High] net: out-of-bounds access in xsc_alloc_rx_wqes error path
- [High] net: page pool leak due to missing skb_mark_for_recycle
- [High] net: xsc_handle_csum reads out-of-bounds garbage due to missing headroom and PPH flag checks
- [High] net: missing napi_disable() in xsc_eth_open_channels error path
- [High] net: soft lockup in xsc_eth_napi_poll on RX buffer allocation failure
- [High] net: xsc: off-by-one in xsc_poll_tx_cq causes TX queue stalls
- [High] net: missing DMA sync for PPH header in non-linear RX path
- [High] net: xsc_handle_csum treats MAC header as PPH when PPH is absent
- [High] net: memory and resource leak in xsc_eth_open_rss_qp_rqs error path
- [Medium] net: missing u64_stats_sync for 64-bit network statistics
- [Medium] net: memory leak in xsc_eth_close_qp_rq if hardware destruction fails
- [Medium] net: page leak in xsc_alloc_rx_wqes error path for multi-fragment pages
- [Medium] net: missing DMA sync for PPH header in xsc_handle_csum
- [Medium] net: uninitialized rx headroom causes skb reallocation
- [Medium] net: missing u64_stats_sync protection for 64-bit RX/TX statistics
--

commit 4d5a3a720a692e412128ddb17b3a9264e7943e11
Author: Xin Tian <tianx@yunsilicon.com>
Subject: yunsilicon: add ethernet RX path and statistics

This commit adds the receive path for the yunsilicon driver, including page
pool backed RX queues, NAPI polling, CQ processing, and RSS/checksum
handling. It also introduces software statistics reporting via ndo_get_stats64.

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.c around line 31 in
xsc_eth_fold_sw_stats64, and in drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c
around line 158 in xsc_complete_rx_cqe, is it safe to update and read the
raw 64-bit counters without u64_stats_update_begin/end?

The driver tracks RX/TX packets and bytes using 64-bit counters (struct
xsc_rq_stats and struct xsc_sq_stats). These are updated in NAPI softirq
context without sequence locks, and read concurrently from process context
by ndo_get_stats64. On 32-bit systems, a 64-bit load/store is not atomic,
which can cause readers to observe torn values. Can we wrap these 64-bit
statistic updates and reads with the u64_stats_sync API to ensure correctness
across architectures?

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/main.c around line 956 in
xsc_eth_open_rss_qp_rqs, does the direct return of 'err' cause a memory
and resource leak?

The driver allocates software receive queues and their backing memory inside
a loop. If the firmware command xsc_core_create_resource_common() fails for
any queue, the code sets 'err = ret', continues the loop, and later executes
a direct 'return err;'. This direct return bypasses the err_create_rss_rqs
and err_alloc_rqs cleanup labels entirely, permanently leaking all previously
allocated WQ controls, fragment arrays, DMA pages, and page pools across all
channels.

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/net/main.c around line 943 in
xsc_eth_close_qp_rq, will a hardware destruction failure result in a
permanent software resource leak?

The driver issues the firmware command xsc_core_eth_destroy_qp() to destroy
the hardware queue. If this command fails, the function returns early,
skipping the vital software cleanup routines xsc_eth_free_rx_wqe() and
xsc_free_qp_rq(). Since the caller xsc_eth_close_channel() around line 1046
ignores this return value and unconditionally zeroes out the queue structure
pointers with memset, the dynamically allocated page pools, fragment arrays,
and DMA lists are permanently orphaned.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c around line 454 in
the xsc_alloc_rx_wqes error path, does passing the unmasked index 'ix + i'
cause an out-of-bounds array access?

When allocating WQEs successfully, the driver correctly calculates the
wrapped index using xsc_wq_cyc_ctr2ix(wq, ix + i). However, if an allocation
fails, the err_free_wqes recovery path passes the raw, unmasked 'ix + i'
directly to xsc_eth_dealloc_rx_wqe(). This function then passes the out-of-
bounds index to get_frag() around line 313, which blindly shifts the
unmasked value and accesses rq->wqe.frags, resulting in out-of-bounds
kernel memory reads/writes.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c around line 224 in
xsc_skb_from_cqe_linear, is there a page pool leak due to the missing
skb_mark_for_recycle() call?

In both the linear and nonlinear RX paths, the driver allocates pages from a
page_pool and attaches them to an SKB, but fails to call
skb_mark_for_recycle(skb). Without setting the recycling flag, when the
networking stack eventually frees the SKB, it will drop the page reference
via the standard put_page() path instead of routing it to
page_pool_recycle_direct(). This leaks the page pool's inflight counter and
prevents proper page recycling.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c around line 82 in
xsc_handle_csum, does the code read uninitialized out-of-bounds memory and
incorrectly parse the MAC header?

The function computes the hardware Packet Processing Header (PPH) address
using page_address(dma_info->page) + offset_from. This calculation omits
rq->buff.headroom, causing the pointer to reference uninitialized memory
before the packet payload. Additionally, the function unconditionally casts
the first bytes of this buffer to a PPH structure to read the error bitmap
without checking if the CQE indicates a PPH is present (via
XSC_CQE_HAS_PPH). If a packet lacks a PPH, this will read the Ethernet MAC
header as if it were a PPH, which can trigger random drops or misinterpret
the checksum offload status.

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c around line 305 in
xsc_put_rx_frag, can a partial allocation failure leak multi-fragment pages?

The driver manually fragments a single allocated page across multiple WQEs,
setting frag->last_in_page = 1 only on the final fragment of the page. The
recycling function relies strictly on this flag to call
page_pool_recycle_direct(). If an allocation fails midway in
xsc_alloc_rx_wqes(), the rollback path deallocates the partially populated
WQEs. If the rollback includes fragments of a page but does not reach the
WQE containing last_in_page == 1, the page is skipped and never recycled,
permanently leaking the page reference.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c around line 240 in
xsc_skb_from_cqe_nonlinear, does advancing head_offset leave the PPH header
unsynchronized for the CPU?

When a packet has a PPH header, head_offset is advanced by XSC_PPH_HEAD_LEN
before copying the header to the linear SKB buffer. As a result, both
xsc_copy_skb_header() and xsc_add_skb_frag() entirely skip the first
XSC_PPH_HEAD_LEN bytes when calling dma_sync_single_for_cpu(). Later,
around line 81, xsc_handle_csum() reads the PPH from the very beginning of
the DMA buffer. Since this memory was never synced for the CPU, non-cache-
coherent architectures will read stale memory, potentially causing the PPH
error bitmap check to fail.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/main.c around line 1051 in
xsc_eth_close_channel, is there a use-after-free risk due to a missing
napi_disable() in the error path?

During xsc_eth_open_channels() around line 1100, NAPI instances are enabled
for all channels. If a subsequent operation fails, the driver jumps to
err_close_channel. However, xsc_eth_close_channel() only calls
netif_napi_del() and entirely omits calling napi_disable(). Because
netif_napi_del() unlinks the struct but does not stop or wait for active
polling to complete, a concurrent xsc_eth_napi_poll() could run while the
underlying channel structures and memory are being freed.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c around line 142 in
xsc_eth_napi_poll, does an RX buffer allocation failure trigger an infinite
NAPI poll loop?

The driver attempts to replenish RX WQEs by calling rq->post_wqes(rq). If
memory allocation fails (e.g., page pool exhaustion), this function sets the
busy flag to true. Because busy is true, the NAPI poll routine
unconditionally returns the full budget rather than the actual work_done.
Returning the full budget signals to the networking core that the driver has
exhausted its quota and still has more work to do, causing the kernel to
immediately reschedule the NAPI poll, creating a 100 percent CPU soft lockup
until memory becomes available.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c around line 110 in
xsc_poll_tx_cq, does the loop condition cause premature NAPI termination and
TX queue stalls?

The do-while loop condition (++i <= napi_budget) allows the loop to execute
napi_budget + 1 times when the TX queue has more completions than the
budget. After processing napi_budget + 1 CQEs, the loop terminates with i
equal to napi_budget + 1. The function then returns (i == napi_budget),
which evaluates to false. This incorrectly indicates to the caller that the
TX budget was not exhausted. If RX polling also finishes within its budget,
NAPI polling stops. Because there are still pending unpolled TX CQEs that
will not trigger a new MSI-X interrupt, the TX queue can stall permanently.

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/net/main.c during xsc_eth_alloc_rq,
is rq->buff.headroom left uninitialized, causing SKB reallocation in the
RX hot path?

The driver defines rq->buff.headroom but never initializes it, leaving it
at 0. Later, in drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_rx.c around
line 207 in xsc_skb_from_cqe_linear, this results in skb_reserve(skb, 0).
Creating linear SKBs with 0 headroom forces the network stack's core
processing paths to reallocate the SKB header (via pskb_expand_head)
whenever headers need to be prepended, causing significant performance
degradation. The comment about the XDP frame data area suggests headroom
was intended to be reserved but was forgotten.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=8

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

* Re: [PATCH 05/15] yunsilicon: add the CQ and QP resource pools
  2026-10-12 11:44 ` [PATCH 05/15] yunsilicon: add the CQ and QP resource pools Xin Tian
@ 2026-10-13 11:46   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] networking-core: UAF in xsc_cq_completion due to missing RCU grace period
- [High] networking-drivers: Race condition exposing uninitialized QP state and NULL callbacks
- [High] networking-core: RCU Use-After-Free in xsc_cq_completion due to missing synchronize_rcu()
- [High] Infinite loop in xsc_set_cq_buf_pa if max_cmd_in_len is small
- [Medium] networking-drivers: Resource leak and synchronization bypass on HW teardown error
- [Medium] networking-drivers: Dead code and masked HW errors due to xsc_cmd_exec status override
- [Medium] networking-drivers: in_hdr->ver is unconditionally zeroed out in xsc_cmd_exec
- [Low] networking-drivers: Endianness mismatch printing __be16 with %d
--

commit 395a471908d4219d4c81557da37b36e1a6d0b7d1
Author: Xin Tian <tianx@yunsilicon.com>

    yunsilicon: add the CQ and QP resource pools
    
    Add the core-side completion queue and queue pair pools: the CQ/QP
    table management, the create/destroy/drain helpers and the EQ dispatch
    of CQ completion, CQ error and QP events.

> diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/cq.c b/drivers/net/ethernet/yunsilicon/xsc/pci/cq.c
> new file mode 100644
> index 0000000000000..39715682fecc3
> --- /dev/null
> +++ b/drivers/net/ethernet/yunsilicon/xsc/pci/cq.c
[ ... ]
> +static int xsc_set_cq_context(struct xsc_core_device *xdev,
> +			      struct xsc_cq_context_ex *ctx_ex,
> +			      u32 *pa_list_base, u32 *cqn)
> +{
> +	struct xsc_set_cq_context_mbox_in in;
> +	struct xsc_set_cq_context_mbox_out out;
> +	int err;
> +
> +	memset(&in, 0, sizeof(in));
> +	memcpy(&in.ctx_ex, ctx_ex, sizeof(*ctx_ex));
> +	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_SET_CQ_CONTEXT);
> +	in.hdr.ver = cpu_to_be16(CMD_SET_CQ_CONTEXT_V1);
> +
> +	memset(&out, 0, sizeof(out));
> +	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));

[Severity: Medium]
Does xsc_cmd_exec() preserve the version parameter set here? It looks like
xsc_cmd_exec() might unconditionally zero out in_hdr->ver before dispatching
the command to hardware, which would discard the CMD_SET_CQ_CONTEXT_V1 flag.

> +	if (err || out.hdr.status) {
> +		pci_err(xdev->pdev, "failed to set cq context\n");
> +		return -EIO;
> +	}
> +
> +	*pa_list_base = be32_to_cpu(out.cq_pa_list_base);
> +	*cqn = be32_to_cpu(out.cqn);
> +	return 0;
> +}
> +
> +static int xsc_set_cq_buf_pa(struct xsc_core_device *xdev,
> +			     struct xsc_create_cq_ex_mbox_in *req,
> +			     u32 pa_list_base, u32 cqn)
> +{
> +	struct xsc_set_cq_buf_pa_mbox_in *in;
> +	struct xsc_set_cq_buf_pa_mbox_out out;
> +	u32 pa_num_total = be32_to_cpu(req->ctx_ex.pa_num_ex);
> +	u32 pa_num_max = (xdev->caps.max_cmd_in_len - sizeof(*in)) / sizeof(__be64);
> +	u32 pa_num_left = pa_num_total;
> +	u32 pa_num, copy_len;
> +	int in_len, err;
> +
> +	while (pa_num_left) {
> +		pa_num = min(pa_num_max, pa_num_left);

[Severity: High]
Could this result in an infinite loop if the hardware returns a capability
where max_cmd_in_len is small (e.g., between 16 and 23)? pa_num_max would
evaluate to 0, causing pa_num to be 0. The loop would never decrement
pa_num_left and run infinitely. If max_cmd_in_len is < 16, the subtraction
might underflow to a massive unsigned value. Does xsc_cmd_query_hca_cap()
enforce a safe minimum bound?

> +		copy_len = pa_num * sizeof(__be64);
> +		in_len = sizeof(*in) + copy_len;
[ ... ]
> +int xsc_core_create_cq(struct xsc_core_device *xdev, struct xsc_core_cq *cq,
> +		       struct xsc_create_cq_ex_mbox_in *in, int inlen)
> +{
> +	struct xsc_cq_table *table = &xdev->dev_res->cq_table;
> +	struct xsc_create_cq_mbox_out out;
> +	u32 pa_list_base = 0;
> +	u32 cqn = 0;
> +	int err;
> +
> +	if (inlen < xdev->caps.max_cmd_in_len) {
> +		in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_CREATE_CQ_EX);
> +		memset(&out, 0, sizeof(out));
> +		err = xsc_cmd_exec(xdev, in, inlen, &out, sizeof(out));
> +		if (err)
> +			return err;
> +
> +		if (out.hdr.status && out.hdr.status != XSC_CMD_STATUS_NOT_SUPPORTED)
> +			return xsc_cmd_status_to_err(&out.hdr);
> +
> +		if (out.hdr.status == XSC_CMD_STATUS_NOT_SUPPORTED) {
> +			err = xsc_create_cq_compat(xdev, in, &out);

[Severity: Medium]
Will this compatibility fallback ever be reached? If xsc_cmd_exec() sees that
out_hdr->status is set, it might return -EIO directly, triggering the earlier
if (err) return err; block. This would make the check for
XSC_CMD_STATUS_NOT_SUPPORTED dead code and mask the specific firmware error.

> +			if (err)
> +				return err;
> +		}
[ ... ]
> +int xsc_core_destroy_cq(struct xsc_core_device *xdev, struct xsc_core_cq *cq)
> +{
> +	struct xsc_cq_table *table = &xdev->dev_res->cq_table;
> +	struct xsc_destroy_cq_mbox_in in;
> +	struct xsc_destroy_cq_mbox_out out;
> +	struct xsc_core_cq *tmp;
> +	int err;
> +
> +	spin_lock_irq(&table->lock);
> +	tmp = radix_tree_delete(&table->tree, cq->cqn);
> +	spin_unlock_irq(&table->lock);
> +	if (!tmp || tmp != cq) {
> +		pci_warn(xdev->pdev, "cq 0x%x not found in tree\n", cq->cqn);
> +		return -EINVAL;
> +	}
> +
> +	memset(&in, 0, sizeof(in));
> +	memset(&out, 0, sizeof(out));
> +	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DESTROY_CQ);
> +	in.cqn = cpu_to_be32(cq->cqn);
> +
> +	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
> +	if (err)
> +		return err;
> +
> +	if (out.hdr.status)
> +		return xsc_cmd_status_to_err(&out.hdr);

[Severity: Medium]
If the hardware command fails, returning early here skips the refcount
decrement and the wait_for_completion() synchronization. Can this leak the
resource if the base reference is never dropped, or cause a use-after-free
for active concurrent event handlers if the caller frees the memory anyway?

[Severity: High]
Since the CQ is deleted from the RCU-protected radix tree above, shouldn't
there be a synchronize_rcu() or kfree_rcu() wait before returning? Without
an RCU grace period, concurrent lockless readers (like xsc_cq_completion)
might access the CQ structure after the caller frees the memory.

> +
> +	if (atomic_dec_and_test(&cq->refcount))
> +		complete(&cq->free);
> +	wait_for_completion(&cq->free);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(xsc_core_destroy_cq);
> diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/eq.c b/drivers/net/ethernet/yunsilicon/xsc/pci/eq.c
> index d273a42d6ffe7..ff4bcb42b33ff 100644
> --- a/drivers/net/ethernet/yunsilicon/xsc/pci/eq.c
> +++ b/drivers/net/ethernet/yunsilicon/xsc/pci/eq.c
> @@ -53,16 +53,59 @@ static void eq_update_ci(struct xsc_eq *eq, int arm)
>  
>  static void xsc_cq_completion(struct xsc_core_device *xdev, u32 cqn)
>  {
> +	struct xsc_cq_table *table = &xdev->dev_res->cq_table;
> +	struct xsc_core_cq *cq;
> +
> +	rcu_read_lock();
> +	cq = radix_tree_lookup(&table->tree, cqn);
> +	if (likely(cq))
> +		atomic_inc(&cq->refcount);

[Severity: High]
Is this lockless RCU lookup safe for an object being destroyed? The teardown
path in xsc_core_destroy_cq() deletes the CQ from the tree without waiting
for an RCU grace period. Additionally, shouldn't this use
atomic_inc_not_zero() instead of atomic_inc() to avoid resurrecting an
object that might already have its refcount at 0?

> +	rcu_read_unlock();
[ ... ]
> +static void xsc_eq_cq_event(struct xsc_core_device *xdev,
> +			    u32 cqn, int event_type)
> +{
> +	struct xsc_cq_table *table = &xdev->dev_res->cq_table;
> +	struct xsc_core_cq *cq;
> +
> +	spin_lock(&table->lock);
> +	cq = radix_tree_lookup(&table->tree, cqn);
> +	if (likely(cq))
> +		atomic_inc(&cq->refcount);
> +	spin_unlock(&table->lock);
> +
> +	if (unlikely(!cq)) {
> +		pci_err(xdev->pdev, "Async event for bogus CQ, cqn=%d\n",
> +			cqn);
> +		return;
> +	}
> +
> +	cq->event(cq, event_type);

[Severity: High]
Is it possible for cq->event to be NULL here? The CQ is added to the global
radix tree in xsc_core_create_cq() before the caller has a chance to
initialize the cq->event callback. If an interrupt arrives immediately, this
could cause a NULL pointer dereference.

> +
> +	if (atomic_dec_and_test(&cq->refcount))
> +		complete(&cq->free);
>  }
>  
>  static int xsc_eq_int(struct xsc_core_device *xdev, struct xsc_eq *eq)
[ ... ]
> diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/qp.c b/drivers/net/ethernet/yunsilicon/xsc/pci/qp.c
> new file mode 100644
> index 0000000000000..8ea57a09f6ceb
> --- /dev/null
> +++ b/drivers/net/ethernet/yunsilicon/xsc/pci/qp.c
> @@ -0,0 +1,356 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (C) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
> + * All rights reserved.
> + */
> +
> +#include <linux/gfp.h>
> +#include <linux/time.h>
> +#include <linux/export.h>
> +#include <linux/kthread.h>
> +
> +#include "common/xsc_core.h"
> +#include "common/xsc_cmd_api.h"
> +#include "qp.h"
> +
> +int xsc_core_create_resource_common(struct xsc_core_device *xdev,
> +				    struct xsc_core_qp *qp)
> +{
> +	struct xsc_qp_table *table = &xdev->dev_res->qp_table;
> +	int err;
> +
> +	spin_lock_irq(&table->lock);
> +	err = radix_tree_insert(&table->tree, qp->qpn, qp);
> +	spin_unlock_irq(&table->lock);
> +	if (err)
> +		return err;
> +
> +	atomic_set(&qp->refcount, 1);
> +	init_completion(&qp->free);

[Severity: High]
Should the refcount and completion be initialized before the QP is inserted
into the radix tree? Once inserted, asynchronous event handlers like
xsc_qp_event() can look up the QP. If an event fires immediately, the
handler might execute atomic_inc(&qp->refcount) on uninitialized memory,
which is then clobbered by atomic_set() here.

> +	qp->pid = current->pid;
> +
> +	return 0;
> +}
[ ... ]
> +void xsc_qp_event(struct xsc_core_device *xdev, u32 qpn, int event_type)
> +{
> +	struct xsc_qp_table *table = &xdev->dev_res->qp_table;
> +	struct xsc_core_qp *qp;
> +
> +	spin_lock(&table->lock);
> +
> +	qp = radix_tree_lookup(&table->tree, qpn);
> +	if (qp)
> +		atomic_inc(&qp->refcount);
> +
> +	spin_unlock(&table->lock);
> +
> +	if (!qp) {
> +		pci_err(xdev->pdev, "Async event for bogus QP 0x%x\n", qpn);
> +		return;
> +	}
> +
> +	qp->event(qp, event_type);

[Severity: High]
Can qp->event be NULL here? The QP is published to the radix tree before the
caller initializes the qp->event function pointer. An early interrupt could
trigger a NULL pointer dereference.

> +
> +	if (atomic_dec_and_test(&qp->refcount))
> +		complete(&qp->free);
> +}
[ ... ]
> +int xsc_core_eth_create_rss_qp_rqs(struct xsc_core_device *xdev,
> +				   struct xsc_create_multiqp_mbox_in *in,
> +				   int insize,
> +				   u32 *p_qpn_base)
> +{
> +	struct xsc_create_multiqp_mbox_out out;
> +	int err;
> +
> +	in->hdr.opcode = cpu_to_be16(XSC_CMD_OP_CREATE_MULTI_QP);
> +	err = xsc_cmd_exec(xdev, in, insize, &out, sizeof(out));
> +	if (err) {
> +		pci_err(xdev->pdev,
> +			"failed to create rss rq, qp_num=%d, type=%d, err=%d out.status=%u\n",
> +			in->qp_num, in->qp_type, err, out.hdr.status);

[Severity: Low]
Is it intentional to pass in->qp_num directly to %d? Since in->qp_num is a
__be16 field, this will print garbled values on little-endian architectures
unless converted with be16_to_cpu().

> +		return err;
> +	}
[ ... ]
> +int xsc_core_destroy_qp(struct xsc_core_device *xdev,
> +			struct xsc_core_qp *qp)
> +{
> +	struct xsc_destroy_qp_mbox_in in;
> +	struct xsc_destroy_qp_mbox_out out;
> +	int err;
> +
> +	xsc_core_destroy_resource_common(xdev, qp);
> +
> +	memset(&in, 0, sizeof(in));
> +	memset(&out, 0, sizeof(out));
> +	in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_DESTROY_QP);
> +	in.qpn = cpu_to_be32(qp->qpn);
> +
> +	err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
> +	if (err)
> +		return err;
> +
> +	if (out.hdr.status)
> +		return xsc_cmd_status_to_err(&out.hdr);

[Severity: Medium]
Does returning early here skip decrementing the global active QP counter
(atomic_dec(&xdev->num_qps))? In xsc_core_create_qp(), num_qps is incremented
after resource creation, but a failure in the hardware destroy path seems
to leak this counter.

> +
> +	atomic_dec(&xdev->num_qps);
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(xsc_core_destroy_qp);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=5

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

* Re: [PATCH 03/15] yunsilicon: add the command queue
  2026-10-12 11:43 ` [PATCH 03/15] yunsilicon: add the command queue Xin Tian
  2026-10-13  3:11   ` Junxian Huang
@ 2026-10-13 11:46   ` sashiko-bot
  1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 14 potential issue(s) to consider:
- [High] net: yunsilicon: UAF and double semaphore release on command timeout
- [High] net: yunsilicon: kernel panic on kthread_create failure due to ERR_PTR
- [High] net: yunsilicon: deadlock between mode transition and workqueue
- [High] net: yunsilicon: index bounds check bypass and NULL dereference
- [High] net: yunsilicon: DMA API violation for continuously polled command rings
- [High] net: yunsilicon: missing dma_rmb() before reading polled descriptor
- [High] locking: ABBA deadlock and data race on cmd->cq_cid in xsc_cmd_use_events
- [Medium] net: yunsilicon: CPU hog via mdelay in kthread polling loop
- [Medium] net: yunsilicon: infinite tight loop on owner_bit mismatch
- [Medium] net: yunsilicon: lockless access to global board_info array causes race condition
- [Medium] net: yunsilicon: signed integer overflows in bitwise shifts
- [Medium] net: yunsilicon: semaphore corruption and hang on xsc_alloc_ent failure
- [Low] net: yunsilicon: dead code in command header version validation
- [Low] net: yunsilicon: min() macro type mismatch causes compile failure
--

Do not include the diff headers (e.g., 'diff --git...'). Do not copy-paste code snippets from the prompt. Just describe the problem conceptually and point to where it occurs.commit 2019eb79ab2f1a25c0ab805fcdc1f2dc6fc6691f
Author: Xin Tian <tianx@yunsilicon.com>
Subject: yunsilicon: add the command queue

This commit adds the hardware command queue for the yunsilicon driver,
including the request/response rings, kthread-based polling, and the
initialization sequence for HCA capabilities. It introduces the `xsc_cmd`
API functions and the internal hardware setup logic.

[Severity: High]
Can the timeout path in xsc_cmd_invoke() lead to a use-after-free and a
double semaphore release? When wait_for_completion_timeout() returns a
timeout error, the code jumps to err_sem_up where it immediately calls
up(&cmd->sem) and xsc_free_cmd(ent). The pending work item is never
canceled. If the work hasn't run yet, cmd_work_handler() could later
dereference the freed entity and block on the semaphore. Furthermore, since
the entry is not removed from cmd->ent_arr, the hardware completion handler
(xsc_cmd_comp_handler) will eventually process the timed-out entry, writing
to the freed structure and calling up(&cmd->sem) a second time, which
corrupts the driver's flow control state.

[Severity: High]
Does xsc_cmd_init() correctly handle kthread_create() failures? The code
checks for an error using "if (!cmd->cq_task)", assuming that
kthread_create() returns NULL on failure. However, kthread_create() returns
an ERR_PTR on failure. Since the boolean check will not catch the error, the
invalid pointer is immediately passed to wake_up_process(), which will cause
a kernel panic.

[Severity: High]
Is there an ABBA deadlock risk during the transition to event mode? In
xsc_cmd_use_events(), the code acquires all max_reg_cmds semaphore permits
in a loop and then calls flush_workqueue(cmd->wq). However, the workqueue
thread running cmd_work_handler() unconditionally attempts to acquire a
permit using down(&cmd->sem). If a work item was recently queued, it will
block indefinitely waiting for a semaphore held by xsc_cmd_use_events(),
while xsc_cmd_use_events() blocks indefinitely waiting for the workqueue to
flush. The same deadlock risk applies to xsc_cmd_use_polling().

[Severity: High]
Are there concurrent lockless updates to the completion queue index? Both
xsc_cmd_resp_handler() and the kthread xsc_cmd_cq_polling() update
cmd->cq_cid directly without any synchronization. If a spurious interrupt or
asynchronous hardware event fires while the polling thread is still active
(for example, during the mode transition window), they can concurrently
process the same completions and corrupt the CID index.

[Severity: Medium]
Does the polling kthread loop properly yield the CPU? In
xsc_cmd_cq_polling(), if no new responses are detected, it delays for 3
milliseconds using mdelay(3) before checking again. Because mdelay() uses a
busy-wait loop, this continuously running thread will peg an entire CPU core
at 100% usage without yielding it to the scheduler. 

[Severity: Medium]
What happens if the hardware updates the doorbell but the buffer is not yet
ready? In xsc_cmd_cq_polling(), if cmd->owner_bit does not match
rsp->owner_bit, the code prints an error and executes a continue statement.
Because this skips the delay and jumps immediately to the top of the loop,
it instantly re-evaluates the condition, creating an unthrottled infinite
loop that spams the log and soft-locks the CPU.

[Severity: High]
Does the array bounds check in xsc_cmd_comp_handler() allow an out-of-bounds
access? The function checks if idx > cmd->max_reg_cmds, where max_reg_cmds
is 31. If the hardware provides an index of 31, it passes this check. The
subsequent bitmask check shifts 1 by 31, which triggers a signed integer
overflow resulting in zero, meaning it also passes the mask validation.
Since xsc_alloc_ent() only allocates indices 0 to 30, cmd->ent_arr[31] is
NULL, causing an immediate kernel panic when dereferenced. The check should
be idx >= cmd->max_reg_cmds.

[Severity: Medium]
Is the lockless access to the board_info array safe during driver probe? In
xsc_alloc_board_info() in hw.c, the code searches for a NULL slot and
allocates a new entry. During simultaneous probing of multiple devices (like
VFs or multi-port interfaces), concurrent threads could observe the same
empty index and allocate memory to the same slot, resulting in a memory leak
and corrupted sharing configuration.

[Severity: High]
Does the command ring mapping violate DMA API rules for polled memory? The
command buffers are allocated with __get_free_pages() and mapped using
dma_map_single(DMA_BIDIRECTIONAL). Since the driver continuously polls this
memory concurrently with hardware updates (checking rsp->owner_bit) without
calling dma_sync_single_for_cpu(), CPUs on architectures without cache-coherent
PCIe will perpetually read stale cached values and hang. Polled rings should
use dma_alloc_coherent().

[Severity: High]
Is a memory barrier missing when checking the ownership bit? In the
completion handlers, the CPU checks if cmd->owner_bit matches
rsp->owner_bit. Once confirmed, it proceeds to read the descriptor data.
Without a dma_rmb() immediately following the ownership check, the CPU may
speculatively fetch the descriptor payload before the ownership bit has
flipped, causing it to process stale or corrupted hardware data.

[Severity: Low]
Does xsc_cmd_exec() validate the correct version field? The function forces
the input header version to 0, and then immediately checks if it is not 0,
which triggers a WARN_ON. This logic is dead code and seems to be missing
the intended validation of the hardware's output response version.

[Severity: Low]
Will the strict type-checking of the min() macro cause a build failure? In
xsc_alloc_cmd_msg(), the code uses the min() macro to compare the result of
sizeof() with an unsigned int. Because sizeof returns a size_t, this will
cause a compile-time failure.

[Severity: Medium]
Do the bitwise shift operations invoke undefined behavior? In
xsc_cmd_init(), the entry mask is created using (1 << cmd->max_reg_cmds). If
max_reg_cmds is 31, this causes a signed integer overflow. A similar
overflow occurs in xsc_cmd_query_hca_cap() when calculating max_mtt if the
hardware capability value is greater than or equal to 31. These should use
unsigned literals like 1UL or 1ULL.

[Severity: Medium]
Does an allocation failure in cmd_work_handler() lead to a hanging thread?
If xsc_alloc_ent() fails, the handler prints an error, drops the semaphore,
and returns without calling complete(). This abandons the completion object,
forcing the initiating thread to hang for the full 60-second timeout. Once
it times out, the error path releases the semaphore again, artificially
incrementing the permit count and permanently corrupting the driver's state.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=3

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

* Re: [PATCH 10/15] yunsilicon: add RDMA PD/MR
  2026-10-12 11:44 ` [PATCH 10/15] yunsilicon: add RDMA PD/MR Xin Tian
@ 2026-10-13 11:46   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] rdma/xsc: massive heap buffer overflow due to 32-bit truncation of npages
- [Critical] rdma/xsc: arbitrary physical memory exposure via unvalidated mmap size
- [High] rdma/xsc: MPT resource leak on DMA MR deregistration due to npages check
- [High] rdma/xsc: xsc_ib_get_dma_mr ignores ib_pd argument and hardcodes pdn to 0
- [High] drivers/infiniband: hw/xsc: unhandled xsc_reg_local_dma_mr failure
- [Medium] rdma/xsc: undefined behavior and zero-division on bitwise shifts exceeding 31 bits
--

commit 858863cb9a838a1ac3899ebcbed7d2f60b3b8241
Author: Xin Tian <tianx@yunsilicon.com>

yunsilicon: add RDMA PD/MR

This commit adds protection domains and memory regions for the yunsilicon
RDMA driver. It introduces PD allocation, kernel and user MR registration
paths, and DMA address translation helpers.

> diff --git a/drivers/infiniband/hw/xsc/main.c b/drivers/infiniband/hw/xsc/main.c
> --- a/drivers/infiniband/hw/xsc/main.c
> +++ b/drivers/infiniband/hw/xsc/main.c
> @@ -343,45 +343,45 @@ static int xsc_ib_mmap(struct ib_ucontext *ibcontext, struct vm_area_struct *vma
>  {
>  	struct xsc_ib_dev *dev = to_xdev(ibcontext->device);
>  	struct xsc_core_device *xdev = dev->xdev;
>  	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
>  	resource_size_t reg_base;
>  	resource_size_t reg_size = vma->vm_end - vma->vm_start;

[ ... ]

>  	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
>  	return remap_pfn_range(vma, vma->vm_start, reg_base >> PAGE_SHIFT,
>  			       reg_size, vma->vm_page_prot);
>  }

[Severity: Critical]
Can this mapping size be safely used without validation?

In xsc_ib_mmap(), the requested mapping size is calculated entirely from
userspace input (reg_size = vma->vm_end - vma->vm_start). The function
checks the offset parameter to select a hardware base address, but it
doesn't validate whether reg_size exceeds the actual bounds of that
hardware region.

Passing this unvalidated reg_size directly to remap_pfn_range() could allow
userspace to map arbitrary adjacent physical memory or PCI MMIO regions.

> diff --git a/drivers/infiniband/hw/xsc/mem.c b/drivers/infiniband/hw/xsc/mem.c
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/mem.c
> @@ -0,0 +1,161 @@

[ ... ]

> +static void __xsc_ib_cont_pages(struct ib_umem *umem, u64 addr,
> +				unsigned long max_page_shift,
> +				int *count, int *shift, int *ncont)
> +{

[ ... ]

> +		if (base + p != pfn) {
> +			/* If either the offset or the new
> +			 * base are unaligned update m
> +			 */
> +			tmp = (unsigned long)(pfn | p);
> +			if (!IS_ALIGNED(tmp, 1 << m))
> +				m = find_first_bit(&tmp, BITS_PER_LONG);
> +
> +			base = pfn;
> +			p = 0;
> +		}

[Severity: Medium]
Could this result in undefined behavior and a zero division?

If the DMA address is 0 (which is a valid IOMMU DMA address), tmp will be
0, causing find_first_bit() to return BITS_PER_LONG (64). The subsequent
shift of a 32-bit signed literal (1 << m) will be shifted by 64 bits.

This undefined value is later used as a divisor:
*ncont = DIV_ROUND_UP(i, (1 << m));

Similarly, in xsc_ib_get_buf_offset() lower in this file,
page_size = 1 << page_shift will overflow for 2GB hugepages
(page_shift == 31), incorrectly sign-extending the 64-bit page_mask.

> diff --git a/drivers/infiniband/hw/xsc/mr.c b/drivers/infiniband/hw/xsc/mr.c
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/mr.c
> @@ -0,0 +1,313 @@

[ ... ]

> +struct ib_mr *xsc_ib_get_dma_mr(struct ib_pd *pd, int acc)
> +{

[ ... ]

> +	req = &in->req;
> +	req->acc = convert_access(acc);
> +	req->va_base = 0;
> +	req->map_en = !(XSC_MPT_MAP_EN);
> +
> +	err = xsc_core_create_mkey(xdev, &mr->xmr);

[Severity: High]
Does this code assign the protection domain to the DMA MR?

The req structure is zero-initialized via kzalloc_obj(), so req->pdn
defaults to 0. The ib_pd argument provided by the caller doesn't appear
to be assigned to req->pdn before calling xsc_core_register_mr().

This could cause the hardware to register the MR under PD 0, which might
break kernel components that allocate QPs in their own PDs and rely on
this DMA MR.

[ ... ]

> +struct ib_mr *xsc_ib_reg_user_mr(struct ib_pd *pd, u64 start, u64 length,
> +				 u64 virt_addr, int access_flags,
> +				 struct ib_dmah *dmah,
> +				 struct ib_udata *udata)
> +{

[ ... ]

> +	pgsz = ib_umem_find_best_pgsz(umem, XSC_MR_PAGE_CAP_MASK, virt_addr);
> +	if (!pgsz) {
> +		ibdev_warn(&dev->ib_dev, "find best page size failed\n");
> +		err = -EINVAL;
> +		goto error;
> +	}
> +
> +	page_shift = order_base_2(pgsz);
> +	npages = ib_umem_num_dma_blocks(umem, pgsz);

[Severity: Critical]
Does this code truncate the block count?

ib_umem_num_dma_blocks() returns a 64-bit size_t block count, which is
being assigned to a 32-bit signed int npages.

For large memory regions, truncating this value causes the subsequent
buffer allocation in reg_create() to be undersized:

inlen = sizeof(*in) + sizeof(*in->req.pas) * npages;
in = xsc_vzalloc(inlen);

Then rdma_umem_for_each_dma_block() will execute based on the actual umem
blocks, writing 64-bit physical addresses far out of the allocated bounds.
Could this lead to a heap buffer overflow?

[ ... ]

> +int xsc_ib_dereg_mr(struct ib_mr *ibmr, struct ib_udata *udata)
> +{

[ ... ]

> +	ibdev_dbg(&dev->ib_dev, "dereg mkey = 0x%x\n", mr->xmr.key);
> +
> +	if (mr->npages) {
> +		err = xsc_core_dereg_mr(dev->xdev, &mr->xmr);

[Severity: High]
Does this skip firmware deregistration for local DMA MRs?

xsc_ib_get_dma_mr() initializes a local DMA MR with req->pa_num = 0, which
results in mr->npages = 0, but it successfully allocates and configures an
MPT entry in hardware.

When deregistering, checking if (mr->npages) bypasses the
XSC_CMD_OP_DEREG_MR teardown command for these MRs. Will this leak MPT
resources for every DMA MR created and destroyed?

[ ... ]

> +void xsc_reg_local_dma_mr(struct xsc_core_device *dev)
> +{

[ ... ]

> +	err = xsc_core_register_mr(dev, NULL, &in, sizeof(in));
> +	if (err)
> +		dev_err(&dev->pdev->dev, "\n");
> +}

[Severity: High]
Is it safe to ignore this MR registration failure?

If xsc_core_register_mr() fails, the error is caught and printed, but the
function returns void without propagating the failure. The caller (init_one)
will then proceed to register the IB device and advertise
local_dma_lkey = 0xFF.

If components attempt to use this unregistered MKEY for DMA, won't the
hardware generate memory protection faults?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=10

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

* Re: [PATCH 06/15] yunsilicon: add ethernet netdev registration
  2026-10-12 11:44 ` [PATCH 06/15] yunsilicon: add ethernet netdev registration Xin Tian
@ 2026-10-13 11:46   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] net: Negative error code truncated to u8 falsely reports Link UP on firmware failure
- [High] net: Out-of-bounds array write in XPS configuration when comp_vectors exceeds netdev queues
- [High] yunsilicon: TOCTOU race on link_event_handler causing NULL pointer dereference
- [High] net: carrier state desync due to lockless adapter->opened race
- [Medium] net: XPS cpumask accumulates CPUs across queue iterations breaking 1-to-1 mapping
- [Medium] net: Missing u64_stats_sync protection for 64-bit per-CPU statistics
- [Medium] net: xsc_eth_open silently ignores xsc_set_port_admin_status failure
- [Medium] net: Race between xsc_eth_close and link event handler leaves carrier ON while device closed
- [Medium] net: Race condition in xsc_eth_open silently drops valid hardware link events
- [Low] net: Hardware-provided MAC address is not validated before assignment
--

commit a6ac5cd27a115b0dca9b12956a0808733a45d432
Author: Xin Tian <tianx@yunsilicon.com>
Subject: yunsilicon: add ethernet netdev registration

This commit adds the ethernet auxiliary driver for Yunsilicon XSC devices. It allocates the netdev, programs the MAC address and MTU, registers it, and implements the basic open and stop operations for port admin status and link events.

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/net/main.c at xsc_set_default_xps_cpumasks (around line 155), cpumask_clear is executed exactly once before the loop.

Inside the loop, cpumask_set_cpu adds a CPU for the current queue index. Because the shared mask is not cleared between iterations, CPUs accumulate across queues.

Could this break the intended Transmit Packet Steering isolation by mapping all traffic to the highest-indexed TX queue?

[Severity: Low]
Looking at drivers/net/ethernet/yunsilicon/xsc/net/main.c in xsc_eth_l2_addr_init (around line 318), the hardware-provided MAC address is retrieved via xsc_eth_get_mac.

If the hardware returns success, the retrieved MAC is directly applied to the netdevice without validation.

Should we check the address with is_valid_ether_addr to ensure the hardware doesn't supply an all-zero or multicast MAC address before setting it?

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/pci/vport.c at xsc_core_query_vport_state (around line 28), the function returns a u8 status.

When xsc_cmd_exec fails, it returns a standard negative integer error code which is then truncated to a u8.

Back in drivers/net/ethernet/yunsilicon/xsc/net/main.c at xsc_eth_get_link_status (around line 105), this truncated non-zero value is evaluated as a successful Link UP state.

Might this cause the driver to erroneously transition the carrier state to UP during a mailbox timeout or hardware failure?

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_stats.h (around line 11), struct xsc_rq_stats and struct xsc_sq_stats store 64-bit packet and byte counters as plain u64 fields.

Since these counters are updated in the NAPI softirq path and read globally without u64_stats_sync protection, will this result in torn reads on 32-bit platforms?

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/net/main.c at xsc_eth_open (around line 189), the driver sends a hardware mailbox command via xsc_set_port_admin_status to physically enable the port.

The return value is discarded and the function unconditionally returns 0.

If the hardware initialization fails, could the kernel network stack incorrectly consider the interface UP while the physical port remains down?

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/net/main.c at xsc_eth_close (around line 208), adapter->opened is set to false, followed by netif_carrier_off, and finally xsc_irq_event_unregister.

Is there a race condition where a concurrent link event handler passes the opened check before it flips, waits out the carrier status change, and then turns the carrier back ON just before the unregister step, leaving the carrier ON while the device is closed?

[Severity: Medium]
Also in drivers/net/ethernet/yunsilicon/xsc/net/main.c at xsc_eth_open (around line 182), the event handler is registered before the adapter->opened flag is set to true.

If a hardware link state change triggers an interrupt in this exact window, wouldn't the async event handler execute, check the flag, and return early, thereby permanently dropping a valid hardware link event?

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/main.c at xsc_eth_build_nic_params (around line 74), the raw hardware capability is stored without capping it to the maximum channel size.

Then in xsc_set_default_xps_cpumasks (around line 155), the loop iterates up to this uncapped limit when calling netif_set_xps_queue.

If the hardware supports more completion vectors than allocated TX queues, could this trigger an out-of-bounds array write in the network core?

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/pci/pci_irq.c at xsc_event_work (around line 309), the link_event_handler function pointer is checked and then called.

Concurrently, xsc_irq_event_unregister (around line 358) assigns NULL to the handler before flushing the workqueue.

Since READ_ONCE is not used, is it possible for the compiler to emit multiple memory loads, allowing the function pointer to become NULL between the check and the call, leading to a panic?

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/net/main.c, xsc_eth_link_event_handler (around line 138) performs a lockless read of adapter->opened.

Because this races with both ndo_open and ndo_stop, is there a risk that valid link UP events are dropped during open, or stale carrier ON events are applied after close, leading to a complete desync of the carrier state?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=6

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

* Re: [PATCH 01/15] yunsilicon: add PCI core driver skeleton
  2026-10-12 11:43 ` [PATCH 01/15] yunsilicon: add PCI core driver skeleton Xin Tian
@ 2026-10-13 11:46   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] mm: invalid kfree() on memory allocated with kvzalloc_obj()
- [High] hw: endian-dependent C bitfields in hardware descriptors corrupt data
- [High] hw: native host-endian types used in hardware DMA structures
- [High] hw: implicit compiler padding corrupts hardware command layouts
- [Medium] pci: missing dma_set_mask() forces 32-bit streaming DMA fallback
--

commit 2f6b2de0263e58c06ddcb030ee0e72ee0f685f39
Author: Xin Tian <tianx@yunsilicon.com>
Subject: yunsilicon: add PCI core driver skeleton

This commit adds the basic PCI core driver for the Yunsilicon XSC network
device. It includes Kconfig, Makefiles, register definitions, and basic PCI
probe/remove routines that enable the device and allocate the per-function
resource structure.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/pci/main.c, inside xsc_dev_res_init()
around line 105, xdev->dev_res is allocated using kvzalloc_obj(). However,
in xsc_dev_res_cleanup() at line 118, the memory is freed using kfree()
instead of kvfree(). Does this code potentially cause a kernel panic or SLUB
corruption during driver unbind or probe failure? If kvmalloc() falls back to
a vmalloc address because physically contiguous memory is unavailable, calling
kfree() on it violates the allocator API.

[Severity: Medium]
In drivers/net/ethernet/yunsilicon/xsc/pci/main.c within xsc_set_dma_caps()
at line 22, the code configures a 64-bit mask for coherent allocations via
dma_set_coherent_mask(), but omits dma_set_mask() for the streaming DMA mask.
Is the streaming DMA mask intentionally left at the 32-bit default? By not
setting it, all network packet buffers mapped via dma_map_single() or
dma_map_page() will be restricted to the lower 4GB of physical RAM. On 64-bit
systems with more than 4GB of RAM, this forces the networking stack to bounce
all network traffic through the SWIOTLB, which can cause massive performance
degradation and potential silent allocation failures if the SWIOTLB is
exhausted.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/common/xsc_core.h (for xsc_cqe at line
242, and xsc_send_wqe_ctrl_seg at line 99) and xsc_cmdq.h (for
xsc_cmd_layout at line 113), hardware descriptors mapped to device DMA memory
define C bitfields using types like __le32 and u8. Will these hardware
descriptor layouts break on big-endian architectures? The C standard dictates
that bitfield packing order is implementation-defined. On little-endian
systems, GCC packs bits from LSB to MSB, but on big-endian systems, from MSB
to LSB. Furthermore, mixing types like u8 and __le32 in bitfields causes
compiler-dependent alignment padding that bloats structural sizes. Compiling
this driver on a big-endian system might result in malformed commands and
silent data corruption. Fixed-size types with bitwise masks should likely be
used instead.

[Severity: High]
In drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd.h, several structures
passed to or read from the hardware via DMA or mailbox commands define fields
using native host types. For example, xsc_cq_context_ex uses u32 pa_num_ex
at line 196, xsc_event_linkinfo uses u32 and u64 for linkspeed and supported
at line 863, and xsc_hw_stats_rdma_pf uses u64 for statistics at line 927.
Can this cause data corruption on big-endian hosts? Hardware devices operate
with a fixed endianness, and using native host types will cause the CPU on
big-endian hosts to read or write the bytes without swapping them, producing
incorrect values. All fields in DMA-shared structures typically need explicit
__le32/__le64 or __be32/__be64 types.

[Severity: High]
Also in drivers/net/ethernet/yunsilicon/xsc/common/xsc_cmd.h, missing packed
attributes or explicit manual padding creates implicit holes in hardware
command and configuration structures. For instance, in struct xsc_hca_cap,
__be64 flags requires 8-byte alignment but follows u8 rsvd16[2], resulting in
6 bytes of implicit padding. In struct xsc_create_qp_request at line 313,
__be64 pas[] forces 4 bytes of padding after u8 rsvd. In struct xsc_rss_attr
at line 782, __be32 hash_tmpl forces 2 bytes of padding after __be16 rqn_num.
Does this implicit compiler padding misalign the hardware command layouts?
This padding shifts all subsequent fields transmitted to and from the
hardware, potentially corrupting the mailbox commands and breaking the ABI
contract with the firmware.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=1

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

* Re: [PATCH 12/15] yunsilicon: add RDMA QP verbs
  2026-10-12 11:44 ` [PATCH 12/15] yunsilicon: add RDMA QP verbs Xin Tian
@ 2026-10-13 11:46   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 14 potential issue(s) to consider:
- [Critical] rdma: integer overflow on queue sizes leads to OOB DMA
- [High] rdma: uninitialized stack memory leaked to userspace in create_user_qp
- [High] rdma: sleeping allocation with spinlocks held in xsc_ib_err_state_qp
- [High] rdma: UAF and HW resource leak on allocation failure in destroy_qp_common
- [High] rdma: UAF and HW resource leak on qp_node allocation failure in create_qp_common
- [High] rdma: race condition adding QP node to SRQ list in create_qp_common
- [High] rdma: double byteswap and endianness corruption of qp_access_flags
- [High] infiniband/xsc: hardware DMA into stale memory due to uninitialized SGEs in xsc_ib_post_recv
- [High] xsc: ABI breakage and OOB read in create_qp due to size_t and blind copy
- [High] yunsilicon: dead code and missing error return in xsc_check_max_qp_depth
- [Medium] rdma: memory leak of mailbox in on error paths in create_qp_common
- [Medium] rdma: uninitialized stack memory copied to hardware context in xsc_set_path
- [Medium] rdma: undefined behavior due to negative shift in create_kernel_qp
- [Medium] rdma: passing struct size to sqd_event parameter in destroy_qp_common
--

commit 624855b048b853b9911aa55007116f0a1e95d930
Author: Xin Tian <tianx@yunsilicon.com>
Subject: yunsilicon: add RDMA QP verbs

This commit adds the queue pair control path: create, destroy, modify and
query, for both kernel and user queue pairs, including the address path setup
for RoCE.

diff --git a/drivers/infiniband/hw/xsc/qp.c b/drivers/infiniband/hw/xsc/qp.c
index 28c8a1246dfae..6a83adc9c833f 100644
--- a/drivers/infiniband/hw/xsc/qp.c
+++ b/drivers/infiniband/hw/xsc/qp.c
@@ -101,11 +101,11 @@ static int calc_sq_size(struct xsc_ib_dev *dev, struct ib_qp_init_attr *attr,
>  	qp->sq.wqe_cnt = roundup_pow_of_two(attr->cap.max_send_wr);
>  
>  	check_res = xsc_check_max_qp_depth(dev->xdev, &qp->sq.wqe_cnt, dev->xdev->caps.max_wqes);
>  	if (check_res) {
>  		ibdev_err(&dev->ib_dev, "max_send_wr:%d exceed max sq depth\n",
>  			  attr->cap.max_send_wr);
>  		return -EINVAL;
>  	}

[Severity: High]
Does this error path ever execute?

The helper xsc_check_max_qp_depth() unconditionally returns false while
silently clamping the queue depth instead of indicating an error. This hides
invalid user requests. 

Does clamping the queue depth break the power-of-two property if max_wqes is
not a power of two? This could break the wrapping logic in the post_send and
post_recv paths, potentially leading to memory corruption.

[ ... ]

>  	err = ib_copy_from_udata(&ucmd, udata, sizeof(ucmd));
>  	if (err) {
>  		ibdev_err(&dev->ib_dev, "failed to copy from udata, err=%d\n", err);
>  		return err;
>  	}

[Severity: High]
Is there a risk of an out-of-bounds memory read here?

This code reads sizeof(ucmd) bytes from userspace without verifying if
udata->inlen is large enough. If a 32-bit application or an older tool
passes a smaller struct, it might trigger an -EFAULT by crossing an unmapped
page boundary, or read uninitialized memory into fields like dmabuf_sz.

Also, does using size_t for dmabuf_sz in the xsc_ib_create_qp UAPI structure
break layout compatibility between 32-bit userspace and 64-bit kernels?

[ ... ]

>  	qp->sq.ds_cnt = ucmd.sq_wqe_count;
>  	qp->sq.wqe_cnt = ucmd.sq_wqe_count;
>  	qp->sq.wqe_shift = XSC_BASE_WQE_SHIFT;
>  	qp->rq.ds_cnt = ucmd.rq_wqe_count;
>  	qp->rq.wqe_cnt = ucmd.rq_wqe_count;
>  	qp->rq.wqe_shift = XSC_BASE_WQE_SHIFT;
>  
>  	qp->buf_size = (qp->sq.wqe_cnt << qp->sq.wqe_shift) + (qp->rq.wqe_cnt << qp->rq.wqe_shift);

[Severity: Critical]
Can this lead to an integer overflow and out-of-bounds DMA?

Unvalidated queue counts from userspace can overflow a 32-bit signed integer
when calculating qp->buf_size, resulting in a small allocation. However, the
hardware will receive the huge queue counts and expect a large ring buffer.
This could potentially allow malicious userspace to trigger out-of-bounds DMA
across host physical memory.

[ ... ]

>  	*inlen = sizeof(**in) + sizeof(*((*in)->req.pas)) * hw_npages;
>  	*in = xsc_vzalloc(*inlen);
>  	if (!*in) {
>  		err = -ENOMEM;
>  		goto err_umem;
>  	}
>  	xsc_ib_populate_pas(dev, qp->umem, page_shift, (*in)->req.pas, hw_npages, true);
>  	(*in)->req.pa_num = cpu_to_be16(hw_npages);
>  
>  	err = ib_copy_to_udata(udata, resp, sizeof(*resp));
>  	if (err) {

[Severity: High]
Does this leak uninitialized stack memory to userspace?

The resp structure is declared on the stack in create_qp_common() without
initialization, passed down to this function, and immediately copied to
userspace before any of its fields are populated.

[ ... ]

>  	sq_size = calc_sq_size(dev, init_attr, qp);
>  	if (sq_size < 0) {
>  		err = -EINVAL;
>  		ibdev_err(&dev->ib_dev, "err %d\n", err);
>  		return err;
>  	}
>  
>  	qp->rq.ds_cnt = qp->rq.wqe_cnt << (qp->rq.wqe_shift - XSC_BASE_WQE_SHIFT);

[Severity: Medium]
Could this evaluate to a negative shift?

If a QP is created without a receive queue, set_rq_size() sets wqe_shift to
0. Since XSC_BASE_WQE_SHIFT is non-zero, this evaluates to a bitwise left
shift by a negative amount, which is undefined behavior.

[ ... ]

>  err_wrid:
>  	kfree(qp->sq.prev_ce_wqe);
>  	kfree(qp->sq.need_flush);
>  	kfree(qp->sq.wr_opcode);
>  	kfree(qp->sq.wqe_head);
>  	kfree(qp->sq.w_list);
>  	kfree(qp->sq.wrid);
>  	kfree(qp->sq.wr_data);
>  	kfree(qp->rq.wrid);
>  
>  err_buf:
>  	xsc_buf_free(dev->xdev, &qp->buf);
>  	return err;

[Severity: Medium]
Does this error path leak the mailbox *in structure?

The memory is allocated with xsc_vzalloc() earlier in the function but does
not appear to be freed before returning here.

[ ... ]

>  	if (pd->uobject) {
>  		err = create_user_qp(dev, pd, qp, udata, &in, &resp, &inlen);
>  		if (err)
>  			ibdev_err(&dev->ib_dev, "failed to create user qp, err = %d\n", err);
>  	} else {
>  		err = create_kernel_qp(dev, init_attr, qp, &in, &inlen);
>  		if (err)
>  			ibdev_err(&dev->ib_dev, "failed to create kernel qp, err = %d\n", err);
>  		else
>  			qp->pa_lkey = to_xpd(pd)->pa_lkey;
>  	}
>  
>  	if (err)
>  		return err;

[Severity: Medium]
If create_user_qp() or create_kernel_qp() fails, does returning err here
directly bypass the err_create label where the mailbox structure is supposed
to be freed?

[ ... ]

>  	if (!pd->uobject && init_attr->srq) {
>  		qp_node = kvzalloc_obj(*qp_node, GFP_KERNEL);
>  		if (!qp_node)
>  			goto err_create;

[Severity: High]
If kvzalloc_obj() fails here, err is 0, so the function will jump to
err_create but return success to the caller.

Does this result in the hardware QP remaining active while the software
buffers are freed? The QP might be left dangling in the CQ lists, which can
lead to a use-after-free and a resource leak.

>  		qp_node->ibqp = &qp->ibqp;
>  		qp_node->qp_id = qp->xqp.qpn;
>  		ibdev_dbg(&dev->ib_dev, "qpn:%u add into srq_qp_list\n", qp_node->qp_id);
>  		list_add_tail(&qp_node->entry, &to_xsrq(init_attr->srq)->xsrq.srq_qp_list);

[Severity: High]
Is there a race condition here?

The QP node is being added to the SRQ tracking list without acquiring the
xsrq->lock. Concurrent QP creations or destructions on the same SRQ might
corrupt the linked list.

[ ... ]

>  static void destroy_qp_common(struct xsc_ib_dev *dev, struct xsc_ib_qp *qp)
>  {
>  	struct xsc_ib_cq *send_cq = NULL;
>  	struct xsc_ib_cq *recv_cq = NULL;
>  	struct xsc_modify_qp_mbox_in *in;
>  	int err;
>  
>  	in = kzalloc_obj(*in, GFP_KERNEL);
>  	if (!in)
>  		return;

[Severity: High]
Does an early return here abort the cleanup and cause a use-after-free?

If allocation fails, the QP destruction process ends prematurely, meaning the
hardware QP is not destroyed and it remains on the CQ lists. Since the caller
returns 0 unconditionally, the RDMA subsystem will free the struct ib_qp,
leaving dangling pointers behind.

>  
>  	if (qp->xqp.qp_type_internal == XSC_QUEUE_TYPE_RAW ||
>  	    qp->xqp.qp_type_internal == XSC_QUEUE_TYPE_RAW_TSO ||
>  	    qp->xqp.qp_type_internal == XSC_QUEUE_TYPE_RAW_TX ||
>  	    qp->state != IB_QPS_RESET)
>  		if (xsc_core_qp_modify(dev->xdev, to_xsc_state(qp->state),
>  				       XSC_QP_STATE_RST, in, sizeof(*in), &qp->xqp))

[Severity: Medium]
Is sizeof(*in) the correct argument for the sqd_event parameter in
xsc_core_qp_modify()?

Passing a structure size to a parameter intended as an event flag or boolean
might be a type mismatch that corrupts hardware behavior.

[ ... ]

>  static int __xsc_ib_modify_qp(struct ib_qp *ibqp,
>  			      const struct ib_qp_attr *attr, int attr_mask,
>  			      enum ib_qp_state cur_state, enum ib_qp_state new_state,
>  			      const struct xsc_ib_modify_qp *ucmd,
>  			      struct ib_udata *udata)
>  {
>  	struct xsc_ib_dev *dev = to_xdev(ibqp->device);
>  	struct xsc_ib_qp *qp = to_xqp(ibqp);
>  	struct xsc_ib_cq *send_cq, *recv_cq;
>  	struct xsc_qp_context *context;
>  	struct xsc_modify_qp_mbox_in *in;
>  	struct xsc_qp_path path;

[ ... ]

>  	if (attr_mask & IB_QP_AV) {
>  		err = xsc_set_path(dev, &attr->ah_attr, &path,
>  				   attr_mask & IB_QP_PORT ? attr->port_num : qp->port,
>  				   attr_mask, 0, attr, qp);
>  		if (err)
>  			goto out;
>  
>  		context->src_udp_port = path.sport;
>  		context->dscp = path.ecn_dscp;
>  		context->hop_limit = path.hop_limit;
>  		context->ip_type = (path.af_type == AF_INET ? 0 : 1);
>  		context->ip_type = cpu_to_be16(context->ip_type);
>  		memcpy(context->dip, path.dip, sizeof(context->dip));
>  		memcpy(context->sip, path.sip, sizeof(context->sip));

[Severity: Medium]
Could this copy uninitialized stack memory into the hardware context?

The path structure is declared on the stack and passed to xsc_set_path().
For IPv4, only 4 bytes of sip and dip are populated. Copying the full
IPv6-sized array from path to context might leak uninitialized stack data to
the device firmware.

[ ... ]

>  	if (udata && udata->inlen && attr_mask & IB_QP_STATE && new_state == IB_QPS_RTR) {
>  		if (!ucmd->profile &&
>  		    xdev->rdma_proto_mode == RDMA_PROTO_VEROCE) {
>  			context->swith_qp_type = 1;
>  			context->qp_access_flags |= QP_ACCESS_MODIFY_QP_TYPE;
>  			context->qp_access_flags = cpu_to_be32(context->qp_access_flags);

[Severity: High]
Does this corrupt the endianness of qp_access_flags?

It looks like context->qp_access_flags was already converted to big-endian
earlier via cpu_to_be32() in xsc_set_qp_access_flag(). Applying a bitwise OR
directly to the big-endian variable and then byteswapping it a second time
will corrupt the flag bits on little-endian architectures.

[ ... ]

>  	if (!err && (attr_mask & IB_QP_STATE) && !udata) {
>  		get_cqs(qp->xqp.qp_type, qp->send_cq, qp->recv_cq, &send_cq, &recv_cq);
>  		xsc_ib_lock_cqs(send_cq, recv_cq);
>  		err = xsc_ib_err_state_qp(dev, qp, to_xsc_state(cur_state),
>  					  to_xsc_state(new_state));
>  		xsc_ib_unlock_cqs(send_cq, recv_cq);

[Severity: High]
Is there a sleeping allocation happening while holding spinlocks?

xsc_ib_lock_cqs() acquires spinlocks and disables interrupts. Inside
xsc_ib_err_state_qp(), memory is allocated using kzalloc_obj(..., GFP_KERNEL),
which can sleep and trigger a panic in an atomic context.

[ ... ]

>  		for (i = 0; i < wr->num_sge; ++i) {
>  			if (unlikely(!wr->sg_list[i].length))
>  				continue;
>  			data_seg = get_seg_wqe(recv_head, i);
>  			data_seg->in_line = 0;
>  			WR_LE_64(data_seg->va, wr->sg_list[i].addr);
>  			WR_LE_32(data_seg->mkey, wr->sg_list[i].lkey);
>  			if (is_qp1(qp->xqp.qp_type))
>  				WR_LE_32(data_seg->seg_len, xdev->caps.rx_pkt_len_max);
>  			else
>  				WR_LE_32(data_seg->seg_len, wr->sg_list[i].length);
>  		}

[Severity: High]
Can the hardware scatter incoming network data into stale memory addresses?

When this loop skips 0-length SGEs or when wr->num_sge is less than the
maximum permitted, it leaves trailing data segments in the Receive WQE
uninitialized. Since the receive queue is a ring buffer, these slots might
contain stale SGE data from previous work requests.

Without a control segment specifying the valid SGE count, the hardware could
process these stale entries and write into memory that has already been freed
or reallocated.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=12

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

* Re: [PATCH 11/15] yunsilicon: add RDMA CQ/SRQ/AH
  2026-10-12 11:44 ` [PATCH 11/15] yunsilicon: add RDMA CQ/SRQ/AH Xin Tian
@ 2026-10-13 11:46   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [Critical] core: hardware DMA buffer overflow in QP1 (MAD) receive path
- [High] locking: AB-BA deadlock between cq->lock and srq->lock
- [High] mm: sleeping in atomic context due to GFP_KERNEL allocations under spinlocks
- [High] mm: missing kcalloc NULL check in xsc_cache_srq_wr causes immediate panic
- [High] core: dangling tail pointer leads to Use-After-Free list corruption in SRQ cache
- [High] locking: unprotected list modification on cross-CQ err_state_qp_list
- [High] core: SRQ destruction UAF race with global ID reclamation
- [High] core: skipping zero-length SGEs leaves uninitialized hardware WQE gaps
- [High] core: uninitialized cqe_sz variable breaks error CQ cleaning for userspace CQs
- [High] rdma/xsc: memory leak and subsequent NULL dereference in xsc_post_cache_wrs error path
- [Medium] net: out-of-bounds read when parsing MAD headers from truncated packets
- [Medium] core: max_cqes truncation introduces non-power-of-two mask indexing vulnerability
- [Low] core: xsc_ib_query_ah drops cached RoCE destination MAC address
--

commit 362ed58c2fd14cd6742383cf2e788372cccd3288
Author: Xin Tian <tianx@yunsilicon.com>

yunsilicon: add RDMA CQ/SRQ/AH

Add the completion queues, shared receive queues and address handles: the
create/destroy/query/modify verbs and the CQ poll and completion
processing.

The QP receive helper and error-state tracking are included here
because the CQ and SRQ paths reference them.

> diff --git a/drivers/infiniband/hw/xsc/ah.c b/drivers/infiniband/hw/xsc/ah.c
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/ah.c
[ ... ]
> @@ -112,23 +112,33 @@ int xsc_ib_query_ah(struct ib_ah *ibah, struct rdma_ah_attr *ah_attr)
>  {
>  	struct xsc_ib_ah *ah = to_xah(ibah);
>  	u32 tmp;
>  
>  	memset(ah_attr, 0, sizeof(*ah_attr));
>  	ah_attr->type = ibah->type;
[ ... ]
>  	rdma_ah_set_static_rate(ah_attr, ah->av.stat_rate_sl >> 4);
>  	if (ah_attr->type == RDMA_AH_ATTR_TYPE_ROCE) {
>  		rdma_ah_set_sl(ah_attr, (ah->av.stat_rate_sl >> 1) & 0x7);
>  	} else {

[Severity: Low]
Does this code intentionally omit restoring the dmac field for RoCE
connections? During xsc_ib_create_ah(), the destination MAC address is
saved into ah->av.rmac, but the query path appears to leave it zeroed out
here, which might cause the driver to report a zeroed MAC address.

> diff --git a/drivers/infiniband/hw/xsc/cq.c b/drivers/infiniband/hw/xsc/cq.c
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/cq.c
[ ... ]
> @@ -201,15 +201,17 @@ static void xsc_handle_rdma_mad_resp_recv(struct xsc_ib_cq *cq,
>  	if (eth_type == ETH_P_IP) {
>  		pading_sz = sizeof(*grh) - sizeof(*ip4h);
>  		memmove((u8 *)(grh + 1) - sizeof(*ip4h), ip4h, sizeof(*ip4h));
>  		memset(grh, 0, pading_sz);
>  	} else {
>  		memmove(grh, ip6h, sizeof(*ip6h));
>  	}
>  	memmove(grh + 1, mad, sizeof(*mad));

[Severity: Medium]
Is it possible for memmove to read out of bounds here if an attacker or
misconfigured node sends a truncated packet? There does not seem to be a
check against the overall packet length (wc->byte_len) before copying
sizeof(*mad) bytes.

[ ... ]
> @@ -375,14 +375,16 @@ int xsc_ib_create_cq(struct ib_cq *ibcq,
>  	int irqn;
>  	int err;
>  	unsigned int eqn;
>  
>  	entries = roundup_pow_of_two(entries);
>  
>  	ibdev_dbg(&dev->ib_dev, "entries:%d, vector:%d, max_cqes:%d\n", entries, vector,
>  		  dev->xdev->caps.max_cqes);
>  
>  	if (entries > dev->xdev->caps.max_cqes)
>  		entries = dev->xdev->caps.max_cqes;

[Severity: Medium]
Could capping entries at max_cqes break the power-of-two invariant
established just above it? If the hardware reports a max_cqes value that
is not a power of two, the polling paths that use bitwise AND masking to
calculate ring buffer indices might experience discontinuous indices and read
out of bounds.

>  	cq = to_xcq(ibcq);
>  	cq->ibcq.cqe = entries;
[ ... ]
>  	cq->create_flags = attr->flags;
>  	cq->invalid = 0;
>  
>  	if (udata) {
>  		err = create_cq_user(dev, udata, NULL, cq, entries,
>  				     &cqb, &cqe_size, &index, &inlen);
>  		if (err)
>  			goto err_create;
>  	} else {
>  		cqe_size = is_support_cqe64(dev->xdev) ?
>  			sizeof(struct xsc_cqe64) : sizeof(struct xsc_cqe);
>  		err = create_cq_kernel(dev, cq, entries, cqe_size, &cqb, &index, &inlen);
>  		if (err)
>  			goto err_create;
>  	}
>  
>  	cq->cqe_size = cqe_size;

[Severity: High]
Does the userspace CQ creation path leave cq->xcq.cqe_sz uninitialized?
create_cq_kernel() sets it internally, but create_cq_user() does not appear
to. If it remains 0, the flush-cleanup routine __xsc_ib_cq_clean() might
fail to step through the buffer array correctly when overwriting matching
QP elements.

> diff --git a/drivers/infiniband/hw/xsc/qp.c b/drivers/infiniband/hw/xsc/qp.c
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/qp.c
[ ... ]
> @@ -100,18 +100,20 @@ int xsc_ib_err_state_qp(struct xsc_ib_dev *dev, struct xsc_ib_qp *qp,
>  	if (cur_state != XSC_QP_STATE_ERR && state == XSC_QP_STATE_ERR &&
>  	    !qp->has_trig_cq_comp_handle) {
>  		if (!qp->ibqp.srq && qp->recv_cq) {
>  			err_rq_node = kzalloc_obj(*err_rq_node, GFP_KERNEL);

[Severity: High]
Can this allocation trigger a scheduling while atomic panic?
xsc_ib_err_state_qp() is called from xsc_poll_one() during CQ polling,
which holds cq->lock via spin_lock_irqsave. Calling kzalloc_obj with
GFP_KERNEL under a spinlock might sleep.

>  			if (!err_rq_node)
>  				return -ENOMEM;
>  			err_rq_node->qp_id = qp->xqp.qpn;
>  			err_rq_node->is_sq = false;
>  			recv_xcq = to_xcq(qp->recv_cq);
>  			list_add_tail(&err_rq_node->entry, &recv_xcq->err_state_qp_list);

[Severity: High]
Does this introduce unprotected list modifications? Since this is called
synchronously while holding only the lock for the CQ currently being
polled (e.g., the send CQ), modifying the receive CQ's err_state_qp_list
appears to happen locklessly. This might corrupt the double-linked list
if a concurrent polling thread traverses it.

[ ... ]
> @@ -131,10 +131,11 @@ static int xsc_wq_overflow(struct xsc_ib_wq *wq, int nreq, struct xsc_ib_cq *cq)
>  {
>  	unsigned int cur;
>  
>  	cur = wq->head - wq->tail;
>  	if (likely(cur + nreq < wq->max_post))
>  		return 0;
>  
>  	spin_lock(&cq->lock);

[Severity: High]
Could this lead to a circular locking deadlock between cq->lock and
srq->lock?

Path A acquires cq->lock first, then srq->lock:
xsc_ib_poll_cq() -> acquires cq->lock
   xsc_poll_one()
      xsc_free_one_cache_srq_wr() -> acquires srq->lock

Path B acquires srq->lock first, then cq->lock:
xsc_ib_post_srq_recv() -> acquires srq->lock
   xsc_post_cache_wrs()
      xsc_ib_post_recv()
         xsc_wq_overflow() -> acquires cq->lock

This might trigger a hard kernel deadlock when receive operations coincide
with polling.

[ ... ]
> @@ -186,13 +186,17 @@ int xsc_ib_post_recv(struct ib_qp *ibqp, const struct ib_recv_wr *wr,
>  		recv_head = get_recv_wqe(qp, idx);
>  
>  		for (i = 0; i < wr->num_sge; ++i) {
>  			if (unlikely(!wr->sg_list[i].length))
>  				continue;

[Severity: High]
Does skipping zero-length SGEs here leave uninitialized gaps in the
hardware WQE? By using continue, the data_seg at index i is skipped. If
the hardware descriptor ring contains stale data from previous work
requests, the NIC might execute the stale SGE and cause spurious DMA
mapping faults.

>  			data_seg = get_seg_wqe(recv_head, i);
>  			data_seg->in_line = 0;
>  			WR_LE_64(data_seg->va, wr->sg_list[i].addr);
>  			WR_LE_32(data_seg->mkey, wr->sg_list[i].lkey);
>  			if (is_qp1(qp->xqp.qp_type))
>  				WR_LE_32(data_seg->seg_len, xdev->caps.rx_pkt_len_max);

[Severity: Critical]
Could instructing the hardware to use rx_pkt_len_max for QP1 packets
cause a DMA buffer overflow? MAD receive buffers allocated by the IB core
are typically only large enough for GRH + MAD (~300 bytes). If an incoming
padded or malformed packet arrives, writing up to the port MTU size might
corrupt remote kernel memory.

>  			else
>  				WR_LE_32(data_seg->seg_len, wr->sg_list[i].length);
>  		}

> diff --git a/drivers/infiniband/hw/xsc/srq.c b/drivers/infiniband/hw/xsc/srq.c
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/srq.c
[ ... ]
> @@ -51,11 +51,13 @@ void xsc_free_one_cache_srq_wr(struct xsc_ib_srq *srq, struct xsc_ib_qp *qp, u64
>  		if (tmp_wr->wr_id == wr_id) {
>  			if (prev_wr)
>  				prev_wr->next = tmp_wr->next;
>  			else
>  				qp->srq_head_wr = tmp_wr->next;
>  
>  			kfree(tmp_wr->sg_list);
>  			kfree(tmp_wr);
>  			break;
>  		}

[Severity: High]
If the item being freed is the tail of the list (tmp_wr == qp->srq_tail_wr),
does qp->srq_tail_wr need to be updated? If it is left dangling, a
subsequent call to xsc_post_cache_wrs() with init=false might execute
qp->srq_tail_wr->next = wr_head, leading to a use-after-free.

[ ... ]
> @@ -279,15 +279,16 @@ int xsc_ib_destroy_srq(struct ib_srq *srq, struct ib_udata *udata)
>  {
>  	struct xsc_ib_srq *xsrq = to_xsrq(srq);
>  	struct xsc_ib_dev *dev = to_xdev(srq->device);
>  
>  	xsc_core_destroy_srq(dev->xdev, &xsrq->xsrq);
>  	if (xsrq->create_type == XSC_SRQ_USER) {
>  		destroy_srq_user(xsrq, NULL);
>  	} else {
>  		xsc_destroy_cache_wr(xsrq);
>  		destroy_srq_kernel(dev, xsrq);
>  	}

[Severity: High]
Is there a race condition between releasing the SRQ ID globally and
clearing its local data? xsc_core_destroy_srq() frees the ID for immediate
reuse. If a concurrent thread creates an SRQ and reuses srqn before
xsc_destroy_cache_wr() executes memset(&cache_wr[srqn], 0, ...), it might
wipe out the newly allocated cache list of the new SRQ.

[ ... ]
> @@ -290,14 +290,17 @@ static int xsc_cache_srq_wr(struct xsc_ib_srq *srq, const struct ib_recv_wr *wr,
>  {
>  	int i, n;
>  	int srqn = srq->xsrq.srqn;
>  	struct xsc_srq_cache_wr *cache_wr = srq->xsrq.cache_wr;
>  	struct ib_recv_wr *head_wr = NULL, *new_wr = NULL, *prev_wr = NULL;
>  
>  	for (n = 0; wr; wr = wr->next, n++) {
>  		new_wr = kcalloc(1, sizeof(struct ib_recv_wr), GFP_KERNEL);

[Severity: High]
Can these allocations trigger a scheduling while atomic panic?
xsc_ib_post_srq_recv() acquires srq->lock with spin_lock_irqsave before
calling xsc_cache_srq_wr(), meaning this kcalloc(..., GFP_KERNEL) happens
with interrupts disabled.

>  		if (prev_wr)
>  			prev_wr->next = new_wr;
>  		else
>  			head_wr = new_wr;
>  		new_wr->wr_id = wr->wr_id;
>  		new_wr->sg_list = kcalloc(wr->num_sge, sizeof(struct ib_sge), GFP_KERNEL);

[Severity: High]
Are NULL checks missing for the kcalloc() returns? If either allocation
fails, dereferencing new_wr->wr_id or passing new_wr->sg_list to memcpy()
will cause a deterministic NULL pointer dereference.

>  		for (i = 0; i < wr->num_sge; i++)
>  			memcpy(&new_wr->sg_list[i], &wr->sg_list[i], sizeof(struct ib_sge));

[ ... ]
> @@ -355,14 +355,15 @@ void xsc_post_cache_wrs(struct xsc_ib_srq *srq, struct xsc_ib_qp *qp, int wr_num
>  
>  	cache_wr[srqn].head_wr = qp->srq_tail_wr->next;
>  	qp->srq_tail_wr->next = NULL;
>  	tmp_wr = wr_head;
>  	err = xsc_ib_post_recv(&qp->ibqp, tmp_wr, &bad_wr);
>  	if (err) {
>  		qp->srq_tail_wr->next = cache_wr[srqn].head_wr;
>  		qp->srq_tail_wr = NULL;
>  		for (; wr_head && (bad_wr->wr_id != wr_head->wr_id); wr_head = wr_head->next)
>  			qp->srq_tail_wr = wr_head;
>  
>  		if (!qp->srq_tail_wr) {
>  			cache_wr[srqn].head_wr = wr_head;
>  			qp->srq_head_wr = NULL;
>  		} else {

[Severity: High]
Does this error path lead to memory leaks and a subsequent NULL pointer
dereference? If xsc_ib_post_recv() fails on the very first element (e.g.,
due to WQ overflow), qp->srq_tail_wr is left as NULL. The code then
executes qp->srq_head_wr = NULL, orphaning all previously successfully
posted Work Requests and leaking their memory.

Additionally, because qp->srq_tail_wr is left as NULL, the next
invocation of xsc_post_cache_wrs() with init = false will execute:

qp->srq_tail_wr->next = wr_head;

This will cause a deterministic NULL pointer dereference.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=11

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

* Re: [PATCH 09/15] yunsilicon: add RDMA device support
  2026-10-12 11:44 ` [PATCH 09/15] yunsilicon: add RDMA device support Xin Tian
  2026-10-13  6:17   ` Junxian Huang
@ 2026-10-13 11:46   ` sashiko-bot
  1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [Critical] rdma: arbitrary physical memory mapping via unvalidated vma size
- [High] rdma: ABI breakage due to size_t in UAPI structures
- [High] rdma: out-of-bounds access due to duplicated core private structs
- [High] rdma: hardcoded physical address offset breaks PCI mapping
- [High] rdma: API violation using virt_to_phys on DMA coherent memory
- [High] rdma: double free due to auxiliary driver unregister in reboot notifier
- [High] rdma: use-after-free of netdev pointer due to missing reference and unregister handling
- [High] rdma: race condition exposing uninitialized device state to userspace
- [Medium] rdma: truncated UAPI data due to fixed struct offset accumulator
- [Medium] rdma: integer underflow causes small MTUs to report maximum MTU
- [Medium] rdma: uninitialized ib_event structure dispatched on unhandled events
- [Medium] rdma: missing udata->inlen check in xsc_ib_alloc_ucontext()
- [Low] rdma: endian-dependent firmware version formatting
--

commit 206aea3bd72ce8a9a32742a3fba3649e658d1875
Author: Xin Tian <tianx@yunsilicon.com>
Subject: yunsilicon: add RDMA device support

This commit adds basic RDMA driver support for Yunsilicon XSC devices. It
includes device registration, port queries, memory mapping, and the uverbs
ABI header.

> diff --git a/drivers/infiniband/hw/xsc/main.c b/drivers/infiniband/hw/xsc/main.c
> index 0000000000000..9d53a89beee02
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/main.c
> @@ -40,15 +40,29 @@ static int xsc_ib_query_device(struct ib_device *ibdev,
>  {
>  	size_t udata_outlen = (udata) ? udata->outlen : 0;
[ ... ]
>  	fw_ver.data = 0;
>  	fw_ver.s.ver_major = dev->xdev->fw_version_major;
>  	fw_ver.s.ver_minor = dev->xdev->fw_version_minor;
>  	fw_ver.s.ver_patch = dev->xdev->fw_version_patch;
>  	fw_ver.s.ver_tweak = dev->xdev->fw_version_tweak;
>  	props->fw_ver = fw_ver.data;

[Severity: Low]
Does this union-based assignment expose host endianness variations to the
UAPI? Because struct bitfield and byte layout depend on host endianness, the
resulting u64 will have a different value on little-endian versus big-endian
systems. Could explicit bit shifts be used instead to ensure a consistent
firmware version value across architectures?

>  	/*response tso_caps extend param*/
>  	if (offsetofend(typeof(resp), tso_caps) <= udata_outlen) {
>  		max_tso = dev->xdev->caps.log_max_tso ? (1 << dev->xdev->caps.log_max_tso) : 0;
>  		if (max_tso) {
>  			resp.tso_caps.max_tso = max_tso;
>  			resp.tso_caps.supported_qpts |= 1 << IB_QPT_RAW_PACKET;
>  			resp.response_length += sizeof(resp.tso_caps);
>  		}
>  	}

[Severity: Medium]
Will this dynamic accumulation of lengths truncate UAPI data? The destination
buffer resp is a monolithic C structure with fixed offsets. If an
intermediate feature is disabled, its size is not added to response_length,
making the length fall behind the actual struct offset of subsequent fields.
When ib_copy_to_udata() is called with this length, will trailing features
like packet_pacing_caps be silently truncated before reaching userspace?

> @@ -292,10 +306,12 @@ int xsc_ib_alloc_ucontext(struct ib_ucontext *uctx,
>  			  struct ib_udata *udata)
>  {
>  	struct ib_device *ibdev = uctx->device;
>  	struct xsc_ib_dev *dev = to_xdev(ibdev);
>  	struct xsc_ib_alloc_ucontext_req req;
>  	struct xsc_ib_alloc_ucontext_resp resp;
>  	int err;
>  
>  	if (!dev->ib_active)
>  		return -EAGAIN;
>  
>  	err = ib_copy_from_udata(&req, udata, sizeof(req));

[Severity: Medium]
Can this copy read out-of-bounds memory or cause a fault? The copy happens
unconditionally using sizeof(req) without checking if udata->inlen is large
enough. If userspace provides a smaller buffer or passes 0 bytes, this could
read garbage data or return -EFAULT. Should it use min(udata->inlen,
sizeof(req)) instead?

> @@ -318,29 +334,16 @@ static int xsc_ib_mmap(struct ib_ucontext *ibcontext, struct vm_area_struct *vma
>  {
>  	struct xsc_ib_dev *dev = to_xdev(ibcontext->device);
>  	struct xsc_core_device *xdev = dev->xdev;
>  	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
>  	resource_size_t reg_base;
>  	resource_size_t reg_size = vma->vm_end - vma->vm_start;

[Severity: Critical]
Does this allow mapping arbitrary physical memory? The reg_size is derived
directly from the userspace mmap syscall arguments without any upper bound
validation against the legitimate boundaries of the PCI resource. When this
unvalidated reg_size is later passed to remap_pfn_range(), could a local user
map adjacent physical memory belonging to the kernel or other hardware?

>  	if (offset == (tx_db & PAGE_MASK))
>  		reg_base = pci_resource_start(xdev->pdev, xdev->bar_num) + (tx_db & PAGE_MASK);
[ ... ]
>  	reg_base = (xsc_core_is_pf(xdev) && !is_pf_bar_compressed(xdev)) ?
>  		reg_base - 0xA0000000 : reg_base;

[Severity: High]
Does subtracting this hardcoded constant break correct memory mapping on
generic platforms? PCI BARs are dynamically mapped, and the CPU physical
address from pci_resource_start() is already correct. Modifying the generic
PCI physical address with a hardcoded, architecture-specific offset like
0xA0000000 could cause remap_pfn_range() to map unrelated RAM or trap the
system instead of the intended MMIO registers.

> @@ -417,31 +419,25 @@ static void xsc_ib_event(struct xsc_core_device *dev, void *context,
>  			 enum xsc_dev_event event, unsigned long data)
>  {
>  	struct xsc_ib_dev *ibdev = (struct xsc_ib_dev *)context;
>  	struct ib_event ibev;
>  	u8 port = 0;
>  
>  	switch (event) {
[ ... ]
>  	}
>  
>  	ibev.device	      = &ibdev->ib_dev;
>  	ibev.element.port_num = port;
>  
>  	if (ibdev->ib_active)
>  		ib_dispatch_event(&ibev);

[Severity: Medium]
Could this dispatch an uninitialized ib_event structure? The stack-allocated
ibev is not zero-initialized, and the switch statement lacks a default case.
If called with an unhandled event, ibev.event remains uninitialized, leading
to a garbage event type containing stack memory being dispatched to the core
and userspace.

> @@ -592,44 +601,31 @@ static int init_one(struct xsc_core_device *xdev,
>  		    struct xsc_ib_dev **ibdev)
>  {
>  	struct xsc_ib_dev *dev;
>  	int err;
>  
>  	dev = (struct xsc_ib_dev *)ib_alloc_device(xsc_ib_dev, ib_dev);
>  	if (!dev)
>  		return -ENOMEM;
>  
>  	dev->xdev = xdev;
>  	dev->netdev = xdev->netdev;

[Severity: High]
Does this store a pointer to the netdevice without taking a reference? The
assignment happens without dev_hold().

Looking at xsc_netdev_event() below, it explicitly ignores the
NETDEV_UNREGISTER event:

	switch (event) {
	case NETDEV_CHANGE:
	case NETDEV_UP:
	case NETDEV_DOWN:
		xsc_get_port_state(ibdev->netdev, &ev);
		xsc_ib_event(ibdev->xdev, ibdev, ev, (unsigned long)&port);
		break;
	default:
		break;
	}

If the underlying ethernet device is unbound, the netdevice is freed, leaving
dev->netdev as a dangling pointer. Subsequent calls to ib callbacks like
xsc_ib_query_port() would dereference this pointer, triggering a
use-after-free.

>  	err = ib_register_device(&dev->ib_dev, dev->ib_dev.name,
>  				 dev->xdev->device);
>  	if (err)
>  		goto err_free;
>  
>  	rdma_roce_rescan_device(&dev->ib_dev);
>  	dev->ib_active = true;
>  	*ibdev = dev;
>  
>  	xsc_register_netdev_notifier(dev);

[Severity: High]
Is there a race condition exposing the device before initialization is
complete? ib_register_device() publishes the device to userspace. However,
the netdev notifier is registered after this, and looking at xsc_ib_probe(),
init_iommu_state() is called after init_one() returns. This creates a window
where userspace can open the device and attempt operations before the IOMMU
mode is resolved or link events are tracked.

> @@ -704,22 +701,21 @@ static void init_iommu_state(struct xsc_ib_dev *dev)
>  {
>  	struct iommu_domain *domain;
>  
>  	dev->iommu_state = XSC_IB_IOMMU_MAP_DISABLE;
>  	domain = iommu_get_domain_for_dev(dev->ib_dev.dma_device);
>  	if (domain) {
>  		if (domain->type & __IOMMU_DOMAIN_DMA_API)
>  			dev->iommu_state = XSC_IB_IOMMU_MAP_NORMAL;
>  	} else {
>  		dma_addr_t dma_addr;
>  		void *tmp;
>  
>  		tmp = dma_alloc_coherent(dev->ib_dev.dma_device, PAGE_SIZE,
>  					 &dma_addr, GFP_KERNEL);
>  		if (tmp) {
>  			if (virt_to_phys(tmp) != dma_addr)

[Severity: High]
Does this violate the DMA API? Calling virt_to_phys() on memory returned by
dma_alloc_coherent() is generally unsafe because the virtual address may not
be part of the direct linear mapping (e.g., vmalloc space on ARM/MIPS).
Passing such an address to virt_to_phys() yields an incorrect physical
address and can trigger crashes if CONFIG_DEBUG_VIRTUAL is enabled.

> @@ -758,21 +757,17 @@ static struct auxiliary_driver xsc_ib_driver = {
>  	.id_table = xsc_ib_id_table,
>  };
>  
>  static int xsc_ib_reboot_event_handler(struct notifier_block *nb, unsigned long action, void *data)
>  {
>  	pr_info("xsc ib driver recv %lu event\n", action);
>  
>  	xsc_remove_rdma_driver();
>  
>  	return NOTIFY_OK;
>  }
>  
>  struct notifier_block xsc_ib_nb = {
>  	.notifier_call = xsc_ib_reboot_event_handler,
>  	.next = NULL,
>  	.priority = 2,
>  };
>  
>  void xsc_remove_rdma_driver(void)
>  {
>  	auxiliary_driver_unregister(&xsc_ib_driver);
>  }
>  
>  static int __init xsc_ib_init(void)
>  {
>  	int ret;
>  
>  	ret = auxiliary_driver_register(&xsc_ib_driver);
>  	if (ret) {
>  		pr_err("failed to register auxiliary driver\n");
>  		return ret;
>  	}
>  
>  	register_reboot_notifier(&xsc_ib_nb);
>  
>  	return 0;
>  }
>  
>  static void __exit xsc_ib_cleanup(void)
>  {
>  	unregister_reboot_notifier(&xsc_ib_nb);
>  	xsc_remove_rdma_driver();
>  }

[Severity: High]
Can this cause a double-free of the driver name? The reboot notifier
unconditionally calls xsc_remove_rdma_driver(), which invokes
auxiliary_driver_unregister(). If the module is later unloaded,
xsc_ib_cleanup() calls unregister_reboot_notifier() and then
xsc_remove_rdma_driver() a second time.

> diff --git a/drivers/infiniband/hw/xsc/xsc_ib.h b/drivers/infiniband/hw/xsc/xsc_ib.h
> index 0000000000000..a2ae382e22ce8
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/xsc_ib.h
> @@ -379,15 +379,20 @@ static inline enum ib_mtu xsc_net_to_ib_mtu(unsigned int mtu)
>  {
>  	mtu = mtu - (IB_GRH_BYTES + IB_UDP_BYTES + IB_BTH_BYTES +
>  		     IB_EXT_XRC_BYTES + IB_EXT_ATOMICETH_BYTES +
>  		     IB_ICRC_BYTES);
>  	if (mtu >= 8192)
>  		return IB_MTU_8192;

[Severity: Medium]
Will this trigger an integer underflow if the netdevice MTU is smaller than
96 bytes? Subtracting the fixed headers without an upper bound or minimum
check means a small MTU wraps around to a massive positive number. The check
for mtu >= 8192 then evaluates to true, incorrectly returning IB_MTU_8192
instead of properly clamping the value.

> @@ -436,25 +436,20 @@ struct ib_mad_list_head {
>  	struct list_head list;
>  	struct ib_cqe cqe;
>  	struct ib_mad_queue *mad_queue;
>  };
>  
>  #define IB_MAD_SEND_REQ_MAX_SG 2
>  struct ib_mad_send_wr_private {
>  	struct ib_mad_list_head mad_list;
>  	struct list_head agent_list;
>  	struct ib_mad_agent_private *mad_agent_priv;

[Severity: High]
Does this safely duplicate internal RDMA MAD core structures? The driver
illegitimately duplicates private core structures like
ib_mad_send_wr_private and ib_mad_private. Since these are internal to the
RDMA core, they may change layout at any time.

When xsc_ib_send_mad_sg_virt_addr() unsafely casts core pointers using these
locally duplicated structures:

	mad_list = container_of(wr->wr_cqe, struct ib_mad_list_head, cqe);
	mad_send_wr = container_of(mad_list, struct ib_mad_send_wr_private,
				   mad_list);

will this lead to out-of-bounds reads or memory corruption if the core
updates the upstream layout?

> @@ -483,16 +483,12 @@ static inline void *xsc_ib_send_mad_sg_virt_addr(struct ib_device *ibdev,
>  						 const struct ib_send_wr *wr,
>  						 int sg)
>  {
>  	struct ib_mad_send_wr_private *mad_send_wr;
>  	struct ib_mad_list_head *mad_list;
>  	int iommu_state = xsc_ib_iommu_dma_map(ibdev);
>  
>  	/* direct dma mapping */
>  	if (!iommu_state)
>  		return phys_to_virt(dma_to_phys(ibdev->dma_device, wr->sg_list[sg].addr));

[Severity: High]
Does manually reverse-mapping a dma_addr_t to a virtual address using
phys_to_virt(dma_to_phys(...)) break coherency on SWIOTLB architectures or
cause crashes if the buffer is in HighMem?

> diff --git a/include/uapi/rdma/xsc-abi.h b/include/uapi/rdma/xsc-abi.h
> index 0000000000000..c4d0db30e43c4
> --- /dev/null
> +++ b/include/uapi/rdma/xsc-abi.h
> @@ -82,21 +82,18 @@ struct xsc_ib_alloc_ucontext_resp {
>  	__u32	reserved[32];
>  };
>  
>  struct xsc_ib_create_qp {
>  	__u64	buf_addr;
>  	__u64	db_addr;
>  	__u32	sq_wqe_count;
>  	__u32	rq_wqe_count;
>  	__u32	rq_wqe_shift;
>  	__u32	flags;
>  
>  	int	dmabuf_fd;
>  	size_t	dmabuf_sz;
>  };

[Severity: High]
Does using size_t in a UAPI struct break ABI compatibility? size_t changes
from 4 bytes on 32-bit architectures to 8 bytes on 64-bit architectures,
causing struct size and alignment mismatches. If a 32-bit userspace program
interacts with a 64-bit kernel, the kernel will read fields at incorrect
offsets. Should an explicitly sized type like __u64 be used instead?

> @@ -112,18 +112,16 @@ struct xsc_ib_create_srq_resp {
>  };
>  
>  struct xsc_ib_create_cq {
>  	__u64 buf_addr;
>  	__u64 db_addr;
>  	__u32	cqe_size;
>  
>  	int	dmabuf_fd;
>  	size_t	dmabuf_sz;
>  };

[Severity: High]
Does using size_t in this UAPI struct cause the same ABI breakage across
32-bit and 64-bit boundaries as mentioned above? Explicitly sized types are
required for stable UAPI boundaries.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=9

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

* Re: [PATCH 02/15] yunsilicon: add the register HAL
  2026-10-12 11:43 ` [PATCH 02/15] yunsilicon: add the register HAL Xin Tian
  2026-10-12 15:21   ` Andrew Lunn
@ 2026-10-13 11:46   ` sashiko-bot
  1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] net: xsc_get_db_addr returns hardcoded physical address instead of PCI BAR
- [High] net: global state corruption via shared static xsc_hw_reg structures
- [High] net: endianness breakage from C bitfields mapped to hardware descriptors
- [High] net: hardcoded 0xf clear of W1C interrupt register causes interrupt storm
- [High] net: silent fallback to 1GB page size for unhandled page shifts
- [Medium] net: uninitialized stack padding leaked to hardware doorbell MMIO registers
- [Medium] net: stale in_line bit retained in recycled DMA data segments
- [Medium] net: integer overflow in xsc_get_max_mr_size on 32-bit architectures
--

commit ab5e7ef7c47a715df12f926749f1e49fe112c55d
Author: Xin Tian <tianx@yunsilicon.com>
yunsilicon: add the register HAL

This commit adds the register access layer and per-device register map for
doorbells, interrupts, and command queues. The implementation provides
register offsets and configurations for the Andes device.

diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c b/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c
new file mode 100644
index 0000000000000..46fc045f92c02
--- /dev/null
+++ b/drivers/net/ethernet/yunsilicon/xsc/pci/hal/andes_impl.c
@@ -0,0 +1,708 @@
[ ... ]
> +static void andes_ring_rx_doorbell(struct xsc_hw_reg *regs,
> +				   void __iomem *bar, u32 rqn, u32 next_pid)
> +{
> +	union xsc_recv_doorbell {
> +		struct{
> +			u32  next_pid : 13;
> +			u32 qp_id : 15;
> +		};
> +		u32 raw;
> +	} db;
> +
> +	db.next_pid = next_pid;
> +	db.qp_id = rqn;
> +
> +	/* Make sure that descriptors are written before
> +	 * updating doorbell record and ringing the doorbell
> +	 */
> +	wmb();
> +
> +	writel(db.raw, REG_ADDR(bar, regs->rx_db));

[Severity: Medium]
Does this leak uninitialized kernel stack memory to the hardware? The union
instantiated on the stack is not fully zero-initialized before its raw 32-bit
value is written to MMIO, leaving padding bits uninitialized.

[ ... ]
> +static void andes_set_eq_ci(struct xsc_hw_reg *regs,
> +			    void __iomem *bar, u32 eqn, u32 next_cid,
> +			    u8 arm)
> +{
> +	union andes_eq_doorbell {
> +		struct{
> +			u32 eq_next_cid : 11;
> +			u32 eq_id : 11;
> +			u32 arm : 1;
> +		};
> +		u32 val;
> +	} db;
> +
> +	db.eq_next_cid = next_cid;
> +	db.eq_id = eqn;
> +	db.arm = !!arm;
> +
> +	/* make sure val write to memory done */
> +	wmb();
> +	writel(db.val, REG_ADDR(bar, regs->event_db));

[Severity: Medium]
Could this leak uninitialized stack memory to the event doorbell register,
similar to the rx doorbell above?

[ ... ]
> +static u8 andes_get_mr_page_mode(u8 page_shift)
> +{
> +enum {
> +	XSC_PAGE_SHIFT_4K	= 12,
> +	XSC_PAGE_SHIFT_64K	= 16,
> +	XSC_PAGE_SHIFT_2M	= 21,
> +	XSC_PAGE_SHIFT_1G	= 30,
> +};
> +
> +enum {
> +	XSC_PAGE_MODE_4K	= 0,
> +	XSC_PAGE_MODE_64K	= 1,
> +	XSC_PAGE_MODE_2M	= 2,
> +	XSC_PAGE_MODE_1G	= 3,
> +};
> +
> +	return (page_shift == XSC_PAGE_SHIFT_4K ? XSC_PAGE_MODE_4K :
> +		(page_shift == XSC_PAGE_SHIFT_64K ? XSC_PAGE_MODE_64K :
> +		(page_shift == XSC_PAGE_SHIFT_2M ? XSC_PAGE_MODE_2M : XSC_PAGE_MODE_1G)));

[Severity: High]
Does this silently fall back to XSC_PAGE_MODE_1G for unhandled page shifts
(such as 8K or 16K)? Could this cause out-of-bounds DMA accesses if the
hardware incorrectly maps 8K/16K buffers as 1GB pages?

[ ... ]
> +struct andes_cqe {
> +	union {
> +		u8		msg_opcode;
> +		struct {
> +			u8		error_code:7;
> +			u8		is_error:1;
> +		};
> +	};
> +	__le32		qp_id:15;
> +	u8		rsv1:1;
> +	u8		se:1;
> +	u8		has_pph:1;
> +	u8		type:1;
> +	u8		with_immdt:1;
> +	u8		csum_err:4;
> +	__le32		imm_data;
> +	__le32		msg_len;
> +	__le32		vni;
> +	__le64		ts:48;
> +	__le16		wqe_id;
> +	__le16		rsv[3];
> +	__le16		rsv2:15;
> +	u8		owner:1;
> +};
> +
> +static int andes_get_cqe_wqe_id(void *cqe)
> +{
> +	return ((struct andes_cqe *)cqe)->wqe_id;

[Severity: High]
Does mapping C bitfields directly to hardware descriptors break on big-endian
systems? The compiler layout of bitfields is implementation-defined, which
frequently results in reversed bit packing.

Also, shouldn't le16_to_cpu() be used here when returning the __le16 wqe_id
as a native integer to avoid returning out-of-bounds indices on big-endian
architectures?

[ ... ]
> +static void andes_set_data_seg(void *data_seg, u32 length, u32 key, u64 addr)
> +{
> +	struct andes_data_seg *seg = data_seg;
> +
> +	seg->length = length;
> +	seg->key = key;
> +	seg->addr = addr;

[Severity: Medium]
If a data segment descriptor is recycled from a ring buffer, does failing to
explicitly clear the in_line bitfield here cause the hardware to misinterpret
the address and key as inline data?

[ ... ]
> +struct andes_send_wqe_ctrl_seg {
> +	__le32		msg_opcode:8;
> +	__le32		with_immdt:1;
> +	__le32		csum_en:2;
> +	__le32		ds_data_num:5;
> +	__le32		wqe_id:16;
> +	__le32		msg_len;
[ ... ]
> +static void andes_set_wqe_id(void *cseg, u32 wqe_id)
> +{
> +	u16 _wqe_id = wqe_id;
> +
> +	((struct andes_send_wqe_ctrl_seg *)cseg)->wqe_id = cpu_to_le16(_wqe_id);

[Severity: High]
Is it safe to assign an explicitly byte-swapped value directly to a bitfield?
This could doubly corrupt the data on big-endian systems.

[ ... ]
> +static struct xsc_hw_reg andes_pf_regs = {
> +	.req_pid_addr = HIF_CMDQM_HOST_REQ_PID_MEM_ADDR - 0xa0000000,
> +	.req_cid_addr = HIF_CMDQM_HOST_REQ_CID_MEM_ADDR - 0xa0000000,
[ ... ]
> +static struct xsc_hw_reg andes_compressed_regs = {
> +	.tx_db = TX_DB_FUNC_MEM_ADDR,
> +	.rx_db = RX_DB_FUNC_MEM_ADDR,
[ ... ]
> +void xsc_init_hal(struct xsc_core_device *xdev, u32 device_id)
> +{
> +	switch (device_id) {
> +	case XSC_MC_VF_DEV_ID:
> +	case XSC_MF_HOST_VF_DEV_ID:
> +	case XSC_MS_VF_DEV_ID:
> +	case XSC_MV_HOST_VF_DEV_ID:
> +		/* VFs expose the same BAR-relative window as a bar-compressed PF */
> +		xdev->hal_regs = &andes_compressed_regs;
> +		break;
> +	default:
> +		if (is_pf_bar_compressed(xdev))
> +			xdev->hal_regs = &andes_compressed_regs;
> +		else
> +			xdev->hal_regs = &andes_pf_regs;

[Severity: High]
Could multiple devices probed at the same time overwrite each other's hardware
capability offsets? It appears xdev->hal_regs is assigned to a globally shared
static structure rather than allocating a separate configuration per-device.

[ ... ]
> +void xsc_set_mtt_info(struct xsc_core_device *xdev)
> +{
> +	struct xsc_hw_reg *regs = xdev->hal_regs;
> +
> +	regs->mpt_tbl_addr = xdev->caps.mpt_tbl_addr - 0xa0000000;
> +	regs->mpt_tbl_depth = xdev->caps.mpt_tbl_depth;
> +	regs->mpt_tbl_width = xdev->caps.mpt_tbl_width;
> +	regs->mtt_inst_base_addr = xdev->caps.mtt_inst_base_addr - 0xa0000000;

[Severity: High]
Does this mutate the global shared static structure assigned during
xsc_init_hal() with device-specific values?

[ ... ]
> +void xsc_get_db_addr(struct xsc_core_device *xdev,
> +		     u64 *tx_db, u64 *rx_db, u64 *cq_db, u64 *cq_reg, u64 *eq_db)
> +{
> +	struct xsc_hw_reg *regs = xdev->hal_regs;
> +
> +	if (!xsc_core_is_pf(xdev) || is_pf_bar_compressed(xdev)) {
[ ... ]
> +	} else {
> +		if (tx_db)
> +			*tx_db = regs->tx_db + 0xa0000000;
> +		if (rx_db)
> +			*rx_db = regs->rx_db + 0xa0000000;
> +		if (cq_db)
> +			*cq_db = regs->complete_db + 0xa0000000;
> +		if (cq_reg)
> +			*cq_reg = regs->complete_reg + 0xa0000000;
> +		if (eq_db)
> +			*eq_db = regs->event_db + 0xa0000000;
> +	}

[Severity: Critical]
Does adding a hardcoded offset of 0xa0000000 to the doorbell register offsets
yield a valid physical address? PCI device BAR physical addresses are typically
dynamically assigned and should be retrieved via pci_resource_start().
Returning a hardcoded physical address to upper layers could map arbitrary
physical memory.

[ ... ]
> +int xsc_handle_cmdq_interrupt(struct xsc_core_device *xdev)
> +{
> +	union interrupt_stat {
> +		struct {
> +			u32	hw_read_req_err:1;
> +			u32	hw_write_req_err:1;
> +			u32	req_pid_err:1;
> +			u32	rsp_cid_err:1;
> +		};
> +		u32	raw;
> +	} stat;
> +
> +	int err = 0;
> +	int retry = 0;
> +
> +	xsc_read32(xdev->bar, xdev->hal_regs->interrupt_stat_addr, &stat.raw,
> +		   sizeof(stat.raw));
> +	while (stat.raw != 0) {
> +		err++;
> +		if (stat.hw_read_req_err) {
[ ... ]
> +		} else {
> +			stat.raw = 0;
> +			pci_err(xdev->pdev, "ignore unknown interrupt!\n");
> +		}
> +	}
> +
> +	if (retry) {
> +		if (xdev->cmd.retry_cnt >= XSC_CMD_MAX_RETRY_CNT) {
> +			pci_warn(xdev->pdev, "err handler have retried for %d times, stop retry\n",
> +				 xdev->cmd.retry_cnt);
> +			goto clear_err_int;
> +		}
> +		xsc_write32(xdev->bar, xdev->hal_regs->req_pid_addr,
> +			    &xdev->cmd.cmd_pid);
> +		xdev->cmd.retry_cnt++;
> +	}
> +
> +clear_err_int:
> +	if (err) {
> +		stat.raw = 0xf;
> +		xsc_write32(xdev->bar, xdev->hal_regs->interrupt_stat_addr, &stat.raw);

[Severity: High]
Will writing a hardcoded 0xf back to the hardware W1C register cause an
interrupt storm if a higher-order bit (e.g., bit 4) was asserted, since the
unknown bit would never be cleared?

Also, could unconditionally writing 1s to bits 0-3 clear new legitimate
interrupts that arrived after the initial read?

[ ... ]
> +u64 xsc_get_max_mr_size(struct xsc_core_device *xdev)
> +{
> +	u64 max_mpt_mr_size = xdev->caps.max_mr_size;
> +	u64 max_mtt_mr_size = xdev->caps.max_mtt * PAGE_SIZE;

[Severity: Medium]
Can this calculation overflow on 32-bit architectures where PAGE_SIZE is an
unsigned long? Both operands to the multiplication are 32-bit integers before
the assignment to the 64-bit result, which could truncate the capacity before
the type conversion happens.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=2

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

* Re: [PATCH 13/15] yunsilicon: add the RDMA send path
  2026-10-12 11:44 ` [PATCH 13/15] yunsilicon: add the RDMA send path Xin Tian
@ 2026-10-13 11:46   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 18 potential issue(s) to consider:
- [Critical] rdma: inline data payload overwrites remote address segment in RDMA Read/Write
- [Critical] rdma: hardware DMA buffer overflow due to overriding SGE length with maximum MTU
- [High] rdma: silent drop of remaining WRs in chain on zero-length RDMA READ
- [High] rdma: stack use-after-free in xsc_ib_drain_rq_done and xsc_ib_drain_sq_done
- [High] rdma: stack out-of-bounds read and kernel panic during UD QP drain
- [High] rdma: unaligned 32-bit pointer assignment causes kernel panics
- [High] rdma: uninitialized receive WQE segments leave hardware exposed to stale physical addresses
- [High] rdma: early return in QP drain routines causes Use-After-Free by breaking synchronous drain contract
- [High] rdma: set_data_inl_seg buffer overflow and RAddr segment corruption
- [High] rdma/xsc: buffer overflow and data corruption in set_data_inl_seg for multiple inline SGEs
- [High] xsc: wild pointer dereference in xsc_ib_drain_sq() for UD QPs
- [High] xsc: silent data loss when posting zero-length RDMA READ in a chain
- [Medium] rdma: unaligned multi-byte memory accesses on stack array in xsc_icrc_hdr
- [Medium] rdma: read-modify-write on DMA memory and uninitialized WQE control fields
- [Medium] rdma: xsc_icrc_hdr unconditionally adds UDP header size, breaking RoCEv1 packets
- [Medium] rdma/xsc: QP drain routines deadlock on IB_POLL_DIRECT CQs
- [Low] rdma: xsc_icrc_hdr returns negative error code for unsigned return type
- [Low] xsc: global namespace pollution by redefining struct rxe_bth
--

commit fdfdd79a8a3fbba8cb02ffc2535c12c0197fac66
Author: Xin Tian <tianx@yunsilicon.com>
Subject: yunsilicon: add the RDMA send path

This commit introduces the RDMA send path for the Yunsilicon XSC driver. 
It implements the work request processing for various operation types, 
QP1 MAD header construction, doorbell updates, and completion queue drains.

> diff --git a/drivers/infiniband/hw/xsc/qp.c b/drivers/infiniband/hw/xsc/qp.c
> index 6a83adc9c833f..9c79e09a7fd1f 100644
> --- a/drivers/infiniband/hw/xsc/qp.c
> +++ b/drivers/infiniband/hw/xsc/qp.c
[ ... ]
> +static int set_data_inl_seg(struct xsc_ib_qp *qp, const struct ib_send_wr *wr, void *ctrl)
> +{
> +	struct xsc_wqe_data_seg *data_seg;
> +	unsigned int seg_index;
> +	void *addr;
> +	int len;
> +	int i;
> +
> +	for (i = 0, seg_index = 1; i < wr->num_sge; ++i, ++seg_index) {
> +		if (likely(wr->sg_list[i].length)) {
> +			addr = (void *)wr->sg_list[i].addr;
> +			len  = wr->sg_list[i].length;
> +
> +			if (unlikely(len > qp->max_inline_data))
> +				return -ENOMEM;
> +
> +			data_seg = get_seg_wqe(ctrl, seg_index);
> +			data_seg->in_line = 1;
> +			data_seg->len = len;
> +			memcpy(data_seg->in_line_data, addr, len);
> +		}
> +	}
> +
> +	return 0;
> +}

[Severity: Critical]
By hardcoding seg_index = 1 here, does this overwrite the remote address 
(RAddr) segment for RDMA Write and Read requests? xsc_ib_post_send appears 
to place the RAddr at seg_index = 1 right before invoking this inline data 
logic.

Additionally, since seg_index is only incremented by 1 per SGE, what happens 
if an inline SGE is larger than 16 bytes? The memcpy will spill across 
multiple hardware WQE segments, but the loop will write the next SGE's data 
at seg_index + 1, overwriting the tail of the previous SGE's payload. 

Finally, the bounds check len > qp->max_inline_data only checks each SGE 
individually. If wr->num_sge > 1, does the sum of the SGE lengths overflow 
the pre-allocated WQE slot boundary and corrupt adjacent WQEs in the SQ 
ring buffer?

[ ... ]
> +static u32 xsc_icrc_hdr(struct xsc_ib_dev *dev, void *pkt, u32 size, u32 *icrc)
> +{
> +	struct iphdr *ip4h = NULL;
> +	struct ipv6hdr *ip6h = NULL;
> +	struct udphdr *udph;
> +	struct ib_unpacked_eth *eth;
> +	struct rxe_bth *bth;
> +	struct ib_unpacked_deth *deth;
> +	struct ib_unpacked_vlan *vlan;
> +	int crc;
> +	int crc_field_len;
> +	__be16 l3_type;
> +	u8 *l3_start;
> +
> +	int hdr_size;
> +
> +	/* pseudo header buffer size is calculate using ipv6 header size since
> +	 * it is bigger than ipv4
> +	 */
> +	u8 pshdr[sizeof(struct udphdr) +
> +		sizeof(struct ipv6hdr) +
> +		sizeof(*bth) + sizeof(*deth)];

[Severity: Medium]
Since pshdr is allocated as a byte array (u8) on the stack, it is only 
guaranteed to be 1-byte aligned. Does casting this buffer to struct iphdr * 
and struct rxe_bth * and performing multi-byte writes trigger alignment 
traps on architectures with strict memory alignment requirements?

[Severity: Low]
This function internally uses struct rxe_bth and struct rxe_deth. Does 
using another software driver's prefix (rxe_) pollute the global structure 
namespace and cause BTF deduplication conflicts? Should it use standard 
RDMA core structures like ib_unpacked_bth instead?

> +
> +	eth = pkt;
> +
> +	if (eth->type == htons(ETH_P_8021Q)) {
> +		vlan = (struct ib_unpacked_vlan *)(eth + 1);
> +		l3_type = vlan->type;
> +		l3_start = (u8 *)(vlan + 1);
> +		size -= 4;
> +	} else {
> +		l3_type = eth->type;
> +		l3_start = (u8 *)(eth + 1);
> +	}
> +
> +	hdr_size = sizeof(struct udphdr) +
> +		(l3_type == htons(ETH_P_IP) ?
> +		sizeof(struct iphdr) : sizeof(struct ipv6hdr));
> +
> +	crc_field_len = hdr_size + sizeof(*bth) + sizeof(*deth);
> +
> +	if (crc_field_len != size) {
> +		ibdev_err(&dev->ib_dev, "Unmatched hdr: expect %d actual %d\n",
> +			  crc_field_len, size);
> +		return -EINVAL;
> +	}

[Severity: Low]
Does returning -EINVAL from a function returning u32 implicitly cast the 
error code to a large positive integer, making it difficult for callers 
to properly check for errors?

[Severity: Medium]
Does hdr_size unconditionally adding sizeof(struct udphdr) break support 
for RoCEv1 (IBoE) packets? For RoCEv1, there is no UDP header, meaning 
crc_field_len != size will always evaluate to true, rejecting the packet.

[ ... ]
> +static void zero_send_ds(struct xsc_ib_qp *qp, int idx)
> +{
> +	void *seg;
> +	int i;
> +	int ds_num;
> +	u64 *p;
> +
> +	ds_num = XSC_SEND_SEG_NUM << (qp->sq.wqe_shift - XSC_SEND_WQE_SHIFT);
> +	seg = (void *)xsc_get_send_wqe(qp, idx);
> +	for (i = 1; i < ds_num; i++) {
> +		p = get_seg_wqe(seg, i);
> +		p[0] = 0;
> +		p[1] = 0;
> +	}
> +}

[Severity: Medium]
Since this loop explicitly starts at i = 1, the control segment (i = 0) is 
left uninitialized. Later in xsc_ib_post_send, the driver modifies bitfields 
(ctrl->se, ctrl->ce) on this uninitialized memory. Does this perform unsafe 
read-modify-write operations on DMA-mapped memory and leave unassigned 
fields with stale values from previous work requests?

[ ... ]
> +		if (unlikely(wr->opcode == IB_WR_RDMA_READ && msg_len == 0)) {
> +			ibdev_err(&dev->ib_dev, "rdma read, msg len should not be 0\n");
> +			/* workaround, return success for posting zero-length read */
> +			err = 0;
> +			goto out;
> +		}

[Severity: High]
Does this workaround silently drop the current work request and any remaining 
linked work requests in the wr->next chain? By returning success without 
generating completion entries for the dropped WRs, ULPs will hang 
indefinitely waiting for them. Zero-length RDMA READs are explicitly valid 
in the RDMA specification.

[ ... ]
> +			crc = xsc_crc32(dev, crc, mad_send_base + sg.length, ctrl->msg_len);
> +			ctrl->msg_len += sg.length;
> +			seg_index++;
> +
> +			*(u32 *)&mad_send_base[ctrl->msg_len] = ~crc;

[Severity: High]
Since ctrl->msg_len might not be 4-byte aligned, will this direct pointer 
cast and assignment cause unaligned 32-bit writes? Architectures lacking 
hardware support for unaligned access will panic here. Should this use 
put_unaligned instead?

[ ... ]
> +		for (i = 0; i < wr->num_sge; ++i) {
> +			if (unlikely(!wr->sg_list[i].length))
> +				continue;
> +			data_seg = get_seg_wqe(recv_head, i);
> +			data_seg->in_line = 0;
> +			WR_LE_64(data_seg->va, wr->sg_list[i].addr);
> +			WR_LE_32(data_seg->mkey, wr->sg_list[i].lkey);
> +			if (is_qp1(qp->xqp.qp_type))
> +				WR_LE_32(data_seg->seg_len, xdev->caps.rx_pkt_len_max);
> +			else
> +				WR_LE_32(data_seg->seg_len, wr->sg_list[i].length);
> +		}

[Severity: High]
If an SGE length is 0, the loop skips it via continue, failing to initialize 
the corresponding WQE segment. Because receive WQEs are reused from a ring 
buffer without zeroing, does this leave stale memory keys and physical 
addresses for the hardware to process, potentially DMAing incoming data 
into arbitrary memory?

[Severity: Critical]
For QP1 (GSI), the driver unconditionally overrides the consumer-provided 
buffer length (wr->sg_list[i].length) with the global max packet length 
xdev->caps.rx_pkt_len_max. Does this instruct the hardware that it can DMA 
data far beyond the actual bounds of the software-allocated scatter/gather 
buffer, causing memory corruption?

[ ... ]
> +static void xsc_ib_drain_rq_done(struct ib_cq *cq, struct ib_wc *wc)
> +{
> +	struct xsc_ib_drain_cqe *cqe = container_of(wc->wr_cqe,
> +						     struct xsc_ib_drain_cqe, cqe);
> +	struct xsc_ib_dev *dev = to_xdev(cq->device);
> +
> +	complete(&cqe->done);
> +	ibdev_dbg(&dev->ib_dev, "QP %d ib_drain_rq done!", cqe->qpn);
> +}

[Severity: High]
Since cqe is allocated on the stack in xsc_ib_drain_rq, the moment 
complete(&cqe->done) is called, the blocked thread can wake up and destroy 
the stack frame. Does the subsequent access to cqe->qpn dereference a 
potentially freed stack pointer, causing a use-after-free race? The same 
pattern is present in xsc_ib_drain_sq_done.

[ ... ]
> +void xsc_ib_drain_sq(struct ib_qp *qp)
> +{
[ ... ]
> +	const struct ib_send_wr swr = {
> +		.next = NULL,
> +		{ .wr_cqe	= &sdrain.cqe, },
> +		.opcode	= IB_WR_RDMA_WRITE,
> +		.send_flags = IB_SEND_SIGNALED,
> +	};
[ ... ]
> +	ret = xsc_ib_post_send(qp, &swr, &bad_swr);
> +	if (ret) {
> +		ibdev_err(&dev->ib_dev, "failed to drain send queue: %d\n", ret);
> +		return;
> +	}
> +	xqp->post_send_invalid = true;
> +
> +	xcq->xcq.comp(&xcq->xcq);
> +	wait_for_completion(&sdrain.done);
> +}

[Severity: High]
For UD QPs, xsc_ib_post_send unconditionally forwards this dummy WR to 
build_qp1_send_v2. Because swr is allocated purely as a base struct 
ib_send_wr, when build_qp1_send_v2 casts it to struct ib_ud_wr and 
dereferences ud_wr(wr)->ah, won't this read out-of-bounds stack garbage 
and cause a kernel panic?

[Severity: High]
The RDMA API requires the drain functions to block until all in-flight WQEs 
complete. By returning early when xsc_ib_post_send fails, does this break 
the synchronous drain contract, allowing the ULP to free memory while the 
hardware is still asynchronously processing in-flight WQEs?

[Severity: Medium]
If the CQ uses IB_POLL_DIRECT, there is no background task polling it. Will 
wait_for_completion block indefinitely unless a concurrent thread happens to 
poll the CQ? The core RDMA layer fallback handles this by calling 
ib_process_cq_direct(cq, -1) in a loop. Additionally, unconditionally calling 
xcq->xcq.comp(&xcq->xcq) will trigger a warning in ib_cq_completion_direct.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=13

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

* Re: [PATCH 14/15] yunsilicon: add RDMA counters and sysfs
  2026-10-12 11:44 ` [PATCH 14/15] yunsilicon: add RDMA counters and sysfs Xin Tian
@ 2026-10-13 11:46   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-13 11:46 UTC (permalink / raw)
  To: Xin Tian; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] sysfs: kobject UAF via missing .release and synchronous kfree
- [High] sysfs: global singleton g_global_cnt_interface corrupts multi-device state
- [Medium] sysfs: counters_value_read breaks partial reads and ignores offset
- [Medium] infiniband: sysfs attribute creation races with udev KOBJ_ADD
--

commit e407716ecbb78f776fbe73fcb02888c7ab5c00c8
Author: Xin Tian <tianx@yunsilicon.com>

yunsilicon: add RDMA counters and sysfs

This commit adds hardware counter reporting through the RDMA counter
interface, global counter sysfs attributes, and hca_type/hw_rev sysfs
attributes.

> diff --git a/drivers/infiniband/hw/xsc/counters.c b/drivers/infiniband/hw/xsc/counters.c
> new file mode 100644
> index 0000000000000..135df95954a75
> --- /dev/null
> +++ b/drivers/infiniband/hw/xsc/counters.c
[ ... ]
> +static ssize_t counters_value_read(struct file *file, struct kobject *kob,
> +				   const struct bin_attribute *bin_attr,
> +				   char *buf, loff_t loff, size_t size)
> +{
[ ... ]
> +	if (bin_attr->size > size || bin_attr->size == 0)
> +		return 0;

[Severity: Medium]
Does this check incorrectly reject partial or offset-bounded reads? 

Since the sysfs core bounds the requested size to max_size - loff, a read
with a non-zero offset or a user buffer smaller than bin_attr->size might
immediately fail with an early EOF here.

[ ... ]
> +	mask = xsc_get_rdma_stat_mask(xdev, be16_to_cpu(stats_rdma.ver));
> +	j = 0;
> +	for (i = 0; i < desc_size; i++) {
> +		if (!((1ULL << i) & mask))
> +			continue;
> +		tmp_value[j] = be64_to_cpu(*(u64 *)(stats + desc[i].offset));
> +		j++;
> +	}
> +
> +	memcpy(buf, tmp_value, bin_attr->size);
> +
> +	kfree(tmp_value);
> +	return bin_attr->size;
> +}

[Severity: Medium]
Could this ignore the requested offset during a read?

The loff parameter is not used in the memcpy() call, so if a read succeeded
at a non-zero offset, it would incorrectly copy from the beginning of
tmp_value instead of applying the requested offset.

[ ... ]
> +static const struct kobj_type global_cnt_ktype = {
> +	.sysfs_ops     = &global_cnt_sysfs_ops,
> +	.default_groups = global_cnt_groups,
> +};

[Severity: High]
Is this kobj_type missing the mandatory .release callback required for
kobjects? 

Without it, the kobject container memory cannot be safely managed when the
reference count reaches zero.

> +
> +static struct xsc_global_cnt_interface *g_global_cnt_interface;
> +
> +static int global_cnt_sysfs_init(struct ib_device *ib_dev, struct xsc_core_device *xdev)
> +{
[ ... ]
> +	err = kobject_init_and_add(&tmp->kobj, &global_cnt_ktype,
> +				   &ib_dev->dev.kobj, GLOBAL_COUNTERS_GROUP_NAME);
> +	if (err)
> +		goto error_return;
> +
> +	g_global_cnt_interface = tmp;
> +	tmp->xdev = xdev;
> +	return 0;

[Severity: High]
Does this blindly overwrite the global pointer g_global_cnt_interface for
every initialized physical function?

If the system contains multiple Yunsilicon network adapters, initializing the
second one would overwrite the pointer. This would lose the previous device's
state and permanently leak its memory and kobject. Furthermore, removing the
first device would cause global_cnt_sysfs_fini() to mistakenly operate on the
second device's state.

[ ... ]
> +static void global_cnt_sysfs_fini(struct xsc_core_device *xdev)
> +{
> +	if (!g_global_cnt_interface || !xdev || !xsc_core_is_pf(xdev) || xdev->pf_id != 0)
> +		return;
> +
> +	kobject_put(&g_global_cnt_interface->kobj);
> +	kfree(g_global_cnt_interface);
> +	g_global_cnt_interface = NULL;
> +}

[Severity: High]
Is it safe to synchronously free the kobject container memory here?

Since kobject_put() merely decrements the reference count, the kobject and
its sysfs representation could remain active if userspace still holds an
open file descriptor. Calling kfree() immediately afterwards destroys the
underlying memory prematurely, which might lead to a use-after-free crash
when userspace subsequently accesses the sysfs files.

> diff --git a/drivers/infiniband/hw/xsc/main.c b/drivers/infiniband/hw/xsc/main.c
> index 30927006bae2d..38c8b4f0690c2 100644
> --- a/drivers/infiniband/hw/xsc/main.c
> +++ b/drivers/infiniband/hw/xsc/main.c
[ ... ]
> @@ -745,7 +745,9 @@ static int init_one(struct xsc_core_device *xdev,
>  
>  	xsc_register_netdev_notifier(dev);
>  
> +	xsc_counters_init(&dev->ib_dev, xdev);
>  
> +	xsc_ib_sysfs_init(&dev->ib_dev, xdev);
>  
>  	return 0;

[Severity: Medium]
Does creating the sysfs attribute files here cause a race condition with
udev?

The driver registers the device earlier in init_one() by calling
ib_register_device(), which triggers a KOBJ_ADD uevent. Adding the sysfs
files after this registration means udev rules might execute before the
attributes actually exist. Should the sysfs groups be assigned to the
device's attribute groups (e.g., ib_dev->groups) prior to registration
instead?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261012114352.2471317-1-tianx@yunsilicon.com?part=14

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

* Re: [PATCH 03/15] yunsilicon: add the command queue
  2026-10-13  3:11   ` Junxian Huang
@ 2026-10-13 14:31     ` Leon Romanovsky
  0 siblings, 0 replies; 35+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:31 UTC (permalink / raw)
  To: Junxian Huang
  Cc: Xin Tian, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Jason Gunthorpe, weihg, linux-kernel,
	netdev, linux-rdma

On Tue, Oct 13, 2026 at 11:11:17AM +0800, Junxian Huang wrote:
> 
> 
> On 2026/10/12 19:43, Xin Tian wrote:
> > Add the command queue that is shared by the ethernet and RDMA functions:
> > the request/response rings, the command work handling and the device
> > initialisation sequence that queries the HCA capabilities, the GUID and
> > the message opcode table.
> > 
> > Signed-off-by: Xin Tian <tianx@yunsilicon.com>
> > ---
> >  .../net/ethernet/yunsilicon/xsc/pci/Makefile  |    2 +-
> >  .../net/ethernet/yunsilicon/xsc/pci/cmdq.c    | 1247 +++++++++++++++++
> >  drivers/net/ethernet/yunsilicon/xsc/pci/hw.c  |  310 ++++
> >  drivers/net/ethernet/yunsilicon/xsc/pci/hw.h  |   18 +
> >  .../net/ethernet/yunsilicon/xsc/pci/main.c    |   55 +
> >  5 files changed, 1631 insertions(+), 1 deletion(-)
> >  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
> >  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hw.c
> >  create mode 100644 drivers/net/ethernet/yunsilicon/xsc/pci/hw.h
> > 
> > diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
> > index 81b65232e..656cdffa1 100644
> > --- a/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
> > +++ b/drivers/net/ethernet/yunsilicon/xsc/pci/Makefile
> > @@ -6,4 +6,4 @@ ccflags-y += -I$(srctree)/drivers/net/ethernet/yunsilicon/xsc
> >  
> >  obj-$(CONFIG_YUNSILICON_XSC_PCI) += xsc_pci.o
> >  
> > -xsc_pci-y := main.o hal/andes_impl.o
> > +xsc_pci-y := main.o cmdq.o hw.o hal/andes_impl.o
> > diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c b/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
> > new file mode 100644
> > index 000000000..48832b26d
> > --- /dev/null
> > +++ b/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
> > @@ -0,0 +1,1247 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2021-2026, Shanghai Yunsilicon Technology Co., Ltd.
> > + * All rights reserved.
> > + * Copyright (c) 2013-2016, Mellanox Technologies. All rights reserved.
> 
> Why Mellanox?

This is how new RDMA drivers are born :)

Thanks

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

* Re: [PATCH 00/15] Yunsilicon ethernet and RDMA drivers
  2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
                   ` (14 preceding siblings ...)
  2026-10-12 11:44 ` [PATCH 15/15] MAINTAINERS: add the Yunsilicon XSC drivers Xin Tian
@ 2026-10-13 14:34 ` Leon Romanovsky
  15 siblings, 0 replies; 35+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:34 UTC (permalink / raw)
  To: Xin Tian
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jason Gunthorpe, weihg, linux-kernel, netdev,
	linux-rdma

On Mon, Oct 12, 2026 at 07:44:32PM +0800, Xin Tian wrote:
> Hi all,
> 
> This is a new submission from Yunsilicon to upstream our converged
> network adapter driver.  The hardware is a dual-function PCIe device
> (PCI vendor 0x1f67) that supports both ethernet and RDMA operations.
> 
> The series adds three drivers, which share the xsc prefix.  A shared PCI
> core driver binds to the physical function, brings up the command queue,
> the event queues and the interrupt handling, and publishes one auxiliary
> device per function.  The ethernet and RDMA drivers bind to that
> auxiliary device instead of driving the PCI function themselves, so the
> two personalities share a single probe and resource set.
> 
>   1. xsc_pci - the PCI core driver (CONFIG_YUNSILICON_XSC_PCI).
>      Owns the PCI function, the command queue, the event queues, the
>      interrupt vectors, the register access layer and the CQ/QP/SRQ
>      resource pools.  Patches 01-05.
> 
>   2. xsc_eth - the ethernet driver (CONFIG_YUNSILICON_XSC_ETH).
>      Registers the netdev, implements the TX/RX packet paths with NAPI
>      and page_pool, RSS, checksum offloads and statistics.  Patches
>      06-08, on top of the core.
> 
>   3. xsc_ib - the RDMA driver (CONFIG_INFINIBAND_XSC).
>      Registers the ib_device and provides the PD/MR, CQ/SRQ/AH and QP
>      verbs including the send/receive data paths, plus RDMA counters and
>      sysfs attributes.  Patches 09-14, on top of the core and the
>      ethernet driver (the netdev supplies port state and GID).
> 
> Patch 15 adds the MAINTAINERS entry.
> 
> The series is split so that every commit builds: the core patches
> introduce the register access, the command queue, the event queues and
> the resource pools incrementally, and the ethernet and RDMA patches only
> start creating queues once their dependencies are in place.  Patch 05
> (core CQ/QP pools) is deliberately shared by both upper drivers; that is
> why it precedes the ethernet patches.
> 
> The RDMA device is registered at patch 09, before all verbs exist, so
> kverbs_provider stays false until the mandatory verbs appear in patches
> 10-13.  This keeps each intermediate tree compilable.
> 
> Please review.

Please run Sashiko locally first.

Before we begin an active review of a new driver, we expect it to have
only a small number of Sashiko findings.

Thanks

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

end of thread, other threads:[~2026-10-13 14:34 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-12 11:44 [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Xin Tian
2026-10-12 11:43 ` [PATCH 01/15] yunsilicon: add PCI core driver skeleton Xin Tian
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:43 ` [PATCH 02/15] yunsilicon: add the register HAL Xin Tian
2026-10-12 15:21   ` Andrew Lunn
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:43 ` [PATCH 03/15] yunsilicon: add the command queue Xin Tian
2026-10-13  3:11   ` Junxian Huang
2026-10-13 14:31     ` Leon Romanovsky
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 04/15] yunsilicon: add event queues, interrupts and the Xin Tian
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 05/15] yunsilicon: add the CQ and QP resource pools Xin Tian
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 06/15] yunsilicon: add ethernet netdev registration Xin Tian
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 07/15] yunsilicon: add ethernet TX path Xin Tian
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 08/15] yunsilicon: add ethernet RX path and statistics Xin Tian
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 09/15] yunsilicon: add RDMA device support Xin Tian
2026-10-13  6:17   ` Junxian Huang
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 10/15] yunsilicon: add RDMA PD/MR Xin Tian
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 11/15] yunsilicon: add RDMA CQ/SRQ/AH Xin Tian
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 12/15] yunsilicon: add RDMA QP verbs Xin Tian
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 13/15] yunsilicon: add the RDMA send path Xin Tian
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 14/15] yunsilicon: add RDMA counters and sysfs Xin Tian
2026-10-13 11:46   ` sashiko-bot
2026-10-12 11:44 ` [PATCH 15/15] MAINTAINERS: add the Yunsilicon XSC drivers Xin Tian
2026-10-13 14:34 ` [PATCH 00/15] Yunsilicon ethernet and RDMA drivers Leon Romanovsky

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.