--- dev/sound/pcm/buffer.c.orig +++ dev/sound/pcm/buffer.c @@ -117,6 +117,11 @@ 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 @@ 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 @@ 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 @@ 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 @@ free(f1, M_DEVBUF); if (f2) free(f2, M_DEVBUF); + if (f3) + free(f3, M_DEVBUF); ret = 0; out: @@ -214,6 +234,15 @@ 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 @@ } } +/** + * @brief Zap buffer contents, resetting "ready area" fields + * + * @param b buffer context + */ void sndbuf_fillsilence(struct snd_dbuf *b) { @@ -260,7 +294,24 @@ 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) { b->hp = 0; @@ -272,6 +323,7 @@ b->xrun = 0; if (b->buf && b->bufsize > 0) sndbuf_clear(b, b->bufsize); + sndbuf_clearshadow(b); } u_int32_t @@ -493,6 +545,19 @@ /************************************************************/ +/** + * @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 @@ 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 @@ 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 --- dev/sound/pcm/buffer.h.orig +++ 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 @@ 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 @@ 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 --- dev/sound/pcm/channel.c.orig +++ dev/sound/pcm/channel.c @@ -68,6 +68,24 @@ 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 @@ 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 @@ #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 @@ 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; @@ -783,6 +823,7 @@ chn_resetbuf(c); r = CHANNEL_RESETDONE(c->methods, c->devinfo); } + chn_syncdestroy(c); return r; } @@ -829,6 +870,7 @@ 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 +895,19 @@ 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); @@ -888,6 +943,7 @@ c->flags |= CHN_F_DEAD; sndbuf_destroy(bs); sndbuf_destroy(b); + chn_syncdestroy(c); chn_lockdestroy(c); return 0; } @@ -1195,6 +1251,12 @@ 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 +1305,17 @@ 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,7 +1574,61 @@ 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 is meant to be called before destroying the channel. + * + * @param c channel facing imminent destruction + */ void +chn_syncdestroy(struct pcm_channel *c) +{ + struct pcmchan_syncmember *sm; + struct pcmchan_syncgroup *sg; + + if (c->sm == NULL) + return; + + 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); + free(sg, M_DEVBUF); + } +} + +void chn_lock(struct pcm_channel *c) { CHN_LOCK(c); @@ -1512,3 +1639,12 @@ { 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 --- dev/sound/pcm/channel.h.orig +++ dev/sound/pcm/channel.h @@ -37,6 +37,36 @@ 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) + +/** + * @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 +93,33 @@ 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 +159,22 @@ void chn_lock(struct pcm_channel *c); void chn_unlock(struct pcm_channel *c); +int chn_getrates(struct pcm_channel *c, int **rates); +void 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 --- dev/sound/pcm/channel_if.m.orig +++ dev/sound/pcm/channel_if.m @@ -70,6 +70,19 @@ 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 @@ 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; --- dev/sound/pcm/dsp.c.orig +++ dev/sound/pcm/dsp.c @@ -58,6 +58,20 @@ 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) { @@ -394,7 +408,17 @@ } 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 +431,9 @@ 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 +451,20 @@ return EBADF; } + /* + * Certain ioctls may be made on any type of device (audio, mixer, + * and MIDI). + */ + if (IOCGROUP(cmd) == 'X') { + switch(cmd) { + case SNDCTL_MIXERINFO: + return mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg); + break; + default: + return EINVAL; + } + } + getchns(i_dev, &rdch, &wrch, 0); kill = 0; @@ -1032,6 +1073,318 @@ dsp_set_flags(i_dev, dsp_get_flags(i_dev)^SD_F_SIMPLEX); break; + case SNDCTL_SYSINFO: + sound_oss_sysinfo((oss_sysinfo *)arg); + break; + case SNDCTL_AUDIOINFO: + ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg); + 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 +1559,762 @@ 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; + + smrd = NULL; + smwr = NULL; + sg = NULL; + ret = 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); + chn_syncdestroy(wrch); + } + + if (rdch) { + CHN_LOCK(rdch); + 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 bad; + } + + /* + * 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 = (int)sg; + + 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 bad; + + /* + * 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 bad; + } + + 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 bad; + } + + SLIST_INSERT_HEAD(&sg->members, smwr, link); + smwr->parent = sg; + smwr->ch = wrch; + + chn_abort(wrch); + wrch->flags |= CHN_F_NOTRIGGER; + wrch->sm = smwr; + } + + + if (wrch) + CHN_UNLOCK(wrch); + if (rdch) + CHN_UNLOCK(rdch); + + PCM_SG_UNLOCK(); + + return 0; + +bad: + if (smrd != NULL) + free(smrd, M_DEVBUF); + if (smwr != NULL) + free(smwr, M_DEVBUF); + if ((group->id == 0) && (sg != NULL)) { + SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); + free(sg, M_DEVBUF); + } + + if (wrch) { + wrch->sm = NULL; + CHN_UNLOCK(wrch); + } + if (rdch) { + rdch->sm = NULL; + CHN_UNLOCK(rdch); + } + + PCM_SG_UNLOCK(); + + 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(); + 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 */ --- dev/sound/pcm/dsp.h.orig +++ 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_ */ --- dev/sound/pcm/mixer.c.orig +++ dev/sound/pcm/mixer.c @@ -49,6 +49,13 @@ 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 @@ .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 @@ 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 @@ 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 @@ snddev = device_get_softc(dev); snddev->mixer_dev = pdev; + ++mixer_count; + return 0; bad: @@ -300,6 +453,8 @@ d->mixer_dev = NULL; + --mixer_count; + return 0; } @@ -481,6 +636,11 @@ 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 @@ 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 @@ 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; +} --- dev/sound/pcm/mixer.h.orig +++ dev/sound/pcm/mixer.h @@ -30,6 +30,7 @@ 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,13 @@ 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)) +#define MIXER_SIZE (512 + sizeof(struct kobj) + \ + sizeof(oss_mixer_enuminfo)) #define MIXER_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, MIXER_SIZE) --- dev/sound/pcm/sound.c.orig +++ dev/sound/pcm/sound.c @@ -1126,6 +1126,118 @@ /************************************************************************/ +/** + * @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) { --- dev/sound/pcm/sound.h.orig +++ 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 @@ #include #include #include +#include #define PCM_SOFTC_SIZE 512 @@ -304,6 +306,8 @@ }; +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) --- sys/soundcard.h.orig +++ 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: src/sys/sys/soundcard.h,v 1.45 2005/07/31 16:08:03 netchild Exp $ */ +/* + * 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_ /* @@ -1437,4 +1443,392 @@ #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; + +#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_ */