The feature of control element set has been abandoned for a long time since
firstly introduced in 2003. Furthermore, there's few applications to utilize
this feature. These situations bring a hard work to persons who need the
feature. Especially, a lack of test program make it harder to fix much bugs
in this feature.
This commit adds a test program as a sample of the feature. This program
adds element sets of each element type to 'hw:0' in this order; boolean,
integer, enumerated, bytes, IEC958 and integer64. Each iteration includes
below scheme:
1. add an element set with 900 elements. Each of them has maximum number
of members allowed by ALSA ctl core.
2. check all of events generated by above operation.
3. retrieve information of each element, then validate it.
4. unlock each member of all elements because they're initially locked.
5. write to all of members in all elements and read.
6. check all of events generated by above operation.
7. write information for threshold level to the element set and read it.
8. check all of events generated by above operation.
9. remove the element set.
10.check all of events generated by above operation.
When any of these operations fail, it means regression occurs. Then, added
elements still remain in a certain sound card. In this case, unloading
drivers corresponding to the card is an easy way to recover.
Besides, this program doesn't perform below element operations of ALSA ctl
feature:
- list
- lock
- replace
Signed-off-by: Takashi Sakamoto <o-takashi(a)sakamocchi.jp>
---
test/Makefile.am | 5 +-
test/user-ctl-element-set.c | 573 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 577 insertions(+), 1 deletion(-)
create mode 100644 test/user-ctl-element-set.c
diff --git a/test/Makefile.am b/test/Makefile.am
index ce6e521..cbf2ab4 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 0000000..09c7929
--- /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;
+}
--
2.7.4