| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * GXP firmware data manager. |
| * |
| * Copyright (C) 2021 Google LLC |
| */ |
| |
| #include <linux/slab.h> |
| |
| #include "gxp-config.h" |
| #include "gxp-debug-dump.h" |
| #include "gxp-firmware-data.h" |
| #include "gxp-host-device-structs.h" |
| #include "gxp-internal.h" |
| #include "gxp-vd.h" |
| #include "gxp.h" |
| |
| /* A byte pattern to pre-populate the FW region with */ |
| #define FW_DATA_DEBUG_PATTERN 0x66 |
| |
| /* Default application parameters */ |
| #define DEFAULT_APP_ID 1 |
| |
| /* |
| * Holds information about system-wide HW and memory resources given to the FWs |
| * of GXP devices. |
| */ |
| struct gxp_fw_data_manager { |
| /* Cached core telemetry descriptors. */ |
| struct gxp_core_telemetry_descriptor core_telemetry_desc; |
| /* |
| * A host-view of the System configuration descriptor. This same desc |
| * is provided to all VDs and all cores. This is the R/O section. |
| */ |
| struct gxp_system_descriptor_ro *sys_desc_ro; |
| /* |
| * A host-view of the System configuration descriptor. This same desc |
| * is provided to all VDs and all cores. This is the R/W section. |
| */ |
| struct gxp_system_descriptor_rw *sys_desc_rw; |
| }; |
| |
| /* |
| * Here assumes sys_cfg contains gxp_system_descriptor_ro in the first page and |
| * gxp_system_descriptor_rw in the second page. |
| */ |
| static void set_system_cfg_region(struct gxp_dev *gxp, void *sys_cfg) |
| { |
| struct gxp_system_descriptor_ro *des_ro = sys_cfg; |
| struct gxp_system_descriptor_rw *des_rw = sys_cfg + SZ_4K; |
| struct gxp_core_telemetry_descriptor *descriptor = |
| &gxp->data_mgr->core_telemetry_desc; |
| struct telemetry_descriptor_ro *ro; |
| struct telemetry_descriptor_rw *rw; |
| struct core_telemetry_descriptor *des; |
| int i; |
| |
| if (gxp->debug_dump_mgr) |
| des_ro->debug_dump_dev_addr = gxp->debug_dump_mgr->buf.dsp_addr; |
| else |
| des_ro->debug_dump_dev_addr = 0; |
| |
| for (i = 0; i < GXP_NUM_CORES; i++) { |
| ro = &des_ro->telemetry_desc.per_core_loggers[i]; |
| rw = &des_rw->telemetry_desc.per_core_loggers[i]; |
| des = &descriptor->per_core_loggers[i]; |
| ro->host_status = des->host_status; |
| ro->buffer_addr = des->buffer_addr; |
| ro->buffer_size = des->buffer_size; |
| rw->device_status = des->device_status; |
| rw->data_available = des->watermark_level; |
| } |
| |
| /* Update the global descriptors. */ |
| gxp->data_mgr->sys_desc_ro = des_ro; |
| gxp->data_mgr->sys_desc_rw = des_rw; |
| } |
| |
| static void _gxp_fw_data_populate_vd_cfg(struct gxp_dev *gxp, |
| struct gxp_virtual_device *vd) |
| { |
| struct gxp_host_control_region *core_cfg; |
| struct gxp_job_descriptor job; |
| struct gxp_vd_descriptor *vd_desc; |
| int i; |
| |
| if (!gxp_is_direct_mode(gxp)) |
| return; |
| if (!vd->vd_cfg.vaddr || !vd->core_cfg.vaddr) { |
| dev_warn( |
| gxp->dev, |
| "Missing VD and core CFG in image config, firmware is not bootable\n"); |
| return; |
| } |
| /* Set up VD config region. */ |
| vd_desc = vd->vd_cfg.vaddr; |
| vd_desc->application_id = DEFAULT_APP_ID; |
| vd_desc->vd_is_initialized = 0; |
| /* Set up core config region. */ |
| job.workers_count = vd->num_cores; |
| for (i = 0; i < ARRAY_SIZE(job.worker_to_fw); i++) { |
| /* |
| * Kernel-initiated workloads always act like the entire VD is |
| * one giant N-core job where N is the number of cores allocated |
| * to that VD. |
| * The MCU, on the other hand, can have multiple jobs dispatched |
| * to the same VD at the same time. |
| */ |
| if (i < job.workers_count) |
| job.worker_to_fw[i] = i; |
| else |
| job.worker_to_fw[i] = -1; |
| } |
| /* Give each VD a unique HW resources slot. */ |
| job.hardware_resources_slot = gxp_vd_hw_slot_id(vd); |
| /* Assign the same job descriptor to all cores in this VD */ |
| for (i = 0; i < GXP_NUM_CORES; i++) { |
| core_cfg = vd->core_cfg.vaddr + |
| vd->core_cfg.size / GXP_NUM_CORES * i; |
| core_cfg->job_descriptor = job; |
| } |
| } |
| |
| int gxp_fw_data_init(struct gxp_dev *gxp) |
| { |
| struct gxp_fw_data_manager *mgr; |
| void *virt; |
| |
| mgr = devm_kzalloc(gxp->dev, sizeof(*mgr), GFP_KERNEL); |
| if (!mgr) |
| return -ENOMEM; |
| |
| if (gxp_is_direct_mode(gxp)) { |
| virt = memremap(gxp->fwdatabuf.paddr, gxp->fwdatabuf.size, MEMREMAP_WC); |
| if (IS_ERR_OR_NULL(virt)) { |
| dev_err(gxp->dev, "Failed to map fw data region\n"); |
| return -ENODEV; |
| } |
| gxp->fwdatabuf.vaddr = virt; |
| |
| /* Populate the region with a pre-defined pattern. */ |
| memset(virt, FW_DATA_DEBUG_PATTERN, gxp->fwdatabuf.size); |
| } |
| gxp->data_mgr = mgr; |
| |
| return 0; |
| } |
| |
| void gxp_fw_data_destroy(struct gxp_dev *gxp) |
| { |
| struct gxp_fw_data_manager *mgr = gxp->data_mgr; |
| |
| if (gxp->fwdatabuf.vaddr) |
| memunmap(gxp->fwdatabuf.vaddr); |
| |
| devm_kfree(gxp->dev, mgr); |
| gxp->data_mgr = NULL; |
| } |
| |
| void gxp_fw_data_populate_vd_cfg(struct gxp_dev *gxp, struct gxp_virtual_device *vd) |
| { |
| _gxp_fw_data_populate_vd_cfg(gxp, vd); |
| } |
| |
| int gxp_fw_data_set_core_telemetry_descriptors(struct gxp_dev *gxp, u32 host_status, |
| struct gxp_coherent_buf *buffers, |
| u32 per_buffer_size) |
| { |
| struct gxp_core_telemetry_descriptor *descriptor = &gxp->data_mgr->core_telemetry_desc; |
| struct core_telemetry_descriptor *core_descriptors = descriptor->per_core_loggers; |
| uint core; |
| bool enable; |
| |
| enable = (host_status & GXP_CORE_TELEMETRY_HOST_STATUS_ENABLED); |
| |
| if (enable) { |
| /* Validate that the provided IOVAs are addressable (i.e. 32-bit) */ |
| for (core = 0; core < GXP_NUM_CORES; core++) { |
| if (buffers && buffers[core].dsp_addr > U32_MAX && |
| buffers[core].size == per_buffer_size) |
| return -EINVAL; |
| } |
| |
| for (core = 0; core < GXP_NUM_CORES; core++) { |
| core_descriptors[core].host_status = host_status; |
| core_descriptors[core].buffer_addr = |
| (u32)buffers[core].dsp_addr; |
| core_descriptors[core].buffer_size = per_buffer_size; |
| } |
| } else { |
| for (core = 0; core < GXP_NUM_CORES; core++) { |
| core_descriptors[core].host_status = host_status; |
| core_descriptors[core].buffer_addr = 0; |
| core_descriptors[core].buffer_size = 0; |
| } |
| } |
| |
| return 0; |
| } |
| |
| u32 gxp_fw_data_get_core_telemetry_device_status(struct gxp_dev *gxp, uint core) |
| { |
| struct gxp_system_descriptor_rw *des_rw = gxp->data_mgr->sys_desc_rw; |
| |
| if (core >= GXP_NUM_CORES) |
| return 0; |
| |
| return des_rw->telemetry_desc.per_core_loggers[core].device_status; |
| } |
| |
| struct gxp_mapped_resource gxp_fw_data_resource(struct gxp_dev *gxp) |
| { |
| /* |
| * For direct mode, the config regions are programmed by host (us); for |
| * MCU mode, the config regions are programmed by MCU. |
| */ |
| if (gxp_is_direct_mode(gxp)) { |
| return gxp->fwdatabuf; |
| } else { |
| return gxp->shared_buf; |
| } |
| } |
| |
| void *gxp_fw_data_system_cfg(struct gxp_dev *gxp) |
| { |
| struct gxp_mapped_resource res = gxp_fw_data_resource(gxp); |
| |
| /* Use the end of the shared region for system cfg. */ |
| return res.vaddr + res.size - GXP_FW_DATA_SYSCFG_SIZE; |
| } |
| |
| void gxp_fw_data_populate_system_config(struct gxp_dev *gxp) |
| { |
| set_system_cfg_region(gxp, gxp_fw_data_system_cfg(gxp)); |
| } |