diff options
Diffstat (limited to 'drivers/accel/amdxdna/amdxdna_gem.c')
| -rw-r--r-- | drivers/accel/amdxdna/amdxdna_gem.c | 461 |
1 files changed, 321 insertions, 140 deletions
diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 4b0d58d0329b..f14484bc31ec 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -14,6 +14,7 @@ #include <linux/dma-direct.h> #include <linux/iosys-map.h> #include <linux/pagemap.h> +#include <linux/swap.h> #include <linux/vmalloc.h> #include "amdxdna_cbuf.h" @@ -37,6 +38,7 @@ amdxdna_init_dev_bo(struct amdxdna_gem_obj *dev_bo) struct amdxdna_gem_obj *heap; u64 heap_addr, exp_heap_uva; u32 heap_id; + int ret; if (xa_empty(&client->dev_heap_xa)) { XDNA_DBG(xdna, "Empty heap xa"); @@ -58,24 +60,32 @@ amdxdna_init_dev_bo(struct amdxdna_gem_obj *dev_bo) heap = xa_load(&client->dev_heap_xa, heap_id); exp_heap_uva = amdxdna_gem_uva(heap); heap_addr = amdxdna_gem_dev_addr(heap); - dev_bo->heap_start_id = heap_id; dev_bo->mem.uva = dev_bo->mm_node.start - heap_addr + exp_heap_uva; for (; heap_id < client->dev_heap_nid; heap_id++) { heap = xa_load(&client->dev_heap_xa, heap_id); if (!heap) { XDNA_ERR(xdna, "Failed to load heap %d", heap_id); - return -EINVAL; + ret = -EINVAL; + goto cleanup_heap_xa; } heap_addr = amdxdna_gem_uva(heap); if (heap_addr == AMDXDNA_INVALID_ADDR) { XDNA_ERR(xdna, "Heap %d is not mapped", heap_id); - return -EAGAIN; + ret = -EAGAIN; + goto cleanup_heap_xa; } if (heap_addr != exp_heap_uva) { XDNA_ERR(xdna, "Heap %d uva is not contiguous", heap_id); - return -EINVAL; + ret = -EINVAL; + goto cleanup_heap_xa; + } + + ret = xa_insert(&dev_bo->heap_xa, heap_id, heap, GFP_KERNEL); + if (ret) { + ret = -ENOMEM; + goto cleanup_heap_xa; } if (heap->dev_addr + heap->mem.size >= @@ -87,12 +97,15 @@ amdxdna_init_dev_bo(struct amdxdna_gem_obj *dev_bo) if (heap_id == client->dev_heap_nid) { XDNA_DBG(xdna, "Can not find heap end"); - return -EAGAIN; + ret = -EAGAIN; + goto cleanup_heap_xa; } - dev_bo->heap_end_id = heap_id; - return 0; + +cleanup_heap_xa: + xa_destroy(&dev_bo->heap_xa); + return ret; } static int @@ -132,8 +145,7 @@ amdxdna_gem_heap_alloc(struct amdxdna_gem_obj *abo) } client->heap_usage += mem->size; - xa_for_each_range(&client->dev_heap_xa, heap_id, heap, - abo->heap_start_id, abo->heap_end_id) + xa_for_each(&abo->heap_xa, heap_id, heap) drm_gem_object_get(to_gobj(heap)); unlock_out: @@ -142,48 +154,16 @@ unlock_out: return ret; } -static void -amdxdna_gem_heap_free(struct amdxdna_gem_obj *abo) +void amdxdna_gem_heap_free(struct amdxdna_client *client, struct amdxdna_gem_obj *abo) { - struct amdxdna_client *client = abo->client; - struct amdxdna_gem_obj *heap; - unsigned long heap_id; - mutex_lock(&client->mm_lock); drm_mm_remove_node(&abo->mm_node); client->heap_usage -= abo->mem.size; - xa_for_each_range(&client->dev_heap_xa, heap_id, heap, - abo->heap_start_id, abo->heap_end_id) - drm_gem_object_put(to_gobj(heap)); - mutex_unlock(&client->mm_lock); } -static struct amdxdna_gem_obj * -amdxdna_gem_create_obj(struct drm_device *dev, size_t size) -{ - struct amdxdna_gem_obj *abo; - - abo = kzalloc_obj(*abo); - if (!abo) - return ERR_PTR(-ENOMEM); - - abo->pinned = false; - abo->assigned_hwctx = AMDXDNA_INVALID_CTX_HANDLE; - mutex_init(&abo->lock); - - abo->mem.dma_addr = AMDXDNA_INVALID_ADDR; - abo->mem.uva = AMDXDNA_INVALID_ADDR; - abo->mem.size = size; - abo->open_ref = 0; - abo->internal = false; - INIT_LIST_HEAD(&abo->mem.umap_list); - - return abo; -} - static void amdxdna_gem_destroy_obj(struct amdxdna_gem_obj *abo) { @@ -253,13 +233,16 @@ static bool amdxdna_hmm_invalidate(struct mmu_interval_notifier *mni, struct amdxdna_gem_obj *abo = mapp->abo; struct amdxdna_dev *xdna; + if (!mmu_notifier_range_blockable(range)) + return false; + + if (mapp->unmapped) + return true; + xdna = to_xdna_dev(to_gobj(abo)->dev); XDNA_DBG(xdna, "Invalidating range 0x%lx, 0x%lx, type %d", mapp->range.start, mapp->range.end, abo->type); - if (!mmu_notifier_range_blockable(range)) - return false; - down_write(&xdna->notifier_lock); abo->mem.map_invalid = true; mapp->invalid = true; @@ -271,10 +254,8 @@ static bool amdxdna_hmm_invalidate(struct mmu_interval_notifier *mni, if (range->event == MMU_NOTIFY_UNMAP) { down_write(&xdna->notifier_lock); - if (!mapp->unmapped) { - queue_work(xdna->notifier_wq, &mapp->hmm_unreg_work); - mapp->unmapped = true; - } + mapp->unmapped = true; + queue_work(xdna->notifier_wq, &abo->hmm_unreg_work); up_write(&xdna->notifier_lock); } @@ -301,33 +282,42 @@ static void amdxdna_hmm_unregister(struct amdxdna_gem_obj *abo, down_write(&xdna->notifier_lock); list_for_each_entry(mapp, &abo->mem.umap_list, node) { - if (!vma || compare_range(mapp, vma->vm_mm, vma->vm_start, vma->vm_end)) { - if (!mapp->unmapped) { - queue_work(xdna->notifier_wq, &mapp->hmm_unreg_work); - mapp->unmapped = true; - } - if (vma) - break; - } + if (!compare_range(mapp, vma->vm_mm, vma->vm_start, vma->vm_end)) + continue; + + mapp->unmapped = true; + queue_work(xdna->notifier_wq, &abo->hmm_unreg_work); } up_write(&xdna->notifier_lock); } -static void amdxdna_umap_release(struct kref *ref) +static void amdxdna_hmm_unreg_umaps(struct amdxdna_gem_obj *abo, bool force) { - struct amdxdna_umap *mapp = container_of(ref, struct amdxdna_umap, refcnt); - struct amdxdna_gem_obj *abo = mapp->abo; - struct amdxdna_dev *xdna; - - mmu_interval_notifier_remove(&mapp->notifier); + struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev); + struct amdxdna_umap *mapp, *tmp; + LIST_HEAD(dead); - xdna = to_xdna_dev(to_gobj(mapp->abo)->dev); down_write(&xdna->notifier_lock); - list_del(&mapp->node); + list_for_each_entry_safe(mapp, tmp, &abo->mem.umap_list, node) { + if (!force && !mapp->unmapped) + continue; + + mapp->unmapped = true; + list_move(&mapp->node, &dead); + } if (list_empty(&abo->mem.umap_list)) abo->mem.uva = AMDXDNA_INVALID_ADDR; up_write(&xdna->notifier_lock); + list_for_each_entry_safe(mapp, tmp, &dead, node) + amdxdna_umap_put(mapp); +} + +static void amdxdna_umap_release(struct kref *ref) +{ + struct amdxdna_umap *mapp = container_of(ref, struct amdxdna_umap, refcnt); + + mmu_interval_notifier_remove(&mapp->notifier); kvfree(mapp->range.hmm_pfns); kfree(mapp); } @@ -339,10 +329,10 @@ void amdxdna_umap_put(struct amdxdna_umap *mapp) static void amdxdna_hmm_unreg_work(struct work_struct *work) { - struct amdxdna_umap *mapp = container_of(work, struct amdxdna_umap, - hmm_unreg_work); + struct amdxdna_gem_obj *abo = container_of(work, struct amdxdna_gem_obj, + hmm_unreg_work); - amdxdna_umap_put(mapp); + amdxdna_hmm_unreg_umaps(abo, false); } static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo, @@ -387,6 +377,21 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo, goto free_map; } + mapp->range.notifier = &mapp->notifier; + mapp->range.start = vma->vm_start; + mapp->range.end = vma->vm_end; + /* + * Access permissions are fixed at mmap() time. Changing them later + * with mprotect() is not supported: the range keeps requesting the + * original permissions, so the application may see a fault failure + * or an IOMMU fault. + */ + mapp->range.default_flags = HMM_PFN_REQ_FAULT; + if (vma->vm_flags & VM_WRITE) + mapp->range.default_flags |= HMM_PFN_REQ_WRITE; + mapp->abo = abo; + kref_init(&mapp->refcnt); + ret = mmu_interval_notifier_insert_locked(&mapp->notifier, current->mm, addr, @@ -397,15 +402,6 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo, goto free_pfns; } - mapp->range.notifier = &mapp->notifier; - mapp->range.start = vma->vm_start; - mapp->range.end = vma->vm_end; - mapp->range.default_flags = HMM_PFN_REQ_FAULT; - mapp->abo = abo; - kref_init(&mapp->refcnt); - - INIT_WORK(&mapp->hmm_unreg_work, amdxdna_hmm_unreg_work); - down_write(&xdna->notifier_lock); if (list_empty(&abo->mem.umap_list)) abo->mem.uva = addr; @@ -421,17 +417,47 @@ free_map: return ret; } +static struct amdxdna_gem_obj * +amdxdna_gem_create_obj(struct drm_device *dev, size_t size) +{ + struct amdxdna_gem_obj *abo; + + abo = kzalloc_obj(*abo); + if (!abo) + return ERR_PTR(-ENOMEM); + + abo->pinned = false; + abo->assigned_hwctx = AMDXDNA_INVALID_CTX_HANDLE; + mutex_init(&abo->lock); + + abo->mem.dma_addr = AMDXDNA_INVALID_ADDR; + abo->mem.uva = AMDXDNA_INVALID_ADDR; + abo->mem.size = size; + abo->open_ref = 0; + abo->internal = false; + INIT_LIST_HEAD(&abo->mem.umap_list); + xa_init_flags(&abo->heap_xa, XA_FLAGS_ALLOC); + INIT_WORK(&abo->hmm_unreg_work, amdxdna_hmm_unreg_work); + + return abo; +} + static void amdxdna_gem_dev_obj_free(struct drm_gem_object *gobj) { struct amdxdna_dev *xdna = to_xdna_dev(gobj->dev); struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); + struct amdxdna_gem_obj *heap; + unsigned long heap_id; XDNA_DBG(xdna, "BO type %d xdna_addr 0x%llx", abo->type, amdxdna_gem_dev_addr(abo)); if (abo->pinned) amdxdna_gem_unpin(abo); amdxdna_gem_vunmap(abo); - amdxdna_gem_heap_free(abo); + xa_for_each(&abo->heap_xa, heap_id, heap) + drm_gem_object_put(to_gobj(heap)); + xa_destroy(&abo->heap_xa); + drm_gem_object_release(gobj); amdxdna_gem_destroy_obj(abo); } @@ -458,16 +484,16 @@ static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo, { struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev); unsigned long num_pages = vma_pages(vma); - unsigned long offset = 0; int ret; - if (!is_import_bo(abo)) { - ret = drm_gem_shmem_mmap(&abo->base, vma); - if (ret) { - XDNA_ERR(xdna, "Failed shmem mmap %d", ret); - return ret; - } - } else { + /* + * Until today there is not any use case to mmap with non-zero + * offset. Put an explicit check here. + */ + if (vma->vm_pgoff - drm_vma_node_start(&to_gobj(abo)->vma_node)) + return -EINVAL; + + if (is_import_bo(abo)) { vma->vm_private_data = NULL; vma->vm_ops = NULL; ret = dma_buf_mmap(abo->dma_buf, vma, 0); @@ -476,23 +502,28 @@ static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo, return ret; } + amdxdna_mark_mapp_invalid(abo, vma); + /* Drop the reference drm_gem_mmap_obj() acquired.*/ drm_gem_object_put(to_gobj(abo)); + return 0; } - do { - vm_fault_t fault_ret; - - fault_ret = handle_mm_fault(vma, vma->vm_start + offset, - FAULT_FLAG_WRITE, NULL); - if (fault_ret & VM_FAULT_ERROR) { - XDNA_ERR(xdna, "Fault in page failed"); - amdxdna_mark_mapp_invalid(abo, vma); - break; - } + ret = drm_gem_shmem_mmap(&abo->base, vma); + if (ret) { + XDNA_ERR(xdna, "Failed shmem mmap %d", ret); + return ret; + } - offset += PAGE_SIZE; - } while (--num_pages); + vm_flags_mod(vma, VM_MIXEDMAP, VM_PFNMAP); + ret = vm_insert_pages(vma, vma->vm_start, abo->base.pages, &num_pages); + if (ret) { + XDNA_ERR(xdna, "Failed to insert pages %d", ret); + dma_resv_lock(to_gobj(abo)->resv, NULL); + drm_gem_shmem_put_pages_locked(&abo->base); + dma_resv_unlock(to_gobj(abo)->resv); + return ret; + } return 0; } @@ -504,6 +535,10 @@ static int amdxdna_gem_obj_mmap(struct drm_gem_object *gobj, struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); int ret; + XDNA_DBG(xdna, "BO map_offset 0x%llx type %d userptr 0x%lx size 0x%lx", + drm_vma_node_offset_addr(&gobj->vma_node), abo->type, + vma->vm_start, gobj->size); + ret = amdxdna_hmm_register(abo, vma); if (ret) return ret; @@ -514,9 +549,6 @@ static int amdxdna_gem_obj_mmap(struct drm_gem_object *gobj, goto hmm_unreg; } - XDNA_DBG(xdna, "BO map_offset 0x%llx type %d userptr 0x%lx size 0x%lx", - drm_vma_node_offset_addr(&gobj->vma_node), abo->type, - vma->vm_start, gobj->size); return 0; hmm_unreg: @@ -524,14 +556,99 @@ hmm_unreg: return ret; } +/* + * VM operations for amdxdna shmem VMAs that use VM_MIXEDMAP. + * + * drm_gem_shmem_vm_ops cannot be used on VM_MIXEDMAP VMAs because its fault + * handler calls vmf_insert_pfn() → vmf_insert_pfn_prot() which contains: + * BUG_ON((vma->vm_flags & VM_MIXEDMAP) && pfn_valid(pfn)) + * All amdxdna shmem pages are ordinary struct pages so pfn_valid() is always + * true, making the combination fatal. + * + * These ops use vmf_insert_page() (struct-page based) instead, which is the + * correct API for VM_MIXEDMAP VMAs backed by real struct pages. The open and + * close handlers replicate drm_gem_shmem_vm_open/close using only exported + * symbols. + */ +static vm_fault_t amdxdna_gem_mixedmap_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; + struct drm_gem_object *gobj = vma->vm_private_data; + struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(gobj); + loff_t num_pages = gobj->size >> PAGE_SHIFT; + vm_fault_t ret = VM_FAULT_SIGBUS; + pgoff_t page_offset; + struct page *page; + + /* + * Partial free of vma is unexpected. Otherwise, the wrong page + * will be faulted in and the user application may crash itself. + */ + page_offset = linear_page_delta(vma, vmf->address); + + dma_resv_lock(gobj->resv, NULL); + + if (!shmem->pages || shmem->madv < 0 || page_offset >= num_pages) + goto out; + + page = shmem->pages[page_offset]; + if (WARN_ON_ONCE(!page)) + goto out; + + /* + * Use vmf_insert_page() (struct-page path) not vmf_insert_pfn() + * (PFN path) because this VMA carries VM_MIXEDMAP. + */ + ret = vmf_insert_page(vma, vmf->address, page); + if (ret == VM_FAULT_NOPAGE) + folio_mark_accessed(page_folio(page)); + +out: + dma_resv_unlock(gobj->resv); + return ret; +} + +static void amdxdna_gem_mixedmap_vm_open(struct vm_area_struct *vma) +{ + struct drm_gem_object *gobj = vma->vm_private_data; + struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(gobj); + + /* + * Bump pages_use_count so the page array stays alive for the new + * mapping copy created by fork(). Mirrors drm_gem_shmem_vm_open(). + */ + dma_resv_lock(gobj->resv, NULL); + drm_WARN_ON_ONCE(gobj->dev, !refcount_inc_not_zero(&shmem->pages_use_count)); + dma_resv_unlock(gobj->resv); + + drm_gem_vm_open(vma); +} + +static void amdxdna_gem_mixedmap_vm_close(struct vm_area_struct *vma) +{ + struct drm_gem_object *gobj = vma->vm_private_data; + struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(gobj); + + dma_resv_lock(gobj->resv, NULL); + drm_gem_shmem_put_pages_locked(shmem); + dma_resv_unlock(gobj->resv); + + drm_gem_vm_close(vma); +} + +static const struct vm_operations_struct amdxdna_gem_mixedmap_vm_ops = { + .fault = amdxdna_gem_mixedmap_fault, + .open = amdxdna_gem_mixedmap_vm_open, + .close = amdxdna_gem_mixedmap_vm_close, +}; + static int amdxdna_gem_dmabuf_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma) { struct drm_gem_object *gobj = dma_buf->priv; struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); - unsigned long num_pages = vma_pages(vma); int ret; - vma->vm_ops = &drm_gem_shmem_vm_ops; + vma->vm_ops = &amdxdna_gem_mixedmap_vm_ops; vma->vm_private_data = gobj; drm_gem_object_get(gobj); @@ -541,16 +658,9 @@ static int amdxdna_gem_dmabuf_mmap(struct dma_buf *dma_buf, struct vm_area_struc /* The buffer is based on memory pages. Fix the flag. */ vm_flags_mod(vma, VM_MIXEDMAP, VM_PFNMAP); - ret = vm_insert_pages(vma, vma->vm_start, abo->base.pages, - &num_pages); - if (ret) - goto close_vma; return 0; -close_vma: - vma->vm_ops->close(vma); - return ret; put_obj: drm_gem_object_put(gobj); return ret; @@ -609,10 +719,8 @@ amdxdna_gem_skip_bo_usage(struct amdxdna_gem_obj *abo) } static void -amdxdna_gem_add_bo_usage(struct amdxdna_gem_obj *abo) +amdxdna_gem_add_bo_usage(struct amdxdna_client *client, struct amdxdna_gem_obj *abo) { - struct amdxdna_client *client = abo->client; - if (amdxdna_gem_skip_bo_usage(abo)) return; @@ -624,10 +732,8 @@ amdxdna_gem_add_bo_usage(struct amdxdna_gem_obj *abo) } static void -amdxdna_gem_del_bo_usage(struct amdxdna_gem_obj *abo) +amdxdna_gem_del_bo_usage(struct amdxdna_client *client, struct amdxdna_gem_obj *abo) { - struct amdxdna_client *client = abo->client; - if (amdxdna_gem_skip_bo_usage(abo)) return; @@ -643,8 +749,9 @@ static void amdxdna_gem_obj_free(struct drm_gem_object *gobj) struct amdxdna_dev *xdna = to_xdna_dev(gobj->dev); struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); - amdxdna_hmm_unregister(abo, NULL); - flush_workqueue(xdna->notifier_wq); + /* No notifier survives this, so no new work can be queued. */ + amdxdna_hmm_unreg_umaps(abo, true); + cancel_work_sync(&abo->hmm_unreg_work); if (abo->pinned) amdxdna_gem_unpin(abo); @@ -663,12 +770,20 @@ static int amdxdna_gem_obj_open(struct drm_gem_object *gobj, struct drm_file *fi { struct amdxdna_dev *xdna = to_xdna_dev(gobj->dev); struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); + struct amdxdna_client *client; int ret; - guard(mutex)(&abo->lock); + mutex_lock(&abo->lock); + if (abo->open_ref > 0 && filp->driver_priv != abo->client) { + mutex_unlock(&abo->lock); + return -EPERM; + } + abo->open_ref++; - if (abo->open_ref > 1) + if (abo->open_ref > 1) { + mutex_unlock(&abo->lock); return 0; + } /* Attached to the client when first opened by it. */ abo->client = filp->driver_priv; @@ -679,26 +794,34 @@ static int amdxdna_gem_obj_open(struct drm_gem_object *gobj, struct drm_file *fi if (ret) { abo->open_ref--; abo->client = NULL; + mutex_unlock(&abo->lock); return ret; } } + client = abo->client; + mutex_unlock(&abo->lock); - amdxdna_gem_add_bo_usage(abo); + amdxdna_gem_add_bo_usage(client, abo); return 0; } static void amdxdna_gem_obj_close(struct drm_gem_object *gobj, struct drm_file *filp) { struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); + struct amdxdna_client *client = NULL; - guard(mutex)(&abo->lock); + mutex_lock(&abo->lock); abo->open_ref--; if (abo->open_ref == 0) { - amdxdna_gem_del_bo_usage(abo); /* Detach from the client when last closed by it. */ + client = abo->client; abo->client = NULL; } + mutex_unlock(&abo->lock); + + if (client) + amdxdna_gem_del_bo_usage(client, abo); } static int amdxdna_gem_obj_vmap(struct drm_gem_object *obj, struct iosys_map *map) @@ -734,19 +857,72 @@ static void amdxdna_gem_obj_vunmap(struct drm_gem_object *obj, struct iosys_map drm_gem_shmem_object_vunmap(obj, map); } +static int amdxdna_gem_dev_obj_open(struct drm_gem_object *gobj, struct drm_file *filp) +{ + struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); + + guard(mutex)(&abo->lock); + if (filp->driver_priv != abo->client) + return -EPERM; + abo->open_ref++; + + return 0; +} + +static void amdxdna_gem_dev_obj_close(struct drm_gem_object *gobj, struct drm_file *filp) +{ + struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); + struct amdxdna_client *client = NULL; + + mutex_lock(&abo->lock); + abo->open_ref--; + + /* + * Freeing the heap allocation here, when the handle is closed, is + * intentional. DEV BOs are carved out of a per-client drm_mm heap; + * any subsequent allocation that lands on the same device address will + * also belong to the same client. If the user closes the handle while + * a job is still in flight the only consequence is self-inflicted + * corruption within their own context -- it cannot affect other + * processes. The GEM reference held by the in-flight job keeps the + * amdxdna_gem_obj struct alive until the job completes; it does not + * prevent the device address from being reclaimed by the allocator. + * + * Cross-process handle creation for DEV BOs is rejected in + * amdxdna_gem_dev_obj_open(), which prevents the following UAF: + * if a second process shared the handle via GEM flink and the + * original creator exited (freeing client), the importer would later + * reach open_ref == 0 here and call amdxdna_gem_heap_free() with a + * dangling abo->client pointer. Because cross-process opens are + * rejected, the process arriving here is always the owning client, + * which is still alive. abo->client is nulled out afterwards so that + * any code path running on a lingering GEM reference (e.g. an + * in-flight job) cannot silently dereference a stale pointer. + */ + if (abo->open_ref == 0) { + client = abo->client; + abo->client = NULL; + } + mutex_unlock(&abo->lock); + + if (client) + amdxdna_gem_heap_free(client, abo); +} + static int amdxdna_gem_dev_obj_vmap(struct drm_gem_object *obj, struct iosys_map *map) { struct amdxdna_gem_obj *abo = to_xdna_obj(obj); struct amdxdna_gem_obj *heap; + unsigned long index = 0; void *base; u64 offset; - /* vmap dev bo which is across more than 1 heap is not allowed */ - if (abo->heap_start_id != abo->heap_end_id) + heap = xa_find(&abo->heap_xa, &index, ULONG_MAX, XA_PRESENT); + if (!heap) return -ENOMEM; - heap = xa_load(&abo->client->dev_heap_xa, abo->heap_start_id); - if (!heap) + /* vmap dev bo which is across more than 1 heap is not allowed */ + if (xa_find_after(&abo->heap_xa, &index, ULONG_MAX, XA_PRESENT)) return -ENOMEM; base = amdxdna_gem_vmap(heap); @@ -765,6 +941,8 @@ static struct dma_buf *amdxdna_gem_dev_obj_export(struct drm_gem_object *gobj, i static const struct drm_gem_object_funcs amdxdna_gem_dev_obj_funcs = { .free = amdxdna_gem_dev_obj_free, + .open = amdxdna_gem_dev_obj_open, + .close = amdxdna_gem_dev_obj_close, .vmap = amdxdna_gem_dev_obj_vmap, .export = amdxdna_gem_dev_obj_export, }; @@ -780,7 +958,7 @@ static const struct drm_gem_object_funcs amdxdna_gem_shmem_funcs = { .vmap = amdxdna_gem_obj_vmap, .vunmap = amdxdna_gem_obj_vunmap, .mmap = amdxdna_gem_obj_mmap, - .vm_ops = &drm_gem_shmem_vm_ops, + .vm_ops = &amdxdna_gem_mixedmap_vm_ops, .export = amdxdna_gem_prime_export, }; @@ -962,6 +1140,11 @@ amdxdna_drm_create_dev_heap_bo(struct drm_device *dev, struct amdxdna_gem_obj *abo; int ret; + if (!xdna->dev_info->dev_mem_size) { + XDNA_DBG(xdna, "Device heap is not supported"); + return ERR_PTR(-EOPNOTSUPP); + } + WARN_ON(!is_power_of_2(xdna->dev_info->dev_mem_size)); XDNA_DBG(xdna, "Requested dev heap size 0x%llx", args->size); if (!args->size || !IS_ALIGNED(args->size, xdna->dev_info->dev_mem_size)) { @@ -1103,6 +1286,8 @@ int amdxdna_drm_create_bo_ioctl(struct drm_device *dev, void *data, struct drm_f args->handle, args->type, amdxdna_gem_uva(abo), amdxdna_gem_dev_addr(abo), abo->mem.size); put_obj: + if (ret && abo->type == AMDXDNA_BO_DEV) + amdxdna_gem_heap_free(client, abo); /* Dereference object reference. Handle holds it now. */ drm_gem_object_put(to_gobj(abo)); return ret; @@ -1136,7 +1321,6 @@ static void amdxdna_bo_unpin(struct amdxdna_gem_obj *abo) int amdxdna_gem_pin_nolock(struct amdxdna_gem_obj *abo) { - struct amdxdna_client *client = abo->client; struct amdxdna_gem_obj *heap; unsigned long heap_id, last = ULONG_MAX; int ret = 0; @@ -1144,17 +1328,15 @@ int amdxdna_gem_pin_nolock(struct amdxdna_gem_obj *abo) if (abo->type != AMDXDNA_BO_DEV) return amdxdna_bo_pin(abo); - xa_for_each_range(&client->dev_heap_xa, heap_id, heap, - abo->heap_start_id, abo->heap_end_id) { + xa_for_each(&abo->heap_xa, heap_id, heap) { ret = amdxdna_bo_pin(heap); if (ret) break; last = heap_id; } - if (ret && last <= abo->heap_end_id) { - xa_for_each_range(&client->dev_heap_xa, heap_id, heap, - abo->heap_start_id, last) + if (ret && last != ULONG_MAX) { + xa_for_each_range(&abo->heap_xa, heap_id, heap, 0, last) amdxdna_bo_unpin(heap); } @@ -1179,8 +1361,7 @@ void amdxdna_gem_unpin(struct amdxdna_gem_obj *abo) struct amdxdna_gem_obj *heap; unsigned long heap_id; - xa_for_each_range(&abo->client->dev_heap_xa, heap_id, heap, - abo->heap_start_id, abo->heap_end_id) + xa_for_each(&abo->heap_xa, heap_id, heap) amdxdna_bo_unpin(heap); } else { amdxdna_bo_unpin(abo); @@ -1300,8 +1481,7 @@ int amdxdna_drm_sync_bo_ioctl(struct drm_device *dev, u64 flush_start = bo_start + args->offset; u64 flush_end = flush_start + args->size; - xa_for_each_range(&client->dev_heap_xa, heap_id, heap, - abo->heap_start_id, abo->heap_end_id) { + xa_for_each(&abo->heap_xa, heap_id, heap) { u64 heap_start = amdxdna_gem_dev_addr(heap); u64 heap_end = heap_start + heap->mem.size; u64 start = max(flush_start, heap_start); @@ -1328,7 +1508,7 @@ int amdxdna_drm_sync_bo_ioctl(struct drm_device *dev, amdxdna_gem_unpin(abo); if (ret) { - drm_WARN(&xdna->ddev, 1, "Can not get flush memory"); + XDNA_DBG(xdna, "Flush BO %d failed, ret %d", args->handle, ret); goto put_obj; } } @@ -1336,7 +1516,8 @@ int amdxdna_drm_sync_bo_ioctl(struct drm_device *dev, XDNA_DBG(xdna, "Sync bo %d offset 0x%llx, size 0x%llx\n", args->handle, args->offset, args->size); - if (args->direction == SYNC_DIRECT_FROM_DEVICE) + if (abo->assigned_hwctx != AMDXDNA_INVALID_CTX_HANDLE && + args->direction == SYNC_DIRECT_FROM_DEVICE) ret = amdxdna_hwctx_sync_debug_bo(client, args->handle); put_obj: |
