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