mirror of
git://nv-tegra.nvidia.com/linux-nvgpu.git
synced 2025-12-23 01:50:07 +03:00
Add error prints for DMA alloc failures. This way when there is a DMA alloc failure the failure is clear. Without this it's hard to know what exactly is causing any given -ENOMEM issue or what the specifics of said ENOMEM case are. Change-Id: Ia535895ae07bc1704edaed564edbb6f6dfbf6518 Signed-off-by: Alex Waterman <alexw@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/1976441 GVS: Gerrit_Virtual_Submit Reviewed-by: Vinod Gopalakrishnakurup <vinodg@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com> Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
510 lines
12 KiB
C
510 lines
12 KiB
C
/*
|
|
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/version.h>
|
|
|
|
#include <nvgpu/log.h>
|
|
#include <nvgpu/dma.h>
|
|
#include <nvgpu/lock.h>
|
|
#include <nvgpu/bug.h>
|
|
#include <nvgpu/gmmu.h>
|
|
#include <nvgpu/kmem.h>
|
|
#include <nvgpu/enabled.h>
|
|
#include <nvgpu/vidmem.h>
|
|
#include <nvgpu/gk20a.h>
|
|
#include <nvgpu/nvgpu_sgt.h>
|
|
|
|
#include <nvgpu/linux/dma.h>
|
|
|
|
#include "platform_gk20a.h"
|
|
#include "os_linux.h"
|
|
#include "dmabuf_vidmem.h"
|
|
|
|
/*
|
|
* Enough to hold all the possible flags in string form. When a new flag is
|
|
* added it must be added here as well!!
|
|
*/
|
|
#define NVGPU_DMA_STR_SIZE \
|
|
sizeof("NO_KERNEL_MAPPING FORCE_CONTIGUOUS PHYSICALLY_ADDRESSED")
|
|
|
|
/*
|
|
* This function can't fail. It will always at minimum memset() the buf which
|
|
* is assumed to be able to hold at least %NVGPU_DMA_STR_SIZE bytes.
|
|
*/
|
|
void nvgpu_dma_flags_to_str(struct gk20a *g, unsigned long flags, char *buf)
|
|
{
|
|
int bytes_available = NVGPU_DMA_STR_SIZE;
|
|
|
|
memset(buf, 0, NVGPU_DMA_STR_SIZE);
|
|
|
|
#define APPEND_FLAG(flag, str_flag) \
|
|
do { \
|
|
if (flags & flag) { \
|
|
strncat(buf, str_flag, bytes_available); \
|
|
bytes_available -= strlen(str_flag); \
|
|
} \
|
|
} while (false)
|
|
|
|
APPEND_FLAG(NVGPU_DMA_NO_KERNEL_MAPPING, "NO_KERNEL_MAPPING ");
|
|
APPEND_FLAG(NVGPU_DMA_FORCE_CONTIGUOUS, "FORCE_CONTIGUOUS ");
|
|
APPEND_FLAG(NVGPU_DMA_PHYSICALLY_ADDRESSED, "PHYSICALLY_ADDRESSED");
|
|
#undef APPEND_FLAG
|
|
}
|
|
|
|
/**
|
|
* __dma_dbg - Debug print for DMA allocs and frees.
|
|
*
|
|
* @g - The GPU.
|
|
* @size - The requested size of the alloc (size_t).
|
|
* @flags - The flags (unsigned long).
|
|
* @type - A string describing the type (i.e: sysmem or vidmem).
|
|
* @what - A string with 'alloc' or 'free'.
|
|
*
|
|
* @flags is the DMA flags. If there are none or it doesn't make sense to print
|
|
* flags just pass 0.
|
|
*
|
|
* Please use dma_dbg_alloc() and dma_dbg_free() instead of this function.
|
|
*/
|
|
static void __dma_dbg(struct gk20a *g, size_t size, unsigned long flags,
|
|
const char *type, const char *what,
|
|
const char *func, int line)
|
|
{
|
|
char flags_str[NVGPU_DMA_STR_SIZE];
|
|
|
|
/*
|
|
* Don't bother making the flags_str if debugging is not enabled.
|
|
*/
|
|
if (!nvgpu_log_mask_enabled(g, gpu_dbg_dma))
|
|
return;
|
|
|
|
nvgpu_dma_flags_to_str(g, flags, flags_str);
|
|
|
|
__nvgpu_log_dbg(g, gpu_dbg_dma,
|
|
func, line,
|
|
"DMA %s: [%s] size=%-7zu "
|
|
"aligned=%-7zu total=%-10llukB %s",
|
|
what, type,
|
|
size, PAGE_ALIGN(size),
|
|
g->dma_memory_used >> 10,
|
|
flags_str);
|
|
}
|
|
|
|
static void nvgpu_dma_print_err(struct gk20a *g, size_t size,
|
|
const char *type, const char *what,
|
|
unsigned long flags)
|
|
{
|
|
char flags_str[NVGPU_DMA_STR_SIZE];
|
|
|
|
nvgpu_dma_flags_to_str(g, flags, flags_str);
|
|
|
|
nvgpu_err(g,
|
|
"DMA %s FAILED: [%s] size=%-7zu "
|
|
"aligned=%-7zu flags:%s",
|
|
what, type,
|
|
size, PAGE_ALIGN(size), flags_str);
|
|
}
|
|
|
|
#define dma_dbg_alloc(g, size, flags, type) \
|
|
__dma_dbg(g, size, flags, type, "alloc", __func__, __LINE__)
|
|
#define dma_dbg_free(g, size, flags, type) \
|
|
__dma_dbg(g, size, flags, type, "free", __func__, __LINE__)
|
|
|
|
/*
|
|
* For after the DMA alloc is done.
|
|
*/
|
|
#define __dma_dbg_done(g, size, type, what) \
|
|
nvgpu_log(g, gpu_dbg_dma, \
|
|
"DMA %s: [%s] size=%-7zu Done!", \
|
|
what, type, size); \
|
|
|
|
#define dma_dbg_alloc_done(g, size, type) \
|
|
__dma_dbg_done(g, size, type, "alloc")
|
|
#define dma_dbg_free_done(g, size, type) \
|
|
__dma_dbg_done(g, size, type, "free")
|
|
|
|
#if defined(CONFIG_GK20A_VIDMEM)
|
|
static u64 __nvgpu_dma_alloc(struct nvgpu_allocator *allocator, u64 at,
|
|
size_t size)
|
|
{
|
|
u64 addr = 0;
|
|
|
|
if (at)
|
|
addr = nvgpu_alloc_fixed(allocator, at, size, 0);
|
|
else
|
|
addr = nvgpu_alloc(allocator, size);
|
|
|
|
return addr;
|
|
}
|
|
#endif
|
|
|
|
static void nvgpu_dma_flags_to_attrs(unsigned long *attrs,
|
|
unsigned long flags)
|
|
{
|
|
if (flags & NVGPU_DMA_NO_KERNEL_MAPPING)
|
|
*attrs |= DMA_ATTR_NO_KERNEL_MAPPING;
|
|
if (flags & NVGPU_DMA_FORCE_CONTIGUOUS)
|
|
*attrs |= DMA_ATTR_FORCE_CONTIGUOUS;
|
|
}
|
|
|
|
int nvgpu_dma_alloc_flags_sys(struct gk20a *g, unsigned long flags,
|
|
size_t size, struct nvgpu_mem *mem)
|
|
{
|
|
struct device *d = dev_from_gk20a(g);
|
|
dma_addr_t iova;
|
|
unsigned long dma_attrs = 0;
|
|
void *alloc_ret;
|
|
int err;
|
|
|
|
if (nvgpu_mem_is_valid(mem)) {
|
|
nvgpu_warn(g, "memory leak !!");
|
|
WARN_ON(1);
|
|
}
|
|
|
|
if ((flags & NVGPU_DMA_PHYSICALLY_ADDRESSED) &&
|
|
(!nvgpu_iommuable(g) ||
|
|
nvgpu_is_enabled(g, NVGPU_MM_USE_PHYSICAL_SG))) {
|
|
flags |= NVGPU_DMA_FORCE_CONTIGUOUS;
|
|
}
|
|
|
|
/*
|
|
* Before the debug print so we see this in the total. But during
|
|
* cleanup in the fail path this has to be subtracted.
|
|
*/
|
|
g->dma_memory_used += PAGE_ALIGN(size);
|
|
|
|
dma_dbg_alloc(g, size, flags, "sysmem");
|
|
|
|
/*
|
|
* Save the old size but for actual allocation purposes the size is
|
|
* going to be page aligned.
|
|
*/
|
|
mem->size = size;
|
|
size = PAGE_ALIGN(size);
|
|
|
|
nvgpu_dma_flags_to_attrs(&dma_attrs, flags);
|
|
alloc_ret = dma_alloc_attrs(d, size, &iova,
|
|
GFP_KERNEL|__GFP_ZERO, dma_attrs);
|
|
if (!alloc_ret) {
|
|
err = -ENOMEM;
|
|
goto print_dma_err;
|
|
}
|
|
|
|
if (flags & NVGPU_DMA_NO_KERNEL_MAPPING) {
|
|
mem->priv.pages = alloc_ret;
|
|
err = nvgpu_get_sgtable_from_pages(g, &mem->priv.sgt,
|
|
mem->priv.pages,
|
|
iova, size);
|
|
} else {
|
|
mem->cpu_va = alloc_ret;
|
|
err = nvgpu_get_sgtable_attrs(g, &mem->priv.sgt, mem->cpu_va,
|
|
iova, size, flags);
|
|
}
|
|
if (err)
|
|
goto fail_free_dma;
|
|
|
|
mem->aligned_size = size;
|
|
mem->aperture = APERTURE_SYSMEM;
|
|
mem->priv.flags = flags;
|
|
|
|
dma_dbg_alloc_done(g, mem->size, "sysmem");
|
|
|
|
return 0;
|
|
|
|
fail_free_dma:
|
|
dma_free_attrs(d, size, alloc_ret, iova, dma_attrs);
|
|
mem->cpu_va = NULL;
|
|
mem->priv.sgt = NULL;
|
|
mem->size = 0;
|
|
g->dma_memory_used -= mem->aligned_size;
|
|
print_dma_err:
|
|
nvgpu_dma_print_err(g, size, "sysmem", "alloc", flags);
|
|
return err;
|
|
}
|
|
|
|
int nvgpu_dma_alloc_flags_vid_at(struct gk20a *g, unsigned long flags,
|
|
size_t size, struct nvgpu_mem *mem, u64 at)
|
|
{
|
|
#if defined(CONFIG_GK20A_VIDMEM)
|
|
u64 addr;
|
|
int err;
|
|
struct nvgpu_allocator *vidmem_alloc = g->mm.vidmem.cleared ?
|
|
&g->mm.vidmem.allocator :
|
|
&g->mm.vidmem.bootstrap_allocator;
|
|
u64 before_pending;
|
|
|
|
if (nvgpu_mem_is_valid(mem)) {
|
|
nvgpu_warn(g, "memory leak !!");
|
|
WARN_ON(1);
|
|
}
|
|
|
|
dma_dbg_alloc(g, size, flags, "vidmem");
|
|
|
|
mem->size = size;
|
|
size = PAGE_ALIGN(size);
|
|
|
|
if (!nvgpu_alloc_initialized(&g->mm.vidmem.allocator)) {
|
|
err = -ENOSYS;
|
|
goto print_dma_err;
|
|
}
|
|
|
|
/*
|
|
* Our own allocator doesn't have any flags yet, and we can't
|
|
* kernel-map these, so require explicit flags.
|
|
*/
|
|
WARN_ON(flags != NVGPU_DMA_NO_KERNEL_MAPPING);
|
|
|
|
nvgpu_mutex_acquire(&g->mm.vidmem.clear_list_mutex);
|
|
before_pending = atomic64_read(&g->mm.vidmem.bytes_pending.atomic_var);
|
|
addr = __nvgpu_dma_alloc(vidmem_alloc, at, size);
|
|
nvgpu_mutex_release(&g->mm.vidmem.clear_list_mutex);
|
|
if (!addr) {
|
|
/*
|
|
* If memory is known to be freed soon, let the user know that
|
|
* it may be available after a while.
|
|
*/
|
|
if (before_pending) {
|
|
return -EAGAIN;
|
|
} else {
|
|
err = -ENOMEM;
|
|
goto print_dma_err;
|
|
}
|
|
}
|
|
|
|
if (at)
|
|
mem->mem_flags |= NVGPU_MEM_FLAG_FIXED;
|
|
|
|
mem->priv.sgt = nvgpu_kzalloc(g, sizeof(struct sg_table));
|
|
if (!mem->priv.sgt) {
|
|
err = -ENOMEM;
|
|
goto fail_physfree;
|
|
}
|
|
|
|
err = sg_alloc_table(mem->priv.sgt, 1, GFP_KERNEL);
|
|
if (err)
|
|
goto fail_kfree;
|
|
|
|
nvgpu_vidmem_set_page_alloc(mem->priv.sgt->sgl, addr);
|
|
sg_set_page(mem->priv.sgt->sgl, NULL, size, 0);
|
|
|
|
mem->aligned_size = size;
|
|
mem->aperture = APERTURE_VIDMEM;
|
|
mem->vidmem_alloc = (struct nvgpu_page_alloc *)(uintptr_t)addr;
|
|
mem->allocator = vidmem_alloc;
|
|
mem->priv.flags = flags;
|
|
|
|
nvgpu_init_list_node(&mem->clear_list_entry);
|
|
|
|
dma_dbg_alloc_done(g, mem->size, "vidmem");
|
|
|
|
return 0;
|
|
|
|
fail_kfree:
|
|
nvgpu_kfree(g, mem->priv.sgt);
|
|
fail_physfree:
|
|
nvgpu_free(&g->mm.vidmem.allocator, addr);
|
|
mem->size = 0;
|
|
print_dma_err:
|
|
nvgpu_dma_print_err(g, size, "vidmem", "alloc", flags);
|
|
return err;
|
|
#else
|
|
return -ENOSYS;
|
|
#endif
|
|
}
|
|
|
|
void nvgpu_dma_free_sys(struct gk20a *g, struct nvgpu_mem *mem)
|
|
{
|
|
struct device *d = dev_from_gk20a(g);
|
|
unsigned long dma_attrs = 0;
|
|
|
|
g->dma_memory_used -= mem->aligned_size;
|
|
|
|
dma_dbg_free(g, mem->size, mem->priv.flags, "sysmem");
|
|
|
|
if (!(mem->mem_flags & NVGPU_MEM_FLAG_SHADOW_COPY) &&
|
|
!(mem->mem_flags & __NVGPU_MEM_FLAG_NO_DMA) &&
|
|
(mem->cpu_va || mem->priv.pages)) {
|
|
if (mem->priv.flags) {
|
|
|
|
nvgpu_dma_flags_to_attrs(&dma_attrs, mem->priv.flags);
|
|
|
|
if (mem->priv.flags & NVGPU_DMA_NO_KERNEL_MAPPING) {
|
|
dma_free_attrs(d, mem->aligned_size, mem->priv.pages,
|
|
sg_dma_address(mem->priv.sgt->sgl),
|
|
dma_attrs);
|
|
} else {
|
|
dma_free_attrs(d, mem->aligned_size, mem->cpu_va,
|
|
sg_dma_address(mem->priv.sgt->sgl),
|
|
dma_attrs);
|
|
}
|
|
} else {
|
|
dma_free_coherent(d, mem->aligned_size, mem->cpu_va,
|
|
sg_dma_address(mem->priv.sgt->sgl));
|
|
}
|
|
mem->cpu_va = NULL;
|
|
mem->priv.pages = NULL;
|
|
}
|
|
|
|
/*
|
|
* When this flag is set this means we are freeing a "phys" nvgpu_mem.
|
|
* To handle this just nvgpu_kfree() the nvgpu_sgt and nvgpu_sgl.
|
|
*/
|
|
if (mem->mem_flags & __NVGPU_MEM_FLAG_NO_DMA) {
|
|
nvgpu_kfree(g, mem->phys_sgt->sgl);
|
|
nvgpu_kfree(g, mem->phys_sgt);
|
|
}
|
|
|
|
if ((mem->mem_flags & NVGPU_MEM_FLAG_FOREIGN_SGT) == 0 &&
|
|
mem->priv.sgt != NULL) {
|
|
nvgpu_free_sgtable(g, &mem->priv.sgt);
|
|
}
|
|
|
|
dma_dbg_free_done(g, mem->size, "sysmem");
|
|
|
|
mem->size = 0;
|
|
mem->aligned_size = 0;
|
|
mem->aperture = APERTURE_INVALID;
|
|
}
|
|
|
|
void nvgpu_dma_free_vid(struct gk20a *g, struct nvgpu_mem *mem)
|
|
{
|
|
#if defined(CONFIG_GK20A_VIDMEM)
|
|
size_t mem_size = mem->size;
|
|
|
|
dma_dbg_free(g, mem->size, mem->priv.flags, "vidmem");
|
|
|
|
/* Sanity check - only this supported when allocating. */
|
|
WARN_ON(mem->priv.flags != NVGPU_DMA_NO_KERNEL_MAPPING);
|
|
|
|
if (mem->mem_flags & NVGPU_MEM_FLAG_USER_MEM) {
|
|
int err = nvgpu_vidmem_clear_list_enqueue(g, mem);
|
|
|
|
/*
|
|
* If there's an error here then that means we can't clear the
|
|
* vidmem. That's too bad; however, we still own the nvgpu_mem
|
|
* buf so we have to free that.
|
|
*
|
|
* We don't need to worry about the vidmem allocator itself
|
|
* since when that gets cleaned up in the driver shutdown path
|
|
* all the outstanding allocs are force freed.
|
|
*/
|
|
if (err)
|
|
nvgpu_kfree(g, mem);
|
|
} else {
|
|
nvgpu_memset(g, mem, 0, 0, mem->aligned_size);
|
|
nvgpu_free(mem->allocator,
|
|
(u64)nvgpu_vidmem_get_page_alloc(mem->priv.sgt->sgl));
|
|
nvgpu_free_sgtable(g, &mem->priv.sgt);
|
|
|
|
mem->size = 0;
|
|
mem->aligned_size = 0;
|
|
mem->aperture = APERTURE_INVALID;
|
|
}
|
|
|
|
dma_dbg_free_done(g, mem_size, "vidmem");
|
|
#endif
|
|
}
|
|
|
|
int nvgpu_get_sgtable_attrs(struct gk20a *g, struct sg_table **sgt,
|
|
void *cpuva, u64 iova, size_t size, unsigned long flags)
|
|
{
|
|
int err = 0;
|
|
struct sg_table *tbl;
|
|
unsigned long dma_attrs = 0;
|
|
|
|
tbl = nvgpu_kzalloc(g, sizeof(struct sg_table));
|
|
if (!tbl) {
|
|
err = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
nvgpu_dma_flags_to_attrs(&dma_attrs, flags);
|
|
err = dma_get_sgtable_attrs(dev_from_gk20a(g), tbl, cpuva, iova,
|
|
size, dma_attrs);
|
|
if (err)
|
|
goto fail;
|
|
|
|
sg_dma_address(tbl->sgl) = iova;
|
|
*sgt = tbl;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
if (tbl)
|
|
nvgpu_kfree(g, tbl);
|
|
|
|
return err;
|
|
}
|
|
|
|
int nvgpu_get_sgtable(struct gk20a *g, struct sg_table **sgt,
|
|
void *cpuva, u64 iova, size_t size)
|
|
{
|
|
return nvgpu_get_sgtable_attrs(g, sgt, cpuva, iova, size, 0);
|
|
}
|
|
|
|
int nvgpu_get_sgtable_from_pages(struct gk20a *g, struct sg_table **sgt,
|
|
struct page **pages, u64 iova, size_t size)
|
|
{
|
|
int err = 0;
|
|
struct sg_table *tbl;
|
|
|
|
tbl = nvgpu_kzalloc(g, sizeof(struct sg_table));
|
|
if (!tbl) {
|
|
err = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
err = sg_alloc_table_from_pages(tbl, pages,
|
|
DIV_ROUND_UP(size, PAGE_SIZE),
|
|
0, size, GFP_KERNEL);
|
|
if (err)
|
|
goto fail;
|
|
|
|
sg_dma_address(tbl->sgl) = iova;
|
|
*sgt = tbl;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
if (tbl)
|
|
nvgpu_kfree(g, tbl);
|
|
|
|
return err;
|
|
}
|
|
|
|
void nvgpu_free_sgtable(struct gk20a *g, struct sg_table **sgt)
|
|
{
|
|
sg_free_table(*sgt);
|
|
nvgpu_kfree(g, *sgt);
|
|
*sgt = NULL;
|
|
}
|
|
|
|
bool nvgpu_iommuable(struct gk20a *g)
|
|
{
|
|
#ifdef CONFIG_TEGRA_GK20A
|
|
struct nvgpu_os_linux *l = nvgpu_os_linux_from_gk20a(g);
|
|
|
|
/*
|
|
* Check against the nvgpu device to see if it's been marked as
|
|
* IOMMU'able.
|
|
*/
|
|
if (!device_is_iommuable(l->dev))
|
|
return false;
|
|
#endif
|
|
|
|
return true;
|
|
}
|