summaryrefslogtreecommitdiff
path: root/sound/soc/sof/ipc4-loader.c
diff options
context:
space:
mode:
authorPeter Ujfalusi <peter.ujfalusi@linux.intel.com>2022-10-20 15:12:38 +0300
committerMark Brown <broonie@kernel.org>2022-10-21 13:05:11 +0100
commit73c091a2fe96fac2b893ba166fa7cd11eff45947 (patch)
treeb10d59633f0a2c818cfc178ac728656e4f2af004 /sound/soc/sof/ipc4-loader.c
parentba42b8bac3fd10b90eefbe42d8d0839d71bf7638 (diff)
ASoC: SOF: ipc4-loader: Support for loading external libraries
In case the requested module is not available among the loaded libraries, try to load it as external library. The kernel will try to load the file from <fw_lib_prefix>/<module_uuid>.bin If the file found, then the ext manifest of it is parsed, placed it under XArray and the pointer to the module is returned to the caller. Releasing the firmware will be done on ipc cleanup time. Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com> Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Reviewed-by: Chao Song <chao.song@intel.com> Reviewed-by: Kai Vehmanen <kai.vehmanen@linux.intel.com> Link: https://lore.kernel.org/r/20221020121238.18339-20-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
Diffstat (limited to 'sound/soc/sof/ipc4-loader.c')
-rw-r--r--sound/soc/sof/ipc4-loader.c155
1 files changed, 150 insertions, 5 deletions
diff --git a/sound/soc/sof/ipc4-loader.c b/sound/soc/sof/ipc4-loader.c
index dbe3ee4ef08c..af0018b38cf0 100644
--- a/sound/soc/sof/ipc4-loader.c
+++ b/sound/soc/sof/ipc4-loader.c
@@ -14,6 +14,9 @@
#include "sof-priv.h"
#include "ops.h"
+/* The module ID includes the id of the library it is part of at offset 12 */
+#define SOF_IPC4_MOD_LIB_ID_SHIFT 12
+
static size_t sof_ipc4_fw_parse_ext_man(struct snd_sof_dev *sdev,
struct sof_ipc4_fw_library *fw_lib)
{
@@ -71,17 +74,18 @@ static size_t sof_ipc4_fw_parse_ext_man(struct snd_sof_dev *sdev,
return -EINVAL;
}
- dev_info(sdev->dev, "Loaded firmware version: %u.%u.%u.%u\n",
- fw_header->major_version, fw_header->minor_version,
+ dev_info(sdev->dev, "Loaded firmware library: %s, version: %u.%u.%u.%u\n",
+ fw_header->name, fw_header->major_version, fw_header->minor_version,
fw_header->hotfix_version, fw_header->build_version);
- dev_dbg(sdev->dev, "Firmware name: %s, header length: %u, module count: %u\n",
- fw_header->name, fw_header->len, fw_header->num_module_entries);
+ dev_dbg(sdev->dev, "Header length: %u, module count: %u\n",
+ fw_header->len, fw_header->num_module_entries);
fw_lib->modules = devm_kmalloc_array(sdev->dev, fw_header->num_module_entries,
sizeof(*fw_module), GFP_KERNEL);
if (!fw_lib->modules)
return -ENOMEM;
+ fw_lib->name = fw_header->name;
fw_lib->num_modules = fw_header->num_module_entries;
fw_module = fw_lib->modules;
@@ -160,13 +164,111 @@ static size_t sof_ipc4_fw_parse_basefw_ext_man(struct snd_sof_dev *sdev)
return payload_offset;
}
+static int sof_ipc4_load_library_by_uuid(struct snd_sof_dev *sdev,
+ unsigned long lib_id, const guid_t *uuid)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_ipc4_fw_library *fw_lib;
+ const char *fw_filename;
+ size_t payload_offset;
+ int ret, i, err;
+
+ if (!sdev->pdata->fw_lib_prefix) {
+ dev_err(sdev->dev,
+ "Library loading is not supported due to not set library path\n");
+ return -EINVAL;
+ }
+
+ if (!ipc4_data->load_library) {
+ dev_err(sdev->dev, "Library loading is not supported on this platform\n");
+ return -EOPNOTSUPP;
+ }
+
+ fw_lib = devm_kzalloc(sdev->dev, sizeof(*fw_lib), GFP_KERNEL);
+ if (!fw_lib)
+ return -ENOMEM;
+
+ fw_filename = kasprintf(GFP_KERNEL, "%s/%pUL.bin",
+ sdev->pdata->fw_lib_prefix, uuid);
+ if (!fw_filename) {
+ ret = -ENOMEM;
+ goto free_fw_lib;
+ }
+
+ ret = request_firmware(&fw_lib->sof_fw.fw, fw_filename, sdev->dev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Library file '%s' is missing\n", fw_filename);
+ goto free_filename;
+ } else {
+ dev_dbg(sdev->dev, "Library file '%s' loaded\n", fw_filename);
+ }
+
+ payload_offset = sof_ipc4_fw_parse_ext_man(sdev, fw_lib);
+ if (payload_offset <= 0) {
+ if (!payload_offset)
+ ret = -EINVAL;
+ else
+ ret = payload_offset;
+
+ goto release;
+ }
+
+ fw_lib->sof_fw.payload_offset = payload_offset;
+ fw_lib->id = lib_id;
+
+ /* Fix up the module ID numbers within the library */
+ for (i = 0; i < fw_lib->num_modules; i++)
+ fw_lib->modules[i].man4_module_entry.id |= (lib_id << SOF_IPC4_MOD_LIB_ID_SHIFT);
+
+ /*
+ * Make sure that the DSP is booted and stays up while attempting the
+ * loading the library for the first time
+ */
+ ret = pm_runtime_resume_and_get(sdev->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(sdev->dev, "%s: pm_runtime resume failed: %d\n",
+ __func__, ret);
+ goto release;
+ }
+
+ ret = ipc4_data->load_library(sdev, fw_lib, false);
+
+ pm_runtime_mark_last_busy(sdev->dev);
+ err = pm_runtime_put_autosuspend(sdev->dev);
+ if (err < 0)
+ dev_err_ratelimited(sdev->dev, "%s: pm_runtime idle failed: %d\n",
+ __func__, err);
+
+ if (ret)
+ goto release;
+
+ ret = xa_insert(&ipc4_data->fw_lib_xa, lib_id, fw_lib, GFP_KERNEL);
+ if (unlikely(ret))
+ goto release;
+
+ kfree(fw_filename);
+
+ return 0;
+
+release:
+ release_firmware(fw_lib->sof_fw.fw);
+ /* Allocated within sof_ipc4_fw_parse_ext_man() */
+ devm_kfree(sdev->dev, fw_lib->modules);
+free_filename:
+ kfree(fw_filename);
+free_fw_lib:
+ devm_kfree(sdev->dev, fw_lib);
+
+ return ret;
+}
+
struct sof_ipc4_fw_module *sof_ipc4_find_module_by_uuid(struct snd_sof_dev *sdev,
const guid_t *uuid)
{
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
struct sof_ipc4_fw_library *fw_lib;
unsigned long lib_id;
- int i;
+ int i, ret;
if (guid_is_null(uuid))
return NULL;
@@ -178,6 +280,30 @@ struct sof_ipc4_fw_module *sof_ipc4_find_module_by_uuid(struct snd_sof_dev *sdev
}
}
+ /*
+ * Do not attempt to load external library in case the maximum number of
+ * firmware libraries have been already loaded
+ */
+ if ((lib_id + 1) == ipc4_data->max_libs_count) {
+ dev_err(sdev->dev,
+ "%s: Maximum allowed number of libraries reached (%u)\n",
+ __func__, ipc4_data->max_libs_count);
+ return NULL;
+ }
+
+ /* The module cannot be found, try to load it as a library */
+ ret = sof_ipc4_load_library_by_uuid(sdev, lib_id + 1, uuid);
+ if (ret)
+ return NULL;
+
+ /* Look for the module in the newly loaded library, it should be available now */
+ xa_for_each_start(&ipc4_data->fw_lib_xa, lib_id, fw_lib, lib_id) {
+ for (i = 0; i < fw_lib->num_modules; i++) {
+ if (guid_equal(uuid, &fw_lib->modules[i].man4_module_entry.uuid))
+ return &fw_lib->modules[i];
+ }
+ }
+
return NULL;
}
@@ -270,6 +396,25 @@ out:
return ret;
}
+int sof_ipc4_reload_fw_libraries(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_ipc4_fw_library *fw_lib;
+ unsigned long lib_id;
+ int ret = 0;
+
+ xa_for_each_start(&ipc4_data->fw_lib_xa, lib_id, fw_lib, 1) {
+ ret = ipc4_data->load_library(sdev, fw_lib, true);
+ if (ret) {
+ dev_err(sdev->dev, "%s: Failed to reload library: %s, %d\n",
+ __func__, fw_lib->name, ret);
+ break;
+ }
+ }
+
+ return ret;
+}
+
const struct sof_ipc_fw_loader_ops ipc4_loader_ops = {
.validate = sof_ipc4_validate_firmware,
.parse_ext_manifest = sof_ipc4_fw_parse_basefw_ext_man,