1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2024 The FreeBSD Foundation 5 * 6 * This software was developed by Christos Margiolis <christos@FreeBSD.org> 7 * under sponsorship from the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/param.h> 32 #include <sys/linker.h> 33 #include <sys/nv.h> 34 #include <sys/sndstat.h> 35 #include <sys/soundcard.h> 36 37 #include <atf-c.h> 38 #include <errno.h> 39 #include <fcntl.h> 40 #include <stdlib.h> 41 #include <unistd.h> 42 43 ATF_TC(sndstat_nv); 44 ATF_TC_HEAD(sndstat_nv, tc) 45 { 46 atf_tc_set_md_var(tc, "descr", "/dev/sndstat nvlist test"); 47 atf_tc_set_md_var(tc, "require.kmods", "snd_dummy"); 48 } 49 50 ATF_TC_BODY(sndstat_nv, tc) 51 { 52 nvlist_t *nvl; 53 const nvlist_t * const *di; 54 const nvlist_t * const *cdi; 55 struct sndstioc_nv_arg arg; 56 size_t nitems, nchans, i, j; 57 int fd, rc, pchan, rchan; 58 59 if ((fd = open("/dev/sndstat", O_RDONLY)) < 0) 60 atf_tc_skip("/dev/sndstat not found, load sound(4)"); 61 62 rc = ioctl(fd, SNDSTIOC_REFRESH_DEVS, NULL); 63 ATF_REQUIRE_EQ(rc, 0); 64 65 arg.nbytes = 0; 66 arg.buf = NULL; 67 rc = ioctl(fd, SNDSTIOC_GET_DEVS, &arg); 68 ATF_REQUIRE_EQ_MSG(rc, 0, "ioctl(SNDSTIOC_GET_DEVS#1) failed"); 69 70 arg.buf = malloc(arg.nbytes); 71 ATF_REQUIRE(arg.buf != NULL); 72 73 rc = ioctl(fd, SNDSTIOC_GET_DEVS, &arg); 74 ATF_REQUIRE_EQ_MSG(rc, 0, "ioctl(SNDSTIOC_GET_DEVS#2) failed"); 75 76 nvl = nvlist_unpack(arg.buf, arg.nbytes, 0); 77 ATF_REQUIRE(nvl != NULL); 78 79 if (nvlist_empty(nvl) || !nvlist_exists(nvl, SNDST_DSPS)) 80 atf_tc_skip("no soundcards attached"); 81 82 di = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &nitems); 83 for (i = 0; i < nitems; i++) { 84 #define NV(type, item) do { \ 85 ATF_REQUIRE_MSG(nvlist_exists(di[i], SNDST_DSPS_ ## item), \ 86 "SNDST_DSPS_" #item " does not exist"); \ 87 nvlist_get_ ## type (di[i], SNDST_DSPS_ ## item); \ 88 } while (0) 89 NV(string, NAMEUNIT); 90 NV(bool, FROM_USER); 91 NV(string, DEVNODE); 92 NV(string, DESC); 93 NV(string, PROVIDER); 94 NV(number, PCHAN); 95 NV(number, RCHAN); 96 #undef NV 97 98 /* Cannot asign using the macro. */ 99 pchan = nvlist_get_number(di[i], SNDST_DSPS_PCHAN); 100 rchan = nvlist_get_number(di[i], SNDST_DSPS_RCHAN); 101 102 if (pchan && !nvlist_exists(di[i], SNDST_DSPS_INFO_PLAY)) 103 atf_tc_fail("playback channel list empty"); 104 if (rchan && !nvlist_exists(di[i], SNDST_DSPS_INFO_REC)) 105 atf_tc_fail("recording channel list empty"); 106 107 #define NV(type, mode, item) do { \ 108 ATF_REQUIRE_MSG(nvlist_exists(nvlist_get_nvlist(di[i], \ 109 SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item), \ 110 "SNDST_DSPS_INFO_" #item " does not exist"); \ 111 nvlist_get_ ## type (nvlist_get_nvlist(di[i], \ 112 SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item); \ 113 } while (0) 114 if (pchan) { 115 NV(number, PLAY, MIN_RATE); 116 NV(number, PLAY, MAX_RATE); 117 NV(number, PLAY, FORMATS); 118 NV(number, PLAY, MIN_CHN); 119 NV(number, PLAY, MAX_CHN); 120 } 121 if (rchan) { 122 NV(number, REC, MIN_RATE); 123 NV(number, REC, MAX_RATE); 124 NV(number, REC, FORMATS); 125 NV(number, REC, MIN_CHN); 126 NV(number, REC, MAX_CHN); 127 } 128 #undef NV 129 130 if (!nvlist_exists(di[i], SNDST_DSPS_PROVIDER_INFO)) 131 continue; 132 133 #define NV(type, item) do { \ 134 ATF_REQUIRE_MSG(nvlist_exists(nvlist_get_nvlist(di[i], \ 135 SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_ ## item), \ 136 "SNDST_DSPS_SOUND4_" #item " does not exist"); \ 137 nvlist_get_ ## type (nvlist_get_nvlist(di[i], \ 138 SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_ ## item); \ 139 } while (0) 140 NV(number, UNIT); 141 NV(string, STATUS); 142 NV(bool, BITPERFECT); 143 NV(bool, PVCHAN); 144 NV(number, PVCHANRATE); 145 NV(number, PVCHANFORMAT); 146 NV(bool, RVCHAN); 147 NV(number, PVCHANRATE); 148 NV(number, PVCHANFORMAT); 149 #undef NV 150 151 if (!nvlist_exists(nvlist_get_nvlist(di[i], 152 SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_CHAN_INFO)) 153 atf_tc_fail("channel info list empty"); 154 155 cdi = nvlist_get_nvlist_array( 156 nvlist_get_nvlist(di[i], SNDST_DSPS_PROVIDER_INFO), 157 SNDST_DSPS_SOUND4_CHAN_INFO, &nchans); 158 for (j = 0; j < nchans; j++) { 159 #define NV(type, item) do { \ 160 ATF_REQUIRE_MSG(nvlist_exists(cdi[j], SNDST_DSPS_SOUND4_CHAN_ ## item), \ 161 "SNDST_DSPS_SOUND4_CHAN_" #item " does not exist"); \ 162 nvlist_get_ ## type (cdi[j], SNDST_DSPS_SOUND4_CHAN_ ## item); \ 163 } while (0) 164 NV(string, NAME); 165 NV(string, PARENTCHAN); 166 NV(number, UNIT); 167 NV(number, CAPS); 168 NV(number, LATENCY); 169 NV(number, RATE); 170 NV(number, FORMAT); 171 NV(number, PID); 172 NV(string, COMM); 173 NV(number, INTR); 174 NV(number, XRUNS); 175 NV(number, FEEDCNT); 176 NV(number, LEFTVOL); 177 NV(number, RIGHTVOL); 178 NV(number, HWBUF_FORMAT); 179 NV(number, HWBUF_RATE); 180 NV(number, HWBUF_SIZE); 181 NV(number, HWBUF_BLKSZ); 182 NV(number, HWBUF_BLKCNT); 183 NV(number, HWBUF_FREE); 184 NV(number, HWBUF_READY); 185 NV(number, SWBUF_FORMAT); 186 NV(number, SWBUF_RATE); 187 NV(number, SWBUF_SIZE); 188 NV(number, SWBUF_BLKSZ); 189 NV(number, SWBUF_BLKCNT); 190 NV(number, SWBUF_FREE); 191 NV(number, SWBUF_READY); 192 NV(string, FEEDERCHAIN); 193 #undef NV 194 } 195 } 196 197 free(arg.buf); 198 nvlist_destroy(nvl); 199 close(fd); 200 } 201 202 #define UDEV_PROVIDER "sndstat_udev" 203 #define UDEV_NAMEUNIT "sndstat_udev" 204 #define UDEV_DEVNODE "sndstat_udev" 205 #define UDEV_DESC "Test Device" 206 #define UDEV_PCHAN 1 207 #define UDEV_RCHAN 1 208 #define UDEV_MIN_RATE 8000 209 #define UDEV_MAX_RATE 96000 210 #define UDEV_FORMATS (AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE) 211 #define UDEV_MIN_CHN 1 212 #define UDEV_MAX_CHN 2 213 214 ATF_TC(sndstat_udev); 215 ATF_TC_HEAD(sndstat_udev, tc) 216 { 217 atf_tc_set_md_var(tc, "descr", "/dev/sndstat userdev interface test"); 218 atf_tc_set_md_var(tc, "require.kmods", "snd_dummy"); 219 } 220 221 ATF_TC_BODY(sndstat_udev, tc) 222 { 223 nvlist_t *nvl, *di, *dichild; 224 const nvlist_t * const *rdi; 225 struct sndstioc_nv_arg arg; 226 const char *str; 227 size_t nitems, i; 228 int fd, rc, pchan, rchan, n; 229 230 if ((fd = open("/dev/sndstat", O_RDWR)) < 0) 231 atf_tc_skip("/dev/sndstat not found, load sound(4)"); 232 233 nvl = nvlist_create(0); 234 ATF_REQUIRE(nvl != NULL); 235 236 di = nvlist_create(0); 237 ATF_REQUIRE(di != NULL); 238 239 dichild = nvlist_create(0); 240 ATF_REQUIRE(dichild != NULL); 241 242 nvlist_add_string(di, SNDST_DSPS_PROVIDER, UDEV_PROVIDER); 243 nvlist_add_string(di, SNDST_DSPS_NAMEUNIT, UDEV_NAMEUNIT); 244 nvlist_add_string(di, SNDST_DSPS_DESC, UDEV_DESC); 245 nvlist_add_string(di, SNDST_DSPS_DEVNODE, UDEV_DEVNODE); 246 nvlist_add_number(di, SNDST_DSPS_PCHAN, UDEV_PCHAN); 247 nvlist_add_number(di, SNDST_DSPS_RCHAN, UDEV_RCHAN); 248 249 nvlist_add_number(dichild, SNDST_DSPS_INFO_MIN_RATE, UDEV_MIN_RATE); 250 nvlist_add_number(dichild, SNDST_DSPS_INFO_MAX_RATE, UDEV_MAX_RATE); 251 nvlist_add_number(dichild, SNDST_DSPS_INFO_FORMATS, UDEV_FORMATS); 252 nvlist_add_number(dichild, SNDST_DSPS_INFO_MIN_CHN, UDEV_MIN_CHN); 253 nvlist_add_number(dichild, SNDST_DSPS_INFO_MAX_CHN, UDEV_MAX_CHN); 254 255 nvlist_add_nvlist(di, SNDST_DSPS_INFO_PLAY, dichild); 256 nvlist_add_nvlist(di, SNDST_DSPS_INFO_REC, dichild); 257 258 nvlist_append_nvlist_array(nvl, SNDST_DSPS, di); 259 ATF_REQUIRE_EQ(nvlist_error(nvl), 0); 260 261 arg.buf = nvlist_pack(nvl, &arg.nbytes); 262 ATF_REQUIRE_MSG(arg.buf != NULL, "failed to pack nvlist"); 263 264 rc = ioctl(fd, SNDSTIOC_ADD_USER_DEVS, &arg); 265 free(arg.buf); 266 ATF_REQUIRE_EQ_MSG(rc, 0, "ioctl(SNDSTIOC_ADD_USER_DEVS) failed"); 267 268 nvlist_destroy(di); 269 nvlist_destroy(dichild); 270 nvlist_destroy(nvl); 271 272 /* Read back registered values. */ 273 rc = ioctl(fd, SNDSTIOC_REFRESH_DEVS, NULL); 274 ATF_REQUIRE_EQ(rc, 0); 275 276 arg.nbytes = 0; 277 arg.buf = NULL; 278 rc = ioctl(fd, SNDSTIOC_GET_DEVS, &arg); 279 ATF_REQUIRE_EQ_MSG(rc, 0, "ioctl(SNDSTIOC_GET_DEVS#1) failed"); 280 281 arg.buf = malloc(arg.nbytes); 282 ATF_REQUIRE(arg.buf != NULL); 283 284 rc = ioctl(fd, SNDSTIOC_GET_DEVS, &arg); 285 ATF_REQUIRE_EQ_MSG(rc, 0, "ioctl(SNDSTIOC_GET_DEVS#2) failed"); 286 287 nvl = nvlist_unpack(arg.buf, arg.nbytes, 0); 288 ATF_REQUIRE(nvl != NULL); 289 290 if (nvlist_empty(nvl) || !nvlist_exists(nvl, SNDST_DSPS)) 291 atf_tc_skip("no soundcards attached"); 292 293 rdi = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &nitems); 294 for (i = 0; i < nitems; i++) { 295 #define NV(type, item, var) do { \ 296 ATF_REQUIRE_MSG(nvlist_exists(rdi[i], SNDST_DSPS_ ## item), \ 297 "SNDST_DSPS_" #item " does not exist"); \ 298 var = nvlist_get_ ## type (rdi[i], SNDST_DSPS_ ## item); \ 299 } while (0) 300 /* Search for our device. */ 301 NV(string, NAMEUNIT, str); 302 if (strcmp(str, UDEV_NAMEUNIT) == 0) 303 break; 304 } 305 if (i == nitems) 306 atf_tc_fail("userland device %s not found", UDEV_NAMEUNIT); 307 308 NV(string, NAMEUNIT, str); 309 ATF_CHECK(strcmp(str, UDEV_NAMEUNIT) == 0); 310 311 NV(bool, FROM_USER, n); 312 ATF_CHECK(n); 313 314 NV(string, DEVNODE, str); 315 ATF_CHECK(strcmp(str, UDEV_DEVNODE) == 0); 316 317 NV(string, DESC, str); 318 ATF_CHECK(strcmp(str, UDEV_DESC) == 0); 319 320 NV(string, PROVIDER, str); 321 ATF_CHECK(strcmp(str, UDEV_PROVIDER) == 0); 322 323 NV(number, PCHAN, pchan); 324 ATF_CHECK(pchan == UDEV_PCHAN); 325 if (pchan && !nvlist_exists(rdi[i], SNDST_DSPS_INFO_PLAY)) 326 atf_tc_fail("playback channel list empty"); 327 328 NV(number, RCHAN, rchan); 329 ATF_CHECK(rchan == UDEV_RCHAN); 330 if (rchan && !nvlist_exists(rdi[i], SNDST_DSPS_INFO_REC)) 331 atf_tc_fail("recording channel list empty"); 332 #undef NV 333 334 #define NV(type, mode, item, var) do { \ 335 ATF_REQUIRE_MSG(nvlist_exists(nvlist_get_nvlist(rdi[i], \ 336 SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item), \ 337 "SNDST_DSPS_INFO_" #item " does not exist"); \ 338 var = nvlist_get_ ## type (nvlist_get_nvlist(rdi[i], \ 339 SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item); \ 340 } while (0) 341 if (pchan) { 342 NV(number, PLAY, MIN_RATE, n); 343 ATF_CHECK(n == UDEV_MIN_RATE); 344 345 NV(number, PLAY, MAX_RATE, n); 346 ATF_CHECK(n == UDEV_MAX_RATE); 347 348 NV(number, PLAY, FORMATS, n); 349 ATF_CHECK(n == UDEV_FORMATS); 350 351 NV(number, PLAY, MIN_CHN, n); 352 ATF_CHECK(n == UDEV_MIN_CHN); 353 354 NV(number, PLAY, MAX_CHN, n); 355 ATF_CHECK(n == UDEV_MAX_CHN); 356 } 357 if (rchan) { 358 NV(number, REC, MIN_RATE, n); 359 ATF_CHECK(n == UDEV_MIN_RATE); 360 361 NV(number, REC, MAX_RATE, n); 362 ATF_CHECK(n == UDEV_MAX_RATE); 363 364 NV(number, REC, FORMATS, n); 365 ATF_CHECK(n == UDEV_FORMATS); 366 367 NV(number, REC, MIN_CHN, n); 368 ATF_CHECK(n == UDEV_MIN_CHN); 369 370 NV(number, REC, MAX_CHN, n); 371 ATF_CHECK(n == UDEV_MAX_CHN); 372 } 373 #undef NV 374 375 free(arg.buf); 376 nvlist_destroy(nvl); 377 close(fd); 378 } 379 380 ATF_TP_ADD_TCS(tp) 381 { 382 ATF_TP_ADD_TC(tp, sndstat_nv); 383 ATF_TP_ADD_TC(tp, sndstat_udev); 384 385 return (atf_no_error()); 386 } 387