| // 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; |
| } |