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