siofile.c revision 1.22
1/*	$OpenBSD: siofile.c,v 1.22 2020/06/28 05:21:39 ratchov Exp $	*/
2/*
3 * Copyright (c) 2008-2012 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#include <sys/time.h>
18#include <sys/types.h>
19
20#include <poll.h>
21#include <sndio.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25
26#include "abuf.h"
27#include "defs.h"
28#include "dev.h"
29#include "dev_sioctl.h"
30#include "dsp.h"
31#include "fdpass.h"
32#include "file.h"
33#include "siofile.h"
34#include "utils.h"
35
36#define WATCHDOG_USEC	4000000		/* 4 seconds */
37
38void dev_sio_onmove(void *, int);
39void dev_sio_timeout(void *);
40int dev_sio_pollfd(void *, struct pollfd *);
41int dev_sio_revents(void *, struct pollfd *);
42void dev_sio_run(void *);
43void dev_sio_hup(void *);
44
45extern struct fileops dev_sioctl_ops;
46
47struct fileops dev_sio_ops = {
48	"sio",
49	dev_sio_pollfd,
50	dev_sio_revents,
51	dev_sio_run,
52	dev_sio_run,
53	dev_sio_hup
54};
55
56void
57dev_sio_onmove(void *arg, int delta)
58{
59	struct dev *d = arg;
60
61#ifdef DEBUG
62	if (log_level >= 4) {
63		dev_log(d);
64		log_puts(": tick, delta = ");
65		log_puti(delta);
66		log_puts("\n");
67	}
68	d->sio.sum_utime += file_utime - d->sio.utime;
69	d->sio.sum_wtime += file_wtime - d->sio.wtime;
70	d->sio.wtime = file_wtime;
71	d->sio.utime = file_utime;
72	if (d->mode & MODE_PLAY)
73		d->sio.pused -= delta;
74	if (d->mode & MODE_REC)
75		d->sio.rused += delta;
76#endif
77	dev_onmove(d, delta);
78}
79
80void
81dev_sio_timeout(void *arg)
82{
83	struct dev *d = arg;
84
85	dev_log(d);
86	log_puts(": watchdog timeout\n");
87	dev_abort(d);
88}
89
90/*
91 * open the device using one of the provided paths
92 */
93static struct sio_hdl *
94dev_sio_openlist(struct dev *d, unsigned int mode, struct sioctl_hdl **rctlhdl)
95{
96	struct dev_alt *n;
97	struct sio_hdl *hdl;
98	struct sioctl_hdl *ctlhdl;
99	struct ctl *c;
100	int val;
101
102	for (n = d->alt_list; n != NULL; n = n->next) {
103		if (d->alt_num == n->idx)
104			continue;
105		hdl = fdpass_sio_open(d->num, n->idx, mode);
106		if (hdl != NULL) {
107			if (log_level >= 2) {
108				dev_log(d);
109				log_puts(": using ");
110				log_puts(n->name);
111				log_puts("\n");
112			}
113			ctlhdl = fdpass_sioctl_open(d->num, n->idx,
114			    SIOCTL_READ | SIOCTL_WRITE);
115			if (ctlhdl == NULL) {
116				if (log_level >= 1) {
117					dev_log(d);
118					log_puts(": no control device\n");
119				}
120			}
121			d->alt_num = n->idx;
122			for (c = d->ctl_list; c != NULL; c = c->next) {
123				if (c->addr < CTLADDR_ALT_SEL ||
124				    c->addr >= CTLADDR_ALT_SEL + DEV_NMAX)
125					continue;
126				val = (c->addr - CTLADDR_ALT_SEL) == n->idx;
127				if (c->curval == val)
128					continue;
129				c->curval = val;
130				if (val)
131					c->val_mask = ~0U;
132			}
133			*rctlhdl = ctlhdl;
134			return hdl;
135		}
136	}
137	return NULL;
138}
139
140/*
141 * open the device.
142 */
143int
144dev_sio_open(struct dev *d)
145{
146	struct sio_par par;
147	unsigned int mode = d->mode & (MODE_PLAY | MODE_REC);
148
149	d->sio.hdl = dev_sio_openlist(d, mode, &d->sioctl.hdl);
150	if (d->sio.hdl == NULL) {
151		if (mode != (SIO_PLAY | SIO_REC))
152			return 0;
153		d->sio.hdl = dev_sio_openlist(d, SIO_PLAY, &d->sioctl.hdl);
154		if (d->sio.hdl != NULL)
155			mode = SIO_PLAY;
156		else {
157			d->sio.hdl = dev_sio_openlist(d,
158			    SIO_REC, &d->sioctl.hdl);
159			if (d->sio.hdl != NULL)
160				mode = SIO_REC;
161			else
162				return 0;
163		}
164		if (log_level >= 1) {
165			log_puts("warning, device opened in ");
166			log_puts(mode == SIO_PLAY ? "play-only" : "rec-only");
167			log_puts(" mode\n");
168		}
169	}
170	sio_initpar(&par);
171	par.bits = d->par.bits;
172	par.bps = d->par.bps;
173	par.sig = d->par.sig;
174	par.le = d->par.le;
175	par.msb = d->par.msb;
176	if (mode & SIO_PLAY)
177		par.pchan = d->pchan;
178	if (mode & SIO_REC)
179		par.rchan = d->rchan;
180	if (d->bufsz)
181		par.appbufsz = d->bufsz;
182	if (d->round)
183		par.round = d->round;
184	if (d->rate)
185		par.rate = d->rate;
186	if (!sio_setpar(d->sio.hdl, &par))
187		goto bad_close;
188	if (!sio_getpar(d->sio.hdl, &par))
189		goto bad_close;
190
191#ifdef DEBUG
192	/*
193	 * We support any parameter combination exposed by the kernel,
194	 * and we have no other choice than trusting the kernel for
195	 * returning correct parameters. But let's check parameters
196	 * early and nicely report kernel bugs rather than crashing
197	 * later in memset(), malloc() or alike.
198	 */
199
200	if (par.bits > BITS_MAX) {
201		dev_log(d);
202		log_puts(": ");
203		log_putu(par.bits);
204		log_puts(": unsupported number of bits\n");
205		goto bad_close;
206	}
207	if (par.bps > SIO_BPS(BITS_MAX)) {
208		dev_log(d);
209		log_puts(": ");
210		log_putu(par.bps);
211		log_puts(": unsupported sample size\n");
212		goto bad_close;
213	}
214	if ((mode & SIO_PLAY) && par.pchan > NCHAN_MAX) {
215		dev_log(d);
216		log_puts(": ");
217		log_putu(par.pchan);
218		log_puts(": unsupported number of play channels\n");
219		goto bad_close;
220	}
221	if ((mode & SIO_REC) && par.rchan > NCHAN_MAX) {
222		dev_log(d);
223		log_puts(": ");
224		log_putu(par.rchan);
225		log_puts(": unsupported number of rec channels\n");
226		goto bad_close;
227	}
228	if (par.bufsz == 0 || par.bufsz > RATE_MAX) {
229		dev_log(d);
230		log_puts(": ");
231		log_putu(par.bufsz);
232		log_puts(": unsupported buffer size\n");
233		goto bad_close;
234	}
235	if (par.round == 0 || par.round > par.bufsz ||
236	    par.bufsz % par.round != 0) {
237		dev_log(d);
238		log_puts(": ");
239		log_putu(par.round);
240		log_puts(": unsupported block size\n");
241		goto bad_close;
242	}
243	if (par.rate == 0 || par.rate > RATE_MAX) {
244		dev_log(d);
245		log_puts(": ");
246		log_putu(par.rate);
247		log_puts(": unsupported rate\n");
248		goto bad_close;
249	}
250#endif
251
252	d->par.bits = par.bits;
253	d->par.bps = par.bps;
254	d->par.sig = par.sig;
255	d->par.le = par.le;
256	d->par.msb = par.msb;
257	if (mode & SIO_PLAY)
258		d->pchan = par.pchan;
259	if (mode & SIO_REC)
260		d->rchan = par.rchan;
261	d->bufsz = par.bufsz;
262	d->round = par.round;
263	d->rate = par.rate;
264	if (!(mode & MODE_PLAY))
265		d->mode &= ~(MODE_PLAY | MODE_MON);
266	if (!(mode & MODE_REC))
267		d->mode &= ~MODE_REC;
268	sio_onmove(d->sio.hdl, dev_sio_onmove, d);
269	d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(d->sio.hdl));
270	if (d->sioctl.hdl) {
271		d->sioctl.file = file_new(&dev_sioctl_ops, d, "mix",
272		    sioctl_nfds(d->sioctl.hdl));
273	}
274	timo_set(&d->sio.watchdog, dev_sio_timeout, d);
275	dev_sioctl_open(d);
276	return 1;
277 bad_close:
278	sio_close(d->sio.hdl);
279	if (d->sioctl.hdl) {
280		sioctl_close(d->sioctl.hdl);
281		d->sioctl.hdl = NULL;
282	}
283	return 0;
284}
285
286/*
287 * Open an alternate device. Upon success and if the new device is
288 * compatible with the old one, close the old device and continue
289 * using the new one. The new device is not started.
290 */
291int
292dev_sio_reopen(struct dev *d)
293{
294	struct sioctl_hdl *ctlhdl;
295	struct sio_par par;
296	struct sio_hdl *hdl;
297
298	hdl = dev_sio_openlist(d, d->mode & (MODE_PLAY | MODE_REC), &ctlhdl);
299	if (hdl == NULL) {
300		if (log_level >= 1) {
301			dev_log(d);
302			log_puts(": couldn't open an alternate device\n");
303		}
304		return 0;
305	}
306
307	sio_initpar(&par);
308	par.bits = d->par.bits;
309	par.bps = d->par.bps;
310	par.sig = d->par.sig;
311	par.le = d->par.le;
312	par.msb = d->par.msb;
313	if (d->mode & SIO_PLAY)
314		par.pchan = d->pchan;
315	if (d->mode & SIO_REC)
316		par.rchan = d->rchan;
317	par.appbufsz = d->bufsz;
318	par.round = d->round;
319	par.rate = d->rate;
320	if (!sio_setpar(hdl, &par))
321		goto bad_close;
322	if (!sio_getpar(hdl, &par))
323		goto bad_close;
324
325	/* check if new parameters are compatible with old ones */
326	if (par.round != d->round || par.bufsz != d->bufsz ||
327	    par.rate != d->rate) {
328		if (log_level >= 1) {
329			dev_log(d);
330			log_puts(": alternate device not compatible\n");
331		}
332		goto bad_close;
333	}
334
335	/* close unused device */
336	timo_del(&d->sio.watchdog);
337	file_del(d->sio.file);
338	sio_close(d->sio.hdl);
339	if (d->sioctl.hdl) {
340		file_del(d->sioctl.file);
341		sioctl_close(d->sioctl.hdl);
342		d->sioctl.hdl = NULL;
343	}
344
345	/* update parameters */
346	d->par.bits = par.bits;
347	d->par.bps = par.bps;
348	d->par.sig = par.sig;
349	d->par.le = par.le;
350	d->par.msb = par.msb;
351	if (d->mode & SIO_PLAY)
352		d->pchan = par.pchan;
353	if (d->mode & SIO_REC)
354		d->rchan = par.rchan;
355
356	d->sio.hdl = hdl;
357	d->sioctl.hdl = ctlhdl;
358	d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(hdl));
359	if (d->sioctl.hdl) {
360		d->sioctl.file = file_new(&dev_sioctl_ops, d, "mix",
361		    sioctl_nfds(ctlhdl));
362	}
363	sio_onmove(hdl, dev_sio_onmove, d);
364	return 1;
365bad_close:
366	sio_close(hdl);
367	if (ctlhdl)
368		sioctl_close(ctlhdl);
369	return 0;
370}
371
372void
373dev_sio_close(struct dev *d)
374{
375	dev_sioctl_close(d);
376#ifdef DEBUG
377	if (log_level >= 3) {
378		dev_log(d);
379		log_puts(": closed\n");
380	}
381#endif
382	timo_del(&d->sio.watchdog);
383	file_del(d->sio.file);
384	sio_close(d->sio.hdl);
385	if (d->sioctl.hdl) {
386		file_del(d->sioctl.file);
387		sioctl_close(d->sioctl.hdl);
388		d->sioctl.hdl = NULL;
389	}
390	d->alt_num = -1;
391}
392
393void
394dev_sio_start(struct dev *d)
395{
396	if (!sio_start(d->sio.hdl)) {
397		if (log_level >= 1) {
398			dev_log(d);
399			log_puts(": failed to start device\n");
400		}
401		return;
402	}
403	if (d->mode & MODE_PLAY) {
404		d->sio.cstate = DEV_SIO_CYCLE;
405		d->sio.todo = 0;
406	} else {
407		d->sio.cstate = DEV_SIO_READ;
408		d->sio.todo = d->round * d->rchan * d->par.bps;
409	}
410#ifdef DEBUG
411	d->sio.pused = 0;
412	d->sio.rused = 0;
413	d->sio.sum_utime = 0;
414	d->sio.sum_wtime = 0;
415	d->sio.wtime = file_wtime;
416	d->sio.utime = file_utime;
417	if (log_level >= 3) {
418		dev_log(d);
419		log_puts(": started\n");
420	}
421#endif
422	timo_add(&d->sio.watchdog, WATCHDOG_USEC);
423}
424
425void
426dev_sio_stop(struct dev *d)
427{
428	if (!sio_eof(d->sio.hdl) && !sio_stop(d->sio.hdl)) {
429		if (log_level >= 1) {
430			dev_log(d);
431			log_puts(": failed to stop device\n");
432		}
433		return;
434	}
435#ifdef DEBUG
436	if (log_level >= 3) {
437		dev_log(d);
438		log_puts(": stopped, load avg = ");
439		log_puti(d->sio.sum_utime / 1000);
440		log_puts(" / ");
441		log_puti(d->sio.sum_wtime / 1000);
442		log_puts("\n");
443	}
444#endif
445	timo_del(&d->sio.watchdog);
446}
447
448int
449dev_sio_pollfd(void *arg, struct pollfd *pfd)
450{
451	struct dev *d = arg;
452	int events;
453
454	events = (d->sio.cstate == DEV_SIO_READ) ? POLLIN : POLLOUT;
455	return sio_pollfd(d->sio.hdl, pfd, events);
456}
457
458int
459dev_sio_revents(void *arg, struct pollfd *pfd)
460{
461	struct dev *d = arg;
462	int events;
463
464	events = sio_revents(d->sio.hdl, pfd);
465#ifdef DEBUG
466	d->sio.events = events;
467#endif
468	return events;
469}
470
471void
472dev_sio_run(void *arg)
473{
474	struct dev *d = arg;
475	unsigned char *data, *base;
476	unsigned int n;
477
478	/*
479	 * sio_read() and sio_write() would block at the end of the
480	 * cycle so we *must* return and restart poll()'ing. Otherwise
481	 * we may trigger dev_cycle() which would make all clients
482	 * underrun (ex, on a play-only device)
483	 */
484	for (;;) {
485		if (d->pstate != DEV_RUN)
486			return;
487		switch (d->sio.cstate) {
488		case DEV_SIO_READ:
489#ifdef DEBUG
490			if (!(d->sio.events & POLLIN)) {
491				dev_log(d);
492				log_puts(": recording, but POLLIN not set\n");
493				panic();
494			}
495			if (d->sio.todo == 0) {
496				dev_log(d);
497				log_puts(": can't read data\n");
498				panic();
499			}
500			if (d->prime > 0) {
501				dev_log(d);
502				log_puts(": unexpected data\n");
503				panic();
504			}
505#endif
506			base = d->decbuf ? d->decbuf : (unsigned char *)d->rbuf;
507			data = base +
508			    d->rchan * d->round * d->par.bps -
509			    d->sio.todo;
510			n = sio_read(d->sio.hdl, data, d->sio.todo);
511			d->sio.todo -= n;
512#ifdef DEBUG
513			if (log_level >= 4) {
514				dev_log(d);
515				log_puts(": read ");
516				log_putu(n);
517				log_puts(": bytes, todo ");
518				log_putu(d->sio.todo);
519				log_puts("/");
520				log_putu(d->round * d->rchan * d->par.bps);
521				log_puts("\n");
522			}
523#endif
524			if (d->sio.todo > 0)
525				return;
526#ifdef DEBUG
527			d->sio.rused -= d->round;
528			if (log_level >= 2) {
529				if (d->sio.rused >= d->round) {
530					dev_log(d);
531					log_puts(": rec hw xrun, rused = ");
532					log_puti(d->sio.rused);
533					log_puts("/");
534					log_puti(d->bufsz);
535					log_puts("\n");
536				}
537				if (d->sio.rused < 0 ||
538				    d->sio.rused >= d->bufsz) {
539					dev_log(d);
540					log_puts(": out of bounds rused = ");
541					log_puti(d->sio.rused);
542					log_puts("/");
543					log_puti(d->bufsz);
544					log_puts("\n");
545				}
546			}
547#endif
548			d->sio.cstate = DEV_SIO_CYCLE;
549			break;
550		case DEV_SIO_CYCLE:
551			timo_del(&d->sio.watchdog);
552			timo_add(&d->sio.watchdog, WATCHDOG_USEC);
553
554#ifdef DEBUG
555			/*
556			 * check that we're called at cycle boundary:
557			 * either after a recorded block, or when POLLOUT is
558			 * raised
559			 */
560			if (!((d->mode & MODE_REC) && d->prime == 0) &&
561			    !(d->sio.events & POLLOUT)) {
562				dev_log(d);
563				log_puts(": cycle not at block boundary\n");
564				panic();
565			}
566#endif
567			dev_cycle(d);
568			if (d->mode & MODE_PLAY) {
569				d->sio.cstate = DEV_SIO_WRITE;
570				d->sio.todo = d->round * d->pchan * d->par.bps;
571				break;
572			} else {
573				d->sio.cstate = DEV_SIO_READ;
574				d->sio.todo = d->round * d->rchan * d->par.bps;
575				return;
576			}
577		case DEV_SIO_WRITE:
578#ifdef DEBUG
579			if (d->sio.todo == 0) {
580				dev_log(d);
581				log_puts(": can't write data\n");
582				panic();
583			}
584#endif
585			base = d->encbuf ? d->encbuf : (unsigned char *)DEV_PBUF(d);
586			data = base +
587			    d->pchan * d->round * d->par.bps -
588			    d->sio.todo;
589			n = sio_write(d->sio.hdl, data, d->sio.todo);
590			d->sio.todo -= n;
591#ifdef DEBUG
592			if (log_level >= 4) {
593				dev_log(d);
594				log_puts(": wrote ");
595				log_putu(n);
596				log_puts(" bytes, todo ");
597				log_putu(d->sio.todo);
598				log_puts("/");
599				log_putu(d->round * d->pchan * d->par.bps);
600				log_puts("\n");
601			}
602#endif
603			if (d->sio.todo > 0)
604				return;
605#ifdef DEBUG
606			d->sio.pused += d->round;
607			if (log_level >= 2) {
608				if (d->prime == 0 &&
609				    d->sio.pused <= d->bufsz - d->round) {
610					dev_log(d);
611					log_puts(": play hw xrun, pused = ");
612					log_puti(d->sio.pused);
613					log_puts("/");
614					log_puti(d->bufsz);
615					log_puts("\n");
616				}
617				if (d->sio.pused < 0 ||
618				    d->sio.pused > d->bufsz) {
619					/* device driver or libsndio bug */
620					dev_log(d);
621					log_puts(": out of bounds pused = ");
622					log_puti(d->sio.pused);
623					log_puts("/");
624					log_puti(d->bufsz);
625					log_puts("\n");
626				}
627			}
628#endif
629			d->poffs += d->round;
630			if (d->poffs == d->psize)
631				d->poffs = 0;
632			if ((d->mode & MODE_REC) && d->prime == 0) {
633				d->sio.cstate = DEV_SIO_READ;
634				d->sio.todo = d->round * d->rchan * d->par.bps;
635			} else
636				d->sio.cstate = DEV_SIO_CYCLE;
637			return;
638		}
639	}
640}
641
642void
643dev_sio_hup(void *arg)
644{
645	struct dev *d = arg;
646
647#ifdef DEBUG
648	if (log_level >= 2) {
649		dev_log(d);
650		log_puts(": disconnected\n");
651	}
652#endif
653	if (!dev_reopen(d))
654		dev_abort(d);
655}
656