Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Sarah Walker | 2325 | 99.06% | 6 | 85.71% |
Donald Robson | 22 | 0.94% | 1 | 14.29% |
Total | 2347 | 7 |
// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright (c) 2023 Imagination Technologies Ltd. */ #include "pvr_free_list.h" #include "pvr_gem.h" #include "pvr_hwrt.h" #include "pvr_rogue_fwif.h" #include "pvr_vm.h" #include <drm/drm_gem.h> #include <linux/slab.h> #include <linux/xarray.h> #include <uapi/drm/pvr_drm.h> #define FREE_LIST_ENTRY_SIZE sizeof(u32) #define FREE_LIST_ALIGNMENT \ ((ROGUE_BIF_PM_FREELIST_BASE_ADDR_ALIGNSIZE / FREE_LIST_ENTRY_SIZE) - 1) #define FREE_LIST_MIN_PAGES 50 #define FREE_LIST_MIN_PAGES_BRN66011 40 #define FREE_LIST_MIN_PAGES_ROGUEXE 25 /** * pvr_get_free_list_min_pages() - Get minimum free list size for this device * @pvr_dev: Device pointer. * * Returns: * * Minimum free list size, in PM physical pages. */ u32 pvr_get_free_list_min_pages(struct pvr_device *pvr_dev) { u32 value; if (PVR_HAS_FEATURE(pvr_dev, roguexe)) { if (PVR_HAS_QUIRK(pvr_dev, 66011)) value = FREE_LIST_MIN_PAGES_BRN66011; else value = FREE_LIST_MIN_PAGES_ROGUEXE; } else { value = FREE_LIST_MIN_PAGES; } return value; } static int free_list_create_kernel_structure(struct pvr_file *pvr_file, struct drm_pvr_ioctl_create_free_list_args *args, struct pvr_free_list *free_list) { struct pvr_gem_object *free_list_obj; struct pvr_vm_context *vm_ctx; u64 free_list_size; int err; if (args->grow_threshold > 100 || args->initial_num_pages > args->max_num_pages || args->grow_num_pages > args->max_num_pages || args->max_num_pages == 0 || (args->initial_num_pages < args->max_num_pages && !args->grow_num_pages) || (args->initial_num_pages == args->max_num_pages && args->grow_num_pages)) return -EINVAL; if ((args->initial_num_pages & FREE_LIST_ALIGNMENT) || (args->max_num_pages & FREE_LIST_ALIGNMENT) || (args->grow_num_pages & FREE_LIST_ALIGNMENT)) return -EINVAL; vm_ctx = pvr_vm_context_lookup(pvr_file, args->vm_context_handle); if (!vm_ctx) return -EINVAL; free_list_obj = pvr_vm_find_gem_object(vm_ctx, args->free_list_gpu_addr, NULL, &free_list_size); if (!free_list_obj) { err = -EINVAL; goto err_put_vm_context; } if ((free_list_obj->flags & DRM_PVR_BO_ALLOW_CPU_USERSPACE_ACCESS) || !(free_list_obj->flags & DRM_PVR_BO_PM_FW_PROTECT) || free_list_size < (args->max_num_pages * FREE_LIST_ENTRY_SIZE)) { err = -EINVAL; goto err_put_free_list_obj; } free_list->pvr_dev = pvr_file->pvr_dev; free_list->current_pages = 0; free_list->max_pages = args->max_num_pages; free_list->grow_pages = args->grow_num_pages; free_list->grow_threshold = args->grow_threshold; free_list->obj = free_list_obj; free_list->free_list_gpu_addr = args->free_list_gpu_addr; free_list->initial_num_pages = args->initial_num_pages; pvr_vm_context_put(vm_ctx); return 0; err_put_free_list_obj: pvr_gem_object_put(free_list_obj); err_put_vm_context: pvr_vm_context_put(vm_ctx); return err; } static void free_list_destroy_kernel_structure(struct pvr_free_list *free_list) { WARN_ON(!list_empty(&free_list->hwrt_list)); pvr_gem_object_put(free_list->obj); } /** * calculate_free_list_ready_pages_locked() - Function to work out the number of free * list pages to reserve for growing within * the FW without having to wait for the * host to progress a grow request * @free_list: Pointer to free list. * @pages: Total pages currently in free list. * * If the threshold or grow size means less than the alignment size (4 pages on * Rogue), then the feature is not used. * * Caller must hold &free_list->lock. * * Return: number of pages to reserve. */ static u32 calculate_free_list_ready_pages_locked(struct pvr_free_list *free_list, u32 pages) { u32 ready_pages; lockdep_assert_held(&free_list->lock); ready_pages = ((pages * free_list->grow_threshold) / 100); /* The number of pages must be less than the grow size. */ ready_pages = min(ready_pages, free_list->grow_pages); /* * The number of pages must be a multiple of the free list align size. */ ready_pages &= ~FREE_LIST_ALIGNMENT; return ready_pages; } static u32 calculate_free_list_ready_pages(struct pvr_free_list *free_list, u32 pages) { u32 ret; mutex_lock(&free_list->lock); ret = calculate_free_list_ready_pages_locked(free_list, pages); mutex_unlock(&free_list->lock); return ret; } static void free_list_fw_init(void *cpu_ptr, void *priv) { struct rogue_fwif_freelist *fw_data = cpu_ptr; struct pvr_free_list *free_list = priv; u32 ready_pages; /* Fill out FW structure */ ready_pages = calculate_free_list_ready_pages(free_list, free_list->initial_num_pages); fw_data->max_pages = free_list->max_pages; fw_data->current_pages = free_list->initial_num_pages - ready_pages; fw_data->grow_pages = free_list->grow_pages; fw_data->ready_pages = ready_pages; fw_data->freelist_id = free_list->fw_id; fw_data->grow_pending = false; fw_data->current_stack_top = fw_data->current_pages - 1; fw_data->freelist_dev_addr = free_list->free_list_gpu_addr; fw_data->current_dev_addr = (fw_data->freelist_dev_addr + ((fw_data->max_pages - fw_data->current_pages) * FREE_LIST_ENTRY_SIZE)) & ~((u64)ROGUE_BIF_PM_FREELIST_BASE_ADDR_ALIGNSIZE - 1); } static int free_list_create_fw_structure(struct pvr_file *pvr_file, struct drm_pvr_ioctl_create_free_list_args *args, struct pvr_free_list *free_list) { struct pvr_device *pvr_dev = pvr_file->pvr_dev; /* * Create and map the FW structure so we can initialise it. This is not * accessed on the CPU side post-initialisation so the mapping lifetime * is only for this function. */ free_list->fw_data = pvr_fw_object_create_and_map(pvr_dev, sizeof(*free_list->fw_data), PVR_BO_FW_FLAGS_DEVICE_UNCACHED, free_list_fw_init, free_list, &free_list->fw_obj); if (IS_ERR(free_list->fw_data)) return PTR_ERR(free_list->fw_data); return 0; } static void free_list_destroy_fw_structure(struct pvr_free_list *free_list) { pvr_fw_object_unmap_and_destroy(free_list->fw_obj); } static int pvr_free_list_insert_pages_locked(struct pvr_free_list *free_list, struct sg_table *sgt, u32 offset, u32 num_pages) { struct sg_dma_page_iter dma_iter; u32 *page_list; lockdep_assert_held(&free_list->lock); page_list = pvr_gem_object_vmap(free_list->obj); if (IS_ERR(page_list)) return PTR_ERR(page_list); offset /= FREE_LIST_ENTRY_SIZE; /* clang-format off */ for_each_sgtable_dma_page(sgt, &dma_iter, 0) { dma_addr_t dma_addr = sg_page_iter_dma_address(&dma_iter); u64 dma_pfn = dma_addr >> ROGUE_BIF_PM_PHYSICAL_PAGE_ALIGNSHIFT; u32 dma_addr_offset; BUILD_BUG_ON(ROGUE_BIF_PM_PHYSICAL_PAGE_SIZE > PAGE_SIZE); for (dma_addr_offset = 0; dma_addr_offset < PAGE_SIZE; dma_addr_offset += ROGUE_BIF_PM_PHYSICAL_PAGE_SIZE) { WARN_ON_ONCE(dma_pfn >> 32); page_list[offset++] = (u32)dma_pfn; dma_pfn++; num_pages--; if (!num_pages) break; } if (!num_pages) break; } /* clang-format on */ /* Make sure our free_list update is flushed. */ wmb(); pvr_gem_object_vunmap(free_list->obj); return 0; } static int pvr_free_list_insert_node_locked(struct pvr_free_list_node *free_list_node) { struct pvr_free_list *free_list = free_list_node->free_list; struct sg_table *sgt; u32 start_page; u32 offset; int err; lockdep_assert_held(&free_list->lock); start_page = free_list->max_pages - free_list->current_pages - free_list_node->num_pages; offset = (start_page * FREE_LIST_ENTRY_SIZE) & ~((u64)ROGUE_BIF_PM_FREELIST_BASE_ADDR_ALIGNSIZE - 1); sgt = drm_gem_shmem_get_pages_sgt(&free_list_node->mem_obj->base); if (WARN_ON(IS_ERR(sgt))) return PTR_ERR(sgt); err = pvr_free_list_insert_pages_locked(free_list, sgt, offset, free_list_node->num_pages); if (!err) free_list->current_pages += free_list_node->num_pages; return err; } static int pvr_free_list_grow(struct pvr_free_list *free_list, u32 num_pages) { struct pvr_device *pvr_dev = free_list->pvr_dev; struct pvr_free_list_node *free_list_node; int err; mutex_lock(&free_list->lock); if (num_pages & FREE_LIST_ALIGNMENT) { err = -EINVAL; goto err_unlock; } free_list_node = kzalloc(sizeof(*free_list_node), GFP_KERNEL); if (!free_list_node) { err = -ENOMEM; goto err_unlock; } free_list_node->num_pages = num_pages; free_list_node->free_list = free_list; free_list_node->mem_obj = pvr_gem_object_create(pvr_dev, num_pages << ROGUE_BIF_PM_PHYSICAL_PAGE_ALIGNSHIFT, PVR_BO_FW_FLAGS_DEVICE_CACHED); if (IS_ERR(free_list_node->mem_obj)) { err = PTR_ERR(free_list_node->mem_obj); goto err_free; } err = pvr_free_list_insert_node_locked(free_list_node); if (err) goto err_destroy_gem_object; list_add_tail(&free_list_node->node, &free_list->mem_block_list); /* * Reserve a number ready pages to allow the FW to process OOM quickly * and asynchronously request a grow. */ free_list->ready_pages = calculate_free_list_ready_pages_locked(free_list, free_list->current_pages); free_list->current_pages -= free_list->ready_pages; mutex_unlock(&free_list->lock); return 0; err_destroy_gem_object: pvr_gem_object_put(free_list_node->mem_obj); err_free: kfree(free_list_node); err_unlock: mutex_unlock(&free_list->lock); return err; } void pvr_free_list_process_grow_req(struct pvr_device *pvr_dev, struct rogue_fwif_fwccb_cmd_freelist_gs_data *req) { struct pvr_free_list *free_list = pvr_free_list_lookup_id(pvr_dev, req->freelist_id); struct rogue_fwif_kccb_cmd resp_cmd = { .cmd_type = ROGUE_FWIF_KCCB_CMD_FREELIST_GROW_UPDATE, }; struct rogue_fwif_freelist_gs_data *resp = &resp_cmd.cmd_data.free_list_gs_data; u32 grow_pages = 0; /* If we don't have a freelist registered for this ID, we can't do much. */ if (WARN_ON(!free_list)) return; /* Since the FW made the request, it has already consumed the ready pages, * update the host struct. */ free_list->current_pages += free_list->ready_pages; free_list->ready_pages = 0; /* If the grow succeeds, update the grow_pages argument. */ if (!pvr_free_list_grow(free_list, free_list->grow_pages)) grow_pages = free_list->grow_pages; /* Now prepare the response and send it back to the FW. */ pvr_fw_object_get_fw_addr(free_list->fw_obj, &resp->freelist_fw_addr); resp->delta_pages = grow_pages; resp->new_pages = free_list->current_pages + free_list->ready_pages; resp->ready_pages = free_list->ready_pages; pvr_free_list_put(free_list); WARN_ON(pvr_kccb_send_cmd(pvr_dev, &resp_cmd, NULL)); } static void pvr_free_list_free_node(struct pvr_free_list_node *free_list_node) { pvr_gem_object_put(free_list_node->mem_obj); kfree(free_list_node); } /** * pvr_free_list_create() - Create a new free list and return an object pointer * @pvr_file: Pointer to pvr_file structure. * @args: Creation arguments from userspace. * * Return: * * Pointer to new free_list, or * * ERR_PTR(-%ENOMEM) on out of memory. */ struct pvr_free_list * pvr_free_list_create(struct pvr_file *pvr_file, struct drm_pvr_ioctl_create_free_list_args *args) { struct pvr_free_list *free_list; int err; /* Create and fill out the kernel structure */ free_list = kzalloc(sizeof(*free_list), GFP_KERNEL); if (!free_list) return ERR_PTR(-ENOMEM); kref_init(&free_list->ref_count); INIT_LIST_HEAD(&free_list->mem_block_list); INIT_LIST_HEAD(&free_list->hwrt_list); mutex_init(&free_list->lock); err = free_list_create_kernel_structure(pvr_file, args, free_list); if (err < 0) goto err_free; /* Allocate global object ID for firmware. */ err = xa_alloc(&pvr_file->pvr_dev->free_list_ids, &free_list->fw_id, free_list, xa_limit_32b, GFP_KERNEL); if (err) goto err_destroy_kernel_structure; err = free_list_create_fw_structure(pvr_file, args, free_list); if (err < 0) goto err_free_fw_id; err = pvr_free_list_grow(free_list, args->initial_num_pages); if (err < 0) goto err_fw_struct_cleanup; return free_list; err_fw_struct_cleanup: WARN_ON(pvr_fw_structure_cleanup(free_list->pvr_dev, ROGUE_FWIF_CLEANUP_FREELIST, free_list->fw_obj, 0)); err_free_fw_id: xa_erase(&free_list->pvr_dev->free_list_ids, free_list->fw_id); err_destroy_kernel_structure: free_list_destroy_kernel_structure(free_list); err_free: mutex_destroy(&free_list->lock); kfree(free_list); return ERR_PTR(err); } static void pvr_free_list_release(struct kref *ref_count) { struct pvr_free_list *free_list = container_of(ref_count, struct pvr_free_list, ref_count); struct list_head *pos, *n; int err; xa_erase(&free_list->pvr_dev->free_list_ids, free_list->fw_id); err = pvr_fw_structure_cleanup(free_list->pvr_dev, ROGUE_FWIF_CLEANUP_FREELIST, free_list->fw_obj, 0); if (err == -EBUSY) { /* Flush the FWCCB to process any HWR or freelist reconstruction * request that might keep the freelist busy, and try again. */ pvr_fwccb_process(free_list->pvr_dev); err = pvr_fw_structure_cleanup(free_list->pvr_dev, ROGUE_FWIF_CLEANUP_FREELIST, free_list->fw_obj, 0); } WARN_ON(err); /* clang-format off */ list_for_each_safe(pos, n, &free_list->mem_block_list) { struct pvr_free_list_node *free_list_node = container_of(pos, struct pvr_free_list_node, node); list_del(pos); pvr_free_list_free_node(free_list_node); } /* clang-format on */ free_list_destroy_kernel_structure(free_list); free_list_destroy_fw_structure(free_list); mutex_destroy(&free_list->lock); kfree(free_list); } /** * pvr_destroy_free_lists_for_file: Destroy any free lists associated with the * given file. * @pvr_file: Pointer to pvr_file structure. * * Removes all free lists associated with @pvr_file from the device free_list * list and drops initial references. Free lists will then be destroyed once * all outstanding references are dropped. */ void pvr_destroy_free_lists_for_file(struct pvr_file *pvr_file) { struct pvr_free_list *free_list; unsigned long handle; xa_for_each(&pvr_file->free_list_handles, handle, free_list) { (void)free_list; pvr_free_list_put(xa_erase(&pvr_file->free_list_handles, handle)); } } /** * pvr_free_list_put() - Release reference on free list * @free_list: Pointer to list to release reference on */ void pvr_free_list_put(struct pvr_free_list *free_list) { if (free_list) kref_put(&free_list->ref_count, pvr_free_list_release); } void pvr_free_list_add_hwrt(struct pvr_free_list *free_list, struct pvr_hwrt_data *hwrt_data) { mutex_lock(&free_list->lock); list_add_tail(&hwrt_data->freelist_node, &free_list->hwrt_list); mutex_unlock(&free_list->lock); } void pvr_free_list_remove_hwrt(struct pvr_free_list *free_list, struct pvr_hwrt_data *hwrt_data) { mutex_lock(&free_list->lock); list_del(&hwrt_data->freelist_node); mutex_unlock(&free_list->lock); } static void pvr_free_list_reconstruct(struct pvr_device *pvr_dev, u32 freelist_id) { struct pvr_free_list *free_list = pvr_free_list_lookup_id(pvr_dev, freelist_id); struct pvr_free_list_node *free_list_node; struct rogue_fwif_freelist *fw_data; struct pvr_hwrt_data *hwrt_data; if (!free_list) return; mutex_lock(&free_list->lock); /* Rebuild the free list based on the memory block list. */ free_list->current_pages = 0; list_for_each_entry(free_list_node, &free_list->mem_block_list, node) WARN_ON(pvr_free_list_insert_node_locked(free_list_node)); /* * Remove the ready pages, which are reserved to allow the FW to process OOM quickly and * asynchronously request a grow. */ free_list->current_pages -= free_list->ready_pages; fw_data = free_list->fw_data; fw_data->current_stack_top = fw_data->current_pages - 1; fw_data->allocated_page_count = 0; fw_data->allocated_mmu_page_count = 0; /* Reset the state of any associated HWRTs. */ list_for_each_entry(hwrt_data, &free_list->hwrt_list, freelist_node) { struct rogue_fwif_hwrtdata *hwrt_fw_data = pvr_fw_object_vmap(hwrt_data->fw_obj); if (!WARN_ON(IS_ERR(hwrt_fw_data))) { hwrt_fw_data->state = ROGUE_FWIF_RTDATA_STATE_HWR; hwrt_fw_data->hwrt_data_flags &= ~HWRTDATA_HAS_LAST_GEOM; } pvr_fw_object_vunmap(hwrt_data->fw_obj); } mutex_unlock(&free_list->lock); pvr_free_list_put(free_list); } void pvr_free_list_process_reconstruct_req(struct pvr_device *pvr_dev, struct rogue_fwif_fwccb_cmd_freelists_reconstruction_data *req) { struct rogue_fwif_kccb_cmd resp_cmd = { .cmd_type = ROGUE_FWIF_KCCB_CMD_FREELISTS_RECONSTRUCTION_UPDATE, }; struct rogue_fwif_freelists_reconstruction_data *resp = &resp_cmd.cmd_data.free_lists_reconstruction_data; for (u32 i = 0; i < req->freelist_count; i++) pvr_free_list_reconstruct(pvr_dev, req->freelist_ids[i]); resp->freelist_count = req->freelist_count; memcpy(resp->freelist_ids, req->freelist_ids, req->freelist_count * sizeof(resp->freelist_ids[0])); WARN_ON(pvr_kccb_send_cmd(pvr_dev, &resp_cmd, NULL)); }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1