1/*	$OpenBSD: siofile.c,v 1.27 2024/04/02 05:32:10 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_migrate(d);
88	dev_abort(d);
89}
90
91/*
92 * open the device.
93 */
94int
95dev_sio_open(struct dev *d)
96{
97	struct sio_par par;
98	unsigned int rate, mode = d->reqmode & (SIO_PLAY | SIO_REC);
99
100	d->sio.hdl = fdpass_sio_open(d->num, mode);
101	if (d->sio.hdl == NULL) {
102		if (mode != (SIO_PLAY | SIO_REC))
103			return 0;
104		d->sio.hdl = fdpass_sio_open(d->num, SIO_PLAY);
105		if (d->sio.hdl != NULL)
106			mode = SIO_PLAY;
107		else {
108			d->sio.hdl = fdpass_sio_open(d->num, SIO_REC);
109			if (d->sio.hdl != NULL)
110				mode = SIO_REC;
111			else
112				return 0;
113		}
114		if (log_level >= 1) {
115			log_puts("warning, device opened in ");
116			log_puts(mode == SIO_PLAY ? "play-only" : "rec-only");
117			log_puts(" mode\n");
118		}
119	}
120	d->mode = mode;
121
122	d->sioctl.hdl = fdpass_sioctl_open(d->num, SIOCTL_READ | SIOCTL_WRITE);
123	if (d->sioctl.hdl == NULL) {
124		if (log_level >= 1) {
125			dev_log(d);
126			log_puts(": no control device\n");
127		}
128	}
129
130	sio_initpar(&par);
131	par.bits = d->par.bits;
132	par.bps = d->par.bps;
133	par.sig = d->par.sig;
134	par.le = d->par.le;
135	par.msb = d->par.msb;
136	if (d->mode & SIO_PLAY)
137		par.pchan = d->pchan;
138	if (d->mode & SIO_REC)
139		par.rchan = d->rchan;
140	par.appbufsz = d->bufsz;
141	par.round = d->round;
142	par.rate = d->rate;
143	if (!sio_setpar(d->sio.hdl, &par))
144		goto bad_close;
145	if (!sio_getpar(d->sio.hdl, &par))
146		goto bad_close;
147
148	/*
149	 * If the requested rate is not supported by the device,
150	 * use the new one, but retry using a block size that would
151	 * match the requested one
152	 */
153	rate = par.rate;
154	if (rate != d->rate) {
155		sio_initpar(&par);
156		par.bits = d->par.bits;
157		par.bps = d->par.bps;
158		par.sig = d->par.sig;
159		par.le = d->par.le;
160		par.msb = d->par.msb;
161		if (mode & SIO_PLAY)
162			par.pchan = d->reqpchan;
163		if (mode & SIO_REC)
164			par.rchan = d->reqrchan;
165		par.appbufsz = d->bufsz * rate / d->rate;
166		par.round = d->round * rate / d->rate;
167		par.rate = rate;
168		if (!sio_setpar(d->sio.hdl, &par))
169			goto bad_close;
170		if (!sio_getpar(d->sio.hdl, &par))
171			goto bad_close;
172	}
173
174#ifdef DEBUG
175	/*
176	 * We support any parameter combination exposed by the kernel,
177	 * and we have no other choice than trusting the kernel for
178	 * returning correct parameters. But let's check parameters
179	 * early and nicely report kernel bugs rather than crashing
180	 * later in memset(), malloc() or alike.
181	 */
182
183	if (par.bits > BITS_MAX) {
184		dev_log(d);
185		log_puts(": ");
186		log_putu(par.bits);
187		log_puts(": unsupported number of bits\n");
188		goto bad_close;
189	}
190	if (par.bps > SIO_BPS(BITS_MAX)) {
191		dev_log(d);
192		log_puts(": ");
193		log_putu(par.bps);
194		log_puts(": unsupported sample size\n");
195		goto bad_close;
196	}
197	if ((d->mode & SIO_PLAY) && par.pchan > NCHAN_MAX) {
198		dev_log(d);
199		log_puts(": ");
200		log_putu(par.pchan);
201		log_puts(": unsupported number of play channels\n");
202		goto bad_close;
203	}
204	if ((d->mode & SIO_REC) && par.rchan > NCHAN_MAX) {
205		dev_log(d);
206		log_puts(": ");
207		log_putu(par.rchan);
208		log_puts(": unsupported number of rec channels\n");
209		goto bad_close;
210	}
211	if (par.bufsz == 0 || par.bufsz > RATE_MAX) {
212		dev_log(d);
213		log_puts(": ");
214		log_putu(par.bufsz);
215		log_puts(": unsupported buffer size\n");
216		goto bad_close;
217	}
218	if (par.round == 0 || par.round > par.bufsz ||
219	    par.bufsz % par.round != 0) {
220		dev_log(d);
221		log_puts(": ");
222		log_putu(par.round);
223		log_puts(": unsupported block size\n");
224		goto bad_close;
225	}
226	if (par.rate == 0 || par.rate > RATE_MAX) {
227		dev_log(d);
228		log_puts(": ");
229		log_putu(par.rate);
230		log_puts(": unsupported rate\n");
231		goto bad_close;
232	}
233#endif
234	d->par.bits = par.bits;
235	d->par.bps = par.bps;
236	d->par.sig = par.sig;
237	d->par.le = par.le;
238	d->par.msb = par.msb;
239	if (d->mode & SIO_PLAY)
240		d->pchan = par.pchan;
241	if (d->mode & SIO_REC)
242		d->rchan = par.rchan;
243	d->bufsz = par.bufsz;
244	d->round = par.round;
245	d->rate = par.rate;
246	if (d->mode & MODE_PLAY)
247		d->mode |= MODE_MON;
248	sio_onmove(d->sio.hdl, dev_sio_onmove, d);
249	d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(d->sio.hdl));
250	if (d->sioctl.hdl) {
251		d->sioctl.file = file_new(&dev_sioctl_ops, d, "mix",
252		    sioctl_nfds(d->sioctl.hdl));
253	}
254	timo_set(&d->sio.watchdog, dev_sio_timeout, d);
255	dev_sioctl_open(d);
256	return 1;
257 bad_close:
258	sio_close(d->sio.hdl);
259	if (d->sioctl.hdl) {
260		sioctl_close(d->sioctl.hdl);
261		d->sioctl.hdl = NULL;
262	}
263	return 0;
264}
265
266void
267dev_sio_close(struct dev *d)
268{
269	dev_sioctl_close(d);
270#ifdef DEBUG
271	if (log_level >= 3) {
272		dev_log(d);
273		log_puts(": closed\n");
274	}
275#endif
276	timo_del(&d->sio.watchdog);
277	file_del(d->sio.file);
278	sio_close(d->sio.hdl);
279	if (d->sioctl.hdl) {
280		file_del(d->sioctl.file);
281		sioctl_close(d->sioctl.hdl);
282		d->sioctl.hdl = NULL;
283	}
284}
285
286void
287dev_sio_start(struct dev *d)
288{
289	if (!sio_start(d->sio.hdl)) {
290		if (log_level >= 1) {
291			dev_log(d);
292			log_puts(": failed to start device\n");
293		}
294		return;
295	}
296	if (d->mode & MODE_PLAY) {
297		d->sio.cstate = DEV_SIO_CYCLE;
298		d->sio.todo = 0;
299	} else {
300		d->sio.cstate = DEV_SIO_READ;
301		d->sio.todo = d->round * d->rchan * d->par.bps;
302	}
303#ifdef DEBUG
304	d->sio.pused = 0;
305	d->sio.rused = 0;
306	d->sio.sum_utime = 0;
307	d->sio.sum_wtime = 0;
308	d->sio.wtime = file_wtime;
309	d->sio.utime = file_utime;
310	if (log_level >= 3) {
311		dev_log(d);
312		log_puts(": started\n");
313	}
314#endif
315	timo_add(&d->sio.watchdog, WATCHDOG_USEC);
316}
317
318void
319dev_sio_stop(struct dev *d)
320{
321	if (!sio_eof(d->sio.hdl) && !sio_flush(d->sio.hdl)) {
322		if (log_level >= 1) {
323			dev_log(d);
324			log_puts(": failed to stop device\n");
325		}
326		return;
327	}
328#ifdef DEBUG
329	if (log_level >= 3) {
330		dev_log(d);
331		log_puts(": stopped, load avg = ");
332		log_puti(d->sio.sum_utime / 1000);
333		log_puts(" / ");
334		log_puti(d->sio.sum_wtime / 1000);
335		log_puts("\n");
336	}
337#endif
338	timo_del(&d->sio.watchdog);
339}
340
341int
342dev_sio_pollfd(void *arg, struct pollfd *pfd)
343{
344	struct dev *d = arg;
345	int events;
346
347	events = (d->sio.cstate == DEV_SIO_READ) ? POLLIN : POLLOUT;
348	return sio_pollfd(d->sio.hdl, pfd, events);
349}
350
351int
352dev_sio_revents(void *arg, struct pollfd *pfd)
353{
354	struct dev *d = arg;
355	int events;
356
357	events = sio_revents(d->sio.hdl, pfd);
358#ifdef DEBUG
359	d->sio.events = events;
360#endif
361	return events;
362}
363
364void
365dev_sio_run(void *arg)
366{
367	struct dev *d = arg;
368	unsigned char *data, *base;
369	unsigned int n;
370
371	/*
372	 * sio_read() and sio_write() would block at the end of the
373	 * cycle so we *must* return and restart poll()'ing. Otherwise
374	 * we may trigger dev_cycle() which would make all clients
375	 * underrun (ex, on a play-only device)
376	 */
377	for (;;) {
378		if (d->pstate != DEV_RUN)
379			return;
380		switch (d->sio.cstate) {
381		case DEV_SIO_READ:
382#ifdef DEBUG
383			if (!(d->sio.events & POLLIN)) {
384				dev_log(d);
385				log_puts(": recording, but POLLIN not set\n");
386				panic();
387			}
388			if (d->sio.todo == 0) {
389				dev_log(d);
390				log_puts(": can't read data\n");
391				panic();
392			}
393			if (d->prime > 0) {
394				dev_log(d);
395				log_puts(": unexpected data\n");
396				panic();
397			}
398#endif
399			base = d->decbuf ? d->decbuf : (unsigned char *)d->rbuf;
400			data = base +
401			    d->rchan * d->round * d->par.bps -
402			    d->sio.todo;
403			n = sio_read(d->sio.hdl, data, d->sio.todo);
404			d->sio.todo -= n;
405#ifdef DEBUG
406			if (log_level >= 4) {
407				dev_log(d);
408				log_puts(": read ");
409				log_putu(n);
410				log_puts(": bytes, todo ");
411				log_putu(d->sio.todo);
412				log_puts("/");
413				log_putu(d->round * d->rchan * d->par.bps);
414				log_puts("\n");
415			}
416#endif
417			if (d->sio.todo > 0)
418				return;
419#ifdef DEBUG
420			d->sio.rused -= d->round;
421			if (log_level >= 2) {
422				if (d->sio.rused >= d->round) {
423					dev_log(d);
424					log_puts(": rec hw xrun, rused = ");
425					log_puti(d->sio.rused);
426					log_puts("/");
427					log_puti(d->bufsz);
428					log_puts("\n");
429				}
430				if (d->sio.rused < 0 ||
431				    d->sio.rused >= d->bufsz) {
432					dev_log(d);
433					log_puts(": out of bounds rused = ");
434					log_puti(d->sio.rused);
435					log_puts("/");
436					log_puti(d->bufsz);
437					log_puts("\n");
438				}
439			}
440#endif
441			d->sio.cstate = DEV_SIO_CYCLE;
442			break;
443		case DEV_SIO_CYCLE:
444			timo_del(&d->sio.watchdog);
445			timo_add(&d->sio.watchdog, WATCHDOG_USEC);
446
447#ifdef DEBUG
448			/*
449			 * check that we're called at cycle boundary:
450			 * either after a recorded block, or when POLLOUT is
451			 * raised
452			 */
453			if (!((d->mode & MODE_REC) && d->prime == 0) &&
454			    !(d->sio.events & POLLOUT)) {
455				dev_log(d);
456				log_puts(": cycle not at block boundary\n");
457				panic();
458			}
459#endif
460			dev_cycle(d);
461			if (d->mode & MODE_PLAY) {
462				d->sio.cstate = DEV_SIO_WRITE;
463				d->sio.todo = d->round * d->pchan * d->par.bps;
464				break;
465			} else {
466				d->sio.cstate = DEV_SIO_READ;
467				d->sio.todo = d->round * d->rchan * d->par.bps;
468				return;
469			}
470		case DEV_SIO_WRITE:
471#ifdef DEBUG
472			if (d->sio.todo == 0) {
473				dev_log(d);
474				log_puts(": can't write data\n");
475				panic();
476			}
477#endif
478			base = d->encbuf ? d->encbuf : (unsigned char *)DEV_PBUF(d);
479			data = base +
480			    d->pchan * d->round * d->par.bps -
481			    d->sio.todo;
482			n = sio_write(d->sio.hdl, data, d->sio.todo);
483			d->sio.todo -= n;
484#ifdef DEBUG
485			if (log_level >= 4) {
486				dev_log(d);
487				log_puts(": wrote ");
488				log_putu(n);
489				log_puts(" bytes, todo ");
490				log_putu(d->sio.todo);
491				log_puts("/");
492				log_putu(d->round * d->pchan * d->par.bps);
493				log_puts("\n");
494			}
495#endif
496			if (d->sio.todo > 0)
497				return;
498#ifdef DEBUG
499			d->sio.pused += d->round;
500			if (log_level >= 2) {
501				if (d->prime == 0 &&
502				    d->sio.pused <= d->bufsz - d->round) {
503					dev_log(d);
504					log_puts(": play hw xrun, pused = ");
505					log_puti(d->sio.pused);
506					log_puts("/");
507					log_puti(d->bufsz);
508					log_puts("\n");
509				}
510				if (d->sio.pused < 0 ||
511				    d->sio.pused > d->bufsz) {
512					/* device driver or libsndio bug */
513					dev_log(d);
514					log_puts(": out of bounds pused = ");
515					log_puti(d->sio.pused);
516					log_puts("/");
517					log_puti(d->bufsz);
518					log_puts("\n");
519				}
520			}
521#endif
522			d->poffs += d->round;
523			if (d->poffs == d->psize)
524				d->poffs = 0;
525			if ((d->mode & MODE_REC) && d->prime == 0) {
526				d->sio.cstate = DEV_SIO_READ;
527				d->sio.todo = d->round * d->rchan * d->par.bps;
528			} else
529				d->sio.cstate = DEV_SIO_CYCLE;
530			return;
531		}
532	}
533}
534
535void
536dev_sio_hup(void *arg)
537{
538	struct dev *d = arg;
539
540#ifdef DEBUG
541	if (log_level >= 2) {
542		dev_log(d);
543		log_puts(": disconnected\n");
544	}
545#endif
546	dev_migrate(d);
547	dev_abort(d);
548}
549