OpenCloudOS-Kernel/drivers/thirdparty/hinic/hinic_qp.c

224 lines
6.1 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Huawei HiNIC PCI Express Linux driver
* Copyright(c) 2017 Huawei Technologies Co., Ltd
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": [NIC]" fmt
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/io.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include "hinic_nic_io.h"
#include "hinic_qp.h"
#define BUF_DESC_SHIFT 1
#define BUF_DESC_SIZE(nr_descs) (((u32)nr_descs) << BUF_DESC_SHIFT)
void hinic_prepare_sq_ctrl(struct hinic_sq_ctrl *ctrl, u32 queue_info,
int nr_descs, u8 owner)
{
u32 ctrl_size, task_size, bufdesc_size;
ctrl_size = SIZE_8BYTES(sizeof(struct hinic_sq_ctrl));
task_size = SIZE_8BYTES(sizeof(struct hinic_sq_task));
bufdesc_size = BUF_DESC_SIZE(nr_descs);
ctrl->ctrl_fmt = SQ_CTRL_SET(bufdesc_size, BUFDESC_SECT_LEN) |
SQ_CTRL_SET(task_size, TASKSECT_LEN) |
SQ_CTRL_SET(SQ_NORMAL_WQE, DATA_FORMAT) |
SQ_CTRL_SET(ctrl_size, LEN) |
SQ_CTRL_SET(owner, OWNER);
ctrl->ctrl_fmt = be32_to_cpu(ctrl->ctrl_fmt);
ctrl->queue_info = queue_info;
ctrl->queue_info |= SQ_CTRL_QUEUE_INFO_SET(1U, UC);
if (!SQ_CTRL_QUEUE_INFO_GET(ctrl->queue_info, MSS)) {
ctrl->queue_info |= SQ_CTRL_QUEUE_INFO_SET(TX_MSS_DEFAULT, MSS);
} else if (SQ_CTRL_QUEUE_INFO_GET(ctrl->queue_info, MSS) < TX_MSS_MIN) {
/* mss should not less than 80 */
ctrl->queue_info = SQ_CTRL_QUEUE_INFO_CLEAR(ctrl->queue_info,
MSS);
ctrl->queue_info |= SQ_CTRL_QUEUE_INFO_SET(TX_MSS_MIN, MSS);
}
ctrl->queue_info = be32_to_cpu(ctrl->queue_info);
}
int hinic_get_rx_done(struct hinic_rq_cqe *cqe)
{
u32 status;
int rx_done;
status = be32_to_cpu(cqe->status);
rx_done = RQ_CQE_STATUS_GET(status, RXDONE);
if (!rx_done)
return 0;
return 1;
}
void hinic_clear_rx_done(struct hinic_rq_cqe *cqe, u32 status_old)
{
u32 status;
status = RQ_CQE_STATUS_CLEAR(status_old, RXDONE);
cqe->status = cpu_to_be32(status);
/* Make sure Rxdone has been set */
wmb();
}
int hinic_get_super_cqe_en(struct hinic_rq_cqe *cqe)
{
u32 pkt_info;
int super_cqe_en;
pkt_info = be32_to_cpu(cqe->pkt_info);
super_cqe_en = RQ_CQE_SUPER_CQE_EN_GET(pkt_info, SUPER_CQE_EN);
if (!super_cqe_en)
return 0;
return 1;
}
u32 hinic_get_pkt_len(struct hinic_rq_cqe *cqe)
{
u32 vlan_len = be32_to_cpu(cqe->vlan_len);
return RQ_CQE_SGE_GET(vlan_len, LEN);
}
u32 hinic_get_pkt_num(struct hinic_rq_cqe *cqe)
{
u32 pkt_num = be32_to_cpu(cqe->pkt_info);
return RQ_CQE_PKT_NUM_GET(pkt_num, NUM);
}
u32 hinic_get_pkt_len_for_super_cqe(struct hinic_rq_cqe *cqe,
bool last)
{
u32 pkt_len = be32_to_cpu(cqe->pkt_info);
if (!last)
return RQ_CQE_PKT_LEN_GET(pkt_len, FIRST_LEN);
else
return RQ_CQE_PKT_LEN_GET(pkt_len, LAST_LEN);
}
void hinic_prepare_rq_wqe(void *wqe, u16 pi, dma_addr_t buf_addr,
dma_addr_t cqe_dma)
{
struct hinic_rq_wqe *rq_wqe = (struct hinic_rq_wqe *)wqe;
struct hinic_rq_ctrl *ctrl = &rq_wqe->ctrl;
struct hinic_rq_cqe_sect *cqe_sect = &rq_wqe->cqe_sect;
struct hinic_rq_bufdesc *buf_desc = &rq_wqe->buf_desc;
u32 rq_ceq_len = sizeof(struct hinic_rq_cqe);
ctrl->ctrl_fmt =
RQ_CTRL_SET(SIZE_8BYTES(sizeof(*ctrl)), LEN) |
RQ_CTRL_SET(SIZE_8BYTES(sizeof(*cqe_sect)), COMPLETE_LEN) |
RQ_CTRL_SET(SIZE_8BYTES(sizeof(*buf_desc)), BUFDESC_SECT_LEN) |
RQ_CTRL_SET(RQ_COMPLETE_SGE, COMPLETE_FORMAT);
hinic_set_sge(&cqe_sect->sge, cqe_dma, rq_ceq_len);
buf_desc->addr_high = upper_32_bits(buf_addr);
buf_desc->addr_low = lower_32_bits(buf_addr);
}
void hinic_set_cs_inner_l4(struct hinic_sq_task *task,
u32 *queue_info,
enum sq_l4offload_type l4_offload,
u32 l4_len, u32 offset)
{
u32 tcp_udp_cs = 0, sctp = 0;
u32 mss = TX_MSS_DEFAULT;
/* tcp_udp_cs should be setted to calculate outter checksum when vxlan
* packets without inner l3 and l4
*/
if (unlikely(l4_offload == SCTP_OFFLOAD_ENABLE))
sctp = 1;
else
tcp_udp_cs = 1;
task->pkt_info0 |= SQ_TASK_INFO0_SET(l4_offload, L4OFFLOAD);
task->pkt_info1 |= SQ_TASK_INFO1_SET(l4_len, INNER_L4LEN);
*queue_info |= SQ_CTRL_QUEUE_INFO_SET(offset, PLDOFF) |
SQ_CTRL_QUEUE_INFO_SET(tcp_udp_cs, TCPUDP_CS) |
SQ_CTRL_QUEUE_INFO_SET(sctp, SCTP);
*queue_info = SQ_CTRL_QUEUE_INFO_CLEAR(*queue_info, MSS);
*queue_info |= SQ_CTRL_QUEUE_INFO_SET(mss, MSS);
}
void hinic_set_tso_inner_l4(struct hinic_sq_task *task,
u32 *queue_info,
enum sq_l4offload_type l4_offload,
u32 l4_len,
u32 offset, u32 ip_ident, u32 mss)
{
u32 tso = 0, ufo = 0;
if (l4_offload == TCP_OFFLOAD_ENABLE)
tso = 1;
else if (l4_offload == UDP_OFFLOAD_ENABLE)
ufo = 1;
task->ufo_v6_identify = be32_to_cpu(ip_ident);
/* just keep the same code style here */
task->pkt_info0 |= SQ_TASK_INFO0_SET(l4_offload, L4OFFLOAD);
task->pkt_info0 |= SQ_TASK_INFO0_SET(tso || ufo, TSO_UFO);
task->pkt_info1 |= SQ_TASK_INFO1_SET(l4_len, INNER_L4LEN);
*queue_info |= SQ_CTRL_QUEUE_INFO_SET(offset, PLDOFF) |
SQ_CTRL_QUEUE_INFO_SET(tso, TSO) |
SQ_CTRL_QUEUE_INFO_SET(ufo, UFO) |
SQ_CTRL_QUEUE_INFO_SET(!!l4_offload, TCPUDP_CS);
/* cs must be calculate by hw if tso is enable */
*queue_info = SQ_CTRL_QUEUE_INFO_CLEAR(*queue_info, MSS);
/* qsf was initialized in prepare_sq_wqe */
*queue_info |= SQ_CTRL_QUEUE_INFO_SET(mss, MSS);
}
void hinic_set_vlan_tx_offload(struct hinic_sq_task *task,
u32 *queue_info,
u16 vlan_tag, u16 vlan_pri)
{
task->pkt_info0 |= SQ_TASK_INFO0_SET(vlan_tag, VLAN_TAG) |
SQ_TASK_INFO0_SET(1U, VLAN_OFFLOAD);
*queue_info |= SQ_CTRL_QUEUE_INFO_SET(vlan_pri, PRI);
}
void hinic_task_set_tx_offload_valid(struct hinic_sq_task *task, u32 l2hdr_len)
{
task->pkt_info0 |= SQ_TASK_INFO0_SET(l2hdr_len, L2HDR_LEN);
}