/*
* Copyright 2014-2018 Advanced Micro Devices, Inc.
*
* 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.
*/
#define pr_fmt(fmt) "kfd2kgd: " fmt
#include <linux/list.h>
#include <drm/drmP.h>
#include "amdgpu_object.h"
#include "amdgpu_vm.h"
#include "amdgpu_amdkfd.h"
/* Special VM and GART address alignment needed for VI pre-Fiji due to
* a HW bug.
*/
#define VI_BO_SIZE_ALIGN (0x8000)
/* Impose limit on how much memory KFD can use */
static struct {
uint64_t max_system_mem_limit;
int64_t system_mem_used;
spinlock_t mem_limit_lock;
} kfd_mem_limit;
/* Struct used for amdgpu_amdkfd_bo_validate */
struct amdgpu_vm_parser {
uint32_t domain;
bool wait;
};
static const char * const domain_bit_to_string[] = {
"CPU",
"GTT",
"VRAM",
"GDS",
"GWS",
"OA"
};
#define domain_string(domain) domain_bit_to_string[ffs(domain)-1]
static inline struct amdgpu_device *get_amdgpu_device(struct kgd_dev *kgd)
{
return (struct amdgpu_device *)kgd;
}
static bool check_if_add_bo_to_vm(struct amdgpu_vm *avm,
struct kgd_mem *mem)
{
struct kfd_bo_va_list *entry;
list_for_each_entry(entry, &mem->bo_va_list, bo_list)
if (entry->bo_va->base.vm == avm)
return false;
return true;
}
/* Set memory usage limits. Current, limits are
* System (kernel) memory - 3/8th System RAM
*/
void amdgpu_amdkfd_gpuvm_init_mem_limits(void)
{
struct sysinfo si;
uint64_t mem;
si_meminfo(&si);
mem = si.totalram - si.totalhigh;
mem *= si.mem_unit;
spin_lock_init(&kfd_mem_limit.mem_limit_lock);
kfd_mem_limit.max_system_mem_limit = (mem >> 1) - (mem >> 3);
pr_debug("Kernel memory limit %lluM\n",
(kfd_mem_limit.max_system_mem_limit >> 20));
}
static int amdgpu_amdkfd_reserve_system_mem_limit(struct amdgpu_device *adev,
uint64_t size, u32 domain)
{
size_t acc_size;
int ret = 0;
acc_size = ttm_bo_dma_acc_size(&adev->mman.bdev, size,
sizeof(struct amdgpu_bo));
spin_lock(&kfd_mem_limit.mem_limit_lock);
if (domain == AMDGPU_GEM_DOMAIN_GTT) {
if (kfd_mem_limit.system_mem_used + (acc_size + size) >
kfd_mem_limit.max_system_mem_limit) {
ret = -ENOMEM;
goto err_no_mem;
}
kfd_mem_limit.system_mem_used += (acc_size + size);
}
err_no_mem:
spin_unlock(&kfd_mem_limit.mem_limit_lock);
return ret;
}
static void unreserve_system_mem_limit(struct amdgpu_device *adev,
uint64_t size, u32 domain)
{
size_t acc_size;
acc_size = ttm_bo_dma_acc_size(&adev->mman.bdev, size,
sizeof(struct amdgpu_bo));
spin_lock(&kfd_mem_limit.mem_limit_lock);
if (domain == AMDGPU_GEM_DOMAIN_GTT)
kfd_mem_limit.system_mem_used -= (acc_size + size);
WARN_ONCE(kfd_mem_limit.system_mem_used < 0,
"kfd system memory accounting unbalanced");
spin_unlock(&kfd_mem_limit.mem_limit_lock);
}
void amdgpu_amdkfd_unreserve_system_memory_limit(struct amdgpu_bo *bo)
{
spin_lock(&kfd_mem_limit.mem_limit_lock);
if (bo->preferred_domains == AMDGPU_GEM_DOMAIN_GTT) {
kfd_mem_limit.system_mem_used -=
(bo->tbo.acc_size + amdgpu_bo_size(bo));
}
WARN_ONCE(kfd_mem_limit.system_mem_used < 0,
"kfd system memory accounting unbalanced");
spin_unlock(&kfd_mem_limit.mem_limit_lock);
}
/* amdgpu_amdkfd_remove_eviction_fence - Removes eviction fence(s) from BO's
* reservation object.
*
* @bo: [IN] Remove eviction fence(s) from this BO
* @ef: [IN] If ef is specified, then this eviction fence is removed if it
* is present in the shared list.
* @ef_list: [OUT] Returns list of eviction fences. These fences are removed
* from BO's reservation object shared list.
* @ef_count: [OUT] Number of fences in ef_list.
*
* NOTE: If called with ef_list, then amdgpu_amdkfd_add_eviction_fence must be
* called to restore the eviction fences and to avoid memory leak. This is
* useful for shared BOs.
* NOTE: Must be called with BO reserved i.e. bo->tbo.resv->lock held.
*/
static int amdgpu_amdkfd_remove_eviction_fence(struct amdgpu_bo *bo,
struct amdgpu_amdkfd_fence *ef,
struct amdgpu_amdkfd_fence ***ef_list,
unsigned int *ef_count)
{
struct reservation_object_list *fobj;
struct reservation_object *resv;
unsigned int i = 0, j = 0, k = 0, shared_count;
unsigned int count = 0;
struct amdgpu_amdkfd_fence **fence_list;
if (!ef && !ef_list)
return -EINVAL;
if (ef_list) {
*ef_list = NULL;
*ef_count = 0;
}
resv = bo->tbo.resv;
fobj = reservation_object_get_list(resv);
if (!fobj)
return 0;
preempt_disable();
write_seqcount_begin(&resv->seq);
/* Go through all the shared fences in the resevation object. If
* ef is specified and it exists in the list, remove it and reduce the
* count. If ef is not specified, then get the count of eviction fences
* present.
*/
shared_count = fobj->shared_count;
for (i = 0; i < shared_count; ++i) {
struct dma_fence *f;
f = rcu_dereference_protected(fobj->shared[i],
reservation_object_held(resv));
if (ef) {
if (f->context == ef->base.context) {
dma_fence_put(f);
fobj->shared_count--;
} else {
RCU_INIT_POINTER(fobj->shared[j++], f);
}
} else if (to_amdgpu_amdkfd_fence(f))
count++;
}
write_seqcount_end(&resv->seq);
preempt_enable();
if (ef || !count)
return 0;
/* Alloc memory for count number of eviction fence pointers. Fill the
* ef_list array and ef_count
*/
fence_list = kcalloc(count, sizeof(struct amdgpu_amdkfd_fence *),
GFP_KERNEL);
if (!fence_list)
return -ENOMEM;
preempt_disable();
write_seqcount_begin(&resv->seq);
j = 0;
for (i = 0; i < shared_count; ++i) {
struct dma_fence *f;
struct amdgpu_amdkfd_fence *efence;
f = rcu_dereference_protected(fobj->shared[i],
reservation_object_held(resv));
efence = to_amdgpu_amdkfd_fence(f);
if (efence) {
fence_list[k++] = efence;
fobj->shared_count--;
} else {
RCU_INIT_POINTER(fobj->shared[j++], f);
}
}
write_seqcount_end(&resv->seq);
preempt_enable();
*ef_list = fence_list;
*ef_count = k;
return 0;
}
/* amdgpu_amdkfd_add_eviction_fence - Adds eviction fence(s) back into BO's
* reservation object.
*
* @bo: [IN] Add eviction fences to this BO
* @ef_list: [IN] List of eviction fences to be added
* @ef_count: [IN] Number of fences in ef_list.
*
* NOTE: Must call amdgpu_amdkfd_remove_eviction_fence before calling this
* function.
*/
static void amdgpu_amdkfd_add_eviction_fence(struct amdgpu_bo *bo,
struct amdgpu_amdkfd_fence **ef_list,
unsigned int ef_count)
{
int i;
if (!ef_list || !ef_count)
return;
for (i = 0; i < ef_count; i++) {
amdgpu_bo_fence(bo, &ef_list[i]->base, true);
/* Re-adding the fence takes an additional reference. Drop that
* reference.
*/
dma_fence_put(&ef_list[i]->base);
}
|