1c0c77d8fSBjörn Töpel // SPDX-License-Identifier: GPL-2.0 2c0c77d8fSBjörn Töpel /* XDP user-space packet buffer 3c0c77d8fSBjörn Töpel * Copyright(c) 2018 Intel Corporation. 4c0c77d8fSBjörn Töpel */ 5c0c77d8fSBjörn Töpel 6c0c77d8fSBjörn Töpel #include <linux/init.h> 7c0c77d8fSBjörn Töpel #include <linux/sched/mm.h> 8c0c77d8fSBjörn Töpel #include <linux/sched/signal.h> 9c0c77d8fSBjörn Töpel #include <linux/sched/task.h> 10c0c77d8fSBjörn Töpel #include <linux/uaccess.h> 11c0c77d8fSBjörn Töpel #include <linux/slab.h> 12c0c77d8fSBjörn Töpel #include <linux/bpf.h> 13c0c77d8fSBjörn Töpel #include <linux/mm.h> 14c0c77d8fSBjörn Töpel 15c0c77d8fSBjörn Töpel #include "xdp_umem.h" 16e61e62b9SBjörn Töpel #include "xsk_queue.h" 17c0c77d8fSBjörn Töpel 18bbff2f32SBjörn Töpel #define XDP_UMEM_MIN_CHUNK_SIZE 2048 19c0c77d8fSBjörn Töpel 20*173d3adbSBjörn Töpel int xdp_umem_assign_dev(struct xdp_umem *umem, struct net_device *dev, 21*173d3adbSBjörn Töpel u32 queue_id, u16 flags) 22*173d3adbSBjörn Töpel { 23*173d3adbSBjörn Töpel bool force_zc, force_copy; 24*173d3adbSBjörn Töpel struct netdev_bpf bpf; 25*173d3adbSBjörn Töpel int err; 26*173d3adbSBjörn Töpel 27*173d3adbSBjörn Töpel force_zc = flags & XDP_ZEROCOPY; 28*173d3adbSBjörn Töpel force_copy = flags & XDP_COPY; 29*173d3adbSBjörn Töpel 30*173d3adbSBjörn Töpel if (force_zc && force_copy) 31*173d3adbSBjörn Töpel return -EINVAL; 32*173d3adbSBjörn Töpel 33*173d3adbSBjörn Töpel if (force_copy) 34*173d3adbSBjörn Töpel return 0; 35*173d3adbSBjörn Töpel 36*173d3adbSBjörn Töpel dev_hold(dev); 37*173d3adbSBjörn Töpel 38*173d3adbSBjörn Töpel if (dev->netdev_ops->ndo_bpf) { 39*173d3adbSBjörn Töpel bpf.command = XDP_QUERY_XSK_UMEM; 40*173d3adbSBjörn Töpel 41*173d3adbSBjörn Töpel rtnl_lock(); 42*173d3adbSBjörn Töpel err = dev->netdev_ops->ndo_bpf(dev, &bpf); 43*173d3adbSBjörn Töpel rtnl_unlock(); 44*173d3adbSBjörn Töpel 45*173d3adbSBjörn Töpel if (err) { 46*173d3adbSBjörn Töpel dev_put(dev); 47*173d3adbSBjörn Töpel return force_zc ? -ENOTSUPP : 0; 48*173d3adbSBjörn Töpel } 49*173d3adbSBjörn Töpel 50*173d3adbSBjörn Töpel bpf.command = XDP_SETUP_XSK_UMEM; 51*173d3adbSBjörn Töpel bpf.xsk.umem = umem; 52*173d3adbSBjörn Töpel bpf.xsk.queue_id = queue_id; 53*173d3adbSBjörn Töpel 54*173d3adbSBjörn Töpel rtnl_lock(); 55*173d3adbSBjörn Töpel err = dev->netdev_ops->ndo_bpf(dev, &bpf); 56*173d3adbSBjörn Töpel rtnl_unlock(); 57*173d3adbSBjörn Töpel 58*173d3adbSBjörn Töpel if (err) { 59*173d3adbSBjörn Töpel dev_put(dev); 60*173d3adbSBjörn Töpel return force_zc ? err : 0; /* fail or fallback */ 61*173d3adbSBjörn Töpel } 62*173d3adbSBjörn Töpel 63*173d3adbSBjörn Töpel umem->dev = dev; 64*173d3adbSBjörn Töpel umem->queue_id = queue_id; 65*173d3adbSBjörn Töpel umem->zc = true; 66*173d3adbSBjörn Töpel return 0; 67*173d3adbSBjörn Töpel } 68*173d3adbSBjörn Töpel 69*173d3adbSBjörn Töpel dev_put(dev); 70*173d3adbSBjörn Töpel return force_zc ? -ENOTSUPP : 0; /* fail or fallback */ 71*173d3adbSBjörn Töpel } 72*173d3adbSBjörn Töpel 73*173d3adbSBjörn Töpel void xdp_umem_clear_dev(struct xdp_umem *umem) 74*173d3adbSBjörn Töpel { 75*173d3adbSBjörn Töpel struct netdev_bpf bpf; 76*173d3adbSBjörn Töpel int err; 77*173d3adbSBjörn Töpel 78*173d3adbSBjörn Töpel if (umem->dev) { 79*173d3adbSBjörn Töpel bpf.command = XDP_SETUP_XSK_UMEM; 80*173d3adbSBjörn Töpel bpf.xsk.umem = NULL; 81*173d3adbSBjörn Töpel bpf.xsk.queue_id = umem->queue_id; 82*173d3adbSBjörn Töpel 83*173d3adbSBjörn Töpel rtnl_lock(); 84*173d3adbSBjörn Töpel err = umem->dev->netdev_ops->ndo_bpf(umem->dev, &bpf); 85*173d3adbSBjörn Töpel rtnl_unlock(); 86*173d3adbSBjörn Töpel 87*173d3adbSBjörn Töpel if (err) 88*173d3adbSBjörn Töpel WARN(1, "failed to disable umem!\n"); 89*173d3adbSBjörn Töpel 90*173d3adbSBjörn Töpel dev_put(umem->dev); 91*173d3adbSBjörn Töpel umem->dev = NULL; 92*173d3adbSBjörn Töpel } 93*173d3adbSBjörn Töpel } 94*173d3adbSBjörn Töpel 95c0c77d8fSBjörn Töpel static void xdp_umem_unpin_pages(struct xdp_umem *umem) 96c0c77d8fSBjörn Töpel { 97c0c77d8fSBjörn Töpel unsigned int i; 98c0c77d8fSBjörn Töpel 99c0c77d8fSBjörn Töpel for (i = 0; i < umem->npgs; i++) { 100c0c77d8fSBjörn Töpel struct page *page = umem->pgs[i]; 101c0c77d8fSBjörn Töpel 102c0c77d8fSBjörn Töpel set_page_dirty_lock(page); 103c0c77d8fSBjörn Töpel put_page(page); 104c0c77d8fSBjörn Töpel } 105c0c77d8fSBjörn Töpel 106c0c77d8fSBjörn Töpel kfree(umem->pgs); 107c0c77d8fSBjörn Töpel umem->pgs = NULL; 108c0c77d8fSBjörn Töpel } 109c0c77d8fSBjörn Töpel 110c0c77d8fSBjörn Töpel static void xdp_umem_unaccount_pages(struct xdp_umem *umem) 111c0c77d8fSBjörn Töpel { 112c0c77d8fSBjörn Töpel atomic_long_sub(umem->npgs, &umem->user->locked_vm); 113c0c77d8fSBjörn Töpel free_uid(umem->user); 114c0c77d8fSBjörn Töpel } 115c0c77d8fSBjörn Töpel 116c0c77d8fSBjörn Töpel static void xdp_umem_release(struct xdp_umem *umem) 117c0c77d8fSBjörn Töpel { 118c0c77d8fSBjörn Töpel struct task_struct *task; 119c0c77d8fSBjörn Töpel struct mm_struct *mm; 120c0c77d8fSBjörn Töpel 121*173d3adbSBjörn Töpel xdp_umem_clear_dev(umem); 122*173d3adbSBjörn Töpel 123423f3832SMagnus Karlsson if (umem->fq) { 124423f3832SMagnus Karlsson xskq_destroy(umem->fq); 125423f3832SMagnus Karlsson umem->fq = NULL; 126423f3832SMagnus Karlsson } 127423f3832SMagnus Karlsson 128fe230832SMagnus Karlsson if (umem->cq) { 129fe230832SMagnus Karlsson xskq_destroy(umem->cq); 130fe230832SMagnus Karlsson umem->cq = NULL; 131fe230832SMagnus Karlsson } 132fe230832SMagnus Karlsson 133c0c77d8fSBjörn Töpel xdp_umem_unpin_pages(umem); 134c0c77d8fSBjörn Töpel 135c0c77d8fSBjörn Töpel task = get_pid_task(umem->pid, PIDTYPE_PID); 136c0c77d8fSBjörn Töpel put_pid(umem->pid); 137c0c77d8fSBjörn Töpel if (!task) 138c0c77d8fSBjörn Töpel goto out; 139c0c77d8fSBjörn Töpel mm = get_task_mm(task); 140c0c77d8fSBjörn Töpel put_task_struct(task); 141c0c77d8fSBjörn Töpel if (!mm) 142c0c77d8fSBjörn Töpel goto out; 143c0c77d8fSBjörn Töpel 144c0c77d8fSBjörn Töpel mmput(mm); 1458aef7340SBjörn Töpel kfree(umem->pages); 1468aef7340SBjörn Töpel umem->pages = NULL; 1478aef7340SBjörn Töpel 148c0c77d8fSBjörn Töpel xdp_umem_unaccount_pages(umem); 149c0c77d8fSBjörn Töpel out: 150c0c77d8fSBjörn Töpel kfree(umem); 151c0c77d8fSBjörn Töpel } 152c0c77d8fSBjörn Töpel 153c0c77d8fSBjörn Töpel static void xdp_umem_release_deferred(struct work_struct *work) 154c0c77d8fSBjörn Töpel { 155c0c77d8fSBjörn Töpel struct xdp_umem *umem = container_of(work, struct xdp_umem, work); 156c0c77d8fSBjörn Töpel 157c0c77d8fSBjörn Töpel xdp_umem_release(umem); 158c0c77d8fSBjörn Töpel } 159c0c77d8fSBjörn Töpel 160c0c77d8fSBjörn Töpel void xdp_get_umem(struct xdp_umem *umem) 161c0c77d8fSBjörn Töpel { 162d3b42f14SBjörn Töpel refcount_inc(&umem->users); 163c0c77d8fSBjörn Töpel } 164c0c77d8fSBjörn Töpel 165c0c77d8fSBjörn Töpel void xdp_put_umem(struct xdp_umem *umem) 166c0c77d8fSBjörn Töpel { 167c0c77d8fSBjörn Töpel if (!umem) 168c0c77d8fSBjörn Töpel return; 169c0c77d8fSBjörn Töpel 170d3b42f14SBjörn Töpel if (refcount_dec_and_test(&umem->users)) { 171c0c77d8fSBjörn Töpel INIT_WORK(&umem->work, xdp_umem_release_deferred); 172c0c77d8fSBjörn Töpel schedule_work(&umem->work); 173c0c77d8fSBjörn Töpel } 174c0c77d8fSBjörn Töpel } 175c0c77d8fSBjörn Töpel 176c0c77d8fSBjörn Töpel static int xdp_umem_pin_pages(struct xdp_umem *umem) 177c0c77d8fSBjörn Töpel { 178c0c77d8fSBjörn Töpel unsigned int gup_flags = FOLL_WRITE; 179c0c77d8fSBjörn Töpel long npgs; 180c0c77d8fSBjörn Töpel int err; 181c0c77d8fSBjörn Töpel 182c0c77d8fSBjörn Töpel umem->pgs = kcalloc(umem->npgs, sizeof(*umem->pgs), GFP_KERNEL); 183c0c77d8fSBjörn Töpel if (!umem->pgs) 184c0c77d8fSBjörn Töpel return -ENOMEM; 185c0c77d8fSBjörn Töpel 186c0c77d8fSBjörn Töpel down_write(¤t->mm->mmap_sem); 187c0c77d8fSBjörn Töpel npgs = get_user_pages(umem->address, umem->npgs, 188c0c77d8fSBjörn Töpel gup_flags, &umem->pgs[0], NULL); 189c0c77d8fSBjörn Töpel up_write(¤t->mm->mmap_sem); 190c0c77d8fSBjörn Töpel 191c0c77d8fSBjörn Töpel if (npgs != umem->npgs) { 192c0c77d8fSBjörn Töpel if (npgs >= 0) { 193c0c77d8fSBjörn Töpel umem->npgs = npgs; 194c0c77d8fSBjörn Töpel err = -ENOMEM; 195c0c77d8fSBjörn Töpel goto out_pin; 196c0c77d8fSBjörn Töpel } 197c0c77d8fSBjörn Töpel err = npgs; 198c0c77d8fSBjörn Töpel goto out_pgs; 199c0c77d8fSBjörn Töpel } 200c0c77d8fSBjörn Töpel return 0; 201c0c77d8fSBjörn Töpel 202c0c77d8fSBjörn Töpel out_pin: 203c0c77d8fSBjörn Töpel xdp_umem_unpin_pages(umem); 204c0c77d8fSBjörn Töpel out_pgs: 205c0c77d8fSBjörn Töpel kfree(umem->pgs); 206c0c77d8fSBjörn Töpel umem->pgs = NULL; 207c0c77d8fSBjörn Töpel return err; 208c0c77d8fSBjörn Töpel } 209c0c77d8fSBjörn Töpel 210c0c77d8fSBjörn Töpel static int xdp_umem_account_pages(struct xdp_umem *umem) 211c0c77d8fSBjörn Töpel { 212c0c77d8fSBjörn Töpel unsigned long lock_limit, new_npgs, old_npgs; 213c0c77d8fSBjörn Töpel 214c0c77d8fSBjörn Töpel if (capable(CAP_IPC_LOCK)) 215c0c77d8fSBjörn Töpel return 0; 216c0c77d8fSBjörn Töpel 217c0c77d8fSBjörn Töpel lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; 218c0c77d8fSBjörn Töpel umem->user = get_uid(current_user()); 219c0c77d8fSBjörn Töpel 220c0c77d8fSBjörn Töpel do { 221c0c77d8fSBjörn Töpel old_npgs = atomic_long_read(&umem->user->locked_vm); 222c0c77d8fSBjörn Töpel new_npgs = old_npgs + umem->npgs; 223c0c77d8fSBjörn Töpel if (new_npgs > lock_limit) { 224c0c77d8fSBjörn Töpel free_uid(umem->user); 225c0c77d8fSBjörn Töpel umem->user = NULL; 226c0c77d8fSBjörn Töpel return -ENOBUFS; 227c0c77d8fSBjörn Töpel } 228c0c77d8fSBjörn Töpel } while (atomic_long_cmpxchg(&umem->user->locked_vm, old_npgs, 229c0c77d8fSBjörn Töpel new_npgs) != old_npgs); 230c0c77d8fSBjörn Töpel return 0; 231c0c77d8fSBjörn Töpel } 232c0c77d8fSBjörn Töpel 233a49049eaSBjörn Töpel static int xdp_umem_reg(struct xdp_umem *umem, struct xdp_umem_reg *mr) 234c0c77d8fSBjörn Töpel { 235bbff2f32SBjörn Töpel u32 chunk_size = mr->chunk_size, headroom = mr->headroom; 236bbff2f32SBjörn Töpel unsigned int chunks, chunks_per_page; 237c0c77d8fSBjörn Töpel u64 addr = mr->addr, size = mr->len; 2388aef7340SBjörn Töpel int size_chk, err, i; 239c0c77d8fSBjörn Töpel 240bbff2f32SBjörn Töpel if (chunk_size < XDP_UMEM_MIN_CHUNK_SIZE || chunk_size > PAGE_SIZE) { 241c0c77d8fSBjörn Töpel /* Strictly speaking we could support this, if: 242c0c77d8fSBjörn Töpel * - huge pages, or* 243c0c77d8fSBjörn Töpel * - using an IOMMU, or 244c0c77d8fSBjörn Töpel * - making sure the memory area is consecutive 245c0c77d8fSBjörn Töpel * but for now, we simply say "computer says no". 246c0c77d8fSBjörn Töpel */ 247c0c77d8fSBjörn Töpel return -EINVAL; 248c0c77d8fSBjörn Töpel } 249c0c77d8fSBjörn Töpel 250bbff2f32SBjörn Töpel if (!is_power_of_2(chunk_size)) 251c0c77d8fSBjörn Töpel return -EINVAL; 252c0c77d8fSBjörn Töpel 253c0c77d8fSBjörn Töpel if (!PAGE_ALIGNED(addr)) { 254c0c77d8fSBjörn Töpel /* Memory area has to be page size aligned. For 255c0c77d8fSBjörn Töpel * simplicity, this might change. 256c0c77d8fSBjörn Töpel */ 257c0c77d8fSBjörn Töpel return -EINVAL; 258c0c77d8fSBjörn Töpel } 259c0c77d8fSBjörn Töpel 260c0c77d8fSBjörn Töpel if ((addr + size) < addr) 261c0c77d8fSBjörn Töpel return -EINVAL; 262c0c77d8fSBjörn Töpel 263bbff2f32SBjörn Töpel chunks = (unsigned int)div_u64(size, chunk_size); 264bbff2f32SBjörn Töpel if (chunks == 0) 265c0c77d8fSBjörn Töpel return -EINVAL; 266c0c77d8fSBjörn Töpel 267bbff2f32SBjörn Töpel chunks_per_page = PAGE_SIZE / chunk_size; 268bbff2f32SBjörn Töpel if (chunks < chunks_per_page || chunks % chunks_per_page) 269c0c77d8fSBjörn Töpel return -EINVAL; 270c0c77d8fSBjörn Töpel 271bbff2f32SBjörn Töpel headroom = ALIGN(headroom, 64); 272c0c77d8fSBjörn Töpel 273bbff2f32SBjörn Töpel size_chk = chunk_size - headroom - XDP_PACKET_HEADROOM; 274c0c77d8fSBjörn Töpel if (size_chk < 0) 275c0c77d8fSBjörn Töpel return -EINVAL; 276c0c77d8fSBjörn Töpel 277c0c77d8fSBjörn Töpel umem->pid = get_task_pid(current, PIDTYPE_PID); 278c0c77d8fSBjörn Töpel umem->address = (unsigned long)addr; 279bbff2f32SBjörn Töpel umem->props.chunk_mask = ~((u64)chunk_size - 1); 280bbff2f32SBjörn Töpel umem->props.size = size; 281bbff2f32SBjörn Töpel umem->headroom = headroom; 282bbff2f32SBjörn Töpel umem->chunk_size_nohr = chunk_size - headroom; 283c0c77d8fSBjörn Töpel umem->npgs = size / PAGE_SIZE; 284c0c77d8fSBjörn Töpel umem->pgs = NULL; 285c0c77d8fSBjörn Töpel umem->user = NULL; 286c0c77d8fSBjörn Töpel 287d3b42f14SBjörn Töpel refcount_set(&umem->users, 1); 288c0c77d8fSBjörn Töpel 289c0c77d8fSBjörn Töpel err = xdp_umem_account_pages(umem); 290c0c77d8fSBjörn Töpel if (err) 291c0c77d8fSBjörn Töpel goto out; 292c0c77d8fSBjörn Töpel 293c0c77d8fSBjörn Töpel err = xdp_umem_pin_pages(umem); 294c0c77d8fSBjörn Töpel if (err) 295c0c77d8fSBjörn Töpel goto out_account; 2968aef7340SBjörn Töpel 2978aef7340SBjörn Töpel umem->pages = kcalloc(umem->npgs, sizeof(*umem->pages), GFP_KERNEL); 2988aef7340SBjörn Töpel if (!umem->pages) { 2998aef7340SBjörn Töpel err = -ENOMEM; 3008aef7340SBjörn Töpel goto out_account; 3018aef7340SBjörn Töpel } 3028aef7340SBjörn Töpel 3038aef7340SBjörn Töpel for (i = 0; i < umem->npgs; i++) 3048aef7340SBjörn Töpel umem->pages[i].addr = page_address(umem->pgs[i]); 3058aef7340SBjörn Töpel 306c0c77d8fSBjörn Töpel return 0; 307c0c77d8fSBjörn Töpel 308c0c77d8fSBjörn Töpel out_account: 309c0c77d8fSBjörn Töpel xdp_umem_unaccount_pages(umem); 310c0c77d8fSBjörn Töpel out: 311c0c77d8fSBjörn Töpel put_pid(umem->pid); 312c0c77d8fSBjörn Töpel return err; 313c0c77d8fSBjörn Töpel } 314965a9909SMagnus Karlsson 315a49049eaSBjörn Töpel struct xdp_umem *xdp_umem_create(struct xdp_umem_reg *mr) 316a49049eaSBjörn Töpel { 317a49049eaSBjörn Töpel struct xdp_umem *umem; 318a49049eaSBjörn Töpel int err; 319a49049eaSBjörn Töpel 320a49049eaSBjörn Töpel umem = kzalloc(sizeof(*umem), GFP_KERNEL); 321a49049eaSBjörn Töpel if (!umem) 322a49049eaSBjörn Töpel return ERR_PTR(-ENOMEM); 323a49049eaSBjörn Töpel 324a49049eaSBjörn Töpel err = xdp_umem_reg(umem, mr); 325a49049eaSBjörn Töpel if (err) { 326a49049eaSBjörn Töpel kfree(umem); 327a49049eaSBjörn Töpel return ERR_PTR(err); 328a49049eaSBjörn Töpel } 329a49049eaSBjörn Töpel 330a49049eaSBjörn Töpel return umem; 331a49049eaSBjörn Töpel } 332a49049eaSBjörn Töpel 333965a9909SMagnus Karlsson bool xdp_umem_validate_queues(struct xdp_umem *umem) 334965a9909SMagnus Karlsson { 335da60cf00SBjörn Töpel return umem->fq && umem->cq; 336965a9909SMagnus Karlsson } 337