xref: /openbsd/lib/libsndio/sio_sun.c (revision 2c53affb)
1 /*	$OpenBSD: sio_sun.c,v 1.30 2022/12/27 17:10:07 jmc Exp $	*/
2 /*
3  * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/types.h>
19 #include <sys/ioctl.h>
20 #include <sys/audioio.h>
21 
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <limits.h>
25 #include <poll.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include "debug.h"
32 #include "sio_priv.h"
33 
34 #define DEVPATH_PREFIX	"/dev/audio"
35 #define DEVPATH_MAX 	(1 +		\
36 	sizeof(DEVPATH_PREFIX) - 1 +	\
37 	sizeof(int) * 3)
38 
39 struct sio_sun_hdl {
40 	struct sio_hdl sio;
41 	int fd;
42 	int filling;
43 	unsigned int ibpf, obpf;	/* bytes per frame */
44 	unsigned int ibytes, obytes;	/* bytes the hw transferred */
45 	unsigned int ierr, oerr;	/* frames the hw dropped */
46 	int idelta, odelta;		/* position reported to client */
47 };
48 
49 static void sio_sun_close(struct sio_hdl *);
50 static int sio_sun_start(struct sio_hdl *);
51 static int sio_sun_flush(struct sio_hdl *);
52 static int sio_sun_setpar(struct sio_hdl *, struct sio_par *);
53 static int sio_sun_getpar(struct sio_hdl *, struct sio_par *);
54 static int sio_sun_getcap(struct sio_hdl *, struct sio_cap *);
55 static size_t sio_sun_read(struct sio_hdl *, void *, size_t);
56 static size_t sio_sun_write(struct sio_hdl *, const void *, size_t);
57 static int sio_sun_nfds(struct sio_hdl *);
58 static int sio_sun_pollfd(struct sio_hdl *, struct pollfd *, int);
59 static int sio_sun_revents(struct sio_hdl *, struct pollfd *);
60 
61 static struct sio_ops sio_sun_ops = {
62 	sio_sun_close,
63 	sio_sun_setpar,
64 	sio_sun_getpar,
65 	sio_sun_getcap,
66 	sio_sun_write,
67 	sio_sun_read,
68 	sio_sun_start,
69 	NULL,
70 	sio_sun_flush,
71 	sio_sun_nfds,
72 	sio_sun_pollfd,
73 	sio_sun_revents,
74 	NULL, /* setvol */
75 	NULL, /* getvol */
76 };
77 
78 static int
sio_sun_adjpar(struct sio_sun_hdl * hdl,struct audio_swpar * ap)79 sio_sun_adjpar(struct sio_sun_hdl *hdl, struct audio_swpar *ap)
80 {
81 	if (hdl->sio.eof)
82 		return 0;
83 	if (ioctl(hdl->fd, AUDIO_SETPAR, ap) == -1) {
84 		DPERROR("AUDIO_SETPAR");
85 		hdl->sio.eof = 1;
86 		return 0;
87 	}
88 	if (ioctl(hdl->fd, AUDIO_GETPAR, ap) == -1) {
89 		DPERROR("AUDIO_GETPAR");
90 		hdl->sio.eof = 1;
91 		return 0;
92 	}
93 	return 1;
94 }
95 
96 /*
97  * try to set the device to the given parameters and check that the
98  * device can use them; return 1 on success, 0 on failure or error
99  */
100 static int
sio_sun_testpar(struct sio_sun_hdl * hdl,struct sio_enc * enc,unsigned int pchan,unsigned int rchan,unsigned int rate)101 sio_sun_testpar(struct sio_sun_hdl *hdl, struct sio_enc *enc,
102     unsigned int pchan, unsigned int rchan, unsigned int rate)
103 {
104 	struct audio_swpar ap;
105 
106 	AUDIO_INITPAR(&ap);
107 	if (enc != NULL) {
108 		ap.sig = enc->sig;
109 		ap.bits = enc->bits;
110 		ap.bps = enc->bps;
111 		if (ap.bps > 1)
112 			ap.le = enc->le;
113 		if (ap.bps * 8 > ap.bits)
114 			ap.msb = enc->msb;
115 	}
116 	if (rate)
117 		ap.rate = rate;
118 	if (pchan && (hdl->sio.mode & SIO_PLAY))
119 		ap.pchan = pchan;
120 	if (rchan && (hdl->sio.mode & SIO_REC))
121 		ap.rchan = rchan;
122 	if (!sio_sun_adjpar(hdl, &ap))
123 		return 0;
124 	if (pchan && ap.pchan != pchan)
125 		return 0;
126 	if (rchan && ap.rchan != rchan)
127 		return 0;
128 	if (rate && ap.rate != rate)
129 		return 0;
130 	if (enc) {
131 		if (ap.sig != enc->sig)
132 			return 0;
133 		if (ap.bits != enc->bits)
134 			return 0;
135 		if (ap.bps != enc->bps)
136 			return 0;
137 		if (ap.bps > 1 && ap.le != enc->le)
138 			return 0;
139 		if (ap.bits < ap.bps * 8 && ap.msb != enc->msb)
140 			return 0;
141 	}
142 	return 1;
143 }
144 
145 /*
146  * guess device capabilities
147  */
148 static int
sio_sun_getcap(struct sio_hdl * sh,struct sio_cap * cap)149 sio_sun_getcap(struct sio_hdl *sh, struct sio_cap *cap)
150 {
151 	static unsigned int chans[] = {
152 		1, 2, 4, 6, 8, 10, 12
153 	};
154 	static unsigned int rates[] = {
155 		8000, 11025, 12000, 16000, 22050, 24000,
156 		32000, 44100, 48000, 64000, 88200, 96000
157 	};
158 	static unsigned int encs[] = {
159 		8, 16, 24, 32
160 	};
161 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
162 	struct audio_swpar savepar, ap;
163 	unsigned int nconf = 0;
164 	unsigned int enc_map = 0, rchan_map = 0, pchan_map = 0, rate_map;
165 	unsigned int i, j, conf;
166 
167 	if (ioctl(hdl->fd, AUDIO_GETPAR, &savepar) == -1) {
168 		DPERROR("AUDIO_GETPAR");
169 		hdl->sio.eof = 1;
170 		return 0;
171 	}
172 
173 	/*
174 	 * get a subset of supported encodings
175 	 */
176 	for (i = 0; i < sizeof(encs) / sizeof(encs[0]); i++) {
177 		AUDIO_INITPAR(&ap);
178 		ap.bits = encs[i];
179 		ap.sig = (ap.bits > 8) ? 1 : 0;
180 		if (!sio_sun_adjpar(hdl, &ap))
181 			return 0;
182 		if (ap.bits == encs[i]) {
183 			cap->enc[i].sig = ap.sig;
184 			cap->enc[i].bits = ap.bits;
185 			cap->enc[i].le = ap.le;
186 			cap->enc[i].bps = ap.bps;
187 			cap->enc[i].msb = ap.msb;
188 			enc_map |= 1 << i;
189 		}
190 	}
191 
192 	/*
193 	 * fill channels
194 	 *
195 	 * for now we're lucky: all kernel devices assume that the
196 	 * number of channels and the encoding are independent so we can
197 	 * use the current encoding and try various channels.
198 	 */
199 	if (hdl->sio.mode & SIO_PLAY) {
200 		for (i = 0; i < sizeof(chans) / sizeof(chans[0]); i++) {
201 			AUDIO_INITPAR(&ap);
202 			ap.pchan = chans[i];
203 			if (!sio_sun_adjpar(hdl, &ap))
204 				return 0;
205 			if (ap.pchan == chans[i]) {
206 				cap->pchan[i] = chans[i];
207 				pchan_map |= (1 << i);
208 			}
209 		}
210 	}
211 	if (hdl->sio.mode & SIO_REC) {
212 		for (i = 0; i < sizeof(chans) / sizeof(chans[0]); i++) {
213 			AUDIO_INITPAR(&ap);
214 			ap.pchan = chans[i];
215 			if (!sio_sun_adjpar(hdl, &ap))
216 				return 0;
217 			if (ap.rchan == chans[i]) {
218 				cap->rchan[i] = chans[i];
219 				rchan_map |= (1 << i);
220 			}
221 		}
222 	}
223 
224 	/*
225 	 * fill rates
226 	 *
227 	 * rates are not independent from other parameters (eg. on
228 	 * uaudio devices), so certain rates may not be allowed with
229 	 * certain encodings. We have to check rates for all encodings
230 	 */
231 	for (j = 0; j < sizeof(encs) / sizeof(encs[0]); j++) {
232 		rate_map = 0;
233 		if ((enc_map & (1 << j)) == 0)
234 			continue;
235 		for (i = 0; i < sizeof(rates) / sizeof(rates[0]); i++) {
236 			if (sio_sun_testpar(hdl,
237 				&cap->enc[j], 0, 0, rates[i])) {
238 				cap->rate[i] = rates[i];
239 				rate_map |= (1 << i);
240 			}
241 		}
242 		for (conf = 0; conf < nconf; conf++) {
243 			if (cap->confs[conf].rate == rate_map) {
244 				cap->confs[conf].enc |= (1 << j);
245 				break;
246 			}
247 		}
248 		if (conf == nconf) {
249 			if (nconf == SIO_NCONF)
250 				break;
251 			cap->confs[nconf].enc = (1 << j);
252 			cap->confs[nconf].pchan = pchan_map;
253 			cap->confs[nconf].rchan = rchan_map;
254 			cap->confs[nconf].rate = rate_map;
255 			nconf++;
256 		}
257 	}
258 	cap->nconf = nconf;
259 
260 	if (ioctl(hdl->fd, AUDIO_SETPAR, &savepar) == -1) {
261 		DPERROR("AUDIO_SETPAR");
262 		hdl->sio.eof = 1;
263 		return 0;
264 	}
265 	return 1;
266 }
267 
268 int
sio_sun_getfd(const char * str,unsigned int mode,int nbio)269 sio_sun_getfd(const char *str, unsigned int mode, int nbio)
270 {
271 	const char *p;
272 	char path[DEVPATH_MAX];
273 	unsigned int devnum;
274 	int fd, flags;
275 
276 #ifdef DEBUG
277 	_sndio_debug_init();
278 #endif
279 	p = _sndio_parsetype(str, "rsnd");
280 	if (p == NULL) {
281 		DPRINTF("sio_sun_getfd: %s: \"rsnd\" expected\n", str);
282 		return -1;
283 	}
284 	switch (*p) {
285 	case '/':
286 		p++;
287 		break;
288 	default:
289 		DPRINTF("sio_sun_getfd: %s: '/' expected\n", str);
290 		return -1;
291 	}
292 	p = _sndio_parsenum(p, &devnum, 255);
293 	if (p == NULL || *p != '\0') {
294 		DPRINTF("sio_sun_getfd: %s: number expected after '/'\n", str);
295 		return -1;
296 	}
297 	snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum);
298 	if (mode == (SIO_PLAY | SIO_REC))
299 		flags = O_RDWR;
300 	else
301 		flags = (mode & SIO_PLAY) ? O_WRONLY : O_RDONLY;
302 	while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) == -1) {
303 		if (errno == EINTR)
304 			continue;
305 		DPERROR(path);
306 		return -1;
307 	}
308 	return fd;
309 }
310 
311 struct sio_hdl *
sio_sun_fdopen(int fd,unsigned int mode,int nbio)312 sio_sun_fdopen(int fd, unsigned int mode, int nbio)
313 {
314 	struct sio_sun_hdl *hdl;
315 
316 #ifdef DEBUG
317 	_sndio_debug_init();
318 #endif
319 	hdl = malloc(sizeof(struct sio_sun_hdl));
320 	if (hdl == NULL)
321 		return NULL;
322 	_sio_create(&hdl->sio, &sio_sun_ops, mode, nbio);
323 
324 	/*
325 	 * pause the device
326 	 */
327 	if (ioctl(fd, AUDIO_STOP) == -1) {
328 		DPERROR("AUDIO_STOP");
329 		free(hdl);
330 		return NULL;
331 	}
332 	hdl->fd = fd;
333 	hdl->filling = 0;
334 	return (struct sio_hdl *)hdl;
335 }
336 
337 struct sio_hdl *
_sio_sun_open(const char * str,unsigned int mode,int nbio)338 _sio_sun_open(const char *str, unsigned int mode, int nbio)
339 {
340 	struct sio_hdl *hdl;
341 	int fd;
342 
343 	fd = sio_sun_getfd(str, mode, nbio);
344 	if (fd == -1)
345 		return NULL;
346 	hdl = sio_sun_fdopen(fd, mode, nbio);
347 	if (hdl != NULL)
348 		return hdl;
349 	while (close(fd) == -1 && errno == EINTR)
350 		; /* retry */
351 	return NULL;
352 }
353 
354 static void
sio_sun_close(struct sio_hdl * sh)355 sio_sun_close(struct sio_hdl *sh)
356 {
357 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
358 
359 	while (close(hdl->fd) == -1 && errno == EINTR)
360 		; /* retry */
361 	free(hdl);
362 }
363 
364 static int
sio_sun_start(struct sio_hdl * sh)365 sio_sun_start(struct sio_hdl *sh)
366 {
367 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
368 
369 	hdl->obpf = hdl->sio.par.pchan * hdl->sio.par.bps;
370 	hdl->ibpf = hdl->sio.par.rchan * hdl->sio.par.bps;
371 	hdl->ibytes = 0;
372 	hdl->obytes = 0;
373 	hdl->ierr = 0;
374 	hdl->oerr = 0;
375 	hdl->idelta = 0;
376 	hdl->odelta = 0;
377 
378 	if (hdl->sio.mode & SIO_PLAY) {
379 		/*
380 		 * keep the device paused and let sio_sun_pollfd() trigger the
381 		 * start later, to avoid buffer underruns
382 		 */
383 		hdl->filling = 1;
384 	} else {
385 		/*
386 		 * no play buffers to fill, start now!
387 		 */
388 		if (ioctl(hdl->fd, AUDIO_START) == -1) {
389 			DPERROR("AUDIO_START");
390 			hdl->sio.eof = 1;
391 			return 0;
392 		}
393 		_sio_onmove_cb(&hdl->sio, 0);
394 	}
395 	return 1;
396 }
397 
398 static int
sio_sun_flush(struct sio_hdl * sh)399 sio_sun_flush(struct sio_hdl *sh)
400 {
401 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
402 
403 	if (hdl->filling) {
404 		hdl->filling = 0;
405 		return 1;
406 	}
407 	if (ioctl(hdl->fd, AUDIO_STOP) == -1) {
408 		DPERROR("AUDIO_STOP");
409 		hdl->sio.eof = 1;
410 		return 0;
411 	}
412 	return 1;
413 }
414 
415 static int
sio_sun_setpar(struct sio_hdl * sh,struct sio_par * par)416 sio_sun_setpar(struct sio_hdl *sh, struct sio_par *par)
417 {
418 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
419 	struct audio_swpar ap;
420 
421 	AUDIO_INITPAR(&ap);
422 	ap.sig = par->sig;
423 	ap.le = par->le;
424 	ap.bits = par->bits;
425 	ap.bps = par->bps;
426 	ap.msb = par->msb;
427 	ap.rate = par->rate;
428 	if (hdl->sio.mode & SIO_PLAY)
429 		ap.pchan = par->pchan;
430 	if (hdl->sio.mode & SIO_REC)
431 		ap.rchan = par->rchan;
432 	if (par->round != ~0U && par->appbufsz != ~0U) {
433 		ap.round = par->round;
434 		ap.nblks = par->appbufsz / par->round;
435 	} else if (par->round != ~0U) {
436 		ap.round = par->round;
437 		ap.nblks = 2;
438 	} else if (par->appbufsz != ~0U) {
439 		ap.round = par->appbufsz / 2;
440 		ap.nblks = 2;
441 	}
442 	if (ioctl(hdl->fd, AUDIO_SETPAR, &ap) == -1) {
443 		DPERROR("AUDIO_SETPAR");
444 		hdl->sio.eof = 1;
445 		return 0;
446 	}
447 	return 1;
448 }
449 
450 static int
sio_sun_getpar(struct sio_hdl * sh,struct sio_par * par)451 sio_sun_getpar(struct sio_hdl *sh, struct sio_par *par)
452 {
453 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
454 	struct audio_swpar ap;
455 
456 	if (ioctl(hdl->fd, AUDIO_GETPAR, &ap) == -1) {
457 		DPERROR("AUDIO_GETPAR");
458 		hdl->sio.eof = 1;
459 		return 0;
460 	}
461 	par->sig = ap.sig;
462 	par->le = ap.le;
463 	par->bits = ap.bits;
464 	par->bps = ap.bps;
465 	par->msb = ap.msb;
466 	par->rate = ap.rate;
467 	par->pchan = ap.pchan;
468 	par->rchan = ap.rchan;
469 	par->round = ap.round;
470 	par->appbufsz = par->bufsz = ap.nblks * ap.round;
471 	par->xrun = SIO_IGNORE;
472 	return 1;
473 }
474 
475 static size_t
sio_sun_read(struct sio_hdl * sh,void * buf,size_t len)476 sio_sun_read(struct sio_hdl *sh, void *buf, size_t len)
477 {
478 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
479 	ssize_t n;
480 
481 	while ((n = read(hdl->fd, buf, len)) == -1) {
482 		if (errno == EINTR)
483 			continue;
484 		if (errno != EAGAIN) {
485 			DPERROR("sio_sun_read: read");
486 			hdl->sio.eof = 1;
487 		}
488 		return 0;
489 	}
490 	if (n == 0) {
491 		DPRINTF("sio_sun_read: eof\n");
492 		hdl->sio.eof = 1;
493 		return 0;
494 	}
495 	return n;
496 }
497 
498 static size_t
sio_sun_write(struct sio_hdl * sh,const void * buf,size_t len)499 sio_sun_write(struct sio_hdl *sh, const void *buf, size_t len)
500 {
501 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
502 	const unsigned char *data = buf;
503 	ssize_t n, todo;
504 
505 	todo = len;
506 	while ((n = write(hdl->fd, data, todo)) == -1) {
507 		if (errno == EINTR)
508 			continue;
509 		if (errno != EAGAIN) {
510 			DPERROR("sio_sun_write: write");
511 			hdl->sio.eof = 1;
512 		}
513 		return 0;
514 	}
515 	return n;
516 }
517 
518 static int
sio_sun_nfds(struct sio_hdl * hdl)519 sio_sun_nfds(struct sio_hdl *hdl)
520 {
521 	return 1;
522 }
523 
524 static int
sio_sun_pollfd(struct sio_hdl * sh,struct pollfd * pfd,int events)525 sio_sun_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events)
526 {
527 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
528 
529 	pfd->fd = hdl->fd;
530 	pfd->events = events;
531 	if (hdl->filling && hdl->sio.wused == hdl->sio.par.bufsz *
532 		hdl->sio.par.pchan * hdl->sio.par.bps) {
533 		hdl->filling = 0;
534 		if (ioctl(hdl->fd, AUDIO_START) == -1) {
535 			DPERROR("AUDIO_START");
536 			hdl->sio.eof = 1;
537 			return 0;
538 		}
539 		_sio_onmove_cb(&hdl->sio, 0);
540 	}
541 	return 1;
542 }
543 
544 int
sio_sun_revents(struct sio_hdl * sh,struct pollfd * pfd)545 sio_sun_revents(struct sio_hdl *sh, struct pollfd *pfd)
546 {
547 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
548 	struct audio_pos ap;
549 	int dierr = 0, doerr = 0, offset, delta;
550 	int revents = pfd->revents;
551 
552 	if ((pfd->revents & POLLHUP) ||
553 	    (pfd->revents & (POLLIN | POLLOUT)) == 0)
554 		return pfd->revents;
555 	if (ioctl(hdl->fd, AUDIO_GETPOS, &ap) == -1) {
556 		DPERROR("sio_sun_revents: GETPOS");
557 		hdl->sio.eof = 1;
558 		return POLLHUP;
559 	}
560 	if (hdl->sio.mode & SIO_PLAY) {
561 		delta = (ap.play_pos - hdl->obytes) / hdl->obpf;
562 		doerr = (ap.play_xrun - hdl->oerr) / hdl->obpf;
563 		hdl->obytes = ap.play_pos;
564 		hdl->oerr = ap.play_xrun;
565 		hdl->odelta += delta;
566 		if (!(hdl->sio.mode & SIO_REC)) {
567 			hdl->idelta += delta;
568 			dierr = doerr;
569 		}
570 		if (doerr > 0)
571 			DPRINTFN(2, "play xrun %d\n", doerr);
572 	}
573 	if (hdl->sio.mode & SIO_REC) {
574 		delta = (ap.rec_pos - hdl->ibytes) / hdl->ibpf;
575 		dierr = (ap.rec_xrun - hdl->ierr) / hdl->ibpf;
576 		hdl->ibytes = ap.rec_pos;
577 		hdl->ierr = ap.rec_xrun;
578 		hdl->idelta += delta;
579 		if (!(hdl->sio.mode & SIO_PLAY)) {
580 			hdl->odelta += delta;
581 			doerr = dierr;
582 		}
583 		if (dierr > 0)
584 			DPRINTFN(2, "rec xrun %d\n", dierr);
585 	}
586 
587 	/*
588 	 * GETPOS reports positions including xruns,
589 	 * so we have to subtract to get the real position
590 	 */
591 	hdl->idelta -= dierr;
592 	hdl->odelta -= doerr;
593 
594 	offset = doerr - dierr;
595 	if (offset > 0) {
596 		hdl->sio.rdrop += offset * hdl->ibpf;
597 		hdl->idelta -= offset;
598 		DPRINTFN(2, "will drop %d and pause %d\n", offset, doerr);
599 	} else if (offset < 0) {
600 		hdl->sio.wsil += -offset * hdl->obpf;
601 		hdl->odelta -= -offset;
602 		DPRINTFN(2, "will insert %d and pause %d\n", -offset, dierr);
603 	}
604 
605 	delta = (hdl->idelta > hdl->odelta) ? hdl->idelta : hdl->odelta;
606 	if (delta > 0) {
607 		_sio_onmove_cb(&hdl->sio, delta);
608 		hdl->idelta -= delta;
609 		hdl->odelta -= delta;
610 	}
611 	return revents;
612 }
613