2020-05-20 22:20:53 +03:00
// SPDX-License-Identifier: GPL-2.0
# include <net/xsk_buff_pool.h>
# include <net/xdp_sock.h>
2020-08-28 11:26:17 +03:00
# include <net/xdp_sock_drv.h>
# include <linux/dma-direct.h>
# include <linux/dma-noncoherent.h>
# include <linux/swiotlb.h>
2020-05-20 22:20:53 +03:00
# include "xsk_queue.h"
2020-08-28 11:26:17 +03:00
# include "xdp_umem.h"
# include "xsk.h"
2020-05-20 22:20:53 +03:00
2020-08-28 11:26:20 +03:00
void xp_add_xsk ( struct xsk_buff_pool * pool , struct xdp_sock * xs )
{
unsigned long flags ;
if ( ! xs - > tx )
return ;
spin_lock_irqsave ( & pool - > xsk_tx_list_lock , flags ) ;
list_add_rcu ( & xs - > tx_list , & pool - > xsk_tx_list ) ;
spin_unlock_irqrestore ( & pool - > xsk_tx_list_lock , flags ) ;
}
void xp_del_xsk ( struct xsk_buff_pool * pool , struct xdp_sock * xs )
{
unsigned long flags ;
if ( ! xs - > tx )
return ;
spin_lock_irqsave ( & pool - > xsk_tx_list_lock , flags ) ;
list_del_rcu ( & xs - > tx_list ) ;
spin_unlock_irqrestore ( & pool - > xsk_tx_list_lock , flags ) ;
}
2020-05-20 22:20:53 +03:00
static void xp_addr_unmap ( struct xsk_buff_pool * pool )
{
vunmap ( pool - > addrs ) ;
}
static int xp_addr_map ( struct xsk_buff_pool * pool ,
struct page * * pages , u32 nr_pages )
{
pool - > addrs = vmap ( pages , nr_pages , VM_MAP , PAGE_KERNEL ) ;
if ( ! pool - > addrs )
return - ENOMEM ;
return 0 ;
}
void xp_destroy ( struct xsk_buff_pool * pool )
{
if ( ! pool )
return ;
xp_addr_unmap ( pool ) ;
kvfree ( pool - > heads ) ;
kvfree ( pool ) ;
}
2020-08-28 11:26:17 +03:00
struct xsk_buff_pool * xp_create_and_assign_umem ( struct xdp_sock * xs ,
struct xdp_umem * umem )
2020-05-20 22:20:53 +03:00
{
struct xsk_buff_pool * pool ;
struct xdp_buff_xsk * xskb ;
int err ;
u32 i ;
2020-08-28 11:26:17 +03:00
pool = kvzalloc ( struct_size ( pool , free_heads , umem - > chunks ) ,
GFP_KERNEL ) ;
2020-05-20 22:20:53 +03:00
if ( ! pool )
goto out ;
2020-08-28 11:26:17 +03:00
pool - > heads = kvcalloc ( umem - > chunks , sizeof ( * pool - > heads ) , GFP_KERNEL ) ;
2020-05-20 22:20:53 +03:00
if ( ! pool - > heads )
goto out ;
2020-08-28 11:26:17 +03:00
pool - > chunk_mask = ~ ( ( u64 ) umem - > chunk_size - 1 ) ;
pool - > addrs_cnt = umem - > size ;
pool - > heads_cnt = umem - > chunks ;
pool - > free_heads_cnt = umem - > chunks ;
pool - > headroom = umem - > headroom ;
pool - > chunk_size = umem - > chunk_size ;
pool - > unaligned = umem - > flags & XDP_UMEM_UNALIGNED_CHUNK_FLAG ;
pool - > frame_len = umem - > chunk_size - umem - > headroom -
XDP_PACKET_HEADROOM ;
2020-08-28 11:26:15 +03:00
pool - > umem = umem ;
2020-05-20 22:20:53 +03:00
INIT_LIST_HEAD ( & pool - > free_list ) ;
2020-08-28 11:26:20 +03:00
INIT_LIST_HEAD ( & pool - > xsk_tx_list ) ;
spin_lock_init ( & pool - > xsk_tx_list_lock ) ;
2020-08-28 11:26:17 +03:00
refcount_set ( & pool - > users , 1 ) ;
2020-05-20 22:20:53 +03:00
2020-08-28 11:26:18 +03:00
pool - > fq = xs - > fq_tmp ;
pool - > cq = xs - > cq_tmp ;
xs - > fq_tmp = NULL ;
xs - > cq_tmp = NULL ;
2020-05-20 22:20:53 +03:00
for ( i = 0 ; i < pool - > free_heads_cnt ; i + + ) {
xskb = & pool - > heads [ i ] ;
xskb - > pool = pool ;
2020-08-28 11:26:17 +03:00
xskb - > xdp . frame_sz = umem - > chunk_size - umem - > headroom ;
2020-05-20 22:20:53 +03:00
pool - > free_heads [ i ] = xskb ;
}
2020-08-28 11:26:15 +03:00
err = xp_addr_map ( pool , umem - > pgs , umem - > npgs ) ;
2020-05-20 22:20:53 +03:00
if ( ! err )
return pool ;
out :
xp_destroy ( pool ) ;
return NULL ;
}
void xp_set_rxq_info ( struct xsk_buff_pool * pool , struct xdp_rxq_info * rxq )
{
u32 i ;
for ( i = 0 ; i < pool - > heads_cnt ; i + + )
pool - > heads [ i ] . xdp . rxq = rxq ;
}
EXPORT_SYMBOL ( xp_set_rxq_info ) ;
2020-08-28 11:26:19 +03:00
int xp_assign_dev ( struct xsk_buff_pool * pool , struct net_device * netdev ,
2020-08-28 11:26:17 +03:00
u16 queue_id , u16 flags )
{
bool force_zc , force_copy ;
struct netdev_bpf bpf ;
int err = 0 ;
ASSERT_RTNL ( ) ;
force_zc = flags & XDP_ZEROCOPY ;
force_copy = flags & XDP_COPY ;
if ( force_zc & & force_copy )
return - EINVAL ;
2020-08-28 11:26:19 +03:00
if ( xsk_get_pool_from_qid ( netdev , queue_id ) )
2020-08-28 11:26:17 +03:00
return - EBUSY ;
2020-08-28 11:26:19 +03:00
err = xsk_reg_pool_at_qid ( netdev , pool , queue_id ) ;
2020-08-28 11:26:17 +03:00
if ( err )
return err ;
if ( flags & XDP_USE_NEED_WAKEUP ) {
2020-08-28 11:26:19 +03:00
pool - > uses_need_wakeup = true ;
2020-08-28 11:26:17 +03:00
/* Tx needs to be explicitly woken up the first time.
* Also for supporting drivers that do not implement this
* feature . They will always have to call sendto ( ) .
*/
2020-08-28 11:26:19 +03:00
pool - > cached_need_wakeup = XDP_WAKEUP_TX ;
2020-08-28 11:26:17 +03:00
}
2020-08-28 11:26:19 +03:00
dev_hold ( netdev ) ;
2020-08-28 11:26:17 +03:00
if ( force_copy )
/* For copy-mode, we are done. */
return 0 ;
2020-08-28 11:26:19 +03:00
if ( ! netdev - > netdev_ops - > ndo_bpf | |
! netdev - > netdev_ops - > ndo_xsk_wakeup ) {
2020-08-28 11:26:17 +03:00
err = - EOPNOTSUPP ;
goto err_unreg_pool ;
}
bpf . command = XDP_SETUP_XSK_POOL ;
bpf . xsk . pool = pool ;
bpf . xsk . queue_id = queue_id ;
2020-08-28 11:26:19 +03:00
err = netdev - > netdev_ops - > ndo_bpf ( netdev , & bpf ) ;
2020-08-28 11:26:17 +03:00
if ( err )
goto err_unreg_pool ;
2020-08-28 11:26:19 +03:00
pool - > netdev = netdev ;
pool - > queue_id = queue_id ;
pool - > umem - > zc = true ;
2020-08-28 11:26:17 +03:00
return 0 ;
err_unreg_pool :
if ( ! force_zc )
err = 0 ; /* fallback to copy mode */
if ( err )
2020-08-28 11:26:19 +03:00
xsk_clear_pool_at_qid ( netdev , queue_id ) ;
2020-08-28 11:26:17 +03:00
return err ;
}
void xp_clear_dev ( struct xsk_buff_pool * pool )
{
struct netdev_bpf bpf ;
int err ;
ASSERT_RTNL ( ) ;
2020-08-28 11:26:19 +03:00
if ( ! pool - > netdev )
2020-08-28 11:26:17 +03:00
return ;
2020-08-28 11:26:19 +03:00
if ( pool - > umem - > zc ) {
2020-08-28 11:26:17 +03:00
bpf . command = XDP_SETUP_XSK_POOL ;
bpf . xsk . pool = NULL ;
2020-08-28 11:26:19 +03:00
bpf . xsk . queue_id = pool - > queue_id ;
2020-08-28 11:26:17 +03:00
2020-08-28 11:26:19 +03:00
err = pool - > netdev - > netdev_ops - > ndo_bpf ( pool - > netdev , & bpf ) ;
2020-08-28 11:26:17 +03:00
if ( err )
2020-08-28 11:26:19 +03:00
WARN ( 1 , " Failed to disable zero-copy! \n " ) ;
2020-08-28 11:26:17 +03:00
}
2020-08-28 11:26:19 +03:00
xsk_clear_pool_at_qid ( pool - > netdev , pool - > queue_id ) ;
dev_put ( pool - > netdev ) ;
pool - > netdev = NULL ;
2020-08-28 11:26:17 +03:00
}
static void xp_release_deferred ( struct work_struct * work )
{
struct xsk_buff_pool * pool = container_of ( work , struct xsk_buff_pool ,
work ) ;
rtnl_lock ( ) ;
xp_clear_dev ( pool ) ;
rtnl_unlock ( ) ;
2020-08-28 11:26:18 +03:00
if ( pool - > fq ) {
xskq_destroy ( pool - > fq ) ;
pool - > fq = NULL ;
}
if ( pool - > cq ) {
xskq_destroy ( pool - > cq ) ;
pool - > cq = NULL ;
}
2020-08-28 11:26:17 +03:00
xdp_put_umem ( pool - > umem ) ;
xp_destroy ( pool ) ;
}
void xp_get_pool ( struct xsk_buff_pool * pool )
{
refcount_inc ( & pool - > users ) ;
}
void xp_put_pool ( struct xsk_buff_pool * pool )
{
if ( ! pool )
return ;
if ( refcount_dec_and_test ( & pool - > users ) ) {
INIT_WORK ( & pool - > work , xp_release_deferred ) ;
schedule_work ( & pool - > work ) ;
}
}
2020-05-20 22:20:53 +03:00
void xp_dma_unmap ( struct xsk_buff_pool * pool , unsigned long attrs )
{
dma_addr_t * dma ;
u32 i ;
if ( pool - > dma_pages_cnt = = 0 )
return ;
for ( i = 0 ; i < pool - > dma_pages_cnt ; i + + ) {
dma = & pool - > dma_pages [ i ] ;
if ( * dma ) {
dma_unmap_page_attrs ( pool - > dev , * dma , PAGE_SIZE ,
DMA_BIDIRECTIONAL , attrs ) ;
* dma = 0 ;
}
}
kvfree ( pool - > dma_pages ) ;
pool - > dma_pages_cnt = 0 ;
pool - > dev = NULL ;
}
EXPORT_SYMBOL ( xp_dma_unmap ) ;
static void xp_check_dma_contiguity ( struct xsk_buff_pool * pool )
{
u32 i ;
for ( i = 0 ; i < pool - > dma_pages_cnt - 1 ; i + + ) {
if ( pool - > dma_pages [ i ] + PAGE_SIZE = = pool - > dma_pages [ i + 1 ] )
pool - > dma_pages [ i ] | = XSK_NEXT_PG_CONTIG_MASK ;
else
pool - > dma_pages [ i ] & = ~ XSK_NEXT_PG_CONTIG_MASK ;
}
}
int xp_dma_map ( struct xsk_buff_pool * pool , struct device * dev ,
unsigned long attrs , struct page * * pages , u32 nr_pages )
{
dma_addr_t dma ;
u32 i ;
pool - > dma_pages = kvcalloc ( nr_pages , sizeof ( * pool - > dma_pages ) ,
GFP_KERNEL ) ;
if ( ! pool - > dma_pages )
return - ENOMEM ;
pool - > dev = dev ;
pool - > dma_pages_cnt = nr_pages ;
2020-06-29 16:03:59 +03:00
pool - > dma_need_sync = false ;
2020-05-20 22:20:53 +03:00
for ( i = 0 ; i < pool - > dma_pages_cnt ; i + + ) {
dma = dma_map_page_attrs ( dev , pages [ i ] , 0 , PAGE_SIZE ,
DMA_BIDIRECTIONAL , attrs ) ;
if ( dma_mapping_error ( dev , dma ) ) {
xp_dma_unmap ( pool , attrs ) ;
return - ENOMEM ;
}
2020-06-29 16:03:59 +03:00
if ( dma_need_sync ( dev , dma ) )
pool - > dma_need_sync = true ;
2020-05-20 22:20:53 +03:00
pool - > dma_pages [ i ] = dma ;
}
if ( pool - > unaligned )
xp_check_dma_contiguity ( pool ) ;
return 0 ;
}
EXPORT_SYMBOL ( xp_dma_map ) ;
static bool xp_addr_crosses_non_contig_pg ( struct xsk_buff_pool * pool ,
u64 addr )
{
return xp_desc_crosses_non_contig_pg ( pool , addr , pool - > chunk_size ) ;
}
static bool xp_check_unaligned ( struct xsk_buff_pool * pool , u64 * addr )
{
* addr = xp_unaligned_extract_addr ( * addr ) ;
if ( * addr > = pool - > addrs_cnt | |
* addr + pool - > chunk_size > pool - > addrs_cnt | |
xp_addr_crosses_non_contig_pg ( pool , * addr ) )
return false ;
return true ;
}
static bool xp_check_aligned ( struct xsk_buff_pool * pool , u64 * addr )
{
* addr = xp_aligned_extract_addr ( pool , * addr ) ;
return * addr < pool - > addrs_cnt ;
}
static struct xdp_buff_xsk * __xp_alloc ( struct xsk_buff_pool * pool )
{
struct xdp_buff_xsk * xskb ;
u64 addr ;
bool ok ;
if ( pool - > free_heads_cnt = = 0 )
return NULL ;
xskb = pool - > free_heads [ - - pool - > free_heads_cnt ] ;
for ( ; ; ) {
if ( ! xskq_cons_peek_addr_unchecked ( pool - > fq , & addr ) ) {
2020-07-08 10:28:33 +03:00
pool - > fq - > queue_empty_descs + + ;
2020-05-20 22:20:53 +03:00
xp_release ( xskb ) ;
return NULL ;
}
ok = pool - > unaligned ? xp_check_unaligned ( pool , & addr ) :
xp_check_aligned ( pool , & addr ) ;
if ( ! ok ) {
pool - > fq - > invalid_descs + + ;
xskq_cons_release ( pool - > fq ) ;
continue ;
}
break ;
}
xskq_cons_release ( pool - > fq ) ;
xskb - > orig_addr = addr ;
xskb - > xdp . data_hard_start = pool - > addrs + addr + pool - > headroom ;
if ( pool - > dma_pages_cnt ) {
xskb - > frame_dma = ( pool - > dma_pages [ addr > > PAGE_SHIFT ] &
~ XSK_NEXT_PG_CONTIG_MASK ) +
( addr & ~ PAGE_MASK ) ;
xskb - > dma = xskb - > frame_dma + pool - > headroom +
XDP_PACKET_HEADROOM ;
}
return xskb ;
}
struct xdp_buff * xp_alloc ( struct xsk_buff_pool * pool )
{
struct xdp_buff_xsk * xskb ;
if ( ! pool - > free_list_cnt ) {
xskb = __xp_alloc ( pool ) ;
if ( ! xskb )
return NULL ;
} else {
pool - > free_list_cnt - - ;
xskb = list_first_entry ( & pool - > free_list , struct xdp_buff_xsk ,
free_list_node ) ;
list_del ( & xskb - > free_list_node ) ;
}
xskb - > xdp . data = xskb - > xdp . data_hard_start + XDP_PACKET_HEADROOM ;
xskb - > xdp . data_meta = xskb - > xdp . data ;
2020-06-29 16:03:57 +03:00
if ( pool - > dma_need_sync ) {
2020-05-20 22:20:53 +03:00
dma_sync_single_range_for_device ( pool - > dev , xskb - > dma , 0 ,
pool - > frame_len ,
DMA_BIDIRECTIONAL ) ;
}
return & xskb - > xdp ;
}
EXPORT_SYMBOL ( xp_alloc ) ;
bool xp_can_alloc ( struct xsk_buff_pool * pool , u32 count )
{
if ( pool - > free_list_cnt > = count )
return true ;
return xskq_cons_has_entries ( pool - > fq , count - pool - > free_list_cnt ) ;
}
EXPORT_SYMBOL ( xp_can_alloc ) ;
void xp_free ( struct xdp_buff_xsk * xskb )
{
xskb - > pool - > free_list_cnt + + ;
list_add ( & xskb - > free_list_node , & xskb - > pool - > free_list ) ;
}
EXPORT_SYMBOL ( xp_free ) ;
void * xp_raw_get_data ( struct xsk_buff_pool * pool , u64 addr )
{
addr = pool - > unaligned ? xp_unaligned_add_offset_to_addr ( addr ) : addr ;
return pool - > addrs + addr ;
}
EXPORT_SYMBOL ( xp_raw_get_data ) ;
dma_addr_t xp_raw_get_dma ( struct xsk_buff_pool * pool , u64 addr )
{
addr = pool - > unaligned ? xp_unaligned_add_offset_to_addr ( addr ) : addr ;
return ( pool - > dma_pages [ addr > > PAGE_SHIFT ] &
~ XSK_NEXT_PG_CONTIG_MASK ) +
( addr & ~ PAGE_MASK ) ;
}
EXPORT_SYMBOL ( xp_raw_get_dma ) ;
2020-05-20 22:21:02 +03:00
void xp_dma_sync_for_cpu_slow ( struct xdp_buff_xsk * xskb )
2020-05-20 22:20:53 +03:00
{
dma_sync_single_range_for_cpu ( xskb - > pool - > dev , xskb - > dma , 0 ,
xskb - > pool - > frame_len , DMA_BIDIRECTIONAL ) ;
}
2020-05-20 22:21:02 +03:00
EXPORT_SYMBOL ( xp_dma_sync_for_cpu_slow ) ;
2020-05-20 22:20:53 +03:00
2020-05-20 22:21:02 +03:00
void xp_dma_sync_for_device_slow ( struct xsk_buff_pool * pool , dma_addr_t dma ,
size_t size )
2020-05-20 22:20:53 +03:00
{
dma_sync_single_range_for_device ( pool - > dev , dma , 0 ,
size , DMA_BIDIRECTIONAL ) ;
}
2020-05-20 22:21:02 +03:00
EXPORT_SYMBOL ( xp_dma_sync_for_device_slow ) ;