staging: vchiq_arm: Add compatibility wrappers for ioctls
authorMichael Zoran <mzoran@crowfest.net>
Wed, 8 Mar 2017 03:23:35 +0000 (19:23 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 9 Mar 2017 12:39:05 +0000 (13:39 +0100)
This patch adds compatibility wrappers for the ioctls
exposed by vchiq/vc04_services.  The compat ioctls are
completely implemented on top of the native ioctls.  No
existing lines are modified.

While the ideal approach would be to cleanup the existing
code, this path is simplier and easier to review. While
it does have a small runtime performance penality vs
seperating the existing code into wrapper+worker functions,
the penality is small since only the metadata is copied
back onto the 32 bit user mode stack.

The on top of approach is the approach used by several
existing performance critical subsystems of Linux such
as the DRM 3D graphics subsystem.

Testing:

1. A 32 bit chroot was created on a RPI 3 and vchiq_test
was built for armhf.  The usual tests were run such as
vchiq_test -f 10 and vchiq_test -p.

2. This patch was copied onto the shipping version of
the Linux kernel used for the RPI and that kernel was
built for arm64. That kernel was used to boot Raspbian.
Many of the builtin features are now functional such
as the "hello_pi" examples, and minecraft_pi.

Signed-off-by: Michael Zoran <mzoran@crowfest.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c

index cc4cb168f4837b696f1893cf124f012900788493..ca6ab47fba6fa39168c9c81310ef1801a170ddf8 100644 (file)
@@ -48,6 +48,7 @@
 #include <linux/list.h>
 #include <linux/of.h>
 #include <linux/platform_device.h>
+#include <linux/compat.h>
 #include <soc/bcm2835/raspberrypi-firmware.h>
 
 #include "vchiq_core.h"
@@ -1228,6 +1229,485 @@ vchiq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
        return ret;
 }
 
