| // SPDX-License-Identifier: GPL-2.0-only OR MIT | |
| /* Copyright (c) 2023 Imagination Technologies Ltd. */ | |
| #include "pvr_ccb.h" | |
| #include "pvr_device.h" | |
| #include "pvr_device_info.h" | |
| #include "pvr_fw.h" | |
| #include "pvr_fw_info.h" | |
| #include "pvr_fw_startstop.h" | |
| #include "pvr_fw_trace.h" | |
| #include "pvr_gem.h" | |
| #include "pvr_power.h" | |
| #include "pvr_rogue_fwif_dev_info.h" | |
| #include "pvr_rogue_heap_config.h" | |
| #include "pvr_vm.h" | |
| #include <drm/drm_drv.h> | |
| #include <drm/drm_managed.h> | |
| #include <drm/drm_mm.h> | |
| #include <drm/drm_print.h> | |
| #include <linux/clk.h> | |
| #include <linux/firmware.h> | |
| #include <linux/math.h> | |
| #include <linux/minmax.h> | |
| #include <linux/sizes.h> | |
| #define FW_MAX_SUPPORTED_MAJOR_VERSION 1 | |
| #define FW_BOOT_TIMEOUT_USEC 5000000 | |
| /* Config heap occupies top 192k of the firmware heap. */ | |
| #define PVR_ROGUE_FW_CONFIG_HEAP_GRANULARITY SZ_64K | |
| #define PVR_ROGUE_FW_CONFIG_HEAP_SIZE (3 * PVR_ROGUE_FW_CONFIG_HEAP_GRANULARITY) | |
| /* Main firmware allocations should come from the remainder of the heap. */ | |
| #define PVR_ROGUE_FW_MAIN_HEAP_BASE ROGUE_FW_HEAP_BASE | |
| /* Offsets from start of configuration area of FW heap. */ | |
| #define PVR_ROGUE_FWIF_CONNECTION_CTL_OFFSET 0 | |
| #define PVR_ROGUE_FWIF_OSINIT_OFFSET \ | |
| (PVR_ROGUE_FWIF_CONNECTION_CTL_OFFSET + PVR_ROGUE_FW_CONFIG_HEAP_GRANULARITY) | |
| #define PVR_ROGUE_FWIF_SYSINIT_OFFSET \ | |
| (PVR_ROGUE_FWIF_OSINIT_OFFSET + PVR_ROGUE_FW_CONFIG_HEAP_GRANULARITY) | |
| #define PVR_ROGUE_FAULT_PAGE_SIZE SZ_4K | |
| #define PVR_SYNC_OBJ_SIZE sizeof(u32) | |
| const struct pvr_fw_layout_entry * | |
| pvr_fw_find_layout_entry(struct pvr_device *pvr_dev, enum pvr_fw_section_id id) | |
| { | |
| const struct pvr_fw_layout_entry *layout_entries = pvr_dev->fw_dev.layout_entries; | |
| u32 num_layout_entries = pvr_dev->fw_dev.header->layout_entry_num; | |
| for (u32 entry = 0; entry < num_layout_entries; entry++) { | |
| if (layout_entries[entry].id == id) | |
| return &layout_entries[entry]; | |
| } | |
| return NULL; | |
| } | |
| static const struct pvr_fw_layout_entry * | |
| pvr_fw_find_private_data(struct pvr_device *pvr_dev) | |
| { | |
| const struct pvr_fw_layout_entry *layout_entries = pvr_dev->fw_dev.layout_entries; | |
| u32 num_layout_entries = pvr_dev->fw_dev.header->layout_entry_num; | |
| for (u32 entry = 0; entry < num_layout_entries; entry++) { | |
| if (layout_entries[entry].id == META_PRIVATE_DATA || | |
| layout_entries[entry].id == MIPS_PRIVATE_DATA || | |
| layout_entries[entry].id == RISCV_PRIVATE_DATA) | |
| return &layout_entries[entry]; | |
| } | |
| return NULL; | |
| } | |
| #define DEV_INFO_MASK_SIZE(x) DIV_ROUND_UP(x, 64) | |
| /** | |
| * pvr_fw_validate() - Parse firmware header and check compatibility | |
| * @pvr_dev: Device pointer. | |
| * | |
| * Returns: | |
| * * 0 on success, or | |
| * * -EINVAL if firmware is incompatible. | |
| */ | |
| static int | |
| pvr_fw_validate(struct pvr_device *pvr_dev) | |
| { | |
| struct drm_device *drm_dev = from_pvr_device(pvr_dev); | |
| const struct firmware *firmware = pvr_dev->fw_dev.firmware; | |
| const struct pvr_fw_layout_entry *layout_entries; | |
| const struct pvr_fw_info_header *header; | |
| const u8 *fw = firmware->data; | |
| u32 fw_offset = firmware->size - SZ_4K; | |
| u32 layout_table_size; | |
| if (firmware->size < SZ_4K || (firmware->size % FW_BLOCK_SIZE)) | |
| return -EINVAL; | |
| header = (const struct pvr_fw_info_header *)&fw[fw_offset]; | |
| if (header->info_version != PVR_FW_INFO_VERSION) { | |
| drm_err(drm_dev, "Unsupported fw info version %u\n", | |
| header->info_version); | |
| return -EINVAL; | |
| } | |
| if (header->header_len != sizeof(struct pvr_fw_info_header) || | |
| header->layout_entry_size != sizeof(struct pvr_fw_layout_entry) || | |
| header->layout_entry_num > PVR_FW_INFO_MAX_NUM_ENTRIES) { | |
| drm_err(drm_dev, "FW info format mismatch\n"); | |
| return -EINVAL; | |
| } | |
| if (!(header->flags & PVR_FW_FLAGS_OPEN_SOURCE) || | |
| header->fw_version_major > FW_MAX_SUPPORTED_MAJOR_VERSION || | |
| header->fw_version_major == 0) { | |
| drm_err(drm_dev, "Unsupported FW version %u.%u (build: %u%s)\n", | |
| header->fw_version_major, header->fw_version_minor, | |
| header->fw_version_build, | |
| (header->flags & PVR_FW_FLAGS_OPEN_SOURCE) ? " OS" : ""); | |
| return -EINVAL; | |
| } | |
| if (pvr_gpu_id_to_packed_bvnc(&pvr_dev->gpu_id) != header->bvnc) { | |
| struct pvr_gpu_id fw_gpu_id; | |
| packed_bvnc_to_pvr_gpu_id(header->bvnc, &fw_gpu_id); | |
| drm_err(drm_dev, "FW built for incorrect GPU ID %i.%i.%i.%i (expected %i.%i.%i.%i)\n", | |
| fw_gpu_id.b, fw_gpu_id.v, fw_gpu_id.n, fw_gpu_id.c, | |
| pvr_dev->gpu_id.b, pvr_dev->gpu_id.v, pvr_dev->gpu_id.n, pvr_dev->gpu_id.c); | |
| return -EINVAL; | |
| } | |
| fw_offset += header->header_len; | |
| layout_table_size = | |
| header->layout_entry_size * header->layout_entry_num; | |
| if ((fw_offset + layout_table_size) > firmware->size) | |
| return -EINVAL; | |
| layout_entries = (const struct pvr_fw_layout_entry *)&fw[fw_offset]; | |
| for (u32 entry = 0; entry < header->layout_entry_num; entry++) { | |
| u32 start_addr = layout_entries[entry].base_addr; | |
| u32 end_addr = start_addr + layout_entries[entry].alloc_size; | |
| if (start_addr >= end_addr) | |
| return -EINVAL; | |
| } | |
| fw_offset = (firmware->size - SZ_4K) - header->device_info_size; | |
| drm_info(drm_dev, "FW version v%u.%u (build %u OS)\n", header->fw_version_major, | |
| header->fw_version_minor, header->fw_version_build); | |
| pvr_dev->fw_version.major = header->fw_version_major; | |
| pvr_dev->fw_version.minor = header->fw_version_minor; | |
| pvr_dev->fw_dev.header = header; | |
| pvr_dev->fw_dev.layout_entries = layout_entries; | |
| return 0; | |
| } | |
| static int | |
| pvr_fw_get_device_info(struct pvr_device *pvr_dev) | |
| { | |
| const struct firmware *firmware = pvr_dev->fw_dev.firmware; | |
| struct pvr_fw_device_info_header *header; | |
| const u8 *fw = firmware->data; | |
| const u64 *dev_info; | |
| u32 fw_offset; | |
| fw_offset = (firmware->size - SZ_4K) - pvr_dev->fw_dev.header->device_info_size; | |
| header = (struct pvr_fw_device_info_header *)&fw[fw_offset]; | |
| dev_info = (u64 *)(header + 1); | |
| pvr_device_info_set_quirks(pvr_dev, dev_info, header->brn_mask_size); | |
| dev_info += header->brn_mask_size; | |
| pvr_device_info_set_enhancements(pvr_dev, dev_info, header->ern_mask_size); | |
| dev_info += header->ern_mask_size; | |
| return pvr_device_info_set_features(pvr_dev, dev_info, header->feature_mask_size, | |
| header->feature_param_size); | |
| } | |
| static void | |
| layout_get_sizes(struct pvr_device *pvr_dev) | |
| { | |
| const struct pvr_fw_layout_entry *layout_entries = pvr_dev->fw_dev.layout_entries; | |
| u32 num_layout_entries = pvr_dev->fw_dev.header->layout_entry_num; | |
| struct pvr_fw_mem *fw_mem = &pvr_dev->fw_dev.mem; | |
| fw_mem->code_alloc_size = 0; | |
| fw_mem->data_alloc_size = 0; | |
| fw_mem->core_code_alloc_size = 0; | |
| fw_mem->core_data_alloc_size = 0; | |
| /* Extract section sizes from FW layout table. */ | |
| for (u32 entry = 0; entry < num_layout_entries; entry++) { | |
| switch (layout_entries[entry].type) { | |
| case FW_CODE: | |
| fw_mem->code_alloc_size += layout_entries[entry].alloc_size; | |
| break; | |
| case FW_DATA: | |
| fw_mem->data_alloc_size += layout_entries[entry].alloc_size; | |
| break; | |
| case FW_COREMEM_CODE: | |
| fw_mem->core_code_alloc_size += | |
| layout_entries[entry].alloc_size; | |
| break; | |
| case FW_COREMEM_DATA: | |
| fw_mem->core_data_alloc_size += | |
| layout_entries[entry].alloc_size; | |
| break; | |
| case NONE: | |
| break; | |
| } | |
| } | |
| } | |
| int | |
| pvr_fw_find_mmu_segment(struct pvr_device *pvr_dev, u32 addr, u32 size, void *fw_code_ptr, | |
| void *fw_data_ptr, void *fw_core_code_ptr, void *fw_core_data_ptr, | |
| void **host_addr_out) | |
| { | |
| const struct pvr_fw_layout_entry *layout_entries = pvr_dev->fw_dev.layout_entries; | |
| u32 num_layout_entries = pvr_dev->fw_dev.header->layout_entry_num; | |
| u32 end_addr = addr + size; | |
| /* Ensure requested range is not zero, and size is not causing addr to overflow. */ | |
| if (end_addr <= addr) | |
| return -EINVAL; | |
| for (int entry = 0; entry < num_layout_entries; entry++) { | |
| u32 entry_start_addr = layout_entries[entry].base_addr; | |
| u32 entry_end_addr = entry_start_addr + layout_entries[entry].alloc_size; | |
| if (addr >= entry_start_addr && addr < entry_end_addr && | |
| end_addr > entry_start_addr && end_addr <= entry_end_addr) { | |
| switch (layout_entries[entry].type) { | |
| case FW_CODE: | |
| *host_addr_out = fw_code_ptr; | |
| break; | |
| case FW_DATA: | |
| *host_addr_out = fw_data_ptr; | |
| break; | |
| case FW_COREMEM_CODE: | |
| *host_addr_out = fw_core_code_ptr; | |
| break; | |
| case FW_COREMEM_DATA: | |
| *host_addr_out = fw_core_data_ptr; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| /* Direct Mem write to mapped memory */ | |
| addr -= layout_entries[entry].base_addr; | |
| addr += layout_entries[entry].alloc_offset; | |
| /* | |
| * Add offset to pointer to FW allocation only if that | |
| * allocation is available | |
| */ | |
| *(u8 **)host_addr_out += addr; | |
| return 0; | |
| } | |
| } | |
| return -EINVAL; | |
| } | |
| static int | |
| pvr_fw_create_fwif_connection_ctl(struct pvr_device *pvr_dev) | |
| { | |
| struct drm_device *drm_dev = from_pvr_device(pvr_dev); | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| fw_dev->fwif_connection_ctl = | |
| pvr_fw_object_create_and_map_offset(pvr_dev, | |
| fw_dev->fw_heap_info.config_offset + | |
| PVR_ROGUE_FWIF_CONNECTION_CTL_OFFSET, | |
| sizeof(*fw_dev->fwif_connection_ctl), | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| NULL, NULL, | |
| &fw_dev->mem.fwif_connection_ctl_obj); | |
| if (IS_ERR(fw_dev->fwif_connection_ctl)) { | |
| drm_err(drm_dev, | |
| "Unable to allocate FWIF connection control memory\n"); | |
| return PTR_ERR(fw_dev->fwif_connection_ctl); | |
| } | |
| return 0; | |
| } | |
| static void | |
| pvr_fw_fini_fwif_connection_ctl(struct pvr_device *pvr_dev) | |
| { | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| pvr_fw_object_unmap_and_destroy(fw_dev->mem.fwif_connection_ctl_obj); | |
| } | |
| static void | |
| fw_osinit_init(void *cpu_ptr, void *priv) | |
| { | |
| struct rogue_fwif_osinit *fwif_osinit = cpu_ptr; | |
| struct pvr_device *pvr_dev = priv; | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| struct pvr_fw_mem *fw_mem = &fw_dev->mem; | |
| fwif_osinit->kernel_ccbctl_fw_addr = pvr_dev->kccb.ccb.ctrl_fw_addr; | |
| fwif_osinit->kernel_ccb_fw_addr = pvr_dev->kccb.ccb.ccb_fw_addr; | |
| pvr_fw_object_get_fw_addr(pvr_dev->kccb.rtn_obj, | |
| &fwif_osinit->kernel_ccb_rtn_slots_fw_addr); | |
| fwif_osinit->firmware_ccbctl_fw_addr = pvr_dev->fwccb.ctrl_fw_addr; | |
| fwif_osinit->firmware_ccb_fw_addr = pvr_dev->fwccb.ccb_fw_addr; | |
| fwif_osinit->work_est_firmware_ccbctl_fw_addr = 0; | |
| fwif_osinit->work_est_firmware_ccb_fw_addr = 0; | |
| pvr_fw_object_get_fw_addr(fw_mem->hwrinfobuf_obj, | |
| &fwif_osinit->rogue_fwif_hwr_info_buf_ctl_fw_addr); | |
| pvr_fw_object_get_fw_addr(fw_mem->osdata_obj, &fwif_osinit->fw_os_data_fw_addr); | |
| fwif_osinit->hwr_debug_dump_limit = 0; | |
| rogue_fwif_compchecks_bvnc_init(&fwif_osinit->rogue_comp_checks.hw_bvnc); | |
| rogue_fwif_compchecks_bvnc_init(&fwif_osinit->rogue_comp_checks.fw_bvnc); | |
| } | |
| static void | |
| fw_osdata_init(void *cpu_ptr, void *priv) | |
| { | |
| struct rogue_fwif_osdata *fwif_osdata = cpu_ptr; | |
| struct pvr_device *pvr_dev = priv; | |
| struct pvr_fw_mem *fw_mem = &pvr_dev->fw_dev.mem; | |
| pvr_fw_object_get_fw_addr(fw_mem->power_sync_obj, &fwif_osdata->power_sync_fw_addr); | |
| } | |
| static void | |
| fw_fault_page_init(void *cpu_ptr, void *priv) | |
| { | |
| u32 *fault_page = cpu_ptr; | |
| for (int i = 0; i < PVR_ROGUE_FAULT_PAGE_SIZE / sizeof(*fault_page); i++) | |
| fault_page[i] = 0xdeadbee0; | |
| } | |
| static void | |
| fw_sysinit_init(void *cpu_ptr, void *priv) | |
| { | |
| struct rogue_fwif_sysinit *fwif_sysinit = cpu_ptr; | |
| struct pvr_device *pvr_dev = priv; | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| struct pvr_fw_mem *fw_mem = &fw_dev->mem; | |
| dma_addr_t fault_dma_addr = 0; | |
| u32 clock_speed_hz = clk_get_rate(pvr_dev->core_clk); | |
| WARN_ON(!clock_speed_hz); | |
| WARN_ON(pvr_fw_object_get_dma_addr(fw_mem->fault_page_obj, 0, &fault_dma_addr)); | |
| fwif_sysinit->fault_phys_addr = (u64)fault_dma_addr; | |
| fwif_sysinit->pds_exec_base = ROGUE_PDSCODEDATA_HEAP_BASE; | |
| fwif_sysinit->usc_exec_base = ROGUE_USCCODE_HEAP_BASE; | |
| pvr_fw_object_get_fw_addr(fw_mem->runtime_cfg_obj, &fwif_sysinit->runtime_cfg_fw_addr); | |
| pvr_fw_object_get_fw_addr(fw_dev->fw_trace.tracebuf_ctrl_obj, | |
| &fwif_sysinit->trace_buf_ctl_fw_addr); | |
| pvr_fw_object_get_fw_addr(fw_mem->sysdata_obj, &fwif_sysinit->fw_sys_data_fw_addr); | |
| pvr_fw_object_get_fw_addr(fw_mem->gpu_util_fwcb_obj, | |
| &fwif_sysinit->gpu_util_fw_cb_ctl_fw_addr); | |
| if (fw_mem->core_data_obj) { | |
| pvr_fw_object_get_fw_addr(fw_mem->core_data_obj, | |
| &fwif_sysinit->coremem_data_store.fw_addr); | |
| } | |
| /* Currently unsupported. */ | |
| fwif_sysinit->counter_dump_ctl.buffer_fw_addr = 0; | |
| fwif_sysinit->counter_dump_ctl.size_in_dwords = 0; | |
| /* Skip alignment checks. */ | |
| fwif_sysinit->align_checks = 0; | |
| fwif_sysinit->filter_flags = 0; | |
| fwif_sysinit->hw_perf_filter = 0; | |
| fwif_sysinit->firmware_perf = FW_PERF_CONF_NONE; | |
| fwif_sysinit->initial_core_clock_speed = clock_speed_hz; | |
| fwif_sysinit->active_pm_latency_ms = 0; | |
| fwif_sysinit->gpio_validation_mode = ROGUE_FWIF_GPIO_VAL_OFF; | |
| fwif_sysinit->firmware_started = false; | |
| fwif_sysinit->marker_val = 1; | |
| memset(&fwif_sysinit->bvnc_km_feature_flags, 0, | |
| sizeof(fwif_sysinit->bvnc_km_feature_flags)); | |
| } | |
| #define ROGUE_FWIF_SLC_MIN_SIZE_FOR_DM_OVERLAP_KB 4 | |
| static void | |
| fw_sysdata_init(void *cpu_ptr, void *priv) | |
| { | |
| struct rogue_fwif_sysdata *fwif_sysdata = cpu_ptr; | |
| struct pvr_device *pvr_dev = priv; | |
| u32 slc_size_in_kilobytes = 0; | |
| u32 config_flags = 0; | |
| WARN_ON(PVR_FEATURE_VALUE(pvr_dev, slc_size_in_kilobytes, &slc_size_in_kilobytes)); | |
| if (slc_size_in_kilobytes < ROGUE_FWIF_SLC_MIN_SIZE_FOR_DM_OVERLAP_KB) | |
| config_flags |= ROGUE_FWIF_INICFG_DISABLE_DM_OVERLAP; | |
| fwif_sysdata->config_flags = config_flags; | |
| } | |
| static void | |
| fw_runtime_cfg_init(void *cpu_ptr, void *priv) | |
| { | |
| struct rogue_fwif_runtime_cfg *runtime_cfg = cpu_ptr; | |
| struct pvr_device *pvr_dev = priv; | |
| u32 clock_speed_hz = clk_get_rate(pvr_dev->core_clk); | |
| WARN_ON(!clock_speed_hz); | |
| runtime_cfg->core_clock_speed = clock_speed_hz; | |
| runtime_cfg->active_pm_latency_ms = 0; | |
| runtime_cfg->active_pm_latency_persistant = true; | |
| WARN_ON(PVR_FEATURE_VALUE(pvr_dev, num_clusters, | |
| &runtime_cfg->default_dusts_num_init) != 0); | |
| /* Keep watchdog timer disabled. */ | |
| runtime_cfg->wdg_period_us = 0; | |
| } | |
| static void | |
| fw_gpu_util_fwcb_init(void *cpu_ptr, void *priv) | |
| { | |
| struct rogue_fwif_gpu_util_fwcb *gpu_util_fwcb = cpu_ptr; | |
| gpu_util_fwcb->last_word = PVR_FWIF_GPU_UTIL_STATE_IDLE; | |
| } | |
| static int | |
| pvr_fw_create_structures(struct pvr_device *pvr_dev) | |
| { | |
| struct drm_device *drm_dev = from_pvr_device(pvr_dev); | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| struct pvr_fw_mem *fw_mem = &fw_dev->mem; | |
| int err; | |
| fw_dev->power_sync = pvr_fw_object_create_and_map(pvr_dev, sizeof(*fw_dev->power_sync), | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| NULL, NULL, &fw_mem->power_sync_obj); | |
| if (IS_ERR(fw_dev->power_sync)) { | |
| drm_err(drm_dev, "Unable to allocate FW power_sync structure\n"); | |
| return PTR_ERR(fw_dev->power_sync); | |
| } | |
| fw_dev->hwrinfobuf = pvr_fw_object_create_and_map(pvr_dev, sizeof(*fw_dev->hwrinfobuf), | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| NULL, NULL, &fw_mem->hwrinfobuf_obj); | |
| if (IS_ERR(fw_dev->hwrinfobuf)) { | |
| drm_err(drm_dev, | |
| "Unable to allocate FW hwrinfobuf structure\n"); | |
| err = PTR_ERR(fw_dev->hwrinfobuf); | |
| goto err_release_power_sync; | |
| } | |
| err = pvr_fw_object_create(pvr_dev, PVR_SYNC_OBJ_SIZE, | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| NULL, NULL, &fw_mem->mmucache_sync_obj); | |
| if (err) { | |
| drm_err(drm_dev, | |
| "Unable to allocate MMU cache sync object\n"); | |
| goto err_release_hwrinfobuf; | |
| } | |
| fw_dev->fwif_sysdata = pvr_fw_object_create_and_map(pvr_dev, | |
| sizeof(*fw_dev->fwif_sysdata), | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| fw_sysdata_init, pvr_dev, | |
| &fw_mem->sysdata_obj); | |
| if (IS_ERR(fw_dev->fwif_sysdata)) { | |
| drm_err(drm_dev, "Unable to allocate FW SYSDATA structure\n"); | |
| err = PTR_ERR(fw_dev->fwif_sysdata); | |
| goto err_release_mmucache_sync_obj; | |
| } | |
| err = pvr_fw_object_create(pvr_dev, PVR_ROGUE_FAULT_PAGE_SIZE, | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| fw_fault_page_init, NULL, &fw_mem->fault_page_obj); | |
| if (err) { | |
| drm_err(drm_dev, "Unable to allocate FW fault page\n"); | |
| goto err_release_sysdata; | |
| } | |
| err = pvr_fw_object_create(pvr_dev, sizeof(struct rogue_fwif_gpu_util_fwcb), | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| fw_gpu_util_fwcb_init, pvr_dev, &fw_mem->gpu_util_fwcb_obj); | |
| if (err) { | |
| drm_err(drm_dev, "Unable to allocate GPU util FWCB\n"); | |
| goto err_release_fault_page; | |
| } | |
| err = pvr_fw_object_create(pvr_dev, sizeof(struct rogue_fwif_runtime_cfg), | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| fw_runtime_cfg_init, pvr_dev, &fw_mem->runtime_cfg_obj); | |
| if (err) { | |
| drm_err(drm_dev, "Unable to allocate FW runtime config\n"); | |
| goto err_release_gpu_util_fwcb; | |
| } | |
| err = pvr_fw_trace_init(pvr_dev); | |
| if (err) | |
| goto err_release_runtime_cfg; | |
| fw_dev->fwif_osdata = pvr_fw_object_create_and_map(pvr_dev, | |
| sizeof(*fw_dev->fwif_osdata), | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| fw_osdata_init, pvr_dev, | |
| &fw_mem->osdata_obj); | |
| if (IS_ERR(fw_dev->fwif_osdata)) { | |
| drm_err(drm_dev, "Unable to allocate FW OSDATA structure\n"); | |
| err = PTR_ERR(fw_dev->fwif_osdata); | |
| goto err_fw_trace_fini; | |
| } | |
| fw_dev->fwif_osinit = | |
| pvr_fw_object_create_and_map_offset(pvr_dev, | |
| fw_dev->fw_heap_info.config_offset + | |
| PVR_ROGUE_FWIF_OSINIT_OFFSET, | |
| sizeof(*fw_dev->fwif_osinit), | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| fw_osinit_init, pvr_dev, &fw_mem->osinit_obj); | |
| if (IS_ERR(fw_dev->fwif_osinit)) { | |
| drm_err(drm_dev, "Unable to allocate FW OSINIT structure\n"); | |
| err = PTR_ERR(fw_dev->fwif_osinit); | |
| goto err_release_osdata; | |
| } | |
| fw_dev->fwif_sysinit = | |
| pvr_fw_object_create_and_map_offset(pvr_dev, | |
| fw_dev->fw_heap_info.config_offset + | |
| PVR_ROGUE_FWIF_SYSINIT_OFFSET, | |
| sizeof(*fw_dev->fwif_sysinit), | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| fw_sysinit_init, pvr_dev, &fw_mem->sysinit_obj); | |
| if (IS_ERR(fw_dev->fwif_sysinit)) { | |
| drm_err(drm_dev, "Unable to allocate FW SYSINIT structure\n"); | |
| err = PTR_ERR(fw_dev->fwif_sysinit); | |
| goto err_release_osinit; | |
| } | |
| return 0; | |
| err_release_osinit: | |
| pvr_fw_object_unmap_and_destroy(fw_mem->osinit_obj); | |
| err_release_osdata: | |
| pvr_fw_object_unmap_and_destroy(fw_mem->osdata_obj); | |
| err_fw_trace_fini: | |
| pvr_fw_trace_fini(pvr_dev); | |
| err_release_runtime_cfg: | |
| pvr_fw_object_destroy(fw_mem->runtime_cfg_obj); | |
| err_release_gpu_util_fwcb: | |
| pvr_fw_object_destroy(fw_mem->gpu_util_fwcb_obj); | |
| err_release_fault_page: | |
| pvr_fw_object_destroy(fw_mem->fault_page_obj); | |
| err_release_sysdata: | |
| pvr_fw_object_unmap_and_destroy(fw_mem->sysdata_obj); | |
| err_release_mmucache_sync_obj: | |
| pvr_fw_object_destroy(fw_mem->mmucache_sync_obj); | |
| err_release_hwrinfobuf: | |
| pvr_fw_object_unmap_and_destroy(fw_mem->hwrinfobuf_obj); | |
| err_release_power_sync: | |
| pvr_fw_object_unmap_and_destroy(fw_mem->power_sync_obj); | |
| return err; | |
| } | |
| static void | |
| pvr_fw_destroy_structures(struct pvr_device *pvr_dev) | |
| { | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| struct pvr_fw_mem *fw_mem = &fw_dev->mem; | |
| pvr_fw_trace_fini(pvr_dev); | |
| pvr_fw_object_destroy(fw_mem->runtime_cfg_obj); | |
| pvr_fw_object_destroy(fw_mem->gpu_util_fwcb_obj); | |
| pvr_fw_object_destroy(fw_mem->fault_page_obj); | |
| pvr_fw_object_unmap_and_destroy(fw_mem->sysdata_obj); | |
| pvr_fw_object_unmap_and_destroy(fw_mem->sysinit_obj); | |
| pvr_fw_object_destroy(fw_mem->mmucache_sync_obj); | |
| pvr_fw_object_unmap_and_destroy(fw_mem->hwrinfobuf_obj); | |
| pvr_fw_object_unmap_and_destroy(fw_mem->power_sync_obj); | |
| pvr_fw_object_unmap_and_destroy(fw_mem->osdata_obj); | |
| pvr_fw_object_unmap_and_destroy(fw_mem->osinit_obj); | |
| } | |
| /** | |
| * pvr_fw_process() - Process firmware image, allocate FW memory and create boot | |
| * arguments | |
| * @pvr_dev: Device pointer. | |
| * | |
| * Returns: | |
| * * 0 on success, or | |
| * * Any error returned by pvr_fw_object_create_and_map_offset(), or | |
| * * Any error returned by pvr_fw_object_create_and_map(). | |
| */ | |
| static int | |
| pvr_fw_process(struct pvr_device *pvr_dev) | |
| { | |
| struct drm_device *drm_dev = from_pvr_device(pvr_dev); | |
| struct pvr_fw_mem *fw_mem = &pvr_dev->fw_dev.mem; | |
| const u8 *fw = pvr_dev->fw_dev.firmware->data; | |
| const struct pvr_fw_layout_entry *private_data; | |
| u8 *fw_code_ptr; | |
| u8 *fw_data_ptr; | |
| u8 *fw_core_code_ptr; | |
| u8 *fw_core_data_ptr; | |
| int err; | |
| layout_get_sizes(pvr_dev); | |
| private_data = pvr_fw_find_private_data(pvr_dev); | |
| if (!private_data) | |
| return -EINVAL; | |
| /* Allocate and map memory for firmware sections. */ | |
| /* | |
| * Code allocation must be at the start of the firmware heap, otherwise | |
| * firmware processor will be unable to boot. | |
| * | |
| * This has the useful side-effect that for every other object in the | |
| * driver, a firmware address of 0 is invalid. | |
| */ | |
| fw_code_ptr = pvr_fw_object_create_and_map_offset(pvr_dev, 0, fw_mem->code_alloc_size, | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| NULL, NULL, &fw_mem->code_obj); | |
| if (IS_ERR(fw_code_ptr)) { | |
| drm_err(drm_dev, "Unable to allocate FW code memory\n"); | |
| return PTR_ERR(fw_code_ptr); | |
| } | |
| if (pvr_dev->fw_dev.defs->has_fixed_data_addr) { | |
| u32 base_addr = private_data->base_addr & pvr_dev->fw_dev.fw_heap_info.offset_mask; | |
| fw_data_ptr = | |
| pvr_fw_object_create_and_map_offset(pvr_dev, base_addr, | |
| fw_mem->data_alloc_size, | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| NULL, NULL, &fw_mem->data_obj); | |
| } else { | |
| fw_data_ptr = pvr_fw_object_create_and_map(pvr_dev, fw_mem->data_alloc_size, | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| NULL, NULL, &fw_mem->data_obj); | |
| } | |
| if (IS_ERR(fw_data_ptr)) { | |
| drm_err(drm_dev, "Unable to allocate FW data memory\n"); | |
| err = PTR_ERR(fw_data_ptr); | |
| goto err_free_fw_code_obj; | |
| } | |
| /* Core code and data sections are optional. */ | |
| if (fw_mem->core_code_alloc_size) { | |
| fw_core_code_ptr = | |
| pvr_fw_object_create_and_map(pvr_dev, fw_mem->core_code_alloc_size, | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| NULL, NULL, &fw_mem->core_code_obj); | |
| if (IS_ERR(fw_core_code_ptr)) { | |
| drm_err(drm_dev, | |
| "Unable to allocate FW core code memory\n"); | |
| err = PTR_ERR(fw_core_code_ptr); | |
| goto err_free_fw_data_obj; | |
| } | |
| } else { | |
| fw_core_code_ptr = NULL; | |
| } | |
| if (fw_mem->core_data_alloc_size) { | |
| fw_core_data_ptr = | |
| pvr_fw_object_create_and_map(pvr_dev, fw_mem->core_data_alloc_size, | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| NULL, NULL, &fw_mem->core_data_obj); | |
| if (IS_ERR(fw_core_data_ptr)) { | |
| drm_err(drm_dev, | |
| "Unable to allocate FW core data memory\n"); | |
| err = PTR_ERR(fw_core_data_ptr); | |
| goto err_free_fw_core_code_obj; | |
| } | |
| } else { | |
| fw_core_data_ptr = NULL; | |
| } | |
| fw_mem->code = kzalloc(fw_mem->code_alloc_size, GFP_KERNEL); | |
| fw_mem->data = kzalloc(fw_mem->data_alloc_size, GFP_KERNEL); | |
| if (fw_mem->core_code_alloc_size) | |
| fw_mem->core_code = kzalloc(fw_mem->core_code_alloc_size, GFP_KERNEL); | |
| if (fw_mem->core_data_alloc_size) | |
| fw_mem->core_data = kzalloc(fw_mem->core_data_alloc_size, GFP_KERNEL); | |
| if (!fw_mem->code || !fw_mem->data || | |
| (!fw_mem->core_code && fw_mem->core_code_alloc_size) || | |
| (!fw_mem->core_data && fw_mem->core_data_alloc_size)) { | |
| err = -ENOMEM; | |
| goto err_free_kdata; | |
| } | |
| err = pvr_dev->fw_dev.defs->fw_process(pvr_dev, fw, | |
| fw_mem->code, fw_mem->data, fw_mem->core_code, | |
| fw_mem->core_data, fw_mem->core_code_alloc_size); | |
| if (err) | |
| goto err_free_kdata; | |
| memcpy(fw_code_ptr, fw_mem->code, fw_mem->code_alloc_size); | |
| memcpy(fw_data_ptr, fw_mem->data, fw_mem->data_alloc_size); | |
| if (fw_mem->core_code) | |
| memcpy(fw_core_code_ptr, fw_mem->core_code, fw_mem->core_code_alloc_size); | |
| if (fw_mem->core_data) | |
| memcpy(fw_core_data_ptr, fw_mem->core_data, fw_mem->core_data_alloc_size); | |
| /* We're finished with the firmware section memory on the CPU, unmap. */ | |
| if (fw_core_data_ptr) { | |
| pvr_fw_object_vunmap(fw_mem->core_data_obj); | |
| fw_core_data_ptr = NULL; | |
| } | |
| if (fw_core_code_ptr) { | |
| pvr_fw_object_vunmap(fw_mem->core_code_obj); | |
| fw_core_code_ptr = NULL; | |
| } | |
| pvr_fw_object_vunmap(fw_mem->data_obj); | |
| fw_data_ptr = NULL; | |
| pvr_fw_object_vunmap(fw_mem->code_obj); | |
| fw_code_ptr = NULL; | |
| err = pvr_fw_create_fwif_connection_ctl(pvr_dev); | |
| if (err) | |
| goto err_free_kdata; | |
| return 0; | |
| err_free_kdata: | |
| kfree(fw_mem->core_data); | |
| kfree(fw_mem->core_code); | |
| kfree(fw_mem->data); | |
| kfree(fw_mem->code); | |
| if (fw_core_data_ptr) | |
| pvr_fw_object_vunmap(fw_mem->core_data_obj); | |
| if (fw_mem->core_data_obj) | |
| pvr_fw_object_destroy(fw_mem->core_data_obj); | |
| err_free_fw_core_code_obj: | |
| if (fw_core_code_ptr) | |
| pvr_fw_object_vunmap(fw_mem->core_code_obj); | |
| if (fw_mem->core_code_obj) | |
| pvr_fw_object_destroy(fw_mem->core_code_obj); | |
| err_free_fw_data_obj: | |
| if (fw_data_ptr) | |
| pvr_fw_object_vunmap(fw_mem->data_obj); | |
| pvr_fw_object_destroy(fw_mem->data_obj); | |
| err_free_fw_code_obj: | |
| if (fw_code_ptr) | |
| pvr_fw_object_vunmap(fw_mem->code_obj); | |
| pvr_fw_object_destroy(fw_mem->code_obj); | |
| return err; | |
| } | |
| static int | |
| pvr_copy_to_fw(struct pvr_fw_object *dest_obj, u8 *src_ptr, u32 size) | |
| { | |
| u8 *dest_ptr = pvr_fw_object_vmap(dest_obj); | |
| if (IS_ERR(dest_ptr)) | |
| return PTR_ERR(dest_ptr); | |
| memcpy(dest_ptr, src_ptr, size); | |
| pvr_fw_object_vunmap(dest_obj); | |
| return 0; | |
| } | |
| static int | |
| pvr_fw_reinit_code_data(struct pvr_device *pvr_dev) | |
| { | |
| struct pvr_fw_mem *fw_mem = &pvr_dev->fw_dev.mem; | |
| int err; | |
| err = pvr_copy_to_fw(fw_mem->code_obj, fw_mem->code, fw_mem->code_alloc_size); | |
| if (err) | |
| return err; | |
| err = pvr_copy_to_fw(fw_mem->data_obj, fw_mem->data, fw_mem->data_alloc_size); | |
| if (err) | |
| return err; | |
| if (fw_mem->core_code) { | |
| err = pvr_copy_to_fw(fw_mem->core_code_obj, fw_mem->core_code, | |
| fw_mem->core_code_alloc_size); | |
| if (err) | |
| return err; | |
| } | |
| if (fw_mem->core_data) { | |
| err = pvr_copy_to_fw(fw_mem->core_data_obj, fw_mem->core_data, | |
| fw_mem->core_data_alloc_size); | |
| if (err) | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static void | |
| pvr_fw_cleanup(struct pvr_device *pvr_dev) | |
| { | |
| struct pvr_fw_mem *fw_mem = &pvr_dev->fw_dev.mem; | |
| pvr_fw_fini_fwif_connection_ctl(pvr_dev); | |
| kfree(fw_mem->core_data); | |
| kfree(fw_mem->core_code); | |
| kfree(fw_mem->data); | |
| kfree(fw_mem->code); | |
| if (fw_mem->core_code_obj) | |
| pvr_fw_object_destroy(fw_mem->core_code_obj); | |
| if (fw_mem->core_data_obj) | |
| pvr_fw_object_destroy(fw_mem->core_data_obj); | |
| pvr_fw_object_destroy(fw_mem->code_obj); | |
| pvr_fw_object_destroy(fw_mem->data_obj); | |
| } | |
| /** | |
| * pvr_wait_for_fw_boot() - Wait for firmware to finish booting | |
| * @pvr_dev: Target PowerVR device. | |
| * | |
| * Returns: | |
| * * 0 on success, or | |
| * * -%ETIMEDOUT if firmware fails to boot within timeout. | |
| */ | |
| int | |
| pvr_wait_for_fw_boot(struct pvr_device *pvr_dev) | |
| { | |
| ktime_t deadline = ktime_add_us(ktime_get(), FW_BOOT_TIMEOUT_USEC); | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| while (ktime_to_ns(ktime_sub(deadline, ktime_get())) > 0) { | |
| if (READ_ONCE(fw_dev->fwif_sysinit->firmware_started)) | |
| return 0; | |
| } | |
| return -ETIMEDOUT; | |
| } | |
| /* | |
| * pvr_fw_heap_info_init() - Calculate size and masks for FW heap | |
| * @pvr_dev: Target PowerVR device. | |
| * @log2_size: Log2 of raw heap size. | |
| * @reserved_size: Size of reserved area of heap, in bytes. May be zero. | |
| */ | |
| void | |
| pvr_fw_heap_info_init(struct pvr_device *pvr_dev, u32 log2_size, u32 reserved_size) | |
| { | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| fw_dev->fw_heap_info.gpu_addr = PVR_ROGUE_FW_MAIN_HEAP_BASE; | |
| fw_dev->fw_heap_info.log2_size = log2_size; | |
| fw_dev->fw_heap_info.reserved_size = reserved_size; | |
| fw_dev->fw_heap_info.raw_size = 1 << fw_dev->fw_heap_info.log2_size; | |
| fw_dev->fw_heap_info.offset_mask = fw_dev->fw_heap_info.raw_size - 1; | |
| fw_dev->fw_heap_info.config_offset = fw_dev->fw_heap_info.raw_size - | |
| PVR_ROGUE_FW_CONFIG_HEAP_SIZE; | |
| fw_dev->fw_heap_info.size = fw_dev->fw_heap_info.raw_size - | |
| (PVR_ROGUE_FW_CONFIG_HEAP_SIZE + reserved_size); | |
| } | |
| /** | |
| * pvr_fw_validate_init_device_info() - Validate firmware and initialise device information | |
| * @pvr_dev: Target PowerVR device. | |
| * | |
| * This function must be called before querying device information. | |
| * | |
| * Returns: | |
| * * 0 on success, or | |
| * * -%EINVAL if firmware validation fails. | |
| */ | |
| int | |
| pvr_fw_validate_init_device_info(struct pvr_device *pvr_dev) | |
| { | |
| int err; | |
| err = pvr_fw_validate(pvr_dev); | |
| if (err) | |
| return err; | |
| return pvr_fw_get_device_info(pvr_dev); | |
| } | |
| /** | |
| * pvr_fw_init() - Initialise and boot firmware | |
| * @pvr_dev: Target PowerVR device | |
| * | |
| * On successful completion of the function the PowerVR device will be | |
| * initialised and ready to use. | |
| * | |
| * Returns: | |
| * * 0 on success, | |
| * * -%EINVAL on invalid firmware image, | |
| * * -%ENOMEM on out of memory, or | |
| * * -%ETIMEDOUT if firmware processor fails to boot or on register poll timeout. | |
| */ | |
| int | |
| pvr_fw_init(struct pvr_device *pvr_dev) | |
| { | |
| static const struct pvr_fw_defs *fw_defs[PVR_FW_PROCESSOR_TYPE_COUNT] = { | |
| [PVR_FW_PROCESSOR_TYPE_META] = &pvr_fw_defs_meta, | |
| [PVR_FW_PROCESSOR_TYPE_MIPS] = &pvr_fw_defs_mips, | |
| [PVR_FW_PROCESSOR_TYPE_RISCV] = &pvr_fw_defs_riscv, | |
| }; | |
| u32 kccb_size_log2 = ROGUE_FWIF_KCCB_NUMCMDS_LOG2_DEFAULT; | |
| u32 kccb_rtn_size = (1 << kccb_size_log2) * sizeof(*pvr_dev->kccb.rtn); | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| int err; | |
| if (fw_dev->processor_type >= PVR_FW_PROCESSOR_TYPE_COUNT) | |
| return -EINVAL; | |
| fw_dev->defs = fw_defs[fw_dev->processor_type]; | |
| err = fw_dev->defs->init(pvr_dev); | |
| if (err) | |
| return err; | |
| drm_mm_init(&fw_dev->fw_mm, ROGUE_FW_HEAP_BASE, fw_dev->fw_heap_info.raw_size); | |
| fw_dev->fw_mm_base = ROGUE_FW_HEAP_BASE; | |
| spin_lock_init(&fw_dev->fw_mm_lock); | |
| INIT_LIST_HEAD(&fw_dev->fw_objs.list); | |
| err = drmm_mutex_init(from_pvr_device(pvr_dev), &fw_dev->fw_objs.lock); | |
| if (err) | |
| goto err_mm_takedown; | |
| err = pvr_fw_process(pvr_dev); | |
| if (err) | |
| goto err_mm_takedown; | |
| /* Initialise KCCB and FWCCB. */ | |
| err = pvr_kccb_init(pvr_dev); | |
| if (err) | |
| goto err_fw_cleanup; | |
| err = pvr_fwccb_init(pvr_dev); | |
| if (err) | |
| goto err_kccb_fini; | |
| /* Allocate memory for KCCB return slots. */ | |
| pvr_dev->kccb.rtn = pvr_fw_object_create_and_map(pvr_dev, kccb_rtn_size, | |
| PVR_BO_FW_FLAGS_DEVICE_UNCACHED, | |
| NULL, NULL, &pvr_dev->kccb.rtn_obj); | |
| if (IS_ERR(pvr_dev->kccb.rtn)) { | |
| err = PTR_ERR(pvr_dev->kccb.rtn); | |
| goto err_fwccb_fini; | |
| } | |
| err = pvr_fw_create_structures(pvr_dev); | |
| if (err) | |
| goto err_kccb_rtn_release; | |
| err = pvr_fw_start(pvr_dev); | |
| if (err) | |
| goto err_destroy_structures; | |
| err = pvr_wait_for_fw_boot(pvr_dev); | |
| if (err) { | |
| drm_err(from_pvr_device(pvr_dev), "Firmware failed to boot\n"); | |
| goto err_fw_stop; | |
| } | |
| WRITE_ONCE(fw_dev->initialised, true); | |
| return 0; | |
| err_fw_stop: | |
| pvr_fw_stop(pvr_dev); | |
| err_destroy_structures: | |
| pvr_fw_destroy_structures(pvr_dev); | |
| err_kccb_rtn_release: | |
| pvr_fw_object_unmap_and_destroy(pvr_dev->kccb.rtn_obj); | |
| err_fwccb_fini: | |
| pvr_ccb_fini(&pvr_dev->fwccb); | |
| err_kccb_fini: | |
| pvr_kccb_fini(pvr_dev); | |
| err_fw_cleanup: | |
| pvr_fw_cleanup(pvr_dev); | |
| err_mm_takedown: | |
| drm_mm_takedown(&fw_dev->fw_mm); | |
| if (fw_dev->defs->fini) | |
| fw_dev->defs->fini(pvr_dev); | |
| return err; | |
| } | |
| /** | |
| * pvr_fw_fini() - Shutdown firmware processor and free associated memory | |
| * @pvr_dev: Target PowerVR device | |
| */ | |
| void | |
| pvr_fw_fini(struct pvr_device *pvr_dev) | |
| { | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| WRITE_ONCE(fw_dev->initialised, false); | |
| pvr_fw_destroy_structures(pvr_dev); | |
| pvr_fw_object_unmap_and_destroy(pvr_dev->kccb.rtn_obj); | |
| /* | |
| * Ensure FWCCB worker has finished executing before destroying FWCCB. The IRQ handler has | |
| * been unregistered at this point so no new work should be being submitted. | |
| */ | |
| pvr_ccb_fini(&pvr_dev->fwccb); | |
| pvr_kccb_fini(pvr_dev); | |
| pvr_fw_cleanup(pvr_dev); | |
| mutex_lock(&pvr_dev->fw_dev.fw_objs.lock); | |
| WARN_ON(!list_empty(&pvr_dev->fw_dev.fw_objs.list)); | |
| mutex_unlock(&pvr_dev->fw_dev.fw_objs.lock); | |
| drm_mm_takedown(&fw_dev->fw_mm); | |
| if (fw_dev->defs->fini) | |
| fw_dev->defs->fini(pvr_dev); | |
| } | |
| /** | |
| * pvr_fw_mts_schedule() - Schedule work via an MTS kick | |
| * @pvr_dev: Target PowerVR device | |
| * @val: Kick mask. Should be a combination of %ROGUE_CR_MTS_SCHEDULE_* | |
| */ | |
| void | |
| pvr_fw_mts_schedule(struct pvr_device *pvr_dev, u32 val) | |
| { | |
| /* Ensure memory is flushed before kicking MTS. */ | |
| wmb(); | |
| pvr_cr_write32(pvr_dev, ROGUE_CR_MTS_SCHEDULE, val); | |
| /* Ensure the MTS kick goes through before continuing. */ | |
| mb(); | |
| } | |
| /** | |
| * pvr_fw_structure_cleanup() - Send FW cleanup request for an object | |
| * @pvr_dev: Target PowerVR device. | |
| * @type: Type of object to cleanup. Must be one of &enum rogue_fwif_cleanup_type. | |
| * @fw_obj: Pointer to FW object containing object to cleanup. | |
| * @offset: Offset within FW object of object to cleanup. | |
| * | |
| * Returns: | |
| * * 0 on success, | |
| * * -EBUSY if object is busy, | |
| * * -ETIMEDOUT on timeout, or | |
| * * -EIO if device is lost. | |
| */ | |
| int | |
| pvr_fw_structure_cleanup(struct pvr_device *pvr_dev, u32 type, struct pvr_fw_object *fw_obj, | |
| u32 offset) | |
| { | |
| struct rogue_fwif_kccb_cmd cmd; | |
| int slot_nr; | |
| int idx; | |
| int err; | |
| u32 rtn; | |
| struct rogue_fwif_cleanup_request *cleanup_req = &cmd.cmd_data.cleanup_data; | |
| down_read(&pvr_dev->reset_sem); | |
| if (!drm_dev_enter(from_pvr_device(pvr_dev), &idx)) { | |
| err = -EIO; | |
| goto err_up_read; | |
| } | |
| cmd.cmd_type = ROGUE_FWIF_KCCB_CMD_CLEANUP; | |
| cmd.kccb_flags = 0; | |
| cleanup_req->cleanup_type = type; | |
| switch (type) { | |
| case ROGUE_FWIF_CLEANUP_FWCOMMONCONTEXT: | |
| pvr_fw_object_get_fw_addr_offset(fw_obj, offset, | |
| &cleanup_req->cleanup_data.context_fw_addr); | |
| break; | |
| case ROGUE_FWIF_CLEANUP_HWRTDATA: | |
| pvr_fw_object_get_fw_addr_offset(fw_obj, offset, | |
| &cleanup_req->cleanup_data.hwrt_data_fw_addr); | |
| break; | |
| case ROGUE_FWIF_CLEANUP_FREELIST: | |
| pvr_fw_object_get_fw_addr_offset(fw_obj, offset, | |
| &cleanup_req->cleanup_data.freelist_fw_addr); | |
| break; | |
| default: | |
| err = -EINVAL; | |
| goto err_drm_dev_exit; | |
| } | |
| err = pvr_kccb_send_cmd(pvr_dev, &cmd, &slot_nr); | |
| if (err) | |
| goto err_drm_dev_exit; | |
| err = pvr_kccb_wait_for_completion(pvr_dev, slot_nr, HZ, &rtn); | |
| if (err) | |
| goto err_drm_dev_exit; | |
| if (rtn & ROGUE_FWIF_KCCB_RTN_SLOT_CLEANUP_BUSY) | |
| err = -EBUSY; | |
| err_drm_dev_exit: | |
| drm_dev_exit(idx); | |
| err_up_read: | |
| up_read(&pvr_dev->reset_sem); | |
| return err; | |
| } | |
| /** | |
| * pvr_fw_object_fw_map() - Map a FW object in firmware address space | |
| * @pvr_dev: Device pointer. | |
| * @fw_obj: FW object to map. | |
| * @dev_addr: Desired address in device space, if a specific address is | |
| * required. 0 otherwise. | |
| * | |
| * Returns: | |
| * * 0 on success, or | |
| * * -%EINVAL if @fw_obj is already mapped but has no references, or | |
| * * Any error returned by DRM. | |
| */ | |
| static int | |
| pvr_fw_object_fw_map(struct pvr_device *pvr_dev, struct pvr_fw_object *fw_obj, u64 dev_addr) | |
| { | |
| struct pvr_gem_object *pvr_obj = fw_obj->gem; | |
| struct drm_gem_object *gem_obj = gem_from_pvr_gem(pvr_obj); | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| int err; | |
| spin_lock(&fw_dev->fw_mm_lock); | |
| if (drm_mm_node_allocated(&fw_obj->fw_mm_node)) { | |
| err = -EINVAL; | |
| goto err_unlock; | |
| } | |
| if (!dev_addr) { | |
| /* | |
| * Allocate from the main heap only (firmware heap minus | |
| * config space). | |
| */ | |
| err = drm_mm_insert_node_in_range(&fw_dev->fw_mm, &fw_obj->fw_mm_node, | |
| gem_obj->size, 0, 0, | |
| fw_dev->fw_heap_info.gpu_addr, | |
| fw_dev->fw_heap_info.gpu_addr + | |
| fw_dev->fw_heap_info.size, 0); | |
| if (err) | |
| goto err_unlock; | |
| } else { | |
| fw_obj->fw_mm_node.start = dev_addr; | |
| fw_obj->fw_mm_node.size = gem_obj->size; | |
| err = drm_mm_reserve_node(&fw_dev->fw_mm, &fw_obj->fw_mm_node); | |
| if (err) | |
| goto err_unlock; | |
| } | |
| spin_unlock(&fw_dev->fw_mm_lock); | |
| /* Map object on GPU. */ | |
| err = fw_dev->defs->vm_map(pvr_dev, fw_obj); | |
| if (err) | |
| goto err_remove_node; | |
| fw_obj->fw_addr_offset = (u32)(fw_obj->fw_mm_node.start - fw_dev->fw_mm_base); | |
| return 0; | |
| err_remove_node: | |
| spin_lock(&fw_dev->fw_mm_lock); | |
| drm_mm_remove_node(&fw_obj->fw_mm_node); | |
| err_unlock: | |
| spin_unlock(&fw_dev->fw_mm_lock); | |
| return err; | |
| } | |
| /** | |
| * pvr_fw_object_fw_unmap() - Unmap a previously mapped FW object | |
| * @fw_obj: FW object to unmap. | |
| * | |
| * Returns: | |
| * * 0 on success, or | |
| * * -%EINVAL if object is not currently mapped. | |
| */ | |
| static int | |
| pvr_fw_object_fw_unmap(struct pvr_fw_object *fw_obj) | |
| { | |
| struct pvr_gem_object *pvr_obj = fw_obj->gem; | |
| struct drm_gem_object *gem_obj = gem_from_pvr_gem(pvr_obj); | |
| struct pvr_device *pvr_dev = to_pvr_device(gem_obj->dev); | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| fw_dev->defs->vm_unmap(pvr_dev, fw_obj); | |
| spin_lock(&fw_dev->fw_mm_lock); | |
| if (!drm_mm_node_allocated(&fw_obj->fw_mm_node)) { | |
| spin_unlock(&fw_dev->fw_mm_lock); | |
| return -EINVAL; | |
| } | |
| drm_mm_remove_node(&fw_obj->fw_mm_node); | |
| spin_unlock(&fw_dev->fw_mm_lock); | |
| return 0; | |
| } | |
| static void * | |
| pvr_fw_object_create_and_map_common(struct pvr_device *pvr_dev, size_t size, | |
| u64 flags, u64 dev_addr, | |
| void (*init)(void *cpu_ptr, void *priv), | |
| void *init_priv, struct pvr_fw_object **fw_obj_out) | |
| { | |
| struct pvr_fw_object *fw_obj; | |
| void *cpu_ptr; | |
| int err; | |
| /* %DRM_PVR_BO_PM_FW_PROTECT is implicit for FW objects. */ | |
| flags |= DRM_PVR_BO_PM_FW_PROTECT; | |
| fw_obj = kzalloc_obj(*fw_obj); | |
| if (!fw_obj) | |
| return ERR_PTR(-ENOMEM); | |
| INIT_LIST_HEAD(&fw_obj->node); | |
| fw_obj->init = init; | |
| fw_obj->init_priv = init_priv; | |
| fw_obj->gem = pvr_gem_object_create(pvr_dev, size, flags); | |
| if (IS_ERR(fw_obj->gem)) { | |
| err = PTR_ERR(fw_obj->gem); | |
| fw_obj->gem = NULL; | |
| goto err_put_object; | |
| } | |
| err = pvr_fw_object_fw_map(pvr_dev, fw_obj, dev_addr); | |
| if (err) | |
| goto err_put_object; | |
| cpu_ptr = pvr_fw_object_vmap(fw_obj); | |
| if (IS_ERR(cpu_ptr)) { | |
| err = PTR_ERR(cpu_ptr); | |
| goto err_put_object; | |
| } | |
| *fw_obj_out = fw_obj; | |
| if (fw_obj->init) | |
| fw_obj->init(cpu_ptr, fw_obj->init_priv); | |
| mutex_lock(&pvr_dev->fw_dev.fw_objs.lock); | |
| list_add_tail(&fw_obj->node, &pvr_dev->fw_dev.fw_objs.list); | |
| mutex_unlock(&pvr_dev->fw_dev.fw_objs.lock); | |
| return cpu_ptr; | |
| err_put_object: | |
| pvr_fw_object_destroy(fw_obj); | |
| return ERR_PTR(err); | |
| } | |
| /** | |
| * pvr_fw_object_create() - Create a FW object and map to firmware | |
| * @pvr_dev: PowerVR device pointer. | |
| * @size: Size of object, in bytes. | |
| * @flags: Options which affect both this operation and future mapping | |
| * operations performed on the returned object. Must be a combination of | |
| * DRM_PVR_BO_* and/or PVR_BO_* flags. | |
| * @init: Initialisation callback. | |
| * @init_priv: Private pointer to pass to initialisation callback. | |
| * @fw_obj_out: Pointer to location to store created object pointer. | |
| * | |
| * %DRM_PVR_BO_DEVICE_PM_FW_PROTECT is implied for all FW objects. Consequently, | |
| * this function will fail if @flags has %DRM_PVR_BO_CPU_ALLOW_USERSPACE_ACCESS | |
| * set. | |
| * | |
| * Returns: | |
| * * 0 on success, or | |
| * * Any error returned by pvr_fw_object_create_common(). | |
| */ | |
| int | |
| pvr_fw_object_create(struct pvr_device *pvr_dev, size_t size, u64 flags, | |
| void (*init)(void *cpu_ptr, void *priv), void *init_priv, | |
| struct pvr_fw_object **fw_obj_out) | |
| { | |
| void *cpu_ptr; | |
| cpu_ptr = pvr_fw_object_create_and_map_common(pvr_dev, size, flags, 0, init, init_priv, | |
| fw_obj_out); | |
| if (IS_ERR(cpu_ptr)) | |
| return PTR_ERR(cpu_ptr); | |
| pvr_fw_object_vunmap(*fw_obj_out); | |
| return 0; | |
| } | |
| /** | |
| * pvr_fw_object_create_and_map() - Create a FW object and map to firmware and CPU | |
| * @pvr_dev: PowerVR device pointer. | |
| * @size: Size of object, in bytes. | |
| * @flags: Options which affect both this operation and future mapping | |
| * operations performed on the returned object. Must be a combination of | |
| * DRM_PVR_BO_* and/or PVR_BO_* flags. | |
| * @init: Initialisation callback. | |
| * @init_priv: Private pointer to pass to initialisation callback. | |
| * @fw_obj_out: Pointer to location to store created object pointer. | |
| * | |
| * %DRM_PVR_BO_DEVICE_PM_FW_PROTECT is implied for all FW objects. Consequently, | |
| * this function will fail if @flags has %DRM_PVR_BO_CPU_ALLOW_USERSPACE_ACCESS | |
| * set. | |
| * | |
| * Caller is responsible for calling pvr_fw_object_vunmap() to release the CPU | |
| * mapping. | |
| * | |
| * Returns: | |
| * * Pointer to CPU mapping of newly created object, or | |
| * * Any error returned by pvr_fw_object_create(), or | |
| * * Any error returned by pvr_fw_object_vmap(). | |
| */ | |
| void * | |
| pvr_fw_object_create_and_map(struct pvr_device *pvr_dev, size_t size, u64 flags, | |
| void (*init)(void *cpu_ptr, void *priv), | |
| void *init_priv, struct pvr_fw_object **fw_obj_out) | |
| { | |
| return pvr_fw_object_create_and_map_common(pvr_dev, size, flags, 0, init, init_priv, | |
| fw_obj_out); | |
| } | |
| /** | |
| * pvr_fw_object_create_and_map_offset() - Create a FW object and map to | |
| * firmware at the provided offset and to the CPU. | |
| * @pvr_dev: PowerVR device pointer. | |
| * @dev_offset: Base address of desired FW mapping, offset from start of FW heap. | |
| * @size: Size of object, in bytes. | |
| * @flags: Options which affect both this operation and future mapping | |
| * operations performed on the returned object. Must be a combination of | |
| * DRM_PVR_BO_* and/or PVR_BO_* flags. | |
| * @init: Initialisation callback. | |
| * @init_priv: Private pointer to pass to initialisation callback. | |
| * @fw_obj_out: Pointer to location to store created object pointer. | |
| * | |
| * %DRM_PVR_BO_DEVICE_PM_FW_PROTECT is implied for all FW objects. Consequently, | |
| * this function will fail if @flags has %DRM_PVR_BO_CPU_ALLOW_USERSPACE_ACCESS | |
| * set. | |
| * | |
| * Caller is responsible for calling pvr_fw_object_vunmap() to release the CPU | |
| * mapping. | |
| * | |
| * Returns: | |
| * * Pointer to CPU mapping of newly created object, or | |
| * * Any error returned by pvr_fw_object_create(), or | |
| * * Any error returned by pvr_fw_object_vmap(). | |
| */ | |
| void * | |
| pvr_fw_object_create_and_map_offset(struct pvr_device *pvr_dev, | |
| u32 dev_offset, size_t size, u64 flags, | |
| void (*init)(void *cpu_ptr, void *priv), | |
| void *init_priv, struct pvr_fw_object **fw_obj_out) | |
| { | |
| u64 dev_addr = pvr_dev->fw_dev.fw_mm_base + dev_offset; | |
| return pvr_fw_object_create_and_map_common(pvr_dev, size, flags, dev_addr, init, init_priv, | |
| fw_obj_out); | |
| } | |
| /** | |
| * pvr_fw_object_destroy() - Destroy a pvr_fw_object | |
| * @fw_obj: Pointer to object to destroy. | |
| */ | |
| void pvr_fw_object_destroy(struct pvr_fw_object *fw_obj) | |
| { | |
| struct pvr_gem_object *pvr_obj = fw_obj->gem; | |
| struct drm_gem_object *gem_obj = gem_from_pvr_gem(pvr_obj); | |
| struct pvr_device *pvr_dev = to_pvr_device(gem_obj->dev); | |
| mutex_lock(&pvr_dev->fw_dev.fw_objs.lock); | |
| list_del(&fw_obj->node); | |
| mutex_unlock(&pvr_dev->fw_dev.fw_objs.lock); | |
| if (drm_mm_node_allocated(&fw_obj->fw_mm_node)) { | |
| /* If we can't unmap, leak the memory. */ | |
| if (WARN_ON(pvr_fw_object_fw_unmap(fw_obj))) | |
| return; | |
| } | |
| if (fw_obj->gem) | |
| pvr_gem_object_put(fw_obj->gem); | |
| kfree(fw_obj); | |
| } | |
| /** | |
| * pvr_fw_object_get_fw_addr_offset() - Return address of object in firmware address space, with | |
| * given offset. | |
| * @fw_obj: Pointer to object. | |
| * @offset: Desired offset from start of object. | |
| * @fw_addr_out: Location to store address to. | |
| */ | |
| void pvr_fw_object_get_fw_addr_offset(struct pvr_fw_object *fw_obj, u32 offset, u32 *fw_addr_out) | |
| { | |
| struct pvr_gem_object *pvr_obj = fw_obj->gem; | |
| struct pvr_device *pvr_dev = to_pvr_device(gem_from_pvr_gem(pvr_obj)->dev); | |
| *fw_addr_out = pvr_dev->fw_dev.defs->get_fw_addr_with_offset(fw_obj, offset); | |
| } | |
| u64 | |
| pvr_fw_obj_get_gpu_addr(struct pvr_fw_object *fw_obj) | |
| { | |
| struct pvr_device *pvr_dev = to_pvr_device(gem_from_pvr_gem(fw_obj->gem)->dev); | |
| struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; | |
| return fw_dev->fw_heap_info.gpu_addr + fw_obj->fw_addr_offset; | |
| } | |
| /* | |
| * pvr_fw_hard_reset() - Re-initialise the FW code and data segments, and reset all global FW | |
| * structures | |
| * @pvr_dev: Device pointer | |
| * | |
| * If this function returns an error then the caller must regard the device as lost. | |
| * | |
| * Returns: | |
| * * 0 on success, or | |
| * * Any error returned by pvr_fw_init_dev_structures() or pvr_fw_reset_all(). | |
| */ | |
| int | |
| pvr_fw_hard_reset(struct pvr_device *pvr_dev) | |
| { | |
| struct list_head *pos; | |
| int err; | |
| /* Reset all FW objects */ | |
| mutex_lock(&pvr_dev->fw_dev.fw_objs.lock); | |
| list_for_each(pos, &pvr_dev->fw_dev.fw_objs.list) { | |
| struct pvr_fw_object *fw_obj = container_of(pos, struct pvr_fw_object, node); | |
| void *cpu_ptr = pvr_fw_object_vmap(fw_obj); | |
| WARN_ON(IS_ERR(cpu_ptr)); | |
| if (!(fw_obj->gem->flags & PVR_BO_FW_NO_CLEAR_ON_RESET)) { | |
| memset(cpu_ptr, 0, pvr_gem_object_size(fw_obj->gem)); | |
| if (fw_obj->init) | |
| fw_obj->init(cpu_ptr, fw_obj->init_priv); | |
| } | |
| pvr_fw_object_vunmap(fw_obj); | |
| } | |
| mutex_unlock(&pvr_dev->fw_dev.fw_objs.lock); | |
| err = pvr_fw_reinit_code_data(pvr_dev); | |
| if (err) | |
| return err; | |
| return 0; | |
| } |