vchan.c revision 154564
1/*-
2 * Copyright (c) 2001 Cameron Grant <cg@freebsd.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <dev/sound/pcm/sound.h>
28#include <dev/sound/pcm/vchan.h>
29#include "feeder_if.h"
30
31SND_DECLARE_FILE("$FreeBSD: head/sys/dev/sound/pcm/vchan.c 154564 2006-01-20 03:46:02Z ariff $");
32
33/*
34 * Default speed
35 */
36#define VCHAN_DEFAULT_SPEED	48000
37
38extern int feeder_rate_ratemin;
39extern int feeder_rate_ratemax;
40
41struct vchinfo {
42	u_int32_t spd, fmt, blksz, bps, run;
43	struct pcm_channel *channel, *parent;
44	struct pcmchan_caps caps;
45};
46
47static u_int32_t vchan_fmt[] = {
48	AFMT_STEREO | AFMT_S16_LE,
49	0
50};
51
52static int
53vchan_mix_s16(int16_t *to, int16_t *tmp, unsigned int count)
54{
55	/*
56	 * to is the output buffer, tmp is the input buffer
57	 * count is the number of 16bit samples to mix
58	 */
59	int i;
60	int x;
61
62	for(i = 0; i < count; i++) {
63		x = to[i];
64		x += tmp[i];
65		if (x < -32768) {
66			/* printf("%d + %d = %d (u)\n", to[i], tmp[i], x); */
67			x = -32768;
68		}
69		if (x > 32767) {
70			/* printf("%d + %d = %d (o)\n", to[i], tmp[i], x); */
71			x = 32767;
72		}
73		to[i] = x & 0x0000ffff;
74	}
75	return 0;
76}
77
78static int
79feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source)
80{
81	/* we're going to abuse things a bit */
82	struct snd_dbuf *src = source;
83	struct pcmchan_children *cce;
84	struct pcm_channel *ch;
85	uint32_t sz;
86	int16_t *tmp, *dst;
87	unsigned int cnt, rcnt = 0;
88
89	#if 0
90	if (sndbuf_getsize(src) < count)
91		panic("feed_vchan_s16(%s): tmp buffer size %d < count %d, flags = 0x%x",
92		    c->name, sndbuf_getsize(src), count, c->flags);
93	#endif
94	sz = sndbuf_getsize(src);
95	if (sz < count)
96		count = sz;
97	count &= ~1;
98	if (count < 2)
99		return 0;
100	bzero(b, count);
101
102	/*
103	 * we are going to use our source as a temporary buffer since it's
104	 * got no other purpose.  we obtain our data by traversing the channel
105	 * list of children and calling vchan_mix_* to mix count bytes from each
106	 * into our destination buffer, b
107	 */
108	dst = (int16_t *)b;
109	tmp = (int16_t *)sndbuf_getbuf(src);
110	bzero(tmp, count);
111	SLIST_FOREACH(cce, &c->children, link) {
112		ch = cce->channel;
113   		CHN_LOCK(ch);
114		if (ch->flags & CHN_F_TRIGGERED) {
115			if (ch->flags & CHN_F_MAPPED)
116				sndbuf_acquire(ch->bufsoft, NULL, sndbuf_getfree(ch->bufsoft));
117			cnt = FEEDER_FEED(ch->feeder, ch, (u_int8_t *)tmp, count, ch->bufsoft);
118			vchan_mix_s16(dst, tmp, cnt >> 1);
119			if (cnt > rcnt)
120				rcnt = cnt;
121		}
122   		CHN_UNLOCK(ch);
123	}
124
125	return rcnt & ~1;
126}
127
128static struct pcm_feederdesc feeder_vchan_s16_desc[] = {
129	{FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0},
130	{0},
131};
132static kobj_method_t feeder_vchan_s16_methods[] = {
133    	KOBJMETHOD(feeder_feed,		feed_vchan_s16),
134	{ 0, 0 }
135};
136FEEDER_DECLARE(feeder_vchan_s16, 2, NULL);
137
138/************************************************************/
139
140static void *
141vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir)
142{
143	struct vchinfo *ch;
144	struct pcm_channel *parent = devinfo;
145
146	KASSERT(dir == PCMDIR_PLAY, ("vchan_init: bad direction"));
147	ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO);
148	if (!ch)
149		return NULL;
150	ch->parent = parent;
151	ch->channel = c;
152	ch->fmt = AFMT_U8;
153	ch->spd = DSP_DEFAULT_SPEED;
154	ch->blksz = 2048;
155
156	c->flags |= CHN_F_VIRTUAL;
157
158	return ch;
159}
160
161static int
162vchan_free(kobj_t obj, void *data)
163{
164	return 0;
165}
166
167static int
168vchan_setformat(kobj_t obj, void *data, u_int32_t format)
169{
170	struct vchinfo *ch = data;
171	struct pcm_channel *parent = ch->parent;
172	struct pcm_channel *channel = ch->channel;
173
174	ch->fmt = format;
175	ch->bps = 1;
176	ch->bps <<= (ch->fmt & AFMT_STEREO)? 1 : 0;
177	if (ch->fmt & AFMT_16BIT)
178		ch->bps <<= 1;
179	else if (ch->fmt & AFMT_24BIT)
180		ch->bps *= 3;
181	else if (ch->fmt & AFMT_32BIT)
182		ch->bps <<= 2;
183   	CHN_UNLOCK(channel);
184	chn_notify(parent, CHN_N_FORMAT);
185   	CHN_LOCK(channel);
186	sndbuf_setfmt(channel->bufsoft, format);
187	return 0;
188}
189
190static int
191vchan_setspeed(kobj_t obj, void *data, u_int32_t speed)
192{
193	struct vchinfo *ch = data;
194	struct pcm_channel *parent = ch->parent;
195	struct pcm_channel *channel = ch->channel;
196
197	ch->spd = speed;
198	CHN_UNLOCK(channel);
199	CHN_LOCK(parent);
200	speed = sndbuf_getspd(parent->bufsoft);
201	CHN_UNLOCK(parent);
202	CHN_LOCK(channel);
203	return speed;
204}
205
206static int
207vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
208{
209	struct vchinfo *ch = data;
210	struct pcm_channel *channel = ch->channel;
211	struct pcm_channel *parent = ch->parent;
212	/* struct pcm_channel *channel = ch->channel; */
213	int prate, crate;
214
215	ch->blksz = blocksize;
216   	/* CHN_UNLOCK(channel); */
217	sndbuf_setblksz(channel->bufhard, blocksize);
218	chn_notify(parent, CHN_N_BLOCKSIZE);
219   	CHN_LOCK(parent);
220   	/* CHN_LOCK(channel); */
221
222	crate = ch->spd * ch->bps;
223	prate = sndbuf_getspd(parent->bufsoft) * sndbuf_getbps(parent->bufsoft);
224	blocksize = sndbuf_getblksz(parent->bufsoft);
225   	CHN_UNLOCK(parent);
226	blocksize *= prate;
227	blocksize /= crate;
228
229	return blocksize;
230}
231
232static int
233vchan_trigger(kobj_t obj, void *data, int go)
234{
235	struct vchinfo *ch = data;
236	struct pcm_channel *parent = ch->parent;
237	struct pcm_channel *channel = ch->channel;
238
239	if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)
240		return 0;
241
242	ch->run = (go == PCMTRIG_START)? 1 : 0;
243   	CHN_UNLOCK(channel);
244	chn_notify(parent, CHN_N_TRIGGER);
245   	CHN_LOCK(channel);
246
247	return 0;
248}
249
250static struct pcmchan_caps *
251vchan_getcaps(kobj_t obj, void *data)
252{
253	struct vchinfo *ch = data;
254
255	ch->caps.minspeed = sndbuf_getspd(ch->parent->bufsoft);
256	ch->caps.maxspeed = ch->caps.minspeed;
257	ch->caps.fmtlist = vchan_fmt;
258	ch->caps.caps = 0;
259
260	return &ch->caps;
261}
262
263static kobj_method_t vchan_methods[] = {
264    	KOBJMETHOD(channel_init,		vchan_init),
265    	KOBJMETHOD(channel_free,		vchan_free),
266    	KOBJMETHOD(channel_setformat,		vchan_setformat),
267    	KOBJMETHOD(channel_setspeed,		vchan_setspeed),
268    	KOBJMETHOD(channel_setblocksize,	vchan_setblocksize),
269    	KOBJMETHOD(channel_trigger,		vchan_trigger),
270    	KOBJMETHOD(channel_getcaps,		vchan_getcaps),
271	{ 0, 0 }
272};
273CHANNEL_DECLARE(vchan);
274
275/*
276 * On the fly vchan rate settings
277 */
278#ifdef SND_DYNSYSCTL
279static int
280sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS)
281{
282	struct snddev_info *d;
283    	struct snddev_channel *sce;
284	struct pcm_channel *c, *ch = NULL, *fake;
285	struct pcmchan_caps *caps;
286	int err = 0;
287	int newspd = 0;
288
289	d = oidp->oid_arg1;
290	if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1)
291		return EINVAL;
292	SLIST_FOREACH(sce, &d->channels, link) {
293		c = sce->channel;
294		CHN_LOCK(c);
295		if (c->direction == PCMDIR_PLAY) {
296			if (c->flags & CHN_F_VIRTUAL) {
297				if (req->newptr != NULL &&
298						(c->flags & CHN_F_BUSY)) {
299					CHN_UNLOCK(c);
300					return EBUSY;
301				}
302				if (ch == NULL)
303					ch = c->parentchannel;
304			}
305		}
306		CHN_UNLOCK(c);
307	}
308	if (ch != NULL) {
309		CHN_LOCK(ch);
310		newspd = ch->speed;
311		CHN_UNLOCK(ch);
312	}
313	err = sysctl_handle_int(oidp, &newspd, sizeof(newspd), req);
314	if (err == 0 && req->newptr != NULL) {
315		if (ch == NULL || newspd < 1 ||
316				newspd < feeder_rate_ratemin ||
317				newspd > feeder_rate_ratemax)
318			return EINVAL;
319		if (pcm_inprog(d, 1) != 1) {
320			pcm_inprog(d, -1);
321			return EINPROGRESS;
322		}
323		CHN_LOCK(ch);
324		caps = chn_getcaps(ch);
325		if (caps == NULL || newspd < caps->minspeed ||
326				newspd > caps->maxspeed) {
327			CHN_UNLOCK(ch);
328			pcm_inprog(d, -1);
329			return EINVAL;
330		}
331		if (newspd != ch->speed) {
332			err = chn_setspeed(ch, newspd);
333			/*
334			 * Try to avoid FEEDER_RATE on parent channel if the
335			 * requested value is not supported by the hardware.
336			 */
337			if (!err && (ch->feederflags & (1 << FEEDER_RATE))) {
338				newspd = sndbuf_getspd(ch->bufhard);
339				err = chn_setspeed(ch, newspd);
340			}
341			CHN_UNLOCK(ch);
342			if (err == 0) {
343				fake = pcm_getfakechan(d);
344				if (fake != NULL) {
345					CHN_LOCK(fake);
346					fake->speed = newspd;
347					CHN_UNLOCK(fake);
348				}
349			}
350		} else
351			CHN_UNLOCK(ch);
352		pcm_inprog(d, -1);
353	}
354	return err;
355}
356#endif
357
358/* virtual channel interface */
359
360int
361vchan_create(struct pcm_channel *parent)
362{
363    	struct snddev_info *d = parent->parentsnddev;
364	struct pcmchan_children *pce;
365	struct pcm_channel *child, *fake;
366	struct pcmchan_caps *parent_caps;
367	int err, first, speed = 0;
368
369	if (!(parent->flags & CHN_F_BUSY))
370		return EBUSY;
371
372
373	CHN_UNLOCK(parent);
374
375	pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO);
376	if (!pce) {
377   		CHN_LOCK(parent);
378		return ENOMEM;
379	}
380
381	/* create a new playback channel */
382	child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent);
383	if (!child) {
384		free(pce, M_DEVBUF);
385   		CHN_LOCK(parent);
386		return ENODEV;
387	}
388	pce->channel = child;
389
390	/* add us to our grandparent's channel list */
391	/*
392	 * XXX maybe we shouldn't always add the dev_t
393 	 */
394	err = pcm_chn_add(d, child);
395	if (err) {
396		pcm_chn_destroy(child);
397		free(pce, M_DEVBUF);
398		CHN_LOCK(parent);
399		return err;
400	}
401
402   	CHN_LOCK(parent);
403	/* add us to our parent channel's children */
404	first = SLIST_EMPTY(&parent->children);
405	SLIST_INSERT_HEAD(&parent->children, pce, link);
406	parent->flags |= CHN_F_HAS_VCHAN;
407
408	if (first) {
409		parent_caps = chn_getcaps(parent);
410		if (parent_caps == NULL)
411			err = EINVAL;
412
413		if (!err)
414			err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE);
415
416		if (!err) {
417			fake = pcm_getfakechan(d);
418			if (fake != NULL) {
419				/*
420				 * Avoid querying kernel hint, use saved value
421				 * from fake channel.
422				 */
423				CHN_UNLOCK(parent);
424				CHN_LOCK(fake);
425				speed = fake->speed;
426				CHN_UNLOCK(fake);
427				CHN_LOCK(parent);
428			}
429
430			/*
431			 * This is very sad. Few soundcards advertised as being
432			 * able to do (insanely) higher/lower speed, but in
433			 * reality, they simply can't. At least, we give user chance
434			 * to set sane value via kernel hints or sysctl.
435			 */
436			if (speed < 1) {
437				int r;
438				CHN_UNLOCK(parent);
439				r = resource_int_value(device_get_name(parent->dev),
440							device_get_unit(parent->dev),
441								"vchanrate", &speed);
442				CHN_LOCK(parent);
443				if (r != 0) {
444					/*
445					 * Workaround for sb16 running
446					 * poorly at 45k / 49k.
447					 */
448					switch (parent_caps->maxspeed) {
449					case 45000:
450					case 49000:
451						speed = 44100;
452						break;
453					default:
454						speed = VCHAN_DEFAULT_SPEED;
455						break;
456					}
457				}
458			}
459
460			/*
461			 * Limit speed based on driver caps.
462			 * This is supposed to help fixed rate, non-VRA
463			 * AC97 cards, but.. (see below)
464			 */
465			if (speed < parent_caps->minspeed)
466				speed = parent_caps->minspeed;
467			if (speed > parent_caps->maxspeed)
468				speed = parent_caps->maxspeed;
469
470			/*
471			 * We still need to limit the speed between
472			 * feeder_rate_ratemin <-> feeder_rate_ratemax. This is
473			 * just an escape goat if all of the above failed
474			 * miserably.
475			 */
476			if (speed < feeder_rate_ratemin)
477				speed = feeder_rate_ratemin;
478			if (speed > feeder_rate_ratemax)
479				speed = feeder_rate_ratemax;
480
481			err = chn_setspeed(parent, speed);
482			/*
483			 * Try to avoid FEEDER_RATE on parent channel if the
484			 * requested value is not supported by the hardware.
485			 */
486			if (!err && (parent->feederflags & (1 << FEEDER_RATE))) {
487				speed = sndbuf_getspd(parent->bufhard);
488				err = chn_setspeed(parent, speed);
489			}
490
491			if (!err && fake != NULL) {
492				/*
493				 * Save new value to fake channel.
494				 */
495				CHN_UNLOCK(parent);
496				CHN_LOCK(fake);
497				fake->speed = speed;
498				CHN_UNLOCK(fake);
499				CHN_LOCK(parent);
500			}
501		}
502
503		if (err) {
504			SLIST_REMOVE(&parent->children, pce, pcmchan_children, link);
505			parent->flags &= ~CHN_F_HAS_VCHAN;
506			CHN_UNLOCK(parent);
507			free(pce, M_DEVBUF);
508			pcm_chn_remove(d, child);
509			pcm_chn_destroy(child);
510			CHN_LOCK(parent);
511			return err;
512		}
513	}
514
515	return 0;
516}
517
518int
519vchan_destroy(struct pcm_channel *c)
520{
521	struct pcm_channel *parent = c->parentchannel;
522    	struct snddev_info *d = parent->parentsnddev;
523	struct pcmchan_children *pce;
524	struct snddev_channel *sce;
525	int err, last;
526
527	CHN_LOCK(parent);
528	if (!(parent->flags & CHN_F_BUSY)) {
529		CHN_UNLOCK(parent);
530		return EBUSY;
531	}
532	if (SLIST_EMPTY(&parent->children)) {
533		CHN_UNLOCK(parent);
534		return EINVAL;
535	}
536
537	/* remove us from our parent's children list */
538	SLIST_FOREACH(pce, &parent->children, link) {
539		if (pce->channel == c)
540			goto gotch;
541	}
542	CHN_UNLOCK(parent);
543	return EINVAL;
544gotch:
545	SLIST_FOREACH(sce, &d->channels, link) {
546		if (sce->channel == c) {
547			if (sce->dsp_devt)
548				destroy_dev(sce->dsp_devt);
549			if (sce->dspW_devt)
550				destroy_dev(sce->dspW_devt);
551			if (sce->audio_devt)
552				destroy_dev(sce->audio_devt);
553			if (sce->dspr_devt)
554				destroy_dev(sce->dspr_devt);
555			break;
556		}
557	}
558	SLIST_REMOVE(&parent->children, pce, pcmchan_children, link);
559	free(pce, M_DEVBUF);
560
561	last = SLIST_EMPTY(&parent->children);
562	if (last) {
563		parent->flags &= ~CHN_F_BUSY;
564		parent->flags &= ~CHN_F_HAS_VCHAN;
565	}
566
567	/* remove us from our grandparent's channel list */
568	err = pcm_chn_remove(d, c);
569
570	CHN_UNLOCK(parent);
571	/* destroy ourselves */
572	if (!err)
573		err = pcm_chn_destroy(c);
574
575#if 0
576	if (!err && last) {
577		CHN_LOCK(parent);
578		chn_reset(parent, chn_getcaps(parent)->fmtlist[0]);
579		chn_setspeed(parent, chn_getcaps(parent)->minspeed);
580		CHN_UNLOCK(parent);
581	}
582#endif
583
584	return err;
585}
586
587int
588vchan_initsys(device_t dev)
589{
590#ifdef SND_DYNSYSCTL
591	struct snddev_info *d;
592
593    	d = device_get_softc(dev);
594	SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)),
595            OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d),
596	    sysctl_hw_snd_vchans, "I", "");
597	SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)),
598	    OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d),
599	    sysctl_hw_snd_vchanrate, "I", "");
600#endif
601
602	return 0;
603}
604