Skip to content

Commit

Permalink
ASoC: SOF: core: Support for ipc type fallback if files are missing
Browse files Browse the repository at this point in the history
In case the firmware file or the topology file is missing from the
filesystem provide a way to test other supported IPC types to see if both
firmware and topology file can be found and use that instead of the
requested, default IPC type.

If a user requested a specific firmware file (overriding path and/or name)
then read the file and use the IPC type of the selected firmware to look
for the topology file and firmware libraries (IPC4 only).

This feature will help with migrating platforms to a new IPC type in
phases, providing increasing number of topology files in sof-bin.
If the topology file is present, the IPC type can be used, if it is not
then we fall back to older IPC type and use it without breaking the audio
for the user.

Signed-off-by: Peter Ujfalusi <[email protected]>
  • Loading branch information
ujfalusi committed Apr 27, 2023
1 parent 692bc67 commit d14d318
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 24 deletions.
56 changes: 39 additions & 17 deletions sound/soc/sof/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,34 @@ static int sof_select_ipc_and_paths(struct snd_sof_dev *sdev)
return 0;
}

static int validate_sof_ops(struct snd_sof_dev *sdev)
{
int ret;

/* init ops, if necessary */
ret = sof_ops_init(sdev);
if (ret < 0)
return ret;

/* check all mandatory ops */
if (!sof_ops(sdev) || !sof_ops(sdev)->probe) {
dev_err(sdev->dev, "missing mandatory ops\n");
sof_ops_free(sdev);
return -EINVAL;
}

if (!sdev->dspless_mode_selected &&
(!sof_ops(sdev)->run || !sof_ops(sdev)->block_read ||
!sof_ops(sdev)->block_write || !sof_ops(sdev)->send_msg ||
!sof_ops(sdev)->load_firmware || !sof_ops(sdev)->ipc_msg_data)) {
dev_err(sdev->dev, "missing mandatory DSP ops\n");
sof_ops_free(sdev);
return -EINVAL;
}

return 0;
}

