1 /*
2  * ALSA <-> OSS PCM I/O plugin
3  *
4  * Copyright (c) 2005 by Takashi Iwai <tiwai@suse.de>
5  *
6  * This library is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20 
21 #include <stdio.h>
22 #include <sys/ioctl.h>
23 #include <alsa/asoundlib.h>
24 #include <alsa/pcm_external.h>
25 #ifdef __linux__
26 #include <linux/soundcard.h>
27 #else
28 #include <sys/soundcard.h>
29 #endif
30 
31 #define ARRAY_SIZE(x)	(sizeof(x) / sizeof(*(x)))
32 
33 #ifdef __FreeBSD__
34 #define FREEBSD_OSS_RATE_MIN	1
35 #define FREEBSD_OSS_RATE_MAX	384000
36 
37 #define FREEBSD_OSS_CHANNELS_MIN	1
38 #define FREEBSD_OSS_CHANNELS_MAX	8
39 
40 #define FREEBSD_OSS_BUFSZ_MAX	131072
41 #define FREEBSD_OSS_BLKCNT_MIN	2
42 #define FREEBSD_OSS_BLKSZ_MIN	16 /* (FREEBSD_OSS_CHANNELS_MAX * 4) */
43 
44 #define FREEBSD_OSS_BUFSZ_MIN	(FREEBSD_OSS_BLKCNT_MIN * FREEBSD_OSS_BLKSZ_MIN)
45 #define FREEBSD_OSS_BLKCNT_MAX	(FREEBSD_OSS_BUFSZ_MAX / FREEBSD_OSS_BUFSZ_MIN)
46 #define FREEBSD_OSS_BLKSZ_MAX	(FREEBSD_OSS_BUFSZ_MAX / FREEBSD_OSS_BLKCNT_MIN)
47 #endif
48 
49 typedef struct snd_pcm_oss {
50 	snd_pcm_ioplug_t io;
51 	char *device;
52 	int fd;
53 #ifdef __FreeBSD__
54 	int bufsz, ptr, ptr_align, last_bytes;
55 #else
56 	int fragment_set;
57 	int caps;
58 #endif
59 	int format;
60 #ifndef __FreeBSD__
61 	unsigned int period_shift;
62 	unsigned int periods;
63 #endif
64 	unsigned int frame_bytes;
65 } snd_pcm_oss_t;
66 
oss_write(snd_pcm_ioplug_t * io,const snd_pcm_channel_area_t * areas,snd_pcm_uframes_t offset,snd_pcm_uframes_t size)67 static snd_pcm_sframes_t oss_write(snd_pcm_ioplug_t *io,
68 				   const snd_pcm_channel_area_t *areas,
69 				   snd_pcm_uframes_t offset,
70 				   snd_pcm_uframes_t size)
71 {
72 	snd_pcm_oss_t *oss = io->private_data;
73 	const char *buf;
74 	ssize_t result;
75 
76 	/* we handle only an interleaved buffer */
77 	buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8;
78 	size *= oss->frame_bytes;
79 	result = write(oss->fd, buf, size);
80 #ifdef __FreeBSD__
81 	if (result == -1) {
82 		if (errno == EAGAIN)
83 			return 0;
84 		else
85 			return -errno;
86 	}
87 #else
88 	if (result <= 0) {
89 		if (result == -EAGAIN)
90 			return 0;
91 		else
92 			return result;
93 	}
94 #endif
95 	return result / oss->frame_bytes;
96 }
97 
oss_read(snd_pcm_ioplug_t * io,const snd_pcm_channel_area_t * areas,snd_pcm_uframes_t offset,snd_pcm_uframes_t size)98 static snd_pcm_sframes_t oss_read(snd_pcm_ioplug_t *io,
99 				  const snd_pcm_channel_area_t *areas,
100 				  snd_pcm_uframes_t offset,
101 				  snd_pcm_uframes_t size)
102 {
103 	snd_pcm_oss_t *oss = io->private_data;
104 	char *buf;
105 	ssize_t result;
106 
107 	/* we handle only an interleaved buffer */
108 	buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8;
109 	size *= oss->frame_bytes;
110 	result = read(oss->fd, buf, size);
111 #ifdef __FreeBSD__
112 	if (result == -1) {
113 		if (errno == EAGAIN)
114 			return 0;
115 		else
116 			return -errno;
117 	}
118 #else
119 	if (result <= 0) {
120 		if (result == -EAGAIN)
121 			return 0;
122 		else
123 			return result;
124 	}
125 #endif
126 	return result / oss->frame_bytes;
127 }
128 
oss_pointer(snd_pcm_ioplug_t * io)129 static snd_pcm_sframes_t oss_pointer(snd_pcm_ioplug_t *io)
130 {
131 #ifdef __FreeBSD__
132 	snd_pcm_oss_t *oss = io->private_data;
133 #ifdef FREEBSD_OSS_USE_IO_PTR
134 	struct count_info ci;
135 #endif
136 	audio_buf_info bi;
137 
138 	if (io->state != SND_PCM_STATE_RUNNING)
139 		return 0;
140 
141 	if (io->state == SND_PCM_STATE_XRUN)
142 		return -EPIPE;
143 
144 #ifdef FREEBSD_OSS_USE_IO_PTR
145 	if (ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
146 	    SNDCTL_DSP_GETOPTR : SNDCTL_DSP_GETIPTR, &ci) < 0)
147 		return -EINVAL;
148 
149 	if (ci.ptr == oss->last_bytes &&
150 	    ((ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
151 	    SNDCTL_DSP_GETOSPACE : SNDCTL_DSP_GETISPACE, &bi) < 0) ||
152 	    bi.bytes == oss->bufsz))
153 		return -EPIPE;
154 
155 	if (ci.ptr < oss->last_bytes)
156 		oss->ptr += oss->bufsz;
157 
158 	oss->ptr += ci.ptr;
159 	oss->ptr -= oss->last_bytes;
160 	oss->ptr %= oss->ptr_align;
161 
162 	oss->last_bytes = ci.ptr;
163 #else	/* !FREEBSD_OSS_USE_IO_PTR */
164 	if (ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
165 	    SNDCTL_DSP_GETOSPACE : SNDCTL_DSP_GETISPACE, &bi) < 0)
166 		return -EINVAL;
167 
168 	if (bi.bytes == oss->bufsz && bi.bytes == oss->last_bytes) {
169 #if 0
170 #ifdef SNDCTL_DSP_GETERROR
171 		audio_errinfo ei;
172 		if (ioctl(oss->fd, SNDCTL_DSP_GETERROR, &ei) < 0 ||
173 		    (io->stream == SND_PCM_STREAM_PLAYBACK &&
174 		    ei.play_underruns != 0) ||
175 		    (io->stream == SND_PCM_STREAM_CAPTURE &&
176 		    ei.rec_overruns != 0))
177 #endif
178 #endif
179 			return -EPIPE;
180 	}
181 
182 	if (bi.bytes > oss->last_bytes) {
183 		oss->ptr += bi.bytes - oss->last_bytes;
184 		oss->ptr %= oss->ptr_align;
185 	}
186 
187 	oss->last_bytes = bi.bytes;
188 #endif	/* FREEBSD_OSS_USE_IO_PTR */
189 
190 	return snd_pcm_bytes_to_frames(io->pcm, oss->ptr);
191 #else
192 	snd_pcm_oss_t *oss = io->private_data;
193 	struct count_info info;
194 	int ptr;
195 
196 	if (ioctl(oss->fd, io->stream == SND_PCM_STREAM_PLAYBACK ?
197 		  SNDCTL_DSP_GETOPTR : SNDCTL_DSP_GETIPTR, &info) < 0) {
198 		fprintf(stderr, "*** OSS: oss_pointer error\n");
199 		return 0;
200 	}
201 	ptr = snd_pcm_bytes_to_frames(io->pcm, info.ptr);
202 	return ptr;
203 #endif
204 }
205 
oss_start(snd_pcm_ioplug_t * io)206 static int oss_start(snd_pcm_ioplug_t *io)
207 {
208 	snd_pcm_oss_t *oss = io->private_data;
209 #ifdef __FreeBSD__
210 	audio_buf_info bi;
211 #ifdef FREEBSD_OSS_USE_IO_PTR
212 	struct count_info ci;
213 #endif
214 #endif
215 	int tmp = io->stream == SND_PCM_STREAM_PLAYBACK ?
216 		PCM_ENABLE_OUTPUT : PCM_ENABLE_INPUT;
217 
218 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
219 	fprintf(stderr, "%s()\n", __func__);
220 #endif
221 
222 	if (ioctl(oss->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0) {
223 		fprintf(stderr, "*** OSS: trigger failed\n");
224 #ifdef __FreeBSD__
225 		return -EINVAL;
226 #else
227 		if (io->stream == SND_PCM_STREAM_CAPTURE)
228 			/* fake read to trigger */
229 			read(oss->fd, &tmp, 0);
230 #endif
231 	}
232 
233 #ifdef __FreeBSD__
234 	if (ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
235 	    SNDCTL_DSP_GETOSPACE : SNDCTL_DSP_GETISPACE, &bi) < 0)
236 		return -EINVAL;
237 
238 	if (oss->bufsz != (bi.fragsize * bi.fragstotal)) {
239 		fprintf(stderr, "%s(): WARNING - bufsz changed! %d -> %d\n",
240 		    __func__, oss->bufsz, bi.fragsize * bi.fragstotal);
241 		oss->bufsz = bi.fragsize * bi.fragstotal;
242 	}
243 
244 #ifdef FREEBSD_OSS_USE_IO_PTR
245 	if (ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
246 	    SNDCTL_DSP_GETOPTR : SNDCTL_DSP_GETIPTR, &ci) < 0)
247 		return -EINVAL;
248 
249 	oss->last_bytes = ci.ptr;
250 #else
251 	oss->last_bytes = bi.bytes;
252 #endif
253 	oss->ptr = 0;
254 #endif
255 
256 	return 0;
257 }
258 
oss_stop(snd_pcm_ioplug_t * io)259 static int oss_stop(snd_pcm_ioplug_t *io)
260 {
261 	snd_pcm_oss_t *oss = io->private_data;
262 	int tmp = 0;
263 
264 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
265 	fprintf(stderr, "%s()\n", __func__);
266 #endif
267 
268 	ioctl(oss->fd, SNDCTL_DSP_SETTRIGGER, &tmp);
269 	return 0;
270 }
271 
oss_drain(snd_pcm_ioplug_t * io)272 static int oss_drain(snd_pcm_ioplug_t *io)
273 {
274 	snd_pcm_oss_t *oss = io->private_data;
275 
276 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
277 	fprintf(stderr, "%s()\n", __func__);
278 #endif
279 
280 	if (io->stream == SND_PCM_STREAM_PLAYBACK)
281 		ioctl(oss->fd, SNDCTL_DSP_SYNC, NULL);
282 	return 0;
283 }
284 
oss_delay(snd_pcm_ioplug_t * io,snd_pcm_sframes_t * delayp)285 static int oss_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp)
286 {
287 	snd_pcm_oss_t *oss = io->private_data;
288 	int tmp;
289 
290 	if (oss->fd < 0)
291 		return -EBADFD;
292 
293 	if (io->stream == SND_PCM_STREAM_PLAYBACK) {
294 		if (ioctl(oss->fd, SNDCTL_DSP_GETODELAY, &tmp) < 0 || tmp < 0)
295 			tmp = 0;
296 	} else {
297 		tmp = 0;
298 	}
299 	*delayp = snd_pcm_bytes_to_frames(io->pcm, tmp);
300 
301 	return (0);
302 }
303 
304 #ifndef __FreeBSD__
oss_prepare(snd_pcm_ioplug_t * io)305 static int oss_prepare(snd_pcm_ioplug_t *io)
306 {
307 	snd_pcm_oss_t *oss = io->private_data;
308 	int tmp;
309 
310 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
311 	fprintf(stderr, "%s()\n", __func__);
312 #endif
313 
314 	ioctl(oss->fd, SNDCTL_DSP_RESET, NULL);
315 
316 	tmp = io->channels;
317 	if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &tmp) < 0) {
318 		perror("SNDCTL_DSP_CHANNELS");
319 		return -EINVAL;
320 	}
321 	tmp = oss->format;
322 	if (ioctl(oss->fd, SNDCTL_DSP_SETFMT, &tmp) < 0) {
323 		perror("SNDCTL_DSP_SETFMT");
324 		return -EINVAL;
325 	}
326 	tmp = io->rate;
327 	if (ioctl(oss->fd, SNDCTL_DSP_SPEED, &tmp) < 0 ||
328 	    tmp > io->rate * 1.01 || tmp < io->rate * 0.99) {
329 		perror("SNDCTL_DSP_SPEED");
330 		return -EINVAL;
331 	}
332 	return 0;
333 }
334 #endif
335 
336 #ifdef __FreeBSD__
337 static const struct {
338 	int oss_format;
339 	snd_pcm_format_t alsa_format;
340 } oss_formats_tab[] = {
341 	{ AFMT_U8, SND_PCM_FORMAT_U8 },
342 	{ AFMT_S8, SND_PCM_FORMAT_S8 },
343 	{ AFMT_MU_LAW, SND_PCM_FORMAT_MU_LAW  },
344 	{  AFMT_A_LAW, SND_PCM_FORMAT_A_LAW   },
345 	{ AFMT_S16_LE, SND_PCM_FORMAT_S16_LE  },
346 	{ AFMT_S16_BE, SND_PCM_FORMAT_S16_BE  },
347 	{ AFMT_U16_LE, SND_PCM_FORMAT_U16_LE  },
348 	{ AFMT_U16_BE, SND_PCM_FORMAT_U16_BE  },
349 	{ AFMT_S24_LE, SND_PCM_FORMAT_S24_3LE },
350 	{ AFMT_S24_BE, SND_PCM_FORMAT_S24_3BE },
351 	{ AFMT_U24_LE, SND_PCM_FORMAT_U24_3LE },
352 	{ AFMT_U24_BE, SND_PCM_FORMAT_U24_3BE },
353 	{ AFMT_S32_LE, SND_PCM_FORMAT_S32_LE  },
354 	{ AFMT_S32_BE, SND_PCM_FORMAT_S32_BE  },
355 	{ AFMT_U32_LE, SND_PCM_FORMAT_U32_LE  },
356 	{ AFMT_U32_BE, SND_PCM_FORMAT_U32_BE  },
357 	/* Special */
358 	{ AFMT_S24_LE, SND_PCM_FORMAT_S20_3LE },
359 	{ AFMT_S24_BE, SND_PCM_FORMAT_S20_3BE },
360 	{ AFMT_U24_LE, SND_PCM_FORMAT_U20_3LE },
361 	{ AFMT_U24_BE, SND_PCM_FORMAT_U20_3BE },
362 	{ AFMT_S24_LE, SND_PCM_FORMAT_S18_3LE },
363 	{ AFMT_S24_BE, SND_PCM_FORMAT_S18_3BE },
364 	{ AFMT_U24_LE, SND_PCM_FORMAT_U18_3LE },
365 	{ AFMT_U24_BE, SND_PCM_FORMAT_U18_3BE },
366 	{ AFMT_S32_LE, SND_PCM_FORMAT_S24_LE  },
367 	{ AFMT_S32_BE, SND_PCM_FORMAT_S24_BE  },
368 	{ AFMT_U32_LE, SND_PCM_FORMAT_U24_LE  },
369 	{ AFMT_U32_BE, SND_PCM_FORMAT_U24_BE  },
370 };
371 #endif
372 
oss_hw_params(snd_pcm_ioplug_t * io,snd_pcm_hw_params_t * params ATTRIBUTE_UNUSED)373 static int oss_hw_params(snd_pcm_ioplug_t *io,
374 			 snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED)
375 {
376 	snd_pcm_oss_t *oss = io->private_data;
377 	int i, tmp, err;
378 #ifdef __FreeBSD__
379 	int blksz_shift, blkcnt;
380 	audio_buf_info bi;
381 #else
382 	unsigned int period_bytes;
383 #endif
384 	long oflags, flags;
385 
386 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
387 	fprintf(stderr, "%s()\n", __func__);
388 #endif
389 
390 	oss->frame_bytes = (snd_pcm_format_physical_width(io->format) * io->channels) / 8;
391 #ifdef __FreeBSD__
392 	oss->ptr_align = io->buffer_size * oss->frame_bytes;
393 
394 	oss->format = 0;
395 	for (i = 0; i < ARRAY_SIZE(oss_formats_tab); i++) {
396 		if (oss_formats_tab[i].alsa_format == io->format) {
397 			oss->format = oss_formats_tab[i].oss_format;
398 			break;
399 		}
400 	}
401 	if (oss->format == 0) {
402 #else
403 	switch (io->format) {
404 	case SND_PCM_FORMAT_U8:
405 		oss->format = AFMT_U8;
406 		break;
407 	case SND_PCM_FORMAT_S16_LE:
408 		oss->format = AFMT_S16_LE;
409 		break;
410 	case SND_PCM_FORMAT_S16_BE:
411 		oss->format = AFMT_S16_BE;
412 		break;
413 	default:
414 #endif
415 		fprintf(stderr, "*** OSS: unsupported format %s\n", snd_pcm_format_name(io->format));
416 		return -EINVAL;
417 	}
418 #ifdef __FreeBSD__
419 
420 	ioctl(oss->fd, SNDCTL_DSP_RESET);
421 
422 	/* use a 16ms HW buffer by default */
423 	tmp = ((16 * io->rate) / 1000) * oss->frame_bytes;
424 
425 	/* round up to nearest power of two */
426 	while (tmp & (tmp - 1))
427 		tmp += tmp & ~(tmp - 1);
428 
429 	/* get logarithmic value */
430 	for (blksz_shift = 0; blksz_shift < 24; blksz_shift++) {
431 		if (tmp == (1 << blksz_shift))
432 			break;
433 	}
434 
435 	tmp = io->buffer_size * oss->frame_bytes;
436 
437 	/* compute HW buffer big enough to hold SW buffer */
438 	for (blkcnt = FREEBSD_OSS_BLKCNT_MIN; blkcnt != FREEBSD_OSS_BLKCNT_MAX; blkcnt *= 2) {
439 		if ((blkcnt << blksz_shift) >= tmp)
440 			break;
441 	}
442 
443 	tmp = blksz_shift | (blkcnt << 16);
444 	if (ioctl(oss->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0) {
445 		perror("SNDCTL_DSP_SETFRAGMENTS");
446 		return -EINVAL;
447 	}
448 
449 	tmp = oss->format;
450 	if (ioctl(oss->fd, SNDCTL_DSP_SETFMT, &tmp) < 0 ||
451 	    tmp != oss->format) {
452 		perror("SNDCTL_DSP_SETFMT");
453 		return -EINVAL;
454 	}
455 
456 	tmp = io->channels;
457 	if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &tmp) < 0 ||
458 	    tmp != io->channels) {
459 		perror("SNDCTL_DSP_CHANNELS");
460 		return -EINVAL;
461 	}
462 
463 	tmp = io->rate;
464 	if (ioctl(oss->fd, SNDCTL_DSP_SPEED, &tmp) < 0 ||
465 	    tmp > io->rate * 1.01 || tmp < io->rate * 0.99) {
466 		perror("SNDCTL_DSP_SPEED");
467 		return -EINVAL;
468 	}
469 
470 	if (ioctl(oss->fd, (io->stream == SND_PCM_STREAM_PLAYBACK) ?
471 	    SNDCTL_DSP_GETOSPACE : SNDCTL_DSP_GETISPACE, &bi) < 0) {
472 		perror("SNDCTL_DSP_GET[I/O]SPACE");
473 		return -EINVAL;
474 	}
475 
476 	oss->bufsz = bi.fragsize * bi.fragstotal;
477 
478 #ifdef SNDCTL_DSP_LOW_WATER
479 	tmp = ((io->period_size * oss->frame_bytes) * 3) / 4;
480 	tmp -= tmp % oss->frame_bytes;
481 	if (tmp < oss->frame_bytes)
482 		tmp = oss->frame_bytes;
483 	if (tmp > bi.fragsize)
484 		tmp = bi.fragsize;
485 	if (ioctl(oss->fd, SNDCTL_DSP_LOW_WATER, &tmp) < 0)
486 		perror("SNDCTL_DSP_LOW_WATER");
487 #endif
488 
489 #ifdef FREEBSD_OSS_DEBUG_VERBOSE
490 	fprintf(stderr,
491 	    "\n\n[%lu -> %d] %lu ~ %d -> %d, %lu ~ %d -> %d [d:%ld lw:%d]\n\n",
492 	    io->buffer_size / io->period_size, bi.fragstotal,
493 	    io->buffer_size * oss->frame_bytes,
494 	    (1 << blksz_shift) * blkcnt, oss->bufsz,
495 	    io->period_size * oss->frame_bytes, 1 << blksz_shift,
496 	    bi.fragsize,
497 	    (long)(io->buffer_size * oss->frame_bytes) -
498 	    oss->bufsz, tmp);
499 #endif
500 #else
501 	period_bytes = io->period_size * oss->frame_bytes;
502 	oss->period_shift = 0;
503 	for (i = 31; i >= 4; i--) {
504 		if (period_bytes & (1U << i)) {
505 			oss->period_shift = i;
506 			break;
507 		}
508 	}
509 	if (! oss->period_shift) {
510 		fprintf(stderr, "*** OSS: invalid period size %d\n", (int)io->period_size);
511 		return -EINVAL;
512 	}
513 	oss->periods = io->buffer_size / io->period_size;
514 
515  _retry:
516 	tmp = oss->period_shift | (oss->periods << 16);
517 	if (ioctl(oss->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0) {
518 		if (! oss->fragment_set) {
519 			perror("SNDCTL_DSP_SETFRAGMENT");
520 			fprintf(stderr, "*** period shift = %d, periods = %d\n", oss->period_shift, oss->periods);
521 			return -EINVAL;
522 		}
523 		/* OSS has no proper way to reinitialize the fragments */
524 		/* try to reopen the device */
525 		close(oss->fd);
526 		oss->fd = open(oss->device, io->stream == SND_PCM_STREAM_PLAYBACK ?
527 			       O_WRONLY : O_RDONLY);
528 		if (oss->fd < 0) {
529 			err = -errno;
530 			SNDERR("Cannot reopen the device %s", oss->device);
531 			return err;
532 		}
533 		io->poll_fd = oss->fd;
534 		io->poll_events = io->stream == SND_PCM_STREAM_PLAYBACK ?
535 			POLLOUT : POLLIN;
536 		snd_pcm_ioplug_reinit_status(io);
537 		oss->fragment_set = 0;
538 		goto _retry;
539 	}
540 	oss->fragment_set = 1;
541 #endif
542 
543 	if ((flags = fcntl(oss->fd, F_GETFL)) < 0) {
544 		err = -errno;
545 		perror("F_GETFL");
546 	} else {
547 		oflags = flags;
548 		if (io->nonblock)
549 			flags |= O_NONBLOCK;
550 		else
551 			flags &= ~O_NONBLOCK;
552 		if (flags != oflags &&
553 		    fcntl(oss->fd, F_SETFL, flags) < 0) {
554 			err = -errno;
555 			perror("F_SETFL");
556 		}
557 	}
558 
559 	return 0;
560 }
561 
562 static int oss_hw_constraint(snd_pcm_oss_t *oss)
563 {
564 #ifdef __FreeBSD__
565 	snd_pcm_ioplug_t *io = &oss->io;
566 	static const snd_pcm_access_t access_list[] = {
567 		SND_PCM_ACCESS_RW_INTERLEAVED,
568 		SND_PCM_ACCESS_MMAP_INTERLEAVED
569 	};
570 #ifdef FREEBSD_OSS_BLKCNT_P2
571 	unsigned int period_list[30];
572 #endif
573 #ifdef FREEBSD_OSS_BUFSZ_P2
574 	unsigned int bufsz_list[30];
575 #endif
576 	unsigned int nformats;
577 	unsigned int format[ARRAY_SIZE(oss_formats_tab)];
578 #if 0
579 	unsigned int nchannels;
580 	unsigned int channel[FREEBSD_OSS_CHANNELS_MAX];
581 #endif
582 	int i, err, tmp;
583 
584 #ifdef FREEBSD_OSS_DEBUG_VERBOSE
585 	fprintf(stderr, "%s()\n", __func__);
586 #endif
587 
588 	/* check trigger */
589 	tmp = 0;
590 	if (ioctl(oss->fd, SNDCTL_DSP_GETCAPS, &tmp) >= 0) {
591 		if (!(tmp & DSP_CAP_TRIGGER))
592 			fprintf(stderr, "*** OSS: trigger is not supported!\n");
593 	}
594 
595 	/* access type - interleaved only */
596 	if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
597 						 ARRAY_SIZE(access_list), access_list)) < 0)
598 		return err;
599 
600 	/* supported formats. */
601 	tmp = 0;
602 	ioctl(oss->fd, SNDCTL_DSP_GETFMTS, &tmp);
603 	nformats = 0;
604 	for (i = 0; i < ARRAY_SIZE(oss_formats_tab); i++) {
605 		if (tmp & oss_formats_tab[i].oss_format)
606 			format[nformats++] = oss_formats_tab[i].alsa_format;
607 	}
608 	if (! nformats)
609 		format[nformats++] = SND_PCM_FORMAT_S16;
610 	if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
611 						 nformats, format)) < 0)
612 		return err;
613 
614 #if 0
615 	/* supported channels */
616 	nchannels = 0;
617 	for (i = 0; i < ARRAY_SIZE(channel); i++) {
618 		tmp = i + 1;
619 		if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &tmp) >= 0 &&
620 		    1 + i == tmp)
621 			channel[nchannels++] = tmp;
622 	}
623 	if (! nchannels) /* assume 2ch stereo */
624 		err = snd_pcm_ioplug_set_param_minmax(io,
625 		    SND_PCM_IOPLUG_HW_CHANNELS, 2, 2);
626 	else
627 		err = snd_pcm_ioplug_set_param_list(io,
628 		    SND_PCM_IOPLUG_HW_CHANNELS, nchannels, channel);
629 	if (err < 0)
630 		return err;
631 #endif
632 	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
633 	    FREEBSD_OSS_CHANNELS_MIN, FREEBSD_OSS_CHANNELS_MAX);
634 	if (err < 0)
635 		return err;
636 
637 	/* supported rates */
638 	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE,
639 	    FREEBSD_OSS_RATE_MIN, FREEBSD_OSS_RATE_MAX);
640 	if (err < 0)
641 		return err;
642 
643 	/*
644 	 *  Maximum buffer size on FreeBSD can go up to 131072 bytes without
645 	 *  strict ^2 alignment so that s24le in 3bytes packing can be fed
646 	 *  directly.
647 	 */
648 
649 #ifdef FREEBSD_OSS_BLKCNT_P2
650 	tmp = 0;
651 	for (i = 1; i < 31 && tmp < ARRAY_SIZE(period_list); i++) {
652 		if ((1 << i) > FREEBSD_OSS_BLKCNT_MAX)
653 			break;
654 		if ((1 << i) < FREEBSD_OSS_BLKCNT_MIN)
655 			continue;
656 		period_list[tmp++] = 1 << i;
657 	}
658 
659 	if (tmp > 0)
660 		err = snd_pcm_ioplug_set_param_list(io,
661 		    SND_PCM_IOPLUG_HW_PERIODS, tmp, period_list);
662 	else
663 #endif
664 	/* periods , not strictly ^2 but later on will be refined */
665 		err = snd_pcm_ioplug_set_param_minmax(io,
666 		    SND_PCM_IOPLUG_HW_PERIODS, FREEBSD_OSS_BLKCNT_MIN,
667 		    FREEBSD_OSS_BLKCNT_MAX);
668 	if (err < 0)
669 		return err;
670 
671 	/* period size , not strictly ^2 */
672 	err = snd_pcm_ioplug_set_param_minmax(io,
673 	    SND_PCM_IOPLUG_HW_PERIOD_BYTES, FREEBSD_OSS_BLKSZ_MIN,
674 	    FREEBSD_OSS_BLKSZ_MAX);
675 	if (err < 0)
676 		return err;
677 
678 #ifdef FREEBSD_OSS_BUFSZ_P2
679 	tmp = 0;
680 	for (i = 1; i < 31 && tmp < ARRAY_SIZE(bufsz_list); i++) {
681 		if ((1 << i) > FREEBSD_OSS_BUFSZ_MAX)
682 			break;
683 		if ((1 << i) < FREEBSD_OSS_BUFSZ_MIN)
684 			continue;
685 		bufsz_list[tmp++] = 1 << i;
686 	}
687 
688 	if (tmp > 0)
689 		err = snd_pcm_ioplug_set_param_list(io,
690 		    SND_PCM_IOPLUG_HW_BUFFER_BYTES, tmp, bufsz_list);
691 	else
692 #endif
693 	/* buffer size , not strictly ^2 */
694 	err = snd_pcm_ioplug_set_param_minmax(io,
695 	    SND_PCM_IOPLUG_HW_BUFFER_BYTES, FREEBSD_OSS_BUFSZ_MIN,
696 	    FREEBSD_OSS_BUFSZ_MAX);
697 	if (err < 0)
698 		return err;
699 
700 	return 0;
701 #else
702 	snd_pcm_ioplug_t *io = &oss->io;
703 	static const snd_pcm_access_t access_list[] = {
704 		SND_PCM_ACCESS_RW_INTERLEAVED,
705 		SND_PCM_ACCESS_MMAP_INTERLEAVED
706 	};
707 	unsigned int nformats;
708 	unsigned int format[5];
709 	unsigned int nchannels;
710 	unsigned int channel[6];
711 	/* period and buffer bytes must be power of two */
712 	static const unsigned int bytes_list[] = {
713 		1U<<8, 1U<<9, 1U<<10, 1U<<11, 1U<<12, 1U<<13, 1U<<14, 1U<<15,
714 		1U<<16, 1U<<17, 1U<<18, 1U<<19, 1U<<20, 1U<<21, 1U<<22, 1U<<23
715 	};
716 	int i, err, tmp;
717 
718 	/* check trigger */
719 	oss->caps = 0;
720 	if (ioctl(oss->fd, SNDCTL_DSP_GETCAPS, &oss->caps) >= 0) {
721 		if (! (oss->caps & DSP_CAP_TRIGGER))
722 			fprintf(stderr, "*** OSS: trigger is not supported!\n");
723 	}
724 
725 	/* access type - interleaved only */
726 	if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
727 						 ARRAY_SIZE(access_list), access_list)) < 0)
728 		return err;
729 
730 	/* supported formats */
731 	tmp = 0;
732 	ioctl(oss->fd, SNDCTL_DSP_GETFMTS, &tmp);
733 	nformats = 0;
734 	if (tmp & AFMT_U8)
735 		format[nformats++] = SND_PCM_FORMAT_U8;
736 	if (tmp & AFMT_S16_LE)
737 		format[nformats++] = SND_PCM_FORMAT_S16_LE;
738 	if (tmp & AFMT_S16_BE)
739 		format[nformats++] = SND_PCM_FORMAT_S16_BE;
740 	if (tmp & AFMT_MU_LAW)
741 		format[nformats++] = SND_PCM_FORMAT_MU_LAW;
742 	if (! nformats)
743 		format[nformats++] = SND_PCM_FORMAT_S16;
744 	if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
745 						 nformats, format)) < 0)
746 		return err;
747 
748 	/* supported channels */
749 	nchannels = 0;
750 	for (i = 0; i < 6; i++) {
751 		tmp = i + 1;
752 		if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &tmp) >= 0)
753 			channel[nchannels++] = tmp;
754 	}
755 	if (! nchannels) /* assume 2ch stereo */
756 		err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
757 						      2, 2);
758 	else
759 		err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_CHANNELS,
760 						    nchannels, channel);
761 	if (err < 0)
762 		return err;
763 
764 	/* supported rates */
765 	/* FIXME: should query? */
766 	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, 8000, 480000);
767 	if (err < 0)
768 		return err;
769 
770 	/* period size (in power of two) */
771 	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES,
772 					    ARRAY_SIZE(bytes_list), bytes_list);
773 	if (err < 0)
774 		return err;
775 	/* periods */
776 	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, 2, 1024);
777 	if (err < 0)
778 		return err;
779 	/* buffer size (in power of two) */
780 	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_BUFFER_BYTES,
781 					    ARRAY_SIZE(bytes_list), bytes_list);
782 	if (err < 0)
783 		return err;
784 
785 	return 0;
786 #endif
787 }
788 
789 
790 static int oss_close(snd_pcm_ioplug_t *io)
791 {
792 	snd_pcm_oss_t *oss = io->private_data;
793 
794 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
795 	fprintf(stderr, "%s()\n", __func__);
796 #endif
797 
798 	close(oss->fd);
799 	free(oss->device);
800 	free(oss);
801 	return 0;
802 }
803 
804 static const snd_pcm_ioplug_callback_t oss_playback_callback = {
805 	.start = oss_start,
806 	.stop = oss_stop,
807 	.transfer = oss_write,
808 	.pointer = oss_pointer,
809 	.close = oss_close,
810 	.hw_params = oss_hw_params,
811 #ifndef __FreeBSD__
812 	.prepare = oss_prepare,
813 #endif
814 	.drain = oss_drain,
815 	.delay = oss_delay,
816 };
817 
818 static const snd_pcm_ioplug_callback_t oss_capture_callback = {
819 	.start = oss_start,
820 	.stop = oss_stop,
821 	.transfer = oss_read,
822 	.pointer = oss_pointer,
823 	.close = oss_close,
824 	.hw_params = oss_hw_params,
825 #ifndef __FreeBSD__
826 	.prepare = oss_prepare,
827 #endif
828 	.drain = oss_drain,
829 	.delay = oss_delay,
830 };
831 
832 
833 SND_PCM_PLUGIN_DEFINE_FUNC(oss)
834 {
835 	snd_config_iterator_t i, next;
836 	const char *device = "/dev/dsp";
837 	int err;
838 	snd_pcm_oss_t *oss;
839 
840 #if defined(__FreeBSD__) && defined(FREEBSD_OSS_DEBUG_VERBOSE)
841 	fprintf(stderr, "%s()\n", __func__);
842 #endif
843 
844 	snd_config_for_each(i, next, conf) {
845 		snd_config_t *n = snd_config_iterator_entry(i);
846 		const char *id;
847 		if (snd_config_get_id(n, &id) < 0)
848 			continue;
849 		if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 || strcmp(id, "hint") == 0)
850 			continue;
851 		if (strcmp(id, "device") == 0) {
852 			if (snd_config_get_string(n, &device) < 0) {
853 				SNDERR("Invalid type for %s", id);
854 				return -EINVAL;
855 			}
856 			continue;
857 		}
858 		SNDERR("Unknown field %s", id);
859 		return -EINVAL;
860 	}
861 
862 	oss = calloc(1, sizeof(*oss));
863 	if (! oss) {
864 		SNDERR("cannot allocate");
865 		return -ENOMEM;
866 	}
867 
868 	oss->device = strdup(device);
869 	if (oss->device == NULL) {
870 		SNDERR("cannot allocate");
871 		free(oss);
872 		return -ENOMEM;
873 	}
874 	oss->fd = open(device, stream == SND_PCM_STREAM_PLAYBACK ?
875 		       O_WRONLY : O_RDONLY);
876 	if (oss->fd < 0) {
877 		err = -errno;
878 		SNDERR("Cannot open device %s", device);
879 		goto error;
880 	}
881 
882 	oss->io.version = SND_PCM_IOPLUG_VERSION;
883 	oss->io.name = "ALSA <-> OSS PCM I/O Plugin";
884 	oss->io.poll_fd = oss->fd;
885 	oss->io.poll_events = stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;
886 	oss->io.mmap_rw = 0;
887 	oss->io.callback = stream == SND_PCM_STREAM_PLAYBACK ?
888 		&oss_playback_callback : &oss_capture_callback;
889 	oss->io.private_data = oss;
890 
891 	err = snd_pcm_ioplug_create(&oss->io, name, stream, mode);
892 	if (err < 0)
893 		goto error;
894 
895 	if ((err = oss_hw_constraint(oss)) < 0) {
896 		snd_pcm_ioplug_delete(&oss->io);
897 		return err;
898 	}
899 
900 	*pcmp = oss->io.pcm;
901 	return 0;
902 
903  error:
904 	if (oss->fd >= 0)
905 		close(oss->fd);
906 	free(oss->device);
907 	free(oss);
908 	return err;
909 }
910 
911 SND_PCM_PLUGIN_SYMBOL(oss);
912