linux/drivers/gpu/drm/i915/intel_memory_region.c
Chris Wilson 0a9a5532d2 drm/i915/gem: Apply lmem size restriction to get_pages
When creating a handle, it is just that, an abstract handle. The fact
that we cannot currently support a handle larger than the size of the
backing storage is an artifact of our whole-object-at-a-time handling in
get_pages() and being an implementation limitation is best handled at
that point -- similar to shmem, where we only barf when asked to
populate the whole object if larger than RAM. (Pinning the whole object
at a time is major hindrance that we are likely to have to overcome in
the near future.) In the case of the buddy allocator, the late check is
preferable as the request size may often be smaller than the required
size.

Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
Cc: Matthew Auld <matthew.auld@intel.com>
Cc: Joonas Lahtinen <joonas.lahtinen@linux.intel.com>
Reviewed-by: Matthew Auld <matthew.auld@intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20191216122603.2598155-1-chris@chris-wilson.co.uk
2019-12-16 23:13:12 +00:00

276 lines
6.0 KiB
C

// SPDX-License-Identifier: MIT
/*
* Copyright © 2019 Intel Corporation
*/
#include "intel_memory_region.h"
#include "i915_drv.h"
/* XXX: Hysterical raisins. BIT(inst) needs to just be (inst) at some point. */
#define REGION_MAP(type, inst) \
BIT((type) + INTEL_MEMORY_TYPE_SHIFT) | BIT(inst)
const u32 intel_region_map[] = {
[INTEL_REGION_SMEM] = REGION_MAP(INTEL_MEMORY_SYSTEM, 0),
[INTEL_REGION_LMEM] = REGION_MAP(INTEL_MEMORY_LOCAL, 0),
[INTEL_REGION_STOLEN] = REGION_MAP(INTEL_MEMORY_STOLEN, 0),
};
static u64
intel_memory_region_free_pages(struct intel_memory_region *mem,
struct list_head *blocks)
{
struct i915_buddy_block *block, *on;
u64 size = 0;
list_for_each_entry_safe(block, on, blocks, link) {
size += i915_buddy_block_size(&mem->mm, block);
i915_buddy_free(&mem->mm, block);
}
INIT_LIST_HEAD(blocks);
return size;
}
void
__intel_memory_region_put_pages_buddy(struct intel_memory_region *mem,
struct list_head *blocks)
{
mutex_lock(&mem->mm_lock);
intel_memory_region_free_pages(mem, blocks);
mutex_unlock(&mem->mm_lock);
}
void
__intel_memory_region_put_block_buddy(struct i915_buddy_block *block)
{
struct list_head blocks;
INIT_LIST_HEAD(&blocks);
list_add(&block->link, &blocks);
__intel_memory_region_put_pages_buddy(block->private, &blocks);
}
int
__intel_memory_region_get_pages_buddy(struct intel_memory_region *mem,
resource_size_t size,
unsigned int flags,
struct list_head *blocks)
{
unsigned int min_order = 0;
unsigned long n_pages;
GEM_BUG_ON(!IS_ALIGNED(size, mem->mm.chunk_size));
GEM_BUG_ON(!list_empty(blocks));
if (flags & I915_ALLOC_MIN_PAGE_SIZE) {
min_order = ilog2(mem->min_page_size) -
ilog2(mem->mm.chunk_size);
}
if (flags & I915_ALLOC_CONTIGUOUS) {
size = roundup_pow_of_two(size);
min_order = ilog2(size) - ilog2(mem->mm.chunk_size);
}
if (size > BIT(mem->mm.max_order) * mem->mm.chunk_size)
return -E2BIG;
n_pages = size >> ilog2(mem->mm.chunk_size);
mutex_lock(&mem->mm_lock);
do {
struct i915_buddy_block *block;
unsigned int order;
order = fls(n_pages) - 1;
GEM_BUG_ON(order > mem->mm.max_order);
GEM_BUG_ON(order < min_order);
do {
block = i915_buddy_alloc(&mem->mm, order);
if (!IS_ERR(block))
break;
if (order-- == min_order)
goto err_free_blocks;
} while (1);
n_pages -= BIT(order);
block->private = mem;
list_add(&block->link, blocks);
if (!n_pages)
break;
} while (1);
mutex_unlock(&mem->mm_lock);
return 0;
err_free_blocks:
intel_memory_region_free_pages(mem, blocks);
mutex_unlock(&mem->mm_lock);
return -ENXIO;
}
struct i915_buddy_block *
__intel_memory_region_get_block_buddy(struct intel_memory_region *mem,
resource_size_t size,
unsigned int flags)
{
struct i915_buddy_block *block;
LIST_HEAD(blocks);
int ret;
ret = __intel_memory_region_get_pages_buddy(mem, size, flags, &blocks);
if (ret)
return ERR_PTR(ret);
block = list_first_entry(&blocks, typeof(*block), link);
list_del_init(&block->link);
return block;
}
int intel_memory_region_init_buddy(struct intel_memory_region *mem)
{
return i915_buddy_init(&mem->mm, resource_size(&mem->region),
PAGE_SIZE);
}
void intel_memory_region_release_buddy(struct intel_memory_region *mem)
{
i915_buddy_fini(&mem->mm);
}
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,
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->min_page_size = min_page_size;
mem->ops = ops;
mutex_init(&mem->objects.lock);
INIT_LIST_HEAD(&mem->objects.list);
INIT_LIST_HEAD(&mem->objects.purgeable);
mutex_init(&mem->mm_lock);
if (ops->init) {
err = ops->init(mem);
if (err)
goto err_free;
}
kref_init(&mem->kref);
return mem;
err_free:
kfree(mem);
return ERR_PTR(err);
}
static void __intel_memory_region_destroy(struct kref *kref)
{
struct intel_memory_region *mem =
container_of(kref, typeof(*mem), kref);
if (mem->ops->release)
mem->ops->release(mem);
mutex_destroy(&mem->mm_lock);
mutex_destroy(&mem->objects.lock);
kfree(mem);
}
struct intel_memory_region *
intel_memory_region_get(struct intel_memory_region *mem)
{
kref_get(&mem->kref);
return mem;
}
void intel_memory_region_put(struct intel_memory_region *mem)
{
kref_put(&mem->kref, __intel_memory_region_destroy);
}
/* 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);
u32 type;
if (!HAS_REGION(i915, BIT(i)))
continue;
type = MEMORY_TYPE_FROM_REGION(intel_region_map[i]);
switch (type) {
case INTEL_MEMORY_SYSTEM:
mem = i915_gem_shmem_setup(i915);
break;
case INTEL_MEMORY_STOLEN:
mem = i915_gem_stolen_setup(i915);
break;
case INTEL_MEMORY_LOCAL:
mem = intel_setup_fake_lmem(i915);
break;
}
if (IS_ERR(mem)) {
err = PTR_ERR(mem);
DRM_ERROR("Failed to setup region(%d) type=%d\n", err, type);
goto out_cleanup;
}
mem->id = intel_region_map[i];
mem->type = type;
mem->instance = MEMORY_INSTANCE_FROM_REGION(intel_region_map[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_put(region);
}
}
#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST)
#include "selftests/intel_memory_region.c"
#include "selftests/mock_region.c"
#endif