425 lines
8.6 KiB
C
425 lines
8.6 KiB
C
/*
|
|
* sound/oss/midibuf.c
|
|
*
|
|
* Device file manager for /dev/midi#
|
|
*/
|
|
/*
|
|
* Copyright (C) by Hannu Savolainen 1993-1997
|
|
*
|
|
* OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
|
|
* Version 2 (June 1991). See the "COPYING" file distributed with this software
|
|
* for more info.
|
|
*/
|
|
/*
|
|
* Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
|
|
*/
|
|
#include <linux/stddef.h>
|
|
#include <linux/kmod.h>
|
|
#include <linux/spinlock.h>
|
|
#define MIDIBUF_C
|
|
|
|
#include "sound_config.h"
|
|
|
|
|
|
/*
|
|
* Don't make MAX_QUEUE_SIZE larger than 4000
|
|
*/
|
|
|
|
#define MAX_QUEUE_SIZE 4000
|
|
|
|
static wait_queue_head_t midi_sleeper[MAX_MIDI_DEV];
|
|
static wait_queue_head_t input_sleeper[MAX_MIDI_DEV];
|
|
|
|
struct midi_buf
|
|
{
|
|
int len, head, tail;
|
|
unsigned char queue[MAX_QUEUE_SIZE];
|
|
};
|
|
|
|
struct midi_parms
|
|
{
|
|
long prech_timeout; /*
|
|
* Timeout before the first ch
|
|
*/
|
|
};
|
|
|
|
static struct midi_buf *midi_out_buf[MAX_MIDI_DEV] = {NULL};
|
|
static struct midi_buf *midi_in_buf[MAX_MIDI_DEV] = {NULL};
|
|
static struct midi_parms parms[MAX_MIDI_DEV];
|
|
|
|
static void midi_poll(unsigned long dummy);
|
|
|
|
|
|
static DEFINE_TIMER(poll_timer, midi_poll, 0, 0);
|
|
|
|
static volatile int open_devs;
|
|
static DEFINE_SPINLOCK(lock);
|
|
|
|
#define DATA_AVAIL(q) (q->len)
|
|
#define SPACE_AVAIL(q) (MAX_QUEUE_SIZE - q->len)
|
|
|
|
#define QUEUE_BYTE(q, data) \
|
|
if (SPACE_AVAIL(q)) \
|
|
{ \
|
|
unsigned long flags; \
|
|
spin_lock_irqsave(&lock, flags); \
|
|
q->queue[q->tail] = (data); \
|
|
q->len++; q->tail = (q->tail+1) % MAX_QUEUE_SIZE; \
|
|
spin_unlock_irqrestore(&lock, flags); \
|
|
}
|
|
|
|
#define REMOVE_BYTE(q, data) \
|
|
if (DATA_AVAIL(q)) \
|
|
{ \
|
|
unsigned long flags; \
|
|
spin_lock_irqsave(&lock, flags); \
|
|
data = q->queue[q->head]; \
|
|
q->len--; q->head = (q->head+1) % MAX_QUEUE_SIZE; \
|
|
spin_unlock_irqrestore(&lock, flags); \
|
|
}
|
|
|
|
static void drain_midi_queue(int dev)
|
|
{
|
|
|
|
/*
|
|
* Give the Midi driver time to drain its output queues
|
|
*/
|
|
|
|
if (midi_devs[dev]->buffer_status != NULL)
|
|
wait_event_interruptible_timeout(midi_sleeper[dev],
|
|
!midi_devs[dev]->buffer_status(dev), HZ/10);
|
|
}
|
|
|
|
static void midi_input_intr(int dev, unsigned char data)
|
|
{
|
|
if (midi_in_buf[dev] == NULL)
|
|
return;
|
|
|
|
if (data == 0xfe) /*
|
|
* Active sensing
|
|
*/
|
|
return; /*
|
|
* Ignore
|
|
*/
|
|
|
|
if (SPACE_AVAIL(midi_in_buf[dev])) {
|
|
QUEUE_BYTE(midi_in_buf[dev], data);
|
|
wake_up(&input_sleeper[dev]);
|
|
}
|
|
}
|
|
|
|
static void midi_output_intr(int dev)
|
|
{
|
|
/*
|
|
* Currently NOP
|
|
*/
|
|
}
|
|
|
|
static void midi_poll(unsigned long dummy)
|
|
{
|
|
unsigned long flags;
|
|
int dev;
|
|
|
|
spin_lock_irqsave(&lock, flags);
|
|
if (open_devs)
|
|
{
|
|
for (dev = 0; dev < num_midis; dev++)
|
|
if (midi_devs[dev] != NULL && midi_out_buf[dev] != NULL)
|
|
{
|
|
while (DATA_AVAIL(midi_out_buf[dev]))
|
|
{
|
|
int ok;
|
|
int c = midi_out_buf[dev]->queue[midi_out_buf[dev]->head];
|
|
|
|
spin_unlock_irqrestore(&lock,flags);/* Give some time to others */
|
|
ok = midi_devs[dev]->outputc(dev, c);
|
|
spin_lock_irqsave(&lock, flags);
|
|
if (!ok)
|
|
break;
|
|
midi_out_buf[dev]->head = (midi_out_buf[dev]->head + 1) % MAX_QUEUE_SIZE;
|
|
midi_out_buf[dev]->len--;
|
|
}
|
|
|
|
if (DATA_AVAIL(midi_out_buf[dev]) < 100)
|
|
wake_up(&midi_sleeper[dev]);
|
|
}
|
|
poll_timer.expires = (1) + jiffies;
|
|
add_timer(&poll_timer);
|
|
/*
|
|
* Come back later
|
|
*/
|
|
}
|
|
spin_unlock_irqrestore(&lock, flags);
|
|
}
|
|
|
|
int MIDIbuf_open(int dev, struct file *file)
|
|
{
|
|
int mode, err;
|
|
|
|
dev = dev >> 4;
|
|
mode = translate_mode(file);
|
|
|
|
if (num_midis > MAX_MIDI_DEV)
|
|
{
|
|
printk(KERN_ERR "midi: Too many midi interfaces\n");
|
|
num_midis = MAX_MIDI_DEV;
|
|
}
|
|
if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
|
|
return -ENXIO;
|
|
/*
|
|
* Interrupts disabled. Be careful
|
|
*/
|
|
|
|
module_put(midi_devs[dev]->owner);
|
|
|
|
if ((err = midi_devs[dev]->open(dev, mode,
|
|
midi_input_intr, midi_output_intr)) < 0)
|
|
return err;
|
|
|
|
parms[dev].prech_timeout = MAX_SCHEDULE_TIMEOUT;
|
|
midi_in_buf[dev] = vmalloc(sizeof(struct midi_buf));
|
|
|
|
if (midi_in_buf[dev] == NULL)
|
|
{
|
|
printk(KERN_WARNING "midi: Can't allocate buffer\n");
|
|
midi_devs[dev]->close(dev);
|
|
return -EIO;
|
|
}
|
|
midi_in_buf[dev]->len = midi_in_buf[dev]->head = midi_in_buf[dev]->tail = 0;
|
|
|
|
midi_out_buf[dev] = vmalloc(sizeof(struct midi_buf));
|
|
|
|
if (midi_out_buf[dev] == NULL)
|
|
{
|
|
printk(KERN_WARNING "midi: Can't allocate buffer\n");
|
|
midi_devs[dev]->close(dev);
|
|
vfree(midi_in_buf[dev]);
|
|
midi_in_buf[dev] = NULL;
|
|
return -EIO;
|
|
}
|
|
midi_out_buf[dev]->len = midi_out_buf[dev]->head = midi_out_buf[dev]->tail = 0;
|
|
open_devs++;
|
|
|
|
init_waitqueue_head(&midi_sleeper[dev]);
|
|
init_waitqueue_head(&input_sleeper[dev]);
|
|
|
|
if (open_devs < 2) /* This was first open */
|
|
{
|
|
poll_timer.expires = 1 + jiffies;
|
|
add_timer(&poll_timer); /* Start polling */
|
|
}
|
|
return err;
|
|
}
|
|
|
|
void MIDIbuf_release(int dev, struct file *file)
|
|
{
|
|
int mode;
|
|
|
|
dev = dev >> 4;
|
|
mode = translate_mode(file);
|
|
|
|
if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
|
|
return;
|
|
|
|
/*
|
|
* Wait until the queue is empty
|
|
*/
|
|
|
|
if (mode != OPEN_READ)
|
|
{
|
|
midi_devs[dev]->outputc(dev, 0xfe); /*
|
|
* Active sensing to shut the
|
|
* devices
|
|
*/
|
|
|
|
wait_event_interruptible(midi_sleeper[dev],
|
|
!DATA_AVAIL(midi_out_buf[dev]));
|
|
/*
|
|
* Sync
|
|
*/
|
|
|
|
drain_midi_queue(dev); /*
|
|
* Ensure the output queues are empty
|
|
*/
|
|
}
|
|
|
|
midi_devs[dev]->close(dev);
|
|
|
|
open_devs--;
|
|
if (open_devs == 0)
|
|
del_timer_sync(&poll_timer);
|
|
vfree(midi_in_buf[dev]);
|
|
vfree(midi_out_buf[dev]);
|
|
midi_in_buf[dev] = NULL;
|
|
midi_out_buf[dev] = NULL;
|
|
|
|
module_put(midi_devs[dev]->owner);
|
|
}
|
|
|
|
int MIDIbuf_write(int dev, struct file *file, const char __user *buf, int count)
|
|
{
|
|
int c, n, i;
|
|
unsigned char tmp_data;
|
|
|
|
dev = dev >> 4;
|
|
|
|
if (!count)
|
|
return 0;
|
|
|
|
c = 0;
|
|
|
|
while (c < count)
|
|
{
|
|
n = SPACE_AVAIL(midi_out_buf[dev]);
|
|
|
|
if (n == 0) { /*
|
|
* No space just now.
|
|
*/
|
|
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
c = -EAGAIN;
|
|
goto out;
|
|
}
|
|
|
|
if (wait_event_interruptible(midi_sleeper[dev],
|
|
SPACE_AVAIL(midi_out_buf[dev])))
|
|
{
|
|
c = -EINTR;
|
|
goto out;
|
|
}
|
|
n = SPACE_AVAIL(midi_out_buf[dev]);
|
|
}
|
|
if (n > (count - c))
|
|
n = count - c;
|
|
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
/* BROKE BROKE BROKE - CAN'T DO THIS WITH CLI !! */
|
|
/* yes, think the same, so I removed the cli() brackets
|
|
QUEUE_BYTE is protected against interrupts */
|
|
if (copy_from_user((char *) &tmp_data, &(buf)[c], 1)) {
|
|
c = -EFAULT;
|
|
goto out;
|
|
}
|
|
QUEUE_BYTE(midi_out_buf[dev], tmp_data);
|
|
c++;
|
|
}
|
|
}
|
|
out:
|
|
return c;
|
|
}
|
|
|
|
|
|
int MIDIbuf_read(int dev, struct file *file, char __user *buf, int count)
|
|
{
|
|
int n, c = 0;
|
|
unsigned char tmp_data;
|
|
|
|
dev = dev >> 4;
|
|
|
|
if (!DATA_AVAIL(midi_in_buf[dev])) { /*
|
|
* No data yet, wait
|
|
*/
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
c = -EAGAIN;
|
|
goto out;
|
|
}
|
|
wait_event_interruptible_timeout(input_sleeper[dev],
|
|
DATA_AVAIL(midi_in_buf[dev]),
|
|
parms[dev].prech_timeout);
|
|
|
|
if (signal_pending(current))
|
|
c = -EINTR; /* The user is getting restless */
|
|
}
|
|
if (c == 0 && DATA_AVAIL(midi_in_buf[dev])) /*
|
|
* Got some bytes
|
|
*/
|
|
{
|
|
n = DATA_AVAIL(midi_in_buf[dev]);
|
|
if (n > count)
|
|
n = count;
|
|
c = 0;
|
|
|
|
while (c < n)
|
|
{
|
|
char *fixit;
|
|
REMOVE_BYTE(midi_in_buf[dev], tmp_data);
|
|
fixit = (char *) &tmp_data;
|
|
/* BROKE BROKE BROKE */
|
|
/* yes removed the cli() brackets again
|
|
should q->len,tail&head be atomic_t? */
|
|
if (copy_to_user(&(buf)[c], fixit, 1)) {
|
|
c = -EFAULT;
|
|
goto out;
|
|
}
|
|
c++;
|
|
}
|
|
}
|
|
out:
|
|
return c;
|
|
}
|
|
|
|
int MIDIbuf_ioctl(int dev, struct file *file,
|
|
unsigned int cmd, void __user *arg)
|
|
{
|
|
int val;
|
|
|
|
dev = dev >> 4;
|
|
|
|
if (((cmd >> 8) & 0xff) == 'C')
|
|
{
|
|
if (midi_devs[dev]->coproc) /* Coprocessor ioctl */
|
|
return midi_devs[dev]->coproc->ioctl(midi_devs[dev]->coproc->devc, cmd, arg, 0);
|
|
/* printk("/dev/midi%d: No coprocessor for this device\n", dev);*/
|
|
return -ENXIO;
|
|
}
|
|
else
|
|
{
|
|
switch (cmd)
|
|
{
|
|
case SNDCTL_MIDI_PRETIME:
|
|
if (get_user(val, (int __user *)arg))
|
|
return -EFAULT;
|
|
if (val < 0)
|
|
val = 0;
|
|
val = (HZ * val) / 10;
|
|
parms[dev].prech_timeout = val;
|
|
return put_user(val, (int __user *)arg);
|
|
|
|
default:
|
|
if (!midi_devs[dev]->ioctl)
|
|
return -EINVAL;
|
|
return midi_devs[dev]->ioctl(dev, cmd, arg);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* No kernel lock - fine */
|
|
unsigned int MIDIbuf_poll(int dev, struct file *file, poll_table * wait)
|
|
{
|
|
unsigned int mask = 0;
|
|
|
|
dev = dev >> 4;
|
|
|
|
/* input */
|
|
poll_wait(file, &input_sleeper[dev], wait);
|
|
if (DATA_AVAIL(midi_in_buf[dev]))
|
|
mask |= POLLIN | POLLRDNORM;
|
|
|
|
/* output */
|
|
poll_wait(file, &midi_sleeper[dev], wait);
|
|
if (!SPACE_AVAIL(midi_out_buf[dev]))
|
|
mask |= POLLOUT | POLLWRNORM;
|
|
|
|
return mask;
|
|
}
|
|
|
|
|
|
int MIDIbuf_avail(int dev)
|
|
{
|
|
if (midi_in_buf[dev])
|
|
return DATA_AVAIL (midi_in_buf[dev]);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(MIDIbuf_avail);
|
|
|