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