diff options
Diffstat (limited to 'drivers/net/ethernet/huawei/hinic/hinic_rx.c')
-rw-r--r-- | drivers/net/ethernet/huawei/hinic/hinic_rx.c | 82 |
1 files changed, 65 insertions, 17 deletions
diff --git a/drivers/net/ethernet/huawei/hinic/hinic_rx.c b/drivers/net/ethernet/huawei/hinic/hinic_rx.c index 0850ea83d6c1..56ea6d692f1c 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_rx.c +++ b/drivers/net/ethernet/huawei/hinic/hinic_rx.c @@ -18,6 +18,7 @@ #include <linux/dma-mapping.h> #include <linux/prefetch.h> #include <linux/cpumask.h> +#include <linux/if_vlan.h> #include <asm/barrier.h> #include "hinic_common.h" @@ -36,6 +37,15 @@ #define RX_IRQ_NO_RESEND_TIMER 0 #define HINIC_RX_BUFFER_WRITE 16 +#define HINIC_RX_IPV6_PKT 7 +#define LRO_PKT_HDR_LEN_IPV4 66 +#define LRO_PKT_HDR_LEN_IPV6 86 +#define LRO_REPLENISH_THLD 256 + +#define LRO_PKT_HDR_LEN(cqe) \ + (HINIC_GET_RX_PKT_TYPE(be32_to_cpu((cqe)->offload_type)) == \ + HINIC_RX_IPV6_PKT ? LRO_PKT_HDR_LEN_IPV6 : LRO_PKT_HDR_LEN_IPV4) + /** * hinic_rxq_clean_stats - Clean the statistics of specific queue * @rxq: Logical Rx Queue @@ -47,6 +57,9 @@ void hinic_rxq_clean_stats(struct hinic_rxq *rxq) u64_stats_update_begin(&rxq_stats->syncp); rxq_stats->pkts = 0; rxq_stats->bytes = 0; + rxq_stats->errors = 0; + rxq_stats->csum_errors = 0; + rxq_stats->other_errors = 0; u64_stats_update_end(&rxq_stats->syncp); } @@ -65,6 +78,10 @@ void hinic_rxq_get_stats(struct hinic_rxq *rxq, struct hinic_rxq_stats *stats) start = u64_stats_fetch_begin(&rxq_stats->syncp); stats->pkts = rxq_stats->pkts; stats->bytes = rxq_stats->bytes; + stats->errors = rxq_stats->csum_errors + + rxq_stats->other_errors; + stats->csum_errors = rxq_stats->csum_errors; + stats->other_errors = rxq_stats->other_errors; } while (u64_stats_fetch_retry(&rxq_stats->syncp, start)); u64_stats_update_end(&stats->syncp); } @@ -81,27 +98,25 @@ static void rxq_stats_init(struct hinic_rxq *rxq) hinic_rxq_clean_stats(rxq); } -static void rx_csum(struct hinic_rxq *rxq, u16 cons_idx, +static void rx_csum(struct hinic_rxq *rxq, u32 status, struct sk_buff *skb) { struct net_device *netdev = rxq->netdev; - struct hinic_rq_cqe *cqe; - struct hinic_rq *rq; u32 csum_err; - u32 status; - rq = rxq->rq; - cqe = rq->cqe[cons_idx]; - status = be32_to_cpu(cqe->status); csum_err = HINIC_RQ_CQE_STATUS_GET(status, CSUM_ERR); if (!(netdev->features & NETIF_F_RXCSUM)) return; - if (!csum_err) + if (!csum_err) { skb->ip_summed = CHECKSUM_UNNECESSARY; - else + } else { + if (!(csum_err & (HINIC_RX_CSUM_HW_CHECK_NONE | + HINIC_RX_CSUM_IPSU_OTHER_ERR))) + rxq->rxq_stats.csum_errors++; skb->ip_summed = CHECKSUM_NONE; + } } /** * rx_alloc_skb - allocate skb and map it to dma address @@ -311,13 +326,21 @@ static int rx_recv_jumbo_pkt(struct hinic_rxq *rxq, struct sk_buff *head_skb, static int rxq_recv(struct hinic_rxq *rxq, int budget) { struct hinic_qp *qp = container_of(rxq->rq, struct hinic_qp, rq); + struct net_device *netdev = rxq->netdev; u64 pkt_len = 0, rx_bytes = 0; + struct hinic_rq *rq = rxq->rq; struct hinic_rq_wqe *rq_wqe; unsigned int free_wqebbs; + struct hinic_rq_cqe *cqe; int num_wqes, pkts = 0; struct hinic_sge sge; + unsigned int status; struct sk_buff *skb; - u16 ci; + u32 offload_type; + u16 ci, num_lro; + u16 num_wqe = 0; + u32 vlan_len; + u16 vid; while (pkts < budget) { num_wqes = 0; @@ -327,11 +350,13 @@ static int rxq_recv(struct hinic_rxq *rxq, int budget) if (!rq_wqe) break; + cqe = rq->cqe[ci]; + status = be32_to_cpu(cqe->status); hinic_rq_get_sge(rxq->rq, rq_wqe, ci, &sge); rx_unmap_skb(rxq, hinic_sge_to_dma(&sge)); - rx_csum(rxq, ci, skb); + rx_csum(rxq, status, skb); prefetch(skb->data); @@ -345,9 +370,17 @@ static int rxq_recv(struct hinic_rxq *rxq, int budget) HINIC_RX_BUF_SZ, ci); } - hinic_rq_put_wqe(rxq->rq, ci, + hinic_rq_put_wqe(rq, ci, (num_wqes + 1) * HINIC_RQ_WQE_SIZE); + offload_type = be32_to_cpu(cqe->offload_type); + vlan_len = be32_to_cpu(cqe->len); + if ((netdev->features & NETIF_F_HW_VLAN_CTAG_RX) && + HINIC_GET_RX_VLAN_OFFLOAD_EN(offload_type)) { + vid = HINIC_GET_RX_VLAN_TAG(vlan_len); + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid); + } + skb_record_rx_queue(skb, qp->q_id); skb->protocol = eth_type_trans(skb, rxq->netdev); @@ -355,6 +388,21 @@ static int rxq_recv(struct hinic_rxq *rxq, int budget) pkts++; rx_bytes += pkt_len; + + num_lro = HINIC_GET_RX_NUM_LRO(status); + if (num_lro) { + rx_bytes += ((num_lro - 1) * + LRO_PKT_HDR_LEN(cqe)); + + num_wqe += + (u16)(pkt_len >> rxq->rx_buff_shift) + + ((pkt_len & (rxq->buf_len - 1)) ? 1 : 0); + } + + cqe->status = 0; + + if (num_wqe >= LRO_REPLENISH_THLD) + break; } free_wqebbs = hinic_get_rq_free_wqebbs(rxq->rq); @@ -469,20 +517,20 @@ int hinic_init_rxq(struct hinic_rxq *rxq, struct hinic_rq *rq, struct net_device *netdev) { struct hinic_qp *qp = container_of(rq, struct hinic_qp, rq); - int err, pkts, irqname_len; + int err, pkts; rxq->netdev = netdev; rxq->rq = rq; + rxq->buf_len = HINIC_RX_BUF_SZ; + rxq->rx_buff_shift = ilog2(HINIC_RX_BUF_SZ); rxq_stats_init(rxq); - irqname_len = snprintf(NULL, 0, "hinic_rxq%d", qp->q_id) + 1; - rxq->irq_name = devm_kzalloc(&netdev->dev, irqname_len, GFP_KERNEL); + rxq->irq_name = devm_kasprintf(&netdev->dev, GFP_KERNEL, + "hinic_rxq%d", qp->q_id); if (!rxq->irq_name) return -ENOMEM; - sprintf(rxq->irq_name, "hinic_rxq%d", qp->q_id); - pkts = rx_alloc_pkts(rxq); if (!pkts) { err = -ENOMEM; |