Compare commits

...

8 commits

Author SHA1 Message Date
Ranjani Sridharan
70615ba71b
Merge ffc63f76de into 02b0c3af56 2024-07-24 15:41:12 -07:00
Takashi Iwai
02b0c3af56 aseqdump: Avoid OOB access with broken SysEx UMP packets
UMP SysEx messages have length field to specify the contained data
bytes, but they can be over the actual packet size.  Add the proper
size limit checks for avoiding the access overflow.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-24 14:05:55 +02:00
Takashi Iwai
df736ad67a aseqdump: Support of UMP 8-bit SysEx messages
Add the support to dump UMP 8-bit SysEx messages.
A slight code refactoring to share the code snippet between 7bit and
8bit SysEx handling, too.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-24 13:59:05 +02:00
Takashi Iwai
7de3cd3b8d aseqsend: Simplify using the standard helper function
Use the standard MIDI event encoder function provided in alsa-lib and
simplify the code.  We can reduce a lot of lines.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-23 15:50:57 +02:00
Takashi Iwai
580ea3c85e aseqsend: Move snd_seq_set_client_midi_version() call out of main()
Move the low-level API function call to init_seq() instead of calling
directly from main().  Just a code refactoring.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-23 13:51:18 +02:00
Takashi Iwai
dec1ef064a aseqsend: Refine man page
Make man page a bit nicer; reformatting and setting bold/italic
properly, typo fixes, and adding a few more words and sections.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-23 13:44:05 +02:00
Takashi Iwai
df68ec3343 aseqsend: Update the help texts for long options
The help text was forgotten to be updated for the new long options.
Updated now.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-23 13:41:07 +02:00
Ranjani Sridharan
ffc63f76de topology: pre-processor: Introduce a new feature for subtree copies
Introduce a new kyword "SubTreeCopy" for extneding existing conf nodes
with additional nodes. This feature is useful for extending previous
pipeline class definitions with the addition of one or more widgets
without having to duplicate everything in the new class definition.

For example: Consider a pipeline class definition as below. Note that
only the widgets & routes are shown here.

Class.Pipeline.mixout-gain-dai-copier-playback {
	Object.Widget {
		mixout."1" {}
		dai-copier."1" {}
		gain."1" {}
		pipeline."1" {}
	}

	Object.Base {
		!route [
			{
				source mixout.$index.1
				sink	gain.$index.1
			}
		]
	}
}

If we want to extend this pipeline with the addition of an eqiir/eqfir,
we can create a SubTreeCopy node with type extend as follows:

Class.Pipeline.mixout-gain-eqiir-eqfir-dai-copier-playback {
	SubTreeCopy.baseclass {
		source "Class.Pipeline.mixout-gain-dai-copier-playback"
		type extend

                tree {
			Object.Widget {
				eqiir.1 {}
				eqfir.1 {}
			}

			Object.Base {
				!route [
					{
						source gain.$index.1
						sink   eqiir.$index.1
					}
					{
						source eqiir.$index.1
						sink   eqfir.$index.1
					}
				]
			}
		}
	}
}

Note that the target is left undefined, which means that the newly
created subtree will be merged to the parent node that contains the
"SubTreeCopy" node i.e. in this case
Class.Pipeline.mixout-gain-eqiir-eqfir-dai-copier-playback".

But if we want to modify an existing pipeline class while modifying the
order of widgets and/or inserting new widgets, we should use the type
"override" instead. This allows for adding new widgets to the list of
widgets in the base class definition while also allowing overriding the
routes to allow inserting the new widgets and reordering the widgets in
the base class. For example, if we want to add a drc widget between the
gain and the eqiir modules in the above class, we can do the following:

