/*
**********************************************************************
* midi.c - /dev/midi interface for emu10k1 driver
* Copyright 1999, 2000 Creative Labs, Inc.
*
**********************************************************************
*
* Date Author Summary of changes
* ---- ------ ------------------
* October 20, 1999 Bertrand Lee base code release
*
**********************************************************************
*
* 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., 675 Mass Ave, Cambridge, MA 02139,
* USA.
*
**********************************************************************
*/
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/smp_lock.h>
#include <asm/uaccess.h>
#include "hwaccess.h"
#include "cardmo.h"
#include "cardmi.h"
#include "midi.h"
#ifdef EMU10K1_SEQUENCER
#include "../sound_config.h"
#endif
static DEFINE_SPINLOCK(midi_spinlock);
static void init_midi_hdr(struct midi_hdr *midihdr)
{
midihdr->bufferlength = MIDIIN_BUFLEN;
midihdr->bytesrecorded = 0;
midihdr->flags = 0;
}
static int midiin_add_buffer(struct emu10k1_mididevice *midi_dev, struct midi_hdr **midihdrptr)
{
struct midi_hdr *midihdr;
if ((midihdr = kmalloc(sizeof(struct midi_hdr), GFP_KERNEL)) == NULL) {
ERROR();
return -EINVAL;
}
init_midi_hdr(midihdr);
midihdr->data = kmalloc(MIDIIN_BUFLEN, GFP_KERNEL);
if (!midihdr->data) {
ERROR();
kfree(midihdr);
return -1;
}
if (emu10k1_mpuin_add_buffer(midi_dev->card->mpuin, midihdr) < 0) {
ERROR();
kfree(midihdr->data);
kfree(midihdr);
return -1;
}
*midihdrptr = midihdr;
list_add_tail(&midihdr->list, &midi_dev->mid_hdrs);
return 0;
}
static int emu10k1_midi_open(struct inode *inode, struct file *file)
{
int minor = iminor(inode);
struct emu10k1_card *card = NULL;
struct emu10k1_mididevice *midi_dev;
struct list_head *entry;
DPF(2, "emu10k1_midi_open()\n");
/* Check for correct device to open */
list_for_each(entry, &emu10k1_devs) {
card = list_entry(entry, struct emu10k1_card, list);
if (card->midi_dev == minor)
goto match;
}
return -ENODEV;
match:
#ifdef EMU10K1_SEQUENCER
if (card->seq_mididev) /* card is opened by sequencer */
return -EBUSY;
#endif
/* Wait for device to become free */
mutex_lock(&card->open_sem);
while (card->open_mode & (file->f_mode << FMODE_MIDI_SHIFT)) {
if (file->f_flags & O_NONBLOCK) {
mutex_unlock(&card->open_sem);
return -EBUSY;
}
mutex_unlock(&card->open_sem);
interruptible_sleep_on(&card->open_wait);
if (signal_pending(current)) {
return -ERESTARTSYS;
}
mutex_lock(&card->open_sem);
}
if ((midi_dev = kmalloc(sizeof(*midi_dev), GFP_KERNEL)) == NULL)
return -EINVAL;
midi_dev->card = card;
midi_dev->mistate = MIDIIN_STATE_STOPPED;
init_waitqueue_head(&midi_dev->oWait);
init_waitqueue_head(&midi_dev->iWait);
midi_dev->ird = 0;
midi_dev->iwr = 0;
midi_dev->icnt = 0;
INIT_LIST_HEAD(&midi_dev->mid_hdrs);
if (file->f_mode & FMODE_READ) {
struct midi_openinfo dsCardMidiOpenInfo;
struct midi_hdr *midihdr1;
struct midi_hdr *midihdr2;
dsCardMidiOpenInfo.refdata = (unsigned long) midi_dev;
if (emu10k1_mpuin_open(card, &dsCardMidiOpenInfo) < 0) {
ERROR();
kfree(midi_dev);
return -ENODEV;
}
/* Add two buffers to receive sysex buffer */
if (midiin_add_buffer(midi_dev, &midihdr1) < 0) {
kfree(midi_dev);
return -ENODEV;
}
if (midiin_add_buffer(midi_dev, &midihdr2) < 0) {
list_del(&midihdr1->list);
kfree(midihdr1->data);
kfree(midihdr1);
kfree(midi_dev);
return -ENODEV;
}
}
if (file->f_mode & FMODE_WRITE) {
struct midi_openinfo dsCardMidiOpenInfo;
dsCardMidiOpenInfo.refdata = (unsigned long) midi_dev;
if (emu10k1_mpuout_open(card, &dsCardMidiOpenInfo) < 0) {
ERROR();
kfree(midi_dev);
return -ENODEV;
}
}
file->private_data = (void *) midi_dev;
card->open_mode |= (file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ | FMODE_MIDI_WRITE);
mutex_unlock(&card->open_sem);
return nonseekable_open(inode, file);
}
static int emu10k1_midi_release(struct inode *inode, struct file *file)
{
struct emu10k1_mididevice *midi_dev = (struct emu10k1_mididevice *) file->private_data;
struct emu10k1_card *card;
lock_kernel();
card = midi_dev->card;
DPF(2, "emu10k1_midi_release()\n");
if (file->f_mode & FMODE_WRITE) {
if (!(file->f_flags & O_NONBLOCK)) {
while (!signal_pending(current) && (card->mpuout->firstmidiq != NULL)) {
DPF(4, "Cannot close - buffers not empty\n");
interruptible_sleep_on(&midi_dev->oWait);
}
}
emu10k1_mpuout_close(card);
}
if (file->f_mode & FMODE_READ) {
struct midi_hdr *midihdr;
if (midi_dev->mistate == MIDIIN_STATE_STARTED) {
emu10k1_mpuin_stop(card);
midi_dev->mistate = MIDIIN_STATE_STOPPED;
}
emu10k1_mpuin_reset(card);
emu10k1_mpuin_close(card);
while (!list_empty(&midi_dev->mid_hdrs)) {
midihdr = list_entry(midi_dev->mid_hdrs.next, struct midi_hdr, list);
list_del(midi_dev->mid_hdrs.next);
kfree(midihdr->data);
kfree(midihdr);
}
}
kfree(midi_dev);
mutex_lock(&card->open_sem);
card->open_mode &= ~((file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ | FMODE_MIDI_WRITE));
mutex_unlock(&card->open_sem);
wake_up_interruptible(&card->open_wait);
unlock_kernel();
return 0;
}
static ssize_t emu10k1_midi_read(struct file *file, char __user *buffer, size_t count, loff_t * pos)
{
struct emu10k1_mididevice *midi_dev = (struct emu10k1_mididevice *) file->private_data;
ssize_t ret = 0;
u16 cnt;
unsigned long flags;
DPD(4, "emu10k1_midi_read(), count %#x\n", (u32) count);
if (!access_ok(VERIFY_WRITE, buffer, count))
return -EFAULT;
if (midi_dev->mistate == MIDIIN_STATE_STOPPED) {
if (emu10k1_mpuin_start(midi_dev->card) < 0) {
ERROR();
return -EINVAL;
}
midi_dev->mistate = MIDIIN_STATE_STARTED;
}
while (count > 0) {
cnt = MIDIIN_BUFLEN - midi_dev->ird;
spin_lock_irqsave(&midi_spinlock, flags);
if (midi_dev->icnt < cnt)
cnt = midi_dev->icnt;
spin_unlock_irqrestore(&midi_spinlock, flags);
if (cnt > count)
cnt = count;
if (cnt <= 0) {
if (file->f_flags & O_NONBLOCK)
return ret ? ret : -EAGAIN;
DPF(2, " Go to sleep...\n");
interruptible_sleep_on(&midi_dev->iWait);
if (signal_pending(current))
return ret ? ret : -ERESTARTSYS;
continue;
}
if (copy_to_user(buffer, midi_dev->iBuf + midi_dev->ird, cnt)) {
ERROR();
return ret ? ret : -EFAULT;
}
midi_dev->ird += cnt;
midi_dev->ird %= MIDIIN_BUFLEN;
spin_lock_irqsave(&midi_spinlock, flags);
midi_dev->icnt -= cnt;
spin_unlock_irqrestore(&midi_spinlock, flags);
count -= cnt;
buffer += cnt;
ret += cnt;
if (midi_dev->icnt == 0)
break;
}
return ret;
}
static ssize_t emu10k1_midi_write(struct file *file, const char __user *buffer, size_t count, loff_t * pos)
{
struct emu10k1_mididevice *midi_dev = (struct emu10k1_mididevice *) file->private_data;
struct midi_hdr *midihdr;
unsigned long flags;
DPD(4, "emu10k1_midi_write(), count=%#x\n", (u32) count);
if (!access_ok(VERIFY_READ, buffer, count))
return -EFAULT;
if ((midihdr = kmalloc(sizeof(struct midi_hdr), GFP_KERNEL)) == NULL)
return -EINVAL;
midihdr->bufferlength = count;
midihdr->bytesrecorded = 0;
midihdr->flags = 0;
midihdr->data = kmalloc(count, GFP_KERNEL);
if (!midihdr->data) {
ERROR();
kfree(midihdr);
return -EINVAL;
}
if (copy_from_user(midihdr->data, buffer, count)) {
kfree(midihdr->data);
kfree(midihdr);
return -EFAULT;
}
spin_lock_irqsave(&midi_spinlock, flags);
if (emu10k1_mpuout_add_buffer(midi_dev->card, midihdr) < 0) {
ERROR();
kfree(midihdr->data);
kfree(midihdr);
spin_unlock_irqrestore(&midi_spinlock, flags);
return -EINVAL;
}
spin_unlock_irqrestore(&midi_spinlock, flags);
return count;
}
static unsigned int emu10k1_midi_poll(struct file *file, struct poll_table_struct *wait)
{
struct emu10k1_mididevice *midi_dev = (struct emu10k1_mididevice *) file->private_data;
unsigned long flags;
unsigned int mask = 0;
DPF(4, "emu10k1_midi_poll() called\n");
if (file->f_mode & FMODE_WRITE)
poll_wait(file, &midi_dev->oWait, wait);
if (file->f_mode & FMODE_READ)
poll_wait(file, &midi_dev->iWait, wait);
spin_lock_irqsave(&midi_spinlock, flags);
if (file->f_mode & FMODE_WRITE)
mask |= POLLOUT | POLLWRNORM;
if (file->f_mode & FMODE_READ) {
if (midi_dev->mistate == MIDIIN_STATE_STARTED)
if (midi_dev->icnt > 0)
mask |= POLLIN | POLLRDNORM;
}
spin_unlock_irqrestore(&midi_spinlock, flags);
return mask;
}
int emu10k1_midi_callback(unsigned long msg, unsigned long refdata, unsigned long *pmsg)
{
struct emu10k1_mididevice *midi_dev = (struct emu10k1_mididevice *) refdata;
struct midi_hdr *midihdr = NULL;
unsigned long flags;
int i;
DPF(4, "emu10k1_midi_callback()\n");
spin_lock_irqsave(&midi_spinlock, flags);
switch (msg) {
case ICARDMIDI_OUTLONGDATA:
midihdr = (struct midi_hdr *) pmsg[2];
kfree(midihdr->data);
kfree(midihdr);
wake_up_interruptible(&midi_dev->oWait);
break;
case ICARDMIDI_INLONGDATA:
midihdr = (struct midi_hdr *) pmsg[2];
for (i = 0; i < midihdr->bytesrecorded; i++) {
midi_dev->iBuf[midi_dev->iwr++] = midihdr->data[i];
midi_dev->iwr %= MIDIIN_BUFLEN;
}
midi_dev->icnt += midihdr->bytesrecorded;
if (midi_dev->mistate == MIDIIN_STATE_STARTED) {
init_midi_hdr(midihdr);
emu10k1_mpuin_add_buffer(midi_dev->card->mpuin, midihdr);
wake_up_interruptible(&midi_dev->iWait);
}
break;
case ICARDMIDI_INDATA:
{
u8 *pBuf = (u8 *) & pmsg[1];
u16 bytesvalid = pmsg[2];
for (i = 0; i < bytesvalid; i++) {
midi_dev->iBuf[midi_dev->iwr++] = pBuf[i];
midi_dev->iwr %= MIDIIN_BUFLEN;
}
midi_dev->icnt += bytesvalid;
}
wake_up_interruptible(&midi_dev->iWait);
break;
default: /* Unknown message */
spin_unlock_irqrestore(&midi_spinlock, flags);
return -1;
}
spin_unlock_irqrestore(&midi_spinlock, flags);
return 0;
}
/* MIDI file operations */
const struct file_operations emu10k1_midi_fops = {
.owner = THIS_MODULE,
.read = emu10k1_midi_read,
.write = emu10k1_midi_write,
.poll = emu10k1_midi_poll,
.open = emu10k1_midi_open,
.release = emu10k1_midi_release,
};
#ifdef EMU10K1_SEQUENCER
/* functions used for sequencer access */
int emu10k1_seq_midi_open(int dev, int mode,
void (*input) (int dev, unsigned char data),
void (*output) (int dev))
{
struct emu10k1_card *card;
struct midi_openinfo dsCardMidiOpenInfo;
struct emu10k1_mididevice *midi_dev;
if (midi_devs[dev] == NULL || midi_devs[dev]->devc == NULL)
return -EINVAL;
card = midi_devs[dev]->devc;
if (card->open_mode) /* card is opened native */
return -EBUSY;
DPF(2, "emu10k1_seq_midi_open()\n");
if ((midi_dev = kmalloc(sizeof(*midi_dev), GFP_KERNEL)) == NULL)
return -EINVAL;
midi_dev->card = card;
midi_dev->mistate = MIDIIN_STATE_STOPPED;
init_waitqueue_head(&midi_dev->oWait);
init_waitqueue_head(&midi_dev->iWait);
midi_dev->ird = 0;
midi_dev->iwr = 0;
midi_dev->icnt = 0;
INIT_LIST_HEAD(&midi_dev->mid_hdrs);
dsCardMidiOpenInfo.refdata = (unsigned long) midi_dev;
if (emu10k1_mpuout_open(card, &dsCardMidiOpenInfo) < 0) {
ERROR();
return -ENODEV;
}
card->seq_mididev = midi_dev;
return 0;
}
void emu10k1_seq_midi_close(int dev)
{
struct emu10k1_card *card;
DPF(2, "emu10k1_seq_midi_close()\n");
if (midi_devs[dev] == NULL || midi_devs[dev]->devc == NULL)
return;
card = midi_devs[dev]->devc;
emu10k1_mpuout_close(card);
kfree(card->seq_mididev);
card->seq_mididev = NULL;
}
int emu10k1_seq_midi_out(int dev, unsigned char midi_byte)
{
struct emu10k1_card *card;
struct midi_hdr *midihdr;
unsigned long flags;
if (midi_devs[dev] == NULL || midi_devs[dev]->devc == NULL)
return -EINVAL;
card = midi_devs[dev]->devc;
if ((midihdr = kmalloc(sizeof(struct midi_hdr), GFP_KERNEL)) == NULL)
return -EINVAL;
midihdr->bufferlength = 1;
midihdr->bytesrecorded = 0;
midihdr->flags = 0;
midihdr->data = kmalloc(1, GFP_KERNEL);
if (!midihdr->data) {
ERROR();
kfree(midihdr);
return -EINVAL;
}
*(midihdr->data) = midi_byte;
spin_lock_irqsave(&midi_spinlock, flags);
if (emu10k1_mpuout_add_buffer(card, midihdr) < 0) {
ERROR();
kfree(midihdr->data);
kfree(midihdr);
spin_unlock_irqrestore(&midi_spinlock, flags);
return -EINVAL;
}
spin_unlock_irqrestore(&midi_spinlock, flags);
return 1;
}
int emu10k1_seq_midi_start_read(int dev)
{
return 0;
}
int emu10k1_seq_midi_end_read(int dev)
{
return 0;
}
void emu10k1_seq_midi_kick(int dev)
{
}
int emu10k1_seq_midi_buffer_status(int dev)
{
int count;
struct midi_queue *queue;
struct emu10k1_card *card;
if (midi_devs[dev] == NULL || midi_devs[dev]->devc == NULL)
return -EINVAL;
count = 0;
card = midi_devs[dev]->devc;
queue = card->mpuout->firstmidiq;
while (queue != NULL) {
count++;
if (queue == card->mpuout->lastmidiq)
break;
queue = queue->next;
}
return count;
}
#endif