summaryrefslogtreecommitdiff
path: root/gxp-firmware-loader.c
diff options
context:
space:
mode:
Diffstat (limited to 'gxp-firmware-loader.c')
-rw-r--r--gxp-firmware-loader.c301
1 files changed, 301 insertions, 0 deletions
diff --git a/gxp-firmware-loader.c b/gxp-firmware-loader.c
new file mode 100644
index 0000000..5f64bd4
--- /dev/null
+++ b/gxp-firmware-loader.c
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GXP firmware loading management.
+ *
+ * Copyright (C) 2023 Google LLC
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+
+#include <gcip/gcip-common-image-header.h>
+#include <gcip/gcip-image-config.h>
+
+#include "gxp-config.h"
+#include "gxp-firmware-loader.h"
+#include "gxp-firmware.h"
+#include "gxp-internal.h"
+
+#if GXP_HAS_MCU
+#include <linux/gsa/gsa_dsp.h>
+
+#include "gxp-mcu-firmware.h"
+#endif
+
+#if GXP_HAS_MCU
+static int gxp_firmware_loader_gsa_auth(struct gxp_dev *gxp)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+ int ret;
+ uint core;
+ dma_addr_t headers_dma_addr;
+ void *header_vaddr;
+ const u8 *data;
+ struct gxp_mcu_firmware *mcu_fw = gxp_mcu_firmware_of(gxp);
+
+ if (!mcu_fw->is_secure) {
+ dev_warn(
+ gxp->dev,
+ "No need to do firmware authentication with non-secure privilege\n");
+ return 0;
+ }
+ if (!gxp->gsa_dev) {
+ dev_warn(
+ gxp->dev,
+ "No GSA device available, skipping firmware authentication\n");
+ return 0;
+ }
+ /* Authenticate MCU firmware */
+ header_vaddr = dma_alloc_coherent(gxp->gsa_dev, GCIP_FW_HEADER_SIZE,
+ &headers_dma_addr, GFP_KERNEL);
+ if (!header_vaddr) {
+ dev_err(gxp->dev,
+ "Failed to allocate coherent memory for header\n");
+ return -ENOMEM;
+ }
+ memcpy(header_vaddr, mgr->mcu_firmware->data, GCIP_FW_HEADER_SIZE);
+ ret = gsa_load_dsp_fw_image(gxp->gsa_dev, headers_dma_addr,
+ mcu_fw->image_buf.paddr);
+ if (ret) {
+ dev_err(gxp->dev, "MCU fw GSA authentication fails");
+ goto err_load_mcu_fw;
+ }
+
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ data = mgr->core_firmware[core]->data;
+ /* Authenticate core firmware */
+ memcpy(header_vaddr, data, GCIP_FW_HEADER_SIZE);
+ ret = gsa_load_dsp_fw_image(gxp->gsa_dev, headers_dma_addr,
+ gxp->fwbufs[core].paddr);
+ if (ret) {
+ dev_err(gxp->dev,
+ "Core %u firmware authentication fails", core);
+ goto err_load_core_fw;
+ }
+ }
+ dma_free_coherent(gxp->gsa_dev, GCIP_FW_HEADER_SIZE, header_vaddr,
+ headers_dma_addr);
+ return 0;
+err_load_core_fw:
+ gsa_unload_dsp_fw_image(gxp->gsa_dev);
+err_load_mcu_fw:
+ dma_free_coherent(gxp->gsa_dev, GCIP_FW_HEADER_SIZE, header_vaddr,
+ headers_dma_addr);
+ return ret;
+}
+
+static void gxp_firmware_loader_gsa_unload(struct gxp_dev *gxp)
+{
+ struct gxp_mcu_firmware *mcu_fw = gxp_mcu_firmware_of(gxp);
+
+ if (mcu_fw->is_secure)
+ gsa_unload_dsp_fw_image(gxp->gsa_dev);
+}
+#endif /* GXP_HAS_MCU */
+
+int gxp_firmware_loader_init(struct gxp_dev *gxp)
+{
+ struct gxp_firmware_loader_manager *mgr;
+
+ mgr = devm_kzalloc(gxp->dev, sizeof(*mgr), GFP_KERNEL);
+ if (!mgr)
+ return -ENOMEM;
+ gxp->fw_loader_mgr = mgr;
+ mutex_init(&mgr->lock);
+ return 0;
+}
+
+void gxp_firmware_loader_destroy(struct gxp_dev *gxp)
+{
+ gxp_firmware_loader_unload(gxp);
+}
+
+void gxp_firmware_loader_set_core_fw_name(struct gxp_dev *gxp,
+ const char *fw_name)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+
+ mutex_lock(&mgr->lock);
+ mgr->core_firmware_name = kstrdup(fw_name, GFP_KERNEL);
+ mutex_unlock(&mgr->lock);
+}
+
+char *gxp_firmware_loader_get_core_fw_name(struct gxp_dev *gxp)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+ char *name;
+
+ mutex_lock(&mgr->lock);
+ if (mgr->core_firmware_name)
+ name = kstrdup(mgr->core_firmware_name, GFP_KERNEL);
+ else
+ name = kstrdup(DSP_FIRMWARE_DEFAULT_PREFIX, GFP_KERNEL);
+ mutex_unlock(&mgr->lock);
+ return name;
+}
+
+/*
+ * Fetches and records image config of the first core firmware.
+ */
+static void gxp_firmware_loader_get_core_image_config(struct gxp_dev *gxp)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+ struct gcip_common_image_header *hdr =
+ (struct gcip_common_image_header *)mgr->core_firmware[0]->data;
+ struct gcip_image_config *cfg;
+
+ if (unlikely(mgr->core_firmware[0]->size < GCIP_FW_HEADER_SIZE))
+ return;
+ cfg = get_image_config_from_hdr(hdr);
+ if (cfg)
+ mgr->core_img_cfg = *cfg;
+ else
+ dev_warn(gxp->dev,
+ "Core 0 Firmware doesn't have a valid image config");
+}
+
+/*
+ * Call this function when mgr->core_firmware have been populated.
+ * This function sets is_loaded to true.
+ *
+ */
+static void gxp_firmware_loader_has_loaded(struct gxp_dev *gxp)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+
+ lockdep_assert_held(&mgr->lock);
+ gxp_firmware_loader_get_core_image_config(gxp);
+ mgr->is_loaded = true;
+}
+
+static void gxp_firmware_loader_unload_core_firmware(struct gxp_dev *gxp)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+ uint core;
+
+ lockdep_assert_held(&mgr->lock);
+ for (core = 0; core < GXP_NUM_CORES; core++) {
+ if (mgr->core_firmware[core]) {
+ release_firmware(mgr->core_firmware[core]);
+ mgr->core_firmware[core] = NULL;
+ }
+ }
+ kfree(mgr->core_firmware_name);
+ mgr->core_firmware_name = NULL;
+}
+
+#if GXP_HAS_MCU
+static void gxp_firmware_loader_unload_mcu_firmware(struct gxp_dev *gxp)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+
+ lockdep_assert_held(&mgr->lock);
+ if (!gxp_is_direct_mode(gxp)) {
+ if (mgr->mcu_firmware) {
+ gxp_mcu_firmware_unload(gxp, mgr->mcu_firmware);
+ release_firmware(mgr->mcu_firmware);
+ mgr->mcu_firmware = NULL;
+ }
+ kfree(mgr->mcu_firmware_name);
+ mgr->mcu_firmware_name = NULL;
+ }
+}
+#endif /* GXP_HAS_MCU */
+
+static int gxp_firmware_loader_load_locked(struct gxp_dev *gxp)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+ int ret;
+
+ lockdep_assert_held(&mgr->lock);
+ ret = gxp_firmware_load_core_firmware(gxp, mgr->core_firmware_name,
+ mgr->core_firmware);
+ if (ret)
+ return ret;
+
+#if GXP_HAS_MCU
+ if (!gxp_is_direct_mode(gxp)) {
+ ret = gxp_mcu_firmware_load(gxp, mgr->mcu_firmware_name,
+ &mgr->mcu_firmware);
+ if (ret)
+ goto err_unload_core;
+
+ ret = gxp_firmware_loader_gsa_auth(gxp);
+ if (ret)
+ goto err_unload_mcu;
+ }
+#endif
+ ret = gxp_firmware_rearrange_elf(gxp, mgr->core_firmware);
+ if (ret)
+ goto err_unload;
+ gxp_firmware_loader_has_loaded(gxp);
+ return 0;
+
+err_unload:
+#if GXP_HAS_MCU
+ if (!gxp_is_direct_mode(gxp))
+ gxp_firmware_loader_gsa_unload(gxp);
+err_unload_mcu:
+ if (!gxp_is_direct_mode(gxp))
+ gxp_firmware_loader_unload_mcu_firmware(gxp);
+err_unload_core:
+#endif
+ gxp_firmware_loader_unload_core_firmware(gxp);
+ return ret;
+}
+
+int gxp_firmware_loader_load_if_needed(struct gxp_dev *gxp)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+ int ret = 0;
+
+ mutex_lock(&mgr->lock);
+ if (mgr->is_loaded)
+ goto out;
+ ret = gxp_firmware_loader_load_locked(gxp);
+out:
+ mutex_unlock(&mgr->lock);
+ return ret;
+}
+
+void gxp_firmware_loader_unload(struct gxp_dev *gxp)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+
+ mutex_lock(&mgr->lock);
+ if (mgr->is_loaded) {
+#if GXP_HAS_MCU
+ gxp_firmware_loader_gsa_unload(gxp);
+ gxp_firmware_loader_unload_mcu_firmware(gxp);
+#endif
+ gxp_firmware_loader_unload_core_firmware(gxp);
+ }
+ mgr->is_loaded = false;
+ mutex_unlock(&mgr->lock);
+}
+
+#if GXP_HAS_MCU
+void gxp_firmware_loader_set_mcu_fw_name(struct gxp_dev *gxp,
+ const char *fw_name)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+
+ mutex_lock(&mgr->lock);
+ mgr->mcu_firmware_name = kstrdup(fw_name, GFP_KERNEL);
+ mutex_unlock(&mgr->lock);
+}
+
+char *gxp_firmware_loader_get_mcu_fw_name(struct gxp_dev *gxp)
+{
+ struct gxp_firmware_loader_manager *mgr = gxp->fw_loader_mgr;
+ char *name;
+
+ mutex_lock(&mgr->lock);
+ if (mgr->mcu_firmware_name)
+ name = kstrdup(mgr->mcu_firmware_name, GFP_KERNEL);
+ else
+ name = kstrdup(GXP_DEFAULT_MCU_FIRMWARE, GFP_KERNEL);
+ mutex_unlock(&mgr->lock);
+ return name;
+}
+#endif /* GXP_HAS_MCU */