xref: /freebsd/tests/sys/sound/sndstat.c (revision 74898ce961e1f4818eba500dfa3a06bc30c1d9b3)
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);
ATF_TC_HEAD(sndstat_nv,tc)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 
ATF_TC_BODY(sndstat_nv,tc)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);
ATF_TC_HEAD(sndstat_udev,tc)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 
ATF_TC_BODY(sndstat_udev,tc)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 
ATF_TP_ADD_TCS(tp)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