blob: 5f64bd4b2604b36d648a9733cc2a602582c972cf [file] [log] [blame]
// 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 */