diff --git a/drivers/infiniband/hw/hns/Makefile b/drivers/infiniband/hw/hns/Makefile
index be1e1cdbcfa8..161615fda3ae 100644
--- a/drivers/infiniband/hw/hns/Makefile
+++ b/drivers/infiniband/hw/hns/Makefile
@@ -8,7 +8,7 @@ ccflags-y :=  -I $(srctree)/drivers/net/ethernet/hisilicon/hns3
 hns-roce-objs := hns_roce_main.o hns_roce_cmd.o hns_roce_pd.o \
 	hns_roce_ah.o hns_roce_hem.o hns_roce_mr.o hns_roce_qp.o \
 	hns_roce_cq.o hns_roce_alloc.o hns_roce_db.o hns_roce_srq.o hns_roce_restrack.o \
-	hns_roce_debugfs.o
+	hns_roce_debugfs.o hns_roce_sysfs.o
 
 ifdef CONFIG_INFINIBAND_HNS_HIP08
 hns-roce-hw-v2-objs := hns_roce_hw_v2.o $(hns-roce-objs)
diff --git a/drivers/infiniband/hw/hns/hns_roce_device.h b/drivers/infiniband/hw/hns/hns_roce_device.h
index c3cbd0a494bf..d6a9a510f541 100644
--- a/drivers/infiniband/hw/hns/hns_roce_device.h
+++ b/drivers/infiniband/hw/hns/hns_roce_device.h
@@ -599,6 +599,7 @@ enum hns_roce_cong_type {
 	CONG_TYPE_LDCP,
 	CONG_TYPE_HC3,
 	CONG_TYPE_DIP,
+	CONG_TYPE_TOTAL,
 };
 
 struct hns_roce_qp {
@@ -952,6 +953,20 @@ struct hns_roce_hw {
 				u64 *stats, u32 port, int *hw_counters);
 	const struct ib_device_ops *hns_roce_dev_ops;
 	const struct ib_device_ops *hns_roce_dev_srq_ops;
+	int (*config_scc_param)(struct hns_roce_dev *hr_dev,
+				enum hns_roce_cong_type algo);
+	int (*query_scc_param)(struct hns_roce_dev *hr_dev,
+			       enum hns_roce_cong_type alog);
+};
+
+#define HNS_ROCE_SCC_PARAM_SIZE 4
+struct hns_roce_scc_param {
+	__le32 param[HNS_ROCE_SCC_PARAM_SIZE];
+	u32 lifespan;
+	unsigned long timestamp;
+	enum hns_roce_cong_type algo_type;
+	struct delayed_work scc_cfg_dwork;
+	struct hns_roce_dev *hr_dev;
 };
 
 struct hns_roce_dev {
@@ -1017,6 +1032,7 @@ struct hns_roce_dev {
 	u64 dwqe_page;
 	struct hns_roce_dev_debugfs dbgfs;
 	atomic64_t *dfx_cnt;
+	struct hns_roce_scc_param *scc_param;
 };
 
 static inline struct hns_roce_dev *to_hr_dev(struct ib_device *ib_dev)
@@ -1152,6 +1168,8 @@ static inline u8 get_tclass(const struct ib_global_route *grh)
 	       grh->traffic_class >> DSCP_SHIFT : grh->traffic_class;
 }
 
+extern const struct attribute_group *hns_attr_port_groups[];
+
 void hns_roce_init_uar_table(struct hns_roce_dev *dev);
 int hns_roce_uar_alloc(struct hns_roce_dev *dev, struct hns_roce_uar *uar);
 
@@ -1292,4 +1310,6 @@ struct hns_user_mmap_entry *
 hns_roce_user_mmap_entry_insert(struct ib_ucontext *ucontext, u64 address,
 				size_t length,
 				enum hns_roce_mmap_type mmap_type);
+void hns_roce_register_sysfs(struct hns_roce_dev *hr_dev);
+void hns_roce_unregister_sysfs(struct hns_roce_dev *hr_dev);
 #endif /* _HNS_ROCE_DEVICE_H */
diff --git a/drivers/infiniband/hw/hns/hns_roce_hw_v2.c b/drivers/infiniband/hw/hns/hns_roce_hw_v2.c
index ba7ae792d279..9d16ccc3d65d 100644
--- a/drivers/infiniband/hw/hns/hns_roce_hw_v2.c
+++ b/drivers/infiniband/hw/hns/hns_roce_hw_v2.c
@@ -6689,6 +6689,63 @@ static void hns_roce_v2_cleanup_eq_table(struct hns_roce_dev *hr_dev)
 	kfree(eq_table->eq);
 }
 
