// SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2021 Huawei Technologies Co., Ltd */ #define pr_fmt(fmt) KBUILD_MODNAME ": [NIC]" fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ossl_knl.h" #include "hinic3_crm.h" #include "hinic3_nic_qp.h" #include "hinic3_nic_io.h" #include "hinic3_nic_cfg.h" #include "hinic3_srv_nic.h" #include "hinic3_nic_dev.h" #include "hinic3_tx.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 HINIC3_TX_TASK_WRAPPED 1 #define HINIC3_TX_BD_DESC_WRAPPED 2 #define TXQ_STATS_INC(txq, field) \ do { \ u64_stats_update_begin(&(txq)->txq_stats.syncp); \ (txq)->txq_stats.field++; \ u64_stats_update_end(&(txq)->txq_stats.syncp); \ } while (0) void hinic3_txq_get_stats(struct hinic3_txq *txq, struct hinic3_txq_stats *stats) { struct hinic3_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; } while (u64_stats_fetch_retry(&txq_stats->syncp, start)); u64_stats_update_end(&stats->syncp); } 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->wake = 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; txq_stats->rsvd1 = 0; txq_stats->rsvd2 = 0; u64_stats_update_end(&txq_stats->syncp); } static void 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); } static inline void hinic3_set_buf_desc(struct hinic3_sq_bufdesc *buf_descs, dma_addr_t addr, u32 len) { buf_descs->hi_addr = hinic3_hw_be32(upper_32_bits(addr)); buf_descs->lo_addr = hinic3_hw_be32(lower_32_bits(addr)); buf_descs->len = hinic3_hw_be32(len); } static int tx_map_skb(struct hinic3_nic_dev *nic_dev, struct sk_buff *skb, u16 valid_nr_frags, 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_dma_info *dma_info = tx_info->dma_info; struct pci_dev *pdev = nic_dev->pdev; skb_frag_t *frag = NULL; u32 j, i; 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)) { TXQ_STATS_INC(txq, map_frag_err); return -EFAULT; } dma_info[0].len = skb_headlen(skb); wqe_desc->hi_addr = hinic3_hw_be32(upper_32_bits(dma_info[0].dma)); wqe_desc->lo_addr = hinic3_hw_be32(lower_32_bits(dma_info[0].dma)); wqe_desc->ctrl_len = dma_info[0].len; for (i = 0; i < valid_nr_frags;) { frag = &(skb_shinfo(skb)->frags[i]); if (unlikely(i == wqe_combo->first_bds_num)) buf_desc = wqe_combo->bds_sec2; i++; dma_info[i].dma = skb_frag_dma_map(&pdev->dev, frag, 0, skb_frag_size(frag), DMA_TO_DEVICE); if (dma_mapping_error(&pdev->dev, dma_info[i].dma)) { TXQ_STATS_INC(txq, map_frag_err); i--; err = -EFAULT; goto frag_map_err; } dma_info[i].len = skb_frag_size(frag); hinic3_set_buf_desc(buf_desc, dma_info[i].dma, dma_info[i].len); buf_desc++; } return 0; frag_map_err: for (j = 0; j < i;) { j++; dma_unmap_page(&pdev->dev, dma_info[j].dma, dma_info[j].len, DMA_TO_DEVICE); } dma_unmap_single(&pdev->dev, dma_info[0].dma, dma_info[0].len, DMA_TO_DEVICE); return err; } static inline void tx_unmap_skb(struct hinic3_nic_dev *nic_dev, struct sk_buff *skb, u16 valid_nr_frags, struct hinic3_dma_info *dma_info) { struct pci_dev *pdev = nic_dev->pdev; int i; for (i = 0; i < valid_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); } union hinic3_l4 { struct tcphdr *tcp; struct udphdr *udp; unsigned char *hdr; }; enum sq_l3_type { UNKNOWN_L3TYPE = 0, IPV6_PKT = 1, IPV4_PKT_NO_CHKSUM_OFFLOAD = 2, IPV4_PKT_WITH_CHKSUM_OFFLOAD = 3, }; enum sq_l4offload_type { OFFLOAD_DISABLE = 0, TCP_OFFLOAD_ENABLE = 1, SCTP_OFFLOAD_ENABLE = 2, UDP_OFFLOAD_ENABLE = 3, }; /* initialize l4_len and offset */ static void get_inner_l4_info(struct sk_buff *skb, union hinic3_l4 *l4, u8 l4_proto, u32 *offset, enum sq_l4offload_type *l4_offload) { switch (l4_proto) { case IPPROTO_TCP: *l4_offload = TCP_OFFLOAD_ENABLE; /* To keep same with TSO, payload offset begins from paylaod */ *offset = (l4->tcp->doff << TCP_HDR_DATA_OFF_UNIT_SHIFT) + TRANSPORT_OFFSET(l4->hdr, skb); break; case IPPROTO_UDP: *l4_offload = UDP_OFFLOAD_ENABLE; *offset = TRANSPORT_OFFSET(l4->hdr, skb); break; default: break; } } 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 |= SQ_TASK_INFO0_SET(1U, TUNNEL_FLAG); ip.hdr = skb_network_header(skb); if (ip.v4->version == IPV4_VERSION) { l4_proto = ip.v4->protocol; } else if (ip.v4->version == IPV6_VERSION) { union hinic3_l4 l4; unsigned char *exthdr; __be16 frag_off; #ifdef HAVE_OUTER_IPV6_TUNNEL_OFFLOAD task->pkt_info0 |= SQ_TASK_INFO0_SET(1U, OUT_L4_EN); #endif 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) { TXQ_STATS_INC(txq, unknown_tunnel_pkt); /* Unsupport tunnel packet, disable csum offload */ skb_checksum_help(skb); return 0; } } task->pkt_info0 |= SQ_TASK_INFO0_SET(1U, 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 sq_l3_type *l3_type, u8 *l4_proto) { unsigned char *exthdr = NULL; if (ip->v4->version == IP4_VERSION) { *l3_type = IPV4_PKT_WITH_CHKSUM_OFFLOAD; *l4_proto = ip->v4->protocol; #ifdef HAVE_OUTER_IPV6_TUNNEL_OFFLOAD /* inner_transport_header is wrong in centos7.0 and suse12.1 */ l4->hdr = ip->hdr + ((u8)ip->v4->ihl << IP_HDR_IHL_UNIT_SHIFT); #endif } else if (ip->v4->version == IP6_VERSION) { *l3_type = IPV6_PKT; exthdr = ip->hdr + sizeof(*ip->v6); *l4_proto = ip->v6->nexthdr; if (exthdr != l4->hdr) { __be16 frag_off = 0; #ifndef HAVE_OUTER_IPV6_TUNNEL_OFFLOAD ipv6_skip_exthdr(skb, (int)(exthdr - skb->data), l4_proto, &frag_off); #else int pld_off = 0; pld_off = ipv6_skip_exthdr(skb, (int)(exthdr - skb->data), l4_proto, &frag_off); l4->hdr = skb->data + pld_off; #endif } } else { *l3_type = UNKNOWN_L3TYPE; *l4_proto = 0; } } static void hinic3_set_tso_info(struct hinic3_sq_task *task, u32 *queue_info, enum sq_l4offload_type l4_offload, u32 offset, u32 mss) { if (l4_offload == TCP_OFFLOAD_ENABLE) { *queue_info |= SQ_CTRL_QUEUE_INFO_SET(1U, TSO); task->pkt_info0 |= SQ_TASK_INFO0_SET(1U, INNER_L4_EN); } else if (l4_offload == UDP_OFFLOAD_ENABLE) { *queue_info |= SQ_CTRL_QUEUE_INFO_SET(1U, UFO); task->pkt_info0 |= SQ_TASK_INFO0_SET(1U, INNER_L4_EN); } /* Default enable L3 calculation */ task->pkt_info0 |= SQ_TASK_INFO0_SET(1U, INNER_L3_EN); *queue_info |= SQ_CTRL_QUEUE_INFO_SET(offset >> 1, PLDOFF); /* set MSS value */ *queue_info = SQ_CTRL_QUEUE_INFO_CLEAR(*queue_info, MSS); *queue_info |= SQ_CTRL_QUEUE_INFO_SET(mss, MSS); } static int hinic3_tso(struct hinic3_sq_task *task, u32 *queue_info, struct sk_buff *skb) { enum sq_l4offload_type l4_offload = OFFLOAD_DISABLE; enum sq_l3_type l3_type; union hinic3_ip ip; union hinic3_l4 l4; u32 offset = 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 gso_type = skb_shinfo(skb)->gso_type; /* L3 checksum always enable */ task->pkt_info0 |= SQ_TASK_INFO0_SET(1U, OUT_L3_EN); task->pkt_info0 |= SQ_TASK_INFO0_SET(1U, 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 |= SQ_TASK_INFO0_SET(1U, OUT_L4_EN); } else if (gso_type & SKB_GSO_UDP_TUNNEL) { #ifdef HAVE_OUTER_IPV6_TUNNEL_OFFLOAD if (ip.v4->version == 6) { l4.udp->check = ~csum_magic(&ip, IPPROTO_UDP); task->pkt_info0 |= SQ_TASK_INFO0_SET(1U, OUT_L4_EN); } #endif } 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); #ifdef HAVE_IP6_FRAG_ID_ENABLE_UFO else if (l4_proto == IPPROTO_UDP && ip.v4->version == 6) task->ip_identify = be32_to_cpu(skb_shinfo(skb)->ip6_frag_id); #endif get_inner_l4_info(skb, &l4, l4_proto, &offset, &l4_offload); #ifdef HAVE_OUTER_IPV6_TUNNEL_OFFLOAD u32 network_hdr_len; if (unlikely(l3_type == UNKNOWN_L3TYPE)) network_hdr_len = 0; else network_hdr_len = l4.hdr - ip.hdr; if (unlikely(!offset)) { if (l3_type == UNKNOWN_L3TYPE) offset = ip.hdr - skb->data; else if (l4_offload == OFFLOAD_DISABLE) offset = ip.hdr - skb->data + network_hdr_len; } #endif hinic3_set_tso_info(task, queue_info, l4_offload, offset, skb_shinfo(skb)->gso_size); return 1; } static u32 hinic3_tx_offload(struct sk_buff *skb, struct hinic3_sq_task *task, u32 *queue_info, struct hinic3_txq *txq) { u32 offload = 0; int tso_cs_en; task->pkt_info0 = 0; task->ip_identify = 0; task->pkt_info2 = 0; task->vlan_offload = 0; tso_cs_en = hinic3_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 = hinic3_tx_csum(txq, task, skb); if (tso_cs_en) offload |= 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 |= TX_OFFLOAD_VLAN; } if (unlikely(SQ_CTRL_QUEUE_INFO_GET(*queue_info, PLDOFF) > MAX_PAYLOAD_OFFSET)) { offload = TX_OFFLOAD_INVALID; return offload; } return offload; } static void get_pkt_stats(struct hinic3_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; } static inline int hinic3_maybe_stop_tx(struct hinic3_txq *txq, u16 wqebb_cnt) { if (likely(hinic3_get_sq_free_wqebbs(txq->sq) >= wqebb_cnt)) return 0; /* We need to check again in a case another CPU has just * made room available. */ netif_stop_subqueue(txq->netdev, txq->q_id); if (likely(hinic3_get_sq_free_wqebbs(txq->sq) < wqebb_cnt)) return -EBUSY; /* there have enough wqebbs after queue is wake up */ netif_start_subqueue(txq->netdev, txq->q_id); return 0; } 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) { void *second_part_wqebbs_addr = NULL; void *wqe = NULL; u16 first_part_wqebbs_num, tmp_pi; wqe_combo->ctrl_bd0 = hinic3_get_sq_one_wqebb(txq->sq, 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_get_sq_one_wqebb(txq->sq, &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 */ wqe = hinic3_get_sq_multi_wqebbs(txq->sq, num_sge - 1, &tmp_pi, &second_part_wqebbs_addr, &first_part_wqebbs_num); wqe_combo->bds_head = wqe; wqe_combo->bds_sec2 = second_part_wqebbs_addr; wqe_combo->first_bds_num = first_part_wqebbs_num; } return hinic3_get_and_update_sq_owner(txq->sq, *curr_pi, num_sge + (u16)!!offload); } /* * * hinic3_prepare_sq_ctrl - init sq wqe cs * @nr_descs: total sge_num, include bd0 in cs */ static void hinic3_prepare_sq_ctrl(struct hinic3_sq_wqe_combo *wqe_combo, u32 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 |= SQ_CTRL_SET(SQ_NORMAL_WQE, DATA_FORMAT) | SQ_CTRL_SET(wqe_combo->wqe_type, EXTENDED) | SQ_CTRL_SET(owner, OWNER); wqe_desc->ctrl_len = hinic3_hw_be32(wqe_desc->ctrl_len); /* compact wqe queue_info will transfer to ucode */ wqe_desc->queue_info = 0; return; } wqe_desc->ctrl_len |= 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->ctrl_len = hinic3_hw_be32(wqe_desc->ctrl_len); wqe_desc->queue_info = queue_info; wqe_desc->queue_info |= SQ_CTRL_QUEUE_INFO_SET(1U, UC); if (!SQ_CTRL_QUEUE_INFO_GET(wqe_desc->queue_info, MSS)) { wqe_desc->queue_info |= SQ_CTRL_QUEUE_INFO_SET(TX_MSS_DEFAULT, MSS); } else if (SQ_CTRL_QUEUE_INFO_GET(wqe_desc->queue_info, MSS) < TX_MSS_MIN) { /* mss should not less than 80 */ wqe_desc->queue_info = SQ_CTRL_QUEUE_INFO_CLEAR(wqe_desc->queue_info, MSS); wqe_desc->queue_info |= SQ_CTRL_QUEUE_INFO_SET(TX_MSS_MIN, MSS); } wqe_desc->queue_info = hinic3_hw_be32(wqe_desc->queue_info); } static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb, struct net_device *netdev, struct hinic3_txq *txq) { struct hinic3_nic_dev *nic_dev = netdev_priv(netdev); struct hinic3_sq_wqe_combo wqe_combo = {0}; struct hinic3_tx_info *tx_info = NULL; struct hinic3_sq_task task; u32 offload, queue_info = 0; u16 owner = 0, pi = 0; u16 wqebb_cnt, num_sge, valid_nr_frags; bool find_zero_sge_len = false; int err, i; 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; } valid_nr_frags = 0; for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { if (!skb_frag_size(&skb_shinfo(skb)->frags[i])) { find_zero_sge_len = true; continue; } else if (find_zero_sge_len) { TXQ_STATS_INC(txq, frag_size_err); goto tx_drop_pkts; } valid_nr_frags++; } num_sge = valid_nr_frags + 1; /* assume need normal TS format wqe, task info need 1 wqebb */ wqebb_cnt = num_sge + 1; if (unlikely(hinic3_maybe_stop_tx(txq, wqebb_cnt))) { TXQ_STATS_INC(txq, busy); return NETDEV_TX_BUSY; } offload = hinic3_tx_offload(skb, &task, &queue_info, txq); if (unlikely(offload == TX_OFFLOAD_INVALID)) { TXQ_STATS_INC(txq, offload_cow_skb_err); goto tx_drop_pkts; } else if (!offload) { /* no TS in current wqe */ wqebb_cnt -= 1; if (unlikely(num_sge == 1 && skb->len > COMPACET_WQ_SKB_MAX_LEN)) goto tx_drop_pkts; } owner = hinic3_set_wqe_combo(txq, &wqe_combo, offload, num_sge, &pi); if (offload) { /* ip6_frag_id is big endiant, not need to transfer */ wqe_combo.task->ip_identify = hinic3_hw_be32(task.ip_identify); wqe_combo.task->pkt_info0 = hinic3_hw_be32(task.pkt_info0); wqe_combo.task->pkt_info2 = hinic3_hw_be32(task.pkt_info2); wqe_combo.task->vlan_offload = hinic3_hw_be32(task.vlan_offload); } tx_info = &txq->tx_info[pi]; tx_info->skb = skb; tx_info->wqebb_cnt = wqebb_cnt; tx_info->valid_nr_frags = valid_nr_frags; err = tx_map_skb(nic_dev, skb, valid_nr_frags, txq, tx_info, &wqe_combo); if (err) { hinic3_rollback_sq_wqebbs(txq->sq, wqebb_cnt, owner); goto tx_drop_pkts; } get_pkt_stats(tx_info, skb); hinic3_prepare_sq_ctrl(&wqe_combo, queue_info, num_sge, owner); hinic3_write_db(txq->sq, txq->cos, SQ_CFLAG_DP, hinic3_get_sq_local_pi(txq->sq)); return NETDEV_TX_OK; tx_drop_pkts: dev_kfree_skb_any(skb); tx_skb_pad_err: TXQ_STATS_INC(txq, dropped); return NETDEV_TX_OK; } netdev_tx_t hinic3_lb_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); struct hinic3_txq *txq = &nic_dev->txqs[q_id]; return hinic3_send_one_skb(skb, netdev, txq); } netdev_tx_t hinic3_xmit_frame(struct sk_buff *skb, struct net_device *netdev) { struct hinic3_nic_dev *nic_dev = netdev_priv(netdev); struct hinic3_txq *txq = NULL; u16 q_id = skb_get_queue_mapping(skb); if (unlikely(!netif_carrier_ok(netdev))) { dev_kfree_skb_any(skb); HINIC3_NIC_STATS_INC(nic_dev, tx_carrier_off_drop); return NETDEV_TX_OK; } if (unlikely(q_id >= nic_dev->q_params.num_qps)) { txq = &nic_dev->txqs[0]; HINIC3_NIC_STATS_INC(nic_dev, tx_invalid_qid); goto tx_drop_pkts; } txq = &nic_dev->txqs[q_id]; return hinic3_send_one_skb(skb, netdev, txq); 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 hinic3_nic_dev *nic_dev, struct hinic3_tx_info *tx_info) { tx_unmap_skb(nic_dev, tx_info->skb, tx_info->valid_nr_frags, tx_info->dma_info); dev_kfree_skb_any(tx_info->skb); tx_info->skb = NULL; } static void free_all_tx_skbs(struct hinic3_nic_dev *nic_dev, u32 sq_depth, struct hinic3_tx_info *tx_info_arr) { struct hinic3_tx_info *tx_info = NULL; u32 idx; for (idx = 0; idx < sq_depth; idx++) { tx_info = &tx_info_arr[idx]; if (tx_info->skb) tx_free_skb(nic_dev, tx_info); } } int hinic3_tx_poll(struct hinic3_txq *txq, int budget) { struct hinic3_nic_dev *nic_dev = netdev_priv(txq->netdev); struct hinic3_tx_info *tx_info = NULL; u64 tx_bytes = 0, wake = 0, nr_pkts = 0; int pkts = 0; u16 wqebb_cnt = 0; u16 hw_ci, sw_ci = 0, q_id = txq->sq->q_id; 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]; /* 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 = (sw_ci + tx_info->wqebb_cnt) & (u16)txq->q_mask; prefetch(&txq->tx_info[sw_ci]); wqebb_cnt += tx_info->wqebb_cnt; tx_bytes += tx_info->num_bytes; nr_pkts += tx_info->num_pkts; pkts++; tx_free_skb(nic_dev, tx_info); } while (likely(pkts < budget)); hinic3_update_sq_local_ci(txq->sq, wqebb_cnt); if (unlikely(__netif_subqueue_stopped(nic_dev->netdev, q_id) && hinic3_get_sq_free_wqebbs(txq->sq) >= 1 && test_bit(HINIC3_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; } void hinic3_set_txq_cos(struct hinic3_nic_dev *nic_dev, u16 start_qid, u16 q_num, u8 cos) { u16 idx; for (idx = 0; idx < q_num; idx++) nic_dev->txqs[idx + start_qid].cos = cos; } #define HINIC3_BDS_PER_SQ_WQEBB \ (HINIC3_SQ_WQEBB_SIZE / sizeof(struct hinic3_sq_bufdesc)) int hinic3_alloc_txqs_res(struct hinic3_nic_dev *nic_dev, u16 num_sq, u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res) { struct hinic3_dyna_txq_res *tqres = NULL; int idx, i; u64 size; for (idx = 0; idx < num_sq; idx++) { tqres = &txqs_res[idx]; size = sizeof(*tqres->tx_info) * sq_depth; tqres->tx_info = kzalloc(size, GFP_KERNEL); if (!tqres->tx_info) { nicif_err(nic_dev, drv, nic_dev->netdev, "Failed to alloc txq%d tx info\n", idx); goto err_out; } size = sizeof(*tqres->bds) * (sq_depth * HINIC3_BDS_PER_SQ_WQEBB + HINIC3_MAX_SQ_SGE); tqres->bds = kzalloc(size, GFP_KERNEL); if (!tqres->bds) { kfree(tqres->tx_info); nicif_err(nic_dev, drv, nic_dev->netdev, "Failed to alloc txq%d bds info\n", idx); goto err_out; } } return 0; err_out: for (i = 0; i < idx; i++) { tqres = &txqs_res[i]; kfree(tqres->bds); kfree(tqres->tx_info); } return -ENOMEM; } void hinic3_free_txqs_res(struct hinic3_nic_dev *nic_dev, u16 num_sq, u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res) { struct hinic3_dyna_txq_res *tqres = NULL; int idx; for (idx = 0; idx < num_sq; idx++) { tqres = &txqs_res[idx]; free_all_tx_skbs(nic_dev, sq_depth, tqres->tx_info); kfree(tqres->bds); kfree(tqres->tx_info); } } int hinic3_configure_txqs(struct hinic3_nic_dev *nic_dev, u16 num_sq, u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res) { struct hinic3_dyna_txq_res *tqres = NULL; struct hinic3_txq *txq = NULL; 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_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 = hinic3_get_nic_queue(nic_dev->hwdev, q_id, HINIC3_SQ); if (!txq->sq) { nicif_err(nic_dev, drv, nic_dev->netdev, "Failed to get %u sq\n", q_id); return -EFAULT; } } return 0; } int hinic3_alloc_txqs(struct net_device *netdev) { struct hinic3_nic_dev *nic_dev = netdev_priv(netdev); struct pci_dev *pdev = nic_dev->pdev; struct hinic3_txq *txq = NULL; 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) 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; 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 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_TIMEOUT 1000 static int hinic3_stop_sq(struct hinic3_txq *txq) { struct hinic3_nic_dev *nic_dev = netdev_priv(txq->netdev); unsigned long timeout; int err; timeout = msecs_to_jiffies(HINIC3_FLUSH_QUEUE_TIMEOUT) + jiffies; do { if (is_hw_complete_sq_process(txq->sq)) return 0; usleep_range(900, 1000); /* sleep 900 us ~ 1000 us */ } while (time_before(jiffies, timeout)); /* force hardware to drop packets */ timeout = msecs_to_jiffies(HINIC3_FLUSH_QUEUE_TIMEOUT) + jiffies; do { if (is_hw_complete_sq_process(txq->sq)) return 0; err = hinic3_force_drop_tx_pkt(nic_dev->hwdev); if (err) break; usleep_range(9900, 10000); /* sleep 9900 us ~ 10000 us */ } while (time_before(jiffies, timeout)); /* Avoid msleep takes too long and get a fake result */ if (is_hw_complete_sq_process(txq->sq)) return 0; return -EFAULT; } /* should stop transmit any packets before calling this function */ int 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]); if (err) nicif_err(nic_dev, drv, netdev, "Failed to stop sq%u\n", qid); } return 0; }