// SPDX-License-Identifier: GPL-2.0-or-later /* * Driver for Alibaba Elastic Ethernet Adapter. * * Copyright (C) 2025 Alibaba Inc. */ #include #include "eea_net.h" #include "eea_pci.h" #include "eea_ring.h" struct eea_sq_free_stats { u64 packets; u64 bytes; }; struct eea_tx_meta { struct eea_tx_meta *next; u32 id; union { struct sk_buff *skb; void *data; }; u32 num; dma_addr_t dma_addr; struct eea_tx_desc *desc; u32 dma_len; bool unmap; bool unmap_single; }; static struct eea_tx_meta *eea_tx_meta_get(struct eea_net_tx *tx) { struct eea_tx_meta *meta; if (!tx->free) return NULL; meta = tx->free; tx->free = meta->next; return meta; } static void eea_tx_meta_put_and_unmap(struct eea_net_tx *tx, struct eea_tx_meta *meta) { struct eea_tx_meta *head; head = meta; while (true) { if (meta->unmap) { if (meta->unmap_single) dma_unmap_single(tx->dma_dev, meta->dma_addr, meta->dma_len, DMA_TO_DEVICE); else dma_unmap_page(tx->dma_dev, meta->dma_addr, meta->dma_len, DMA_TO_DEVICE); } if (meta->next) { meta = meta->next; continue; } break; } meta->next = tx->free; tx->free = head; } static void eea_meta_free_xmit(struct eea_net_tx *tx, struct eea_tx_meta *meta, int budget, struct eea_tx_cdesc *desc, struct eea_sq_free_stats *stats) { struct sk_buff *skb = meta->skb; if (unlikely((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && desc)) { struct skb_shared_hwtstamps ts = {}; ts.hwtstamp = EEA_DESC_TS(desc) + tx->enet->hw_ts_offset; skb_tstamp_tx(skb, &ts); } ++stats->packets; stats->bytes += meta->skb->len; napi_consume_skb(meta->skb, budget); meta->data = NULL; } static int eea_clean_tx(struct eea_net_tx *tx, int budget) { struct eea_sq_free_stats stats = {0}; struct eea_tx_cdesc *desc; struct eea_tx_meta *meta; int desc_n; u16 id; while (stats.packets < budget) { desc = eea_ering_cq_get_desc(tx->ering); if (!desc) break; id = le16_to_cpu(desc->id); if (unlikely(id >= tx->ering->num)) { if (net_ratelimit()) netdev_err(tx->enet->netdev, "tx invalid id %d\n", id); eea_ering_cq_ack_desc(tx->ering, 1); continue; } meta = &tx->meta[id]; if (meta->data) { eea_tx_meta_put_and_unmap(tx, meta); eea_meta_free_xmit(tx, meta, budget, desc, &stats); desc_n = meta->num; } else { if (net_ratelimit()) netdev_err(tx->enet->netdev, "tx meta->data is null. id %d num: %d\n", meta->id, meta->num); desc_n = 1; } eea_ering_cq_ack_desc(tx->ering, desc_n); } if (stats.packets) { u64_stats_update_begin(&tx->stats.syncp); u64_stats_add(&tx->stats.bytes, stats.bytes); u64_stats_add(&tx->stats.packets, stats.packets); u64_stats_update_end(&tx->stats.syncp); } return stats.packets; } int eea_poll_tx(struct eea_net_tx *tx, int budget) { struct eea_net *enet = tx->enet; u32 index = tx - enet->tx; struct netdev_queue *txq; int num; txq = netdev_get_tx_queue(enet->netdev, index); __netif_tx_lock(txq, smp_processor_id()); num = eea_clean_tx(tx, budget); if (netif_tx_queue_stopped(txq) && tx->ering->num_free >= MAX_SKB_FRAGS + 2) netif_tx_wake_queue(txq); __netif_tx_unlock(txq); return num; } static int eea_fill_desc_from_skb(const struct sk_buff *skb, struct eea_tx_desc *desc) { if (skb_is_gso(skb)) { struct skb_shared_info *sinfo = skb_shinfo(skb); desc->gso_size = cpu_to_le16(sinfo->gso_size); if (sinfo->gso_type & SKB_GSO_TCPV4) desc->gso_type = EEA_TX_GSO_TCPV4; else if (sinfo->gso_type & SKB_GSO_TCPV6) desc->gso_type = EEA_TX_GSO_TCPV6; else if (sinfo->gso_type & SKB_GSO_UDP_L4) desc->gso_type = EEA_TX_GSO_UDP_L4; else return -EINVAL; if (sinfo->gso_type & SKB_GSO_TCP_ECN) desc->gso_type |= EEA_TX_GSO_ECN; } else { desc->gso_type = EEA_TX_GSO_NONE; } if (skb->ip_summed == CHECKSUM_PARTIAL) { desc->csum_start = cpu_to_le16(skb_checksum_start_offset(skb)); desc->csum_offset = cpu_to_le16(skb->csum_offset); } return 0; } static struct eea_tx_meta *__eea_tx_desc_fill(struct eea_net_tx *tx, struct eea_tx_meta *head_meta, dma_addr_t addr, u32 data_len, u32 dma_len, bool last, void *data, u16 flags, bool unmap) { struct eea_tx_meta *meta; struct eea_tx_desc *desc; meta = eea_tx_meta_get(tx); desc = eea_ering_sq_alloc_desc(tx->ering, meta->id, last, flags); desc->addr = cpu_to_le64(addr); desc->len = cpu_to_le16(data_len); meta->next = NULL; meta->dma_len = dma_len; meta->dma_addr = addr; meta->data = data; meta->num = 1; meta->desc = desc; meta->unmap = unmap; meta->unmap_single = false; if (head_meta) { meta->next = head_meta->next; head_meta->next = meta; ++head_meta->num; } return meta; } static struct eea_tx_meta *eea_tx_desc_fill(struct eea_net_tx *tx, struct eea_tx_meta *head_meta, dma_addr_t addr, u32 length, bool is_last, void *data, u16 flags) { struct eea_tx_meta *meta; u16 len, last; WARN_ON_ONCE(length >= 2 * USHRT_MAX); /* Since eea does not support BIG TCP, the maximum GSO size is capped at * 64KB. Consequently, a single skb buffer (head or fragment) will not * require more than two descriptors */ if (length > USHRT_MAX) { len = USHRT_MAX; last = false; } else { len = length; last = is_last; } meta = __eea_tx_desc_fill(tx, head_meta, addr, len, length, last, data, flags, true); if (length > USHRT_MAX) { if (!head_meta) head_meta = meta; addr += USHRT_MAX; len = length - USHRT_MAX; __eea_tx_desc_fill(tx, head_meta, addr, len, 0, is_last, NULL, 0, false); } return meta; } static int eea_tx_add_skb_frag(struct eea_net_tx *tx, struct eea_tx_meta *head_meta, const skb_frag_t *frag, bool is_last) { u32 len = skb_frag_size(frag); dma_addr_t addr; addr = skb_frag_dma_map(tx->dma_dev, frag, 0, len, DMA_TO_DEVICE); if (unlikely(dma_mapping_error(tx->dma_dev, addr))) return -ENOMEM; eea_tx_desc_fill(tx, head_meta, addr, len, is_last, NULL, 0); return 0; } static int eea_tx_post_skb(struct eea_net_tx *tx, struct sk_buff *skb) { const struct skb_shared_info *shinfo = skb_shinfo(skb); u32 hlen = skb_headlen(skb); struct eea_tx_meta *meta; const skb_frag_t *frag; dma_addr_t addr; u32 len = hlen; int i, err; u16 flags; bool last; if (len) { addr = dma_map_single(tx->dma_dev, skb->data, len, DMA_TO_DEVICE); if (unlikely(dma_mapping_error(tx->dma_dev, addr))) return -ENOMEM; last = !shinfo->nr_frags; i = 0; } else { /* The net stack will never submit an skb with an skb->len of * 0. If the head len is 0, the number of frags must be greater * than 0. */ frag = &shinfo->frags[0]; len = skb_frag_size(frag); addr = skb_frag_dma_map(tx->dma_dev, frag, 0, len, DMA_TO_DEVICE); if (unlikely(dma_mapping_error(tx->dma_dev, addr))) return -ENOMEM; last = shinfo->nr_frags == 1; i = 1; } flags = skb->ip_summed == CHECKSUM_PARTIAL ? EEA_DESC_F_DO_CSUM : 0; meta = eea_tx_desc_fill(tx, NULL, addr, len, last, skb, flags); meta->unmap_single = !!hlen; err = eea_fill_desc_from_skb(skb, meta->desc); if (err) goto err_cancel; for (; i < shinfo->nr_frags; i++) { frag = &shinfo->frags[i]; bool is_last = i == (shinfo->nr_frags - 1); err = eea_tx_add_skb_frag(tx, meta, frag, is_last); if (err) goto err_cancel; } eea_ering_sq_commit_desc(tx->ering); u64_stats_update_begin(&tx->stats.syncp); u64_stats_add(&tx->stats.descs, meta->num); u64_stats_update_end(&tx->stats.syncp); return 0; err_cancel: eea_ering_sq_cancel(tx->ering); eea_tx_meta_put_and_unmap(tx, meta); meta->data = NULL; return err; } static void eea_tx_kick(struct eea_net_tx *tx) { eea_ering_kick(tx->ering); u64_stats_update_begin(&tx->stats.syncp); u64_stats_inc(&tx->stats.kicks); u64_stats_update_end(&tx->stats.syncp); } static int eea_tx_check_free_num(struct eea_net_tx *tx, struct netdev_queue *txq) { int n; /* MAX_SKB_FRAGS + 1: Covers the skb linear head and all paged fragments * 1: Extra slot for a head or fragment that exceeds 64KB. */ n = MAX_SKB_FRAGS + 2; return netif_txq_maybe_stop(txq, tx->ering->num_free, n, n); } netdev_tx_t eea_tx_xmit(struct sk_buff *skb, struct net_device *netdev) { struct eea_net *enet = netdev_priv(netdev); int qnum = skb_get_queue_mapping(skb); struct eea_net_tx *tx = &enet->tx[qnum]; struct netdev_queue *txq; int err, enable; txq = netdev_get_tx_queue(netdev, qnum); enable = eea_tx_check_free_num(tx, txq); if (!enable) return NETDEV_TX_BUSY; err = eea_tx_post_skb(tx, skb); if (unlikely(err)) { u64_stats_update_begin(&tx->stats.syncp); u64_stats_inc(&tx->stats.drops); u64_stats_update_end(&tx->stats.syncp); dev_kfree_skb_any(skb); } else { if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; skb_tx_timestamp(skb); } /* NETDEV_TX_BUSY is expensive. So stop advancing the TX queue. */ eea_tx_check_free_num(tx, txq); if (!netdev_xmit_more() || netif_xmit_stopped(txq)) eea_tx_kick(tx); return NETDEV_TX_OK; } static void eea_free_meta(struct eea_net_tx *tx, struct eea_net_cfg *cfg) { struct eea_sq_free_stats stats = {0}; struct eea_tx_meta *meta; int i; while ((meta = eea_tx_meta_get(tx))) meta->skb = NULL; for (i = 0; i < cfg->tx_ring_depth; i++) { meta = &tx->meta[i]; if (!meta->skb) continue; eea_tx_meta_put_and_unmap(tx, meta); eea_meta_free_xmit(tx, meta, 0, NULL, &stats); } kvfree(tx->meta); tx->meta = NULL; } /* Maybe called before eea_bind_q_and_cfg. So the cfg must be passed. */ void eea_free_tx(struct eea_net_tx *tx, struct eea_net_cfg *cfg) { if (!tx) return; if (tx->ering) { eea_ering_free(tx->ering); tx->ering = NULL; } if (tx->meta) eea_free_meta(tx, cfg); } int eea_alloc_tx(struct eea_net_init_ctx *ctx, struct eea_net_tx *tx, u32 idx) { struct eea_tx_meta *meta; struct eea_ring *ering; u32 i; u64_stats_init(&tx->stats.syncp); snprintf(tx->name, sizeof(tx->name), "tx.%u", idx); ering = eea_ering_alloc(idx * 2 + 1, ctx->cfg.tx_ring_depth, ctx->edev, ctx->cfg.tx_sq_desc_size, ctx->cfg.tx_cq_desc_size, tx->name); if (!ering) goto err_free_tx; tx->ering = ering; tx->index = idx; tx->dma_dev = ctx->edev->dma_dev; /* meta */ tx->meta = kvcalloc(ctx->cfg.tx_ring_depth, sizeof(*tx->meta), GFP_KERNEL); if (!tx->meta) goto err_free_tx; for (i = 0; i < ctx->cfg.tx_ring_depth; ++i) { meta = &tx->meta[i]; meta->id = i; meta->next = tx->free; tx->free = meta; } return 0; err_free_tx: eea_free_tx(tx, &ctx->cfg); return -ENOMEM; }