diff options
Diffstat (limited to 'tests/sys/sound')
-rw-r--r-- | tests/sys/sound/Makefile | 13 | ||||
-rw-r--r-- | tests/sys/sound/pcm_read_write.c | 262 | ||||
-rw-r--r-- | tests/sys/sound/sndstat.c | 395 |
3 files changed, 670 insertions, 0 deletions
diff --git a/tests/sys/sound/Makefile b/tests/sys/sound/Makefile new file mode 100644 index 000000000000..74a0765a0540 --- /dev/null +++ b/tests/sys/sound/Makefile @@ -0,0 +1,13 @@ +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/sys/sound + +ATF_TESTS_C+= pcm_read_write +ATF_TESTS_C+= sndstat + +CFLAGS+= -I${SRCTOP}/sys +LDFLAGS+= -lnv + +CFLAGS.pcm_read_write.c+= -Wno-cast-align + +.include <bsd.test.mk> diff --git a/tests/sys/sound/pcm_read_write.c b/tests/sys/sound/pcm_read_write.c new file mode 100644 index 000000000000..a77b953a78a0 --- /dev/null +++ b/tests/sys/sound/pcm_read_write.c @@ -0,0 +1,262 @@ +/*- + * Copyright (c) 2025 Florian Walpen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * These tests exercise conversion functions of the sound module, used to read + * pcm samples from a buffer, and write pcm samples to a buffer. The test cases + * are non-exhaustive, but should detect systematic errors in conversion of the + * various sample formats supported. In particular, the test cases establish + * correctness independent of the machine's endianness, making them suitable to + * check for architecture-specific problems. + */ + +#include <sys/types.h> +#include <sys/soundcard.h> + +#include <atf-c.h> +#include <stdio.h> +#include <string.h> + +#include <dev/sound/pcm/sound.h> +#include <dev/sound/pcm/pcm.h> +#include <dev/sound/pcm/g711.h> + +/* Generic test data, with buffer content matching the sample values. */ +static struct afmt_test_data { + const char *label; + uint8_t buffer[4]; + size_t size; + int format; + intpcm_t value; + _Static_assert((sizeof(intpcm_t) == 4), + "Test data assumes 32bit, adjust negative values to new size."); +} const afmt_tests[] = { + /* 8 bit sample formats. */ + {"s8_1", {0x01, 0x00, 0x00, 0x00}, 1, AFMT_S8, 0x00000001}, + {"s8_2", {0x81, 0x00, 0x00, 0x00}, 1, AFMT_S8, 0xffffff81}, + {"u8_1", {0x01, 0x00, 0x00, 0x00}, 1, AFMT_U8, 0xffffff81}, + {"u8_2", {0x81, 0x00, 0x00, 0x00}, 1, AFMT_U8, 0x00000001}, + + /* 16 bit sample formats. */ + {"s16le_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_S16_LE, 0x00000201}, + {"s16le_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_S16_LE, 0xffff8281}, + {"s16be_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_S16_BE, 0x00000102}, + {"s16be_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_S16_BE, 0xffff8182}, + {"u16le_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_U16_LE, 0xffff8201}, + {"u16le_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_U16_LE, 0x00000281}, + {"u16be_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_U16_BE, 0xffff8102}, + {"u16be_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_U16_BE, 0x00000182}, + + /* 24 bit sample formats. */ + {"s24le_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_S24_LE, 0x00030201}, + {"s24le_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_S24_LE, 0xff838281}, + {"s24be_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_S24_BE, 0x00010203}, + {"s24be_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_S24_BE, 0xff818283}, + {"u24le_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_U24_LE, 0xff830201}, + {"u24le_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_U24_LE, 0x00038281}, + {"u24be_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_U24_BE, 0xff810203}, + {"u24be_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_U24_BE, 0x00018283}, + + /* 32 bit sample formats. */ + {"s32le_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_S32_LE, 0x04030201}, + {"s32le_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_S32_LE, 0x84838281}, + {"s32be_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_S32_BE, 0x01020304}, + {"s32be_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_S32_BE, 0x81828384}, + {"u32le_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_U32_LE, 0x84030201}, + {"u32le_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_U32_LE, 0x04838281}, + {"u32be_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_U32_BE, 0x81020304}, + {"u32be_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_U32_BE, 0x01828384}, + + /* 32 bit floating point sample formats. */ + {"f32le_1", {0x00, 0x00, 0x00, 0x3f}, 4, AFMT_F32_LE, 0x40000000}, + {"f32le_2", {0x00, 0x00, 0x00, 0xbf}, 4, AFMT_F32_LE, 0xc0000000}, + {"f32be_1", {0x3f, 0x00, 0x00, 0x00}, 4, AFMT_F32_BE, 0x40000000}, + {"f32be_2", {0xbf, 0x00, 0x00, 0x00}, 4, AFMT_F32_BE, 0xc0000000}, + + /* u-law and A-law sample formats. */ + {"mulaw_1", {0x01, 0x00, 0x00, 0x00}, 1, AFMT_MU_LAW, 0xffffff87}, + {"mulaw_2", {0x81, 0x00, 0x00, 0x00}, 1, AFMT_MU_LAW, 0x00000079}, + {"alaw_1", {0x2a, 0x00, 0x00, 0x00}, 1, AFMT_A_LAW, 0xffffff83}, + {"alaw_2", {0xab, 0x00, 0x00, 0x00}, 1, AFMT_A_LAW, 0x00000079} +}; + +/* Normalize sample values in strictly correct (but slow) c. */ +static intpcm_t +local_normalize(intpcm_t value, int val_bits, int norm_bits) +{ + int32_t divisor; + intpcm_t remainder; + + /* Avoid undefined or implementation defined behavior. */ + if (val_bits < norm_bits) + /* Multiply instead of left shift (value may be negative). */ + return (value * (1 << (norm_bits - val_bits))); + else if (val_bits > norm_bits) { + divisor = (1 << (val_bits - norm_bits)); + /* Positive remainder, to discard lowest bits from value. */ + remainder = value % divisor; + remainder = (remainder + divisor) % divisor; + /* Divide instead of right shift (value may be negative). */ + return ((value - remainder) / divisor); + } + return value; +} + +/* Restrict magnitude of sample value to 24bit for 32bit calculations. */ +static intpcm_t +local_calc_limit(intpcm_t value, int val_bits) +{ + /* + * When intpcm32_t is defined to be 32bit, calculations for mixing and + * volume changes use 32bit integers instead of 64bit. To get some + * headroom for calculations, 32bit sample values are restricted to + * 24bit magnitude in that case. Also avoid implementation defined + * behavior here. + */ + if (sizeof(intpcm32_t) == (32 / 8) && val_bits == 32) + return (local_normalize(value, 32, 24)); + return value; +} + +ATF_TC(pcm_read); +ATF_TC_HEAD(pcm_read, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Read and verify different pcm sample formats."); +} +ATF_TC_BODY(pcm_read, tc) +{ + const struct afmt_test_data *test; + uint8_t src[4]; + intpcm_t expected, result; + size_t i; + + for (i = 0; i < nitems(afmt_tests); i++) { + test = &afmt_tests[i]; + + /* Copy byte representation, fill with distinctive pattern. */ + memset(src, 0x66, sizeof(src)); + memcpy(src, test->buffer, test->size); + + /* Read sample at format magnitude. */ + expected = test->value; + result = pcm_sample_read(src, test->format); + ATF_CHECK_MSG(result == expected, + "pcm_read[\"%s\"].value: expected=0x%08x, result=0x%08x", + test->label, expected, result); + + /* Read sample at format magnitude, for calculations. */ + expected = local_calc_limit(test->value, test->size * 8); + result = pcm_sample_read_calc(src, test->format); + ATF_CHECK_MSG(result == expected, + "pcm_read[\"%s\"].calc: expected=0x%08x, result=0x%08x", + test->label, expected, result); + + /* Read sample at full 32 bit magnitude. */ + expected = local_normalize(test->value, test->size * 8, 32); + result = pcm_sample_read_norm(src, test->format); + ATF_CHECK_MSG(result == expected, + "pcm_read[\"%s\"].norm: expected=0x%08x, result=0x%08x", + test->label, expected, result); + } +} + +ATF_TC(pcm_write); +ATF_TC_HEAD(pcm_write, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Write and verify different pcm sample formats."); +} +ATF_TC_BODY(pcm_write, tc) +{ + const struct afmt_test_data *test; + uint8_t expected[4]; + uint8_t dst[4]; + intpcm_t value; + size_t i; + + for (i = 0; i < nitems(afmt_tests); i++) { + test = &afmt_tests[i]; + + /* Write sample of format specific magnitude. */ + memcpy(expected, test->buffer, sizeof(expected)); + memset(dst, 0x00, sizeof(dst)); + value = test->value; + pcm_sample_write(dst, value, test->format); + ATF_CHECK_MSG(memcmp(dst, expected, sizeof(dst)) == 0, + "pcm_write[\"%s\"].value: " + "expected={0x%02x, 0x%02x, 0x%02x, 0x%02x}, " + "result={0x%02x, 0x%02x, 0x%02x, 0x%02x}, ", test->label, + expected[0], expected[1], expected[2], expected[3], + dst[0], dst[1], dst[2], dst[3]); + + /* Write sample of format specific, calculation magnitude. */ + memcpy(expected, test->buffer, sizeof(expected)); + memset(dst, 0x00, sizeof(dst)); + value = local_calc_limit(test->value, test->size * 8); + if (value != test->value) { + /* + * 32 bit sample was reduced to 24 bit resolution + * for calculation, least significant byte is lost. + */ + if (test->format & AFMT_BIGENDIAN) + expected[3] = 0x00; + else + expected[0] = 0x00; + } + pcm_sample_write_calc(dst, value, test->format); + ATF_CHECK_MSG(memcmp(dst, expected, sizeof(dst)) == 0, + "pcm_write[\"%s\"].calc: " + "expected={0x%02x, 0x%02x, 0x%02x, 0x%02x}, " + "result={0x%02x, 0x%02x, 0x%02x, 0x%02x}, ", test->label, + expected[0], expected[1], expected[2], expected[3], + dst[0], dst[1], dst[2], dst[3]); + + /* Write normalized sample of full 32 bit magnitude. */ + memcpy(expected, test->buffer, sizeof(expected)); + memset(dst, 0x00, sizeof(dst)); + value = local_normalize(test->value, test->size * 8, 32); + pcm_sample_write_norm(dst, value, test->format); + ATF_CHECK_MSG(memcmp(dst, expected, sizeof(dst)) == 0, + "pcm_write[\"%s\"].norm: " + "expected={0x%02x, 0x%02x, 0x%02x, 0x%02x}, " + "result={0x%02x, 0x%02x, 0x%02x, 0x%02x}, ", test->label, + expected[0], expected[1], expected[2], expected[3], + dst[0], dst[1], dst[2], dst[3]); + } +} + +ATF_TC(pcm_format_bits); +ATF_TC_HEAD(pcm_format_bits, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Verify bit width of different pcm sample formats."); +} +ATF_TC_BODY(pcm_format_bits, tc) +{ + const struct afmt_test_data *test; + size_t bits; + size_t i; + + for (i = 0; i < nitems(afmt_tests); i++) { + test = &afmt_tests[i]; + + /* Check bit width determined for given sample format. */ + bits = AFMT_BIT(test->format); + ATF_CHECK_MSG(bits == test->size * 8, + "format_bits[%zu].size: expected=%zu, result=%zu", + i, test->size * 8, bits); + } +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, pcm_read); + ATF_TP_ADD_TC(tp, pcm_write); + ATF_TP_ADD_TC(tp, pcm_format_bits); + + return atf_no_error(); +} diff --git a/tests/sys/sound/sndstat.c b/tests/sys/sound/sndstat.c new file mode 100644 index 000000000000..ed292b570429 --- /dev/null +++ b/tests/sys/sound/sndstat.c @@ -0,0 +1,395 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2024 The FreeBSD Foundation + * + * This software was developed by Christos Margiolis <christos@FreeBSD.org> + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/linker.h> +#include <sys/nv.h> +#include <sys/sndstat.h> +#include <sys/soundcard.h> + +#include <atf-c.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> + +static void +load_dummy(void) +{ + if (kldload("snd_dummy.ko") < 0 && errno != EEXIST) + atf_tc_skip("snd_dummy.ko not found"); +} + +ATF_TC(sndstat_nv); +ATF_TC_HEAD(sndstat_nv, tc) +{ + atf_tc_set_md_var(tc, "descr", "/dev/sndstat nvlist test"); +} + +ATF_TC_BODY(sndstat_nv, tc) +{ + nvlist_t *nvl; + const nvlist_t * const *di; + const nvlist_t * const *cdi; + struct sndstioc_nv_arg arg; + size_t nitems, nchans, i, j; + int fd, rc, pchan, rchan; + + load_dummy(); + + if ((fd = open("/dev/sndstat", O_RDONLY)) < 0) + atf_tc_skip("/dev/sndstat not found, load sound(4)"); + + rc = ioctl(fd, SNDSTIOC_REFRESH_DEVS, NULL); + ATF_REQUIRE_EQ(rc, 0); + + arg.nbytes = 0; + arg.buf = NULL; + rc = ioctl(fd, SNDSTIOC_GET_DEVS, &arg); + ATF_REQUIRE_EQ_MSG(rc, 0, "ioctl(SNDSTIOC_GET_DEVS#1) failed"); + + arg.buf = malloc(arg.nbytes); + ATF_REQUIRE(arg.buf != NULL); + + rc = ioctl(fd, SNDSTIOC_GET_DEVS, &arg); + ATF_REQUIRE_EQ_MSG(rc, 0, "ioctl(SNDSTIOC_GET_DEVS#2) failed"); + + nvl = nvlist_unpack(arg.buf, arg.nbytes, 0); + ATF_REQUIRE(nvl != NULL); + + if (nvlist_empty(nvl) || !nvlist_exists(nvl, SNDST_DSPS)) + atf_tc_skip("no soundcards attached"); + + di = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &nitems); + for (i = 0; i < nitems; i++) { +#define NV(type, item) do { \ + ATF_REQUIRE_MSG(nvlist_exists(di[i], SNDST_DSPS_ ## item), \ + "SNDST_DSPS_" #item " does not exist"); \ + nvlist_get_ ## type (di[i], SNDST_DSPS_ ## item); \ +} while (0) + NV(string, NAMEUNIT); + NV(bool, FROM_USER); + NV(string, DEVNODE); + NV(string, DESC); + NV(string, PROVIDER); + NV(number, PCHAN); + NV(number, RCHAN); +#undef NV + + /* Cannot asign using the macro. */ + pchan = nvlist_get_number(di[i], SNDST_DSPS_PCHAN); + rchan = nvlist_get_number(di[i], SNDST_DSPS_RCHAN); + + if (pchan && !nvlist_exists(di[i], SNDST_DSPS_INFO_PLAY)) + atf_tc_fail("playback channel list empty"); + if (rchan && !nvlist_exists(di[i], SNDST_DSPS_INFO_REC)) + atf_tc_fail("recording channel list empty"); + +#define NV(type, mode, item) do { \ + ATF_REQUIRE_MSG(nvlist_exists(nvlist_get_nvlist(di[i], \ + SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item), \ + "SNDST_DSPS_INFO_" #item " does not exist"); \ + nvlist_get_ ## type (nvlist_get_nvlist(di[i], \ + SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item); \ +} while (0) + if (pchan) { + NV(number, PLAY, MIN_RATE); + NV(number, PLAY, MAX_RATE); + NV(number, PLAY, FORMATS); + NV(number, PLAY, MIN_CHN); + NV(number, PLAY, MAX_CHN); + } + if (rchan) { + NV(number, REC, MIN_RATE); + NV(number, REC, MAX_RATE); + NV(number, REC, FORMATS); + NV(number, REC, MIN_CHN); + NV(number, REC, MAX_CHN); + } +#undef NV + + if (!nvlist_exists(di[i], SNDST_DSPS_PROVIDER_INFO)) + continue; + +#define NV(type, item) do { \ + ATF_REQUIRE_MSG(nvlist_exists(nvlist_get_nvlist(di[i], \ + SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_ ## item), \ + "SNDST_DSPS_SOUND4_" #item " does not exist"); \ + nvlist_get_ ## type (nvlist_get_nvlist(di[i], \ + SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_ ## item); \ +} while (0) + NV(number, UNIT); + NV(string, STATUS); + NV(bool, BITPERFECT); + NV(bool, PVCHAN); + NV(number, PVCHANRATE); + NV(number, PVCHANFORMAT); + NV(bool, RVCHAN); + NV(number, PVCHANRATE); + NV(number, PVCHANFORMAT); +#undef NV + + if (!nvlist_exists(nvlist_get_nvlist(di[i], + SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_CHAN_INFO)) + atf_tc_fail("channel info list empty"); + + cdi = nvlist_get_nvlist_array( + nvlist_get_nvlist(di[i], SNDST_DSPS_PROVIDER_INFO), + SNDST_DSPS_SOUND4_CHAN_INFO, &nchans); + for (j = 0; j < nchans; j++) { +#define NV(type, item) do { \ + ATF_REQUIRE_MSG(nvlist_exists(cdi[j], SNDST_DSPS_SOUND4_CHAN_ ## item), \ + "SNDST_DSPS_SOUND4_CHAN_" #item " does not exist"); \ + nvlist_get_ ## type (cdi[j], SNDST_DSPS_SOUND4_CHAN_ ## item); \ +} while (0) + NV(string, NAME); + NV(string, PARENTCHAN); + NV(number, UNIT); + NV(number, CAPS); + NV(number, LATENCY); + NV(number, RATE); + NV(number, FORMAT); + NV(number, PID); + NV(string, COMM); + NV(number, INTR); + NV(number, XRUNS); + NV(number, FEEDCNT); + NV(number, LEFTVOL); + NV(number, RIGHTVOL); + NV(number, HWBUF_FORMAT); + NV(number, HWBUF_RATE); + NV(number, HWBUF_SIZE); + NV(number, HWBUF_BLKSZ); + NV(number, HWBUF_BLKCNT); + NV(number, HWBUF_FREE); + NV(number, HWBUF_READY); + NV(number, SWBUF_FORMAT); + NV(number, SWBUF_RATE); + NV(number, SWBUF_SIZE); + NV(number, SWBUF_BLKSZ); + NV(number, SWBUF_BLKCNT); + NV(number, SWBUF_FREE); + NV(number, SWBUF_READY); + NV(string, FEEDERCHAIN); +#undef NV + } + } + + free(arg.buf); + nvlist_destroy(nvl); + close(fd); +} + +#define UDEV_PROVIDER "sndstat_udev" +#define UDEV_NAMEUNIT "sndstat_udev" +#define UDEV_DEVNODE "sndstat_udev" +#define UDEV_DESC "Test Device" +#define UDEV_PCHAN 1 +#define UDEV_RCHAN 1 +#define UDEV_MIN_RATE 8000 +#define UDEV_MAX_RATE 96000 +#define UDEV_FORMATS (AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE) +#define UDEV_MIN_CHN 1 +#define UDEV_MAX_CHN 2 + +ATF_TC(sndstat_udev); +ATF_TC_HEAD(sndstat_udev, tc) +{ + atf_tc_set_md_var(tc, "descr", "/dev/sndstat userdev interface test"); +} + +ATF_TC_BODY(sndstat_udev, tc) +{ + nvlist_t *nvl, *di, *dichild; + const nvlist_t * const *rdi; + struct sndstioc_nv_arg arg; + const char *str; + size_t nitems, i; + int fd, rc, pchan, rchan, n; + + load_dummy(); + + if ((fd = open("/dev/sndstat", O_RDWR)) < 0) + atf_tc_skip("/dev/sndstat not found, load sound(4)"); + + nvl = nvlist_create(0); + ATF_REQUIRE(nvl != NULL); + + di = nvlist_create(0); + ATF_REQUIRE(di != NULL); + + dichild = nvlist_create(0); + ATF_REQUIRE(dichild != NULL); + + nvlist_add_string(di, SNDST_DSPS_PROVIDER, UDEV_PROVIDER); + nvlist_add_string(di, SNDST_DSPS_NAMEUNIT, UDEV_NAMEUNIT); + nvlist_add_string(di, SNDST_DSPS_DESC, UDEV_DESC); + nvlist_add_string(di, SNDST_DSPS_DEVNODE, UDEV_DEVNODE); + nvlist_add_number(di, SNDST_DSPS_PCHAN, UDEV_PCHAN); + nvlist_add_number(di, SNDST_DSPS_RCHAN, UDEV_RCHAN); + + nvlist_add_number(dichild, SNDST_DSPS_INFO_MIN_RATE, UDEV_MIN_RATE); + nvlist_add_number(dichild, SNDST_DSPS_INFO_MAX_RATE, UDEV_MAX_RATE); + nvlist_add_number(dichild, SNDST_DSPS_INFO_FORMATS, UDEV_FORMATS); + nvlist_add_number(dichild, SNDST_DSPS_INFO_MIN_CHN, UDEV_MIN_CHN); + nvlist_add_number(dichild, SNDST_DSPS_INFO_MAX_CHN, UDEV_MAX_CHN); + + nvlist_add_nvlist(di, SNDST_DSPS_INFO_PLAY, dichild); + nvlist_add_nvlist(di, SNDST_DSPS_INFO_REC, dichild); + + nvlist_append_nvlist_array(nvl, SNDST_DSPS, di); + ATF_REQUIRE_EQ(nvlist_error(nvl), 0); + + arg.buf = nvlist_pack(nvl, &arg.nbytes); + ATF_REQUIRE_MSG(arg.buf != NULL, "failed to pack nvlist"); + + rc = ioctl(fd, SNDSTIOC_ADD_USER_DEVS, &arg); + free(arg.buf); + ATF_REQUIRE_EQ_MSG(rc, 0, "ioctl(SNDSTIOC_ADD_USER_DEVS) failed"); + + nvlist_destroy(di); + nvlist_destroy(dichild); + nvlist_destroy(nvl); + + /* Read back registered values. */ + rc = ioctl(fd, SNDSTIOC_REFRESH_DEVS, NULL); + ATF_REQUIRE_EQ(rc, 0); + + arg.nbytes = 0; + arg.buf = NULL; + rc = ioctl(fd, SNDSTIOC_GET_DEVS, &arg); + ATF_REQUIRE_EQ_MSG(rc, 0, "ioctl(SNDSTIOC_GET_DEVS#1) failed"); + + arg.buf = malloc(arg.nbytes); + ATF_REQUIRE(arg.buf != NULL); + + rc = ioctl(fd, SNDSTIOC_GET_DEVS, &arg); + ATF_REQUIRE_EQ_MSG(rc, 0, "ioctl(SNDSTIOC_GET_DEVS#2) failed"); + + nvl = nvlist_unpack(arg.buf, arg.nbytes, 0); + ATF_REQUIRE(nvl != NULL); + + if (nvlist_empty(nvl) || !nvlist_exists(nvl, SNDST_DSPS)) + atf_tc_skip("no soundcards attached"); + + rdi = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &nitems); + for (i = 0; i < nitems; i++) { +#define NV(type, item, var) do { \ + ATF_REQUIRE_MSG(nvlist_exists(rdi[i], SNDST_DSPS_ ## item), \ + "SNDST_DSPS_" #item " does not exist"); \ + var = nvlist_get_ ## type (rdi[i], SNDST_DSPS_ ## item); \ +} while (0) + /* Search for our device. */ + NV(string, NAMEUNIT, str); + if (strcmp(str, UDEV_NAMEUNIT) == 0) + break; + } + if (i == nitems) + atf_tc_fail("userland device %s not found", UDEV_NAMEUNIT); + + NV(string, NAMEUNIT, str); + ATF_CHECK(strcmp(str, UDEV_NAMEUNIT) == 0); + + NV(bool, FROM_USER, n); + ATF_CHECK(n); + + NV(string, DEVNODE, str); + ATF_CHECK(strcmp(str, UDEV_DEVNODE) == 0); + + NV(string, DESC, str); + ATF_CHECK(strcmp(str, UDEV_DESC) == 0); + + NV(string, PROVIDER, str); + ATF_CHECK(strcmp(str, UDEV_PROVIDER) == 0); + + NV(number, PCHAN, pchan); + ATF_CHECK(pchan == UDEV_PCHAN); + if (pchan && !nvlist_exists(rdi[i], SNDST_DSPS_INFO_PLAY)) + atf_tc_fail("playback channel list empty"); + + NV(number, RCHAN, rchan); + ATF_CHECK(rchan == UDEV_RCHAN); + if (rchan && !nvlist_exists(rdi[i], SNDST_DSPS_INFO_REC)) + atf_tc_fail("recording channel list empty"); +#undef NV + +#define NV(type, mode, item, var) do { \ + ATF_REQUIRE_MSG(nvlist_exists(nvlist_get_nvlist(rdi[i], \ + SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item), \ + "SNDST_DSPS_INFO_" #item " does not exist"); \ + var = nvlist_get_ ## type (nvlist_get_nvlist(rdi[i], \ + SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item); \ +} while (0) + if (pchan) { + NV(number, PLAY, MIN_RATE, n); + ATF_CHECK(n == UDEV_MIN_RATE); + + NV(number, PLAY, MAX_RATE, n); + ATF_CHECK(n == UDEV_MAX_RATE); + + NV(number, PLAY, FORMATS, n); + ATF_CHECK(n == UDEV_FORMATS); + + NV(number, PLAY, MIN_CHN, n); + ATF_CHECK(n == UDEV_MIN_CHN); + + NV(number, PLAY, MAX_CHN, n); + ATF_CHECK(n == UDEV_MAX_CHN); + } + if (rchan) { + NV(number, REC, MIN_RATE, n); + ATF_CHECK(n == UDEV_MIN_RATE); + + NV(number, REC, MAX_RATE, n); + ATF_CHECK(n == UDEV_MAX_RATE); + + NV(number, REC, FORMATS, n); + ATF_CHECK(n == UDEV_FORMATS); + + NV(number, REC, MIN_CHN, n); + ATF_CHECK(n == UDEV_MIN_CHN); + + NV(number, REC, MAX_CHN, n); + ATF_CHECK(n == UDEV_MAX_CHN); + } +#undef NV + + free(arg.buf); + nvlist_destroy(nvl); + close(fd); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, sndstat_nv); + ATF_TP_ADD_TC(tp, sndstat_udev); + + return (atf_no_error()); +} |