xref: /openbsd-src/lib/libsndio/sio_sun.c (revision 9d7d964fbfd7c6870149c32eb6bcc810e77b6fdf)
1 /*	$OpenBSD: sio_sun.c,v 1.16 2015/07/28 20:48:49 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/types.h>
19 #include <sys/ioctl.h>
20 #include <sys/audioio.h>
21 #include <sys/stat.h>
22 
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <limits.h>
26 #include <poll.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 
32 #include "debug.h"
33 #include "sio_priv.h"
34 
35 struct sio_sun_hdl {
36 	struct sio_hdl sio;
37 	int fd;
38 	int filling;
39 	unsigned int ibpf, obpf;	/* bytes per frame */
40 	unsigned int ibytes, obytes;	/* bytes the hw transferred */
41 	unsigned int ierr, oerr;	/* frames the hw dropped */
42 	int idelta, odelta;		/* position reported to client */
43 };
44 
45 static void sio_sun_close(struct sio_hdl *);
46 static int sio_sun_start(struct sio_hdl *);
47 static int sio_sun_stop(struct sio_hdl *);
48 static int sio_sun_setpar(struct sio_hdl *, struct sio_par *);
49 static int sio_sun_getpar(struct sio_hdl *, struct sio_par *);
50 static int sio_sun_getcap(struct sio_hdl *, struct sio_cap *);
51 static size_t sio_sun_read(struct sio_hdl *, void *, size_t);
52 static size_t sio_sun_write(struct sio_hdl *, const void *, size_t);
53 static int sio_sun_nfds(struct sio_hdl *);
54 static int sio_sun_pollfd(struct sio_hdl *, struct pollfd *, int);
55 static int sio_sun_revents(struct sio_hdl *, struct pollfd *);
56 
57 static struct sio_ops sio_sun_ops = {
58 	sio_sun_close,
59 	sio_sun_setpar,
60 	sio_sun_getpar,
61 	sio_sun_getcap,
62 	sio_sun_write,
63 	sio_sun_read,
64 	sio_sun_start,
65 	sio_sun_stop,
66 	sio_sun_nfds,
67 	sio_sun_pollfd,
68 	sio_sun_revents,
69 	NULL, /* setvol */
70 	NULL, /* getvol */
71 };
72 
73 /*
74  * convert sun encoding to sio_par encoding
75  */
76 static int
77 sio_sun_infotoenc(struct sio_sun_hdl *hdl, struct audio_prinfo *ai,
78     struct sio_par *par)
79 {
80 	par->msb = ai->msb;
81 	par->bits = ai->precision;
82 	par->bps = ai->bps;
83 	switch (ai->encoding) {
84 	case AUDIO_ENCODING_SLINEAR_LE:
85 		par->le = 1;
86 		par->sig = 1;
87 		break;
88 	case AUDIO_ENCODING_SLINEAR_BE:
89 		par->le = 0;
90 		par->sig = 1;
91 		break;
92 	case AUDIO_ENCODING_ULINEAR_LE:
93 		par->le = 1;
94 		par->sig = 0;
95 		break;
96 	case AUDIO_ENCODING_ULINEAR_BE:
97 		par->le = 0;
98 		par->sig = 0;
99 		break;
100 	case AUDIO_ENCODING_SLINEAR:
101 		par->le = SIO_LE_NATIVE;
102 		par->sig = 1;
103 		break;
104 	case AUDIO_ENCODING_ULINEAR:
105 		par->le = SIO_LE_NATIVE;
106 		par->sig = 0;
107 		break;
108 	default:
109 		DPRINTF("sio_sun_infotoenc: unsupported encoding\n");
110 		hdl->sio.eof = 1;
111 		return 0;
112 	}
113 	return 1;
114 }
115 
116 /*
117  * convert sio_par encoding to sun encoding
118  */
119 static void
120 sio_sun_enctoinfo(struct sio_sun_hdl *hdl,
121     unsigned int *renc, struct sio_par *par)
122 {
123 	if (par->le == ~0U && par->sig == ~0U) {
124 		*renc = ~0U;
125 	} else if (par->le == ~0U || par->sig == ~0U) {
126 		*renc = AUDIO_ENCODING_SLINEAR;
127 	} else if (par->le && par->sig) {
128 		*renc = AUDIO_ENCODING_SLINEAR_LE;
129 	} else if (!par->le && par->sig) {
130 		*renc = AUDIO_ENCODING_SLINEAR_BE;
131 	} else if (par->le && !par->sig) {
132 		*renc = AUDIO_ENCODING_ULINEAR_LE;
133 	} else {
134 		*renc = AUDIO_ENCODING_ULINEAR_BE;
135 	}
136 }
137 
138 /*
139  * try to set the device to the given parameters and check that the
140  * device can use them; return 1 on success, 0 on failure or error
141  */
142 static int
143 sio_sun_tryinfo(struct sio_sun_hdl *hdl, struct sio_enc *enc,
144     unsigned int pchan, unsigned int rchan, unsigned int rate)
145 {
146 	struct audio_info aui;
147 	struct audio_prinfo *pr;
148 
149 	pr = (hdl->sio.mode & SIO_PLAY) ? &aui.play : &aui.record;
150 
151 	AUDIO_INITINFO(&aui);
152 	if (enc) {
153 		if (enc->le && enc->sig) {
154 			pr->encoding = AUDIO_ENCODING_SLINEAR_LE;
155 		} else if (!enc->le && enc->sig) {
156 			pr->encoding = AUDIO_ENCODING_SLINEAR_BE;
157 		} else if (enc->le && !enc->sig) {
158 			pr->encoding = AUDIO_ENCODING_ULINEAR_LE;
159 		} else {
160 			pr->encoding = AUDIO_ENCODING_ULINEAR_BE;
161 		}
162 		pr->precision = enc->bits;
163 	}
164 	if (rate)
165 		pr->sample_rate = rate;
166 	if ((hdl->sio.mode & (SIO_PLAY | SIO_REC)) == (SIO_PLAY | SIO_REC))
167 		aui.record = aui.play;
168 	if (pchan && (hdl->sio.mode & SIO_PLAY))
169 		aui.play.channels = pchan;
170 	if (rchan && (hdl->sio.mode & SIO_REC))
171 		aui.record.channels = rchan;
172 	if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
173 		if (errno == EINVAL)
174 			return 0;
175 		DPERROR("sio_sun_tryinfo: setinfo");
176 		hdl->sio.eof = 1;
177 		return 0;
178 	}
179 	if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
180 		DPERROR("sio_sun_tryinfo: getinfo");
181 		hdl->sio.eof = 1;
182 		return 0;
183 	}
184 	if (pchan && aui.play.channels != pchan)
185 		return 0;
186 	if (rchan && aui.record.channels != rchan)
187 		return 0;
188 	if (rate) {
189 		if ((hdl->sio.mode & SIO_PLAY) &&
190 		    (aui.play.sample_rate != rate))
191 			return 0;
192 		if ((hdl->sio.mode & SIO_REC) &&
193 		    (aui.record.sample_rate != rate))
194 			return 0;
195 	}
196 	return 1;
197 }
198 
199 /*
200  * guess device capabilities
201  */
202 static int
203 sio_sun_getcap(struct sio_hdl *sh, struct sio_cap *cap)
204 {
205 #define NCHANS (sizeof(chans) / sizeof(chans[0]))
206 #define NRATES (sizeof(rates) / sizeof(rates[0]))
207 	static unsigned int chans[] = {
208 		1, 2, 4, 6, 8, 10, 12
209 	};
210 	static unsigned int rates[] = {
211 		8000, 11025, 12000, 16000, 22050, 24000,
212 		32000, 44100, 48000, 64000, 88200, 96000
213 	};
214 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
215 	struct sio_par savepar;
216 	struct audio_encoding ae;
217 	unsigned int nenc = 0, nconf = 0;
218 	unsigned int enc_map = 0, rchan_map = 0, pchan_map = 0, rate_map;
219 	unsigned int i, j, conf;
220 
221 	if (!sio_sun_getpar(&hdl->sio, &savepar))
222 		return 0;
223 
224 	/*
225 	 * fill encoding list
226 	 */
227 	for (ae.index = 0; nenc < SIO_NENC; ae.index++) {
228 		if (ioctl(hdl->fd, AUDIO_GETENC, &ae) < 0) {
229 			if (errno == EINVAL)
230 				break;
231 			DPERROR("sio_sun_getcap: getenc");
232 			hdl->sio.eof = 1;
233 			return 0;
234 		}
235 		if (ae.flags & AUDIO_ENCODINGFLAG_EMULATED)
236 			continue;
237 		if (ae.encoding == AUDIO_ENCODING_SLINEAR_LE) {
238 			cap->enc[nenc].le = 1;
239 			cap->enc[nenc].sig = 1;
240 		} else if (ae.encoding == AUDIO_ENCODING_SLINEAR_BE) {
241 			cap->enc[nenc].le = 0;
242 			cap->enc[nenc].sig = 1;
243 		} else if (ae.encoding == AUDIO_ENCODING_ULINEAR_LE) {
244 			cap->enc[nenc].le = 1;
245 			cap->enc[nenc].sig = 0;
246 		} else if (ae.encoding == AUDIO_ENCODING_ULINEAR_BE) {
247 			cap->enc[nenc].le = 0;
248 			cap->enc[nenc].sig = 0;
249 		} else if (ae.encoding == AUDIO_ENCODING_SLINEAR) {
250 			cap->enc[nenc].le = SIO_LE_NATIVE;
251 			cap->enc[nenc].sig = 1;
252 		} else if (ae.encoding == AUDIO_ENCODING_ULINEAR) {
253 			cap->enc[nenc].le = SIO_LE_NATIVE;
254 			cap->enc[nenc].sig = 0;
255 		} else {
256 			/* unsipported encoding */
257 			continue;
258 		}
259 		cap->enc[nenc].bits = ae.precision;
260 		cap->enc[nenc].bps = ae.bps;
261 		cap->enc[nenc].msb = ae.msb;
262 		enc_map |= (1 << nenc);
263 		nenc++;
264 	}
265 
266 	/*
267 	 * fill channels
268 	 *
269 	 * for now we're lucky: all kernel devices assume that the
270 	 * number of channels and the encoding are independent so we can
271 	 * use the current encoding and try various channels.
272 	 */
273 	if (hdl->sio.mode & SIO_PLAY) {
274 		memcpy(&cap->pchan, chans, NCHANS * sizeof(unsigned int));
275 		for (i = 0; i < NCHANS; i++) {
276 			if (sio_sun_tryinfo(hdl, NULL, chans[i], 0, 0))
277 				pchan_map |= (1 << i);
278 		}
279 	}
280 	if (hdl->sio.mode & SIO_REC) {
281 		memcpy(&cap->rchan, chans, NCHANS * sizeof(unsigned int));
282 		for (i = 0; i < NCHANS; i++) {
283 			if (sio_sun_tryinfo(hdl, NULL, 0, chans[i], 0))
284 				rchan_map |= (1 << i);
285 		}
286 	}
287 
288 	/*
289 	 * fill rates
290 	 *
291 	 * rates are not independent from other parameters (eg. on
292 	 * uaudio devices), so certain rates may not be allowed with
293 	 * certain encodings. We have to check rates for all encodings
294 	 */
295 	memcpy(&cap->rate, rates, NRATES * sizeof(unsigned int));
296 	for (j = 0; j < nenc; j++) {
297 		rate_map = 0;
298 		for (i = 0; i < NRATES; i++) {
299 			if (sio_sun_tryinfo(hdl, &cap->enc[j], 0, 0, rates[i]))
300 				rate_map |= (1 << i);
301 		}
302 		for (conf = 0; conf < nconf; conf++) {
303 			if (cap->confs[conf].rate == rate_map) {
304 				cap->confs[conf].enc |= (1 << j);
305 				break;
306 			}
307 		}
308 		if (conf == nconf) {
309 			if (nconf == SIO_NCONF)
310 				break;
311 			cap->confs[nconf].enc = (1 << j);
312 			cap->confs[nconf].pchan = pchan_map;
313 			cap->confs[nconf].rchan = rchan_map;
314 			cap->confs[nconf].rate = rate_map;
315 			nconf++;
316 		}
317 	}
318 	cap->nconf = nconf;
319 	if (!sio_sun_setpar(&hdl->sio, &savepar))
320 		return 0;
321 	return 1;
322 #undef NCHANS
323 #undef NRATES
324 }
325 
326 struct sio_hdl *
327 _sio_sun_open(const char *str, unsigned int mode, int nbio)
328 {
329 	int fd, flags, fullduplex;
330 	struct audio_info aui;
331 	struct sio_sun_hdl *hdl;
332 	struct sio_par par;
333 	char path[PATH_MAX];
334 
335 	switch (*str) {
336 	case '/':
337 		str++;
338 		break;
339 	default:
340 		DPRINTF("_sio_sun_open: %s: '/<devnum>' expected\n", str);
341 		return NULL;
342 	}
343 	hdl = malloc(sizeof(struct sio_sun_hdl));
344 	if (hdl == NULL)
345 		return NULL;
346 	_sio_create(&hdl->sio, &sio_sun_ops, mode, nbio);
347 
348 	snprintf(path, sizeof(path), "/dev/audio%s", str);
349 	if (mode == (SIO_PLAY | SIO_REC))
350 		flags = O_RDWR;
351 	else
352 		flags = (mode & SIO_PLAY) ? O_WRONLY : O_RDONLY;
353 
354 	while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) < 0) {
355 		if (errno == EINTR)
356 			continue;
357 		DPERROR(path);
358 		goto bad_free;
359 	}
360 
361 	/*
362 	 * pause the device
363 	 */
364 	AUDIO_INITINFO(&aui);
365 	if (mode & SIO_PLAY)
366 		aui.play.pause = 1;
367 	if (mode & SIO_REC)
368 		aui.record.pause = 1;
369 	if (ioctl(fd, AUDIO_SETINFO, &aui) < 0) {
370 		DPERROR("sio_open_sun: setinfo");
371 		goto bad_close;
372 	}
373 	/*
374 	 * If both play and record are requested then
375 	 * set full duplex mode.
376 	 */
377 	if (mode == (SIO_PLAY | SIO_REC)) {
378 		fullduplex = 1;
379 		if (ioctl(fd, AUDIO_SETFD, &fullduplex) < 0) {
380 			DPRINTF("sio_open_sun: %s: can't set full-duplex\n", path);
381 			goto bad_close;
382 		}
383 	}
384 	hdl->fd = fd;
385 
386 	/*
387 	 * Default parameters may not be compatible with libsndio (eg. mulaw
388 	 * encodings, different playback and recording parameters, etc...), so
389 	 * set parameters to a random value. If the requested parameters are
390 	 * not supported by the device, then sio_setpar() will pick supported
391 	 * ones.
392 	 */
393 	sio_initpar(&par);
394 	par.rate = 48000;
395 	par.le = SIO_LE_NATIVE;
396 	par.sig = 1;
397 	par.bits = 16;
398 	par.appbufsz = 1200;
399 	if (!sio_setpar(&hdl->sio, &par))
400 		goto bad_close;
401 	return (struct sio_hdl *)hdl;
402  bad_close:
403 	while (close(fd) < 0 && errno == EINTR)
404 		; /* retry */
405  bad_free:
406 	free(hdl);
407 	return NULL;
408 }
409 
410 static void
411 sio_sun_close(struct sio_hdl *sh)
412 {
413 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
414 
415 	while (close(hdl->fd) < 0 && errno == EINTR)
416 		; /* retry */
417 	free(hdl);
418 }
419 
420 static int
421 sio_sun_start(struct sio_hdl *sh)
422 {
423 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
424 	struct audio_info aui;
425 
426 	hdl->obpf = hdl->sio.par.pchan * hdl->sio.par.bps;
427 	hdl->ibpf = hdl->sio.par.rchan * hdl->sio.par.bps;
428 	hdl->ibytes = 0;
429 	hdl->obytes = 0;
430 	hdl->ierr = 0;
431 	hdl->oerr = 0;
432 	hdl->idelta = 0;
433 	hdl->odelta = 0;
434 
435 	if (hdl->sio.mode & SIO_PLAY) {
436 		/*
437 		 * keep the device paused and let sio_sun_write() trigger the
438 		 * start later, to avoid buffer underruns
439 		 */
440 		hdl->filling = 1;
441 	} else {
442 		/*
443 		 * no play buffers to fill, start now!
444 		 */
445 		AUDIO_INITINFO(&aui);
446 		if (hdl->sio.mode & SIO_REC)
447 			aui.record.pause = 0;
448 		if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
449 			DPERROR("sio_sun_start: setinfo");
450 			hdl->sio.eof = 1;
451 			return 0;
452 		}
453 		hdl->filling = 0;
454 		_sio_onmove_cb(&hdl->sio, 0);
455 	}
456 	return 1;
457 }
458 
459 static int
460 sio_sun_stop(struct sio_hdl *sh)
461 {
462 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
463 	struct audio_info aui;
464 	int mode;
465 
466 	if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
467 		DPERROR("sio_sun_stop: getinfo");
468 		hdl->sio.eof = 1;
469 		return 0;
470 	}
471 	mode = aui.mode;
472 
473 	/*
474 	 * there's no way to drain the device without blocking, so just
475 	 * stop it until the kernel driver get fixed
476 	 */
477 	AUDIO_INITINFO(&aui);
478 	aui.mode = 0;
479 	if (hdl->sio.mode & SIO_PLAY)
480 		aui.play.pause = 1;
481 	if (hdl->sio.mode & SIO_REC)
482 		aui.record.pause = 1;
483 	if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
484 		DPERROR("sio_sun_stop: setinfo1");
485 		hdl->sio.eof = 1;
486 		return 0;
487 	}
488 	AUDIO_INITINFO(&aui);
489 	aui.mode = mode;
490 	if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
491 		DPERROR("sio_sun_stop: setinfo2");
492 		hdl->sio.eof = 1;
493 		return 0;
494 	}
495 	return 1;
496 }
497 
498 static int
499 sio_sun_setpar(struct sio_hdl *sh, struct sio_par *par)
500 {
501 #define NRETRIES 8
502 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
503 	struct audio_info aui;
504 	unsigned int i, infr, ibpf, onfr, obpf;
505 	unsigned int bufsz, round;
506 	unsigned int rate, req_rate, prec, enc;
507 
508 	/*
509 	 * try to set parameters until the device accepts
510 	 * a common encoding and rate for play and record
511 	 */
512 	rate = par->rate;
513 	prec = par->bits;
514 	sio_sun_enctoinfo(hdl, &enc, par);
515 	for (i = 0;; i++) {
516 		if (i == NRETRIES) {
517 			DPRINTF("sio_sun_setpar: couldn't set parameters\n");
518 			hdl->sio.eof = 1;
519 			return 0;
520 		}
521 		AUDIO_INITINFO(&aui);
522 		if (hdl->sio.mode & SIO_PLAY) {
523 			aui.play.sample_rate = rate;
524 			aui.play.precision = prec;
525 			aui.play.encoding = enc;
526 			aui.play.channels = par->pchan;
527 		}
528 		if (hdl->sio.mode & SIO_REC) {
529 			aui.record.sample_rate = rate;
530 			aui.record.precision = prec;
531 			aui.record.encoding = enc;
532 			aui.record.channels = par->rchan;
533 		}
534 		DPRINTFN(2, "sio_sun_setpar: %i: trying pars = %u/%u/%u\n",
535 		    i, rate, prec, enc);
536 		if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0 && errno != EINVAL) {
537 			DPERROR("sio_sun_setpar: setinfo(pars)");
538 			hdl->sio.eof = 1;
539 			return 0;
540 		}
541 		if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
542 			DPERROR("sio_sun_setpar: getinfo(pars)");
543 			hdl->sio.eof = 1;
544 			return 0;
545 		}
546 		enc = (hdl->sio.mode & SIO_REC) ?
547 		    aui.record.encoding : aui.play.encoding;
548 		switch (enc) {
549 		case AUDIO_ENCODING_SLINEAR_LE:
550 		case AUDIO_ENCODING_SLINEAR_BE:
551 		case AUDIO_ENCODING_ULINEAR_LE:
552 		case AUDIO_ENCODING_ULINEAR_BE:
553 		case AUDIO_ENCODING_SLINEAR:
554 		case AUDIO_ENCODING_ULINEAR:
555 			break;
556 		default:
557 			DPRINTF("sio_sun_setpar: couldn't set linear encoding\n");
558 			hdl->sio.eof = 1;
559 			return 0;
560 		}
561 		if (hdl->sio.mode != (SIO_REC | SIO_PLAY))
562 			break;
563 		if (aui.play.sample_rate == aui.record.sample_rate &&
564 		    aui.play.precision == aui.record.precision &&
565 		    aui.play.encoding == aui.record.encoding)
566 			break;
567 		if (i < NRETRIES / 2) {
568 			rate = aui.play.sample_rate;
569 			prec = aui.play.precision;
570 			enc = aui.play.encoding;
571 		} else {
572 			rate = aui.record.sample_rate;
573 			prec = aui.record.precision;
574 			enc = aui.record.encoding;
575 		}
576 	}
577 
578 	/*
579 	 * If the rate that the hardware is using is different than
580 	 * the requested rate, scale buffer sizes so they will be the
581 	 * same time duration as what was requested.  This just gets
582 	 * the rates to use for scaling, that actual scaling is done
583 	 * later.
584 	 */
585 	rate = (hdl->sio.mode & SIO_REC) ? aui.record.sample_rate :
586 	    aui.play.sample_rate;
587 	req_rate = rate;
588 	if (par->rate && par->rate != ~0U)
589 		req_rate = par->rate;
590 
591 	/*
592 	 * if block size and buffer size are not both set then
593 	 * set the blocksize to half the buffer size
594 	 */
595 	bufsz = par->appbufsz;
596 	round = par->round;
597 	if (bufsz != ~0U) {
598 		bufsz = bufsz * rate / req_rate;
599 		if (round == ~0U)
600 			round = (bufsz + 1) / 2;
601 		else
602 			round = round * rate / req_rate;
603 	} else if (round != ~0U) {
604 		round = round * rate / req_rate;
605 		bufsz = round * 2;
606 	} else
607 		return 1;
608 
609 	/*
610 	 * get the play/record frame size in bytes
611 	 */
612 	if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
613 		DPERROR("sio_sun_setpar: GETINFO");
614 		hdl->sio.eof = 1;
615 		return 0;
616 	}
617 	ibpf = (hdl->sio.mode & SIO_REC) ?
618 	    aui.record.channels * aui.record.bps : 1;
619 	obpf = (hdl->sio.mode & SIO_PLAY) ?
620 	    aui.play.channels * aui.play.bps : 1;
621 
622 	DPRINTFN(2, "sio_sun_setpar: bpf = (%u, %u)\n", ibpf, obpf);
623 
624 	/*
625 	 * try to set parameters until the device accepts
626 	 * a common block size for play and record
627 	 */
628 	for (i = 0; i < NRETRIES; i++) {
629 		AUDIO_INITINFO(&aui);
630 		aui.hiwat = (bufsz + round - 1) / round;
631 		aui.lowat = aui.hiwat;
632 		if (hdl->sio.mode & SIO_REC)
633 			aui.record.block_size = round * ibpf;
634 		if (hdl->sio.mode & SIO_PLAY)
635 			aui.play.block_size = round * obpf;
636 		if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
637 			DPERROR("sio_sun_setpar2: SETINFO");
638 			hdl->sio.eof = 1;
639 			return 0;
640 		}
641 		if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
642 			DPERROR("sio_sun_setpar2: GETINFO");
643 			hdl->sio.eof = 1;
644 			return 0;
645 		}
646 		infr = aui.record.block_size / ibpf;
647 		onfr = aui.play.block_size / obpf;
648 		DPRINTFN(2, "sio_sun_setpar: %i: trying round = %u -> (%u, %u)\n",
649 		    i, round, infr, onfr);
650 
651 		/*
652 		 * if half-duplex or both block sizes match, we're done
653 		 */
654 		if (hdl->sio.mode != (SIO_REC | SIO_PLAY) || infr == onfr) {
655 			DPRINTFN(2, "sio_sun_setpar: blocksize ok\n");
656 			return 1;
657 		}
658 
659 		/*
660 		 * half of the retries, retry with the smaller value,
661 		 * then with the larger returned value
662 		 */
663 		if (i < NRETRIES / 2)
664 			round = infr < onfr ? infr : onfr;
665 		else
666 			round = infr < onfr ? onfr : infr;
667 	}
668 	DPRINTFN(2, "sio_sun_setpar: couldn't find a working blocksize\n");
669 	hdl->sio.eof = 1;
670 	return 0;
671 #undef NRETRIES
672 }
673 
674 static int
675 sio_sun_getpar(struct sio_hdl *sh, struct sio_par *par)
676 {
677 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
678 	struct audio_info aui;
679 
680 	if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
681 		DPERROR("sio_sun_getpar: getinfo");
682 		hdl->sio.eof = 1;
683 		return 0;
684 	}
685 	if (hdl->sio.mode & SIO_PLAY) {
686 		par->rate = aui.play.sample_rate;
687 		if (!sio_sun_infotoenc(hdl, &aui.play, par))
688 			return 0;
689 	} else if (hdl->sio.mode & SIO_REC) {
690 		par->rate = aui.record.sample_rate;
691 		if (!sio_sun_infotoenc(hdl, &aui.record, par))
692 			return 0;
693 	} else
694 		return 0;
695 	par->pchan = (hdl->sio.mode & SIO_PLAY) ?
696 	    aui.play.channels : 0;
697 	par->rchan = (hdl->sio.mode & SIO_REC) ?
698 	    aui.record.channels : 0;
699 	par->round = (hdl->sio.mode & SIO_REC) ?
700 	    aui.record.block_size / (par->bps * par->rchan) :
701 	    aui.play.block_size / (par->bps * par->pchan);
702 	par->appbufsz = aui.hiwat * par->round;
703 	par->bufsz = par->appbufsz;
704 	par->xrun = SIO_IGNORE;
705 	return 1;
706 }
707 
708 static size_t
709 sio_sun_read(struct sio_hdl *sh, void *buf, size_t len)
710 {
711 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
712 	ssize_t n;
713 
714 	while ((n = read(hdl->fd, buf, len)) < 0) {
715 		if (errno == EINTR)
716 			continue;
717 		if (errno != EAGAIN) {
718 			DPERROR("sio_sun_read: read");
719 			hdl->sio.eof = 1;
720 		}
721 		return 0;
722 	}
723 	if (n == 0) {
724 		DPRINTF("sio_sun_read: eof\n");
725 		hdl->sio.eof = 1;
726 		return 0;
727 	}
728 	return n;
729 }
730 
731 static size_t
732 sio_sun_autostart(struct sio_sun_hdl *hdl)
733 {
734 	struct audio_info aui;
735 	struct pollfd pfd;
736 
737 	pfd.fd = hdl->fd;
738 	pfd.events = POLLOUT;
739 	while (poll(&pfd, 1, 0) < 0) {
740 		if (errno == EINTR)
741 			continue;
742 		DPERROR("sio_sun_autostart: poll");
743 		hdl->sio.eof = 1;
744 		return 0;
745 	}
746 	if (!(pfd.revents & POLLOUT)) {
747 		hdl->filling = 0;
748 		AUDIO_INITINFO(&aui);
749 		if (hdl->sio.mode & SIO_PLAY)
750 			aui.play.pause = 0;
751 		if (hdl->sio.mode & SIO_REC)
752 			aui.record.pause = 0;
753 		if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
754 			DPERROR("sio_sun_autostart: setinfo");
755 			hdl->sio.eof = 1;
756 			return 0;
757 		}
758 		_sio_onmove_cb(&hdl->sio, 0);
759 	}
760 	return 1;
761 }
762 
763 static size_t
764 sio_sun_write(struct sio_hdl *sh, const void *buf, size_t len)
765 {
766 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
767 	const unsigned char *data = buf;
768 	ssize_t n, todo;
769 
770 	todo = len;
771 	while ((n = write(hdl->fd, data, todo)) < 0) {
772 		if (errno == EINTR)
773 			continue;
774 		if (errno != EAGAIN) {
775 			DPERROR("sio_sun_write: write");
776 			hdl->sio.eof = 1;
777 		}
778  		return 0;
779 	}
780 	if (hdl->filling) {
781 		if (!sio_sun_autostart(hdl))
782 			return 0;
783 	}
784 	return n;
785 }
786 
787 static int
788 sio_sun_nfds(struct sio_hdl *hdl)
789 {
790 	return 1;
791 }
792 
793 static int
794 sio_sun_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events)
795 {
796 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
797 
798 	pfd->fd = hdl->fd;
799 	pfd->events = events;
800 	return 1;
801 }
802 
803 int
804 sio_sun_revents(struct sio_hdl *sh, struct pollfd *pfd)
805 {
806 	struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
807 	struct audio_pos ap;
808 	int dierr = 0, doerr = 0, offset, delta;
809 	int revents = pfd->revents;
810 
811 	if (!hdl->sio.started)
812 		return pfd->revents;
813 	if (ioctl(hdl->fd, AUDIO_GETPOS, &ap) < 0) {
814 		DPERROR("sio_sun_revents: GETPOS");
815 		hdl->sio.eof = 1;
816 		return POLLHUP;
817 	}
818 	if (hdl->sio.mode & SIO_PLAY) {
819 		delta = (ap.play_pos - hdl->obytes) / hdl->obpf;
820 		doerr = (ap.play_xrun - hdl->oerr) / hdl->obpf;
821 		hdl->obytes = ap.play_pos;
822 		hdl->oerr = ap.play_xrun;
823 		hdl->odelta += delta;
824 		if (!(hdl->sio.mode & SIO_REC)) {
825 			hdl->idelta += delta;
826 			dierr = doerr;
827 		}
828 		if (doerr > 0)
829 			DPRINTFN(2, "play xrun %d\n", doerr);
830 	}
831 	if (hdl->sio.mode & SIO_REC) {
832 		delta = (ap.rec_pos - hdl->ibytes) / hdl->ibpf;
833 		dierr = (ap.rec_xrun - hdl->ierr) / hdl->ibpf;
834 		hdl->ibytes = ap.rec_pos;
835 		hdl->ierr = ap.rec_xrun;
836 		hdl->idelta += delta;
837 		if (!(hdl->sio.mode & SIO_PLAY)) {
838 			hdl->odelta += delta;
839 			doerr = dierr;
840 		}
841 		if (dierr > 0)
842 			DPRINTFN(2, "rec xrun %d\n", dierr);
843 	}
844 
845 	/*
846 	 * GETPOS reports positions including xruns,
847 	 * so we have to substract to get the real position
848 	 */
849 	hdl->idelta -= dierr;
850 	hdl->odelta -= doerr;
851 
852 	offset = doerr - dierr;
853 	if (offset > 0) {
854 		hdl->sio.rdrop += offset * hdl->ibpf;
855 		hdl->idelta -= offset;
856 		DPRINTFN(2, "will drop %d and pause %d\n", offset, doerr);
857 	} else if (offset < 0) {
858 		hdl->sio.wsil += -offset * hdl->obpf;
859 		hdl->odelta -= -offset;
860 		DPRINTFN(2, "will insert %d and pause %d\n", -offset, dierr);
861 	}
862 
863 	delta = (hdl->idelta > hdl->odelta) ? hdl->idelta : hdl->odelta;
864 	if (delta > 0) {
865 		_sio_onmove_cb(&hdl->sio, delta);
866 		hdl->idelta -= delta;
867 		hdl->odelta -= delta;
868 	}
869 
870 	if (hdl->filling)
871 		revents |= POLLOUT; /* XXX: is this necessary ? */
872 	return revents;
873 }
874