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