diff --git a/lib/libmixer/mixer.3 b/lib/libmixer/mixer.3 index af2a8ff135a8..a3593898ed68 100644 --- a/lib/libmixer/mixer.3 +++ b/lib/libmixer/mixer.3 @@ -1,550 +1,553 @@ .\"- .\" Copyright (c) 2021-2022 Christos Margiolis .\" .\" Permission is hereby granted, free of charge, to any person obtaining a copy .\" of this software and associated documentation files (the "Software"), to deal .\" in the Software without restriction, including without limitation the rights .\" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .\" copies of the Software, and to permit persons to whom the Software is .\" furnished to do so, subject to the following conditions: .\" .\" The above copyright notice and this permission notice shall be included in .\" all copies or substantial portions of the Software. .\" .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .\" THE SOFTWARE. .\" -.Dd January 19, 2023 +.Dd May 22, 2024 .Dt MIXER 3 .Os .Sh NAME .Nm mixer_open , .Nm mixer_close , .Nm mixer_get_dev , .Nm mixer_get_dev_byname , .Nm mixer_add_ctl , .Nm mixer_add_ctl_s , .Nm mixer_remove_ctl , .Nm mixer_get_ctl , .Nm mixer_get_ctl_byname , .Nm mixer_set_vol , .Nm mixer_set_mute , .Nm mixer_mod_recsrc , .Nm mixer_get_dunit , .Nm mixer_set_dunit , .Nm mixer_get_mode , .Nm mixer_get_nmixers , .Nm MIX_ISDEV , .Nm MIX_ISMUTE , .Nm MIX_ISREC , .Nm MIX_ISRECSRC , .Nm MIX_VOLNORM , .Nm MIX_VOLDENORM .Nd interface to OSS mixers .Sh LIBRARY Mixer library (libmixer, -lmixer) .Sh SYNOPSIS .In mixer.h .Ft struct mixer * .Fn mixer_open "const char *name" .Ft int .Fn mixer_close "struct mixer *m" .Ft struct mix_dev * .Fn mixer_get_dev "struct mixer *m" "int devno" .Ft struct mix_dev * .Fn mixer_get_dev_byname "struct mixer *m" "name" .Ft int .Fn mixer_add_ctl "struct mix_dev *parent" "int id" "const char *name" \ "int (*mod)(struct mix_dev *d, void *p)" \ "int (*print)(struct mix_dev *d, void *p)" .Ft int .Fn mixer_add_ctl_s "mix_ctl_t *ctl" .Ft int .Fn mixer_remove_ctl "mix_ctl_t *ctl" .Ft mix_ctl_t * .Fn mixer_get_ctl "struct mix_dev *d" "int id" .Ft mix_ctl_t * .Fn mixer_get_ctl_byname "struct mix_dev *d" "const char *name" .Ft int .Fn mixer_set_vol "struct mixer *m" "mix_volume_t vol" .Ft int .Fn mixer_set_mute "struct mixer *m" "int opt" .Ft int .Fn mixer_mod_recsrc "struct mixer *m" "int opt" .Ft int .Fn mixer_get_dunit "void" .Ft int .Fn mixer_set_dunit "struct mixer *m" "int unit" .Ft int .Fn mixer_get_mode "int unit" .Ft int .Fn mixer_get_nmixers "void" .Ft int .Fn MIX_ISDEV "struct mixer *m" "int devno" .Ft int .Fn MIX_ISMUTE "struct mixer *m" "int devno" .Ft int .Fn MIX_ISREC "struct mixer *m" "int devno" .Ft int .Fn MIX_ISRECSRC "struct mixer *m" "int devno" .Ft float .Fn MIX_VOLNORM "int v" .Ft int .Fn MIX_VOLDENORM "float v" .Sh DESCRIPTION The .Nm mixer library allows userspace programs to access and manipulate OSS sound mixers in a simple way. .Ss Mixer A mixer is described by the following structure: .Bd -literal struct mixer { TAILQ_HEAD(mix_devhead, mix_dev) devs; /* device list */ struct mix_dev *dev; /* selected device */ oss_mixerinfo mi; /* mixer info */ oss_card_info ci; /* audio card info */ char name[NAME_MAX]; /* mixer name (e.g /dev/mixer0) */ int fd; /* file descriptor */ int unit; /* audio card unit */ int ndev; /* number of devices */ int devmask; /* supported devices */ #define MIX_MUTE 0x01 #define MIX_UNMUTE 0x02 #define MIX_TOGGLEMUTE 0x04 int mutemask; /* muted devices */ int recmask; /* recording devices */ #define MIX_ADDRECSRC 0x01 #define MIX_REMOVERECSRC 0x02 #define MIX_SETRECSRC 0x04 #define MIX_TOGGLERECSRC 0x08 int recsrc; /* recording sources */ #define MIX_MODE_MIXER 0x01 #define MIX_MODE_PLAY 0x02 #define MIX_MODE_REC 0x04 int mode; /* dev.pcm.X.mode sysctl */ int f_default; /* default mixer flag */ }; .Ed .Pp The fields are follows: .Bl -tag -width "f_default" .It Fa devs A tail queue structure containing all supported mixer devices. .It Fa dev A pointer to the currently selected device. The device is one of the elements in .Ar devs . .It Fa mi OSS information about the mixer. Look at the definition of the .Ft oss_mixerinfo structure in .In sys/soundcard.h to see its fields. .It Fa ci OSS audio card information. This structure is also defined in .In sys/soundcard.h . .It Fa name Path to the mixer (e.g /dev/mixer0). .It Fa fd File descriptor returned when the mixer is opened in .Fn mixer_open . .It Fa unit Audio card unit. Since each mixer device maps to a pcmX device, .Ar unit is always equal to the number of that pcmX device. For example, if the audio device's number is 0 (i.e pcm0), then .Ar unit is 0 as well. This number is useful when checking if the mixer's audio card is the default one. .It Fa ndev Number of devices in .Ar devs . .It Fa devmask Bit mask containing all supported devices for the mixer. For example, if device 10 is supported, then the 10th bit in the mask will be set. By default, .Fn mixer_open stores only the supported devices in devs, so it is very unlikely this mask will be needed. .It Fa mutemask Bit mask containing all muted devices. The logic is the same as with .Ar devmask . .It Fa recmask Bit mask containing all recording devices. Again, same logic as with the other masks. .It Fa recsrc Bit mask containing all recording sources. Yes, same logic again. .It Fa mode Bit mask containing the supported modes for this audio device. It holds the value of the .Ar dev.pcm.X.mode sysctl. .It Fa f_default Flag which tells whether the mixer's audio card is the default one. .El .Ss Mixer device Each mixer device stored in a mixer is described as follows: .Bd -literal struct mix_dev { struct mixer *parent_mixer; /* parent mixer */ char name[NAME_MAX]; /* device name (e.g "vol") */ int devno; /* device number */ struct mix_volume { #define MIX_VOLMIN 0.0f #define MIX_VOLMAX 1.0f #define MIX_VOLNORM(v) ((v) / 100.0f) #define MIX_VOLDENORM(v) ((int)((v) * 100.0f + 0.5f)) float left; /* left volume */ float right; /* right volume */ } vol; int nctl; /* number of controls */ TAILQ_HEAD(mix_ctlhead, mix_ctl) ctls; /* control list */ TAILQ_ENTRY(mix_dev) devs; }; .Ed .Pp The fields are follows: .Bl -tag -width "parent_mixer" .It Fa parent_mixer Pointer to the mixer the device is attached to. .It Fa name Device name given by the OSS API. Devices can have one of the following names: .Bd -ragged vol, bass, treble, synth, pcm, speaker, line, mic, cd, mix, pcm2, rec, igain, ogain, line1, line2, line3, dig1, dig2, dig3, phin, phout, video, radio, and monitor. .Ed .It Fa devno Device's index in the SOUND_MIXER_NRDEVICES macro defined in .In sys/soundcard.h . This number is used to check against the masks defined in the .Ar mixer structure. .It Fa left right Left and right-ear volumes. Although the OSS API stores volumes in integers from 0-100, \ we normalize them to 32-bit floating point numbers. However, the volumes can be denormalized using the .Ar MIX_VOLDENORM macro if needed. .It Fa nctl Number of user-defined mixer controls associated with the device. .It Fa ctls A tail queue containing user-defined mixer controls. .El .Ss User-defined mixer controls Each mixer device can have user-defined controls. The control structure is defined as follows: .Bd -literal struct mix_ctl { struct mix_dev *parent_dev; /* parent device */ int id; /* control id */ char name[NAME_MAX]; /* control name */ int (*mod)(struct mix_dev *, void *); /* modify control values */ int (*print)(struct mix_dev *, void *); /* print control */ TAILQ_ENTRY(mix_ctl) ctls; }; .Ed .Pp The fields are follows: .Bl -tag -width "parent_dev" .It Fa parent_dev Pointer to the device the control is attached to. .It Fa id Control ID assigned by the caller. Even though the library will report it, care has to be taken to not give \ a control the same ID in case the caller has to choose controls using their ID. .It Fa name Control name. As with .Ar id , the caller has to make sure the same name is not used more than once. .It Fa mod Function pointer to a control modification function. As in .Xr mixer 8 , each mixer control's values can be modified. For example, if we have a volume control, the .Ar mod function will be responsible for handling volume changes. .It Fa print Function pointer to a control print function. .El .Ss Opening and closing the mixer The application must first call the .Fn mixer_open function to obtain a handle to the device, which is used as an argument \ in most other functions and macros. The parameter .Ar name specifies the path to the mixer. OSS mixers are stored under .Ar /dev/mixerN where .Ar N is the number of the mixer device. Each device maps to an actual .Ar pcm audio card, so .Ar /dev/mixer0 is the mixer for .Ar pcm0 , and so on. If .Ar name is .Ar NULL or .Ar /dev/mixer , .Fn mixer_open opens the default mixer (hw.snd.default_unit). .Pp The .Fn mixer_close function frees resources and closes the mixer device. It is a good practice to always call it when the application is done using the mixer. .Ss Manipulating the mixer The .Fn mixer_get_dev and .Fn mixer_get_dev_byname functions select a mixer device, either by its number or by its name respectively. The mixer structure keeps a list of all the devices, but only \ one can be manipulated at a time. Each time a new device is to be manipulated, one of the two functions has to be called. .Pp The .Fn mixer_set_vol function changes the volume of the selected mixer device. The .Ar vol parameter is a structure that stores the left and right volumes of a given device. The allowed volume values are between MIX_VOLMIN (0.0) and MIX_VOLMAX (1.0). .Pp The .Fn mixer_set_mute function modifies the mute of a selected device. The .Ar opt parameter has to be one of the following options: .Bl -tag -width MIX_TOGGLEMUTE -offset indent .It Dv MIX_MUTE Mute the device. .It Dv MIX_UNMUTE Unmute the device. .It Dv MIX_TOGGLEMUTE Toggle the device's mute (e.g mute if unmuted and unmute if muted). .El .Pp The .Fn mixer_mod_recsrc function modifies a recording device. The selected device has to be a recording device, otherwise the function will fail. The .Ar opt parameter has to be one of the following options: .Bl -tag -width MIX_REMOVERECSRC -offset indent .It Dv MIX_ADDRECSRC Add device to the recording sources. .It Dv MIX_REMOVERECSRC Remove device from the recording sources. .It Dv MIX_SETRECSRC Set device as the only recording source. .It Dv MIX_TOGGLERECSRC Toggle device from the recording sources. .El .Pp The .Fn mixer_get_dunit and .Fn mixer_set_dunit functions get and set the default audio card in the system. Although this is not really a mixer feature, it is useful to have instead of \ having to use the .Xr sysctl 3 controls. .Pp The .Fn mixer_get_mode function returns the playback/recording mode of the audio device the mixer \ belongs to. The available values are the following: .Bl -tag -width "MIX_STATUS_PLAY | MIX_STATUS_REC" -offset indent .It Dv MIX_STATUS_NONE Neither playback nor recording. .It Dv MIX_STATUS_PLAY Playback. .It Dv MIX_STATUS_REC Recording. .It Dv MIX_STATUS_PLAY | MIX_STATUS_REC Playback and recording. .El .Pp The .Fn mixer_get_nmixers -function returns the total number of mixer devices in the system. +function returns the maximum mixer unit number. +Although this might sound as incorrect behavior, given that one would expect +"nmixers" to refer to the total number of active mixers, it is more intuitive +for applications that want to loop through all mixer devices. .Pp The .Fn MIX_ISDEV macro checks if a device is actually a valid device for a given mixer. It is very unlikely that this macro will ever be needed since the library \ stores only valid devices by default. .Pp The .Fn MIX_ISMUTE macro checks if a device is muted. .Pp The .Fn MIX_ISREC macro checks if a device is a recording device. .Pp The .Fn MIX_ISRECSRC macro checks if a device is a recording source. .Pp The .Fn MIX_VOLNORM macro normalizes a value to 32-bit floating point number. It is used to normalize the volumes read from the OSS API. .Pp The .Fn MIX_VOLDENORM macro denormalizes the left and right volumes stores in the .Ft mix_dev structure. .Ss Defining and using mixer controls The .Fn mix_add_ctl function creates a control and attaches it to the device specified in the .Ar parent argument. .Pp The .Fn mix_add_ctl_s function does the same thing as with .Fn mix_add_ctl but the caller passes a .Ft mix_ctl_t * structure instead of each field as a separate argument. .Pp The .Fn mixer_remove_ctl functions removes a control from the device its attached to. .Pp The .Fn mixer_get_ctl function searches for a control in the device specified in the .Ar d argument and returns a pointer to it. The search is done using the control's ID. .Pp The .Fn mixer_get_ctl_byname function is the same as with .Fn mixer_get_ctl but the search is done using the control's name. .Sh RETURN VALUES The .Fn mixer_open function returns the newly created handle on success and NULL on failure. .Pp The .Fn mixer_close , .Fn mixer_set_vol , .Fn mixer_set_mute , .Fn mixer_mod_recsrc , .Fn mixer_get_dunut , .Fn mixer_set_dunit and .Fn mixer_get_nmixers functions return 0 or positive values on success and -1 on failure. .Pp The .Fn mixer_get_dev and .Fn mixer_get_dev_byname functions return the selected device on success and NULL on failure. .Pp All functions set the value of .Ar errno on failure. .Sh EXAMPLES .Ss Change the volume of a device .Bd -literal struct mixer *m; mix_volume_t vol; char *mix_name, *dev_name; mix_name = ...; if ((m = mixer_open(mix_name)) == NULL) err(1, "mixer_open: %s", mix_name); dev_name = ...; if ((m->dev = mixer_get_dev_byname(m, dev_name)) < 0) err(1, "unknown device: %s", dev_name); vol.left = ...; vol.right = ....; if (mixer_set_vol(m, vol) < 0) warn("cannot change volume"); (void)mixer_close(m); .Ed .Ss Mute all unmuted devices .Bd -literal struct mixer *m; struct mix_dev *dp; if ((m = mixer_open(NULL)) == NULL) /* Open the default mixer. */ err(1, "mixer_open"); TAILQ_FOREACH(dp, &m->devs, devs) { m->dev = dp; /* Select device. */ if (M_ISMUTE(m, dp->devno)) continue; if (mixer_set_mute(m, MIX_MUTE) < 0) warn("cannot mute device: %s", dp->name); } (void)mixer_close(m); .Ed .Ss Print all recording sources' names and volumes .Bd -literal struct mixer *m; struct mix_dev *dp; char *mix_name, *dev_name; mix_name = ...; if ((m = mixer_open(mix_name)) == NULL) err(1, "mixer_open: %s", mix_name); TAILQ_FOREACH(dp, &m->devs, devs) { if (M_ISRECSRC(m, dp->devno)) printf("%s\\t%.2f:%.2f\\n", dp->name, dp->vol.left, dp->vol.right); } (void)mixer_close(m); .Ed .Sh SEE ALSO .Xr queue 3 , .Xr sysctl 3 , .Xr sound 4 , .Xr mixer 8 and .Xr errno 2 .Sh AUTHORS .An Christos Margiolis Aq Mt christos@FreeBSD.org diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index 4ee5d7c6738c..c737df33b97d 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -1,3032 +1,3051 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * All rights reserved. * Copyright (c) 2024 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include #include #include struct dsp_cdevpriv { struct snddev_info *sc; struct pcm_channel *rdch; struct pcm_channel *wrch; struct pcm_channel *volch; int simplex; }; static int dsp_mmap_allow_prot_exec = 0; SYSCTL_INT(_hw_snd, OID_AUTO, compat_linux_mmap, CTLFLAG_RWTUN, &dsp_mmap_allow_prot_exec, 0, "linux mmap compatibility (-1=force disable 0=auto 1=force enable)"); static int dsp_basename_clone = 1; SYSCTL_INT(_hw_snd, OID_AUTO, basename_clone, CTLFLAG_RWTUN, &dsp_basename_clone, 0, "DSP basename cloning (0: Disable; 1: Enabled)"); #define DSP_REGISTERED(x) (PCM_REGISTERED(x) && (x)->dsp_dev != NULL) #define OLDPCM_IOCTL static d_open_t dsp_open; static d_read_t dsp_read; static d_write_t dsp_write; static d_ioctl_t dsp_ioctl; static d_poll_t dsp_poll; static d_mmap_t dsp_mmap; static d_mmap_single_t dsp_mmap_single; struct cdevsw dsp_cdevsw = { .d_version = D_VERSION, .d_open = dsp_open, .d_read = dsp_read, .d_write = dsp_write, .d_ioctl = dsp_ioctl, .d_poll = dsp_poll, .d_mmap = dsp_mmap, .d_mmap_single = dsp_mmap_single, .d_name = "dsp", }; static eventhandler_tag dsp_ehtag = NULL; 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); 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_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch, int *mask); #ifdef OSSV4_EXPERIMENT 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 int dsp_make_dev(device_t dev) { struct make_dev_args devargs; struct snddev_info *sc; int err, unit; sc = device_get_softc(dev); unit = device_get_unit(dev); make_dev_args_init(&devargs); devargs.mda_devsw = &dsp_cdevsw; devargs.mda_uid = UID_ROOT; devargs.mda_gid = GID_WHEEL; devargs.mda_mode = 0666; devargs.mda_si_drv1 = sc; err = make_dev_s(&devargs, &sc->dsp_dev, "dsp%d", unit); if (err != 0) { device_printf(dev, "failed to create dsp%d: error %d", unit, err); return (ENXIO); } return (0); } void dsp_destroy_dev(device_t dev) { struct snddev_info *d; d = device_get_softc(dev); destroy_dev_sched(d->dsp_dev); } static void getchns(struct dsp_cdevpriv *priv, uint32_t prio) { struct snddev_info *d; struct pcm_channel *ch; uint32_t flags; if (priv->simplex) { d = priv->sc; if (!PCM_REGISTERED(d)) return; PCM_LOCK(d); PCM_WAIT(d); PCM_ACQUIRE(d); /* * Note: order is important - * pcm flags -> prio query flags -> wild guess */ ch = NULL; flags = pcm_getflags(d->dev); if (flags & SD_F_PRIO_WR) { ch = priv->rdch; } else if (flags & SD_F_PRIO_RD) { ch = priv->wrch; } else if (prio & SD_F_PRIO_WR) { ch = priv->rdch; flags |= SD_F_PRIO_WR; } else if (prio & SD_F_PRIO_RD) { ch = priv->wrch; flags |= SD_F_PRIO_RD; } else if (priv->wrch != NULL) { ch = priv->rdch; flags |= SD_F_PRIO_WR; } else if (priv->rdch != NULL) { ch = priv->wrch; flags |= SD_F_PRIO_RD; } pcm_setflags(d->dev, flags); if (ch != NULL) { CHN_LOCK(ch); chn_ref(ch, -1); chn_release(ch); } PCM_RELEASE(d); PCM_UNLOCK(d); } if (priv->rdch != NULL && (prio & SD_F_PRIO_RD)) CHN_LOCK(priv->rdch); if (priv->wrch != NULL && (prio & SD_F_PRIO_WR)) CHN_LOCK(priv->wrch); } static void relchns(struct dsp_cdevpriv *priv, uint32_t prio) { if (priv->rdch != NULL && (prio & SD_F_PRIO_RD)) CHN_UNLOCK(priv->rdch); if (priv->wrch != NULL && (prio & SD_F_PRIO_WR)) CHN_UNLOCK(priv->wrch); } #define DSP_F_VALID(x) ((x) & (FREAD | FWRITE)) #define DSP_F_DUPLEX(x) (((x) & (FREAD | FWRITE)) == (FREAD | FWRITE)) #define DSP_F_SIMPLEX(x) (!DSP_F_DUPLEX(x)) #define DSP_F_READ(x) ((x) & FREAD) #define DSP_F_WRITE(x) ((x) & FWRITE) static const struct { int type; char *name; char *sep; char *alias; } dsp_cdevs[] = { { SND_DEV_DSP, "dsp", ".", NULL }, { SND_DEV_DSPHW_PLAY, "dsp", ".p", NULL }, { SND_DEV_DSPHW_VPLAY, "dsp", ".vp", NULL }, { SND_DEV_DSPHW_REC, "dsp", ".r", NULL }, { SND_DEV_DSPHW_VREC, "dsp", ".vr", NULL }, /* Low priority, OSSv4 aliases. */ { SND_DEV_DSP, "dsp_ac3", ".", "dsp" }, { SND_DEV_DSP, "dsp_mmap", ".", "dsp" }, { SND_DEV_DSP, "dsp_multich", ".", "dsp" }, { SND_DEV_DSP, "dsp_spdifout", ".", "dsp" }, { SND_DEV_DSP, "dsp_spdifin", ".", "dsp" }, }; static void dsp_close(void *data) { struct dsp_cdevpriv *priv = data; struct pcm_channel *rdch, *wrch, *volch; struct snddev_info *d; int sg_ids, rdref, wdref; if (priv == NULL) return; d = priv->sc; /* At this point pcm_unregister() will destroy all channels anyway. */ if (PCM_DETACHING(d)) goto skip; PCM_GIANT_ENTER(d); PCM_LOCK(d); PCM_WAIT(d); PCM_ACQUIRE(d); rdch = priv->rdch; wrch = priv->wrch; volch = priv->volch; rdref = -1; wdref = -1; if (volch != NULL) { if (volch == rdch) rdref--; else if (volch == wrch) wdref--; else { CHN_LOCK(volch); chn_ref(volch, -1); CHN_UNLOCK(volch); } } if (rdch != NULL) CHN_REMOVE(d, rdch, channels.pcm.opened); if (wrch != NULL) CHN_REMOVE(d, wrch, channels.pcm.opened); if (rdch != NULL || wrch != NULL) { PCM_UNLOCK(d); if (rdch != NULL) { /* * 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 = chn_syncdestroy(rdch); PCM_SG_UNLOCK(); if (sg_ids != 0) free_unr(pcmsg_unrhdr, sg_ids); CHN_LOCK(rdch); chn_ref(rdch, rdref); chn_abort(rdch); /* won't sleep */ rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | CHN_F_DEAD | CHN_F_EXCLUSIVE); chn_reset(rdch, 0, 0); chn_release(rdch); } if (wrch != NULL) { /* * Please see block above. */ PCM_SG_LOCK(); sg_ids = chn_syncdestroy(wrch); PCM_SG_UNLOCK(); if (sg_ids != 0) free_unr(pcmsg_unrhdr, sg_ids); CHN_LOCK(wrch); chn_ref(wrch, wdref); chn_flush(wrch); /* may sleep */ wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | CHN_F_DEAD | CHN_F_EXCLUSIVE); chn_reset(wrch, 0, 0); chn_release(wrch); } PCM_LOCK(d); } PCM_RELEASE(d); PCM_UNLOCK(d); PCM_GIANT_LEAVE(d); skip: free(priv, M_DEVBUF); priv = NULL; } #define DSP_FIXUP_ERROR() do { \ prio = pcm_getflags(d->dev); \ if (!DSP_F_VALID(flags)) \ error = EINVAL; \ if (!DSP_F_DUPLEX(flags) && \ ((DSP_F_READ(flags) && d->reccount == 0) || \ (DSP_F_WRITE(flags) && d->playcount == 0))) \ error = ENOTSUP; \ else if (!DSP_F_DUPLEX(flags) && (prio & SD_F_SIMPLEX) && \ ((DSP_F_READ(flags) && (prio & SD_F_PRIO_WR)) || \ (DSP_F_WRITE(flags) && (prio & SD_F_PRIO_RD)))) \ error = EBUSY; \ } while (0) static int dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct dsp_cdevpriv *priv; struct pcm_channel *rdch, *wrch; struct snddev_info *d; uint32_t fmt, spd, prio; int error, rderror, wrerror; /* Kind of impossible.. */ if (i_dev == NULL || td == NULL) return (ENODEV); d = i_dev->si_drv1; if (PCM_DETACHING(d) || !PCM_REGISTERED(d)) return (EBADF); priv = malloc(sizeof(*priv), M_DEVBUF, M_WAITOK | M_ZERO); priv->sc = d; priv->rdch = NULL; priv->wrch = NULL; priv->volch = NULL; priv->simplex = (pcm_getflags(d->dev) & SD_F_SIMPLEX) ? 1 : 0; error = devfs_set_cdevpriv(priv, dsp_close); if (error != 0) return (error); PCM_GIANT_ENTER(d); /* Lock snddev so nobody else can monkey with it. */ PCM_LOCK(d); PCM_WAIT(d); error = 0; DSP_FIXUP_ERROR(); if (error != 0) { PCM_UNLOCK(d); PCM_GIANT_EXIT(d); return (error); } /* * That is just enough. Acquire and unlock pcm lock so * the other will just have to wait until we finish doing * everything. */ PCM_ACQUIRE(d); PCM_UNLOCK(d); fmt = SND_FORMAT(AFMT_U8, 1, 0); spd = DSP_DEFAULT_SPEED; rdch = NULL; wrch = NULL; rderror = 0; wrerror = 0; if (DSP_F_READ(flags)) { /* open for read */ rderror = pcm_chnalloc(d, &rdch, PCMDIR_REC, td->td_proc->p_pid, td->td_proc->p_comm); if (rderror == 0 && chn_reset(rdch, fmt, spd) != 0) rderror = ENXIO; if (rderror != 0) { if (rdch != NULL) chn_release(rdch); if (!DSP_F_DUPLEX(flags)) { PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (rderror); } rdch = NULL; } else { if (flags & O_NONBLOCK) rdch->flags |= CHN_F_NBIO; if (flags & O_EXCL) rdch->flags |= CHN_F_EXCLUSIVE; chn_ref(rdch, 1); chn_vpc_reset(rdch, SND_VOL_C_PCM, 0); CHN_UNLOCK(rdch); } } if (DSP_F_WRITE(flags)) { /* open for write */ wrerror = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, td->td_proc->p_comm); if (wrerror == 0 && chn_reset(wrch, fmt, spd) != 0) wrerror = ENXIO; if (wrerror != 0) { if (wrch != NULL) chn_release(wrch); if (!DSP_F_DUPLEX(flags)) { if (rdch != NULL) { /* * Lock, deref and release previously * created record channel */ CHN_LOCK(rdch); chn_ref(rdch, -1); chn_release(rdch); } PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (wrerror); } wrch = NULL; } else { if (flags & O_NONBLOCK) wrch->flags |= CHN_F_NBIO; if (flags & O_EXCL) wrch->flags |= CHN_F_EXCLUSIVE; chn_ref(wrch, 1); chn_vpc_reset(wrch, SND_VOL_C_PCM, 0); CHN_UNLOCK(wrch); } } PCM_LOCK(d); if (wrch == NULL && rdch == NULL) { PCM_RELEASE(d); PCM_UNLOCK(d); PCM_GIANT_EXIT(d); if (wrerror != 0) return (wrerror); if (rderror != 0) return (rderror); return (EINVAL); } if (rdch != NULL) CHN_INSERT_HEAD(d, rdch, channels.pcm.opened); if (wrch != NULL) CHN_INSERT_HEAD(d, wrch, channels.pcm.opened); priv->rdch = rdch; priv->wrch = wrch; PCM_RELEASE(d); PCM_UNLOCK(d); PCM_GIANT_LEAVE(d); return (0); } static __inline int dsp_io_ops(struct dsp_cdevpriv *priv, struct uio *buf) { struct snddev_info *d; struct pcm_channel **ch; int (*chn_io)(struct pcm_channel *, struct uio *); int prio, ret; pid_t runpid; KASSERT(buf != NULL && (buf->uio_rw == UIO_READ || buf->uio_rw == UIO_WRITE), ("%s(): io train wreck!", __func__)); d = priv->sc; if (PCM_DETACHING(d) || !DSP_REGISTERED(d)) return (EBADF); PCM_GIANT_ENTER(d); switch (buf->uio_rw) { case UIO_READ: prio = SD_F_PRIO_RD; ch = &priv->rdch; chn_io = chn_read; break; case UIO_WRITE: prio = SD_F_PRIO_WR; ch = &priv->wrch; chn_io = chn_write; break; default: panic("invalid/corrupted uio direction: %d", buf->uio_rw); break; } runpid = buf->uio_td->td_proc->p_pid; getchns(priv, prio); if (*ch == NULL || !((*ch)->flags & CHN_F_BUSY)) { if (priv->rdch != NULL || priv->wrch != NULL) relchns(priv, prio); PCM_GIANT_EXIT(d); return (EBADF); } if (((*ch)->flags & (CHN_F_MMAP | CHN_F_DEAD)) || (((*ch)->flags & CHN_F_RUNNING) && (*ch)->pid != runpid)) { relchns(priv, prio); PCM_GIANT_EXIT(d); return (EINVAL); } else if (!((*ch)->flags & CHN_F_RUNNING)) { (*ch)->flags |= CHN_F_RUNNING; (*ch)->pid = runpid; } /* * chn_read/write must give up channel lock in order to copy bytes * from/to userland, so up the "in progress" counter to make sure * someone else doesn't come along and muss up the buffer. */ ++(*ch)->inprog; ret = chn_io(*ch, buf); --(*ch)->inprog; CHN_BROADCAST(&(*ch)->cv); relchns(priv, prio); PCM_GIANT_LEAVE(d); return (ret); } static int dsp_read(struct cdev *i_dev, struct uio *buf, int flag) { struct dsp_cdevpriv *priv; int err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); return (dsp_io_ops(priv, buf)); } static int dsp_write(struct cdev *i_dev, struct uio *buf, int flag) { struct dsp_cdevpriv *priv; int err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); return (dsp_io_ops(priv, buf)); } static int dsp_ioctl_channel(struct dsp_cdevpriv *priv, struct pcm_channel *volch, u_long cmd, caddr_t arg) { struct snddev_info *d; struct pcm_channel *rdch, *wrch; int j, left, right, center, mute; d = priv->sc; if (!PCM_REGISTERED(d) || !(pcm_getflags(d->dev) & SD_F_VPC)) return (-1); PCM_UNLOCKASSERT(d); j = cmd & 0xff; rdch = priv->rdch; wrch = priv->wrch; /* No specific channel, look into cache */ if (volch == NULL) volch = priv->volch; /* Look harder */ if (volch == NULL) { if (j == SOUND_MIXER_RECLEV && rdch != NULL) volch = rdch; else if (j == SOUND_MIXER_PCM && wrch != NULL) volch = wrch; } /* Final validation */ if (volch == NULL) return (EINVAL); CHN_LOCK(volch); if (!(volch->feederflags & (1 << FEEDER_VOLUME))) { CHN_UNLOCK(volch); return (EINVAL); } switch (cmd & ~0xff) { case MIXER_WRITE(0): switch (j) { case SOUND_MIXER_MUTE: if (volch->direction == PCMDIR_REC) { chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_RECLEV) != 0); } else { chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_PCM) != 0); } break; case SOUND_MIXER_PCM: if (volch->direction != PCMDIR_PLAY) break; left = *(int *)arg & 0x7f; right = ((*(int *)arg) >> 8) & 0x7f; center = (left + right) >> 1; chn_setvolume_multi(volch, SND_VOL_C_PCM, left, right, center); break; case SOUND_MIXER_RECLEV: if (volch->direction != PCMDIR_REC) break; left = *(int *)arg & 0x7f; right = ((*(int *)arg) >> 8) & 0x7f; center = (left + right) >> 1; chn_setvolume_multi(volch, SND_VOL_C_PCM, left, right, center); break; default: /* ignore all other mixer writes */ break; } break; case MIXER_READ(0): switch (j) { case SOUND_MIXER_MUTE: mute = CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FL) || CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FR); if (volch->direction == PCMDIR_REC) { *(int *)arg = mute << SOUND_MIXER_RECLEV; } else { *(int *)arg = mute << SOUND_MIXER_PCM; } break; case SOUND_MIXER_PCM: if (volch->direction != PCMDIR_PLAY) break; *(int *)arg = CHN_GETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FL); *(int *)arg |= CHN_GETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; break; case SOUND_MIXER_RECLEV: if (volch->direction != PCMDIR_REC) break; *(int *)arg = CHN_GETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FL); *(int *)arg |= CHN_GETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; break; case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: if (volch->direction == PCMDIR_REC) *(int *)arg = SOUND_MASK_RECLEV; else *(int *)arg = SOUND_MASK_PCM; break; default: *(int *)arg = 0; break; } break; default: break; } CHN_UNLOCK(volch); return (0); } static int dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) { struct dsp_cdevpriv *priv; struct pcm_channel *chn, *rdch, *wrch; struct snddev_info *d; u_long xcmd; int *arg_i, ret, tmp, err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; if (PCM_DETACHING(d) || !DSP_REGISTERED(d)) return (EBADF); PCM_GIANT_ENTER(d); arg_i = (int *)arg; ret = 0; xcmd = 0; chn = NULL; if (IOCGROUP(cmd) == 'M') { if (cmd == OSS_GETVERSION) { *arg_i = SOUND_VERSION; PCM_GIANT_EXIT(d); return (0); } ret = dsp_ioctl_channel(priv, priv->volch, cmd, arg); if (ret != -1) { PCM_GIANT_EXIT(d); return (ret); } if (d->mixer_dev != NULL) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } else ret = EBADF; PCM_GIANT_EXIT(d); return (ret); } /* * Certain ioctls may be made on any type of device (audio, mixer, * and MIDI). Handle those special cases here. */ if (IOCGROUP(cmd) == 'X') { PCM_ACQUIRE_QUICK(d); switch(cmd) { case SNDCTL_SYSINFO: sound_oss_sysinfo((oss_sysinfo *)arg); break; case SNDCTL_CARDINFO: ret = sound_oss_card_info((oss_card_info *)arg); break; case SNDCTL_AUDIOINFO: ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg, false); break; case SNDCTL_AUDIOINFO_EX: ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg, true); break; case SNDCTL_ENGINEINFO: ret = dsp_oss_engineinfo(i_dev, (oss_audioinfo *)arg); break; case SNDCTL_MIXERINFO: ret = mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg); break; default: ret = EINVAL; } PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (ret); } getchns(priv, 0); rdch = priv->rdch; wrch = priv->wrch; if (wrch != NULL && (wrch->flags & CHN_F_DEAD)) wrch = NULL; if (rdch != NULL && (rdch->flags & CHN_F_DEAD)) rdch = NULL; if (wrch == NULL && rdch == NULL) { PCM_GIANT_EXIT(d); return (EINVAL); } switch(cmd) { #ifdef OLDPCM_IOCTL /* * we start with the new ioctl interface. */ case AIONWRITE: /* how many bytes can write ? */ if (wrch) { CHN_LOCK(wrch); /* if (wrch && wrch->bufhard.dl) while (chn_wrfeed(wrch) == 0); */ *arg_i = sndbuf_getfree(wrch->bufsoft); CHN_UNLOCK(wrch); } else { *arg_i = 0; ret = EINVAL; } break; case AIOSSIZE: /* set the current blocksize */ { struct snd_size *p = (struct snd_size *)arg; p->play_size = 0; p->rec_size = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); chn_setblocksize(wrch, 2, p->play_size); p->play_size = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_setblocksize(rdch, 2, p->rec_size); p->rec_size = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); } break; case AIOGSIZE: /* get the current blocksize */ { struct snd_size *p = (struct snd_size *)arg; if (wrch) { CHN_LOCK(wrch); p->play_size = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); p->rec_size = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); } } break; case AIOSFMT: case AIOGFMT: { snd_chan_param *p = (snd_chan_param *)arg; if (cmd == AIOSFMT && ((p->play_format != 0 && p->play_rate == 0) || (p->rec_format != 0 && p->rec_rate == 0))) { ret = EINVAL; break; } PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); if (cmd == AIOSFMT && p->play_format != 0) { chn_setformat(wrch, SND_FORMAT(p->play_format, AFMT_CHANNEL(wrch->format), AFMT_EXTCHANNEL(wrch->format))); chn_setspeed(wrch, p->play_rate); } p->play_rate = wrch->speed; p->play_format = AFMT_ENCODING(wrch->format); CHN_UNLOCK(wrch); } else { p->play_rate = 0; p->play_format = 0; } if (rdch) { CHN_LOCK(rdch); if (cmd == AIOSFMT && p->rec_format != 0) { chn_setformat(rdch, SND_FORMAT(p->rec_format, AFMT_CHANNEL(rdch->format), AFMT_EXTCHANNEL(rdch->format))); chn_setspeed(rdch, p->rec_rate); } p->rec_rate = rdch->speed; p->rec_format = AFMT_ENCODING(rdch->format); CHN_UNLOCK(rdch); } else { p->rec_rate = 0; p->rec_format = 0; } PCM_RELEASE_QUICK(d); } break; case AIOGCAP: /* get capabilities */ { snd_capabilities *p = (snd_capabilities *)arg; struct pcmchan_caps *pcaps = NULL, *rcaps = NULL; struct cdev *pdev; PCM_LOCK(d); if (rdch) { CHN_LOCK(rdch); rcaps = chn_getcaps(rdch); } if (wrch) { CHN_LOCK(wrch); pcaps = chn_getcaps(wrch); } p->rate_min = max(rcaps? rcaps->minspeed : 0, pcaps? pcaps->minspeed : 0); p->rate_max = min(rcaps? rcaps->maxspeed : 1000000, pcaps? pcaps->maxspeed : 1000000); p->bufsize = min(rdch? sndbuf_getsize(rdch->bufsoft) : 1000000, wrch? sndbuf_getsize(wrch->bufsoft) : 1000000); /* XXX bad on sb16 */ p->formats = (rdch? chn_getformats(rdch) : 0xffffffff) & (wrch? chn_getformats(wrch) : 0xffffffff); if (rdch && wrch) { p->formats |= (pcm_getflags(d->dev) & SD_F_SIMPLEX) ? 0 : AFMT_FULLDUPLEX; } pdev = d->mixer_dev; p->mixers = 1; /* default: one mixer */ p->inputs = pdev->si_drv1? mix_getdevs(pdev->si_drv1) : 0; p->left = p->right = 100; if (wrch) CHN_UNLOCK(wrch); if (rdch) CHN_UNLOCK(rdch); PCM_UNLOCK(d); } break; case AIOSTOP: if (*arg_i == AIOSYNC_PLAY && wrch) { CHN_LOCK(wrch); *arg_i = chn_abort(wrch); CHN_UNLOCK(wrch); } else if (*arg_i == AIOSYNC_CAPTURE && rdch) { CHN_LOCK(rdch); *arg_i = chn_abort(rdch); CHN_UNLOCK(rdch); } else { printf("AIOSTOP: bad channel 0x%x\n", *arg_i); *arg_i = 0; } break; case AIOSYNC: printf("AIOSYNC chan 0x%03lx pos %lu unimplemented\n", ((snd_sync_parm *)arg)->chan, ((snd_sync_parm *)arg)->pos); break; #endif /* * here follow the standard ioctls (filio.h etc.) */ case FIONREAD: /* get # bytes to read */ if (rdch) { CHN_LOCK(rdch); /* if (rdch && rdch->bufhard.dl) while (chn_rdfeed(rdch) == 0); */ *arg_i = sndbuf_getready(rdch->bufsoft); CHN_UNLOCK(rdch); } else { *arg_i = 0; ret = EINVAL; } break; case FIOASYNC: /*set/clear async i/o */ DEB( printf("FIOASYNC\n") ; ) break; case SNDCTL_DSP_NONBLOCK: /* set non-blocking i/o */ case FIONBIO: /* set/clear non-blocking i/o */ if (rdch) { CHN_LOCK(rdch); if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) rdch->flags |= CHN_F_NBIO; else rdch->flags &= ~CHN_F_NBIO; CHN_UNLOCK(rdch); } if (wrch) { CHN_LOCK(wrch); if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) wrch->flags |= CHN_F_NBIO; else wrch->flags &= ~CHN_F_NBIO; CHN_UNLOCK(wrch); } break; /* * Finally, here is the linux-compatible ioctl interface */ #define THE_REAL_SNDCTL_DSP_GETBLKSIZE _IOWR('P', 4, int) case THE_REAL_SNDCTL_DSP_GETBLKSIZE: case SNDCTL_DSP_GETBLKSIZE: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = sndbuf_getblksz(chn->bufsoft); CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETBLKSIZE: RANGE(*arg_i, 16, 65536); PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); chn_setblocksize(wrch, 2, *arg_i); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_setblocksize(rdch, 2, *arg_i); CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_RESET: DEB(printf("dsp reset\n")); if (wrch) { CHN_LOCK(wrch); chn_abort(wrch); chn_resetbuf(wrch); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_abort(rdch); chn_resetbuf(rdch); CHN_UNLOCK(rdch); } break; case SNDCTL_DSP_SYNC: DEB(printf("dsp sync\n")); /* chn_sync may sleep */ if (wrch) { CHN_LOCK(wrch); chn_sync(wrch, 0); CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_SPEED: /* chn_setspeed may sleep */ tmp = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setspeed(wrch, *arg_i); tmp = wrch->speed; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setspeed(rdch, *arg_i); if (tmp == 0) tmp = rdch->speed; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; break; case SOUND_PCM_READ_RATE: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = chn->speed; CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_STEREO: tmp = -1; *arg_i = (*arg_i)? 2 : 1; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(wrch->format, *arg_i, 0)); tmp = (AFMT_CHANNEL(wrch->format) > 1)? 1 : 0; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(rdch->format, *arg_i, 0)); if (tmp == -1) tmp = (AFMT_CHANNEL(rdch->format) > 1)? 1 : 0; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; break; case SOUND_PCM_WRITE_CHANNELS: /* case SNDCTL_DSP_CHANNELS: ( == SOUND_PCM_WRITE_CHANNELS) */ if (*arg_i < 0 || *arg_i > AFMT_CHANNEL_MAX) { *arg_i = 0; ret = EINVAL; break; } if (*arg_i != 0) { uint32_t ext = 0; tmp = 0; /* * Map channel number to surround sound formats. * Devices that need bitperfect mode to operate * (e.g. more than SND_CHN_MAX channels) are not * subject to any mapping. */ if (!(pcm_getflags(d->dev) & SD_F_BITPERFECT)) { struct pcmchan_matrix *m; if (*arg_i > SND_CHN_MAX) *arg_i = SND_CHN_MAX; m = feeder_matrix_default_channel_map(*arg_i); if (m != NULL) ext = m->ext; } PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(wrch->format, *arg_i, ext)); tmp = AFMT_CHANNEL(wrch->format); CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(rdch->format, *arg_i, ext)); if (tmp == 0) tmp = AFMT_CHANNEL(rdch->format); CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; } else { chn = wrch ? wrch : rdch; CHN_LOCK(chn); *arg_i = AFMT_CHANNEL(chn->format); CHN_UNLOCK(chn); } break; case SOUND_PCM_READ_CHANNELS: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = AFMT_CHANNEL(chn->format); CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_GETFMTS: /* returns a mask of supported fmts */ chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = chn_getformats(chn); CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETFMT: /* sets _one_ format */ if (*arg_i != AFMT_QUERY) { tmp = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(*arg_i, AFMT_CHANNEL(wrch->format), AFMT_EXTCHANNEL(wrch->format))); tmp = wrch->format; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(*arg_i, AFMT_CHANNEL(rdch->format), AFMT_EXTCHANNEL(rdch->format))); if (tmp == 0) tmp = rdch->format; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = AFMT_ENCODING(tmp); } else { chn = wrch ? wrch : rdch; CHN_LOCK(chn); *arg_i = AFMT_ENCODING(chn->format); CHN_UNLOCK(chn); } break; case SNDCTL_DSP_SETFRAGMENT: DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg)); { uint32_t fragln = (*arg_i) & 0x0000ffff; uint32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16; uint32_t fragsz; uint32_t r_maxfrags, r_fragsz; RANGE(fragln, 4, 16); fragsz = 1 << fragln; if (maxfrags == 0) maxfrags = CHN_2NDBUFMAXSIZE / fragsz; if (maxfrags < 2) maxfrags = 2; if (maxfrags * fragsz > CHN_2NDBUFMAXSIZE) maxfrags = CHN_2NDBUFMAXSIZE / fragsz; DEB(printf("SNDCTL_DSP_SETFRAGMENT %d frags, %d sz\n", maxfrags, fragsz)); PCM_ACQUIRE_QUICK(d); if (rdch) { CHN_LOCK(rdch); ret = chn_setblocksize(rdch, maxfrags, fragsz); r_maxfrags = sndbuf_getblkcnt(rdch->bufsoft); r_fragsz = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); } else { r_maxfrags = maxfrags; r_fragsz = fragsz; } if (wrch && ret == 0) { CHN_LOCK(wrch); ret = chn_setblocksize(wrch, maxfrags, fragsz); maxfrags = sndbuf_getblkcnt(wrch->bufsoft); fragsz = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); } else { /* use whatever came from the read channel */ maxfrags = r_maxfrags; fragsz = r_fragsz; } PCM_RELEASE_QUICK(d); fragln = 0; while (fragsz > 1) { fragln++; fragsz >>= 1; } *arg_i = (maxfrags << 16) | fragln; } break; case SNDCTL_DSP_GETISPACE: /* return the size of data available in the input queue */ { audio_buf_info *a = (audio_buf_info *)arg; if (rdch) { struct snd_dbuf *bs = rdch->bufsoft; CHN_LOCK(rdch); a->bytes = sndbuf_getready(bs); a->fragments = a->bytes / sndbuf_getblksz(bs); a->fragstotal = sndbuf_getblkcnt(bs); a->fragsize = sndbuf_getblksz(bs); CHN_UNLOCK(rdch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETOSPACE: /* return space available in the output queue */ { audio_buf_info *a = (audio_buf_info *)arg; if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); /* XXX abusive DMA update: chn_wrupdate(wrch); */ a->bytes = sndbuf_getfree(bs); a->fragments = a->bytes / sndbuf_getblksz(bs); a->fragstotal = sndbuf_getblkcnt(bs); a->fragsize = sndbuf_getblksz(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETIPTR: { count_info *a = (count_info *)arg; if (rdch) { struct snd_dbuf *bs = rdch->bufsoft; CHN_LOCK(rdch); /* XXX abusive DMA update: chn_rdupdate(rdch); */ a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - rdch->blocks; a->ptr = sndbuf_getfreeptr(bs); rdch->blocks = sndbuf_getblocks(bs); CHN_UNLOCK(rdch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETOPTR: { count_info *a = (count_info *)arg; if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); /* XXX abusive DMA update: chn_wrupdate(wrch); */ a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - wrch->blocks; a->ptr = sndbuf_getreadyptr(bs); wrch->blocks = sndbuf_getblocks(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETCAPS: PCM_LOCK(d); *arg_i = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER; if (rdch && wrch && !(pcm_getflags(d->dev) & SD_F_SIMPLEX)) *arg_i |= PCM_CAP_DUPLEX; if (rdch && (rdch->flags & CHN_F_VIRTUAL) != 0) *arg_i |= PCM_CAP_VIRTUAL; if (wrch && (wrch->flags & CHN_F_VIRTUAL) != 0) *arg_i |= PCM_CAP_VIRTUAL; PCM_UNLOCK(d); break; case SOUND_PCM_READ_BITS: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); if (chn->format & AFMT_8BIT) *arg_i = 8; else if (chn->format & AFMT_16BIT) *arg_i = 16; else if (chn->format & AFMT_24BIT) *arg_i = 24; else if (chn->format & AFMT_32BIT) *arg_i = 32; else ret = EINVAL; CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETTRIGGER: if (rdch) { CHN_LOCK(rdch); rdch->flags &= ~CHN_F_NOTRIGGER; if (*arg_i & PCM_ENABLE_INPUT) chn_start(rdch, 1); else { chn_abort(rdch); chn_resetbuf(rdch); rdch->flags |= CHN_F_NOTRIGGER; } CHN_UNLOCK(rdch); } if (wrch) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_NOTRIGGER; if (*arg_i & PCM_ENABLE_OUTPUT) chn_start(wrch, 1); else { chn_abort(wrch); chn_resetbuf(wrch); wrch->flags |= CHN_F_NOTRIGGER; } CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_GETTRIGGER: *arg_i = 0; if (wrch) { CHN_LOCK(wrch); if (wrch->flags & CHN_F_TRIGGERED) *arg_i |= PCM_ENABLE_OUTPUT; CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); if (rdch->flags & CHN_F_TRIGGERED) *arg_i |= PCM_ENABLE_INPUT; CHN_UNLOCK(rdch); } break; case SNDCTL_DSP_GETODELAY: if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); /* XXX abusive DMA update: chn_wrupdate(wrch); */ *arg_i = sndbuf_getready(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; break; case SNDCTL_DSP_POST: if (wrch) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_NOTRIGGER; chn_start(wrch, 1); CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_SETDUPLEX: /* * switch to full-duplex mode if card is in half-duplex * mode and is able to work in full-duplex mode */ PCM_LOCK(d); if (rdch && wrch && (pcm_getflags(d->dev) & SD_F_SIMPLEX)) pcm_setflags(d->dev, pcm_getflags(d->dev)^SD_F_SIMPLEX); PCM_UNLOCK(d); 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; chn = rdch; } /* FALLTHROUGH */ case SNDCTL_DSP_SETRECVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_WRITE_RECLEV; chn = rdch; } /* FALLTHROUGH */ case SNDCTL_DSP_GETPLAYVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_READ_PCM; chn = wrch; } /* FALLTHROUGH */ case SNDCTL_DSP_SETPLAYVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_WRITE_PCM; chn = wrch; } ret = dsp_ioctl_channel(priv, chn, xcmd, arg); if (ret != -1) { PCM_GIANT_EXIT(d); return (ret); } if (d->mixer_dev != NULL) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, xcmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } 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) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } 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_getalign(b); oc->fifo_samples = (sndbuf_getready(b) - tmp) / sndbuf_getalign(b); #else oc->samples = sndbuf_gettotal(bs) / sndbuf_getalign(bs); oc->fifo_samples = sndbuf_getready(bs) / sndbuf_getalign(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 useful 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: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_syncgroup(wrch, rdch, (oss_syncgroup *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_SYNCSTART: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_syncstart(*arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_POLICY: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_policy(wrch, rdch, *arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_COOKEDMODE: PCM_ACQUIRE_QUICK(d); if (!(pcm_getflags(d->dev) & SD_F_BITPERFECT)) ret = dsp_oss_cookedmode(wrch, rdch, *arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_GET_CHNORDER: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_getchnorder(wrch, rdch, (unsigned long long *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_SET_CHNORDER: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_setchnorder(wrch, rdch, (unsigned long long *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_GETCHANNELMASK: /* XXX vlc */ PCM_ACQUIRE_QUICK(d); ret = dsp_oss_getchannelmask(wrch, rdch, (int *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_BIND_CHANNEL: /* XXX what?!? */ ret = EINVAL; 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; /* * XXX Once implemented, revisit this for proper cv protection * (if necessary). */ 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 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: /* undocumented */ case SNDCTL_DSP_SUBDIVIDE: case SOUND_PCM_WRITE_FILTER: case SOUND_PCM_READ_FILTER: /* dunno what these do, don't sound important */ default: DEB(printf("default ioctl fn 0x%08lx fail\n", cmd)); ret = EINVAL; break; } PCM_GIANT_LEAVE(d); return (ret); } static int dsp_poll(struct cdev *i_dev, int events, struct thread *td) { struct dsp_cdevpriv *priv; struct snddev_info *d; struct pcm_channel *wrch, *rdch; int ret, e, err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; if (PCM_DETACHING(d) || !DSP_REGISTERED(d)) { /* XXX many clients don't understand POLLNVAL */ return (events & (POLLHUP | POLLPRI | POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM)); } PCM_GIANT_ENTER(d); ret = 0; getchns(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); wrch = priv->wrch; rdch = priv->rdch; if (wrch != NULL && !(wrch->flags & CHN_F_DEAD)) { e = (events & (POLLOUT | POLLWRNORM)); if (e) ret |= chn_poll(wrch, e, td); } if (rdch != NULL && !(rdch->flags & CHN_F_DEAD)) { e = (events & (POLLIN | POLLRDNORM)); if (e) ret |= chn_poll(rdch, e, td); } relchns(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); PCM_GIANT_LEAVE(d); return (ret); } static int dsp_mmap(struct cdev *i_dev, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) { /* * offset is in range due to checks in dsp_mmap_single(). * XXX memattr is not honored. */ *paddr = vtophys(offset); return (0); } static int dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, vm_size_t size, struct vm_object **object, int nprot) { struct dsp_cdevpriv *priv; struct snddev_info *d; struct pcm_channel *wrch, *rdch, *c; int err; /* * Reject PROT_EXEC by default. It just doesn't makes sense. * Unfortunately, we have to give up this one due to linux_mmap * changes. * * https://lists.freebsd.org/pipermail/freebsd-emulation/2007-June/003698.html * */ #ifdef SV_ABI_LINUX if ((nprot & PROT_EXEC) && (dsp_mmap_allow_prot_exec < 0 || (dsp_mmap_allow_prot_exec == 0 && SV_CURPROC_ABI() != SV_ABI_LINUX))) #else if ((nprot & PROT_EXEC) && dsp_mmap_allow_prot_exec < 1) #endif return (EINVAL); /* * PROT_READ (alone) selects the input buffer. * PROT_WRITE (alone) selects the output buffer. * PROT_WRITE|PROT_READ together select the output buffer. */ if ((nprot & (PROT_READ | PROT_WRITE)) == 0) return (EINVAL); if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; if (PCM_DETACHING(d) || !DSP_REGISTERED(d)) return (EINVAL); PCM_GIANT_ENTER(d); getchns(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); wrch = priv->wrch; rdch = priv->rdch; c = ((nprot & PROT_WRITE) != 0) ? wrch : rdch; if (c == NULL || (c->flags & CHN_F_MMAP_INVALID) || (*offset + size) > sndbuf_getallocsize(c->bufsoft) || (wrch != NULL && (wrch->flags & CHN_F_MMAP_INVALID)) || (rdch != NULL && (rdch->flags & CHN_F_MMAP_INVALID))) { relchns(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); PCM_GIANT_EXIT(d); return (EINVAL); } if (wrch != NULL) wrch->flags |= CHN_F_MMAP; if (rdch != NULL) rdch->flags |= CHN_F_MMAP; *offset = (uintptr_t)sndbuf_getbufofs(c->bufsoft, *offset); relchns(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); *object = vm_pager_allocate(OBJT_DEVICE, i_dev, size, nprot, *offset, curthread->td_ucred); PCM_GIANT_LEAVE(d); if (*object == NULL) return (EINVAL); return (0); } static void dsp_clone(void *arg, struct ucred *cred, char *name, int namelen, struct cdev **dev) { struct snddev_info *d; size_t i; if (*dev != NULL) return; if (strcmp(name, "dsp") == 0 && dsp_basename_clone) goto found; for (i = 0; i < nitems(dsp_cdevs); i++) { if (dsp_cdevs[i].alias != NULL && strcmp(name, dsp_cdevs[i].name) == 0) goto found; } return; found: bus_topo_lock(); d = devclass_get_softc(pcm_devclass, snd_unit); /* * If we only have a single soundcard attached and we detach it right * before entering dsp_clone(), there is a chance pcm_unregister() will * have returned already, meaning it will have set snd_unit to -1, and * thus devclass_get_softc() will return NULL here. */ if (d != NULL && PCM_REGISTERED(d) && d->dsp_dev != NULL) { *dev = d->dsp_dev; dev_ref(*dev); } bus_topo_unlock(); } static void dsp_sysinit(void *p) { if (dsp_ehtag != NULL) return; dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000); } static void dsp_sysuninit(void *p) { if (dsp_ehtag == NULL) return; EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); dsp_ehtag = NULL; } SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL); SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL); char * dsp_unit2name(char *buf, size_t len, struct pcm_channel *ch) { size_t i; KASSERT(buf != NULL && len != 0, ("bogus buf=%p len=%ju", buf, (uintmax_t)len)); for (i = 0; i < nitems(dsp_cdevs); i++) { if (ch->type != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL) continue; snprintf(buf, len, "%s%d%s%d", dsp_cdevs[i].name, device_get_unit(ch->dev), dsp_cdevs[i].sep, ch->unit); return (buf); } return (NULL); } +static void +dsp_oss_audioinfo_unavail(oss_audioinfo *ai, int unit) +{ + bzero(ai, sizeof(*ai)); + ai->dev = unit; + snprintf(ai->name, sizeof(ai->name), "pcm%d (unavailable)", unit); + ai->pid = -1; + ai->card_number = unit; + ai->port_number = unit; + ai->mixer_dev = -1; + ai->legacy_device = unit; +} + /** * @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 * 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 * @param ex flag to distinguish between SNDCTL_AUDIOINFO from * SNDCTL_AUDIOINFO_EX * * @retval 0 success * @retval EINVAL ai->dev specifies an invalid device */ int dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai, bool ex) { struct pcmchan_caps *caps; struct pcm_channel *ch; struct snddev_info *d; uint32_t fmts; int i, minch, maxch, unit; /* * 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); for (unit = 0; pcm_devclass != NULL && unit < devclass_get_maxunit(pcm_devclass); unit++) { d = devclass_get_softc(pcm_devclass, unit); if (!PCM_REGISTERED(d)) { - d = NULL; - continue; + if ((ai->dev == -1 && unit == snd_unit) || + ai->dev == unit) { + dsp_oss_audioinfo_unavail(ai, unit); + return (0); + } else { + d = NULL; + continue; + } } PCM_UNLOCKASSERT(d); PCM_LOCK(d); if ((ai->dev == -1 && d->dsp_dev == i_dev) || (ai->dev == unit)) { PCM_UNLOCK(d); break; } else { PCM_UNLOCK(d); d = NULL; } } /* Exhausted the search -- nothing is locked, so return. */ if (d == NULL) return (EINVAL); /* XXX Need Giant magic entry ??? */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); bzero((void *)ai, sizeof(oss_audioinfo)); ai->dev = unit; strlcpy(ai->name, device_get_desc(d->dev), sizeof(ai->name)); ai->pid = -1; ai->card_number = -1; ai->port_number = -1; ai->mixer_dev = (d->mixer_dev != NULL) ? unit : -1; ai->legacy_device = unit; snprintf(ai->devnode, sizeof(ai->devnode), "/dev/dsp%d", unit); ai->enabled = device_is_attached(d->dev) ? 1 : 0; ai->next_play_engine = 0; ai->next_rec_engine = 0; ai->busy = 0; ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER; ai->iformats = 0; ai->oformats = 0; ai->min_rate = INT_MAX; ai->max_rate = 0; ai->min_channels = INT_MAX; ai->max_channels = 0; /* Gather global information about the device. */ CHN_FOREACH(ch, d, channels.pcm) { CHN_UNLOCKASSERT(ch); CHN_LOCK(ch); /* * Skip physical channels if we are servicing SNDCTL_AUDIOINFO, * or VCHANs if we are servicing SNDCTL_AUDIOINFO_EX. */ if ((ex && (ch->flags & CHN_F_VIRTUAL) != 0) || (!ex && (ch->flags & CHN_F_VIRTUAL) == 0)) { CHN_UNLOCK(ch); continue; } if ((ch->flags & CHN_F_BUSY) == 0) { ai->busy |= (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ; } ai->caps |= ((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) | ((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT); caps = chn_getcaps(ch); minch = INT_MAX; maxch = 0; fmts = 0; for (i = 0; caps->fmtlist[i]; i++) { fmts |= AFMT_ENCODING(caps->fmtlist[i]); minch = min(AFMT_CHANNEL(caps->fmtlist[i]), minch); maxch = max(AFMT_CHANNEL(caps->fmtlist[i]), maxch); } if (ch->direction == PCMDIR_PLAY) ai->oformats |= fmts; else ai->iformats |= fmts; ai->min_rate = min(ai->min_rate, caps->minspeed); ai->max_rate = max(ai->max_rate, caps->maxspeed); ai->min_channels = min(ai->min_channels, minch); ai->max_channels = max(ai->max_channels, maxch); CHN_UNLOCK(ch); } PCM_UNLOCK(d); return (0); } static int dsp_oss_engineinfo_cb(void *data, void *arg) { struct dsp_cdevpriv *priv = data; struct pcm_channel *ch = arg; if (DSP_REGISTERED(priv->sc) && (ch == priv->rdch || ch == priv->wrch)) return (1); return (0); } /** * @brief Handler for SNDCTL_ENGINEINFO * * Gathers information about the audio device's engine 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 * 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 */ int dsp_oss_engineinfo(struct cdev *i_dev, oss_audioinfo *ai) { struct pcmchan_caps *caps; struct pcm_channel *ch; struct snddev_info *d; uint32_t fmts; int i, nchan, *rates, minch, maxch, unit; char *devname, buf[CHN_NAMELEN]; /* * 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; devname = NULL; nchan = 0; bzero(buf, sizeof(buf)); /* * Search for the requested audio device (channel). Start by * iterating over pcm devices. */ for (unit = 0; pcm_devclass != NULL && unit < devclass_get_maxunit(pcm_devclass); unit++) { d = devclass_get_softc(pcm_devclass, unit); if (!PCM_REGISTERED(d)) continue; /* XXX Need Giant magic entry ??? */ /* See the note in function docblock */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); CHN_FOREACH(ch, d, channels.pcm) { CHN_UNLOCKASSERT(ch); CHN_LOCK(ch); if (ai->dev == -1) { if (devfs_foreach_cdevpriv(i_dev, dsp_oss_engineinfo_cb, ch) != 0) { devname = dsp_unit2name(buf, sizeof(buf), ch); } } else if (ai->dev == nchan) devname = dsp_unit2name(buf, sizeof(buf), ch); if (devname != NULL) break; CHN_UNLOCK(ch); ++nchan; } if (devname != NULL) { /* * At this point, the following synchronization stuff * has happened: * - a specific PCM device is locked. * - 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 PCM_CAP_DUPLEX is out. */ /** * @todo @c SNDCTL_AUDIOINFO::caps - Make drivers keep * these in pcmchan::caps? */ ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER | ((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) | ((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT); /* * 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 = INT_MAX; maxch = 0; fmts = 0; for (i = 0; caps->fmtlist[i]; i++) { fmts |= AFMT_ENCODING(caps->fmtlist[i]); minch = min(AFMT_CHANNEL(caps->fmtlist[i]), minch); maxch = max(AFMT_CHANNEL(caps->fmtlist[i]), 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) ? unit : -1; /** * @note * @c legacy_device - OSSv4 docs: "Obsolete." */ ai->legacy_device = -1; snprintf(ai->devnode, sizeof(ai->devnode), "/dev/dsp%d", unit); 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 > OSS_MAX_SAMPLE_RATES) ai->nrates = OSS_MAX_SAMPLE_RATES; for (i = 0; i < ai->nrates; i++) ai->rates[i] = rates[i]; ai->next_play_engine = 0; ai->next_rec_engine = 0; CHN_UNLOCK(ch); } PCM_UNLOCK(d); if (devname != NULL) return (0); } /* Exhausted the search -- nothing is locked, so return. */ return (EINVAL); } /** * @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 ((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, "pcmsg", timo); if (ret == EINTR || ret == ERESTART) break; needlocks = 1; ret = 0; /* Assumes ret == EAGAIN... */ } } } 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 ret; if (policy < CHN_POLICY_MIN || policy > CHN_POLICY_MAX) return (EIO); /* Default: success */ ret = 0; if (rdch) { CHN_LOCK(rdch); ret = chn_setlatency(rdch, policy); CHN_UNLOCK(rdch); } if (wrch && ret == 0) { CHN_LOCK(wrch); ret = chn_setlatency(wrch, policy); CHN_UNLOCK(wrch); } if (ret) ret = EIO; return (ret); } /** * @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. * * @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) { /* * XXX I just don't get it. Why don't they call it * "BITPERFECT" ~ SNDCTL_DSP_BITPERFECT !?!?. * This is just plain so confusing, incoherent, * . */ if (!(enabled == 1 || enabled == 0)) return (EINVAL); /* * I won't give in. I'm inverting its logic here and now. * Brag all you want, but "BITPERFECT" should be the better * term here. */ enabled ^= 0x00000001; if (wrch != NULL) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_BITPERFECT; wrch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000; CHN_UNLOCK(wrch); } if (rdch != NULL) { CHN_LOCK(rdch); rdch->flags &= ~CHN_F_BITPERFECT; rdch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000; CHN_UNLOCK(rdch); } return (0); } /** * @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) { struct pcm_channel *ch; int ret; ch = (wrch != NULL) ? wrch : rdch; if (ch != NULL) { CHN_LOCK(ch); ret = chn_oss_getorder(ch, map); CHN_UNLOCK(ch); } else ret = EINVAL; return (ret); } /** * @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) { int ret; ret = 0; if (wrch != NULL) { CHN_LOCK(wrch); ret = chn_oss_setorder(wrch, map); CHN_UNLOCK(wrch); } if (ret == 0 && rdch != NULL) { CHN_LOCK(rdch); ret = chn_oss_setorder(rdch, map); CHN_UNLOCK(rdch); } return (ret); } static int dsp_oss_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch, int *mask) { struct pcm_channel *ch; uint32_t chnmask; int ret; chnmask = 0; ch = (wrch != NULL) ? wrch : rdch; if (ch != NULL) { CHN_LOCK(ch); ret = chn_oss_getmask(ch, &chnmask); CHN_UNLOCK(ch); } else ret = EINVAL; if (ret == 0) *mask = chnmask; return (ret); } #ifdef OSSV4_EXPERIMENT /** * @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/mixer.c b/sys/dev/sound/pcm/mixer.c index 130333c7c442..0cf6a9f42f8f 100644 --- a/sys/dev/sound/pcm/mixer.c +++ b/sys/dev/sound/pcm/mixer.c @@ -1,1592 +1,1597 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include "feeder_if.h" #include "mixer_if.h" static MALLOC_DEFINE(M_MIXER, "mixer", "mixer"); static int mixer_bypass = 1; SYSCTL_INT(_hw_snd, OID_AUTO, vpc_mixer_bypass, CTLFLAG_RWTUN, &mixer_bypass, 0, "control channel pcm/rec volume, bypassing real mixer device"); #define MIXER_NAMELEN 16 struct snd_mixer { KOBJ_FIELDS; void *devinfo; int busy; int hwvol_mixer; int hwvol_step; int type; device_t dev; u_int32_t devs; u_int32_t mutedevs; u_int32_t recdevs; u_int32_t recsrc; u_int16_t level[32]; u_int16_t level_muted[32]; u_int8_t parent[32]; u_int32_t child[32]; u_int8_t realdev[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] = { [SOUND_MIXER_VOLUME] = 75, [SOUND_MIXER_BASS] = 50, [SOUND_MIXER_TREBLE] = 50, [SOUND_MIXER_SYNTH] = 75, [SOUND_MIXER_PCM] = 75, [SOUND_MIXER_SPEAKER] = 75, [SOUND_MIXER_LINE] = 75, [SOUND_MIXER_MIC] = 25, [SOUND_MIXER_CD] = 75, [SOUND_MIXER_IGAIN] = 0, [SOUND_MIXER_LINE1] = 75, [SOUND_MIXER_VIDEO] = 75, [SOUND_MIXER_RECLEV] = 75, [SOUND_MIXER_OGAIN] = 50, [SOUND_MIXER_MONITOR] = 75, }; static char* snd_mixernames[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; static d_open_t mixer_open; static d_close_t mixer_close; static d_ioctl_t mixer_ioctl; static struct cdevsw mixer_cdevsw = { .d_version = D_VERSION, .d_open = mixer_open, .d_close = mixer_close, .d_ioctl = mixer_ioctl, .d_name = "mixer", }; -/** - * Keeps a count of mixer devices; used only by OSSv4 SNDCTL_SYSINFO ioctl. - */ -int mixer_count = 0; - static eventhandler_tag mixer_ehtag = NULL; static struct cdev * mixer_get_devt(device_t dev) { struct snddev_info *snddev; snddev = device_get_softc(dev); return snddev->mixer_dev; } static int mixer_lookup(char *devname) { int i; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) if (strncmp(devname, snd_mixernames[i], strlen(snd_mixernames[i])) == 0) return i; return -1; } #define MIXER_SET_UNLOCK(x, y) do { \ if ((y) != 0) \ snd_mtxunlock((x)->lock); \ } while (0) #define MIXER_SET_LOCK(x, y) do { \ if ((y) != 0) \ snd_mtxlock((x)->lock); \ } while (0) static int mixer_set_softpcmvol(struct snd_mixer *m, struct snddev_info *d, u_int left, u_int right) { struct pcm_channel *c; int dropmtx, acquiremtx; if (!PCM_REGISTERED(d) || PCM_DETACHING(d)) return (EINVAL); if (mtx_owned(m->lock)) dropmtx = 1; else dropmtx = 0; if (!(d->flags & SD_F_MPSAFE) || mtx_owned(d->lock) != 0) acquiremtx = 0; else acquiremtx = 1; /* * Be careful here. If we're coming from cdev ioctl, it is OK to * not doing locking AT ALL (except on individual channel) since * we've been heavily guarded by pcm cv, or if we're still * under Giant influence. Since we also have mix_* calls, we cannot * assume such protection and just do the lock as usuall. */ MIXER_SET_UNLOCK(m, dropmtx); MIXER_SET_LOCK(d, acquiremtx); CHN_FOREACH(c, d, channels.pcm.busy) { CHN_LOCK(c); if (c->direction == PCMDIR_PLAY && (c->feederflags & (1 << FEEDER_VOLUME))) chn_setvolume_multi(c, SND_VOL_C_MASTER, left, right, (left + right) >> 1); CHN_UNLOCK(c); } MIXER_SET_UNLOCK(d, acquiremtx); MIXER_SET_LOCK(m, dropmtx); return (0); } static int mixer_set_eq(struct snd_mixer *m, struct snddev_info *d, u_int dev, u_int level) { struct pcm_channel *c; struct pcm_feeder *f; int tone, dropmtx, acquiremtx; if (dev == SOUND_MIXER_TREBLE) tone = FEEDEQ_TREBLE; else if (dev == SOUND_MIXER_BASS) tone = FEEDEQ_BASS; else return (EINVAL); if (!PCM_REGISTERED(d) || PCM_DETACHING(d)) return (EINVAL); if (mtx_owned(m->lock)) dropmtx = 1; else dropmtx = 0; if (!(d->flags & SD_F_MPSAFE) || mtx_owned(d->lock) != 0) acquiremtx = 0; else acquiremtx = 1; /* * Be careful here. If we're coming from cdev ioctl, it is OK to * not doing locking AT ALL (except on individual channel) since * we've been heavily guarded by pcm cv, or if we're still * under Giant influence. Since we also have mix_* calls, we cannot * assume such protection and just do the lock as usuall. */ MIXER_SET_UNLOCK(m, dropmtx); MIXER_SET_LOCK(d, acquiremtx); CHN_FOREACH(c, d, channels.pcm.busy) { CHN_LOCK(c); f = chn_findfeeder(c, FEEDER_EQ); if (f != NULL) (void)FEEDER_SET(f, tone, level); CHN_UNLOCK(c); } MIXER_SET_UNLOCK(d, acquiremtx); MIXER_SET_LOCK(m, dropmtx); return (0); } static int mixer_set(struct snd_mixer *m, u_int dev, u_int32_t muted, u_int lev) { struct snddev_info *d; u_int l, r, tl, tr; u_int32_t parent = SOUND_MIXER_NONE, child = 0; u_int32_t realdev; int i, dropmtx; if (m == NULL || dev >= SOUND_MIXER_NRDEVICES || (0 == (m->devs & (1 << dev)))) return (-1); l = min((lev & 0x00ff), 100); r = min(((lev & 0xff00) >> 8), 100); realdev = m->realdev[dev]; d = device_get_softc(m->dev); if (d == NULL) return (-1); /* It is safe to drop this mutex due to Giant. */ if (!(d->flags & SD_F_MPSAFE) && mtx_owned(m->lock) != 0) dropmtx = 1; else dropmtx = 0; /* Allow the volume to be "changed" while muted. */ if (muted & (1 << dev)) { m->level_muted[dev] = l | (r << 8); return (0); } MIXER_SET_UNLOCK(m, dropmtx); /* TODO: recursive handling */ parent = m->parent[dev]; if (parent >= SOUND_MIXER_NRDEVICES) parent = SOUND_MIXER_NONE; if (parent == SOUND_MIXER_NONE) child = m->child[dev]; if (parent != SOUND_MIXER_NONE) { tl = (l * (m->level[parent] & 0x00ff)) / 100; tr = (r * ((m->level[parent] & 0xff00) >> 8)) / 100; if (dev == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) (void)mixer_set_softpcmvol(m, d, tl, tr); else if (realdev != SOUND_MIXER_NONE && MIXER_SET(m, realdev, tl, tr) < 0) { MIXER_SET_LOCK(m, dropmtx); return (-1); } } else if (child != 0) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (!(child & (1 << i)) || m->parent[i] != dev) continue; realdev = m->realdev[i]; tl = (l * (m->level[i] & 0x00ff)) / 100; tr = (r * ((m->level[i] & 0xff00) >> 8)) / 100; if (i == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) (void)mixer_set_softpcmvol(m, d, tl, tr); else if (realdev != SOUND_MIXER_NONE) MIXER_SET(m, realdev, tl, tr); } realdev = m->realdev[dev]; if (realdev != SOUND_MIXER_NONE && MIXER_SET(m, realdev, l, r) < 0) { MIXER_SET_LOCK(m, dropmtx); return (-1); } } else { if (dev == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) (void)mixer_set_softpcmvol(m, d, l, r); else if ((dev == SOUND_MIXER_TREBLE || dev == SOUND_MIXER_BASS) && (d->flags & SD_F_EQ)) (void)mixer_set_eq(m, d, dev, (l + r) >> 1); else if (realdev != SOUND_MIXER_NONE && MIXER_SET(m, realdev, l, r) < 0) { MIXER_SET_LOCK(m, dropmtx); return (-1); } } MIXER_SET_LOCK(m, dropmtx); m->level[dev] = l | (r << 8); m->modify_counter++; return (0); } static int mixer_get(struct snd_mixer *mixer, int dev) { if ((dev < SOUND_MIXER_NRDEVICES) && (mixer->devs & (1 << dev))) { if (mixer->mutedevs & (1 << dev)) return (mixer->level_muted[dev]); else return (mixer->level[dev]); } else { return (-1); } } void mix_setmutedevs(struct snd_mixer *mixer, u_int32_t mutedevs) { u_int32_t delta; /* Filter out invalid values. */ mutedevs &= mixer->devs; delta = (mixer->mutedevs ^ mutedevs) & mixer->devs; mixer->mutedevs = mutedevs; for (int i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (!(delta & (1 << i))) continue; if (mutedevs & (1 << i)) { mixer->level_muted[i] = mixer->level[i]; mixer_set(mixer, i, 0, 0); } else { mixer_set(mixer, i, 0, mixer->level_muted[i]); } } } static int mixer_setrecsrc(struct snd_mixer *mixer, u_int32_t src) { struct snddev_info *d; u_int32_t recsrc; int dropmtx; d = device_get_softc(mixer->dev); if (d == NULL) return -1; if (!(d->flags & SD_F_MPSAFE) && mtx_owned(mixer->lock) != 0) dropmtx = 1; else dropmtx = 0; src &= mixer->recdevs; if (src == 0) src = mixer->recdevs & SOUND_MASK_MIC; if (src == 0) src = mixer->recdevs & SOUND_MASK_MONITOR; if (src == 0) src = mixer->recdevs & SOUND_MASK_LINE; if (src == 0 && mixer->recdevs != 0) src = (1 << (ffs(mixer->recdevs) - 1)); /* It is safe to drop this mutex due to Giant. */ MIXER_SET_UNLOCK(mixer, dropmtx); recsrc = MIXER_SETRECSRC(mixer, src); MIXER_SET_LOCK(mixer, dropmtx); mixer->recsrc = recsrc; return 0; } static int 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) { struct snddev_info *d; int i; if (m == NULL) return; d = device_get_softc(m->dev); if (d != NULL && (d->flags & SD_F_SOFTPCMVOL)) v |= SOUND_MASK_PCM; if (d != NULL && (d->flags & SD_F_EQ)) v |= SOUND_MASK_TREBLE | SOUND_MASK_BASS; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (m->parent[i] < SOUND_MIXER_NRDEVICES) v |= 1 << m->parent[i]; v |= m->child[i]; } 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; } void mix_setparentchild(struct snd_mixer *m, u_int32_t parent, u_int32_t childs) { u_int32_t mask = 0; int i; if (m == NULL || parent >= SOUND_MIXER_NRDEVICES) return; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (i == parent) continue; if (childs & (1 << i)) { mask |= 1 << i; if (m->parent[i] < SOUND_MIXER_NRDEVICES) m->child[m->parent[i]] &= ~(1 << i); m->parent[i] = parent; m->child[i] = 0; } } mask &= ~(1 << parent); m->child[parent] = mask; } void mix_setrealdev(struct snd_mixer *m, u_int32_t dev, u_int32_t realdev) { if (m == NULL || dev >= SOUND_MIXER_NRDEVICES || !(realdev == SOUND_MIXER_NONE || realdev < SOUND_MIXER_NRDEVICES)) return; m->realdev[dev] = realdev; } u_int32_t mix_getparent(struct snd_mixer *m, u_int32_t dev) { if (m == NULL || dev >= SOUND_MIXER_NRDEVICES) return SOUND_MIXER_NONE; return m->parent[dev]; } u_int32_t mix_getchild(struct snd_mixer *m, u_int32_t dev) { if (m == NULL || dev >= SOUND_MIXER_NRDEVICES) return 0; return m->child[dev]; } u_int32_t mix_getdevs(struct snd_mixer *m) { return m->devs; } u_int32_t mix_getmutedevs(struct snd_mixer *m) { return m->mutedevs; } u_int32_t mix_getrecdevs(struct snd_mixer *m) { return m->recdevs; } void * mix_getdevinfo(struct snd_mixer *m) { return m->devinfo; } static struct snd_mixer * mixer_obj_create(device_t dev, kobj_class_t cls, void *devinfo, int type, const char *desc) { struct snd_mixer *m; size_t i; KASSERT(dev != NULL && cls != NULL && devinfo != NULL, ("%s(): NULL data dev=%p cls=%p devinfo=%p", __func__, dev, cls, devinfo)); KASSERT(type == MIXER_TYPE_PRIMARY || type == MIXER_TYPE_SECONDARY, ("invalid mixer type=%d", type)); m = (struct snd_mixer *)kobj_create(cls, M_MIXER, M_WAITOK | M_ZERO); snprintf(m->name, sizeof(m->name), "%s:mixer", device_get_nameunit(dev)); if (desc != NULL) { strlcat(m->name, ":", sizeof(m->name)); strlcat(m->name, desc, sizeof(m->name)); } m->lock = snd_mtxcreate(m->name, (type == MIXER_TYPE_PRIMARY) ? "primary pcm mixer" : "secondary pcm mixer"); m->type = type; m->devinfo = devinfo; m->busy = 0; m->dev = dev; for (i = 0; i < nitems(m->parent); i++) { m->parent[i] = SOUND_MIXER_NONE; m->child[i] = 0; m->realdev[i] = i; } if (MIXER_INIT(m)) { snd_mtxlock(m->lock); snd_mtxfree(m->lock); kobj_delete((kobj_t)m, M_MIXER); return (NULL); } return (m); } int mixer_delete(struct snd_mixer *m) { KASSERT(m != NULL, ("NULL snd_mixer")); KASSERT(m->type == MIXER_TYPE_SECONDARY, ("%s(): illegal mixer type=%d", __func__, m->type)); /* mixer uninit can sleep --hps */ MIXER_UNINIT(m); snd_mtxfree(m->lock); kobj_delete((kobj_t)m, M_MIXER); - --mixer_count; - return (0); } struct snd_mixer * mixer_create(device_t dev, kobj_class_t cls, void *devinfo, const char *desc) { - struct snd_mixer *m; - - m = mixer_obj_create(dev, cls, devinfo, MIXER_TYPE_SECONDARY, desc); - - if (m != NULL) - ++mixer_count; - - return (m); + return (mixer_obj_create(dev, cls, devinfo, MIXER_TYPE_SECONDARY, desc)); } int mixer_init(device_t dev, kobj_class_t cls, void *devinfo) { struct snddev_info *snddev; struct snd_mixer *m; u_int16_t v; struct cdev *pdev; const char *name; int i, unit, val; snddev = device_get_softc(dev); if (snddev == NULL) return (-1); name = device_get_name(dev); unit = device_get_unit(dev); if (resource_int_value(name, unit, "eq", &val) == 0 && val != 0) { snddev->flags |= SD_F_EQ; if ((val & SD_F_EQ_MASK) == val) snddev->flags |= val; else snddev->flags |= SD_F_EQ_DEFAULT; snddev->eqpreamp = 0; } m = mixer_obj_create(dev, cls, devinfo, MIXER_TYPE_PRIMARY, NULL); if (m == NULL) return (-1); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { v = snd_mixerdefaults[i]; if (resource_int_value(name, unit, snd_mixernames[i], &val) == 0) { if (val >= 0 && val <= 100) { v = (u_int16_t) val; } } mixer_set(m, i, 0, v | (v << 8)); } mixer_setrecsrc(m, 0); /* Set default input. */ pdev = make_dev(&mixer_cdevsw, SND_DEV_CTL, UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit); pdev->si_drv1 = m; snddev->mixer_dev = pdev; - ++mixer_count; - if (bootverbose) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (!(m->devs & (1 << i))) continue; if (m->realdev[i] != i) { device_printf(dev, "Mixer \"%s\" -> \"%s\":", snd_mixernames[i], (m->realdev[i] < SOUND_MIXER_NRDEVICES) ? snd_mixernames[m->realdev[i]] : "none"); } else { device_printf(dev, "Mixer \"%s\":", snd_mixernames[i]); } if (m->parent[i] < SOUND_MIXER_NRDEVICES) printf(" parent=\"%s\"", snd_mixernames[m->parent[i]]); if (m->child[i] != 0) printf(" child=0x%08x", m->child[i]); printf("\n"); } if (snddev->flags & SD_F_SOFTPCMVOL) device_printf(dev, "Soft PCM mixer ENABLED\n"); if (snddev->flags & SD_F_EQ) device_printf(dev, "EQ Treble/Bass ENABLED\n"); } return (0); } int mixer_uninit(device_t dev) { int i; struct snddev_info *d; struct snd_mixer *m; struct cdev *pdev; d = device_get_softc(dev); pdev = mixer_get_devt(dev); if (d == NULL || pdev == NULL || pdev->si_drv1 == NULL) return EBADF; m = pdev->si_drv1; KASSERT(m != NULL, ("NULL snd_mixer")); KASSERT(m->type == MIXER_TYPE_PRIMARY, ("%s(): illegal mixer type=%d", __func__, m->type)); pdev->si_drv1 = NULL; destroy_dev(pdev); snd_mtxlock(m->lock); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) mixer_set(m, i, 0, 0); mixer_setrecsrc(m, SOUND_MASK_MIC); snd_mtxunlock(m->lock); /* mixer uninit can sleep --hps */ MIXER_UNINIT(m); snd_mtxfree(m->lock); kobj_delete((kobj_t)m, M_MIXER); d->mixer_dev = NULL; - --mixer_count; - return 0; } int mixer_reinit(device_t dev) { struct snd_mixer *m; struct cdev *pdev; int i; pdev = mixer_get_devt(dev); m = pdev->si_drv1; snd_mtxlock(m->lock); i = MIXER_REINIT(m); if (i) { snd_mtxunlock(m->lock); return i; } for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (m->mutedevs & (1 << i)) mixer_set(m, i, 0, 0); else mixer_set(m, i, 0, m->level[i]); } mixer_setrecsrc(m, m->recsrc); snd_mtxunlock(m->lock); return 0; } static int sysctl_hw_snd_hwvol_mixer(SYSCTL_HANDLER_ARGS) { char devname[32]; int error, dev; struct snd_mixer *m; m = oidp->oid_arg1; snd_mtxlock(m->lock); strlcpy(devname, snd_mixernames[m->hwvol_mixer], sizeof(devname)); snd_mtxunlock(m->lock); error = sysctl_handle_string(oidp, &devname[0], sizeof(devname), req); snd_mtxlock(m->lock); if (error == 0 && req->newptr != NULL) { dev = mixer_lookup(devname); if (dev == -1) { snd_mtxunlock(m->lock); return EINVAL; } else { m->hwvol_mixer = dev; } } snd_mtxunlock(m->lock); return error; } int mixer_hwvol_init(device_t dev) { struct snd_mixer *m; struct cdev *pdev; pdev = mixer_get_devt(dev); m = pdev->si_drv1; m->hwvol_mixer = SOUND_MIXER_VOLUME; m->hwvol_step = 5; SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_step", CTLFLAG_RWTUN, &m->hwvol_step, 0, ""); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_mixer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, m, 0, sysctl_hw_snd_hwvol_mixer, "A", ""); return 0; } void mixer_hwvol_mute_locked(struct snd_mixer *m) { mix_setmutedevs(m, m->mutedevs ^ (1 << m->hwvol_mixer)); } void mixer_hwvol_mute(device_t dev) { struct snd_mixer *m; struct cdev *pdev; pdev = mixer_get_devt(dev); m = pdev->si_drv1; snd_mtxlock(m->lock); mixer_hwvol_mute_locked(m); snd_mtxunlock(m->lock); } void mixer_hwvol_step_locked(struct snd_mixer *m, int left_step, int right_step) { int level, left, right; level = mixer_get(m, m->hwvol_mixer); if (level != -1) { left = level & 0xff; right = (level >> 8) & 0xff; left += left_step * m->hwvol_step; if (left < 0) left = 0; else if (left > 100) left = 100; right += right_step * m->hwvol_step; if (right < 0) right = 0; else if (right > 100) right = 100; mixer_set(m, m->hwvol_mixer, m->mutedevs, left | right << 8); } } void mixer_hwvol_step(device_t dev, int left_step, int right_step) { struct snd_mixer *m; struct cdev *pdev; pdev = mixer_get_devt(dev); m = pdev->si_drv1; snd_mtxlock(m->lock); mixer_hwvol_step_locked(m, left_step, right_step); snd_mtxunlock(m->lock); } int mixer_busy(struct snd_mixer *m) { KASSERT(m != NULL, ("NULL snd_mixer")); return (m->busy); } int mix_set(struct snd_mixer *m, u_int dev, u_int left, u_int right) { int ret; KASSERT(m != NULL, ("NULL snd_mixer")); snd_mtxlock(m->lock); ret = mixer_set(m, dev, m->mutedevs, left | (right << 8)); snd_mtxunlock(m->lock); return ((ret != 0) ? ENXIO : 0); } int mix_get(struct snd_mixer *m, u_int dev) { int ret; KASSERT(m != NULL, ("NULL snd_mixer")); snd_mtxlock(m->lock); ret = mixer_get(m, dev); snd_mtxunlock(m->lock); return (ret); } int mix_setrecsrc(struct snd_mixer *m, u_int32_t src) { int ret; KASSERT(m != NULL, ("NULL snd_mixer")); snd_mtxlock(m->lock); ret = mixer_setrecsrc(m, src); snd_mtxunlock(m->lock); return ((ret != 0) ? ENXIO : 0); } u_int32_t mix_getrecsrc(struct snd_mixer *m) { u_int32_t ret; KASSERT(m != NULL, ("NULL snd_mixer")); snd_mtxlock(m->lock); ret = mixer_getrecsrc(m); snd_mtxunlock(m->lock); return (ret); } int mix_get_type(struct snd_mixer *m) { KASSERT(m != NULL, ("NULL snd_mixer")); return (m->type); } device_t mix_get_dev(struct snd_mixer *m) { KASSERT(m != NULL, ("NULL snd_mixer")); return (m->dev); } /* ----------------------------------------------------------------------- */ static int mixer_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct snddev_info *d; struct snd_mixer *m; if (i_dev == NULL || i_dev->si_drv1 == NULL) return (EBADF); m = i_dev->si_drv1; d = device_get_softc(m->dev); if (!PCM_REGISTERED(d) || PCM_DETACHING(d)) return (EBADF); /* XXX Need Giant magic entry ??? */ snd_mtxlock(m->lock); m->busy = 1; snd_mtxunlock(m->lock); return (0); } static int mixer_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct snddev_info *d; struct snd_mixer *m; int ret; if (i_dev == NULL || i_dev->si_drv1 == NULL) return (EBADF); m = i_dev->si_drv1; d = device_get_softc(m->dev); if (!PCM_REGISTERED(d)) return (EBADF); /* XXX Need Giant magic entry ??? */ snd_mtxlock(m->lock); ret = (m->busy == 0) ? EBADF : 0; m->busy = 0; snd_mtxunlock(m->lock); return (ret); } static int mixer_ioctl_channel(struct cdev *dev, u_long cmd, caddr_t arg, int mode, struct thread *td, int from) { struct snddev_info *d; struct snd_mixer *m; struct pcm_channel *c, *rdch, *wrch; pid_t pid; int j, ret; if (td == NULL || td->td_proc == NULL) return (-1); m = dev->si_drv1; d = device_get_softc(m->dev); j = cmd & 0xff; switch (j) { case SOUND_MIXER_PCM: case SOUND_MIXER_RECLEV: case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: break; default: return (-1); break; } pid = td->td_proc->p_pid; rdch = NULL; wrch = NULL; c = NULL; ret = -1; /* * This is unfair. Imagine single proc opening multiple * instances of same direction. What we do right now * is looking for the first matching proc/pid, and just * that. Nothing more. Consider it done. * * The better approach of controlling specific channel * pcm or rec volume is by doing mixer ioctl * (SNDCTL_DSP_[SET|GET][PLAY|REC]VOL / SOUND_MIXER_[PCM|RECLEV] * on its open fd, rather than cracky mixer bypassing here. */ CHN_FOREACH(c, d, channels.pcm.opened) { CHN_LOCK(c); if (c->pid != pid || !(c->feederflags & (1 << FEEDER_VOLUME))) { CHN_UNLOCK(c); continue; } if (rdch == NULL && c->direction == PCMDIR_REC) { rdch = c; if (j == SOUND_MIXER_RECLEV) goto mixer_ioctl_channel_proc; } else if (wrch == NULL && c->direction == PCMDIR_PLAY) { wrch = c; if (j == SOUND_MIXER_PCM) goto mixer_ioctl_channel_proc; } CHN_UNLOCK(c); if (rdch != NULL && wrch != NULL) break; } if (rdch == NULL && wrch == NULL) return (-1); if ((j == SOUND_MIXER_DEVMASK || j == SOUND_MIXER_CAPS || j == SOUND_MIXER_STEREODEVS) && (cmd & ~0xff) == MIXER_READ(0)) { snd_mtxlock(m->lock); *(int *)arg = mix_getdevs(m); snd_mtxunlock(m->lock); if (rdch != NULL) *(int *)arg |= SOUND_MASK_RECLEV; if (wrch != NULL) *(int *)arg |= SOUND_MASK_PCM; ret = 0; } return (ret); mixer_ioctl_channel_proc: KASSERT(c != NULL, ("%s(): NULL channel", __func__)); CHN_LOCKASSERT(c); if ((cmd & ~0xff) == MIXER_WRITE(0)) { int left, right, center; left = *(int *)arg & 0x7f; right = (*(int *)arg >> 8) & 0x7f; center = (left + right) >> 1; chn_setvolume_multi(c, SND_VOL_C_PCM, left, right, center); } else if ((cmd & ~0xff) == MIXER_READ(0)) { *(int *)arg = CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL); *(int *)arg |= CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; } CHN_UNLOCK(c); return (0); } static int mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) { struct snddev_info *d; int ret; if (i_dev == NULL || i_dev->si_drv1 == NULL) return (EBADF); d = device_get_softc(((struct snd_mixer *)i_dev->si_drv1)->dev); if (!PCM_REGISTERED(d) || PCM_DETACHING(d)) return (EBADF); PCM_GIANT_ENTER(d); PCM_ACQUIRE_QUICK(d); ret = -1; if (mixer_bypass != 0 && (d->flags & SD_F_VPC)) ret = mixer_ioctl_channel(i_dev, cmd, arg, mode, td, MIXER_CMD_CDEV); if (ret == -1) ret = mixer_ioctl_cmd(i_dev, cmd, arg, mode, td, MIXER_CMD_CDEV); PCM_RELEASE_QUICK(d); PCM_GIANT_LEAVE(d); return (ret); } static void mixer_mixerinfo(struct snd_mixer *m, mixer_info *mi) { bzero((void *)mi, sizeof(*mi)); strlcpy(mi->id, m->name, sizeof(mi->id)); strlcpy(mi->name, device_get_desc(m->dev), sizeof(mi->name)); mi->modify_counter = m->modify_counter; } /* * XXX Make sure you can guarantee concurrency safety before calling this * function, be it through Giant, PCM_*, etc ! */ int mixer_ioctl_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td, int from) { struct snd_mixer *m; int ret = EINVAL, *arg_i = (int *)arg; int v = -1, j = cmd & 0xff; /* * 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); return (0); case SNDCTL_CARDINFO: return (sound_oss_card_info((oss_card_info *)arg)); case SNDCTL_AUDIOINFO: return (dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg, false)); case SNDCTL_AUDIOINFO_EX: return (dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg, true)); case SNDCTL_ENGINEINFO: return (dsp_oss_engineinfo(i_dev, (oss_audioinfo *)arg)); case SNDCTL_MIXERINFO: return (mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg)); } return (EINVAL); } m = i_dev->si_drv1; if (m == NULL) return (EBADF); snd_mtxlock(m->lock); if (from == MIXER_CMD_CDEV && !m->busy) { snd_mtxunlock(m->lock); return (EBADF); } switch (cmd) { case SNDCTL_DSP_GET_RECSRC_NAMES: bcopy((void *)&m->enuminfo, arg, sizeof(oss_mixer_enuminfo)); ret = 0; goto done; case SNDCTL_DSP_GET_RECSRC: ret = mixer_get_recroute(m, arg_i); goto done; case SNDCTL_DSP_SET_RECSRC: ret = mixer_set_recroute(m, *arg_i); goto done; case OSS_GETVERSION: *arg_i = SOUND_VERSION; ret = 0; goto done; case SOUND_MIXER_INFO: mixer_mixerinfo(m, (mixer_info *)arg); ret = 0; goto done; } if ((cmd & ~0xff) == MIXER_WRITE(0)) { switch (j) { case SOUND_MIXER_RECSRC: ret = mixer_setrecsrc(m, *arg_i); break; case SOUND_MIXER_MUTE: mix_setmutedevs(m, *arg_i); ret = 0; break; default: ret = mixer_set(m, j, m->mutedevs, *arg_i); break; } snd_mtxunlock(m->lock); return ((ret == 0) ? 0 : ENXIO); } if ((cmd & ~0xff) == MIXER_READ(0)) { switch (j) { case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: v = mix_getdevs(m); break; case SOUND_MIXER_MUTE: v = mix_getmutedevs(m); break; case SOUND_MIXER_RECMASK: v = mix_getrecdevs(m); break; case SOUND_MIXER_RECSRC: v = mixer_getrecsrc(m); break; default: v = mixer_get(m, j); break; } *arg_i = v; snd_mtxunlock(m->lock); return ((v != -1) ? 0 : ENXIO); } done: snd_mtxunlock(m->lock); return (ret); } static void mixer_clone(void *arg, struct ucred *cred, char *name, int namelen, struct cdev **dev) { struct snddev_info *d; if (*dev != NULL) return; if (strcmp(name, "mixer") == 0) { bus_topo_lock(); d = devclass_get_softc(pcm_devclass, snd_unit); /* See related comment in dsp_clone(). */ if (d != NULL && PCM_REGISTERED(d) && d->mixer_dev != NULL) { *dev = d->mixer_dev; dev_ref(*dev); } bus_topo_unlock(); } } static void mixer_sysinit(void *p) { if (mixer_ehtag != NULL) return; mixer_ehtag = EVENTHANDLER_REGISTER(dev_clone, mixer_clone, 0, 1000); } static void mixer_sysuninit(void *p) { if (mixer_ehtag == NULL) return; EVENTHANDLER_DEREGISTER(dev_clone, mixer_ehtag); mixer_ehtag = NULL; } SYSINIT(mixer_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysinit, NULL); SYSUNINIT(mixer_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysuninit, NULL); +static void +mixer_oss_mixerinfo_unavail(oss_mixerinfo *mi, int unit) +{ + bzero(mi, sizeof(*mi)); + mi->dev = unit; + snprintf(mi->id, sizeof(mi->id), "mixer%d (n/a)", unit); + snprintf(mi->name, sizeof(mi->name), "pcm%d:mixer (unavailable)", unit); + mi->card_number = unit; + mi->legacy_device = unit; +} + /** * @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; int 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); d = NULL; m = NULL; /* * 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; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); - if (!PCM_REGISTERED(d) || PCM_DETACHING(d)) - continue; + if (!PCM_REGISTERED(d) || PCM_DETACHING(d)) { + if ((mi->dev == -1 && i == snd_unit) || mi->dev == i) { + mixer_oss_mixerinfo_unavail(mi, i); + return (0); + } else + continue; + } /* XXX Need Giant magic entry */ /* See the note in function docblock. */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); - if (d->mixer_dev != NULL && d->mixer_dev->si_drv1 != NULL && - ((mi->dev == -1 && d->mixer_dev == i_dev) || + if (!((d->mixer_dev == i_dev && mi->dev == -1) || mi->dev == i)) { - m = d->mixer_dev->si_drv1; - mtx_lock(m->lock); - - /* - * At this point, the following synchronization stuff - * has happened: - * - a specific PCM device is locked. - * - a specific mixer device has been locked, so be - * sure to unlock when existing. - */ - bzero((void *)mi, sizeof(*mi)); - mi->dev = i; - snprintf(mi->id, sizeof(mi->id), "mixer%d", i); - strlcpy(mi->name, m->name, sizeof(mi->name)); - mi->modify_counter = m->modify_counter; - mi->card_number = i; - /* - * 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. - */ - - snprintf(mi->devnode, sizeof(mi->devnode), "/dev/mixer%d", i); - mi->legacy_device = i; + PCM_UNLOCK(d); + continue; + } - mtx_unlock(m->lock); + if (d->mixer_dev->si_drv1 == NULL) { + mixer_oss_mixerinfo_unavail(mi, i); + PCM_UNLOCK(d); + return (0); } + m = d->mixer_dev->si_drv1; + mtx_lock(m->lock); + + /* + * At this point, the following synchronization stuff + * has happened: + * - a specific PCM device is locked. + * - a specific mixer device has been locked, so be + * sure to unlock when existing. + */ + bzero((void *)mi, sizeof(*mi)); + mi->dev = i; + snprintf(mi->id, sizeof(mi->id), "mixer%d", i); + strlcpy(mi->name, m->name, sizeof(mi->name)); + mi->modify_counter = m->modify_counter; + mi->card_number = i; + /* + * 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. + */ + + snprintf(mi->devnode, sizeof(mi->devnode), "/dev/mixer%d", i); + mi->legacy_device = i; + + mtx_unlock(m->lock); + PCM_UNLOCK(d); - if (m != NULL) - return (0); + return (0); } return (EINVAL); } /* * Allow the sound driver to use the mixer lock to protect its mixer * data: */ struct mtx * mixer_get_lock(struct snd_mixer *m) { if (m->lock == NULL) { return (&Giant); } return (m->lock); } int mix_get_locked(struct snd_mixer *m, u_int dev, int *pleft, int *pright) { int level; level = mixer_get(m, dev); if (level < 0) { *pright = *pleft = -1; return (-1); } *pleft = level & 0xFF; *pright = (level >> 8) & 0xFF; return (0); } int mix_set_locked(struct snd_mixer *m, u_int dev, int left, int right) { int level; level = (left & 0xFF) | ((right & 0xFF) << 8); return (mixer_set(m, dev, m->mutedevs, level)); } diff --git a/sys/dev/sound/pcm/mixer.h b/sys/dev/sound/pcm/mixer.h index 6c5c8f3ec3fe..7139a766b392 100644 --- a/sys/dev/sound/pcm/mixer.h +++ b/sys/dev/sound/pcm/mixer.h @@ -1,89 +1,87 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _PCM_MIXER_H_ #define _PCM_MIXER_H_ struct snd_mixer *mixer_create(device_t dev, kobj_class_t cls, void *devinfo, const char *desc); int mixer_delete(struct snd_mixer *m); 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_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td, int from); int mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi); int mixer_hwvol_init(device_t dev); void mixer_hwvol_mute_locked(struct snd_mixer *m); void mixer_hwvol_mute(device_t dev); void mixer_hwvol_step_locked(struct snd_mixer *m, int l_step, int r_step); void mixer_hwvol_step(device_t dev, int left_step, int right_step); int mixer_busy(struct snd_mixer *m); int mix_get_locked(struct snd_mixer *m, u_int dev, int *pleft, int *pright); int mix_set_locked(struct snd_mixer *m, u_int dev, int left, int right); int mix_set(struct snd_mixer *m, u_int dev, u_int left, u_int right); int mix_get(struct snd_mixer *m, u_int dev); int mix_setrecsrc(struct snd_mixer *m, u_int32_t src); u_int32_t mix_getrecsrc(struct snd_mixer *m); int mix_get_type(struct snd_mixer *m); device_t mix_get_dev(struct snd_mixer *m); void mix_setdevs(struct snd_mixer *m, u_int32_t v); void mix_setrecdevs(struct snd_mixer *m, u_int32_t v); void mix_setmutedevs(struct snd_mixer *m, u_int32_t v); u_int32_t mix_getdevs(struct snd_mixer *m); u_int32_t mix_getrecdevs(struct snd_mixer *m); u_int32_t mix_getmutedevs(struct snd_mixer *m); void mix_setparentchild(struct snd_mixer *m, u_int32_t parent, u_int32_t childs); void mix_setrealdev(struct snd_mixer *m, u_int32_t dev, u_int32_t realdev); u_int32_t mix_getparent(struct snd_mixer *m, u_int32_t dev); u_int32_t mix_getchild(struct snd_mixer *m, u_int32_t dev); void *mix_getdevinfo(struct snd_mixer *m); struct mtx *mixer_get_lock(struct snd_mixer *m); -extern int mixer_count; - #define MIXER_CMD_DIRECT 0 /* send command within driver */ #define MIXER_CMD_CDEV 1 /* send command from cdev/ioctl */ #define MIXER_TYPE_PRIMARY 0 /* mixer_init() */ #define MIXER_TYPE_SECONDARY 1 /* mixer_create() */ /* * 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) + \ sizeof(oss_mixer_enuminfo)) #define MIXER_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, MIXER_SIZE) #endif /* _PCM_MIXER_H_ */ diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c index 072d42c4e989..8d97dcd60231 100644 --- a/sys/dev/sound/pcm/sound.c +++ b/sys/dev/sound/pcm/sound.c @@ -1,852 +1,863 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * Copyright (c) 1997 Luigi Rizzo * All rights reserved. * Copyright (c) 2024 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include "feeder_if.h" devclass_t pcm_devclass; int pcm_veto_load = 1; int snd_unit = -1; static int snd_unit_auto = -1; SYSCTL_INT(_hw_snd, OID_AUTO, default_auto, CTLFLAG_RWTUN, &snd_unit_auto, 0, "assign default unit to a newly attached device"); SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Sound driver"); static void pcm_sysinit(device_t); /** * @brief Unit number allocator for syncgroup IDs */ struct unrhdr *pcmsg_unrhdr = NULL; void * snd_mtxcreate(const char *desc, const char *type) { struct mtx *m; m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO); mtx_init(m, desc, type, MTX_DEF); return m; } void snd_mtxfree(void *m) { struct mtx *mtx = m; mtx_destroy(mtx); free(mtx, M_DEVBUF); } void snd_mtxassert(void *m) { #ifdef INVARIANTS struct mtx *mtx = m; mtx_assert(mtx, MA_OWNED); #endif } int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep) { struct snddev_info *d; flags &= INTR_MPSAFE; flags |= INTR_TYPE_AV; d = device_get_softc(dev); if (d != NULL && (flags & INTR_MPSAFE)) d->flags |= SD_F_MPSAFE; return bus_setup_intr(dev, res, flags, NULL, hand, param, cookiep); } /* return error status and a locked channel */ int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, pid_t pid, char *comm) { struct pcm_channel *c; int err, vchancount, vchan_num; bool retry; KASSERT(d != NULL && ch != NULL && (direction == PCMDIR_PLAY || direction == PCMDIR_REC), ("%s(): invalid d=%p ch=%p direction=%d pid=%d", __func__, d, ch, direction, pid)); PCM_BUSYASSERT(d); *ch = NULL; vchan_num = 0; vchancount = (direction == PCMDIR_PLAY) ? d->pvchancount : d->rvchancount; retry = false; retry_chnalloc: err = ENOTSUP; /* scan for a free channel */ CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); if (c->direction == direction && (c->flags & CHN_F_VIRTUAL)) { if (vchancount < snd_maxautovchans && vchan_num < c->unit) { CHN_UNLOCK(c); goto vchan_alloc; } vchan_num++; } if (c->direction == direction && !(c->flags & CHN_F_BUSY)) { c->flags |= CHN_F_BUSY; c->pid = pid; strlcpy(c->comm, (comm != NULL) ? comm : CHN_COMM_UNKNOWN, sizeof(c->comm)); *ch = c; return (0); } else if (c->direction == direction && (c->flags & CHN_F_BUSY)) err = EBUSY; CHN_UNLOCK(c); } /* * We came from retry_chnalloc and still didn't find a free channel. */ if (retry) return (err); vchan_alloc: /* no channel available */ if (!(vchancount > 0 && vchancount < snd_maxautovchans)) return (err); err = vchan_setnew(d, direction, vchancount + 1); if (err == 0) { retry = true; goto retry_chnalloc; } return (err); } static int sysctl_hw_snd_default_unit(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int error, unit; unit = snd_unit; error = sysctl_handle_int(oidp, &unit, 0, req); if (error == 0 && req->newptr != NULL) { d = devclass_get_softc(pcm_devclass, unit); if (!PCM_REGISTERED(d) || CHN_EMPTY(d, channels.pcm)) return EINVAL; snd_unit = unit; snd_unit_auto = 0; } return (error); } /* XXX: do we need a way to let the user change the default unit? */ SYSCTL_PROC(_hw_snd, OID_AUTO, default_unit, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_ANYBODY | CTLFLAG_NEEDGIANT, 0, sizeof(int), sysctl_hw_snd_default_unit, "I", "default sound device"); void pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch) { PCM_BUSYASSERT(d); PCM_LOCKASSERT(d); KASSERT(ch != NULL && (ch->direction == PCMDIR_PLAY || ch->direction == PCMDIR_REC), ("Invalid pcm channel")); CHN_INSERT_SORT_ASCEND(d, ch, channels.pcm); switch (ch->type) { case SND_DEV_DSPHW_PLAY: d->playcount++; break; case SND_DEV_DSPHW_VPLAY: d->pvchancount++; break; case SND_DEV_DSPHW_REC: d->reccount++; break; case SND_DEV_DSPHW_VREC: d->rvchancount++; break; default: __assert_unreachable(); } } int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch) { struct pcm_channel *tmp; PCM_BUSYASSERT(d); PCM_LOCKASSERT(d); tmp = NULL; CHN_FOREACH(tmp, d, channels.pcm) { if (tmp == ch) break; } if (tmp != ch) return (EINVAL); CHN_REMOVE(d, ch, channels.pcm); switch (ch->type) { case SND_DEV_DSPHW_PLAY: d->playcount--; break; case SND_DEV_DSPHW_VPLAY: d->pvchancount--; break; case SND_DEV_DSPHW_REC: d->reccount--; break; case SND_DEV_DSPHW_VREC: d->rvchancount--; break; default: __assert_unreachable(); } return (0); } int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo) { struct snddev_info *d = device_get_softc(dev); struct pcm_channel *ch; PCM_BUSYASSERT(d); PCM_LOCK(d); ch = chn_init(d, NULL, cls, dir, devinfo); if (!ch) { device_printf(d->dev, "chn_init(%s, %d, %p) failed\n", cls->name, dir, devinfo); PCM_UNLOCK(d); return (ENODEV); } pcm_chn_add(d, ch); PCM_UNLOCK(d); return (0); } static void pcm_killchans(struct snddev_info *d) { struct pcm_channel *ch; int error; bool found; PCM_BUSYASSERT(d); do { found = false; CHN_FOREACH(ch, d, channels.pcm) { CHN_LOCK(ch); /* * Make sure no channel has went to sleep in the * meantime. */ chn_shutdown(ch); /* * We have to give a thread sleeping in chn_sleep() a * chance to observe that the channel is dead. */ if ((ch->flags & CHN_F_SLEEPING) == 0) { found = true; CHN_UNLOCK(ch); break; } CHN_UNLOCK(ch); } /* * All channels are still sleeping. Sleep for a bit and try * again to see if any of them is awake now. */ if (!found) { pause_sbt("pcmkillchans", SBT_1MS * 5, 0, 0); continue; } PCM_LOCK(d); error = pcm_chn_remove(d, ch); PCM_UNLOCK(d); if (error == 0) chn_kill(ch); } while (!CHN_EMPTY(d, channels.pcm)); } static int pcm_best_unit(int old) { struct snddev_info *d; int i, best, bestprio, prio; best = -1; bestprio = -100; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; prio = 0; if (d->playcount == 0) prio -= 10; if (d->reccount == 0) prio -= 2; if (prio > bestprio || (prio == bestprio && i == old)) { best = i; bestprio = prio; } } return (best); } int pcm_setstatus(device_t dev, char *str) { struct snddev_info *d = device_get_softc(dev); /* should only be called once */ if (d->flags & SD_F_REGISTERED) return (EINVAL); PCM_BUSYASSERT(d); if (d->playcount == 0 || d->reccount == 0) d->flags |= SD_F_SIMPLEX; if (d->playcount > 0 || d->reccount > 0) d->flags |= SD_F_AUTOVCHAN; vchan_setmaxauto(d, snd_maxautovchans); strlcpy(d->status, str, SND_STATUSLEN); PCM_LOCK(d); /* Done, we're ready.. */ d->flags |= SD_F_REGISTERED; PCM_RELEASE(d); PCM_UNLOCK(d); /* * Create all sysctls once SD_F_REGISTERED is set else * tunable sysctls won't work: */ pcm_sysinit(dev); if (snd_unit_auto < 0) snd_unit_auto = (snd_unit < 0) ? 1 : 0; if (snd_unit < 0 || snd_unit_auto > 1) snd_unit = device_get_unit(dev); else if (snd_unit_auto == 1) snd_unit = pcm_best_unit(snd_unit); return (0); } uint32_t pcm_getflags(device_t dev) { struct snddev_info *d = device_get_softc(dev); return d->flags; } void pcm_setflags(device_t dev, uint32_t val) { struct snddev_info *d = device_get_softc(dev); d->flags = val; } void * pcm_getdevinfo(device_t dev) { struct snddev_info *d = device_get_softc(dev); return d->devinfo; } unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz) { struct snddev_info *d = device_get_softc(dev); int sz, x; sz = 0; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "buffersize", &sz) == 0) { x = sz; RANGE(sz, minbufsz, maxbufsz); if (x != sz) device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, minbufsz, maxbufsz, sz); x = minbufsz; while (x < sz) x <<= 1; if (x > sz) x >>= 1; if (x != sz) { device_printf(dev, "'buffersize=%d' hint is not a power of 2, using %d\n", sz, x); sz = x; } } else { sz = deflt; } d->bufsz = sz; return sz; } static int sysctl_dev_pcm_bitperfect(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int err, val; d = oidp->oid_arg1; if (!PCM_REGISTERED(d)) return (ENODEV); PCM_LOCK(d); PCM_WAIT(d); val = (d->flags & SD_F_BITPERFECT) ? 1 : 0; PCM_ACQUIRE(d); PCM_UNLOCK(d); err = sysctl_handle_int(oidp, &val, 0, req); if (err == 0 && req->newptr != NULL) { if (!(val == 0 || val == 1)) { PCM_RELEASE_QUICK(d); return (EINVAL); } PCM_LOCK(d); d->flags &= ~SD_F_BITPERFECT; d->flags |= (val != 0) ? SD_F_BITPERFECT : 0; PCM_RELEASE(d); PCM_UNLOCK(d); } else PCM_RELEASE_QUICK(d); return (err); } static u_int8_t pcm_mode_init(struct snddev_info *d) { u_int8_t mode = 0; if (d->playcount > 0) mode |= PCM_MODE_PLAY; if (d->reccount > 0) mode |= PCM_MODE_REC; if (d->mixer_dev != NULL) mode |= PCM_MODE_MIXER; return (mode); } static void pcm_sysinit(device_t dev) { struct snddev_info *d = device_get_softc(dev); u_int8_t mode; mode = pcm_mode_init(d); /* XXX: a user should be able to set this with a control tool, the sysadmin then needs min+max sysctls for this */ SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "allocated buffer size"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "bitperfect", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, d, sizeof(d), sysctl_dev_pcm_bitperfect, "I", "bit-perfect playback/recording (0=disable, 1=enable)"); SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "mode", CTLFLAG_RD, NULL, mode, "mode (1=mixer, 2=play, 4=rec. The values are OR'ed if more than " "one mode is supported)"); if (d->flags & SD_F_AUTOVCHAN) vchan_initsys(dev); if (d->flags & SD_F_EQ) feeder_eq_initsys(dev); } int pcm_register(device_t dev, void *devinfo, int numplay, int numrec) { struct snddev_info *d; int i; if (pcm_veto_load) { device_printf(dev, "disabled due to an error while initialising: %d\n", pcm_veto_load); return EINVAL; } d = device_get_softc(dev); d->dev = dev; d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); cv_init(&d->cv, device_get_nameunit(dev)); PCM_ACQUIRE_QUICK(d); #if 0 /* * d->flags should be cleared by the allocator of the softc. * We cannot clear this field here because several devices set * this flag before calling pcm_register(). */ d->flags = 0; #endif i = 0; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "vpc", &i) != 0 || i != 0) d->flags |= SD_F_VPC; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "bitperfect", &i) == 0 && i != 0) d->flags |= SD_F_BITPERFECT; d->devinfo = devinfo; d->reccount = 0; d->playcount = 0; d->pvchancount = 0; d->rvchancount = 0; d->pvchanrate = 0; d->pvchanformat = 0; d->rvchanrate = 0; d->rvchanformat = 0; CHN_INIT(d, channels.pcm); CHN_INIT(d, channels.pcm.busy); CHN_INIT(d, channels.pcm.opened); /* XXX This is incorrect, but lets play along for now. */ if ((numplay == 0 || numrec == 0) && numplay != numrec) d->flags |= SD_F_SIMPLEX; sysctl_ctx_init(&d->play_sysctl_ctx); d->play_sysctl_tree = SYSCTL_ADD_NODE(&d->play_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "play", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "playback channels node"); sysctl_ctx_init(&d->rec_sysctl_ctx); d->rec_sysctl_tree = SYSCTL_ADD_NODE(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rec", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "recording channels node"); if (numplay > 0 || numrec > 0) d->flags |= SD_F_AUTOVCHAN; sndstat_register(dev, d->status); return (dsp_make_dev(dev)); } int pcm_unregister(device_t dev) { struct snddev_info *d; struct pcm_channel *ch; d = device_get_softc(dev); if (!PCM_ALIVE(d)) { device_printf(dev, "unregister: device not configured\n"); return (0); } PCM_LOCK(d); PCM_WAIT(d); d->flags |= SD_F_DETACHING; PCM_ACQUIRE(d); PCM_UNLOCK(d); CHN_FOREACH(ch, d, channels.pcm) { CHN_LOCK(ch); /* * Do not wait for the timeout in chn_read()/chn_write(). Wake * up the sleeping thread and kill the channel. */ chn_shutdown(ch); chn_abort(ch); CHN_UNLOCK(ch); } /* remove /dev/sndstat entry first */ sndstat_unregister(dev); PCM_LOCK(d); d->flags |= SD_F_DYING; d->flags &= ~SD_F_REGISTERED; PCM_UNLOCK(d); if (d->play_sysctl_tree != NULL) { sysctl_ctx_free(&d->play_sysctl_ctx); d->play_sysctl_tree = NULL; } if (d->rec_sysctl_tree != NULL) { sysctl_ctx_free(&d->rec_sysctl_ctx); d->rec_sysctl_tree = NULL; } dsp_destroy_dev(dev); (void)mixer_uninit(dev); pcm_killchans(d); PCM_LOCK(d); PCM_RELEASE(d); cv_destroy(&d->cv); PCM_UNLOCK(d); snd_mtxfree(d->lock); if (snd_unit == device_get_unit(dev)) { snd_unit = pcm_best_unit(-1); if (snd_unit_auto == 0) snd_unit_auto = 1; } return (0); } /************************************************************************/ /** * @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 char si_license[] = "BSD"; static int intnbits = sizeof(int) * 8; /* Better suited as macro? Must pester a C guru. */ struct snddev_info *d; struct pcm_channel *c; int j; size_t i; strlcpy(si->product, si_product, sizeof(si->product)); strlcpy(si->version, si_version, sizeof(si->version)); si->versionnum = SOUND_VERSION; strlcpy(si->license, si_license, sizeof(si->license)); /* * Iterate over PCM devices and their channels, gathering up data * for the numaudioengines and openedaudio fields. */ si->numaudioengines = 0; bzero((void *)&si->openedaudio, sizeof(si->openedaudio)); j = 0; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; /* XXX Need Giant magic entry ??? */ /* See note in function's docblock */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); si->numaudioengines += PCM_CHANCOUNT(d); CHN_FOREACH(c, d, channels.pcm) { CHN_UNLOCKASSERT(c); CHN_LOCK(c); if (c->flags & CHN_F_BUSY) si->openedaudio[j / intnbits] |= (1 << (j % intnbits)); CHN_UNLOCK(c); j++; } PCM_UNLOCK(d); } 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; + /* + * Set this to the maximum unit number so that applications will not + * break if they try to loop through all mixers and some of them are + * not available. + */ + si->nummixers = devclass_get_maxunit(pcm_devclass); si->numcards = devclass_get_maxunit(pcm_devclass); si->numaudios = devclass_get_maxunit(pcm_devclass); /* 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 < nitems(si->filler); i++) si->filler[i] = -1; } int sound_oss_card_info(oss_card_info *si) { struct snddev_info *d; int i; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); - if (!PCM_REGISTERED(d)) - continue; - if (i != si->card) continue; - PCM_UNLOCKASSERT(d); - PCM_LOCK(d); - - strlcpy(si->shortname, device_get_nameunit(d->dev), - sizeof(si->shortname)); - strlcpy(si->longname, device_get_desc(d->dev), - sizeof(si->longname)); - strlcpy(si->hw_info, d->status, sizeof(si->hw_info)); - si->intr_count = si->ack_count = 0; - - PCM_UNLOCK(d); + if (!PCM_REGISTERED(d)) { + snprintf(si->shortname, sizeof(si->shortname), + "pcm%d (n/a)", i); + strlcpy(si->longname, "Device unavailable", + sizeof(si->longname)); + si->hw_info[0] = '\0'; + si->intr_count = si->ack_count = 0; + } else { + PCM_UNLOCKASSERT(d); + PCM_LOCK(d); + + strlcpy(si->shortname, device_get_nameunit(d->dev), + sizeof(si->shortname)); + strlcpy(si->longname, device_get_desc(d->dev), + sizeof(si->longname)); + strlcpy(si->hw_info, d->status, sizeof(si->hw_info)); + si->intr_count = si->ack_count = 0; + + PCM_UNLOCK(d); + } return (0); } return (ENXIO); } /************************************************************************/ static int sound_modevent(module_t mod, int type, void *data) { int ret; ret = 0; switch (type) { case MOD_LOAD: pcm_devclass = devclass_create("pcm"); pcmsg_unrhdr = new_unrhdr(1, INT_MAX, NULL); break; case MOD_UNLOAD: if (pcmsg_unrhdr != NULL) { delete_unrhdr(pcmsg_unrhdr); pcmsg_unrhdr = NULL; } break; case MOD_SHUTDOWN: break; default: ret = ENOTSUP; } return ret; } DEV_MODULE(sound, sound_modevent, NULL); MODULE_VERSION(sound, SOUND_MODVER);