diff options
Diffstat (limited to 'sys/dev/sound/pcm')
-rw-r--r-- | sys/dev/sound/pcm/ac97.c | 671 | ||||
-rw-r--r-- | sys/dev/sound/pcm/ac97.h | 103 | ||||
-rw-r--r-- | sys/dev/sound/pcm/ac97_if.m | 60 | ||||
-rw-r--r-- | sys/dev/sound/pcm/buffer.c | 665 | ||||
-rw-r--r-- | sys/dev/sound/pcm/buffer.h | 96 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel.c | 1214 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel.h | 159 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel_if.m | 141 | ||||
-rw-r--r-- | sys/dev/sound/pcm/dsp.c | 1147 | ||||
-rw-r--r-- | sys/dev/sound/pcm/dsp.h | 32 | ||||
-rw-r--r-- | sys/dev/sound/pcm/fake.c | 137 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder.c | 421 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder.h | 83 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder_fmt.c | 543 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder_if.m | 87 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder_rate.c | 192 | ||||
-rw-r--r-- | sys/dev/sound/pcm/mixer.c | 519 | ||||
-rw-r--r-- | sys/dev/sound/pcm/mixer.h | 50 | ||||
-rw-r--r-- | sys/dev/sound/pcm/mixer_if.m | 66 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sndstat.c | 400 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.c | 907 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.h | 289 | ||||
-rw-r--r-- | sys/dev/sound/pcm/vchan.c | 344 | ||||
-rw-r--r-- | sys/dev/sound/pcm/vchan.h | 33 |
24 files changed, 8359 insertions, 0 deletions
diff --git a/sys/dev/sound/pcm/ac97.c b/sys/dev/sound/pcm/ac97.c new file mode 100644 index 0000000..427fb98 --- /dev/null +++ b/sys/dev/sound/pcm/ac97.c @@ -0,0 +1,671 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + */ + +#include <dev/sound/pcm/sound.h> +#include <dev/sound/pcm/ac97.h> + +#include "mixer_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +MALLOC_DEFINE(M_AC97, "ac97", "ac97 codec"); + +struct ac97mixtable_entry { + int reg:8; + unsigned bits:4; + unsigned ofs:4; + unsigned stereo:1; + unsigned mute:1; + unsigned recidx:4; + unsigned mask:1; + unsigned enable:1; +}; + +#define AC97_NAMELEN 16 +struct ac97_info { + kobj_t methods; + device_t dev; + void *devinfo; + char *id; + char rev; + unsigned count, caps, se, extcaps, extid, extstat, noext:1; + u_int32_t flags; + struct ac97mixtable_entry mix[32]; + char name[AC97_NAMELEN]; + struct mtx *lock; +}; + +struct ac97_codecid { + u_int32_t id, noext:1; + char *name; +}; + +static const struct ac97mixtable_entry ac97mixtable_default[32] = { + [SOUND_MIXER_VOLUME] = { AC97_MIX_MASTER, 5, 0, 1, 1, 6, 0, 1 }, + [SOUND_MIXER_MONITOR] = { AC97_MIX_AUXOUT, 5, 0, 1, 1, 0, 0, 0 }, + [SOUND_MIXER_PHONEOUT] = { AC97_MIX_MONO, 5, 0, 0, 1, 7, 0, 0 }, + [SOUND_MIXER_BASS] = { AC97_MIX_TONE, 4, 8, 0, 0, 0, 1, 0 }, + [SOUND_MIXER_TREBLE] = { AC97_MIX_TONE, 4, 0, 0, 0, 0, 1, 0 }, + [SOUND_MIXER_PCM] = { AC97_MIX_PCM, 5, 0, 1, 1, 0, 0, 1 }, + [SOUND_MIXER_SPEAKER] = { AC97_MIX_BEEP, 4, 1, 0, 1, 0, 0, 0 }, + [SOUND_MIXER_LINE] = { AC97_MIX_LINE, 5, 0, 1, 1, 5, 0, 1 }, + [SOUND_MIXER_PHONEIN] = { AC97_MIX_PHONE, 5, 0, 0, 1, 8, 0, 0 }, + [SOUND_MIXER_MIC] = { AC97_MIX_MIC, 5, 0, 0, 1, 1, 0, 1 }, + [SOUND_MIXER_CD] = { AC97_MIX_CD, 5, 0, 1, 1, 2, 0, 1 }, + [SOUND_MIXER_LINE1] = { AC97_MIX_AUX, 5, 0, 1, 1, 4, 0, 0 }, + [SOUND_MIXER_VIDEO] = { AC97_MIX_VIDEO, 5, 0, 1, 1, 3, 0, 0 }, + [SOUND_MIXER_RECLEV] = { -AC97_MIX_RGAIN, 4, 0, 1, 1, 0, 0, 1 } +}; + +static struct ac97_codecid ac97codecid[] = { + { 0x41445303, 0, "Analog Devices AD1819" }, + { 0x41445340, 0, "Analog Devices AD1881" }, + { 0x41445348, 0, "Analog Devices AD1881A" }, + { 0x41445360, 0, "Analog Devices AD1885" }, + { 0x414b4d00, 1, "Asahi Kasei AK4540" }, + { 0x414b4d01, 1, "Asahi Kasei AK4542" }, + { 0x414b4d02, 1, "Asahi Kasei AK4543" }, + { 0x414c4710, 0, "Avance Logic ALC200/200P" }, + { 0x43525900, 0, "Cirrus Logic CS4297" }, + { 0x43525903, 0, "Cirrus Logic CS4297" }, + { 0x43525913, 0, "Cirrus Logic CS4297A" }, + { 0x43525914, 0, "Cirrus Logic CS4297B" }, + { 0x43525923, 0, "Cirrus Logic CS4294C" }, + { 0x4352592b, 0, "Cirrus Logic CS4298C" }, + { 0x43525931, 0, "Cirrus Logic CS4299A" }, + { 0x43525933, 0, "Cirrus Logic CS4299C" }, + { 0x43525934, 0, "Cirrus Logic CS4299D" }, + { 0x43525941, 0, "Cirrus Logic CS4201A" }, + { 0x43525951, 0, "Cirrus Logic CS4205A" }, + { 0x43525961, 0, "Cirrus Logic CS4291A" }, + { 0x45838308, 0, "ESS Technology ES1921" }, + { 0x49434511, 0, "ICEnsemble ICE1232" }, + { 0x4e534331, 0, "National Semiconductor LM4549" }, + { 0x83847600, 0, "SigmaTel STAC9700/9783/9784" }, + { 0x83847604, 0, "SigmaTel STAC9701/9703/9704/9705" }, + { 0x83847605, 0, "SigmaTel STAC9704" }, + { 0x83847608, 0, "SigmaTel STAC9708/9711" }, + { 0x83847609, 0, "SigmaTel STAC9721/9723" }, + { 0x83847644, 0, "SigmaTel STAC9744" }, + { 0x83847656, 0, "SigmaTel STAC9756/9757" }, + { 0x53494c22, 0, "Silicon Laboratory Si3036" }, + { 0x53494c23, 0, "Silicon Laboratory Si3038" }, + { 0x54524103, 0, "TriTech TR?????" }, + { 0x54524106, 0, "TriTech TR28026" }, + { 0x54524108, 0, "TriTech TR28028" }, + { 0x54524123, 0, "TriTech TR28602" }, + { 0x574d4c00, 0, "Wolfson WM9701A" }, + { 0x574d4c03, 0, "Wolfson WM9703/9704" }, + { 0x574d4c04, 0, "Wolfson WM9704 (quad)" }, + { 0, 0, NULL } +}; + +static char *ac97enhancement[] = { + "no 3D Stereo Enhancement", + "Analog Devices Phat Stereo", + "Creative Stereo Enhancement", + "National Semi 3D Stereo Enhancement", + "Yamaha Ymersion", + "BBE 3D Stereo Enhancement", + "Crystal Semi 3D Stereo Enhancement", + "Qsound QXpander", + "Spatializer 3D Stereo Enhancement", + "SRS 3D Stereo Enhancement", + "Platform Tech 3D Stereo Enhancement", + "AKM 3D Audio", + "Aureal Stereo Enhancement", + "Aztech 3D Enhancement", + "Binaura 3D Audio Enhancement", + "ESS Technology Stereo Enhancement", + "Harman International VMAx", + "Nvidea 3D Stereo Enhancement", + "Philips Incredible Sound", + "Texas Instruments 3D Stereo Enhancement", + "VLSI Technology 3D Stereo Enhancement", + "TriTech 3D Stereo Enhancement", + "Realtek 3D Stereo Enhancement", + "Samsung 3D Stereo Enhancement", + "Wolfson Microelectronics 3D Enhancement", + "Delta Integration 3D Enhancement", + "SigmaTel 3D Enhancement", + "Reserved 27", + "Rockwell 3D Stereo Enhancement", + "Reserved 29", + "Reserved 30", + "Reserved 31" +}; + +static char *ac97feature[] = { + "mic channel", + "reserved", + "tone", + "simulated stereo", + "headphone", + "bass boost", + "18 bit DAC", + "20 bit DAC", + "18 bit ADC", + "20 bit ADC" +}; + +static char *ac97extfeature[] = { + "variable rate PCM", + "double rate PCM", + "reserved 1", + "variable rate mic", + "reserved 2", + "reserved 3", + "center DAC", + "surround DAC", + "LFE DAC", + "AMAP", + "reserved 4", + "reserved 5", + "reserved 6", + "reserved 7", +}; + +static u_int16_t +rdcd(struct ac97_info *codec, int reg) +{ + return AC97_READ(codec->methods, codec->devinfo, reg); +} + +static void +wrcd(struct ac97_info *codec, int reg, u_int16_t val) +{ + AC97_WRITE(codec->methods, codec->devinfo, reg, val); +} + +static void +ac97_reset(struct ac97_info *codec) +{ + u_int32_t i, ps; + wrcd(codec, AC97_REG_RESET, 0); + for (i = 0; i < 500; i++) { + ps = rdcd(codec, AC97_REG_POWER) & AC97_POWER_STATUS; + if (ps == AC97_POWER_STATUS) + return; + DELAY(1000); + } + device_printf(codec->dev, "AC97 reset timed out."); +} + +int +ac97_setrate(struct ac97_info *codec, int which, int rate) +{ + u_int16_t v; + + switch(which) { + case AC97_REGEXT_FDACRATE: + case AC97_REGEXT_SDACRATE: + case AC97_REGEXT_LDACRATE: + case AC97_REGEXT_LADCRATE: + case AC97_REGEXT_MADCRATE: + break; + + default: + return -1; + } + + snd_mtxlock(codec->lock); + if (rate != 0) { + v = rate; + if (codec->extstat & AC97_EXTCAP_DRA) + v >>= 1; + wrcd(codec, which, v); + } + v = rdcd(codec, which); + if (codec->extstat & AC97_EXTCAP_DRA) + v <<= 1; + snd_mtxunlock(codec->lock); + return v; +} + +int +ac97_setextmode(struct ac97_info *codec, u_int16_t mode) +{ + mode &= AC97_EXTCAPS; + if ((mode & ~codec->extcaps) != 0) { + device_printf(codec->dev, "ac97 invalid mode set 0x%04x\n", + mode); + return -1; + } + snd_mtxlock(codec->lock); + wrcd(codec, AC97_REGEXT_STAT, mode); + codec->extstat = rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS; + snd_mtxunlock(codec->lock); + return (mode == codec->extstat)? 0 : -1; +} + +u_int16_t +ac97_getextmode(struct ac97_info *codec) +{ + return codec->extstat; +} + +u_int16_t +ac97_getextcaps(struct ac97_info *codec) +{ + return codec->extcaps; +} + +u_int16_t +ac97_getcaps(struct ac97_info *codec) +{ + return codec->caps; +} + +static int +ac97_setrecsrc(struct ac97_info *codec, int channel) +{ + struct ac97mixtable_entry *e = &codec->mix[channel]; + + if (e->recidx > 0) { + int val = e->recidx - 1; + val |= val << 8; + snd_mtxlock(codec->lock); + wrcd(codec, AC97_REG_RECSEL, val); + snd_mtxunlock(codec->lock); + return 0; + } else + return -1; +} + +static int +ac97_setmixer(struct ac97_info *codec, unsigned channel, unsigned left, unsigned right) +{ + struct ac97mixtable_entry *e = &codec->mix[channel]; + + if (e->reg && e->enable && e->bits) { + int max, val, reg = (e->reg >= 0)? e->reg : -e->reg; + + if (!e->stereo) + right = left; + if (e->reg > 0) { + left = 100 - left; + right = 100 - right; + } + + max = (1 << e->bits) - 1; + left = (left * max) / 100; + right = (right * max) / 100; + + val = (left << 8) | right; + + left = (left * 100) / max; + right = (right * 100) / max; + + if (e->reg > 0) { + left = 100 - left; + right = 100 - right; + } + + if (!e->stereo) { + val &= max; + val <<= e->ofs; + if (e->mask) { + int cur = rdcd(codec, e->reg); + val |= cur & ~(max << e->ofs); + } + } + if (left == 0 && right == 0 && e->mute == 1) + val = AC97_MUTE; + snd_mtxlock(codec->lock); + wrcd(codec, reg, val); + snd_mtxunlock(codec->lock); + return left | (right << 8); + } else { + /* printf("ac97_setmixer: reg=%d, bits=%d, enable=%d\n", e->reg, e->bits, e->enable); */ + return -1; + } +} + +#if 0 +static int +ac97_getmixer(struct ac97_info *codec, int channel) +{ + struct ac97mixtable_entry *e = &codec->mix[channel]; + if (channel < SOUND_MIXER_NRDEVICES && e->reg != 0) { + int max, val, volume; + + max = (1 << e->bits) - 1; + val = rdcd(code, e->reg); + if (val == AC97_MUTE && e->mute == 1) + volume = 0; + else { + if (e->stereo == 0) val >>= e->ofs; + val &= max; + volume = (val * 100) / max; + if (e->reg > 0) volume = 100 - volume; + } + return volume; + } else + return -1; +} +#endif + +static void +ac97_fix_auxout(struct ac97_info *codec) +{ + /* Determine what AUXOUT really means, it can be: + * + * 1. Headphone out. + * 2. 4-Channel Out + * 3. True line level out (effectively master volume). + * + * See Sections 5.2.1 and 5.27 for AUX_OUT Options in AC97r2.{2,3}. + */ + if (codec->caps & AC97_CAP_HEADPHONE) { + /* XXX We should probably check the AUX_OUT initial value. + * Leave AC97_MIX_AUXOUT - SOUND_MIXER_MONITOR relationship */ + return; + } else if (codec->extcaps & AC97_EXTCAP_SDAC && + rdcd(codec, AC97_MIXEXT_SURROUND) == 0x8080) { + /* 4-Channel Out, add an additional gain setting. */ + codec->mix[SOUND_MIXER_OGAIN] = codec->mix[SOUND_MIXER_MONITOR]; + } else { + /* Master volume is/maybe fixed in h/w, not sufficiently + * clear in spec to blat SOUND_MIXER_MASTER. */ + codec->mix[SOUND_MIXER_OGAIN] = codec->mix[SOUND_MIXER_MONITOR]; + } + /* Blat monitor, inappropriate label if we get here */ + bzero(&codec->mix[SOUND_MIXER_MONITOR], + sizeof(codec->mix[SOUND_MIXER_MONITOR])); +} + +static unsigned +ac97_initmixer(struct ac97_info *codec) +{ + unsigned i, j, k, old; + u_int32_t id; + + snd_mtxlock(codec->lock); + codec->count = AC97_INIT(codec->methods, codec->devinfo); + if (codec->count == 0) { + device_printf(codec->dev, "ac97 codec init failed\n"); + snd_mtxunlock(codec->lock); + return ENODEV; + } + + wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000); + ac97_reset(codec); + wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000); + + i = rdcd(codec, AC97_REG_RESET); + codec->caps = i & 0x03ff; + codec->se = (i & 0x7c00) >> 10; + + id = (rdcd(codec, AC97_REG_ID1) << 16) | rdcd(codec, AC97_REG_ID2); + codec->rev = id & 0x000000ff; + if (id == 0 || id == 0xffffffff) { + device_printf(codec->dev, "ac97 codec invalid or not present (id == %x)\n", id); + snd_mtxunlock(codec->lock); + return ENODEV; + } + + codec->noext = 0; + codec->id = NULL; + for (i = 0; ac97codecid[i].id; i++) { + if (ac97codecid[i].id == id) { + codec->id = ac97codecid[i].name; + codec->noext = ac97codecid[i].noext; + } + } + + codec->extcaps = 0; + codec->extid = 0; + codec->extstat = 0; + if (!codec->noext) { + i = rdcd(codec, AC97_REGEXT_ID); + if (i != 0xffff) { + codec->extcaps = i & 0x3fff; + codec->extid = (i & 0xc000) >> 14; + codec->extstat = rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS; + } + } + + for (i = 0; i < 32; i++) { + codec->mix[i] = ac97mixtable_default[i]; + } + ac97_fix_auxout(codec); + + for (i = 0; i < 32; i++) { + k = codec->noext? codec->mix[i].enable : 1; + if (k && (codec->mix[i].reg > 0)) { + old = rdcd(codec, codec->mix[i].reg); + wrcd(codec, codec->mix[i].reg, 0x3f); + j = rdcd(codec, codec->mix[i].reg); + wrcd(codec, codec->mix[i].reg, old); + codec->mix[i].enable = (j != 0 && j != old)? 1 : 0; + for (k = 1; j & (1 << k); k++); + codec->mix[i].bits = j? k - codec->mix[i].ofs : 0; + } + /* printf("mixch %d, en=%d, b=%d\n", i, codec->mix[i].enable, codec->mix[i].bits); */ + } + + if (bootverbose) { + device_printf(codec->dev, "ac97 codec id 0x%08x", id); + if (codec->id) + printf(" (%s)", codec->id); + printf("\n"); + device_printf(codec->dev, "ac97 codec features "); + for (i = j = 0; i < 10; i++) + if (codec->caps & (1 << i)) + printf("%s%s", j++? ", " : "", ac97feature[i]); + printf("%s%d bit master volume", j++? ", " : "", codec->mix[SOUND_MIXER_VOLUME].bits); + printf("%s%s\n", j? ", " : "", ac97enhancement[codec->se]); + + if (codec->extcaps != 0 || codec->extid) { + device_printf(codec->dev, "ac97 %s codec", + codec->extid? "secondary" : "primary"); + if (codec->extcaps) + printf(" extended features "); + for (i = j = 0; i < 14; i++) + if (codec->extcaps & (1 << i)) + printf("%s%s", j++? ", " : "", ac97extfeature[i]); + printf("\n"); + } + } + + if ((rdcd(codec, AC97_REG_POWER) & 2) == 0) + device_printf(codec->dev, "ac97 codec reports dac not ready\n"); + snd_mtxunlock(codec->lock); + return 0; +} + +static unsigned +ac97_reinitmixer(struct ac97_info *codec) +{ + snd_mtxlock(codec->lock); + codec->count = AC97_INIT(codec->methods, codec->devinfo); + if (codec->count == 0) { + device_printf(codec->dev, "ac97 codec init failed\n"); + snd_mtxunlock(codec->lock); + return ENODEV; + } + + wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000); + ac97_reset(codec); + wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000); + + if (!codec->noext) { + wrcd(codec, AC97_REGEXT_STAT, codec->extstat); + if ((rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS) + != codec->extstat) + device_printf(codec->dev, "ac97 codec failed to reset extended mode (%x, got %x)\n", + codec->extstat, + rdcd(codec, AC97_REGEXT_STAT) & + AC97_EXTCAPS); + } + + if ((rdcd(codec, AC97_REG_POWER) & 2) == 0) + device_printf(codec->dev, "ac97 codec reports dac not ready\n"); + snd_mtxunlock(codec->lock); + return 0; +} + +struct ac97_info * +ac97_create(device_t dev, void *devinfo, kobj_class_t cls) +{ + struct ac97_info *codec; + + codec = (struct ac97_info *)malloc(sizeof *codec, M_AC97, M_NOWAIT); + if (codec == NULL) + return NULL; + + snprintf(codec->name, AC97_NAMELEN, "%s:ac97", device_get_nameunit(dev)); + codec->lock = snd_mtxcreate(codec->name, "ac97 codec"); + codec->methods = kobj_create(cls, M_AC97, M_WAITOK); + if (codec->methods == NULL) { + snd_mtxlock(codec->lock); + snd_mtxfree(codec->lock); + free(codec, M_AC97); + return NULL; + } + + codec->dev = dev; + codec->devinfo = devinfo; + codec->flags = 0; + return codec; +} + +void +ac97_destroy(struct ac97_info *codec) +{ + snd_mtxlock(codec->lock); + if (codec->methods != NULL) + kobj_delete(codec->methods, M_AC97); + snd_mtxfree(codec->lock); + free(codec, M_AC97); +} + +void +ac97_setflags(struct ac97_info *codec, u_int32_t val) +{ + codec->flags = val; +} + +u_int32_t +ac97_getflags(struct ac97_info *codec) +{ + return codec->flags; +} + +/* -------------------------------------------------------------------- */ + +static int +ac97mix_init(struct snd_mixer *m) +{ + struct ac97_info *codec = mix_getdevinfo(m); + u_int32_t i, mask; + + if (codec == NULL) + return -1; + + if (ac97_initmixer(codec)) + return -1; + + mask = 0; + for (i = 0; i < 32; i++) + mask |= codec->mix[i].enable? 1 << i : 0; + mix_setdevs(m, mask); + + mask = 0; + for (i = 0; i < 32; i++) + mask |= codec->mix[i].recidx? 1 << i : 0; + mix_setrecdevs(m, mask); + return 0; +} + +static int +ac97mix_uninit(struct snd_mixer *m) +{ + struct ac97_info *codec = mix_getdevinfo(m); + + if (codec == NULL) + return -1; + /* + if (ac97_uninitmixer(codec)) + return -1; + */ + ac97_destroy(codec); + return 0; +} + +static int +ac97mix_reinit(struct snd_mixer *m) +{ + struct ac97_info *codec = mix_getdevinfo(m); + + if (codec == NULL) + return -1; + return ac97_reinitmixer(codec); +} + +static int +ac97mix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct ac97_info *codec = mix_getdevinfo(m); + + if (codec == NULL) + return -1; + return ac97_setmixer(codec, dev, left, right); +} + +static int +ac97mix_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + int i; + struct ac97_info *codec = mix_getdevinfo(m); + + if (codec == NULL) + return -1; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if ((src & (1 << i)) != 0) + break; + return (ac97_setrecsrc(codec, i) == 0)? 1 << i : -1; +} + +static kobj_method_t ac97mixer_methods[] = { + KOBJMETHOD(mixer_init, ac97mix_init), + KOBJMETHOD(mixer_uninit, ac97mix_uninit), + KOBJMETHOD(mixer_reinit, ac97mix_reinit), + KOBJMETHOD(mixer_set, ac97mix_set), + KOBJMETHOD(mixer_setrecsrc, ac97mix_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(ac97mixer); + +/* -------------------------------------------------------------------- */ + +kobj_class_t +ac97_getmixerclass(void) +{ + return &ac97mixer_class; +} + + diff --git a/sys/dev/sound/pcm/ac97.h b/sys/dev/sound/pcm/ac97.h new file mode 100644 index 0000000..9c18121 --- /dev/null +++ b/sys/dev/sound/pcm/ac97.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + * + * $FreeBSD$ + */ + +#define AC97_MUTE 0x8000 + +#define AC97_REG_RESET 0x00 +#define AC97_CAP_MICCHANNEL (1 << 0) +#define AC97_CAP_TONE (1 << 2) +#define AC97_CAP_SIMSTEREO (1 << 3) +#define AC97_CAP_HEADPHONE (1 << 4) +#define AC97_CAP_LOUDNESS (1 << 5) +#define AC97_CAP_DAC_18 (1 << 6) +#define AC97_CAP_DAC_20 (1 << 7) +#define AC97_CAP_ADC_18 (1 << 8) +#define AC97_CAP_ADC_20 (1 << 9) +#define AC97_MIX_MASTER 0x02 +#define AC97_MIX_AUXOUT 0x04 +#define AC97_MIX_MONO 0x06 +#define AC97_MIX_TONE 0x08 +#define AC97_MIX_BEEP 0x0a +#define AC97_MIX_PHONE 0x0c +#define AC97_MIX_MIC 0x0e +#define AC97_MIX_LINE 0x10 +#define AC97_MIX_CD 0x12 +#define AC97_MIX_VIDEO 0x14 +#define AC97_MIX_AUX 0x16 +#define AC97_MIX_PCM 0x18 +#define AC97_REG_RECSEL 0x1a +#define AC97_MIX_RGAIN 0x1c +#define AC97_MIX_MGAIN 0x1e +#define AC97_REG_GEN 0x20 +#define AC97_REG_3D 0x22 +#define AC97_REG_POWER 0x26 +#define AC97_POWER_ADC (1 << 0) +#define AC97_POWER_DAC (1 << 1) +#define AC97_POWER_ANL (1 << 2) +#define AC97_POWER_REF (1 << 3) +#define AC97_POWER_STATUS (AC97_POWER_ADC | AC97_POWER_DAC | \ + AC97_POWER_REF | AC97_POWER_ANL ) +#define AC97_REGEXT_ID 0x28 +#define AC97_EXTCAP_VRA (1 << 0) +#define AC97_EXTCAP_DRA (1 << 1) +#define AC97_EXTCAP_VRM (1 << 3) +#define AC97_EXTCAPS (AC97_EXTCAP_VRA | AC97_EXTCAP_DRA | AC97_EXTCAP_VRM) +#define AC97_EXTCAP_SDAC (1 << 7) + +#define AC97_REGEXT_STAT 0x2a +#define AC97_REGEXT_FDACRATE 0x2c +#define AC97_REGEXT_SDACRATE 0x2e +#define AC97_REGEXT_LDACRATE 0x30 +#define AC97_REGEXT_LADCRATE 0x32 +#define AC97_REGEXT_MADCRATE 0x34 +#define AC97_MIXEXT_CLFE 0x36 +#define AC97_MIXEXT_SURROUND 0x38 +#define AC97_REG_ID1 0x7c +#define AC97_REG_ID2 0x7e + +#define AC97_F_EAPD_INV 0x00000001 + +#define AC97_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, sizeof(struct kobj)) +#define AC97_CREATE(dev, devinfo, cls) ac97_create(dev, devinfo, &cls ## _class) + +struct ac97_info; + +#include "ac97_if.h" + +extern kobj_class_t ac97_getmixerclass(void); + +struct ac97_info *ac97_create(device_t dev, void *devinfo, kobj_class_t cls); +void ac97_destroy(struct ac97_info *codec); +void ac97_setflags(struct ac97_info *codec, u_int32_t val); +u_int32_t ac97_getflags(struct ac97_info *codec); +int ac97_setrate(struct ac97_info *codec, int which, int rate); +int ac97_setextmode(struct ac97_info *codec, u_int16_t mode); +u_int16_t ac97_getextmode(struct ac97_info *codec); +u_int16_t ac97_getextcaps(struct ac97_info *codec); +u_int16_t ac97_getcaps(struct ac97_info *codec); + diff --git a/sys/dev/sound/pcm/ac97_if.m b/sys/dev/sound/pcm/ac97_if.m new file mode 100644 index 0000000..36c5c19 --- /dev/null +++ b/sys/dev/sound/pcm/ac97_if.m @@ -0,0 +1,60 @@ +# KOBJ +# +# Copyright (c) 2000 Cameron Grant <cg@freebsd.org> +# 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. +# +# $FreeBSD$ +# + +#include <dev/sound/pcm/sound.h> + +INTERFACE ac97; + +CODE { + + static u_int32_t + ac97_noinit(kobj_t obj, void *devinfo) + { + return 1; + } + +}; + +METHOD u_int32_t init { + kobj_t obj; + void *devinfo; +} DEFAULT ac97_noinit; + +METHOD int read { + kobj_t obj; + void *devinfo; + int regno; +}; + +METHOD int write { + kobj_t obj; + void *devinfo; + int regno; + u_int32_t data; +}; diff --git a/sys/dev/sound/pcm/buffer.c b/sys/dev/sound/pcm/buffer.c new file mode 100644 index 0000000..d498591 --- /dev/null +++ b/sys/dev/sound/pcm/buffer.c @@ -0,0 +1,665 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + */ + +#include <dev/sound/pcm/sound.h> + +#include "feeder_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +#define MIN(x, y) (((x) < (y))? (x) : (y)) + +#define SNDBUF_NAMELEN 48 +struct snd_dbuf { + device_t dev; + u_int8_t *buf, *tmpbuf; + unsigned int bufsize, maxsize; + volatile int dl; /* transfer size */ + volatile int rp; /* pointers to the ready area */ + volatile int rl; /* length of ready area */ + volatile int hp; + volatile u_int32_t total, prev_total; + int isadmachan, dir; /* dma channel */ + u_int32_t fmt, spd, bps; + unsigned int blksz, blkcnt; + int xrun; + u_int32_t flags; + bus_dmamap_t dmamap; + bus_dma_tag_t dmatag; + struct selinfo sel; + char name[SNDBUF_NAMELEN]; +}; + +struct snd_dbuf * +sndbuf_create(device_t dev, char *drv, char *desc) +{ + struct snd_dbuf *b; + + b = malloc(sizeof(*b), M_DEVBUF, M_WAITOK | M_ZERO); + snprintf(b->name, SNDBUF_NAMELEN, "%s:%s", drv, desc); + b->dev = dev; + + return b; +} + +void +sndbuf_destroy(struct snd_dbuf *b) +{ + free(b, M_DEVBUF); +} + +static void +sndbuf_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct snd_dbuf *b = (struct snd_dbuf *)arg; + + if (bootverbose) { + device_printf(b->dev, "sndbuf_setmap %lx, %lx; ", (unsigned long)segs->ds_addr, + (unsigned long)segs->ds_len); + printf("%p -> %lx\n", b->buf, (unsigned long)vtophys(b->buf)); + } +} + +/* + * Allocate memory for DMA buffer. If the device does not use DMA transfers, + * the driver can call malloc(9) and sndbuf_setup() itself. + */ +int +sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, unsigned int size) +{ + b->dmatag = dmatag; + b->maxsize = size; + b->bufsize = b->maxsize; + if (bus_dmamem_alloc(b->dmatag, (void **)&b->buf, BUS_DMA_NOWAIT, &b->dmamap)) + return ENOSPC; + if (bus_dmamap_load(b->dmatag, b->dmamap, b->buf, b->maxsize, sndbuf_setmap, b, 0)) + return ENOSPC; + return sndbuf_resize(b, 2, b->maxsize / 2); +} + +int +sndbuf_setup(struct snd_dbuf *b, void *buf, unsigned int size) +{ + b->buf = buf; + b->maxsize = size; + b->bufsize = b->maxsize; + return sndbuf_resize(b, 2, b->maxsize / 2); +} + +void +sndbuf_free(struct snd_dbuf *b) +{ + if (b->tmpbuf) + free(b->tmpbuf, M_DEVBUF); + b->tmpbuf = NULL; + + if (b->dmamap) + bus_dmamap_unload(b->dmatag, b->dmamap); + + if (b->dmamap && b->buf) + bus_dmamem_free(b->dmatag, b->buf, b->dmamap); + b->dmamap = NULL; + b->buf = NULL; +} + +int +sndbuf_resize(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) +{ + if (b->maxsize == 0) + return 0; + if (blkcnt == 0) + blkcnt = b->blkcnt; + if (blksz == 0) + blksz = b->blksz; + if (blkcnt < 2 || blksz < 16 || (blkcnt * blksz > b->maxsize)) + return EINVAL; + if (blkcnt == b->blkcnt && blksz == b->blksz) + return 0; + b->blkcnt = blkcnt; + b->blksz = blksz; + b->bufsize = blkcnt * blksz; + if (b->tmpbuf) + free(b->tmpbuf, M_DEVBUF); + b->tmpbuf = malloc(b->bufsize, M_DEVBUF, M_NOWAIT); + sndbuf_reset(b); + return 0; +} + +int +sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) +{ + u_int8_t *buf, *tmpbuf, *f1, *f2; + unsigned int bufsize; + + if (blkcnt < 2 || blksz < 16) + return EINVAL; + + bufsize = blksz * blkcnt; + + + buf = malloc(bufsize, M_DEVBUF, M_NOWAIT); + if (buf == NULL) + return ENOMEM; + + tmpbuf = malloc(bufsize, M_DEVBUF, M_NOWAIT); + if (tmpbuf == NULL) { + free(buf, M_DEVBUF); + return ENOMEM; + } + + b->blkcnt = blkcnt; + b->blksz = blksz; + b->bufsize = bufsize; + b->maxsize = bufsize; + f1 = b->buf; + f2 = b->tmpbuf; + b->buf = buf; + b->tmpbuf = tmpbuf; + + if (f1) + free(f1, M_DEVBUF); + if (f2) + free(f2, M_DEVBUF); + + sndbuf_reset(b); + return 0; +} + +void +sndbuf_clear(struct snd_dbuf *b, unsigned int length) +{ + int i; + u_char data, *p; + + if (length == 0) + return; + if (length > b->bufsize) + length = b->bufsize; + + if (b->fmt & AFMT_SIGNED) + data = 0x00; + else + data = 0x80; + + i = sndbuf_getfreeptr(b); + p = sndbuf_getbuf(b); + while (length > 0) { + p[i] = data; + length--; + i++; + if (i >= b->bufsize) + i = 0; + } +} + +void +sndbuf_fillsilence(struct snd_dbuf *b) +{ + int i; + u_char data, *p; + + if (b->fmt & AFMT_SIGNED) + data = 0x00; + else + data = 0x80; + + i = 0; + p = sndbuf_getbuf(b); + while (i < b->bufsize) + p[i++] = data; + b->rp = 0; + b->rl = b->bufsize; +} + +void +sndbuf_reset(struct snd_dbuf *b) +{ + b->hp = 0; + b->rp = 0; + b->rl = 0; + b->dl = 0; + b->prev_total = 0; + b->total = 0; + b->xrun = 0; + if (b->buf && b->bufsize > 0) + sndbuf_clear(b, b->bufsize); +} + +u_int32_t +sndbuf_getfmt(struct snd_dbuf *b) +{ + return b->fmt; +} + +int +sndbuf_setfmt(struct snd_dbuf *b, u_int32_t fmt) +{ + b->fmt = fmt; + b->bps = 1; + b->bps <<= (b->fmt & AFMT_STEREO)? 1 : 0; + b->bps <<= (b->fmt & AFMT_16BIT)? 1 : 0; + b->bps <<= (b->fmt & AFMT_32BIT)? 2 : 0; + return 0; +} + +unsigned int +sndbuf_getspd(struct snd_dbuf *b) +{ + return b->spd; +} + +void +sndbuf_setspd(struct snd_dbuf *b, unsigned int spd) +{ + b->spd = spd; +} + +unsigned int +sndbuf_getalign(struct snd_dbuf *b) +{ + static int align[] = {0, 1, 1, 2, 2, 2, 2, 3}; + + return align[b->bps - 1]; +} + +unsigned int +sndbuf_getblkcnt(struct snd_dbuf *b) +{ + return b->blkcnt; +} + +void +sndbuf_setblkcnt(struct snd_dbuf *b, unsigned int blkcnt) +{ + b->blkcnt = blkcnt; +} + +unsigned int +sndbuf_getblksz(struct snd_dbuf *b) +{ + return b->blksz; +} + +void +sndbuf_setblksz(struct snd_dbuf *b, unsigned int blksz) +{ + b->blksz = blksz; +} + +unsigned int +sndbuf_getbps(struct snd_dbuf *b) +{ + return b->bps; +} + +void * +sndbuf_getbuf(struct snd_dbuf *b) +{ + return b->buf; +} + +void * +sndbuf_getbufofs(struct snd_dbuf *b, unsigned int ofs) +{ + KASSERT(ofs < b->bufsize, ("%s: ofs invalid %d", __func__, ofs)); + + return b->buf + ofs; +} + +unsigned int +sndbuf_getsize(struct snd_dbuf *b) +{ + return b->bufsize; +} + +unsigned int +sndbuf_getmaxsize(struct snd_dbuf *b) +{ + return b->maxsize; +} + +unsigned int +sndbuf_runsz(struct snd_dbuf *b) +{ + return b->dl; +} + +void +sndbuf_setrun(struct snd_dbuf *b, int go) +{ + b->dl = go? b->blksz : 0; +} + +struct selinfo * +sndbuf_getsel(struct snd_dbuf *b) +{ + return &b->sel; +} + +/************************************************************/ +unsigned int +sndbuf_getxrun(struct snd_dbuf *b) +{ + SNDBUF_LOCKASSERT(b); + + return b->xrun; +} + +void +sndbuf_setxrun(struct snd_dbuf *b, unsigned int cnt) +{ + SNDBUF_LOCKASSERT(b); + + b->xrun = cnt; +} + +unsigned int +sndbuf_gethwptr(struct snd_dbuf *b) +{ + SNDBUF_LOCKASSERT(b); + + return b->hp; +} + +void +sndbuf_sethwptr(struct snd_dbuf *b, unsigned int ptr) +{ + SNDBUF_LOCKASSERT(b); + + b->hp = ptr; +} + +unsigned int +sndbuf_getready(struct snd_dbuf *b) +{ + SNDBUF_LOCKASSERT(b); + KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); + + return b->rl; +} + +unsigned int +sndbuf_getreadyptr(struct snd_dbuf *b) +{ + SNDBUF_LOCKASSERT(b); + KASSERT((b->rp >= 0) && (b->rp <= b->bufsize), ("%s: b->rp invalid %d", __func__, b->rp)); + + return b->rp; +} + +unsigned int +sndbuf_getfree(struct snd_dbuf *b) +{ + SNDBUF_LOCKASSERT(b); + KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); + + return b->bufsize - b->rl; +} + +unsigned int +sndbuf_getfreeptr(struct snd_dbuf *b) +{ + SNDBUF_LOCKASSERT(b); + KASSERT((b->rp >= 0) && (b->rp <= b->bufsize), ("%s: b->rp invalid %d", __func__, b->rp)); + KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); + + return (b->rp + b->rl) % b->bufsize; +} + +unsigned int +sndbuf_getblocks(struct snd_dbuf *b) +{ + SNDBUF_LOCKASSERT(b); + + return b->total / b->blksz; +} + +unsigned int +sndbuf_getprevblocks(struct snd_dbuf *b) +{ + SNDBUF_LOCKASSERT(b); + + return b->prev_total / b->blksz; +} + +unsigned int +sndbuf_gettotal(struct snd_dbuf *b) +{ + SNDBUF_LOCKASSERT(b); + + return b->total; +} + +void +sndbuf_updateprevtotal(struct snd_dbuf *b) +{ + SNDBUF_LOCKASSERT(b); + + b->prev_total = b->total; +} + +/************************************************************/ + +int +sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count) +{ + int l; + + KASSERT(count <= sndbuf_getfree(b), ("%s: count %d > free %d", __func__, count, sndbuf_getfree(b))); + KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); + b->total += count; + if (from != NULL) { + while (count > 0) { + l = MIN(count, sndbuf_getsize(b) - sndbuf_getfreeptr(b)); + bcopy(from, sndbuf_getbufofs(b, sndbuf_getfreeptr(b)), l); + from += l; + b->rl += l; + count -= l; + } + } else + b->rl += count; + KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d, count %d", __func__, b->rl, count)); + + return 0; +} + +int +sndbuf_dispose(struct snd_dbuf *b, u_int8_t *to, unsigned int count) +{ + int l; + + KASSERT(count <= sndbuf_getready(b), ("%s: count %d > ready %d", __func__, count, sndbuf_getready(b))); + KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); + if (to != NULL) { + while (count > 0) { + l = MIN(count, sndbuf_getsize(b) - sndbuf_getreadyptr(b)); + bcopy(sndbuf_getbufofs(b, sndbuf_getreadyptr(b)), to, l); + to += l; + b->rl -= l; + b->rp = (b->rp + l) % b->bufsize; + count -= l; + } + } else { + b->rl -= count; + b->rp = (b->rp + count) % b->bufsize; + } + KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d, count %d", __func__, b->rl, count)); + + return 0; +} + +int +sndbuf_uiomove(struct snd_dbuf *b, struct uio *uio, unsigned int count) +{ + int x, c, p, rd, err; + + err = 0; + rd = (uio->uio_rw == UIO_READ)? 1 : 0; + if (count > uio->uio_resid) + return EINVAL; + + if (count > (rd? sndbuf_getready(b) : sndbuf_getfree(b))) { + return EINVAL; + } + + while (err == 0 && count > 0) { + p = rd? sndbuf_getreadyptr(b) : sndbuf_getfreeptr(b); + c = MIN(count, sndbuf_getsize(b) - p); + x = uio->uio_resid; + err = uiomove(sndbuf_getbufofs(b, p), c, uio); + x -= uio->uio_resid; + count -= x; + x = rd? sndbuf_dispose(b, NULL, x) : sndbuf_acquire(b, NULL, x); + } + + return 0; +} + +/* count is number of bytes we want added to destination buffer */ +int +sndbuf_feed(struct snd_dbuf *from, struct snd_dbuf *to, struct pcm_channel *channel, struct pcm_feeder *feeder, unsigned int count) +{ + KASSERT(count > 0, ("can't feed 0 bytes")); + + if (sndbuf_getfree(to) < count) + return EINVAL; + + count = FEEDER_FEED(feeder, channel, to->tmpbuf, count, from); + if (count) + sndbuf_acquire(to, to->tmpbuf, count); + /* the root feeder has called sndbuf_dispose(from, , bytes fetched) */ + + return 0; +} + +/************************************************************/ + +void +sndbuf_dump(struct snd_dbuf *b, char *s, u_int32_t what) +{ + printf("%s: [", s); + if (what & 0x01) + printf(" bufsize: %d, maxsize: %d", b->bufsize, b->maxsize); + if (what & 0x02) + printf(" dl: %d, rp: %d, rl: %d, hp: %d", b->dl, b->rp, b->rl, b->hp); + if (what & 0x04) + printf(" total: %d, prev_total: %d, xrun: %d", b->total, b->prev_total, b->xrun); + if (what & 0x08) + printf(" fmt: 0x%x, spd: %d", b->fmt, b->spd); + if (what & 0x10) + printf(" blksz: %d, blkcnt: %d, flags: 0x%x", b->blksz, b->blkcnt, b->flags); + printf(" ]\n"); +} + +/************************************************************/ +u_int32_t +sndbuf_getflags(struct snd_dbuf *b) +{ + return b->flags; +} + +void +sndbuf_setflags(struct snd_dbuf *b, u_int32_t flags, int on) +{ + b->flags &= ~flags; + if (on) + b->flags |= flags; +} + +/************************************************************/ + +int +sndbuf_isadmasetup(struct snd_dbuf *b, struct resource *drq) +{ + /* should do isa_dma_acquire/isa_dma_release here */ + if (drq == NULL) { + b->isadmachan = -1; + } else { + sndbuf_setflags(b, SNDBUF_F_ISADMA, 1); + b->isadmachan = rman_get_start(drq); + } + return 0; +} + +int +sndbuf_isadmasetdir(struct snd_dbuf *b, int dir) +{ + KASSERT(b, ("sndbuf_isadmasetdir called with b == NULL")); + KASSERT(sndbuf_getflags(b) & SNDBUF_F_ISADMA, ("sndbuf_isadmasetdir called on non-ISA buffer")); + + b->dir = (dir == PCMDIR_PLAY)? ISADMA_WRITE : ISADMA_READ; + return 0; +} + +void +sndbuf_isadma(struct snd_dbuf *b, int go) +{ + KASSERT(b, ("sndbuf_isadma called with b == NULL")); + KASSERT(sndbuf_getflags(b) & SNDBUF_F_ISADMA, ("sndbuf_isadma called on non-ISA buffer")); + + switch (go) { + case PCMTRIG_START: + /* isa_dmainit(b->chan, size); */ + isa_dmastart(b->dir | ISADMA_RAW, b->buf, b->bufsize, b->isadmachan); + break; + + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + isa_dmastop(b->isadmachan); + isa_dmadone(b->dir | ISADMA_RAW, b->buf, b->bufsize, b->isadmachan); + break; + } + + DEB(printf("buf 0x%p ISA DMA %s, channel %d\n", + b, + (go == PCMTRIG_START)? "started" : "stopped", + b->isadmachan)); +} + +int +sndbuf_isadmaptr(struct snd_dbuf *b) +{ + int i; + + KASSERT(b, ("sndbuf_isadmaptr called with b == NULL")); + KASSERT(sndbuf_getflags(b) & SNDBUF_F_ISADMA, ("sndbuf_isadmaptr called on non-ISA buffer")); + + if (!sndbuf_runsz(b)) + return 0; + i = isa_dmastatus(b->isadmachan); + KASSERT(i >= 0, ("isa_dmastatus returned %d", i)); + return b->bufsize - i; +} + +void +sndbuf_isadmabounce(struct snd_dbuf *b) +{ + KASSERT(b, ("sndbuf_isadmabounce called with b == NULL")); + KASSERT(sndbuf_getflags(b) & SNDBUF_F_ISADMA, ("sndbuf_isadmabounce called on non-ISA buffer")); + + /* tell isa_dma to bounce data in/out */ +} + diff --git a/sys/dev/sound/pcm/buffer.h b/sys/dev/sound/pcm/buffer.h new file mode 100644 index 0000000..930d2e5 --- /dev/null +++ b/sys/dev/sound/pcm/buffer.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + * + * $FreeBSD$ + */ + +#define ISA_DMA(b) (sndbuf_getflags((b)) & SNDBUF_F_ISADMA) +#define SNDBUF_LOCKASSERT(b) + +#define SNDBUF_F_ISADMA 0x00000001 +#define SNDBUF_F_XRUN 0x00000002 +#define SNDBUF_F_RUNNING 0x00000004 + +struct snd_dbuf *sndbuf_create(device_t dev, char *drv, char *desc); +void sndbuf_destroy(struct snd_dbuf *b); + +void sndbuf_dump(struct snd_dbuf *b, char *s, u_int32_t what); + +int sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, unsigned int size); +int sndbuf_setup(struct snd_dbuf *b, void *buf, unsigned int size); +void sndbuf_free(struct snd_dbuf *b); +int sndbuf_resize(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz); +int sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz); +void sndbuf_reset(struct snd_dbuf *b); +void sndbuf_clear(struct snd_dbuf *b, unsigned int length); +void sndbuf_fillsilence(struct snd_dbuf *b); + +u_int32_t sndbuf_getfmt(struct snd_dbuf *b); +int sndbuf_setfmt(struct snd_dbuf *b, u_int32_t fmt); +unsigned int sndbuf_getspd(struct snd_dbuf *b); +void sndbuf_setspd(struct snd_dbuf *b, unsigned int spd); +unsigned int sndbuf_getbps(struct snd_dbuf *b); + +void *sndbuf_getbuf(struct snd_dbuf *b); +void *sndbuf_getbufofs(struct snd_dbuf *b, unsigned int ofs); +unsigned int sndbuf_getsize(struct snd_dbuf *b); +unsigned int sndbuf_getmaxsize(struct snd_dbuf *b); +unsigned int sndbuf_getalign(struct snd_dbuf *b); +unsigned int sndbuf_getblkcnt(struct snd_dbuf *b); +void sndbuf_setblkcnt(struct snd_dbuf *b, unsigned int blkcnt); +unsigned int sndbuf_getblksz(struct snd_dbuf *b); +void sndbuf_setblksz(struct snd_dbuf *b, unsigned int blksz); +unsigned int sndbuf_runsz(struct snd_dbuf *b); +void sndbuf_setrun(struct snd_dbuf *b, int go); +struct selinfo *sndbuf_getsel(struct snd_dbuf *b); + +unsigned int sndbuf_getxrun(struct snd_dbuf *b); +void sndbuf_setxrun(struct snd_dbuf *b, unsigned int cnt); +unsigned int sndbuf_gethwptr(struct snd_dbuf *b); +void sndbuf_sethwptr(struct snd_dbuf *b, unsigned int ptr); +unsigned int sndbuf_getfree(struct snd_dbuf *b); +unsigned int sndbuf_getfreeptr(struct snd_dbuf *b); +unsigned int sndbuf_getready(struct snd_dbuf *b); +unsigned int sndbuf_getreadyptr(struct snd_dbuf *b); +unsigned int sndbuf_getblocks(struct snd_dbuf *b); +unsigned int sndbuf_getprevblocks(struct snd_dbuf *b); +unsigned int sndbuf_gettotal(struct snd_dbuf *b); +void sndbuf_updateprevtotal(struct snd_dbuf *b); + +int sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count); +int sndbuf_dispose(struct snd_dbuf *b, u_int8_t *to, unsigned int count); +int sndbuf_uiomove(struct snd_dbuf *b, struct uio *uio, unsigned int count); +int sndbuf_feed(struct snd_dbuf *from, struct snd_dbuf *to, struct pcm_channel *channel, struct pcm_feeder *feeder, unsigned int count); + +u_int32_t sndbuf_getflags(struct snd_dbuf *b); +void sndbuf_setflags(struct snd_dbuf *b, u_int32_t flags, int on); + +int sndbuf_isadmasetup(struct snd_dbuf *b, struct resource *drq); +int sndbuf_isadmasetdir(struct snd_dbuf *b, int dir); +void sndbuf_isadma(struct snd_dbuf *b, int go); +int sndbuf_isadmaptr(struct snd_dbuf *b); +void sndbuf_isadmabounce(struct snd_dbuf *b); + + diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c new file mode 100644 index 0000000..9bf9158 --- /dev/null +++ b/sys/dev/sound/pcm/channel.c @@ -0,0 +1,1214 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Portions Copyright by Luigi Rizzo - 1997-99 + * 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. + */ + +#include <dev/sound/pcm/sound.h> + +#include "feeder_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +#define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ +#define DMA_ALIGN_THRESHOLD 4 +#define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1)) + +#define MIN(x, y) (((x) < (y))? (x) : (y)) +#define CANCHANGE(c) (!(c->flags & CHN_F_TRIGGERED)) + +/* +#define DEB(x) x +*/ + +static int chn_targetirqrate = 32; +TUNABLE_INT("hw.snd.targetirqrate", &chn_targetirqrate); + +static int +sysctl_hw_snd_targetirqrate(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = chn_targetirqrate; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + if (val < 16 || val > 512) + err = EINVAL; + else + chn_targetirqrate = val; + + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, targetirqrate, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_targetirqrate, "I", ""); +static int report_soft_formats = 1; +SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, + &report_soft_formats, 1, "report software-emulated formats"); + +static int chn_buildfeeder(struct pcm_channel *c); + +static void +chn_lockinit(struct pcm_channel *c) +{ + c->lock = snd_mtxcreate(c->name, "pcm channel"); +} + +static void +chn_lockdestroy(struct pcm_channel *c) +{ + snd_mtxfree(c->lock); +} + +static int +chn_polltrigger(struct pcm_channel *c) +{ + struct snd_dbuf *bs = c->bufsoft; + unsigned amt, lim; + + CHN_LOCKASSERT(c); + if (c->flags & CHN_F_MAPPED) { + if (sndbuf_getprevblocks(bs) == 0) + return 1; + else + return (sndbuf_getblocks(bs) > sndbuf_getprevblocks(bs))? 1 : 0; + } else { + amt = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); + lim = (c->flags & CHN_F_HAS_SIZE)? sndbuf_getblksz(bs) : 1; + lim = 1; + return (amt >= lim)? 1 : 0; + } + return 0; +} + +static int +chn_pollreset(struct pcm_channel *c) +{ + struct snd_dbuf *bs = c->bufsoft; + + CHN_LOCKASSERT(c); + sndbuf_updateprevtotal(bs); + return 1; +} + +static void +chn_wakeup(struct pcm_channel *c) +{ + struct snd_dbuf *bs = c->bufsoft; + + CHN_LOCKASSERT(c); + if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c)) + selwakeup(sndbuf_getsel(bs)); + wakeup(bs); +} + +static int +chn_sleep(struct pcm_channel *c, char *str, int timeout) +{ + struct snd_dbuf *bs = c->bufsoft; + int ret; + + CHN_LOCKASSERT(c); +#ifdef USING_MUTEX + ret = msleep(bs, c->lock, PRIBIO | PCATCH, str, timeout); +#else + ret = tsleep(bs, PRIBIO | PCATCH, str, timeout); +#endif + + return ret; +} + +/* + * chn_dmaupdate() tracks the status of a dma transfer, + * updating pointers. It must be called at spltty(). + */ + +static unsigned int +chn_dmaupdate(struct pcm_channel *c) +{ + struct snd_dbuf *b = c->bufhard; + unsigned int delta, old, hwptr, amt; + + KASSERT(sndbuf_getsize(b) > 0, ("bufsize == 0")); + CHN_LOCKASSERT(c); + + old = sndbuf_gethwptr(b); + hwptr = chn_getptr(c); + delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b); + sndbuf_sethwptr(b, hwptr); + + DEB( + if (delta >= ((sndbuf_getsize(b) * 15) / 16)) { + if (!(c->flags & (CHN_F_CLOSING | CHN_F_ABORTING))) + device_printf(c->dev, "hwptr went backwards %d -> %d\n", old, hwptr); + } + ); + + if (c->direction == PCMDIR_PLAY) { + amt = MIN(delta, sndbuf_getready(b)); + if (amt > 0) + sndbuf_dispose(b, NULL, amt); + } else { + amt = MIN(delta, sndbuf_getfree(b)); + if (amt > 0) + sndbuf_acquire(b, NULL, amt); + } + + return delta; +} + +void +chn_wrupdate(struct pcm_channel *c) +{ + int ret; + + CHN_LOCKASSERT(c); + KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel")); + + if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || !(c->flags & CHN_F_TRIGGERED)) + return; + chn_dmaupdate(c); + ret = chn_wrfeed(c); + /* tell the driver we've updated the primary buffer */ + chn_trigger(c, PCMTRIG_EMLDMAWR); + DEB(if (ret) + printf("chn_wrupdate: chn_wrfeed returned %d\n", ret);) + +} + +int +chn_wrfeed(struct pcm_channel *c) +{ + struct snd_dbuf *b = c->bufhard; + struct snd_dbuf *bs = c->bufsoft; + unsigned int ret, amt; + + CHN_LOCKASSERT(c); + DEB( + if (c->flags & CHN_F_CLOSING) { + sndbuf_dump(b, "b", 0x02); + sndbuf_dump(bs, "bs", 0x02); + }) + + if (c->flags & CHN_F_MAPPED) + sndbuf_acquire(bs, NULL, sndbuf_getfree(bs)); + + amt = sndbuf_getfree(b); + if (sndbuf_getready(bs) < amt) + c->xruns++; + + ret = (amt > 0)? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; + if (ret == 0 && sndbuf_getfree(b) < amt) + chn_wakeup(c); + + return ret; +} + +static void +chn_wrintr(struct pcm_channel *c) +{ + int ret; + + CHN_LOCKASSERT(c); + /* update pointers in primary buffer */ + chn_dmaupdate(c); + /* ...and feed from secondary to primary */ + ret = chn_wrfeed(c); + /* tell the driver we've updated the primary buffer */ + chn_trigger(c, PCMTRIG_EMLDMAWR); + DEB(if (ret) + printf("chn_wrintr: chn_wrfeed returned %d\n", ret);) +} + +/* + * user write routine - uiomove data into secondary buffer, trigger if necessary + * if blocking, sleep, rinse and repeat. + * + * called externally, so must handle locking + */ + +int +chn_write(struct pcm_channel *c, struct uio *buf) +{ + int ret, timeout, newsize, count, sz; + struct snd_dbuf *bs = c->bufsoft; + + CHN_LOCKASSERT(c); + /* + * XXX Certain applications attempt to write larger size + * of pcm data than c->blocksize2nd without blocking, + * resulting partial write. Expand the block size so that + * the write operation avoids blocking. + */ + if ((c->flags & CHN_F_NBIO) && buf->uio_resid > sndbuf_getblksz(bs)) { + DEB(device_printf(c->dev, "broken app, nbio and tried to write %d bytes with fragsz %d\n", + buf->uio_resid, sndbuf_getblksz(bs))); + newsize = 16; + while (newsize < min(buf->uio_resid, CHN_2NDBUFMAXSIZE / 2)) + newsize <<= 1; + chn_setblocksize(c, sndbuf_getblkcnt(bs), newsize); + DEB(device_printf(c->dev, "frags reset to %d x %d\n", sndbuf_getblkcnt(bs), sndbuf_getblksz(bs))); + } + + ret = 0; + count = hz; + while (!ret && (buf->uio_resid > 0) && (count > 0)) { + sz = sndbuf_getfree(bs); + if (sz == 0) { + if (c->flags & CHN_F_NBIO) + ret = EWOULDBLOCK; + else { + timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); + if (timeout < 1) + timeout = 1; + timeout = 1; + ret = chn_sleep(c, "pcmwr", timeout); + if (ret == EWOULDBLOCK) { + count -= timeout; + ret = 0; + } else if (ret == 0) + count = hz; + } + } else { + sz = MIN(sz, buf->uio_resid); + KASSERT(sz > 0, ("confusion in chn_write")); + /* printf("sz: %d\n", sz); */ + ret = sndbuf_uiomove(bs, buf, sz); + if (ret == 0 && !(c->flags & CHN_F_TRIGGERED)) + chn_start(c, 0); + } + } + /* printf("ret: %d left: %d\n", ret, buf->uio_resid); */ + + if (count <= 0) { + c->flags |= CHN_F_DEAD; + printf("%s: play interrupt timeout, channel dead\n", c->name); + } + + return ret; +} + +static int +chn_rddump(struct pcm_channel *c, unsigned int cnt) +{ + struct snd_dbuf *b = c->bufhard; + + CHN_LOCKASSERT(c); + sndbuf_setxrun(b, sndbuf_getxrun(b) + cnt); + return sndbuf_dispose(b, NULL, cnt); +} + +/* + * Feed new data from the read buffer. Can be called in the bottom half. + * Hence must be called at spltty. + */ +int +chn_rdfeed(struct pcm_channel *c) +{ + struct snd_dbuf *b = c->bufhard; + struct snd_dbuf *bs = c->bufsoft; + unsigned int ret, amt; + + CHN_LOCKASSERT(c); + DEB( + if (c->flags & CHN_F_CLOSING) { + sndbuf_dump(b, "b", 0x02); + sndbuf_dump(bs, "bs", 0x02); + }) + + amt = sndbuf_getready(b); + if (sndbuf_getfree(bs) < amt) { + c->xruns++; + amt = sndbuf_getfree(bs); + } + ret = (amt > 0)? sndbuf_feed(b, bs, c, c->feeder, amt) : 0; + + amt = sndbuf_getready(b); + if (amt > 0) + chn_rddump(c, amt); + + chn_wakeup(c); + + return ret; +} + +void +chn_rdupdate(struct pcm_channel *c) +{ + int ret; + + CHN_LOCKASSERT(c); + KASSERT(c->direction == PCMDIR_REC, ("chn_rdupdate on bad channel")); + + if ((c->flags & CHN_F_MAPPED) || !(c->flags & CHN_F_TRIGGERED)) + return; + chn_trigger(c, PCMTRIG_EMLDMARD); + chn_dmaupdate(c); + ret = chn_rdfeed(c); + if (ret) + printf("chn_rdfeed: %d\n", ret); + +} + +/* read interrupt routine. Must be called with interrupts blocked. */ +static void +chn_rdintr(struct pcm_channel *c) +{ + int ret; + + CHN_LOCKASSERT(c); + /* tell the driver to update the primary buffer if non-dma */ + chn_trigger(c, PCMTRIG_EMLDMARD); + /* update pointers in primary buffer */ + chn_dmaupdate(c); + /* ...and feed from primary to secondary */ + ret = chn_rdfeed(c); +} + +/* + * user read routine - trigger if necessary, uiomove data from secondary buffer + * if blocking, sleep, rinse and repeat. + * + * called externally, so must handle locking + */ + +int +chn_read(struct pcm_channel *c, struct uio *buf) +{ + int ret, timeout, sz, count; + struct snd_dbuf *bs = c->bufsoft; + + CHN_LOCKASSERT(c); + if (!(c->flags & CHN_F_TRIGGERED)) + chn_start(c, 0); + + ret = 0; + count = hz; + while (!ret && (buf->uio_resid > 0) && (count > 0)) { + sz = MIN(buf->uio_resid, sndbuf_getready(bs)); + + if (sz > 0) { + ret = sndbuf_uiomove(bs, buf, sz); + } else { + if (c->flags & CHN_F_NBIO) { + ret = EWOULDBLOCK; + } else { + timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); + if (timeout < 1) + timeout = 1; + ret = chn_sleep(c, "pcmrd", timeout); + if (ret == EWOULDBLOCK) { + count -= timeout; + ret = 0; + } else { + count = hz; + } + + } + } + } + + if (count <= 0) { + c->flags |= CHN_F_DEAD; + printf("%s: record interrupt timeout, channel dead\n", c->name); + } + + return ret; +} + +void +chn_intr(struct pcm_channel *c) +{ + CHN_LOCK(c); + c->interrupts++; + if (c->direction == PCMDIR_PLAY) + chn_wrintr(c); + else + chn_rdintr(c); + CHN_UNLOCK(c); +} + +u_int32_t +chn_start(struct pcm_channel *c, int force) +{ + u_int32_t i, j; + struct snd_dbuf *b = c->bufhard; + struct snd_dbuf *bs = c->bufsoft; + + CHN_LOCKASSERT(c); + /* if we're running, or if we're prevented from triggering, bail */ + if ((c->flags & CHN_F_TRIGGERED) || ((c->flags & CHN_F_NOTRIGGER) && !force)) + return EINVAL; + + i = (c->direction == PCMDIR_PLAY)? sndbuf_getready(bs) : sndbuf_getfree(bs); + j = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(b) : sndbuf_getready(b); + if (force || (i >= j)) { + c->flags |= CHN_F_TRIGGERED; + /* + * if we're starting because a vchan started, don't feed any data + * or it becomes impossible to start vchans synchronised with the + * first one. the hardbuf should be empty so we top it up with + * silence to give it something to chew. the real data will be + * fed at the first irq. + */ + if (c->direction == PCMDIR_PLAY) { + if (SLIST_EMPTY(&c->children)) + chn_wrfeed(c); + else + sndbuf_fillsilence(b); + } + sndbuf_setrun(b, 1); + c->xruns = 0; + chn_trigger(c, PCMTRIG_START); + return 0; + } + + return 0; +} + +void +chn_resetbuf(struct pcm_channel *c) +{ + struct snd_dbuf *b = c->bufhard; + struct snd_dbuf *bs = c->bufsoft; + + c->blocks = 0; + sndbuf_reset(b); + sndbuf_reset(bs); +} + +/* + * chn_sync waits until the space in the given channel goes above + * a threshold. The threshold is checked against fl or rl respectively. + * Assume that the condition can become true, do not check here... + */ +int +chn_sync(struct pcm_channel *c, int threshold) +{ + u_long rdy; + int ret; + struct snd_dbuf *bs = c->bufsoft; + + CHN_LOCKASSERT(c); + for (;;) { + rdy = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); + if (rdy <= threshold) { + ret = chn_sleep(c, "pcmsyn", 1); + if (ret == ERESTART || ret == EINTR) { + DEB(printf("chn_sync: tsleep returns %d\n", ret)); + return -1; + } + } else + break; + } + return 0; +} + +/* called externally, handle locking */ +int +chn_poll(struct pcm_channel *c, int ev, struct thread *td) +{ + struct snd_dbuf *bs = c->bufsoft; + int ret; + + CHN_LOCKASSERT(c); + if (!(c->flags & CHN_F_MAPPED) && !(c->flags & CHN_F_TRIGGERED)) + chn_start(c, 1); + ret = 0; + if (chn_polltrigger(c) && chn_pollreset(c)) + ret = ev; + else + selrecord(td, sndbuf_getsel(bs)); + return ret; +} + +/* + * chn_abort terminates a running dma transfer. it may sleep up to 200ms. + * it returns the number of bytes that have not been transferred. + * + * called from: dsp_close, dsp_ioctl, with channel locked + */ +int +chn_abort(struct pcm_channel *c) +{ + int missing = 0; + struct snd_dbuf *b = c->bufhard; + struct snd_dbuf *bs = c->bufsoft; + + CHN_LOCKASSERT(c); + if (!(c->flags & CHN_F_TRIGGERED)) + return 0; + c->flags |= CHN_F_ABORTING; + + c->flags &= ~CHN_F_TRIGGERED; + /* kill the channel */ + chn_trigger(c, PCMTRIG_ABORT); + sndbuf_setrun(b, 0); + if (!(c->flags & CHN_F_VIRTUAL)) + chn_dmaupdate(c); + missing = sndbuf_getready(bs) + sndbuf_getready(b); + + c->flags &= ~CHN_F_ABORTING; + return missing; +} + +/* + * this routine tries to flush the dma transfer. It is called + * on a close. We immediately abort any read DMA + * operation, and then wait for the play buffer to drain. + * + * called from: dsp_close + */ + +int +chn_flush(struct pcm_channel *c) +{ + int ret, count, resid, resid_p; + struct snd_dbuf *b = c->bufhard; + struct snd_dbuf *bs = c->bufsoft; + + CHN_LOCKASSERT(c); + KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel")); + DEB(printf("chn_flush c->flags 0x%08x\n", c->flags)); + if (!(c->flags & CHN_F_TRIGGERED)) + return 0; + + c->flags |= CHN_F_CLOSING; + resid = sndbuf_getready(bs) + sndbuf_getready(b); + resid_p = resid; + count = 10; + ret = 0; + while ((count > 0) && (resid > sndbuf_getsize(b)) && (ret == 0)) { + /* still pending output data. */ + ret = chn_sleep(c, "pcmflu", hz / 10); + if (ret == EWOULDBLOCK) + ret = 0; + if (ret == 0) { + resid = sndbuf_getready(bs) + sndbuf_getready(b); + if (resid >= resid_p) + count--; + resid_p = resid; + } + } + if (count == 0) + DEB(printf("chn_flush: timeout\n")); + + c->flags &= ~CHN_F_TRIGGERED; + /* kill the channel */ + chn_trigger(c, PCMTRIG_ABORT); + sndbuf_setrun(b, 0); + + c->flags &= ~CHN_F_CLOSING; + return 0; +} + +int +fmtvalid(u_int32_t fmt, u_int32_t *fmtlist) +{ + int i; + + for (i = 0; fmtlist[i]; i++) + if (fmt == fmtlist[i]) + return 1; + return 0; +} + +int +chn_reset(struct pcm_channel *c, u_int32_t fmt) +{ + int hwspd, r; + + CHN_LOCKASSERT(c); + c->flags &= CHN_F_RESET; + c->interrupts = 0; + c->xruns = 0; + + r = CHANNEL_RESET(c->methods, c->devinfo); + if (fmt != 0) { + hwspd = DSP_DEFAULT_SPEED; + /* only do this on a record channel until feederbuilder works */ + if (c->direction == PCMDIR_REC) + RANGE(hwspd, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed); + c->speed = hwspd; + + if (r == 0) + r = chn_setformat(c, fmt); + if (r == 0) + r = chn_setspeed(c, hwspd); + if (r == 0) + r = chn_setvolume(c, 100, 100); + } + if (r == 0) + r = chn_setblocksize(c, 0, 0); + if (r == 0) { + chn_resetbuf(c); + r = CHANNEL_RESETDONE(c->methods, c->devinfo); + } + return r; +} + +int +chn_init(struct pcm_channel *c, void *devinfo, int dir) +{ + struct feeder_class *fc; + struct snd_dbuf *b, *bs; + int ret; + + chn_lockinit(c); + + b = NULL; + bs = NULL; + c->devinfo = NULL; + c->feeder = NULL; + + ret = EINVAL; + fc = feeder_getclass(NULL); + if (fc == NULL) + goto out; + if (chn_addfeeder(c, fc, NULL)) + goto out; + + ret = ENOMEM; + b = sndbuf_create(c->dev, c->name, "primary"); + if (b == NULL) + goto out; + bs = sndbuf_create(c->dev, c->name, "secondary"); + if (bs == NULL) + goto out; + sndbuf_setup(bs, NULL, 0); + c->bufhard = b; + c->bufsoft = bs; + c->flags = 0; + c->feederflags = 0; + + ret = ENODEV; + c->devinfo = CHANNEL_INIT(c->methods, devinfo, b, c, dir); + if (c->devinfo == NULL) + goto out; + + ret = ENOMEM; + if ((sndbuf_getsize(b) == 0) && ((c->flags & CHN_F_VIRTUAL) == 0)) + goto out; + + ret = chn_setdir(c, dir); + if (ret) + goto out; + + ret = sndbuf_setfmt(b, AFMT_U8); + if (ret) + goto out; + + ret = sndbuf_setfmt(bs, AFMT_U8); + if (ret) + goto out; + + +out: + if (ret) { + if (c->devinfo) { + if (CHANNEL_FREE(c->methods, c->devinfo)) + sndbuf_free(b); + } + if (bs) + sndbuf_destroy(bs); + if (b) + sndbuf_destroy(b); + c->flags |= CHN_F_DEAD; + chn_lockdestroy(c); + + return ret; + } + + return 0; +} + +int +chn_kill(struct pcm_channel *c) +{ + struct snd_dbuf *b = c->bufhard; + struct snd_dbuf *bs = c->bufsoft; + + if (c->flags & CHN_F_TRIGGERED) + chn_trigger(c, PCMTRIG_ABORT); + while (chn_removefeeder(c) == 0); + if (CHANNEL_FREE(c->methods, c->devinfo)) + sndbuf_free(b); + c->flags |= CHN_F_DEAD; + sndbuf_destroy(bs); + sndbuf_destroy(b); + chn_lockdestroy(c); + return 0; +} + +int +chn_setdir(struct pcm_channel *c, int dir) +{ + struct snd_dbuf *b = c->bufhard; + int r; + + CHN_LOCKASSERT(c); + c->direction = dir; + r = CHANNEL_SETDIR(c->methods, c->devinfo, c->direction); + if (!r && ISA_DMA(b)) + sndbuf_isadmasetdir(b, c->direction); + return r; +} + +int +chn_setvolume(struct pcm_channel *c, int left, int right) +{ + CHN_LOCKASSERT(c); + /* could add a feeder for volume changing if channel returns -1 */ + c->volume = (left << 8) | right; + return 0; +} + +static int +chn_tryspeed(struct pcm_channel *c, int speed) +{ + struct pcm_feeder *f; + struct snd_dbuf *b = c->bufhard; + struct snd_dbuf *bs = c->bufsoft; + struct snd_dbuf *x; + int r, delta; + + CHN_LOCKASSERT(c); + DEB(printf("setspeed, channel %s\n", c->name)); + DEB(printf("want speed %d, ", speed)); + if (speed <= 0) + return EINVAL; + if (CANCHANGE(c)) { + r = 0; + c->speed = speed; + sndbuf_setspd(bs, speed); + RANGE(speed, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed); + DEB(printf("try speed %d, ", speed)); + sndbuf_setspd(b, CHANNEL_SETSPEED(c->methods, c->devinfo, speed)); + DEB(printf("got speed %d\n", sndbuf_getspd(b))); + + delta = sndbuf_getspd(b) - sndbuf_getspd(bs); + if (delta < 0) + delta = -delta; + + c->feederflags &= ~(1 << FEEDER_RATE); + if (delta > 500) + c->feederflags |= 1 << FEEDER_RATE; + else + sndbuf_setspd(bs, sndbuf_getspd(b)); + + r = chn_buildfeeder(c); + DEB(printf("r = %d\n", r)); + if (r) + goto out; + + r = chn_setblocksize(c, 0, 0); + if (r) + goto out; + + if (!(c->feederflags & (1 << FEEDER_RATE))) + goto out; + + r = EINVAL; + f = chn_findfeeder(c, FEEDER_RATE); + DEB(printf("feedrate = %p\n", f)); + if (f == NULL) + goto out; + + x = (c->direction == PCMDIR_REC)? b : bs; + r = FEEDER_SET(f, FEEDRATE_SRC, sndbuf_getspd(x)); + DEB(printf("feeder_set(FEEDRATE_SRC, %d) = %d\n", sndbuf_getspd(x), r)); + if (r) + goto out; + + x = (c->direction == PCMDIR_REC)? bs : b; + r = FEEDER_SET(f, FEEDRATE_DST, sndbuf_getspd(x)); + DEB(printf("feeder_set(FEEDRATE_DST, %d) = %d\n", sndbuf_getspd(x), r)); +out: + DEB(printf("setspeed done, r = %d\n", r)); + return r; + } else + return EINVAL; +} + +int +chn_setspeed(struct pcm_channel *c, int speed) +{ + int r, oldspeed = c->speed; + + r = chn_tryspeed(c, speed); + if (r) { + DEB(printf("Failed to set speed %d falling back to %d\n", speed, oldspeed)); + chn_tryspeed(c, oldspeed); + } + return r; +} + +static int +chn_tryformat(struct pcm_channel *c, u_int32_t fmt) +{ + struct snd_dbuf *b = c->bufhard; + struct snd_dbuf *bs = c->bufsoft; + int r; + + CHN_LOCKASSERT(c); + if (CANCHANGE(c)) { + DEB(printf("want format %d\n", fmt)); + c->format = fmt; + r = chn_buildfeeder(c); + if (r == 0) { + sndbuf_setfmt(bs, c->format); + chn_resetbuf(c); + r = CHANNEL_SETFORMAT(c->methods, c->devinfo, sndbuf_getfmt(b)); + if (r == 0) + r = chn_tryspeed(c, c->speed); + } + return r; + } else + return EINVAL; +} + +int +chn_setformat(struct pcm_channel *c, u_int32_t fmt) +{ + u_int32_t oldfmt = c->format; + int r; + + r = chn_tryformat(c, fmt); + if (r) { + DEB(printf("Format change %d failed, reverting to %d\n", fmt, oldfmt)); + chn_tryformat(c, oldfmt); + } + return r; +} + +int +chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) +{ + struct snd_dbuf *b = c->bufhard; + struct snd_dbuf *bs = c->bufsoft; + int bufsz, irqhz, tmp, ret; + + CHN_LOCKASSERT(c); + if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED)) + return EINVAL; + + ret = 0; + DEB(printf("%s(%d, %d)\n", __func__, blkcnt, blksz)); + if (blksz == 0 || blksz == -1) { + if (blksz == -1) + c->flags &= ~CHN_F_HAS_SIZE; + if (!(c->flags & CHN_F_HAS_SIZE)) { + blksz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / chn_targetirqrate; + tmp = 32; + while (tmp <= blksz) + tmp <<= 1; + tmp >>= 1; + blksz = tmp; + blkcnt = CHN_2NDBUFMAXSIZE / blksz; + + RANGE(blksz, 16, CHN_2NDBUFMAXSIZE / 2); + RANGE(blkcnt, 2, CHN_2NDBUFMAXSIZE / blksz); + DEB(printf("%s: defaulting to (%d, %d)\n", __func__, blkcnt, blksz)); + } else { + blkcnt = sndbuf_getblkcnt(bs); + blksz = sndbuf_getblksz(bs); + DEB(printf("%s: updating (%d, %d)\n", __func__, blkcnt, blksz)); + } + } else { + ret = EINVAL; + if ((blksz < 16) || (blkcnt < 2) || (blkcnt * blksz > CHN_2NDBUFMAXSIZE)) + goto out; + ret = 0; + c->flags |= CHN_F_HAS_SIZE; + } + + bufsz = blkcnt * blksz; + + ret = sndbuf_remalloc(bs, blkcnt, blksz); + if (ret) + goto out; + + /* adjust for different hw format/speed */ + irqhz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / sndbuf_getblksz(bs); + DEB(printf("%s: soft bps %d, spd %d, irqhz == %d\n", __func__, sndbuf_getbps(bs), sndbuf_getspd(bs), irqhz)); + RANGE(irqhz, 16, 512); + + sndbuf_setblksz(b, (sndbuf_getbps(b) * sndbuf_getspd(b)) / irqhz); + + /* round down to 2^x */ + blksz = 32; + while (blksz <= sndbuf_getblksz(b)) + blksz <<= 1; + blksz >>= 1; + + /* round down to fit hw buffer size */ + RANGE(blksz, 16, sndbuf_getmaxsize(b) / 2); + DEB(printf("%s: hard blksz requested %d (maxsize %d), ", __func__, blksz, sndbuf_getmaxsize(b))); + + sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, blksz)); + + irqhz = (sndbuf_getbps(b) * sndbuf_getspd(b)) / sndbuf_getblksz(b); + DEB(printf("got %d, irqhz == %d\n", sndbuf_getblksz(b), irqhz)); + + chn_resetbuf(c); +out: + return ret; +} + +int +chn_trigger(struct pcm_channel *c, int go) +{ + struct snd_dbuf *b = c->bufhard; + int ret; + + CHN_LOCKASSERT(c); + if (ISA_DMA(b) && (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)) + sndbuf_isadmabounce(b); + ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go); + + return ret; +} + +int +chn_getptr(struct pcm_channel *c) +{ + int hwptr; + int a = (1 << c->align) - 1; + + CHN_LOCKASSERT(c); + hwptr = (c->flags & CHN_F_TRIGGERED)? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; + /* don't allow unaligned values in the hwa ptr */ +#if 1 + hwptr &= ~a ; /* Apply channel align mask */ +#endif + hwptr &= DMA_ALIGN_MASK; /* Apply DMA align mask */ + return hwptr; +} + +struct pcmchan_caps * +chn_getcaps(struct pcm_channel *c) +{ + CHN_LOCKASSERT(c); + return CHANNEL_GETCAPS(c->methods, c->devinfo); +} + +u_int32_t +chn_getformats(struct pcm_channel *c) +{ + u_int32_t *fmtlist, fmts; + int i; + + fmtlist = chn_getcaps(c)->fmtlist; + fmts = 0; + for (i = 0; fmtlist[i]; i++) + fmts |= fmtlist[i]; + + /* report software-supported formats */ + if (report_soft_formats) + fmts |= AFMT_MU_LAW|AFMT_A_LAW|AFMT_U16_LE|AFMT_U16_BE| + AFMT_S16_LE|AFMT_S16_BE|AFMT_U8|AFMT_S8; + + return fmts; +} + +static int +chn_buildfeeder(struct pcm_channel *c) +{ + struct feeder_class *fc; + struct pcm_feederdesc desc; + u_int32_t tmp[2], type, flags, hwfmt; + int err; + + CHN_LOCKASSERT(c); + while (chn_removefeeder(c) == 0); + KASSERT((c->feeder == NULL), ("feeder chain not empty")); + + c->align = sndbuf_getalign(c->bufsoft); + + if (SLIST_EMPTY(&c->children)) { + fc = feeder_getclass(NULL); + KASSERT(fc != NULL, ("can't find root feeder")); + + err = chn_addfeeder(c, fc, NULL); + if (err) { + DEB(printf("can't add root feeder, err %d\n", err)); + + return err; + } + c->feeder->desc->out = c->format; + } else { + desc.type = FEEDER_MIXER; + desc.in = 0; + desc.out = c->format; + desc.flags = 0; + fc = feeder_getclass(&desc); + if (fc == NULL) { + DEB(printf("can't find vchan feeder\n")); + + return EOPNOTSUPP; + } + + err = chn_addfeeder(c, fc, &desc); + if (err) { + DEB(printf("can't add vchan feeder, err %d\n", err)); + + return err; + } + } + flags = c->feederflags; + + DEB(printf("not mapped, feederflags %x\n", flags)); + + for (type = FEEDER_RATE; type <= FEEDER_LAST; type++) { + if (flags & (1 << type)) { + desc.type = type; + desc.in = 0; + desc.out = 0; + desc.flags = 0; + DEB(printf("find feeder type %d, ", type)); + fc = feeder_getclass(&desc); + DEB(printf("got %p\n", fc)); + if (fc == NULL) { + DEB(printf("can't find required feeder type %d\n", type)); + + return EOPNOTSUPP; + } + + if (c->feeder->desc->out != fc->desc->in) { + DEB(printf("build fmtchain from %x to %x: ", c->feeder->desc->out, fc->desc->in)); + tmp[0] = fc->desc->in; + tmp[1] = 0; + if (chn_fmtchain(c, tmp) == 0) { + DEB(printf("failed\n")); + + return ENODEV; + } + DEB(printf("ok\n")); + } + + err = chn_addfeeder(c, fc, fc->desc); + if (err) { + DEB(printf("can't add feeder %p, output %x, err %d\n", fc, fc->desc->out, err)); + + return err; + } + DEB(printf("added feeder %p, output %x\n", fc, c->feeder->desc->out)); + } + } + + if (fmtvalid(c->feeder->desc->out, chn_getcaps(c)->fmtlist)) { + hwfmt = c->feeder->desc->out; + } else { + if (c->direction == PCMDIR_REC) { + tmp[0] = c->format; + tmp[1] = 0; + hwfmt = chn_fmtchain(c, tmp); + } else { +#if 0 + u_int32_t *x = chn_getcaps(c)->fmtlist; + printf("acceptable formats for %s:\n", c->name); + while (*x) { + printf("[%8x] ", *x); + x++; + } +#endif + hwfmt = chn_fmtchain(c, chn_getcaps(c)->fmtlist); + } + } + + if (hwfmt == 0) + return ENODEV; + + sndbuf_setfmt(c->bufhard, hwfmt); + + return 0; +} + +int +chn_notify(struct pcm_channel *c, u_int32_t flags) +{ + struct pcmchan_children *pce; + struct pcm_channel *child; + int run; + + if (SLIST_EMPTY(&c->children)) + return ENODEV; + + run = (c->flags & CHN_F_TRIGGERED)? 1 : 0; + /* + * if the hwchan is running, we can't change its rate, format or + * blocksize + */ + if (run) + flags &= CHN_N_VOLUME | CHN_N_TRIGGER; + + if (flags & CHN_N_RATE) { + /* + * we could do something here, like scan children and decide on + * the most appropriate rate to mix at, but we don't for now + */ + } + if (flags & CHN_N_FORMAT) { + /* + * we could do something here, like scan children and decide on + * the most appropriate mixer feeder to use, but we don't for now + */ + } + if (flags & CHN_N_VOLUME) { + /* + * we could do something here but we don't for now + */ + } + if (flags & CHN_N_BLOCKSIZE) { + int blksz; + /* + * scan the children, find the lowest blocksize and use that + * for the hard blocksize + */ + blksz = sndbuf_getmaxsize(c->bufhard) / 2; + SLIST_FOREACH(pce, &c->children, link) { + child = pce->channel; + if (sndbuf_getblksz(child->bufhard) < blksz) + blksz = sndbuf_getblksz(child->bufhard); + } + chn_setblocksize(c, 2, blksz); + } + if (flags & CHN_N_TRIGGER) { + int nrun; + /* + * scan the children, and figure out if any are running + * if so, we need to be running, otherwise we need to be stopped + * if we aren't in our target sstate, move to it + */ + nrun = 0; + SLIST_FOREACH(pce, &c->children, link) { + child = pce->channel; + if (child->flags & CHN_F_TRIGGERED) + nrun = 1; + } + if (nrun && !run) + chn_start(c, 1); + if (!nrun && run) + chn_abort(c); + } + return 0; +} diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h new file mode 100644 index 0000000..4db60cb --- /dev/null +++ b/sys/dev/sound/pcm/channel.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + * + * $FreeBSD$ + */ + +struct pcmchan_children { + SLIST_ENTRY(pcmchan_children) link; + struct pcm_channel *channel; +}; + +struct pcmchan_caps { + u_int32_t minspeed, maxspeed; + u_int32_t *fmtlist; + u_int32_t caps; +}; + +#define CHN_NAMELEN 32 +struct pcm_channel { + kobj_t methods; + + int num; + pid_t pid; + int refcount; + struct pcm_feeder *feeder; + u_int32_t align; + + int volume; + u_int32_t speed; + u_int32_t format; + u_int32_t flags; + u_int32_t feederflags; + u_int32_t blocks; + + int direction; + unsigned int interrupts, xruns; + struct snd_dbuf *bufhard, *bufsoft; + struct snddev_info *parentsnddev; + struct pcm_channel *parentchannel; + void *devinfo; + device_t dev; + char name[CHN_NAMELEN]; + struct mtx *lock; + SLIST_HEAD(, pcmchan_children) children; +}; + +#include "channel_if.h" + +int chn_reinit(struct pcm_channel *c); +int chn_write(struct pcm_channel *c, struct uio *buf); +int chn_read(struct pcm_channel *c, struct uio *buf); +u_int32_t chn_start(struct pcm_channel *c, int force); +int chn_sync(struct pcm_channel *c, int threshold); +int chn_flush(struct pcm_channel *c); +int chn_poll(struct pcm_channel *c, int ev, struct thread *td); + +int chn_init(struct pcm_channel *c, void *devinfo, int dir); +int chn_kill(struct pcm_channel *c); +int chn_setdir(struct pcm_channel *c, int dir); +int chn_reset(struct pcm_channel *c, u_int32_t fmt); +int chn_setvolume(struct pcm_channel *c, int left, int right); +int chn_setspeed(struct pcm_channel *c, int speed); +int chn_setformat(struct pcm_channel *c, u_int32_t fmt); +int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz); +int chn_trigger(struct pcm_channel *c, int go); +int chn_getptr(struct pcm_channel *c); +struct pcmchan_caps *chn_getcaps(struct pcm_channel *c); +u_int32_t chn_getformats(struct pcm_channel *c); + +void chn_resetbuf(struct pcm_channel *c); +void chn_intr(struct pcm_channel *c); +int chn_wrfeed(struct pcm_channel *c); +int chn_rdfeed(struct pcm_channel *c); +int chn_abort(struct pcm_channel *c); + +void chn_wrupdate(struct pcm_channel *c); +void chn_rdupdate(struct pcm_channel *c); + +int chn_notify(struct pcm_channel *c, u_int32_t flags); + +#ifdef USING_MUTEX +#define CHN_LOCK(c) mtx_lock((struct mtx *)((c)->lock)) +#define CHN_UNLOCK(c) mtx_unlock((struct mtx *)((c)->lock)) +#define CHN_LOCKASSERT(c) +#else +#define CHN_LOCK(c) +#define CHN_UNLOCK(c) +#define CHN_LOCKASSERT(c) +#endif + +int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist); + +#define PCMDIR_VIRTUAL 2 +#define PCMDIR_PLAY 1 +#define PCMDIR_REC -1 + +#define PCMTRIG_START 1 +#define PCMTRIG_EMLDMAWR 2 +#define PCMTRIG_EMLDMARD 3 +#define PCMTRIG_STOP 0 +#define PCMTRIG_ABORT -1 + +#define CHN_F_CLOSING 0x00000004 /* a pending close */ +#define CHN_F_ABORTING 0x00000008 /* a pending abort */ +#define CHN_F_RUNNING 0x00000010 /* dma is running */ +#define CHN_F_TRIGGERED 0x00000020 +#define CHN_F_NOTRIGGER 0x00000040 + +#define CHN_F_BUSY 0x00001000 /* has been opened */ +#define CHN_F_HAS_SIZE 0x00002000 /* user set block size */ +#define CHN_F_NBIO 0x00004000 /* do non-blocking i/o */ +#define CHN_F_MAPPED 0x00010000 /* has been mmap()ed */ +#define CHN_F_DEAD 0x00020000 +#define CHN_F_BADSETTING 0x00040000 + +#define CHN_F_VIRTUAL 0x10000000 /* not backed by hardware */ + +#define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD | CHN_F_VIRTUAL) + +#define CHN_N_RATE 0x00000001 +#define CHN_N_FORMAT 0x00000002 +#define CHN_N_VOLUME 0x00000004 +#define CHN_N_BLOCKSIZE 0x00000008 +#define CHN_N_TRIGGER 0x00000010 + +/* + * This should be large enough to hold all pcm data between + * tsleeps in chn_{read,write} at the highest sample rate. + * (which is usually 48kHz * 16bit * stereo = 192000 bytes/sec) + */ +#define CHN_2NDBUFBLKSIZE (2 * 1024) +/* The total number of blocks per secondary bufhard. */ +#define CHN_2NDBUFBLKNUM (32) +/* The size of a whole secondary bufhard. */ +#define CHN_2NDBUFMAXSIZE (131072) + +#define CHANNEL_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, sizeof(struct kobj)) diff --git a/sys/dev/sound/pcm/channel_if.m b/sys/dev/sound/pcm/channel_if.m new file mode 100644 index 0000000..9d8e289 --- /dev/null +++ b/sys/dev/sound/pcm/channel_if.m @@ -0,0 +1,141 @@ +# KOBJ +# +# Copyright (c) 2000 Cameron Grant <cg@freebsd.org> +# 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. +# +# $FreeBSD$ +# + +#include <dev/sound/pcm/sound.h> + +INTERFACE channel; + +CODE { + + static int + channel_nosetdir(kobj_t obj, void *data, int dir) + { + return 0; + } + + static int + channel_noreset(kobj_t obj, void *data) + { + return 0; + } + + static int + channel_noresetdone(kobj_t obj, void *data) + { + return 0; + } + + static int + channel_nofree(kobj_t obj, void *data) + { + return 1; + } + + static u_int32_t + channel_nogetptr(kobj_t obj, void *data) + { + return 0; + } + + static int + channel_nonotify(kobj_t obj, void *data, u_int32_t changed) + { + return 0; + } + +}; + +METHOD void* init { + kobj_t obj; + void *devinfo; + struct snd_dbuf *b; + struct pcm_channel *c; + int dir; +}; + +METHOD int free { + kobj_t obj; + void *data; +} DEFAULT channel_nofree; + +METHOD int reset { + kobj_t obj; + void *data; +} DEFAULT channel_noreset; + +METHOD int resetdone { + kobj_t obj; + void *data; +} DEFAULT channel_noresetdone; + +METHOD int setdir { + kobj_t obj; + void *data; + int dir; +} DEFAULT channel_nosetdir; + +METHOD u_int32_t setformat { + kobj_t obj; + void *data; + u_int32_t format; +}; + +METHOD u_int32_t setspeed { + kobj_t obj; + void *data; + u_int32_t speed; +}; + +METHOD u_int32_t setblocksize { + kobj_t obj; + void *data; + u_int32_t blocksize; +}; + +METHOD int trigger { + kobj_t obj; + void *data; + int go; +}; + +METHOD u_int32_t getptr { + kobj_t obj; + void *data; +} DEFAULT channel_nogetptr; + +METHOD struct pcmchan_caps* getcaps { + kobj_t obj; + void *data; +}; + +METHOD int notify { + kobj_t obj; + void *data; + u_int32_t changed; +} DEFAULT channel_nonotify; diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c new file mode 100644 index 0000000..7baa442 --- /dev/null +++ b/sys/dev/sound/pcm/dsp.c @@ -0,0 +1,1147 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + */ + +#include <sys/param.h> +#include <sys/queue.h> + +#include <dev/sound/pcm/sound.h> + +SND_DECLARE_FILE("$FreeBSD$"); + +#define OLDPCM_IOCTL + +static d_open_t dsp_open; +static d_close_t dsp_close; +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 struct cdevsw dsp_cdevsw = { + /* open */ dsp_open, + /* close */ dsp_close, + /* read */ dsp_read, + /* write */ dsp_write, + /* ioctl */ dsp_ioctl, + /* poll */ dsp_poll, + /* mmap */ dsp_mmap, + /* strategy */ nostrategy, + /* name */ "dsp", + /* maj */ SND_CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + +#ifdef USING_DEVFS +static eventhandler_tag dsp_ehtag; +#endif + +static struct snddev_info * +dsp_get_info(dev_t dev) +{ + struct snddev_info *d; + int unit; + + unit = PCMUNIT(dev); + if (unit >= devclass_get_maxunit(pcm_devclass)) + return NULL; + d = devclass_get_softc(pcm_devclass, unit); + + return d; +} + +static u_int32_t +dsp_get_flags(dev_t dev) +{ + device_t bdev; + int unit; + + unit = PCMUNIT(dev); + if (unit >= devclass_get_maxunit(pcm_devclass)) + return 0xffffffff; + bdev = devclass_get_device(pcm_devclass, unit); + + return pcm_getflags(bdev); +} + +static void +dsp_set_flags(dev_t dev, u_int32_t flags) +{ + device_t bdev; + int unit; + + unit = PCMUNIT(dev); + if (unit >= devclass_get_maxunit(pcm_devclass)) + return; + bdev = devclass_get_device(pcm_devclass, unit); + + pcm_setflags(bdev, flags); +} + +/* + * return the channels channels associated with an open device instance. + * set the priority if the device is simplex and one direction (only) is + * specified. + * lock channels specified. + */ +static int +getchns(dev_t dev, struct pcm_channel **rdch, struct pcm_channel **wrch, u_int32_t prio) +{ + struct snddev_info *d; + u_int32_t flags; + + flags = dsp_get_flags(dev); + d = dsp_get_info(dev); + pcm_lock(d); + pcm_inprog(d, 1); + KASSERT((flags & SD_F_PRIO_SET) != SD_F_PRIO_SET, \ + ("getchns: read and write both prioritised")); + + if ((flags & SD_F_PRIO_SET) == 0 && (prio != (SD_F_PRIO_RD | SD_F_PRIO_WR))) { + flags |= prio & (SD_F_PRIO_RD | SD_F_PRIO_WR); + dsp_set_flags(dev, flags); + } + + *rdch = dev->si_drv1; + *wrch = dev->si_drv2; + if ((flags & SD_F_SIMPLEX) && (flags & SD_F_PRIO_SET)) { + if (prio) { + if (*rdch && flags & SD_F_PRIO_WR) { + dev->si_drv1 = NULL; + *rdch = pcm_getfakechan(d); + } else if (*wrch && flags & SD_F_PRIO_RD) { + dev->si_drv2 = NULL; + *wrch = pcm_getfakechan(d); + } + } + + pcm_getfakechan(d)->flags |= CHN_F_BUSY; + } + pcm_unlock(d); + + if (*rdch && *rdch != pcm_getfakechan(d) && (prio & SD_F_PRIO_RD)) + CHN_LOCK(*rdch); + if (*wrch && *wrch != pcm_getfakechan(d) && (prio & SD_F_PRIO_WR)) + CHN_LOCK(*wrch); + + return 0; +} + +/* unlock specified channels */ +static void +relchns(dev_t dev, struct pcm_channel *rdch, struct pcm_channel *wrch, u_int32_t prio) +{ + struct snddev_info *d; + + d = dsp_get_info(dev); + if (wrch && wrch != pcm_getfakechan(d) && (prio & SD_F_PRIO_WR)) + CHN_UNLOCK(wrch); + if (rdch && rdch != pcm_getfakechan(d) && (prio & SD_F_PRIO_RD)) + CHN_UNLOCK(rdch); + pcm_lock(d); + pcm_inprog(d, -1); + pcm_unlock(d); +} + +static int +dsp_open(dev_t i_dev, int flags, int mode, struct thread *td) +{ + struct pcm_channel *rdch, *wrch; + struct snddev_info *d; + intrmask_t s; + u_int32_t fmt; + int devtype; + + s = spltty(); + d = dsp_get_info(i_dev); + devtype = PCMDEV(i_dev); + + /* decide default format */ + switch (devtype) { + case SND_DEV_DSP16: + fmt = AFMT_S16_LE; + break; + + case SND_DEV_DSP: + fmt = AFMT_U8; + break; + + case SND_DEV_AUDIO: + fmt = AFMT_MU_LAW; + break; + + case SND_DEV_NORESET: + fmt = 0; + break; + + case SND_DEV_DSPREC: + fmt = AFMT_U8; + if (mode & FWRITE) { + splx(s); + return EINVAL; + } + break; + + default: + panic("impossible devtype %d", devtype); + } + + /* lock snddev so nobody else can monkey with it */ + pcm_lock(d); + + rdch = i_dev->si_drv1; + wrch = i_dev->si_drv2; + + if ((dsp_get_flags(i_dev) & SD_F_SIMPLEX) && (rdch || wrch)) { + /* simplex device, already open, exit */ + pcm_unlock(d); + splx(s); + return EBUSY; + } + + if (((flags & FREAD) && rdch) || ((flags & FWRITE) && wrch)) { + /* device already open in one or both directions */ + pcm_unlock(d); + splx(s); + return EBUSY; + } + + /* if we get here, the open request is valid */ + if (flags & FREAD) { + /* open for read */ + if (devtype == SND_DEV_DSPREC) + rdch = pcm_chnalloc(d, PCMDIR_REC, td->td_proc->p_pid, PCMCHAN(i_dev)); + else + rdch = pcm_chnalloc(d, PCMDIR_REC, td->td_proc->p_pid, -1); + if (!rdch) { + /* no channel available, exit */ + pcm_unlock(d); + splx(s); + return EBUSY; + } + /* got a channel, already locked for us */ + } + + if (flags & FWRITE) { + /* open for write */ + wrch = pcm_chnalloc(d, PCMDIR_PLAY, td->td_proc->p_pid, -1); + if (!wrch) { + /* no channel available */ + if (flags & FREAD) { + /* just opened a read channel, release it */ + pcm_chnrelease(rdch); + } + /* exit */ + pcm_unlock(d); + splx(s); + return EBUSY; + } + /* got a channel, already locked for us */ + } + + i_dev->si_drv1 = rdch; + i_dev->si_drv2 = wrch; + pcm_unlock(d); + /* finished with snddev, new channels still locked */ + + /* bump refcounts, reset and unlock any channels that we just opened */ + if (flags & FREAD) { + if (chn_reset(rdch, fmt)) { + pcm_lock(d); + pcm_chnrelease(rdch); + if (wrch && (flags & FWRITE)) + pcm_chnrelease(wrch); + pcm_unlock(d); + splx(s); + return ENODEV; + } + if (flags & O_NONBLOCK) + rdch->flags |= CHN_F_NBIO; + pcm_chnref(rdch, 1); + CHN_UNLOCK(rdch); + } + if (flags & FWRITE) { + if (chn_reset(wrch, fmt)) { + pcm_lock(d); + pcm_chnrelease(wrch); + if (flags & FREAD) { + CHN_LOCK(rdch); + pcm_chnref(rdch, -1); + pcm_chnrelease(rdch); + CHN_UNLOCK(rdch); + } + pcm_unlock(d); + splx(s); + return ENODEV; + } + if (flags & O_NONBLOCK) + wrch->flags |= CHN_F_NBIO; + pcm_chnref(wrch, 1); + CHN_UNLOCK(wrch); + } + splx(s); + return 0; +} + +static int +dsp_close(dev_t i_dev, int flags, int mode, struct thread *td) +{ + struct pcm_channel *rdch, *wrch; + struct snddev_info *d; + intrmask_t s; + int exit; + + s = spltty(); + d = dsp_get_info(i_dev); + pcm_lock(d); + rdch = i_dev->si_drv1; + wrch = i_dev->si_drv2; + + exit = 0; + + /* decrement refcount for each channel, exit if nonzero */ + if (rdch) { + CHN_LOCK(rdch); + if (pcm_chnref(rdch, -1) > 0) { + CHN_UNLOCK(rdch); + exit = 1; + } + } + if (wrch) { + CHN_LOCK(wrch); + if (pcm_chnref(wrch, -1) > 0) { + CHN_UNLOCK(wrch); + exit = 1; + } + } + if (exit) { + pcm_unlock(d); + splx(s); + return 0; + } + + /* both refcounts are zero, abort and release */ + + if (pcm_getfakechan(d)) + pcm_getfakechan(d)->flags = 0; + + i_dev->si_drv1 = NULL; + i_dev->si_drv2 = NULL; + + dsp_set_flags(i_dev, dsp_get_flags(i_dev) & ~SD_F_TRANSIENT); + pcm_unlock(d); + + if (rdch) { + chn_abort(rdch); /* won't sleep */ + rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD); + chn_reset(rdch, 0); + pcm_chnrelease(rdch); + } + if (wrch) { + chn_flush(wrch); /* may sleep */ + wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD); + chn_reset(wrch, 0); + pcm_chnrelease(wrch); + } + + splx(s); + return 0; +} + +static int +dsp_read(dev_t i_dev, struct uio *buf, int flag) +{ + struct pcm_channel *rdch, *wrch; + intrmask_t s; + int ret; + + s = spltty(); + getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD); + + KASSERT(rdch, ("dsp_read: nonexistant channel")); + KASSERT(rdch->flags & CHN_F_BUSY, ("dsp_read: nonbusy channel")); + + if (rdch->flags & (CHN_F_MAPPED | CHN_F_DEAD)) { + relchns(i_dev, rdch, wrch, SD_F_PRIO_RD); + splx(s); + return EINVAL; + } + if (!(rdch->flags & CHN_F_RUNNING)) + rdch->flags |= CHN_F_RUNNING; + ret = chn_read(rdch, buf); + relchns(i_dev, rdch, wrch, SD_F_PRIO_RD); + + splx(s); + return ret; +} + +static int +dsp_write(dev_t i_dev, struct uio *buf, int flag) +{ + struct pcm_channel *rdch, *wrch; + intrmask_t s; + int ret; + + s = spltty(); + getchns(i_dev, &rdch, &wrch, SD_F_PRIO_WR); + + KASSERT(wrch, ("dsp_write: nonexistant channel")); + KASSERT(wrch->flags & CHN_F_BUSY, ("dsp_write: nonbusy channel")); + + if (wrch->flags & (CHN_F_MAPPED | CHN_F_DEAD)) { + relchns(i_dev, rdch, wrch, SD_F_PRIO_WR); + splx(s); + return EINVAL; + } + if (!(wrch->flags & CHN_F_RUNNING)) + wrch->flags |= CHN_F_RUNNING; + ret = chn_write(wrch, buf); + relchns(i_dev, rdch, wrch, SD_F_PRIO_WR); + + splx(s); + return ret; +} + +static int +dsp_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +{ + struct pcm_channel *wrch, *rdch; + struct snddev_info *d; + intrmask_t s; + int kill; + int ret = 0, *arg_i = (int *)arg, tmp; + + /* + * this is an evil hack to allow broken apps to perform mixer ioctls + * on dsp devices. + */ + + if (IOCGROUP(cmd) == 'M') { + dev_t pdev; + + pdev = makedev(SND_CDEV_MAJOR, PCMMKMINOR(PCMUNIT(i_dev), SND_DEV_CTL, 0)); + return mixer_ioctl(pdev, cmd, arg, mode, td); + } + + s = spltty(); + d = dsp_get_info(i_dev); + getchns(i_dev, &rdch, &wrch, 0); + + kill = 0; + if (wrch && (wrch->flags & CHN_F_DEAD)) + kill |= 1; + if (rdch && (rdch->flags & CHN_F_DEAD)) + kill |= 2; + if (kill == 3) { + relchns(i_dev, rdch, wrch, 0); + splx(s); + return EINVAL; + } + if (kill & 1) + wrch = NULL; + if (kill & 2) + rdch = NULL; + + switch(cmd) { +#ifdef OLDPCM_IOCTL + /* + * we start with the new ioctl interface. + */ + case AIONWRITE: /* how many bytes can write ? */ +/* + if (wrch && wrch->bufhard.dl) + while (chn_wrfeed(wrch) == 0); +*/ + *arg_i = wrch? sndbuf_getfree(wrch->bufsoft) : 0; + break; + + case AIOSSIZE: /* set the current blocksize */ + { + struct snd_size *p = (struct snd_size *)arg; + + p->play_size = 0; + p->rec_size = 0; + 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); + } + } + break; + case AIOGSIZE: /* get the current blocksize */ + { + struct snd_size *p = (struct snd_size *)arg; + + if (wrch) + p->play_size = sndbuf_getblksz(wrch->bufsoft); + if (rdch) + p->rec_size = sndbuf_getblksz(rdch->bufsoft); + } + break; + + case AIOSFMT: + { + snd_chan_param *p = (snd_chan_param *)arg; + + if (wrch) { + CHN_LOCK(wrch); + chn_setformat(wrch, p->play_format); + chn_setspeed(wrch, p->play_rate); + CHN_UNLOCK(wrch); + } + if (rdch) { + CHN_LOCK(rdch); + chn_setformat(rdch, p->rec_format); + chn_setspeed(rdch, p->rec_rate); + CHN_UNLOCK(rdch); + } + } + /* FALLTHROUGH */ + + case AIOGFMT: + { + snd_chan_param *p = (snd_chan_param *)arg; + + p->play_rate = wrch? wrch->speed : 0; + p->rec_rate = rdch? rdch->speed : 0; + p->play_format = wrch? wrch->format : 0; + p->rec_format = rdch? rdch->format : 0; + } + break; + + case AIOGCAP: /* get capabilities */ + { + snd_capabilities *p = (snd_capabilities *)arg; + struct pcmchan_caps *pcaps = NULL, *rcaps = NULL; + dev_t pdev; + + 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 |= (dsp_get_flags(i_dev) & SD_F_SIMPLEX)? 0 : AFMT_FULLDUPLEX; + pdev = makedev(SND_CDEV_MAJOR, PCMMKMINOR(PCMUNIT(i_dev), SND_DEV_CTL, 0)); + 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); + } + break; + + case AIOSTOP: + if (*arg_i == AIOSYNC_PLAY && wrch) + *arg_i = chn_abort(wrch); + else if (*arg_i == AIOSYNC_CAPTURE && rdch) + *arg_i = chn_abort(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 && rdch->bufhard.dl) + while (chn_rdfeed(rdch) == 0); +*/ *arg_i = rdch? sndbuf_getready(rdch->bufsoft) : 0; + break; + + case FIOASYNC: /*set/clear async i/o */ + DEB( printf("FIOASYNC\n") ; ) + break; + + case SNDCTL_DSP_NONBLOCK: + case FIONBIO: /* set/clear non-blocking i/o */ + if (rdch) + rdch->flags &= ~CHN_F_NBIO; + if (wrch) + wrch->flags &= ~CHN_F_NBIO; + if (*arg_i) { + if (rdch) + rdch->flags |= CHN_F_NBIO; + if (wrch) + wrch->flags |= CHN_F_NBIO; + } + 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: + if (wrch) + *arg_i = sndbuf_getblksz(wrch->bufsoft); + else if (rdch) + *arg_i = sndbuf_getblksz(rdch->bufsoft); + else + *arg_i = 0; + break ; + + case SNDCTL_DSP_SETBLKSIZE: + RANGE(*arg_i, 16, 65536); + 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); + } + break; + + case SNDCTL_DSP_RESET: + DEB(printf("dsp reset\n")); + if (wrch) + chn_abort(wrch); + if (rdch) + chn_abort(rdch); + break; + + case SNDCTL_DSP_SYNC: + DEB(printf("dsp sync\n")); + /* chn_sync may sleep */ + if (wrch) { + CHN_LOCK(wrch); + chn_sync(wrch, sndbuf_getsize(wrch->bufsoft) - 4); + CHN_UNLOCK(wrch); + } + break; + + case SNDCTL_DSP_SPEED: + /* chn_setspeed may sleep */ + tmp = 0; + 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); + } + *arg_i = tmp; + break; + + case SOUND_PCM_READ_RATE: + *arg_i = wrch? wrch->speed : rdch->speed; + break; + + case SNDCTL_DSP_STEREO: + tmp = -1; + *arg_i = (*arg_i)? AFMT_STEREO : 0; + if (wrch) { + CHN_LOCK(wrch); + ret = chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | *arg_i); + tmp = (wrch->format & AFMT_STEREO)? 1 : 0; + CHN_UNLOCK(wrch); + } + if (rdch && ret == 0) { + CHN_LOCK(rdch); + ret = chn_setformat(rdch, (rdch->format & ~AFMT_STEREO) | *arg_i); + if (tmp == -1) + tmp = (rdch->format & AFMT_STEREO)? 1 : 0; + CHN_UNLOCK(rdch); + } + *arg_i = tmp; + break; + + case SOUND_PCM_WRITE_CHANNELS: +/* case SNDCTL_DSP_CHANNELS: ( == SOUND_PCM_WRITE_CHANNELS) */ + if (*arg_i != 0) { + tmp = 0; + *arg_i = (*arg_i != 1)? AFMT_STEREO : 0; + if (wrch) { + CHN_LOCK(wrch); + ret = chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | *arg_i); + tmp = (wrch->format & AFMT_STEREO)? 2 : 1; + CHN_UNLOCK(wrch); + } + if (rdch && ret == 0) { + CHN_LOCK(rdch); + ret = chn_setformat(rdch, (rdch->format & ~AFMT_STEREO) | *arg_i); + if (tmp == 0) + tmp = (rdch->format & AFMT_STEREO)? 2 : 1; + CHN_UNLOCK(rdch); + } + *arg_i = tmp; + } else { + *arg_i = ((wrch? wrch->format : rdch->format) & AFMT_STEREO)? 2 : 1; + } + break; + + case SOUND_PCM_READ_CHANNELS: + *arg_i = ((wrch? wrch->format : rdch->format) & AFMT_STEREO)? 2 : 1; + break; + + case SNDCTL_DSP_GETFMTS: /* returns a mask of supported fmts */ + *arg_i = wrch? chn_getformats(wrch) : chn_getformats(rdch); + break ; + + case SNDCTL_DSP_SETFMT: /* sets _one_ format */ + /* XXX locking */ + if ((*arg_i != AFMT_QUERY)) { + tmp = 0; + if (wrch) { + CHN_LOCK(wrch); + ret = chn_setformat(wrch, (*arg_i) | (wrch->format & AFMT_STEREO)); + tmp = wrch->format & ~AFMT_STEREO; + CHN_UNLOCK(wrch); + } + if (rdch && ret == 0) { + CHN_LOCK(rdch); + ret = chn_setformat(rdch, (*arg_i) | (rdch->format & AFMT_STEREO)); + if (tmp == 0) + tmp = rdch->format & ~AFMT_STEREO; + CHN_UNLOCK(rdch); + } + *arg_i = tmp; + } else + *arg_i = (wrch? wrch->format : rdch->format) & ~AFMT_STEREO; + break; + + case SNDCTL_DSP_SETFRAGMENT: + /* XXX locking */ + DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg)); + { + u_int32_t fragln = (*arg_i) & 0x0000ffff; + u_int32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16; + u_int32_t fragsz; + + RANGE(fragln, 4, 16); + fragsz = 1 << fragln; + + if (maxfrags == 0) + maxfrags = CHN_2NDBUFMAXSIZE / fragsz; + if (maxfrags < 2) { + ret = EINVAL; + break; + } + if (maxfrags * fragsz > CHN_2NDBUFMAXSIZE) + maxfrags = CHN_2NDBUFMAXSIZE / fragsz; + + DEB(printf("SNDCTL_DSP_SETFRAGMENT %d frags, %d sz\n", maxfrags, fragsz)); + if (rdch) { + CHN_LOCK(rdch); + ret = chn_setblocksize(rdch, maxfrags, fragsz); + maxfrags = sndbuf_getblkcnt(rdch->bufsoft); + fragsz = sndbuf_getblksz(rdch->bufsoft); + CHN_UNLOCK(rdch); + } + 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); + } + + 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); + } + } + 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); + 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); + } + } + break; + + case SNDCTL_DSP_GETIPTR: + { + count_info *a = (count_info *)arg; + if (rdch) { + struct snd_dbuf *bs = rdch->bufsoft; + + CHN_LOCK(rdch); + chn_rdupdate(rdch); + a->bytes = sndbuf_gettotal(bs); + a->blocks = sndbuf_getblocks(bs) - rdch->blocks; + a->ptr = sndbuf_getreadyptr(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); + 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: + *arg_i = DSP_CAP_REALTIME | DSP_CAP_MMAP | DSP_CAP_TRIGGER; + if (rdch && wrch && !(dsp_get_flags(i_dev) & SD_F_SIMPLEX)) + *arg_i |= DSP_CAP_DUPLEX; + break; + + case SOUND_PCM_READ_BITS: + *arg_i = ((wrch? wrch->format : rdch->format) & AFMT_16BIT)? 16 : 8; + break; + + case SNDCTL_DSP_SETTRIGGER: + if (rdch) { + CHN_LOCK(rdch); + rdch->flags &= ~(CHN_F_TRIGGERED | CHN_F_NOTRIGGER); + if (*arg_i & PCM_ENABLE_INPUT) + chn_start(rdch, 1); + else + rdch->flags |= CHN_F_NOTRIGGER; + CHN_UNLOCK(rdch); + } + if (wrch) { + CHN_LOCK(wrch); + wrch->flags &= ~(CHN_F_TRIGGERED | CHN_F_NOTRIGGER); + if (*arg_i & PCM_ENABLE_OUTPUT) + chn_start(wrch, 1); + else + wrch->flags |= CHN_F_NOTRIGGER; + CHN_UNLOCK(wrch); + } + break; + + case SNDCTL_DSP_GETTRIGGER: + *arg_i = 0; + if (wrch && wrch->flags & CHN_F_TRIGGERED) + *arg_i |= PCM_ENABLE_OUTPUT; + if (rdch && rdch->flags & CHN_F_TRIGGERED) + *arg_i |= PCM_ENABLE_INPUT; + break; + + case SNDCTL_DSP_GETODELAY: + if (wrch) { + struct snd_dbuf *b = wrch->bufhard; + struct snd_dbuf *bs = wrch->bufsoft; + + CHN_LOCK(wrch); + chn_wrupdate(wrch); + *arg_i = sndbuf_getready(b) + 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_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; + } + relchns(i_dev, rdch, wrch, 0); + splx(s); + return ret; +} + +static int +dsp_poll(dev_t i_dev, int events, struct thread *td) +{ + struct pcm_channel *wrch = NULL, *rdch = NULL; + intrmask_t s; + int ret, e; + + s = spltty(); + ret = 0; + getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); + + if (wrch) { + e = (events & (POLLOUT | POLLWRNORM)); + if (e) + ret |= chn_poll(wrch, e, td); + } + if (rdch) { + e = (events & (POLLIN | POLLRDNORM)); + if (e) + ret |= chn_poll(rdch, e, td); + } + relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); + + splx(s); + return ret; +} + +static int +dsp_mmap(dev_t i_dev, vm_offset_t offset, int nprot) +{ + struct pcm_channel *wrch = NULL, *rdch = NULL, *c; + intrmask_t s; + int ret; + + if (nprot & PROT_EXEC) + return -1; + + s = spltty(); + getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); +#if 0 + /* + * XXX the linux api uses the nprot to select read/write buffer + * our vm system doesn't allow this, so force write buffer + */ + + if (wrch && (nprot & PROT_WRITE)) { + c = wrch; + } else if (rdch && (nprot & PROT_READ)) { + c = rdch; + } else { + splx(s); + return -1; + } +#else + c = wrch; +#endif + + if (c == NULL) { + relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); + splx(s); + return -1; + } + + if (offset >= sndbuf_getsize(c->bufsoft)) { + relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); + splx(s); + return -1; + } + + if (!(c->flags & CHN_F_MAPPED)) + c->flags |= CHN_F_MAPPED; + + ret = atop(vtophys(sndbuf_getbufofs(c->bufsoft, offset))); + relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); + + splx(s); + return ret; +} + +int +dsp_register(int unit, int channel) +{ + make_dev(&dsp_cdevsw, PCMMKMINOR(unit, SND_DEV_DSP, channel), + UID_ROOT, GID_WHEEL, 0666, "dsp%d.%d", unit, channel); + make_dev(&dsp_cdevsw, PCMMKMINOR(unit, SND_DEV_DSP16, channel), + UID_ROOT, GID_WHEEL, 0666, "dspW%d.%d", unit, channel); + make_dev(&dsp_cdevsw, PCMMKMINOR(unit, SND_DEV_AUDIO, channel), + UID_ROOT, GID_WHEEL, 0666, "audio%d.%d", unit, channel); + + return 0; +} + +int +dsp_registerrec(int unit, int channel) +{ + make_dev(&dsp_cdevsw, PCMMKMINOR(unit, SND_DEV_DSPREC, channel), + UID_ROOT, GID_WHEEL, 0666, "dspr%d.%d", unit, channel); + + return 0; +} + +int +dsp_unregister(int unit, int channel) +{ + dev_t pdev; + + pdev = makedev(SND_CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP, channel)); + destroy_dev(pdev); + pdev = makedev(SND_CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP16, channel)); + destroy_dev(pdev); + pdev = makedev(SND_CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_AUDIO, channel)); + destroy_dev(pdev); + + return 0; +} + +int +dsp_unregisterrec(int unit, int channel) +{ + dev_t pdev; + + pdev = makedev(SND_CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSPREC, channel)); + destroy_dev(pdev); + + return 0; +} + +#ifdef USING_DEVFS +static void +dsp_clone(void *arg, char *name, int namelen, dev_t *dev) +{ + dev_t pdev; + int i, cont, unit, devtype; + int devtypes[3] = {SND_DEV_DSP, SND_DEV_DSP16, SND_DEV_AUDIO}; + char *devnames[3] = {"dsp", "dspW", "audio"}; + + if (*dev != NODEV) + return; + if (pcm_devclass == NULL) + return; + + devtype = 0; + unit = -1; + for (i = 0; (i < 3) && (unit == -1); i++) { + devtype = devtypes[i]; + if (strcmp(name, devnames[i]) == 0) { + unit = snd_unit; + } else { + if (dev_stdclone(name, NULL, devnames[i], &unit) != 1) + unit = -1; + } + } + if (unit == -1 || unit >= devclass_get_maxunit(pcm_devclass)) + return; + + cont = 1; + for (i = 0; cont; i++) { + pdev = makedev(SND_CDEV_MAJOR, PCMMKMINOR(unit, devtype, i)); + if (pdev->si_flags & SI_NAMED) { + if ((pdev->si_drv1 == NULL) && (pdev->si_drv2 == NULL)) { + *dev = pdev; + return; + } + } else { + cont = 0; + } + } +} + +static void +dsp_sysinit(void *p) +{ + dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000); +} + +static void +dsp_sysuninit(void *p) +{ + if (dsp_ehtag != NULL) + EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); +} + +SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL); +SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL); +#endif + + diff --git a/sys/dev/sound/pcm/dsp.h b/sys/dev/sound/pcm/dsp.h new file mode 100644 index 0000000..7943bec --- /dev/null +++ b/sys/dev/sound/pcm/dsp.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + * + * $FreeBSD$ + */ + +int dsp_register(int unit, int channel); +int dsp_registerrec(int unit, int channel); +int dsp_unregister(int unit, int channel); +int dsp_unregisterrec(int unit, int channel); diff --git a/sys/dev/sound/pcm/fake.c b/sys/dev/sound/pcm/fake.c new file mode 100644 index 0000000..8c4ba77 --- /dev/null +++ b/sys/dev/sound/pcm/fake.c @@ -0,0 +1,137 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + */ + +#include <dev/sound/pcm/sound.h> + +SND_DECLARE_FILE("$FreeBSD$"); + +static u_int32_t fk_fmt[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + AFMT_S8, + AFMT_STEREO | AFMT_S8, + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + AFMT_U16_LE, + AFMT_STEREO | AFMT_U16_LE, + AFMT_S16_BE, + AFMT_STEREO | AFMT_S16_BE, + AFMT_U16_BE, + AFMT_STEREO | AFMT_U16_BE, + 0 +}; +static struct pcmchan_caps fk_caps = {0, 1000000, fk_fmt, 0}; + +#define FKBUFSZ 4096 +static char fakebuf[FKBUFSZ]; + +/* channel interface */ +static void * +fkchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + sndbuf_setup(b, fakebuf, FKBUFSZ); + return (void *)0xbabef00d; +} + +static int +fkchan_free(kobj_t obj, void *data) +{ + return 0; +} + +static int +fkchan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + return 0; +} + +static int +fkchan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + return speed; +} + +static int +fkchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + return blocksize; +} + +static int +fkchan_trigger(kobj_t obj, void *data, int go) +{ + return 0; +} + +static int +fkchan_getptr(kobj_t obj, void *data) +{ + return 0; +} + +static struct pcmchan_caps * +fkchan_getcaps(kobj_t obj, void *data) +{ + return &fk_caps; +} + +static kobj_method_t fkchan_methods[] = { + KOBJMETHOD(channel_init, fkchan_init), + KOBJMETHOD(channel_free, fkchan_free), + KOBJMETHOD(channel_setformat, fkchan_setformat), + KOBJMETHOD(channel_setspeed, fkchan_setspeed), + KOBJMETHOD(channel_setblocksize, fkchan_setblocksize), + KOBJMETHOD(channel_trigger, fkchan_trigger), + KOBJMETHOD(channel_getptr, fkchan_getptr), + KOBJMETHOD(channel_getcaps, fkchan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(fkchan); + +struct pcm_channel * +fkchan_setup(device_t dev) +{ + struct snddev_info *d = device_get_softc(dev); + struct pcm_channel *c; + + c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK); + c->methods = kobj_create(&fkchan_class, M_DEVBUF, M_WAITOK); + c->parentsnddev = d; + snprintf(c->name, CHN_NAMELEN, "%s:fake", device_get_nameunit(dev)); + + return c; +} + +int +fkchan_kill(struct pcm_channel *c) +{ + kobj_delete(c->methods, M_DEVBUF); + c->methods = NULL; + free(c, M_DEVBUF); + return 0; +} + + diff --git a/sys/dev/sound/pcm/feeder.c b/sys/dev/sound/pcm/feeder.c new file mode 100644 index 0000000..424da4d --- /dev/null +++ b/sys/dev/sound/pcm/feeder.c @@ -0,0 +1,421 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + */ + +#include <dev/sound/pcm/sound.h> + +#include "feeder_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +MALLOC_DEFINE(M_FEEDER, "feeder", "pcm feeder"); + +#define MAXFEEDERS 256 +#undef FEEDER_DEBUG + +struct feedertab_entry { + SLIST_ENTRY(feedertab_entry) link; + struct feeder_class *feederclass; + struct pcm_feederdesc *desc; + + int idx; +}; +static SLIST_HEAD(, feedertab_entry) feedertab; + +/*****************************************************************************/ + +void +feeder_register(void *p) +{ + static int feedercnt = 0; + + struct feeder_class *fc = p; + struct feedertab_entry *fte; + int i; + + if (feedercnt == 0) { + KASSERT(fc->desc == NULL, ("first feeder not root: %s", fc->name)); + + SLIST_INIT(&feedertab); + fte = malloc(sizeof(*fte), M_FEEDER, M_WAITOK | M_ZERO); + if (fte == NULL) { + printf("can't allocate memory for root feeder: %s\n", + fc->name); + + return; + } + fte->feederclass = fc; + fte->desc = NULL; + fte->idx = feedercnt; + SLIST_INSERT_HEAD(&feedertab, fte, link); + feedercnt++; + + /* we've got our root feeder so don't veto pcm loading anymore */ + pcm_veto_load = 0; + + return; + } + + KASSERT(fc->desc != NULL, ("feeder '%s' has no descriptor", fc->name)); + + /* beyond this point failure is non-fatal but may result in some translations being unavailable */ + i = 0; + while ((feedercnt < MAXFEEDERS) && (fc->desc[i].type > 0)) { + /* printf("adding feeder %s, %x -> %x\n", fc->name, fc->desc[i].in, fc->desc[i].out); */ + fte = malloc(sizeof(*fte), M_FEEDER, M_WAITOK | M_ZERO); + if (fte == NULL) { + printf("can't allocate memory for feeder '%s', %x -> %x\n", fc->name, fc->desc[i].in, fc->desc[i].out); + + return; + } + fte->feederclass = fc; + fte->desc = &fc->desc[i]; + fte->idx = feedercnt; + fte->desc->idx = feedercnt; + SLIST_INSERT_HEAD(&feedertab, fte, link); + i++; + } + feedercnt++; + if (feedercnt >= MAXFEEDERS) + printf("MAXFEEDERS (%d >= %d) exceeded\n", feedercnt, MAXFEEDERS); +} + +static void +feeder_unregisterall(void *p) +{ + struct feedertab_entry *fte, *next; + + next = SLIST_FIRST(&feedertab); + while (next != NULL) { + fte = next; + next = SLIST_NEXT(fte, link); + free(fte, M_FEEDER); + } +} + +static int +cmpdesc(struct pcm_feederdesc *n, struct pcm_feederdesc *m) +{ + return ((n->type == m->type) && + ((n->in == 0) || (n->in == m->in)) && + ((n->out == 0) || (n->out == m->out)) && + (n->flags == m->flags)); +} + +static void +feeder_destroy(struct pcm_feeder *f) +{ + FEEDER_FREE(f); + kobj_delete((kobj_t)f, M_FEEDER); +} + +static struct pcm_feeder * +feeder_create(struct feeder_class *fc, struct pcm_feederdesc *desc) +{ + struct pcm_feeder *f; + int err; + + f = (struct pcm_feeder *)kobj_create((kobj_class_t)fc, M_FEEDER, M_NOWAIT | M_ZERO); + if (f == NULL) + return NULL; + + f->align = fc->align; + f->data = fc->data; + f->source = NULL; + f->parent = NULL; + f->class = fc; + f->desc = &(f->desc_static); + + if (desc) { + *(f->desc) = *desc; + } else { + f->desc->type = FEEDER_ROOT; + f->desc->in = 0; + f->desc->out = 0; + f->desc->flags = 0; + f->desc->idx = 0; + } + + err = FEEDER_INIT(f); + if (err) { + printf("feeder_init(%p) on %s returned %d\n", f, fc->name, err); + feeder_destroy(f); + + return NULL; + } + + return f; +} + +struct feeder_class * +feeder_getclass(struct pcm_feederdesc *desc) +{ + struct feedertab_entry *fte; + + SLIST_FOREACH(fte, &feedertab, link) { + if ((desc == NULL) && (fte->desc == NULL)) + return fte->feederclass; + if ((fte->desc != NULL) && (desc != NULL) && cmpdesc(desc, fte->desc)) + return fte->feederclass; + } + return NULL; +} + +int +chn_addfeeder(struct pcm_channel *c, struct feeder_class *fc, struct pcm_feederdesc *desc) +{ + struct pcm_feeder *nf; + + nf = feeder_create(fc, desc); + if (nf == NULL) + return ENOSPC; + + nf->source = c->feeder; + + if (nf->align > 0) + c->align += nf->align; + else if (nf->align < 0 && c->align < -nf->align) + c->align = -nf->align; + + c->feeder = nf; + + return 0; +} + +int +chn_removefeeder(struct pcm_channel *c) +{ + struct pcm_feeder *f; + + if (c->feeder == NULL) + return -1; + f = c->feeder; + c->feeder = c->feeder->source; + feeder_destroy(f); + + return 0; +} + +struct pcm_feeder * +chn_findfeeder(struct pcm_channel *c, u_int32_t type) +{ + struct pcm_feeder *f; + + f = c->feeder; + while (f != NULL) { + if (f->desc->type == type) + return f; + f = f->source; + } + + return NULL; +} + +static int +chainok(struct pcm_feeder *test, struct pcm_feeder *stop) +{ + u_int32_t visited[MAXFEEDERS / 32]; + u_int32_t idx, mask; + + bzero(visited, sizeof(visited)); + while (test && (test != stop)) { + idx = test->desc->idx; + if (idx < 0) + panic("bad idx %d", idx); + if (idx >= MAXFEEDERS) + panic("bad idx %d", idx); + mask = 1 << (idx & 31); + idx >>= 5; + if (visited[idx] & mask) + return 0; + visited[idx] |= mask; + test = test->source; + } + + return 1; +} + +static struct pcm_feeder * +feeder_fmtchain(u_int32_t *to, struct pcm_feeder *source, struct pcm_feeder *stop, int maxdepth) +{ + struct feedertab_entry *fte; + struct pcm_feeder *try, *ret; + + /* printf("trying %s (%x -> %x)...\n", source->class->name, source->desc->in, source->desc->out); */ + if (fmtvalid(source->desc->out, to)) { + /* printf("got it\n"); */ + return source; + } + + if (maxdepth < 0) + return NULL; + + SLIST_FOREACH(fte, &feedertab, link) { + if (fte->desc == NULL) + continue; + if (fte->desc->type != FEEDER_FMT) + continue; + if (fte->desc->in == source->desc->out) { + try = feeder_create(fte->feederclass, fte->desc); + if (try) { + try->source = source; + ret = chainok(try, stop)? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; + if (ret != NULL) + return ret; + feeder_destroy(try); + } + } + } + /* printf("giving up %s...\n", source->class->name); */ + + return NULL; +} + +u_int32_t +chn_fmtchain(struct pcm_channel *c, u_int32_t *to) +{ + struct pcm_feeder *try, *del, *stop; + u_int32_t tmpfrom[2], best, *from; + int i, max, bestmax; + + KASSERT(c != NULL, ("c == NULL")); + KASSERT(c->feeder != NULL, ("c->feeder == NULL")); + KASSERT(to != NULL, ("to == NULL")); + KASSERT(to[0] != 0, ("to[0] == 0")); + + stop = c->feeder; + + if (c->direction == PCMDIR_REC && c->feeder->desc->type == FEEDER_ROOT) { + from = chn_getcaps(c)->fmtlist; + } else { + tmpfrom[0] = c->feeder->desc->out; + tmpfrom[1] = 0; + from = tmpfrom; + } + + i = 0; + best = 0; + bestmax = 100; + while (from[i] != 0) { + c->feeder->desc->out = from[i]; + try = NULL; + max = 0; + while (try == NULL && max < 8) { + try = feeder_fmtchain(to, c->feeder, stop, max); + if (try == NULL) + max++; + } + if (try != NULL && max < bestmax) { + bestmax = max; + best = from[i]; + } + while (try != NULL && try != stop) { + del = try; + try = try->source; + feeder_destroy(del); + } + i++; + } + if (best == 0) + return 0; + + c->feeder->desc->out = best; + try = feeder_fmtchain(to, c->feeder, stop, bestmax); + if (try == NULL) + return 0; + + c->feeder = try; + c->align = 0; +#ifdef FEEDER_DEBUG + printf("\n\nchain: "); +#endif + while (try && (try != stop)) { +#ifdef FEEDER_DEBUG + printf("%s [%d]", try->class->name, try->desc->idx); + if (try->source) + printf(" -> "); +#endif + if (try->source) + try->source->parent = try; + if (try->align > 0) + c->align += try->align; + else if (try->align < 0 && c->align < -try->align) + c->align = -try->align; + try = try->source; + } +#ifdef FEEDER_DEBUG + printf("%s [%d]\n", try->class->name, try->desc->idx); +#endif + + return (c->direction == PCMDIR_REC)? best : c->feeder->desc->out; +} + +/*****************************************************************************/ + +static int +feed_root(struct pcm_feeder *feeder, struct pcm_channel *ch, u_int8_t *buffer, u_int32_t count, void *source) +{ + struct snd_dbuf *src = source; + int l; + u_int8_t x; + + KASSERT(count > 0, ("feed_root: count == 0")); + /* count &= ~((1 << ch->align) - 1); */ + KASSERT(count > 0, ("feed_root: aligned count == 0 (align = %d)", ch->align)); + + l = min(count, sndbuf_getready(src)); + sndbuf_dispose(src, buffer, l); + +/* + if (l < count) + printf("appending %d bytes\n", count - l); +*/ + + x = (sndbuf_getfmt(src) & AFMT_SIGNED)? 0 : 0x80; + while (l < count) + buffer[l++] = x; + + return count; +} + +static kobj_method_t feeder_root_methods[] = { + KOBJMETHOD(feeder_feed, feed_root), + { 0, 0 } +}; +static struct feeder_class feeder_root_class = { + name: "feeder_root", + methods: feeder_root_methods, + size: sizeof(struct pcm_feeder), + align: 0, + desc: NULL, + data: NULL, +}; +SYSINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_register, &feeder_root_class); +SYSUNINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_unregisterall, NULL); + + + + + diff --git a/sys/dev/sound/pcm/feeder.h b/sys/dev/sound/pcm/feeder.h new file mode 100644 index 0000000..9fb8ff7 --- /dev/null +++ b/sys/dev/sound/pcm/feeder.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + * + * $FreeBSD$ + */ + +struct pcm_feederdesc { + u_int32_t type; + u_int32_t in, out; + u_int32_t flags; + int idx; +}; + +struct feeder_class { + KOBJ_CLASS_FIELDS; + int align; + struct pcm_feederdesc *desc; + void *data; +}; + +struct pcm_feeder { + KOBJ_FIELDS; + int align; + struct pcm_feederdesc *desc, desc_static; + void *data; + struct feeder_class *class; + struct pcm_feeder *source, *parent; + +}; + +void feeder_register(void *p); +struct feeder_class *feeder_getclass(struct pcm_feederdesc *desc); + +u_int32_t chn_fmtchain(struct pcm_channel *c, u_int32_t *to); +int chn_addfeeder(struct pcm_channel *c, struct feeder_class *fc, struct pcm_feederdesc *desc); +int chn_removefeeder(struct pcm_channel *c); +struct pcm_feeder *chn_findfeeder(struct pcm_channel *c, u_int32_t type); + +#define FEEDER_DECLARE(feeder, palign, pdata) \ +static struct feeder_class feeder ## _class = { \ + name: #feeder, \ + methods: feeder ## _methods, \ + size: sizeof(struct pcm_feeder), \ + align: palign, \ + desc: feeder ## _desc, \ + data: pdata, \ +}; \ +SYSINIT(feeder, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, feeder_register, &feeder ## _class); + +#define FEEDER_ROOT 1 +#define FEEDER_FMT 2 +#define FEEDER_MIXER 3 +#define FEEDER_RATE 4 +#define FEEDER_FILTER 5 +#define FEEDER_VOLUME 6 +#define FEEDER_LAST FEEDER_VOLUME + +#define FEEDRATE_SRC 1 +#define FEEDRATE_DST 2 + + diff --git a/sys/dev/sound/pcm/feeder_fmt.c b/sys/dev/sound/pcm/feeder_fmt.c new file mode 100644 index 0000000..b4fa4f3 --- /dev/null +++ b/sys/dev/sound/pcm/feeder_fmt.c @@ -0,0 +1,543 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + */ + +#include <dev/sound/pcm/sound.h> + +#include "feeder_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +MALLOC_DEFINE(M_FMTFEEDER, "fmtfeed", "pcm format feeder"); + +#define FEEDBUFSZ 8192 + +static unsigned char ulaw_to_u8[] = { + 3, 7, 11, 15, 19, 23, 27, 31, + 35, 39, 43, 47, 51, 55, 59, 63, + 66, 68, 70, 72, 74, 76, 78, 80, + 82, 84, 86, 88, 90, 92, 94, 96, + 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, 113, + 113, 114, 114, 115, 115, 116, 116, 117, + 117, 118, 118, 119, 119, 120, 120, 121, + 121, 121, 122, 122, 122, 122, 123, 123, + 123, 123, 124, 124, 124, 124, 125, 125, + 125, 125, 125, 125, 126, 126, 126, 126, + 126, 126, 126, 126, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 253, 249, 245, 241, 237, 233, 229, 225, + 221, 217, 213, 209, 205, 201, 197, 193, + 190, 188, 186, 184, 182, 180, 178, 176, + 174, 172, 170, 168, 166, 164, 162, 160, + 158, 157, 156, 155, 154, 153, 152, 151, + 150, 149, 148, 147, 146, 145, 144, 143, + 143, 142, 142, 141, 141, 140, 140, 139, + 139, 138, 138, 137, 137, 136, 136, 135, + 135, 135, 134, 134, 134, 134, 133, 133, + 133, 133, 132, 132, 132, 132, 131, 131, + 131, 131, 131, 131, 130, 130, 130, 130, + 130, 130, 130, 130, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, +}; + +static unsigned char u8_to_ulaw[] = { + 0, 0, 0, 0, 0, 1, 1, 1, + 1, 2, 2, 2, 2, 3, 3, 3, + 3, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, + 7, 8, 8, 8, 8, 9, 9, 9, + 9, 10, 10, 10, 10, 11, 11, 11, + 11, 12, 12, 12, 12, 13, 13, 13, + 13, 14, 14, 14, 14, 15, 15, 15, + 15, 16, 16, 17, 17, 18, 18, 19, + 19, 20, 20, 21, 21, 22, 22, 23, + 23, 24, 24, 25, 25, 26, 26, 27, + 27, 28, 28, 29, 29, 30, 30, 31, + 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, + 47, 49, 51, 53, 55, 57, 59, 61, + 63, 66, 70, 74, 78, 84, 92, 104, + 254, 231, 219, 211, 205, 201, 197, 193, + 190, 188, 186, 184, 182, 180, 178, 176, + 175, 174, 173, 172, 171, 170, 169, 168, + 167, 166, 165, 164, 163, 162, 161, 160, + 159, 159, 158, 158, 157, 157, 156, 156, + 155, 155, 154, 154, 153, 153, 152, 152, + 151, 151, 150, 150, 149, 149, 148, 148, + 147, 147, 146, 146, 145, 145, 144, 144, + 143, 143, 143, 143, 142, 142, 142, 142, + 141, 141, 141, 141, 140, 140, 140, 140, + 139, 139, 139, 139, 138, 138, 138, 138, + 137, 137, 137, 137, 136, 136, 136, 136, + 135, 135, 135, 135, 134, 134, 134, 134, + 133, 133, 133, 133, 132, 132, 132, 132, + 131, 131, 131, 131, 130, 130, 130, 130, + 129, 129, 129, 129, 128, 128, 128, 128, +}; + +static unsigned char alaw_to_ulaw[] = { + 42, 43, 40, 41, 46, 47, 44, 45, + 34, 35, 32, 33, 38, 39, 36, 37, + 57, 58, 55, 56, 61, 62, 59, 60, + 49, 50, 48, 48, 53, 54, 51, 52, + 10, 11, 8, 9, 14, 15, 12, 13, + 2, 3, 0, 1, 6, 7, 4, 5, + 26, 27, 24, 25, 30, 31, 28, 29, + 18, 19, 16, 17, 22, 23, 20, 21, + 98, 99, 96, 97, 102, 103, 100, 101, + 93, 93, 92, 92, 95, 95, 94, 94, + 116, 118, 112, 114, 124, 126, 120, 122, + 106, 107, 104, 105, 110, 111, 108, 109, + 72, 73, 70, 71, 76, 77, 74, 75, + 64, 65, 63, 63, 68, 69, 66, 67, + 86, 87, 84, 85, 90, 91, 88, 89, + 79, 79, 78, 78, 82, 83, 80, 81, + 170, 171, 168, 169, 174, 175, 172, 173, + 162, 163, 160, 161, 166, 167, 164, 165, + 185, 186, 183, 184, 189, 190, 187, 188, + 177, 178, 176, 176, 181, 182, 179, 180, + 138, 139, 136, 137, 142, 143, 140, 141, + 130, 131, 128, 129, 134, 135, 132, 133, + 154, 155, 152, 153, 158, 159, 156, 157, + 146, 147, 144, 145, 150, 151, 148, 149, + 226, 227, 224, 225, 230, 231, 228, 229, + 221, 221, 220, 220, 223, 223, 222, 222, + 244, 246, 240, 242, 252, 254, 248, 250, + 234, 235, 232, 233, 238, 239, 236, 237, + 200, 201, 198, 199, 204, 205, 202, 203, + 192, 193, 191, 191, 196, 197, 194, 195, + 214, 215, 212, 213, 218, 219, 216, 217, + 207, 207, 206, 206, 210, 211, 208, 209, +}; + +static unsigned char ulaw_to_alaw[] = { + 42, 43, 40, 41, 46, 47, 44, 45, + 34, 35, 32, 33, 38, 39, 36, 37, + 58, 59, 56, 57, 62, 63, 60, 61, + 50, 51, 48, 49, 54, 55, 52, 53, + 10, 11, 8, 9, 14, 15, 12, 13, + 2, 3, 0, 1, 6, 7, 4, 5, + 27, 24, 25, 30, 31, 28, 29, 18, + 19, 16, 17, 22, 23, 20, 21, 106, + 104, 105, 110, 111, 108, 109, 98, 99, + 96, 97, 102, 103, 100, 101, 122, 120, + 126, 127, 124, 125, 114, 115, 112, 113, + 118, 119, 116, 117, 75, 73, 79, 77, + 66, 67, 64, 65, 70, 71, 68, 69, + 90, 91, 88, 89, 94, 95, 92, 93, + 82, 82, 83, 83, 80, 80, 81, 81, + 86, 86, 87, 87, 84, 84, 85, 85, + 170, 171, 168, 169, 174, 175, 172, 173, + 162, 163, 160, 161, 166, 167, 164, 165, + 186, 187, 184, 185, 190, 191, 188, 189, + 178, 179, 176, 177, 182, 183, 180, 181, + 138, 139, 136, 137, 142, 143, 140, 141, + 130, 131, 128, 129, 134, 135, 132, 133, + 155, 152, 153, 158, 159, 156, 157, 146, + 147, 144, 145, 150, 151, 148, 149, 234, + 232, 233, 238, 239, 236, 237, 226, 227, + 224, 225, 230, 231, 228, 229, 250, 248, + 254, 255, 252, 253, 242, 243, 240, 241, + 246, 247, 244, 245, 203, 201, 207, 205, + 194, 195, 192, 193, 198, 199, 196, 197, + 218, 219, 216, 217, 222, 223, 220, 221, + 210, 210, 211, 211, 208, 208, 209, 209, + 214, 214, 215, 215, 212, 212, 213, 213, +}; + +/*****************************************************************************/ + +static int +feed_8to16le(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + int i, j, k; + + k = FEEDER_FEED(f->source, c, b, count / 2, source); + j = k - 1; + i = j * 2 + 1; + while (i > 0 && j >= 0) { + b[i--] = b[j--]; + b[i--] = 0; + } + return k * 2; +} + +static struct pcm_feederdesc feeder_8to16le_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_8to16le_methods[] = { + KOBJMETHOD(feeder_feed, feed_8to16le), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_8to16le, 0, NULL); + +/*****************************************************************************/ + +static int +feed_16to8_init(struct pcm_feeder *f) +{ + f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); + return (f->data)? 0 : ENOMEM; +} + +static int +feed_16to8_free(struct pcm_feeder *f) +{ + if (f->data) + free(f->data, M_FMTFEEDER); + f->data = NULL; + return 0; +} + +static int +feed_16leto8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + u_int32_t i = 0, toget = count * 2; + int j = 1, k; + + k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); + while (j < k) { + b[i++] = ((u_int8_t *)f->data)[j]; + j += 2; + } + return i; +} + +static struct pcm_feederdesc feeder_16leto8_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_16leto8_methods[] = { + KOBJMETHOD(feeder_init, feed_16to8_init), + KOBJMETHOD(feeder_free, feed_16to8_free), + KOBJMETHOD(feeder_feed, feed_16leto8), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_16leto8, 1, NULL); + +/*****************************************************************************/ + +static int +feed_monotostereo8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + int i, j, k = FEEDER_FEED(f->source, c, b, count / 2, source); + + j = k - 1; + i = j * 2 + 1; + while (i > 0 && j >= 0) { + b[i--] = b[j]; + b[i--] = b[j]; + j--; + } + return k * 2; +} + +static struct pcm_feederdesc feeder_monotostereo8_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S8 | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_monotostereo8_methods[] = { + KOBJMETHOD(feeder_feed, feed_monotostereo8), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_monotostereo8, 0, NULL); + +/*****************************************************************************/ + +static int +feed_monotostereo16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + int i, j, k = FEEDER_FEED(f->source, c, b, count / 2, source); + u_int8_t x, y; + + j = k - 1; + i = j * 2 + 1; + while (i > 3 && j >= 1) { + x = b[j--]; + y = b[j--]; + b[i--] = x; + b[i--] = y; + b[i--] = x; + b[i--] = y; + } + return k * 2; +} + +static struct pcm_feederdesc feeder_monotostereo16_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_BE | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_monotostereo16_methods[] = { + KOBJMETHOD(feeder_feed, feed_monotostereo16), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_monotostereo16, 0, NULL); + +/*****************************************************************************/ + +static int +feed_stereotomono8_init(struct pcm_feeder *f) +{ + f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); + return (f->data)? 0 : ENOMEM; +} + +static int +feed_stereotomono8_free(struct pcm_feeder *f) +{ + if (f->data) + free(f->data, M_FMTFEEDER); + f->data = NULL; + return 0; +} + +static int +feed_stereotomono8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + u_int32_t i = 0, toget = count * 2; + int j = 0, k; + + k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); + while (j < k) { + b[i++] = ((u_int8_t *)f->data)[j]; + j += 2; + } + return i; +} + +static struct pcm_feederdesc feeder_stereotomono8_desc[] = { + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S8, 0}, + {0}, +}; +static kobj_method_t feeder_stereotomono8_methods[] = { + KOBJMETHOD(feeder_init, feed_stereotomono8_init), + KOBJMETHOD(feeder_free, feed_stereotomono8_free), + KOBJMETHOD(feeder_feed, feed_stereotomono8), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_stereotomono8, 1, NULL); + +/*****************************************************************************/ + +static int +feed_stereotomono16_init(struct pcm_feeder *f) +{ + f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); + return (f->data)? 0 : ENOMEM; +} + +static int +feed_stereotomono16_free(struct pcm_feeder *f) +{ + if (f->data) + free(f->data, M_FMTFEEDER); + f->data = NULL; + return 0; +} + +static int +feed_stereotomono16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + u_int32_t i = 0, toget = count * 2; + int j = 0, k; + + k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); + while (j < k) { + b[i++] = ((u_int8_t *)f->data)[j]; + b[i++] = ((u_int8_t *)f->data)[j + 1]; + j += 4; + } + return i; +} + +static struct pcm_feederdesc feeder_stereotomono16_desc[] = { + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE, 0}, + {0}, +}; +static kobj_method_t feeder_stereotomono16_methods[] = { + KOBJMETHOD(feeder_init, feed_stereotomono16_init), + KOBJMETHOD(feeder_free, feed_stereotomono16_free), + KOBJMETHOD(feeder_feed, feed_stereotomono16), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_stereotomono16, 1, NULL); + +/*****************************************************************************/ + +static int +feed_endian(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + u_int8_t t; + int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); + + while (i < j) { + t = b[i]; + b[i] = b[i + 1]; + b[i + 1] = t; + i += 2; + } + return i; +} + +static struct pcm_feederdesc feeder_endian_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_endian_methods[] = { + KOBJMETHOD(feeder_feed, feed_endian), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_endian, 0, NULL); + +/*****************************************************************************/ + +static int +feed_sign(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); + intptr_t ssz = (intptr_t)f->data, ofs = ssz - 1; + + while (i < j) { + b[i + ofs] ^= 0x80; + i += ssz; + } + return i; +} + +static struct pcm_feederdesc feeder_sign8_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_sign8_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_sign8, 0, (void *)1); + +static struct pcm_feederdesc feeder_sign16le_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_sign16le_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_sign16le, -1, (void *)2); + +/*****************************************************************************/ + +static int +feed_table(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); + + while (i < j) { + b[i] = ((u_int8_t *)f->data)[b[i]]; + i++; + } + return i; +} + +static struct pcm_feederdesc feeder_ulawtou8_desc[] = { + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_ulawtou8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_ulawtou8, 0, ulaw_to_u8); + +static struct pcm_feederdesc feeder_u8toulaw_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_u8toulaw_methods[] = { + KOBJMETHOD(feeder_feed, feed_table), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_u8toulaw, 0, u8_to_ulaw); + +static struct pcm_feederdesc feeder_alawtoulaw_desc[] = { + {FEEDER_FMT, AFMT_A_LAW, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_alawtoulaw_methods[] = { + KOBJMETHOD(feeder_feed, feed_table), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_alawtoulaw, 0, alaw_to_ulaw); + +static struct pcm_feederdesc feeder_ulawtoalaw_desc[] = { + {FEEDER_FMT, AFMT_MU_LAW, AFMT_A_LAW, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_ulawtoalaw_methods[] = { + KOBJMETHOD(feeder_feed, feed_table), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_ulawtoalaw, 0, ulaw_to_alaw); + + + diff --git a/sys/dev/sound/pcm/feeder_if.m b/sys/dev/sound/pcm/feeder_if.m new file mode 100644 index 0000000..a100a6d --- /dev/null +++ b/sys/dev/sound/pcm/feeder_if.m @@ -0,0 +1,87 @@ +# KOBJ +# +# Copyright (c) 2000 Cameron Grant <cg@freebsd.org> +# 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. +# +# $FreeBSD$ +# + +#include <dev/sound/pcm/sound.h> + +INTERFACE feeder; + +CODE { + + static int + feeder_noinit(struct pcm_feeder* feeder) + { + return 0; + } + + static int + feeder_nofree(struct pcm_feeder* feeder) + { + return 0; + } + + static int + feeder_noset(struct pcm_feeder* feeder, int what, int value) + { + return -1; + } + + static int + feeder_noget(struct pcm_feeder* feeder, int what) + { + return -1; + } + +}; + +METHOD int init { + struct pcm_feeder* feeder; +} DEFAULT feeder_noinit; + +METHOD int free { + struct pcm_feeder* feeder; +} DEFAULT feeder_nofree; + +METHOD int set { + struct pcm_feeder* feeder; + int what; + int value; +} DEFAULT feeder_noset; + +METHOD int get { + struct pcm_feeder* feeder; + int what; +} DEFAULT feeder_noget; + +METHOD int feed { + struct pcm_feeder* feeder; + struct pcm_channel* c; + u_int8_t* buffer; + u_int32_t count; + void* source; +}; diff --git a/sys/dev/sound/pcm/feeder_rate.c b/sys/dev/sound/pcm/feeder_rate.c new file mode 100644 index 0000000..fca795c --- /dev/null +++ b/sys/dev/sound/pcm/feeder_rate.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + */ + +#include <dev/sound/pcm/sound.h> + +#include "feeder_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +MALLOC_DEFINE(M_RATEFEEDER, "ratefeed", "pcm rate feeder"); + +#define FEEDBUFSZ 8192 +#undef FEEDER_DEBUG + +struct feed_rate_info { + u_int32_t src, dst; + int srcpos, srcinc; + int16_t *buffer; + u_int16_t alpha; +}; + +static int +feed_rate_setup(struct pcm_feeder *f) +{ + struct feed_rate_info *info = f->data; + + info->srcinc = (info->src << 16) / info->dst; + /* srcinc is 16.16 fixed point increment for srcpos for each dstpos */ + info->srcpos = 0; + return 0; +} + +static int +feed_rate_set(struct pcm_feeder *f, int what, int value) +{ + struct feed_rate_info *info = f->data; + + switch(what) { + case FEEDRATE_SRC: + info->src = value; + break; + case FEEDRATE_DST: + info->dst = value; + break; + default: + return -1; + } + return feed_rate_setup(f); +} + +static int +feed_rate_get(struct pcm_feeder *f, int what) +{ + struct feed_rate_info *info = f->data; + + switch(what) { + case FEEDRATE_SRC: + return info->src; + case FEEDRATE_DST: + return info->dst; + default: + return -1; + } + return -1; +} + +static int +feed_rate_init(struct pcm_feeder *f) +{ + struct feed_rate_info *info; + + info = malloc(sizeof(*info), M_RATEFEEDER, M_NOWAIT | M_ZERO); + if (info == NULL) + return ENOMEM; + info->buffer = malloc(FEEDBUFSZ, M_RATEFEEDER, M_NOWAIT | M_ZERO); + if (info->buffer == NULL) { + free(info, M_RATEFEEDER); + return ENOMEM; + } + info->src = DSP_DEFAULT_SPEED; + info->dst = DSP_DEFAULT_SPEED; + info->alpha = 0; + f->data = info; + return feed_rate_setup(f); +} + +static int +feed_rate_free(struct pcm_feeder *f) +{ + struct feed_rate_info *info = f->data; + + if (info) { + if (info->buffer) + free(info->buffer, M_RATEFEEDER); + free(info, M_RATEFEEDER); + } + f->data = NULL; + return 0; +} + +static int +feed_rate(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + struct feed_rate_info *info = f->data; + int16_t *destbuf = (int16_t *)b; + int fetch, v, alpha, hidelta, spos, dpos; + + /* + * at this point: + * info->srcpos is 24.8 fixed offset into the fetchbuffer. 0 <= srcpos <= 0xff + * + * our input and output are always AFMT_S16LE stereo. this simplifies things. + */ + + /* + * we start by fetching enough source data into our buffer to generate + * about as much as was requested. we put it at offset 2 in the + * buffer so that we can interpolate from the last samples in the + * previous iteration- when we finish we will move our last samples + * to the start of the buffer. + */ + spos = 0; + dpos = 0; + + /* fetch is in bytes */ + fetch = (count * info->srcinc) >> 16; + fetch = min(fetch, FEEDBUFSZ - 4) & ~3; + if (fetch == 0) + return 0; + fetch = FEEDER_FEED(f->source, c, ((u_int8_t *)info->buffer) + 4, fetch, source); + fetch /= 2; + + alpha = info->alpha; + hidelta = min(info->srcinc >> 16, 1) * 2; + while ((spos + hidelta + 1) < fetch) { + v = (info->buffer[spos] * (0xffff - alpha)) + (info->buffer[spos + hidelta] * alpha); + destbuf[dpos++] = v >> 16; + + v = (info->buffer[spos + 1] * (0xffff - alpha)) + (info->buffer[spos + hidelta + 1] * alpha); + destbuf[dpos++] = v >> 16; + + alpha += info->srcinc; + spos += (alpha >> 16) * 2; + alpha &= 0xffff; + + } + info->alpha = alpha & 0xffff; + info->buffer[0] = info->buffer[spos - hidelta]; + info->buffer[1] = info->buffer[spos - hidelta + 1]; + + count = dpos * 2; + return count; +} + +static struct pcm_feederdesc feeder_rate_desc[] = { + {FEEDER_RATE, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_rate_methods[] = { + KOBJMETHOD(feeder_init, feed_rate_init), + KOBJMETHOD(feeder_free, feed_rate_free), + KOBJMETHOD(feeder_set, feed_rate_set), + KOBJMETHOD(feeder_get, feed_rate_get), + KOBJMETHOD(feeder_feed, feed_rate), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_rate, 2, NULL); + + diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c new file mode 100644 index 0000000..182d290 --- /dev/null +++ b/sys/dev/sound/pcm/mixer.c @@ -0,0 +1,519 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + */ + +#include <dev/sound/pcm/sound.h> + +#include "mixer_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +MALLOC_DEFINE(M_MIXER, "mixer", "mixer"); + +#define MIXER_NAMELEN 16 +struct snd_mixer { + KOBJ_FIELDS; + const char *type; + void *devinfo; + int busy; + int hwvol_muted; + int hwvol_mixer; + int hwvol_step; + u_int32_t hwvol_mute_level; + u_int32_t devs; + u_int32_t recdevs; + u_int32_t recsrc; + u_int16_t level[32]; + char name[MIXER_NAMELEN]; + struct mtx *lock; +}; + +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] = 0, + [SOUND_MIXER_CD] = 75, + [SOUND_MIXER_LINE1] = 75, + [SOUND_MIXER_VIDEO] = 75, + [SOUND_MIXER_RECLEV] = 0, + [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 struct cdevsw mixer_cdevsw = { + /* open */ mixer_open, + /* close */ mixer_close, + /* read */ noread, + /* write */ nowrite, + /* ioctl */ mixer_ioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "mixer", + /* maj */ SND_CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + +#ifdef USING_DEVFS +static eventhandler_tag mixer_ehtag; +#endif + +static dev_t +mixer_get_devt(device_t dev) +{ + dev_t pdev; + int unit; + + unit = device_get_unit(dev); + pdev = makedev(SND_CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_CTL, 0)); + + return pdev; +} + +#ifdef SND_DYNSYSCTL +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; +} +#endif + +static int +mixer_set(struct snd_mixer *mixer, unsigned dev, unsigned lev) +{ + unsigned l, r; + int v; + + if ((dev >= SOUND_MIXER_NRDEVICES) || (0 == (mixer->devs & (1 << dev)))) + return -1; + + l = min((lev & 0x00ff), 100); + r = min(((lev & 0xff00) >> 8), 100); + + v = MIXER_SET(mixer, dev, l, r); + if (v < 0) + return -1; + + mixer->level[dev] = l | (r << 8); + return 0; +} + +static int +mixer_get(struct snd_mixer *mixer, int dev) +{ + if ((dev < SOUND_MIXER_NRDEVICES) && (mixer->devs & (1 << dev))) + return mixer->level[dev]; + else return -1; +} + +static int +mixer_setrecsrc(struct snd_mixer *mixer, u_int32_t src) +{ + src &= mixer->recdevs; + if (src == 0) + src = SOUND_MASK_MIC; + mixer->recsrc = MIXER_SETRECSRC(mixer, src); + return 0; +} + +static int +mixer_getrecsrc(struct snd_mixer *mixer) +{ + return mixer->recsrc; +} + +void +mix_setdevs(struct snd_mixer *m, u_int32_t v) +{ + m->devs = v; +} + +void +mix_setrecdevs(struct snd_mixer *m, u_int32_t v) +{ + m->recdevs = v; +} + +u_int32_t +mix_getdevs(struct snd_mixer *m) +{ + return m->devs; +} + +u_int32_t +mix_getrecdevs(struct snd_mixer *m) +{ + return m->recdevs; +} + +void * +mix_getdevinfo(struct snd_mixer *m) +{ + return m->devinfo; +} + +int +mixer_init(device_t dev, kobj_class_t cls, void *devinfo) +{ + struct snd_mixer *m; + u_int16_t v; + dev_t pdev; + int i, unit; + + m = (struct snd_mixer *)kobj_create(cls, M_MIXER, M_WAITOK | M_ZERO); + snprintf(m->name, MIXER_NAMELEN, "%s:mixer", device_get_nameunit(dev)); + m->lock = snd_mtxcreate(m->name, "pcm mixer"); + m->type = cls->name; + m->devinfo = devinfo; + m->busy = 0; + + if (MIXER_INIT(m)) + goto bad; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + v = snd_mixerdefaults[i]; + mixer_set(m, i, v | (v << 8)); + } + + mixer_setrecsrc(m, SOUND_MASK_MIC); + + unit = device_get_unit(dev); + pdev = make_dev(&mixer_cdevsw, PCMMKMINOR(unit, SND_DEV_CTL, 0), + UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit); + pdev->si_drv1 = m; + + return 0; + +bad: + snd_mtxlock(m->lock); + snd_mtxfree(m->lock); + kobj_delete((kobj_t)m, M_MIXER); + return -1; +} + +int +mixer_uninit(device_t dev) +{ + int i; + struct snd_mixer *m; + dev_t pdev; + + pdev = mixer_get_devt(dev); + m = pdev->si_drv1; + snd_mtxlock(m->lock); + + if (m->busy) { + snd_mtxunlock(m->lock); + return EBUSY; + } + + pdev->si_drv1 = NULL; + destroy_dev(pdev); + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + mixer_set(m, i, 0); + + mixer_setrecsrc(m, SOUND_MASK_MIC); + + MIXER_UNINIT(m); + + snd_mtxfree(m->lock); + kobj_delete((kobj_t)m, M_MIXER); + + return 0; +} + +int +mixer_reinit(device_t dev) +{ + struct snd_mixer *m; + dev_t 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++) + mixer_set(m, i, m->level[i]); + + mixer_setrecsrc(m, m->recsrc); + snd_mtxunlock(m->lock); + + return 0; +} + +#ifdef SND_DYNSYSCTL +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); + strncpy(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 if (dev != m->hwvol_mixer) { + m->hwvol_mixer = dev; + m->hwvol_muted = 0; + } + } + snd_mtxunlock(m->lock); + return error; +} +#endif + +int +mixer_hwvol_init(device_t dev) +{ + struct snd_mixer *m; + dev_t pdev; + + pdev = mixer_get_devt(dev); + m = pdev->si_drv1; + snd_mtxlock(m->lock); + + m->hwvol_mixer = SOUND_MIXER_VOLUME; + m->hwvol_step = 5; +#ifdef SND_DYNSYSCTL + SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "hwvol_step", CTLFLAG_RW, &m->hwvol_step, 0, ""); + SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "hwvol_mixer", CTLTYPE_STRING | CTLFLAG_RW, m, 0, + sysctl_hw_snd_hwvol_mixer, "A", ""); +#endif + snd_mtxunlock(m->lock); + return 0; +} + +void +mixer_hwvol_mute(device_t dev) +{ + struct snd_mixer *m; + dev_t pdev; + + pdev = mixer_get_devt(dev); + m = pdev->si_drv1; + snd_mtxlock(m->lock); + if (m->hwvol_muted) { + m->hwvol_muted = 0; + mixer_set(m, m->hwvol_mixer, m->hwvol_mute_level); + } else { + m->hwvol_muted++; + m->hwvol_mute_level = mixer_get(m, m->hwvol_mixer); + mixer_set(m, m->hwvol_mixer, 0); + } + snd_mtxunlock(m->lock); +} + +void +mixer_hwvol_step(device_t dev, int left_step, int right_step) +{ + struct snd_mixer *m; + int level, left, right; + dev_t pdev; + + pdev = mixer_get_devt(dev); + m = pdev->si_drv1; + snd_mtxlock(m->lock); + if (m->hwvol_muted) { + m->hwvol_muted = 0; + level = m->hwvol_mute_level; + } else + level = mixer_get(m, m->hwvol_mixer); + if (level != -1) { + left = level & 0xff; + right = level >> 8; + left += left_step * m->hwvol_step; + if (left < 0) + left = 0; + right += right_step * m->hwvol_step; + if (right < 0) + right = 0; + mixer_set(m, m->hwvol_mixer, left | right << 8); + } + snd_mtxunlock(m->lock); +} + +/* ----------------------------------------------------------------------- */ + +static int +mixer_open(dev_t i_dev, int flags, int mode, struct thread *td) +{ + struct snd_mixer *m; + intrmask_t s; + + m = i_dev->si_drv1; + s = spltty(); + snd_mtxlock(m->lock); + + m->busy++; + + snd_mtxunlock(m->lock); + splx(s); + return 0; +} + +static int +mixer_close(dev_t i_dev, int flags, int mode, struct thread *td) +{ + struct snd_mixer *m; + intrmask_t s; + + m = i_dev->si_drv1; + s = spltty(); + snd_mtxlock(m->lock); + + if (!m->busy) { + snd_mtxunlock(m->lock); + splx(s); + return EBADF; + } + m->busy--; + + snd_mtxunlock(m->lock); + splx(s); + return 0; +} + +int +mixer_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +{ + struct snd_mixer *m; + intrmask_t s; + int ret, *arg_i = (int *)arg; + int v = -1, j = cmd & 0xff; + + m = i_dev->si_drv1; + if (!m->busy) + return EBADF; + + s = spltty(); + snd_mtxlock(m->lock); + if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { + if (j == SOUND_MIXER_RECSRC) + ret = mixer_setrecsrc(m, *arg_i); + else + ret = mixer_set(m, j, *arg_i); + snd_mtxunlock(m->lock); + splx(s); + return (ret == 0)? 0 : ENXIO; + } + + if ((cmd & MIXER_READ(0)) == 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_RECMASK: + v = mix_getrecdevs(m); + break; + + case SOUND_MIXER_RECSRC: + v = mixer_getrecsrc(m); + break; + + default: + v = mixer_get(m, j); + } + *arg_i = v; + snd_mtxunlock(m->lock); + return (v != -1)? 0 : ENXIO; + } + snd_mtxunlock(m->lock); + splx(s); + return ENXIO; +} + +#ifdef USING_DEVFS +static void +mixer_clone(void *arg, char *name, int namelen, dev_t *dev) +{ + dev_t pdev; + + if (*dev != NODEV) + return; + if (strcmp(name, "mixer") == 0) { + pdev = makedev(SND_CDEV_MAJOR, PCMMKMINOR(snd_unit, SND_DEV_CTL, 0)); + if (pdev->si_flags & SI_NAMED) + *dev = pdev; + } +} + +static void +mixer_sysinit(void *p) +{ + mixer_ehtag = EVENTHANDLER_REGISTER(dev_clone, mixer_clone, 0, 1000); +} + +static void +mixer_sysuninit(void *p) +{ + if (mixer_ehtag != NULL) + EVENTHANDLER_DEREGISTER(dev_clone, mixer_ehtag); +} + +SYSINIT(mixer_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysinit, NULL); +SYSUNINIT(mixer_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysuninit, NULL); +#endif + + diff --git a/sys/dev/sound/pcm/mixer.h b/sys/dev/sound/pcm/mixer.h new file mode 100644 index 0000000..4f51bf2 --- /dev/null +++ b/sys/dev/sound/pcm/mixer.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + * + * $FreeBSD$ + */ + +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(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td); + +int mixer_hwvol_init(device_t dev); +void mixer_hwvol_mute(device_t dev); +void mixer_hwvol_step(device_t dev, int left_step, int right_step); + +void mix_setdevs(struct snd_mixer *m, u_int32_t v); +void mix_setrecdevs(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); +void *mix_getdevinfo(struct snd_mixer *m); + +/* + * this is a kludge to allow hiding of the struct snd_mixer definition + * 512 should be enough for all architectures + */ +#define MIXER_SIZE (512 + sizeof(struct kobj)) + +#define MIXER_DECLARE(name) DEFINE_CLASS(name, name ## _methods, MIXER_SIZE) diff --git a/sys/dev/sound/pcm/mixer_if.m b/sys/dev/sound/pcm/mixer_if.m new file mode 100644 index 0000000..38d99fb --- /dev/null +++ b/sys/dev/sound/pcm/mixer_if.m @@ -0,0 +1,66 @@ +# KOBJ +# +# Copyright (c) 2000 Cameron Grant <cg@freebsd.org> +# 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. +# +# $FreeBSD$ +# + +#include <dev/sound/pcm/sound.h> + +INTERFACE mixer; + +CODE { + + static int + mixer_noreinit(struct snd_mixer *m) + { + return 0; + } + +}; + +METHOD int init { + struct snd_mixer *m; +}; + +METHOD int reinit { + struct snd_mixer *m; +} DEFAULT mixer_noreinit; + +METHOD int uninit { + struct snd_mixer *m; +}; + +METHOD int set { + struct snd_mixer *m; + unsigned dev; + unsigned left; + unsigned right; +}; + +METHOD u_int32_t setrecsrc { + struct snd_mixer *m; + u_int32_t src; +}; diff --git a/sys/dev/sound/pcm/sndstat.c b/sys/dev/sound/pcm/sndstat.c new file mode 100644 index 0000000..bdaff2b --- /dev/null +++ b/sys/dev/sound/pcm/sndstat.c @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2001 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + */ + +#include <dev/sound/pcm/sound.h> +#include <dev/sound/pcm/vchan.h> + +SND_DECLARE_FILE("$FreeBSD$"); + +#define SS_TYPE_MODULE 0 +#define SS_TYPE_FIRST 1 +#define SS_TYPE_PCM 1 +#define SS_TYPE_MIDI 2 +#define SS_TYPE_SEQUENCER 3 +#define SS_TYPE_LAST 3 + +static d_open_t sndstat_open; +static d_close_t sndstat_close; +static d_read_t sndstat_read; + +static struct cdevsw sndstat_cdevsw = { + /* open */ sndstat_open, + /* close */ sndstat_close, + /* read */ sndstat_read, + /* write */ nowrite, + /* ioctl */ noioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "sndstat", + /* maj */ SND_CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + +struct sndstat_entry { + SLIST_ENTRY(sndstat_entry) link; + device_t dev; + char *str; + sndstat_handler handler; + int type, unit; +}; + +#ifdef USING_MUTEX +static struct mtx sndstat_lock; +#endif +static struct sbuf sndstat_sbuf; +static dev_t sndstat_dev = 0; +static int sndstat_isopen = 0; +static int sndstat_bufptr; +static int sndstat_maxunit = -1; +static int sndstat_files = 0; + +static SLIST_HEAD(, sndstat_entry) sndstat_devlist = SLIST_HEAD_INITIALIZER(none); + +static int sndstat_verbose = 1; +#ifdef USING_MUTEX +TUNABLE_INT("hw.snd.verbose", &sndstat_verbose); +#else +TUNABLE_INT_DECL("hw.snd.verbose", 1, sndstat_verbose); +#endif + +static int sndstat_prepare(struct sbuf *s); + +static int +sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS) +{ + intrmask_t s; + int error, verbose; + + verbose = sndstat_verbose; + error = sysctl_handle_int(oidp, &verbose, sizeof(verbose), req); + if (error == 0 && req->newptr != NULL) { + s = spltty(); + mtx_lock(&sndstat_lock); + if (verbose < 0 || verbose > 3) + error = EINVAL; + else + sndstat_verbose = verbose; + mtx_unlock(&sndstat_lock); + splx(s); + } + return error; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_sndverbose, "I", ""); + +static int +sndstat_open(dev_t i_dev, int flags, int mode, struct thread *td) +{ + intrmask_t s; + int error; + + s = spltty(); + mtx_lock(&sndstat_lock); + if (sndstat_isopen) { + mtx_unlock(&sndstat_lock); + splx(s); + return EBUSY; + } + sndstat_isopen = 1; + mtx_unlock(&sndstat_lock); + splx(s); + if (sbuf_new(&sndstat_sbuf, NULL, 4096, 0) == NULL) { + error = ENXIO; + goto out; + } + sndstat_bufptr = 0; + error = (sndstat_prepare(&sndstat_sbuf) > 0) ? 0 : ENOMEM; +out: + if (error) { + s = spltty(); + mtx_lock(&sndstat_lock); + sndstat_isopen = 0; + mtx_unlock(&sndstat_lock); + splx(s); + } + return (error); +} + +static int +sndstat_close(dev_t i_dev, int flags, int mode, struct thread *td) +{ + intrmask_t s; + + s = spltty(); + mtx_lock(&sndstat_lock); + if (!sndstat_isopen) { + mtx_unlock(&sndstat_lock); + splx(s); + return EBADF; + } + sbuf_delete(&sndstat_sbuf); + sndstat_isopen = 0; + + mtx_unlock(&sndstat_lock); + splx(s); + return 0; +} + +static int +sndstat_read(dev_t i_dev, struct uio *buf, int flag) +{ + intrmask_t s; + int l, err; + + s = spltty(); + mtx_lock(&sndstat_lock); + if (!sndstat_isopen) { + mtx_unlock(&sndstat_lock); + splx(s); + return EBADF; + } + l = min(buf->uio_resid, sbuf_len(&sndstat_sbuf) - sndstat_bufptr); + err = (l > 0)? uiomove(sbuf_data(&sndstat_sbuf) + sndstat_bufptr, l, buf) : 0; + sndstat_bufptr += l; + + mtx_unlock(&sndstat_lock); + splx(s); + return err; +} + +/************************************************************************/ + +static struct sndstat_entry * +sndstat_find(int type, int unit) +{ + struct sndstat_entry *ent; + + SLIST_FOREACH(ent, &sndstat_devlist, link) { + if (ent->type == type && ent->unit == unit) + return ent; + } + + return NULL; +} + +int +sndstat_register(device_t dev, char *str, sndstat_handler handler) +{ + intrmask_t s; + struct sndstat_entry *ent; + const char *devtype; + int type, unit; + + if (dev) { + unit = device_get_unit(dev); + devtype = device_get_name(dev); + if (!strcmp(devtype, "pcm")) + type = SS_TYPE_PCM; + else if (!strcmp(devtype, "midi")) + type = SS_TYPE_MIDI; + else if (!strcmp(devtype, "sequencer")) + type = SS_TYPE_SEQUENCER; + else + return EINVAL; + } else { + type = SS_TYPE_MODULE; + unit = -1; + } + + ent = malloc(sizeof *ent, M_DEVBUF, M_ZERO | M_WAITOK); + if (!ent) + return ENOSPC; + + ent->dev = dev; + ent->str = str; + ent->type = type; + ent->unit = unit; + ent->handler = handler; + + s = spltty(); + mtx_lock(&sndstat_lock); + SLIST_INSERT_HEAD(&sndstat_devlist, ent, link); + if (type == SS_TYPE_MODULE) + sndstat_files++; + sndstat_maxunit = (unit > sndstat_maxunit)? unit : sndstat_maxunit; + mtx_unlock(&sndstat_lock); + splx(s); + + return 0; +} + +int +sndstat_registerfile(char *str) +{ + return sndstat_register(NULL, str, NULL); +} + +int +sndstat_unregister(device_t dev) +{ + intrmask_t s; + struct sndstat_entry *ent; + + s = spltty(); + mtx_lock(&sndstat_lock); + SLIST_FOREACH(ent, &sndstat_devlist, link) { + if (ent->dev == dev) { + SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link); + mtx_unlock(&sndstat_lock); + splx(s); + free(ent, M_DEVBUF); + + return 0; + } + } + mtx_unlock(&sndstat_lock); + splx(s); + + return ENXIO; +} + +int +sndstat_unregisterfile(char *str) +{ + intrmask_t s; + struct sndstat_entry *ent; + + s = spltty(); + mtx_lock(&sndstat_lock); + SLIST_FOREACH(ent, &sndstat_devlist, link) { + if (ent->dev == NULL && ent->str == str) { + SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link); + sndstat_files--; + mtx_unlock(&sndstat_lock); + splx(s); + free(ent, M_DEVBUF); + + return 0; + } + } + mtx_unlock(&sndstat_lock); + splx(s); + + return ENXIO; +} + +/************************************************************************/ + +static int +sndstat_prepare(struct sbuf *s) +{ + struct sndstat_entry *ent; + int i, j; + + sbuf_printf(s, "FreeBSD Audio Driver (newpcm)\n"); + if (SLIST_EMPTY(&sndstat_devlist)) { + sbuf_printf(s, "No devices installed.\n"); + sbuf_finish(s); + return sbuf_len(s); + } + + sbuf_printf(s, "Installed devices:\n"); + + for (i = 0; i <= sndstat_maxunit; i++) { + for (j = SS_TYPE_FIRST; j <= SS_TYPE_LAST; j++) { + ent = sndstat_find(j, i); + if (!ent) + continue; + sbuf_printf(s, "%s:", device_get_nameunit(ent->dev)); + sbuf_printf(s, " <%s>", device_get_desc(ent->dev)); + sbuf_printf(s, " %s", ent->str); + if (ent->handler) + ent->handler(s, ent->dev, sndstat_verbose); + else + sbuf_printf(s, " [no handler]"); + sbuf_printf(s, "\n"); + } + } + + if (sndstat_verbose >= 3 && sndstat_files > 0) { + sbuf_printf(s, "\nFile Versions:\n"); + + SLIST_FOREACH(ent, &sndstat_devlist, link) { + if (ent->dev == NULL && ent->str != NULL) + sbuf_printf(s, "%s\n", ent->str); + } + } + + sbuf_finish(s); + return sbuf_len(s); +} + +static int +sndstat_init(void) +{ + mtx_init(&sndstat_lock, "sndstat", NULL, 0); + sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS, UID_ROOT, GID_WHEEL, 0444, "sndstat"); + + return (sndstat_dev != 0)? 0 : ENXIO; +} + +static int +sndstat_uninit(void) +{ + intrmask_t s; + + s = spltty(); + mtx_lock(&sndstat_lock); + if (sndstat_isopen) { + mtx_unlock(&sndstat_lock); + splx(s); + return EBUSY; + } + + if (sndstat_dev) + destroy_dev(sndstat_dev); + sndstat_dev = 0; + + splx(s); + mtx_destroy(&sndstat_lock); + return 0; +} + +int +sndstat_busy(void) +{ + return (sndstat_isopen); +} + +static void +sndstat_sysinit(void *p) +{ + sndstat_init(); +} + +static void +sndstat_sysuninit(void *p) +{ + sndstat_uninit(); +} + +SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL); +SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL); + + diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c new file mode 100644 index 0000000..2b77684 --- /dev/null +++ b/sys/dev/sound/pcm/sound.c @@ -0,0 +1,907 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) + * 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. + */ + +#include <dev/sound/pcm/sound.h> +#include <dev/sound/pcm/vchan.h> +#include <sys/sysctl.h> + +#include "feeder_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +struct snddev_channel { + SLIST_ENTRY(snddev_channel) link; + struct pcm_channel *channel; +}; + +struct snddev_info { + SLIST_HEAD(, snddev_channel) channels; + struct pcm_channel *fakechan; + unsigned devcount, playcount, reccount, vchancount; + unsigned flags; + int inprog; + unsigned int bufsz; + void *devinfo; + device_t dev; + char status[SND_STATUSLEN]; + struct sysctl_ctx_list sysctl_tree; + struct sysctl_oid *sysctl_tree_top; + struct mtx *lock; +}; + +devclass_t pcm_devclass; + +int pcm_veto_load = 1; + +#ifdef USING_DEVFS +int snd_unit = 0; +TUNABLE_INT("hw.snd.unit", &snd_unit); +#endif + +int snd_maxautovchans = 0; +TUNABLE_INT("hw.snd.maxautovchans", &snd_maxautovchans); + +SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver"); + +static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose); + +struct sysctl_ctx_list * +snd_sysctl_tree(device_t dev) +{ + struct snddev_info *d = device_get_softc(dev); + + return &d->sysctl_tree; +} + +struct sysctl_oid * +snd_sysctl_tree_top(device_t dev) +{ + struct snddev_info *d = device_get_softc(dev); + + return d->sysctl_tree_top; +} + +void * +snd_mtxcreate(const char *desc, const char *type) +{ +#ifdef USING_MUTEX + struct mtx *m; + + m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO); + if (m == NULL) + return NULL; + mtx_init(m, desc, type, MTX_RECURSE); + return m; +#else + return (void *)0xcafebabe; +#endif +} + +void +snd_mtxfree(void *m) +{ +#ifdef USING_MUTEX + struct mtx *mtx = m; + + /* mtx_assert(mtx, MA_OWNED); */ + mtx_destroy(mtx); + free(mtx, M_DEVBUF); +#endif +} + +void +snd_mtxassert(void *m) +{ +#ifdef USING_MUTEX +#ifdef INVARIANTS + struct mtx *mtx = m; + + mtx_assert(mtx, MA_OWNED); +#endif +#endif +} +/* +void +snd_mtxlock(void *m) +{ +#ifdef USING_MUTEX + struct mtx *mtx = m; + + mtx_lock(mtx); +#endif +} + +void +snd_mtxunlock(void *m) +{ +#ifdef USING_MUTEX + struct mtx *mtx = m; + + mtx_unlock(mtx); +#endif +} +*/ +int +snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep) +{ +#ifdef USING_MUTEX + flags &= INTR_MPSAFE; + flags |= INTR_TYPE_AV; +#else + flags = INTR_TYPE_AV; +#endif + return bus_setup_intr(dev, res, flags, hand, param, cookiep); +} + +void +pcm_lock(struct snddev_info *d) +{ + snd_mtxlock(d->lock); +} + +void +pcm_unlock(struct snddev_info *d) +{ + snd_mtxunlock(d->lock); +} + +struct pcm_channel * +pcm_getfakechan(struct snddev_info *d) +{ + return d->fakechan; +} + +/* return a locked channel */ +struct pcm_channel * +pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum) +{ + struct pcm_channel *c; + struct snddev_channel *sce; + int err; + + snd_mtxassert(d->lock); + + /* scan for a free channel */ + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + CHN_LOCK(c); + if ((c->direction == direction) && !(c->flags & CHN_F_BUSY)) { + if (chnum == -1 || c->num == chnum) { + c->flags |= CHN_F_BUSY; + c->pid = pid; + return c; + } + } + CHN_UNLOCK(c); + } + + /* no channel available */ + if (direction == PCMDIR_PLAY) { + if ((d->vchancount > 0) && (d->vchancount < snd_maxautovchans)) { + /* try to create a vchan */ + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + if (!SLIST_EMPTY(&c->children)) { + err = vchan_create(c); + if (!err) + return pcm_chnalloc(d, direction, pid, -1); + else + device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err); + } + } + } + } + + return NULL; +} + +/* release a locked channel and unlock it */ +int +pcm_chnrelease(struct pcm_channel *c) +{ + CHN_LOCKASSERT(c); + c->flags &= ~CHN_F_BUSY; + c->pid = -1; + CHN_UNLOCK(c); + return 0; +} + +int +pcm_chnref(struct pcm_channel *c, int ref) +{ + int r; + + CHN_LOCKASSERT(c); + c->refcount += ref; + r = c->refcount; + return r; +} + +int +pcm_inprog(struct snddev_info *d, int delta) +{ + d->inprog += delta; + return d->inprog; +} + +static void +pcm_setmaxautovchans(struct snddev_info *d, int num) +{ + struct pcm_channel *c; + struct snddev_channel *sce; + int err, done; + + if (num > 0 && d->vchancount == 0) { + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + if ((c->direction == PCMDIR_PLAY) && !(c->flags & CHN_F_BUSY)) { + c->flags |= CHN_F_BUSY; + err = vchan_create(c); + if (err) { + device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err); + c->flags &= ~CHN_F_BUSY; + } + return; + } + } + } + if (num == 0 && d->vchancount > 0) { + done = 0; + while (!done) { + done = 1; + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + if ((c->flags & CHN_F_VIRTUAL) && !(c->flags & CHN_F_BUSY)) { + done = 0; + err = vchan_destroy(c); + if (err) + device_printf(d->dev, "vchan_destroy(%s) == %d\n", c->name, err); + break; /* restart */ + } + } + } + } +} + +#ifdef USING_DEVFS +static int +sysctl_hw_snd_unit(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + int error, unit; + + unit = snd_unit; + error = sysctl_handle_int(oidp, &unit, sizeof(unit), req); + if (error == 0 && req->newptr != NULL) { + if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass)) + return EINVAL; + d = devclass_get_softc(pcm_devclass, unit); + if (d == NULL || SLIST_EMPTY(&d->channels)) + return EINVAL; + snd_unit = unit; + } + return (error); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_unit, "I", ""); +#endif + +static int +sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + int i, v, error; + + v = snd_maxautovchans; + error = sysctl_handle_int(oidp, &v, sizeof(v), req); + if (error == 0 && req->newptr != NULL) { + if (v < 0 || v >= SND_MAXVCHANS) + return EINVAL; + if (v != snd_maxautovchans) { + for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!d) + continue; + pcm_setmaxautovchans(d, v); + } + } + snd_maxautovchans = v; + } + return (error); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", ""); + +struct pcm_channel * +pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) +{ + struct pcm_channel *ch; + char *dirs; + int err, *pnum; + + switch(dir) { + case PCMDIR_PLAY: + dirs = "play"; + pnum = &d->playcount; + break; + + case PCMDIR_REC: + dirs = "record"; + pnum = &d->reccount; + break; + + case PCMDIR_VIRTUAL: + dirs = "virtual"; + dir = PCMDIR_PLAY; + pnum = &d->vchancount; + break; + + default: + return NULL; + } + + ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); + if (!ch) + return NULL; + + ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK); + if (!ch->methods) { + free(ch, M_DEVBUF); + + return NULL; + } + + snd_mtxlock(d->lock); + ch->num = (*pnum)++; + snd_mtxunlock(d->lock); + + ch->pid = -1; + ch->parentsnddev = d; + ch->parentchannel = parent; + ch->dev = d->dev; + snprintf(ch->name, 32, "%s:%s:%d", device_get_nameunit(ch->dev), dirs, ch->num); + + err = chn_init(ch, devinfo, dir); + if (err) { + device_printf(d->dev, "chn_init(%s) failed: err = %d\n", ch->name, err); + kobj_delete(ch->methods, M_DEVBUF); + free(ch, M_DEVBUF); + snd_mtxlock(d->lock); + (*pnum)--; + snd_mtxunlock(d->lock); + + return NULL; + } + + return ch; +} + +int +pcm_chn_destroy(struct pcm_channel *ch) +{ + struct snddev_info *d; + int err; + + d = ch->parentsnddev; + err = chn_kill(ch); + if (err) { + device_printf(d->dev, "chn_kill(%s) failed, err = %d\n", ch->name, err); + return err; + } + + kobj_delete(ch->methods, M_DEVBUF); + free(ch, M_DEVBUF); + + return 0; +} + +int +pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch, int mkdev) +{ + struct snddev_channel *sce, *tmp, *after; + int unit = device_get_unit(d->dev); + int x = -1; + + sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO); + if (!sce) { + return ENOMEM; + } + + snd_mtxlock(d->lock); + sce->channel = ch; + if (SLIST_EMPTY(&d->channels)) { + SLIST_INSERT_HEAD(&d->channels, sce, link); + } else { + after = NULL; + SLIST_FOREACH(tmp, &d->channels, link) { + after = tmp; + } + SLIST_INSERT_AFTER(after, sce, link); + } + if (mkdev) + x = d->devcount++; + snd_mtxunlock(d->lock); + + if (mkdev) { + dsp_register(unit, x); + if (ch->direction == PCMDIR_REC) + dsp_registerrec(unit, ch->num); + } + + return 0; +} + +int +pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch, int rmdev) +{ + struct snddev_channel *sce; + int unit = device_get_unit(d->dev); + + snd_mtxlock(d->lock); + SLIST_FOREACH(sce, &d->channels, link) { + if (sce->channel == ch) + goto gotit; + } + snd_mtxunlock(d->lock); + return EINVAL; +gotit: + SLIST_REMOVE(&d->channels, sce, snddev_channel, link); + if (rmdev) { + dsp_unregister(unit, --d->devcount); + if (ch->direction == PCMDIR_REC) + dsp_unregisterrec(unit, ch->num); + } + + if (ch->direction == PCMDIR_REC) + d->reccount--; + else if (ch->flags & CHN_F_VIRTUAL) + d->vchancount--; + else + d->playcount--; + + snd_mtxunlock(d->lock); + free(sce, M_DEVBUF); + + 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; + int err; + + ch = pcm_chn_create(d, NULL, cls, dir, devinfo); + if (!ch) { + device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo); + return ENODEV; + } + + err = pcm_chn_add(d, ch, 1); + if (err) { + device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err); + snd_mtxunlock(d->lock); + pcm_chn_destroy(ch); + return err; + } + + if (snd_maxautovchans > 0 && (d->flags & SD_F_AUTOVCHAN) && + ch->direction == PCMDIR_PLAY && d->vchancount == 0) { + ch->flags |= CHN_F_BUSY; + err = vchan_create(ch); + if (err) { + device_printf(d->dev, "vchan_create(%s) == %d\n", ch->name, err); + ch->flags &= ~CHN_F_BUSY; + } + } + + return err; +} + +static int +pcm_killchan(device_t dev) +{ + struct snddev_info *d = device_get_softc(dev); + struct snddev_channel *sce; + struct pcm_channel *ch; + int error = 0; + + snd_mtxlock(d->lock); + sce = SLIST_FIRST(&d->channels); + snd_mtxunlock(d->lock); + ch = sce->channel; + + error = pcm_chn_remove(d, sce->channel, 1); + if (error) + return (error); + return (pcm_chn_destroy(ch)); +} + +int +pcm_setstatus(device_t dev, char *str) +{ + struct snddev_info *d = device_get_softc(dev); + + snd_mtxlock(d->lock); + strncpy(d->status, str, SND_STATUSLEN); + snd_mtxunlock(d->lock); + return 0; +} + +u_int32_t +pcm_getflags(device_t dev) +{ + struct snddev_info *d = device_get_softc(dev); + + return d->flags; +} + +void +pcm_setflags(device_t dev, u_int32_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 min, unsigned int deflt, unsigned int max) +{ + 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, min, max); + if (x != sz) + device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, min, max, sz); + x = min; + 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; +} + +int +pcm_register(device_t dev, void *devinfo, int numplay, int numrec) +{ + struct snddev_info *d = device_get_softc(dev); + + if (pcm_veto_load) { + device_printf(dev, "disabled due to an error while initialising: %d\n", pcm_veto_load); + + return EINVAL; + } + + d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); + + d->flags = 0; + d->dev = dev; + d->devinfo = devinfo; + d->devcount = 0; + d->reccount = 0; + d->playcount = 0; + d->vchancount = 0; + d->inprog = 0; + + if (((numplay == 0) || (numrec == 0)) && (numplay != numrec)) + d->flags |= SD_F_SIMPLEX; + + d->fakechan = fkchan_setup(dev); + chn_init(d->fakechan, NULL, 0); + +#ifdef SND_DYNSYSCTL + sysctl_ctx_init(&d->sysctl_tree); + d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree, + SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO, + device_get_nameunit(dev), CTLFLAG_RD, 0, ""); + if (d->sysctl_tree_top == NULL) { + sysctl_ctx_free(&d->sysctl_tree); + goto no; + } + SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, ""); +#endif + if (numplay > 0) + vchan_initsys(dev); + if (numplay == 1) + d->flags |= SD_F_AUTOVCHAN; + + sndstat_register(dev, d->status, sndstat_prepare_pcm); + return 0; +no: + snd_mtxfree(d->lock); + return ENXIO; +} + +int +pcm_unregister(device_t dev) +{ + struct snddev_info *d = device_get_softc(dev); + struct snddev_channel *sce; + struct pcm_channel *ch; + + snd_mtxlock(d->lock); + if (d->inprog) { + device_printf(dev, "unregister: operation in progress\n"); + snd_mtxunlock(d->lock); + return EBUSY; + } + if (sndstat_busy() != 0) { + device_printf(dev, "unregister: sndstat busy\n"); + snd_mtxunlock(d->lock); + return EBUSY; + } + SLIST_FOREACH(sce, &d->channels, link) { + ch = sce->channel; + if (ch->refcount > 0) { + device_printf(dev, "unregister: channel %s busy (pid %d)\n", ch->name, ch->pid); + snd_mtxunlock(d->lock); + return EBUSY; + } + } + if (mixer_uninit(dev)) { + device_printf(dev, "unregister: mixer busy\n"); + snd_mtxunlock(d->lock); + return EBUSY; + } + +#ifdef SND_DYNSYSCTL + d->sysctl_tree_top = NULL; + sysctl_ctx_free(&d->sysctl_tree); +#endif + while (!SLIST_EMPTY(&d->channels)) + pcm_killchan(dev); + + chn_kill(d->fakechan); + fkchan_kill(d->fakechan); + + sndstat_unregister(dev); + snd_mtxfree(d->lock); + return 0; +} + +/************************************************************************/ + +static int +sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) +{ + struct snddev_info *d; + struct snddev_channel *sce; + struct pcm_channel *c; + struct pcm_feeder *f; + int pc, rc, vc; + + if (verbose < 1) + return 0; + + d = device_get_softc(dev); + if (!d) + return ENXIO; + + snd_mtxlock(d->lock); + if (!SLIST_EMPTY(&d->channels)) { + pc = rc = vc = 0; + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + if (c->direction == PCMDIR_PLAY) { + if (c->flags & CHN_F_VIRTUAL) + vc++; + else + pc++; + } else + rc++; + } + sbuf_printf(s, " (%dp/%dr/%dv channels%s%s)", d->playcount, d->reccount, d->vchancount, + (d->flags & SD_F_SIMPLEX)? "" : " duplex", +#ifdef USING_DEVFS + (device_get_unit(dev) == snd_unit)? " default" : "" +#else + "" +#endif + ); + if (verbose <= 1) + goto skipverbose; + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + sbuf_printf(s, "\n\t"); + + sbuf_printf(s, "%s[%s]: ", c->parentchannel? c->parentchannel->name : "", c->name); + sbuf_printf(s, "spd %d", c->speed); + if (c->speed != sndbuf_getspd(c->bufhard)) + sbuf_printf(s, "/%d", sndbuf_getspd(c->bufhard)); + sbuf_printf(s, ", fmt 0x%08x", c->format); + if (c->format != sndbuf_getfmt(c->bufhard)) + sbuf_printf(s, "/0x%08x", sndbuf_getfmt(c->bufhard)); + sbuf_printf(s, ", flags %08x", c->flags); + if (c->pid != -1) + sbuf_printf(s, ", pid %d", c->pid); + sbuf_printf(s, "\n\t"); + + if (c->bufhard != NULL && c->bufsoft != NULL) { + sbuf_printf(s, "interrupts %d, ", c->interrupts); + if (c->direction == PCMDIR_REC) + sbuf_printf(s, "overruns %d, hfree %d, sfree %d", + c->xruns, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft)); + else + sbuf_printf(s, "underruns %d, ready %d", + c->xruns, sndbuf_getready(c->bufsoft)); + sbuf_printf(s, "\n\t"); + } + + sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "hardware" : "userland"); + sbuf_printf(s, " -> "); + f = c->feeder; + while (f->source != NULL) + f = f->source; + while (f != NULL) { + sbuf_printf(s, "%s", f->class->name); + if (f->desc->type == FEEDER_FMT) + sbuf_printf(s, "(0x%08x -> 0x%08x)", f->desc->in, f->desc->out); + if (f->desc->type == FEEDER_RATE) + sbuf_printf(s, "(%d -> %d)", FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST)); + if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER) + sbuf_printf(s, "(0x%08x)", f->desc->out); + sbuf_printf(s, " -> "); + f = f->parent; + } + sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "userland" : "hardware"); + } + } else + sbuf_printf(s, " (mixer only)"); +skipverbose: + snd_mtxunlock(d->lock); + + return 0; +} + +/************************************************************************/ + +#ifdef SND_DYNSYSCTL +int +sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + struct snddev_channel *sce; + struct pcm_channel *c; + int err, newcnt, cnt; + + d = oidp->oid_arg1; + + pcm_lock(d); + cnt = 0; + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL)) + cnt++; + } + newcnt = cnt; + + pcm_unlock(d); + err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req); + pcm_lock(d); + /* + * Since we dropped the pcm_lock, reload cnt now as it may + * have changed. + */ + cnt = 0; + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL)) + cnt++; + } + if (err == 0 && req->newptr != NULL) { + if (newcnt < 0 || newcnt > SND_MAXVCHANS) { + pcm_unlock(d); + return EINVAL; + } + + if (newcnt > cnt) { + /* add new vchans - find a parent channel first */ + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + /* not a candidate if not a play channel */ + if (c->direction != PCMDIR_PLAY) + continue; + /* not a candidate if a virtual channel */ + if (c->flags & CHN_F_VIRTUAL) + continue; + /* not a candidate if it's in use */ + if ((c->flags & CHN_F_BUSY) && (SLIST_EMPTY(&c->children))) + continue; + /* + * if we get here we're a nonvirtual play channel, and either + * 1) not busy + * 2) busy with children, not directly open + * + * thus we can add children + */ + goto addok; + } + pcm_unlock(d); + return EBUSY; +addok: + c->flags |= CHN_F_BUSY; + while (err == 0 && newcnt > cnt) { + err = vchan_create(c); + if (err == 0) + cnt++; + } + if (SLIST_EMPTY(&c->children)) + c->flags &= ~CHN_F_BUSY; + } else if (newcnt < cnt) { + while (err == 0 && newcnt < cnt) { + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + if ((c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL) + goto remok; + } + pcm_unlock(d); + return EINVAL; +remok: + err = vchan_destroy(c); + if (err == 0) + cnt--; + } + } + } + + pcm_unlock(d); + return err; +} +#endif + +/************************************************************************/ + +static moduledata_t sndpcm_mod = { + "snd_pcm", + NULL, + NULL +}; +DECLARE_MODULE(snd_pcm, sndpcm_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); +MODULE_VERSION(snd_pcm, PCM_MODVER); diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h new file mode 100644 index 0000000..5038a28 --- /dev/null +++ b/sys/dev/sound/pcm/sound.h @@ -0,0 +1,289 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Copyright by Hannu Savolainen 1995 + * 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. + * + * $FreeBSD$ + */ + +/* + * first, include kernel header files. + */ + +#ifndef _OS_H_ +#define _OS_H_ + +#ifdef _KERNEL +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/ioccom.h> +#include <sys/filio.h> +#include <sys/sockio.h> +#include <sys/fcntl.h> +#include <sys/tty.h> +#include <sys/proc.h> +#include <sys/kernel.h> /* for DATA_SET */ +#include <sys/module.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/uio.h> +#include <sys/syslog.h> +#include <sys/errno.h> +#include <sys/malloc.h> +#include <sys/bus.h> +#if __FreeBSD_version < 500000 +#include <sys/buf.h> +#endif +#include <machine/clock.h> /* for DELAY */ +#include <machine/resource.h> +#include <machine/bus.h> +#include <sys/rman.h> +#include <sys/mman.h> +#include <sys/poll.h> +#include <sys/sbuf.h> +#include <sys/soundcard.h> +#include <sys/sysctl.h> +#include <isa/isavar.h> +#include <sys/kobj.h> +#include <vm/vm.h> +#include <vm/pmap.h> + +#undef USING_MUTEX +#undef USING_DEVFS + +#if __FreeBSD_version > 500000 +#include <sys/lock.h> +#include <sys/mutex.h> + +#define USING_MUTEX +#define USING_DEVFS +#endif + +#define SND_DYNSYSCTL + +#ifndef INTR_MPSAFE +#define INTR_TYPE_AV INTR_TYPE_TTY +#endif + +#ifndef INTR_MPSAFE +#define INTR_MPSAFE 0 +#endif + +struct pcm_channel; +struct pcm_feeder; +struct snd_dbuf; +struct snd_mixer; + +#include <dev/sound/pcm/buffer.h> +#include <dev/sound/pcm/channel.h> +#include <dev/sound/pcm/feeder.h> +#include <dev/sound/pcm/mixer.h> +#include <dev/sound/pcm/dsp.h> + +#define PCM_SOFTC_SIZE 512 + +#define SND_STATUSLEN 64 +/* descriptor of audio device */ +#ifndef ISADMA_WRITE +#define ISADMA_WRITE B_WRITE +#define ISADMA_READ B_READ +#define ISADMA_RAW B_RAW +#endif + +#define PCM_MODVER 1 + +#define PCM_MINVER 1 +#define PCM_PREFVER PCM_MODVER +#define PCM_MAXVER 1 + +/* +PROPOSAL: +each unit needs: +status, mixer, dsp, dspW, audio, sequencer, midi-in, seq2, sndproc = 9 devices +dspW and audio are deprecated. +dsp needs min 64 channels, will give it 256 + +minor = (unit << 20) + (dev << 16) + channel +currently minor = (channel << 16) + (unit << 4) + dev + +nomenclature: + /dev/pcmX/dsp.(0..255) + /dev/pcmX/dspW + /dev/pcmX/audio + /dev/pcmX/status + /dev/pcmX/mixer + [etc.] +*/ + +#define PCMMINOR(x) (minor(x)) +#define PCMCHAN(x) ((PCMMINOR(x) & 0x00ff0000) >> 16) +#define PCMUNIT(x) ((PCMMINOR(x) & 0x000000f0) >> 4) +#define PCMDEV(x) (PCMMINOR(x) & 0x0000000f) +#define PCMMKMINOR(u, d, c) ((((c) & 0xff) << 16) | (((u) & 0x0f) << 4) | ((d) & 0x0f)) + +#define SD_F_SIMPLEX 0x00000001 +#define SD_F_AUTOVCHAN 0x00000002 +#define SD_F_PRIO_RD 0x10000000 +#define SD_F_PRIO_WR 0x20000000 +#define SD_F_PRIO_SET (SD_F_PRIO_RD | SD_F_PRIO_WR) +#define SD_F_DIR_SET 0x40000000 +#define SD_F_TRANSIENT 0xf0000000 + +/* many variables should be reduced to a range. Here define a macro */ +#define RANGE(var, low, high) (var) = \ + (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) +#define DSP_BUFFSIZE (8192) + +/* make figuring out what a format is easier. got AFMT_STEREO already */ +#define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE) +#define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) +#define AFMT_8BIT (AFMT_U8 | AFMT_S8) +#define AFMT_SIGNED (AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) +#define AFMT_BIGENDIAN (AFMT_S16_BE | AFMT_U16_BE) + +struct pcm_channel *fkchan_setup(device_t dev); +int fkchan_kill(struct pcm_channel *c); + +/* + * Major nuber for the sound driver. + */ +#define SND_CDEV_MAJOR 30 + +#define SND_MAXVCHANS 255 + +/* + * Minor numbers for the sound driver. + * + * Unfortunately Creative called the codec chip of SB as a DSP. For this + * reason the /dev/dsp is reserved for digitized audio use. There is a + * device for true DSP processors but it will be called something else. + * In v3.0 it's /dev/sndproc but this could be a temporary solution. + */ + +#define SND_DEV_CTL 0 /* Control port /dev/mixer */ +#define SND_DEV_SEQ 1 /* Sequencer /dev/sequencer */ +#define SND_DEV_MIDIN 2 /* Raw midi access */ +#define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ +#define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ +#define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ +#define SND_DEV_STATUS 6 /* /dev/sndstat */ + /* #7 not in use now. */ +#define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */ +#define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ +#define SND_DEV_PSS SND_DEV_SNDPROC /* ? */ +#define SND_DEV_NORESET 10 +#define SND_DEV_DSPREC 11 /* recording channels */ + +#define DSP_DEFAULT_SPEED 8000 + +#define ON 1 +#define OFF 0 + +extern int pcm_veto_load; +extern int snd_unit; +extern devclass_t pcm_devclass; + +/* + * some macros for debugging purposes + * DDB/DEB to enable/disable debugging stuff + * BVDDB to enable debugging when bootverbose + */ +#define DDB(x) x /* XXX */ +#define BVDDB(x) if (bootverbose) x + +#ifndef DEB +#define DEB(x) +#endif + +SYSCTL_DECL(_hw_snd); + +struct sysctl_ctx_list *snd_sysctl_tree(device_t dev); +struct sysctl_oid *snd_sysctl_tree_top(device_t dev); + +void pcm_lock(struct snddev_info *d); +void pcm_unlock(struct snddev_info *d); +struct pcm_channel *pcm_getfakechan(struct snddev_info *d); +struct pcm_channel *pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum); +int pcm_chnrelease(struct pcm_channel *c); +int pcm_chnref(struct pcm_channel *c, int ref); +int pcm_inprog(struct snddev_info *d, int delta); + +struct pcm_channel *pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo); +int pcm_chn_destroy(struct pcm_channel *ch); +int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch, int mkdev); +int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch, int rmdev); + +int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo); +unsigned int pcm_getbuffersize(device_t dev, unsigned int min, unsigned int deflt, unsigned int max); +int pcm_register(device_t dev, void *devinfo, int numplay, int numrec); +int pcm_unregister(device_t dev); +int pcm_setstatus(device_t dev, char *str); +u_int32_t pcm_getflags(device_t dev); +void pcm_setflags(device_t dev, u_int32_t val); +void *pcm_getdevinfo(device_t dev); + +int snd_setup_intr(device_t dev, struct resource *res, int flags, + driver_intr_t hand, void *param, void **cookiep); + +void *snd_mtxcreate(const char *desc, const char *type); +void snd_mtxfree(void *m); +void snd_mtxassert(void *m); +/* +void snd_mtxlock(void *m); +void snd_mtxunlock(void *m); +*/ +int sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS); + +typedef int (*sndstat_handler)(struct sbuf *s, device_t dev, int verbose); +int sndstat_register(device_t dev, char *str, sndstat_handler handler); +int sndstat_registerfile(char *str); +int sndstat_unregister(device_t dev); +int sndstat_unregisterfile(char *str); +int sndstat_busy(void); + +#define SND_DECLARE_FILE(version) \ + _SND_DECLARE_FILE(__LINE__, version) + +#define _SND_DECLARE_FILE(uniq, version) \ + __SND_DECLARE_FILE(uniq, version) + +#define __SND_DECLARE_FILE(uniq, version) \ + static char sndstat_vinfo[] = version; \ + SYSINIT(sdf_ ## uniq, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, sndstat_registerfile, sndstat_vinfo); \ + SYSUNINIT(sdf_ ## uniq, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, sndstat_unregisterfile, sndstat_vinfo); + +/* usage of flags in device config entry (config file) */ +#define DV_F_DRQ_MASK 0x00000007 /* mask for secondary drq */ +#define DV_F_DUAL_DMA 0x00000010 /* set to use secondary dma channel */ + +/* ought to be made obsolete */ +#define DV_F_DEV_MASK 0x0000ff00 /* force device type/class */ +#define DV_F_DEV_SHIFT 8 /* force device type/class */ + +#define snd_mtxlock(m) mtx_lock(m) +#define snd_mtxunlock(m) mtx_unlock(m) + +#endif /* _KERNEL */ + +#endif /* _OS_H_ */ diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c new file mode 100644 index 0000000..8da3c3e --- /dev/null +++ b/sys/dev/sound/pcm/vchan.c @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2001 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + */ + +#include <dev/sound/pcm/sound.h> +#include <dev/sound/pcm/vchan.h> +#include "feeder_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +struct vchinfo { + u_int32_t spd, fmt, blksz, bps, run; + struct pcm_channel *channel, *parent; + struct pcmchan_caps caps; +}; + +static u_int32_t vchan_fmt[] = { + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + 0 +}; + +static int +vchan_mix_s16(int16_t *to, int16_t *tmp, unsigned int count) +{ + /* + * to is the output buffer, tmp is the input buffer + * count is the number of 16bit samples to mix + */ + int i; + int x; + + for(i = 0; i < count; i++) { + x = to[i]; + x += tmp[i]; + if (x < -32768) { + /* printf("%d + %d = %d (u)\n", to[i], tmp[i], x); */ + x = -32768; + } + if (x > 32767) { + /* printf("%d + %d = %d (o)\n", to[i], tmp[i], x); */ + x = 32767; + } + to[i] = x & 0x0000ffff; + } + return 0; +} + +static int +feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + /* we're going to abuse things a bit */ + struct snd_dbuf *src = source; + struct pcmchan_children *cce; + struct pcm_channel *ch; + int16_t *tmp, *dst; + unsigned int cnt; + + KASSERT(sndbuf_getsize(src) >= count, ("bad bufsize")); + count &= ~1; + bzero(b, count); + + /* + * we are going to use our source as a temporary buffer since it's + * got no other purpose. we obtain our data by traversing the channel + * list of children and calling vchan_mix_* to mix count bytes from each + * into our destination buffer, b + */ + dst = (int16_t *)b; + tmp = (int16_t *)sndbuf_getbuf(src); + bzero(tmp, count); + SLIST_FOREACH(cce, &c->children, link) { + ch = cce->channel; + if (ch->flags & CHN_F_TRIGGERED) { + if (ch->flags & CHN_F_MAPPED) + sndbuf_acquire(ch->bufsoft, NULL, sndbuf_getfree(ch->bufsoft)); + cnt = FEEDER_FEED(ch->feeder, ch, (u_int8_t *)tmp, count, ch->bufsoft); + vchan_mix_s16(dst, tmp, cnt / 2); + } + } + + return count; +} + +static struct pcm_feederdesc feeder_vchan_s16_desc[] = { + {FEEDER_MIXER, AFMT_S16_LE, AFMT_S16_LE, 0}, + {FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_vchan_s16_methods[] = { + KOBJMETHOD(feeder_feed, feed_vchan_s16), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_vchan_s16, 2, NULL); + +/************************************************************/ + +static void * +vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + struct vchinfo *ch; + struct pcm_channel *parent = devinfo; + + KASSERT(dir == PCMDIR_PLAY, ("vchan_init: bad direction")); + ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); + ch->parent = parent; + ch->channel = c; + ch->fmt = AFMT_U8; + ch->spd = DSP_DEFAULT_SPEED; + ch->blksz = 2048; + + c->flags |= CHN_F_VIRTUAL; + + return ch; +} + +static int +vchan_free(kobj_t obj, void *data) +{ + return 0; +} + +static int +vchan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct vchinfo *ch = data; + struct pcm_channel *parent = ch->parent; + + ch->fmt = format; + ch->bps = 1; + ch->bps <<= (ch->fmt & AFMT_STEREO)? 1 : 0; + ch->bps <<= (ch->fmt & AFMT_16BIT)? 1 : 0; + ch->bps <<= (ch->fmt & AFMT_32BIT)? 2 : 0; + chn_notify(parent, CHN_N_FORMAT); + return 0; +} + +static int +vchan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct vchinfo *ch = data; + struct pcm_channel *parent = ch->parent; + + ch->spd = speed; + chn_notify(parent, CHN_N_RATE); + return speed; +} + +static int +vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct vchinfo *ch = data; + struct pcm_channel *parent = ch->parent; + int prate, crate; + + ch->blksz = blocksize; + chn_notify(parent, CHN_N_BLOCKSIZE); + + crate = ch->spd * ch->bps; + prate = sndbuf_getspd(parent->bufhard) * sndbuf_getbps(parent->bufhard); + blocksize = sndbuf_getblksz(parent->bufhard); + blocksize *= prate; + blocksize /= crate; + + return blocksize; +} + +static int +vchan_trigger(kobj_t obj, void *data, int go) +{ + struct vchinfo *ch = data; + struct pcm_channel *parent = ch->parent; + + if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + return 0; + + ch->run = (go == PCMTRIG_START)? 1 : 0; + chn_notify(parent, CHN_N_TRIGGER); + + return 0; +} + +static struct pcmchan_caps * +vchan_getcaps(kobj_t obj, void *data) +{ + struct vchinfo *ch = data; + + ch->caps.minspeed = sndbuf_getspd(ch->parent->bufhard); + ch->caps.maxspeed = ch->caps.minspeed; + ch->caps.fmtlist = vchan_fmt; + ch->caps.caps = 0; + + return &ch->caps; +} + +static kobj_method_t vchan_methods[] = { + KOBJMETHOD(channel_init, vchan_init), + KOBJMETHOD(channel_free, vchan_free), + KOBJMETHOD(channel_setformat, vchan_setformat), + KOBJMETHOD(channel_setspeed, vchan_setspeed), + KOBJMETHOD(channel_setblocksize, vchan_setblocksize), + KOBJMETHOD(channel_trigger, vchan_trigger), + KOBJMETHOD(channel_getcaps, vchan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(vchan); + +/* virtual channel interface */ + +int +vchan_create(struct pcm_channel *parent) +{ + struct snddev_info *d = parent->parentsnddev; + struct pcmchan_children *pce; + struct pcm_channel *child; + int err, first; + + pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO); + if (!pce) { + return ENOMEM; + } + + /* create a new playback channel */ + child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent); + if (!child) { + free(pce, M_DEVBUF); + return ENODEV; + } + + CHN_LOCK(parent); + if (!(parent->flags & CHN_F_BUSY)) { + CHN_UNLOCK(parent); + return EBUSY; + } + + first = SLIST_EMPTY(&parent->children); + /* add us to our parent channel's children */ + pce->channel = child; + SLIST_INSERT_HEAD(&parent->children, pce, link); + CHN_UNLOCK(parent); + + /* add us to our grandparent's channel list */ + err = pcm_chn_add(d, child, !first); + if (err) { + pcm_chn_destroy(child); + free(pce, M_DEVBUF); + } + + /* XXX gross ugly hack, murder death kill */ + if (first && !err) { + err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE); + if (err) + printf("chn_reset: %d\n", err); + err = chn_setspeed(parent, 44100); + if (err) + printf("chn_setspeed: %d\n", err); + } + + return err; +} + +int +vchan_destroy(struct pcm_channel *c) +{ + struct pcm_channel *parent = c->parentchannel; + struct snddev_info *d = parent->parentsnddev; + struct pcmchan_children *pce; + int err, last; + + CHN_LOCK(parent); + if (!(parent->flags & CHN_F_BUSY)) { + CHN_UNLOCK(parent); + return EBUSY; + } + if (SLIST_EMPTY(&parent->children)) { + CHN_UNLOCK(parent); + return EINVAL; + } + + /* remove us from our parent's children list */ + SLIST_FOREACH(pce, &parent->children, link) { + if (pce->channel == c) + goto gotch; + } + CHN_UNLOCK(parent); + return EINVAL; +gotch: + SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); + free(pce, M_DEVBUF); + + last = SLIST_EMPTY(&parent->children); + if (last) + parent->flags &= ~CHN_F_BUSY; + + /* remove us from our grantparent's channel list */ + err = pcm_chn_remove(d, c, !last); + if (err) + return err; + + CHN_UNLOCK(parent); + /* destroy ourselves */ + err = pcm_chn_destroy(c); + + return err; +} + +int +vchan_initsys(device_t dev) +{ +#ifdef SND_DYNSYSCTL + struct snddev_info *d; + + d = device_get_softc(dev); + SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_hw_snd_vchans, "I", ""); +#endif + + return 0; +} + + diff --git a/sys/dev/sound/pcm/vchan.h b/sys/dev/sound/pcm/vchan.h new file mode 100644 index 0000000..d3121e9 --- /dev/null +++ b/sys/dev/sound/pcm/vchan.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2001 Cameron Grant <gandalf@vilnya.demon.co.uk> + * 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. + * + * $FreeBSD$ + */ + +int vchan_create(struct pcm_channel *parent); +int vchan_destroy(struct pcm_channel *c); +int vchan_initsys(device_t dev); + + |