Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Huawei driver configuration
#
config HINIC3
tristate "Huawei 3rd generation network adapters (HINIC3) support"
# Fields of HW and management structures are little endian and are
# currently not converted
depends on !CPU_BIG_ENDIAN
depends on X86 || ARM64 || COMPILE_TEST
depends on PCI_MSI && 64BIT
select AUXILIARY_BUS
select DIMLIB
select PAGE_POOL
help
This driver supports HiNIC 3rd gen Network Adapter (HINIC3).
The driver is supported on X86_64 and ARM64 little endian.
To compile this driver as a module, choose M here.
The module will be called hinic3.
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
obj-$(CONFIG_HINIC3) += hinic3.o
hinic3-objs := hinic3_cmdq.o \
hinic3_common.o \
hinic3_eqs.o \
hinic3_ethtool.o \
hinic3_filter.o \
hinic3_hw_cfg.o \
hinic3_hw_comm.o \
hinic3_hwdev.o \
hinic3_hwif.o \
hinic3_irq.o \
hinic3_lld.o \
hinic3_main.o \
hinic3_mbox.o \
hinic3_mgmt.o \
hinic3_netdev_ops.o \
hinic3_nic_cfg.o \
hinic3_nic_io.o \
hinic3_queue_common.o \
hinic3_rss.o \
hinic3_rx.o \
hinic3_tx.o \
hinic3_wq.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,171 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_CMDQ_H_
#define _HINIC3_CMDQ_H_
#include <linux/dmapool.h>
#include "hinic3_hw_intf.h"
#include "hinic3_wq.h"
#define CMDQ_DEPTH 4096
struct cmdq_db {
__le32 db_head;
__le32 db_info;
};
/* hw defined cmdq wqe header */
struct cmdq_header {
__le32 header_info;
__le32 saved_data;
};
struct cmdq_lcmd_bufdesc {
struct hinic3_sge sge;
__le64 rsvd2;
__le64 rsvd3;
};
struct cmdq_status {
__le32 status_info;
};
struct cmdq_ctrl {
__le32 ctrl_info;
};
struct cmdq_direct_resp {
__le64 val;
__le64 rsvd;
};
struct cmdq_completion {
union {
struct hinic3_sge sge;
struct cmdq_direct_resp direct;
} resp;
};
struct cmdq_wqe_scmd {
struct cmdq_header header;
__le64 rsvd3;
struct cmdq_status status;
struct cmdq_ctrl ctrl;
struct cmdq_completion completion;
__le32 rsvd10[6];
};
struct cmdq_wqe_lcmd {
struct cmdq_header header;
struct cmdq_status status;
struct cmdq_ctrl ctrl;
struct cmdq_completion completion;
struct cmdq_lcmd_bufdesc buf_desc;
};
struct cmdq_wqe {
union {
struct cmdq_wqe_scmd wqe_scmd;
struct cmdq_wqe_lcmd wqe_lcmd;
};
};
static_assert(sizeof(struct cmdq_wqe) == 64);
enum hinic3_cmdq_type {
HINIC3_CMDQ_SYNC = 0,
HINIC3_MAX_CMDQ_TYPES = 4
};
enum hinic3_cmdq_status {
HINIC3_CMDQ_ENABLE = BIT(0),
};
enum hinic3_cmdq_cmd_type {
HINIC3_CMD_TYPE_NONE,
HINIC3_CMD_TYPE_SET_ARM,
HINIC3_CMD_TYPE_DIRECT_RESP,
HINIC3_CMD_TYPE_SGE_RESP,
HINIC3_CMD_TYPE_FAKE_TIMEOUT,
HINIC3_CMD_TYPE_TIMEOUT,
HINIC3_CMD_TYPE_FORCE_STOP,
};
struct hinic3_cmd_buf {
void *buf;
dma_addr_t dma_addr;
__le16 size;
refcount_t ref_cnt;
};
struct hinic3_cmd_buf_pair {
struct hinic3_cmd_buf *in;
struct hinic3_cmd_buf *out;
};
struct hinic3_cmdq_cmd_info {
enum hinic3_cmdq_cmd_type cmd_type;
struct completion *done;
int *errcode;
/* completion code */
int *cmpt_code;
__le64 *direct_resp;
u64 cmdq_msg_id;
struct hinic3_cmd_buf *buf_in;
struct hinic3_cmd_buf *buf_out;
};
struct hinic3_cmdq {
struct hinic3_wq wq;
enum hinic3_cmdq_type cmdq_type;
u8 wrapped;
/* synchronize command submission with completions via event queue */
spinlock_t cmdq_lock;
struct hinic3_cmdq_cmd_info *cmd_infos;
struct hinic3_hwdev *hwdev;
};
struct hinic3_cmdqs {
struct hinic3_hwdev *hwdev;
struct hinic3_cmdq cmdq[HINIC3_MAX_CMDQ_TYPES];
struct dma_pool *cmd_buf_pool;
/* doorbell area */
u8 __iomem *cmdqs_db_base;
/* When command queue uses multiple memory pages (1-level CLA), this
* block will hold aggregated indirection table for all command queues
* of cmdqs. Not used for small cmdq (0-level CLA).
*/
dma_addr_t wq_block_paddr;
void *wq_block_vaddr;
u32 status;
u32 disable_flag;
u8 cmdq_num;
};
int hinic3_cmdqs_init(struct hinic3_hwdev *hwdev);
void hinic3_cmdqs_free(struct hinic3_hwdev *hwdev);
struct hinic3_cmd_buf *hinic3_alloc_cmd_buf(struct hinic3_hwdev *hwdev);
void hinic3_free_cmd_buf(struct hinic3_hwdev *hwdev,
struct hinic3_cmd_buf *cmd_buf);
void hinic3_cmdq_ceq_handler(struct hinic3_hwdev *hwdev, __le32 ceqe_data);
int hinic3_cmd_buf_pair_init(struct hinic3_hwdev *hwdev,
struct hinic3_cmd_buf_pair *pair);
void hinic3_cmd_buf_pair_uninit(struct hinic3_hwdev *hwdev,
struct hinic3_cmd_buf_pair *pair);
int hinic3_cmdq_direct_resp(struct hinic3_hwdev *hwdev, u8 mod, u8 cmd,
struct hinic3_cmd_buf *buf_in, __le64 *out_param);
int hinic3_cmdq_detail_resp(struct hinic3_hwdev *hwdev, u8 mod, u8 cmd,
struct hinic3_cmd_buf *buf_in,
struct hinic3_cmd_buf *buf_out, __le64 *out_param);
void hinic3_cmdq_flush_sync_cmd(struct hinic3_hwdev *hwdev);
int hinic3_reinit_cmdq_ctxts(struct hinic3_hwdev *hwdev);
bool hinic3_cmdq_idle(struct hinic3_cmdq *cmdq);
#endif
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/iopoll.h>
#include "hinic3_common.h"
int hinic3_dma_zalloc_coherent_align(struct device *dev, u32 size, u32 align,
gfp_t flag,
struct hinic3_dma_addr_align *mem_align)
{
dma_addr_t paddr, align_paddr;
void *vaddr, *align_vaddr;
u32 real_size = size;
vaddr = dma_alloc_coherent(dev, real_size, &paddr, flag);
if (!vaddr)
return -ENOMEM;
align_paddr = ALIGN(paddr, align);
if (align_paddr == paddr) {
align_vaddr = vaddr;
goto out;
}
dma_free_coherent(dev, real_size, vaddr, paddr);
/* realloc memory for align */
real_size = size + align;
vaddr = dma_alloc_coherent(dev, real_size, &paddr, flag);
if (!vaddr)
return -ENOMEM;
align_paddr = ALIGN(paddr, align);
align_vaddr = vaddr + (align_paddr - paddr);
out:
mem_align->real_size = real_size;
mem_align->ori_vaddr = vaddr;
mem_align->ori_paddr = paddr;
mem_align->align_vaddr = align_vaddr;
mem_align->align_paddr = align_paddr;
return 0;
}
void hinic3_dma_free_coherent_align(struct device *dev,
struct hinic3_dma_addr_align *mem_align)
{
dma_free_coherent(dev, mem_align->real_size,
mem_align->ori_vaddr, mem_align->ori_paddr);
}
int hinic3_wait_for_timeout(void *priv_data, wait_cpl_handler handler,
u32 wait_total_ms, u32 wait_once_us)
{
enum hinic3_wait_return ret;
int err;
err = read_poll_timeout(handler, ret,
!(ret & HINIC3_WAIT_PROCESS_WAITING),
wait_once_us, wait_total_ms * USEC_PER_MSEC,
false, priv_data);
if (ret == HINIC3_WAIT_PROCESS_ERR)
return -EIO;
return err;
}
/* Data provided to/by cmdq is arranged in structs with little endian fields but
* every dword (32bits) should be swapped since HW swaps it again when it
* copies it from/to host memory.
*/
void hinic3_cmdq_buf_swab32(void *data, int len)
{
swab32_array(data, len / sizeof(u32));
}
@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_COMMON_H_
#define _HINIC3_COMMON_H_
#include <linux/device.h>
#define HINIC3_MIN_PAGE_SIZE 0x1000
struct hinic3_dma_addr_align {
u32 real_size;
void *ori_vaddr;
dma_addr_t ori_paddr;
void *align_vaddr;
dma_addr_t align_paddr;
};
enum hinic3_wait_return {
HINIC3_WAIT_PROCESS_CPL = 0,
HINIC3_WAIT_PROCESS_WAITING = 1,
HINIC3_WAIT_PROCESS_ERR = 2,
};
struct hinic3_sge {
__le32 hi_addr;
__le32 lo_addr;
__le32 len;
__le32 rsvd;
};
static inline void hinic3_set_sge(struct hinic3_sge *sge, dma_addr_t addr,
__le32 len)
{
sge->hi_addr = cpu_to_le32(upper_32_bits(addr));
sge->lo_addr = cpu_to_le32(lower_32_bits(addr));
sge->len = len;
sge->rsvd = 0;
}
int hinic3_dma_zalloc_coherent_align(struct device *dev, u32 size, u32 align,
gfp_t flag,
struct hinic3_dma_addr_align *mem_align);
void hinic3_dma_free_coherent_align(struct device *dev,
struct hinic3_dma_addr_align *mem_align);
typedef enum hinic3_wait_return (*wait_cpl_handler)(void *priv_data);
int hinic3_wait_for_timeout(void *priv_data, wait_cpl_handler handler,
u32 wait_total_ms, u32 wait_once_us);
void hinic3_cmdq_buf_swab32(void *data, int len);
#endif
@@ -0,0 +1,87 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_CSR_H_
#define _HINIC3_CSR_H_
#define HINIC3_CFG_REGS_FLAG 0x40000000
#define HINIC3_MGMT_REGS_FLAG 0xC0000000
#define HINIC3_REGS_FLAG_MASK 0x3FFFFFFF
#define HINIC3_VF_CFG_REG_OFFSET 0x2000
/* HW interface registers */
#define HINIC3_CSR_FUNC_ATTR0_ADDR (HINIC3_CFG_REGS_FLAG + 0x0)
#define HINIC3_CSR_FUNC_ATTR1_ADDR (HINIC3_CFG_REGS_FLAG + 0x4)
#define HINIC3_CSR_FUNC_ATTR2_ADDR (HINIC3_CFG_REGS_FLAG + 0x8)
#define HINIC3_CSR_FUNC_ATTR3_ADDR (HINIC3_CFG_REGS_FLAG + 0xC)
#define HINIC3_CSR_FUNC_ATTR4_ADDR (HINIC3_CFG_REGS_FLAG + 0x10)
#define HINIC3_CSR_FUNC_ATTR5_ADDR (HINIC3_CFG_REGS_FLAG + 0x14)
#define HINIC3_CSR_FUNC_ATTR6_ADDR (HINIC3_CFG_REGS_FLAG + 0x18)
#define HINIC3_FUNC_CSR_MAILBOX_DATA_OFF 0x80
#define HINIC3_FUNC_CSR_MAILBOX_CONTROL_OFF (HINIC3_CFG_REGS_FLAG + 0x0100)
#define HINIC3_FUNC_CSR_MAILBOX_INT_OFF (HINIC3_CFG_REGS_FLAG + 0x0104)
#define HINIC3_FUNC_CSR_MAILBOX_RESULT_H_OFF (HINIC3_CFG_REGS_FLAG + 0x0108)
#define HINIC3_FUNC_CSR_MAILBOX_RESULT_L_OFF (HINIC3_CFG_REGS_FLAG + 0x010C)
#define HINIC3_HOST_CSR_BASE_ADDR (HINIC3_MGMT_REGS_FLAG + 0x6000)
#define HINIC3_PPF_ELECTION_OFFSET 0x0
#define HINIC3_CSR_PPF_ELECTION_ADDR \
(HINIC3_HOST_CSR_BASE_ADDR + HINIC3_PPF_ELECTION_OFFSET)
#define HINIC3_CSR_DMA_ATTR_TBL_ADDR (HINIC3_CFG_REGS_FLAG + 0x380)
#define HINIC3_CSR_DMA_ATTR_INDIR_IDX_ADDR (HINIC3_CFG_REGS_FLAG + 0x390)
/* MSI-X registers */
#define HINIC3_CSR_FUNC_MSI_CLR_WR_ADDR (HINIC3_CFG_REGS_FLAG + 0x58)
#define HINIC3_MSI_CLR_INDIR_RESEND_TIMER_CLR_MASK BIT(0)
#define HINIC3_MSI_CLR_INDIR_INT_MSK_SET_MASK BIT(1)
#define HINIC3_MSI_CLR_INDIR_INT_MSK_CLR_MASK BIT(2)
#define HINIC3_MSI_CLR_INDIR_AUTO_MSK_SET_MASK BIT(3)
#define HINIC3_MSI_CLR_INDIR_AUTO_MSK_CLR_MASK BIT(4)
#define HINIC3_MSI_CLR_INDIR_SIMPLE_INDIR_IDX_MASK GENMASK(31, 22)
#define HINIC3_MSI_CLR_INDIR_SET(val, member) \
FIELD_PREP(HINIC3_MSI_CLR_INDIR_##member##_MASK, val)
/* EQ registers */
#define HINIC3_AEQ_INDIR_IDX_ADDR (HINIC3_CFG_REGS_FLAG + 0x210)
#define HINIC3_CEQ_INDIR_IDX_ADDR (HINIC3_CFG_REGS_FLAG + 0x290)
#define HINIC3_EQ_INDIR_IDX_ADDR(type) \
((type == HINIC3_AEQ) ? HINIC3_AEQ_INDIR_IDX_ADDR : \
HINIC3_CEQ_INDIR_IDX_ADDR)
#define HINIC3_AEQ_MTT_OFF_BASE_ADDR (HINIC3_CFG_REGS_FLAG + 0x240)
#define HINIC3_CEQ_MTT_OFF_BASE_ADDR (HINIC3_CFG_REGS_FLAG + 0x2C0)
#define HINIC3_CSR_EQ_PAGE_OFF_STRIDE 8
#define HINIC3_AEQ_HI_PHYS_ADDR_REG(pg_num) \
(HINIC3_AEQ_MTT_OFF_BASE_ADDR + (pg_num) * \
HINIC3_CSR_EQ_PAGE_OFF_STRIDE)
#define HINIC3_AEQ_LO_PHYS_ADDR_REG(pg_num) \
(HINIC3_AEQ_MTT_OFF_BASE_ADDR + (pg_num) * \
HINIC3_CSR_EQ_PAGE_OFF_STRIDE + 4)
#define HINIC3_CEQ_HI_PHYS_ADDR_REG(pg_num) \
(HINIC3_CEQ_MTT_OFF_BASE_ADDR + (pg_num) * \
HINIC3_CSR_EQ_PAGE_OFF_STRIDE)
#define HINIC3_CEQ_LO_PHYS_ADDR_REG(pg_num) \
(HINIC3_CEQ_MTT_OFF_BASE_ADDR + (pg_num) * \
HINIC3_CSR_EQ_PAGE_OFF_STRIDE + 4)
#define HINIC3_CSR_AEQ_CTRL_0_ADDR (HINIC3_CFG_REGS_FLAG + 0x200)
#define HINIC3_CSR_AEQ_CTRL_1_ADDR (HINIC3_CFG_REGS_FLAG + 0x204)
#define HINIC3_CSR_AEQ_CONS_IDX_ADDR (HINIC3_CFG_REGS_FLAG + 0x208)
#define HINIC3_CSR_AEQ_PROD_IDX_ADDR (HINIC3_CFG_REGS_FLAG + 0x20C)
#define HINIC3_CSR_AEQ_CI_SIMPLE_INDIR_ADDR (HINIC3_CFG_REGS_FLAG + 0x50)
#define HINIC3_CSR_CEQ_CONS_IDX_ADDR (HINIC3_CFG_REGS_FLAG + 0x288)
#define HINIC3_CSR_CEQ_PROD_IDX_ADDR (HINIC3_CFG_REGS_FLAG + 0x28c)
#define HINIC3_CSR_CEQ_CI_SIMPLE_INDIR_ADDR (HINIC3_CFG_REGS_FLAG + 0x54)
#endif
@@ -0,0 +1,840 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/delay.h>
#include "hinic3_csr.h"
#include "hinic3_eqs.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_mbox.h"
#define AEQ_CTRL_0_INTR_IDX_MASK GENMASK(9, 0)
#define AEQ_CTRL_0_DMA_ATTR_MASK GENMASK(17, 12)
#define AEQ_CTRL_0_PCI_INTF_IDX_MASK GENMASK(22, 20)
#define AEQ_CTRL_0_INTR_MODE_MASK BIT(31)
#define AEQ_CTRL_0_SET(val, member) \
FIELD_PREP(AEQ_CTRL_0_##member##_MASK, val)
#define AEQ_CTRL_1_LEN_MASK GENMASK(20, 0)
#define AEQ_CTRL_1_ELEM_SIZE_MASK GENMASK(25, 24)
#define AEQ_CTRL_1_PAGE_SIZE_MASK GENMASK(31, 28)
#define AEQ_CTRL_1_SET(val, member) \
FIELD_PREP(AEQ_CTRL_1_##member##_MASK, val)
#define CEQ_CTRL_0_INTR_IDX_MASK GENMASK(9, 0)
#define CEQ_CTRL_0_DMA_ATTR_MASK GENMASK(17, 12)
#define CEQ_CTRL_0_LIMIT_KICK_MASK GENMASK(23, 20)
#define CEQ_CTRL_0_PCI_INTF_IDX_MASK GENMASK(25, 24)
#define CEQ_CTRL_0_PAGE_SIZE_MASK GENMASK(30, 27)
#define CEQ_CTRL_0_INTR_MODE_MASK BIT(31)
#define CEQ_CTRL_0_SET(val, member) \
FIELD_PREP(CEQ_CTRL_0_##member##_MASK, val)
#define CEQ_CTRL_1_LEN_MASK GENMASK(19, 0)
#define CEQ_CTRL_1_SET(val, member) \
FIELD_PREP(CEQ_CTRL_1_##member##_MASK, val)
#define CEQE_TYPE_MASK GENMASK(25, 23)
#define CEQE_TYPE(type) \
FIELD_GET(CEQE_TYPE_MASK, le32_to_cpu(type))
#define CEQE_DATA_MASK GENMASK(25, 0)
#define CEQE_DATA(data) ((data) & cpu_to_le32(CEQE_DATA_MASK))
#define EQ_ELEM_DESC_TYPE_MASK GENMASK(6, 0)
#define EQ_ELEM_DESC_SRC_MASK BIT(7)
#define EQ_ELEM_DESC_SIZE_MASK GENMASK(15, 8)
#define EQ_ELEM_DESC_WRAPPED_MASK BIT(31)
#define EQ_ELEM_DESC_GET(val, member) \
FIELD_GET(EQ_ELEM_DESC_##member##_MASK, le32_to_cpu(val))
#define EQ_CI_SIMPLE_INDIR_CI_MASK GENMASK(20, 0)
#define EQ_CI_SIMPLE_INDIR_ARMED_MASK BIT(21)
#define EQ_CI_SIMPLE_INDIR_AEQ_IDX_MASK GENMASK(31, 30)
#define EQ_CI_SIMPLE_INDIR_CEQ_IDX_MASK GENMASK(31, 24)
#define EQ_CI_SIMPLE_INDIR_SET(val, member) \
FIELD_PREP(EQ_CI_SIMPLE_INDIR_##member##_MASK, val)
#define EQ_CONS_IDX_REG_ADDR(eq) \
(((eq)->type == HINIC3_AEQ) ? \
HINIC3_CSR_AEQ_CONS_IDX_ADDR : HINIC3_CSR_CEQ_CONS_IDX_ADDR)
#define EQ_CI_SIMPLE_INDIR_REG_ADDR(eq) \
(((eq)->type == HINIC3_AEQ) ? \
HINIC3_CSR_AEQ_CI_SIMPLE_INDIR_ADDR : \
HINIC3_CSR_CEQ_CI_SIMPLE_INDIR_ADDR)
#define EQ_PROD_IDX_REG_ADDR(eq) \
(((eq)->type == HINIC3_AEQ) ? \
HINIC3_CSR_AEQ_PROD_IDX_ADDR : HINIC3_CSR_CEQ_PROD_IDX_ADDR)
#define EQ_HI_PHYS_ADDR_REG(type, pg_num) \
(((type) == HINIC3_AEQ) ? \
HINIC3_AEQ_HI_PHYS_ADDR_REG(pg_num) : \
HINIC3_CEQ_HI_PHYS_ADDR_REG(pg_num))
#define EQ_LO_PHYS_ADDR_REG(type, pg_num) \
(((type) == HINIC3_AEQ) ? \
HINIC3_AEQ_LO_PHYS_ADDR_REG(pg_num) : \
HINIC3_CEQ_LO_PHYS_ADDR_REG(pg_num))
#define EQ_MSIX_RESEND_TIMER_CLEAR 1
#define HINIC3_EQ_MAX_PAGES(eq) \
((eq)->type == HINIC3_AEQ ? \
HINIC3_AEQ_MAX_PAGES : HINIC3_CEQ_MAX_PAGES)
#define HINIC3_TASK_PROCESS_EQE_LIMIT 1024
#define HINIC3_EQ_UPDATE_CI_STEP 64
#define HINIC3_EQS_WQ_NAME "hinic3_eqs"
#define HINIC3_EQ_VALID_SHIFT 31
#define HINIC3_EQ_WRAPPED(eq) \
((eq)->wrapped << HINIC3_EQ_VALID_SHIFT)
#define HINIC3_EQ_WRAPPED_SHIFT 20
#define HINIC3_EQ_CONS_IDX(eq) \
((eq)->cons_idx | ((eq)->wrapped << HINIC3_EQ_WRAPPED_SHIFT))
static const struct hinic3_aeq_elem *get_curr_aeq_elem(const struct hinic3_eq *eq)
{
return get_q_element(&eq->qpages, eq->cons_idx, NULL);
}
static const __be32 *get_curr_ceq_elem(const struct hinic3_eq *eq)
{
return get_q_element(&eq->qpages, eq->cons_idx, NULL);
}
int hinic3_aeq_register_cb(struct hinic3_hwdev *hwdev,
enum hinic3_aeq_type event,
hinic3_aeq_event_cb hwe_cb)
{
struct hinic3_aeqs *aeqs;
aeqs = hwdev->aeqs;
aeqs->aeq_cb[event] = hwe_cb;
spin_lock_init(&aeqs->aeq_lock);
return 0;
}
void hinic3_aeq_unregister_cb(struct hinic3_hwdev *hwdev,
enum hinic3_aeq_type event)
{
struct hinic3_aeqs *aeqs;
aeqs = hwdev->aeqs;
spin_lock_bh(&aeqs->aeq_lock);
aeqs->aeq_cb[event] = NULL;
spin_unlock_bh(&aeqs->aeq_lock);
}
int hinic3_ceq_register_cb(struct hinic3_hwdev *hwdev,
enum hinic3_ceq_event event,
hinic3_ceq_event_cb callback)
{
struct hinic3_ceqs *ceqs;
ceqs = hwdev->ceqs;
ceqs->ceq_cb[event] = callback;
spin_lock_init(&ceqs->ceq_lock);
return 0;
}
void hinic3_ceq_unregister_cb(struct hinic3_hwdev *hwdev,
enum hinic3_ceq_event event)
{
struct hinic3_ceqs *ceqs;
ceqs = hwdev->ceqs;
spin_lock_bh(&ceqs->ceq_lock);
ceqs->ceq_cb[event] = NULL;
spin_unlock_bh(&ceqs->ceq_lock);
}
/* Set consumer index in the hw. */
static void set_eq_cons_idx(struct hinic3_eq *eq, u32 arm_state)
{
u32 addr = EQ_CI_SIMPLE_INDIR_REG_ADDR(eq);
u32 eq_wrap_ci, val;
eq_wrap_ci = HINIC3_EQ_CONS_IDX(eq);
val = EQ_CI_SIMPLE_INDIR_SET(arm_state, ARMED);
if (eq->type == HINIC3_AEQ) {
val = val |
EQ_CI_SIMPLE_INDIR_SET(eq_wrap_ci, CI) |
EQ_CI_SIMPLE_INDIR_SET(eq->q_id, AEQ_IDX);
} else {
val = val |
EQ_CI_SIMPLE_INDIR_SET(eq_wrap_ci, CI) |
EQ_CI_SIMPLE_INDIR_SET(eq->q_id, CEQ_IDX);
}
hinic3_hwif_write_reg(eq->hwdev->hwif, addr, val);
}
static struct hinic3_ceqs *ceq_to_ceqs(const struct hinic3_eq *eq)
{
return container_of(eq, struct hinic3_ceqs, ceq[eq->q_id]);
}
static void ceq_event_handler(struct hinic3_ceqs *ceqs, __le32 ceqe)
{
enum hinic3_ceq_event event = CEQE_TYPE(ceqe);
struct hinic3_hwdev *hwdev = ceqs->hwdev;
__le32 ceqe_data = CEQE_DATA(ceqe);
if (event >= HINIC3_MAX_CEQ_EVENTS) {
dev_warn(hwdev->dev, "Ceq unknown event:%d, ceqe data: 0x%x\n",
event, ceqe_data);
return;
}
spin_lock_bh(&ceqs->ceq_lock);
if (ceqs->ceq_cb[event])
ceqs->ceq_cb[event](hwdev, ceqe_data);
spin_unlock_bh(&ceqs->ceq_lock);
}
static struct hinic3_aeqs *aeq_to_aeqs(const struct hinic3_eq *eq)
{
return container_of(eq, struct hinic3_aeqs, aeq[eq->q_id]);
}
static void aeq_event_handler(struct hinic3_aeqs *aeqs, __le32 aeqe,
const struct hinic3_aeq_elem *aeqe_pos)
{
struct hinic3_hwdev *hwdev = aeqs->hwdev;
u8 data[HINIC3_AEQE_DATA_SIZE], size;
enum hinic3_aeq_type event;
hinic3_aeq_event_cb hwe_cb;
if (EQ_ELEM_DESC_GET(aeqe, SRC))
return;
event = EQ_ELEM_DESC_GET(aeqe, TYPE);
if (event >= HINIC3_MAX_AEQ_EVENTS) {
dev_warn(hwdev->dev, "Aeq unknown event:%d\n", event);
return;
}
memcpy(data, aeqe_pos->aeqe_data, HINIC3_AEQE_DATA_SIZE);
swab32_array((u32 *)data, HINIC3_AEQE_DATA_SIZE / sizeof(u32));
size = EQ_ELEM_DESC_GET(aeqe, SIZE);
spin_lock_bh(&aeqs->aeq_lock);
hwe_cb = aeqs->aeq_cb[event];
if (hwe_cb)
hwe_cb(aeqs->hwdev, data, size);
spin_unlock_bh(&aeqs->aeq_lock);
}
static int aeq_irq_handler(struct hinic3_eq *eq)
{
const struct hinic3_aeq_elem *aeqe_pos;
struct hinic3_aeqs *aeqs;
u32 i, eqe_cnt = 0;
__le32 aeqe;
aeqs = aeq_to_aeqs(eq);
for (i = 0; i < HINIC3_TASK_PROCESS_EQE_LIMIT; i++) {
aeqe_pos = get_curr_aeq_elem(eq);
aeqe = (__force __le32)swab32((__force __u32)aeqe_pos->desc);
/* HW updates wrapped bit, when it adds eq element event */
if (EQ_ELEM_DESC_GET(aeqe, WRAPPED) == eq->wrapped)
return 0;
/* Prevent speculative reads from element */
dma_rmb();
aeq_event_handler(aeqs, aeqe, aeqe_pos);
eq->cons_idx++;
if (eq->cons_idx == eq->eq_len) {
eq->cons_idx = 0;
eq->wrapped = !eq->wrapped;
}
if (++eqe_cnt >= HINIC3_EQ_UPDATE_CI_STEP) {
eqe_cnt = 0;
set_eq_cons_idx(eq, HINIC3_EQ_NOT_ARMED);
}
}
return -EAGAIN;
}
static int ceq_irq_handler(struct hinic3_eq *eq)
{
struct hinic3_ceqs *ceqs;
u32 eqe_cnt = 0;
__be32 ceqe_raw;
__le32 ceqe;
u32 i;
ceqs = ceq_to_ceqs(eq);
for (i = 0; i < HINIC3_TASK_PROCESS_EQE_LIMIT; i++) {
ceqe_raw = *get_curr_ceq_elem(eq);
ceqe = (__force __le32)swab32((__force __u32)ceqe_raw);
/* HW updates wrapped bit, when it adds eq element event */
if (EQ_ELEM_DESC_GET(ceqe, WRAPPED) == eq->wrapped)
return 0;
ceq_event_handler(ceqs, ceqe);
eq->cons_idx++;
if (eq->cons_idx == eq->eq_len) {
eq->cons_idx = 0;
eq->wrapped = !eq->wrapped;
}
if (++eqe_cnt >= HINIC3_EQ_UPDATE_CI_STEP) {
eqe_cnt = 0;
set_eq_cons_idx(eq, HINIC3_EQ_NOT_ARMED);
}
}
return -EAGAIN;
}
static void reschedule_aeq_handler(struct hinic3_eq *eq)
{
struct hinic3_aeqs *aeqs = aeq_to_aeqs(eq);
queue_work(aeqs->workq, &eq->aeq_work);
}
static int eq_irq_handler(struct hinic3_eq *eq)
{
int err;
if (eq->type == HINIC3_AEQ)
err = aeq_irq_handler(eq);
else
err = ceq_irq_handler(eq);
set_eq_cons_idx(eq, err ? HINIC3_EQ_NOT_ARMED :
HINIC3_EQ_ARMED);
return err;
}
static void aeq_irq_work(struct work_struct *work)
{
struct hinic3_eq *eq = container_of(work, struct hinic3_eq, aeq_work);
int err;
err = eq_irq_handler(eq);
if (err)
reschedule_aeq_handler(eq);
}
static irqreturn_t aeq_interrupt(int irq, void *data)
{
struct workqueue_struct *workq;
struct hinic3_eq *aeq = data;
struct hinic3_hwdev *hwdev;
struct hinic3_aeqs *aeqs;
aeqs = aeq_to_aeqs(aeq);
hwdev = aeq->hwdev;
/* clear resend timer cnt register */
workq = aeqs->workq;
hinic3_msix_intr_clear_resend_bit(hwdev, aeq->msix_entry_idx,
EQ_MSIX_RESEND_TIMER_CLEAR);
queue_work(workq, &aeq->aeq_work);
return IRQ_HANDLED;
}
static irqreturn_t ceq_interrupt(int irq, void *data)
{
struct hinic3_eq *ceq = data;
int err;
ceq->soft_intr_jif = jiffies;
/* clear resend timer counters */
hinic3_msix_intr_clear_resend_bit(ceq->hwdev, ceq->msix_entry_idx,
EQ_MSIX_RESEND_TIMER_CLEAR);
err = eq_irq_handler(ceq);
if (err)
return IRQ_NONE;
return IRQ_HANDLED;
}
static int hinic3_set_ceq_ctrl_reg(struct hinic3_hwdev *hwdev, u16 q_id,
u32 ctrl0, u32 ctrl1)
{
struct comm_cmd_set_ceq_ctrl_reg ceq_ctrl = {};
struct mgmt_msg_params msg_params = {};
int err;
ceq_ctrl.func_id = hinic3_global_func_id(hwdev);
ceq_ctrl.q_id = q_id;
ceq_ctrl.ctrl0 = ctrl0;
ceq_ctrl.ctrl1 = ctrl1;
mgmt_msg_params_init_default(&msg_params, &ceq_ctrl, sizeof(ceq_ctrl));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_SET_CEQ_CTRL_REG, &msg_params);
if (err || ceq_ctrl.head.status) {
dev_err(hwdev->dev, "Failed to set ceq %u ctrl reg, err: %d status: 0x%x\n",
q_id, err, ceq_ctrl.head.status);
return -EFAULT;
}
return 0;
}
static int set_eq_ctrls(struct hinic3_eq *eq)
{
struct hinic3_hwif *hwif = eq->hwdev->hwif;
struct hinic3_queue_pages *qpages;
u8 pci_intf_idx, elem_size;
u32 mask, ctrl0, ctrl1;
u32 page_size_val;
int err;
qpages = &eq->qpages;
page_size_val = ilog2(qpages->page_size / HINIC3_MIN_PAGE_SIZE);
pci_intf_idx = hwif->attr.pci_intf_idx;
if (eq->type == HINIC3_AEQ) {
/* set ctrl0 using read-modify-write */
mask = AEQ_CTRL_0_INTR_IDX_MASK |
AEQ_CTRL_0_DMA_ATTR_MASK |
AEQ_CTRL_0_PCI_INTF_IDX_MASK |
AEQ_CTRL_0_INTR_MODE_MASK;
ctrl0 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_AEQ_CTRL_0_ADDR);
ctrl0 = (ctrl0 & ~mask) |
AEQ_CTRL_0_SET(eq->msix_entry_idx, INTR_IDX) |
AEQ_CTRL_0_SET(0, DMA_ATTR) |
AEQ_CTRL_0_SET(pci_intf_idx, PCI_INTF_IDX) |
AEQ_CTRL_0_SET(HINIC3_INTR_MODE_ARMED, INTR_MODE);
hinic3_hwif_write_reg(hwif, HINIC3_CSR_AEQ_CTRL_0_ADDR, ctrl0);
/* HW expects log2(number of 32 byte units). */
elem_size = qpages->elem_size_shift - 5;
ctrl1 = AEQ_CTRL_1_SET(eq->eq_len, LEN) |
AEQ_CTRL_1_SET(elem_size, ELEM_SIZE) |
AEQ_CTRL_1_SET(page_size_val, PAGE_SIZE);
hinic3_hwif_write_reg(hwif, HINIC3_CSR_AEQ_CTRL_1_ADDR, ctrl1);
} else {
ctrl0 = CEQ_CTRL_0_SET(eq->msix_entry_idx, INTR_IDX) |
CEQ_CTRL_0_SET(0, DMA_ATTR) |
CEQ_CTRL_0_SET(0, LIMIT_KICK) |
CEQ_CTRL_0_SET(pci_intf_idx, PCI_INTF_IDX) |
CEQ_CTRL_0_SET(page_size_val, PAGE_SIZE) |
CEQ_CTRL_0_SET(HINIC3_INTR_MODE_ARMED, INTR_MODE);
ctrl1 = CEQ_CTRL_1_SET(eq->eq_len, LEN);
/* set ceq ctrl reg through mgmt cpu */
err = hinic3_set_ceq_ctrl_reg(eq->hwdev, eq->q_id, ctrl0,
ctrl1);
if (err)
return err;
}
return 0;
}
static void ceq_elements_init(struct hinic3_eq *eq, u32 init_val)
{
__be32 *ceqe;
u32 i;
for (i = 0; i < eq->eq_len; i++) {
ceqe = get_q_element(&eq->qpages, i, NULL);
*ceqe = cpu_to_be32(init_val);
}
wmb(); /* Clear ceq elements bit */
}
static void aeq_elements_init(struct hinic3_eq *eq, u32 init_val)
{
struct hinic3_aeq_elem *aeqe;
u32 i;
for (i = 0; i < eq->eq_len; i++) {
aeqe = get_q_element(&eq->qpages, i, NULL);
aeqe->desc = cpu_to_be32(init_val);
}
wmb(); /* Clear aeq elements bit */
}
static void eq_elements_init(struct hinic3_eq *eq, u32 init_val)
{
if (eq->type == HINIC3_AEQ)
aeq_elements_init(eq, init_val);
else
ceq_elements_init(eq, init_val);
}
static int alloc_eq_pages(struct hinic3_eq *eq)
{
struct hinic3_hwif *hwif = eq->hwdev->hwif;
struct hinic3_queue_pages *qpages;
dma_addr_t page_paddr;
u32 reg, init_val;
u16 pg_idx;
int err;
qpages = &eq->qpages;
err = hinic3_queue_pages_alloc(eq->hwdev, qpages, HINIC3_MIN_PAGE_SIZE);
if (err)
return err;
for (pg_idx = 0; pg_idx < qpages->num_pages; pg_idx++) {
page_paddr = qpages->pages[pg_idx].align_paddr;
reg = EQ_HI_PHYS_ADDR_REG(eq->type, pg_idx);
hinic3_hwif_write_reg(hwif, reg, upper_32_bits(page_paddr));
reg = EQ_LO_PHYS_ADDR_REG(eq->type, pg_idx);
hinic3_hwif_write_reg(hwif, reg, lower_32_bits(page_paddr));
}
init_val = HINIC3_EQ_WRAPPED(eq);
eq_elements_init(eq, init_val);
return 0;
}
static void eq_calc_page_size_and_num(struct hinic3_eq *eq, u32 elem_size)
{
u32 max_pages, min_page_size, page_size, total_size;
/* No need for complicated arithmetic. All values must be power of 2.
* Multiplications give power of 2 and divisions give power of 2 without
* remainder.
*/
max_pages = HINIC3_EQ_MAX_PAGES(eq);
min_page_size = HINIC3_MIN_PAGE_SIZE;
total_size = eq->eq_len * elem_size;
if (total_size <= max_pages * min_page_size)
page_size = min_page_size;
else
page_size = total_size / max_pages;
hinic3_queue_pages_init(&eq->qpages, eq->eq_len, page_size, elem_size);
}
static int request_eq_irq(struct hinic3_eq *eq)
{
int err;
if (eq->type == HINIC3_AEQ) {
INIT_WORK(&eq->aeq_work, aeq_irq_work);
snprintf(eq->irq_name, sizeof(eq->irq_name),
"hinic3_aeq%u@pci:%s", eq->q_id,
pci_name(eq->hwdev->pdev));
err = request_irq(eq->irq_id, aeq_interrupt, 0,
eq->irq_name, eq);
} else {
snprintf(eq->irq_name, sizeof(eq->irq_name),
"hinic3_ceq%u@pci:%s", eq->q_id,
pci_name(eq->hwdev->pdev));
err = request_threaded_irq(eq->irq_id, NULL, ceq_interrupt,
IRQF_ONESHOT, eq->irq_name, eq);
}
return err;
}
static void reset_eq(struct hinic3_eq *eq)
{
/* clear eq_len to force eqe drop in hardware */
if (eq->type == HINIC3_AEQ)
hinic3_hwif_write_reg(eq->hwdev->hwif,
HINIC3_CSR_AEQ_CTRL_1_ADDR, 0);
else
hinic3_set_ceq_ctrl_reg(eq->hwdev, eq->q_id, 0, 0);
hinic3_hwif_write_reg(eq->hwdev->hwif, EQ_PROD_IDX_REG_ADDR(eq), 0);
}
static int init_eq(struct hinic3_eq *eq, struct hinic3_hwdev *hwdev, u16 q_id,
u32 q_len, enum hinic3_eq_type type,
struct msix_entry *msix_entry)
{
u32 elem_size;
int err;
eq->hwdev = hwdev;
eq->q_id = q_id;
eq->type = type;
eq->eq_len = q_len;
/* Indirect access should set q_id first */
hinic3_hwif_write_reg(hwdev->hwif, HINIC3_EQ_INDIR_IDX_ADDR(eq->type),
eq->q_id);
reset_eq(eq);
eq->cons_idx = 0;
eq->wrapped = 0;
elem_size = (type == HINIC3_AEQ) ? HINIC3_AEQE_SIZE : HINIC3_CEQE_SIZE;
eq_calc_page_size_and_num(eq, elem_size);
err = alloc_eq_pages(eq);
if (err) {
dev_err(hwdev->dev, "Failed to allocate pages for eq\n");
return err;
}
eq->msix_entry_idx = msix_entry->entry;
eq->irq_id = msix_entry->vector;
err = set_eq_ctrls(eq);
if (err) {
dev_err(hwdev->dev, "Failed to set ctrls for eq\n");
goto err_free_queue_pages;
}
set_eq_cons_idx(eq, HINIC3_EQ_ARMED);
err = request_eq_irq(eq);
if (err) {
dev_err(hwdev->dev,
"Failed to request irq for the eq, err: %d\n", err);
goto err_free_queue_pages;
}
hinic3_set_msix_state(hwdev, eq->msix_entry_idx, HINIC3_MSIX_DISABLE);
return 0;
err_free_queue_pages:
hinic3_queue_pages_free(hwdev, &eq->qpages);
return err;
}
static void remove_eq(struct hinic3_eq *eq)
{
hinic3_set_msix_state(eq->hwdev, eq->msix_entry_idx,
HINIC3_MSIX_DISABLE);
free_irq(eq->irq_id, eq);
/* Indirect access should set q_id first */
hinic3_hwif_write_reg(eq->hwdev->hwif,
HINIC3_EQ_INDIR_IDX_ADDR(eq->type),
eq->q_id);
if (eq->type == HINIC3_AEQ) {
disable_work_sync(&eq->aeq_work);
/* clear eq_len to avoid hw access host memory */
hinic3_hwif_write_reg(eq->hwdev->hwif,
HINIC3_CSR_AEQ_CTRL_1_ADDR, 0);
} else {
hinic3_set_ceq_ctrl_reg(eq->hwdev, eq->q_id, 0, 0);
}
/* update consumer index to avoid invalid interrupt */
eq->cons_idx = hinic3_hwif_read_reg(eq->hwdev->hwif,
EQ_PROD_IDX_REG_ADDR(eq));
set_eq_cons_idx(eq, HINIC3_EQ_NOT_ARMED);
hinic3_queue_pages_free(eq->hwdev, &eq->qpages);
}
int hinic3_aeqs_init(struct hinic3_hwdev *hwdev, u16 num_aeqs,
struct msix_entry *msix_entries)
{
struct hinic3_aeqs *aeqs;
u16 q_id;
int err;
aeqs = kzalloc_obj(*aeqs);
if (!aeqs)
return -ENOMEM;
hwdev->aeqs = aeqs;
aeqs->hwdev = hwdev;
aeqs->num_aeqs = num_aeqs;
aeqs->workq = alloc_workqueue(HINIC3_EQS_WQ_NAME, WQ_MEM_RECLAIM | WQ_PERCPU,
HINIC3_MAX_AEQS);
if (!aeqs->workq) {
dev_err(hwdev->dev, "Failed to initialize aeq workqueue\n");
err = -ENOMEM;
goto err_free_aeqs;
}
for (q_id = 0; q_id < num_aeqs; q_id++) {
err = init_eq(&aeqs->aeq[q_id], hwdev, q_id,
HINIC3_DEFAULT_AEQ_LEN, HINIC3_AEQ,
&msix_entries[q_id]);
if (err) {
dev_err(hwdev->dev, "Failed to init aeq %u\n",
q_id);
goto err_remove_eqs;
}
}
for (q_id = 0; q_id < num_aeqs; q_id++)
hinic3_set_msix_state(hwdev, aeqs->aeq[q_id].msix_entry_idx,
HINIC3_MSIX_ENABLE);
return 0;
err_remove_eqs:
while (q_id > 0) {
q_id--;
remove_eq(&aeqs->aeq[q_id]);
}
destroy_workqueue(aeqs->workq);
err_free_aeqs:
kfree(aeqs);
return err;
}
void hinic3_aeqs_free(struct hinic3_hwdev *hwdev)
{
struct hinic3_aeqs *aeqs = hwdev->aeqs;
enum hinic3_aeq_type aeq_event;
struct hinic3_eq *eq;
u16 q_id;
for (q_id = 0; q_id < aeqs->num_aeqs; q_id++) {
eq = aeqs->aeq + q_id;
remove_eq(eq);
hinic3_free_irq(hwdev, eq->irq_id);
}
for (aeq_event = 0; aeq_event < HINIC3_MAX_AEQ_EVENTS; aeq_event++)
hinic3_aeq_unregister_cb(hwdev, aeq_event);
destroy_workqueue(aeqs->workq);
kfree(aeqs);
}
void hinic3_dump_aeq_info(struct hinic3_hwdev *hwdev)
{
const struct hinic3_aeq_elem *aeqe_pos;
u32 addr, ci, pi, ctrl0, idx;
struct hinic3_eq *eq;
int q_id;
for (q_id = 0; q_id < hwdev->aeqs->num_aeqs; q_id++) {
eq = &hwdev->aeqs->aeq[q_id];
/* Indirect access should set q_id first */
hinic3_hwif_write_reg(eq->hwdev->hwif,
HINIC3_EQ_INDIR_IDX_ADDR(eq->type),
eq->q_id);
addr = HINIC3_CSR_AEQ_CTRL_0_ADDR;
ctrl0 = hinic3_hwif_read_reg(hwdev->hwif, addr);
idx = hinic3_hwif_read_reg(hwdev->hwif,
HINIC3_EQ_INDIR_IDX_ADDR(eq->type));
addr = EQ_CONS_IDX_REG_ADDR(eq);
ci = hinic3_hwif_read_reg(hwdev->hwif, addr);
addr = EQ_PROD_IDX_REG_ADDR(eq);
pi = hinic3_hwif_read_reg(hwdev->hwif, addr);
aeqe_pos = get_curr_aeq_elem(eq);
dev_err(hwdev->dev,
"Aeq id: %d, idx: %u, ctrl0: 0x%08x, ci: 0x%08x, pi: 0x%x, work_state: 0x%x, wrap: %u, desc: 0x%x swci:0x%x\n",
q_id, idx, ctrl0, ci, pi, work_busy(&eq->aeq_work),
eq->wrapped, be32_to_cpu(aeqe_pos->desc), eq->cons_idx);
}
}
int hinic3_ceqs_init(struct hinic3_hwdev *hwdev, u16 num_ceqs,
struct msix_entry *msix_entries)
{
struct hinic3_ceqs *ceqs;
u16 q_id;
int err;
ceqs = kzalloc_obj(*ceqs);
if (!ceqs)
return -ENOMEM;
hwdev->ceqs = ceqs;
ceqs->hwdev = hwdev;
ceqs->num_ceqs = num_ceqs;
for (q_id = 0; q_id < num_ceqs; q_id++) {
err = init_eq(&ceqs->ceq[q_id], hwdev, q_id,
HINIC3_DEFAULT_CEQ_LEN, HINIC3_CEQ,
&msix_entries[q_id]);
if (err) {
dev_err(hwdev->dev, "Failed to init ceq %u\n",
q_id);
goto err_free_ceqs;
}
}
for (q_id = 0; q_id < num_ceqs; q_id++)
hinic3_set_msix_state(hwdev, ceqs->ceq[q_id].msix_entry_idx,
HINIC3_MSIX_ENABLE);
return 0;
err_free_ceqs:
while (q_id > 0) {
q_id--;
remove_eq(&ceqs->ceq[q_id]);
}
kfree(ceqs);
return err;
}
void hinic3_ceqs_free(struct hinic3_hwdev *hwdev)
{
struct hinic3_ceqs *ceqs = hwdev->ceqs;
enum hinic3_ceq_event ceq_event;
struct hinic3_eq *eq;
u16 q_id;
for (q_id = 0; q_id < ceqs->num_ceqs; q_id++) {
eq = ceqs->ceq + q_id;
remove_eq(eq);
hinic3_free_irq(hwdev, eq->irq_id);
}
for (ceq_event = 0; ceq_event < HINIC3_MAX_CEQ_EVENTS; ceq_event++)
hinic3_ceq_unregister_cb(hwdev, ceq_event);
kfree(ceqs);
}
void hinic3_dump_ceq_info(struct hinic3_hwdev *hwdev)
{
struct hinic3_eq *eq;
u32 addr, ci, pi;
int q_id;
for (q_id = 0; q_id < hwdev->ceqs->num_ceqs; q_id++) {
eq = &hwdev->ceqs->ceq[q_id];
/* Indirect access should set q_id first */
hinic3_hwif_write_reg(eq->hwdev->hwif,
HINIC3_EQ_INDIR_IDX_ADDR(eq->type),
eq->q_id);
addr = EQ_CONS_IDX_REG_ADDR(eq);
ci = hinic3_hwif_read_reg(hwdev->hwif, addr);
addr = EQ_PROD_IDX_REG_ADDR(eq);
pi = hinic3_hwif_read_reg(hwdev->hwif, addr);
dev_err(hwdev->dev,
"Ceq id: %d, ci: 0x%08x, sw_ci: 0x%08x, pi: 0x%x, wrap: %u, ceqe: 0x%x\n",
q_id, ci, eq->cons_idx, pi,
eq->wrapped, be32_to_cpu(*get_curr_ceq_elem(eq)));
dev_err(hwdev->dev, "Ceq last response soft interrupt time: %u\n",
jiffies_to_msecs(jiffies - eq->soft_intr_jif));
}
}
@@ -0,0 +1,127 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_EQS_H_
#define _HINIC3_EQS_H_
#include <linux/interrupt.h>
#include "hinic3_hw_cfg.h"
#include "hinic3_queue_common.h"
#define HINIC3_MAX_AEQS 4
#define HINIC3_MAX_CEQS 32
#define HINIC3_AEQ_MAX_PAGES 4
#define HINIC3_CEQ_MAX_PAGES 8
#define HINIC3_AEQE_SIZE 64
#define HINIC3_CEQE_SIZE 4
#define HINIC3_AEQE_DESC_SIZE 4
#define HINIC3_AEQE_DATA_SIZE (HINIC3_AEQE_SIZE - HINIC3_AEQE_DESC_SIZE)
#define HINIC3_DEFAULT_AEQ_LEN 0x10000
#define HINIC3_DEFAULT_CEQ_LEN 0x10000
#define HINIC3_EQ_IRQ_NAME_LEN 64
#define HINIC3_EQ_USLEEP_LOW_BOUND 900
#define HINIC3_EQ_USLEEP_HIGH_BOUND 1000
enum hinic3_eq_type {
HINIC3_AEQ = 0,
HINIC3_CEQ = 1,
};
enum hinic3_eq_intr_mode {
HINIC3_INTR_MODE_ARMED = 0,
HINIC3_INTR_MODE_ALWAYS = 1,
};
enum hinic3_eq_ci_arm_state {
HINIC3_EQ_NOT_ARMED = 0,
HINIC3_EQ_ARMED = 1,
};
struct hinic3_eq {
struct hinic3_hwdev *hwdev;
struct hinic3_queue_pages qpages;
u16 q_id;
enum hinic3_eq_type type;
u32 eq_len;
u32 cons_idx;
u8 wrapped;
u32 irq_id;
u16 msix_entry_idx;
char irq_name[HINIC3_EQ_IRQ_NAME_LEN];
struct work_struct aeq_work;
u64 soft_intr_jif;
};
struct hinic3_aeq_elem {
u8 aeqe_data[HINIC3_AEQE_DATA_SIZE];
__be32 desc;
};
enum hinic3_aeq_type {
HINIC3_HW_INTER_INT = 0,
HINIC3_MBX_FROM_FUNC = 1,
HINIC3_MSG_FROM_FW = 2,
HINIC3_MAX_AEQ_EVENTS = 6,
};
typedef void (*hinic3_aeq_event_cb)(struct hinic3_hwdev *hwdev, u8 *data,
u8 size);
struct hinic3_aeqs {
struct hinic3_hwdev *hwdev;
hinic3_aeq_event_cb aeq_cb[HINIC3_MAX_AEQ_EVENTS];
struct hinic3_eq aeq[HINIC3_MAX_AEQS];
u16 num_aeqs;
struct workqueue_struct *workq;
/* lock for aeq event flag */
spinlock_t aeq_lock;
};
enum hinic3_ceq_event {
HINIC3_CMDQ = 3,
HINIC3_MAX_CEQ_EVENTS = 6,
};
typedef void (*hinic3_ceq_event_cb)(struct hinic3_hwdev *hwdev,
__le32 ceqe_data);
struct hinic3_ceqs {
struct hinic3_hwdev *hwdev;
hinic3_ceq_event_cb ceq_cb[HINIC3_MAX_CEQ_EVENTS];
struct hinic3_eq ceq[HINIC3_MAX_CEQS];
u16 num_ceqs;
/* lock for ceq event flag */
spinlock_t ceq_lock;
};
int hinic3_aeqs_init(struct hinic3_hwdev *hwdev, u16 num_aeqs,
struct msix_entry *msix_entries);
void hinic3_aeqs_free(struct hinic3_hwdev *hwdev);
int hinic3_aeq_register_cb(struct hinic3_hwdev *hwdev,
enum hinic3_aeq_type event,
hinic3_aeq_event_cb hwe_cb);
void hinic3_aeq_unregister_cb(struct hinic3_hwdev *hwdev,
enum hinic3_aeq_type event);
void hinic3_dump_aeq_info(struct hinic3_hwdev *hwdev);
int hinic3_ceqs_init(struct hinic3_hwdev *hwdev, u16 num_ceqs,
struct msix_entry *msix_entries);
void hinic3_ceqs_free(struct hinic3_hwdev *hwdev);
int hinic3_ceq_register_cb(struct hinic3_hwdev *hwdev,
enum hinic3_ceq_event event,
hinic3_ceq_event_cb callback);
void hinic3_ceq_unregister_cb(struct hinic3_hwdev *hwdev,
enum hinic3_ceq_event event);
void hinic3_dump_ceq_info(struct hinic3_hwdev *hwdev);
#endif
@@ -0,0 +1,425 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <linux/ethtool.h>
#include "hinic3_lld.h"
#include "hinic3_hw_comm.h"
#include "hinic3_nic_dev.h"
#include "hinic3_nic_cfg.h"
#define HINIC3_MGMT_VERSION_MAX_LEN 32
static void hinic3_get_drvinfo(struct net_device *netdev,
struct ethtool_drvinfo *info)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
u8 mgmt_ver[HINIC3_MGMT_VERSION_MAX_LEN];
struct pci_dev *pdev = nic_dev->pdev;
int err;
strscpy(info->driver, HINIC3_NIC_DRV_NAME, sizeof(info->driver));
strscpy(info->bus_info, pci_name(pdev), sizeof(info->bus_info));
err = hinic3_get_mgmt_version(nic_dev->hwdev, mgmt_ver,
HINIC3_MGMT_VERSION_MAX_LEN);
if (err) {
netdev_err(netdev, "Failed to get fw version\n");
return;
}
snprintf(info->fw_version, sizeof(info->fw_version), "%s", mgmt_ver);
}
static u32 hinic3_get_msglevel(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
return nic_dev->msg_enable;
}
static void hinic3_set_msglevel(struct net_device *netdev, u32 data)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
nic_dev->msg_enable = data;
netdev_dbg(netdev, "Set message level: 0x%x\n", data);
}
static const u32 hinic3_link_mode_ge[] = {
ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
};
static const u32 hinic3_link_mode_10ge_base_r[] = {
ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
ETHTOOL_LINK_MODE_10000baseCR_Full_BIT,
ETHTOOL_LINK_MODE_10000baseSR_Full_BIT,
ETHTOOL_LINK_MODE_10000baseLR_Full_BIT,
ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
};
static const u32 hinic3_link_mode_25ge_base_r[] = {
ETHTOOL_LINK_MODE_25000baseCR_Full_BIT,
ETHTOOL_LINK_MODE_25000baseKR_Full_BIT,
ETHTOOL_LINK_MODE_25000baseSR_Full_BIT,
};
static const u32 hinic3_link_mode_40ge_base_r4[] = {
ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT,
ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT,
ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT,
ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT,
};
static const u32 hinic3_link_mode_50ge_base_r[] = {
ETHTOOL_LINK_MODE_50000baseKR_Full_BIT,
ETHTOOL_LINK_MODE_50000baseSR_Full_BIT,
ETHTOOL_LINK_MODE_50000baseCR_Full_BIT,
};
static const u32 hinic3_link_mode_50ge_base_r2[] = {
ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT,
ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT,
ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT,
};
static const u32 hinic3_link_mode_100ge_base_r[] = {
ETHTOOL_LINK_MODE_100000baseKR_Full_BIT,
ETHTOOL_LINK_MODE_100000baseSR_Full_BIT,
ETHTOOL_LINK_MODE_100000baseCR_Full_BIT,
};
static const u32 hinic3_link_mode_100ge_base_r2[] = {
ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT,
ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT,
ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT,
};
static const u32 hinic3_link_mode_100ge_base_r4[] = {
ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT,
ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT,
ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT,
ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT,
};
static const u32 hinic3_link_mode_200ge_base_r2[] = {
ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT,
ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT,
ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT,
};
static const u32 hinic3_link_mode_200ge_base_r4[] = {
ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT,
ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT,
ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT,
};
struct hw2ethtool_link_mode {
const u32 *link_mode_bit_arr;
u32 arr_size;
u32 speed;
};
static const struct hw2ethtool_link_mode
hw2ethtool_link_mode_table[LINK_MODE_MAX_NUMBERS] = {
[LINK_MODE_GE] = {
.link_mode_bit_arr = hinic3_link_mode_ge,
.arr_size = ARRAY_SIZE(hinic3_link_mode_ge),
.speed = SPEED_1000,
},
[LINK_MODE_10GE_BASE_R] = {
.link_mode_bit_arr = hinic3_link_mode_10ge_base_r,
.arr_size = ARRAY_SIZE(hinic3_link_mode_10ge_base_r),
.speed = SPEED_10000,
},
[LINK_MODE_25GE_BASE_R] = {
.link_mode_bit_arr = hinic3_link_mode_25ge_base_r,
.arr_size = ARRAY_SIZE(hinic3_link_mode_25ge_base_r),
.speed = SPEED_25000,
},
[LINK_MODE_40GE_BASE_R4] = {
.link_mode_bit_arr = hinic3_link_mode_40ge_base_r4,
.arr_size = ARRAY_SIZE(hinic3_link_mode_40ge_base_r4),
.speed = SPEED_40000,
},
[LINK_MODE_50GE_BASE_R] = {
.link_mode_bit_arr = hinic3_link_mode_50ge_base_r,
.arr_size = ARRAY_SIZE(hinic3_link_mode_50ge_base_r),
.speed = SPEED_50000,
},
[LINK_MODE_50GE_BASE_R2] = {
.link_mode_bit_arr = hinic3_link_mode_50ge_base_r2,
.arr_size = ARRAY_SIZE(hinic3_link_mode_50ge_base_r2),
.speed = SPEED_50000,
},
[LINK_MODE_100GE_BASE_R] = {
.link_mode_bit_arr = hinic3_link_mode_100ge_base_r,
.arr_size = ARRAY_SIZE(hinic3_link_mode_100ge_base_r),
.speed = SPEED_100000,
},
[LINK_MODE_100GE_BASE_R2] = {
.link_mode_bit_arr = hinic3_link_mode_100ge_base_r2,
.arr_size = ARRAY_SIZE(hinic3_link_mode_100ge_base_r2),
.speed = SPEED_100000,
},
[LINK_MODE_100GE_BASE_R4] = {
.link_mode_bit_arr = hinic3_link_mode_100ge_base_r4,
.arr_size = ARRAY_SIZE(hinic3_link_mode_100ge_base_r4),
.speed = SPEED_100000,
},
[LINK_MODE_200GE_BASE_R2] = {
.link_mode_bit_arr = hinic3_link_mode_200ge_base_r2,
.arr_size = ARRAY_SIZE(hinic3_link_mode_200ge_base_r2),
.speed = SPEED_200000,
},
[LINK_MODE_200GE_BASE_R4] = {
.link_mode_bit_arr = hinic3_link_mode_200ge_base_r4,
.arr_size = ARRAY_SIZE(hinic3_link_mode_200ge_base_r4),
.speed = SPEED_200000,
},
};
#define GET_SUPPORTED_MODE 0
#define GET_ADVERTISED_MODE 1
struct hinic3_link_settings {
__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
__ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
u32 speed;
u8 duplex;
u8 port;
u8 autoneg;
};
#define HINIC3_ADD_SUPPORTED_LINK_MODE(ecmd, mode) \
set_bit(ETHTOOL_LINK_##mode##_BIT, (ecmd)->supported)
#define HINIC3_ADD_ADVERTISED_LINK_MODE(ecmd, mode) \
set_bit(ETHTOOL_LINK_##mode##_BIT, (ecmd)->advertising)
static void hinic3_add_speed_link_mode(unsigned long *bitmap, u32 mode)
{
u32 i;
for (i = 0; i < hw2ethtool_link_mode_table[mode].arr_size; i++) {
if (hw2ethtool_link_mode_table[mode].link_mode_bit_arr[i] >=
__ETHTOOL_LINK_MODE_MASK_NBITS)
continue;
set_bit(hw2ethtool_link_mode_table[mode].link_mode_bit_arr[i],
bitmap);
}
}
/* Related to enum mag_cmd_port_speed */
static const u32 hw_to_ethtool_speed[] = {
(u32)SPEED_UNKNOWN, SPEED_10, SPEED_100, SPEED_1000, SPEED_10000,
SPEED_25000, SPEED_40000, SPEED_50000, SPEED_100000, SPEED_200000
};
static void
hinic3_add_ethtool_link_mode(struct hinic3_link_settings *link_settings,
u32 hw_link_mode, u32 name)
{
unsigned long *advertising_mask = link_settings->advertising;
unsigned long *supported_mask = link_settings->supported;
u32 link_mode;
for (link_mode = 0; link_mode < LINK_MODE_MAX_NUMBERS; link_mode++) {
if (hw_link_mode & BIT(link_mode)) {
if (name == GET_SUPPORTED_MODE)
hinic3_add_speed_link_mode(supported_mask,
link_mode);
else
hinic3_add_speed_link_mode(advertising_mask,
link_mode);
}
}
}
static void
hinic3_link_speed_set(struct net_device *netdev,
struct hinic3_link_settings *link_settings,
struct hinic3_nic_port_info *port_info)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
bool link_status_up;
int err;
if (port_info->supported_mode != LINK_MODE_UNKNOWN)
hinic3_add_ethtool_link_mode(link_settings,
port_info->supported_mode,
GET_SUPPORTED_MODE);
if (port_info->advertised_mode != LINK_MODE_UNKNOWN)
hinic3_add_ethtool_link_mode(link_settings,
port_info->advertised_mode,
GET_ADVERTISED_MODE);
err = hinic3_get_link_status(nic_dev->hwdev, &link_status_up);
if (!err && link_status_up) {
link_settings->speed =
port_info->speed < ARRAY_SIZE(hw_to_ethtool_speed) ?
hw_to_ethtool_speed[port_info->speed] :
(u32)SPEED_UNKNOWN;
link_settings->duplex = port_info->duplex;
} else {
link_settings->speed = (u32)SPEED_UNKNOWN;
link_settings->duplex = DUPLEX_UNKNOWN;
}
}
static void
hinic3_link_port_type_set(struct hinic3_link_settings *link_settings,
u8 port_type)
{
switch (port_type) {
case MAG_CMD_WIRE_TYPE_ELECTRIC:
HINIC3_ADD_SUPPORTED_LINK_MODE(link_settings, MODE_TP);
HINIC3_ADD_ADVERTISED_LINK_MODE(link_settings, MODE_TP);
link_settings->port = PORT_TP;
break;
case MAG_CMD_WIRE_TYPE_AOC:
case MAG_CMD_WIRE_TYPE_MM:
case MAG_CMD_WIRE_TYPE_SM:
HINIC3_ADD_SUPPORTED_LINK_MODE(link_settings, MODE_FIBRE);
HINIC3_ADD_ADVERTISED_LINK_MODE(link_settings, MODE_FIBRE);
link_settings->port = PORT_FIBRE;
break;
case MAG_CMD_WIRE_TYPE_COPPER:
HINIC3_ADD_SUPPORTED_LINK_MODE(link_settings, MODE_FIBRE);
HINIC3_ADD_ADVERTISED_LINK_MODE(link_settings, MODE_FIBRE);
link_settings->port = PORT_DA;
break;
case MAG_CMD_WIRE_TYPE_BACKPLANE:
HINIC3_ADD_SUPPORTED_LINK_MODE(link_settings, MODE_Backplane);
HINIC3_ADD_ADVERTISED_LINK_MODE(link_settings, MODE_Backplane);
link_settings->port = PORT_NONE;
break;
default:
link_settings->port = PORT_OTHER;
break;
}
}
static int
hinic3_get_link_pause_settings(struct net_device *netdev,
struct hinic3_link_settings *link_settings)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_nic_pause_config nic_pause = {};
int err;
err = hinic3_get_pause_info(nic_dev, &nic_pause);
if (err) {
netdev_err(netdev, "Failed to get pause param from hw\n");
return err;
}
HINIC3_ADD_SUPPORTED_LINK_MODE(link_settings, MODE_Pause);
if (nic_pause.rx_pause && nic_pause.tx_pause) {
HINIC3_ADD_ADVERTISED_LINK_MODE(link_settings, MODE_Pause);
} else if (nic_pause.tx_pause) {
HINIC3_ADD_ADVERTISED_LINK_MODE(link_settings,
MODE_Asym_Pause);
} else if (nic_pause.rx_pause) {
HINIC3_ADD_ADVERTISED_LINK_MODE(link_settings, MODE_Pause);
HINIC3_ADD_ADVERTISED_LINK_MODE(link_settings,
MODE_Asym_Pause);
}
return 0;
}
static int
hinic3_get_link_settings(struct net_device *netdev,
struct hinic3_link_settings *link_settings)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_nic_port_info port_info = {};
int err;
err = hinic3_get_port_info(nic_dev->hwdev, &port_info);
if (err) {
netdev_err(netdev, "Failed to get port info\n");
return err;
}
hinic3_link_speed_set(netdev, link_settings, &port_info);
hinic3_link_port_type_set(link_settings, port_info.port_type);
link_settings->autoneg = port_info.autoneg_state == PORT_CFG_AN_ON ?
AUTONEG_ENABLE : AUTONEG_DISABLE;
if (port_info.autoneg_cap)
HINIC3_ADD_SUPPORTED_LINK_MODE(link_settings, MODE_Autoneg);
if (port_info.autoneg_state == PORT_CFG_AN_ON)
HINIC3_ADD_ADVERTISED_LINK_MODE(link_settings, MODE_Autoneg);
if (!HINIC3_IS_VF(nic_dev->hwdev)) {
err = hinic3_get_link_pause_settings(netdev, link_settings);
if (err)
return err;
}
return 0;
}
static int
hinic3_get_link_ksettings(struct net_device *netdev,
struct ethtool_link_ksettings *link_settings)
{
struct ethtool_link_settings *base = &link_settings->base;
struct hinic3_link_settings settings = {};
int err;
ethtool_link_ksettings_zero_link_mode(link_settings, supported);
ethtool_link_ksettings_zero_link_mode(link_settings, advertising);
err = hinic3_get_link_settings(netdev, &settings);
if (err)
return err;
bitmap_copy(link_settings->link_modes.supported, settings.supported,
__ETHTOOL_LINK_MODE_MASK_NBITS);
bitmap_copy(link_settings->link_modes.advertising, settings.advertising,
__ETHTOOL_LINK_MODE_MASK_NBITS);
base->autoneg = settings.autoneg;
base->speed = settings.speed;
base->duplex = settings.duplex;
base->port = settings.port;
return 0;
}
static const struct ethtool_ops hinic3_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
.get_link_ksettings = hinic3_get_link_ksettings,
.get_drvinfo = hinic3_get_drvinfo,
.get_msglevel = hinic3_get_msglevel,
.set_msglevel = hinic3_set_msglevel,
.get_link = ethtool_op_get_link,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
{
netdev->ethtool_ops = &hinic3_ethtool_ops;
}
@@ -0,0 +1,417 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include "hinic3_hwif.h"
#include "hinic3_nic_dev.h"
#include "hinic3_nic_cfg.h"
static int hinic3_filter_addr_sync(struct net_device *netdev, u8 *addr)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
return hinic3_set_mac(nic_dev->hwdev, addr, 0,
hinic3_global_func_id(nic_dev->hwdev));
}
static int hinic3_filter_addr_unsync(struct net_device *netdev, u8 *addr)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
/* The addr is in use */
if (ether_addr_equal(addr, netdev->dev_addr))
return 0;
return hinic3_del_mac(nic_dev->hwdev, addr, 0,
hinic3_global_func_id(nic_dev->hwdev));
}
void hinic3_clean_mac_list_filter(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_mac_filter *ftmp;
struct hinic3_mac_filter *f;
list_for_each_entry_safe(f, ftmp, &nic_dev->uc_filter_list, list) {
if (f->state == HINIC3_MAC_HW_SYNCED)
hinic3_filter_addr_unsync(netdev, f->addr);
list_del(&f->list);
kfree(f);
}
list_for_each_entry_safe(f, ftmp, &nic_dev->mc_filter_list, list) {
if (f->state == HINIC3_MAC_HW_SYNCED)
hinic3_filter_addr_unsync(netdev, f->addr);
list_del(&f->list);
kfree(f);
}
}
static struct hinic3_mac_filter *
hinic3_find_mac(const struct list_head *filter_list, u8 *addr)
{
struct hinic3_mac_filter *f;
list_for_each_entry(f, filter_list, list) {
if (ether_addr_equal(addr, f->addr))
return f;
}
return NULL;
}
static void hinic3_add_filter(struct net_device *netdev,
struct list_head *mac_filter_list,
u8 *addr)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_mac_filter *f;
f = kzalloc_obj(*f, GFP_ATOMIC);
if (!f)
return;
ether_addr_copy(f->addr, addr);
INIT_LIST_HEAD(&f->list);
list_add_tail(&f->list, mac_filter_list);
f->state = HINIC3_MAC_WAIT_HW_SYNC;
set_bit(HINIC3_MAC_FILTER_CHANGED, &nic_dev->flags);
}
static void hinic3_del_filter(struct net_device *netdev,
struct hinic3_mac_filter *f)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
set_bit(HINIC3_MAC_FILTER_CHANGED, &nic_dev->flags);
if (f->state == HINIC3_MAC_WAIT_HW_SYNC) {
/* have not added to hw, delete it directly */
list_del(&f->list);
kfree(f);
return;
}
f->state = HINIC3_MAC_WAIT_HW_UNSYNC;
}
static struct hinic3_mac_filter *
hinic3_mac_filter_entry_clone(const struct hinic3_mac_filter *src)
{
struct hinic3_mac_filter *f;
f = kzalloc_obj(*f, GFP_ATOMIC);
if (!f)
return NULL;
*f = *src;
INIT_LIST_HEAD(&f->list);
return f;
}
static void hinic3_undo_del_filter_entries(struct list_head *filter_list,
const struct list_head *from)
{
struct hinic3_mac_filter *ftmp;
struct hinic3_mac_filter *f;
list_for_each_entry_safe(f, ftmp, from, list) {
if (hinic3_find_mac(filter_list, f->addr))
continue;
if (f->state == HINIC3_MAC_HW_UNSYNCED)
f->state = HINIC3_MAC_WAIT_HW_UNSYNC;
list_move_tail(&f->list, filter_list);
}
}
static void hinic3_undo_add_filter_entries(struct list_head *filter_list,
const struct list_head *from)
{
struct hinic3_mac_filter *ftmp;
struct hinic3_mac_filter *tmp;
struct hinic3_mac_filter *f;
list_for_each_entry_safe(f, ftmp, from, list) {
tmp = hinic3_find_mac(filter_list, f->addr);
if (tmp && tmp->state == HINIC3_MAC_HW_SYNCING)
tmp->state = HINIC3_MAC_WAIT_HW_SYNC;
}
}
static void hinic3_cleanup_filter_list(const struct list_head *head)
{
struct hinic3_mac_filter *ftmp;
struct hinic3_mac_filter *f;
list_for_each_entry_safe(f, ftmp, head, list) {
list_del(&f->list);
kfree(f);
}
}
static int hinic3_mac_filter_sync_hw(struct net_device *netdev,
struct list_head *del_list,
struct list_head *add_list,
int *add_count)
{
struct hinic3_mac_filter *ftmp;
struct hinic3_mac_filter *f;
int err;
if (!list_empty(del_list)) {
list_for_each_entry_safe(f, ftmp, del_list, list) {
/* ignore errors when deleting mac */
hinic3_filter_addr_unsync(netdev, f->addr);
list_del(&f->list);
kfree(f);
}
}
if (!list_empty(add_list)) {
list_for_each_entry_safe(f, ftmp, add_list, list) {
if (f->state != HINIC3_MAC_HW_SYNCING)
continue;
err = hinic3_filter_addr_sync(netdev, f->addr);
if (err) {
netdev_err(netdev, "Failed to add mac\n");
return err;
}
f->state = HINIC3_MAC_HW_SYNCED;
(*add_count)++;
}
}
return 0;
}
static int hinic3_mac_filter_sync(struct net_device *netdev,
struct list_head *mac_filter_list, bool uc)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct list_head tmp_del_list, tmp_add_list;
struct hinic3_mac_filter *fclone;
struct hinic3_mac_filter *ftmp;
struct hinic3_mac_filter *f;
int err = 0, add_count = 0;
INIT_LIST_HEAD(&tmp_del_list);
INIT_LIST_HEAD(&tmp_add_list);
list_for_each_entry_safe(f, ftmp, mac_filter_list, list) {
if (f->state != HINIC3_MAC_WAIT_HW_UNSYNC)
continue;
f->state = HINIC3_MAC_HW_UNSYNCED;
list_move_tail(&f->list, &tmp_del_list);
}
list_for_each_entry_safe(f, ftmp, mac_filter_list, list) {
if (f->state != HINIC3_MAC_WAIT_HW_SYNC)
continue;
fclone = hinic3_mac_filter_entry_clone(f);
if (!fclone) {
hinic3_undo_del_filter_entries(mac_filter_list,
&tmp_del_list);
hinic3_undo_add_filter_entries(mac_filter_list,
&tmp_add_list);
netdev_err(netdev,
"Failed to clone mac_filter_entry\n");
err = -ENOMEM;
goto cleanup_tmp_filter_list;
}
f->state = HINIC3_MAC_HW_SYNCING;
list_add_tail(&fclone->list, &tmp_add_list);
}
err = hinic3_mac_filter_sync_hw(netdev, &tmp_del_list,
&tmp_add_list, &add_count);
if (err) {
/* there were errors, delete all mac in hw */
hinic3_undo_add_filter_entries(mac_filter_list, &tmp_add_list);
add_count = 0;
/* VF does not support promiscuous mode,
* don't delete any other uc mac.
*/
if (!HINIC3_IS_VF(nic_dev->hwdev) || !uc) {
list_for_each_entry_safe(f, ftmp, mac_filter_list,
list) {
if (f->state != HINIC3_MAC_HW_SYNCED)
continue;
fclone = hinic3_mac_filter_entry_clone(f);
if (!fclone)
break;
f->state = HINIC3_MAC_WAIT_HW_SYNC;
list_add_tail(&fclone->list, &tmp_del_list);
}
}
hinic3_mac_filter_sync_hw(netdev, &tmp_del_list,
&tmp_add_list, &add_count);
}
cleanup_tmp_filter_list:
hinic3_cleanup_filter_list(&tmp_del_list);
hinic3_cleanup_filter_list(&tmp_add_list);
return err ? err : add_count;
}
static void hinic3_mac_filter_sync_all(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int add_count;
if (test_bit(HINIC3_MAC_FILTER_CHANGED, &nic_dev->flags)) {
clear_bit(HINIC3_MAC_FILTER_CHANGED, &nic_dev->flags);
add_count = hinic3_mac_filter_sync(netdev,
&nic_dev->uc_filter_list,
true);
if (add_count < 0 &&
hinic3_test_support(nic_dev, HINIC3_NIC_F_PROMISC))
set_bit(HINIC3_PROMISC_FORCE_ON,
&nic_dev->rx_mod_state);
else if (add_count)
clear_bit(HINIC3_PROMISC_FORCE_ON,
&nic_dev->rx_mod_state);
add_count = hinic3_mac_filter_sync(netdev,
&nic_dev->mc_filter_list,
false);
if (add_count < 0 &&
hinic3_test_support(nic_dev, HINIC3_NIC_F_ALLMULTI))
set_bit(HINIC3_ALLMULTI_FORCE_ON,
&nic_dev->rx_mod_state);
else if (add_count)
clear_bit(HINIC3_ALLMULTI_FORCE_ON,
&nic_dev->rx_mod_state);
}
}
#define HINIC3_DEFAULT_RX_MODE \
(L2NIC_RX_MODE_UC | L2NIC_RX_MODE_MC | L2NIC_RX_MODE_BC)
static void hinic3_update_mac_filter(struct net_device *netdev,
const struct netdev_hw_addr_list *src_list,
struct list_head *filter_list)
{
struct hinic3_mac_filter *filter;
struct hinic3_mac_filter *ftmp;
struct hinic3_mac_filter *f;
struct netdev_hw_addr *ha;
/* add addr if not already in the filter list */
netif_addr_lock_bh(netdev);
netdev_hw_addr_list_for_each(ha, src_list) {
filter = hinic3_find_mac(filter_list, ha->addr);
if (!filter)
hinic3_add_filter(netdev, filter_list, ha->addr);
else if (filter->state == HINIC3_MAC_WAIT_HW_UNSYNC)
filter->state = HINIC3_MAC_HW_SYNCED;
}
netif_addr_unlock_bh(netdev);
/* delete addr if not in netdev list */
list_for_each_entry_safe(f, ftmp, filter_list, list) {
bool found = false;
netif_addr_lock_bh(netdev);
netdev_hw_addr_list_for_each(ha, src_list)
if (ether_addr_equal(ha->addr, f->addr)) {
found = true;
break;
}
netif_addr_unlock_bh(netdev);
if (found)
continue;
hinic3_del_filter(netdev, f);
}
}
static void hinic3_sync_rx_mode_to_hw(struct net_device *netdev, int promisc_en,
int allmulti_en)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
u32 rx_mode = HINIC3_DEFAULT_RX_MODE;
int err;
rx_mode |= (promisc_en ? L2NIC_RX_MODE_PROMISC : 0);
rx_mode |= (allmulti_en ? L2NIC_RX_MODE_MC_ALL : 0);
if (promisc_en != test_bit(HINIC3_HW_PROMISC_ON,
&nic_dev->rx_mod_state))
netdev_dbg(netdev, "%s promisc mode\n",
promisc_en ? "Enter" : "Left");
if (allmulti_en !=
test_bit(HINIC3_HW_ALLMULTI_ON, &nic_dev->rx_mod_state))
netdev_dbg(netdev, "%s all_multi mode\n",
allmulti_en ? "Enter" : "Left");
err = hinic3_set_rx_mode(nic_dev->hwdev, rx_mode);
if (err) {
netdev_err(netdev, "Failed to set rx_mode\n");
return;
}
promisc_en ? set_bit(HINIC3_HW_PROMISC_ON, &nic_dev->rx_mod_state) :
clear_bit(HINIC3_HW_PROMISC_ON, &nic_dev->rx_mod_state);
allmulti_en ? set_bit(HINIC3_HW_ALLMULTI_ON, &nic_dev->rx_mod_state) :
clear_bit(HINIC3_HW_ALLMULTI_ON, &nic_dev->rx_mod_state);
}
void hinic3_set_rx_mode_work(struct work_struct *work)
{
int promisc_en = 0, allmulti_en = 0;
struct hinic3_nic_dev *nic_dev;
struct net_device *netdev;
nic_dev = container_of(work, struct hinic3_nic_dev, rx_mode_work);
netdev = nic_dev->netdev;
if (test_and_clear_bit(HINIC3_UPDATE_MAC_FILTER, &nic_dev->flags)) {
hinic3_update_mac_filter(netdev, &netdev->uc,
&nic_dev->uc_filter_list);
hinic3_update_mac_filter(netdev, &netdev->mc,
&nic_dev->mc_filter_list);
}
hinic3_mac_filter_sync_all(netdev);
if (hinic3_test_support(nic_dev, HINIC3_NIC_F_PROMISC))
promisc_en = !!(netdev->flags & IFF_PROMISC) ||
test_bit(HINIC3_PROMISC_FORCE_ON,
&nic_dev->rx_mod_state);
if (hinic3_test_support(nic_dev, HINIC3_NIC_F_ALLMULTI))
allmulti_en = !!(netdev->flags & IFF_ALLMULTI) ||
test_bit(HINIC3_ALLMULTI_FORCE_ON,
&nic_dev->rx_mod_state);
if (promisc_en != test_bit(HINIC3_HW_PROMISC_ON,
&nic_dev->rx_mod_state) ||
allmulti_en != test_bit(HINIC3_HW_ALLMULTI_ON,
&nic_dev->rx_mod_state))
hinic3_sync_rx_mode_to_hw(netdev, promisc_en, allmulti_en);
}
@@ -0,0 +1,276 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/device.h>
#include "hinic3_hw_cfg.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_mbox.h"
#define HINIC3_CFG_MAX_QP 256
static void hinic3_parse_pub_res_cap(struct hinic3_hwdev *hwdev,
struct hinic3_dev_cap *cap,
const struct cfg_cmd_dev_cap *dev_cap,
enum hinic3_func_type type)
{
cap->port_id = dev_cap->port_id;
cap->supp_svcs_bitmap = dev_cap->svc_cap_en;
cap->cos_valid_bitmap = dev_cap->valid_cos_bitmap;
cap->port_cos_valid_bitmap = dev_cap->port_cos_valid_bitmap;
if (type != HINIC3_FUNC_TYPE_VF)
cap->max_vf = dev_cap->max_vf;
else
cap->max_vf = 0;
dev_dbg(hwdev->dev, "Port_id: 0x%x, cos_bitmap: 0x%x, Max_vf: 0x%x\n",
cap->port_id, cap->cos_valid_bitmap, cap->max_vf);
}
static void hinic3_parse_l2nic_res_cap(struct hinic3_hwdev *hwdev,
struct hinic3_dev_cap *cap,
const struct cfg_cmd_dev_cap *dev_cap,
enum hinic3_func_type type)
{
struct hinic3_nic_service_cap *nic_svc_cap = &cap->nic_svc_cap;
nic_svc_cap->max_sqs = min(dev_cap->nic_max_sq_id + 1,
HINIC3_CFG_MAX_QP);
nic_svc_cap->max_rqs = min(dev_cap->nic_max_rq_id + 1,
HINIC3_CFG_MAX_QP);
nic_svc_cap->default_num_queues = dev_cap->nic_default_num_queues;
dev_dbg(hwdev->dev, "L2nic resource capability, max_sqs: 0x%x, max_rqs: 0x%x\n",
nic_svc_cap->max_sqs, nic_svc_cap->max_rqs);
}
static void hinic3_parse_dev_cap(struct hinic3_hwdev *hwdev,
const struct cfg_cmd_dev_cap *dev_cap,
enum hinic3_func_type type)
{
struct hinic3_dev_cap *cap = &hwdev->cfg_mgmt->cap;
/* Public resource */
hinic3_parse_pub_res_cap(hwdev, cap, dev_cap, type);
/* L2 NIC resource */
if (hinic3_support_nic(hwdev))
hinic3_parse_l2nic_res_cap(hwdev, cap, dev_cap, type);
}
static int hinic3_get_cap_from_fw(struct hinic3_hwdev *hwdev,
enum hinic3_func_type type)
{
struct mgmt_msg_params msg_params = {};
struct cfg_cmd_dev_cap dev_cap = {};
int err;
dev_cap.func_id = hinic3_global_func_id(hwdev);
mgmt_msg_params_init_default(&msg_params, &dev_cap, sizeof(dev_cap));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_CFGM,
CFG_CMD_GET_DEV_CAP, &msg_params);
if (err || dev_cap.head.status) {
dev_err(hwdev->dev,
"Failed to get capability from FW, err: %d, status: 0x%x\n",
err, dev_cap.head.status);
return -EIO;
}
hinic3_parse_dev_cap(hwdev, &dev_cap, type);
return 0;
}
static int hinic3_get_dev_cap(struct hinic3_hwdev *hwdev)
{
enum hinic3_func_type type = HINIC3_FUNC_TYPE(hwdev);
int err;
switch (type) {
case HINIC3_FUNC_TYPE_PF:
case HINIC3_FUNC_TYPE_VF:
err = hinic3_get_cap_from_fw(hwdev, type);
if (err) {
dev_err(hwdev->dev, "Failed to get FW capability\n");
return err;
}
break;
default:
dev_err(hwdev->dev, "Unsupported PCI Function type: %d\n",
type);
return -EINVAL;
}
return 0;
}
static int hinic3_init_irq_info(struct hinic3_hwdev *hwdev)
{
struct hinic3_cfg_mgmt_info *cfg_mgmt = hwdev->cfg_mgmt;
struct hinic3_hwif *hwif = hwdev->hwif;
u16 intr_num = hwif->attr.num_irqs;
struct hinic3_irq_info *irq_info;
u16 intr_needed;
intr_needed = hwif->attr.msix_flex_en ? (hwif->attr.num_aeqs +
hwif->attr.num_ceqs + hwif->attr.num_sq) : intr_num;
if (intr_needed > intr_num) {
dev_warn(hwdev->dev, "Irq num cfg %d is less than the needed irq num %d msix_flex_en %d\n",
intr_num, intr_needed, hwdev->hwif->attr.msix_flex_en);
intr_needed = intr_num;
}
irq_info = &cfg_mgmt->irq_info;
irq_info->irq = kzalloc_objs(struct hinic3_irq, intr_num);
if (!irq_info->irq)
return -ENOMEM;
irq_info->num_irq_hw = intr_needed;
mutex_init(&irq_info->irq_mutex);
return 0;
}
static int hinic3_init_irq_alloc_info(struct hinic3_hwdev *hwdev)
{
struct hinic3_cfg_mgmt_info *cfg_mgmt = hwdev->cfg_mgmt;
struct hinic3_irq *irq = cfg_mgmt->irq_info.irq;
u16 nreq = cfg_mgmt->irq_info.num_irq_hw;
struct pci_dev *pdev = hwdev->pdev;
int actual_irq;
u16 i;
actual_irq = pci_alloc_irq_vectors(pdev, 2, nreq, PCI_IRQ_MSIX);
if (actual_irq < 0) {
dev_err(hwdev->dev, "Alloc msix entries with threshold 2 failed. actual_irq: %d\n",
actual_irq);
return -ENOMEM;
}
nreq = actual_irq;
cfg_mgmt->irq_info.num_irq = nreq;
for (i = 0; i < nreq; ++i) {
irq[i].msix_entry_idx = i;
irq[i].irq_id = pci_irq_vector(pdev, i);
irq[i].allocated = false;
}
return 0;
}
int hinic3_init_cfg_mgmt(struct hinic3_hwdev *hwdev)
{
struct hinic3_cfg_mgmt_info *cfg_mgmt;
int err;
cfg_mgmt = kzalloc_obj(*cfg_mgmt);
if (!cfg_mgmt)
return -ENOMEM;
hwdev->cfg_mgmt = cfg_mgmt;
err = hinic3_init_irq_info(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init hinic3_irq_mgmt_info, err: %d\n",
err);
goto err_free_cfg_mgmt;
}
err = hinic3_init_irq_alloc_info(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init hinic3_irq_info, err: %d\n",
err);
goto err_free_irq_info;
}
return 0;
err_free_irq_info:
kfree(cfg_mgmt->irq_info.irq);
cfg_mgmt->irq_info.irq = NULL;
err_free_cfg_mgmt:
kfree(cfg_mgmt);
return err;
}
void hinic3_free_cfg_mgmt(struct hinic3_hwdev *hwdev)
{
struct hinic3_cfg_mgmt_info *cfg_mgmt = hwdev->cfg_mgmt;
pci_free_irq_vectors(hwdev->pdev);
kfree(cfg_mgmt->irq_info.irq);
cfg_mgmt->irq_info.irq = NULL;
kfree(cfg_mgmt);
}
int hinic3_alloc_irqs(struct hinic3_hwdev *hwdev, u16 num,
struct msix_entry *alloc_arr, u16 *act_num)
{
struct hinic3_irq_info *irq_info;
struct hinic3_irq *curr;
u16 i, found = 0;
irq_info = &hwdev->cfg_mgmt->irq_info;
mutex_lock(&irq_info->irq_mutex);
for (i = 0; i < irq_info->num_irq && found < num; i++) {
curr = irq_info->irq + i;
if (curr->allocated)
continue;
curr->allocated = true;
alloc_arr[found].vector = curr->irq_id;
alloc_arr[found].entry = curr->msix_entry_idx;
found++;
}
mutex_unlock(&irq_info->irq_mutex);
*act_num = found;
return found == 0 ? -ENOMEM : 0;
}
void hinic3_free_irq(struct hinic3_hwdev *hwdev, u32 irq_id)
{
struct hinic3_irq_info *irq_info;
struct hinic3_irq *curr;
u16 i;
irq_info = &hwdev->cfg_mgmt->irq_info;
mutex_lock(&irq_info->irq_mutex);
for (i = 0; i < irq_info->num_irq; i++) {
curr = irq_info->irq + i;
if (curr->irq_id == irq_id) {
curr->allocated = false;
break;
}
}
mutex_unlock(&irq_info->irq_mutex);
}
int hinic3_init_capability(struct hinic3_hwdev *hwdev)
{
return hinic3_get_dev_cap(hwdev);
}
bool hinic3_support_nic(struct hinic3_hwdev *hwdev)
{
return hwdev->cfg_mgmt->cap.supp_svcs_bitmap &
BIT(HINIC3_SERVICE_T_NIC);
}
u16 hinic3_func_max_qnum(struct hinic3_hwdev *hwdev)
{
return hwdev->cfg_mgmt->cap.nic_svc_cap.max_sqs;
}
u8 hinic3_physical_port_id(struct hinic3_hwdev *hwdev)
{
return hwdev->cfg_mgmt->cap.port_id;
}
@@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_HW_CFG_H_
#define _HINIC3_HW_CFG_H_
#include <linux/mutex.h>
#include <linux/pci.h>
struct hinic3_hwdev;
struct hinic3_irq {
u32 irq_id;
u16 msix_entry_idx;
bool allocated;
};
struct hinic3_irq_info {
struct hinic3_irq *irq;
u16 num_irq;
/* device max irq number */
u16 num_irq_hw;
/* protect irq alloc and free */
struct mutex irq_mutex;
};
struct hinic3_nic_service_cap {
u16 max_sqs;
u16 max_rqs;
u16 default_num_queues;
};
/* Device capabilities */
struct hinic3_dev_cap {
/* Bitmasks of services supported by device */
u16 supp_svcs_bitmap;
/* Physical port */
u8 port_id;
u8 cos_valid_bitmap;
u8 port_cos_valid_bitmap;
/* max number of VFs that PF supports */
u16 max_vf;
struct hinic3_nic_service_cap nic_svc_cap;
};
struct hinic3_cfg_mgmt_info {
struct hinic3_irq_info irq_info;
struct hinic3_dev_cap cap;
};
int hinic3_init_cfg_mgmt(struct hinic3_hwdev *hwdev);
void hinic3_free_cfg_mgmt(struct hinic3_hwdev *hwdev);
int hinic3_alloc_irqs(struct hinic3_hwdev *hwdev, u16 num,
struct msix_entry *alloc_arr, u16 *act_num);
void hinic3_free_irq(struct hinic3_hwdev *hwdev, u32 irq_id);
int hinic3_init_capability(struct hinic3_hwdev *hwdev);
bool hinic3_support_nic(struct hinic3_hwdev *hwdev);
u16 hinic3_func_max_qnum(struct hinic3_hwdev *hwdev);
u8 hinic3_physical_port_id(struct hinic3_hwdev *hwdev);
#endif
@@ -0,0 +1,610 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/delay.h>
#include "hinic3_cmdq.h"
#include "hinic3_hw_comm.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_mbox.h"
static int hinic3_get_interrupt_cfg(struct hinic3_hwdev *hwdev,
struct hinic3_interrupt_info *info)
{
struct comm_cmd_cfg_msix_ctrl_reg msix_cfg = {};
struct mgmt_msg_params msg_params = {};
int err;
msix_cfg.func_id = hinic3_global_func_id(hwdev);
msix_cfg.msix_index = info->msix_index;
msix_cfg.opcode = MGMT_MSG_CMD_OP_GET;
mgmt_msg_params_init_default(&msg_params, &msix_cfg, sizeof(msix_cfg));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_CFG_MSIX_CTRL_REG, &msg_params);
if (err || msix_cfg.head.status) {
dev_err(hwdev->dev, "Failed to get interrupt config, err: %d, status: 0x%x\n",
err, msix_cfg.head.status);
return -EFAULT;
}
info->lli_credit_limit = msix_cfg.lli_credit_cnt;
info->lli_timer_cfg = msix_cfg.lli_timer_cnt;
info->pending_limit = msix_cfg.pending_cnt;
info->coalesc_timer_cfg = msix_cfg.coalesce_timer_cnt;
info->resend_timer_cfg = msix_cfg.resend_timer_cnt;
return 0;
}
int hinic3_set_interrupt_cfg_direct(struct hinic3_hwdev *hwdev,
const struct hinic3_interrupt_info *info)
{
struct comm_cmd_cfg_msix_ctrl_reg msix_cfg = {};
struct mgmt_msg_params msg_params = {};
int err;
msix_cfg.func_id = hinic3_global_func_id(hwdev);
msix_cfg.msix_index = info->msix_index;
msix_cfg.opcode = MGMT_MSG_CMD_OP_SET;
msix_cfg.lli_credit_cnt = info->lli_credit_limit;
msix_cfg.lli_timer_cnt = info->lli_timer_cfg;
msix_cfg.pending_cnt = info->pending_limit;
msix_cfg.coalesce_timer_cnt = info->coalesc_timer_cfg;
msix_cfg.resend_timer_cnt = info->resend_timer_cfg;
mgmt_msg_params_init_default(&msg_params, &msix_cfg, sizeof(msix_cfg));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_CFG_MSIX_CTRL_REG, &msg_params);
if (err || msix_cfg.head.status) {
dev_err(hwdev->dev,
"Failed to set interrupt config, err: %d, status: 0x%x\n",
err, msix_cfg.head.status);
return -EINVAL;
}
return 0;
}
int hinic3_set_interrupt_cfg(struct hinic3_hwdev *hwdev,
struct hinic3_interrupt_info info)
{
struct hinic3_interrupt_info temp_info;
int err;
temp_info.msix_index = info.msix_index;
err = hinic3_get_interrupt_cfg(hwdev, &temp_info);
if (err)
return err;
info.lli_credit_limit = temp_info.lli_credit_limit;
info.lli_timer_cfg = temp_info.lli_timer_cfg;
if (!info.interrupt_coalesc_set) {
info.pending_limit = temp_info.pending_limit;
info.coalesc_timer_cfg = temp_info.coalesc_timer_cfg;
info.resend_timer_cfg = temp_info.resend_timer_cfg;
}
return hinic3_set_interrupt_cfg_direct(hwdev, &info);
}
int hinic3_func_reset(struct hinic3_hwdev *hwdev, u16 func_id, u64 reset_flag)
{
struct comm_cmd_func_reset func_reset = {};
struct mgmt_msg_params msg_params = {};
int err;
func_reset.func_id = func_id;
func_reset.reset_flag = reset_flag;
mgmt_msg_params_init_default(&msg_params, &func_reset,
sizeof(func_reset));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_FUNC_RESET, &msg_params);
if (err || func_reset.head.status) {
dev_err(hwdev->dev, "Failed to reset func resources, reset_flag 0x%llx, err: %d, status: 0x%x\n",
reset_flag, err, func_reset.head.status);
return -EIO;
}
return 0;
}
static int hinic3_comm_features_nego(struct hinic3_hwdev *hwdev, u8 opcode,
u64 *s_feature, u16 size)
{
struct comm_cmd_feature_nego feature_nego = {};
struct mgmt_msg_params msg_params = {};
int err;
feature_nego.func_id = hinic3_global_func_id(hwdev);
feature_nego.opcode = opcode;
if (opcode == MGMT_MSG_CMD_OP_SET)
memcpy(feature_nego.s_feature, s_feature,
array_size(size, sizeof(u64)));
mgmt_msg_params_init_default(&msg_params, &feature_nego,
sizeof(feature_nego));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_FEATURE_NEGO, &msg_params);
if (err || feature_nego.head.status) {
dev_err(hwdev->dev, "Failed to negotiate feature, err: %d, status: 0x%x\n",
err, feature_nego.head.status);
return -EINVAL;
}
if (opcode == MGMT_MSG_CMD_OP_GET)
memcpy(s_feature, feature_nego.s_feature,
array_size(size, sizeof(u64)));
return 0;
}
int hinic3_get_comm_features(struct hinic3_hwdev *hwdev, u64 *s_feature,
u16 size)
{
return hinic3_comm_features_nego(hwdev, MGMT_MSG_CMD_OP_GET, s_feature,
size);
}
int hinic3_set_comm_features(struct hinic3_hwdev *hwdev, u64 *s_feature,
u16 size)
{
return hinic3_comm_features_nego(hwdev, MGMT_MSG_CMD_OP_SET, s_feature,
size);
}
int hinic3_get_global_attr(struct hinic3_hwdev *hwdev,
struct comm_global_attr *attr)
{
struct comm_cmd_get_glb_attr get_attr = {};
struct mgmt_msg_params msg_params = {};
int err;
mgmt_msg_params_init_default(&msg_params, &get_attr, sizeof(get_attr));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_GET_GLOBAL_ATTR, &msg_params);
if (err || get_attr.head.status) {
dev_err(hwdev->dev,
"Failed to get global attribute, err: %d, status: 0x%x\n",
err, get_attr.head.status);
return -EIO;
}
memcpy(attr, &get_attr.attr, sizeof(*attr));
return 0;
}
int hinic3_set_func_svc_used_state(struct hinic3_hwdev *hwdev, u16 svc_type,
u8 state)
{
struct comm_cmd_set_func_svc_used_state used_state = {};
struct mgmt_msg_params msg_params = {};
int err;
used_state.func_id = hinic3_global_func_id(hwdev);
used_state.svc_type = svc_type;
used_state.used_state = state;
mgmt_msg_params_init_default(&msg_params, &used_state,
sizeof(used_state));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_SET_FUNC_SVC_USED_STATE,
&msg_params);
if (err || used_state.head.status) {
dev_err(hwdev->dev,
"Failed to set func service used state, err: %d, status: 0x%x\n",
err, used_state.head.status);
return -EIO;
}
return 0;
}
int hinic3_set_dma_attr_tbl(struct hinic3_hwdev *hwdev, u8 entry_idx, u8 st,
u8 at, u8 ph, u8 no_snooping, u8 tph_en)
{
struct comm_cmd_set_dma_attr dma_attr = {};
struct mgmt_msg_params msg_params = {};
int err;
dma_attr.func_id = hinic3_global_func_id(hwdev);
dma_attr.entry_idx = entry_idx;
dma_attr.st = st;
dma_attr.at = at;
dma_attr.ph = ph;
dma_attr.no_snooping = no_snooping;
dma_attr.tph_en = tph_en;
mgmt_msg_params_init_default(&msg_params, &dma_attr, sizeof(dma_attr));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_SET_DMA_ATTR, &msg_params);
if (err || dma_attr.head.status) {
dev_err(hwdev->dev, "Failed to set dma attr, err: %d, status: 0x%x\n",
err, dma_attr.head.status);
return -EIO;
}
return 0;
}
int hinic3_set_wq_page_size(struct hinic3_hwdev *hwdev, u16 func_idx,
u32 page_size)
{
struct comm_cmd_cfg_wq_page_size page_size_info = {};
struct mgmt_msg_params msg_params = {};
int err;
page_size_info.func_id = func_idx;
page_size_info.page_size = ilog2(page_size / HINIC3_MIN_PAGE_SIZE);
page_size_info.opcode = MGMT_MSG_CMD_OP_SET;
mgmt_msg_params_init_default(&msg_params, &page_size_info,
sizeof(page_size_info));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_CFG_PAGESIZE, &msg_params);
if (err || page_size_info.head.status) {
dev_err(hwdev->dev,
"Failed to set wq page size, err: %d, status: 0x%x\n",
err, page_size_info.head.status);
return -EFAULT;
}
return 0;
}
int hinic3_set_cmdq_depth(struct hinic3_hwdev *hwdev, u16 cmdq_depth)
{
struct comm_cmd_set_root_ctxt root_ctxt = {};
struct mgmt_msg_params msg_params = {};
int err;
root_ctxt.func_id = hinic3_global_func_id(hwdev);
root_ctxt.set_cmdq_depth = 1;
root_ctxt.cmdq_depth = ilog2(cmdq_depth);
mgmt_msg_params_init_default(&msg_params, &root_ctxt,
sizeof(root_ctxt));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_SET_VAT, &msg_params);
if (err || root_ctxt.head.status) {
dev_err(hwdev->dev,
"Failed to set cmdq depth, err: %d, status: 0x%x\n",
err, root_ctxt.head.status);
return -EFAULT;
}
return 0;
}
#define HINIC3_FLR_TIMEOUT 1000
static enum hinic3_wait_return hinic3_check_flr_finish_handler(void *priv_data)
{
struct hinic3_hwdev *hwdev = priv_data;
struct hinic3_hwif *hwif = hwdev->hwif;
enum hinic3_pf_status status;
if (!hwdev->chip_present_flag)
return HINIC3_WAIT_PROCESS_ERR;
status = hinic3_get_pf_status(hwif);
if (status == HINIC3_PF_STATUS_FLR_FINISH_FLAG) {
hinic3_set_pf_status(hwif, HINIC3_PF_STATUS_ACTIVE_FLAG);
return HINIC3_WAIT_PROCESS_CPL;
}
return HINIC3_WAIT_PROCESS_WAITING;
}
static int hinic3_wait_for_flr_finish(struct hinic3_hwdev *hwdev)
{
return hinic3_wait_for_timeout(hwdev, hinic3_check_flr_finish_handler,
HINIC3_FLR_TIMEOUT, 0xa * USEC_PER_MSEC);
}
#define HINIC3_WAIT_CMDQ_IDLE_TIMEOUT 5000
static enum hinic3_wait_return check_cmdq_stop_handler(void *priv_data)
{
struct hinic3_hwdev *hwdev = priv_data;
enum hinic3_cmdq_type cmdq_type;
struct hinic3_cmdqs *cmdqs;
/* Stop waiting when card unpresent */
if (!hwdev->chip_present_flag)
return HINIC3_WAIT_PROCESS_ERR;
cmdqs = hwdev->cmdqs;
for (cmdq_type = 0; cmdq_type < cmdqs->cmdq_num; cmdq_type++) {
if (!hinic3_cmdq_idle(&cmdqs->cmdq[cmdq_type]))
return HINIC3_WAIT_PROCESS_WAITING;
}
return HINIC3_WAIT_PROCESS_CPL;
}
static int wait_cmdq_stop(struct hinic3_hwdev *hwdev)
{
struct hinic3_cmdqs *cmdqs = hwdev->cmdqs;
enum hinic3_cmdq_type cmdq_type;
int err;
if (!(cmdqs->status & HINIC3_CMDQ_ENABLE))
return 0;
cmdqs->status &= ~HINIC3_CMDQ_ENABLE;
err = hinic3_wait_for_timeout(hwdev, check_cmdq_stop_handler,
HINIC3_WAIT_CMDQ_IDLE_TIMEOUT,
USEC_PER_MSEC);
if (err)
goto err_reenable_cmdq;
return 0;
err_reenable_cmdq:
for (cmdq_type = 0; cmdq_type < cmdqs->cmdq_num; cmdq_type++) {
if (!hinic3_cmdq_idle(&cmdqs->cmdq[cmdq_type]))
dev_err(hwdev->dev, "Cmdq %d is busy\n", cmdq_type);
}
cmdqs->status |= HINIC3_CMDQ_ENABLE;
return err;
}
int hinic3_func_rx_tx_flush(struct hinic3_hwdev *hwdev)
{
struct comm_cmd_clear_resource clear_db = {};
struct comm_cmd_clear_resource clr_res = {};
struct hinic3_hwif *hwif = hwdev->hwif;
struct mgmt_msg_params msg_params = {};
int ret = 0;
int err;
if (!hwdev->chip_present_flag)
return 0;
err = wait_cmdq_stop(hwdev);
if (err) {
dev_warn(hwdev->dev, "CMDQ is still working, CMDQ timeout value is unreasonable\n");
ret = err;
}
hinic3_toggle_doorbell(hwif, DISABLE_DOORBELL);
clear_db.func_id = hwif->attr.func_global_idx;
mgmt_msg_params_init_default(&msg_params, &clear_db, sizeof(clear_db));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_FLUSH_DOORBELL, &msg_params);
if (err || clear_db.head.status) {
dev_warn(hwdev->dev, "Failed to flush doorbell, err: %d, status: 0x%x\n",
err, clear_db.head.status);
if (err)
ret = err;
else
ret = -EFAULT;
}
hinic3_set_pf_status(hwif, HINIC3_PF_STATUS_FLR_START_FLAG);
clr_res.func_id = hwif->attr.func_global_idx;
msg_params.buf_in = &clr_res;
msg_params.in_size = sizeof(clr_res);
err = hinic3_send_mbox_to_mgmt_no_ack(hwdev, MGMT_MOD_COMM,
COMM_CMD_START_FLUSH,
&msg_params);
if (err) {
dev_warn(hwdev->dev, "Failed to notice flush message, err: %d\n",
err);
ret = err;
}
if (HINIC3_FUNC_TYPE(hwdev) != HINIC3_FUNC_TYPE_VF) {
err = hinic3_wait_for_flr_finish(hwdev);
if (err) {
dev_warn(hwdev->dev, "Wait firmware FLR timeout\n");
ret = err;
}
}
hinic3_toggle_doorbell(hwif, ENABLE_DOORBELL);
err = hinic3_reinit_cmdq_ctxts(hwdev);
if (err) {
dev_warn(hwdev->dev, "Failed to reinit cmdq\n");
ret = err;
}
return ret;
}
int hinic3_set_bdf_ctxt(struct hinic3_hwdev *hwdev,
struct comm_cmd_bdf_info *bdf_info)
{
struct mgmt_msg_params msg_params = {};
int err;
mgmt_msg_params_init_default(&msg_params, bdf_info, sizeof(*bdf_info));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_SEND_BDF_INFO, &msg_params);
if (err || bdf_info->head.status) {
dev_err(hwdev->dev,
"Failed to set bdf info to fw, err: %d, status: 0x%x\n",
err, bdf_info->head.status);
return -EFAULT;
}
return 0;
}
static int hinic3_sync_time(struct hinic3_hwdev *hwdev, u64 time)
{
struct comm_cmd_sync_time time_info = {};
struct mgmt_msg_params msg_params = {};
int err;
time_info.mstime = time;
mgmt_msg_params_init_default(&msg_params, &time_info,
sizeof(time_info));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_SYNC_TIME, &msg_params);
if (err || time_info.head.status) {
dev_err(hwdev->dev,
"Failed to sync time to mgmt, err: %d, status: 0x%x\n",
err, time_info.head.status);
return -EFAULT;
}
return 0;
}
void hinic3_sync_time_to_fw(struct hinic3_hwdev *hwdev)
{
struct timespec64 ts = {};
u64 time;
int err;
ktime_get_real_ts64(&ts);
time = (u64)(ts.tv_sec * MSEC_PER_SEC + ts.tv_nsec / NSEC_PER_MSEC);
err = hinic3_sync_time(hwdev, time);
if (err)
dev_err(hwdev->dev,
"Synchronize UTC time to firmware failed, err=%d\n",
err);
}
static int get_hw_rx_buf_size_idx(int rx_buf_sz, u16 *buf_sz_idx)
{
/* Supported RX buffer sizes in bytes. Configured by array index. */
static const int supported_sizes[16] = {
[0] = 32, [1] = 64, [2] = 96, [3] = 128,
[4] = 192, [5] = 256, [6] = 384, [7] = 512,
[8] = 768, [9] = 1024, [10] = 1536, [11] = 2048,
[12] = 3072, [13] = 4096, [14] = 8192, [15] = 16384,
};
u16 idx;
/* Scan from biggest to smallest. Choose supported size that is equal or
* smaller. For smaller value HW will under-utilize posted buffers. For
* bigger value HW may overrun posted buffers.
*/
idx = ARRAY_SIZE(supported_sizes);
while (idx > 0) {
idx--;
if (supported_sizes[idx] <= rx_buf_sz) {
*buf_sz_idx = idx;
return 0;
}
}
return -EINVAL;
}
int hinic3_set_root_ctxt(struct hinic3_hwdev *hwdev, u32 rq_depth, u32 sq_depth,
int rx_buf_sz)
{
struct comm_cmd_set_root_ctxt root_ctxt = {};
struct mgmt_msg_params msg_params = {};
u16 buf_sz_idx;
int err;
err = get_hw_rx_buf_size_idx(rx_buf_sz, &buf_sz_idx);
if (err)
return err;
root_ctxt.func_id = hinic3_global_func_id(hwdev);
root_ctxt.set_cmdq_depth = 0;
root_ctxt.cmdq_depth = 0;
root_ctxt.lro_en = 1;
root_ctxt.rq_depth = ilog2(rq_depth);
root_ctxt.rx_buf_sz = buf_sz_idx;
root_ctxt.sq_depth = ilog2(sq_depth);
mgmt_msg_params_init_default(&msg_params, &root_ctxt,
sizeof(root_ctxt));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_SET_VAT, &msg_params);
if (err || root_ctxt.head.status) {
dev_err(hwdev->dev,
"Failed to set root context, err: %d, status: 0x%x\n",
err, root_ctxt.head.status);
return -EFAULT;
}
return 0;
}
int hinic3_clean_root_ctxt(struct hinic3_hwdev *hwdev)
{
struct comm_cmd_set_root_ctxt root_ctxt = {};
struct mgmt_msg_params msg_params = {};
int err;
root_ctxt.func_id = hinic3_global_func_id(hwdev);
mgmt_msg_params_init_default(&msg_params, &root_ctxt,
sizeof(root_ctxt));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_SET_VAT, &msg_params);
if (err || root_ctxt.head.status) {
dev_err(hwdev->dev,
"Failed to set root context, err: %d, status: 0x%x\n",
err, root_ctxt.head.status);
return -EFAULT;
}
return 0;
}
#define HINIC3_FW_VER_TYPE_MPU 1
int hinic3_get_mgmt_version(struct hinic3_hwdev *hwdev, u8 *mgmt_ver,
u8 version_size)
{
struct comm_cmd_get_fw_version fw_ver = {};
struct mgmt_msg_params msg_params = {};
int err;
fw_ver.fw_type = HINIC3_FW_VER_TYPE_MPU;
mgmt_msg_params_init_default(&msg_params, &fw_ver, sizeof(fw_ver));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_COMM,
COMM_CMD_GET_FW_VERSION, &msg_params);
if (err || fw_ver.head.status) {
dev_err(hwdev->dev,
"Failed to get fw version, err: %d, status: 0x%x\n",
err, fw_ver.head.status);
return -EFAULT;
}
snprintf(mgmt_ver, version_size, "%s", fw_ver.ver);
return 0;
}
@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_HW_COMM_H_
#define _HINIC3_HW_COMM_H_
#include "hinic3_hw_intf.h"
struct hinic3_hwdev;
#define HINIC3_WQ_PAGE_SIZE_ORDER 8
struct hinic3_interrupt_info {
u32 lli_set;
u32 interrupt_coalesc_set;
u16 msix_index;
u8 lli_credit_limit;
u8 lli_timer_cfg;
u8 pending_limit;
u8 coalesc_timer_cfg;
u8 resend_timer_cfg;
};
int hinic3_set_interrupt_cfg_direct(struct hinic3_hwdev *hwdev,
const struct hinic3_interrupt_info *info);
int hinic3_set_interrupt_cfg(struct hinic3_hwdev *hwdev,
struct hinic3_interrupt_info info);
int hinic3_func_reset(struct hinic3_hwdev *hwdev, u16 func_id, u64 reset_flag);
int hinic3_get_comm_features(struct hinic3_hwdev *hwdev, u64 *s_feature,
u16 size);
int hinic3_set_comm_features(struct hinic3_hwdev *hwdev, u64 *s_feature,
u16 size);
int hinic3_get_global_attr(struct hinic3_hwdev *hwdev,
struct comm_global_attr *attr);
int hinic3_set_func_svc_used_state(struct hinic3_hwdev *hwdev, u16 svc_type,
u8 state);
int hinic3_set_dma_attr_tbl(struct hinic3_hwdev *hwdev, u8 entry_idx, u8 st,
u8 at, u8 ph, u8 no_snooping, u8 tph_en);
int hinic3_set_wq_page_size(struct hinic3_hwdev *hwdev, u16 func_idx,
u32 page_size);
int hinic3_set_cmdq_depth(struct hinic3_hwdev *hwdev, u16 cmdq_depth);
int hinic3_func_rx_tx_flush(struct hinic3_hwdev *hwdev);
int hinic3_set_bdf_ctxt(struct hinic3_hwdev *hwdev,
struct comm_cmd_bdf_info *bdf_info);
void hinic3_sync_time_to_fw(struct hinic3_hwdev *hwdev);
int hinic3_set_root_ctxt(struct hinic3_hwdev *hwdev, u32 rq_depth, u32 sq_depth,
int rx_buf_sz);
int hinic3_clean_root_ctxt(struct hinic3_hwdev *hwdev);
int hinic3_get_mgmt_version(struct hinic3_hwdev *hwdev, u8 *mgmt_ver,
u8 version_size);
#endif
@@ -0,0 +1,300 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_HW_INTF_H_
#define _HINIC3_HW_INTF_H_
#include <linux/bits.h>
#include <linux/types.h>
#define MGMT_MSG_CMD_OP_SET 1
#define MGMT_MSG_CMD_OP_GET 0
#define MGMT_STATUS_PF_SET_VF_ALREADY 0x4
#define MGMT_STATUS_EXIST 0x6
#define MGMT_STATUS_CMD_UNSUPPORTED 0xFF
#define MGMT_MSG_POLLING_TIMEOUT 0
struct mgmt_msg_head {
u8 status;
u8 version;
u8 rsvd0[6];
};
struct mgmt_msg_params {
const void *buf_in;
u32 in_size;
void *buf_out;
u32 expected_out_size;
u32 timeout_ms;
};
/* CMDQ MODULE_TYPE */
enum mgmt_mod_type {
/* HW communication module */
MGMT_MOD_COMM = 0,
/* L2NIC module */
MGMT_MOD_L2NIC = 1,
/* Configuration module */
MGMT_MOD_CFGM = 7,
MGMT_MOD_HILINK = 14,
/* hardware max module id */
MGMT_MOD_HW_MAX = 20,
};
static inline void mgmt_msg_params_init_default(struct mgmt_msg_params *msg_params,
void *inout_buf, u32 buf_size)
{
msg_params->buf_in = inout_buf;
msg_params->buf_out = inout_buf;
msg_params->in_size = buf_size;
msg_params->expected_out_size = buf_size;
msg_params->timeout_ms = 0;
}
enum cfg_cmd {
CFG_CMD_GET_DEV_CAP = 0,
};
/* Device capabilities, defined by hw */
struct cfg_cmd_dev_cap {
struct mgmt_msg_head head;
u16 func_id;
u16 rsvd1;
/* Public resources */
u8 host_id;
u8 ep_id;
u8 er_id;
u8 port_id;
u16 host_total_func;
u8 host_pf_num;
u8 pf_id_start;
u16 host_vf_num;
u16 vf_id_start;
u8 host_oq_id_mask_val;
u8 timer_en;
u8 host_valid_bitmap;
u8 rsvd_host;
u16 svc_cap_en;
u16 max_vf;
u8 flexq_en;
u8 valid_cos_bitmap;
u8 port_cos_valid_bitmap;
u8 rsvd2[45];
/* l2nic */
u16 nic_max_sq_id;
u16 nic_max_rq_id;
u16 nic_default_num_queues;
u8 rsvd3[250];
};
/* COMM Commands between Driver to fw */
enum comm_cmd {
/* Commands for clearing FLR and resources */
COMM_CMD_FUNC_RESET = 0,
COMM_CMD_FEATURE_NEGO = 1,
COMM_CMD_FLUSH_DOORBELL = 2,
COMM_CMD_START_FLUSH = 3,
COMM_CMD_GET_GLOBAL_ATTR = 5,
COMM_CMD_SET_FUNC_SVC_USED_STATE = 7,
/* Driver Configuration Commands */
COMM_CMD_SET_CMDQ_CTXT = 20,
COMM_CMD_SET_VAT = 21,
COMM_CMD_CFG_PAGESIZE = 22,
COMM_CMD_CFG_MSIX_CTRL_REG = 23,
COMM_CMD_SET_CEQ_CTRL_REG = 24,
COMM_CMD_SET_DMA_ATTR = 25,
/* Commands for obtaining information */
COMM_CMD_GET_FW_VERSION = 60,
COMM_CMD_SYNC_TIME = 62,
COMM_CMD_SEND_BDF_INFO = 64,
};
struct comm_cmd_cfg_msix_ctrl_reg {
struct mgmt_msg_head head;
u16 func_id;
u8 opcode;
u8 rsvd1;
u16 msix_index;
u8 pending_cnt;
u8 coalesce_timer_cnt;
u8 resend_timer_cnt;
u8 lli_timer_cnt;
u8 lli_credit_cnt;
u8 rsvd2[5];
};
enum comm_func_reset_bits {
COMM_FUNC_RESET_BIT_FLUSH = BIT(0),
COMM_FUNC_RESET_BIT_MQM = BIT(1),
COMM_FUNC_RESET_BIT_SMF = BIT(2),
COMM_FUNC_RESET_BIT_PF_BW_CFG = BIT(3),
COMM_FUNC_RESET_BIT_COMM = BIT(10),
/* clear mbox and aeq, The COMM_FUNC_RESET_BIT_COMM bit must be set */
COMM_FUNC_RESET_BIT_COMM_MGMT_CH = BIT(11),
/* clear cmdq and ceq, The COMM_FUNC_RESET_BIT_COMM bit must be set */
COMM_FUNC_RESET_BIT_COMM_CMD_CH = BIT(12),
COMM_FUNC_RESET_BIT_NIC = BIT(13),
};
#define COMM_FUNC_RESET_FLAG \
(COMM_FUNC_RESET_BIT_COMM | COMM_FUNC_RESET_BIT_COMM_CMD_CH | \
COMM_FUNC_RESET_BIT_FLUSH | COMM_FUNC_RESET_BIT_MQM | \
COMM_FUNC_RESET_BIT_SMF | COMM_FUNC_RESET_BIT_PF_BW_CFG)
struct comm_cmd_func_reset {
struct mgmt_msg_head head;
u16 func_id;
u16 rsvd1[3];
u64 reset_flag;
};
#define COMM_MAX_FEATURE_QWORD 4
struct comm_cmd_feature_nego {
struct mgmt_msg_head head;
u16 func_id;
u8 opcode;
u8 rsvd;
u64 s_feature[COMM_MAX_FEATURE_QWORD];
};
struct comm_global_attr {
u8 max_host_num;
u8 max_pf_num;
u16 vf_id_start;
/* for api cmd to mgmt cpu */
u8 mgmt_host_node_id;
u8 cmdq_num;
u8 rsvd1[34];
};
struct comm_cmd_get_glb_attr {
struct mgmt_msg_head head;
struct comm_global_attr attr;
};
enum comm_func_svc_type {
COMM_FUNC_SVC_T_COMM = 0,
COMM_FUNC_SVC_T_NIC = 1,
};
struct comm_cmd_set_func_svc_used_state {
struct mgmt_msg_head head;
u16 func_id;
u16 svc_type;
u8 used_state;
u8 rsvd[35];
};
struct comm_cmd_set_dma_attr {
struct mgmt_msg_head head;
u16 func_id;
u8 entry_idx;
u8 st;
u8 at;
u8 ph;
u8 no_snooping;
u8 tph_en;
u32 resv1;
};
struct comm_cmd_set_ceq_ctrl_reg {
struct mgmt_msg_head head;
u16 func_id;
u16 q_id;
u32 ctrl0;
u32 ctrl1;
u32 rsvd1;
};
struct comm_cmd_cfg_wq_page_size {
struct mgmt_msg_head head;
u16 func_id;
u8 opcode;
/* real_size=4KB*2^page_size, range(0~20) must be checked by driver */
u8 page_size;
u32 rsvd1;
};
struct comm_cmd_set_root_ctxt {
struct mgmt_msg_head head;
u16 func_id;
u8 set_cmdq_depth;
u8 cmdq_depth;
u16 rx_buf_sz;
u8 lro_en;
u8 rsvd1;
u16 sq_depth;
u16 rq_depth;
u64 rsvd2;
};
struct comm_cmdq_ctxt_info {
__le64 curr_wqe_page_pfn;
__le64 wq_block_pfn;
};
struct comm_cmd_set_cmdq_ctxt {
struct mgmt_msg_head head;
u16 func_id;
u8 cmdq_id;
u8 rsvd1[5];
struct comm_cmdq_ctxt_info ctxt;
};
struct comm_cmd_clear_resource {
struct mgmt_msg_head head;
u16 func_id;
u16 rsvd1[3];
};
struct comm_cmd_sync_time {
struct mgmt_msg_head head;
u64 mstime;
u64 rsvd1;
};
struct comm_cmd_bdf_info {
struct mgmt_msg_head head;
u16 function_idx;
u8 rsvd1[2];
u8 bus;
u8 device;
u8 function;
u8 rsvd2[5];
};
#define COMM_FW_VERSION_LEN 16
#define COMM_FW_COMPILE_TIME_LEN 20
struct comm_cmd_get_fw_version {
struct mgmt_msg_head head;
u16 fw_type;
u16 rsvd1;
u8 ver[COMM_FW_VERSION_LEN];
u8 time[COMM_FW_COMPILE_TIME_LEN];
};
/* Services supported by HW. HW uses these values when delivering events.
* HW supports multiple services that are not yet supported by driver
* (e.g. RoCE).
*/
enum hinic3_service_type {
HINIC3_SERVICE_T_NIC = 0,
/* MAX is only used by SW for array sizes. */
HINIC3_SERVICE_T_MAX = 1,
};
#endif
@@ -0,0 +1,657 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include "hinic3_cmdq.h"
#include "hinic3_csr.h"
#include "hinic3_eqs.h"
#include "hinic3_hw_comm.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_mbox.h"
#include "hinic3_mgmt.h"
#define HINIC3_PCIE_SNOOP 0
#define HINIC3_PCIE_TPH_DISABLE 0
#define HINIC3_SYNFW_TIME_PERIOD (60 * 60 * 1000)
#define HINIC3_DMA_ATTR_INDIR_IDX_MASK GENMASK(9, 0)
#define HINIC3_DMA_ATTR_INDIR_IDX_SET(val, member) \
FIELD_PREP(HINIC3_DMA_ATTR_INDIR_##member##_MASK, val)
#define HINIC3_DMA_ATTR_ENTRY_ST_MASK GENMASK(7, 0)
#define HINIC3_DMA_ATTR_ENTRY_AT_MASK GENMASK(9, 8)
#define HINIC3_DMA_ATTR_ENTRY_PH_MASK GENMASK(11, 10)
#define HINIC3_DMA_ATTR_ENTRY_NO_SNOOPING_MASK BIT(12)
#define HINIC3_DMA_ATTR_ENTRY_TPH_EN_MASK BIT(13)
#define HINIC3_DMA_ATTR_ENTRY_SET(val, member) \
FIELD_PREP(HINIC3_DMA_ATTR_ENTRY_##member##_MASK, val)
#define HINIC3_PCIE_ST_DISABLE 0
#define HINIC3_PCIE_AT_DISABLE 0
#define HINIC3_PCIE_PH_DISABLE 0
#define HINIC3_PCIE_MSIX_ATTR_ENTRY 0
#define HINIC3_CHIP_PRESENT 1
#define HINIC3_CHIP_ABSENT 0
#define HINIC3_DEFAULT_EQ_MSIX_PENDING_LIMIT 0
#define HINIC3_DEFAULT_EQ_MSIX_COALESC_TIMER_CFG 0xFF
#define HINIC3_DEFAULT_EQ_MSIX_RESEND_TIMER_CFG 7
#define HINIC3_HWDEV_WQ_NAME "hinic3_hardware"
#define HINIC3_WQ_MAX_REQ 10
enum hinic3_hwdev_init_state {
HINIC3_HWDEV_MGMT_INITED = 1,
HINIC3_HWDEV_MBOX_INITED = 2,
HINIC3_HWDEV_CMDQ_INITED = 3,
};
static int hinic3_comm_aeqs_init(struct hinic3_hwdev *hwdev)
{
struct msix_entry aeq_msix_entries[HINIC3_MAX_AEQS];
u16 num_aeqs, resp_num_irq, i;
int err;
num_aeqs = hwdev->hwif->attr.num_aeqs;
if (num_aeqs > HINIC3_MAX_AEQS) {
dev_warn(hwdev->dev, "Adjust aeq num to %d\n",
HINIC3_MAX_AEQS);
num_aeqs = HINIC3_MAX_AEQS;
}
err = hinic3_alloc_irqs(hwdev, num_aeqs, aeq_msix_entries,
&resp_num_irq);
if (err) {
dev_err(hwdev->dev, "Failed to alloc aeq irqs, num_aeqs: %u\n",
num_aeqs);
return err;
}
if (resp_num_irq < num_aeqs) {
dev_warn(hwdev->dev, "Adjust aeq num to %u\n",
resp_num_irq);
num_aeqs = resp_num_irq;
}
err = hinic3_aeqs_init(hwdev, num_aeqs, aeq_msix_entries);
if (err) {
dev_err(hwdev->dev, "Failed to init aeqs\n");
goto err_free_irqs;
}
return 0;
err_free_irqs:
for (i = 0; i < num_aeqs; i++)
hinic3_free_irq(hwdev, aeq_msix_entries[i].vector);
return err;
}
static int hinic3_comm_ceqs_init(struct hinic3_hwdev *hwdev)
{
struct msix_entry ceq_msix_entries[HINIC3_MAX_CEQS];
u16 num_ceqs, resp_num_irq, i;
int err;
num_ceqs = hwdev->hwif->attr.num_ceqs;
if (num_ceqs > HINIC3_MAX_CEQS) {
dev_warn(hwdev->dev, "Adjust ceq num to %d\n",
HINIC3_MAX_CEQS);
num_ceqs = HINIC3_MAX_CEQS;
}
err = hinic3_alloc_irqs(hwdev, num_ceqs, ceq_msix_entries,
&resp_num_irq);
if (err) {
dev_err(hwdev->dev, "Failed to alloc ceq irqs, num_ceqs: %u\n",
num_ceqs);
return err;
}
if (resp_num_irq < num_ceqs) {
dev_warn(hwdev->dev, "Adjust ceq num to %u\n",
resp_num_irq);
num_ceqs = resp_num_irq;
}
err = hinic3_ceqs_init(hwdev, num_ceqs, ceq_msix_entries);
if (err) {
dev_err(hwdev->dev,
"Failed to init ceqs, err:%d\n", err);
goto err_free_irqs;
}
return 0;
err_free_irqs:
for (i = 0; i < num_ceqs; i++)
hinic3_free_irq(hwdev, ceq_msix_entries[i].vector);
return err;
}
static int hinic3_comm_mbox_init(struct hinic3_hwdev *hwdev)
{
int err;
err = hinic3_init_mbox(hwdev);
if (err)
return err;
hinic3_aeq_register_cb(hwdev, HINIC3_MBX_FROM_FUNC,
hinic3_mbox_func_aeqe_handler);
hinic3_aeq_register_cb(hwdev, HINIC3_MSG_FROM_FW,
hinic3_mgmt_msg_aeqe_handler);
set_bit(HINIC3_HWDEV_MBOX_INITED, &hwdev->func_state);
return 0;
}
static void hinic3_comm_mbox_free(struct hinic3_hwdev *hwdev)
{
spin_lock_bh(&hwdev->channel_lock);
clear_bit(HINIC3_HWDEV_MBOX_INITED, &hwdev->func_state);
spin_unlock_bh(&hwdev->channel_lock);
hinic3_aeq_unregister_cb(hwdev, HINIC3_MBX_FROM_FUNC);
hinic3_aeq_unregister_cb(hwdev, HINIC3_MSG_FROM_FW);
hinic3_free_mbox(hwdev);
}
static int init_aeqs_msix_attr(struct hinic3_hwdev *hwdev)
{
struct hinic3_aeqs *aeqs = hwdev->aeqs;
struct hinic3_interrupt_info info = {};
struct hinic3_eq *eq;
u16 q_id;
int err;
info.interrupt_coalesc_set = 1;
info.pending_limit = HINIC3_DEFAULT_EQ_MSIX_PENDING_LIMIT;
info.coalesc_timer_cfg = HINIC3_DEFAULT_EQ_MSIX_COALESC_TIMER_CFG;
info.resend_timer_cfg = HINIC3_DEFAULT_EQ_MSIX_RESEND_TIMER_CFG;
for (q_id = 0; q_id < aeqs->num_aeqs; q_id++) {
eq = &aeqs->aeq[q_id];
info.msix_index = eq->msix_entry_idx;
err = hinic3_set_interrupt_cfg_direct(hwdev, &info);
if (err) {
dev_err(hwdev->dev, "Set msix attr for aeq %d failed\n",
q_id);
return err;
}
}
return 0;
}
static int init_ceqs_msix_attr(struct hinic3_hwdev *hwdev)
{
struct hinic3_ceqs *ceqs = hwdev->ceqs;
struct hinic3_interrupt_info info = {};
struct hinic3_eq *eq;
u16 q_id;
int err;
info.interrupt_coalesc_set = 1;
info.pending_limit = HINIC3_DEFAULT_EQ_MSIX_PENDING_LIMIT;
info.coalesc_timer_cfg = HINIC3_DEFAULT_EQ_MSIX_COALESC_TIMER_CFG;
info.resend_timer_cfg = HINIC3_DEFAULT_EQ_MSIX_RESEND_TIMER_CFG;
for (q_id = 0; q_id < ceqs->num_ceqs; q_id++) {
eq = &ceqs->ceq[q_id];
info.msix_index = eq->msix_entry_idx;
err = hinic3_set_interrupt_cfg(hwdev, info);
if (err) {
dev_err(hwdev->dev, "Set msix attr for ceq %u failed\n",
q_id);
return err;
}
}
return 0;
}
static int hinic3_comm_pf_to_mgmt_init(struct hinic3_hwdev *hwdev)
{
int err;
if (HINIC3_IS_VF(hwdev))
return 0;
err = hinic3_pf_to_mgmt_init(hwdev);
if (err)
return err;
set_bit(HINIC3_HWDEV_MGMT_INITED, &hwdev->func_state);
return 0;
}
static void hinic3_comm_pf_to_mgmt_free(struct hinic3_hwdev *hwdev)
{
if (HINIC3_IS_VF(hwdev))
return;
spin_lock_bh(&hwdev->channel_lock);
clear_bit(HINIC3_HWDEV_MGMT_INITED, &hwdev->func_state);
spin_unlock_bh(&hwdev->channel_lock);
hinic3_aeq_unregister_cb(hwdev, HINIC3_MSG_FROM_FW);
hinic3_pf_to_mgmt_free(hwdev);
}
static int init_basic_mgmt_channel(struct hinic3_hwdev *hwdev)
{
int err;
err = hinic3_comm_aeqs_init(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init async event queues\n");
return err;
}
err = hinic3_comm_mbox_init(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init mailbox\n");
goto err_free_comm_aeqs;
}
err = init_aeqs_msix_attr(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init aeqs msix attr\n");
goto err_free_comm_mbox;
}
return 0;
err_free_comm_mbox:
hinic3_comm_mbox_free(hwdev);
err_free_comm_aeqs:
hinic3_aeqs_free(hwdev);
return err;
}
static void free_base_mgmt_channel(struct hinic3_hwdev *hwdev)
{
hinic3_comm_mbox_free(hwdev);
hinic3_aeqs_free(hwdev);
}
static int dma_attr_table_init(struct hinic3_hwdev *hwdev)
{
u32 addr, val, dst_attr;
/* Indirect access, set entry_idx first */
addr = HINIC3_CSR_DMA_ATTR_INDIR_IDX_ADDR;
val = hinic3_hwif_read_reg(hwdev->hwif, addr);
val &= ~HINIC3_DMA_ATTR_ENTRY_AT_MASK;
val |= HINIC3_DMA_ATTR_INDIR_IDX_SET(HINIC3_PCIE_MSIX_ATTR_ENTRY, IDX);
hinic3_hwif_write_reg(hwdev->hwif, addr, val);
addr = HINIC3_CSR_DMA_ATTR_TBL_ADDR;
val = hinic3_hwif_read_reg(hwdev->hwif, addr);
dst_attr = HINIC3_DMA_ATTR_ENTRY_SET(HINIC3_PCIE_ST_DISABLE, ST) |
HINIC3_DMA_ATTR_ENTRY_SET(HINIC3_PCIE_AT_DISABLE, AT) |
HINIC3_DMA_ATTR_ENTRY_SET(HINIC3_PCIE_PH_DISABLE, PH) |
HINIC3_DMA_ATTR_ENTRY_SET(HINIC3_PCIE_SNOOP, NO_SNOOPING) |
HINIC3_DMA_ATTR_ENTRY_SET(HINIC3_PCIE_TPH_DISABLE, TPH_EN);
if (val == dst_attr)
return 0;
return hinic3_set_dma_attr_tbl(hwdev,
HINIC3_PCIE_MSIX_ATTR_ENTRY,
HINIC3_PCIE_ST_DISABLE,
HINIC3_PCIE_AT_DISABLE,
HINIC3_PCIE_PH_DISABLE,
HINIC3_PCIE_SNOOP,
HINIC3_PCIE_TPH_DISABLE);
}
static int init_basic_attributes(struct hinic3_hwdev *hwdev)
{
struct comm_global_attr glb_attr;
int err;
err = hinic3_func_reset(hwdev, hinic3_global_func_id(hwdev),
COMM_FUNC_RESET_FLAG);
if (err)
return err;
err = hinic3_get_comm_features(hwdev, hwdev->features,
COMM_MAX_FEATURE_QWORD);
if (err)
return err;
dev_dbg(hwdev->dev, "Comm hw features: 0x%llx\n", hwdev->features[0]);
err = hinic3_get_global_attr(hwdev, &glb_attr);
if (err)
return err;
err = hinic3_set_func_svc_used_state(hwdev, COMM_FUNC_SVC_T_COMM, 1);
if (err)
return err;
err = dma_attr_table_init(hwdev);
if (err)
return err;
hwdev->max_cmdq = min(glb_attr.cmdq_num, HINIC3_MAX_CMDQ_TYPES);
dev_dbg(hwdev->dev,
"global attribute: max_host: 0x%x, max_pf: 0x%x, vf_id_start: 0x%x, mgmt node id: 0x%x, cmdq_num: 0x%x\n",
glb_attr.max_host_num, glb_attr.max_pf_num,
glb_attr.vf_id_start, glb_attr.mgmt_host_node_id,
glb_attr.cmdq_num);
return 0;
}
static int hinic3_comm_cmdqs_init(struct hinic3_hwdev *hwdev)
{
int err;
err = hinic3_cmdqs_init(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init cmd queues\n");
return err;
}
hinic3_ceq_register_cb(hwdev, HINIC3_CMDQ, hinic3_cmdq_ceq_handler);
err = hinic3_set_cmdq_depth(hwdev, CMDQ_DEPTH);
if (err) {
dev_err(hwdev->dev, "Failed to set cmdq depth\n");
goto err_free_cmdqs;
}
set_bit(HINIC3_HWDEV_CMDQ_INITED, &hwdev->func_state);
return 0;
err_free_cmdqs:
hinic3_cmdqs_free(hwdev);
return err;
}
static void hinic3_comm_cmdqs_free(struct hinic3_hwdev *hwdev)
{
spin_lock_bh(&hwdev->channel_lock);
clear_bit(HINIC3_HWDEV_CMDQ_INITED, &hwdev->func_state);
spin_unlock_bh(&hwdev->channel_lock);
hinic3_ceq_unregister_cb(hwdev, HINIC3_CMDQ);
hinic3_cmdqs_free(hwdev);
}
static int init_cmdqs_channel(struct hinic3_hwdev *hwdev)
{
int err;
err = hinic3_comm_ceqs_init(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init completion event queues\n");
return err;
}
err = init_ceqs_msix_attr(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init ceqs msix attr\n");
goto err_free_ceqs;
}
hwdev->wq_page_size = HINIC3_MIN_PAGE_SIZE << HINIC3_WQ_PAGE_SIZE_ORDER;
err = hinic3_set_wq_page_size(hwdev, hinic3_global_func_id(hwdev),
hwdev->wq_page_size);
if (err) {
dev_err(hwdev->dev, "Failed to set wq page size\n");
goto err_free_ceqs;
}
err = hinic3_comm_cmdqs_init(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init cmd queues\n");
goto err_reset_wq_page_size;
}
return 0;
err_reset_wq_page_size:
hinic3_set_wq_page_size(hwdev, hinic3_global_func_id(hwdev),
HINIC3_MIN_PAGE_SIZE);
err_free_ceqs:
hinic3_ceqs_free(hwdev);
return err;
}
static void hinic3_free_cmdqs_channel(struct hinic3_hwdev *hwdev)
{
hinic3_comm_cmdqs_free(hwdev);
hinic3_set_wq_page_size(hwdev, hinic3_global_func_id(hwdev),
HINIC3_MIN_PAGE_SIZE);
hinic3_ceqs_free(hwdev);
}
static int hinic3_init_comm_ch(struct hinic3_hwdev *hwdev)
{
int err;
err = init_basic_mgmt_channel(hwdev);
if (err)
return err;
err = hinic3_comm_pf_to_mgmt_init(hwdev);
if (err)
goto err_free_basic_mgmt_ch;
err = init_basic_attributes(hwdev);
if (err)
goto err_free_comm_pf_to_mgmt;
err = init_cmdqs_channel(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init cmdq channel\n");
goto err_clear_func_svc_used_state;
}
hinic3_set_pf_status(hwdev->hwif, HINIC3_PF_STATUS_ACTIVE_FLAG);
return 0;
err_clear_func_svc_used_state:
hinic3_set_func_svc_used_state(hwdev, COMM_FUNC_SVC_T_COMM, 0);
err_free_comm_pf_to_mgmt:
hinic3_comm_pf_to_mgmt_free(hwdev);
err_free_basic_mgmt_ch:
free_base_mgmt_channel(hwdev);
return err;
}
static void hinic3_uninit_comm_ch(struct hinic3_hwdev *hwdev)
{
hinic3_set_pf_status(hwdev->hwif, HINIC3_PF_STATUS_INIT);
hinic3_free_cmdqs_channel(hwdev);
hinic3_set_func_svc_used_state(hwdev, COMM_FUNC_SVC_T_COMM, 0);
hinic3_comm_pf_to_mgmt_free(hwdev);
free_base_mgmt_channel(hwdev);
}
static void hinic3_auto_sync_time_work(struct work_struct *work)
{
struct delayed_work *delay = to_delayed_work(work);
struct hinic3_hwdev *hwdev;
hwdev = container_of(delay, struct hinic3_hwdev, sync_time_task);
hinic3_sync_time_to_fw(hwdev);
queue_delayed_work(hwdev->workq, &hwdev->sync_time_task,
msecs_to_jiffies(HINIC3_SYNFW_TIME_PERIOD));
}
static void hinic3_init_ppf_work(struct hinic3_hwdev *hwdev)
{
if (hinic3_ppf_idx(hwdev) != hinic3_global_func_id(hwdev))
return;
INIT_DELAYED_WORK(&hwdev->sync_time_task, hinic3_auto_sync_time_work);
queue_delayed_work(hwdev->workq, &hwdev->sync_time_task,
msecs_to_jiffies(HINIC3_SYNFW_TIME_PERIOD));
}
static void hinic3_free_ppf_work(struct hinic3_hwdev *hwdev)
{
if (hinic3_ppf_idx(hwdev) != hinic3_global_func_id(hwdev))
return;
disable_delayed_work_sync(&hwdev->sync_time_task);
}
static DEFINE_IDA(hinic3_adev_ida);
static int hinic3_adev_idx_alloc(void)
{
return ida_alloc(&hinic3_adev_ida, GFP_KERNEL);
}
static void hinic3_adev_idx_free(int id)
{
ida_free(&hinic3_adev_ida, id);
}
int hinic3_init_hwdev(struct pci_dev *pdev)
{
struct hinic3_pcidev *pci_adapter = pci_get_drvdata(pdev);
struct hinic3_hwdev *hwdev;
int err;
hwdev = kzalloc_obj(*hwdev);
if (!hwdev)
return -ENOMEM;
pci_adapter->hwdev = hwdev;
hwdev->adapter = pci_adapter;
hwdev->pdev = pci_adapter->pdev;
hwdev->dev = &pci_adapter->pdev->dev;
hwdev->func_state = 0;
hwdev->dev_id = hinic3_adev_idx_alloc();
spin_lock_init(&hwdev->channel_lock);
err = hinic3_init_hwif(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init hwif\n");
goto err_free_hwdev;
}
hwdev->chip_present_flag = HINIC3_CHIP_PRESENT;
hwdev->workq = alloc_workqueue(HINIC3_HWDEV_WQ_NAME, WQ_MEM_RECLAIM | WQ_PERCPU,
HINIC3_WQ_MAX_REQ);
if (!hwdev->workq) {
dev_err(hwdev->dev, "Failed to alloc hardware workq\n");
err = -ENOMEM;
goto err_free_hwif;
}
err = hinic3_init_cfg_mgmt(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init config mgmt\n");
goto err_destroy_workqueue;
}
err = hinic3_init_comm_ch(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init communication channel\n");
goto err_free_cfg_mgmt;
}
err = hinic3_init_capability(hwdev);
if (err) {
dev_err(hwdev->dev, "Failed to init capability\n");
goto err_uninit_comm_ch;
}
hinic3_init_ppf_work(hwdev);
err = hinic3_set_comm_features(hwdev, hwdev->features,
COMM_MAX_FEATURE_QWORD);
if (err) {
dev_err(hwdev->dev, "Failed to set comm features\n");
goto err_free_ppf_work;
}
return 0;
err_free_ppf_work:
hinic3_free_ppf_work(hwdev);
err_uninit_comm_ch:
hinic3_uninit_comm_ch(hwdev);
err_free_cfg_mgmt:
hinic3_free_cfg_mgmt(hwdev);
err_destroy_workqueue:
destroy_workqueue(hwdev->workq);
err_free_hwif:
hinic3_free_hwif(hwdev);
err_free_hwdev:
pci_adapter->hwdev = NULL;
hinic3_adev_idx_free(hwdev->dev_id);
kfree(hwdev);
return err;
}
void hinic3_free_hwdev(struct hinic3_hwdev *hwdev)
{
u64 drv_features[COMM_MAX_FEATURE_QWORD] = {};
hinic3_set_comm_features(hwdev, drv_features, COMM_MAX_FEATURE_QWORD);
hinic3_free_ppf_work(hwdev);
hinic3_func_rx_tx_flush(hwdev);
hinic3_uninit_comm_ch(hwdev);
hinic3_free_cfg_mgmt(hwdev);
destroy_workqueue(hwdev->workq);
hinic3_free_hwif(hwdev);
hinic3_adev_idx_free(hwdev->dev_id);
kfree(hwdev);
}
void hinic3_set_api_stop(struct hinic3_hwdev *hwdev)
{
struct hinic3_recv_msg *recv_resp_msg;
struct hinic3_mbox *mbox;
hwdev->chip_present_flag = HINIC3_CHIP_ABSENT;
spin_lock_bh(&hwdev->channel_lock);
if (HINIC3_IS_PF(hwdev) &&
test_bit(HINIC3_HWDEV_MGMT_INITED, &hwdev->func_state)) {
recv_resp_msg = &hwdev->pf_to_mgmt->recv_resp_msg_from_mgmt;
spin_lock_bh(&hwdev->pf_to_mgmt->sync_event_lock);
if (hwdev->pf_to_mgmt->event_flag == COMM_SEND_EVENT_START) {
complete(&recv_resp_msg->recv_done);
hwdev->pf_to_mgmt->event_flag = COMM_SEND_EVENT_TIMEOUT;
}
spin_unlock_bh(&hwdev->pf_to_mgmt->sync_event_lock);
}
if (test_bit(HINIC3_HWDEV_MBOX_INITED, &hwdev->func_state)) {
mbox = hwdev->mbox;
spin_lock(&mbox->mbox_lock);
if (mbox->event_flag == MBOX_EVENT_START)
mbox->event_flag = MBOX_EVENT_TIMEOUT;
spin_unlock(&mbox->mbox_lock);
}
if (test_bit(HINIC3_HWDEV_CMDQ_INITED, &hwdev->func_state))
hinic3_cmdq_flush_sync_cmd(hwdev);
spin_unlock_bh(&hwdev->channel_lock);
}
@@ -0,0 +1,103 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_HWDEV_H_
#define _HINIC3_HWDEV_H_
#include <linux/auxiliary_bus.h>
#include <linux/pci.h>
#include "hinic3_hw_intf.h"
struct hinic3_cmdqs;
struct hinic3_hwif;
enum hinic3_event_service_type {
HINIC3_EVENT_SRV_COMM = 0,
HINIC3_EVENT_SRV_NIC = 1
};
enum hinic3_comm_event_type {
HINIC3_COMM_EVENT_PCIE_LINK_DOWN = 0,
HINIC3_COMM_EVENT_HEART_LOST = 1,
HINIC3_COMM_EVENT_FAULT = 2,
HINIC3_COMM_EVENT_SRIOV_STATE_CHANGE = 3,
HINIC3_COMM_EVENT_CARD_REMOVE = 4,
HINIC3_COMM_EVENT_MGMT_WATCHDOG = 5,
};
enum hinic3_fault_err_level {
HINIC3_FAULT_LEVEL_SERIOUS_FLR = 3,
};
enum hinic3_fault_source_type {
HINIC3_FAULT_SRC_HW_PHY_FAULT = 9,
HINIC3_FAULT_SRC_TX_TIMEOUT = 22,
};
#define HINIC3_SRV_EVENT_TYPE(svc, type) (((svc) << 16) | (type))
/* driver-specific data of pci_dev */
struct hinic3_pcidev {
struct pci_dev *pdev;
struct hinic3_hwdev *hwdev;
/* Auxiliary devices */
struct hinic3_adev *hadev[HINIC3_SERVICE_T_MAX];
void __iomem *cfg_reg_base;
void __iomem *intr_reg_base;
void __iomem *mgmt_reg_base;
void __iomem *db_base;
u64 db_dwqe_len;
u64 db_base_phy;
/* lock for attach/detach uld */
struct mutex pdev_mutex;
unsigned long state;
};
struct hinic3_hwdev {
struct hinic3_pcidev *adapter;
struct pci_dev *pdev;
struct device *dev;
int dev_id;
struct hinic3_hwif *hwif;
struct hinic3_cfg_mgmt_info *cfg_mgmt;
struct hinic3_aeqs *aeqs;
struct hinic3_ceqs *ceqs;
struct hinic3_mbox *mbox;
struct hinic3_cmdqs *cmdqs;
struct delayed_work sync_time_task;
struct workqueue_struct *workq;
struct hinic3_msg_pf_to_mgmt *pf_to_mgmt;
/* protect channel init and uninit */
spinlock_t channel_lock;
u64 features[COMM_MAX_FEATURE_QWORD];
u32 wq_page_size;
u8 max_cmdq;
ulong func_state;
int chip_present_flag;
};
struct hinic3_event_info {
/* enum hinic3_event_service_type */
u16 service;
u16 type;
u8 event_data[104];
};
struct hinic3_adev {
struct auxiliary_device adev;
struct hinic3_hwdev *hwdev;
enum hinic3_service_type svc_type;
void (*event)(struct auxiliary_device *adev,
struct hinic3_event_info *event);
};
int hinic3_init_hwdev(struct pci_dev *pdev);
void hinic3_free_hwdev(struct hinic3_hwdev *hwdev);
void hinic3_set_api_stop(struct hinic3_hwdev *hwdev);
#endif
@@ -0,0 +1,543 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/io.h>
#include "hinic3_common.h"
#include "hinic3_csr.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#define HINIC3_HWIF_READY_TIMEOUT 10000
#define HINIC3_DB_AND_OUTBOUND_EN_TIMEOUT 60000
#define HINIC3_PCIE_LINK_DOWN 0xFFFFFFFF
/* config BAR4/5 4MB, DB & DWQE both 2MB */
#define HINIC3_DB_DWQE_SIZE 0x00400000
/* db/dwqe page size: 4K */
#define HINIC3_DB_PAGE_SIZE 0x00001000
#define HINIC3_DWQE_OFFSET 0x00000800
#define HINIC3_DB_MAX_AREAS (HINIC3_DB_DWQE_SIZE / HINIC3_DB_PAGE_SIZE)
#define HINIC3_MAX_MSIX_ENTRY 2048
#define HINIC3_AF0_FUNC_GLOBAL_IDX_MASK GENMASK(11, 0)
#define HINIC3_AF0_P2P_IDX_MASK GENMASK(16, 12)
#define HINIC3_AF0_PCI_INTF_IDX_MASK GENMASK(19, 17)
#define HINIC3_AF0_FUNC_TYPE_MASK BIT(28)
#define HINIC3_AF0_GET(val, member) \
FIELD_GET(HINIC3_AF0_##member##_MASK, val)
#define HINIC3_AF1_PPF_IDX_MASK GENMASK(5, 0)
#define HINIC3_AF1_AEQS_PER_FUNC_MASK GENMASK(9, 8)
#define HINIC3_AF1_MGMT_INIT_STATUS_MASK BIT(30)
#define HINIC3_AF1_GET(val, member) \
FIELD_GET(HINIC3_AF1_##member##_MASK, val)
#define HINIC3_AF2_CEQS_PER_FUNC_MASK GENMASK(8, 0)
#define HINIC3_AF2_IRQS_PER_FUNC_MASK GENMASK(26, 16)
#define HINIC3_AF2_GET(val, member) \
FIELD_GET(HINIC3_AF2_##member##_MASK, val)
#define HINIC3_AF3_GLOBAL_VF_ID_OF_PF_MASK GENMASK(27, 16)
#define HINIC3_AF3_GET(val, member) \
FIELD_GET(HINIC3_AF3_##member##_MASK, val)
#define HINIC3_AF4_DOORBELL_CTRL_MASK BIT(0)
#define HINIC3_AF4_GET(val, member) \
FIELD_GET(HINIC3_AF4_##member##_MASK, val)
#define HINIC3_AF4_SET(val, member) \
FIELD_PREP(HINIC3_AF4_##member##_MASK, val)
#define HINIC3_AF5_OUTBOUND_CTRL_MASK BIT(0)
#define HINIC3_AF5_GET(val, member) \
FIELD_GET(HINIC3_AF5_##member##_MASK, val)
#define HINIC3_AF6_PF_STATUS_MASK GENMASK(15, 0)
#define HINIC3_AF6_FUNC_MAX_SQ_MASK GENMASK(31, 23)
#define HINIC3_AF6_MSIX_FLEX_EN_MASK BIT(22)
#define HINIC3_AF6_SET(val, member) \
FIELD_PREP(HINIC3_AF6_##member##_MASK, val)
#define HINIC3_AF6_GET(val, member) \
FIELD_GET(HINIC3_AF6_##member##_MASK, val)
#define HINIC3_PPF_ELECTION_IDX_MASK GENMASK(5, 0)
#define HINIC3_PPF_ELECTION_SET(val, member) \
FIELD_PREP(HINIC3_PPF_ELECTION_##member##_MASK, val)
#define HINIC3_PPF_ELECTION_GET(val, member) \
FIELD_GET(HINIC3_PPF_ELECTION_##member##_MASK, val)
#define HINIC3_GET_REG_FLAG(reg) ((reg) & (~(HINIC3_REGS_FLAG_MASK)))
#define HINIC3_GET_REG_ADDR(reg) ((reg) & (HINIC3_REGS_FLAG_MASK))
static void __iomem *hinic3_reg_addr(struct hinic3_hwif *hwif, u32 reg)
{
void __iomem *addr;
if (HINIC3_GET_REG_FLAG(reg) == HINIC3_MGMT_REGS_FLAG)
addr = hwif->mgmt_regs_base + HINIC3_GET_REG_ADDR(reg);
else
addr = hwif->cfg_regs_base + HINIC3_GET_REG_ADDR(reg);
return addr;
}
u32 hinic3_hwif_read_reg(struct hinic3_hwif *hwif, u32 reg)
{
void __iomem *addr = hinic3_reg_addr(hwif, reg);
return ioread32be(addr);
}
void hinic3_hwif_write_reg(struct hinic3_hwif *hwif, u32 reg, u32 val)
{
void __iomem *addr = hinic3_reg_addr(hwif, reg);
iowrite32be(val, addr);
}
static enum hinic3_wait_return check_hwif_ready_handler(void *priv_data)
{
struct hinic3_hwdev *hwdev = priv_data;
u32 attr1;
attr1 = hinic3_hwif_read_reg(hwdev->hwif, HINIC3_CSR_FUNC_ATTR1_ADDR);
if (attr1 == HINIC3_PCIE_LINK_DOWN)
return HINIC3_WAIT_PROCESS_ERR;
return HINIC3_AF1_GET(attr1, MGMT_INIT_STATUS) ?
HINIC3_WAIT_PROCESS_CPL : HINIC3_WAIT_PROCESS_WAITING;
}
static int wait_hwif_ready(struct hinic3_hwdev *hwdev)
{
return hinic3_wait_for_timeout(hwdev, check_hwif_ready_handler,
HINIC3_HWIF_READY_TIMEOUT,
USEC_PER_MSEC);
}
/* Set attr struct from HW attr values. */
static void set_hwif_attr(struct hinic3_func_attr *attr, u32 attr0, u32 attr1,
u32 attr2, u32 attr3, u32 attr6)
{
attr->func_global_idx = HINIC3_AF0_GET(attr0, FUNC_GLOBAL_IDX);
attr->port_to_port_idx = HINIC3_AF0_GET(attr0, P2P_IDX);
attr->pci_intf_idx = HINIC3_AF0_GET(attr0, PCI_INTF_IDX);
attr->func_type = HINIC3_AF0_GET(attr0, FUNC_TYPE);
attr->ppf_idx = HINIC3_AF1_GET(attr1, PPF_IDX);
attr->num_aeqs = BIT(HINIC3_AF1_GET(attr1, AEQS_PER_FUNC));
attr->num_ceqs = HINIC3_AF2_GET(attr2, CEQS_PER_FUNC);
attr->num_irqs = HINIC3_AF2_GET(attr2, IRQS_PER_FUNC);
if (attr->num_irqs > HINIC3_MAX_MSIX_ENTRY)
attr->num_irqs = HINIC3_MAX_MSIX_ENTRY;
attr->global_vf_id_of_pf = HINIC3_AF3_GET(attr3, GLOBAL_VF_ID_OF_PF);
attr->num_sq = HINIC3_AF6_GET(attr6, FUNC_MAX_SQ);
attr->msix_flex_en = HINIC3_AF6_GET(attr6, MSIX_FLEX_EN);
}
/* Read attributes from HW and set attribute struct. */
static int init_hwif_attr(struct hinic3_hwdev *hwdev)
{
u32 attr0, attr1, attr2, attr3, attr6;
struct hinic3_func_attr *attr;
struct hinic3_hwif *hwif;
hwif = hwdev->hwif;
attr0 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR0_ADDR);
if (attr0 == HINIC3_PCIE_LINK_DOWN)
return -EFAULT;
attr1 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR1_ADDR);
if (attr1 == HINIC3_PCIE_LINK_DOWN)
return -EFAULT;
attr2 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR2_ADDR);
if (attr2 == HINIC3_PCIE_LINK_DOWN)
return -EFAULT;
attr3 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR3_ADDR);
if (attr3 == HINIC3_PCIE_LINK_DOWN)
return -EFAULT;
attr6 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR6_ADDR);
if (attr6 == HINIC3_PCIE_LINK_DOWN)
return -EFAULT;
attr = &hwif->attr;
set_hwif_attr(attr, attr0, attr1, attr2, attr3, attr6);
if (attr->func_type != HINIC3_FUNC_TYPE_VF &&
attr->func_type != HINIC3_FUNC_TYPE_PF) {
dev_err(hwdev->dev, "unexpected func_type %u\n",
attr->func_type);
return -EINVAL;
}
if (!hwif->attr.num_ceqs) {
dev_err(hwdev->dev, "Ceq num cfg in fw is zero\n");
return -EFAULT;
}
if (!hwif->attr.num_irqs) {
dev_err(hwdev->dev,
"Irq num cfg in fw is zero, msix_flex_en %d\n",
hwif->attr.msix_flex_en);
return -EFAULT;
}
return 0;
}
static enum hinic3_doorbell_ctrl hinic3_get_doorbell_ctrl_status(struct hinic3_hwif *hwif)
{
u32 attr4 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR4_ADDR);
return HINIC3_AF4_GET(attr4, DOORBELL_CTRL);
}
static enum hinic3_outbound_ctrl hinic3_get_outbound_ctrl_status(struct hinic3_hwif *hwif)
{
u32 attr5 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR5_ADDR);
return HINIC3_AF5_GET(attr5, OUTBOUND_CTRL);
}
void hinic3_toggle_doorbell(struct hinic3_hwif *hwif,
enum hinic3_doorbell_ctrl flag)
{
u32 addr, attr4;
addr = HINIC3_CSR_FUNC_ATTR4_ADDR;
attr4 = hinic3_hwif_read_reg(hwif, addr);
attr4 &= ~HINIC3_AF4_DOORBELL_CTRL_MASK;
attr4 |= HINIC3_AF4_SET(flag, DOORBELL_CTRL);
hinic3_hwif_write_reg(hwif, addr, attr4);
}
static void hinic3_set_ppf(struct hinic3_hwdev *hwdev)
{
struct hinic3_hwif *hwif = hwdev->hwif;
struct hinic3_func_attr *attr;
u32 addr, val;
if (HINIC3_IS_VF(hwdev))
return;
/* Read Modify Write */
attr = &hwif->attr;
addr = HINIC3_CSR_PPF_ELECTION_ADDR;
val = hinic3_hwif_read_reg(hwif, addr);
val &= ~HINIC3_PPF_ELECTION_IDX_MASK;
val |= HINIC3_PPF_ELECTION_SET(attr->func_global_idx, IDX);
hinic3_hwif_write_reg(hwif, addr, val);
/* Check PPF index */
val = hinic3_hwif_read_reg(hwif, addr);
attr->ppf_idx = HINIC3_PPF_ELECTION_GET(val, IDX);
}
static int db_area_idx_init(struct hinic3_hwif *hwif, u64 db_base_phy,
u8 __iomem *db_base, u64 db_dwqe_len)
{
struct hinic3_db_area *db_area = &hwif->db_area;
u32 db_max_areas;
hwif->db_base_phy = db_base_phy;
hwif->db_base = db_base;
hwif->db_dwqe_len = db_dwqe_len;
db_max_areas = db_dwqe_len > HINIC3_DB_DWQE_SIZE ?
HINIC3_DB_MAX_AREAS : db_dwqe_len / HINIC3_DB_PAGE_SIZE;
db_area->db_bitmap_array = bitmap_zalloc(db_max_areas, GFP_KERNEL);
if (!db_area->db_bitmap_array)
return -ENOMEM;
db_area->db_max_areas = db_max_areas;
spin_lock_init(&db_area->idx_lock);
return 0;
}
static void db_area_idx_free(struct hinic3_db_area *db_area)
{
bitmap_free(db_area->db_bitmap_array);
}
static int get_db_idx(struct hinic3_hwif *hwif, u32 *idx)
{
struct hinic3_db_area *db_area = &hwif->db_area;
u32 pg_idx;
spin_lock(&db_area->idx_lock);
pg_idx = find_first_zero_bit(db_area->db_bitmap_array,
db_area->db_max_areas);
if (pg_idx == db_area->db_max_areas) {
spin_unlock(&db_area->idx_lock);
return -ENOMEM;
}
set_bit(pg_idx, db_area->db_bitmap_array);
spin_unlock(&db_area->idx_lock);
*idx = pg_idx;
return 0;
}
static void free_db_idx(struct hinic3_hwif *hwif, u32 idx)
{
struct hinic3_db_area *db_area = &hwif->db_area;
spin_lock(&db_area->idx_lock);
clear_bit(idx, db_area->db_bitmap_array);
spin_unlock(&db_area->idx_lock);
}
void hinic3_free_db_addr(struct hinic3_hwdev *hwdev, const u8 __iomem *db_base)
{
struct hinic3_hwif *hwif;
uintptr_t distance;
u32 idx;
hwif = hwdev->hwif;
distance = db_base - hwif->db_base;
idx = distance / HINIC3_DB_PAGE_SIZE;
free_db_idx(hwif, idx);
}
int hinic3_alloc_db_addr(struct hinic3_hwdev *hwdev, void __iomem **db_base,
void __iomem **dwqe_base)
{
struct hinic3_hwif *hwif;
u8 __iomem *addr;
u32 idx;
int err;
hwif = hwdev->hwif;
err = get_db_idx(hwif, &idx);
if (err)
return err;
addr = hwif->db_base + idx * HINIC3_DB_PAGE_SIZE;
*db_base = addr;
if (dwqe_base)
*dwqe_base = addr + HINIC3_DWQE_OFFSET;
return 0;
}
void hinic3_set_msix_state(struct hinic3_hwdev *hwdev, u16 msix_idx,
enum hinic3_msix_state flag)
{
struct hinic3_hwif *hwif;
u8 int_msk = 1;
u32 mask_bits;
u32 addr;
hwif = hwdev->hwif;
if (flag)
mask_bits = HINIC3_MSI_CLR_INDIR_SET(int_msk, INT_MSK_SET);
else
mask_bits = HINIC3_MSI_CLR_INDIR_SET(int_msk, INT_MSK_CLR);
mask_bits = mask_bits |
HINIC3_MSI_CLR_INDIR_SET(msix_idx, SIMPLE_INDIR_IDX);
addr = HINIC3_CSR_FUNC_MSI_CLR_WR_ADDR;
hinic3_hwif_write_reg(hwif, addr, mask_bits);
}
static void disable_all_msix(struct hinic3_hwdev *hwdev)
{
u16 num_irqs = hwdev->hwif->attr.num_irqs;
u16 i;
for (i = 0; i < num_irqs; i++)
hinic3_set_msix_state(hwdev, i, HINIC3_MSIX_DISABLE);
}
void hinic3_msix_intr_clear_resend_bit(struct hinic3_hwdev *hwdev, u16 msix_idx,
u8 clear_resend_en)
{
struct hinic3_hwif *hwif;
u32 msix_ctrl, addr;
hwif = hwdev->hwif;
msix_ctrl = HINIC3_MSI_CLR_INDIR_SET(msix_idx, SIMPLE_INDIR_IDX) |
HINIC3_MSI_CLR_INDIR_SET(clear_resend_en, RESEND_TIMER_CLR);
addr = HINIC3_CSR_FUNC_MSI_CLR_WR_ADDR;
hinic3_hwif_write_reg(hwif, addr, msix_ctrl);
}
void hinic3_set_msix_auto_mask_state(struct hinic3_hwdev *hwdev, u16 msix_idx,
enum hinic3_msix_auto_mask flag)
{
struct hinic3_hwif *hwif;
u32 mask_bits;
u32 addr;
hwif = hwdev->hwif;
if (flag)
mask_bits = HINIC3_MSI_CLR_INDIR_SET(1, AUTO_MSK_SET);
else
mask_bits = HINIC3_MSI_CLR_INDIR_SET(1, AUTO_MSK_CLR);
mask_bits = mask_bits |
HINIC3_MSI_CLR_INDIR_SET(msix_idx, SIMPLE_INDIR_IDX);
addr = HINIC3_CSR_FUNC_MSI_CLR_WR_ADDR;
hinic3_hwif_write_reg(hwif, addr, mask_bits);
}
static enum hinic3_wait_return check_db_outbound_enable_handler(void *priv_data)
{
enum hinic3_outbound_ctrl outbound_ctrl;
struct hinic3_hwif *hwif = priv_data;
enum hinic3_doorbell_ctrl db_ctrl;
db_ctrl = hinic3_get_doorbell_ctrl_status(hwif);
outbound_ctrl = hinic3_get_outbound_ctrl_status(hwif);
if (outbound_ctrl == ENABLE_OUTBOUND && db_ctrl == ENABLE_DOORBELL)
return HINIC3_WAIT_PROCESS_CPL;
return HINIC3_WAIT_PROCESS_WAITING;
}
static int wait_until_doorbell_and_outbound_enabled(struct hinic3_hwif *hwif)
{
return hinic3_wait_for_timeout(hwif, check_db_outbound_enable_handler,
HINIC3_DB_AND_OUTBOUND_EN_TIMEOUT,
USEC_PER_MSEC);
}
void hinic3_set_pf_status(struct hinic3_hwif *hwif,
enum hinic3_pf_status status)
{
u32 attr6 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR6_ADDR);
attr6 &= ~HINIC3_AF6_PF_STATUS_MASK;
attr6 |= HINIC3_AF6_SET(status, PF_STATUS);
if (hwif->attr.func_type == HINIC3_FUNC_TYPE_VF)
return;
hinic3_hwif_write_reg(hwif, HINIC3_CSR_FUNC_ATTR6_ADDR, attr6);
}
enum hinic3_pf_status hinic3_get_pf_status(struct hinic3_hwif *hwif)
{
u32 attr6 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR6_ADDR);
return HINIC3_AF6_GET(attr6, PF_STATUS);
}
int hinic3_init_hwif(struct hinic3_hwdev *hwdev)
{
struct hinic3_pcidev *pci_adapter = hwdev->adapter;
struct hinic3_hwif *hwif;
u32 attr1, attr4, attr5;
int err;
hwif = kzalloc_obj(*hwif);
if (!hwif)
return -ENOMEM;
hwdev->hwif = hwif;
/* if function is VF, mgmt_regs_base will be NULL */
hwif->cfg_regs_base = pci_adapter->mgmt_reg_base ?
pci_adapter->cfg_reg_base :
(u8 __iomem *)pci_adapter->cfg_reg_base +
HINIC3_VF_CFG_REG_OFFSET;
hwif->intr_regs_base = pci_adapter->intr_reg_base;
hwif->mgmt_regs_base = pci_adapter->mgmt_reg_base;
err = db_area_idx_init(hwif, pci_adapter->db_base_phy,
pci_adapter->db_base,
pci_adapter->db_dwqe_len);
if (err) {
dev_err(hwdev->dev, "Failed to init db area.\n");
goto err_free_hwif;
}
err = wait_hwif_ready(hwdev);
if (err) {
attr1 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR1_ADDR);
dev_err(hwdev->dev, "Chip status is not ready, attr1:0x%x\n",
attr1);
goto err_free_db_area_idx;
}
err = init_hwif_attr(hwdev);
if (err) {
dev_err(hwdev->dev, "Init hwif attr failed\n");
goto err_free_db_area_idx;
}
err = wait_until_doorbell_and_outbound_enabled(hwif);
if (err) {
attr4 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR4_ADDR);
attr5 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_FUNC_ATTR5_ADDR);
dev_err(hwdev->dev, "HW doorbell/outbound is disabled, attr4 0x%x attr5 0x%x\n",
attr4, attr5);
goto err_free_db_area_idx;
}
hinic3_set_ppf(hwdev);
disable_all_msix(hwdev);
/* disable mgmt cpu from reporting any event */
hinic3_set_pf_status(hwdev->hwif, HINIC3_PF_STATUS_INIT);
dev_dbg(hwdev->dev, "global_func_idx: %u, func_type: %d, host_id: %u, ppf: %u\n",
hwif->attr.func_global_idx, hwif->attr.func_type,
hwif->attr.pci_intf_idx, hwif->attr.ppf_idx);
return 0;
err_free_db_area_idx:
db_area_idx_free(&hwif->db_area);
err_free_hwif:
kfree(hwif);
return err;
}
void hinic3_free_hwif(struct hinic3_hwdev *hwdev)
{
db_area_idx_free(&hwdev->hwif->db_area);
kfree(hwdev->hwif);
}
u16 hinic3_global_func_id(struct hinic3_hwdev *hwdev)
{
return hwdev->hwif->attr.func_global_idx;
}
u8 hinic3_pf_id_of_vf(struct hinic3_hwdev *hwdev)
{
return hwdev->hwif->attr.port_to_port_idx;
}
u16 hinic3_glb_pf_vf_offset(struct hinic3_hwdev *hwdev)
{
return hwdev->hwif->attr.global_vf_id_of_pf;
}
u8 hinic3_ppf_idx(struct hinic3_hwdev *hwdev)
{
return hwdev->hwif->attr.ppf_idx;
}
@@ -0,0 +1,113 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_HWIF_H_
#define _HINIC3_HWIF_H_
#include <linux/build_bug.h>
#include <linux/spinlock_types.h>
struct hinic3_hwdev;
enum hinic3_func_type {
HINIC3_FUNC_TYPE_PF = 0,
HINIC3_FUNC_TYPE_VF = 1,
};
struct hinic3_db_area {
unsigned long *db_bitmap_array;
u32 db_max_areas;
/* protect doorbell area alloc and free */
spinlock_t idx_lock;
};
struct hinic3_func_attr {
enum hinic3_func_type func_type;
u16 func_global_idx;
u16 global_vf_id_of_pf;
u16 num_irqs;
u16 num_sq;
u8 port_to_port_idx;
u8 pci_intf_idx;
u8 ppf_idx;
u8 num_aeqs;
u8 num_ceqs;
u8 msix_flex_en;
};
static_assert(sizeof(struct hinic3_func_attr) == 20);
struct hinic3_hwif {
u8 __iomem *cfg_regs_base;
u8 __iomem *intr_regs_base;
u8 __iomem *mgmt_regs_base;
u64 db_base_phy;
u64 db_dwqe_len;
u8 __iomem *db_base;
struct hinic3_db_area db_area;
struct hinic3_func_attr attr;
};
enum hinic3_outbound_ctrl {
ENABLE_OUTBOUND = 0x0,
DISABLE_OUTBOUND = 0x1,
};
enum hinic3_pf_status {
HINIC3_PF_STATUS_INIT = 0x0,
HINIC3_PF_STATUS_ACTIVE_FLAG = 0x11,
HINIC3_PF_STATUS_FLR_START_FLAG = 0x12,
HINIC3_PF_STATUS_FLR_FINISH_FLAG = 0x13,
};
enum hinic3_doorbell_ctrl {
ENABLE_DOORBELL = 0,
DISABLE_DOORBELL = 1,
};
enum hinic3_msix_state {
HINIC3_MSIX_ENABLE,
HINIC3_MSIX_DISABLE,
};
enum hinic3_msix_auto_mask {
HINIC3_CLR_MSIX_AUTO_MASK,
HINIC3_SET_MSIX_AUTO_MASK,
};
#define HINIC3_FUNC_TYPE(hwdev) ((hwdev)->hwif->attr.func_type)
#define HINIC3_IS_PF(hwdev) \
(HINIC3_FUNC_TYPE(hwdev) == HINIC3_FUNC_TYPE_PF)
#define HINIC3_IS_VF(hwdev) \
(HINIC3_FUNC_TYPE(hwdev) == HINIC3_FUNC_TYPE_VF)
u32 hinic3_hwif_read_reg(struct hinic3_hwif *hwif, u32 reg);
void hinic3_hwif_write_reg(struct hinic3_hwif *hwif, u32 reg, u32 val);
void hinic3_toggle_doorbell(struct hinic3_hwif *hwif,
enum hinic3_doorbell_ctrl flag);
int hinic3_alloc_db_addr(struct hinic3_hwdev *hwdev, void __iomem **db_base,
void __iomem **dwqe_base);
void hinic3_free_db_addr(struct hinic3_hwdev *hwdev, const u8 __iomem *db_base);
void hinic3_set_pf_status(struct hinic3_hwif *hwif,
enum hinic3_pf_status status);
enum hinic3_pf_status hinic3_get_pf_status(struct hinic3_hwif *hwif);
int hinic3_init_hwif(struct hinic3_hwdev *hwdev);
void hinic3_free_hwif(struct hinic3_hwdev *hwdev);
void hinic3_set_msix_state(struct hinic3_hwdev *hwdev, u16 msix_idx,
enum hinic3_msix_state flag);
void hinic3_msix_intr_clear_resend_bit(struct hinic3_hwdev *hwdev, u16 msix_idx,
u8 clear_resend_en);
void hinic3_set_msix_auto_mask_state(struct hinic3_hwdev *hwdev, u16 msix_idx,
enum hinic3_msix_auto_mask flag);
u16 hinic3_global_func_id(struct hinic3_hwdev *hwdev);
u8 hinic3_pf_id_of_vf(struct hinic3_hwdev *hwdev);
u16 hinic3_glb_pf_vf_offset(struct hinic3_hwdev *hwdev);
u8 hinic3_ppf_idx(struct hinic3_hwdev *hwdev);
#endif
@@ -0,0 +1,291 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/dim.h>
#include <linux/netdevice.h>
#include "hinic3_hw_comm.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_nic_dev.h"
#include "hinic3_rx.h"
#include "hinic3_tx.h"
#define HINIC3_COAL_PKT_SHIFT 5
static void hinic3_net_dim(struct hinic3_nic_dev *nic_dev,
struct hinic3_irq_cfg *irq_cfg)
{
struct hinic3_rxq *rxq = irq_cfg->rxq;
struct dim_sample sample = {};
if (!test_bit(HINIC3_INTF_UP, &nic_dev->flags) ||
!nic_dev->adaptive_rx_coal)
return;
dim_update_sample(irq_cfg->total_events, rxq->rxq_stats.packets,
rxq->rxq_stats.bytes, &sample);
net_dim(&rxq->dim, &sample);
}
static int hinic3_poll(struct napi_struct *napi, int budget)
{
struct hinic3_irq_cfg *irq_cfg =
container_of(napi, struct hinic3_irq_cfg, napi);
struct hinic3_nic_dev *nic_dev;
bool busy = false;
int work_done;
nic_dev = netdev_priv(irq_cfg->netdev);
busy |= hinic3_tx_poll(irq_cfg->txq, budget);
if (unlikely(!budget))
return 0;
work_done = hinic3_rx_poll(irq_cfg->rxq, budget);
busy |= work_done >= budget;
if (busy)
return budget;
if (likely(napi_complete_done(napi, work_done))) {
hinic3_net_dim(nic_dev, irq_cfg);
hinic3_set_msix_state(nic_dev->hwdev, irq_cfg->msix_entry_idx,
HINIC3_MSIX_ENABLE);
}
return work_done;
}
static void qp_add_napi(struct hinic3_irq_cfg *irq_cfg)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(irq_cfg->netdev);
netif_napi_add(nic_dev->netdev, &irq_cfg->napi, hinic3_poll);
napi_enable(&irq_cfg->napi);
}
static void qp_del_napi(struct hinic3_irq_cfg *irq_cfg)
{
napi_disable(&irq_cfg->napi);
netif_napi_del(&irq_cfg->napi);
}
static irqreturn_t qp_irq(int irq, void *data)
{
struct hinic3_irq_cfg *irq_cfg = data;
struct hinic3_nic_dev *nic_dev;
nic_dev = netdev_priv(irq_cfg->netdev);
hinic3_msix_intr_clear_resend_bit(nic_dev->hwdev,
irq_cfg->msix_entry_idx, 1);
irq_cfg->total_events++;
napi_schedule(&irq_cfg->napi);
return IRQ_HANDLED;
}
static int hinic3_request_irq(struct hinic3_irq_cfg *irq_cfg, u16 q_id)
{
struct hinic3_interrupt_info info = {};
struct hinic3_nic_dev *nic_dev;
struct net_device *netdev;
int err;
netdev = irq_cfg->netdev;
nic_dev = netdev_priv(netdev);
qp_add_napi(irq_cfg);
info.msix_index = irq_cfg->msix_entry_idx;
info.interrupt_coalesc_set = 1;
info.pending_limit = nic_dev->intr_coalesce[q_id].pending_limit;
info.coalesc_timer_cfg =
nic_dev->intr_coalesce[q_id].coalesce_timer_cfg;
info.resend_timer_cfg = nic_dev->intr_coalesce[q_id].resend_timer_cfg;
err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
if (err) {
netdev_err(netdev, "Failed to set RX interrupt coalescing attribute.\n");
qp_del_napi(irq_cfg);
return err;
}
err = request_irq(irq_cfg->irq_id, qp_irq, 0, irq_cfg->irq_name,
irq_cfg);
if (err) {
qp_del_napi(irq_cfg);
return err;
}
irq_set_affinity_hint(irq_cfg->irq_id, &irq_cfg->affinity_mask);
return 0;
}
static void hinic3_release_irq(struct hinic3_irq_cfg *irq_cfg)
{
irq_set_affinity_hint(irq_cfg->irq_id, NULL);
free_irq(irq_cfg->irq_id, irq_cfg);
}
static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
u8 coalesc_timer_cfg, u8 pending_limit)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_interrupt_info info = {};
int err;
if (q_id >= nic_dev->q_params.num_qps)
return 0;
info.interrupt_coalesc_set = 1;
info.coalesc_timer_cfg = coalesc_timer_cfg;
info.pending_limit = pending_limit;
info.msix_index = nic_dev->q_params.irq_cfg[q_id].msix_entry_idx;
info.resend_timer_cfg =
nic_dev->intr_coalesce[q_id].resend_timer_cfg;
err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
if (err) {
netdev_err(netdev,
"Failed to modify moderation for Queue: %u\n", q_id);
} else {
nic_dev->rxqs[q_id].last_coalesc_timer_cfg = coalesc_timer_cfg;
nic_dev->rxqs[q_id].last_pending_limit = pending_limit;
}
return err;
}
static void hinic3_update_queue_coal(struct net_device *netdev, u16 q_id,
u16 coal_timer, u16 coal_pkts)
{
struct hinic3_intr_coal_info *q_coal;
u8 coalesc_timer_cfg, pending_limit;
struct hinic3_nic_dev *nic_dev;
nic_dev = netdev_priv(netdev);
q_coal = &nic_dev->intr_coalesce[q_id];
coalesc_timer_cfg = (u8)coal_timer;
pending_limit = clamp_t(u8, coal_pkts >> HINIC3_COAL_PKT_SHIFT,
q_coal->rx_pending_limit_low,
q_coal->rx_pending_limit_high);
hinic3_set_interrupt_moder(nic_dev->netdev, q_id,
coalesc_timer_cfg, pending_limit);
}
static void hinic3_rx_dim_work(struct work_struct *work)
{
struct dim_cq_moder cur_moder;
struct hinic3_rxq *rxq;
struct dim *dim;
dim = container_of(work, struct dim, work);
rxq = container_of(dim, struct hinic3_rxq, dim);
cur_moder = net_dim_get_rx_moderation(dim->mode, dim->profile_ix);
hinic3_update_queue_coal(rxq->netdev, rxq->q_id,
cur_moder.usec, cur_moder.pkts);
dim->state = DIM_START_MEASURE;
}
int hinic3_qps_irq_init(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct pci_dev *pdev = nic_dev->pdev;
struct hinic3_irq_cfg *irq_cfg;
struct msix_entry *msix_entry;
u32 local_cpu;
u16 q_id;
int err;
for (q_id = 0; q_id < nic_dev->q_params.num_qps; q_id++) {
msix_entry = &nic_dev->qps_msix_entries[q_id];
irq_cfg = &nic_dev->q_params.irq_cfg[q_id];
irq_cfg->irq_id = msix_entry->vector;
irq_cfg->msix_entry_idx = msix_entry->entry;
irq_cfg->netdev = netdev;
irq_cfg->txq = &nic_dev->txqs[q_id];
irq_cfg->rxq = &nic_dev->rxqs[q_id];
nic_dev->rxqs[q_id].irq_cfg = irq_cfg;
local_cpu = cpumask_local_spread(q_id, dev_to_node(&pdev->dev));
cpumask_set_cpu(local_cpu, &irq_cfg->affinity_mask);
snprintf(irq_cfg->irq_name, sizeof(irq_cfg->irq_name),
"%s_qp%u", netdev->name, q_id);
err = hinic3_request_irq(irq_cfg, q_id);
if (err) {
netdev_err(netdev, "Failed to request Rx irq\n");
goto err_release_irqs;
}
INIT_WORK(&irq_cfg->rxq->dim.work, hinic3_rx_dim_work);
irq_cfg->rxq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_CQE;
netif_queue_set_napi(irq_cfg->netdev, q_id,
NETDEV_QUEUE_TYPE_RX, &irq_cfg->napi);
netif_queue_set_napi(irq_cfg->netdev, q_id,
NETDEV_QUEUE_TYPE_TX, &irq_cfg->napi);
hinic3_set_msix_auto_mask_state(nic_dev->hwdev,
irq_cfg->msix_entry_idx,
HINIC3_SET_MSIX_AUTO_MASK);
hinic3_set_msix_state(nic_dev->hwdev, irq_cfg->msix_entry_idx,
HINIC3_MSIX_ENABLE);
}
return 0;
err_release_irqs:
while (q_id > 0) {
q_id--;
irq_cfg = &nic_dev->q_params.irq_cfg[q_id];
qp_del_napi(irq_cfg);
netif_queue_set_napi(irq_cfg->netdev, q_id,
NETDEV_QUEUE_TYPE_RX, NULL);
netif_queue_set_napi(irq_cfg->netdev, q_id,
NETDEV_QUEUE_TYPE_TX, NULL);
hinic3_set_msix_state(nic_dev->hwdev, irq_cfg->msix_entry_idx,
HINIC3_MSIX_DISABLE);
hinic3_set_msix_auto_mask_state(nic_dev->hwdev,
irq_cfg->msix_entry_idx,
HINIC3_CLR_MSIX_AUTO_MASK);
hinic3_release_irq(irq_cfg);
disable_work_sync(&irq_cfg->rxq->dim.work);
}
return err;
}
void hinic3_qps_irq_uninit(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_irq_cfg *irq_cfg;
u16 q_id;
for (q_id = 0; q_id < nic_dev->q_params.num_qps; q_id++) {
irq_cfg = &nic_dev->q_params.irq_cfg[q_id];
qp_del_napi(irq_cfg);
netif_queue_set_napi(irq_cfg->netdev, q_id,
NETDEV_QUEUE_TYPE_RX, NULL);
netif_queue_set_napi(irq_cfg->netdev, q_id,
NETDEV_QUEUE_TYPE_TX, NULL);
hinic3_set_msix_state(nic_dev->hwdev, irq_cfg->msix_entry_idx,
HINIC3_MSIX_DISABLE);
hinic3_set_msix_auto_mask_state(nic_dev->hwdev,
irq_cfg->msix_entry_idx,
HINIC3_CLR_MSIX_AUTO_MASK);
hinic3_release_irq(irq_cfg);
disable_work_sync(&irq_cfg->rxq->dim.work);
}
}
@@ -0,0 +1,463 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/delay.h>
#include <linux/iopoll.h>
#include "hinic3_hw_cfg.h"
#include "hinic3_hw_comm.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_lld.h"
#include "hinic3_mgmt.h"
#include "hinic3_pci_id_tbl.h"
#define HINIC3_VF_PCI_CFG_REG_BAR 0
#define HINIC3_PF_PCI_CFG_REG_BAR 1
#define HINIC3_PCI_INTR_REG_BAR 2
/* Only PF has mgmt bar */
#define HINIC3_PCI_MGMT_REG_BAR 3
#define HINIC3_PCI_DB_BAR 4
#define HINIC3_IS_VF_DEV(pdev) ((pdev)->device == PCI_DEV_ID_HINIC3_VF)
#define HINIC3_EVENT_POLL_SLEEP_US 1000
#define HINIC3_EVENT_POLL_TIMEOUT_US 10000000
static struct hinic3_adev_device {
const char *name;
} hinic3_adev_devices[HINIC3_SERVICE_T_MAX] = {
[HINIC3_SERVICE_T_NIC] = {
.name = "nic",
},
};
static bool hinic3_adev_svc_supported(struct hinic3_hwdev *hwdev,
enum hinic3_service_type svc_type)
{
switch (svc_type) {
case HINIC3_SERVICE_T_NIC:
return hinic3_support_nic(hwdev);
default:
break;
}
return false;
}
static void hinic3_comm_adev_release(struct device *dev)
{
struct hinic3_adev *hadev = container_of(dev, struct hinic3_adev,
adev.dev);
kfree(hadev);
}
static struct hinic3_adev *hinic3_add_one_adev(struct hinic3_hwdev *hwdev,
enum hinic3_service_type svc_type)
{
struct hinic3_adev *hadev;
const char *svc_name;
int ret;
hadev = kzalloc_obj(*hadev);
if (!hadev)
return NULL;
svc_name = hinic3_adev_devices[svc_type].name;
hadev->adev.name = svc_name;
hadev->adev.id = hwdev->dev_id;
hadev->adev.dev.parent = hwdev->dev;
hadev->adev.dev.release = hinic3_comm_adev_release;
hadev->svc_type = svc_type;
hadev->hwdev = hwdev;
ret = auxiliary_device_init(&hadev->adev);
if (ret) {
dev_err(hwdev->dev, "failed init adev %s %u\n",
svc_name, hwdev->dev_id);
kfree(hadev);
return NULL;
}
ret = auxiliary_device_add(&hadev->adev);
if (ret) {
dev_err(hwdev->dev, "failed to add adev %s %u\n",
svc_name, hwdev->dev_id);
auxiliary_device_uninit(&hadev->adev);
return NULL;
}
return hadev;
}
static void hinic3_del_one_adev(struct hinic3_hwdev *hwdev,
enum hinic3_service_type svc_type)
{
struct hinic3_pcidev *pci_adapter = hwdev->adapter;
struct hinic3_adev *hadev;
int timeout;
bool state;
timeout = read_poll_timeout(test_and_set_bit, state, !state,
HINIC3_EVENT_POLL_SLEEP_US,
HINIC3_EVENT_POLL_TIMEOUT_US,
false, svc_type, &pci_adapter->state);
hadev = pci_adapter->hadev[svc_type];
auxiliary_device_delete(&hadev->adev);
auxiliary_device_uninit(&hadev->adev);
pci_adapter->hadev[svc_type] = NULL;
if (!timeout)
clear_bit(svc_type, &pci_adapter->state);
}
static int hinic3_attach_aux_devices(struct hinic3_hwdev *hwdev)
{
struct hinic3_pcidev *pci_adapter = hwdev->adapter;
enum hinic3_service_type svc_type;
mutex_lock(&pci_adapter->pdev_mutex);
for (svc_type = 0; svc_type < HINIC3_SERVICE_T_MAX; svc_type++) {
if (!hinic3_adev_svc_supported(hwdev, svc_type))
continue;
pci_adapter->hadev[svc_type] = hinic3_add_one_adev(hwdev,
svc_type);
if (!pci_adapter->hadev[svc_type])
goto err_del_adevs;
}
mutex_unlock(&pci_adapter->pdev_mutex);
return 0;
err_del_adevs:
while (svc_type > 0) {
svc_type--;
if (pci_adapter->hadev[svc_type]) {
hinic3_del_one_adev(hwdev, svc_type);
pci_adapter->hadev[svc_type] = NULL;
}
}
mutex_unlock(&pci_adapter->pdev_mutex);
return -ENOMEM;
}
static void hinic3_detach_aux_devices(struct hinic3_hwdev *hwdev)
{
struct hinic3_pcidev *pci_adapter = hwdev->adapter;
int i;
mutex_lock(&pci_adapter->pdev_mutex);
for (i = 0; i < ARRAY_SIZE(hinic3_adev_devices); i++) {
if (pci_adapter->hadev[i])
hinic3_del_one_adev(hwdev, i);
}
mutex_unlock(&pci_adapter->pdev_mutex);
}
struct hinic3_hwdev *hinic3_adev_get_hwdev(struct auxiliary_device *adev)
{
struct hinic3_adev *hadev;
hadev = container_of(adev, struct hinic3_adev, adev);
return hadev->hwdev;
}
void hinic3_adev_event_register(struct auxiliary_device *adev,
void (*event_handler)(struct auxiliary_device *adev,
struct hinic3_event_info *event))
{
struct hinic3_adev *hadev;
hadev = container_of(adev, struct hinic3_adev, adev);
hadev->event = event_handler;
}
void hinic3_adev_event_unregister(struct auxiliary_device *adev)
{
struct hinic3_adev *hadev;
hadev = container_of(adev, struct hinic3_adev, adev);
hadev->event = NULL;
}
static int hinic3_mapping_bar(struct pci_dev *pdev,
struct hinic3_pcidev *pci_adapter)
{
int cfg_bar;
cfg_bar = HINIC3_IS_VF_DEV(pdev) ?
HINIC3_VF_PCI_CFG_REG_BAR : HINIC3_PF_PCI_CFG_REG_BAR;
pci_adapter->cfg_reg_base = pci_ioremap_bar(pdev, cfg_bar);
if (!pci_adapter->cfg_reg_base) {
dev_err(&pdev->dev, "Failed to map configuration regs\n");
return -ENOMEM;
}
pci_adapter->intr_reg_base = pci_ioremap_bar(pdev,
HINIC3_PCI_INTR_REG_BAR);
if (!pci_adapter->intr_reg_base) {
dev_err(&pdev->dev, "Failed to map interrupt regs\n");
goto err_unmap_cfg_reg_base;
}
if (!HINIC3_IS_VF_DEV(pdev)) {
pci_adapter->mgmt_reg_base =
pci_ioremap_bar(pdev, HINIC3_PCI_MGMT_REG_BAR);
if (!pci_adapter->mgmt_reg_base) {
dev_err(&pdev->dev, "Failed to map mgmt regs\n");
goto err_unmap_intr_reg_base;
}
}
pci_adapter->db_base_phy = pci_resource_start(pdev, HINIC3_PCI_DB_BAR);
pci_adapter->db_dwqe_len = pci_resource_len(pdev, HINIC3_PCI_DB_BAR);
pci_adapter->db_base = pci_ioremap_bar(pdev, HINIC3_PCI_DB_BAR);
if (!pci_adapter->db_base) {
dev_err(&pdev->dev, "Failed to map doorbell regs\n");
goto err_unmap_mgmt_reg_base;
}
return 0;
err_unmap_mgmt_reg_base:
if (!HINIC3_IS_VF_DEV(pdev))
iounmap(pci_adapter->mgmt_reg_base);
err_unmap_intr_reg_base:
iounmap(pci_adapter->intr_reg_base);
err_unmap_cfg_reg_base:
iounmap(pci_adapter->cfg_reg_base);
return -ENOMEM;
}
static void hinic3_unmapping_bar(struct hinic3_pcidev *pci_adapter)
{
iounmap(pci_adapter->db_base);
if (!HINIC3_IS_VF_DEV(pci_adapter->pdev))
iounmap(pci_adapter->mgmt_reg_base);
iounmap(pci_adapter->intr_reg_base);
iounmap(pci_adapter->cfg_reg_base);
}
static int hinic3_pci_init(struct pci_dev *pdev)
{
struct hinic3_pcidev *pci_adapter;
int err;
pci_adapter = kzalloc_obj(*pci_adapter);
if (!pci_adapter)
return -ENOMEM;
pci_adapter->pdev = pdev;
mutex_init(&pci_adapter->pdev_mutex);
pci_set_drvdata(pdev, pci_adapter);
err = pci_enable_device(pdev);
if (err) {
dev_err(&pdev->dev, "Failed to enable PCI device\n");
goto err_free_pci_adapter;
}
err = pci_request_regions(pdev, HINIC3_NIC_DRV_NAME);
if (err) {
dev_err(&pdev->dev, "Failed to request regions\n");
goto err_disable_device;
}
pci_set_master(pdev);
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (err) {
dev_err(&pdev->dev, "Failed to set DMA mask\n");
goto err_release_regions;
}
return 0;
err_release_regions:
pci_clear_master(pdev);
pci_release_regions(pdev);
err_disable_device:
pci_disable_device(pdev);
err_free_pci_adapter:
pci_set_drvdata(pdev, NULL);
mutex_destroy(&pci_adapter->pdev_mutex);
kfree(pci_adapter);
return err;
}
static void hinic3_pci_uninit(struct pci_dev *pdev)
{
struct hinic3_pcidev *pci_adapter = pci_get_drvdata(pdev);
pci_clear_master(pdev);
pci_release_regions(pdev);
pci_disable_device(pdev);
pci_set_drvdata(pdev, NULL);
mutex_destroy(&pci_adapter->pdev_mutex);
kfree(pci_adapter);
}
static int hinic3_func_init(struct pci_dev *pdev,
struct hinic3_pcidev *pci_adapter)
{
int err;
err = hinic3_init_hwdev(pdev);
if (err) {
dev_err(&pdev->dev, "Failed to initialize hardware device\n");
return err;
}
if (HINIC3_IS_PF(pci_adapter->hwdev))
hinic3_sync_time_to_fw(pci_adapter->hwdev);
err = hinic3_attach_aux_devices(pci_adapter->hwdev);
if (err)
goto err_free_hwdev;
return 0;
err_free_hwdev:
hinic3_free_hwdev(pci_adapter->hwdev);
return err;
}
static void hinic3_func_uninit(struct pci_dev *pdev)
{
struct hinic3_pcidev *pci_adapter = pci_get_drvdata(pdev);
/* disable mgmt reporting before flushing mgmt work-queue. */
hinic3_set_pf_status(pci_adapter->hwdev->hwif, HINIC3_PF_STATUS_INIT);
hinic3_flush_mgmt_workq(pci_adapter->hwdev);
hinic3_detach_aux_devices(pci_adapter->hwdev);
hinic3_free_hwdev(pci_adapter->hwdev);
}
static int hinic3_probe_func(struct hinic3_pcidev *pci_adapter)
{
struct pci_dev *pdev = pci_adapter->pdev;
struct comm_cmd_bdf_info bdf_info = {};
int err;
err = hinic3_mapping_bar(pdev, pci_adapter);
if (err) {
dev_err(&pdev->dev, "Failed to map bar\n");
goto err_out;
}
err = hinic3_func_init(pdev, pci_adapter);
if (err)
goto err_unmap_bar;
if (HINIC3_IS_PF(pci_adapter->hwdev)) {
bdf_info.function_idx =
hinic3_global_func_id(pci_adapter->hwdev);
bdf_info.bus = pdev->bus->number;
bdf_info.device = PCI_SLOT(pdev->devfn);
bdf_info.function = PCI_FUNC(pdev->devfn);
err = hinic3_set_bdf_ctxt(pci_adapter->hwdev, &bdf_info);
if (err) {
dev_err(&pdev->dev, "Failed to set BDF info to fw\n");
goto err_uninit_func;
}
}
return 0;
err_uninit_func:
hinic3_func_uninit(pdev);
err_unmap_bar:
hinic3_unmapping_bar(pci_adapter);
err_out:
dev_err(&pdev->dev, "PCIe device probe function failed\n");
return err;
}
static void hinic3_remove_func(struct hinic3_pcidev *pci_adapter)
{
struct pci_dev *pdev = pci_adapter->pdev;
hinic3_func_uninit(pdev);
hinic3_unmapping_bar(pci_adapter);
}
static int hinic3_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct hinic3_pcidev *pci_adapter;
int err;
err = hinic3_pci_init(pdev);
if (err)
goto err_out;
pci_adapter = pci_get_drvdata(pdev);
err = hinic3_probe_func(pci_adapter);
if (err)
goto err_uninit_pci;
return 0;
err_uninit_pci:
hinic3_pci_uninit(pdev);
err_out:
dev_err(&pdev->dev, "PCIe device probe failed\n");
return err;
}
static void hinic3_remove(struct pci_dev *pdev)
{
struct hinic3_pcidev *pci_adapter = pci_get_drvdata(pdev);
hinic3_remove_func(pci_adapter);
hinic3_pci_uninit(pdev);
}
static const struct pci_device_id hinic3_pci_table[] = {
{ PCI_VDEVICE(HUAWEI, PCI_DEV_ID_HINIC3_PF) },
{ PCI_VDEVICE(HUAWEI, PCI_DEV_ID_HINIC3_VF) },
{ }
};
MODULE_DEVICE_TABLE(pci, hinic3_pci_table);
static void hinic3_shutdown(struct pci_dev *pdev)
{
struct hinic3_pcidev *pci_adapter = pci_get_drvdata(pdev);
pci_disable_device(pdev);
if (pci_adapter)
hinic3_set_api_stop(pci_adapter->hwdev);
}
static struct pci_driver hinic3_driver = {
.name = HINIC3_NIC_DRV_NAME,
.id_table = hinic3_pci_table,
.probe = hinic3_probe,
.remove = hinic3_remove,
.shutdown = hinic3_shutdown,
.sriov_configure = pci_sriov_configure_simple
};
int hinic3_lld_init(void)
{
return pci_register_driver(&hinic3_driver);
}
void hinic3_lld_exit(void)
{
pci_unregister_driver(&hinic3_driver);
}
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_LLD_H_
#define _HINIC3_LLD_H_
#include <linux/auxiliary_bus.h>
struct hinic3_event_info;
#define HINIC3_NIC_DRV_NAME "hinic3"
int hinic3_lld_init(void);
void hinic3_lld_exit(void);
void hinic3_adev_event_register(struct auxiliary_device *adev,
void (*event_handler)(struct auxiliary_device *adev,
struct hinic3_event_info *event));
void hinic3_adev_event_unregister(struct auxiliary_device *adev);
struct hinic3_hwdev *hinic3_adev_get_hwdev(struct auxiliary_device *adev);
#endif
@@ -0,0 +1,563 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include "hinic3_common.h"
#include "hinic3_hw_comm.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_lld.h"
#include "hinic3_nic_cfg.h"
#include "hinic3_nic_dev.h"
#include "hinic3_nic_io.h"
#include "hinic3_rss.h"
#include "hinic3_rx.h"
#include "hinic3_tx.h"
#define HINIC3_NIC_DRV_DESC "Intelligent Network Interface Card Driver"
#define HINIC3_DEFAULT_MSG_ENABLE (NETIF_MSG_DRV | NETIF_MSG_LINK)
#define HINIC3_RX_BUF_LEN 2048
#define HINIC3_LRO_REPLENISH_THLD 256
#define HINIC3_NIC_DEV_WQ_NAME "hinic3_nic_dev_wq"
#define HINIC3_SQ_DEPTH 1024
#define HINIC3_RQ_DEPTH 1024
#define HINIC3_DEFAULT_TXRX_MSIX_PENDING_LIMIT 2
#define HINIC3_DEFAULT_TXRX_MSIX_COALESC_TIMER_CFG 25
#define HINIC3_DEFAULT_TXRX_MSIX_RESEND_TIMER_CFG 7
#define HINIC3_RX_PENDING_LIMIT_LOW 2
#define HINIC3_RX_PENDING_LIMIT_HIGH 8
static void init_intr_coal_param(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_intr_coal_info *info;
u16 i;
for (i = 0; i < nic_dev->max_qps; i++) {
info = &nic_dev->intr_coalesce[i];
info->pending_limit = HINIC3_DEFAULT_TXRX_MSIX_PENDING_LIMIT;
info->coalesce_timer_cfg =
HINIC3_DEFAULT_TXRX_MSIX_COALESC_TIMER_CFG;
info->resend_timer_cfg =
HINIC3_DEFAULT_TXRX_MSIX_RESEND_TIMER_CFG;
info->rx_pending_limit_high = HINIC3_RX_PENDING_LIMIT_HIGH;
info->rx_pending_limit_low = HINIC3_RX_PENDING_LIMIT_LOW;
}
nic_dev->adaptive_rx_coal = 1;
}
static int hinic3_init_intr_coalesce(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
nic_dev->intr_coalesce = kzalloc_objs(*nic_dev->intr_coalesce,
nic_dev->max_qps);
if (!nic_dev->intr_coalesce)
return -ENOMEM;
init_intr_coal_param(netdev);
return 0;
}
static void hinic3_free_intr_coalesce(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
kfree(nic_dev->intr_coalesce);
}
static int hinic3_alloc_txrxqs(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
int err;
err = hinic3_alloc_txqs(netdev);
if (err) {
dev_err(hwdev->dev, "Failed to alloc txqs\n");
return err;
}
err = hinic3_alloc_rxqs(netdev);
if (err) {
dev_err(hwdev->dev, "Failed to alloc rxqs\n");
goto err_free_txqs;
}
err = hinic3_init_intr_coalesce(netdev);
if (err) {
dev_err(hwdev->dev, "Failed to init_intr_coalesce\n");
goto err_free_rxqs;
}
return 0;
err_free_rxqs:
hinic3_free_rxqs(netdev);
err_free_txqs:
hinic3_free_txqs(netdev);
return err;
}
static void hinic3_free_txrxqs(struct net_device *netdev)
{
hinic3_free_intr_coalesce(netdev);
hinic3_free_rxqs(netdev);
hinic3_free_txqs(netdev);
}
static void hinic3_periodic_work_handler(struct work_struct *work)
{
struct delayed_work *delay = to_delayed_work(work);
struct hinic3_nic_dev *nic_dev;
nic_dev = container_of(delay, struct hinic3_nic_dev, periodic_work);
if (test_and_clear_bit(HINIC3_EVENT_WORK_TX_TIMEOUT,
&nic_dev->event_flag))
dev_info(nic_dev->hwdev->dev,
"Fault event report, src: %u, level: %u\n",
HINIC3_FAULT_SRC_TX_TIMEOUT,
HINIC3_FAULT_LEVEL_SERIOUS_FLR);
queue_delayed_work(nic_dev->workq, &nic_dev->periodic_work, HZ);
}
static int hinic3_init_nic_dev(struct net_device *netdev,
struct hinic3_hwdev *hwdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct pci_dev *pdev = hwdev->pdev;
nic_dev->netdev = netdev;
SET_NETDEV_DEV(netdev, &pdev->dev);
nic_dev->hwdev = hwdev;
nic_dev->pdev = pdev;
nic_dev->msg_enable = HINIC3_DEFAULT_MSG_ENABLE;
nic_dev->rx_buf_len = HINIC3_RX_BUF_LEN;
nic_dev->lro_replenish_thld = HINIC3_LRO_REPLENISH_THLD;
nic_dev->vlan_bitmap = kzalloc(HINIC3_VLAN_BITMAP_SIZE(nic_dev),
GFP_KERNEL);
if (!nic_dev->vlan_bitmap)
return -ENOMEM;
nic_dev->nic_svc_cap = hwdev->cfg_mgmt->cap.nic_svc_cap;
nic_dev->workq = create_singlethread_workqueue(HINIC3_NIC_DEV_WQ_NAME);
if (!nic_dev->workq) {
dev_err(hwdev->dev, "Failed to initialize nic workqueue\n");
kfree(nic_dev->vlan_bitmap);
return -ENOMEM;
}
INIT_DELAYED_WORK(&nic_dev->periodic_work,
hinic3_periodic_work_handler);
INIT_LIST_HEAD(&nic_dev->uc_filter_list);
INIT_LIST_HEAD(&nic_dev->mc_filter_list);
INIT_WORK(&nic_dev->rx_mode_work, hinic3_set_rx_mode_work);
return 0;
}
static int hinic3_sw_init(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
u8 mac_addr[ETH_ALEN];
int err;
mutex_init(&nic_dev->port_state_mutex);
nic_dev->q_params.sq_depth = HINIC3_SQ_DEPTH;
nic_dev->q_params.rq_depth = HINIC3_RQ_DEPTH;
hinic3_try_to_enable_rss(netdev);
if (HINIC3_IS_VF(hwdev)) {
/* VF driver always uses random MAC address. During VM migration
* to a new device, the new device should learn the VMs old MAC
* rather than provide its own MAC. The product design assumes
* that every VF is susceptible to migration so the device
* avoids offering MAC address to VFs.
*/
eth_hw_addr_random(netdev);
} else {
err = hinic3_get_default_mac(hwdev, mac_addr);
if (err) {
dev_err(hwdev->dev, "Failed to get MAC address\n");
goto err_clear_rss_config;
}
eth_hw_addr_set(netdev, mac_addr);
}
err = hinic3_set_mac(hwdev, netdev->dev_addr, 0,
hinic3_global_func_id(hwdev));
/* Failure to set MAC is not a fatal error for VF since its MAC may have
* already been set by PF
*/
if (err && err != -EADDRINUSE) {
dev_err(hwdev->dev, "Failed to set default MAC\n");
goto err_clear_rss_config;
}
err = hinic3_alloc_txrxqs(netdev);
if (err) {
dev_err(hwdev->dev, "Failed to alloc qps\n");
goto err_del_mac;
}
return 0;
err_del_mac:
hinic3_del_mac(hwdev, netdev->dev_addr, 0,
hinic3_global_func_id(hwdev));
err_clear_rss_config:
hinic3_clear_rss_config(netdev);
return err;
}
static void hinic3_sw_uninit(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
hinic3_free_txrxqs(netdev);
hinic3_clean_mac_list_filter(netdev);
hinic3_del_mac(nic_dev->hwdev, netdev->dev_addr, 0,
hinic3_global_func_id(nic_dev->hwdev));
hinic3_clear_rss_config(netdev);
}
static void hinic3_assign_netdev_ops(struct net_device *netdev)
{
hinic3_set_netdev_ops(netdev);
hinic3_set_ethtool_ops(netdev);
}
static void netdev_feature_init(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
netdev_features_t hw_features = 0;
netdev_features_t vlan_fts = 0;
netdev_features_t cso_fts = 0;
netdev_features_t tso_fts = 0;
netdev_features_t dft_fts;
dft_fts = NETIF_F_SG | NETIF_F_HIGHDMA;
if (hinic3_test_support(nic_dev, HINIC3_NIC_F_CSUM))
cso_fts |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM;
if (hinic3_test_support(nic_dev, HINIC3_NIC_F_SCTP_CRC))
cso_fts |= NETIF_F_SCTP_CRC;
if (hinic3_test_support(nic_dev, HINIC3_NIC_F_TSO))
tso_fts |= NETIF_F_TSO | NETIF_F_TSO6;
if (hinic3_test_support(nic_dev, HINIC3_NIC_F_RX_VLAN_STRIP |
HINIC3_NIC_F_TX_VLAN_INSERT))
vlan_fts |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX;
if (hinic3_test_support(nic_dev, HINIC3_NIC_F_RX_VLAN_FILTER))
vlan_fts |= NETIF_F_HW_VLAN_CTAG_FILTER;
if (hinic3_test_support(nic_dev, HINIC3_NIC_F_VXLAN_OFFLOAD))
tso_fts |= NETIF_F_GSO_UDP_TUNNEL | NETIF_F_GSO_UDP_TUNNEL_CSUM;
/* LRO is disabled by default, only set hw features */
if (hinic3_test_support(nic_dev, HINIC3_NIC_F_LRO))
hw_features |= NETIF_F_LRO;
netdev->features |= dft_fts | cso_fts | tso_fts | vlan_fts;
netdev->vlan_features |= dft_fts | cso_fts | tso_fts;
hw_features |= netdev->hw_features | netdev->features;
netdev->hw_features = hw_features;
netdev->priv_flags |= IFF_UNICAST_FLT;
netdev->hw_enc_features |= dft_fts;
if (hinic3_test_support(nic_dev, HINIC3_NIC_F_VXLAN_OFFLOAD))
netdev->hw_enc_features |= cso_fts | tso_fts | NETIF_F_TSO_ECN;
}
static int hinic3_set_default_hw_feature(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
int err;
err = hinic3_set_nic_feature_to_hw(nic_dev);
if (err) {
dev_err(hwdev->dev, "Failed to set nic features\n");
return err;
}
err = hinic3_set_hw_features(netdev);
if (err) {
hinic3_update_nic_feature(nic_dev, 0);
hinic3_set_nic_feature_to_hw(nic_dev);
return err;
}
return 0;
}
static void hinic3_link_status_change(struct net_device *netdev,
bool link_status_up)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
if (link_status_up) {
if (netif_carrier_ok(netdev))
return;
nic_dev->link_status_up = true;
netif_carrier_on(netdev);
netdev_dbg(netdev, "Link is up\n");
} else {
if (!netif_carrier_ok(netdev))
return;
nic_dev->link_status_up = false;
netif_carrier_off(netdev);
netdev_dbg(netdev, "Link is down\n");
}
}
static void hinic3_port_module_event_handler(struct net_device *netdev,
struct hinic3_event_info *event)
{
const char *g_hinic3_module_link_err[LINK_ERR_NUM] = {
"Unrecognized module"
};
struct hinic3_port_module_event *module_event;
enum port_module_event_type type;
enum link_err_type err_type;
module_event = (struct hinic3_port_module_event *)event->event_data;
type = module_event->type;
err_type = module_event->err_type;
switch (type) {
case HINIC3_PORT_MODULE_CABLE_PLUGGED:
case HINIC3_PORT_MODULE_CABLE_UNPLUGGED:
netdev_info(netdev, "Port module event: Cable %s\n",
type == HINIC3_PORT_MODULE_CABLE_PLUGGED ?
"plugged" : "unplugged");
break;
case HINIC3_PORT_MODULE_LINK_ERR:
if (err_type >= LINK_ERR_NUM) {
netdev_info(netdev, "Link failed, Unknown error type: 0x%x\n",
err_type);
} else {
netdev_info(netdev,
"Link failed, error type: 0x%x: %s\n",
err_type,
g_hinic3_module_link_err[err_type]);
}
break;
default:
netdev_err(netdev, "Unknown port module type %d\n", type);
break;
}
}
static void hinic3_nic_event(struct auxiliary_device *adev,
struct hinic3_event_info *event)
{
struct hinic3_nic_dev *nic_dev = dev_get_drvdata(&adev->dev);
struct net_device *netdev;
netdev = nic_dev->netdev;
switch (HINIC3_SRV_EVENT_TYPE(event->service, event->type)) {
case HINIC3_SRV_EVENT_TYPE(HINIC3_EVENT_SRV_NIC,
HINIC3_NIC_EVENT_LINK_UP):
hinic3_link_status_change(netdev, true);
break;
case HINIC3_SRV_EVENT_TYPE(HINIC3_EVENT_SRV_NIC,
HINIC3_NIC_EVENT_PORT_MODULE_EVENT):
hinic3_port_module_event_handler(netdev, event);
break;
case HINIC3_SRV_EVENT_TYPE(HINIC3_EVENT_SRV_NIC,
HINIC3_NIC_EVENT_LINK_DOWN):
case HINIC3_SRV_EVENT_TYPE(HINIC3_EVENT_SRV_COMM,
HINIC3_COMM_EVENT_FAULT):
case HINIC3_SRV_EVENT_TYPE(HINIC3_EVENT_SRV_COMM,
HINIC3_COMM_EVENT_PCIE_LINK_DOWN):
case HINIC3_SRV_EVENT_TYPE(HINIC3_EVENT_SRV_COMM,
HINIC3_COMM_EVENT_HEART_LOST):
case HINIC3_SRV_EVENT_TYPE(HINIC3_EVENT_SRV_COMM,
HINIC3_COMM_EVENT_MGMT_WATCHDOG):
hinic3_link_status_change(netdev, false);
break;
default:
break;
}
}
static void hinic3_free_nic_dev(struct hinic3_nic_dev *nic_dev)
{
destroy_workqueue(nic_dev->workq);
kfree(nic_dev->vlan_bitmap);
}
static int hinic3_nic_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
struct hinic3_hwdev *hwdev = hinic3_adev_get_hwdev(adev);
struct pci_dev *pdev = hwdev->pdev;
struct hinic3_nic_dev *nic_dev;
struct net_device *netdev;
u16 max_qps, glb_func_id;
int err;
if (!hinic3_support_nic(hwdev)) {
dev_dbg(&adev->dev, "HW doesn't support nic\n");
return 0;
}
hinic3_adev_event_register(adev, hinic3_nic_event);
glb_func_id = hinic3_global_func_id(hwdev);
err = hinic3_func_reset(hwdev, glb_func_id, COMM_FUNC_RESET_BIT_NIC);
if (err) {
dev_err(&adev->dev, "Failed to reset function\n");
goto err_unregister_adev_event;
}
max_qps = hinic3_func_max_qnum(hwdev);
netdev = alloc_etherdev_mq(sizeof(*nic_dev), max_qps);
if (!netdev) {
dev_err(&adev->dev, "Failed to allocate netdev\n");
err = -ENOMEM;
goto err_unregister_adev_event;
}
nic_dev = netdev_priv(netdev);
dev_set_drvdata(&adev->dev, nic_dev);
err = hinic3_init_nic_dev(netdev, hwdev);
if (err)
goto err_free_netdev;
err = hinic3_init_nic_io(nic_dev);
if (err)
goto err_free_nic_dev;
err = hinic3_sw_init(netdev);
if (err)
goto err_free_nic_io;
hinic3_assign_netdev_ops(netdev);
netdev_feature_init(netdev);
err = hinic3_set_default_hw_feature(netdev);
if (err)
goto err_uninit_sw;
queue_delayed_work(nic_dev->workq, &nic_dev->periodic_work, HZ);
netif_carrier_off(netdev);
err = register_netdev(netdev);
if (err)
goto err_uninit_nic_feature;
return 0;
err_uninit_nic_feature:
disable_delayed_work_sync(&nic_dev->periodic_work);
hinic3_update_nic_feature(nic_dev, 0);
hinic3_set_nic_feature_to_hw(nic_dev);
err_uninit_sw:
hinic3_sw_uninit(netdev);
err_free_nic_io:
hinic3_free_nic_io(nic_dev);
err_free_nic_dev:
hinic3_free_nic_dev(nic_dev);
err_free_netdev:
free_netdev(netdev);
err_unregister_adev_event:
hinic3_adev_event_unregister(adev);
dev_err(&pdev->dev, "NIC service probe failed\n");
return err;
}
static void hinic3_nic_remove(struct auxiliary_device *adev)
{
struct hinic3_nic_dev *nic_dev = dev_get_drvdata(&adev->dev);
struct net_device *netdev;
if (!hinic3_support_nic(nic_dev->hwdev))
return;
netdev = nic_dev->netdev;
unregister_netdev(netdev);
disable_delayed_work_sync(&nic_dev->periodic_work);
cancel_work_sync(&nic_dev->rx_mode_work);
hinic3_free_nic_dev(nic_dev);
hinic3_update_nic_feature(nic_dev, 0);
hinic3_set_nic_feature_to_hw(nic_dev);
hinic3_sw_uninit(netdev);
hinic3_free_nic_io(nic_dev);
free_netdev(netdev);
}
static const struct auxiliary_device_id hinic3_nic_id_table[] = {
{
.name = HINIC3_NIC_DRV_NAME ".nic",
},
{}
};
static struct auxiliary_driver hinic3_nic_driver = {
.probe = hinic3_nic_probe,
.remove = hinic3_nic_remove,
.suspend = NULL,
.resume = NULL,
.name = "nic",
.id_table = hinic3_nic_id_table,
};
static __init int hinic3_nic_lld_init(void)
{
int err;
err = hinic3_lld_init();
if (err)
return err;
err = auxiliary_driver_register(&hinic3_nic_driver);
if (err) {
hinic3_lld_exit();
return err;
}
return 0;
}
static __exit void hinic3_nic_lld_exit(void)
{
auxiliary_driver_unregister(&hinic3_nic_driver);
hinic3_lld_exit();
}
module_init(hinic3_nic_lld_init);
module_exit(hinic3_nic_lld_exit);
MODULE_AUTHOR("Huawei Technologies CO., Ltd");
MODULE_DESCRIPTION(HINIC3_NIC_DRV_DESC);
MODULE_LICENSE("GPL");
@@ -0,0 +1,928 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/dma-mapping.h>
#include "hinic3_common.h"
#include "hinic3_csr.h"
#include "hinic3_eqs.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_mbox.h"
#define MBOX_INT_DST_AEQN_MASK GENMASK(11, 10)
#define MBOX_INT_SRC_RESP_AEQN_MASK GENMASK(13, 12)
#define MBOX_INT_STAT_DMA_MASK GENMASK(19, 14)
/* TX size, expressed in 4 bytes units */
#define MBOX_INT_TX_SIZE_MASK GENMASK(24, 20)
/* SO_RO == strong order, relaxed order */
#define MBOX_INT_STAT_DMA_SO_RO_MASK GENMASK(26, 25)
#define MBOX_INT_WB_EN_MASK BIT(28)
#define MBOX_INT_SET(val, field) \
FIELD_PREP(MBOX_INT_##field##_MASK, val)
#define MBOX_CTRL_TRIGGER_AEQE_MASK BIT(0)
#define MBOX_CTRL_TX_STATUS_MASK BIT(1)
#define MBOX_CTRL_DST_FUNC_MASK GENMASK(28, 16)
#define MBOX_CTRL_SET(val, field) \
FIELD_PREP(MBOX_CTRL_##field##_MASK, val)
#define MBOX_MSG_POLLING_TIMEOUT_MS 8000 // send msg seg timeout
#define MBOX_COMP_POLLING_TIMEOUT_MS 40000 // response
#define MBOX_MAX_BUF_SZ 2048
#define MBOX_HEADER_SZ 8
/* MBOX size is 64B, 8B for mbox_header, 8B reserved */
#define MBOX_SEG_LEN 48
#define MBOX_SEG_LEN_ALIGN 4
#define MBOX_WB_STATUS_LEN 16
#define MBOX_SEQ_ID_START_VAL 0
#define MBOX_SEQ_ID_MAX_VAL 42
#define MBOX_LAST_SEG_MAX_LEN \
(MBOX_MAX_BUF_SZ - MBOX_SEQ_ID_MAX_VAL * MBOX_SEG_LEN)
/* mbox write back status is 16B, only first 4B is used */
#define MBOX_WB_STATUS_ERRCODE_MASK 0xFFFF
#define MBOX_WB_STATUS_MASK 0xFF
#define MBOX_WB_ERROR_CODE_MASK 0xFF00
#define MBOX_WB_STATUS_FINISHED_SUCCESS 0xFF
#define MBOX_WB_STATUS_NOT_FINISHED 0x00
#define MBOX_STATUS_FINISHED(wb) \
((FIELD_GET(MBOX_WB_STATUS_MASK, (wb))) != MBOX_WB_STATUS_NOT_FINISHED)
#define MBOX_STATUS_SUCCESS(wb) \
((FIELD_GET(MBOX_WB_STATUS_MASK, (wb))) == \
MBOX_WB_STATUS_FINISHED_SUCCESS)
#define MBOX_STATUS_ERRCODE(wb) \
((wb) & MBOX_WB_ERROR_CODE_MASK)
#define MBOX_DMA_MSG_QUEUE_DEPTH 32
#define MBOX_AREA(hwif) \
((hwif)->cfg_regs_base + HINIC3_FUNC_CSR_MAILBOX_DATA_OFF)
#define MBOX_MQ_CI_OFFSET \
(HINIC3_CFG_REGS_FLAG + HINIC3_FUNC_CSR_MAILBOX_DATA_OFF + \
MBOX_HEADER_SZ + MBOX_SEG_LEN)
#define MBOX_MQ_SYNC_CI_MASK GENMASK(7, 0)
#define MBOX_MQ_ASYNC_CI_MASK GENMASK(15, 8)
#define MBOX_MQ_CI_GET(val, field) \
FIELD_GET(MBOX_MQ_##field##_CI_MASK, val)
#define MBOX_MGMT_FUNC_ID 0x1FFF
#define MBOX_COMM_F_MBOX_SEGMENT BIT(3)
static u8 *get_mobx_body_from_hdr(u8 *header)
{
return header + MBOX_HEADER_SZ;
}
static struct hinic3_msg_desc *get_mbox_msg_desc(struct hinic3_mbox *mbox,
enum mbox_msg_direction_type dir,
u16 src_func_id)
{
struct hinic3_hwdev *hwdev = mbox->hwdev;
struct hinic3_msg_channel *msg_ch;
if (src_func_id == MBOX_MGMT_FUNC_ID) {
msg_ch = &mbox->mgmt_msg;
} else if (HINIC3_IS_VF(hwdev)) {
/* message from pf */
msg_ch = mbox->func_msg;
if (src_func_id != hinic3_pf_id_of_vf(hwdev) || !msg_ch)
return NULL;
} else {
return NULL;
}
return (dir == MBOX_MSG_SEND) ?
&msg_ch->recv_msg : &msg_ch->resp_msg;
}
static void resp_mbox_handler(struct hinic3_mbox *mbox,
const struct hinic3_msg_desc *msg_desc)
{
spin_lock(&mbox->mbox_lock);
if (msg_desc->msg_info.msg_id == mbox->send_msg_id &&
mbox->event_flag == MBOX_EVENT_START)
mbox->event_flag = MBOX_EVENT_SUCCESS;
spin_unlock(&mbox->mbox_lock);
}
static bool mbox_segment_valid(struct hinic3_mbox *mbox,
struct hinic3_msg_desc *msg_desc,
__le64 mbox_header)
{
u8 seq_id, seg_len, msg_id, mod;
__le16 src_func_idx, cmd;
seq_id = MBOX_MSG_HEADER_GET(mbox_header, SEQID);
seg_len = MBOX_MSG_HEADER_GET(mbox_header, SEG_LEN);
msg_id = MBOX_MSG_HEADER_GET(mbox_header, MSG_ID);
mod = MBOX_MSG_HEADER_GET(mbox_header, MODULE);
cmd = cpu_to_le16(MBOX_MSG_HEADER_GET(mbox_header, CMD));
src_func_idx = cpu_to_le16(MBOX_MSG_HEADER_GET(mbox_header,
SRC_GLB_FUNC_IDX));
if (seq_id > MBOX_SEQ_ID_MAX_VAL || seg_len > MBOX_SEG_LEN ||
(seq_id == MBOX_SEQ_ID_MAX_VAL && seg_len > MBOX_LAST_SEG_MAX_LEN))
goto err_seg;
if (seq_id == 0) {
msg_desc->seq_id = seq_id;
msg_desc->msg_info.msg_id = msg_id;
msg_desc->mod = mod;
msg_desc->cmd = cmd;
} else {
if (seq_id != msg_desc->seq_id + 1 ||
msg_id != msg_desc->msg_info.msg_id ||
mod != msg_desc->mod || cmd != msg_desc->cmd)
goto err_seg;
msg_desc->seq_id = seq_id;
}
return true;
err_seg:
dev_err(mbox->hwdev->dev,
"Mailbox segment check failed, src func id: 0x%x, front seg info: seq id: 0x%x, msg id: 0x%x, mod: 0x%x, cmd: 0x%x\n",
src_func_idx, msg_desc->seq_id, msg_desc->msg_info.msg_id,
msg_desc->mod, msg_desc->cmd);
dev_err(mbox->hwdev->dev,
"Current seg info: seg len: 0x%x, seq id: 0x%x, msg id: 0x%x, mod: 0x%x, cmd: 0x%x\n",
seg_len, seq_id, msg_id, mod, cmd);
return false;
}
static void recv_mbox_handler(struct hinic3_mbox *mbox,
u8 *header, struct hinic3_msg_desc *msg_desc)
{
__le64 mbox_header = *((__force __le64 *)header);
u8 *mbox_body = get_mobx_body_from_hdr(header);
u8 seq_id, seg_len;
int pos;
if (!mbox_segment_valid(mbox, msg_desc, mbox_header)) {
msg_desc->seq_id = MBOX_SEQ_ID_MAX_VAL;
return;
}
seq_id = MBOX_MSG_HEADER_GET(mbox_header, SEQID);
seg_len = MBOX_MSG_HEADER_GET(mbox_header, SEG_LEN);
pos = seq_id * MBOX_SEG_LEN;
memcpy(msg_desc->msg + pos, mbox_body, seg_len);
if (!MBOX_MSG_HEADER_GET(mbox_header, LAST))
return;
msg_desc->msg_len = cpu_to_le16(MBOX_MSG_HEADER_GET(mbox_header,
MSG_LEN));
msg_desc->msg_info.status = MBOX_MSG_HEADER_GET(mbox_header, STATUS);
if (MBOX_MSG_HEADER_GET(mbox_header, DIRECTION) == MBOX_MSG_RESP)
resp_mbox_handler(mbox, msg_desc);
}
void hinic3_mbox_func_aeqe_handler(struct hinic3_hwdev *hwdev, u8 *header,
u8 size)
{
__le64 mbox_header = *((__force __le64 *)header);
enum mbox_msg_direction_type dir;
struct hinic3_msg_desc *msg_desc;
struct hinic3_mbox *mbox;
u16 src_func_id;
mbox = hwdev->mbox;
dir = MBOX_MSG_HEADER_GET(mbox_header, DIRECTION);
src_func_id = MBOX_MSG_HEADER_GET(mbox_header, SRC_GLB_FUNC_IDX);
msg_desc = get_mbox_msg_desc(mbox, dir, src_func_id);
if (!msg_desc) {
dev_err(mbox->hwdev->dev,
"Mailbox source function id: %u is invalid for current function\n",
src_func_id);
return;
}
recv_mbox_handler(mbox, header, msg_desc);
}
static int init_mbox_dma_queue(struct hinic3_hwdev *hwdev,
struct mbox_dma_queue *mq)
{
u32 size;
mq->depth = MBOX_DMA_MSG_QUEUE_DEPTH;
mq->prod_idx = 0;
mq->cons_idx = 0;
size = mq->depth * MBOX_MAX_BUF_SZ;
mq->dma_buf_vaddr = dma_alloc_coherent(hwdev->dev, size,
&mq->dma_buf_paddr,
GFP_KERNEL);
if (!mq->dma_buf_vaddr)
return -ENOMEM;
return 0;
}
static void uninit_mbox_dma_queue(struct hinic3_hwdev *hwdev,
struct mbox_dma_queue *mq)
{
dma_free_coherent(hwdev->dev, mq->depth * MBOX_MAX_BUF_SZ,
mq->dma_buf_vaddr, mq->dma_buf_paddr);
}
static int hinic3_init_mbox_dma_queue(struct hinic3_mbox *mbox)
{
u32 val;
int err;
err = init_mbox_dma_queue(mbox->hwdev, &mbox->sync_msg_queue);
if (err)
return err;
err = init_mbox_dma_queue(mbox->hwdev, &mbox->async_msg_queue);
if (err) {
uninit_mbox_dma_queue(mbox->hwdev, &mbox->sync_msg_queue);
return err;
}
val = hinic3_hwif_read_reg(mbox->hwdev->hwif, MBOX_MQ_CI_OFFSET);
val &= ~MBOX_MQ_SYNC_CI_MASK;
val &= ~MBOX_MQ_ASYNC_CI_MASK;
hinic3_hwif_write_reg(mbox->hwdev->hwif, MBOX_MQ_CI_OFFSET, val);
return 0;
}
static void hinic3_uninit_mbox_dma_queue(struct hinic3_mbox *mbox)
{
uninit_mbox_dma_queue(mbox->hwdev, &mbox->sync_msg_queue);
uninit_mbox_dma_queue(mbox->hwdev, &mbox->async_msg_queue);
}
static int alloc_mbox_msg_channel(struct hinic3_msg_channel *msg_ch)
{
msg_ch->resp_msg.msg = kzalloc(MBOX_MAX_BUF_SZ, GFP_KERNEL);
if (!msg_ch->resp_msg.msg)
return -ENOMEM;
msg_ch->recv_msg.msg = kzalloc(MBOX_MAX_BUF_SZ, GFP_KERNEL);
if (!msg_ch->recv_msg.msg) {
kfree(msg_ch->resp_msg.msg);
return -ENOMEM;
}
msg_ch->resp_msg.seq_id = MBOX_SEQ_ID_MAX_VAL;
msg_ch->recv_msg.seq_id = MBOX_SEQ_ID_MAX_VAL;
return 0;
}
static void free_mbox_msg_channel(struct hinic3_msg_channel *msg_ch)
{
kfree(msg_ch->recv_msg.msg);
kfree(msg_ch->resp_msg.msg);
}
static int init_mgmt_msg_channel(struct hinic3_mbox *mbox)
{
int err;
err = alloc_mbox_msg_channel(&mbox->mgmt_msg);
if (err) {
dev_err(mbox->hwdev->dev, "Failed to alloc mgmt message channel\n");
return err;
}
err = hinic3_init_mbox_dma_queue(mbox);
if (err) {
dev_err(mbox->hwdev->dev, "Failed to init mbox dma queue\n");
free_mbox_msg_channel(&mbox->mgmt_msg);
return err;
}
return 0;
}
static void uninit_mgmt_msg_channel(struct hinic3_mbox *mbox)
{
hinic3_uninit_mbox_dma_queue(mbox);
free_mbox_msg_channel(&mbox->mgmt_msg);
}
static int hinic3_init_func_mbox_msg_channel(struct hinic3_hwdev *hwdev)
{
struct hinic3_mbox *mbox;
int err;
mbox = hwdev->mbox;
mbox->func_msg = kzalloc_obj(*mbox->func_msg);
if (!mbox->func_msg)
return -ENOMEM;
err = alloc_mbox_msg_channel(mbox->func_msg);
if (err)
goto err_free_func_msg;
return 0;
err_free_func_msg:
kfree(mbox->func_msg);
mbox->func_msg = NULL;
return err;
}
static void hinic3_uninit_func_mbox_msg_channel(struct hinic3_hwdev *hwdev)
{
struct hinic3_mbox *mbox = hwdev->mbox;
free_mbox_msg_channel(mbox->func_msg);
kfree(mbox->func_msg);
mbox->func_msg = NULL;
}
static void prepare_send_mbox(struct hinic3_mbox *mbox)
{
struct hinic3_send_mbox *send_mbox = &mbox->send_mbox;
send_mbox->data = MBOX_AREA(mbox->hwdev->hwif);
}
static int alloc_mbox_wb_status(struct hinic3_mbox *mbox)
{
struct hinic3_send_mbox *send_mbox = &mbox->send_mbox;
struct hinic3_hwdev *hwdev = mbox->hwdev;
u32 addr_h, addr_l;
send_mbox->wb_vaddr = dma_alloc_coherent(hwdev->dev,
MBOX_WB_STATUS_LEN,
&send_mbox->wb_paddr,
GFP_KERNEL);
if (!send_mbox->wb_vaddr)
return -ENOMEM;
addr_h = upper_32_bits(send_mbox->wb_paddr);
addr_l = lower_32_bits(send_mbox->wb_paddr);
hinic3_hwif_write_reg(hwdev->hwif, HINIC3_FUNC_CSR_MAILBOX_RESULT_H_OFF,
addr_h);
hinic3_hwif_write_reg(hwdev->hwif, HINIC3_FUNC_CSR_MAILBOX_RESULT_L_OFF,
addr_l);
return 0;
}
static void free_mbox_wb_status(struct hinic3_mbox *mbox)
{
struct hinic3_send_mbox *send_mbox = &mbox->send_mbox;
struct hinic3_hwdev *hwdev = mbox->hwdev;
hinic3_hwif_write_reg(hwdev->hwif, HINIC3_FUNC_CSR_MAILBOX_RESULT_H_OFF,
0);
hinic3_hwif_write_reg(hwdev->hwif, HINIC3_FUNC_CSR_MAILBOX_RESULT_L_OFF,
0);
dma_free_coherent(hwdev->dev, MBOX_WB_STATUS_LEN,
send_mbox->wb_vaddr, send_mbox->wb_paddr);
}
static int hinic3_mbox_pre_init(struct hinic3_hwdev *hwdev,
struct hinic3_mbox *mbox)
{
mbox->hwdev = hwdev;
mutex_init(&mbox->mbox_send_lock);
mutex_init(&mbox->mbox_seg_send_lock);
spin_lock_init(&mbox->mbox_lock);
mbox->workq = create_singlethread_workqueue(HINIC3_MBOX_WQ_NAME);
if (!mbox->workq) {
dev_err(hwdev->dev, "Failed to initialize MBOX workqueue\n");
return -ENOMEM;
}
hwdev->mbox = mbox;
return 0;
}
int hinic3_init_mbox(struct hinic3_hwdev *hwdev)
{
struct hinic3_mbox *mbox;
int err;
mbox = kzalloc_obj(*mbox);
if (!mbox)
return -ENOMEM;
err = hinic3_mbox_pre_init(hwdev, mbox);
if (err)
goto err_free_mbox;
err = init_mgmt_msg_channel(mbox);
if (err)
goto err_destroy_workqueue;
if (HINIC3_IS_VF(hwdev)) {
/* VF to PF mbox message channel */
err = hinic3_init_func_mbox_msg_channel(hwdev);
if (err)
goto err_uninit_mgmt_msg_ch;
}
err = alloc_mbox_wb_status(mbox);
if (err) {
dev_err(hwdev->dev, "Failed to alloc mbox write back status\n");
goto err_uninit_func_mbox_msg_ch;
}
prepare_send_mbox(mbox);
return 0;
err_uninit_func_mbox_msg_ch:
if (HINIC3_IS_VF(hwdev))
hinic3_uninit_func_mbox_msg_channel(hwdev);
err_uninit_mgmt_msg_ch:
uninit_mgmt_msg_channel(mbox);
err_destroy_workqueue:
destroy_workqueue(mbox->workq);
err_free_mbox:
kfree(mbox);
return err;
}
void hinic3_free_mbox(struct hinic3_hwdev *hwdev)
{
struct hinic3_mbox *mbox = hwdev->mbox;
destroy_workqueue(mbox->workq);
free_mbox_wb_status(mbox);
if (HINIC3_IS_VF(hwdev))
hinic3_uninit_func_mbox_msg_channel(hwdev);
uninit_mgmt_msg_channel(mbox);
kfree(mbox);
}
#define MBOX_DMA_MSG_INIT_XOR_VAL 0x5a5a5a5a
#define MBOX_XOR_DATA_ALIGN 4
static u32 mbox_dma_msg_xor(u32 *data, u32 msg_len)
{
u32 xor = MBOX_DMA_MSG_INIT_XOR_VAL;
u32 dw_len = msg_len / sizeof(u32);
u32 i;
for (i = 0; i < dw_len; i++)
xor ^= data[i];
return xor;
}
#define MBOX_MQ_ID_MASK(mq, idx) ((idx) & ((mq)->depth - 1))
static bool is_msg_queue_full(struct mbox_dma_queue *mq)
{
return MBOX_MQ_ID_MASK(mq, (mq)->prod_idx + 1) ==
MBOX_MQ_ID_MASK(mq, (mq)->cons_idx);
}
static int mbox_prepare_dma_entry(struct hinic3_mbox *mbox,
struct mbox_dma_queue *mq,
struct mbox_dma_msg *dma_msg,
const void *msg, u32 msg_len)
{
u64 dma_addr, offset;
void *dma_vaddr;
if (is_msg_queue_full(mq)) {
dev_err(mbox->hwdev->dev, "Mbox sync message queue is busy, pi: %u, ci: %u\n",
mq->prod_idx, MBOX_MQ_ID_MASK(mq, mq->cons_idx));
return -EBUSY;
}
/* copy data to DMA buffer */
offset = mq->prod_idx * MBOX_MAX_BUF_SZ;
dma_vaddr = (u8 *)mq->dma_buf_vaddr + offset;
memcpy(dma_vaddr, msg, msg_len);
dma_addr = mq->dma_buf_paddr + offset;
dma_msg->dma_addr_high = cpu_to_le32(upper_32_bits(dma_addr));
dma_msg->dma_addr_low = cpu_to_le32(lower_32_bits(dma_addr));
dma_msg->msg_len = cpu_to_le32(msg_len);
/* The firmware obtains message based on 4B alignment. */
dma_msg->xor = cpu_to_le32(mbox_dma_msg_xor(dma_vaddr,
ALIGN(msg_len, MBOX_XOR_DATA_ALIGN)));
mq->prod_idx++;
mq->prod_idx = MBOX_MQ_ID_MASK(mq, mq->prod_idx);
return 0;
}
static int mbox_prepare_dma_msg(struct hinic3_mbox *mbox,
enum mbox_msg_ack_type ack_type,
struct mbox_dma_msg *dma_msg, const void *msg,
u32 msg_len)
{
struct mbox_dma_queue *mq;
u32 val;
val = hinic3_hwif_read_reg(mbox->hwdev->hwif, MBOX_MQ_CI_OFFSET);
if (ack_type == MBOX_MSG_ACK) {
mq = &mbox->sync_msg_queue;
mq->cons_idx = MBOX_MQ_CI_GET(val, SYNC);
} else {
mq = &mbox->async_msg_queue;
mq->cons_idx = MBOX_MQ_CI_GET(val, ASYNC);
}
return mbox_prepare_dma_entry(mbox, mq, dma_msg, msg, msg_len);
}
static void clear_mbox_status(struct hinic3_send_mbox *mbox)
{
__be64 *wb_status = mbox->wb_vaddr;
*wb_status = 0;
/* clear mailbox write back status */
wmb();
}
static void mbox_dword_write(const void *src, void __iomem *dst, u32 count)
{
const __le32 *src32 = src;
u32 __iomem *dst32 = dst;
u32 i;
/* Data written to mbox is arranged in structs with little endian fields
* but when written to HW every dword (32bits) should be swapped since
* the HW will swap it again.
*/
for (i = 0; i < count; i++)
__raw_writel(swab32((__force __u32)src32[i]), dst32 + i);
}
static void mbox_copy_header(struct hinic3_hwdev *hwdev,
struct hinic3_send_mbox *mbox, __le64 *header)
{
mbox_dword_write(header, mbox->data, MBOX_HEADER_SZ / sizeof(__le32));
}
static void mbox_copy_send_data(struct hinic3_hwdev *hwdev,
struct hinic3_send_mbox *mbox, void *seg,
u32 seg_len)
{
u32 __iomem *dst = (u32 __iomem *)(mbox->data + MBOX_HEADER_SZ);
u32 count, leftover, last_dword;
const __le32 *src = seg;
count = seg_len / sizeof(u32);
leftover = seg_len % sizeof(u32);
if (count > 0)
mbox_dword_write(src, dst, count);
if (leftover > 0) {
last_dword = 0;
memcpy(&last_dword, src + count, leftover);
mbox_dword_write(&last_dword, dst + count, 1);
}
}
static void write_mbox_msg_attr(struct hinic3_mbox *mbox,
u16 dst_func, u16 dst_aeqn, u32 seg_len)
{
struct hinic3_hwif *hwif = mbox->hwdev->hwif;
u32 mbox_int, mbox_ctrl, tx_size;
u16 func = dst_func;
/* VF can send non-management messages only to PF. We set DST_FUNC field
* to 0 since HW will ignore it anyway.
*/
if (HINIC3_IS_VF(mbox->hwdev) && dst_func != MBOX_MGMT_FUNC_ID)
func = 0;
tx_size = ALIGN(seg_len + MBOX_HEADER_SZ, MBOX_SEG_LEN_ALIGN) >> 2;
mbox_int = MBOX_INT_SET(dst_aeqn, DST_AEQN) |
MBOX_INT_SET(0, STAT_DMA) |
MBOX_INT_SET(tx_size, TX_SIZE) |
MBOX_INT_SET(0, STAT_DMA_SO_RO) |
MBOX_INT_SET(1, WB_EN);
mbox_ctrl = MBOX_CTRL_SET(1, TX_STATUS) |
MBOX_CTRL_SET(0, TRIGGER_AEQE) |
MBOX_CTRL_SET(func, DST_FUNC);
hinic3_hwif_write_reg(hwif, HINIC3_FUNC_CSR_MAILBOX_INT_OFF, mbox_int);
hinic3_hwif_write_reg(hwif, HINIC3_FUNC_CSR_MAILBOX_CONTROL_OFF,
mbox_ctrl);
}
static void hinic3_dump_mbox_reg(struct hinic3_hwdev *hwdev)
{
u32 val;
val = hinic3_hwif_read_reg(hwdev->hwif,
HINIC3_FUNC_CSR_MAILBOX_CONTROL_OFF);
dev_err(hwdev->dev, "Mailbox control reg: 0x%x\n", val);
val = hinic3_hwif_read_reg(hwdev->hwif,
HINIC3_FUNC_CSR_MAILBOX_INT_OFF);
dev_err(hwdev->dev, "Mailbox interrupt offset: 0x%x\n", val);
}
static u16 get_mbox_status(const struct hinic3_send_mbox *mbox)
{
__be64 *wb_status = mbox->wb_vaddr;
u64 wb_val;
wb_val = be64_to_cpu(*wb_status);
/* verify reading before check */
rmb();
return wb_val & MBOX_WB_STATUS_ERRCODE_MASK;
}
static enum hinic3_wait_return check_mbox_wb_status(void *priv_data)
{
struct hinic3_mbox *mbox = priv_data;
u16 wb_status;
if (!mbox->hwdev->chip_present_flag)
return HINIC3_WAIT_PROCESS_ERR;
wb_status = get_mbox_status(&mbox->send_mbox);
return MBOX_STATUS_FINISHED(wb_status) ?
HINIC3_WAIT_PROCESS_CPL : HINIC3_WAIT_PROCESS_WAITING;
}
static int send_mbox_seg(struct hinic3_mbox *mbox, __le64 header,
u16 dst_func, void *seg, u32 seg_len, void *msg_info)
{
struct hinic3_send_mbox *send_mbox = &mbox->send_mbox;
struct hinic3_hwdev *hwdev = mbox->hwdev;
u8 num_aeqs = hwdev->hwif->attr.num_aeqs;
enum mbox_msg_direction_type dir;
u16 dst_aeqn, wb_status, errcode;
int err;
/* mbox to mgmt cpu, hardware doesn't care about dst aeq id */
if (num_aeqs > MBOX_MSG_AEQ_FOR_MBOX) {
dir = MBOX_MSG_HEADER_GET(header, DIRECTION);
dst_aeqn = (dir == MBOX_MSG_SEND) ?
MBOX_MSG_AEQ_FOR_EVENT : MBOX_MSG_AEQ_FOR_MBOX;
} else {
dst_aeqn = 0;
}
clear_mbox_status(send_mbox);
mbox_copy_header(hwdev, send_mbox, &header);
mbox_copy_send_data(hwdev, send_mbox, seg, seg_len);
write_mbox_msg_attr(mbox, dst_func, dst_aeqn, seg_len);
err = hinic3_wait_for_timeout(mbox, check_mbox_wb_status,
MBOX_MSG_POLLING_TIMEOUT_MS,
USEC_PER_MSEC);
wb_status = get_mbox_status(send_mbox);
if (err) {
dev_err(hwdev->dev, "Send mailbox segment timeout, wb status: 0x%x\n",
wb_status);
hinic3_dump_mbox_reg(hwdev);
return err;
}
if (!MBOX_STATUS_SUCCESS(wb_status)) {
dev_err(hwdev->dev,
"Send mailbox segment to function %u error, wb status: 0x%x\n",
dst_func, wb_status);
errcode = MBOX_STATUS_ERRCODE(wb_status);
return errcode ? errcode : -EFAULT;
}
return 0;
}
static int send_mbox_msg(struct hinic3_mbox *mbox, u8 mod, u16 cmd,
const void *msg, u32 msg_len, u16 dst_func,
enum mbox_msg_direction_type direction,
enum mbox_msg_ack_type ack_type,
struct mbox_msg_info *msg_info)
{
enum mbox_msg_data_type data_type = MBOX_MSG_DATA_INLINE;
struct hinic3_hwdev *hwdev = mbox->hwdev;
struct mbox_dma_msg dma_msg;
u32 seg_len = MBOX_SEG_LEN;
__le64 header = 0;
u32 seq_id = 0;
u16 rsp_aeq_id;
u8 *msg_seg;
int err = 0;
u32 left;
if (hwdev->hwif->attr.num_aeqs > MBOX_MSG_AEQ_FOR_MBOX)
rsp_aeq_id = MBOX_MSG_AEQ_FOR_MBOX;
else
rsp_aeq_id = 0;
mutex_lock(&mbox->mbox_seg_send_lock);
if (dst_func == MBOX_MGMT_FUNC_ID &&
!(hwdev->features[0] & MBOX_COMM_F_MBOX_SEGMENT)) {
err = mbox_prepare_dma_msg(mbox, ack_type, &dma_msg,
msg, msg_len);
if (err)
goto err_send;
msg = &dma_msg;
msg_len = sizeof(dma_msg);
data_type = MBOX_MSG_DATA_DMA;
}
msg_seg = (u8 *)msg;
left = msg_len;
header = cpu_to_le64(MBOX_MSG_HEADER_SET(msg_len, MSG_LEN) |
MBOX_MSG_HEADER_SET(mod, MODULE) |
MBOX_MSG_HEADER_SET(seg_len, SEG_LEN) |
MBOX_MSG_HEADER_SET(ack_type, NO_ACK) |
MBOX_MSG_HEADER_SET(data_type, DATA_TYPE) |
MBOX_MSG_HEADER_SET(MBOX_SEQ_ID_START_VAL, SEQID) |
MBOX_MSG_HEADER_SET(direction, DIRECTION) |
MBOX_MSG_HEADER_SET(cmd, CMD) |
MBOX_MSG_HEADER_SET(msg_info->msg_id, MSG_ID) |
MBOX_MSG_HEADER_SET(rsp_aeq_id, AEQ_ID) |
MBOX_MSG_HEADER_SET(MBOX_MSG_FROM_MBOX, SOURCE) |
MBOX_MSG_HEADER_SET(!!msg_info->status, STATUS));
while (!(MBOX_MSG_HEADER_GET(header, LAST))) {
if (left <= MBOX_SEG_LEN) {
header &= cpu_to_le64(~MBOX_MSG_HEADER_SEG_LEN_MASK);
header |=
cpu_to_le64(MBOX_MSG_HEADER_SET(left, SEG_LEN) |
MBOX_MSG_HEADER_SET(1, LAST));
seg_len = left;
}
err = send_mbox_seg(mbox, header, dst_func, msg_seg,
seg_len, msg_info);
if (err) {
dev_err(hwdev->dev, "Failed to send mbox seg, seq_id=0x%llx\n",
MBOX_MSG_HEADER_GET(header, SEQID));
goto err_send;
}
left -= MBOX_SEG_LEN;
msg_seg += MBOX_SEG_LEN;
seq_id++;
header &= cpu_to_le64(~MBOX_MSG_HEADER_SEG_LEN_MASK);
header |= cpu_to_le64(MBOX_MSG_HEADER_SET(seq_id, SEQID));
}
err_send:
mutex_unlock(&mbox->mbox_seg_send_lock);
return err;
}
static void set_mbox_to_func_event(struct hinic3_mbox *mbox,
enum mbox_event_state event_flag)
{
spin_lock(&mbox->mbox_lock);
mbox->event_flag = event_flag;
spin_unlock(&mbox->mbox_lock);
}
static enum hinic3_wait_return check_mbox_msg_finish(void *priv_data)
{
struct hinic3_mbox *mbox = priv_data;
if (!mbox->hwdev->chip_present_flag)
return HINIC3_WAIT_PROCESS_ERR;
return (mbox->event_flag == MBOX_EVENT_SUCCESS) ?
HINIC3_WAIT_PROCESS_CPL : HINIC3_WAIT_PROCESS_WAITING;
}
static int wait_mbox_msg_completion(struct hinic3_mbox *mbox,
u32 timeout)
{
u32 wait_time;
int err;
wait_time = (timeout != 0) ? timeout : MBOX_COMP_POLLING_TIMEOUT_MS;
err = hinic3_wait_for_timeout(mbox, check_mbox_msg_finish,
wait_time, USEC_PER_MSEC);
if (err) {
set_mbox_to_func_event(mbox, MBOX_EVENT_TIMEOUT);
return err;
}
set_mbox_to_func_event(mbox, MBOX_EVENT_END);
return 0;
}
int hinic3_send_mbox_to_mgmt(struct hinic3_hwdev *hwdev, u8 mod, u16 cmd,
const struct mgmt_msg_params *msg_params)
{
struct hinic3_mbox *mbox = hwdev->mbox;
struct mbox_msg_info msg_info = {};
struct hinic3_msg_desc *msg_desc;
u32 msg_len;
int err;
if (!hwdev->chip_present_flag)
return -EPERM;
/* expect response message */
msg_desc = get_mbox_msg_desc(mbox, MBOX_MSG_RESP, MBOX_MGMT_FUNC_ID);
mutex_lock(&mbox->mbox_send_lock);
msg_info.msg_id = (mbox->send_msg_id + 1) & 0xF;
mbox->send_msg_id = msg_info.msg_id;
set_mbox_to_func_event(mbox, MBOX_EVENT_START);
err = send_mbox_msg(mbox, mod, cmd, msg_params->buf_in,
msg_params->in_size, MBOX_MGMT_FUNC_ID,
MBOX_MSG_SEND, MBOX_MSG_ACK, &msg_info);
if (err) {
dev_err(hwdev->dev, "Send mailbox mod %u, cmd %u failed, msg_id: %u, err: %d\n",
mod, cmd, msg_info.msg_id, err);
set_mbox_to_func_event(mbox, MBOX_EVENT_FAIL);
goto err_send;
}
if (wait_mbox_msg_completion(mbox, msg_params->timeout_ms)) {
dev_err(hwdev->dev,
"Send mbox msg timeout, msg_id: %u\n", msg_info.msg_id);
hinic3_dump_aeq_info(mbox->hwdev);
err = -ETIMEDOUT;
goto err_send;
}
if (mod != msg_desc->mod || cmd != le16_to_cpu(msg_desc->cmd)) {
dev_err(hwdev->dev,
"Invalid response mbox message, mod: 0x%x, cmd: 0x%x, expect mod: 0x%x, cmd: 0x%x\n",
msg_desc->mod, msg_desc->cmd, mod, cmd);
err = -EFAULT;
goto err_send;
}
if (msg_desc->msg_info.status) {
err = msg_desc->msg_info.status;
goto err_send;
}
if (msg_params->buf_out) {
msg_len = le16_to_cpu(msg_desc->msg_len);
if (msg_len != msg_params->expected_out_size) {
dev_err(hwdev->dev,
"Invalid response mbox message length: %u for mod %d cmd %u, expected length: %u\n",
msg_desc->msg_len, mod, cmd,
msg_params->expected_out_size);
err = -EFAULT;
goto err_send;
}
memcpy(msg_params->buf_out, msg_desc->msg, msg_len);
}
err_send:
mutex_unlock(&mbox->mbox_send_lock);
return err;
}
void hinic3_response_mbox_to_mgmt(struct hinic3_hwdev *hwdev, u8 mod, u16 cmd,
const void *buf_in, u32 in_size, u16 msg_id)
{
struct mbox_msg_info msg_info;
msg_info.msg_id = (u8)msg_id;
msg_info.status = 0;
send_mbox_msg(hwdev->mbox, mod, cmd, buf_in, in_size,
MBOX_MGMT_FUNC_ID, MBOX_MSG_RESP,
MBOX_MSG_NO_ACK, &msg_info);
}
int hinic3_send_mbox_to_mgmt_no_ack(struct hinic3_hwdev *hwdev, u8 mod, u16 cmd,
const struct mgmt_msg_params *msg_params)
{
struct hinic3_mbox *mbox = hwdev->mbox;
struct mbox_msg_info msg_info = {};
int err;
if (!hwdev->chip_present_flag)
return -EPERM;
mutex_lock(&mbox->mbox_send_lock);
err = send_mbox_msg(mbox, mod, cmd, msg_params->buf_in,
msg_params->in_size, MBOX_MGMT_FUNC_ID,
MBOX_MSG_SEND, MBOX_MSG_NO_ACK, &msg_info);
if (err)
dev_err(hwdev->dev, "Send mailbox no ack failed\n");
mutex_unlock(&mbox->mbox_send_lock);
return err;
}
@@ -0,0 +1,147 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_MBOX_H_
#define _HINIC3_MBOX_H_
#include <linux/bitfield.h>
#include <linux/mutex.h>
struct hinic3_hwdev;
struct mgmt_msg_params;
#define MBOX_MSG_HEADER_SRC_GLB_FUNC_IDX_MASK GENMASK_ULL(12, 0)
#define MBOX_MSG_HEADER_STATUS_MASK BIT_ULL(13)
#define MBOX_MSG_HEADER_SOURCE_MASK BIT_ULL(15)
#define MBOX_MSG_HEADER_AEQ_ID_MASK GENMASK_ULL(17, 16)
#define MBOX_MSG_HEADER_MSG_ID_MASK GENMASK_ULL(21, 18)
#define MBOX_MSG_HEADER_CMD_MASK GENMASK_ULL(31, 22)
#define MBOX_MSG_HEADER_MSG_LEN_MASK GENMASK_ULL(42, 32)
#define MBOX_MSG_HEADER_MODULE_MASK GENMASK_ULL(47, 43)
#define MBOX_MSG_HEADER_SEG_LEN_MASK GENMASK_ULL(53, 48)
#define MBOX_MSG_HEADER_NO_ACK_MASK BIT_ULL(54)
#define MBOX_MSG_HEADER_DATA_TYPE_MASK BIT_ULL(55)
#define MBOX_MSG_HEADER_SEQID_MASK GENMASK_ULL(61, 56)
#define MBOX_MSG_HEADER_LAST_MASK BIT_ULL(62)
#define MBOX_MSG_HEADER_DIRECTION_MASK BIT_ULL(63)
#define MBOX_MSG_HEADER_SET(val, member) \
FIELD_PREP(MBOX_MSG_HEADER_##member##_MASK, val)
#define MBOX_MSG_HEADER_GET(val, member) \
FIELD_GET(MBOX_MSG_HEADER_##member##_MASK, le64_to_cpu(val))
/* identifies if a segment belongs to a message or to a response. A VF is only
* expected to send messages and receive responses. PF driver could receive
* messages and send responses.
*/
enum mbox_msg_direction_type {
MBOX_MSG_SEND = 0,
MBOX_MSG_RESP = 1,
};
/* Indicates if mbox message expects a response (ack) or not */
enum mbox_msg_ack_type {
MBOX_MSG_ACK = 0,
MBOX_MSG_NO_ACK = 1,
};
enum mbox_msg_data_type {
MBOX_MSG_DATA_INLINE = 0,
MBOX_MSG_DATA_DMA = 1,
};
enum mbox_msg_src_type {
MBOX_MSG_FROM_MBOX = 1,
};
enum mbox_msg_aeq_type {
MBOX_MSG_AEQ_FOR_EVENT = 0,
MBOX_MSG_AEQ_FOR_MBOX = 1,
};
#define HINIC3_MBOX_WQ_NAME "hinic3_mbox"
struct mbox_msg_info {
u8 msg_id;
u8 status;
};
struct hinic3_msg_desc {
u8 *msg;
__le16 msg_len;
u8 seq_id;
u8 mod;
__le16 cmd;
struct mbox_msg_info msg_info;
};
struct hinic3_msg_channel {
struct hinic3_msg_desc resp_msg;
struct hinic3_msg_desc recv_msg;
};
struct hinic3_send_mbox {
u8 __iomem *data;
void *wb_vaddr;
dma_addr_t wb_paddr;
};
enum mbox_event_state {
MBOX_EVENT_START = 0,
MBOX_EVENT_FAIL = 1,
MBOX_EVENT_SUCCESS = 2,
MBOX_EVENT_TIMEOUT = 3,
MBOX_EVENT_END = 4,
};
struct mbox_dma_msg {
__le32 xor;
__le32 dma_addr_high;
__le32 dma_addr_low;
__le32 msg_len;
__le64 rsvd;
};
struct mbox_dma_queue {
void *dma_buf_vaddr;
dma_addr_t dma_buf_paddr;
u16 depth;
u16 prod_idx;
u16 cons_idx;
};
struct hinic3_mbox {
struct hinic3_hwdev *hwdev;
/* lock for send mbox message and ack message */
struct mutex mbox_send_lock;
/* lock for send message transmission.
* The lock hierarchy is mbox_send_lock -> mbox_seg_send_lock.
*/
struct mutex mbox_seg_send_lock;
struct hinic3_send_mbox send_mbox;
struct mbox_dma_queue sync_msg_queue;
struct mbox_dma_queue async_msg_queue;
struct workqueue_struct *workq;
/* driver and MGMT CPU */
struct hinic3_msg_channel mgmt_msg;
/* VF to PF */
struct hinic3_msg_channel *func_msg;
u8 send_msg_id;
enum mbox_event_state event_flag;
/* lock for mbox event flag */
spinlock_t mbox_lock;
};
void hinic3_mbox_func_aeqe_handler(struct hinic3_hwdev *hwdev, u8 *header,
u8 size);
int hinic3_init_mbox(struct hinic3_hwdev *hwdev);
void hinic3_free_mbox(struct hinic3_hwdev *hwdev);
int hinic3_send_mbox_to_mgmt(struct hinic3_hwdev *hwdev, u8 mod, u16 cmd,
const struct mgmt_msg_params *msg_params);
void hinic3_response_mbox_to_mgmt(struct hinic3_hwdev *hwdev, u8 mod, u16 cmd,
const void *buf_in, u32 in_size, u16 msg_id);
int hinic3_send_mbox_to_mgmt_no_ack(struct hinic3_hwdev *hwdev, u8 mod, u16 cmd,
const struct mgmt_msg_params *msg_params);
#endif
@@ -0,0 +1,332 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include "hinic3_eqs.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_mbox.h"
#include "hinic3_mgmt.h"
#define HINIC3_MSG_TO_MGMT_MAX_LEN 2016
#define MGMT_MAX_PF_BUF_SIZE 2048UL
#define MGMT_SEG_LEN_MAX 48
#define MGMT_ASYNC_MSG_FLAG 0x8
#define HINIC3_MGMT_WQ_NAME "hinic3_mgmt"
/* Bogus sequence ID to prevent accidental match following partial message */
#define MGMT_BOGUS_SEQ_ID \
(MGMT_MAX_PF_BUF_SIZE / MGMT_SEG_LEN_MAX + 1)
static void
hinic3_mgmt_resp_msg_handler(struct hinic3_msg_pf_to_mgmt *pf_to_mgmt,
struct hinic3_recv_msg *recv_msg)
{
struct device *dev = pf_to_mgmt->hwdev->dev;
/* Ignore async msg */
if (recv_msg->msg_id & MGMT_ASYNC_MSG_FLAG)
return;
spin_lock(&pf_to_mgmt->sync_event_lock);
if (recv_msg->msg_id != pf_to_mgmt->sync_msg_id) {
dev_err(dev, "msg id mismatch, send msg id: 0x%x, recv msg id: 0x%x, event state: %d\n",
pf_to_mgmt->sync_msg_id, recv_msg->msg_id,
pf_to_mgmt->event_flag);
} else if (pf_to_mgmt->event_flag == COMM_SEND_EVENT_START) {
pf_to_mgmt->event_flag = COMM_SEND_EVENT_SUCCESS;
complete(&recv_msg->recv_done);
} else {
dev_err(dev, "Wait timeout, send msg id: 0x%x, recv msg id: 0x%x, event state: %d\n",
pf_to_mgmt->sync_msg_id, recv_msg->msg_id,
pf_to_mgmt->event_flag);
}
spin_unlock(&pf_to_mgmt->sync_event_lock);
}
static void hinic3_recv_mgmt_msg_work_handler(struct work_struct *work)
{
struct hinic3_msg_pf_to_mgmt *pf_to_mgmt;
struct mgmt_msg_handle_work *mgmt_work;
struct mgmt_msg_head *ack_cmd;
mgmt_work = container_of(work, struct mgmt_msg_handle_work, work);
/* At the moment, we do not expect any meaningful messages but if the
* sender expects an ACK we still need to provide one with "unsupported"
* status.
*/
if (mgmt_work->async_mgmt_to_pf)
goto out;
pf_to_mgmt = mgmt_work->pf_to_mgmt;
ack_cmd = pf_to_mgmt->mgmt_ack_buf;
memset(ack_cmd, 0, sizeof(*ack_cmd));
ack_cmd->status = MGMT_STATUS_CMD_UNSUPPORTED;
hinic3_response_mbox_to_mgmt(pf_to_mgmt->hwdev, mgmt_work->mod,
mgmt_work->cmd, ack_cmd, sizeof(*ack_cmd),
mgmt_work->msg_id);
out:
kfree(mgmt_work->msg);
kfree(mgmt_work);
}
static int hinic3_recv_msg_add_seg(struct hinic3_recv_msg *recv_msg,
__le64 msg_header, const void *seg_data,
bool *is_complete)
{
u8 seq_id, msg_id, seg_len, is_last;
char *msg_buff;
u32 offset;
seg_len = MBOX_MSG_HEADER_GET(msg_header, SEG_LEN);
is_last = MBOX_MSG_HEADER_GET(msg_header, LAST);
seq_id = MBOX_MSG_HEADER_GET(msg_header, SEQID);
msg_id = MBOX_MSG_HEADER_GET(msg_header, MSG_ID);
if (seg_len > MGMT_SEG_LEN_MAX)
return -EINVAL;
/* All segments but last must be of maximal size */
if (seg_len != MGMT_SEG_LEN_MAX && !is_last)
return -EINVAL;
if (seq_id == 0) {
recv_msg->seq_id = seq_id;
recv_msg->msg_id = msg_id;
} else if (seq_id != recv_msg->seq_id + 1 ||
msg_id != recv_msg->msg_id) {
return -EINVAL;
}
offset = seq_id * MGMT_SEG_LEN_MAX;
if (offset + seg_len > MGMT_MAX_PF_BUF_SIZE)
return -EINVAL;
msg_buff = recv_msg->msg;
memcpy(msg_buff + offset, seg_data, seg_len);
recv_msg->msg_len = offset + seg_len;
recv_msg->seq_id = seq_id;
*is_complete = !!is_last;
return 0;
}
static void hinic3_init_mgmt_msg_work(struct hinic3_msg_pf_to_mgmt *pf_to_mgmt,
struct hinic3_recv_msg *recv_msg)
{
struct mgmt_msg_handle_work *mgmt_work;
mgmt_work = kmalloc_obj(*mgmt_work);
if (!mgmt_work)
return;
if (recv_msg->msg_len) {
mgmt_work->msg = kmemdup(recv_msg->msg, recv_msg->msg_len,
GFP_KERNEL);
if (!mgmt_work->msg) {
kfree(mgmt_work);
return;
}
} else {
mgmt_work->msg = NULL;
}
mgmt_work->pf_to_mgmt = pf_to_mgmt;
mgmt_work->msg_len = recv_msg->msg_len;
mgmt_work->msg_id = recv_msg->msg_id;
mgmt_work->mod = recv_msg->mod;
mgmt_work->cmd = recv_msg->cmd;
mgmt_work->async_mgmt_to_pf = recv_msg->async_mgmt_to_pf;
INIT_WORK(&mgmt_work->work, hinic3_recv_mgmt_msg_work_handler);
queue_work(pf_to_mgmt->workq, &mgmt_work->work);
}
static void
hinic3_recv_mgmt_msg_handler(struct hinic3_msg_pf_to_mgmt *pf_to_mgmt,
const u8 *header,
struct hinic3_recv_msg *recv_msg)
{
struct hinic3_hwdev *hwdev = pf_to_mgmt->hwdev;
const void *seg_data;
__le64 msg_header;
bool is_complete;
u8 dir, msg_id;
int err;
msg_header = *(__force __le64 *)header;
dir = MBOX_MSG_HEADER_GET(msg_header, DIRECTION);
msg_id = MBOX_MSG_HEADER_GET(msg_header, MSG_ID);
/* Don't need to get anything from hw when cmd is async */
if (dir == MBOX_MSG_RESP && (msg_id & MGMT_ASYNC_MSG_FLAG))
return;
seg_data = header + sizeof(msg_header);
err = hinic3_recv_msg_add_seg(recv_msg, msg_header,
seg_data, &is_complete);
if (err) {
dev_err(hwdev->dev, "invalid receive segment\n");
/* set seq_id to invalid seq_id */
recv_msg->seq_id = MGMT_BOGUS_SEQ_ID;
return;
}
if (!is_complete)
return;
recv_msg->cmd = MBOX_MSG_HEADER_GET(msg_header, CMD);
recv_msg->mod = MBOX_MSG_HEADER_GET(msg_header, MODULE);
recv_msg->async_mgmt_to_pf = MBOX_MSG_HEADER_GET(msg_header, NO_ACK);
recv_msg->seq_id = MGMT_BOGUS_SEQ_ID;
if (dir == MBOX_MSG_RESP)
hinic3_mgmt_resp_msg_handler(pf_to_mgmt, recv_msg);
else
hinic3_init_mgmt_msg_work(pf_to_mgmt, recv_msg);
}
static int alloc_recv_msg(struct hinic3_recv_msg *recv_msg)
{
recv_msg->seq_id = MGMT_BOGUS_SEQ_ID;
recv_msg->msg = kzalloc(MGMT_MAX_PF_BUF_SIZE, GFP_KERNEL);
if (!recv_msg->msg)
return -ENOMEM;
return 0;
}
static void free_recv_msg(struct hinic3_recv_msg *recv_msg)
{
kfree(recv_msg->msg);
}
static int alloc_msg_buf(struct hinic3_msg_pf_to_mgmt *pf_to_mgmt)
{
struct device *dev = pf_to_mgmt->hwdev->dev;
int err;
err = alloc_recv_msg(&pf_to_mgmt->recv_msg_from_mgmt);
if (err) {
dev_err(dev, "Failed to allocate recv msg\n");
return err;
}
err = alloc_recv_msg(&pf_to_mgmt->recv_resp_msg_from_mgmt);
if (err) {
dev_err(dev, "Failed to allocate resp recv msg\n");
goto err_free_msg_from_mgmt;
}
pf_to_mgmt->mgmt_ack_buf = kzalloc(MGMT_MAX_PF_BUF_SIZE, GFP_KERNEL);
if (!pf_to_mgmt->mgmt_ack_buf) {
err = -ENOMEM;
goto err_free_resp_msg_from_mgmt;
}
return 0;
err_free_resp_msg_from_mgmt:
free_recv_msg(&pf_to_mgmt->recv_resp_msg_from_mgmt);
err_free_msg_from_mgmt:
free_recv_msg(&pf_to_mgmt->recv_msg_from_mgmt);
return err;
}
static void free_msg_buf(struct hinic3_msg_pf_to_mgmt *pf_to_mgmt)
{
kfree(pf_to_mgmt->mgmt_ack_buf);
free_recv_msg(&pf_to_mgmt->recv_resp_msg_from_mgmt);
free_recv_msg(&pf_to_mgmt->recv_msg_from_mgmt);
}
int hinic3_pf_to_mgmt_init(struct hinic3_hwdev *hwdev)
{
struct hinic3_msg_pf_to_mgmt *pf_to_mgmt;
int err;
pf_to_mgmt = kzalloc_obj(*pf_to_mgmt);
if (!pf_to_mgmt)
return -ENOMEM;
hwdev->pf_to_mgmt = pf_to_mgmt;
pf_to_mgmt->hwdev = hwdev;
spin_lock_init(&pf_to_mgmt->sync_event_lock);
pf_to_mgmt->workq = create_singlethread_workqueue(HINIC3_MGMT_WQ_NAME);
if (!pf_to_mgmt->workq) {
dev_err(hwdev->dev, "Failed to initialize MGMT workqueue\n");
err = -ENOMEM;
goto err_free_pf_to_mgmt;
}
err = alloc_msg_buf(pf_to_mgmt);
if (err) {
dev_err(hwdev->dev, "Failed to allocate msg buffers\n");
goto err_destroy_workqueue;
}
return 0;
err_destroy_workqueue:
destroy_workqueue(pf_to_mgmt->workq);
err_free_pf_to_mgmt:
kfree(pf_to_mgmt);
return err;
}
void hinic3_pf_to_mgmt_free(struct hinic3_hwdev *hwdev)
{
struct hinic3_msg_pf_to_mgmt *pf_to_mgmt = hwdev->pf_to_mgmt;
/* destroy workqueue before free related pf_to_mgmt resources in case of
* illegal resource access
*/
destroy_workqueue(pf_to_mgmt->workq);
free_msg_buf(pf_to_mgmt);
kfree(pf_to_mgmt);
}
void hinic3_flush_mgmt_workq(struct hinic3_hwdev *hwdev)
{
if (hwdev->aeqs)
flush_workqueue(hwdev->aeqs->workq);
if (HINIC3_IS_PF(hwdev) && hwdev->pf_to_mgmt)
flush_workqueue(hwdev->pf_to_mgmt->workq);
}
void hinic3_mgmt_msg_aeqe_handler(struct hinic3_hwdev *hwdev, u8 *header,
u8 size)
{
struct hinic3_msg_pf_to_mgmt *pf_to_mgmt;
struct hinic3_recv_msg *recv_msg;
__le64 msg_header;
bool is_send_dir;
if (MBOX_MSG_HEADER_GET(*(__force __le64 *)header, SOURCE) ==
MBOX_MSG_FROM_MBOX) {
hinic3_mbox_func_aeqe_handler(hwdev, header, size);
return;
}
pf_to_mgmt = hwdev->pf_to_mgmt;
msg_header = *(__force __le64 *)header;
is_send_dir = (MBOX_MSG_HEADER_GET(msg_header, DIRECTION) ==
MBOX_MSG_SEND) ? true : false;
recv_msg = is_send_dir ? &pf_to_mgmt->recv_msg_from_mgmt :
&pf_to_mgmt->recv_resp_msg_from_mgmt;
hinic3_recv_mgmt_msg_handler(pf_to_mgmt, header, recv_msg);
}
@@ -0,0 +1,68 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_MGMT_H_
#define _HINIC3_MGMT_H_
#include <linux/types.h>
#include "hinic3_mbox.h"
#include "hinic3_hw_intf.h"
struct hinic3_hwdev;
struct hinic3_recv_msg {
/* Preallocated buffer of size MAX_PF_MGMT_BUF_SIZE that accumulates
* receive message, segment-by-segment.
*/
void *msg;
/* Message id for which segments are accumulated. */
u8 msg_id;
/* Sequence id of last received segment of current message. */
u8 seq_id;
u16 msg_len;
int async_mgmt_to_pf;
enum mgmt_mod_type mod;
u16 cmd;
struct completion recv_done;
};
enum comm_pf_to_mgmt_event_state {
COMM_SEND_EVENT_UNINIT,
COMM_SEND_EVENT_START,
COMM_SEND_EVENT_SUCCESS,
COMM_SEND_EVENT_TIMEOUT,
};
struct hinic3_msg_pf_to_mgmt {
struct hinic3_hwdev *hwdev;
struct workqueue_struct *workq;
void *mgmt_ack_buf;
struct hinic3_recv_msg recv_msg_from_mgmt;
struct hinic3_recv_msg recv_resp_msg_from_mgmt;
u16 async_msg_id;
u16 sync_msg_id;
void *async_msg_cb_data[MGMT_MOD_HW_MAX];
/* synchronizes message send with message receives via event queue */
spinlock_t sync_event_lock;
enum comm_pf_to_mgmt_event_state event_flag;
};
struct mgmt_msg_handle_work {
struct work_struct work;
struct hinic3_msg_pf_to_mgmt *pf_to_mgmt;
void *msg;
u16 msg_len;
enum mgmt_mod_type mod;
u16 cmd;
u16 msg_id;
int async_mgmt_to_pf;
};
int hinic3_pf_to_mgmt_init(struct hinic3_hwdev *hwdev);
void hinic3_pf_to_mgmt_free(struct hinic3_hwdev *hwdev);
void hinic3_flush_mgmt_workq(struct hinic3_hwdev *hwdev);
void hinic3_mgmt_msg_aeqe_handler(struct hinic3_hwdev *hwdev,
u8 *header, u8 size);
#endif
@@ -0,0 +1,307 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_MGMT_INTERFACE_H_
#define _HINIC3_MGMT_INTERFACE_H_
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/if_ether.h>
#include "hinic3_hw_intf.h"
struct l2nic_cmd_feature_nego {
struct mgmt_msg_head msg_head;
u16 func_id;
u8 opcode;
u8 rsvd;
u64 s_feature[4];
};
enum l2nic_func_tbl_cfg_bitmap {
L2NIC_FUNC_TBL_CFG_INIT = 0,
L2NIC_FUNC_TBL_CFG_RX_BUF_SIZE = 1,
L2NIC_FUNC_TBL_CFG_MTU = 2,
};
struct l2nic_func_tbl_cfg {
u16 rx_wqe_buf_size;
u16 mtu;
u32 rsvd[9];
};
struct l2nic_cmd_set_func_tbl {
struct mgmt_msg_head msg_head;
u16 func_id;
u16 rsvd;
u32 cfg_bitmap;
struct l2nic_func_tbl_cfg tbl_cfg;
};
struct l2nic_cmd_set_mac {
struct mgmt_msg_head msg_head;
u16 func_id;
u16 vlan_id;
u16 rsvd1;
u8 mac[ETH_ALEN];
};
struct l2nic_cmd_update_mac {
struct mgmt_msg_head msg_head;
u16 func_id;
u16 vlan_id;
u16 rsvd1;
u8 old_mac[ETH_ALEN];
u16 rsvd2;
u8 new_mac[ETH_ALEN];
};
struct l2nic_cmd_vlan_config {
struct mgmt_msg_head msg_head;
u16 func_id;
u8 opcode;
u8 rsvd1;
u16 vlan_id;
u16 rsvd2;
};
struct l2nic_cmd_vlan_offload {
struct mgmt_msg_head msg_head;
u16 func_id;
u8 vlan_offload;
u8 rsvd1[5];
};
/* set vlan filter */
struct l2nic_cmd_set_vlan_filter {
struct mgmt_msg_head msg_head;
u16 func_id;
u8 rsvd[2];
/* bit0:vlan filter en; bit1:broadcast_filter_en */
u32 vlan_filter_ctrl;
};
struct l2nic_cmd_set_ci_attr {
struct mgmt_msg_head msg_head;
u16 func_idx;
u8 dma_attr_off;
u8 pending_limit;
u8 coalescing_time;
u8 intr_en;
u16 intr_idx;
u32 l2nic_sqn;
u32 rsvd;
u64 ci_addr;
};
struct l2nic_cmd_clear_qp_resource {
struct mgmt_msg_head msg_head;
u16 func_id;
u16 rsvd1;
};
struct l2nic_cmd_force_pkt_drop {
struct mgmt_msg_head msg_head;
u8 port;
u8 rsvd1[3];
};
struct l2nic_cmd_set_vport_state {
struct mgmt_msg_head msg_head;
u16 func_id;
u16 rsvd1;
/* 0--disable, 1--enable */
u8 state;
u8 rsvd2[3];
};
/* *
* Definition of the NIC receiving mode
*/
#define L2NIC_RX_MODE_UC 0x01
#define L2NIC_RX_MODE_MC 0x02
#define L2NIC_RX_MODE_BC 0x04
#define L2NIC_RX_MODE_MC_ALL 0x08
#define L2NIC_RX_MODE_PROMISC 0x10
struct l2nic_rx_mode_config {
struct mgmt_msg_head msg_head;
u16 func_id;
u16 rsvd1;
u32 rx_mode;
};
struct l2nic_cmd_set_dcb_state {
struct mgmt_msg_head head;
u16 func_id;
/* 0 - get dcb state, 1 - set dcb state */
u8 op_code;
/* 0 - disable, 1 - enable dcb */
u8 state;
/* 0 - disable, 1 - enable dcb */
u8 port_state;
u8 rsvd[7];
};
struct l2nic_cmd_lro_config {
struct mgmt_msg_head msg_head;
u16 func_id;
u8 opcode;
u8 rsvd1;
u8 lro_ipv4_en;
u8 lro_ipv6_en;
/* unit is 1K */
u8 lro_max_pkt_len;
u8 resv2[13];
};
struct l2nic_cmd_lro_timer {
struct mgmt_msg_head msg_head;
/* 1: set timer value, 0: get timer value */
u8 opcode;
u8 rsvd[3];
u32 timer;
};
#define L2NIC_RSS_TYPE_VALID_MASK BIT(23)
#define L2NIC_RSS_TYPE_TCP_IPV6_EXT_MASK BIT(24)
#define L2NIC_RSS_TYPE_IPV6_EXT_MASK BIT(25)
#define L2NIC_RSS_TYPE_TCP_IPV6_MASK BIT(26)
#define L2NIC_RSS_TYPE_IPV6_MASK BIT(27)
#define L2NIC_RSS_TYPE_TCP_IPV4_MASK BIT(28)
#define L2NIC_RSS_TYPE_IPV4_MASK BIT(29)
#define L2NIC_RSS_TYPE_UDP_IPV6_MASK BIT(30)
#define L2NIC_RSS_TYPE_UDP_IPV4_MASK BIT(31)
#define L2NIC_RSS_TYPE_SET(val, member) \
FIELD_PREP(L2NIC_RSS_TYPE_##member##_MASK, val)
#define L2NIC_RSS_TYPE_GET(val, member) \
FIELD_GET(L2NIC_RSS_TYPE_##member##_MASK, val)
#define L2NIC_RSS_INDIR_SIZE 256
#define L2NIC_RSS_KEY_SIZE 40
/* IEEE 802.1Qaz std */
#define L2NIC_DCB_COS_MAX 0x8
struct l2nic_cmd_pause_config {
struct mgmt_msg_head msg_head;
u8 port_id;
u8 opcode;
u16 rsvd1;
u8 auto_neg;
u8 rx_pause;
u8 tx_pause;
u8 rsvd2[5];
};
struct l2nic_cmd_rss_ctx_tbl {
struct mgmt_msg_head msg_head;
u16 func_id;
u16 rsvd1;
u32 context;
};
struct l2nic_cmd_cfg_rss_engine {
struct mgmt_msg_head msg_head;
u16 func_id;
u8 opcode;
u8 hash_engine;
u8 rsvd1[4];
};
struct l2nic_cmd_cfg_rss_hash_key {
struct mgmt_msg_head msg_head;
u16 func_id;
u8 opcode;
u8 rsvd1;
u8 key[L2NIC_RSS_KEY_SIZE];
};
struct l2nic_cmd_cfg_rss {
struct mgmt_msg_head msg_head;
u16 func_id;
u8 rss_en;
u8 rq_priority_number;
u8 prio_tc[L2NIC_DCB_COS_MAX];
u16 num_qps;
u16 rsvd1;
};
/* Commands between NIC to fw */
enum l2nic_cmd {
/* FUNC CFG */
L2NIC_CMD_SET_FUNC_TBL = 5,
L2NIC_CMD_SET_VPORT_ENABLE = 6,
L2NIC_CMD_SET_RX_MODE = 7,
L2NIC_CMD_SET_SQ_CI_ATTR = 8,
L2NIC_CMD_CLEAR_QP_RESOURCE = 11,
L2NIC_CMD_CFG_RX_LRO = 13,
L2NIC_CMD_CFG_LRO_TIMER = 14,
L2NIC_CMD_FEATURE_NEGO = 15,
L2NIC_CMD_GET_MAC = 20,
L2NIC_CMD_SET_MAC = 21,
L2NIC_CMD_DEL_MAC = 22,
L2NIC_CMD_UPDATE_MAC = 23,
L2NIC_CMD_CFG_FUNC_VLAN = 25,
L2NIC_CMD_SET_VLAN_FILTER_EN = 26,
L2NIC_CMD_SET_RX_VLAN_OFFLOAD = 27,
L2NIC_CMD_CFG_RSS = 60,
L2NIC_CMD_CFG_RSS_HASH_KEY = 63,
L2NIC_CMD_CFG_RSS_HASH_ENGINE = 64,
L2NIC_CMD_SET_RSS_CTX_TBL = 65,
L2NIC_CMD_CFG_PAUSE_INFO = 101,
L2NIC_CMD_QOS_DCB_STATE = 110,
L2NIC_CMD_FORCE_PKT_DROP = 113,
L2NIC_CMD_MAX = 256,
};
struct l2nic_cmd_rss_set_indir_tbl {
__le32 rsvd[4];
__le16 entry[L2NIC_RSS_INDIR_SIZE];
};
/* NIC CMDQ MODE */
enum l2nic_ucode_cmd {
L2NIC_UCODE_CMD_MODIFY_QUEUE_CTX = 0,
L2NIC_UCODE_CMD_CLEAN_QUEUE_CTX = 1,
L2NIC_UCODE_CMD_SET_RSS_INDIR_TBL = 4,
};
/* hilink mac group command */
enum mag_cmd {
MAG_CMD_SET_PORT_ENABLE = 6,
MAG_CMD_GET_LINK_STATUS = 7,
MAG_CMD_GET_PORT_INFO = 153,
};
/* firmware also use this cmd report link event to driver */
struct mag_cmd_get_link_status {
struct mgmt_msg_head head;
u8 port_id;
/* 0:link down 1:link up */
u8 status;
u8 rsvd0[2];
};
enum hinic3_nic_feature_cap {
HINIC3_NIC_F_CSUM = BIT(0),
HINIC3_NIC_F_SCTP_CRC = BIT(1),
HINIC3_NIC_F_TSO = BIT(2),
HINIC3_NIC_F_LRO = BIT(3),
HINIC3_NIC_F_UFO = BIT(4),
HINIC3_NIC_F_RSS = BIT(5),
HINIC3_NIC_F_RX_VLAN_FILTER = BIT(6),
HINIC3_NIC_F_RX_VLAN_STRIP = BIT(7),
HINIC3_NIC_F_TX_VLAN_INSERT = BIT(8),
HINIC3_NIC_F_VXLAN_OFFLOAD = BIT(9),
HINIC3_NIC_F_FDIR = BIT(11),
HINIC3_NIC_F_PROMISC = BIT(12),
HINIC3_NIC_F_ALLMULTI = BIT(13),
HINIC3_NIC_F_RATE_LIMIT = BIT(16),
};
#define HINIC3_NIC_F_ALL_MASK 0x33bff
#define HINIC3_NIC_DRV_DEFAULT_FEATURE 0x3f03f
#endif
@@ -0,0 +1,873 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/netdevice.h>
#include <net/vxlan.h>
#include "hinic3_hwif.h"
#include "hinic3_nic_cfg.h"
#include "hinic3_nic_dev.h"
#include "hinic3_nic_io.h"
#include "hinic3_rss.h"
#include "hinic3_rx.h"
#include "hinic3_tx.h"
#define HINIC3_LRO_DEFAULT_COAL_PKT_SIZE 32
#define HINIC3_LRO_DEFAULT_TIME_LIMIT 16
#define VLAN_BITMAP_BITS_SIZE(nic_dev) (sizeof(*(nic_dev)->vlan_bitmap) * 8)
#define VID_LINE(nic_dev, vid) \
((vid) / VLAN_BITMAP_BITS_SIZE(nic_dev))
#define VID_COL(nic_dev, vid) \
((vid) & (VLAN_BITMAP_BITS_SIZE(nic_dev) - 1))
/* try to modify the number of irq to the target number,
* and return the actual number of irq.
*/
static u16 hinic3_qp_irq_change(struct net_device *netdev,
u16 dst_num_qp_irq)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct msix_entry *qps_msix_entries;
u16 resp_irq_num, irq_num_gap, i;
u16 idx;
int err;
qps_msix_entries = nic_dev->qps_msix_entries;
if (dst_num_qp_irq > nic_dev->num_qp_irq) {
irq_num_gap = dst_num_qp_irq - nic_dev->num_qp_irq;
err = hinic3_alloc_irqs(nic_dev->hwdev, irq_num_gap,
&qps_msix_entries[nic_dev->num_qp_irq],
&resp_irq_num);
if (err) {
netdev_err(netdev, "Failed to alloc irqs\n");
return nic_dev->num_qp_irq;
}
nic_dev->num_qp_irq += resp_irq_num;
} else if (dst_num_qp_irq < nic_dev->num_qp_irq) {
irq_num_gap = nic_dev->num_qp_irq - dst_num_qp_irq;
for (i = 0; i < irq_num_gap; i++) {
idx = (nic_dev->num_qp_irq - i) - 1;
hinic3_free_irq(nic_dev->hwdev,
qps_msix_entries[idx].vector);
qps_msix_entries[idx].vector = 0;
qps_msix_entries[idx].entry = 0;
}
nic_dev->num_qp_irq = dst_num_qp_irq;
}
return nic_dev->num_qp_irq;
}
static void hinic3_config_num_qps(struct net_device *netdev,
struct hinic3_dyna_txrxq_params *q_params)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
u16 alloc_num_irq, cur_num_irq;
u16 dst_num_irq;
if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags))
q_params->num_qps = 1;
if (nic_dev->num_qp_irq >= q_params->num_qps)
goto out;
cur_num_irq = nic_dev->num_qp_irq;
alloc_num_irq = hinic3_qp_irq_change(netdev, q_params->num_qps);
if (alloc_num_irq < q_params->num_qps) {
q_params->num_qps = alloc_num_irq;
netdev_warn(netdev, "Can not get enough irqs, adjust num_qps to %u\n",
q_params->num_qps);
/* The current irq may be in use, we must keep it */
dst_num_irq = max_t(u16, cur_num_irq, q_params->num_qps);
hinic3_qp_irq_change(netdev, dst_num_irq);
}
out:
netdev_dbg(netdev, "No need to change irqs, num_qps is %u\n",
q_params->num_qps);
}
static int hinic3_setup_num_qps(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
nic_dev->num_qp_irq = 0;
nic_dev->qps_msix_entries = kzalloc_objs(struct msix_entry,
nic_dev->max_qps);
if (!nic_dev->qps_msix_entries)
return -ENOMEM;
hinic3_config_num_qps(netdev, &nic_dev->q_params);
return 0;
}
static void hinic3_destroy_num_qps(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
u16 i;
for (i = 0; i < nic_dev->num_qp_irq; i++)
hinic3_free_irq(nic_dev->hwdev,
nic_dev->qps_msix_entries[i].vector);
kfree(nic_dev->qps_msix_entries);
}
static int hinic3_alloc_txrxq_resources(struct net_device *netdev,
struct hinic3_dyna_txrxq_params *q_params)
{
int err;
q_params->txqs_res = kzalloc_objs(*q_params->txqs_res,
q_params->num_qps);
if (!q_params->txqs_res)
return -ENOMEM;
q_params->rxqs_res = kzalloc_objs(*q_params->rxqs_res,
q_params->num_qps);
if (!q_params->rxqs_res) {
err = -ENOMEM;
goto err_free_txqs_res_arr;
}
q_params->irq_cfg = kzalloc_objs(*q_params->irq_cfg, q_params->num_qps);
if (!q_params->irq_cfg) {
err = -ENOMEM;
goto err_free_rxqs_res_arr;
}
err = hinic3_alloc_txqs_res(netdev, q_params->num_qps,
q_params->sq_depth, q_params->txqs_res);
if (err) {
netdev_err(netdev, "Failed to alloc txqs resource\n");
goto err_free_irq_cfg;
}
err = hinic3_alloc_rxqs_res(netdev, q_params->num_qps,
q_params->rq_depth, q_params->rxqs_res);
if (err) {
netdev_err(netdev, "Failed to alloc rxqs resource\n");
goto err_free_txqs_res;
}
return 0;
err_free_txqs_res:
hinic3_free_txqs_res(netdev, q_params->num_qps, q_params->sq_depth,
q_params->txqs_res);
err_free_irq_cfg:
kfree(q_params->irq_cfg);
q_params->irq_cfg = NULL;
err_free_rxqs_res_arr:
kfree(q_params->rxqs_res);
q_params->rxqs_res = NULL;
err_free_txqs_res_arr:
kfree(q_params->txqs_res);
q_params->txqs_res = NULL;
return err;
}
static void hinic3_free_txrxq_resources(struct net_device *netdev,
struct hinic3_dyna_txrxq_params *q_params)
{
hinic3_free_rxqs_res(netdev, q_params->num_qps, q_params->rq_depth,
q_params->rxqs_res);
hinic3_free_txqs_res(netdev, q_params->num_qps, q_params->sq_depth,
q_params->txqs_res);
kfree(q_params->irq_cfg);
q_params->irq_cfg = NULL;
kfree(q_params->rxqs_res);
q_params->rxqs_res = NULL;
kfree(q_params->txqs_res);
q_params->txqs_res = NULL;
}
static int hinic3_configure_txrxqs(struct net_device *netdev,
struct hinic3_dyna_txrxq_params *q_params)
{
int err;
err = hinic3_configure_txqs(netdev, q_params->num_qps,
q_params->sq_depth, q_params->txqs_res);
if (err) {
netdev_err(netdev, "Failed to configure txqs\n");
return err;
}
err = hinic3_configure_rxqs(netdev, q_params->num_qps,
q_params->rq_depth, q_params->rxqs_res);
if (err) {
netdev_err(netdev, "Failed to configure rxqs\n");
return err;
}
return 0;
}
static int hinic3_configure(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
netdev->min_mtu = HINIC3_MIN_MTU_SIZE;
netdev->max_mtu = HINIC3_MAX_JUMBO_FRAME_SIZE;
err = hinic3_set_port_mtu(netdev, netdev->mtu);
if (err) {
netdev_err(netdev, "Failed to set mtu\n");
return err;
}
/* Ensure DCB is disabled */
hinic3_sync_dcb_state(nic_dev->hwdev, 1, 0);
if (test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
err = hinic3_rss_init(netdev);
if (err) {
netdev_err(netdev, "Failed to init rss\n");
return err;
}
}
return 0;
}
static void hinic3_remove_configure(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
if (test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags))
hinic3_rss_uninit(netdev);
}
static int hinic3_alloc_channel_resources(struct net_device *netdev,
struct hinic3_dyna_qp_params *qp_params,
struct hinic3_dyna_txrxq_params *trxq_params)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
qp_params->num_qps = trxq_params->num_qps;
qp_params->sq_depth = trxq_params->sq_depth;
qp_params->rq_depth = trxq_params->rq_depth;
err = hinic3_alloc_qps(nic_dev, qp_params);
if (err) {
netdev_err(netdev, "Failed to alloc qps\n");
return err;
}
err = hinic3_alloc_txrxq_resources(netdev, trxq_params);
if (err) {
netdev_err(netdev, "Failed to alloc txrxq resources\n");
hinic3_free_qps(nic_dev, qp_params);
return err;
}
return 0;
}
static void hinic3_free_channel_resources(struct net_device *netdev,
struct hinic3_dyna_qp_params *qp_params,
struct hinic3_dyna_txrxq_params *trxq_params)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
hinic3_free_txrxq_resources(netdev, trxq_params);
hinic3_free_qps(nic_dev, qp_params);
}
static int hinic3_open_channel(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
err = hinic3_init_qp_ctxts(nic_dev);
if (err) {
netdev_err(netdev, "Failed to init qps\n");
return err;
}
err = hinic3_configure_txrxqs(netdev, &nic_dev->q_params);
if (err) {
netdev_err(netdev, "Failed to configure txrxqs\n");
goto err_free_qp_ctxts;
}
err = hinic3_qps_irq_init(netdev);
if (err) {
netdev_err(netdev, "Failed to init txrxq irq\n");
goto err_free_qp_ctxts;
}
err = hinic3_configure(netdev);
if (err) {
netdev_err(netdev, "Failed to configure device resources\n");
goto err_uninit_qps_irq;
}
return 0;
err_uninit_qps_irq:
hinic3_qps_irq_uninit(netdev);
err_free_qp_ctxts:
hinic3_free_qp_ctxts(nic_dev);
return err;
}
static void hinic3_close_channel(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
hinic3_remove_configure(netdev);
hinic3_qps_irq_uninit(netdev);
hinic3_free_qp_ctxts(nic_dev);
}
static int hinic3_maybe_set_port_state(struct net_device *netdev, bool enable)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
mutex_lock(&nic_dev->port_state_mutex);
err = hinic3_set_port_enable(nic_dev->hwdev, enable);
mutex_unlock(&nic_dev->port_state_mutex);
return err;
}
static void hinic3_print_link_message(struct net_device *netdev,
bool link_status_up)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
if (nic_dev->link_status_up == link_status_up)
return;
nic_dev->link_status_up = link_status_up;
netdev_dbg(netdev, "Link is %s\n", str_up_down(link_status_up));
}
static int hinic3_vport_up(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
bool link_status_up;
u16 glb_func_id;
int err;
glb_func_id = hinic3_global_func_id(nic_dev->hwdev);
err = hinic3_set_vport_enable(nic_dev->hwdev, glb_func_id, true);
if (err) {
netdev_err(netdev, "Failed to enable vport\n");
goto err_flush_qps_res;
}
err = hinic3_maybe_set_port_state(netdev, true);
if (err) {
netdev_err(netdev, "Failed to enable port\n");
goto err_disable_vport;
}
err = netif_set_real_num_queues(netdev, nic_dev->q_params.num_qps,
nic_dev->q_params.num_qps);
if (err) {
netdev_err(netdev, "Failed to set real number of queues\n");
goto err_disable_vport;
}
netif_tx_start_all_queues(netdev);
err = hinic3_get_link_status(nic_dev->hwdev, &link_status_up);
if (!err && link_status_up)
netif_carrier_on(netdev);
hinic3_print_link_message(netdev, link_status_up);
return 0;
err_disable_vport:
hinic3_set_vport_enable(nic_dev->hwdev, glb_func_id, false);
err_flush_qps_res:
hinic3_flush_qps_res(nic_dev->hwdev);
/* wait to guarantee that no packets will be sent to host */
msleep(100);
return err;
}
static void hinic3_vport_down(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
u16 glb_func_id;
netif_carrier_off(netdev);
netif_tx_disable(netdev);
if (nic_dev->hwdev->chip_present_flag) {
hinic3_maybe_set_port_state(netdev, false);
glb_func_id = hinic3_global_func_id(nic_dev->hwdev);
hinic3_set_vport_enable(nic_dev->hwdev, glb_func_id, false);
hinic3_flush_txqs(netdev);
/* wait to guarantee that no packets will be sent to host */
msleep(100);
hinic3_flush_qps_res(nic_dev->hwdev);
}
}
static int hinic3_open(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_dyna_qp_params qp_params;
int err;
if (test_bit(HINIC3_INTF_UP, &nic_dev->flags)) {
netdev_dbg(netdev, "Netdev already open, do nothing\n");
return 0;
}
err = hinic3_init_nicio_res(nic_dev);
if (err) {
netdev_err(netdev, "Failed to init nicio resources\n");
return err;
}
err = hinic3_setup_num_qps(netdev);
if (err) {
netdev_err(netdev, "Failed to setup num_qps\n");
goto err_free_nicio_res;
}
err = hinic3_alloc_channel_resources(netdev, &qp_params,
&nic_dev->q_params);
if (err)
goto err_destroy_num_qps;
hinic3_init_qps(nic_dev, &qp_params);
err = hinic3_open_channel(netdev);
if (err)
goto err_uninit_qps;
err = hinic3_vport_up(netdev);
if (err)
goto err_close_channel;
set_bit(HINIC3_INTF_UP, &nic_dev->flags);
return 0;
err_close_channel:
hinic3_close_channel(netdev);
err_uninit_qps:
hinic3_uninit_qps(nic_dev, &qp_params);
hinic3_free_channel_resources(netdev, &qp_params, &nic_dev->q_params);
err_destroy_num_qps:
hinic3_destroy_num_qps(netdev);
err_free_nicio_res:
hinic3_free_nicio_res(nic_dev);
return err;
}
static int hinic3_close(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_dyna_qp_params qp_params;
if (!test_and_clear_bit(HINIC3_INTF_UP, &nic_dev->flags)) {
netdev_dbg(netdev, "Netdev already close, do nothing\n");
return 0;
}
hinic3_vport_down(netdev);
hinic3_close_channel(netdev);
hinic3_uninit_qps(nic_dev, &qp_params);
hinic3_free_channel_resources(netdev, &qp_params, &nic_dev->q_params);
return 0;
}
#define SET_FEATURES_OP_STR(op) ((op) ? "Enable" : "Disable")
static int hinic3_set_feature_rx_csum(struct net_device *netdev,
netdev_features_t wanted_features,
netdev_features_t features,
netdev_features_t *failed_features)
{
netdev_features_t changed = wanted_features ^ features;
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
if (changed & NETIF_F_RXCSUM)
dev_dbg(hwdev->dev, "%s rx csum success\n",
SET_FEATURES_OP_STR(wanted_features & NETIF_F_RXCSUM));
return 0;
}
static int hinic3_set_feature_tso(struct net_device *netdev,
netdev_features_t wanted_features,
netdev_features_t features,
netdev_features_t *failed_features)
{
netdev_features_t changed = wanted_features ^ features;
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
if (changed & NETIF_F_TSO)
dev_dbg(hwdev->dev, "%s tso success\n",
SET_FEATURES_OP_STR(wanted_features & NETIF_F_TSO));
return 0;
}
static int hinic3_set_feature_lro(struct net_device *netdev,
netdev_features_t wanted_features,
netdev_features_t features,
netdev_features_t *failed_features)
{
netdev_features_t changed = wanted_features ^ features;
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
bool en = !!(wanted_features & NETIF_F_LRO);
int err;
if (!(changed & NETIF_F_LRO))
return 0;
err = hinic3_set_rx_lro_state(hwdev, en,
HINIC3_LRO_DEFAULT_TIME_LIMIT,
HINIC3_LRO_DEFAULT_COAL_PKT_SIZE);
if (err) {
dev_err(hwdev->dev, "%s lro failed\n", SET_FEATURES_OP_STR(en));
*failed_features |= NETIF_F_LRO;
}
return err;
}
static int hinic3_set_feature_rx_cvlan(struct net_device *netdev,
netdev_features_t wanted_features,
netdev_features_t features,
netdev_features_t *failed_features)
{
bool en = !!(wanted_features & NETIF_F_HW_VLAN_CTAG_RX);
netdev_features_t changed = wanted_features ^ features;
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
int err;
if (!(changed & NETIF_F_HW_VLAN_CTAG_RX))
return 0;
err = hinic3_set_rx_vlan_offload(hwdev, en);
if (err) {
dev_err(hwdev->dev, "%s rx vlan offload failed\n",
SET_FEATURES_OP_STR(en));
*failed_features |= NETIF_F_HW_VLAN_CTAG_RX;
}
return err;
}
static int hinic3_set_feature_vlan_filter(struct net_device *netdev,
netdev_features_t wanted_features,
netdev_features_t features,
netdev_features_t *failed_features)
{
bool en = !!(wanted_features & NETIF_F_HW_VLAN_CTAG_FILTER);
netdev_features_t changed = wanted_features ^ features;
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
int err;
if (!(changed & NETIF_F_HW_VLAN_CTAG_FILTER))
return 0;
err = hinic3_set_vlan_filter(hwdev, en);
if (err) {
dev_err(hwdev->dev, "%s rx vlan filter failed\n",
SET_FEATURES_OP_STR(en));
*failed_features |= NETIF_F_HW_VLAN_CTAG_FILTER;
}
return err;
}
static int hinic3_set_features(struct net_device *netdev,
netdev_features_t curr,
netdev_features_t wanted)
{
netdev_features_t failed = 0;
int err;
err = hinic3_set_feature_rx_csum(netdev, wanted, curr, &failed) |
hinic3_set_feature_tso(netdev, wanted, curr, &failed) |
hinic3_set_feature_lro(netdev, wanted, curr, &failed) |
hinic3_set_feature_rx_cvlan(netdev, wanted, curr, &failed) |
hinic3_set_feature_vlan_filter(netdev, wanted, curr, &failed);
if (err) {
netdev->features = wanted ^ failed;
return err;
}
return 0;
}
static int hinic3_ndo_set_features(struct net_device *netdev,
netdev_features_t features)
{
return hinic3_set_features(netdev, netdev->features, features);
}
static netdev_features_t hinic3_fix_features(struct net_device *netdev,
netdev_features_t features)
{
netdev_features_t features_tmp = features;
/* If Rx checksum is disabled, then LRO should also be disabled */
if (!(features_tmp & NETIF_F_RXCSUM))
features_tmp &= ~NETIF_F_LRO;
return features_tmp;
}
static netdev_features_t hinic3_features_check(struct sk_buff *skb,
struct net_device *dev,
netdev_features_t features)
{
features = vlan_features_check(skb, features);
features = vxlan_features_check(skb, features);
return features;
}
int hinic3_set_hw_features(struct net_device *netdev)
{
netdev_features_t wanted, curr;
wanted = netdev->features;
/* fake current features so all wanted are enabled */
curr = ~wanted;
return hinic3_set_features(netdev, curr, wanted);
}
static int hinic3_change_mtu(struct net_device *netdev, int new_mtu)
{
int err;
err = hinic3_set_port_mtu(netdev, new_mtu);
if (err) {
netdev_err(netdev, "Failed to change port mtu to %d\n",
new_mtu);
return err;
}
netdev_dbg(netdev, "Change mtu from %u to %d\n", netdev->mtu, new_mtu);
WRITE_ONCE(netdev->mtu, new_mtu);
return 0;
}
static int hinic3_set_mac_addr(struct net_device *netdev, void *addr)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct sockaddr *saddr = addr;
int err;
if (!is_valid_ether_addr(saddr->sa_data))
return -EADDRNOTAVAIL;
if (ether_addr_equal(netdev->dev_addr, saddr->sa_data))
return 0;
err = hinic3_update_mac(nic_dev->hwdev, netdev->dev_addr,
saddr->sa_data, 0,
hinic3_global_func_id(nic_dev->hwdev));
if (err)
return err;
eth_hw_addr_set(netdev, saddr->sa_data);
return 0;
}
static int hinic3_vlan_rx_add_vid(struct net_device *netdev,
__be16 proto, u16 vid)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
unsigned long *vlan_bitmap = nic_dev->vlan_bitmap;
u32 column, row;
u16 func_id;
int err;
column = VID_COL(nic_dev, vid);
row = VID_LINE(nic_dev, vid);
func_id = hinic3_global_func_id(nic_dev->hwdev);
err = hinic3_add_vlan(nic_dev->hwdev, vid, func_id);
if (err) {
netdev_err(netdev, "Failed to add vlan %u\n", vid);
goto out;
}
set_bit(column, &vlan_bitmap[row]);
netdev_dbg(netdev, "Add vlan %u\n", vid);
out:
return err;
}
static int hinic3_vlan_rx_kill_vid(struct net_device *netdev,
__be16 proto, u16 vid)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
unsigned long *vlan_bitmap = nic_dev->vlan_bitmap;
u32 column, row;
u16 func_id;
int err;
column = VID_COL(nic_dev, vid);
row = VID_LINE(nic_dev, vid);
func_id = hinic3_global_func_id(nic_dev->hwdev);
err = hinic3_del_vlan(nic_dev->hwdev, vid, func_id);
if (err) {
netdev_err(netdev, "Failed to delete vlan %u\n", vid);
goto out;
}
clear_bit(column, &vlan_bitmap[row]);
netdev_dbg(netdev, "Remove vlan %u\n", vid);
out:
return err;
}
static void hinic3_tx_timeout(struct net_device *netdev, unsigned int txqueue)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_io_queue *sq;
u16 sw_pi, hw_ci;
sq = nic_dev->txqs[txqueue].sq;
sw_pi = hinic3_get_sq_local_pi(sq);
hw_ci = hinic3_get_sq_hw_ci(sq);
netdev_dbg(netdev,
"txq%u: sw_pi: %u, hw_ci: %u, sw_ci: %u, napi->state: 0x%lx.\n",
txqueue, sw_pi, hw_ci, hinic3_get_sq_local_ci(sq),
nic_dev->q_params.irq_cfg[txqueue].napi.state);
if (sw_pi != hw_ci)
set_bit(HINIC3_EVENT_WORK_TX_TIMEOUT, &nic_dev->event_flag);
}
static void hinic3_get_stats64(struct net_device *netdev,
struct rtnl_link_stats64 *stats)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
u64 bytes, packets, dropped, errors;
struct hinic3_txq_stats *txq_stats;
struct hinic3_rxq_stats *rxq_stats;
struct hinic3_txq *txq;
struct hinic3_rxq *rxq;
unsigned int start;
int i;
bytes = 0;
packets = 0;
dropped = 0;
for (i = 0; i < nic_dev->max_qps; i++) {
if (!nic_dev->txqs)
break;
txq = &nic_dev->txqs[i];
txq_stats = &txq->txq_stats;
do {
start = u64_stats_fetch_begin(&txq_stats->syncp);
bytes += txq_stats->bytes;
packets += txq_stats->packets;
dropped += txq_stats->dropped;
} while (u64_stats_fetch_retry(&txq_stats->syncp, start));
}
stats->tx_packets = packets;
stats->tx_bytes = bytes;
stats->tx_dropped = dropped;
bytes = 0;
packets = 0;
errors = 0;
dropped = 0;
for (i = 0; i < nic_dev->max_qps; i++) {
if (!nic_dev->rxqs)
break;
rxq = &nic_dev->rxqs[i];
rxq_stats = &rxq->rxq_stats;
do {
start = u64_stats_fetch_begin(&rxq_stats->syncp);
bytes += rxq_stats->bytes;
packets += rxq_stats->packets;
errors += rxq_stats->csum_errors +
rxq_stats->other_errors;
dropped += rxq_stats->dropped;
} while (u64_stats_fetch_retry(&rxq_stats->syncp, start));
}
stats->rx_packets = packets;
stats->rx_bytes = bytes;
stats->rx_errors = errors;
stats->rx_dropped = dropped;
}
static void hinic3_nic_set_rx_mode(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
if (netdev_uc_count(netdev) != nic_dev->netdev_uc_cnt ||
netdev_mc_count(netdev) != nic_dev->netdev_mc_cnt) {
set_bit(HINIC3_UPDATE_MAC_FILTER, &nic_dev->flags);
nic_dev->netdev_uc_cnt = netdev_uc_count(netdev);
nic_dev->netdev_mc_cnt = netdev_mc_count(netdev);
}
queue_work(nic_dev->workq, &nic_dev->rx_mode_work);
}
static const struct net_device_ops hinic3_netdev_ops = {
.ndo_open = hinic3_open,
.ndo_stop = hinic3_close,
.ndo_set_features = hinic3_ndo_set_features,
.ndo_fix_features = hinic3_fix_features,
.ndo_features_check = hinic3_features_check,
.ndo_change_mtu = hinic3_change_mtu,
.ndo_set_mac_address = hinic3_set_mac_addr,
.ndo_validate_addr = eth_validate_addr,
.ndo_vlan_rx_add_vid = hinic3_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = hinic3_vlan_rx_kill_vid,
.ndo_tx_timeout = hinic3_tx_timeout,
.ndo_get_stats64 = hinic3_get_stats64,
.ndo_set_rx_mode = hinic3_nic_set_rx_mode,
.ndo_start_xmit = hinic3_xmit_frame,
};
void hinic3_set_netdev_ops(struct net_device *netdev)
{
netdev->netdev_ops = &hinic3_netdev_ops;
}
@@ -0,0 +1,740 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/if_vlan.h>
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_mbox.h"
#include "hinic3_nic_cfg.h"
#include "hinic3_nic_dev.h"
#include "hinic3_nic_io.h"
#define MGMT_MSG_CMD_OP_ADD 1
#define MGMT_MSG_CMD_OP_DEL 0
static int hinic3_feature_nego(struct hinic3_hwdev *hwdev, u8 opcode,
u64 *s_feature, u16 size)
{
struct l2nic_cmd_feature_nego feature_nego = {};
struct mgmt_msg_params msg_params = {};
int err;
feature_nego.func_id = hinic3_global_func_id(hwdev);
feature_nego.opcode = opcode;
if (opcode == MGMT_MSG_CMD_OP_SET)
memcpy(feature_nego.s_feature, s_feature,
array_size(size, sizeof(u64)));
mgmt_msg_params_init_default(&msg_params, &feature_nego,
sizeof(feature_nego));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_FEATURE_NEGO, &msg_params);
if (err || feature_nego.msg_head.status) {
dev_err(hwdev->dev, "Failed to negotiate nic feature, err:%d, status: 0x%x\n",
err, feature_nego.msg_head.status);
return -EIO;
}
if (opcode == MGMT_MSG_CMD_OP_GET)
memcpy(s_feature, feature_nego.s_feature,
array_size(size, sizeof(u64)));
return 0;
}
int hinic3_get_nic_feature_from_hw(struct hinic3_nic_dev *nic_dev)
{
return hinic3_feature_nego(nic_dev->hwdev, MGMT_MSG_CMD_OP_GET,
&nic_dev->nic_io->feature_cap, 1);
}
int hinic3_set_nic_feature_to_hw(struct hinic3_nic_dev *nic_dev)
{
return hinic3_feature_nego(nic_dev->hwdev, MGMT_MSG_CMD_OP_SET,
&nic_dev->nic_io->feature_cap, 1);
}
bool hinic3_test_support(struct hinic3_nic_dev *nic_dev,
enum hinic3_nic_feature_cap feature_bits)
{
return (nic_dev->nic_io->feature_cap & feature_bits) == feature_bits;
}
static int hinic3_set_rx_lro(struct hinic3_hwdev *hwdev, u8 ipv4_en, u8 ipv6_en,
u8 lro_max_pkt_len)
{
struct l2nic_cmd_lro_config lro_cfg = {};
struct mgmt_msg_params msg_params = {};
int err;
lro_cfg.func_id = hinic3_global_func_id(hwdev);
lro_cfg.opcode = MGMT_MSG_CMD_OP_SET;
lro_cfg.lro_ipv4_en = ipv4_en;
lro_cfg.lro_ipv6_en = ipv6_en;
lro_cfg.lro_max_pkt_len = lro_max_pkt_len;
mgmt_msg_params_init_default(&msg_params, &lro_cfg,
sizeof(lro_cfg));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_CFG_RX_LRO,
&msg_params);
if (err || lro_cfg.msg_head.status) {
dev_err(hwdev->dev, "Failed to set lro offload, err: %d, status: 0x%x\n",
err, lro_cfg.msg_head.status);
return -EFAULT;
}
return 0;
}
static int hinic3_set_rx_lro_timer(struct hinic3_hwdev *hwdev, u32 timer_value)
{
struct l2nic_cmd_lro_timer lro_timer = {};
struct mgmt_msg_params msg_params = {};
int err;
lro_timer.opcode = MGMT_MSG_CMD_OP_SET;
lro_timer.timer = timer_value;
mgmt_msg_params_init_default(&msg_params, &lro_timer,
sizeof(lro_timer));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_CFG_LRO_TIMER,
&msg_params);
if (err || lro_timer.msg_head.status) {
dev_err(hwdev->dev, "Failed to set lro timer, err: %d, status: 0x%x\n",
err, lro_timer.msg_head.status);
return -EFAULT;
}
return 0;
}
int hinic3_set_rx_lro_state(struct hinic3_hwdev *hwdev, u8 lro_en,
u32 lro_timer, u8 lro_max_pkt_len)
{
u8 ipv4_en, ipv6_en;
int err;
ipv4_en = lro_en ? 1 : 0;
ipv6_en = lro_en ? 1 : 0;
dev_dbg(hwdev->dev, "Set LRO max coalesce packet size to %uK\n",
lro_max_pkt_len);
err = hinic3_set_rx_lro(hwdev, ipv4_en, ipv6_en, lro_max_pkt_len);
if (err)
return err;
/* we don't set LRO timer for VF */
if (HINIC3_IS_VF(hwdev))
return 0;
dev_dbg(hwdev->dev, "Set LRO timer to %u\n", lro_timer);
return hinic3_set_rx_lro_timer(hwdev, lro_timer);
}
int hinic3_set_rx_vlan_offload(struct hinic3_hwdev *hwdev, u8 en)
{
struct l2nic_cmd_vlan_offload vlan_cfg = {};
struct mgmt_msg_params msg_params = {};
int err;
vlan_cfg.func_id = hinic3_global_func_id(hwdev);
vlan_cfg.vlan_offload = en;
mgmt_msg_params_init_default(&msg_params, &vlan_cfg,
sizeof(vlan_cfg));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_SET_RX_VLAN_OFFLOAD,
&msg_params);
if (err || vlan_cfg.msg_head.status) {
dev_err(hwdev->dev, "Failed to set rx vlan offload, err: %d, status: 0x%x\n",
err, vlan_cfg.msg_head.status);
return -EFAULT;
}
return 0;
}
int hinic3_set_vlan_filter(struct hinic3_hwdev *hwdev, u32 vlan_filter_ctrl)
{
struct l2nic_cmd_set_vlan_filter vlan_filter = {};
struct mgmt_msg_params msg_params = {};
int err;
vlan_filter.func_id = hinic3_global_func_id(hwdev);
vlan_filter.vlan_filter_ctrl = vlan_filter_ctrl;
mgmt_msg_params_init_default(&msg_params, &vlan_filter,
sizeof(vlan_filter));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_SET_VLAN_FILTER_EN,
&msg_params);
if (err || vlan_filter.msg_head.status) {
dev_err(hwdev->dev, "Failed to set vlan filter, err: %d, status: 0x%x\n",
err, vlan_filter.msg_head.status);
return -EFAULT;
}
return 0;
}
void hinic3_update_nic_feature(struct hinic3_nic_dev *nic_dev, u64 feature_cap)
{
nic_dev->nic_io->feature_cap = feature_cap;
}
static int hinic3_set_function_table(struct hinic3_hwdev *hwdev, u32 cfg_bitmap,
const struct l2nic_func_tbl_cfg *cfg)
{
struct l2nic_cmd_set_func_tbl cmd_func_tbl = {};
struct mgmt_msg_params msg_params = {};
int err;
cmd_func_tbl.func_id = hinic3_global_func_id(hwdev);
cmd_func_tbl.cfg_bitmap = cfg_bitmap;
cmd_func_tbl.tbl_cfg = *cfg;
mgmt_msg_params_init_default(&msg_params, &cmd_func_tbl,
sizeof(cmd_func_tbl));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_SET_FUNC_TBL, &msg_params);
if (err || cmd_func_tbl.msg_head.status) {
dev_err(hwdev->dev,
"Failed to set func table, bitmap: 0x%x, err: %d, status: 0x%x\n",
cfg_bitmap, err, cmd_func_tbl.msg_head.status);
return -EFAULT;
}
return 0;
}
int hinic3_init_function_table(struct hinic3_nic_dev *nic_dev)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
struct l2nic_func_tbl_cfg func_tbl_cfg = {};
u32 cfg_bitmap;
func_tbl_cfg.mtu = 0x3FFF; /* default, max mtu */
func_tbl_cfg.rx_wqe_buf_size = nic_io->rx_buf_len;
cfg_bitmap = BIT(L2NIC_FUNC_TBL_CFG_INIT) |
BIT(L2NIC_FUNC_TBL_CFG_MTU) |
BIT(L2NIC_FUNC_TBL_CFG_RX_BUF_SIZE);
return hinic3_set_function_table(nic_dev->hwdev, cfg_bitmap,
&func_tbl_cfg);
}
int hinic3_set_port_mtu(struct net_device *netdev, u16 new_mtu)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct l2nic_func_tbl_cfg func_tbl_cfg = {};
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
func_tbl_cfg.mtu = new_mtu;
return hinic3_set_function_table(hwdev, BIT(L2NIC_FUNC_TBL_CFG_MTU),
&func_tbl_cfg);
}
static bool hinic3_check_vf_set_by_pf(struct hinic3_hwdev *hwdev,
u8 status)
{
return HINIC3_IS_VF(hwdev) && status == HINIC3_PF_SET_VF_ALREADY;
}
static int hinic3_check_mac_info(struct hinic3_hwdev *hwdev, u8 status,
u16 vlan_id)
{
if ((status && status != MGMT_STATUS_EXIST) ||
((vlan_id & BIT(15)) && status == MGMT_STATUS_EXIST)) {
if (hinic3_check_vf_set_by_pf(hwdev, status))
return 0;
return -EINVAL;
}
return 0;
}
int hinic3_get_default_mac(struct hinic3_hwdev *hwdev, u8 *mac_addr)
{
struct l2nic_cmd_set_mac mac_info = {};
struct mgmt_msg_params msg_params = {};
int err;
mac_info.func_id = hinic3_global_func_id(hwdev);
mgmt_msg_params_init_default(&msg_params, &mac_info, sizeof(mac_info));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_GET_MAC,
&msg_params);
if (err || mac_info.msg_head.status) {
dev_err(hwdev->dev,
"Failed to get mac, err: %d, status: 0x%x\n",
err, mac_info.msg_head.status);
return -EFAULT;
}
ether_addr_copy(mac_addr, mac_info.mac);
return 0;
}
int hinic3_set_mac(struct hinic3_hwdev *hwdev, const u8 *mac_addr, u16 vlan_id,
u16 func_id)
{
struct l2nic_cmd_set_mac mac_info = {};
struct mgmt_msg_params msg_params = {};
int err;
if ((vlan_id & HINIC3_VLAN_ID_MASK) >= VLAN_N_VID) {
dev_err(hwdev->dev, "Invalid VLAN number: %d\n",
(vlan_id & HINIC3_VLAN_ID_MASK));
return -EINVAL;
}
mac_info.func_id = func_id;
mac_info.vlan_id = vlan_id;
ether_addr_copy(mac_info.mac, mac_addr);
mgmt_msg_params_init_default(&msg_params, &mac_info, sizeof(mac_info));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_SET_MAC, &msg_params);
if (err || hinic3_check_mac_info(hwdev, mac_info.msg_head.status,
mac_info.vlan_id)) {
dev_err(hwdev->dev,
"Failed to update MAC, err: %d, status: 0x%x\n",
err, mac_info.msg_head.status);
return -EIO;
}
if (hinic3_check_vf_set_by_pf(hwdev, mac_info.msg_head.status)) {
dev_warn(hwdev->dev, "PF has already set VF mac, Ignore set operation\n");
return -EADDRINUSE;
}
if (mac_info.msg_head.status == MGMT_STATUS_EXIST) {
dev_warn(hwdev->dev, "MAC is repeated. Ignore update operation\n");
return 0;
}
return 0;
}
int hinic3_del_mac(struct hinic3_hwdev *hwdev, const u8 *mac_addr, u16 vlan_id,
u16 func_id)
{
struct l2nic_cmd_set_mac mac_info = {};
struct mgmt_msg_params msg_params = {};
int err;
if ((vlan_id & HINIC3_VLAN_ID_MASK) >= VLAN_N_VID) {
dev_err(hwdev->dev, "Invalid VLAN number: %d\n",
(vlan_id & HINIC3_VLAN_ID_MASK));
return -EINVAL;
}
mac_info.func_id = func_id;
mac_info.vlan_id = vlan_id;
ether_addr_copy(mac_info.mac, mac_addr);
mgmt_msg_params_init_default(&msg_params, &mac_info, sizeof(mac_info));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_DEL_MAC, &msg_params);
if (err ||
(mac_info.msg_head.status &&
!hinic3_check_vf_set_by_pf(hwdev, mac_info.msg_head.status))) {
dev_err(hwdev->dev,
"Failed to delete MAC, err: %d, status: 0x%x\n",
err, mac_info.msg_head.status);
return -EFAULT;
}
if (hinic3_check_vf_set_by_pf(hwdev, mac_info.msg_head.status)) {
dev_warn(hwdev->dev, "PF has already set VF mac, Ignore delete operation.\n");
return -EADDRINUSE;
}
return 0;
}
int hinic3_update_mac(struct hinic3_hwdev *hwdev, const u8 *old_mac,
u8 *new_mac, u16 vlan_id, u16 func_id)
{
struct l2nic_cmd_update_mac mac_info = {};
struct mgmt_msg_params msg_params = {};
int err;
if ((vlan_id & HINIC3_VLAN_ID_MASK) >= VLAN_N_VID) {
dev_err(hwdev->dev, "Invalid VLAN number: %d\n",
(vlan_id & HINIC3_VLAN_ID_MASK));
return -EINVAL;
}
mac_info.func_id = func_id;
mac_info.vlan_id = vlan_id;
ether_addr_copy(mac_info.old_mac, old_mac);
ether_addr_copy(mac_info.new_mac, new_mac);
mgmt_msg_params_init_default(&msg_params, &mac_info, sizeof(mac_info));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_UPDATE_MAC, &msg_params);
if (err || hinic3_check_mac_info(hwdev, mac_info.msg_head.status,
mac_info.vlan_id)) {
dev_err(hwdev->dev,
"Failed to update MAC, err: %d, status: 0x%x\n",
err, mac_info.msg_head.status);
return -EIO;
}
if (hinic3_check_vf_set_by_pf(hwdev, mac_info.msg_head.status)) {
dev_warn(hwdev->dev, "PF has already set VF MAC. Ignore update operation\n");
return -EADDRINUSE;
}
if (mac_info.msg_head.status == HINIC3_MGMT_STATUS_EXIST) {
dev_warn(hwdev->dev,
"MAC is repeated. Ignore update operation\n");
return 0;
}
return 0;
}
int hinic3_set_ci_table(struct hinic3_hwdev *hwdev, struct hinic3_sq_attr *attr)
{
struct l2nic_cmd_set_ci_attr cons_idx_attr = {};
struct mgmt_msg_params msg_params = {};
int err;
cons_idx_attr.func_idx = hinic3_global_func_id(hwdev);
cons_idx_attr.dma_attr_off = attr->dma_attr_off;
cons_idx_attr.pending_limit = attr->pending_limit;
cons_idx_attr.coalescing_time = attr->coalescing_time;
if (attr->intr_en) {
cons_idx_attr.intr_en = attr->intr_en;
cons_idx_attr.intr_idx = attr->intr_idx;
}
cons_idx_attr.l2nic_sqn = attr->l2nic_sqn;
cons_idx_attr.ci_addr = attr->ci_dma_base;
mgmt_msg_params_init_default(&msg_params, &cons_idx_attr,
sizeof(cons_idx_attr));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_SET_SQ_CI_ATTR, &msg_params);
if (err || cons_idx_attr.msg_head.status) {
dev_err(hwdev->dev,
"Failed to set ci attribute table, err: %d, status: 0x%x\n",
err, cons_idx_attr.msg_head.status);
return -EFAULT;
}
return 0;
}
int hinic3_flush_qps_res(struct hinic3_hwdev *hwdev)
{
struct l2nic_cmd_clear_qp_resource sq_res = {};
struct mgmt_msg_params msg_params = {};
int err;
sq_res.func_id = hinic3_global_func_id(hwdev);
mgmt_msg_params_init_default(&msg_params, &sq_res, sizeof(sq_res));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_CLEAR_QP_RESOURCE,
&msg_params);
if (err || sq_res.msg_head.status) {
dev_err(hwdev->dev, "Failed to clear sq resources, err: %d, status: 0x%x\n",
err, sq_res.msg_head.status);
return -EINVAL;
}
return 0;
}
int hinic3_force_drop_tx_pkt(struct hinic3_hwdev *hwdev)
{
struct l2nic_cmd_force_pkt_drop pkt_drop = {};
struct mgmt_msg_params msg_params = {};
int err;
pkt_drop.port = hinic3_physical_port_id(hwdev);
mgmt_msg_params_init_default(&msg_params, &pkt_drop, sizeof(pkt_drop));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_FORCE_PKT_DROP, &msg_params);
if ((pkt_drop.msg_head.status != MGMT_STATUS_CMD_UNSUPPORTED &&
pkt_drop.msg_head.status) || err) {
dev_err(hwdev->dev,
"Failed to set force tx packets drop, err: %d, status: 0x%x\n",
err, pkt_drop.msg_head.status);
return -EFAULT;
}
return pkt_drop.msg_head.status;
}
int hinic3_set_rx_mode(struct hinic3_hwdev *hwdev, u32 rx_mode)
{
struct l2nic_rx_mode_config rx_mode_cfg = {};
struct mgmt_msg_params msg_params = {};
int err;
rx_mode_cfg.func_id = hinic3_global_func_id(hwdev);
rx_mode_cfg.rx_mode = rx_mode;
mgmt_msg_params_init_default(&msg_params, &rx_mode_cfg,
sizeof(rx_mode_cfg));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_SET_RX_MODE, &msg_params);
if (err || rx_mode_cfg.msg_head.status) {
dev_err(hwdev->dev, "Failed to set rx mode, err: %d, status: 0x%x\n",
err, rx_mode_cfg.msg_head.status);
return -EFAULT;
}
return 0;
}
static int hinic3_config_vlan(struct hinic3_hwdev *hwdev,
u8 opcode, u16 vlan_id, u16 func_id)
{
struct l2nic_cmd_vlan_config vlan_info = {};
struct mgmt_msg_params msg_params = {};
int err;
vlan_info.opcode = opcode;
vlan_info.func_id = func_id;
vlan_info.vlan_id = vlan_id;
mgmt_msg_params_init_default(&msg_params, &vlan_info,
sizeof(vlan_info));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_CFG_FUNC_VLAN, &msg_params);
if (err || vlan_info.msg_head.status) {
dev_err(hwdev->dev,
"Failed to %s vlan, err: %d, status: 0x%x\n",
opcode == MGMT_MSG_CMD_OP_ADD ? "add" : "delete",
err, vlan_info.msg_head.status);
return -EFAULT;
}
return 0;
}
int hinic3_add_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id)
{
return hinic3_config_vlan(hwdev, MGMT_MSG_CMD_OP_ADD, vlan_id, func_id);
}
int hinic3_del_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id)
{
return hinic3_config_vlan(hwdev, MGMT_MSG_CMD_OP_DEL, vlan_id, func_id);
}
int hinic3_set_port_enable(struct hinic3_hwdev *hwdev, bool enable)
{
struct mag_cmd_set_port_enable en_state = {};
struct mgmt_msg_params msg_params = {};
int err;
if (HINIC3_IS_VF(hwdev))
return 0;
en_state.function_id = hinic3_global_func_id(hwdev);
en_state.state = enable ? MAG_CMD_TX_ENABLE | MAG_CMD_RX_ENABLE :
MAG_CMD_PORT_DISABLE;
mgmt_msg_params_init_default(&msg_params, &en_state,
sizeof(en_state));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_HILINK,
MAG_CMD_SET_PORT_ENABLE, &msg_params);
if (err || en_state.head.status) {
dev_err(hwdev->dev, "Failed to set port state, err: %d, status: 0x%x\n",
err, en_state.head.status);
return -EFAULT;
}
return 0;
}
int hinic3_sync_dcb_state(struct hinic3_hwdev *hwdev, u8 op_code, u8 state)
{
struct l2nic_cmd_set_dcb_state dcb_state = {};
struct mgmt_msg_params msg_params = {};
int err;
dcb_state.op_code = op_code;
dcb_state.state = state;
dcb_state.func_id = hinic3_global_func_id(hwdev);
mgmt_msg_params_init_default(&msg_params, &dcb_state,
sizeof(dcb_state));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_QOS_DCB_STATE, &msg_params);
if (err || dcb_state.head.status) {
dev_err(hwdev->dev,
"Failed to set dcb state, err: %d, status: 0x%x\n",
err, dcb_state.head.status);
return -EFAULT;
}
return 0;
}
int hinic3_get_link_status(struct hinic3_hwdev *hwdev, bool *link_status_up)
{
struct mag_cmd_get_link_status get_link = {};
struct mgmt_msg_params msg_params = {};
int err;
get_link.port_id = hinic3_physical_port_id(hwdev);
mgmt_msg_params_init_default(&msg_params, &get_link, sizeof(get_link));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_HILINK,
MAG_CMD_GET_LINK_STATUS, &msg_params);
if (err || get_link.head.status) {
dev_err(hwdev->dev, "Failed to get link state, err: %d, status: 0x%x\n",
err, get_link.head.status);
return -EIO;
}
*link_status_up = !!get_link.status;
return 0;
}
int hinic3_get_port_info(struct hinic3_hwdev *hwdev,
struct hinic3_nic_port_info *port_info)
{
struct mag_cmd_get_port_info port_msg = {};
struct mgmt_msg_params msg_params = {};
int err;
port_msg.port_id = hinic3_physical_port_id(hwdev);
mgmt_msg_params_init_default(&msg_params, &port_msg, sizeof(port_msg));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_HILINK,
MAG_CMD_GET_PORT_INFO, &msg_params);
if (err || port_msg.head.status) {
dev_err(hwdev->dev,
"Failed to get port info, err: %d, status: 0x%x\n",
err, port_msg.head.status);
return -EFAULT;
}
port_info->autoneg_cap = port_msg.an_support;
port_info->autoneg_state = port_msg.an_en;
port_info->duplex = port_msg.duplex;
port_info->port_type = port_msg.wire_type;
port_info->speed = port_msg.speed;
port_info->fec = port_msg.fec;
port_info->supported_mode = port_msg.supported_mode;
port_info->advertised_mode = port_msg.advertised_mode;
return 0;
}
int hinic3_set_vport_enable(struct hinic3_hwdev *hwdev, u16 func_id,
bool enable)
{
struct l2nic_cmd_set_vport_state en_state = {};
struct mgmt_msg_params msg_params = {};
int err;
en_state.func_id = func_id;
en_state.state = enable ? 1 : 0;
mgmt_msg_params_init_default(&msg_params, &en_state, sizeof(en_state));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_SET_VPORT_ENABLE, &msg_params);
if (err || en_state.msg_head.status) {
dev_err(hwdev->dev, "Failed to set vport state, err: %d, status: 0x%x\n",
err, en_state.msg_head.status);
return -EINVAL;
}
return 0;
}
static int hinic3_cfg_hw_pause(struct hinic3_hwdev *hwdev, u8 opcode,
struct hinic3_nic_pause_config *nic_pause)
{
struct l2nic_cmd_pause_config pause_info = {};
struct mgmt_msg_params msg_params = {};
int err;
pause_info.port_id = hinic3_physical_port_id(hwdev);
pause_info.opcode = opcode;
if (opcode == MGMT_MSG_CMD_OP_SET) {
pause_info.auto_neg = nic_pause->auto_neg;
pause_info.rx_pause = nic_pause->rx_pause;
pause_info.tx_pause = nic_pause->tx_pause;
}
mgmt_msg_params_init_default(&msg_params, &pause_info,
sizeof(pause_info));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_CFG_PAUSE_INFO, &msg_params);
if (err || pause_info.msg_head.status) {
dev_err(hwdev->dev, "Failed to %s pause info, err: %d, status: 0x%x\n",
opcode == MGMT_MSG_CMD_OP_SET ? "set" : "get",
err, pause_info.msg_head.status);
return -EFAULT;
}
if (opcode == MGMT_MSG_CMD_OP_GET) {
nic_pause->auto_neg = pause_info.auto_neg;
nic_pause->rx_pause = pause_info.rx_pause;
nic_pause->tx_pause = pause_info.tx_pause;
}
return 0;
}
int hinic3_get_pause_info(struct hinic3_nic_dev *nic_dev,
struct hinic3_nic_pause_config *nic_pause)
{
return hinic3_cfg_hw_pause(nic_dev->hwdev, MGMT_MSG_CMD_OP_GET,
nic_pause);
}
@@ -0,0 +1,218 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_NIC_CFG_H_
#define _HINIC3_NIC_CFG_H_
#include <linux/types.h>
#include "hinic3_hwif.h"
#include "hinic3_hw_intf.h"
#include "hinic3_mgmt_interface.h"
struct hinic3_hwdev;
struct hinic3_nic_dev;
#define HINIC3_MIN_MTU_SIZE 256
#define HINIC3_MAX_JUMBO_FRAME_SIZE 9600
#define HINIC3_VLAN_ID_MASK 0x7FFF
#define HINIC3_PF_SET_VF_ALREADY 0x4
#define HINIC3_MGMT_STATUS_EXIST 0x6
enum hinic3_nic_event_type {
HINIC3_NIC_EVENT_LINK_DOWN = 0,
HINIC3_NIC_EVENT_LINK_UP = 1,
HINIC3_NIC_EVENT_PORT_MODULE_EVENT = 2,
};
struct hinic3_sq_attr {
u8 dma_attr_off;
u8 pending_limit;
u8 coalescing_time;
u8 intr_en;
u16 intr_idx;
u32 l2nic_sqn;
u64 ci_dma_base;
};
enum mag_cmd_port_an {
PORT_CFG_AN_ON = 1,
};
/* mag supported/advertised link mode bitmap */
enum mag_cmd_link_mode {
LINK_MODE_GE = 0,
LINK_MODE_10GE_BASE_R = 1,
LINK_MODE_25GE_BASE_R = 2,
LINK_MODE_40GE_BASE_R4 = 3,
LINK_MODE_50GE_BASE_R = 4,
LINK_MODE_50GE_BASE_R2 = 5,
LINK_MODE_100GE_BASE_R = 6,
LINK_MODE_100GE_BASE_R2 = 7,
LINK_MODE_100GE_BASE_R4 = 8,
LINK_MODE_200GE_BASE_R2 = 9,
LINK_MODE_200GE_BASE_R4 = 10,
LINK_MODE_MAX_NUMBERS,
LINK_MODE_UNKNOWN = 0xFFFF
};
struct mag_cmd_get_port_info {
struct mgmt_msg_head head;
u8 port_id;
u8 rsvd0[3];
u8 wire_type;
u8 an_support;
u8 an_en;
u8 duplex;
u8 speed;
u8 fec;
u8 lanes;
u8 rsvd1;
u32 supported_mode;
u32 advertised_mode;
u8 rsvd2[8];
};
#define MAG_CMD_PORT_DISABLE 0x0
#define MAG_CMD_TX_ENABLE 0x1
#define MAG_CMD_RX_ENABLE 0x2
/* the physical port is disabled only when all pf of the port are set to down,
* if any pf is enabled, the port is enabled
*/
struct mag_cmd_set_port_enable {
struct mgmt_msg_head head;
u16 function_id;
u16 rsvd0;
/* bitmap bit0:tx_en bit1:rx_en */
u8 state;
u8 rsvd1[3];
};
/* xsfp wire type, refers to cmis protocol definition */
enum mag_wire_type {
MAG_CMD_WIRE_TYPE_UNKNOWN = 0x0,
MAG_CMD_WIRE_TYPE_MM = 0x1,
MAG_CMD_WIRE_TYPE_SM = 0x2,
MAG_CMD_WIRE_TYPE_COPPER = 0x3,
MAG_CMD_WIRE_TYPE_ACC = 0x4,
MAG_CMD_WIRE_TYPE_BASET = 0x5,
MAG_CMD_WIRE_TYPE_AOC = 0x40,
MAG_CMD_WIRE_TYPE_ELECTRIC = 0x41,
MAG_CMD_WIRE_TYPE_BACKPLANE = 0x42
};
#define XSFP_INFO_MAX_SIZE 640
struct mag_cmd_get_xsfp_info {
struct mgmt_msg_head head;
u8 port_id;
u8 wire_type;
u16 out_len;
u32 rsvd;
u8 sfp_info[XSFP_INFO_MAX_SIZE];
};
struct mag_cmd_get_xsfp_present {
struct mgmt_msg_head head;
u8 port_id;
/* 0:present, 1:absent */
u8 abs_status;
u8 rsvd[2];
};
enum link_err_type {
LINK_ERR_MODULE_UNRECOGENIZED,
LINK_ERR_NUM,
};
enum port_module_event_type {
HINIC3_PORT_MODULE_CABLE_PLUGGED,
HINIC3_PORT_MODULE_CABLE_UNPLUGGED,
HINIC3_PORT_MODULE_LINK_ERR,
HINIC3_PORT_MODULE_MAX_EVENT,
};
struct hinic3_port_module_event {
enum port_module_event_type type;
enum link_err_type err_type;
};
struct hinic3_nic_port_info {
u8 port_type;
u8 autoneg_cap;
u8 autoneg_state;
u8 duplex;
u8 speed;
u8 fec;
u32 supported_mode;
u32 advertised_mode;
};
struct hinic3_nic_pause_config {
u8 auto_neg;
u8 rx_pause;
u8 tx_pause;
};
struct hinic3_nic_cfg {
/* Valid when pfc is disabled */
bool pause_set;
struct hinic3_nic_pause_config nic_pause;
u8 pfc_en;
u8 pfc_bitmap;
struct hinic3_nic_port_info port_info;
};
int hinic3_get_nic_feature_from_hw(struct hinic3_nic_dev *nic_dev);
int hinic3_set_nic_feature_to_hw(struct hinic3_nic_dev *nic_dev);
bool hinic3_test_support(struct hinic3_nic_dev *nic_dev,
enum hinic3_nic_feature_cap feature_bits);
void hinic3_update_nic_feature(struct hinic3_nic_dev *nic_dev, u64 feature_cap);
int hinic3_set_rx_lro_state(struct hinic3_hwdev *hwdev, u8 lro_en,
u32 lro_timer, u8 lro_max_pkt_len);
int hinic3_set_rx_vlan_offload(struct hinic3_hwdev *hwdev, u8 en);
int hinic3_set_vlan_filter(struct hinic3_hwdev *hwdev, u32 vlan_filter_ctrl);
int hinic3_init_function_table(struct hinic3_nic_dev *nic_dev);
int hinic3_set_port_mtu(struct net_device *netdev, u16 new_mtu);
int hinic3_get_default_mac(struct hinic3_hwdev *hwdev, u8 *mac_addr);
int hinic3_set_mac(struct hinic3_hwdev *hwdev, const u8 *mac_addr, u16 vlan_id,
u16 func_id);
int hinic3_del_mac(struct hinic3_hwdev *hwdev, const u8 *mac_addr, u16 vlan_id,
u16 func_id);
int hinic3_update_mac(struct hinic3_hwdev *hwdev, const u8 *old_mac,
u8 *new_mac, u16 vlan_id, u16 func_id);
int hinic3_set_ci_table(struct hinic3_hwdev *hwdev,
struct hinic3_sq_attr *attr);
int hinic3_flush_qps_res(struct hinic3_hwdev *hwdev);
int hinic3_force_drop_tx_pkt(struct hinic3_hwdev *hwdev);
int hinic3_set_rx_mode(struct hinic3_hwdev *hwdev, u32 rx_mode);
int hinic3_sync_dcb_state(struct hinic3_hwdev *hwdev, u8 op_code, u8 state);
int hinic3_set_port_enable(struct hinic3_hwdev *hwdev, bool enable);
int hinic3_get_link_status(struct hinic3_hwdev *hwdev, bool *link_status_up);
int hinic3_get_port_info(struct hinic3_hwdev *hwdev,
struct hinic3_nic_port_info *port_info);
int hinic3_set_vport_enable(struct hinic3_hwdev *hwdev, u16 func_id,
bool enable);
int hinic3_add_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id);
int hinic3_del_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id);
int hinic3_get_pause_info(struct hinic3_nic_dev *nic_dev,
struct hinic3_nic_pause_config *nic_pause);
#endif
@@ -0,0 +1,154 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_NIC_DEV_H_
#define _HINIC3_NIC_DEV_H_
#include <linux/if_vlan.h>
#include <linux/netdevice.h>
#include "hinic3_hw_cfg.h"
#include "hinic3_hwdev.h"
#include "hinic3_mgmt_interface.h"
#define HINIC3_VLAN_BITMAP_BYTE_SIZE(nic_dev) (sizeof(*(nic_dev)->vlan_bitmap))
#define HINIC3_VLAN_BITMAP_SIZE(nic_dev) \
(VLAN_N_VID / HINIC3_VLAN_BITMAP_BYTE_SIZE(nic_dev))
enum hinic3_flags {
HINIC3_INTF_UP,
HINIC3_MAC_FILTER_CHANGED,
HINIC3_RSS_ENABLE,
HINIC3_UPDATE_MAC_FILTER,
};
enum hinic3_event_work_flags {
HINIC3_EVENT_WORK_TX_TIMEOUT,
};
enum hinic3_rx_mode_state {
HINIC3_HW_PROMISC_ON,
HINIC3_HW_ALLMULTI_ON,
HINIC3_PROMISC_FORCE_ON,
HINIC3_ALLMULTI_FORCE_ON,
};
enum hinic3_mac_filter_state {
HINIC3_MAC_WAIT_HW_SYNC,
HINIC3_MAC_HW_SYNCING,
HINIC3_MAC_HW_SYNCED,
HINIC3_MAC_WAIT_HW_UNSYNC,
HINIC3_MAC_HW_UNSYNCED,
};
struct hinic3_mac_filter {
struct list_head list;
u8 addr[ETH_ALEN];
unsigned long state;
};
enum hinic3_rss_hash_type {
HINIC3_RSS_HASH_ENGINE_TYPE_XOR = 0,
HINIC3_RSS_HASH_ENGINE_TYPE_TOEP = 1,
};
struct hinic3_rss_type {
u8 tcp_ipv6_ext;
u8 ipv6_ext;
u8 tcp_ipv6;
u8 ipv6;
u8 tcp_ipv4;
u8 ipv4;
u8 udp_ipv6;
u8 udp_ipv4;
};
struct hinic3_irq_cfg {
struct net_device *netdev;
u16 msix_entry_idx;
/* provided by OS */
u32 irq_id;
char irq_name[IFNAMSIZ + 16];
struct napi_struct napi;
cpumask_t affinity_mask;
struct hinic3_txq *txq;
struct hinic3_rxq *rxq;
u16 total_events;
};
struct hinic3_dyna_txrxq_params {
u16 num_qps;
u32 sq_depth;
u32 rq_depth;
struct hinic3_dyna_txq_res *txqs_res;
struct hinic3_dyna_rxq_res *rxqs_res;
struct hinic3_irq_cfg *irq_cfg;
};
struct hinic3_intr_coal_info {
u8 pending_limit;
u8 coalesce_timer_cfg;
u8 resend_timer_cfg;
u8 rx_pending_limit_low;
u8 rx_pending_limit_high;
};
struct hinic3_nic_dev {
struct pci_dev *pdev;
struct net_device *netdev;
struct hinic3_hwdev *hwdev;
struct hinic3_nic_io *nic_io;
u32 msg_enable;
u16 max_qps;
u16 rx_buf_len;
u32 lro_replenish_thld;
unsigned long *vlan_bitmap;
unsigned long flags;
struct hinic3_nic_service_cap nic_svc_cap;
struct hinic3_dyna_txrxq_params q_params;
struct hinic3_txq *txqs;
struct hinic3_rxq *rxqs;
enum hinic3_rss_hash_type rss_hash_type;
struct hinic3_rss_type rss_type;
u8 *rss_hkey;
u16 *rss_indir;
u16 num_qp_irq;
struct msix_entry *qps_msix_entries;
struct hinic3_intr_coal_info *intr_coalesce;
u32 adaptive_rx_coal;
struct workqueue_struct *workq;
struct delayed_work periodic_work;
struct work_struct rx_mode_work;
/* lock for enable/disable port */
struct mutex port_state_mutex;
struct list_head uc_filter_list;
struct list_head mc_filter_list;
unsigned long rx_mod_state;
int netdev_uc_cnt;
int netdev_mc_cnt;
/* flag bits defined by hinic3_event_work_flags */
unsigned long event_flag;
bool link_status_up;
};
void hinic3_set_netdev_ops(struct net_device *netdev);
int hinic3_set_hw_features(struct net_device *netdev);
int hinic3_qps_irq_init(struct net_device *netdev);
void hinic3_qps_irq_uninit(struct net_device *netdev);
void hinic3_set_rx_mode_work(struct work_struct *work);
void hinic3_clean_mac_list_filter(struct net_device *netdev);
void hinic3_set_ethtool_ops(struct net_device *netdev);
#endif
@@ -0,0 +1,889 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include "hinic3_cmdq.h"
#include "hinic3_hw_comm.h"
#include "hinic3_hw_intf.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_nic_cfg.h"
#include "hinic3_nic_dev.h"
#include "hinic3_nic_io.h"
#define HINIC3_DEFAULT_TX_CI_PENDING_LIMIT 1
#define HINIC3_DEFAULT_TX_CI_COALESCING_TIME 1
#define HINIC3_DEFAULT_DROP_THD_ON (0xFFFF)
#define HINIC3_DEFAULT_DROP_THD_OFF 0
#define HINIC3_CI_Q_ADDR_SIZE (64)
#define HINIC3_CI_TABLE_SIZE(num_qps) \
(ALIGN((num_qps) * HINIC3_CI_Q_ADDR_SIZE, HINIC3_MIN_PAGE_SIZE))
#define HINIC3_CI_VADDR(base_addr, q_id) \
((u8 *)(base_addr) + (q_id) * HINIC3_CI_Q_ADDR_SIZE)
#define HINIC3_CI_PADDR(base_paddr, q_id) \
((base_paddr) + (q_id) * HINIC3_CI_Q_ADDR_SIZE)
#define SQ_WQ_PREFETCH_MAX 1
#define SQ_WQ_PREFETCH_MIN 1
#define SQ_WQ_PREFETCH_THRESHOLD 16
#define RQ_WQ_PREFETCH_MAX 4
#define RQ_WQ_PREFETCH_MIN 1
#define RQ_WQ_PREFETCH_THRESHOLD 256
/* (2048 - 8) / 64 */
#define HINIC3_Q_CTXT_MAX 31
enum hinic3_qp_ctxt_type {
HINIC3_QP_CTXT_TYPE_SQ = 0,
HINIC3_QP_CTXT_TYPE_RQ = 1,
};
struct hinic3_qp_ctxt_hdr {
__le16 num_queues;
__le16 queue_type;
__le16 start_qid;
__le16 rsvd;
};
struct hinic3_sq_ctxt {
__le32 ci_pi;
__le32 drop_mode_sp;
__le32 wq_pfn_hi_owner;
__le32 wq_pfn_lo;
__le32 rsvd0;
__le32 pkt_drop_thd;
__le32 global_sq_id;
__le32 vlan_ceq_attr;
__le32 pref_cache;
__le32 pref_ci_owner;
__le32 pref_wq_pfn_hi_ci;
__le32 pref_wq_pfn_lo;
__le32 rsvd8;
__le32 rsvd9;
__le32 wq_block_pfn_hi;
__le32 wq_block_pfn_lo;
};
struct hinic3_rq_ctxt {
__le32 ci_pi;
__le32 ceq_attr;
__le32 wq_pfn_hi_type_owner;
__le32 wq_pfn_lo;
__le32 rsvd[3];
__le32 cqe_sge_len;
__le32 pref_cache;
__le32 pref_ci_owner;
__le32 pref_wq_pfn_hi_ci;
__le32 pref_wq_pfn_lo;
__le32 pi_paddr_hi;
__le32 pi_paddr_lo;
__le32 wq_block_pfn_hi;
__le32 wq_block_pfn_lo;
};
struct hinic3_sq_ctxt_block {
struct hinic3_qp_ctxt_hdr cmdq_hdr;
struct hinic3_sq_ctxt sq_ctxt[HINIC3_Q_CTXT_MAX];
};
struct hinic3_rq_ctxt_block {
struct hinic3_qp_ctxt_hdr cmdq_hdr;
struct hinic3_rq_ctxt rq_ctxt[HINIC3_Q_CTXT_MAX];
};
struct hinic3_clean_queue_ctxt {
struct hinic3_qp_ctxt_hdr cmdq_hdr;
__le32 rsvd;
};
#define SQ_CTXT_SIZE(num_sqs) \
(sizeof(struct hinic3_qp_ctxt_hdr) + \
(num_sqs) * sizeof(struct hinic3_sq_ctxt))
#define RQ_CTXT_SIZE(num_rqs) \
(sizeof(struct hinic3_qp_ctxt_hdr) + \
(num_rqs) * sizeof(struct hinic3_rq_ctxt))
#define SQ_CTXT_PREF_CI_HI_SHIFT 12
#define SQ_CTXT_PREF_CI_HI(val) ((val) >> SQ_CTXT_PREF_CI_HI_SHIFT)
#define SQ_CTXT_PI_IDX_MASK GENMASK(15, 0)
#define SQ_CTXT_CI_IDX_MASK GENMASK(31, 16)
#define SQ_CTXT_CI_PI_SET(val, member) \
FIELD_PREP(SQ_CTXT_##member##_MASK, val)
#define SQ_CTXT_MODE_SP_FLAG_MASK BIT(0)
#define SQ_CTXT_MODE_PKT_DROP_MASK BIT(1)
#define SQ_CTXT_MODE_SET(val, member) \
FIELD_PREP(SQ_CTXT_MODE_##member##_MASK, val)
#define SQ_CTXT_WQ_PAGE_HI_PFN_MASK GENMASK(19, 0)
#define SQ_CTXT_WQ_PAGE_OWNER_MASK BIT(23)
#define SQ_CTXT_WQ_PAGE_SET(val, member) \
FIELD_PREP(SQ_CTXT_WQ_PAGE_##member##_MASK, val)
#define SQ_CTXT_PKT_DROP_THD_ON_MASK GENMASK(15, 0)
#define SQ_CTXT_PKT_DROP_THD_OFF_MASK GENMASK(31, 16)
#define SQ_CTXT_PKT_DROP_THD_SET(val, member) \
FIELD_PREP(SQ_CTXT_PKT_DROP_##member##_MASK, val)
#define SQ_CTXT_GLOBAL_SQ_ID_MASK GENMASK(12, 0)
#define SQ_CTXT_GLOBAL_QUEUE_ID_SET(val, member) \
FIELD_PREP(SQ_CTXT_##member##_MASK, val)
#define SQ_CTXT_VLAN_INSERT_MODE_MASK GENMASK(20, 19)
#define SQ_CTXT_VLAN_CEQ_EN_MASK BIT(23)
#define SQ_CTXT_VLAN_CEQ_SET(val, member) \
FIELD_PREP(SQ_CTXT_VLAN_##member##_MASK, val)
#define SQ_CTXT_PREF_CACHE_THRESHOLD_MASK GENMASK(13, 0)
#define SQ_CTXT_PREF_CACHE_MAX_MASK GENMASK(24, 14)
#define SQ_CTXT_PREF_CACHE_MIN_MASK GENMASK(31, 25)
#define SQ_CTXT_PREF_CI_HI_MASK GENMASK(3, 0)
#define SQ_CTXT_PREF_OWNER_MASK BIT(4)
#define SQ_CTXT_PREF_WQ_PFN_HI_MASK GENMASK(19, 0)
#define SQ_CTXT_PREF_CI_LOW_MASK GENMASK(31, 20)
#define SQ_CTXT_PREF_SET(val, member) \
FIELD_PREP(SQ_CTXT_PREF_##member##_MASK, val)
#define SQ_CTXT_WQ_BLOCK_PFN_HI_MASK GENMASK(22, 0)
#define SQ_CTXT_WQ_BLOCK_SET(val, member) \
FIELD_PREP(SQ_CTXT_WQ_BLOCK_##member##_MASK, val)
/* reuse SQ macro for RQ because the hardware format is identical */
#define RQ_CTXT_PREF_CI_HI(val) SQ_CTXT_PREF_CI_HI(val)
#define RQ_CTXT_PI_IDX_MASK GENMASK(15, 0)
#define RQ_CTXT_CI_IDX_MASK GENMASK(31, 16)
#define RQ_CTXT_CI_PI_SET(val, member) \
FIELD_PREP(RQ_CTXT_##member##_MASK, val)
#define RQ_CTXT_CEQ_ATTR_INTR_MASK GENMASK(30, 21)
#define RQ_CTXT_CEQ_ATTR_EN_MASK BIT(31)
#define RQ_CTXT_CEQ_ATTR_SET(val, member) \
FIELD_PREP(RQ_CTXT_CEQ_ATTR_##member##_MASK, val)
#define RQ_CTXT_WQ_PAGE_HI_PFN_MASK GENMASK(19, 0)
#define RQ_CTXT_WQ_PAGE_WQE_TYPE_MASK GENMASK(29, 28)
#define RQ_CTXT_WQ_PAGE_OWNER_MASK BIT(31)
#define RQ_CTXT_WQ_PAGE_SET(val, member) \
FIELD_PREP(RQ_CTXT_WQ_PAGE_##member##_MASK, val)
#define RQ_CTXT_CQE_LEN_MASK GENMASK(29, 28)
#define RQ_CTXT_CQE_LEN_SET(val, member) \
FIELD_PREP(RQ_CTXT_##member##_MASK, val)
#define RQ_CTXT_PREF_CACHE_THRESHOLD_MASK GENMASK(13, 0)
#define RQ_CTXT_PREF_CACHE_MAX_MASK GENMASK(24, 14)
#define RQ_CTXT_PREF_CACHE_MIN_MASK GENMASK(31, 25)
#define RQ_CTXT_PREF_CI_HI_MASK GENMASK(3, 0)
#define RQ_CTXT_PREF_OWNER_MASK BIT(4)
#define RQ_CTXT_PREF_WQ_PFN_HI_MASK GENMASK(19, 0)
#define RQ_CTXT_PREF_CI_LOW_MASK GENMASK(31, 20)
#define RQ_CTXT_PREF_SET(val, member) \
FIELD_PREP(RQ_CTXT_PREF_##member##_MASK, val)
#define RQ_CTXT_WQ_BLOCK_PFN_HI_MASK GENMASK(22, 0)
#define RQ_CTXT_WQ_BLOCK_SET(val, member) \
FIELD_PREP(RQ_CTXT_WQ_BLOCK_##member##_MASK, val)
#define WQ_PAGE_PFN_SHIFT 12
#define WQ_BLOCK_PFN_SHIFT 9
#define WQ_PAGE_PFN(page_addr) ((page_addr) >> WQ_PAGE_PFN_SHIFT)
#define WQ_BLOCK_PFN(page_addr) ((page_addr) >> WQ_BLOCK_PFN_SHIFT)
int hinic3_init_nic_io(struct hinic3_nic_dev *nic_dev)
{
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
struct hinic3_nic_io *nic_io;
int err;
nic_io = kzalloc_obj(*nic_io);
if (!nic_io)
return -ENOMEM;
nic_dev->nic_io = nic_io;
err = hinic3_set_func_svc_used_state(hwdev, COMM_FUNC_SVC_T_NIC, 1);
if (err) {
dev_err(hwdev->dev, "Failed to set function svc used state\n");
goto err_free_nicio;
}
err = hinic3_init_function_table(nic_dev);
if (err) {
dev_err(hwdev->dev, "Failed to init function table\n");
goto err_clear_func_svc_used_state;
}
nic_io->rx_buf_len = nic_dev->rx_buf_len;
err = hinic3_get_nic_feature_from_hw(nic_dev);
if (err) {
dev_err(hwdev->dev, "Failed to get nic features\n");
goto err_clear_func_svc_used_state;
}
nic_io->feature_cap &= HINIC3_NIC_F_ALL_MASK;
nic_io->feature_cap &= HINIC3_NIC_DRV_DEFAULT_FEATURE;
dev_dbg(hwdev->dev, "nic features: 0x%llx\n\n", nic_io->feature_cap);
return 0;
err_clear_func_svc_used_state:
hinic3_set_func_svc_used_state(hwdev, COMM_FUNC_SVC_T_NIC, 0);
err_free_nicio:
nic_dev->nic_io = NULL;
kfree(nic_io);
return err;
}
void hinic3_free_nic_io(struct hinic3_nic_dev *nic_dev)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
hinic3_set_func_svc_used_state(nic_dev->hwdev, COMM_FUNC_SVC_T_NIC, 0);
nic_dev->nic_io = NULL;
kfree(nic_io);
}
int hinic3_init_nicio_res(struct hinic3_nic_dev *nic_dev)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
void __iomem *db_base;
int err;
nic_io->max_qps = hinic3_func_max_qnum(hwdev);
err = hinic3_alloc_db_addr(hwdev, &db_base, NULL);
if (err) {
dev_err(hwdev->dev, "Failed to allocate doorbell for sqs\n");
return err;
}
nic_io->sqs_db_addr = db_base;
err = hinic3_alloc_db_addr(hwdev, &db_base, NULL);
if (err) {
hinic3_free_db_addr(hwdev, nic_io->sqs_db_addr);
dev_err(hwdev->dev, "Failed to allocate doorbell for rqs\n");
return err;
}
nic_io->rqs_db_addr = db_base;
nic_io->ci_vaddr_base =
dma_alloc_coherent(hwdev->dev,
HINIC3_CI_TABLE_SIZE(nic_io->max_qps),
&nic_io->ci_dma_base,
GFP_KERNEL);
if (!nic_io->ci_vaddr_base) {
hinic3_free_db_addr(hwdev, nic_io->sqs_db_addr);
hinic3_free_db_addr(hwdev, nic_io->rqs_db_addr);
return -ENOMEM;
}
return 0;
}
void hinic3_free_nicio_res(struct hinic3_nic_dev *nic_dev)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
dma_free_coherent(hwdev->dev,
HINIC3_CI_TABLE_SIZE(nic_io->max_qps),
nic_io->ci_vaddr_base, nic_io->ci_dma_base);
hinic3_free_db_addr(hwdev, nic_io->sqs_db_addr);
hinic3_free_db_addr(hwdev, nic_io->rqs_db_addr);
}
static int hinic3_create_sq(struct hinic3_hwdev *hwdev,
struct hinic3_io_queue *sq,
u16 q_id, u32 sq_depth, u16 sq_msix_idx)
{
int err;
/* sq used & hardware request init 1 */
sq->owner = 1;
sq->q_id = q_id;
sq->msix_entry_idx = sq_msix_idx;
err = hinic3_wq_create(hwdev, &sq->wq, sq_depth,
BIT(HINIC3_SQ_WQEBB_SHIFT));
if (err) {
dev_err(hwdev->dev, "Failed to create tx queue %u wq\n",
q_id);
return err;
}
return 0;
}
static int hinic3_create_rq(struct hinic3_hwdev *hwdev,
struct hinic3_io_queue *rq,
u16 q_id, u32 rq_depth, u16 rq_msix_idx)
{
int err;
rq->q_id = q_id;
rq->msix_entry_idx = rq_msix_idx;
err = hinic3_wq_create(hwdev, &rq->wq, rq_depth,
BIT(HINIC3_RQ_WQEBB_SHIFT +
HINIC3_NORMAL_RQ_WQE));
if (err) {
dev_err(hwdev->dev, "Failed to create rx queue %u wq\n",
q_id);
return err;
}
return 0;
}
static int hinic3_create_qp(struct hinic3_hwdev *hwdev,
struct hinic3_io_queue *sq,
struct hinic3_io_queue *rq, u16 q_id, u32 sq_depth,
u32 rq_depth, u16 qp_msix_idx)
{
int err;
err = hinic3_create_sq(hwdev, sq, q_id, sq_depth, qp_msix_idx);
if (err) {
dev_err(hwdev->dev, "Failed to create sq, qid: %u\n",
q_id);
return err;
}
err = hinic3_create_rq(hwdev, rq, q_id, rq_depth, qp_msix_idx);
if (err) {
dev_err(hwdev->dev, "Failed to create rq, qid: %u\n",
q_id);
goto err_destroy_sq_wq;
}
return 0;
err_destroy_sq_wq:
hinic3_wq_destroy(hwdev, &sq->wq);
return err;
}
static void hinic3_destroy_qp(struct hinic3_hwdev *hwdev,
struct hinic3_io_queue *sq,
struct hinic3_io_queue *rq)
{
hinic3_wq_destroy(hwdev, &sq->wq);
hinic3_wq_destroy(hwdev, &rq->wq);
}
int hinic3_alloc_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params)
{
struct msix_entry *qps_msix_entries = nic_dev->qps_msix_entries;
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
struct hinic3_io_queue *sqs;
struct hinic3_io_queue *rqs;
u16 q_id;
int err;
if (qp_params->num_qps > nic_io->max_qps || !qp_params->num_qps)
return -EINVAL;
sqs = kzalloc_objs(*sqs, qp_params->num_qps);
if (!sqs) {
err = -ENOMEM;
goto err_out;
}
rqs = kzalloc_objs(*rqs, qp_params->num_qps);
if (!rqs) {
err = -ENOMEM;
goto err_free_sqs;
}
for (q_id = 0; q_id < qp_params->num_qps; q_id++) {
err = hinic3_create_qp(hwdev, &sqs[q_id], &rqs[q_id], q_id,
qp_params->sq_depth, qp_params->rq_depth,
qps_msix_entries[q_id].entry);
if (err) {
dev_err(hwdev->dev, "Failed to allocate qp %u, err: %d\n",
q_id, err);
goto err_destroy_qp;
}
}
qp_params->sqs = sqs;
qp_params->rqs = rqs;
return 0;
err_destroy_qp:
while (q_id > 0) {
q_id--;
hinic3_destroy_qp(hwdev, &sqs[q_id], &rqs[q_id]);
}
kfree(rqs);
err_free_sqs:
kfree(sqs);
err_out:
return err;
}
void hinic3_free_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params)
{
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
u16 q_id;
for (q_id = 0; q_id < qp_params->num_qps; q_id++)
hinic3_destroy_qp(hwdev, &qp_params->sqs[q_id],
&qp_params->rqs[q_id]);
kfree(qp_params->sqs);
kfree(qp_params->rqs);
}
void hinic3_init_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
struct hinic3_io_queue *sqs = qp_params->sqs;
struct hinic3_io_queue *rqs = qp_params->rqs;
u16 q_id;
nic_io->num_qps = qp_params->num_qps;
nic_io->sq = qp_params->sqs;
nic_io->rq = qp_params->rqs;
for (q_id = 0; q_id < nic_io->num_qps; q_id++) {
sqs[q_id].cons_idx_addr =
(u16 *)HINIC3_CI_VADDR(nic_io->ci_vaddr_base, q_id);
/* clear ci value */
WRITE_ONCE(*sqs[q_id].cons_idx_addr, 0);
sqs[q_id].db_addr = nic_io->sqs_db_addr;
rqs[q_id].db_addr = nic_io->rqs_db_addr;
}
}
void hinic3_uninit_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
qp_params->sqs = nic_io->sq;
qp_params->rqs = nic_io->rq;
qp_params->num_qps = nic_io->num_qps;
}
static void hinic3_qp_prepare_cmdq_header(struct hinic3_qp_ctxt_hdr *qp_ctxt_hdr,
enum hinic3_qp_ctxt_type ctxt_type,
u16 num_queues, u16 q_id)
{
qp_ctxt_hdr->queue_type = cpu_to_le16(ctxt_type);
qp_ctxt_hdr->num_queues = cpu_to_le16(num_queues);
qp_ctxt_hdr->start_qid = cpu_to_le16(q_id);
qp_ctxt_hdr->rsvd = 0;
}
static void hinic3_sq_prepare_ctxt(struct hinic3_io_queue *sq, u16 sq_id,
struct hinic3_sq_ctxt *sq_ctxt)
{
u64 wq_page_addr, wq_page_pfn, wq_block_pfn;
u32 wq_block_pfn_hi, wq_block_pfn_lo;
u32 wq_page_pfn_hi, wq_page_pfn_lo;
u16 pi_start, ci_start;
ci_start = hinic3_get_sq_local_ci(sq);
pi_start = hinic3_get_sq_local_pi(sq);
wq_page_addr = hinic3_wq_get_first_wqe_page_addr(&sq->wq);
wq_page_pfn = WQ_PAGE_PFN(wq_page_addr);
wq_page_pfn_hi = upper_32_bits(wq_page_pfn);
wq_page_pfn_lo = lower_32_bits(wq_page_pfn);
wq_block_pfn = WQ_BLOCK_PFN(sq->wq.wq_block_paddr);
wq_block_pfn_hi = upper_32_bits(wq_block_pfn);
wq_block_pfn_lo = lower_32_bits(wq_block_pfn);
sq_ctxt->ci_pi =
cpu_to_le32(SQ_CTXT_CI_PI_SET(ci_start, CI_IDX) |
SQ_CTXT_CI_PI_SET(pi_start, PI_IDX));
sq_ctxt->drop_mode_sp =
cpu_to_le32(SQ_CTXT_MODE_SET(0, SP_FLAG) |
SQ_CTXT_MODE_SET(0, PKT_DROP));
sq_ctxt->wq_pfn_hi_owner =
cpu_to_le32(SQ_CTXT_WQ_PAGE_SET(wq_page_pfn_hi, HI_PFN) |
SQ_CTXT_WQ_PAGE_SET(1, OWNER));
sq_ctxt->wq_pfn_lo = cpu_to_le32(wq_page_pfn_lo);
sq_ctxt->pkt_drop_thd =
cpu_to_le32(SQ_CTXT_PKT_DROP_THD_SET(HINIC3_DEFAULT_DROP_THD_ON, THD_ON) |
SQ_CTXT_PKT_DROP_THD_SET(HINIC3_DEFAULT_DROP_THD_OFF, THD_OFF));
sq_ctxt->global_sq_id =
cpu_to_le32(SQ_CTXT_GLOBAL_QUEUE_ID_SET((u32)sq_id,
GLOBAL_SQ_ID));
/* enable insert c-vlan by default */
sq_ctxt->vlan_ceq_attr =
cpu_to_le32(SQ_CTXT_VLAN_CEQ_SET(0, CEQ_EN) |
SQ_CTXT_VLAN_CEQ_SET(1, INSERT_MODE));
sq_ctxt->rsvd0 = 0;
sq_ctxt->pref_cache =
cpu_to_le32(SQ_CTXT_PREF_SET(SQ_WQ_PREFETCH_MIN, CACHE_MIN) |
SQ_CTXT_PREF_SET(SQ_WQ_PREFETCH_MAX, CACHE_MAX) |
SQ_CTXT_PREF_SET(SQ_WQ_PREFETCH_THRESHOLD, CACHE_THRESHOLD));
sq_ctxt->pref_ci_owner =
cpu_to_le32(SQ_CTXT_PREF_SET(SQ_CTXT_PREF_CI_HI(ci_start), CI_HI) |
SQ_CTXT_PREF_SET(1, OWNER));
sq_ctxt->pref_wq_pfn_hi_ci =
cpu_to_le32(SQ_CTXT_PREF_SET(ci_start, CI_LOW) |
SQ_CTXT_PREF_SET(wq_page_pfn_hi, WQ_PFN_HI));
sq_ctxt->pref_wq_pfn_lo = cpu_to_le32(wq_page_pfn_lo);
sq_ctxt->wq_block_pfn_hi =
cpu_to_le32(SQ_CTXT_WQ_BLOCK_SET(wq_block_pfn_hi, PFN_HI));
sq_ctxt->wq_block_pfn_lo = cpu_to_le32(wq_block_pfn_lo);
}
static void hinic3_rq_prepare_ctxt_get_wq_info(struct hinic3_io_queue *rq,
u32 *wq_page_pfn_hi,
u32 *wq_page_pfn_lo,
u32 *wq_block_pfn_hi,
u32 *wq_block_pfn_lo)
{
u64 wq_page_addr, wq_page_pfn, wq_block_pfn;
wq_page_addr = hinic3_wq_get_first_wqe_page_addr(&rq->wq);
wq_page_pfn = WQ_PAGE_PFN(wq_page_addr);
*wq_page_pfn_hi = upper_32_bits(wq_page_pfn);
*wq_page_pfn_lo = lower_32_bits(wq_page_pfn);
wq_block_pfn = WQ_BLOCK_PFN(rq->wq.wq_block_paddr);
*wq_block_pfn_hi = upper_32_bits(wq_block_pfn);
*wq_block_pfn_lo = lower_32_bits(wq_block_pfn);
}
static void hinic3_rq_prepare_ctxt(struct hinic3_io_queue *rq,
struct hinic3_rq_ctxt *rq_ctxt)
{
u32 wq_block_pfn_hi, wq_block_pfn_lo;
u32 wq_page_pfn_hi, wq_page_pfn_lo;
u16 pi_start, ci_start;
ci_start = (rq->wq.cons_idx & rq->wq.idx_mask) << HINIC3_NORMAL_RQ_WQE;
pi_start = (rq->wq.prod_idx & rq->wq.idx_mask) << HINIC3_NORMAL_RQ_WQE;
hinic3_rq_prepare_ctxt_get_wq_info(rq, &wq_page_pfn_hi, &wq_page_pfn_lo,
&wq_block_pfn_hi, &wq_block_pfn_lo);
rq_ctxt->ci_pi =
cpu_to_le32(RQ_CTXT_CI_PI_SET(ci_start, CI_IDX) |
RQ_CTXT_CI_PI_SET(pi_start, PI_IDX));
rq_ctxt->ceq_attr =
cpu_to_le32(RQ_CTXT_CEQ_ATTR_SET(0, EN) |
RQ_CTXT_CEQ_ATTR_SET(rq->msix_entry_idx, INTR));
rq_ctxt->wq_pfn_hi_type_owner =
cpu_to_le32(RQ_CTXT_WQ_PAGE_SET(wq_page_pfn_hi, HI_PFN) |
RQ_CTXT_WQ_PAGE_SET(1, OWNER));
/* use 16Byte WQE */
rq_ctxt->wq_pfn_hi_type_owner |=
cpu_to_le32(RQ_CTXT_WQ_PAGE_SET(2, WQE_TYPE));
rq_ctxt->cqe_sge_len = cpu_to_le32(RQ_CTXT_CQE_LEN_SET(1, CQE_LEN));
rq_ctxt->wq_pfn_lo = cpu_to_le32(wq_page_pfn_lo);
rq_ctxt->pref_cache =
cpu_to_le32(RQ_CTXT_PREF_SET(RQ_WQ_PREFETCH_MIN, CACHE_MIN) |
RQ_CTXT_PREF_SET(RQ_WQ_PREFETCH_MAX, CACHE_MAX) |
RQ_CTXT_PREF_SET(RQ_WQ_PREFETCH_THRESHOLD, CACHE_THRESHOLD));
rq_ctxt->pref_ci_owner =
cpu_to_le32(RQ_CTXT_PREF_SET(RQ_CTXT_PREF_CI_HI(ci_start),
CI_HI) |
RQ_CTXT_PREF_SET(1, OWNER));
rq_ctxt->pref_wq_pfn_hi_ci =
cpu_to_le32(RQ_CTXT_PREF_SET(wq_page_pfn_hi, WQ_PFN_HI) |
RQ_CTXT_PREF_SET(ci_start, CI_LOW));
rq_ctxt->pref_wq_pfn_lo = cpu_to_le32(wq_page_pfn_lo);
rq_ctxt->wq_block_pfn_hi =
cpu_to_le32(RQ_CTXT_WQ_BLOCK_SET(wq_block_pfn_hi, PFN_HI));
rq_ctxt->wq_block_pfn_lo = cpu_to_le32(wq_block_pfn_lo);
}
static int init_sq_ctxts(struct hinic3_nic_dev *nic_dev)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
struct hinic3_sq_ctxt_block *sq_ctxt_block;
u16 q_id, curr_id, max_ctxts, i;
struct hinic3_sq_ctxt *sq_ctxt;
struct hinic3_cmd_buf *cmd_buf;
struct hinic3_io_queue *sq;
__le64 out_param;
int err = 0;
cmd_buf = hinic3_alloc_cmd_buf(hwdev);
if (!cmd_buf) {
dev_err(hwdev->dev, "Failed to allocate cmd buf\n");
return -ENOMEM;
}
q_id = 0;
while (q_id < nic_io->num_qps) {
sq_ctxt_block = cmd_buf->buf;
sq_ctxt = sq_ctxt_block->sq_ctxt;
max_ctxts = (nic_io->num_qps - q_id) > HINIC3_Q_CTXT_MAX ?
HINIC3_Q_CTXT_MAX : (nic_io->num_qps - q_id);
hinic3_qp_prepare_cmdq_header(&sq_ctxt_block->cmdq_hdr,
HINIC3_QP_CTXT_TYPE_SQ, max_ctxts,
q_id);
for (i = 0; i < max_ctxts; i++) {
curr_id = q_id + i;
sq = &nic_io->sq[curr_id];
hinic3_sq_prepare_ctxt(sq, curr_id, &sq_ctxt[i]);
}
hinic3_cmdq_buf_swab32(sq_ctxt_block, sizeof(*sq_ctxt_block));
cmd_buf->size = cpu_to_le16(SQ_CTXT_SIZE(max_ctxts));
err = hinic3_cmdq_direct_resp(hwdev, MGMT_MOD_L2NIC,
L2NIC_UCODE_CMD_MODIFY_QUEUE_CTX,
cmd_buf, &out_param);
if (err || out_param) {
dev_err(hwdev->dev, "Failed to set SQ ctxts, err: %d, out_param: 0x%llx\n",
err, out_param);
err = -EFAULT;
break;
}
q_id += max_ctxts;
}
hinic3_free_cmd_buf(hwdev, cmd_buf);
return err;
}
static int init_rq_ctxts(struct hinic3_nic_dev *nic_dev)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
struct hinic3_rq_ctxt_block *rq_ctxt_block;
u16 q_id, curr_id, max_ctxts, i;
struct hinic3_rq_ctxt *rq_ctxt;
struct hinic3_cmd_buf *cmd_buf;
struct hinic3_io_queue *rq;
__le64 out_param;
int err = 0;
cmd_buf = hinic3_alloc_cmd_buf(hwdev);
if (!cmd_buf) {
dev_err(hwdev->dev, "Failed to allocate cmd buf\n");
return -ENOMEM;
}
q_id = 0;
while (q_id < nic_io->num_qps) {
rq_ctxt_block = cmd_buf->buf;
rq_ctxt = rq_ctxt_block->rq_ctxt;
max_ctxts = (nic_io->num_qps - q_id) > HINIC3_Q_CTXT_MAX ?
HINIC3_Q_CTXT_MAX : (nic_io->num_qps - q_id);
hinic3_qp_prepare_cmdq_header(&rq_ctxt_block->cmdq_hdr,
HINIC3_QP_CTXT_TYPE_RQ, max_ctxts,
q_id);
for (i = 0; i < max_ctxts; i++) {
curr_id = q_id + i;
rq = &nic_io->rq[curr_id];
hinic3_rq_prepare_ctxt(rq, &rq_ctxt[i]);
}
hinic3_cmdq_buf_swab32(rq_ctxt_block, sizeof(*rq_ctxt_block));
cmd_buf->size = cpu_to_le16(RQ_CTXT_SIZE(max_ctxts));
err = hinic3_cmdq_direct_resp(hwdev, MGMT_MOD_L2NIC,
L2NIC_UCODE_CMD_MODIFY_QUEUE_CTX,
cmd_buf, &out_param);
if (err || out_param) {
dev_err(hwdev->dev, "Failed to set RQ ctxts, err: %d, out_param: 0x%llx\n",
err, out_param);
err = -EFAULT;
break;
}
q_id += max_ctxts;
}
hinic3_free_cmd_buf(hwdev, cmd_buf);
return err;
}
static int init_qp_ctxts(struct hinic3_nic_dev *nic_dev)
{
int err;
err = init_sq_ctxts(nic_dev);
if (err)
return err;
err = init_rq_ctxts(nic_dev);
if (err)
return err;
return 0;
}
static int clean_queue_offload_ctxt(struct hinic3_nic_dev *nic_dev,
enum hinic3_qp_ctxt_type ctxt_type)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
struct hinic3_clean_queue_ctxt *ctxt_block;
struct hinic3_cmd_buf *cmd_buf;
__le64 out_param;
int err;
cmd_buf = hinic3_alloc_cmd_buf(hwdev);
if (!cmd_buf) {
dev_err(hwdev->dev, "Failed to allocate cmd buf\n");
return -ENOMEM;
}
ctxt_block = cmd_buf->buf;
ctxt_block->cmdq_hdr.num_queues = cpu_to_le16(nic_io->max_qps);
ctxt_block->cmdq_hdr.queue_type = cpu_to_le16(ctxt_type);
ctxt_block->cmdq_hdr.start_qid = 0;
ctxt_block->cmdq_hdr.rsvd = 0;
ctxt_block->rsvd = 0;
hinic3_cmdq_buf_swab32(ctxt_block, sizeof(*ctxt_block));
cmd_buf->size = cpu_to_le16(sizeof(*ctxt_block));
err = hinic3_cmdq_direct_resp(hwdev, MGMT_MOD_L2NIC,
L2NIC_UCODE_CMD_CLEAN_QUEUE_CTX,
cmd_buf, &out_param);
if (err || out_param) {
dev_err(hwdev->dev, "Failed to clean queue offload ctxts, err: %d,out_param: 0x%llx\n",
err, out_param);
err = -EFAULT;
}
hinic3_free_cmd_buf(hwdev, cmd_buf);
return err;
}
static int clean_qp_offload_ctxt(struct hinic3_nic_dev *nic_dev)
{
/* clean LRO/TSO context space */
return clean_queue_offload_ctxt(nic_dev, HINIC3_QP_CTXT_TYPE_SQ) ||
clean_queue_offload_ctxt(nic_dev, HINIC3_QP_CTXT_TYPE_RQ);
}
/* init qps ctxt and set sq ci attr and arm all sq */
int hinic3_init_qp_ctxts(struct hinic3_nic_dev *nic_dev)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
struct hinic3_sq_attr sq_attr;
u32 rq_depth;
u16 q_id;
int err;
err = init_qp_ctxts(nic_dev);
if (err) {
dev_err(hwdev->dev, "Failed to init QP ctxts\n");
return err;
}
/* clean LRO/TSO context space */
err = clean_qp_offload_ctxt(nic_dev);
if (err) {
dev_err(hwdev->dev, "Failed to clean qp offload ctxts\n");
return err;
}
rq_depth = nic_io->rq[0].wq.q_depth << HINIC3_NORMAL_RQ_WQE;
err = hinic3_set_root_ctxt(hwdev, rq_depth, nic_io->sq[0].wq.q_depth,
nic_io->rx_buf_len);
if (err) {
dev_err(hwdev->dev, "Failed to set root context\n");
return err;
}
for (q_id = 0; q_id < nic_io->num_qps; q_id++) {
sq_attr.ci_dma_base =
HINIC3_CI_PADDR(nic_io->ci_dma_base, q_id) >> 0x2;
sq_attr.pending_limit = HINIC3_DEFAULT_TX_CI_PENDING_LIMIT;
sq_attr.coalescing_time = HINIC3_DEFAULT_TX_CI_COALESCING_TIME;
sq_attr.intr_en = 1;
sq_attr.intr_idx = nic_io->sq[q_id].msix_entry_idx;
sq_attr.l2nic_sqn = q_id;
sq_attr.dma_attr_off = 0;
err = hinic3_set_ci_table(hwdev, &sq_attr);
if (err) {
dev_err(hwdev->dev, "Failed to set ci table\n");
goto err_clean_root_ctxt;
}
}
return 0;
err_clean_root_ctxt:
hinic3_clean_root_ctxt(hwdev);
return err;
}
void hinic3_free_qp_ctxts(struct hinic3_nic_dev *nic_dev)
{
hinic3_clean_root_ctxt(nic_dev->hwdev);
}
@@ -0,0 +1,145 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_NIC_IO_H_
#define _HINIC3_NIC_IO_H_
#include <linux/bitfield.h>
#include "hinic3_wq.h"
struct hinic3_nic_dev;
#define HINIC3_SQ_WQEBB_SHIFT 4
#define HINIC3_RQ_WQEBB_SHIFT 3
#define HINIC3_SQ_WQEBB_SIZE BIT(HINIC3_SQ_WQEBB_SHIFT)
/* ******************** RQ_CTRL ******************** */
enum hinic3_rq_wqe_type {
HINIC3_NORMAL_RQ_WQE = 1,
};
/* ******************** SQ_CTRL ******************** */
#define HINIC3_TX_MSS_DEFAULT 0x3E00
#define HINIC3_TX_MSS_MIN 0x50
#define HINIC3_MAX_SQ_SGE 18
struct hinic3_io_queue {
struct hinic3_wq wq;
u8 owner;
u16 q_id;
u16 msix_entry_idx;
u8 __iomem *db_addr;
u16 *cons_idx_addr;
} ____cacheline_aligned;
static inline u16 hinic3_get_sq_local_ci(const struct hinic3_io_queue *sq)
{
const struct hinic3_wq *wq = &sq->wq;
return wq->cons_idx & wq->idx_mask;
}
static inline u16 hinic3_get_sq_local_pi(const struct hinic3_io_queue *sq)
{
const struct hinic3_wq *wq = &sq->wq;
return wq->prod_idx & wq->idx_mask;
}
static inline u16 hinic3_get_sq_hw_ci(const struct hinic3_io_queue *sq)
{
const struct hinic3_wq *wq = &sq->wq;
return READ_ONCE(*sq->cons_idx_addr) & wq->idx_mask;
}
/* ******************** DB INFO ******************** */
#define DB_INFO_QID_MASK GENMASK(12, 0)
#define DB_INFO_CFLAG_MASK BIT(23)
#define DB_INFO_COS_MASK GENMASK(26, 24)
#define DB_INFO_TYPE_MASK GENMASK(31, 27)
#define DB_INFO_SET(val, member) \
FIELD_PREP(DB_INFO_##member##_MASK, val)
#define DB_PI_LOW_MASK 0xFFU
#define DB_PI_HIGH_MASK 0xFFU
#define DB_PI_HI_SHIFT 8
#define DB_PI_LOW(pi) ((pi) & DB_PI_LOW_MASK)
#define DB_PI_HIGH(pi) (((pi) >> DB_PI_HI_SHIFT) & DB_PI_HIGH_MASK)
#define DB_ADDR(q, pi) ((u64 __iomem *)((q)->db_addr) + DB_PI_LOW(pi))
#define DB_SRC_TYPE 1
/* CFLAG_DATA_PATH */
#define DB_CFLAG_DP_SQ 0
#define DB_CFLAG_DP_RQ 1
struct hinic3_nic_db {
__le32 db_info;
__le32 pi_hi;
};
static inline void hinic3_write_db(struct hinic3_io_queue *queue, int cos,
u8 cflag, u16 pi)
{
struct hinic3_nic_db db;
db.db_info =
cpu_to_le32(DB_INFO_SET(DB_SRC_TYPE, TYPE) |
DB_INFO_SET(cflag, CFLAG) |
DB_INFO_SET(cos, COS) |
DB_INFO_SET(queue->q_id, QID));
db.pi_hi = cpu_to_le32(DB_PI_HIGH(pi));
writeq(*((u64 *)&db), DB_ADDR(queue, pi));
}
struct hinic3_dyna_qp_params {
u16 num_qps;
u32 sq_depth;
u32 rq_depth;
struct hinic3_io_queue *sqs;
struct hinic3_io_queue *rqs;
};
struct hinic3_nic_io {
struct hinic3_io_queue *sq;
struct hinic3_io_queue *rq;
u16 num_qps;
u16 max_qps;
/* Base address for consumer index of all tx queues. Each queue is
* given a full cache line to hold its consumer index. HW updates
* current consumer index as it consumes tx WQEs.
*/
void *ci_vaddr_base;
dma_addr_t ci_dma_base;
u8 __iomem *sqs_db_addr;
u8 __iomem *rqs_db_addr;
u16 rx_buf_len;
u64 feature_cap;
};
int hinic3_init_nic_io(struct hinic3_nic_dev *nic_dev);
void hinic3_free_nic_io(struct hinic3_nic_dev *nic_dev);
int hinic3_init_nicio_res(struct hinic3_nic_dev *nic_dev);
void hinic3_free_nicio_res(struct hinic3_nic_dev *nic_dev);
int hinic3_alloc_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params);
void hinic3_free_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params);
void hinic3_init_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params);
void hinic3_uninit_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params);
int hinic3_init_qp_ctxts(struct hinic3_nic_dev *nic_dev);
void hinic3_free_qp_ctxts(struct hinic3_nic_dev *nic_dev);
#endif
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_PCI_ID_TBL_H_
#define _HINIC3_PCI_ID_TBL_H_
#define PCI_DEV_ID_HINIC3_PF 0x0222
#define PCI_DEV_ID_HINIC3_VF 0x375F
#endif
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/device.h>
#include "hinic3_hwdev.h"
#include "hinic3_queue_common.h"
void hinic3_queue_pages_init(struct hinic3_queue_pages *qpages, u32 q_depth,
u32 page_size, u32 elem_size)
{
u32 elem_per_page;
elem_per_page = min(page_size / elem_size, q_depth);
qpages->pages = NULL;
qpages->page_size = page_size;
qpages->num_pages = max(q_depth / elem_per_page, 1);
qpages->elem_size_shift = ilog2(elem_size);
qpages->elem_per_pg_shift = ilog2(elem_per_page);
}
static void __queue_pages_free(struct hinic3_hwdev *hwdev,
struct hinic3_queue_pages *qpages, u32 pg_cnt)
{
while (pg_cnt > 0) {
pg_cnt--;
hinic3_dma_free_coherent_align(hwdev->dev,
qpages->pages + pg_cnt);
}
kfree(qpages->pages);
qpages->pages = NULL;
}
void hinic3_queue_pages_free(struct hinic3_hwdev *hwdev,
struct hinic3_queue_pages *qpages)
{
__queue_pages_free(hwdev, qpages, qpages->num_pages);
}
int hinic3_queue_pages_alloc(struct hinic3_hwdev *hwdev,
struct hinic3_queue_pages *qpages, u32 align)
{
u32 pg_idx;
int err;
qpages->pages = kzalloc_objs(qpages->pages[0], qpages->num_pages);
if (!qpages->pages)
return -ENOMEM;
if (align == 0)
align = qpages->page_size;
for (pg_idx = 0; pg_idx < qpages->num_pages; pg_idx++) {
err = hinic3_dma_zalloc_coherent_align(hwdev->dev,
qpages->page_size,
align,
GFP_KERNEL,
qpages->pages + pg_idx);
if (err) {
__queue_pages_free(hwdev, qpages, pg_idx);
return err;
}
}
return 0;
}
@@ -0,0 +1,54 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_QUEUE_COMMON_H_
#define _HINIC3_QUEUE_COMMON_H_
#include <linux/types.h>
#include "hinic3_common.h"
struct hinic3_hwdev;
struct hinic3_queue_pages {
/* Array of DMA-able pages that actually holds the queue entries. */
struct hinic3_dma_addr_align *pages;
/* Page size in bytes. */
u32 page_size;
/* Number of pages, must be power of 2. */
u16 num_pages;
u8 elem_size_shift;
u8 elem_per_pg_shift;
};
void hinic3_queue_pages_init(struct hinic3_queue_pages *qpages, u32 q_depth,
u32 page_size, u32 elem_size);
int hinic3_queue_pages_alloc(struct hinic3_hwdev *hwdev,
struct hinic3_queue_pages *qpages, u32 align);
void hinic3_queue_pages_free(struct hinic3_hwdev *hwdev,
struct hinic3_queue_pages *qpages);
/* Get pointer to queue entry at the specified index. Index does not have to be
* masked to queue depth, only least significant bits will be used. Also
* provides remaining elements in same page (including the first one) in case
* caller needs multiple entries.
*/
static inline void *get_q_element(const struct hinic3_queue_pages *qpages,
u32 idx, u32 *remaining_in_page)
{
const struct hinic3_dma_addr_align *page;
u32 page_idx, elem_idx, elem_per_pg, ofs;
u8 shift;
shift = qpages->elem_per_pg_shift;
page_idx = (idx >> shift) & (qpages->num_pages - 1);
elem_per_pg = 1 << shift;
elem_idx = idx & (elem_per_pg - 1);
if (remaining_in_page)
*remaining_in_page = elem_per_pg - elem_idx;
ofs = elem_idx << qpages->elem_size_shift;
page = qpages->pages + page_idx;
return (char *)page->align_vaddr + ofs;
}
#endif
@@ -0,0 +1,336 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/ethtool.h>
#include "hinic3_cmdq.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_mbox.h"
#include "hinic3_nic_cfg.h"
#include "hinic3_nic_dev.h"
#include "hinic3_rss.h"
static void hinic3_fillout_indir_tbl(struct net_device *netdev, u16 *indir)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
u16 i, num_qps;
num_qps = nic_dev->q_params.num_qps;
for (i = 0; i < L2NIC_RSS_INDIR_SIZE; i++)
indir[i] = ethtool_rxfh_indir_default(i, num_qps);
}
static int hinic3_rss_cfg(struct hinic3_hwdev *hwdev, u8 rss_en, u16 num_qps)
{
struct mgmt_msg_params msg_params = {};
struct l2nic_cmd_cfg_rss rss_cfg = {};
int err;
rss_cfg.func_id = hinic3_global_func_id(hwdev);
rss_cfg.rss_en = rss_en;
rss_cfg.rq_priority_number = 0;
rss_cfg.num_qps = num_qps;
mgmt_msg_params_init_default(&msg_params, &rss_cfg, sizeof(rss_cfg));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_CFG_RSS, &msg_params);
if (err || rss_cfg.msg_head.status) {
dev_err(hwdev->dev, "Failed to set rss cfg, err: %d, status: 0x%x\n",
err, rss_cfg.msg_head.status);
return -EINVAL;
}
return 0;
}
static void hinic3_init_rss_parameters(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
nic_dev->rss_hash_type = HINIC3_RSS_HASH_ENGINE_TYPE_XOR;
nic_dev->rss_type.tcp_ipv6_ext = 1;
nic_dev->rss_type.ipv6_ext = 1;
nic_dev->rss_type.tcp_ipv6 = 1;
nic_dev->rss_type.ipv6 = 1;
nic_dev->rss_type.tcp_ipv4 = 1;
nic_dev->rss_type.ipv4 = 1;
nic_dev->rss_type.udp_ipv6 = 1;
nic_dev->rss_type.udp_ipv4 = 1;
}
static void decide_num_qps(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
unsigned int dev_cpus;
dev_cpus = netif_get_num_default_rss_queues();
nic_dev->q_params.num_qps = min(dev_cpus, nic_dev->max_qps);
}
static int alloc_rss_resource(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
nic_dev->rss_hkey = kmalloc_array(L2NIC_RSS_KEY_SIZE,
sizeof(nic_dev->rss_hkey[0]),
GFP_KERNEL);
if (!nic_dev->rss_hkey)
return -ENOMEM;
netdev_rss_key_fill(nic_dev->rss_hkey, L2NIC_RSS_KEY_SIZE);
nic_dev->rss_indir = kcalloc(L2NIC_RSS_INDIR_SIZE, sizeof(u16),
GFP_KERNEL);
if (!nic_dev->rss_indir) {
kfree(nic_dev->rss_hkey);
nic_dev->rss_hkey = NULL;
return -ENOMEM;
}
return 0;
}
static int hinic3_rss_set_indir_tbl(struct hinic3_hwdev *hwdev,
const u16 *indir_table)
{
struct l2nic_cmd_rss_set_indir_tbl *indir_tbl;
struct hinic3_cmd_buf *cmd_buf;
__le64 out_param;
int err;
u32 i;
cmd_buf = hinic3_alloc_cmd_buf(hwdev);
if (!cmd_buf) {
dev_err(hwdev->dev, "Failed to allocate cmd buf\n");
return -ENOMEM;
}
cmd_buf->size = cpu_to_le16(sizeof(struct l2nic_cmd_rss_set_indir_tbl));
indir_tbl = cmd_buf->buf;
memset(indir_tbl, 0, sizeof(*indir_tbl));
for (i = 0; i < L2NIC_RSS_INDIR_SIZE; i++)
indir_tbl->entry[i] = cpu_to_le16(indir_table[i]);
hinic3_cmdq_buf_swab32(indir_tbl, sizeof(*indir_tbl));
err = hinic3_cmdq_direct_resp(hwdev, MGMT_MOD_L2NIC,
L2NIC_UCODE_CMD_SET_RSS_INDIR_TBL,
cmd_buf, &out_param);
if (err || out_param) {
dev_err(hwdev->dev, "Failed to set rss indir table\n");
err = -EFAULT;
}
hinic3_free_cmd_buf(hwdev, cmd_buf);
return err;
}
static int hinic3_set_rss_type(struct hinic3_hwdev *hwdev,
struct hinic3_rss_type rss_type)
{
struct l2nic_cmd_rss_ctx_tbl ctx_tbl = {};
struct mgmt_msg_params msg_params = {};
u32 ctx;
int err;
ctx_tbl.func_id = hinic3_global_func_id(hwdev);
ctx = L2NIC_RSS_TYPE_SET(1, VALID) |
L2NIC_RSS_TYPE_SET(rss_type.ipv4, IPV4) |
L2NIC_RSS_TYPE_SET(rss_type.ipv6, IPV6) |
L2NIC_RSS_TYPE_SET(rss_type.ipv6_ext, IPV6_EXT) |
L2NIC_RSS_TYPE_SET(rss_type.tcp_ipv4, TCP_IPV4) |
L2NIC_RSS_TYPE_SET(rss_type.tcp_ipv6, TCP_IPV6) |
L2NIC_RSS_TYPE_SET(rss_type.tcp_ipv6_ext, TCP_IPV6_EXT) |
L2NIC_RSS_TYPE_SET(rss_type.udp_ipv4, UDP_IPV4) |
L2NIC_RSS_TYPE_SET(rss_type.udp_ipv6, UDP_IPV6);
ctx_tbl.context = ctx;
mgmt_msg_params_init_default(&msg_params, &ctx_tbl, sizeof(ctx_tbl));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_SET_RSS_CTX_TBL, &msg_params);
if (ctx_tbl.msg_head.status == MGMT_STATUS_CMD_UNSUPPORTED) {
return MGMT_STATUS_CMD_UNSUPPORTED;
} else if (err || ctx_tbl.msg_head.status) {
dev_err(hwdev->dev, "mgmt Failed to set rss context offload, err: %d, status: 0x%x\n",
err, ctx_tbl.msg_head.status);
return -EINVAL;
}
return 0;
}
static int hinic3_rss_cfg_hash_type(struct hinic3_hwdev *hwdev, u8 opcode,
enum hinic3_rss_hash_type *type)
{
struct l2nic_cmd_cfg_rss_engine hash_type_cmd = {};
struct mgmt_msg_params msg_params = {};
int err;
hash_type_cmd.func_id = hinic3_global_func_id(hwdev);
hash_type_cmd.opcode = opcode;
if (opcode == MGMT_MSG_CMD_OP_SET)
hash_type_cmd.hash_engine = *type;
mgmt_msg_params_init_default(&msg_params, &hash_type_cmd,
sizeof(hash_type_cmd));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_CFG_RSS_HASH_ENGINE,
&msg_params);
if (err || hash_type_cmd.msg_head.status) {
dev_err(hwdev->dev, "Failed to %s hash engine, err: %d, status: 0x%x\n",
opcode == MGMT_MSG_CMD_OP_SET ? "set" : "get",
err, hash_type_cmd.msg_head.status);
return -EIO;
}
if (opcode == MGMT_MSG_CMD_OP_GET)
*type = hash_type_cmd.hash_engine;
return 0;
}
static int hinic3_rss_set_hash_type(struct hinic3_hwdev *hwdev,
enum hinic3_rss_hash_type type)
{
return hinic3_rss_cfg_hash_type(hwdev, MGMT_MSG_CMD_OP_SET, &type);
}
static int hinic3_config_rss_hw_resource(struct net_device *netdev,
u16 *indir_tbl)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
err = hinic3_rss_set_indir_tbl(nic_dev->hwdev, indir_tbl);
if (err)
return err;
err = hinic3_set_rss_type(nic_dev->hwdev, nic_dev->rss_type);
if (err)
return err;
return hinic3_rss_set_hash_type(nic_dev->hwdev, nic_dev->rss_hash_type);
}
static int hinic3_rss_cfg_hash_key(struct hinic3_hwdev *hwdev, u8 opcode,
u8 *key)
{
struct l2nic_cmd_cfg_rss_hash_key hash_key = {};
struct mgmt_msg_params msg_params = {};
int err;
hash_key.func_id = hinic3_global_func_id(hwdev);
hash_key.opcode = opcode;
if (opcode == MGMT_MSG_CMD_OP_SET)
memcpy(hash_key.key, key, L2NIC_RSS_KEY_SIZE);
mgmt_msg_params_init_default(&msg_params, &hash_key, sizeof(hash_key));
err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
L2NIC_CMD_CFG_RSS_HASH_KEY, &msg_params);
if (err || hash_key.msg_head.status) {
dev_err(hwdev->dev, "Failed to %s hash key, err: %d, status: 0x%x\n",
opcode == MGMT_MSG_CMD_OP_SET ? "set" : "get",
err, hash_key.msg_head.status);
return -EINVAL;
}
if (opcode == MGMT_MSG_CMD_OP_GET)
memcpy(key, hash_key.key, L2NIC_RSS_KEY_SIZE);
return 0;
}
static int hinic3_rss_set_hash_key(struct hinic3_hwdev *hwdev, u8 *key)
{
return hinic3_rss_cfg_hash_key(hwdev, MGMT_MSG_CMD_OP_SET, key);
}
static int hinic3_set_hw_rss_parameters(struct net_device *netdev, u8 rss_en)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
err = hinic3_rss_set_hash_key(nic_dev->hwdev, nic_dev->rss_hkey);
if (err)
return err;
hinic3_fillout_indir_tbl(netdev, nic_dev->rss_indir);
err = hinic3_config_rss_hw_resource(netdev, nic_dev->rss_indir);
if (err)
return err;
err = hinic3_rss_cfg(nic_dev->hwdev, rss_en, nic_dev->q_params.num_qps);
if (err)
return err;
return 0;
}
int hinic3_rss_init(struct net_device *netdev)
{
return hinic3_set_hw_rss_parameters(netdev, 1);
}
void hinic3_rss_uninit(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
hinic3_rss_cfg(nic_dev->hwdev, 0, 0);
}
void hinic3_clear_rss_config(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
kfree(nic_dev->rss_hkey);
nic_dev->rss_hkey = NULL;
kfree(nic_dev->rss_indir);
nic_dev->rss_indir = NULL;
}
void hinic3_try_to_enable_rss(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_hwdev *hwdev = nic_dev->hwdev;
int err;
nic_dev->max_qps = hinic3_func_max_qnum(hwdev);
if (nic_dev->max_qps <= 1 ||
!hinic3_test_support(nic_dev, HINIC3_NIC_F_RSS))
goto err_reset_q_params;
err = alloc_rss_resource(netdev);
if (err) {
nic_dev->max_qps = 1;
goto err_reset_q_params;
}
set_bit(HINIC3_RSS_ENABLE, &nic_dev->flags);
decide_num_qps(netdev);
hinic3_init_rss_parameters(netdev);
err = hinic3_set_hw_rss_parameters(netdev, 0);
if (err) {
dev_err(hwdev->dev, "Failed to set hardware rss parameters\n");
hinic3_clear_rss_config(netdev);
nic_dev->max_qps = 1;
goto err_reset_q_params;
}
return;
err_reset_q_params:
clear_bit(HINIC3_RSS_ENABLE, &nic_dev->flags);
nic_dev->q_params.num_qps = nic_dev->max_qps;
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_RSS_H_
#define _HINIC3_RSS_H_
#include <linux/netdevice.h>
int hinic3_rss_init(struct net_device *netdev);
void hinic3_rss_uninit(struct net_device *netdev);
void hinic3_try_to_enable_rss(struct net_device *netdev);
void hinic3_clear_rss_config(struct net_device *netdev);
#endif
@@ -0,0 +1,592 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/netdevice.h>
#include <net/gro.h>
#include <net/page_pool/helpers.h>
#include "hinic3_hwdev.h"
#include "hinic3_nic_dev.h"
#include "hinic3_nic_io.h"
#include "hinic3_rx.h"
#define HINIC3_RX_HDR_SIZE 256
#define HINIC3_RX_BUFFER_WRITE 16
#define HINIC3_RX_TCP_PKT 0x3
#define HINIC3_RX_UDP_PKT 0x4
#define HINIC3_RX_SCTP_PKT 0x7
#define HINIC3_RX_IPV4_PKT 0
#define HINIC3_RX_IPV6_PKT 1
#define HINIC3_RX_INVALID_IP_TYPE 2
#define HINIC3_RX_PKT_FORMAT_NON_TUNNEL 0
#define HINIC3_RX_PKT_FORMAT_VXLAN 1
#define HINIC3_LRO_PKT_HDR_LEN_IPV4 66
#define HINIC3_LRO_PKT_HDR_LEN_IPV6 86
#define HINIC3_LRO_PKT_HDR_LEN(cqe) \
(RQ_CQE_OFFOLAD_TYPE_GET((cqe)->offload_type, IP_TYPE) == \
HINIC3_RX_IPV6_PKT ? HINIC3_LRO_PKT_HDR_LEN_IPV6 : \
HINIC3_LRO_PKT_HDR_LEN_IPV4)
static void hinic3_rxq_clean_stats(struct hinic3_rxq_stats *rxq_stats)
{
u64_stats_update_begin(&rxq_stats->syncp);
rxq_stats->bytes = 0;
rxq_stats->packets = 0;
rxq_stats->errors = 0;
rxq_stats->csum_errors = 0;
rxq_stats->other_errors = 0;
rxq_stats->dropped = 0;
rxq_stats->rx_buf_empty = 0;
rxq_stats->alloc_skb_err = 0;
rxq_stats->alloc_rx_buf_err = 0;
rxq_stats->restore_drop_sge = 0;
u64_stats_update_end(&rxq_stats->syncp);
}
static void hinic3_rxq_stats_init(struct hinic3_rxq *rxq)
{
struct hinic3_rxq_stats *rxq_stats = &rxq->rxq_stats;
u64_stats_init(&rxq_stats->syncp);
hinic3_rxq_clean_stats(rxq_stats);
}
int hinic3_alloc_rxqs(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct pci_dev *pdev = nic_dev->pdev;
u16 num_rxqs = nic_dev->max_qps;
struct hinic3_rxq *rxq;
u16 q_id;
nic_dev->rxqs = kzalloc_objs(*nic_dev->rxqs, num_rxqs);
if (!nic_dev->rxqs)
return -ENOMEM;
for (q_id = 0; q_id < num_rxqs; q_id++) {
rxq = &nic_dev->rxqs[q_id];
rxq->netdev = netdev;
rxq->dev = &pdev->dev;
rxq->q_id = q_id;
rxq->buf_len = nic_dev->rx_buf_len;
rxq->buf_len_shift = ilog2(nic_dev->rx_buf_len);
rxq->q_depth = nic_dev->q_params.rq_depth;
rxq->q_mask = nic_dev->q_params.rq_depth - 1;
hinic3_rxq_stats_init(rxq);
}
return 0;
}
void hinic3_free_rxqs(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
kfree(nic_dev->rxqs);
}
static int rx_alloc_mapped_page(struct page_pool *page_pool,
struct hinic3_rx_info *rx_info, u16 buf_len)
{
struct page *page;
u32 page_offset;
if (likely(rx_info->page))
return 0;
page = page_pool_dev_alloc_frag(page_pool, &page_offset, buf_len);
if (unlikely(!page))
return -ENOMEM;
rx_info->page = page;
rx_info->page_offset = page_offset;
return 0;
}
/* Associate fixed completion element to every wqe in the rq. Every rq wqe will
* always post completion to the same place.
*/
static void rq_associate_cqes(struct hinic3_rxq *rxq)
{
struct hinic3_queue_pages *qpages;
struct hinic3_rq_wqe *rq_wqe;
dma_addr_t cqe_dma;
u32 i;
qpages = &rxq->rq->wq.qpages;
for (i = 0; i < rxq->q_depth; i++) {
rq_wqe = get_q_element(qpages, i, NULL);
cqe_dma = rxq->cqe_start_paddr +
i * sizeof(struct hinic3_rq_cqe);
rq_wqe->cqe_hi_addr = cpu_to_le32(upper_32_bits(cqe_dma));
rq_wqe->cqe_lo_addr = cpu_to_le32(lower_32_bits(cqe_dma));
}
}
static void rq_wqe_buf_set(struct hinic3_io_queue *rq, uint32_t wqe_idx,
dma_addr_t dma_addr, u16 len)
{
struct hinic3_rq_wqe *rq_wqe;
rq_wqe = get_q_element(&rq->wq.qpages, wqe_idx, NULL);
rq_wqe->buf_hi_addr = cpu_to_le32(upper_32_bits(dma_addr));
rq_wqe->buf_lo_addr = cpu_to_le32(lower_32_bits(dma_addr));
}
static u32 hinic3_rx_fill_buffers(struct hinic3_rxq *rxq)
{
u32 i, free_wqebbs = rxq->delta - 1;
struct hinic3_rx_info *rx_info;
dma_addr_t dma_addr;
int err;
for (i = 0; i < free_wqebbs; i++) {
rx_info = &rxq->rx_info[rxq->next_to_update];
err = rx_alloc_mapped_page(rxq->page_pool, rx_info,
rxq->buf_len);
if (unlikely(err))
break;
dma_addr = page_pool_get_dma_addr(rx_info->page) +
rx_info->page_offset;
rq_wqe_buf_set(rxq->rq, rxq->next_to_update, dma_addr,
rxq->buf_len);
rxq->next_to_update = (rxq->next_to_update + 1) & rxq->q_mask;
}
if (likely(i)) {
hinic3_write_db(rxq->rq, rxq->q_id & 3, DB_CFLAG_DP_RQ,
rxq->next_to_update << HINIC3_NORMAL_RQ_WQE);
rxq->delta -= i;
rxq->next_to_alloc = rxq->next_to_update;
}
return i;
}
static u32 hinic3_alloc_rx_buffers(struct hinic3_dyna_rxq_res *rqres,
u32 rq_depth, u16 buf_len)
{
u32 free_wqebbs = rq_depth - 1;
u32 idx;
int err;
for (idx = 0; idx < free_wqebbs; idx++) {
err = rx_alloc_mapped_page(rqres->page_pool,
&rqres->rx_info[idx], buf_len);
if (err)
break;
}
return idx;
}
static void hinic3_free_rx_buffers(struct hinic3_dyna_rxq_res *rqres,
u32 q_depth)
{
struct hinic3_rx_info *rx_info;
u32 i;
/* Free all the Rx ring sk_buffs */
for (i = 0; i < q_depth; i++) {
rx_info = &rqres->rx_info[i];
if (rx_info->page) {
page_pool_put_full_page(rqres->page_pool,
rx_info->page, false);
rx_info->page = NULL;
}
}
}
static void hinic3_add_rx_frag(struct hinic3_rxq *rxq,
struct hinic3_rx_info *rx_info,
struct sk_buff *skb, u32 size)
{
struct page *page;
u8 *va;
page = rx_info->page;
va = (u8 *)page_address(page) + rx_info->page_offset;
net_prefetch(va);
page_pool_dma_sync_for_cpu(rxq->page_pool, page, rx_info->page_offset,
rxq->buf_len);
if (size <= HINIC3_RX_HDR_SIZE && !skb_is_nonlinear(skb)) {
memcpy(__skb_put(skb, size), va,
ALIGN(size, sizeof(long)));
page_pool_put_full_page(rxq->page_pool, page, false);
return;
}
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page,
rx_info->page_offset, size, rxq->buf_len);
skb_mark_for_recycle(skb);
}
static void packaging_skb(struct hinic3_rxq *rxq, struct sk_buff *skb,
u32 sge_num, u32 pkt_len)
{
struct hinic3_rx_info *rx_info;
u32 temp_pkt_len = pkt_len;
u32 temp_sge_num = sge_num;
u32 sw_ci;
u32 size;
sw_ci = rxq->cons_idx & rxq->q_mask;
while (temp_sge_num) {
rx_info = &rxq->rx_info[sw_ci];
sw_ci = (sw_ci + 1) & rxq->q_mask;
if (unlikely(temp_pkt_len > rxq->buf_len)) {
size = rxq->buf_len;
temp_pkt_len -= rxq->buf_len;
} else {
size = temp_pkt_len;
}
hinic3_add_rx_frag(rxq, rx_info, skb, size);
/* clear contents of buffer_info */
rx_info->page = NULL;
temp_sge_num--;
}
}
static u32 hinic3_get_sge_num(struct hinic3_rxq *rxq, u32 pkt_len)
{
u32 sge_num;
sge_num = pkt_len >> rxq->buf_len_shift;
sge_num += (pkt_len & (rxq->buf_len - 1)) ? 1 : 0;
return sge_num;
}
static struct sk_buff *hinic3_fetch_rx_buffer(struct hinic3_rxq *rxq,
u32 pkt_len)
{
struct sk_buff *skb;
u32 sge_num;
skb = napi_alloc_skb(&rxq->irq_cfg->napi, HINIC3_RX_HDR_SIZE);
if (unlikely(!skb))
return NULL;
sge_num = hinic3_get_sge_num(rxq, pkt_len);
net_prefetchw(skb->data);
packaging_skb(rxq, skb, sge_num, pkt_len);
rxq->cons_idx += sge_num;
rxq->delta += sge_num;
return skb;
}
static void hinic3_pull_tail(struct sk_buff *skb)
{
skb_frag_t *frag = &skb_shinfo(skb)->frags[0];
unsigned int pull_len;
unsigned char *va;
va = skb_frag_address(frag);
/* we need the header to contain the greater of either ETH_HLEN or
* 60 bytes if the skb->len is less than 60 for skb_pad.
*/
pull_len = eth_get_headlen(skb->dev, va, HINIC3_RX_HDR_SIZE);
/* align pull length to size of long to optimize memcpy performance */
skb_copy_to_linear_data(skb, va, ALIGN(pull_len, sizeof(long)));
/* update all of the pointers */
skb_frag_size_sub(frag, pull_len);
skb_frag_off_add(frag, pull_len);
skb->data_len -= pull_len;
skb->tail += pull_len;
}
static void hinic3_rx_csum(struct hinic3_rxq *rxq, u32 offload_type,
u32 status, struct sk_buff *skb)
{
u32 pkt_fmt = RQ_CQE_OFFOLAD_TYPE_GET(offload_type, TUNNEL_PKT_FORMAT);
u32 pkt_type = RQ_CQE_OFFOLAD_TYPE_GET(offload_type, PKT_TYPE);
u32 ip_type = RQ_CQE_OFFOLAD_TYPE_GET(offload_type, IP_TYPE);
u32 csum_err = RQ_CQE_STATUS_GET(status, CSUM_ERR);
struct net_device *netdev = rxq->netdev;
bool l2_tunnel;
if (!(netdev->features & NETIF_F_RXCSUM))
return;
if (unlikely(csum_err)) {
/* pkt type is recognized by HW, and csum is wrong */
skb->ip_summed = CHECKSUM_NONE;
return;
}
if (ip_type == HINIC3_RX_INVALID_IP_TYPE ||
!(pkt_fmt == HINIC3_RX_PKT_FORMAT_NON_TUNNEL ||
pkt_fmt == HINIC3_RX_PKT_FORMAT_VXLAN)) {
skb->ip_summed = CHECKSUM_NONE;
return;
}
switch (pkt_type) {
case HINIC3_RX_TCP_PKT:
case HINIC3_RX_UDP_PKT:
case HINIC3_RX_SCTP_PKT:
skb->ip_summed = CHECKSUM_UNNECESSARY;
l2_tunnel = HINIC3_GET_RX_TUNNEL_PKT_FORMAT(offload_type) ==
HINIC3_RX_PKT_FORMAT_VXLAN ? 1 : 0;
if (l2_tunnel) {
/* If we checked the outer header let the stack know */
skb->csum_level = 1;
}
break;
default:
skb->ip_summed = CHECKSUM_NONE;
break;
}
}
static void hinic3_lro_set_gso_params(struct sk_buff *skb, u16 num_lro)
{
struct ethhdr *eth = (struct ethhdr *)(skb->data);
__be16 proto;
proto = __vlan_get_protocol(skb, eth->h_proto, NULL);
skb_shinfo(skb)->gso_size = DIV_ROUND_UP(skb->len - skb_headlen(skb),
num_lro);
skb_shinfo(skb)->gso_type = proto == htons(ETH_P_IP) ?
SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
skb_shinfo(skb)->gso_segs = num_lro;
}
static int recv_one_pkt(struct hinic3_rxq *rxq, struct hinic3_rq_cqe *rx_cqe,
u32 pkt_len, u32 vlan_len, u32 status)
{
struct net_device *netdev = rxq->netdev;
struct sk_buff *skb;
u32 offload_type;
u16 num_lro;
skb = hinic3_fetch_rx_buffer(rxq, pkt_len);
if (unlikely(!skb))
return -ENOMEM;
/* place header in linear portion of buffer */
if (skb_is_nonlinear(skb))
hinic3_pull_tail(skb);
offload_type = le32_to_cpu(rx_cqe->offload_type);
hinic3_rx_csum(rxq, offload_type, status, skb);
if ((netdev->features & NETIF_F_HW_VLAN_CTAG_RX) &&
RQ_CQE_OFFOLAD_TYPE_GET(offload_type, VLAN_EN)) {
u16 vid = RQ_CQE_SGE_GET(vlan_len, VLAN);
/* if the packet is a vlan pkt, the vid may be 0 */
__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid);
}
num_lro = RQ_CQE_STATUS_GET(status, NUM_LRO);
if (num_lro)
hinic3_lro_set_gso_params(skb, num_lro);
skb_record_rx_queue(skb, rxq->q_id);
skb->protocol = eth_type_trans(skb, netdev);
if (skb_has_frag_list(skb)) {
napi_gro_flush(&rxq->irq_cfg->napi, false);
netif_receive_skb(skb);
} else {
napi_gro_receive(&rxq->irq_cfg->napi, skb);
}
return 0;
}
int hinic3_alloc_rxqs_res(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res)
{
u64 cqe_mem_size = sizeof(struct hinic3_rq_cqe) * rq_depth;
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct page_pool_params pp_params = {};
struct hinic3_dyna_rxq_res *rqres;
u32 pkt_idx;
int idx;
for (idx = 0; idx < num_rq; idx++) {
rqres = &rxqs_res[idx];
rqres->rx_info = kzalloc_objs(*rqres->rx_info, rq_depth);
if (!rqres->rx_info)
goto err_free_rqres;
rqres->cqe_start_vaddr =
dma_alloc_coherent(&nic_dev->pdev->dev, cqe_mem_size,
&rqres->cqe_start_paddr, GFP_KERNEL);
if (!rqres->cqe_start_vaddr) {
netdev_err(netdev, "Failed to alloc rxq%d rx cqe\n",
idx);
goto err_free_rx_info;
}
pp_params.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
pp_params.pool_size = rq_depth * nic_dev->rx_buf_len /
PAGE_SIZE;
pp_params.nid = dev_to_node(&nic_dev->pdev->dev);
pp_params.dev = &nic_dev->pdev->dev;
pp_params.dma_dir = DMA_FROM_DEVICE;
pp_params.max_len = PAGE_SIZE;
rqres->page_pool = page_pool_create(&pp_params);
if (IS_ERR(rqres->page_pool)) {
netdev_err(netdev, "Failed to create rxq%d page pool\n",
idx);
goto err_free_cqe;
}
pkt_idx = hinic3_alloc_rx_buffers(rqres, rq_depth,
nic_dev->rx_buf_len);
if (!pkt_idx) {
netdev_err(netdev, "Failed to alloc rxq%d rx buffers\n",
idx);
goto err_destroy_page_pool;
}
rqres->next_to_alloc = pkt_idx;
}
return 0;
err_destroy_page_pool:
page_pool_destroy(rqres->page_pool);
err_free_cqe:
dma_free_coherent(&nic_dev->pdev->dev, cqe_mem_size,
rqres->cqe_start_vaddr,
rqres->cqe_start_paddr);
err_free_rx_info:
kfree(rqres->rx_info);
err_free_rqres:
hinic3_free_rxqs_res(netdev, idx, rq_depth, rxqs_res);
return -ENOMEM;
}
void hinic3_free_rxqs_res(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res)
{
u64 cqe_mem_size = sizeof(struct hinic3_rq_cqe) * rq_depth;
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_dyna_rxq_res *rqres;
int idx;
for (idx = 0; idx < num_rq; idx++) {
rqres = &rxqs_res[idx];
hinic3_free_rx_buffers(rqres, rq_depth);
page_pool_destroy(rqres->page_pool);
dma_free_coherent(&nic_dev->pdev->dev, cqe_mem_size,
rqres->cqe_start_vaddr,
rqres->cqe_start_paddr);
kfree(rqres->rx_info);
}
}
int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_dyna_rxq_res *rqres;
struct msix_entry *msix_entry;
struct hinic3_rxq *rxq;
u16 q_id;
u32 pkts;
for (q_id = 0; q_id < num_rq; q_id++) {
rxq = &nic_dev->rxqs[q_id];
rqres = &rxqs_res[q_id];
msix_entry = &nic_dev->qps_msix_entries[q_id];
rxq->irq_id = msix_entry->vector;
rxq->msix_entry_idx = msix_entry->entry;
rxq->next_to_update = 0;
rxq->next_to_alloc = rqres->next_to_alloc;
rxq->q_depth = rq_depth;
rxq->delta = rxq->q_depth;
rxq->q_mask = rxq->q_depth - 1;
rxq->cons_idx = 0;
rxq->cqe_arr = rqres->cqe_start_vaddr;
rxq->cqe_start_paddr = rqres->cqe_start_paddr;
rxq->rx_info = rqres->rx_info;
rxq->page_pool = rqres->page_pool;
rxq->rq = &nic_dev->nic_io->rq[rxq->q_id];
rq_associate_cqes(rxq);
pkts = hinic3_rx_fill_buffers(rxq);
if (!pkts) {
netdev_err(netdev, "Failed to fill Rx buffer\n");
return -ENOMEM;
}
}
return 0;
}
int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(rxq->netdev);
u32 sw_ci, status, pkt_len, vlan_len;
struct hinic3_rq_cqe *rx_cqe;
u32 num_wqe = 0;
int nr_pkts = 0;
u16 num_lro;
while (likely(nr_pkts < budget)) {
sw_ci = rxq->cons_idx & rxq->q_mask;
rx_cqe = rxq->cqe_arr + sw_ci;
status = le32_to_cpu(rx_cqe->status);
if (!RQ_CQE_STATUS_GET(status, RXDONE))
break;
/* make sure we read rx_done before packet length */
rmb();
vlan_len = le32_to_cpu(rx_cqe->vlan_len);
pkt_len = RQ_CQE_SGE_GET(vlan_len, LEN);
if (recv_one_pkt(rxq, rx_cqe, pkt_len, vlan_len, status))
break;
nr_pkts++;
num_lro = RQ_CQE_STATUS_GET(status, NUM_LRO);
if (num_lro)
num_wqe += hinic3_get_sge_num(rxq, pkt_len);
rx_cqe->status = 0;
if (num_wqe >= nic_dev->lro_replenish_thld)
break;
}
if (rxq->delta >= HINIC3_RX_BUFFER_WRITE)
hinic3_rx_fill_buffers(rxq);
return nr_pkts;
}
@@ -0,0 +1,128 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_RX_H_
#define _HINIC3_RX_H_
#include <linux/bitfield.h>
#include <linux/dim.h>
#include <linux/netdevice.h>
#define RQ_CQE_OFFOLAD_TYPE_PKT_TYPE_MASK GENMASK(4, 0)
#define RQ_CQE_OFFOLAD_TYPE_IP_TYPE_MASK GENMASK(6, 5)
#define RQ_CQE_OFFOLAD_TYPE_TUNNEL_PKT_FORMAT_MASK GENMASK(11, 8)
#define RQ_CQE_OFFOLAD_TYPE_VLAN_EN_MASK BIT(21)
#define RQ_CQE_OFFOLAD_TYPE_GET(val, member) \
FIELD_GET(RQ_CQE_OFFOLAD_TYPE_##member##_MASK, val)
#define HINIC3_GET_RX_TUNNEL_PKT_FORMAT(offload_type) \
RQ_CQE_OFFOLAD_TYPE_GET(offload_type, TUNNEL_PKT_FORMAT)
#define RQ_CQE_SGE_VLAN_MASK GENMASK(15, 0)
#define RQ_CQE_SGE_LEN_MASK GENMASK(31, 16)
#define RQ_CQE_SGE_GET(val, member) \
FIELD_GET(RQ_CQE_SGE_##member##_MASK, val)
#define RQ_CQE_STATUS_CSUM_ERR_MASK GENMASK(15, 0)
#define RQ_CQE_STATUS_NUM_LRO_MASK GENMASK(23, 16)
#define RQ_CQE_STATUS_RXDONE_MASK BIT(31)
#define RQ_CQE_STATUS_GET(val, member) \
FIELD_GET(RQ_CQE_STATUS_##member##_MASK, val)
struct hinic3_rxq_stats {
u64 packets;
u64 bytes;
u64 errors;
u64 csum_errors;
u64 other_errors;
u64 dropped;
u64 rx_buf_empty;
u64 alloc_skb_err;
u64 alloc_rx_buf_err;
u64 restore_drop_sge;
struct u64_stats_sync syncp;
};
/* RX Completion information that is provided by HW for a specific RX WQE */
struct hinic3_rq_cqe {
__le32 status;
__le32 vlan_len;
__le32 offload_type;
__le32 rsvd3;
__le32 rsvd4;
__le32 rsvd5;
__le32 rsvd6;
__le32 pkt_info;
};
struct hinic3_rq_wqe {
__le32 buf_hi_addr;
__le32 buf_lo_addr;
__le32 cqe_hi_addr;
__le32 cqe_lo_addr;
};
struct hinic3_rx_info {
struct page *page;
u32 page_offset;
};
struct hinic3_rxq {
struct net_device *netdev;
u16 q_id;
u32 q_depth;
u32 q_mask;
u16 buf_len;
u32 buf_len_shift;
struct hinic3_rxq_stats rxq_stats;
u32 cons_idx;
u32 delta;
u32 irq_id;
u16 msix_entry_idx;
/* cqe_arr and rx_info are arrays of rq_depth elements. Each element is
* statically associated (by index) to a specific rq_wqe.
*/
struct hinic3_rq_cqe *cqe_arr;
struct hinic3_rx_info *rx_info;
struct page_pool *page_pool;
struct hinic3_io_queue *rq;
struct hinic3_irq_cfg *irq_cfg;
u16 next_to_alloc;
u16 next_to_update;
struct device *dev; /* device for DMA mapping */
dma_addr_t cqe_start_paddr;
struct dim dim;
u8 last_coalesc_timer_cfg;
u8 last_pending_limit;
} ____cacheline_aligned;
struct hinic3_dyna_rxq_res {
u16 next_to_alloc;
struct hinic3_rx_info *rx_info;
dma_addr_t cqe_start_paddr;
void *cqe_start_vaddr;
struct page_pool *page_pool;
};
int hinic3_alloc_rxqs(struct net_device *netdev);
void hinic3_free_rxqs(struct net_device *netdev);
int hinic3_alloc_rxqs_res(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res);
void hinic3_free_rxqs_res(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res);
int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res);
int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget);
#endif
@@ -0,0 +1,797 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/if_vlan.h>
#include <linux/iopoll.h>
#include <net/ip6_checksum.h>
#include <net/ipv6.h>
#include <net/netdev_queues.h>
#include "hinic3_hwdev.h"
#include "hinic3_nic_cfg.h"
#include "hinic3_nic_dev.h"
#include "hinic3_nic_io.h"
#include "hinic3_tx.h"
#include "hinic3_wq.h"
#define MIN_SKB_LEN 32
static void hinic3_txq_clean_stats(struct hinic3_txq_stats *txq_stats)
{
u64_stats_update_begin(&txq_stats->syncp);
txq_stats->bytes = 0;
txq_stats->packets = 0;
txq_stats->busy = 0;
txq_stats->dropped = 0;
txq_stats->skb_pad_err = 0;
txq_stats->frag_len_overflow = 0;
txq_stats->offload_cow_skb_err = 0;
txq_stats->map_frag_err = 0;
txq_stats->unknown_tunnel_pkt = 0;
txq_stats->frag_size_err = 0;
u64_stats_update_end(&txq_stats->syncp);
}
static void hinic3_txq_stats_init(struct hinic3_txq *txq)
{
struct hinic3_txq_stats *txq_stats = &txq->txq_stats;
u64_stats_init(&txq_stats->syncp);
hinic3_txq_clean_stats(txq_stats);
}
int hinic3_alloc_txqs(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
u16 q_id, num_txqs = nic_dev->max_qps;
struct pci_dev *pdev = nic_dev->pdev;
struct hinic3_txq *txq;
nic_dev->txqs = kzalloc_objs(*nic_dev->txqs, num_txqs);
if (!nic_dev->txqs)
return -ENOMEM;
for (q_id = 0; q_id < num_txqs; q_id++) {
txq = &nic_dev->txqs[q_id];
txq->netdev = netdev;
txq->q_id = q_id;
txq->q_depth = nic_dev->q_params.sq_depth;
txq->q_mask = nic_dev->q_params.sq_depth - 1;
txq->dev = &pdev->dev;
hinic3_txq_stats_init(txq);
}
return 0;
}
void hinic3_free_txqs(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
kfree(nic_dev->txqs);
}
static void hinic3_set_buf_desc(struct hinic3_sq_bufdesc *buf_descs,
dma_addr_t addr, u32 len)
{
buf_descs->hi_addr = cpu_to_le32(upper_32_bits(addr));
buf_descs->lo_addr = cpu_to_le32(lower_32_bits(addr));
buf_descs->len = cpu_to_le32(len);
}
static int hinic3_tx_map_skb(struct net_device *netdev, struct sk_buff *skb,
struct hinic3_txq *txq,
struct hinic3_tx_info *tx_info,
struct hinic3_sq_wqe_combo *wqe_combo)
{
struct hinic3_sq_wqe_desc *wqe_desc = wqe_combo->ctrl_bd0;
struct hinic3_sq_bufdesc *buf_desc = wqe_combo->bds_head;
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_dma_info *dma_info = tx_info->dma_info;
struct pci_dev *pdev = nic_dev->pdev;
skb_frag_t *frag;
u32 i, idx;
int err;
dma_info[0].dma = dma_map_single(&pdev->dev, skb->data,
skb_headlen(skb), DMA_TO_DEVICE);
if (dma_mapping_error(&pdev->dev, dma_info[0].dma))
return -EFAULT;
dma_info[0].len = skb_headlen(skb);
wqe_desc->hi_addr = cpu_to_le32(upper_32_bits(dma_info[0].dma));
wqe_desc->lo_addr = cpu_to_le32(lower_32_bits(dma_info[0].dma));
wqe_desc->ctrl_len = cpu_to_le32(dma_info[0].len);
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
frag = &(skb_shinfo(skb)->frags[i]);
if (unlikely(i == wqe_combo->first_bds_num))
buf_desc = wqe_combo->bds_sec2;
idx = i + 1;
dma_info[idx].dma = skb_frag_dma_map(&pdev->dev, frag, 0,
skb_frag_size(frag),
DMA_TO_DEVICE);
if (dma_mapping_error(&pdev->dev, dma_info[idx].dma)) {
err = -EFAULT;
goto err_unmap_page;
}
dma_info[idx].len = skb_frag_size(frag);
hinic3_set_buf_desc(buf_desc, dma_info[idx].dma,
dma_info[idx].len);
buf_desc++;
}
return 0;
err_unmap_page:
while (idx > 1) {
idx--;
dma_unmap_page(&pdev->dev, dma_info[idx].dma,
dma_info[idx].len, DMA_TO_DEVICE);
}
dma_unmap_single(&pdev->dev, dma_info[0].dma, dma_info[0].len,
DMA_TO_DEVICE);
return err;
}
static void hinic3_tx_unmap_skb(struct net_device *netdev,
struct sk_buff *skb,
struct hinic3_dma_info *dma_info)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct pci_dev *pdev = nic_dev->pdev;
int i;
for (i = 0; i < skb_shinfo(skb)->nr_frags;) {
i++;
dma_unmap_page(&pdev->dev,
dma_info[i].dma,
dma_info[i].len, DMA_TO_DEVICE);
}
dma_unmap_single(&pdev->dev, dma_info[0].dma,
dma_info[0].len, DMA_TO_DEVICE);
}
static void free_all_tx_skbs(struct net_device *netdev, u32 sq_depth,
struct hinic3_tx_info *tx_info_arr)
{
struct hinic3_tx_info *tx_info;
u32 idx;
for (idx = 0; idx < sq_depth; idx++) {
tx_info = &tx_info_arr[idx];
if (tx_info->skb) {
hinic3_tx_unmap_skb(netdev, tx_info->skb,
tx_info->dma_info);
dev_kfree_skb_any(tx_info->skb);
tx_info->skb = NULL;
}
}
}
union hinic3_ip {
struct iphdr *v4;
struct ipv6hdr *v6;
unsigned char *hdr;
};
union hinic3_l4 {
struct tcphdr *tcp;
struct udphdr *udp;
unsigned char *hdr;
};
enum hinic3_l3_type {
HINIC3_L3_UNKNOWN = 0,
HINIC3_L3_IP6_PKT = 1,
HINIC3_L3_IP4_PKT_NO_CSUM = 2,
HINIC3_L3_IP4_PKT_CSUM = 3,
};
enum hinic3_l4_offload_type {
HINIC3_L4_OFFLOAD_DISABLE = 0,
HINIC3_L4_OFFLOAD_TCP = 1,
HINIC3_L4_OFFLOAD_STCP = 2,
HINIC3_L4_OFFLOAD_UDP = 3,
};
/* initialize l4 offset and offload */
static void get_inner_l4_info(struct sk_buff *skb, union hinic3_l4 *l4,
u8 l4_proto, u32 *offset,
enum hinic3_l4_offload_type *l4_offload)
{
switch (l4_proto) {
case IPPROTO_TCP:
*l4_offload = HINIC3_L4_OFFLOAD_TCP;
/* To be same with TSO, payload offset begins from payload */
*offset = (l4->tcp->doff << TCP_HDR_DATA_OFF_UNIT_SHIFT) +
TRANSPORT_OFFSET(l4->hdr, skb);
break;
case IPPROTO_UDP:
*l4_offload = HINIC3_L4_OFFLOAD_UDP;
*offset = TRANSPORT_OFFSET(l4->hdr, skb);
break;
default:
*l4_offload = HINIC3_L4_OFFLOAD_DISABLE;
*offset = 0;
}
}
static int hinic3_tx_csum(struct hinic3_txq *txq, struct hinic3_sq_task *task,
struct sk_buff *skb)
{
if (skb->ip_summed != CHECKSUM_PARTIAL)
return 0;
if (skb->encapsulation) {
union hinic3_ip ip;
u8 l4_proto;
task->pkt_info0 |= cpu_to_le32(SQ_TASK_INFO0_SET(1,
TUNNEL_FLAG));
ip.hdr = skb_network_header(skb);
if (ip.v4->version == 4) {
l4_proto = ip.v4->protocol;
} else if (ip.v4->version == 6) {
union hinic3_l4 l4;
unsigned char *exthdr;
__be16 frag_off;
exthdr = ip.hdr + sizeof(*ip.v6);
l4_proto = ip.v6->nexthdr;
l4.hdr = skb_transport_header(skb);
if (l4.hdr != exthdr)
ipv6_skip_exthdr(skb, exthdr - skb->data,
&l4_proto, &frag_off);
} else {
l4_proto = IPPROTO_RAW;
}
if (l4_proto != IPPROTO_UDP ||
((struct udphdr *)skb_transport_header(skb))->dest !=
VXLAN_OFFLOAD_PORT_LE) {
/* Unsupported tunnel packet, disable csum offload */
skb_checksum_help(skb);
return 0;
}
}
task->pkt_info0 |= cpu_to_le32(SQ_TASK_INFO0_SET(1, INNER_L4_EN));
return 1;
}
static void get_inner_l3_l4_type(struct sk_buff *skb, union hinic3_ip *ip,
union hinic3_l4 *l4,
enum hinic3_l3_type *l3_type, u8 *l4_proto)
{
unsigned char *exthdr;
__be16 frag_off;
if (ip->v4->version == 4) {
*l3_type = HINIC3_L3_IP4_PKT_CSUM;
*l4_proto = ip->v4->protocol;
} else if (ip->v4->version == 6) {
*l3_type = HINIC3_L3_IP6_PKT;
exthdr = ip->hdr + sizeof(*ip->v6);
*l4_proto = ip->v6->nexthdr;
if (exthdr != l4->hdr) {
ipv6_skip_exthdr(skb, exthdr - skb->data,
l4_proto, &frag_off);
}
} else {
*l3_type = HINIC3_L3_UNKNOWN;
*l4_proto = 0;
}
}
static void hinic3_set_tso_info(struct hinic3_sq_task *task, __le32 *queue_info,
enum hinic3_l4_offload_type l4_offload,
u32 offset, u32 mss)
{
if (l4_offload == HINIC3_L4_OFFLOAD_TCP) {
*queue_info |= cpu_to_le32(SQ_CTRL_QUEUE_INFO_SET(1, TSO));
task->pkt_info0 |= cpu_to_le32(SQ_TASK_INFO0_SET(1,
INNER_L4_EN));
} else if (l4_offload == HINIC3_L4_OFFLOAD_UDP) {
*queue_info |= cpu_to_le32(SQ_CTRL_QUEUE_INFO_SET(1, UFO));
task->pkt_info0 |= cpu_to_le32(SQ_TASK_INFO0_SET(1,
INNER_L4_EN));
}
/* enable L3 calculation */
task->pkt_info0 |= cpu_to_le32(SQ_TASK_INFO0_SET(1, INNER_L3_EN));
*queue_info |= cpu_to_le32(SQ_CTRL_QUEUE_INFO_SET(offset >> 1, PLDOFF));
/* set MSS value */
*queue_info &= cpu_to_le32(~SQ_CTRL_QUEUE_INFO_MSS_MASK);
*queue_info |= cpu_to_le32(SQ_CTRL_QUEUE_INFO_SET(mss, MSS));
}
static __sum16 csum_magic(union hinic3_ip *ip, unsigned short proto)
{
return (ip->v4->version == 4) ?
csum_tcpudp_magic(ip->v4->saddr, ip->v4->daddr, 0, proto, 0) :
csum_ipv6_magic(&ip->v6->saddr, &ip->v6->daddr, 0, proto, 0);
}
static int hinic3_tso(struct hinic3_sq_task *task, __le32 *queue_info,
struct sk_buff *skb)
{
enum hinic3_l4_offload_type l4_offload;
enum hinic3_l3_type l3_type;
union hinic3_ip ip;
union hinic3_l4 l4;
u8 l4_proto;
u32 offset;
int err;
if (!skb_is_gso(skb))
return 0;
err = skb_cow_head(skb, 0);
if (err < 0)
return err;
if (skb->encapsulation) {
u32 gso_type = skb_shinfo(skb)->gso_type;
/* L3 checksum is always enabled */
task->pkt_info0 |= cpu_to_le32(SQ_TASK_INFO0_SET(1, OUT_L3_EN));
task->pkt_info0 |= cpu_to_le32(SQ_TASK_INFO0_SET(1,
TUNNEL_FLAG));
l4.hdr = skb_transport_header(skb);
ip.hdr = skb_network_header(skb);
if (gso_type & SKB_GSO_UDP_TUNNEL_CSUM) {
l4.udp->check = ~csum_magic(&ip, IPPROTO_UDP);
task->pkt_info0 |=
cpu_to_le32(SQ_TASK_INFO0_SET(1, OUT_L4_EN));
}
ip.hdr = skb_inner_network_header(skb);
l4.hdr = skb_inner_transport_header(skb);
} else {
ip.hdr = skb_network_header(skb);
l4.hdr = skb_transport_header(skb);
}
get_inner_l3_l4_type(skb, &ip, &l4, &l3_type, &l4_proto);
if (l4_proto == IPPROTO_TCP)
l4.tcp->check = ~csum_magic(&ip, IPPROTO_TCP);
get_inner_l4_info(skb, &l4, l4_proto, &offset, &l4_offload);
hinic3_set_tso_info(task, queue_info, l4_offload, offset,
skb_shinfo(skb)->gso_size);
return 1;
}
static void hinic3_set_vlan_tx_offload(struct hinic3_sq_task *task,
u16 vlan_tag, u8 vlan_tpid)
{
/* vlan_tpid: 0=select TPID0 in IPSU, 1=select TPID1 in IPSU
* 2=select TPID2 in IPSU, 3=select TPID3 in IPSU,
* 4=select TPID4 in IPSU
*/
task->vlan_offload =
cpu_to_le32(SQ_TASK_INFO3_SET(vlan_tag, VLAN_TAG) |
SQ_TASK_INFO3_SET(vlan_tpid, VLAN_TPID) |
SQ_TASK_INFO3_SET(1, VLAN_TAG_VALID));
}
static u32 hinic3_tx_offload(struct sk_buff *skb, struct hinic3_sq_task *task,
__le32 *queue_info, struct hinic3_txq *txq)
{
u32 offload = 0;
int tso_cs_en;
task->pkt_info0 = 0;
task->ip_identify = 0;
task->rsvd = 0;
task->vlan_offload = 0;
tso_cs_en = hinic3_tso(task, queue_info, skb);
if (tso_cs_en < 0) {
offload = HINIC3_TX_OFFLOAD_INVALID;
return offload;
} else if (tso_cs_en) {
offload |= HINIC3_TX_OFFLOAD_TSO;
} else {
tso_cs_en = hinic3_tx_csum(txq, task, skb);
if (tso_cs_en)
offload |= HINIC3_TX_OFFLOAD_CSUM;
}
#define VLAN_INSERT_MODE_MAX 5
if (unlikely(skb_vlan_tag_present(skb))) {
/* select vlan insert mode by qid, default 802.1Q Tag type */
hinic3_set_vlan_tx_offload(task, skb_vlan_tag_get(skb),
txq->q_id % VLAN_INSERT_MODE_MAX);
offload |= HINIC3_TX_OFFLOAD_VLAN;
}
if (unlikely(SQ_CTRL_QUEUE_INFO_GET(*queue_info, PLDOFF) >
SQ_CTRL_MAX_PLDOFF)) {
offload = HINIC3_TX_OFFLOAD_INVALID;
return offload;
}
return offload;
}
static u16 hinic3_get_and_update_sq_owner(struct hinic3_io_queue *sq,
u16 curr_pi, u16 wqebb_cnt)
{
u16 owner = sq->owner;
if (unlikely(curr_pi + wqebb_cnt >= sq->wq.q_depth))
sq->owner = !sq->owner;
return owner;
}
static u16 hinic3_set_wqe_combo(struct hinic3_txq *txq,
struct hinic3_sq_wqe_combo *wqe_combo,
u32 offload, u16 num_sge, u16 *curr_pi)
{
struct hinic3_sq_bufdesc *first_part_wqebbs, *second_part_wqebbs;
u16 first_part_wqebbs_num, tmp_pi;
wqe_combo->ctrl_bd0 = hinic3_wq_get_one_wqebb(&txq->sq->wq, curr_pi);
if (!offload && num_sge == 1) {
wqe_combo->wqe_type = SQ_WQE_COMPACT_TYPE;
return hinic3_get_and_update_sq_owner(txq->sq, *curr_pi, 1);
}
wqe_combo->wqe_type = SQ_WQE_EXTENDED_TYPE;
if (offload) {
wqe_combo->task = hinic3_wq_get_one_wqebb(&txq->sq->wq,
&tmp_pi);
wqe_combo->task_type = SQ_WQE_TASKSECT_16BYTES;
} else {
wqe_combo->task_type = SQ_WQE_TASKSECT_46BITS;
}
if (num_sge > 1) {
/* first wqebb contain bd0, and bd size is equal to sq wqebb
* size, so we use (num_sge - 1) as wanted weqbb_cnt
*/
hinic3_wq_get_multi_wqebbs(&txq->sq->wq, num_sge - 1, &tmp_pi,
&first_part_wqebbs,
&second_part_wqebbs,
&first_part_wqebbs_num);
wqe_combo->bds_head = first_part_wqebbs;
wqe_combo->bds_sec2 = second_part_wqebbs;
wqe_combo->first_bds_num = first_part_wqebbs_num;
}
return hinic3_get_and_update_sq_owner(txq->sq, *curr_pi,
num_sge + !!offload);
}
static void hinic3_prepare_sq_ctrl(struct hinic3_sq_wqe_combo *wqe_combo,
__le32 queue_info, int nr_descs, u16 owner)
{
struct hinic3_sq_wqe_desc *wqe_desc = wqe_combo->ctrl_bd0;
if (wqe_combo->wqe_type == SQ_WQE_COMPACT_TYPE) {
wqe_desc->ctrl_len |=
cpu_to_le32(SQ_CTRL_SET(SQ_NORMAL_WQE, DATA_FORMAT) |
SQ_CTRL_SET(wqe_combo->wqe_type, EXTENDED) |
SQ_CTRL_SET(owner, OWNER));
/* compact wqe queue_info will transfer to chip */
wqe_desc->queue_info = 0;
return;
}
wqe_desc->ctrl_len |=
cpu_to_le32(SQ_CTRL_SET(nr_descs, BUFDESC_NUM) |
SQ_CTRL_SET(wqe_combo->task_type, TASKSECT_LEN) |
SQ_CTRL_SET(SQ_NORMAL_WQE, DATA_FORMAT) |
SQ_CTRL_SET(wqe_combo->wqe_type, EXTENDED) |
SQ_CTRL_SET(owner, OWNER));
wqe_desc->queue_info = queue_info;
wqe_desc->queue_info |= cpu_to_le32(SQ_CTRL_QUEUE_INFO_SET(1, UC));
if (!SQ_CTRL_QUEUE_INFO_GET(wqe_desc->queue_info, MSS)) {
wqe_desc->queue_info |=
cpu_to_le32(SQ_CTRL_QUEUE_INFO_SET(HINIC3_TX_MSS_DEFAULT, MSS));
} else if (SQ_CTRL_QUEUE_INFO_GET(wqe_desc->queue_info, MSS) <
HINIC3_TX_MSS_MIN) {
/* mss should not be less than 80 */
wqe_desc->queue_info &=
cpu_to_le32(~SQ_CTRL_QUEUE_INFO_MSS_MASK);
wqe_desc->queue_info |=
cpu_to_le32(SQ_CTRL_QUEUE_INFO_SET(HINIC3_TX_MSS_MIN, MSS));
}
}
static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
struct net_device *netdev,
struct hinic3_txq *txq)
{
struct hinic3_sq_wqe_combo wqe_combo = {};
struct hinic3_tx_info *tx_info;
struct hinic3_sq_task task;
u16 wqebb_cnt, num_sge;
__le32 queue_info = 0;
u16 saved_wq_prod_idx;
u16 owner, pi = 0;
u8 saved_sq_owner;
u32 offload;
int err;
if (unlikely(skb->len < MIN_SKB_LEN)) {
if (skb_pad(skb, MIN_SKB_LEN - skb->len))
goto err_out;
skb->len = MIN_SKB_LEN;
}
num_sge = skb_shinfo(skb)->nr_frags + 1;
/* assume normal wqe format + 1 wqebb for task info */
wqebb_cnt = num_sge + 1;
if (unlikely(hinic3_wq_free_wqebbs(&txq->sq->wq) < wqebb_cnt)) {
if (likely(wqebb_cnt > txq->tx_stop_thrs))
txq->tx_stop_thrs = min(wqebb_cnt, txq->tx_start_thrs);
netif_subqueue_try_stop(netdev, txq->sq->q_id,
hinic3_wq_free_wqebbs(&txq->sq->wq),
txq->tx_start_thrs);
return NETDEV_TX_BUSY;
}
offload = hinic3_tx_offload(skb, &task, &queue_info, txq);
if (unlikely(offload == HINIC3_TX_OFFLOAD_INVALID)) {
goto err_drop_pkt;
} else if (!offload) {
wqebb_cnt -= 1;
if (unlikely(num_sge == 1 &&
skb->len > HINIC3_COMPACT_WQEE_SKB_MAX_LEN))
goto err_drop_pkt;
}
saved_wq_prod_idx = txq->sq->wq.prod_idx;
saved_sq_owner = txq->sq->owner;
owner = hinic3_set_wqe_combo(txq, &wqe_combo, offload, num_sge, &pi);
if (offload)
*wqe_combo.task = task;
tx_info = &txq->tx_info[pi];
tx_info->skb = skb;
tx_info->wqebb_cnt = wqebb_cnt;
err = hinic3_tx_map_skb(netdev, skb, txq, tx_info, &wqe_combo);
if (err) {
/* Rollback work queue to reclaim the wqebb we did not use */
txq->sq->wq.prod_idx = saved_wq_prod_idx;
txq->sq->owner = saved_sq_owner;
goto err_drop_pkt;
}
netif_subqueue_sent(netdev, txq->sq->q_id, skb->len);
netif_subqueue_maybe_stop(netdev, txq->sq->q_id,
hinic3_wq_free_wqebbs(&txq->sq->wq),
txq->tx_stop_thrs,
txq->tx_start_thrs);
hinic3_prepare_sq_ctrl(&wqe_combo, queue_info, num_sge, owner);
hinic3_write_db(txq->sq, 0, DB_CFLAG_DP_SQ,
hinic3_get_sq_local_pi(txq->sq));
return NETDEV_TX_OK;
err_drop_pkt:
dev_kfree_skb_any(skb);
err_out:
return NETDEV_TX_OK;
}
netdev_tx_t hinic3_xmit_frame(struct sk_buff *skb, struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
u16 q_id = skb_get_queue_mapping(skb);
if (unlikely(!netif_carrier_ok(netdev)))
goto err_drop_pkt;
if (unlikely(q_id >= nic_dev->q_params.num_qps))
goto err_drop_pkt;
return hinic3_send_one_skb(skb, netdev, &nic_dev->txqs[q_id]);
err_drop_pkt:
dev_kfree_skb_any(skb);
return NETDEV_TX_OK;
}
static bool is_hw_complete_sq_process(struct hinic3_io_queue *sq)
{
u16 sw_pi, hw_ci;
sw_pi = hinic3_get_sq_local_pi(sq);
hw_ci = hinic3_get_sq_hw_ci(sq);
return sw_pi == hw_ci;
}
#define HINIC3_FLUSH_QUEUE_POLL_SLEEP_US 10000
#define HINIC3_FLUSH_QUEUE_POLL_TIMEOUT_US 10000000
static int hinic3_stop_sq(struct hinic3_txq *txq)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(txq->netdev);
int err, rc;
err = read_poll_timeout(hinic3_force_drop_tx_pkt, rc,
is_hw_complete_sq_process(txq->sq) || rc,
HINIC3_FLUSH_QUEUE_POLL_SLEEP_US,
HINIC3_FLUSH_QUEUE_POLL_TIMEOUT_US,
true, nic_dev->hwdev);
if (rc)
return rc;
else
return err;
}
/* packet transmission should be stopped before calling this function */
void hinic3_flush_txqs(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
u16 qid;
int err;
for (qid = 0; qid < nic_dev->q_params.num_qps; qid++) {
err = hinic3_stop_sq(&nic_dev->txqs[qid]);
netdev_tx_reset_subqueue(netdev, qid);
if (err)
netdev_err(netdev, "Failed to stop sq%u\n", qid);
}
}
#define HINIC3_BDS_PER_SQ_WQEBB \
(HINIC3_SQ_WQEBB_SIZE / sizeof(struct hinic3_sq_bufdesc))
int hinic3_alloc_txqs_res(struct net_device *netdev, u16 num_sq,
u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res)
{
struct hinic3_dyna_txq_res *tqres;
int idx;
for (idx = 0; idx < num_sq; idx++) {
tqres = &txqs_res[idx];
tqres->tx_info = kzalloc_objs(*tqres->tx_info, sq_depth);
if (!tqres->tx_info)
goto err_free_tqres;
tqres->bds = kzalloc_objs(*tqres->bds,
sq_depth * HINIC3_BDS_PER_SQ_WQEBB + HINIC3_MAX_SQ_SGE);
if (!tqres->bds) {
kfree(tqres->tx_info);
goto err_free_tqres;
}
}
return 0;
err_free_tqres:
while (idx > 0) {
idx--;
tqres = &txqs_res[idx];
kfree(tqres->bds);
kfree(tqres->tx_info);
}
return -ENOMEM;
}
void hinic3_free_txqs_res(struct net_device *netdev, u16 num_sq,
u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res)
{
struct hinic3_dyna_txq_res *tqres;
int idx;
for (idx = 0; idx < num_sq; idx++) {
tqres = &txqs_res[idx];
free_all_tx_skbs(netdev, sq_depth, tqres->tx_info);
kfree(tqres->bds);
kfree(tqres->tx_info);
}
}
int hinic3_configure_txqs(struct net_device *netdev, u16 num_sq,
u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_dyna_txq_res *tqres;
struct hinic3_txq *txq;
u16 q_id;
u32 idx;
for (q_id = 0; q_id < num_sq; q_id++) {
txq = &nic_dev->txqs[q_id];
tqres = &txqs_res[q_id];
txq->q_depth = sq_depth;
txq->q_mask = sq_depth - 1;
txq->tx_stop_thrs = min(HINIC3_DEFAULT_STOP_THRS,
sq_depth / 20);
txq->tx_start_thrs = min(HINIC3_DEFAULT_START_THRS,
sq_depth / 10);
txq->tx_info = tqres->tx_info;
for (idx = 0; idx < sq_depth; idx++)
txq->tx_info[idx].dma_info =
&tqres->bds[idx * HINIC3_BDS_PER_SQ_WQEBB];
txq->sq = &nic_dev->nic_io->sq[q_id];
}
return 0;
}
bool hinic3_tx_poll(struct hinic3_txq *txq, int budget)
{
struct net_device *netdev = txq->netdev;
u16 hw_ci, sw_ci, q_id = txq->sq->q_id;
struct hinic3_tx_info *tx_info;
unsigned int bytes_compl = 0;
unsigned int pkts = 0;
u16 wqebb_cnt = 0;
hw_ci = hinic3_get_sq_hw_ci(txq->sq);
dma_rmb();
sw_ci = hinic3_get_sq_local_ci(txq->sq);
do {
tx_info = &txq->tx_info[sw_ci];
/* Did all wqebb of this wqe complete? */
if (hw_ci == sw_ci ||
((hw_ci - sw_ci) & txq->q_mask) < tx_info->wqebb_cnt)
break;
sw_ci = (sw_ci + tx_info->wqebb_cnt) & txq->q_mask;
net_prefetch(&txq->tx_info[sw_ci]);
wqebb_cnt += tx_info->wqebb_cnt;
bytes_compl += tx_info->skb->len;
pkts++;
hinic3_tx_unmap_skb(netdev, tx_info->skb, tx_info->dma_info);
napi_consume_skb(tx_info->skb, budget);
tx_info->skb = NULL;
} while (likely(pkts < HINIC3_TX_POLL_WEIGHT));
hinic3_wq_put_wqebbs(&txq->sq->wq, wqebb_cnt);
netif_subqueue_completed_wake(netdev, q_id, pkts, bytes_compl,
hinic3_wq_free_wqebbs(&txq->sq->wq),
txq->tx_start_thrs);
return pkts == HINIC3_TX_POLL_WEIGHT;
}
@@ -0,0 +1,163 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_TX_H_
#define _HINIC3_TX_H_
#include <linux/bitops.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/netdevice.h>
#include <net/checksum.h>
#define VXLAN_OFFLOAD_PORT_LE cpu_to_be16(4789)
#define TCP_HDR_DATA_OFF_UNIT_SHIFT 2
#define TRANSPORT_OFFSET(l4_hdr, skb) ((l4_hdr) - (skb)->data)
#define HINIC3_COMPACT_WQEE_SKB_MAX_LEN 16383
#define HINIC3_TX_POLL_WEIGHT 64
#define HINIC3_DEFAULT_STOP_THRS 6
#define HINIC3_DEFAULT_START_THRS 24
enum sq_wqe_data_format {
SQ_NORMAL_WQE = 0,
};
enum sq_wqe_ec_type {
SQ_WQE_COMPACT_TYPE = 0,
SQ_WQE_EXTENDED_TYPE = 1,
};
enum sq_wqe_tasksect_len_type {
SQ_WQE_TASKSECT_46BITS = 0,
SQ_WQE_TASKSECT_16BYTES = 1,
};
enum hinic3_tx_offload_type {
HINIC3_TX_OFFLOAD_TSO = BIT(0),
HINIC3_TX_OFFLOAD_CSUM = BIT(1),
HINIC3_TX_OFFLOAD_VLAN = BIT(2),
HINIC3_TX_OFFLOAD_INVALID = BIT(3),
HINIC3_TX_OFFLOAD_ESP = BIT(4),
};
#define SQ_CTRL_BUFDESC_NUM_MASK GENMASK(26, 19)
#define SQ_CTRL_TASKSECT_LEN_MASK BIT(27)
#define SQ_CTRL_DATA_FORMAT_MASK BIT(28)
#define SQ_CTRL_EXTENDED_MASK BIT(30)
#define SQ_CTRL_OWNER_MASK BIT(31)
#define SQ_CTRL_SET(val, member) \
FIELD_PREP(SQ_CTRL_##member##_MASK, val)
#define SQ_CTRL_QUEUE_INFO_PLDOFF_MASK GENMASK(9, 2)
#define SQ_CTRL_QUEUE_INFO_UFO_MASK BIT(10)
#define SQ_CTRL_QUEUE_INFO_TSO_MASK BIT(11)
#define SQ_CTRL_QUEUE_INFO_MSS_MASK GENMASK(26, 13)
#define SQ_CTRL_QUEUE_INFO_UC_MASK BIT(28)
#define SQ_CTRL_QUEUE_INFO_SET(val, member) \
FIELD_PREP(SQ_CTRL_QUEUE_INFO_##member##_MASK, val)
#define SQ_CTRL_QUEUE_INFO_GET(val, member) \
FIELD_GET(SQ_CTRL_QUEUE_INFO_##member##_MASK, le32_to_cpu(val))
#define SQ_CTRL_MAX_PLDOFF 221
#define SQ_TASK_INFO0_TUNNEL_FLAG_MASK BIT(19)
#define SQ_TASK_INFO0_INNER_L4_EN_MASK BIT(24)
#define SQ_TASK_INFO0_INNER_L3_EN_MASK BIT(25)
#define SQ_TASK_INFO0_OUT_L4_EN_MASK BIT(27)
#define SQ_TASK_INFO0_OUT_L3_EN_MASK BIT(28)
#define SQ_TASK_INFO0_SET(val, member) \
FIELD_PREP(SQ_TASK_INFO0_##member##_MASK, val)
#define SQ_TASK_INFO3_VLAN_TAG_MASK GENMASK(15, 0)
#define SQ_TASK_INFO3_VLAN_TPID_MASK GENMASK(18, 16)
#define SQ_TASK_INFO3_VLAN_TAG_VALID_MASK BIT(19)
#define SQ_TASK_INFO3_SET(val, member) \
FIELD_PREP(SQ_TASK_INFO3_##member##_MASK, val)
struct hinic3_sq_wqe_desc {
__le32 ctrl_len;
__le32 queue_info;
__le32 hi_addr;
__le32 lo_addr;
};
struct hinic3_sq_task {
__le32 pkt_info0;
__le32 ip_identify;
__le32 rsvd;
__le32 vlan_offload;
};
struct hinic3_sq_wqe_combo {
struct hinic3_sq_wqe_desc *ctrl_bd0;
struct hinic3_sq_task *task;
struct hinic3_sq_bufdesc *bds_head;
struct hinic3_sq_bufdesc *bds_sec2;
u16 first_bds_num;
u32 wqe_type;
u32 task_type;
};
struct hinic3_txq_stats {
u64 packets;
u64 bytes;
u64 busy;
u64 dropped;
u64 skb_pad_err;
u64 frag_len_overflow;
u64 offload_cow_skb_err;
u64 map_frag_err;
u64 unknown_tunnel_pkt;
u64 frag_size_err;
struct u64_stats_sync syncp;
};
struct hinic3_dma_info {
dma_addr_t dma;
u32 len;
};
struct hinic3_tx_info {
struct sk_buff *skb;
u16 wqebb_cnt;
struct hinic3_dma_info *dma_info;
};
struct hinic3_txq {
struct net_device *netdev;
struct device *dev;
u16 q_id;
u16 tx_stop_thrs;
u16 tx_start_thrs;
u32 q_mask;
u32 q_depth;
struct hinic3_tx_info *tx_info;
struct hinic3_io_queue *sq;
struct hinic3_txq_stats txq_stats;
} ____cacheline_aligned;
struct hinic3_dyna_txq_res {
struct hinic3_tx_info *tx_info;
struct hinic3_dma_info *bds;
};
int hinic3_alloc_txqs(struct net_device *netdev);
void hinic3_free_txqs(struct net_device *netdev);
int hinic3_alloc_txqs_res(struct net_device *netdev, u16 num_sq,
u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res);
void hinic3_free_txqs_res(struct net_device *netdev, u16 num_sq,
u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res);
int hinic3_configure_txqs(struct net_device *netdev, u16 num_sq,
u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res);
netdev_tx_t hinic3_xmit_frame(struct sk_buff *skb, struct net_device *netdev);
bool hinic3_tx_poll(struct hinic3_txq *txq, int budget);
void hinic3_flush_txqs(struct net_device *netdev);
#endif
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
#include <linux/dma-mapping.h>
#include "hinic3_hwdev.h"
#include "hinic3_wq.h"
#define WQ_MIN_DEPTH 64
#define WQ_MAX_DEPTH 65536
#define WQ_PAGE_ADDR_SIZE sizeof(u64)
#define WQ_MAX_NUM_PAGES (HINIC3_MIN_PAGE_SIZE / WQ_PAGE_ADDR_SIZE)
static int wq_init_wq_block(struct hinic3_hwdev *hwdev, struct hinic3_wq *wq)
{
struct hinic3_queue_pages *qpages = &wq->qpages;
int i;
if (hinic3_wq_is_0_level_cla(wq)) {
wq->wq_block_paddr = qpages->pages[0].align_paddr;
wq->wq_block_vaddr = qpages->pages[0].align_vaddr;
return 0;
}
if (wq->qpages.num_pages > WQ_MAX_NUM_PAGES) {
dev_err(hwdev->dev, "wq num_pages exceed limit: %lu\n",
WQ_MAX_NUM_PAGES);
return -EFAULT;
}
wq->wq_block_vaddr = dma_alloc_coherent(hwdev->dev,
HINIC3_MIN_PAGE_SIZE,
&wq->wq_block_paddr,
GFP_KERNEL);
if (!wq->wq_block_vaddr)
return -ENOMEM;
for (i = 0; i < qpages->num_pages; i++)
wq->wq_block_vaddr[i] = cpu_to_be64(qpages->pages[i].align_paddr);
return 0;
}
static int wq_alloc_pages(struct hinic3_hwdev *hwdev, struct hinic3_wq *wq)
{
int err;
err = hinic3_queue_pages_alloc(hwdev, &wq->qpages, 0);
if (err)
return err;
err = wq_init_wq_block(hwdev, wq);
if (err) {
hinic3_queue_pages_free(hwdev, &wq->qpages);
return err;
}
return 0;
}
static void wq_free_pages(struct hinic3_hwdev *hwdev, struct hinic3_wq *wq)
{
if (!hinic3_wq_is_0_level_cla(wq))
dma_free_coherent(hwdev->dev,
HINIC3_MIN_PAGE_SIZE,
wq->wq_block_vaddr,
wq->wq_block_paddr);
hinic3_queue_pages_free(hwdev, &wq->qpages);
}
int hinic3_wq_create(struct hinic3_hwdev *hwdev, struct hinic3_wq *wq,
u32 q_depth, u16 wqebb_size)
{
u32 wq_page_size;
if (q_depth < WQ_MIN_DEPTH || q_depth > WQ_MAX_DEPTH ||
!is_power_of_2(q_depth) || !is_power_of_2(wqebb_size)) {
dev_err(hwdev->dev, "Invalid WQ: q_depth %u, wqebb_size %u\n",
q_depth, wqebb_size);
return -EINVAL;
}
wq_page_size = ALIGN(hwdev->wq_page_size, HINIC3_MIN_PAGE_SIZE);
memset(wq, 0, sizeof(*wq));
wq->q_depth = q_depth;
wq->idx_mask = q_depth - 1;
hinic3_queue_pages_init(&wq->qpages, q_depth, wq_page_size, wqebb_size);
return wq_alloc_pages(hwdev, wq);
}
void hinic3_wq_destroy(struct hinic3_hwdev *hwdev, struct hinic3_wq *wq)
{
wq_free_pages(hwdev, wq);
}
void hinic3_wq_reset(struct hinic3_wq *wq)
{
struct hinic3_queue_pages *qpages = &wq->qpages;
u16 pg_idx;
wq->cons_idx = 0;
wq->prod_idx = 0;
for (pg_idx = 0; pg_idx < qpages->num_pages; pg_idx++)
memset(qpages->pages[pg_idx].align_vaddr, 0, qpages->page_size);
}
void hinic3_wq_get_multi_wqebbs(struct hinic3_wq *wq,
u16 num_wqebbs, u16 *prod_idx,
struct hinic3_sq_bufdesc **first_part_wqebbs,
struct hinic3_sq_bufdesc **second_part_wqebbs,
u16 *first_part_wqebbs_num)
{
u32 idx, remaining;
idx = wq->prod_idx & wq->idx_mask;
wq->prod_idx += num_wqebbs;
*prod_idx = idx;
*first_part_wqebbs = get_q_element(&wq->qpages, idx, &remaining);
if (likely(remaining >= num_wqebbs)) {
*first_part_wqebbs_num = num_wqebbs;
*second_part_wqebbs = NULL;
} else {
*first_part_wqebbs_num = remaining;
idx += remaining;
*second_part_wqebbs = get_q_element(&wq->qpages, idx, NULL);
}
}
bool hinic3_wq_is_0_level_cla(const struct hinic3_wq *wq)
{
return wq->qpages.num_pages == 1;
}
@@ -0,0 +1,87 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
#ifndef _HINIC3_WQ_H_
#define _HINIC3_WQ_H_
#include <linux/io.h>
#include "hinic3_queue_common.h"
struct hinic3_sq_bufdesc {
/* 31-bits Length, L2NIC only uses length[17:0] */
__le32 len;
__le32 rsvd;
__le32 hi_addr;
__le32 lo_addr;
};
/* Work queue is used to submit elements (tx, rx, cmd) to hw.
* Driver is the producer that advances prod_idx. cons_idx is advanced when
* HW reports completions of previously submitted elements.
*/
struct hinic3_wq {
struct hinic3_queue_pages qpages;
/* Unmasked producer/consumer indices that are advanced to natural
* integer overflow regardless of queue depth.
*/
u16 cons_idx;
u16 prod_idx;
u32 q_depth;
u16 idx_mask;
/* Work Queue (logical WQEBB array) is mapped to hw via Chip Logical
* Address (CLA) using 1 of 2 levels:
* level 0 - direct mapping of single wq page
* level 1 - indirect mapping of multiple pages via additional page
* table.
* When wq uses level 1, wq_block will hold the allocated indirection
* table.
*/
dma_addr_t wq_block_paddr;
__be64 *wq_block_vaddr;
} ____cacheline_aligned;
/* Get number of elements in work queue that are in-use. */
static inline u16 hinic3_wq_get_used(const struct hinic3_wq *wq)
{
return READ_ONCE(wq->prod_idx) - READ_ONCE(wq->cons_idx);
}
static inline u16 hinic3_wq_free_wqebbs(struct hinic3_wq *wq)
{
/* Don't allow queue to become completely full, report (free - 1). */
return wq->q_depth - hinic3_wq_get_used(wq) - 1;
}
static inline void *hinic3_wq_get_one_wqebb(struct hinic3_wq *wq, u16 *pi)
{
*pi = wq->prod_idx & wq->idx_mask;
wq->prod_idx++;
return get_q_element(&wq->qpages, *pi, NULL);
}
static inline void hinic3_wq_put_wqebbs(struct hinic3_wq *wq, u16 num_wqebbs)
{
wq->cons_idx += num_wqebbs;
}
static inline u64 hinic3_wq_get_first_wqe_page_addr(const struct hinic3_wq *wq)
{
return wq->qpages.pages[0].align_paddr;
}
int hinic3_wq_create(struct hinic3_hwdev *hwdev, struct hinic3_wq *wq,
u32 q_depth, u16 wqebb_size);
void hinic3_wq_destroy(struct hinic3_hwdev *hwdev, struct hinic3_wq *wq);
void hinic3_wq_reset(struct hinic3_wq *wq);
void hinic3_wq_get_multi_wqebbs(struct hinic3_wq *wq,
u16 num_wqebbs, u16 *prod_idx,
struct hinic3_sq_bufdesc **first_part_wqebbs,
struct hinic3_sq_bufdesc **second_part_wqebbs,
u16 *first_part_wqebbs_num);
bool hinic3_wq_is_0_level_cla(const struct hinic3_wq *wq);
#endif