| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188 |
- // 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/bug.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;
- };
- #define to_pvr_vm_gpuva(va) container_of_const(va, struct pvr_vm_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,
- struct pvr_gem_object *pvr_obj,
- 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->pvr_obj = pvr_obj;
- 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);
- kfree(to_pvr_vm_gpuva(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);
- kfree(to_pvr_vm_gpuva(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 also ensures that no mappings are left dangling by calling
- * pvr_vm_unmap_all.
- */
- 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);
- pvr_vm_unmap_all(vm_ctx);
- 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);
- pvr_vm_context_get(vm_ctx);
- 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;
- /* Acquire lock on the GEM object being mapped/unmapped. */
- 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_obj_locked() - Unmap an already mapped section of device-virtual
- * memory.
- * @vm_ctx: Target VM context.
- * @pvr_obj: Target PowerVR memory object.
- * @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).
- *
- * The vm_ctx->lock must be held when calling this function.
- */
- static int
- pvr_vm_unmap_obj_locked(struct pvr_vm_context *vm_ctx,
- struct pvr_gem_object *pvr_obj,
- 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, pvr_obj,
- 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_obj() - Unmap an already mapped section of device-virtual
- * memory.
- * @vm_ctx: Target VM context.
- * @pvr_obj: Target PowerVR memory object.
- * @device_addr: Virtual device address at the start of the target mapping.
- * @size: Size of the target mapping.
- *
- * Return:
- * * 0 on success,
- * * Any error encountered by pvr_vm_unmap_obj_locked.
- */
- int
- pvr_vm_unmap_obj(struct pvr_vm_context *vm_ctx, struct pvr_gem_object *pvr_obj,
- u64 device_addr, u64 size)
- {
- int err;
- mutex_lock(&vm_ctx->lock);
- err = pvr_vm_unmap_obj_locked(vm_ctx, pvr_obj, device_addr, size);
- mutex_unlock(&vm_ctx->lock);
- 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,
- * * Any error encountered by drm_gpuva_find,
- * * Any error encountered by pvr_vm_unmap_obj_locked.
- */
- int
- pvr_vm_unmap(struct pvr_vm_context *vm_ctx, u64 device_addr, u64 size)
- {
- struct pvr_gem_object *pvr_obj;
- struct drm_gpuva *va;
- int err;
- mutex_lock(&vm_ctx->lock);
- va = drm_gpuva_find(&vm_ctx->gpuvm_mgr, device_addr, size);
- if (va) {
- pvr_obj = gem_to_pvr_gem(va->gem.obj);
- err = pvr_vm_unmap_obj_locked(vm_ctx, pvr_obj,
- va->va.addr, va->va.range);
- } else {
- err = -ENOENT;
- }
- mutex_unlock(&vm_ctx->lock);
- return err;
- }
- /**
- * pvr_vm_unmap_all() - Unmap all mappings associated with a VM context.
- * @vm_ctx: Target VM context.
- *
- * This function ensures that no mappings are left dangling by unmapping them
- * all in order of ascending device-virtual address.
- */
- void
- pvr_vm_unmap_all(struct pvr_vm_context *vm_ctx)
- {
- mutex_lock(&vm_ctx->lock);
- for (;;) {
- struct pvr_gem_object *pvr_obj;
- struct drm_gpuva *va;
- va = drm_gpuva_find_first(&vm_ctx->gpuvm_mgr,
- vm_ctx->gpuvm_mgr.mm_start,
- vm_ctx->gpuvm_mgr.mm_range);
- if (!va)
- break;
- pvr_obj = gem_to_pvr_gem(va->gem.obj);
- WARN_ON(pvr_vm_unmap_obj_locked(vm_ctx, pvr_obj,
- va->va.addr, va->va.range));
- }
- mutex_unlock(&vm_ctx->lock);
- }
- /* 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;
- }
|