mbox series

[v3,00/17] ASoC: Intel: AVS - Audio DSP for cAVS

Message ID 20220304145755.2844173-1-cezary.rojewski@intel.com
Headers show
Series ASoC: Intel: AVS - Audio DSP for cAVS | expand

Message

Cezary Rojewski March 4, 2022, 2:57 p.m. UTC
A continuation of cleanup work of Intel SST solutions found in
sound/soc/intel/. With two major chapters released last year catpt [1]
and removal of haswell solution [2], time has come for Skylake-driver.

Througout 2019, 2020 and 2021 Skylake-driver has had many fixes applied
and even attempts of refactors as seen in fundamental overhaul [3], IPC
flow adjustments [4] and LARGE_CONFIG overhaul [5] series.
Unfortunately, story repeats itself - problems are found within the core
of a driver. Painting it with different colors does not change the fact
that is it still a house of cards. As changes needed to address those
issues would make Skylake solution incompatible with its previous
revisions, a decision has been made to provide a new solution instead.
In time it would deprecate and replace Skylake-driver.

That solution has been called AVS - from AudioDSP architecture name:
Audio-Voice-Speech. It is meant to provide support for the exact same
range of platforms as its predecessor: SKL, KBL, AML and APL.

Note: this series is dependent upon HDA-series [6] which exposes several
codec-organization functions allowing for reduced code size on
avs-driver side.

Note: this series does not add fully functional driver as its size would
get out of control. Here, focus is put on adding IPC protocol and code
loading code.


Changes v2 -> v3:
- fixed variable initialization errors mentioned by sparse


Changes v1 -> v2:
Almost all updates here are thanks to feedback from Pierre.

- several comments and few kernel-docs have been added in areas which
  felt more or less unclear
- avs_ipc_wait_busy_completion() now spins up to 'repeats_left' number
  of times before giving up
- 'adsp_ba' field of struct avs_dev has been renamed to 'dsp_ba'
- 'dops' field of struct avs_spec has been renamed to 'dsp_ops'
- IPC abstraction has been simplified: SKL_ADSP_REG_HIP* regs are used
  directly
- fixed allnoconfig with AVS enabled compilation issues
- fixed code loading error paths: previously requested firmware is now
  released before function return to the caller
- code and function arguments tied to D0IX support have been removed
  from this patchset and will be part of followup series adding that
  feature instead
- enriched dev_err() messages in avs_dsp_get_core() and
  avs_dsp_put_core()
- numerous wording fixes used in power/reset/stall DSP operations


Changes RFC v1 [7]: -> v1:
- separated HDA codec-organization patches, path and topology handling,
  PCM and complementary features such as recovery from this series to
  ease the review process
- fixed EXPORT_SYMBOL_GPL for exported members of ASoC framework
- result of stall() is now checked when sending ROM message
- result of snd_hdac_ext_stream_set_spib() is now checked when loading
  basefw
- if basefw is not ready, notification processing is now skipped
- documented several topology parsing helpers


Changes [internal] RFC v2 -> [public] RFC v1:
- dropped any sysfs related changes from this series, moved to follow up
  one
- dropped entire subscription-mechanism found in ipc.c. Handlers that
  are delegated to service certain firmware notifications are now called
  directly
- fixed kernel doc for snd_soc_dapm_new_dai_widgets() as reported by ikp
- prefixed snd_hda_codec_device_init() as suggested by Amadeo
- improved comments for d0ix transitions for APL-based platforms as
  suggested by Pierre
- a ton of spelling related fixes in most of the commit messages
- fixed remaining warnings pointed by scan-build (variable assigned but
  not used)
- replaced most of 'cAVS X.Y' expression usages with 'platform-based'
  equivalents as suggested by Pierre e.g.: cAVS 1.5 -> SKL-based


Changes [internal] RFC v1 -> [internal] RFC v2:
- fixed memleak caused by lack of kfree(vols) if memory allocation fails
  in avs_peakvol_create() as reported by Curtis
- fixed missing 'i' iterator incrementation in avs_widget_ready()
  causing reference loss as reported by Curtis
- replace hardcode: 0x40 usage with snd_hdac_calc_stream_format as
  suggested by Curtis.
  In consequence, readability for all code loading (CL) procedues has
  increased and such approach auto-documents the CL stream preparation

- updated behavior of all index-fetching functions found in utils.c:
  avs_module_entry_index(), avs_module_id_entry_index() and follow ups:
  avs_get_module_entry(), avs_get_module_id_entry() to better conform to
  linux-kernel standard when no entry is found (return -ENOENT) rather
  than C++ standard (return -1, what in kernel case translated to -EPERM)
  as suggested by Curtis and Peter
- several suggestions have been made regarding spacing, and so far, I've
  agreed and applied with all of them. None proposed seemed out of place
  or redundant

- avs_path_stop() renamed to avs_path_pause() pipeline states are
  represented by RESET/PAUSED/RUNNING. avs_path_reset() and
  avs_path_run() were already there and avs_path_stop() just didn't look
  cohesive
- added missing parsers for num_output_pin and num_input_pin which are
  required for custom modules such as WAVES or DSM
- dropped 'priv_param_length' from custom module descriptor as this
  field is obsolete in firmware

- parse_dictionary() has been split into parse_dictionary_header() and
  parse_dictionary_entries() to drop code duplication present in several
  parsing function which could not re-use entire parse_dictionary()
- added avs_tplg_vendor_array_lookup_next() and
  avs_tplg_vendor_entry_next() to drop code duplicated present in several
  parsing functions. This change greatly impacted readability of all
  parsers
- parsing helpers such avs_tplg_vendor_array_lookup() now return offset
  by updating specified in function argument list u32 *offset variable.
  This is to address problem when u32 offset would be greater than max
  int, which is the return type for these functions
- AVS_DEFINE_PTR_PARSER() macro has been introduced to drop code
  duplication for all ptr-parsing users

- all struct avs_path_module creators have had their declaration
  updated: function argument *owner ceased to exist as it could already
  be accessed by mod->owner

- fixed the order of operation for conditional paths (e.g.: echo
  reference) so these are no longer controlled by "source" path and
  instead are impacted by state changes of source and sink paths both.
  Previously only source path e.g. playback sourcing echo reference
  would trigger RUNNING status for conditional path. Equivalent RUNNING
  on WoV path which is in this case sink path, would not do so, leading
  to order-of-operation problems. Behavior has been changed to: both
  source and sink need to be RUNNING for conditional path to be set to
  RUNNING too. PAUSED for either source or sink will cause PAUSED
  transition for conditional path.
- to achieve the above, path states are now saved in 'state' i.e. new
  u32 field for struct avs_path

- resigned from fw_filename field usage in favour of newly added
  tplg_filename for machine board descriptors as suggested by Pierre
- platform descriptor fields have had their names update better reflect
  their purpose as suggested by Pierre
- fixed comp_list missing locking when manipulated
- all message senders now accept request as pointer as suggeseted by
  Peter
- resigned of AZX_ usage for all ADSP-related registers, leaving them
  only for HOST memory space related operations
- fixed disable path for core DSP operations: power/reset/stall as
  reported by Peter

- safety when locking between received responses (reply vs notification)
  has been lowered as suggested by Pierre. Most usages are not performed
  in IRQ context and none is done in hard-IRQ one
- s/master/main/ plus AVS_MAIN_CORE_MASK has replaced ->master_mask
- several functions have had their logging updated - logs have been
  moved to lower level functions as suggested by Pierre
- hdac_ext_stream usage has been streamlined to estream, hdac_streams
  are represented by hstream instead
- hw_params() are resilient to scenarios when they are called mutliple
  times as reported by Pierre
- avs_dsp_enable() now collapses if any of its steps fails as reported
  by Pierre and Peter
- avs_module_ida_empty() now returns value of type bool as suggested by
  Bard


[1]: https://www.spinics.net/lists/alsa-devel/msg116440.html
[2]: https://www.spinics.net/lists/alsa-devel/msg116901.html
[3]: https://www.spinics.net/lists/alsa-devel/msg94199.html
[4]: https://www.spinics.net/lists/alsa-devel/msg92588.html
[5]: https://lore.kernel.org/all/20190808181549.12521-1-cezary.rojewski@intel.com/
[6]: https://lore.kernel.org/alsa-devel/20220207114906.3759800-1-cezary.rojewski@intel.com/T/#t
[7]: https://lore.kernel.org/all/20211208111301.1817725-1-cezary.rojewski@intel.com/


Cezary Rojewski (17):
  ALSA: hda: Add helper macros for DSP capable devices
  ASoC: Export DAI register and widget ctor and dctor functions
  ASoC: Intel: Introduce AVS driver
  ASoC: Intel: avs: Inter process communication
  ASoC: Intel: avs: Add code loading requests
  ASoC: Intel: avs: Add pipeline management requests
  ASoC: Intel: avs: Add module management requests
  ASoC: Intel: avs: Add power management requests
  ASoC: Intel: avs: Add ROM requests
  ASoC: Intel: avs: Add basefw runtime-parameter requests
  ASoC: Intel: avs: Firmware resources management utilities
  ASoC: Intel: avs: Declare module configuration types
  ASoC: Intel: avs: Dynamic firmware resources management
  ASoC: Intel: avs: General code loading flow
  ASoC: Intel: avs: Implement CLDMA transfer
  ASoC: Intel: avs: Code loading over CLDMA
  ASoC: Intel: avs: Code loading over HDA

 include/sound/hdaudio.h         |   2 +
 include/sound/hdaudio_ext.h     |  50 +++
 include/sound/soc-dapm.h        |   1 +
 sound/soc/intel/Kconfig         |  12 +
 sound/soc/intel/Makefile        |   1 +
 sound/soc/intel/avs/Makefile    |   6 +
 sound/soc/intel/avs/avs.h       | 248 +++++++++++
 sound/soc/intel/avs/cldma.c     | 317 ++++++++++++++
 sound/soc/intel/avs/cldma.h     |  29 ++
 sound/soc/intel/avs/core.c      |  62 +++
 sound/soc/intel/avs/dsp.c       | 303 +++++++++++++
 sound/soc/intel/avs/ipc.c       | 393 +++++++++++++++++
 sound/soc/intel/avs/loader.c    | 607 +++++++++++++++++++++++++
 sound/soc/intel/avs/messages.c  | 697 +++++++++++++++++++++++++++++
 sound/soc/intel/avs/messages.h  | 753 ++++++++++++++++++++++++++++++++
 sound/soc/intel/avs/registers.h |  75 ++++
 sound/soc/intel/avs/utils.c     | 301 +++++++++++++
 sound/soc/soc-core.c            |   1 +
 sound/soc/soc-dapm.c            |  15 +
 19 files changed, 3873 insertions(+)
 create mode 100644 sound/soc/intel/avs/Makefile
 create mode 100644 sound/soc/intel/avs/avs.h
 create mode 100644 sound/soc/intel/avs/cldma.c
 create mode 100644 sound/soc/intel/avs/cldma.h
 create mode 100644 sound/soc/intel/avs/core.c
 create mode 100644 sound/soc/intel/avs/dsp.c
 create mode 100644 sound/soc/intel/avs/ipc.c
 create mode 100644 sound/soc/intel/avs/loader.c
 create mode 100644 sound/soc/intel/avs/messages.c
 create mode 100644 sound/soc/intel/avs/messages.h
 create mode 100644 sound/soc/intel/avs/registers.h
 create mode 100644 sound/soc/intel/avs/utils.c

Comments

