diff options
Diffstat (limited to 'drivers/gpu/drm/imagination/pvr_vm.c')
| -rw-r--r-- | drivers/gpu/drm/imagination/pvr_vm.c | 1090 | 
1 files changed, 1090 insertions, 0 deletions
| diff --git a/drivers/gpu/drm/imagination/pvr_vm.c b/drivers/gpu/drm/imagination/pvr_vm.c new file mode 100644 index 000000000000..e59517ba039e --- /dev/null +++ b/drivers/gpu/drm/imagination/pvr_vm.c @@ -0,0 +1,1090 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright (c) 2023 Imagination Technologies Ltd. */ + +#include "pvr_vm.h" + +#include "pvr_device.h" +#include "pvr_drv.h" +#include "pvr_gem.h" +#include "pvr_mmu.h" +#include "pvr_rogue_fwif.h" +#include "pvr_rogue_heap_config.h" + +#include <drm/drm_exec.h> +#include <drm/drm_gem.h> +#include <drm/drm_gpuvm.h> + +#include <linux/container_of.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/gfp_types.h> +#include <linux/kref.h> +#include <linux/mutex.h> +#include <linux/stddef.h> + +/** + * DOC: Memory context + * + * This is the "top level" datatype in the VM code. It's exposed in the public + * API as an opaque handle. + */ + +/** + * struct pvr_vm_context - Context type used to represent a single VM. + */ +struct pvr_vm_context { +	/** +	 * @pvr_dev: The PowerVR device to which this context is bound. +	 * This binding is immutable for the life of the context. +	 */ +	struct pvr_device *pvr_dev; + +	/** @mmu_ctx: The context for binding to physical memory. */ +	struct pvr_mmu_context *mmu_ctx; + +	/** @gpuvm_mgr: GPUVM object associated with this context. */ +	struct drm_gpuvm gpuvm_mgr; + +	/** @lock: Global lock on this VM. */ +	struct mutex lock; + +	/** +	 * @fw_mem_ctx_obj: Firmware object representing firmware memory +	 * context. +	 */ +	struct pvr_fw_object *fw_mem_ctx_obj; + +	/** @ref_count: Reference count of object. */ +	struct kref ref_count; + +	/** +	 * @dummy_gem: GEM object to enable VM reservation. All private BOs +	 * should use the @dummy_gem.resv and not their own _resv field. +	 */ +	struct drm_gem_object dummy_gem; +}; + +static inline +struct pvr_vm_context *to_pvr_vm_context(struct drm_gpuvm *gpuvm) +{ +	return container_of(gpuvm, struct pvr_vm_context, gpuvm_mgr); +} + +struct pvr_vm_context *pvr_vm_context_get(struct pvr_vm_context *vm_ctx) +{ +	if (vm_ctx) +		kref_get(&vm_ctx->ref_count); + +	return vm_ctx; +} + +/** + * pvr_vm_get_page_table_root_addr() - Get the DMA address of the root of the + *                                     page table structure behind a VM context. + * @vm_ctx: Target VM context. + */ +dma_addr_t pvr_vm_get_page_table_root_addr(struct pvr_vm_context *vm_ctx) +{ +	return pvr_mmu_get_root_table_dma_addr(vm_ctx->mmu_ctx); +} + +/** + * pvr_vm_get_dma_resv() - Expose the dma_resv owned by the VM context. + * @vm_ctx: Target VM context. + * + * This is used to allow private BOs to share a dma_resv for faster fence + * updates. + * + * Returns: The dma_resv pointer. + */ +struct dma_resv *pvr_vm_get_dma_resv(struct pvr_vm_context *vm_ctx) +{ +	return vm_ctx->dummy_gem.resv; +} + +/** + * DOC: Memory mappings + */ + +/** + * struct pvr_vm_gpuva - Wrapper type representing a single VM mapping. + */ +struct pvr_vm_gpuva { +	/** @base: The wrapped drm_gpuva object. */ +	struct drm_gpuva base; +}; + +enum pvr_vm_bind_type { +	PVR_VM_BIND_TYPE_MAP, +	PVR_VM_BIND_TYPE_UNMAP, +}; + +/** + * struct pvr_vm_bind_op - Context of a map/unmap operation. + */ +struct pvr_vm_bind_op { +	/** @type: Map or unmap. */ +	enum pvr_vm_bind_type type; + +	/** @pvr_obj: Object associated with mapping (map only). */ +	struct pvr_gem_object *pvr_obj; + +	/** +	 * @vm_ctx: VM context where the mapping will be created or destroyed. +	 */ +	struct pvr_vm_context *vm_ctx; + +	/** @mmu_op_ctx: MMU op context. */ +	struct pvr_mmu_op_context *mmu_op_ctx; + +	/** @gpuvm_bo: Prealloced wrapped BO for attaching to the gpuvm. */ +	struct drm_gpuvm_bo *gpuvm_bo; + +	/** +	 * @new_va: Prealloced VA mapping object (init in callback). +	 * Used when creating a mapping. +	 */ +	struct pvr_vm_gpuva *new_va; + +	/** +	 * @prev_va: Prealloced VA mapping object (init in callback). +	 * Used when a mapping or unmapping operation overlaps an existing +	 * mapping and splits away the beginning into a new mapping. +	 */ +	struct pvr_vm_gpuva *prev_va; + +	/** +	 * @next_va: Prealloced VA mapping object (init in callback). +	 * Used when a mapping or unmapping operation overlaps an existing +	 * mapping and splits away the end into a new mapping. +	 */ +	struct pvr_vm_gpuva *next_va; + +	/** @offset: Offset into @pvr_obj to begin mapping from. */ +	u64 offset; + +	/** @device_addr: Device-virtual address at the start of the mapping. */ +	u64 device_addr; + +	/** @size: Size of the desired mapping. */ +	u64 size; +}; + +/** + * pvr_vm_bind_op_exec() - Execute a single bind op. + * @bind_op: Bind op context. + * + * Returns: + *  * 0 on success, + *  * Any error code returned by drm_gpuva_sm_map(), drm_gpuva_sm_unmap(), or + *    a callback function. + */ +static int pvr_vm_bind_op_exec(struct pvr_vm_bind_op *bind_op) +{ +	switch (bind_op->type) { +	case PVR_VM_BIND_TYPE_MAP: +		return drm_gpuvm_sm_map(&bind_op->vm_ctx->gpuvm_mgr, +					bind_op, bind_op->device_addr, +					bind_op->size, +					gem_from_pvr_gem(bind_op->pvr_obj), +					bind_op->offset); + +	case PVR_VM_BIND_TYPE_UNMAP: +		return drm_gpuvm_sm_unmap(&bind_op->vm_ctx->gpuvm_mgr, +					  bind_op, bind_op->device_addr, +					  bind_op->size); +	} + +	/* +	 * This shouldn't happen unless something went wrong +	 * in drm_sched. +	 */ +	WARN_ON(1); +	return -EINVAL; +} + +static void pvr_vm_bind_op_fini(struct pvr_vm_bind_op *bind_op) +{ +	drm_gpuvm_bo_put(bind_op->gpuvm_bo); + +	kfree(bind_op->new_va); +	kfree(bind_op->prev_va); +	kfree(bind_op->next_va); + +	if (bind_op->pvr_obj) +		pvr_gem_object_put(bind_op->pvr_obj); + +	if (bind_op->mmu_op_ctx) +		pvr_mmu_op_context_destroy(bind_op->mmu_op_ctx); +} + +static int +pvr_vm_bind_op_map_init(struct pvr_vm_bind_op *bind_op, +			struct pvr_vm_context *vm_ctx, +			struct pvr_gem_object *pvr_obj, u64 offset, +			u64 device_addr, u64 size) +{ +	struct drm_gem_object *obj = gem_from_pvr_gem(pvr_obj); +	const bool is_user = vm_ctx != vm_ctx->pvr_dev->kernel_vm_ctx; +	const u64 pvr_obj_size = pvr_gem_object_size(pvr_obj); +	struct sg_table *sgt; +	u64 offset_plus_size; +	int err; + +	if (check_add_overflow(offset, size, &offset_plus_size)) +		return -EINVAL; + +	if (is_user && +	    !pvr_find_heap_containing(vm_ctx->pvr_dev, device_addr, size)) { +		return -EINVAL; +	} + +	if (!pvr_device_addr_and_size_are_valid(vm_ctx, device_addr, size) || +	    offset & ~PAGE_MASK || size & ~PAGE_MASK || +	    offset >= pvr_obj_size || offset_plus_size > pvr_obj_size) +		return -EINVAL; + +	bind_op->type = PVR_VM_BIND_TYPE_MAP; + +	dma_resv_lock(obj->resv, NULL); +	bind_op->gpuvm_bo = drm_gpuvm_bo_obtain(&vm_ctx->gpuvm_mgr, obj); +	dma_resv_unlock(obj->resv); +	if (IS_ERR(bind_op->gpuvm_bo)) +		return PTR_ERR(bind_op->gpuvm_bo); + +	bind_op->new_va = kzalloc(sizeof(*bind_op->new_va), GFP_KERNEL); +	bind_op->prev_va = kzalloc(sizeof(*bind_op->prev_va), GFP_KERNEL); +	bind_op->next_va = kzalloc(sizeof(*bind_op->next_va), GFP_KERNEL); +	if (!bind_op->new_va || !bind_op->prev_va || !bind_op->next_va) { +		err = -ENOMEM; +		goto err_bind_op_fini; +	} + +	/* Pin pages so they're ready for use. */ +	sgt = pvr_gem_object_get_pages_sgt(pvr_obj); +	err = PTR_ERR_OR_ZERO(sgt); +	if (err) +		goto err_bind_op_fini; + +	bind_op->mmu_op_ctx = +		pvr_mmu_op_context_create(vm_ctx->mmu_ctx, sgt, offset, size); +	err = PTR_ERR_OR_ZERO(bind_op->mmu_op_ctx); +	if (err) { +		bind_op->mmu_op_ctx = NULL; +		goto err_bind_op_fini; +	} + +	bind_op->pvr_obj = pvr_obj; +	bind_op->vm_ctx = vm_ctx; +	bind_op->device_addr = device_addr; +	bind_op->size = size; +	bind_op->offset = offset; + +	return 0; + +err_bind_op_fini: +	pvr_vm_bind_op_fini(bind_op); + +	return err; +} + +static int +pvr_vm_bind_op_unmap_init(struct pvr_vm_bind_op *bind_op, +			  struct pvr_vm_context *vm_ctx, u64 device_addr, +			  u64 size) +{ +	int err; + +	if (!pvr_device_addr_and_size_are_valid(vm_ctx, device_addr, size)) +		return -EINVAL; + +	bind_op->type = PVR_VM_BIND_TYPE_UNMAP; + +	bind_op->prev_va = kzalloc(sizeof(*bind_op->prev_va), GFP_KERNEL); +	bind_op->next_va = kzalloc(sizeof(*bind_op->next_va), GFP_KERNEL); +	if (!bind_op->prev_va || !bind_op->next_va) { +		err = -ENOMEM; +		goto err_bind_op_fini; +	} + +	bind_op->mmu_op_ctx = +		pvr_mmu_op_context_create(vm_ctx->mmu_ctx, NULL, 0, 0); +	err = PTR_ERR_OR_ZERO(bind_op->mmu_op_ctx); +	if (err) { +		bind_op->mmu_op_ctx = NULL; +		goto err_bind_op_fini; +	} + +	bind_op->vm_ctx = vm_ctx; +	bind_op->device_addr = device_addr; +	bind_op->size = size; + +	return 0; + +err_bind_op_fini: +	pvr_vm_bind_op_fini(bind_op); + +	return err; +} + +/** + * pvr_vm_gpuva_map() - Insert a mapping into a memory context. + * @op: gpuva op containing the remap details. + * @op_ctx: Operation context. + * + * Context: Called by drm_gpuvm_sm_map following a successful mapping while + * @op_ctx.vm_ctx mutex is held. + * + * Return: + *  * 0 on success, or + *  * Any error returned by pvr_mmu_map(). + */ +static int +pvr_vm_gpuva_map(struct drm_gpuva_op *op, void *op_ctx) +{ +	struct pvr_gem_object *pvr_gem = gem_to_pvr_gem(op->map.gem.obj); +	struct pvr_vm_bind_op *ctx = op_ctx; +	int err; + +	if ((op->map.gem.offset | op->map.va.range) & ~PVR_DEVICE_PAGE_MASK) +		return -EINVAL; + +	err = pvr_mmu_map(ctx->mmu_op_ctx, op->map.va.range, pvr_gem->flags, +			  op->map.va.addr); +	if (err) +		return err; + +	drm_gpuva_map(&ctx->vm_ctx->gpuvm_mgr, &ctx->new_va->base, &op->map); +	drm_gpuva_link(&ctx->new_va->base, ctx->gpuvm_bo); +	ctx->new_va = NULL; + +	return 0; +} + +/** + * pvr_vm_gpuva_unmap() - Remove a mapping from a memory context. + * @op: gpuva op containing the unmap details. + * @op_ctx: Operation context. + * + * Context: Called by drm_gpuvm_sm_unmap following a successful unmapping while + * @op_ctx.vm_ctx mutex is held. + * + * Return: + *  * 0 on success, or + *  * Any error returned by pvr_mmu_unmap(). + */ +static int +pvr_vm_gpuva_unmap(struct drm_gpuva_op *op, void *op_ctx) +{ +	struct pvr_vm_bind_op *ctx = op_ctx; + +	int err = pvr_mmu_unmap(ctx->mmu_op_ctx, op->unmap.va->va.addr, +				op->unmap.va->va.range); + +	if (err) +		return err; + +	drm_gpuva_unmap(&op->unmap); +	drm_gpuva_unlink(op->unmap.va); + +	return 0; +} + +/** + * pvr_vm_gpuva_remap() - Remap a mapping within a memory context. + * @op: gpuva op containing the remap details. + * @op_ctx: Operation context. + * + * Context: Called by either drm_gpuvm_sm_map or drm_gpuvm_sm_unmap when a + * mapping or unmapping operation causes a region to be split. The + * @op_ctx.vm_ctx mutex is held. + * + * Return: + *  * 0 on success, or + *  * Any error returned by pvr_vm_gpuva_unmap() or pvr_vm_gpuva_unmap(). + */ +static int +pvr_vm_gpuva_remap(struct drm_gpuva_op *op, void *op_ctx) +{ +	struct pvr_vm_bind_op *ctx = op_ctx; +	u64 va_start = 0, va_range = 0; +	int err; + +	drm_gpuva_op_remap_to_unmap_range(&op->remap, &va_start, &va_range); +	err = pvr_mmu_unmap(ctx->mmu_op_ctx, va_start, va_range); +	if (err) +		return err; + +	/* No actual remap required: the page table tree depth is fixed to 3, +	 * and we use 4k page table entries only for now. +	 */ +	drm_gpuva_remap(&ctx->prev_va->base, &ctx->next_va->base, &op->remap); + +	if (op->remap.prev) { +		pvr_gem_object_get(gem_to_pvr_gem(ctx->prev_va->base.gem.obj)); +		drm_gpuva_link(&ctx->prev_va->base, ctx->gpuvm_bo); +		ctx->prev_va = NULL; +	} + +	if (op->remap.next) { +		pvr_gem_object_get(gem_to_pvr_gem(ctx->next_va->base.gem.obj)); +		drm_gpuva_link(&ctx->next_va->base, ctx->gpuvm_bo); +		ctx->next_va = NULL; +	} + +	drm_gpuva_unlink(op->remap.unmap->va); + +	return 0; +} + +/* + * Public API + * + * For an overview of these functions, see *DOC: Public API* in "pvr_vm.h". + */ + +/** + * pvr_device_addr_is_valid() - Tests whether a device-virtual address + *                              is valid. + * @device_addr: Virtual device address to test. + * + * Return: + *  * %true if @device_addr is within the valid range for a device page + *    table and is aligned to the device page size, or + *  * %false otherwise. + */ +bool +pvr_device_addr_is_valid(u64 device_addr) +{ +	return (device_addr & ~PVR_PAGE_TABLE_ADDR_MASK) == 0 && +	       (device_addr & ~PVR_DEVICE_PAGE_MASK) == 0; +} + +/** + * pvr_device_addr_and_size_are_valid() - Tests whether a device-virtual + * address and associated size are both valid. + * @vm_ctx: Target VM context. + * @device_addr: Virtual device address to test. + * @size: Size of the range based at @device_addr to test. + * + * Calling pvr_device_addr_is_valid() twice (once on @size, and again on + * @device_addr + @size) to verify a device-virtual address range initially + * seems intuitive, but it produces a false-negative when the address range + * is right at the end of device-virtual address space. + * + * This function catches that corner case, as well as checking that + * @size is non-zero. + * + * Return: + *  * %true if @device_addr is device page aligned; @size is device page + *    aligned; the range specified by @device_addr and @size is within the + *    bounds of the device-virtual address space, and @size is non-zero, or + *  * %false otherwise. + */ +bool +pvr_device_addr_and_size_are_valid(struct pvr_vm_context *vm_ctx, +				   u64 device_addr, u64 size) +{ +	return pvr_device_addr_is_valid(device_addr) && +	       drm_gpuvm_range_valid(&vm_ctx->gpuvm_mgr, device_addr, size) && +	       size != 0 && (size & ~PVR_DEVICE_PAGE_MASK) == 0 && +	       (device_addr + size <= PVR_PAGE_TABLE_ADDR_SPACE_SIZE); +} + +static void pvr_gpuvm_free(struct drm_gpuvm *gpuvm) +{ +	kfree(to_pvr_vm_context(gpuvm)); +} + +static const struct drm_gpuvm_ops pvr_vm_gpuva_ops = { +	.vm_free = pvr_gpuvm_free, +	.sm_step_map = pvr_vm_gpuva_map, +	.sm_step_remap = pvr_vm_gpuva_remap, +	.sm_step_unmap = pvr_vm_gpuva_unmap, +}; + +static void +fw_mem_context_init(void *cpu_ptr, void *priv) +{ +	struct rogue_fwif_fwmemcontext *fw_mem_ctx = cpu_ptr; +	struct pvr_vm_context *vm_ctx = priv; + +	fw_mem_ctx->pc_dev_paddr = pvr_vm_get_page_table_root_addr(vm_ctx); +	fw_mem_ctx->page_cat_base_reg_set = ROGUE_FW_BIF_INVALID_PCSET; +} + +/** + * pvr_vm_create_context() - Create a new VM context. + * @pvr_dev: Target PowerVR device. + * @is_userspace_context: %true if this context is for userspace. This will + *                        create a firmware memory context for the VM context + *                        and disable warnings when tearing down mappings. + * + * Return: + *  * A handle to the newly-minted VM context on success, + *  * -%EINVAL if the feature "virtual address space bits" on @pvr_dev is + *    missing or has an unsupported value, + *  * -%ENOMEM if allocation of the structure behind the opaque handle fails, + *    or + *  * Any error encountered while setting up internal structures. + */ +struct pvr_vm_context * +pvr_vm_create_context(struct pvr_device *pvr_dev, bool is_userspace_context) +{ +	struct drm_device *drm_dev = from_pvr_device(pvr_dev); + +	struct pvr_vm_context *vm_ctx; +	u16 device_addr_bits; + +	int err; + +	err = PVR_FEATURE_VALUE(pvr_dev, virtual_address_space_bits, +				&device_addr_bits); +	if (err) { +		drm_err(drm_dev, +			"Failed to get device virtual address space bits\n"); +		return ERR_PTR(err); +	} + +	if (device_addr_bits != PVR_PAGE_TABLE_ADDR_BITS) { +		drm_err(drm_dev, +			"Device has unsupported virtual address space size\n"); +		return ERR_PTR(-EINVAL); +	} + +	vm_ctx = kzalloc(sizeof(*vm_ctx), GFP_KERNEL); +	if (!vm_ctx) +		return ERR_PTR(-ENOMEM); + +	vm_ctx->pvr_dev = pvr_dev; + +	vm_ctx->mmu_ctx = pvr_mmu_context_create(pvr_dev); +	err = PTR_ERR_OR_ZERO(vm_ctx->mmu_ctx); +	if (err) +		goto err_free; + +	if (is_userspace_context) { +		err = pvr_fw_object_create(pvr_dev, sizeof(struct rogue_fwif_fwmemcontext), +					   PVR_BO_FW_FLAGS_DEVICE_UNCACHED, +					   fw_mem_context_init, vm_ctx, &vm_ctx->fw_mem_ctx_obj); + +		if (err) +			goto err_page_table_destroy; +	} + +	drm_gem_private_object_init(&pvr_dev->base, &vm_ctx->dummy_gem, 0); +	drm_gpuvm_init(&vm_ctx->gpuvm_mgr, +		       is_userspace_context ? "PowerVR-user-VM" : "PowerVR-FW-VM", +		       0, &pvr_dev->base, &vm_ctx->dummy_gem, +		       0, 1ULL << device_addr_bits, 0, 0, &pvr_vm_gpuva_ops); + +	mutex_init(&vm_ctx->lock); +	kref_init(&vm_ctx->ref_count); + +	return vm_ctx; + +err_page_table_destroy: +	pvr_mmu_context_destroy(vm_ctx->mmu_ctx); + +err_free: +	kfree(vm_ctx); + +	return ERR_PTR(err); +} + +/** + * pvr_vm_context_release() - Teardown a VM context. + * @ref_count: Pointer to reference counter of the VM context. + * + * This function ensures that no mappings are left dangling by unmapping them + * all in order of ascending device-virtual address. + */ +static void +pvr_vm_context_release(struct kref *ref_count) +{ +	struct pvr_vm_context *vm_ctx = +		container_of(ref_count, struct pvr_vm_context, ref_count); + +	if (vm_ctx->fw_mem_ctx_obj) +		pvr_fw_object_destroy(vm_ctx->fw_mem_ctx_obj); + +	WARN_ON(pvr_vm_unmap(vm_ctx, vm_ctx->gpuvm_mgr.mm_start, +			     vm_ctx->gpuvm_mgr.mm_range)); + +	pvr_mmu_context_destroy(vm_ctx->mmu_ctx); +	drm_gem_private_object_fini(&vm_ctx->dummy_gem); +	mutex_destroy(&vm_ctx->lock); + +	drm_gpuvm_put(&vm_ctx->gpuvm_mgr); +} + +/** + * pvr_vm_context_lookup() - Look up VM context from handle + * @pvr_file: Pointer to pvr_file structure. + * @handle: Object handle. + * + * Takes reference on VM context object. Call pvr_vm_context_put() to release. + * + * Returns: + *  * The requested object on success, or + *  * %NULL on failure (object does not exist in list, or is not a VM context) + */ +struct pvr_vm_context * +pvr_vm_context_lookup(struct pvr_file *pvr_file, u32 handle) +{ +	struct pvr_vm_context *vm_ctx; + +	xa_lock(&pvr_file->vm_ctx_handles); +	vm_ctx = xa_load(&pvr_file->vm_ctx_handles, handle); +	if (vm_ctx) +		kref_get(&vm_ctx->ref_count); + +	xa_unlock(&pvr_file->vm_ctx_handles); + +	return vm_ctx; +} + +/** + * pvr_vm_context_put() - Release a reference on a VM context + * @vm_ctx: Target VM context. + * + * Returns: + *  * %true if the VM context was destroyed, or + *  * %false if there are any references still remaining. + */ +bool +pvr_vm_context_put(struct pvr_vm_context *vm_ctx) +{ +	if (vm_ctx) +		return kref_put(&vm_ctx->ref_count, pvr_vm_context_release); + +	return true; +} + +/** + * pvr_destroy_vm_contexts_for_file: Destroy any VM contexts associated with the + * given file. + * @pvr_file: Pointer to pvr_file structure. + * + * Removes all vm_contexts associated with @pvr_file from the device VM context + * list and drops initial references. vm_contexts will then be destroyed once + * all outstanding references are dropped. + */ +void pvr_destroy_vm_contexts_for_file(struct pvr_file *pvr_file) +{ +	struct pvr_vm_context *vm_ctx; +	unsigned long handle; + +	xa_for_each(&pvr_file->vm_ctx_handles, handle, vm_ctx) { +		/* vm_ctx is not used here because that would create a race with xa_erase */ +		pvr_vm_context_put(xa_erase(&pvr_file->vm_ctx_handles, handle)); +	} +} + +static int +pvr_vm_lock_extra(struct drm_gpuvm_exec *vm_exec) +{ +	struct pvr_vm_bind_op *bind_op = vm_exec->extra.priv; +	struct pvr_gem_object *pvr_obj = bind_op->pvr_obj; + +	/* Unmap operations don't have an object to lock. */ +	if (!pvr_obj) +		return 0; + +	/* Acquire lock on the GEM being mapped. */ +	return drm_exec_lock_obj(&vm_exec->exec, gem_from_pvr_gem(pvr_obj)); +} + +/** + * pvr_vm_map() - Map a section of physical memory into a section of + * device-virtual memory. + * @vm_ctx: Target VM context. + * @pvr_obj: Target PowerVR memory object. + * @pvr_obj_offset: Offset into @pvr_obj to map from. + * @device_addr: Virtual device address at the start of the requested mapping. + * @size: Size of the requested mapping. + * + * No handle is returned to represent the mapping. Instead, callers should + * remember @device_addr and use that as a handle. + * + * Return: + *  * 0 on success, + *  * -%EINVAL if @device_addr is not a valid page-aligned device-virtual + *    address; the region specified by @pvr_obj_offset and @size does not fall + *    entirely within @pvr_obj, or any part of the specified region of @pvr_obj + *    is not device-virtual page-aligned, + *  * Any error encountered while performing internal operations required to + *    destroy the mapping (returned from pvr_vm_gpuva_map or + *    pvr_vm_gpuva_remap). + */ +int +pvr_vm_map(struct pvr_vm_context *vm_ctx, struct pvr_gem_object *pvr_obj, +	   u64 pvr_obj_offset, u64 device_addr, u64 size) +{ +	struct pvr_vm_bind_op bind_op = {0}; +	struct drm_gpuvm_exec vm_exec = { +		.vm = &vm_ctx->gpuvm_mgr, +		.flags = DRM_EXEC_INTERRUPTIBLE_WAIT | +			 DRM_EXEC_IGNORE_DUPLICATES, +		.extra = { +			.fn = pvr_vm_lock_extra, +			.priv = &bind_op, +		}, +	}; + +	int err = pvr_vm_bind_op_map_init(&bind_op, vm_ctx, pvr_obj, +					  pvr_obj_offset, device_addr, +					  size); + +	if (err) +		return err; + +	pvr_gem_object_get(pvr_obj); + +	err = drm_gpuvm_exec_lock(&vm_exec); +	if (err) +		goto err_cleanup; + +	err = pvr_vm_bind_op_exec(&bind_op); + +	drm_gpuvm_exec_unlock(&vm_exec); + +err_cleanup: +	pvr_vm_bind_op_fini(&bind_op); + +	return err; +} + +/** + * pvr_vm_unmap() - Unmap an already mapped section of device-virtual memory. + * @vm_ctx: Target VM context. + * @device_addr: Virtual device address at the start of the target mapping. + * @size: Size of the target mapping. + * + * Return: + *  * 0 on success, + *  * -%EINVAL if @device_addr is not a valid page-aligned device-virtual + *    address, + *  * Any error encountered while performing internal operations required to + *    destroy the mapping (returned from pvr_vm_gpuva_unmap or + *    pvr_vm_gpuva_remap). + */ +int +pvr_vm_unmap(struct pvr_vm_context *vm_ctx, u64 device_addr, u64 size) +{ +	struct pvr_vm_bind_op bind_op = {0}; +	struct drm_gpuvm_exec vm_exec = { +		.vm = &vm_ctx->gpuvm_mgr, +		.flags = DRM_EXEC_INTERRUPTIBLE_WAIT | +			 DRM_EXEC_IGNORE_DUPLICATES, +		.extra = { +			.fn = pvr_vm_lock_extra, +			.priv = &bind_op, +		}, +	}; + +	int err = pvr_vm_bind_op_unmap_init(&bind_op, vm_ctx, device_addr, +					    size); +	if (err) +		return err; + +	err = drm_gpuvm_exec_lock(&vm_exec); +	if (err) +		goto err_cleanup; + +	err = pvr_vm_bind_op_exec(&bind_op); + +	drm_gpuvm_exec_unlock(&vm_exec); + +err_cleanup: +	pvr_vm_bind_op_fini(&bind_op); + +	return err; +} + +/* Static data areas are determined by firmware. */ +static const struct drm_pvr_static_data_area static_data_areas[] = { +	{ +		.area_usage = DRM_PVR_STATIC_DATA_AREA_FENCE, +		.location_heap_id = DRM_PVR_HEAP_GENERAL, +		.offset = 0, +		.size = 128, +	}, +	{ +		.area_usage = DRM_PVR_STATIC_DATA_AREA_YUV_CSC, +		.location_heap_id = DRM_PVR_HEAP_GENERAL, +		.offset = 128, +		.size = 1024, +	}, +	{ +		.area_usage = DRM_PVR_STATIC_DATA_AREA_VDM_SYNC, +		.location_heap_id = DRM_PVR_HEAP_PDS_CODE_DATA, +		.offset = 0, +		.size = 128, +	}, +	{ +		.area_usage = DRM_PVR_STATIC_DATA_AREA_EOT, +		.location_heap_id = DRM_PVR_HEAP_PDS_CODE_DATA, +		.offset = 128, +		.size = 128, +	}, +	{ +		.area_usage = DRM_PVR_STATIC_DATA_AREA_VDM_SYNC, +		.location_heap_id = DRM_PVR_HEAP_USC_CODE, +		.offset = 0, +		.size = 128, +	}, +}; + +#define GET_RESERVED_SIZE(last_offset, last_size) round_up((last_offset) + (last_size), PAGE_SIZE) + +/* + * The values given to GET_RESERVED_SIZE() are taken from the last entry in the corresponding + * static data area for each heap. + */ +static const struct drm_pvr_heap pvr_heaps[] = { +	[DRM_PVR_HEAP_GENERAL] = { +		.base = ROGUE_GENERAL_HEAP_BASE, +		.size = ROGUE_GENERAL_HEAP_SIZE, +		.flags = 0, +		.page_size_log2 = PVR_DEVICE_PAGE_SHIFT, +	}, +	[DRM_PVR_HEAP_PDS_CODE_DATA] = { +		.base = ROGUE_PDSCODEDATA_HEAP_BASE, +		.size = ROGUE_PDSCODEDATA_HEAP_SIZE, +		.flags = 0, +		.page_size_log2 = PVR_DEVICE_PAGE_SHIFT, +	}, +	[DRM_PVR_HEAP_USC_CODE] = { +		.base = ROGUE_USCCODE_HEAP_BASE, +		.size = ROGUE_USCCODE_HEAP_SIZE, +		.flags = 0, +		.page_size_log2 = PVR_DEVICE_PAGE_SHIFT, +	}, +	[DRM_PVR_HEAP_RGNHDR] = { +		.base = ROGUE_RGNHDR_HEAP_BASE, +		.size = ROGUE_RGNHDR_HEAP_SIZE, +		.flags = 0, +		.page_size_log2 = PVR_DEVICE_PAGE_SHIFT, +	}, +	[DRM_PVR_HEAP_VIS_TEST] = { +		.base = ROGUE_VISTEST_HEAP_BASE, +		.size = ROGUE_VISTEST_HEAP_SIZE, +		.flags = 0, +		.page_size_log2 = PVR_DEVICE_PAGE_SHIFT, +	}, +	[DRM_PVR_HEAP_TRANSFER_FRAG] = { +		.base = ROGUE_TRANSFER_FRAG_HEAP_BASE, +		.size = ROGUE_TRANSFER_FRAG_HEAP_SIZE, +		.flags = 0, +		.page_size_log2 = PVR_DEVICE_PAGE_SHIFT, +	}, +}; + +int +pvr_static_data_areas_get(const struct pvr_device *pvr_dev, +			  struct drm_pvr_ioctl_dev_query_args *args) +{ +	struct drm_pvr_dev_query_static_data_areas query = {0}; +	int err; + +	if (!args->pointer) { +		args->size = sizeof(struct drm_pvr_dev_query_static_data_areas); +		return 0; +	} + +	err = PVR_UOBJ_GET(query, args->size, args->pointer); +	if (err < 0) +		return err; + +	if (!query.static_data_areas.array) { +		query.static_data_areas.count = ARRAY_SIZE(static_data_areas); +		query.static_data_areas.stride = sizeof(struct drm_pvr_static_data_area); +		goto copy_out; +	} + +	if (query.static_data_areas.count > ARRAY_SIZE(static_data_areas)) +		query.static_data_areas.count = ARRAY_SIZE(static_data_areas); + +	err = PVR_UOBJ_SET_ARRAY(&query.static_data_areas, static_data_areas); +	if (err < 0) +		return err; + +copy_out: +	err = PVR_UOBJ_SET(args->pointer, args->size, query); +	if (err < 0) +		return err; + +	args->size = sizeof(query); +	return 0; +} + +int +pvr_heap_info_get(const struct pvr_device *pvr_dev, +		  struct drm_pvr_ioctl_dev_query_args *args) +{ +	struct drm_pvr_dev_query_heap_info query = {0}; +	u64 dest; +	int err; + +	if (!args->pointer) { +		args->size = sizeof(struct drm_pvr_dev_query_heap_info); +		return 0; +	} + +	err = PVR_UOBJ_GET(query, args->size, args->pointer); +	if (err < 0) +		return err; + +	if (!query.heaps.array) { +		query.heaps.count = ARRAY_SIZE(pvr_heaps); +		query.heaps.stride = sizeof(struct drm_pvr_heap); +		goto copy_out; +	} + +	if (query.heaps.count > ARRAY_SIZE(pvr_heaps)) +		query.heaps.count = ARRAY_SIZE(pvr_heaps); + +	/* Region header heap is only present if BRN63142 is present. */ +	dest = query.heaps.array; +	for (size_t i = 0; i < query.heaps.count; i++) { +		struct drm_pvr_heap heap = pvr_heaps[i]; + +		if (i == DRM_PVR_HEAP_RGNHDR && !PVR_HAS_QUIRK(pvr_dev, 63142)) +			heap.size = 0; + +		err = PVR_UOBJ_SET(dest, query.heaps.stride, heap); +		if (err < 0) +			return err; + +		dest += query.heaps.stride; +	} + +copy_out: +	err = PVR_UOBJ_SET(args->pointer, args->size, query); +	if (err < 0) +		return err; + +	args->size = sizeof(query); +	return 0; +} + +/** + * pvr_heap_contains_range() - Determine if a given heap contains the specified + *                             device-virtual address range. + * @pvr_heap: Target heap. + * @start: Inclusive start of the target range. + * @end: Inclusive end of the target range. + * + * It is an error to call this function with values of @start and @end that do + * not satisfy the condition @start <= @end. + */ +static __always_inline bool +pvr_heap_contains_range(const struct drm_pvr_heap *pvr_heap, u64 start, u64 end) +{ +	return pvr_heap->base <= start && end < pvr_heap->base + pvr_heap->size; +} + +/** + * pvr_find_heap_containing() - Find a heap which contains the specified + *                              device-virtual address range. + * @pvr_dev: Target PowerVR device. + * @start: Start of the target range. + * @size: Size of the target range. + * + * Return: + *  * A pointer to a constant instance of struct drm_pvr_heap representing the + *    heap containing the entire range specified by @start and @size on + *    success, or + *  * %NULL if no such heap exists. + */ +const struct drm_pvr_heap * +pvr_find_heap_containing(struct pvr_device *pvr_dev, u64 start, u64 size) +{ +	u64 end; + +	if (check_add_overflow(start, size - 1, &end)) +		return NULL; + +	/* +	 * There are no guarantees about the order of address ranges in +	 * &pvr_heaps, so iterate over the entire array for a heap whose +	 * range completely encompasses the given range. +	 */ +	for (u32 heap_id = 0; heap_id < ARRAY_SIZE(pvr_heaps); heap_id++) { +		/* Filter heaps that present only with an associated quirk */ +		if (heap_id == DRM_PVR_HEAP_RGNHDR && +		    !PVR_HAS_QUIRK(pvr_dev, 63142)) { +			continue; +		} + +		if (pvr_heap_contains_range(&pvr_heaps[heap_id], start, end)) +			return &pvr_heaps[heap_id]; +	} + +	return NULL; +} + +/** + * pvr_vm_find_gem_object() - Look up a buffer object from a given + *                            device-virtual address. + * @vm_ctx: [IN] Target VM context. + * @device_addr: [IN] Virtual device address at the start of the required + *               object. + * @mapped_offset_out: [OUT] Pointer to location to write offset of the start + *                     of the mapped region within the buffer object. May be + *                     %NULL if this information is not required. + * @mapped_size_out: [OUT] Pointer to location to write size of the mapped + *                   region. May be %NULL if this information is not required. + * + * If successful, a reference will be taken on the buffer object. The caller + * must drop the reference with pvr_gem_object_put(). + * + * Return: + *  * The PowerVR buffer object mapped at @device_addr if one exists, or + *  * %NULL otherwise. + */ +struct pvr_gem_object * +pvr_vm_find_gem_object(struct pvr_vm_context *vm_ctx, u64 device_addr, +		       u64 *mapped_offset_out, u64 *mapped_size_out) +{ +	struct pvr_gem_object *pvr_obj; +	struct drm_gpuva *va; + +	mutex_lock(&vm_ctx->lock); + +	va = drm_gpuva_find_first(&vm_ctx->gpuvm_mgr, device_addr, 1); +	if (!va) +		goto err_unlock; + +	pvr_obj = gem_to_pvr_gem(va->gem.obj); +	pvr_gem_object_get(pvr_obj); + +	if (mapped_offset_out) +		*mapped_offset_out = va->gem.offset; +	if (mapped_size_out) +		*mapped_size_out = va->va.range; + +	mutex_unlock(&vm_ctx->lock); + +	return pvr_obj; + +err_unlock: +	mutex_unlock(&vm_ctx->lock); + +	return NULL; +} + +/** + * pvr_vm_get_fw_mem_context: Get object representing firmware memory context + * @vm_ctx: Target VM context. + * + * Returns: + *  * FW object representing firmware memory context, or + *  * %NULL if this VM context does not have a firmware memory context. + */ +struct pvr_fw_object * +pvr_vm_get_fw_mem_context(struct pvr_vm_context *vm_ctx) +{ +	return vm_ctx->fw_mem_ctx_obj; +} | 
