ALSA: seq: Add UMP support
[linux-2.6-microblaze.git] / sound / core / seq / seq_clientmgr.c
index 89a8d14..801d5ee 100644 (file)
@@ -387,6 +387,15 @@ static int snd_seq_release(struct inode *inode, struct file *file)
        return 0;
 }
 
+static bool event_is_compatible(const struct snd_seq_client *client,
+                               const struct snd_seq_event *ev)
+{
+       if (snd_seq_ev_is_ump(ev) && !client->midi_version)
+               return false;
+       if (snd_seq_ev_is_ump(ev) && snd_seq_ev_is_variable(ev))
+               return false;
+       return true;
+}
 
 /* handle client read() */
 /* possible error values:
@@ -400,6 +409,7 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count,
 {
        struct snd_seq_client *client = file->private_data;
        struct snd_seq_fifo *fifo;
+       size_t aligned_size;
        int err;
        long result = 0;
        struct snd_seq_event_cell *cell;
@@ -431,43 +441,54 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count,
        err = 0;
        snd_seq_fifo_lock(fifo);
 
+       if (client->midi_version > 0)
+               aligned_size = sizeof(struct snd_seq_ump_event);
+       else
+               aligned_size = sizeof(struct snd_seq_event);
+
        /* while data available in queue */