+static const enum hns_roce_opcode_type scc_opcode[] = {
+	HNS_ROCE_OPC_CFG_DCQCN_PARAM,
+	HNS_ROCE_OPC_CFG_LDCP_PARAM,
+	HNS_ROCE_OPC_CFG_HC3_PARAM,
+	HNS_ROCE_OPC_CFG_DIP_PARAM,
+};
+
+static int hns_roce_v2_config_scc_param(struct hns_roce_dev *hr_dev,
+					enum hns_roce_cong_type algo)
+{
+	struct hns_roce_scc_param *scc_param;
+	struct hns_roce_cmq_desc desc;
+	int ret;
+
+	if (algo >= CONG_TYPE_TOTAL)
+		return -EINVAL;
+
+	hns_roce_cmq_setup_basic_desc(&desc, scc_opcode[algo], false);
+	scc_param = &hr_dev->scc_param[algo];
+	memcpy(&desc.data, scc_param, sizeof(scc_param->param));
+
+	ret = hns_roce_cmq_send(hr_dev, &desc, 1);
+	if (ret)
+		ibdev_err_ratelimited(&hr_dev->ib_dev,
+				      "failed to configure scc param, opcode: 0x%x, ret = %d.\n",
+				      le16_to_cpu(desc.opcode), ret);
+	return ret;
+}
+
+static int hns_roce_v2_query_scc_param(struct hns_roce_dev *hr_dev,
+				       enum hns_roce_cong_type algo)
+{
+	struct hns_roce_scc_param *scc_param;
+	struct hns_roce_cmq_desc desc;
+	int ret;
+
+	if (hr_dev->pci_dev->revision <= PCI_REVISION_ID_HIP08 || hr_dev->is_vf)
+		return -EOPNOTSUPP;
+
+	if (algo >= CONG_TYPE_TOTAL)
+		return -EINVAL;
+
+	hns_roce_cmq_setup_basic_desc(&desc, scc_opcode[algo], true);
+	ret = hns_roce_cmq_send(hr_dev, &desc, 1);
+	if (ret) {
+		ibdev_err_ratelimited(&hr_dev->ib_dev,
+				      "failed to query scc param, opcode: 0x%x, ret = %d.\n",
+				      le16_to_cpu(desc.opcode), ret);
+		return ret;
+	}
+
+	scc_param = &hr_dev->scc_param[algo];
+	memcpy(scc_param, &desc.data, sizeof(scc_param->param));
+
+	return 0;
+}
+
 static const struct ib_device_ops hns_roce_v2_dev_ops = {
 	.destroy_qp = hns_roce_v2_destroy_qp,
 	.modify_cq = hns_roce_v2_modify_cq,
@@ -6737,6 +6794,8 @@ static const struct hns_roce_hw hns_roce_hw_v2 = {
 	.query_hw_counter = hns_roce_hw_v2_query_counter,
 	.hns_roce_dev_ops = &hns_roce_v2_dev_ops,
 	.hns_roce_dev_srq_ops = &hns_roce_v2_dev_srq_ops,
+	.config_scc_param = hns_roce_v2_config_scc_param,
+	.query_scc_param = hns_roce_v2_query_scc_param,
 };
 
 static const struct pci_device_id hns_roce_hw_v2_pci_tbl[] = {
diff --git a/drivers/infiniband/hw/hns/hns_roce_hw_v2.h b/drivers/infiniband/hw/hns/hns_roce_hw_v2.h
index df04bc8ede57..3167003c4f94 100644
--- a/drivers/infiniband/hw/hns/hns_roce_hw_v2.h
+++ b/drivers/infiniband/hw/hns/hns_roce_hw_v2.h
@@ -195,6 +195,10 @@ enum {
 /* CMQ command */
 enum hns_roce_opcode_type {
 	HNS_QUERY_FW_VER				= 0x0001,
+	HNS_ROCE_OPC_CFG_DCQCN_PARAM			= 0x1A80,
+	HNS_ROCE_OPC_CFG_LDCP_PARAM			= 0x1A81,
+	HNS_ROCE_OPC_CFG_HC3_PARAM			= 0x1A82,
+	HNS_ROCE_OPC_CFG_DIP_PARAM			= 0x1A83,
 	HNS_ROCE_OPC_QUERY_HW_VER			= 0x8000,
 	HNS_ROCE_OPC_CFG_GLOBAL_PARAM			= 0x8001,
 	HNS_ROCE_OPC_ALLOC_PF_RES			= 0x8004,
@@ -1429,6 +1433,134 @@ struct hns_roce_wqe_atomic_seg {
 	__le64          cmp_data;
 };
 
+#define HNS_ROCE_DCQCN_AI_OFS 0
+#define HNS_ROCE_DCQCN_AI_SZ sizeof(u16)
+#define HNS_ROCE_DCQCN_AI_MAX ((u16)(~0U))
+#define HNS_ROCE_DCQCN_F_OFS (HNS_ROCE_DCQCN_AI_OFS + HNS_ROCE_DCQCN_AI_SZ)
+#define HNS_ROCE_DCQCN_F_SZ sizeof(u8)
+#define HNS_ROCE_DCQCN_F_MAX ((u8)(~0U))
+#define HNS_ROCE_DCQCN_TKP_OFS (HNS_ROCE_DCQCN_F_OFS + HNS_ROCE_DCQCN_F_SZ)
+#define HNS_ROCE_DCQCN_TKP_SZ sizeof(u8)
+#define HNS_ROCE_DCQCN_TKP_MAX 15
+#define HNS_ROCE_DCQCN_TMP_OFS (HNS_ROCE_DCQCN_TKP_OFS + HNS_ROCE_DCQCN_TKP_SZ)
+#define HNS_ROCE_DCQCN_TMP_SZ sizeof(u16)
+#define HNS_ROCE_DCQCN_TMP_MAX 15
+#define HNS_ROCE_DCQCN_ALP_OFS (HNS_ROCE_DCQCN_TMP_OFS + HNS_ROCE_DCQCN_TMP_SZ)
+#define HNS_ROCE_DCQCN_ALP_SZ sizeof(u16)
+#define HNS_ROCE_DCQCN_ALP_MAX ((u16)(~0U))
+#define HNS_ROCE_DCQCN_MAX_SPEED_OFS (HNS_ROCE_DCQCN_ALP_OFS + \
+					HNS_ROCE_DCQCN_ALP_SZ)
+#define HNS_ROCE_DCQCN_MAX_SPEED_SZ sizeof(u32)
+#define HNS_ROCE_DCQCN_MAX_SPEED_MAX ((u32)(~0U))
+#define HNS_ROCE_DCQCN_G_OFS (HNS_ROCE_DCQCN_MAX_SPEED_OFS + \
+					HNS_ROCE_DCQCN_MAX_SPEED_SZ)
+#define HNS_ROCE_DCQCN_G_SZ sizeof(u8)
+#define HNS_ROCE_DCQCN_G_MAX 15
+#define HNS_ROCE_DCQCN_AL_OFS (HNS_ROCE_DCQCN_G_OFS + HNS_ROCE_DCQCN_G_SZ)
+#define HNS_ROCE_DCQCN_AL_SZ sizeof(u8)
+#define HNS_ROCE_DCQCN_AL_MAX ((u8)(~0U))
+#define HNS_ROCE_DCQCN_CNP_TIME_OFS (HNS_ROCE_DCQCN_AL_OFS + \
+					HNS_ROCE_DCQCN_AL_SZ)
+#define HNS_ROCE_DCQCN_CNP_TIME_SZ sizeof(u8)
+#define HNS_ROCE_DCQCN_CNP_TIME_MAX ((u8)(~0U))
+#define HNS_ROCE_DCQCN_ASHIFT_OFS (HNS_ROCE_DCQCN_CNP_TIME_OFS + \
+					HNS_ROCE_DCQCN_CNP_TIME_SZ)
+#define HNS_ROCE_DCQCN_ASHIFT_SZ sizeof(u8)
+#define HNS_ROCE_DCQCN_ASHIFT_MAX 15
+#define HNS_ROCE_DCQCN_LIFESPAN_OFS (HNS_ROCE_DCQCN_ASHIFT_OFS + \
+					HNS_ROCE_DCQCN_ASHIFT_SZ)
+#define HNS_ROCE_DCQCN_LIFESPAN_SZ sizeof(u32)
+#define HNS_ROCE_DCQCN_LIFESPAN_MAX 1000
+
+#define HNS_ROCE_LDCP_CWD0_OFS 0
+#define HNS_ROCE_LDCP_CWD0_SZ sizeof(u32)
+#define HNS_ROCE_LDCP_CWD0_MAX ((u32)(~0U))
+#define HNS_ROCE_LDCP_ALPHA_OFS (HNS_ROCE_LDCP_CWD0_OFS + HNS_ROCE_LDCP_CWD0_SZ)
+#define HNS_ROCE_LDCP_ALPHA_SZ sizeof(u8)
+#define HNS_ROCE_LDCP_ALPHA_MAX ((u8)(~0U))
+#define HNS_ROCE_LDCP_GAMMA_OFS (HNS_ROCE_LDCP_ALPHA_OFS + \
+					HNS_ROCE_LDCP_ALPHA_SZ)
+#define HNS_ROCE_LDCP_GAMMA_SZ sizeof(u8)
+#define HNS_ROCE_LDCP_GAMMA_MAX ((u8)(~0U))
+#define HNS_ROCE_LDCP_BETA_OFS (HNS_ROCE_LDCP_GAMMA_OFS + \
+					HNS_ROCE_LDCP_GAMMA_SZ)
+#define HNS_ROCE_LDCP_BETA_SZ sizeof(u8)
+#define HNS_ROCE_LDCP_BETA_MAX ((u8)(~0U))
+#define HNS_ROCE_LDCP_ETA_OFS (HNS_ROCE_LDCP_BETA_OFS + HNS_ROCE_LDCP_BETA_SZ)
+#define HNS_ROCE_LDCP_ETA_SZ sizeof(u8)
+#define HNS_ROCE_LDCP_ETA_MAX ((u8)(~0U))
+#define HNS_ROCE_LDCP_LIFESPAN_OFS (4 * sizeof(u32))
+#define HNS_ROCE_LDCP_LIFESPAN_SZ sizeof(u32)
+#define HNS_ROCE_LDCP_LIFESPAN_MAX 1000
+
+#define HNS_ROCE_HC3_INITIAL_WINDOW_OFS 0
+#define HNS_ROCE_HC3_INITIAL_WINDOW_SZ sizeof(u32)
+#define HNS_ROCE_HC3_INITIAL_WINDOW_MAX ((u32)(~0U))
+#define HNS_ROCE_HC3_BANDWIDTH_OFS (HNS_ROCE_HC3_INITIAL_WINDOW_OFS + \
+					HNS_ROCE_HC3_INITIAL_WINDOW_SZ)
+#define HNS_ROCE_HC3_BANDWIDTH_SZ sizeof(u32)
+#define HNS_ROCE_HC3_BANDWIDTH_MAX ((u32)(~0U))
+#define HNS_ROCE_HC3_QLEN_SHIFT_OFS (HNS_ROCE_HC3_BANDWIDTH_OFS + \
+					HNS_ROCE_HC3_BANDWIDTH_SZ)
+#define HNS_ROCE_HC3_QLEN_SHIFT_SZ sizeof(u8)
+#define HNS_ROCE_HC3_QLEN_SHIFT_MAX ((u8)(~0U))
+#define HNS_ROCE_HC3_PORT_USAGE_SHIFT_OFS (HNS_ROCE_HC3_QLEN_SHIFT_OFS + \
+						HNS_ROCE_HC3_QLEN_SHIFT_SZ)
+#define HNS_ROCE_HC3_PORT_USAGE_SHIFT_SZ sizeof(u8)
+#define HNS_ROCE_HC3_PORT_USAGE_SHIFT_MAX ((u8)(~0U))
+#define HNS_ROCE_HC3_OVER_PERIOD_OFS (HNS_ROCE_HC3_PORT_USAGE_SHIFT_OFS + \
+					HNS_ROCE_HC3_PORT_USAGE_SHIFT_SZ)
+#define HNS_ROCE_HC3_OVER_PERIOD_SZ sizeof(u8)
+#define HNS_ROCE_HC3_OVER_PERIOD_MAX ((u8)(~0U))
+#define HNS_ROCE_HC3_MAX_STAGE_OFS (HNS_ROCE_HC3_OVER_PERIOD_OFS + \
+					HNS_ROCE_HC3_OVER_PERIOD_SZ)
+#define HNS_ROCE_HC3_MAX_STAGE_SZ sizeof(u8)
+#define HNS_ROCE_HC3_MAX_STAGE_MAX ((u8)(~0U))
+#define HNS_ROCE_HC3_GAMMA_SHIFT_OFS (HNS_ROCE_HC3_MAX_STAGE_OFS + \
+					HNS_ROCE_HC3_MAX_STAGE_SZ)
+#define HNS_ROCE_HC3_GAMMA_SHIFT_SZ sizeof(u8)
+#define HNS_ROCE_HC3_GAMMA_SHIFT_MAX 15
+#define HNS_ROCE_HC3_LIFESPAN_OFS (4 * sizeof(u32))
+#define HNS_ROCE_HC3_LIFESPAN_SZ sizeof(u32)
+#define HNS_ROCE_HC3_LIFESPAN_MAX 1000
+
+#define HNS_ROCE_DIP_AI_OFS 0
+#define HNS_ROCE_DIP_AI_SZ sizeof(u16)
+#define HNS_ROCE_DIP_AI_MAX ((u16)(~0U))
+#define HNS_ROCE_DIP_F_OFS (HNS_ROCE_DIP_AI_OFS + HNS_ROCE_DIP_AI_SZ)
+#define HNS_ROCE_DIP_F_SZ sizeof(u8)
+#define HNS_ROCE_DIP_F_MAX ((u8)(~0U))
+#define HNS_ROCE_DIP_TKP_OFS (HNS_ROCE_DIP_F_OFS + HNS_ROCE_DIP_F_SZ)
+#define HNS_ROCE_DIP_TKP_SZ sizeof(u8)
+#define HNS_ROCE_DIP_TKP_MAX 15
+#define HNS_ROCE_DIP_TMP_OFS (HNS_ROCE_DIP_TKP_OFS + HNS_ROCE_DIP_TKP_SZ)
+#define HNS_ROCE_DIP_TMP_SZ sizeof(u16)
+#define HNS_ROCE_DIP_TMP_MAX 15
+#define HNS_ROCE_DIP_ALP_OFS (HNS_ROCE_DIP_TMP_OFS + HNS_ROCE_DIP_TMP_SZ)
+#define HNS_ROCE_DIP_ALP_SZ sizeof(u16)
+#define HNS_ROCE_DIP_ALP_MAX ((u16)(~0U))
+#define HNS_ROCE_DIP_MAX_SPEED_OFS (HNS_ROCE_DIP_ALP_OFS + HNS_ROCE_DIP_ALP_SZ)
+#define HNS_ROCE_DIP_MAX_SPEED_SZ sizeof(u32)
+#define HNS_ROCE_DIP_MAX_SPEED_MAX ((u32)(~0U))
+#define HNS_ROCE_DIP_G_OFS (HNS_ROCE_DIP_MAX_SPEED_OFS + \
+				HNS_ROCE_DIP_MAX_SPEED_SZ)
+#define HNS_ROCE_DIP_G_SZ sizeof(u8)
+#define HNS_ROCE_DIP_G_MAX 15
+#define HNS_ROCE_DIP_AL_OFS (HNS_ROCE_DIP_G_OFS + HNS_ROCE_DIP_G_SZ)
+#define HNS_ROCE_DIP_AL_SZ sizeof(u8)
+#define HNS_ROCE_DIP_AL_MAX ((u8)(~0U))
+#define HNS_ROCE_DIP_CNP_TIME_OFS (HNS_ROCE_DIP_AL_OFS + HNS_ROCE_DIP_AL_SZ)
+#define HNS_ROCE_DIP_CNP_TIME_SZ sizeof(u8)
+#define HNS_ROCE_DIP_CNP_TIME_MAX ((u8)(~0U))
+#define HNS_ROCE_DIP_ASHIFT_OFS (HNS_ROCE_DIP_CNP_TIME_OFS + \
+					HNS_ROCE_DIP_CNP_TIME_SZ)
+#define HNS_ROCE_DIP_ASHIFT_SZ sizeof(u8)
+#define HNS_ROCE_DIP_ASHIFT_MAX 15
+#define HNS_ROCE_DIP_LIFESPAN_OFS (HNS_ROCE_DIP_ASHIFT_OFS + \
+					HNS_ROCE_DIP_ASHIFT_SZ)
+#define HNS_ROCE_DIP_LIFESPAN_SZ sizeof(u32)
+#define HNS_ROCE_DIP_LIFESPAN_MAX 1000
+
 struct hns_roce_sccc_clr {
 	__le32 qpn;
 	__le32 rsv[5];
diff --git a/drivers/infiniband/hw/hns/hns_roce_main.c b/drivers/infiniband/hw/hns/hns_roce_main.c
index 1dc60c2b2b7a..f0d7bf3f0d67 100644
--- a/drivers/infiniband/hw/hns/hns_roce_main.c
+++ b/drivers/infiniband/hw/hns/hns_roce_main.c
@@ -636,6 +636,7 @@ static const struct ib_device_ops hns_roce_dev_ops = {
 	.query_pkey = hns_roce_query_pkey,
 	.query_port = hns_roce_query_port,
 	.reg_user_mr = hns_roce_reg_user_mr,
+	.port_groups = hns_attr_port_groups,
 
 	INIT_RDMA_OBJ_SIZE(ib_ah, hns_roce_ah, ibah),
 	INIT_RDMA_OBJ_SIZE(ib_cq, hns_roce_cq, ib_cq),
@@ -1110,6 +1111,7 @@ int hns_roce_init(struct hns_roce_dev *hr_dev)
 		goto error_failed_register_device;
 
 	hns_roce_register_debugfs(hr_dev);
+	hns_roce_register_sysfs(hr_dev);
 
 	return 0;
 
@@ -1143,6 +1145,7 @@ int hns_roce_init(struct hns_roce_dev *hr_dev)
 
 void hns_roce_exit(struct hns_roce_dev *hr_dev)
 {
+	hns_roce_unregister_sysfs(hr_dev);
 	hns_roce_unregister_debugfs(hr_dev);
 	hns_roce_unregister_device(hr_dev);
 
diff --git a/drivers/infiniband/hw/hns/hns_roce_sysfs.c b/drivers/infiniband/hw/hns/hns_roce_sysfs.c
new file mode 100644
index 000000000000..4600144dceff
--- /dev/null
+++ b/drivers/infiniband/hw/hns/hns_roce_sysfs.c
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * Copyright (c) 2024 Hisilicon Limited.
+ */
+
+#include <rdma/ib_sysfs.h>
+
+#include "hnae3.h"
+#include "hns_roce_device.h"
+#include "hns_roce_hw_v2.h"
+
+static void scc_param_config_work(struct work_struct *work)
+{
+	struct hns_roce_scc_param *scc_param = container_of(work,
+			struct hns_roce_scc_param, scc_cfg_dwork.work);
+	struct hns_roce_dev *hr_dev = scc_param->hr_dev;
+
+	hr_dev->hw->config_scc_param(hr_dev, scc_param->algo_type);
+}
+
+static void get_default_scc_param(struct hns_roce_dev *hr_dev)
+{
+	int ret;
+	int i;
+
+	for (i = 0; i < CONG_TYPE_TOTAL; i++) {
+		hr_dev->scc_param[i].timestamp = jiffies;
+		ret = hr_dev->hw->query_scc_param(hr_dev, i);
+		if (ret && ret != -EOPNOTSUPP)
+			ibdev_warn_ratelimited(&hr_dev->ib_dev,
+					       "failed to get default parameters of scc algo %d, ret = %d.\n",
+					       i, ret);
+	}
+}
+
+static int alloc_scc_param(struct hns_roce_dev *hr_dev)
+{
+	struct hns_roce_scc_param *scc_param;
+	int i;
+
+	scc_param = kvcalloc(CONG_TYPE_TOTAL, sizeof(*scc_param),
+			     GFP_KERNEL);
+	if (!scc_param)
+		return -ENOMEM;
+
+	for (i = 0; i < CONG_TYPE_TOTAL; i++) {
+		scc_param[i].algo_type = i;
+		scc_param[i].hr_dev = hr_dev;
+		INIT_DELAYED_WORK(&scc_param[i].scc_cfg_dwork,
+				  scc_param_config_work);
+	}
+
+	hr_dev->scc_param = scc_param;
+
+	get_default_scc_param(hr_dev);
+
+	return 0;
+}
+
+struct hns_port_cc_attr {
+	struct ib_port_attribute port_attr;
+	enum hns_roce_cong_type algo_type;
+	u32 offset;
+	u32 size;
+	u32 max;
+	u32 min;
+};
+
+static int scc_attr_check(struct hns_roce_dev *hr_dev,
+			  struct hns_port_cc_attr *scc_attr, u32 port_num)
+{
+	if (port_num > hr_dev->caps.num_ports)
+		return -ENODEV;
+
+	if (WARN_ON(scc_attr->size > sizeof(u32)))
+		return -EINVAL;
+
+	if (WARN_ON(scc_attr->algo_type >= CONG_TYPE_TOTAL))
+		return -EINVAL;
+
+	return 0;
+}
+
+static ssize_t scc_attr_show(struct ib_device *ibdev, u32 port_num,
+			     struct ib_port_attribute *attr, char *buf)
+{
+	struct hns_port_cc_attr *scc_attr =
+		container_of(attr, struct hns_port_cc_attr, port_attr);
+	struct hns_roce_dev *hr_dev = to_hr_dev(ibdev);
+	struct hns_roce_scc_param *scc_param;
+	__le32 val = 0;
+	int ret;
+
+	ret = scc_attr_check(hr_dev, scc_attr, port_num);
+	if (ret)
+		return ret;
+
+	scc_param = &hr_dev->scc_param[scc_attr->algo_type];
+
+	memcpy(&val, (void *)scc_param + scc_attr->offset, scc_attr->size);
+
+	return sysfs_emit(buf, "%u\n", le32_to_cpu(val));
+}
+
+static ssize_t scc_attr_store(struct ib_device *ibdev, u32 port_num,
+			      struct ib_port_attribute *attr, const char *buf,
+			      size_t count)
+{
+	struct hns_port_cc_attr *scc_attr =
+		container_of(attr, struct hns_port_cc_attr, port_attr);
+	struct hns_roce_dev *hr_dev = to_hr_dev(ibdev);
+	struct hns_roce_scc_param *scc_param;
+	unsigned long lifespan_jiffies;
+	unsigned long exp_time;
+	__le32 attr_val;
+	u32 val;
+	int ret;
+
+	ret = scc_attr_check(hr_dev, scc_attr, port_num);
+	if (ret)
+		return ret;
+
+	if (kstrtou32(buf, 0, &val))
+		return -EINVAL;
+
+	if (val > scc_attr->max || val < scc_attr->min)
+		return -EINVAL;
+
+	attr_val = cpu_to_le32(val);
+	scc_param = &hr_dev->scc_param[scc_attr->algo_type];
+	memcpy((void *)scc_param + scc_attr->offset, &attr_val,
+	       scc_attr->size);
+
+	/* lifespan is only used for driver */
+	if (scc_attr->offset >= offsetof(typeof(*scc_param), lifespan))
+		return count;
+
+	lifespan_jiffies = msecs_to_jiffies(scc_param->lifespan);
+	exp_time = scc_param->timestamp + lifespan_jiffies;
+
+	if (time_is_before_eq_jiffies(exp_time)) {
+		scc_param->timestamp = jiffies;
+		queue_delayed_work(hr_dev->irq_workq, &scc_param->scc_cfg_dwork,
+				   lifespan_jiffies);
+	}
+
+	return count;
+}
+
+static umode_t scc_attr_is_visible(struct kobject *kobj,
+				   struct attribute *attr, int i)
+{
+	struct ib_port_attribute *port_attr =
+		container_of(attr, struct ib_port_attribute, attr);
+	struct hns_port_cc_attr *scc_attr =
+		container_of(port_attr, struct hns_port_cc_attr, port_attr);
+	u32 port_num;
+	struct ib_device *ibdev = ib_port_sysfs_get_ibdev_kobj(kobj, &port_num);
+	struct hns_roce_dev *hr_dev = to_hr_dev(ibdev);
+
+	if (hr_dev->is_vf ||
+	    !(hr_dev->caps.flags & HNS_ROCE_CAP_FLAG_QP_FLOW_CTRL))
+		return 0;
+
+	if (!(hr_dev->caps.cong_cap & (1 << scc_attr->algo_type)))
+		return 0;
+
+	return 0644;
+}
+
+#define __HNS_SCC_ATTR(_name, _type, _offset, _size, _min, _max) {		\
+	.port_attr = __ATTR(_name, 0644, scc_attr_show,  scc_attr_store),	\
+	.algo_type = _type,							\
+	.offset = _offset,							\
+	.size = _size,								\
+	.min = _min,								\
+	.max = _max,								\
+}
+
+#define HNS_PORT_DCQCN_CC_ATTR_RW(_name, NAME)				\
+	struct hns_port_cc_attr hns_roce_port_attr_dcqcn_##_name =	\
+	__HNS_SCC_ATTR(_name, CONG_TYPE_DCQCN,				\
+			HNS_ROCE_DCQCN_##NAME##_OFS,			\
+			HNS_ROCE_DCQCN_##NAME##_SZ,			\
+			0, HNS_ROCE_DCQCN_##NAME##_MAX)
+
+HNS_PORT_DCQCN_CC_ATTR_RW(ai, AI);
+HNS_PORT_DCQCN_CC_ATTR_RW(f, F);
+HNS_PORT_DCQCN_CC_ATTR_RW(tkp, TKP);
+HNS_PORT_DCQCN_CC_ATTR_RW(tmp, TMP);
+HNS_PORT_DCQCN_CC_ATTR_RW(alp, ALP);
+HNS_PORT_DCQCN_CC_ATTR_RW(max_speed, MAX_SPEED);
+HNS_PORT_DCQCN_CC_ATTR_RW(g, G);
+HNS_PORT_DCQCN_CC_ATTR_RW(al, AL);
+HNS_PORT_DCQCN_CC_ATTR_RW(cnp_time, CNP_TIME);
+HNS_PORT_DCQCN_CC_ATTR_RW(ashift, ASHIFT);
+HNS_PORT_DCQCN_CC_ATTR_RW(lifespan, LIFESPAN);
+
+static struct attribute *dcqcn_param_attrs[] = {
+	&hns_roce_port_attr_dcqcn_ai.port_attr.attr,
+	&hns_roce_port_attr_dcqcn_f.port_attr.attr,
+	&hns_roce_port_attr_dcqcn_tkp.port_attr.attr,
+	&hns_roce_port_attr_dcqcn_tmp.port_attr.attr,
+	&hns_roce_port_attr_dcqcn_alp.port_attr.attr,
+	&hns_roce_port_attr_dcqcn_max_speed.port_attr.attr,
+	&hns_roce_port_attr_dcqcn_g.port_attr.attr,
+	&hns_roce_port_attr_dcqcn_al.port_attr.attr,
+	&hns_roce_port_attr_dcqcn_cnp_time.port_attr.attr,
+	&hns_roce_port_attr_dcqcn_ashift.port_attr.attr,
+	&hns_roce_port_attr_dcqcn_lifespan.port_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group dcqcn_cc_param_group = {
+	.name = "dcqcn_cc_param",
+	.attrs = dcqcn_param_attrs,
+	.is_visible = scc_attr_is_visible,
+};
+
+#define HNS_PORT_LDCP_CC_ATTR_RW(_name, NAME)				\
+	struct hns_port_cc_attr hns_roce_port_attr_ldcp_##_name =	\
+	__HNS_SCC_ATTR(_name, CONG_TYPE_LDCP,				\
+			HNS_ROCE_LDCP_##NAME##_OFS,			\
+			HNS_ROCE_LDCP_##NAME##_SZ,			\
+			0, HNS_ROCE_LDCP_##NAME##_MAX)
+
+HNS_PORT_LDCP_CC_ATTR_RW(cwd0, CWD0);
+HNS_PORT_LDCP_CC_ATTR_RW(alpha, ALPHA);
+HNS_PORT_LDCP_CC_ATTR_RW(gamma, GAMMA);
+HNS_PORT_LDCP_CC_ATTR_RW(beta, BETA);
+HNS_PORT_LDCP_CC_ATTR_RW(eta, ETA);
+HNS_PORT_LDCP_CC_ATTR_RW(lifespan, LIFESPAN);
+
+static struct attribute *ldcp_param_attrs[] = {
+	&hns_roce_port_attr_ldcp_cwd0.port_attr.attr,
+	&hns_roce_port_attr_ldcp_alpha.port_attr.attr,
+	&hns_roce_port_attr_ldcp_gamma.port_attr.attr,
+	&hns_roce_port_attr_ldcp_beta.port_attr.attr,
+	&hns_roce_port_attr_ldcp_eta.port_attr.attr,
+	&hns_roce_port_attr_ldcp_lifespan.port_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group ldcp_cc_param_group = {
+	.name = "ldcp_cc_param",
+	.attrs = ldcp_param_attrs,
+	.is_visible = scc_attr_is_visible,
+};
+
+#define HNS_PORT_HC3_CC_ATTR_RW(_name, NAME)				\
+	struct hns_port_cc_attr hns_roce_port_attr_hc3_##_name =	\
+	__HNS_SCC_ATTR(_name, CONG_TYPE_HC3,				\
+			HNS_ROCE_HC3_##NAME##_OFS,			\
+			HNS_ROCE_HC3_##NAME##_SZ,			\
+			0, HNS_ROCE_HC3_##NAME##_MAX)
+
+HNS_PORT_HC3_CC_ATTR_RW(initial_window, INITIAL_WINDOW);
+HNS_PORT_HC3_CC_ATTR_RW(bandwidth, BANDWIDTH);
+HNS_PORT_HC3_CC_ATTR_RW(qlen_shift, QLEN_SHIFT);
+HNS_PORT_HC3_CC_ATTR_RW(port_usage_shift, PORT_USAGE_SHIFT);
+HNS_PORT_HC3_CC_ATTR_RW(over_period, OVER_PERIOD);
+HNS_PORT_HC3_CC_ATTR_RW(max_stage, MAX_STAGE);
+HNS_PORT_HC3_CC_ATTR_RW(gamma_shift, GAMMA_SHIFT);
+HNS_PORT_HC3_CC_ATTR_RW(lifespan, LIFESPAN);
+
+static struct attribute *hc3_param_attrs[] = {
+	&hns_roce_port_attr_hc3_initial_window.port_attr.attr,
+	&hns_roce_port_attr_hc3_bandwidth.port_attr.attr,
+	&hns_roce_port_attr_hc3_qlen_shift.port_attr.attr,
+	&hns_roce_port_attr_hc3_port_usage_shift.port_attr.attr,
+	&hns_roce_port_attr_hc3_over_period.port_attr.attr,
+	&hns_roce_port_attr_hc3_max_stage.port_attr.attr,
+	&hns_roce_port_attr_hc3_gamma_shift.port_attr.attr,
+	&hns_roce_port_attr_hc3_lifespan.port_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group hc3_cc_param_group = {
+	.name = "hc3_cc_param",
+	.attrs = hc3_param_attrs,
+	.is_visible = scc_attr_is_visible,
+};
+
+#define HNS_PORT_DIP_CC_ATTR_RW(_name, NAME)				\
+	struct hns_port_cc_attr hns_roce_port_attr_dip_##_name =	\
+	__HNS_SCC_ATTR(_name, CONG_TYPE_DIP,				\
+			HNS_ROCE_DIP_##NAME##_OFS,			\
+			HNS_ROCE_DIP_##NAME##_SZ,			\
+			0, HNS_ROCE_DIP_##NAME##_MAX)
+
+HNS_PORT_DIP_CC_ATTR_RW(ai, AI);
+HNS_PORT_DIP_CC_ATTR_RW(f, F);
+HNS_PORT_DIP_CC_ATTR_RW(tkp, TKP);
+HNS_PORT_DIP_CC_ATTR_RW(tmp, TMP);
+HNS_PORT_DIP_CC_ATTR_RW(alp, ALP);
+HNS_PORT_DIP_CC_ATTR_RW(max_speed, MAX_SPEED);
+HNS_PORT_DIP_CC_ATTR_RW(g, G);
+HNS_PORT_DIP_CC_ATTR_RW(al, AL);
+HNS_PORT_DIP_CC_ATTR_RW(cnp_time, CNP_TIME);
+HNS_PORT_DIP_CC_ATTR_RW(ashift, ASHIFT);
+HNS_PORT_DIP_CC_ATTR_RW(lifespan, LIFESPAN);
+
+static struct attribute *dip_param_attrs[] = {
+	&hns_roce_port_attr_dip_ai.port_attr.attr,
+	&hns_roce_port_attr_dip_f.port_attr.attr,
+	&hns_roce_port_attr_dip_tkp.port_attr.attr,
+	&hns_roce_port_attr_dip_tmp.port_attr.attr,
+	&hns_roce_port_attr_dip_alp.port_attr.attr,
+	&hns_roce_port_attr_dip_max_speed.port_attr.attr,
+	&hns_roce_port_attr_dip_g.port_attr.attr,
+	&hns_roce_port_attr_dip_al.port_attr.attr,
+	&hns_roce_port_attr_dip_cnp_time.port_attr.attr,
+	&hns_roce_port_attr_dip_ashift.port_attr.attr,
+	&hns_roce_port_attr_dip_lifespan.port_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group dip_cc_param_group = {
+	.name = "dip_cc_param",
+	.attrs = dip_param_attrs,
+	.is_visible = scc_attr_is_visible,
+};
+
+const struct attribute_group *hns_attr_port_groups[] = {
+	&dcqcn_cc_param_group,
+	&ldcp_cc_param_group,
+	&hc3_cc_param_group,
+	&dip_cc_param_group,
+	NULL,
+};
+
+void hns_roce_register_sysfs(struct hns_roce_dev *hr_dev)
+{
+	int ret;
+
+	ret = alloc_scc_param(hr_dev);
+	if (ret)
+		dev_err(hr_dev->dev, "alloc scc param failed, ret = %d!\n",
+			ret);
+}
+
+void hns_roce_unregister_sysfs(struct hns_roce_dev *hr_dev)
+{
+	if (hr_dev->scc_param)
+		kvfree(hr_dev->scc_param);
+}