+#if defined(CONFIG_COMPAT)
+
+struct vchiq_service_params32 {
+       int fourcc;
+       compat_uptr_t callback;
+       compat_uptr_t userdata;
+       short version; /* Increment for non-trivial changes */
+       short version_min; /* Update for incompatible changes */
+};
+
+struct vchiq_create_service32 {
+       struct vchiq_service_params32 params;
+       int is_open;
+       int is_vchi;
+       unsigned int handle; /* OUT */
+};
+
+#define VCHIQ_IOC_CREATE_SERVICE32 \
+       _IOWR(VCHIQ_IOC_MAGIC, 2, struct vchiq_create_service32)
+
+static long
+vchiq_compat_ioctl_create_service(
+       struct file *file,
+       unsigned int cmd,
+       unsigned long arg)
+{
+       VCHIQ_CREATE_SERVICE_T __user *args;
+       struct vchiq_create_service32 __user *ptrargs32 =
+               (struct vchiq_create_service32 __user *)arg;
+       struct vchiq_create_service32 args32;
+       long ret;
+
+       args = compat_alloc_user_space(sizeof(*args));
+       if (!args)
+               return -EFAULT;
+
+       if (copy_from_user(&args32,
+                          (struct vchiq_create_service32 __user *)arg,
+                          sizeof(args32)))
+               return -EFAULT;
+
+       if (put_user(args32.params.fourcc, &args->params.fourcc) ||
+           put_user(compat_ptr(args32.params.callback),
+                    &args->params.callback) ||
+           put_user(compat_ptr(args32.params.userdata),
+                    &args->params.userdata) ||
+           put_user(args32.params.version, &args->params.version) ||
+           put_user(args32.params.version_min,
+                    &args->params.version_min) ||
+           put_user(args32.is_open, &args->is_open) ||
+           put_user(args32.is_vchi, &args->is_vchi) ||
+           put_user(args32.handle, &args->handle))
+               return -EFAULT;
+
+       ret = vchiq_ioctl(file, VCHIQ_IOC_CREATE_SERVICE, (unsigned long)args);
+
+       if (ret < 0)
+               return ret;
+
+       if (get_user(args32.handle, &args->handle))
+               return -EFAULT;
+
+       if (copy_to_user(&ptrargs32->handle,
+                        &args32.handle,
+                        sizeof(args32.handle)))
+               return -EFAULT;
+
+       return 0;
+}
+
+struct vchiq_element32 {
+       compat_uptr_t data;
+       unsigned int size;
+};
+
+struct vchiq_queue_message32 {
+       unsigned int handle;
+       unsigned int count;
+       compat_uptr_t elements;
+};
+
+#define VCHIQ_IOC_QUEUE_MESSAGE32 \
+       _IOW(VCHIQ_IOC_MAGIC,  4, struct vchiq_queue_message32)
+
+static long
+vchiq_compat_ioctl_queue_message(struct file *file,
+                                unsigned int cmd,
+                                unsigned long arg)
+{
+       VCHIQ_QUEUE_MESSAGE_T *args;
+       VCHIQ_ELEMENT_T *elements;
+       struct vchiq_queue_message32 args32;
+       unsigned int count;
+
+       if (copy_from_user(&args32,
+                          (struct vchiq_queue_message32 __user *)arg,
+                          sizeof(args32)))
+               return -EFAULT;
+
+       args = compat_alloc_user_space(sizeof(*args) +
+                                      (sizeof(*elements) * MAX_ELEMENTS));
+
+       if (!args)
+               return -EFAULT;
+
+       if (put_user(args32.handle, &args->handle) ||
+           put_user(args32.count, &args->count) ||
+           put_user(compat_ptr(args32.elements), &args->elements))
+               return -EFAULT;
+
+       if (args32.count > MAX_ELEMENTS)
+               return -EINVAL;
+
+       if (args32.elements && args32.count) {
+               struct vchiq_element32 tempelement32[MAX_ELEMENTS];
+
+               elements = (VCHIQ_ELEMENT_T __user *)(args + 1);
+
+               if (copy_from_user(&tempelement32,
+                                  compat_ptr(args32.elements),
+                                  sizeof(tempelement32)))
+                       return -EFAULT;
+
+               for (count = 0; count < args32.count; count++) {
+                       if (put_user(compat_ptr(tempelement32[count].data),
+                                    &elements[count].data) ||
+                           put_user(tempelement32[count].size,
+                                    &elements[count].size))
+                               return -EFAULT;
+               }
+
+               if (put_user(elements, &args->elements))
+                       return -EFAULT;
+       }
+
+       return vchiq_ioctl(file, VCHIQ_IOC_QUEUE_MESSAGE, (unsigned long)args);
+}
+
+struct vchiq_queue_bulk_transfer32 {
+       unsigned int handle;
+       compat_uptr_t data;
+       unsigned int size;
+       compat_uptr_t userdata;
+       VCHIQ_BULK_MODE_T mode;
+};
+
+#define VCHIQ_IOC_QUEUE_BULK_TRANSMIT32 \
+       _IOWR(VCHIQ_IOC_MAGIC, 5, struct vchiq_queue_bulk_transfer32)
+#define VCHIQ_IOC_QUEUE_BULK_RECEIVE32 \
+       _IOWR(VCHIQ_IOC_MAGIC, 6, struct vchiq_queue_bulk_transfer32)
+
+static long
+vchiq_compat_ioctl_queue_bulk(struct file *file,
+                             unsigned int cmd,
+                             unsigned long arg)
+{
+       VCHIQ_QUEUE_BULK_TRANSFER_T *args;
+       struct vchiq_queue_bulk_transfer32 args32;
+       struct vchiq_queue_bulk_transfer32 *ptrargs32 =
+               (struct vchiq_queue_bulk_transfer32 *)arg;
+       long ret;
+
+       args = compat_alloc_user_space(sizeof(*args));
+       if (!args)
+               return -EFAULT;
+
+       if (copy_from_user(&args32,
+                          (struct vchiq_queue_bulk_transfer32 __user *)arg,
+                          sizeof(args32)))
+               return -EFAULT;
+
+       if (put_user(args32.handle, &args->handle) ||
+           put_user(compat_ptr(args32.data), &args->data) ||
+           put_user(args32.size, &args->size) ||
+           put_user(compat_ptr(args32.userdata), &args->userdata) ||
+           put_user(args32.mode, &args->mode))
+               return -EFAULT;
+
+       if (cmd == VCHIQ_IOC_QUEUE_BULK_TRANSMIT32)
+               cmd = VCHIQ_IOC_QUEUE_BULK_TRANSMIT;
+       else
+               cmd = VCHIQ_IOC_QUEUE_BULK_RECEIVE;
+
+       ret = vchiq_ioctl(file, cmd, (unsigned long)args);
+
+       if (ret < 0)
+               return ret;
+
+       if (get_user(args32.mode, &args->mode))
+               return -EFAULT;
+
+       if (copy_to_user(&ptrargs32->mode,
+                        &args32.mode,
+                        sizeof(args32.mode)))
+               return -EFAULT;
+
+       return 0;
+}
+
+struct vchiq_completion_data32 {
+       VCHIQ_REASON_T reason;
+       compat_uptr_t header;
+       compat_uptr_t service_userdata;
+       compat_uptr_t bulk_userdata;
+};
+
+struct vchiq_await_completion32 {
+       unsigned int count;
+       compat_uptr_t buf;
+       unsigned int msgbufsize;
+       unsigned int msgbufcount; /* IN/OUT */
+       compat_uptr_t msgbufs;
+};
+
+#define VCHIQ_IOC_AWAIT_COMPLETION32 \
+       _IOWR(VCHIQ_IOC_MAGIC, 7, struct vchiq_await_completion32)
+
+static long
+vchiq_compat_ioctl_await_completion(struct file *file,
+                                   unsigned int cmd,
+                                   unsigned long arg)
+{
+       VCHIQ_AWAIT_COMPLETION_T *args;
+       VCHIQ_COMPLETION_DATA_T *completion;
+       VCHIQ_COMPLETION_DATA_T completiontemp;
+       struct vchiq_await_completion32 args32;
+       struct vchiq_completion_data32 completion32;
+       unsigned int *msgbufcount32;
+       compat_uptr_t msgbuf32;
+       void *msgbuf;
+       void **msgbufptr;
+       long ret;
+
+       args = compat_alloc_user_space(sizeof(*args) +
+                                      sizeof(*completion) +
+                                      sizeof(*msgbufptr));
+       if (!args)
+               return -EFAULT;
+
+       completion = (VCHIQ_COMPLETION_DATA_T *)(args + 1);
+       msgbufptr = (void __user **)(completion + 1);
+
+       if (copy_from_user(&args32,
+                          (struct vchiq_completion_data32 *)arg,
+                          sizeof(args32)))
+               return -EFAULT;
+
+       if (put_user(args32.count, &args->count) ||
+           put_user(compat_ptr(args32.buf), &args->buf) ||
+           put_user(args32.msgbufsize, &args->msgbufsize) ||
+           put_user(args32.msgbufcount, &args->msgbufcount) ||
+           put_user(compat_ptr(args32.msgbufs), &args->msgbufs))
+               return -EFAULT;
+
+       /* These are simple cases, so just fall into the native handler */
+       if (!args32.count || !args32.buf || !args32.msgbufcount)
+               return vchiq_ioctl(file,
+                                  VCHIQ_IOC_AWAIT_COMPLETION,
+                                  (unsigned long)args);
+
+       /*
+        * These are the more complex cases.  Typical applications of this
+        * ioctl will use a very large count, with a very large msgbufcount.
+        * Since the native ioctl can asynchronously fill in the returned
+        * buffers and the application can in theory begin processing messages
+        * even before the ioctl returns, a bit of a trick is used here.
+        *
+        * By forcing both count and msgbufcount to be 1, it forces the native
+        * ioctl to only claim at most 1 message is available.   This tricks
+        * the calling application into thinking only 1 message was actually
+        * available in the queue so like all good applications it will retry
+        * waiting until all the required messages are received.
+        *
+        * This trick has been tested and proven to work with vchiq_test,
+        * Minecraft_PI, the "hello pi" examples, and various other
+        * applications that are included in Raspbian.
+        */
+
+       if (copy_from_user(&msgbuf32,
+                          compat_ptr(args32.msgbufs) +
+                          (sizeof(compat_uptr_t) *
+                          (args32.msgbufcount - 1)),
+                          sizeof(msgbuf32)))
+               return -EFAULT;
+
+       msgbuf = compat_ptr(msgbuf32);
+
+       if (copy_to_user(msgbufptr,
+                        &msgbuf,
+                        sizeof(msgbuf)))
+               return -EFAULT;
+
+       if (copy_to_user(&args->msgbufs,
+                        &msgbufptr,
+                        sizeof(msgbufptr)))
+               return -EFAULT;
+
+       if (put_user(1U, &args->count) ||
+           put_user(completion, &args->buf) ||
+           put_user(1U, &args->msgbufcount))
+               return -EFAULT;
+
+       ret = vchiq_ioctl(file,
+                         VCHIQ_IOC_AWAIT_COMPLETION,
+                         (unsigned long)args);
+
+       /*
+        * An return value of 0 here means that no messages where available
+        * in the message queue.  In this case the native ioctl does not
+        * return any data to the application at all.  Not even to update
+        * msgbufcount.  This functionality needs to be kept here for
+        * compatibility.
+        *
+        * Of course, < 0 means that an error occurred and no data is being
+        * returned.
+        *
+        * Since count and msgbufcount was forced to 1, that means
+        * the only other possible return value is 1. Meaning that 1 message
+        * was available, so that multiple message case does not need to be
+        * handled here.
+        */
+       if (ret <= 0)
+               return ret;
+
+       if (copy_from_user(&completiontemp, completion, sizeof(*completion)))
+               return -EFAULT;
+
+       completion32.reason = completiontemp.reason;
+       completion32.header = ptr_to_compat(completiontemp.header);
+       completion32.service_userdata =
+               ptr_to_compat(completiontemp.service_userdata);
+       completion32.bulk_userdata =
+               ptr_to_compat(completiontemp.bulk_userdata);
+
+       if (copy_to_user(compat_ptr(args32.buf),
+                        &completion32,
+                        sizeof(completion32)))
+               return -EFAULT;
+
+       args32.msgbufcount--;
+
+       msgbufcount32 =
+               &((struct vchiq_await_completion32 __user *)arg)->msgbufcount;
+
+       if (copy_to_user(msgbufcount32,
+                        &args32.msgbufcount,
+                        sizeof(args32.msgbufcount)))
+               return -EFAULT;
+
+       return 1;
+}
+
+struct vchiq_dequeue_message32 {
+       unsigned int handle;
+       int blocking;
+       unsigned int bufsize;
+       compat_uptr_t buf;
+};
+
+#define VCHIQ_IOC_DEQUEUE_MESSAGE32 \
+       _IOWR(VCHIQ_IOC_MAGIC, 8, struct vchiq_dequeue_message32)
+
+static long
+vchiq_compat_ioctl_dequeue_message(struct file *file,
+                                  unsigned int cmd,
+                                  unsigned long arg)
+{
+       VCHIQ_DEQUEUE_MESSAGE_T *args;
+       struct vchiq_dequeue_message32 args32;
+
+       args = compat_alloc_user_space(sizeof(*args));
+       if (!args)
+               return -EFAULT;
+
+       if (copy_from_user(&args32,
+                          (struct vchiq_dequeue_message32 *)arg,
+                          sizeof(args32)))
+               return -EFAULT;
+
+       if (put_user(args32.handle, &args->handle) ||
+           put_user(args32.blocking, &args->blocking) ||
+           put_user(args32.bufsize, &args->bufsize) ||
+           put_user(compat_ptr(args32.buf), &args->buf))
+               return -EFAULT;
+
+       return vchiq_ioctl(file, VCHIQ_IOC_DEQUEUE_MESSAGE,
+                          (unsigned long)args);
+}
+
+struct vchiq_get_config32 {
+       unsigned int config_size;
+       compat_uptr_t pconfig;
+};
+
+#define VCHIQ_IOC_GET_CONFIG32 \
+       _IOWR(VCHIQ_IOC_MAGIC, 10, struct vchiq_get_config32)
+
+static long
+vchiq_compat_ioctl_get_config(struct file *file,
+                             unsigned int cmd,
+                             unsigned long arg)
+{
+       VCHIQ_GET_CONFIG_T *args;
+       struct vchiq_get_config32 args32;
+
+       args = compat_alloc_user_space(sizeof(*args));
+       if (!args)
+               return -EFAULT;
+
+       if (copy_from_user(&args32,
+                          (struct vchiq_get_config32 *)arg,
+                          sizeof(args32)))
+               return -EFAULT;
+
+       if (put_user(args32.config_size, &args->config_size) ||
+           put_user(compat_ptr(args32.pconfig), &args->pconfig))
+               return -EFAULT;
+
+       return vchiq_ioctl(file, VCHIQ_IOC_GET_CONFIG, (unsigned long)args);
+}
+
+struct vchiq_dump_mem32 {
+       compat_uptr_t virt_addr;
+       u32 num_bytes;
+};
+
+#define VCHIQ_IOC_DUMP_PHYS_MEM32 \
+       _IOW(VCHIQ_IOC_MAGIC, 15, struct vchiq_dump_mem32)
+
+static long
+vchiq_compat_ioctl_dump_phys_mem(struct file *file,
+                                unsigned int cmd,
+                                unsigned long arg)
+{
+       VCHIQ_DUMP_MEM_T *args;
+       struct vchiq_dump_mem32 args32;
+
+       args = compat_alloc_user_space(sizeof(*args));
+       if (!args)
+               return -EFAULT;
+
+       if (copy_from_user(&args32,
+                          (struct vchiq_dump_mem32 *)arg,
+                          sizeof(args32)))
+               return -EFAULT;
+
+       if (put_user(compat_ptr(args32.virt_addr), &args->virt_addr) ||
+           put_user(args32.num_bytes, &args->num_bytes))
+               return -EFAULT;
+
+       return vchiq_ioctl(file, VCHIQ_IOC_DUMP_PHYS_MEM, (unsigned long)args);
+}
+
+static long
+vchiq_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+       switch (cmd) {
+       case VCHIQ_IOC_CREATE_SERVICE32:
+               return vchiq_compat_ioctl_create_service(file, cmd, arg);
+       case VCHIQ_IOC_QUEUE_MESSAGE32:
+               return vchiq_compat_ioctl_queue_message(file, cmd, arg);
+       case VCHIQ_IOC_QUEUE_BULK_TRANSMIT32:
+       case VCHIQ_IOC_QUEUE_BULK_RECEIVE32:
+               return vchiq_compat_ioctl_queue_bulk(file, cmd, arg);
+       case VCHIQ_IOC_AWAIT_COMPLETION32:
+               return vchiq_compat_ioctl_await_completion(file, cmd, arg);
+       case VCHIQ_IOC_DEQUEUE_MESSAGE32:
+               return vchiq_compat_ioctl_dequeue_message(file, cmd, arg);
+       case VCHIQ_IOC_GET_CONFIG32:
+               return vchiq_compat_ioctl_get_config(file, cmd, arg);
+       case VCHIQ_IOC_DUMP_PHYS_MEM32:
+               return vchiq_compat_ioctl_dump_phys_mem(file, cmd, arg);
+       default:
+               return vchiq_ioctl(file, cmd, arg);
+       }
+}
+
+#endif
+
 /****************************************************************************
 *
 *   vchiq_open
@@ -1688,6 +2168,9 @@ static const struct file_operations
 vchiq_fops = {
        .owner = THIS_MODULE,
        .unlocked_ioctl = vchiq_ioctl,
+#if defined(CONFIG_COMPAT)
+       .compat_ioctl = vchiq_compat_ioctl,
+#endif
        .open = vchiq_open,
        .release = vchiq_release,
        .read = vchiq_read