-       while (count >= sizeof(struct snd_seq_event)) {
+       while (count >= aligned_size) {
                int nonblock;
 
                nonblock = (file->f_flags & O_NONBLOCK) || result > 0;
                err = snd_seq_fifo_cell_out(fifo, &cell, nonblock);
                if (err < 0)
                        break;
+               if (!event_is_compatible(client, &cell->event)) {
+                       snd_seq_cell_free(cell);
+                       cell = NULL;
+                       continue;
+               }
                if (snd_seq_ev_is_variable(&cell->event)) {
-                       struct snd_seq_event tmpev;
-                       tmpev = cell->event;
+                       struct snd_seq_ump_event tmpev;
+
+                       memcpy(&tmpev, &cell->event, aligned_size);
                        tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK;
-                       if (copy_to_user(buf, &tmpev, sizeof(struct snd_seq_event))) {
+                       if (copy_to_user(buf, &tmpev, aligned_size)) {
                                err = -EFAULT;
                                break;
                        }
-                       count -= sizeof(struct snd_seq_event);
-                       buf += sizeof(struct snd_seq_event);
+                       count -= aligned_size;
+                       buf += aligned_size;
                        err = snd_seq_expand_var_event(&cell->event, count,
                                                       (char __force *)buf, 0,
-                                                      sizeof(struct snd_seq_event));
+                                                      aligned_size);
                        if (err < 0)
                                break;
                        result += err;
                        count -= err;
                        buf += err;
                } else {
-                       if (copy_to_user(buf, &cell->event, sizeof(struct snd_seq_event))) {
+                       if (copy_to_user(buf, &cell->event, aligned_size)) {
                                err = -EFAULT;
                                break;
                        }
-                       count -= sizeof(struct snd_seq_event);
-                       buf += sizeof(struct snd_seq_event);
+                       count -= aligned_size;
+                       buf += aligned_size;
                }
                snd_seq_cell_free(cell);
                cell = NULL; /* to be sure */
-               result += sizeof(struct snd_seq_event);
+               result += aligned_size;
        }
 
        if (err < 0) {
@@ -665,15 +686,17 @@ static int deliver_to_subscribers(struct snd_seq_client *client,
 {
        struct snd_seq_subscribers *subs;
        int err, result = 0, num_ev = 0;
-       struct snd_seq_event event_saved;
        struct snd_seq_client_port *src_port;
+       union __snd_seq_event event_saved;
+       size_t saved_size;
        struct snd_seq_port_subs_info *grp;
 
        src_port = snd_seq_port_use_ptr(client, event->source.port);
        if (src_port == NULL)
                return -EINVAL; /* invalid source port */
        /* save original event record */
-       event_saved = *event;
+       saved_size = snd_seq_event_packet_size(event);
+       memcpy(&event_saved, event, saved_size);
        grp = &src_port->c_src;
        
        /* lock list */
@@ -700,14 +723,13 @@ static int deliver_to_subscribers(struct snd_seq_client *client,
                }
                num_ev++;
                /* restore original event record */
-               *event = event_saved;
+               memcpy(event, &event_saved, saved_size);
        }
        if (atomic)
                read_unlock(&grp->list_lock);
        else
                up_read(&grp->list_mutex);
-       *event = event_saved; /* restore */
-       snd_seq_port_unlock(src_port);
+       memcpy(event, &event_saved, saved_size);
        return (result < 0) ? result : num_ev;
 }
 
@@ -769,7 +791,8 @@ int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop)
                return -EINVAL;
        }
 
-       if (cell->event.type == SNDRV_SEQ_EVENT_NOTE) {
+       if (!snd_seq_ev_is_ump(&cell->event) &&
+           cell->event.type == SNDRV_SEQ_EVENT_NOTE) {
                /* NOTE event:
                 * the event cell is re-used as a NOTE-OFF event and
                 * enqueued again.
@@ -793,7 +816,7 @@ int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop)
                /* add the duration time */
                switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) {
                case SNDRV_SEQ_TIME_STAMP_TICK:
-                       ev->time.tick += ev->data.note.duration;
+                       cell->event.time.tick += ev->data.note.duration;
                        break;
                case SNDRV_SEQ_TIME_STAMP_REAL:
                        /* unit for duration is ms */
@@ -850,7 +873,8 @@ static int snd_seq_client_enqueue_event(struct snd_seq_client *client,
 
        /* direct event processing without enqueued */
        if (snd_seq_ev_is_direct(event)) {
-               if (event->type == SNDRV_SEQ_EVENT_NOTE)
+               if (!snd_seq_ev_is_ump(event) &&
+                   event->type == SNDRV_SEQ_EVENT_NOTE)
                        return -EINVAL; /* this event must be enqueued! */
                return snd_seq_deliver_event(client, event, atomic, hop);
        }
@@ -920,7 +944,8 @@ static ssize_t snd_seq_write(struct file *file, const char __user *buf,
        struct snd_seq_client *client = file->private_data;
        int written = 0, len;
        int err, handled;
-       struct snd_seq_event event;
+       union __snd_seq_event __event;
+       struct snd_seq_event *ev = &__event.legacy;
 
        if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT))
                return -ENXIO;
@@ -946,49 +971,66 @@ static ssize_t snd_seq_write(struct file *file, const char __user *buf,
        err = -EINVAL;
        while (count >= sizeof(struct snd_seq_event)) {
                /* Read in the event header from the user */
-               len = sizeof(event);
-               if (copy_from_user(&event, buf, len)) {
+               len = sizeof(struct snd_seq_event);
+               if (copy_from_user(ev, buf, len)) {
                        err = -EFAULT;
                        break;
                }
-               event.source.client = client->number;   /* fill in client number */
+               /* read in the rest bytes for UMP events */
+               if (snd_seq_ev_is_ump(ev)) {
+                       if (count < sizeof(struct snd_seq_ump_event))
+                               break;
+                       if (copy_from_user((char *)ev + len, buf + len,
+                                          sizeof(struct snd_seq_ump_event) - len)) {
+                               err = -EFAULT;
+                               break;
+                       }
+                       len = sizeof(struct snd_seq_ump_event);
+               }
+
+               ev->source.client = client->number;     /* fill in client number */
                /* Check for extension data length */
-               if (check_event_type_and_length(&event)) {
+               if (check_event_type_and_length(ev)) {
                        err = -EINVAL;
                        break;
                }
 
-               /* check for special events */
-               if (event.type == SNDRV_SEQ_EVENT_NONE)
-                       goto __skip_event;
-               else if (snd_seq_ev_is_reserved(&event)) {
+               if (!event_is_compatible(client, ev)) {
                        err = -EINVAL;
                        break;
                }
 
-               if (snd_seq_ev_is_variable(&event)) {
-                       int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK;
+               /* check for special events */
+               if (!snd_seq_ev_is_ump(ev)) {
+                       if (ev->type == SNDRV_SEQ_EVENT_NONE)
+                               goto __skip_event;
+                       else if (snd_seq_ev_is_reserved(ev)) {
+                               err = -EINVAL;
+                               break;
+                       }
+               }
+
+               if (snd_seq_ev_is_variable(ev)) {
+                       int extlen = ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
                        if ((size_t)(extlen + len) > count) {
                                /* back out, will get an error this time or next */
                                err = -EINVAL;
                                break;
                        }
                        /* set user space pointer */
-                       event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR;
-                       event.data.ext.ptr = (char __force *)buf
-                                               + sizeof(struct snd_seq_event);
+                       ev->data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR;
+                       ev->data.ext.ptr = (char __force *)buf + len;
                        len += extlen; /* increment data length */
                } else {
 #ifdef CONFIG_COMPAT
-                       if (client->convert32 && snd_seq_ev_is_varusr(&event)) {
-                               void *ptr = (void __force *)compat_ptr(event.data.raw32.d[1]);
-                               event.data.ext.ptr = ptr;
-                       }
+                       if (client->convert32 && snd_seq_ev_is_varusr(ev))
+                               ev->data.ext.ptr =
+                                       (void __force *)compat_ptr(ev->data.raw32.d[1]);
 #endif
                }
 
                /* ok, enqueue it */
-               err = snd_seq_client_enqueue_event(client, &event, file,
+               err = snd_seq_client_enqueue_event(client, ev, file,
                                                   !(file->f_flags & O_NONBLOCK),
                                                   0, 0, &client->ioctl_mutex);
                if (err < 0)
@@ -1146,6 +1188,7 @@ static void get_client_info(struct snd_seq_client *cptr,
        else
                info->card = -1;
 
+       info->midi_version = cptr->midi_version;
        memset(info->reserved, 0, sizeof(info->reserved));
 }
 
@@ -1180,12 +1223,19 @@ static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client,
        if (client->type != client_info->type)
                return -EINVAL;
 
+       /* check validity of midi_version field */
+       if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3) &&
+           client_info->midi_version > SNDRV_SEQ_CLIENT_UMP_MIDI_2_0)
+               return -EINVAL;
+
        /* fill the info fields */
        if (client_info->name[0])
                strscpy(client->name, client_info->name, sizeof(client->name));
 
        client->filter = client_info->filter;
        client->event_lost = client_info->event_lost;
+       if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3))
+               client->midi_version = client_info->midi_version;
        memcpy(client->event_filter, client_info->event_filter, 32);
 
        return 0;
@@ -2181,10 +2231,12 @@ int snd_seq_kernel_client_enqueue(int client, struct snd_seq_event *ev,
        if (snd_BUG_ON(!ev))
                return -EINVAL;
 
-       if (ev->type == SNDRV_SEQ_EVENT_NONE)
-               return 0; /* ignore this */
-       if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
-               return -EINVAL; /* quoted events can't be enqueued */
+       if (!snd_seq_ev_is_ump(ev)) {
+               if (ev->type == SNDRV_SEQ_EVENT_NONE)
+                       return 0; /* ignore this */
+               if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
+                       return -EINVAL; /* quoted events can't be enqueued */
+       }
 
        /* fill in client number */
        ev->source.client = client;
@@ -2376,6 +2428,19 @@ static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer,
        mutex_unlock(&client->ports_mutex);
 }
 
+static const char *midi_version_string(unsigned int version)
+{
+       switch (version) {
+       case SNDRV_SEQ_CLIENT_LEGACY_MIDI:
+               return "Legacy";
+       case SNDRV_SEQ_CLIENT_UMP_MIDI_1_0:
+               return "UMP MIDI1";
+       case SNDRV_SEQ_CLIENT_UMP_MIDI_2_0:
+               return "UMP MIDI2";
+       default:
+               return "Unknown";
+       }
+}
 
 /* exported to seq_info.c */
 void snd_seq_info_clients_read(struct snd_info_entry *entry, 
@@ -2400,9 +2465,10 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry,
                        continue;
                }
 
-               snd_iprintf(buffer, "Client %3d : \"%s\" [%s]\n",
+               snd_iprintf(buffer, "Client %3d : \"%s\" [%s %s]\n",
                            c, client->name,
-                           client->type == USER_CLIENT ? "User" : "Kernel");
+                           client->type == USER_CLIENT ? "User" : "Kernel",
+                           midi_version_string(client->midi_version));
                snd_seq_info_dump_ports(buffer, client);
                if (snd_seq_write_pool_allocated(client)) {
                        snd_iprintf(buffer, "  Output pool :\n");