Files
linux/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c
T
Linus Torvalds 18fbf5151d Merge tag 'mm-stable-2026-08-26-15-22' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
Pull more MM updates from Andrew Morton:

 - "mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff"
   (Lorenzo Stoakes)

   Index MAP_PRIVATE file-backed folios by their anonymous page offset
   to resolve confusion around reverse mapping for zeroed and CoW'd
   file-backed memory.

   Use this new VMA anonymous page offset tracking to eliminate index
   conflicts and lay the foundation for scalable CoW performance
   improvements.

 - "promote mapped executable folios after first usage for MGLRU"
   (Baolin Wang)

   Make MGLRU's protection of mapped executable file folios more
   reliable. Follow the classical LRU's logic, promoting mapped
   executable file folios after their first usage to give executable
   code a better chance to stay in memory and improve workload
   performance.

 - "mm: vmscan: fix node reclaim ignoring swappiness parameter" (Ridong
   Chen)

   Fix per-node proactive reclaim interface's ignoring the swappiness
   parameter when CONFIG_MEMCG is disabled by consolidating
   sc_swappiness() into a single function that checks
   proactive_swappiness regardless of kernel configuration.

 - "mm/vmscan: reduce lru_lock contention via vmstat-derived
   scan-balance cost" (Usama Arif)

   Reduce lru_lock contention in the reclaim path by deriving
   scan-balance costs from vmstat counters rather than lock-acquired
   producer updates.

   Read and decay these cost signals on the reclaim side under a
   dedicated per-lruvec lock, reducing total LRU lock wait time by over
   60% without impacting scan throughput.

 - "zram: fix zram issues reported by sashiko" (Sergey Senozhatsky)

   Fix two low-risk zram bugs which Sashiko spotted in drive-by review.

 - "Honor XA_FLAGS_ACCOUNT in xas_split_alloc() and charge to folio's
   memcg" (Zi Yan)

   Fix xas_split_alloc() by enabling target folio memcg charging during
   splits and adding the missing __GFP_ACCOUNT flag for proper XArray
   node memory accounting.

 - "selftests/mm: use pattern matching in .gitignore" (Pratyush Mallick)

   Replace hardcoded binary names in selftests/mm/.gitignore with a
   generic pattern-matching rule to automatically ignore generated test
   files and avoid manual updates when adding new tests.

 - "mm/page_ext: remove pgdat_page_ext_init()" (Sang-Heon Jeon)

   Make the incompatibility between FLATMEM and NUMA explicit in
   mm/Kconfig and remove the unused pgdat_page_ext_init() function.

 - "zram: fix zstd error paths and add parameter validation" (Haoqin
   Huang)

   Clean up zram compression backends by removing redundant error
   cleanup, adding parameter and dictionary validation, auto-prefixing
   algorithm error logs, and resetting parameters prior to
   reinitialization.

 - "zram: fix stale scan bounds after reinitialization" (Longlong Xia)

   Prevent out-of-bounds slot accesses during concurrent zram resets by
   moving table scan bound calculations under dev_lock in
   writeback_store() and read_block_state().

 - "add anon mTHP collapse test cases" (Baolin Wang)

   Extend selftests helper functions to support arbitrary page orders
   and add new test cases and options for mTHP collapse in khugepaged.

 - "selftests/mm: Handle unsupported and transient test conditions"
   (Muhammad Usama Anjum)

   Update MM selftests to report a SKIP status instead of a failure when
   required kernel or filesystem features are unsupported, while adding
   retry logic for transient page migration errors.

 - "mm/zswap: Fixes and improves the zswap shrink" (Hao Jia)

   Fix the missing zswap global shrinker when CONFIG_MEMCG is disabled
   and extend shrink_memcg() to support batch writeback for improved
   writeback efficiency.

 - "alloc_tag: introduce IOCTL-based filtering for MAP" (Suren
   Baghdasaryan)

   Introduce an IOCTL-based binary interface for memory allocation
   profiling that enables kernel-side filtering before per-CPU counter
   aggregation.

   This eliminates the text-parsing overhead of /proc/allocinfo and
   provides up to a 20x speedup by transferring only filtered allocation
   data to userspace.

 - "better block swap batching and a different take on swap_ops v5"
   (Christoph Hellwig)

   Refactor block swap I/O to use swap_iocb for batching instead of
   single-bio requests and rebase the swap_ops interface, achieving
   faster swap throughput during kernel builds.

 - "mm: kmemleak: reduce transient false positives by confirming leaks"
   (Catalin Marinas)

   Reduce false-positive kmemleak reports by combining two kmemleak
   enhancements that add a second confirmation scan and a configurable
   minimum unreferenced scan count module parameter.

 - "mm: kmemleak: default min_unref_scans to 2 for verbose kernels"
   (Breno Leitao)

   Auto-scanning kernels can generate false-positive memory leak reports
   on single scans, so this patch defaults min_unref_scans to 2 when
   CONFIG_DEBUG_KMEMLEAK_VERBOSE is enabled to require a second
   confirming scan.

 - "swap_ops updates" (Christoph Hellwig)

   Batching I/O for synchronous swap devices causes performance
   regressions and filesystem-based swap suffers from double-indirection
   overhead. This series resolves both issues by reintroducing per-folio
   writes for synchronous swap and allowing filesystems to directly
   export their own swap_ops.

 - "mm/khugepaged: several cleanups" (Nico Pache)

   khugepaged accumulated redundant state-checking patterns and outdated
   comments following mTHP integration. Introduce dedicated helpers for
   PTE validation and event counting while refreshing the internal
   documentation.

 - "maple_tree: lock checking and clean ups" (Liam Howlett)

   Syzbot reports incorrectly blame memory management exit paths for
   locking bugs, maple tree erase operations risk allocation failures
   without gfp flags and internal documentation lacks clarity.

   Improve lock error detection, update docs, fix race and allocation
   edge cases and optimize erase allocations using a fallback to
   GFP_KERNEL | GFP_NOFAIL.

* tag 'mm-stable-2026-08-26-15-22' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: (172 commits)
  selftests/proc: make proc-maps-race work with READ_IMPLIES_EXEC
  memcg: move LRU size accounting on reparenting instead of copying it
  mm/vmscan: fix comment logic in balance_pgdat
  maple_tree: add helper mas_make_walkable()
  maple_tree: avoid extra gap calculation
  maple_tree: fix argument name in header
  maple_tree: change two GFP flags in tests
  maple_tree: document erase and allocations better
  maple_tree: avoid mas_erase() and mtree_erase() failures
  maple_tree: document that erase may use GFP_KERNEL for allocations
  maple_tree: catch race in mas_alloc_cyclic()
  maple_tree: add bulk parent set helper
  maple_tree: micro optimisation of mas_wr_store_type()
  maple_tree: optimise mas_wr_node_store() when not in rcu mode
  maple_tree: use prefetched value in mas_wr_store_type()
  maple_tree: clarify comments on mas_nomem()
  maple_tree: drop MAPLE_ALLOC_SLOTS
  maple_tree: drop dead code from mas_extend_spanning_null()
  maple_tree: documentation fix
  maple_tree: add write lock checking with lockdep sequence numbers
  ...
