CHROMIUM: drm/evdi: add 32-bit ioctl compatibility routines.

Module drm/evdi defines several ioctl structs containing user pointers. These
require special handling for 32-bit ioctl compatibility.

BUG=chrome-os-partner:59063
TEST=connect elm to DL3 docking station with HDMI TV

Change-Id: I8dca1f1ea851bc11a4b24192d5959a7aab03e4b8
Signed-off-by: Haixia Shi <hshi@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/406013
Reviewed-by: Daniel Kurtz <djkurtz@chromium.org>
(cherry picked from commit a84fb131cefe054ba942f21cd068791d4e736569)
Reviewed-on: https://chromium-review.googlesource.com/407123
Reviewed-by: Stéphane Marchesin <marcheu@chromium.org>
diff --git a/drivers/gpu/drm/evdi/Makefile b/drivers/gpu/drm/evdi/Makefile
index 411cab0..d1a7044 100644
--- a/drivers/gpu/drm/evdi/Makefile
+++ b/drivers/gpu/drm/evdi/Makefile
@@ -9,5 +9,7 @@
 
 evdi-y := evdi_drv.o evdi_modeset.o evdi_connector.o evdi_encoder.o evdi_main.o evdi_fb.o evdi_gem.o evdi_stats.o evdi_painter.o evdi_debug.o evdi_cursor.o
 
+evdi-$(CONFIG_COMPAT) += evdi_ioc32.o
+
 obj-$(CONFIG_DRM_EVDI) := evdi.o
 
diff --git a/drivers/gpu/drm/evdi/evdi_drv.c b/drivers/gpu/drm/evdi/evdi_drv.c
index 88b553a..c0bb8e9 100644
--- a/drivers/gpu/drm/evdi/evdi_drv.c
+++ b/drivers/gpu/drm/evdi/evdi_drv.c
@@ -55,7 +55,7 @@
 	.unlocked_ioctl = drm_ioctl,
 	.release = drm_release,
 #ifdef CONFIG_COMPAT
-	.compat_ioctl = drm_compat_ioctl,
+	.compat_ioctl = evdi_compat_ioctl,
 #endif
 	.llseek = noop_llseek,
 };
diff --git a/drivers/gpu/drm/evdi/evdi_drv.h b/drivers/gpu/drm/evdi/evdi_drv.h
index 547a902..607aeb6 100644
--- a/drivers/gpu/drm/evdi/evdi_drv.h
+++ b/drivers/gpu/drm/evdi/evdi_drv.h
@@ -77,6 +77,10 @@
 int evdi_driver_unload(struct drm_device *dev);
 void evdi_driver_preclose(struct drm_device *dev, struct drm_file *file_priv);
 
+#ifdef CONFIG_COMPAT
+long evdi_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
+#endif
+
 int evdi_fbdev_init(struct drm_device *dev);
 void evdi_fbdev_cleanup(struct drm_device *dev);
 void evdi_fbdev_unplug(struct drm_device *dev);
diff --git a/drivers/gpu/drm/evdi/evdi_ioc32.c b/drivers/gpu/drm/evdi/evdi_ioc32.c
new file mode 100644
index 0000000..1c6a4c4
--- /dev/null
+++ b/drivers/gpu/drm/evdi/evdi_ioc32.c
@@ -0,0 +1,125 @@
+/**
+ * evdi_ioc32.c
+ *
+ * Copyright (c) 2016 The Chromium OS Authors
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/compat.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_edid.h>
+#include <drm/evdi_drm.h>
+
+#include "evdi_drv.h"
+
+struct drm_evdi_connect32 {
+	int32_t connected;
+	int32_t dev_index;
+	uint32_t edid_ptr32;
+	uint32_t edid_length;
+};
+
+struct drm_evdi_grabpix32 {
+	uint32_t mode;
+	int32_t buf_width;
+	int32_t buf_height;
+	int32_t buf_byte_stride;
+	uint32_t buffer_ptr32;
+	int32_t num_rects;
+	uint32_t rects_ptr32;
+};
+
+static int compat_evdi_connect(struct file *file, unsigned int cmd,
+				unsigned long arg)
+{
+	struct drm_evdi_connect32 req32;
+	struct drm_evdi_connect __user *request;
+
+	if (copy_from_user(&req32, (void __user *)arg, sizeof(req32)))
+		return -EFAULT;
+
+	request = compat_alloc_user_space(sizeof(*request));
+	if (!access_ok(VERIFY_WRITE, request, sizeof(*request))
+	    || __put_user(req32.connected, &request->connected)
+	    || __put_user(req32.dev_index, &request->dev_index)
+	    || __put_user((void __user *)(unsigned long)req32.edid_ptr32,
+			  &request->edid)
+	    || __put_user(req32.edid_length, &request->edid_length))
+		return -EFAULT;
+
+	return drm_ioctl(file, DRM_IOCTL_EVDI_CONNECT,
+			 (unsigned long)request);
+}
+
+static int compat_evdi_grabpix(struct file *file, unsigned int cmd,
+				unsigned long arg)
+{
+	struct drm_evdi_grabpix32 req32;
+	struct drm_evdi_grabpix __user *request;
+
+	if (copy_from_user(&req32, (void __user *)arg, sizeof(req32)))
+		return -EFAULT;
+
+	request = compat_alloc_user_space(sizeof(*request));
+	if (!access_ok(VERIFY_WRITE, request, sizeof(*request))
+	    || __put_user(req32.mode, &request->mode)
+	    || __put_user(req32.buf_width, &request->buf_width)
+	    || __put_user(req32.buf_height, &request->buf_height)
+	    || __put_user(req32.buf_byte_stride, &request->buf_byte_stride)
+	    || __put_user((void __user *)(unsigned long)req32.buffer_ptr32,
+			  &request->buffer)
+	    || __put_user(req32.num_rects, &request->num_rects)
+	    || __put_user((void __user *)(unsigned long)req32.rects_ptr32,
+			  &request->rects))
+		return -EFAULT;
+
+	return drm_ioctl(file, DRM_IOCTL_EVDI_GRABPIX,
+			 (unsigned long)request);
+}
+
+static drm_ioctl_compat_t *evdi_compat_ioctls[] = {
+	[DRM_EVDI_CONNECT] = compat_evdi_connect,
+	[DRM_EVDI_GRABPIX] = compat_evdi_grabpix,
+};
+
+/**
+ * Called whenever a 32-bit process running under a 64-bit kernel
+ * performs an ioctl on /dev/dri/card<n>.
+ *
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument.
+ * \return zero on success or negative number on failure.
+ */
+long evdi_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	unsigned int nr = DRM_IOCTL_NR(cmd);
+	drm_ioctl_compat_t *fn = NULL;
+	int ret;
+
+	if (nr < DRM_COMMAND_BASE || nr >= DRM_COMMAND_END)
+		return drm_compat_ioctl(filp, cmd, arg);
+
+	if (nr < DRM_COMMAND_BASE + ARRAY_SIZE(evdi_compat_ioctls))
+		fn = evdi_compat_ioctls[nr - DRM_COMMAND_BASE];
+
+	if (fn != NULL)
+		ret = (*fn) (filp, cmd, arg);
+	else
+		ret = drm_ioctl(filp, cmd, arg);
+
+	return ret;
+}