diff --git a/include/sound/sof.h b/include/sound/sof.h index d3c41f87ac3191..87186f4647af50 100644 --- a/include/sound/sof.h +++ b/include/sound/sof.h @@ -57,6 +57,17 @@ enum sof_ipc_type { SOF_IPC_TYPE_COUNT }; +struct sof_loadable_file_profile { + enum sof_ipc_type ipc_type; + + const char *fw_path; + const char *fw_name; + const char *fw_lib_path; + const char *fw_path_postfix; + const char *tplg_path; + const char *tplg_name; +}; + /* * SOF Platform data. */ @@ -78,6 +89,9 @@ struct snd_sof_pdata { /* descriptor */ const struct sof_dev_desc *desc; + /* platform's preferred IPC type and path overrides */ + struct sof_loadable_file_profile ipc_file_profile_base; + /* firmware and topology filenames */ const char *fw_filename_prefix; const char *fw_filename; diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 308d8763991636..6c9b775fbd985f 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -1,7 +1,8 @@ # SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ - control.o trace.o iomem-utils.o sof-audio.o stream-ipc.o + control.o trace.o iomem-utils.o sof-audio.o stream-ipc.o\ + fw-file-profile.o # IPC implementations ifneq ($(CONFIG_SND_SOC_SOF_IPC3),) diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 9a9d82220fd0d7..4a589ea4c1a7e5 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -349,6 +349,35 @@ static void sof_probe_work(struct work_struct *work) } } +static int sof_select_ipc_and_paths(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + struct sof_loadable_file_profile *base_profile = &plat_data->ipc_file_profile_base; + struct sof_loadable_file_profile out_profile; + int ret; + + /* check IPC support */ + if (!(BIT(base_profile->ipc_type) & plat_data->desc->ipc_supported_mask)) { + dev_err(sdev->dev, + "ipc_type %d is not supported on this platform, mask is %#x\n", + base_profile->ipc_type, plat_data->desc->ipc_supported_mask); + return -EINVAL; + } + + ret = sof_create_ipc_file_profile(sdev, base_profile, &out_profile); + if (ret) + return ret; + + plat_data->ipc_type = out_profile.ipc_type; + plat_data->fw_filename = out_profile.fw_name; + plat_data->fw_filename_prefix = out_profile.fw_path; + plat_data->fw_lib_prefix = out_profile.fw_lib_path; + plat_data->tplg_filename_prefix = out_profile.tplg_path; + plat_data->tplg_filename = out_profile.tplg_name; + + return 0; +} + int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) { struct snd_sof_dev *sdev; @@ -380,12 +409,9 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) } } - /* check IPC support */ - if (!(BIT(plat_data->ipc_type) & plat_data->desc->ipc_supported_mask)) { - dev_err(dev, "ipc_type %d is not supported on this platform, mask is %#x\n", - plat_data->ipc_type, plat_data->desc->ipc_supported_mask); - return -EINVAL; - } + ret = sof_select_ipc_and_paths(sdev); + if (ret) + return ret; /* init ops, if necessary */ ret = sof_ops_init(sdev); diff --git a/sound/soc/sof/fw-file-profile.c b/sound/soc/sof/fw-file-profile.c new file mode 100644 index 00000000000000..0f97f01ba38dd8 --- /dev/null +++ b/sound/soc/sof/fw-file-profile.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2023 Intel Corporation. All rights reserved. +// + +#include +#include +#include +#include "sof-priv.h" + +static int sof_test_file_profile(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_file_profile_for_ipc_type(struct device *dev, + 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) +{ + bool fw_path_allocated = false; + bool fw_lib_path_allocated = false; + int ret = 0; + + /* firmware path */ + if (base_profile->fw_path) { + out_profile->fw_path = base_profile->fw_path; + } else if (base_profile->fw_path_postfix) { + out_profile->fw_path = devm_kasprintf(dev, GFP_KERNEL, "%s/%s", + desc->default_fw_path[ipc_type], + base_profile->fw_path_postfix); + if (!out_profile->fw_path) + return -ENOMEM; + + fw_path_allocated = true; + } else { + out_profile->fw_path = desc->default_fw_path[ipc_type]; + } + + /* firmware filename */ + if (base_profile->fw_name) + out_profile->fw_name = base_profile->fw_name; + 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_file_profile(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) { + out_profile->fw_lib_path = base_profile->fw_lib_path; + } else if (desc->default_lib_path[ipc_type]) { + if (base_profile->fw_path_postfix) { + out_profile->fw_lib_path = devm_kasprintf(dev, + GFP_KERNEL, "%s/%s", + desc->default_lib_path[ipc_type], + base_profile->fw_path_postfix); + if (!out_profile->fw_lib_path) { + ret = -ENOMEM; + goto out; + } + + fw_lib_path_allocated = true; + } else { + out_profile->fw_lib_path = desc->default_lib_path[ipc_type]; + } + } + + if (base_profile->fw_path_postfix) + out_profile->fw_path_postfix = base_profile->fw_path_postfix; + + /* topology path */ + if (base_profile->tplg_path) + out_profile->tplg_path = base_profile->tplg_path; + else + out_profile->tplg_path = desc->default_tplg_path[ipc_type]; + + /* topology name */ + if (base_profile->tplg_name) + out_profile->tplg_name = base_profile->tplg_name; + + out_profile->ipc_type = ipc_type; + + /* Test only default firmware file */ + if (!base_profile->fw_path && !base_profile->fw_name) + ret = sof_test_file_profile(dev, out_profile, NULL); + +out: + if (ret) { + /* Free up path strings created with devm_kasprintf */ + if (fw_path_allocated) + devm_kfree(dev, out_profile->fw_path); + if (fw_lib_path_allocated) + devm_kfree(dev, out_profile->fw_lib_path); + + memset(out_profile, 0, sizeof(*out_profile)); + } + + return ret; +} + +static void +sof_missing_firmware_notification(struct device *dev, + enum sof_ipc_type ipc_type, + const struct sof_dev_desc *desc, + struct sof_loadable_file_profile *base_profile) +{ + char *marker; + int i; + + dev_err(dev, "No SOF firmware file was 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"; + + if (base_profile->fw_path_postfix) + dev_err(dev, " %s file: %s/%s/%s\n", marker, + desc->default_fw_path[i], + base_profile->fw_path_postfix, + desc->default_fw_filename[i]); + else + dev_err(dev, " %s file: %s/%s\n", marker, + desc->default_fw_path[i], + desc->default_fw_filename[i]); + } + + dev_err(dev, "Check if you have 'sof-firmware' package installed.\n"); + dev_err(dev, "Optionally it can be manually downloaded from:\n"); + dev_err(dev, " https://github.com/thesofproject/sof-bin/\n"); +} + +static void sof_print_profile_info(struct device *dev, + enum sof_ipc_type ipc_type, + struct sof_loadable_file_profile *profile) +{ + 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%s for ipc type %d:\n", + ipc_type != profile->ipc_type ? " (fallback)" : "", + profile->ipc_type); + + dev_dbg(dev, " Firmware file: %s/%s\n", profile->fw_path, profile->fw_name); + if (profile->fw_lib_path) + dev_dbg(dev, " Firmware lib path: %s\n", profile->fw_lib_path); + dev_dbg(dev, " Topology path: %s\n", profile->tplg_path); + if (profile->tplg_name) + dev_dbg(dev, " Topology name: %s\n", profile->tplg_name); +} + +int sof_create_ipc_file_profile(struct snd_sof_dev *sdev, + struct sof_loadable_file_profile *base_profile, + struct sof_loadable_file_profile *out_profile) +{ + const struct sof_dev_desc *desc = sdev->pdata->desc; + struct device *dev = sdev->dev; + int ret = -ENOENT; + int i; + + memset(out_profile, 0, sizeof(*out_profile)); + + if (base_profile->fw_path) + dev_dbg(dev, "Module parameter used, changed fw path to %s\n", + base_profile->fw_path); + else if (base_profile->fw_path_postfix) + dev_dbg(dev, "Path postfix appended to default fw path: %s\n", + base_profile->fw_path_postfix); + + if (base_profile->fw_lib_path) + dev_dbg(dev, "Module parameter used, changed fw_lib path to %s\n", + base_profile->fw_lib_path); + else if (base_profile->fw_path_postfix) + dev_dbg(dev, "Path postfix appended to default fw_lib path: %s\n", + base_profile->fw_path_postfix); + + if (base_profile->fw_name) + dev_dbg(dev, "Module parameter used, changed fw filename to %s\n", + base_profile->fw_name); + + if (base_profile->tplg_path) + dev_dbg(dev, "Module parameter used, changed tplg path to %s\n", + base_profile->tplg_path); + + if (base_profile->tplg_name) + dev_dbg(dev, "Module parameter used, changed tplg name to %s\n", + base_profile->tplg_name); + + ret = sof_file_profile_for_ipc_type(dev, base_profile->ipc_type, desc, + base_profile, out_profile); + if (!ret) + goto out; + + /* + * 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; + + ret = sof_file_profile_for_ipc_type(dev, i, desc, base_profile, + out_profile); + if (!ret) + break; + } + +out: + if (ret) + sof_missing_firmware_notification(dev, base_profile->ipc_type, + desc, base_profile); + else + sof_print_profile_info(dev, base_profile->ipc_type, out_profile); + + return ret; +} +EXPORT_SYMBOL(sof_create_ipc_file_profile); diff --git a/sound/soc/sof/sof-acpi-dev.c b/sound/soc/sof/sof-acpi-dev.c index 1b04dcb332935f..e4b61f08202c65 100644 --- a/sound/soc/sof/sof-acpi-dev.c +++ b/sound/soc/sof/sof-acpi-dev.c @@ -74,20 +74,10 @@ int sof_acpi_probe(struct platform_device *pdev, const struct sof_dev_desc *desc sof_pdata->desc = desc; sof_pdata->dev = &pdev->dev; - sof_pdata->fw_filename = desc->default_fw_filename[SOF_IPC]; - - /* alternate fw and tplg filenames ? */ - if (fw_path) - sof_pdata->fw_filename_prefix = fw_path; - else - sof_pdata->fw_filename_prefix = - sof_pdata->desc->default_fw_path[SOF_IPC]; - - if (tplg_path) - sof_pdata->tplg_filename_prefix = tplg_path; - else - sof_pdata->tplg_filename_prefix = - sof_pdata->desc->default_tplg_path[SOF_IPC]; + + sof_pdata->ipc_file_profile_base.ipc_type = desc->ipc_default; + sof_pdata->ipc_file_profile_base.fw_path = fw_path; + sof_pdata->ipc_file_profile_base.tplg_path = tplg_path; /* set callback to be called on successful device probe to enable runtime_pm */ sof_pdata->sof_probe_complete = sof_acpi_probe_complete; diff --git a/sound/soc/sof/sof-of-dev.c b/sound/soc/sof/sof-of-dev.c index 53faeccedd4f0d..54715b7e059b05 100644 --- a/sound/soc/sof/sof-of-dev.c +++ b/sound/soc/sof/sof-of-dev.c @@ -64,17 +64,10 @@ int sof_of_probe(struct platform_device *pdev) sof_pdata->desc = desc; sof_pdata->dev = &pdev->dev; - sof_pdata->fw_filename = desc->default_fw_filename[SOF_IPC]; - if (fw_path) - sof_pdata->fw_filename_prefix = fw_path; - else - sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path[SOF_IPC]; - - if (tplg_path) - sof_pdata->tplg_filename_prefix = tplg_path; - else - sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path[SOF_IPC]; + sof_pdata->ipc_file_profile_base.ipc_type = desc->ipc_default; + sof_pdata->ipc_file_profile_base.fw_path = fw_path; + sof_pdata->ipc_file_profile_base.tplg_path = tplg_path; /* set callback to be called on successful device probe to enable runtime_pm */ sof_pdata->sof_probe_complete = sof_of_probe_complete; diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c index f5ece43d0ec247..3ecc868bcdf5f2 100644 --- a/sound/soc/sof/sof-pci-dev.c +++ b/sound/soc/sof/sof-pci-dev.c @@ -183,6 +183,7 @@ static void sof_pci_probe_complete(struct device *dev) int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { + struct sof_loadable_file_profile *path_override; struct device *dev = &pci->dev; const struct sof_dev_desc *desc = (const struct sof_dev_desc *)pci_id->driver_data; @@ -217,10 +218,10 @@ int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) sof_pdata->desc = desc; sof_pdata->dev = dev; - sof_pdata->ipc_type = desc->ipc_default; + path_override = &sof_pdata->ipc_file_profile_base; if (sof_pci_ipc_type < 0) { - sof_pdata->ipc_type = desc->ipc_default; + path_override->ipc_type = desc->ipc_default; } else { dev_info(dev, "overriding default IPC %d to requested %d\n", desc->ipc_default, sof_pci_ipc_type); @@ -235,88 +236,32 @@ int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) ret = -EINVAL; goto out; } - sof_pdata->ipc_type = sof_pci_ipc_type; + path_override->ipc_type = sof_pci_ipc_type; } - if (fw_filename) { - sof_pdata->fw_filename = fw_filename; - - dev_dbg(dev, "Module parameter used, changed fw filename to %s\n", - sof_pdata->fw_filename); - } else { - sof_pdata->fw_filename = desc->default_fw_filename[sof_pdata->ipc_type]; - } - - /* - * for platforms using the SOF community key, change the - * default path automatically to pick the right files from the - * linux-firmware tree. This can be overridden with the - * fw_path kernel parameter, e.g. for developers. - */ - - /* alternate fw and tplg filenames ? */ - if (fw_path) { - sof_pdata->fw_filename_prefix = fw_path; - - dev_dbg(dev, - "Module parameter used, changed fw path to %s\n", - sof_pdata->fw_filename_prefix); - - } else if (dmi_check_system(community_key_platforms) && sof_dmi_use_community_key) { - sof_pdata->fw_filename_prefix = - devm_kasprintf(dev, GFP_KERNEL, "%s/%s", - sof_pdata->desc->default_fw_path[sof_pdata->ipc_type], - "community"); - - dev_dbg(dev, - "Platform uses community key, changed fw path to %s\n", - sof_pdata->fw_filename_prefix); - } else { - sof_pdata->fw_filename_prefix = - sof_pdata->desc->default_fw_path[sof_pdata->ipc_type]; - } - - if (lib_path) { - sof_pdata->fw_lib_prefix = lib_path; - - dev_dbg(dev, "Module parameter used, changed fw_lib path to %s\n", - sof_pdata->fw_lib_prefix); - - } else if (sof_pdata->desc->default_lib_path[sof_pdata->ipc_type]) { - if (dmi_check_system(community_key_platforms) && sof_dmi_use_community_key) { - sof_pdata->fw_lib_prefix = - devm_kasprintf(dev, GFP_KERNEL, "%s/%s", - sof_pdata->desc->default_lib_path[sof_pdata->ipc_type], - "community"); - - dev_dbg(dev, - "Platform uses community key, changed fw_lib path to %s\n", - sof_pdata->fw_lib_prefix); - } else { - sof_pdata->fw_lib_prefix = - sof_pdata->desc->default_lib_path[sof_pdata->ipc_type]; - } + path_override->fw_path = fw_path; + path_override->fw_name = fw_filename; + path_override->fw_lib_path = lib_path; + path_override->tplg_path = tplg_path; + + if (dmi_check_system(community_key_platforms) && + sof_dmi_use_community_key) { + path_override->fw_path_postfix = devm_kstrdup(dev, "community", + GFP_KERNEL); + if (!path_override->fw_path_postfix) + return -ENOMEM; } - if (tplg_path) - sof_pdata->tplg_filename_prefix = tplg_path; - else - sof_pdata->tplg_filename_prefix = - sof_pdata->desc->default_tplg_path[sof_pdata->ipc_type]; - /* * the topology filename will be provided in the machine descriptor, unless * it is overridden by a module parameter or DMI quirk. */ if (tplg_filename) { - sof_pdata->tplg_filename = tplg_filename; - - dev_dbg(dev, "Module parameter used, changed tplg filename to %s\n", - sof_pdata->tplg_filename); + path_override->tplg_name = tplg_filename; } else { dmi_check_system(sof_tplg_table); if (sof_dmi_override_tplg_name) - sof_pdata->tplg_filename = sof_dmi_override_tplg_name; + path_override->tplg_name = sof_dmi_override_tplg_name; } /* set callback to be called on successful device probe to enable runtime_pm */ diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index cd4f6ac126eca7..9fed1c1d1b56be 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -690,6 +690,13 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev); */ extern struct snd_compress_ops sof_compressed_ops; +/* + * Firmware (firmware, libraries, topologies) file location + */ +int sof_create_ipc_file_profile(struct snd_sof_dev *sdev, + struct sof_loadable_file_profile *base_profile, + struct sof_loadable_file_profile *out_profile); + /* * Firmware loading. */