2026-08-27 09:17:06 -07:00

1394 lines
36 KiB
C

/*
* Copyright 2008 Advanced Micro Devices, Inc.
* Copyright 2008 Red Hat Inc.
* Copyright 2009 Jerome Glisse.
*
* 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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.
*
* Authors: Dave Airlie
* Alex Deucher
* Jerome Glisse
*/
#include <linux/ktime.h>
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/pagemap.h>
#include <linux/pci.h>
#include <linux/dma-buf.h>
#include <linux/dma-fence-unwrap.h>
#include <linux/uaccess.h>
#include <drm/amdgpu_drm.h>
#include <drm/drm_drv.h>
#include <drm/drm_exec.h>
#include <drm/drm_gem_ttm_helper.h>
#include <drm/ttm/ttm_tt.h>
#include <drm/drm_syncobj.h>
#include "amdgpu.h"
#include "amdgpu_display.h"
#include "amdgpu_dma_buf.h"
#include "amdgpu_hmm.h"
#include "amdgpu_xgmi.h"
#include "amdgpu_vm.h"
static int
amdgpu_gem_add_input_fence(struct drm_file *filp,
uint64_t syncobj_handles_array,
uint32_t num_syncobj_handles)
{
struct dma_fence *fence;
uint32_t *syncobj_handles;
int ret, i;
if (!num_syncobj_handles)
return 0;
syncobj_handles = memdup_user(u64_to_user_ptr(syncobj_handles_array),
size_mul(sizeof(uint32_t), num_syncobj_handles));
if (IS_ERR(syncobj_handles))
return PTR_ERR(syncobj_handles);
for (i = 0; i < num_syncobj_handles; i++) {
if (!syncobj_handles[i]) {
ret = -EINVAL;
goto free_memdup;
}
ret = drm_syncobj_find_fence(filp, syncobj_handles[i], 0, 0, &fence);
if (ret)
goto free_memdup;
dma_fence_wait(fence, false);
/* TODO: optimize async handling */
dma_fence_put(fence);
}
free_memdup:
kfree(syncobj_handles);
return ret;
}
static int
amdgpu_gem_update_timeline_node(struct drm_file *filp,
uint32_t syncobj_handle,
uint64_t point,
struct drm_syncobj **syncobj,
struct dma_fence_chain **chain)
{
if (!syncobj_handle)
return 0;
/* Find the sync object */
*syncobj = drm_syncobj_find(filp, syncobj_handle);
if (!*syncobj)
return -ENOENT;
if (!point)
return 0;
/* Allocate the chain node */
*chain = dma_fence_chain_alloc();
if (!*chain) {
drm_syncobj_put(*syncobj);
*syncobj = NULL;
return -ENOMEM;
}
return 0;
}
static vm_fault_t amdgpu_gem_fault(struct vm_fault *vmf)
{
struct ttm_buffer_object *bo = vmf->vma->vm_private_data;
struct drm_device *ddev = bo->base.dev;
vm_fault_t ret;
int idx;
ret = ttm_bo_vm_reserve(bo, vmf);
if (ret)
return ret;
if (drm_dev_enter(ddev, &idx)) {
ret = amdgpu_bo_fault_reserve_notify(bo);
if (ret) {
drm_dev_exit(idx);
goto unlock;
}
ret = ttm_bo_vm_fault_reserved(vmf, vmf->vma->vm_page_prot,
TTM_BO_VM_NUM_PREFAULT);
drm_dev_exit(idx);
} else {
ret = ttm_bo_vm_dummy_page(vmf, vmf->vma->vm_page_prot);
}
if (ret == VM_FAULT_RETRY && !(vmf->flags & FAULT_FLAG_RETRY_NOWAIT))
return ret;
unlock:
dma_resv_unlock(bo->base.resv);
return ret;
}
static const struct vm_operations_struct amdgpu_gem_vm_ops = {
.fault = amdgpu_gem_fault,
.open = ttm_bo_vm_open,
.close = ttm_bo_vm_close,
.access = ttm_bo_vm_access
};
static void amdgpu_gem_object_free(struct drm_gem_object *gobj)
{
struct amdgpu_bo *aobj = gem_to_amdgpu_bo(gobj);
amdgpu_hmm_unregister(aobj);
ttm_bo_fini(&aobj->tbo);
}
int amdgpu_gem_object_create(struct amdgpu_device *adev, unsigned long size,
int alignment, u32 initial_domain,
u64 flags, enum ttm_bo_type type,
struct dma_resv *resv,
struct drm_gem_object **obj, int8_t xcp_id_plus1)
{
struct amdgpu_bo *bo;
struct amdgpu_bo_user *ubo;
struct amdgpu_bo_param bp;
int r;
memset(&bp, 0, sizeof(bp));
*obj = NULL;
flags |= AMDGPU_GEM_CREATE_VRAM_WIPE_ON_RELEASE;
bp.size = size;
bp.byte_align = alignment;
bp.type = type;
bp.resv = resv;
bp.preferred_domain = initial_domain;
bp.flags = flags;
bp.domain = initial_domain;
bp.bo_ptr_size = sizeof(struct amdgpu_bo);
bp.xcp_id_plus1 = xcp_id_plus1;
r = amdgpu_bo_create_user(adev, &bp, &ubo);
if (r)
return r;
bo = &ubo->bo;
*obj = &bo->tbo.base;
return 0;
}
void amdgpu_gem_force_release(struct amdgpu_device *adev)
{
struct drm_device *ddev = adev_to_drm(adev);
struct drm_file *file;
mutex_lock(&ddev->filelist_mutex);
list_for_each_entry(file, &ddev->filelist, lhead) {
struct drm_gem_object *gobj;
int handle;
WARN_ONCE(1, "Still active user space clients!\n");
spin_lock(&file->table_lock);
idr_for_each_entry(&file->object_idr, gobj, handle) {
WARN_ONCE(1, "And also active allocations!\n");
drm_gem_object_put(gobj);
}
idr_destroy(&file->object_idr);
spin_unlock(&file->table_lock);
}
mutex_unlock(&ddev->filelist_mutex);
}
/*
* Call from drm_gem_handle_create which appear in both new and open ioctl
* case.
*/
static int amdgpu_gem_object_open(struct drm_gem_object *obj,
struct drm_file *file_priv)
{
struct amdgpu_bo *abo = gem_to_amdgpu_bo(obj);
struct amdgpu_device *adev = amdgpu_ttm_adev(abo->tbo.bdev);
struct amdgpu_fpriv *fpriv = file_priv->driver_priv;
struct amdgpu_vm *vm = &fpriv->vm;
struct amdgpu_bo_va *bo_va;
struct mm_struct *mm;
struct drm_exec exec;
int r;
mm = amdgpu_ttm_tt_get_usermm(abo->tbo.ttm);
if (mm && mm != current->mm)
return -EPERM;
if (abo->flags & AMDGPU_GEM_CREATE_VM_ALWAYS_VALID &&
!amdgpu_vm_is_bo_always_valid(vm, abo))
return -EPERM;
drm_exec_init(&exec, DRM_EXEC_IGNORE_DUPLICATES, 0);
drm_exec_until_all_locked(&exec) {
r = drm_exec_prepare_obj(&exec, &abo->tbo.base, TTM_NUM_MOVE_FENCES + 1);
drm_exec_retry_on_contention(&exec);
if (unlikely(r))
goto out_unlock;
r = amdgpu_vm_lock_pd(vm, &exec, 0);
drm_exec_retry_on_contention(&exec);
if (unlikely(r))
goto out_unlock;
}
amdgpu_vm_bo_update_shared(abo);
bo_va = amdgpu_vm_bo_find(vm, abo);
if (!bo_va) {
bo_va = amdgpu_vm_bo_add(adev, vm, abo);
r = amdgpu_evf_mgr_attach_fence(&fpriv->evf_mgr, abo);
if (r)
goto out_unlock;
} else {
++bo_va->ref_count;
}
drm_exec_fini(&exec);
/* Validate and add eviction fence to DMABuf imports with dynamic
* attachment in compute VMs. Re-validation will be done by
* amdgpu_vm_validate. Fences are on the reservation shared with the
* export, which is currently required to be validated and fenced
* already by amdgpu_amdkfd_gpuvm_restore_process_bos.
*
* Nested locking below for the case that a GEM object is opened in
* kfd_mem_export_dmabuf. Since the lock below is only taken for imports,
* but not for export, this is a different lock class that cannot lead to
* circular lock dependencies.
*/
if (!vm->is_compute_context || !vm->process_info)
return 0;
if (!drm_gem_is_imported(obj) ||
!dma_buf_is_dynamic(obj->import_attach->dmabuf))
return 0;
mutex_lock_nested(&vm->process_info->lock, 1);
if (!WARN_ON(!vm->process_info->eviction_fence)) {
r = amdgpu_amdkfd_bo_validate_and_fence(abo, AMDGPU_GEM_DOMAIN_GTT,
&vm->process_info->eviction_fence->base);
if (r) {
struct amdgpu_task_info *ti = amdgpu_vm_get_task_info_vm(vm);
dev_warn(adev->dev, "validate_and_fence failed: %d\n", r);
if (ti) {
dev_warn(adev->dev, "pid %d\n", ti->task.pid);
amdgpu_vm_put_task_info(ti);
}
}
}
mutex_unlock(&vm->process_info->lock);
return r;
out_unlock:
drm_exec_fini(&exec);
return r;
}
static void amdgpu_gem_object_close(struct drm_gem_object *obj,
struct drm_file *file_priv)
{
struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj);
struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev);
struct amdgpu_fpriv *fpriv = file_priv->driver_priv;
struct amdgpu_vm *vm = &fpriv->vm;
struct dma_fence *fence = NULL;
struct amdgpu_bo_va *bo_va;
struct drm_exec exec;
long r;
drm_exec_init(&exec, DRM_EXEC_IGNORE_DUPLICATES, 0);
drm_exec_until_all_locked(&exec) {
r = drm_exec_prepare_obj(&exec, &bo->tbo.base, 1);
drm_exec_retry_on_contention(&exec);
if (unlikely(r))
goto out_unlock;
r = amdgpu_vm_lock_pd(vm, &exec, 0);
drm_exec_retry_on_contention(&exec);
if (unlikely(r))
goto out_unlock;
}
if (!amdgpu_vm_is_bo_always_valid(vm, bo))
amdgpu_evf_mgr_detach_fence(&fpriv->evf_mgr, bo);
bo_va = amdgpu_vm_bo_find(vm, bo);
if (!bo_va || --bo_va->ref_count)
goto out_unlock;
amdgpu_vm_bo_del(adev, bo_va);
amdgpu_vm_bo_update_shared(bo);
if (!amdgpu_vm_ready(vm))
goto out_unlock;
r = amdgpu_vm_clear_freed(adev, vm, &fence);
if (unlikely(r < 0) && !drm_dev_is_unplugged(adev_to_drm(adev)))
dev_err(adev->dev, "failed to clear page "
"tables on GEM object close (%ld)\n", r);
if (r || !fence)
goto out_unlock;
amdgpu_bo_fence(bo, fence, true);
dma_fence_put(fence);
out_unlock:
if (r && !drm_dev_is_unplugged(adev_to_drm(adev)))
dev_err(adev->dev, "leaking bo va (%ld)\n", r);
drm_exec_fini(&exec);
}
static int amdgpu_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
{
struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj);
if (amdgpu_ttm_tt_get_usermm(bo->tbo.ttm))
return -EPERM;
if (bo->flags & AMDGPU_GEM_CREATE_NO_CPU_ACCESS)
return -EPERM;
/* Workaround for Thunk bug creating PROT_NONE,MAP_PRIVATE mappings
* for debugger access to invisible VRAM. Should have used MAP_SHARED
* instead. Clearing VM_MAYWRITE prevents the mapping from ever
* becoming writable and makes vma_is_cow_mapping(vma) false.
*/
if (vma_is_cow_mapping(vma) &&
!(vma->vm_flags & VM_ACCESS_FLAGS))
vm_flags_clear(vma, VM_MAYWRITE);
return drm_gem_ttm_mmap(obj, vma);
}
const struct drm_gem_object_funcs amdgpu_gem_object_funcs = {
.free = amdgpu_gem_object_free,
.open = amdgpu_gem_object_open,
.close = amdgpu_gem_object_close,
.export = amdgpu_gem_prime_export,
.vmap = drm_gem_ttm_vmap,
.vunmap = drm_gem_ttm_vunmap,
.mmap = amdgpu_gem_object_mmap,
.vm_ops = &amdgpu_gem_vm_ops,
};
static bool amdgpu_gem_are_domains_valid(u32 domains)
{
u32 normal = AMDGPU_GEM_DOMAIN_CPU |
AMDGPU_GEM_DOMAIN_GTT |
AMDGPU_GEM_DOMAIN_VRAM;
/* Treat all non CPU/GTT/VRAM domains as special domains. */
u32 special = AMDGPU_GEM_DOMAIN_MASK & ~normal;
u32 normal_mask = domains & normal;
u32 special_mask = domains & special;
if (!special_mask)
return true;
if (normal_mask)
return false;
return !(special_mask & (special_mask - 1));
}
/*
* GEM ioctls.
*/
int amdgpu_gem_create_ioctl(struct drm_device *dev, void *data,
struct drm_file *filp)
{
struct amdgpu_device *adev = drm_to_adev(dev);
struct amdgpu_fpriv *fpriv = filp->driver_priv;
struct amdgpu_vm *vm = &fpriv->vm;
union drm_amdgpu_gem_create *args = data;
uint64_t flags = args->in.domain_flags;
uint64_t size = args->in.bo_size;
struct dma_resv *resv = NULL;
struct drm_gem_object *gobj;
uint32_t handle, initial_domain;
int r;
/* reject invalid gem flags */
if (flags & ~AMDGPU_GEM_CREATE_SETTABLE_MASK)
return -EINVAL;
/* reject invalid gem domains */
if (args->in.domains & ~AMDGPU_GEM_DOMAIN_MASK)
return -EINVAL;
if (!amdgpu_gem_are_domains_valid(args->in.domains))
return -EINVAL;
if (!amdgpu_is_tmz(adev) && (flags & AMDGPU_GEM_CREATE_ENCRYPTED)) {
DRM_NOTE_ONCE("Cannot allocate secure buffer since TMZ is disabled\n");
return -EINVAL;
}
/* always clear VRAM */
flags |= AMDGPU_GEM_CREATE_VRAM_CLEARED;
/* create a gem object to contain this object in */
if (args->in.domains & (AMDGPU_GEM_DOMAIN_GDS |
AMDGPU_GEM_DOMAIN_GWS | AMDGPU_GEM_DOMAIN_OA)) {
if (flags & AMDGPU_GEM_CREATE_VM_ALWAYS_VALID) {
/* if gds bo is created from user space, it must be
* passed to bo list
*/
DRM_ERROR("GDS bo cannot be per-vm-bo\n");
return -EINVAL;
}
flags |= AMDGPU_GEM_CREATE_NO_CPU_ACCESS;
}
if (flags & AMDGPU_GEM_CREATE_VM_ALWAYS_VALID) {
r = amdgpu_bo_reserve(vm->root.bo, false);
if (r)
return r;
resv = vm->root.bo->tbo.base.resv;
}
initial_domain = (u32)(0xffffffff & args->in.domains);
retry:
r = amdgpu_gem_object_create(adev, size, args->in.alignment,
initial_domain,
flags, ttm_bo_type_device, resv, &gobj, fpriv->xcp_id + 1);
if (r && r != -ERESTARTSYS) {
if (flags & AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED) {
flags &= ~AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED;
goto retry;
}
if (initial_domain == AMDGPU_GEM_DOMAIN_VRAM) {
initial_domain |= AMDGPU_GEM_DOMAIN_GTT;
goto retry;
}
DRM_DEBUG("Failed to allocate GEM object (%llu, %d, %llu, %d)\n",
size, initial_domain, args->in.alignment, r);
}
if (flags & AMDGPU_GEM_CREATE_VM_ALWAYS_VALID) {
if (!r) {
struct amdgpu_bo *abo = gem_to_amdgpu_bo(gobj);
abo->parent = amdgpu_bo_ref(vm->root.bo);
}
amdgpu_bo_unreserve(vm->root.bo);
}
if (r)
return r;
r = drm_gem_handle_create(filp, gobj, &handle);
/* drop reference from allocate - handle holds it now */
drm_gem_object_put(gobj);
if (r)
return r;
memset(args, 0, sizeof(*args));
args->out.handle = handle;
return 0;
}
int amdgpu_gem_userptr_ioctl(struct drm_device *dev, void *data,
struct drm_file *filp)
{
struct ttm_operation_ctx ctx = { true, false };
struct amdgpu_device *adev = drm_to_adev(dev);
struct drm_amdgpu_gem_userptr *args = data;
struct amdgpu_fpriv *fpriv = filp->driver_priv;
struct drm_gem_object *gobj;
struct amdgpu_hmm_range *range;
struct amdgpu_bo *bo;
uint32_t handle;
int r;
args->addr = untagged_addr(args->addr);
if (offset_in_page(args->addr | args->size))
return -EINVAL;
if (!access_ok((void __user *)(uintptr_t)args->addr, args->size))
return -EFAULT;
/* reject unknown flag values */
if (args->flags & ~(AMDGPU_GEM_USERPTR_READONLY |
AMDGPU_GEM_USERPTR_ANONONLY | AMDGPU_GEM_USERPTR_VALIDATE |
AMDGPU_GEM_USERPTR_REGISTER))
return -EINVAL;
if (!(args->flags & AMDGPU_GEM_USERPTR_READONLY) &&
!(args->flags & AMDGPU_GEM_USERPTR_REGISTER)) {
/* if we want to write to it we must install a MMU notifier */
return -EACCES;
}
/* create a gem object to contain this object in */
r = amdgpu_gem_object_create(adev, args->size, 0, AMDGPU_GEM_DOMAIN_CPU,
0, ttm_bo_type_device, NULL, &gobj, fpriv->xcp_id + 1);
if (r)
return r;
bo = gem_to_amdgpu_bo(gobj);
bo->preferred_domains = AMDGPU_GEM_DOMAIN_GTT;
bo->allowed_domains = AMDGPU_GEM_DOMAIN_GTT;
bo->parent = amdgpu_bo_ref(fpriv->vm.root.bo);
r = amdgpu_ttm_tt_set_userptr(&bo->tbo, args->addr, args->flags);
if (r)
goto release_object;
r = amdgpu_hmm_register(bo, args->addr);
if (r)
goto release_object;
if (args->flags & AMDGPU_GEM_USERPTR_VALIDATE) {
range = amdgpu_hmm_range_alloc(NULL);
if (unlikely(!range))
return -ENOMEM;
r = amdgpu_ttm_tt_get_user_pages(bo, range);
if (r) {
amdgpu_hmm_range_free(range);
goto release_object;
}
r = amdgpu_bo_reserve(bo, true);
if (r)
goto user_pages_done;
amdgpu_ttm_tt_set_user_pages(bo->tbo.ttm, range);
amdgpu_bo_placement_from_domain(bo, AMDGPU_GEM_DOMAIN_GTT);
r = ttm_bo_validate(&bo->tbo, &bo->placement, &ctx);
amdgpu_bo_unreserve(bo);
if (r)
goto user_pages_done;
}
r = drm_gem_handle_create(filp, gobj, &handle);
if (r)
goto user_pages_done;
args->handle = handle;
user_pages_done:
if (args->flags & AMDGPU_GEM_USERPTR_VALIDATE)
amdgpu_hmm_range_free(range);
release_object:
drm_gem_object_put(gobj);
return r;
}
int amdgpu_mode_dumb_mmap(struct drm_file *filp,
struct drm_device *dev,
uint32_t handle, uint64_t *offset_p)
{
struct drm_gem_object *gobj;
struct amdgpu_bo *robj;
gobj = drm_gem_object_lookup(filp, handle);
if (!gobj)
return -ENOENT;
robj = gem_to_amdgpu_bo(gobj);
if (amdgpu_ttm_tt_get_usermm(robj->tbo.ttm) ||
(robj->flags & AMDGPU_GEM_CREATE_NO_CPU_ACCESS)) {
drm_gem_object_put(gobj);
return -EPERM;
}
*offset_p = amdgpu_bo_mmap_offset(robj);
drm_gem_object_put(gobj);
return 0;
}
int amdgpu_gem_mmap_ioctl(struct drm_device *dev, void *data,
struct drm_file *filp)
{
union drm_amdgpu_gem_mmap *args = data;
uint32_t handle = args->in.handle;
memset(args, 0, sizeof(*args));
return amdgpu_mode_dumb_mmap(filp, dev, handle, &args->out.addr_ptr);
}
/**
* amdgpu_gem_timeout - calculate jiffies timeout from absolute value
*
* @timeout_ns: timeout in ns
*
* Calculate the timeout in jiffies from an absolute timeout in ns.
*/
unsigned long amdgpu_gem_timeout(uint64_t timeout_ns)
{
unsigned long timeout_jiffies;
ktime_t timeout;
/* clamp timeout if it's to large */
if (((int64_t)timeout_ns) < 0)
return MAX_SCHEDULE_TIMEOUT;
timeout = ktime_sub(ns_to_ktime(timeout_ns), ktime_get());
if (ktime_to_ns(timeout) < 0)
return 0;
timeout_jiffies = nsecs_to_jiffies(ktime_to_ns(timeout));
/* clamp timeout to avoid unsigned-> signed overflow */
if (timeout_jiffies > MAX_SCHEDULE_TIMEOUT)
return MAX_SCHEDULE_TIMEOUT - 1;
return timeout_jiffies;
}
int amdgpu_gem_wait_idle_ioctl(struct drm_device *dev, void *data,
struct drm_file *filp)
{
union drm_amdgpu_gem_wait_idle *args = data;
struct drm_gem_object *gobj;
struct amdgpu_bo *robj;
uint32_t handle = args->in.handle;
unsigned long timeout = amdgpu_gem_timeout(args->in.timeout);
int r = 0;
long ret;
gobj = drm_gem_object_lookup(filp, handle);
if (!gobj)
return -ENOENT;
robj = gem_to_amdgpu_bo(gobj);
ret = dma_resv_wait_timeout(robj->tbo.base.resv, DMA_RESV_USAGE_READ,
true, timeout);
/* ret == 0 means not signaled,
* ret > 0 means signaled
* ret < 0 means interrupted before timeout
*/
if (ret >= 0) {
memset(args, 0, sizeof(*args));
args->out.status = (ret == 0);
} else
r = ret;
drm_gem_object_put(gobj);
return r;
}
int amdgpu_gem_metadata_ioctl(struct drm_device *dev, void *data,
struct drm_file *filp)
{
struct drm_amdgpu_gem_metadata *args = data;
struct drm_gem_object *gobj;
struct amdgpu_bo *robj;
int r = -1;
DRM_DEBUG("%d\n", args->handle);
gobj = drm_gem_object_lookup(filp, args->handle);
if (gobj == NULL)
return -ENOENT;
robj = gem_to_amdgpu_bo(gobj);
r = amdgpu_bo_reserve(robj, false);
if (unlikely(r != 0))
goto out;
/* Reject MMIO_REMAP BOs at IOCTL level: metadata/tiling does not apply. */
if (robj->tbo.resource &&
robj->tbo.resource->mem_type == AMDGPU_PL_MMIO_REMAP) {
DRM_WARN("metadata ioctl on MMIO_REMAP BO (handle %d)\n",
args->handle);
r = -EINVAL;
goto unreserve;
}
if (args->op == AMDGPU_GEM_METADATA_OP_GET_METADATA) {
amdgpu_bo_get_tiling_flags(robj, &args->data.tiling_info);
r = amdgpu_bo_get_metadata(robj, args->data.data,
sizeof(args->data.data),
&args->data.data_size_bytes,
&args->data.flags);
} else if (args->op == AMDGPU_GEM_METADATA_OP_SET_METADATA) {
if (args->data.data_size_bytes > sizeof(args->data.data)) {
r = -EINVAL;
goto unreserve;
}
r = amdgpu_bo_set_tiling_flags(robj, args->data.tiling_info);
if (!r)
r = amdgpu_bo_set_metadata(robj, args->data.data,
args->data.data_size_bytes,
args->data.flags);
}
unreserve:
amdgpu_bo_unreserve(robj);
out:
drm_gem_object_put(gobj);
return r;
}
/**
* amdgpu_gem_va_update_vm -update the bo_va in its VM
*
* @adev: amdgpu_device pointer
* @vm: vm to update
* @bo_va: bo_va to update
* @operation: map, unmap or clear
*
* Update the bo_va directly after setting its address. Errors are not
* vital here, so they are not reported back to userspace.
*
* Returns resulting fence if freed BO(s) got cleared from the PT.
* otherwise stub fence in case of error.
*/
static struct dma_fence *
amdgpu_gem_va_update_vm(struct amdgpu_device *adev,
struct amdgpu_vm *vm,
struct amdgpu_bo_va *bo_va,
uint32_t operation)
{
struct dma_fence *fence;
int r = 0;
/* If the VM is not ready return only a stub. */
if (!amdgpu_vm_ready(vm))
return dma_fence_get_stub();
/*
* First clean up any freed mappings in the VM.
*
* amdgpu_vm_clear_freed() may replace @fence with a new fence if it
* schedules GPU work. If nothing needs clearing, @fence can remain as
* the original vm->last_update.
*/
r = amdgpu_vm_clear_freed(adev, vm, &vm->last_update);
if (r)
goto error;
/* For MAP/REPLACE we also need to update the BO mappings. */
if (operation == AMDGPU_VA_OP_MAP ||
operation == AMDGPU_VA_OP_REPLACE) {
r = amdgpu_vm_bo_update(adev, bo_va, false);
if (r)
goto error;
}
/* Always update PDEs after we touched the mappings. */
r = amdgpu_vm_update_pdes(adev, vm, false);
if (r)
goto error;
if ((operation == AMDGPU_VA_OP_MAP ||
operation == AMDGPU_VA_OP_REPLACE) &&
!amdgpu_vm_is_bo_always_valid(vm, bo_va->base.bo)) {
/*
* For MAP/REPLACE of non per-VM BOs we need to sync to both the
* bo_va->last_pt_update and vm->last_update or otherwise we
* potentially miss the PDE updates.
*/
fence = dma_fence_unwrap_merge(vm->last_update,
bo_va->last_pt_update);
if (!fence) {
/* As fallback in OOM situations */
dma_fence_wait(vm->last_update, false);
dma_fence_wait(bo_va->last_pt_update, false);
fence = dma_fence_get_stub();
}
} else {
fence = dma_fence_get(vm->last_update);
}
return fence;
error:
if (r && r != -ERESTARTSYS)
DRM_ERROR("Couldn't update BO_VA (%d)\n", r);
return dma_fence_get(vm->last_update);
}
int amdgpu_gem_va_ioctl(struct drm_device *dev, void *data,
struct drm_file *filp)
{
const uint32_t valid_flags = AMDGPU_VM_DELAY_UPDATE |
AMDGPU_VM_PAGE_READABLE | AMDGPU_VM_PAGE_WRITEABLE |
AMDGPU_VM_PAGE_EXECUTABLE | AMDGPU_VM_MTYPE_MASK |
AMDGPU_VM_PAGE_NOALLOC;
const uint32_t prt_flags = AMDGPU_VM_DELAY_UPDATE |
AMDGPU_VM_PAGE_PRT;
struct drm_amdgpu_gem_va *args = data;
struct drm_gem_object *gobj;
struct amdgpu_device *adev = drm_to_adev(dev);
struct amdgpu_fpriv *fpriv = filp->driver_priv;
struct amdgpu_bo *abo;
struct amdgpu_bo_va *bo_va;
struct drm_syncobj *timeline_syncobj = NULL;
struct dma_fence_chain *timeline_chain = NULL;
struct drm_exec exec;
uint64_t vm_size, tmp;
int r = 0;
/* Validate virtual address range against reserved regions. */
if (args->va_address < AMDGPU_VA_RESERVED_BOTTOM) {
dev_dbg(dev->dev,
"va_address 0x%llx is in reserved area 0x%llx\n",
args->va_address, AMDGPU_VA_RESERVED_BOTTOM);
return -EINVAL;
}
if (args->va_address >= AMDGPU_GMC_HOLE_START &&
args->va_address < AMDGPU_GMC_HOLE_END) {
dev_dbg(dev->dev,
"va_address 0x%llx is in VA hole 0x%llx-0x%llx\n",
args->va_address, AMDGPU_GMC_HOLE_START,
AMDGPU_GMC_HOLE_END);
return -EINVAL;
}
args->va_address &= AMDGPU_GMC_HOLE_MASK;
vm_size = adev->vm_manager.max_pfn * AMDGPU_GPU_PAGE_SIZE;
vm_size -= AMDGPU_VA_RESERVED_TOP;
if (check_add_overflow(args->va_address, args->map_size, &tmp) || tmp > vm_size) {
dev_dbg(dev->dev,
"va_address 0x%llx is in top reserved area 0x%llx\n",
args->va_address + args->map_size, vm_size);
return -EINVAL;
}
if ((args->flags & ~valid_flags) && (args->flags & ~prt_flags)) {
dev_dbg(dev->dev, "invalid flags combination 0x%08X\n",
args->flags);
return -EINVAL;
}
/* Validate operation type. */
switch (args->operation) {
case AMDGPU_VA_OP_MAP:
case AMDGPU_VA_OP_UNMAP:
case AMDGPU_VA_OP_CLEAR:
case AMDGPU_VA_OP_REPLACE:
break;
default:
dev_dbg(dev->dev, "unsupported operation %d\n",
args->operation);
return -EINVAL;
}
if (args->flags & AMDGPU_VM_DELAY_UPDATE &&
args->vm_timeline_syncobj_out)
return -EINVAL;
if ((args->operation != AMDGPU_VA_OP_CLEAR) &&
!(args->flags & AMDGPU_VM_PAGE_PRT)) {
gobj = drm_gem_object_lookup(filp, args->handle);
if (gobj == NULL)
return -ENOENT;
abo = gem_to_amdgpu_bo(gobj);
} else {
gobj = NULL;
abo = NULL;
}
/* Add input syncobj fences (if any) for synchronization. */
r = amdgpu_gem_add_input_fence(filp,
args->input_fence_syncobj_handles,
args->num_syncobj_handles);
if (r)
goto error_put_gobj;
drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
DRM_EXEC_IGNORE_DUPLICATES, 0);
drm_exec_until_all_locked(&exec) {
if (gobj) {
r = drm_exec_lock_obj(&exec, gobj);
drm_exec_retry_on_contention(&exec);
if (unlikely(r))
goto error;
}
r = amdgpu_vm_lock_pd(&fpriv->vm, &exec, 2);
drm_exec_retry_on_contention(&exec);
if (unlikely(r))
goto error;
}
/* Resolve the BO-VA mapping for this VM/BO combination. */
if (abo) {
bo_va = amdgpu_vm_bo_find(&fpriv->vm, abo);
if (!bo_va) {
r = -ENOENT;
goto error;
}
} else if (args->operation != AMDGPU_VA_OP_CLEAR) {
bo_va = fpriv->prt_va;
} else {
bo_va = NULL;
}
/*
* Prepare the timeline syncobj node if the user requested a VM
* timeline update. This only allocates/looks up the syncobj and
* chain node; the actual fence is attached later.
*/
r = amdgpu_gem_update_timeline_node(filp,
args->vm_timeline_syncobj_out,
args->vm_timeline_point,
&timeline_syncobj,
&timeline_chain);
if (r)
goto error;
switch (args->operation) {
case AMDGPU_VA_OP_MAP:
r = amdgpu_vm_bo_map(adev, bo_va, args->va_address,
args->offset_in_bo, args->map_size,
args->flags);
break;
case AMDGPU_VA_OP_UNMAP:
r = amdgpu_vm_bo_unmap(adev, bo_va, args->va_address);
break;
case AMDGPU_VA_OP_CLEAR:
r = amdgpu_vm_bo_clear_mappings(adev, &fpriv->vm,
args->va_address,
args->map_size);
break;
case AMDGPU_VA_OP_REPLACE:
r = amdgpu_vm_bo_replace_map(adev, bo_va, args->va_address,
args->offset_in_bo, args->map_size,
args->flags);
break;
default:
break;
}
/*
* Once the VA operation is done, update the VM and obtain the fence
* that represents the last relevant update for this mapping. This
* fence can then be exported to the user-visible VM timeline.
*/
if (!r && !(args->flags & AMDGPU_VM_DELAY_UPDATE) &&
(!adev->debug_vm || timeline_syncobj)) {
struct dma_fence *fence;
fence = amdgpu_gem_va_update_vm(adev, &fpriv->vm, bo_va,
args->operation);
if (timeline_syncobj) {
if (!args->vm_timeline_point) {
/* Replace the existing fence when no point is given. */
drm_syncobj_replace_fence(timeline_syncobj,
fence);
} else {
/* Attach the last-update fence at a specific point. */
drm_syncobj_add_point(timeline_syncobj,
timeline_chain,
fence,
args->vm_timeline_point);
timeline_chain = NULL;
}
}
dma_fence_put(fence);
}
error:
dma_fence_chain_free(timeline_chain);
if (timeline_syncobj)
drm_syncobj_put(timeline_syncobj);
drm_exec_fini(&exec);
error_put_gobj:
drm_gem_object_put(gobj);
return r;
}
int amdgpu_gem_op_ioctl(struct drm_device *dev, void *data,
struct drm_file *filp)
{
struct drm_amdgpu_gem_op *args = data;
struct drm_gem_object *gobj;
struct amdgpu_vm_bo_base *base;
struct amdgpu_bo *robj;
struct drm_exec exec;
struct amdgpu_fpriv *fpriv = filp->driver_priv;
int r;
if (args->padding)
return -EINVAL;
gobj = drm_gem_object_lookup(filp, args->handle);
if (!gobj)
return -ENOENT;
robj = gem_to_amdgpu_bo(gobj);
drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
DRM_EXEC_IGNORE_DUPLICATES, 0);
drm_exec_until_all_locked(&exec) {
r = drm_exec_lock_obj(&exec, gobj);
drm_exec_retry_on_contention(&exec);
if (r)
goto out_exec;
if (args->op == AMDGPU_GEM_OP_GET_MAPPING_INFO) {
r = amdgpu_vm_lock_pd(&fpriv->vm, &exec, 0);
drm_exec_retry_on_contention(&exec);
if (r)
goto out_exec;
}
}
switch (args->op) {
case AMDGPU_GEM_OP_GET_GEM_CREATE_INFO: {
struct drm_amdgpu_gem_create_in info;
void __user *out = u64_to_user_ptr(args->value);
info.bo_size = robj->tbo.base.size;
info.alignment = robj->tbo.page_alignment << PAGE_SHIFT;
info.domains = robj->preferred_domains;
info.domain_flags = robj->flags;
drm_exec_fini(&exec);
if (copy_to_user(out, &info, sizeof(info)))
r = -EFAULT;
break;
}
case AMDGPU_GEM_OP_SET_PLACEMENT:
if (drm_gem_is_imported(&robj->tbo.base) &&
args->value & AMDGPU_GEM_DOMAIN_VRAM) {
r = -EINVAL;
goto out_exec;
}
if (amdgpu_ttm_tt_get_usermm(robj->tbo.ttm)) {
r = -EPERM;
goto out_exec;
}
for (base = robj->vm_bo; base; base = base->next)
if (amdgpu_xgmi_same_hive(amdgpu_ttm_adev(robj->tbo.bdev),
amdgpu_ttm_adev(base->vm->root.bo->tbo.bdev))) {
r = -EINVAL;
goto out_exec;
}
robj->preferred_domains = args->value & (AMDGPU_GEM_DOMAIN_VRAM |
AMDGPU_GEM_DOMAIN_GTT |
AMDGPU_GEM_DOMAIN_CPU);
robj->allowed_domains = robj->preferred_domains;
if (robj->allowed_domains == AMDGPU_GEM_DOMAIN_VRAM)
robj->allowed_domains |= AMDGPU_GEM_DOMAIN_GTT;
if (robj->flags & AMDGPU_GEM_CREATE_VM_ALWAYS_VALID)
amdgpu_vm_bo_invalidate(robj, true);
drm_exec_fini(&exec);
break;
case AMDGPU_GEM_OP_GET_MAPPING_INFO: {
struct amdgpu_bo_va *bo_va = amdgpu_vm_bo_find(&fpriv->vm, robj);
struct drm_amdgpu_gem_vm_entry *vm_entries;
struct amdgpu_bo_va_mapping *mapping;
int num_mappings = 0;
/*
* num_entries is set as an input to the size of the user-allocated array of
* drm_amdgpu_gem_vm_entry stored at args->value.
* num_entries is sent back as output as the number of mappings the bo has.
* If that number is larger than the size of the array, the ioctl must
* be retried.
*/
if (!bo_va) {
r = -ENOENT;
goto out_exec;
}
if (args->num_entries > INT_MAX / sizeof(*vm_entries)) {
r = -EINVAL;
goto out_exec;
}
vm_entries = kvcalloc(args->num_entries, sizeof(*vm_entries), GFP_KERNEL);
if (!vm_entries) {
r = -ENOMEM;
goto out_exec;
}
amdgpu_vm_bo_va_for_each_valid_mapping(bo_va, mapping) {
if (num_mappings < args->num_entries) {
vm_entries[num_mappings].addr = mapping->start * AMDGPU_GPU_PAGE_SIZE;
vm_entries[num_mappings].size = (mapping->last - mapping->start + 1) * AMDGPU_GPU_PAGE_SIZE;
vm_entries[num_mappings].offset = mapping->offset;
vm_entries[num_mappings].flags = mapping->flags;
}
num_mappings += 1;
}
amdgpu_vm_bo_va_for_each_invalid_mapping(bo_va, mapping) {
if (num_mappings < args->num_entries) {
vm_entries[num_mappings].addr = mapping->start * AMDGPU_GPU_PAGE_SIZE;
vm_entries[num_mappings].size = (mapping->last - mapping->start + 1) * AMDGPU_GPU_PAGE_SIZE;
vm_entries[num_mappings].offset = mapping->offset;
vm_entries[num_mappings].flags = mapping->flags;
}
num_mappings += 1;
}
drm_exec_fini(&exec);
if (num_mappings > 0 && num_mappings <= args->num_entries)
if (copy_to_user(u64_to_user_ptr(args->value), vm_entries, num_mappings * sizeof(*vm_entries)))
r = -EFAULT;
args->num_entries = num_mappings;
kvfree(vm_entries);
break;
}
default:
drm_exec_fini(&exec);
r = -EINVAL;
}
drm_gem_object_put(gobj);
return r;
out_exec:
drm_exec_fini(&exec);
drm_gem_object_put(gobj);
return r;
}
/**
* amdgpu_gem_list_handles_ioctl - get information about a process' buffer objects
*
* @dev: drm device pointer
* @data: drm_amdgpu_gem_list_handles
* @filp: drm file pointer
*
* num_entries is set as an input to the size of the entries array.
* num_entries is sent back as output as the number of bos in the process.
* If that number is larger than the size of the array, the ioctl must
* be retried.
*
* Returns:
* 0 for success, -errno for errors.
*/
int amdgpu_gem_list_handles_ioctl(struct drm_device *dev, void *data,
struct drm_file *filp)
{
struct drm_amdgpu_gem_list_handles *args = data;
struct drm_amdgpu_gem_list_handles_entry *bo_entries;
struct drm_gem_object *gobj;
int id, ret = 0;
int bo_index = 0;
int num_bos = 0;
spin_lock(&filp->table_lock);
idr_for_each_entry(&filp->object_idr, gobj, id)
num_bos += 1;
spin_unlock(&filp->table_lock);
if (args->num_entries < num_bos) {
args->num_entries = num_bos;
return 0;
}
if (num_bos == 0) {
args->num_entries = 0;
return 0;
}
bo_entries = kvzalloc_objs(*bo_entries, num_bos);
if (!bo_entries)
return -ENOMEM;
spin_lock(&filp->table_lock);
idr_for_each_entry(&filp->object_idr, gobj, id) {
struct amdgpu_bo *bo = gem_to_amdgpu_bo(gobj);
struct drm_amdgpu_gem_list_handles_entry *bo_entry;
if (bo_index >= num_bos) {
ret = -EAGAIN;
break;
}
bo_entry = &bo_entries[bo_index];
bo_entry->size = amdgpu_bo_size(bo);
bo_entry->alloc_flags = bo->flags & AMDGPU_GEM_CREATE_SETTABLE_MASK;
bo_entry->preferred_domains = bo->preferred_domains;
bo_entry->gem_handle = id;
bo_entry->alignment = bo->tbo.page_alignment;
if (bo->tbo.base.import_attach)
bo_entry->flags |= AMDGPU_GEM_LIST_HANDLES_FLAG_IS_IMPORT;
bo_index += 1;
}
spin_unlock(&filp->table_lock);
args->num_entries = bo_index;
if (!ret)
if (copy_to_user(u64_to_user_ptr(args->entries), bo_entries, num_bos * sizeof(*bo_entries)))
ret = -EFAULT;
kvfree(bo_entries);
return ret;
}
static unsigned int amdgpu_gem_align_pitch(struct amdgpu_device *adev,
unsigned int width,
unsigned int cpp,
bool tiled)
{
unsigned int aligned = width;
unsigned int pitch_mask = 0;
unsigned int pitch;
switch (cpp) {
case 1:
pitch_mask = 255;
break;
case 2:
pitch_mask = 127;
break;
case 3:
case 4:
pitch_mask = 63;
break;
}
if (check_add_overflow(aligned, pitch_mask, &aligned))
return 0;
aligned &= ~pitch_mask;
if (check_mul_overflow(aligned, cpp, &pitch))
return 0;
return pitch;
}
int amdgpu_mode_dumb_create(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
struct amdgpu_device *adev = drm_to_adev(dev);
struct amdgpu_fpriv *fpriv = file_priv->driver_priv;
struct drm_gem_object *gobj;
uint32_t handle;
u64 flags = AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED |
AMDGPU_GEM_CREATE_CPU_GTT_USWC |
AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS;
u32 domain;
int r;
/*
* The buffer returned from this function should be cleared, but
* it can only be done if the ring is enabled or we'll fail to
* create the buffer.
*/
if (adev->mman.buffer_funcs_enabled)
flags |= AMDGPU_GEM_CREATE_VRAM_CLEARED;
args->pitch = amdgpu_gem_align_pitch(adev, args->width,
DIV_ROUND_UP(args->bpp, 8), 0);
if (!args->pitch)
return -EINVAL;
args->size = (u64)args->pitch * args->height;
args->size = ALIGN(args->size, PAGE_SIZE);
if (!args->size)
return -EINVAL;
domain = amdgpu_bo_get_preferred_domain(adev,
amdgpu_display_supported_domains(adev, flags));
r = amdgpu_gem_object_create(adev, args->size, 0, domain, flags,
ttm_bo_type_device, NULL, &gobj, fpriv->xcp_id + 1);
if (r)
return -ENOMEM;
r = drm_gem_handle_create(file_priv, gobj, &handle);
/* drop reference from allocate - handle holds it now */
drm_gem_object_put(gobj);
if (r)
return r;
args->handle = handle;
return 0;
}
#if defined(CONFIG_DEBUG_FS)
static int amdgpu_debugfs_gem_info_show(struct seq_file *m, void *unused)
{
struct amdgpu_device *adev = m->private;
struct drm_device *dev = adev_to_drm(adev);
struct drm_file *file;
int r;
r = mutex_lock_interruptible(&dev->filelist_mutex);
if (r)
return r;
list_for_each_entry(file, &dev->filelist, lhead) {
struct task_struct *task;
struct drm_gem_object *gobj;
struct pid *pid;
int id;
/*
* Although we have a valid reference on file->pid, that does
* not guarantee that the task_struct who called get_pid() is
* still alive (e.g. get_pid(current) => fork() => exit()).
* Therefore, we need to protect this ->comm access using RCU.
*/
rcu_read_lock();
pid = rcu_dereference(file->pid);
task = pid_task(pid, PIDTYPE_TGID);
seq_printf(m, "pid %8d command %s:\n", pid_nr(pid),
task ? task->comm : "<unknown>");
rcu_read_unlock();
spin_lock(&file->table_lock);
idr_for_each_entry(&file->object_idr, gobj, id) {
struct amdgpu_bo *bo = gem_to_amdgpu_bo(gobj);
amdgpu_bo_print_info(id, bo, m);
}
spin_unlock(&file->table_lock);
}
mutex_unlock(&dev->filelist_mutex);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(amdgpu_debugfs_gem_info);
#endif
void amdgpu_debugfs_gem_init(struct amdgpu_device *adev)
{
#if defined(CONFIG_DEBUG_FS)
struct drm_minor *minor = adev_to_drm(adev)->primary;
struct dentry *root = minor->debugfs_root;
debugfs_create_file("amdgpu_gem_info", 0444, root, adev,
&amdgpu_debugfs_gem_info_fops);
#endif
}