Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Xuan Zhuo 2319 100.00% 5 100.00%
Total 2319 5


// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Driver for Alibaba Elastic Ethernet Adapter.
 *
 * Copyright (C) 2025 Alibaba Inc.
 */

#include <net/netdev_queues.h>

#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;
}