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 +#include #include +#include #include #include @@ -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); }