xref: /openbsd-src/lib/libsndio/sio.c (revision ea3820122545f88c1cd741b06bdc167d4808a522)
1 /*	$OpenBSD: sio.c,v 1.14 2013/08/24 12:32:34 ratchov 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/param.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <poll.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <time.h>
29 #include <unistd.h>
30 
31 #include "debug.h"
32 #include "sio_priv.h"
33 
34 #define SIO_PAR_MAGIC	0x83b905a4
35 
36 void
37 sio_initpar(struct sio_par *par)
38 {
39 	memset(par, 0xff, sizeof(struct sio_par));
40 	par->__magic = SIO_PAR_MAGIC;
41 }
42 
43 struct sio_hdl *
44 sio_open(const char *str, unsigned int mode, int nbio)
45 {
46 	static char devany[] = SIO_DEVANY;
47 	struct sio_hdl *hdl;
48 	const char *p;
49 
50 #ifdef DEBUG
51 	sndio_debug_init();
52 #endif
53 	if ((mode & (SIO_PLAY | SIO_REC)) == 0)
54 		return NULL;
55 	if (str == NULL) /* backward compat */
56 		str = devany;
57 	if (strcmp(str, devany) == 0 && !issetugid()) {
58 		str = getenv("AUDIODEVICE");
59 		if (str == NULL)
60 			str = devany;
61 	}
62 	if (strcmp(str, devany) == 0) {
63 		hdl = sio_aucat_open("/0", mode, nbio);
64 		if (hdl != NULL)
65 			return hdl;
66 		return sio_sun_open("/0", mode, nbio);
67 	}
68 	if ((p = sndio_parsetype(str, "snd")) != NULL ||
69 	    (p = sndio_parsetype(str, "aucat")) != NULL)
70 		return sio_aucat_open(p, mode, nbio);
71 	if ((p = sndio_parsetype(str, "rsnd")) != NULL ||
72 	    (p = sndio_parsetype(str, "sun")) != NULL) {
73 		return sio_sun_open(p, mode, nbio);
74 	}
75 	DPRINTF("sio_open: %s: unknown device type\n", str);
76 	return NULL;
77 }
78 
79 void
80 sio_create(struct sio_hdl *hdl, struct sio_ops *ops,
81     unsigned int mode, int nbio)
82 {
83 	hdl->ops = ops;
84 	hdl->mode = mode;
85 	hdl->nbio = nbio;
86 	hdl->started = 0;
87 	hdl->eof = 0;
88 	hdl->move_cb = NULL;
89 	hdl->vol_cb = NULL;
90 }
91 
92 void
93 sio_close(struct sio_hdl *hdl)
94 {
95 	hdl->ops->close(hdl);
96 }
97 
98 int
99 sio_start(struct sio_hdl *hdl)
100 {
101 #ifdef DEBUG
102 	struct timespec ts;
103 #endif
104 
105 	if (hdl->eof) {
106 		DPRINTF("sio_start: eof\n");
107 		return 0;
108 	}
109 	if (hdl->started) {
110 		DPRINTF("sio_start: already started\n");
111 		hdl->eof = 1;
112 		return 0;
113 	}
114 #ifdef DEBUG
115 	if (!sio_getpar(hdl, &hdl->par))
116 		return 0;
117 	hdl->pollcnt = hdl->wcnt = hdl->rcnt = hdl->cpos = 0;
118 	clock_gettime(CLOCK_MONOTONIC, &ts);
119 	hdl->start_nsec = 1000000000LL * ts.tv_sec + ts.tv_nsec;
120 #endif
121 	hdl->rdrop = hdl->wsil = 0;
122 	if (!hdl->ops->start(hdl))
123 		return 0;
124 	hdl->started = 1;
125 	return 1;
126 }
127 
128 int
129 sio_stop(struct sio_hdl *hdl)
130 {
131 	if (hdl->eof) {
132 		DPRINTF("sio_stop: eof\n");
133 		return 0;
134 	}
135 	if (!hdl->started) {
136 		DPRINTF("sio_stop: not started\n");
137 		hdl->eof = 1;
138 		return 0;
139 	}
140 	if (!hdl->ops->stop(hdl))
141 		return 0;
142 #ifdef DEBUG
143 	DPRINTF("libsndio: polls: %llu, written = %llu, read: %llu\n",
144 	    hdl->pollcnt, hdl->wcnt, hdl->rcnt);
145 #endif
146 	hdl->started = 0;
147 	return 1;
148 }
149 
150 int
151 sio_setpar(struct sio_hdl *hdl, struct sio_par *par)
152 {
153 	if (hdl->eof) {
154 		DPRINTF("sio_setpar: eof\n");
155 		return 0;
156 	}
157 	if (par->__magic != SIO_PAR_MAGIC) {
158 		DPRINTF("sio_setpar: use of uninitialized sio_par structure\n");
159 		hdl->eof = 1;
160 		return 0;
161 	}
162 	if (hdl->started) {
163 		DPRINTF("sio_setpar: already started\n");
164 		hdl->eof = 1;
165 		return 0;
166 	}
167 	if (par->bufsz != ~0U) {
168 		DPRINTF("sio_setpar: setting bufsz is deprecated\n");
169 		par->appbufsz = par->bufsz;
170 		par->bufsz = ~0U;
171 	}
172 	if (par->rate != ~0U && par->appbufsz == ~0U)
173 		par->appbufsz = par->rate * 200 / 1000;
174 	return hdl->ops->setpar(hdl, par);
175 }
176 
177 int
178 sio_getpar(struct sio_hdl *hdl, struct sio_par *par)
179 {
180 	if (hdl->eof) {
181 		DPRINTF("sio_getpar: eof\n");
182 		return 0;
183 	}
184 	if (hdl->started) {
185 		DPRINTF("sio_getpar: already started\n");
186 		hdl->eof = 1;
187 		return 0;
188 	}
189 	if (!hdl->ops->getpar(hdl, par)) {
190 		par->__magic = 0;
191 		return 0;
192 	}
193 	par->__magic = 0;
194 	return 1;
195 }
196 
197 int
198 sio_getcap(struct sio_hdl *hdl, struct sio_cap *cap)
199 {
200 	if (hdl->eof) {
201 		DPRINTF("sio_getcap: eof\n");
202 		return 0;
203 	}
204 	if (hdl->started) {
205 		DPRINTF("sio_getcap: already started\n");
206 		hdl->eof = 1;
207 		return 0;
208 	}
209 	return hdl->ops->getcap(hdl, cap);
210 }
211 
212 static int
213 sio_psleep(struct sio_hdl *hdl, int event)
214 {
215 	struct pollfd pfd[SIO_MAXNFDS];
216 	int revents;
217 	int nfds;
218 
219 	nfds = sio_nfds(hdl);
220 	if (nfds > SIO_MAXNFDS) {
221 		DPRINTF("sio_psleep: %d: too many descriptors\n", nfds);
222 		hdl->eof = 1;
223 		return 0;
224 	}
225 	for (;;) {
226 		nfds = sio_pollfd(hdl, pfd, event);
227 		while (poll(pfd, nfds, -1) < 0) {
228 			if (errno == EINTR)
229 				continue;
230 			DPERROR("sio_psleep: poll");
231 			hdl->eof = 1;
232 			return 0;
233 		}
234 		revents = sio_revents(hdl, pfd);
235 		if (revents & POLLHUP) {
236 			DPRINTF("sio_psleep: hang-up\n");
237 			return 0;
238 		}
239 		if (revents & event)
240 			break;
241 	}
242 	return 1;
243 }
244 
245 static int
246 sio_rdrop(struct sio_hdl *hdl)
247 {
248 #define DROP_NMAX 0x1000
249 	static char dummy[DROP_NMAX];
250 	ssize_t n, todo;
251 
252 	while (hdl->rdrop > 0) {
253 		todo = hdl->rdrop;
254 		if (todo > DROP_NMAX)
255 			todo = DROP_NMAX;
256 		n = hdl->ops->read(hdl, dummy, todo);
257 		if (n == 0)
258 			return 0;
259 		hdl->rdrop -= n;
260 		DPRINTF("sio_rdrop: dropped %zu bytes\n", n);
261 	}
262 	return 1;
263 }
264 
265 static int
266 sio_wsil(struct sio_hdl *hdl)
267 {
268 #define ZERO_NMAX 0x1000
269 	static char zero[ZERO_NMAX];
270 	ssize_t n, todo;
271 
272 	while (hdl->wsil > 0) {
273 		todo = hdl->wsil;
274 		if (todo > ZERO_NMAX)
275 			todo = ZERO_NMAX;
276 		n = hdl->ops->write(hdl, zero, todo);
277 		if (n == 0)
278 			return 0;
279 		hdl->wsil -= n;
280 		DPRINTF("sio_wsil: inserted %zu bytes\n", n);
281 	}
282 	return 1;
283 }
284 
285 size_t
286 sio_read(struct sio_hdl *hdl, void *buf, size_t len)
287 {
288 	unsigned int n;
289 	char *data = buf;
290 	size_t todo = len;
291 
292 	if (hdl->eof) {
293 		DPRINTF("sio_read: eof\n");
294 		return 0;
295 	}
296 	if (!hdl->started || !(hdl->mode & SIO_REC)) {
297 		DPRINTF("sio_read: recording not started\n");
298 		hdl->eof = 1;
299 		return 0;
300 	}
301 	if (todo == 0) {
302 		DPRINTF("sio_read: zero length read ignored\n");
303 		return 0;
304 	}
305 	if (!sio_rdrop(hdl))
306 		return 0;
307 	while (todo > 0) {
308 		n = hdl->ops->read(hdl, data, todo);
309 		if (n == 0) {
310 			if (hdl->nbio || hdl->eof || todo < len)
311 				break;
312 			if (!sio_psleep(hdl, POLLIN))
313 				break;
314 			continue;
315 		}
316 		data += n;
317 		todo -= n;
318 #ifdef DEBUG
319 		hdl->rcnt += n;
320 #endif
321 	}
322 	return len - todo;
323 }
324 
325 size_t
326 sio_write(struct sio_hdl *hdl, const void *buf, size_t len)
327 {
328 	unsigned int n;
329 	const unsigned char *data = buf;
330 	size_t todo = len;
331 
332 	if (hdl->eof) {
333 		DPRINTF("sio_write: eof\n");
334 		return 0;
335 	}
336 	if (!hdl->started || !(hdl->mode & SIO_PLAY)) {
337 		DPRINTF("sio_write: playback not started\n");
338 		hdl->eof = 1;
339 		return 0;
340 	}
341 	if (todo == 0) {
342 		DPRINTF("sio_write: zero length write ignored\n");
343 		return 0;
344 	}
345 	if (!sio_wsil(hdl))
346 		return 0;
347 	while (todo > 0) {
348 		n = hdl->ops->write(hdl, data, todo);
349 		if (n == 0) {
350 			if (hdl->nbio || hdl->eof)
351 				break;
352 			if (!sio_psleep(hdl, POLLOUT))
353 				break;
354 			continue;
355 		}
356 		data += n;
357 		todo -= n;
358 #ifdef DEBUG
359 		hdl->wcnt += n;
360 #endif
361 	}
362 	return len - todo;
363 }
364 
365 int
366 sio_nfds(struct sio_hdl *hdl)
367 {
368 	return hdl->ops->nfds(hdl);
369 }
370 
371 int
372 sio_pollfd(struct sio_hdl *hdl, struct pollfd *pfd, int events)
373 {
374 	if (hdl->eof)
375 		return 0;
376 	if (!hdl->started)
377 		events = 0;
378 	return hdl->ops->pollfd(hdl, pfd, events);
379 }
380 
381 int
382 sio_revents(struct sio_hdl *hdl, struct pollfd *pfd)
383 {
384 	int revents;
385 #ifdef DEBUG
386 	struct timespec ts0, ts1;
387 
388 	if (sndio_debug >= 2)
389 		clock_gettime(CLOCK_MONOTONIC, &ts0);
390 #endif
391 	if (hdl->eof)
392 		return POLLHUP;
393 #ifdef DEBUG
394 	hdl->pollcnt++;
395 #endif
396 	revents = hdl->ops->revents(hdl, pfd);
397 	if (!hdl->started)
398 		return revents & POLLHUP;
399 #ifdef DEBUG
400 	if (sndio_debug >= 3) {
401 		clock_gettime(CLOCK_MONOTONIC, &ts1);
402 		DPRINTF("%09lld: sio_revents: revents = 0x%x, took %lldns\n",
403 		    1000000000LL * ts0.tv_sec +
404 		    ts0.tv_nsec - hdl->start_nsec,
405 		    revents,
406 		    1000000000LL * (ts1.tv_sec - ts0.tv_sec) +
407 		    ts1.tv_nsec - ts0.tv_nsec);
408 	}
409 #endif
410 	if ((hdl->mode & SIO_PLAY) && !sio_wsil(hdl))
411 		revents &= ~POLLOUT;
412 	if ((hdl->mode & SIO_REC) && !sio_rdrop(hdl))
413 		revents &= ~POLLIN;
414 	return revents;
415 }
416 
417 int
418 sio_eof(struct sio_hdl *hdl)
419 {
420 	return hdl->eof;
421 }
422 
423 void
424 sio_onmove(struct sio_hdl *hdl, void (*cb)(void *, int), void *addr)
425 {
426 	if (hdl->started) {
427 		DPRINTF("sio_onmove: already started\n");
428 		hdl->eof = 1;
429 		return;
430 	}
431 	hdl->move_cb = cb;
432 	hdl->move_addr = addr;
433 }
434 
435 #ifdef DEBUG
436 void
437 sio_printpos(struct sio_hdl *hdl)
438 {
439 	struct timespec ts;
440 	long long rpos, rdiff;
441 	long long cpos, cdiff;
442 	long long wpos, wdiff;
443 
444 	clock_gettime(CLOCK_MONOTONIC, &ts);
445 
446 	rpos = (hdl->mode & SIO_REC) ?
447 	    hdl->rcnt / (hdl->par.bps * hdl->par.rchan) : 0;
448 	wpos = (hdl->mode & SIO_PLAY) ?
449 	    hdl->wcnt / (hdl->par.bps * hdl->par.pchan) : 0;
450 
451 	cdiff = hdl->cpos % hdl->par.round;
452 	cpos  = hdl->cpos / hdl->par.round;
453 	if (cdiff > hdl->par.round / 2) {
454 		cpos++;
455 		cdiff = cdiff - hdl->par.round;
456 	}
457 	rdiff = rpos % hdl->par.round;
458 	rpos  = rpos / hdl->par.round;
459 	if (rdiff > hdl->par.round / 2) {
460 		rpos++;
461 		rdiff = rdiff - hdl->par.round;
462 	}
463 	wdiff = wpos % hdl->par.round;
464 	wpos  = wpos / hdl->par.round;
465 	if (wdiff > hdl->par.round / 2) {
466 		wpos++;
467 		wdiff = wdiff - hdl->par.round;
468 	}
469 	DPRINTF("%011lld: "
470 	    "clk %+5lld%+5lld, wr %+5lld%+5lld rd: %+5lld%+5lld\n",
471 	    1000000000LL * ts.tv_sec + ts.tv_nsec - hdl->start_nsec,
472 	    cpos, cdiff, wpos, wdiff, rpos, rdiff);
473 }
474 #endif
475 
476 void
477 sio_onmove_cb(struct sio_hdl *hdl, int delta)
478 {
479 #ifdef DEBUG
480 	hdl->cpos += delta;
481 	if (sndio_debug >= 2)
482 		sio_printpos(hdl);
483 #endif
484 	if (hdl->move_cb)
485 		hdl->move_cb(hdl->move_addr, delta);
486 }
487 
488 int
489 sio_setvol(struct sio_hdl *hdl, unsigned int ctl)
490 {
491 	if (hdl->eof)
492 		return 0;
493 	if (!hdl->ops->setvol)
494 		return 1;
495 	if (!hdl->ops->setvol(hdl, ctl))
496 		return 0;
497 	hdl->ops->getvol(hdl);
498 	return 1;
499 }
500 
501 int
502 sio_onvol(struct sio_hdl *hdl, void (*cb)(void *, unsigned int), void *addr)
503 {
504 	if (hdl->started) {
505 		DPRINTF("sio_onvol: already started\n");
506 		hdl->eof = 1;
507 		return 0;
508 	}
509 	if (!hdl->ops->setvol)
510 		return 0;
511 	hdl->vol_cb = cb;
512 	hdl->vol_addr = addr;
513 	hdl->ops->getvol(hdl);
514 	return 1;
515 }
516 
517 void
518 sio_onvol_cb(struct sio_hdl *hdl, unsigned int ctl)
519 {
520 	if (hdl->vol_cb)
521 		hdl->vol_cb(hdl->vol_addr, ctl);
522 }
523