virtnet_xsk_pool_enable() DMA maps the static xsk_hdr, which lives in
the kernel image. On arm64 the kernel image is mapped in the vmalloc
area, so when the device uses the DMA API the mapping is rejected:

  virtio_net virtio0: rejecting DMA map of vmalloc memory
  WARNING: drivers/virtio/virtio_ring.c:3882 ...

and binding an AF_XDP zero-copy socket fails with -ENOMEM. Even where
the mapping succeeds, the header shares cache lines with unrelated data.

Allocate the all-zero header per send queue when the pool is enabled
and free it when it is disabled.

Fixes: 21a4e3ce6dc7 ("virtio_net: xsk: bind/unbind xsk for tx")
Signed-off-by: Haishuang Yan <[email protected]>
---
 drivers/net/virtio_net.c | 23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index dd2c41ef6fbe..4f5d8812f75b 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -315,6 +315,8 @@ struct send_queue {
 
        struct xsk_buff_pool *xsk_pool;
 
+       /* All-zero header shared by the AF_XDP packets sent on this queue */
+       struct virtio_net_common_hdr *xsk_hdr;
        dma_addr_t xsk_hdr_dma_addr;
 };
 
@@ -500,8 +502,6 @@ struct virtio_net_common_hdr {
        };
 };
 
-static struct virtio_net_common_hdr xsk_hdr;
-
 static void virtnet_sq_free_unused_buf(struct virtqueue *vq, void *buf);
 static void virtnet_sq_free_unused_buf_done(struct virtqueue *vq);
 static int virtnet_xdp_handler(struct bpf_prog *xdp_prog, struct xdp_buff *xdp,
@@ -5971,11 +5971,21 @@ static int virtnet_xsk_pool_enable(struct net_device 
*dev,
        if (!rq->xsk_buffs)
                return -ENOMEM;
 
-       hdr_dma = virtqueue_map_single_attrs(sq->vq, &xsk_hdr, vi->hdr_len,
+       /*
+        * Don't map a static header: the kernel image is not in the linear
+        * map on all architectures (e.g. arm64), so it can't be DMA mapped.
+        */
+       sq->xsk_hdr = kzalloc_obj(*sq->xsk_hdr);
+       if (!sq->xsk_hdr) {
+               err = -ENOMEM;
+               goto err_free_buffs;
+       }
+
+       hdr_dma = virtqueue_map_single_attrs(sq->vq, sq->xsk_hdr, vi->hdr_len,
                                             DMA_TO_DEVICE, 0);
        if (virtqueue_map_mapping_error(sq->vq, hdr_dma)) {
                err = -ENOMEM;
-               goto err_free_buffs;
+               goto err_free_hdr;
        }
 
        err = xsk_pool_dma_map(pool, dma_dev, 0);
@@ -6004,6 +6014,9 @@ static int virtnet_xsk_pool_enable(struct net_device *dev,
 err_xsk_map:
        virtqueue_unmap_single_attrs(rq->vq, hdr_dma, vi->hdr_len,
                                     DMA_TO_DEVICE, 0);
+err_free_hdr:
+       kfree(sq->xsk_hdr);
+       sq->xsk_hdr = NULL;
 err_free_buffs:
        kvfree(rq->xsk_buffs);
        return err;
@@ -6032,6 +6045,8 @@ static int virtnet_xsk_pool_disable(struct net_device 
*dev, u16 qid)
 
        virtqueue_unmap_single_attrs(sq->vq, sq->xsk_hdr_dma_addr,
                                     vi->hdr_len, DMA_TO_DEVICE, 0);
+       kfree(sq->xsk_hdr);
+       sq->xsk_hdr = NULL;
        kvfree(rq->xsk_buffs);
 
        return err;
-- 
2.43.0




Reply via email to