diff --git a/test/Makefile.am b/test/Makefile.am index ce6e521d..cbf2ab4d 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -3,7 +3,7 @@ SUBDIRS=. lsb check_PROGRAMS=control pcm pcm_min latency seq \ playmidi1 timer rawmidi midiloop \ oldapi queue_timer namehint client_event_filter \ - chmap audio_time + chmap audio_time user-ctl-element-set control_LDADD=../src/libasound.la pcm_LDADD=../src/libasound.la @@ -24,6 +24,9 @@ code_CFLAGS=-Wall -pipe -g -O2 chmap_LDADD=../src/libasound.la audio_time_LDADD=../src/libasound.la +user_ctl_element_set_LDADD=../src/libasound.la +user_ctl_element_set_CFLAGS=-Wall -g + AM_CPPFLAGS=-I$(top_srcdir)/include AM_CFLAGS=-Wall -pipe -g diff --git a/test/user-ctl-element-set.c b/test/user-ctl-element-set.c new file mode 100644 index 00000000..09c79291 --- /dev/null +++ b/test/user-ctl-element-set.c @@ -0,0 +1,573 @@ +/* + * user-control-element-set.c - a program to test in-kernel implementation of + * user-defined control element set. + * + * Copyright (c) 2015-2016 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "../include/asoundlib.h" + +struct elem_set_trial { + snd_ctl_t *handle; + + snd_ctl_elem_type_t type; + unsigned int member_count; + unsigned int element_count; + + snd_ctl_elem_id_t *id; + + int (*add_elem_set)(struct elem_set_trial *trial); + int (*check_elem_props)(struct elem_set_trial *trial, + snd_ctl_elem_info_t *info); + void (*change_elem_members)(struct elem_set_trial *trial, + snd_ctl_elem_value_t *elem_data); +}; + +/* Operations for elements in an element set with boolean type. */ +static int add_bool_elem_set(struct elem_set_trial *trial) +{ + return snd_ctl_elem_add_boolean_set(trial->handle, trial->id, + trial->element_count, trial->member_count); +} + +static void change_bool_elem_members(struct elem_set_trial *trial, + snd_ctl_elem_value_t *elem_data) +{ + int val; + unsigned int i; + + for (i = 0; i < trial->member_count; ++i) { + val = snd_ctl_elem_value_get_boolean(elem_data, i); + snd_ctl_elem_value_set_boolean(elem_data, i, !val); + } +} + +/* Operations for elements in an element set with integer type. */ +static int add_int_elem_set(struct elem_set_trial *trial) +{ + return snd_ctl_elem_add_integer_set(trial->handle, trial->id, + trial->element_count, trial->member_count, + 0, 99, 1); +} + +static int check_int_elem_props(struct elem_set_trial *trial, + snd_ctl_elem_info_t *info) +{ + if (snd_ctl_elem_info_get_min(info) != 0) + return -EIO; + if (snd_ctl_elem_info_get_max(info) != 99) + return -EIO; + if (snd_ctl_elem_info_get_step(info) != 1) + return -EIO; + + return 0; +} + +static void change_int_elem_members(struct elem_set_trial *trial, + snd_ctl_elem_value_t *elem_data) +{ + long val; + unsigned int i; + + for (i = 0; i < trial->member_count; ++i) { + val = snd_ctl_elem_value_get_integer(elem_data, i); + snd_ctl_elem_value_set_integer(elem_data, i, ++val); + } +} + +/* Operations for elements in an element set with enumerated type. */ +static const char *const labels[] = { + "trusty", + "utopic", + "vivid", + "willy", + "xenial", +}; + +static int add_enum_elem_set(struct elem_set_trial *trial) +{ + return snd_ctl_elem_add_enumerated_set(trial->handle, trial->id, + trial->element_count, trial->member_count, + sizeof(labels) / sizeof(labels[0]), + labels); +} + +static int check_enum_elem_props(struct elem_set_trial *trial, + snd_ctl_elem_info_t *info) +{ + unsigned int items; + unsigned int i; + const char *label; + int err; + + items = snd_ctl_elem_info_get_items(info); + if (items != sizeof(labels) / sizeof(labels[0])) + return -EIO; + + /* Enumerate and validate all of labels registered to this element. */ + for (i = 0; i < items; ++i) { + snd_ctl_elem_info_set_item(info, i); + err = snd_ctl_elem_info(trial->handle, info); + if (err < 0) + return err; + + label = snd_ctl_elem_info_get_item_name(info); + if (strncmp(label, labels[i], strlen(labels[i])) != 0) + return -EIO; + } + + return 0; +} + +static void change_enum_elem_members(struct elem_set_trial *trial, + snd_ctl_elem_value_t *elem_data) +{ + unsigned int val; + unsigned int i; + + for (i = 0; i < trial->member_count; ++i) { + val = snd_ctl_elem_value_get_enumerated(elem_data, i); + snd_ctl_elem_value_set_enumerated(elem_data, i, ++val); + } +} + +/* Operations for elements in an element set with bytes type. */ +static int add_bytes_elem_set(struct elem_set_trial *trial) +{ + return snd_ctl_elem_add_bytes_set(trial->handle, trial->id, + trial->element_count, trial->member_count); +} + +static void change_bytes_elem_members(struct elem_set_trial *trial, + snd_ctl_elem_value_t *elem_data) +{ + unsigned char val; + unsigned int i; + + for (i = 0; i < trial->member_count; ++i) { + val = snd_ctl_elem_value_get_byte(elem_data, i); + snd_ctl_elem_value_set_byte(elem_data, i, ++val); + } +} + +/* Operations for elements in an element set with iec958 type. */ +static int add_iec958_elem_set(struct elem_set_trial *trial) +{ + return snd_ctl_elem_add_iec958(trial->handle, trial->id); +} + +static void change_iec958_elem_members(struct elem_set_trial *trial, + snd_ctl_elem_value_t *elem_data) +{ + snd_aes_iec958_t data; + + /* To suppress GCC warnings. */ + trial->element_count = 1; + + snd_ctl_elem_value_get_iec958(elem_data, &data); + /* This is an arbitrary number. */ + data.pad = 10; + snd_ctl_elem_value_set_iec958(elem_data, &data); +} + +/* Operations for elements in an element set with integer64 type. */ +static int add_int64_elem_set(struct elem_set_trial *trial) +{ + return snd_ctl_elem_add_integer64_set(trial->handle, trial->id, + trial->element_count, trial->member_count, + 100, 10000, 30); +} + +static int check_int64_elem_props(struct elem_set_trial *trial, + snd_ctl_elem_info_t *info) +{ + if (snd_ctl_elem_info_get_min64(info) != 100) + return -EIO; + if (snd_ctl_elem_info_get_max64(info) != 10000) + return -EIO; + if (snd_ctl_elem_info_get_step64(info) != 30) + return -EIO; + + return 0; +} + +static void change_int64_elem_members(struct elem_set_trial *trial, + snd_ctl_elem_value_t *elem_data) +{ + long long val; + unsigned int i; + + for (i = 0; i < trial->member_count; ++i) { + val = snd_ctl_elem_value_get_integer64(elem_data, i); + snd_ctl_elem_value_set_integer64(elem_data, i, ++val); + } +} + +/* Common operations. */ +static int add_elem_set(struct elem_set_trial *trial) +{ + char name[64] = {0}; + + snprintf(name, 64, "userspace-control-element-%s", + snd_ctl_elem_type_name(trial->type)); + + memset(trial->id, 0, snd_ctl_elem_id_sizeof()); + snd_ctl_elem_id_set_interface(trial->id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(trial->id, name); + + return trial->add_elem_set(trial); +} + +static int check_event(struct elem_set_trial *trial, unsigned int mask, + unsigned int expected_count) +{ + struct pollfd pfds; + int count; + unsigned short revents; + snd_ctl_event_t *event; + int err; + + snd_ctl_event_alloca(&event); + + if (snd_ctl_poll_descriptors_count(trial->handle) != 1) + return -ENXIO; + + if (snd_ctl_poll_descriptors(trial->handle, &pfds, 1) != 1) + return -ENXIO; + + while (expected_count > 0) { + count = poll(&pfds, 1, 1000); + if (count < 0) + return errno; + /* Some events are already supplied. */ + if (count == 0) + return -ETIMEDOUT; + + err = snd_ctl_poll_descriptors_revents(trial->handle, &pfds, + count, &revents); + if (err < 0) + return err; + if (revents & POLLERR) + return -EIO; + if (!(revents & POLLIN)) + continue; + + err = snd_ctl_read(trial->handle, event); + if (err < 0) + return err; + if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM) + continue; + /* + * I expect each event is generated separately to the same + * element. + */ + if (!(snd_ctl_event_elem_get_mask(event) & mask)) + continue; + --expected_count; + } + + if (expected_count != 0) + return -EIO; + + return 0; +} + +static int check_elem_set_props(struct elem_set_trial *trial) +{ + snd_ctl_elem_id_t *id; + snd_ctl_elem_info_t *info; + unsigned int numid; + unsigned int i; + int err; + + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_info_alloca(&info); + + snd_ctl_elem_info_set_id(info, trial->id); + numid = snd_ctl_elem_info_get_numid(info); + + for (i = 0; i < trial->element_count; ++i) { + snd_ctl_elem_info_set_numid(info, numid + i); + + err = snd_ctl_elem_info(trial->handle, info); + if (err < 0) + return err; + + /* Check some common properties. */ + if (snd_ctl_elem_info_get_type(info) != trial->type) + return -EIO; + if (snd_ctl_elem_info_get_count(info) != trial->member_count) + return -EIO; + + /* + * In a case of IPC, this is the others. But in this case, + * it's myself. + */ + if (snd_ctl_elem_info_get_owner(info) != getpid()) + return -EIO; + + /* + * Just adding an element set by userspace applications, + * included elements are initially locked. + */ + if (!snd_ctl_elem_info_is_locked(info)) + return -EIO; + + /* Check type-specific properties. */ + if (trial->check_elem_props != NULL) { + err = trial->check_elem_props(trial, info); + if (err < 0) + return err; + } + + snd_ctl_elem_info_get_id(info, id); + err = snd_ctl_elem_unlock(trial->handle, id); + if (err < 0) + return err; + } + + return 0; +} + +static int check_elems(struct elem_set_trial *trial) +{ + snd_ctl_elem_value_t *data; + unsigned int numid; + unsigned int i; + int err; + + snd_ctl_elem_value_alloca(&data); + + snd_ctl_elem_value_set_id(data, trial->id); + + /* + * Get the value of numid field in identical information for the first + * element of this element set. + */ + numid = snd_ctl_elem_value_get_numid(data); + + for (i = 0; i < trial->element_count; ++i) { + /* + * Here, I increment numid, while we can also increment offset + * to enumerate each element in this element set. + */ + snd_ctl_elem_value_set_numid(data, numid + i); + + err = snd_ctl_elem_read(trial->handle, data); + if (err < 0) + return err; + + /* Change members of an element in this element set. */ + trial->change_elem_members(trial, data); + + err = snd_ctl_elem_write(trial->handle, data); + if (err < 0) + return err; + } + + return 0; +} + +static int check_tlv(struct elem_set_trial *trial) +{ + unsigned int orig[8], curr[8]; + int err; + + /* + * See a layout of 'struct snd_ctl_tlv'. I don't know the reason to + * construct this buffer with the same layout. It should be abstracted + * inner userspace library... + */ + orig[0] = snd_ctl_elem_id_get_numid(trial->id); + orig[1] = 6 * sizeof(orig[0]); + orig[2] = 'a'; + orig[3] = 'b'; + orig[4] = 'c'; + orig[5] = 'd'; + orig[6] = 'e'; + orig[7] = 'f'; + + /* + * In in-kernel implementation, write and command operations are the + * same for an element set added by userspace applications. Here, I + * use write. + */ + err = snd_ctl_elem_tlv_write(trial->handle, trial->id, + (const unsigned int *)orig); + if (err < 0) + return err; + + err = snd_ctl_elem_tlv_read(trial->handle, trial->id, curr, + sizeof(curr)); + if (err < 0) + return err; + + if (memcmp(curr, orig, sizeof(orig)) != 0) + return -EIO; + + return 0; +} + +int main(void) +{ + struct elem_set_trial trial = {0}; + unsigned int i; + int err; + + snd_ctl_elem_id_alloca(&trial.id); + + err = snd_ctl_open(&trial.handle, "hw:0", 0); + if (err < 0) + return EXIT_FAILURE; + + err = snd_ctl_subscribe_events(trial.handle, 1); + if (err < 0) + return EXIT_FAILURE; + + /* Test all of types. */ + for (i = 0; i < SND_CTL_ELEM_TYPE_LAST; ++i) { + trial.type = i + 1; + + /* Assign type-dependent operations. */ + switch (trial.type) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + trial.element_count = 900; + trial.member_count = 128; + trial.add_elem_set = add_bool_elem_set; + trial.check_elem_props = NULL; + trial.change_elem_members = change_bool_elem_members; + break; + case SND_CTL_ELEM_TYPE_INTEGER: + trial.element_count = 900; + trial.member_count = 128; + trial.add_elem_set = add_int_elem_set; + trial.check_elem_props = check_int_elem_props; + trial.change_elem_members = change_int_elem_members; + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + trial.element_count = 900; + trial.member_count = 128; + trial.add_elem_set = add_enum_elem_set; + trial.check_elem_props = check_enum_elem_props; + trial.change_elem_members = change_enum_elem_members; + break; + case SND_CTL_ELEM_TYPE_BYTES: + trial.element_count = 900; + trial.member_count = 512; + trial.add_elem_set = add_bytes_elem_set; + trial.check_elem_props = NULL; + trial.change_elem_members = change_bytes_elem_members; + break; + case SND_CTL_ELEM_TYPE_IEC958: + trial.element_count = 1; + trial.member_count = 1; + trial.add_elem_set = add_iec958_elem_set; + trial.check_elem_props = NULL; + trial.change_elem_members = change_iec958_elem_members; + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + default: + trial.element_count = 900; + trial.member_count = 64; + trial.add_elem_set = add_int64_elem_set; + trial.check_elem_props = check_int64_elem_props; + trial.change_elem_members = change_int64_elem_members; + break; + } + + /* Test an operation to add an element set. */ + err = add_elem_set(&trial); + if (err < 0) { + printf("Fail to add an element set with %s type.\n", + snd_ctl_elem_type_name(trial.type)); + break; + } + err = check_event(&trial, SND_CTL_EVENT_MASK_ADD, + trial.element_count); + if (err < 0) { + printf("Fail to check some events to add elements with " + "%s type.\n", + snd_ctl_elem_type_name(trial.type)); + break; + } + + /* Check properties of each element in this element set. */ + err = check_elem_set_props(&trial); + if (err < 0) { + printf("Fail to check propetries of each element with " + "%s type.\n", + snd_ctl_elem_type_name(trial.type)); + break; + } + + /* + * Test operations to change the state of members in each + * element in the element set. + */ + err = check_elems(&trial); + if (err < 0) { + printf("Fail to change status of each element with %s " + "type.\n", + snd_ctl_elem_type_name(trial.type)); + break; + } + err = check_event(&trial, SND_CTL_EVENT_MASK_VALUE, + trial.element_count); + if (err < 0) { + printf("Fail to check some events to change status of " + "each elements with %s type.\n", + snd_ctl_elem_type_name(trial.type)); + break; + } + + /* + * Test an operation to change threshold data of this element set, + * except for IEC958 type. + */ + if (trial.type != SND_CTL_ELEM_TYPE_IEC958) { + err = check_tlv(&trial); + if (err < 0) { + printf("Fail to change threshold level of an " + "element set with %s type.\n", + snd_ctl_elem_type_name(trial.type)); + break; + } + err = check_event(&trial, SND_CTL_EVENT_MASK_TLV, 1); + if (err < 0) { + printf("Fail to check an event to change " + "threshold level of an an element set " + "with %s type.\n", + snd_ctl_elem_type_name(trial.type)); + break; + } + } + + /* Test an operation to remove elements in this element set. */ + err = snd_ctl_elem_remove(trial.handle, trial.id); + if (err < 0) { + printf("Fail to remove elements with %s type.\n", + snd_ctl_elem_type_name(trial.type)); + break; + } + err = check_event(&trial, SND_CTL_EVENT_MASK_REMOVE, + trial.element_count); + if (err < 0) { + printf("Fail to check some events to remove each " + "element with %s type.\n", + snd_ctl_elem_type_name(trial.type)); + break; + } + } + + if (err < 0) { + printf("%s\n", snd_strerror(err)); + + /* To ensure. */ + snd_ctl_elem_remove(trial.handle, trial.id); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +}