diff options
Diffstat (limited to 'kernel/framework/mixer')
-rw-r--r-- | kernel/framework/mixer/.config | 1 | ||||
-rw-r--r-- | kernel/framework/mixer/mixerdefs.h | 120 | ||||
-rw-r--r-- | kernel/framework/mixer/oss_mixer_core.c | 2812 |
3 files changed, 2933 insertions, 0 deletions
diff --git a/kernel/framework/mixer/.config b/kernel/framework/mixer/.config new file mode 100644 index 0000000..e44f78f --- /dev/null +++ b/kernel/framework/mixer/.config @@ -0,0 +1 @@ +targetcpu=any diff --git a/kernel/framework/mixer/mixerdefs.h b/kernel/framework/mixer/mixerdefs.h new file mode 100644 index 0000000..5c9b4de --- /dev/null +++ b/kernel/framework/mixer/mixerdefs.h @@ -0,0 +1,120 @@ +/* + * Purpose: Mixer enum control defines for older OSS drivers. + * + * This file contains choice names for MIXT_ENUM controls defined by some + * older drivers. All drivers developed recently will use an embedded + * mechanism for setting this information. + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +typedef struct +{ + char *name, *strings; +} mixer_def_t; + +static const mixer_def_t mixer_defs[] = { + {"setup.mon1l", "OFF A0 A1 A2 A3 A4 A5 A6 A7 B0 B1 B2 B3 B4 B5 B6 B7"}, + {"setup.mon1r", "OFF A0 A1 A2 A3 A4 A5 A6 A7 B0 B1 B2 B3 B4 B5 B6 B7"}, + {"setup.mon2l", "OFF A0 A1 A2 A3 A4 A5 A6 A7 B0 B1 B2 B3 B4 B5 B6 B7"}, + {"setup.mon2r", "OFF A0 A1 A2 A3 A4 A5 A6 A7 B0 B1 B2 B3 B4 B5 B6 B7"}, + {"fpga.srcclock", "44.1K 48K PLL AES"}, + {"fpga.clock", "44.1K 48K PLL AES"}, + {"fpga.pll", "PORTA PORTB TIMER1 EXTERNAL"}, + {"aes.mode", "CONSUMER PRO"}, + {"aes.copy", "INHIBITED PERMITTED"}, + {"aes.audio", "AUDIO DATA"}, + {"aes.preemph", "NONE 50/15us"}, + {"digi32.sync", "EXTERNAL INTERNAL"}, + {"digi32.aesmode", "CONSUMER PRO"}, + {"digi32.input", "OPTICAL RCA INTERNAL XLR"}, + {"out1.src", "CODEC DSP"}, + {"in.src", "CODEC LINE OPTICAL COAX"}, + {"reverb.type", "ROOM1 ROOM2 ROOM3 HALL1 HALL2 PLATE DELAY PANDELAY"}, + {"chorus.type", + "CHORUS1 CHORUS2 CHORUS3 CHORUS4 FBCHORUS FLANGER SHORTDELAY FBDELAY"}, + {"digi96.sync", "EXTERNAL INTERNAL"}, + {"digi96.input", "OPTICAL COAXIAL INTERNAL XLR"}, + {"digi96.sel", "BYPASS NORMAL"}, + {"digi96.mode", "SPDIF AESEBU ADAT"}, + {"digi96.data", "AUDIO DATA"}, + {"envy24.sync", "INTERNAL SPDIF WCLOCK"}, + {"envy24.spdin", "COAX OPTICAL"}, + {"gain.out1/2", "+4DB CONSUMER -10DB"}, + {"gain.out3/4", "+4DB CONSUMER -10DB"}, + {"gain.out5/6", "+4DB CONSUMER -10DB"}, + {"gain.out7/8", "+4DB CONSUMER -10DB"}, + {"gain.in1/2", "+4DB CONSUMER -10DB"}, + {"gain.in3/4", "+4DB CONSUMER -10DB"}, + {"gain.in5/6", "+4DB CONSUMER -10DB"}, + {"gain.in7/8", "+4DB CONSUMER -10DB"}, + {"gain.out1", "+4DB CONSUMER -10DB"}, + {"gain.out2", "+4DB CONSUMER -10DB"}, + {"gain.out3", "+4DB CONSUMER -10DB"}, + {"gain.out4", "+4DB CONSUMER -10DB"}, + {"gain.out5", "+4DB CONSUMER -10DB"}, + {"gain.out6", "+4DB CONSUMER -10DB"}, + {"gain.out7", "+4DB CONSUMER -10DB"}, + {"gain.out8", "+4DB CONSUMER -10DB"}, + {"gain.in1", "+4DB CONSUMER -10DB"}, + {"gain.in2", "+4DB CONSUMER -10DB"}, + {"gain.in3", "+4DB CONSUMER -10DB"}, + {"gain.in4", "+4DB CONSUMER -10DB"}, + {"gain.in5", "+4DB CONSUMER -10DB"}, + {"gain.in6", "+4DB CONSUMER -10DB"}, + {"gain.in7", "+4DB CONSUMER -10DB"}, + {"gain.in8", "+4DB CONSUMER -10DB"}, + {"route.out1/2", "DMA MONITOR IN1/2 IN3/4 IN5/6 IN7/8 SPDIF"}, + {"route.out3/4", "DMA MONITOR IN1/2 IN3/4 IN5/6 IN7/8 SPDIF"}, + {"route.out5/6", "DMA MONITOR IN1/2 IN3/4 IN5/6 IN7/8 SPDIF"}, + {"route.out7/8", "DMA MONITOR IN1/2 IN3/4 IN5/6 IN7/8 SPDIF"}, + {"route.spdif", "DMA MONITOR IN1/2 IN3/4 IN5/6 IN7/8 SPDIF"}, + {"route.out1", "DMA MONITOR IN1 IN2 IN3 IN4 IN5 IN6 IN7 IN8 SPDIFL SPDIFR"}, + {"route.out2", "DMA MONITOR IN1 IN2 IN3 IN4 IN5 IN6 IN7 IN8 SPDIFL SPDIFR"}, + {"route.out3", "DMA MONITOR IN1 IN2 IN3 IN4 IN5 IN6 IN7 IN8 SPDIFL SPDIFR"}, + {"route.out4", "DMA MONITOR IN1 IN2 IN3 IN4 IN5 IN6 IN7 IN8 SPDIFL SPDIFR"}, + {"route.out5", "DMA MONITOR IN1 IN2 IN3 IN4 IN5 IN6 IN7 IN8 SPDIFL SPDIFR"}, + {"route.out6", "DMA MONITOR IN1 IN2 IN3 IN4 IN5 IN6 IN7 IN8 SPDIFL SPDIFR"}, + {"route.out7", "DMA MONITOR IN1 IN2 IN3 IN4 IN5 IN6 IN7 IN8 SPDIFL SPDIFR"}, + {"route.out8", "DMA MONITOR IN1 IN2 IN3 IN4 IN5 IN6 IN7 IN8 SPDIFL SPDIFR"}, + {"route.spdifl", + "DMA MONITOR IN1 IN2 IN3 IN4 IN5 IN6 IN7 IN8 SPDIFL SPDIFR"}, + {"route.spdifr", + "DMA MONITOR IN1 IN2 IN3 IN4 IN5 IN6 IN7 IN8 SPDIFL SPDIFR"}, + {"ews88d.spdin", "OPTICAL COAX"}, + {"ews88d.optout", "SPDIF ADAT"}, + {"codec.recsrc", "ANALOG OPTICAL COAX CD AUX"}, + {"route.front", "DMA ANALOGIN DIGITALIN"}, + {"route.rear", "DMA ANALOGIN DIGITALIN"}, + {"route.surround", "DMA ANALOGIN DIGITALIN"}, + {"route.c/l", "DMA ANALOGIN DIGITALIN"}, + {"route.spdifout", "DMA ANALOGIN DIGITALIN"}, + {"lynxone.sync", "INTERNAL DIGITAL EXTW EXT27 EXT13 HDRW HDR27 HDR13"}, + {"lynxone.format", "AESEBU SPDIF"}, + {"lynxone.trim", "+4DB -10DB"}, + {"spkmode", "FRONT SURR FRONT+SURR DISCRETE 3D"}, + {"ext.recsrc", "SPDIF_OUT I2S_OUT SPDIF_IN I2S_IN AC97 SRC"}, + {"ext.loopback", "DSP0 DSP1 DSP2 DSP3"}, + {"3dsurround.mode", "OFF NORMAL 2X 3X"}, + {"spdout.pro", "Consumer Professional"}, + {"spdout.audio", "AUDIO DATA"}, + {"spdout.rate", "48000 44100 32000"}, + {"spdif.mode", "CONSUMER PRO"}, + {"spdif.audio", "AUDIO DATA"}, + {"spdif.copyright", "YES NO"}, + {"spdif.generat", "COPY ORIGINAL"}, + {"spdif.preemph", "OFF 50/16usec"}, + {"mixext.spkmode", "FRONT SPREAD"}, + {"effects.reverb.preset", + "SMALL_ROOM MEDIUM_ROOM LARGE_ROOM SMALL_HALL LARGE_HALL"}, + {NULL} +}; diff --git a/kernel/framework/mixer/oss_mixer_core.c b/kernel/framework/mixer/oss_mixer_core.c new file mode 100644 index 0000000..44f4eff --- /dev/null +++ b/kernel/framework/mixer/oss_mixer_core.c @@ -0,0 +1,2812 @@ +/* + * Purpose: OSS mixer core. + * Copyright (C) 4Front Technologies, 2002-2004. All rights reserved. + * + * Description: + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include <oss_config.h> +#include <midi_core.h> +#include <stdarg.h> +oss_mutex_t oss_timing_mutex; + +char *oss_license_string = OSS_LICENSE; + +#ifdef DO_TIMINGS +static int timing_is_active = 0; /* 1=The readtimings utility has been active */ +#endif + +void *mixer_devs_p = NULL; +/* + * Mixer device list + */ +mixer_operations_t **mixer_devs = NULL; +int num_mixers = 0; +int mixer_port_number = 0; +int current_mixer_card = -1; + +/* + * mix_cvt is used for scaling mixer levels by various drivers. + */ +char mix_cvt[101] = { + 0, 0, 3, 7, 10, 13, 16, 19, 21, 23, 26, 28, 30, 32, 34, 35, 37, 39, 40, 42, + 43, 45, 46, 47, 49, 50, 51, 52, 53, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, + 65, 66, 67, 68, 69, 70, 70, 71, 72, 73, 73, 74, 75, 75, 76, 77, 77, 78, 79, + 79, + 80, 81, 81, 82, 82, 83, 84, 84, 85, 85, 86, 86, 87, 87, 88, 88, 89, 89, 90, + 90, + 91, 91, 92, 92, 93, 93, 94, 94, 95, 95, 96, 96, 96, 97, 97, 98, 98, 98, 99, + 99, + 100 +}; + +static int +get_mixer_info (int dev, ioctl_arg arg) +{ + mixer_info *info = (mixer_info *) arg; + + memset(info, 0, sizeof(*info)); + + if (dev < 0 || dev >= num_mixers) + return OSS_ENXIO; + + strcpy (info->id, mixer_devs[dev]->id); + strncpy (info->name, mixer_devs[dev]->name, 32); + info->name[31] = '\0'; + info->modify_counter = mixer_devs[dev]->modify_counter; + + return 0; +} + +/* + * Legacy mixer handling + */ + +int +oss_legacy_mixer_ioctl (int mixdev, int audiodev, unsigned int cmd, + ioctl_arg arg) +{ + int ret; + +#ifdef DO_TIMINGS + oss_timing_printf ("oss_legacy_mixer_ioctl(%d/%d, %08x) entered", mixdev, + audiodev, cmd); +#endif + + if (!(cmd & SIOC_OUT) && !(cmd & SIOC_IN)) + return OSS_EINVAL; + + if (arg == 0) + return OSS_EINVAL; + + if (((cmd >> 8) & 0xff) != 'M') /* Not a mixer ioctl */ + return OSS_EINVAL; + + if (mixdev < 0 || mixdev >= num_mixers) + { + cmn_err (CE_WARN, "Bad mixer device %d\n", mixdev); + return OSS_EIO; + } + + if (cmd == SOUND_MIXER_INFO) + return get_mixer_info (mixdev, arg); + + if (!mixer_devs[mixdev]->enabled || mixer_devs[mixdev]->unloaded) + return OSS_ENXIO; + + if (mixer_devs[mixdev]->d->ioctl == NULL) + return OSS_EINVAL; + + if (IOC_IS_OUTPUT (cmd)) + mixer_devs[mixdev]->modify_counter++; + + ret = mixer_devs[mixdev]->d->ioctl (mixdev, audiodev, cmd, arg); + + return ret; +} + +/* + * Handling of initial mixer volumes + */ + +static int muted_mixer_levels[32] = {0}; + +static int default_mixer_levels[32] = { + 0x3232, /* Master Volume */ + 0x3232, /* Bass */ + 0x3232, /* Treble */ + 0x4b4b, /* FM */ + 0x3232, /* PCM */ + 0x1515, /* PC Speaker */ + 0x2020, /* Ext Line */ + 0x2020, /* Mic */ + 0x4b4b, /* CD */ + 0x0000, /* Recording monitor */ + 0x4b4b, /* Second PCM */ + 0x4b4b, /* Recording level */ + 0x4b4b, /* Input gain */ + 0x4b4b, /* Output gain */ + 0x2020, /* Line1 */ + 0x2020, /* Line2 */ + 0x1515 /* Line3 (usually line in) */ +}; + +/* + * Table for configurable mixer volume handling + */ +static mixer_vol_table mixer_vols[MAX_MIXER_DEV]; +static int num_mixer_volumes = 0; + +int * +load_mixer_volumes (char *name, int *levels, int present) +{ + int i, n; + extern int mixer_muted; + + if (mixer_muted) /* Config setting from osscore.conf */ + return muted_mixer_levels; + + if (levels == NULL) + levels = default_mixer_levels; + + for (i = 0; i < num_mixer_volumes; i++) + if (strcmp (name, mixer_vols[i].name) == 0) + { + if (present) + mixer_vols[i].num = i; + return mixer_vols[i].levels; + } + + if (num_mixer_volumes >= MAX_MIXER_DEV) + { + cmn_err (CE_WARN, "Too many mixers (%s/%d/%d)\n", + name, num_mixer_volumes, MAX_MIXER_DEV); + return levels; + } + + n = num_mixer_volumes++; + + strcpy (mixer_vols[n].name, name); + + if (present) + mixer_vols[n].num = n; + else + mixer_vols[n].num = -1; + + for (i = 0; i < 32; i++) + mixer_vols[n].levels[i] = levels[i]; + return mixer_vols[n].levels; +} + +/* + * Mixer "extension" handling + */ + +static int +oss_mixer_ext_info (oss_mixext * ent) +{ + + int dev, ctrl; + int extnr; + + if (ent == NULL) + return OSS_EFAULT; + + if (ent->dev < 0 || ent->dev >= num_mixers) + return OSS_ENXIO; + + dev = ent->dev; + if (!mixer_devs[dev]->enabled || mixer_devs[dev]->unloaded) + return OSS_ENXIO; + touch_mixer (dev); + + ctrl = ent->ctrl; + if (ent->ctrl < 0 || ent->ctrl >= mixer_devs[dev]->nr_ext) + return OSS_EIDRM; + extnr = ent->ctrl; + + memcpy ((char *) ent, (char *) &mixer_devs[dev]->extensions[extnr].ext, + sizeof (*ent)); + + switch (ent->type) + { + case MIXT_MONOPEAK: + case MIXT_STEREOPEAK: + case MIXT_MONOVU: + case MIXT_STEREOVU: + /* Peak meters will need to be polled */ + ent->flags |= MIXF_POLL; + break; + } + +/* + * Read-only controls are likely to change their value spontaneously so + * they should be polled. + */ + if (!(ent->flags & MIXF_WRITEABLE)) + ent->flags |= MIXF_POLL; + + ent->ctrl = ctrl; + return 0; +} + +static int +mixer_ext_get_enuminfo (oss_mixer_enuminfo * ent) +{ + + int dev, ctrl; + + if (ent == NULL) + return OSS_EFAULT; + + dev = ent->dev; + ctrl = ent->ctrl; + memset (ent, 0, sizeof (*ent)); + + if (dev < 0 || dev >= num_mixers) + { + return OSS_ENXIO; + } + + touch_mixer (dev); + + if (ctrl < 0 || ctrl >= mixer_devs[dev]->nr_ext) + { + return OSS_EIDRM; + } + + if (mixer_devs[dev]->extensions[ctrl].enum_info == NULL) + { + return OSS_EIO; + } + + memcpy ((char *) ent, + (char *) mixer_devs[dev]->extensions[ctrl].enum_info, + sizeof (*ent)); + ent->ctrl = ctrl; + return 0; +} + +static int +mixer_ext_get_description (oss_mixer_enuminfo * ent) +{ + + int dev, ctrl; + char *s; + + if (ent == NULL) + return OSS_EFAULT; + + dev = ent->dev; + ctrl = ent->ctrl; + memset (ent, 0, sizeof (*ent)); + + if (dev < 0 || dev >= num_mixers) + { + return OSS_ENXIO; + } + + touch_mixer (dev); + + if (ctrl < 0 || ctrl >= mixer_devs[dev]->nr_ext) + { + return OSS_EIDRM; + } + + if (mixer_devs[dev]->extensions[ctrl].description == NULL) + { + return OSS_EIO; + } + + s = mixer_devs[dev]->extensions[ctrl].description; + + strncpy (ent->strings, s, sizeof (ent->strings)); + ent->strings [sizeof (ent->strings) - 1] = '\0'; + ent->ctrl = ctrl; + return 0; +} + +int +mixer_ext_set_strings (int dev, int ctl, const char *s, int version) +{ +/* + * Note! The version parameter should usually be set to 0. However if the + * value set can change dynamically then it must be initially set to 1 + * and later incremented every time the value set changes. + * This tells the applications to poll for updated values. + */ + static oss_mixer_enuminfo ent; + + memset (&ent, 0, sizeof (ent)); + ent.dev = dev; + ent.ctrl = ctl; + ent.version = version; + ent.nvalues = 0; + strcpy (ent.strings, s); + return mixer_ext_set_enum (&ent); +} + +static int +rebuild_list (oss_mixer_enuminfo * ent) +{ + int i, n, l; + + n = 1; + ent->nvalues = 0; + ent->strindex[0] = 0; + l = strlen (ent->strings); + + for (i = 0; i < l; i++) + if (n < OSS_ENUM_MAXVALUE) + { + if (ent->strings[i] == ' ') + { + ent->strindex[n++] = i + 1; + ent->strings[i] = 0; + } + } + + ent->nvalues = n; + return 0; +} + +int +mixer_ext_set_enum (oss_mixer_enuminfo * ent) +{ + int dev; + int extnr; + + if (ent == NULL) + return OSS_EFAULT; + + if (ent->dev < 0 || ent->dev >= num_mixers) + return OSS_ENXIO; + + dev = ent->dev; + touch_mixer (dev); + + if (ent->ctrl < 0 || ent->ctrl >= mixer_devs[dev]->nr_ext) + return OSS_EIDRM; + extnr = ent->ctrl; + + if (mixer_devs[dev]->extensions[extnr].enum_info == NULL) + mixer_devs[dev]->extensions[extnr].enum_info = + PMALLOC (mixer_devs[dev]->osdev, sizeof (*ent)); + + if (mixer_devs[dev]->extensions[extnr].enum_info == NULL) + return OSS_EIO; + + memcpy ((char *) mixer_devs[dev]->extensions[extnr].enum_info, + (char *) ent, sizeof (*ent)); + + ent = mixer_devs[dev]->extensions[extnr].enum_info; + + if (ent->nvalues <= 0) + return rebuild_list (ent); + + if (ent->nvalues >= OSS_ENUM_MAXVALUE) + { + mixer_devs[dev]->extensions[extnr].enum_info = NULL; + return OSS_EIO; + } + + return 0; +} + +int +mixer_ext_set_description (int dev, int ctrl, const char *desc) +{ + int l = strlen(desc); + + if (dev < 0 || dev >= num_mixers) + return OSS_ENXIO; + + touch_mixer (dev); + + if (ctrl < 0 || ctrl >= mixer_devs[dev]->nr_ext) + return OSS_EIDRM; + + if (l > OSS_ENUM_STRINGSIZE) l = OSS_ENUM_STRINGSIZE; + + mixer_devs[dev]->extensions[ctrl].description = + PMALLOC (mixer_devs[dev]->osdev, l); + + if (mixer_devs[dev]->extensions[ctrl].description == NULL) + return OSS_EIO; + + strncpy (mixer_devs[dev]->extensions[ctrl].description, desc, l); + mixer_devs[dev]->extensions[ctrl].description[l-1] = '\0'; + + mixer_devs[dev]->extensions[ctrl].ext.flags |= MIXF_DESCR; + + return 0; +} + +static int +mixer_ext_read (oss_mixer_value * val) +{ + + int dev; + int extnr; + oss_mixext *ext; + oss_mixext_desc *ext_desc; + mixer_ext_fn func; + + if (val == NULL) + return OSS_EFAULT; + + if (val->dev < 0 || val->dev >= num_mixers) + return OSS_ENXIO; + + dev = val->dev; + + if (!mixer_devs[dev]->enabled || mixer_devs[dev]->unloaded) + return OSS_ENXIO; + touch_mixer (dev); + + if (val->ctrl < 0 || val->ctrl >= mixer_devs[dev]->nr_ext) + return OSS_EIDRM; + extnr = val->ctrl; + + ext_desc = &mixer_devs[dev]->extensions[extnr]; + ext = &ext_desc->ext; + + if (val->timestamp != ext->timestamp) + { + return OSS_EIDRM; + } + + if (ext_desc->handler == NULL || !(ext->flags & MIXF_READABLE)) + return OSS_EFAULT; + + func = (mixer_ext_fn) ext_desc->handler; + return (val->value = func (dev, ext->ctrl, SNDCTL_MIX_READ, val->value)); +} + +static int +mixer_ext_write (oss_mixer_value * val) +{ + + int dev; + int extnr; + int err; + oss_mixext *ext; + oss_mixext_desc *ext_desc; + mixer_ext_fn func; + + if (val == NULL) + { + cmn_err (CE_WARN, "NULL argument in mixer call\n"); + return OSS_EFAULT; + } + + if (val->dev < 0 || val->dev >= num_mixers) + return OSS_ENXIO; + + dev = val->dev; + if (!mixer_devs[dev]->enabled || mixer_devs[dev]->unloaded) + return OSS_ENXIO; + touch_mixer (dev); + + if (val->ctrl < 0 || val->ctrl >= mixer_devs[dev]->nr_ext) + return OSS_EIDRM; + extnr = val->ctrl; + + ext_desc = &mixer_devs[dev]->extensions[extnr]; + ext = &ext_desc->ext; + + if (ext_desc->handler == NULL || !(ext->flags & MIXF_WRITEABLE)) + { + cmn_err (CE_WARN, "NULL handler or control not writeable\n"); + return OSS_EFAULT; + } + + if (val->timestamp != ext->timestamp) + { + return OSS_EIDRM; + } + + func = (mixer_ext_fn) ext_desc->handler; + mixer_devs[dev]->modify_counter++; + err = val->value = func (dev, ext->ctrl, SNDCTL_MIX_WRITE, val->value); + + if (err >= 0) + ext->update_counter++; + return err; +} + +int +mixer_ext_create_device (int dev, int maxentries) +{ + oss_mixext_root *mixroot; + oss_mixext *mixext; + oss_mixext_desc *mixext_desc; + char *name = mixer_devs[dev]->name; + char *id = mixer_devs[dev]->id; + int i; + + maxentries++; /* Needs space for the device root node */ + + if (mixer_devs[dev]->max_ext == 0) + { + mixext_desc = + PMALLOC (mixer_devs[dev]->osdev, sizeof (*mixext_desc) * maxentries); + mixer_devs[dev]->max_ext = maxentries; + mixer_devs[dev]->extensions = mixext_desc; + } + else + { + mixext_desc = mixer_devs[dev]->extensions; + } + + if (mixext_desc == NULL) + { + cmn_err (CE_CONT, "Not enough memory for mixer%d (ext)\n", dev); + return OSS_EIO; + } + + mixer_devs[dev]->nr_ext = 1; + mixer_devs[dev]->timestamp = GET_JIFFIES (); + + mixext = &mixext_desc->ext; + mixext->dev = dev; + mixext->ctrl = -1; /* Undefined */ + mixext->type = MIXT_DEVROOT; + mixext->maxvalue = 0; + mixext->minvalue = 0; + mixext->flags = 0; + strcpy (mixext->id, "DEVROOT"); + mixext->parent = 0; /* Link to itself */ + mixext->timestamp = mixer_devs[dev]->timestamp; + mixext_desc->handler = NULL; + memset (mixext->data, 0, sizeof (mixext->data)); + + mixroot = (oss_mixext_root *) & mixext->data; + + for (i = 0; i < 15 && id[i]; i++) + mixroot->id[i] = id[i]; + mixroot->id[15] = 0; + + for (i = 0; i < 47 && name[i]; i++) + mixroot->name[i] = name[i]; + mixroot->name[47] = 0; + + return 0; +} + +int +mixer_ext_create_group_flags (int dev, int parent, const char *id, + unsigned int flags) +{ + oss_mixext *mixext; + oss_mixext_desc *mixext_desc, *parent_desc; + int enumber; + + flags &= ~MIXF_DESCR; + + if (mixer_devs[dev]->extensions == NULL) + { + cmn_err (CE_WARN, "Mixer extensions not initialized for device %d\n", + dev); + return OSS_EFAULT; + } + + /* + * Ensure that the parent node number is valid. + */ + if (parent < 0 || parent >= mixer_devs[dev]->nr_ext) + parent = 0; + + parent_desc = + &mixer_devs[dev]->extensions[parent]; + mixext = &parent_desc->ext; + + if (mixext->type != MIXT_DEVROOT && mixext->type != MIXT_GROUP) + parent = 0; /* Point to the root group */ + + if (mixer_devs[dev]->nr_ext >= mixer_devs[dev]->max_ext) + { + cmn_err (CE_WARN, "Out of mixer controls for device %d/%s (%d)\n", dev, + mixer_devs[dev]->name, mixer_devs[dev]->max_ext); + return OSS_ENOSPC; + } + + mixext_desc = + &mixer_devs[dev]->extensions[(enumber = mixer_devs[dev]->nr_ext++)]; + mixext = &mixext_desc->ext; + mixext->dev = dev; + mixext->ctrl = -1; /* Undefined */ + mixext->type = MIXT_GROUP; + mixext->maxvalue = 0; + mixext->minvalue = 0; + mixext->flags = flags | MIXF_FLAT; /* Will be unflattened later if required */ + strcpy (mixext->id, id); + mixext->parent = parent; + mixext_desc->handler = NULL; + mixext_desc->enum_info = NULL; + memset (mixext->enum_present, 0xff, sizeof (mixext->enum_present)); + mixext->timestamp = mixer_devs[dev]->timestamp; + mixext->control_no = -1; + mixext->desc = 0; + memset (mixext->data, 0, sizeof (mixext->data)); + + return enumber; +} + +int +mixer_ext_create_group (int dev, int parent, const char *id) +{ + return mixer_ext_create_group_flags (dev, parent, id, 0); +} + +int +mixer_ext_truncate (int dev, int index) +{ + if (index < mixer_devs[dev]->nr_ext) + { + mixer_devs[dev]->nr_ext = index; + mixer_devs[dev]->modify_counter++; + mixer_devs[dev]->timestamp++; + } + return 0; +} + +static void expand_names (int dev); +static void unflatten_group (int dev, int group); +static void touch_parents (int dev, int group); + +int +mixer_ext_create_control (int dev, int parent, int ctrl, mixer_ext_fn func, + int type, const char *id, int maxvalue, int flags) +{ + oss_mixext *mixext; + oss_mixext_desc *mixext_desc, *parent_desc; + int enumber; + + flags &= ~MIXF_DESCR; + + if (mixer_devs[dev]->extensions == NULL) + { + cmn_err (CE_WARN, "Mixer extensions not initialized for device %d\n", + dev); + return OSS_EFAULT; + } + + if (mixer_devs[dev]->nr_ext >= mixer_devs[dev]->max_ext) + { + cmn_err (CE_WARN, "Out of mixer controls for device %d/%s (%d)\n", dev, + mixer_devs[dev]->name, mixer_devs[dev]->max_ext); + return OSS_ENOSPC; + } + + if (func == NULL) /* No access function */ + flags &= ~(MIXF_READABLE | MIXF_WRITEABLE); + + /* + * Ensure that the parent node number is valid. + */ + if (parent < 0 || parent >= mixer_devs[dev]->nr_ext) + parent = 0; + + parent_desc = + &mixer_devs[dev]->extensions[parent]; + mixext = &parent_desc->ext; + + if (mixext->type != MIXT_DEVROOT && mixext->type != MIXT_GROUP) + parent = 0; /* Point to the root group */ + + + mixext_desc = + &mixer_devs[dev]->extensions[(enumber = mixer_devs[dev]->nr_ext++)]; + mixext = &mixext_desc->ext; + mixext->dev = dev; + mixext->ctrl = ctrl; + mixext->type = type; + mixext->maxvalue = maxvalue; + mixext->minvalue = 0; + mixext->flags = flags; + strncpy (mixext->id, id, sizeof (mixext->id)); + mixext->id[sizeof (mixext->id) - 1] = 0; + mixext->parent = parent; + mixext->timestamp = mixer_devs[dev]->timestamp; + mixext_desc->handler = (mixer_ext_fn) func; + mixext_desc->enum_info = NULL; + memset (mixext->data, 0, sizeof (mixext->data)); + memset (mixext->enum_present, 0xff, sizeof (mixext->enum_present)); + mixext->control_no = -1; + mixext->desc = 0; + +/* + * Perform name expansion too if it has already been done for the + * earlier controls. Note that this only gets done with rare devices + * that add/remove controls on fly. + * + * TODO: Optimize this to expand only the current control. Scanning through + * all the controls may be bit time consuming with future devices having 100s + * of controls. + */ + if (mixer_devs[dev]->names_checked) + expand_names (dev); + else + strcpy(mixext->extname, mixext->id); + +/* + * Mark groups with tall controls such as peak meters and sliders as + * non-flat + */ + + switch (type) + { + case MIXT_SLIDER: + case MIXT_MONOSLIDER: + case MIXT_STEREOSLIDER: + case MIXT_MONOSLIDER16: + case MIXT_STEREOSLIDER16: + case MIXT_MONOVU: + case MIXT_STEREOVU: + case MIXT_MONOPEAK: + case MIXT_STEREOPEAK: + case MIXT_MONODB: + case MIXT_STEREODB: + case MIXT_3D: + unflatten_group (dev, parent); + break; + } + + touch_parents(dev, parent); + + return enumber; +} + + +oss_mixext * +mixer_find_ext (int dev, int enumber) +{ + oss_mixext_desc *mixext; + + if (dev < 0 || dev >= num_mixers) + { + return NULL; + } + touch_mixer (dev); + + if (enumber < 0 || enumber >= mixer_devs[dev]->nr_ext) + { + return NULL; + } + + mixext = &mixer_devs[dev]->extensions[enumber]; + + return &mixext->ext; +} + +/* + * Default read/write access functions + */ +int +mixer_ext_rw (int dev, int ctrl, unsigned int cmd, int value) +{ + int err; + + if (cmd == SNDCTL_MIX_READ) + { + if ((err = + oss_legacy_mixer_ioctl (dev, -1, MIXER_READ (ctrl), + (ioctl_arg) & value)) < 0) + return err; + return value; + } + + if (cmd == SNDCTL_MIX_WRITE) + { + if ((err = + oss_legacy_mixer_ioctl (dev, -1, MIXER_WRITE (ctrl), + (ioctl_arg) & value)) < 0) + return err; + return value; + } + + return OSS_EINVAL; +} + +int +mixer_ext_recrw (int dev, int ctrl, unsigned int cmd, int value) +{ + int caps, recmask, err; + + if ((err = + oss_legacy_mixer_ioctl (dev, -1, SOUND_MIXER_READ_CAPS, + (ioctl_arg) & caps)) < 0) + caps = SOUND_CAP_EXCL_INPUT; /* Default */ + + if ((err = + oss_legacy_mixer_ioctl (dev, -1, SOUND_MIXER_READ_RECSRC, + (ioctl_arg) & recmask)) < 0) + return err; + + if (cmd == SNDCTL_MIX_READ) + return (recmask & (1 << ctrl)) ? 1 : 0; + + if (caps & SOUND_CAP_EXCL_INPUT) /* Single recording source */ + recmask = 0; + + if (value) + recmask |= (1 << ctrl); + else + recmask &= ~(1 << ctrl); + + if (recmask == 0) + return 1; /* Can't remove the only recording source */ + + if ((err = + oss_legacy_mixer_ioctl (dev, -1, SOUND_MIXER_WRITE_RECSRC, + (ioctl_arg) & recmask)) < 0) + return err; + + + return (recmask & (1 << ctrl)) ? 1 : 0; +} + +/* + * Mixer extension initialization + */ + +static void +store_name (oss_mixext * thisrec, char *name) +{ + int i; + + while (*name == '.') + name++; + strncpy (thisrec->extname, name, 32); + thisrec->extname[31] = '\0'; + + name = thisrec->extname; + for (i = 0; i < strlen (name); i++) + if (name[i] >= 'A' && name[i] <= 'Z') + name[i] += 32; +} + +static char * +cut_name (char *name) +{ + char *s = name; + while (*s) + if (*s++ == '_') + return s; + + if (name[0] == '@') + return &name[1]; + + return name; +} + +#include "mixerdefs.h" + +static void +find_enum_defs (oss_mixext * thisrec, int dev, int ctl) +{ + int i; + + for (i = 0; mixer_defs[i].name != NULL; i++) + if (strcmp (thisrec->extname, mixer_defs[i].name) == 0) + { + mixer_ext_set_strings (dev, ctl, mixer_defs[i].strings, 0); + return; + } +} + +static void +expand_names (int dev) +{ + int i, n; + oss_mixext_desc *mixext_desc; + + n = mixer_devs[dev]->nr_ext; + mixer_devs[dev]->names_checked = 1; + + if (n < 1) + return; + + for (i = 0; i < n; i++) + { + char tmp[100], *name; + int parent = 0; + oss_mixext *thisrec = NULL, *parentrec = NULL; + + mixext_desc = &mixer_devs[dev]->extensions[i]; + thisrec = &mixext_desc->ext; + + switch (thisrec->type) + { + case MIXT_DEVROOT: + thisrec->extname[0] = 0; + break; + + case MIXT_GROUP: + parent = thisrec->parent; + mixext_desc = &mixer_devs[dev]->extensions[parent]; + parentrec = &mixext_desc->ext; + name = cut_name (thisrec->id); + if (parentrec->extname[0] == 0) + strcpy (tmp, name); + else + sprintf (tmp, "%s.%s", parentrec->extname, name); + store_name (thisrec, tmp); + break; + + case MIXT_STEREOSLIDER: + case MIXT_STEREOSLIDER16: + case MIXT_STEREODB: + case MIXT_STEREOVU: + case MIXT_MONODB: + case MIXT_MONOSLIDER: + case MIXT_MONOSLIDER16: + case MIXT_SLIDER: + case MIXT_MONOVU: + case MIXT_MONOPEAK: + case MIXT_STEREOPEAK: + case MIXT_ONOFF: + case MIXT_MUTE: + case MIXT_ENUM: + case MIXT_VALUE: + case MIXT_HEXVALUE: + case MIXT_3D: + parent = thisrec->parent; + mixext_desc = &mixer_devs[dev]->extensions[parent]; + parentrec = &mixext_desc->ext; + name = cut_name (thisrec->id); + if (*thisrec->id == 0 || *thisrec->id == '-') /* Special (hidden) names */ + strcpy (thisrec->extname, parentrec->extname); + else + { + sprintf (tmp, "%s.%s", parentrec->extname, name); + store_name (thisrec, tmp); + + if (thisrec->type == MIXT_ENUM) + find_enum_defs (thisrec, dev, i); + } + break; + + case MIXT_MARKER: + break; + + default:; + } + } +/* + * Fix duplicate names. + */ + + for (i = 0; i < n; i++) + { + char tmp[100]; + int j, dupes = 0; + oss_mixext *thisrec = NULL; + + mixext_desc = &mixer_devs[dev]->extensions[i]; + thisrec = &mixext_desc->ext; + + if (thisrec->type == MIXT_GROUP) + continue; + + strcpy (tmp, thisrec->extname); + + for (j = i + 1; j < n; j++) + { + oss_mixext_desc *mixext_desc2; + oss_mixext *thisrec2 = NULL; + mixext_desc2 = &mixer_devs[dev]->extensions[j]; + thisrec2 = &mixext_desc2->ext; + + if (thisrec2->type == MIXT_GROUP) + continue; + + if (strcmp (thisrec2->extname, tmp) == 0) + dupes++; + } + + if (dupes > 0) /* Need to fix duplicates */ + { + int count = 1, len; + char tmp2[32]; + + for (j = i; j < n; j++) + { + oss_mixext_desc *mixext_desc2; + oss_mixext *thisrec2 = NULL; + mixext_desc2 = &mixer_devs[dev]->extensions[j]; + thisrec2 = &mixext_desc2->ext; + + if (thisrec2->type != MIXT_GROUP) + if (strcmp (thisrec2->extname, tmp) == 0) + { + sprintf (tmp2, "%d", count++); + tmp2[31] = '\0'; + len = strlen (thisrec2->extname); + if (len >= sizeof (thisrec2->extname) - strlen (tmp2)) + len = sizeof (thisrec2->extname) - strlen (tmp2) - 1; + strcpy (thisrec2->extname + len, tmp2); + } + } + } + } +} + +static void +unflatten_group (int dev, int group) +{ +/* + * Clear the MIXF_FLAT flags from all parent groups (recursively): + */ + int n; + oss_mixext_desc *mixext_desc; + oss_mixext *thisrec = NULL; + + n = mixer_devs[dev]->nr_ext; + + if (n < 1) + return; + + if (group <= 0 || group >= n) + return; + + mixext_desc = &mixer_devs[dev]->extensions[group]; + thisrec = &mixext_desc->ext; + + if (thisrec->type != MIXT_GROUP) /* Not a group */ + return; + + if (!(thisrec->flags & MIXF_FLAT)) /* Already unflattened */ + return; + + thisrec->flags &= ~MIXF_FLAT; + + if (thisrec->parent >= group) /* Broken link */ + return; + + unflatten_group (dev, thisrec->parent); /* Unflatten the parent */ +} + +static void +touch_parents (int dev, int group) +{ + int n; + oss_mixext_desc *mixext_desc; + oss_mixext *thisrec = NULL; + + n = mixer_devs[dev]->nr_ext; + + if (n < 1) + return; + + if (group <= 0 || group >= n) + return; + + mixext_desc = &mixer_devs[dev]->extensions[group]; + thisrec = &mixext_desc->ext; + + while (thisrec->type != MIXT_DEVROOT) + { + if (thisrec->type != MIXT_GROUP) /* Not a group */ + return; + + thisrec->update_counter++; + + if (thisrec->parent >= group) /* Broken link */ + return; + + unflatten_group (dev, thisrec->parent); /* Unflatten the parent */ + + mixext_desc = &mixer_devs[dev]->extensions[thisrec->parent]; + thisrec = &mixext_desc->ext; + } + + thisrec->update_counter++; +} + +#define INPUT_MASK (SOUND_MASK_RECLEV|SOUND_MASK_IGAIN) +#define OUTPUT_MASK (SOUND_MASK_PCM|SOUND_MASK_ALTPCM|SOUND_MASK_VOLUME| \ + SOUND_MASK_BASS|SOUND_MASK_TREBLE|SOUND_MASK_SYNTH| \ + SOUND_MASK_IMIX|SOUND_MASK_REARVOL|SOUND_MASK_CENTERVOL| \ + SOUND_MASK_SIDEVOL) +#define MONITOR_MASK (SOUND_MASK_SPEAKER|SOUND_MASK_LINE|SOUND_MASK_LINE| \ + SOUND_MASK_MIC|SOUND_MASK_CD|SOUND_MASK_LINE1|SOUND_MASK_LINE2| \ + SOUND_MASK_LINE3|SOUND_MASK_DIGITAL1|SOUND_MASK_DIGITAL2| \ + SOUND_MASK_DIGITAL3|SOUND_MASK_MONO|SOUND_MASK_PHONE| \ + SOUND_MASK_RADIO|SOUND_MASK_VIDEO) + +void +touch_mixer (int dev) +{ + int i, n, devmask, recmask, stereomask, grp, root = 0, caps = 0; + static char *id[] = SOUND_DEVICE_NAMES; + int created = 0; + + if (mixer_devs[dev] == NULL || mixer_devs[dev]->unloaded + || !mixer_devs[dev]->enabled) + { + return; + } + +/* + * Create default mixer extension records if required. + */ + if (mixer_devs[dev]->nr_ext > 0) /* Already initialized */ + return; + +/* + * Compute number of required mixer extension entries + */ + + n = mixer_devs[dev]->nr_extra_ext; /* Reserve space for the actual driver */ + if (n < 20) + n = 20; + + if (oss_legacy_mixer_ioctl + (dev, -1, SOUND_MIXER_READ_CAPS, (ioctl_arg) & caps) < 0) + caps = 0; /* Error */ + + if (oss_legacy_mixer_ioctl + (dev, -1, SOUND_MIXER_READ_DEVMASK, (ioctl_arg) & devmask) < 0) + goto skip; /* Error */ + + /* Remove devices that are handled otherwise */ + devmask &= ~mixer_devs[dev]->ignore_mask; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (devmask & (1 << i)) /* This control is supported */ + n++; + + n = n * 2; + + if (oss_legacy_mixer_ioctl + (dev, -1, SOUND_MIXER_READ_RECMASK, (ioctl_arg) & recmask) < 0) + goto skip; /* Error */ + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (recmask & (1 << i)) /* This control is also recording device */ + n++; + + root = 0; + +#ifdef CONFIG_OSSD + n += 20; /* Space for OSSD use */ +#endif + + n = n + 5; /* The marker entry and some spare space */ + if ((root = mixer_ext_create_device (dev, n)) < 0) + return; /* Error */ + created = 1; + + if (oss_legacy_mixer_ioctl (dev, -1, SOUND_MIXER_READ_STEREODEVS, + (ioctl_arg) & stereomask) < 0) + stereomask = -1; /* Assume all stereo */ + + if (!(caps & SOUND_CAP_NOLEGACY)) /* Don't (re)export the legacy mixer */ + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (devmask & (1 << i)) + { + if ((grp = + mixer_ext_create_group_flags (dev, root, id[i], + MIXF_LEGACY)) > 0) + { + int cnum; + oss_mixext *ent; + int flags = 0; + + /* + * Set the type hints for main and PCM volume controls + */ + + switch (i) + { + case SOUND_MIXER_VOLUME: + case SOUND_MIXER_MONO: + case SOUND_MIXER_REARVOL: + case SOUND_MIXER_CENTERVOL: + case SOUND_MIXER_SIDEVOL: + flags |= MIXF_MAINVOL; + break; + + case SOUND_MIXER_PCM: + case SOUND_MIXER_ALTPCM: + flags |= MIXF_PCMVOL; + break; + + case SOUND_MIXER_RECLEV: + case SOUND_MIXER_IGAIN: + flags |= MIXF_RECVOL; + break; + + case SOUND_MIXER_SYNTH: + case SOUND_MIXER_SPEAKER: + case SOUND_MIXER_LINE: + case SOUND_MIXER_LINE1: + case SOUND_MIXER_LINE2: + case SOUND_MIXER_LINE3: + case SOUND_MIXER_MIC: + case SOUND_MIXER_CD: + case SOUND_MIXER_DIGITAL1: + case SOUND_MIXER_DIGITAL2: + case SOUND_MIXER_DIGITAL3: + case SOUND_MIXER_PHONE: + case SOUND_MIXER_VIDEO: + case SOUND_MIXER_RADIO: + flags |= MIXF_MONVOL; + break; + } + + if (stereomask & (1 << i)) + cnum = mixer_ext_create_control (dev, grp, i, mixer_ext_rw, + MIXT_STEREOSLIDER, + "", 100, + flags | MIXF_READABLE | + MIXF_WRITEABLE); + else + cnum = mixer_ext_create_control (dev, grp, i, mixer_ext_rw, + MIXT_MONOSLIDER, + "", 100, + flags | MIXF_READABLE | + MIXF_WRITEABLE); + + if ((ent = mixer_find_ext (dev, cnum)) != NULL) + { + ent->control_no = i; + + if ((1 << i) & INPUT_MASK) + ent->desc &= MIXEXT_SCOPE_INPUT; + if ((1 << i) & OUTPUT_MASK) + ent->desc &= MIXEXT_SCOPE_OUTPUT; + if ((1 << i) & MONITOR_MASK) + ent->desc &= MIXEXT_SCOPE_MONITOR; + + /* + * Set the RGB color for some of the controls + * to match the usual jack color. + */ + + switch (i) + { + case SOUND_MIXER_MIC: ent->rgbcolor=OSS_RGB_PINK; break; + case SOUND_MIXER_LINE: ent->rgbcolor=OSS_RGB_BLUE; break; + case SOUND_MIXER_VOLUME: ent->rgbcolor=OSS_RGB_GREEN; break; + case SOUND_MIXER_REARVOL: ent->rgbcolor=OSS_RGB_BLACK; break; + case SOUND_MIXER_SIDEVOL: ent->rgbcolor=OSS_RGB_GRAY; break; + case SOUND_MIXER_CENTERVOL: ent->rgbcolor=OSS_RGB_ORANGE; break; + } + } + + if (recmask & (1 << i)) + { + cnum = + mixer_ext_create_control (dev, grp, i, mixer_ext_recrw, + MIXT_ONOFF, "REC", 1, + MIXF_READABLE | MIXF_WRITEABLE | MIXF_RECVOL); + if ((ent = mixer_find_ext (dev, cnum)) != NULL) + { + ent->desc &= MIXEXT_SCOPE_RECSWITCH; + } + } + } + } + +skip: + if (!created) + if ((root = mixer_ext_create_device (dev, n)) < 0) + return; /* Error */ + mixer_ext_create_control (dev, root, 0, NULL, MIXT_MARKER, "", 0, 0); + + if (mixer_devs[dev]->create_controls != NULL) + mixer_devs[dev]->create_controls (dev); + expand_names (dev); +} + +int +mixer_ext_set_init_fn (int dev, mixer_create_controls_t func, int nextra) +{ +/* + * Set device dependent mixer extension initialization function and + * reserve some extension entries for device dependent use. + * + * This initialization function will be called later when/if the + * extended mixer is actually used. + */ + if (dev < 0 || dev >= num_mixers) + return OSS_ENXIO; + + mixer_devs[dev]->nr_extra_ext = nextra; + mixer_devs[dev]->create_controls = func; + return 0; +} + +int +mixer_ext_rebuild_all (int dev, mixer_create_controls_t func, int nextra) +{ +/* + * Throw away all existing mixer controls and recreate the mixer. + */ + if (dev < 0 || dev >= num_mixers) + return OSS_ENXIO; + mixer_devs[dev]->nr_ext = 0; + + mixer_devs[dev]->nr_extra_ext = nextra; + mixer_devs[dev]->create_controls = func; + + touch_mixer (dev); + if (mixer_devs[dev]->create_vmix_controls != NULL) + { + mixer_devs[dev]->create_vmix_controls(dev); + } + + return 0; +} + +int +mixer_ext_set_vmix_init_fn (int dev, mixer_create_controls_t func, int nextra, + void *devc) +{ +/* + * Set device dependent mixer extension initialization function and + * reserve some extension entries for device dependent use. + * + * This initialization function will be called later when/if the + * extended mixer is actually used. + */ + if (dev < 0 || dev >= num_mixers) + return OSS_ENXIO; + + mixer_devs[dev]->nr_extra_ext += nextra; + mixer_devs[dev]->create_vmix_controls = func; + mixer_devs[dev]->vmix_devc = devc; + touch_mixer (dev); + func (dev); + expand_names (dev); + return 0; +} + +#ifdef VDEV_SUPPORT +static void +ainfo_combine_caps (oss_audioinfo * ainfo, adev_p adev) +{ + if (!(adev->flags & ADEV_NOOUTPUT)) + ainfo->caps |= DSP_CAP_OUTPUT; + else + ainfo->caps &= ~DSP_CAP_OUTPUT; + + if (!(adev->flags & ADEV_NOINPUT)) + ainfo->caps |= DSP_CAP_INPUT; + else + ainfo->caps &= ~DSP_CAP_INPUT; + + if (adev->flags & ADEV_DUPLEX) + ainfo->caps |= DSP_CAP_DUPLEX; + else + ainfo->caps &= ~DSP_CAP_DUPLEX; + +#ifdef ALLOW_BUFFER_MAPPING + if (!(adev->flags & ADEV_NOMMAP)) + ainfo->caps |= DSP_CAP_MMAP; +#endif +} +#endif + +#if 0 +/* + * Device list support is currently not used + */ +static int +check_list (oss_devlist_t * oldlist, oss_devlist_t * newlist) +{ +/* + * Check that the same devices are present in both lists. Any difference + * indicates that the device configuration has changed (invalidates the list). + */ +#if MAX_AUDIO_DEVFILES > 64 +#error Too many audio devices - fix this algorithm +#endif + unsigned long long mask1, mask2; + int i; + + if (newlist->ndevs != oldlist->ndevs) + return 0; + + mask1 = 0LL; + mask2 = 0LL; + + for (i = 0; i < oldlist->ndevs; i++) + mask1 |= 1LL << oldlist->devices[i]; + for (i = 0; i < newlist->ndevs; i++) + mask1 |= 1LL << newlist->devices[i]; + + if (mask1 != mask2) + return 0; + + return 1; +} +#endif + +static int +get_engineinfo (int dev, oss_audioinfo * info, int combine_slaves) +{ + int dev_present = 0; + int i; + oss_native_word flags; + + adev_p adev, next; + + flags = 0; + memset ((char *) info, 0, sizeof (*info)); + + if (dev < 0 || dev >= num_audio_engines) + return OSS_ENXIO; + + adev = audio_engines[dev]; + if (adev == NULL) + { + cmn_err (CE_WARN, "Internal error - adev==NULL (%d)\n", dev); + return OSS_ENXIO; + } + + if (!adev->unloaded && adev->enabled) + dev_present = 1; + + if (dev_present) + { + MUTEX_ENTER_IRQDISABLE (adev->mutex, flags); + } + info->dev = dev; + strcpy (info->name, adev->name); + strcpy (info->handle, adev->handle); + info->busy = adev->open_mode; + info->caps = adev->caps; + if (!(adev->flags & ADEV_NOINPUT)) + info->caps |= PCM_CAP_INPUT; + if (!(adev->flags & ADEV_NOOUTPUT)) + info->caps |= PCM_CAP_OUTPUT; + if (adev->flags & ADEV_SPECIAL) + info->caps |= PCM_CAP_SPECIAL; + if (adev->flags & ADEV_VIRTUAL) + info->caps |= PCM_CAP_VIRTUAL; + + if (adev->flags & (ADEV_HIDDEN | ADEV_SHADOW)) + { + info->caps |= PCM_CAP_HIDDEN; + } + + if (adev->flags & ADEV_DUPLEX) + { + info->caps |= PCM_CAP_DUPLEX; + } + if (adev->d->adrv_trigger) /* Supports SETTRIGGER */ + info->caps |= PCM_CAP_TRIGGER; +#ifdef ALLOW_BUFFER_MAPPING + if (!(adev->flags & ADEV_NOMMAP)) + info->caps |= PCM_CAP_MMAP; +#endif + + info->oformats = adev->oformat_mask; + info->iformats = adev->iformat_mask; + info->pid = adev->pid; + info->latency = adev->latency; + *info->cmd = 0; + strncpy (info->cmd, adev->cmd, sizeof (info->cmd)); + info->cmd[sizeof (info->cmd) - 1] = 0; + + strcpy (info->devnode, adev->devnode); + + if (!adev->unloaded && adev->enabled) + { + if (audio_engines[dev]->d->adrv_ioctl (dev, SNDCTL_GETSONG, + (ioctl_arg) info->song_name) == + OSS_EINVAL) + strcpy (info->song_name, adev->song_name); + if (audio_engines[dev]->d->adrv_ioctl (dev, SNDCTL_GETLABEL, + (ioctl_arg) info->label) == + OSS_EINVAL) + strcpy (info->label, adev->label); + } + + if (*info->label == 0) + { + strncpy (info->label, info->cmd, sizeof (info->label)); + info->label[sizeof (info->label) - 1] = 0; + } + + info->magic = adev->magic; + info->card_number = adev->card_number; + info->port_number = adev->port_number; + info->mixer_dev = adev->mixer_dev; + info->legacy_device = adev->real_dev; + info->rate_source = adev->rate_source; + info->enabled = (adev->enabled && !adev->unloaded); + info->flags = adev->flags; + info->min_rate = adev->min_rate; + info->max_rate = adev->max_rate; + info->min_channels = adev->min_channels; + info->max_channels = adev->max_channels; + info->binding = adev->binding; + info->nrates = adev->nrates; + for (i = 0; i < info->nrates; i++) + info->rates[i] = adev->rates[i]; + + if (adev->next_out == NULL || !dev_present) + info->next_play_engine = 0; + else + { + info->next_play_engine = adev->next_out->engine_num; + next = adev->next_out; + +#ifdef VDEV_SUPPORT + i = 0; + while (combine_slaves && next != NULL && i++ < num_audio_engines) + { + ainfo_combine_caps (info, next); + next = next->next_out; + } +#endif + } + + if (adev->next_in == NULL || !dev_present) + info->next_rec_engine = 0; + else + { + info->next_rec_engine = adev->next_in->engine_num; + next = adev->next_in; + +#ifdef VDEV_SUPPORT + i=0; + while (combine_slaves && next != NULL && i++ < num_audio_engines) + { + ainfo_combine_caps (info, next); + next = next->next_in; + } +#endif + } + + if (dev_present) + { + MUTEX_EXIT_IRQRESTORE (adev->mutex, flags); + } + return 0; +} + +#ifdef CONFIG_OSS_VMIX +static int +vmixctl_attach(vmixctl_attach_t *att) +{ + int err; + oss_device_t *osdev; + + if (att->masterdev<0 || att->masterdev >= num_audio_engines) + return OSS_ENXIO; + + if (att->inputdev != -1) + if (att->inputdev<0 || att->inputdev >= num_audio_engines) + return OSS_ENXIO; + + osdev=audio_engines[att->masterdev]->master_osdev; + + if ((err=vmix_attach_audiodev(osdev, att->masterdev, att->inputdev, att->attach_flags))<0) + return err; + + return 0; +} + +static int +vmixctl_detach(vmixctl_attach_t *att) +{ + int err; + oss_device_t *osdev; + + if (att->masterdev<0 || att->masterdev >= num_audio_engines) + return OSS_ENXIO; + + osdev=audio_engines[att->masterdev]->master_osdev; + + if ((err=vmix_detach_audiodev(att->masterdev))<0) + return err; + + return 0; +} + +static int +vmixctl_rate(vmixctl_rate_t *rate) +{ + int err; + + if (rate->masterdev<0 || rate->masterdev >= num_audio_engines) + return OSS_ENXIO; + + if ((err=vmix_set_master_rate(rate->masterdev, rate->rate))<0) + return err; + + return 0; +} + +static int +vmixctl_map_channels(vmixctl_map_t *map) +{ + int err; + + if (map->masterdev < 0 || map->masterdev >= num_audio_engines) + return OSS_ENXIO; + + if ((err = vmix_set_channel_map (map->masterdev, &map->map)) < 0) + return err; + + return 0; +} +#endif + +int +oss_mixer_ext (int orig_dev, int class, unsigned int cmd, ioctl_arg arg) +{ + int val; + int combine_slaves = 0; +#ifdef MANAGE_DEV_DSP +#ifdef VDEV_SUPPORT + extern void oss_combine_write_lists (void); +#endif +#endif + + switch (cmd) + { + case SNDCTL_SYSINFO: /* Formerly OSS_SYSINFO */ + { + oss_sysinfo *info = (oss_sysinfo *) arg; + int i; + + memset (info, 0, sizeof (*info)); + strcpy (info->product, "OSS"); + strncpy (info->version, OSS_VERSION_STRING, sizeof (info->version)); + info->version [sizeof (info->version) - 1] = '\0'; + strcpy (info->license, oss_license_string); + info->versionnum = OSS_VERSION; + +#ifdef OSS_HG_INFO + /* Detailed Mercurial version */ + strncpy (info->revision_info, OSS_HG_INFO, sizeof(info->revision_info)); + info->revision_info[sizeof(info->revision_info)-1]=0; +#endif + + memset (info->options, 0, sizeof (info->options)); + + info->numaudios = num_audio_devfiles; + info->numaudioengines = num_audio_engines; + for (i = 0; i < 8; i++) + info->openedaudio[i] = 0; + for (i = 0; i < num_audio_engines; i++) + if (audio_engines[i] != NULL) + { + if (audio_engines[i]->flags & ADEV_OPENED) + if (audio_engines[i]->next_out != NULL) + { + int x = audio_engines[i]->real_dev; + info->openedaudio[x / 32] |= 1 << (x % 32); + } + } + for (i = 0; i < 8; i++) + info->openedmidi[i] = 0; + for (i = 0; i < num_mididevs; i++) + if (midi_devs[i]->open_mode != 0) + info->openedmidi[i / 32] |= 1 << (i % 32); + + info->numsynths = 0; +#ifdef CONFIG_OSS_MIDI + info->nummidis = num_mididevs; +#endif + info->numtimers = oss_num_timers; + info->nummixers = num_mixers; + info->numcards = oss_num_cards; + + return 0; + } + break; + + case SNDCTL_MIX_NRMIX: + return *arg = num_mixers; + break; + + case SNDCTL_MIX_NREXT: /* Return # of mixer extensions for device */ + val = *arg; + *arg = 0; + if (val==-1) + val=orig_dev; + if (val < 0 || val >= num_mixers) + return OSS_ENXIO; + if (mixer_devs[val] == NULL || mixer_devs[val]->unloaded + || !mixer_devs[val]->enabled) + { + return OSS_ENXIO; + } + + + touch_mixer (val); + return *arg = mixer_devs[val]->nr_ext; + break; + + case SNDCTL_MIX_EXTINFO: + return oss_mixer_ext_info ((oss_mixext *) arg); + break; + + case SNDCTL_MIX_ENUMINFO: + return mixer_ext_get_enuminfo ((oss_mixer_enuminfo *) arg); + break; + + case SNDCTL_MIX_DESCRIPTION: + return mixer_ext_get_description ((oss_mixer_enuminfo *) arg); + break; + + case SNDCTL_MIX_READ: + return mixer_ext_read ((oss_mixer_value *) arg); + break; + + case SNDCTL_MIX_WRITE: + return mixer_ext_write ((oss_mixer_value *) arg); + break; + + case OSS_GETVERSION: + return *arg = OSS_VERSION; + break; + + case SNDCTL_AUDIOINFO: + case SNDCTL_AUDIOINFO_EX: + { + int dev; + oss_audioinfo *info = (oss_audioinfo *) arg; + + if (info == NULL) + return OSS_EFAULT; + + dev = info->dev; + + if (dev == -1) /* Request for the current device */ + { + oss_audio_set_error (orig_dev, E_PLAY, + OSSERR (1022, + "SNDCTL_AUDIOINFO called with dev=-1.."), + 0); + /* + * Errordesc: + * Applications that try to obtain audio device information about + * the current device should call SNDCTL_ENGINEINFO instead of + * SNDCTL_AUDIOINFO. + * + * Audio file descriptors returned by open(2) are bound + * directly to specific audio engine instead of the + * device file. + */ + return OSS_EINVAL; + } + + if (dev < 0 || dev >= num_audio_devfiles) + { + return OSS_EINVAL; + } + + if (audio_devfiles[dev] == NULL) + { + return OSS_EIO; + } + + if (dev >= 0) + { + dev = audio_devfiles[dev]->engine_num; /* Get the engine number */ + if (cmd == SNDCTL_AUDIOINFO && info->dev != -1) + combine_slaves = 1; + } + return get_engineinfo (dev, info, combine_slaves); + } + break; + + case SNDCTL_ENGINEINFO: + { + int dev; + oss_audioinfo *info = (oss_audioinfo *) arg; + + if (info == NULL) + return OSS_EFAULT; + + dev = info->dev; + + if (dev == -1) /* Request for the current device */ + switch (class) + { + case OSS_DEV_DSP: + case OSS_DEV_DSP_ENGINE: + dev = orig_dev; + break; + + default: + cmn_err(CE_WARN, "Unrecognized device class %d for dev %d\n", class, orig_dev); + return OSS_EINVAL; + } + + if (dev < 0 || dev >= num_audio_engines) + { + return OSS_EINVAL; + } + + return get_engineinfo (dev, info, 0); + } + break; + +#ifdef CONFIG_OSS_MIDI + case SNDCTL_MIDIINFO: + { + int dev; + oss_native_word flags; + extern int oss_num_midi_clients; /* midi.c */ + + oss_midi_info *info = (oss_midi_info *) arg; + mididev_t *mdev; + + dev = info->dev; + + if (dev == -1) /* Request for the current device */ + switch (class) + { + case OSS_DEV_MIDI: + /* + * Figure out the HW device connected to this client (orig_dev). + */ + dev = orig_dev; + if (dev < 0 || dev >= oss_num_midi_clients) + return OSS_ENXIO; + if (oss_midi_clients[dev]->mididev == NULL) + return OSS_EBUSY; /* No binding established (yet) */ + dev = oss_midi_clients[dev]->mididev->dev; + break; + + default: + return OSS_EINVAL; + } + + if (dev < 0 || dev >= num_mididevs) + { + return OSS_EINVAL; + } + + memset ((char *) info, 0, sizeof (*info)); + + mdev = midi_devs[dev]; + MUTEX_ENTER_IRQDISABLE (mdev->mutex, flags); + info->dev = dev; + strcpy (info->name, mdev->name); + strcpy (info->handle, mdev->handle); + info->pid = mdev->pid; + info->busy = mdev->open_mode; + *info->cmd = 0; + strncpy (info->cmd, mdev->cmd, sizeof (info->cmd)); + info->cmd[sizeof (info->cmd) - 1] = 0; + info->magic = mdev->magic; + info->card_number = mdev->card_number; + strcpy (info->devnode, mdev->devnode); + info->legacy_device = mdev->real_dev; + info->port_number = mdev->port_number; + info->enabled = mdev->enabled; + info->flags = mdev->flags; + info->caps = mdev->caps; + info->latency = mdev->latency; + if (!(info->caps & MIDI_CAP_INOUT)) + info->caps |= MIDI_CAP_INOUT; + if (mdev->flags & MFLAG_VIRTUAL) + info->caps |= MIDI_CAP_VIRTUAL; + if (mdev->flags & MFLAG_CLIENT) + info->caps |= MIDI_CAP_CLIENT; + if (mdev->flags & MFLAG_SERVER) + info->caps |= MIDI_CAP_SERVER; + if (mdev->flags & MFLAG_INTERNAL) + info->caps |= MIDI_CAP_INTERNAL; + if (mdev->flags & MFLAG_EXTERNAL) + info->caps |= MIDI_CAP_EXTERNAL; + if (mdev->flags & MFLAG_MTC) + info->caps |= MIDI_CAP_MTC; + if (midi_devs[dev]->enabled && !midi_devs[dev]->unloaded) + if (midi_devs[dev]->d->ioctl) + { + midi_devs[dev]->d->ioctl (dev, SNDCTL_GETSONG, + (ioctl_arg) info->song_name); + midi_devs[dev]->d->ioctl (dev, SNDCTL_GETLABEL, + (ioctl_arg) info->label); + } + if (*info->label == 0) + { + strncpy (info->label, info->cmd, sizeof (info->label)); + info->label[sizeof (info->label) - 1] = 0; + } + MUTEX_EXIT_IRQRESTORE (mdev->mutex, flags); + } + return 0; + break; +#endif + + case SNDCTL_CARDINFO: + { + int card, err; + + oss_card_info *info = (oss_card_info *) arg; + card = info->card; + + if (card < 0 || card >= oss_num_cards) + { + return OSS_ENXIO; + } + + memset ((char *) info, 0, sizeof (*info)); + if ((err = oss_get_cardinfo (card, info)) < 0) + return err; + info->card = card; + } + return 0; + break; + + case SNDCTL_MIXERINFO: + { + int dev; + + oss_mixerinfo *info = (oss_mixerinfo *) arg; + mixer_operations_t *mdev; + + dev = info->dev; + + if (dev == -1) /* Request for the current device */ + switch (class) + { + case OSS_DEV_MIXER: + dev = orig_dev; + break; + + default: + return OSS_EINVAL; + } + + if (dev < 0 || dev >= num_mixers) + { + return OSS_ENXIO; + } + + if (mixer_devs[dev] == NULL) + return OSS_ENXIO; + + memset ((char *) info, 0, sizeof (*info)); + touch_mixer (dev); + + mdev = mixer_devs[dev]; + info->dev = dev; + strncpy (info->name, mdev->name, sizeof (info->name)); + info->name[sizeof (info->name) - 1] = '\0'; + strcpy (info->id, mdev->id); + strcpy (info->handle, mdev->handle); + info->card_number = mdev->card_number; + info->port_number = mdev->port_number; + info->enabled = (mdev->enabled && !mdev->unloaded); + info->magic = mdev->magic; + info->caps = mdev->caps; + info->flags = mdev->flags; + info->modify_counter = mdev->modify_counter; + info->nrext = mdev->nr_ext; + info->priority = mdev->priority; + strcpy (info->devnode, mdev->devnode); + info->legacy_device = mdev->real_dev; + } + return 0; + break; + + case OSSCTL_RENUM_AUDIODEVS: + { + oss_renumber_t *r = (oss_renumber_t *) arg; + int i; + +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + + if (r->n != num_audio_devfiles) /* Wrong map size? */ + { + cmn_err (CE_NOTE, "Legacy audio map size mismatch %d/%d\n", + r->n, num_audio_devfiles); + return OSS_EINVAL; + } + + for (i = 0; i < r->n; i++) + { + adev_p adev = audio_devfiles[i]; + + if (r->map[i] >= HARD_MAX_AUDIO_DEVFILES) /* May be unnecessary check */ + return OSS_EINVAL; + + if (r->map[i] < -1) + r->map[i] = -1; + + adev->real_dev = r->map[i]; + } + } + return 0; + break; + + case OSSCTL_RENUM_MIXERDEVS: + { + oss_renumber_t *r = (oss_renumber_t *) arg; + int i; + +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + + if (r->n != num_mixers) /* Wrong map size? */ + return OSS_EINVAL; + + for (i = 0; i < r->n; i++) + { + mixdev_p mdev = mixer_devs[i]; + + if (r->map[i] >= HARD_MAX_AUDIO_DEVFILES) /* May be unnecessary check */ + return OSS_EINVAL; + + mdev->real_dev = r->map[i]; + } + } + return 0; + break; + + case OSSCTL_RENUM_MIDIDEVS: + { + oss_renumber_t *r = (oss_renumber_t *) arg; + int i; + +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + + if (r->n != num_mididevs) /* Wrong map size? */ + return OSS_EINVAL; + + for (i = 0; i < r->n; i++) + { + mididev_p mdev = midi_devs[i]; + + if (r->map[i] >= HARD_MAX_AUDIO_DEVFILES) /* May be unnecessary check */ + return OSS_EINVAL; + + mdev->real_dev = r->map[i]; + } + } + return 0; + break; + +#ifdef CONFIG_OSS_VMIX + case VMIXCTL_ATTACH: +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + return vmixctl_attach((vmixctl_attach_t*)arg); + break; + + case VMIXCTL_DETACH: +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + return vmixctl_detach((vmixctl_attach_t*)arg); + break; + + case VMIXCTL_RATE: +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + return vmixctl_rate((vmixctl_rate_t*)arg); + break; + + case VMIXCTL_REMAP: +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + return vmixctl_map_channels((vmixctl_map_t *)arg); + break; + +#endif + +#if 0 +/* + * These calls are obsolete and disabled in current OSS version. + */ + case OSSCTL_GET_REROUTE: + { + oss_reroute_t *r = (oss_reroute_t *) arg; + +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + { + return OSS_EINVAL; + } +#endif + + switch (r->mode) + { + case OPEN_READ: + memcpy (&r->devlist, &dspinlist, sizeof (oss_devlist_t)); + break; + + case OPEN_WRITE: +#ifdef MANAGE_DEV_DSP +#ifdef VDEV_SUPPORT + oss_combine_write_lists (); +#endif +#endif + memcpy (&r->devlist, &dspoutlist, sizeof (oss_devlist_t)); + break; + + case OPEN_WRITE | OPEN_READ: + memcpy (&r->devlist, &dspinoutlist, sizeof (oss_devlist_t)); + break; + + default: + return OSS_EINVAL; + } + } + return 0; + break; + +#if 0 + case OSSCTL_SET_REROUTE: + { + oss_reroute_t *r = (oss_reroute_t *) arg; + int i, d; + +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + + for (i = 0; i < r->devlist.ndevs; i++) + { + if ((d = r->devlist.devices[i]) < 0 || d >= num_audio_devfiles) + return OSS_EINVAL; + } + + switch (r->mode) + { + case OPEN_READ: + /* Refuse if number of devices has changed */ + if (!check_list (&r->devlist, &dspinlist)) + { + return OSS_EINVAL; + } + memcpy (&dspinlist, &r->devlist, sizeof (oss_devlist_t)); + break; + + case OPEN_WRITE: +#ifdef MANAGE_DEV_DSP +#ifdef VDEV_SUPPORT + oss_combine_write_lists (); +#endif +#endif + /* Refuse if number of devices has changed */ + if (!check_list (&r->devlist, &dspoutlist)) + { + return OSS_EINVAL; + } + memcpy (&dspoutlist, &r->devlist, sizeof (oss_devlist_t)); + dspoutlist2.ndevs = 0; + break; + + case OPEN_WRITE | OPEN_READ: + /* Refuse if number of devices has changed */ + if (!check_list (&r->devlist, &dspinoutlist)) + { + return OSS_EINVAL; + } + memcpy (&dspinoutlist, &r->devlist, sizeof (oss_devlist_t)); + break; + + default: + return OSS_EINVAL; + } + } + return 0; + break; +#endif + +#ifdef APPLIST_SUPPORT + case OSSCTL_RESET_APPLIST: +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + + oss_applist_size = 0; + return 0; + break; + + case OSSCTL_ADD_APPLIST: + { + app_routing_t *def, *parm = (app_routing_t *) arg; + +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + + if (oss_applist_size >= APPLIST_SIZE) + return OSS_ENOSPC; + + if (parm->dev < -1 || parm->dev >= num_audio_devfiles) + return OSS_ENXIO; + + def = &oss_applist[oss_applist_size]; + + memset (def, 0, sizeof (*def)); + strcpy (def->name, parm->name); + def->mode = parm->mode & (OPEN_READ | OPEN_WRITE); + def->dev = parm->dev; + def->open_flags = parm->open_flags; + oss_applist_size++; + return 0; + } + break; + +#endif +#endif + default: +#if 0 + if (mixer_devs[orig_dev]->d->ioctl != NULL) + return mixer_devs[orig_dev]->d->ioctl (orig_dev, -1, cmd, arg); +#endif + + return OSS_EINVAL; + } +} + +/*ARGSUSED*/ +static int +oss_mixer_open (int dev, int dev_type, struct fileinfo *file, int recursive, + int open_flags, int *newdev) +{ +/* + * Permit opening nonexistent mixer so that certain mixer ioctl calls + * can be called. Other code must check that the devices really exist + * before permitting the calls. + */ + if (dev >= 0 && dev < num_mixers) + return 0; + + if (mixer_devs == NULL) + return 0; + + if (mixer_devs[dev]->unloaded) + return OSS_ENODEV; + + if (!mixer_devs[dev]->enabled) + return OSS_ENXIO; + + return 0; +} + +/*ARGSUSED*/ +static void +oss_mixer_release (int dev, struct fileinfo *file) +{ +} + +/*ARGSUSED*/ +int +oss_mixer_ioctl (int dev, struct fileinfo *bogus, + unsigned int cmd, ioctl_arg arg) +{ + int ret; + + if (cmd == OSS_GETVERSION) + return *arg = OSS_VERSION; + +/* + * Handle SNDCTL_SYSINFO/CARDINFO/etc even if there are no mixer devices in the + * system. + */ + switch (cmd) + { + case OSS_GETVERSION: + case SNDCTL_SYSINFO: + case SNDCTL_CARDINFO: + case SNDCTL_MIXERINFO: + case SNDCTL_MIDIINFO: + case SNDCTL_AUDIOINFO: + case SNDCTL_AUDIOINFO_EX: + case SNDCTL_ENGINEINFO: + case OSSCTL_RENUM_AUDIODEVS: + case OSSCTL_RENUM_MIXERDEVS: + case OSSCTL_RENUM_MIDIDEVS: + case SNDCTL_MIX_EXTINFO: + case SNDCTL_MIX_ENUMINFO: + case SNDCTL_MIX_READ: + case SNDCTL_MIX_WRITE: + case SNDCTL_MIX_NRMIX: + case SNDCTL_MIX_MATRIX_WRITE: + case SNDCTL_MIX_MATRIX_READ: + case VMIXCTL_ATTACH: + case VMIXCTL_DETACH: + case VMIXCTL_RATE: + return oss_mixer_ext (dev, OSS_DEV_MIXER, cmd, arg); + break; + } + + if (dev < 0 || dev >= num_mixers) + { + return OSS_ENXIO; + } + + if (mixer_devs == NULL) + { + return OSS_ENXIO; + } + + if (!mixer_devs[dev]->enabled || mixer_devs[dev]->unloaded) + { + return OSS_ENODEV; + } + + if ((ret = oss_legacy_mixer_ioctl (dev, -1, cmd, arg)) != OSS_EINVAL) + { + return ret; + } + + return oss_mixer_ext (dev, OSS_DEV_MIXER, cmd, arg); +} + +#ifdef DO_TIMINGS + +static char timing_buf[256 * 1024] = { 0 }, *timing_ptr = timing_buf; +static int timing_prev_time = 0; +int timing_flags = 0x7fffffff; + +#define TM_SCALE 256 +static oss_timing_timer_func tmfunc = NULL; +static void *tmfunc_arg = NULL; + +void +timing_install_timer (oss_timing_timer_func f, void *x) +{ + tmfunc = f; + tmfunc_arg = x; +} + +static void +oss_do_timing_ (char *txt) +{ + int l = strlen (txt) + 20; + oss_native_word flags; + oss_native_word this_time; + + if (!timing_is_active) /* Nobody is listening */ + return; + + MUTEX_ENTER_IRQDISABLE (oss_timing_mutex, flags); + if ((long) (&timing_buf[sizeof (timing_buf)] - timing_ptr - 8) <= l) + { + MUTEX_EXIT_IRQRESTORE (oss_timing_mutex, flags); + return; + } + + if (tmfunc != NULL) + { + this_time = tmfunc (tmfunc_arg); + } + else + { + this_time = 0; /* TODO: Get the actual audio pointer */ + + if (this_time == 0) + this_time = GET_JIFFIES (); + } + + sprintf (timing_ptr, "%ld/%d: %s\n", this_time, + this_time - timing_prev_time, txt); + l = strlen (timing_ptr); + timing_ptr += l; + timing_prev_time = this_time; + MUTEX_EXIT_IRQRESTORE (oss_timing_mutex, flags); +} + +typedef struct +{ + oss_native_word sum; + oss_native_word open_time; +} +timing_entry; + +static timing_entry timing_bins[DF_NRBINS]; +static oss_native_word timing_start = 0; + +static char *bin_names[DF_NRBINS] = { + "Iddle", + "Write", + "Read", + "Interrupt", + "Write sleep", + "Read sleep", + "Write SRC", + "Read SRC" +}; + +void +timing_open (void) +{ + int i; + + if (tmfunc == NULL) + return; + timing_start = tmfunc (tmfunc_arg) / TM_SCALE; + + for (i = 0; i < DF_NRBINS; i++) + { + timing_bins[i].sum = 0; + timing_bins[i].open_time = 0xffffffff; + } + +} + +void +timing_close (void) +{ + char tmp[64]; + int i; + oss_native_word t, sum, pc; + if (tmfunc == NULL) + return; + + t = tmfunc (tmfunc_arg) / TM_SCALE - timing_start; + + sprintf (tmp, "Timing close, elapsed=%d", t); + oss_do_timing2 (DFLAG_PROFILE, tmp); + + sum = 0; + + for (i = 0; i < DF_NRBINS; i++) + { + sum += timing_bins[i].sum; + pc = (timing_bins[i].sum * 1000) / t; + sprintf (tmp, "Bin %s: %d/%d (%d.%d%%)", bin_names[i], + timing_bins[i].sum, pc, pc / 10, pc % 10); + oss_do_timing2 (DFLAG_PROFILE, tmp); + } + + /* sum = sum-timing_bins[DF_SLEEPWRITE].sum-timing_bins[DF_SLEEPREAD].sum; */ + pc = (sum * 10000) / t; + + sprintf (tmp, "OSS Total: %d (%d.%d%%)", sum, pc / 100, pc % 100); + oss_do_timing2 (DFLAG_PROFILE, tmp); +} + +void +oss_timing_enter (int bin) +{ + if (tmfunc == NULL) + return; + + timing_bins[bin].open_time = tmfunc (tmfunc_arg) / TM_SCALE; +} + +void +oss_timing_leave (int bin) +{ + oss_native_word t; + + if (tmfunc == NULL) + return; + + if (timing_bins[bin].open_time >= 0xfffffffe) + return; + + t = tmfunc (tmfunc_arg) / TM_SCALE - timing_bins[bin].open_time; + timing_bins[bin].sum += t; + timing_bins[bin].open_time = 0xfffffffe; +} + +void +oss_do_timing (char *txt) +{ + if (!timing_is_active) /* Nobody is listening */ + return; + + if (timing_flags & DFLAG_ALL) + oss_do_timing_ (txt); +} + +void +oss_do_timing2 (int mask, char *txt) +{ + if (!timing_is_active) /* Nobody is listening */ + return; + + if ((timing_flags & DFLAG_ALL) || (timing_flags & mask)) + oss_do_timing_ (txt); +} + +void +oss_timing_printf (char *s, ...) +{ + char tmp[1024], *a[6]; + va_list ap; + int i, n = 0; + + if (!timing_is_active) /* Nobody is listening */ + return; + + va_start (ap, s); + + for (i = 0; i < strlen (s); i++) + if (s[i] == '%') + n++; + + for (i = 0; i < n && i < 6; i++) + a[i] = va_arg (ap, char *); + + for (i = n; i < 6; i++) + a[i] = NULL; + + sprintf (tmp, s, a[0], a[1], a[2], a[3], a[4], a[5], NULL, + NULL, NULL, NULL); + oss_do_timing(tmp); + + va_end (ap); +} + +static int +timing_read (int dev, struct fileinfo *file, uio_t * buf, int count) +{ + /* + * Return at most 'count' bytes from the status_buf. + */ + int l; + oss_native_word flags; + + timing_is_active = 1; + + MUTEX_ENTER_IRQDISABLE (oss_timing_mutex, flags); + + l = timing_ptr - timing_buf; + if (l <= 0) + { + MUTEX_EXIT_IRQRESTORE (oss_timing_mutex, flags); + return 0; + } + + if (l > count) + l = count; + + timing_ptr = timing_buf; + + MUTEX_EXIT_IRQRESTORE (oss_timing_mutex, flags); + + if (uiomove (timing_buf, l, UIO_READ, buf) != 0) + cmn_err (CE_WARN, "audio: uiomove(UIO_READ) failed\n"); + + return l; +} +#else +/* + * Dummy wrappers + */ + +/*ARGSUSED*/ +void +oss_timing_enter (int bin) +{ +} + +/*ARGSUSED*/ +void +oss_timing_leave (int bin) +{ +} + +/*ARGSUSED*/ +void +oss_do_timing (char *txt) +{ +} + +/*ARGSUSED*/ +void +oss_do_timing2 (int mask, char *txt) +{ +} + +/*ARGSUSED*/ +void +oss_timing_printf (char *s, ...) +{ +} +#endif + +static oss_cdev_drv_t mixer_cdev_drv = { + oss_mixer_open, + oss_mixer_release, +#ifdef DO_TIMINGS + timing_read, +#else + NULL, /* read */ +#endif + NULL, /* write */ + oss_mixer_ioctl +}; + +int +oss_install_mixer (int vers, + oss_device_t * osdev, + oss_device_t * master_osdev, + const char *name, + mixer_driver_t * driver, int driver_size, void *devc) +{ + mixer_operations_t *op = NULL; + mixer_driver_t *d; + + int i, num; + char handle[32]; + + if (master_osdev == NULL) + master_osdev = osdev; + + if (mixer_devs == NULL) + { + mixer_devs = PMALLOC (osdev, sizeof (mixdev_p) * MAX_MIXER_DEV); + memset (mixer_devs, 0, sizeof (mixdev_p) * MAX_MIXER_DEV); + mixer_devs_p = mixer_devs; + } + + if (num_mixers >= MAX_MIXER_DEV - 1) + { + static int nnn = 0; + cmn_err (CE_WARN, "Too many mixer devices %d/%d (%s)\n", + num_mixers, MAX_MIXER_DEV, name); + /* + * In some special situations a driver may keep trying to install a mixer + * in infinite loop if the request fails. Stop this by panicking after + * this has continued for more than 50 times. In this case we can get an + * error message instead of having the system to lock up foreever. + */ + if (nnn++ > 50) + cmn_err (CE_PANIC, "Killing runaway system.\n"); + return OSS_EIO; + } + + if (vers != OSS_MIXER_DRIVER_VERSION) + { + cmn_err (CE_WARN, "Incompatible mixer driver for %s\n", name); + return OSS_EIO; + } + + if (driver_size > sizeof (mixer_driver_t)) + driver_size = sizeof (mixer_driver_t); + +/* + * Check if this device was earlier unloaded and now returning back. + */ + num = -1; + for (i = 0; i < num_mixers; i++) + { + if (mixer_devs[i]->unloaded + && mixer_devs[i]->os_id == oss_get_osid (osdev)) + { + op = mixer_devs[i]; + num = i; + break; + } + } + + if ((d = PMALLOC (osdev, sizeof (*d))) == NULL) + { + cmn_err (CE_WARN, "Can't allocate mixer driver for (%s)\n", name); + return OSS_ENOSPC; + } + + if (num == -1) + { + op = PMALLOC (osdev, sizeof (mixer_operations_t)); + if (op == NULL) + { + cmn_err (CE_WARN, "Can't allocate mixer driver for (%s)\n", name); + return OSS_ENOSPC; + } + + memset ((char *) op, 0, sizeof (mixer_operations_t)); + num = num_mixers++; + sprintf (handle, "%s-mx%02d", osdev->handle, osdev->num_mixerdevs+1); + op->port_number = osdev->num_mixerdevs++; + } + else + { + strcpy (handle, op->handle); /* Preserve the previous handle */ + } + + memset ((char *) d, 0, sizeof (mixer_driver_t)); + memcpy ((char *) d, (char *) driver, driver_size); + strcpy (op->handle, handle); + strcpy (op->id, osdev->nick); + op->d = d; + + strncpy (op->name, name, sizeof (op->name)); + op->name[sizeof (op->name) - 1] = 0; + op->devc = devc; + op->osdev = osdev; + op->os_id = oss_get_osid (osdev); + op->master_osdev = master_osdev; + op->hw_devc = NULL; + op->max_ext = op->nr_ext = 0; + op->names_checked = 0; + op->extensions = NULL; + op->timestamp = GET_JIFFIES (); + op->ignore_mask = 0; + op->card_number = osdev->cardnum; + op->enabled = 1; + op->unloaded = 0; + op->flags = 0; + op->caps = 0; + op->priority = 0; /* Normal (low) priority */ + op->real_dev = num; + + if (osdev->first_mixer == -1) /* Not defined yet */ + osdev->first_mixer = num; + + mixer_devs[num] = op; +/* + * Create the device node + */ + + { + oss_devnode_t name; + +#ifdef NEW_DEVICE_NAMING +# ifdef USE_DEVICE_SUBDIRS + sprintf (name, "oss/%s/mix%d", osdev->nick, osdev->num_mixerdevs - 1); +# else + sprintf (name, "%s_mix%d", osdev->nick, osdev->num_mixerdevs - 1); +# endif +#else + sprintf (name, "mixer%d", num); +#endif + oss_install_chrdev (osdev, name, OSS_DEV_MIXER, num, &mixer_cdev_drv, 0); + sprintf (op->devnode, "/dev/%s", name); + +#if 0 + /* + * Moved to install_dev_mixer() + */ + if (num == 0) + { + oss_install_chrdev (osdev, "mixer", OSS_DEV_MIXER, num, + &mixer_cdev_drv, 0); + } +#endif + } + + return num; +} + +void +install_dev_mixer (oss_device_t * osdev) +{ +/* + * Install the default mixer node if necessary + */ + oss_install_chrdev (osdev, "mixer", OSS_DEV_MIXER, 0, &mixer_cdev_drv, 0); +} |