/* * 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/dma-fence-array.h>#include <linux/interval_tree_generic.h>#include <linux/idr.h>#include <linux/dma-buf.h> #include <drm/amdgpu_drm.h>#include <drm/drm_drv.h>#include <drm/ttm/ttm_tt.h>#include <drm/drm_exec.h>#include "amdgpu.h"#include "amdgpu_vm.h"#include "amdgpu_trace.h"#include "amdgpu_amdkfd.h"#include "amdgpu_gmc.h"#include "amdgpu_xgmi.h"#include "amdgpu_dma_buf.h"#include "amdgpu_res_cursor.h"#include "kfd_svm.h" /** * DOC: GPUVM * * GPUVM is the MMU functionality provided on the GPU. * GPUVM is similar to the legacy GART on older asics, however * rather than there being a single global GART table * for the entire GPU, there can be multiple GPUVM page tables active * at any given time. The GPUVM page tables can contain a mix * VRAM pages and system pages (both memory and MMIO) and system pages * can be mapped as snooped (cached system pages) or unsnooped * (uncached system pages). * * Each active GPUVM has an ID associated with it and there is a page table * linked with each VMID. When executing a command buffer, * the kernel tells the engine what VMID to use for that command * buffer. VMIDs are allocated dynamically as commands are submitted. * The userspace drivers maintain their own address space and the kernel * sets up their pages tables accordingly when they submit their * command buffers and a VMID is assigned. * The hardware supports up to 16 active GPUVMs at any given time. * * Each GPUVM is represented by a 1-2 or 1-5 level page table, depending * on the ASIC family. GPUVM supports RWX attributes on each page as well * as other features such as encryption and caching attributes. * * VMID 0 is special. It is the GPUVM used for the kernel driver. In * addition to an aperture managed by a page table, VMID 0 also has * several other apertures. There is an aperture for direct access to VRAM * and there is a legacy AGP aperture which just forwards accesses directly * to the matching system physical addresses (or IOVAs when an IOMMU is * present). These apertures provide direct access to these memories without * incurring the overhead of a page table. VMID 0 is used by the kernel * driver for tasks like memory management. * * GPU clients (i.e., engines on the GPU) use GPUVM VMIDs to access memory. * For user applications, each application can have their own unique GPUVM * address space. The application manages the address space and the kernel * driver manages the GPUVM page tables for each process. If an GPU client * accesses an invalid page, it will generate a GPU page fault, similar to * accessing an invalid page on a CPU. */ #define START(node) ((node)->start)#define LAST(node) ((node)->last) INTERVAL_TREE_DEFINE(struct amdgpu_bo_va_mapping, rb, uint64_t, __subtree_last, START, LAST, static, amdgpu_vm_it) #undef START#undef LAST /** * struct amdgpu_prt_cb - Helper to disable partial resident texture feature from a fence callback */struct amdgpu_prt_cb { /** * @adev: amdgpu device */ struct amdgpu_device *adev; /** * @cb: callback */ struct dma_fence_cb cb;}; /** * struct amdgpu_vm_tlb_seq_struct - Helper to increment the TLB flush sequence */struct amdgpu_vm_tlb_seq_struct { /** * @vm: pointer to the amdgpu_vm structure to set the fence sequence on */ struct amdgpu_vm *vm; /** * @cb: callback */ struct dma_fence_cb cb;}; /** * amdgpu_vm_assert_locked - check if VM is correctly locked * @vm: the VM which schould be tested * * Asserts that the VM root PD is locked. */static void amdgpu_vm_assert_locked(struct amdgpu_vm *vm){ dma_resv_assert_held(vm->root.bo->tbo.base.resv);} /* Initialize the amdgpu_vm_bo_status object */static void amdgpu_vm_bo_status_init(struct amdgpu_vm_bo_status *lists){ INIT_LIST_HEAD(&lists->evicted); INIT_LIST_HEAD(&lists->needs_update); INIT_LIST_HEAD(&lists->idle);} /* * Make sure we have the lock to modify the vm_bo status and return the object * with the status lists. */static struct amdgpu_vm_bo_status *amdgpu_vm_bo_lock_lists(struct amdgpu_vm_bo_base *vm_bo){ struct amdgpu_vm *vm = vm_bo->vm; struct amdgpu_bo *bo = vm_bo->bo; if (amdgpu_vm_is_bo_always_valid(vm, bo)) { /* No extra locking needed, protected by the root PD resv lock */ amdgpu_vm_assert_locked(vm); if (bo->tbo.type == ttm_bo_type_kernel) return &vm->kernel; return &vm->always_valid; } spin_lock(&vm_bo->vm->individual_lock); return &vm->individual;} /* Eventually unlock the status list lock again */static void amdgpu_vm_bo_unlock_lists(struct amdgpu_vm_bo_base *vm_bo){ if (amdgpu_vm_is_bo_always_valid(vm_bo->vm, vm_bo->bo)) amdgpu_vm_assert_locked(vm_bo->vm); else spin_unlock(&vm_bo->vm->individual_lock);} /** * amdgpu_vm_is_bo_always_valid - check if the BO is VM always valid * * @vm: VM to test against. * @bo: BO to be tested. * * Returns true if the BO shares the dma_resv object with the root PD and is * always guaranteed to be valid inside the VM. */bool amdgpu_vm_is_bo_always_valid(struct amdgpu_vm *vm, struct amdgpu_bo *bo){ return bo && bo->tbo.base.resv == vm->root.bo->tbo.base.resv;} /** * amdgpu_vm_bo_evicted - vm_bo is evicted * * @vm_bo: vm_bo which is evicted * * State for vm_bo objects meaning the underlying BO was evicted and need to * move in place again. */static void amdgpu_vm_bo_evicted(struct amdgpu_vm_bo_base *vm_bo){ struct amdgpu_vm_bo_status *lists; lists = amdgpu_vm_bo_lock_lists(vm_bo); vm_bo->moved = true; list_move(&vm_bo->vm_status, &lists->evicted); amdgpu_vm_bo_unlock_lists(vm_bo);}/** * amdgpu_vm_bo_needs_update - vm_bo needs pagetable update * * @vm_bo: vm_bo which is out of date * * State for vm_bo objects meaning the underlying BO had mapping changes (move, PRT bind/unbind) * but the new location is not yet reflected in the page tables. */static void amdgpu_vm_bo_needs_update(struct amdgpu_vm_bo_base *vm_bo){ struct amdgpu_vm_bo_status *lists; struct amdgpu_bo *bo = vm_bo->bo; /* * The root PD doesn't have a parent PDE and goes directly into the * idle state. */ lists = amdgpu_vm_bo_lock_lists(vm_bo); if (bo && bo->tbo.type == ttm_bo_type_kernel && !bo->parent) { vm_bo->moved = false; list_move(&vm_bo->vm_status, &lists->idle); } else { list_move(&vm_bo->vm_status, &lists->needs_update); } amdgpu_vm_bo_unlock_lists(vm_bo);} /** * amdgpu_vm_bo_idle - vm_bo is idle * * @vm_bo: vm_bo which is now idle * * State for vm_bo objects meaning we are done with the state machine and no * further action is necessary. */static void amdgpu_vm_bo_idle(struct amdgpu_vm_bo_base *vm_bo){ struct amdgpu_vm_bo_status *lists; lists = amdgpu_vm_bo_lock_lists(vm_bo); if (!amdgpu_vm_is_bo_always_valid(vm_bo->vm, vm_bo->bo)) vm_bo->moved = false; list_move(&vm_bo->vm_status, &lists->idle); amdgpu_vm_bo_unlock_lists(vm_bo);} /** * amdgpu_vm_bo_reset_state_machine - reset the vm_bo state machine * @vm: the VM which state machine to reset * * Move all vm_bo object in the VM into a state where their location will be * updated in the page tables again. */static void amdgpu_vm_bo_reset_state_machine(struct amdgpu_vm *vm){ struct amdgpu_vm_bo_base *vm_bo, *tmp; /* * Don't use list splice here, we need the special handling for the root * PD and set the moved flag appropriately. */ amdgpu_vm_assert_locked(vm); list_for_each_entry_safe(vm_bo, tmp, &vm->kernel.idle, vm_status) amdgpu_vm_bo_needs_update(vm_bo); list_for_each_entry_safe(vm_bo, tmp, &vm->always_valid.idle, vm_status) amdgpu_vm_bo_needs_update(vm_bo); spin_lock(&vm->individual_lock); list_for_each_entry_safe(vm_bo, tmp, &vm->individual.idle, vm_status) { vm_bo->moved = true; list_move(&vm_bo->vm_status, &vm->individual.needs_update); } spin_unlock(&vm->individual_lock);} /** * amdgpu_vm_update_shared - helper to update shared memory stat * @base: base structure for tracking BO usage in a VM * * Takes the vm stats_lock and updates the shared memory stat. If the basic * stat changed (e.g. buffer was moved) amdgpu_vm_update_stats need to be called * as well. */static void amdgpu_vm_update_shared(struct amdgpu_vm_bo_base *base){ struct amdgpu_vm *vm = base->vm; struct amdgpu_bo *bo = base->bo; uint64_t size = amdgpu_bo_size(bo); uint32_t bo_memtype = amdgpu_bo_mem_stats_placement(bo); bool shared; dma_resv_assert_held(bo->tbo.base.resv); spin_lock(&vm->stats_lock); shared = drm_gem_object_is_shared_for_memory_stats(&bo->tbo.base); if (base->shared != shared) { base->shared = shared; if (shared) { vm->stats[bo_memtype].drm.shared += size; vm->stats[bo_memtype].drm.private -= size; } else { vm->stats[bo_memtype].drm.shared -= size; vm->stats[bo_memtype].drm.private += size; } } spin_unlock(&vm->stats_lock);} /** * amdgpu_vm_bo_update_shared - callback when bo gets shared/unshared * @bo: amdgpu buffer object * * Update the per VM stats for all the vm if needed from private to shared or * vice versa. */void amdgpu_vm_bo_update_shared(struct amdgpu_bo *bo){ struct amdgpu_vm_bo_base *base; for (base = bo->vm_bo; base; base = base->next) amdgpu_vm_update_shared(base);} /** * amdgpu_vm_update_stats_locked - helper to update normal memory stat * @base: base structure for tracking BO usage in a VM * @res: the ttm_resource to use for the purpose of accounting, may or may not * be bo->tbo.resource * @sign: if we should add (+1) or subtract (-1) from the stat * * Caller need to have the vm stats_lock held. Useful for when multiple update * need to happen at the same time. */static void amdgpu_vm_update_stats_locked(struct amdgpu_vm_bo_base *base, struct ttm_resource *res, int sign){ struct amdgpu_vm *vm = base->vm; struct amdgpu_bo *bo = base->bo; int64_t size = sign * amdgpu_bo_size(bo); uint32_t bo_memtype = amdgpu_bo_mem_stats_placement(bo); /* For drm-total- and drm-shared-, BO are accounted by their preferred * placement, see also amdgpu_bo_mem_stats_placement. */ if (base->shared) vm->stats[bo_memtype].drm.shared += size; else vm->stats[bo_memtype].drm.private += size; if (res && res->mem_type < __AMDGPU_PL_NUM) { uint32_t res_memtype = res->mem_type; vm->stats[res_memtype].drm.resident += size; /* BO only count as purgeable if it is resident, * since otherwise there's nothing to purge. */ if (bo->flags & AMDGPU_GEM_CREATE_DISCARDABLE) vm->stats[res_memtype].drm.purgeable += size; if (!(bo->preferred_domains & amdgpu_mem_type_to_domain(res_memtype))) vm->stats[bo_memtype].evicted += size; }} /** * amdgpu_vm_update_stats - helper to update normal memory stat * @base: base structure for tracking BO usage in a VM * @res: the ttm_resource to use for the purpose of accounting, may or may not * be bo->tbo.resource * @sign: if we should add (+1) or subtract (-1) from the stat * * Updates the basic memory stat when bo is added/deleted/moved. */void amdgpu_vm_update_stats(struct amdgpu_vm_bo_base *base, struct ttm_resource *res, int sign){ struct amdgpu_vm *vm = base->vm; spin_lock(&vm->stats_lock); amdgpu_vm_update_stats_locked(base, res, sign); spin_unlock(&vm->stats_lock);} /** * amdgpu_vm_bo_base_init - Adds bo to the list of bos associated with the vm * * @base: base structure for tracking BO usage in a VM * @vm: vm to which bo is to be added * @bo: amdgpu buffer object * * Initialize a bo_va_base structure and add it to the appropriate lists * */void amdgpu_vm_bo_base_init(struct amdgpu_vm_bo_base *base, struct amdgpu_vm *vm, struct amdgpu_bo *bo){ base->vm = vm; base->bo = bo; base->next = NULL; INIT_LIST_HEAD(&base->vm_status); dma_resv_assert_held(vm->root.bo->tbo.base.resv); if (!bo) return; base->next = bo->vm_bo; bo->vm_bo = base; spin_lock(&vm->stats_lock); base->shared = drm_gem_object_is_shared_for_memory_stats(&bo->tbo.base); amdgpu_vm_update_stats_locked(base, bo->tbo.resource, +1); spin_unlock(&vm->stats_lock); if (!amdgpu_vm_is_bo_always_valid(vm, bo)) { amdgpu_vm_bo_idle(base); return; } ttm_bo_set_bulk_move(&bo->tbo, &vm->lru_bulk_move); /* * When a per VM isn't in the desired domain put it into the evicted * state to make sure that it gets validated on the next best occasion. */ if (bo->preferred_domains & amdgpu_mem_type_to_domain(bo->tbo.resource->mem_type)) amdgpu_vm_bo_needs_update(base); else amdgpu_vm_bo_evicted(base);} /** * amdgpu_vm_lock_pd - lock PD in drm_exec * * @vm: vm providing the BOs * @exec: drm execution context * @num_fences: number of extra fences to reserve * * Lock the VM root PD in the DRM execution context. */int amdgpu_vm_lock_pd(struct amdgpu_vm *vm, struct drm_exec *exec, unsigned int num_fences){ /* We need at least two fences for the VM PD/PT updates */ return drm_exec_prepare_obj(exec, &vm->root.bo->tbo.base, 2 + num_fences);} /** * amdgpu_vm_lock_individual - lock all BOs on the individual idle list * @vm: vm providing the BOs * @exec: drm execution context * @num_fences: number of extra fences to reserve * * Lock the BOs on the individual idle list in the DRM execution context. */int amdgpu_vm_lock_individual(struct amdgpu_vm *vm, struct drm_exec *exec, unsigned int num_fences){ struct list_head *prev = &vm->individual.idle; struct amdgpu_bo_va *bo_va; struct amdgpu_bo *bo; int ret; /* We can only trust prev->next while holding the lock */ spin_lock(&vm->individual_lock); while (!list_is_head(prev->next, &vm->individual.idle)) { bo_va = list_entry(prev->next, typeof(*bo_va), base.vm_status); bo = bo_va->base.bo; if (bo) { amdgpu_bo_ref(bo); spin_unlock(&vm->individual_lock); ret = drm_exec_prepare_obj(exec, &bo->tbo.base, num_fences); amdgpu_bo_unref(&bo); if (unlikely(ret)) return ret; spin_lock(&vm->individual_lock); } prev = prev->next; } spin_unlock(&vm->individual_lock); return 0;} /**Lines 1–500 of 3241. Load more
/* * 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/dma-fence-array.h> #include <linux/interval_tree_generic.h> #include <linux/idr.h> #include <linux/dma-buf.h> #include <drm/amdgpu_drm.h> #include <drm/drm_drv.h> #include <drm/ttm/ttm_tt.h> #include <drm/drm_exec.h> #include "amdgpu.h" #include "amdgpu_vm.h" #include "amdgpu_trace.h" #include "amdgpu_amdkfd.h" #include "amdgpu_gmc.h" #include "amdgpu_xgmi.h" #include "amdgpu_dma_buf.h" #include "amdgpu_res_cursor.h" #include "kfd_svm.h" /** * DOC: GPUVM * * GPUVM is the MMU functionality provided on the GPU. * GPUVM is similar to the legacy GART on older asics, however * rather than there being a single global GART table * for the entire GPU, there can be multiple GPUVM page tables active * at any given time. The GPUVM page tables can contain a mix * VRAM pages and system pages (both memory and MMIO) and system pages * can be mapped as snooped (cached system pages) or unsnooped * (uncached system pages). * * Each active GPUVM has an ID associated with it and there is a page table * linked with each VMID. When executing a command buffer, * the kernel tells the engine what VMID to use for that command * buffer. VMIDs are allocated dynamically as commands are submitted. * The userspace drivers maintain their own address space and the kernel * sets up their pages tables accordingly when they submit their * command buffers and a VMID is assigned. * The hardware supports up to 16 active GPUVMs at any given time. * * Each GPUVM is represented by a 1-2 or 1-5 level page table, depending * on the ASIC family. GPUVM supports RWX attributes on each page as well * as other features such as encryption and caching attributes. * * VMID 0 is special. It is the GPUVM used for the kernel driver. In * addition to an aperture managed by a page table, VMID 0 also has * several other apertures. There is an aperture for direct access to VRAM * and there is a legacy AGP aperture which just forwards accesses directly * to the matching system physical addresses (or IOVAs when an IOMMU is * present). These apertures provide direct access to these memories without * incurring the overhead of a page table. VMID 0 is used by the kernel * driver for tasks like memory management. * * GPU clients (i.e., engines on the GPU) use GPUVM VMIDs to access memory. * For user applications, each application can have their own unique GPUVM * address space. The application manages the address space and the kernel * driver manages the GPUVM page tables for each process. If an GPU client * accesses an invalid page, it will generate a GPU page fault, similar to * accessing an invalid page on a CPU. */ #define START(node) ((node)->start) #define LAST(node) ((node)->last) INTERVAL_TREE_DEFINE(struct amdgpu_bo_va_mapping, rb, uint64_t, __subtree_last, START, LAST, static, amdgpu_vm_it) #undef START #undef LAST /** * struct amdgpu_prt_cb - Helper to disable partial resident texture feature from a fence callback */ struct amdgpu_prt_cb { /** * @adev: amdgpu device */ struct amdgpu_device *adev; /** * @cb: callback */ struct dma_fence_cb cb; }; /** * struct amdgpu_vm_tlb_seq_struct - Helper to increment the TLB flush sequence */ struct amdgpu_vm_tlb_seq_struct { /** * @vm: pointer to the amdgpu_vm structure to set the fence sequence on */ struct amdgpu_vm *vm; /** * @cb: callback */ struct dma_fence_cb cb; }; /** * amdgpu_vm_assert_locked - check if VM is correctly locked * @vm: the VM which schould be tested * * Asserts that the VM root PD is locked. */ static void amdgpu_vm_assert_locked(struct amdgpu_vm *vm) { dma_resv_assert_held(vm->root.bo->tbo.base.resv); } /* Initialize the amdgpu_vm_bo_status object */ static void amdgpu_vm_bo_status_init(struct amdgpu_vm_bo_status *lists) { INIT_LIST_HEAD(&lists->evicted); INIT_LIST_HEAD(&lists->needs_update); INIT_LIST_HEAD(&lists->idle); } /* * Make sure we have the lock to modify the vm_bo status and return the object * with the status lists. */ static struct amdgpu_vm_bo_status * amdgpu_vm_bo_lock_lists(struct amdgpu_vm_bo_base *vm_bo) { struct amdgpu_vm *vm = vm_bo->vm; struct amdgpu_bo *bo = vm_bo->bo; if (amdgpu_vm_is_bo_always_valid(vm, bo)) { /* No extra locking needed, protected by the root PD resv lock */ amdgpu_vm_assert_locked(vm); if (bo->tbo.type == ttm_bo_type_kernel) return &vm->kernel; return &vm->always_valid; } spin_lock(&vm_bo->vm->individual_lock); return &vm->individual; } /* Eventually unlock the status list lock again */ static void amdgpu_vm_bo_unlock_lists(struct amdgpu_vm_bo_base *vm_bo) { if (amdgpu_vm_is_bo_always_valid(vm_bo->vm, vm_bo->bo)) amdgpu_vm_assert_locked(vm_bo->vm); else spin_unlock(&vm_bo->vm->individual_lock); } /** * amdgpu_vm_is_bo_always_valid - check if the BO is VM always valid * * @vm: VM to test against. * @bo: BO to be tested. * * Returns true if the BO shares the dma_resv object with the root PD and is * always guaranteed to be valid inside the VM. */ bool amdgpu_vm_is_bo_always_valid(struct amdgpu_vm *vm, struct amdgpu_bo *bo) { return bo && bo->tbo.base.resv == vm->root.bo->tbo.base.resv; } /** * amdgpu_vm_bo_evicted - vm_bo is evicted * * @vm_bo: vm_bo which is evicted * * State for vm_bo objects meaning the underlying BO was evicted and need to * move in place again. */ static void amdgpu_vm_bo_evicted(struct amdgpu_vm_bo_base *vm_bo) { struct amdgpu_vm_bo_status *lists; lists = amdgpu_vm_bo_lock_lists(vm_bo); vm_bo->moved = true; list_move(&vm_bo->vm_status, &lists->evicted); amdgpu_vm_bo_unlock_lists(vm_bo); } /** * amdgpu_vm_bo_needs_update - vm_bo needs pagetable update * * @vm_bo: vm_bo which is out of date * * State for vm_bo objects meaning the underlying BO had mapping changes (move, PRT bind/unbind) * but the new location is not yet reflected in the page tables. */ static void amdgpu_vm_bo_needs_update(struct amdgpu_vm_bo_base *vm_bo) { struct amdgpu_vm_bo_status *lists; struct amdgpu_bo *bo = vm_bo->bo; /* * The root PD doesn't have a parent PDE and goes directly into the * idle state. */ lists = amdgpu_vm_bo_lock_lists(vm_bo); if (bo && bo->tbo.type == ttm_bo_type_kernel && !bo->parent) { vm_bo->moved = false; list_move(&vm_bo->vm_status, &lists->idle); } else { list_move(&vm_bo->vm_status, &lists->needs_update); } amdgpu_vm_bo_unlock_lists(vm_bo); } /** * amdgpu_vm_bo_idle - vm_bo is idle * * @vm_bo: vm_bo which is now idle * * State for vm_bo objects meaning we are done with the state machine and no * further action is necessary. */ static void amdgpu_vm_bo_idle(struct amdgpu_vm_bo_base *vm_bo) { struct amdgpu_vm_bo_status *lists; lists = amdgpu_vm_bo_lock_lists(vm_bo); if (!amdgpu_vm_is_bo_always_valid(vm_bo->vm, vm_bo->bo)) vm_bo->moved = false; list_move(&vm_bo->vm_status, &lists->idle); amdgpu_vm_bo_unlock_lists(vm_bo); } /** * amdgpu_vm_bo_reset_state_machine - reset the vm_bo state machine * @vm: the VM which state machine to reset * * Move all vm_bo object in the VM into a state where their location will be * updated in the page tables again. */ static void amdgpu_vm_bo_reset_state_machine(struct amdgpu_vm *vm) { struct amdgpu_vm_bo_base *vm_bo, *tmp; /* * Don't use list splice here, we need the special handling for the root * PD and set the moved flag appropriately. */ amdgpu_vm_assert_locked(vm); list_for_each_entry_safe(vm_bo, tmp, &vm->kernel.idle, vm_status) amdgpu_vm_bo_needs_update(vm_bo); list_for_each_entry_safe(vm_bo, tmp, &vm->always_valid.idle, vm_status) amdgpu_vm_bo_needs_update(vm_bo); spin_lock(&vm->individual_lock); list_for_each_entry_safe(vm_bo, tmp, &vm->individual.idle, vm_status) { vm_bo->moved = true; list_move(&vm_bo->vm_status, &vm->individual.needs_update); } spin_unlock(&vm->individual_lock); } /** * amdgpu_vm_update_shared - helper to update shared memory stat * @base: base structure for tracking BO usage in a VM * * Takes the vm stats_lock and updates the shared memory stat. If the basic * stat changed (e.g. buffer was moved) amdgpu_vm_update_stats need to be called * as well. */ static void amdgpu_vm_update_shared(struct amdgpu_vm_bo_base *base) { struct amdgpu_vm *vm = base->vm; struct amdgpu_bo *bo = base->bo; uint64_t size = amdgpu_bo_size(bo); uint32_t bo_memtype = amdgpu_bo_mem_stats_placement(bo); bool shared; dma_resv_assert_held(bo->tbo.base.resv); spin_lock(&vm->stats_lock); shared = drm_gem_object_is_shared_for_memory_stats(&bo->tbo.base); if (base->shared != shared) { base->shared = shared; if (shared) { vm->stats[bo_memtype].drm.shared += size; vm->stats[bo_memtype].drm.private -= size; } else { vm->stats[bo_memtype].drm.shared -= size; vm->stats[bo_memtype].drm.private += size; } } spin_unlock(&vm->stats_lock); } /** * amdgpu_vm_bo_update_shared - callback when bo gets shared/unshared * @bo: amdgpu buffer object * * Update the per VM stats for all the vm if needed from private to shared or * vice versa. */ void amdgpu_vm_bo_update_shared(struct amdgpu_bo *bo) { struct amdgpu_vm_bo_base *base; for (base = bo->vm_bo; base; base = base->next) amdgpu_vm_update_shared(base); } /** * amdgpu_vm_update_stats_locked - helper to update normal memory stat * @base: base structure for tracking BO usage in a VM * @res: the ttm_resource to use for the purpose of accounting, may or may not * be bo->tbo.resource * @sign: if we should add (+1) or subtract (-1) from the stat * * Caller need to have the vm stats_lock held. Useful for when multiple update * need to happen at the same time. */ static void amdgpu_vm_update_stats_locked(struct amdgpu_vm_bo_base *base, struct ttm_resource *res, int sign) { struct amdgpu_vm *vm = base->vm; struct amdgpu_bo *bo = base->bo; int64_t size = sign * amdgpu_bo_size(bo); uint32_t bo_memtype = amdgpu_bo_mem_stats_placement(bo); /* For drm-total- and drm-shared-, BO are accounted by their preferred * placement, see also amdgpu_bo_mem_stats_placement. */ if (base->shared) vm->stats[bo_memtype].drm.shared += size; else vm->stats[bo_memtype].drm.private += size; if (res && res->mem_type < __AMDGPU_PL_NUM) { uint32_t res_memtype = res->mem_type; vm->stats[res_memtype].drm.resident += size; /* BO only count as purgeable if it is resident, * since otherwise there's nothing to purge. */ if (bo->flags & AMDGPU_GEM_CREATE_DISCARDABLE) vm->stats[res_memtype].drm.purgeable += size; if (!(bo->preferred_domains & amdgpu_mem_type_to_domain(res_memtype))) vm->stats[bo_memtype].evicted += size; } } /** * amdgpu_vm_update_stats - helper to update normal memory stat * @base: base structure for tracking BO usage in a VM * @res: the ttm_resource to use for the purpose of accounting, may or may not * be bo->tbo.resource * @sign: if we should add (+1) or subtract (-1) from the stat * * Updates the basic memory stat when bo is added/deleted/moved. */ void amdgpu_vm_update_stats(struct amdgpu_vm_bo_base *base, struct ttm_resource *res, int sign) { struct amdgpu_vm *vm = base->vm; spin_lock(&vm->stats_lock); amdgpu_vm_update_stats_locked(base, res, sign); spin_unlock(&vm->stats_lock); } /** * amdgpu_vm_bo_base_init - Adds bo to the list of bos associated with the vm * * @base: base structure for tracking BO usage in a VM * @vm: vm to which bo is to be added * @bo: amdgpu buffer object * * Initialize a bo_va_base structure and add it to the appropriate lists * */ void amdgpu_vm_bo_base_init(struct amdgpu_vm_bo_base *base, struct amdgpu_vm *vm, struct amdgpu_bo *bo) { base->vm = vm; base->bo = bo; base->next = NULL; INIT_LIST_HEAD(&base->vm_status); dma_resv_assert_held(vm->root.bo->tbo.base.resv); if (!bo) return; base->next = bo->vm_bo; bo->vm_bo = base; spin_lock(&vm->stats_lock); base->shared = drm_gem_object_is_shared_for_memory_stats(&bo->tbo.base); amdgpu_vm_update_stats_locked(base, bo->tbo.resource, +1); spin_unlock(&vm->stats_lock); if (!amdgpu_vm_is_bo_always_valid(vm, bo)) { amdgpu_vm_bo_idle(base); return; } ttm_bo_set_bulk_move(&bo->tbo, &vm->lru_bulk_move); /* * When a per VM isn't in the desired domain put it into the evicted * state to make sure that it gets validated on the next best occasion. */ if (bo->preferred_domains & amdgpu_mem_type_to_domain(bo->tbo.resource->mem_type)) amdgpu_vm_bo_needs_update(base); else amdgpu_vm_bo_evicted(base); } /** * amdgpu_vm_lock_pd - lock PD in drm_exec * * @vm: vm providing the BOs * @exec: drm execution context * @num_fences: number of extra fences to reserve * * Lock the VM root PD in the DRM execution context. */ int amdgpu_vm_lock_pd(struct amdgpu_vm *vm, struct drm_exec *exec, unsigned int num_fences) { /* We need at least two fences for the VM PD/PT updates */ return drm_exec_prepare_obj(exec, &vm->root.bo->tbo.base, 2 + num_fences); } /** * amdgpu_vm_lock_individual - lock all BOs on the individual idle list * @vm: vm providing the BOs * @exec: drm execution context * @num_fences: number of extra fences to reserve * * Lock the BOs on the individual idle list in the DRM execution context. */ int amdgpu_vm_lock_individual(struct amdgpu_vm *vm, struct drm_exec *exec, unsigned int num_fences) { struct list_head *prev = &vm->individual.idle; struct amdgpu_bo_va *bo_va; struct amdgpu_bo *bo; int ret; /* We can only trust prev->next while holding the lock */ spin_lock(&vm->individual_lock); while (!list_is_head(prev->next, &vm->individual.idle)) { bo_va = list_entry(prev->next, typeof(*bo_va), base.vm_status); bo = bo_va->base.bo; if (bo) { amdgpu_bo_ref(bo); spin_unlock(&vm->individual_lock); ret = drm_exec_prepare_obj(exec, &bo->tbo.base, num_fences); amdgpu_bo_unref(&bo); if (unlikely(ret)) return ret; spin_lock(&vm->individual_lock); } prev = prev->next; } spin_unlock(&vm->individual_lock); return 0; } /**