ALSA: seq: add an alternative way to handle ioctl requests
[cascardo/linux.git] / sound / core / seq / seq_clientmgr.c
index 37590f8..cf37003 100644 (file)
@@ -2168,6 +2168,13 @@ static int snd_seq_ioctl_query_next_port(struct snd_seq_client *client,
 
 /* -------------------------------------------------------- */
 
+static const struct ioctl_handler {
+       unsigned int cmd;
+       int (*func)(struct snd_seq_client *client, void *arg);
+} ioctl_handlers[] = {
+       { 0, NULL },
+};
+
 static struct seq_ioctl_table {
        unsigned int cmd;
        int (*func)(struct snd_seq_client *client, void __user * arg);
@@ -2204,6 +2211,63 @@ static struct seq_ioctl_table {
        { 0, NULL },
 };
 
+static long seq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+       struct snd_seq_client *client = file->private_data;
+       /* To use kernel stack for ioctl data. */
+       union ioctl_arg {
+               int pversion;
+               int client_id;
+               struct snd_seq_system_info      system_info;
+               struct snd_seq_running_info     running_info;
+               struct snd_seq_client_info      client_info;
+               struct snd_seq_port_info        port_info;
+               struct snd_seq_port_subscribe   port_subscribe;
+               struct snd_seq_queue_info       queue_info;
+               struct snd_seq_queue_status     queue_status;
+               struct snd_seq_queue_tempo      tempo;
+               struct snd_seq_queue_timer      queue_timer;
+               struct snd_seq_queue_client     queue_client;
+               struct snd_seq_client_pool      client_pool;
+               struct snd_seq_remove_events    remove_events;
+               struct snd_seq_query_subs       query_subs;
+       } buf = {0};
+       const struct ioctl_handler *handler;
+       unsigned long size;
+       int err;
+
+       if (snd_BUG_ON(!client))
+               return -ENXIO;
+
+       for (handler = ioctl_handlers; handler->cmd > 0; ++handler) {
+               if (handler->cmd == cmd)
+                       break;
+       }
+       if (handler->cmd == 0)
+               return -ENOTTY;
+       /*
+        * All of ioctl commands for ALSA sequencer get an argument of size
+        * within 13 bits. We can safely pick up the size from the command.
+        */
+       size = _IOC_SIZE(handler->cmd);
+       if (_IOC_DIR(handler->cmd) & IOC_IN) {
+               if (copy_from_user(&buf, (const void __user *)arg, size))
+                       return -EFAULT;
+       }
+
+       err = handler->func(client, &buf);
+       if (err >= 0) {
+               /* Some commands includes a bug in 'dir' field. */
+               if (handler->cmd == SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT ||
+                   handler->cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_POOL ||
+                   (_IOC_DIR(handler->cmd) & IOC_OUT))
+                       if (copy_to_user((void __user *)arg, &buf, size))
+                               return -EFAULT;
+       }
+
+       return err;
+}
+
 static int snd_seq_do_ioctl(struct snd_seq_client *client, unsigned int cmd,
                            void __user *arg)
 {
@@ -2234,9 +2298,12 @@ static long snd_seq_ioctl(struct file *file, unsigned int cmd, unsigned long arg
 {
        struct snd_seq_client *client = file->private_data;
 
+       if (seq_ioctl(file, cmd, arg) >= 0)
+               return 0;
+
        if (snd_BUG_ON(!client))
                return -ENXIO;
-               
+
        return snd_seq_do_ioctl(client, cmd, (void __user *) arg);
 }
 
@@ -2437,6 +2504,7 @@ EXPORT_SYMBOL(snd_seq_kernel_client_dispatch);
  */
 int snd_seq_kernel_client_ctl(int clientid, unsigned int cmd, void *arg)
 {
+       const struct ioctl_handler *handler;
        struct snd_seq_client *client;
        mm_segment_t fs;
        int result;
@@ -2444,6 +2512,12 @@ int snd_seq_kernel_client_ctl(int clientid, unsigned int cmd, void *arg)
        client = clientptr(clientid);
        if (client == NULL)
                return -ENXIO;
+
+       for (handler = ioctl_handlers; handler->cmd > 0; ++handler) {
+               if (handler->cmd == cmd)
+                       return handler->func(client, arg);
+       }
+
        fs = snd_enter_user();
        result = snd_seq_do_ioctl(client, cmd, (void __force __user *)arg);
        snd_leave_user(fs);