162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci#include <net/xsk_buff_pool.h>
462306a36Sopenharmony_ci#include <net/xdp_sock.h>
562306a36Sopenharmony_ci#include <net/xdp_sock_drv.h>
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include "xsk_queue.h"
862306a36Sopenharmony_ci#include "xdp_umem.h"
962306a36Sopenharmony_ci#include "xsk.h"
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_civoid xp_add_xsk(struct xsk_buff_pool *pool, struct xdp_sock *xs)
1262306a36Sopenharmony_ci{
1362306a36Sopenharmony_ci	unsigned long flags;
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci	if (!xs->tx)
1662306a36Sopenharmony_ci		return;
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci	spin_lock_irqsave(&pool->xsk_tx_list_lock, flags);
1962306a36Sopenharmony_ci	list_add_rcu(&xs->tx_list, &pool->xsk_tx_list);
2062306a36Sopenharmony_ci	spin_unlock_irqrestore(&pool->xsk_tx_list_lock, flags);
2162306a36Sopenharmony_ci}
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_civoid xp_del_xsk(struct xsk_buff_pool *pool, struct xdp_sock *xs)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	unsigned long flags;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	if (!xs->tx)
2862306a36Sopenharmony_ci		return;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	spin_lock_irqsave(&pool->xsk_tx_list_lock, flags);
3162306a36Sopenharmony_ci	list_del_rcu(&xs->tx_list);
3262306a36Sopenharmony_ci	spin_unlock_irqrestore(&pool->xsk_tx_list_lock, flags);
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_civoid xp_destroy(struct xsk_buff_pool *pool)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	if (!pool)
3862306a36Sopenharmony_ci		return;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	kvfree(pool->tx_descs);
4162306a36Sopenharmony_ci	kvfree(pool->heads);
4262306a36Sopenharmony_ci	kvfree(pool);
4362306a36Sopenharmony_ci}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ciint xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	pool->tx_descs = kvcalloc(xs->tx->nentries, sizeof(*pool->tx_descs),
4862306a36Sopenharmony_ci				  GFP_KERNEL);
4962306a36Sopenharmony_ci	if (!pool->tx_descs)
5062306a36Sopenharmony_ci		return -ENOMEM;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	return 0;
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistruct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,
5662306a36Sopenharmony_ci						struct xdp_umem *umem)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	bool unaligned = umem->flags & XDP_UMEM_UNALIGNED_CHUNK_FLAG;
5962306a36Sopenharmony_ci	struct xsk_buff_pool *pool;
6062306a36Sopenharmony_ci	struct xdp_buff_xsk *xskb;
6162306a36Sopenharmony_ci	u32 i, entries;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	entries = unaligned ? umem->chunks : 0;
6462306a36Sopenharmony_ci	pool = kvzalloc(struct_size(pool, free_heads, entries),	GFP_KERNEL);
6562306a36Sopenharmony_ci	if (!pool)
6662306a36Sopenharmony_ci		goto out;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	pool->heads = kvcalloc(umem->chunks, sizeof(*pool->heads), GFP_KERNEL);
6962306a36Sopenharmony_ci	if (!pool->heads)
7062306a36Sopenharmony_ci		goto out;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	if (xs->tx)
7362306a36Sopenharmony_ci		if (xp_alloc_tx_descs(pool, xs))
7462306a36Sopenharmony_ci			goto out;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	pool->chunk_mask = ~((u64)umem->chunk_size - 1);
7762306a36Sopenharmony_ci	pool->addrs_cnt = umem->size;
7862306a36Sopenharmony_ci	pool->heads_cnt = umem->chunks;
7962306a36Sopenharmony_ci	pool->free_heads_cnt = umem->chunks;
8062306a36Sopenharmony_ci	pool->headroom = umem->headroom;
8162306a36Sopenharmony_ci	pool->chunk_size = umem->chunk_size;
8262306a36Sopenharmony_ci	pool->chunk_shift = ffs(umem->chunk_size) - 1;
8362306a36Sopenharmony_ci	pool->unaligned = unaligned;
8462306a36Sopenharmony_ci	pool->frame_len = umem->chunk_size - umem->headroom -
8562306a36Sopenharmony_ci		XDP_PACKET_HEADROOM;
8662306a36Sopenharmony_ci	pool->umem = umem;
8762306a36Sopenharmony_ci	pool->addrs = umem->addrs;
8862306a36Sopenharmony_ci	INIT_LIST_HEAD(&pool->free_list);
8962306a36Sopenharmony_ci	INIT_LIST_HEAD(&pool->xskb_list);
9062306a36Sopenharmony_ci	INIT_LIST_HEAD(&pool->xsk_tx_list);
9162306a36Sopenharmony_ci	spin_lock_init(&pool->xsk_tx_list_lock);
9262306a36Sopenharmony_ci	spin_lock_init(&pool->cq_lock);
9362306a36Sopenharmony_ci	refcount_set(&pool->users, 1);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	pool->fq = xs->fq_tmp;
9662306a36Sopenharmony_ci	pool->cq = xs->cq_tmp;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	for (i = 0; i < pool->free_heads_cnt; i++) {
9962306a36Sopenharmony_ci		xskb = &pool->heads[i];
10062306a36Sopenharmony_ci		xskb->pool = pool;
10162306a36Sopenharmony_ci		xskb->xdp.frame_sz = umem->chunk_size - umem->headroom;
10262306a36Sopenharmony_ci		INIT_LIST_HEAD(&xskb->free_list_node);
10362306a36Sopenharmony_ci		INIT_LIST_HEAD(&xskb->xskb_list_node);
10462306a36Sopenharmony_ci		if (pool->unaligned)
10562306a36Sopenharmony_ci			pool->free_heads[i] = xskb;
10662306a36Sopenharmony_ci		else
10762306a36Sopenharmony_ci			xp_init_xskb_addr(xskb, pool, i * pool->chunk_size);
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	return pool;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ciout:
11362306a36Sopenharmony_ci	xp_destroy(pool);
11462306a36Sopenharmony_ci	return NULL;
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_civoid xp_set_rxq_info(struct xsk_buff_pool *pool, struct xdp_rxq_info *rxq)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	u32 i;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	for (i = 0; i < pool->heads_cnt; i++)
12262306a36Sopenharmony_ci		pool->heads[i].xdp.rxq = rxq;
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ciEXPORT_SYMBOL(xp_set_rxq_info);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic void xp_disable_drv_zc(struct xsk_buff_pool *pool)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	struct netdev_bpf bpf;
12962306a36Sopenharmony_ci	int err;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	ASSERT_RTNL();
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	if (pool->umem->zc) {
13462306a36Sopenharmony_ci		bpf.command = XDP_SETUP_XSK_POOL;
13562306a36Sopenharmony_ci		bpf.xsk.pool = NULL;
13662306a36Sopenharmony_ci		bpf.xsk.queue_id = pool->queue_id;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci		err = pool->netdev->netdev_ops->ndo_bpf(pool->netdev, &bpf);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci		if (err)
14162306a36Sopenharmony_ci			WARN(1, "Failed to disable zero-copy!\n");
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci#define NETDEV_XDP_ACT_ZC	(NETDEV_XDP_ACT_BASIC |		\
14662306a36Sopenharmony_ci				 NETDEV_XDP_ACT_REDIRECT |	\
14762306a36Sopenharmony_ci				 NETDEV_XDP_ACT_XSK_ZEROCOPY)
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ciint xp_assign_dev(struct xsk_buff_pool *pool,
15062306a36Sopenharmony_ci		  struct net_device *netdev, u16 queue_id, u16 flags)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	bool force_zc, force_copy;
15362306a36Sopenharmony_ci	struct netdev_bpf bpf;
15462306a36Sopenharmony_ci	int err = 0;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	ASSERT_RTNL();
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	force_zc = flags & XDP_ZEROCOPY;
15962306a36Sopenharmony_ci	force_copy = flags & XDP_COPY;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	if (force_zc && force_copy)
16262306a36Sopenharmony_ci		return -EINVAL;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	if (xsk_get_pool_from_qid(netdev, queue_id))
16562306a36Sopenharmony_ci		return -EBUSY;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	pool->netdev = netdev;
16862306a36Sopenharmony_ci	pool->queue_id = queue_id;
16962306a36Sopenharmony_ci	err = xsk_reg_pool_at_qid(netdev, pool, queue_id);
17062306a36Sopenharmony_ci	if (err)
17162306a36Sopenharmony_ci		return err;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	if (flags & XDP_USE_SG)
17462306a36Sopenharmony_ci		pool->umem->flags |= XDP_UMEM_SG_FLAG;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	if (flags & XDP_USE_NEED_WAKEUP)
17762306a36Sopenharmony_ci		pool->uses_need_wakeup = true;
17862306a36Sopenharmony_ci	/* Tx needs to be explicitly woken up the first time.  Also
17962306a36Sopenharmony_ci	 * for supporting drivers that do not implement this
18062306a36Sopenharmony_ci	 * feature. They will always have to call sendto() or poll().
18162306a36Sopenharmony_ci	 */
18262306a36Sopenharmony_ci	pool->cached_need_wakeup = XDP_WAKEUP_TX;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	dev_hold(netdev);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (force_copy)
18762306a36Sopenharmony_ci		/* For copy-mode, we are done. */
18862306a36Sopenharmony_ci		return 0;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	if ((netdev->xdp_features & NETDEV_XDP_ACT_ZC) != NETDEV_XDP_ACT_ZC) {
19162306a36Sopenharmony_ci		err = -EOPNOTSUPP;
19262306a36Sopenharmony_ci		goto err_unreg_pool;
19362306a36Sopenharmony_ci	}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (netdev->xdp_zc_max_segs == 1 && (flags & XDP_USE_SG)) {
19662306a36Sopenharmony_ci		err = -EOPNOTSUPP;
19762306a36Sopenharmony_ci		goto err_unreg_pool;
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	bpf.command = XDP_SETUP_XSK_POOL;
20162306a36Sopenharmony_ci	bpf.xsk.pool = pool;
20262306a36Sopenharmony_ci	bpf.xsk.queue_id = queue_id;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	err = netdev->netdev_ops->ndo_bpf(netdev, &bpf);
20562306a36Sopenharmony_ci	if (err)
20662306a36Sopenharmony_ci		goto err_unreg_pool;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (!pool->dma_pages) {
20962306a36Sopenharmony_ci		WARN(1, "Driver did not DMA map zero-copy buffers");
21062306a36Sopenharmony_ci		err = -EINVAL;
21162306a36Sopenharmony_ci		goto err_unreg_xsk;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci	pool->umem->zc = true;
21462306a36Sopenharmony_ci	return 0;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cierr_unreg_xsk:
21762306a36Sopenharmony_ci	xp_disable_drv_zc(pool);
21862306a36Sopenharmony_cierr_unreg_pool:
21962306a36Sopenharmony_ci	if (!force_zc)
22062306a36Sopenharmony_ci		err = 0; /* fallback to copy mode */
22162306a36Sopenharmony_ci	if (err) {
22262306a36Sopenharmony_ci		xsk_clear_pool_at_qid(netdev, queue_id);
22362306a36Sopenharmony_ci		dev_put(netdev);
22462306a36Sopenharmony_ci	}
22562306a36Sopenharmony_ci	return err;
22662306a36Sopenharmony_ci}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ciint xp_assign_dev_shared(struct xsk_buff_pool *pool, struct xdp_sock *umem_xs,
22962306a36Sopenharmony_ci			 struct net_device *dev, u16 queue_id)
23062306a36Sopenharmony_ci{
23162306a36Sopenharmony_ci	u16 flags;
23262306a36Sopenharmony_ci	struct xdp_umem *umem = umem_xs->umem;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	/* One fill and completion ring required for each queue id. */
23562306a36Sopenharmony_ci	if (!pool->fq || !pool->cq)
23662306a36Sopenharmony_ci		return -EINVAL;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	flags = umem->zc ? XDP_ZEROCOPY : XDP_COPY;
23962306a36Sopenharmony_ci	if (umem_xs->pool->uses_need_wakeup)
24062306a36Sopenharmony_ci		flags |= XDP_USE_NEED_WAKEUP;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	return xp_assign_dev(pool, dev, queue_id, flags);
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_civoid xp_clear_dev(struct xsk_buff_pool *pool)
24662306a36Sopenharmony_ci{
24762306a36Sopenharmony_ci	if (!pool->netdev)
24862306a36Sopenharmony_ci		return;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	xp_disable_drv_zc(pool);
25162306a36Sopenharmony_ci	xsk_clear_pool_at_qid(pool->netdev, pool->queue_id);
25262306a36Sopenharmony_ci	dev_put(pool->netdev);
25362306a36Sopenharmony_ci	pool->netdev = NULL;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cistatic void xp_release_deferred(struct work_struct *work)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	struct xsk_buff_pool *pool = container_of(work, struct xsk_buff_pool,
25962306a36Sopenharmony_ci						  work);
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	rtnl_lock();
26262306a36Sopenharmony_ci	xp_clear_dev(pool);
26362306a36Sopenharmony_ci	rtnl_unlock();
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	if (pool->fq) {
26662306a36Sopenharmony_ci		xskq_destroy(pool->fq);
26762306a36Sopenharmony_ci		pool->fq = NULL;
26862306a36Sopenharmony_ci	}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (pool->cq) {
27162306a36Sopenharmony_ci		xskq_destroy(pool->cq);
27262306a36Sopenharmony_ci		pool->cq = NULL;
27362306a36Sopenharmony_ci	}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	xdp_put_umem(pool->umem, false);
27662306a36Sopenharmony_ci	xp_destroy(pool);
27762306a36Sopenharmony_ci}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_civoid xp_get_pool(struct xsk_buff_pool *pool)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	refcount_inc(&pool->users);
28262306a36Sopenharmony_ci}
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_cibool xp_put_pool(struct xsk_buff_pool *pool)
28562306a36Sopenharmony_ci{
28662306a36Sopenharmony_ci	if (!pool)
28762306a36Sopenharmony_ci		return false;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	if (refcount_dec_and_test(&pool->users)) {
29062306a36Sopenharmony_ci		INIT_WORK(&pool->work, xp_release_deferred);
29162306a36Sopenharmony_ci		schedule_work(&pool->work);
29262306a36Sopenharmony_ci		return true;
29362306a36Sopenharmony_ci	}
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	return false;
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cistatic struct xsk_dma_map *xp_find_dma_map(struct xsk_buff_pool *pool)
29962306a36Sopenharmony_ci{
30062306a36Sopenharmony_ci	struct xsk_dma_map *dma_map;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	list_for_each_entry(dma_map, &pool->umem->xsk_dma_list, list) {
30362306a36Sopenharmony_ci		if (dma_map->netdev == pool->netdev)
30462306a36Sopenharmony_ci			return dma_map;
30562306a36Sopenharmony_ci	}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	return NULL;
30862306a36Sopenharmony_ci}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_cistatic struct xsk_dma_map *xp_create_dma_map(struct device *dev, struct net_device *netdev,
31162306a36Sopenharmony_ci					     u32 nr_pages, struct xdp_umem *umem)
31262306a36Sopenharmony_ci{
31362306a36Sopenharmony_ci	struct xsk_dma_map *dma_map;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	dma_map = kzalloc(sizeof(*dma_map), GFP_KERNEL);
31662306a36Sopenharmony_ci	if (!dma_map)
31762306a36Sopenharmony_ci		return NULL;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	dma_map->dma_pages = kvcalloc(nr_pages, sizeof(*dma_map->dma_pages), GFP_KERNEL);
32062306a36Sopenharmony_ci	if (!dma_map->dma_pages) {
32162306a36Sopenharmony_ci		kfree(dma_map);
32262306a36Sopenharmony_ci		return NULL;
32362306a36Sopenharmony_ci	}
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	dma_map->netdev = netdev;
32662306a36Sopenharmony_ci	dma_map->dev = dev;
32762306a36Sopenharmony_ci	dma_map->dma_need_sync = false;
32862306a36Sopenharmony_ci	dma_map->dma_pages_cnt = nr_pages;
32962306a36Sopenharmony_ci	refcount_set(&dma_map->users, 1);
33062306a36Sopenharmony_ci	list_add(&dma_map->list, &umem->xsk_dma_list);
33162306a36Sopenharmony_ci	return dma_map;
33262306a36Sopenharmony_ci}
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_cistatic void xp_destroy_dma_map(struct xsk_dma_map *dma_map)
33562306a36Sopenharmony_ci{
33662306a36Sopenharmony_ci	list_del(&dma_map->list);
33762306a36Sopenharmony_ci	kvfree(dma_map->dma_pages);
33862306a36Sopenharmony_ci	kfree(dma_map);
33962306a36Sopenharmony_ci}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic void __xp_dma_unmap(struct xsk_dma_map *dma_map, unsigned long attrs)
34262306a36Sopenharmony_ci{
34362306a36Sopenharmony_ci	dma_addr_t *dma;
34462306a36Sopenharmony_ci	u32 i;
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	for (i = 0; i < dma_map->dma_pages_cnt; i++) {
34762306a36Sopenharmony_ci		dma = &dma_map->dma_pages[i];
34862306a36Sopenharmony_ci		if (*dma) {
34962306a36Sopenharmony_ci			*dma &= ~XSK_NEXT_PG_CONTIG_MASK;
35062306a36Sopenharmony_ci			dma_unmap_page_attrs(dma_map->dev, *dma, PAGE_SIZE,
35162306a36Sopenharmony_ci					     DMA_BIDIRECTIONAL, attrs);
35262306a36Sopenharmony_ci			*dma = 0;
35362306a36Sopenharmony_ci		}
35462306a36Sopenharmony_ci	}
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	xp_destroy_dma_map(dma_map);
35762306a36Sopenharmony_ci}
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_civoid xp_dma_unmap(struct xsk_buff_pool *pool, unsigned long attrs)
36062306a36Sopenharmony_ci{
36162306a36Sopenharmony_ci	struct xsk_dma_map *dma_map;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	if (!pool->dma_pages)
36462306a36Sopenharmony_ci		return;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	dma_map = xp_find_dma_map(pool);
36762306a36Sopenharmony_ci	if (!dma_map) {
36862306a36Sopenharmony_ci		WARN(1, "Could not find dma_map for device");
36962306a36Sopenharmony_ci		return;
37062306a36Sopenharmony_ci	}
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	if (!refcount_dec_and_test(&dma_map->users))
37362306a36Sopenharmony_ci		return;
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	__xp_dma_unmap(dma_map, attrs);
37662306a36Sopenharmony_ci	kvfree(pool->dma_pages);
37762306a36Sopenharmony_ci	pool->dma_pages = NULL;
37862306a36Sopenharmony_ci	pool->dma_pages_cnt = 0;
37962306a36Sopenharmony_ci	pool->dev = NULL;
38062306a36Sopenharmony_ci}
38162306a36Sopenharmony_ciEXPORT_SYMBOL(xp_dma_unmap);
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_cistatic void xp_check_dma_contiguity(struct xsk_dma_map *dma_map)
38462306a36Sopenharmony_ci{
38562306a36Sopenharmony_ci	u32 i;
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	for (i = 0; i < dma_map->dma_pages_cnt - 1; i++) {
38862306a36Sopenharmony_ci		if (dma_map->dma_pages[i] + PAGE_SIZE == dma_map->dma_pages[i + 1])
38962306a36Sopenharmony_ci			dma_map->dma_pages[i] |= XSK_NEXT_PG_CONTIG_MASK;
39062306a36Sopenharmony_ci		else
39162306a36Sopenharmony_ci			dma_map->dma_pages[i] &= ~XSK_NEXT_PG_CONTIG_MASK;
39262306a36Sopenharmony_ci	}
39362306a36Sopenharmony_ci}
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_cistatic int xp_init_dma_info(struct xsk_buff_pool *pool, struct xsk_dma_map *dma_map)
39662306a36Sopenharmony_ci{
39762306a36Sopenharmony_ci	if (!pool->unaligned) {
39862306a36Sopenharmony_ci		u32 i;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci		for (i = 0; i < pool->heads_cnt; i++) {
40162306a36Sopenharmony_ci			struct xdp_buff_xsk *xskb = &pool->heads[i];
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci			xp_init_xskb_dma(xskb, pool, dma_map->dma_pages, xskb->orig_addr);
40462306a36Sopenharmony_ci		}
40562306a36Sopenharmony_ci	}
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	pool->dma_pages = kvcalloc(dma_map->dma_pages_cnt, sizeof(*pool->dma_pages), GFP_KERNEL);
40862306a36Sopenharmony_ci	if (!pool->dma_pages)
40962306a36Sopenharmony_ci		return -ENOMEM;
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	pool->dev = dma_map->dev;
41262306a36Sopenharmony_ci	pool->dma_pages_cnt = dma_map->dma_pages_cnt;
41362306a36Sopenharmony_ci	pool->dma_need_sync = dma_map->dma_need_sync;
41462306a36Sopenharmony_ci	memcpy(pool->dma_pages, dma_map->dma_pages,
41562306a36Sopenharmony_ci	       pool->dma_pages_cnt * sizeof(*pool->dma_pages));
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	return 0;
41862306a36Sopenharmony_ci}
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ciint xp_dma_map(struct xsk_buff_pool *pool, struct device *dev,
42162306a36Sopenharmony_ci	       unsigned long attrs, struct page **pages, u32 nr_pages)
42262306a36Sopenharmony_ci{
42362306a36Sopenharmony_ci	struct xsk_dma_map *dma_map;
42462306a36Sopenharmony_ci	dma_addr_t dma;
42562306a36Sopenharmony_ci	int err;
42662306a36Sopenharmony_ci	u32 i;
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	dma_map = xp_find_dma_map(pool);
42962306a36Sopenharmony_ci	if (dma_map) {
43062306a36Sopenharmony_ci		err = xp_init_dma_info(pool, dma_map);
43162306a36Sopenharmony_ci		if (err)
43262306a36Sopenharmony_ci			return err;
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci		refcount_inc(&dma_map->users);
43562306a36Sopenharmony_ci		return 0;
43662306a36Sopenharmony_ci	}
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	dma_map = xp_create_dma_map(dev, pool->netdev, nr_pages, pool->umem);
43962306a36Sopenharmony_ci	if (!dma_map)
44062306a36Sopenharmony_ci		return -ENOMEM;
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	for (i = 0; i < dma_map->dma_pages_cnt; i++) {
44362306a36Sopenharmony_ci		dma = dma_map_page_attrs(dev, pages[i], 0, PAGE_SIZE,
44462306a36Sopenharmony_ci					 DMA_BIDIRECTIONAL, attrs);
44562306a36Sopenharmony_ci		if (dma_mapping_error(dev, dma)) {
44662306a36Sopenharmony_ci			__xp_dma_unmap(dma_map, attrs);
44762306a36Sopenharmony_ci			return -ENOMEM;
44862306a36Sopenharmony_ci		}
44962306a36Sopenharmony_ci		if (dma_need_sync(dev, dma))
45062306a36Sopenharmony_ci			dma_map->dma_need_sync = true;
45162306a36Sopenharmony_ci		dma_map->dma_pages[i] = dma;
45262306a36Sopenharmony_ci	}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	if (pool->unaligned)
45562306a36Sopenharmony_ci		xp_check_dma_contiguity(dma_map);
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	err = xp_init_dma_info(pool, dma_map);
45862306a36Sopenharmony_ci	if (err) {
45962306a36Sopenharmony_ci		__xp_dma_unmap(dma_map, attrs);
46062306a36Sopenharmony_ci		return err;
46162306a36Sopenharmony_ci	}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	return 0;
46462306a36Sopenharmony_ci}
46562306a36Sopenharmony_ciEXPORT_SYMBOL(xp_dma_map);
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_cistatic bool xp_addr_crosses_non_contig_pg(struct xsk_buff_pool *pool,
46862306a36Sopenharmony_ci					  u64 addr)
46962306a36Sopenharmony_ci{
47062306a36Sopenharmony_ci	return xp_desc_crosses_non_contig_pg(pool, addr, pool->chunk_size);
47162306a36Sopenharmony_ci}
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_cistatic bool xp_check_unaligned(struct xsk_buff_pool *pool, u64 *addr)
47462306a36Sopenharmony_ci{
47562306a36Sopenharmony_ci	*addr = xp_unaligned_extract_addr(*addr);
47662306a36Sopenharmony_ci	if (*addr >= pool->addrs_cnt ||
47762306a36Sopenharmony_ci	    *addr + pool->chunk_size > pool->addrs_cnt ||
47862306a36Sopenharmony_ci	    xp_addr_crosses_non_contig_pg(pool, *addr))
47962306a36Sopenharmony_ci		return false;
48062306a36Sopenharmony_ci	return true;
48162306a36Sopenharmony_ci}
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_cistatic bool xp_check_aligned(struct xsk_buff_pool *pool, u64 *addr)
48462306a36Sopenharmony_ci{
48562306a36Sopenharmony_ci	*addr = xp_aligned_extract_addr(pool, *addr);
48662306a36Sopenharmony_ci	return *addr < pool->addrs_cnt;
48762306a36Sopenharmony_ci}
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_cistatic struct xdp_buff_xsk *__xp_alloc(struct xsk_buff_pool *pool)
49062306a36Sopenharmony_ci{
49162306a36Sopenharmony_ci	struct xdp_buff_xsk *xskb;
49262306a36Sopenharmony_ci	u64 addr;
49362306a36Sopenharmony_ci	bool ok;
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	if (pool->free_heads_cnt == 0)
49662306a36Sopenharmony_ci		return NULL;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	for (;;) {
49962306a36Sopenharmony_ci		if (!xskq_cons_peek_addr_unchecked(pool->fq, &addr)) {
50062306a36Sopenharmony_ci			pool->fq->queue_empty_descs++;
50162306a36Sopenharmony_ci			return NULL;
50262306a36Sopenharmony_ci		}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci		ok = pool->unaligned ? xp_check_unaligned(pool, &addr) :
50562306a36Sopenharmony_ci		     xp_check_aligned(pool, &addr);
50662306a36Sopenharmony_ci		if (!ok) {
50762306a36Sopenharmony_ci			pool->fq->invalid_descs++;
50862306a36Sopenharmony_ci			xskq_cons_release(pool->fq);
50962306a36Sopenharmony_ci			continue;
51062306a36Sopenharmony_ci		}
51162306a36Sopenharmony_ci		break;
51262306a36Sopenharmony_ci	}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	if (pool->unaligned) {
51562306a36Sopenharmony_ci		xskb = pool->free_heads[--pool->free_heads_cnt];
51662306a36Sopenharmony_ci		xp_init_xskb_addr(xskb, pool, addr);
51762306a36Sopenharmony_ci		if (pool->dma_pages)
51862306a36Sopenharmony_ci			xp_init_xskb_dma(xskb, pool, pool->dma_pages, addr);
51962306a36Sopenharmony_ci	} else {
52062306a36Sopenharmony_ci		xskb = &pool->heads[xp_aligned_extract_idx(pool, addr)];
52162306a36Sopenharmony_ci	}
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	xskq_cons_release(pool->fq);
52462306a36Sopenharmony_ci	return xskb;
52562306a36Sopenharmony_ci}
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_cistruct xdp_buff *xp_alloc(struct xsk_buff_pool *pool)
52862306a36Sopenharmony_ci{
52962306a36Sopenharmony_ci	struct xdp_buff_xsk *xskb;
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	if (!pool->free_list_cnt) {
53262306a36Sopenharmony_ci		xskb = __xp_alloc(pool);
53362306a36Sopenharmony_ci		if (!xskb)
53462306a36Sopenharmony_ci			return NULL;
53562306a36Sopenharmony_ci	} else {
53662306a36Sopenharmony_ci		pool->free_list_cnt--;
53762306a36Sopenharmony_ci		xskb = list_first_entry(&pool->free_list, struct xdp_buff_xsk,
53862306a36Sopenharmony_ci					free_list_node);
53962306a36Sopenharmony_ci		list_del_init(&xskb->free_list_node);
54062306a36Sopenharmony_ci	}
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ci	xskb->xdp.data = xskb->xdp.data_hard_start + XDP_PACKET_HEADROOM;
54362306a36Sopenharmony_ci	xskb->xdp.data_meta = xskb->xdp.data;
54462306a36Sopenharmony_ci	xskb->xdp.flags = 0;
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci	if (pool->dma_need_sync) {
54762306a36Sopenharmony_ci		dma_sync_single_range_for_device(pool->dev, xskb->dma, 0,
54862306a36Sopenharmony_ci						 pool->frame_len,
54962306a36Sopenharmony_ci						 DMA_BIDIRECTIONAL);
55062306a36Sopenharmony_ci	}
55162306a36Sopenharmony_ci	return &xskb->xdp;
55262306a36Sopenharmony_ci}
55362306a36Sopenharmony_ciEXPORT_SYMBOL(xp_alloc);
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_cistatic u32 xp_alloc_new_from_fq(struct xsk_buff_pool *pool, struct xdp_buff **xdp, u32 max)
55662306a36Sopenharmony_ci{
55762306a36Sopenharmony_ci	u32 i, cached_cons, nb_entries;
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	if (max > pool->free_heads_cnt)
56062306a36Sopenharmony_ci		max = pool->free_heads_cnt;
56162306a36Sopenharmony_ci	max = xskq_cons_nb_entries(pool->fq, max);
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	cached_cons = pool->fq->cached_cons;
56462306a36Sopenharmony_ci	nb_entries = max;
56562306a36Sopenharmony_ci	i = max;
56662306a36Sopenharmony_ci	while (i--) {
56762306a36Sopenharmony_ci		struct xdp_buff_xsk *xskb;
56862306a36Sopenharmony_ci		u64 addr;
56962306a36Sopenharmony_ci		bool ok;
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci		__xskq_cons_read_addr_unchecked(pool->fq, cached_cons++, &addr);
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci		ok = pool->unaligned ? xp_check_unaligned(pool, &addr) :
57462306a36Sopenharmony_ci			xp_check_aligned(pool, &addr);
57562306a36Sopenharmony_ci		if (unlikely(!ok)) {
57662306a36Sopenharmony_ci			pool->fq->invalid_descs++;
57762306a36Sopenharmony_ci			nb_entries--;
57862306a36Sopenharmony_ci			continue;
57962306a36Sopenharmony_ci		}
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ci		if (pool->unaligned) {
58262306a36Sopenharmony_ci			xskb = pool->free_heads[--pool->free_heads_cnt];
58362306a36Sopenharmony_ci			xp_init_xskb_addr(xskb, pool, addr);
58462306a36Sopenharmony_ci			if (pool->dma_pages)
58562306a36Sopenharmony_ci				xp_init_xskb_dma(xskb, pool, pool->dma_pages, addr);
58662306a36Sopenharmony_ci		} else {
58762306a36Sopenharmony_ci			xskb = &pool->heads[xp_aligned_extract_idx(pool, addr)];
58862306a36Sopenharmony_ci		}
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci		*xdp = &xskb->xdp;
59162306a36Sopenharmony_ci		xdp++;
59262306a36Sopenharmony_ci	}
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	xskq_cons_release_n(pool->fq, max);
59562306a36Sopenharmony_ci	return nb_entries;
59662306a36Sopenharmony_ci}
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_cistatic u32 xp_alloc_reused(struct xsk_buff_pool *pool, struct xdp_buff **xdp, u32 nb_entries)
59962306a36Sopenharmony_ci{
60062306a36Sopenharmony_ci	struct xdp_buff_xsk *xskb;
60162306a36Sopenharmony_ci	u32 i;
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci	nb_entries = min_t(u32, nb_entries, pool->free_list_cnt);
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	i = nb_entries;
60662306a36Sopenharmony_ci	while (i--) {
60762306a36Sopenharmony_ci		xskb = list_first_entry(&pool->free_list, struct xdp_buff_xsk, free_list_node);
60862306a36Sopenharmony_ci		list_del_init(&xskb->free_list_node);
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci		*xdp = &xskb->xdp;
61162306a36Sopenharmony_ci		xdp++;
61262306a36Sopenharmony_ci	}
61362306a36Sopenharmony_ci	pool->free_list_cnt -= nb_entries;
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci	return nb_entries;
61662306a36Sopenharmony_ci}
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ciu32 xp_alloc_batch(struct xsk_buff_pool *pool, struct xdp_buff **xdp, u32 max)
61962306a36Sopenharmony_ci{
62062306a36Sopenharmony_ci	u32 nb_entries1 = 0, nb_entries2;
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_ci	if (unlikely(pool->dma_need_sync)) {
62362306a36Sopenharmony_ci		struct xdp_buff *buff;
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci		/* Slow path */
62662306a36Sopenharmony_ci		buff = xp_alloc(pool);
62762306a36Sopenharmony_ci		if (buff)
62862306a36Sopenharmony_ci			*xdp = buff;
62962306a36Sopenharmony_ci		return !!buff;
63062306a36Sopenharmony_ci	}
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci	if (unlikely(pool->free_list_cnt)) {
63362306a36Sopenharmony_ci		nb_entries1 = xp_alloc_reused(pool, xdp, max);
63462306a36Sopenharmony_ci		if (nb_entries1 == max)
63562306a36Sopenharmony_ci			return nb_entries1;
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci		max -= nb_entries1;
63862306a36Sopenharmony_ci		xdp += nb_entries1;
63962306a36Sopenharmony_ci	}
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	nb_entries2 = xp_alloc_new_from_fq(pool, xdp, max);
64262306a36Sopenharmony_ci	if (!nb_entries2)
64362306a36Sopenharmony_ci		pool->fq->queue_empty_descs++;
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	return nb_entries1 + nb_entries2;
64662306a36Sopenharmony_ci}
64762306a36Sopenharmony_ciEXPORT_SYMBOL(xp_alloc_batch);
64862306a36Sopenharmony_ci
64962306a36Sopenharmony_cibool xp_can_alloc(struct xsk_buff_pool *pool, u32 count)
65062306a36Sopenharmony_ci{
65162306a36Sopenharmony_ci	if (pool->free_list_cnt >= count)
65262306a36Sopenharmony_ci		return true;
65362306a36Sopenharmony_ci	return xskq_cons_has_entries(pool->fq, count - pool->free_list_cnt);
65462306a36Sopenharmony_ci}
65562306a36Sopenharmony_ciEXPORT_SYMBOL(xp_can_alloc);
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_civoid xp_free(struct xdp_buff_xsk *xskb)
65862306a36Sopenharmony_ci{
65962306a36Sopenharmony_ci	if (!list_empty(&xskb->free_list_node))
66062306a36Sopenharmony_ci		return;
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_ci	xskb->pool->free_list_cnt++;
66362306a36Sopenharmony_ci	list_add(&xskb->free_list_node, &xskb->pool->free_list);
66462306a36Sopenharmony_ci}
66562306a36Sopenharmony_ciEXPORT_SYMBOL(xp_free);
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_civoid *xp_raw_get_data(struct xsk_buff_pool *pool, u64 addr)
66862306a36Sopenharmony_ci{
66962306a36Sopenharmony_ci	addr = pool->unaligned ? xp_unaligned_add_offset_to_addr(addr) : addr;
67062306a36Sopenharmony_ci	return pool->addrs + addr;
67162306a36Sopenharmony_ci}
67262306a36Sopenharmony_ciEXPORT_SYMBOL(xp_raw_get_data);
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_cidma_addr_t xp_raw_get_dma(struct xsk_buff_pool *pool, u64 addr)
67562306a36Sopenharmony_ci{
67662306a36Sopenharmony_ci	addr = pool->unaligned ? xp_unaligned_add_offset_to_addr(addr) : addr;
67762306a36Sopenharmony_ci	return (pool->dma_pages[addr >> PAGE_SHIFT] &
67862306a36Sopenharmony_ci		~XSK_NEXT_PG_CONTIG_MASK) +
67962306a36Sopenharmony_ci		(addr & ~PAGE_MASK);
68062306a36Sopenharmony_ci}
68162306a36Sopenharmony_ciEXPORT_SYMBOL(xp_raw_get_dma);
68262306a36Sopenharmony_ci
68362306a36Sopenharmony_civoid xp_dma_sync_for_cpu_slow(struct xdp_buff_xsk *xskb)
68462306a36Sopenharmony_ci{
68562306a36Sopenharmony_ci	dma_sync_single_range_for_cpu(xskb->pool->dev, xskb->dma, 0,
68662306a36Sopenharmony_ci				      xskb->pool->frame_len, DMA_BIDIRECTIONAL);
68762306a36Sopenharmony_ci}
68862306a36Sopenharmony_ciEXPORT_SYMBOL(xp_dma_sync_for_cpu_slow);
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_civoid xp_dma_sync_for_device_slow(struct xsk_buff_pool *pool, dma_addr_t dma,
69162306a36Sopenharmony_ci				 size_t size)
69262306a36Sopenharmony_ci{
69362306a36Sopenharmony_ci	dma_sync_single_range_for_device(pool->dev, dma, 0,
69462306a36Sopenharmony_ci					 size, DMA_BIDIRECTIONAL);
69562306a36Sopenharmony_ci}
69662306a36Sopenharmony_ciEXPORT_SYMBOL(xp_dma_sync_for_device_slow);
697