Linux-6.18.2/drivers/net/ethernet/huawei/hinic3/hinic3_hw_comm.c
2025-12-23 20:06:59 +08:00

427 lines
11 KiB
C

// 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"
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_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_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;
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;
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;
}
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;
}
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;
}
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;
}