Ranjani Sridharan March 4, 2022, 4:09 p.m. UTC | #1
On Fri, 2022-03-04 at 15:57 +0100, Cezary Rojewski wrote:
> Implement the IPC between Intel audio firmware and kernel driver. The
> IPC allows transmission of requests, handling of responses as well as
> unsolicited (i.e. firmware-generated) notifications.
> 
> A subscription mechanism is added to enable different parts of the
> driver to register for specific notifications.
> 
> The part of the DSP boot process that involves sending ROM message
> requires an extra step - must be followed by unstall operation of
> MAIN_CORE. All other types of messages do not require such specific
> handling, so separate set of functions is provided for sending these.
> 
> Signed-off-by: Amadeusz Sławiński <
> amadeuszx.slawinski@linux.intel.com>
> Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
> ---
>  sound/soc/intel/avs/Makefile    |   2 +-
>  sound/soc/intel/avs/avs.h       | 100 +++++++++
>  sound/soc/intel/avs/ipc.c       | 387
> ++++++++++++++++++++++++++++++++
>  sound/soc/intel/avs/messages.h  | 170 ++++++++++++++
>  sound/soc/intel/avs/registers.h |  45 ++++
>  5 files changed, 703 insertions(+), 1 deletion(-)
>  create mode 100644 sound/soc/intel/avs/ipc.c
>  create mode 100644 sound/soc/intel/avs/messages.h
> 
> diff --git a/sound/soc/intel/avs/Makefile
> b/sound/soc/intel/avs/Makefile
> index 5f7976a95fe2..e243806dd38a 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
> +snd-soc-avs-objs := dsp.o ipc.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 d4e6310e4bf7..841b8541b101 100644
> --- a/sound/soc/intel/avs/avs.h
> +++ b/sound/soc/intel/avs/avs.h
> @@ -11,13 +11,27 @@
>  
>  #include <linux/device.h>
>  #include <sound/hda_codec.h>
> +#include "messages.h"
>  
>  struct avs_dev;
>  
> +/*
> + * struct avs_dsp_ops - Platform-specific DSP operations
> + *
> + * @power: Power on or off DSP cores
> + * @reset: Enter or exit reset state on DSP cores
> + * @stall: Stall or run DSP cores
> + * @irq_handler: Top half of IPC servicing
> + * @irq_thread: Bottom half of IPC servicing
> + * @int_control: Enable or disable IPC interrupts
> + */
>  struct avs_dsp_ops {
>  	int (* const power)(struct avs_dev *, u32, bool);
>  	int (* const reset)(struct avs_dev *, u32, bool);
>  	int (* const stall)(struct avs_dev *, u32, bool);
> +	irqreturn_t (* const irq_handler)(int, void *);
> +	irqreturn_t (* const irq_thread)(int, void *);
> +	void (* const int_control)(struct avs_dev *, bool);
>  };
>  
>  #define avs_dsp_op(adev, op, ...) \
> @@ -34,6 +48,9 @@ struct avs_spec {
>  
>  	const u32 core_init_mask;	/* used during DSP boot */
>  	const u64 attributes;		/* bitmask of AVS_PLATATTR_*
> */
> +	const u32 sram_base_offset;
> +	const u32 sram_window_size;
> +	const u32 rom_status;
>  };
>  
>  /*
> @@ -49,6 +66,9 @@ struct avs_dev {
>  
>  	void __iomem *dsp_ba;
>  	const struct avs_spec *spec;
> +	struct avs_ipc *ipc;
> +
> +	struct completion fw_ready;
>  };
>  
>  /* from hda_bus to avs_dev */
> @@ -68,4 +88,84 @@ int avs_dsp_core_stall(struct avs_dev *adev, u32
> core_mask, bool stall);
>  int avs_dsp_core_enable(struct avs_dev *adev, u32 core_mask);
>  int avs_dsp_core_disable(struct avs_dev *adev, u32 core_mask);
>  
> +/* Inter Process Communication */
> +
> +struct avs_ipc_msg {
> +	union {
> +		u64 header;
> +		union avs_global_msg glb;
> +		union avs_reply_msg rsp;
> +	};
> +	void *data;
> +	size_t size;
> +};
> +
> +/*
> + * struct avs_ipc - DSP IPC context
> + *
> + * @dev: PCI device
> + * @rx: Reply message cache
> + * @default_timeout_ms: default message timeout in MS
> + * @ready: whether firmware is ready and communication is open
> + * @rx_completed: whether RX for previously sent TX has been
> received
> + * @rx_lock: for serializating manipulation of rx_* fields
> + * @msg_lock: for synchronizing request handling
> + * @done_completion: DONE-part of IPC i.e. ROM and ACKs from FW
> + * @busy_completion: BUSY-part of IPC i.e. receiving responses from
> FW
> + */
> +struct avs_ipc {
> +	struct device *dev;
> +
> +	struct avs_ipc_msg rx;
> +	u32 default_timeout_ms;
> +	bool ready;
> +
> +	bool rx_completed;
> +	spinlock_t rx_lock;
> +	struct mutex msg_mutex;
> +	struct completion done_completion;
> +	struct completion busy_completion;
> +};
> +
> +#define AVS_EIPC	EREMOTEIO
> +/*
> + * IPC handlers may return positive value (firmware error code) what
> denotes
> + * successful HOST <-> DSP communication yet failure to process
> specific request.
> + *
> + * Below macro converts returned value to linux kernel error code.
> + * All IPC callers MUST use it as soon as firmware error code is
> consumed.
> + */
> +#define AVS_IPC_RET(ret) \
> +	(((ret) <= 0) ? (ret) : -AVS_EIPC)
> +
> +static inline void avs_ipc_err(struct avs_dev *adev, struct
> avs_ipc_msg *tx,
> +			       const char *name, int error)
> +{
> +	/*
> +	 * If IPC channel is blocked e.g.: due to ongoing recovery,
Do you mean firmware recovery? In which cases do you perform a
recovery?
> +	 * -EPERM error code is expected and thus it's not an actual
> error.
And what happens in this case? do you retry the IPC after recovery?
> +	 */
> +	if (error == -EPERM)
> +		dev_dbg(adev->dev, "%s 0x%08x 0x%08x failed: %d\n",
> name,
> +			tx->glb.primary, tx->glb.ext.val, error);
> +	else
> +		dev_err(adev->dev, "%s 0x%08x 0x%08x failed: %d\n",
> name,
> +			tx->glb.primary, tx->glb.ext.val, error);
> +}
> +
> +irqreturn_t avs_dsp_irq_handler(int irq, void *dev_id);
> +irqreturn_t avs_dsp_irq_thread(int irq, void *dev_id);
> +void avs_dsp_process_response(struct avs_dev *adev, u64 header);
> +int avs_dsp_send_msg_timeout(struct avs_dev *adev,
> +			     struct avs_ipc_msg *request,
> +			     struct avs_ipc_msg *reply, int timeout);
> +int avs_dsp_send_msg(struct avs_dev *adev,
> +		     struct avs_ipc_msg *request, struct avs_ipc_msg
> *reply);
> +int avs_dsp_send_rom_msg_timeout(struct avs_dev *adev,
> +				 struct avs_ipc_msg *request, int
> timeout);
> +int avs_dsp_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg
> *request);
> +void avs_dsp_interrupt_control(struct avs_dev *adev, bool enable);
> +int avs_ipc_init(struct avs_ipc *ipc, struct device *dev);
> +void avs_ipc_block(struct avs_ipc *ipc);
> +
>  #endif /* __SOUND_SOC_INTEL_AVS_H */
> diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c
> new file mode 100644
> index 000000000000..c0722f8b195f
> --- /dev/null
> +++ b/sound/soc/intel/avs/ipc.c
> @@ -0,0 +1,387 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +//
> +// Copyright(c) 2021 Intel Corporation. All rights reserved.
> +//
> +// Authors: Cezary Rojewski <cezary.rojewski@intel.com>
> +//          Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>
> +//
> +
> +#include <linux/slab.h>
> +#include <sound/hdaudio_ext.h>
> +#include "avs.h"
> +#include "messages.h"
> +#include "registers.h"
> +
> +#define AVS_IPC_TIMEOUT_MS	300
> +
> +static void avs_dsp_receive_rx(struct avs_dev *adev, u64 header)
> +{
> +	struct avs_ipc *ipc = adev->ipc;
> +	union avs_reply_msg msg = AVS_MSG(header);
> +
> +	ipc->rx.header = header;
> +	/* Abort copying payload if request processing was
> unsuccessful. */
This seems misplaced? Why would you called this function is the status
showed an error?

> +	if (!msg.status)
> +		memcpy_fromio(ipc->rx.data, avs_uplink_addr(adev),
> +			      ipc->rx.size);
> +}
> +
> +static void avs_dsp_process_notification(struct avs_dev *adev, u64
> header)
> +{
> +	struct avs_notify_mod_data mod_data;
> +	union avs_notify_msg msg = AVS_MSG(header);
> +	size_t data_size = 0;
> +	void *data = NULL;
> +
> +	/* Ignore spurious notifications until handshake is
> established. */
> +	if (!adev->ipc->ready && msg.notify_msg_type !=
> AVS_NOTIFY_FW_READY) {
> +		dev_dbg(adev->dev, "FW not ready, skip notification:
> 0x%08x\n",
> +			msg.primary);
> +		return;
> +	}
> +
> +	/* Calculate notification payload size. */
> +	switch (msg.notify_msg_type) {
> +	case AVS_NOTIFY_FW_READY:
> +		break;
> +
> +	case AVS_NOTIFY_PHRASE_DETECTED:
> +		data_size = sizeof(struct avs_notify_voice_data);
> +		break;
> +
> +	case AVS_NOTIFY_RESOURCE_EVENT:
> +		data_size = sizeof(struct avs_notify_res_data);
> +		break;
> +
> +	case AVS_NOTIFY_MODULE_EVENT:
> +		/* To know the total payload size, header needs to be
> read first. */
> +		memcpy_fromio(&mod_data, avs_uplink_addr(adev),
> sizeof(mod_data));
> +		data_size = sizeof(mod_data) + mod_data.data_size;
> +		break;
> +
> +	default:
> +		dev_info(adev->dev, "unknown notification: 0x%08x\n",
> +			 msg.primary);
info? should it be a warning?
> +		break;
> +	}
> +
> +	if (data_size) {
> +		data = kmalloc(data_size, GFP_KERNEL);
> +		if (!data)
> +			return;
Should this function be modified to return the error? If it failed
here, all subsequent IPC's rec'd will also fail isnt it?
> +
> +		memcpy_fromio(data, avs_uplink_addr(adev), data_size);
> +	}
> +
> +	/* Perform notification-specific operations. */
> +	switch (msg.notify_msg_type) {
> +	case AVS_NOTIFY_FW_READY:
> +		dev_dbg(adev->dev, "FW READY 0x%08x\n", msg.primary);
> +		adev->ipc->ready = true;
> +		complete(&adev->fw_ready);
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	kfree(data);
You alloc memory for "data", copy the data and free it? Where is it
used?
> +}
> +
> +void avs_dsp_process_response(struct avs_dev *adev, u64 header)
> +{
> +	struct avs_ipc *ipc = adev->ipc;
> +
> +	/*
> +	 * Response may either be solicited - a reply for a request
> that has
> +	 * been sent beforehand - or unsolicited (notification).
> +	 */
> +	if (avs_msg_is_reply(header)) {
> +		/* Response processing is invoked from IRQ thread. */
> +		spin_lock_irq(&ipc->rx_lock);
> +		avs_dsp_receive_rx(adev, header);
> +		ipc->rx_completed = true;
> +		spin_unlock_irq(&ipc->rx_lock);
> +	} else {
> +		avs_dsp_process_notification(adev, header);
> +	}
> +
> +	complete(&ipc->busy_completion);
> +}
> +
> +irqreturn_t avs_dsp_irq_handler(int irq, void *dev_id)
> +{
> +	struct avs_dev *adev = dev_id;
> +	struct avs_ipc *ipc = adev->ipc;
> +	u32 adspis, hipc_rsp, hipc_ack;
> +	irqreturn_t ret = IRQ_NONE;
> +
> +	adspis = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPIS);
> +	if (adspis == UINT_MAX || !(adspis & AVS_ADSP_ADSPIS_IPC))
> +		return ret;
> +
> +	hipc_ack = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCIE);
> +	hipc_rsp = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCT);
> +
> +	/* DSP acked host's request */
> +	if (hipc_ack & SKL_ADSP_HIPCIE_DONE) {
> +		/*
> +		 * As an extra precaution, mask done interrupt. Code
> executed
> +		 * due to complete() found below does not assume any
> masking.
> +		 */
> +		snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL,
> +				      AVS_ADSP_HIPCCTL_DONE, 0);
> +
> +		complete(&ipc->done_completion);
> +
> +		/* tell DSP it has our attention */
> +		snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCIE,
> +				      SKL_ADSP_HIPCIE_DONE,
> +				      SKL_ADSP_HIPCIE_DONE);
> +		/* unmask done interrupt */
> +		snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL,
> +				      AVS_ADSP_HIPCCTL_DONE,
> +				      AVS_ADSP_HIPCCTL_DONE);
> +		ret = IRQ_HANDLED;
> +	}
> +
> +	/* DSP sent new response to process */
> +	if (hipc_rsp & SKL_ADSP_HIPCT_BUSY) {
> +		/* mask busy interrupt */
> +		snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL,
> +				      AVS_ADSP_HIPCCTL_BUSY, 0);
> +
> +		ret = IRQ_WAKE_THREAD;
> +	}
> +
> +	return ret;
> +}
> +
> +irqreturn_t avs_dsp_irq_thread(int irq, void *dev_id)
> +{
> +	struct avs_dev *adev = dev_id;
> +	union avs_reply_msg msg;
> +	u32 hipct, hipcte;
> +
> +	hipct = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCT);
> +	hipcte = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCTE);
> +
> +	/* ensure DSP sent new response to process */
> +	if (!(hipct & SKL_ADSP_HIPCT_BUSY))
> +		return IRQ_NONE;
> +
> +	msg.primary = hipct;
> +	msg.ext.val = hipcte;
> +	avs_dsp_process_response(adev, msg.val);
> +
> +	/* tell DSP we accepted its message */
> +	snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCT,
> +			      SKL_ADSP_HIPCT_BUSY,
> SKL_ADSP_HIPCT_BUSY);
> +	/* unmask busy interrupt */
> +	snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL,
> +			      AVS_ADSP_HIPCCTL_BUSY,
> AVS_ADSP_HIPCCTL_BUSY);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static bool avs_ipc_is_busy(struct avs_ipc *ipc)
> +{
> +	struct avs_dev *adev = to_avs_dev(ipc->dev);
> +	u32 hipc_rsp;
> +
> +	hipc_rsp = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCT);
> +	return hipc_rsp & SKL_ADSP_HIPCT_BUSY;
> +}
> +
> +static int avs_ipc_wait_busy_completion(struct avs_ipc *ipc, int
> timeout)
> +{
> +	u32 repeats_left = 128; /* to avoid infinite looping */
> +	int ret;
> +
> +again:
> +	ret = wait_for_completion_timeout(&ipc->busy_completion,
> +					  msecs_to_jiffies(timeout));
> +
> +	/* DSP could be unresponsive at this point. */
> +	if (!ipc->ready)
> +		return -EPERM;
> +
> +	if (!ret) {
> +		if (!avs_ipc_is_busy(ipc))
> +			return -ETIMEDOUT;
> +		/*
> +		 * Firmware did its job, either notification or reply
> +		 * has been received - now wait until it's processed.
> +		 */
> +		wait_for_completion_killable(&ipc->busy_completion);
> +	}
> +
> +	/* Ongoing notification's bottom-half may cause early wakeup */
> +	spin_lock(&ipc->rx_lock);
> +	if (!ipc->rx_completed) {
> +		if (repeats_left) {
> +			/* Reply delayed due to notification. */
> +			repeats_left--;
> +			reinit_completion(&ipc->busy_completion);
> +			spin_unlock(&ipc->rx_lock);
> +			goto again;
> +		}
> +
> +		spin_unlock(&ipc->rx_lock);
> +		return -ETIMEDOUT;
> +	}
> +
> +	spin_unlock(&ipc->rx_lock);
> +	return 0;
> +}
> +
> +static void avs_ipc_msg_init(struct avs_ipc *ipc, struct avs_ipc_msg
> *reply)
> +{
> +	lockdep_assert_held(&ipc->rx_lock);
> +
> +	ipc->rx.header = 0;
> +	ipc->rx.size = reply ? reply->size : 0;
> +	ipc->rx_completed = false;
> +
> +	reinit_completion(&ipc->done_completion);
> +	reinit_completion(&ipc->busy_completion);
> +}
> +
> +static void avs_dsp_send_tx(struct avs_dev *adev, struct avs_ipc_msg
> *tx)
send_tx? send and tx both imply the same isnt it? maybe just use one or
the other?
> +{
> +	tx->header |= SKL_ADSP_HIPCI_BUSY;
> +
> +	if (tx->size)
> +		memcpy_toio(avs_downlink_addr(adev), tx->data, tx-
> >size);
> +	snd_hdac_adsp_writel(adev, SKL_ADSP_REG_HIPCIE, tx->header >>
> 32);
> +	snd_hdac_adsp_writel(adev, SKL_ADSP_REG_HIPCI, tx->header &
> UINT_MAX);
> +}
> +
> +static int avs_dsp_do_send_msg(struct avs_dev *adev, struct
> avs_ipc_msg *request,
> +			       struct avs_ipc_msg *reply, int timeout)
> +{
> +	struct avs_ipc *ipc = adev->ipc;
> +	int ret;
> +
> +	if (!ipc->ready)
> +		return -EPERM;
> +
> +	mutex_lock(&ipc->msg_mutex);
> +
> +	spin_lock(&ipc->rx_lock);
> +	avs_ipc_msg_init(ipc, reply);
> +	avs_dsp_send_tx(adev, request);
> +	spin_unlock(&ipc->rx_lock);
> +
> +	ret = avs_ipc_wait_busy_completion(ipc, timeout);
> +	if (ret) {
> +		if (ret == -ETIMEDOUT) {
> +			dev_crit(adev->dev, "communication severed: %d,
> rebooting dsp..\n",
Where does this reboot happen?
Thanks,
Ranjani
Ranjani Sridharan March 4, 2022, 4:21 p.m. UTC | #2
On Fri, 2022-03-04 at 15:57 +0100, Cezary Rojewski wrote:
> Firmware modules implement processing algorithms. Their lifecycle,
> similarly to pipelines is being controlled by IPCs: initialization,
> deletion and (un)binding.
> 
> Modules can be configured at runtime - runtime parameters. This is
> done
> with help of LARGE_CONFIG IPCs: getter and setter.
> 
> Signed-off-by: Amadeusz Sławiński <
> amadeuszx.slawinski@linux.intel.com>
> Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
> ---
>  sound/soc/intel/avs/ipc.c      |   8 +-
>  sound/soc/intel/avs/messages.c | 262
> +++++++++++++++++++++++++++++++++
>  sound/soc/intel/avs/messages.h |  53 +++++++
>  3 files changed, 322 insertions(+), 1 deletion(-)
> 
> diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c
> index c0722f8b195f..9770368a898e 100644
> --- a/sound/soc/intel/avs/ipc.c
> +++ b/sound/soc/intel/avs/ipc.c
> @@ -21,9 +21,15 @@ static void avs_dsp_receive_rx(struct avs_dev
> *adev, u64 header)
>  
>  	ipc->rx.header = header;
>  	/* Abort copying payload if request processing was
> unsuccessful. */
> -	if (!msg.status)
> +	if (!msg.status) {
> +		/* update size in case of LARGE_CONFIG_GET */
> +		if (msg.msg_target == AVS_MOD_MSG &&
> +		    msg.global_msg_type == AVS_MOD_LARGE_CONFIG_GET)
> +			ipc->rx.size =
> msg.ext.large_config.data_off_size;
> +
>  		memcpy_fromio(ipc->rx.data, avs_uplink_addr(adev),
>  			      ipc->rx.size);
> +	}
>  }
>  
>  static void avs_dsp_process_notification(struct avs_dev *adev, u64
> header)
> diff --git a/sound/soc/intel/avs/messages.c
> b/sound/soc/intel/avs/messages.c
> index de2d50f8c6b4..613c9452226d 100644
> --- a/sound/soc/intel/avs/messages.c
> +++ b/sound/soc/intel/avs/messages.c
> @@ -6,6 +6,7 @@
>  //          Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>
>  //
>  
> +#include <linux/slab.h>
>  #include "avs.h"
>  #include "messages.h"
>  
> @@ -139,3 +140,264 @@ int avs_ipc_get_pipeline_state(struct avs_dev
> *adev, u8 instance_id,
>  	*state = reply.rsp.ext.get_ppl_state.state;
>  	return ret;
>  }
> +
> +/*
> + * avs_ipc_init_instance - Initialize module instance
> + *
> + * @adev: Driver context
> + * @module_id: Module-type id
> + * @instance_id: Unique module instance id
> + * @ppl_id: Parent pipeline id
> + * @core_id: DSP core to allocate module on
> + * @domain: Processing domain (low latency or data processing)
> + * @param: Module-type specific configuration
> + * @param_size: Size of @param in bytes
> + *
> + * Argument verification, as well as pipeline state checks are done
> by the
> + * firmware.
> + *
> + * Note: @ppl_id and @core_id are independent of each other as
> single pipeline
> + * can be composed of module instances located on different DSP
> cores.
> + */
> +int avs_ipc_init_instance(struct avs_dev *adev, u16 module_id, u8
> instance_id,
> +			  u8 ppl_id, u8 core_id, u8 domain,
> +			  void *param, u32 param_size)
> +{
> +	union avs_module_msg msg = AVS_MODULE_REQUEST(INIT_INSTANCE);
> +	struct avs_ipc_msg request;
> +	int ret;
> +
> +	msg.module_id = module_id;
> +	msg.instance_id = instance_id;
> +	/* firmware expects size provided in dwords */
> +	msg.ext.init_instance.param_block_size =
> +			DIV_ROUND_UP(param_size, sizeof(u32));
> +	msg.ext.init_instance.ppl_instance_id = ppl_id;
> +	msg.ext.init_instance.core_id = core_id;
> +	msg.ext.init_instance.proc_domain = domain;
> +
> +	request.header = msg.val;
> +	request.data = param;
> +	request.size = param_size;
> +
> +	ret = avs_dsp_send_msg(adev, &request, NULL);
> +	if (ret)
> +		avs_ipc_err(adev, &request, "init instance", ret);
> +
> +	return ret;
> +}
> +
> +/*
> + * avs_ipc_delete_instance - Delete module instance
> + *
> + * @adev: Driver context
> + * @module_id: Module-type id
> + * @instance_id: Unique module instance id
> + *
> + * Argument verification, as well as pipeline state checks are done
> by the
> + * firmware.
> + *
> + * Note: only standalone modules i.e. without a parent pipeline
> shall be
> + * deleted using this IPC message. In all other cases, pipeline
> owning the
> + * modules peforms cleanup automatically when it is deleted.
Can you please provide an example of such stand-alone modules? If they
aren't part of any pipeline, how do they get scheduled?

> + */
> +int avs_ipc_delete_instance(struct avs_dev *adev, u16 module_id, u8
> instance_id)
> +{
> +	union avs_module_msg msg = AVS_MODULE_REQUEST(DELETE_INSTANCE);
> +	struct avs_ipc_msg request = {{0}};
> +	int ret;
> +
> +	msg.module_id = module_id;
> +	msg.instance_id = instance_id;
> +	request.header = msg.val;
> +
> +	ret = avs_dsp_send_msg(adev, &request, NULL);
> +	if (ret)
> +		avs_ipc_err(adev, &request, "delete instance", ret);
> +
> +	return ret;
> +}
> +
> +/*
> + * avs_ipc_bind - Bind two module instances
> + *
> + * @adev: Driver context
> + * @module_id: Source module-type id
> + * @instance_id: Source module instance id
> + * @dst_module_id: Sink module-type id
> + * @dst_instance_id: Sink module instance id
> + * @dst_queue: Sink module pin to bind @src_queue with
> + * @src_queue: Source module pin to bind @dst_queue with
> + */
> +int avs_ipc_bind(struct avs_dev *adev, u16 module_id, u8
> instance_id,
> +		 u16 dst_module_id, u8 dst_instance_id,
> +		 u8 dst_queue, u8 src_queue)
> +{
> +	union avs_module_msg msg = AVS_MODULE_REQUEST(BIND);
> +	struct avs_ipc_msg request = {{0}};
> +	int ret;
> +
> +	msg.module_id = module_id;
> +	msg.instance_id = instance_id;
> +	msg.ext.bind_unbind.dst_module_id = dst_module_id;
> +	msg.ext.bind_unbind.dst_instance_id = dst_instance_id;
> +	msg.ext.bind_unbind.dst_queue = dst_queue;
> +	msg.ext.bind_unbind.src_queue = src_queue;
> +	request.header = msg.val;
> +
> +	ret = avs_dsp_send_msg(adev, &request, NULL);
> +	if (ret)
> +		avs_ipc_err(adev, &request, "bind modules", ret);
> +
> +	return ret;
> +}
> +
> +/*
> + * avs_ipc_unbind - Unbind two module instances
> + *
> + * @adev: Driver context
> + * @module_id: Source module-type id
> + * @instance_id: Source module instance id
> + * @dst_module_id: Sink module-type id
> + * @dst_instance_id: Sink module instance id
> + * @dst_queue: Sink module pin to unbind @src_queue from
> + * @src_queue: Source module pin to unbind @dst_queue from
> + */
Are there any rules for unbinding? For example if you have 2 modules
connected to a mixer? Can you unbind the module belonging to the host
pipeline that is getting stopped while the mixer is still active?

> +int avs_ipc_unbind(struct avs_dev *adev, u16 module_id, u8
> instance_id,
> +		   u16 dst_module_id, u8 dst_instance_id,
> +		   u8 dst_queue, u8 src_queue)
> +{
> +	union avs_module_msg msg = AVS_MODULE_REQUEST(UNBIND);
> +	struct avs_ipc_msg request = {{0}};
> +	int ret;
> +
> +	msg.module_id = module_id;
> +	msg.instance_id = instance_id;
> +	msg.ext.bind_unbind.dst_module_id = dst_module_id;
> +	msg.ext.bind_unbind.dst_instance_id = dst_instance_id;
> +	msg.ext.bind_unbind.dst_queue = dst_queue;
> +	msg.ext.bind_unbind.src_queue = src_queue;
> +	request.header = msg.val;
> +
> +	ret = avs_dsp_send_msg(adev, &request, NULL);
> +	if (ret)
> +		avs_ipc_err(adev, &request, "unbind modules", ret);
> +
> +	return ret;
> +}
> +
> +static int __avs_ipc_set_large_config(struct avs_dev *adev, u16
> module_id, u8 instance_id,
> +				      u8 param_id, bool init_block,
> bool final_block,
> +				      u8 *request_data, size_t
> request_size, size_t off_size)
> +{
> +	union avs_module_msg msg =
> AVS_MODULE_REQUEST(LARGE_CONFIG_SET);
> +	struct avs_ipc_msg request;
> +	int ret;
> +
> +	msg.module_id = module_id;
> +	msg.instance_id = instance_id;
> +	msg.ext.large_config.data_off_size = off_size;
> +	msg.ext.large_config.large_param_id = param_id;
> +	msg.ext.large_config.final_block = final_block;
> +	msg.ext.large_config.init_block = init_block;
> +
> +	request.header = msg.val;
> +	request.data = request_data;
> +	request.size = request_size;
> +
> +	ret = avs_dsp_send_msg(adev, &request, NULL);
> +	if (ret)
> +		avs_ipc_err(adev, &request, "large config set", ret);
> +
> +	return ret;
> +}
> +
> +int avs_ipc_set_large_config(struct avs_dev *adev, u16 module_id,
> +			     u8 instance_id, u8 param_id,
> +			     u8 *request, size_t request_size)
> +{
> +	size_t remaining, tx_size;
> +	bool final;
> +	int ret;
> +
> +	remaining = request_size;
> +	tx_size = min_t(size_t, AVS_MAILBOX_SIZE, remaining);
> +	final = (tx_size == remaining);
> +
> +	/* Initial request states total payload size. */
> +	ret = __avs_ipc_set_large_config(adev, module_id, instance_id,
> +					 param_id, 1, final, request,
> tx_size,
> +					 request_size);
> +	if (ret)
> +		return ret;
> +
> +	remaining -= tx_size;
> +
> +	/* Loop the rest only when payload exceeds mailbox's size. */
> +	while (remaining) {
> +		size_t offset;
> +
> +		offset = request_size - remaining;
> +		tx_size = min_t(size_t, AVS_MAILBOX_SIZE, remaining);
> +		final = (tx_size == remaining);
> +
> +		ret = __avs_ipc_set_large_config(adev, module_id,
> instance_id,
> +						 param_id, 0, final,
> +						 request + offset,
> tx_size,
> +						 offset);
> +		if (ret)
> +			return ret;
> +
> +		remaining -= tx_size;
> +	}
> +
> +	return 0;
> +}
> +
> +int avs_ipc_get_large_config(struct avs_dev *adev, u16 module_id, u8
> instance_id,
> +			     u8 param_id, u8 *request_data, size_t
> request_size,
> +			     u8 **reply_data, size_t *reply_size)
> +{
> +	union avs_module_msg msg =
> AVS_MODULE_REQUEST(LARGE_CONFIG_GET);
> +	struct avs_ipc_msg request;
> +	struct avs_ipc_msg reply = {{0}};
> +	size_t size;
> +	void *buf;
> +	int ret;
> +
> +	reply.data = kzalloc(AVS_MAILBOX_SIZE, GFP_KERNEL);
> +	if (!reply.data)
> +		return -ENOMEM;
> +
> +	msg.module_id = module_id;
> +	msg.instance_id = instance_id;
> +	msg.ext.large_config.data_off_size = request_size;
> +	msg.ext.large_config.large_param_id = param_id;
> +	/* final_block is always 0 on request. Updated by fw on reply.
> */
> +	msg.ext.large_config.final_block = 0;
> +	msg.ext.large_config.init_block = 1;
> +
> +	request.header = msg.val;
> +	request.data = request_data;
> +	request.size = request_size;
> +	reply.size = AVS_MAILBOX_SIZE;
> +
> +	ret = avs_dsp_send_msg(adev, &request, &reply);
> +	if (ret) {
> +		avs_ipc_err(adev, &request, "large config get", ret);
> +		kfree(reply.data);
> +		return ret;
> +	}
How come you dont have a loop here? What if the rec'd data size if
larger than the max size of IP payload?
Thanks,Ranjani
Ranjani Sridharan March 4, 2022, 4:47 p.m. UTC | #3
On Fri, 2022-03-04 at 15:57 +0100, Cezary Rojewski wrote:
> Wrap elementary DSP-core operations and resource control into more
> complex handlers. This is done to reduce the number of invocations of
> wrapped operations throughout the driver as order of operations
> matters -
> most flows involve register manipulation and IPCs combined.
> 
> Signed-off-by: Amadeusz Sławiński <
> amadeuszx.slawinski@linux.intel.com>
> Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
> ---
>  sound/soc/intel/avs/avs.h |  10 +++
>  sound/soc/intel/avs/dsp.c | 170
> ++++++++++++++++++++++++++++++++++++++
>  2 files changed, 180 insertions(+)
> 
> diff --git a/sound/soc/intel/avs/avs.h b/sound/soc/intel/avs/avs.h
> index 02d7591d0eac..0034c075fa64 100644
> --- a/sound/soc/intel/avs/avs.h
> +++ b/sound/soc/intel/avs/avs.h
> @@ -89,6 +89,7 @@ struct avs_dev {
>  	struct mutex modres_mutex;
>  	struct ida ppl_ida;
>  	struct list_head fw_list;
> +	int *core_refs;

Is this a per core ref_count? a comment or explicitly calling it
core_ref_count would help.

Thanks,Ranjani
Ranjani Sridharan March 4, 2022, 4:59 p.m. UTC | #4
On Fri, 2022-03-04 at 15:57 +0100, Cezary Rojewski wrote:
> Compared to SKL and KBL, younger cAVS platforms are meant to re-use
> one
Younger? you mean newer?
> of HDAudio streams during boot procedure causing CLDMA to become
> obsolete. Once transferred, given stream is returned to pool
> available
> for audio streaming.
> 
> Module loading handler is dummy as library and module code became
> inseparable in later firmware generations.
replace dummy with "stub" maybe? lets use inclusive terms
> 
> Signed-off-by: Amadeusz Sławiński <
> amadeuszx.slawinski@linux.intel.com>
> Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
> ---
>  sound/soc/intel/Kconfig      |   1 +
>  sound/soc/intel/avs/avs.h    |   5 +
>  sound/soc/intel/avs/loader.c | 211
> +++++++++++++++++++++++++++++++++++
>  3 files changed, 217 insertions(+)
> 
> diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig
> index e9768c4aa1a9..d025ca0c77fa 100644
> --- a/sound/soc/intel/Kconfig
> +++ b/sound/soc/intel/Kconfig
> @@ -215,6 +215,7 @@ config SND_SOC_INTEL_AVS
>  	depends on COMMON_CLK
>  	select SND_SOC_ACPI
>  	select SND_HDA_EXT_CORE
> +	select SND_HDA_DSP_LOADER
>  	help
>  	  Enable support for Intel(R) cAVS 1.5 platforms with DSP
>  	  capabilities. This includes Skylake, Kabylake, Amberlake and
> diff --git a/sound/soc/intel/avs/avs.h b/sound/soc/intel/avs/avs.h
> index fb3520f32488..6dc5d17ccf21 100644
> --- a/sound/soc/intel/avs/avs.h
> +++ b/sound/soc/intel/avs/avs.h
> @@ -45,6 +45,7 @@ struct avs_dsp_ops {
>  	((adev)->spec->dsp_ops->op(adev, ## __VA_ARGS__))
>  
>  #define AVS_PLATATTR_CLDMA		BIT_ULL(0)
> +#define AVS_PLATATTR_IMR		BIT_ULL(1)
>  
>  #define avs_platattr_test(adev, attr) \
>  	((adev)->spec->attributes & AVS_PLATATTR_##attr)
> @@ -239,5 +240,9 @@ int avs_cldma_load_basefw(struct avs_dev *adev,
> struct firmware *fw);
>  int avs_cldma_load_library(struct avs_dev *adev, struct firmware
> *lib, u32 id);
>  int avs_cldma_transfer_modules(struct avs_dev *adev, bool load,
>  			       struct avs_module_entry *mods, u32
> num_mods);
> +int avs_hda_load_basefw(struct avs_dev *adev, struct firmware *fw);
> +int avs_hda_load_library(struct avs_dev *adev, struct firmware *lib,
> u32 id);
> +int avs_hda_transfer_modules(struct avs_dev *adev, bool load,
> +			     struct avs_module_entry *mods, u32
> num_mods);
>  
>  #endif /* __SOUND_SOC_INTEL_AVS_H */
> diff --git a/sound/soc/intel/avs/loader.c
> b/sound/soc/intel/avs/loader.c
> index 2134aaabe2c6..6520f23fc70e 100644
> --- a/sound/soc/intel/avs/loader.c
> +++ b/sound/soc/intel/avs/loader.c
> @@ -9,6 +9,7 @@
>  #include <linux/firmware.h>
>  #include <linux/module.h>
>  #include <linux/slab.h>
> +#include <sound/hdaudio.h>
>  #include <sound/hdaudio_ext.h>
>  #include "avs.h"
>  #include "cldma.h"
> @@ -18,8 +19,11 @@
>  #define AVS_ROM_STS_MASK		0xFF
>  #define AVS_ROM_INIT_DONE		0x1
>  #define SKL_ROM_BASEFW_ENTERED		0xF
> +#define APL_ROM_FW_ENTERED		0x5
>  #define AVS_ROM_INIT_POLLING_US		5
>  #define SKL_ROM_INIT_TIMEOUT_US		1000000
> +#define APL_ROM_INIT_TIMEOUT_US		300000
> +#define APL_ROM_INIT_RETRIES		3
>  
>  #define AVS_FW_INIT_POLLING_US		500
>  #define AVS_FW_INIT_TIMEOUT_US		3000000
> @@ -261,6 +265,204 @@ int avs_cldma_transfer_modules(struct avs_dev
> *adev, bool load,
>  	return 0;
>  }
>  
> +static int
> +avs_hda_init_rom(struct avs_dev *adev, unsigned int dma_id, bool
> purge)
> +{
> +	const struct avs_spec *const spec = adev->spec;
> +	unsigned int corex_mask, reg;
> +	int ret;
> +
> +	corex_mask = spec->core_init_mask & ~AVS_MAIN_CORE_MASK;
> +
> +	ret = avs_dsp_op(adev, power, spec->core_init_mask, true);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = avs_dsp_op(adev, reset, AVS_MAIN_CORE_MASK, false);
> +	if (ret < 0)
> +		goto err;
> +
> +	reinit_completion(&adev->fw_ready);
> +	avs_dsp_op(adev, int_control, true);
> +
> +	/* set boot config */
> +	ret = avs_ipc_set_boot_config(adev, dma_id, purge);
> +	if (ret) {
> +		ret = AVS_IPC_RET(ret);
> +		goto err;
> +	}
> +
> +	/* await ROM init */
> +	ret = snd_hdac_adsp_readq_poll(adev, spec->rom_status, reg,
> +				       (reg & 0xF) == AVS_ROM_INIT_DONE
> ||
> +				       (reg & 0xF) ==
> APL_ROM_FW_ENTERED,
> +				       AVS_ROM_INIT_POLLING_US,
> APL_ROM_INIT_TIMEOUT_US);
> +	if (ret < 0) {
> +		dev_err(adev->dev, "rom init timeout: %d\n", ret);
> +		goto err;
> +	}
> +
> +	/* power down non-main cores */
> +	if (corex_mask)
> +		avs_dsp_op(adev, power, corex_mask, false);
What if this fails?
> +
> +	return 0;
> +
> +err:
> +	avs_dsp_core_disable(adev, spec->core_init_mask);
> +	return ret;
> +}
> +
> +static int avs_imr_load_basefw(struct avs_dev *adev)
> +{
> +	int ret;
> +
> +	/* DMA id ignored when flashing from IMR as no transfer occurs.
> */
> +	ret = avs_hda_init_rom(adev, 0, false);
> +	if (ret < 0) {
> +		dev_err(adev->dev, "rom init failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	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);
> +		return -ETIMEDOUT;
> +	}
> +
> +	return 0;
> +}
> +
> +int avs_hda_load_basefw(struct avs_dev *adev, struct firmware *fw)
> +{
> +	struct snd_pcm_substream substream;
> +	struct snd_dma_buffer dmab;
> +	struct hdac_ext_stream *estream;
> +	struct hdac_stream *hstream;
> +	struct hdac_bus *bus = &adev->base.core;
> +	unsigned int sdfmt, reg;
> +	int ret, i;
> +
> +	/* configure hda dma */
> +	memset(&substream, 0, sizeof(substream));
> +	substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
> +	estream = snd_hdac_ext_stream_assign(bus, &substream,
> +					     HDAC_EXT_STREAM_TYPE_HOST)
> ;
> +	if (!estream)
> +		return -ENODEV;
> +	hstream = hdac_stream(estream);
> +
> +	/* code loading performed with default format */
> +	sdfmt = snd_hdac_calc_stream_format(48000, 1,
> SNDRV_PCM_FORMAT_S32_LE, 32, 0);
> +	ret = snd_hdac_dsp_prepare(hstream, sdfmt, fw->size, &dmab);
> +	if (ret < 0)
> +		goto release_stream;
> +
> +	/* enable SPIB for hda stream */
> +	snd_hdac_ext_stream_spbcap_enable(bus, true, hstream->index);
> +	ret = snd_hdac_ext_stream_set_spib(bus, estream, fw->size);
> +	if (ret)
> +		goto cleanup_resources;
> +
> +	memcpy(dmab.area, fw->data, fw->size);
> +
> +	for (i = 0; i < APL_ROM_INIT_RETRIES; i++) {
> +		unsigned int dma_id = hstream->stream_tag - 1;
> +
> +		ret = avs_hda_init_rom(adev, dma_id, true);
> +		if (!ret)
> +			break;
> +		dev_info(adev->dev, "#%d rom init fail: %d\n", i + 1,
> ret);
> +	}
> +	if (ret < 0)
> +		goto cleanup_resources;
> +
> +	/* transfer firmware */
> +	snd_hdac_dsp_trigger(hstream, true);
> +	ret = snd_hdac_adsp_readl_poll(adev, AVS_FW_REG_STATUS(adev),
> reg,
> +				       (reg & AVS_ROM_STS_MASK) ==
> APL_ROM_FW_ENTERED,
> +				       AVS_FW_INIT_POLLING_US,
> AVS_FW_INIT_TIMEOUT_US);
> +	snd_hdac_dsp_trigger(hstream, false);
> +	if (ret < 0) {
> +		dev_err(adev->dev, "transfer fw failed: %d\n", ret);
> +		avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK);
> +	}
> +
> +cleanup_resources:
> +	/* disable SPIB for hda stream */
> +	snd_hdac_ext_stream_spbcap_enable(bus, false, hstream->index);
> +	snd_hdac_ext_stream_set_spib(bus, estream, 0);
> +
> +	snd_hdac_dsp_cleanup(hstream, &dmab);
> +release_stream:
> +	snd_hdac_ext_stream_release(estream,
> HDAC_EXT_STREAM_TYPE_HOST);
> +
> +	return ret;
> +}
> +
> +int avs_hda_load_library(struct avs_dev *adev, struct firmware *lib,
> u32 id)
> +{
> +	struct snd_pcm_substream substream;
> +	struct snd_dma_buffer dmab;
> +	struct hdac_ext_stream *estream;
> +	struct hdac_stream *stream;
> +	struct hdac_bus *bus = &adev->base.core;
> +	unsigned int sdfmt;
> +	int ret;
> +
> +	/* configure hda dma */
> +	memset(&substream, 0, sizeof(substream));
> +	substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
> +	estream = snd_hdac_ext_stream_assign(bus, &substream,
> +					     HDAC_EXT_STREAM_TYPE_HOST)
> ;
> +	if (!estream)
> +		return -ENODEV;
> +	stream = hdac_stream(estream);
> +
> +	/* code loading performed with default format */
> +	sdfmt = snd_hdac_calc_stream_format(48000, 1,
> SNDRV_PCM_FORMAT_S32_LE, 32, 0);
> +	ret = snd_hdac_dsp_prepare(stream, sdfmt, lib->size, &dmab);
> +	if (ret < 0)
> +		goto release_stream;
> +
> +	/* enable SPIB for hda stream */
> +	snd_hdac_ext_stream_spbcap_enable(bus, true, stream->index);
> +	snd_hdac_ext_stream_set_spib(bus, estream, lib->size);
> +
> +	memcpy(dmab.area, lib->data, lib->size);
> +
> +	/* transfer firmware */
> +	snd_hdac_dsp_trigger(stream, true);
> +	ret = avs_ipc_load_library(adev, stream->stream_tag - 1, id);
> +	snd_hdac_dsp_trigger(stream, false);
> +	if (ret) {
> +		dev_err(adev->dev, "transfer lib %d failed: %d\n", id,
> ret);
> +		ret = AVS_IPC_RET(ret);
> +	}
> +
> +	/* disable SPIB for hda stream */
> +	snd_hdac_ext_stream_spbcap_enable(bus, false, stream->index);
> +	snd_hdac_ext_stream_set_spib(bus, estream, 0);
> +
> +	snd_hdac_dsp_cleanup(stream, &dmab);
> +release_stream:
> +	snd_hdac_ext_stream_release(estream,
> HDAC_EXT_STREAM_TYPE_HOST);
> +
> +	return ret;
> +}
> +
> +int avs_hda_transfer_modules(struct avs_dev *adev, bool load,
> +			     struct avs_module_entry *mods, u32
> num_mods)
What is the difference between transfer_modules and load_library?

Thanks,Ranjani
Cezary Rojewski March 4, 2022, 5:21 p.m. UTC | #5
On 2022-03-04 5:21 PM, Ranjani Sridharan wrote:
> On Fri, 2022-03-04 at 15:57 +0100, Cezary Rojewski wrote:

...

>> +/*
>> + * avs_ipc_delete_instance - Delete module instance
>> + *
>> + * @adev: Driver context
>> + * @module_id: Module-type id
>> + * @instance_id: Unique module instance id
>> + *
>> + * Argument verification, as well as pipeline state checks are done
>> by the
>> + * firmware.
>> + *
>> + * Note: only standalone modules i.e. without a parent pipeline
>> shall be
>> + * deleted using this IPC message. In all other cases, pipeline
>> owning the
>> + * modules peforms cleanup automatically when it is deleted.
> Can you please provide an example of such stand-alone modules? If they
> aren't part of any pipeline, how do they get scheduled?


Thanks for feedback! Consider dropping the unnecessary bits so it is 
easier to navigate through your responses.

Please note: kernel mailing list is not for explaining SW <-> FW 
communication details. Feel free to contact my colleagues from firmware 
team if in need of any FW-iface details.

That goes for most of the comments found below too.

>> +/*
>> + * avs_ipc_unbind - Unbind two module instances
>> + *
>> + * @adev: Driver context
>> + * @module_id: Source module-type id
>> + * @instance_id: Source module instance id
>> + * @dst_module_id: Sink module-type id
>> + * @dst_instance_id: Sink module instance id
>> + * @dst_queue: Sink module pin to unbind @src_queue from
>> + * @src_queue: Source module pin to unbind @dst_queue from
>> + */
> Are there any rules for unbinding? For example if you have 2 modules
> connected to a mixer? Can you unbind the module belonging to the host
> pipeline that is getting stopped while the mixer is still active?


Here we have just a delegate. All the rules are defined and enforced by 
the firmware.

>> +int avs_ipc_unbind(struct avs_dev *adev, u16 module_id, u8
>> instance_id,
>> +		   u16 dst_module_id, u8 dst_instance_id,
>> +		   u8 dst_queue, u8 src_queue)
>> +{
>> +	union avs_module_msg msg = AVS_MODULE_REQUEST(UNBIND);
>> +	struct avs_ipc_msg request = {{0}};
>> +	int ret;
>> +
>> +	msg.module_id = module_id;
>> +	msg.instance_id = instance_id;
>> +	msg.ext.bind_unbind.dst_module_id = dst_module_id;
>> +	msg.ext.bind_unbind.dst_instance_id = dst_instance_id;
>> +	msg.ext.bind_unbind.dst_queue = dst_queue;
>> +	msg.ext.bind_unbind.src_queue = src_queue;
>> +	request.header = msg.val;
>> +
>> +	ret = avs_dsp_send_msg(adev, &request, NULL);
>> +	if (ret)
>> +		avs_ipc_err(adev, &request, "unbind modules", ret);
>> +
>> +	return ret;
>> +}

...

>> +int avs_ipc_get_large_config(struct avs_dev *adev, u16 module_id, u8
>> instance_id,
>> +			     u8 param_id, u8 *request_data, size_t
>> request_size,
>> +			     u8 **reply_data, size_t *reply_size)
>> +{
>> +	union avs_module_msg msg =
>> AVS_MODULE_REQUEST(LARGE_CONFIG_GET);
>> +	struct avs_ipc_msg request;
>> +	struct avs_ipc_msg reply = {{0}};
>> +	size_t size;
>> +	void *buf;
>> +	int ret;
>> +
>> +	reply.data = kzalloc(AVS_MAILBOX_SIZE, GFP_KERNEL);
>> +	if (!reply.data)
>> +		return -ENOMEM;
>> +
>> +	msg.module_id = module_id;
>> +	msg.instance_id = instance_id;
>> +	msg.ext.large_config.data_off_size = request_size;
>> +	msg.ext.large_config.large_param_id = param_id;
>> +	/* final_block is always 0 on request. Updated by fw on reply.
>> */
>> +	msg.ext.large_config.final_block = 0;
>> +	msg.ext.large_config.init_block = 1;
>> +
>> +	request.header = msg.val;
>> +	request.data = request_data;
>> +	request.size = request_size;
>> +	reply.size = AVS_MAILBOX_SIZE;
>> +
>> +	ret = avs_dsp_send_msg(adev, &request, &reply);
>> +	if (ret) {
>> +		avs_ipc_err(adev, &request, "large config get", ret);
>> +		kfree(reply.data);
>> +		return ret;
>> +	}
> How come you dont have a loop here? What if the rec'd data size if
> larger than the max size of IP payload?


That's not how LARGE_CONFIG_GET message works. There is no looping 
involved or expected by the firmware and so we don't have it here.


Regards,
Czarek
Cezary Rojewski March 4, 2022, 5:33 p.m. UTC | #6
On 2022-03-04 5:26 PM, Ranjani Sridharan wrote:
> On Fri, 2022-03-04 at 15:57 +0100, Cezary Rojewski wrote:
>> ROM requests are messages initiated by Host to alter firmware early
>> boot
>> process. They specify whether the next boot should be a fresh start
>> or if
>> IMR can be used to speed things up.
>>
>> Signed-off-by: Amadeusz Sławiński <
>> amadeuszx.slawinski@linux.intel.com>
>> Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
>> ---
>>   sound/soc/intel/avs/messages.c | 18 ++++++++++++++++++
>>   sound/soc/intel/avs/messages.h | 14 ++++++++++++++
>>   2 files changed, 32 insertions(+)
>>
>> diff --git a/sound/soc/intel/avs/messages.c
>> b/sound/soc/intel/avs/messages.c
>> index e8f441b28d71..f7d00e541323 100644
>> --- a/sound/soc/intel/avs/messages.c
>> +++ b/sound/soc/intel/avs/messages.c
>> @@ -12,6 +12,24 @@
>>   
>>   #define AVS_CL_TIMEOUT_MS	5000
>>   
>> +int avs_ipc_set_boot_config(struct avs_dev *adev, u32 dma_id, u32
>> purge)
> Does purge set to true indicate a fresh boot and a false indicate IMR
> restore? A description would help.


Indeed, purge indicates a fresh boot.

I believe flow is explained well by the layout of the calling functions, 
local variable naming and step-ordering which are found later in the series.


Regards,
Czarek
Cezary Rojewski March 4, 2022, 6:02 p.m. UTC | #7
On 2022-03-04 5:41 PM, Ranjani Sridharan wrote:
> On Fri, 2022-03-04 at 15:57 +0100, Cezary Rojewski wrote:

>>   /*
>>    * struct avs_dev - Intel HD-Audio driver data
>>    *
>>    * @dev: PCI device
>>    * @dsp_ba: DSP bar address
>>    * @spec: platform-specific descriptor
>> + * @fw_cfg: Firmware configuration, obtained through FW_CONFIG
>> message
>> + * @hw_cfg: Hardware configuration, obtained through HW_CONFIG
>> message
>> + * @mods_info: Available module-types, obtained through MODULES_INFO
>> message
>> + * @mod_idas: Module instance ID pool, one per module-type
>> + * @modres_mutex: For synchronizing any @mods_info updates
> Is this mutex really necessary? Can you please elaborate under what
> circumstances your will have parallel module updates?


Yes, we believe modres_mutex is necessary. All information regarding 
modules exposed by the firmware are stored within ->mods_info cache.

That's just a snapshot though. When a new library gets loaded, new 
modules may be available for use and so the driver updates the 
->mods_info cache to have the latest snapshot. As information found 
there is used when streaming (e.g.: instantiating modules), we enter a 
scenario when multiple threads could be reading/updating the ->mods_info 
at once. To prevent any unwanted behavior, mutex has been added.

>> +void avs_module_info_free(struct avs_dev *adev)
>> +{
>> +	mutex_lock(&adev->modres_mutex);
>> +
>> +	avs_module_ida_destroy(adev);
>> +	kfree(adev->mods_info);
>> +	adev->mods_info = NULL;
>> +
>> +	mutex_unlock(&adev->modres_mutex);
>> +}
>> +
>> +int avs_module_id_alloc(struct avs_dev *adev, u16 module_id)
>> +{
>> +	int ret, idx, max_id;
>> +
>> +	mutex_lock(&adev->modres_mutex);
>> +
>> +	idx = avs_module_id_entry_index(adev, module_id);
>> +	if (idx == -ENOENT) {
> Can you please help me understand when this can happen? If all modules
> required by the topology are already initialized, will this ever
> happen?


I want to help! Just not understanding the meaning of: "all modules 
required by the topology are already initialized".

Will answer best I can though: topology carries just patterns, it may 
happen that module found within topology file is not actually exposed by 
the firmware. In such case, we drop an error. This keeps recovery 
scenarios sane too - when recovering, libraries may have to be 
re-loaded, depending on the firmware generation and whether basefw 
recovery was successful or not.


Regards,
Czarek
Cezary Rojewski March 4, 2022, 6:15 p.m. UTC | #8
On 2022-03-04 5:47 PM, Ranjani Sridharan wrote:
> On Fri, 2022-03-04 at 15:57 +0100, Cezary Rojewski wrote:
>> Wrap elementary DSP-core operations and resource control into more
>> complex handlers. This is done to reduce the number of invocations of
>> wrapped operations throughout the driver as order of operations
>> matters -
>> most flows involve register manipulation and IPCs combined.
>>
>> Signed-off-by: Amadeusz Sławiński <
>> amadeuszx.slawinski@linux.intel.com>
>> Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
>> ---
>>   sound/soc/intel/avs/avs.h |  10 +++
>>   sound/soc/intel/avs/dsp.c | 170
>> ++++++++++++++++++++++++++++++++++++++
>>   2 files changed, 180 insertions(+)
>>
>> diff --git a/sound/soc/intel/avs/avs.h b/sound/soc/intel/avs/avs.h
>> index 02d7591d0eac..0034c075fa64 100644
>> --- a/sound/soc/intel/avs/avs.h
>> +++ b/sound/soc/intel/avs/avs.h
>> @@ -89,6 +89,7 @@ struct avs_dev {
>>   	struct mutex modres_mutex;
>>   	struct ida ppl_ida;
>>   	struct list_head fw_list;
>> +	int *core_refs;
> 
> Is this a per core ref_count? a comment or explicitly calling it
> core_ref_count would help.


Hmm.. That's bit of a nitpick. Isn't a 's' enough here? I.e.: here we 
have an array of core references. Array, as the number of elements is 
retrieved dynamically via LARGE_CONFIG_GET and only then the array gets 
allocated.


Regards,
Czarek
Cezary Rojewski March 4, 2022, 6:29 p.m. UTC | #9
On 2022-03-04 5:54 PM, Ranjani Sridharan wrote:
> On Fri, 2022-03-04 at 15:57 +0100, Cezary Rojewski wrote:

...

>> +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?


Unfortunately not. I share the similar thinking but the build system has 
its history and several things which should not happen, had happened. 
There could be _large_ API changes without any meaningful version 
updates at all. To prevent any unwanted behavior, this check is as 
strict as it can get.

>> +		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;
>> +}

...

>> +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?


Loop below just clears the data. The new snapshot will be received once 
the basefw and libraries get loaded. The execution of library loading is 
not part of this patch anymore as it is dependent on the 
avs-soc-component stuff. To make things easier to review, request was to 
split main series into chucks. I do believe it is easier to read and 
review indeed.

>> +	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.


avs_dsp_boot_firmware() and avs_dsp_first_boot_firmware() (found just 
below this one) are two separate functions. Only the latter has things 
done once. Anything else can happen several times throughout the 
lifetime of the avs-driver.


Regards,
Czarek
Pierre-Louis Bossart March 4, 2022, 6:56 p.m. UTC | #10
>>> Compared to SKL and KBL, younger cAVS platforms are meant to re-use
>>> one
>> Younger? you mean newer?
> 
> 
> Isn't the meaning of the two quite similar in this context?

younger doesn't sound quite right to me.

'cAVS platforms more recent that SKL and KBL...'

> 
>>> of HDAudio streams during boot procedure causing CLDMA to become
>>> obsolete. Once transferred, given stream is returned to pool
>>> available
>>> for audio streaming.
>>>
>>> Module loading handler is dummy as library and module code became
>>> inseparable in later firmware generations.
>> replace dummy with "stub" maybe? lets use inclusive terms
> 
> 
> Is 'dummy' categorized as non-inclusive? We have several usages of 
> 'dummy' even within /sound (e.g. soc-utils.c).

Intel and plenty of other companies recommend a different wording. 
mock-up, stub, placeholder, etc. see e.g.

https://developers.google.com/style/inclusive-documentation

Yes we have plenty of existing uses of 'dummy', but that doesn't mean we 
should add new ones.

>>> +static int
>>> +avs_hda_init_rom(struct avs_dev *adev, unsigned int dma_id, bool
>>> purge)
>>> +{
>>> +    const struct avs_spec *const spec = adev->spec;
>>> +    unsigned int corex_mask, reg;
>>> +    int ret;
>>> +
>>> +    corex_mask = spec->core_init_mask & ~AVS_MAIN_CORE_MASK;
>>> +
>>> +    ret = avs_dsp_op(adev, power, spec->core_init_mask, true);
>>> +    if (ret < 0)
>>> +        goto err;
>>> +
>>> +    ret = avs_dsp_op(adev, reset, AVS_MAIN_CORE_MASK, false);
>>> +    if (ret < 0)
>>> +        goto err;
>>> +
>>> +    reinit_completion(&adev->fw_ready);
>>> +    avs_dsp_op(adev, int_control, true);
>>> +
>>> +    /* set boot config */
>>> +    ret = avs_ipc_set_boot_config(adev, dma_id, purge);
>>> +    if (ret) {
>>> +        ret = AVS_IPC_RET(ret);
>>> +        goto err;
>>> +    }
>>> +
>>> +    /* await ROM init */
>>> +    ret = snd_hdac_adsp_readq_poll(adev, spec->rom_status, reg,
>>> +                       (reg & 0xF) == AVS_ROM_INIT_DONE
>>> ||
>>> +                       (reg & 0xF) ==
>>> APL_ROM_FW_ENTERED,
>>> +                       AVS_ROM_INIT_POLLING_US,
>>> APL_ROM_INIT_TIMEOUT_US);
>>> +    if (ret < 0) {
>>> +        dev_err(adev->dev, "rom init timeout: %d\n", ret);
>>> +        goto err;
>>> +    }
>>> +
>>> +    /* power down non-main cores */
>>> +    if (corex_mask)
>>> +        avs_dsp_op(adev, power, corex_mask, false);
>> What if this fails?
> 
> 
> We are still in happy path, worst thing could happen here is increased 
> power consumption.

'happy path'?
Cezary Rojewski March 7, 2022, 2:31 p.m. UTC | #11
On 2022-03-04 7:56 PM, Pierre-Louis Bossart wrote:
> 
>>>> Compared to SKL and KBL, younger cAVS platforms are meant to re-use
>>>> one
>>> Younger? you mean newer?
>>
>>
>> Isn't the meaning of the two quite similar in this context?
> 
> younger doesn't sound quite right to me.
> 
> 'cAVS platforms more recent that SKL and KBL...'


Sure, can reword.

>>>> Module loading handler is dummy as library and module code became
>>>> inseparable in later firmware generations.
>>> replace dummy with "stub" maybe? lets use inclusive terms
>>
>>
>> Is 'dummy' categorized as non-inclusive? We have several usages of 
>> 'dummy' even within /sound (e.g. soc-utils.c).
> 
> Intel and plenty of other companies recommend a different wording. 
> mock-up, stub, placeholder, etc. see e.g.
> 
> https://developers.google.com/style/inclusive-documentation
> 
> Yes we have plenty of existing uses of 'dummy', but that doesn't mean we 
> should add new ones.


No problem with rewording this one in v4.

>>>> +static int
>>>> +avs_hda_init_rom(struct avs_dev *adev, unsigned int dma_id, bool
>>>> purge)
>>>> +{
>>>> +    const struct avs_spec *const spec = adev->spec;
>>>> +    unsigned int corex_mask, reg;
>>>> +    int ret;
>>>> +
>>>> +    corex_mask = spec->core_init_mask & ~AVS_MAIN_CORE_MASK;
>>>> +
>>>> +    ret = avs_dsp_op(adev, power, spec->core_init_mask, true);
>>>> +    if (ret < 0)
>>>> +        goto err;
>>>> +
>>>> +    ret = avs_dsp_op(adev, reset, AVS_MAIN_CORE_MASK, false);
>>>> +    if (ret < 0)
>>>> +        goto err;
>>>> +
>>>> +    reinit_completion(&adev->fw_ready);
>>>> +    avs_dsp_op(adev, int_control, true);
>>>> +
>>>> +    /* set boot config */
>>>> +    ret = avs_ipc_set_boot_config(adev, dma_id, purge);
>>>> +    if (ret) {
>>>> +        ret = AVS_IPC_RET(ret);
>>>> +        goto err;
>>>> +    }
>>>> +
>>>> +    /* await ROM init */
>>>> +    ret = snd_hdac_adsp_readq_poll(adev, spec->rom_status, reg,
>>>> +                       (reg & 0xF) == AVS_ROM_INIT_DONE
>>>> ||
>>>> +                       (reg & 0xF) ==
>>>> APL_ROM_FW_ENTERED,
>>>> +                       AVS_ROM_INIT_POLLING_US,
>>>> APL_ROM_INIT_TIMEOUT_US);
>>>> +    if (ret < 0) {
>>>> +        dev_err(adev->dev, "rom init timeout: %d\n", ret);
>>>> +        goto err;
>>>> +    }
>>>> +
>>>> +    /* power down non-main cores */
>>>> +    if (corex_mask)
>>>> +        avs_dsp_op(adev, power, corex_mask, false);
>>> What if this fails?
>>
>>
>> We are still in happy path, worst thing could happen here is increased 
>> power consumption.
> 
> 'happy path'?

The question is whether we value user experience over error-checking 
this case. I've never seen this step failing and the step itself is 
quite.. extraordinary : ) It's also invoked on very limited number of 
cAVS platforms, for everything else it's just: 'return 0'.


Regards,
Czarek
Ranjani Sridharan March 7, 2022, 4:15 p.m. UTC | #12
On Fri, 2022-03-04 at 18:11 +0100, Cezary Rojewski wrote:
> > > +     * -EPERM error code is expected and thus it's not an actual
> > > error.
> > And what happens in this case? do you retry the IPC after recovery?
> 
> 
> 
> 
> Not at all. Why would you want retry IPC after recovery in the first
> place?
So what happens after FW recovery? Will this lead to an xrun and cause
usersapce to restart the stream?

Thanks,Ranjani
Cezary Rojewski March 7, 2022, 4:23 p.m. UTC | #13
On 2022-03-07 5:15 PM, Ranjani Sridharan wrote:
> On Fri, 2022-03-04 at 18:11 +0100, Cezary Rojewski wrote:
>>>> +     * -EPERM error code is expected and thus it's not an actual
>>>> error.
>>> And what happens in this case? do you retry the IPC after recovery?
>>
>>
>>
>>
>> Not at all. Why would you want retry IPC after recovery in the first
>> place?
> So what happens after FW recovery? Will this lead to an xrun and cause
> usersapce to restart the stream?

If DSP dies so does the stream. Recovery is not guaranteed, it's an 
attempt. What userspace does with that is for userspace to decide.


Regards,
Czarek
Ranjani Sridharan March 7, 2022, 4:39 p.m. UTC | #14
On Fri, 2022-03-04 at 18:21 +0100, Cezary Rojewski wrote:
> > Are there any rules for unbinding? For example if you have 2
> > modules
> > connected to a mixer? Can you unbind the module belonging to the
> > host
> > pipeline that is getting stopped while the mixer is still active?
> 
> 
> 
> 
> Here we have just a delegate. All the rules are defined and enforced
> by 
> 
> the firmware.
I'm not following this, Czarek. If there are rules defined by the FW,
the driver has to follow it isnt it? What I am asking is how and where
do you enforce this in the AVS driver?

...
> > > 
> > How come you dont have a loop here? What if the rec'd data size if
> > larger than the max size of IP payload?
> 
> 
> 
> 
> That's not how LARGE_CONFIG_GET message works. There is no looping 
> 
> involved or expected by the firmware and so we don't have it here.

So, are you saying that when retrieving data from the FW, the size of
the retrieved data can never exceed max IPC payload size?

Thanks,Ranjani
Ranjani Sridharan March 7, 2022, 4:46 p.m. UTC | #15
> > > + * @modres_mutex: For synchronizing any @mods_info updates
> > Is this mutex really necessary? Can you please elaborate under what
> > circumstances your will have parallel module updates?
> 
> 
> 
> 
> Yes, we believe modres_mutex is necessary. All information regarding 
> 
> modules exposed by the firmware are stored within ->mods_info cache.
> 
> 
> 
> That's just a snapshot though. When a new library gets loaded, new 
> 
> modules may be available for use and so the driver updates the 
> 
> ->mods_info cache to have the latest snapshot. As information found 
> 
> there is used when streaming (e.g.: instantiating modules), we enter
> a 
> 
> scenario when multiple threads could be reading/updating the
> ->mods_info 
> 
> at once. To prevent any unwanted behavior, mutex has been added.
This is the part that's hard to follow without seeing the actual code
where this new library is loaded. When does a libray get loaded? When
you start streaming and you realize that the stream requires a module
that is not built into the base FW? Can this be done during topology
loading instead?

Thanks,
Rnajani
Cezary Rojewski March 7, 2022, 4:58 p.m. UTC | #16
On 2022-03-07 5:39 PM, Ranjani Sridharan wrote:
> On Fri, 2022-03-04 at 18:21 +0100, Cezary Rojewski wrote:
>>> Are there any rules for unbinding? For example if you have 2
>>> modules
>>> connected to a mixer? Can you unbind the module belonging to the
>>> host
>>> pipeline that is getting stopped while the mixer is still active?
>>
>>
>>
>>
>> Here we have just a delegate. All the rules are defined and enforced
>> by
>>
>> the firmware.
> I'm not following this, Czarek. If there are rules defined by the FW,
> the driver has to follow it isnt it? What I am asking is how and where
> do you enforce this in the AVS driver?


How the stream looks is defined by the topology. Code that translates 
such patterns into runtime being is not part of this patchset. It's part 
of avs_path and its collaterals instead.

>>>>
>>> How come you dont have a loop here? What if the rec'd data size if
>>> larger than the max size of IP payload?
>>
>>
>>
>>
>> That's not how LARGE_CONFIG_GET message works. There is no looping
>>
>> involved or expected by the firmware and so we don't have it here.
> 
> So, are you saying that when retrieving data from the FW, the size of
> the retrieved data can never exceed max IPC payload size?


Precisely.

Regards,
Czarek
Ranjani Sridharan March 7, 2022, 5:05 p.m. UTC | #17
On Mon, 2022-03-07 at 17:58 +0100, Cezary Rojewski wrote:
> > I'm not following this, Czarek. If there are rules defined by the
> > FW,
> > the driver has to follow it isnt it? What I am asking is how and
> > where
> > do you enforce this in the AVS driver?
> 
> 
> 
> 
> How the stream looks is defined by the topology. Code that
> translates 
> 
> such patterns into runtime being is not part of this patchset. It's
> part 
> 
> of avs_path and its collaterals instead.
Alright, I'll wait for the next patchset for the explanation.

> 
> 
> 
> > > > How come you dont have a loop here? What if the rec'd data size
> > > > if
> > > > larger than the max size of IP payload?
> > > That's not how LARGE_CONFIG_GET message works. There is no
> > > looping
> > > involved or expected by the firmware and so we don't have it
> > > here.
> > So, are you saying that when retrieving data from the FW, the size
> > of
> > the retrieved data can never exceed max IPC payload size?
> 
> 
> 
> 
> Precisely.
This is fundmentally flawed isnt it? If set_large_config() sets a
config that can exceed max IPC size, get_large_config() has to be able
to support it.

Thanks,
Ranjani
Cezary Rojewski March 7, 2022, 5:13 p.m. UTC | #18
On 2022-03-07 5:46 PM, Ranjani Sridharan wrote:
>>>> + * @modres_mutex: For synchronizing any @mods_info updates
>>> Is this mutex really necessary? Can you please elaborate under what
>>> circumstances your will have parallel module updates?
>>
>>
>>
>>
>> Yes, we believe modres_mutex is necessary. All information regarding
>>
>> modules exposed by the firmware are stored within ->mods_info cache.
>>
>>
>>
>> That's just a snapshot though. When a new library gets loaded, new
>>
>> modules may be available for use and so the driver updates the
>>
>> ->mods_info cache to have the latest snapshot. As information found
>>
>> there is used when streaming (e.g.: instantiating modules), we enter
>> a
>>
>> scenario when multiple threads could be reading/updating the
>> ->mods_info
>>
>> at once. To prevent any unwanted behavior, mutex has been added.
> This is the part that's hard to follow without seeing the actual code
> where this new library is loaded. When does a libray get loaded? When
> you start streaming and you realize that the stream requires a module
> that is not built into the base FW? Can this be done during topology
> loading instead?

But that's already done during topology load! If there is no topology 
telling the driver: "hey, load this lib for me!", nothing gets loaded 
regardless of how your /lib/firmware looks like. Libraries get loaded 
during soc-component's (platform component) ->probe(). This is place 
where snd_soc_tplg_component_load() is called.

However, if platform has no IMR capability, driver has to re-load 
libraries for all platform components of bound sound cards on every 
pm_runtime_resume().


Regards,
Czarek
Cezary Rojewski March 7, 2022, 5:27 p.m. UTC | #19
On 2022-03-07 6:05 PM, Ranjani Sridharan wrote:
> On Mon, 2022-03-07 at 17:58 +0100, Cezary Rojewski wrote:
>>> I'm not following this, Czarek. If there are rules defined by the
>>> FW,
>>> the driver has to follow it isnt it? What I am asking is how and
>>> where
>>> do you enforce this in the AVS driver?
>>
>>
>>
>>
>> How the stream looks is defined by the topology. Code that
>> translates
>>
>> such patterns into runtime being is not part of this patchset. It's
>> part
>>
>> of avs_path and its collaterals instead.
> Alright, I'll wait for the next patchset for the explanation.
> 
>>
>>
>>
>>>>> How come you dont have a loop here? What if the rec'd data size
>>>>> if
>>>>> larger than the max size of IP payload?
>>>> That's not how LARGE_CONFIG_GET message works. There is no
>>>> looping
>>>> involved or expected by the firmware and so we don't have it
>>>> here.
>>> So, are you saying that when retrieving data from the FW, the size
>>> of
>>> the retrieved data can never exceed max IPC payload size?
>>
>>
>>
>>
>> Precisely.
> This is fundmentally flawed isnt it? If set_large_config() sets a
> config that can exceed max IPC size, get_large_config() has to be able
> to support it.

I could ask people on the list to "not look for a second" then there 
would be no problem explaining all the *recommended flows*.

Simple, honest answer is: Yes, that's fundamentally flawed.
Now, as older firmware generations do not expect nor support larger 
payload sizes, adding such code here is essentially adding dead code so 
we have decided to add none of it.


Regards,
Czarek
Ranjani Sridharan March 7, 2022, 5:30 p.m. UTC | #20
On Mon, 2022-03-07 at 18:13 +0100, Cezary Rojewski wrote:
> > This is the part that's hard to follow without seeing the actual
> > code
> > where this new library is loaded. When does a libray get loaded?
> > When
> > you start streaming and you realize that the stream requires a
> > module
> > that is not built into the base FW? Can this be done during
> > topology
> > loading instead?
> 
> 
> But that's already done during topology load! If there is no
> topology 
> 
> telling the driver: "hey, load this lib for me!", nothing gets
> loaded 
> 
> regardless of how your /lib/firmware looks like. Libraries get
> loaded 
> 
> during soc-component's (platform component) ->probe(). This is place 
> 
> where snd_soc_tplg_component_load() is called.
> 
> 
> 
> However, if platform has no IMR capability, driver has to re-load 
> 
> libraries for all platform components of bound sound cards on every 
> 
> pm_runtime_resume().

And if it done during pm_runtime_resume(), where would the problem with
synchronizing arise from? userspace apps do not get resumed until after
the PCI device is resumed isnt it?

Thanks,
Ranjani
Pierre-Louis Bossart March 7, 2022, 5:47 p.m. UTC | #21
>>>>>> How come you dont have a loop here? What if the rec'd data size
>>>>>> if
>>>>>> larger than the max size of IP payload?
>>>>> That's not how LARGE_CONFIG_GET message works. There is no
>>>>> looping
>>>>> involved or expected by the firmware and so we don't have it
>>>>> here.
>>>> So, are you saying that when retrieving data from the FW, the size
>>>> of
>>>> the retrieved data can never exceed max IPC payload size?
>>>
>>>
>>>
>>>
>>> Precisely.
>> This is fundmentally flawed isnt it? If set_large_config() sets a
>> config that can exceed max IPC size, get_large_config() has to be able
>> to support it.
> 
> I could ask people on the list to "not look for a second" then there 
> would be no problem explaining all the *recommended flows*.
> 
> Simple, honest answer is: Yes, that's fundamentally flawed.
> Now, as older firmware generations do not expect nor support larger 
> payload sizes, adding such code here is essentially adding dead code so 
> we have decided to add none of it.

Adding a comment and/or an explanation in the commit message wouldn't 
hurt then.
Cezary Rojewski March 8, 2022, 4:57 p.m. UTC | #22
On 2022-03-07 6:30 PM, Ranjani Sridharan wrote:
> On Mon, 2022-03-07 at 18:13 +0100, Cezary Rojewski wrote:
>>> This is the part that's hard to follow without seeing the actual
>>> code
>>> where this new library is loaded. When does a libray get loaded?
>>> When
>>> you start streaming and you realize that the stream requires a
>>> module
>>> that is not built into the base FW? Can this be done during
>>> topology
>>> loading instead?
>>
>>
>> But that's already done during topology load! If there is no
>> topology
>>
>> telling the driver: "hey, load this lib for me!", nothing gets
>> loaded
>>
>> regardless of how your /lib/firmware looks like. Libraries get
>> loaded
>>
>> during soc-component's (platform component) ->probe(). This is place
>>
>> where snd_soc_tplg_component_load() is called.
>>
>>
>>
>> However, if platform has no IMR capability, driver has to re-load
>>
>> libraries for all platform components of bound sound cards on every
>>
>> pm_runtime_resume().
> 
> And if it done during pm_runtime_resume(), where would the problem with
> synchronizing arise from? userspace apps do not get resumed until after
> the PCI device is resumed isnt it?

Scenario you describe is correct and does not prompt the need for the mutex.

However, ->mods_info is accessed through getters found in utils.c (this 
very patch) during stream creation too. That fragment is part of path 
management series though - it was requested to split those bits away.

So, there is a possibility for a platform-soc-component to have its 
->probe() called - and thus triggering ->mods_info update - while a 
stream is being opened on a different sound card simultaneously. To 
avoid unwanted behavior e.g.: looping through ->mods_info while it's 
being updated in separate thread, we lock the array.


Regards,
Czarek
Ranjani Sridharan March 8, 2022, 5:22 p.m. UTC | #23
On Tue, 2022-03-08 at 17:57 +0100, Cezary Rojewski wrote:
> Scenario you describe is correct and does not prompt the need for the
> mutex.
> 
> 
> 
> However, ->mods_info is accessed through getters found in utils.c
> (this 
> 
> very patch) during stream creation too. That fragment is part of
> path 
> 
> management series though - it was requested to split those bits away.
> 
> 
> 
> So, there is a possibility for a platform-soc-component to have its 
> 
> ->probe() called - and thus triggering ->mods_info update - while a 
> 
> stream is being opened on a different sound card simultaneously. To 
> 
> avoid unwanted behavior e.g.: looping through ->mods_info while it's 
> 
> being updated in separate thread, we lock the array.

Keeping in mind that this driver is meant for older platforms, how
likely are you to support multiple sound cards with those topologies?

Thanks,
Ranjani
Cezary Rojewski March 8, 2022, 6:07 p.m. UTC | #24
On 2022-03-08 6:22 PM, Ranjani Sridharan wrote:
> On Tue, 2022-03-08 at 17:57 +0100, Cezary Rojewski wrote:
>> Scenario you describe is correct and does not prompt the need for the
>> mutex.
>>
>>
>>
>> However, ->mods_info is accessed through getters found in utils.c
>> (this
>>
>> very patch) during stream creation too. That fragment is part of
>> path
>>
>> management series though - it was requested to split those bits away.
>>
>>
>>
>> So, there is a possibility for a platform-soc-component to have its
>>
>> ->probe() called - and thus triggering ->mods_info update - while a
>>
>> stream is being opened on a different sound card simultaneously. To
>>
>> avoid unwanted behavior e.g.: looping through ->mods_info while it's
>>
>> being updated in separate thread, we lock the array.
> 
> Keeping in mind that this driver is meant for older platforms, how
> likely are you to support multiple sound cards with those topologies?

Not sure what's the question here. Age of the platform has nothing to do 
with the subject. There is not a single DSP-capable platform that Intel 
has shipped that would not contain more one audio device onboard. At 
least I'm not aware of any.


Regards,
Czarek
Ranjani Sridharan March 8, 2022, 6:26 p.m. UTC | #25
On Tue, 2022-03-08 at 19:07 +0100, Cezary Rojewski wrote:
> > Keeping in mind that this driver is meant for older platforms, how
> > likely are you to support multiple sound cards with those
> > topologies?
> 
> 
> Not sure what's the question here. Age of the platform has nothing to
> do 
> 
> with the subject. There is not a single DSP-capable platform that
> Intel 
> 
> has shipped that would not contain more one audio device onboard. At 
> 
> least I'm not aware of any.

My question was related to your comment about multiple sound cards.
What I asked was do you plan to support multiple topologies with
modules spread across then with multiple sound component drivers with
the AVS driver and firmware? Does this mean you will need multiple
topology files and machine driver? And what is the rationale for this?

If not, there's no need for the mutex.

Thanks,
Ranjani
Cezary Rojewski March 8, 2022, 6:31 p.m. UTC | #26
On 2022-03-08 7:26 PM, Ranjani Sridharan wrote:
> On Tue, 2022-03-08 at 19:07 +0100, Cezary Rojewski wrote:
>>> Keeping in mind that this driver is meant for older platforms, how
>>> likely are you to support multiple sound cards with those
>>> topologies?
>>
>>
>> Not sure what's the question here. Age of the platform has nothing to
>> do
>>
>> with the subject. There is not a single DSP-capable platform that
>> Intel
>>
>> has shipped that would not contain more one audio device onboard. At
>>
>> least I'm not aware of any.
> 
> My question was related to your comment about multiple sound cards.
> What I asked was do you plan to support multiple topologies with
> modules spread across then with multiple sound component drivers with
> the AVS driver and firmware? Does this mean you will need multiple
> topology files and machine driver? And what is the rationale for this?
> 
> If not, there's no need for the mutex.

Yes, avs-driver embraces granular sound card approach as opposed to 
super card approach. There is one topology file per sound card.

That subject is not part of this patchset though.


Regards,
Czarek
Pierre-Louis Bossart March 8, 2022, 7:42 p.m. UTC | #27
On 3/8/22 12:31, Cezary Rojewski wrote:
> On 2022-03-08 7:26 PM, Ranjani Sridharan wrote:
>> On Tue, 2022-03-08 at 19:07 +0100, Cezary Rojewski wrote:
>>>> Keeping in mind that this driver is meant for older platforms, how
>>>> likely are you to support multiple sound cards with those
>>>> topologies?
>>>
>>>
>>> Not sure what's the question here. Age of the platform has nothing to
>>> do
>>>
>>> with the subject. There is not a single DSP-capable platform that
>>> Intel
>>>
>>> has shipped that would not contain more one audio device onboard. At
>>>
>>> least I'm not aware of any.
>>
>> My question was related to your comment about multiple sound cards.
>> What I asked was do you plan to support multiple topologies with
>> modules spread across then with multiple sound component drivers with
>> the AVS driver and firmware? Does this mean you will need multiple
>> topology files and machine driver? And what is the rationale for this?
>>
>> If not, there's no need for the mutex.
> 
> Yes, avs-driver embraces granular sound card approach as opposed to 
> super card approach. There is one topology file per sound card.
> 
> That subject is not part of this patchset though.

You would still need to clarify how the same set of modules might be 
accessed or configured when different cards are created, or if there is 
a 'clean' split with a 1:1 mapping between module and cards.

The more I think of this the less practical this 'granular' approach 
looks, e.g. if you want to route the same stream to different interfaces 
handled by different cards. An example is playing a notification on 
local speakers controlled by HDaudio and a Bluetooth headset using I2S. 
This could be really fun to represent even basic volume control to 
users: controls are card-specific and some parts may be handled in 
different cards and thus different UCM files.

I really think the best split of a DSP topology is between orthogonal 
parts. When muxers/demux are used, or multi-input modules such as AEC, 
the routing complexity outweighs the benefits of a simpler card design.
Cezary Rojewski March 9, 2022, 5:23 p.m. UTC | #28
On 2022-03-08 8:42 PM, Pierre-Louis Bossart wrote:
> On 3/8/22 12:31, Cezary Rojewski wrote:

...

>> Yes, avs-driver embraces granular sound card approach as opposed to 
>> super card approach. There is one topology file per sound card.
>>
>> That subject is not part of this patchset though.
> 
> You would still need to clarify how the same set of modules might be 
> accessed or configured when different cards are created, or if there is 
> a 'clean' split with a 1:1 mapping between module and cards.
> 
> The more I think of this the less practical this 'granular' approach 
> looks, e.g. if you want to route the same stream to different interfaces 
> handled by different cards. An example is playing a notification on 
> local speakers controlled by HDaudio and a Bluetooth headset using I2S. 
> This could be really fun to represent even basic volume control to 
> users: controls are card-specific and some parts may be handled in 
> different cards and thus different UCM files.
> 
> I really think the best split of a DSP topology is between orthogonal 
> parts. When muxers/demux are used, or multi-input modules such as AEC, 
> the routing complexity outweighs the benefits of a simpler card design.

There is a clean split - for all the typical scenarios, topology tied to 
given sound card defines the stream patterns completely.

Even when considering "functional" split as you mention in third 
paragraph, multiple sound cards are still there. So, as I have described 
earlier, to prevent any unwanted behaviour, cached module information in 
form of ->mods_info, is being locked with help of a mutex. We do not 
duplicate such information per sound card as that would be a waste of 
memory. No matter how many libraries are mentioned by the topology files 
and loaded throughout the lifetime of a driver, there is always just one 
base firmware we sent MODULE_INFO to.

In regard to advanced scenarios and more complex routing, it would be 
good to have that conversation as a part of dedicated patchset which is 
going to follow this one (sooner or later).


Regards,
Czarek