DF-5190 / fix.diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | diff --git a/sys/dev/drm/linux_vmalloc.c b/sys/dev/drm/linux_vmalloc.c --- a/sys/dev/drm/linux_vmalloc.c +++ b/sys/dev/drm/linux_vmalloc.c @@ -30,6 +30,8 @@ #include <sys/queue.h> +#include <sys/kernel.h> #include <vm/vm_extern.h> +#include <vm/vm_kern.h> #include <linux/vmalloc.h> #include <linux/slab.h> @@ -40,9 +42,17 @@ struct vmap { void *addr; int npages; SLIST_ENTRY(vmap) vm_vmaps; }; +struct vmalloc_entry { + void *addr; + size_t size; + SLIST_ENTRY(vmalloc_entry) entries; +}; + struct lock vmap_lock = LOCK_INITIALIZER("dlvml", 0, LK_CANRECURSE); SLIST_HEAD(vmap_list_head, vmap) vmap_list = SLIST_HEAD_INITIALIZER(vmap_list); +static SLIST_HEAD(vmalloc_head, vmalloc_entry) vmalloc_list = + SLIST_HEAD_INITIALIZER(vmalloc_list); @@ -105,21 +115,66 @@ is_vmalloc_addr(const void *x) return false; } +/* + * vmalloc-family allocations: page-aligned, whole-page, (optionally) zeroed + * kernel memory. Backed by kmem_alloc3() instead of kmalloc() so the Linux + * contract holds: callers may map this memory to userspace page-by-page + * without exposing neighbouring kernel heap objects (the whole point of + * vmalloc_user()). + */ +static void * +vmalloc_common(unsigned long size, int zero) +{ + struct vmalloc_entry *ve; + vm_offset_t addr; + + size = (size + PAGE_SIZE - 1) & ~(unsigned long)PAGE_MASK; + if (size == 0) + size = PAGE_SIZE; + + addr = kmem_alloc3(kernel_map, size, VM_SUBSYS_DRM, 0); + if (addr == 0) + return (NULL); + + ve = kmalloc(sizeof(*ve), M_DRM, M_WAITOK | M_ZERO); + ve->addr = (void *)addr; + ve->size = size; + lockmgr(&vmap_lock, LK_EXCLUSIVE); + SLIST_INSERT_HEAD(&vmalloc_list, ve, entries); + lockmgr(&vmap_lock, LK_RELEASE); + + if (zero) + bzero((void *)addr, size); + + return ((void *)addr); +} + void * vmalloc(unsigned long size) { - return kmalloc(size, M_DRM, M_WAITOK); + return vmalloc_common(size, 0); } void * vzalloc(unsigned long size) { - return kmalloc(size, M_DRM, M_WAITOK | M_ZERO); + return vmalloc_common(size, 1); } /* allocate zeroed virtually contiguous memory for userspace */ void * vmalloc_user(unsigned long size) { - return kmalloc(size, M_DRM, M_WAITOK | M_ZERO); + return vmalloc_common(size, 1); } void vfree(const void *addr) { void *nc_addr; + struct vmalloc_entry *ve; memcpy(&nc_addr, &addr, sizeof(void *)); + + lockmgr(&vmap_lock, LK_EXCLUSIVE); + SLIST_FOREACH(ve, &vmalloc_list, entries) { + if (ve->addr == nc_addr) { + SLIST_REMOVE(&vmalloc_list, ve, vmalloc_entry, entries); + break; + } + } + lockmgr(&vmap_lock, LK_RELEASE); + if (ve != NULL) { + kmem_free(kernel_map, (vm_offset_t)nc_addr, ve->size); + kfree(ve, M_DRM); + return; + } + + /* Not a vmalloc-family pointer: legacy kmalloc fallback. */ kfree(nc_addr); } |