On 2021-12-21 6:39 PM, Mark Brown wrote:
On Wed, Dec 08, 2021 at 12:12:42PM +0100, Cezary Rojewski wrote:
Implementation of ASoC topology feature for AVS driver. AudioDSP firmware supports a wide range of audio formats, module configurations and multi-pipeline streams. To represent all of this in form of static ALSA topology file, which resides usually in /lib/firmware/, while simultaneously not hindering user from any of the possibilities, 'path template' and its 'path variants' concept is introduced. These are later converted into actual runtime path. This part is explained in follow-up change.
This sounds like it should be extending the topology code (it's talking about "ALSA topologies") but it seems to be outside of that.
Path template is just a pattern like its name suggests. It is tied to DAPM widget which represents a FE or a BE and is used during path instantiation when substream is opened for streaming. It carries a range of available variants and only these represent actual implementation of a runtime path in AudioDSP. Only one variant of given path template can be instantiated at a time and selection is based off of audio format provided from userspace and currently selected one on the codec.
So this sounds like it's baking a table of use cases into the firmware rather than a separate UCM type configuration file?
AVS topology is split into two major parts: dictionaries - found within ASoC topology manifest - and path templates - found within DAPM widget private data. Dictionaries job is to reduce the total amount of memory
Or are the use cases baked into the driver code if they're in the DAPM widget private data?
I'm sorry for any confusion that arisen in above commit message. avs-driver still makes use of typical UCM files, just like skylake-driver does. The file has just a different structure (e.g. different tokens used) to do both simultaneously: make use of available ASoC-topology structs and types while still allowing for shaping streams in recommended/optimal manner on ADSP side.
To better understand what I meant by 'shaping': a stream on ADSP side is a sequence of pipelines (a sophisticated containers) where each hosts one or a number of modules (processing units). The number of pipelines and numbers found within them is not constant. In almost all cases such stream on ADSP side has a beginning and an end. 'Gateway' is the word used when describing the edges of a stream. There is usually a HOST (cpu side) gateway and a LINK (hw side e.g. I2S; 'codec' comes probably as the closest relative word from ALSA dictionary) gateway. To preserve ADSP memory and reduce power consumption there are recommendations of how to shape streams.
ADSP firmware supports a large number of configurations and stream layouts. Depending on the environment, (even by changing BE/FE formats provided), stream layout can differ. Let's assume 3 ADSP module types: module1, module2 and module3 and a single playback FE "Speaker" visible in userspace.
Your typical playback on FE: "Speaker" for audio format: 16b, 2ch, 48kHz could take following shape:
HOST [ [module1] ] -> [ [module2] ] LINK pipeline1 pipeline2
However, for a different format (e.g. 44.1Khz), it might be recommended to do:
HOST [ [module1] -> [module3] ] -> [ [module2] ] LINK pipeline1 pipeline2
HOST: cpu side LINK: hw side e.g. I2S interface.
And that's why information attached to DAPM widget's private data translates to 'set' of concrete stream implementations (i.e. concrete data that is later sent to firmware over IPC) rather than a single stream implementation. This 'set' has been called 'path template' and each entry a 'path variant'. During runtime, depending on the conditions provided in hw_params(), a single variant is selected from the set and instantiated.
+struct avs_tplg_token_parser {
- enum avs_tplg_token token;
- u32 type;
- u32 offset;
- int (*parse)(struct snd_soc_component *comp, void *elem, void *object, u32 offset);
+};
+static int +avs_parse_uuid_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{
- struct snd_soc_tplg_vendor_value_elem *tuple = elem;
- guid_t *val = (guid_t *)((u8 *)object + offset);
- guid_copy((guid_t *)val, (const guid_t *)&tuple->value);
- return 0;
+}
I have to say I'm having a hard time telling if these parsers are doing the right thing - the interface is a bit obscure and all the void *s make it hard to follow, and of course the format is undocumented. I looked through a lot of it but I've definitely not gone through this code properly.
Again, I'm sorry for the confusion with the parsing technique used here. Ack on the documenting the general idea behind parsing seen in topology.c file.
To give some immediate insight: Each 'struct avs_tplg_token_parser *' instance describes a recipe for parsing data coming from UCM topology file and assigning obtained value to a field of avs-driver specific structure so it can be used later on during runtime - streaming.
Let's take a look at an example: static const struct avs_tplg_token_parser audio_format_parsers[] = { { .token = AVS_TKN_AFMT_SAMPLE_RATE_U32 .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, .offset = offsetof(struct avs_audio_format, sampling_freq), .parse = avs_parse_word_token, }, (...) };
Entry found above is meant to parse vendor tuples with token=AVS_TKN_AFMT_SAMPLE_RATE_U32 and assign value coupled with that token to a field 'sampling_freq' of struct avs_audio_format. Let take a look at said struct:
struct avs_audio_format { u32 sampling_freq; u32 bit_depth; u32 channel_map; u32 channel_config; u32 interleaving; u32 num_channels:8; u32 valid_bit_depth:8; u32 sample_type:8; u32 reserved:8; } __packed;
For every member (except for 'reserved' one of corse) of that struct, a parser will be assigned so a complete set of information that comes from UCM file can be eventually translated into an instance of 'struct avs_audio_format *'. 'offset' and 'type' members of struct avs_tplg_token_parser are here to ensure correct field is assigned to and specify the value-type of such field.
At the top of topology.c file we have placed several helpers, all of that is done to drastically reduce the size of code found below them. These helpers are also equipped with sanity-checks - these checks were one of the major reasons for separating the helper code into functions so all the if-statements are not repeated dozens of times.
Now, in regard to types: standard types supported by vendor tuples are uuid, bool, byte, short, word and string. We have added a 'default' parser for each of them. Again, let's look at an example:
static int avs_parse_word_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) { struct snd_soc_tplg_vendor_value_elem *tuple = elem; u32 *val = (u32 *)((u8 *)object + offset);
*val = le32_to_cpu(tuple->value);
return 0; }
where: - comp, a soc component that is tied to topology being currently parsed - elem, an instance of a vendor tuple that is being parsed - object, an instance of an object which fields are being assigned to during currently ongoing parsing procedure - offset, offset of a field that is a member of type which 'object' is instance of
From here, the flow is straightforward: obtain the pointer to the field to assign value of 'tuple->value' to and assign it.
For all 'more advanced' fields, additional parsers have been provided - with more checks and such to maintain sanity.
Regards, Czarek