static int sof_init_environment(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *plat_data = sdev->pdata;
Expand All @@ -266,26 +294,10 @@ static int sof_init_environment(struct snd_sof_dev *sdev)
plat_data->tplg_filename = base_profile->tplg_name;

/* init ops, if necessary */
ret = sof_ops_init(sdev);
ret = validate_sof_ops(sdev);
if (ret < 0)
return ret;

/* check all mandatory ops */
if (!sof_ops(sdev) || !sof_ops(sdev)->probe) {
dev_err(sdev->dev, "missing mandatory ops\n");
sof_ops_free(sdev);
return -EINVAL;
}

if (!sdev->dspless_mode_selected &&
(!sof_ops(sdev)->run || !sof_ops(sdev)->block_read ||
!sof_ops(sdev)->block_write || !sof_ops(sdev)->send_msg ||
!sof_ops(sdev)->load_firmware || !sof_ops(sdev)->ipc_msg_data)) {
dev_err(sdev->dev, "missing mandatory DSP ops\n");
sof_ops_free(sdev);
return -EINVAL;
}

/* probe the DSP hardware */
ret = snd_sof_probe(sdev);
if (ret < 0) {
Expand All @@ -302,6 +314,16 @@ static int sof_init_environment(struct snd_sof_dev *sdev)
}

ret = sof_select_ipc_and_paths(sdev);
if (!ret && plat_data->ipc_type != base_profile->ipc_type) {
/* IPC type changed, re-initialize the ops */
sof_ops_free(sdev);

ret = validate_sof_ops(sdev);
if (ret < 0) {
snd_sof_remove(sdev);
return ret;
}
}

err_machine_check:
if (ret) {
Expand Down
192 changes: 185 additions & 7 deletions sound/soc/sof/fw-file-profile.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,92 @@
// Copyright(c) 2023 Intel Corporation. All rights reserved.
//

#include <linux/firmware.h>
#include <sound/sof.h>
#include <sound/sof/ext_manifest4.h>
#include "sof-priv.h"

static int sof_test_firmware_file(struct device *dev,
struct sof_loadable_file_profile *profile,
enum sof_ipc_type *ipc_type_to_adjust)
{
enum sof_ipc_type fw_ipc_type;
const struct firmware *fw;
const char *fw_filename;
const u32 *magic;
int ret;

fw_filename = kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path,
profile->fw_name);
if (!fw_filename)
return -ENOMEM;

ret = firmware_request_nowarn(&fw, fw_filename, dev);
if (ret < 0) {
kfree(fw_filename);
return ret;
}

/* firmware file exists, check the magic number */
magic = (const u32 *)fw->data;
switch (*magic) {
case SOF_EXT_MAN_MAGIC_NUMBER:
fw_ipc_type = SOF_IPC;
break;
case SOF_EXT_MAN4_MAGIC_NUMBER:
fw_ipc_type = SOF_INTEL_IPC4;
break;
default:
dev_err(dev, "Invalid firmware magic: %#x\n", *magic);
ret = -EINVAL;
goto out;
}

if (ipc_type_to_adjust) {
*ipc_type_to_adjust = fw_ipc_type;
} else if (fw_ipc_type != profile->ipc_type) {
dev_err(dev,
"ipc type mismatch between %s and expected: %d vs %d\n",
fw_filename, fw_ipc_type, profile->ipc_type);
ret = -EINVAL;
}
out:
release_firmware(fw);
kfree(fw_filename);

return ret;
}

static int sof_test_topology_file(struct device *dev,
struct sof_loadable_file_profile *profile)
{
const struct firmware *fw;
const char *fw_filename;
int ret;

if (!profile->tplg_path || !profile->tplg_name)
return 0;

fw_filename = kasprintf(GFP_KERNEL, "%s/%s", profile->tplg_path,
profile->tplg_name);
if (!fw_filename)
return -ENOMEM;

ret = firmware_request_nowarn(&fw, fw_filename, dev);
if (!ret)
release_firmware(fw);

kfree(fw_filename);
return ret;
}

static int
sof_file_profile_for_ipc_type(struct snd_sof_dev *sdev,
enum sof_ipc_type ipc_type,
const struct sof_dev_desc *desc,
struct sof_loadable_file_profile *base_profile,
struct sof_loadable_file_profile *out_profile)
{
enum sof_ipc_type ipc_type = base_profile->ipc_type;
struct snd_sof_pdata *plat_data = sdev->pdata;
bool fw_lib_path_allocated = false;
struct device *dev = sdev->dev;
Expand Down Expand Up @@ -43,6 +119,25 @@ sof_file_profile_for_ipc_type(struct snd_sof_dev *sdev,
else
out_profile->fw_name = desc->default_fw_filename[ipc_type];

/*
* Check the custom firmware path/filename and adjust the ipc_type to
* match with the existing file for the remaining path configuration.
*
* For default path and firmware name do a verification before
* continuing further.
*/
if (base_profile->fw_path || base_profile->fw_name) {
ret = sof_test_firmware_file(dev, out_profile, &ipc_type);
if (ret)
return ret;

if (!(desc->ipc_supported_mask & BIT(ipc_type))) {
dev_err(dev, "Unsupported IPC type %d needed by %s/%s\n",
ipc_type, base_profile->fw_path,
base_profile->fw_name);
return -EINVAL;
}
}

/* firmware library path */
if (base_profile->fw_lib_path) {
Expand Down Expand Up @@ -78,6 +173,13 @@ sof_file_profile_for_ipc_type(struct snd_sof_dev *sdev,

out_profile->ipc_type = ipc_type;

/* Test only default firmware file */
if (!base_profile->fw_path && !base_profile->fw_name)
ret = sof_test_firmware_file(dev, out_profile, NULL);

if (!ret)
ret = sof_test_topology_file(dev, out_profile);

out:
if (ret) {
/* Free up path strings created with devm_kasprintf */
Expand All @@ -92,11 +194,65 @@ sof_file_profile_for_ipc_type(struct snd_sof_dev *sdev,
return ret;
}

static void
sof_missing_firmware_notification(struct snd_sof_dev *sdev,
enum sof_ipc_type ipc_type,
struct sof_loadable_file_profile *base_profile)
{
struct snd_sof_pdata *plat_data = sdev->pdata;
const struct sof_dev_desc *desc = plat_data->desc;
struct device *dev = sdev->dev;
char *marker;
int i;

dev_err(dev, "SOF firmware and/or topology file not found.\n");

for (i = 0; i < SOF_IPC_TYPE_COUNT; i++) {
if (!(desc->ipc_supported_mask & BIT(i)))
continue;

if (i == ipc_type)
marker = "Requested";
else
marker = "Fallback";

dev_info(dev, "Available default profiles\n");
dev_info(dev, "- ipc type %d (%s):\n", i, marker);
if (base_profile->fw_path_postfix)
dev_info(dev, " Firmware file: %s/%s/%s\n",
desc->default_fw_path[i],
base_profile->fw_path_postfix,
desc->default_fw_filename[i]);
else
dev_info(dev, " Firmware file: %s/%s\n",
desc->default_fw_path[i],
desc->default_fw_filename[i]);

dev_info(dev, " Topology file: %s/%s\n",
desc->default_tplg_path[i],
plat_data->tplg_filename);
}

if (base_profile->fw_path || base_profile->fw_name ||
base_profile->tplg_path || base_profile->tplg_name)
dev_info(dev, "Try to remove custom kernel module parameters.\n");

dev_info(dev, "Check if you have 'sof-firmware' package installed.\n");
dev_info(dev, "Optionally it can be manually downloaded from:\n");
dev_info(dev, " https://github.com/thesofproject/sof-bin/\n");
}

static void sof_print_profile_info(struct snd_sof_dev *sdev,
enum sof_ipc_type ipc_type,
struct sof_loadable_file_profile *profile)
{
struct device *dev = sdev->dev;

if (ipc_type != profile->ipc_type)
dev_info(dev,
"Using fallback IPC type %d (requested type was %d)\n",
profile->ipc_type, ipc_type);

dev_dbg(dev, "Loadable file profile for ipc type %d:\n", profile->ipc_type);

dev_dbg(dev, " Firmware file: %s/%s\n", profile->fw_path,
Expand All @@ -114,16 +270,38 @@ int sof_create_ipc_file_profile(struct snd_sof_dev *sdev,
struct sof_loadable_file_profile *out_profile)
{
const struct sof_dev_desc *desc = sdev->pdata->desc;
int ret;
int ret = -ENOENT;
int i;

memset(out_profile, 0, sizeof(*out_profile));

ret = sof_file_profile_for_ipc_type(sdev, desc, base_profile, out_profile);
if (ret)
return ret;
ret = sof_file_profile_for_ipc_type(sdev, base_profile->ipc_type, desc,
base_profile, out_profile);
if (!ret)
goto out;

sof_print_profile_info(sdev, out_profile);
/*
* No firmware file was found for the requested IPC type, check all
* IPC types as fallback
*/
for (i = 0; i < SOF_IPC_TYPE_COUNT; i++) {
if (i == base_profile->ipc_type ||
!(desc->ipc_supported_mask & BIT(i)))
continue;

return 0;
ret = sof_file_profile_for_ipc_type(sdev, i, desc, base_profile,
out_profile);
if (!ret)
break;
}

out:
if (ret)
sof_missing_firmware_notification(sdev, base_profile->ipc_type,
base_profile);
else
sof_print_profile_info(sdev, base_profile->ipc_type, out_profile);

return ret;
}
EXPORT_SYMBOL(sof_create_ipc_file_profile);

0 comments on commit d14d318

Please sign in to comment.