mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2025-10-29 05:40:25 -04:00
In former commits, APIs to add an element set are extended to support extra fields to information structure. Currently, the fields are mainly used to describe dimension level. This commit adds tests to check the dimension level. Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp> Signed-off-by: Takashi Iwai <tiwai@suse.de>
639 lines
16 KiB
C
639 lines
16 KiB
C
/*
|
|
* 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 dimension[4];
|
|
|
|
int (*add_elem_set)(struct elem_set_trial *trial,
|
|
snd_ctl_elem_info_t *info);
|
|
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,
|
|
snd_ctl_elem_info_t *info)
|
|
{
|
|
return snd_ctl_elem_add_boolean_set(trial->handle, info,
|
|
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,
|
|
snd_ctl_elem_info_t *info)
|
|
{
|
|
return snd_ctl_elem_add_integer_set(trial->handle, info,
|
|
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,
|
|
snd_ctl_elem_info_t *info)
|
|
{
|
|
return snd_ctl_elem_add_enumerated_set(trial->handle, info,
|
|
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,
|
|
snd_ctl_elem_info_t *info)
|
|
{
|
|
return snd_ctl_elem_add_bytes_set(trial->handle, info,
|
|
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,
|
|
snd_ctl_elem_info_t *info)
|
|
{
|
|
int err;
|
|
|
|
snd_ctl_elem_info_get_id(info, trial->id);
|
|
|
|
err = snd_ctl_elem_add_iec958(trial->handle, trial->id);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/*
|
|
* In historical reason, the above API is not allowed to fill all of
|
|
* fields in identification data.
|
|
*/
|
|
return snd_ctl_elem_info(trial->handle, info);
|
|
}
|
|
|
|
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,
|
|
snd_ctl_elem_info_t *info)
|
|
{
|
|
return snd_ctl_elem_add_integer64_set(trial->handle, info,
|
|
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)
|
|
{
|
|
snd_ctl_elem_info_t *info;
|
|
char name[64] = {0};
|
|
int err;
|
|
|
|
snprintf(name, 64, "userspace-control-element-%s",
|
|
snd_ctl_elem_type_name(trial->type));
|
|
|
|
snd_ctl_elem_info_alloca(&info);
|
|
snd_ctl_elem_info_set_interface(info, SND_CTL_ELEM_IFACE_MIXER);
|
|
snd_ctl_elem_info_set_name(info, name);
|
|
snd_ctl_elem_info_set_dimension(info, trial->dimension);
|
|
|
|
err = trial->add_elem_set(trial, info);
|
|
if (err >= 0)
|
|
snd_ctl_elem_info_get_id(info, trial->id);
|
|
|
|
return err;
|
|
}
|
|
|
|
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 index;
|
|
unsigned int i;
|
|
unsigned int j;
|
|
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_id_get_numid(trial->id);
|
|
index = snd_ctl_elem_id_get_index(trial->id);
|
|
|
|
for (i = 0; i < trial->element_count; ++i) {
|
|
snd_ctl_elem_info_set_index(info, index + i);
|
|
|
|
/*
|
|
* In Linux 4.0 or former, ioctl(SNDRV_CTL_IOCTL_ELEM_ADD)
|
|
* doesn't fill all of fields for identification.
|
|
*/
|
|
if (numid > 0)
|
|
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;
|
|
for (j = 0; j < 4; ++j) {
|
|
if (snd_ctl_elem_info_get_dimension(info, j) !=
|
|
trial->dimension[j])
|
|
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 index;
|
|
unsigned int i;
|
|
int err;
|
|
|
|
snd_ctl_elem_value_alloca(&data);
|
|
|
|
snd_ctl_elem_value_set_id(data, trial->id);
|
|
numid = snd_ctl_elem_id_get_numid(trial->id);
|
|
index = snd_ctl_elem_id_get_index(trial->id);
|
|
|
|
for (i = 0; i < trial->element_count; ++i) {
|
|
snd_ctl_elem_value_set_index(data, index + i);
|
|
|
|
/*
|
|
* In Linux 4.0 or former, ioctl(SNDRV_CTL_IOCTL_ELEM_ADD)
|
|
* doesn't fill all of fields for identification.
|
|
*/
|
|
if (numid > 0)
|
|
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.dimension[0] = 4;
|
|
trial.dimension[1] = 4;
|
|
trial.dimension[2] = 8;
|
|
trial.dimension[3] = 0;
|
|
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.dimension[0] = 128;
|
|
trial.dimension[1] = 0;
|
|
trial.dimension[2] = 0;
|
|
trial.dimension[3] = 0;
|
|
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.dimension[0] = 16;
|
|
trial.dimension[1] = 8;
|
|
trial.dimension[2] = 0;
|
|
trial.dimension[3] = 0;
|
|
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.dimension[0] = 8;
|
|
trial.dimension[1] = 4;
|
|
trial.dimension[2] = 8;
|
|
trial.dimension[3] = 4;
|
|
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.dimension[0] = 0;
|
|
trial.dimension[1] = 0;
|
|
trial.dimension[2] = 0;
|
|
trial.dimension[3] = 0;
|
|
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.dimension[0] = 0;
|
|
trial.dimension[1] = 0;
|
|
trial.dimension[2] = 0;
|
|
trial.dimension[3] = 0;
|
|
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;
|
|
}
|