| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * GXP MicroController Unit firmware management. |
| * |
| * Copyright (C) 2022 Google LLC |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/firmware.h> |
| #include <linux/io.h> |
| #include <linux/lockdep.h> |
| #include <linux/mutex.h> |
| #include <linux/resource.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include <linux/workqueue.h> |
| |
| #include <gcip/gcip-common-image-header.h> |
| #include <gcip/gcip-fault-injection.h> |
| #include <gcip/gcip-image-config.h> |
| #include <gcip/gcip-iommu.h> |
| #include <gcip/gcip-pm.h> |
| #include <gcip/gcip-thermal.h> |
| |
| #include "gxp-bpm.h" |
| #include "gxp-config.h" |
| #include "gxp-core-telemetry.h" |
| #include "gxp-debug-dump.h" |
| #include "gxp-doorbell.h" |
| #include "gxp-firmware-loader.h" |
| #include "gxp-gsa.h" |
| #include "gxp-internal.h" |
| #include "gxp-kci.h" |
| #include "gxp-lpm.h" |
| #include "gxp-mailbox-driver.h" |
| #include "gxp-mcu-firmware.h" |
| #include "gxp-mcu-platform.h" |
| #include "gxp-mcu.h" |
| #include "gxp-pm.h" |
| #include "mobile-soc.h" |
| |
| #if IS_GXP_TEST |
| #define TEST_FLUSH_KCI_WORKERS(kci) \ |
| do { \ |
| kthread_flush_worker(&(kci).mbx->response_worker); \ |
| flush_work(&(kci).mbx->mbx_impl.gcip_kci->work); \ |
| flush_work(&(kci).mbx->mbx_impl.gcip_kci->rkci.work); \ |
| } while (0) |
| #else |
| #define TEST_FLUSH_KCI_WORKERS(...) |
| #endif |
| |
| /* Value of Magic field in the common header "DSPF' as a 32-bit LE int */ |
| #define GXP_FW_MAGIC 0x46505344 |
| |
| /* The number of times trying to rescue MCU. */ |
| #define MCU_RESCUE_TRY 3 |
| |
| /* Time(us) to boot MCU in recovery mode. */ |
| #define GXP_MCU_RECOVERY_BOOT_DELAY 100 |
| /* |
| * Programs instruction remap CSRs. |
| */ |
| static void program_iremap_csr(struct gxp_dev *gxp, |
| struct gxp_mapped_resource *buf) |
| { |
| dev_info(gxp->dev, "Program instruction remap CSRs"); |
| gxp_soc_set_iremap_context(gxp); |
| gxp_write_32(gxp, GXP_REG_CFGVECTABLE0, buf->daddr); |
| |
| gxp_write_32(gxp, GXP_REG_IREMAP_LOW, buf->daddr); |
| gxp_write_32(gxp, GXP_REG_IREMAP_HIGH, buf->daddr + buf->size); |
| gxp_write_32(gxp, GXP_REG_IREMAP_TARGET, buf->daddr); |
| gxp_write_32(gxp, GXP_REG_IREMAP_ENABLE, 1); |
| } |
| |
| /* |
| * Check whether the firmware file is signed or not. |
| */ |
| static bool is_signed_firmware(const struct firmware *fw, |
| const struct gcip_common_image_header *hdr) |
| { |
| if (fw->size < GCIP_FW_HEADER_SIZE) |
| return false; |
| |
| if (hdr->common.magic != GXP_FW_MAGIC) |
| return false; |
| |
| return true; |
| } |
| |
| static void gxp_mcu_set_boot_mode(struct gxp_mcu_firmware *mcu_fw, uint32_t mode) |
| { |
| writel(mode, GXP_MCU_BOOT_MODE_OFFSET + mcu_fw->image_buf.vaddr); |
| } |
| |
| static int gxp_mcu_firmware_handshake(struct gxp_mcu_firmware *mcu_fw) |
| { |
| struct gxp_dev *gxp = mcu_fw->gxp; |
| struct gxp_mcu *mcu = container_of(mcu_fw, struct gxp_mcu, fw); |
| enum gcip_fw_flavor fw_flavor; |
| int ret; |
| |
| dev_dbg(gxp->dev, "Detecting MCU firmware info..."); |
| mcu_fw->fw_info.fw_build_time = 0; |
| mcu_fw->fw_info.fw_flavor = GCIP_FW_FLAVOR_UNKNOWN; |
| mcu_fw->fw_info.fw_changelist = 0; |
| ret = gxp_mailbox_wait_for_device_mailbox_init(mcu->kci.mbx); |
| if (!ret) { |
| fw_flavor = gxp_kci_fw_info(&mcu->kci, &mcu_fw->fw_info); |
| } else { |
| fw_flavor = ret; |
| dev_err(gxp->dev, "Device mailbox init failed: %d", ret); |
| } |
| dev_info(gxp->dev, "MCU boot stage: %u\n", gxp_read_32(gxp, GXP_REG_MCU_BOOT_STAGE)); |
| if (fw_flavor < 0) { |
| dev_err(gxp->dev, "MCU firmware handshake failed: %d", |
| fw_flavor); |
| mcu_fw->fw_info.fw_flavor = GCIP_FW_FLAVOR_UNKNOWN; |
| mcu_fw->fw_info.fw_changelist = 0; |
| mcu_fw->fw_info.fw_build_time = 0; |
| return fw_flavor; |
| } |
| |
| dev_info(gxp->dev, "loaded %s MCU firmware (%u)", |
| gcip_fw_flavor_str(fw_flavor), mcu_fw->fw_info.fw_changelist); |
| |
| gxp_bpm_stop(gxp, GXP_MCU_CORE_ID); |
| dev_notice(gxp->dev, "MCU Instruction read transactions: 0x%x\n", |
| gxp_bpm_read_counter(gxp, GXP_MCU_CORE_ID, INST_BPM_OFFSET)); |
| |
| ret = gxp_mcu_telemetry_kci(mcu); |
| if (ret) |
| dev_warn(gxp->dev, "telemetry KCI error: %d", ret); |
| |
| ret = gcip_thermal_restore_on_powering(gxp->thermal); |
| if (ret) |
| dev_warn(gxp->dev, "thermal restore error: %d", ret); |
| |
| ret = gxp_kci_set_device_properties(&mcu->kci, &gxp->device_prop); |
| if (ret) |
| dev_warn(gxp->dev, "Failed to pass device_prop to fw: %d\n", ret); |
| |
| return 0; |
| } |
| |
| /* |
| * Waits for the MCU LPM transition to the PG state. |
| * |
| * Must be called with holding @mcu_fw->lock. |
| * |
| * @force: force MCU to boot in recovery mode and execute WFI so that it can |
| * go in PG state. |
| * Returns true if MCU successfully transited to PG state, otherwise false. |
| */ |
| static bool gxp_pg_by_recovery_boot(struct gxp_dev *gxp, bool force) |
| { |
| struct gxp_mcu_firmware *mcu_fw = gxp_mcu_firmware_of(gxp); |
| int try = MCU_RESCUE_TRY, ret; |
| |
| lockdep_assert_held(&mcu_fw->lock); |
| |
| do { |
| if (force) { |
| gxp_mcu_set_boot_mode(mcu_fw, GXP_MCU_BOOT_MODE_RECOVERY); |
| ret = gxp_mcu_reset(gxp, true); |
| udelay(GXP_MCU_RECOVERY_BOOT_DELAY); |
| if (ret) { |
| dev_err(gxp->dev, "Failed to reset MCU (ret=%d)", ret); |
| continue; |
| } |
| } |
| |
| if (gxp_lpm_wait_state_eq(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID), LPM_PG_STATE)) |
| return true; |
| |
| dev_warn(gxp->dev, "MCU PSM transition to PS3 fails, current state: %u, try: %d", |
| gxp_lpm_get_state(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID)), try); |
| /* |
| * If PG transition fails, MCU will not fall into WFI after the reset. |
| * Therefore, we must boot into recovery to force WFI transition. |
| */ |
| force = true; |
| } while (--try > 0); |
| |
| return false; |
| } |
| |
| /* |
| * Waits for the MCU LPM transition to the PG state. |
| * |
| * Must be called with holding @mcu_fw->lock. |
| * |
| * @ring_doorbell: If the situation is that the MCU cannot execute the transition by itself such |
| * as HW watchdog timeout, it must be passed as true to trigger the doorbell and |
| * let the MCU do that forcefully. |
| * |
| * Returns true if MCU successfully transited to PG state, otherwise false. |
| */ |
| static bool gxp_pg_by_doorbell(struct gxp_dev *gxp, bool ring_doorbell) |
| { |
| struct gxp_mcu *mcu = &to_mcu_dev(gxp)->mcu; |
| struct gxp_mcu_firmware *mcu_fw = gxp_mcu_firmware_of(gxp); |
| int try = MCU_RESCUE_TRY, ret; |
| |
| lockdep_assert_held(&mcu_fw->lock); |
| |
| do { |
| if (ring_doorbell) { |
| gxp_mailbox_set_control(mcu->kci.mbx, GXP_MBOX_CONTROL_MAGIC_POWER_DOWN); |
| gxp_doorbell_enable_for_core(gxp, CORE_WAKEUP_DOORBELL(GXP_MCU_CORE_ID), |
| GXP_MCU_CORE_ID); |
| gxp_doorbell_set(gxp, CORE_WAKEUP_DOORBELL(GXP_MCU_CORE_ID)); |
| } |
| |
| if (gxp_lpm_wait_state_eq(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID), LPM_PG_STATE)) |
| return true; |
| |
| dev_warn(gxp->dev, "MCU PSM transition to PS3 fails, current state: %u, try: %d", |
| gxp_lpm_get_state(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID)), try); |
| |
| /* |
| * If PG transition fails, MCU will not fall into WFI after the reset below. |
| * Therefore, we must ring doorbell to let it fall into WFI from the next try. |
| */ |
| ring_doorbell = true; |
| |
| ret = gxp_mcu_reset(gxp, true); |
| if (ret) { |
| dev_err(gxp->dev, "Failed to reset MCU after PG transition fails (ret=%d)", |
| ret); |
| continue; |
| } |
| |
| /* |
| * We should give enough time to MCU to register doorbell handler. We hope MCU |
| * successfully registers the handler after the reset even if the handshake fails. |
| */ |
| ret = gxp_mcu_firmware_handshake(mcu_fw); |
| if (ret) |
| dev_err(gxp->dev, |
| "Failed to handshake with MCU after PG transition fails (ret=%d)", |
| ret); |
| } while (--try > 0); |
| |
| return false; |
| } |
| |
| static bool wait_for_pg_state_locked(struct gxp_dev *gxp, bool force) |
| { |
| bool ret; |
| |
| /* TODO(b/317756665): Remove when recovery mode boot is supported in firmware. */ |
| ret = gxp_pg_by_doorbell(gxp, force); |
| if (ret) |
| return ret; |
| |
| /* For firmwares that supports recovery mode. */ |
| return gxp_pg_by_recovery_boot(gxp, force); |
| } |
| |
| int gxp_mcu_firmware_load(struct gxp_dev *gxp, char *fw_name, |
| const struct firmware **fw) |
| { |
| int ret; |
| struct gxp_mcu_firmware *mcu_fw = gxp_mcu_firmware_of(gxp); |
| struct device *dev = gxp->dev; |
| struct gcip_image_config *imgcfg; |
| struct gcip_common_image_header *hdr; |
| size_t size; |
| |
| mutex_lock(&mcu_fw->lock); |
| if (mcu_fw->status == GCIP_FW_LOADING || |
| mcu_fw->status == GCIP_FW_VALID) { |
| dev_info(gxp->dev, "MCU firmware is loaded, skip loading"); |
| goto out; |
| } |
| |
| mcu_fw->status = GCIP_FW_LOADING; |
| if (fw_name == NULL) |
| fw_name = GXP_DEFAULT_MCU_FIRMWARE; |
| dev_info(gxp->dev, "MCU firmware %s loading", fw_name); |
| |
| ret = request_firmware(fw, fw_name, dev); |
| if (ret) { |
| dev_err(dev, "request firmware '%s' failed: %d", fw_name, ret); |
| goto err_out; |
| } |
| |
| hdr = (struct gcip_common_image_header *)(*fw)->data; |
| |
| if (!is_signed_firmware(*fw, hdr)) { |
| dev_err(dev, "Invalid firmware format %s", fw_name); |
| ret = -EINVAL; |
| goto err_release_firmware; |
| } |
| |
| size = (*fw)->size - GCIP_FW_HEADER_SIZE; |
| |
| if (size > mcu_fw->image_buf.size) { |
| dev_err(dev, "firmware %s size %#zx exceeds buffer size %#llx", |
| fw_name, size, mcu_fw->image_buf.size); |
| ret = -ENOSPC; |
| goto err_release_firmware; |
| } |
| |
| imgcfg = get_image_config_from_hdr(hdr); |
| if (!imgcfg) { |
| dev_err(dev, "Unsupported image header generation"); |
| ret = -EINVAL; |
| goto err_release_firmware; |
| } |
| /* Initialize the secure telemetry buffers if available. */ |
| if (imgcfg->secure_telemetry_region_start) { |
| ret = gxp_secure_core_telemetry_init( |
| gxp, imgcfg->secure_telemetry_region_start); |
| if (ret) |
| dev_warn(dev, |
| "Secure telemetry initialization failed."); |
| } |
| ret = gcip_image_config_parse(&mcu_fw->cfg_parser, imgcfg); |
| if (ret) { |
| dev_err(dev, "image config parsing failed: %d", ret); |
| goto err_release_firmware; |
| } |
| if (!gcip_image_config_is_ns(imgcfg) && !gxp->gsa_dev) { |
| dev_err(dev, |
| "Can't run MCU in secure mode without the GSA device"); |
| ret = -EINVAL; |
| goto err_clear_config; |
| } |
| mcu_fw->is_secure = !gcip_image_config_is_ns(imgcfg); |
| |
| memcpy(mcu_fw->image_buf.vaddr, (*fw)->data + GCIP_FW_HEADER_SIZE, |
| size); |
| out: |
| mutex_unlock(&mcu_fw->lock); |
| return 0; |
| |
| err_clear_config: |
| gcip_image_config_clear(&mcu_fw->cfg_parser); |
| err_release_firmware: |
| release_firmware(*fw); |
| err_out: |
| mcu_fw->status = GCIP_FW_INVALID; |
| mutex_unlock(&mcu_fw->lock); |
| return ret; |
| } |
| |
| void gxp_mcu_firmware_unload(struct gxp_dev *gxp, const struct firmware *fw) |
| { |
| struct gxp_mcu_firmware *mcu_fw = gxp_mcu_firmware_of(gxp); |
| |
| mutex_lock(&mcu_fw->lock); |
| if (mcu_fw->status == GCIP_FW_INVALID) { |
| dev_err(mcu_fw->gxp->dev, "Failed to unload MCU firmware"); |
| mutex_unlock(&mcu_fw->lock); |
| return; |
| } |
| gcip_image_config_clear(&mcu_fw->cfg_parser); |
| mcu_fw->status = GCIP_FW_INVALID; |
| mutex_unlock(&mcu_fw->lock); |
| } |
| |
| /* |
| * Boots up the MCU and program instructions. |
| * It sends `START` command to GSA in the secure mode. |
| */ |
| static int gxp_mcu_firmware_start(struct gxp_mcu_firmware *mcu_fw) |
| { |
| struct gxp_dev *gxp = mcu_fw->gxp; |
| int ret, state; |
| |
| gxp_bpm_configure(gxp, GXP_MCU_CORE_ID, INST_BPM_OFFSET, |
| BPM_EVENT_READ_XFER); |
| |
| ret = gxp_lpm_up(gxp, GXP_MCU_CORE_ID); |
| if (ret) |
| return ret; |
| |
| gxp_write_32(gxp, GXP_REG_MCU_BOOT_STAGE, 0); |
| gxp_mcu_set_boot_mode(mcu_fw, GXP_MCU_BOOT_MODE_NORMAL); |
| if (mcu_fw->is_secure) { |
| state = gsa_send_dsp_cmd(gxp->gsa_dev, GSA_DSP_START); |
| if (state != GSA_DSP_STATE_RUNNING) { |
| gxp_lpm_down(gxp, GXP_MCU_CORE_ID); |
| return -EIO; |
| } |
| } else { |
| program_iremap_csr(gxp, &mcu_fw->image_buf); |
| /* Raise wakeup doorbell */ |
| dev_dbg(gxp->dev, "Raising doorbell %d interrupt\n", |
| CORE_WAKEUP_DOORBELL(GXP_MCU_CORE_ID)); |
| gxp_doorbell_enable_for_core( |
| gxp, CORE_WAKEUP_DOORBELL(GXP_MCU_CORE_ID), |
| GXP_MCU_CORE_ID); |
| gxp_doorbell_set(gxp, CORE_WAKEUP_DOORBELL(GXP_MCU_CORE_ID)); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Shutdowns the MCU. |
| * It sends `SHUTDOWN` command to GSA in the secure mode. |
| * |
| * Note that this function doesn't call `gxp_lpm_down`. |
| * |
| * 1. When MCU normally powered off after SHUTDOWN KCI. |
| * : It is already in PG state and we don't need to call that. |
| * |
| * 2. When we are going to shutdown MCU which is in abnormal state even after trying to rescue it. |
| * : We can't decide the state of MCU PSM or the AUR_BLOCK and accessing LPM CSRs might not be |
| * a good idea. |
| */ |
| static void gxp_mcu_firmware_shutdown(struct gxp_mcu_firmware *mcu_fw) |
| { |
| struct gxp_dev *gxp = mcu_fw->gxp; |
| |
| if (mcu_fw->is_secure) |
| gsa_send_dsp_cmd(gxp->gsa_dev, GSA_DSP_SHUTDOWN); |
| } |
| |
| /* |
| * Rescues the MCU which is not working properly. After the rescue, the MCU must be in PS0 state |
| * with an expectation of working normally. Basically, what this function doing is resetting MCU, |
| * block power cycling and handshaking with MCU. |
| * |
| * Must be called with holding @mcu_fw->lock and @pm->lock. |
| * |
| * Returns 0 if it successfully rescued and hanshaked with the MCU. |
| */ |
| static int gxp_mcu_firmware_rescue(struct gxp_dev *gxp) |
| { |
| struct gxp_mcu_firmware *mcu_fw = gxp_mcu_firmware_of(gxp); |
| int try = MCU_RESCUE_TRY, ret = 0; |
| |
| gcip_pm_lockdep_assert_held(gxp->power_mgr->pm); |
| lockdep_assert_held(&mcu_fw->lock); |
| |
| do { |
| dev_warn(gxp->dev, "Try to rescue MCU (try=%d)", try); |
| |
| if (!wait_for_pg_state_locked(gxp, true)) { |
| dev_err(gxp->dev, |
| "Cannot proceed MCU rescue because it is not in PG state"); |
| ret = -EAGAIN; |
| continue; |
| } |
| |
| /* Try power cycle after resetting the MCU and still holding the reset bits. */ |
| ret = gxp_mcu_reset(gxp, false); |
| if (ret) { |
| dev_err(gxp->dev, "Failed to reset MCU (ret=%d)", ret); |
| continue; |
| } |
| |
| gxp_mcu_firmware_shutdown(mcu_fw); |
| |
| ret = gxp_pm_blk_reboot(gxp, 5000); |
| if (ret) { |
| dev_err(gxp->dev, "Failed to power cycle AUR block, (ret=%d)", ret); |
| continue; |
| } |
| |
| #if GXP_LPM_IN_AON |
| /* |
| * MCU reset mechanisms are chip specific. For some chips, reset assert bits may |
| * belong to LPM csr space which doesn't get reset on block power cycle and still be |
| * held. Release reset bits to arm MCU for a run. |
| */ |
| ret = gxp_mcu_reset(gxp, true); |
| if (ret) { |
| dev_err(gxp->dev, "Failed to reset MCU after blk reboot (ret=%d)", ret); |
| continue; |
| } |
| #endif |
| /* Try booting MCU up again and handshaking with it. */ |
| ret = gxp_mcu_firmware_start(mcu_fw); |
| if (ret) { |
| dev_err(gxp->dev, "Failed to boot MCU up, (ret=%d)", ret); |
| continue; |
| } |
| |
| ret = gxp_mcu_firmware_handshake(mcu_fw); |
| if (ret) { |
| dev_err(gxp->dev, "Failed to handshake with MCU even after rescue (ret=%d)", |
| ret); |
| continue; |
| } |
| dev_info(gxp->dev, "Succeeded in rescuing MCU"); |
| } while (ret && --try > 0); |
| |
| return ret; |
| } |
| |
| static void gxp_mcu_firmware_stop_locked(struct gxp_mcu_firmware *mcu_fw) |
| { |
| struct gxp_dev *gxp = mcu_fw->gxp; |
| struct gxp_mcu *mcu = container_of(mcu_fw, struct gxp_mcu, fw); |
| int ret; |
| |
| lockdep_assert_held(&mcu_fw->lock); |
| |
| gxp_lpm_enable_state(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID), LPM_PG_STATE); |
| |
| /* Clear doorbell to refuse non-expected interrupts */ |
| gxp_doorbell_clear(gxp, CORE_WAKEUP_DOORBELL(GXP_MCU_CORE_ID)); |
| |
| ret = gxp_kci_shutdown(&mcu->kci); |
| if (ret) |
| dev_warn(gxp->dev, "KCI shutdown failed: %d", ret); |
| |
| /* TODO(b/296980539): revert this change after the bug is fixed. */ |
| #if IS_ENABLED(CONFIG_GXP_GEM5) |
| gxp_lpm_set_state(gxp, LPM_PSM_MCU, LPM_PG_STATE, true); |
| #else |
| /* |
| * Waits for MCU transiting to PG state. If KCI shutdown was failed above (ret != 0), it |
| * will force to PG state. |
| */ |
| if (!wait_for_pg_state_locked(gxp, /*force=*/ret)) |
| dev_warn(gxp->dev, "Failed to transit MCU to PG state after KCI shutdown"); |
| #endif /* IS_ENABLED(CONFIG_GXP_GEM5) */ |
| |
| /* To test the case of the MCU FW sending FW_CRASH RKCI in the middle. */ |
| TEST_FLUSH_KCI_WORKERS(mcu->kci); |
| |
| gxp_kci_cancel_work_queues(&mcu->kci); |
| /* |
| * Clears up all remaining UCI/KCI commands. Otherwise, MCU may drain them improperly after |
| * it reboots. |
| */ |
| gxp_mcu_reset_mailbox(mcu); |
| |
| gxp_mcu_firmware_shutdown(mcu_fw); |
| } |
| |
| /* |
| * Caller must hold firmware lock. |
| */ |
| static int gxp_mcu_firmware_run_locked(struct gxp_mcu_firmware *mcu_fw) |
| { |
| struct gxp_dev *gxp = mcu_fw->gxp; |
| struct gxp_mcu *mcu = container_of(mcu_fw, struct gxp_mcu, fw); |
| int ret; |
| |
| lockdep_assert_held(&mcu_fw->lock); |
| |
| /* |
| * Resets UCI/KCI CSRs to ensure that no unconsumed commands are carried over from the last |
| * execution. |
| */ |
| gxp_mcu_reset_mailbox(mcu); |
| |
| ret = gxp_mcu_firmware_start(mcu_fw); |
| if (ret) |
| return ret; |
| |
| gcip_fault_inject_send(mcu_fw->fault_inject); |
| |
| ret = gxp_mcu_firmware_handshake(mcu_fw); |
| if (ret) { |
| dev_warn(gxp->dev, "Retry MCU firmware handshake with resetting MCU"); |
| if (!gxp_mcu_reset(gxp, true)) |
| ret = gxp_mcu_firmware_handshake(mcu_fw); |
| } |
| |
| /* |
| * We don't need to handshake again if it successfully rescues MCU because it will try |
| * handshake internally. |
| */ |
| if (ret) { |
| ret = gxp_mcu_firmware_rescue(gxp); |
| if (ret) { |
| dev_err(gxp->dev, "Failed to run MCU even after trying to rescue it: %d", |
| ret); |
| gxp_mcu_firmware_shutdown(mcu_fw); |
| wait_for_pg_state_locked(gxp, true); |
| return ret; |
| } |
| } |
| |
| mcu_fw->status = GCIP_FW_VALID; |
| |
| dev_info(gxp->dev, "MCU firmware run succeeded"); |
| |
| return 0; |
| } |
| |
| static int init_mcu_firmware_buf(struct gxp_dev *gxp, |
| struct gxp_mapped_resource *buf) |
| { |
| struct resource r; |
| int ret; |
| |
| ret = gxp_acquire_rmem_resource(gxp, &r, "gxp-mcu-fw-region"); |
| if (ret) |
| return ret; |
| buf->size = resource_size(&r); |
| buf->paddr = r.start; |
| buf->daddr = GXP_IREMAP_CODE_BASE; |
| buf->vaddr = |
| devm_memremap(gxp->dev, buf->paddr, buf->size, MEMREMAP_WC); |
| if (IS_ERR(buf->vaddr)) |
| ret = PTR_ERR(buf->vaddr); |
| return ret; |
| } |
| |
| static char *fw_name_from_buf(struct gxp_dev *gxp, const char *buf) |
| { |
| size_t len; |
| char *name; |
| |
| len = strlen(buf); |
| /* buf from sysfs attribute contains the last line feed character */ |
| if (len == 0 || buf[len - 1] != '\n') |
| return ERR_PTR(-EINVAL); |
| |
| name = devm_kstrdup(gxp->dev, buf, GFP_KERNEL); |
| if (!name) |
| return ERR_PTR(-ENOMEM); |
| /* name should not contain the last line feed character */ |
| name[len - 1] = '\0'; |
| return name; |
| } |
| |
| static ssize_t load_firmware_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct gxp_dev *gxp = dev_get_drvdata(dev); |
| ssize_t ret; |
| char *firmware_name = gxp_firmware_loader_get_mcu_fw_name(gxp); |
| |
| ret = sysfs_emit(buf, "%s\n", firmware_name); |
| kfree(firmware_name); |
| return ret; |
| } |
| |
| static ssize_t load_firmware_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct gxp_dev *gxp = dev_get_drvdata(dev); |
| int ret; |
| char *name; |
| |
| name = fw_name_from_buf(gxp, buf); |
| if (IS_ERR(name)) |
| return PTR_ERR(name); |
| if (gcip_pm_is_powered(gxp->power_mgr->pm)) { |
| dev_err(gxp->dev, |
| "Reject firmware loading because wakelocks are holding"); |
| return -EBUSY; |
| /* |
| * Note: it's still possible a wakelock is acquired by |
| * clients after the check above, but this function is for |
| * development purpose only, we don't insist on preventing |
| * race condition bugs. |
| */ |
| } |
| dev_info(gxp->dev, "loading firmware %s from SysFS", name); |
| /* |
| * It's possible a race condition bug here that someone opens a gxp |
| * device and loads the firmware between below unload/load functions in |
| * another thread, but this interface is only for developer debugging. |
| * We don't insist on preventing the race condition bug. |
| */ |
| gxp_firmware_loader_unload(gxp); |
| gxp_firmware_loader_set_mcu_fw_name(gxp, name); |
| ret = gxp_firmware_loader_load_if_needed(gxp); |
| if (ret) { |
| dev_err(gxp->dev, "Failed to load MCU firmware: %s\n", name); |
| return ret; |
| } |
| return count; |
| } |
| |
| static DEVICE_ATTR_RW(load_firmware); |
| |
| /* Provide the version info of the firmware including CL and privilege_level */ |
| static ssize_t firmware_version_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct gxp_dev *gxp = dev_get_drvdata(dev); |
| struct gxp_mcu_firmware *mcu_fw = gxp_mcu_firmware_of(gxp); |
| const char *priv; |
| |
| if (mcu_fw->status != GCIP_FW_VALID) { |
| dev_warn(gxp->dev, "firmware_version is not available, firmware status: %d", |
| mcu_fw->status); |
| return -ENODEV; |
| } |
| |
| switch (mcu_fw->cfg_parser.last_config.privilege_level) { |
| case GCIP_FW_PRIV_LEVEL_GSA: |
| priv = "GSA"; |
| break; |
| case GCIP_FW_PRIV_LEVEL_TZ: |
| priv = "secure"; |
| break; |
| case GCIP_FW_PRIV_LEVEL_NS: |
| priv = "non-secure"; |
| break; |
| default: |
| priv = "error"; |
| } |
| |
| return sysfs_emit(buf, "cl=%d priv=%s\n", mcu_fw->fw_info.fw_changelist, priv); |
| } |
| |
| static DEVICE_ATTR_RO(firmware_version); |
| |
| /* Provide the count of firmware crash. */ |
| static ssize_t firmware_crash_counter_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct gxp_dev *gxp = dev_get_drvdata(dev); |
| struct gxp_mcu_firmware *mcu_fw = gxp_mcu_firmware_of(gxp); |
| |
| return sysfs_emit(buf, "%d\n", mcu_fw->crash_cnt); |
| } |
| |
| static DEVICE_ATTR_RO(firmware_crash_counter); |
| |
| static struct attribute *dev_attrs[] = { |
| &dev_attr_load_firmware.attr, |
| &dev_attr_firmware_version.attr, |
| &dev_attr_firmware_crash_counter.attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group firmware_attr_group = { |
| .attrs = dev_attrs, |
| }; |
| |
| static int image_config_map(void *data, dma_addr_t daddr, phys_addr_t paddr, |
| size_t size, unsigned int flags) |
| { |
| struct gxp_dev *gxp = data; |
| const bool ns = !(flags & GCIP_IMAGE_CONFIG_FLAGS_SECURE); |
| |
| if (ns) { |
| dev_err(gxp->dev, "image config NS mappings are not supported"); |
| return -EINVAL; |
| } |
| |
| return gcip_iommu_map(gxp_iommu_get_domain_for_dev(gxp), daddr, paddr, size, |
| GCIP_MAP_FLAGS_DMA_RW); |
| } |
| |
| static void image_config_unmap(void *data, dma_addr_t daddr, size_t size, unsigned int flags) |
| { |
| struct gxp_dev *gxp = data; |
| |
| gcip_iommu_unmap(gxp_iommu_get_domain_for_dev(gxp), daddr, size); |
| } |
| |
| static void gxp_mcu_firmware_crash_handler_work(struct work_struct *work) |
| { |
| struct gxp_mcu_firmware *mcu_fw = |
| container_of(work, struct gxp_mcu_firmware, fw_crash_handler_work); |
| |
| gxp_mcu_firmware_crash_handler(mcu_fw->gxp, GCIP_FW_CRASH_UNRECOVERABLE_FAULT); |
| } |
| |
| static int gxp_mcu_firmware_fault_inject_init(struct gxp_mcu_firmware *mcu_fw) |
| { |
| struct gxp_dev *gxp = mcu_fw->gxp; |
| struct gxp_mcu *mcu = container_of(mcu_fw, struct gxp_mcu, fw); |
| struct gcip_fault_inject *injection; |
| const struct gcip_fault_inject_args args = { .dev = gxp->dev, |
| .parent_dentry = gxp->d_entry, |
| .pm = gxp->power_mgr->pm, |
| .send_kci = gxp_kci_fault_injection, |
| .kci_data = &mcu->kci }; |
| |
| injection = gcip_fault_inject_create(&args); |
| |
| if (IS_ERR(injection)) |
| return PTR_ERR(injection); |
| |
| mcu_fw->fault_inject = injection; |
| |
| return 0; |
| } |
| |
| static void gxp_mcu_firmware_fault_inject_exit(struct gxp_mcu_firmware *mcu_fw) |
| { |
| gcip_fault_inject_destroy(mcu_fw->fault_inject); |
| } |
| |
| int gxp_mcu_firmware_init(struct gxp_dev *gxp, struct gxp_mcu_firmware *mcu_fw) |
| { |
| static const struct gcip_image_config_ops image_config_parser_ops = { |
| .map = image_config_map, |
| .unmap = image_config_unmap, |
| }; |
| int ret; |
| |
| ret = gcip_image_config_parser_init( |
| &mcu_fw->cfg_parser, &image_config_parser_ops, gxp->dev, gxp); |
| if (unlikely(ret)) { |
| dev_err(gxp->dev, "failed to init config parser: %d", ret); |
| return ret; |
| } |
| ret = init_mcu_firmware_buf(gxp, &mcu_fw->image_buf); |
| if (ret) { |
| dev_err(gxp->dev, "failed to init MCU firmware buffer: %d", |
| ret); |
| return ret; |
| } |
| mcu_fw->gxp = gxp; |
| mcu_fw->status = GCIP_FW_INVALID; |
| mcu_fw->crash_cnt = 0; |
| mutex_init(&mcu_fw->lock); |
| INIT_WORK(&mcu_fw->fw_crash_handler_work, gxp_mcu_firmware_crash_handler_work); |
| |
| ret = device_add_group(gxp->dev, &firmware_attr_group); |
| if (ret) { |
| dev_err(gxp->dev, "failed to create firmware device group"); |
| return ret; |
| } |
| |
| ret = gxp_mcu_firmware_fault_inject_init(mcu_fw); |
| if (ret) |
| dev_warn(gxp->dev, "failed to init fault injection: %d", ret); |
| |
| return 0; |
| } |
| |
| void gxp_mcu_firmware_exit(struct gxp_mcu_firmware *mcu_fw) |
| { |
| if (IS_GXP_TEST && (!mcu_fw || !mcu_fw->gxp)) |
| return; |
| |
| gxp_mcu_firmware_fault_inject_exit(mcu_fw); |
| cancel_work_sync(&mcu_fw->fw_crash_handler_work); |
| device_remove_group(mcu_fw->gxp->dev, &firmware_attr_group); |
| } |
| |
| int gxp_mcu_firmware_run(struct gxp_mcu_firmware *mcu_fw) |
| { |
| int ret; |
| |
| mutex_lock(&mcu_fw->lock); |
| if (mcu_fw->status == GCIP_FW_INVALID) |
| ret = -EINVAL; |
| else |
| ret = gxp_mcu_firmware_run_locked(mcu_fw); |
| mutex_unlock(&mcu_fw->lock); |
| return ret; |
| } |
| |
| void gxp_mcu_firmware_stop(struct gxp_mcu_firmware *mcu_fw) |
| { |
| mutex_lock(&mcu_fw->lock); |
| gxp_mcu_firmware_stop_locked(mcu_fw); |
| mutex_unlock(&mcu_fw->lock); |
| } |
| |
| void gxp_mcu_firmware_crash_handler(struct gxp_dev *gxp, |
| enum gcip_fw_crash_type crash_type) |
| { |
| struct gxp_mcu_firmware *mcu_fw = gxp_mcu_firmware_of(gxp); |
| struct gxp_client *client; |
| struct gcip_pm *pm = gxp->power_mgr->pm; |
| int ret; |
| |
| dev_err(gxp->dev, "MCU firmware is crashed, crash_type=%d", crash_type); |
| |
| /* |
| * This crash handler can be triggered in two cases: |
| * 1. The MCU firmware detects some unrecoverable faults and sends FW_CRASH RKCI to the |
| * kernel driver. (GCIP_FW_CRASH_UNRECOVERABLE_FAULT) |
| * 2. The MCU firmware is crashed some reasons which cannot be detected by itself and the |
| * kernel driver notices the MCU crash with the HW watchdog timeout. |
| * (GCIP_FW_CRASH_HW_WDG_TIMEOUT) |
| * |
| * As those two cases are asynchronous, they can happen simultaneously. In the first case, |
| * the MCU firmware must turn off the HW watchdog first to prevent that race case. |
| */ |
| if (crash_type != GCIP_FW_CRASH_UNRECOVERABLE_FAULT && |
| crash_type != GCIP_FW_CRASH_HW_WDG_TIMEOUT) |
| return; |
| |
| dev_err(gxp->dev, "Unrecoverable MCU firmware fault, handle it"); |
| |
| mcu_fw->crash_cnt += 1; |
| |
| /* |
| * In the case of stopping MCU FW while it is handling `CLIENT_FATAL_ERROR` RKCI, it will |
| * acquire locks in this order: |
| * gcip_pm_put -> holds @pm->lock -> gxp_mcu_firmware_stop -> holds @mcu_fw->lock |
| * -> waits for the completion of RKCI handler -> gxp_vd_invalidate_with_client_id |
| * -> holds @gxp->client_list_lock -> hold @client->semaphore -> holds @gxp->vd_semaphore |
| * |
| * Also, in the case of starting MCU FW, the locking order will be: |
| * gcip_pm_get -> holds @pm->lock -> gxp_mcu_firmware_run -> holds @mcu_fw->lock |
| * |
| * To prevent a deadlock issue, we have to follow the same locking order from here. |
| */ |
| |
| /* |
| * Holding the PM lock due to the reasons listed below. |
| * 1. As we are recovering the MCU firmware, we should block the PM requests (e.g., |
| * acquiring or releasing the block wakelock) until the rescuing is finished. |
| * 2. Restarting the MCU firmware might involve restore functions (e.g., |
| * gcip_thermal_restore_on_powering) which require the caller to hold the PM lock. |
| */ |
| gcip_pm_lock(pm); |
| |
| /* |
| * By the race, if all clients left earlier than this handler, all block wakleock should be |
| * already released and the BLK is turned off. We don't have to rescue the MCU firmware. |
| */ |
| if (!gcip_pm_is_powered(pm)) { |
| dev_info( |
| gxp->dev, |
| "The block wakelock is already released, skip restarting MCU firmware"); |
| goto out_unlock_pm; |
| } |
| |
| /* Hold @mcu_fw->lock because manipulating the MCU FW state must be a critical section. */ |
| mutex_lock(&mcu_fw->lock); |
| |
| /* |
| * Prevent @gxp->client_list is being changed while handling the crash. |
| * The user cannot open or close a fd until this function releases the lock. |
| */ |
| mutex_lock(&gxp->client_list_lock); |
| |
| /* |
| * Hold @client->semaphore first to prevent deadlock. |
| * By holding this lock, clients cannot proceed most IOCTLs. |
| */ |
| list_for_each_entry (client, &gxp->client_list, list_entry) { |
| down_write(&client->semaphore); |
| } |
| |
| /* |
| * Holding @client->semaphore will block the most client actions, but let's make sure |
| * it by holding the locks directly related to the actions we want to block accordingly. |
| * For example, in the case of the block wakelock, the debug dump can try to acquire it |
| * which cannot be blocked by holding @client->semaphore. |
| * |
| * However, we don't lock @gxp->vd_semaphore for not increasing lock dependency since |
| * holding @gxp->client_list_lock and @client->semaphore is enough to ensure no new VD |
| * being allocated. |
| */ |
| |
| /* |
| * Discard all pending/unconsumed UCI responses and change the state of all virtual devices |
| * to GXP_VD_UNAVAILABLE. From now on, all clients cannot request new UCI commands. |
| */ |
| list_for_each_entry (client, &gxp->client_list, list_entry) { |
| if (client->has_block_wakelock && client->vd) { |
| gxp_vd_invalidate(gxp, client->vd, GXP_INVALIDATED_MCU_CRASH); |
| client->vd->mcu_crashed = true; |
| } |
| } |
| |
| /* Dump diagnostic information for MCU crash before resetting it. */ |
| gxp_debug_dump_report_mcu_crash(gxp); |
| |
| /* Waits for the MCU transiting to PG state and restart the MCU firmware. */ |
| if (!wait_for_pg_state_locked(gxp, crash_type == GCIP_FW_CRASH_HW_WDG_TIMEOUT)) { |
| dev_warn(gxp->dev, "Failed to transit MCU LPM state to PG"); |
| goto out; |
| } |
| |
| ret = gxp_mcu_firmware_run_locked(mcu_fw); |
| if (ret) |
| dev_warn(gxp->dev, "Failed to run MCU firmware (ret=%d)", ret); |
| |
| out: |
| list_for_each_entry (client, &gxp->client_list, list_entry) { |
| up_write(&client->semaphore); |
| } |
| mutex_unlock(&gxp->client_list_lock); |
| mutex_unlock(&mcu_fw->lock); |
| out_unlock_pm: |
| gcip_pm_unlock(pm); |
| } |