[RFC 18/37] ASoC: Intel: avs: Topology parsing

Cezary Rojewski cezary.rojewski at intel.com
Wed Dec 22 15:21:54 CET 2021


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


More information about the Alsa-devel mailing list