Class.Pipeline.mixout-efx-dai-copier-playback {
	# This copy will override all widgets/routes in the base class
	SubTreeCopy.baseclass {
		source "Class.Pipeline.mixout-gain-eqiir-eqfir-dai-copier-playback"
		target "Class.Pipeline"
		type override

		tree {
			Object.Widget {
				drc.1 {}
			}

			Object.Base {
				!route [
					{
						source mixout.$index.1
						sink	gain.$index.1
					}
					{
						source gain.$index.1
						sink	drc.$index.1
					}
					{
						source	drc.$index.1
						sink	eqiir.$index.1
					}
					{
						source	eqiir.$index.1
						sink	eqfir.$index.1
					}
				]
			}
		}
	}

	# Explicitly copy the widgets from the base class now
	SubTreeCopy.widgets {
		source "Class.Pipeline.mixout-gain-eqiir-eqfir-dai-copier-playback.Object.Widget"
		target "Class.Pipeline.mixout-efx-dai-copier-playback.Object.Widget"
	}
}

Signed-off-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
2024-07-12 10:25:21 -07:00
4 changed files with 329 additions and 263 deletions

View file

@ -668,13 +668,10 @@ static unsigned char ump_sysex7_data(const unsigned int *ump,
return (ump[offset / 4] >> ((3 - (offset & 3)) * 8)) & 0xff;
}
static void dump_ump_sysex_event(const unsigned int *ump)
static void dump_ump_sysex_status(const char *prefix, unsigned int status)
{
int i, length;
printf("Group %2d, ", group_number(snd_ump_msg_group(ump)));
printf("SysEx ");
switch (snd_ump_sysex_msg_status(ump)) {
printf("%s ", prefix);
switch (status) {
case SND_UMP_SYSEX_STATUS_SINGLE:
printf("Single ");
break;
@ -688,17 +685,49 @@ static void dump_ump_sysex_event(const unsigned int *ump)
printf("End ");
break;
default:
printf("Unknown(0x%x)", snd_ump_sysex_msg_status(ump));
printf("(0x%04x)", status);
break;
}
}
static void dump_ump_sysex_event(const unsigned int *ump)
{
int i, length;
printf("Group %2d, ", group_number(snd_ump_msg_group(ump)));
dump_ump_sysex_status("SysEx", snd_ump_sysex_msg_status(ump));
length = snd_ump_sysex_msg_length(ump);
printf(" length %d ", length);
if (length > 14)
length = 14;
for (i = 0; i < length; i++)
printf("%s%02x", i ? ":" : "", ump_sysex7_data(ump, i));
printf("\n");
}
static unsigned char ump_sysex8_data(const unsigned int *ump,
unsigned int offset)
{
offset += 3;
return (ump[offset / 4] >> ((3 - (offset & 3)) * 8)) & 0xff;
}
static void dump_ump_sysex8_event(const unsigned int *ump)
{
int i, length;
printf("Group %2d, ", group_number(snd_ump_msg_group(ump)));
dump_ump_sysex_status("SysEx8", snd_ump_sysex_msg_status(ump));
length = snd_ump_sysex_msg_length(ump);
printf(" length %d ", length);
printf(" stream %d ", (ump[0] >> 8) & 0xff);
if (length > 13)
length = 13;
for (i = 0; i < length; i++)
printf("%s%02x", i ? ":" : "", ump_sysex8_data(ump, i));
printf("\n");
}
static void print_ump_string(const unsigned int *ump, unsigned int fmt,
unsigned int offset, int maxlen)
{
@ -965,6 +994,9 @@ static void dump_ump_event(const snd_seq_ump_event_t *ev)
case SND_UMP_MSG_TYPE_DATA:
dump_ump_sysex_event(ev->ump);
break;
case SND_UMP_MSG_TYPE_EXTENDED_DATA:
dump_ump_sysex8_event(ev->ump);
break;
case SND_UMP_MSG_TYPE_FLEX_DATA:
dump_ump_flex_data_event(ev->ump);
break;

View file

@ -1,55 +1,63 @@
.TH ASEQSEND 1 "11 Mar 2024"
.SH NAME
.B aseqsend
\- send arbitrary messages to selected ALSA MIDI seqencer port
aseqsend \- send arbitrary messages to selected ALSA MIDI seqencer port
.SH SYNOPSIS
aseqsend \-p client:port -s file-name|"hex encoded byte-string"
\fBaseqsend\fP \-p client:port -s file-name
.br
\fBaseqsend\fP \-p client:port "hex encoded byte-string"
.SH DESCRIPTION
.B aseqsend
is a command-line utility which allows one to send SysEx (system exclusive) data to ALSA MIDI seqencer port.
\fBaseqsend\fP is a command-line utility which allows one to send
SysEx (system exclusive) data to ALSA MIDI sequencer port.
It can also send any other MIDI commands.
Messages to be send can be given in the last argument as hex encoded byte string or can be read from raw binary file.
When sending several SysEx messages at once there is a delay of 1ms after each message as deafult and can be set to different value with option \-i.
Messages to be sent can be given in the last argument as hex encoded
byte string or can be read from raw binary file.
When sending several SysEx messages at once there is a delay of 1ms
after each message as default and can be set to different value with
option \-i.
A client can be specified by its number, its name, or a prefix of its
name. A port is specified by its number; for port 0 of a client, the
":0" part of the port specification can be omitted.
\fBaseqsend\fP can send UMP packets as MIDI 2.0 device by specifying
via \-u option as well, while the default operation is the legacy MIDI
1.0 byte stream.
.SH OPTIONS
.TP
\-h, \-\-help
\fI\-h, \-\-help\fP
Prints a list of options.
.TP
\-V, \-\-version
\fI\-V, \-\-version\fP
Prints the current version.
.TP
\-l, \-\-list
\fI\-l, \-\-list\FP
Prints a list of possible output ports.
.TP
\-v, \-\-verbose
\fI\-v, \-\-verbose\fP
Prints number of bytes actually sent
.TP
\-p, -\-port=client:port
\fI\-p, -\-port=client:port\fP
Target port by number or name
.TP
\-s, \-\-file=filename
\fI\-s, \-\-file=filename\fP
Send raw binary data from given file name
.TP
\-i, \-\-interval=msec
Interval between SysEx messages in miliseconds
\fI\-i, \-\-interval=msec\fP
Interval between SysEx messages in milliseconds
.TP
\-u, \-\-ump=version
\fI\-u, \-\-ump=version\fP
Specify the MIDI version. 0 for the legacy MIDI 1.0 (default),
1 for UMP MIDI 1.0 protocol and 2 for UMP MIDI 2.0 protocol.
@ -58,9 +66,12 @@ reads the input as raw UMP packets, 4 each byte in big endian.
.SH EXAMPLES
aseqsend -p 128:0 "F0 41 10 00 00 64 12 18 00 21 06 59 41 59 4E F7"
\fBaseqsend -p 128:0 "F0 41 10 00 00 64 12 18 00 21 06 59 41 59 4E F7"\fP
aseqsend -p 128:0 -s I7BulkDump.syx
\fBaseqsend -p 128:0 -s I7BulkDump.syx\fP
.SH SEE ALSO
\fBaseqdump(1)\fP
.SH AUTHOR
Miroslav Kovac <mixxoo@gmail.com>

View file

@ -44,7 +44,10 @@ static char *send_hex;
static mbyte_t *send_data;
static snd_seq_addr_t addr;
static int send_data_length;
static int sent_data_c;
static int ump_version;
static int sysex_interval = 1000; //us
static snd_midi_event_t *edev;
static void error(const char *format, ...)
{
@ -72,13 +75,14 @@ static void usage(void)
{
printf(
"\nUsage: aseqsend -p target-port -s file-name|\"hex encoded bytes\"\n\n"
" -h this help\n"
" -V print current version\n"
" -v verbose\n"
" -l list all sequencer ports\n"
" -p target port by number or name\n"
" -s send binary data from given file name\n"
" -i interval between SysEx messages in miliseconds\n\n");
" -h,--help this help\n"
" -V,--version print current version\n"
" -v,--verbose verbose mode\n"
" -l,--list list all sequencer ports\n"
" -p,--port=c:p target port by number or name\n"
" -s,--file=name send binary data from given file name\n"
" -i,--interval=v interval between SysEx messages in miliseconds\n"
" -u,--ump=version MIDI version: 0=legacy (default), 1=MIDI1, 2=MIDI2\n\n");
}
static void version(void)
@ -211,6 +215,9 @@ static void init_seq(void)
/* set our client's name */
err = snd_seq_set_client_name(seq, "aseqsend");
check_snd("set client name", err);
err = snd_seq_set_client_midi_version(seq, ump_version);
check_snd("set client midi version", err);
}
static void create_port(void)
@ -224,6 +231,13 @@ static void create_port(void)
check_snd("create port", err);
}
static void init_midi_event_encoder(void)
{
int err;
err = snd_midi_event_new(256, &edev);
check_snd("create midi event encoder", err);
}
static void list_ports(void)
{
@ -256,110 +270,82 @@ static void list_ports(void)
}
}
static void send_midi_msg(snd_seq_event_type_t type, mbyte_t *data, int len)
/* compose a UMP event, submit it, return the next data position */
static int send_ump(int pos)
{
int ump_len = 0, offset = 0;
unsigned int ump[4];
snd_seq_ump_event_t ev;
snd_seq_ump_ev_clear(&ev);
snd_seq_ev_set_source(&ev, 0);
snd_seq_ev_set_dest(&ev, addr.client, addr.port);
snd_seq_ev_set_direct(&ev);
do {
const mbyte_t *data = send_data + pos;
if (pos >= send_data_length)
return pos;
ump[offset] = (data[0] << 24) | (data[1] << 16) |
(data[2] << 8) | data[3];
if (!offset)
ump_len = snd_ump_packet_length(snd_ump_msg_type(ump));
offset++;
pos += 4;
} while (offset < ump_len);
snd_seq_ev_set_ump_data(&ev, ump, ump_len * 4);
snd_seq_ump_event_output(seq, &ev);
snd_seq_drain_output(seq);
sent_data_c += ump_len * 4;
return pos;
}
/* compose an event, submit it, return the next data position */
static int send_midi_bytes(int pos)
{
const mbyte_t *data = send_data + pos;
snd_seq_event_t ev;
int is_sysex = 0;
int end;
snd_seq_ev_clear(&ev);
snd_seq_ev_set_source(&ev, 0);
snd_seq_ev_set_dest(&ev, addr.client, addr.port);
snd_seq_ev_set_direct(&ev);
if (type == SND_SEQ_EVENT_SYSEX) {
snd_seq_ev_set_sysex(&ev, len, data);
} else {
mbyte_t ch = data[0] & 0xF;
switch (type) {
case SND_SEQ_EVENT_NOTEON:
snd_seq_ev_set_noteon(&ev, ch, data[1], data[2]);
if (send_data[pos] == 0xf0) {
is_sysex = 1;
for (end = pos + 1; end < send_data_length; end++) {
if (send_data[end] == 0xf7)
break;
case SND_SEQ_EVENT_NOTEOFF:
snd_seq_ev_set_noteoff(&ev, ch, data[1], data[2]);
break;
case SND_SEQ_EVENT_KEYPRESS:
snd_seq_ev_set_keypress(&ev, ch, data[1], data[2]);
break;
case SND_SEQ_EVENT_CONTROLLER:
snd_seq_ev_set_controller(&ev, ch, data[1], data[2]);
break;
case SND_SEQ_EVENT_PITCHBEND:
snd_seq_ev_set_pitchbend(&ev, ch, (data[1]<<7|data[2])-8192);
break;
case SND_SEQ_EVENT_PGMCHANGE:
snd_seq_ev_set_pgmchange(&ev, ch, data[1]);
break;
case SND_SEQ_EVENT_CHANPRESS:
snd_seq_ev_set_chanpress(&ev, ch, data[1]);
break;
case SND_SEQ_EVENT_QFRAME:
case SND_SEQ_EVENT_SONGSEL:
ev.type = type;
ev.data.control.channel = ch;
ev.data.control.value = data[1];
break;
case SND_SEQ_EVENT_SONGPOS:
ev.type = type;
ev.data.control.channel = ch;
ev.data.control.value = data[1] | (data[2] << 7);
break;
case SND_SEQ_EVENT_TUNE_REQUEST:
case SND_SEQ_EVENT_CLOCK:
case SND_SEQ_EVENT_START:
case SND_SEQ_EVENT_CONTINUE:
case SND_SEQ_EVENT_STOP:
case SND_SEQ_EVENT_SENSING:
case SND_SEQ_EVENT_RESET:
ev.type = type;
ev.data.control.channel = ch;
break;
default:
ev.type = SND_SEQ_EVENT_NONE;
}
if (end == send_data_length)
fatal("SysEx is missing terminating byte (0xF7)");
end++;
snd_seq_ev_set_sysex(&ev, end - pos, send_data + pos);
} else {
end = pos;
while (!snd_midi_event_encode_byte(edev, *data++, &ev)) {
if (++end >= send_data_length)
return end;
}
end++;
}
snd_seq_event_output(seq, &ev);
snd_seq_drain_output(seq);
if (is_sysex)
usleep(sysex_interval);
sent_data_c += end - pos;
return end;
}
static int send_ump(const unsigned char *data)
{
static int ump_len = 0, offset = 0;
unsigned int ump[4];
snd_seq_ump_event_t ev;
ump[offset] = (data[0] << 24) | (data[1] << 16) |
(data[2] << 8) | data[3];
if (!offset)
ump_len = snd_ump_packet_length(snd_ump_msg_type(ump));
offset++;
if (offset < ump_len)
return 0;
snd_seq_ump_ev_clear(&ev);
snd_seq_ev_set_source(&ev, 0);
snd_seq_ev_set_dest(&ev, addr.client, addr.port);
snd_seq_ev_set_direct(&ev);
snd_seq_ev_set_ump_data(&ev, ump, ump_len * 4);
snd_seq_ump_event_output(seq, &ev);
snd_seq_drain_output(seq);
offset = 0;
return ump_len * 4;
}
static int msg_byte_in_range(mbyte_t *data, mbyte_t len)
{
for (int i = 0; i < len; i++) {
if (data[i] > 0x7F) {
error("msg byte value out of range 0-127");
return 0;
}
}
return 1;
}
int main(int argc, char *argv[])
{
static const struct option long_options[] = {
@ -377,8 +363,6 @@ int main(int argc, char *argv[])
char do_send_file = 0;
char do_port_list = 0;
char verbose = 0;
int sysex_interval = 1000; //us
int sent_data_c;
int k;
while ((c = getopt_long(argc, argv, "hi:Vvlp:s:u:", long_options, NULL)) != -1) {
@ -447,8 +431,9 @@ int main(int argc, char *argv[])
fatal("UMP data must be aligned to 4 bytes");
init_seq();
snd_seq_set_client_midi_version(seq, ump_version);
create_port();
if (!ump_version)
init_midi_event_encoder();
if (snd_seq_parse_address(seq, &addr, port_name) < 0) {
error("Unable to parse port name!");
@ -456,147 +441,13 @@ int main(int argc, char *argv[])
}
sent_data_c = 0; //counter of actually sent bytes
k = 0;
while (k < send_data_length) {
if (ump_version) {
sent_data_c += send_ump(send_data + k);
k += 4;
continue;
}
if (send_data[k] == 0xF0) {
int c1 = k;
while (c1 < send_data_length) {
if (send_data[c1] == 0xF7)
break;
c1++;
}
if (c1 == send_data_length)
fatal("SysEx is missing terminating byte (0xF7)");
int sl = c1-k+1;
sent_data_c += sl;
send_midi_msg(SND_SEQ_EVENT_SYSEX, send_data+k, sl);
usleep(sysex_interval);
k = c1+1;
} else {
mbyte_t tp = send_data[k] >> 4;
if (tp == 0x8) {
if (msg_byte_in_range(send_data + k + 1, 2)) {
send_midi_msg(SND_SEQ_EVENT_NOTEOFF, send_data+k, 3);
sent_data_c += 3;
}
k += 3;
} else if (tp == 0x9) {
if (msg_byte_in_range(send_data + k + 1, 2)) {
send_midi_msg(SND_SEQ_EVENT_NOTEON, send_data+k, 3);
sent_data_c += 3;
}
k += 3;
} else if (tp == 0xA) {
if (msg_byte_in_range(send_data + k + 1, 2)) {
send_midi_msg(SND_SEQ_EVENT_KEYPRESS, send_data+k, 3);
sent_data_c += 3;
}
k += 3;
} else if (tp == 0xB) {
if (msg_byte_in_range(send_data + k + 1, 2)) {
send_midi_msg(SND_SEQ_EVENT_CONTROLLER, send_data+k, 3);
sent_data_c += 3;
}
k += 3;
} else if (tp == 0xC) {
if (msg_byte_in_range(send_data + k + 1, 1)) {
send_midi_msg(SND_SEQ_EVENT_PGMCHANGE, send_data+k, 2);
sent_data_c += 2;
}
k += 2;
} else if (tp == 0xD) {
if (msg_byte_in_range(send_data + k + 1, 1)) {
send_midi_msg(SND_SEQ_EVENT_CHANPRESS, send_data+k, 2);
sent_data_c += 2;
}
k += 2;
} else if (tp == 0xE) {
if (msg_byte_in_range(send_data + k + 1, 2)) {
send_midi_msg(SND_SEQ_EVENT_PITCHBEND, send_data+k, 3);
sent_data_c += 3;
}
k += 3;
} else {
switch (send_data[k]) {
case 0xF1:
if (msg_byte_in_range(send_data + k + 1, 1)) {
send_midi_msg(SND_SEQ_EVENT_QFRAME, send_data+k, 2);
sent_data_c += 2;
}
k += 2;
break;
case 0xF2:
if (msg_byte_in_range(send_data + k + 1, 2)) {
send_midi_msg(SND_SEQ_EVENT_SONGPOS, send_data+k, 3);
sent_data_c += 3;
}
k += 3;
break;
case 0xF3:
if (msg_byte_in_range(send_data + k + 1, 1)) {
send_midi_msg(SND_SEQ_EVENT_SONGSEL, send_data+k, 2);
sent_data_c += 2;
}
k += 2;
break;
case 0xF6:
send_midi_msg(SND_SEQ_EVENT_TUNE_REQUEST, send_data+k, 1);
sent_data_c++;
k++;
break;
case 0xF8:
send_midi_msg(SND_SEQ_EVENT_CLOCK, send_data+k, 1);
sent_data_c++;
k++;
break;
case 0xFA:
send_midi_msg(SND_SEQ_EVENT_START, send_data+k, 1);
sent_data_c++;
k++;
break;
case 0xFB:
send_midi_msg(SND_SEQ_EVENT_CONTINUE, send_data+k, 1);
sent_data_c++;
k++;
break;
case 0xFC:
send_midi_msg(SND_SEQ_EVENT_STOP, send_data+k, 1);
sent_data_c++;
k++;
break;
case 0xFE:
send_midi_msg(SND_SEQ_EVENT_SENSING, send_data+k, 1);
sent_data_c++;
k++;
break;
case 0xFF:
send_midi_msg(SND_SEQ_EVENT_RESET, send_data+k, 1);
sent_data_c++;
k++;
break;
default:
k++;
break;
}
}
}
if (ump_version)
k = send_ump(k);
else
k = send_midi_bytes(k);
}
if (verbose)

View file

@ -921,6 +921,172 @@ static int pre_process_arrays(struct tplg_pre_processor *tplg_pp, snd_config_t *
return 0;
}
static int pre_process_subtree_copy(struct tplg_pre_processor *tplg_pp, snd_config_t *curr,
snd_config_t *top)
{
snd_config_iterator_t i, next;
snd_config_t *subtrees;
int ret;
ret = snd_config_search(curr, "SubTreeCopy", &subtrees);
if (ret < 0)
return 0;
snd_config_for_each(i, next, subtrees) {
snd_config_t *n, *source_cfg, *target_cfg, *type_cfg;
snd_config_t *source_tree, *target_tree, *tmp, *subtree_cfg;
const char *id, *source_id;
const char *type = NULL;
const char *target_id = NULL;
bool override = false;
n = snd_config_iterator_entry(i);
ret = snd_config_get_id(n, &id);
if (ret < 0) {
SNDERR("Failed to get ID for subtree copy\n");
return ret;
}
/* get the type of copy ex: override/extend if set, by default override is false */
ret = snd_config_search(n, "type", &type_cfg);
if (ret >= 0) {
ret = snd_config_get_string(type_cfg, &type);
if (ret >= 0) {
if (strcmp(type, "override") && strcmp(type, "extend")) {
SNDERR("Invalid value for sub tree copy type %s\n", type);
return ret;
}
if (!strcmp(type, "override"))
override = true;
}
}
ret = snd_config_search(n, "source", &source_cfg);
if (ret < 0) {
SNDERR("failed to get source config for subtree %s\n", id);
return ret;
}
/* if the target node is not set, the subtree will be copied to current node */
ret = snd_config_search(n, "target", &target_cfg);
if (ret >= 0) {
snd_config_t *temp_target;
char *s;
ret = snd_config_get_string(target_cfg, &target_id);
if (ret < 0) {
SNDERR("Invalid target id for subtree %s\n", id);
return ret;
}
/*
* create a temporary node with target_id and merge with top to avoid
* failure in case the target node ID doesn't exist already
*/
s = tplg_snprintf("%s {}", target_id);
if (!s)
return -ENOMEM;
ret = snd_config_load_string(&temp_target, s, 0);
free(s);
if (ret < 0) {
SNDERR("Cannot create temp node with target id %s\n", target_id);
return ret;
}
ret = snd_config_merge(top, temp_target, false);
if (ret < 0) {
SNDERR("Cannot merge temp node with target id %s\n", target_id);
return ret;
}
ret = snd_config_search(top, target_id, &target_tree);
if (ret < 0) {
SNDERR("failed to get target tree %s\n", target_id);
return ret;
}
} else {
target_tree = curr;
}
/* get the source tree node */
ret = snd_config_get_string(source_cfg, &source_id);
if (ret < 0) {
SNDERR("Invalid source node id for subtree %s\n", id);
return ret;
}
ret = snd_config_search(top, source_id, &source_tree);
if (ret < 0) {
SNDERR("failed to get source tree %s\n", source_id);
return ret;
}
/* get the subtree to be merged */
ret = snd_config_search(n, "tree", &subtree_cfg);
if (ret < 0)
subtree_cfg = NULL;
/* make a temp copy of the source tree */
ret = snd_config_copy(&tmp, source_tree);
if (ret < 0) {
SNDERR("failed to copy source tree for subtreecopy %s\n", id);
return ret;
}
/* merge the current block with the source tree */
ret = snd_config_merge(tmp, subtree_cfg, override);
if (ret < 0) {
snd_config_delete(tmp);
SNDERR("Failed to merge source tree w/ subtree %s\n", id);
return ret;
}
/* merge the merged block to the target tree */
ret = snd_config_merge(target_tree, tmp, override);
if (ret < 0) {
snd_config_delete(tmp);
SNDERR("Failed to merge subtree %s w/ target\n", id);
return ret;
}
}
/* all subtree copies have been processed, remove the node */
snd_config_delete(subtrees);
return 0;
}
static int pre_process_subtree_copies(struct tplg_pre_processor *tplg_pp, snd_config_t *top,
snd_config_t *curr)
{
snd_config_iterator_t i, next;
int ret;
if (snd_config_get_type(curr) != SND_CONFIG_TYPE_COMPOUND)
return 0;
/* process subtreecopies at this node */
ret = pre_process_subtree_copy(tplg_pp, curr, top);
if (ret < 0)
return ret;
/* process subtreecopies at all child nodes */
snd_config_for_each(i, next, curr) {
snd_config_t *n;
n = snd_config_iterator_entry(i);
/* process subtreecopies at this node */
ret = pre_process_subtree_copies(tplg_pp, top, n);
if (ret < 0)
return ret;
}
return 0;
}
#endif /* version < 1.2.6 */
int pre_process(struct tplg_pre_processor *tplg_pp, char *config, size_t config_size,
@ -980,6 +1146,12 @@ int pre_process(struct tplg_pre_processor *tplg_pp, char *config, size_t config_
fprintf(stderr, "Failed to process object arrays in input config\n");
goto err;
}
err = pre_process_subtree_copies(tplg_pp, tplg_pp->input_cfg, tplg_pp->input_cfg);
if (err < 0) {
SNDERR("Failed to process subtree copies in input config\n");
goto err;
}
#endif
err = pre_process_config(tplg_pp, tplg_pp->input_cfg);