linux/drivers/gpu/drm/i915/intel_memory_region.c
Matthew Auld 235582ca96 drm/i915: add io_size plumbing
With small LMEM-BAR we need to be able to differentiate between the
total size of LMEM, and how much of it is CPU mappable. The end goal is
to be able to utilize the entire range, even if part of is it not CPU
accessible.

v2: also update intelfb_create

Signed-off-by: Matthew Auld <matthew.auld@intel.com>
Cc: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Acked-by: Nirmoy Das <nirmoy.das@intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20220225145502.331818-1-matthew.auld@intel.com
2022-02-28 08:47:27 +00:00

367 lines
8.1 KiB
C

// SPDX-License-Identifier: MIT
/*
* Copyright © 2019 Intel Corporation
*/
#include <linux/prandom.h>
#include "intel_memory_region.h"
#include "i915_drv.h"
#include "i915_ttm_buddy_manager.h"
static const struct {
u16 class;
u16 instance;
} intel_region_map[] = {
[INTEL_REGION_SMEM] = {
.class = INTEL_MEMORY_SYSTEM,
.instance = 0,
},
[INTEL_REGION_LMEM] = {
.class = INTEL_MEMORY_LOCAL,
.instance = 0,
},
[INTEL_REGION_STOLEN_SMEM] = {
.class = INTEL_MEMORY_STOLEN_SYSTEM,
.instance = 0,
},
[INTEL_REGION_STOLEN_LMEM] = {
.class = INTEL_MEMORY_STOLEN_LOCAL,
.instance = 0,
},
};
static int __iopagetest(struct intel_memory_region *mem,
u8 __iomem *va, int pagesize,
u8 value, resource_size_t offset,
const void *caller)
{
int byte = prandom_u32_max(pagesize);
u8 result[3];
memset_io(va, value, pagesize); /* or GPF! */
wmb();
result[0] = ioread8(va);
result[1] = ioread8(va + byte);
result[2] = ioread8(va + pagesize - 1);
if (memchr_inv(result, value, sizeof(result))) {
dev_err(mem->i915->drm.dev,
"Failed to read back from memory region:%pR at [%pa + %pa] for %ps; wrote %x, read (%x, %x, %x)\n",
&mem->region, &mem->io_start, &offset, caller,
value, result[0], result[1], result[2]);
return -EINVAL;
}
return 0;
}
static int iopagetest(struct intel_memory_region *mem,
resource_size_t offset,
const void *caller)
{
const u8 val[] = { 0x0, 0xa5, 0xc3, 0xf0 };
void __iomem *va;
int err;
int i;
va = ioremap_wc(mem->io_start + offset, PAGE_SIZE);
if (!va) {
dev_err(mem->i915->drm.dev,
"Failed to ioremap memory region [%pa + %pa] for %ps\n",
&mem->io_start, &offset, caller);
return -EFAULT;
}
for (i = 0; i < ARRAY_SIZE(val); i++) {
err = __iopagetest(mem, va, PAGE_SIZE, val[i], offset, caller);
if (err)
break;
err = __iopagetest(mem, va, PAGE_SIZE, ~val[i], offset, caller);
if (err)
break;
}
iounmap(va);
return err;
}
static resource_size_t random_page(resource_size_t last)
{
/* Limited to low 44b (16TiB), but should suffice for a spot check */
return prandom_u32_max(last >> PAGE_SHIFT) << PAGE_SHIFT;
}
static int iomemtest(struct intel_memory_region *mem,
bool test_all,
const void *caller)
{
resource_size_t last, page;
int err;
if (mem->io_size < PAGE_SIZE)
return 0;
last = mem->io_size - PAGE_SIZE;
/*
* Quick test to check read/write access to the iomap (backing store).
*
* Write a byte, read it back. If the iomapping fails, we expect
* a GPF preventing further execution. If the backing store does not
* exist, the read back will return garbage. We check a couple of pages,
* the first and last of the specified region to confirm the backing
* store + iomap does cover the entire memory region; and we check
* a random offset within as a quick spot check for bad memory.
*/
if (test_all) {
for (page = 0; page <= last; page += PAGE_SIZE) {
err = iopagetest(mem, page, caller);
if (err)
return err;
}
} else {
err = iopagetest(mem, 0, caller);
if (err)
return err;
err = iopagetest(mem, last, caller);
if (err)
return err;
err = iopagetest(mem, random_page(last), caller);
if (err)
return err;
}
return 0;
}
struct intel_memory_region *
intel_memory_region_lookup(struct drm_i915_private *i915,
u16 class, u16 instance)
{
struct intel_memory_region *mr;
int id;
/* XXX: consider maybe converting to an rb tree at some point */
for_each_memory_region(mr, i915, id) {
if (mr->type == class && mr->instance == instance)
return mr;
}
return NULL;
}
struct intel_memory_region *
intel_memory_region_by_type(struct drm_i915_private *i915,
enum intel_memory_type mem_type)
{
struct intel_memory_region *mr;
int id;
for_each_memory_region(mr, i915, id)
if (mr->type == mem_type)
return mr;
return NULL;
}
/**
* intel_memory_region_reserve - Reserve a memory range
* @mem: The region for which we want to reserve a range.
* @offset: Start of the range to reserve.
* @size: The size of the range to reserve.
*
* Return: 0 on success, negative error code on failure.
*/
int intel_memory_region_reserve(struct intel_memory_region *mem,
resource_size_t offset,
resource_size_t size)
{
struct ttm_resource_manager *man = mem->region_private;
GEM_BUG_ON(mem->is_range_manager);
return i915_ttm_buddy_man_reserve(man, offset, size);
}
void intel_memory_region_debug(struct intel_memory_region *mr,
struct drm_printer *printer)
{
drm_printf(printer, "%s: ", mr->name);
if (mr->region_private)
ttm_resource_manager_debug(mr->region_private, printer);
else
drm_printf(printer, "total:%pa, available:%pa bytes\n",
&mr->total, &mr->avail);
}
static int intel_memory_region_memtest(struct intel_memory_region *mem,
void *caller)
{
struct drm_i915_private *i915 = mem->i915;
int err = 0;
if (!mem->io_start)
return 0;
if (IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM) || i915->params.memtest)
err = iomemtest(mem, i915->params.memtest, caller);
return err;
}
struct intel_memory_region *
intel_memory_region_create(struct drm_i915_private *i915,
resource_size_t start,
resource_size_t size,
resource_size_t min_page_size,
resource_size_t io_start,
resource_size_t io_size,
u16 type,
u16 instance,
const struct intel_memory_region_ops *ops)
{
struct intel_memory_region *mem;
int err;
mem = kzalloc(sizeof(*mem), GFP_KERNEL);
if (!mem)
return ERR_PTR(-ENOMEM);
mem->i915 = i915;
mem->region = (struct resource)DEFINE_RES_MEM(start, size);
mem->io_start = io_start;
mem->io_size = io_size;
mem->min_page_size = min_page_size;
mem->ops = ops;
mem->total = size;
mem->avail = mem->total;
mem->type = type;
mem->instance = instance;
mutex_init(&mem->objects.lock);
INIT_LIST_HEAD(&mem->objects.list);
if (ops->init) {
err = ops->init(mem);
if (err)
goto err_free;
}
err = intel_memory_region_memtest(mem, (void *)_RET_IP_);
if (err)
goto err_release;
return mem;
err_release:
if (mem->ops->release)
mem->ops->release(mem);
err_free:
kfree(mem);
return ERR_PTR(err);
}
void intel_memory_region_set_name(struct intel_memory_region *mem,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsnprintf(mem->name, sizeof(mem->name), fmt, ap);
va_end(ap);
}
void intel_memory_region_destroy(struct intel_memory_region *mem)
{
int ret = 0;
if (mem->ops->release)
ret = mem->ops->release(mem);
GEM_WARN_ON(!list_empty_careful(&mem->objects.list));
mutex_destroy(&mem->objects.lock);
if (!ret)
kfree(mem);
}
/* Global memory region registration -- only slight layer inversions! */
int intel_memory_regions_hw_probe(struct drm_i915_private *i915)
{
int err, i;
for (i = 0; i < ARRAY_SIZE(i915->mm.regions); i++) {
struct intel_memory_region *mem = ERR_PTR(-ENODEV);
u16 type, instance;
if (!HAS_REGION(i915, BIT(i)))
continue;
type = intel_region_map[i].class;
instance = intel_region_map[i].instance;
switch (type) {
case INTEL_MEMORY_SYSTEM:
if (IS_DGFX(i915))
mem = i915_gem_ttm_system_setup(i915, type,
instance);
else
mem = i915_gem_shmem_setup(i915, type,
instance);
break;
case INTEL_MEMORY_STOLEN_LOCAL:
mem = i915_gem_stolen_lmem_setup(i915, type, instance);
if (!IS_ERR(mem))
i915->mm.stolen_region = mem;
break;
case INTEL_MEMORY_STOLEN_SYSTEM:
mem = i915_gem_stolen_smem_setup(i915, type, instance);
if (!IS_ERR(mem))
i915->mm.stolen_region = mem;
break;
default:
continue;
}
if (IS_ERR(mem)) {
err = PTR_ERR(mem);
drm_err(&i915->drm,
"Failed to setup region(%d) type=%d\n",
err, type);
goto out_cleanup;
}
mem->id = i;
i915->mm.regions[i] = mem;
}
return 0;
out_cleanup:
intel_memory_regions_driver_release(i915);
return err;
}
void intel_memory_regions_driver_release(struct drm_i915_private *i915)
{
int i;
for (i = 0; i < ARRAY_SIZE(i915->mm.regions); i++) {
struct intel_memory_region *region =
fetch_and_zero(&i915->mm.regions[i]);
if (region)
intel_memory_region_destroy(region);
}
}
#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST)
#include "selftests/intel_memory_region.c"
#include "selftests/mock_region.c"
#endif