* [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-12 11:43 ` [PATCH 02/15] yunsilicon: add the register HAL Xin Tian
` (14 subsequent siblings)
15 siblings, 0 replies; 21+ 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 [flat|nested] 21+ 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-12 11:43 ` [PATCH 03/15] yunsilicon: add the command queue Xin Tian
` (13 subsequent siblings)
15 siblings, 1 reply; 21+ 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 [flat|nested] 21+ 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-12 11:44 ` [PATCH 04/15] yunsilicon: add event queues, interrupts and the Xin Tian
` (12 subsequent siblings)
15 siblings, 1 reply; 21+ 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 [flat|nested] 21+ 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-12 11:44 ` [PATCH 05/15] yunsilicon: add the CQ and QP resource pools Xin Tian
` (11 subsequent siblings)
15 siblings, 0 replies; 21+ 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 [flat|nested] 21+ 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-12 11:44 ` [PATCH 06/15] yunsilicon: add ethernet netdev registration Xin Tian
` (10 subsequent siblings)
15 siblings, 0 replies; 21+ 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 [flat|nested] 21+ 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-12 11:44 ` [PATCH 07/15] yunsilicon: add ethernet TX path Xin Tian
` (9 subsequent siblings)
15 siblings, 0 replies; 21+ 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 [flat|nested] 21+ 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-12 11:44 ` [PATCH 08/15] yunsilicon: add ethernet RX path and statistics Xin Tian
` (8 subsequent siblings)
15 siblings, 0 replies; 21+ 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 [flat|nested] 21+ 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-12 11:44 ` [PATCH 09/15] yunsilicon: add RDMA device support Xin Tian
` (7 subsequent siblings)
15 siblings, 0 replies; 21+ 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 [flat|nested] 21+ 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-12 11:44 ` [PATCH 10/15] yunsilicon: add RDMA PD/MR Xin Tian
` (6 subsequent siblings)
15 siblings, 1 reply; 21+ 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 [flat|nested] 21+ 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-12 11:44 ` [PATCH 11/15] yunsilicon: add RDMA CQ/SRQ/AH Xin Tian
` (5 subsequent siblings)
15 siblings, 0 replies; 21+ 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 [flat|nested] 21+ 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-12 11:44 ` [PATCH 12/15] yunsilicon: add RDMA QP verbs Xin Tian
` (4 subsequent siblings)
15 siblings, 0 replies; 21+ 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 [flat|nested] 21+ 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-12 11:44 ` [PATCH 13/15] yunsilicon: add the RDMA send path Xin Tian
` (3 subsequent siblings)
15 siblings, 0 replies; 21+ 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 [flat|nested] 21+ 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-12 11:44 ` [PATCH 14/15] yunsilicon: add RDMA counters and sysfs Xin Tian
` (2 subsequent siblings)
15 siblings, 0 replies; 21+ 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 [flat|nested] 21+ 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-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, 0 replies; 21+ 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 [flat|nested] 21+ 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; 21+ 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 [flat|nested] 21+ 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; 21+ 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] 21+ 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
0 siblings, 0 replies; 21+ 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] 21+ 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
0 siblings, 1 reply; 21+ 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] 21+ 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
0 siblings, 0 replies; 21+ 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] 21+ 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; 21+ 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] 21+ 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; 21+ 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] 21+ messages in thread
end of thread, other threads:[~2026-10-13 14:34 UTC | newest]
Thread overview: 21+ 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-12 11:43 ` [PATCH 02/15] yunsilicon: add the register HAL Xin Tian
2026-10-12 15:21 ` Andrew Lunn
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-12 11:44 ` [PATCH 04/15] yunsilicon: add event queues, interrupts and the Xin Tian
2026-10-12 11:44 ` [PATCH 05/15] yunsilicon: add the CQ and QP resource pools Xin Tian
2026-10-12 11:44 ` [PATCH 06/15] yunsilicon: add ethernet netdev registration Xin Tian
2026-10-12 11:44 ` [PATCH 07/15] yunsilicon: add ethernet TX path Xin Tian
2026-10-12 11:44 ` [PATCH 08/15] yunsilicon: add ethernet RX path and statistics Xin Tian
2026-10-12 11:44 ` [PATCH 09/15] yunsilicon: add RDMA device support Xin Tian
2026-10-13 6:17 ` Junxian Huang
2026-10-12 11:44 ` [PATCH 10/15] yunsilicon: add RDMA PD/MR Xin Tian
2026-10-12 11:44 ` [PATCH 11/15] yunsilicon: add RDMA CQ/SRQ/AH Xin Tian
2026-10-12 11:44 ` [PATCH 12/15] yunsilicon: add RDMA QP verbs Xin Tian
2026-10-12 11:44 ` [PATCH 13/15] yunsilicon: add the RDMA send path Xin Tian
2026-10-12 11:44 ` [PATCH 14/15] yunsilicon: add RDMA counters and sysfs Xin Tian
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®