[PATCH v3 14/17] ASoC: Intel: avs: General code loading flow

Ranjani Sridharan ranjani.sridharan at linux.intel.com
Fri Mar 4 17:54:55 CET 2022


On Fri, 2022-03-04 at 15:57 +0100, Cezary Rojewski wrote:
> Code loading is a complex procedure and requires combined effort of
> DMA
> and IPCs. With IPCs already in place, lay out ground for specific DMA
> transfer operations.
> 
> Signed-off-by: Amadeusz Sławiński <
> amadeuszx.slawinski at linux.intel.com>
> Signed-off-by: Cezary Rojewski <cezary.rojewski at intel.com>
> ---
>  sound/soc/intel/avs/Makefile    |   2 +-
>  sound/soc/intel/avs/avs.h       |  18 +++
>  sound/soc/intel/avs/core.c      |  62 +++++++++
>  sound/soc/intel/avs/dsp.c       |  26 ++++
>  sound/soc/intel/avs/loader.c    | 237
> ++++++++++++++++++++++++++++++++
>  sound/soc/intel/avs/registers.h |   6 +
>  6 files changed, 350 insertions(+), 1 deletion(-)
>  create mode 100644 sound/soc/intel/avs/core.c
>  create mode 100644 sound/soc/intel/avs/loader.c
> 
> diff --git a/sound/soc/intel/avs/Makefile
> b/sound/soc/intel/avs/Makefile
> index d9f92c5f5407..d9c793160612 100644
> --- a/sound/soc/intel/avs/Makefile
> +++ b/sound/soc/intel/avs/Makefile
> @@ -1,5 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0-only
>  
> -snd-soc-avs-objs := dsp.o ipc.o messages.o utils.o
> +snd-soc-avs-objs := dsp.o ipc.o messages.o utils.o core.o loader.o
>  
>  obj-$(CONFIG_SND_SOC_INTEL_AVS) += snd-soc-avs.o
> diff --git a/sound/soc/intel/avs/avs.h b/sound/soc/intel/avs/avs.h
> index 0034c075fa64..2527d6170417 100644
> --- a/sound/soc/intel/avs/avs.h
> +++ b/sound/soc/intel/avs/avs.h
> @@ -10,8 +10,11 @@
>  #define __SOUND_SOC_INTEL_AVS_H
>  
>  #include <linux/device.h>
> +#include <linux/firmware.h>
>  #include <sound/hda_codec.h>
> +#include <sound/hda_register.h>
>  #include "messages.h"
> +#include "registers.h"
>  
>  struct avs_dev;
>  
> @@ -32,6 +35,10 @@ struct avs_dsp_ops {
>  	irqreturn_t (* const irq_handler)(int, void *);
>  	irqreturn_t (* const irq_thread)(int, void *);
>  	void (* const int_control)(struct avs_dev *, bool);
> +	int (* const load_basefw)(struct avs_dev *, struct firmware *);
> +	int (* const load_lib)(struct avs_dev *, struct firmware *,
> u32);
> +	int (* const transfer_mods)(struct avs_dev *, bool,
> +				    struct avs_module_entry *, u32);
>  };
>  
>  #define avs_dsp_op(adev, op, ...) \
> @@ -45,6 +52,7 @@ struct avs_spec {
>  	const char *name;
>  
>  	const struct avs_dsp_ops *const dsp_ops;
> +	struct avs_fw_version min_fw_version; /* anything below is
> rejected */
>  
>  	const u32 core_init_mask;	/* used during DSP boot */
>  	const u64 attributes;		/* bitmask of AVS_PLATATTR_*
> */
> @@ -90,6 +98,7 @@ struct avs_dev {
>  	struct ida ppl_ida;
>  	struct list_head fw_list;
>  	int *core_refs;
> +	char **lib_names;
>  
>  	struct completion fw_ready;
>  };
> @@ -215,4 +224,13 @@ int avs_dsp_create_pipeline(struct avs_dev
> *adev, u16 req_size, u8 priority,
>  			    bool lp, u16 attributes, u8 *instance_id);
>  int avs_dsp_delete_pipeline(struct avs_dev *adev, u8 instance_id);
>  
> +/* Firmware loading */
> +
> +void avs_hda_clock_gating_enable(struct avs_dev *adev, bool enable);
> +void avs_hda_power_gating_enable(struct avs_dev *adev, bool enable);
> +void avs_hda_l1sen_enable(struct avs_dev *adev, bool enable);
> +
> +int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge);
> +int avs_dsp_first_boot_firmware(struct avs_dev *adev);
> +
>  #endif /* __SOUND_SOC_INTEL_AVS_H */
> diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c
> new file mode 100644
> index 000000000000..117b31ef9cd0
> --- /dev/null
> +++ b/sound/soc/intel/avs/core.c
> @@ -0,0 +1,62 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +//
> +// Copyright(c) 2021 Intel Corporation. All rights reserved.
> +//
> +// Authors: Cezary Rojewski <cezary.rojewski at intel.com>
> +//          Amadeusz Slawinski <amadeuszx.slawinski at linux.intel.com>
> +//
> +// Special thanks to:
> +//    Krzysztof Hejmowski <krzysztof.hejmowski at intel.com>
> +//    Michal Sienkiewicz <michal.sienkiewicz at intel.com>
> +//    Filip Proborszcz
> +//
> +// for sharing Intel AudioDSP expertise and helping shape the very
> +// foundation of this driver
> +//
> +
> +#include <linux/pci.h>
> +#include <sound/hdaudio.h>
> +#include "avs.h"
> +
> +static void
> +avs_hda_update_config_dword(struct hdac_bus *bus, u32 reg, u32 mask,
> u32 value)
> +{
> +	struct pci_dev *pci = to_pci_dev(bus->dev);
> +	u32 data;
> +
> +	pci_read_config_dword(pci, reg, &data);
> +	data &= ~mask;
> +	data |= (value & mask);
> +	pci_write_config_dword(pci, reg, data);
> +}
> +
> +void avs_hda_power_gating_enable(struct avs_dev *adev, bool enable)
> +{
> +	u32 value;
> +
> +	value = enable ? 0 : AZX_PGCTL_LSRMD_MASK;
> +	avs_hda_update_config_dword(&adev->base.core, AZX_PCIREG_PGCTL,
> +				    AZX_PGCTL_LSRMD_MASK, value);
> +}
> +
> +static void avs_hdac_clock_gating_enable(struct hdac_bus *bus, bool
> enable)
> +{
> +	u32 value;
> +
> +	value = enable ? AZX_CGCTL_MISCBDCGE_MASK : 0;
> +	avs_hda_update_config_dword(bus, AZX_PCIREG_CGCTL,
> +				    AZX_CGCTL_MISCBDCGE_MASK, value);
> +}
> +
> +void avs_hda_clock_gating_enable(struct avs_dev *adev, bool enable)
> +{
> +	avs_hdac_clock_gating_enable(&adev->base.core, enable);
> +}
> +
> +void avs_hda_l1sen_enable(struct avs_dev *adev, bool enable)
> +{
> +	u32 value;
> +
> +	value = enable ? AZX_VS_EM2_L1SEN : 0;
> +	snd_hdac_chip_updatel(&adev->base.core, VS_EM2,
> AZX_VS_EM2_L1SEN, value);
> +}
> diff --git a/sound/soc/intel/avs/dsp.c b/sound/soc/intel/avs/dsp.c
> index 5994d64d2468..a434e9918c51 100644
> --- a/sound/soc/intel/avs/dsp.c
> +++ b/sound/soc/intel/avs/dsp.c
> @@ -198,6 +198,7 @@ int avs_dsp_init_module(struct avs_dev *adev, u16
> module_id, u8 ppl_instance_id,
>  			u16 *instance_id)
>  {
>  	struct avs_module_entry mentry;
> +	bool was_loaded = false;
>  	int ret, id;
>  
>  	id = avs_module_id_alloc(adev, module_id);
> @@ -212,6 +213,16 @@ int avs_dsp_init_module(struct avs_dev *adev,
> u16 module_id, u8 ppl_instance_id,
>  	if (ret)
>  		goto err_mod_entry;
>  
> +	/* Load code into memory if this is the first instance. */
> +	if (!id && !avs_module_entry_is_loaded(&mentry)) {
> +		ret = avs_dsp_op(adev, transfer_mods, true, &mentry,
> 1);
> +		if (ret) {
> +			dev_err(adev->dev, "load modules failed: %d\n",
> ret);
> +			goto err_mod_entry;
> +		}
> +		was_loaded = true;
> +	}
> +
>  	ret = avs_ipc_init_instance(adev, module_id, id,
> ppl_instance_id,
>  				    core_id, domain, param,
> param_size);
>  	if (ret) {
> @@ -223,6 +234,8 @@ int avs_dsp_init_module(struct avs_dev *adev, u16
> module_id, u8 ppl_instance_id,
>  	return 0;
>  
>  err_ipc:
> +	if (was_loaded)
> +		avs_dsp_op(adev, transfer_mods, false, &mentry, 1);
>  	avs_dsp_put_core(adev, core_id);
>  err_mod_entry:
>  	avs_module_id_free(adev, module_id, id);
> @@ -232,12 +245,25 @@ int avs_dsp_init_module(struct avs_dev *adev,
> u16 module_id, u8 ppl_instance_id,
>  void avs_dsp_delete_module(struct avs_dev *adev, u16 module_id, u16
> instance_id,
>  			   u8 ppl_instance_id, u8 core_id)
>  {
> +	struct avs_module_entry mentry;
> +	int ret;
> +
>  	/* Modules not owned by any pipeline need to be freed
> explicitly. */
>  	if (ppl_instance_id == INVALID_PIPELINE_ID)
>  		avs_ipc_delete_instance(adev, module_id, instance_id);
>  
>  	avs_module_id_free(adev, module_id, instance_id);
>  
> +	ret = avs_get_module_id_entry(adev, module_id, &mentry);
> +	/* Unload occupied memory if this was the last instance. */
> +	if (!ret && mentry.type.load_type ==
> AVS_MODULE_LOAD_TYPE_LOADABLE) {
> +		if (avs_is_module_ida_empty(adev, module_id)) {
> +			ret = avs_dsp_op(adev, transfer_mods, false,
> &mentry, 1);
> +			if (ret)
> +				dev_err(adev->dev, "unload modules
> failed: %d\n", ret);
> +		}
> +	}
> +
>  	avs_dsp_put_core(adev, core_id);
>  }
>  
> diff --git a/sound/soc/intel/avs/loader.c
> b/sound/soc/intel/avs/loader.c
> new file mode 100644
> index 000000000000..47e1f9a21e43
> --- /dev/null
> +++ b/sound/soc/intel/avs/loader.c
> @@ -0,0 +1,237 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +//
> +// Copyright(c) 2021 Intel Corporation. All rights reserved.
> +//
> +// Authors: Cezary Rojewski <cezary.rojewski at intel.com>
> +//          Amadeusz Slawinski <amadeuszx.slawinski at linux.intel.com>
> +//
> +
> +#include <linux/firmware.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include "avs.h"
> +#include "messages.h"
> +#include "registers.h"
> +
> +#define AVS_FW_INIT_TIMEOUT_MS		3000
> +
> +#define AVS_ROOT_DIR			"intel/avs"
> +#define AVS_BASEFW_FILENAME		"dsp_basefw.bin"
> +#define AVS_EXT_MANIFEST_MAGIC		0x31454124
> +#define SKL_MANIFEST_MAGIC		0x00000006
> +#define SKL_ADSPFW_OFFSET		0x284
> +
> +/* Occasionally, engineering (release candidate) firmware is
> provided for testing. */
> +static bool debug_ignore_fw_version;
> +module_param_named(ignore_fw_version, debug_ignore_fw_version, bool,
> 0444);
> +MODULE_PARM_DESC(ignore_fw_version, "Verify FW version 0=yes
> (default), 1=no");
> +
> +#define AVS_LIB_NAME_SIZE	8
> +
> +struct avs_fw_manifest {
> +	u32 id;
> +	u32 len;
> +	char name[AVS_LIB_NAME_SIZE];
> +	u32 preload_page_count;
> +	u32 img_flags;
> +	u32 feature_mask;
> +	struct avs_fw_version version;
> +} __packed;
> +
> +struct avs_fw_ext_manifest {
> +	u32 id;
> +	u32 len;
> +	u16 version_major;
> +	u16 version_minor;
> +	u32 entries;
> +} __packed;
> +
> +static int avs_fw_ext_manifest_strip(struct firmware *fw)
> +{
> +	struct avs_fw_ext_manifest *man;
> +
> +	if (fw->size < sizeof(*man))
> +		return -EINVAL;
> +
> +	man = (struct avs_fw_ext_manifest *)fw->data;
> +	if (man->id == AVS_EXT_MANIFEST_MAGIC) {
> +		fw->data += man->len;
> +		fw->size -= man->len;
> +	}
> +
> +	return 0;
> +}
> +
> +static int avs_fw_manifest_offset(struct firmware *fw)
> +{
> +	/* Header type found in first DWORD of fw binary. */
> +	u32 magic = *(u32 *)fw->data;
> +
> +	switch (magic) {
> +	case SKL_MANIFEST_MAGIC:
> +		return SKL_ADSPFW_OFFSET;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int avs_fw_manifest_strip_verify(struct avs_dev *adev, struct
> firmware *fw,
> +					const struct avs_fw_version
> *min)
> +{
> +	struct avs_fw_manifest *man;
> +	int offset, ret;
> +
> +	ret = avs_fw_ext_manifest_strip(fw);
> +	if (ret)
> +		return ret;
> +
> +	offset = avs_fw_manifest_offset(fw);
> +	if (offset < 0)
> +		return offset;
> +
> +	if (fw->size < offset + sizeof(*man))
> +		return -EINVAL;
> +	if (!min)
> +		return 0;
> +
> +	man = (struct avs_fw_manifest *)(fw->data + offset);
> +	if (man->version.major != min->major ||
> +	    man->version.minor != min->minor ||
> +	    man->version.hotfix != min->hotfix ||
> +	    man->version.build < min->build) {
Isnt this check a bit too strict? Isnt a check major enough?

> +		dev_warn(adev->dev, "bad FW version %d.%d.%d.%d,
> expected %d.%d.%d.%d or newer\n",
> +			 man->version.major, man->version.minor,
> +			 man->version.hotfix, man->version.build,
> +			 min->major, min->minor, min->hotfix, min-
> >build);
> +
> +		if (!debug_ignore_fw_version)
> +			return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int avs_dsp_load_basefw(struct avs_dev *adev)
> +{
> +	const struct avs_fw_version *min_req;
> +	const struct avs_spec *const spec = adev->spec;
> +	const struct firmware *fw;
> +	struct firmware stripped_fw;
> +	char *filename;
> +	int ret;
> +
> +	filename = kasprintf(GFP_KERNEL, "%s/%s/%s", AVS_ROOT_DIR,
> spec->name,
> +			     AVS_BASEFW_FILENAME);
> +	if (!filename)
> +		return -ENOMEM;
> +
> +	ret = avs_request_firmware(adev, &fw, filename);
> +	kfree(filename);
> +	if (ret < 0) {
> +		dev_err(adev->dev, "request firmware failed: %d\n",
> ret);
> +		return ret;
> +	}
> +
> +	stripped_fw = *fw;
> +	min_req = &adev->spec->min_fw_version;
> +
> +	ret = avs_fw_manifest_strip_verify(adev, &stripped_fw,
> min_req);
> +	if (ret < 0) {
> +		dev_err(adev->dev, "invalid firmware data: %d\n", ret);
> +		goto release_fw;
> +	}
> +
> +	ret = avs_dsp_op(adev, load_basefw, &stripped_fw);
> +	if (ret < 0) {
> +		dev_err(adev->dev, "basefw load failed: %d\n", ret);
> +		goto release_fw;
> +	}
> +
> +	ret = wait_for_completion_timeout(&adev->fw_ready,
> +					  msecs_to_jiffies(AVS_FW_INIT_
> TIMEOUT_MS));
> +	if (!ret) {
> +		dev_err(adev->dev, "firmware ready timeout\n");
> +		avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK);
> +		ret = -ETIMEDOUT;
> +		goto release_fw;
> +	}
> +
> +	return 0;
> +
> +release_fw:
> +	avs_release_last_firmware(adev);
> +	return ret;
> +}
> +
> +int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge)
> +{
> +	int ret, i;
> +
> +	/* Full boot, clear cached data except for basefw (slot 0). */
Does this mean IMR restore is only available for base FW and not for
module libraries? Do I understand this correctly?
> +	for (i = 1; i < adev->fw_cfg.max_libs_count; i++)
> +		memset(adev->lib_names[i], 0, AVS_LIB_NAME_SIZE);
> +
> +	avs_hda_clock_gating_enable(adev, false);
> +	avs_hda_l1sen_enable(adev, false);
> +
> +	ret = avs_dsp_load_basefw(adev);
> +
> +	avs_hda_l1sen_enable(adev, true);
> +	avs_hda_clock_gating_enable(adev, true);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	/* With all code loaded, refresh module information. */
> +	ret = avs_module_info_init(adev, true);
It is not clear if this required only after first boot or after a
suspend/resume as well.
Thanks,Ranjani



More information about the Alsa-devel mailing list