/* * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "nvidia-drm-conftest.h" #if defined(NV_DRM_AVAILABLE) #if defined(NV_DRM_DRM_PRIME_H_PRESENT) #include #endif #include "nvidia-drm-gem-user-memory.h" #include "nvidia-drm-helper.h" #include "nvidia-drm-ioctl.h" #include "linux/dma-buf.h" #include "linux/mm.h" #include "nv-mm.h" #if defined(NV_BSD) #include #endif static inline void __nv_drm_gem_user_memory_free(struct nv_drm_gem_object *nv_gem) { struct nv_drm_gem_user_memory *nv_user_memory = to_nv_user_memory(nv_gem); nv_drm_unlock_user_pages(nv_user_memory->pages_count, nv_user_memory->pages); nv_drm_free(nv_user_memory); } static struct sg_table *__nv_drm_gem_user_memory_prime_get_sg_table( struct nv_drm_gem_object *nv_gem) { struct nv_drm_gem_user_memory *nv_user_memory = to_nv_user_memory(nv_gem); struct drm_gem_object *gem = &nv_gem->base; return nv_drm_prime_pages_to_sg(gem->dev, nv_user_memory->pages, nv_user_memory->pages_count); } static void *__nv_drm_gem_user_memory_prime_vmap( struct nv_drm_gem_object *nv_gem) { struct nv_drm_gem_user_memory *nv_user_memory = to_nv_user_memory(nv_gem); return nv_drm_vmap(nv_user_memory->pages, nv_user_memory->pages_count); } static void __nv_drm_gem_user_memory_prime_vunmap( struct nv_drm_gem_object *gem, void *address) { nv_drm_vunmap(address); } static int __nv_drm_gem_user_memory_mmap(struct nv_drm_gem_object *nv_gem, struct vm_area_struct *vma) { int ret = drm_gem_mmap_obj(&nv_gem->base, drm_vma_node_size(&nv_gem->base.vma_node) << PAGE_SHIFT, vma); if (ret < 0) { return ret; } /* * Enforce that user-memory GEM mappings are MAP_SHARED, to prevent COW * with MAP_PRIVATE and VM_MIXEDMAP */ if (!(vma->vm_flags & VM_SHARED)) { return -EINVAL; } nv_vm_flags_clear(vma, VM_PFNMAP); nv_vm_flags_clear(vma, VM_IO); nv_vm_flags_set(vma, VM_MIXEDMAP); return 0; } static vm_fault_t __nv_drm_gem_user_memory_handle_vma_fault( struct nv_drm_gem_object *nv_gem, struct vm_area_struct *vma, struct vm_fault *vmf) { struct nv_drm_gem_user_memory *nv_user_memory = to_nv_user_memory(nv_gem); unsigned long address = nv_page_fault_va(vmf); struct drm_gem_object *gem = vma->vm_private_data; unsigned long page_offset; vm_fault_t ret; page_offset = vmf->pgoff - drm_vma_node_start(&gem->vma_node); BUG_ON(page_offset >= nv_user_memory->pages_count); #if !defined(NV_LINUX) ret = vmf_insert_pfn(vma, address, page_to_pfn(nv_user_memory->pages[page_offset])); #else /* !defined(NV_LINUX) */ ret = vm_insert_page(vma, address, nv_user_memory->pages[page_offset]); switch (ret) { case 0: case -EBUSY: /* * EBUSY indicates that another thread already handled * the faulted range. */ ret = VM_FAULT_NOPAGE; break; case -ENOMEM: ret = VM_FAULT_OOM; break; default: WARN_ONCE(1, "Unhandled error in %s: %d\n", __FUNCTION__, ret); ret = VM_FAULT_SIGBUS; break; } #endif /* !defined(NV_LINUX) */ return ret; } static int __nv_drm_gem_user_create_mmap_offset( struct nv_drm_device *nv_dev, struct nv_drm_gem_object *nv_gem, uint64_t *offset) { (void)nv_dev; return nv_drm_gem_create_mmap_offset(nv_gem, offset); } const struct nv_drm_gem_object_funcs __nv_gem_user_memory_ops = { .free = __nv_drm_gem_user_memory_free, .prime_get_sg_table = __nv_drm_gem_user_memory_prime_get_sg_table, .prime_vmap = __nv_drm_gem_user_memory_prime_vmap, .prime_vunmap = __nv_drm_gem_user_memory_prime_vunmap, .mmap = __nv_drm_gem_user_memory_mmap, .handle_vma_fault = __nv_drm_gem_user_memory_handle_vma_fault, .create_mmap_offset = __nv_drm_gem_user_create_mmap_offset, }; int nv_drm_gem_import_userspace_memory_ioctl(struct drm_device *dev, void *data, struct drm_file *filep) { struct nv_drm_device *nv_dev = to_nv_device(dev); struct drm_nvidia_gem_import_userspace_memory_params *params = data; struct nv_drm_gem_user_memory *nv_user_memory; struct page **pages = NULL; unsigned long pages_count = 0; int ret = 0; if ((params->size % PAGE_SIZE) != 0) { NV_DRM_DEV_LOG_ERR( nv_dev, "Userspace memory 0x%" NvU64_fmtx " size should be in a multiple of page " "size to create a gem object", params->address); return -EINVAL; } pages_count = params->size / PAGE_SIZE; ret = nv_drm_lock_user_pages(params->address, pages_count, &pages); if (ret != 0) { NV_DRM_DEV_LOG_ERR( nv_dev, "Failed to lock user pages for address 0x%" NvU64_fmtx ": %d", params->address, ret); return ret; } if ((nv_user_memory = nv_drm_calloc(1, sizeof(*nv_user_memory))) == NULL) { ret = -ENOMEM; goto failed; } nv_user_memory->pages = pages; nv_user_memory->pages_count = pages_count; nv_drm_gem_object_init(nv_dev, &nv_user_memory->base, &__nv_gem_user_memory_ops, params->size, NULL /* pMemory */); return nv_drm_gem_handle_create_drop_reference(filep, &nv_user_memory->base, ¶ms->handle); failed: nv_drm_unlock_user_pages(pages_count, pages); return ret; } #endif