1
0

Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
This commit is contained in:
Linus Torvalds
2005-04-16 15:20:36 -07:00
commit 1da177e4c3
17291 changed files with 6718755 additions and 0 deletions

89
sound/Kconfig Normal file
View File

@@ -0,0 +1,89 @@
# sound/Config.in
#
menu "Sound"
config SOUND
tristate "Sound card support"
help
If you have a sound card in your computer, i.e. if it can say more
than an occasional beep, say Y. Be sure to have all the information
about your sound card and its configuration down (I/O port,
interrupt and DMA channel), because you will be asked for it.
You want to read the Sound-HOWTO, available from
<http://www.tldp.org/docs.html#howto>. General information about
the modular sound system is contained in the files
<file:Documentation/sound/oss/Introduction>. The file
<file:Documentation/sound/oss/README.OSS> contains some slightly
outdated but still useful information as well. Newer sound
driver documentation is found in <file:Documentation/sound/alsa/*>.
If you have a PnP sound card and you want to configure it at boot
time using the ISA PnP tools (read
<http://www.roestock.demon.co.uk/isapnptools/>), then you need to
compile the sound card support as a module and load that module
after the PnP configuration is finished. To do this, choose M here
and read <file:Documentation/sound/oss/README.modules>; the module
will be called soundcore.
I'm told that even without a sound card, you can make your computer
say more than an occasional beep, by programming the PC speaker.
Kernel patches and supporting utilities to do that are in the pcsp
package, available at <ftp://ftp.infradead.org/pub/pcsp/>.
source "sound/oss/dmasound/Kconfig"
if !M68K
menu "Advanced Linux Sound Architecture"
depends on SOUND!=n
config SND
tristate "Advanced Linux Sound Architecture"
depends on SOUND
source "sound/core/Kconfig"
source "sound/drivers/Kconfig"
source "sound/isa/Kconfig"
source "sound/pci/Kconfig"
source "sound/ppc/Kconfig"
source "sound/arm/Kconfig"
source "sound/mips/Kconfig"
# the following will depenend on the order of config.
# here assuming USB is defined before ALSA
source "sound/usb/Kconfig"
# the following will depenend on the order of config.
# here assuming PCMCIA is defined before ALSA
source "sound/pcmcia/Kconfig"
source "sound/sparc/Kconfig"
source "sound/parisc/Kconfig"
endmenu
menu "Open Sound System"
depends on SOUND!=n && (BROKEN || (!SPARC32 && !SPARC64))
config SOUND_PRIME
tristate "Open Sound System (DEPRECATED)"
depends on SOUND
help
Say 'Y' or 'M' to enable Open Sound System drivers.
source "sound/oss/Kconfig"
endmenu
endif
endmenu

13
sound/Makefile Normal file
View File

@@ -0,0 +1,13 @@
# Makefile for the Linux sound card driver
#
obj-$(CONFIG_SOUND) += soundcore.o
obj-$(CONFIG_SOUND_PRIME) += oss/
obj-$(CONFIG_DMASOUND) += oss/
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ synth/ usb/ sparc/ parisc/ pcmcia/ mips/
ifeq ($(CONFIG_SND),y)
obj-y += last.o
endif
soundcore-objs := sound_core.o sound_firmware.o

18
sound/arm/Kconfig Normal file
View File

@@ -0,0 +1,18 @@
# ALSA ARM drivers
menu "ALSA ARM devices"
depends on SND!=n && ARM
config SND_SA11XX_UDA1341
tristate "SA11xx UDA1341TS driver (iPaq H3600)"
depends on ARCH_SA1100 && SND && L3
select SND_PCM
help
Say Y here if you have a Compaq iPaq H3x00 handheld computer
and want to use its Philips UDA 1341 audio chip.
To compile this driver as a module, choose M here: the module
will be called snd-sa11xx-uda1341.
endmenu

8
sound/arm/Makefile Normal file
View File

@@ -0,0 +1,8 @@
#
# Makefile for ALSA
#
snd-sa11xx-uda1341-objs := sa11xx-uda1341.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_SA11XX_UDA1341) += snd-sa11xx-uda1341.o

973
sound/arm/sa11xx-uda1341.c Normal file
View File

@@ -0,0 +1,973 @@
/*
* Driver for Philips UDA1341TS on Compaq iPAQ H3600 soundcard
* Copyright (C) 2002 Tomas Kasparek <tomas.kasparek@seznam.cz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License.
*
* History:
*
* 2002-03-13 Tomas Kasparek initial release - based on h3600-uda1341.c from OSS
* 2002-03-20 Tomas Kasparek playback over ALSA is working
* 2002-03-28 Tomas Kasparek playback over OSS emulation is working
* 2002-03-29 Tomas Kasparek basic capture is working (native ALSA)
* 2002-03-29 Tomas Kasparek capture is working (OSS emulation)
* 2002-04-04 Tomas Kasparek better rates handling (allow non-standard rates)
* 2003-02-14 Brian Avery fixed full duplex mode, other updates
* 2003-02-20 Tomas Kasparek merged updates by Brian (except HAL)
* 2003-04-19 Jaroslav Kysela recoded DMA stuff to follow 2.4.18rmk3-hh24 kernel
* working suspend and resume
* 2003-04-28 Tomas Kasparek updated work by Jaroslav to compile it under 2.5.x again
* merged HAL layer (patches from Brian)
*/
/* $Id: sa11xx-uda1341.c,v 1.21 2005/01/28 19:34:04 tiwai Exp $ */
/***************************************************************************************************
*
* To understand what Alsa Drivers should be doing look at "Writing an Alsa Driver" by Takashi Iwai
* available in the Alsa doc section on the website
*
* A few notes to make things clearer. The UDA1341 is hooked up to Serial port 4 on the SA1100.
* We are using SSP mode to talk to the UDA1341. The UDA1341 bit & wordselect clocks are generated
* by this UART. Unfortunately, the clock only runs if the transmit buffer has something in it.
* So, if we are just recording, we feed the transmit DMA stream a bunch of 0x0000 so that the
* transmit buffer is full and the clock keeps going. The zeroes come from FLUSH_BASE_PHYS which
* is a mem loc that always decodes to 0's w/ no off chip access.
*
* Some alsa terminology:
* frame => num_channels * sample_size e.g stereo 16 bit is 2 * 16 = 32 bytes
* period => the least number of bytes that will generate an interrupt e.g. we have a 1024 byte
* buffer and 4 periods in the runtime structure this means we'll get an int every 256
* bytes or 4 times per buffer.
* A number of the sizes are in frames rather than bytes, use frames_to_bytes and
* bytes_to_frames to convert. The easiest way to tell the units is to look at the
* type i.e. runtime-> buffer_size is in frames and its type is snd_pcm_uframes_t
*
* Notes about the pointer fxn:
* The pointer fxn needs to return the offset into the dma buffer in frames.
* Interrupts must be blocked before calling the dma_get_pos fxn to avoid race with interrupts.
*
* Notes about pause/resume
* Implementing this would be complicated so it's skipped. The problem case is:
* A full duplex connection is going, then play is paused. At this point you need to start xmitting
* 0's to keep the record active which means you cant just freeze the dma and resume it later you'd
* need to save off the dma info, and restore it properly on a resume. Yeach!
*
* Notes about transfer methods:
* The async write calls fail. I probably need to implement something else to support them?
*
***************************************************************************************************/
#include <linux/config.h>
#include <sound/driver.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/delay.h>
#include <linux/slab.h>
#ifdef CONFIG_PM
#include <linux/pm.h>
#endif
#include <asm/hardware.h>
#include <asm/arch/h3600.h>
#include <asm/mach-types.h>
#include <asm/dma.h>
#ifdef CONFIG_H3600_HAL
#include <asm/semaphore.h>
#include <asm/uaccess.h>
#include <asm/arch/h3600_hal.h>
#endif
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <linux/l3/l3.h>
#undef DEBUG_MODE
#undef DEBUG_FUNCTION_NAMES
#include <sound/uda1341.h>
/*
* FIXME: Is this enough as autodetection of 2.4.X-rmkY-hhZ kernels?
* We use DMA stuff from 2.4.18-rmk3-hh24 here to be able to compile this
* module for Familiar 0.6.1
*/
#ifdef CONFIG_H3600_HAL
#define HH_VERSION 1
#endif
/* {{{ Type definitions */
MODULE_AUTHOR("Tomas Kasparek <tomas.kasparek@seznam.cz>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SA1100/SA1111 + UDA1341TS driver for ALSA");
MODULE_SUPPORTED_DEVICE("{{UDA1341,iPAQ H3600 UDA1341TS}}");
static char *id = NULL; /* ID for this card */
module_param(id, charp, 0444);
MODULE_PARM_DESC(id, "ID string for SA1100/SA1111 + UDA1341TS soundcard.");
typedef struct audio_stream {
char *id; /* identification string */
int stream_id; /* numeric identification */
dma_device_t dma_dev; /* device identifier for DMA */
#ifdef HH_VERSION
dmach_t dmach; /* dma channel identification */
#else
dma_regs_t *dma_regs; /* points to our DMA registers */
#endif
int active:1; /* we are using this stream for transfer now */
int period; /* current transfer period */
int periods; /* current count of periods registerd in the DMA engine */
int tx_spin; /* are we recoding - flag used to do DMA trans. for sync */
unsigned int old_offset;
spinlock_t dma_lock; /* for locking in DMA operations (see dma-sa1100.c in the kernel) */
snd_pcm_substream_t *stream;
}audio_stream_t;
typedef struct snd_card_sa11xx_uda1341 {
snd_card_t *card;
struct l3_client *uda1341;
snd_pcm_t *pcm;
long samplerate;
audio_stream_t s[2]; /* playback & capture */
} sa11xx_uda1341_t;
static struct snd_card_sa11xx_uda1341 *sa11xx_uda1341 = NULL;
static unsigned int rates[] = {
8000, 10666, 10985, 14647,
16000, 21970, 22050, 24000,
29400, 32000, 44100, 48000,
};
static snd_pcm_hw_constraint_list_t hw_constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
/* }}} */
/* {{{ Clock and sample rate stuff */
/*
* Stop-gap solution until rest of hh.org HAL stuff is merged.
*/
#define GPIO_H3600_CLK_SET0 GPIO_GPIO (12)
#define GPIO_H3600_CLK_SET1 GPIO_GPIO (13)
#ifdef CONFIG_SA1100_H3XXX
#define clr_sa11xx_uda1341_egpio(x) clr_h3600_egpio(x)
#define set_sa11xx_uda1341_egpio(x) set_h3600_egpio(x)
#else
#error This driver could serve H3x00 handhelds only!
#endif
static void sa11xx_uda1341_set_audio_clock(long val)
{
switch (val) {
case 24000: case 32000: case 48000: /* 00: 12.288 MHz */
GPCR = GPIO_H3600_CLK_SET0 | GPIO_H3600_CLK_SET1;
break;
case 22050: case 29400: case 44100: /* 01: 11.2896 MHz */
GPSR = GPIO_H3600_CLK_SET0;
GPCR = GPIO_H3600_CLK_SET1;
break;
case 8000: case 10666: case 16000: /* 10: 4.096 MHz */
GPCR = GPIO_H3600_CLK_SET0;
GPSR = GPIO_H3600_CLK_SET1;
break;
case 10985: case 14647: case 21970: /* 11: 5.6245 MHz */
GPSR = GPIO_H3600_CLK_SET0 | GPIO_H3600_CLK_SET1;
break;
}
}
static void sa11xx_uda1341_set_samplerate(sa11xx_uda1341_t *sa11xx_uda1341, long rate)
{
int clk_div = 0;
int clk=0;
/* We don't want to mess with clocks when frames are in flight */
Ser4SSCR0 &= ~SSCR0_SSE;
/* wait for any frame to complete */
udelay(125);
/*
* We have the following clock sources:
* 4.096 MHz, 5.6245 MHz, 11.2896 MHz, 12.288 MHz
* Those can be divided either by 256, 384 or 512.
* This makes up 12 combinations for the following samplerates...
*/
if (rate >= 48000)
rate = 48000;
else if (rate >= 44100)
rate = 44100;
else if (rate >= 32000)
rate = 32000;
else if (rate >= 29400)
rate = 29400;
else if (rate >= 24000)
rate = 24000;
else if (rate >= 22050)
rate = 22050;
else if (rate >= 21970)
rate = 21970;
else if (rate >= 16000)
rate = 16000;
else if (rate >= 14647)
rate = 14647;
else if (rate >= 10985)
rate = 10985;
else if (rate >= 10666)
rate = 10666;
else
rate = 8000;
/* Set the external clock generator */
#ifdef CONFIG_H3600_HAL
h3600_audio_clock(rate);
#else
sa11xx_uda1341_set_audio_clock(rate);
#endif
/* Select the clock divisor */
switch (rate) {
case 8000:
case 10985:
case 22050:
case 24000:
clk = F512;
clk_div = SSCR0_SerClkDiv(16);
break;
case 16000:
case 21970:
case 44100:
case 48000:
clk = F256;
clk_div = SSCR0_SerClkDiv(8);
break;
case 10666:
case 14647:
case 29400:
case 32000:
clk = F384;
clk_div = SSCR0_SerClkDiv(12);
break;
}
/* FMT setting should be moved away when other FMTs are added (FIXME) */
l3_command(sa11xx_uda1341->uda1341, CMD_FORMAT, (void *)LSB16);
l3_command(sa11xx_uda1341->uda1341, CMD_FS, (void *)clk);
Ser4SSCR0 = (Ser4SSCR0 & ~0xff00) + clk_div + SSCR0_SSE;
sa11xx_uda1341->samplerate = rate;
}
/* }}} */
/* {{{ HW init and shutdown */
static void sa11xx_uda1341_audio_init(sa11xx_uda1341_t *sa11xx_uda1341)
{
unsigned long flags;
/* Setup DMA stuff */
sa11xx_uda1341->s[SNDRV_PCM_STREAM_PLAYBACK].id = "UDA1341 out";
sa11xx_uda1341->s[SNDRV_PCM_STREAM_PLAYBACK].stream_id = SNDRV_PCM_STREAM_PLAYBACK;
sa11xx_uda1341->s[SNDRV_PCM_STREAM_PLAYBACK].dma_dev = DMA_Ser4SSPWr;
sa11xx_uda1341->s[SNDRV_PCM_STREAM_CAPTURE].id = "UDA1341 in";
sa11xx_uda1341->s[SNDRV_PCM_STREAM_CAPTURE].stream_id = SNDRV_PCM_STREAM_CAPTURE;
sa11xx_uda1341->s[SNDRV_PCM_STREAM_CAPTURE].dma_dev = DMA_Ser4SSPRd;
/* Initialize the UDA1341 internal state */
/* Setup the uarts */
local_irq_save(flags);
GAFR |= (GPIO_SSP_CLK);
GPDR &= ~(GPIO_SSP_CLK);
Ser4SSCR0 = 0;
Ser4SSCR0 = SSCR0_DataSize(16) + SSCR0_TI + SSCR0_SerClkDiv(8);
Ser4SSCR1 = SSCR1_SClkIactL + SSCR1_SClk1P + SSCR1_ExtClk;
Ser4SSCR0 |= SSCR0_SSE;
local_irq_restore(flags);
/* Enable the audio power */
#ifdef CONFIG_H3600_HAL
h3600_audio_power(AUDIO_RATE_DEFAULT);
#else
clr_sa11xx_uda1341_egpio(IPAQ_EGPIO_CODEC_NRESET);
set_sa11xx_uda1341_egpio(IPAQ_EGPIO_AUDIO_ON);
set_sa11xx_uda1341_egpio(IPAQ_EGPIO_QMUTE);
#endif
/* Wait for the UDA1341 to wake up */
mdelay(1); //FIXME - was removed by Perex - Why?
/* Initialize the UDA1341 internal state */
l3_open(sa11xx_uda1341->uda1341);
/* external clock configuration (after l3_open - regs must be initialized */
sa11xx_uda1341_set_samplerate(sa11xx_uda1341, sa11xx_uda1341->samplerate);
/* Wait for the UDA1341 to wake up */
set_sa11xx_uda1341_egpio(IPAQ_EGPIO_CODEC_NRESET);
mdelay(1);
/* make the left and right channels unswapped (flip the WS latch) */
Ser4SSDR = 0;
#ifdef CONFIG_H3600_HAL
h3600_audio_mute(0);
#else
clr_sa11xx_uda1341_egpio(IPAQ_EGPIO_QMUTE);
#endif
}
static void sa11xx_uda1341_audio_shutdown(sa11xx_uda1341_t *sa11xx_uda1341)
{
/* mute on */
#ifdef CONFIG_H3600_HAL
h3600_audio_mute(1);
#else
set_sa11xx_uda1341_egpio(IPAQ_EGPIO_QMUTE);
#endif
/* disable the audio power and all signals leading to the audio chip */
l3_close(sa11xx_uda1341->uda1341);
Ser4SSCR0 = 0;
clr_sa11xx_uda1341_egpio(IPAQ_EGPIO_CODEC_NRESET);
/* power off and mute off */
/* FIXME - is muting off necesary??? */
#ifdef CONFIG_H3600_HAL
h3600_audio_power(0);
h3600_audio_mute(0);
#else
clr_sa11xx_uda1341_egpio(IPAQ_EGPIO_AUDIO_ON);
clr_sa11xx_uda1341_egpio(IPAQ_EGPIO_QMUTE);
#endif
}
/* }}} */
/* {{{ DMA staff */
/*
* these are the address and sizes used to fill the xmit buffer
* so we can get a clock in record only mode
*/
#define FORCE_CLOCK_ADDR (dma_addr_t)FLUSH_BASE_PHYS
#define FORCE_CLOCK_SIZE 4096 // was 2048
// FIXME Why this value exactly - wrote comment
#define DMA_BUF_SIZE 8176 /* <= MAX_DMA_SIZE from asm/arch-sa1100/dma.h */
#ifdef HH_VERSION
static int audio_dma_request(audio_stream_t *s, void (*callback)(void *, int))
{
int ret;
ret = sa1100_request_dma(&s->dmach, s->id, s->dma_dev);
if (ret < 0) {
printk(KERN_ERR "unable to grab audio dma 0x%x\n", s->dma_dev);
return ret;
}
sa1100_dma_set_callback(s->dmach, callback);
return 0;
}
static inline void audio_dma_free(audio_stream_t *s)
{
sa1100_free_dma(s->dmach);
s->dmach = -1;
}
#else
static int audio_dma_request(audio_stream_t *s, void (*callback)(void *))
{
int ret;
ret = sa1100_request_dma(s->dma_dev, s->id, callback, s, &s->dma_regs);
if (ret < 0)
printk(KERN_ERR "unable to grab audio dma 0x%x\n", s->dma_dev);
return ret;
}
static void audio_dma_free(audio_stream_t *s)
{
sa1100_free_dma((s)->dma_regs);
(s)->dma_regs = 0;
}
#endif
static u_int audio_get_dma_pos(audio_stream_t *s)
{
snd_pcm_substream_t * substream = s->stream;
snd_pcm_runtime_t *runtime = substream->runtime;
unsigned int offset;
unsigned long flags;
dma_addr_t addr;
// this must be called w/ interrupts locked out see dma-sa1100.c in the kernel
spin_lock_irqsave(&s->dma_lock, flags);
#ifdef HH_VERSION
sa1100_dma_get_current(s->dmach, NULL, &addr);
#else
addr = sa1100_get_dma_pos((s)->dma_regs);
#endif
offset = addr - runtime->dma_addr;
spin_unlock_irqrestore(&s->dma_lock, flags);
offset = bytes_to_frames(runtime,offset);
if (offset >= runtime->buffer_size)
offset = 0;
return offset;
}
/*
* this stops the dma and clears the dma ptrs
*/
static void audio_stop_dma(audio_stream_t *s)
{
unsigned long flags;
spin_lock_irqsave(&s->dma_lock, flags);
s->active = 0;
s->period = 0;
/* this stops the dma channel and clears the buffer ptrs */
#ifdef HH_VERSION
sa1100_dma_flush_all(s->dmach);
#else
sa1100_clear_dma(s->dma_regs);
#endif
spin_unlock_irqrestore(&s->dma_lock, flags);
}
static void audio_process_dma(audio_stream_t *s)
{
snd_pcm_substream_t *substream = s->stream;
snd_pcm_runtime_t *runtime;
unsigned int dma_size;
unsigned int offset;
int ret;
/* we are requested to process synchronization DMA transfer */
if (s->tx_spin) {
snd_assert(s->stream_id == SNDRV_PCM_STREAM_PLAYBACK, return);
/* fill the xmit dma buffers and return */
#ifdef HH_VERSION
sa1100_dma_set_spin(s->dmach, FORCE_CLOCK_ADDR, FORCE_CLOCK_SIZE);
#else
while (1) {
ret = sa1100_start_dma(s->dma_regs, FORCE_CLOCK_ADDR, FORCE_CLOCK_SIZE);
if (ret)
return;
}
#endif
return;
}
/* must be set here - only valid for running streams, not for forced_clock dma fills */
runtime = substream->runtime;
while (s->active && s->periods < runtime->periods) {
dma_size = frames_to_bytes(runtime, runtime->period_size);
if (s->old_offset) {
/* a little trick, we need resume from old position */
offset = frames_to_bytes(runtime, s->old_offset - 1);
s->old_offset = 0;
s->periods = 0;
s->period = offset / dma_size;
offset %= dma_size;
dma_size = dma_size - offset;
if (!dma_size)
continue; /* special case */
} else {
offset = dma_size * s->period;
snd_assert(dma_size <= DMA_BUF_SIZE, );
}
#ifdef HH_VERSION
ret = sa1100_dma_queue_buffer(s->dmach, s, runtime->dma_addr + offset, dma_size);
if (ret)
return; //FIXME
#else
ret = sa1100_start_dma((s)->dma_regs, runtime->dma_addr + offset, dma_size);
if (ret) {
printk(KERN_ERR "audio_process_dma: cannot queue DMA buffer (%i)\n", ret);
return;
}
#endif
s->period++;
s->period %= runtime->periods;
s->periods++;
}
}
#ifdef HH_VERSION
static void audio_dma_callback(void *data, int size)
#else
static void audio_dma_callback(void *data)
#endif
{
audio_stream_t *s = data;
/*
* If we are getting a callback for an active stream then we inform
* the PCM middle layer we've finished a period
*/
if (s->active)
snd_pcm_period_elapsed(s->stream);
spin_lock(&s->dma_lock);
if (!s->tx_spin && s->periods > 0)
s->periods--;
audio_process_dma(s);
spin_unlock(&s->dma_lock);
}
/* }}} */
/* {{{ PCM setting */
/* {{{ trigger & timer */
static int snd_sa11xx_uda1341_trigger(snd_pcm_substream_t * substream, int cmd)
{
sa11xx_uda1341_t *chip = snd_pcm_substream_chip(substream);
int stream_id = substream->pstr->stream;
audio_stream_t *s = &chip->s[stream_id];
audio_stream_t *s1 = &chip->s[stream_id ^ 1];
int err = 0;
/* note local interrupts are already disabled in the midlevel code */
spin_lock(&s->dma_lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
/* now we need to make sure a record only stream has a clock */
if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1->active) {
/* we need to force fill the xmit DMA with zeros */
s1->tx_spin = 1;
audio_process_dma(s1);
}
/* this case is when you were recording then you turn on a
* playback stream so we stop (also clears it) the dma first,
* clear the sync flag and then we let it turned on
*/
else {
s->tx_spin = 0;
}
/* requested stream startup */
s->active = 1;
audio_process_dma(s);
break;
case SNDRV_PCM_TRIGGER_STOP:
/* requested stream shutdown */
audio_stop_dma(s);
/*
* now we need to make sure a record only stream has a clock
* so if we're stopping a playback with an active capture
* we need to turn the 0 fill dma on for the xmit side
*/
if (stream_id == SNDRV_PCM_STREAM_PLAYBACK && s1->active) {
/* we need to force fill the xmit DMA with zeros */
s->tx_spin = 1;
audio_process_dma(s);
}
/*
* we killed a capture only stream, so we should also kill
* the zero fill transmit
*/
else {
if (s1->tx_spin) {
s1->tx_spin = 0;
audio_stop_dma(s1);
}
}
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
s->active = 0;
#ifdef HH_VERSION
sa1100_dma_stop(s->dmach);
#else
//FIXME - DMA API
#endif
s->old_offset = audio_get_dma_pos(s) + 1;
#ifdef HH_VERSION
sa1100_dma_flush_all(s->dmach);
#else
//FIXME - DMA API
#endif
s->periods = 0;
break;
case SNDRV_PCM_TRIGGER_RESUME:
s->active = 1;
s->tx_spin = 0;
audio_process_dma(s);
if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1->active) {
s1->tx_spin = 1;
audio_process_dma(s1);
}
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
#ifdef HH_VERSION
sa1100_dma_stop(s->dmach);
#else
//FIXME - DMA API
#endif
s->active = 0;
if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) {
if (s1->active) {
s->tx_spin = 1;
s->old_offset = audio_get_dma_pos(s) + 1;
#ifdef HH_VERSION
sa1100_dma_flush_all(s->dmach);
#else
//FIXME - DMA API
#endif
audio_process_dma(s);
}
} else {
if (s1->tx_spin) {
s1->tx_spin = 0;
#ifdef HH_VERSION
sa1100_dma_flush_all(s1->dmach);
#else
//FIXME - DMA API
#endif
}
}
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
s->active = 1;
if (s->old_offset) {
s->tx_spin = 0;
audio_process_dma(s);
break;
}
if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1->active) {
s1->tx_spin = 1;
audio_process_dma(s1);
}
#ifdef HH_VERSION
sa1100_dma_resume(s->dmach);
#else
//FIXME - DMA API
#endif
break;
default:
err = -EINVAL;
break;
}
spin_unlock(&s->dma_lock);
return err;
}
static int snd_sa11xx_uda1341_prepare(snd_pcm_substream_t * substream)
{
sa11xx_uda1341_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
audio_stream_t *s = &chip->s[substream->pstr->stream];
/* set requested samplerate */
sa11xx_uda1341_set_samplerate(chip, runtime->rate);
/* set requestd format when available */
/* set FMT here !!! FIXME */
s->period = 0;
s->periods = 0;
return 0;
}
static snd_pcm_uframes_t snd_sa11xx_uda1341_pointer(snd_pcm_substream_t * substream)
{
sa11xx_uda1341_t *chip = snd_pcm_substream_chip(substream);
return audio_get_dma_pos(&chip->s[substream->pstr->stream]);
}
/* }}} */
static snd_pcm_hardware_t snd_sa11xx_uda1341_capture =
{
.info = (SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |\
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
SNDRV_PCM_RATE_KNOT),
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 64*1024,
.period_bytes_min = 64,
.period_bytes_max = DMA_BUF_SIZE,
.periods_min = 2,
.periods_max = 255,
.fifo_size = 0,
};
static snd_pcm_hardware_t snd_sa11xx_uda1341_playback =
{
.info = (SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |\
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
SNDRV_PCM_RATE_KNOT),
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 64*1024,
.period_bytes_min = 64,
.period_bytes_max = DMA_BUF_SIZE,
.periods_min = 2,
.periods_max = 255,
.fifo_size = 0,
};
static int snd_card_sa11xx_uda1341_open(snd_pcm_substream_t * substream)
{
sa11xx_uda1341_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
int stream_id = substream->pstr->stream;
int err;
chip->s[stream_id].stream = substream;
if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = snd_sa11xx_uda1341_playback;
else
runtime->hw = snd_sa11xx_uda1341_capture;
if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
return err;
if ((err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates)) < 0)
return err;
return 0;
}
static int snd_card_sa11xx_uda1341_close(snd_pcm_substream_t * substream)
{
sa11xx_uda1341_t *chip = snd_pcm_substream_chip(substream);
chip->s[substream->pstr->stream].stream = NULL;
return 0;
}
/* {{{ HW params & free */
static int snd_sa11xx_uda1341_hw_params(snd_pcm_substream_t * substream,
snd_pcm_hw_params_t * hw_params)
{
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
}
static int snd_sa11xx_uda1341_hw_free(snd_pcm_substream_t * substream)
{
return snd_pcm_lib_free_pages(substream);
}
/* }}} */
static snd_pcm_ops_t snd_card_sa11xx_uda1341_playback_ops = {
.open = snd_card_sa11xx_uda1341_open,
.close = snd_card_sa11xx_uda1341_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_sa11xx_uda1341_hw_params,
.hw_free = snd_sa11xx_uda1341_hw_free,
.prepare = snd_sa11xx_uda1341_prepare,
.trigger = snd_sa11xx_uda1341_trigger,
.pointer = snd_sa11xx_uda1341_pointer,
};
static snd_pcm_ops_t snd_card_sa11xx_uda1341_capture_ops = {
.open = snd_card_sa11xx_uda1341_open,
.close = snd_card_sa11xx_uda1341_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_sa11xx_uda1341_hw_params,
.hw_free = snd_sa11xx_uda1341_hw_free,
.prepare = snd_sa11xx_uda1341_prepare,
.trigger = snd_sa11xx_uda1341_trigger,
.pointer = snd_sa11xx_uda1341_pointer,
};
static int __init snd_card_sa11xx_uda1341_pcm(sa11xx_uda1341_t *sa11xx_uda1341, int device)
{
snd_pcm_t *pcm;
int err;
if ((err = snd_pcm_new(sa11xx_uda1341->card, "UDA1341 PCM", device, 1, 1, &pcm)) < 0)
return err;
/*
* this sets up our initial buffers and sets the dma_type to isa.
* isa works but I'm not sure why (or if) it's the right choice
* this may be too large, trying it for now
*/
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_ISA,
snd_pcm_dma_flags(0),
64*1024, 64*1024);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_card_sa11xx_uda1341_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_sa11xx_uda1341_capture_ops);
pcm->private_data = sa11xx_uda1341;
pcm->info_flags = 0;
strcpy(pcm->name, "UDA1341 PCM");
sa11xx_uda1341_audio_init(sa11xx_uda1341);
/* setup DMA controller */
audio_dma_request(&sa11xx_uda1341->s[SNDRV_PCM_STREAM_PLAYBACK], audio_dma_callback);
audio_dma_request(&sa11xx_uda1341->s[SNDRV_PCM_STREAM_CAPTURE], audio_dma_callback);
sa11xx_uda1341->pcm = pcm;
return 0;
}
/* }}} */
/* {{{ module init & exit */
#ifdef CONFIG_PM
static int snd_sa11xx_uda1341_suspend(snd_card_t *card, pm_message_t state)
{
sa11xx_uda1341_t *chip = card->pm_private_data;
snd_pcm_suspend_all(chip->pcm);
#ifdef HH_VERSION
sa1100_dma_sleep(chip->s[SNDRV_PCM_STREAM_PLAYBACK].dmach);
sa1100_dma_sleep(chip->s[SNDRV_PCM_STREAM_CAPTURE].dmach);
#else
//FIXME
#endif
l3_command(chip->uda1341, CMD_SUSPEND, NULL);
sa11xx_uda1341_audio_shutdown(chip);
return 0;
}
static int snd_sa11xx_uda1341_resume(snd_card_t *card)
{
sa11xx_uda1341_t *chip = card->pm_private_data;
sa11xx_uda1341_audio_init(chip);
l3_command(chip->uda1341, CMD_RESUME, NULL);
#ifdef HH_VERSION
sa1100_dma_wakeup(chip->s[SNDRV_PCM_STREAM_PLAYBACK].dmach);
sa1100_dma_wakeup(chip->s[SNDRV_PCM_STREAM_CAPTURE].dmach);
#else
//FIXME
#endif
return 0;
}
#endif /* COMFIG_PM */
void snd_sa11xx_uda1341_free(snd_card_t *card)
{
sa11xx_uda1341_t *chip = card->private_data;
audio_dma_free(&chip->s[SNDRV_PCM_STREAM_PLAYBACK]);
audio_dma_free(&chip->s[SNDRV_PCM_STREAM_CAPTURE]);
sa11xx_uda1341 = NULL;
card->private_data = NULL;
kfree(chip);
}
static int __init sa11xx_uda1341_init(void)
{
int err;
snd_card_t *card;
if (!machine_is_h3xxx())
return -ENODEV;
/* register the soundcard */
card = snd_card_new(-1, id, THIS_MODULE, sizeof(sa11xx_uda1341_t));
if (card == NULL)
return -ENOMEM;
sa11xx_uda1341 = kcalloc(1, sizeof(*sa11xx_uda1341), GFP_KERNEL);
if (sa11xx_uda1341 == NULL)
return -ENOMEM;
spin_lock_init(&chip->s[0].dma_lock);
spin_lock_init(&chip->s[1].dma_lock);
card->private_data = (void *)sa11xx_uda1341;
card->private_free = snd_sa11xx_uda1341_free;
sa11xx_uda1341->card = card;
sa11xx_uda1341->samplerate = AUDIO_RATE_DEFAULT;
// mixer
if ((err = snd_chip_uda1341_mixer_new(sa11xx_uda1341->card, &sa11xx_uda1341->uda1341)))
goto nodev;
// PCM
if ((err = snd_card_sa11xx_uda1341_pcm(sa11xx_uda1341, 0)) < 0)
goto nodev;
snd_card_set_generic_pm_callback(card,
snd_sa11xx_uda1341_suspend, snd_sa11_uda1341_resume,
sa11xx_uda1341);
strcpy(card->driver, "UDA1341");
strcpy(card->shortname, "H3600 UDA1341TS");
sprintf(card->longname, "Compaq iPAQ H3600 with Philips UDA1341TS");
if ((err = snd_card_register(card)) == 0) {
printk( KERN_INFO "iPAQ audio support initialized\n" );
return 0;
}
nodev:
snd_card_free(card);
return err;
}
static void __exit sa11xx_uda1341_exit(void)
{
snd_card_free(sa11xx_uda1341->card);
}
module_init(sa11xx_uda1341_init);
module_exit(sa11xx_uda1341_exit);
/* }}} */
/*
* Local variables:
* indent-tabs-mode: t
* End:
*/

133
sound/core/Kconfig Normal file
View File

@@ -0,0 +1,133 @@
# ALSA soundcard-configuration
config SND_TIMER
tristate
depends on SND
config SND_PCM
tristate
select SND_TIMER
depends on SND
config SND_HWDEP
tristate
depends on SND
config SND_RAWMIDI
tristate
depends on SND
config SND_SEQUENCER
tristate "Sequencer support"
depends on SND
select SND_TIMER
help
Say Y or M to enable MIDI sequencer and router support. This
feature allows routing and enqueueing of MIDI events. Events
can be processed at a given time.
Many programs require this feature, so you should enable it
unless you know what you're doing.
config SND_SEQ_DUMMY
tristate "Sequencer dummy client"
depends on SND_SEQUENCER
help
Say Y here to enable the dummy sequencer client. This client
is a simple MIDI-through client: all normal input events are
redirected to the output port immediately.
You don't need this unless you want to connect many MIDI
devices or applications together.
To compile this driver as a module, choose M here: the module
will be called snd-seq-dummy.
config SND_OSSEMUL
bool
depends on SND
config SND_MIXER_OSS
tristate "OSS Mixer API"
depends on SND
select SND_OSSEMUL
help
To enable OSS mixer API emulation (/dev/mixer*), say Y here
and read <file:Documentation/sound/alsa/OSS-Emulation.txt>.
Many programs still use the OSS API, so say Y.
To compile this driver as a module, choose M here: the module
will be called snd-mixer-oss.
config SND_PCM_OSS
tristate "OSS PCM (digital audio) API"
depends on SND
select SND_OSSEMUL
select SND_PCM
help
To enable OSS digital audio (PCM) emulation (/dev/dsp*), say Y
here and read <file:Documentation/sound/alsa/OSS-Emulation.txt>.
Many programs still use the OSS API, so say Y.
To compile this driver as a module, choose M here: the module
will be called snd-pcm-oss.
config SND_SEQUENCER_OSS
bool "OSS Sequencer API"
depends on SND && SND_SEQUENCER
select SND_OSSEMUL
help
Say Y here to enable OSS sequencer emulation (both
/dev/sequencer and /dev/music interfaces).
Many programs still use the OSS API, so say Y.
To compile this driver as a module, choose M here: the module
will be called snd-seq-oss.
config SND_RTCTIMER
tristate "RTC Timer support"
depends on SND && RTC
select SND_TIMER
help
Say Y here to enable RTC timer support for ALSA. ALSA uses
the RTC timer as a precise timing source and maps the RTC
timer to ALSA's timer interface. The ALSA sequencer code also
can use this timing source.
To compile this driver as a module, choose M here: the module
will be called snd-rtctimer.
config SND_VERBOSE_PRINTK
bool "Verbose printk"
depends on SND
help
Say Y here to enable verbose log messages. These messages
will help to identify source file and position containing
printed messages.
You don't need this unless you're debugging ALSA.
config SND_DEBUG
bool "Debug"
depends on SND
help
Say Y here to enable ALSA debug code.
config SND_DEBUG_MEMORY
bool "Debug memory"
depends on SND_DEBUG
help
Say Y here to enable debugging of memory allocations.
config SND_DEBUG_DETECT
bool "Debug detection"
depends on SND_DEBUG
help
Say Y here to enable extra-verbose log messages printed when
detecting devices.
config SND_GENERIC_PM
bool
depends on SND

33
sound/core/Makefile Normal file
View File

@@ -0,0 +1,33 @@
#
# Makefile for ALSA
# Copyright (c) 1999,2001 by Jaroslav Kysela <perex@suse.cz>
#
snd-objs := sound.o init.o memory.o info.o control.o misc.o \
device.o wrappers.o
ifeq ($(CONFIG_ISA),y)
snd-objs += isadma.o
endif
ifeq ($(CONFIG_SND_OSSEMUL),y)
snd-objs += sound_oss.o info_oss.o
endif
snd-pcm-objs := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \
pcm_memory.o
snd-page-alloc-objs := memalloc.o sgbuf.o
snd-rawmidi-objs := rawmidi.o
snd-timer-objs := timer.o
snd-rtctimer-objs := rtctimer.o
snd-hwdep-objs := hwdep.o
obj-$(CONFIG_SND) += snd.o
obj-$(CONFIG_SND_HWDEP) += snd-hwdep.o
obj-$(CONFIG_SND_TIMER) += snd-timer.o
obj-$(CONFIG_SND_RTCTIMER) += snd-rtctimer.o
obj-$(CONFIG_SND_PCM) += snd-pcm.o snd-page-alloc.o
obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o
obj-$(CONFIG_SND_OSSEMUL) += oss/
obj-$(CONFIG_SND_SEQUENCER) += seq/

1375
sound/core/control.c Normal file

File diff suppressed because it is too large Load Diff

412
sound/core/control_compat.c Normal file
View File

@@ -0,0 +1,412 @@
/*
* compat ioctls for control API
*
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* this file included from control.c */
#include <linux/compat.h>
struct sndrv_ctl_elem_list32 {
u32 offset;
u32 space;
u32 used;
u32 count;
u32 pids;
unsigned char reserved[50];
} /* don't set packed attribute here */;
static int snd_ctl_elem_list_compat(snd_card_t *card, struct sndrv_ctl_elem_list32 __user *data32)
{
struct sndrv_ctl_elem_list __user *data;
compat_caddr_t ptr;
int err;
data = compat_alloc_user_space(sizeof(*data));
/* offset, space, used, count */
if (copy_in_user(data, data32, 4 * sizeof(u32)))
return -EFAULT;
/* pids */
if (get_user(ptr, &data32->pids) ||
put_user(compat_ptr(ptr), &data->pids))
return -EFAULT;
err = snd_ctl_elem_list(card, data);
if (err < 0)
return err;
/* copy the result */
if (copy_in_user(data32, data, 4 * sizeof(u32)))
return -EFAULT;
return 0;
}
/*
* control element info
* it uses union, so the things are not easy..
*/
struct sndrv_ctl_elem_info32 {
struct sndrv_ctl_elem_id id; // the size of struct is same
s32 type;
u32 access;
u32 count;
s32 owner;
union {
struct {
s32 min;
s32 max;
s32 step;
} integer;
struct {
u64 min;
u64 max;
u64 step;
} integer64;
struct {
u32 items;
u32 item;
char name[64];
} enumerated;
unsigned char reserved[128];
} value;
unsigned char reserved[64];
} __attribute__((packed));
static int snd_ctl_elem_info_compat(snd_ctl_file_t *ctl, struct sndrv_ctl_elem_info32 __user *data32)
{
struct sndrv_ctl_elem_info *data;
int err;
data = kcalloc(1, sizeof(*data), GFP_KERNEL);
if (! data)
return -ENOMEM;
err = -EFAULT;
/* copy id */
if (copy_from_user(&data->id, &data32->id, sizeof(data->id)))
goto error;
/* we need to copy the item index.
* hope this doesn't break anything..
*/
if (get_user(data->value.enumerated.item, &data32->value.enumerated.item))
goto error;
err = snd_ctl_elem_info(ctl, data);
if (err < 0)
goto error;
/* restore info to 32bit */
err = -EFAULT;
/* id, type, access, count */
if (copy_to_user(&data32->id, &data->id, sizeof(data->id)) ||
copy_to_user(&data32->type, &data->type, 3 * sizeof(u32)))
goto error;
if (put_user(data->owner, &data32->owner))
goto error;
switch (data->type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
case SNDRV_CTL_ELEM_TYPE_INTEGER:
if (put_user(data->value.integer.min, &data32->value.integer.min) ||
put_user(data->value.integer.max, &data32->value.integer.max) ||
put_user(data->value.integer.step, &data32->value.integer.step))
goto error;
break;
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
if (copy_to_user(&data32->value.integer64,
&data->value.integer64,
sizeof(data->value.integer64)))
goto error;
break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
if (copy_to_user(&data32->value.enumerated,
&data->value.enumerated,
sizeof(data->value.enumerated)))
goto error;
break;
default:
break;
}
err = 0;
error:
kfree(data);
return err;
}
/* read / write */
struct sndrv_ctl_elem_value32 {
struct sndrv_ctl_elem_id id;
unsigned int indirect; /* bit-field causes misalignment */
union {
s32 integer[128];
unsigned char data[512];
#ifndef CONFIG_X86_64
s64 integer64[64];
#endif
} value;
unsigned char reserved[128];
};
/* get the value type and count of the control */
static int get_ctl_type(snd_card_t *card, snd_ctl_elem_id_t *id, int *countp)
{
snd_kcontrol_t *kctl;
snd_ctl_elem_info_t info;
int err;
down_read(&card->controls_rwsem);
kctl = snd_ctl_find_id(card, id);
if (! kctl) {
up_read(&card->controls_rwsem);
return -ENXIO;
}
info.id = *id;
err = kctl->info(kctl, &info);
up_read(&card->controls_rwsem);
if (err >= 0) {
err = info.type;
*countp = info.count;
}
return err;
}
static int get_elem_size(int type, int count)
{
switch (type) {
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
return sizeof(s64) * count;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
return sizeof(int) * count;
case SNDRV_CTL_ELEM_TYPE_BYTES:
return 512;
case SNDRV_CTL_ELEM_TYPE_IEC958:
return sizeof(struct sndrv_aes_iec958);
default:
return -1;
}
}
static int copy_ctl_value_from_user(snd_card_t *card,
struct sndrv_ctl_elem_value *data,
struct sndrv_ctl_elem_value32 __user *data32,
int *typep, int *countp)
{
int i, type, count, size;
unsigned int indirect;
if (copy_from_user(&data->id, &data32->id, sizeof(data->id)))
return -EFAULT;
if (get_user(indirect, &data32->indirect))
return -EFAULT;
if (indirect)
return -EINVAL;
type = get_ctl_type(card, &data->id, &count);
if (type < 0)
return type;
if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
for (i = 0; i < count; i++) {
int val;
if (get_user(val, &data32->value.integer[i]))
return -EFAULT;
data->value.integer.value[i] = val;
}
} else {
size = get_elem_size(type, count);
if (size < 0) {
printk(KERN_ERR "snd_ioctl32_ctl_elem_value: unknown type %d\n", type);
return -EINVAL;
}
if (copy_from_user(data->value.bytes.data,
data32->value.data, size))
return -EFAULT;
}
*typep = type;
*countp = count;
return 0;
}
/* restore the value to 32bit */
static int copy_ctl_value_to_user(struct sndrv_ctl_elem_value32 __user *data32,
struct sndrv_ctl_elem_value *data,
int type, int count)
{
int i, size;
if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
for (i = 0; i < count; i++) {
int val;
val = data->value.integer.value[i];
if (put_user(val, &data32->value.integer[i]))
return -EFAULT;
}
} else {
size = get_elem_size(type, count);
if (copy_to_user(data32->value.data,
data->value.bytes.data, size))
return -EFAULT;
}
return 0;
}
static int snd_ctl_elem_read_user_compat(snd_card_t *card,
struct sndrv_ctl_elem_value32 __user *data32)
{
struct sndrv_ctl_elem_value *data;
int err, type, count;
data = kcalloc(1, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0)
goto error;
if ((err = snd_ctl_elem_read(card, data)) < 0)
goto error;
err = copy_ctl_value_to_user(data32, data, type, count);
error:
kfree(data);
return err;
}
static int snd_ctl_elem_write_user_compat(snd_ctl_file_t *file,
struct sndrv_ctl_elem_value32 __user *data32)
{
struct sndrv_ctl_elem_value *data;
int err, type, count;
data = kcalloc(1, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
if ((err = copy_ctl_value_from_user(file->card, data, data32, &type, &count)) < 0)
goto error;
if ((err = snd_ctl_elem_write(file->card, file, data)) < 0)
goto error;
err = copy_ctl_value_to_user(data32, data, type, count);
error:
kfree(data);
return err;
}
/* add or replace a user control */
static int snd_ctl_elem_add_compat(snd_ctl_file_t *file,
struct sndrv_ctl_elem_info32 __user *data32,
int replace)
{
struct sndrv_ctl_elem_info *data;
int err;
data = kcalloc(1, sizeof(*data), GFP_KERNEL);
if (! data)
return -ENOMEM;
err = -EFAULT;
/* id, type, access, count */ \
if (copy_from_user(&data->id, &data32->id, sizeof(data->id)) ||
copy_from_user(&data->type, &data32->type, 3 * sizeof(u32)))
goto error;
if (get_user(data->owner, &data32->owner) ||
get_user(data->type, &data32->type))
goto error;
switch (data->type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
case SNDRV_CTL_ELEM_TYPE_INTEGER:
if (get_user(data->value.integer.min, &data32->value.integer.min) ||
get_user(data->value.integer.max, &data32->value.integer.max) ||
get_user(data->value.integer.step, &data32->value.integer.step))
goto error;
break;
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
if (copy_from_user(&data->value.integer64,
&data32->value.integer64,
sizeof(data->value.integer64)))
goto error;
break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
if (copy_from_user(&data->value.enumerated,
&data32->value.enumerated,
sizeof(data->value.enumerated)))
goto error;
break;
default:
break;
}
err = snd_ctl_elem_add(file, data, replace);
error:
kfree(data);
return err;
}
enum {
SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct sndrv_ctl_elem_list32),
SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct sndrv_ctl_elem_info32),
SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct sndrv_ctl_elem_value32),
SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct sndrv_ctl_elem_value32),
SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct sndrv_ctl_elem_info32),
SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct sndrv_ctl_elem_info32),
};
static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
{
snd_ctl_file_t *ctl;
struct list_head *list;
void __user *argp = compat_ptr(arg);
int err;
ctl = file->private_data;
snd_assert(ctl && ctl->card, return -ENXIO);
switch (cmd) {
case SNDRV_CTL_IOCTL_PVERSION:
case SNDRV_CTL_IOCTL_CARD_INFO:
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
case SNDRV_CTL_IOCTL_POWER:
case SNDRV_CTL_IOCTL_POWER_STATE:
case SNDRV_CTL_IOCTL_ELEM_LOCK:
case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
return snd_ctl_ioctl(file, cmd, (unsigned long)argp);
case SNDRV_CTL_IOCTL_ELEM_LIST32:
return snd_ctl_elem_list_compat(ctl->card, argp);
case SNDRV_CTL_IOCTL_ELEM_INFO32:
return snd_ctl_elem_info_compat(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_READ32:
return snd_ctl_elem_read_user_compat(ctl->card, argp);
case SNDRV_CTL_IOCTL_ELEM_WRITE32:
return snd_ctl_elem_write_user_compat(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_ADD32:
return snd_ctl_elem_add_compat(ctl, argp, 0);
case SNDRV_CTL_IOCTL_ELEM_REPLACE32:
return snd_ctl_elem_add_compat(ctl, argp, 1);
}
down_read(&snd_ioctl_rwsem);
list_for_each(list, &snd_control_compat_ioctls) {
snd_kctl_ioctl_t *p = list_entry(list, snd_kctl_ioctl_t, list);
if (p->fioctl) {
err = p->fioctl(ctl->card, ctl, cmd, arg);
if (err != -ENOIOCTLCMD) {
up_read(&snd_ioctl_rwsem);
return err;
}
}
}
up_read(&snd_ioctl_rwsem);
return -ENOIOCTLCMD;
}

240
sound/core/device.c Normal file
View File

@@ -0,0 +1,240 @@
/*
* Device management routines
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/errno.h>
#include <sound/core.h>
/**
* snd_device_new - create an ALSA device component
* @card: the card instance
* @type: the device type, SNDRV_DEV_TYPE_XXX
* @device_data: the data pointer of this device
* @ops: the operator table
*
* Creates a new device component for the given data pointer.
* The device will be assigned to the card and managed together
* by the card.
*
* The data pointer plays a role as the identifier, too, so the
* pointer address must be unique and unchanged.
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_device_new(snd_card_t *card, snd_device_type_t type,
void *device_data, snd_device_ops_t *ops)
{
snd_device_t *dev;
snd_assert(card != NULL && device_data != NULL && ops != NULL, return -ENXIO);
dev = kcalloc(1, sizeof(*dev), GFP_KERNEL);
if (dev == NULL)
return -ENOMEM;
dev->card = card;
dev->type = type;
dev->state = SNDRV_DEV_BUILD;
dev->device_data = device_data;
dev->ops = ops;
list_add(&dev->list, &card->devices); /* add to the head of list */
return 0;
}
/**
* snd_device_free - release the device from the card
* @card: the card instance
* @device_data: the data pointer to release
*
* Removes the device from the list on the card and invokes the
* callback, dev_unregister or dev_free, corresponding to the state.
* Then release the device.
*
* Returns zero if successful, or a negative error code on failure or if the
* device not found.
*/
int snd_device_free(snd_card_t *card, void *device_data)
{
struct list_head *list;
snd_device_t *dev;
snd_assert(card != NULL, return -ENXIO);
snd_assert(device_data != NULL, return -ENXIO);
list_for_each(list, &card->devices) {
dev = snd_device(list);
if (dev->device_data != device_data)
continue;
/* unlink */
list_del(&dev->list);
if ((dev->state == SNDRV_DEV_REGISTERED || dev->state == SNDRV_DEV_DISCONNECTED) &&
dev->ops->dev_unregister) {
if (dev->ops->dev_unregister(dev))
snd_printk(KERN_ERR "device unregister failure\n");
} else {
if (dev->ops->dev_free) {
if (dev->ops->dev_free(dev))
snd_printk(KERN_ERR "device free failure\n");
}
}
kfree(dev);
return 0;
}
snd_printd("device free %p (from %p), not found\n", device_data, __builtin_return_address(0));
return -ENXIO;
}
/**
* snd_device_free - disconnect the device
* @card: the card instance
* @device_data: the data pointer to disconnect
*
* Turns the device into the disconnection state, invoking
* dev_disconnect callback, if the device was already registered.
*
* Usually called from snd_card_disconnect().
*
* Returns zero if successful, or a negative error code on failure or if the
* device not found.
*/
int snd_device_disconnect(snd_card_t *card, void *device_data)
{
struct list_head *list;
snd_device_t *dev;
snd_assert(card != NULL, return -ENXIO);
snd_assert(device_data != NULL, return -ENXIO);
list_for_each(list, &card->devices) {
dev = snd_device(list);
if (dev->device_data != device_data)
continue;
if (dev->state == SNDRV_DEV_REGISTERED && dev->ops->dev_disconnect) {
if (dev->ops->dev_disconnect(dev))
snd_printk(KERN_ERR "device disconnect failure\n");
dev->state = SNDRV_DEV_DISCONNECTED;
}
return 0;
}
snd_printd("device disconnect %p (from %p), not found\n", device_data, __builtin_return_address(0));
return -ENXIO;
}
/**
* snd_device_register - register the device
* @card: the card instance
* @device_data: the data pointer to register
*
* Registers the device which was already created via
* snd_device_new(). Usually this is called from snd_card_register(),
* but it can be called later if any new devices are created after
* invocation of snd_card_register().
*
* Returns zero if successful, or a negative error code on failure or if the
* device not found.
*/
int snd_device_register(snd_card_t *card, void *device_data)
{
struct list_head *list;
snd_device_t *dev;
int err;
snd_assert(card != NULL && device_data != NULL, return -ENXIO);
list_for_each(list, &card->devices) {
dev = snd_device(list);
if (dev->device_data != device_data)
continue;
if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
if ((err = dev->ops->dev_register(dev)) < 0)
return err;
dev->state = SNDRV_DEV_REGISTERED;
return 0;
}
return -EBUSY;
}
snd_BUG();
return -ENXIO;
}
/*
* register all the devices on the card.
* called from init.c
*/
int snd_device_register_all(snd_card_t *card)
{
struct list_head *list;
snd_device_t *dev;
int err;
snd_assert(card != NULL, return -ENXIO);
list_for_each(list, &card->devices) {
dev = snd_device(list);
if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
if ((err = dev->ops->dev_register(dev)) < 0)
return err;
dev->state = SNDRV_DEV_REGISTERED;
}
}
return 0;
}
/*
* disconnect all the devices on the card.
* called from init.c
*/
int snd_device_disconnect_all(snd_card_t *card)
{
snd_device_t *dev;
struct list_head *list;
int err = 0;
snd_assert(card != NULL, return -ENXIO);
list_for_each(list, &card->devices) {
dev = snd_device(list);
if (snd_device_disconnect(card, dev->device_data) < 0)
err = -ENXIO;
}
return err;
}
/*
* release all the devices on the card.
* called from init.c
*/
int snd_device_free_all(snd_card_t *card, snd_device_cmd_t cmd)
{
snd_device_t *dev;
struct list_head *list;
int err;
unsigned int range_low, range_high;
snd_assert(card != NULL, return -ENXIO);
range_low = cmd * SNDRV_DEV_TYPE_RANGE_SIZE;
range_high = range_low + SNDRV_DEV_TYPE_RANGE_SIZE - 1;
__again:
list_for_each(list, &card->devices) {
dev = snd_device(list);
if (dev->type >= range_low && dev->type <= range_high) {
if ((err = snd_device_free(card, dev->device_data)) < 0)
return err;
goto __again;
}
}
return 0;
}

524
sound/core/hwdep.c Normal file
View File

@@ -0,0 +1,524 @@
/*
* Hardware dependent layer
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/major.h>
#include <linux/init.h>
#include <linux/smp_lock.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/minors.h>
#include <sound/hwdep.h>
#include <sound/info.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("Hardware dependent layer");
MODULE_LICENSE("GPL");
static snd_hwdep_t *snd_hwdep_devices[SNDRV_CARDS * SNDRV_MINOR_HWDEPS];
static DECLARE_MUTEX(register_mutex);
static int snd_hwdep_free(snd_hwdep_t *hwdep);
static int snd_hwdep_dev_free(snd_device_t *device);
static int snd_hwdep_dev_register(snd_device_t *device);
static int snd_hwdep_dev_unregister(snd_device_t *device);
/*
*/
static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig)
{
snd_hwdep_t *hw = file->private_data;
if (hw->ops.llseek)
return hw->ops.llseek(hw, file, offset, orig);
return -ENXIO;
}
static ssize_t snd_hwdep_read(struct file * file, char __user *buf, size_t count, loff_t *offset)
{
snd_hwdep_t *hw = file->private_data;
if (hw->ops.read)
return hw->ops.read(hw, buf, count, offset);
return -ENXIO;
}
static ssize_t snd_hwdep_write(struct file * file, const char __user *buf, size_t count, loff_t *offset)
{
snd_hwdep_t *hw = file->private_data;
if (hw->ops.write)
return hw->ops.write(hw, buf, count, offset);
return -ENXIO;
}
static int snd_hwdep_open(struct inode *inode, struct file * file)
{
int major = imajor(inode);
int cardnum;
int device;
snd_hwdep_t *hw;
int err;
wait_queue_t wait;
switch (major) {
case CONFIG_SND_MAJOR:
cardnum = SNDRV_MINOR_CARD(iminor(inode));
device = SNDRV_MINOR_DEVICE(iminor(inode)) - SNDRV_MINOR_HWDEP;
break;
#ifdef CONFIG_SND_OSSEMUL
case SOUND_MAJOR:
cardnum = SNDRV_MINOR_OSS_CARD(iminor(inode));
device = 0;
break;
#endif
default:
return -ENXIO;
}
cardnum %= SNDRV_CARDS;
device %= SNDRV_MINOR_HWDEPS;
hw = snd_hwdep_devices[(cardnum * SNDRV_MINOR_HWDEPS) + device];
if (hw == NULL)
return -ENODEV;
if (!hw->ops.open)
return -ENXIO;
#ifdef CONFIG_SND_OSSEMUL
if (major == SOUND_MAJOR && hw->oss_type < 0)
return -ENXIO;
#endif
if (!try_module_get(hw->card->module))
return -EFAULT;
init_waitqueue_entry(&wait, current);
add_wait_queue(&hw->open_wait, &wait);
down(&hw->open_mutex);
while (1) {
if (hw->exclusive && hw->used > 0) {
err = -EBUSY;
break;
}
err = hw->ops.open(hw, file);
if (err >= 0)
break;
if (err == -EAGAIN) {
if (file->f_flags & O_NONBLOCK) {
err = -EBUSY;
break;
}
} else
break;
set_current_state(TASK_INTERRUPTIBLE);
up(&hw->open_mutex);
schedule();
down(&hw->open_mutex);
if (signal_pending(current)) {
err = -ERESTARTSYS;
break;
}
}
remove_wait_queue(&hw->open_wait, &wait);
if (err >= 0) {
err = snd_card_file_add(hw->card, file);
if (err >= 0) {
file->private_data = hw;
hw->used++;
} else {
if (hw->ops.release)
hw->ops.release(hw, file);
}
}
up(&hw->open_mutex);
if (err < 0)
module_put(hw->card->module);
return err;
}
static int snd_hwdep_release(struct inode *inode, struct file * file)
{
int err = -ENXIO;
snd_hwdep_t *hw = file->private_data;
down(&hw->open_mutex);
if (hw->ops.release) {
err = hw->ops.release(hw, file);
wake_up(&hw->open_wait);
}
if (hw->used > 0)
hw->used--;
snd_card_file_remove(hw->card, file);
up(&hw->open_mutex);
module_put(hw->card->module);
return err;
}
static unsigned int snd_hwdep_poll(struct file * file, poll_table * wait)
{
snd_hwdep_t *hw = file->private_data;
if (hw->ops.poll)
return hw->ops.poll(hw, file, wait);
return 0;
}
static int snd_hwdep_info(snd_hwdep_t *hw, snd_hwdep_info_t __user *_info)
{
snd_hwdep_info_t info;
memset(&info, 0, sizeof(info));
info.card = hw->card->number;
strlcpy(info.id, hw->id, sizeof(info.id));
strlcpy(info.name, hw->name, sizeof(info.name));
info.iface = hw->iface;
if (copy_to_user(_info, &info, sizeof(info)))
return -EFAULT;
return 0;
}
static int snd_hwdep_dsp_status(snd_hwdep_t *hw, snd_hwdep_dsp_status_t __user *_info)
{
snd_hwdep_dsp_status_t info;
int err;
if (! hw->ops.dsp_status)
return -ENXIO;
memset(&info, 0, sizeof(info));
info.dsp_loaded = hw->dsp_loaded;
if ((err = hw->ops.dsp_status(hw, &info)) < 0)
return err;
if (copy_to_user(_info, &info, sizeof(info)))
return -EFAULT;
return 0;
}
static int snd_hwdep_dsp_load(snd_hwdep_t *hw, snd_hwdep_dsp_image_t __user *_info)
{
snd_hwdep_dsp_image_t info;
int err;
if (! hw->ops.dsp_load)
return -ENXIO;
memset(&info, 0, sizeof(info));
if (copy_from_user(&info, _info, sizeof(info)))
return -EFAULT;
/* check whether the dsp was already loaded */
if (hw->dsp_loaded & (1 << info.index))
return -EBUSY;
if (!access_ok(VERIFY_READ, info.image, info.length))
return -EFAULT;
err = hw->ops.dsp_load(hw, &info);
if (err < 0)
return err;
hw->dsp_loaded |= (1 << info.index);
return 0;
}
static long snd_hwdep_ioctl(struct file * file, unsigned int cmd, unsigned long arg)
{
snd_hwdep_t *hw = file->private_data;
void __user *argp = (void __user *)arg;
switch (cmd) {
case SNDRV_HWDEP_IOCTL_PVERSION:
return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp);
case SNDRV_HWDEP_IOCTL_INFO:
return snd_hwdep_info(hw, argp);
case SNDRV_HWDEP_IOCTL_DSP_STATUS:
return snd_hwdep_dsp_status(hw, argp);
case SNDRV_HWDEP_IOCTL_DSP_LOAD:
return snd_hwdep_dsp_load(hw, argp);
}
if (hw->ops.ioctl)
return hw->ops.ioctl(hw, file, cmd, arg);
return -ENOTTY;
}
static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma)
{
snd_hwdep_t *hw = file->private_data;
if (hw->ops.mmap)
return hw->ops.mmap(hw, file, vma);
return -ENXIO;
}
static int snd_hwdep_control_ioctl(snd_card_t * card, snd_ctl_file_t * control,
unsigned int cmd, unsigned long arg)
{
unsigned int tmp;
tmp = card->number * SNDRV_MINOR_HWDEPS;
switch (cmd) {
case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE:
{
int device;
if (get_user(device, (int __user *)arg))
return -EFAULT;
device = device < 0 ? 0 : device + 1;
while (device < SNDRV_MINOR_HWDEPS) {
if (snd_hwdep_devices[tmp + device])
break;
device++;
}
if (device >= SNDRV_MINOR_HWDEPS)
device = -1;
if (put_user(device, (int __user *)arg))
return -EFAULT;
return 0;
}
case SNDRV_CTL_IOCTL_HWDEP_INFO:
{
snd_hwdep_info_t __user *info = (snd_hwdep_info_t __user *)arg;
int device;
snd_hwdep_t *hwdep;
if (get_user(device, &info->device))
return -EFAULT;
if (device < 0 || device >= SNDRV_MINOR_HWDEPS)
return -ENXIO;
hwdep = snd_hwdep_devices[tmp + device];
if (hwdep == NULL)
return -ENXIO;
return snd_hwdep_info(hwdep, info);
}
}
return -ENOIOCTLCMD;
}
#ifdef CONFIG_COMPAT
#include "hwdep_compat.c"
#else
#define snd_hwdep_ioctl_compat NULL
#endif
/*
*/
static struct file_operations snd_hwdep_f_ops =
{
.owner = THIS_MODULE,
.llseek = snd_hwdep_llseek,
.read = snd_hwdep_read,
.write = snd_hwdep_write,
.open = snd_hwdep_open,
.release = snd_hwdep_release,
.poll = snd_hwdep_poll,
.unlocked_ioctl = snd_hwdep_ioctl,
.compat_ioctl = snd_hwdep_ioctl_compat,
.mmap = snd_hwdep_mmap,
};
static snd_minor_t snd_hwdep_reg =
{
.comment = "hardware dependent",
.f_ops = &snd_hwdep_f_ops,
};
/**
* snd_hwdep_new - create a new hwdep instance
* @card: the card instance
* @id: the id string
* @device: the device index (zero-based)
* @rhwdep: the pointer to store the new hwdep instance
*
* Creates a new hwdep instance with the given index on the card.
* The callbacks (hwdep->ops) must be set on the returned instance
* after this call manually by the caller.
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_hwdep_new(snd_card_t * card, char *id, int device, snd_hwdep_t ** rhwdep)
{
snd_hwdep_t *hwdep;
int err;
static snd_device_ops_t ops = {
.dev_free = snd_hwdep_dev_free,
.dev_register = snd_hwdep_dev_register,
.dev_unregister = snd_hwdep_dev_unregister
};
snd_assert(rhwdep != NULL, return -EINVAL);
*rhwdep = NULL;
snd_assert(card != NULL, return -ENXIO);
hwdep = kcalloc(1, sizeof(*hwdep), GFP_KERNEL);
if (hwdep == NULL)
return -ENOMEM;
hwdep->card = card;
hwdep->device = device;
if (id) {
strlcpy(hwdep->id, id, sizeof(hwdep->id));
}
#ifdef CONFIG_SND_OSSEMUL
hwdep->oss_type = -1;
#endif
if ((err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops)) < 0) {
snd_hwdep_free(hwdep);
return err;
}
init_waitqueue_head(&hwdep->open_wait);
init_MUTEX(&hwdep->open_mutex);
*rhwdep = hwdep;
return 0;
}
static int snd_hwdep_free(snd_hwdep_t *hwdep)
{
snd_assert(hwdep != NULL, return -ENXIO);
if (hwdep->private_free)
hwdep->private_free(hwdep);
kfree(hwdep);
return 0;
}
static int snd_hwdep_dev_free(snd_device_t *device)
{
snd_hwdep_t *hwdep = device->device_data;
return snd_hwdep_free(hwdep);
}
static int snd_hwdep_dev_register(snd_device_t *device)
{
snd_hwdep_t *hwdep = device->device_data;
int idx, err;
char name[32];
down(&register_mutex);
idx = (hwdep->card->number * SNDRV_MINOR_HWDEPS) + hwdep->device;
if (snd_hwdep_devices[idx]) {
up(&register_mutex);
return -EBUSY;
}
snd_hwdep_devices[idx] = hwdep;
sprintf(name, "hwC%iD%i", hwdep->card->number, hwdep->device);
if ((err = snd_register_device(SNDRV_DEVICE_TYPE_HWDEP,
hwdep->card, hwdep->device,
&snd_hwdep_reg, name)) < 0) {
snd_printk(KERN_ERR "unable to register hardware dependent device %i:%i\n",
hwdep->card->number, hwdep->device);
snd_hwdep_devices[idx] = NULL;
up(&register_mutex);
return err;
}
#ifdef CONFIG_SND_OSSEMUL
hwdep->ossreg = 0;
if (hwdep->oss_type >= 0) {
if ((hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM) && (hwdep->device != 0)) {
snd_printk (KERN_WARNING "only hwdep device 0 can be registered as OSS direct FM device!\n");
} else {
if (snd_register_oss_device(hwdep->oss_type,
hwdep->card, hwdep->device,
&snd_hwdep_reg, hwdep->oss_dev) < 0) {
snd_printk(KERN_ERR "unable to register OSS compatibility device %i:%i\n",
hwdep->card->number, hwdep->device);
} else
hwdep->ossreg = 1;
}
}
#endif
up(&register_mutex);
return 0;
}
static int snd_hwdep_dev_unregister(snd_device_t *device)
{
snd_hwdep_t *hwdep = device->device_data;
int idx;
snd_assert(hwdep != NULL, return -ENXIO);
down(&register_mutex);
idx = (hwdep->card->number * SNDRV_MINOR_HWDEPS) + hwdep->device;
if (snd_hwdep_devices[idx] != hwdep) {
up(&register_mutex);
return -EINVAL;
}
#ifdef CONFIG_SND_OSSEMUL
if (hwdep->ossreg)
snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device);
#endif
snd_unregister_device(SNDRV_DEVICE_TYPE_HWDEP, hwdep->card, hwdep->device);
snd_hwdep_devices[idx] = NULL;
up(&register_mutex);
return snd_hwdep_free(hwdep);
}
/*
* Info interface
*/
static void snd_hwdep_proc_read(snd_info_entry_t *entry,
snd_info_buffer_t * buffer)
{
int idx;
snd_hwdep_t *hwdep;
down(&register_mutex);
for (idx = 0; idx < SNDRV_CARDS * SNDRV_MINOR_HWDEPS; idx++) {
hwdep = snd_hwdep_devices[idx];
if (hwdep == NULL)
continue;
snd_iprintf(buffer, "%02i-%02i: %s\n",
idx / SNDRV_MINOR_HWDEPS,
idx % SNDRV_MINOR_HWDEPS,
hwdep->name);
}
up(&register_mutex);
}
/*
* ENTRY functions
*/
static snd_info_entry_t *snd_hwdep_proc_entry = NULL;
static int __init alsa_hwdep_init(void)
{
snd_info_entry_t *entry;
memset(snd_hwdep_devices, 0, sizeof(snd_hwdep_devices));
if ((entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL)) != NULL) {
entry->c.text.read_size = 512;
entry->c.text.read = snd_hwdep_proc_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
snd_hwdep_proc_entry = entry;
snd_ctl_register_ioctl(snd_hwdep_control_ioctl);
snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl);
return 0;
}
static void __exit alsa_hwdep_exit(void)
{
snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl);
snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl);
if (snd_hwdep_proc_entry) {
snd_info_unregister(snd_hwdep_proc_entry);
snd_hwdep_proc_entry = NULL;
}
}
module_init(alsa_hwdep_init)
module_exit(alsa_hwdep_exit)
EXPORT_SYMBOL(snd_hwdep_new);

77
sound/core/hwdep_compat.c Normal file
View File

@@ -0,0 +1,77 @@
/*
* 32bit -> 64bit ioctl wrapper for hwdep API
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* This file is included from hwdep.c */
#include <linux/compat.h>
struct sndrv_hwdep_dsp_image32 {
u32 index;
unsigned char name[64];
u32 image; /* pointer */
u32 length;
u32 driver_data;
} /* don't set packed attribute here */;
static int snd_hwdep_dsp_load_compat(snd_hwdep_t *hw,
struct sndrv_hwdep_dsp_image32 __user *src)
{
struct sndrv_hwdep_dsp_image *dst;
compat_caddr_t ptr;
u32 val;
dst = compat_alloc_user_space(sizeof(*dst));
/* index and name */
if (copy_in_user(dst, src, 4 + 64))
return -EFAULT;
if (get_user(ptr, &src->image) ||
put_user(compat_ptr(ptr), &dst->image))
return -EFAULT;
if (get_user(val, &src->length) ||
put_user(val, &dst->length))
return -EFAULT;
if (get_user(val, &src->driver_data) ||
put_user(val, &dst->driver_data))
return -EFAULT;
return snd_hwdep_dsp_load(hw, dst);
}
enum {
SNDRV_HWDEP_IOCTL_DSP_LOAD32 = _IOW('H', 0x03, struct sndrv_hwdep_dsp_image32)
};
static long snd_hwdep_ioctl_compat(struct file * file, unsigned int cmd, unsigned long arg)
{
snd_hwdep_t *hw = file->private_data;
void __user *argp = compat_ptr(arg);
switch (cmd) {
case SNDRV_HWDEP_IOCTL_PVERSION:
case SNDRV_HWDEP_IOCTL_INFO:
case SNDRV_HWDEP_IOCTL_DSP_STATUS:
return snd_hwdep_ioctl(file, cmd, (unsigned long)argp);
case SNDRV_HWDEP_IOCTL_DSP_LOAD32:
return snd_hwdep_dsp_load_compat(hw, argp);
}
if (hw->ops.ioctl_compat)
return hw->ops.ioctl_compat(hw, file, cmd, arg);
return -ENOIOCTLCMD;
}

989
sound/core/info.c Normal file
View File

@@ -0,0 +1,989 @@
/*
* Information interface for ALSA driver
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/time.h>
#include <linux/smp_lock.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <sound/info.h>
#include <sound/version.h>
#include <linux/proc_fs.h>
#include <linux/devfs_fs_kernel.h>
#include <stdarg.h>
/*
*
*/
int snd_info_check_reserved_words(const char *str)
{
static char *reserved[] =
{
"version",
"meminfo",
"memdebug",
"detect",
"devices",
"oss",
"cards",
"timers",
"synth",
"pcm",
"seq",
NULL
};
char **xstr = reserved;
while (*xstr) {
if (!strcmp(*xstr, str))
return 0;
xstr++;
}
if (!strncmp(str, "card", 4))
return 0;
return 1;
}
#ifdef CONFIG_PROC_FS
static DECLARE_MUTEX(info_mutex);
typedef struct _snd_info_private_data {
snd_info_buffer_t *rbuffer;
snd_info_buffer_t *wbuffer;
snd_info_entry_t *entry;
void *file_private_data;
} snd_info_private_data_t;
static int snd_info_version_init(void);
static int snd_info_version_done(void);
/**
* snd_iprintf - printf on the procfs buffer
* @buffer: the procfs buffer
* @fmt: the printf format
*
* Outputs the string on the procfs buffer just like printf().
*
* Returns the size of output string.
*/
int snd_iprintf(snd_info_buffer_t * buffer, char *fmt,...)
{
va_list args;
int len, res;
if (buffer->stop || buffer->error)
return 0;
len = buffer->len - buffer->size;
va_start(args, fmt);
res = vsnprintf(buffer->curr, len, fmt, args);
va_end(args);
if (res >= len) {
buffer->stop = 1;
return 0;
}
buffer->curr += res;
buffer->size += res;
return res;
}
/*
*/
static struct proc_dir_entry *snd_proc_root = NULL;
snd_info_entry_t *snd_seq_root = NULL;
#ifdef CONFIG_SND_OSSEMUL
snd_info_entry_t *snd_oss_root = NULL;
#endif
static inline void snd_info_entry_prepare(struct proc_dir_entry *de)
{
de->owner = THIS_MODULE;
}
static void snd_remove_proc_entry(struct proc_dir_entry *parent,
struct proc_dir_entry *de)
{
if (de)
remove_proc_entry(de->name, parent);
}
static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig)
{
snd_info_private_data_t *data;
struct snd_info_entry *entry;
loff_t ret;
data = file->private_data;
entry = data->entry;
lock_kernel();
switch (entry->content) {
case SNDRV_INFO_CONTENT_TEXT:
switch (orig) {
case 0: /* SEEK_SET */
file->f_pos = offset;
ret = file->f_pos;
goto out;
case 1: /* SEEK_CUR */
file->f_pos += offset;
ret = file->f_pos;
goto out;
case 2: /* SEEK_END */
default:
ret = -EINVAL;
goto out;
}
break;
case SNDRV_INFO_CONTENT_DATA:
if (entry->c.ops->llseek) {
ret = entry->c.ops->llseek(entry,
data->file_private_data,
file, offset, orig);
goto out;
}
break;
}
ret = -ENXIO;
out:
unlock_kernel();
return ret;
}
static ssize_t snd_info_entry_read(struct file *file, char __user *buffer,
size_t count, loff_t * offset)
{
snd_info_private_data_t *data;
struct snd_info_entry *entry;
snd_info_buffer_t *buf;
size_t size = 0;
loff_t pos;
data = file->private_data;
snd_assert(data != NULL, return -ENXIO);
pos = *offset;
if (pos < 0 || (long) pos != pos || (ssize_t) count < 0)
return -EIO;
if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos)
return -EIO;
entry = data->entry;
switch (entry->content) {
case SNDRV_INFO_CONTENT_TEXT:
buf = data->rbuffer;
if (buf == NULL)
return -EIO;
if (pos >= buf->size)
return 0;
size = buf->size - pos;
size = min(count, size);
if (copy_to_user(buffer, buf->buffer + pos, size))
return -EFAULT;
break;
case SNDRV_INFO_CONTENT_DATA:
if (entry->c.ops->read)
size = entry->c.ops->read(entry,
data->file_private_data,
file, buffer, count, pos);
break;
}
if ((ssize_t) size > 0)
*offset = pos + size;
return size;
}
static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer,
size_t count, loff_t * offset)
{
snd_info_private_data_t *data;
struct snd_info_entry *entry;
snd_info_buffer_t *buf;
size_t size = 0;
loff_t pos;
data = file->private_data;
snd_assert(data != NULL, return -ENXIO);
entry = data->entry;
pos = *offset;
if (pos < 0 || (long) pos != pos || (ssize_t) count < 0)
return -EIO;
if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos)
return -EIO;
switch (entry->content) {
case SNDRV_INFO_CONTENT_TEXT:
buf = data->wbuffer;
if (buf == NULL)
return -EIO;
if (pos >= buf->len)
return -ENOMEM;
size = buf->len - pos;
size = min(count, size);
if (copy_from_user(buf->buffer + pos, buffer, size))
return -EFAULT;
if ((long)buf->size < pos + size)
buf->size = pos + size;
break;
case SNDRV_INFO_CONTENT_DATA:
if (entry->c.ops->write)
size = entry->c.ops->write(entry,
data->file_private_data,
file, buffer, count, pos);
break;
}
if ((ssize_t) size > 0)
*offset = pos + size;
return size;
}
static int snd_info_entry_open(struct inode *inode, struct file *file)
{
snd_info_entry_t *entry;
snd_info_private_data_t *data;
snd_info_buffer_t *buffer;
struct proc_dir_entry *p;
int mode, err;
down(&info_mutex);
p = PDE(inode);
entry = p == NULL ? NULL : (snd_info_entry_t *)p->data;
if (entry == NULL || entry->disconnected) {
up(&info_mutex);
return -ENODEV;
}
if (!try_module_get(entry->module)) {
err = -EFAULT;
goto __error1;
}
mode = file->f_flags & O_ACCMODE;
if (mode == O_RDONLY || mode == O_RDWR) {
if ((entry->content == SNDRV_INFO_CONTENT_TEXT &&
!entry->c.text.read_size) ||
(entry->content == SNDRV_INFO_CONTENT_DATA &&
entry->c.ops->read == NULL)) {
err = -ENODEV;
goto __error;
}
}
if (mode == O_WRONLY || mode == O_RDWR) {
if ((entry->content == SNDRV_INFO_CONTENT_TEXT &&
!entry->c.text.write_size) ||
(entry->content == SNDRV_INFO_CONTENT_DATA &&
entry->c.ops->write == NULL)) {
err = -ENODEV;
goto __error;
}
}
data = kcalloc(1, sizeof(*data), GFP_KERNEL);
if (data == NULL) {
err = -ENOMEM;
goto __error;
}
data->entry = entry;
switch (entry->content) {
case SNDRV_INFO_CONTENT_TEXT:
if (mode == O_RDONLY || mode == O_RDWR) {
buffer = kcalloc(1, sizeof(*buffer), GFP_KERNEL);
if (buffer == NULL) {
kfree(data);
err = -ENOMEM;
goto __error;
}
buffer->len = (entry->c.text.read_size +
(PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
buffer->buffer = vmalloc(buffer->len);
if (buffer->buffer == NULL) {
kfree(buffer);
kfree(data);
err = -ENOMEM;
goto __error;
}
buffer->curr = buffer->buffer;
data->rbuffer = buffer;
}
if (mode == O_WRONLY || mode == O_RDWR) {
buffer = kcalloc(1, sizeof(*buffer), GFP_KERNEL);
if (buffer == NULL) {
if (mode == O_RDWR) {
vfree(data->rbuffer->buffer);
kfree(data->rbuffer);
}
kfree(data);
err = -ENOMEM;
goto __error;
}
buffer->len = (entry->c.text.write_size +
(PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
buffer->buffer = vmalloc(buffer->len);
if (buffer->buffer == NULL) {
if (mode == O_RDWR) {
vfree(data->rbuffer->buffer);
kfree(data->rbuffer);
}
kfree(buffer);
kfree(data);
err = -ENOMEM;
goto __error;
}
buffer->curr = buffer->buffer;
data->wbuffer = buffer;
}
break;
case SNDRV_INFO_CONTENT_DATA: /* data */
if (entry->c.ops->open) {
if ((err = entry->c.ops->open(entry, mode,
&data->file_private_data)) < 0) {
kfree(data);
goto __error;
}
}
break;
}
file->private_data = data;
up(&info_mutex);
if (entry->content == SNDRV_INFO_CONTENT_TEXT &&
(mode == O_RDONLY || mode == O_RDWR)) {
if (entry->c.text.read) {
down(&entry->access);
entry->c.text.read(entry, data->rbuffer);
up(&entry->access);
}
}
return 0;
__error:
module_put(entry->module);
__error1:
up(&info_mutex);
return err;
}
static int snd_info_entry_release(struct inode *inode, struct file *file)
{
snd_info_entry_t *entry;
snd_info_private_data_t *data;
int mode;
mode = file->f_flags & O_ACCMODE;
data = file->private_data;
entry = data->entry;
switch (entry->content) {
case SNDRV_INFO_CONTENT_TEXT:
if (mode == O_RDONLY || mode == O_RDWR) {
vfree(data->rbuffer->buffer);
kfree(data->rbuffer);
}
if (mode == O_WRONLY || mode == O_RDWR) {
if (entry->c.text.write) {
entry->c.text.write(entry, data->wbuffer);
if (data->wbuffer->error) {
snd_printk(KERN_WARNING "data write error to %s (%i)\n",
entry->name,
data->wbuffer->error);
}
}
vfree(data->wbuffer->buffer);
kfree(data->wbuffer);
}
break;
case SNDRV_INFO_CONTENT_DATA:
if (entry->c.ops->release)
entry->c.ops->release(entry, mode,
data->file_private_data);
break;
}
module_put(entry->module);
kfree(data);
return 0;
}
static unsigned int snd_info_entry_poll(struct file *file, poll_table * wait)
{
snd_info_private_data_t *data;
struct snd_info_entry *entry;
unsigned int mask;
data = file->private_data;
if (data == NULL)
return 0;
entry = data->entry;
mask = 0;
switch (entry->content) {
case SNDRV_INFO_CONTENT_DATA:
if (entry->c.ops->poll)
return entry->c.ops->poll(entry,
data->file_private_data,
file, wait);
if (entry->c.ops->read)
mask |= POLLIN | POLLRDNORM;
if (entry->c.ops->write)
mask |= POLLOUT | POLLWRNORM;
break;
}
return mask;
}
static inline int _snd_info_entry_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
snd_info_private_data_t *data;
struct snd_info_entry *entry;
data = file->private_data;
if (data == NULL)
return 0;
entry = data->entry;
switch (entry->content) {
case SNDRV_INFO_CONTENT_DATA:
if (entry->c.ops->ioctl)
return entry->c.ops->ioctl(entry,
data->file_private_data,
file, cmd, arg);
break;
}
return -ENOTTY;
}
/* FIXME: need to unlock BKL to allow preemption */
static int snd_info_entry_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int err;
unlock_kernel();
err = _snd_info_entry_ioctl(inode, file, cmd, arg);
lock_kernel();
return err;
}
static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma)
{
struct inode *inode = file->f_dentry->d_inode;
snd_info_private_data_t *data;
struct snd_info_entry *entry;
data = file->private_data;
if (data == NULL)
return 0;
entry = data->entry;
switch (entry->content) {
case SNDRV_INFO_CONTENT_DATA:
if (entry->c.ops->mmap)
return entry->c.ops->mmap(entry,
data->file_private_data,
inode, file, vma);
break;
}
return -ENXIO;
}
static struct file_operations snd_info_entry_operations =
{
.owner = THIS_MODULE,
.llseek = snd_info_entry_llseek,
.read = snd_info_entry_read,
.write = snd_info_entry_write,
.poll = snd_info_entry_poll,
.ioctl = snd_info_entry_ioctl,
.mmap = snd_info_entry_mmap,
.open = snd_info_entry_open,
.release = snd_info_entry_release,
};
/**
* snd_create_proc_entry - create a procfs entry
* @name: the name of the proc file
* @mode: the file permission bits, S_Ixxx
* @parent: the parent proc-directory entry
*
* Creates a new proc file entry with the given name and permission
* on the given directory.
*
* Returns the pointer of new instance or NULL on failure.
*/
static struct proc_dir_entry *snd_create_proc_entry(const char *name, mode_t mode,
struct proc_dir_entry *parent)
{
struct proc_dir_entry *p;
p = create_proc_entry(name, mode, parent);
if (p)
snd_info_entry_prepare(p);
return p;
}
int __init snd_info_init(void)
{
struct proc_dir_entry *p;
p = snd_create_proc_entry("asound", S_IFDIR | S_IRUGO | S_IXUGO, &proc_root);
if (p == NULL)
return -ENOMEM;
snd_proc_root = p;
#ifdef CONFIG_SND_OSSEMUL
{
snd_info_entry_t *entry;
if ((entry = snd_info_create_module_entry(THIS_MODULE, "oss", NULL)) == NULL)
return -ENOMEM;
entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
return -ENOMEM;
}
snd_oss_root = entry;
}
#endif
#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
{
snd_info_entry_t *entry;
if ((entry = snd_info_create_module_entry(THIS_MODULE, "seq", NULL)) == NULL)
return -ENOMEM;
entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
return -ENOMEM;
}
snd_seq_root = entry;
}
#endif
snd_info_version_init();
snd_memory_info_init();
snd_minor_info_init();
snd_minor_info_oss_init();
snd_card_info_init();
return 0;
}
int __exit snd_info_done(void)
{
snd_card_info_done();
snd_minor_info_oss_done();
snd_minor_info_done();
snd_memory_info_done();
snd_info_version_done();
if (snd_proc_root) {
#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
if (snd_seq_root)
snd_info_unregister(snd_seq_root);
#endif
#ifdef CONFIG_SND_OSSEMUL
if (snd_oss_root)
snd_info_unregister(snd_oss_root);
#endif
snd_remove_proc_entry(&proc_root, snd_proc_root);
}
return 0;
}
/*
*/
/*
* create a card proc file
* called from init.c
*/
int snd_info_card_create(snd_card_t * card)
{
char str[8];
snd_info_entry_t *entry;
snd_assert(card != NULL, return -ENXIO);
sprintf(str, "card%i", card->number);
if ((entry = snd_info_create_module_entry(card->module, str, NULL)) == NULL)
return -ENOMEM;
entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
return -ENOMEM;
}
card->proc_root = entry;
return 0;
}
/*
* register the card proc file
* called from init.c
*/
int snd_info_card_register(snd_card_t * card)
{
struct proc_dir_entry *p;
snd_assert(card != NULL, return -ENXIO);
if (!strcmp(card->id, card->proc_root->name))
return 0;
p = proc_symlink(card->id, snd_proc_root, card->proc_root->name);
if (p == NULL)
return -ENOMEM;
card->proc_root_link = p;
return 0;
}
/*
* de-register the card proc file
* called from init.c
*/
int snd_info_card_free(snd_card_t * card)
{
snd_assert(card != NULL, return -ENXIO);
if (card->proc_root_link) {
snd_remove_proc_entry(snd_proc_root, card->proc_root_link);
card->proc_root_link = NULL;
}
if (card->proc_root) {
snd_info_unregister(card->proc_root);
card->proc_root = NULL;
}
return 0;
}
/**
* snd_info_get_line - read one line from the procfs buffer
* @buffer: the procfs buffer
* @line: the buffer to store
* @len: the max. buffer size - 1
*
* Reads one line from the buffer and stores the string.
*
* Returns zero if successful, or 1 if error or EOF.
*/
int snd_info_get_line(snd_info_buffer_t * buffer, char *line, int len)
{
int c = -1;
if (len <= 0 || buffer->stop || buffer->error)
return 1;
while (--len > 0) {
c = *buffer->curr++;
if (c == '\n') {
if ((buffer->curr - buffer->buffer) >= (long)buffer->size) {
buffer->stop = 1;
}
break;
}
*line++ = c;
if ((buffer->curr - buffer->buffer) >= (long)buffer->size) {
buffer->stop = 1;
break;
}
}
while (c != '\n' && !buffer->stop) {
c = *buffer->curr++;
if ((buffer->curr - buffer->buffer) >= (long)buffer->size) {
buffer->stop = 1;
}
}
*line = '\0';
return 0;
}
/**
* snd_info_get_line - parse a string token
* @dest: the buffer to store the string token
* @src: the original string
* @len: the max. length of token - 1
*
* Parses the original string and copy a token to the given
* string buffer.
*
* Returns the updated pointer of the original string so that
* it can be used for the next call.
*/
char *snd_info_get_str(char *dest, char *src, int len)
{
int c;
while (*src == ' ' || *src == '\t')
src++;
if (*src == '"' || *src == '\'') {
c = *src++;
while (--len > 0 && *src && *src != c) {
*dest++ = *src++;
}
if (*src == c)
src++;
} else {
while (--len > 0 && *src && *src != ' ' && *src != '\t') {
*dest++ = *src++;
}
}
*dest = 0;
while (*src == ' ' || *src == '\t')
src++;
return src;
}
/**
* snd_info_create_entry - create an info entry
* @name: the proc file name
*
* Creates an info entry with the given file name and initializes as
* the default state.
*
* Usually called from other functions such as
* snd_info_create_card_entry().
*
* Returns the pointer of the new instance, or NULL on failure.
*/
static snd_info_entry_t *snd_info_create_entry(const char *name)
{
snd_info_entry_t *entry;
entry = kcalloc(1, sizeof(*entry), GFP_KERNEL);
if (entry == NULL)
return NULL;
entry->name = snd_kmalloc_strdup(name, GFP_KERNEL);
if (entry->name == NULL) {
kfree(entry);
return NULL;
}
entry->mode = S_IFREG | S_IRUGO;
entry->content = SNDRV_INFO_CONTENT_TEXT;
init_MUTEX(&entry->access);
return entry;
}
/**
* snd_info_create_module_entry - create an info entry for the given module
* @module: the module pointer
* @name: the file name
* @parent: the parent directory
*
* Creates a new info entry and assigns it to the given module.
*
* Returns the pointer of the new instance, or NULL on failure.
*/
snd_info_entry_t *snd_info_create_module_entry(struct module * module,
const char *name,
snd_info_entry_t *parent)
{
snd_info_entry_t *entry = snd_info_create_entry(name);
if (entry) {
entry->module = module;
entry->parent = parent;
}
return entry;
}
/**
* snd_info_create_card_entry - create an info entry for the given card
* @card: the card instance
* @name: the file name
* @parent: the parent directory
*
* Creates a new info entry and assigns it to the given card.
*
* Returns the pointer of the new instance, or NULL on failure.
*/
snd_info_entry_t *snd_info_create_card_entry(snd_card_t * card,
const char *name,
snd_info_entry_t * parent)
{
snd_info_entry_t *entry = snd_info_create_entry(name);
if (entry) {
entry->module = card->module;
entry->card = card;
entry->parent = parent;
}
return entry;
}
static int snd_info_dev_free_entry(snd_device_t *device)
{
snd_info_entry_t *entry = device->device_data;
snd_info_free_entry(entry);
return 0;
}
static int snd_info_dev_register_entry(snd_device_t *device)
{
snd_info_entry_t *entry = device->device_data;
return snd_info_register(entry);
}
static int snd_info_dev_disconnect_entry(snd_device_t *device)
{
snd_info_entry_t *entry = device->device_data;
entry->disconnected = 1;
return 0;
}
static int snd_info_dev_unregister_entry(snd_device_t *device)
{
snd_info_entry_t *entry = device->device_data;
return snd_info_unregister(entry);
}
/**
* snd_card_proc_new - create an info entry for the given card
* @card: the card instance
* @name: the file name
* @entryp: the pointer to store the new info entry
*
* Creates a new info entry and assigns it to the given card.
* Unlike snd_info_create_card_entry(), this function registers the
* info entry as an ALSA device component, so that it can be
* unregistered/released without explicit call.
* Also, you don't have to register this entry via snd_info_register(),
* since this will be registered by snd_card_register() automatically.
*
* The parent is assumed as card->proc_root.
*
* For releasing this entry, use snd_device_free() instead of
* snd_info_free_entry().
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_card_proc_new(snd_card_t *card, const char *name,
snd_info_entry_t **entryp)
{
static snd_device_ops_t ops = {
.dev_free = snd_info_dev_free_entry,
.dev_register = snd_info_dev_register_entry,
.dev_disconnect = snd_info_dev_disconnect_entry,
.dev_unregister = snd_info_dev_unregister_entry
};
snd_info_entry_t *entry;
int err;
entry = snd_info_create_card_entry(card, name, card->proc_root);
if (! entry)
return -ENOMEM;
if ((err = snd_device_new(card, SNDRV_DEV_INFO, entry, &ops)) < 0) {
snd_info_free_entry(entry);
return err;
}
if (entryp)
*entryp = entry;
return 0;
}
/**
* snd_info_free_entry - release the info entry
* @entry: the info entry
*
* Releases the info entry. Don't call this after registered.
*/
void snd_info_free_entry(snd_info_entry_t * entry)
{
if (entry == NULL)
return;
kfree(entry->name);
if (entry->private_free)
entry->private_free(entry);
kfree(entry);
}
/**
* snd_info_register - register the info entry
* @entry: the info entry
*
* Registers the proc info entry.
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_info_register(snd_info_entry_t * entry)
{
struct proc_dir_entry *root, *p = NULL;
snd_assert(entry != NULL, return -ENXIO);
root = entry->parent == NULL ? snd_proc_root : entry->parent->p;
down(&info_mutex);
p = snd_create_proc_entry(entry->name, entry->mode, root);
if (!p) {
up(&info_mutex);
return -ENOMEM;
}
p->owner = entry->module;
if (!S_ISDIR(entry->mode))
p->proc_fops = &snd_info_entry_operations;
p->size = entry->size;
p->data = entry;
entry->p = p;
up(&info_mutex);
return 0;
}
/**
* snd_info_unregister - de-register the info entry
* @entry: the info entry
*
* De-registers the info entry and releases the instance.
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_info_unregister(snd_info_entry_t * entry)
{
struct proc_dir_entry *root;
snd_assert(entry != NULL && entry->p != NULL, return -ENXIO);
root = entry->parent == NULL ? snd_proc_root : entry->parent->p;
snd_assert(root, return -ENXIO);
down(&info_mutex);
snd_remove_proc_entry(root, entry->p);
up(&info_mutex);
snd_info_free_entry(entry);
return 0;
}
/*
*/
static snd_info_entry_t *snd_info_version_entry = NULL;
static void snd_info_version_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
{
snd_iprintf(buffer,
"Advanced Linux Sound Architecture Driver Version "
CONFIG_SND_VERSION CONFIG_SND_DATE ".\n"
);
}
static int __init snd_info_version_init(void)
{
snd_info_entry_t *entry;
entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL);
if (entry == NULL)
return -ENOMEM;
entry->c.text.read_size = 256;
entry->c.text.read = snd_info_version_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
return -ENOMEM;
}
snd_info_version_entry = entry;
return 0;
}
static int __exit snd_info_version_done(void)
{
if (snd_info_version_entry)
snd_info_unregister(snd_info_version_entry);
return 0;
}
#endif /* CONFIG_PROC_FS */

137
sound/core/info_oss.c Normal file
View File

@@ -0,0 +1,137 @@
/*
* Information interface for ALSA driver
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <sound/info.h>
#include <sound/version.h>
#include <linux/utsname.h>
#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
/*
* OSS compatible part
*/
static DECLARE_MUTEX(strings);
static char *snd_sndstat_strings[SNDRV_CARDS][SNDRV_OSS_INFO_DEV_COUNT];
static snd_info_entry_t *snd_sndstat_proc_entry;
int snd_oss_info_register(int dev, int num, char *string)
{
char *x;
snd_assert(dev >= 0 && dev < SNDRV_OSS_INFO_DEV_COUNT, return -ENXIO);
snd_assert(num >= 0 && num < SNDRV_CARDS, return -ENXIO);
down(&strings);
if (string == NULL) {
if ((x = snd_sndstat_strings[num][dev]) != NULL) {
kfree(x);
x = NULL;
}
} else {
x = snd_kmalloc_strdup(string, GFP_KERNEL);
if (x == NULL) {
up(&strings);
return -ENOMEM;
}
}
snd_sndstat_strings[num][dev] = x;
up(&strings);
return 0;
}
extern void snd_card_info_read_oss(snd_info_buffer_t * buffer);
static int snd_sndstat_show_strings(snd_info_buffer_t * buf, char *id, int dev)
{
int idx, ok = -1;
char *str;
snd_iprintf(buf, "\n%s:", id);
down(&strings);
for (idx = 0; idx < SNDRV_CARDS; idx++) {
str = snd_sndstat_strings[idx][dev];
if (str) {
if (ok < 0) {
snd_iprintf(buf, "\n");
ok++;
}
snd_iprintf(buf, "%i: %s\n", idx, str);
}
}
up(&strings);
if (ok < 0)
snd_iprintf(buf, " NOT ENABLED IN CONFIG\n");
return ok;
}
static void snd_sndstat_proc_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
{
snd_iprintf(buffer, "Sound Driver:3.8.1a-980706 (ALSA v" CONFIG_SND_VERSION " emulation code)\n");
snd_iprintf(buffer, "Kernel: %s %s %s %s %s\n",
system_utsname.sysname,
system_utsname.nodename,
system_utsname.release,
system_utsname.version,
system_utsname.machine);
snd_iprintf(buffer, "Config options: 0\n");
snd_iprintf(buffer, "\nInstalled drivers: \n");
snd_iprintf(buffer, "Type 10: ALSA emulation\n");
snd_iprintf(buffer, "\nCard config: \n");
snd_card_info_read_oss(buffer);
snd_sndstat_show_strings(buffer, "Audio devices", SNDRV_OSS_INFO_DEV_AUDIO);
snd_sndstat_show_strings(buffer, "Synth devices", SNDRV_OSS_INFO_DEV_SYNTH);
snd_sndstat_show_strings(buffer, "Midi devices", SNDRV_OSS_INFO_DEV_MIDI);
snd_sndstat_show_strings(buffer, "Timers", SNDRV_OSS_INFO_DEV_TIMERS);
snd_sndstat_show_strings(buffer, "Mixers", SNDRV_OSS_INFO_DEV_MIXERS);
}
int snd_info_minor_register(void)
{
snd_info_entry_t *entry;
memset(snd_sndstat_strings, 0, sizeof(snd_sndstat_strings));
if ((entry = snd_info_create_module_entry(THIS_MODULE, "sndstat", snd_oss_root)) != NULL) {
entry->c.text.read_size = 2048;
entry->c.text.read = snd_sndstat_proc_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
snd_sndstat_proc_entry = entry;
return 0;
}
int snd_info_minor_unregister(void)
{
if (snd_sndstat_proc_entry) {
snd_info_unregister(snd_sndstat_proc_entry);
snd_sndstat_proc_entry = NULL;
}
return 0;
}
#endif /* CONFIG_SND_OSSEMUL */

887
sound/core/init.c Normal file
View File

@@ -0,0 +1,887 @@
/*
* Initialization routines
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/file.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/ctype.h>
#include <linux/pci.h>
#include <linux/pm.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
struct snd_shutdown_f_ops {
struct file_operations f_ops;
struct snd_shutdown_f_ops *next;
};
unsigned int snd_cards_lock = 0; /* locked for registering/using */
snd_card_t *snd_cards[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = NULL};
DEFINE_RWLOCK(snd_card_rwlock);
#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
int (*snd_mixer_oss_notify_callback)(snd_card_t *card, int free_flag);
#endif
static void snd_card_id_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
{
snd_iprintf(buffer, "%s\n", entry->card->id);
}
static void snd_card_free_thread(void * __card);
/**
* snd_card_new - create and initialize a soundcard structure
* @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
* @xid: card identification (ASCII string)
* @module: top level module for locking
* @extra_size: allocate this extra size after the main soundcard structure
*
* Creates and initializes a soundcard structure.
*
* Returns kmallocated snd_card_t structure. Creates the ALSA control interface
* (which is blocked until snd_card_register function is called).
*/
snd_card_t *snd_card_new(int idx, const char *xid,
struct module *module, int extra_size)
{
snd_card_t *card;
int err;
if (extra_size < 0)
extra_size = 0;
card = kcalloc(1, sizeof(*card) + extra_size, GFP_KERNEL);
if (card == NULL)
return NULL;
if (xid) {
if (!snd_info_check_reserved_words(xid))
goto __error;
strlcpy(card->id, xid, sizeof(card->id));
}
err = 0;
write_lock(&snd_card_rwlock);
if (idx < 0) {
int idx2;
for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
if (~snd_cards_lock & idx & 1<<idx2) {
idx = idx2;
if (idx >= snd_ecards_limit)
snd_ecards_limit = idx + 1;
break;
}
} else if (idx < snd_ecards_limit) {
if (snd_cards_lock & (1 << idx))
err = -ENODEV; /* invalid */
} else if (idx < SNDRV_CARDS)
snd_ecards_limit = idx + 1; /* increase the limit */
else
err = -ENODEV;
if (idx < 0 || err < 0) {
write_unlock(&snd_card_rwlock);
snd_printk(KERN_ERR "cannot find the slot for index %d (range 0-%i)\n", idx, snd_ecards_limit - 1);
goto __error;
}
snd_cards_lock |= 1 << idx; /* lock it */
write_unlock(&snd_card_rwlock);
card->number = idx;
card->module = module;
INIT_LIST_HEAD(&card->devices);
init_rwsem(&card->controls_rwsem);
rwlock_init(&card->ctl_files_rwlock);
INIT_LIST_HEAD(&card->controls);
INIT_LIST_HEAD(&card->ctl_files);
spin_lock_init(&card->files_lock);
init_waitqueue_head(&card->shutdown_sleep);
INIT_WORK(&card->free_workq, snd_card_free_thread, card);
#ifdef CONFIG_PM
init_MUTEX(&card->power_lock);
init_waitqueue_head(&card->power_sleep);
#endif
/* the control interface cannot be accessed from the user space until */
/* snd_cards_bitmask and snd_cards are set with snd_card_register */
if ((err = snd_ctl_create(card)) < 0) {
snd_printd("unable to register control minors\n");
goto __error;
}
if ((err = snd_info_card_create(card)) < 0) {
snd_printd("unable to create card info\n");
goto __error_ctl;
}
if (extra_size > 0)
card->private_data = (char *)card + sizeof(snd_card_t);
return card;
__error_ctl:
snd_device_free_all(card, SNDRV_DEV_CMD_PRE);
__error:
kfree(card);
return NULL;
}
static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait)
{
return POLLERR | POLLNVAL;
}
/**
* snd_card_disconnect - disconnect all APIs from the file-operations (user space)
* @card: soundcard structure
*
* Disconnects all APIs from the file-operations (user space).
*
* Returns zero, otherwise a negative error code.
*
* Note: The current implementation replaces all active file->f_op with special
* dummy file operations (they do nothing except release).
*/
int snd_card_disconnect(snd_card_t * card)
{
struct snd_monitor_file *mfile;
struct file *file;
struct snd_shutdown_f_ops *s_f_ops;
struct file_operations *f_ops, *old_f_ops;
int err;
spin_lock(&card->files_lock);
if (card->shutdown) {
spin_unlock(&card->files_lock);
return 0;
}
card->shutdown = 1;
spin_unlock(&card->files_lock);
/* phase 1: disable fops (user space) operations for ALSA API */
write_lock(&snd_card_rwlock);
snd_cards[card->number] = NULL;
write_unlock(&snd_card_rwlock);
/* phase 2: replace file->f_op with special dummy operations */
spin_lock(&card->files_lock);
mfile = card->files;
while (mfile) {
file = mfile->file;
/* it's critical part, use endless loop */
/* we have no room to fail */
s_f_ops = kmalloc(sizeof(struct snd_shutdown_f_ops), GFP_ATOMIC);
if (s_f_ops == NULL)
panic("Atomic allocation failed for snd_shutdown_f_ops!");
f_ops = &s_f_ops->f_ops;
memset(f_ops, 0, sizeof(*f_ops));
f_ops->owner = file->f_op->owner;
f_ops->release = file->f_op->release;
f_ops->poll = snd_disconnect_poll;
s_f_ops->next = card->s_f_ops;
card->s_f_ops = s_f_ops;
f_ops = fops_get(f_ops);
old_f_ops = file->f_op;
file->f_op = f_ops; /* must be atomic */
fops_put(old_f_ops);
mfile = mfile->next;
}
spin_unlock(&card->files_lock);
/* phase 3: notify all connected devices about disconnection */
/* at this point, they cannot respond to any calls except release() */
#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
if (snd_mixer_oss_notify_callback)
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT);
#endif
/* notify all devices that we are disconnected */
err = snd_device_disconnect_all(card);
if (err < 0)
snd_printk(KERN_ERR "not all devices for card %i can be disconnected\n", card->number);
return 0;
}
#if defined(CONFIG_PM) && defined(CONFIG_SND_GENERIC_PM)
static void snd_generic_device_unregister(struct snd_generic_device *dev);
#endif
/**
* snd_card_free - frees given soundcard structure
* @card: soundcard structure
*
* This function releases the soundcard structure and the all assigned
* devices automatically. That is, you don't have to release the devices
* by yourself.
*
* Returns zero. Frees all associated devices and frees the control
* interface associated to given soundcard.
*/
int snd_card_free(snd_card_t * card)
{
struct snd_shutdown_f_ops *s_f_ops;
if (card == NULL)
return -EINVAL;
write_lock(&snd_card_rwlock);
snd_cards[card->number] = NULL;
write_unlock(&snd_card_rwlock);
#ifdef CONFIG_PM
wake_up(&card->power_sleep);
#ifdef CONFIG_SND_GENERIC_PM
if (card->pm_dev) {
snd_generic_device_unregister(card->pm_dev);
card->pm_dev = NULL;
}
#endif
#endif
/* wait, until all devices are ready for the free operation */
wait_event(card->shutdown_sleep, card->files == NULL);
#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
if (snd_mixer_oss_notify_callback)
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE);
#endif
if (snd_device_free_all(card, SNDRV_DEV_CMD_PRE) < 0) {
snd_printk(KERN_ERR "unable to free all devices (pre)\n");
/* Fatal, but this situation should never occur */
}
if (snd_device_free_all(card, SNDRV_DEV_CMD_NORMAL) < 0) {
snd_printk(KERN_ERR "unable to free all devices (normal)\n");
/* Fatal, but this situation should never occur */
}
if (snd_device_free_all(card, SNDRV_DEV_CMD_POST) < 0) {
snd_printk(KERN_ERR "unable to free all devices (post)\n");
/* Fatal, but this situation should never occur */
}
if (card->private_free)
card->private_free(card);
if (card->proc_id)
snd_info_unregister(card->proc_id);
if (snd_info_card_free(card) < 0) {
snd_printk(KERN_WARNING "unable to free card info\n");
/* Not fatal error */
}
while (card->s_f_ops) {
s_f_ops = card->s_f_ops;
card->s_f_ops = s_f_ops->next;
kfree(s_f_ops);
}
write_lock(&snd_card_rwlock);
snd_cards_lock &= ~(1 << card->number);
write_unlock(&snd_card_rwlock);
kfree(card);
return 0;
}
static void snd_card_free_thread(void * __card)
{
snd_card_t *card = __card;
struct module * module = card->module;
if (!try_module_get(module)) {
snd_printk(KERN_ERR "unable to lock toplevel module for card %i in free thread\n", card->number);
module = NULL;
}
snd_card_free(card);
module_put(module);
}
/**
* snd_card_free_in_thread - call snd_card_free() in thread
* @card: soundcard structure
*
* This function schedules the call of snd_card_free() function in a
* work queue. When all devices are released (non-busy), the work
* is woken up and calls snd_card_free().
*
* When a card can be disconnected at any time by hotplug service,
* this function should be used in disconnect (or detach) callback
* instead of calling snd_card_free() directly.
*
* Returns - zero otherwise a negative error code if the start of thread failed.
*/
int snd_card_free_in_thread(snd_card_t * card)
{
if (card->files == NULL) {
snd_card_free(card);
return 0;
}
if (schedule_work(&card->free_workq))
return 0;
snd_printk(KERN_ERR "schedule_work() failed in snd_card_free_in_thread for card %i\n", card->number);
/* try to free the structure immediately */
snd_card_free(card);
return -EFAULT;
}
static void choose_default_id(snd_card_t * card)
{
int i, len, idx_flag = 0, loops = 8;
char *id, *spos;
id = spos = card->shortname;
while (*id != '\0') {
if (*id == ' ')
spos = id + 1;
id++;
}
id = card->id;
while (*spos != '\0' && !isalnum(*spos))
spos++;
if (isdigit(*spos))
*id++ = isalpha(card->shortname[0]) ? card->shortname[0] : 'D';
while (*spos != '\0' && (size_t)(id - card->id) < sizeof(card->id) - 1) {
if (isalnum(*spos))
*id++ = *spos;
spos++;
}
*id = '\0';
id = card->id;
if (*id == '\0')
strcpy(id, "default");
while (1) {
if (loops-- == 0) {
snd_printk(KERN_ERR "unable to choose default card id (%s)\n", id);
strcpy(card->id, card->proc_root->name);
return;
}
if (!snd_info_check_reserved_words(id))
goto __change;
for (i = 0; i < snd_ecards_limit; i++) {
if (snd_cards[i] && !strcmp(snd_cards[i]->id, id))
goto __change;
}
break;
__change:
len = strlen(id);
if (idx_flag)
id[len-1]++;
else if ((size_t)len <= sizeof(card->id) - 3) {
strcat(id, "_1");
idx_flag++;
} else {
spos = id + len - 2;
if ((size_t)len <= sizeof(card->id) - 2)
spos++;
*spos++ = '_';
*spos++ = '1';
*spos++ = '\0';
idx_flag++;
}
}
}
/**
* snd_card_register - register the soundcard
* @card: soundcard structure
*
* This function registers all the devices assigned to the soundcard.
* Until calling this, the ALSA control interface is blocked from the
* external accesses. Thus, you should call this function at the end
* of the initialization of the card.
*
* Returns zero otherwise a negative error code if the registrain failed.
*/
int snd_card_register(snd_card_t * card)
{
int err;
snd_info_entry_t *entry;
snd_runtime_check(card != NULL, return -EINVAL);
if ((err = snd_device_register_all(card)) < 0)
return err;
write_lock(&snd_card_rwlock);
if (snd_cards[card->number]) {
/* already registered */
write_unlock(&snd_card_rwlock);
return 0;
}
if (card->id[0] == '\0')
choose_default_id(card);
snd_cards[card->number] = card;
write_unlock(&snd_card_rwlock);
if ((err = snd_info_card_register(card)) < 0) {
snd_printd("unable to create card info\n");
goto __skip_info;
}
if ((entry = snd_info_create_card_entry(card, "id", card->proc_root)) == NULL) {
snd_printd("unable to create card entry\n");
goto __skip_info;
}
entry->c.text.read_size = PAGE_SIZE;
entry->c.text.read = snd_card_id_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
card->proc_id = entry;
__skip_info:
#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
if (snd_mixer_oss_notify_callback)
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
#endif
return 0;
}
static snd_info_entry_t *snd_card_info_entry = NULL;
static void snd_card_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
{
int idx, count;
snd_card_t *card;
for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
read_lock(&snd_card_rwlock);
if ((card = snd_cards[idx]) != NULL) {
count++;
snd_iprintf(buffer, "%i [%-15s]: %s - %s\n",
idx,
card->id,
card->driver,
card->shortname);
snd_iprintf(buffer, " %s\n",
card->longname);
}
read_unlock(&snd_card_rwlock);
}
if (!count)
snd_iprintf(buffer, "--- no soundcards ---\n");
}
#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
void snd_card_info_read_oss(snd_info_buffer_t * buffer)
{
int idx, count;
snd_card_t *card;
for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
read_lock(&snd_card_rwlock);
if ((card = snd_cards[idx]) != NULL) {
count++;
snd_iprintf(buffer, "%s\n", card->longname);
}
read_unlock(&snd_card_rwlock);
}
if (!count) {
snd_iprintf(buffer, "--- no soundcards ---\n");
}
}
#endif
#ifdef MODULE
static snd_info_entry_t *snd_card_module_info_entry;
static void snd_card_module_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
{
int idx;
snd_card_t *card;
for (idx = 0; idx < SNDRV_CARDS; idx++) {
read_lock(&snd_card_rwlock);
if ((card = snd_cards[idx]) != NULL)
snd_iprintf(buffer, "%i %s\n", idx, card->module->name);
read_unlock(&snd_card_rwlock);
}
}
#endif
int __init snd_card_info_init(void)
{
snd_info_entry_t *entry;
entry = snd_info_create_module_entry(THIS_MODULE, "cards", NULL);
snd_runtime_check(entry != NULL, return -ENOMEM);
entry->c.text.read_size = PAGE_SIZE;
entry->c.text.read = snd_card_info_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
return -ENOMEM;
}
snd_card_info_entry = entry;
#ifdef MODULE
entry = snd_info_create_module_entry(THIS_MODULE, "modules", NULL);
if (entry) {
entry->c.text.read_size = PAGE_SIZE;
entry->c.text.read = snd_card_module_info_read;
if (snd_info_register(entry) < 0)
snd_info_free_entry(entry);
else
snd_card_module_info_entry = entry;
}
#endif
return 0;
}
int __exit snd_card_info_done(void)
{
if (snd_card_info_entry)
snd_info_unregister(snd_card_info_entry);
#ifdef MODULE
if (snd_card_module_info_entry)
snd_info_unregister(snd_card_module_info_entry);
#endif
return 0;
}
/**
* snd_component_add - add a component string
* @card: soundcard structure
* @component: the component id string
*
* This function adds the component id string to the supported list.
* The component can be referred from the alsa-lib.
*
* Returns zero otherwise a negative error code.
*/
int snd_component_add(snd_card_t *card, const char *component)
{
char *ptr;
int len = strlen(component);
ptr = strstr(card->components, component);
if (ptr != NULL) {
if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */
return 1;
}
if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) {
snd_BUG();
return -ENOMEM;
}
if (card->components[0] != '\0')
strcat(card->components, " ");
strcat(card->components, component);
return 0;
}
/**
* snd_card_file_add - add the file to the file list of the card
* @card: soundcard structure
* @file: file pointer
*
* This function adds the file to the file linked-list of the card.
* This linked-list is used to keep tracking the connection state,
* and to avoid the release of busy resources by hotplug.
*
* Returns zero or a negative error code.
*/
int snd_card_file_add(snd_card_t *card, struct file *file)
{
struct snd_monitor_file *mfile;
mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
if (mfile == NULL)
return -ENOMEM;
mfile->file = file;
mfile->next = NULL;
spin_lock(&card->files_lock);
if (card->shutdown) {
spin_unlock(&card->files_lock);
kfree(mfile);
return -ENODEV;
}
mfile->next = card->files;
card->files = mfile;
spin_unlock(&card->files_lock);
return 0;
}
/**
* snd_card_file_remove - remove the file from the file list
* @card: soundcard structure
* @file: file pointer
*
* This function removes the file formerly added to the card via
* snd_card_file_add() function.
* If all files are removed and the release of the card is
* scheduled, it will wake up the the thread to call snd_card_free()
* (see snd_card_free_in_thread() function).
*
* Returns zero or a negative error code.
*/
int snd_card_file_remove(snd_card_t *card, struct file *file)
{
struct snd_monitor_file *mfile, *pfile = NULL;
spin_lock(&card->files_lock);
mfile = card->files;
while (mfile) {
if (mfile->file == file) {
if (pfile)
pfile->next = mfile->next;
else
card->files = mfile->next;
break;
}
pfile = mfile;
mfile = mfile->next;
}
spin_unlock(&card->files_lock);
if (card->files == NULL)
wake_up(&card->shutdown_sleep);
if (!mfile) {
snd_printk(KERN_ERR "ALSA card file remove problem (%p)\n", file);
return -ENOENT;
}
kfree(mfile);
return 0;
}
#ifdef CONFIG_PM
/**
* snd_power_wait - wait until the power-state is changed.
* @card: soundcard structure
* @power_state: expected power state
* @file: file structure for the O_NONBLOCK check (optional)
*
* Waits until the power-state is changed.
*
* Note: the power lock must be active before call.
*/
int snd_power_wait(snd_card_t *card, unsigned int power_state, struct file *file)
{
wait_queue_t wait;
int result = 0;
/* fastpath */
if (snd_power_get_state(card) == power_state)
return 0;
init_waitqueue_entry(&wait, current);
add_wait_queue(&card->power_sleep, &wait);
while (1) {
if (card->shutdown) {
result = -ENODEV;
break;
}
if (snd_power_get_state(card) == power_state)
break;
#if 0 /* block all devices */
if (file && (file->f_flags & O_NONBLOCK)) {
result = -EAGAIN;
break;
}
#endif
set_current_state(TASK_UNINTERRUPTIBLE);
snd_power_unlock(card);
schedule_timeout(30 * HZ);
snd_power_lock(card);
}
remove_wait_queue(&card->power_sleep, &wait);
return result;
}
/**
* snd_card_set_pm_callback - set the PCI power-management callbacks
* @card: soundcard structure
* @suspend: suspend callback function
* @resume: resume callback function
* @private_data: private data to pass to the callback functions
*
* Sets the power-management callback functions of the card.
* These callbacks are called from ALSA's common PCI suspend/resume
* handler and from the control API.
*/
int snd_card_set_pm_callback(snd_card_t *card,
int (*suspend)(snd_card_t *, pm_message_t),
int (*resume)(snd_card_t *),
void *private_data)
{
card->pm_suspend = suspend;
card->pm_resume = resume;
card->pm_private_data = private_data;
return 0;
}
#ifdef CONFIG_SND_GENERIC_PM
/*
* use platform_device for generic power-management without a proper bus
* (e.g. ISA)
*/
struct snd_generic_device {
struct platform_device pdev;
snd_card_t *card;
};
#define get_snd_generic_card(dev) container_of(to_platform_device(dev), struct snd_generic_device, pdev)->card
#define SND_GENERIC_NAME "snd_generic_pm"
static int snd_generic_suspend(struct device *dev, u32 state, u32 level);
static int snd_generic_resume(struct device *dev, u32 level);
static struct device_driver snd_generic_driver = {
.name = SND_GENERIC_NAME,
.bus = &platform_bus_type,
.suspend = snd_generic_suspend,
.resume = snd_generic_resume,
};
static int generic_driver_registered;
static void generic_driver_unregister(void)
{
if (generic_driver_registered) {
generic_driver_registered--;
if (! generic_driver_registered)
driver_unregister(&snd_generic_driver);
}
}
static struct snd_generic_device *snd_generic_device_register(snd_card_t *card)
{
struct snd_generic_device *dev;
if (! generic_driver_registered) {
if (driver_register(&snd_generic_driver) < 0)
return NULL;
}
generic_driver_registered++;
dev = kcalloc(1, sizeof(*dev), GFP_KERNEL);
if (! dev) {
generic_driver_unregister();
return NULL;
}
dev->pdev.name = SND_GENERIC_NAME;
dev->pdev.id = card->number;
dev->card = card;
if (platform_device_register(&dev->pdev) < 0) {
kfree(dev);
generic_driver_unregister();
return NULL;
}
return dev;
}
static void snd_generic_device_unregister(struct snd_generic_device *dev)
{
platform_device_unregister(&dev->pdev);
kfree(dev);
generic_driver_unregister();
}
/* suspend/resume callbacks for snd_generic platform device */
static int snd_generic_suspend(struct device *dev, u32 state, u32 level)
{
snd_card_t *card;
if (level != SUSPEND_DISABLE)
return 0;
card = get_snd_generic_card(dev);
if (card->power_state == SNDRV_CTL_POWER_D3hot)
return 0;
card->pm_suspend(card, PMSG_SUSPEND);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
return 0;
}
static int snd_generic_resume(struct device *dev, u32 level)
{
snd_card_t *card;
if (level != RESUME_ENABLE)
return 0;
card = get_snd_generic_card(dev);
if (card->power_state == SNDRV_CTL_POWER_D0)
return 0;
card->pm_resume(card);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
}
/**
* snd_card_set_generic_pm_callback - set the generic power-management callbacks
* @card: soundcard structure
* @suspend: suspend callback function
* @resume: resume callback function
* @private_data: private data to pass to the callback functions
*
* Registers the power-management and sets the lowlevel callbacks for
* the given card. These callbacks are called from the ALSA's common
* PM handler and from the control API.
*/
int snd_card_set_generic_pm_callback(snd_card_t *card,
int (*suspend)(snd_card_t *, pm_message_t),
int (*resume)(snd_card_t *),
void *private_data)
{
card->pm_dev = snd_generic_device_register(card);
if (! card->pm_dev)
return -ENOMEM;
snd_card_set_pm_callback(card, suspend, resume, private_data);
return 0;
}
#endif /* CONFIG_SND_GENERIC_PM */
#ifdef CONFIG_PCI
int snd_card_pci_suspend(struct pci_dev *dev, pm_message_t state)
{
snd_card_t *card = pci_get_drvdata(dev);
int err;
if (! card || ! card->pm_suspend)
return 0;
if (card->power_state == SNDRV_CTL_POWER_D3hot)
return 0;
err = card->pm_suspend(card, PMSG_SUSPEND);
pci_save_state(dev);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
return err;
}
int snd_card_pci_resume(struct pci_dev *dev)
{
snd_card_t *card = pci_get_drvdata(dev);
if (! card || ! card->pm_resume)
return 0;
if (card->power_state == SNDRV_CTL_POWER_D0)
return 0;
/* restore the PCI config space */
pci_restore_state(dev);
card->pm_resume(card);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
}
#endif
#endif /* CONFIG_PM */

103
sound/core/isadma.c Normal file
View File

@@ -0,0 +1,103 @@
/*
* ISA DMA support functions
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*
* Defining following add some delay. Maybe this helps for some broken
* ISA DMA controllers.
*/
#undef HAVE_REALLY_SLOW_DMA_CONTROLLER
#include <sound/driver.h>
#include <sound/core.h>
#include <asm/dma.h>
/**
* snd_dma_program - program an ISA DMA transfer
* @dma: the dma number
* @addr: the physical address of the buffer
* @size: the DMA transfer size
* @mode: the DMA transfer mode, DMA_MODE_XXX
*
* Programs an ISA DMA transfer for the given buffer.
*/
void snd_dma_program(unsigned long dma,
unsigned long addr, unsigned int size,
unsigned short mode)
{
unsigned long flags;
flags = claim_dma_lock();
disable_dma(dma);
clear_dma_ff(dma);
set_dma_mode(dma, mode);
set_dma_addr(dma, addr);
set_dma_count(dma, size);
if (!(mode & DMA_MODE_NO_ENABLE))
enable_dma(dma);
release_dma_lock(flags);
}
/**
* snd_dma_disable - stop the ISA DMA transfer
* @dma: the dma number
*
* Stops the ISA DMA transfer.
*/
void snd_dma_disable(unsigned long dma)
{
unsigned long flags;
flags = claim_dma_lock();
clear_dma_ff(dma);
disable_dma(dma);
release_dma_lock(flags);
}
/**
* snd_dma_pointer - return the current pointer to DMA transfer buffer in bytes
* @dma: the dma number
* @size: the dma transfer size
*
* Returns the current pointer in DMA tranfer buffer in bytes
*/
unsigned int snd_dma_pointer(unsigned long dma, unsigned int size)
{
unsigned long flags;
unsigned int result;
flags = claim_dma_lock();
clear_dma_ff(dma);
if (!isa_dma_bridge_buggy)
disable_dma(dma);
result = get_dma_residue(dma);
if (!isa_dma_bridge_buggy)
enable_dma(dma);
release_dma_lock(flags);
#ifdef CONFIG_SND_DEBUG
if (result > size)
snd_printk(KERN_ERR "pointer (0x%x) for DMA #%ld is greater than transfer size (0x%x)\n", result, dma, size);
#endif
if (result >= size || result == 0)
return 0;
else
return size - result;
}

663
sound/core/memalloc.c Normal file
View File

@@ -0,0 +1,663 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
* Takashi Iwai <tiwai@suse.de>
*
* Generic memory allocators
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/moduleparam.h>
#include <asm/semaphore.h>
#include <sound/memalloc.h>
#ifdef CONFIG_SBUS
#include <asm/sbus.h>
#endif
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("Memory allocator for ALSA system.");
MODULE_LICENSE("GPL");
#ifndef SNDRV_CARDS
#define SNDRV_CARDS 8
#endif
/* FIXME: so far only some PCI devices have the preallocation table */
#ifdef CONFIG_PCI
static int enable[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1};
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable cards to allocate buffers.");
#endif
/*
*/
void *snd_malloc_sgbuf_pages(struct device *device,
size_t size, struct snd_dma_buffer *dmab,
size_t *res_size);
int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab);
/*
*/
static DECLARE_MUTEX(list_mutex);
static LIST_HEAD(mem_list_head);
/* buffer preservation list */
struct snd_mem_list {
struct snd_dma_buffer buffer;
unsigned int id;
struct list_head list;
};
/* id for pre-allocated buffers */
#define SNDRV_DMA_DEVICE_UNUSED (unsigned int)-1
#ifdef CONFIG_SND_DEBUG
#define __ASTRING__(x) #x
#define snd_assert(expr, args...) do {\
if (!(expr)) {\
printk(KERN_ERR "snd-malloc: BUG? (%s) (called from %p)\n", __ASTRING__(expr), __builtin_return_address(0));\
args;\
}\
} while (0)
#else
#define snd_assert(expr, args...) /**/
#endif
/*
* Hacks
*/
#if defined(__i386__) || defined(__ppc__) || defined(__x86_64__)
/*
* A hack to allocate large buffers via dma_alloc_coherent()
*
* since dma_alloc_coherent always tries GFP_DMA when the requested
* pci memory region is below 32bit, it happens quite often that even
* 2 order of pages cannot be allocated.
*
* so in the following, we allocate at first without dma_mask, so that
* allocation will be done without GFP_DMA. if the area doesn't match
* with the requested region, then realloate with the original dma_mask
* again.
*
* Really, we want to move this type of thing into dma_alloc_coherent()
* so dma_mask doesn't have to be messed with.
*/
static void *snd_dma_hack_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, int flags)
{
void *ret;
u64 dma_mask, coherent_dma_mask;
if (dev == NULL || !dev->dma_mask)
return dma_alloc_coherent(dev, size, dma_handle, flags);
dma_mask = *dev->dma_mask;
coherent_dma_mask = dev->coherent_dma_mask;
*dev->dma_mask = 0xffffffff; /* do without masking */
dev->coherent_dma_mask = 0xffffffff; /* do without masking */
ret = dma_alloc_coherent(dev, size, dma_handle, flags);
*dev->dma_mask = dma_mask; /* restore */
dev->coherent_dma_mask = coherent_dma_mask; /* restore */
if (ret) {
/* obtained address is out of range? */
if (((unsigned long)*dma_handle + size - 1) & ~dma_mask) {
/* reallocate with the proper mask */
dma_free_coherent(dev, size, ret, *dma_handle);
ret = dma_alloc_coherent(dev, size, dma_handle, flags);
}
} else {
/* wish to success now with the proper mask... */
if (dma_mask != 0xffffffffUL) {
/* allocation with GFP_ATOMIC to avoid the long stall */
flags &= ~GFP_KERNEL;
flags |= GFP_ATOMIC;
ret = dma_alloc_coherent(dev, size, dma_handle, flags);
}
}
return ret;
}
/* redefine dma_alloc_coherent for some architectures */
#undef dma_alloc_coherent
#define dma_alloc_coherent snd_dma_hack_alloc_coherent
#endif /* arch */
#if ! defined(__arm__)
#define NEED_RESERVE_PAGES
#endif
/*
*
* Generic memory allocators
*
*/
static long snd_allocated_pages; /* holding the number of allocated pages */
static inline void inc_snd_pages(int order)
{
snd_allocated_pages += 1 << order;
}
static inline void dec_snd_pages(int order)
{
snd_allocated_pages -= 1 << order;
}
static void mark_pages(struct page *page, int order)
{
struct page *last_page = page + (1 << order);
while (page < last_page)
SetPageReserved(page++);
}
static void unmark_pages(struct page *page, int order)
{
struct page *last_page = page + (1 << order);
while (page < last_page)
ClearPageReserved(page++);
}
/**
* snd_malloc_pages - allocate pages with the given size
* @size: the size to allocate in bytes
* @gfp_flags: the allocation conditions, GFP_XXX
*
* Allocates the physically contiguous pages with the given size.
*
* Returns the pointer of the buffer, or NULL if no enoguh memory.
*/
void *snd_malloc_pages(size_t size, unsigned int gfp_flags)
{
int pg;
void *res;
snd_assert(size > 0, return NULL);
snd_assert(gfp_flags != 0, return NULL);
pg = get_order(size);
if ((res = (void *) __get_free_pages(gfp_flags, pg)) != NULL) {
mark_pages(virt_to_page(res), pg);
inc_snd_pages(pg);
}
return res;
}
/**
* snd_free_pages - release the pages
* @ptr: the buffer pointer to release
* @size: the allocated buffer size
*
* Releases the buffer allocated via snd_malloc_pages().
*/
void snd_free_pages(void *ptr, size_t size)
{
int pg;
if (ptr == NULL)
return;
pg = get_order(size);
dec_snd_pages(pg);
unmark_pages(virt_to_page(ptr), pg);
free_pages((unsigned long) ptr, pg);
}
/*
*
* Bus-specific memory allocators
*
*/
/* allocate the coherent DMA pages */
static void *snd_malloc_dev_pages(struct device *dev, size_t size, dma_addr_t *dma)
{
int pg;
void *res;
unsigned int gfp_flags;
snd_assert(size > 0, return NULL);
snd_assert(dma != NULL, return NULL);
pg = get_order(size);
gfp_flags = GFP_KERNEL
| __GFP_NORETRY /* don't trigger OOM-killer */
| __GFP_NOWARN; /* no stack trace print - this call is non-critical */
res = dma_alloc_coherent(dev, PAGE_SIZE << pg, dma, gfp_flags);
if (res != NULL) {
#ifdef NEED_RESERVE_PAGES
mark_pages(virt_to_page(res), pg); /* should be dma_to_page() */
#endif
inc_snd_pages(pg);
}
return res;
}
/* free the coherent DMA pages */
static void snd_free_dev_pages(struct device *dev, size_t size, void *ptr,
dma_addr_t dma)
{
int pg;
if (ptr == NULL)
return;
pg = get_order(size);
dec_snd_pages(pg);
#ifdef NEED_RESERVE_PAGES
unmark_pages(virt_to_page(ptr), pg); /* should be dma_to_page() */
#endif
dma_free_coherent(dev, PAGE_SIZE << pg, ptr, dma);
}
#ifdef CONFIG_SBUS
static void *snd_malloc_sbus_pages(struct device *dev, size_t size,
dma_addr_t *dma_addr)
{
struct sbus_dev *sdev = (struct sbus_dev *)dev;
int pg;
void *res;
snd_assert(size > 0, return NULL);
snd_assert(dma_addr != NULL, return NULL);
pg = get_order(size);
res = sbus_alloc_consistent(sdev, PAGE_SIZE * (1 << pg), dma_addr);
if (res != NULL)
inc_snd_pages(pg);
return res;
}
static void snd_free_sbus_pages(struct device *dev, size_t size,
void *ptr, dma_addr_t dma_addr)
{
struct sbus_dev *sdev = (struct sbus_dev *)dev;
int pg;
if (ptr == NULL)
return;
pg = get_order(size);
dec_snd_pages(pg);
sbus_free_consistent(sdev, PAGE_SIZE * (1 << pg), ptr, dma_addr);
}
#endif /* CONFIG_SBUS */
/*
*
* ALSA generic memory management
*
*/
/**
* snd_dma_alloc_pages - allocate the buffer area according to the given type
* @type: the DMA buffer type
* @device: the device pointer
* @size: the buffer size to allocate
* @dmab: buffer allocation record to store the allocated data
*
* Calls the memory-allocator function for the corresponding
* buffer type.
*
* Returns zero if the buffer with the given size is allocated successfuly,
* other a negative value at error.
*/
int snd_dma_alloc_pages(int type, struct device *device, size_t size,
struct snd_dma_buffer *dmab)
{
snd_assert(size > 0, return -ENXIO);
snd_assert(dmab != NULL, return -ENXIO);
dmab->dev.type = type;
dmab->dev.dev = device;
dmab->bytes = 0;
switch (type) {
case SNDRV_DMA_TYPE_CONTINUOUS:
dmab->area = snd_malloc_pages(size, (unsigned long)device);
dmab->addr = 0;
break;
#ifdef CONFIG_SBUS
case SNDRV_DMA_TYPE_SBUS:
dmab->area = snd_malloc_sbus_pages(device, size, &dmab->addr);
break;
#endif
case SNDRV_DMA_TYPE_DEV:
dmab->area = snd_malloc_dev_pages(device, size, &dmab->addr);
break;
case SNDRV_DMA_TYPE_DEV_SG:
snd_malloc_sgbuf_pages(device, size, dmab, NULL);
break;
default:
printk(KERN_ERR "snd-malloc: invalid device type %d\n", type);
dmab->area = NULL;
dmab->addr = 0;
return -ENXIO;
}
if (! dmab->area)
return -ENOMEM;
dmab->bytes = size;
return 0;
}
/**
* snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback
* @type: the DMA buffer type
* @device: the device pointer
* @size: the buffer size to allocate
* @dmab: buffer allocation record to store the allocated data
*
* Calls the memory-allocator function for the corresponding
* buffer type. When no space is left, this function reduces the size and
* tries to allocate again. The size actually allocated is stored in
* res_size argument.
*
* Returns zero if the buffer with the given size is allocated successfuly,
* other a negative value at error.
*/
int snd_dma_alloc_pages_fallback(int type, struct device *device, size_t size,
struct snd_dma_buffer *dmab)
{
int err;
snd_assert(size > 0, return -ENXIO);
snd_assert(dmab != NULL, return -ENXIO);
while ((err = snd_dma_alloc_pages(type, device, size, dmab)) < 0) {
if (err != -ENOMEM)
return err;
size >>= 1;
if (size <= PAGE_SIZE)
return -ENOMEM;
}
if (! dmab->area)
return -ENOMEM;
return 0;
}
/**
* snd_dma_free_pages - release the allocated buffer
* @dmab: the buffer allocation record to release
*
* Releases the allocated buffer via snd_dma_alloc_pages().
*/
void snd_dma_free_pages(struct snd_dma_buffer *dmab)
{
switch (dmab->dev.type) {
case SNDRV_DMA_TYPE_CONTINUOUS:
snd_free_pages(dmab->area, dmab->bytes);
break;
#ifdef CONFIG_SBUS
case SNDRV_DMA_TYPE_SBUS:
snd_free_sbus_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr);
break;
#endif
case SNDRV_DMA_TYPE_DEV:
snd_free_dev_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr);
break;
case SNDRV_DMA_TYPE_DEV_SG:
snd_free_sgbuf_pages(dmab);
break;
default:
printk(KERN_ERR "snd-malloc: invalid device type %d\n", dmab->dev.type);
}
}
/**
* snd_dma_get_reserved - get the reserved buffer for the given device
* @dmab: the buffer allocation record to store
* @id: the buffer id
*
* Looks for the reserved-buffer list and re-uses if the same buffer
* is found in the list. When the buffer is found, it's removed from the free list.
*
* Returns the size of buffer if the buffer is found, or zero if not found.
*/
size_t snd_dma_get_reserved_buf(struct snd_dma_buffer *dmab, unsigned int id)
{
struct list_head *p;
struct snd_mem_list *mem;
snd_assert(dmab, return 0);
down(&list_mutex);
list_for_each(p, &mem_list_head) {
mem = list_entry(p, struct snd_mem_list, list);
if (mem->id == id &&
! memcmp(&mem->buffer.dev, &dmab->dev, sizeof(dmab->dev))) {
list_del(p);
*dmab = mem->buffer;
kfree(mem);
up(&list_mutex);
return dmab->bytes;
}
}
up(&list_mutex);
return 0;
}
/**
* snd_dma_reserve_buf - reserve the buffer
* @dmab: the buffer to reserve
* @id: the buffer id
*
* Reserves the given buffer as a reserved buffer.
*
* Returns zero if successful, or a negative code at error.
*/
int snd_dma_reserve_buf(struct snd_dma_buffer *dmab, unsigned int id)
{
struct snd_mem_list *mem;
snd_assert(dmab, return -EINVAL);
mem = kmalloc(sizeof(*mem), GFP_KERNEL);
if (! mem)
return -ENOMEM;
down(&list_mutex);
mem->buffer = *dmab;
mem->id = id;
list_add_tail(&mem->list, &mem_list_head);
up(&list_mutex);
return 0;
}
/*
* purge all reserved buffers
*/
static void free_all_reserved_pages(void)
{
struct list_head *p;
struct snd_mem_list *mem;
down(&list_mutex);
while (! list_empty(&mem_list_head)) {
p = mem_list_head.next;
mem = list_entry(p, struct snd_mem_list, list);
list_del(p);
snd_dma_free_pages(&mem->buffer);
kfree(mem);
}
up(&list_mutex);
}
/*
* allocation of buffers for pre-defined devices
*/
#ifdef CONFIG_PCI
/* FIXME: for pci only - other bus? */
struct prealloc_dev {
unsigned short vendor;
unsigned short device;
unsigned long dma_mask;
unsigned int size;
unsigned int buffers;
};
#define HAMMERFALL_BUFFER_SIZE (16*1024*4*(26+1)+0x10000)
static struct prealloc_dev prealloc_devices[] __initdata = {
{
/* hammerfall */
.vendor = 0x10ee,
.device = 0x3fc4,
.dma_mask = 0xffffffff,
.size = HAMMERFALL_BUFFER_SIZE,
.buffers = 2
},
{
/* HDSP */
.vendor = 0x10ee,
.device = 0x3fc5,
.dma_mask = 0xffffffff,
.size = HAMMERFALL_BUFFER_SIZE,
.buffers = 2
},
{ }, /* terminator */
};
static void __init preallocate_cards(void)
{
struct pci_dev *pci = NULL;
int card;
card = 0;
while ((pci = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pci)) != NULL) {
struct prealloc_dev *dev;
unsigned int i;
if (card >= SNDRV_CARDS)
break;
for (dev = prealloc_devices; dev->vendor; dev++) {
if (dev->vendor == pci->vendor && dev->device == pci->device)
break;
}
if (! dev->vendor)
continue;
if (! enable[card++]) {
printk(KERN_DEBUG "snd-page-alloc: skipping card %d, device %04x:%04x\n", card, pci->vendor, pci->device);
continue;
}
if (pci_set_dma_mask(pci, dev->dma_mask) < 0 ||
pci_set_consistent_dma_mask(pci, dev->dma_mask) < 0) {
printk(KERN_ERR "snd-page-alloc: cannot set DMA mask %lx for pci %04x:%04x\n", dev->dma_mask, dev->vendor, dev->device);
continue;
}
for (i = 0; i < dev->buffers; i++) {
struct snd_dma_buffer dmab;
memset(&dmab, 0, sizeof(dmab));
if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
dev->size, &dmab) < 0)
printk(KERN_WARNING "snd-page-alloc: cannot allocate buffer pages (size = %d)\n", dev->size);
else
snd_dma_reserve_buf(&dmab, snd_dma_pci_buf_id(pci));
}
}
}
#else
#define preallocate_cards() /* NOP */
#endif
#ifdef CONFIG_PROC_FS
/*
* proc file interface
*/
static int snd_mem_proc_read(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
int len = 0;
long pages = snd_allocated_pages >> (PAGE_SHIFT-12);
struct list_head *p;
struct snd_mem_list *mem;
int devno;
static char *types[] = { "UNKNOWN", "CONT", "DEV", "DEV-SG", "SBUS" };
down(&list_mutex);
len += snprintf(page + len, count - len,
"pages : %li bytes (%li pages per %likB)\n",
pages * PAGE_SIZE, pages, PAGE_SIZE / 1024);
devno = 0;
list_for_each(p, &mem_list_head) {
mem = list_entry(p, struct snd_mem_list, list);
devno++;
len += snprintf(page + len, count - len,
"buffer %d : ID %08x : type %s\n",
devno, mem->id, types[mem->buffer.dev.type]);
len += snprintf(page + len, count - len,
" addr = 0x%lx, size = %d bytes\n",
(unsigned long)mem->buffer.addr, (int)mem->buffer.bytes);
}
up(&list_mutex);
return len;
}
#endif /* CONFIG_PROC_FS */
/*
* module entry
*/
static int __init snd_mem_init(void)
{
#ifdef CONFIG_PROC_FS
create_proc_read_entry("driver/snd-page-alloc", 0, NULL, snd_mem_proc_read, NULL);
#endif
preallocate_cards();
return 0;
}
static void __exit snd_mem_exit(void)
{
remove_proc_entry("driver/snd-page-alloc", NULL);
free_all_reserved_pages();
if (snd_allocated_pages > 0)
printk(KERN_ERR "snd-malloc: Memory leak? pages not freed = %li\n", snd_allocated_pages);
}
module_init(snd_mem_init)
module_exit(snd_mem_exit)
/*
* exports
*/
EXPORT_SYMBOL(snd_dma_alloc_pages);
EXPORT_SYMBOL(snd_dma_alloc_pages_fallback);
EXPORT_SYMBOL(snd_dma_free_pages);
EXPORT_SYMBOL(snd_dma_get_reserved_buf);
EXPORT_SYMBOL(snd_dma_reserve_buf);
EXPORT_SYMBOL(snd_malloc_pages);
EXPORT_SYMBOL(snd_free_pages);

306
sound/core/memory.c Normal file
View File

@@ -0,0 +1,306 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
* Memory allocation helpers.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/pci.h>
#include <sound/core.h>
#include <sound/info.h>
/*
* memory allocation helpers and debug routines
*/
#ifdef CONFIG_SND_DEBUG_MEMORY
struct snd_alloc_track {
unsigned long magic;
void *caller;
size_t size;
struct list_head list;
long data[0];
};
#define snd_alloc_track_entry(obj) (struct snd_alloc_track *)((char*)obj - (unsigned long)((struct snd_alloc_track *)0)->data)
static long snd_alloc_kmalloc;
static long snd_alloc_vmalloc;
static LIST_HEAD(snd_alloc_kmalloc_list);
static LIST_HEAD(snd_alloc_vmalloc_list);
static DEFINE_SPINLOCK(snd_alloc_kmalloc_lock);
static DEFINE_SPINLOCK(snd_alloc_vmalloc_lock);
#define KMALLOC_MAGIC 0x87654321
#define VMALLOC_MAGIC 0x87654320
static snd_info_entry_t *snd_memory_info_entry;
void snd_memory_init(void)
{
snd_alloc_kmalloc = 0;
snd_alloc_vmalloc = 0;
}
void snd_memory_done(void)
{
struct list_head *head;
struct snd_alloc_track *t;
if (snd_alloc_kmalloc > 0)
snd_printk(KERN_ERR "Not freed snd_alloc_kmalloc = %li\n", snd_alloc_kmalloc);
if (snd_alloc_vmalloc > 0)
snd_printk(KERN_ERR "Not freed snd_alloc_vmalloc = %li\n", snd_alloc_vmalloc);
list_for_each_prev(head, &snd_alloc_kmalloc_list) {
t = list_entry(head, struct snd_alloc_track, list);
if (t->magic != KMALLOC_MAGIC) {
snd_printk(KERN_ERR "Corrupted kmalloc\n");
break;
}
snd_printk(KERN_ERR "kmalloc(%ld) from %p not freed\n", (long) t->size, t->caller);
}
list_for_each_prev(head, &snd_alloc_vmalloc_list) {
t = list_entry(head, struct snd_alloc_track, list);
if (t->magic != VMALLOC_MAGIC) {
snd_printk(KERN_ERR "Corrupted vmalloc\n");
break;
}
snd_printk(KERN_ERR "vmalloc(%ld) from %p not freed\n", (long) t->size, t->caller);
}
}
static void *__snd_kmalloc(size_t size, int flags, void *caller)
{
unsigned long cpu_flags;
struct snd_alloc_track *t;
void *ptr;
ptr = snd_wrapper_kmalloc(size + sizeof(struct snd_alloc_track), flags);
if (ptr != NULL) {
t = (struct snd_alloc_track *)ptr;
t->magic = KMALLOC_MAGIC;
t->caller = caller;
spin_lock_irqsave(&snd_alloc_kmalloc_lock, cpu_flags);
list_add_tail(&t->list, &snd_alloc_kmalloc_list);
spin_unlock_irqrestore(&snd_alloc_kmalloc_lock, cpu_flags);
t->size = size;
snd_alloc_kmalloc += size;
ptr = t->data;
}
return ptr;
}
#define _snd_kmalloc(size, flags) __snd_kmalloc((size), (flags), __builtin_return_address(0));
void *snd_hidden_kmalloc(size_t size, int flags)
{
return _snd_kmalloc(size, flags);
}
void *snd_hidden_kcalloc(size_t n, size_t size, int flags)
{
void *ret = NULL;
if (n != 0 && size > INT_MAX / n)
return ret;
ret = _snd_kmalloc(n * size, flags);
if (ret)
memset(ret, 0, n * size);
return ret;
}
void snd_hidden_kfree(const void *obj)
{
unsigned long flags;
struct snd_alloc_track *t;
if (obj == NULL)
return;
t = snd_alloc_track_entry(obj);
if (t->magic != KMALLOC_MAGIC) {
snd_printk(KERN_WARNING "bad kfree (called from %p)\n", __builtin_return_address(0));
return;
}
spin_lock_irqsave(&snd_alloc_kmalloc_lock, flags);
list_del(&t->list);
spin_unlock_irqrestore(&snd_alloc_kmalloc_lock, flags);
t->magic = 0;
snd_alloc_kmalloc -= t->size;
obj = t;
snd_wrapper_kfree(obj);
}
void *snd_hidden_vmalloc(unsigned long size)
{
void *ptr;
ptr = snd_wrapper_vmalloc(size + sizeof(struct snd_alloc_track));
if (ptr) {
struct snd_alloc_track *t = (struct snd_alloc_track *)ptr;
t->magic = VMALLOC_MAGIC;
t->caller = __builtin_return_address(0);
spin_lock(&snd_alloc_vmalloc_lock);
list_add_tail(&t->list, &snd_alloc_vmalloc_list);
spin_unlock(&snd_alloc_vmalloc_lock);
t->size = size;
snd_alloc_vmalloc += size;
ptr = t->data;
}
return ptr;
}
void snd_hidden_vfree(void *obj)
{
struct snd_alloc_track *t;
if (obj == NULL)
return;
t = snd_alloc_track_entry(obj);
if (t->magic != VMALLOC_MAGIC) {
snd_printk(KERN_ERR "bad vfree (called from %p)\n", __builtin_return_address(0));
return;
}
spin_lock(&snd_alloc_vmalloc_lock);
list_del(&t->list);
spin_unlock(&snd_alloc_vmalloc_lock);
t->magic = 0;
snd_alloc_vmalloc -= t->size;
obj = t;
snd_wrapper_vfree(obj);
}
static void snd_memory_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
{
snd_iprintf(buffer, "kmalloc: %li bytes\n", snd_alloc_kmalloc);
snd_iprintf(buffer, "vmalloc: %li bytes\n", snd_alloc_vmalloc);
}
int __init snd_memory_info_init(void)
{
snd_info_entry_t *entry;
entry = snd_info_create_module_entry(THIS_MODULE, "meminfo", NULL);
if (entry) {
entry->c.text.read_size = 256;
entry->c.text.read = snd_memory_info_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
snd_memory_info_entry = entry;
return 0;
}
int __exit snd_memory_info_done(void)
{
if (snd_memory_info_entry)
snd_info_unregister(snd_memory_info_entry);
return 0;
}
#else
#define _snd_kmalloc kmalloc
#endif /* CONFIG_SND_DEBUG_MEMORY */
/**
* snd_kmalloc_strdup - copy the string
* @string: the original string
* @flags: allocation conditions, GFP_XXX
*
* Allocates a memory chunk via kmalloc() and copies the string to it.
*
* Returns the pointer, or NULL if no enoguh memory.
*/
char *snd_kmalloc_strdup(const char *string, int flags)
{
size_t len;
char *ptr;
if (!string)
return NULL;
len = strlen(string) + 1;
ptr = _snd_kmalloc(len, flags);
if (ptr)
memcpy(ptr, string, len);
return ptr;
}
/**
* copy_to_user_fromio - copy data from mmio-space to user-space
* @dst: the destination pointer on user-space
* @src: the source pointer on mmio
* @count: the data size to copy in bytes
*
* Copies the data from mmio-space to user-space.
*
* Returns zero if successful, or non-zero on failure.
*/
int copy_to_user_fromio(void __user *dst, const volatile void __iomem *src, size_t count)
{
#if defined(__i386__) || defined(CONFIG_SPARC32)
return copy_to_user(dst, (const void*)src, count) ? -EFAULT : 0;
#else
char buf[256];
while (count) {
size_t c = count;
if (c > sizeof(buf))
c = sizeof(buf);
memcpy_fromio(buf, (void __iomem *)src, c);
if (copy_to_user(dst, buf, c))
return -EFAULT;
count -= c;
dst += c;
src += c;
}
return 0;
#endif
}
/**
* copy_from_user_toio - copy data from user-space to mmio-space
* @dst: the destination pointer on mmio-space
* @src: the source pointer on user-space
* @count: the data size to copy in bytes
*
* Copies the data from user-space to mmio-space.
*
* Returns zero if successful, or non-zero on failure.
*/
int copy_from_user_toio(volatile void __iomem *dst, const void __user *src, size_t count)
{
#if defined(__i386__) || defined(CONFIG_SPARC32)
return copy_from_user((void*)dst, src, count) ? -EFAULT : 0;
#else
char buf[256];
while (count) {
size_t c = count;
if (c > sizeof(buf))
c = sizeof(buf);
if (copy_from_user(buf, src, c))
return -EFAULT;
memcpy_toio(dst, buf, c);
count -= c;
dst += c;
src += c;
}
return 0;
#endif
}

76
sound/core/misc.c Normal file
View File

@@ -0,0 +1,76 @@
/*
* Misc and compatibility things
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <sound/core.h>
int snd_task_name(struct task_struct *task, char *name, size_t size)
{
unsigned int idx;
snd_assert(task != NULL && name != NULL && size >= 2, return -EINVAL);
for (idx = 0; idx < sizeof(task->comm) && idx + 1 < size; idx++)
name[idx] = task->comm[idx];
name[idx] = '\0';
return 0;
}
#ifdef CONFIG_SND_VERBOSE_PRINTK
void snd_verbose_printk(const char *file, int line, const char *format, ...)
{
va_list args;
if (format[0] == '<' && format[1] >= '0' && format[1] <= '9' && format[2] == '>') {
char tmp[] = "<0>";
tmp[1] = format[1];
printk("%sALSA %s:%d: ", tmp, file, line);
format += 3;
} else {
printk("ALSA %s:%d: ", file, line);
}
va_start(args, format);
vprintk(format, args);
va_end(args);
}
#endif
#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_SND_VERBOSE_PRINTK)
void snd_verbose_printd(const char *file, int line, const char *format, ...)
{
va_list args;
if (format[0] == '<' && format[1] >= '0' && format[1] <= '9' && format[2] == '>') {
char tmp[] = "<0>";
tmp[1] = format[1];
printk("%sALSA %s:%d: ", tmp, file, line);
format += 3;
} else {
printk(KERN_DEBUG "ALSA %s:%d: ", file, line);
}
va_start(args, format);
vprintk(format, args);
va_end(args);
}
#endif

12
sound/core/oss/Makefile Normal file
View File

@@ -0,0 +1,12 @@
#
# Makefile for ALSA
# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
#
snd-mixer-oss-objs := mixer_oss.o
snd-pcm-oss-objs := pcm_oss.o pcm_plugin.o \
io.o copy.o linear.o mulaw.o route.o rate.o
obj-$(CONFIG_SND_MIXER_OSS) += snd-mixer-oss.o
obj-$(CONFIG_SND_PCM_OSS) += snd-pcm-oss.o

87
sound/core/oss/copy.c Normal file
View File

@@ -0,0 +1,87 @@
/*
* Linear conversion Plug-In
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "pcm_plugin.h"
static snd_pcm_sframes_t copy_transfer(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
snd_pcm_uframes_t frames)
{
unsigned int channel;
unsigned int nchannels;
snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
if (frames == 0)
return 0;
nchannels = plugin->src_format.channels;
for (channel = 0; channel < nchannels; channel++) {
snd_assert(src_channels->area.first % 8 == 0 &&
src_channels->area.step % 8 == 0,
return -ENXIO);
snd_assert(dst_channels->area.first % 8 == 0 &&
dst_channels->area.step % 8 == 0,
return -ENXIO);
if (!src_channels->enabled) {
if (dst_channels->wanted)
snd_pcm_area_silence(&dst_channels->area, 0, frames, plugin->dst_format.format);
dst_channels->enabled = 0;
continue;
}
dst_channels->enabled = 1;
snd_pcm_area_copy(&src_channels->area, 0, &dst_channels->area, 0, frames, plugin->src_format.format);
src_channels++;
dst_channels++;
}
return frames;
}
int snd_pcm_plugin_build_copy(snd_pcm_plug_t *plug,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
snd_pcm_plugin_t **r_plugin)
{
int err;
snd_pcm_plugin_t *plugin;
int width;
snd_assert(r_plugin != NULL, return -ENXIO);
*r_plugin = NULL;
snd_assert(src_format->format == dst_format->format, return -ENXIO);
snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
width = snd_pcm_format_physical_width(src_format->format);
snd_assert(width > 0, return -ENXIO);
err = snd_pcm_plugin_build(plug, "copy", src_format, dst_format,
0, &plugin);
if (err < 0)
return err;
plugin->transfer = copy_transfer;
*r_plugin = plugin;
return 0;
}

134
sound/core/oss/io.c Normal file
View File

@@ -0,0 +1,134 @@
/*
* PCM I/O Plug-In Interface
* Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "pcm_plugin.h"
#define pcm_write(plug,buf,count) snd_pcm_oss_write3(plug,buf,count,1)
#define pcm_writev(plug,vec,count) snd_pcm_oss_writev3(plug,vec,count,1)
#define pcm_read(plug,buf,count) snd_pcm_oss_read3(plug,buf,count,1)
#define pcm_readv(plug,vec,count) snd_pcm_oss_readv3(plug,vec,count,1)
/*
* Basic io plugin
*/
static snd_pcm_sframes_t io_playback_transfer(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels ATTRIBUTE_UNUSED,
snd_pcm_uframes_t frames)
{
snd_assert(plugin != NULL, return -ENXIO);
snd_assert(src_channels != NULL, return -ENXIO);
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
return pcm_write(plugin->plug, src_channels->area.addr, frames);
} else {
int channel, channels = plugin->dst_format.channels;
void **bufs = (void**)plugin->extra_data;
snd_assert(bufs != NULL, return -ENXIO);
for (channel = 0; channel < channels; channel++) {
if (src_channels[channel].enabled)
bufs[channel] = src_channels[channel].area.addr;
else
bufs[channel] = NULL;
}
return pcm_writev(plugin->plug, bufs, frames);
}
}
static snd_pcm_sframes_t io_capture_transfer(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels ATTRIBUTE_UNUSED,
snd_pcm_plugin_channel_t *dst_channels,
snd_pcm_uframes_t frames)
{
snd_assert(plugin != NULL, return -ENXIO);
snd_assert(dst_channels != NULL, return -ENXIO);
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
return pcm_read(plugin->plug, dst_channels->area.addr, frames);
} else {
int channel, channels = plugin->dst_format.channels;
void **bufs = (void**)plugin->extra_data;
snd_assert(bufs != NULL, return -ENXIO);
for (channel = 0; channel < channels; channel++) {
if (dst_channels[channel].enabled)
bufs[channel] = dst_channels[channel].area.addr;
else
bufs[channel] = NULL;
}
return pcm_readv(plugin->plug, bufs, frames);
}
return 0;
}
static snd_pcm_sframes_t io_src_channels(snd_pcm_plugin_t *plugin,
snd_pcm_uframes_t frames,
snd_pcm_plugin_channel_t **channels)
{
int err;
unsigned int channel;
snd_pcm_plugin_channel_t *v;
err = snd_pcm_plugin_client_channels(plugin, frames, &v);
if (err < 0)
return err;
*channels = v;
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
for (channel = 0; channel < plugin->src_format.channels; ++channel, ++v)
v->wanted = 1;
}
return frames;
}
int snd_pcm_plugin_build_io(snd_pcm_plug_t *plug,
snd_pcm_hw_params_t *params,
snd_pcm_plugin_t **r_plugin)
{
int err;
snd_pcm_plugin_format_t format;
snd_pcm_plugin_t *plugin;
snd_assert(r_plugin != NULL, return -ENXIO);
*r_plugin = NULL;
snd_assert(plug != NULL && params != NULL, return -ENXIO);
format.format = params_format(params);
format.rate = params_rate(params);
format.channels = params_channels(params);
err = snd_pcm_plugin_build(plug, "I/O io",
&format, &format,
sizeof(void *) * format.channels,
&plugin);
if (err < 0)
return err;
plugin->access = params_access(params);
if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
plugin->transfer = io_playback_transfer;
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED)
plugin->client_channels = io_src_channels;
} else {
plugin->transfer = io_capture_transfer;
}
*r_plugin = plugin;
return 0;
}

158
sound/core/oss/linear.c Normal file
View File

@@ -0,0 +1,158 @@
/*
* Linear conversion Plug-In
* Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>,
* Abramo Bagnara <abramo@alsa-project.org>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "pcm_plugin.h"
/*
* Basic linear conversion plugin
*/
typedef struct linear_private_data {
int conv;
} linear_t;
static void convert(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
snd_pcm_uframes_t frames)
{
#define CONV_LABELS
#include "plugin_ops.h"
#undef CONV_LABELS
linear_t *data = (linear_t *)plugin->extra_data;
void *conv = conv_labels[data->conv];
int channel;
int nchannels = plugin->src_format.channels;
for (channel = 0; channel < nchannels; ++channel) {
char *src;
char *dst;
int src_step, dst_step;
snd_pcm_uframes_t frames1;
if (!src_channels[channel].enabled) {
if (dst_channels[channel].wanted)
snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
dst_channels[channel].enabled = 0;
continue;
}
dst_channels[channel].enabled = 1;
src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
src_step = src_channels[channel].area.step / 8;
dst_step = dst_channels[channel].area.step / 8;
frames1 = frames;
while (frames1-- > 0) {
goto *conv;
#define CONV_END after
#include "plugin_ops.h"
#undef CONV_END
after:
src += src_step;
dst += dst_step;
}
}
}
static snd_pcm_sframes_t linear_transfer(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
snd_pcm_uframes_t frames)
{
linear_t *data;
snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
data = (linear_t *)plugin->extra_data;
if (frames == 0)
return 0;
#ifdef CONFIG_SND_DEBUG
{
unsigned int channel;
for (channel = 0; channel < plugin->src_format.channels; channel++) {
snd_assert(src_channels[channel].area.first % 8 == 0 &&
src_channels[channel].area.step % 8 == 0,
return -ENXIO);
snd_assert(dst_channels[channel].area.first % 8 == 0 &&
dst_channels[channel].area.step % 8 == 0,
return -ENXIO);
}
}
#endif
convert(plugin, src_channels, dst_channels, frames);
return frames;
}
int conv_index(int src_format, int dst_format)
{
int src_endian, dst_endian, sign, src_width, dst_width;
sign = (snd_pcm_format_signed(src_format) !=
snd_pcm_format_signed(dst_format));
#ifdef SNDRV_LITTLE_ENDIAN
src_endian = snd_pcm_format_big_endian(src_format);
dst_endian = snd_pcm_format_big_endian(dst_format);
#else
src_endian = snd_pcm_format_little_endian(src_format);
dst_endian = snd_pcm_format_little_endian(dst_format);
#endif
if (src_endian < 0)
src_endian = 0;
if (dst_endian < 0)
dst_endian = 0;
src_width = snd_pcm_format_width(src_format) / 8 - 1;
dst_width = snd_pcm_format_width(dst_format) / 8 - 1;
return src_width * 32 + src_endian * 16 + sign * 8 + dst_width * 2 + dst_endian;
}
int snd_pcm_plugin_build_linear(snd_pcm_plug_t *plug,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
snd_pcm_plugin_t **r_plugin)
{
int err;
struct linear_private_data *data;
snd_pcm_plugin_t *plugin;
snd_assert(r_plugin != NULL, return -ENXIO);
*r_plugin = NULL;
snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
snd_assert(snd_pcm_format_linear(src_format->format) &&
snd_pcm_format_linear(dst_format->format), return -ENXIO);
err = snd_pcm_plugin_build(plug, "linear format conversion",
src_format, dst_format,
sizeof(linear_t), &plugin);
if (err < 0)
return err;
data = (linear_t *)plugin->extra_data;
data->conv = conv_index(src_format->format, dst_format->format);
plugin->transfer = linear_transfer;
*r_plugin = plugin;
return 0;
}

1340
sound/core/oss/mixer_oss.c Normal file

File diff suppressed because it is too large Load Diff

308
sound/core/oss/mulaw.c Normal file
View File

@@ -0,0 +1,308 @@
/*
* Mu-Law conversion Plug-In Interface
* Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
* Uros Bizjak <uros@kss-loka.si>
*
* Based on reference implementation by Sun Microsystems, Inc.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "pcm_plugin.h"
#define SIGN_BIT (0x80) /* Sign bit for a u-law byte. */
#define QUANT_MASK (0xf) /* Quantization field mask. */
#define NSEGS (8) /* Number of u-law segments. */
#define SEG_SHIFT (4) /* Left shift for segment number. */
#define SEG_MASK (0x70) /* Segment field mask. */
static inline int val_seg(int val)
{
int r = 0;
val >>= 7;
if (val & 0xf0) {
val >>= 4;
r += 4;
}
if (val & 0x0c) {
val >>= 2;
r += 2;
}
if (val & 0x02)
r += 1;
return r;
}
#define BIAS (0x84) /* Bias for linear code. */
/*
* linear2ulaw() - Convert a linear PCM value to u-law
*
* In order to simplify the encoding process, the original linear magnitude
* is biased by adding 33 which shifts the encoding range from (0 - 8158) to
* (33 - 8191). The result can be seen in the following encoding table:
*
* Biased Linear Input Code Compressed Code
* ------------------------ ---------------
* 00000001wxyza 000wxyz
* 0000001wxyzab 001wxyz
* 000001wxyzabc 010wxyz
* 00001wxyzabcd 011wxyz
* 0001wxyzabcde 100wxyz
* 001wxyzabcdef 101wxyz
* 01wxyzabcdefg 110wxyz
* 1wxyzabcdefgh 111wxyz
*
* Each biased linear code has a leading 1 which identifies the segment
* number. The value of the segment number is equal to 7 minus the number
* of leading 0's. The quantization interval is directly available as the
* four bits wxyz. * The trailing bits (a - h) are ignored.
*
* Ordinarily the complement of the resulting code word is used for
* transmission, and so the code word is complemented before it is returned.
*
* For further information see John C. Bellamy's Digital Telephony, 1982,
* John Wiley & Sons, pps 98-111 and 472-476.
*/
static unsigned char linear2ulaw(int pcm_val) /* 2's complement (16-bit range) */
{
int mask;
int seg;
unsigned char uval;
/* Get the sign and the magnitude of the value. */
if (pcm_val < 0) {
pcm_val = BIAS - pcm_val;
mask = 0x7F;
} else {
pcm_val += BIAS;
mask = 0xFF;
}
if (pcm_val > 0x7FFF)
pcm_val = 0x7FFF;
/* Convert the scaled magnitude to segment number. */
seg = val_seg(pcm_val);
/*
* Combine the sign, segment, quantization bits;
* and complement the code word.
*/
uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF);
return uval ^ mask;
}
/*
* ulaw2linear() - Convert a u-law value to 16-bit linear PCM
*
* First, a biased linear code is derived from the code word. An unbiased
* output can then be obtained by subtracting 33 from the biased code.
*
* Note that this function expects to be passed the complement of the
* original code word. This is in keeping with ISDN conventions.
*/
static int ulaw2linear(unsigned char u_val)
{
int t;
/* Complement to obtain normal u-law value. */
u_val = ~u_val;
/*
* Extract and bias the quantization bits. Then
* shift up by the segment number and subtract out the bias.
*/
t = ((u_val & QUANT_MASK) << 3) + BIAS;
t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
}
/*
* Basic Mu-Law plugin
*/
typedef void (*mulaw_f)(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
snd_pcm_uframes_t frames);
typedef struct mulaw_private_data {
mulaw_f func;
int conv;
} mulaw_t;
static void mulaw_decode(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
snd_pcm_uframes_t frames)
{
#define PUT_S16_LABELS
#include "plugin_ops.h"
#undef PUT_S16_LABELS
mulaw_t *data = (mulaw_t *)plugin->extra_data;
void *put = put_s16_labels[data->conv];
int channel;
int nchannels = plugin->src_format.channels;
for (channel = 0; channel < nchannels; ++channel) {
char *src;
char *dst;
int src_step, dst_step;
snd_pcm_uframes_t frames1;
if (!src_channels[channel].enabled) {
if (dst_channels[channel].wanted)
snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
dst_channels[channel].enabled = 0;
continue;
}
dst_channels[channel].enabled = 1;
src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
src_step = src_channels[channel].area.step / 8;
dst_step = dst_channels[channel].area.step / 8;
frames1 = frames;
while (frames1-- > 0) {
signed short sample = ulaw2linear(*src);
goto *put;
#define PUT_S16_END after
#include "plugin_ops.h"
#undef PUT_S16_END
after:
src += src_step;
dst += dst_step;
}
}
}
static void mulaw_encode(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
snd_pcm_uframes_t frames)
{
#define GET_S16_LABELS
#include "plugin_ops.h"
#undef GET_S16_LABELS
mulaw_t *data = (mulaw_t *)plugin->extra_data;
void *get = get_s16_labels[data->conv];
int channel;
int nchannels = plugin->src_format.channels;
signed short sample = 0;
for (channel = 0; channel < nchannels; ++channel) {
char *src;
char *dst;
int src_step, dst_step;
snd_pcm_uframes_t frames1;
if (!src_channels[channel].enabled) {
if (dst_channels[channel].wanted)
snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
dst_channels[channel].enabled = 0;
continue;
}
dst_channels[channel].enabled = 1;
src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
src_step = src_channels[channel].area.step / 8;
dst_step = dst_channels[channel].area.step / 8;
frames1 = frames;
while (frames1-- > 0) {
goto *get;
#define GET_S16_END after
#include "plugin_ops.h"
#undef GET_S16_END
after:
*dst = linear2ulaw(sample);
src += src_step;
dst += dst_step;
}
}
}
static snd_pcm_sframes_t mulaw_transfer(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
snd_pcm_uframes_t frames)
{
mulaw_t *data;
snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
if (frames == 0)
return 0;
#ifdef CONFIG_SND_DEBUG
{
unsigned int channel;
for (channel = 0; channel < plugin->src_format.channels; channel++) {
snd_assert(src_channels[channel].area.first % 8 == 0 &&
src_channels[channel].area.step % 8 == 0,
return -ENXIO);
snd_assert(dst_channels[channel].area.first % 8 == 0 &&
dst_channels[channel].area.step % 8 == 0,
return -ENXIO);
}
}
#endif
data = (mulaw_t *)plugin->extra_data;
data->func(plugin, src_channels, dst_channels, frames);
return frames;
}
int snd_pcm_plugin_build_mulaw(snd_pcm_plug_t *plug,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
snd_pcm_plugin_t **r_plugin)
{
int err;
mulaw_t *data;
snd_pcm_plugin_t *plugin;
snd_pcm_plugin_format_t *format;
mulaw_f func;
snd_assert(r_plugin != NULL, return -ENXIO);
*r_plugin = NULL;
snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
if (dst_format->format == SNDRV_PCM_FORMAT_MU_LAW) {
format = src_format;
func = mulaw_encode;
}
else if (src_format->format == SNDRV_PCM_FORMAT_MU_LAW) {
format = dst_format;
func = mulaw_decode;
}
else {
snd_BUG();
return -EINVAL;
}
snd_assert(snd_pcm_format_linear(format->format) != 0, return -ENXIO);
err = snd_pcm_plugin_build(plug, "Mu-Law<->linear conversion",
src_format, dst_format,
sizeof(mulaw_t), &plugin);
if (err < 0)
return err;
data = (mulaw_t*)plugin->extra_data;
data->func = func;
data->conv = getput_index(format->format);
snd_assert(data->conv >= 0 && data->conv < 4*2*2, return -EINVAL);
plugin->transfer = mulaw_transfer;
*r_plugin = plugin;
return 0;
}

2530
sound/core/oss/pcm_oss.c Normal file

File diff suppressed because it is too large Load Diff

921
sound/core/oss/pcm_plugin.c Normal file
View File

@@ -0,0 +1,921 @@
/*
* PCM Plug-In shared (kernel/library) code
* Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#if 0
#define PLUGIN_DEBUG
#endif
#include <sound/driver.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/vmalloc.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "pcm_plugin.h"
#define snd_pcm_plug_first(plug) ((plug)->runtime->oss.plugin_first)
#define snd_pcm_plug_last(plug) ((plug)->runtime->oss.plugin_last)
static int snd_pcm_plugin_src_channels_mask(snd_pcm_plugin_t *plugin,
bitset_t *dst_vmask,
bitset_t **src_vmask)
{
bitset_t *vmask = plugin->src_vmask;
bitset_copy(vmask, dst_vmask, plugin->src_format.channels);
*src_vmask = vmask;
return 0;
}
static int snd_pcm_plugin_dst_channels_mask(snd_pcm_plugin_t *plugin,
bitset_t *src_vmask,
bitset_t **dst_vmask)
{
bitset_t *vmask = plugin->dst_vmask;
bitset_copy(vmask, src_vmask, plugin->dst_format.channels);
*dst_vmask = vmask;
return 0;
}
/*
* because some cards might have rates "very close", we ignore
* all "resampling" requests within +-5%
*/
static int rate_match(unsigned int src_rate, unsigned int dst_rate)
{
unsigned int low = (src_rate * 95) / 100;
unsigned int high = (src_rate * 105) / 100;
return dst_rate >= low && dst_rate <= high;
}
static int snd_pcm_plugin_alloc(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames)
{
snd_pcm_plugin_format_t *format;
ssize_t width;
size_t size;
unsigned int channel;
snd_pcm_plugin_channel_t *c;
if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) {
format = &plugin->src_format;
} else {
format = &plugin->dst_format;
}
if ((width = snd_pcm_format_physical_width(format->format)) < 0)
return width;
size = frames * format->channels * width;
snd_assert((size % 8) == 0, return -ENXIO);
size /= 8;
if (plugin->buf_frames < frames) {
vfree(plugin->buf);
plugin->buf = vmalloc(size);
plugin->buf_frames = frames;
}
if (!plugin->buf) {
plugin->buf_frames = 0;
return -ENOMEM;
}
c = plugin->buf_channels;
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
for (channel = 0; channel < format->channels; channel++, c++) {
c->frames = frames;
c->enabled = 1;
c->wanted = 0;
c->area.addr = plugin->buf;
c->area.first = channel * width;
c->area.step = format->channels * width;
}
} else if (plugin->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) {
snd_assert((size % format->channels) == 0,);
size /= format->channels;
for (channel = 0; channel < format->channels; channel++, c++) {
c->frames = frames;
c->enabled = 1;
c->wanted = 0;
c->area.addr = plugin->buf + (channel * size);
c->area.first = 0;
c->area.step = width;
}
} else
return -EINVAL;
return 0;
}
int snd_pcm_plug_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t frames)
{
int err;
snd_assert(snd_pcm_plug_first(plug) != NULL, return -ENXIO);
if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
snd_pcm_plugin_t *plugin = snd_pcm_plug_first(plug);
while (plugin->next) {
if (plugin->dst_frames)
frames = plugin->dst_frames(plugin, frames);
snd_assert(frames > 0, return -ENXIO);
plugin = plugin->next;
err = snd_pcm_plugin_alloc(plugin, frames);
if (err < 0)
return err;
}
} else {
snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug);
while (plugin->prev) {
if (plugin->src_frames)
frames = plugin->src_frames(plugin, frames);
snd_assert(frames > 0, return -ENXIO);
plugin = plugin->prev;
err = snd_pcm_plugin_alloc(plugin, frames);
if (err < 0)
return err;
}
}
return 0;
}
snd_pcm_sframes_t snd_pcm_plugin_client_channels(snd_pcm_plugin_t *plugin,
snd_pcm_uframes_t frames,
snd_pcm_plugin_channel_t **channels)
{
*channels = plugin->buf_channels;
return frames;
}
int snd_pcm_plugin_build(snd_pcm_plug_t *plug,
const char *name,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
size_t extra,
snd_pcm_plugin_t **ret)
{
snd_pcm_plugin_t *plugin;
unsigned int channels;
snd_assert(plug != NULL, return -ENXIO);
snd_assert(src_format != NULL && dst_format != NULL, return -ENXIO);
plugin = kcalloc(1, sizeof(*plugin) + extra, GFP_KERNEL);
if (plugin == NULL)
return -ENOMEM;
plugin->name = name;
plugin->plug = plug;
plugin->stream = snd_pcm_plug_stream(plug);
plugin->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
plugin->src_format = *src_format;
plugin->src_width = snd_pcm_format_physical_width(src_format->format);
snd_assert(plugin->src_width > 0, );
plugin->dst_format = *dst_format;
plugin->dst_width = snd_pcm_format_physical_width(dst_format->format);
snd_assert(plugin->dst_width > 0, );
if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK)
channels = src_format->channels;
else
channels = dst_format->channels;
plugin->buf_channels = kcalloc(channels, sizeof(*plugin->buf_channels), GFP_KERNEL);
if (plugin->buf_channels == NULL) {
snd_pcm_plugin_free(plugin);
return -ENOMEM;
}
plugin->src_vmask = bitset_alloc(src_format->channels);
if (plugin->src_vmask == NULL) {
snd_pcm_plugin_free(plugin);
return -ENOMEM;
}
plugin->dst_vmask = bitset_alloc(dst_format->channels);
if (plugin->dst_vmask == NULL) {
snd_pcm_plugin_free(plugin);
return -ENOMEM;
}
plugin->client_channels = snd_pcm_plugin_client_channels;
plugin->src_channels_mask = snd_pcm_plugin_src_channels_mask;
plugin->dst_channels_mask = snd_pcm_plugin_dst_channels_mask;
*ret = plugin;
return 0;
}
int snd_pcm_plugin_free(snd_pcm_plugin_t *plugin)
{
if (! plugin)
return 0;
if (plugin->private_free)
plugin->private_free(plugin);
kfree(plugin->buf_channels);
vfree(plugin->buf);
kfree(plugin->src_vmask);
kfree(plugin->dst_vmask);
kfree(plugin);
return 0;
}
snd_pcm_sframes_t snd_pcm_plug_client_size(snd_pcm_plug_t *plug, snd_pcm_uframes_t drv_frames)
{
snd_pcm_plugin_t *plugin, *plugin_prev, *plugin_next;
int stream = snd_pcm_plug_stream(plug);
snd_assert(plug != NULL, return -ENXIO);
if (drv_frames == 0)
return 0;
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
plugin = snd_pcm_plug_last(plug);
while (plugin && drv_frames > 0) {
plugin_prev = plugin->prev;
if (plugin->src_frames)
drv_frames = plugin->src_frames(plugin, drv_frames);
plugin = plugin_prev;
}
} else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
plugin = snd_pcm_plug_first(plug);
while (plugin && drv_frames > 0) {
plugin_next = plugin->next;
if (plugin->dst_frames)
drv_frames = plugin->dst_frames(plugin, drv_frames);
plugin = plugin_next;
}
} else
snd_BUG();
return drv_frames;
}
snd_pcm_sframes_t snd_pcm_plug_slave_size(snd_pcm_plug_t *plug, snd_pcm_uframes_t clt_frames)
{
snd_pcm_plugin_t *plugin, *plugin_prev, *plugin_next;
snd_pcm_sframes_t frames;
int stream = snd_pcm_plug_stream(plug);
snd_assert(plug != NULL, return -ENXIO);
if (clt_frames == 0)
return 0;
frames = clt_frames;
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
plugin = snd_pcm_plug_first(plug);
while (plugin && frames > 0) {
plugin_next = plugin->next;
if (plugin->dst_frames) {
frames = plugin->dst_frames(plugin, frames);
if (frames < 0)
return frames;
}
plugin = plugin_next;
}
} else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
plugin = snd_pcm_plug_last(plug);
while (plugin) {
plugin_prev = plugin->prev;
if (plugin->src_frames) {
frames = plugin->src_frames(plugin, frames);
if (frames < 0)
return frames;
}
plugin = plugin_prev;
}
} else
snd_BUG();
return frames;
}
static int snd_pcm_plug_formats(snd_mask_t *mask, int format)
{
snd_mask_t formats = *mask;
u64 linfmts = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE |
SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_U24_BE | SNDRV_PCM_FMTBIT_S24_BE |
SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE |
SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE);
snd_mask_set(&formats, SNDRV_PCM_FORMAT_MU_LAW);
if (formats.bits[0] & (u32)linfmts)
formats.bits[0] |= (u32)linfmts;
if (formats.bits[1] & (u32)(linfmts >> 32))
formats.bits[1] |= (u32)(linfmts >> 32);
return snd_mask_test(&formats, format);
}
static int preferred_formats[] = {
SNDRV_PCM_FORMAT_S16_LE,
SNDRV_PCM_FORMAT_S16_BE,
SNDRV_PCM_FORMAT_U16_LE,
SNDRV_PCM_FORMAT_U16_BE,
SNDRV_PCM_FORMAT_S24_LE,
SNDRV_PCM_FORMAT_S24_BE,
SNDRV_PCM_FORMAT_U24_LE,
SNDRV_PCM_FORMAT_U24_BE,
SNDRV_PCM_FORMAT_S32_LE,
SNDRV_PCM_FORMAT_S32_BE,
SNDRV_PCM_FORMAT_U32_LE,
SNDRV_PCM_FORMAT_U32_BE,
SNDRV_PCM_FORMAT_S8,
SNDRV_PCM_FORMAT_U8
};
int snd_pcm_plug_slave_format(int format, snd_mask_t *format_mask)
{
if (snd_mask_test(format_mask, format))
return format;
if (! snd_pcm_plug_formats(format_mask, format))
return -EINVAL;
if (snd_pcm_format_linear(format)) {
int width = snd_pcm_format_width(format);
int unsignd = snd_pcm_format_unsigned(format);
int big = snd_pcm_format_big_endian(format);
int format1;
int wid, width1=width;
int dwidth1 = 8;
for (wid = 0; wid < 4; ++wid) {
int end, big1 = big;
for (end = 0; end < 2; ++end) {
int sgn, unsignd1 = unsignd;
for (sgn = 0; sgn < 2; ++sgn) {
format1 = snd_pcm_build_linear_format(width1, unsignd1, big1);
if (format1 >= 0 &&
snd_mask_test(format_mask, format1))
goto _found;
unsignd1 = !unsignd1;
}
big1 = !big1;
}
if (width1 == 32) {
dwidth1 = -dwidth1;
width1 = width;
}
width1 += dwidth1;
}
return -EINVAL;
_found:
return format1;
} else {
unsigned int i;
switch (format) {
case SNDRV_PCM_FORMAT_MU_LAW:
for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) {
int format1 = preferred_formats[i];
if (snd_mask_test(format_mask, format1))
return format1;
}
default:
return -EINVAL;
}
}
}
int snd_pcm_plug_format_plugins(snd_pcm_plug_t *plug,
snd_pcm_hw_params_t *params,
snd_pcm_hw_params_t *slave_params)
{
snd_pcm_plugin_format_t tmpformat;
snd_pcm_plugin_format_t dstformat;
snd_pcm_plugin_format_t srcformat;
int src_access, dst_access;
snd_pcm_plugin_t *plugin = NULL;
int err;
int stream = snd_pcm_plug_stream(plug);
int slave_interleaved = (params_channels(slave_params) == 1 ||
params_access(slave_params) == SNDRV_PCM_ACCESS_RW_INTERLEAVED);
switch (stream) {
case SNDRV_PCM_STREAM_PLAYBACK:
dstformat.format = params_format(slave_params);
dstformat.rate = params_rate(slave_params);
dstformat.channels = params_channels(slave_params);
srcformat.format = params_format(params);
srcformat.rate = params_rate(params);
srcformat.channels = params_channels(params);
src_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
dst_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
break;
case SNDRV_PCM_STREAM_CAPTURE:
dstformat.format = params_format(params);
dstformat.rate = params_rate(params);
dstformat.channels = params_channels(params);
srcformat.format = params_format(slave_params);
srcformat.rate = params_rate(slave_params);
srcformat.channels = params_channels(slave_params);
src_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
dst_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
break;
default:
snd_BUG();
return -EINVAL;
}
tmpformat = srcformat;
pdprintf("srcformat: format=%i, rate=%i, channels=%i\n",
srcformat.format,
srcformat.rate,
srcformat.channels);
pdprintf("dstformat: format=%i, rate=%i, channels=%i\n",
dstformat.format,
dstformat.rate,
dstformat.channels);
/* Format change (linearization) */
if ((srcformat.format != dstformat.format ||
!rate_match(srcformat.rate, dstformat.rate) ||
srcformat.channels != dstformat.channels) &&
!snd_pcm_format_linear(srcformat.format)) {
if (snd_pcm_format_linear(dstformat.format))
tmpformat.format = dstformat.format;
else
tmpformat.format = SNDRV_PCM_FORMAT_S16;
switch (srcformat.format) {
case SNDRV_PCM_FORMAT_MU_LAW:
err = snd_pcm_plugin_build_mulaw(plug,
&srcformat, &tmpformat,
&plugin);
break;
default:
return -EINVAL;
}
pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* channels reduction */
if (srcformat.channels > dstformat.channels) {
int sv = srcformat.channels;
int dv = dstformat.channels;
route_ttable_entry_t *ttable = kcalloc(dv * sv, sizeof(*ttable), GFP_KERNEL);
if (ttable == NULL)
return -ENOMEM;
#if 1
if (sv == 2 && dv == 1) {
ttable[0] = HALF;
ttable[1] = HALF;
} else
#endif
{
int v;
for (v = 0; v < dv; ++v)
ttable[v * sv + v] = FULL;
}
tmpformat.channels = dstformat.channels;
if (rate_match(srcformat.rate, dstformat.rate) &&
snd_pcm_format_linear(dstformat.format))
tmpformat.format = dstformat.format;
err = snd_pcm_plugin_build_route(plug,
&srcformat, &tmpformat,
ttable, &plugin);
kfree(ttable);
pdprintf("channels reduction: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* rate resampling */
if (!rate_match(srcformat.rate, dstformat.rate)) {
tmpformat.rate = dstformat.rate;
if (srcformat.channels == dstformat.channels &&
snd_pcm_format_linear(dstformat.format))
tmpformat.format = dstformat.format;
err = snd_pcm_plugin_build_rate(plug,
&srcformat, &tmpformat,
&plugin);
pdprintf("rate down resampling: src=%i, dst=%i returns %i\n", srcformat.rate, tmpformat.rate, err);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* channels extension */
if (srcformat.channels < dstformat.channels) {
int sv = srcformat.channels;
int dv = dstformat.channels;
route_ttable_entry_t *ttable = kcalloc(dv * sv, sizeof(*ttable), GFP_KERNEL);
if (ttable == NULL)
return -ENOMEM;
#if 0
{
int v;
for (v = 0; v < sv; ++v)
ttable[v * sv + v] = FULL;
}
#else
{
/* Playback is spreaded on all channels */
int vd, vs;
for (vd = 0, vs = 0; vd < dv; ++vd) {
ttable[vd * sv + vs] = FULL;
vs++;
if (vs == sv)
vs = 0;
}
}
#endif
tmpformat.channels = dstformat.channels;
if (snd_pcm_format_linear(dstformat.format))
tmpformat.format = dstformat.format;
err = snd_pcm_plugin_build_route(plug,
&srcformat, &tmpformat,
ttable, &plugin);
kfree(ttable);
pdprintf("channels extension: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* format change */
if (srcformat.format != dstformat.format) {
tmpformat.format = dstformat.format;
if (tmpformat.format == SNDRV_PCM_FORMAT_MU_LAW) {
err = snd_pcm_plugin_build_mulaw(plug,
&srcformat, &tmpformat,
&plugin);
}
else if (snd_pcm_format_linear(srcformat.format) &&
snd_pcm_format_linear(tmpformat.format)) {
err = snd_pcm_plugin_build_linear(plug,
&srcformat, &tmpformat,
&plugin);
}
else
return -EINVAL;
pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* de-interleave */
if (src_access != dst_access) {
err = snd_pcm_plugin_build_copy(plug,
&srcformat,
&tmpformat,
&plugin);
pdprintf("interleave change (copy: returns %i)\n", err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
}
return 0;
}
snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(snd_pcm_plug_t *plug,
char *buf,
snd_pcm_uframes_t count,
snd_pcm_plugin_channel_t **channels)
{
snd_pcm_plugin_t *plugin;
snd_pcm_plugin_channel_t *v;
snd_pcm_plugin_format_t *format;
int width, nchannels, channel;
int stream = snd_pcm_plug_stream(plug);
snd_assert(buf != NULL, return -ENXIO);
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
plugin = snd_pcm_plug_first(plug);
format = &plugin->src_format;
} else {
plugin = snd_pcm_plug_last(plug);
format = &plugin->dst_format;
}
v = plugin->buf_channels;
*channels = v;
if ((width = snd_pcm_format_physical_width(format->format)) < 0)
return width;
nchannels = format->channels;
snd_assert(plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || format->channels <= 1, return -ENXIO);
for (channel = 0; channel < nchannels; channel++, v++) {
v->frames = count;
v->enabled = 1;
v->wanted = (stream == SNDRV_PCM_STREAM_CAPTURE);
v->area.addr = buf;
v->area.first = channel * width;
v->area.step = nchannels * width;
}
return count;
}
static int snd_pcm_plug_playback_channels_mask(snd_pcm_plug_t *plug,
bitset_t *client_vmask)
{
snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug);
if (plugin == NULL) {
return 0;
} else {
int schannels = plugin->dst_format.channels;
bitset_t bs[bitset_size(schannels)];
bitset_t *srcmask;
bitset_t *dstmask = bs;
int err;
bitset_one(dstmask, schannels);
if (plugin == NULL) {
bitset_and(client_vmask, dstmask, schannels);
return 0;
}
while (1) {
err = plugin->src_channels_mask(plugin, dstmask, &srcmask);
if (err < 0)
return err;
dstmask = srcmask;
if (plugin->prev == NULL)
break;
plugin = plugin->prev;
}
bitset_and(client_vmask, dstmask, plugin->src_format.channels);
return 0;
}
}
static int snd_pcm_plug_playback_disable_useless_channels(snd_pcm_plug_t *plug,
snd_pcm_plugin_channel_t *src_channels)
{
snd_pcm_plugin_t *plugin = snd_pcm_plug_first(plug);
unsigned int nchannels = plugin->src_format.channels;
bitset_t bs[bitset_size(nchannels)];
bitset_t *srcmask = bs;
int err;
unsigned int channel;
for (channel = 0; channel < nchannels; channel++) {
if (src_channels[channel].enabled)
bitset_set(srcmask, channel);
else
bitset_reset(srcmask, channel);
}
err = snd_pcm_plug_playback_channels_mask(plug, srcmask);
if (err < 0)
return err;
for (channel = 0; channel < nchannels; channel++) {
if (!bitset_get(srcmask, channel))
src_channels[channel].enabled = 0;
}
return 0;
}
static int snd_pcm_plug_capture_disable_useless_channels(snd_pcm_plug_t *plug,
snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *client_channels)
{
snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug);
unsigned int nchannels = plugin->dst_format.channels;
bitset_t bs[bitset_size(nchannels)];
bitset_t *dstmask = bs;
bitset_t *srcmask;
int err;
unsigned int channel;
for (channel = 0; channel < nchannels; channel++) {
if (client_channels[channel].enabled)
bitset_set(dstmask, channel);
else
bitset_reset(dstmask, channel);
}
while (plugin) {
err = plugin->src_channels_mask(plugin, dstmask, &srcmask);
if (err < 0)
return err;
dstmask = srcmask;
plugin = plugin->prev;
}
plugin = snd_pcm_plug_first(plug);
nchannels = plugin->src_format.channels;
for (channel = 0; channel < nchannels; channel++) {
if (!bitset_get(dstmask, channel))
src_channels[channel].enabled = 0;
}
return 0;
}
snd_pcm_sframes_t snd_pcm_plug_write_transfer(snd_pcm_plug_t *plug, snd_pcm_plugin_channel_t *src_channels, snd_pcm_uframes_t size)
{
snd_pcm_plugin_t *plugin, *next;
snd_pcm_plugin_channel_t *dst_channels;
int err;
snd_pcm_sframes_t frames = size;
if ((err = snd_pcm_plug_playback_disable_useless_channels(plug, src_channels)) < 0)
return err;
plugin = snd_pcm_plug_first(plug);
while (plugin && frames > 0) {
if ((next = plugin->next) != NULL) {
snd_pcm_sframes_t frames1 = frames;
if (plugin->dst_frames)
frames1 = plugin->dst_frames(plugin, frames);
if ((err = next->client_channels(next, frames1, &dst_channels)) < 0) {
return err;
}
if (err != frames1) {
frames = err;
if (plugin->src_frames)
frames = plugin->src_frames(plugin, frames1);
}
} else
dst_channels = NULL;
pdprintf("write plugin: %s, %li\n", plugin->name, frames);
if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0)
return frames;
src_channels = dst_channels;
plugin = next;
}
return snd_pcm_plug_client_size(plug, frames);
}
snd_pcm_sframes_t snd_pcm_plug_read_transfer(snd_pcm_plug_t *plug, snd_pcm_plugin_channel_t *dst_channels_final, snd_pcm_uframes_t size)
{
snd_pcm_plugin_t *plugin, *next;
snd_pcm_plugin_channel_t *src_channels, *dst_channels;
snd_pcm_sframes_t frames = size;
int err;
frames = snd_pcm_plug_slave_size(plug, frames);
if (frames < 0)
return frames;
src_channels = NULL;
plugin = snd_pcm_plug_first(plug);
while (plugin && frames > 0) {
if ((next = plugin->next) != NULL) {
if ((err = plugin->client_channels(plugin, frames, &dst_channels)) < 0) {
return err;
}
frames = err;
if (!plugin->prev) {
if ((err = snd_pcm_plug_capture_disable_useless_channels(plug, dst_channels, dst_channels_final)) < 0)
return err;
}
} else {
dst_channels = dst_channels_final;
}
pdprintf("read plugin: %s, %li\n", plugin->name, frames);
if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0)
return frames;
plugin = next;
src_channels = dst_channels;
}
return frames;
}
int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_area, size_t dst_offset,
size_t samples, int format)
{
/* FIXME: sub byte resolution and odd dst_offset */
unsigned char *dst;
unsigned int dst_step;
int width;
const unsigned char *silence;
if (!dst_area->addr)
return 0;
dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
width = snd_pcm_format_physical_width(format);
if (width <= 0)
return -EINVAL;
if (dst_area->step == (unsigned int) width && width >= 8)
return snd_pcm_format_set_silence(format, dst, samples);
silence = snd_pcm_format_silence_64(format);
if (! silence)
return -EINVAL;
dst_step = dst_area->step / 8;
if (width == 4) {
/* Ima ADPCM */
int dstbit = dst_area->first % 8;
int dstbit_step = dst_area->step % 8;
while (samples-- > 0) {
if (dstbit)
*dst &= 0xf0;
else
*dst &= 0x0f;
dst += dst_step;
dstbit += dstbit_step;
if (dstbit == 8) {
dst++;
dstbit = 0;
}
}
} else {
width /= 8;
while (samples-- > 0) {
memcpy(dst, silence, width);
dst += dst_step;
}
}
return 0;
}
int snd_pcm_area_copy(const snd_pcm_channel_area_t *src_area, size_t src_offset,
const snd_pcm_channel_area_t *dst_area, size_t dst_offset,
size_t samples, int format)
{
/* FIXME: sub byte resolution and odd dst_offset */
char *src, *dst;
int width;
int src_step, dst_step;
src = src_area->addr + (src_area->first + src_area->step * src_offset) / 8;
if (!src_area->addr)
return snd_pcm_area_silence(dst_area, dst_offset, samples, format);
dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
if (!dst_area->addr)
return 0;
width = snd_pcm_format_physical_width(format);
if (width <= 0)
return -EINVAL;
if (src_area->step == (unsigned int) width &&
dst_area->step == (unsigned int) width && width >= 8) {
size_t bytes = samples * width / 8;
memcpy(dst, src, bytes);
return 0;
}
src_step = src_area->step / 8;
dst_step = dst_area->step / 8;
if (width == 4) {
/* Ima ADPCM */
int srcbit = src_area->first % 8;
int srcbit_step = src_area->step % 8;
int dstbit = dst_area->first % 8;
int dstbit_step = dst_area->step % 8;
while (samples-- > 0) {
unsigned char srcval;
if (srcbit)
srcval = *src & 0x0f;
else
srcval = (*src & 0xf0) >> 4;
if (dstbit)
*dst = (*dst & 0xf0) | srcval;
else
*dst = (*dst & 0x0f) | (srcval << 4);
src += src_step;
srcbit += srcbit_step;
if (srcbit == 8) {
src++;
srcbit = 0;
}
dst += dst_step;
dstbit += dstbit_step;
if (dstbit == 8) {
dst++;
dstbit = 0;
}
}
} else {
width /= 8;
while (samples-- > 0) {
memcpy(dst, src, width);
src += src_step;
dst += dst_step;
}
}
return 0;
}

250
sound/core/oss/pcm_plugin.h Normal file
View File

@@ -0,0 +1,250 @@
#ifndef __PCM_PLUGIN_H
#define __PCM_PLUGIN_H
/*
* Digital Audio (Plugin interface) abstract layer
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef ATTRIBUTE_UNUSED
#define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
#endif
typedef unsigned int bitset_t;
static inline size_t bitset_size(int nbits)
{
return (nbits + sizeof(bitset_t) * 8 - 1) / (sizeof(bitset_t) * 8);
}
static inline bitset_t *bitset_alloc(int nbits)
{
return kcalloc(bitset_size(nbits), sizeof(bitset_t), GFP_KERNEL);
}
static inline void bitset_set(bitset_t *bitmap, unsigned int pos)
{
size_t bits = sizeof(*bitmap) * 8;
bitmap[pos / bits] |= 1 << (pos % bits);
}
static inline void bitset_reset(bitset_t *bitmap, unsigned int pos)
{
size_t bits = sizeof(*bitmap) * 8;
bitmap[pos / bits] &= ~(1 << (pos % bits));
}
static inline int bitset_get(bitset_t *bitmap, unsigned int pos)
{
size_t bits = sizeof(*bitmap) * 8;
return !!(bitmap[pos / bits] & (1 << (pos % bits)));
}
static inline void bitset_copy(bitset_t *dst, bitset_t *src, unsigned int nbits)
{
memcpy(dst, src, bitset_size(nbits) * sizeof(bitset_t));
}
static inline void bitset_and(bitset_t *dst, bitset_t *bs, unsigned int nbits)
{
bitset_t *end = dst + bitset_size(nbits);
while (dst < end)
*dst++ &= *bs++;
}
static inline void bitset_or(bitset_t *dst, bitset_t *bs, unsigned int nbits)
{
bitset_t *end = dst + bitset_size(nbits);
while (dst < end)
*dst++ |= *bs++;
}
static inline void bitset_zero(bitset_t *dst, unsigned int nbits)
{
bitset_t *end = dst + bitset_size(nbits);
while (dst < end)
*dst++ = 0;
}
static inline void bitset_one(bitset_t *dst, unsigned int nbits)
{
bitset_t *end = dst + bitset_size(nbits);
while (dst < end)
*dst++ = ~(bitset_t)0;
}
#define snd_pcm_plug_t snd_pcm_substream_t
#define snd_pcm_plug_stream(plug) ((plug)->stream)
typedef enum {
INIT = 0,
PREPARE = 1,
} snd_pcm_plugin_action_t;
typedef struct _snd_pcm_channel_area {
void *addr; /* base address of channel samples */
unsigned int first; /* offset to first sample in bits */
unsigned int step; /* samples distance in bits */
} snd_pcm_channel_area_t;
typedef struct _snd_pcm_plugin_channel {
void *aptr; /* pointer to the allocated area */
snd_pcm_channel_area_t area;
snd_pcm_uframes_t frames; /* allocated frames */
unsigned int enabled:1; /* channel need to be processed */
unsigned int wanted:1; /* channel is wanted */
} snd_pcm_plugin_channel_t;
typedef struct _snd_pcm_plugin_format {
int format;
unsigned int rate;
unsigned int channels;
} snd_pcm_plugin_format_t;
struct _snd_pcm_plugin {
const char *name; /* plug-in name */
int stream;
snd_pcm_plugin_format_t src_format; /* source format */
snd_pcm_plugin_format_t dst_format; /* destination format */
int src_width; /* sample width in bits */
int dst_width; /* sample width in bits */
int access;
snd_pcm_sframes_t (*src_frames)(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t dst_frames);
snd_pcm_sframes_t (*dst_frames)(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t src_frames);
snd_pcm_sframes_t (*client_channels)(snd_pcm_plugin_t *plugin,
snd_pcm_uframes_t frames,
snd_pcm_plugin_channel_t **channels);
int (*src_channels_mask)(snd_pcm_plugin_t *plugin,
bitset_t *dst_vmask,
bitset_t **src_vmask);
int (*dst_channels_mask)(snd_pcm_plugin_t *plugin,
bitset_t *src_vmask,
bitset_t **dst_vmask);
snd_pcm_sframes_t (*transfer)(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
snd_pcm_uframes_t frames);
int (*action)(snd_pcm_plugin_t *plugin,
snd_pcm_plugin_action_t action,
unsigned long data);
snd_pcm_plugin_t *prev;
snd_pcm_plugin_t *next;
snd_pcm_plug_t *plug;
void *private_data;
void (*private_free)(snd_pcm_plugin_t *plugin);
char *buf;
snd_pcm_uframes_t buf_frames;
snd_pcm_plugin_channel_t *buf_channels;
bitset_t *src_vmask;
bitset_t *dst_vmask;
char extra_data[0];
};
int snd_pcm_plugin_build(snd_pcm_plug_t *handle,
const char *name,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
size_t extra,
snd_pcm_plugin_t **ret);
int snd_pcm_plugin_free(snd_pcm_plugin_t *plugin);
int snd_pcm_plugin_clear(snd_pcm_plugin_t **first);
int snd_pcm_plug_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t frames);
snd_pcm_sframes_t snd_pcm_plug_client_size(snd_pcm_plug_t *handle, snd_pcm_uframes_t drv_size);
snd_pcm_sframes_t snd_pcm_plug_slave_size(snd_pcm_plug_t *handle, snd_pcm_uframes_t clt_size);
#define FULL ROUTE_PLUGIN_RESOLUTION
#define HALF ROUTE_PLUGIN_RESOLUTION / 2
typedef int route_ttable_entry_t;
int snd_pcm_plugin_build_io(snd_pcm_plug_t *handle,
snd_pcm_hw_params_t *params,
snd_pcm_plugin_t **r_plugin);
int snd_pcm_plugin_build_linear(snd_pcm_plug_t *handle,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
snd_pcm_plugin_t **r_plugin);
int snd_pcm_plugin_build_mulaw(snd_pcm_plug_t *handle,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
snd_pcm_plugin_t **r_plugin);
int snd_pcm_plugin_build_rate(snd_pcm_plug_t *handle,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
snd_pcm_plugin_t **r_plugin);
int snd_pcm_plugin_build_route(snd_pcm_plug_t *handle,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
route_ttable_entry_t *ttable,
snd_pcm_plugin_t **r_plugin);
int snd_pcm_plugin_build_copy(snd_pcm_plug_t *handle,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
snd_pcm_plugin_t **r_plugin);
int snd_pcm_plug_format_plugins(snd_pcm_plug_t *substream,
snd_pcm_hw_params_t *params,
snd_pcm_hw_params_t *slave_params);
int snd_pcm_plug_slave_format(int format, snd_mask_t *format_mask);
int snd_pcm_plugin_append(snd_pcm_plugin_t *plugin);
snd_pcm_sframes_t snd_pcm_plug_write_transfer(snd_pcm_plug_t *handle, snd_pcm_plugin_channel_t *src_channels, snd_pcm_uframes_t size);
snd_pcm_sframes_t snd_pcm_plug_read_transfer(snd_pcm_plug_t *handle, snd_pcm_plugin_channel_t *dst_channels_final, snd_pcm_uframes_t size);
snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(snd_pcm_plug_t *handle,
char *buf, snd_pcm_uframes_t count,
snd_pcm_plugin_channel_t **channels);
snd_pcm_sframes_t snd_pcm_plugin_client_channels(snd_pcm_plugin_t *plugin,
snd_pcm_uframes_t frames,
snd_pcm_plugin_channel_t **channels);
int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_channel, size_t dst_offset,
size_t samples, int format);
int snd_pcm_area_copy(const snd_pcm_channel_area_t *src_channel, size_t src_offset,
const snd_pcm_channel_area_t *dst_channel, size_t dst_offset,
size_t samples, int format);
void *snd_pcm_plug_buf_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t size);
void snd_pcm_plug_buf_unlock(snd_pcm_plug_t *plug, void *ptr);
snd_pcm_sframes_t snd_pcm_oss_write3(snd_pcm_substream_t *substream, const char *ptr, snd_pcm_uframes_t size, int in_kernel);
snd_pcm_sframes_t snd_pcm_oss_read3(snd_pcm_substream_t *substream, char *ptr, snd_pcm_uframes_t size, int in_kernel);
snd_pcm_sframes_t snd_pcm_oss_writev3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel);
snd_pcm_sframes_t snd_pcm_oss_readv3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel);
#define ROUTE_PLUGIN_RESOLUTION 16
int getput_index(int format);
int copy_index(int format);
int conv_index(int src_format, int dst_format);
void zero_channel(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *dst_channel,
size_t samples);
#ifdef PLUGIN_DEBUG
#define pdprintf( fmt, args... ) printk( "plugin: " fmt, ##args)
#else
#define pdprintf( fmt, args... )
#endif
#endif /* __PCM_PLUGIN_H */

536
sound/core/oss/plugin_ops.h Normal file
View File

@@ -0,0 +1,536 @@
/*
* Plugin sample operators with fast switch
* Copyright (c) 2000 by Jaroslav Kysela <perex@suse.cz>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#define as_u8(ptr) (*(u_int8_t*)(ptr))
#define as_u16(ptr) (*(u_int16_t*)(ptr))
#define as_u32(ptr) (*(u_int32_t*)(ptr))
#define as_u64(ptr) (*(u_int64_t*)(ptr))
#define as_s8(ptr) (*(int8_t*)(ptr))
#define as_s16(ptr) (*(int16_t*)(ptr))
#define as_s32(ptr) (*(int32_t*)(ptr))
#define as_s64(ptr) (*(int64_t*)(ptr))
#ifdef COPY_LABELS
static void *copy_labels[4] = {
&&copy_8,
&&copy_16,
&&copy_32,
&&copy_64
};
#endif
#ifdef COPY_END
while(0) {
copy_8: as_s8(dst) = as_s8(src); goto COPY_END;
copy_16: as_s16(dst) = as_s16(src); goto COPY_END;
copy_32: as_s32(dst) = as_s32(src); goto COPY_END;
copy_64: as_s64(dst) = as_s64(src); goto COPY_END;
}
#endif
#ifdef CONV_LABELS
/* src_wid src_endswap sign_toggle dst_wid dst_endswap */
static void *conv_labels[4 * 2 * 2 * 4 * 2] = {
&&conv_xxx1_xxx1, /* 8h -> 8h */
&&conv_xxx1_xxx1, /* 8h -> 8s */
&&conv_xxx1_xx10, /* 8h -> 16h */
&&conv_xxx1_xx01, /* 8h -> 16s */
&&conv_xxx1_x100, /* 8h -> 24h */
&&conv_xxx1_001x, /* 8h -> 24s */
&&conv_xxx1_1000, /* 8h -> 32h */
&&conv_xxx1_0001, /* 8h -> 32s */
&&conv_xxx1_xxx9, /* 8h ^> 8h */
&&conv_xxx1_xxx9, /* 8h ^> 8s */
&&conv_xxx1_xx90, /* 8h ^> 16h */
&&conv_xxx1_xx09, /* 8h ^> 16s */
&&conv_xxx1_x900, /* 8h ^> 24h */
&&conv_xxx1_009x, /* 8h ^> 24s */
&&conv_xxx1_9000, /* 8h ^> 32h */
&&conv_xxx1_0009, /* 8h ^> 32s */
&&conv_xxx1_xxx1, /* 8s -> 8h */
&&conv_xxx1_xxx1, /* 8s -> 8s */
&&conv_xxx1_xx10, /* 8s -> 16h */
&&conv_xxx1_xx01, /* 8s -> 16s */
&&conv_xxx1_x100, /* 8s -> 24h */
&&conv_xxx1_001x, /* 8s -> 24s */
&&conv_xxx1_1000, /* 8s -> 32h */
&&conv_xxx1_0001, /* 8s -> 32s */
&&conv_xxx1_xxx9, /* 8s ^> 8h */
&&conv_xxx1_xxx9, /* 8s ^> 8s */
&&conv_xxx1_xx90, /* 8s ^> 16h */
&&conv_xxx1_xx09, /* 8s ^> 16s */
&&conv_xxx1_x900, /* 8s ^> 24h */
&&conv_xxx1_009x, /* 8s ^> 24s */
&&conv_xxx1_9000, /* 8s ^> 32h */
&&conv_xxx1_0009, /* 8s ^> 32s */
&&conv_xx12_xxx1, /* 16h -> 8h */
&&conv_xx12_xxx1, /* 16h -> 8s */
&&conv_xx12_xx12, /* 16h -> 16h */
&&conv_xx12_xx21, /* 16h -> 16s */
&&conv_xx12_x120, /* 16h -> 24h */
&&conv_xx12_021x, /* 16h -> 24s */
&&conv_xx12_1200, /* 16h -> 32h */
&&conv_xx12_0021, /* 16h -> 32s */
&&conv_xx12_xxx9, /* 16h ^> 8h */
&&conv_xx12_xxx9, /* 16h ^> 8s */
&&conv_xx12_xx92, /* 16h ^> 16h */
&&conv_xx12_xx29, /* 16h ^> 16s */
&&conv_xx12_x920, /* 16h ^> 24h */
&&conv_xx12_029x, /* 16h ^> 24s */
&&conv_xx12_9200, /* 16h ^> 32h */
&&conv_xx12_0029, /* 16h ^> 32s */
&&conv_xx12_xxx2, /* 16s -> 8h */
&&conv_xx12_xxx2, /* 16s -> 8s */
&&conv_xx12_xx21, /* 16s -> 16h */
&&conv_xx12_xx12, /* 16s -> 16s */
&&conv_xx12_x210, /* 16s -> 24h */
&&conv_xx12_012x, /* 16s -> 24s */
&&conv_xx12_2100, /* 16s -> 32h */
&&conv_xx12_0012, /* 16s -> 32s */
&&conv_xx12_xxxA, /* 16s ^> 8h */
&&conv_xx12_xxxA, /* 16s ^> 8s */
&&conv_xx12_xxA1, /* 16s ^> 16h */
&&conv_xx12_xx1A, /* 16s ^> 16s */
&&conv_xx12_xA10, /* 16s ^> 24h */
&&conv_xx12_01Ax, /* 16s ^> 24s */
&&conv_xx12_A100, /* 16s ^> 32h */
&&conv_xx12_001A, /* 16s ^> 32s */
&&conv_x123_xxx1, /* 24h -> 8h */
&&conv_x123_xxx1, /* 24h -> 8s */
&&conv_x123_xx12, /* 24h -> 16h */
&&conv_x123_xx21, /* 24h -> 16s */
&&conv_x123_x123, /* 24h -> 24h */
&&conv_x123_321x, /* 24h -> 24s */
&&conv_x123_1230, /* 24h -> 32h */
&&conv_x123_0321, /* 24h -> 32s */
&&conv_x123_xxx9, /* 24h ^> 8h */
&&conv_x123_xxx9, /* 24h ^> 8s */
&&conv_x123_xx92, /* 24h ^> 16h */
&&conv_x123_xx29, /* 24h ^> 16s */
&&conv_x123_x923, /* 24h ^> 24h */
&&conv_x123_329x, /* 24h ^> 24s */
&&conv_x123_9230, /* 24h ^> 32h */
&&conv_x123_0329, /* 24h ^> 32s */
&&conv_123x_xxx3, /* 24s -> 8h */
&&conv_123x_xxx3, /* 24s -> 8s */
&&conv_123x_xx32, /* 24s -> 16h */
&&conv_123x_xx23, /* 24s -> 16s */
&&conv_123x_x321, /* 24s -> 24h */
&&conv_123x_123x, /* 24s -> 24s */
&&conv_123x_3210, /* 24s -> 32h */
&&conv_123x_0123, /* 24s -> 32s */
&&conv_123x_xxxB, /* 24s ^> 8h */
&&conv_123x_xxxB, /* 24s ^> 8s */
&&conv_123x_xxB2, /* 24s ^> 16h */
&&conv_123x_xx2B, /* 24s ^> 16s */
&&conv_123x_xB21, /* 24s ^> 24h */
&&conv_123x_12Bx, /* 24s ^> 24s */
&&conv_123x_B210, /* 24s ^> 32h */
&&conv_123x_012B, /* 24s ^> 32s */
&&conv_1234_xxx1, /* 32h -> 8h */
&&conv_1234_xxx1, /* 32h -> 8s */
&&conv_1234_xx12, /* 32h -> 16h */
&&conv_1234_xx21, /* 32h -> 16s */
&&conv_1234_x123, /* 32h -> 24h */
&&conv_1234_321x, /* 32h -> 24s */
&&conv_1234_1234, /* 32h -> 32h */
&&conv_1234_4321, /* 32h -> 32s */
&&conv_1234_xxx9, /* 32h ^> 8h */
&&conv_1234_xxx9, /* 32h ^> 8s */
&&conv_1234_xx92, /* 32h ^> 16h */
&&conv_1234_xx29, /* 32h ^> 16s */
&&conv_1234_x923, /* 32h ^> 24h */
&&conv_1234_329x, /* 32h ^> 24s */
&&conv_1234_9234, /* 32h ^> 32h */
&&conv_1234_4329, /* 32h ^> 32s */
&&conv_1234_xxx4, /* 32s -> 8h */
&&conv_1234_xxx4, /* 32s -> 8s */
&&conv_1234_xx43, /* 32s -> 16h */
&&conv_1234_xx34, /* 32s -> 16s */
&&conv_1234_x432, /* 32s -> 24h */
&&conv_1234_234x, /* 32s -> 24s */
&&conv_1234_4321, /* 32s -> 32h */
&&conv_1234_1234, /* 32s -> 32s */
&&conv_1234_xxxC, /* 32s ^> 8h */
&&conv_1234_xxxC, /* 32s ^> 8s */
&&conv_1234_xxC3, /* 32s ^> 16h */
&&conv_1234_xx3C, /* 32s ^> 16s */
&&conv_1234_xC32, /* 32s ^> 24h */
&&conv_1234_23Cx, /* 32s ^> 24s */
&&conv_1234_C321, /* 32s ^> 32h */
&&conv_1234_123C, /* 32s ^> 32s */
};
#endif
#ifdef CONV_END
while(0) {
conv_xxx1_xxx1: as_u8(dst) = as_u8(src); goto CONV_END;
conv_xxx1_xx10: as_u16(dst) = (u_int16_t)as_u8(src) << 8; goto CONV_END;
conv_xxx1_xx01: as_u16(dst) = (u_int16_t)as_u8(src); goto CONV_END;
conv_xxx1_x100: as_u32(dst) = (u_int32_t)as_u8(src) << 16; goto CONV_END;
conv_xxx1_001x: as_u32(dst) = (u_int32_t)as_u8(src) << 8; goto CONV_END;
conv_xxx1_1000: as_u32(dst) = (u_int32_t)as_u8(src) << 24; goto CONV_END;
conv_xxx1_0001: as_u32(dst) = (u_int32_t)as_u8(src); goto CONV_END;
conv_xxx1_xxx9: as_u8(dst) = as_u8(src) ^ 0x80; goto CONV_END;
conv_xxx1_xx90: as_u16(dst) = (u_int16_t)(as_u8(src) ^ 0x80) << 8; goto CONV_END;
conv_xxx1_xx09: as_u16(dst) = (u_int16_t)(as_u8(src) ^ 0x80); goto CONV_END;
conv_xxx1_x900: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 16; goto CONV_END;
conv_xxx1_009x: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 8; goto CONV_END;
conv_xxx1_9000: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 24; goto CONV_END;
conv_xxx1_0009: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80); goto CONV_END;
conv_xx12_xxx1: as_u8(dst) = as_u16(src) >> 8; goto CONV_END;
conv_xx12_xx12: as_u16(dst) = as_u16(src); goto CONV_END;
conv_xx12_xx21: as_u16(dst) = swab16(as_u16(src)); goto CONV_END;
conv_xx12_x120: as_u32(dst) = (u_int32_t)as_u16(src) << 8; goto CONV_END;
conv_xx12_021x: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 8; goto CONV_END;
conv_xx12_1200: as_u32(dst) = (u_int32_t)as_u16(src) << 16; goto CONV_END;
conv_xx12_0021: as_u32(dst) = (u_int32_t)swab16(as_u16(src)); goto CONV_END;
conv_xx12_xxx9: as_u8(dst) = (as_u16(src) >> 8) ^ 0x80; goto CONV_END;
conv_xx12_xx92: as_u16(dst) = as_u16(src) ^ 0x8000; goto CONV_END;
conv_xx12_xx29: as_u16(dst) = swab16(as_u16(src)) ^ 0x80; goto CONV_END;
conv_xx12_x920: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x8000) << 8; goto CONV_END;
conv_xx12_029x: as_u32(dst) = (u_int32_t)(swab16(as_u16(src)) ^ 0x80) << 8; goto CONV_END;
conv_xx12_9200: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x8000) << 16; goto CONV_END;
conv_xx12_0029: as_u32(dst) = (u_int32_t)(swab16(as_u16(src)) ^ 0x80); goto CONV_END;
conv_xx12_xxx2: as_u8(dst) = as_u16(src) & 0xff; goto CONV_END;
conv_xx12_x210: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 8; goto CONV_END;
conv_xx12_012x: as_u32(dst) = (u_int32_t)as_u16(src) << 8; goto CONV_END;
conv_xx12_2100: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 16; goto CONV_END;
conv_xx12_0012: as_u32(dst) = (u_int32_t)as_u16(src); goto CONV_END;
conv_xx12_xxxA: as_u8(dst) = (as_u16(src) ^ 0x80) & 0xff; goto CONV_END;
conv_xx12_xxA1: as_u16(dst) = swab16(as_u16(src) ^ 0x80); goto CONV_END;
conv_xx12_xx1A: as_u16(dst) = as_u16(src) ^ 0x80; goto CONV_END;
conv_xx12_xA10: as_u32(dst) = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 8; goto CONV_END;
conv_xx12_01Ax: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x80) << 8; goto CONV_END;
conv_xx12_A100: as_u32(dst) = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 16; goto CONV_END;
conv_xx12_001A: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x80); goto CONV_END;
conv_x123_xxx1: as_u8(dst) = as_u32(src) >> 16; goto CONV_END;
conv_x123_xx12: as_u16(dst) = as_u32(src) >> 8; goto CONV_END;
conv_x123_xx21: as_u16(dst) = swab16(as_u32(src) >> 8); goto CONV_END;
conv_x123_x123: as_u32(dst) = as_u32(src); goto CONV_END;
conv_x123_321x: as_u32(dst) = swab32(as_u32(src)); goto CONV_END;
conv_x123_1230: as_u32(dst) = as_u32(src) << 8; goto CONV_END;
conv_x123_0321: as_u32(dst) = swab32(as_u32(src)) >> 8; goto CONV_END;
conv_x123_xxx9: as_u8(dst) = (as_u32(src) >> 16) ^ 0x80; goto CONV_END;
conv_x123_xx92: as_u16(dst) = (as_u32(src) >> 8) ^ 0x8000; goto CONV_END;
conv_x123_xx29: as_u16(dst) = swab16(as_u32(src) >> 8) ^ 0x80; goto CONV_END;
conv_x123_x923: as_u32(dst) = as_u32(src) ^ 0x800000; goto CONV_END;
conv_x123_329x: as_u32(dst) = swab32(as_u32(src)) ^ 0x8000; goto CONV_END;
conv_x123_9230: as_u32(dst) = (as_u32(src) ^ 0x800000) << 8; goto CONV_END;
conv_x123_0329: as_u32(dst) = (swab32(as_u32(src)) >> 8) ^ 0x80; goto CONV_END;
conv_123x_xxx3: as_u8(dst) = (as_u32(src) >> 8) & 0xff; goto CONV_END;
conv_123x_xx32: as_u16(dst) = swab16(as_u32(src) >> 8); goto CONV_END;
conv_123x_xx23: as_u16(dst) = (as_u32(src) >> 8) & 0xffff; goto CONV_END;
conv_123x_x321: as_u32(dst) = swab32(as_u32(src)); goto CONV_END;
conv_123x_123x: as_u32(dst) = as_u32(src); goto CONV_END;
conv_123x_3210: as_u32(dst) = swab32(as_u32(src)) << 8; goto CONV_END;
conv_123x_0123: as_u32(dst) = as_u32(src) >> 8; goto CONV_END;
conv_123x_xxxB: as_u8(dst) = ((as_u32(src) >> 8) & 0xff) ^ 0x80; goto CONV_END;
conv_123x_xxB2: as_u16(dst) = swab16((as_u32(src) >> 8) ^ 0x80); goto CONV_END;
conv_123x_xx2B: as_u16(dst) = ((as_u32(src) >> 8) & 0xffff) ^ 0x80; goto CONV_END;
conv_123x_xB21: as_u32(dst) = swab32(as_u32(src)) ^ 0x800000; goto CONV_END;
conv_123x_12Bx: as_u32(dst) = as_u32(src) ^ 0x8000; goto CONV_END;
conv_123x_B210: as_u32(dst) = swab32(as_u32(src) ^ 0x8000) << 8; goto CONV_END;
conv_123x_012B: as_u32(dst) = (as_u32(src) >> 8) ^ 0x80; goto CONV_END;
conv_1234_xxx1: as_u8(dst) = as_u32(src) >> 24; goto CONV_END;
conv_1234_xx12: as_u16(dst) = as_u32(src) >> 16; goto CONV_END;
conv_1234_xx21: as_u16(dst) = swab16(as_u32(src) >> 16); goto CONV_END;
conv_1234_x123: as_u32(dst) = as_u32(src) >> 8; goto CONV_END;
conv_1234_321x: as_u32(dst) = swab32(as_u32(src)) << 8; goto CONV_END;
conv_1234_1234: as_u32(dst) = as_u32(src); goto CONV_END;
conv_1234_4321: as_u32(dst) = swab32(as_u32(src)); goto CONV_END;
conv_1234_xxx9: as_u8(dst) = (as_u32(src) >> 24) ^ 0x80; goto CONV_END;
conv_1234_xx92: as_u16(dst) = (as_u32(src) >> 16) ^ 0x8000; goto CONV_END;
conv_1234_xx29: as_u16(dst) = swab16(as_u32(src) >> 16) ^ 0x80; goto CONV_END;
conv_1234_x923: as_u32(dst) = (as_u32(src) >> 8) ^ 0x800000; goto CONV_END;
conv_1234_329x: as_u32(dst) = (swab32(as_u32(src)) ^ 0x80) << 8; goto CONV_END;
conv_1234_9234: as_u32(dst) = as_u32(src) ^ 0x80000000; goto CONV_END;
conv_1234_4329: as_u32(dst) = swab32(as_u32(src)) ^ 0x80; goto CONV_END;
conv_1234_xxx4: as_u8(dst) = as_u32(src) & 0xff; goto CONV_END;
conv_1234_xx43: as_u16(dst) = swab16(as_u32(src)); goto CONV_END;
conv_1234_xx34: as_u16(dst) = as_u32(src) & 0xffff; goto CONV_END;
conv_1234_x432: as_u32(dst) = swab32(as_u32(src)) >> 8; goto CONV_END;
conv_1234_234x: as_u32(dst) = as_u32(src) << 8; goto CONV_END;
conv_1234_xxxC: as_u8(dst) = (as_u32(src) & 0xff) ^ 0x80; goto CONV_END;
conv_1234_xxC3: as_u16(dst) = swab16(as_u32(src) ^ 0x80); goto CONV_END;
conv_1234_xx3C: as_u16(dst) = (as_u32(src) & 0xffff) ^ 0x80; goto CONV_END;
conv_1234_xC32: as_u32(dst) = (swab32(as_u32(src)) >> 8) ^ 0x800000; goto CONV_END;
conv_1234_23Cx: as_u32(dst) = (as_u32(src) ^ 0x80) << 8; goto CONV_END;
conv_1234_C321: as_u32(dst) = swab32(as_u32(src) ^ 0x80); goto CONV_END;
conv_1234_123C: as_u32(dst) = as_u32(src) ^ 0x80; goto CONV_END;
}
#endif
#ifdef GET_S16_LABELS
/* src_wid src_endswap unsigned */
static void *get_s16_labels[4 * 2 * 2] = {
&&get_s16_xxx1_xx10, /* 8h -> 16h */
&&get_s16_xxx1_xx90, /* 8h ^> 16h */
&&get_s16_xxx1_xx10, /* 8s -> 16h */
&&get_s16_xxx1_xx90, /* 8s ^> 16h */
&&get_s16_xx12_xx12, /* 16h -> 16h */
&&get_s16_xx12_xx92, /* 16h ^> 16h */
&&get_s16_xx12_xx21, /* 16s -> 16h */
&&get_s16_xx12_xxA1, /* 16s ^> 16h */
&&get_s16_x123_xx12, /* 24h -> 16h */
&&get_s16_x123_xx92, /* 24h ^> 16h */
&&get_s16_123x_xx32, /* 24s -> 16h */
&&get_s16_123x_xxB2, /* 24s ^> 16h */
&&get_s16_1234_xx12, /* 32h -> 16h */
&&get_s16_1234_xx92, /* 32h ^> 16h */
&&get_s16_1234_xx43, /* 32s -> 16h */
&&get_s16_1234_xxC3, /* 32s ^> 16h */
};
#endif
#ifdef GET_S16_END
while(0) {
get_s16_xxx1_xx10: sample = (u_int16_t)as_u8(src) << 8; goto GET_S16_END;
get_s16_xxx1_xx90: sample = (u_int16_t)(as_u8(src) ^ 0x80) << 8; goto GET_S16_END;
get_s16_xx12_xx12: sample = as_u16(src); goto GET_S16_END;
get_s16_xx12_xx92: sample = as_u16(src) ^ 0x8000; goto GET_S16_END;
get_s16_xx12_xx21: sample = swab16(as_u16(src)); goto GET_S16_END;
get_s16_xx12_xxA1: sample = swab16(as_u16(src) ^ 0x80); goto GET_S16_END;
get_s16_x123_xx12: sample = as_u32(src) >> 8; goto GET_S16_END;
get_s16_x123_xx92: sample = (as_u32(src) >> 8) ^ 0x8000; goto GET_S16_END;
get_s16_123x_xx32: sample = swab16(as_u32(src) >> 8); goto GET_S16_END;
get_s16_123x_xxB2: sample = swab16((as_u32(src) >> 8) ^ 0x8000); goto GET_S16_END;
get_s16_1234_xx12: sample = as_u32(src) >> 16; goto GET_S16_END;
get_s16_1234_xx92: sample = (as_u32(src) >> 16) ^ 0x8000; goto GET_S16_END;
get_s16_1234_xx43: sample = swab16(as_u32(src)); goto GET_S16_END;
get_s16_1234_xxC3: sample = swab16(as_u32(src) ^ 0x80); goto GET_S16_END;
}
#endif
#ifdef PUT_S16_LABELS
/* dst_wid dst_endswap unsigned */
static void *put_s16_labels[4 * 2 * 2] = {
&&put_s16_xx12_xxx1, /* 16h -> 8h */
&&put_s16_xx12_xxx9, /* 16h ^> 8h */
&&put_s16_xx12_xxx1, /* 16h -> 8s */
&&put_s16_xx12_xxx9, /* 16h ^> 8s */
&&put_s16_xx12_xx12, /* 16h -> 16h */
&&put_s16_xx12_xx92, /* 16h ^> 16h */
&&put_s16_xx12_xx21, /* 16h -> 16s */
&&put_s16_xx12_xx29, /* 16h ^> 16s */
&&put_s16_xx12_x120, /* 16h -> 24h */
&&put_s16_xx12_x920, /* 16h ^> 24h */
&&put_s16_xx12_021x, /* 16h -> 24s */
&&put_s16_xx12_029x, /* 16h ^> 24s */
&&put_s16_xx12_1200, /* 16h -> 32h */
&&put_s16_xx12_9200, /* 16h ^> 32h */
&&put_s16_xx12_0021, /* 16h -> 32s */
&&put_s16_xx12_0029, /* 16h ^> 32s */
};
#endif
#ifdef PUT_S16_END
while (0) {
put_s16_xx12_xxx1: as_u8(dst) = sample >> 8; goto PUT_S16_END;
put_s16_xx12_xxx9: as_u8(dst) = (sample >> 8) ^ 0x80; goto PUT_S16_END;
put_s16_xx12_xx12: as_u16(dst) = sample; goto PUT_S16_END;
put_s16_xx12_xx92: as_u16(dst) = sample ^ 0x8000; goto PUT_S16_END;
put_s16_xx12_xx21: as_u16(dst) = swab16(sample); goto PUT_S16_END;
put_s16_xx12_xx29: as_u16(dst) = swab16(sample) ^ 0x80; goto PUT_S16_END;
put_s16_xx12_x120: as_u32(dst) = (u_int32_t)sample << 8; goto PUT_S16_END;
put_s16_xx12_x920: as_u32(dst) = (u_int32_t)(sample ^ 0x8000) << 8; goto PUT_S16_END;
put_s16_xx12_021x: as_u32(dst) = (u_int32_t)swab16(sample) << 8; goto PUT_S16_END;
put_s16_xx12_029x: as_u32(dst) = (u_int32_t)(swab16(sample) ^ 0x80) << 8; goto PUT_S16_END;
put_s16_xx12_1200: as_u32(dst) = (u_int32_t)sample << 16; goto PUT_S16_END;
put_s16_xx12_9200: as_u32(dst) = (u_int32_t)(sample ^ 0x8000) << 16; goto PUT_S16_END;
put_s16_xx12_0021: as_u32(dst) = (u_int32_t)swab16(sample); goto PUT_S16_END;
put_s16_xx12_0029: as_u32(dst) = (u_int32_t)swab16(sample) ^ 0x80; goto PUT_S16_END;
}
#endif
#if 0
#ifdef GET32_LABELS
/* src_wid src_endswap unsigned */
static void *get32_labels[4 * 2 * 2] = {
&&get32_xxx1_1000, /* 8h -> 32h */
&&get32_xxx1_9000, /* 8h ^> 32h */
&&get32_xxx1_1000, /* 8s -> 32h */
&&get32_xxx1_9000, /* 8s ^> 32h */
&&get32_xx12_1200, /* 16h -> 32h */
&&get32_xx12_9200, /* 16h ^> 32h */
&&get32_xx12_2100, /* 16s -> 32h */
&&get32_xx12_A100, /* 16s ^> 32h */
&&get32_x123_1230, /* 24h -> 32h */
&&get32_x123_9230, /* 24h ^> 32h */
&&get32_123x_3210, /* 24s -> 32h */
&&get32_123x_B210, /* 24s ^> 32h */
&&get32_1234_1234, /* 32h -> 32h */
&&get32_1234_9234, /* 32h ^> 32h */
&&get32_1234_4321, /* 32s -> 32h */
&&get32_1234_C321, /* 32s ^> 32h */
};
#endif
#ifdef GET32_END
while (0) {
get32_xxx1_1000: sample = (u_int32_t)as_u8(src) << 24; goto GET32_END;
get32_xxx1_9000: sample = (u_int32_t)(as_u8(src) ^ 0x80) << 24; goto GET32_END;
get32_xx12_1200: sample = (u_int32_t)as_u16(src) << 16; goto GET32_END;
get32_xx12_9200: sample = (u_int32_t)(as_u16(src) ^ 0x8000) << 16; goto GET32_END;
get32_xx12_2100: sample = (u_int32_t)swab16(as_u16(src)) << 16; goto GET32_END;
get32_xx12_A100: sample = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 16; goto GET32_END;
get32_x123_1230: sample = as_u32(src) << 8; goto GET32_END;
get32_x123_9230: sample = (as_u32(src) << 8) ^ 0x80000000; goto GET32_END;
get32_123x_3210: sample = swab32(as_u32(src) >> 8); goto GET32_END;
get32_123x_B210: sample = swab32((as_u32(src) >> 8) ^ 0x80); goto GET32_END;
get32_1234_1234: sample = as_u32(src); goto GET32_END;
get32_1234_9234: sample = as_u32(src) ^ 0x80000000; goto GET32_END;
get32_1234_4321: sample = swab32(as_u32(src)); goto GET32_END;
get32_1234_C321: sample = swab32(as_u32(src) ^ 0x80); goto GET32_END;
}
#endif
#endif
#ifdef PUT_U32_LABELS
/* dst_wid dst_endswap unsigned */
static void *put_u32_labels[4 * 2 * 2] = {
&&put_u32_1234_xxx9, /* u32h -> s8h */
&&put_u32_1234_xxx1, /* u32h -> u8h */
&&put_u32_1234_xxx9, /* u32h -> s8s */
&&put_u32_1234_xxx1, /* u32h -> u8s */
&&put_u32_1234_xx92, /* u32h -> s16h */
&&put_u32_1234_xx12, /* u32h -> u16h */
&&put_u32_1234_xx29, /* u32h -> s16s */
&&put_u32_1234_xx21, /* u32h -> u16s */
&&put_u32_1234_x923, /* u32h -> s24h */
&&put_u32_1234_x123, /* u32h -> u24h */
&&put_u32_1234_329x, /* u32h -> s24s */
&&put_u32_1234_321x, /* u32h -> u24s */
&&put_u32_1234_9234, /* u32h -> s32h */
&&put_u32_1234_1234, /* u32h -> u32h */
&&put_u32_1234_4329, /* u32h -> s32s */
&&put_u32_1234_4321, /* u32h -> u32s */
};
#endif
#ifdef PUT_U32_END
while (0) {
put_u32_1234_xxx1: as_u8(dst) = sample >> 24; goto PUT_U32_END;
put_u32_1234_xxx9: as_u8(dst) = (sample >> 24) ^ 0x80; goto PUT_U32_END;
put_u32_1234_xx12: as_u16(dst) = sample >> 16; goto PUT_U32_END;
put_u32_1234_xx92: as_u16(dst) = (sample >> 16) ^ 0x8000; goto PUT_U32_END;
put_u32_1234_xx21: as_u16(dst) = swab16(sample >> 16); goto PUT_U32_END;
put_u32_1234_xx29: as_u16(dst) = swab16(sample >> 16) ^ 0x80; goto PUT_U32_END;
put_u32_1234_x123: as_u32(dst) = sample >> 8; goto PUT_U32_END;
put_u32_1234_x923: as_u32(dst) = (sample >> 8) ^ 0x800000; goto PUT_U32_END;
put_u32_1234_321x: as_u32(dst) = swab32(sample) << 8; goto PUT_U32_END;
put_u32_1234_329x: as_u32(dst) = (swab32(sample) ^ 0x80) << 8; goto PUT_U32_END;
put_u32_1234_1234: as_u32(dst) = sample; goto PUT_U32_END;
put_u32_1234_9234: as_u32(dst) = sample ^ 0x80000000; goto PUT_U32_END;
put_u32_1234_4321: as_u32(dst) = swab32(sample); goto PUT_U32_END;
put_u32_1234_4329: as_u32(dst) = swab32(sample) ^ 0x80; goto PUT_U32_END;
}
#endif
#ifdef GET_U_LABELS
/* width endswap unsigned*/
static void *get_u_labels[4 * 2 * 2] = {
&&get_u_s8, /* s8 -> u8 */
&&get_u_u8, /* u8 -> u8 */
&&get_u_s8, /* s8 -> u8 */
&&get_u_u8, /* u8 -> u8 */
&&get_u_s16h, /* s16h -> u16h */
&&get_u_u16h, /* u16h -> u16h */
&&get_u_s16s, /* s16s -> u16h */
&&get_u_u16s, /* u16s -> u16h */
&&get_u_s24h, /* s24h -> u32h */
&&get_u_u24h, /* u24h -> u32h */
&&get_u_s24s, /* s24s -> u32h */
&&get_u_u24s, /* u24s -> u32h */
&&get_u_s32h, /* s32h -> u32h */
&&get_u_u32h, /* u32h -> u32h */
&&get_u_s32s, /* s32s -> u32h */
&&get_u_u32s, /* u32s -> u32h */
};
#endif
#ifdef GET_U_END
while (0) {
get_u_s8: sample = as_u8(src) ^ 0x80; goto GET_U_END;
get_u_u8: sample = as_u8(src); goto GET_U_END;
get_u_s16h: sample = as_u16(src) ^ 0x8000; goto GET_U_END;
get_u_u16h: sample = as_u16(src); goto GET_U_END;
get_u_s16s: sample = swab16(as_u16(src) ^ 0x80); goto GET_U_END;
get_u_u16s: sample = swab16(as_u16(src)); goto GET_U_END;
get_u_s24h: sample = (as_u32(src) ^ 0x800000); goto GET_U_END;
get_u_u24h: sample = as_u32(src); goto GET_U_END;
get_u_s24s: sample = swab32(as_u32(src) ^ 0x800000); goto GET_U_END;
get_u_u24s: sample = swab32(as_u32(src)); goto GET_U_END;
get_u_s32h: sample = as_u32(src) ^ 0x80000000; goto GET_U_END;
get_u_u32h: sample = as_u32(src); goto GET_U_END;
get_u_s32s: sample = swab32(as_u32(src) ^ 0x80); goto GET_U_END;
get_u_u32s: sample = swab32(as_u32(src)); goto GET_U_END;
}
#endif
#if 0
#ifdef PUT_LABELS
/* width endswap unsigned */
static void *put_labels[4 * 2 * 2] = {
&&put_s8, /* s8 -> s8 */
&&put_u8, /* u8 -> s8 */
&&put_s8, /* s8 -> s8 */
&&put_u8, /* u8 -> s8 */
&&put_s16h, /* s16h -> s16h */
&&put_u16h, /* u16h -> s16h */
&&put_s16s, /* s16s -> s16h */
&&put_u16s, /* u16s -> s16h */
&&put_s24h, /* s24h -> s32h */
&&put_u24h, /* u24h -> s32h */
&&put_s24s, /* s24s -> s32h */
&&put_u24s, /* u24s -> s32h */
&&put_s32h, /* s32h -> s32h */
&&put_u32h, /* u32h -> s32h */
&&put_s32s, /* s32s -> s32h */
&&put_u32s, /* u32s -> s32h */
};
#endif
#ifdef PUT_END
put_s8: as_s8(dst) = sample; goto PUT_END;
put_u8: as_u8(dst) = sample ^ 0x80; goto PUT_END;
put_s16h: as_s16(dst) = sample; goto PUT_END;
put_u16h: as_u16(dst) = sample ^ 0x8000; goto PUT_END;
put_s16s: as_s16(dst) = swab16(sample); goto PUT_END;
put_u16s: as_u16(dst) = swab16(sample ^ 0x80); goto PUT_END;
put_s24h: as_s24(dst) = sample & 0xffffff; goto PUT_END;
put_u24h: as_u24(dst) = sample ^ 0x80000000; goto PUT_END;
put_s24s: as_s24(dst) = swab32(sample & 0xffffff); goto PUT_END;
put_u24s: as_u24(dst) = swab32(sample ^ 0x80); goto PUT_END;
put_s32h: as_s32(dst) = sample; goto PUT_END;
put_u32h: as_u32(dst) = sample ^ 0x80000000; goto PUT_END;
put_s32s: as_s32(dst) = swab32(sample); goto PUT_END;
put_u32s: as_u32(dst) = swab32(sample ^ 0x80); goto PUT_END;
#endif
#endif
#undef as_u8
#undef as_u16
#undef as_u32
#undef as_s8
#undef as_s16
#undef as_s32

378
sound/core/oss/rate.c Normal file
View File

@@ -0,0 +1,378 @@
/*
* Rate conversion Plug-In
* Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "pcm_plugin.h"
#define SHIFT 11
#define BITS (1<<SHIFT)
#define R_MASK (BITS-1)
/*
* Basic rate conversion plugin
*/
typedef struct {
signed short last_S1;
signed short last_S2;
} rate_channel_t;
typedef void (*rate_f)(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
int src_frames, int dst_frames);
typedef struct rate_private_data {
unsigned int pitch;
unsigned int pos;
rate_f func;
int get, put;
snd_pcm_sframes_t old_src_frames, old_dst_frames;
rate_channel_t channels[0];
} rate_t;
static void rate_init(snd_pcm_plugin_t *plugin)
{
unsigned int channel;
rate_t *data = (rate_t *)plugin->extra_data;
data->pos = 0;
for (channel = 0; channel < plugin->src_format.channels; channel++) {
data->channels[channel].last_S1 = 0;
data->channels[channel].last_S2 = 0;
}
}
static void resample_expand(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
int src_frames, int dst_frames)
{
unsigned int pos = 0;
signed int val;
signed short S1, S2;
char *src, *dst;
unsigned int channel;
int src_step, dst_step;
int src_frames1, dst_frames1;
rate_t *data = (rate_t *)plugin->extra_data;
rate_channel_t *rchannels = data->channels;
#define GET_S16_LABELS
#define PUT_S16_LABELS
#include "plugin_ops.h"
#undef GET_S16_LABELS
#undef PUT_S16_LABELS
void *get = get_s16_labels[data->get];
void *put = put_s16_labels[data->put];
signed short sample = 0;
for (channel = 0; channel < plugin->src_format.channels; channel++) {
pos = data->pos;
S1 = rchannels->last_S1;
S2 = rchannels->last_S2;
if (!src_channels[channel].enabled) {
if (dst_channels[channel].wanted)
snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format);
dst_channels[channel].enabled = 0;
continue;
}
dst_channels[channel].enabled = 1;
src = (char *)src_channels[channel].area.addr + src_channels[channel].area.first / 8;
dst = (char *)dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
src_step = src_channels[channel].area.step / 8;
dst_step = dst_channels[channel].area.step / 8;
src_frames1 = src_frames;
dst_frames1 = dst_frames;
while (dst_frames1-- > 0) {
if (pos & ~R_MASK) {
pos &= R_MASK;
S1 = S2;
if (src_frames1-- > 0) {
goto *get;
#define GET_S16_END after_get
#include "plugin_ops.h"
#undef GET_S16_END
after_get:
S2 = sample;
src += src_step;
}
}
val = S1 + ((S2 - S1) * (signed int)pos) / BITS;
if (val < -32768)
val = -32768;
else if (val > 32767)
val = 32767;
sample = val;
goto *put;
#define PUT_S16_END after_put
#include "plugin_ops.h"
#undef PUT_S16_END
after_put:
dst += dst_step;
pos += data->pitch;
}
rchannels->last_S1 = S1;
rchannels->last_S2 = S2;
rchannels++;
}
data->pos = pos;
}
static void resample_shrink(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
int src_frames, int dst_frames)
{
unsigned int pos = 0;
signed int val;
signed short S1, S2;
char *src, *dst;
unsigned int channel;
int src_step, dst_step;
int src_frames1, dst_frames1;
rate_t *data = (rate_t *)plugin->extra_data;
rate_channel_t *rchannels = data->channels;
#define GET_S16_LABELS
#define PUT_S16_LABELS
#include "plugin_ops.h"
#undef GET_S16_LABELS
#undef PUT_S16_LABELS
void *get = get_s16_labels[data->get];
void *put = put_s16_labels[data->put];
signed short sample = 0;
for (channel = 0; channel < plugin->src_format.channels; ++channel) {
pos = data->pos;
S1 = rchannels->last_S1;
S2 = rchannels->last_S2;
if (!src_channels[channel].enabled) {
if (dst_channels[channel].wanted)
snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format);
dst_channels[channel].enabled = 0;
continue;
}
dst_channels[channel].enabled = 1;
src = (char *)src_channels[channel].area.addr + src_channels[channel].area.first / 8;
dst = (char *)dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
src_step = src_channels[channel].area.step / 8;
dst_step = dst_channels[channel].area.step / 8;
src_frames1 = src_frames;
dst_frames1 = dst_frames;
while (dst_frames1 > 0) {
S1 = S2;
if (src_frames1-- > 0) {
goto *get;
#define GET_S16_END after_get
#include "plugin_ops.h"
#undef GET_S16_END
after_get:
S2 = sample;
src += src_step;
}
if (pos & ~R_MASK) {
pos &= R_MASK;
val = S1 + ((S2 - S1) * (signed int)pos) / BITS;
if (val < -32768)
val = -32768;
else if (val > 32767)
val = 32767;
sample = val;
goto *put;
#define PUT_S16_END after_put
#include "plugin_ops.h"
#undef PUT_S16_END
after_put:
dst += dst_step;
dst_frames1--;
}
pos += data->pitch;
}
rchannels->last_S1 = S1;
rchannels->last_S2 = S2;
rchannels++;
}
data->pos = pos;
}
static snd_pcm_sframes_t rate_src_frames(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames)
{
rate_t *data;
snd_pcm_sframes_t res;
snd_assert(plugin != NULL, return -ENXIO);
if (frames == 0)
return 0;
data = (rate_t *)plugin->extra_data;
if (plugin->src_format.rate < plugin->dst_format.rate) {
res = (((frames * data->pitch) + (BITS/2)) >> SHIFT);
} else {
res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch);
}
if (data->old_src_frames > 0) {
snd_pcm_sframes_t frames1 = frames, res1 = data->old_dst_frames;
while (data->old_src_frames < frames1) {
frames1 >>= 1;
res1 <<= 1;
}
while (data->old_src_frames > frames1) {
frames1 <<= 1;
res1 >>= 1;
}
if (data->old_src_frames == frames1)
return res1;
}
data->old_src_frames = frames;
data->old_dst_frames = res;
return res;
}
static snd_pcm_sframes_t rate_dst_frames(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames)
{
rate_t *data;
snd_pcm_sframes_t res;
snd_assert(plugin != NULL, return -ENXIO);
if (frames == 0)
return 0;
data = (rate_t *)plugin->extra_data;
if (plugin->src_format.rate < plugin->dst_format.rate) {
res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch);
} else {
res = (((frames * data->pitch) + (BITS/2)) >> SHIFT);
}
if (data->old_dst_frames > 0) {
snd_pcm_sframes_t frames1 = frames, res1 = data->old_src_frames;
while (data->old_dst_frames < frames1) {
frames1 >>= 1;
res1 <<= 1;
}
while (data->old_dst_frames > frames1) {
frames1 <<= 1;
res1 >>= 1;
}
if (data->old_dst_frames == frames1)
return res1;
}
data->old_dst_frames = frames;
data->old_src_frames = res;
return res;
}
static snd_pcm_sframes_t rate_transfer(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
snd_pcm_uframes_t frames)
{
snd_pcm_uframes_t dst_frames;
rate_t *data;
snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
if (frames == 0)
return 0;
#ifdef CONFIG_SND_DEBUG
{
unsigned int channel;
for (channel = 0; channel < plugin->src_format.channels; channel++) {
snd_assert(src_channels[channel].area.first % 8 == 0 &&
src_channels[channel].area.step % 8 == 0,
return -ENXIO);
snd_assert(dst_channels[channel].area.first % 8 == 0 &&
dst_channels[channel].area.step % 8 == 0,
return -ENXIO);
}
}
#endif
dst_frames = rate_dst_frames(plugin, frames);
if (dst_frames > dst_channels[0].frames)
dst_frames = dst_channels[0].frames;
data = (rate_t *)plugin->extra_data;
data->func(plugin, src_channels, dst_channels, frames, dst_frames);
return dst_frames;
}
static int rate_action(snd_pcm_plugin_t *plugin,
snd_pcm_plugin_action_t action,
unsigned long udata ATTRIBUTE_UNUSED)
{
snd_assert(plugin != NULL, return -ENXIO);
switch (action) {
case INIT:
case PREPARE:
rate_init(plugin);
break;
default:
break;
}
return 0; /* silenty ignore other actions */
}
int snd_pcm_plugin_build_rate(snd_pcm_plug_t *plug,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
snd_pcm_plugin_t **r_plugin)
{
int err;
rate_t *data;
snd_pcm_plugin_t *plugin;
snd_assert(r_plugin != NULL, return -ENXIO);
*r_plugin = NULL;
snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
snd_assert(src_format->channels > 0, return -ENXIO);
snd_assert(snd_pcm_format_linear(src_format->format) != 0, return -ENXIO);
snd_assert(snd_pcm_format_linear(dst_format->format) != 0, return -ENXIO);
snd_assert(src_format->rate != dst_format->rate, return -ENXIO);
err = snd_pcm_plugin_build(plug, "rate conversion",
src_format, dst_format,
sizeof(rate_t) + src_format->channels * sizeof(rate_channel_t),
&plugin);
if (err < 0)
return err;
data = (rate_t *)plugin->extra_data;
data->get = getput_index(src_format->format);
snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL);
data->put = getput_index(dst_format->format);
snd_assert(data->put >= 0 && data->put < 4*2*2, return -EINVAL);
if (src_format->rate < dst_format->rate) {
data->pitch = ((src_format->rate << SHIFT) + (dst_format->rate >> 1)) / dst_format->rate;
data->func = resample_expand;
} else {
data->pitch = ((dst_format->rate << SHIFT) + (src_format->rate >> 1)) / src_format->rate;
data->func = resample_shrink;
}
data->pos = 0;
rate_init(plugin);
data->old_src_frames = data->old_dst_frames = 0;
plugin->transfer = rate_transfer;
plugin->src_frames = rate_src_frames;
plugin->dst_frames = rate_dst_frames;
plugin->action = rate_action;
*r_plugin = plugin;
return 0;
}

519
sound/core/oss/route.c Normal file
View File

@@ -0,0 +1,519 @@
/*
* Attenuated route Plug-In
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "pcm_plugin.h"
/* The best possible hack to support missing optimization in gcc 2.7.2.3 */
#if ROUTE_PLUGIN_RESOLUTION & (ROUTE_PLUGIN_RESOLUTION - 1) != 0
#define div(a) a /= ROUTE_PLUGIN_RESOLUTION
#elif ROUTE_PLUGIN_RESOLUTION == 16
#define div(a) a >>= 4
#else
#error "Add some code here"
#endif
typedef struct ttable_dst ttable_dst_t;
typedef struct route_private_data route_t;
typedef void (*route_channel_f)(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channel,
ttable_dst_t* ttable, snd_pcm_uframes_t frames);
typedef struct {
int channel;
int as_int;
} ttable_src_t;
struct ttable_dst {
int att; /* Attenuated */
unsigned int nsrcs;
ttable_src_t* srcs;
route_channel_f func;
};
struct route_private_data {
enum {R_UINT32=0, R_UINT64=1} sum_type;
int get, put;
int conv;
int src_sample_size;
ttable_dst_t ttable[0];
};
typedef union {
u_int32_t as_uint32;
u_int64_t as_uint64;
} sum_t;
static void route_to_channel_from_zero(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels ATTRIBUTE_UNUSED,
snd_pcm_plugin_channel_t *dst_channel,
ttable_dst_t* ttable ATTRIBUTE_UNUSED, snd_pcm_uframes_t frames)
{
if (dst_channel->wanted)
snd_pcm_area_silence(&dst_channel->area, 0, frames, plugin->dst_format.format);
dst_channel->enabled = 0;
}
static void route_to_channel_from_one(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channel,
ttable_dst_t* ttable, snd_pcm_uframes_t frames)
{
#define CONV_LABELS
#include "plugin_ops.h"
#undef CONV_LABELS
route_t *data = (route_t *)plugin->extra_data;
void *conv;
const snd_pcm_plugin_channel_t *src_channel = NULL;
unsigned int srcidx;
char *src, *dst;
int src_step, dst_step;
for (srcidx = 0; srcidx < ttable->nsrcs; ++srcidx) {
src_channel = &src_channels[ttable->srcs[srcidx].channel];
if (src_channel->area.addr != NULL)
break;
}
if (srcidx == ttable->nsrcs) {
route_to_channel_from_zero(plugin, src_channels, dst_channel, ttable, frames);
return;
}
dst_channel->enabled = 1;
conv = conv_labels[data->conv];
src = src_channel->area.addr + src_channel->area.first / 8;
src_step = src_channel->area.step / 8;
dst = dst_channel->area.addr + dst_channel->area.first / 8;
dst_step = dst_channel->area.step / 8;
while (frames-- > 0) {
goto *conv;
#define CONV_END after
#include "plugin_ops.h"
#undef CONV_END
after:
src += src_step;
dst += dst_step;
}
}
static void route_to_channel(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channel,
ttable_dst_t* ttable, snd_pcm_uframes_t frames)
{
#define GET_U_LABELS
#define PUT_U32_LABELS
#include "plugin_ops.h"
#undef GET_U_LABELS
#undef PUT_U32_LABELS
static void *zero_labels[2] = { &&zero_int32, &&zero_int64 };
/* sum_type att */
static void *add_labels[2 * 2] = { &&add_int32_noatt, &&add_int32_att,
&&add_int64_noatt, &&add_int64_att,
};
/* sum_type att shift */
static void *norm_labels[2 * 2 * 4] = { NULL,
&&norm_int32_8_noatt,
&&norm_int32_16_noatt,
&&norm_int32_24_noatt,
NULL,
&&norm_int32_8_att,
&&norm_int32_16_att,
&&norm_int32_24_att,
&&norm_int64_0_noatt,
&&norm_int64_8_noatt,
&&norm_int64_16_noatt,
&&norm_int64_24_noatt,
&&norm_int64_0_att,
&&norm_int64_8_att,
&&norm_int64_16_att,
&&norm_int64_24_att,
};
route_t *data = (route_t *)plugin->extra_data;
void *zero, *get, *add, *norm, *put_u32;
int nsrcs = ttable->nsrcs;
char *dst;
int dst_step;
char *srcs[nsrcs];
int src_steps[nsrcs];
ttable_src_t src_tt[nsrcs];
u_int32_t sample = 0;
int srcidx, srcidx1 = 0;
for (srcidx = 0; srcidx < nsrcs; ++srcidx) {
const snd_pcm_plugin_channel_t *src_channel = &src_channels[ttable->srcs[srcidx].channel];
if (!src_channel->enabled)
continue;
srcs[srcidx1] = src_channel->area.addr + src_channel->area.first / 8;
src_steps[srcidx1] = src_channel->area.step / 8;
src_tt[srcidx1] = ttable->srcs[srcidx];
srcidx1++;
}
nsrcs = srcidx1;
if (nsrcs == 0) {
route_to_channel_from_zero(plugin, src_channels, dst_channel, ttable, frames);
return;
} else if (nsrcs == 1 && src_tt[0].as_int == ROUTE_PLUGIN_RESOLUTION) {
route_to_channel_from_one(plugin, src_channels, dst_channel, ttable, frames);
return;
}
dst_channel->enabled = 1;
zero = zero_labels[data->sum_type];
get = get_u_labels[data->get];
add = add_labels[data->sum_type * 2 + ttable->att];
norm = norm_labels[data->sum_type * 8 + ttable->att * 4 + 4 - data->src_sample_size];
put_u32 = put_u32_labels[data->put];
dst = dst_channel->area.addr + dst_channel->area.first / 8;
dst_step = dst_channel->area.step / 8;
while (frames-- > 0) {
ttable_src_t *ttp = src_tt;
sum_t sum;
/* Zero sum */
goto *zero;
zero_int32:
sum.as_uint32 = 0;
goto zero_end;
zero_int64:
sum.as_uint64 = 0;
goto zero_end;
zero_end:
for (srcidx = 0; srcidx < nsrcs; ++srcidx) {
char *src = srcs[srcidx];
/* Get sample */
goto *get;
#define GET_U_END after_get
#include "plugin_ops.h"
#undef GET_U_END
after_get:
/* Sum */
goto *add;
add_int32_att:
sum.as_uint32 += sample * ttp->as_int;
goto after_sum;
add_int32_noatt:
if (ttp->as_int)
sum.as_uint32 += sample;
goto after_sum;
add_int64_att:
sum.as_uint64 += (u_int64_t) sample * ttp->as_int;
goto after_sum;
add_int64_noatt:
if (ttp->as_int)
sum.as_uint64 += sample;
goto after_sum;
after_sum:
srcs[srcidx] += src_steps[srcidx];
ttp++;
}
/* Normalization */
goto *norm;
norm_int32_8_att:
sum.as_uint64 = sum.as_uint32;
norm_int64_8_att:
sum.as_uint64 <<= 8;
norm_int64_0_att:
div(sum.as_uint64);
goto norm_int;
norm_int32_16_att:
sum.as_uint64 = sum.as_uint32;
norm_int64_16_att:
sum.as_uint64 <<= 16;
div(sum.as_uint64);
goto norm_int;
norm_int32_24_att:
sum.as_uint64 = sum.as_uint32;
norm_int64_24_att:
sum.as_uint64 <<= 24;
div(sum.as_uint64);
goto norm_int;
norm_int32_8_noatt:
sum.as_uint64 = sum.as_uint32;
norm_int64_8_noatt:
sum.as_uint64 <<= 8;
goto norm_int;
norm_int32_16_noatt:
sum.as_uint64 = sum.as_uint32;
norm_int64_16_noatt:
sum.as_uint64 <<= 16;
goto norm_int;
norm_int32_24_noatt:
sum.as_uint64 = sum.as_uint32;
norm_int64_24_noatt:
sum.as_uint64 <<= 24;
goto norm_int;
norm_int64_0_noatt:
norm_int:
if (sum.as_uint64 > (u_int32_t)0xffffffff)
sample = (u_int32_t)0xffffffff;
else
sample = sum.as_uint64;
goto after_norm;
after_norm:
/* Put sample */
goto *put_u32;
#define PUT_U32_END after_put_u32
#include "plugin_ops.h"
#undef PUT_U32_END
after_put_u32:
dst += dst_step;
}
}
static int route_src_channels_mask(snd_pcm_plugin_t *plugin,
bitset_t *dst_vmask,
bitset_t **src_vmask)
{
route_t *data = (route_t *)plugin->extra_data;
int schannels = plugin->src_format.channels;
int dchannels = plugin->dst_format.channels;
bitset_t *vmask = plugin->src_vmask;
int channel;
ttable_dst_t *dp = data->ttable;
bitset_zero(vmask, schannels);
for (channel = 0; channel < dchannels; channel++, dp++) {
unsigned int src;
ttable_src_t *sp;
if (!bitset_get(dst_vmask, channel))
continue;
sp = dp->srcs;
for (src = 0; src < dp->nsrcs; src++, sp++)
bitset_set(vmask, sp->channel);
}
*src_vmask = vmask;
return 0;
}
static int route_dst_channels_mask(snd_pcm_plugin_t *plugin,
bitset_t *src_vmask,
bitset_t **dst_vmask)
{
route_t *data = (route_t *)plugin->extra_data;
int dchannels = plugin->dst_format.channels;
bitset_t *vmask = plugin->dst_vmask;
int channel;
ttable_dst_t *dp = data->ttable;
bitset_zero(vmask, dchannels);
for (channel = 0; channel < dchannels; channel++, dp++) {
unsigned int src;
ttable_src_t *sp;
sp = dp->srcs;
for (src = 0; src < dp->nsrcs; src++, sp++) {
if (bitset_get(src_vmask, sp->channel)) {
bitset_set(vmask, channel);
break;
}
}
}
*dst_vmask = vmask;
return 0;
}
static void route_free(snd_pcm_plugin_t *plugin)
{
route_t *data = (route_t *)plugin->extra_data;
unsigned int dst_channel;
for (dst_channel = 0; dst_channel < plugin->dst_format.channels; ++dst_channel) {
kfree(data->ttable[dst_channel].srcs);
}
}
static int route_load_ttable(snd_pcm_plugin_t *plugin,
const route_ttable_entry_t* src_ttable)
{
route_t *data;
unsigned int src_channel, dst_channel;
const route_ttable_entry_t *sptr;
ttable_dst_t *dptr;
if (src_ttable == NULL)
return 0;
data = (route_t *)plugin->extra_data;
dptr = data->ttable;
sptr = src_ttable;
plugin->private_free = route_free;
for (dst_channel = 0; dst_channel < plugin->dst_format.channels; ++dst_channel) {
route_ttable_entry_t t = 0;
int att = 0;
int nsrcs = 0;
ttable_src_t srcs[plugin->src_format.channels];
for (src_channel = 0; src_channel < plugin->src_format.channels; ++src_channel) {
snd_assert(*sptr >= 0 || *sptr <= FULL, return -ENXIO);
if (*sptr != 0) {
srcs[nsrcs].channel = src_channel;
srcs[nsrcs].as_int = *sptr;
if (*sptr != FULL)
att = 1;
t += *sptr;
nsrcs++;
}
sptr++;
}
dptr->att = att;
dptr->nsrcs = nsrcs;
if (nsrcs == 0)
dptr->func = route_to_channel_from_zero;
else if (nsrcs == 1 && !att)
dptr->func = route_to_channel_from_one;
else
dptr->func = route_to_channel;
if (nsrcs > 0) {
int srcidx;
dptr->srcs = kcalloc(nsrcs, sizeof(*srcs), GFP_KERNEL);
for(srcidx = 0; srcidx < nsrcs; srcidx++)
dptr->srcs[srcidx] = srcs[srcidx];
} else
dptr->srcs = NULL;
dptr++;
}
return 0;
}
static snd_pcm_sframes_t route_transfer(snd_pcm_plugin_t *plugin,
const snd_pcm_plugin_channel_t *src_channels,
snd_pcm_plugin_channel_t *dst_channels,
snd_pcm_uframes_t frames)
{
route_t *data;
int src_nchannels, dst_nchannels;
int dst_channel;
ttable_dst_t *ttp;
snd_pcm_plugin_channel_t *dvp;
snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
if (frames == 0)
return 0;
data = (route_t *)plugin->extra_data;
src_nchannels = plugin->src_format.channels;
dst_nchannels = plugin->dst_format.channels;
#ifdef CONFIG_SND_DEBUG
{
int src_channel;
for (src_channel = 0; src_channel < src_nchannels; ++src_channel) {
snd_assert(src_channels[src_channel].area.first % 8 == 0 ||
src_channels[src_channel].area.step % 8 == 0,
return -ENXIO);
}
for (dst_channel = 0; dst_channel < dst_nchannels; ++dst_channel) {
snd_assert(dst_channels[dst_channel].area.first % 8 == 0 ||
dst_channels[dst_channel].area.step % 8 == 0,
return -ENXIO);
}
}
#endif
ttp = data->ttable;
dvp = dst_channels;
for (dst_channel = 0; dst_channel < dst_nchannels; ++dst_channel) {
ttp->func(plugin, src_channels, dvp, ttp, frames);
dvp++;
ttp++;
}
return frames;
}
int getput_index(int format)
{
int sign, width, endian;
sign = !snd_pcm_format_signed(format);
width = snd_pcm_format_width(format) / 8 - 1;
if (width < 0 || width > 3) {
snd_printk(KERN_ERR "snd-pcm-oss: invalid format %d\n", format);
width = 0;
}
#ifdef SNDRV_LITTLE_ENDIAN
endian = snd_pcm_format_big_endian(format);
#else
endian = snd_pcm_format_little_endian(format);
#endif
if (endian < 0)
endian = 0;
return width * 4 + endian * 2 + sign;
}
int snd_pcm_plugin_build_route(snd_pcm_plug_t *plug,
snd_pcm_plugin_format_t *src_format,
snd_pcm_plugin_format_t *dst_format,
route_ttable_entry_t *ttable,
snd_pcm_plugin_t **r_plugin)
{
route_t *data;
snd_pcm_plugin_t *plugin;
int err;
snd_assert(r_plugin != NULL, return -ENXIO);
*r_plugin = NULL;
snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
snd_assert(snd_pcm_format_linear(src_format->format) != 0 &&
snd_pcm_format_linear(dst_format->format) != 0,
return -ENXIO);
err = snd_pcm_plugin_build(plug, "attenuated route conversion",
src_format, dst_format,
sizeof(route_t) + sizeof(data->ttable[0]) * dst_format->channels,
&plugin);
if (err < 0)
return err;
data = (route_t *) plugin->extra_data;
data->get = getput_index(src_format->format);
snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL);
data->put = getput_index(dst_format->format);
snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL);
data->conv = conv_index(src_format->format, dst_format->format);
if (snd_pcm_format_width(src_format->format) == 32)
data->sum_type = R_UINT64;
else
data->sum_type = R_UINT32;
data->src_sample_size = snd_pcm_format_width(src_format->format) / 8;
if ((err = route_load_ttable(plugin, ttable)) < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
plugin->transfer = route_transfer;
plugin->src_channels_mask = route_src_channels_mask;
plugin->dst_channels_mask = route_dst_channels_mask;
*r_plugin = plugin;
return 0;
}

1074
sound/core/pcm.c Normal file

File diff suppressed because it is too large Load Diff

513
sound/core/pcm_compat.c Normal file
View File

@@ -0,0 +1,513 @@
/*
* 32bit -> 64bit ioctl wrapper for PCM API
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* This file included from pcm_native.c */
#include <linux/compat.h>
static int snd_pcm_ioctl_delay_compat(snd_pcm_substream_t *substream,
s32 __user *src)
{
snd_pcm_sframes_t delay;
mm_segment_t fs;
int err;
fs = snd_enter_user();
err = snd_pcm_delay(substream, &delay);
snd_leave_user(fs);
if (err < 0)
return err;
if (put_user(delay, src))
return -EFAULT;
return err;
}
static int snd_pcm_ioctl_rewind_compat(snd_pcm_substream_t *substream,
u32 __user *src)
{
snd_pcm_uframes_t frames;
int err;
if (get_user(frames, src))
return -EFAULT;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
err = snd_pcm_playback_rewind(substream, frames);
else
err = snd_pcm_capture_rewind(substream, frames);
if (put_user(err, src))
return -EFAULT;
return err < 0 ? err : 0;
}
static int snd_pcm_ioctl_forward_compat(snd_pcm_substream_t *substream,
u32 __user *src)
{
snd_pcm_uframes_t frames;
int err;
if (get_user(frames, src))
return -EFAULT;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
err = snd_pcm_playback_forward(substream, frames);
else
err = snd_pcm_capture_forward(substream, frames);
if (put_user(err, src))
return -EFAULT;
return err < 0 ? err : 0;
}
struct sndrv_pcm_hw_params32 {
u32 flags;
struct sndrv_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]; /* this must be identical */
struct sndrv_mask mres[5]; /* reserved masks */
struct sndrv_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
struct sndrv_interval ires[9]; /* reserved intervals */
u32 rmask;
u32 cmask;
u32 info;
u32 msbits;
u32 rate_num;
u32 rate_den;
u32 fifo_size;
unsigned char reserved[64];
};
struct sndrv_pcm_sw_params32 {
s32 tstamp_mode;
u32 period_step;
u32 sleep_min;
u32 avail_min;
u32 xfer_align;
u32 start_threshold;
u32 stop_threshold;
u32 silence_threshold;
u32 silence_size;
u32 boundary;
unsigned char reserved[64];
};
static int snd_pcm_ioctl_sw_params_compat(snd_pcm_substream_t *substream,
struct sndrv_pcm_sw_params32 __user *src)
{
snd_pcm_sw_params_t params;
int err;
memset(&params, 0, sizeof(params));
if (get_user(params.tstamp_mode, &src->tstamp_mode) ||
get_user(params.period_step, &src->period_step) ||
get_user(params.sleep_min, &src->sleep_min) ||
get_user(params.avail_min, &src->avail_min) ||
get_user(params.xfer_align, &src->xfer_align) ||
get_user(params.start_threshold, &src->start_threshold) ||
get_user(params.stop_threshold, &src->stop_threshold) ||
get_user(params.silence_threshold, &src->silence_threshold) ||
get_user(params.silence_size, &src->silence_size))
return -EFAULT;
err = snd_pcm_sw_params(substream, &params);
if (err < 0)
return err;
if (put_user(params.boundary, &src->boundary))
return -EFAULT;
return err;
}
struct sndrv_pcm_channel_info32 {
u32 channel;
u32 offset;
u32 first;
u32 step;
};
static int snd_pcm_ioctl_channel_info_compat(snd_pcm_substream_t *substream,
struct sndrv_pcm_channel_info32 __user *src)
{
snd_pcm_channel_info_t info;
int err;
if (get_user(info.channel, &src->channel) ||
get_user(info.offset, &src->offset) ||
get_user(info.first, &src->first) ||
get_user(info.step, &src->step))
return -EFAULT;
err = snd_pcm_channel_info(substream, &info);
if (err < 0)
return err;
if (put_user(info.channel, &src->channel) ||
put_user(info.offset, &src->offset) ||
put_user(info.first, &src->first) ||
put_user(info.step, &src->step))
return -EFAULT;
return err;
}
struct sndrv_pcm_status32 {
s32 state;
struct compat_timespec trigger_tstamp;
struct compat_timespec tstamp;
u32 appl_ptr;
u32 hw_ptr;
s32 delay;
u32 avail;
u32 avail_max;
u32 overrange;
s32 suspended_state;
unsigned char reserved[60];
} __attribute__((packed));
static int snd_pcm_status_user_compat(snd_pcm_substream_t *substream,
struct sndrv_pcm_status32 __user *src)
{
snd_pcm_status_t status;
int err;
err = snd_pcm_status(substream, &status);
if (err < 0)
return err;
if (put_user(status.state, &src->state) ||
put_user(status.trigger_tstamp.tv_sec, &src->trigger_tstamp.tv_sec) ||
put_user(status.trigger_tstamp.tv_nsec, &src->trigger_tstamp.tv_nsec) ||
put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) ||
put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) ||
put_user(status.appl_ptr, &src->appl_ptr) ||
put_user(status.hw_ptr, &src->hw_ptr) ||
put_user(status.delay, &src->delay) ||
put_user(status.avail, &src->avail) ||
put_user(status.avail_max, &src->avail_max) ||
put_user(status.overrange, &src->overrange) ||
put_user(status.suspended_state, &src->suspended_state))
return -EFAULT;
return err;
}
/* recalcuate the boundary within 32bit */
static void recalculate_boundary(snd_pcm_runtime_t *runtime)
{
if (! runtime->buffer_size)
return;
runtime->boundary = runtime->buffer_size;
while (runtime->boundary * 2 <= 0x7fffffffUL - runtime->buffer_size)
runtime->boundary *= 2;
}
/* both for HW_PARAMS and HW_REFINE */
static int snd_pcm_ioctl_hw_params_compat(snd_pcm_substream_t *substream,
int refine,
struct sndrv_pcm_hw_params32 __user *data32)
{
struct sndrv_pcm_hw_params *data;
snd_pcm_runtime_t *runtime;
int err;
if (! (runtime = substream->runtime))
return -ENOTTY;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
/* only fifo_size is different, so just copy all */
if (copy_from_user(data, data32, sizeof(*data32))) {
err = -EFAULT;
goto error;
}
if (refine)
err = snd_pcm_hw_refine(substream, data);
else
err = snd_pcm_hw_params(substream, data);
if (err < 0)
goto error;
if (copy_to_user(data32, data, sizeof(*data32)) ||
put_user(data->fifo_size, &data32->fifo_size)) {
err = -EFAULT;
goto error;
}
if (! refine)
recalculate_boundary(runtime);
error:
kfree(data);
return err;
}
/*
*/
struct sndrv_xferi32 {
s32 result;
u32 buf;
u32 frames;
};
static int snd_pcm_ioctl_xferi_compat(snd_pcm_substream_t *substream,
int dir, struct sndrv_xferi32 __user *data32)
{
compat_caddr_t buf;
u32 frames;
int err;
if (! substream->runtime)
return -ENOTTY;
if (substream->stream != dir)
return -EINVAL;
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD;
if (get_user(buf, &data32->buf) ||
get_user(frames, &data32->frames))
return -EFAULT;
if (dir == SNDRV_PCM_STREAM_PLAYBACK)
err = snd_pcm_lib_write(substream, compat_ptr(buf), frames);
else
err = snd_pcm_lib_read(substream, compat_ptr(buf), frames);
if (err < 0)
return err;
/* copy the result */
if (put_user(err, &data32->result))
return -EFAULT;
return 0;
}
/* snd_xfern needs remapping of bufs */
struct sndrv_xfern32 {
s32 result;
u32 bufs; /* this is void **; */
u32 frames;
};
/*
* xfern ioctl nees to copy (up to) 128 pointers on stack.
* although we may pass the copied pointers through f_op->ioctl, but the ioctl
* handler there expands again the same 128 pointers on stack, so it is better
* to handle the function (calling pcm_readv/writev) directly in this handler.
*/
static int snd_pcm_ioctl_xfern_compat(snd_pcm_substream_t *substream,
int dir, struct sndrv_xfern32 __user *data32)
{
compat_caddr_t buf;
compat_caddr_t __user *bufptr;
u32 frames;
void __user **bufs;
int err, ch, i;
if (! substream->runtime)
return -ENOTTY;
if (substream->stream != dir)
return -EINVAL;
if ((ch = substream->runtime->channels) > 128)
return -EINVAL;
if (get_user(buf, &data32->bufs) ||
get_user(frames, &data32->frames))
return -EFAULT;
bufptr = compat_ptr(buf);
bufs = kmalloc(sizeof(void __user *) * ch, GFP_KERNEL);
if (bufs == NULL)
return -ENOMEM;
for (i = 0; i < ch; i++) {
u32 ptr;
if (get_user(ptr, bufptr)) {
kfree(bufs);
return -EFAULT;
}
bufs[ch] = compat_ptr(ptr);
bufptr++;
}
if (dir == SNDRV_PCM_STREAM_PLAYBACK)
err = snd_pcm_lib_writev(substream, bufs, frames);
else
err = snd_pcm_lib_readv(substream, bufs, frames);
if (err >= 0) {
if (put_user(err, &data32->result))
err = -EFAULT;
}
kfree(bufs);
return err;
}
struct sndrv_pcm_mmap_status32 {
s32 state;
s32 pad1;
u32 hw_ptr;
struct compat_timespec tstamp;
s32 suspended_state;
} __attribute__((packed));
struct sndrv_pcm_mmap_control32 {
u32 appl_ptr;
u32 avail_min;
};
struct sndrv_pcm_sync_ptr32 {
u32 flags;
union {
struct sndrv_pcm_mmap_status32 status;
unsigned char reserved[64];
} s;
union {
struct sndrv_pcm_mmap_control32 control;
unsigned char reserved[64];
} c;
} __attribute__((packed));
static int snd_pcm_ioctl_sync_ptr_compat(snd_pcm_substream_t *substream,
struct sndrv_pcm_sync_ptr32 __user *src)
{
snd_pcm_runtime_t *runtime = substream->runtime;
volatile struct sndrv_pcm_mmap_status *status;
volatile struct sndrv_pcm_mmap_control *control;
u32 sflags;
struct sndrv_pcm_mmap_control scontrol;
struct sndrv_pcm_mmap_status sstatus;
int err;
snd_assert(runtime, return -EINVAL);
if (get_user(sflags, &src->flags) ||
get_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
get_user(scontrol.avail_min, &src->c.control.avail_min))
return -EFAULT;
if (sflags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
err = snd_pcm_hwsync(substream);
if (err < 0)
return err;
}
status = runtime->status;
control = runtime->control;
snd_pcm_stream_lock_irq(substream);
if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL))
control->appl_ptr = scontrol.appl_ptr;
else
scontrol.appl_ptr = control->appl_ptr;
if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN))
control->avail_min = scontrol.avail_min;
else
scontrol.avail_min = control->avail_min;
sstatus.state = status->state;
sstatus.hw_ptr = status->hw_ptr;
sstatus.tstamp = status->tstamp;
sstatus.suspended_state = status->suspended_state;
snd_pcm_stream_unlock_irq(substream);
if (put_user(sstatus.state, &src->s.status.state) ||
put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp.tv_sec) ||
put_user(sstatus.tstamp.tv_nsec, &src->s.status.tstamp.tv_nsec) ||
put_user(sstatus.suspended_state, &src->s.status.suspended_state) ||
put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
put_user(scontrol.avail_min, &src->c.control.avail_min))
return -EFAULT;
return 0;
}
/*
*/
enum {
SNDRV_PCM_IOCTL_HW_REFINE32 = _IOWR('A', 0x10, struct sndrv_pcm_hw_params32),
SNDRV_PCM_IOCTL_HW_PARAMS32 = _IOWR('A', 0x11, struct sndrv_pcm_hw_params32),
SNDRV_PCM_IOCTL_SW_PARAMS32 = _IOWR('A', 0x13, struct sndrv_pcm_sw_params32),
SNDRV_PCM_IOCTL_STATUS32 = _IOR('A', 0x20, struct sndrv_pcm_status32),
SNDRV_PCM_IOCTL_DELAY32 = _IOR('A', 0x21, s32),
SNDRV_PCM_IOCTL_CHANNEL_INFO32 = _IOR('A', 0x32, struct sndrv_pcm_channel_info32),
SNDRV_PCM_IOCTL_REWIND32 = _IOW('A', 0x46, u32),
SNDRV_PCM_IOCTL_FORWARD32 = _IOW('A', 0x49, u32),
SNDRV_PCM_IOCTL_WRITEI_FRAMES32 = _IOW('A', 0x50, struct sndrv_xferi32),
SNDRV_PCM_IOCTL_READI_FRAMES32 = _IOR('A', 0x51, struct sndrv_xferi32),
SNDRV_PCM_IOCTL_WRITEN_FRAMES32 = _IOW('A', 0x52, struct sndrv_xfern32),
SNDRV_PCM_IOCTL_READN_FRAMES32 = _IOR('A', 0x53, struct sndrv_xfern32),
SNDRV_PCM_IOCTL_SYNC_PTR32 = _IOWR('A', 0x23, struct sndrv_pcm_sync_ptr32),
};
static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
{
snd_pcm_file_t *pcm_file;
snd_pcm_substream_t *substream;
void __user *argp = compat_ptr(arg);
pcm_file = file->private_data;
if (! pcm_file)
return -ENOTTY;
substream = pcm_file->substream;
if (! substream)
return -ENOTTY;
/*
* When PCM is used on 32bit mode, we need to disable
* mmap of PCM status/control records because of the size
* incompatibility.
*/
substream->no_mmap_ctrl = 1;
switch (cmd) {
case SNDRV_PCM_IOCTL_PVERSION:
case SNDRV_PCM_IOCTL_INFO:
case SNDRV_PCM_IOCTL_TSTAMP:
case SNDRV_PCM_IOCTL_HWSYNC:
case SNDRV_PCM_IOCTL_PREPARE:
case SNDRV_PCM_IOCTL_RESET:
case SNDRV_PCM_IOCTL_START:
case SNDRV_PCM_IOCTL_DROP:
case SNDRV_PCM_IOCTL_DRAIN:
case SNDRV_PCM_IOCTL_PAUSE:
case SNDRV_PCM_IOCTL_HW_FREE:
case SNDRV_PCM_IOCTL_RESUME:
case SNDRV_PCM_IOCTL_XRUN:
case SNDRV_PCM_IOCTL_LINK:
case SNDRV_PCM_IOCTL_UNLINK:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
return snd_pcm_playback_ioctl1(substream, cmd, argp);
else
return snd_pcm_capture_ioctl1(substream, cmd, argp);
case SNDRV_PCM_IOCTL_HW_REFINE32:
return snd_pcm_ioctl_hw_params_compat(substream, 1, argp);
case SNDRV_PCM_IOCTL_HW_PARAMS32:
return snd_pcm_ioctl_hw_params_compat(substream, 0, argp);
case SNDRV_PCM_IOCTL_SW_PARAMS32:
return snd_pcm_ioctl_sw_params_compat(substream, argp);
case SNDRV_PCM_IOCTL_STATUS32:
return snd_pcm_status_user_compat(substream, argp);
case SNDRV_PCM_IOCTL_SYNC_PTR32:
return snd_pcm_ioctl_sync_ptr_compat(substream, argp);
case SNDRV_PCM_IOCTL_CHANNEL_INFO32:
return snd_pcm_ioctl_channel_info_compat(substream, argp);
case SNDRV_PCM_IOCTL_WRITEI_FRAMES32:
return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp);
case SNDRV_PCM_IOCTL_READI_FRAMES32:
return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp);
case SNDRV_PCM_IOCTL_WRITEN_FRAMES32:
return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp);
case SNDRV_PCM_IOCTL_READN_FRAMES32:
return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp);
case SNDRV_PCM_IOCTL_DELAY32:
return snd_pcm_ioctl_delay_compat(substream, argp);
case SNDRV_PCM_IOCTL_REWIND32:
return snd_pcm_ioctl_rewind_compat(substream, argp);
case SNDRV_PCM_IOCTL_FORWARD32:
return snd_pcm_ioctl_forward_compat(substream, argp);
}
return -ENOIOCTLCMD;
}

2612
sound/core/pcm_lib.c Normal file

File diff suppressed because it is too large Load Diff

363
sound/core/pcm_memory.c Normal file
View File

@@ -0,0 +1,363 @@
/*
* Digital Audio (PCM) abstract layer
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <asm/io.h>
#include <linux/time.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/info.h>
#include <sound/initval.h>
static int preallocate_dma = 1;
module_param(preallocate_dma, int, 0444);
MODULE_PARM_DESC(preallocate_dma, "Preallocate DMA memory when the PCM devices are initialized.");
static int maximum_substreams = 4;
module_param(maximum_substreams, int, 0444);
MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA memory.");
static const size_t snd_minimum_buffer = 16384;
/*
* try to allocate as the large pages as possible.
* stores the resultant memory size in *res_size.
*
* the minimum size is snd_minimum_buffer. it should be power of 2.
*/
static int preallocate_pcm_pages(snd_pcm_substream_t *substream, size_t size)
{
struct snd_dma_buffer *dmab = &substream->dma_buffer;
int err;
snd_assert(size > 0, return -EINVAL);
/* already reserved? */
if (snd_dma_get_reserved_buf(dmab, substream->dma_buf_id) > 0) {
if (dmab->bytes >= size)
return 0; /* yes */
/* no, free the reserved block */
snd_dma_free_pages(dmab);
dmab->bytes = 0;
}
do {
if ((err = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev,
size, dmab)) < 0) {
if (err != -ENOMEM)
return err; /* fatal error */
} else
return 0;
size >>= 1;
} while (size >= snd_minimum_buffer);
dmab->bytes = 0; /* tell error */
return 0;
}
/*
* release the preallocated buffer if not yet done.
*/
static void snd_pcm_lib_preallocate_dma_free(snd_pcm_substream_t *substream)
{
if (substream->dma_buffer.area == NULL)
return;
if (substream->dma_buf_id)
snd_dma_reserve_buf(&substream->dma_buffer, substream->dma_buf_id);
else
snd_dma_free_pages(&substream->dma_buffer);
substream->dma_buffer.area = NULL;
}
/**
* snd_pcm_lib_preallocate_free - release the preallocated buffer of the specified substream.
* @substream: the pcm substream instance
*
* Releases the pre-allocated buffer of the given substream.
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_preallocate_free(snd_pcm_substream_t *substream)
{
snd_pcm_lib_preallocate_dma_free(substream);
if (substream->proc_prealloc_entry) {
snd_info_unregister(substream->proc_prealloc_entry);
substream->proc_prealloc_entry = NULL;
}
return 0;
}
/**
* snd_pcm_lib_preallocate_free_for_all - release all pre-allocated buffers on the pcm
* @pcm: the pcm instance
*
* Releases all the pre-allocated buffers on the given pcm.
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_preallocate_free_for_all(snd_pcm_t *pcm)
{
snd_pcm_substream_t *substream;
int stream;
for (stream = 0; stream < 2; stream++)
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
snd_pcm_lib_preallocate_free(substream);
return 0;
}
/*
* read callback for prealloc proc file
*
* prints the current allocated size in kB.
*/
static void snd_pcm_lib_preallocate_proc_read(snd_info_entry_t *entry,
snd_info_buffer_t *buffer)
{
snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_buffer.bytes / 1024);
}
/*
* write callback for prealloc proc file
*
* accepts the preallocation size in kB.
*/
static void snd_pcm_lib_preallocate_proc_write(snd_info_entry_t *entry,
snd_info_buffer_t *buffer)
{
snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
char line[64], str[64];
size_t size;
struct snd_dma_buffer new_dmab;
if (substream->runtime) {
buffer->error = -EBUSY;
return;
}
if (!snd_info_get_line(buffer, line, sizeof(line))) {
snd_info_get_str(str, line, sizeof(str));
size = simple_strtoul(str, NULL, 10) * 1024;
if ((size != 0 && size < 8192) || size > substream->dma_max) {
buffer->error = -EINVAL;
return;
}
if (substream->dma_buffer.bytes == size)
return;
memset(&new_dmab, 0, sizeof(new_dmab));
new_dmab.dev = substream->dma_buffer.dev;
if (size > 0) {
if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
substream->dma_buffer.dev.dev,
size, &new_dmab) < 0) {
buffer->error = -ENOMEM;
return;
}
substream->buffer_bytes_max = size;
} else {
substream->buffer_bytes_max = UINT_MAX;
}
if (substream->dma_buffer.area)
snd_dma_free_pages(&substream->dma_buffer);
substream->dma_buffer = new_dmab;
} else {
buffer->error = -EINVAL;
}
}
/*
* pre-allocate the buffer and create a proc file for the substream
*/
static int snd_pcm_lib_preallocate_pages1(snd_pcm_substream_t *substream,
size_t size, size_t max)
{
snd_info_entry_t *entry;
if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
preallocate_pcm_pages(substream, size);
if (substream->dma_buffer.bytes > 0)
substream->buffer_bytes_max = substream->dma_buffer.bytes;
substream->dma_max = max;
if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) {
entry->c.text.read_size = 64;
entry->c.text.read = snd_pcm_lib_preallocate_proc_read;
entry->c.text.write_size = 64;
entry->c.text.write = snd_pcm_lib_preallocate_proc_write;
entry->private_data = substream;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
substream->proc_prealloc_entry = entry;
return 0;
}
/**
* snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type
* @substream: the pcm substream instance
* @type: DMA type (SNDRV_DMA_TYPE_*)
* @data: DMA type dependant data
* @size: the requested pre-allocation size in bytes
* @max: the max. allowed pre-allocation size
*
* Do pre-allocation for the given DMA buffer type.
*
* When substream->dma_buf_id is set, the function tries to look for
* the reserved buffer, and the buffer is not freed but reserved at
* destruction time. The dma_buf_id must be unique for all systems
* (in the same DMA buffer type) e.g. using snd_dma_pci_buf_id().
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_preallocate_pages(snd_pcm_substream_t *substream,
int type, struct device *data,
size_t size, size_t max)
{
substream->dma_buffer.dev.type = type;
substream->dma_buffer.dev.dev = data;
return snd_pcm_lib_preallocate_pages1(substream, size, max);
}
/**
* snd_pcm_lib_preallocate_pages_for_all - pre-allocation for continous memory type (all substreams)
* @substream: the pcm substream instance
* @type: DMA type (SNDRV_DMA_TYPE_*)
* @data: DMA type dependant data
* @size: the requested pre-allocation size in bytes
* @max: the max. allowed pre-allocation size
*
* Do pre-allocation to all substreams of the given pcm for the
* specified DMA type.
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_preallocate_pages_for_all(snd_pcm_t *pcm,
int type, void *data,
size_t size, size_t max)
{
snd_pcm_substream_t *substream;
int stream, err;
for (stream = 0; stream < 2; stream++)
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
if ((err = snd_pcm_lib_preallocate_pages(substream, type, data, size, max)) < 0)
return err;
return 0;
}
/**
* snd_pcm_sgbuf_ops_page - get the page struct at the given offset
* @substream: the pcm substream instance
* @offset: the buffer offset
*
* Returns the page struct at the given buffer offset.
* Used as the page callback of PCM ops.
*/
struct page *snd_pcm_sgbuf_ops_page(snd_pcm_substream_t *substream, unsigned long offset)
{
struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
unsigned int idx = offset >> PAGE_SHIFT;
if (idx >= (unsigned int)sgbuf->pages)
return NULL;
return sgbuf->page_table[idx];
}
/**
* snd_pcm_lib_malloc_pages - allocate the DMA buffer
* @substream: the substream to allocate the DMA buffer to
* @size: the requested buffer size in bytes
*
* Allocates the DMA buffer on the BUS type given earlier to
* snd_pcm_lib_preallocate_xxx_pages().
*
* Returns 1 if the buffer is changed, 0 if not changed, or a negative
* code on failure.
*/
int snd_pcm_lib_malloc_pages(snd_pcm_substream_t *substream, size_t size)
{
snd_pcm_runtime_t *runtime;
struct snd_dma_buffer *dmab = NULL;
snd_assert(substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_UNKNOWN, return -EINVAL);
snd_assert(substream != NULL, return -EINVAL);
runtime = substream->runtime;
snd_assert(runtime != NULL, return -EINVAL);
if (runtime->dma_buffer_p) {
/* perphaps, we might free the large DMA memory region
to save some space here, but the actual solution
costs us less time */
if (runtime->dma_buffer_p->bytes >= size) {
runtime->dma_bytes = size;
return 0; /* ok, do not change */
}
snd_pcm_lib_free_pages(substream);
}
if (substream->dma_buffer.area != NULL && substream->dma_buffer.bytes >= size) {
dmab = &substream->dma_buffer; /* use the pre-allocated buffer */
} else {
dmab = kcalloc(1, sizeof(*dmab), GFP_KERNEL);
if (! dmab)
return -ENOMEM;
dmab->dev = substream->dma_buffer.dev;
if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
substream->dma_buffer.dev.dev,
size, dmab) < 0) {
kfree(dmab);
return -ENOMEM;
}
}
snd_pcm_set_runtime_buffer(substream, dmab);
runtime->dma_bytes = size;
return 1; /* area was changed */
}
/**
* snd_pcm_lib_free_pages - release the allocated DMA buffer.
* @substream: the substream to release the DMA buffer
*
* Releases the DMA buffer allocated via snd_pcm_lib_malloc_pages().
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_free_pages(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime;
snd_assert(substream != NULL, return -EINVAL);
runtime = substream->runtime;
snd_assert(runtime != NULL, return -EINVAL);
if (runtime->dma_area == NULL)
return 0;
if (runtime->dma_buffer_p != &substream->dma_buffer) {
/* it's a newly allocated buffer. release it now. */
snd_dma_free_pages(runtime->dma_buffer_p);
kfree(runtime->dma_buffer_p);
}
snd_pcm_set_runtime_buffer(substream, NULL);
return 0;
}

481
sound/core/pcm_misc.c Normal file
View File

@@ -0,0 +1,481 @@
/*
* PCM Interface - misc routines
* Copyright (c) 1998 by Jaroslav Kysela <perex@suse.cz>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#define SND_PCM_FORMAT_UNKNOWN (-1)
/* NOTE: "signed" prefix must be given below since the default char is
* unsigned on some architectures!
*/
struct pcm_format_data {
unsigned char width; /* bit width */
unsigned char phys; /* physical bit width */
signed char le; /* 0 = big-endian, 1 = little-endian, -1 = others */
signed char signd; /* 0 = unsigned, 1 = signed, -1 = others */
unsigned char silence[8]; /* silence data to fill */
};
static struct pcm_format_data pcm_formats[SNDRV_PCM_FORMAT_LAST+1] = {
[SNDRV_PCM_FORMAT_S8] = {
.width = 8, .phys = 8, .le = -1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U8] = {
.width = 8, .phys = 8, .le = -1, .signd = 0,
.silence = { 0x80 },
},
[SNDRV_PCM_FORMAT_S16_LE] = {
.width = 16, .phys = 16, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S16_BE] = {
.width = 16, .phys = 16, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U16_LE] = {
.width = 16, .phys = 16, .le = 1, .signd = 0,
.silence = { 0x00, 0x80 },
},
[SNDRV_PCM_FORMAT_U16_BE] = {
.width = 16, .phys = 16, .le = 0, .signd = 0,
.silence = { 0x80, 0x00 },
},
[SNDRV_PCM_FORMAT_S24_LE] = {
.width = 24, .phys = 32, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S24_BE] = {
.width = 24, .phys = 32, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U24_LE] = {
.width = 24, .phys = 32, .le = 1, .signd = 0,
.silence = { 0x00, 0x00, 0x80 },
},
[SNDRV_PCM_FORMAT_U24_BE] = {
.width = 24, .phys = 32, .le = 0, .signd = 0,
.silence = { 0x80, 0x00, 0x00 },
},
[SNDRV_PCM_FORMAT_S32_LE] = {
.width = 32, .phys = 32, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S32_BE] = {
.width = 32, .phys = 32, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U32_LE] = {
.width = 32, .phys = 32, .le = 1, .signd = 0,
.silence = { 0x00, 0x00, 0x00, 0x80 },
},
[SNDRV_PCM_FORMAT_U32_BE] = {
.width = 32, .phys = 32, .le = 0, .signd = 0,
.silence = { 0x80, 0x00, 0x00, 0x00 },
},
[SNDRV_PCM_FORMAT_FLOAT_LE] = {
.width = 32, .phys = 32, .le = 1, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_FLOAT_BE] = {
.width = 32, .phys = 32, .le = 0, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_FLOAT64_LE] = {
.width = 64, .phys = 64, .le = 1, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_FLOAT64_BE] = {
.width = 64, .phys = 64, .le = 0, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE] = {
.width = 32, .phys = 32, .le = 1, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE] = {
.width = 32, .phys = 32, .le = 0, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_MU_LAW] = {
.width = 8, .phys = 8, .le = -1, .signd = -1,
.silence = { 0x7f },
},
[SNDRV_PCM_FORMAT_A_LAW] = {
.width = 8, .phys = 8, .le = -1, .signd = -1,
.silence = { 0x55 },
},
[SNDRV_PCM_FORMAT_IMA_ADPCM] = {
.width = 4, .phys = 4, .le = -1, .signd = -1,
.silence = {},
},
/* FIXME: the following three formats are not defined properly yet */
[SNDRV_PCM_FORMAT_MPEG] = {
.le = -1, .signd = -1,
},
[SNDRV_PCM_FORMAT_GSM] = {
.le = -1, .signd = -1,
},
[SNDRV_PCM_FORMAT_SPECIAL] = {
.le = -1, .signd = -1,
},
[SNDRV_PCM_FORMAT_S24_3LE] = {
.width = 24, .phys = 24, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S24_3BE] = {
.width = 24, .phys = 24, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U24_3LE] = {
.width = 24, .phys = 24, .le = 1, .signd = 0,
.silence = { 0x00, 0x00, 0x80 },
},
[SNDRV_PCM_FORMAT_U24_3BE] = {
.width = 24, .phys = 24, .le = 0, .signd = 0,
.silence = { 0x80, 0x00, 0x00 },
},
[SNDRV_PCM_FORMAT_S20_3LE] = {
.width = 20, .phys = 24, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S20_3BE] = {
.width = 20, .phys = 24, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U20_3LE] = {
.width = 20, .phys = 24, .le = 1, .signd = 0,
.silence = { 0x00, 0x00, 0x08 },
},
[SNDRV_PCM_FORMAT_U20_3BE] = {
.width = 20, .phys = 24, .le = 0, .signd = 0,
.silence = { 0x08, 0x00, 0x00 },
},
[SNDRV_PCM_FORMAT_S18_3LE] = {
.width = 18, .phys = 24, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S18_3BE] = {
.width = 18, .phys = 24, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U18_3LE] = {
.width = 18, .phys = 24, .le = 1, .signd = 0,
.silence = { 0x00, 0x00, 0x02 },
},
[SNDRV_PCM_FORMAT_U18_3BE] = {
.width = 18, .phys = 24, .le = 0, .signd = 0,
.silence = { 0x02, 0x00, 0x00 },
},
};
/**
* snd_pcm_format_signed - Check the PCM format is signed linear
* @format: the format to check
*
* Returns 1 if the given PCM format is signed linear, 0 if unsigned
* linear, and a negative error code for non-linear formats.
*/
int snd_pcm_format_signed(snd_pcm_format_t format)
{
int val;
if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
return -EINVAL;
if ((val = pcm_formats[format].signd) < 0)
return -EINVAL;
return val;
}
/**
* snd_pcm_format_unsigned - Check the PCM format is unsigned linear
* @format: the format to check
*
* Returns 1 if the given PCM format is unsigned linear, 0 if signed
* linear, and a negative error code for non-linear formats.
*/
int snd_pcm_format_unsigned(snd_pcm_format_t format)
{
int val;
val = snd_pcm_format_signed(format);
if (val < 0)
return val;
return !val;
}
/**
* snd_pcm_format_linear - Check the PCM format is linear
* @format: the format to check
*
* Returns 1 if the given PCM format is linear, 0 if not.
*/
int snd_pcm_format_linear(snd_pcm_format_t format)
{
return snd_pcm_format_signed(format) >= 0;
}
/**
* snd_pcm_format_little_endian - Check the PCM format is little-endian
* @format: the format to check
*
* Returns 1 if the given PCM format is little-endian, 0 if
* big-endian, or a negative error code if endian not specified.
*/
int snd_pcm_format_little_endian(snd_pcm_format_t format)
{
int val;
if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
return -EINVAL;
if ((val = pcm_formats[format].le) < 0)
return -EINVAL;
return val;
}
/**
* snd_pcm_format_big_endian - Check the PCM format is big-endian
* @format: the format to check
*
* Returns 1 if the given PCM format is big-endian, 0 if
* little-endian, or a negative error code if endian not specified.
*/
int snd_pcm_format_big_endian(snd_pcm_format_t format)
{
int val;
val = snd_pcm_format_little_endian(format);
if (val < 0)
return val;
return !val;
}
/**
* snd_pcm_format_cpu_endian - Check the PCM format is CPU-endian
* @format: the format to check
*
* Returns 1 if the given PCM format is CPU-endian, 0 if
* opposite, or a negative error code if endian not specified.
*/
int snd_pcm_format_cpu_endian(snd_pcm_format_t format)
{
#ifdef SNDRV_LITTLE_ENDIAN
return snd_pcm_format_little_endian(format);
#else
return snd_pcm_format_big_endian(format);
#endif
}
/**
* snd_pcm_format_width - return the bit-width of the format
* @format: the format to check
*
* Returns the bit-width of the format, or a negative error code
* if unknown format.
*/
int snd_pcm_format_width(snd_pcm_format_t format)
{
int val;
if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
return -EINVAL;
if ((val = pcm_formats[format].width) == 0)
return -EINVAL;
return val;
}
/**
* snd_pcm_format_physical_width - return the physical bit-width of the format
* @format: the format to check
*
* Returns the physical bit-width of the format, or a negative error code
* if unknown format.
*/
int snd_pcm_format_physical_width(snd_pcm_format_t format)
{
int val;
if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
return -EINVAL;
if ((val = pcm_formats[format].phys) == 0)
return -EINVAL;
return val;
}
/**
* snd_pcm_format_size - return the byte size of samples on the given format
* @format: the format to check
*
* Returns the byte size of the given samples for the format, or a
* negative error code if unknown format.
*/
ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples)
{
int phys_width = snd_pcm_format_physical_width(format);
if (phys_width < 0)
return -EINVAL;
return samples * phys_width / 8;
}
/**
* snd_pcm_format_silence_64 - return the silent data in 8 bytes array
* @format: the format to check
*
* Returns the format pattern to fill or NULL if error.
*/
const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format)
{
if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
return NULL;
if (! pcm_formats[format].phys)
return NULL;
return pcm_formats[format].silence;
}
/**
* snd_pcm_format_set_silence - set the silence data on the buffer
* @format: the PCM format
* @data: the buffer pointer
* @samples: the number of samples to set silence
*
* Sets the silence data on the buffer for the given samples.
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int samples)
{
int width;
unsigned char *dst, *pat;
if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
return -EINVAL;
if (samples == 0)
return 0;
width = pcm_formats[format].phys; /* physical width */
pat = pcm_formats[format].silence;
if (! width)
return -EINVAL;
/* signed or 1 byte data */
if (pcm_formats[format].signd == 1 || width <= 8) {
unsigned int bytes = samples * width / 8;
memset(data, *pat, bytes);
return 0;
}
/* non-zero samples, fill using a loop */
width /= 8;
dst = data;
#if 0
while (samples--) {
memcpy(dst, pat, width);
dst += width;
}
#else
/* a bit optimization for constant width */
switch (width) {
case 2:
while (samples--) {
memcpy(dst, pat, 2);
dst += 2;
}
break;
case 3:
while (samples--) {
memcpy(dst, pat, 3);
dst += 3;
}
break;
case 4:
while (samples--) {
memcpy(dst, pat, 4);
dst += 4;
}
break;
case 8:
while (samples--) {
memcpy(dst, pat, 8);
dst += 8;
}
break;
}
#endif
return 0;
}
/* [width][unsigned][bigendian] */
static int linear_formats[4][2][2] = {
{{ SNDRV_PCM_FORMAT_S8, SNDRV_PCM_FORMAT_S8},
{ SNDRV_PCM_FORMAT_U8, SNDRV_PCM_FORMAT_U8}},
{{SNDRV_PCM_FORMAT_S16_LE, SNDRV_PCM_FORMAT_S16_BE},
{SNDRV_PCM_FORMAT_U16_LE, SNDRV_PCM_FORMAT_U16_BE}},
{{SNDRV_PCM_FORMAT_S24_LE, SNDRV_PCM_FORMAT_S24_BE},
{SNDRV_PCM_FORMAT_U24_LE, SNDRV_PCM_FORMAT_U24_BE}},
{{SNDRV_PCM_FORMAT_S32_LE, SNDRV_PCM_FORMAT_S32_BE},
{SNDRV_PCM_FORMAT_U32_LE, SNDRV_PCM_FORMAT_U32_BE}}
};
/**
* snd_pcm_build_linear_format - return the suitable linear format for the given condition
* @width: the bit-width
* @unsignd: 1 if unsigned, 0 if signed.
* @big_endian: 1 if big-endian, 0 if little-endian
*
* Returns the suitable linear format for the given condition.
*/
snd_pcm_format_t snd_pcm_build_linear_format(int width, int unsignd, int big_endian)
{
if (width & 7)
return SND_PCM_FORMAT_UNKNOWN;
width = (width / 8) - 1;
if (width < 0 || width >= 4)
return SND_PCM_FORMAT_UNKNOWN;
return linear_formats[width][!!unsignd][!!big_endian];
}
/**
* snd_pcm_limit_hw_rates - determine rate_min/rate_max fields
* @runtime: the runtime instance
*
* Determines the rate_min and rate_max fields from the rates bits of
* the given runtime->hw.
*
* Returns zero if successful.
*/
int snd_pcm_limit_hw_rates(snd_pcm_runtime_t *runtime)
{
static unsigned rates[] = {
/* ATTENTION: these values depend on the definition in pcm.h! */
5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000,
64000, 88200, 96000, 176400, 192000
};
int i;
for (i = 0; i < (int)ARRAY_SIZE(rates); i++) {
if (runtime->hw.rates & (1 << i)) {
runtime->hw.rate_min = rates[i];
break;
}
}
for (i = (int)ARRAY_SIZE(rates) - 1; i >= 0; i--) {
if (runtime->hw.rates & (1 << i)) {
runtime->hw.rate_max = rates[i];
break;
}
}
return 0;
}

3364
sound/core/pcm_native.c Normal file

File diff suppressed because it is too large Load Diff

161
sound/core/pcm_timer.c Normal file
View File

@@ -0,0 +1,161 @@
/*
* Digital Audio (PCM) abstract layer
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/timer.h>
/*
* Timer functions
*/
/* Greatest common divisor */
static unsigned long gcd(unsigned long a, unsigned long b)
{
unsigned long r;
if (a < b) {
r = a;
a = b;
b = r;
}
while ((r = a % b) != 0) {
a = b;
b = r;
}
return b;
}
void snd_pcm_timer_resolution_change(snd_pcm_substream_t *substream)
{
unsigned long rate, mult, fsize, l, post;
snd_pcm_runtime_t *runtime = substream->runtime;
mult = 1000000000;
rate = runtime->rate;
snd_assert(rate != 0, return);
l = gcd(mult, rate);
mult /= l;
rate /= l;
fsize = runtime->period_size;
snd_assert(fsize != 0, return);
l = gcd(rate, fsize);
rate /= l;
fsize /= l;
post = 1;
while ((mult * fsize) / fsize != mult) {
mult /= 2;
post *= 2;
}
if (rate == 0) {
snd_printk(KERN_ERR "pcm timer resolution out of range (rate = %u, period_size = %lu)\n", runtime->rate, runtime->period_size);
runtime->timer_resolution = -1;
return;
}
runtime->timer_resolution = (mult * fsize / rate) * post;
}
static unsigned long snd_pcm_timer_resolution(snd_timer_t * timer)
{
snd_pcm_substream_t * substream;
substream = timer->private_data;
return substream->runtime ? substream->runtime->timer_resolution : 0;
}
static int snd_pcm_timer_start(snd_timer_t * timer)
{
unsigned long flags;
snd_pcm_substream_t * substream;
substream = snd_timer_chip(timer);
spin_lock_irqsave(&substream->timer_lock, flags);
substream->timer_running = 1;
spin_unlock_irqrestore(&substream->timer_lock, flags);
return 0;
}
static int snd_pcm_timer_stop(snd_timer_t * timer)
{
unsigned long flags;
snd_pcm_substream_t * substream;
substream = snd_timer_chip(timer);
spin_lock_irqsave(&substream->timer_lock, flags);
substream->timer_running = 0;
spin_unlock_irqrestore(&substream->timer_lock, flags);
return 0;
}
static struct _snd_timer_hardware snd_pcm_timer =
{
.flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_SLAVE,
.resolution = 0,
.ticks = 1,
.c_resolution = snd_pcm_timer_resolution,
.start = snd_pcm_timer_start,
.stop = snd_pcm_timer_stop,
};
/*
* Init functions
*/
static void snd_pcm_timer_free(snd_timer_t *timer)
{
snd_pcm_substream_t *substream = timer->private_data;
substream->timer = NULL;
}
void snd_pcm_timer_init(snd_pcm_substream_t *substream)
{
snd_timer_id_t tid;
snd_timer_t *timer;
tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
tid.dev_class = SNDRV_TIMER_CLASS_PCM;
tid.card = substream->pcm->card->number;
tid.device = substream->pcm->device;
tid.subdevice = (substream->number << 1) | (substream->stream & 1);
if (snd_timer_new(substream->pcm->card, "PCM", &tid, &timer) < 0)
return;
sprintf(timer->name, "PCM %s %i-%i-%i",
substream->stream == SNDRV_PCM_STREAM_CAPTURE ?
"capture" : "playback",
tid.card, tid.device, tid.subdevice);
timer->hw = snd_pcm_timer;
if (snd_device_register(timer->card, timer) < 0) {
snd_device_free(timer->card, timer);
return;
}
timer->private_data = substream;
timer->private_free = snd_pcm_timer_free;
substream->timer = timer;
}
void snd_pcm_timer_done(snd_pcm_substream_t *substream)
{
if (substream->timer) {
snd_device_free(substream->pcm->card, substream->timer);
substream->timer = NULL;
}
}

1680
sound/core/rawmidi.c Normal file

File diff suppressed because it is too large Load Diff

120
sound/core/rawmidi_compat.c Normal file
View File

@@ -0,0 +1,120 @@
/*
* 32bit -> 64bit ioctl wrapper for raw MIDI API
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* This file included from rawmidi.c */
#include <linux/compat.h>
struct sndrv_rawmidi_params32 {
s32 stream;
u32 buffer_size;
u32 avail_min;
unsigned int no_active_sensing; /* avoid bit-field */
unsigned char reserved[16];
} __attribute__((packed));
static int snd_rawmidi_ioctl_params_compat(snd_rawmidi_file_t *rfile,
struct sndrv_rawmidi_params32 __user *src)
{
snd_rawmidi_params_t params;
unsigned int val;
if (rfile->output == NULL)
return -EINVAL;
if (get_user(params.stream, &src->stream) ||
get_user(params.buffer_size, &src->buffer_size) ||
get_user(params.avail_min, &src->avail_min) ||
get_user(val, &src->no_active_sensing))
return -EFAULT;
params.no_active_sensing = val;
switch (params.stream) {
case SNDRV_RAWMIDI_STREAM_OUTPUT:
return snd_rawmidi_output_params(rfile->output, &params);
case SNDRV_RAWMIDI_STREAM_INPUT:
return snd_rawmidi_input_params(rfile->input, &params);
}
return -EINVAL;
}
struct sndrv_rawmidi_status32 {
s32 stream;
struct compat_timespec tstamp;
u32 avail;
u32 xruns;
unsigned char reserved[16];
} __attribute__((packed));
static int snd_rawmidi_ioctl_status_compat(snd_rawmidi_file_t *rfile,
struct sndrv_rawmidi_status32 __user *src)
{
int err;
snd_rawmidi_status_t status;
if (rfile->output == NULL)
return -EINVAL;
if (get_user(status.stream, &src->stream))
return -EFAULT;
switch (status.stream) {
case SNDRV_RAWMIDI_STREAM_OUTPUT:
err = snd_rawmidi_output_status(rfile->output, &status);
break;
case SNDRV_RAWMIDI_STREAM_INPUT:
err = snd_rawmidi_input_status(rfile->input, &status);
break;
default:
return -EINVAL;
}
if (err < 0)
return err;
if (put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) ||
put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) ||
put_user(status.avail, &src->avail) ||
put_user(status.xruns, &src->xruns))
return -EFAULT;
return 0;
}
enum {
SNDRV_RAWMIDI_IOCTL_PARAMS32 = _IOWR('W', 0x10, struct sndrv_rawmidi_params32),
SNDRV_RAWMIDI_IOCTL_STATUS32 = _IOWR('W', 0x20, struct sndrv_rawmidi_status32),
};
static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
{
snd_rawmidi_file_t *rfile;
void __user *argp = compat_ptr(arg);
rfile = file->private_data;
switch (cmd) {
case SNDRV_RAWMIDI_IOCTL_PVERSION:
case SNDRV_RAWMIDI_IOCTL_INFO:
case SNDRV_RAWMIDI_IOCTL_DROP:
case SNDRV_RAWMIDI_IOCTL_DRAIN:
return snd_rawmidi_ioctl(file, cmd, (unsigned long)argp);
case SNDRV_RAWMIDI_IOCTL_PARAMS32:
return snd_rawmidi_ioctl_params_compat(rfile, argp);
case SNDRV_RAWMIDI_IOCTL_STATUS32:
return snd_rawmidi_ioctl_status_compat(rfile, argp);
}
return -ENOIOCTLCMD;
}

188
sound/core/rtctimer.c Normal file
View File

@@ -0,0 +1,188 @@
/*
* RTC based high-frequency timer
*
* Copyright (C) 2000 Takashi Iwai
* based on rtctimer.c by Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/threads.h>
#include <linux/interrupt.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/timer.h>
#include <sound/info.h>
#if defined(CONFIG_RTC) || defined(CONFIG_RTC_MODULE)
#include <linux/mc146818rtc.h>
#define RTC_FREQ 1024 /* default frequency */
#define NANO_SEC 1000000000L /* 10^9 in sec */
/*
* prototypes
*/
static int rtctimer_open(snd_timer_t *t);
static int rtctimer_close(snd_timer_t *t);
static int rtctimer_start(snd_timer_t *t);
static int rtctimer_stop(snd_timer_t *t);
/*
* The hardware dependent description for this timer.
*/
static struct _snd_timer_hardware rtc_hw = {
.flags = SNDRV_TIMER_HW_FIRST|SNDRV_TIMER_HW_AUTO,
.ticks = 100000000L, /* FIXME: XXX */
.open = rtctimer_open,
.close = rtctimer_close,
.start = rtctimer_start,
.stop = rtctimer_stop,
};
static int rtctimer_freq = RTC_FREQ; /* frequency */
static snd_timer_t *rtctimer;
static atomic_t rtc_inc = ATOMIC_INIT(0);
static rtc_task_t rtc_task;
static int
rtctimer_open(snd_timer_t *t)
{
int err;
err = rtc_register(&rtc_task);
if (err < 0)
return err;
t->private_data = &rtc_task;
return 0;
}
static int
rtctimer_close(snd_timer_t *t)
{
rtc_task_t *rtc = t->private_data;
if (rtc) {
rtc_unregister(rtc);
t->private_data = NULL;
}
return 0;
}
static int
rtctimer_start(snd_timer_t *timer)
{
rtc_task_t *rtc = timer->private_data;
snd_assert(rtc != NULL, return -EINVAL);
rtc_control(rtc, RTC_IRQP_SET, rtctimer_freq);
rtc_control(rtc, RTC_PIE_ON, 0);
atomic_set(&rtc_inc, 0);
return 0;
}
static int
rtctimer_stop(snd_timer_t *timer)
{
rtc_task_t *rtc = timer->private_data;
snd_assert(rtc != NULL, return -EINVAL);
rtc_control(rtc, RTC_PIE_OFF, 0);
return 0;
}
/*
* interrupt
*/
static void rtctimer_interrupt(void *private_data)
{
int ticks;
atomic_inc(&rtc_inc);
ticks = atomic_read(&rtc_inc);
snd_timer_interrupt((snd_timer_t*)private_data, ticks);
atomic_sub(ticks, &rtc_inc);
}
/*
* ENTRY functions
*/
static int __init rtctimer_init(void)
{
int order, err;
snd_timer_t *timer;
if (rtctimer_freq < 2 || rtctimer_freq > 8192) {
snd_printk(KERN_ERR "rtctimer: invalid frequency %d\n", rtctimer_freq);
return -EINVAL;
}
for (order = 1; rtctimer_freq > order; order <<= 1)
;
if (rtctimer_freq != order) {
snd_printk(KERN_ERR "rtctimer: invalid frequency %d\n", rtctimer_freq);
return -EINVAL;
}
/* Create a new timer and set up the fields */
err = snd_timer_global_new("rtc", SNDRV_TIMER_GLOBAL_RTC, &timer);
if (err < 0)
return err;
strcpy(timer->name, "RTC timer");
timer->hw = rtc_hw;
timer->hw.resolution = NANO_SEC / rtctimer_freq;
/* set up RTC callback */
rtc_task.func = rtctimer_interrupt;
rtc_task.private_data = timer;
err = snd_timer_global_register(timer);
if (err < 0) {
snd_timer_global_free(timer);
return err;
}
rtctimer = timer; /* remember this */
return 0;
}
static void __exit rtctimer_exit(void)
{
if (rtctimer) {
snd_timer_global_unregister(rtctimer);
rtctimer = NULL;
}
}
/*
* exported stuff
*/
module_init(rtctimer_init)
module_exit(rtctimer_exit)
module_param(rtctimer_freq, int, 0444);
MODULE_PARM_DESC(rtctimer_freq, "timer frequency in Hz");
MODULE_LICENSE("GPL");
MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_RTC));
#endif /* CONFIG_RTC || CONFIG_RTC_MODULE */

44
sound/core/seq/Makefile Normal file
View File

@@ -0,0 +1,44 @@
#
# Makefile for ALSA
# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
#
obj-$(CONFIG_SND) += instr/
ifeq ($(CONFIG_SND_SEQUENCER_OSS),y)
obj-$(CONFIG_SND_SEQUENCER) += oss/
endif
snd-seq-device-objs := seq_device.o
snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \
seq_fifo.o seq_prioq.o seq_timer.o \
seq_system.o seq_ports.o seq_info.o
snd-seq-midi-objs := seq_midi.o
snd-seq-midi-emul-objs := seq_midi_emul.o
snd-seq-midi-event-objs := seq_midi_event.o
snd-seq-instr-objs := seq_instr.o
snd-seq-dummy-objs := seq_dummy.o
snd-seq-virmidi-objs := seq_virmidi.o
#
# this function returns:
# "m" - CONFIG_SND_SEQUENCER is m
# <empty string> - CONFIG_SND_SEQUENCER is undefined
# otherwise parameter #1 value
#
sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1)))
obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o snd-seq-device.o
ifeq ($(CONFIG_SND_SEQUENCER_OSS),y)
obj-$(CONFIG_SND_SEQUENCER) += snd-seq-midi-event.o
endif
obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_VIRMIDI) += snd-seq-virmidi.o snd-seq-midi-event.o
obj-$(call sequencer,$(CONFIG_SND_RAWMIDI)) += snd-seq-midi.o snd-seq-midi-event.o
obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-seq-midi-event.o snd-seq-midi-emul.o snd-seq-instr.o
obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-seq-midi-event.o snd-seq-midi-emul.o snd-seq-instr.o
obj-$(call sequencer,$(CONFIG_SND_GUS_SYNTH)) += snd-seq-instr.o
obj-$(call sequencer,$(CONFIG_SND_SBAWE)) += snd-seq-midi-emul.o snd-seq-virmidi.o
obj-$(call sequencer,$(CONFIG_SND_EMU10K1)) += snd-seq-midi-emul.o snd-seq-virmidi.o
obj-$(call sequencer,$(CONFIG_SND_TRIDENT)) += snd-seq-midi-emul.o snd-seq-instr.o

View File

@@ -0,0 +1,23 @@
#
# Makefile for ALSA
# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
#
snd-ainstr-fm-objs := ainstr_fm.o
snd-ainstr-simple-objs := ainstr_simple.o
snd-ainstr-gf1-objs := ainstr_gf1.o
snd-ainstr-iw-objs := ainstr_iw.o
#
# this function returns:
# "m" - CONFIG_SND_SEQUENCER is m
# <empty string> - CONFIG_SND_SEQUENCER is undefined
# otherwise parameter #1 value
#
sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1)))
# Toplevel Module Dependency
obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-ainstr-fm.o
obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-ainstr-fm.o
obj-$(call sequencer,$(CONFIG_SND_GUS_SYNTH)) += snd-ainstr-gf1.o snd-ainstr-simple.o snd-ainstr-iw.o
obj-$(call sequencer,$(CONFIG_SND_TRIDENT)) += snd-ainstr-simple.o

View File

@@ -0,0 +1,156 @@
/*
* FM (OPL2/3) Instrument routines
* Copyright (c) 2000 Uros Bizjak <uros@kss-loka.si>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <sound/core.h>
#include <sound/ainstr_fm.h>
#include <sound/initval.h>
#include <asm/uaccess.h>
MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture FM Instrument support.");
MODULE_LICENSE("GPL");
static int snd_seq_fm_put(void *private_data, snd_seq_kinstr_t *instr,
char __user *instr_data, long len, int atomic, int cmd)
{
fm_instrument_t *ip;
fm_xinstrument_t ix;
int idx;
if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
return -EINVAL;
/* copy instrument data */
if (len < (long)sizeof(ix))
return -EINVAL;
if (copy_from_user(&ix, instr_data, sizeof(ix)))
return -EFAULT;
if (ix.stype != FM_STRU_INSTR)
return -EINVAL;
ip = (fm_instrument_t *)KINSTR_DATA(instr);
ip->share_id[0] = le32_to_cpu(ix.share_id[0]);
ip->share_id[1] = le32_to_cpu(ix.share_id[1]);
ip->share_id[2] = le32_to_cpu(ix.share_id[2]);
ip->share_id[3] = le32_to_cpu(ix.share_id[3]);
ip->type = ix.type;
for (idx = 0; idx < 4; idx++) {
ip->op[idx].am_vib = ix.op[idx].am_vib;
ip->op[idx].ksl_level = ix.op[idx].ksl_level;
ip->op[idx].attack_decay = ix.op[idx].attack_decay;
ip->op[idx].sustain_release = ix.op[idx].sustain_release;
ip->op[idx].wave_select = ix.op[idx].wave_select;
}
for (idx = 0; idx < 2; idx++) {
ip->feedback_connection[idx] = ix.feedback_connection[idx];
}
ip->echo_delay = ix.echo_delay;
ip->echo_atten = ix.echo_atten;
ip->chorus_spread = ix.chorus_spread;
ip->trnsps = ix.trnsps;
ip->fix_dur = ix.fix_dur;
ip->modes = ix.modes;
ip->fix_key = ix.fix_key;
return 0;
}
static int snd_seq_fm_get(void *private_data, snd_seq_kinstr_t *instr,
char __user *instr_data, long len, int atomic,
int cmd)
{
fm_instrument_t *ip;
fm_xinstrument_t ix;
int idx;
if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
return -EINVAL;
if (len < (long)sizeof(ix))
return -ENOMEM;
memset(&ix, 0, sizeof(ix));
ip = (fm_instrument_t *)KINSTR_DATA(instr);
ix.stype = FM_STRU_INSTR;
ix.share_id[0] = cpu_to_le32(ip->share_id[0]);
ix.share_id[1] = cpu_to_le32(ip->share_id[1]);
ix.share_id[2] = cpu_to_le32(ip->share_id[2]);
ix.share_id[3] = cpu_to_le32(ip->share_id[3]);
ix.type = ip->type;
for (idx = 0; idx < 4; idx++) {
ix.op[idx].am_vib = ip->op[idx].am_vib;
ix.op[idx].ksl_level = ip->op[idx].ksl_level;
ix.op[idx].attack_decay = ip->op[idx].attack_decay;
ix.op[idx].sustain_release = ip->op[idx].sustain_release;
ix.op[idx].wave_select = ip->op[idx].wave_select;
}
for (idx = 0; idx < 2; idx++) {
ix.feedback_connection[idx] = ip->feedback_connection[idx];
}
if (copy_to_user(instr_data, &ix, sizeof(ix)))
return -EFAULT;
ix.echo_delay = ip->echo_delay;
ix.echo_atten = ip->echo_atten;
ix.chorus_spread = ip->chorus_spread;
ix.trnsps = ip->trnsps;
ix.fix_dur = ip->fix_dur;
ix.modes = ip->modes;
ix.fix_key = ip->fix_key;
return 0;
}
static int snd_seq_fm_get_size(void *private_data, snd_seq_kinstr_t *instr,
long *size)
{
*size = sizeof(fm_xinstrument_t);
return 0;
}
int snd_seq_fm_init(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_ops_t *next)
{
memset(ops, 0, sizeof(*ops));
// ops->private_data = private_data;
ops->add_len = sizeof(fm_instrument_t);
ops->instr_type = SNDRV_SEQ_INSTR_ID_OPL2_3;
ops->put = snd_seq_fm_put;
ops->get = snd_seq_fm_get;
ops->get_size = snd_seq_fm_get_size;
// ops->remove = snd_seq_fm_remove;
// ops->notify = snd_seq_fm_notify;
ops->next = next;
return 0;
}
/*
* Init part
*/
static int __init alsa_ainstr_fm_init(void)
{
return 0;
}
static void __exit alsa_ainstr_fm_exit(void)
{
}
module_init(alsa_ainstr_fm_init)
module_exit(alsa_ainstr_fm_exit)
EXPORT_SYMBOL(snd_seq_fm_init);

View File

@@ -0,0 +1,358 @@
/*
* GF1 (GUS) Patch - Instrument routines
* Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/ainstr_gf1.h>
#include <sound/initval.h>
#include <asm/uaccess.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture GF1 (GUS) Patch support.");
MODULE_LICENSE("GPL");
static unsigned int snd_seq_gf1_size(unsigned int size, unsigned int format)
{
unsigned int result = size;
if (format & GF1_WAVE_16BIT)
result <<= 1;
if (format & GF1_WAVE_STEREO)
result <<= 1;
return format;
}
static int snd_seq_gf1_copy_wave_from_stream(snd_gf1_ops_t *ops,
gf1_instrument_t *ip,
char __user **data,
long *len,
int atomic)
{
gf1_wave_t *wp, *prev;
gf1_xwave_t xp;
int err, gfp_mask;
unsigned int real_size;
gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
if (*len < (long)sizeof(xp))
return -EINVAL;
if (copy_from_user(&xp, *data, sizeof(xp)))
return -EFAULT;
*data += sizeof(xp);
*len -= sizeof(xp);
wp = kcalloc(1, sizeof(*wp), gfp_mask);
if (wp == NULL)
return -ENOMEM;
wp->share_id[0] = le32_to_cpu(xp.share_id[0]);
wp->share_id[1] = le32_to_cpu(xp.share_id[1]);
wp->share_id[2] = le32_to_cpu(xp.share_id[2]);
wp->share_id[3] = le32_to_cpu(xp.share_id[3]);
wp->format = le32_to_cpu(xp.format);
wp->size = le32_to_cpu(xp.size);
wp->start = le32_to_cpu(xp.start);
wp->loop_start = le32_to_cpu(xp.loop_start);
wp->loop_end = le32_to_cpu(xp.loop_end);
wp->loop_repeat = le16_to_cpu(xp.loop_repeat);
wp->flags = xp.flags;
wp->sample_rate = le32_to_cpu(xp.sample_rate);
wp->low_frequency = le32_to_cpu(xp.low_frequency);
wp->high_frequency = le32_to_cpu(xp.high_frequency);
wp->root_frequency = le32_to_cpu(xp.root_frequency);
wp->tune = le16_to_cpu(xp.tune);
wp->balance = xp.balance;
memcpy(wp->envelope_rate, xp.envelope_rate, 6);
memcpy(wp->envelope_offset, xp.envelope_offset, 6);
wp->tremolo_sweep = xp.tremolo_sweep;
wp->tremolo_rate = xp.tremolo_rate;
wp->tremolo_depth = xp.tremolo_depth;
wp->vibrato_sweep = xp.vibrato_sweep;
wp->vibrato_rate = xp.vibrato_rate;
wp->vibrato_depth = xp.vibrato_depth;
wp->scale_frequency = le16_to_cpu(xp.scale_frequency);
wp->scale_factor = le16_to_cpu(xp.scale_factor);
real_size = snd_seq_gf1_size(wp->size, wp->format);
if ((long)real_size > *len) {
kfree(wp);
return -ENOMEM;
}
if (ops->put_sample) {
err = ops->put_sample(ops->private_data, wp,
*data, real_size, atomic);
if (err < 0) {
kfree(wp);
return err;
}
}
*data += real_size;
*len -= real_size;
prev = ip->wave;
if (prev) {
while (prev->next) prev = prev->next;
prev->next = wp;
} else {
ip->wave = wp;
}
return 0;
}
static void snd_seq_gf1_wave_free(snd_gf1_ops_t *ops,
gf1_wave_t *wave,
int atomic)
{
if (ops->remove_sample)
ops->remove_sample(ops->private_data, wave, atomic);
kfree(wave);
}
static void snd_seq_gf1_instr_free(snd_gf1_ops_t *ops,
gf1_instrument_t *ip,
int atomic)
{
gf1_wave_t *wave;
while ((wave = ip->wave) != NULL) {
ip->wave = wave->next;
snd_seq_gf1_wave_free(ops, wave, atomic);
}
}
static int snd_seq_gf1_put(void *private_data, snd_seq_kinstr_t *instr,
char __user *instr_data, long len, int atomic,
int cmd)
{
snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
gf1_instrument_t *ip;
gf1_xinstrument_t ix;
int err, gfp_mask;
if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
return -EINVAL;
gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
/* copy instrument data */
if (len < (long)sizeof(ix))
return -EINVAL;
if (copy_from_user(&ix, instr_data, sizeof(ix)))
return -EFAULT;
if (ix.stype != GF1_STRU_INSTR)
return -EINVAL;
instr_data += sizeof(ix);
len -= sizeof(ix);
ip = (gf1_instrument_t *)KINSTR_DATA(instr);
ip->exclusion = le16_to_cpu(ix.exclusion);
ip->exclusion_group = le16_to_cpu(ix.exclusion_group);
ip->effect1 = ix.effect1;
ip->effect1_depth = ix.effect1_depth;
ip->effect2 = ix.effect2;
ip->effect2_depth = ix.effect2_depth;
/* copy layers */
while (len > (long)sizeof(__u32)) {
__u32 stype;
if (copy_from_user(&stype, instr_data, sizeof(stype)))
return -EFAULT;
if (stype != GF1_STRU_WAVE) {
snd_seq_gf1_instr_free(ops, ip, atomic);
return -EINVAL;
}
err = snd_seq_gf1_copy_wave_from_stream(ops,
ip,
&instr_data,
&len,
atomic);
if (err < 0) {
snd_seq_gf1_instr_free(ops, ip, atomic);
return err;
}
}
return 0;
}
static int snd_seq_gf1_copy_wave_to_stream(snd_gf1_ops_t *ops,
gf1_instrument_t *ip,
char __user **data,
long *len,
int atomic)
{
gf1_wave_t *wp;
gf1_xwave_t xp;
int err;
unsigned int real_size;
for (wp = ip->wave; wp; wp = wp->next) {
if (*len < (long)sizeof(xp))
return -ENOMEM;
memset(&xp, 0, sizeof(xp));
xp.stype = GF1_STRU_WAVE;
xp.share_id[0] = cpu_to_le32(wp->share_id[0]);
xp.share_id[1] = cpu_to_le32(wp->share_id[1]);
xp.share_id[2] = cpu_to_le32(wp->share_id[2]);
xp.share_id[3] = cpu_to_le32(wp->share_id[3]);
xp.format = cpu_to_le32(wp->format);
xp.size = cpu_to_le32(wp->size);
xp.start = cpu_to_le32(wp->start);
xp.loop_start = cpu_to_le32(wp->loop_start);
xp.loop_end = cpu_to_le32(wp->loop_end);
xp.loop_repeat = cpu_to_le32(wp->loop_repeat);
xp.flags = wp->flags;
xp.sample_rate = cpu_to_le32(wp->sample_rate);
xp.low_frequency = cpu_to_le32(wp->low_frequency);
xp.high_frequency = cpu_to_le32(wp->high_frequency);
xp.root_frequency = cpu_to_le32(wp->root_frequency);
xp.tune = cpu_to_le16(wp->tune);
xp.balance = wp->balance;
memcpy(xp.envelope_rate, wp->envelope_rate, 6);
memcpy(xp.envelope_offset, wp->envelope_offset, 6);
xp.tremolo_sweep = wp->tremolo_sweep;
xp.tremolo_rate = wp->tremolo_rate;
xp.tremolo_depth = wp->tremolo_depth;
xp.vibrato_sweep = wp->vibrato_sweep;
xp.vibrato_rate = wp->vibrato_rate;
xp.vibrato_depth = wp->vibrato_depth;
xp.scale_frequency = cpu_to_le16(wp->scale_frequency);
xp.scale_factor = cpu_to_le16(wp->scale_factor);
if (copy_to_user(*data, &xp, sizeof(xp)))
return -EFAULT;
*data += sizeof(xp);
*len -= sizeof(xp);
real_size = snd_seq_gf1_size(wp->size, wp->format);
if (*len < (long)real_size)
return -ENOMEM;
if (ops->get_sample) {
err = ops->get_sample(ops->private_data, wp,
*data, real_size, atomic);
if (err < 0)
return err;
}
*data += wp->size;
*len -= wp->size;
}
return 0;
}
static int snd_seq_gf1_get(void *private_data, snd_seq_kinstr_t *instr,
char __user *instr_data, long len, int atomic,
int cmd)
{
snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
gf1_instrument_t *ip;
gf1_xinstrument_t ix;
if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
return -EINVAL;
if (len < (long)sizeof(ix))
return -ENOMEM;
memset(&ix, 0, sizeof(ix));
ip = (gf1_instrument_t *)KINSTR_DATA(instr);
ix.stype = GF1_STRU_INSTR;
ix.exclusion = cpu_to_le16(ip->exclusion);
ix.exclusion_group = cpu_to_le16(ip->exclusion_group);
ix.effect1 = cpu_to_le16(ip->effect1);
ix.effect1_depth = cpu_to_le16(ip->effect1_depth);
ix.effect2 = ip->effect2;
ix.effect2_depth = ip->effect2_depth;
if (copy_to_user(instr_data, &ix, sizeof(ix)))
return -EFAULT;
instr_data += sizeof(ix);
len -= sizeof(ix);
return snd_seq_gf1_copy_wave_to_stream(ops,
ip,
&instr_data,
&len,
atomic);
}
static int snd_seq_gf1_get_size(void *private_data, snd_seq_kinstr_t *instr,
long *size)
{
long result;
gf1_instrument_t *ip;
gf1_wave_t *wp;
*size = 0;
ip = (gf1_instrument_t *)KINSTR_DATA(instr);
result = sizeof(gf1_xinstrument_t);
for (wp = ip->wave; wp; wp = wp->next) {
result += sizeof(gf1_xwave_t);
result += wp->size;
}
*size = result;
return 0;
}
static int snd_seq_gf1_remove(void *private_data,
snd_seq_kinstr_t *instr,
int atomic)
{
snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
gf1_instrument_t *ip;
ip = (gf1_instrument_t *)KINSTR_DATA(instr);
snd_seq_gf1_instr_free(ops, ip, atomic);
return 0;
}
static void snd_seq_gf1_notify(void *private_data,
snd_seq_kinstr_t *instr,
int what)
{
snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
if (ops->notify)
ops->notify(ops->private_data, instr, what);
}
int snd_seq_gf1_init(snd_gf1_ops_t *ops,
void *private_data,
snd_seq_kinstr_ops_t *next)
{
memset(ops, 0, sizeof(*ops));
ops->private_data = private_data;
ops->kops.private_data = ops;
ops->kops.add_len = sizeof(gf1_instrument_t);
ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_GUS_PATCH;
ops->kops.put = snd_seq_gf1_put;
ops->kops.get = snd_seq_gf1_get;
ops->kops.get_size = snd_seq_gf1_get_size;
ops->kops.remove = snd_seq_gf1_remove;
ops->kops.notify = snd_seq_gf1_notify;
ops->kops.next = next;
return 0;
}
/*
* Init part
*/
static int __init alsa_ainstr_gf1_init(void)
{
return 0;
}
static void __exit alsa_ainstr_gf1_exit(void)
{
}
module_init(alsa_ainstr_gf1_init)
module_exit(alsa_ainstr_gf1_exit)
EXPORT_SYMBOL(snd_seq_gf1_init);

View File

@@ -0,0 +1,622 @@
/*
* IWFFFF - AMD InterWave (tm) - Instrument routines
* Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/ainstr_iw.h>
#include <sound/initval.h>
#include <asm/uaccess.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture IWFFFF support.");
MODULE_LICENSE("GPL");
static unsigned int snd_seq_iwffff_size(unsigned int size, unsigned int format)
{
unsigned int result = size;
if (format & IWFFFF_WAVE_16BIT)
result <<= 1;
if (format & IWFFFF_WAVE_STEREO)
result <<= 1;
return result;
}
static void snd_seq_iwffff_copy_lfo_from_stream(iwffff_lfo_t *fp,
iwffff_xlfo_t *fx)
{
fp->freq = le16_to_cpu(fx->freq);
fp->depth = le16_to_cpu(fx->depth);
fp->sweep = le16_to_cpu(fx->sweep);
fp->shape = fx->shape;
fp->delay = fx->delay;
}
static int snd_seq_iwffff_copy_env_from_stream(__u32 req_stype,
iwffff_layer_t *lp,
iwffff_env_t *ep,
iwffff_xenv_t *ex,
char __user **data,
long *len,
int gfp_mask)
{
__u32 stype;
iwffff_env_record_t *rp, *rp_last;
iwffff_xenv_record_t rx;
iwffff_env_point_t *pp;
iwffff_xenv_point_t px;
int points_size, idx;
ep->flags = ex->flags;
ep->mode = ex->mode;
ep->index = ex->index;
rp_last = NULL;
while (1) {
if (*len < (long)sizeof(__u32))
return -EINVAL;
if (copy_from_user(&stype, *data, sizeof(stype)))
return -EFAULT;
if (stype == IWFFFF_STRU_WAVE)
return 0;
if (req_stype != stype) {
if (stype == IWFFFF_STRU_ENV_RECP ||
stype == IWFFFF_STRU_ENV_RECV)
return 0;
}
if (*len < (long)sizeof(rx))
return -EINVAL;
if (copy_from_user(&rx, *data, sizeof(rx)))
return -EFAULT;
*data += sizeof(rx);
*len -= sizeof(rx);
points_size = (le16_to_cpu(rx.nattack) + le16_to_cpu(rx.nrelease)) * 2 * sizeof(__u16);
if (points_size > *len)
return -EINVAL;
rp = kcalloc(1, sizeof(*rp) + points_size, gfp_mask);
if (rp == NULL)
return -ENOMEM;
rp->nattack = le16_to_cpu(rx.nattack);
rp->nrelease = le16_to_cpu(rx.nrelease);
rp->sustain_offset = le16_to_cpu(rx.sustain_offset);
rp->sustain_rate = le16_to_cpu(rx.sustain_rate);
rp->release_rate = le16_to_cpu(rx.release_rate);
rp->hirange = rx.hirange;
pp = (iwffff_env_point_t *)(rp + 1);
for (idx = 0; idx < rp->nattack + rp->nrelease; idx++) {
if (copy_from_user(&px, *data, sizeof(px)))
return -EFAULT;
*data += sizeof(px);
*len -= sizeof(px);
pp->offset = le16_to_cpu(px.offset);
pp->rate = le16_to_cpu(px.rate);
}
if (ep->record == NULL) {
ep->record = rp;
} else {
rp_last = rp;
}
rp_last = rp;
}
return 0;
}
static int snd_seq_iwffff_copy_wave_from_stream(snd_iwffff_ops_t *ops,
iwffff_layer_t *lp,
char __user **data,
long *len,
int atomic)
{
iwffff_wave_t *wp, *prev;
iwffff_xwave_t xp;
int err, gfp_mask;
unsigned int real_size;
gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
if (*len < (long)sizeof(xp))
return -EINVAL;
if (copy_from_user(&xp, *data, sizeof(xp)))
return -EFAULT;
*data += sizeof(xp);
*len -= sizeof(xp);
wp = kcalloc(1, sizeof(*wp), gfp_mask);
if (wp == NULL)
return -ENOMEM;
wp->share_id[0] = le32_to_cpu(xp.share_id[0]);
wp->share_id[1] = le32_to_cpu(xp.share_id[1]);
wp->share_id[2] = le32_to_cpu(xp.share_id[2]);
wp->share_id[3] = le32_to_cpu(xp.share_id[3]);
wp->format = le32_to_cpu(xp.format);
wp->address.memory = le32_to_cpu(xp.offset);
wp->size = le32_to_cpu(xp.size);
wp->start = le32_to_cpu(xp.start);
wp->loop_start = le32_to_cpu(xp.loop_start);
wp->loop_end = le32_to_cpu(xp.loop_end);
wp->loop_repeat = le16_to_cpu(xp.loop_repeat);
wp->sample_ratio = le32_to_cpu(xp.sample_ratio);
wp->attenuation = xp.attenuation;
wp->low_note = xp.low_note;
wp->high_note = xp.high_note;
real_size = snd_seq_iwffff_size(wp->size, wp->format);
if (!(wp->format & IWFFFF_WAVE_ROM)) {
if ((long)real_size > *len) {
kfree(wp);
return -ENOMEM;
}
}
if (ops->put_sample) {
err = ops->put_sample(ops->private_data, wp,
*data, real_size, atomic);
if (err < 0) {
kfree(wp);
return err;
}
}
if (!(wp->format & IWFFFF_WAVE_ROM)) {
*data += real_size;
*len -= real_size;
}
prev = lp->wave;
if (prev) {
while (prev->next) prev = prev->next;
prev->next = wp;
} else {
lp->wave = wp;
}
return 0;
}
static void snd_seq_iwffff_env_free(snd_iwffff_ops_t *ops,
iwffff_env_t *env,
int atomic)
{
iwffff_env_record_t *rec;
while ((rec = env->record) != NULL) {
env->record = rec->next;
kfree(rec);
}
}
static void snd_seq_iwffff_wave_free(snd_iwffff_ops_t *ops,
iwffff_wave_t *wave,
int atomic)
{
if (ops->remove_sample)
ops->remove_sample(ops->private_data, wave, atomic);
kfree(wave);
}
static void snd_seq_iwffff_instr_free(snd_iwffff_ops_t *ops,
iwffff_instrument_t *ip,
int atomic)
{
iwffff_layer_t *layer;
iwffff_wave_t *wave;
while ((layer = ip->layer) != NULL) {
ip->layer = layer->next;
snd_seq_iwffff_env_free(ops, &layer->penv, atomic);
snd_seq_iwffff_env_free(ops, &layer->venv, atomic);
while ((wave = layer->wave) != NULL) {
layer->wave = wave->next;
snd_seq_iwffff_wave_free(ops, wave, atomic);
}
kfree(layer);
}
}
static int snd_seq_iwffff_put(void *private_data, snd_seq_kinstr_t *instr,
char __user *instr_data, long len, int atomic,
int cmd)
{
snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
iwffff_instrument_t *ip;
iwffff_xinstrument_t ix;
iwffff_layer_t *lp, *prev_lp;
iwffff_xlayer_t lx;
int err, gfp_mask;
if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
return -EINVAL;
gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
/* copy instrument data */
if (len < (long)sizeof(ix))
return -EINVAL;
if (copy_from_user(&ix, instr_data, sizeof(ix)))
return -EFAULT;
if (ix.stype != IWFFFF_STRU_INSTR)
return -EINVAL;
instr_data += sizeof(ix);
len -= sizeof(ix);
ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
ip->exclusion = le16_to_cpu(ix.exclusion);
ip->layer_type = le16_to_cpu(ix.layer_type);
ip->exclusion_group = le16_to_cpu(ix.exclusion_group);
ip->effect1 = ix.effect1;
ip->effect1_depth = ix.effect1_depth;
ip->effect2 = ix.effect2;
ip->effect2_depth = ix.effect2_depth;
/* copy layers */
prev_lp = NULL;
while (len > 0) {
if (len < (long)sizeof(iwffff_xlayer_t)) {
snd_seq_iwffff_instr_free(ops, ip, atomic);
return -EINVAL;
}
if (copy_from_user(&lx, instr_data, sizeof(lx)))
return -EFAULT;
instr_data += sizeof(lx);
len -= sizeof(lx);
if (lx.stype != IWFFFF_STRU_LAYER) {
snd_seq_iwffff_instr_free(ops, ip, atomic);
return -EINVAL;
}
lp = kcalloc(1, sizeof(*lp), gfp_mask);
if (lp == NULL) {
snd_seq_iwffff_instr_free(ops, ip, atomic);
return -ENOMEM;
}
if (prev_lp) {
prev_lp->next = lp;
} else {
ip->layer = lp;
}
prev_lp = lp;
lp->flags = lx.flags;
lp->velocity_mode = lx.velocity_mode;
lp->layer_event = lx.layer_event;
lp->low_range = lx.low_range;
lp->high_range = lx.high_range;
lp->pan = lx.pan;
lp->pan_freq_scale = lx.pan_freq_scale;
lp->attenuation = lx.attenuation;
snd_seq_iwffff_copy_lfo_from_stream(&lp->tremolo, &lx.tremolo);
snd_seq_iwffff_copy_lfo_from_stream(&lp->vibrato, &lx.vibrato);
lp->freq_scale = le16_to_cpu(lx.freq_scale);
lp->freq_center = lx.freq_center;
err = snd_seq_iwffff_copy_env_from_stream(IWFFFF_STRU_ENV_RECP,
lp,
&lp->penv, &lx.penv,
&instr_data, &len,
gfp_mask);
if (err < 0) {
snd_seq_iwffff_instr_free(ops, ip, atomic);
return err;
}
err = snd_seq_iwffff_copy_env_from_stream(IWFFFF_STRU_ENV_RECV,
lp,
&lp->venv, &lx.venv,
&instr_data, &len,
gfp_mask);
if (err < 0) {
snd_seq_iwffff_instr_free(ops, ip, atomic);
return err;
}
while (len > (long)sizeof(__u32)) {
__u32 stype;
if (copy_from_user(&stype, instr_data, sizeof(stype)))
return -EFAULT;
if (stype != IWFFFF_STRU_WAVE)
break;
err = snd_seq_iwffff_copy_wave_from_stream(ops,
lp,
&instr_data,
&len,
atomic);
if (err < 0) {
snd_seq_iwffff_instr_free(ops, ip, atomic);
return err;
}
}
}
return 0;
}
static void snd_seq_iwffff_copy_lfo_to_stream(iwffff_xlfo_t *fx,
iwffff_lfo_t *fp)
{
fx->freq = cpu_to_le16(fp->freq);
fx->depth = cpu_to_le16(fp->depth);
fx->sweep = cpu_to_le16(fp->sweep);
fp->shape = fx->shape;
fp->delay = fx->delay;
}
static int snd_seq_iwffff_copy_env_to_stream(__u32 req_stype,
iwffff_layer_t *lp,
iwffff_xenv_t *ex,
iwffff_env_t *ep,
char __user **data,
long *len)
{
iwffff_env_record_t *rp;
iwffff_xenv_record_t rx;
iwffff_env_point_t *pp;
iwffff_xenv_point_t px;
int points_size, idx;
ex->flags = ep->flags;
ex->mode = ep->mode;
ex->index = ep->index;
for (rp = ep->record; rp; rp = rp->next) {
if (*len < (long)sizeof(rx))
return -ENOMEM;
memset(&rx, 0, sizeof(rx));
rx.stype = req_stype;
rx.nattack = cpu_to_le16(rp->nattack);
rx.nrelease = cpu_to_le16(rp->nrelease);
rx.sustain_offset = cpu_to_le16(rp->sustain_offset);
rx.sustain_rate = cpu_to_le16(rp->sustain_rate);
rx.release_rate = cpu_to_le16(rp->release_rate);
rx.hirange = cpu_to_le16(rp->hirange);
if (copy_to_user(*data, &rx, sizeof(rx)))
return -EFAULT;
*data += sizeof(rx);
*len -= sizeof(rx);
points_size = (rp->nattack + rp->nrelease) * 2 * sizeof(__u16);
if (*len < points_size)
return -ENOMEM;
pp = (iwffff_env_point_t *)(rp + 1);
for (idx = 0; idx < rp->nattack + rp->nrelease; idx++) {
px.offset = cpu_to_le16(pp->offset);
px.rate = cpu_to_le16(pp->rate);
if (copy_to_user(*data, &px, sizeof(px)))
return -EFAULT;
*data += sizeof(px);
*len -= sizeof(px);
}
}
return 0;
}
static int snd_seq_iwffff_copy_wave_to_stream(snd_iwffff_ops_t *ops,
iwffff_layer_t *lp,
char __user **data,
long *len,
int atomic)
{
iwffff_wave_t *wp;
iwffff_xwave_t xp;
int err;
unsigned int real_size;
for (wp = lp->wave; wp; wp = wp->next) {
if (*len < (long)sizeof(xp))
return -ENOMEM;
memset(&xp, 0, sizeof(xp));
xp.stype = IWFFFF_STRU_WAVE;
xp.share_id[0] = cpu_to_le32(wp->share_id[0]);
xp.share_id[1] = cpu_to_le32(wp->share_id[1]);
xp.share_id[2] = cpu_to_le32(wp->share_id[2]);
xp.share_id[3] = cpu_to_le32(wp->share_id[3]);
xp.format = cpu_to_le32(wp->format);
if (wp->format & IWFFFF_WAVE_ROM)
xp.offset = cpu_to_le32(wp->address.memory);
xp.size = cpu_to_le32(wp->size);
xp.start = cpu_to_le32(wp->start);
xp.loop_start = cpu_to_le32(wp->loop_start);
xp.loop_end = cpu_to_le32(wp->loop_end);
xp.loop_repeat = cpu_to_le32(wp->loop_repeat);
xp.sample_ratio = cpu_to_le32(wp->sample_ratio);
xp.attenuation = wp->attenuation;
xp.low_note = wp->low_note;
xp.high_note = wp->high_note;
if (copy_to_user(*data, &xp, sizeof(xp)))
return -EFAULT;
*data += sizeof(xp);
*len -= sizeof(xp);
real_size = snd_seq_iwffff_size(wp->size, wp->format);
if (!(wp->format & IWFFFF_WAVE_ROM)) {
if (*len < (long)real_size)
return -ENOMEM;
}
if (ops->get_sample) {
err = ops->get_sample(ops->private_data, wp,
*data, real_size, atomic);
if (err < 0)
return err;
}
if (!(wp->format & IWFFFF_WAVE_ROM)) {
*data += real_size;
*len -= real_size;
}
}
return 0;
}
static int snd_seq_iwffff_get(void *private_data, snd_seq_kinstr_t *instr,
char __user *instr_data, long len, int atomic, int cmd)
{
snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
iwffff_instrument_t *ip;
iwffff_xinstrument_t ix;
iwffff_layer_t *lp;
iwffff_xlayer_t lx;
char __user *layer_instr_data;
int err;
if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
return -EINVAL;
if (len < (long)sizeof(ix))
return -ENOMEM;
memset(&ix, 0, sizeof(ix));
ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
ix.stype = IWFFFF_STRU_INSTR;
ix.exclusion = cpu_to_le16(ip->exclusion);
ix.layer_type = cpu_to_le16(ip->layer_type);
ix.exclusion_group = cpu_to_le16(ip->exclusion_group);
ix.effect1 = cpu_to_le16(ip->effect1);
ix.effect1_depth = cpu_to_le16(ip->effect1_depth);
ix.effect2 = ip->effect2;
ix.effect2_depth = ip->effect2_depth;
if (copy_to_user(instr_data, &ix, sizeof(ix)))
return -EFAULT;
instr_data += sizeof(ix);
len -= sizeof(ix);
for (lp = ip->layer; lp; lp = lp->next) {
if (len < (long)sizeof(lx))
return -ENOMEM;
memset(&lx, 0, sizeof(lx));
lx.stype = IWFFFF_STRU_LAYER;
lx.flags = lp->flags;
lx.velocity_mode = lp->velocity_mode;
lx.layer_event = lp->layer_event;
lx.low_range = lp->low_range;
lx.high_range = lp->high_range;
lx.pan = lp->pan;
lx.pan_freq_scale = lp->pan_freq_scale;
lx.attenuation = lp->attenuation;
snd_seq_iwffff_copy_lfo_to_stream(&lx.tremolo, &lp->tremolo);
snd_seq_iwffff_copy_lfo_to_stream(&lx.vibrato, &lp->vibrato);
layer_instr_data = instr_data;
instr_data += sizeof(lx);
len -= sizeof(lx);
err = snd_seq_iwffff_copy_env_to_stream(IWFFFF_STRU_ENV_RECP,
lp,
&lx.penv, &lp->penv,
&instr_data, &len);
if (err < 0)
return err;
err = snd_seq_iwffff_copy_env_to_stream(IWFFFF_STRU_ENV_RECV,
lp,
&lx.venv, &lp->venv,
&instr_data, &len);
if (err < 0)
return err;
/* layer structure updating is now finished */
if (copy_to_user(layer_instr_data, &lx, sizeof(lx)))
return -EFAULT;
err = snd_seq_iwffff_copy_wave_to_stream(ops,
lp,
&instr_data,
&len,
atomic);
if (err < 0)
return err;
}
return 0;
}
static long snd_seq_iwffff_env_size_in_stream(iwffff_env_t *ep)
{
long result = 0;
iwffff_env_record_t *rp;
for (rp = ep->record; rp; rp = rp->next) {
result += sizeof(iwffff_xenv_record_t);
result += (rp->nattack + rp->nrelease) * 2 * sizeof(__u16);
}
return 0;
}
static long snd_seq_iwffff_wave_size_in_stream(iwffff_layer_t *lp)
{
long result = 0;
iwffff_wave_t *wp;
for (wp = lp->wave; wp; wp = wp->next) {
result += sizeof(iwffff_xwave_t);
if (!(wp->format & IWFFFF_WAVE_ROM))
result += wp->size;
}
return result;
}
static int snd_seq_iwffff_get_size(void *private_data, snd_seq_kinstr_t *instr,
long *size)
{
long result;
iwffff_instrument_t *ip;
iwffff_layer_t *lp;
*size = 0;
ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
result = sizeof(iwffff_xinstrument_t);
for (lp = ip->layer; lp; lp = lp->next) {
result += sizeof(iwffff_xlayer_t);
result += snd_seq_iwffff_env_size_in_stream(&lp->penv);
result += snd_seq_iwffff_env_size_in_stream(&lp->venv);
result += snd_seq_iwffff_wave_size_in_stream(lp);
}
*size = result;
return 0;
}
static int snd_seq_iwffff_remove(void *private_data,
snd_seq_kinstr_t *instr,
int atomic)
{
snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
iwffff_instrument_t *ip;
ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
snd_seq_iwffff_instr_free(ops, ip, atomic);
return 0;
}
static void snd_seq_iwffff_notify(void *private_data,
snd_seq_kinstr_t *instr,
int what)
{
snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
if (ops->notify)
ops->notify(ops->private_data, instr, what);
}
int snd_seq_iwffff_init(snd_iwffff_ops_t *ops,
void *private_data,
snd_seq_kinstr_ops_t *next)
{
memset(ops, 0, sizeof(*ops));
ops->private_data = private_data;
ops->kops.private_data = ops;
ops->kops.add_len = sizeof(iwffff_instrument_t);
ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_INTERWAVE;
ops->kops.put = snd_seq_iwffff_put;
ops->kops.get = snd_seq_iwffff_get;
ops->kops.get_size = snd_seq_iwffff_get_size;
ops->kops.remove = snd_seq_iwffff_remove;
ops->kops.notify = snd_seq_iwffff_notify;
ops->kops.next = next;
return 0;
}
/*
* Init part
*/
static int __init alsa_ainstr_iw_init(void)
{
return 0;
}
static void __exit alsa_ainstr_iw_exit(void)
{
}
module_init(alsa_ainstr_iw_init)
module_exit(alsa_ainstr_iw_exit)
EXPORT_SYMBOL(snd_seq_iwffff_init);

View File

@@ -0,0 +1,215 @@
/*
* Simple (MOD player) - Instrument routines
* Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/ainstr_simple.h>
#include <sound/initval.h>
#include <asm/uaccess.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture Simple Instrument support.");
MODULE_LICENSE("GPL");
static unsigned int snd_seq_simple_size(unsigned int size, unsigned int format)
{
unsigned int result = size;
if (format & SIMPLE_WAVE_16BIT)
result <<= 1;
if (format & SIMPLE_WAVE_STEREO)
result <<= 1;
return result;
}
static void snd_seq_simple_instr_free(snd_simple_ops_t *ops,
simple_instrument_t *ip,
int atomic)
{
if (ops->remove_sample)
ops->remove_sample(ops->private_data, ip, atomic);
}
static int snd_seq_simple_put(void *private_data, snd_seq_kinstr_t *instr,
char __user *instr_data, long len,
int atomic, int cmd)
{
snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
simple_instrument_t *ip;
simple_xinstrument_t ix;
int err, gfp_mask;
unsigned int real_size;
if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
return -EINVAL;
gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
/* copy instrument data */
if (len < (long)sizeof(ix))
return -EINVAL;
if (copy_from_user(&ix, instr_data, sizeof(ix)))
return -EFAULT;
if (ix.stype != SIMPLE_STRU_INSTR)
return -EINVAL;
instr_data += sizeof(ix);
len -= sizeof(ix);
ip = (simple_instrument_t *)KINSTR_DATA(instr);
ip->share_id[0] = le32_to_cpu(ix.share_id[0]);
ip->share_id[1] = le32_to_cpu(ix.share_id[1]);
ip->share_id[2] = le32_to_cpu(ix.share_id[2]);
ip->share_id[3] = le32_to_cpu(ix.share_id[3]);
ip->format = le32_to_cpu(ix.format);
ip->size = le32_to_cpu(ix.size);
ip->start = le32_to_cpu(ix.start);
ip->loop_start = le32_to_cpu(ix.loop_start);
ip->loop_end = le32_to_cpu(ix.loop_end);
ip->loop_repeat = le16_to_cpu(ix.loop_repeat);
ip->effect1 = ix.effect1;
ip->effect1_depth = ix.effect1_depth;
ip->effect2 = ix.effect2;
ip->effect2_depth = ix.effect2_depth;
real_size = snd_seq_simple_size(ip->size, ip->format);
if (len < (long)real_size)
return -EINVAL;
if (ops->put_sample) {
err = ops->put_sample(ops->private_data, ip,
instr_data, real_size, atomic);
if (err < 0)
return err;
}
return 0;
}
static int snd_seq_simple_get(void *private_data, snd_seq_kinstr_t *instr,
char __user *instr_data, long len,
int atomic, int cmd)
{
snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
simple_instrument_t *ip;
simple_xinstrument_t ix;
int err;
unsigned int real_size;
if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
return -EINVAL;
if (len < (long)sizeof(ix))
return -ENOMEM;
memset(&ix, 0, sizeof(ix));
ip = (simple_instrument_t *)KINSTR_DATA(instr);
ix.stype = SIMPLE_STRU_INSTR;
ix.share_id[0] = cpu_to_le32(ip->share_id[0]);
ix.share_id[1] = cpu_to_le32(ip->share_id[1]);
ix.share_id[2] = cpu_to_le32(ip->share_id[2]);
ix.share_id[3] = cpu_to_le32(ip->share_id[3]);
ix.format = cpu_to_le32(ip->format);
ix.size = cpu_to_le32(ip->size);
ix.start = cpu_to_le32(ip->start);
ix.loop_start = cpu_to_le32(ip->loop_start);
ix.loop_end = cpu_to_le32(ip->loop_end);
ix.loop_repeat = cpu_to_le32(ip->loop_repeat);
ix.effect1 = cpu_to_le16(ip->effect1);
ix.effect1_depth = cpu_to_le16(ip->effect1_depth);
ix.effect2 = ip->effect2;
ix.effect2_depth = ip->effect2_depth;
if (copy_to_user(instr_data, &ix, sizeof(ix)))
return -EFAULT;
instr_data += sizeof(ix);
len -= sizeof(ix);
real_size = snd_seq_simple_size(ip->size, ip->format);
if (len < (long)real_size)
return -ENOMEM;
if (ops->get_sample) {
err = ops->get_sample(ops->private_data, ip,
instr_data, real_size, atomic);
if (err < 0)
return err;
}
return 0;
}
static int snd_seq_simple_get_size(void *private_data, snd_seq_kinstr_t *instr,
long *size)
{
simple_instrument_t *ip;
ip = (simple_instrument_t *)KINSTR_DATA(instr);
*size = sizeof(simple_xinstrument_t) + snd_seq_simple_size(ip->size, ip->format);
return 0;
}
static int snd_seq_simple_remove(void *private_data,
snd_seq_kinstr_t *instr,
int atomic)
{
snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
simple_instrument_t *ip;
ip = (simple_instrument_t *)KINSTR_DATA(instr);
snd_seq_simple_instr_free(ops, ip, atomic);
return 0;
}
static void snd_seq_simple_notify(void *private_data,
snd_seq_kinstr_t *instr,
int what)
{
snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
if (ops->notify)
ops->notify(ops->private_data, instr, what);
}
int snd_seq_simple_init(snd_simple_ops_t *ops,
void *private_data,
snd_seq_kinstr_ops_t *next)
{
memset(ops, 0, sizeof(*ops));
ops->private_data = private_data;
ops->kops.private_data = ops;
ops->kops.add_len = sizeof(simple_instrument_t);
ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_SIMPLE;
ops->kops.put = snd_seq_simple_put;
ops->kops.get = snd_seq_simple_get;
ops->kops.get_size = snd_seq_simple_get_size;
ops->kops.remove = snd_seq_simple_remove;
ops->kops.notify = snd_seq_simple_notify;
ops->kops.next = next;
return 0;
}
/*
* Init part
*/
static int __init alsa_ainstr_simple_init(void)
{
return 0;
}
static void __exit alsa_ainstr_simple_exit(void)
{
}
module_init(alsa_ainstr_simple_init)
module_exit(alsa_ainstr_simple_exit)
EXPORT_SYMBOL(snd_seq_simple_init);

View File

@@ -0,0 +1,10 @@
#
# Makefile for ALSA
# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
#
snd-seq-oss-objs := seq_oss.o seq_oss_init.o seq_oss_timer.o seq_oss_ioctl.o \
seq_oss_event.o seq_oss_rw.o seq_oss_synth.o \
seq_oss_midi.o seq_oss_readq.o seq_oss_writeq.o
obj-$(CONFIG_SND_SEQUENCER) += snd-seq-oss.o

View File

@@ -0,0 +1,317 @@
/*
* OSS compatible sequencer driver
*
* registration of device and proc
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/smp_lock.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <sound/initval.h>
#include "seq_oss_device.h"
#include "seq_oss_synth.h"
/*
* module option
*/
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("OSS-compatible sequencer module");
MODULE_LICENSE("GPL");
/* Takashi says this is really only for sound-service-0-, but this is OK. */
MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_SEQUENCER);
MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MUSIC);
#ifdef SNDRV_SEQ_OSS_DEBUG
module_param(seq_oss_debug, int, 0644);
MODULE_PARM_DESC(seq_oss_debug, "debug option");
int seq_oss_debug = 0;
#endif
/*
* prototypes
*/
static int register_device(void);
static void unregister_device(void);
static int register_proc(void);
static void unregister_proc(void);
static int odev_open(struct inode *inode, struct file *file);
static int odev_release(struct inode *inode, struct file *file);
static ssize_t odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset);
static ssize_t odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset);
static long odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
static unsigned int odev_poll(struct file *file, poll_table * wait);
#ifdef CONFIG_PROC_FS
static void info_read(snd_info_entry_t *entry, snd_info_buffer_t *buf);
#endif
/*
* module interface
*/
static int __init alsa_seq_oss_init(void)
{
int rc;
static snd_seq_dev_ops_t ops = {
snd_seq_oss_synth_register,
snd_seq_oss_synth_unregister,
};
snd_seq_autoload_lock();
if ((rc = register_device()) < 0)
goto error;
if ((rc = register_proc()) < 0) {
unregister_device();
goto error;
}
if ((rc = snd_seq_oss_create_client()) < 0) {
unregister_proc();
unregister_device();
goto error;
}
if ((rc = snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OSS, &ops,
sizeof(snd_seq_oss_reg_t))) < 0) {
snd_seq_oss_delete_client();
unregister_proc();
unregister_device();
goto error;
}
/* success */
snd_seq_oss_synth_init();
error:
snd_seq_autoload_unlock();
return rc;
}
static void __exit alsa_seq_oss_exit(void)
{
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OSS);
snd_seq_oss_delete_client();
unregister_proc();
unregister_device();
}
module_init(alsa_seq_oss_init)
module_exit(alsa_seq_oss_exit)
/*
* ALSA minor device interface
*/
static DECLARE_MUTEX(register_mutex);
static int
odev_open(struct inode *inode, struct file *file)
{
int level, rc;
if (iminor(inode) == SNDRV_MINOR_OSS_MUSIC)
level = SNDRV_SEQ_OSS_MODE_MUSIC;
else
level = SNDRV_SEQ_OSS_MODE_SYNTH;
down(&register_mutex);
rc = snd_seq_oss_open(file, level);
up(&register_mutex);
return rc;
}
static int
odev_release(struct inode *inode, struct file *file)
{
seq_oss_devinfo_t *dp;
if ((dp = file->private_data) == NULL)
return 0;
snd_seq_oss_drain_write(dp);
down(&register_mutex);
snd_seq_oss_release(dp);
up(&register_mutex);
return 0;
}
static ssize_t
odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
seq_oss_devinfo_t *dp;
dp = file->private_data;
snd_assert(dp != NULL, return -EIO);
return snd_seq_oss_read(dp, buf, count);
}
static ssize_t
odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
seq_oss_devinfo_t *dp;
dp = file->private_data;
snd_assert(dp != NULL, return -EIO);
return snd_seq_oss_write(dp, buf, count, file);
}
static long
odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
seq_oss_devinfo_t *dp;
dp = file->private_data;
snd_assert(dp != NULL, return -EIO);
return snd_seq_oss_ioctl(dp, cmd, arg);
}
#ifdef CONFIG_COMPAT
#define odev_ioctl_compat odev_ioctl
#else
#define odev_ioctl_compat NULL
#endif
static unsigned int
odev_poll(struct file *file, poll_table * wait)
{
seq_oss_devinfo_t *dp;
dp = file->private_data;
snd_assert(dp != NULL, return 0);
return snd_seq_oss_poll(dp, file, wait);
}
/*
* registration of sequencer minor device
*/
static struct file_operations seq_oss_f_ops =
{
.owner = THIS_MODULE,
.read = odev_read,
.write = odev_write,
.open = odev_open,
.release = odev_release,
.poll = odev_poll,
.unlocked_ioctl = odev_ioctl,
.compat_ioctl = odev_ioctl_compat,
};
static snd_minor_t seq_oss_reg = {
.comment = "sequencer",
.f_ops = &seq_oss_f_ops,
};
static int __init
register_device(void)
{
int rc;
down(&register_mutex);
if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER,
NULL, 0,
&seq_oss_reg,
SNDRV_SEQ_OSS_DEVNAME)) < 0) {
snd_printk(KERN_ERR "can't register device seq\n");
up(&register_mutex);
return rc;
}
if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC,
NULL, 0,
&seq_oss_reg,
SNDRV_SEQ_OSS_DEVNAME)) < 0) {
snd_printk(KERN_ERR "can't register device music\n");
snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0);
up(&register_mutex);
return rc;
}
debug_printk(("device registered\n"));
up(&register_mutex);
return 0;
}
static void
unregister_device(void)
{
down(&register_mutex);
debug_printk(("device unregistered\n"));
if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, NULL, 0) < 0)
snd_printk(KERN_ERR "error unregister device music\n");
if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0) < 0)
snd_printk(KERN_ERR "error unregister device seq\n");
up(&register_mutex);
}
/*
* /proc interface
*/
#ifdef CONFIG_PROC_FS
static snd_info_entry_t *info_entry;
static void
info_read(snd_info_entry_t *entry, snd_info_buffer_t *buf)
{
down(&register_mutex);
snd_iprintf(buf, "OSS sequencer emulation version %s\n", SNDRV_SEQ_OSS_VERSION_STR);
snd_seq_oss_system_info_read(buf);
snd_seq_oss_synth_info_read(buf);
snd_seq_oss_midi_info_read(buf);
up(&register_mutex);
}
#endif /* CONFIG_PROC_FS */
static int __init
register_proc(void)
{
#ifdef CONFIG_PROC_FS
snd_info_entry_t *entry;
entry = snd_info_create_module_entry(THIS_MODULE, SNDRV_SEQ_OSS_PROCNAME, snd_seq_root);
if (entry == NULL)
return -ENOMEM;
entry->content = SNDRV_INFO_CONTENT_TEXT;
entry->private_data = NULL;
entry->c.text.read_size = 1024;
entry->c.text.read = info_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
return -ENOMEM;
}
info_entry = entry;
#endif
return 0;
}
static void
unregister_proc(void)
{
#ifdef CONFIG_PROC_FS
if (info_entry)
snd_info_unregister(info_entry);
info_entry = NULL;
#endif
}

View File

@@ -0,0 +1,198 @@
/*
* OSS compatible sequencer driver
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __SEQ_OSS_DEVICE_H
#define __SEQ_OSS_DEVICE_H
#include <sound/driver.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <sound/core.h>
#include <sound/seq_oss.h>
#include <sound/rawmidi.h>
#include <sound/seq_kernel.h>
#include <sound/info.h>
/* enable debug print */
#define SNDRV_SEQ_OSS_DEBUG
/* max. applications */
#define SNDRV_SEQ_OSS_MAX_CLIENTS 16
#define SNDRV_SEQ_OSS_MAX_SYNTH_DEVS 16
#define SNDRV_SEQ_OSS_MAX_MIDI_DEVS 32
/* version */
#define SNDRV_SEQ_OSS_MAJOR_VERSION 0
#define SNDRV_SEQ_OSS_MINOR_VERSION 1
#define SNDRV_SEQ_OSS_TINY_VERSION 8
#define SNDRV_SEQ_OSS_VERSION_STR "0.1.8"
/* device and proc interface name */
#define SNDRV_SEQ_OSS_DEVNAME "seq_oss"
#define SNDRV_SEQ_OSS_PROCNAME "oss"
/*
* type definitions
*/
typedef struct seq_oss_devinfo_t seq_oss_devinfo_t;
typedef struct seq_oss_writeq_t seq_oss_writeq_t;
typedef struct seq_oss_readq_t seq_oss_readq_t;
typedef struct seq_oss_timer_t seq_oss_timer_t;
typedef struct seq_oss_synthinfo_t seq_oss_synthinfo_t;
typedef struct seq_oss_synth_sysex_t seq_oss_synth_sysex_t;
typedef struct seq_oss_chinfo_t seq_oss_chinfo_t;
typedef unsigned int reltime_t;
typedef unsigned int abstime_t;
typedef union evrec_t evrec_t;
/*
* synthesizer channel information
*/
struct seq_oss_chinfo_t {
int note, vel;
};
/*
* synthesizer information
*/
struct seq_oss_synthinfo_t {
snd_seq_oss_arg_t arg;
seq_oss_chinfo_t *ch;
seq_oss_synth_sysex_t *sysex;
int nr_voices;
int opened;
int is_midi;
int midi_mapped;
};
/*
* sequencer client information
*/
struct seq_oss_devinfo_t {
int index; /* application index */
int cseq; /* sequencer client number */
int port; /* sequencer port number */
int queue; /* sequencer queue number */
snd_seq_addr_t addr; /* address of this device */
int seq_mode; /* sequencer mode */
int file_mode; /* file access */
/* midi device table */
int max_mididev;
/* synth device table */
int max_synthdev;
seq_oss_synthinfo_t synths[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
int synth_opened;
/* output queue */
seq_oss_writeq_t *writeq;
/* midi input queue */
seq_oss_readq_t *readq;
/* timer */
seq_oss_timer_t *timer;
};
/*
* function prototypes
*/
/* create/delete OSS sequencer client */
int snd_seq_oss_create_client(void);
int snd_seq_oss_delete_client(void);
/* device file interface */
int snd_seq_oss_open(struct file *file, int level);
void snd_seq_oss_release(seq_oss_devinfo_t *dp);
int snd_seq_oss_ioctl(seq_oss_devinfo_t *dp, unsigned int cmd, unsigned long arg);
int snd_seq_oss_read(seq_oss_devinfo_t *dev, char __user *buf, int count);
int snd_seq_oss_write(seq_oss_devinfo_t *dp, const char __user *buf, int count, struct file *opt);
unsigned int snd_seq_oss_poll(seq_oss_devinfo_t *dp, struct file *file, poll_table * wait);
void snd_seq_oss_reset(seq_oss_devinfo_t *dp);
void snd_seq_oss_drain_write(seq_oss_devinfo_t *dp);
/* */
void snd_seq_oss_process_queue(seq_oss_devinfo_t *dp, abstime_t time);
/* proc interface */
void snd_seq_oss_system_info_read(snd_info_buffer_t *buf);
void snd_seq_oss_midi_info_read(snd_info_buffer_t *buf);
void snd_seq_oss_synth_info_read(snd_info_buffer_t *buf);
void snd_seq_oss_readq_info_read(seq_oss_readq_t *q, snd_info_buffer_t *buf);
/* file mode macros */
#define is_read_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_READ)
#define is_write_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_WRITE)
#define is_nonblock_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_NONBLOCK)
/* dispatch event */
inline static int
snd_seq_oss_dispatch(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int atomic, int hop)
{
return snd_seq_kernel_client_dispatch(dp->cseq, ev, atomic, hop);
}
/* ioctl */
inline static int
snd_seq_oss_control(seq_oss_devinfo_t *dp, unsigned int type, void *arg)
{
return snd_seq_kernel_client_ctl(dp->cseq, type, arg);
}
/* fill the addresses in header */
inline static void
snd_seq_oss_fill_addr(seq_oss_devinfo_t *dp, snd_seq_event_t *ev,
int dest_client, int dest_port)
{
ev->queue = dp->queue;
ev->source = dp->addr;
ev->dest.client = dest_client;
ev->dest.port = dest_port;
}
/* misc. functions for proc interface */
char *enabled_str(int bool);
/* for debug */
#ifdef SNDRV_SEQ_OSS_DEBUG
extern int seq_oss_debug;
#define debug_printk(x) do { if (seq_oss_debug > 0) snd_printk x; } while (0)
#else
#define debug_printk(x) /**/
#endif
#endif /* __SEQ_OSS_DEVICE_H */

View File

@@ -0,0 +1,447 @@
/*
* OSS compatible sequencer driver
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "seq_oss_device.h"
#include "seq_oss_synth.h"
#include "seq_oss_midi.h"
#include "seq_oss_event.h"
#include "seq_oss_timer.h"
#include <sound/seq_oss_legacy.h>
#include "seq_oss_readq.h"
#include "seq_oss_writeq.h"
/*
* prototypes
*/
static int extended_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev);
static int chn_voice_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
static int chn_common_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
static int timing_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
static int local_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
static int old_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev);
static int note_on_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev);
static int note_off_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev);
static int set_note_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int note, int vel, snd_seq_event_t *ev);
static int set_control_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int param, int val, snd_seq_event_t *ev);
static int set_echo_event(seq_oss_devinfo_t *dp, evrec_t *rec, snd_seq_event_t *ev);
/*
* convert an OSS event to ALSA event
* return 0 : enqueued
* non-zero : invalid - ignored
*/
int
snd_seq_oss_process_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
{
switch (q->s.code) {
case SEQ_EXTENDED:
return extended_event(dp, q, ev);
case EV_CHN_VOICE:
return chn_voice_event(dp, q, ev);
case EV_CHN_COMMON:
return chn_common_event(dp, q, ev);
case EV_TIMING:
return timing_event(dp, q, ev);
case EV_SEQ_LOCAL:
return local_event(dp, q, ev);
case EV_SYSEX:
return snd_seq_oss_synth_sysex(dp, q->x.dev, q->x.buf, ev);
case SEQ_MIDIPUTC:
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
return -EINVAL;
/* put a midi byte */
if (! is_write_mode(dp->file_mode))
break;
if (snd_seq_oss_midi_open(dp, q->s.dev, SNDRV_SEQ_OSS_FILE_WRITE))
break;
if (snd_seq_oss_midi_filemode(dp, q->s.dev) & SNDRV_SEQ_OSS_FILE_WRITE)
return snd_seq_oss_midi_putc(dp, q->s.dev, q->s.parm1, ev);
break;
case SEQ_ECHO:
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
return -EINVAL;
return set_echo_event(dp, q, ev);
case SEQ_PRIVATE:
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
return -EINVAL;
return snd_seq_oss_synth_raw_event(dp, q->c[1], q->c, ev);
default:
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
return -EINVAL;
return old_event(dp, q, ev);
}
return -EINVAL;
}
/* old type events: mode1 only */
static int
old_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
{
switch (q->s.code) {
case SEQ_NOTEOFF:
return note_off_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
case SEQ_NOTEON:
return note_on_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
case SEQ_WAIT:
/* skip */
break;
case SEQ_PGMCHANGE:
return set_control_event(dp, 0, SNDRV_SEQ_EVENT_PGMCHANGE,
q->n.chn, 0, q->n.note, ev);
case SEQ_SYNCTIMER:
return snd_seq_oss_timer_reset(dp->timer);
}
return -EINVAL;
}
/* 8bytes extended event: mode1 only */
static int
extended_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
{
int val;
switch (q->e.cmd) {
case SEQ_NOTEOFF:
return note_off_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
case SEQ_NOTEON:
return note_on_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
case SEQ_PGMCHANGE:
return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
q->e.chn, 0, q->e.p1, ev);
case SEQ_AFTERTOUCH:
return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CHANPRESS,
q->e.chn, 0, q->e.p1, ev);
case SEQ_BALANCE:
/* convert -128:127 to 0:127 */
val = (char)q->e.p1;
val = (val + 128) / 2;
return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CONTROLLER,
q->e.chn, CTL_PAN, val, ev);
case SEQ_CONTROLLER:
val = ((short)q->e.p3 << 8) | (short)q->e.p2;
switch (q->e.p1) {
case CTRL_PITCH_BENDER: /* SEQ1 V2 control */
/* -0x2000:0x1fff */
return set_control_event(dp, q->e.dev,
SNDRV_SEQ_EVENT_PITCHBEND,
q->e.chn, 0, val, ev);
case CTRL_PITCH_BENDER_RANGE:
/* conversion: 100/semitone -> 128/semitone */
return set_control_event(dp, q->e.dev,
SNDRV_SEQ_EVENT_REGPARAM,
q->e.chn, 0, val*128/100, ev);
default:
return set_control_event(dp, q->e.dev,
SNDRV_SEQ_EVENT_CONTROL14,
q->e.chn, q->e.p1, val, ev);
}
case SEQ_VOLMODE:
return snd_seq_oss_synth_raw_event(dp, q->e.dev, q->c, ev);
}
return -EINVAL;
}
/* channel voice events: mode1 and 2 */
static int
chn_voice_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
{
if (q->v.chn >= 32)
return -EINVAL;
switch (q->v.cmd) {
case MIDI_NOTEON:
return note_on_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
case MIDI_NOTEOFF:
return note_off_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
case MIDI_KEY_PRESSURE:
return set_note_event(dp, q->v.dev, SNDRV_SEQ_EVENT_KEYPRESS,
q->v.chn, q->v.note, q->v.parm, ev);
}
return -EINVAL;
}
/* channel common events: mode1 and 2 */
static int
chn_common_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
{
if (q->l.chn >= 32)
return -EINVAL;
switch (q->l.cmd) {
case MIDI_PGM_CHANGE:
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
q->l.chn, 0, q->l.p1, ev);
case MIDI_CTL_CHANGE:
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CONTROLLER,
q->l.chn, q->l.p1, q->l.val, ev);
case MIDI_PITCH_BEND:
/* conversion: 0:0x3fff -> -0x2000:0x1fff */
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PITCHBEND,
q->l.chn, 0, q->l.val - 8192, ev);
case MIDI_CHN_PRESSURE:
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CHANPRESS,
q->l.chn, 0, q->l.val, ev);
}
return -EINVAL;
}
/* timer events: mode1 and mode2 */
static int
timing_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
{
switch (q->t.cmd) {
case TMR_ECHO:
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
return set_echo_event(dp, q, ev);
else {
evrec_t tmp;
memset(&tmp, 0, sizeof(tmp));
/* XXX: only for little-endian! */
tmp.echo = (q->t.time << 8) | SEQ_ECHO;
return set_echo_event(dp, &tmp, ev);
}
case TMR_STOP:
if (dp->seq_mode)
return snd_seq_oss_timer_stop(dp->timer);
return 0;
case TMR_CONTINUE:
if (dp->seq_mode)
return snd_seq_oss_timer_continue(dp->timer);
return 0;
case TMR_TEMPO:
if (dp->seq_mode)
return snd_seq_oss_timer_tempo(dp->timer, q->t.time);
return 0;
}
return -EINVAL;
}
/* local events: mode1 and 2 */
static int
local_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
{
return -EINVAL;
}
/*
* process note-on event for OSS synth
* three different modes are available:
* - SNDRV_SEQ_OSS_PROCESS_EVENTS (for one-voice per channel mode)
* Accept note 255 as volume change.
* - SNDRV_SEQ_OSS_PASS_EVENTS
* Pass all events to lowlevel driver anyway
* - SNDRV_SEQ_OSS_PROCESS_KEYPRESS (mostly for Emu8000)
* Use key-pressure if note >= 128
*/
static int
note_on_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev)
{
seq_oss_synthinfo_t *info = &dp->synths[dev];
switch (info->arg.event_passing) {
case SNDRV_SEQ_OSS_PROCESS_EVENTS:
if (! info->ch || ch < 0 || ch >= info->nr_voices) {
/* pass directly */
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
}
if (note == 255 && info->ch[ch].note >= 0) {
/* volume control */
int type;
//if (! vel)
/* set volume to zero -- note off */
// type = SNDRV_SEQ_EVENT_NOTEOFF;
//else
if (info->ch[ch].vel)
/* sample already started -- volume change */
type = SNDRV_SEQ_EVENT_KEYPRESS;
else
/* sample not started -- start now */
type = SNDRV_SEQ_EVENT_NOTEON;
info->ch[ch].vel = vel;
return set_note_event(dp, dev, type, ch, info->ch[ch].note, vel, ev);
} else if (note >= 128)
return -EINVAL; /* invalid */
if (note != info->ch[ch].note && info->ch[ch].note >= 0)
/* note changed - note off at beginning */
set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, info->ch[ch].note, 0, ev);
/* set current status */
info->ch[ch].note = note;
info->ch[ch].vel = vel;
if (vel) /* non-zero velocity - start the note now */
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
return -EINVAL;
case SNDRV_SEQ_OSS_PASS_EVENTS:
/* pass the event anyway */
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
if (note >= 128) /* key pressure: shifted by 128 */
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_KEYPRESS, ch, note - 128, vel, ev);
else /* normal note-on event */
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
}
return -EINVAL;
}
/*
* process note-off event for OSS synth
*/
static int
note_off_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev)
{
seq_oss_synthinfo_t *info = &dp->synths[dev];
switch (info->arg.event_passing) {
case SNDRV_SEQ_OSS_PROCESS_EVENTS:
if (! info->ch || ch < 0 || ch >= info->nr_voices) {
/* pass directly */
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
}
if (info->ch[ch].note >= 0) {
note = info->ch[ch].note;
info->ch[ch].vel = 0;
info->ch[ch].note = -1;
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
}
return -EINVAL; /* invalid */
case SNDRV_SEQ_OSS_PASS_EVENTS:
case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
/* pass the event anyway */
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
}
return -EINVAL;
}
/*
* create a note event
*/
static int
set_note_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int note, int vel, snd_seq_event_t *ev)
{
if (! snd_seq_oss_synth_is_valid(dp, dev))
return -ENXIO;
ev->type = type;
snd_seq_oss_synth_addr(dp, dev, ev);
ev->data.note.channel = ch;
ev->data.note.note = note;
ev->data.note.velocity = vel;
return 0;
}
/*
* create a control event
*/
static int
set_control_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int param, int val, snd_seq_event_t *ev)
{
if (! snd_seq_oss_synth_is_valid(dp, dev))
return -ENXIO;
ev->type = type;
snd_seq_oss_synth_addr(dp, dev, ev);
ev->data.control.channel = ch;
ev->data.control.param = param;
ev->data.control.value = val;
return 0;
}
/*
* create an echo event
*/
static int
set_echo_event(seq_oss_devinfo_t *dp, evrec_t *rec, snd_seq_event_t *ev)
{
ev->type = SNDRV_SEQ_EVENT_ECHO;
/* echo back to itself */
snd_seq_oss_fill_addr(dp, ev, dp->addr.client, dp->addr.port);
memcpy(&ev->data, rec, LONG_EVENT_SIZE);
return 0;
}
/*
* event input callback from ALSA sequencer:
* the echo event is processed here.
*/
int
snd_seq_oss_event_input(snd_seq_event_t *ev, int direct, void *private_data,
int atomic, int hop)
{
seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private_data;
evrec_t *rec;
if (ev->type != SNDRV_SEQ_EVENT_ECHO)
return snd_seq_oss_midi_input(ev, direct, private_data);
if (ev->source.client != dp->cseq)
return 0; /* ignored */
rec = (evrec_t*)&ev->data;
if (rec->s.code == SEQ_SYNCTIMER) {
/* sync echo back */
snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time);
} else {
/* echo back event */
if (dp->readq == NULL)
return 0;
snd_seq_oss_readq_put_event(dp->readq, rec);
}
return 0;
}

View File

@@ -0,0 +1,112 @@
/*
* OSS compatible sequencer driver
*
* seq_oss_event.h - OSS event queue record
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __SEQ_OSS_EVENT_H
#define __SEQ_OSS_EVENT_H
#include "seq_oss_device.h"
#define SHORT_EVENT_SIZE 4
#define LONG_EVENT_SIZE 8
/* short event (4bytes) */
typedef struct evrec_short_t {
unsigned char code;
unsigned char parm1;
unsigned char dev;
unsigned char parm2;
} evrec_short_t;
/* short note events (4bytes) */
typedef struct evrec_note_t {
unsigned char code;
unsigned char chn;
unsigned char note;
unsigned char vel;
} evrec_note_t;
/* long timer events (8bytes) */
typedef struct evrec_timer_t {
unsigned char code;
unsigned char cmd;
unsigned char dummy1, dummy2;
unsigned int time;
} evrec_timer_t;
/* long extended events (8bytes) */
typedef struct evrec_extended_t {
unsigned char code;
unsigned char cmd;
unsigned char dev;
unsigned char chn;
unsigned char p1, p2, p3, p4;
} evrec_extended_t;
/* long channel events (8bytes) */
typedef struct evrec_long_t {
unsigned char code;
unsigned char dev;
unsigned char cmd;
unsigned char chn;
unsigned char p1, p2;
unsigned short val;
} evrec_long_t;
/* channel voice events (8bytes) */
typedef struct evrec_voice_t {
unsigned char code;
unsigned char dev;
unsigned char cmd;
unsigned char chn;
unsigned char note, parm;
unsigned short dummy;
} evrec_voice_t;
/* sysex events (8bytes) */
typedef struct evrec_sysex_t {
unsigned char code;
unsigned char dev;
unsigned char buf[6];
} evrec_sysex_t;
/* event record */
union evrec_t {
evrec_short_t s;
evrec_note_t n;
evrec_long_t l;
evrec_voice_t v;
evrec_timer_t t;
evrec_extended_t e;
evrec_sysex_t x;
unsigned int echo;
unsigned char c[LONG_EVENT_SIZE];
};
#define ev_is_long(ev) ((ev)->s.code >= 128)
#define ev_length(ev) ((ev)->s.code >= 128 ? LONG_EVENT_SIZE : SHORT_EVENT_SIZE)
int snd_seq_oss_process_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev);
int snd_seq_oss_process_timer_event(seq_oss_timer_t *rec, evrec_t *q);
int snd_seq_oss_event_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop);
#endif /* __SEQ_OSS_EVENT_H */

View File

@@ -0,0 +1,555 @@
/*
* OSS compatible sequencer driver
*
* open/close and reset interface
*
* Copyright (C) 1998-1999 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "seq_oss_device.h"
#include "seq_oss_synth.h"
#include "seq_oss_midi.h"
#include "seq_oss_writeq.h"
#include "seq_oss_readq.h"
#include "seq_oss_timer.h"
#include "seq_oss_event.h"
#include <linux/init.h>
#include <linux/moduleparam.h>
/*
* common variables
*/
static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN;
module_param(maxqlen, int, 0444);
MODULE_PARM_DESC(maxqlen, "maximum queue length");
static int system_client = -1; /* ALSA sequencer client number */
static int system_port = -1;
static int num_clients;
static seq_oss_devinfo_t *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS];
/*
* prototypes
*/
static int receive_announce(snd_seq_event_t *ev, int direct, void *private, int atomic, int hop);
static int translate_mode(struct file *file);
static int create_port(seq_oss_devinfo_t *dp);
static int delete_port(seq_oss_devinfo_t *dp);
static int alloc_seq_queue(seq_oss_devinfo_t *dp);
static int delete_seq_queue(int queue);
static void free_devinfo(void *private);
#define call_ctl(type,rec) snd_seq_kernel_client_ctl(system_client, type, rec)
/*
* create sequencer client for OSS sequencer
*/
int __init
snd_seq_oss_create_client(void)
{
int rc;
snd_seq_client_callback_t callback;
snd_seq_client_info_t *info;
snd_seq_port_info_t *port;
snd_seq_port_callback_t port_callback;
info = kmalloc(sizeof(*info), GFP_KERNEL);
port = kmalloc(sizeof(*port), GFP_KERNEL);
if (!info || !port) {
rc = -ENOMEM;
goto __error;
}
/* create ALSA client */
memset(&callback, 0, sizeof(callback));
callback.private_data = NULL;
callback.allow_input = 1;
callback.allow_output = 1;
rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS, &callback);
if (rc < 0)
goto __error;
system_client = rc;
debug_printk(("new client = %d\n", rc));
/* set client information */
memset(info, 0, sizeof(*info));
info->client = system_client;
info->type = KERNEL_CLIENT;
strcpy(info->name, "OSS sequencer");
rc = call_ctl(SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, info);
/* look up midi devices */
snd_seq_oss_midi_lookup_ports(system_client);
/* create annoucement receiver port */
memset(port, 0, sizeof(*port));
strcpy(port->name, "Receiver");
port->addr.client = system_client;
port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* receive only */
port->type = 0;
memset(&port_callback, 0, sizeof(port_callback));
/* don't set port_callback.owner here. otherwise the module counter
* is incremented and we can no longer release the module..
*/
port_callback.event_input = receive_announce;
port->kernel = &port_callback;
call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, port);
if ((system_port = port->addr.port) >= 0) {
snd_seq_port_subscribe_t subs;
memset(&subs, 0, sizeof(subs));
subs.sender.client = SNDRV_SEQ_CLIENT_SYSTEM;
subs.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
subs.dest.client = system_client;
subs.dest.port = system_port;
call_ctl(SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs);
}
rc = 0;
__error:
kfree(port);
kfree(info);
return rc;
}
/*
* receive annoucement from system port, and check the midi device
*/
static int
receive_announce(snd_seq_event_t *ev, int direct, void *private, int atomic, int hop)
{
snd_seq_port_info_t pinfo;
if (atomic)
return 0; /* it must not happen */
switch (ev->type) {
case SNDRV_SEQ_EVENT_PORT_START:
case SNDRV_SEQ_EVENT_PORT_CHANGE:
if (ev->data.addr.client == system_client)
break; /* ignore myself */
memset(&pinfo, 0, sizeof(pinfo));
pinfo.addr = ev->data.addr;
if (call_ctl(SNDRV_SEQ_IOCTL_GET_PORT_INFO, &pinfo) >= 0)
snd_seq_oss_midi_check_new_port(&pinfo);
break;
case SNDRV_SEQ_EVENT_PORT_EXIT:
if (ev->data.addr.client == system_client)
break; /* ignore myself */
snd_seq_oss_midi_check_exit_port(ev->data.addr.client,
ev->data.addr.port);
break;
}
return 0;
}
/*
* delete OSS sequencer client
*/
int
snd_seq_oss_delete_client(void)
{
if (system_client >= 0)
snd_seq_delete_kernel_client(system_client);
snd_seq_oss_midi_clear_all();
return 0;
}
/*
* open sequencer device
*/
int
snd_seq_oss_open(struct file *file, int level)
{
int i, rc;
seq_oss_devinfo_t *dp;
if ((dp = kcalloc(1, sizeof(*dp), GFP_KERNEL)) == NULL) {
snd_printk(KERN_ERR "can't malloc device info\n");
return -ENOMEM;
}
debug_printk(("oss_open: dp = %p\n", dp));
for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) {
if (client_table[i] == NULL)
break;
}
if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) {
snd_printk(KERN_ERR "too many applications\n");
kfree(dp);
return -ENOMEM;
}
dp->index = i;
dp->cseq = system_client;
dp->port = -1;
dp->queue = -1;
dp->readq = NULL;
dp->writeq = NULL;
/* look up synth and midi devices */
snd_seq_oss_synth_setup(dp);
snd_seq_oss_midi_setup(dp);
if (dp->synth_opened == 0 && dp->max_mididev == 0) {
/* snd_printk(KERN_ERR "no device found\n"); */
rc = -ENODEV;
goto _error;
}
/* create port */
debug_printk(("create new port\n"));
if ((rc = create_port(dp)) < 0) {
snd_printk(KERN_ERR "can't create port\n");
goto _error;
}
/* allocate queue */
debug_printk(("allocate queue\n"));
if ((rc = alloc_seq_queue(dp)) < 0)
goto _error;
/* set address */
dp->addr.client = dp->cseq;
dp->addr.port = dp->port;
/*dp->addr.queue = dp->queue;*/
/*dp->addr.channel = 0;*/
dp->seq_mode = level;
/* set up file mode */
dp->file_mode = translate_mode(file);
/* initialize read queue */
debug_printk(("initialize read queue\n"));
if (is_read_mode(dp->file_mode)) {
if ((dp->readq = snd_seq_oss_readq_new(dp, maxqlen)) == NULL) {
rc = -ENOMEM;
goto _error;
}
}
/* initialize write queue */
debug_printk(("initialize write queue\n"));
if (is_write_mode(dp->file_mode)) {
dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen);
if (dp->writeq == NULL) {
rc = -ENOMEM;
goto _error;
}
}
/* initialize timer */
debug_printk(("initialize timer\n"));
if ((dp->timer = snd_seq_oss_timer_new(dp)) == NULL) {
snd_printk(KERN_ERR "can't alloc timer\n");
rc = -ENOMEM;
goto _error;
}
debug_printk(("timer initialized\n"));
/* set private data pointer */
file->private_data = dp;
/* set up for mode2 */
if (level == SNDRV_SEQ_OSS_MODE_MUSIC)
snd_seq_oss_synth_setup_midi(dp);
else if (is_read_mode(dp->file_mode))
snd_seq_oss_midi_open_all(dp, SNDRV_SEQ_OSS_FILE_READ);
client_table[dp->index] = dp;
num_clients++;
debug_printk(("open done\n"));
return 0;
_error:
snd_seq_oss_synth_cleanup(dp);
snd_seq_oss_midi_cleanup(dp);
i = dp->queue;
delete_port(dp);
delete_seq_queue(i);
return rc;
}
/*
* translate file flags to private mode
*/
static int
translate_mode(struct file *file)
{
int file_mode = 0;
if ((file->f_flags & O_ACCMODE) != O_RDONLY)
file_mode |= SNDRV_SEQ_OSS_FILE_WRITE;
if ((file->f_flags & O_ACCMODE) != O_WRONLY)
file_mode |= SNDRV_SEQ_OSS_FILE_READ;
if (file->f_flags & O_NONBLOCK)
file_mode |= SNDRV_SEQ_OSS_FILE_NONBLOCK;
return file_mode;
}
/*
* create sequencer port
*/
static int
create_port(seq_oss_devinfo_t *dp)
{
int rc;
snd_seq_port_info_t port;
snd_seq_port_callback_t callback;
memset(&port, 0, sizeof(port));
port.addr.client = dp->cseq;
sprintf(port.name, "Sequencer-%d", dp->index);
port.capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_WRITE; /* no subscription */
port.type = SNDRV_SEQ_PORT_TYPE_SPECIFIC;
port.midi_channels = 128;
port.synth_voices = 128;
memset(&callback, 0, sizeof(callback));
callback.owner = THIS_MODULE;
callback.private_data = dp;
callback.event_input = snd_seq_oss_event_input;
callback.private_free = free_devinfo;
port.kernel = &callback;
rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, &port);
if (rc < 0)
return rc;
dp->port = port.addr.port;
debug_printk(("new port = %d\n", port.addr.port));
return 0;
}
/*
* delete ALSA port
*/
static int
delete_port(seq_oss_devinfo_t *dp)
{
if (dp->port < 0)
return 0;
debug_printk(("delete_port %i\n", dp->port));
return snd_seq_event_port_detach(dp->cseq, dp->port);
}
/*
* allocate a queue
*/
static int
alloc_seq_queue(seq_oss_devinfo_t *dp)
{
snd_seq_queue_info_t qinfo;
int rc;
memset(&qinfo, 0, sizeof(qinfo));
qinfo.owner = system_client;
qinfo.locked = 1;
strcpy(qinfo.name, "OSS Sequencer Emulation");
if ((rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_QUEUE, &qinfo)) < 0)
return rc;
dp->queue = qinfo.queue;
return 0;
}
/*
* release queue
*/
static int
delete_seq_queue(int queue)
{
snd_seq_queue_info_t qinfo;
int rc;
if (queue < 0)
return 0;
memset(&qinfo, 0, sizeof(qinfo));
qinfo.queue = queue;
rc = call_ctl(SNDRV_SEQ_IOCTL_DELETE_QUEUE, &qinfo);
if (rc < 0)
printk(KERN_ERR "seq-oss: unable to delete queue %d (%d)\n", queue, rc);
return rc;
}
/*
* free device informations - private_free callback of port
*/
static void
free_devinfo(void *private)
{
seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private;
if (dp->timer)
snd_seq_oss_timer_delete(dp->timer);
if (dp->writeq)
snd_seq_oss_writeq_delete(dp->writeq);
if (dp->readq)
snd_seq_oss_readq_delete(dp->readq);
kfree(dp);
}
/*
* close sequencer device
*/
void
snd_seq_oss_release(seq_oss_devinfo_t *dp)
{
int queue;
client_table[dp->index] = NULL;
num_clients--;
debug_printk(("resetting..\n"));
snd_seq_oss_reset(dp);
debug_printk(("cleaning up..\n"));
snd_seq_oss_synth_cleanup(dp);
snd_seq_oss_midi_cleanup(dp);
/* clear slot */
debug_printk(("releasing resource..\n"));
queue = dp->queue;
if (dp->port >= 0)
delete_port(dp);
delete_seq_queue(queue);
debug_printk(("release done\n"));
}
/*
* Wait until the queue is empty (if we don't have nonblock)
*/
void
snd_seq_oss_drain_write(seq_oss_devinfo_t *dp)
{
if (! dp->timer->running)
return;
if (is_write_mode(dp->file_mode) && !is_nonblock_mode(dp->file_mode) &&
dp->writeq) {
debug_printk(("syncing..\n"));
while (snd_seq_oss_writeq_sync(dp->writeq))
;
}
}
/*
* reset sequencer devices
*/
void
snd_seq_oss_reset(seq_oss_devinfo_t *dp)
{
int i;
/* reset all synth devices */
for (i = 0; i < dp->max_synthdev; i++)
snd_seq_oss_synth_reset(dp, i);
/* reset all midi devices */
if (dp->seq_mode != SNDRV_SEQ_OSS_MODE_MUSIC) {
for (i = 0; i < dp->max_mididev; i++)
snd_seq_oss_midi_reset(dp, i);
}
/* remove queues */
if (dp->readq)
snd_seq_oss_readq_clear(dp->readq);
if (dp->writeq)
snd_seq_oss_writeq_clear(dp->writeq);
/* reset timer */
snd_seq_oss_timer_stop(dp->timer);
}
/*
* misc. functions for proc interface
*/
char *
enabled_str(int bool)
{
return bool ? "enabled" : "disabled";
}
static char *
filemode_str(int val)
{
static char *str[] = {
"none", "read", "write", "read/write",
};
return str[val & SNDRV_SEQ_OSS_FILE_ACMODE];
}
/*
* proc interface
*/
void
snd_seq_oss_system_info_read(snd_info_buffer_t *buf)
{
int i;
seq_oss_devinfo_t *dp;
snd_iprintf(buf, "ALSA client number %d\n", system_client);
snd_iprintf(buf, "ALSA receiver port %d\n", system_port);
snd_iprintf(buf, "\nNumber of applications: %d\n", num_clients);
for (i = 0; i < num_clients; i++) {
snd_iprintf(buf, "\nApplication %d: ", i);
if ((dp = client_table[i]) == NULL) {
snd_iprintf(buf, "*empty*\n");
continue;
}
snd_iprintf(buf, "port %d : queue %d\n", dp->port, dp->queue);
snd_iprintf(buf, " sequencer mode = %s : file open mode = %s\n",
(dp->seq_mode ? "music" : "synth"),
filemode_str(dp->file_mode));
if (dp->seq_mode)
snd_iprintf(buf, " timer tempo = %d, timebase = %d\n",
dp->timer->oss_tempo, dp->timer->oss_timebase);
snd_iprintf(buf, " max queue length %d\n", maxqlen);
if (is_read_mode(dp->file_mode) && dp->readq)
snd_seq_oss_readq_info_read(dp->readq, buf);
}
}

View File

@@ -0,0 +1,209 @@
/*
* OSS compatible sequencer driver
*
* OSS compatible i/o control
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "seq_oss_device.h"
#include "seq_oss_readq.h"
#include "seq_oss_writeq.h"
#include "seq_oss_timer.h"
#include "seq_oss_synth.h"
#include "seq_oss_midi.h"
#include "seq_oss_event.h"
static int snd_seq_oss_synth_info_user(seq_oss_devinfo_t *dp, void __user *arg)
{
struct synth_info info;
if (copy_from_user(&info, arg, sizeof(info)))
return -EFAULT;
if (snd_seq_oss_synth_make_info(dp, info.device, &info) < 0)
return -EINVAL;
if (copy_to_user(arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
static int snd_seq_oss_midi_info_user(seq_oss_devinfo_t *dp, void __user *arg)
{
struct midi_info info;
if (copy_from_user(&info, arg, sizeof(info)))
return -EFAULT;
if (snd_seq_oss_midi_make_info(dp, info.device, &info) < 0)
return -EINVAL;
if (copy_to_user(arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
static int snd_seq_oss_oob_user(seq_oss_devinfo_t *dp, void __user *arg)
{
unsigned char ev[8];
snd_seq_event_t tmpev;
if (copy_from_user(ev, arg, 8))
return -EFAULT;
memset(&tmpev, 0, sizeof(tmpev));
snd_seq_oss_fill_addr(dp, &tmpev, dp->addr.port, dp->addr.client);
tmpev.time.tick = 0;
if (! snd_seq_oss_process_event(dp, (evrec_t*)ev, &tmpev)) {
snd_seq_oss_dispatch(dp, &tmpev, 0, 0);
}
return 0;
}
int
snd_seq_oss_ioctl(seq_oss_devinfo_t *dp, unsigned int cmd, unsigned long carg)
{
int dev, val;
void __user *arg = (void __user *)carg;
int __user *p = arg;
switch (cmd) {
case SNDCTL_TMR_TIMEBASE:
case SNDCTL_TMR_TEMPO:
case SNDCTL_TMR_START:
case SNDCTL_TMR_STOP:
case SNDCTL_TMR_CONTINUE:
case SNDCTL_TMR_METRONOME:
case SNDCTL_TMR_SOURCE:
case SNDCTL_TMR_SELECT:
case SNDCTL_SEQ_CTRLRATE:
return snd_seq_oss_timer_ioctl(dp->timer, cmd, arg);
case SNDCTL_SEQ_PANIC:
debug_printk(("panic\n"));
snd_seq_oss_reset(dp);
return -EINVAL;
case SNDCTL_SEQ_SYNC:
debug_printk(("sync\n"));
if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
return 0;
while (snd_seq_oss_writeq_sync(dp->writeq))
;
if (signal_pending(current))
return -ERESTARTSYS;
return 0;
case SNDCTL_SEQ_RESET:
debug_printk(("reset\n"));
snd_seq_oss_reset(dp);
return 0;
case SNDCTL_SEQ_TESTMIDI:
debug_printk(("test midi\n"));
if (get_user(dev, p))
return -EFAULT;
return snd_seq_oss_midi_open(dp, dev, dp->file_mode);
case SNDCTL_SEQ_GETINCOUNT:
debug_printk(("get in count\n"));
if (dp->readq == NULL || ! is_read_mode(dp->file_mode))
return 0;
return put_user(dp->readq->qlen, p) ? -EFAULT : 0;
case SNDCTL_SEQ_GETOUTCOUNT:
debug_printk(("get out count\n"));
if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
return 0;
return put_user(snd_seq_oss_writeq_get_free_size(dp->writeq), p) ? -EFAULT : 0;
case SNDCTL_SEQ_GETTIME:
debug_printk(("get time\n"));
return put_user(snd_seq_oss_timer_cur_tick(dp->timer), p) ? -EFAULT : 0;
case SNDCTL_SEQ_RESETSAMPLES:
debug_printk(("reset samples\n"));
if (get_user(dev, p))
return -EFAULT;
return snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
case SNDCTL_SEQ_NRSYNTHS:
debug_printk(("nr synths\n"));
return put_user(dp->max_synthdev, p) ? -EFAULT : 0;
case SNDCTL_SEQ_NRMIDIS:
debug_printk(("nr midis\n"));
return put_user(dp->max_mididev, p) ? -EFAULT : 0;
case SNDCTL_SYNTH_MEMAVL:
debug_printk(("mem avail\n"));
if (get_user(dev, p))
return -EFAULT;
val = snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
return put_user(val, p) ? -EFAULT : 0;
case SNDCTL_FM_4OP_ENABLE:
debug_printk(("4op\n"));
if (get_user(dev, p))
return -EFAULT;
snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
return 0;
case SNDCTL_SYNTH_INFO:
case SNDCTL_SYNTH_ID:
debug_printk(("synth info\n"));
return snd_seq_oss_synth_info_user(dp, arg);
case SNDCTL_SEQ_OUTOFBAND:
debug_printk(("out of band\n"));
return snd_seq_oss_oob_user(dp, arg);
case SNDCTL_MIDI_INFO:
debug_printk(("midi info\n"));
return snd_seq_oss_midi_info_user(dp, arg);
case SNDCTL_SEQ_THRESHOLD:
debug_printk(("threshold\n"));
if (! is_write_mode(dp->file_mode))
return 0;
if (get_user(val, p))
return -EFAULT;
if (val < 1)
val = 1;
if (val >= dp->writeq->maxlen)
val = dp->writeq->maxlen - 1;
snd_seq_oss_writeq_set_output(dp->writeq, val);
return 0;
case SNDCTL_MIDI_PRETIME:
debug_printk(("pretime\n"));
if (dp->readq == NULL || !is_read_mode(dp->file_mode))
return 0;
if (get_user(val, p))
return -EFAULT;
if (val <= 0)
val = -1;
else
val = (HZ * val) / 10;
dp->readq->pre_event_timeout = val;
return put_user(val, p) ? -EFAULT : 0;
default:
debug_printk(("others\n"));
if (! is_write_mode(dp->file_mode))
return -EIO;
return snd_seq_oss_synth_ioctl(dp, 0, cmd, carg);
}
return 0;
}

View File

@@ -0,0 +1,710 @@
/*
* OSS compatible sequencer driver
*
* MIDI device handlers
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "seq_oss_midi.h"
#include "seq_oss_readq.h"
#include "seq_oss_timer.h"
#include "seq_oss_event.h"
#include <sound/seq_midi_event.h>
#include "../seq_lock.h"
#include <linux/init.h>
/*
* constants
*/
#define SNDRV_SEQ_OSS_MAX_MIDI_NAME 30
/*
* definition of midi device record
*/
struct seq_oss_midi_t {
int seq_device; /* device number */
int client; /* sequencer client number */
int port; /* sequencer port number */
unsigned int flags; /* port capability */
int opened; /* flag for opening */
unsigned char name[SNDRV_SEQ_OSS_MAX_MIDI_NAME];
snd_midi_event_t *coder; /* MIDI event coder */
seq_oss_devinfo_t *devinfo; /* assigned OSSseq device */
snd_use_lock_t use_lock;
};
/*
* midi device table
*/
static int max_midi_devs;
static seq_oss_midi_t *midi_devs[SNDRV_SEQ_OSS_MAX_MIDI_DEVS];
static DEFINE_SPINLOCK(register_lock);
/*
* prototypes
*/
static seq_oss_midi_t *get_mdev(int dev);
static seq_oss_midi_t *get_mididev(seq_oss_devinfo_t *dp, int dev);
static int send_synth_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int dev);
static int send_midi_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, seq_oss_midi_t *mdev);
/*
* look up the existing ports
* this looks a very exhausting job.
*/
int __init
snd_seq_oss_midi_lookup_ports(int client)
{
snd_seq_client_info_t *clinfo;
snd_seq_port_info_t *pinfo;
clinfo = kcalloc(1, sizeof(*clinfo), GFP_KERNEL);
pinfo = kcalloc(1, sizeof(*pinfo), GFP_KERNEL);
if (! clinfo || ! pinfo) {
kfree(clinfo);
kfree(pinfo);
return -ENOMEM;
}
clinfo->client = -1;
while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, clinfo) == 0) {
if (clinfo->client == client)
continue; /* ignore myself */
pinfo->addr.client = clinfo->client;
pinfo->addr.port = -1;
while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, pinfo) == 0)
snd_seq_oss_midi_check_new_port(pinfo);
}
kfree(clinfo);
kfree(pinfo);
return 0;
}
/*
*/
static seq_oss_midi_t *
get_mdev(int dev)
{
seq_oss_midi_t *mdev;
unsigned long flags;
spin_lock_irqsave(&register_lock, flags);
mdev = midi_devs[dev];
if (mdev)
snd_use_lock_use(&mdev->use_lock);
spin_unlock_irqrestore(&register_lock, flags);
return mdev;
}
/*
* look for the identical slot
*/
static seq_oss_midi_t *
find_slot(int client, int port)
{
int i;
seq_oss_midi_t *mdev;
unsigned long flags;
spin_lock_irqsave(&register_lock, flags);
for (i = 0; i < max_midi_devs; i++) {
mdev = midi_devs[i];
if (mdev && mdev->client == client && mdev->port == port) {
/* found! */
snd_use_lock_use(&mdev->use_lock);
spin_unlock_irqrestore(&register_lock, flags);
return mdev;
}
}
spin_unlock_irqrestore(&register_lock, flags);
return NULL;
}
#define PERM_WRITE (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE)
#define PERM_READ (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ)
/*
* register a new port if it doesn't exist yet
*/
int
snd_seq_oss_midi_check_new_port(snd_seq_port_info_t *pinfo)
{
int i;
seq_oss_midi_t *mdev;
unsigned long flags;
debug_printk(("check for MIDI client %d port %d\n", pinfo->addr.client, pinfo->addr.port));
/* the port must include generic midi */
if (! (pinfo->type & SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC))
return 0;
/* either read or write subscribable */
if ((pinfo->capability & PERM_WRITE) != PERM_WRITE &&
(pinfo->capability & PERM_READ) != PERM_READ)
return 0;
/*
* look for the identical slot
*/
if ((mdev = find_slot(pinfo->addr.client, pinfo->addr.port)) != NULL) {
/* already exists */
snd_use_lock_free(&mdev->use_lock);
return 0;
}
/*
* allocate midi info record
*/
if ((mdev = kcalloc(1, sizeof(*mdev), GFP_KERNEL)) == NULL) {
snd_printk(KERN_ERR "can't malloc midi info\n");
return -ENOMEM;
}
/* copy the port information */
mdev->client = pinfo->addr.client;
mdev->port = pinfo->addr.port;
mdev->flags = pinfo->capability;
mdev->opened = 0;
snd_use_lock_init(&mdev->use_lock);
/* copy and truncate the name of synth device */
strlcpy(mdev->name, pinfo->name, sizeof(mdev->name));
/* create MIDI coder */
if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &mdev->coder) < 0) {
snd_printk(KERN_ERR "can't malloc midi coder\n");
kfree(mdev);
return -ENOMEM;
}
/* OSS sequencer adds running status to all sequences */
snd_midi_event_no_status(mdev->coder, 1);
/*
* look for en empty slot
*/
spin_lock_irqsave(&register_lock, flags);
for (i = 0; i < max_midi_devs; i++) {
if (midi_devs[i] == NULL)
break;
}
if (i >= max_midi_devs) {
if (max_midi_devs >= SNDRV_SEQ_OSS_MAX_MIDI_DEVS) {
spin_unlock_irqrestore(&register_lock, flags);
snd_midi_event_free(mdev->coder);
kfree(mdev);
return -ENOMEM;
}
max_midi_devs++;
}
mdev->seq_device = i;
midi_devs[mdev->seq_device] = mdev;
spin_unlock_irqrestore(&register_lock, flags);
return 0;
}
/*
* release the midi device if it was registered
*/
int
snd_seq_oss_midi_check_exit_port(int client, int port)
{
seq_oss_midi_t *mdev;
unsigned long flags;
int index;
if ((mdev = find_slot(client, port)) != NULL) {
spin_lock_irqsave(&register_lock, flags);
midi_devs[mdev->seq_device] = NULL;
spin_unlock_irqrestore(&register_lock, flags);
snd_use_lock_free(&mdev->use_lock);
snd_use_lock_sync(&mdev->use_lock);
if (mdev->coder)
snd_midi_event_free(mdev->coder);
kfree(mdev);
}
spin_lock_irqsave(&register_lock, flags);
for (index = max_midi_devs - 1; index >= 0; index--) {
if (midi_devs[index])
break;
}
max_midi_devs = index + 1;
spin_unlock_irqrestore(&register_lock, flags);
return 0;
}
/*
* release the midi device if it was registered
*/
void
snd_seq_oss_midi_clear_all(void)
{
int i;
seq_oss_midi_t *mdev;
unsigned long flags;
spin_lock_irqsave(&register_lock, flags);
for (i = 0; i < max_midi_devs; i++) {
if ((mdev = midi_devs[i]) != NULL) {
if (mdev->coder)
snd_midi_event_free(mdev->coder);
kfree(mdev);
midi_devs[i] = NULL;
}
}
max_midi_devs = 0;
spin_unlock_irqrestore(&register_lock, flags);
}
/*
* set up midi tables
*/
void
snd_seq_oss_midi_setup(seq_oss_devinfo_t *dp)
{
dp->max_mididev = max_midi_devs;
}
/*
* clean up midi tables
*/
void
snd_seq_oss_midi_cleanup(seq_oss_devinfo_t *dp)
{
int i;
for (i = 0; i < dp->max_mididev; i++)
snd_seq_oss_midi_close(dp, i);
dp->max_mididev = 0;
}
/*
* open all midi devices. ignore errors.
*/
void
snd_seq_oss_midi_open_all(seq_oss_devinfo_t *dp, int file_mode)
{
int i;
for (i = 0; i < dp->max_mididev; i++)
snd_seq_oss_midi_open(dp, i, file_mode);
}
/*
* get the midi device information
*/
static seq_oss_midi_t *
get_mididev(seq_oss_devinfo_t *dp, int dev)
{
if (dev < 0 || dev >= dp->max_mididev)
return NULL;
return get_mdev(dev);
}
/*
* open the midi device if not opened yet
*/
int
snd_seq_oss_midi_open(seq_oss_devinfo_t *dp, int dev, int fmode)
{
int perm;
seq_oss_midi_t *mdev;
snd_seq_port_subscribe_t subs;
if ((mdev = get_mididev(dp, dev)) == NULL)
return -ENODEV;
/* already used? */
if (mdev->opened && mdev->devinfo != dp) {
snd_use_lock_free(&mdev->use_lock);
return -EBUSY;
}
perm = 0;
if (is_write_mode(fmode))
perm |= PERM_WRITE;
if (is_read_mode(fmode))
perm |= PERM_READ;
perm &= mdev->flags;
if (perm == 0) {
snd_use_lock_free(&mdev->use_lock);
return -ENXIO;
}
/* already opened? */
if ((mdev->opened & perm) == perm) {
snd_use_lock_free(&mdev->use_lock);
return 0;
}
perm &= ~mdev->opened;
memset(&subs, 0, sizeof(subs));
if (perm & PERM_WRITE) {
subs.sender = dp->addr;
subs.dest.client = mdev->client;
subs.dest.port = mdev->port;
if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)
mdev->opened |= PERM_WRITE;
}
if (perm & PERM_READ) {
subs.sender.client = mdev->client;
subs.sender.port = mdev->port;
subs.dest = dp->addr;
subs.flags = SNDRV_SEQ_PORT_SUBS_TIMESTAMP;
subs.queue = dp->queue; /* queue for timestamps */
if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)
mdev->opened |= PERM_READ;
}
if (! mdev->opened) {
snd_use_lock_free(&mdev->use_lock);
return -ENXIO;
}
mdev->devinfo = dp;
snd_use_lock_free(&mdev->use_lock);
return 0;
}
/*
* close the midi device if already opened
*/
int
snd_seq_oss_midi_close(seq_oss_devinfo_t *dp, int dev)
{
seq_oss_midi_t *mdev;
snd_seq_port_subscribe_t subs;
if ((mdev = get_mididev(dp, dev)) == NULL)
return -ENODEV;
if (! mdev->opened || mdev->devinfo != dp) {
snd_use_lock_free(&mdev->use_lock);
return 0;
}
debug_printk(("closing client %d port %d mode %d\n", mdev->client, mdev->port, mdev->opened));
memset(&subs, 0, sizeof(subs));
if (mdev->opened & PERM_WRITE) {
subs.sender = dp->addr;
subs.dest.client = mdev->client;
subs.dest.port = mdev->port;
snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);
}
if (mdev->opened & PERM_READ) {
subs.sender.client = mdev->client;
subs.sender.port = mdev->port;
subs.dest = dp->addr;
snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);
}
mdev->opened = 0;
mdev->devinfo = NULL;
snd_use_lock_free(&mdev->use_lock);
return 0;
}
/*
* change seq capability flags to file mode flags
*/
int
snd_seq_oss_midi_filemode(seq_oss_devinfo_t *dp, int dev)
{
seq_oss_midi_t *mdev;
int mode;
if ((mdev = get_mididev(dp, dev)) == NULL)
return 0;
mode = 0;
if (mdev->opened & PERM_WRITE)
mode |= SNDRV_SEQ_OSS_FILE_WRITE;
if (mdev->opened & PERM_READ)
mode |= SNDRV_SEQ_OSS_FILE_READ;
snd_use_lock_free(&mdev->use_lock);
return mode;
}
/*
* reset the midi device and close it:
* so far, only close the device.
*/
void
snd_seq_oss_midi_reset(seq_oss_devinfo_t *dp, int dev)
{
seq_oss_midi_t *mdev;
if ((mdev = get_mididev(dp, dev)) == NULL)
return;
if (! mdev->opened) {
snd_use_lock_free(&mdev->use_lock);
return;
}
if (mdev->opened & PERM_WRITE) {
snd_seq_event_t ev;
int c;
debug_printk(("resetting client %d port %d\n", mdev->client, mdev->port));
memset(&ev, 0, sizeof(ev));
ev.dest.client = mdev->client;
ev.dest.port = mdev->port;
ev.queue = dp->queue;
ev.source.port = dp->port;
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) {
ev.type = SNDRV_SEQ_EVENT_SENSING;
snd_seq_oss_dispatch(dp, &ev, 0, 0); /* active sensing */
}
for (c = 0; c < 16; c++) {
ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
ev.data.control.channel = c;
ev.data.control.param = 123;
snd_seq_oss_dispatch(dp, &ev, 0, 0); /* all notes off */
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
ev.data.control.param = 121;
snd_seq_oss_dispatch(dp, &ev, 0, 0); /* reset all controllers */
ev.type = SNDRV_SEQ_EVENT_PITCHBEND;
ev.data.control.value = 0;
snd_seq_oss_dispatch(dp, &ev, 0, 0); /* bender off */
}
}
}
// snd_seq_oss_midi_close(dp, dev);
snd_use_lock_free(&mdev->use_lock);
}
/*
* get client/port of the specified MIDI device
*/
void
snd_seq_oss_midi_get_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_addr_t *addr)
{
seq_oss_midi_t *mdev;
if ((mdev = get_mididev(dp, dev)) == NULL)
return;
addr->client = mdev->client;
addr->port = mdev->port;
snd_use_lock_free(&mdev->use_lock);
}
/*
* input callback - this can be atomic
*/
int
snd_seq_oss_midi_input(snd_seq_event_t *ev, int direct, void *private_data)
{
seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private_data;
seq_oss_midi_t *mdev;
int rc;
if (dp->readq == NULL)
return 0;
if ((mdev = find_slot(ev->source.client, ev->source.port)) == NULL)
return 0;
if (! (mdev->opened & PERM_READ)) {
snd_use_lock_free(&mdev->use_lock);
return 0;
}
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
rc = send_synth_event(dp, ev, mdev->seq_device);
else
rc = send_midi_event(dp, ev, mdev);
snd_use_lock_free(&mdev->use_lock);
return rc;
}
/*
* convert ALSA sequencer event to OSS synth event
*/
static int
send_synth_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int dev)
{
evrec_t ossev;
memset(&ossev, 0, sizeof(ossev));
switch (ev->type) {
case SNDRV_SEQ_EVENT_NOTEON:
ossev.v.cmd = MIDI_NOTEON; break;
case SNDRV_SEQ_EVENT_NOTEOFF:
ossev.v.cmd = MIDI_NOTEOFF; break;
case SNDRV_SEQ_EVENT_KEYPRESS:
ossev.v.cmd = MIDI_KEY_PRESSURE; break;
case SNDRV_SEQ_EVENT_CONTROLLER:
ossev.l.cmd = MIDI_CTL_CHANGE; break;
case SNDRV_SEQ_EVENT_PGMCHANGE:
ossev.l.cmd = MIDI_PGM_CHANGE; break;
case SNDRV_SEQ_EVENT_CHANPRESS:
ossev.l.cmd = MIDI_CHN_PRESSURE; break;
case SNDRV_SEQ_EVENT_PITCHBEND:
ossev.l.cmd = MIDI_PITCH_BEND; break;
default:
return 0; /* not supported */
}
ossev.v.dev = dev;
switch (ev->type) {
case SNDRV_SEQ_EVENT_NOTEON:
case SNDRV_SEQ_EVENT_NOTEOFF:
case SNDRV_SEQ_EVENT_KEYPRESS:
ossev.v.code = EV_CHN_VOICE;
ossev.v.note = ev->data.note.note;
ossev.v.parm = ev->data.note.velocity;
ossev.v.chn = ev->data.note.channel;
break;
case SNDRV_SEQ_EVENT_CONTROLLER:
case SNDRV_SEQ_EVENT_PGMCHANGE:
case SNDRV_SEQ_EVENT_CHANPRESS:
ossev.l.code = EV_CHN_COMMON;
ossev.l.p1 = ev->data.control.param;
ossev.l.val = ev->data.control.value;
ossev.l.chn = ev->data.control.channel;
break;
case SNDRV_SEQ_EVENT_PITCHBEND:
ossev.l.code = EV_CHN_COMMON;
ossev.l.val = ev->data.control.value + 8192;
ossev.l.chn = ev->data.control.channel;
break;
}
snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);
snd_seq_oss_readq_put_event(dp->readq, &ossev);
return 0;
}
/*
* decode event and send MIDI bytes to read queue
*/
static int
send_midi_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, seq_oss_midi_t *mdev)
{
char msg[32];
int len;
snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);
if (!dp->timer->running)
len = snd_seq_oss_timer_start(dp->timer);
if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {
if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
snd_seq_oss_readq_puts(dp->readq, mdev->seq_device,
ev->data.ext.ptr, ev->data.ext.len);
} else {
len = snd_midi_event_decode(mdev->coder, msg, sizeof(msg), ev);
if (len > 0)
snd_seq_oss_readq_puts(dp->readq, mdev->seq_device, msg, len);
}
return 0;
}
/*
* dump midi data
* return 0 : enqueued
* non-zero : invalid - ignored
*/
int
snd_seq_oss_midi_putc(seq_oss_devinfo_t *dp, int dev, unsigned char c, snd_seq_event_t *ev)
{
seq_oss_midi_t *mdev;
if ((mdev = get_mididev(dp, dev)) == NULL)
return -ENODEV;
if (snd_midi_event_encode_byte(mdev->coder, c, ev) > 0) {
snd_seq_oss_fill_addr(dp, ev, mdev->client, mdev->port);
snd_use_lock_free(&mdev->use_lock);
return 0;
}
snd_use_lock_free(&mdev->use_lock);
return -EINVAL;
}
/*
* create OSS compatible midi_info record
*/
int
snd_seq_oss_midi_make_info(seq_oss_devinfo_t *dp, int dev, struct midi_info *inf)
{
seq_oss_midi_t *mdev;
if ((mdev = get_mididev(dp, dev)) == NULL)
return -ENXIO;
inf->device = dev;
inf->dev_type = 0; /* FIXME: ?? */
inf->capabilities = 0; /* FIXME: ?? */
strlcpy(inf->name, mdev->name, sizeof(inf->name));
snd_use_lock_free(&mdev->use_lock);
return 0;
}
/*
* proc interface
*/
static char *
capmode_str(int val)
{
val &= PERM_READ|PERM_WRITE;
if (val == (PERM_READ|PERM_WRITE))
return "read/write";
else if (val == PERM_READ)
return "read";
else if (val == PERM_WRITE)
return "write";
else
return "none";
}
void
snd_seq_oss_midi_info_read(snd_info_buffer_t *buf)
{
int i;
seq_oss_midi_t *mdev;
snd_iprintf(buf, "\nNumber of MIDI devices: %d\n", max_midi_devs);
for (i = 0; i < max_midi_devs; i++) {
snd_iprintf(buf, "\nmidi %d: ", i);
mdev = get_mdev(i);
if (mdev == NULL) {
snd_iprintf(buf, "*empty*\n");
continue;
}
snd_iprintf(buf, "[%s] ALSA port %d:%d\n", mdev->name,
mdev->client, mdev->port);
snd_iprintf(buf, " capability %s / opened %s\n",
capmode_str(mdev->flags),
capmode_str(mdev->opened));
snd_use_lock_free(&mdev->use_lock);
}
}

View File

@@ -0,0 +1,49 @@
/*
* OSS compatible sequencer driver
*
* midi device information
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __SEQ_OSS_MIDI_H
#define __SEQ_OSS_MIDI_H
#include "seq_oss_device.h"
#include <sound/seq_oss_legacy.h>
typedef struct seq_oss_midi_t seq_oss_midi_t;
int snd_seq_oss_midi_lookup_ports(int client);
int snd_seq_oss_midi_check_new_port(snd_seq_port_info_t *pinfo);
int snd_seq_oss_midi_check_exit_port(int client, int port);
void snd_seq_oss_midi_clear_all(void);
void snd_seq_oss_midi_setup(seq_oss_devinfo_t *dp);
void snd_seq_oss_midi_cleanup(seq_oss_devinfo_t *dp);
int snd_seq_oss_midi_open(seq_oss_devinfo_t *dp, int dev, int file_mode);
void snd_seq_oss_midi_open_all(seq_oss_devinfo_t *dp, int file_mode);
int snd_seq_oss_midi_close(seq_oss_devinfo_t *dp, int dev);
void snd_seq_oss_midi_reset(seq_oss_devinfo_t *dp, int dev);
int snd_seq_oss_midi_putc(seq_oss_devinfo_t *dp, int dev, unsigned char c, snd_seq_event_t *ev);
int snd_seq_oss_midi_input(snd_seq_event_t *ev, int direct, void *private);
int snd_seq_oss_midi_filemode(seq_oss_devinfo_t *dp, int dev);
int snd_seq_oss_midi_make_info(seq_oss_devinfo_t *dp, int dev, struct midi_info *inf);
void snd_seq_oss_midi_get_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_addr_t *addr);
#endif

View File

@@ -0,0 +1,234 @@
/*
* OSS compatible sequencer driver
*
* seq_oss_readq.c - MIDI input queue
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "seq_oss_readq.h"
#include "seq_oss_event.h"
#include <sound/seq_oss_legacy.h>
#include "../seq_lock.h"
#include <linux/wait.h>
/*
* constants
*/
//#define SNDRV_SEQ_OSS_MAX_TIMEOUT (unsigned long)(-1)
#define SNDRV_SEQ_OSS_MAX_TIMEOUT (HZ * 3600)
/*
* prototypes
*/
/*
* create a read queue
*/
seq_oss_readq_t *
snd_seq_oss_readq_new(seq_oss_devinfo_t *dp, int maxlen)
{
seq_oss_readq_t *q;
if ((q = kcalloc(1, sizeof(*q), GFP_KERNEL)) == NULL) {
snd_printk(KERN_ERR "can't malloc read queue\n");
return NULL;
}
if ((q->q = kcalloc(maxlen, sizeof(evrec_t), GFP_KERNEL)) == NULL) {
snd_printk(KERN_ERR "can't malloc read queue buffer\n");
kfree(q);
return NULL;
}
q->maxlen = maxlen;
q->qlen = 0;
q->head = q->tail = 0;
init_waitqueue_head(&q->midi_sleep);
spin_lock_init(&q->lock);
q->pre_event_timeout = SNDRV_SEQ_OSS_MAX_TIMEOUT;
q->input_time = (unsigned long)-1;
return q;
}
/*
* delete the read queue
*/
void
snd_seq_oss_readq_delete(seq_oss_readq_t *q)
{
if (q) {
kfree(q->q);
kfree(q);
}
}
/*
* reset the read queue
*/
void
snd_seq_oss_readq_clear(seq_oss_readq_t *q)
{
if (q->qlen) {
q->qlen = 0;
q->head = q->tail = 0;
}
/* if someone sleeping, wake'em up */
if (waitqueue_active(&q->midi_sleep))
wake_up(&q->midi_sleep);
q->input_time = (unsigned long)-1;
}
/*
* put a midi byte
*/
int
snd_seq_oss_readq_puts(seq_oss_readq_t *q, int dev, unsigned char *data, int len)
{
evrec_t rec;
int result;
memset(&rec, 0, sizeof(rec));
rec.c[0] = SEQ_MIDIPUTC;
rec.c[2] = dev;
while (len-- > 0) {
rec.c[1] = *data++;
result = snd_seq_oss_readq_put_event(q, &rec);
if (result < 0)
return result;
}
return 0;
}
/*
* copy an event to input queue:
* return zero if enqueued
*/
int
snd_seq_oss_readq_put_event(seq_oss_readq_t *q, evrec_t *ev)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
if (q->qlen >= q->maxlen - 1) {
spin_unlock_irqrestore(&q->lock, flags);
return -ENOMEM;
}
memcpy(&q->q[q->tail], ev, sizeof(*ev));
q->tail = (q->tail + 1) % q->maxlen;
q->qlen++;
/* wake up sleeper */
if (waitqueue_active(&q->midi_sleep))
wake_up(&q->midi_sleep);
spin_unlock_irqrestore(&q->lock, flags);
return 0;
}
/*
* pop queue
* caller must hold lock
*/
int
snd_seq_oss_readq_pick(seq_oss_readq_t *q, evrec_t *rec)
{
if (q->qlen == 0)
return -EAGAIN;
memcpy(rec, &q->q[q->head], sizeof(*rec));
return 0;
}
/*
* sleep until ready
*/
void
snd_seq_oss_readq_wait(seq_oss_readq_t *q)
{
wait_event_interruptible_timeout(q->midi_sleep,
(q->qlen > 0 || q->head == q->tail),
q->pre_event_timeout);
}
/*
* drain one record
* caller must hold lock
*/
void
snd_seq_oss_readq_free(seq_oss_readq_t *q)
{
if (q->qlen > 0) {
q->head = (q->head + 1) % q->maxlen;
q->qlen--;
}
}
/*
* polling/select:
* return non-zero if readq is not empty.
*/
unsigned int
snd_seq_oss_readq_poll(seq_oss_readq_t *q, struct file *file, poll_table *wait)
{
poll_wait(file, &q->midi_sleep, wait);
return q->qlen;
}
/*
* put a timestamp
*/
int
snd_seq_oss_readq_put_timestamp(seq_oss_readq_t *q, unsigned long curt, int seq_mode)
{
if (curt != q->input_time) {
evrec_t rec;
memset(&rec, 0, sizeof(rec));
switch (seq_mode) {
case SNDRV_SEQ_OSS_MODE_SYNTH:
rec.echo = (curt << 8) | SEQ_WAIT;
snd_seq_oss_readq_put_event(q, &rec);
break;
case SNDRV_SEQ_OSS_MODE_MUSIC:
rec.t.code = EV_TIMING;
rec.t.cmd = TMR_WAIT_ABS;
rec.t.time = curt;
snd_seq_oss_readq_put_event(q, &rec);
break;
}
q->input_time = curt;
}
return 0;
}
/*
* proc interface
*/
void
snd_seq_oss_readq_info_read(seq_oss_readq_t *q, snd_info_buffer_t *buf)
{
snd_iprintf(buf, " read queue [%s] length = %d : tick = %ld\n",
(waitqueue_active(&q->midi_sleep) ? "sleeping":"running"),
q->qlen, q->input_time);
}

View File

@@ -0,0 +1,56 @@
/*
* OSS compatible sequencer driver
* read fifo queue
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __SEQ_OSS_READQ_H
#define __SEQ_OSS_READQ_H
#include "seq_oss_device.h"
/*
* definition of read queue
*/
struct seq_oss_readq_t {
evrec_t *q;
int qlen;
int maxlen;
int head, tail;
unsigned long pre_event_timeout;
unsigned long input_time;
wait_queue_head_t midi_sleep;
spinlock_t lock;
};
seq_oss_readq_t *snd_seq_oss_readq_new(seq_oss_devinfo_t *dp, int maxlen);
void snd_seq_oss_readq_delete(seq_oss_readq_t *q);
void snd_seq_oss_readq_clear(seq_oss_readq_t *readq);
unsigned int snd_seq_oss_readq_poll(seq_oss_readq_t *readq, struct file *file, poll_table *wait);
int snd_seq_oss_readq_puts(seq_oss_readq_t *readq, int dev, unsigned char *data, int len);
int snd_seq_oss_readq_put_event(seq_oss_readq_t *readq, evrec_t *ev);
int snd_seq_oss_readq_put_timestamp(seq_oss_readq_t *readq, unsigned long curt, int seq_mode);
int snd_seq_oss_readq_pick(seq_oss_readq_t *q, evrec_t *rec);
void snd_seq_oss_readq_wait(seq_oss_readq_t *q);
void snd_seq_oss_readq_free(seq_oss_readq_t *q);
#define snd_seq_oss_readq_lock(q, flags) spin_lock_irqsave(&(q)->lock, flags)
#define snd_seq_oss_readq_unlock(q, flags) spin_unlock_irqrestore(&(q)->lock, flags)
#endif

View File

@@ -0,0 +1,216 @@
/*
* OSS compatible sequencer driver
*
* read/write/select interface to device file
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "seq_oss_device.h"
#include "seq_oss_readq.h"
#include "seq_oss_writeq.h"
#include "seq_oss_synth.h"
#include <sound/seq_oss_legacy.h>
#include "seq_oss_event.h"
#include "seq_oss_timer.h"
#include "../seq_clientmgr.h"
/*
* protoypes
*/
static int insert_queue(seq_oss_devinfo_t *dp, evrec_t *rec, struct file *opt);
/*
* read interface
*/
int
snd_seq_oss_read(seq_oss_devinfo_t *dp, char __user *buf, int count)
{
seq_oss_readq_t *readq = dp->readq;
int result = 0, err = 0;
int ev_len;
evrec_t rec;
unsigned long flags;
if (readq == NULL || ! is_read_mode(dp->file_mode))
return -ENXIO;
while (count >= SHORT_EVENT_SIZE) {
snd_seq_oss_readq_lock(readq, flags);
err = snd_seq_oss_readq_pick(readq, &rec);
if (err == -EAGAIN &&
!is_nonblock_mode(dp->file_mode) && result == 0) {
snd_seq_oss_readq_unlock(readq, flags);
snd_seq_oss_readq_wait(readq);
snd_seq_oss_readq_lock(readq, flags);
if (signal_pending(current))
err = -ERESTARTSYS;
else
err = snd_seq_oss_readq_pick(readq, &rec);
}
if (err < 0) {
snd_seq_oss_readq_unlock(readq, flags);
break;
}
ev_len = ev_length(&rec);
if (ev_len < count) {
snd_seq_oss_readq_unlock(readq, flags);
break;
}
snd_seq_oss_readq_free(readq);
snd_seq_oss_readq_unlock(readq, flags);
if (copy_to_user(buf, &rec, ev_len)) {
err = -EFAULT;
break;
}
result += ev_len;
buf += ev_len;
count -= ev_len;
}
return result > 0 ? result : err;
}
/*
* write interface
*/
int
snd_seq_oss_write(seq_oss_devinfo_t *dp, const char __user *buf, int count, struct file *opt)
{
int result = 0, err = 0;
int ev_size, fmt;
evrec_t rec;
if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
return -ENXIO;
while (count >= SHORT_EVENT_SIZE) {
if (copy_from_user(&rec, buf, SHORT_EVENT_SIZE)) {
err = -EFAULT;
break;
}
if (rec.s.code == SEQ_FULLSIZE) {
/* load patch */
if (result > 0) {
err = -EINVAL;
break;
}
fmt = (*(unsigned short *)rec.c) & 0xffff;
/* FIXME the return value isn't correct */
return snd_seq_oss_synth_load_patch(dp, rec.s.dev,
fmt, buf, 0, count);
}
if (ev_is_long(&rec)) {
/* extended code */
if (rec.s.code == SEQ_EXTENDED &&
dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
err = -EINVAL;
break;
}
ev_size = LONG_EVENT_SIZE;
if (count < ev_size)
break;
/* copy the reset 4 bytes */
if (copy_from_user(rec.c + SHORT_EVENT_SIZE,
buf + SHORT_EVENT_SIZE,
LONG_EVENT_SIZE - SHORT_EVENT_SIZE)) {
err = -EFAULT;
break;
}
} else {
/* old-type code */
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
err = -EINVAL;
break;
}
ev_size = SHORT_EVENT_SIZE;
}
/* insert queue */
if ((err = insert_queue(dp, &rec, opt)) < 0)
break;
result += ev_size;
buf += ev_size;
count -= ev_size;
}
return result > 0 ? result : err;
}
/*
* insert event record to write queue
* return: 0 = OK, non-zero = NG
*/
static int
insert_queue(seq_oss_devinfo_t *dp, evrec_t *rec, struct file *opt)
{
int rc = 0;
snd_seq_event_t event;
/* if this is a timing event, process the current time */
if (snd_seq_oss_process_timer_event(dp->timer, rec))
return 0; /* no need to insert queue */
/* parse this event */
memset(&event, 0, sizeof(event));
/* set dummy -- to be sure */
event.type = SNDRV_SEQ_EVENT_NOTEOFF;
snd_seq_oss_fill_addr(dp, &event, dp->addr.port, dp->addr.client);
if (snd_seq_oss_process_event(dp, rec, &event))
return 0; /* invalid event - no need to insert queue */
event.time.tick = snd_seq_oss_timer_cur_tick(dp->timer);
if (dp->timer->realtime || !dp->timer->running) {
snd_seq_oss_dispatch(dp, &event, 0, 0);
} else {
if (is_nonblock_mode(dp->file_mode))
rc = snd_seq_kernel_client_enqueue(dp->cseq, &event, 0, 0);
else
rc = snd_seq_kernel_client_enqueue_blocking(dp->cseq, &event, opt, 0, 0);
}
return rc;
}
/*
* select / poll
*/
unsigned int
snd_seq_oss_poll(seq_oss_devinfo_t *dp, struct file *file, poll_table * wait)
{
unsigned int mask = 0;
/* input */
if (dp->readq && is_read_mode(dp->file_mode)) {
if (snd_seq_oss_readq_poll(dp->readq, file, wait))
mask |= POLLIN | POLLRDNORM;
}
/* output */
if (dp->writeq && is_write_mode(dp->file_mode)) {
if (snd_seq_kernel_client_write_poll(dp->cseq, file, wait))
mask |= POLLOUT | POLLWRNORM;
}
return mask;
}

View File

@@ -0,0 +1,659 @@
/*
* OSS compatible sequencer driver
*
* synth device handlers
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "seq_oss_synth.h"
#include "seq_oss_midi.h"
#include "../seq_lock.h"
#include <linux/init.h>
/*
* constants
*/
#define SNDRV_SEQ_OSS_MAX_SYNTH_NAME 30
#define MAX_SYSEX_BUFLEN 128
/*
* definition of synth info records
*/
/* sysex buffer */
struct seq_oss_synth_sysex_t {
int len;
int skip;
unsigned char buf[MAX_SYSEX_BUFLEN];
};
/* synth info */
struct seq_oss_synth_t {
int seq_device;
/* for synth_info */
int synth_type;
int synth_subtype;
int nr_voices;
char name[SNDRV_SEQ_OSS_MAX_SYNTH_NAME];
snd_seq_oss_callback_t oper;
int opened;
void *private_data;
snd_use_lock_t use_lock;
};
/*
* device table
*/
static int max_synth_devs;
static seq_oss_synth_t *synth_devs[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
static seq_oss_synth_t midi_synth_dev = {
-1, /* seq_device */
SYNTH_TYPE_MIDI, /* synth_type */
0, /* synth_subtype */
16, /* nr_voices */
"MIDI", /* name */
};
static DEFINE_SPINLOCK(register_lock);
/*
* prototypes
*/
static seq_oss_synth_t *get_synthdev(seq_oss_devinfo_t *dp, int dev);
static void reset_channels(seq_oss_synthinfo_t *info);
/*
* global initialization
*/
void __init
snd_seq_oss_synth_init(void)
{
snd_use_lock_init(&midi_synth_dev.use_lock);
}
/*
* registration of the synth device
*/
int
snd_seq_oss_synth_register(snd_seq_device_t *dev)
{
int i;
seq_oss_synth_t *rec;
snd_seq_oss_reg_t *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
unsigned long flags;
if ((rec = kcalloc(1, sizeof(*rec), GFP_KERNEL)) == NULL) {
snd_printk(KERN_ERR "can't malloc synth info\n");
return -ENOMEM;
}
rec->seq_device = -1;
rec->synth_type = reg->type;
rec->synth_subtype = reg->subtype;
rec->nr_voices = reg->nvoices;
rec->oper = reg->oper;
rec->private_data = reg->private_data;
rec->opened = 0;
snd_use_lock_init(&rec->use_lock);
/* copy and truncate the name of synth device */
strlcpy(rec->name, dev->name, sizeof(rec->name));
/* registration */
spin_lock_irqsave(&register_lock, flags);
for (i = 0; i < max_synth_devs; i++) {
if (synth_devs[i] == NULL)
break;
}
if (i >= max_synth_devs) {
if (max_synth_devs >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) {
spin_unlock_irqrestore(&register_lock, flags);
snd_printk(KERN_ERR "no more synth slot\n");
kfree(rec);
return -ENOMEM;
}
max_synth_devs++;
}
rec->seq_device = i;
synth_devs[i] = rec;
debug_printk(("synth %s registered %d\n", rec->name, i));
spin_unlock_irqrestore(&register_lock, flags);
dev->driver_data = rec;
#ifdef SNDRV_OSS_INFO_DEV_SYNTH
if (i < SNDRV_CARDS)
snd_oss_info_register(SNDRV_OSS_INFO_DEV_SYNTH, i, rec->name);
#endif
return 0;
}
int
snd_seq_oss_synth_unregister(snd_seq_device_t *dev)
{
int index;
seq_oss_synth_t *rec = dev->driver_data;
unsigned long flags;
spin_lock_irqsave(&register_lock, flags);
for (index = 0; index < max_synth_devs; index++) {
if (synth_devs[index] == rec)
break;
}
if (index >= max_synth_devs) {
spin_unlock_irqrestore(&register_lock, flags);
snd_printk(KERN_ERR "can't unregister synth\n");
return -EINVAL;
}
synth_devs[index] = NULL;
if (index == max_synth_devs - 1) {
for (index--; index >= 0; index--) {
if (synth_devs[index])
break;
}
max_synth_devs = index + 1;
}
spin_unlock_irqrestore(&register_lock, flags);
#ifdef SNDRV_OSS_INFO_DEV_SYNTH
if (rec->seq_device < SNDRV_CARDS)
snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_SYNTH, rec->seq_device);
#endif
snd_use_lock_sync(&rec->use_lock);
kfree(rec);
return 0;
}
/*
*/
static seq_oss_synth_t *
get_sdev(int dev)
{
seq_oss_synth_t *rec;
unsigned long flags;
spin_lock_irqsave(&register_lock, flags);
rec = synth_devs[dev];
if (rec)
snd_use_lock_use(&rec->use_lock);
spin_unlock_irqrestore(&register_lock, flags);
return rec;
}
/*
* set up synth tables
*/
void
snd_seq_oss_synth_setup(seq_oss_devinfo_t *dp)
{
int i;
seq_oss_synth_t *rec;
seq_oss_synthinfo_t *info;
dp->max_synthdev = max_synth_devs;
dp->synth_opened = 0;
memset(dp->synths, 0, sizeof(dp->synths));
for (i = 0; i < dp->max_synthdev; i++) {
rec = get_sdev(i);
if (rec == NULL)
continue;
if (rec->oper.open == NULL || rec->oper.close == NULL) {
snd_use_lock_free(&rec->use_lock);
continue;
}
info = &dp->synths[i];
info->arg.app_index = dp->port;
info->arg.file_mode = dp->file_mode;
info->arg.seq_mode = dp->seq_mode;
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH)
info->arg.event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS;
else
info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS;
info->opened = 0;
if (!try_module_get(rec->oper.owner)) {
snd_use_lock_free(&rec->use_lock);
continue;
}
if (rec->oper.open(&info->arg, rec->private_data) < 0) {
module_put(rec->oper.owner);
snd_use_lock_free(&rec->use_lock);
continue;
}
info->nr_voices = rec->nr_voices;
if (info->nr_voices > 0) {
info->ch = kcalloc(info->nr_voices, sizeof(seq_oss_chinfo_t), GFP_KERNEL);
if (!info->ch)
BUG();
reset_channels(info);
}
debug_printk(("synth %d assigned\n", i));
info->opened++;
rec->opened++;
dp->synth_opened++;
snd_use_lock_free(&rec->use_lock);
}
}
/*
* set up synth tables for MIDI emulation - /dev/music mode only
*/
void
snd_seq_oss_synth_setup_midi(seq_oss_devinfo_t *dp)
{
int i;
if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)
return;
for (i = 0; i < dp->max_mididev; i++) {
seq_oss_synthinfo_t *info;
info = &dp->synths[dp->max_synthdev];
if (snd_seq_oss_midi_open(dp, i, dp->file_mode) < 0)
continue;
info->arg.app_index = dp->port;
info->arg.file_mode = dp->file_mode;
info->arg.seq_mode = dp->seq_mode;
info->arg.private_data = info;
info->is_midi = 1;
info->midi_mapped = i;
info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS;
snd_seq_oss_midi_get_addr(dp, i, &info->arg.addr);
info->opened = 1;
midi_synth_dev.opened++;
dp->max_synthdev++;
if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)
break;
}
}
/*
* clean up synth tables
*/
void
snd_seq_oss_synth_cleanup(seq_oss_devinfo_t *dp)
{
int i;
seq_oss_synth_t *rec;
seq_oss_synthinfo_t *info;
snd_assert(dp->max_synthdev <= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS, return);
for (i = 0; i < dp->max_synthdev; i++) {
info = &dp->synths[i];
if (! info->opened)
continue;
if (info->is_midi) {
if (midi_synth_dev.opened > 0) {
snd_seq_oss_midi_close(dp, info->midi_mapped);
midi_synth_dev.opened--;
}
} else {
rec = get_sdev(i);
if (rec == NULL)
continue;
if (rec->opened > 0) {
debug_printk(("synth %d closed\n", i));
rec->oper.close(&info->arg);
module_put(rec->oper.owner);
rec->opened = 0;
}
snd_use_lock_free(&rec->use_lock);
}
if (info->sysex) {
kfree(info->sysex);
info->sysex = NULL;
}
if (info->ch) {
kfree(info->ch);
info->ch = NULL;
}
}
dp->synth_opened = 0;
dp->max_synthdev = 0;
}
/*
* check if the specified device is MIDI mapped device
*/
static int
is_midi_dev(seq_oss_devinfo_t *dp, int dev)
{
if (dev < 0 || dev >= dp->max_synthdev)
return 0;
if (dp->synths[dev].is_midi)
return 1;
return 0;
}
/*
* return synth device information pointer
*/
static seq_oss_synth_t *
get_synthdev(seq_oss_devinfo_t *dp, int dev)
{
seq_oss_synth_t *rec;
if (dev < 0 || dev >= dp->max_synthdev)
return NULL;
if (! dp->synths[dev].opened)
return NULL;
if (dp->synths[dev].is_midi)
return &midi_synth_dev;
if ((rec = get_sdev(dev)) == NULL)
return NULL;
if (! rec->opened) {
snd_use_lock_free(&rec->use_lock);
return NULL;
}
return rec;
}
/*
* reset note and velocity on each channel.
*/
static void
reset_channels(seq_oss_synthinfo_t *info)
{
int i;
if (info->ch == NULL || ! info->nr_voices)
return;
for (i = 0; i < info->nr_voices; i++) {
info->ch[i].note = -1;
info->ch[i].vel = 0;
}
}
/*
* reset synth device:
* call reset callback. if no callback is defined, send a heartbeat
* event to the corresponding port.
*/
void
snd_seq_oss_synth_reset(seq_oss_devinfo_t *dp, int dev)
{
seq_oss_synth_t *rec;
seq_oss_synthinfo_t *info;
snd_assert(dev >= 0 && dev < dp->max_synthdev, return);
info = &dp->synths[dev];
if (! info->opened)
return;
if (info->sysex)
info->sysex->len = 0; /* reset sysex */
reset_channels(info);
if (info->is_midi) {
if (midi_synth_dev.opened <= 0)
return;
snd_seq_oss_midi_reset(dp, info->midi_mapped);
/* reopen the device */
snd_seq_oss_midi_close(dp, dev);
if (snd_seq_oss_midi_open(dp, info->midi_mapped,
dp->file_mode) < 0) {
midi_synth_dev.opened--;
info->opened = 0;
if (info->sysex) {
kfree(info->sysex);
info->sysex = NULL;
}
if (info->ch) {
kfree(info->ch);
info->ch = NULL;
}
}
return;
}
rec = get_sdev(dev);
if (rec == NULL)
return;
if (rec->oper.reset) {
rec->oper.reset(&info->arg);
} else {
snd_seq_event_t ev;
memset(&ev, 0, sizeof(ev));
snd_seq_oss_fill_addr(dp, &ev, info->arg.addr.client,
info->arg.addr.port);
ev.type = SNDRV_SEQ_EVENT_RESET;
snd_seq_oss_dispatch(dp, &ev, 0, 0);
}
snd_use_lock_free(&rec->use_lock);
}
/*
* load a patch record:
* call load_patch callback function
*/
int
snd_seq_oss_synth_load_patch(seq_oss_devinfo_t *dp, int dev, int fmt,
const char __user *buf, int p, int c)
{
seq_oss_synth_t *rec;
int rc;
if (dev < 0 || dev >= dp->max_synthdev)
return -ENXIO;
if (is_midi_dev(dp, dev))
return 0;
if ((rec = get_synthdev(dp, dev)) == NULL)
return -ENXIO;
if (rec->oper.load_patch == NULL)
rc = -ENXIO;
else
rc = rec->oper.load_patch(&dp->synths[dev].arg, fmt, buf, p, c);
snd_use_lock_free(&rec->use_lock);
return rc;
}
/*
* check if the device is valid synth device
*/
int
snd_seq_oss_synth_is_valid(seq_oss_devinfo_t *dp, int dev)
{
seq_oss_synth_t *rec;
rec = get_synthdev(dp, dev);
if (rec) {
snd_use_lock_free(&rec->use_lock);
return 1;
}
return 0;
}
/*
* receive OSS 6 byte sysex packet:
* the full sysex message will be sent if it reaches to the end of data
* (0xff).
*/
int
snd_seq_oss_synth_sysex(seq_oss_devinfo_t *dp, int dev, unsigned char *buf, snd_seq_event_t *ev)
{
int i, send;
unsigned char *dest;
seq_oss_synth_sysex_t *sysex;
if (! snd_seq_oss_synth_is_valid(dp, dev))
return -ENXIO;
sysex = dp->synths[dev].sysex;
if (sysex == NULL) {
sysex = kcalloc(1, sizeof(*sysex), GFP_KERNEL);
if (sysex == NULL)
return -ENOMEM;
dp->synths[dev].sysex = sysex;
}
send = 0;
dest = sysex->buf + sysex->len;
/* copy 6 byte packet to the buffer */
for (i = 0; i < 6; i++) {
if (buf[i] == 0xff) {
send = 1;
break;
}
dest[i] = buf[i];
sysex->len++;
if (sysex->len >= MAX_SYSEX_BUFLEN) {
sysex->len = 0;
sysex->skip = 1;
break;
}
}
if (sysex->len && send) {
if (sysex->skip) {
sysex->skip = 0;
sysex->len = 0;
return -EINVAL; /* skip */
}
/* copy the data to event record and send it */
ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
if (snd_seq_oss_synth_addr(dp, dev, ev))
return -EINVAL;
ev->data.ext.len = sysex->len;
ev->data.ext.ptr = sysex->buf;
sysex->len = 0;
return 0;
}
return -EINVAL; /* skip */
}
/*
* fill the event source/destination addresses
*/
int
snd_seq_oss_synth_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_event_t *ev)
{
if (! snd_seq_oss_synth_is_valid(dp, dev))
return -EINVAL;
snd_seq_oss_fill_addr(dp, ev, dp->synths[dev].arg.addr.client,
dp->synths[dev].arg.addr.port);
return 0;
}
/*
* OSS compatible ioctl
*/
int
snd_seq_oss_synth_ioctl(seq_oss_devinfo_t *dp, int dev, unsigned int cmd, unsigned long addr)
{
seq_oss_synth_t *rec;
int rc;
if (is_midi_dev(dp, dev))
return -ENXIO;
if ((rec = get_synthdev(dp, dev)) == NULL)
return -ENXIO;
if (rec->oper.ioctl == NULL)
rc = -ENXIO;
else
rc = rec->oper.ioctl(&dp->synths[dev].arg, cmd, addr);
snd_use_lock_free(&rec->use_lock);
return rc;
}
/*
* send OSS raw events - SEQ_PRIVATE and SEQ_VOLUME
*/
int
snd_seq_oss_synth_raw_event(seq_oss_devinfo_t *dp, int dev, unsigned char *data, snd_seq_event_t *ev)
{
if (! snd_seq_oss_synth_is_valid(dp, dev) || is_midi_dev(dp, dev))
return -ENXIO;
ev->type = SNDRV_SEQ_EVENT_OSS;
memcpy(ev->data.raw8.d, data, 8);
return snd_seq_oss_synth_addr(dp, dev, ev);
}
/*
* create OSS compatible synth_info record
*/
int
snd_seq_oss_synth_make_info(seq_oss_devinfo_t *dp, int dev, struct synth_info *inf)
{
seq_oss_synth_t *rec;
if (dp->synths[dev].is_midi) {
struct midi_info minf;
snd_seq_oss_midi_make_info(dp, dp->synths[dev].midi_mapped, &minf);
inf->synth_type = SYNTH_TYPE_MIDI;
inf->synth_subtype = 0;
inf->nr_voices = 16;
inf->device = dev;
strlcpy(inf->name, minf.name, sizeof(inf->name));
} else {
if ((rec = get_synthdev(dp, dev)) == NULL)
return -ENXIO;
inf->synth_type = rec->synth_type;
inf->synth_subtype = rec->synth_subtype;
inf->nr_voices = rec->nr_voices;
inf->device = dev;
strlcpy(inf->name, rec->name, sizeof(inf->name));
snd_use_lock_free(&rec->use_lock);
}
return 0;
}
/*
* proc interface
*/
void
snd_seq_oss_synth_info_read(snd_info_buffer_t *buf)
{
int i;
seq_oss_synth_t *rec;
snd_iprintf(buf, "\nNumber of synth devices: %d\n", max_synth_devs);
for (i = 0; i < max_synth_devs; i++) {
snd_iprintf(buf, "\nsynth %d: ", i);
rec = get_sdev(i);
if (rec == NULL) {
snd_iprintf(buf, "*empty*\n");
continue;
}
snd_iprintf(buf, "[%s]\n", rec->name);
snd_iprintf(buf, " type 0x%x : subtype 0x%x : voices %d\n",
rec->synth_type, rec->synth_subtype,
rec->nr_voices);
snd_iprintf(buf, " capabilities : ioctl %s / load_patch %s\n",
enabled_str((long)rec->oper.ioctl),
enabled_str((long)rec->oper.load_patch));
snd_use_lock_free(&rec->use_lock);
}
}

View File

@@ -0,0 +1,49 @@
/*
* OSS compatible sequencer driver
*
* synth device information
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __SEQ_OSS_SYNTH_H
#define __SEQ_OSS_SYNTH_H
#include "seq_oss_device.h"
#include <sound/seq_oss_legacy.h>
#include <sound/seq_device.h>
typedef struct seq_oss_synth_t seq_oss_synth_t;
void snd_seq_oss_synth_init(void);
int snd_seq_oss_synth_register(snd_seq_device_t *dev);
int snd_seq_oss_synth_unregister(snd_seq_device_t *dev);
void snd_seq_oss_synth_setup(seq_oss_devinfo_t *dp);
void snd_seq_oss_synth_setup_midi(seq_oss_devinfo_t *dp);
void snd_seq_oss_synth_cleanup(seq_oss_devinfo_t *dp);
void snd_seq_oss_synth_reset(seq_oss_devinfo_t *dp, int dev);
int snd_seq_oss_synth_load_patch(seq_oss_devinfo_t *dp, int dev, int fmt, const char __user *buf, int p, int c);
int snd_seq_oss_synth_is_valid(seq_oss_devinfo_t *dp, int dev);
int snd_seq_oss_synth_sysex(seq_oss_devinfo_t *dp, int dev, unsigned char *buf, snd_seq_event_t *ev);
int snd_seq_oss_synth_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_event_t *ev);
int snd_seq_oss_synth_ioctl(seq_oss_devinfo_t *dp, int dev, unsigned int cmd, unsigned long addr);
int snd_seq_oss_synth_raw_event(seq_oss_devinfo_t *dp, int dev, unsigned char *data, snd_seq_event_t *ev);
int snd_seq_oss_synth_make_info(seq_oss_devinfo_t *dp, int dev, struct synth_info *inf);
#endif

View File

@@ -0,0 +1,283 @@
/*
* OSS compatible sequencer driver
*
* Timer control routines
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "seq_oss_timer.h"
#include "seq_oss_event.h"
#include <sound/seq_oss_legacy.h>
/*
*/
#define MIN_OSS_TEMPO 8
#define MAX_OSS_TEMPO 360
#define MIN_OSS_TIMEBASE 1
#define MAX_OSS_TIMEBASE 1000
/*
*/
static void calc_alsa_tempo(seq_oss_timer_t *timer);
static int send_timer_event(seq_oss_devinfo_t *dp, int type, int value);
/*
* create and register a new timer.
* if queue is not started yet, start it.
*/
seq_oss_timer_t *
snd_seq_oss_timer_new(seq_oss_devinfo_t *dp)
{
seq_oss_timer_t *rec;
rec = kcalloc(1, sizeof(*rec), GFP_KERNEL);
if (rec == NULL)
return NULL;
rec->dp = dp;
rec->cur_tick = 0;
rec->realtime = 0;
rec->running = 0;
rec->oss_tempo = 60;
rec->oss_timebase = 100;
calc_alsa_tempo(rec);
return rec;
}
/*
* delete timer.
* if no more timer exists, stop the queue.
*/
void
snd_seq_oss_timer_delete(seq_oss_timer_t *rec)
{
if (rec) {
snd_seq_oss_timer_stop(rec);
kfree(rec);
}
}
/*
* process one timing event
* return 1 : event proceseed -- skip this event
* 0 : not a timer event -- enqueue this event
*/
int
snd_seq_oss_process_timer_event(seq_oss_timer_t *rec, evrec_t *ev)
{
abstime_t parm = ev->t.time;
if (ev->t.code == EV_TIMING) {
switch (ev->t.cmd) {
case TMR_WAIT_REL:
parm += rec->cur_tick;
rec->realtime = 0;
/* continue to next */
case TMR_WAIT_ABS:
if (parm == 0) {
rec->realtime = 1;
} else if (parm >= rec->cur_tick) {
rec->realtime = 0;
rec->cur_tick = parm;
}
return 1; /* skip this event */
case TMR_START:
snd_seq_oss_timer_start(rec);
return 1;
}
} else if (ev->s.code == SEQ_WAIT) {
/* time = from 1 to 3 bytes */
parm = (ev->echo >> 8) & 0xffffff;
if (parm > rec->cur_tick) {
/* set next event time */
rec->cur_tick = parm;
rec->realtime = 0;
}
return 1;
}
return 0;
}
/*
* convert tempo units
*/
static void
calc_alsa_tempo(seq_oss_timer_t *timer)
{
timer->tempo = (60 * 1000000) / timer->oss_tempo;
timer->ppq = timer->oss_timebase;
}
/*
* dispatch a timer event
*/
static int
send_timer_event(seq_oss_devinfo_t *dp, int type, int value)
{
snd_seq_event_t ev;
memset(&ev, 0, sizeof(ev));
ev.type = type;
ev.source.client = dp->cseq;
ev.source.port = 0;
ev.dest.client = SNDRV_SEQ_CLIENT_SYSTEM;
ev.dest.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
ev.queue = dp->queue;
ev.data.queue.queue = dp->queue;
ev.data.queue.param.value = value;
return snd_seq_kernel_client_dispatch(dp->cseq, &ev, 1, 0);
}
/*
* set queue tempo and start queue
*/
int
snd_seq_oss_timer_start(seq_oss_timer_t *timer)
{
seq_oss_devinfo_t *dp = timer->dp;
snd_seq_queue_tempo_t tmprec;
if (timer->running)
snd_seq_oss_timer_stop(timer);
memset(&tmprec, 0, sizeof(tmprec));
tmprec.queue = dp->queue;
tmprec.ppq = timer->ppq;
tmprec.tempo = timer->tempo;
snd_seq_set_queue_tempo(dp->cseq, &tmprec);
send_timer_event(dp, SNDRV_SEQ_EVENT_START, 0);
timer->running = 1;
timer->cur_tick = 0;
return 0;
}
/*
* stop queue
*/
int
snd_seq_oss_timer_stop(seq_oss_timer_t *timer)
{
if (! timer->running)
return 0;
send_timer_event(timer->dp, SNDRV_SEQ_EVENT_STOP, 0);
timer->running = 0;
return 0;
}
/*
* continue queue
*/
int
snd_seq_oss_timer_continue(seq_oss_timer_t *timer)
{
if (timer->running)
return 0;
send_timer_event(timer->dp, SNDRV_SEQ_EVENT_CONTINUE, 0);
timer->running = 1;
return 0;
}
/*
* change queue tempo
*/
int
snd_seq_oss_timer_tempo(seq_oss_timer_t *timer, int value)
{
if (value < MIN_OSS_TEMPO)
value = MIN_OSS_TEMPO;
else if (value > MAX_OSS_TEMPO)
value = MAX_OSS_TEMPO;
timer->oss_tempo = value;
calc_alsa_tempo(timer);
if (timer->running)
send_timer_event(timer->dp, SNDRV_SEQ_EVENT_TEMPO, timer->tempo);
return 0;
}
/*
* ioctls
*/
int
snd_seq_oss_timer_ioctl(seq_oss_timer_t *timer, unsigned int cmd, int __user *arg)
{
int value;
if (cmd == SNDCTL_SEQ_CTRLRATE) {
debug_printk(("ctrl rate\n"));
/* if *arg == 0, just return the current rate */
if (get_user(value, arg))
return -EFAULT;
if (value)
return -EINVAL;
value = ((timer->oss_tempo * timer->oss_timebase) + 30) / 60;
return put_user(value, arg) ? -EFAULT : 0;
}
if (timer->dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH)
return 0;
switch (cmd) {
case SNDCTL_TMR_START:
debug_printk(("timer start\n"));
return snd_seq_oss_timer_start(timer);
case SNDCTL_TMR_STOP:
debug_printk(("timer stop\n"));
return snd_seq_oss_timer_stop(timer);
case SNDCTL_TMR_CONTINUE:
debug_printk(("timer continue\n"));
return snd_seq_oss_timer_continue(timer);
case SNDCTL_TMR_TEMPO:
debug_printk(("timer tempo\n"));
if (get_user(value, arg))
return -EFAULT;
return snd_seq_oss_timer_tempo(timer, value);
case SNDCTL_TMR_TIMEBASE:
debug_printk(("timer timebase\n"));
if (get_user(value, arg))
return -EFAULT;
if (value < MIN_OSS_TIMEBASE)
value = MIN_OSS_TIMEBASE;
else if (value > MAX_OSS_TIMEBASE)
value = MAX_OSS_TIMEBASE;
timer->oss_timebase = value;
calc_alsa_tempo(timer);
return 0;
case SNDCTL_TMR_METRONOME:
case SNDCTL_TMR_SELECT:
case SNDCTL_TMR_SOURCE:
debug_printk(("timer XXX\n"));
/* not supported */
return 0;
}
return 0;
}

View File

@@ -0,0 +1,70 @@
/*
* OSS compatible sequencer driver
* timer handling routines
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __SEQ_OSS_TIMER_H
#define __SEQ_OSS_TIMER_H
#include "seq_oss_device.h"
/*
* timer information definition
*/
struct seq_oss_timer_t {
seq_oss_devinfo_t *dp;
reltime_t cur_tick;
int realtime;
int running;
int tempo, ppq; /* ALSA queue */
int oss_tempo, oss_timebase;
};
seq_oss_timer_t *snd_seq_oss_timer_new(seq_oss_devinfo_t *dp);
void snd_seq_oss_timer_delete(seq_oss_timer_t *dp);
int snd_seq_oss_timer_start(seq_oss_timer_t *timer);
int snd_seq_oss_timer_stop(seq_oss_timer_t *timer);
int snd_seq_oss_timer_continue(seq_oss_timer_t *timer);
int snd_seq_oss_timer_tempo(seq_oss_timer_t *timer, int value);
#define snd_seq_oss_timer_reset snd_seq_oss_timer_start
int snd_seq_oss_timer_ioctl(seq_oss_timer_t *timer, unsigned int cmd, int __user *arg);
/*
* get current processed time
*/
static inline abstime_t
snd_seq_oss_timer_cur_tick(seq_oss_timer_t *timer)
{
return timer->cur_tick;
}
/*
* is realtime event?
*/
static inline int
snd_seq_oss_timer_is_realtime(seq_oss_timer_t *timer)
{
return timer->realtime;
}
#endif

View File

@@ -0,0 +1,170 @@
/*
* OSS compatible sequencer driver
*
* seq_oss_writeq.c - write queue and sync
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "seq_oss_writeq.h"
#include "seq_oss_event.h"
#include "seq_oss_timer.h"
#include <sound/seq_oss_legacy.h>
#include "../seq_lock.h"
#include "../seq_clientmgr.h"
#include <linux/wait.h>
/*
* create a write queue record
*/
seq_oss_writeq_t *
snd_seq_oss_writeq_new(seq_oss_devinfo_t *dp, int maxlen)
{
seq_oss_writeq_t *q;
snd_seq_client_pool_t pool;
if ((q = kcalloc(1, sizeof(*q), GFP_KERNEL)) == NULL)
return NULL;
q->dp = dp;
q->maxlen = maxlen;
spin_lock_init(&q->sync_lock);
q->sync_event_put = 0;
q->sync_time = 0;
init_waitqueue_head(&q->sync_sleep);
memset(&pool, 0, sizeof(pool));
pool.client = dp->cseq;
pool.output_pool = maxlen;
pool.output_room = maxlen / 2;
snd_seq_oss_control(dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool);
return q;
}
/*
* delete the write queue
*/
void
snd_seq_oss_writeq_delete(seq_oss_writeq_t *q)
{
snd_seq_oss_writeq_clear(q); /* to be sure */
kfree(q);
}
/*
* reset the write queue
*/
void
snd_seq_oss_writeq_clear(seq_oss_writeq_t *q)
{
snd_seq_remove_events_t reset;
memset(&reset, 0, sizeof(reset));
reset.remove_mode = SNDRV_SEQ_REMOVE_OUTPUT; /* remove all */
snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_REMOVE_EVENTS, &reset);
/* wake up sleepers if any */
snd_seq_oss_writeq_wakeup(q, 0);
}
/*
* wait until the write buffer has enough room
*/
int
snd_seq_oss_writeq_sync(seq_oss_writeq_t *q)
{
seq_oss_devinfo_t *dp = q->dp;
abstime_t time;
time = snd_seq_oss_timer_cur_tick(dp->timer);
if (q->sync_time >= time)
return 0; /* already finished */
if (! q->sync_event_put) {
snd_seq_event_t ev;
evrec_t *rec;
/* put echoback event */
memset(&ev, 0, sizeof(ev));
ev.flags = 0;
ev.type = SNDRV_SEQ_EVENT_ECHO;
ev.time.tick = time;
/* echo back to itself */
snd_seq_oss_fill_addr(dp, &ev, dp->addr.client, dp->addr.port);
rec = (evrec_t*)&ev.data;
rec->t.code = SEQ_SYNCTIMER;
rec->t.time = time;
q->sync_event_put = 1;
snd_seq_kernel_client_enqueue_blocking(dp->cseq, &ev, NULL, 0, 0);
}
wait_event_interruptible_timeout(q->sync_sleep, ! q->sync_event_put, HZ);
if (signal_pending(current))
/* interrupted - return 0 to finish sync */
q->sync_event_put = 0;
if (! q->sync_event_put || q->sync_time >= time)
return 0;
return 1;
}
/*
* wake up sync - echo event was catched
*/
void
snd_seq_oss_writeq_wakeup(seq_oss_writeq_t *q, abstime_t time)
{
unsigned long flags;
spin_lock_irqsave(&q->sync_lock, flags);
q->sync_time = time;
q->sync_event_put = 0;
if (waitqueue_active(&q->sync_sleep)) {
wake_up(&q->sync_sleep);
}
spin_unlock_irqrestore(&q->sync_lock, flags);
}
/*
* return the unused pool size
*/
int
snd_seq_oss_writeq_get_free_size(seq_oss_writeq_t *q)
{
snd_seq_client_pool_t pool;
pool.client = q->dp->cseq;
snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool);
return pool.output_free;
}
/*
* set output threshold size from ioctl
*/
void
snd_seq_oss_writeq_set_output(seq_oss_writeq_t *q, int val)
{
snd_seq_client_pool_t pool;
pool.client = q->dp->cseq;
snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool);
pool.output_room = val;
snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool);
}

View File

@@ -0,0 +1,50 @@
/*
* OSS compatible sequencer driver
* write priority queue
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __SEQ_OSS_WRITEQ_H
#define __SEQ_OSS_WRITEQ_H
#include "seq_oss_device.h"
struct seq_oss_writeq_t {
seq_oss_devinfo_t *dp;
int maxlen;
abstime_t sync_time;
int sync_event_put;
wait_queue_head_t sync_sleep;
spinlock_t sync_lock;
};
/*
* seq_oss_writeq.c
*/
seq_oss_writeq_t *snd_seq_oss_writeq_new(seq_oss_devinfo_t *dp, int maxlen);
void snd_seq_oss_writeq_delete(seq_oss_writeq_t *q);
void snd_seq_oss_writeq_clear(seq_oss_writeq_t *q);
int snd_seq_oss_writeq_sync(seq_oss_writeq_t *q);
void snd_seq_oss_writeq_wakeup(seq_oss_writeq_t *q, abstime_t time);
int snd_seq_oss_writeq_get_free_size(seq_oss_writeq_t *q);
void snd_seq_oss_writeq_set_output(seq_oss_writeq_t *q, int size);
#endif

147
sound/core/seq/seq.c Normal file
View File

@@ -0,0 +1,147 @@
/*
* ALSA sequencer main module
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/seq_kernel.h>
#include "seq_clientmgr.h"
#include "seq_memory.h"
#include "seq_queue.h"
#include "seq_lock.h"
#include "seq_timer.h"
#include "seq_system.h"
#include "seq_info.h"
#include <sound/seq_device.h>
#if defined(CONFIG_SND_SEQ_DUMMY_MODULE)
int seq_client_load[64] = {[0] = SNDRV_SEQ_CLIENT_DUMMY, [1 ... 63] = -1};
#else
int seq_client_load[64] = {[0 ... 63] = -1};
#endif
int seq_default_timer_class = SNDRV_TIMER_CLASS_GLOBAL;
int seq_default_timer_sclass = SNDRV_TIMER_SCLASS_NONE;
int seq_default_timer_card = -1;
int seq_default_timer_device = SNDRV_TIMER_GLOBAL_SYSTEM;
int seq_default_timer_subdevice = 0;
int seq_default_timer_resolution = 0; /* Hz */
MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer.");
MODULE_LICENSE("GPL");
module_param_array(seq_client_load, int, NULL, 0444);
MODULE_PARM_DESC(seq_client_load, "The numbers of global (system) clients to load through kmod.");
module_param(seq_default_timer_class, int, 0644);
MODULE_PARM_DESC(seq_default_timer_class, "The default timer class.");
module_param(seq_default_timer_sclass, int, 0644);
MODULE_PARM_DESC(seq_default_timer_sclass, "The default timer slave class.");
module_param(seq_default_timer_card, int, 0644);
MODULE_PARM_DESC(seq_default_timer_card, "The default timer card number.");
module_param(seq_default_timer_device, int, 0644);
MODULE_PARM_DESC(seq_default_timer_device, "The default timer device number.");
module_param(seq_default_timer_subdevice, int, 0644);
MODULE_PARM_DESC(seq_default_timer_subdevice, "The default timer subdevice number.");
module_param(seq_default_timer_resolution, int, 0644);
MODULE_PARM_DESC(seq_default_timer_resolution, "The default timer resolution in Hz.");
/*
* INIT PART
*/
static int __init alsa_seq_init(void)
{
int err;
snd_seq_autoload_lock();
if ((err = client_init_data()) < 0)
goto error;
/* init memory, room for selected events */
if ((err = snd_sequencer_memory_init()) < 0)
goto error;
/* init event queues */
if ((err = snd_seq_queues_init()) < 0)
goto error;
/* register sequencer device */
if ((err = snd_sequencer_device_init()) < 0)
goto error;
/* register proc interface */
if ((err = snd_seq_info_init()) < 0)
goto error;
/* register our internal client */
if ((err = snd_seq_system_client_init()) < 0)
goto error;
error:
snd_seq_autoload_unlock();
return err;
}
static void __exit alsa_seq_exit(void)
{
/* unregister our internal client */
snd_seq_system_client_done();
/* unregister proc interface */
snd_seq_info_done();
/* delete timing queues */
snd_seq_queues_delete();
/* unregister sequencer device */
snd_sequencer_device_done();
/* release event memory */
snd_sequencer_memory_done();
}
module_init(alsa_seq_init)
module_exit(alsa_seq_exit)
/* seq_clientmgr.c */
EXPORT_SYMBOL(snd_seq_create_kernel_client);
EXPORT_SYMBOL(snd_seq_delete_kernel_client);
EXPORT_SYMBOL(snd_seq_kernel_client_enqueue);
EXPORT_SYMBOL(snd_seq_kernel_client_enqueue_blocking);
EXPORT_SYMBOL(snd_seq_kernel_client_dispatch);
EXPORT_SYMBOL(snd_seq_kernel_client_ctl);
EXPORT_SYMBOL(snd_seq_kernel_client_write_poll);
EXPORT_SYMBOL(snd_seq_set_queue_tempo);
/* seq_memory.c */
EXPORT_SYMBOL(snd_seq_expand_var_event);
EXPORT_SYMBOL(snd_seq_dump_var_event);
/* seq_ports.c */
EXPORT_SYMBOL(snd_seq_event_port_attach);
EXPORT_SYMBOL(snd_seq_event_port_detach);
/* seq_lock.c */
#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG)
/*EXPORT_SYMBOL(snd_seq_sleep_in_lock);*/
/*EXPORT_SYMBOL(snd_seq_sleep_timeout_in_lock);*/
EXPORT_SYMBOL(snd_use_lock_sync_helper);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
/*
* ALSA sequencer Client Manager
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef __SND_SEQ_CLIENTMGR_H
#define __SND_SEQ_CLIENTMGR_H
#include <sound/seq_kernel.h>
#include <linux/bitops.h>
#include "seq_fifo.h"
#include "seq_ports.h"
#include "seq_lock.h"
/* client manager */
struct _snd_seq_user_client {
struct file *file; /* file struct of client */
/* ... */
/* fifo */
fifo_t *fifo; /* queue for incoming events */
int fifo_pool_size;
};
struct _snd_seq_kernel_client {
snd_card_t *card;
/* pointer to client functions */
void *private_data; /* private data for client */
/* ... */
};
struct _snd_seq_client {
snd_seq_client_type_t type;
unsigned int accept_input: 1,
accept_output: 1;
char name[64]; /* client name */
int number; /* client number */
unsigned int filter; /* filter flags */
DECLARE_BITMAP(event_filter, 256);
snd_use_lock_t use_lock;
int event_lost;
/* ports */
int num_ports; /* number of ports */
struct list_head ports_list_head;
rwlock_t ports_lock;
struct semaphore ports_mutex;
int convert32; /* convert 32->64bit */
/* output pool */
pool_t *pool; /* memory pool for this client */
union {
user_client_t user;
kernel_client_t kernel;
} data;
};
/* usage statistics */
typedef struct {
int cur;
int peak;
} usage_t;
extern int client_init_data(void);
extern int snd_sequencer_device_init(void);
extern void snd_sequencer_device_done(void);
/* get locked pointer to client */
extern client_t *snd_seq_client_use_ptr(int clientid);
/* unlock pointer to client */
#define snd_seq_client_unlock(client) snd_use_lock_free(&(client)->use_lock)
/* dispatch event to client(s) */
extern int snd_seq_dispatch_event(snd_seq_event_cell_t *cell, int atomic, int hop);
/* exported to other modules */
extern int snd_seq_register_kernel_client(snd_seq_client_callback_t *callback, void *private_data);
extern int snd_seq_unregister_kernel_client(int client);
extern int snd_seq_kernel_client_enqueue(int client, snd_seq_event_t *ev, int atomic, int hop);
int snd_seq_kernel_client_enqueue_blocking(int client, snd_seq_event_t * ev, struct file *file, int atomic, int hop);
int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait);
int snd_seq_client_notify_subscription(int client, int port, snd_seq_port_subscribe_t *info, int evtype);
#endif

137
sound/core/seq/seq_compat.c Normal file
View File

@@ -0,0 +1,137 @@
/*
* 32bit -> 64bit ioctl wrapper for sequencer API
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* This file included from seq.c */
#include <linux/compat.h>
struct sndrv_seq_port_info32 {
struct sndrv_seq_addr addr; /* client/port numbers */
char name[64]; /* port name */
u32 capability; /* port capability bits */
u32 type; /* port type bits */
s32 midi_channels; /* channels per MIDI port */
s32 midi_voices; /* voices per MIDI port */
s32 synth_voices; /* voices per SYNTH port */
s32 read_use; /* R/O: subscribers for output (from this port) */
s32 write_use; /* R/O: subscribers for input (to this port) */
u32 kernel; /* reserved for kernel use (must be NULL) */
u32 flags; /* misc. conditioning */
unsigned char time_queue; /* queue # for timestamping */
char reserved[59]; /* for future use */
};
static int snd_seq_call_port_info_ioctl(client_t *client, unsigned int cmd,
struct sndrv_seq_port_info32 __user *data32)
{
int err = -EFAULT;
snd_seq_port_info_t *data;
mm_segment_t fs;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (! data)
return -ENOMEM;
if (copy_from_user(data, data32, sizeof(*data32)) ||
get_user(data->flags, &data32->flags) ||
get_user(data->time_queue, &data32->time_queue))
goto error;
data->kernel = NULL;
fs = snd_enter_user();
err = snd_seq_do_ioctl(client, cmd, data);
snd_leave_user(fs);
if (err < 0)
goto error;
if (copy_to_user(data32, data, sizeof(*data32)) ||
put_user(data->flags, &data32->flags) ||
put_user(data->time_queue, &data32->time_queue))
err = -EFAULT;
error:
kfree(data);
return err;
}
/*
*/
enum {
SNDRV_SEQ_IOCTL_CREATE_PORT32 = _IOWR('S', 0x20, struct sndrv_seq_port_info32),
SNDRV_SEQ_IOCTL_DELETE_PORT32 = _IOW ('S', 0x21, struct sndrv_seq_port_info32),
SNDRV_SEQ_IOCTL_GET_PORT_INFO32 = _IOWR('S', 0x22, struct sndrv_seq_port_info32),
SNDRV_SEQ_IOCTL_SET_PORT_INFO32 = _IOW ('S', 0x23, struct sndrv_seq_port_info32),
SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32 = _IOWR('S', 0x52, struct sndrv_seq_port_info32),
};
static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
{
client_t *client = (client_t *) file->private_data;
void __user *argp = compat_ptr(arg);
snd_assert(client != NULL, return -ENXIO);
switch (cmd) {
case SNDRV_SEQ_IOCTL_PVERSION:
case SNDRV_SEQ_IOCTL_CLIENT_ID:
case SNDRV_SEQ_IOCTL_SYSTEM_INFO:
case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO:
case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO:
case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT:
case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT:
case SNDRV_SEQ_IOCTL_CREATE_QUEUE:
case SNDRV_SEQ_IOCTL_DELETE_QUEUE:
case SNDRV_SEQ_IOCTL_GET_QUEUE_INFO:
case SNDRV_SEQ_IOCTL_SET_QUEUE_INFO:
case SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE:
case SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS:
case SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO:
case SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO:
case SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER:
case SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER:
case SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT:
case SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT:
case SNDRV_SEQ_IOCTL_GET_CLIENT_POOL:
case SNDRV_SEQ_IOCTL_SET_CLIENT_POOL:
case SNDRV_SEQ_IOCTL_REMOVE_EVENTS:
case SNDRV_SEQ_IOCTL_QUERY_SUBS:
case SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION:
case SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT:
case SNDRV_SEQ_IOCTL_RUNNING_MODE:
return snd_seq_do_ioctl(client, cmd, argp);
case SNDRV_SEQ_IOCTL_CREATE_PORT32:
return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, argp);
case SNDRV_SEQ_IOCTL_DELETE_PORT32:
return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_DELETE_PORT, argp);
case SNDRV_SEQ_IOCTL_GET_PORT_INFO32:
return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_GET_PORT_INFO, argp);
case SNDRV_SEQ_IOCTL_SET_PORT_INFO32:
return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_SET_PORT_INFO, argp);
case SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32:
return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, argp);
}
return -ENOIOCTLCMD;
}

575
sound/core/seq/seq_device.c Normal file
View File

@@ -0,0 +1,575 @@
/*
* ALSA sequencer device management
* Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
*----------------------------------------------------------------
*
* This device handler separates the card driver module from sequencer
* stuff (sequencer core, synth drivers, etc), so that user can avoid
* to spend unnecessary resources e.g. if he needs only listening to
* MP3s.
*
* The card (or lowlevel) driver creates a sequencer device entry
* via snd_seq_device_new(). This is an entry pointer to communicate
* with the sequencer device "driver", which is involved with the
* actual part to communicate with the sequencer core.
* Each sequencer device entry has an id string and the corresponding
* driver with the same id is loaded when required. For example,
* lowlevel codes to access emu8000 chip on sbawe card are included in
* emu8000-synth module. To activate this module, the hardware
* resources like i/o port are passed via snd_seq_device argument.
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/seq_device.h>
#include <sound/seq_kernel.h>
#include <sound/initval.h>
#include <linux/kmod.h>
#include <linux/slab.h>
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("ALSA sequencer device management");
MODULE_LICENSE("GPL");
/*
* driver list
*/
typedef struct ops_list ops_list_t;
/* driver state */
#define DRIVER_EMPTY 0
#define DRIVER_LOADED (1<<0)
#define DRIVER_REQUESTED (1<<1)
#define DRIVER_LOCKED (1<<2)
struct ops_list {
char id[ID_LEN]; /* driver id */
int driver; /* driver state */
int used; /* reference counter */
int argsize; /* argument size */
/* operators */
snd_seq_dev_ops_t ops;
/* registred devices */
struct list_head dev_list; /* list of devices */
int num_devices; /* number of associated devices */
int num_init_devices; /* number of initialized devices */
struct semaphore reg_mutex;
struct list_head list; /* next driver */
};
static LIST_HEAD(opslist);
static int num_ops;
static DECLARE_MUTEX(ops_mutex);
static snd_info_entry_t *info_entry = NULL;
/*
* prototypes
*/
static int snd_seq_device_free(snd_seq_device_t *dev);
static int snd_seq_device_dev_free(snd_device_t *device);
static int snd_seq_device_dev_register(snd_device_t *device);
static int snd_seq_device_dev_disconnect(snd_device_t *device);
static int snd_seq_device_dev_unregister(snd_device_t *device);
static int init_device(snd_seq_device_t *dev, ops_list_t *ops);
static int free_device(snd_seq_device_t *dev, ops_list_t *ops);
static ops_list_t *find_driver(char *id, int create_if_empty);
static ops_list_t *create_driver(char *id);
static void unlock_driver(ops_list_t *ops);
static void remove_drivers(void);
/*
* show all drivers and their status
*/
static void snd_seq_device_info(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
{
struct list_head *head;
down(&ops_mutex);
list_for_each(head, &opslist) {
ops_list_t *ops = list_entry(head, ops_list_t, list);
snd_iprintf(buffer, "snd-%s%s%s%s,%d\n",
ops->id,
ops->driver & DRIVER_LOADED ? ",loaded" : (ops->driver == DRIVER_EMPTY ? ",empty" : ""),
ops->driver & DRIVER_REQUESTED ? ",requested" : "",
ops->driver & DRIVER_LOCKED ? ",locked" : "",
ops->num_devices);
}
up(&ops_mutex);
}
/*
* load all registered drivers (called from seq_clientmgr.c)
*/
#ifdef CONFIG_KMOD
/* avoid auto-loading during module_init() */
static int snd_seq_in_init;
void snd_seq_autoload_lock(void)
{
snd_seq_in_init++;
}
void snd_seq_autoload_unlock(void)
{
snd_seq_in_init--;
}
#endif
void snd_seq_device_load_drivers(void)
{
#ifdef CONFIG_KMOD
struct list_head *head;
/* Calling request_module during module_init()
* may cause blocking.
*/
if (snd_seq_in_init)
return;
if (! current->fs->root)
return;
down(&ops_mutex);
list_for_each(head, &opslist) {
ops_list_t *ops = list_entry(head, ops_list_t, list);
if (! (ops->driver & DRIVER_LOADED) &&
! (ops->driver & DRIVER_REQUESTED)) {
ops->used++;
up(&ops_mutex);
ops->driver |= DRIVER_REQUESTED;
request_module("snd-%s", ops->id);
down(&ops_mutex);
ops->used--;
}
}
up(&ops_mutex);
#endif
}
/*
* register a sequencer device
* card = card info (NULL allowed)
* device = device number (if any)
* id = id of driver
* result = return pointer (NULL allowed if unnecessary)
*/
int snd_seq_device_new(snd_card_t *card, int device, char *id, int argsize,
snd_seq_device_t **result)
{
snd_seq_device_t *dev;
ops_list_t *ops;
int err;
static snd_device_ops_t dops = {
.dev_free = snd_seq_device_dev_free,
.dev_register = snd_seq_device_dev_register,
.dev_disconnect = snd_seq_device_dev_disconnect,
.dev_unregister = snd_seq_device_dev_unregister
};
if (result)
*result = NULL;
snd_assert(id != NULL, return -EINVAL);
ops = find_driver(id, 1);
if (ops == NULL)
return -ENOMEM;
dev = kcalloc(1, sizeof(*dev)*2 + argsize, GFP_KERNEL);
if (dev == NULL) {
unlock_driver(ops);
return -ENOMEM;
}
/* set up device info */
dev->card = card;
dev->device = device;
strlcpy(dev->id, id, sizeof(dev->id));
dev->argsize = argsize;
dev->status = SNDRV_SEQ_DEVICE_FREE;
/* add this device to the list */
down(&ops->reg_mutex);
list_add_tail(&dev->list, &ops->dev_list);
ops->num_devices++;
up(&ops->reg_mutex);
unlock_driver(ops);
if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) {
snd_seq_device_free(dev);
return err;
}
if (result)
*result = dev;
return 0;
}
/*
* free the existing device
*/
static int snd_seq_device_free(snd_seq_device_t *dev)
{
ops_list_t *ops;
snd_assert(dev != NULL, return -EINVAL);
ops = find_driver(dev->id, 0);
if (ops == NULL)
return -ENXIO;
/* remove the device from the list */
down(&ops->reg_mutex);
list_del(&dev->list);
ops->num_devices--;
up(&ops->reg_mutex);
free_device(dev, ops);
if (dev->private_free)
dev->private_free(dev);
kfree(dev);
unlock_driver(ops);
return 0;
}
static int snd_seq_device_dev_free(snd_device_t *device)
{
snd_seq_device_t *dev = device->device_data;
return snd_seq_device_free(dev);
}
/*
* register the device
*/
static int snd_seq_device_dev_register(snd_device_t *device)
{
snd_seq_device_t *dev = device->device_data;
ops_list_t *ops;
ops = find_driver(dev->id, 0);
if (ops == NULL)
return -ENOENT;
/* initialize this device if the corresponding driver was
* already loaded
*/
if (ops->driver & DRIVER_LOADED)
init_device(dev, ops);
unlock_driver(ops);
return 0;
}
/*
* disconnect the device
*/
static int snd_seq_device_dev_disconnect(snd_device_t *device)
{
snd_seq_device_t *dev = device->device_data;
ops_list_t *ops;
ops = find_driver(dev->id, 0);
if (ops == NULL)
return -ENOENT;
free_device(dev, ops);
unlock_driver(ops);
return 0;
}
/*
* unregister the existing device
*/
static int snd_seq_device_dev_unregister(snd_device_t *device)
{
snd_seq_device_t *dev = device->device_data;
return snd_seq_device_free(dev);
}
/*
* register device driver
* id = driver id
* entry = driver operators - duplicated to each instance
*/
int snd_seq_device_register_driver(char *id, snd_seq_dev_ops_t *entry, int argsize)
{
struct list_head *head;
ops_list_t *ops;
if (id == NULL || entry == NULL ||
entry->init_device == NULL || entry->free_device == NULL)
return -EINVAL;
snd_seq_autoload_lock();
ops = find_driver(id, 1);
if (ops == NULL) {
snd_seq_autoload_unlock();
return -ENOMEM;
}
if (ops->driver & DRIVER_LOADED) {
snd_printk(KERN_WARNING "driver_register: driver '%s' already exists\n", id);
unlock_driver(ops);
snd_seq_autoload_unlock();
return -EBUSY;
}
down(&ops->reg_mutex);
/* copy driver operators */
ops->ops = *entry;
ops->driver |= DRIVER_LOADED;
ops->argsize = argsize;
/* initialize existing devices if necessary */
list_for_each(head, &ops->dev_list) {
snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, list);
init_device(dev, ops);
}
up(&ops->reg_mutex);
unlock_driver(ops);
snd_seq_autoload_unlock();
return 0;
}
/*
* create driver record
*/
static ops_list_t * create_driver(char *id)
{
ops_list_t *ops;
ops = kmalloc(sizeof(*ops), GFP_KERNEL);
if (ops == NULL)
return ops;
memset(ops, 0, sizeof(*ops));
/* set up driver entry */
strlcpy(ops->id, id, sizeof(ops->id));
init_MUTEX(&ops->reg_mutex);
ops->driver = DRIVER_EMPTY;
INIT_LIST_HEAD(&ops->dev_list);
/* lock this instance */
ops->used = 1;
/* register driver entry */
down(&ops_mutex);
list_add_tail(&ops->list, &opslist);
num_ops++;
up(&ops_mutex);
return ops;
}
/*
* unregister the specified driver
*/
int snd_seq_device_unregister_driver(char *id)
{
struct list_head *head;
ops_list_t *ops;
ops = find_driver(id, 0);
if (ops == NULL)
return -ENXIO;
if (! (ops->driver & DRIVER_LOADED) ||
(ops->driver & DRIVER_LOCKED)) {
snd_printk(KERN_ERR "driver_unregister: cannot unload driver '%s': status=%x\n", id, ops->driver);
unlock_driver(ops);
return -EBUSY;
}
/* close and release all devices associated with this driver */
down(&ops->reg_mutex);
ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */
list_for_each(head, &ops->dev_list) {
snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, list);
free_device(dev, ops);
}
ops->driver = 0;
if (ops->num_init_devices > 0)
snd_printk(KERN_ERR "free_driver: init_devices > 0!! (%d)\n", ops->num_init_devices);
up(&ops->reg_mutex);
unlock_driver(ops);
/* remove empty driver entries */
remove_drivers();
return 0;
}
/*
* remove empty driver entries
*/
static void remove_drivers(void)
{
struct list_head *head;
down(&ops_mutex);
head = opslist.next;
while (head != &opslist) {
ops_list_t *ops = list_entry(head, ops_list_t, list);
if (! (ops->driver & DRIVER_LOADED) &&
ops->used == 0 && ops->num_devices == 0) {
head = head->next;
list_del(&ops->list);
kfree(ops);
num_ops--;
} else
head = head->next;
}
up(&ops_mutex);
}
/*
* initialize the device - call init_device operator
*/
static int init_device(snd_seq_device_t *dev, ops_list_t *ops)
{
if (! (ops->driver & DRIVER_LOADED))
return 0; /* driver is not loaded yet */
if (dev->status != SNDRV_SEQ_DEVICE_FREE)
return 0; /* already initialized */
if (ops->argsize != dev->argsize) {
snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", dev->name, ops->id, ops->argsize, dev->argsize);
return -EINVAL;
}
if (ops->ops.init_device(dev) >= 0) {
dev->status = SNDRV_SEQ_DEVICE_REGISTERED;
ops->num_init_devices++;
} else {
snd_printk(KERN_ERR "init_device failed: %s: %s\n", dev->name, dev->id);
}
return 0;
}
/*
* release the device - call free_device operator
*/
static int free_device(snd_seq_device_t *dev, ops_list_t *ops)
{
int result;
if (! (ops->driver & DRIVER_LOADED))
return 0; /* driver is not loaded yet */
if (dev->status != SNDRV_SEQ_DEVICE_REGISTERED)
return 0; /* not registered */
if (ops->argsize != dev->argsize) {
snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", dev->name, ops->id, ops->argsize, dev->argsize);
return -EINVAL;
}
if ((result = ops->ops.free_device(dev)) >= 0 || result == -ENXIO) {
dev->status = SNDRV_SEQ_DEVICE_FREE;
dev->driver_data = NULL;
ops->num_init_devices--;
} else {
snd_printk(KERN_ERR "free_device failed: %s: %s\n", dev->name, dev->id);
}
return 0;
}
/*
* find the matching driver with given id
*/
static ops_list_t * find_driver(char *id, int create_if_empty)
{
struct list_head *head;
down(&ops_mutex);
list_for_each(head, &opslist) {
ops_list_t *ops = list_entry(head, ops_list_t, list);
if (strcmp(ops->id, id) == 0) {
ops->used++;
up(&ops_mutex);
return ops;
}
}
up(&ops_mutex);
if (create_if_empty)
return create_driver(id);
return NULL;
}
static void unlock_driver(ops_list_t *ops)
{
down(&ops_mutex);
ops->used--;
up(&ops_mutex);
}
/*
* module part
*/
static int __init alsa_seq_device_init(void)
{
info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", snd_seq_root);
if (info_entry == NULL)
return -ENOMEM;
info_entry->content = SNDRV_INFO_CONTENT_TEXT;
info_entry->c.text.read_size = 2048;
info_entry->c.text.read = snd_seq_device_info;
if (snd_info_register(info_entry) < 0) {
snd_info_free_entry(info_entry);
return -ENOMEM;
}
return 0;
}
static void __exit alsa_seq_device_exit(void)
{
remove_drivers();
snd_info_unregister(info_entry);
if (num_ops)
snd_printk(KERN_ERR "drivers not released (%d)\n", num_ops);
}
module_init(alsa_seq_device_init)
module_exit(alsa_seq_device_exit)
EXPORT_SYMBOL(snd_seq_device_load_drivers);
EXPORT_SYMBOL(snd_seq_device_new);
EXPORT_SYMBOL(snd_seq_device_register_driver);
EXPORT_SYMBOL(snd_seq_device_unregister_driver);
#ifdef CONFIG_KMOD
EXPORT_SYMBOL(snd_seq_autoload_lock);
EXPORT_SYMBOL(snd_seq_autoload_unlock);
#endif

273
sound/core/seq/seq_dummy.c Normal file
View File

@@ -0,0 +1,273 @@
/*
* ALSA sequencer MIDI-through client
* Copyright (c) 1999-2000 by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include "seq_clientmgr.h"
#include <sound/initval.h>
#include <sound/asoundef.h>
/*
Sequencer MIDI-through client
This gives a simple midi-through client. All the normal input events
are redirected to output port immediately.
The routing can be done via aconnect program in alsa-utils.
Each client has a static client number 62 (= SNDRV_SEQ_CLIENT_DUMMY).
If you want to auto-load this module, you may add the following alias
in your /etc/conf.modules file.
alias snd-seq-client-62 snd-seq-dummy
The module is loaded on demand for client 62, or /proc/asound/seq/
is accessed. If you don't need this module to be loaded, alias
snd-seq-client-62 as "off". This will help modprobe.
The number of ports to be created can be specified via the module
parameter "ports". For example, to create four ports, add the
following option in /etc/modprobe.conf:
option snd-seq-dummy ports=4
The modle option "duplex=1" enables duplex operation to the port.
In duplex mode, a pair of ports are created instead of single port,
and events are tunneled between pair-ports. For example, input to
port A is sent to output port of another port B and vice versa.
In duplex mode, each port has DUPLEX capability.
*/
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("ALSA sequencer MIDI-through client");
MODULE_LICENSE("GPL");
MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY));
static int ports = 1;
static int duplex = 0;
module_param(ports, int, 0444);
MODULE_PARM_DESC(ports, "number of ports to be created");
module_param(duplex, bool, 0444);
MODULE_PARM_DESC(duplex, "create DUPLEX ports");
typedef struct snd_seq_dummy_port {
int client;
int port;
int duplex;
int connect;
} snd_seq_dummy_port_t;
static int my_client = -1;
/*
* unuse callback - send ALL_SOUNDS_OFF and RESET_CONTROLLERS events
* to subscribers.
* Note: this callback is called only after all subscribers are removed.
*/
static int
dummy_unuse(void *private_data, snd_seq_port_subscribe_t *info)
{
snd_seq_dummy_port_t *p;
int i;
snd_seq_event_t ev;
p = private_data;
memset(&ev, 0, sizeof(ev));
if (p->duplex)
ev.source.port = p->connect;
else
ev.source.port = p->port;
ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
for (i = 0; i < 16; i++) {
ev.data.control.channel = i;
ev.data.control.param = MIDI_CTL_ALL_SOUNDS_OFF;
snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
ev.data.control.param = MIDI_CTL_RESET_CONTROLLERS;
snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
}
return 0;
}
/*
* event input callback - just redirect events to subscribers
*/
static int
dummy_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop)
{
snd_seq_dummy_port_t *p;
snd_seq_event_t tmpev;
p = private_data;
if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM ||
ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
return 0; /* ignore system messages */
tmpev = *ev;
if (p->duplex)
tmpev.source.port = p->connect;
else
tmpev.source.port = p->port;
tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop);
}
/*
* free_private callback
*/
static void
dummy_free(void *private_data)
{
snd_seq_dummy_port_t *p;
p = private_data;
kfree(p);
}
/*
* create a port
*/
static snd_seq_dummy_port_t __init *
create_port(int idx, int type)
{
snd_seq_port_info_t pinfo;
snd_seq_port_callback_t pcb;
snd_seq_dummy_port_t *rec;
if ((rec = kcalloc(1, sizeof(*rec), GFP_KERNEL)) == NULL)
return NULL;
rec->client = my_client;
rec->duplex = duplex;
rec->connect = 0;
memset(&pinfo, 0, sizeof(pinfo));
pinfo.addr.client = my_client;
if (duplex)
sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
(type ? 'B' : 'A'));
else
sprintf(pinfo.name, "Midi Through Port-%d", idx);
pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
if (duplex)
pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
memset(&pcb, 0, sizeof(pcb));
pcb.owner = THIS_MODULE;
pcb.unuse = dummy_unuse;
pcb.event_input = dummy_input;
pcb.private_free = dummy_free;
pcb.private_data = rec;
pinfo.kernel = &pcb;
if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
kfree(rec);
return NULL;
}
rec->port = pinfo.addr.port;
return rec;
}
/*
* register client and create ports
*/
static int __init
register_client(void)
{
snd_seq_client_callback_t cb;
snd_seq_client_info_t cinfo;
snd_seq_dummy_port_t *rec1, *rec2;
int i;
if (ports < 1) {
snd_printk(KERN_ERR "invalid number of ports %d\n", ports);
return -EINVAL;
}
/* create client */
memset(&cb, 0, sizeof(cb));
cb.allow_input = 1;
cb.allow_output = 1;
my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY, &cb);
if (my_client < 0)
return my_client;
/* set client name */
memset(&cinfo, 0, sizeof(cinfo));
cinfo.client = my_client;
cinfo.type = KERNEL_CLIENT;
strcpy(cinfo.name, "Midi Through");
snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
/* create ports */
for (i = 0; i < ports; i++) {
rec1 = create_port(i, 0);
if (rec1 == NULL) {
snd_seq_delete_kernel_client(my_client);
return -ENOMEM;
}
if (duplex) {
rec2 = create_port(i, 1);
if (rec2 == NULL) {
snd_seq_delete_kernel_client(my_client);
return -ENOMEM;
}
rec1->connect = rec2->port;
rec2->connect = rec1->port;
}
}
return 0;
}
/*
* delete client if exists
*/
static void __exit
delete_client(void)
{
if (my_client >= 0)
snd_seq_delete_kernel_client(my_client);
}
/*
* Init part
*/
static int __init alsa_seq_dummy_init(void)
{
int err;
snd_seq_autoload_lock();
err = register_client();
snd_seq_autoload_unlock();
return err;
}
static void __exit alsa_seq_dummy_exit(void)
{
delete_client();
}
module_init(alsa_seq_dummy_init)
module_exit(alsa_seq_dummy_exit)

264
sound/core/seq/seq_fifo.c Normal file
View File

@@ -0,0 +1,264 @@
/*
* ALSA sequencer FIFO
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <sound/core.h>
#include <linux/slab.h>
#include "seq_fifo.h"
#include "seq_lock.h"
/* FIFO */
/* create new fifo */
fifo_t *snd_seq_fifo_new(int poolsize)
{
fifo_t *f;
f = kcalloc(1, sizeof(*f), GFP_KERNEL);
if (f == NULL) {
snd_printd("malloc failed for snd_seq_fifo_new() \n");
return NULL;
}
f->pool = snd_seq_pool_new(poolsize);
if (f->pool == NULL) {
kfree(f);
return NULL;
}
if (snd_seq_pool_init(f->pool) < 0) {
snd_seq_pool_delete(&f->pool);
kfree(f);
return NULL;
}
spin_lock_init(&f->lock);
snd_use_lock_init(&f->use_lock);
init_waitqueue_head(&f->input_sleep);
atomic_set(&f->overflow, 0);
f->head = NULL;
f->tail = NULL;
f->cells = 0;
return f;
}
void snd_seq_fifo_delete(fifo_t **fifo)
{
fifo_t *f;
snd_assert(fifo != NULL, return);
f = *fifo;
snd_assert(f != NULL, return);
*fifo = NULL;
snd_seq_fifo_clear(f);
/* wake up clients if any */
if (waitqueue_active(&f->input_sleep))
wake_up(&f->input_sleep);
/* release resources...*/
/*....................*/
if (f->pool) {
snd_seq_pool_done(f->pool);
snd_seq_pool_delete(&f->pool);
}
kfree(f);
}
static snd_seq_event_cell_t *fifo_cell_out(fifo_t *f);
/* clear queue */
void snd_seq_fifo_clear(fifo_t *f)
{
snd_seq_event_cell_t *cell;
unsigned long flags;
/* clear overflow flag */
atomic_set(&f->overflow, 0);
snd_use_lock_sync(&f->use_lock);
spin_lock_irqsave(&f->lock, flags);
/* drain the fifo */
while ((cell = fifo_cell_out(f)) != NULL) {
snd_seq_cell_free(cell);
}
spin_unlock_irqrestore(&f->lock, flags);
}
/* enqueue event to fifo */
int snd_seq_fifo_event_in(fifo_t *f, snd_seq_event_t *event)
{
snd_seq_event_cell_t *cell;
unsigned long flags;
int err;
snd_assert(f != NULL, return -EINVAL);
snd_use_lock_use(&f->use_lock);
err = snd_seq_event_dup(f->pool, event, &cell, 1, NULL); /* always non-blocking */
if (err < 0) {
if (err == -ENOMEM)
atomic_inc(&f->overflow);
snd_use_lock_free(&f->use_lock);
return err;
}
/* append new cells to fifo */
spin_lock_irqsave(&f->lock, flags);
if (f->tail != NULL)
f->tail->next = cell;
f->tail = cell;
if (f->head == NULL)
f->head = cell;
f->cells++;
spin_unlock_irqrestore(&f->lock, flags);
/* wakeup client */
if (waitqueue_active(&f->input_sleep))
wake_up(&f->input_sleep);
snd_use_lock_free(&f->use_lock);
return 0; /* success */
}
/* dequeue cell from fifo */
static snd_seq_event_cell_t *fifo_cell_out(fifo_t *f)
{
snd_seq_event_cell_t *cell;
if ((cell = f->head) != NULL) {
f->head = cell->next;
/* reset tail if this was the last element */
if (f->tail == cell)
f->tail = NULL;
cell->next = NULL;
f->cells--;
}
return cell;
}
/* dequeue cell from fifo and copy on user space */
int snd_seq_fifo_cell_out(fifo_t *f, snd_seq_event_cell_t **cellp, int nonblock)
{
snd_seq_event_cell_t *cell;
unsigned long flags;
wait_queue_t wait;
snd_assert(f != NULL, return -EINVAL);
*cellp = NULL;
init_waitqueue_entry(&wait, current);
spin_lock_irqsave(&f->lock, flags);
while ((cell = fifo_cell_out(f)) == NULL) {
if (nonblock) {
/* non-blocking - return immediately */
spin_unlock_irqrestore(&f->lock, flags);
return -EAGAIN;
}
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&f->input_sleep, &wait);
spin_unlock_irq(&f->lock);
schedule();
spin_lock_irq(&f->lock);
remove_wait_queue(&f->input_sleep, &wait);
if (signal_pending(current)) {
spin_unlock_irqrestore(&f->lock, flags);
return -ERESTARTSYS;
}
}
spin_unlock_irqrestore(&f->lock, flags);
*cellp = cell;
return 0;
}
void snd_seq_fifo_cell_putback(fifo_t *f, snd_seq_event_cell_t *cell)
{
unsigned long flags;
if (cell) {
spin_lock_irqsave(&f->lock, flags);
cell->next = f->head;
f->head = cell;
f->cells++;
spin_unlock_irqrestore(&f->lock, flags);
}
}
/* polling; return non-zero if queue is available */
int snd_seq_fifo_poll_wait(fifo_t *f, struct file *file, poll_table *wait)
{
poll_wait(file, &f->input_sleep, wait);
return (f->cells > 0);
}
/* change the size of pool; all old events are removed */
int snd_seq_fifo_resize(fifo_t *f, int poolsize)
{
unsigned long flags;
pool_t *newpool, *oldpool;
snd_seq_event_cell_t *cell, *next, *oldhead;
snd_assert(f != NULL && f->pool != NULL, return -EINVAL);
/* allocate new pool */
newpool = snd_seq_pool_new(poolsize);
if (newpool == NULL)
return -ENOMEM;
if (snd_seq_pool_init(newpool) < 0) {
snd_seq_pool_delete(&newpool);
return -ENOMEM;
}
spin_lock_irqsave(&f->lock, flags);
/* remember old pool */
oldpool = f->pool;
oldhead = f->head;
/* exchange pools */
f->pool = newpool;
f->head = NULL;
f->tail = NULL;
f->cells = 0;
/* NOTE: overflow flag is not cleared */
spin_unlock_irqrestore(&f->lock, flags);
/* release cells in old pool */
for (cell = oldhead; cell; cell = next) {
next = cell->next;
snd_seq_cell_free(cell);
}
snd_seq_pool_delete(&oldpool);
return 0;
}

72
sound/core/seq/seq_fifo.h Normal file
View File

@@ -0,0 +1,72 @@
/*
* ALSA sequencer FIFO
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef __SND_SEQ_FIFO_H
#define __SND_SEQ_FIFO_H
#include "seq_memory.h"
#include "seq_lock.h"
/* === FIFO === */
typedef struct {
pool_t *pool; /* FIFO pool */
snd_seq_event_cell_t* head; /* pointer to head of fifo */
snd_seq_event_cell_t* tail; /* pointer to tail of fifo */
int cells;
spinlock_t lock;
snd_use_lock_t use_lock;
wait_queue_head_t input_sleep;
atomic_t overflow;
} fifo_t;
/* create new fifo (constructor) */
extern fifo_t *snd_seq_fifo_new(int poolsize);
/* delete fifo (destructor) */
extern void snd_seq_fifo_delete(fifo_t **f);
/* enqueue event to fifo */
extern int snd_seq_fifo_event_in(fifo_t *f, snd_seq_event_t *event);
/* lock fifo from release */
#define snd_seq_fifo_lock(fifo) snd_use_lock_use(&(fifo)->use_lock)
#define snd_seq_fifo_unlock(fifo) snd_use_lock_free(&(fifo)->use_lock)
/* get a cell from fifo - fifo should be locked */
int snd_seq_fifo_cell_out(fifo_t *f, snd_seq_event_cell_t **cellp, int nonblock);
/* free dequeued cell - fifo should be locked */
extern void snd_seq_fifo_cell_putback(fifo_t *f, snd_seq_event_cell_t *cell);
/* clean up queue */
extern void snd_seq_fifo_clear(fifo_t *f);
/* polling */
extern int snd_seq_fifo_poll_wait(fifo_t *f, struct file *file, poll_table *wait);
/* resize pool in fifo */
int snd_seq_fifo_resize(fifo_t *f, int poolsize);
#endif

75
sound/core/seq/seq_info.c Normal file
View File

@@ -0,0 +1,75 @@
/*
* ALSA sequencer /proc interface
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <sound/core.h>
#include "seq_info.h"
#include "seq_clientmgr.h"
#include "seq_timer.h"
static snd_info_entry_t *queues_entry;
static snd_info_entry_t *clients_entry;
static snd_info_entry_t *timer_entry;
static snd_info_entry_t * __init
create_info_entry(char *name, int size, void (*read)(snd_info_entry_t *, snd_info_buffer_t *))
{
snd_info_entry_t *entry;
entry = snd_info_create_module_entry(THIS_MODULE, name, snd_seq_root);
if (entry == NULL)
return NULL;
entry->content = SNDRV_INFO_CONTENT_TEXT;
entry->c.text.read_size = size;
entry->c.text.read = read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
return NULL;
}
return entry;
}
/* create all our /proc entries */
int __init snd_seq_info_init(void)
{
queues_entry = create_info_entry("queues", 512 + (256 * SNDRV_SEQ_MAX_QUEUES),
snd_seq_info_queues_read);
clients_entry = create_info_entry("clients", 512 + (256 * SNDRV_SEQ_MAX_CLIENTS),
snd_seq_info_clients_read);
timer_entry = create_info_entry("timer", 1024, snd_seq_info_timer_read);
return 0;
}
int __exit snd_seq_info_done(void)
{
if (queues_entry)
snd_info_unregister(queues_entry);
if (clients_entry)
snd_info_unregister(clients_entry);
if (timer_entry)
snd_info_unregister(timer_entry);
return 0;
}

36
sound/core/seq/seq_info.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* ALSA sequencer /proc info
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef __SND_SEQ_INFO_H
#define __SND_SEQ_INFO_H
#include <sound/info.h>
#include <sound/seq_kernel.h>
void snd_seq_info_clients_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
void snd_seq_info_timer_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
void snd_seq_info_queues_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
int snd_seq_info_init( void );
int snd_seq_info_done( void );
#endif

653
sound/core/seq/seq_instr.c Normal file
View File

@@ -0,0 +1,653 @@
/*
* Generic Instrument routines for ALSA sequencer
* Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <sound/core.h>
#include "seq_clientmgr.h"
#include <sound/seq_instr.h>
#include <sound/initval.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer instrument library.");
MODULE_LICENSE("GPL");
static void snd_instr_lock_ops(snd_seq_kinstr_list_t *list)
{
if (!(list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT)) {
spin_lock_irqsave(&list->ops_lock, list->ops_flags);
} else {
down(&list->ops_mutex);
}
}
static void snd_instr_unlock_ops(snd_seq_kinstr_list_t *list)
{
if (!(list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT)) {
spin_unlock_irqrestore(&list->ops_lock, list->ops_flags);
} else {
up(&list->ops_mutex);
}
}
static snd_seq_kinstr_t *snd_seq_instr_new(int add_len, int atomic)
{
snd_seq_kinstr_t *instr;
instr = kcalloc(1, sizeof(snd_seq_kinstr_t) + add_len, atomic ? GFP_ATOMIC : GFP_KERNEL);
if (instr == NULL)
return NULL;
instr->add_len = add_len;
return instr;
}
static int snd_seq_instr_free(snd_seq_kinstr_t *instr, int atomic)
{
int result = 0;
if (instr == NULL)
return -EINVAL;
if (instr->ops && instr->ops->remove)
result = instr->ops->remove(instr->ops->private_data, instr, 1);
if (!result)
kfree(instr);
return result;
}
snd_seq_kinstr_list_t *snd_seq_instr_list_new(void)
{
snd_seq_kinstr_list_t *list;
list = kcalloc(1, sizeof(snd_seq_kinstr_list_t), GFP_KERNEL);
if (list == NULL)
return NULL;
spin_lock_init(&list->lock);
spin_lock_init(&list->ops_lock);
init_MUTEX(&list->ops_mutex);
list->owner = -1;
return list;
}
void snd_seq_instr_list_free(snd_seq_kinstr_list_t **list_ptr)
{
snd_seq_kinstr_list_t *list;
snd_seq_kinstr_t *instr;
snd_seq_kcluster_t *cluster;
int idx;
unsigned long flags;
if (list_ptr == NULL)
return;
list = *list_ptr;
*list_ptr = NULL;
if (list == NULL)
return;
for (idx = 0; idx < SNDRV_SEQ_INSTR_HASH_SIZE; idx++) {
while ((instr = list->hash[idx]) != NULL) {
list->hash[idx] = instr->next;
list->count--;
spin_lock_irqsave(&list->lock, flags);
while (instr->use) {
spin_unlock_irqrestore(&list->lock, flags);
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(1);
spin_lock_irqsave(&list->lock, flags);
}
spin_unlock_irqrestore(&list->lock, flags);
if (snd_seq_instr_free(instr, 0)<0)
snd_printk(KERN_WARNING "instrument free problem\n");
}
while ((cluster = list->chash[idx]) != NULL) {
list->chash[idx] = cluster->next;
list->ccount--;
kfree(cluster);
}
}
kfree(list);
}
static int instr_free_compare(snd_seq_kinstr_t *instr,
snd_seq_instr_header_t *ifree,
unsigned int client)
{
switch (ifree->cmd) {
case SNDRV_SEQ_INSTR_FREE_CMD_ALL:
/* all, except private for other clients */
if ((instr->instr.std & 0xff000000) == 0)
return 0;
if (((instr->instr.std >> 24) & 0xff) == client)
return 0;
return 1;
case SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE:
/* all my private instruments */
if ((instr->instr.std & 0xff000000) == 0)
return 1;
if (((instr->instr.std >> 24) & 0xff) == client)
return 0;
return 1;
case SNDRV_SEQ_INSTR_FREE_CMD_CLUSTER:
/* all my private instruments */
if ((instr->instr.std & 0xff000000) == 0) {
if (instr->instr.cluster == ifree->id.cluster)
return 0;
return 1;
}
if (((instr->instr.std >> 24) & 0xff) == client) {
if (instr->instr.cluster == ifree->id.cluster)
return 0;
}
return 1;
}
return 1;
}
int snd_seq_instr_list_free_cond(snd_seq_kinstr_list_t *list,
snd_seq_instr_header_t *ifree,
int client,
int atomic)
{
snd_seq_kinstr_t *instr, *prev, *next, *flist;
int idx;
unsigned long flags;
snd_instr_lock_ops(list);
for (idx = 0; idx < SNDRV_SEQ_INSTR_HASH_SIZE; idx++) {
spin_lock_irqsave(&list->lock, flags);
instr = list->hash[idx];
prev = flist = NULL;
while (instr) {
while (instr && instr_free_compare(instr, ifree, (unsigned int)client)) {
prev = instr;
instr = instr->next;
}
if (instr == NULL)
continue;
if (instr->ops && instr->ops->notify)
instr->ops->notify(instr->ops->private_data, instr, SNDRV_SEQ_INSTR_NOTIFY_REMOVE);
next = instr->next;
if (prev == NULL) {
list->hash[idx] = next;
} else {
prev->next = next;
}
list->count--;
instr->next = flist;
flist = instr;
instr = next;
}
spin_unlock_irqrestore(&list->lock, flags);
while (flist) {
instr = flist;
flist = instr->next;
while (instr->use) {
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(1);
}
if (snd_seq_instr_free(instr, atomic)<0)
snd_printk(KERN_WARNING "instrument free problem\n");
instr = next;
}
}
snd_instr_unlock_ops(list);
return 0;
}
static int compute_hash_instr_key(snd_seq_instr_t *instr)
{
int result;
result = instr->bank | (instr->prg << 16);
result += result >> 24;
result += result >> 16;
result += result >> 8;
return result & (SNDRV_SEQ_INSTR_HASH_SIZE-1);
}
#if 0
static int compute_hash_cluster_key(snd_seq_instr_cluster_t cluster)
{
int result;
result = cluster;
result += result >> 24;
result += result >> 16;
result += result >> 8;
return result & (SNDRV_SEQ_INSTR_HASH_SIZE-1);
}
#endif
static int compare_instr(snd_seq_instr_t *i1, snd_seq_instr_t *i2, int exact)
{
if (exact) {
if (i1->cluster != i2->cluster ||
i1->bank != i2->bank ||
i1->prg != i2->prg)
return 1;
if ((i1->std & 0xff000000) != (i2->std & 0xff000000))
return 1;
if (!(i1->std & i2->std))
return 1;
return 0;
} else {
unsigned int client_check;
if (i2->cluster && i1->cluster != i2->cluster)
return 1;
client_check = i2->std & 0xff000000;
if (client_check) {
if ((i1->std & 0xff000000) != client_check)
return 1;
} else {
if ((i1->std & i2->std) != i2->std)
return 1;
}
return i1->bank != i2->bank || i1->prg != i2->prg;
}
}
snd_seq_kinstr_t *snd_seq_instr_find(snd_seq_kinstr_list_t *list,
snd_seq_instr_t *instr,
int exact,
int follow_alias)
{
unsigned long flags;
int depth = 0;
snd_seq_kinstr_t *result;
if (list == NULL || instr == NULL)
return NULL;
spin_lock_irqsave(&list->lock, flags);
__again:
result = list->hash[compute_hash_instr_key(instr)];
while (result) {
if (!compare_instr(&result->instr, instr, exact)) {
if (follow_alias && (result->type == SNDRV_SEQ_INSTR_ATYPE_ALIAS)) {
instr = (snd_seq_instr_t *)KINSTR_DATA(result);
if (++depth > 10)
goto __not_found;
goto __again;
}
result->use++;
spin_unlock_irqrestore(&list->lock, flags);
return result;
}
result = result->next;
}
__not_found:
spin_unlock_irqrestore(&list->lock, flags);
return NULL;
}
void snd_seq_instr_free_use(snd_seq_kinstr_list_t *list,
snd_seq_kinstr_t *instr)
{
unsigned long flags;
if (list == NULL || instr == NULL)
return;
spin_lock_irqsave(&list->lock, flags);
if (instr->use <= 0) {
snd_printk(KERN_ERR "free_use: fatal!!! use = %i, name = '%s'\n", instr->use, instr->name);
} else {
instr->use--;
}
spin_unlock_irqrestore(&list->lock, flags);
}
static snd_seq_kinstr_ops_t *instr_ops(snd_seq_kinstr_ops_t *ops, char *instr_type)
{
while (ops) {
if (!strcmp(ops->instr_type, instr_type))
return ops;
ops = ops->next;
}
return NULL;
}
static int instr_result(snd_seq_event_t *ev,
int type, int result,
int atomic)
{
snd_seq_event_t sev;
memset(&sev, 0, sizeof(sev));
sev.type = SNDRV_SEQ_EVENT_RESULT;
sev.flags = SNDRV_SEQ_TIME_STAMP_REAL | SNDRV_SEQ_EVENT_LENGTH_FIXED |
SNDRV_SEQ_PRIORITY_NORMAL;
sev.source = ev->dest;
sev.dest = ev->source;
sev.data.result.event = type;
sev.data.result.result = result;
#if 0
printk("instr result - type = %i, result = %i, queue = %i, source.client:port = %i:%i, dest.client:port = %i:%i\n",
type, result,
sev.queue,
sev.source.client, sev.source.port,
sev.dest.client, sev.dest.port);
#endif
return snd_seq_kernel_client_dispatch(sev.source.client, &sev, atomic, 0);
}
static int instr_begin(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int atomic, int hop)
{
unsigned long flags;
spin_lock_irqsave(&list->lock, flags);
if (list->owner >= 0 && list->owner != ev->source.client) {
spin_unlock_irqrestore(&list->lock, flags);
return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_BEGIN, -EBUSY, atomic);
}
list->owner = ev->source.client;
spin_unlock_irqrestore(&list->lock, flags);
return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_BEGIN, 0, atomic);
}
static int instr_end(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int atomic, int hop)
{
unsigned long flags;
/* TODO: timeout handling */
spin_lock_irqsave(&list->lock, flags);
if (list->owner == ev->source.client) {
list->owner = -1;
spin_unlock_irqrestore(&list->lock, flags);
return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_END, 0, atomic);
}
spin_unlock_irqrestore(&list->lock, flags);
return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_END, -EINVAL, atomic);
}
static int instr_info(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int atomic, int hop)
{
return -ENXIO;
}
static int instr_format_info(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int atomic, int hop)
{
return -ENXIO;
}
static int instr_reset(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int atomic, int hop)
{
return -ENXIO;
}
static int instr_status(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int atomic, int hop)
{
return -ENXIO;
}
static int instr_put(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int atomic, int hop)
{
unsigned long flags;
snd_seq_instr_header_t put;
snd_seq_kinstr_t *instr;
int result = -EINVAL, len, key;
if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARUSR)
goto __return;
if (ev->data.ext.len < sizeof(snd_seq_instr_header_t))
goto __return;
if (copy_from_user(&put, (void __user *)ev->data.ext.ptr, sizeof(snd_seq_instr_header_t))) {
result = -EFAULT;
goto __return;
}
snd_instr_lock_ops(list);
if (put.id.instr.std & 0xff000000) { /* private instrument */
put.id.instr.std &= 0x00ffffff;
put.id.instr.std |= (unsigned int)ev->source.client << 24;
}
if ((instr = snd_seq_instr_find(list, &put.id.instr, 1, 0))) {
snd_seq_instr_free_use(list, instr);
snd_instr_unlock_ops(list);
result = -EBUSY;
goto __return;
}
ops = instr_ops(ops, put.data.data.format);
if (ops == NULL) {
snd_instr_unlock_ops(list);
goto __return;
}
len = ops->add_len;
if (put.data.type == SNDRV_SEQ_INSTR_ATYPE_ALIAS)
len = sizeof(snd_seq_instr_t);
instr = snd_seq_instr_new(len, atomic);
if (instr == NULL) {
snd_instr_unlock_ops(list);
result = -ENOMEM;
goto __return;
}
instr->ops = ops;
instr->instr = put.id.instr;
strlcpy(instr->name, put.data.name, sizeof(instr->name));
instr->type = put.data.type;
if (instr->type == SNDRV_SEQ_INSTR_ATYPE_DATA) {
result = ops->put(ops->private_data,
instr,
(void __user *)ev->data.ext.ptr + sizeof(snd_seq_instr_header_t),
ev->data.ext.len - sizeof(snd_seq_instr_header_t),
atomic,
put.cmd);
if (result < 0) {
snd_seq_instr_free(instr, atomic);
snd_instr_unlock_ops(list);
goto __return;
}
}
key = compute_hash_instr_key(&instr->instr);
spin_lock_irqsave(&list->lock, flags);
instr->next = list->hash[key];
list->hash[key] = instr;
list->count++;
spin_unlock_irqrestore(&list->lock, flags);
snd_instr_unlock_ops(list);
result = 0;
__return:
instr_result(ev, SNDRV_SEQ_EVENT_INSTR_PUT, result, atomic);
return result;
}
static int instr_get(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int atomic, int hop)
{
return -ENXIO;
}
static int instr_free(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int atomic, int hop)
{
snd_seq_instr_header_t ifree;
snd_seq_kinstr_t *instr, *prev;
int result = -EINVAL;
unsigned long flags;
unsigned int hash;
if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARUSR)
goto __return;
if (ev->data.ext.len < sizeof(snd_seq_instr_header_t))
goto __return;
if (copy_from_user(&ifree, (void __user *)ev->data.ext.ptr, sizeof(snd_seq_instr_header_t))) {
result = -EFAULT;
goto __return;
}
if (ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_ALL ||
ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE ||
ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_CLUSTER) {
result = snd_seq_instr_list_free_cond(list, &ifree, ev->dest.client, atomic);
goto __return;
}
if (ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_SINGLE) {
if (ifree.id.instr.std & 0xff000000) {
ifree.id.instr.std &= 0x00ffffff;
ifree.id.instr.std |= (unsigned int)ev->source.client << 24;
}
hash = compute_hash_instr_key(&ifree.id.instr);
snd_instr_lock_ops(list);
spin_lock_irqsave(&list->lock, flags);
instr = list->hash[hash];
prev = NULL;
while (instr) {
if (!compare_instr(&instr->instr, &ifree.id.instr, 1))
goto __free_single;
prev = instr;
instr = instr->next;
}
result = -ENOENT;
spin_unlock_irqrestore(&list->lock, flags);
snd_instr_unlock_ops(list);
goto __return;
__free_single:
if (prev) {
prev->next = instr->next;
} else {
list->hash[hash] = instr->next;
}
if (instr->ops && instr->ops->notify)
instr->ops->notify(instr->ops->private_data, instr, SNDRV_SEQ_INSTR_NOTIFY_REMOVE);
while (instr->use) {
spin_unlock_irqrestore(&list->lock, flags);
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(1);
spin_lock_irqsave(&list->lock, flags);
}
spin_unlock_irqrestore(&list->lock, flags);
result = snd_seq_instr_free(instr, atomic);
snd_instr_unlock_ops(list);
goto __return;
}
__return:
instr_result(ev, SNDRV_SEQ_EVENT_INSTR_FREE, result, atomic);
return result;
}
static int instr_list(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int atomic, int hop)
{
return -ENXIO;
}
static int instr_cluster(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int atomic, int hop)
{
return -ENXIO;
}
int snd_seq_instr_event(snd_seq_kinstr_ops_t *ops,
snd_seq_kinstr_list_t *list,
snd_seq_event_t *ev,
int client,
int atomic,
int hop)
{
int direct = 0;
snd_assert(ops != NULL && list != NULL && ev != NULL, return -EINVAL);
if (snd_seq_ev_is_direct(ev)) {
direct = 1;
switch (ev->type) {
case SNDRV_SEQ_EVENT_INSTR_BEGIN:
return instr_begin(ops, list, ev, atomic, hop);
case SNDRV_SEQ_EVENT_INSTR_END:
return instr_end(ops, list, ev, atomic, hop);
}
}
if ((list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT) && !direct)
return -EINVAL;
switch (ev->type) {
case SNDRV_SEQ_EVENT_INSTR_INFO:
return instr_info(ops, list, ev, atomic, hop);
case SNDRV_SEQ_EVENT_INSTR_FINFO:
return instr_format_info(ops, list, ev, atomic, hop);
case SNDRV_SEQ_EVENT_INSTR_RESET:
return instr_reset(ops, list, ev, atomic, hop);
case SNDRV_SEQ_EVENT_INSTR_STATUS:
return instr_status(ops, list, ev, atomic, hop);
case SNDRV_SEQ_EVENT_INSTR_PUT:
return instr_put(ops, list, ev, atomic, hop);
case SNDRV_SEQ_EVENT_INSTR_GET:
return instr_get(ops, list, ev, atomic, hop);
case SNDRV_SEQ_EVENT_INSTR_FREE:
return instr_free(ops, list, ev, atomic, hop);
case SNDRV_SEQ_EVENT_INSTR_LIST:
return instr_list(ops, list, ev, atomic, hop);
case SNDRV_SEQ_EVENT_INSTR_CLUSTER:
return instr_cluster(ops, list, ev, atomic, hop);
}
return -EINVAL;
}
/*
* Init part
*/
static int __init alsa_seq_instr_init(void)
{
return 0;
}
static void __exit alsa_seq_instr_exit(void)
{
}
module_init(alsa_seq_instr_init)
module_exit(alsa_seq_instr_exit)
EXPORT_SYMBOL(snd_seq_instr_list_new);
EXPORT_SYMBOL(snd_seq_instr_list_free);
EXPORT_SYMBOL(snd_seq_instr_list_free_cond);
EXPORT_SYMBOL(snd_seq_instr_find);
EXPORT_SYMBOL(snd_seq_instr_free_use);
EXPORT_SYMBOL(snd_seq_instr_event);

48
sound/core/seq/seq_lock.c Normal file
View File

@@ -0,0 +1,48 @@
/*
* Do sleep inside a spin-lock
* Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <sound/core.h>
#include "seq_lock.h"
#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG)
/* wait until all locks are released */
void snd_use_lock_sync_helper(snd_use_lock_t *lockp, const char *file, int line)
{
int max_count = 5 * HZ;
if (atomic_read(lockp) < 0) {
printk(KERN_WARNING "seq_lock: lock trouble [counter = %d] in %s:%d\n", atomic_read(lockp), file, line);
return;
}
while (atomic_read(lockp) > 0) {
if (max_count == 0) {
snd_printk(KERN_WARNING "seq_lock: timeout [%d left] in %s:%d\n", atomic_read(lockp), file, line);
break;
}
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(1);
max_count--;
}
}
#endif

33
sound/core/seq/seq_lock.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef __SND_SEQ_LOCK_H
#define __SND_SEQ_LOCK_H
#include <linux/sched.h>
#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG)
typedef atomic_t snd_use_lock_t;
/* initialize lock */
#define snd_use_lock_init(lockp) atomic_set(lockp, 0)
/* increment lock */
#define snd_use_lock_use(lockp) atomic_inc(lockp)
/* release lock */
#define snd_use_lock_free(lockp) atomic_dec(lockp)
/* wait until all locks are released */
void snd_use_lock_sync_helper(snd_use_lock_t *lock, const char *file, int line);
#define snd_use_lock_sync(lockp) snd_use_lock_sync_helper(lockp, __BASE_FILE__, __LINE__)
#else /* SMP || CONFIG_SND_DEBUG */
typedef spinlock_t snd_use_lock_t; /* dummy */
#define snd_use_lock_init(lockp) /**/
#define snd_use_lock_use(lockp) /**/
#define snd_use_lock_free(lockp) /**/
#define snd_use_lock_sync(lockp) /**/
#endif /* SMP || CONFIG_SND_DEBUG */
#endif /* __SND_SEQ_LOCK_H */

510
sound/core/seq/seq_memory.c Normal file
View File

@@ -0,0 +1,510 @@
/*
* ALSA sequencer Memory Manager
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
* Jaroslav Kysela <perex@suse.cz>
* 2000 by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <sound/core.h>
#include <sound/seq_kernel.h>
#include "seq_memory.h"
#include "seq_queue.h"
#include "seq_info.h"
#include "seq_lock.h"
/* semaphore in struct file record */
#define semaphore_of(fp) ((fp)->f_dentry->d_inode->i_sem)
inline static int snd_seq_pool_available(pool_t *pool)
{
return pool->total_elements - atomic_read(&pool->counter);
}
inline static int snd_seq_output_ok(pool_t *pool)
{
return snd_seq_pool_available(pool) >= pool->room;
}
/*
* Variable length event:
* The event like sysex uses variable length type.
* The external data may be stored in three different formats.
* 1) kernel space
* This is the normal case.
* ext.data.len = length
* ext.data.ptr = buffer pointer
* 2) user space
* When an event is generated via read(), the external data is
* kept in user space until expanded.
* ext.data.len = length | SNDRV_SEQ_EXT_USRPTR
* ext.data.ptr = userspace pointer
* 3) chained cells
* When the variable length event is enqueued (in prioq or fifo),
* the external data is decomposed to several cells.
* ext.data.len = length | SNDRV_SEQ_EXT_CHAINED
* ext.data.ptr = the additiona cell head
* -> cell.next -> cell.next -> ..
*/
/*
* exported:
* call dump function to expand external data.
*/
static int get_var_len(const snd_seq_event_t *event)
{
if ((event->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
return -EINVAL;
return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
}
int snd_seq_dump_var_event(const snd_seq_event_t *event, snd_seq_dump_func_t func, void *private_data)
{
int len, err;
snd_seq_event_cell_t *cell;
if ((len = get_var_len(event)) <= 0)
return len;
if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) {
char buf[32];
char __user *curptr = (char __user *)event->data.ext.ptr;
while (len > 0) {
int size = sizeof(buf);
if (len < size)
size = len;
if (copy_from_user(buf, curptr, size))
return -EFAULT;
err = func(private_data, buf, size);
if (err < 0)
return err;
curptr += size;
len -= size;
}
return 0;
} if (! (event->data.ext.len & SNDRV_SEQ_EXT_CHAINED)) {
return func(private_data, event->data.ext.ptr, len);
}
cell = (snd_seq_event_cell_t*)event->data.ext.ptr;
for (; len > 0 && cell; cell = cell->next) {
int size = sizeof(snd_seq_event_t);
if (len < size)
size = len;
err = func(private_data, &cell->event, size);
if (err < 0)
return err;
len -= size;
}
return 0;
}
/*
* exported:
* expand the variable length event to linear buffer space.
*/
static int seq_copy_in_kernel(char **bufptr, const void *src, int size)
{
memcpy(*bufptr, src, size);
*bufptr += size;
return 0;
}
static int seq_copy_in_user(char __user **bufptr, const void *src, int size)
{
if (copy_to_user(*bufptr, src, size))
return -EFAULT;
*bufptr += size;
return 0;
}
int snd_seq_expand_var_event(const snd_seq_event_t *event, int count, char *buf, int in_kernel, int size_aligned)
{
int len, newlen;
int err;
if ((len = get_var_len(event)) < 0)
return len;
newlen = len;
if (size_aligned > 0)
newlen = ((len + size_aligned - 1) / size_aligned) * size_aligned;
if (count < newlen)
return -EAGAIN;
if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) {
if (! in_kernel)
return -EINVAL;
if (copy_from_user(buf, (void __user *)event->data.ext.ptr, len))
return -EFAULT;
return newlen;
}
err = snd_seq_dump_var_event(event,
in_kernel ? (snd_seq_dump_func_t)seq_copy_in_kernel :
(snd_seq_dump_func_t)seq_copy_in_user,
&buf);
return err < 0 ? err : newlen;
}
/*
* release this cell, free extended data if available
*/
static inline void free_cell(pool_t *pool, snd_seq_event_cell_t *cell)
{
cell->next = pool->free;
pool->free = cell;
atomic_dec(&pool->counter);
}
void snd_seq_cell_free(snd_seq_event_cell_t * cell)
{
unsigned long flags;
pool_t *pool;
snd_assert(cell != NULL, return);
pool = cell->pool;
snd_assert(pool != NULL, return);
spin_lock_irqsave(&pool->lock, flags);
free_cell(pool, cell);
if (snd_seq_ev_is_variable(&cell->event)) {
if (cell->event.data.ext.len & SNDRV_SEQ_EXT_CHAINED) {
snd_seq_event_cell_t *curp, *nextptr;
curp = cell->event.data.ext.ptr;
for (; curp; curp = nextptr) {
nextptr = curp->next;
curp->next = pool->free;
free_cell(pool, curp);
}
}
}
if (waitqueue_active(&pool->output_sleep)) {
/* has enough space now? */
if (snd_seq_output_ok(pool))
wake_up(&pool->output_sleep);
}
spin_unlock_irqrestore(&pool->lock, flags);
}
/*
* allocate an event cell.
*/
static int snd_seq_cell_alloc(pool_t *pool, snd_seq_event_cell_t **cellp, int nonblock, struct file *file)
{
snd_seq_event_cell_t *cell;
unsigned long flags;
int err = -EAGAIN;
wait_queue_t wait;
if (pool == NULL)
return -EINVAL;
*cellp = NULL;
init_waitqueue_entry(&wait, current);
spin_lock_irqsave(&pool->lock, flags);
if (pool->ptr == NULL) { /* not initialized */
snd_printd("seq: pool is not initialized\n");
err = -EINVAL;
goto __error;
}
while (pool->free == NULL && ! nonblock && ! pool->closing) {
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&pool->output_sleep, &wait);
spin_unlock_irq(&pool->lock);
schedule();
spin_lock_irq(&pool->lock);
remove_wait_queue(&pool->output_sleep, &wait);
/* interrupted? */
if (signal_pending(current)) {
err = -ERESTARTSYS;
goto __error;
}
}
if (pool->closing) { /* closing.. */
err = -ENOMEM;
goto __error;
}
cell = pool->free;
if (cell) {
int used;
pool->free = cell->next;
atomic_inc(&pool->counter);
used = atomic_read(&pool->counter);
if (pool->max_used < used)
pool->max_used = used;
pool->event_alloc_success++;
/* clear cell pointers */
cell->next = NULL;
err = 0;
} else
pool->event_alloc_failures++;
*cellp = cell;
__error:
spin_unlock_irqrestore(&pool->lock, flags);
return err;
}
/*
* duplicate the event to a cell.
* if the event has external data, the data is decomposed to additional
* cells.
*/
int snd_seq_event_dup(pool_t *pool, snd_seq_event_t *event, snd_seq_event_cell_t **cellp, int nonblock, struct file *file)
{
int ncells, err;
unsigned int extlen;
snd_seq_event_cell_t *cell;
*cellp = NULL;
ncells = 0;
extlen = 0;
if (snd_seq_ev_is_variable(event)) {
extlen = event->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
ncells = (extlen + sizeof(snd_seq_event_t) - 1) / sizeof(snd_seq_event_t);
}
if (ncells >= pool->total_elements)
return -ENOMEM;
err = snd_seq_cell_alloc(pool, &cell, nonblock, file);
if (err < 0)
return err;
/* copy the event */
cell->event = *event;
/* decompose */
if (snd_seq_ev_is_variable(event)) {
int len = extlen;
int is_chained = event->data.ext.len & SNDRV_SEQ_EXT_CHAINED;
int is_usrptr = event->data.ext.len & SNDRV_SEQ_EXT_USRPTR;
snd_seq_event_cell_t *src, *tmp, *tail;
char *buf;
cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED;
cell->event.data.ext.ptr = NULL;
src = (snd_seq_event_cell_t*)event->data.ext.ptr;
buf = (char *)event->data.ext.ptr;
tail = NULL;
while (ncells-- > 0) {
int size = sizeof(snd_seq_event_t);
if (len < size)
size = len;
err = snd_seq_cell_alloc(pool, &tmp, nonblock, file);
if (err < 0)
goto __error;
if (cell->event.data.ext.ptr == NULL)
cell->event.data.ext.ptr = tmp;
if (tail)
tail->next = tmp;
tail = tmp;
/* copy chunk */
if (is_chained && src) {
tmp->event = src->event;
src = src->next;
} else if (is_usrptr) {
if (copy_from_user(&tmp->event, (char __user *)buf, size)) {
err = -EFAULT;
goto __error;
}
} else {
memcpy(&tmp->event, buf, size);
}
buf += size;
len -= size;
}
}
*cellp = cell;
return 0;
__error:
snd_seq_cell_free(cell);
return err;
}
/* poll wait */
int snd_seq_pool_poll_wait(pool_t *pool, struct file *file, poll_table *wait)
{
poll_wait(file, &pool->output_sleep, wait);
return snd_seq_output_ok(pool);
}
/* allocate room specified number of events */
int snd_seq_pool_init(pool_t *pool)
{
int cell;
snd_seq_event_cell_t *cellptr;
unsigned long flags;
snd_assert(pool != NULL, return -EINVAL);
if (pool->ptr) /* should be atomic? */
return 0;
pool->ptr = vmalloc(sizeof(snd_seq_event_cell_t) * pool->size);
if (pool->ptr == NULL) {
snd_printd("seq: malloc for sequencer events failed\n");
return -ENOMEM;
}
/* add new cells to the free cell list */
spin_lock_irqsave(&pool->lock, flags);
pool->free = NULL;
for (cell = 0; cell < pool->size; cell++) {
cellptr = pool->ptr + cell;
cellptr->pool = pool;
cellptr->next = pool->free;
pool->free = cellptr;
}
pool->room = (pool->size + 1) / 2;
/* init statistics */
pool->max_used = 0;
pool->total_elements = pool->size;
spin_unlock_irqrestore(&pool->lock, flags);
return 0;
}
/* remove events */
int snd_seq_pool_done(pool_t *pool)
{
unsigned long flags;
snd_seq_event_cell_t *ptr;
int max_count = 5 * HZ;
snd_assert(pool != NULL, return -EINVAL);
/* wait for closing all threads */
spin_lock_irqsave(&pool->lock, flags);
pool->closing = 1;
spin_unlock_irqrestore(&pool->lock, flags);
if (waitqueue_active(&pool->output_sleep))
wake_up(&pool->output_sleep);
while (atomic_read(&pool->counter) > 0) {
if (max_count == 0) {
snd_printk(KERN_WARNING "snd_seq_pool_done timeout: %d cells remain\n", atomic_read(&pool->counter));
break;
}
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(1);
max_count--;
}
/* release all resources */
spin_lock_irqsave(&pool->lock, flags);
ptr = pool->ptr;
pool->ptr = NULL;
pool->free = NULL;
pool->total_elements = 0;
spin_unlock_irqrestore(&pool->lock, flags);
vfree(ptr);
spin_lock_irqsave(&pool->lock, flags);
pool->closing = 0;
spin_unlock_irqrestore(&pool->lock, flags);
return 0;
}
/* init new memory pool */
pool_t *snd_seq_pool_new(int poolsize)
{
pool_t *pool;
/* create pool block */
pool = kcalloc(1, sizeof(*pool), GFP_KERNEL);
if (pool == NULL) {
snd_printd("seq: malloc failed for pool\n");
return NULL;
}
spin_lock_init(&pool->lock);
pool->ptr = NULL;
pool->free = NULL;
pool->total_elements = 0;
atomic_set(&pool->counter, 0);
pool->closing = 0;
init_waitqueue_head(&pool->output_sleep);
pool->size = poolsize;
/* init statistics */
pool->max_used = 0;
return pool;
}
/* remove memory pool */
int snd_seq_pool_delete(pool_t **ppool)
{
pool_t *pool = *ppool;
*ppool = NULL;
if (pool == NULL)
return 0;
snd_seq_pool_done(pool);
kfree(pool);
return 0;
}
/* initialize sequencer memory */
int __init snd_sequencer_memory_init(void)
{
return 0;
}
/* release sequencer memory */
void __exit snd_sequencer_memory_done(void)
{
}
/* exported to seq_clientmgr.c */
void snd_seq_info_pool(snd_info_buffer_t * buffer, pool_t *pool, char *space)
{
if (pool == NULL)
return;
snd_iprintf(buffer, "%sPool size : %d\n", space, pool->total_elements);
snd_iprintf(buffer, "%sCells in use : %d\n", space, atomic_read(&pool->counter));
snd_iprintf(buffer, "%sPeak cells in use : %d\n", space, pool->max_used);
snd_iprintf(buffer, "%sAlloc success : %d\n", space, pool->event_alloc_success);
snd_iprintf(buffer, "%sAlloc failures : %d\n", space, pool->event_alloc_failures);
}

104
sound/core/seq/seq_memory.h Normal file
View File

@@ -0,0 +1,104 @@
/*
* ALSA sequencer Memory Manager
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef __SND_SEQ_MEMORYMGR_H
#define __SND_SEQ_MEMORYMGR_H
#include <sound/seq_kernel.h>
#include <linux/poll.h>
typedef struct pool pool_t;
/* container for sequencer event (internal use) */
typedef struct snd_seq_event_cell_t {
snd_seq_event_t event;
pool_t *pool; /* used pool */
struct snd_seq_event_cell_t *next; /* next cell */
} snd_seq_event_cell_t;
/* design note: the pool is a contigious block of memory, if we dynamicly
want to add additional cells to the pool be better store this in another
pool as we need to know the base address of the pool when releasing
memory. */
struct pool {
snd_seq_event_cell_t *ptr; /* pointer to first event chunk */
snd_seq_event_cell_t *free; /* pointer to the head of the free list */
int total_elements; /* pool size actually allocated */
atomic_t counter; /* cells free */
int size; /* pool size to be allocated */
int room; /* watermark for sleep/wakeup */
int closing;
/* statistics */
int max_used;
int event_alloc_nopool;
int event_alloc_failures;
int event_alloc_success;
/* Write locking */
wait_queue_head_t output_sleep;
/* Pool lock */
spinlock_t lock;
};
extern void snd_seq_cell_free(snd_seq_event_cell_t* cell);
int snd_seq_event_dup(pool_t *pool, snd_seq_event_t *event, snd_seq_event_cell_t **cellp, int nonblock, struct file *file);
/* return number of unused (free) cells */
static inline int snd_seq_unused_cells(pool_t *pool)
{
return pool ? pool->total_elements - atomic_read(&pool->counter) : 0;
}
/* return total number of allocated cells */
static inline int snd_seq_total_cells(pool_t *pool)
{
return pool ? pool->total_elements : 0;
}
/* init pool - allocate events */
int snd_seq_pool_init(pool_t *pool);
/* done pool - free events */
int snd_seq_pool_done(pool_t *pool);
/* create pool */
pool_t *snd_seq_pool_new(int poolsize);
/* remove pool */
int snd_seq_pool_delete(pool_t **pool);
/* init memory */
int snd_sequencer_memory_init(void);
/* release event memory */
void snd_sequencer_memory_done(void);
/* polling */
int snd_seq_pool_poll_wait(pool_t *pool, struct file *file, poll_table *wait);
#endif

489
sound/core/seq/seq_midi.c Normal file
View File

@@ -0,0 +1,489 @@
/*
* Generic MIDI synth driver for ALSA sequencer
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
* Jaroslav Kysela <perex@suse.cz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*
Possible options for midisynth module:
- automatic opening of midi ports on first received event or subscription
(close will be performed when client leaves)
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/moduleparam.h>
#include <asm/semaphore.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/seq_kernel.h>
#include <sound/seq_device.h>
#include <sound/seq_midi_event.h>
#include <sound/initval.h>
MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI synth.");
MODULE_LICENSE("GPL");
static int output_buffer_size = PAGE_SIZE;
module_param(output_buffer_size, int, 0644);
MODULE_PARM_DESC(output_buffer_size, "Output buffer size in bytes.");
static int input_buffer_size = PAGE_SIZE;
module_param(input_buffer_size, int, 0644);
MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes.");
/* data for this midi synth driver */
typedef struct {
snd_card_t *card;
int device;
int subdevice;
snd_rawmidi_file_t input_rfile;
snd_rawmidi_file_t output_rfile;
int seq_client;
int seq_port;
snd_midi_event_t *parser;
} seq_midisynth_t;
typedef struct {
int seq_client;
int num_ports;
int ports_per_device[SNDRV_RAWMIDI_DEVICES];
seq_midisynth_t *ports[SNDRV_RAWMIDI_DEVICES];
} seq_midisynth_client_t;
static seq_midisynth_client_t *synths[SNDRV_CARDS];
static DECLARE_MUTEX(register_mutex);
/* handle rawmidi input event (MIDI v1.0 stream) */
static void snd_midi_input_event(snd_rawmidi_substream_t * substream)
{
snd_rawmidi_runtime_t *runtime;
seq_midisynth_t *msynth;
snd_seq_event_t ev;
char buf[16], *pbuf;
long res, count;
if (substream == NULL)
return;
runtime = substream->runtime;
msynth = (seq_midisynth_t *) runtime->private_data;
if (msynth == NULL)
return;
memset(&ev, 0, sizeof(ev));
while (runtime->avail > 0) {
res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf));
if (res <= 0)
continue;
if (msynth->parser == NULL)
continue;
pbuf = buf;
while (res > 0) {
count = snd_midi_event_encode(msynth->parser, pbuf, res, &ev);
if (count < 0)
break;
pbuf += count;
res -= count;
if (ev.type != SNDRV_SEQ_EVENT_NONE) {
ev.source.port = msynth->seq_port;
ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
snd_seq_kernel_client_dispatch(msynth->seq_client, &ev, 1, 0);
/* clear event and reset header */
memset(&ev, 0, sizeof(ev));
}
}
}
}
static int dump_midi(snd_rawmidi_substream_t *substream, const char *buf, int count)
{
snd_rawmidi_runtime_t *runtime;
int tmp;
snd_assert(substream != NULL || buf != NULL, return -EINVAL);
runtime = substream->runtime;
if ((tmp = runtime->avail) < count) {
snd_printd("warning, output event was lost (count = %i, available = %i)\n", count, tmp);
return -ENOMEM;
}
if (snd_rawmidi_kernel_write(substream, buf, count) < count)
return -EINVAL;
return 0;
}
static int event_process_midi(snd_seq_event_t * ev, int direct,
void *private_data, int atomic, int hop)
{
seq_midisynth_t *msynth = (seq_midisynth_t *) private_data;
unsigned char msg[10]; /* buffer for constructing midi messages */
snd_rawmidi_substream_t *substream;
int res;
snd_assert(msynth != NULL, return -EINVAL);
substream = msynth->output_rfile.output;
if (substream == NULL)
return -ENODEV;
if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { /* special case, to save space */
if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) {
/* invalid event */
snd_printd("seq_midi: invalid sysex event flags = 0x%x\n", ev->flags);
return 0;
}
res = snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)dump_midi, substream);
snd_midi_event_reset_decode(msynth->parser);
if (res < 0)
return res;
} else {
if (msynth->parser == NULL)
return -EIO;
res = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev);
if (res < 0)
return res;
if ((res = dump_midi(substream, msg, res)) < 0) {
snd_midi_event_reset_decode(msynth->parser);
return res;
}
}
return 0;
}
static int snd_seq_midisynth_new(seq_midisynth_t *msynth,
snd_card_t *card,
int device,
int subdevice)
{
if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &msynth->parser) < 0)
return -ENOMEM;
msynth->card = card;
msynth->device = device;
msynth->subdevice = subdevice;
return 0;
}
/* open associated midi device for input */
static int midisynth_subscribe(void *private_data, snd_seq_port_subscribe_t *info)
{
int err;
seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
snd_rawmidi_runtime_t *runtime;
snd_rawmidi_params_t params;
/* open midi port */
if ((err = snd_rawmidi_kernel_open(msynth->card->number, msynth->device, msynth->subdevice, SNDRV_RAWMIDI_LFLG_INPUT, &msynth->input_rfile)) < 0) {
snd_printd("midi input open failed!!!\n");
return err;
}
runtime = msynth->input_rfile.input->runtime;
memset(&params, 0, sizeof(params));
params.avail_min = 1;
params.buffer_size = input_buffer_size;
if ((err = snd_rawmidi_input_params(msynth->input_rfile.input, &params)) < 0) {
snd_rawmidi_kernel_release(&msynth->input_rfile);
return err;
}
snd_midi_event_reset_encode(msynth->parser);
runtime->event = snd_midi_input_event;
runtime->private_data = msynth;
snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0);
return 0;
}
/* close associated midi device for input */
static int midisynth_unsubscribe(void *private_data, snd_seq_port_subscribe_t *info)
{
int err;
seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
snd_assert(msynth->input_rfile.input != NULL, return -EINVAL);
err = snd_rawmidi_kernel_release(&msynth->input_rfile);
return err;
}
/* open associated midi device for output */
static int midisynth_use(void *private_data, snd_seq_port_subscribe_t *info)
{
int err;
seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
snd_rawmidi_params_t params;
/* open midi port */
if ((err = snd_rawmidi_kernel_open(msynth->card->number, msynth->device, msynth->subdevice, SNDRV_RAWMIDI_LFLG_OUTPUT, &msynth->output_rfile)) < 0) {
snd_printd("midi output open failed!!!\n");
return err;
}
memset(&params, 0, sizeof(params));
params.avail_min = 1;
params.buffer_size = output_buffer_size;
if ((err = snd_rawmidi_output_params(msynth->output_rfile.output, &params)) < 0) {
snd_rawmidi_kernel_release(&msynth->output_rfile);
return err;
}
snd_midi_event_reset_decode(msynth->parser);
return 0;
}
/* close associated midi device for output */
static int midisynth_unuse(void *private_data, snd_seq_port_subscribe_t *info)
{
seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
unsigned char buf = 0xff; /* MIDI reset */
snd_assert(msynth->output_rfile.output != NULL, return -EINVAL);
/* sending single MIDI reset message to shut the device up */
snd_rawmidi_kernel_write(msynth->output_rfile.output, &buf, 1);
snd_rawmidi_drain_output(msynth->output_rfile.output);
return snd_rawmidi_kernel_release(&msynth->output_rfile);
}
/* delete given midi synth port */
static void snd_seq_midisynth_delete(seq_midisynth_t *msynth)
{
if (msynth == NULL)
return;
if (msynth->seq_client > 0) {
/* delete port */
snd_seq_event_port_detach(msynth->seq_client, msynth->seq_port);
}
if (msynth->parser)
snd_midi_event_free(msynth->parser);
}
/* set our client name */
static int set_client_name(seq_midisynth_client_t *client, snd_card_t *card,
snd_rawmidi_info_t *rmidi)
{
snd_seq_client_info_t cinfo;
const char *name;
memset(&cinfo, 0, sizeof(cinfo));
cinfo.client = client->seq_client;
cinfo.type = KERNEL_CLIENT;
name = rmidi->name[0] ? (const char *)rmidi->name : "External MIDI";
strlcpy(cinfo.name, name, sizeof(cinfo.name));
return snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
}
/* register new midi synth port */
static int
snd_seq_midisynth_register_port(snd_seq_device_t *dev)
{
seq_midisynth_client_t *client;
seq_midisynth_t *msynth, *ms;
snd_seq_port_info_t *port;
snd_rawmidi_info_t *info;
int newclient = 0;
unsigned int p, ports;
snd_seq_client_callback_t callbacks;
snd_seq_port_callback_t pcallbacks;
snd_card_t *card = dev->card;
int device = dev->device;
unsigned int input_count = 0, output_count = 0;
snd_assert(card != NULL && device >= 0 && device < SNDRV_RAWMIDI_DEVICES, return -EINVAL);
info = kmalloc(sizeof(*info), GFP_KERNEL);
if (! info)
return -ENOMEM;
info->device = device;
info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
info->subdevice = 0;
if (snd_rawmidi_info_select(card, info) >= 0)
output_count = info->subdevices_count;
info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
if (snd_rawmidi_info_select(card, info) >= 0) {
input_count = info->subdevices_count;
}
ports = output_count;
if (ports < input_count)
ports = input_count;
if (ports == 0) {
kfree(info);
return -ENODEV;
}
if (ports > (256 / SNDRV_RAWMIDI_DEVICES))
ports = 256 / SNDRV_RAWMIDI_DEVICES;
down(&register_mutex);
client = synths[card->number];
if (client == NULL) {
newclient = 1;
client = kcalloc(1, sizeof(*client), GFP_KERNEL);
if (client == NULL) {
up(&register_mutex);
kfree(info);
return -ENOMEM;
}
memset(&callbacks, 0, sizeof(callbacks));
callbacks.private_data = client;
callbacks.allow_input = callbacks.allow_output = 1;
client->seq_client = snd_seq_create_kernel_client(card, 0, &callbacks);
if (client->seq_client < 0) {
kfree(client);
up(&register_mutex);
kfree(info);
return -ENOMEM;
}
set_client_name(client, card, info);
} else if (device == 0)
set_client_name(client, card, info); /* use the first device's name */
msynth = kcalloc(ports, sizeof(seq_midisynth_t), GFP_KERNEL);
port = kmalloc(sizeof(*port), GFP_KERNEL);
if (msynth == NULL || port == NULL)
goto __nomem;
for (p = 0; p < ports; p++) {
ms = &msynth[p];
if (snd_seq_midisynth_new(ms, card, device, p) < 0)
goto __nomem;
/* declare port */
memset(port, 0, sizeof(*port));
port->addr.client = client->seq_client;
port->addr.port = device * (256 / SNDRV_RAWMIDI_DEVICES) + p;
port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
memset(info, 0, sizeof(*info));
info->device = device;
if (p < output_count)
info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
else
info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
info->subdevice = p;
if (snd_rawmidi_info_select(card, info) >= 0)
strcpy(port->name, info->subname);
if (! port->name[0]) {
if (info->name[0]) {
if (ports > 1)
snprintf(port->name, sizeof(port->name), "%s-%d", info->name, p);
else
snprintf(port->name, sizeof(port->name), "%s", info->name);
} else {
/* last resort */
if (ports > 1)
sprintf(port->name, "MIDI %d-%d-%d", card->number, device, p);
else
sprintf(port->name, "MIDI %d-%d", card->number, device);
}
}
if ((info->flags & SNDRV_RAWMIDI_INFO_OUTPUT) && p < output_count)
port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
if ((info->flags & SNDRV_RAWMIDI_INFO_INPUT) && p < input_count)
port->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) &&
info->flags & SNDRV_RAWMIDI_INFO_DUPLEX)
port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
port->midi_channels = 16;
memset(&pcallbacks, 0, sizeof(pcallbacks));
pcallbacks.owner = THIS_MODULE;
pcallbacks.private_data = ms;
pcallbacks.subscribe = midisynth_subscribe;
pcallbacks.unsubscribe = midisynth_unsubscribe;
pcallbacks.use = midisynth_use;
pcallbacks.unuse = midisynth_unuse;
pcallbacks.event_input = event_process_midi;
port->kernel = &pcallbacks;
if (snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_CREATE_PORT, port)<0)
goto __nomem;
ms->seq_client = client->seq_client;
ms->seq_port = port->addr.port;
}
client->ports_per_device[device] = ports;
client->ports[device] = msynth;
client->num_ports++;
if (newclient)
synths[card->number] = client;
up(&register_mutex);
return 0; /* success */
__nomem:
if (msynth != NULL) {
for (p = 0; p < ports; p++)
snd_seq_midisynth_delete(&msynth[p]);
kfree(msynth);
}
if (newclient) {
snd_seq_delete_kernel_client(client->seq_client);
kfree(client);
}
kfree(info);
kfree(port);
up(&register_mutex);
return -ENOMEM;
}
/* release midi synth port */
static int
snd_seq_midisynth_unregister_port(snd_seq_device_t *dev)
{
seq_midisynth_client_t *client;
seq_midisynth_t *msynth;
snd_card_t *card = dev->card;
int device = dev->device, p, ports;
down(&register_mutex);
client = synths[card->number];
if (client == NULL || client->ports[device] == NULL) {
up(&register_mutex);
return -ENODEV;
}
ports = client->ports_per_device[device];
client->ports_per_device[device] = 0;
msynth = client->ports[device];
client->ports[device] = NULL;
snd_runtime_check(msynth != NULL || ports <= 0, goto __skip);
for (p = 0; p < ports; p++)
snd_seq_midisynth_delete(&msynth[p]);
kfree(msynth);
__skip:
client->num_ports--;
if (client->num_ports <= 0) {
snd_seq_delete_kernel_client(client->seq_client);
synths[card->number] = NULL;
kfree(client);
}
up(&register_mutex);
return 0;
}
static int __init alsa_seq_midi_init(void)
{
static snd_seq_dev_ops_t ops = {
snd_seq_midisynth_register_port,
snd_seq_midisynth_unregister_port,
};
memset(&synths, 0, sizeof(synths));
snd_seq_autoload_lock();
snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH, &ops, 0);
snd_seq_autoload_unlock();
return 0;
}
static void __exit alsa_seq_midi_exit(void)
{
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH);
}
module_init(alsa_seq_midi_init)
module_exit(alsa_seq_midi_exit)

View File

@@ -0,0 +1,735 @@
/*
* GM/GS/XG midi module.
*
* Copyright (C) 1999 Steve Ratcliffe
*
* Based on awe_wave.c by Takashi Iwai
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*
* This module is used to keep track of the current midi state.
* It can be used for drivers that are required to emulate midi when
* the hardware doesn't.
*
* It was written for a AWE64 driver, but there should be no AWE specific
* code in here. If there is it should be reported as a bug.
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <sound/core.h>
#include <sound/seq_kernel.h>
#include <sound/seq_midi_emul.h>
#include <sound/initval.h>
#include <sound/asoundef.h>
MODULE_AUTHOR("Takashi Iwai / Steve Ratcliffe");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI emulation.");
MODULE_LICENSE("GPL");
/* Prototypes for static functions */
static void note_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, int note, int vel);
static void do_control(snd_midi_op_t *ops, void *private,
snd_midi_channel_set_t *chset, snd_midi_channel_t *chan,
int control, int value);
static void rpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, snd_midi_channel_set_t *chset);
static void nrpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, snd_midi_channel_set_t *chset);
static void sysex(snd_midi_op_t *ops, void *private, unsigned char *sysex, int len, snd_midi_channel_set_t *chset);
static void all_sounds_off(snd_midi_op_t *ops, void *private, snd_midi_channel_t *chan);
static void all_notes_off(snd_midi_op_t *ops, void *private, snd_midi_channel_t *chan);
static void snd_midi_reset_controllers(snd_midi_channel_t *chan);
static void reset_all_channels(snd_midi_channel_set_t *chset);
/*
* Process an event in a driver independent way. This means dealing
* with RPN, NRPN, SysEx etc that are defined for common midi applications
* such as GM, GS and XG.
* There modes that this module will run in are:
* Generic MIDI - no interpretation at all, it will just save current values
* of controlers etc.
* GM - You can use all gm_ prefixed elements of chan. Controls, RPN, NRPN,
* SysEx will be interpreded as defined in General Midi.
* GS - You can use all gs_ prefixed elements of chan. Codes for GS will be
* interpreted.
* XG - You can use all xg_ prefixed elements of chan. Codes for XG will
* be interpreted.
*/
void
snd_midi_process_event(snd_midi_op_t *ops,
snd_seq_event_t *ev, snd_midi_channel_set_t *chanset)
{
snd_midi_channel_t *chan;
void *drv;
int dest_channel = 0;
if (ev == NULL || chanset == NULL) {
snd_printd("ev or chanbase NULL (snd_midi_process_event)\n");
return;
}
if (chanset->channels == NULL)
return;
if (snd_seq_ev_is_channel_type(ev)) {
dest_channel = ev->data.note.channel;
if (dest_channel >= chanset->max_channels) {
snd_printd("dest channel is %d, max is %d\n", dest_channel, chanset->max_channels);
return;
}
}
chan = chanset->channels + dest_channel;
drv = chanset->private_data;
/* EVENT_NOTE should be processed before queued */
if (ev->type == SNDRV_SEQ_EVENT_NOTE)
return;
/* Make sure that we don't have a note on that should really be
* a note off */
if (ev->type == SNDRV_SEQ_EVENT_NOTEON && ev->data.note.velocity == 0)
ev->type = SNDRV_SEQ_EVENT_NOTEOFF;
/* Make sure the note is within array range */
if (ev->type == SNDRV_SEQ_EVENT_NOTEON ||
ev->type == SNDRV_SEQ_EVENT_NOTEOFF ||
ev->type == SNDRV_SEQ_EVENT_KEYPRESS) {
if (ev->data.note.note >= 128)
return;
}
switch (ev->type) {
case SNDRV_SEQ_EVENT_NOTEON:
if (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON) {
if (ops->note_off)
ops->note_off(drv, ev->data.note.note, 0, chan);
}
chan->note[ev->data.note.note] = SNDRV_MIDI_NOTE_ON;
if (ops->note_on)
ops->note_on(drv, ev->data.note.note, ev->data.note.velocity, chan);
break;
case SNDRV_SEQ_EVENT_NOTEOFF:
if (! (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON))
break;
if (ops->note_off)
note_off(ops, drv, chan, ev->data.note.note, ev->data.note.velocity);
break;
case SNDRV_SEQ_EVENT_KEYPRESS:
if (ops->key_press)
ops->key_press(drv, ev->data.note.note, ev->data.note.velocity, chan);
break;
case SNDRV_SEQ_EVENT_CONTROLLER:
do_control(ops, drv, chanset, chan,
ev->data.control.param, ev->data.control.value);
break;
case SNDRV_SEQ_EVENT_PGMCHANGE:
chan->midi_program = ev->data.control.value;
break;
case SNDRV_SEQ_EVENT_PITCHBEND:
chan->midi_pitchbend = ev->data.control.value;
if (ops->control)
ops->control(drv, MIDI_CTL_PITCHBEND, chan);
break;
case SNDRV_SEQ_EVENT_CHANPRESS:
chan->midi_pressure = ev->data.control.value;
if (ops->control)
ops->control(drv, MIDI_CTL_CHAN_PRESSURE, chan);
break;
case SNDRV_SEQ_EVENT_CONTROL14:
/* Best guess is that this is any of the 14 bit controller values */
if (ev->data.control.param < 32) {
/* set low part first */
chan->control[ev->data.control.param + 32] =
ev->data.control.value & 0x7f;
do_control(ops, drv, chanset, chan,
ev->data.control.param,
((ev->data.control.value>>7) & 0x7f));
} else
do_control(ops, drv, chanset, chan,
ev->data.control.param,
ev->data.control.value);
break;
case SNDRV_SEQ_EVENT_NONREGPARAM:
/* Break it back into its controler values */
chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED;
chan->control[MIDI_CTL_MSB_DATA_ENTRY]
= (ev->data.control.value >> 7) & 0x7f;
chan->control[MIDI_CTL_LSB_DATA_ENTRY]
= ev->data.control.value & 0x7f;
chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB]
= (ev->data.control.param >> 7) & 0x7f;
chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB]
= ev->data.control.param & 0x7f;
nrpn(ops, drv, chan, chanset);
break;
case SNDRV_SEQ_EVENT_REGPARAM:
/* Break it back into its controler values */
chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED;
chan->control[MIDI_CTL_MSB_DATA_ENTRY]
= (ev->data.control.value >> 7) & 0x7f;
chan->control[MIDI_CTL_LSB_DATA_ENTRY]
= ev->data.control.value & 0x7f;
chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB]
= (ev->data.control.param >> 7) & 0x7f;
chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB]
= ev->data.control.param & 0x7f;
rpn(ops, drv, chan, chanset);
break;
case SNDRV_SEQ_EVENT_SYSEX:
if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE) {
unsigned char sysexbuf[64];
int len;
len = snd_seq_expand_var_event(ev, sizeof(sysexbuf), sysexbuf, 1, 0);
if (len > 0)
sysex(ops, drv, sysexbuf, len, chanset);
}
break;
case SNDRV_SEQ_EVENT_SONGPOS:
case SNDRV_SEQ_EVENT_SONGSEL:
case SNDRV_SEQ_EVENT_CLOCK:
case SNDRV_SEQ_EVENT_START:
case SNDRV_SEQ_EVENT_CONTINUE:
case SNDRV_SEQ_EVENT_STOP:
case SNDRV_SEQ_EVENT_QFRAME:
case SNDRV_SEQ_EVENT_TEMPO:
case SNDRV_SEQ_EVENT_TIMESIGN:
case SNDRV_SEQ_EVENT_KEYSIGN:
goto not_yet;
case SNDRV_SEQ_EVENT_SENSING:
break;
case SNDRV_SEQ_EVENT_CLIENT_START:
case SNDRV_SEQ_EVENT_CLIENT_EXIT:
case SNDRV_SEQ_EVENT_CLIENT_CHANGE:
case SNDRV_SEQ_EVENT_PORT_START:
case SNDRV_SEQ_EVENT_PORT_EXIT:
case SNDRV_SEQ_EVENT_PORT_CHANGE:
case SNDRV_SEQ_EVENT_SAMPLE:
case SNDRV_SEQ_EVENT_SAMPLE_START:
case SNDRV_SEQ_EVENT_SAMPLE_STOP:
case SNDRV_SEQ_EVENT_SAMPLE_FREQ:
case SNDRV_SEQ_EVENT_SAMPLE_VOLUME:
case SNDRV_SEQ_EVENT_SAMPLE_LOOP:
case SNDRV_SEQ_EVENT_SAMPLE_POSITION:
case SNDRV_SEQ_EVENT_ECHO:
not_yet:
default:
/*snd_printd("Unimplemented event %d\n", ev->type);*/
break;
}
}
/*
* release note
*/
static void
note_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, int note, int vel)
{
if (chan->gm_hold) {
/* Hold this note until pedal is turned off */
chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED;
} else if (chan->note[note] & SNDRV_MIDI_NOTE_SOSTENUTO) {
/* Mark this note as release; it will be turned off when sostenuto
* is turned off */
chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED;
} else {
chan->note[note] = 0;
if (ops->note_off)
ops->note_off(drv, note, vel, chan);
}
}
/*
* Do all driver independent operations for this controler and pass
* events that need to take place immediately to the driver.
*/
static void
do_control(snd_midi_op_t *ops, void *drv, snd_midi_channel_set_t *chset,
snd_midi_channel_t *chan, int control, int value)
{
int i;
/* Switches */
if ((control >=64 && control <=69) || (control >= 80 && control <= 83)) {
/* These are all switches; either off or on so set to 0 or 127 */
value = (value >= 64)? 127: 0;
}
chan->control[control] = value;
switch (control) {
case MIDI_CTL_SUSTAIN:
if (value == 0) {
/* Sustain has been released, turn off held notes */
for (i = 0; i < 128; i++) {
if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) {
chan->note[i] = SNDRV_MIDI_NOTE_OFF;
if (ops->note_off)
ops->note_off(drv, i, 0, chan);
}
}
}
break;
case MIDI_CTL_PORTAMENTO:
break;
case MIDI_CTL_SOSTENUTO:
if (value) {
/* Mark each note that is currently held down */
for (i = 0; i < 128; i++) {
if (chan->note[i] & SNDRV_MIDI_NOTE_ON)
chan->note[i] |= SNDRV_MIDI_NOTE_SOSTENUTO;
}
} else {
/* release all notes that were held */
for (i = 0; i < 128; i++) {
if (chan->note[i] & SNDRV_MIDI_NOTE_SOSTENUTO) {
chan->note[i] &= ~SNDRV_MIDI_NOTE_SOSTENUTO;
if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) {
chan->note[i] = SNDRV_MIDI_NOTE_OFF;
if (ops->note_off)
ops->note_off(drv, i, 0, chan);
}
}
}
}
break;
case MIDI_CTL_MSB_DATA_ENTRY:
chan->control[MIDI_CTL_LSB_DATA_ENTRY] = 0;
/* go through here */
case MIDI_CTL_LSB_DATA_ENTRY:
if (chan->param_type == SNDRV_MIDI_PARAM_TYPE_REGISTERED)
rpn(ops, drv, chan, chset);
else
nrpn(ops, drv, chan, chset);
break;
case MIDI_CTL_REGIST_PARM_NUM_LSB:
case MIDI_CTL_REGIST_PARM_NUM_MSB:
chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED;
break;
case MIDI_CTL_NONREG_PARM_NUM_LSB:
case MIDI_CTL_NONREG_PARM_NUM_MSB:
chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED;
break;
case MIDI_CTL_ALL_SOUNDS_OFF:
all_sounds_off(ops, drv, chan);
break;
case MIDI_CTL_ALL_NOTES_OFF:
all_notes_off(ops, drv, chan);
break;
case MIDI_CTL_MSB_BANK:
if (chset->midi_mode == SNDRV_MIDI_MODE_XG) {
if (value == 127)
chan->drum_channel = 1;
else
chan->drum_channel = 0;
}
break;
case MIDI_CTL_LSB_BANK:
break;
case MIDI_CTL_RESET_CONTROLLERS:
snd_midi_reset_controllers(chan);
break;
case MIDI_CTL_SOFT_PEDAL:
case MIDI_CTL_LEGATO_FOOTSWITCH:
case MIDI_CTL_HOLD2:
case MIDI_CTL_SC1_SOUND_VARIATION:
case MIDI_CTL_SC2_TIMBRE:
case MIDI_CTL_SC3_RELEASE_TIME:
case MIDI_CTL_SC4_ATTACK_TIME:
case MIDI_CTL_SC5_BRIGHTNESS:
case MIDI_CTL_E1_REVERB_DEPTH:
case MIDI_CTL_E2_TREMOLO_DEPTH:
case MIDI_CTL_E3_CHORUS_DEPTH:
case MIDI_CTL_E4_DETUNE_DEPTH:
case MIDI_CTL_E5_PHASER_DEPTH:
goto notyet;
notyet:
default:
if (ops->control)
ops->control(drv, control, chan);
break;
}
}
/*
* initialize the MIDI status
*/
void
snd_midi_channel_set_clear(snd_midi_channel_set_t *chset)
{
int i;
chset->midi_mode = SNDRV_MIDI_MODE_GM;
chset->gs_master_volume = 127;
for (i = 0; i < chset->max_channels; i++) {
snd_midi_channel_t *chan = chset->channels + i;
memset(chan->note, 0, sizeof(chan->note));
chan->midi_aftertouch = 0;
chan->midi_pressure = 0;
chan->midi_program = 0;
chan->midi_pitchbend = 0;
snd_midi_reset_controllers(chan);
chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
chan->gm_rpn_fine_tuning = 0;
chan->gm_rpn_coarse_tuning = 0;
if (i == 9)
chan->drum_channel = 1;
else
chan->drum_channel = 0;
}
}
/*
* Process a rpn message.
*/
static void
rpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan,
snd_midi_channel_set_t *chset)
{
int type;
int val;
if (chset->midi_mode != SNDRV_MIDI_MODE_NONE) {
type = (chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] << 8) |
chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB];
val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) |
chan->control[MIDI_CTL_LSB_DATA_ENTRY];
switch (type) {
case 0x0000: /* Pitch bend sensitivity */
/* MSB only / 1 semitone per 128 */
chan->gm_rpn_pitch_bend_range = val;
break;
case 0x0001: /* fine tuning: */
/* MSB/LSB, 8192=center, 100/8192 cent step */
chan->gm_rpn_fine_tuning = val - 8192;
break;
case 0x0002: /* coarse tuning */
/* MSB only / 8192=center, 1 semitone per 128 */
chan->gm_rpn_coarse_tuning = val - 8192;
break;
case 0x7F7F: /* "lock-in" RPN */
/* ignored */
break;
}
}
/* should call nrpn or rpn callback here.. */
}
/*
* Process an nrpn message.
*/
static void
nrpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan,
snd_midi_channel_set_t *chset)
{
/* parse XG NRPNs here if possible */
if (ops->nrpn)
ops->nrpn(drv, chan, chset);
}
/*
* convert channel parameter in GS sysex
*/
static int
get_channel(unsigned char cmd)
{
int p = cmd & 0x0f;
if (p == 0)
p = 9;
else if (p < 10)
p--;
return p;
}
/*
* Process a sysex message.
*/
static void
sysex(snd_midi_op_t *ops, void *private, unsigned char *buf, int len, snd_midi_channel_set_t *chset)
{
/* GM on */
static unsigned char gm_on_macro[] = {
0x7e,0x7f,0x09,0x01,
};
/* XG on */
static unsigned char xg_on_macro[] = {
0x43,0x10,0x4c,0x00,0x00,0x7e,0x00,
};
/* GS prefix
* drum channel: XX=0x1?(channel), YY=0x15, ZZ=on/off
* reverb mode: XX=0x01, YY=0x30, ZZ=0-7
* chorus mode: XX=0x01, YY=0x38, ZZ=0-7
* master vol: XX=0x00, YY=0x04, ZZ=0-127
*/
static unsigned char gs_pfx_macro[] = {
0x41,0x10,0x42,0x12,0x40,/*XX,YY,ZZ*/
};
int parsed = SNDRV_MIDI_SYSEX_NOT_PARSED;
if (len <= 0 || buf[0] != 0xf0)
return;
/* skip first byte */
buf++;
len--;
/* GM on */
if (len >= (int)sizeof(gm_on_macro) &&
memcmp(buf, gm_on_macro, sizeof(gm_on_macro)) == 0) {
if (chset->midi_mode != SNDRV_MIDI_MODE_GS &&
chset->midi_mode != SNDRV_MIDI_MODE_XG) {
chset->midi_mode = SNDRV_MIDI_MODE_GM;
reset_all_channels(chset);
parsed = SNDRV_MIDI_SYSEX_GM_ON;
}
}
/* GS macros */
else if (len >= 8 &&
memcmp(buf, gs_pfx_macro, sizeof(gs_pfx_macro)) == 0) {
if (chset->midi_mode != SNDRV_MIDI_MODE_GS &&
chset->midi_mode != SNDRV_MIDI_MODE_XG)
chset->midi_mode = SNDRV_MIDI_MODE_GS;
if (buf[5] == 0x00 && buf[6] == 0x7f && buf[7] == 0x00) {
/* GS reset */
parsed = SNDRV_MIDI_SYSEX_GS_RESET;
reset_all_channels(chset);
}
else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x15) {
/* drum pattern */
int p = get_channel(buf[5]);
if (p < chset->max_channels) {
parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL;
if (buf[7])
chset->channels[p].drum_channel = 1;
else
chset->channels[p].drum_channel = 0;
}
} else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x21) {
/* program */
int p = get_channel(buf[5]);
if (p < chset->max_channels &&
! chset->channels[p].drum_channel) {
parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL;
chset->channels[p].midi_program = buf[7];
}
} else if (buf[5] == 0x01 && buf[6] == 0x30) {
/* reverb mode */
parsed = SNDRV_MIDI_SYSEX_GS_REVERB_MODE;
chset->gs_reverb_mode = buf[7];
} else if (buf[5] == 0x01 && buf[6] == 0x38) {
/* chorus mode */
parsed = SNDRV_MIDI_SYSEX_GS_CHORUS_MODE;
chset->gs_chorus_mode = buf[7];
} else if (buf[5] == 0x00 && buf[6] == 0x04) {
/* master volume */
parsed = SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME;
chset->gs_master_volume = buf[7];
}
}
/* XG on */
else if (len >= (int)sizeof(xg_on_macro) &&
memcmp(buf, xg_on_macro, sizeof(xg_on_macro)) == 0) {
int i;
chset->midi_mode = SNDRV_MIDI_MODE_XG;
parsed = SNDRV_MIDI_SYSEX_XG_ON;
/* reset CC#0 for drums */
for (i = 0; i < chset->max_channels; i++) {
if (chset->channels[i].drum_channel)
chset->channels[i].control[MIDI_CTL_MSB_BANK] = 127;
else
chset->channels[i].control[MIDI_CTL_MSB_BANK] = 0;
}
}
if (ops->sysex)
ops->sysex(private, buf - 1, len + 1, parsed, chset);
}
/*
* all sound off
*/
static void
all_sounds_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan)
{
int n;
if (! ops->note_terminate)
return;
for (n = 0; n < 128; n++) {
if (chan->note[n]) {
ops->note_terminate(drv, n, chan);
chan->note[n] = 0;
}
}
}
/*
* all notes off
*/
static void
all_notes_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan)
{
int n;
if (! ops->note_off)
return;
for (n = 0; n < 128; n++) {
if (chan->note[n] == SNDRV_MIDI_NOTE_ON)
note_off(ops, drv, chan, n, 0);
}
}
/*
* Initialise a single midi channel control block.
*/
static void snd_midi_channel_init(snd_midi_channel_t *p, int n)
{
if (p == NULL)
return;
memset(p, 0, sizeof(snd_midi_channel_t));
p->private = NULL;
p->number = n;
snd_midi_reset_controllers(p);
p->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
p->gm_rpn_fine_tuning = 0;
p->gm_rpn_coarse_tuning = 0;
if (n == 9)
p->drum_channel = 1; /* Default ch 10 as drums */
}
/*
* Allocate and initialise a set of midi channel control blocks.
*/
static snd_midi_channel_t *snd_midi_channel_init_set(int n)
{
snd_midi_channel_t *chan;
int i;
chan = kmalloc(n * sizeof(snd_midi_channel_t), GFP_KERNEL);
if (chan) {
for (i = 0; i < n; i++)
snd_midi_channel_init(chan+i, i);
}
return chan;
}
/*
* reset all midi channels
*/
static void
reset_all_channels(snd_midi_channel_set_t *chset)
{
int ch;
for (ch = 0; ch < chset->max_channels; ch++) {
snd_midi_channel_t *chan = chset->channels + ch;
snd_midi_reset_controllers(chan);
chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
chan->gm_rpn_fine_tuning = 0;
chan->gm_rpn_coarse_tuning = 0;
if (ch == 9)
chan->drum_channel = 1;
else
chan->drum_channel = 0;
}
}
/*
* Allocate and initialise a midi channel set.
*/
snd_midi_channel_set_t *snd_midi_channel_alloc_set(int n)
{
snd_midi_channel_set_t *chset;
chset = kmalloc(sizeof(*chset), GFP_KERNEL);
if (chset) {
chset->channels = snd_midi_channel_init_set(n);
chset->private_data = NULL;
chset->max_channels = n;
}
return chset;
}
/*
* Reset the midi controllers on a particular channel to default values.
*/
static void snd_midi_reset_controllers(snd_midi_channel_t *chan)
{
memset(chan->control, 0, sizeof(chan->control));
chan->gm_volume = 127;
chan->gm_expression = 127;
chan->gm_pan = 64;
}
/*
* Free a midi channel set.
*/
void snd_midi_channel_free_set(snd_midi_channel_set_t *chset)
{
if (chset == NULL)
return;
kfree(chset->channels);
kfree(chset);
}
static int __init alsa_seq_midi_emul_init(void)
{
return 0;
}
static void __exit alsa_seq_midi_emul_exit(void)
{
}
module_init(alsa_seq_midi_emul_init)
module_exit(alsa_seq_midi_emul_exit)
EXPORT_SYMBOL(snd_midi_process_event);
EXPORT_SYMBOL(snd_midi_channel_set_clear);
EXPORT_SYMBOL(snd_midi_channel_alloc_set);
EXPORT_SYMBOL(snd_midi_channel_free_set);

View File

@@ -0,0 +1,539 @@
/*
* MIDI byte <-> sequencer event coder
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>,
* Jaroslav Kysela <perex@suse.cz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sound/driver.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <sound/core.h>
#include <sound/seq_kernel.h>
#include <sound/seq_midi_event.h>
#include <sound/asoundef.h>
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("MIDI byte <-> sequencer event coder");
MODULE_LICENSE("GPL");
/* queue type */
/* from 0 to 7 are normal commands (note off, on, etc.) */
#define ST_NOTEOFF 0
#define ST_NOTEON 1
#define ST_SPECIAL 8
#define ST_SYSEX ST_SPECIAL
/* from 8 to 15 are events for 0xf0-0xf7 */
/* status event types */
typedef void (*event_encode_t)(snd_midi_event_t *dev, snd_seq_event_t *ev);
typedef void (*event_decode_t)(snd_seq_event_t *ev, unsigned char *buf);
/*
* prototypes
*/
static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
static void note_decode(snd_seq_event_t *ev, unsigned char *buf);
static void one_param_decode(snd_seq_event_t *ev, unsigned char *buf);
static void pitchbend_decode(snd_seq_event_t *ev, unsigned char *buf);
static void two_param_decode(snd_seq_event_t *ev, unsigned char *buf);
static void songpos_decode(snd_seq_event_t *ev, unsigned char *buf);
/*
* event list
*/
static struct status_event_list_t {
int event;
int qlen;
event_encode_t encode;
event_decode_t decode;
} status_event[] = {
/* 0x80 - 0xf0 */
{SNDRV_SEQ_EVENT_NOTEOFF, 2, note_event, note_decode},
{SNDRV_SEQ_EVENT_NOTEON, 2, note_event, note_decode},
{SNDRV_SEQ_EVENT_KEYPRESS, 2, note_event, note_decode},
{SNDRV_SEQ_EVENT_CONTROLLER, 2, two_param_ctrl_event, two_param_decode},
{SNDRV_SEQ_EVENT_PGMCHANGE, 1, one_param_ctrl_event, one_param_decode},
{SNDRV_SEQ_EVENT_CHANPRESS, 1, one_param_ctrl_event, one_param_decode},
{SNDRV_SEQ_EVENT_PITCHBEND, 2, pitchbend_ctrl_event, pitchbend_decode},
{SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf0 */
/* 0xf0 - 0xff */
{SNDRV_SEQ_EVENT_SYSEX, 1, NULL, NULL}, /* sysex: 0xf0 */
{SNDRV_SEQ_EVENT_QFRAME, 1, one_param_event, one_param_decode}, /* 0xf1 */
{SNDRV_SEQ_EVENT_SONGPOS, 2, songpos_event, songpos_decode}, /* 0xf2 */
{SNDRV_SEQ_EVENT_SONGSEL, 1, one_param_event, one_param_decode}, /* 0xf3 */
{SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf4 */
{SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf5 */
{SNDRV_SEQ_EVENT_TUNE_REQUEST, 0, NULL, NULL}, /* 0xf6 */
{SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf7 */
{SNDRV_SEQ_EVENT_CLOCK, 0, NULL, NULL}, /* 0xf8 */
{SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf9 */
{SNDRV_SEQ_EVENT_START, 0, NULL, NULL}, /* 0xfa */
{SNDRV_SEQ_EVENT_CONTINUE, 0, NULL, NULL}, /* 0xfb */
{SNDRV_SEQ_EVENT_STOP, 0, NULL, NULL}, /* 0xfc */
{SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xfd */
{SNDRV_SEQ_EVENT_SENSING, 0, NULL, NULL}, /* 0xfe */
{SNDRV_SEQ_EVENT_RESET, 0, NULL, NULL}, /* 0xff */
};
static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int len, snd_seq_event_t *ev);
static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev);
static struct extra_event_list_t {
int event;
int (*decode)(snd_midi_event_t *dev, unsigned char *buf, int len, snd_seq_event_t *ev);
} extra_event[] = {
{SNDRV_SEQ_EVENT_CONTROL14, extra_decode_ctrl14},
{SNDRV_SEQ_EVENT_NONREGPARAM, extra_decode_xrpn},
{SNDRV_SEQ_EVENT_REGPARAM, extra_decode_xrpn},
};
/*
* new/delete record
*/
int snd_midi_event_new(int bufsize, snd_midi_event_t **rdev)
{
snd_midi_event_t *dev;
*rdev = NULL;
dev = kcalloc(1, sizeof(*dev), GFP_KERNEL);
if (dev == NULL)
return -ENOMEM;
if (bufsize > 0) {
dev->buf = kmalloc(bufsize, GFP_KERNEL);
if (dev->buf == NULL) {
kfree(dev);
return -ENOMEM;
}
}
dev->bufsize = bufsize;
dev->lastcmd = 0xff;
spin_lock_init(&dev->lock);
*rdev = dev;
return 0;
}
void snd_midi_event_free(snd_midi_event_t *dev)
{
if (dev != NULL) {
kfree(dev->buf);
kfree(dev);
}
}
/*
* initialize record
*/
inline static void reset_encode(snd_midi_event_t *dev)
{
dev->read = 0;
dev->qlen = 0;
dev->type = 0;
}
void snd_midi_event_reset_encode(snd_midi_event_t *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->lock, flags);
reset_encode(dev);
spin_unlock_irqrestore(&dev->lock, flags);
}
void snd_midi_event_reset_decode(snd_midi_event_t *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->lock, flags);
dev->lastcmd = 0xff;
spin_unlock_irqrestore(&dev->lock, flags);
}
void snd_midi_event_init(snd_midi_event_t *dev)
{
snd_midi_event_reset_encode(dev);
snd_midi_event_reset_decode(dev);
}
void snd_midi_event_no_status(snd_midi_event_t *dev, int on)
{
dev->nostat = on ? 1 : 0;
}
/*
* resize buffer
*/
int snd_midi_event_resize_buffer(snd_midi_event_t *dev, int bufsize)
{
unsigned char *new_buf, *old_buf;
unsigned long flags;
if (bufsize == dev->bufsize)
return 0;
new_buf = kmalloc(bufsize, GFP_KERNEL);
if (new_buf == NULL)
return -ENOMEM;
spin_lock_irqsave(&dev->lock, flags);
old_buf = dev->buf;
dev->buf = new_buf;
dev->bufsize = bufsize;
reset_encode(dev);
spin_unlock_irqrestore(&dev->lock, flags);
kfree(old_buf);
return 0;
}
/*
* read bytes and encode to sequencer event if finished
* return the size of encoded bytes
*/
long snd_midi_event_encode(snd_midi_event_t *dev, unsigned char *buf, long count, snd_seq_event_t *ev)
{
long result = 0;
int rc;
ev->type = SNDRV_SEQ_EVENT_NONE;
while (count-- > 0) {
rc = snd_midi_event_encode_byte(dev, *buf++, ev);
result++;
if (rc < 0)
return rc;
else if (rc > 0)
return result;
}
return result;
}
/*
* read one byte and encode to sequencer event:
* return 1 if MIDI bytes are encoded to an event
* 0 data is not finished
* negative for error
*/
int snd_midi_event_encode_byte(snd_midi_event_t *dev, int c, snd_seq_event_t *ev)
{
int rc = 0;
unsigned long flags;
c &= 0xff;
if (c >= MIDI_CMD_COMMON_CLOCK) {
/* real-time event */
ev->type = status_event[ST_SPECIAL + c - 0xf0].event;
ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
return 1;
}
spin_lock_irqsave(&dev->lock, flags);
if (dev->qlen > 0) {
/* rest of command */
dev->buf[dev->read++] = c;
if (dev->type != ST_SYSEX)
dev->qlen--;
} else {
/* new command */
dev->read = 1;
if (c & 0x80) {
dev->buf[0] = c;
if ((c & 0xf0) == 0xf0) /* special events */
dev->type = (c & 0x0f) + ST_SPECIAL;
else
dev->type = (c >> 4) & 0x07;
dev->qlen = status_event[dev->type].qlen;
} else {
/* process this byte as argument */
dev->buf[dev->read++] = c;
dev->qlen = status_event[dev->type].qlen - 1;
}
}
if (dev->qlen == 0) {
ev->type = status_event[dev->type].event;
ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
if (status_event[dev->type].encode) /* set data values */
status_event[dev->type].encode(dev, ev);
rc = 1;
} else if (dev->type == ST_SYSEX) {
if (c == MIDI_CMD_COMMON_SYSEX_END ||
dev->read >= dev->bufsize) {
ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
ev->flags |= SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
ev->type = SNDRV_SEQ_EVENT_SYSEX;
ev->data.ext.len = dev->read;
ev->data.ext.ptr = dev->buf;
if (c != MIDI_CMD_COMMON_SYSEX_END)
dev->read = 0; /* continue to parse */
else
reset_encode(dev); /* all parsed */
rc = 1;
}
}
spin_unlock_irqrestore(&dev->lock, flags);
return rc;
}
/* encode note event */
static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
{
ev->data.note.channel = dev->buf[0] & 0x0f;
ev->data.note.note = dev->buf[1];
ev->data.note.velocity = dev->buf[2];
}
/* encode one parameter controls */
static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
{
ev->data.control.channel = dev->buf[0] & 0x0f;
ev->data.control.value = dev->buf[1];
}
/* encode pitch wheel change */
static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
{
ev->data.control.channel = dev->buf[0] & 0x0f;
ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1] - 8192;
}
/* encode midi control change */
static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
{
ev->data.control.channel = dev->buf[0] & 0x0f;
ev->data.control.param = dev->buf[1];
ev->data.control.value = dev->buf[2];
}
/* encode one parameter value*/
static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
{
ev->data.control.value = dev->buf[1];
}
/* encode song position */
static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
{
ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1];
}
/*
* decode from a sequencer event to midi bytes
* return the size of decoded midi events
*/
long snd_midi_event_decode(snd_midi_event_t *dev, unsigned char *buf, long count, snd_seq_event_t *ev)
{
unsigned int cmd, type;
if (ev->type == SNDRV_SEQ_EVENT_NONE)
return -ENOENT;
for (type = 0; type < ARRAY_SIZE(status_event); type++) {
if (ev->type == status_event[type].event)
goto __found;
}
for (type = 0; type < ARRAY_SIZE(extra_event); type++) {
if (ev->type == extra_event[type].event)
return extra_event[type].decode(dev, buf, count, ev);
}
return -ENOENT;
__found:
if (type >= ST_SPECIAL)
cmd = 0xf0 + (type - ST_SPECIAL);
else
/* data.note.channel and data.control.channel is identical */
cmd = 0x80 | (type << 4) | (ev->data.note.channel & 0x0f);
if (cmd == MIDI_CMD_COMMON_SYSEX) {
snd_midi_event_reset_decode(dev);
return snd_seq_expand_var_event(ev, count, buf, 1, 0);
} else {
int qlen;
unsigned char xbuf[4];
unsigned long flags;
spin_lock_irqsave(&dev->lock, flags);
if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd || dev->nostat) {
dev->lastcmd = cmd;
spin_unlock_irqrestore(&dev->lock, flags);
xbuf[0] = cmd;
if (status_event[type].decode)
status_event[type].decode(ev, xbuf + 1);
qlen = status_event[type].qlen + 1;
} else {
spin_unlock_irqrestore(&dev->lock, flags);
if (status_event[type].decode)
status_event[type].decode(ev, xbuf + 0);
qlen = status_event[type].qlen;
}
if (count < qlen)
return -ENOMEM;
memcpy(buf, xbuf, qlen);
return qlen;
}
}
/* decode note event */
static void note_decode(snd_seq_event_t *ev, unsigned char *buf)
{
buf[0] = ev->data.note.note & 0x7f;
buf[1] = ev->data.note.velocity & 0x7f;
}
/* decode one parameter controls */
static void one_param_decode(snd_seq_event_t *ev, unsigned char *buf)
{
buf[0] = ev->data.control.value & 0x7f;
}
/* decode pitch wheel change */
static void pitchbend_decode(snd_seq_event_t *ev, unsigned char *buf)
{
int value = ev->data.control.value + 8192;
buf[0] = value & 0x7f;
buf[1] = (value >> 7) & 0x7f;
}
/* decode midi control change */
static void two_param_decode(snd_seq_event_t *ev, unsigned char *buf)
{
buf[0] = ev->data.control.param & 0x7f;
buf[1] = ev->data.control.value & 0x7f;
}
/* decode song position */
static void songpos_decode(snd_seq_event_t *ev, unsigned char *buf)
{
buf[0] = ev->data.control.value & 0x7f;
buf[1] = (ev->data.control.value >> 7) & 0x7f;
}
/* decode 14bit control */
static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev)
{
unsigned char cmd;
int idx = 0;
cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f);
if (ev->data.control.param < 0x20) {
if (count < 4)
return -ENOMEM;
if (dev->nostat && count < 6)
return -ENOMEM;
if (cmd != dev->lastcmd || dev->nostat) {
if (count < 5)
return -ENOMEM;
buf[idx++] = dev->lastcmd = cmd;
}
buf[idx++] = ev->data.control.param;
buf[idx++] = (ev->data.control.value >> 7) & 0x7f;
if (dev->nostat)
buf[idx++] = cmd;
buf[idx++] = ev->data.control.param + 0x20;
buf[idx++] = ev->data.control.value & 0x7f;
} else {
if (count < 2)
return -ENOMEM;
if (cmd != dev->lastcmd || dev->nostat) {
if (count < 3)
return -ENOMEM;
buf[idx++] = dev->lastcmd = cmd;
}
buf[idx++] = ev->data.control.param & 0x7f;
buf[idx++] = ev->data.control.value & 0x7f;
}
return idx;
}
/* decode reg/nonreg param */
static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev)
{
unsigned char cmd;
char *cbytes;
static char cbytes_nrpn[4] = { MIDI_CTL_NONREG_PARM_NUM_MSB,
MIDI_CTL_NONREG_PARM_NUM_LSB,
MIDI_CTL_MSB_DATA_ENTRY,
MIDI_CTL_LSB_DATA_ENTRY };
static char cbytes_rpn[4] = { MIDI_CTL_REGIST_PARM_NUM_MSB,
MIDI_CTL_REGIST_PARM_NUM_LSB,
MIDI_CTL_MSB_DATA_ENTRY,
MIDI_CTL_LSB_DATA_ENTRY };
unsigned char bytes[4];
int idx = 0, i;
if (count < 8)
return -ENOMEM;
if (dev->nostat && count < 12)
return -ENOMEM;
cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f);
bytes[0] = ev->data.control.param & 0x007f;
bytes[1] = (ev->data.control.param & 0x3f80) >> 7;
bytes[2] = ev->data.control.value & 0x007f;
bytes[3] = (ev->data.control.value & 0x3f80) >> 7;
if (cmd != dev->lastcmd && !dev->nostat) {
if (count < 9)
return -ENOMEM;
buf[idx++] = dev->lastcmd = cmd;
}
cbytes = ev->type == SNDRV_SEQ_EVENT_NONREGPARAM ? cbytes_nrpn : cbytes_rpn;
for (i = 0; i < 4; i++) {
if (dev->nostat)
buf[idx++] = dev->lastcmd = cmd;
buf[idx++] = cbytes[i];
buf[idx++] = bytes[i];
}
return idx;
}
/*
* exports
*/
EXPORT_SYMBOL(snd_midi_event_new);
EXPORT_SYMBOL(snd_midi_event_free);
EXPORT_SYMBOL(snd_midi_event_resize_buffer);
EXPORT_SYMBOL(snd_midi_event_init);
EXPORT_SYMBOL(snd_midi_event_reset_encode);
EXPORT_SYMBOL(snd_midi_event_reset_decode);
EXPORT_SYMBOL(snd_midi_event_no_status);
EXPORT_SYMBOL(snd_midi_event_encode);
EXPORT_SYMBOL(snd_midi_event_encode_byte);
EXPORT_SYMBOL(snd_midi_event_decode);
static int __init alsa_seq_midi_event_init(void)
{
return 0;
}
static void __exit alsa_seq_midi_event_exit(void)
{
}
module_init(alsa_seq_midi_event_init)
module_exit(alsa_seq_midi_event_exit)

674
sound/core/seq/seq_ports.c Normal file
View File

@@ -0,0 +1,674 @@
/*
* ALSA sequencer Ports
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
* Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <sound/core.h>
#include <linux/slab.h>
#include "seq_system.h"
#include "seq_ports.h"
#include "seq_clientmgr.h"
/*
registration of client ports
*/
/*
NOTE: the current implementation of the port structure as a linked list is
not optimal for clients that have many ports. For sending messages to all
subscribers of a port we first need to find the address of the port
structure, which means we have to traverse the list. A direct access table
(array) would be better, but big preallocated arrays waste memory.
Possible actions:
1) leave it this way, a client does normaly does not have more than a few
ports
2) replace the linked list of ports by a array of pointers which is
dynamicly kmalloced. When a port is added or deleted we can simply allocate
a new array, copy the corresponding pointers, and delete the old one. We
then only need a pointer to this array, and an integer that tells us how
much elements are in array.
*/
/* return pointer to port structure - port is locked if found */
client_port_t *snd_seq_port_use_ptr(client_t *client, int num)
{
struct list_head *p;
client_port_t *port;
if (client == NULL)
return NULL;
read_lock(&client->ports_lock);
list_for_each(p, &client->ports_list_head) {
port = list_entry(p, client_port_t, list);
if (port->addr.port == num) {
if (port->closing)
break; /* deleting now */
snd_use_lock_use(&port->use_lock);
read_unlock(&client->ports_lock);
return port;
}
}
read_unlock(&client->ports_lock);
return NULL; /* not found */
}
/* search for the next port - port is locked if found */
client_port_t *snd_seq_port_query_nearest(client_t *client, snd_seq_port_info_t *pinfo)
{
int num;
struct list_head *p;
client_port_t *port, *found;
num = pinfo->addr.port;
found = NULL;
read_lock(&client->ports_lock);
list_for_each(p, &client->ports_list_head) {
port = list_entry(p, client_port_t, list);
if (port->addr.port < num)
continue;
if (port->addr.port == num) {
found = port;
break;
}
if (found == NULL || port->addr.port < found->addr.port)
found = port;
}
if (found) {
if (found->closing)
found = NULL;
else
snd_use_lock_use(&found->use_lock);
}
read_unlock(&client->ports_lock);
return found;
}
/* initialize port_subs_info_t */
static void port_subs_info_init(port_subs_info_t *grp)
{
INIT_LIST_HEAD(&grp->list_head);
grp->count = 0;
grp->exclusive = 0;
rwlock_init(&grp->list_lock);
init_rwsem(&grp->list_mutex);
grp->open = NULL;
grp->close = NULL;
}
/* create a port, port number is returned (-1 on failure) */
client_port_t *snd_seq_create_port(client_t *client, int port)
{
unsigned long flags;
client_port_t *new_port;
struct list_head *l;
int num = -1;
/* sanity check */
snd_assert(client, return NULL);
if (client->num_ports >= SNDRV_SEQ_MAX_PORTS - 1) {
snd_printk(KERN_WARNING "too many ports for client %d\n", client->number);
return NULL;
}
/* create a new port */
new_port = kcalloc(1, sizeof(*new_port), GFP_KERNEL);
if (! new_port) {
snd_printd("malloc failed for registering client port\n");
return NULL; /* failure, out of memory */
}
/* init port data */
new_port->addr.client = client->number;
new_port->addr.port = -1;
new_port->owner = THIS_MODULE;
sprintf(new_port->name, "port-%d", num);
snd_use_lock_init(&new_port->use_lock);
port_subs_info_init(&new_port->c_src);
port_subs_info_init(&new_port->c_dest);
num = port >= 0 ? port : 0;
down(&client->ports_mutex);
write_lock_irqsave(&client->ports_lock, flags);
list_for_each(l, &client->ports_list_head) {
client_port_t *p = list_entry(l, client_port_t, list);
if (p->addr.port > num)
break;
if (port < 0) /* auto-probe mode */
num = p->addr.port + 1;
}
/* insert the new port */
list_add_tail(&new_port->list, l);
client->num_ports++;
new_port->addr.port = num; /* store the port number in the port */
write_unlock_irqrestore(&client->ports_lock, flags);
up(&client->ports_mutex);
sprintf(new_port->name, "port-%d", num);
return new_port;
}
/* */
enum group_type_t {
SRC_LIST, DEST_LIST
};
static int subscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp, snd_seq_port_subscribe_t *info, int send_ack);
static int unsubscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp, snd_seq_port_subscribe_t *info, int send_ack);
static client_port_t *get_client_port(snd_seq_addr_t *addr, client_t **cp)
{
client_port_t *p;
*cp = snd_seq_client_use_ptr(addr->client);
if (*cp) {
p = snd_seq_port_use_ptr(*cp, addr->port);
if (! p) {
snd_seq_client_unlock(*cp);
*cp = NULL;
}
return p;
}
return NULL;
}
/*
* remove all subscribers on the list
* this is called from port_delete, for each src and dest list.
*/
static void clear_subscriber_list(client_t *client, client_port_t *port,
port_subs_info_t *grp, int grptype)
{
struct list_head *p, *n;
down_write(&grp->list_mutex);
list_for_each_safe(p, n, &grp->list_head) {
subscribers_t *subs;
client_t *c;
client_port_t *aport;
if (grptype == SRC_LIST) {
subs = list_entry(p, subscribers_t, src_list);
aport = get_client_port(&subs->info.dest, &c);
} else {
subs = list_entry(p, subscribers_t, dest_list);
aport = get_client_port(&subs->info.sender, &c);
}
list_del(p);
unsubscribe_port(client, port, grp, &subs->info, 0);
if (!aport) {
/* looks like the connected port is being deleted.
* we decrease the counter, and when both ports are deleted
* remove the subscriber info
*/
if (atomic_dec_and_test(&subs->ref_count))
kfree(subs);
} else {
/* ok we got the connected port */
port_subs_info_t *agrp;
agrp = (grptype == SRC_LIST) ? &aport->c_dest : &aport->c_src;
down_write(&agrp->list_mutex);
if (grptype == SRC_LIST)
list_del(&subs->dest_list);
else
list_del(&subs->src_list);
unsubscribe_port(c, aport, agrp, &subs->info, 1);
kfree(subs);
up_write(&agrp->list_mutex);
snd_seq_port_unlock(aport);
snd_seq_client_unlock(c);
}
}
up_write(&grp->list_mutex);
}
/* delete port data */
static int port_delete(client_t *client, client_port_t *port)
{
/* set closing flag and wait for all port access are gone */
port->closing = 1;
snd_use_lock_sync(&port->use_lock);
/* clear subscribers info */
clear_subscriber_list(client, port, &port->c_src, SRC_LIST);
clear_subscriber_list(client, port, &port->c_dest, DEST_LIST);
if (port->private_free)
port->private_free(port->private_data);
snd_assert(port->c_src.count == 0,);
snd_assert(port->c_dest.count == 0,);
kfree(port);
return 0;
}
/* delete a port with the given port id */
int snd_seq_delete_port(client_t *client, int port)
{
unsigned long flags;
struct list_head *l;
client_port_t *found = NULL;
down(&client->ports_mutex);
write_lock_irqsave(&client->ports_lock, flags);
list_for_each(l, &client->ports_list_head) {
client_port_t *p = list_entry(l, client_port_t, list);
if (p->addr.port == port) {
/* ok found. delete from the list at first */
list_del(l);
client->num_ports--;
found = p;
break;
}
}
write_unlock_irqrestore(&client->ports_lock, flags);
up(&client->ports_mutex);
if (found)
return port_delete(client, found);
else
return -ENOENT;
}
/* delete the all ports belonging to the given client */
int snd_seq_delete_all_ports(client_t *client)
{
unsigned long flags;
struct list_head deleted_list, *p, *n;
/* move the port list to deleted_list, and
* clear the port list in the client data.
*/
down(&client->ports_mutex);
write_lock_irqsave(&client->ports_lock, flags);
if (! list_empty(&client->ports_list_head)) {
__list_add(&deleted_list,
client->ports_list_head.prev,
client->ports_list_head.next);
INIT_LIST_HEAD(&client->ports_list_head);
} else {
INIT_LIST_HEAD(&deleted_list);
}
client->num_ports = 0;
write_unlock_irqrestore(&client->ports_lock, flags);
/* remove each port in deleted_list */
list_for_each_safe(p, n, &deleted_list) {
client_port_t *port = list_entry(p, client_port_t, list);
list_del(p);
snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port);
port_delete(client, port);
}
up(&client->ports_mutex);
return 0;
}
/* set port info fields */
int snd_seq_set_port_info(client_port_t * port, snd_seq_port_info_t * info)
{
snd_assert(port && info, return -EINVAL);
/* set port name */
if (info->name[0])
strlcpy(port->name, info->name, sizeof(port->name));
/* set capabilities */
port->capability = info->capability;
/* get port type */
port->type = info->type;
/* information about supported channels/voices */
port->midi_channels = info->midi_channels;
port->midi_voices = info->midi_voices;
port->synth_voices = info->synth_voices;
/* timestamping */
port->timestamping = (info->flags & SNDRV_SEQ_PORT_FLG_TIMESTAMP) ? 1 : 0;
port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0;
port->time_queue = info->time_queue;
return 0;
}
/* get port info fields */
int snd_seq_get_port_info(client_port_t * port, snd_seq_port_info_t * info)
{
snd_assert(port && info, return -EINVAL);
/* get port name */
strlcpy(info->name, port->name, sizeof(info->name));
/* get capabilities */
info->capability = port->capability;
/* get port type */
info->type = port->type;
/* information about supported channels/voices */
info->midi_channels = port->midi_channels;
info->midi_voices = port->midi_voices;
info->synth_voices = port->synth_voices;
/* get subscriber counts */
info->read_use = port->c_src.count;
info->write_use = port->c_dest.count;
/* timestamping */
info->flags = 0;
if (port->timestamping) {
info->flags |= SNDRV_SEQ_PORT_FLG_TIMESTAMP;
if (port->time_real)
info->flags |= SNDRV_SEQ_PORT_FLG_TIME_REAL;
info->time_queue = port->time_queue;
}
return 0;
}
/*
* call callback functions (if any):
* the callbacks are invoked only when the first (for connection) or
* the last subscription (for disconnection) is done. Second or later
* subscription results in increment of counter, but no callback is
* invoked.
* This feature is useful if these callbacks are associated with
* initialization or termination of devices (see seq_midi.c).
*
* If callback_all option is set, the callback function is invoked
* at each connnection/disconnection.
*/
static int subscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp,
snd_seq_port_subscribe_t *info, int send_ack)
{
int err = 0;
if (!try_module_get(port->owner))
return -EFAULT;
grp->count++;
if (grp->open && (port->callback_all || grp->count == 1)) {
err = grp->open(port->private_data, info);
if (err < 0) {
module_put(port->owner);
grp->count--;
}
}
if (err >= 0 && send_ack && client->type == USER_CLIENT)
snd_seq_client_notify_subscription(port->addr.client, port->addr.port,
info, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED);
return err;
}
static int unsubscribe_port(client_t *client, client_port_t *port,
port_subs_info_t *grp,
snd_seq_port_subscribe_t *info, int send_ack)
{
int err = 0;
if (! grp->count)
return -EINVAL;
grp->count--;
if (grp->close && (port->callback_all || grp->count == 0))
err = grp->close(port->private_data, info);
if (send_ack && client->type == USER_CLIENT)
snd_seq_client_notify_subscription(port->addr.client, port->addr.port,
info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED);
module_put(port->owner);
return err;
}
/* check if both addresses are identical */
static inline int addr_match(snd_seq_addr_t *r, snd_seq_addr_t *s)
{
return (r->client == s->client) && (r->port == s->port);
}
/* check the two subscribe info match */
/* if flags is zero, checks only sender and destination addresses */
static int match_subs_info(snd_seq_port_subscribe_t *r,
snd_seq_port_subscribe_t *s)
{
if (addr_match(&r->sender, &s->sender) &&
addr_match(&r->dest, &s->dest)) {
if (r->flags && r->flags == s->flags)
return r->queue == s->queue;
else if (! r->flags)
return 1;
}
return 0;
}
/* connect two ports */
int snd_seq_port_connect(client_t *connector,
client_t *src_client, client_port_t *src_port,
client_t *dest_client, client_port_t *dest_port,
snd_seq_port_subscribe_t *info)
{
port_subs_info_t *src = &src_port->c_src;
port_subs_info_t *dest = &dest_port->c_dest;
subscribers_t *subs;
struct list_head *p;
int err, src_called = 0;
unsigned long flags;
int exclusive;
subs = kcalloc(1, sizeof(*subs), GFP_KERNEL);
if (! subs)
return -ENOMEM;
subs->info = *info;
atomic_set(&subs->ref_count, 2);
down_write(&src->list_mutex);
down_write(&dest->list_mutex);
exclusive = info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE ? 1 : 0;
err = -EBUSY;
if (exclusive) {
if (! list_empty(&src->list_head) || ! list_empty(&dest->list_head))
goto __error;
} else {
if (src->exclusive || dest->exclusive)
goto __error;
/* check whether already exists */
list_for_each(p, &src->list_head) {
subscribers_t *s = list_entry(p, subscribers_t, src_list);
if (match_subs_info(info, &s->info))
goto __error;
}
list_for_each(p, &dest->list_head) {
subscribers_t *s = list_entry(p, subscribers_t, dest_list);
if (match_subs_info(info, &s->info))
goto __error;
}
}
if ((err = subscribe_port(src_client, src_port, src, info,
connector->number != src_client->number)) < 0)
goto __error;
src_called = 1;
if ((err = subscribe_port(dest_client, dest_port, dest, info,
connector->number != dest_client->number)) < 0)
goto __error;
/* add to list */
write_lock_irqsave(&src->list_lock, flags);
// write_lock(&dest->list_lock); // no other lock yet
list_add_tail(&subs->src_list, &src->list_head);
list_add_tail(&subs->dest_list, &dest->list_head);
// write_unlock(&dest->list_lock); // no other lock yet
write_unlock_irqrestore(&src->list_lock, flags);
src->exclusive = dest->exclusive = exclusive;
up_write(&dest->list_mutex);
up_write(&src->list_mutex);
return 0;
__error:
if (src_called)
unsubscribe_port(src_client, src_port, src, info,
connector->number != src_client->number);
kfree(subs);
up_write(&dest->list_mutex);
up_write(&src->list_mutex);
return err;
}
/* remove the connection */
int snd_seq_port_disconnect(client_t *connector,
client_t *src_client, client_port_t *src_port,
client_t *dest_client, client_port_t *dest_port,
snd_seq_port_subscribe_t *info)
{
port_subs_info_t *src = &src_port->c_src;
port_subs_info_t *dest = &dest_port->c_dest;
subscribers_t *subs;
struct list_head *p;
int err = -ENOENT;
unsigned long flags;
down_write(&src->list_mutex);
down_write(&dest->list_mutex);
/* look for the connection */
list_for_each(p, &src->list_head) {
subs = list_entry(p, subscribers_t, src_list);
if (match_subs_info(info, &subs->info)) {
write_lock_irqsave(&src->list_lock, flags);
// write_lock(&dest->list_lock); // no lock yet
list_del(&subs->src_list);
list_del(&subs->dest_list);
// write_unlock(&dest->list_lock);
write_unlock_irqrestore(&src->list_lock, flags);
src->exclusive = dest->exclusive = 0;
unsubscribe_port(src_client, src_port, src, info,
connector->number != src_client->number);
unsubscribe_port(dest_client, dest_port, dest, info,
connector->number != dest_client->number);
kfree(subs);
err = 0;
break;
}
}
up_write(&dest->list_mutex);
up_write(&src->list_mutex);
return err;
}
/* get matched subscriber */
subscribers_t *snd_seq_port_get_subscription(port_subs_info_t *src_grp,
snd_seq_addr_t *dest_addr)
{
struct list_head *p;
subscribers_t *s, *found = NULL;
down_read(&src_grp->list_mutex);
list_for_each(p, &src_grp->list_head) {
s = list_entry(p, subscribers_t, src_list);
if (addr_match(dest_addr, &s->info.dest)) {
found = s;
break;
}
}
up_read(&src_grp->list_mutex);
return found;
}
/*
* Attach a device driver that wants to receive events from the
* sequencer. Returns the new port number on success.
* A driver that wants to receive the events converted to midi, will
* use snd_seq_midisynth_register_port().
*/
/* exported */
int snd_seq_event_port_attach(int client,
snd_seq_port_callback_t *pcbp,
int cap, int type, int midi_channels,
int midi_voices, char *portname)
{
snd_seq_port_info_t portinfo;
int ret;
/* Set up the port */
memset(&portinfo, 0, sizeof(portinfo));
portinfo.addr.client = client;
strlcpy(portinfo.name, portname ? portname : "Unamed port",
sizeof(portinfo.name));
portinfo.capability = cap;
portinfo.type = type;
portinfo.kernel = pcbp;
portinfo.midi_channels = midi_channels;
portinfo.midi_voices = midi_voices;
/* Create it */
ret = snd_seq_kernel_client_ctl(client,
SNDRV_SEQ_IOCTL_CREATE_PORT,
&portinfo);
if (ret >= 0)
ret = portinfo.addr.port;
return ret;
}
/*
* Detach the driver from a port.
*/
/* exported */
int snd_seq_event_port_detach(int client, int port)
{
snd_seq_port_info_t portinfo;
int err;
memset(&portinfo, 0, sizeof(portinfo));
portinfo.addr.client = client;
portinfo.addr.port = port;
err = snd_seq_kernel_client_ctl(client,
SNDRV_SEQ_IOCTL_DELETE_PORT,
&portinfo);
return err;
}

128
sound/core/seq/seq_ports.h Normal file
View File

@@ -0,0 +1,128 @@
/*
* ALSA sequencer Ports
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef __SND_SEQ_PORTS_H
#define __SND_SEQ_PORTS_H
#include <sound/seq_kernel.h>
#include "seq_lock.h"
/* list of 'exported' ports */
/* Client ports that are not exported are still accessible, but are
anonymous ports.
If a port supports SUBSCRIPTION, that port can send events to all
subscribersto a special address, with address
(queue==SNDRV_SEQ_ADDRESS_SUBSCRIBERS). The message is then send to all
recipients that are registered in the subscription list. A typical
application for these SUBSCRIPTION events is handling of incoming MIDI
data. The port doesn't 'know' what other clients are interested in this
message. If for instance a MIDI recording application would like to receive
the events from that port, it will first have to subscribe with that port.
*/
typedef struct subscribers_t {
snd_seq_port_subscribe_t info; /* additional info */
struct list_head src_list; /* link of sources */
struct list_head dest_list; /* link of destinations */
atomic_t ref_count;
} subscribers_t;
typedef struct port_subs_info_t {
struct list_head list_head; /* list of subscribed ports */
unsigned int count; /* count of subscribers */
unsigned int exclusive: 1; /* exclusive mode */
struct rw_semaphore list_mutex;
rwlock_t list_lock;
snd_seq_kernel_port_open_t *open;
snd_seq_kernel_port_close_t *close;
} port_subs_info_t;
typedef struct client_port_t {
snd_seq_addr_t addr; /* client/port number */
struct module *owner; /* owner of this port */
char name[64]; /* port name */
struct list_head list; /* port list */
snd_use_lock_t use_lock;
/* subscribers */
port_subs_info_t c_src; /* read (sender) list */
port_subs_info_t c_dest; /* write (dest) list */
snd_seq_kernel_port_input_t *event_input;
snd_seq_kernel_port_private_free_t *private_free;
void *private_data;
unsigned int callback_all : 1;
unsigned int closing : 1;
unsigned int timestamping: 1;
unsigned int time_real: 1;
int time_queue;
/* capability, inport, output, sync */
unsigned int capability; /* port capability bits */
unsigned int type; /* port type bits */
/* supported channels */
int midi_channels;
int midi_voices;
int synth_voices;
} client_port_t;
/* return pointer to port structure and lock port */
client_port_t *snd_seq_port_use_ptr(client_t *client, int num);
/* search for next port - port is locked if found */
client_port_t *snd_seq_port_query_nearest(client_t *client, snd_seq_port_info_t *pinfo);
/* unlock the port */
#define snd_seq_port_unlock(port) snd_use_lock_free(&(port)->use_lock)
/* create a port, port number is returned (-1 on failure) */
client_port_t *snd_seq_create_port(client_t *client, int port_index);
/* delete a port */
int snd_seq_delete_port(client_t *client, int port);
/* delete all ports */
int snd_seq_delete_all_ports(client_t *client);
/* set port info fields */
int snd_seq_set_port_info(client_port_t *port, snd_seq_port_info_t *info);
/* get port info fields */
int snd_seq_get_port_info(client_port_t *port, snd_seq_port_info_t *info);
/* add subscriber to subscription list */
int snd_seq_port_connect(client_t *caller, client_t *s, client_port_t *sp, client_t *d, client_port_t *dp, snd_seq_port_subscribe_t *info);
/* remove subscriber from subscription list */
int snd_seq_port_disconnect(client_t *caller, client_t *s, client_port_t *sp, client_t *d, client_port_t *dp, snd_seq_port_subscribe_t *info);
/* subscribe port */
int snd_seq_port_subscribe(client_port_t *port, snd_seq_port_subscribe_t *info);
/* get matched subscriber */
subscribers_t *snd_seq_port_get_subscription(port_subs_info_t *src_grp, snd_seq_addr_t *dest_addr);
#endif

449
sound/core/seq/seq_prioq.c Normal file
View File

@@ -0,0 +1,449 @@
/*
* ALSA sequencer Priority Queue
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/time.h>
#include <linux/slab.h>
#include <sound/core.h>
#include "seq_timer.h"
#include "seq_prioq.h"
/* Implementation is a simple linked list for now...
This priority queue orders the events on timestamp. For events with an
equeal timestamp the queue behaves as a FIFO.
*
* +-------+
* Head --> | first |
* +-------+
* |next
* +-----v-+
* | |
* +-------+
* |
* +-----v-+
* | |
* +-------+
* |
* +-----v-+
* Tail --> | last |
* +-------+
*
*/
/* create new prioq (constructor) */
prioq_t *snd_seq_prioq_new(void)
{
prioq_t *f;
f = kcalloc(1, sizeof(*f), GFP_KERNEL);
if (f == NULL) {
snd_printd("oops: malloc failed for snd_seq_prioq_new()\n");
return NULL;
}
spin_lock_init(&f->lock);
f->head = NULL;
f->tail = NULL;
f->cells = 0;
return f;
}
/* delete prioq (destructor) */
void snd_seq_prioq_delete(prioq_t **fifo)
{
prioq_t *f = *fifo;
*fifo = NULL;
if (f == NULL) {
snd_printd("oops: snd_seq_prioq_delete() called with NULL prioq\n");
return;
}
/* release resources...*/
/*....................*/
if (f->cells > 0) {
/* drain prioQ */
while (f->cells > 0)
snd_seq_cell_free(snd_seq_prioq_cell_out(f));
}
kfree(f);
}
/* compare timestamp between events */
/* return 1 if a >= b; 0 */
static inline int compare_timestamp(snd_seq_event_t * a, snd_seq_event_t * b)
{
if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) {
/* compare ticks */
return (snd_seq_compare_tick_time(&a->time.tick, &b->time.tick));
} else {
/* compare real time */
return (snd_seq_compare_real_time(&a->time.time, &b->time.time));
}
}
/* compare timestamp between events */
/* return negative if a < b;
* zero if a = b;
* positive if a > b;
*/
static inline int compare_timestamp_rel(snd_seq_event_t *a, snd_seq_event_t *b)
{
if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) {
/* compare ticks */
if (a->time.tick > b->time.tick)
return 1;
else if (a->time.tick == b->time.tick)
return 0;
else
return -1;
} else {
/* compare real time */
if (a->time.time.tv_sec > b->time.time.tv_sec)
return 1;
else if (a->time.time.tv_sec == b->time.time.tv_sec) {
if (a->time.time.tv_nsec > b->time.time.tv_nsec)
return 1;
else if (a->time.time.tv_nsec == b->time.time.tv_nsec)
return 0;
else
return -1;
} else
return -1;
}
}
/* enqueue cell to prioq */
int snd_seq_prioq_cell_in(prioq_t * f, snd_seq_event_cell_t * cell)
{
snd_seq_event_cell_t *cur, *prev;
unsigned long flags;
int count;
int prior;
snd_assert(f, return -EINVAL);
snd_assert(cell, return -EINVAL);
/* check flags */
prior = (cell->event.flags & SNDRV_SEQ_PRIORITY_MASK);
spin_lock_irqsave(&f->lock, flags);
/* check if this element needs to inserted at the end (ie. ordered
data is inserted) This will be very likeley if a sequencer
application or midi file player is feeding us (sequential) data */
if (f->tail && !prior) {
if (compare_timestamp(&cell->event, &f->tail->event)) {
/* add new cell to tail of the fifo */
f->tail->next = cell;
f->tail = cell;
cell->next = NULL;
f->cells++;
spin_unlock_irqrestore(&f->lock, flags);
return 0;
}
}
/* traverse list of elements to find the place where the new cell is
to be inserted... Note that this is a order n process ! */
prev = NULL; /* previous cell */
cur = f->head; /* cursor */
count = 10000; /* FIXME: enough big, isn't it? */
while (cur != NULL) {
/* compare timestamps */
int rel = compare_timestamp_rel(&cell->event, &cur->event);
if (rel < 0)
/* new cell has earlier schedule time, */
break;
else if (rel == 0 && prior)
/* equal schedule time and prior to others */
break;
/* new cell has equal or larger schedule time, */
/* move cursor to next cell */
prev = cur;
cur = cur->next;
if (! --count) {
spin_unlock_irqrestore(&f->lock, flags);
snd_printk(KERN_ERR "cannot find a pointer.. infinite loop?\n");
return -EINVAL;
}
}
/* insert it before cursor */
if (prev != NULL)
prev->next = cell;
cell->next = cur;
if (f->head == cur) /* this is the first cell, set head to it */
f->head = cell;
if (cur == NULL) /* reached end of the list */
f->tail = cell;
f->cells++;
spin_unlock_irqrestore(&f->lock, flags);
return 0;
}
/* dequeue cell from prioq */
snd_seq_event_cell_t *snd_seq_prioq_cell_out(prioq_t * f)
{
snd_seq_event_cell_t *cell;
unsigned long flags;
if (f == NULL) {
snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n");
return NULL;
}
spin_lock_irqsave(&f->lock, flags);
cell = f->head;
if (cell) {
f->head = cell->next;
/* reset tail if this was the last element */
if (f->tail == cell)
f->tail = NULL;
cell->next = NULL;
f->cells--;
}
spin_unlock_irqrestore(&f->lock, flags);
return cell;
}
/* return number of events available in prioq */
int snd_seq_prioq_avail(prioq_t * f)
{
if (f == NULL) {
snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n");
return 0;
}
return f->cells;
}
/* peek at cell at the head of the prioq */
snd_seq_event_cell_t *snd_seq_prioq_cell_peek(prioq_t * f)
{
if (f == NULL) {
snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n");
return NULL;
}
return f->head;
}
static inline int prioq_match(snd_seq_event_cell_t *cell, int client, int timestamp)
{
if (cell->event.source.client == client ||
cell->event.dest.client == client)
return 1;
if (!timestamp)
return 0;
switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
case SNDRV_SEQ_TIME_STAMP_TICK:
if (cell->event.time.tick)
return 1;
break;
case SNDRV_SEQ_TIME_STAMP_REAL:
if (cell->event.time.time.tv_sec ||
cell->event.time.time.tv_nsec)
return 1;
break;
}
return 0;
}
/* remove cells for left client */
void snd_seq_prioq_leave(prioq_t * f, int client, int timestamp)
{
register snd_seq_event_cell_t *cell, *next;
unsigned long flags;
snd_seq_event_cell_t *prev = NULL;
snd_seq_event_cell_t *freefirst = NULL, *freeprev = NULL, *freenext;
/* collect all removed cells */
spin_lock_irqsave(&f->lock, flags);
cell = f->head;
while (cell) {
next = cell->next;
if (prioq_match(cell, client, timestamp)) {
/* remove cell from prioq */
if (cell == f->head) {
f->head = cell->next;
} else {
prev->next = cell->next;
}
if (cell == f->tail)
f->tail = cell->next;
f->cells--;
/* add cell to free list */
cell->next = NULL;
if (freefirst == NULL) {
freefirst = cell;
} else {
freeprev->next = cell;
}
freeprev = cell;
} else {
#if 0
printk("type = %i, source = %i, dest = %i, client = %i\n",
cell->event.type,
cell->event.source.client,
cell->event.dest.client,
client);
#endif
prev = cell;
}
cell = next;
}
spin_unlock_irqrestore(&f->lock, flags);
/* remove selected cells */
while (freefirst) {
freenext = freefirst->next;
snd_seq_cell_free(freefirst);
freefirst = freenext;
}
}
static int prioq_remove_match(snd_seq_remove_events_t *info,
snd_seq_event_t *ev)
{
int res;
if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) {
if (ev->dest.client != info->dest.client ||
ev->dest.port != info->dest.port)
return 0;
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST_CHANNEL) {
if (! snd_seq_ev_is_channel_type(ev))
return 0;
/* data.note.channel and data.control.channel are identical */
if (ev->data.note.channel != info->channel)
return 0;
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_AFTER) {
if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK)
res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick);
else
res = snd_seq_compare_real_time(&ev->time.time, &info->time.time);
if (!res)
return 0;
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_BEFORE) {
if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK)
res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick);
else
res = snd_seq_compare_real_time(&ev->time.time, &info->time.time);
if (res)
return 0;
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_EVENT_TYPE) {
if (ev->type != info->type)
return 0;
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_IGNORE_OFF) {
/* Do not remove off events */
switch (ev->type) {
case SNDRV_SEQ_EVENT_NOTEOFF:
/* case SNDRV_SEQ_EVENT_SAMPLE_STOP: */
return 0;
default:
break;
}
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_TAG_MATCH) {
if (info->tag != ev->tag)
return 0;
}
return 1;
}
/* remove cells matching remove criteria */
void snd_seq_prioq_remove_events(prioq_t * f, int client,
snd_seq_remove_events_t *info)
{
register snd_seq_event_cell_t *cell, *next;
unsigned long flags;
snd_seq_event_cell_t *prev = NULL;
snd_seq_event_cell_t *freefirst = NULL, *freeprev = NULL, *freenext;
/* collect all removed cells */
spin_lock_irqsave(&f->lock, flags);
cell = f->head;
while (cell) {
next = cell->next;
if (cell->event.source.client == client &&
prioq_remove_match(info, &cell->event)) {
/* remove cell from prioq */
if (cell == f->head) {
f->head = cell->next;
} else {
prev->next = cell->next;
}
if (cell == f->tail)
f->tail = cell->next;
f->cells--;
/* add cell to free list */
cell->next = NULL;
if (freefirst == NULL) {
freefirst = cell;
} else {
freeprev->next = cell;
}
freeprev = cell;
} else {
prev = cell;
}
cell = next;
}
spin_unlock_irqrestore(&f->lock, flags);
/* remove selected cells */
while (freefirst) {
freenext = freefirst->next;
snd_seq_cell_free(freefirst);
freefirst = freenext;
}
}

View File

@@ -0,0 +1,62 @@
/*
* ALSA sequencer Priority Queue
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef __SND_SEQ_PRIOQ_H
#define __SND_SEQ_PRIOQ_H
#include "seq_memory.h"
/* === PRIOQ === */
typedef struct {
snd_seq_event_cell_t* head; /* pointer to head of prioq */
snd_seq_event_cell_t* tail; /* pointer to tail of prioq */
int cells;
spinlock_t lock;
} prioq_t;
/* create new prioq (constructor) */
extern prioq_t *snd_seq_prioq_new(void);
/* delete prioq (destructor) */
extern void snd_seq_prioq_delete(prioq_t **fifo);
/* enqueue cell to prioq */
extern int snd_seq_prioq_cell_in(prioq_t *f, snd_seq_event_cell_t *cell);
/* dequeue cell from prioq */
extern snd_seq_event_cell_t *snd_seq_prioq_cell_out(prioq_t *f);
/* return number of events available in prioq */
extern int snd_seq_prioq_avail(prioq_t *f);
/* peek at cell at the head of the prioq */
extern snd_seq_event_cell_t *snd_seq_prioq_cell_peek(prioq_t *f);
/* client left queue */
extern void snd_seq_prioq_leave(prioq_t *f, int client, int timestamp);
/* Remove events */
void snd_seq_prioq_remove_events(prioq_t * f, int client,
snd_seq_remove_events_t *info);
#endif

783
sound/core/seq/seq_queue.c Normal file
View File

@@ -0,0 +1,783 @@
/*
* ALSA sequencer Timing queue handling
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* MAJOR CHANGES
* Nov. 13, 1999 Takashi Iwai <iwai@ww.uni-erlangen.de>
* - Queues are allocated dynamically via ioctl.
* - When owner client is deleted, all owned queues are deleted, too.
* - Owner of unlocked queue is kept unmodified even if it is
* manipulated by other clients.
* - Owner field in SET_QUEUE_OWNER ioctl must be identical with the
* caller client. i.e. Changing owner to a third client is not
* allowed.
*
* Aug. 30, 2000 Takashi Iwai
* - Queues are managed in static array again, but with better way.
* The API itself is identical.
* - The queue is locked when queue_t pinter is returned via
* queueptr(). This pointer *MUST* be released afterward by
* queuefree(ptr).
* - Addition of experimental sync support.
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <sound/core.h>
#include "seq_memory.h"
#include "seq_queue.h"
#include "seq_clientmgr.h"
#include "seq_fifo.h"
#include "seq_timer.h"
#include "seq_info.h"
/* list of allocated queues */
static queue_t *queue_list[SNDRV_SEQ_MAX_QUEUES];
static DEFINE_SPINLOCK(queue_list_lock);
/* number of queues allocated */
static int num_queues;
int snd_seq_queue_get_cur_queues(void)
{
return num_queues;
}
/*----------------------------------------------------------------*/
/* assign queue id and insert to list */
static int queue_list_add(queue_t *q)
{
int i;
unsigned long flags;
spin_lock_irqsave(&queue_list_lock, flags);
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
if (! queue_list[i]) {
queue_list[i] = q;
q->queue = i;
num_queues++;
spin_unlock_irqrestore(&queue_list_lock, flags);
return i;
}
}
spin_unlock_irqrestore(&queue_list_lock, flags);
return -1;
}
static queue_t *queue_list_remove(int id, int client)
{
queue_t *q;
unsigned long flags;
spin_lock_irqsave(&queue_list_lock, flags);
q = queue_list[id];
if (q) {
spin_lock(&q->owner_lock);
if (q->owner == client) {
/* found */
q->klocked = 1;
spin_unlock(&q->owner_lock);
queue_list[id] = NULL;
num_queues--;
spin_unlock_irqrestore(&queue_list_lock, flags);
return q;
}
spin_unlock(&q->owner_lock);
}
spin_unlock_irqrestore(&queue_list_lock, flags);
return NULL;
}
/*----------------------------------------------------------------*/
/* create new queue (constructor) */
static queue_t *queue_new(int owner, int locked)
{
queue_t *q;
q = kcalloc(1, sizeof(*q), GFP_KERNEL);
if (q == NULL) {
snd_printd("malloc failed for snd_seq_queue_new()\n");
return NULL;
}
spin_lock_init(&q->owner_lock);
spin_lock_init(&q->check_lock);
init_MUTEX(&q->timer_mutex);
snd_use_lock_init(&q->use_lock);
q->queue = -1;
q->tickq = snd_seq_prioq_new();
q->timeq = snd_seq_prioq_new();
q->timer = snd_seq_timer_new();
if (q->tickq == NULL || q->timeq == NULL || q->timer == NULL) {
snd_seq_prioq_delete(&q->tickq);
snd_seq_prioq_delete(&q->timeq);
snd_seq_timer_delete(&q->timer);
kfree(q);
return NULL;
}
q->owner = owner;
q->locked = locked;
q->klocked = 0;
return q;
}
/* delete queue (destructor) */
static void queue_delete(queue_t *q)
{
/* stop and release the timer */
snd_seq_timer_stop(q->timer);
snd_seq_timer_close(q);
/* wait until access free */
snd_use_lock_sync(&q->use_lock);
/* release resources... */
snd_seq_prioq_delete(&q->tickq);
snd_seq_prioq_delete(&q->timeq);
snd_seq_timer_delete(&q->timer);
kfree(q);
}
/*----------------------------------------------------------------*/
/* setup queues */
int __init snd_seq_queues_init(void)
{
/*
memset(queue_list, 0, sizeof(queue_list));
num_queues = 0;
*/
return 0;
}
/* delete all existing queues */
void __exit snd_seq_queues_delete(void)
{
int i;
/* clear list */
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
if (queue_list[i])
queue_delete(queue_list[i]);
}
}
/* allocate a new queue -
* return queue index value or negative value for error
*/
int snd_seq_queue_alloc(int client, int locked, unsigned int info_flags)
{
queue_t *q;
q = queue_new(client, locked);
if (q == NULL)
return -ENOMEM;
q->info_flags = info_flags;
if (queue_list_add(q) < 0) {
queue_delete(q);
return -ENOMEM;
}
snd_seq_queue_use(q->queue, client, 1); /* use this queue */
return q->queue;
}
/* delete a queue - queue must be owned by the client */
int snd_seq_queue_delete(int client, int queueid)
{
queue_t *q;
if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)
return -EINVAL;
q = queue_list_remove(queueid, client);
if (q == NULL)
return -EINVAL;
queue_delete(q);
return 0;
}
/* return pointer to queue structure for specified id */
queue_t *queueptr(int queueid)
{
queue_t *q;
unsigned long flags;
if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)
return NULL;
spin_lock_irqsave(&queue_list_lock, flags);
q = queue_list[queueid];
if (q)
snd_use_lock_use(&q->use_lock);
spin_unlock_irqrestore(&queue_list_lock, flags);
return q;
}
/* return the (first) queue matching with the specified name */
queue_t *snd_seq_queue_find_name(char *name)
{
int i;
queue_t *q;
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
if ((q = queueptr(i)) != NULL) {
if (strncmp(q->name, name, sizeof(q->name)) == 0)
return q;
queuefree(q);
}
}
return NULL;
}
/* -------------------------------------------------------- */
void snd_seq_check_queue(queue_t *q, int atomic, int hop)
{
unsigned long flags;
snd_seq_event_cell_t *cell;
if (q == NULL)
return;
/* make this function non-reentrant */
spin_lock_irqsave(&q->check_lock, flags);
if (q->check_blocked) {
q->check_again = 1;
spin_unlock_irqrestore(&q->check_lock, flags);
return; /* other thread is already checking queues */
}
q->check_blocked = 1;
spin_unlock_irqrestore(&q->check_lock, flags);
__again:
/* Process tick queue... */
while ((cell = snd_seq_prioq_cell_peek(q->tickq)) != NULL) {
if (snd_seq_compare_tick_time(&q->timer->tick.cur_tick, &cell->event.time.tick)) {
cell = snd_seq_prioq_cell_out(q->tickq);
if (cell)
snd_seq_dispatch_event(cell, atomic, hop);
} else {
/* event remains in the queue */
break;
}
}
/* Process time queue... */
while ((cell = snd_seq_prioq_cell_peek(q->timeq)) != NULL) {
if (snd_seq_compare_real_time(&q->timer->cur_time, &cell->event.time.time)) {
cell = snd_seq_prioq_cell_out(q->timeq);
if (cell)
snd_seq_dispatch_event(cell, atomic, hop);
} else {
/* event remains in the queue */
break;
}
}
/* free lock */
spin_lock_irqsave(&q->check_lock, flags);
if (q->check_again) {
q->check_again = 0;
spin_unlock_irqrestore(&q->check_lock, flags);
goto __again;
}
q->check_blocked = 0;
spin_unlock_irqrestore(&q->check_lock, flags);
}
/* enqueue a event to singe queue */
int snd_seq_enqueue_event(snd_seq_event_cell_t *cell, int atomic, int hop)
{
int dest, err;
queue_t *q;
snd_assert(cell != NULL, return -EINVAL);
dest = cell->event.queue; /* destination queue */
q = queueptr(dest);
if (q == NULL)
return -EINVAL;
/* handle relative time stamps, convert them into absolute */
if ((cell->event.flags & SNDRV_SEQ_TIME_MODE_MASK) == SNDRV_SEQ_TIME_MODE_REL) {
switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
case SNDRV_SEQ_TIME_STAMP_TICK:
cell->event.time.tick += q->timer->tick.cur_tick;
break;
case SNDRV_SEQ_TIME_STAMP_REAL:
snd_seq_inc_real_time(&cell->event.time.time, &q->timer->cur_time);
break;
}
cell->event.flags &= ~SNDRV_SEQ_TIME_MODE_MASK;
cell->event.flags |= SNDRV_SEQ_TIME_MODE_ABS;
}
/* enqueue event in the real-time or midi queue */
switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
case SNDRV_SEQ_TIME_STAMP_TICK:
err = snd_seq_prioq_cell_in(q->tickq, cell);
break;
case SNDRV_SEQ_TIME_STAMP_REAL:
default:
err = snd_seq_prioq_cell_in(q->timeq, cell);
break;
}
if (err < 0) {
queuefree(q); /* unlock */
return err;
}
/* trigger dispatching */
snd_seq_check_queue(q, atomic, hop);
queuefree(q); /* unlock */
return 0;
}
/*----------------------------------------------------------------*/
static inline int check_access(queue_t *q, int client)
{
return (q->owner == client) || (!q->locked && !q->klocked);
}
/* check if the client has permission to modify queue parameters.
* if it does, lock the queue
*/
static int queue_access_lock(queue_t *q, int client)
{
unsigned long flags;
int access_ok;
spin_lock_irqsave(&q->owner_lock, flags);
access_ok = check_access(q, client);
if (access_ok)
q->klocked = 1;
spin_unlock_irqrestore(&q->owner_lock, flags);
return access_ok;
}
/* unlock the queue */
static inline void queue_access_unlock(queue_t *q)
{
unsigned long flags;
spin_lock_irqsave(&q->owner_lock, flags);
q->klocked = 0;
spin_unlock_irqrestore(&q->owner_lock, flags);
}
/* exported - only checking permission */
int snd_seq_queue_check_access(int queueid, int client)
{
queue_t *q = queueptr(queueid);
int access_ok;
unsigned long flags;
if (! q)
return 0;
spin_lock_irqsave(&q->owner_lock, flags);
access_ok = check_access(q, client);
spin_unlock_irqrestore(&q->owner_lock, flags);
queuefree(q);
return access_ok;
}
/*----------------------------------------------------------------*/
/*
* change queue's owner and permission
*/
int snd_seq_queue_set_owner(int queueid, int client, int locked)
{
queue_t *q = queueptr(queueid);
if (q == NULL)
return -EINVAL;
if (! queue_access_lock(q, client)) {
queuefree(q);
return -EPERM;
}
q->locked = locked ? 1 : 0;
q->owner = client;
queue_access_unlock(q);
queuefree(q);
return 0;
}
/*----------------------------------------------------------------*/
/* open timer -
* q->use mutex should be down before calling this function to avoid
* confliction with snd_seq_queue_use()
*/
int snd_seq_queue_timer_open(int queueid)
{
int result = 0;
queue_t *queue;
seq_timer_t *tmr;
queue = queueptr(queueid);
if (queue == NULL)
return -EINVAL;
tmr = queue->timer;
if ((result = snd_seq_timer_open(queue)) < 0) {
snd_seq_timer_defaults(tmr);
result = snd_seq_timer_open(queue);
}
queuefree(queue);
return result;
}
/* close timer -
* q->use mutex should be down before calling this function
*/
int snd_seq_queue_timer_close(int queueid)
{
queue_t *queue;
seq_timer_t *tmr;
int result = 0;
queue = queueptr(queueid);
if (queue == NULL)
return -EINVAL;
tmr = queue->timer;
snd_seq_timer_close(queue);
queuefree(queue);
return result;
}
/* change queue tempo and ppq */
int snd_seq_queue_timer_set_tempo(int queueid, int client, snd_seq_queue_tempo_t *info)
{
queue_t *q = queueptr(queueid);
int result;
if (q == NULL)
return -EINVAL;
if (! queue_access_lock(q, client)) {
queuefree(q);
return -EPERM;
}
result = snd_seq_timer_set_tempo(q->timer, info->tempo);
if (result >= 0)
result = snd_seq_timer_set_ppq(q->timer, info->ppq);
if (result >= 0 && info->skew_base > 0)
result = snd_seq_timer_set_skew(q->timer, info->skew_value, info->skew_base);
queue_access_unlock(q);
queuefree(q);
return result;
}
/* use or unuse this queue -
* if it is the first client, starts the timer.
* if it is not longer used by any clients, stop the timer.
*/
int snd_seq_queue_use(int queueid, int client, int use)
{
queue_t *queue;
queue = queueptr(queueid);
if (queue == NULL)
return -EINVAL;
down(&queue->timer_mutex);
if (use) {
if (!test_and_set_bit(client, queue->clients_bitmap))
queue->clients++;
} else {
if (test_and_clear_bit(client, queue->clients_bitmap))
queue->clients--;
}
if (queue->clients) {
if (use && queue->clients == 1)
snd_seq_timer_defaults(queue->timer);
snd_seq_timer_open(queue);
} else {
snd_seq_timer_close(queue);
}
up(&queue->timer_mutex);
queuefree(queue);
return 0;
}
/*
* check if queue is used by the client
* return negative value if the queue is invalid.
* return 0 if not used, 1 if used.
*/
int snd_seq_queue_is_used(int queueid, int client)
{
queue_t *q;
int result;
q = queueptr(queueid);
if (q == NULL)
return -EINVAL; /* invalid queue */
result = test_bit(client, q->clients_bitmap) ? 1 : 0;
queuefree(q);
return result;
}
/*----------------------------------------------------------------*/
/* notification that client has left the system -
* stop the timer on all queues owned by this client
*/
void snd_seq_queue_client_termination(int client)
{
unsigned long flags;
int i;
queue_t *q;
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
if ((q = queueptr(i)) == NULL)
continue;
spin_lock_irqsave(&q->owner_lock, flags);
if (q->owner == client)
q->klocked = 1;
spin_unlock_irqrestore(&q->owner_lock, flags);
if (q->owner == client) {
if (q->timer->running)
snd_seq_timer_stop(q->timer);
snd_seq_timer_reset(q->timer);
}
queuefree(q);
}
}
/* final stage notification -
* remove cells for no longer exist client (for non-owned queue)
* or delete this queue (for owned queue)
*/
void snd_seq_queue_client_leave(int client)
{
int i;
queue_t *q;
/* delete own queues from queue list */
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
if ((q = queue_list_remove(i, client)) != NULL)
queue_delete(q);
}
/* remove cells from existing queues -
* they are not owned by this client
*/
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
if ((q = queueptr(i)) == NULL)
continue;
if (test_bit(client, q->clients_bitmap)) {
snd_seq_prioq_leave(q->tickq, client, 0);
snd_seq_prioq_leave(q->timeq, client, 0);
snd_seq_queue_use(q->queue, client, 0);
}
queuefree(q);
}
}
/*----------------------------------------------------------------*/
/* remove cells from all queues */
void snd_seq_queue_client_leave_cells(int client)
{
int i;
queue_t *q;
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
if ((q = queueptr(i)) == NULL)
continue;
snd_seq_prioq_leave(q->tickq, client, 0);
snd_seq_prioq_leave(q->timeq, client, 0);
queuefree(q);
}
}
/* remove cells based on flush criteria */
void snd_seq_queue_remove_cells(int client, snd_seq_remove_events_t *info)
{
int i;
queue_t *q;
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
if ((q = queueptr(i)) == NULL)
continue;
if (test_bit(client, q->clients_bitmap) &&
(! (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) ||
q->queue == info->queue)) {
snd_seq_prioq_remove_events(q->tickq, client, info);
snd_seq_prioq_remove_events(q->timeq, client, info);
}
queuefree(q);
}
}
/*----------------------------------------------------------------*/
/*
* send events to all subscribed ports
*/
static void queue_broadcast_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop)
{
snd_seq_event_t sev;
sev = *ev;
sev.flags = SNDRV_SEQ_TIME_STAMP_TICK|SNDRV_SEQ_TIME_MODE_ABS;
sev.time.tick = q->timer->tick.cur_tick;
sev.queue = q->queue;
sev.data.queue.queue = q->queue;
/* broadcast events from Timer port */
sev.source.client = SNDRV_SEQ_CLIENT_SYSTEM;
sev.source.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
sev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
snd_seq_kernel_client_dispatch(SNDRV_SEQ_CLIENT_SYSTEM, &sev, atomic, hop);
}
/*
* process a received queue-control event.
* this function is exported for seq_sync.c.
*/
void snd_seq_queue_process_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop)
{
switch (ev->type) {
case SNDRV_SEQ_EVENT_START:
snd_seq_prioq_leave(q->tickq, ev->source.client, 1);
snd_seq_prioq_leave(q->timeq, ev->source.client, 1);
if (! snd_seq_timer_start(q->timer))
queue_broadcast_event(q, ev, atomic, hop);
break;
case SNDRV_SEQ_EVENT_CONTINUE:
if (! snd_seq_timer_continue(q->timer))
queue_broadcast_event(q, ev, atomic, hop);
break;
case SNDRV_SEQ_EVENT_STOP:
snd_seq_timer_stop(q->timer);
queue_broadcast_event(q, ev, atomic, hop);
break;
case SNDRV_SEQ_EVENT_TEMPO:
snd_seq_timer_set_tempo(q->timer, ev->data.queue.param.value);
queue_broadcast_event(q, ev, atomic, hop);
break;
case SNDRV_SEQ_EVENT_SETPOS_TICK:
if (snd_seq_timer_set_position_tick(q->timer, ev->data.queue.param.time.tick) == 0) {
queue_broadcast_event(q, ev, atomic, hop);
}
break;
case SNDRV_SEQ_EVENT_SETPOS_TIME:
if (snd_seq_timer_set_position_time(q->timer, ev->data.queue.param.time.time) == 0) {
queue_broadcast_event(q, ev, atomic, hop);
}
break;
case SNDRV_SEQ_EVENT_QUEUE_SKEW:
if (snd_seq_timer_set_skew(q->timer,
ev->data.queue.param.skew.value,
ev->data.queue.param.skew.base) == 0) {
queue_broadcast_event(q, ev, atomic, hop);
}
break;
}
}
/*
* Queue control via timer control port:
* this function is exported as a callback of timer port.
*/
int snd_seq_control_queue(snd_seq_event_t *ev, int atomic, int hop)
{
queue_t *q;
snd_assert(ev != NULL, return -EINVAL);
q = queueptr(ev->data.queue.queue);
if (q == NULL)
return -EINVAL;
if (! queue_access_lock(q, ev->source.client)) {
queuefree(q);
return -EPERM;
}
snd_seq_queue_process_event(q, ev, atomic, hop);
queue_access_unlock(q);
queuefree(q);
return 0;
}
/*----------------------------------------------------------------*/
/* exported to seq_info.c */
void snd_seq_info_queues_read(snd_info_entry_t *entry,
snd_info_buffer_t * buffer)
{
int i, bpm;
queue_t *q;
seq_timer_t *tmr;
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
if ((q = queueptr(i)) == NULL)
continue;
tmr = q->timer;
if (tmr->tempo)
bpm = 60000000 / tmr->tempo;
else
bpm = 0;
snd_iprintf(buffer, "queue %d: [%s]\n", q->queue, q->name);
snd_iprintf(buffer, "owned by client : %d\n", q->owner);
snd_iprintf(buffer, "lock status : %s\n", q->locked ? "Locked" : "Free");
snd_iprintf(buffer, "queued time events : %d\n", snd_seq_prioq_avail(q->timeq));
snd_iprintf(buffer, "queued tick events : %d\n", snd_seq_prioq_avail(q->tickq));
snd_iprintf(buffer, "timer state : %s\n", tmr->running ? "Running" : "Stopped");
snd_iprintf(buffer, "timer PPQ : %d\n", tmr->ppq);
snd_iprintf(buffer, "current tempo : %d\n", tmr->tempo);
snd_iprintf(buffer, "current BPM : %d\n", bpm);
snd_iprintf(buffer, "current time : %d.%09d s\n", tmr->cur_time.tv_sec, tmr->cur_time.tv_nsec);
snd_iprintf(buffer, "current tick : %d\n", tmr->tick.cur_tick);
snd_iprintf(buffer, "\n");
queuefree(q);
}
}

140
sound/core/seq/seq_queue.h Normal file
View File

@@ -0,0 +1,140 @@
/*
* ALSA sequencer Queue handling
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef __SND_SEQ_QUEUE_H
#define __SND_SEQ_QUEUE_H
#include "seq_memory.h"
#include "seq_prioq.h"
#include "seq_timer.h"
#include "seq_lock.h"
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/bitops.h>
#define SEQ_QUEUE_NO_OWNER (-1)
struct _snd_seq_queue {
int queue; /* queue number */
char name[64]; /* name of this queue */
prioq_t *tickq; /* midi tick event queue */
prioq_t *timeq; /* real-time event queue */
seq_timer_t *timer; /* time keeper for this queue */
int owner; /* client that 'owns' the timer */
unsigned int locked:1, /* timer is only accesibble by owner if set */
klocked:1, /* kernel lock (after START) */
check_again:1,
check_blocked:1;
unsigned int flags; /* status flags */
unsigned int info_flags; /* info for sync */
spinlock_t owner_lock;
spinlock_t check_lock;
/* clients which uses this queue (bitmap) */
DECLARE_BITMAP(clients_bitmap, SNDRV_SEQ_MAX_CLIENTS);
unsigned int clients; /* users of this queue */
struct semaphore timer_mutex;
snd_use_lock_t use_lock;
};
/* get the number of current queues */
int snd_seq_queue_get_cur_queues(void);
/* init queues structure */
int snd_seq_queues_init(void);
/* delete queues */
void snd_seq_queues_delete(void);
/* create new queue (constructor) */
int snd_seq_queue_alloc(int client, int locked, unsigned int flags);
/* delete queue (destructor) */
int snd_seq_queue_delete(int client, int queueid);
/* notification that client has left the system */
void snd_seq_queue_client_termination(int client);
/* final stage */
void snd_seq_queue_client_leave(int client);
/* enqueue a event received from one the clients */
int snd_seq_enqueue_event(snd_seq_event_cell_t *cell, int atomic, int hop);
/* Remove events */
void snd_seq_queue_client_leave_cells(int client);
void snd_seq_queue_remove_cells(int client, snd_seq_remove_events_t *info);
/* return pointer to queue structure for specified id */
queue_t *queueptr(int queueid);
/* unlock */
#define queuefree(q) snd_use_lock_free(&(q)->use_lock)
/* return the (first) queue matching with the specified name */
queue_t *snd_seq_queue_find_name(char *name);
/* check single queue and dispatch events */
void snd_seq_check_queue(queue_t *q, int atomic, int hop);
/* access to queue's parameters */
int snd_seq_queue_check_access(int queueid, int client);
int snd_seq_queue_timer_set_tempo(int queueid, int client, snd_seq_queue_tempo_t *info);
int snd_seq_queue_set_owner(int queueid, int client, int locked);
int snd_seq_queue_set_locked(int queueid, int client, int locked);
int snd_seq_queue_timer_open(int queueid);
int snd_seq_queue_timer_close(int queueid);
int snd_seq_queue_use(int queueid, int client, int use);
int snd_seq_queue_is_used(int queueid, int client);
int snd_seq_control_queue(snd_seq_event_t *ev, int atomic, int hop);
void snd_seq_queue_process_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop);
/*
* 64bit division - for sync stuff..
*/
#if defined(i386) || defined(i486)
#define udiv_qrnnd(q, r, n1, n0, d) \
__asm__ ("divl %4" \
: "=a" ((u32)(q)), \
"=d" ((u32)(r)) \
: "0" ((u32)(n0)), \
"1" ((u32)(n1)), \
"rm" ((u32)(d)))
#define u64_div(x,y,q) do {u32 __tmp; udiv_qrnnd(q, __tmp, (x)>>32, x, y);} while (0)
#define u64_mod(x,y,r) do {u32 __tmp; udiv_qrnnd(__tmp, q, (x)>>32, x, y);} while (0)
#define u64_divmod(x,y,q,r) udiv_qrnnd(q, r, (x)>>32, x, y)
#else
#define u64_div(x,y,q) ((q) = (u32)((u64)(x) / (u64)(y)))
#define u64_mod(x,y,r) ((r) = (u32)((u64)(x) % (u64)(y)))
#define u64_divmod(x,y,q,r) (u64_div(x,y,q), u64_mod(x,y,r))
#endif
#endif

190
sound/core/seq/seq_system.c Normal file
View File

@@ -0,0 +1,190 @@
/*
* ALSA sequencer System services Client
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <sound/core.h>
#include "seq_system.h"
#include "seq_timer.h"
#include "seq_queue.h"
/* internal client that provide system services, access to timer etc. */
/*
* Port "Timer"
* - send tempo /start/stop etc. events to this port to manipulate the
* queue's timer. The queue address is specified in
* data.queue.queue.
* - this port supports subscription. The received timer events are
* broadcasted to all subscribed clients. The modified tempo
* value is stored on data.queue.value.
* The modifier client/port is not send.
*
* Port "Announce"
* - does not receive message
* - supports supscription. For each client or port attaching to or
* detaching from the system an announcement is send to the subscribed
* clients.
*
* Idea: the subscription mechanism might also work handy for distributing
* synchronisation and timing information. In this case we would ideally have
* a list of subscribers for each type of sync (time, tick), for each timing
* queue.
*
* NOTE: the queue to be started, stopped, etc. must be specified
* in data.queue.addr.queue field. queue is used only for
* scheduling, and no longer referred as affected queue.
* They are used only for timer broadcast (see above).
* -- iwai
*/
/* client id of our system client */
static int sysclient = -1;
/* port id numbers for this client */
static int announce_port = -1;
/* fill standard header data, source port & channel are filled in */
static int setheader(snd_seq_event_t * ev, int client, int port)
{
if (announce_port < 0)
return -ENODEV;
memset(ev, 0, sizeof(snd_seq_event_t));
ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
ev->source.client = sysclient;
ev->source.port = announce_port;
ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
/* fill data */
/*ev->data.addr.queue = SNDRV_SEQ_ADDRESS_UNKNOWN;*/
ev->data.addr.client = client;
ev->data.addr.port = port;
return 0;
}
/* entry points for broadcasting system events */
void snd_seq_system_broadcast(int client, int port, int type)
{
snd_seq_event_t ev;
if (setheader(&ev, client, port) < 0)
return;
ev.type = type;
snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0);
}
/* entry points for broadcasting system events */
int snd_seq_system_notify(int client, int port, snd_seq_event_t *ev)
{
ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
ev->source.client = sysclient;
ev->source.port = announce_port;
ev->dest.client = client;
ev->dest.port = port;
return snd_seq_kernel_client_dispatch(sysclient, ev, 0, 0);
}
/* call-back handler for timer events */
static int event_input_timer(snd_seq_event_t * ev, int direct, void *private_data, int atomic, int hop)
{
return snd_seq_control_queue(ev, atomic, hop);
}
/* register our internal client */
int __init snd_seq_system_client_init(void)
{
snd_seq_client_callback_t callbacks;
snd_seq_port_callback_t pcallbacks;
snd_seq_client_info_t *inf;
snd_seq_port_info_t *port;
inf = kcalloc(1, sizeof(*inf), GFP_KERNEL);
port = kcalloc(1, sizeof(*port), GFP_KERNEL);
if (! inf || ! port) {
kfree(inf);
kfree(port);
return -ENOMEM;
}
memset(&callbacks, 0, sizeof(callbacks));
memset(&pcallbacks, 0, sizeof(pcallbacks));
pcallbacks.owner = THIS_MODULE;
pcallbacks.event_input = event_input_timer;
/* register client */
callbacks.allow_input = callbacks.allow_output = 1;
sysclient = snd_seq_create_kernel_client(NULL, 0, &callbacks);
/* set our name */
inf->client = 0;
inf->type = KERNEL_CLIENT;
strcpy(inf->name, "System");
snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, inf);
/* register timer */
strcpy(port->name, "Timer");
port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* accept queue control */
port->capability |= SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast */
port->kernel = &pcallbacks;
port->type = 0;
port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
port->addr.client = sysclient;
port->addr.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, port);
/* register announcement port */
strcpy(port->name, "Announce");
port->capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast only */
port->kernel = NULL;
port->type = 0;
port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
port->addr.client = sysclient;
port->addr.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, port);
announce_port = port->addr.port;
kfree(inf);
kfree(port);
return 0;
}
/* unregister our internal client */
void __exit snd_seq_system_client_done(void)
{
int oldsysclient = sysclient;
if (oldsysclient >= 0) {
sysclient = -1;
announce_port = -1;
snd_seq_delete_kernel_client(oldsysclient);
}
}

View File

@@ -0,0 +1,46 @@
/*
* ALSA sequencer System Client
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef __SND_SEQ_SYSTEM_H
#define __SND_SEQ_SYSTEM_H
#include <sound/seq_kernel.h>
/* entry points for broadcasting system events */
void snd_seq_system_broadcast(int client, int port, int type);
#define snd_seq_system_client_ev_client_start(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_START)
#define snd_seq_system_client_ev_client_exit(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_EXIT)
#define snd_seq_system_client_ev_client_change(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_CHANGE)
#define snd_seq_system_client_ev_port_start(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_START)
#define snd_seq_system_client_ev_port_exit(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_EXIT)
#define snd_seq_system_client_ev_port_change(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_CHANGE)
int snd_seq_system_notify(int client, int port, snd_seq_event_t *ev);
/* register our internal client */
int snd_seq_system_client_init(void);
/* unregister our internal client */
void snd_seq_system_client_done(void);
#endif

435
sound/core/seq/seq_timer.c Normal file
View File

@@ -0,0 +1,435 @@
/*
* ALSA sequencer Timer
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
* Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <sound/core.h>
#include <linux/slab.h>
#include "seq_timer.h"
#include "seq_queue.h"
#include "seq_info.h"
extern int seq_default_timer_class;
extern int seq_default_timer_sclass;
extern int seq_default_timer_card;
extern int seq_default_timer_device;
extern int seq_default_timer_subdevice;
extern int seq_default_timer_resolution;
#define SKEW_BASE 0x10000 /* 16bit shift */
void snd_seq_timer_set_tick_resolution(seq_timer_tick_t *tick, int tempo, int ppq, int nticks)
{
if (tempo < 1000000)
tick->resolution = (tempo * 1000) / ppq;
else {
/* might overflow.. */
unsigned int s;
s = tempo % ppq;
s = (s * 1000) / ppq;
tick->resolution = (tempo / ppq) * 1000;
tick->resolution += s;
}
if (tick->resolution <= 0)
tick->resolution = 1;
tick->resolution *= nticks;
snd_seq_timer_update_tick(tick, 0);
}
/* create new timer (constructor) */
seq_timer_t *snd_seq_timer_new(void)
{
seq_timer_t *tmr;
tmr = kcalloc(1, sizeof(*tmr), GFP_KERNEL);
if (tmr == NULL) {
snd_printd("malloc failed for snd_seq_timer_new() \n");
return NULL;
}
spin_lock_init(&tmr->lock);
/* reset setup to defaults */
snd_seq_timer_defaults(tmr);
/* reset time */
snd_seq_timer_reset(tmr);
return tmr;
}
/* delete timer (destructor) */
void snd_seq_timer_delete(seq_timer_t **tmr)
{
seq_timer_t *t = *tmr;
*tmr = NULL;
if (t == NULL) {
snd_printd("oops: snd_seq_timer_delete() called with NULL timer\n");
return;
}
t->running = 0;
/* reset time */
snd_seq_timer_stop(t);
snd_seq_timer_reset(t);
kfree(t);
}
void snd_seq_timer_defaults(seq_timer_t * tmr)
{
/* setup defaults */
tmr->ppq = 96; /* 96 PPQ */
tmr->tempo = 500000; /* 120 BPM */
snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1);
tmr->running = 0;
tmr->type = SNDRV_SEQ_TIMER_ALSA;
tmr->alsa_id.dev_class = seq_default_timer_class;
tmr->alsa_id.dev_sclass = seq_default_timer_sclass;
tmr->alsa_id.card = seq_default_timer_card;
tmr->alsa_id.device = seq_default_timer_device;
tmr->alsa_id.subdevice = seq_default_timer_subdevice;
tmr->preferred_resolution = seq_default_timer_resolution;
tmr->skew = tmr->skew_base = SKEW_BASE;
}
void snd_seq_timer_reset(seq_timer_t * tmr)
{
unsigned long flags;
spin_lock_irqsave(&tmr->lock, flags);
/* reset time & songposition */
tmr->cur_time.tv_sec = 0;
tmr->cur_time.tv_nsec = 0;
tmr->tick.cur_tick = 0;
tmr->tick.fraction = 0;
spin_unlock_irqrestore(&tmr->lock, flags);
}
/* called by timer interrupt routine. the period time since previous invocation is passed */
static void snd_seq_timer_interrupt(snd_timer_instance_t *timeri,
unsigned long resolution,
unsigned long ticks)
{
unsigned long flags;
queue_t *q = (queue_t *)timeri->callback_data;
seq_timer_t *tmr;
if (q == NULL)
return;
tmr = q->timer;
if (tmr == NULL)
return;
if (!tmr->running)
return;
resolution *= ticks;
if (tmr->skew != tmr->skew_base) {
/* FIXME: assuming skew_base = 0x10000 */
resolution = (resolution >> 16) * tmr->skew +
(((resolution & 0xffff) * tmr->skew) >> 16);
}
spin_lock_irqsave(&tmr->lock, flags);
/* update timer */
snd_seq_inc_time_nsec(&tmr->cur_time, resolution);
/* calculate current tick */
snd_seq_timer_update_tick(&tmr->tick, resolution);
/* register actual time of this timer update */
do_gettimeofday(&tmr->last_update);
spin_unlock_irqrestore(&tmr->lock, flags);
/* check queues and dispatch events */
snd_seq_check_queue(q, 1, 0);
}
/* set current tempo */
int snd_seq_timer_set_tempo(seq_timer_t * tmr, int tempo)
{
unsigned long flags;
snd_assert(tmr, return -EINVAL);
if (tempo <= 0)
return -EINVAL;
spin_lock_irqsave(&tmr->lock, flags);
if ((unsigned int)tempo != tmr->tempo) {
tmr->tempo = tempo;
snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1);
}
spin_unlock_irqrestore(&tmr->lock, flags);
return 0;
}
/* set current ppq */
int snd_seq_timer_set_ppq(seq_timer_t * tmr, int ppq)
{
unsigned long flags;
snd_assert(tmr, return -EINVAL);
if (ppq <= 0)
return -EINVAL;
spin_lock_irqsave(&tmr->lock, flags);
if (tmr->running && (ppq != tmr->ppq)) {
/* refuse to change ppq on running timers */
/* because it will upset the song position (ticks) */
spin_unlock_irqrestore(&tmr->lock, flags);
snd_printd("seq: cannot change ppq of a running timer\n");
return -EBUSY;
}
tmr->ppq = ppq;
snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1);
spin_unlock_irqrestore(&tmr->lock, flags);
return 0;
}
/* set current tick position */
int snd_seq_timer_set_position_tick(seq_timer_t *tmr, snd_seq_tick_time_t position)
{
unsigned long flags;
snd_assert(tmr, return -EINVAL);
spin_lock_irqsave(&tmr->lock, flags);
tmr->tick.cur_tick = position;
tmr->tick.fraction = 0;
spin_unlock_irqrestore(&tmr->lock, flags);
return 0;
}
/* set current real-time position */
int snd_seq_timer_set_position_time(seq_timer_t *tmr, snd_seq_real_time_t position)
{
unsigned long flags;
snd_assert(tmr, return -EINVAL);
snd_seq_sanity_real_time(&position);
spin_lock_irqsave(&tmr->lock, flags);
tmr->cur_time = position;
spin_unlock_irqrestore(&tmr->lock, flags);
return 0;
}
/* set timer skew */
int snd_seq_timer_set_skew(seq_timer_t *tmr, unsigned int skew, unsigned int base)
{
unsigned long flags;
snd_assert(tmr, return -EINVAL);
/* FIXME */
if (base != SKEW_BASE) {
snd_printd("invalid skew base 0x%x\n", base);
return -EINVAL;
}
spin_lock_irqsave(&tmr->lock, flags);
tmr->skew = skew;
spin_unlock_irqrestore(&tmr->lock, flags);
return 0;
}
int snd_seq_timer_open(queue_t *q)
{
snd_timer_instance_t *t;
seq_timer_t *tmr;
char str[32];
int err;
tmr = q->timer;
snd_assert(tmr != NULL, return -EINVAL);
if (tmr->timeri)
return -EBUSY;
sprintf(str, "sequencer queue %i", q->queue);
if (tmr->type != SNDRV_SEQ_TIMER_ALSA) /* standard ALSA timer */
return -EINVAL;
if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
err = snd_timer_open(&t, str, &tmr->alsa_id, q->queue);
if (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) {
if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL ||
tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) {
snd_timer_id_t tid;
memset(&tid, 0, sizeof(tid));
tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL;
tid.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
tid.card = -1;
tid.device = SNDRV_TIMER_GLOBAL_SYSTEM;
err = snd_timer_open(&t, str, &tid, q->queue);
}
if (err < 0) {
snd_printk(KERN_ERR "seq fatal error: cannot create timer (%i)\n", err);
return err;
}
}
t->callback = snd_seq_timer_interrupt;
t->callback_data = q;
t->flags |= SNDRV_TIMER_IFLG_AUTO;
tmr->timeri = t;
return 0;
}
int snd_seq_timer_close(queue_t *q)
{
seq_timer_t *tmr;
tmr = q->timer;
snd_assert(tmr != NULL, return -EINVAL);
if (tmr->timeri) {
snd_timer_stop(tmr->timeri);
snd_timer_close(tmr->timeri);
tmr->timeri = NULL;
}
return 0;
}
int snd_seq_timer_stop(seq_timer_t * tmr)
{
if (! tmr->timeri)
return -EINVAL;
if (!tmr->running)
return 0;
tmr->running = 0;
snd_timer_pause(tmr->timeri);
return 0;
}
static int initialize_timer(seq_timer_t *tmr)
{
snd_timer_t *t;
t = tmr->timeri->timer;
snd_assert(t, return -EINVAL);
tmr->ticks = 1;
if (tmr->preferred_resolution &&
! (t->hw.flags & SNDRV_TIMER_HW_SLAVE)) {
unsigned long r = t->hw.resolution;
if (! r && t->hw.c_resolution)
r = t->hw.c_resolution(t);
if (r) {
tmr->ticks = (unsigned int)(1000000000uL / (r * tmr->preferred_resolution));
if (! tmr->ticks)
tmr->ticks = 1;
}
}
tmr->initialized = 1;
return 0;
}
int snd_seq_timer_start(seq_timer_t * tmr)
{
if (! tmr->timeri)
return -EINVAL;
if (tmr->running)
snd_seq_timer_stop(tmr);
snd_seq_timer_reset(tmr);
if (initialize_timer(tmr) < 0)
return -EINVAL;
snd_timer_start(tmr->timeri, tmr->ticks);
tmr->running = 1;
do_gettimeofday(&tmr->last_update);
return 0;
}
int snd_seq_timer_continue(seq_timer_t * tmr)
{
if (! tmr->timeri)
return -EINVAL;
if (tmr->running)
return -EBUSY;
if (! tmr->initialized) {
snd_seq_timer_reset(tmr);
if (initialize_timer(tmr) < 0)
return -EINVAL;
}
snd_timer_start(tmr->timeri, tmr->ticks);
tmr->running = 1;
do_gettimeofday(&tmr->last_update);
return 0;
}
/* return current 'real' time. use timeofday() to get better granularity. */
snd_seq_real_time_t snd_seq_timer_get_cur_time(seq_timer_t *tmr)
{
snd_seq_real_time_t cur_time;
cur_time = tmr->cur_time;
if (tmr->running) {
struct timeval tm;
int usec;
do_gettimeofday(&tm);
usec = (int)(tm.tv_usec - tmr->last_update.tv_usec);
if (usec < 0) {
cur_time.tv_nsec += (1000000 + usec) * 1000;
cur_time.tv_sec += tm.tv_sec - tmr->last_update.tv_sec - 1;
} else {
cur_time.tv_nsec += usec * 1000;
cur_time.tv_sec += tm.tv_sec - tmr->last_update.tv_sec;
}
snd_seq_sanity_real_time(&cur_time);
}
return cur_time;
}
/* TODO: use interpolation on tick queue (will only be useful for very
high PPQ values) */
snd_seq_tick_time_t snd_seq_timer_get_cur_tick(seq_timer_t *tmr)
{
return tmr->tick.cur_tick;
}
/* exported to seq_info.c */
void snd_seq_info_timer_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
{
int idx;
queue_t *q;
seq_timer_t *tmr;
snd_timer_instance_t *ti;
unsigned long resolution;
for (idx = 0; idx < SNDRV_SEQ_MAX_QUEUES; idx++) {
q = queueptr(idx);
if (q == NULL)
continue;
if ((tmr = q->timer) == NULL ||
(ti = tmr->timeri) == NULL) {
queuefree(q);
continue;
}
snd_iprintf(buffer, "Timer for queue %i : %s\n", q->queue, ti->timer->name);
resolution = snd_timer_resolution(ti) * tmr->ticks;
snd_iprintf(buffer, " Period time : %lu.%09lu\n", resolution / 1000000000, resolution % 1000000000);
snd_iprintf(buffer, " Skew : %u / %u\n", tmr->skew, tmr->skew_base);
queuefree(q);
}
}

141
sound/core/seq/seq_timer.h Normal file
View File

@@ -0,0 +1,141 @@
/*
* ALSA sequencer Timer
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef __SND_SEQ_TIMER_H
#define __SND_SEQ_TIMER_H
#include <sound/timer.h>
#include <sound/seq_kernel.h>
typedef struct {
snd_seq_tick_time_t cur_tick; /* current tick */
unsigned long resolution; /* time per tick in nsec */
unsigned long fraction; /* current time per tick in nsec */
} seq_timer_tick_t;
typedef struct {
/* ... tempo / offset / running state */
unsigned int running:1, /* running state of queue */
initialized:1; /* timer is initialized */
unsigned int tempo; /* current tempo, us/tick */
int ppq; /* time resolution, ticks/quarter */
snd_seq_real_time_t cur_time; /* current time */
seq_timer_tick_t tick; /* current tick */
int tick_updated;
int type; /* timer type */
snd_timer_id_t alsa_id; /* ALSA's timer ID */
snd_timer_instance_t *timeri; /* timer instance */
unsigned int ticks;
unsigned long preferred_resolution; /* timer resolution, ticks/sec */
unsigned int skew;
unsigned int skew_base;
struct timeval last_update; /* time of last clock update, used for interpolation */
spinlock_t lock;
} seq_timer_t;
/* create new timer (constructor) */
extern seq_timer_t *snd_seq_timer_new(void);
/* delete timer (destructor) */
extern void snd_seq_timer_delete(seq_timer_t **tmr);
void snd_seq_timer_set_tick_resolution(seq_timer_tick_t *tick, int tempo, int ppq, int nticks);
/* */
static inline void snd_seq_timer_update_tick(seq_timer_tick_t *tick, unsigned long resolution)
{
if (tick->resolution > 0) {
tick->fraction += resolution;
tick->cur_tick += (unsigned int)(tick->fraction / tick->resolution);
tick->fraction %= tick->resolution;
}
}
/* compare timestamp between events */
/* return 1 if a >= b; otherwise return 0 */
static inline int snd_seq_compare_tick_time(snd_seq_tick_time_t *a, snd_seq_tick_time_t *b)
{
/* compare ticks */
return (*a >= *b);
}
static inline int snd_seq_compare_real_time(snd_seq_real_time_t *a, snd_seq_real_time_t *b)
{
/* compare real time */
if (a->tv_sec > b->tv_sec)
return 1;
if ((a->tv_sec == b->tv_sec) && (a->tv_nsec >= b->tv_nsec))
return 1;
return 0;
}
static inline void snd_seq_sanity_real_time(snd_seq_real_time_t *tm)
{
while (tm->tv_nsec >= 1000000000) {
/* roll-over */
tm->tv_nsec -= 1000000000;
tm->tv_sec++;
}
}
/* increment timestamp */
static inline void snd_seq_inc_real_time(snd_seq_real_time_t *tm, snd_seq_real_time_t *inc)
{
tm->tv_sec += inc->tv_sec;
tm->tv_nsec += inc->tv_nsec;
snd_seq_sanity_real_time(tm);
}
static inline void snd_seq_inc_time_nsec(snd_seq_real_time_t *tm, unsigned long nsec)
{
tm->tv_nsec += nsec;
snd_seq_sanity_real_time(tm);
}
/* called by timer isr */
int snd_seq_timer_open(queue_t *q);
int snd_seq_timer_close(queue_t *q);
int snd_seq_timer_midi_open(queue_t *q);
int snd_seq_timer_midi_close(queue_t *q);
void snd_seq_timer_defaults(seq_timer_t *tmr);
void snd_seq_timer_reset(seq_timer_t *tmr);
int snd_seq_timer_stop(seq_timer_t *tmr);
int snd_seq_timer_start(seq_timer_t *tmr);
int snd_seq_timer_continue(seq_timer_t *tmr);
int snd_seq_timer_set_tempo(seq_timer_t *tmr, int tempo);
int snd_seq_timer_set_ppq(seq_timer_t *tmr, int ppq);
int snd_seq_timer_set_position_tick(seq_timer_t *tmr, snd_seq_tick_time_t position);
int snd_seq_timer_set_position_time(seq_timer_t *tmr, snd_seq_real_time_t position);
int snd_seq_timer_set_skew(seq_timer_t *tmr, unsigned int skew, unsigned int base);
snd_seq_real_time_t snd_seq_timer_get_cur_time(seq_timer_t *tmr);
snd_seq_tick_time_t snd_seq_timer_get_cur_tick(seq_timer_t *tmr);
#endif

View File

@@ -0,0 +1,551 @@
/*
* Virtual Raw MIDI client on Sequencer
*
* Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>,
* Jaroslav Kysela <perex@perex.cz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*
* Virtual Raw MIDI client
*
* The virtual rawmidi client is a sequencer client which associate
* a rawmidi device file. The created rawmidi device file can be
* accessed as a normal raw midi, but its MIDI source and destination
* are arbitrary. For example, a user-client software synth connected
* to this port can be used as a normal midi device as well.
*
* The virtual rawmidi device accepts also multiple opens. Each file
* has its own input buffer, so that no conflict would occur. The drain
* of input/output buffer acts only to the local buffer.
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/info.h>
#include <sound/control.h>
#include <sound/minors.h>
#include <sound/seq_kernel.h>
#include <sound/seq_midi_event.h>
#include <sound/seq_virmidi.h>
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("Virtual Raw MIDI client on Sequencer");
MODULE_LICENSE("GPL");
/*
* initialize an event record
*/
static void snd_virmidi_init_event(snd_virmidi_t *vmidi, snd_seq_event_t *ev)
{
memset(ev, 0, sizeof(*ev));
ev->source.port = vmidi->port;
switch (vmidi->seq_mode) {
case SNDRV_VIRMIDI_SEQ_DISPATCH:
ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
break;
case SNDRV_VIRMIDI_SEQ_ATTACH:
/* FIXME: source and destination are same - not good.. */
ev->dest.client = vmidi->client;
ev->dest.port = vmidi->port;
break;
}
ev->type = SNDRV_SEQ_EVENT_NONE;
}
/*
* decode input event and put to read buffer of each opened file
*/
static int snd_virmidi_dev_receive_event(snd_virmidi_dev_t *rdev, snd_seq_event_t *ev)
{
snd_virmidi_t *vmidi;
struct list_head *list;
unsigned char msg[4];
int len;
read_lock(&rdev->filelist_lock);
list_for_each(list, &rdev->filelist) {
vmidi = list_entry(list, snd_virmidi_t, list);
if (!vmidi->trigger)
continue;
if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {
if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
continue;
snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)snd_rawmidi_receive, vmidi->substream);
} else {
len = snd_midi_event_decode(vmidi->parser, msg, sizeof(msg), ev);
if (len > 0)
snd_rawmidi_receive(vmidi->substream, msg, len);
}
}
read_unlock(&rdev->filelist_lock);
return 0;
}
/*
* receive an event from the remote virmidi port
*
* for rawmidi inputs, you can call this function from the event
* handler of a remote port which is attached to the virmidi via
* SNDRV_VIRMIDI_SEQ_ATTACH.
*/
/* exported */
int snd_virmidi_receive(snd_rawmidi_t *rmidi, snd_seq_event_t *ev)
{
snd_virmidi_dev_t *rdev;
rdev = rmidi->private_data;
return snd_virmidi_dev_receive_event(rdev, ev);
}
/*
* event handler of virmidi port
*/
static int snd_virmidi_event_input(snd_seq_event_t *ev, int direct,
void *private_data, int atomic, int hop)
{
snd_virmidi_dev_t *rdev;
rdev = private_data;
if (!(rdev->flags & SNDRV_VIRMIDI_USE))
return 0; /* ignored */
return snd_virmidi_dev_receive_event(rdev, ev);
}
/*
* trigger rawmidi stream for input
*/
static void snd_virmidi_input_trigger(snd_rawmidi_substream_t * substream, int up)
{
snd_virmidi_t *vmidi = substream->runtime->private_data;
if (up) {
vmidi->trigger = 1;
} else {
vmidi->trigger = 0;
}
}
/*
* trigger rawmidi stream for output
*/
static void snd_virmidi_output_trigger(snd_rawmidi_substream_t * substream, int up)
{
snd_virmidi_t *vmidi = substream->runtime->private_data;
int count, res;
unsigned char buf[32], *pbuf;
if (up) {
vmidi->trigger = 1;
if (vmidi->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH &&
!(vmidi->rdev->flags & SNDRV_VIRMIDI_SUBSCRIBE)) {
snd_rawmidi_transmit_ack(substream, substream->runtime->buffer_size - substream->runtime->avail);
return; /* ignored */
}
if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) {
if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, 0, 0) < 0)
return;
vmidi->event.type = SNDRV_SEQ_EVENT_NONE;
}
while (1) {
count = snd_rawmidi_transmit_peek(substream, buf, sizeof(buf));
if (count <= 0)
break;
pbuf = buf;
while (count > 0) {
res = snd_midi_event_encode(vmidi->parser, pbuf, count, &vmidi->event);
if (res < 0) {
snd_midi_event_reset_encode(vmidi->parser);
continue;
}
snd_rawmidi_transmit_ack(substream, res);
pbuf += res;
count -= res;
if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) {
if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, 0, 0) < 0)
return;
vmidi->event.type = SNDRV_SEQ_EVENT_NONE;
}
}
}
} else {
vmidi->trigger = 0;
}
}
/*
* open rawmidi handle for input
*/
static int snd_virmidi_input_open(snd_rawmidi_substream_t * substream)
{
snd_virmidi_dev_t *rdev = substream->rmidi->private_data;
snd_rawmidi_runtime_t *runtime = substream->runtime;
snd_virmidi_t *vmidi;
unsigned long flags;
vmidi = kcalloc(1, sizeof(*vmidi), GFP_KERNEL);
if (vmidi == NULL)
return -ENOMEM;
vmidi->substream = substream;
if (snd_midi_event_new(0, &vmidi->parser) < 0) {
kfree(vmidi);
return -ENOMEM;
}
vmidi->seq_mode = rdev->seq_mode;
vmidi->client = rdev->client;
vmidi->port = rdev->port;
runtime->private_data = vmidi;
write_lock_irqsave(&rdev->filelist_lock, flags);
list_add_tail(&vmidi->list, &rdev->filelist);
write_unlock_irqrestore(&rdev->filelist_lock, flags);
vmidi->rdev = rdev;
return 0;
}
/*
* open rawmidi handle for output
*/
static int snd_virmidi_output_open(snd_rawmidi_substream_t * substream)
{
snd_virmidi_dev_t *rdev = substream->rmidi->private_data;
snd_rawmidi_runtime_t *runtime = substream->runtime;
snd_virmidi_t *vmidi;
vmidi = kcalloc(1, sizeof(*vmidi), GFP_KERNEL);
if (vmidi == NULL)
return -ENOMEM;
vmidi->substream = substream;
if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &vmidi->parser) < 0) {
kfree(vmidi);
return -ENOMEM;
}
vmidi->seq_mode = rdev->seq_mode;
vmidi->client = rdev->client;
vmidi->port = rdev->port;
snd_virmidi_init_event(vmidi, &vmidi->event);
vmidi->rdev = rdev;
runtime->private_data = vmidi;
return 0;
}
/*
* close rawmidi handle for input
*/
static int snd_virmidi_input_close(snd_rawmidi_substream_t * substream)
{
snd_virmidi_t *vmidi = substream->runtime->private_data;
snd_midi_event_free(vmidi->parser);
list_del(&vmidi->list);
substream->runtime->private_data = NULL;
kfree(vmidi);
return 0;
}
/*
* close rawmidi handle for output
*/
static int snd_virmidi_output_close(snd_rawmidi_substream_t * substream)
{
snd_virmidi_t *vmidi = substream->runtime->private_data;
snd_midi_event_free(vmidi->parser);
substream->runtime->private_data = NULL;
kfree(vmidi);
return 0;
}
/*
* subscribe callback - allow output to rawmidi device
*/
static int snd_virmidi_subscribe(void *private_data, snd_seq_port_subscribe_t *info)
{
snd_virmidi_dev_t *rdev;
rdev = private_data;
if (!try_module_get(rdev->card->module))
return -EFAULT;
rdev->flags |= SNDRV_VIRMIDI_SUBSCRIBE;
return 0;
}
/*
* unsubscribe callback - disallow output to rawmidi device
*/
static int snd_virmidi_unsubscribe(void *private_data, snd_seq_port_subscribe_t *info)
{
snd_virmidi_dev_t *rdev;
rdev = private_data;
rdev->flags &= ~SNDRV_VIRMIDI_SUBSCRIBE;
module_put(rdev->card->module);
return 0;
}
/*
* use callback - allow input to rawmidi device
*/
static int snd_virmidi_use(void *private_data, snd_seq_port_subscribe_t *info)
{
snd_virmidi_dev_t *rdev;
rdev = private_data;
if (!try_module_get(rdev->card->module))
return -EFAULT;
rdev->flags |= SNDRV_VIRMIDI_USE;
return 0;
}
/*
* unuse callback - disallow input to rawmidi device
*/
static int snd_virmidi_unuse(void *private_data, snd_seq_port_subscribe_t *info)
{
snd_virmidi_dev_t *rdev;
rdev = private_data;
rdev->flags &= ~SNDRV_VIRMIDI_USE;
module_put(rdev->card->module);
return 0;
}
/*
* Register functions
*/
static snd_rawmidi_ops_t snd_virmidi_input_ops = {
.open = snd_virmidi_input_open,
.close = snd_virmidi_input_close,
.trigger = snd_virmidi_input_trigger,
};
static snd_rawmidi_ops_t snd_virmidi_output_ops = {
.open = snd_virmidi_output_open,
.close = snd_virmidi_output_close,
.trigger = snd_virmidi_output_trigger,
};
/*
* create a sequencer client and a port
*/
static int snd_virmidi_dev_attach_seq(snd_virmidi_dev_t *rdev)
{
int client;
snd_seq_client_callback_t callbacks;
snd_seq_port_callback_t pcallbacks;
snd_seq_client_info_t *info;
snd_seq_port_info_t *pinfo;
int err;
if (rdev->client >= 0)
return 0;
info = kmalloc(sizeof(*info), GFP_KERNEL);
pinfo = kmalloc(sizeof(*pinfo), GFP_KERNEL);
if (! info || ! pinfo) {
err = -ENOMEM;
goto __error;
}
memset(&callbacks, 0, sizeof(callbacks));
callbacks.private_data = rdev;
callbacks.allow_input = 1;
callbacks.allow_output = 1;
client = snd_seq_create_kernel_client(rdev->card, rdev->device, &callbacks);
if (client < 0) {
err = client;
goto __error;
}
rdev->client = client;
/* set client name */
memset(info, 0, sizeof(*info));
info->client = client;
info->type = KERNEL_CLIENT;
sprintf(info->name, "%s %d-%d", rdev->rmidi->name, rdev->card->number, rdev->device);
snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &info);
/* create a port */
memset(pinfo, 0, sizeof(*pinfo));
pinfo->addr.client = client;
sprintf(pinfo->name, "VirMIDI %d-%d", rdev->card->number, rdev->device);
/* set all capabilities */
pinfo->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
pinfo->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
pinfo->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
pinfo->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
pinfo->midi_channels = 16;
memset(&pcallbacks, 0, sizeof(pcallbacks));
pcallbacks.owner = THIS_MODULE;
pcallbacks.private_data = rdev;
pcallbacks.subscribe = snd_virmidi_subscribe;
pcallbacks.unsubscribe = snd_virmidi_unsubscribe;
pcallbacks.use = snd_virmidi_use;
pcallbacks.unuse = snd_virmidi_unuse;
pcallbacks.event_input = snd_virmidi_event_input;
pinfo->kernel = &pcallbacks;
err = snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo);
if (err < 0) {
snd_seq_delete_kernel_client(client);
rdev->client = -1;
goto __error;
}
rdev->port = pinfo->addr.port;
err = 0; /* success */
__error:
kfree(info);
kfree(pinfo);
return err;
}
/*
* release the sequencer client
*/
static void snd_virmidi_dev_detach_seq(snd_virmidi_dev_t *rdev)
{
if (rdev->client >= 0) {
snd_seq_delete_kernel_client(rdev->client);
rdev->client = -1;
}
}
/*
* register the device
*/
static int snd_virmidi_dev_register(snd_rawmidi_t *rmidi)
{
snd_virmidi_dev_t *rdev = rmidi->private_data;
int err;
switch (rdev->seq_mode) {
case SNDRV_VIRMIDI_SEQ_DISPATCH:
err = snd_virmidi_dev_attach_seq(rdev);
if (err < 0)
return err;
break;
case SNDRV_VIRMIDI_SEQ_ATTACH:
if (rdev->client == 0)
return -EINVAL;
/* should check presence of port more strictly.. */
break;
default:
snd_printk(KERN_ERR "seq_mode is not set: %d\n", rdev->seq_mode);
return -EINVAL;
}
return 0;
}
/*
* unregister the device
*/
static int snd_virmidi_dev_unregister(snd_rawmidi_t *rmidi)
{
snd_virmidi_dev_t *rdev = rmidi->private_data;
if (rdev->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH)
snd_virmidi_dev_detach_seq(rdev);
return 0;
}
/*
*
*/
static snd_rawmidi_global_ops_t snd_virmidi_global_ops = {
.dev_register = snd_virmidi_dev_register,
.dev_unregister = snd_virmidi_dev_unregister,
};
/*
* free device
*/
static void snd_virmidi_free(snd_rawmidi_t *rmidi)
{
snd_virmidi_dev_t *rdev = rmidi->private_data;
kfree(rdev);
}
/*
* create a new device
*
*/
/* exported */
int snd_virmidi_new(snd_card_t *card, int device, snd_rawmidi_t **rrmidi)
{
snd_rawmidi_t *rmidi;
snd_virmidi_dev_t *rdev;
int err;
*rrmidi = NULL;
if ((err = snd_rawmidi_new(card, "VirMidi", device,
16, /* may be configurable */
16, /* may be configurable */
&rmidi)) < 0)
return err;
strcpy(rmidi->name, rmidi->id);
rdev = kcalloc(1, sizeof(*rdev), GFP_KERNEL);
if (rdev == NULL) {
snd_device_free(card, rmidi);
return -ENOMEM;
}
rdev->card = card;
rdev->rmidi = rmidi;
rdev->device = device;
rdev->client = -1;
rwlock_init(&rdev->filelist_lock);
INIT_LIST_HEAD(&rdev->filelist);
rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH;
rmidi->private_data = rdev;
rmidi->private_free = snd_virmidi_free;
rmidi->ops = &snd_virmidi_global_ops;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_virmidi_input_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_virmidi_output_ops);
rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
*rrmidi = rmidi;
return 0;
}
/*
* ENTRY functions
*/
static int __init alsa_virmidi_init(void)
{
return 0;
}
static void __exit alsa_virmidi_exit(void)
{
}
module_init(alsa_virmidi_init)
module_exit(alsa_virmidi_exit)
EXPORT_SYMBOL(snd_virmidi_new);
EXPORT_SYMBOL(snd_virmidi_receive);

111
sound/core/sgbuf.c Normal file
View File

@@ -0,0 +1,111 @@
/*
* Scatter-Gather buffer
*
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/config.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <sound/memalloc.h>
/* table entries are align to 32 */
#define SGBUF_TBL_ALIGN 32
#define sgbuf_align_table(tbl) ((((tbl) + SGBUF_TBL_ALIGN - 1) / SGBUF_TBL_ALIGN) * SGBUF_TBL_ALIGN)
int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
{
struct snd_sg_buf *sgbuf = dmab->private_data;
struct snd_dma_buffer tmpb;
int i;
if (! sgbuf)
return -EINVAL;
tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
tmpb.dev.dev = sgbuf->dev;
for (i = 0; i < sgbuf->pages; i++) {
tmpb.area = sgbuf->table[i].buf;
tmpb.addr = sgbuf->table[i].addr;
tmpb.bytes = PAGE_SIZE;
snd_dma_free_pages(&tmpb);
}
if (dmab->area)
vunmap(dmab->area);
dmab->area = NULL;
kfree(sgbuf->table);
kfree(sgbuf->page_table);
kfree(sgbuf);
dmab->private_data = NULL;
return 0;
}
void *snd_malloc_sgbuf_pages(struct device *device,
size_t size, struct snd_dma_buffer *dmab,
size_t *res_size)
{
struct snd_sg_buf *sgbuf;
unsigned int i, pages;
struct snd_dma_buffer tmpb;
dmab->area = NULL;
dmab->addr = 0;
dmab->private_data = sgbuf = kmalloc(sizeof(*sgbuf), GFP_KERNEL);
if (! sgbuf)
return NULL;
memset(sgbuf, 0, sizeof(*sgbuf));
sgbuf->dev = device;
pages = snd_sgbuf_aligned_pages(size);
sgbuf->tblsize = sgbuf_align_table(pages);
sgbuf->table = kmalloc(sizeof(*sgbuf->table) * sgbuf->tblsize, GFP_KERNEL);
if (! sgbuf->table)
goto _failed;
memset(sgbuf->table, 0, sizeof(*sgbuf->table) * sgbuf->tblsize);
sgbuf->page_table = kmalloc(sizeof(*sgbuf->page_table) * sgbuf->tblsize, GFP_KERNEL);
if (! sgbuf->page_table)
goto _failed;
memset(sgbuf->page_table, 0, sizeof(*sgbuf->page_table) * sgbuf->tblsize);
/* allocate each page */
for (i = 0; i < pages; i++) {
if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, device, PAGE_SIZE, &tmpb) < 0) {
if (res_size == NULL)
goto _failed;
*res_size = size = sgbuf->pages * PAGE_SIZE;
break;
}
sgbuf->table[i].buf = tmpb.area;
sgbuf->table[i].addr = tmpb.addr;
sgbuf->page_table[i] = virt_to_page(tmpb.area);
sgbuf->pages++;
}
sgbuf->size = size;
dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL);
if (! dmab->area)
goto _failed;
return dmab->area;
_failed:
snd_free_sgbuf_pages(dmab); /* free the table */
return NULL;
}

491
sound/core/sound.c Normal file
View File

@@ -0,0 +1,491 @@
/*
* Advanced Linux Sound Architecture
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <sound/info.h>
#include <sound/version.h>
#include <sound/control.h>
#include <sound/initval.h>
#include <linux/kmod.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/device.h>
#define SNDRV_OS_MINORS 256
static int major = CONFIG_SND_MAJOR;
int snd_major;
static int cards_limit = 1;
static int device_mode = S_IFCHR | S_IRUGO | S_IWUGO;
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture driver for soundcards.");
MODULE_LICENSE("GPL");
module_param(major, int, 0444);
MODULE_PARM_DESC(major, "Major # for sound driver.");
module_param(cards_limit, int, 0444);
MODULE_PARM_DESC(cards_limit, "Count of auto-loadable soundcards.");
#ifdef CONFIG_DEVFS_FS
module_param(device_mode, int, 0444);
MODULE_PARM_DESC(device_mode, "Device file permission mask for devfs.");
#endif
MODULE_ALIAS_CHARDEV_MAJOR(CONFIG_SND_MAJOR);
/* this one holds the actual max. card number currently available.
* as default, it's identical with cards_limit option. when more
* modules are loaded manually, this limit number increases, too.
*/
int snd_ecards_limit;
static struct list_head snd_minors_hash[SNDRV_CARDS];
static DECLARE_MUTEX(sound_mutex);
extern struct class_simple *sound_class;
#ifdef CONFIG_KMOD
/**
* snd_request_card - try to load the card module
* @card: the card number
*
* Tries to load the module "snd-card-X" for the given card number
* via KMOD. Returns immediately if already loaded.
*/
void snd_request_card(int card)
{
int locked;
if (! current->fs->root)
return;
read_lock(&snd_card_rwlock);
locked = snd_cards_lock & (1 << card);
read_unlock(&snd_card_rwlock);
if (locked)
return;
if (card < 0 || card >= cards_limit)
return;
request_module("snd-card-%i", card);
}
static void snd_request_other(int minor)
{
char *str;
if (! current->fs->root)
return;
switch (minor) {
case SNDRV_MINOR_SEQUENCER: str = "snd-seq"; break;
case SNDRV_MINOR_TIMER: str = "snd-timer"; break;
default: return;
}
request_module(str);
}
#endif /* request_module support */
static snd_minor_t *snd_minor_search(int minor)
{
struct list_head *list;
snd_minor_t *mptr;
list_for_each(list, &snd_minors_hash[SNDRV_MINOR_CARD(minor)]) {
mptr = list_entry(list, snd_minor_t, list);
if (mptr->number == minor)
return mptr;
}
return NULL;
}
static int snd_open(struct inode *inode, struct file *file)
{
int minor = iminor(inode);
int card = SNDRV_MINOR_CARD(minor);
int dev = SNDRV_MINOR_DEVICE(minor);
snd_minor_t *mptr = NULL;
struct file_operations *old_fops;
int err = 0;
if (dev != SNDRV_MINOR_SEQUENCER && dev != SNDRV_MINOR_TIMER) {
if (snd_cards[card] == NULL) {
#ifdef CONFIG_KMOD
snd_request_card(card);
if (snd_cards[card] == NULL)
#endif
return -ENODEV;
}
} else {
#ifdef CONFIG_KMOD
if ((mptr = snd_minor_search(minor)) == NULL)
snd_request_other(minor);
#endif
}
if (mptr == NULL && (mptr = snd_minor_search(minor)) == NULL)
return -ENODEV;
old_fops = file->f_op;
file->f_op = fops_get(mptr->f_ops);
if (file->f_op->open)
err = file->f_op->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
return err;
}
static struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open
};
static int snd_kernel_minor(int type, snd_card_t * card, int dev)
{
int minor;
switch (type) {
case SNDRV_DEVICE_TYPE_SEQUENCER:
case SNDRV_DEVICE_TYPE_TIMER:
minor = type;
break;
case SNDRV_DEVICE_TYPE_CONTROL:
snd_assert(card != NULL, return -EINVAL);
minor = SNDRV_MINOR(card->number, type);
break;
case SNDRV_DEVICE_TYPE_HWDEP:
case SNDRV_DEVICE_TYPE_RAWMIDI:
case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:
case SNDRV_DEVICE_TYPE_PCM_CAPTURE:
snd_assert(card != NULL, return -EINVAL);
minor = SNDRV_MINOR(card->number, type + dev);
break;
default:
return -EINVAL;
}
snd_assert(minor >= 0 && minor < SNDRV_OS_MINORS, return -EINVAL);
return minor;
}
/**
* snd_register_device - Register the ALSA device file for the card
* @type: the device type, SNDRV_DEVICE_TYPE_XXX
* @card: the card instance
* @dev: the device index
* @reg: the snd_minor_t record
* @name: the device file name
*
* Registers an ALSA device file for the given card.
* The operators have to be set in reg parameter.
*
* Retrurns zero if successful, or a negative error code on failure.
*/
int snd_register_device(int type, snd_card_t * card, int dev, snd_minor_t * reg, const char *name)
{
int minor = snd_kernel_minor(type, card, dev);
snd_minor_t *preg;
struct device *device = NULL;
if (minor < 0)
return minor;
snd_assert(name, return -EINVAL);
preg = (snd_minor_t *)kmalloc(sizeof(snd_minor_t) + strlen(name) + 1, GFP_KERNEL);
if (preg == NULL)
return -ENOMEM;
*preg = *reg;
preg->number = minor;
preg->device = dev;
strcpy(preg->name, name);
down(&sound_mutex);
if (snd_minor_search(minor)) {
up(&sound_mutex);
kfree(preg);
return -EBUSY;
}
list_add_tail(&preg->list, &snd_minors_hash[SNDRV_MINOR_CARD(minor)]);
if (strncmp(name, "controlC", 8) || card->number >= cards_limit)
devfs_mk_cdev(MKDEV(major, minor), S_IFCHR | device_mode, "snd/%s", name);
if (card)
device = card->dev;
class_simple_device_add(sound_class, MKDEV(major, minor), device, name);
up(&sound_mutex);
return 0;
}
/**
* snd_unregister_device - unregister the device on the given card
* @type: the device type, SNDRV_DEVICE_TYPE_XXX
* @card: the card instance
* @dev: the device index
*
* Unregisters the device file already registered via
* snd_register_device().
*
* Returns zero if sucecessful, or a negative error code on failure
*/
int snd_unregister_device(int type, snd_card_t * card, int dev)
{
int minor = snd_kernel_minor(type, card, dev);
snd_minor_t *mptr;
if (minor < 0)
return minor;
down(&sound_mutex);
if ((mptr = snd_minor_search(minor)) == NULL) {
up(&sound_mutex);
return -EINVAL;
}
if (strncmp(mptr->name, "controlC", 8) || card->number >= cards_limit) /* created in sound.c */
devfs_remove("snd/%s", mptr->name);
class_simple_device_remove(MKDEV(major, minor));
list_del(&mptr->list);
up(&sound_mutex);
kfree(mptr);
return 0;
}
/*
* INFO PART
*/
static snd_info_entry_t *snd_minor_info_entry = NULL;
static void snd_minor_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
{
int card, device;
struct list_head *list;
snd_minor_t *mptr;
down(&sound_mutex);
for (card = 0; card < SNDRV_CARDS; card++) {
list_for_each(list, &snd_minors_hash[card]) {
mptr = list_entry(list, snd_minor_t, list);
if (SNDRV_MINOR_DEVICE(mptr->number) != SNDRV_MINOR_SEQUENCER) {
if ((device = mptr->device) >= 0)
snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", mptr->number, card, device, mptr->comment);
else
snd_iprintf(buffer, "%3i: [%i] : %s\n", mptr->number, card, mptr->comment);
} else {
snd_iprintf(buffer, "%3i: : %s\n", mptr->number, mptr->comment);
}
}
}
up(&sound_mutex);
}
int __init snd_minor_info_init(void)
{
snd_info_entry_t *entry;
entry = snd_info_create_module_entry(THIS_MODULE, "devices", NULL);
if (entry) {
entry->c.text.read_size = PAGE_SIZE;
entry->c.text.read = snd_minor_info_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
snd_minor_info_entry = entry;
return 0;
}
int __exit snd_minor_info_done(void)
{
if (snd_minor_info_entry)
snd_info_unregister(snd_minor_info_entry);
return 0;
}
/*
* INIT PART
*/
static int __init alsa_sound_init(void)
{
short controlnum;
int err;
int card;
snd_major = major;
snd_ecards_limit = cards_limit;
for (card = 0; card < SNDRV_CARDS; card++)
INIT_LIST_HEAD(&snd_minors_hash[card]);
if ((err = snd_oss_init_module()) < 0)
return err;
devfs_mk_dir("snd");
if (register_chrdev(major, "alsa", &snd_fops)) {
snd_printk(KERN_ERR "unable to register native major device number %d\n", major);
devfs_remove("snd");
return -EIO;
}
snd_memory_init();
if (snd_info_init() < 0) {
snd_memory_done();
unregister_chrdev(major, "alsa");
devfs_remove("snd");
return -ENOMEM;
}
snd_info_minor_register();
for (controlnum = 0; controlnum < cards_limit; controlnum++)
devfs_mk_cdev(MKDEV(major, controlnum<<5), S_IFCHR | device_mode, "snd/controlC%d", controlnum);
#ifndef MODULE
printk(KERN_INFO "Advanced Linux Sound Architecture Driver Version " CONFIG_SND_VERSION CONFIG_SND_DATE ".\n");
#endif
return 0;
}
static void __exit alsa_sound_exit(void)
{
short controlnum;
for (controlnum = 0; controlnum < cards_limit; controlnum++)
devfs_remove("snd/controlC%d", controlnum);
snd_info_minor_unregister();
snd_info_done();
snd_memory_done();
if (unregister_chrdev(major, "alsa") != 0)
snd_printk(KERN_ERR "unable to unregister major device number %d\n", major);
devfs_remove("snd");
}
module_init(alsa_sound_init)
module_exit(alsa_sound_exit)
/* sound.c */
EXPORT_SYMBOL(snd_major);
EXPORT_SYMBOL(snd_ecards_limit);
#if defined(CONFIG_KMOD)
EXPORT_SYMBOL(snd_request_card);
#endif
EXPORT_SYMBOL(snd_register_device);
EXPORT_SYMBOL(snd_unregister_device);
#if defined(CONFIG_SND_OSSEMUL)
EXPORT_SYMBOL(snd_register_oss_device);
EXPORT_SYMBOL(snd_unregister_oss_device);
#endif
/* memory.c */
#ifdef CONFIG_SND_DEBUG_MEMORY
EXPORT_SYMBOL(snd_hidden_kmalloc);
EXPORT_SYMBOL(snd_hidden_kcalloc);
EXPORT_SYMBOL(snd_hidden_kfree);
EXPORT_SYMBOL(snd_hidden_vmalloc);
EXPORT_SYMBOL(snd_hidden_vfree);
#endif
EXPORT_SYMBOL(snd_kmalloc_strdup);
EXPORT_SYMBOL(copy_to_user_fromio);
EXPORT_SYMBOL(copy_from_user_toio);
/* init.c */
EXPORT_SYMBOL(snd_cards);
#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
EXPORT_SYMBOL(snd_mixer_oss_notify_callback);
#endif
EXPORT_SYMBOL(snd_card_new);
EXPORT_SYMBOL(snd_card_disconnect);
EXPORT_SYMBOL(snd_card_free);
EXPORT_SYMBOL(snd_card_free_in_thread);
EXPORT_SYMBOL(snd_card_register);
EXPORT_SYMBOL(snd_component_add);
EXPORT_SYMBOL(snd_card_file_add);
EXPORT_SYMBOL(snd_card_file_remove);
#ifdef CONFIG_PM
EXPORT_SYMBOL(snd_power_wait);
EXPORT_SYMBOL(snd_card_set_pm_callback);
#if defined(CONFIG_PM) && defined(CONFIG_SND_GENERIC_PM)
EXPORT_SYMBOL(snd_card_set_generic_pm_callback);
#endif
#ifdef CONFIG_PCI
EXPORT_SYMBOL(snd_card_pci_suspend);
EXPORT_SYMBOL(snd_card_pci_resume);
#endif
#endif
/* device.c */
EXPORT_SYMBOL(snd_device_new);
EXPORT_SYMBOL(snd_device_register);
EXPORT_SYMBOL(snd_device_free);
EXPORT_SYMBOL(snd_device_free_all);
/* isadma.c */
#ifdef CONFIG_ISA
EXPORT_SYMBOL(snd_dma_program);
EXPORT_SYMBOL(snd_dma_disable);
EXPORT_SYMBOL(snd_dma_pointer);
#endif
/* info.c */
#ifdef CONFIG_PROC_FS
EXPORT_SYMBOL(snd_seq_root);
EXPORT_SYMBOL(snd_iprintf);
EXPORT_SYMBOL(snd_info_get_line);
EXPORT_SYMBOL(snd_info_get_str);
EXPORT_SYMBOL(snd_info_create_module_entry);
EXPORT_SYMBOL(snd_info_create_card_entry);
EXPORT_SYMBOL(snd_info_free_entry);
EXPORT_SYMBOL(snd_info_register);
EXPORT_SYMBOL(snd_info_unregister);
EXPORT_SYMBOL(snd_card_proc_new);
#endif
/* info_oss.c */
#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
EXPORT_SYMBOL(snd_oss_info_register);
#endif
/* control.c */
EXPORT_SYMBOL(snd_ctl_new);
EXPORT_SYMBOL(snd_ctl_new1);
EXPORT_SYMBOL(snd_ctl_free_one);
EXPORT_SYMBOL(snd_ctl_add);
EXPORT_SYMBOL(snd_ctl_remove);
EXPORT_SYMBOL(snd_ctl_remove_id);
EXPORT_SYMBOL(snd_ctl_rename_id);
EXPORT_SYMBOL(snd_ctl_find_numid);
EXPORT_SYMBOL(snd_ctl_find_id);
EXPORT_SYMBOL(snd_ctl_notify);
EXPORT_SYMBOL(snd_ctl_register_ioctl);
EXPORT_SYMBOL(snd_ctl_unregister_ioctl);
#ifdef CONFIG_COMPAT
EXPORT_SYMBOL(snd_ctl_register_ioctl_compat);
EXPORT_SYMBOL(snd_ctl_unregister_ioctl_compat);
#endif
EXPORT_SYMBOL(snd_ctl_elem_read);
EXPORT_SYMBOL(snd_ctl_elem_write);
/* misc.c */
EXPORT_SYMBOL(snd_task_name);
#ifdef CONFIG_SND_VERBOSE_PRINTK
EXPORT_SYMBOL(snd_verbose_printk);
#endif
#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_SND_VERBOSE_PRINTK)
EXPORT_SYMBOL(snd_verbose_printd);
#endif
/* wrappers */
#ifdef CONFIG_SND_DEBUG_MEMORY
EXPORT_SYMBOL(snd_wrapper_kmalloc);
EXPORT_SYMBOL(snd_wrapper_kfree);
EXPORT_SYMBOL(snd_wrapper_vmalloc);
EXPORT_SYMBOL(snd_wrapper_vfree);
#endif

250
sound/core/sound_oss.c Normal file
View File

@@ -0,0 +1,250 @@
/*
* Advanced Linux Sound Architecture
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#ifdef CONFIG_SND_OSSEMUL
#if !defined(CONFIG_SOUND) && !(defined(MODULE) && defined(CONFIG_SOUND_MODULE))
#error "Enable the OSS soundcore multiplexer (CONFIG_SOUND) in the kernel."
#endif
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <sound/info.h>
#include <linux/sound.h>
#define SNDRV_OS_MINORS 256
static struct list_head snd_oss_minors_hash[SNDRV_CARDS];
static DECLARE_MUTEX(sound_oss_mutex);
static snd_minor_t *snd_oss_minor_search(int minor)
{
struct list_head *list;
snd_minor_t *mptr;
list_for_each(list, &snd_oss_minors_hash[SNDRV_MINOR_OSS_CARD(minor)]) {
mptr = list_entry(list, snd_minor_t, list);
if (mptr->number == minor)
return mptr;
}
return NULL;
}
static int snd_oss_kernel_minor(int type, snd_card_t * card, int dev)
{
int minor;
switch (type) {
case SNDRV_OSS_DEVICE_TYPE_MIXER:
snd_assert(card != NULL && dev <= 1, return -EINVAL);
minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIXER1 : SNDRV_MINOR_OSS_MIXER));
break;
case SNDRV_OSS_DEVICE_TYPE_SEQUENCER:
minor = SNDRV_MINOR_OSS_SEQUENCER;
break;
case SNDRV_OSS_DEVICE_TYPE_MUSIC:
minor = SNDRV_MINOR_OSS_MUSIC;
break;
case SNDRV_OSS_DEVICE_TYPE_PCM:
snd_assert(card != NULL && dev <= 1, return -EINVAL);
minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_PCM1 : SNDRV_MINOR_OSS_PCM));
break;
case SNDRV_OSS_DEVICE_TYPE_MIDI:
snd_assert(card != NULL && dev <= 1, return -EINVAL);
minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIDI1 : SNDRV_MINOR_OSS_MIDI));
break;
case SNDRV_OSS_DEVICE_TYPE_DMFM:
minor = SNDRV_MINOR_OSS(card->number, SNDRV_MINOR_OSS_DMFM);
break;
case SNDRV_OSS_DEVICE_TYPE_SNDSTAT:
minor = SNDRV_MINOR_OSS_SNDSTAT;
break;
default:
return -EINVAL;
}
snd_assert(minor >= 0 && minor < SNDRV_OS_MINORS, return -EINVAL);
return minor;
}
int snd_register_oss_device(int type, snd_card_t * card, int dev, snd_minor_t * reg, const char *name)
{
int minor = snd_oss_kernel_minor(type, card, dev);
int minor_unit;
snd_minor_t *preg;
int cidx = SNDRV_MINOR_OSS_CARD(minor);
int track2 = -1;
int register1 = -1, register2 = -1;
if (minor < 0)
return minor;
preg = (snd_minor_t *)kmalloc(sizeof(snd_minor_t), GFP_KERNEL);
if (preg == NULL)
return -ENOMEM;
*preg = *reg;
preg->number = minor;
preg->device = dev;
down(&sound_oss_mutex);
list_add_tail(&preg->list, &snd_oss_minors_hash[cidx]);
minor_unit = SNDRV_MINOR_OSS_DEVICE(minor);
switch (minor_unit) {
case SNDRV_MINOR_OSS_PCM:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO);
break;
case SNDRV_MINOR_OSS_MIDI:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI);
break;
case SNDRV_MINOR_OSS_MIDI1:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1);
break;
}
register1 = register_sound_special(reg->f_ops, minor);
if (register1 != minor)
goto __end;
if (track2 >= 0) {
register2 = register_sound_special(reg->f_ops, track2);
if (register2 != track2)
goto __end;
}
up(&sound_oss_mutex);
return 0;
__end:
if (register2 >= 0)
unregister_sound_special(register2);
if (register1 >= 0)
unregister_sound_special(register1);
list_del(&preg->list);
up(&sound_oss_mutex);
kfree(preg);
return -EBUSY;
}
int snd_unregister_oss_device(int type, snd_card_t * card, int dev)
{
int minor = snd_oss_kernel_minor(type, card, dev);
int cidx = SNDRV_MINOR_OSS_CARD(minor);
int track2 = -1;
snd_minor_t *mptr;
if (minor < 0)
return minor;
down(&sound_oss_mutex);
mptr = snd_oss_minor_search(minor);
if (mptr == NULL) {
up(&sound_oss_mutex);
return -ENOENT;
}
unregister_sound_special(minor);
switch (SNDRV_MINOR_OSS_DEVICE(minor)) {
case SNDRV_MINOR_OSS_PCM:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO);
break;
case SNDRV_MINOR_OSS_MIDI:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI);
break;
case SNDRV_MINOR_OSS_MIDI1:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1);
break;
}
if (track2 >= 0)
unregister_sound_special(track2);
list_del(&mptr->list);
up(&sound_oss_mutex);
kfree(mptr);
return 0;
}
/*
* INFO PART
*/
#ifdef CONFIG_PROC_FS
static snd_info_entry_t *snd_minor_info_oss_entry = NULL;
static void snd_minor_info_oss_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
{
int card, dev;
struct list_head *list;
snd_minor_t *mptr;
down(&sound_oss_mutex);
for (card = 0; card < SNDRV_CARDS; card++) {
list_for_each(list, &snd_oss_minors_hash[card]) {
mptr = list_entry(list, snd_minor_t, list);
dev = SNDRV_MINOR_OSS_DEVICE(mptr->number);
if (dev != SNDRV_MINOR_OSS_SNDSTAT &&
dev != SNDRV_MINOR_OSS_SEQUENCER &&
dev != SNDRV_MINOR_OSS_MUSIC)
snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", mptr->number, card, dev, mptr->comment);
else
snd_iprintf(buffer, "%3i: : %s\n", mptr->number, mptr->comment);
}
}
up(&sound_oss_mutex);
}
#endif /* CONFIG_PROC_FS */
int __init snd_minor_info_oss_init(void)
{
#ifdef CONFIG_PROC_FS
snd_info_entry_t *entry;
entry = snd_info_create_module_entry(THIS_MODULE, "devices", snd_oss_root);
if (entry) {
entry->c.text.read_size = PAGE_SIZE;
entry->c.text.read = snd_minor_info_oss_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
snd_minor_info_oss_entry = entry;
#endif
return 0;
}
int __exit snd_minor_info_oss_done(void)
{
#ifdef CONFIG_PROC_FS
if (snd_minor_info_oss_entry)
snd_info_unregister(snd_minor_info_oss_entry);
#endif
return 0;
}
int __init snd_oss_init_module(void)
{
int card;
for (card = 0; card < SNDRV_CARDS; card++)
INIT_LIST_HEAD(&snd_oss_minors_hash[card]);
return 0;
}
#endif /* CONFIG_SND_OSSEMUL */

1901
sound/core/timer.c Normal file

File diff suppressed because it is too large Load Diff

119
sound/core/timer_compat.c Normal file
View File

@@ -0,0 +1,119 @@
/*
* 32bit -> 64bit ioctl wrapper for timer API
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* This file included from timer.c */
#include <linux/compat.h>
struct sndrv_timer_info32 {
u32 flags;
s32 card;
unsigned char id[64];
unsigned char name[80];
u32 reserved0;
u32 resolution;
unsigned char reserved[64];
};
static int snd_timer_user_info_compat(struct file *file,
struct sndrv_timer_info32 __user *_info)
{
snd_timer_user_t *tu;
struct sndrv_timer_info32 info;
snd_timer_t *t;
tu = file->private_data;
snd_assert(tu->timeri != NULL, return -ENXIO);
t = tu->timeri->timer;
snd_assert(t != NULL, return -ENXIO);
memset(&info, 0, sizeof(info));
info.card = t->card ? t->card->number : -1;
if (t->hw.flags & SNDRV_TIMER_HW_SLAVE)
info.flags |= SNDRV_TIMER_FLG_SLAVE;
strlcpy(info.id, t->id, sizeof(info.id));
strlcpy(info.name, t->name, sizeof(info.name));
info.resolution = t->hw.resolution;
if (copy_to_user(_info, &info, sizeof(*_info)))
return -EFAULT;
return 0;
}
struct sndrv_timer_status32 {
struct compat_timespec tstamp;
u32 resolution;
u32 lost;
u32 overrun;
u32 queue;
unsigned char reserved[64];
};
static int snd_timer_user_status_compat(struct file *file,
struct sndrv_timer_status32 __user *_status)
{
snd_timer_user_t *tu;
snd_timer_status_t status;
tu = file->private_data;
snd_assert(tu->timeri != NULL, return -ENXIO);
memset(&status, 0, sizeof(status));
status.tstamp = tu->tstamp;
status.resolution = snd_timer_resolution(tu->timeri);
status.lost = tu->timeri->lost;
status.overrun = tu->overrun;
spin_lock_irq(&tu->qlock);
status.queue = tu->qused;
spin_unlock_irq(&tu->qlock);
if (copy_to_user(_status, &status, sizeof(status)))
return -EFAULT;
return 0;
}
/*
*/
enum {
SNDRV_TIMER_IOCTL_INFO32 = _IOR('T', 0x11, struct sndrv_timer_info32),
SNDRV_TIMER_IOCTL_STATUS32 = _IOW('T', 0x14, struct sndrv_timer_status32),
};
static long snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *argp = compat_ptr(arg);
switch (cmd) {
case SNDRV_TIMER_IOCTL_PVERSION:
case SNDRV_TIMER_IOCTL_TREAD:
case SNDRV_TIMER_IOCTL_GINFO:
case SNDRV_TIMER_IOCTL_GPARAMS:
case SNDRV_TIMER_IOCTL_GSTATUS:
case SNDRV_TIMER_IOCTL_SELECT:
case SNDRV_TIMER_IOCTL_PARAMS:
case SNDRV_TIMER_IOCTL_START:
case SNDRV_TIMER_IOCTL_STOP:
case SNDRV_TIMER_IOCTL_CONTINUE:
case SNDRV_TIMER_IOCTL_NEXT_DEVICE:
return snd_timer_user_ioctl(file, cmd, (unsigned long)argp);
case SNDRV_TIMER_IOCTL_INFO32:
return snd_timer_user_info_compat(file, argp);
case SNDRV_TIMER_IOCTL_STATUS32:
return snd_timer_user_status_compat(file, argp);
}
return -ENOIOCTLCMD;
}

50
sound/core/wrappers.c Normal file
View File

@@ -0,0 +1,50 @@
/*
* Various wrappers
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/vmalloc.h>
#include <linux/fs.h>
#ifdef CONFIG_SND_DEBUG_MEMORY
void *snd_wrapper_kmalloc(size_t size, int flags)
{
return kmalloc(size, flags);
}
void snd_wrapper_kfree(const void *obj)
{
kfree(obj);
}
void *snd_wrapper_vmalloc(unsigned long size)
{
return vmalloc(size);
}
void snd_wrapper_vfree(void *obj)
{
vfree(obj);
}
#endif

Some files were not shown because too many files have changed in this diff Show More