From b611c801f0acad06062769cfc815c6f181c8c66f Mon Sep 17 00:00:00 2001 From: Alexander Leidinger Date: Sat, 23 Sep 2006 20:45:47 +0000 Subject: [PATCH] MFp4 the sound Google Summer of Code project: The goal was to sync with the OSSv4 API 4Front Technologies uses in their proprietary OSS driver. This was successful as far as possible. The part of the API which is stable is implemented, for the rest there are some stubs already. New system ioctls: - SNDCTL_SYSINFO - obtain audio system info (version, # of audio/midi/ mixer devices, etc.) - SNDCTL_AUDIOINFO - fetch details about a specific audio device - SNDCTL_MIXERINFO - fetch details about a specific mixer device New audio ioctls: - Sync groups (SNDCTL_DSP_SYNCGROUP/SNDCTL_DSP_SYNCSTART) which allow triggered playback/recording on multiple devices (even across processes simultaneously). - Peak meters (SNDCTL_DSP_GETIPEAKS/SNDCTL_DSP_GETOPEAKS) - can query audio drivers for peak levels (needs driver support, disabled for now). - Per channel playback/recording levels - SNDCTL_DSP_{GET,SET}{PLAY,REC}VOL. Note that these are still in name only, just wrapping around the AC97-style mixer at the moment. The next step is to push them down to the drivers. Audio ioctls still under development by 4Front (for which stubs may exist in this commit): - SNDCTL_GETNAME, SNDCTL_{GET,SET}{SONG,LABEL} - SNDCTL_DSP_{GET,SET}_CHNORDER - SNDCTL_MIX_ENUMINFO, SNDCTL_MIX_EXTINFO - (might be documented enough in the OSS releases to work on this. These ioctls cover the cool "twiddle any knob on your card" features.) Missing: - SNDCTL_DSP_COOKEDMODE -- this ioctl is used to give applications direct access to a card's buffers, bypassing the feeder architecture. It's a toughy -- "someone" needs to decide : (a) if this is desireable, and (b) if it's reasonably feasible. Updates for driver writers: So far, only two routines to the channel class (in channel_if.m) are added. One is for fetching a list of discrete supported playback/recording rates of a channel, and the other is for fetching peak level info (useful for drawing peak meters). Interested parties may want to help pushing down SNDCTL_DSP_{GET,SET}{PLAY,REC}VOL into the drivers. To use the new stuff you need to rebuild the sound drivers or your kernel (depending on if you use modules or not) and to install soundcard.h (a buildworld/installworld handles this). Sponsored by: Google SoC 2006 Submitted by: ryanb Many thanks to: 4Front Technologies for their cooperation, explanations and the nice license of their soundcard.h. --- sys/dev/sound/pcm/buffer.c | 125 ++++ sys/dev/sound/pcm/buffer.h | 8 + sys/dev/sound/pcm/channel.c | 157 ++++- sys/dev/sound/pcm/channel.h | 67 ++ sys/dev/sound/pcm/channel_if.m | 64 ++ sys/dev/sound/pcm/dsp.c | 1163 +++++++++++++++++++++++++++++++- sys/dev/sound/pcm/dsp.h | 6 + sys/dev/sound/pcm/mixer.c | 336 ++++++++- sys/dev/sound/pcm/mixer.h | 10 +- sys/dev/sound/pcm/sound.c | 138 +++- sys/dev/sound/pcm/sound.h | 5 + sys/sys/param.h | 2 +- sys/sys/soundcard.h | 439 +++++++++++- 13 files changed, 2509 insertions(+), 11 deletions(-) diff --git a/sys/dev/sound/pcm/buffer.c b/sys/dev/sound/pcm/buffer.c index 0be87415b5f6..11c1bfbad806 100644 --- a/sys/dev/sound/pcm/buffer.c +++ b/sys/dev/sound/pcm/buffer.c @@ -117,6 +117,11 @@ sndbuf_free(struct snd_dbuf *b) free(b->tmpbuf, M_DEVBUF); b->tmpbuf = NULL; + if (b->shadbuf) + free(b->shadbuf, M_DEVBUF); + b->shadbuf = NULL; + b->sl = 0; + if (b->dmamap) bus_dmamap_unload(b->dmatag, b->dmamap); @@ -168,6 +173,7 @@ int sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) { u_int8_t *buf, *tmpbuf, *f1, *f2; + u_int8_t *shadbuf, *f3; unsigned int bufsize; int ret; @@ -189,6 +195,15 @@ sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) ret = ENOMEM; goto out; } + + shadbuf = malloc(bufsize, M_DEVBUF, M_WAITOK); + if (shadbuf == NULL) { + free(buf, M_DEVBUF); + free(tmpbuf, M_DEVBUF); + ret = ENOMEM; + goto out; + } + chn_lock(b->channel); b->blkcnt = blkcnt; @@ -199,6 +214,9 @@ sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) f2 = b->tmpbuf; b->buf = buf; b->tmpbuf = tmpbuf; + f3 = b->shadbuf; + b->shadbuf = shadbuf; + b->sl = bufsize; sndbuf_reset(b); @@ -207,6 +225,8 @@ sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) free(f1, M_DEVBUF); if (f2) free(f2, M_DEVBUF); + if (f3) + free(f3, M_DEVBUF); ret = 0; out: @@ -214,6 +234,15 @@ sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) return ret; } +/** + * @brief Zero out space in buffer free area + * + * This function clears a chunk of @c length bytes in the buffer free area + * (i.e., where the next write will be placed). + * + * @param b buffer context + * @param length number of bytes to blank + */ void sndbuf_clear(struct snd_dbuf *b, unsigned int length) { @@ -241,6 +270,11 @@ sndbuf_clear(struct snd_dbuf *b, unsigned int length) } } +/** + * @brief Zap buffer contents, resetting "ready area" fields + * + * @param b buffer context + */ void sndbuf_fillsilence(struct snd_dbuf *b) { @@ -260,6 +294,23 @@ sndbuf_fillsilence(struct snd_dbuf *b) b->rl = b->bufsize; } +/** + * @brief Reset buffer w/o flushing statistics + * + * This function just zeroes out buffer contents and sets the "ready length" + * to zero. This was originally to facilitate minimal playback interruption + * (i.e., dropped samples) in SNDCTL_DSP_SILENCE/SKIP ioctls. + * + * @param b buffer context + */ +void +sndbuf_softreset(struct snd_dbuf *b) +{ + b->rl = 0; + if (b->buf && b->bufsize > 0) + sndbuf_clear(b, b->bufsize); +} + void sndbuf_reset(struct snd_dbuf *b) { @@ -272,6 +323,7 @@ sndbuf_reset(struct snd_dbuf *b) b->xrun = 0; if (b->buf && b->bufsize > 0) sndbuf_clear(b, b->bufsize); + sndbuf_clearshadow(b); } u_int32_t @@ -493,6 +545,19 @@ sndbuf_updateprevtotal(struct snd_dbuf *b) /************************************************************/ +/** + * @brief Acquire buffer space to extend ready area + * + * This function extends the ready area length by @c count bytes, and may + * optionally copy samples from another location stored in @c from. The + * counter @c snd_dbuf::total is also incremented by @c count bytes. + * + * @param b audio buffer + * @param from sample source (optional) + * @param count number of bytes to acquire + * + * @retval 0 Unconditional + */ int sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count) { @@ -516,6 +581,20 @@ sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count) return 0; } +/** + * @brief Dispose samples from channel buffer, increasing size of ready area + * + * This function discards samples from the supplied buffer by advancing the + * ready area start pointer and decrementing the ready area length. If + * @c to is not NULL, then the discard samples will be copied to the location + * it points to. + * + * @param b PCM channel sound buffer + * @param to destination buffer (optional) + * @param count number of bytes to discard + * + * @returns 0 unconditionally + */ int sndbuf_dispose(struct snd_dbuf *b, u_int8_t *to, unsigned int count) { @@ -592,3 +671,49 @@ sndbuf_setflags(struct snd_dbuf *b, u_int32_t flags, int on) b->flags |= flags; } +/** + * @brief Clear the shadow buffer by filling with samples equal to zero. + * + * @param b buffer to clear + */ +void +sndbuf_clearshadow(struct snd_dbuf *b) +{ + KASSERT(b != NULL, ("b is a null pointer")); + KASSERT(b->sl >= 0, ("illegal shadow length")); + + if ((b->shadbuf != NULL) && (b->sl > 0)) { + if (b->fmt & AFMT_SIGNED) + memset(b->shadbuf, 0x00, b->sl); + else + memset(b->shadbuf, 0x80, b->sl); + } +} + +#ifdef OSSV4_EXPERIMENT +/** + * @brief Return peak value from samples in buffer ready area. + * + * Peak ranges from 0-32767. If channel is monaural, most significant 16 + * bits will be zero. For now, only expects to work with 1-2 channel + * buffers. + * + * @note Currently only operates with linear PCM formats. + * + * @param b buffer to analyze + * @param lpeak pointer to store left peak value + * @param rpeak pointer to store right peak value + */ +void +sndbuf_getpeaks(struct snd_dbuf *b, int *lp, int *rp) +{ + u_int32_t lpeak, rpeak; + + lpeak = 0; + rpeak = 0; + + /** + * @todo fill this in later + */ +} +#endif diff --git a/sys/dev/sound/pcm/buffer.h b/sys/dev/sound/pcm/buffer.h index dbbabd9b436d..07724beadea5 100644 --- a/sys/dev/sound/pcm/buffer.h +++ b/sys/dev/sound/pcm/buffer.h @@ -38,6 +38,8 @@ struct snd_dbuf { device_t dev; u_int8_t *buf, *tmpbuf; + u_int8_t *shadbuf; /**< shadow buffer used w/ S_D_SILENCE/SKIP */ + volatile int sl; /**< shadbuf ready length in # of bytes */ unsigned int bufsize, maxsize; volatile int dl; /* transfer size */ volatile int rp; /* pointers to the ready area */ @@ -70,6 +72,8 @@ int sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) void sndbuf_reset(struct snd_dbuf *b); void sndbuf_clear(struct snd_dbuf *b, unsigned int length); void sndbuf_fillsilence(struct snd_dbuf *b); +void sndbuf_softreset(struct snd_dbuf *b); +void sndbuf_clearshadow(struct snd_dbuf *b); u_int32_t sndbuf_getfmt(struct snd_dbuf *b); int sndbuf_setfmt(struct snd_dbuf *b, u_int32_t fmt); @@ -117,3 +121,7 @@ int sndbuf_dmasetdir(struct snd_dbuf *b, int dir); void sndbuf_dma(struct snd_dbuf *b, int go); int sndbuf_dmaptr(struct snd_dbuf *b); void sndbuf_dmabounce(struct snd_dbuf *b); + +#ifdef OSSV4_EXPERIMENT +void sndbuf_getpeaks(struct snd_dbuf *b, int *lp, int *rp); +#endif diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index a2720c302554..6f777757837f 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -68,6 +68,24 @@ static int report_soft_formats = 1; SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, &report_soft_formats, 1, "report software-emulated formats"); +/** + * @brief Channel sync group lock + * + * Clients should acquire this lock @b without holding any channel locks + * before touching syncgroups or the main syncgroup list. + */ +struct mtx snd_pcm_syncgroups_mtx; +MTX_SYSINIT(pcm_syncgroup, &snd_pcm_syncgroups_mtx, "PCM channel sync group lock", MTX_DEF); +/** + * @brief syncgroups' master list + * + * Each time a channel syncgroup is created, it's added to this list. This + * list should only be accessed with @sa snd_pcm_syncgroups_mtx held. + * + * See SNDCTL_DSP_SYNCGROUP for more information. + */ +struct pcm_synclist snd_pcm_syncgroups = SLIST_HEAD_INITIALIZER(head); + static int chn_buildfeeder(struct pcm_channel *c); static void @@ -87,14 +105,23 @@ chn_lockinit(struct pcm_channel *c, int dir) c->lock = snd_mtxcreate(c->name, "pcm fake channel"); break; } + + cv_init(&c->cv, c->name); } static void chn_lockdestroy(struct pcm_channel *c) { snd_mtxfree(c->lock); + cv_destroy(&c->cv); } +/** + * @brief Determine channel is ready for I/O + * + * @retval 1 = ready for I/O + * @retval 0 = not ready for I/O + */ static int chn_polltrigger(struct pcm_channel *c) { @@ -112,8 +139,8 @@ chn_polltrigger(struct pcm_channel *c) #if 0 lim = (c->flags & CHN_F_HAS_SIZE)? sndbuf_getblksz(bs) : 1; #endif - lim = 1; - return (amt >= lim)? 1 : 0; + lim = c->lw; + return (amt >= lim) ? 1 : 0; } return 0; } @@ -310,12 +337,25 @@ chn_write(struct pcm_channel *c, struct uio *buf) ret = 0; count = hz; + while (!ret && (buf->uio_resid > 0) && (count > 0)) { sz = sndbuf_getfree(bs); if (sz == 0) { if (c->flags & CHN_F_NBIO) ret = EWOULDBLOCK; - else { + else if (c->flags & CHN_F_NOTRIGGER) { + /** + * @todo Evaluate whether EAGAIN is truly desirable. + * 4Front drivers behave like this, but I'm + * not sure if it at all violates the "write + * should be allowed to block" model. + * + * The idea is that, while set with CHN_F_NOTRIGGER, + * a channel isn't playing, *but* without this we + * end up with "interrupt timeout / channel dead". + */ + ret = EAGAIN; + } else { timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); if (timeout < 1) timeout = 1; @@ -829,6 +869,7 @@ chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction) c->bufsoft = bs; c->flags = 0; c->feederflags = 0; + c->sm = NULL; ret = ENODEV; CHN_UNLOCK(c); /* XXX - Unlock for CHANNEL_INIT() malloc() call */ @@ -853,6 +894,19 @@ chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction) if (ret) goto out; + /** + * @todo Should this be moved somewhere else? The primary buffer + * is allocated by the driver or via DMA map setup, and tmpbuf + * seems to only come into existence in sndbuf_resize(). + */ + if (c->direction == PCMDIR_PLAY) { + bs->sl = sndbuf_getmaxsize(bs); + bs->shadbuf = malloc(bs->sl, M_DEVBUF, M_NOWAIT); + if (bs->shadbuf == NULL) { + ret = ENOMEM; + goto out; + } + } out: CHN_UNLOCK(c); @@ -1195,6 +1249,12 @@ chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) goto out1; } + /* + * OSSv4 docs: "By default OSS will set the low water level equal + * to the fragment size which is optimal in most cases." + */ + c->lw = sndbuf_getblksz(bs); + chn_resetbuf(c); out1: KASSERT(sndbuf_getsize(bs) == 0 || @@ -1243,6 +1303,17 @@ chn_trigger(struct pcm_channel *c, int go) return ret; } +/** + * @brief Queries sound driver for sample-aligned hardware buffer pointer index + * + * This function obtains the hardware pointer location, then aligns it to + * the current bytes-per-sample value before returning. (E.g., a channel + * running in 16 bit stereo mode would require 4 bytes per sample, so a + * hwptr value ranging from 32-35 would be returned as 32.) + * + * @param c PCM channel context + * @returns sample-aligned hardware buffer pointer index + */ int chn_getptr(struct pcm_channel *c) { @@ -1501,6 +1572,77 @@ chn_notify(struct pcm_channel *c, u_int32_t flags) return 0; } +/** + * @brief Fetch array of supported discrete sample rates + * + * Wrapper for CHANNEL_GETRATES. Please see channel_if.m:getrates() for + * detailed information. + * + * @note If the operation isn't supported, this function will just return 0 + * (no rates in the array), and *rates will be set to NULL. Callers + * should examine rates @b only if this function returns non-zero. + * + * @param c pcm channel to examine + * @param rates pointer to array of integers; rate table will be recorded here + * + * @return number of rates in the array pointed to be @c rates + */ +int +chn_getrates(struct pcm_channel *c, int **rates) +{ + KASSERT(rates != NULL, ("rates is null")); + CHN_LOCKASSERT(c); + return CHANNEL_GETRATES(c->methods, c->devinfo, rates); +} + +/** + * @brief Remove channel from a sync group, if there is one. + * + * This function is initially intended for the following conditions: + * - Starting a syncgroup (@c SNDCTL_DSP_SYNCSTART ioctl) + * - Closing a device. (A channel can't be destroyed if it's still in use.) + * + * @note Before calling this function, the syncgroup list mutex must be + * held. (Consider pcm_channel::sm protected by the SG list mutex + * whether @c c is locked or not.) + * + * @param c channel device to be started or closed + * @returns If this channel was the only member of a group, the group ID + * is returned to the caller so that the caller can release it + * via free_unr() after giving up the syncgroup lock. Else it + * returns 0. + */ +int +chn_syncdestroy(struct pcm_channel *c) +{ + struct pcmchan_syncmember *sm; + struct pcmchan_syncgroup *sg; + int sg_id; + + sg_id = 0; + + PCM_SG_LOCKASSERT(MA_OWNED); + + if (c->sm != NULL) { + sm = c->sm; + sg = sm->parent; + c->sm = NULL; + + KASSERT(sg != NULL, ("syncmember has null parent")); + + SLIST_REMOVE(&sg->members, sm, pcmchan_syncmember, link); + free(sm, M_DEVBUF); + + if (SLIST_EMPTY(&sg->members)) { + SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); + sg_id = sg->id; + free(sg, M_DEVBUF); + } + } + + return sg_id; +} + void chn_lock(struct pcm_channel *c) { @@ -1512,3 +1654,12 @@ chn_unlock(struct pcm_channel *c) { CHN_UNLOCK(c); } + +#ifdef OSSV4_EXPERIMENT +int +chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak) +{ + CHN_LOCKASSERT(c); + return CHANNEL_GETPEAKS(c->methods, c->devinfo, lpeak, rpeak); +} +#endif diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h index b854976893e9..d6426f7c1f0c 100644 --- a/sys/dev/sound/pcm/channel.h +++ b/sys/dev/sound/pcm/channel.h @@ -37,6 +37,37 @@ struct pcmchan_caps { u_int32_t caps; }; +/* Forward declarations */ +struct pcm_channel; +struct pcmchan_syncgroup; +struct pcmchan_syncmember; + +extern struct mtx snd_pcm_syncgroups_mtx; +extern SLIST_HEAD(pcm_synclist, pcmchan_syncgroup) snd_pcm_syncgroups; + +#define PCM_SG_LOCK() mtx_lock(&snd_pcm_syncgroups_mtx) +#define PCM_SG_TRYLOCK() mtx_trylock(&snd_pcm_syncgroups_mtx) +#define PCM_SG_UNLOCK() mtx_unlock(&snd_pcm_syncgroups_mtx) +#define PCM_SG_LOCKASSERT(arg) mtx_assert(&snd_pcm_syncgroups_mtx, arg) + +/** + * @brief Specifies an audio device sync group + */ +struct pcmchan_syncgroup { + SLIST_ENTRY(pcmchan_syncgroup) link; + SLIST_HEAD(, pcmchan_syncmember) members; + int id; /**< Group identifier; set to address of group. */ +}; + +/** + * @brief Specifies a container for members of a sync group + */ +struct pcmchan_syncmember { + SLIST_ENTRY(pcmchan_syncmember) link; + struct pcmchan_syncgroup *parent; /**< group head */ + struct pcm_channel *ch; +}; + #define CHN_NAMELEN 32 struct pcm_channel { kobj_t methods; @@ -63,6 +94,33 @@ struct pcm_channel { device_t dev; char name[CHN_NAMELEN]; struct mtx *lock; + /** + * Increment,decrement this around operations that temporarily yield + * lock. + */ + unsigned int inprog; + /** + * Special channel operations should examine @c inprog after acquiring + * lock. If zero, operations may continue. Else, thread should + * wait on this cv for previous operation to finish. + */ + struct cv cv; + /** + * Low water mark for select()/poll(). + * + * This is initialized to the channel's fragment size, and will be + * overwritten if a new fragment size is set. Users may alter this + * value directly with the @c SNDCTL_DSP_LOW_WATER ioctl. + */ + unsigned int lw; + /** + * If part of a sync group, this will point to the syncmember + * container. + */ + struct pcmchan_syncmember *sm; +#ifdef OSSV4_EXPERIMENT + u_int16_t lpeak, rpeak; /**< Peak value from 0-32767. */ +#endif SLIST_HEAD(, pcmchan_children) children; }; @@ -102,13 +160,22 @@ int chn_notify(struct pcm_channel *c, u_int32_t flags); void chn_lock(struct pcm_channel *c); void chn_unlock(struct pcm_channel *c); +int chn_getrates(struct pcm_channel *c, int **rates); +int chn_syncdestroy(struct pcm_channel *c); + +#ifdef OSSV4_EXPERIMENT +int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak); +#endif + #ifdef USING_MUTEX #define CHN_LOCK(c) mtx_lock((struct mtx *)((c)->lock)) #define CHN_UNLOCK(c) mtx_unlock((struct mtx *)((c)->lock)) +#define CHN_TRYLOCK(c) mtx_trylock((struct mtx *)((c)->lock)) #define CHN_LOCKASSERT(c) mtx_assert((struct mtx *)((c)->lock), MA_OWNED) #else #define CHN_LOCK(c) #define CHN_UNLOCK(c) +#define CHN_TRYLOCK(c) #define CHN_LOCKASSERT(c) #endif diff --git a/sys/dev/sound/pcm/channel_if.m b/sys/dev/sound/pcm/channel_if.m index cdc6a3b6f2b3..d0c07c3f245e 100644 --- a/sys/dev/sound/pcm/channel_if.m +++ b/sys/dev/sound/pcm/channel_if.m @@ -70,6 +70,19 @@ CODE { return 0; } + static int + channel_nogetpeaks(kobj_t obj, void *data, int *lpeak, int *rpeak) + { + return -1; + } + + static int + channel_nogetrates(kobj_t obj, void *data, int **rates) + { + *rates = NULL; + return 0; + } + }; METHOD void* init { @@ -140,3 +153,54 @@ METHOD int notify { void *data; u_int32_t changed; } DEFAULT channel_nonotify; + +/** + * @brief Retrieve channel peak values + * + * This function is intended to obtain peak volume values for samples + * played/recorded on a channel. Values are on a linear scale from 0 to + * 32767. If the channel is monaural, a single value should be recorded + * in @c lpeak. + * + * If hardware support isn't available, the SNDCTL_DSP_GET[IO]PEAKS + * operation should return EINVAL. However, we may opt to provide + * software support that the user may toggle via sysctl/mixext. + * + * @param obj standard kobj object (usually @c channel->methods) + * @param data driver-specific data (usually @c channel->devinfo) + * @param lpeak pointer to store left peak level + * @param rpeak pointer to store right peak level + * + * @retval -1 Error; usually operation isn't supported. + * @retval 0 success + */ +METHOD int getpeaks { + kobj_t obj; + void *data; + int *lpeak; + int *rpeak; +} DEFAULT channel_nogetpeaks; + +/** + * @brief Retrieve discrete supported sample rates + * + * Some cards operate at fixed rates, and this call is intended to retrieve + * those rates primarily for when in-kernel rate adjustment is undesirable + * (e.g., application wants direct DMA access after setting a channel to run + * "uncooked"). + * + * The parameter @c rates is a double pointer which will be reset to + * point to an array of supported sample rates. The number of elements + * in the array is returned to the caller. + * + * @param obj standard kobj object (usually @c channel->methods) + * @param data driver-specific data (usually @c channel->devinfo) + * @param rates rate array pointer + * + * @return Number of rates in the array + */ +METHOD int getrates { + kobj_t obj; + void *data; + int **rates; +} DEFAULT channel_nogetrates; diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index 48153350864b..2eba2c959e6c 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -58,6 +58,20 @@ struct cdevsw dsp_cdevsw = { static eventhandler_tag dsp_ehtag; #endif +static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group); +static int dsp_oss_syncstart(int sg_id); +static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy); +#ifdef OSSV4_EXPERIMENT +static int dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled); +static int dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); +static int dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); +static int dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); +static int dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); +static int dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); +static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); +static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name); +#endif + static struct snddev_info * dsp_get_info(struct cdev *dev) { @@ -300,7 +314,7 @@ dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct pcm_channel *rdch, *wrch; struct snddev_info *d; - int refs; + int refs, sg_ids[2]; d = dsp_get_info(i_dev); pcm_lock(d); @@ -308,9 +322,28 @@ dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td) wrch = i_dev->si_drv2; pcm_unlock(d); + /* + * Free_unr() may sleep, so store released syncgroup IDs until after + * all locks are released. + */ + sg_ids[0] = sg_ids[1] = 0; + if (rdch || wrch) { refs = 0; if (rdch) { + /* + * The channel itself need not be locked because: + * a) Adding a channel to a syncgroup happens only in dsp_ioctl(), + * which cannot run concurrently to dsp_close(). + * b) The syncmember pointer (sm) is protected by the global + * syncgroup list lock. + * c) A channel can't just disappear, invalidating pointers, + * unless it's closed/dereferenced first. + */ + PCM_SG_LOCK(); + sg_ids[0] = chn_syncdestroy(rdch); + PCM_SG_UNLOCK(); + CHN_LOCK(rdch); refs += pcm_chnref(rdch, -1); chn_abort(rdch); /* won't sleep */ @@ -319,6 +352,13 @@ dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td) pcm_chnrelease(rdch); } if (wrch) { + /* + * Please see block above. + */ + PCM_SG_LOCK(); + sg_ids[1] = chn_syncdestroy(wrch); + PCM_SG_UNLOCK(); + CHN_LOCK(wrch); refs += pcm_chnref(wrch, -1); /* @@ -351,6 +391,13 @@ dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td) } pcm_unlock(d); } + + + if (sg_ids[0]) + free_unr(pcmsg_unrhdr, sg_ids[0]); + if (sg_ids[1]) + free_unr(pcmsg_unrhdr, sg_ids[1]); + return 0; } @@ -394,7 +441,17 @@ dsp_write(struct cdev *i_dev, struct uio *buf, int flag) } if (!(wrch->flags & CHN_F_RUNNING)) wrch->flags |= CHN_F_RUNNING; + + /* + * Chn_write() must give up channel lock in order to copy bytes from + * userland, so up the "in progress" counter to make sure someone + * else doesn't come along and muss up the buffer. + */ + ++wrch->inprog; ret = chn_write(wrch, buf); + --wrch->inprog; + cv_signal(&wrch->cv); + relchns(i_dev, rdch, wrch, SD_F_PRIO_WR); return ret; @@ -407,6 +464,9 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread * struct snddev_info *d; int kill; int ret = 0, *arg_i = (int *)arg, tmp; + int xcmd; + + xcmd = 0; /* * this is an evil hack to allow broken apps to perform mixer ioctls @@ -424,6 +484,28 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread * return EBADF; } + /* + * Certain ioctls may be made on any type of device (audio, mixer, + * and MIDI). Handle those special cases here. + */ + if (IOCGROUP(cmd) == 'X') { + switch(cmd) { + case SNDCTL_SYSINFO: + sound_oss_sysinfo((oss_sysinfo *)arg); + break; + case SNDCTL_AUDIOINFO: + ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg); + break; + case SNDCTL_MIXERINFO: + ret = mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg); + break; + default: + ret = EINVAL; + } + + return ret; + } + getchns(i_dev, &rdch, &wrch, 0); kill = 0; @@ -1032,6 +1114,311 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread * dsp_set_flags(i_dev, dsp_get_flags(i_dev)^SD_F_SIMPLEX); break; + /* + * The following four ioctls are simple wrappers around mixer_ioctl + * with no further processing. xcmd is short for "translated + * command". + */ + case SNDCTL_DSP_GETRECVOL: + if (xcmd == 0) + xcmd = SOUND_MIXER_READ_RECLEV; + /* FALLTHROUGH */ + case SNDCTL_DSP_SETRECVOL: + if (xcmd == 0) + xcmd = SOUND_MIXER_WRITE_RECLEV; + /* FALLTHROUGH */ + case SNDCTL_DSP_GETPLAYVOL: + if (xcmd == 0) + xcmd = SOUND_MIXER_READ_PCM; + /* FALLTHROUGH */ + case SNDCTL_DSP_SETPLAYVOL: + if (xcmd == 0) + xcmd = SOUND_MIXER_WRITE_PCM; + + if (d->mixer_dev != NULL) + ret = mixer_ioctl(d->mixer_dev, xcmd, arg, -1, td); + else + ret = ENOTSUP; + break; + + case SNDCTL_DSP_GET_RECSRC_NAMES: + case SNDCTL_DSP_GET_RECSRC: + case SNDCTL_DSP_SET_RECSRC: + if (d->mixer_dev != NULL) + ret = mixer_ioctl(d->mixer_dev, cmd, arg, -1, td); + else + ret = ENOTSUP; + break; + + /* + * The following 3 ioctls aren't very useful at the moment. For + * now, only a single channel is associated with a cdev (/dev/dspN + * instance), so there's only a single output routing to use (i.e., + * the wrch bound to this cdev). + */ + case SNDCTL_DSP_GET_PLAYTGT_NAMES: + { + oss_mixer_enuminfo *ei; + ei = (oss_mixer_enuminfo *)arg; + ei->dev = 0; + ei->ctrl = 0; + ei->version = 0; /* static for now */ + ei->strindex[0] = 0; + + if (wrch != NULL) { + ei->nvalues = 1; + strlcpy(ei->strings, wrch->name, + sizeof(ei->strings)); + } else { + ei->nvalues = 0; + ei->strings[0] = '\0'; + } + } + break; + case SNDCTL_DSP_GET_PLAYTGT: + case SNDCTL_DSP_SET_PLAYTGT: /* yes, they are the same for now */ + /* + * Re: SET_PLAYTGT + * OSSv4: "The value that was accepted by the device will + * be returned back in the variable pointed by the + * argument." + */ + if (wrch != NULL) + *arg_i = 0; + else + ret = EINVAL; + break; + + case SNDCTL_DSP_SILENCE: + /* + * Flush the software (pre-feed) buffer, but try to minimize playback + * interruption. (I.e., record unplayed samples with intent to + * restore by SNDCTL_DSP_SKIP.) Intended for application "pause" + * functionality. + */ + if (wrch == NULL) + ret = EINVAL; + else { + struct snd_dbuf *bs; + CHN_LOCK(wrch); + while (wrch->inprog != 0) + cv_wait(&wrch->cv, wrch->lock); + bs = wrch->bufsoft; + if ((bs->shadbuf != NULL) && (sndbuf_getready(bs) > 0)) { + bs->sl = sndbuf_getready(bs); + sndbuf_dispose(bs, bs->shadbuf, sndbuf_getready(bs)); + sndbuf_fillsilence(bs); + chn_start(wrch, 0); + } + CHN_UNLOCK(wrch); + } + break; + + case SNDCTL_DSP_SKIP: + /* + * OSSv4 docs: "This ioctl call discards all unplayed samples in the + * playback buffer by moving the current write position immediately + * before the point where the device is currently reading the samples." + */ + if (wrch == NULL) + ret = EINVAL; + else { + struct snd_dbuf *bs; + CHN_LOCK(wrch); + while (wrch->inprog != 0) + cv_wait(&wrch->cv, wrch->lock); + bs = wrch->bufsoft; + if ((bs->shadbuf != NULL) && (bs->sl > 0)) { + sndbuf_softreset(bs); + sndbuf_acquire(bs, bs->shadbuf, bs->sl); + bs->sl = 0; + chn_start(wrch, 0); + } + CHN_UNLOCK(wrch); + } + break; + + case SNDCTL_DSP_CURRENT_OPTR: + case SNDCTL_DSP_CURRENT_IPTR: + /** + * @note Changing formats resets the buffer counters, which differs + * from the 4Front drivers. However, I don't expect this to be + * much of a problem. + * + * @note In a test where @c CURRENT_OPTR is called immediately after write + * returns, this driver is about 32K samples behind whereas + * 4Front's is about 8K samples behind. Should determine source + * of discrepancy, even if only out of curiosity. + * + * @todo Actually test SNDCTL_DSP_CURRENT_IPTR. + */ + chn = (cmd == SNDCTL_DSP_CURRENT_OPTR) ? wrch : rdch; + if (chn == NULL) + ret = EINVAL; + else { + struct snd_dbuf *bs; + /* int tmp; */ + + oss_count_t *oc = (oss_count_t *)arg; + + CHN_LOCK(chn); + bs = chn->bufsoft; +#if 0 + tmp = (sndbuf_getsize(b) + chn_getptr(chn) - sndbuf_gethwptr(b)) % sndbuf_getsize(b); + oc->samples = (sndbuf_gettotal(b) + tmp) / sndbuf_getbps(b); + oc->fifo_samples = (sndbuf_getready(b) - tmp) / sndbuf_getbps(b); +#else + oc->samples = sndbuf_gettotal(bs) / sndbuf_getbps(bs); + oc->fifo_samples = sndbuf_getready(bs) / sndbuf_getbps(bs); +#endif + CHN_UNLOCK(chn); + } + break; + + case SNDCTL_DSP_HALT_OUTPUT: + case SNDCTL_DSP_HALT_INPUT: + chn = (cmd == SNDCTL_DSP_HALT_OUTPUT) ? wrch : rdch; + if (chn == NULL) + ret = EINVAL; + else { + CHN_LOCK(chn); + chn_abort(chn); + CHN_UNLOCK(chn); + } + break; + + case SNDCTL_DSP_LOW_WATER: + /* + * Set the number of bytes required to attract attention by + * select/poll. + */ + if (wrch != NULL) { + CHN_LOCK(wrch); + wrch->lw = (*arg_i > 1) ? *arg_i : 1; + CHN_UNLOCK(wrch); + } + if (rdch != NULL) { + CHN_LOCK(rdch); + rdch->lw = (*arg_i > 1) ? *arg_i : 1; + CHN_UNLOCK(rdch); + } + break; + + case SNDCTL_DSP_GETERROR: + /* + * OSSv4 docs: "All errors and counters will automatically be + * cleared to zeroes after the call so each call will return only + * the errors that occurred after the previous invocation. ... The + * play_underruns and rec_overrun fields are the only usefull fields + * returned by OSS 4.0." + */ + { + audio_errinfo *ei = (audio_errinfo *)arg; + + bzero((void *)ei, sizeof(*ei)); + + if (wrch != NULL) { + CHN_LOCK(wrch); + ei->play_underruns = wrch->xruns; + wrch->xruns = 0; + CHN_UNLOCK(wrch); + } + if (rdch != NULL) { + CHN_LOCK(rdch); + ei->rec_overruns = rdch->xruns; + rdch->xruns = 0; + CHN_UNLOCK(rdch); + } + } + break; + + case SNDCTL_DSP_SYNCGROUP: + ret = dsp_oss_syncgroup(wrch, rdch, (oss_syncgroup *)arg); + break; + + case SNDCTL_DSP_SYNCSTART: + ret = dsp_oss_syncstart(*arg_i); + break; + + case SNDCTL_DSP_POLICY: + ret = dsp_oss_policy(wrch, rdch, *arg_i); + break; + +#ifdef OSSV4_EXPERIMENT + /* + * XXX The following ioctls are not yet supported and just return + * EINVAL. + */ + case SNDCTL_DSP_GETOPEAKS: + case SNDCTL_DSP_GETIPEAKS: + chn = (cmd == SNDCTL_DSP_GETOPEAKS) ? wrch : rdch; + if (chn == NULL) + ret = EINVAL; + else { + oss_peaks_t *op = (oss_peaks_t *)arg; + int lpeak, rpeak; + + CHN_LOCK(chn); + ret = chn_getpeaks(chn, &lpeak, &rpeak); + if (ret == -1) + ret = EINVAL; + else { + (*op)[0] = lpeak; + (*op)[1] = rpeak; + } + CHN_UNLOCK(chn); + } + break; + + case SNDCTL_DSP_COOKEDMODE: + ret = dsp_oss_cookedmode(wrch, rdch, *arg_i); + break; + case SNDCTL_DSP_GET_CHNORDER: + ret = dsp_oss_getchnorder(wrch, rdch, (unsigned long long *)arg); + break; + case SNDCTL_DSP_SET_CHNORDER: + ret = dsp_oss_setchnorder(wrch, rdch, (unsigned long long *)arg); + break; + case SNDCTL_GETLABEL: + ret = dsp_oss_getlabel(wrch, rdch, (oss_label_t *)arg); + break; + case SNDCTL_SETLABEL: + ret = dsp_oss_setlabel(wrch, rdch, (oss_label_t *)arg); + break; + case SNDCTL_GETSONG: + ret = dsp_oss_getsong(wrch, rdch, (oss_longname_t *)arg); + break; + case SNDCTL_SETSONG: + ret = dsp_oss_setsong(wrch, rdch, (oss_longname_t *)arg); + break; + case SNDCTL_SETNAME: + ret = dsp_oss_setname(wrch, rdch, (oss_longname_t *)arg); + break; +#if 0 + /** + * @note The SNDCTL_CARDINFO ioctl was omitted per 4Front developer + * documentation. "The usability of this call is very limited. It's + * provided only for completeness of the API. OSS API doesn't have + * any concept of card. Any information returned by this ioctl calld + * is reserved exclusively for the utility programs included in the + * OSS package. Applications should not try to use for this + * information in any ways." + */ + case SNDCTL_CARDINFO: + ret = EINVAL; + break; + /** + * @note The S/PDIF interface ioctls, @c SNDCTL_DSP_READCTL and + * @c SNDCTL_DSP_WRITECTL have been omitted at the suggestion of + * 4Front Technologies. + */ + case SNDCTL_DSP_READCTL: + case SNDCTL_DSP_WRITECTL: + ret = EINVAL; + break; +#endif /* !0 (explicitly omitted ioctls) */ + +#endif /* !OSSV4_EXPERIMENT */ case SNDCTL_DSP_MAPINBUF: case SNDCTL_DSP_MAPOUTBUF: case SNDCTL_DSP_SETSYNCRO: @@ -1206,4 +1593,778 @@ SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL); SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL); #endif +/** + * @brief Handler for SNDCTL_AUDIOINFO. + * + * Gathers information about the audio device specified in ai->dev. If + * ai->dev == -1, then this function gathers information about the current + * device. If the call comes in on a non-audio device and ai->dev == -1, + * return EINVAL. + * + * This routine is supposed to go practically straight to the hardware, + * getting capabilities directly from the sound card driver, side-stepping + * the intermediate channel interface. + * + * Note, however, that the usefulness of this command is significantly + * decreased when requesting info about any device other than the one serving + * the request. While each snddev_channel refers to a specific device node, + * the converse is *not* true. Currently, when a sound device node is opened, + * the sound subsystem scans for an available audio channel (or channels, if + * opened in read+write) and then assigns them to the si_drv[12] private + * data fields. As a result, any information returned linking a channel to + * a specific character device isn't necessarily accurate. + * + * @note + * Calling threads must not hold any snddev_info or pcm_channel locks. + * + * @param dev device on which the ioctl was issued + * @param ai ioctl request data container + * + * @retval 0 success + * @retval EINVAL ai->dev specifies an invalid device + * + * @todo Verify correctness of Doxygen tags. ;) + */ +int +dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) +{ + struct snddev_channel *sce; + struct pcmchan_caps *caps; + struct pcm_channel *ch; + struct snddev_info *d; + struct cdev *t_cdev; + uint32_t fmts; + int i, nchan, ret, *rates, minch, maxch; + /* + * If probing the device that received the ioctl, make sure it's a + * DSP device. (Users may use this ioctl with /dev/mixer and + * /dev/midi.) + */ + if ((ai->dev == -1) && (i_dev->si_devsw != &dsp_cdevsw)) + return EINVAL; + + ch = NULL; + t_cdev = NULL; + nchan = 0; + ret = 0; + + /* + * Search for the requested audio device (channel). Start by + * iterating over pcm devices. + */ + for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (d == NULL) + continue; + + /* See the note in function docblock */ + mtx_assert(d->lock, MA_NOTOWNED); + pcm_inprog(d, 1); + pcm_lock(d); + + SLIST_FOREACH(sce, &d->channels, link) { + ch = sce->channel; + mtx_assert(ch->lock, MA_NOTOWNED); + CHN_LOCK(ch); + if (ai->dev == -1) { + if ((ch == i_dev->si_drv1) || /* record ch */ + (ch == i_dev->si_drv2)) { /* playback ch */ + t_cdev = i_dev; + goto dspfound; + } + } else if (ai->dev == nchan) { + t_cdev = sce->dsp_devt; + goto dspfound; + } + CHN_UNLOCK(ch); + ++nchan; + } + + pcm_unlock(d); + pcm_inprog(d, -1); + } + + /* Exhausted the search -- nothing is locked, so return. */ + return EINVAL; + +dspfound: + /* Should've found the device, but something isn't right */ + if (t_cdev == NULL) { + ret = EINVAL; + goto out; + } + + /* + * At this point, the following synchronization stuff has happened: + * - a specific PCM device is locked and its "in progress + * operations" counter has been incremented, so be sure to unlock + * and decrement when exiting; + * - a specific audio channel has been locked, so be sure to unlock + * when exiting; + */ + + caps = chn_getcaps(ch); + + /* + * With all handles collected, zero out the user's container and + * begin filling in its fields. + */ + bzero((void *)ai, sizeof(oss_audioinfo)); + + ai->dev = nchan; + strlcpy(ai->name, ch->name, sizeof(ai->name)); + + if ((ch->flags & CHN_F_BUSY) == 0) + ai->busy = 0; + else + ai->busy = (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ; + + /** + * @note + * @c cmd - OSSv4 docs: "Only supported under Linux at this moment." + * Cop-out, I know, but I'll save running around in the process + * table for later. Is there a risk of leaking information? + */ + ai->pid = ch->pid; + + /* + * These flags stolen from SNDCTL_DSP_GETCAPS handler. Note, however, + * that a single channel operates in only one direction, so + * DSP_CAP_DUPLEX is out. + */ + /** + * @todo @c SNDCTL_AUDIOINFO::caps - Make drivers keep these in + * pcmchan::caps? + */ + ai->caps = DSP_CAP_REALTIME | DSP_CAP_MMAP | DSP_CAP_TRIGGER; + + /* + * Collect formats supported @b natively by the device. Also + * determine min/max channels. (I.e., mono, stereo, or both?) + * + * If any channel is stereo, maxch = 2; + * if all channels are stereo, minch = 2, too; + * if any channel is mono, minch = 1; + * and if all channels are mono, maxch = 1. + */ + minch = 0; + maxch = 0; + fmts = 0; + for (i = 0; caps->fmtlist[i]; i++) { + fmts |= caps->fmtlist[i]; + if (caps->fmtlist[i] & AFMT_STEREO) { + minch = (minch == 0) ? 2 : minch; + maxch = 2; + } else { + minch = 1; + maxch = (maxch == 0) ? 1 : maxch; + } + } + + if (ch->direction == PCMDIR_PLAY) + ai->oformats = fmts; + else + ai->iformats = fmts; + + /** + * @note + * @c magic - OSSv4 docs: "Reserved for internal use by OSS." + * + * @par + * @c card_number - OSSv4 docs: "Number of the sound card where this + * device belongs or -1 if this information is not available. + * Applications should normally not use this field for any + * purpose." + */ + ai->card_number = -1; + /** + * @todo @c song_name - depends first on SNDCTL_[GS]ETSONG + * @todo @c label - depends on SNDCTL_[GS]ETLABEL + * @todo @c port_number - routing information? + */ + ai->port_number = -1; + ai->mixer_dev = (d->mixer_dev != NULL) ? PCMUNIT(d->mixer_dev) : -1; + /** + * @note + * @c real_device - OSSv4 docs: "Obsolete." + */ + ai->real_device = -1; + strlcpy(ai->devnode, t_cdev->si_name, sizeof(ai->devnode)); + ai->enabled = device_is_attached(d->dev) ? 1 : 0; + /** + * @note + * @c flags - OSSv4 docs: "Reserved for future use." + * + * @note + * @c binding - OSSv4 docs: "Reserved for future use." + * + * @todo @c handle - haven't decided how to generate this yet; bus, + * vendor, device IDs? + */ + ai->min_rate = caps->minspeed; + ai->max_rate = caps->maxspeed; + + ai->min_channels = minch; + ai->max_channels = maxch; + + ai->nrates = chn_getrates(ch, &rates); + if (ai->nrates > MAX_SAMPLE_RATES) + ai->nrates = MAX_SAMPLE_RATES; + + for (i = 0; i < ai->nrates; i++) + ai->rates[i] = rates[i]; + +out: + CHN_UNLOCK(ch); + pcm_unlock(d); + pcm_inprog(d, -1); + + return ret; +} + +/** + * @brief Assigns a PCM channel to a sync group. + * + * Sync groups are used to enable audio operations on multiple devices + * simultaneously. They may be used with any number of devices and may + * span across applications. Devices are added to groups with + * the SNDCTL_DSP_SYNCGROUP ioctl, and operations are triggered with the + * SNDCTL_DSP_SYNCSTART ioctl. + * + * If the @c id field of the @c group parameter is set to zero, then a new + * sync group is created. Otherwise, wrch and rdch (if set) are added to + * the group specified. + * + * @todo As far as memory allocation, should we assume that things are + * okay and allocate with M_WAITOK before acquiring channel locks, + * freeing later if not? + * + * @param wrch output channel associated w/ device (if any) + * @param rdch input channel associated w/ device (if any) + * @param group Sync group parameters + * + * @retval 0 success + * @retval non-zero error to be propagated upstream + */ +static int +dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group) +{ + struct pcmchan_syncmember *smrd, *smwr; + struct pcmchan_syncgroup *sg; + int ret, sg_ids[3]; + + smrd = NULL; + smwr = NULL; + sg = NULL; + ret = 0; + + /* + * Free_unr() may sleep, so store released syncgroup IDs until after + * all locks are released. + */ + sg_ids[0] = sg_ids[1] = sg_ids[2] = 0; + + PCM_SG_LOCK(); + + /* + * - Insert channel(s) into group's member list. + * - Set CHN_F_NOTRIGGER on channel(s). + * - Stop channel(s). + */ + + /* + * If device's channels are already mapped to a group, unmap them. + */ + if (wrch) { + CHN_LOCK(wrch); + sg_ids[0] = chn_syncdestroy(wrch); + } + + if (rdch) { + CHN_LOCK(rdch); + sg_ids[1] = chn_syncdestroy(rdch); + } + + /* + * Verify that mode matches character device properites. + * - Bail if PCM_ENABLE_OUTPUT && wrch == NULL. + * - Bail if PCM_ENABLE_INPUT && rdch == NULL. + */ + if (((wrch == NULL) && (group->mode & PCM_ENABLE_OUTPUT)) || + ((rdch == NULL) && (group->mode & PCM_ENABLE_INPUT))) { + ret = EINVAL; + goto out; + } + + /* + * An id of zero indicates the user wants to create a new + * syncgroup. + */ + if (group->id == 0) { + sg = (struct pcmchan_syncgroup *)malloc(sizeof(*sg), M_DEVBUF, M_NOWAIT); + if (sg != NULL) { + SLIST_INIT(&sg->members); + sg->id = alloc_unr(pcmsg_unrhdr); + + group->id = sg->id; + SLIST_INSERT_HEAD(&snd_pcm_syncgroups, sg, link); + } else + ret = ENOMEM; + } else { + SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { + if (sg->id == group->id) + break; + } + if (sg == NULL) + ret = EINVAL; + } + + /* Couldn't create or find a syncgroup. Fail. */ + if (sg == NULL) + goto out; + + /* + * Allocate a syncmember, assign it and a channel together, and + * insert into syncgroup. + */ + if (group->mode & PCM_ENABLE_INPUT) { + smrd = (struct pcmchan_syncmember *)malloc(sizeof(*smrd), M_DEVBUF, M_NOWAIT); + if (smrd == NULL) { + ret = ENOMEM; + goto out; + } + + SLIST_INSERT_HEAD(&sg->members, smrd, link); + smrd->parent = sg; + smrd->ch = rdch; + + chn_abort(rdch); + rdch->flags |= CHN_F_NOTRIGGER; + rdch->sm = smrd; + } + + if (group->mode & PCM_ENABLE_OUTPUT) { + smwr = (struct pcmchan_syncmember *)malloc(sizeof(*smwr), M_DEVBUF, M_NOWAIT); + if (smwr == NULL) { + ret = ENOMEM; + goto out; + } + + SLIST_INSERT_HEAD(&sg->members, smwr, link); + smwr->parent = sg; + smwr->ch = wrch; + + chn_abort(wrch); + wrch->flags |= CHN_F_NOTRIGGER; + wrch->sm = smwr; + } + + +out: + if (ret != 0) { + if (smrd != NULL) + free(smrd, M_DEVBUF); + if (smwr != NULL) + free(smwr, M_DEVBUF); + if ((sg != NULL) && SLIST_EMPTY(&sg->members)) { + sg_ids[2] = sg->id; + SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); + free(sg, M_DEVBUF); + } + + if (wrch) + wrch->sm = NULL; + if (rdch) + rdch->sm = NULL; + } + + if (wrch) + CHN_UNLOCK(wrch); + if (rdch) + CHN_UNLOCK(rdch); + + PCM_SG_UNLOCK(); + + if (sg_ids[0]) + free_unr(pcmsg_unrhdr, sg_ids[0]); + if (sg_ids[1]) + free_unr(pcmsg_unrhdr, sg_ids[1]); + if (sg_ids[2]) + free_unr(pcmsg_unrhdr, sg_ids[2]); + + return ret; +} + +/** + * @brief Launch a sync group into action + * + * Sync groups are established via SNDCTL_DSP_SYNCGROUP. This function + * iterates over all members, triggering them along the way. + * + * @note Caller must not hold any channel locks. + * + * @param sg_id sync group identifier + * + * @retval 0 success + * @retval non-zero error worthy of propagating upstream to user + */ +static int +dsp_oss_syncstart(int sg_id) +{ + struct pcmchan_syncmember *sm, *sm_tmp; + struct pcmchan_syncgroup *sg; + struct pcm_channel *c; + int ret, needlocks; + + /* Get the synclists lock */ + PCM_SG_LOCK(); + + do { + ret = 0; + needlocks = 0; + + /* Search for syncgroup by ID */ + SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { + if (sg->id == sg_id) + break; + } + + /* Return EINVAL if not found */ + if (sg == NULL) { + ret = EINVAL; + break; + } + + /* Any removals resulting in an empty group should've handled this */ + KASSERT(!SLIST_EMPTY(&sg->members), ("found empty syncgroup")); + + /* + * Attempt to lock all member channels - if any are already + * locked, unlock those acquired, sleep for a bit, and try + * again. + */ + SLIST_FOREACH(sm, &sg->members, link) { + if (CHN_TRYLOCK(sm->ch) == 0) { + int timo = hz * 5/1000; + if (timo < 1) + timo = 1; + + /* Release all locked channels so far, retry */ + SLIST_FOREACH(sm_tmp, &sg->members, link) { + /* sm is the member already locked */ + if (sm == sm_tmp) + break; + CHN_UNLOCK(sm_tmp->ch); + } + + /** @todo Is PRIBIO correct/ */ + ret = msleep(sm, &snd_pcm_syncgroups_mtx, PRIBIO | PCATCH, "pcmsgrp", timo); + if (ret == EINTR || ret == ERESTART) + break; + + needlocks = 1; + ret = 0; /* Assumes ret == EWOULDBLOCK... */ + } + } + } while (needlocks && ret == 0); + + /* Proceed only if no errors encountered. */ + if (ret == 0) { + /* Launch channels */ + while((sm = SLIST_FIRST(&sg->members)) != NULL) { + SLIST_REMOVE_HEAD(&sg->members, link); + + c = sm->ch; + c->sm = NULL; + chn_start(c, 1); + c->flags &= ~CHN_F_NOTRIGGER; + CHN_UNLOCK(c); + + free(sm, M_DEVBUF); + } + + SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); + free(sg, M_DEVBUF); + } + + PCM_SG_UNLOCK(); + + /* + * Free_unr() may sleep, so be sure to give up the syncgroup lock + * first. + */ + if (ret == 0) + free_unr(pcmsg_unrhdr, sg_id); + + return ret; +} + +/** + * @brief Handler for SNDCTL_DSP_POLICY + * + * The SNDCTL_DSP_POLICY ioctl is a simpler interface to control fragment + * size and count like with SNDCTL_DSP_SETFRAGMENT. Instead of the user + * specifying those two parameters, s/he simply selects a number from 0..10 + * which corresponds to a buffer size. Smaller numbers request smaller + * buffers with lower latencies (at greater overhead from more frequent + * interrupts), while greater numbers behave in the opposite manner. + * + * The 4Front spec states that a value of 5 should be the default. However, + * this implementation deviates slightly by using a linear scale without + * consulting drivers. I.e., even though drivers may have different default + * buffer sizes, a policy argument of 5 will have the same result across + * all drivers. + * + * See http://manuals.opensound.com/developer/SNDCTL_DSP_POLICY.html for + * more information. + * + * @todo When SNDCTL_DSP_COOKEDMODE is supported, it'll be necessary to + * work with hardware drivers directly. + * + * @note PCM channel arguments must not be locked by caller. + * + * @param wrch Pointer to opened playback channel (optional; may be NULL) + * @param rdch " recording channel (optional; may be NULL) + * @param policy Integer from [0:10] + * + * @retval 0 constant (for now) + */ +static int +dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy) +{ + int fragln, fragsz, maxfrags, ret; + + /* Default: success */ + ret = 0; + + /* Scale policy [0..10] to fragment size [2^4..2^16]. */ + fragln = policy; + RANGE(fragln, 0, 10); + fragln += 4; + fragsz = 1 << fragln; + + maxfrags = CHN_2NDBUFMAXSIZE / fragsz; + + if (rdch) { + CHN_LOCK(rdch); + ret = chn_setblocksize(rdch, maxfrags, fragsz); + CHN_UNLOCK(rdch); + } + + if (wrch && ret == 0) { + CHN_LOCK(wrch); + ret = chn_setblocksize(wrch, maxfrags, fragsz); + CHN_UNLOCK(wrch); + } + + return ret; +} + +#ifdef OSSV4_EXPERIMENT +/** + * @brief Enable or disable "cooked" mode + * + * This is a handler for @c SNDCTL_DSP_COOKEDMODE. When in cooked mode, which + * is the default, the sound system handles rate and format conversions + * automatically (ex: user writing 11025Hz/8 bit/unsigned but card only + * operates with 44100Hz/16bit/signed samples). + * + * Disabling cooked mode is intended for applications wanting to mmap() + * a sound card's buffer space directly, bypassing the FreeBSD 2-stage + * feeder architecture, presumably to gain as much control over audio + * hardware as possible. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_COOKEDMODE.html + * for more details. + * + * @note Currently, this function is just a stub that always returns EINVAL. + * + * @todo Figure out how to and actually implement this. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param enabled 0 = raw mode, 1 = cooked mode + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled) +{ + return EINVAL; +} + +/** + * @brief Retrieve channel interleaving order + * + * This is the handler for @c SNDCTL_DSP_GET_CHNORDER. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_GET_CHNORDER.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_DSP_GET_CHNORDER. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param map channel map (result will be stored there) + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) +{ + return EINVAL; +} + +/** + * @brief Specify channel interleaving order + * + * This is the handler for @c SNDCTL_DSP_SET_CHNORDER. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support @c SNDCTL_DSP_SET_CHNORDER. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param map channel map + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) +{ + return EINVAL; +} + +/** + * @brief Retrieve an audio device's label + * + * This is a handler for the @c SNDCTL_GETLABEL ioctl. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html + * for more details. + * + * From Hannu@4Front: "For example ossxmix (just like some HW mixer + * consoles) can show variable "labels" for certain controls. By default + * the application name (say quake) is shown as the label but + * applications may change the labels themselves." + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support @c SNDCTL_GETLABEL. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param label label gets copied here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) +{ + return EINVAL; +} + +/** + * @brief Specify an audio device's label + * + * This is a handler for the @c SNDCTL_SETLABEL ioctl. Please see the + * comments for @c dsp_oss_getlabel immediately above. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_SETLABEL. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param label label gets copied from here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) +{ + return EINVAL; +} + +/** + * @brief Retrieve name of currently played song + * + * This is a handler for the @c SNDCTL_GETSONG ioctl. Audio players could + * tell the system the name of the currently playing song, which would be + * visible in @c /dev/sndstat. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_GETSONG.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_GETSONG. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param song song name gets copied here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) +{ + return EINVAL; +} + +/** + * @brief Retrieve name of currently played song + * + * This is a handler for the @c SNDCTL_SETSONG ioctl. Audio players could + * tell the system the name of the currently playing song, which would be + * visible in @c /dev/sndstat. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_SETSONG.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_SETSONG. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param song song name gets copied from here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) +{ + return EINVAL; +} + +/** + * @brief Rename a device + * + * This is a handler for the @c SNDCTL_SETNAME ioctl. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_SETNAME.html for + * more details. + * + * From Hannu@4Front: "This call is used to change the device name + * reported in /dev/sndstat and ossinfo. So instead of using some generic + * 'OSS loopback audio (MIDI) driver' the device may be given a meaningfull + * name depending on the current context (for example 'OSS virtual wave table + * synth' or 'VoIP link to London')." + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_SETNAME. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param name new device name gets copied from here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name) +{ + return EINVAL; +} +#endif /* !OSSV4_EXPERIMENT */ diff --git a/sys/dev/sound/pcm/dsp.h b/sys/dev/sound/pcm/dsp.h index 0d067ededb1a..d56333b0f88a 100644 --- a/sys/dev/sound/pcm/dsp.h +++ b/sys/dev/sound/pcm/dsp.h @@ -1,3 +1,5 @@ +#ifndef _PCMDSP_H_ +#define _PCMDSP_H_ /*- * Copyright (c) 1999 Cameron Grant * All rights reserved. @@ -27,3 +29,7 @@ */ extern struct cdevsw dsp_cdevsw; + +int dsp_oss_audioinfo(struct cdev *, oss_audioinfo *); + +#endif /* !_PCMDSP_H_ */ diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c index 8e4ab84691d1..0e7b65af1cbc 100644 --- a/sys/dev/sound/pcm/mixer.c +++ b/sys/dev/sound/pcm/mixer.c @@ -49,6 +49,13 @@ struct snd_mixer { u_int16_t level[32]; char name[MIXER_NAMELEN]; struct mtx *lock; + oss_mixer_enuminfo enuminfo; + /** + * Counter is incremented when applications change any of this + * mixer's controls. A change in value indicates that persistent + * mixer applications should update their displays. + */ + int modify_counter; }; static u_int16_t snd_mixerdefaults[SOUND_MIXER_NRDEVICES] = { @@ -83,6 +90,11 @@ static struct cdevsw mixer_cdevsw = { .d_name = "mixer", }; +/** + * Keeps a count of mixer devices; used only by OSSv4 SNDCTL_SYSINFO ioctl. + */ +int mixer_count = 0; + #ifdef USING_DEVFS static eventhandler_tag mixer_ehtag; #endif @@ -181,6 +193,83 @@ mixer_getrecsrc(struct snd_mixer *mixer) return mixer->recsrc; } +/** + * @brief Retrieve the route number of the current recording device + * + * OSSv4 assigns routing numbers to recording devices, unlike the previous + * API which relied on a fixed table of device numbers and names. This + * function returns the routing number of the device currently selected + * for recording. + * + * For now, this function is kind of a goofy compatibility stub atop the + * existing sound system. (For example, in theory, the old sound system + * allows multiple recording devices to be specified via a bitmask.) + * + * @param m mixer context container thing + * + * @retval 0 success + * @retval EIDRM no recording device found (generally not possible) + * @todo Ask about error code + */ +static int +mixer_get_recroute(struct snd_mixer *m, int *route) +{ + int i, cnt; + + cnt = 0; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + /** @todo can user set a multi-device mask? (== or &?) */ + if ((1 << i) == m->recsrc) + break; + if ((1 << i) & m->recdevs) + ++cnt; + } + + if (i == SOUND_MIXER_NRDEVICES) + return EIDRM; + + *route = cnt; + return 0; +} + +/** + * @brief Select a device for recording + * + * This function sets a recording source based on a recording device's + * routing number. Said number is translated to an old school recdev + * mask and passed over mixer_setrecsrc. + * + * @param m mixer context container thing + * + * @retval 0 success(?) + * @retval EINVAL User specified an invalid device number + * @retval otherwise error from mixer_setrecsrc + */ +static int +mixer_set_recroute(struct snd_mixer *m, int route) +{ + int i, cnt, ret; + + ret = 0; + cnt = 0; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if ((1 << i) & m->recdevs) { + if (route == cnt) + break; + ++cnt; + } + } + + if (i == SOUND_MIXER_NRDEVICES) + ret = EINVAL; + else + ret = mixer_setrecsrc(m, (1 << i)); + + return ret; +} + void mix_setdevs(struct snd_mixer *m, u_int32_t v) { @@ -190,9 +279,71 @@ mix_setdevs(struct snd_mixer *m, u_int32_t v) m->devs = v; } +/** + * @brief Record mask of available recording devices + * + * Calling functions are responsible for defining the mask of available + * recording devices. This function records that value in a structure + * used by the rest of the mixer code. + * + * This function also populates a structure used by the SNDCTL_DSP_*RECSRC* + * family of ioctls that are part of OSSV4. All recording device labels + * are concatenated in ascending order corresponding to their routing + * numbers. (Ex: a system might have 0 => 'vol', 1 => 'cd', 2 => 'line', + * etc.) For now, these labels are just the standard recording device + * names (cd, line1, etc.), but will eventually be fully dynamic and user + * controlled. + * + * @param m mixer device context container thing + * @param v mask of recording devices + */ void mix_setrecdevs(struct snd_mixer *m, u_int32_t v) { + oss_mixer_enuminfo *ei; + char *loc; + int i, nvalues, nwrote, nleft, ncopied; + + ei = &m->enuminfo; + + nvalues = 0; + nwrote = 0; + nleft = sizeof(ei->strings); + loc = ei->strings; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if ((1 << i) & v) { + ei->strindex[nvalues] = nwrote; + ncopied = strlcpy(loc, snd_mixernames[i], nleft) + 1; + /* strlcpy retval doesn't include terminator */ + + nwrote += ncopied; + nleft -= ncopied; + nvalues++; + + /* + * XXX I don't think this should ever be possible. + * Even with a move to dynamic device/channel names, + * each label is limited to ~16 characters, so that'd + * take a LOT to fill this buffer. + */ + if ((nleft <= 0) || (nvalues >= OSS_ENUM_MAXVALUE)) { + device_printf(m->dev, + "mix_setrecdevs: Not enough room to store device names--please file a bug report.\n"); + device_printf(m->dev, + "mix_setrecdevs: Please include details about your sound hardware, OS version, etc.\n"); + break; + } + + loc = &ei->strings[nwrote]; + } + } + + /* + * NB: The SNDCTL_DSP_GET_RECSRC_NAMES ioctl ignores the dev + * and ctrl fields. + */ + ei->nvalues = nvalues; m->recdevs = v; } @@ -256,6 +407,8 @@ mixer_init(device_t dev, kobj_class_t cls, void *devinfo) snddev = device_get_softc(dev); snddev->mixer_dev = pdev; + ++mixer_count; + return 0; bad: @@ -300,6 +453,8 @@ mixer_uninit(device_t dev) d->mixer_dev = NULL; + --mixer_count; + return 0; } @@ -481,6 +636,11 @@ mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread return EBADF; } + if (cmd == SNDCTL_MIXERINFO) { + snd_mtxunlock(m->lock); + return mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg); + } + if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { if (j == SOUND_MIXER_RECSRC) ret = mixer_setrecsrc(m, *arg_i); @@ -513,8 +673,32 @@ mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread snd_mtxunlock(m->lock); return (v != -1)? 0 : ENXIO; } + + ret = 0; + + switch (cmd) { + /** @todo Double check return values, error codes. */ + case SNDCTL_SYSINFO: + sound_oss_sysinfo((oss_sysinfo *)arg); + break; + case SNDCTL_AUDIOINFO: + ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg); + break; + case SNDCTL_DSP_GET_RECSRC_NAMES: + bcopy((void *)&m->enuminfo, arg, sizeof(oss_mixer_enuminfo)); + break; + case SNDCTL_DSP_GET_RECSRC: + ret = mixer_get_recroute(m, arg_i); + break; + case SNDCTL_DSP_SET_RECSRC: + ret = mixer_set_recroute(m, *arg_i); + break; + default: + ret = ENXIO; + } + snd_mtxunlock(m->lock); - return ENXIO; + return ret; } #ifdef USING_DEVFS @@ -552,4 +736,154 @@ SYSINIT(mixer_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysinit, NULL); SYSUNINIT(mixer_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysuninit, NULL); #endif +/** + * @brief Handler for SNDCTL_MIXERINFO + * + * This function searches for a mixer based on the numeric ID stored + * in oss_miserinfo::dev. If set to -1, then information about the + * current mixer handling the request is provided. Note, however, that + * this ioctl may be made with any sound device (audio, mixer, midi). + * + * @note Caller must not hold any PCM device, channel, or mixer locks. + * + * See http://manuals.opensound.com/developer/SNDCTL_MIXERINFO.html for + * more information. + * + * @param i_dev character device on which the ioctl arrived + * @param arg user argument (oss_mixerinfo *) + * + * @retval EINVAL oss_mixerinfo::dev specified a bad value + * @retval 0 success + */ +int +mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi) +{ + struct snddev_info *d; + struct snd_mixer *m; + struct cdev *t_cdev; + int nmix, ret, pcmunit, i; + /* + * If probing the device handling the ioctl, make sure it's a mixer + * device. (This ioctl is valid on audio, mixer, and midi devices.) + */ + if ((mi->dev == -1) && (i_dev->si_devsw != &mixer_cdevsw)) + return EINVAL; + + m = NULL; + t_cdev = NULL; + nmix = 0; + ret = 0; + pcmunit = -1; /* pcmX */ + + /* + * There's a 1:1 relationship between mixers and PCM devices, so + * begin by iterating over PCM devices and search for our mixer. + */ + for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (d == NULL) + continue; + + /* See the note in function docblock. */ + mtx_assert(d->lock, MA_NOTOWNED); + pcm_inprog(d, 1); + pcm_lock(d); + + if (d->mixer_dev != NULL) { + if (((mi->dev == -1) && (d->mixer_dev == i_dev)) || (mi->dev == nmix)) { + t_cdev = d->mixer_dev; + pcmunit = i; + break; + } + ++nmix; + } + + pcm_unlock(d); + pcm_inprog(d, -1); + } + + /* + * If t_cdev is NULL, then search was exhausted and device wasn't + * found. No locks are held, so just return. + */ + if (t_cdev == NULL) + return EINVAL; + + m = t_cdev->si_drv1; + mtx_lock(m->lock); + + /* + * At this point, the following synchronization stuff has happened: + * - a specific PCM device is locked and its "in progress + * operations" counter has been incremented, so be sure to unlock + * and decrement when exiting; + * - a specific mixer device has been locked, so be sure to unlock + * when existing. + */ + + bzero((void *)mi, sizeof(*mi)); + + mi->dev = nmix; + snprintf(mi->id, sizeof(mi->id), "mixer%d", dev2unit(t_cdev)); + strlcpy(mi->name, m->name, sizeof(mi->name)); + mi->modify_counter = m->modify_counter; + mi->card_number = pcmunit; + /* + * Currently, FreeBSD assumes 1:1 relationship between a pcm and + * mixer devices, so this is hardcoded to 0. + */ + mi->port_number = 0; + + /** + * @todo Fill in @sa oss_mixerinfo::mixerhandle. + * @note From 4Front: "mixerhandle is an arbitrary string that + * identifies the mixer better than the device number + * (mixerinfo.dev). Device numbers may change depending on + * the order the drivers are loaded. However the handle + * should remain the same provided that the sound card is + * not moved to another PCI slot." + */ + + /** + * @note + * @sa oss_mixerinfo::magic is a reserved field. + * + * @par + * From 4Front: "magic is usually 0. However some devices may have + * dedicated setup utilities and the magic field may contain an + * unique driver specific value (managed by [4Front])." + */ + + mi->enabled = device_is_attached(m->dev) ? 1 : 0; + /** + * The only flag for @sa oss_mixerinfo::caps is currently + * MIXER_CAP_VIRTUAL, which I'm not sure we really worry about. + */ + /** + * Mixer extensions currently aren't supported, so leave + * @sa oss_mixerinfo::nrext blank for now. + */ + /** + * @todo Fill in @sa oss_mixerinfo::priority (requires touching + * drivers?) + * @note The priority field is for mixer applets to determine which + * mixer should be the default, with 0 being least preferred and 10 + * being most preferred. From 4Front: "OSS drivers like ICH use + * higher values (10) because such chips are known to be used only + * on motherboards. Drivers for high end pro devices use 0 because + * they will never be the default mixer. Other devices use values 1 + * to 9 depending on the estimated probability of being the default + * device. + * + * XXX Described by Hannu@4Front, but not found in soundcard.h. + strlcpy(mi->devnode, t_cdev->si_name, sizeof(mi->devnode)); + mi->legacy_device = pcmunit; + */ + + mtx_unlock(m->lock); + pcm_unlock(d); + pcm_inprog(d, -1); + + return ret; +} diff --git a/sys/dev/sound/pcm/mixer.h b/sys/dev/sound/pcm/mixer.h index f4be41f0a325..f8e79c98c828 100644 --- a/sys/dev/sound/pcm/mixer.h +++ b/sys/dev/sound/pcm/mixer.h @@ -30,6 +30,7 @@ int mixer_init(device_t dev, kobj_class_t cls, void *devinfo); int mixer_uninit(device_t dev); int mixer_reinit(device_t dev); int mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td); +int mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi); int mixer_hwvol_init(device_t dev); void mixer_hwvol_mute(device_t dev); @@ -41,10 +42,17 @@ u_int32_t mix_getdevs(struct snd_mixer *m); u_int32_t mix_getrecdevs(struct snd_mixer *m); void *mix_getdevinfo(struct snd_mixer *m); +extern int mixer_count; + /* * this is a kludge to allow hiding of the struct snd_mixer definition * 512 should be enough for all architectures */ -#define MIXER_SIZE (512 + sizeof(struct kobj)) +#ifdef OSSV4_EXPERIMENT +# define MIXER_SIZE (512 + sizeof(struct kobj) + \ + sizeof(oss_mixer_enuminfo)) +#else +# define MIXER_SIZE (512 + sizeof(struct kobj)) +#endif #define MIXER_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, MIXER_SIZE) diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c index 6078758d3dbb..5ab8343e3625 100644 --- a/sys/dev/sound/pcm/sound.c +++ b/sys/dev/sound/pcm/sound.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include "feeder_if.h" @@ -51,6 +52,11 @@ TUNABLE_INT("hw.snd.maxautovchans", &snd_maxautovchans); SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver"); +/** + * @brief Unit number allocator for syncgroup IDs + */ +struct unrhdr *pcmsg_unrhdr = NULL; + static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose); struct sysctl_ctx_list * @@ -1126,13 +1132,143 @@ sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) /************************************************************************/ +/** + * @brief Handle OSSv4 SNDCTL_SYSINFO ioctl. + * + * @param si Pointer to oss_sysinfo struct where information about the + * sound subsystem will be written/copied. + * + * This routine returns information about the sound system, such as the + * current OSS version, number of audio, MIDI, and mixer drivers, etc. + * Also includes a bitmask showing which of the above types of devices + * are open (busy). + * + * @note + * Calling threads must not hold any snddev_info or pcm_channel locks. + * + * @author Ryan Beasley + */ +void +sound_oss_sysinfo(oss_sysinfo *si) +{ + static char si_product[] = "FreeBSD native OSS ABI"; + static char si_version[] = __XSTRING(__FreeBSD_version); + static int intnbits = sizeof(int) * 8; /* Better suited as macro? + Must pester a C guru. */ + + struct snddev_channel *sce; + struct snddev_info *d; + struct pcm_channel *c; + int i, j, ncards; + + ncards = 0; + + strlcpy(si->product, si_product, sizeof(si->product)); + strlcpy(si->version, si_version, sizeof(si->version)); + si->versionnum = SOUND_VERSION; + + /* + * Iterate over PCM devices and their channels, gathering up data + * for the numaudios, ncards, and openedaudio fields. + */ + si->numaudios = 0; + bzero((void *)&si->openedaudio, sizeof(si->openedaudio)); + + if (pcm_devclass != NULL) { + j = 0; + + for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!d) + continue; + + /* See note in function's docblock */ + mtx_assert(d->lock, MA_NOTOWNED); + /* Increment device's "operations in progress" */ + pcm_inprog(d, 1); + pcm_lock(d); + + si->numaudios += d->devcount; + ++ncards; + + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + mtx_assert(c->lock, MA_NOTOWNED); + CHN_LOCK(c); + if (c->flags & CHN_F_BUSY) + si->openedaudio[j / intnbits] |= + (1 << (j % intnbits)); + CHN_UNLOCK(c); + j++; + } + + pcm_unlock(d); + pcm_inprog(d, -1); + } + } + + si->numsynths = 0; /* OSSv4 docs: this field is obsolete */ + /** + * @todo Collect num{midis,timers}. + * + * Need access to sound/midi/midi.c::midistat_lock in order + * to safely touch midi_devices and get a head count of, well, + * MIDI devices. midistat_lock is a global static (i.e., local to + * midi.c), but midi_devices is a regular global; should the mutex + * be publicized, or is there another way to get this information? + * + * NB: MIDI/sequencer stuff is currently on hold. + */ + si->nummidis = 0; + si->numtimers = 0; + si->nummixers = mixer_count; + si->numcards = ncards; + /* OSSv4 docs: Intended only for test apps; API doesn't + really have much of a concept of cards. Shouldn't be + used by applications. */ + + /** + * @todo Fill in "busy devices" fields. + * + * si->openedmidi = " MIDI devices + */ + bzero((void *)&si->openedmidi, sizeof(si->openedmidi)); + + /* + * Si->filler is a reserved array, but according to docs each + * element should be set to -1. + */ + for (i = 0; i < sizeof(si->filler)/sizeof(si->filler[0]); i++) + si->filler[i] = -1; +} + +/************************************************************************/ + static int sound_modevent(module_t mod, int type, void *data) { + int ret; #if 0 return (midi_modevent(mod, type, data)); #else - return 0; + ret = 0; + + switch(type) { + case MOD_LOAD: + pcmsg_unrhdr = new_unrhdr(1, INT_MAX, NULL); + break; + case MOD_UNLOAD: + case MOD_SHUTDOWN: + if (pcmsg_unrhdr != NULL) { + delete_unrhdr(pcmsg_unrhdr); + pcmsg_unrhdr = NULL; + } + break; + default: + ret = EOPNOTSUPP; + } + + return ret; #endif } diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index 045e1e57c0b8..936f7c203335 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -73,6 +73,7 @@ #if __FreeBSD_version > 500000 #include #include +#include #define USING_MUTEX #define USING_DEVFS @@ -92,6 +93,7 @@ struct snd_mixer; #include #include #include +#include #define PCM_SOFTC_SIZE 512 @@ -193,6 +195,7 @@ int fkchan_kill(struct pcm_channel *c); extern int pcm_veto_load; extern int snd_unit; extern devclass_t pcm_devclass; +extern struct unrhdr *pcmsg_unrhdr; /* * some macros for debugging purposes @@ -304,6 +307,8 @@ struct snddev_info { }; +void sound_oss_sysinfo(oss_sysinfo *); + #ifdef PCM_DEBUG_MTX #define pcm_lock(d) mtx_lock(((struct snddev_info *)(d))->lock) #define pcm_unlock(d) mtx_unlock(((struct snddev_info *)(d))->lock) diff --git a/sys/sys/param.h b/sys/sys/param.h index e0afaf7a7670..bd3e6a7818bd 100644 --- a/sys/sys/param.h +++ b/sys/sys/param.h @@ -57,7 +57,7 @@ * is created, otherwise 1. */ #undef __FreeBSD_version -#define __FreeBSD_version 700022 /* Master, propagated to newvers */ +#define __FreeBSD_version 700023 /* Master, propagated to newvers */ #ifndef LOCORE #include diff --git a/sys/sys/soundcard.h b/sys/sys/soundcard.h index d4c4cc5e6d07..a43952fd273a 100644 --- a/sys/sys/soundcard.h +++ b/sys/sys/soundcard.h @@ -3,7 +3,7 @@ */ /*- - * Copyright by Hannu Savolainen 1993 + * Copyright by Hannu Savolainen 1993 / 4Front Technologies 1993-2006 * Modified for the new FreeBSD sound driver by Luigi Rizzo, 1997 * * Redistribution and use in source and binary forms, with or without @@ -32,6 +32,12 @@ * $FreeBSD$ */ +/* + * Unless coordinating changes with 4Front Technologies, do NOT make any + * modifications to ioctl commands, types, etc. that would break + * compatibility with the OSS API. + */ + #ifndef _SYS_SOUNDCARD_H_ #define _SYS_SOUNDCARD_H_ /* @@ -719,8 +725,6 @@ struct sound_timer_info { int caps; }; -#define MIDI_CAP_MPU401 1 /* MPU-401 intelligent mode */ - struct midi_info { char name[30]; int device; /* 0-N. INITIALIZE BEFORE CALLING */ @@ -1437,4 +1441,433 @@ void seqbuf_dump(void); /* This function must be provided by programs */ #define SOUND_PCM_MAPINBUF SNDCTL_DSP_MAPINBUF #define SOUND_PCM_MAPOUTBUF SNDCTL_DSP_MAPOUTBUF +/***********************************************************************/ + +/** + * XXX OSSv4 defines -- some bits taken straight out of the new + * sys/soundcard.h bundled with recent OSS releases. + * + * NB: These macros and structures will be reorganized and inserted + * in appropriate places throughout this file once the code begins + * to take shape. + * + * @todo reorganize layout more like the 4Front version + * @todo ask about maintaining __SIOWR vs. _IOWR ioctl cmd defines + */ + +/** + * @note The @c OSSV4_EXPERIMENT macro is meant to wrap new development code + * in the sound system relevant to adopting 4Front's OSSv4 specification. + * Users should not enable this! Really! + */ +#if 0 +# define OSSV4_EXPERIMENT 1 +#else +# undef OSSV4_EXPERIMENT +#endif + +#ifdef SOUND_VERSION +# undef SOUND_VERSION +# define SOUND_VERSION 0x040000 +#endif /* !SOUND_VERSION */ + +#define OSS_LONGNAME_SIZE 64 +#define OSS_LABEL_SIZE 16 +#define OSS_DEVNODE_SIZE 32 +typedef char oss_longname_t[OSS_LONGNAME_SIZE]; +typedef char oss_label_t[OSS_LABEL_SIZE]; +typedef char oss_devnode_t[OSS_DEVNODE_SIZE]; + +typedef struct audio_errinfo +{ + int play_underruns; + int rec_overruns; + unsigned int play_ptradjust; + unsigned int rec_ptradjust; + int play_errorcount; + int rec_errorcount; + int play_lasterror; + int rec_lasterror; + long play_errorparm; + long rec_errorparm; + int filler[16]; +} audio_errinfo; + +#define SNDCTL_DSP_GETPLAYVOL _IOR ('P', 24, int) +#define SNDCTL_DSP_SETPLAYVOL _IOWR('P', 24, int) +#define SNDCTL_DSP_GETERROR _IOR ('P', 25, audio_errinfo) + + +/* + **************************************************************************** + * Sync groups for audio devices + */ +typedef struct oss_syncgroup +{ + int id; + int mode; + int filler[16]; +} oss_syncgroup; + +#define SNDCTL_DSP_SYNCGROUP _IOWR('P', 28, oss_syncgroup) +#define SNDCTL_DSP_SYNCSTART _IOW ('P', 29, int) + +/* + ************************************************************************** + * "cooked" mode enables software based conversions for sample rate, sample + * format (bits) and number of channels (mono/stereo). These conversions are + * required with some devices that support only one sample rate or just stereo + * to let the applications to use other formats. The cooked mode is enabled by + * default. However it's necessary to disable this mode when mmap() is used or + * when very deterministic timing is required. SNDCTL_DSP_COOKEDMODE is an + * optional call introduced in OSS 3.9.6f. It's _error return must be ignored_ + * since normally this call will return erno=EINVAL. + * + * SNDCTL_DSP_COOKEDMODE must be called immediately after open before doing + * anything else. Otherwise the call will not have any effect. + */ +#define SNDCTL_DSP_COOKEDMODE _IOW ('P', 30, int) + +/* + ************************************************************************** + * SNDCTL_DSP_SILENCE and SNDCTL_DSP_SKIP are new calls in OSS 3.99.0 + * that can be used to implement pause/continue during playback (no effect + * on recording). + */ +#define SNDCTL_DSP_SILENCE _IO ('P', 31) +#define SNDCTL_DSP_SKIP _IO ('P', 32) + +/* + **************************************************************************** + * Abort transfer (reset) functions for input and output + */ +#define SNDCTL_DSP_HALT_INPUT _IO ('P', 33) +#define SNDCTL_DSP_RESET_INPUT SNDCTL_DSP_HALT_INPUT /* Old name */ +#define SNDCTL_DSP_HALT_OUTPUT _IO ('P', 34) +#define SNDCTL_DSP_RESET_OUTPUT SNDCTL_DSP_HALT_OUTPUT /* Old name */ + +/* + **************************************************************************** + * Low water level control + */ +#define SNDCTL_DSP_LOW_WATER _IOW ('P', 34, int) + +/** @todo Get rid of OSS_NO_LONG_LONG references? */ + +/* + **************************************************************************** + * 64 bit pointer support. Only available in environments that support + * the 64 bit (long long) integer type. + */ +#ifndef OSS_NO_LONG_LONG +typedef struct +{ + long long samples; + int fifo_samples; + int filler[32]; /* For future use */ +} oss_count_t; + +#define SNDCTL_DSP_CURRENT_IPTR _IOR ('P', 35, oss_count_t) +#define SNDCTL_DSP_CURRENT_OPTR _IOR ('P', 36, oss_count_t) +#endif + +/* + **************************************************************************** + * Interface for selecting recording sources and playback output routings. + */ +#define SNDCTL_DSP_GET_RECSRC_NAMES _IOR ('P', 37, oss_mixer_enuminfo) +#define SNDCTL_DSP_GET_RECSRC _IOR ('P', 38, int) +#define SNDCTL_DSP_SET_RECSRC _IOWR('P', 38, int) + +#define SNDCTL_DSP_GET_PLAYTGT_NAMES _IOR ('P', 39, oss_mixer_enuminfo) +#define SNDCTL_DSP_GET_PLAYTGT _IOR ('P', 40, int) +#define SNDCTL_DSP_SET_PLAYTGT _IOWR('P', 40, int) +#define SNDCTL_DSP_GETRECVOL _IOR ('P', 41, int) +#define SNDCTL_DSP_SETRECVOL _IOWR('P', 41, int) + +/* + *************************************************************************** + * Some calls for setting the channel assignment with multi channel devices + * (see the manual for details). */ +#define SNDCTL_DSP_GET_CHNORDER _IOR ('P', 42, unsigned long long) +#define SNDCTL_DSP_SET_CHNORDER _IOWR('P', 42, unsigned long long) +# define CHID_UNDEF 0 +# define CHID_L 1 # define CHID_R 2 +# define CHID_C 3 +# define CHID_LFE 4 +# define CHID_LS 5 +# define CHID_RS 6 +# define CHID_LR 7 +# define CHID_RR 8 +#define CHNORDER_UNDEF 0x0000000000000000ULL +#define CHNORDER_NORMAL 0x0000000087654321ULL + +#define MAX_PEAK_CHANNELS 128 +typedef unsigned short oss_peaks_t[MAX_PEAK_CHANNELS]; +#define SNDCTL_DSP_GETIPEAKS _IOR('P', 43, oss_peaks_t) +#define SNDCTL_DSP_GETOPEAKS _IOR('P', 44, oss_peaks_t) +#define SNDCTL_DSP_POLICY _IOW('P', 45, int) /* See the manual */ + +/** + * @brief Argument for SNDCTL_SYSINFO ioctl. + * + * For use w/ the SNDCTL_SYSINFO ioctl available on audio (/dev/dsp*), + * mixer, and MIDI devices. + */ +typedef struct oss_sysinfo +{ + char product[32]; /* For example OSS/Free, OSS/Linux or + OSS/Solaris */ + char version[32]; /* For example 4.0a */ + int versionnum; /* See OSS_GETVERSION */ + char options[128]; /* Reserved */ + + int numaudios; /* # of audio/dsp devices */ + int openedaudio[8]; /* Bit mask telling which audio devices + are busy */ + + int numsynths; /* # of availavle synth devices */ + int nummidis; /* # of available MIDI ports */ + int numtimers; /* # of available timer devices */ + int nummixers; /* # of mixer devices */ + + int openedmidi[8]; /* Bit mask telling which midi devices + are busy */ + int numcards; /* Number of sound cards in the system */ + int filler[241]; /* For future expansion (set to -1) */ +} oss_sysinfo; + +typedef struct oss_mixext +{ + int dev; /* Mixer device number */ + int ctrl; /* Controller number */ + int type; /* Entry type */ +# define MIXT_DEVROOT 0 /* Device root entry */ +# define MIXT_GROUP 1 /* Controller group */ +# define MIXT_ONOFF 2 /* OFF (0) or ON (1) */ +# define MIXT_ENUM 3 /* Enumerated (0 to maxvalue) */ +# define MIXT_MONOSLIDER 4 /* Mono slider (0 to 100) */ +# define MIXT_STEREOSLIDER 5 /* Stereo slider (dual 0 to 100) */ +# define MIXT_MESSAGE 6 /* (Readable) textual message */ +# define MIXT_MONOVU 7 /* VU meter value (mono) */ +# define MIXT_STEREOVU 8 /* VU meter value (stereo) */ +# define MIXT_MONOPEAK 9 /* VU meter peak value (mono) */ +# define MIXT_STEREOPEAK 10 /* VU meter peak value (stereo) */ +# define MIXT_RADIOGROUP 11 /* Radio button group */ +# define MIXT_MARKER 12 /* Separator between normal and extension entries */ +# define MIXT_VALUE 13 /* Decimal value entry */ +# define MIXT_HEXVALUE 14 /* Hexadecimal value entry */ +# define MIXT_MONODB 15 /* Mono atten. slider (0 to -144) */ +# define MIXT_STEREODB 16 /* Stereo atten. slider (dual 0 to -144) */ +# define MIXT_SLIDER 17 /* Slider (mono) with full integer range */ +# define MIXT_3D 18 + + /* Possible value range (minvalue to maxvalue) */ + /* Note that maxvalue may also be smaller than minvalue */ + int maxvalue; + int minvalue; + + int flags; +# define MIXF_READABLE 0x00000001 /* Has readable value */ +# define MIXF_WRITEABLE 0x00000002 /* Has writeable value */ +# define MIXF_POLL 0x00000004 /* May change itself */ +# define MIXF_HZ 0x00000008 /* Herz scale */ +# define MIXF_STRING 0x00000010 /* Use dynamic extensions for value */ +# define MIXF_DYNAMIC 0x00000010 /* Supports dynamic extensions */ +# define MIXF_OKFAIL 0x00000020 /* Interpret value as 1=OK, 0=FAIL */ +# define MIXF_FLAT 0x00000040 /* Flat vertical space requirements */ +# define MIXF_LEGACY 0x00000080 /* Legacy mixer control group */ + char id[16]; /* Mnemonic ID (mainly for internal use) */ + int parent; /* Entry# of parent (group) node (-1 if root) */ + + int dummy; /* Internal use */ + + int timestamp; + + char data[64]; /* Misc data (entry type dependent) */ + unsigned char enum_present[32]; /* Mask of allowed enum values */ + int control_no; /* SOUND_MIXER_VOLUME..SOUND_MIXER_MIDI */ + /* (-1 means not indicated) */ + +/* + * The desc field is reserved for internal purposes of OSS. It should not be + * used by applications. + */ + unsigned int desc; +#define MIXEXT_SCOPE_MASK 0x0000003f +#define MIXEXT_SCOPE_OTHER 0x00000000 +#define MIXEXT_SCOPE_INPUT 0x00000001 +#define MIXEXT_SCOPE_OUTPUT 0x00000002 +#define MIXEXT_SCOPE_MONITOR 0x00000003 +#define MIXEXT_SCOPE_RECSWITCH 0x00000004 + + char extname[32]; + int update_counter; + int filler[7]; +} oss_mixext; + +typedef struct oss_mixext_root +{ + char id[16]; + char name[48]; +} oss_mixext_root; + +typedef struct oss_mixer_value +{ + int dev; + int ctrl; + int value; + int flags; /* Reserved for future use. Initialize to 0 */ + int timestamp; /* Must be set to oss_mixext.timestamp */ + int filler[8]; /* Reserved for future use. Initialize to 0 */ +} oss_mixer_value; + +#define OSS_ENUM_MAXVALUE 255 +typedef struct oss_mixer_enuminfo +{ + int dev; + int ctrl; + int nvalues; + int version; /* Read the manual */ + short strindex[OSS_ENUM_MAXVALUE]; + char strings[3000]; +} oss_mixer_enuminfo; + +#define OPEN_READ PCM_ENABLE_INPUT +#define OPEN_WRITE PCM_ENABLE_OUTPUT +#define OPEN_READWRITE (OPEN_READ|OPEN_WRITE) + +/** + * @brief Argument for SNDCTL_AUDIOINFO ioctl. + * + * For use w/ the SNDCTL_AUDIOINFO ioctl available on audio (/dev/dsp*) + * devices. + */ +typedef struct oss_audioinfo +{ + int dev; /* Audio device number */ + char name[64]; + int busy; /* 0, OPEN_READ, OPEN_WRITE or OPEN_READWRITE */ + int pid; + int caps; /* DSP_CAP_INPUT, DSP_CAP_OUTPUT */ + int iformats; + int oformats; + int magic; /* Reserved for internal use */ + char cmd[64]; /* Command using the device (if known) */ + int card_number; + int port_number; + int mixer_dev; + int real_device; /* Obsolete field. Replaced by devnode */ + int enabled; /* 1=enabled, 0=device not ready at this + moment */ + int flags; /* For internal use only - no practical + meaning */ + int min_rate; /* Sample rate limits */ + int max_rate; + int min_channels; /* Number of channels supported */ + int max_channels; + int binding; /* DSP_BIND_FRONT, etc. 0 means undefined */ + int rate_source; + char handle[32]; + #define MAX_SAMPLE_RATES 20 /* Cannot be changed */ + unsigned int nrates; + unsigned int rates[MAX_SAMPLE_RATES]; /* Please read the manual before using these */ + oss_longname_t song_name; /* Song name (if given) */ + oss_label_t label; /* Device label (if given) */ + int latency; /* In usecs, -1=unknown */ + oss_devnode_t devnode; /* Device special file name (inside + /dev) */ + int filler[186]; +} oss_audioinfo; + +typedef struct oss_mixerinfo +{ + int dev; + char id[16]; + char name[32]; + int modify_counter; + int card_number; + int port_number; + char handle[32]; + int magic; /* Reserved */ + int enabled; /* Reserved */ + int caps; +#define MIXER_CAP_VIRTUAL 0x00000001 + int flags; /* Reserved */ + int nrext; + /* + * The priority field can be used to select the default (motherboard) + * mixer device. The mixer with the highest priority is the + * most preferred one. -2 or less means that this device cannot be used + * as the default mixer. + */ + int priority; + int filler[254]; /* Reserved */ +} oss_mixerinfo; + +typedef struct oss_midi_info +{ + int dev; /* Midi device number */ + char name[64]; + int busy; /* 0, OPEN_READ, OPEN_WRITE or OPEN_READWRITE */ + int pid; + char cmd[64]; /* Command using the device (if known) */ + int caps; +#define MIDI_CAP_MPU401 0x00000001 /**** OBSOLETE ****/ +#define MIDI_CAP_INPUT 0x00000002 +#define MIDI_CAP_OUTPUT 0x00000004 +#define MIDI_CAP_INOUT (MIDI_CAP_INPUT|MIDI_CAP_OUTPUT) +#define MIDI_CAP_VIRTUAL 0x00000008 /* Pseudo device */ +#define MIDI_CAP_MTCINPUT 0x00000010 /* Supports SNDCTL_MIDI_MTCINPUT */ +#define MIDI_CAP_CLIENT 0x00000020 /* Virtual client side device */ +#define MIDI_CAP_SERVER 0x00000040 /* Virtual server side device */ +#define MIDI_CAP_INTERNAL 0x00000080 /* Internal (synth) device */ +#define MIDI_CAP_EXTERNAL 0x00000100 /* external (MIDI port) device */ +#define MIDI_CAP_PTOP 0x00000200 /* Point to point link to one device */ +#define MIDI_CAP_MTC 0x00000400 /* MTC/SMPTE (control) device */ + int magic; /* Reserved for internal use */ + int card_number; + int port_number; + int enabled; /* 1=enabled, 0=device not ready at this moment */ + int flags; /* For internal use only - no practical meaning */ + char handle[32]; + oss_longname_t song_name; /* Song name (if known) */ + oss_label_t label; /* Device label (if given) */ + int latency; /* In usecs, -1=unknown */ + int filler[244]; +} oss_midi_info; + +typedef struct oss_card_info +{ + int card; + char shortname[16]; + char longname[128]; + int flags; + int filler[256]; +} oss_card_info; + +#define SNDCTL_SYSINFO _IOR ('X', 1, oss_sysinfo) +#define OSS_SYSINFO SNDCTL_SYSINFO /* Old name */ + +#define SNDCTL_MIX_NRMIX _IOR ('X', 2, int) +#define SNDCTL_MIX_NREXT _IOWR('X', 3, int) +#define SNDCTL_MIX_EXTINFO _IOWR('X', 4, oss_mixext) +#define SNDCTL_MIX_READ _IOWR('X', 5, oss_mixer_value) +#define SNDCTL_MIX_WRITE _IOWR('X', 6, oss_mixer_value) + +#define SNDCTL_AUDIOINFO _IOWR('X', 7, oss_audioinfo) +#define SNDCTL_MIX_ENUMINFO _IOWR('X', 8, oss_mixer_enuminfo) +#define SNDCTL_MIDIINFO _IOWR('X', 9, oss_midi_info) +#define SNDCTL_MIXERINFO _IOWR('X',10, oss_mixerinfo) +#define SNDCTL_CARDINFO _IOWR('X',11, oss_card_info) + +/* + * Few more "globally" available ioctl calls. + */ +#define SNDCTL_SETSONG _IOW ('Y', 2, oss_longname_t) +#define SNDCTL_GETSONG _IOR ('Y', 2, oss_longname_t) +#define SNDCTL_SETNAME _IOW ('Y', 3, oss_longname_t) +#define SNDCTL_SETLABEL _IOW ('Y', 4, oss_label_t) +#define SNDCTL_GETLABEL _IOR ('Y', 4, oss_label_t) + #endif /* !_SYS_SOUNDCARD_H_ */