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> 1484c6b868SJakub Kicinski #include <linux/netdevice.h> 1584c6b868SJakub Kicinski #include <linux/rtnetlink.h> 1650e74c01SBjörn Töpel #include <linux/idr.h> 17624676e7SIvan Khoronzhuk #include <linux/vmalloc.h> 18c0c77d8fSBjörn Töpel 19c0c77d8fSBjörn Töpel #include "xdp_umem.h" 20e61e62b9SBjörn Töpel #include "xsk_queue.h" 21c0c77d8fSBjörn Töpel 22bbff2f32SBjörn Töpel #define XDP_UMEM_MIN_CHUNK_SIZE 2048 23c0c77d8fSBjörn Töpel 2450e74c01SBjörn Töpel static DEFINE_IDA(umem_ida); 2550e74c01SBjörn Töpel 26c0c77d8fSBjörn Töpel static void xdp_umem_unpin_pages(struct xdp_umem *umem) 27c0c77d8fSBjörn Töpel { 28f1f6a7ddSJohn Hubbard unpin_user_pages_dirty_lock(umem->pgs, umem->npgs, true); 29c0c77d8fSBjörn Töpel 30c0c77d8fSBjörn Töpel kfree(umem->pgs); 31c0c77d8fSBjörn Töpel umem->pgs = NULL; 32c0c77d8fSBjörn Töpel } 33c0c77d8fSBjörn Töpel 34c0c77d8fSBjörn Töpel static void xdp_umem_unaccount_pages(struct xdp_umem *umem) 35c0c77d8fSBjörn Töpel { 36c09290c5SDaniel Borkmann if (umem->user) { 37c0c77d8fSBjörn Töpel atomic_long_sub(umem->npgs, &umem->user->locked_vm); 38c0c77d8fSBjörn Töpel free_uid(umem->user); 39c0c77d8fSBjörn Töpel } 40c09290c5SDaniel Borkmann } 41c0c77d8fSBjörn Töpel 427f7ffa4eSMagnus Karlsson static void xdp_umem_addr_unmap(struct xdp_umem *umem) 437f7ffa4eSMagnus Karlsson { 447f7ffa4eSMagnus Karlsson vunmap(umem->addrs); 457f7ffa4eSMagnus Karlsson umem->addrs = NULL; 467f7ffa4eSMagnus Karlsson } 477f7ffa4eSMagnus Karlsson 487f7ffa4eSMagnus Karlsson static int xdp_umem_addr_map(struct xdp_umem *umem, struct page **pages, 497f7ffa4eSMagnus Karlsson u32 nr_pages) 507f7ffa4eSMagnus Karlsson { 517f7ffa4eSMagnus Karlsson umem->addrs = vmap(pages, nr_pages, VM_MAP, PAGE_KERNEL); 527f7ffa4eSMagnus Karlsson if (!umem->addrs) 537f7ffa4eSMagnus Karlsson return -ENOMEM; 547f7ffa4eSMagnus Karlsson return 0; 557f7ffa4eSMagnus Karlsson } 567f7ffa4eSMagnus Karlsson 57c0c77d8fSBjörn Töpel static void xdp_umem_release(struct xdp_umem *umem) 58c0c77d8fSBjörn Töpel { 59c2d3d6a4SMagnus Karlsson umem->zc = false; 6050e74c01SBjörn Töpel ida_simple_remove(&umem_ida, umem->id); 6150e74c01SBjörn Töpel 627f7ffa4eSMagnus Karlsson xdp_umem_addr_unmap(umem); 63c0c77d8fSBjörn Töpel xdp_umem_unpin_pages(umem); 64c0c77d8fSBjörn Töpel 65c0c77d8fSBjörn Töpel xdp_umem_unaccount_pages(umem); 66c0c77d8fSBjörn Töpel kfree(umem); 67c0c77d8fSBjörn Töpel } 68c0c77d8fSBjörn Töpel 69*537cf4e3SMagnus Karlsson static void xdp_umem_release_deferred(struct work_struct *work) 70*537cf4e3SMagnus Karlsson { 71*537cf4e3SMagnus Karlsson struct xdp_umem *umem = container_of(work, struct xdp_umem, work); 72*537cf4e3SMagnus Karlsson 73*537cf4e3SMagnus Karlsson xdp_umem_release(umem); 74*537cf4e3SMagnus Karlsson } 75*537cf4e3SMagnus Karlsson 76c0c77d8fSBjörn Töpel void xdp_get_umem(struct xdp_umem *umem) 77c0c77d8fSBjörn Töpel { 78d3b42f14SBjörn Töpel refcount_inc(&umem->users); 79c0c77d8fSBjörn Töpel } 80c0c77d8fSBjörn Töpel 81*537cf4e3SMagnus Karlsson void xdp_put_umem(struct xdp_umem *umem, bool defer_cleanup) 82c0c77d8fSBjörn Töpel { 83c0c77d8fSBjörn Töpel if (!umem) 84c0c77d8fSBjörn Töpel return; 85c0c77d8fSBjörn Töpel 86*537cf4e3SMagnus Karlsson if (refcount_dec_and_test(&umem->users)) { 87*537cf4e3SMagnus Karlsson if (defer_cleanup) { 88*537cf4e3SMagnus Karlsson INIT_WORK(&umem->work, xdp_umem_release_deferred); 89*537cf4e3SMagnus Karlsson schedule_work(&umem->work); 90*537cf4e3SMagnus Karlsson } else { 911c1efc2aSMagnus Karlsson xdp_umem_release(umem); 92c0c77d8fSBjörn Töpel } 93*537cf4e3SMagnus Karlsson } 94*537cf4e3SMagnus Karlsson } 95c0c77d8fSBjörn Töpel 9607bf2d97SMagnus Karlsson static int xdp_umem_pin_pages(struct xdp_umem *umem, unsigned long address) 97c0c77d8fSBjörn Töpel { 98c0c77d8fSBjörn Töpel unsigned int gup_flags = FOLL_WRITE; 99c0c77d8fSBjörn Töpel long npgs; 100c0c77d8fSBjörn Töpel int err; 101c0c77d8fSBjörn Töpel 102a343993cSBjörn Töpel umem->pgs = kcalloc(umem->npgs, sizeof(*umem->pgs), 103a343993cSBjörn Töpel GFP_KERNEL | __GFP_NOWARN); 104c0c77d8fSBjörn Töpel if (!umem->pgs) 105c0c77d8fSBjörn Töpel return -ENOMEM; 106c0c77d8fSBjörn Töpel 107d8ed45c5SMichel Lespinasse mmap_read_lock(current->mm); 10807bf2d97SMagnus Karlsson npgs = pin_user_pages(address, umem->npgs, 109932f4a63SIra Weiny gup_flags | FOLL_LONGTERM, &umem->pgs[0], NULL); 110d8ed45c5SMichel Lespinasse mmap_read_unlock(current->mm); 111c0c77d8fSBjörn Töpel 112c0c77d8fSBjörn Töpel if (npgs != umem->npgs) { 113c0c77d8fSBjörn Töpel if (npgs >= 0) { 114c0c77d8fSBjörn Töpel umem->npgs = npgs; 115c0c77d8fSBjörn Töpel err = -ENOMEM; 116c0c77d8fSBjörn Töpel goto out_pin; 117c0c77d8fSBjörn Töpel } 118c0c77d8fSBjörn Töpel err = npgs; 119c0c77d8fSBjörn Töpel goto out_pgs; 120c0c77d8fSBjörn Töpel } 121c0c77d8fSBjörn Töpel return 0; 122c0c77d8fSBjörn Töpel 123c0c77d8fSBjörn Töpel out_pin: 124c0c77d8fSBjörn Töpel xdp_umem_unpin_pages(umem); 125c0c77d8fSBjörn Töpel out_pgs: 126c0c77d8fSBjörn Töpel kfree(umem->pgs); 127c0c77d8fSBjörn Töpel umem->pgs = NULL; 128c0c77d8fSBjörn Töpel return err; 129c0c77d8fSBjörn Töpel } 130c0c77d8fSBjörn Töpel 131c0c77d8fSBjörn Töpel static int xdp_umem_account_pages(struct xdp_umem *umem) 132c0c77d8fSBjörn Töpel { 133c0c77d8fSBjörn Töpel unsigned long lock_limit, new_npgs, old_npgs; 134c0c77d8fSBjörn Töpel 135c0c77d8fSBjörn Töpel if (capable(CAP_IPC_LOCK)) 136c0c77d8fSBjörn Töpel return 0; 137c0c77d8fSBjörn Töpel 138c0c77d8fSBjörn Töpel lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; 139c0c77d8fSBjörn Töpel umem->user = get_uid(current_user()); 140c0c77d8fSBjörn Töpel 141c0c77d8fSBjörn Töpel do { 142c0c77d8fSBjörn Töpel old_npgs = atomic_long_read(&umem->user->locked_vm); 143c0c77d8fSBjörn Töpel new_npgs = old_npgs + umem->npgs; 144c0c77d8fSBjörn Töpel if (new_npgs > lock_limit) { 145c0c77d8fSBjörn Töpel free_uid(umem->user); 146c0c77d8fSBjörn Töpel umem->user = NULL; 147c0c77d8fSBjörn Töpel return -ENOBUFS; 148c0c77d8fSBjörn Töpel } 149c0c77d8fSBjörn Töpel } while (atomic_long_cmpxchg(&umem->user->locked_vm, old_npgs, 150c0c77d8fSBjörn Töpel new_npgs) != old_npgs); 151c0c77d8fSBjörn Töpel return 0; 152c0c77d8fSBjörn Töpel } 153c0c77d8fSBjörn Töpel 154a49049eaSBjörn Töpel static int xdp_umem_reg(struct xdp_umem *umem, struct xdp_umem_reg *mr) 155c0c77d8fSBjörn Töpel { 1562b1667e5SBjörn Töpel u32 npgs_rem, chunk_size = mr->chunk_size, headroom = mr->headroom; 157c05cd364SKevin Laatz bool unaligned_chunks = mr->flags & XDP_UMEM_UNALIGNED_CHUNK_FLAG; 158b16a87d0SBjörn Töpel u64 npgs, addr = mr->addr, size = mr->len; 1592b1667e5SBjörn Töpel unsigned int chunks, chunks_rem; 16099e3a236SMagnus Karlsson int err; 161c0c77d8fSBjörn Töpel 162bbff2f32SBjörn Töpel if (chunk_size < XDP_UMEM_MIN_CHUNK_SIZE || chunk_size > PAGE_SIZE) { 163c0c77d8fSBjörn Töpel /* Strictly speaking we could support this, if: 164c0c77d8fSBjörn Töpel * - huge pages, or* 165c0c77d8fSBjörn Töpel * - using an IOMMU, or 166c0c77d8fSBjörn Töpel * - making sure the memory area is consecutive 167c0c77d8fSBjörn Töpel * but for now, we simply say "computer says no". 168c0c77d8fSBjörn Töpel */ 169c0c77d8fSBjörn Töpel return -EINVAL; 170c0c77d8fSBjörn Töpel } 171c0c77d8fSBjörn Töpel 172c2d3d6a4SMagnus Karlsson if (mr->flags & ~XDP_UMEM_UNALIGNED_CHUNK_FLAG) 173c05cd364SKevin Laatz return -EINVAL; 174c05cd364SKevin Laatz 175c05cd364SKevin Laatz if (!unaligned_chunks && !is_power_of_2(chunk_size)) 176c0c77d8fSBjörn Töpel return -EINVAL; 177c0c77d8fSBjörn Töpel 178c0c77d8fSBjörn Töpel if (!PAGE_ALIGNED(addr)) { 179c0c77d8fSBjörn Töpel /* Memory area has to be page size aligned. For 180c0c77d8fSBjörn Töpel * simplicity, this might change. 181c0c77d8fSBjörn Töpel */ 182c0c77d8fSBjörn Töpel return -EINVAL; 183c0c77d8fSBjörn Töpel } 184c0c77d8fSBjörn Töpel 185c0c77d8fSBjörn Töpel if ((addr + size) < addr) 186c0c77d8fSBjörn Töpel return -EINVAL; 187c0c77d8fSBjörn Töpel 1882b1667e5SBjörn Töpel npgs = div_u64_rem(size, PAGE_SIZE, &npgs_rem); 1892b1667e5SBjörn Töpel if (npgs_rem) 1902b1667e5SBjörn Töpel npgs++; 191b16a87d0SBjörn Töpel if (npgs > U32_MAX) 192b16a87d0SBjörn Töpel return -EINVAL; 193b16a87d0SBjörn Töpel 1942b1667e5SBjörn Töpel chunks = (unsigned int)div_u64_rem(size, chunk_size, &chunks_rem); 195bbff2f32SBjörn Töpel if (chunks == 0) 196c0c77d8fSBjörn Töpel return -EINVAL; 197c0c77d8fSBjörn Töpel 1982b1667e5SBjörn Töpel if (!unaligned_chunks && chunks_rem) 199c0c77d8fSBjörn Töpel return -EINVAL; 200c0c77d8fSBjörn Töpel 20199e3a236SMagnus Karlsson if (headroom >= chunk_size - XDP_PACKET_HEADROOM) 202c0c77d8fSBjörn Töpel return -EINVAL; 203c0c77d8fSBjörn Töpel 20493ee30f3SMagnus Karlsson umem->size = size; 205bbff2f32SBjörn Töpel umem->headroom = headroom; 2062b43470aSBjörn Töpel umem->chunk_size = chunk_size; 2071c1efc2aSMagnus Karlsson umem->chunks = chunks; 208b16a87d0SBjörn Töpel umem->npgs = (u32)npgs; 209c0c77d8fSBjörn Töpel umem->pgs = NULL; 210c0c77d8fSBjörn Töpel umem->user = NULL; 211c05cd364SKevin Laatz umem->flags = mr->flags; 212c0c77d8fSBjörn Töpel 213921b6869SMagnus Karlsson INIT_LIST_HEAD(&umem->xsk_dma_list); 214d3b42f14SBjörn Töpel refcount_set(&umem->users, 1); 215c0c77d8fSBjörn Töpel 216c0c77d8fSBjörn Töpel err = xdp_umem_account_pages(umem); 217c0c77d8fSBjörn Töpel if (err) 218044175a0SBjörn Töpel return err; 219c0c77d8fSBjörn Töpel 22007bf2d97SMagnus Karlsson err = xdp_umem_pin_pages(umem, (unsigned long)addr); 221c0c77d8fSBjörn Töpel if (err) 222c0c77d8fSBjörn Töpel goto out_account; 2238aef7340SBjörn Töpel 2247f7ffa4eSMagnus Karlsson err = xdp_umem_addr_map(umem, umem->pgs, umem->npgs); 2257f7ffa4eSMagnus Karlsson if (err) 2267f7ffa4eSMagnus Karlsson goto out_unpin; 2277f7ffa4eSMagnus Karlsson 228c0c77d8fSBjörn Töpel return 0; 229c0c77d8fSBjörn Töpel 2307f7ffa4eSMagnus Karlsson out_unpin: 2317f7ffa4eSMagnus Karlsson xdp_umem_unpin_pages(umem); 232c0c77d8fSBjörn Töpel out_account: 233c0c77d8fSBjörn Töpel xdp_umem_unaccount_pages(umem); 234c0c77d8fSBjörn Töpel return err; 235c0c77d8fSBjörn Töpel } 236965a9909SMagnus Karlsson 237a49049eaSBjörn Töpel struct xdp_umem *xdp_umem_create(struct xdp_umem_reg *mr) 238a49049eaSBjörn Töpel { 239a49049eaSBjörn Töpel struct xdp_umem *umem; 240a49049eaSBjörn Töpel int err; 241a49049eaSBjörn Töpel 242a49049eaSBjörn Töpel umem = kzalloc(sizeof(*umem), GFP_KERNEL); 243a49049eaSBjörn Töpel if (!umem) 244a49049eaSBjörn Töpel return ERR_PTR(-ENOMEM); 245a49049eaSBjörn Töpel 24650e74c01SBjörn Töpel err = ida_simple_get(&umem_ida, 0, 0, GFP_KERNEL); 24750e74c01SBjörn Töpel if (err < 0) { 24850e74c01SBjörn Töpel kfree(umem); 24950e74c01SBjörn Töpel return ERR_PTR(err); 25050e74c01SBjörn Töpel } 25150e74c01SBjörn Töpel umem->id = err; 25250e74c01SBjörn Töpel 253a49049eaSBjörn Töpel err = xdp_umem_reg(umem, mr); 254a49049eaSBjörn Töpel if (err) { 25550e74c01SBjörn Töpel ida_simple_remove(&umem_ida, umem->id); 256a49049eaSBjörn Töpel kfree(umem); 257a49049eaSBjörn Töpel return ERR_PTR(err); 258a49049eaSBjörn Töpel } 259a49049eaSBjörn Töpel 260a49049eaSBjörn Töpel return umem; 261a49049eaSBjörn Töpel } 262