1147 lines
29 KiB
C
1147 lines
29 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* 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/netdevice.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/device.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/tcp.h>
|
|
#include <linux/sctp.h>
|
|
#include <linux/ipv6.h>
|
|
#include <net/ipv6.h>
|
|
#include <net/checksum.h>
|
|
#include <net/ip6_checksum.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/types.h>
|
|
#include <linux/u64_stats_sync.h>
|
|
|
|
#include "ossl_knl.h"
|
|
#include "hinic_hw.h"
|
|
#include "hinic_hw_mgmt.h"
|
|
#include "hinic_nic_io.h"
|
|
#include "hinic_nic_dev.h"
|
|
#include "hinic_qp.h"
|
|
#include "hinic_tx.h"
|
|
#include "hinic_dbg.h"
|
|
|
|
#define MIN_SKB_LEN 32
|
|
#define MAX_PAYLOAD_OFFSET 221
|
|
|
|
#define NIC_QID(q_id, nic_dev) ((q_id) & ((nic_dev)->num_qps - 1))
|
|
|
|
#define TXQ_STATS_INC(txq, field) \
|
|
{ \
|
|
u64_stats_update_begin(&(txq)->txq_stats.syncp); \
|
|
(txq)->txq_stats.field++; \
|
|
u64_stats_update_end(&(txq)->txq_stats.syncp); \
|
|
}
|
|
|
|
void hinic_txq_get_stats(struct hinic_txq *txq,
|
|
struct hinic_txq_stats *stats)
|
|
{
|
|
struct hinic_txq_stats *txq_stats = &txq->txq_stats;
|
|
unsigned int start;
|
|
|
|
u64_stats_update_begin(&stats->syncp);
|
|
do {
|
|
start = u64_stats_fetch_begin(&txq_stats->syncp);
|
|
stats->bytes = txq_stats->bytes;
|
|
stats->packets = txq_stats->packets;
|
|
stats->busy = txq_stats->busy;
|
|
stats->wake = txq_stats->wake;
|
|
stats->dropped = txq_stats->dropped;
|
|
stats->big_frags_pkts = txq_stats->big_frags_pkts;
|
|
stats->big_udp_pkts = txq_stats->big_udp_pkts;
|
|
} while (u64_stats_fetch_retry(&txq_stats->syncp, start));
|
|
u64_stats_update_end(&stats->syncp);
|
|
}
|
|
|
|
void hinic_txq_clean_stats(struct hinic_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->wake = 0;
|
|
txq_stats->dropped = 0;
|
|
txq_stats->big_frags_pkts = 0;
|
|
txq_stats->big_udp_pkts = 0;
|
|
|
|
txq_stats->ufo_pkt_unsupport = 0;
|
|
txq_stats->ufo_linearize_err = 0;
|
|
txq_stats->ufo_alloc_skb_err = 0;
|
|
txq_stats->skb_pad_err = 0;
|
|
txq_stats->frag_len_overflow = 0;
|
|
txq_stats->offload_cow_skb_err = 0;
|
|
txq_stats->alloc_cpy_frag_err = 0;
|
|
txq_stats->map_cpy_frag_err = 0;
|
|
txq_stats->map_frag_err = 0;
|
|
txq_stats->frag_size_err = 0;
|
|
txq_stats->unknown_tunnel_pkt = 0;
|
|
u64_stats_update_end(&txq_stats->syncp);
|
|
}
|
|
|
|
static void txq_stats_init(struct hinic_txq *txq)
|
|
{
|
|
struct hinic_txq_stats *txq_stats = &txq->txq_stats;
|
|
|
|
u64_stats_init(&txq_stats->syncp);
|
|
hinic_txq_clean_stats(txq_stats);
|
|
}
|
|
|
|
inline void hinic_set_buf_desc(struct hinic_sq_bufdesc *buf_descs,
|
|
dma_addr_t addr, u32 len)
|
|
{
|
|
buf_descs->hi_addr = cpu_to_be32(upper_32_bits(addr));
|
|
buf_descs->lo_addr = cpu_to_be32(lower_32_bits(addr));
|
|
buf_descs->len = cpu_to_be32(len);
|
|
}
|
|
|
|
static int tx_map_skb(struct hinic_nic_dev *nic_dev, struct sk_buff *skb,
|
|
struct hinic_txq *txq, struct hinic_tx_info *tx_info,
|
|
struct hinic_sq_bufdesc *buf_descs, u16 skb_nr_frags)
|
|
{
|
|
struct pci_dev *pdev = nic_dev->pdev;
|
|
struct hinic_dma_len *dma_len = tx_info->dma_len;
|
|
skb_frag_t *frag = NULL;
|
|
u16 base_nr_frags;
|
|
int j, i = 0;
|
|
int node, err = 0;
|
|
u32 nsize, cpy_nsize = 0;
|
|
u8 *vaddr, *cpy_buff = NULL;
|
|
|
|
if (unlikely(skb_nr_frags > HINIC_MAX_SKB_NR_FRAGE)) {
|
|
for (i = HINIC_MAX_SKB_NR_FRAGE; i <= skb_nr_frags; i++)
|
|
cpy_nsize +=
|
|
skb_frag_size(&skb_shinfo(skb)->frags[i - 1]);
|
|
if (!cpy_nsize) {
|
|
TXQ_STATS_INC(txq, alloc_cpy_frag_err);
|
|
return -EINVAL;
|
|
}
|
|
|
|
node = dev_to_node(&nic_dev->pdev->dev);
|
|
if (node == NUMA_NO_NODE)
|
|
cpy_buff = kzalloc(cpy_nsize,
|
|
GFP_ATOMIC | __GFP_NOWARN);
|
|
else
|
|
cpy_buff = kzalloc_node(cpy_nsize,
|
|
GFP_ATOMIC | __GFP_NOWARN,
|
|
node);
|
|
|
|
if (!cpy_buff) {
|
|
TXQ_STATS_INC(txq, alloc_cpy_frag_err);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tx_info->cpy_buff = cpy_buff;
|
|
|
|
for (i = HINIC_MAX_SKB_NR_FRAGE; i <= skb_nr_frags; i++) {
|
|
frag = &skb_shinfo(skb)->frags[i - 1];
|
|
nsize = skb_frag_size(frag);
|
|
|
|
vaddr = kmap_local_page(skb_frag_page(frag));
|
|
memcpy(cpy_buff, vaddr + frag->bv_offset, nsize);
|
|
kunmap_local(vaddr);
|
|
cpy_buff += nsize;
|
|
}
|
|
}
|
|
|
|
dma_len[0].dma = dma_map_single(&pdev->dev, skb->data,
|
|
skb_headlen(skb), DMA_TO_DEVICE);
|
|
if (dma_mapping_error(&pdev->dev, dma_len[0].dma)) {
|
|
TXQ_STATS_INC(txq, map_frag_err);
|
|
err = -EFAULT;
|
|
goto map_single_err;
|
|
}
|
|
dma_len[0].len = skb_headlen(skb);
|
|
hinic_set_buf_desc(&buf_descs[0], dma_len[0].dma,
|
|
dma_len[0].len);
|
|
|
|
if (skb_nr_frags > HINIC_MAX_SKB_NR_FRAGE)
|
|
base_nr_frags = HINIC_MAX_SKB_NR_FRAGE - 1;
|
|
else
|
|
base_nr_frags = skb_nr_frags;
|
|
|
|
for (i = 0; i < base_nr_frags; ) {
|
|
frag = &(skb_shinfo(skb)->frags[i]);
|
|
nsize = skb_frag_size(frag);
|
|
i++;
|
|
dma_len[i].dma = skb_frag_dma_map(&pdev->dev, frag, 0,
|
|
nsize, DMA_TO_DEVICE);
|
|
if (dma_mapping_error(&pdev->dev, dma_len[i].dma)) {
|
|
TXQ_STATS_INC(txq, map_frag_err);
|
|
i--;
|
|
err = -EFAULT;
|
|
goto frag_map_err;
|
|
}
|
|
dma_len[i].len = nsize;
|
|
|
|
hinic_set_buf_desc(&buf_descs[i], dma_len[i].dma,
|
|
dma_len[i].len);
|
|
}
|
|
|
|
if (skb_nr_frags > HINIC_MAX_SKB_NR_FRAGE) {
|
|
dma_len[HINIC_MAX_SKB_NR_FRAGE].dma =
|
|
dma_map_single(&pdev->dev, tx_info->cpy_buff,
|
|
cpy_nsize, DMA_TO_DEVICE);
|
|
if (dma_mapping_error(&pdev->dev,
|
|
dma_len[HINIC_MAX_SKB_NR_FRAGE].dma)) {
|
|
TXQ_STATS_INC(txq, map_cpy_frag_err);
|
|
err = -EFAULT;
|
|
goto fusion_map_err;
|
|
}
|
|
|
|
dma_len[HINIC_MAX_SKB_NR_FRAGE].len = cpy_nsize;
|
|
hinic_set_buf_desc(&buf_descs[HINIC_MAX_SKB_NR_FRAGE],
|
|
dma_len[HINIC_MAX_SKB_NR_FRAGE].dma,
|
|
dma_len[HINIC_MAX_SKB_NR_FRAGE].len);
|
|
}
|
|
|
|
return 0;
|
|
|
|
fusion_map_err:
|
|
frag_map_err:
|
|
for (j = 0; j < i;) {
|
|
j++;
|
|
dma_unmap_page(&pdev->dev, dma_len[j].dma,
|
|
dma_len[j].len, DMA_TO_DEVICE);
|
|
}
|
|
dma_unmap_single(&pdev->dev, dma_len[0].dma, dma_len[0].len,
|
|
DMA_TO_DEVICE);
|
|
|
|
map_single_err:
|
|
kfree(tx_info->cpy_buff);
|
|
tx_info->cpy_buff = NULL;
|
|
|
|
return err;
|
|
}
|
|
|
|
static inline void tx_unmap_skb(struct hinic_nic_dev *nic_dev,
|
|
struct sk_buff *skb,
|
|
struct hinic_dma_len *dma_len,
|
|
u16 valid_nr_frags)
|
|
{
|
|
struct pci_dev *pdev = nic_dev->pdev;
|
|
int i;
|
|
u16 nr_frags = valid_nr_frags;
|
|
|
|
if (nr_frags > HINIC_MAX_SKB_NR_FRAGE)
|
|
nr_frags = HINIC_MAX_SKB_NR_FRAGE;
|
|
|
|
for (i = 0; i < nr_frags; ) {
|
|
i++;
|
|
dma_unmap_page(&pdev->dev,
|
|
dma_len[i].dma,
|
|
dma_len[i].len, DMA_TO_DEVICE);
|
|
}
|
|
|
|
dma_unmap_single(&pdev->dev, dma_len[0].dma,
|
|
dma_len[0].len, DMA_TO_DEVICE);
|
|
}
|
|
|
|
union hinic_ip {
|
|
struct iphdr *v4;
|
|
struct ipv6hdr *v6;
|
|
unsigned char *hdr;
|
|
};
|
|
|
|
union hinic_l4 {
|
|
struct tcphdr *tcp;
|
|
struct udphdr *udp;
|
|
unsigned char *hdr;
|
|
};
|
|
|
|
#define TRANSPORT_OFFSET(l4_hdr, skb) ((u32)((l4_hdr) - (skb)->data))
|
|
|
|
static void get_inner_l3_l4_type(struct sk_buff *skb, union hinic_ip *ip,
|
|
union hinic_l4 *l4,
|
|
enum tx_offload_type offload_type,
|
|
enum sq_l3_type *l3_type, u8 *l4_proto)
|
|
{
|
|
unsigned char *exthdr;
|
|
|
|
if (ip->v4->version == 4) {
|
|
*l3_type = (offload_type == TX_OFFLOAD_CSUM) ?
|
|
IPV4_PKT_NO_CHKSUM_OFFLOAD : IPV4_PKT_WITH_CHKSUM_OFFLOAD;
|
|
*l4_proto = ip->v4->protocol;
|
|
} else if (ip->v4->version == 6) {
|
|
*l3_type = IPV6_PKT;
|
|
exthdr = ip->hdr + sizeof(*ip->v6);
|
|
*l4_proto = ip->v6->nexthdr;
|
|
if (exthdr != l4->hdr) {
|
|
__be16 frag_off = 0;
|
|
|
|
ipv6_skip_exthdr(skb, (int)(exthdr - skb->data),
|
|
l4_proto, &frag_off);
|
|
}
|
|
} else {
|
|
*l3_type = UNKNOWN_L3TYPE;
|
|
*l4_proto = 0;
|
|
}
|
|
}
|
|
|
|
static void get_inner_l4_info(struct sk_buff *skb, union hinic_l4 *l4,
|
|
enum tx_offload_type offload_type, u8 l4_proto,
|
|
enum sq_l4offload_type *l4_offload,
|
|
u32 *l4_len, u32 *offset)
|
|
{
|
|
*offset = 0;
|
|
*l4_len = 0;
|
|
*l4_offload = OFFLOAD_DISABLE;
|
|
|
|
switch (l4_proto) {
|
|
case IPPROTO_TCP:
|
|
*l4_offload = TCP_OFFLOAD_ENABLE;
|
|
*l4_len = l4->tcp->doff * 4; /* doff in unit of 4B */
|
|
/* To keep same with TSO, payload offset begins from paylaod */
|
|
*offset = *l4_len + TRANSPORT_OFFSET(l4->hdr, skb);
|
|
break;
|
|
|
|
case IPPROTO_UDP:
|
|
*l4_offload = UDP_OFFLOAD_ENABLE;
|
|
*l4_len = sizeof(struct udphdr);
|
|
*offset = TRANSPORT_OFFSET(l4->hdr, skb);
|
|
break;
|
|
|
|
case IPPROTO_SCTP:
|
|
/* only csum offload support sctp */
|
|
if (offload_type != TX_OFFLOAD_CSUM)
|
|
break;
|
|
|
|
*l4_offload = SCTP_OFFLOAD_ENABLE;
|
|
*l4_len = sizeof(struct sctphdr);
|
|
/* To keep same with UFO, payload offset
|
|
* begins from L4 header
|
|
*/
|
|
*offset = TRANSPORT_OFFSET(l4->hdr, skb);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int hinic_tx_csum(struct hinic_txq *txq, struct hinic_sq_task *task,
|
|
u32 *queue_info, struct sk_buff *skb)
|
|
{
|
|
union hinic_ip ip;
|
|
union hinic_l4 l4;
|
|
enum sq_l3_type l3_type;
|
|
enum sq_l4offload_type l4_offload;
|
|
u32 network_hdr_len;
|
|
u32 offset, l4_len;
|
|
u8 l4_proto;
|
|
|
|
if (skb->ip_summed != CHECKSUM_PARTIAL)
|
|
return 0;
|
|
|
|
if (skb->encapsulation) {
|
|
u32 l4_tunnel_len;
|
|
u32 tunnel_type = TUNNEL_UDP_NO_CSUM;
|
|
|
|
ip.hdr = skb_network_header(skb);
|
|
|
|
if (ip.v4->version == 4) {
|
|
l3_type = IPV4_PKT_NO_CHKSUM_OFFLOAD;
|
|
l4_proto = ip.v4->protocol;
|
|
} else if (ip.v4->version == 6) {
|
|
unsigned char *exthdr;
|
|
__be16 frag_off;
|
|
|
|
l3_type = IPV6_PKT;
|
|
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 {
|
|
l3_type = UNKNOWN_L3TYPE;
|
|
l4_proto = IPPROTO_RAW;
|
|
}
|
|
|
|
hinic_task_set_outter_l3(task, l3_type,
|
|
skb_network_header_len(skb));
|
|
|
|
switch (l4_proto) {
|
|
case IPPROTO_UDP:
|
|
l4_tunnel_len = skb_inner_network_offset(skb) -
|
|
skb_transport_offset(skb);
|
|
ip.hdr = skb_inner_network_header(skb);
|
|
l4.hdr = skb_inner_transport_header(skb);
|
|
network_hdr_len = skb_inner_network_header_len(skb);
|
|
break;
|
|
case IPPROTO_IPIP:
|
|
case IPPROTO_IPV6:
|
|
tunnel_type = NOT_TUNNEL;
|
|
l4_tunnel_len = 0;
|
|
|
|
ip.hdr = skb_inner_network_header(skb);
|
|
l4.hdr = skb_transport_header(skb);
|
|
network_hdr_len = skb_network_header_len(skb);
|
|
break;
|
|
default:
|
|
TXQ_STATS_INC(txq, unknown_tunnel_pkt);
|
|
/* Unsupport tunnel packet, disable csum offload */
|
|
skb_checksum_help(skb);
|
|
return 0;
|
|
}
|
|
|
|
hinic_task_set_tunnel_l4(task, tunnel_type, l4_tunnel_len);
|
|
} else {
|
|
ip.hdr = skb_network_header(skb);
|
|
l4.hdr = skb_transport_header(skb);
|
|
network_hdr_len = skb_network_header_len(skb);
|
|
}
|
|
get_inner_l3_l4_type(skb, &ip, &l4, TX_OFFLOAD_CSUM,
|
|
&l3_type, &l4_proto);
|
|
|
|
get_inner_l4_info(skb, &l4, TX_OFFLOAD_CSUM, l4_proto,
|
|
&l4_offload, &l4_len, &offset);
|
|
|
|
hinic_task_set_inner_l3(task, l3_type, network_hdr_len);
|
|
|
|
hinic_set_cs_inner_l4(task, queue_info, l4_offload, l4_len, offset);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static __sum16 csum_magic(union hinic_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 hinic_tso(struct hinic_sq_task *task, u32 *queue_info,
|
|
struct sk_buff *skb)
|
|
{
|
|
union hinic_ip ip;
|
|
union hinic_l4 l4;
|
|
enum sq_l3_type l3_type;
|
|
enum sq_l4offload_type l4_offload;
|
|
u32 network_hdr_len;
|
|
u32 offset, l4_len;
|
|
u32 ip_identify = 0;
|
|
u8 l4_proto;
|
|
int err;
|
|
|
|
if (!skb_is_gso(skb))
|
|
return 0;
|
|
|
|
err = skb_cow_head(skb, 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (skb->encapsulation) {
|
|
u32 l4_tunnel_len;
|
|
u32 tunnel_type = 0;
|
|
u32 gso_type = skb_shinfo(skb)->gso_type;
|
|
|
|
ip.hdr = skb_network_header(skb);
|
|
l4.hdr = skb_transport_header(skb);
|
|
network_hdr_len = skb_inner_network_header_len(skb);
|
|
|
|
if (ip.v4->version == 4)
|
|
l3_type = IPV4_PKT_WITH_CHKSUM_OFFLOAD;
|
|
else if (ip.v4->version == 6)
|
|
l3_type = IPV6_PKT;
|
|
else
|
|
l3_type = 0;
|
|
|
|
hinic_task_set_outter_l3(task, l3_type,
|
|
skb_network_header_len(skb));
|
|
|
|
if (gso_type & SKB_GSO_UDP_TUNNEL_CSUM) {
|
|
l4.udp->check = ~csum_magic(&ip, IPPROTO_UDP);
|
|
tunnel_type = TUNNEL_UDP_CSUM;
|
|
} else if (gso_type & SKB_GSO_UDP_TUNNEL) {
|
|
tunnel_type = TUNNEL_UDP_NO_CSUM;
|
|
}
|
|
|
|
l4_tunnel_len = skb_inner_network_offset(skb) -
|
|
skb_transport_offset(skb);
|
|
hinic_task_set_tunnel_l4(task, tunnel_type, l4_tunnel_len);
|
|
|
|
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);
|
|
network_hdr_len = skb_network_header_len(skb);
|
|
}
|
|
|
|
get_inner_l3_l4_type(skb, &ip, &l4, TX_OFFLOAD_TSO,
|
|
&l3_type, &l4_proto);
|
|
|
|
if (l4_proto == IPPROTO_TCP)
|
|
l4.tcp->check = ~csum_magic(&ip, IPPROTO_TCP);
|
|
|
|
get_inner_l4_info(skb, &l4, TX_OFFLOAD_TSO, l4_proto,
|
|
&l4_offload, &l4_len, &offset);
|
|
|
|
hinic_task_set_inner_l3(task, l3_type, network_hdr_len);
|
|
|
|
hinic_set_tso_inner_l4(task, queue_info, l4_offload, l4_len,
|
|
offset, ip_identify, skb_shinfo(skb)->gso_size);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static enum tx_offload_type hinic_tx_offload(struct hinic_txq *txq,
|
|
struct sk_buff *skb,
|
|
struct hinic_sq_task *task,
|
|
u32 *queue_info, u8 avd_flag)
|
|
{
|
|
enum tx_offload_type offload = 0;
|
|
int tso_cs_en;
|
|
u16 vlan_tag;
|
|
|
|
task->pkt_info0 = 0;
|
|
task->pkt_info1 = 0;
|
|
task->pkt_info2 = 0;
|
|
|
|
tso_cs_en = hinic_tso(task, queue_info, skb);
|
|
if (tso_cs_en < 0) {
|
|
offload = TX_OFFLOAD_INVALID;
|
|
return offload;
|
|
} else if (tso_cs_en) {
|
|
offload |= TX_OFFLOAD_TSO;
|
|
} else {
|
|
tso_cs_en = hinic_tx_csum(txq, task, queue_info, skb);
|
|
if (tso_cs_en)
|
|
offload |= TX_OFFLOAD_CSUM;
|
|
}
|
|
|
|
if (unlikely(skb_vlan_tag_present(skb))) {
|
|
vlan_tag = skb_vlan_tag_get(skb);
|
|
hinic_set_vlan_tx_offload(task, queue_info, vlan_tag,
|
|
vlan_tag >> VLAN_PRIO_SHIFT);
|
|
offload |= TX_OFFLOAD_VLAN;
|
|
}
|
|
|
|
if (unlikely(SQ_CTRL_QUEUE_INFO_GET(*queue_info, PLDOFF) >
|
|
MAX_PAYLOAD_OFFSET)) {
|
|
offload = TX_OFFLOAD_INVALID;
|
|
return offload;
|
|
}
|
|
|
|
if (avd_flag == HINIC_TX_UFO_AVD)
|
|
task->pkt_info0 |= SQ_TASK_INFO0_SET(1, UFO_AVD);
|
|
|
|
if (offload) {
|
|
hinic_task_set_tx_offload_valid(task, skb_network_offset(skb));
|
|
task->pkt_info0 = be32_to_cpu(task->pkt_info0);
|
|
task->pkt_info1 = be32_to_cpu(task->pkt_info1);
|
|
task->pkt_info2 = be32_to_cpu(task->pkt_info2);
|
|
}
|
|
|
|
return offload;
|
|
}
|
|
|
|
static inline void __get_pkt_stats(struct hinic_tx_info *tx_info,
|
|
struct sk_buff *skb)
|
|
{
|
|
u32 ihs, hdr_len;
|
|
|
|
if (skb_is_gso(skb)) {
|
|
#if (defined(HAVE_SKB_INNER_TRANSPORT_HEADER) && \
|
|
defined(HAVE_SK_BUFF_ENCAPSULATION))
|
|
if (skb->encapsulation) {
|
|
#ifdef HAVE_SKB_INNER_TRANSPORT_OFFSET
|
|
ihs = skb_inner_transport_offset(skb) +
|
|
inner_tcp_hdrlen(skb);
|
|
#else
|
|
ihs = (skb_inner_transport_header(skb) - skb->data) +
|
|
inner_tcp_hdrlen(skb);
|
|
#endif
|
|
} else {
|
|
#endif
|
|
ihs = skb_transport_offset(skb) + tcp_hdrlen(skb);
|
|
#if (defined(HAVE_SKB_INNER_TRANSPORT_HEADER) && \
|
|
defined(HAVE_SK_BUFF_ENCAPSULATION))
|
|
}
|
|
#endif
|
|
hdr_len = (skb_shinfo(skb)->gso_segs - 1) * ihs;
|
|
tx_info->num_bytes = skb->len + (u64)hdr_len;
|
|
|
|
} else {
|
|
tx_info->num_bytes = skb->len > ETH_ZLEN ? skb->len : ETH_ZLEN;
|
|
}
|
|
|
|
tx_info->num_pkts = 1;
|
|
}
|
|
|
|
inline u8 hinic_get_vlan_pri(struct sk_buff *skb)
|
|
{
|
|
u16 vlan_tci = 0;
|
|
int err;
|
|
|
|
err = vlan_get_tag(skb, &vlan_tci);
|
|
if (err)
|
|
return 0;
|
|
|
|
return (vlan_tci & VLAN_PRIO_MASK) >> VLAN_PRIO_SHIFT;
|
|
}
|
|
|
|
static void *__try_to_get_wqe(struct net_device *netdev, u16 q_id,
|
|
int wqebb_cnt, u16 *pi, u8 *owner)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
void *wqe = NULL;
|
|
|
|
netif_stop_subqueue(netdev, q_id);
|
|
/* We need to check again in a case another CPU has just
|
|
* made room available.
|
|
*/
|
|
if (unlikely(hinic_get_sq_free_wqebbs(nic_dev->hwdev, q_id) >=
|
|
wqebb_cnt)) {
|
|
netif_start_subqueue(netdev, q_id);
|
|
/* there have enough wqebbs after queue is wake up */
|
|
wqe = hinic_get_sq_wqe(nic_dev->hwdev, q_id,
|
|
wqebb_cnt, pi, owner);
|
|
}
|
|
|
|
return wqe;
|
|
}
|
|
|
|
#define HINIC_FRAG_STATUS_OK 0
|
|
#define HINIC_FRAG_STATUS_IGNORE 1
|
|
|
|
static netdev_tx_t hinic_send_one_skb(struct sk_buff *skb,
|
|
struct net_device *netdev,
|
|
struct hinic_txq *txq,
|
|
u8 *flag, u8 avd_flag)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct hinic_tx_info *tx_info;
|
|
struct hinic_sq_wqe *wqe = NULL;
|
|
enum tx_offload_type offload = 0;
|
|
u16 q_id = txq->q_id;
|
|
u32 queue_info = 0;
|
|
u8 owner = 0;
|
|
u16 pi = 0;
|
|
int err, wqebb_cnt;
|
|
u16 num_sge = 0;
|
|
u16 original_nr_frags;
|
|
u16 new_nr_frags;
|
|
u16 i;
|
|
int frag_err = HINIC_FRAG_STATUS_OK;
|
|
|
|
/* skb->dev will not initialized when calling netdev_alloc_skb_ip_align
|
|
* and parameter of length is largger then PAGE_SIZE(under redhat7.3),
|
|
* but skb->dev will be used in vlan_get_tag or somewhere
|
|
*/
|
|
if (unlikely(!skb->dev))
|
|
skb->dev = netdev;
|
|
|
|
if (unlikely(skb->len < MIN_SKB_LEN)) {
|
|
if (skb_pad(skb, (int)(MIN_SKB_LEN - skb->len))) {
|
|
TXQ_STATS_INC(txq, skb_pad_err);
|
|
goto tx_skb_pad_err;
|
|
}
|
|
|
|
skb->len = MIN_SKB_LEN;
|
|
}
|
|
|
|
original_nr_frags = skb_shinfo(skb)->nr_frags;
|
|
new_nr_frags = original_nr_frags;
|
|
|
|
/* If size of lastest frags are all zero, should ignore this frags.
|
|
* If size of some frag in the middle is zero, should drop this skb.
|
|
*/
|
|
for (i = 0; i < original_nr_frags; i++) {
|
|
if ((skb_frag_size(&skb_shinfo(skb)->frags[i])) &&
|
|
frag_err == HINIC_FRAG_STATUS_OK)
|
|
continue;
|
|
|
|
if ((!skb_frag_size(&skb_shinfo(skb)->frags[i])) &&
|
|
frag_err == HINIC_FRAG_STATUS_OK) {
|
|
frag_err = HINIC_FRAG_STATUS_IGNORE;
|
|
new_nr_frags = i + 1;
|
|
continue;
|
|
}
|
|
|
|
if ((!skb_frag_size(&skb_shinfo(skb)->frags[i])) &&
|
|
frag_err == HINIC_FRAG_STATUS_IGNORE)
|
|
continue;
|
|
|
|
if ((skb_frag_size(&skb_shinfo(skb)->frags[i])) &&
|
|
frag_err == HINIC_FRAG_STATUS_IGNORE) {
|
|
TXQ_STATS_INC(txq, frag_size_err);
|
|
goto tx_drop_pkts;
|
|
}
|
|
}
|
|
|
|
num_sge = new_nr_frags + 1;
|
|
|
|
/* if skb->len is more than 65536B but num_sge is 1,
|
|
* driver will drop it
|
|
*/
|
|
if (unlikely(skb->len > HINIC_GSO_MAX_SIZE && num_sge == 1)) {
|
|
TXQ_STATS_INC(txq, frag_len_overflow);
|
|
goto tx_drop_pkts;
|
|
}
|
|
|
|
/* if sge number more than 17, driver will set 17 sges */
|
|
if (unlikely(num_sge > HINIC_MAX_SQ_SGE)) {
|
|
TXQ_STATS_INC(txq, big_frags_pkts);
|
|
num_sge = HINIC_MAX_SQ_SGE;
|
|
}
|
|
|
|
wqebb_cnt = HINIC_SQ_WQEBB_CNT(num_sge);
|
|
if (likely(hinic_get_sq_free_wqebbs(nic_dev->hwdev, q_id) >=
|
|
wqebb_cnt)) {
|
|
if (likely(wqebb_cnt == 1)) {
|
|
hinic_update_sq_pi(nic_dev->hwdev, q_id,
|
|
wqebb_cnt, &pi, &owner);
|
|
wqe = txq->tx_info[pi].wqe;
|
|
} else {
|
|
wqe = hinic_get_sq_wqe(nic_dev->hwdev, q_id,
|
|
wqebb_cnt, &pi, &owner);
|
|
}
|
|
|
|
} else {
|
|
wqe = __try_to_get_wqe(netdev, q_id, wqebb_cnt, &pi, &owner);
|
|
if (likely(!wqe)) {
|
|
TXQ_STATS_INC(txq, busy);
|
|
return NETDEV_TX_BUSY;
|
|
}
|
|
}
|
|
|
|
tx_info = &txq->tx_info[pi];
|
|
tx_info->skb = skb;
|
|
tx_info->wqebb_cnt = wqebb_cnt;
|
|
tx_info->valid_nr_frags = new_nr_frags;
|
|
|
|
__get_pkt_stats(tx_info, skb);
|
|
|
|
offload = hinic_tx_offload(txq, skb, &wqe->task, &queue_info, avd_flag);
|
|
if (unlikely(offload == TX_OFFLOAD_INVALID)) {
|
|
hinic_return_sq_wqe(nic_dev->hwdev, q_id, wqebb_cnt, owner);
|
|
TXQ_STATS_INC(txq, offload_cow_skb_err);
|
|
goto tx_drop_pkts;
|
|
}
|
|
|
|
err = tx_map_skb(nic_dev, skb, txq, tx_info, wqe->buf_descs,
|
|
new_nr_frags);
|
|
if (err) {
|
|
hinic_return_sq_wqe(nic_dev->hwdev, q_id, wqebb_cnt, owner);
|
|
goto tx_drop_pkts;
|
|
}
|
|
|
|
hinic_prepare_sq_ctrl(&wqe->ctrl, queue_info, num_sge, owner);
|
|
|
|
hinic_send_sq_wqe(nic_dev->hwdev, q_id, wqe, wqebb_cnt,
|
|
nic_dev->sq_cos_mapping[hinic_get_vlan_pri(skb)]);
|
|
|
|
return NETDEV_TX_OK;
|
|
|
|
tx_drop_pkts:
|
|
dev_kfree_skb_any(skb);
|
|
|
|
tx_skb_pad_err:
|
|
TXQ_STATS_INC(txq, dropped);
|
|
|
|
*flag = HINIC_TX_DROPPED;
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
netdev_tx_t hinic_lb_xmit_frame(struct sk_buff *skb,
|
|
struct net_device *netdev)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
u16 q_id = skb_get_queue_mapping(skb);
|
|
struct hinic_txq *txq;
|
|
u8 flag = 0;
|
|
|
|
if (unlikely(!nic_dev->heart_status)) {
|
|
dev_kfree_skb_any(skb);
|
|
HINIC_NIC_STATS_INC(nic_dev, tx_carrier_off_drop);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
txq = &nic_dev->txqs[q_id];
|
|
|
|
return hinic_send_one_skb(skb, netdev, txq, &flag, HINIC_TX_NON_AVD);
|
|
}
|
|
|
|
netdev_tx_t hinic_xmit_frame(struct sk_buff *skb, struct net_device *netdev)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
u16 q_id = skb_get_queue_mapping(skb);
|
|
struct hinic_txq *txq;
|
|
u8 flag = 0;
|
|
|
|
if (unlikely(!netif_carrier_ok(netdev) ||
|
|
!nic_dev->heart_status)) {
|
|
dev_kfree_skb_any(skb);
|
|
HINIC_NIC_STATS_INC(nic_dev, tx_carrier_off_drop);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
if (unlikely(q_id >= nic_dev->num_qps)) {
|
|
txq = &nic_dev->txqs[0];
|
|
HINIC_NIC_STATS_INC(nic_dev, tx_invalid_qid);
|
|
goto tx_drop_pkts;
|
|
}
|
|
txq = &nic_dev->txqs[q_id];
|
|
|
|
return hinic_send_one_skb(skb, netdev, txq, &flag, HINIC_TX_NON_AVD);
|
|
|
|
tx_drop_pkts:
|
|
dev_kfree_skb_any(skb);
|
|
u64_stats_update_begin(&txq->txq_stats.syncp);
|
|
txq->txq_stats.dropped++;
|
|
u64_stats_update_end(&txq->txq_stats.syncp);
|
|
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
static inline void tx_free_skb(struct hinic_nic_dev *nic_dev,
|
|
struct sk_buff *skb,
|
|
struct hinic_tx_info *tx_info)
|
|
{
|
|
tx_unmap_skb(nic_dev, skb, tx_info->dma_len, tx_info->valid_nr_frags);
|
|
|
|
kfree(tx_info->cpy_buff);
|
|
tx_info->cpy_buff = NULL;
|
|
dev_kfree_skb_any(skb);
|
|
}
|
|
|
|
static void free_all_tx_skbs(struct hinic_txq *txq)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(txq->netdev);
|
|
struct hinic_tx_info *tx_info;
|
|
u16 ci;
|
|
int free_wqebbs = hinic_get_sq_free_wqebbs(nic_dev->hwdev,
|
|
txq->q_id) + 1;
|
|
|
|
while (free_wqebbs < txq->q_depth) {
|
|
ci = hinic_get_sq_local_ci(nic_dev->hwdev, txq->q_id);
|
|
|
|
tx_info = &txq->tx_info[ci];
|
|
|
|
tx_free_skb(nic_dev, tx_info->skb, tx_info);
|
|
|
|
hinic_update_sq_local_ci(nic_dev->hwdev, txq->q_id,
|
|
tx_info->wqebb_cnt);
|
|
|
|
free_wqebbs += tx_info->wqebb_cnt;
|
|
}
|
|
}
|
|
|
|
int hinic_tx_poll(struct hinic_txq *txq, int budget)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(txq->netdev);
|
|
struct sk_buff *skb;
|
|
struct hinic_tx_info *tx_info;
|
|
u64 tx_bytes = 0, wake = 0;
|
|
int pkts = 0, nr_pkts = 0, wqebb_cnt = 0;
|
|
u16 hw_ci, sw_ci = 0, q_id = txq->q_id;
|
|
|
|
hw_ci = hinic_get_sq_hw_ci(nic_dev->hwdev, q_id);
|
|
dma_rmb();
|
|
sw_ci = hinic_get_sq_local_ci(nic_dev->hwdev, q_id);
|
|
|
|
do {
|
|
tx_info = &txq->tx_info[sw_ci];
|
|
|
|
/* Whether all of the wqebb of this wqe is completed */
|
|
if (hw_ci == sw_ci || ((hw_ci - sw_ci) &
|
|
txq->q_mask) < tx_info->wqebb_cnt) {
|
|
break;
|
|
}
|
|
|
|
sw_ci = (u16)(sw_ci + tx_info->wqebb_cnt) & txq->q_mask;
|
|
prefetch(&txq->tx_info[sw_ci]);
|
|
|
|
wqebb_cnt += tx_info->wqebb_cnt;
|
|
|
|
skb = tx_info->skb;
|
|
tx_bytes += tx_info->num_bytes;
|
|
nr_pkts += tx_info->num_pkts;
|
|
pkts++;
|
|
|
|
tx_free_skb(nic_dev, skb, tx_info);
|
|
|
|
} while (likely(pkts < budget));
|
|
|
|
hinic_update_sq_local_ci(nic_dev->hwdev, q_id, wqebb_cnt);
|
|
|
|
if (unlikely(__netif_subqueue_stopped(nic_dev->netdev, q_id) &&
|
|
hinic_get_sq_free_wqebbs(nic_dev->hwdev, q_id) >= 1 &&
|
|
test_bit(HINIC_INTF_UP, &nic_dev->flags))) {
|
|
struct netdev_queue *netdev_txq =
|
|
netdev_get_tx_queue(txq->netdev, q_id);
|
|
|
|
__netif_tx_lock(netdev_txq, smp_processor_id());
|
|
/* To avoid re-waking subqueue with xmit_frame */
|
|
if (__netif_subqueue_stopped(nic_dev->netdev, q_id)) {
|
|
netif_wake_subqueue(nic_dev->netdev, q_id);
|
|
wake++;
|
|
}
|
|
__netif_tx_unlock(netdev_txq);
|
|
}
|
|
|
|
u64_stats_update_begin(&txq->txq_stats.syncp);
|
|
txq->txq_stats.bytes += tx_bytes;
|
|
txq->txq_stats.packets += nr_pkts;
|
|
txq->txq_stats.wake += wake;
|
|
u64_stats_update_end(&txq->txq_stats.syncp);
|
|
|
|
return pkts;
|
|
}
|
|
|
|
int hinic_setup_tx_wqe(struct hinic_txq *txq)
|
|
{
|
|
struct net_device *netdev = txq->netdev;
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct hinic_sq_wqe *wqe;
|
|
struct hinic_tx_info *tx_info;
|
|
u16 pi = 0;
|
|
int i;
|
|
u8 owner = 0;
|
|
|
|
for (i = 0; i < txq->q_depth; i++) {
|
|
tx_info = &txq->tx_info[i];
|
|
|
|
wqe = hinic_get_sq_wqe(nic_dev->hwdev, txq->q_id,
|
|
1, &pi, &owner);
|
|
if (!wqe) {
|
|
nicif_err(nic_dev, drv, netdev, "Failed to get SQ wqe\n");
|
|
break;
|
|
}
|
|
|
|
tx_info->wqe = wqe;
|
|
}
|
|
|
|
hinic_return_sq_wqe(nic_dev->hwdev, txq->q_id, txq->q_depth, owner);
|
|
|
|
return i;
|
|
}
|
|
|
|
int hinic_setup_all_tx_resources(struct net_device *netdev)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct hinic_txq *txq;
|
|
u64 tx_info_sz;
|
|
u16 i, q_id;
|
|
int err;
|
|
|
|
for (q_id = 0; q_id < nic_dev->num_qps; q_id++) {
|
|
txq = &nic_dev->txqs[q_id];
|
|
tx_info_sz = txq->q_depth * sizeof(*txq->tx_info);
|
|
if (!tx_info_sz) {
|
|
nicif_err(nic_dev, drv, netdev, "Cannot allocate zero size txq%d info\n",
|
|
q_id);
|
|
err = -EINVAL;
|
|
goto init_txq_err;
|
|
}
|
|
|
|
txq->tx_info = kzalloc(tx_info_sz, GFP_KERNEL);
|
|
if (!txq->tx_info) {
|
|
err = -ENOMEM;
|
|
goto init_txq_err;
|
|
}
|
|
|
|
err = hinic_setup_tx_wqe(txq);
|
|
if (err != txq->q_depth) {
|
|
nicif_err(nic_dev, drv, netdev, "Failed to setup Tx: %d wqe\n",
|
|
q_id);
|
|
q_id++;
|
|
goto init_txq_err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
init_txq_err:
|
|
for (i = 0; i < q_id; i++) {
|
|
txq = &nic_dev->txqs[i];
|
|
kfree(txq->tx_info);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
void hinic_free_all_tx_resources(struct net_device *netdev)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct hinic_txq *txq;
|
|
u16 q_id;
|
|
|
|
for (q_id = 0; q_id < nic_dev->num_qps; q_id++) {
|
|
txq = &nic_dev->txqs[q_id];
|
|
free_all_tx_skbs(txq);
|
|
kfree(txq->tx_info);
|
|
}
|
|
}
|
|
|
|
void hinic_set_sq_default_cos(struct net_device *netdev, u8 cos_id)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
int up;
|
|
|
|
for (up = HINIC_DCB_UP_MAX - 1; up >= 0; up--)
|
|
nic_dev->sq_cos_mapping[up] = nic_dev->default_cos_id;
|
|
}
|
|
|
|
int hinic_sq_cos_mapping(struct net_device *netdev)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct hinic_dcb_state dcb_state = {0};
|
|
u8 default_cos = 0;
|
|
int err;
|
|
|
|
if (HINIC_FUNC_IS_VF(nic_dev->hwdev)) {
|
|
err = hinic_get_pf_dcb_state(nic_dev->hwdev, &dcb_state);
|
|
if (err) {
|
|
hinic_info(nic_dev, drv, "Failed to get vf default cos\n");
|
|
return err;
|
|
}
|
|
|
|
default_cos = dcb_state.default_cos;
|
|
nic_dev->default_cos_id = default_cos;
|
|
hinic_set_sq_default_cos(nic_dev->netdev, default_cos);
|
|
} else {
|
|
default_cos = nic_dev->default_cos_id;
|
|
if (test_bit(HINIC_DCB_ENABLE, &nic_dev->flags))
|
|
memcpy(nic_dev->sq_cos_mapping, nic_dev->up_cos,
|
|
sizeof(nic_dev->sq_cos_mapping));
|
|
else
|
|
hinic_set_sq_default_cos(nic_dev->netdev, default_cos);
|
|
|
|
dcb_state.dcb_on = !!test_bit(HINIC_DCB_ENABLE,
|
|
&nic_dev->flags);
|
|
dcb_state.default_cos = default_cos;
|
|
memcpy(dcb_state.up_cos, nic_dev->sq_cos_mapping,
|
|
sizeof(dcb_state.up_cos));
|
|
|
|
err = hinic_set_dcb_state(nic_dev->hwdev, &dcb_state);
|
|
if (err)
|
|
hinic_info(nic_dev, drv, "Failed to set vf default cos\n");
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int hinic_alloc_txqs(struct net_device *netdev)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct pci_dev *pdev = nic_dev->pdev;
|
|
struct hinic_txq *txq;
|
|
u16 q_id, num_txqs = nic_dev->max_qps;
|
|
u64 txq_size;
|
|
|
|
txq_size = num_txqs * sizeof(*nic_dev->txqs);
|
|
if (!txq_size) {
|
|
nic_err(&pdev->dev, "Cannot allocate zero size txqs\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
nic_dev->txqs = kzalloc(txq_size, GFP_KERNEL);
|
|
|
|
if (!nic_dev->txqs) {
|
|
nic_err(&pdev->dev, "Failed to allocate txqs\n");
|
|
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->sq_depth;
|
|
txq->q_mask = nic_dev->sq_depth - 1;
|
|
|
|
txq_stats_init(txq);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void hinic_free_txqs(struct net_device *netdev)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
kfree(nic_dev->txqs);
|
|
}
|
|
|
|
/* should stop transmit any packets before calling this function */
|
|
#define HINIC_FLUSH_QUEUE_TIMEOUT 1000
|
|
|
|
static bool hinic_get_hw_handle_status(void *hwdev, u16 q_id)
|
|
{
|
|
u16 sw_pi = 0, hw_ci = 0;
|
|
|
|
sw_pi = hinic_dbg_get_sq_pi(hwdev, q_id);
|
|
hw_ci = hinic_get_sq_hw_ci(hwdev, q_id);
|
|
|
|
return sw_pi == hw_ci;
|
|
}
|
|
|
|
int hinic_stop_sq(struct hinic_txq *txq)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(txq->netdev);
|
|
unsigned long timeout;
|
|
int err;
|
|
|
|
timeout = msecs_to_jiffies(HINIC_FLUSH_QUEUE_TIMEOUT) + jiffies;
|
|
do {
|
|
if (hinic_get_hw_handle_status(nic_dev->hwdev, txq->q_id))
|
|
return 0;
|
|
|
|
usleep_range(900, 1000);
|
|
} while (time_before(jiffies, timeout));
|
|
|
|
/* force hardware to drop packets */
|
|
timeout = msecs_to_jiffies(HINIC_FLUSH_QUEUE_TIMEOUT) + jiffies;
|
|
do {
|
|
if (hinic_get_hw_handle_status(nic_dev->hwdev, txq->q_id))
|
|
return 0;
|
|
|
|
err = hinic_force_drop_tx_pkt(nic_dev->hwdev);
|
|
if (err)
|
|
break;
|
|
|
|
usleep_range(9900, 10000);
|
|
} while (time_before(jiffies, timeout));
|
|
|
|
/* Avoid msleep takes too long and get a fake result */
|
|
if (hinic_get_hw_handle_status(nic_dev->hwdev, txq->q_id))
|
|
return 0;
|
|
|
|
return -EFAULT;
|
|
}
|
|
|
|
void hinic_flush_txqs(struct net_device *netdev)
|
|
{
|
|
struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
|
|
u16 qid;
|
|
int err;
|
|
|
|
for (qid = 0; qid < nic_dev->num_qps; qid++) {
|
|
err = hinic_stop_sq(&nic_dev->txqs[qid]);
|
|
if (err)
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Failed to stop sq%d\n", qid);
|
|
}
|
|
} /*lint -e766*/
|