CHROMIUM: virtwl: add dmabuf vfd support

This change supports the new dmabuf VFD type, which provides zero-copy
graphics output.

TEST=use linux_dmabuf protocol in sommelier
BUG=chromium:837209

Change-Id: Icdc1f7bca07be3766b123074d5f47311262fcfcd
Signed-off-by: David Reveman <reveman@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1030032
Reviewed-by: Zach Reizner <zachr@chromium.org>
diff --git a/drivers/virtio/virtio_wl.c b/drivers/virtio/virtio_wl.c
index 580b1c7..e14f84ed 100644
--- a/drivers/virtio/virtio_wl.c
+++ b/drivers/virtio/virtio_wl.c
@@ -111,6 +111,7 @@ static int virtwl_resp_err(unsigned int type)
 	switch (type) {
 	case VIRTIO_WL_RESP_OK:
 	case VIRTIO_WL_RESP_VFD_NEW:
+	case VIRTIO_WL_RESP_VFD_NEW_DMABUF:
 		return 0;
 	case VIRTIO_WL_RESP_ERR:
 		return -ENODEV; /* Device is no longer reliable */
@@ -875,8 +876,9 @@ static int virtwl_open(struct inode *inodep, struct file *filp)
 	return 0;
 }
 
-static struct virtwl_vfd *do_new(struct virtwl_info *vi, uint32_t type,
-				 uint32_t size, bool nonblock)
+static struct virtwl_vfd *do_new(struct virtwl_info *vi,
+				 struct virtwl_ioctl_new *ioctl_new,
+				 size_t ioctl_new_size, bool nonblock)
 {
 	struct virtio_wl_ctrl_vfd_new *ctrl_new;
 	struct virtwl_vfd *vfd;
@@ -885,10 +887,11 @@ static struct virtwl_vfd *do_new(struct virtwl_info *vi, uint32_t type,
 	struct scatterlist in_sg;
 	int ret = 0;
 
-	if (type != VIRTWL_IOCTL_NEW_CTX &&
-		type != VIRTWL_IOCTL_NEW_ALLOC &&
-		type != VIRTWL_IOCTL_NEW_PIPE_READ &&
-		type != VIRTWL_IOCTL_NEW_PIPE_WRITE)
+	if (ioctl_new->type != VIRTWL_IOCTL_NEW_CTX &&
+		ioctl_new->type != VIRTWL_IOCTL_NEW_ALLOC &&
+		ioctl_new->type != VIRTWL_IOCTL_NEW_PIPE_READ &&
+		ioctl_new->type != VIRTWL_IOCTL_NEW_PIPE_WRITE &&
+		ioctl_new->type != VIRTWL_IOCTL_NEW_DMABUF)
 		return ERR_PTR(-EINVAL);
 
 	ctrl_new = kzalloc(sizeof(*ctrl_new), GFP_KERNEL);
@@ -917,27 +920,33 @@ static struct virtwl_vfd *do_new(struct virtwl_info *vi, uint32_t type,
 	ret = 0;
 
 	ctrl_new->vfd_id = vfd->id;
-	switch (type) {
+	switch (ioctl_new->type) {
 	case VIRTWL_IOCTL_NEW_CTX:
 		ctrl_new->hdr.type = VIRTIO_WL_CMD_VFD_NEW_CTX;
 		ctrl_new->flags = VIRTIO_WL_VFD_WRITE | VIRTIO_WL_VFD_READ;
-		ctrl_new->size = 0;
 		break;
 	case VIRTWL_IOCTL_NEW_ALLOC:
 		ctrl_new->hdr.type = VIRTIO_WL_CMD_VFD_NEW;
-		ctrl_new->flags = 0;
-		ctrl_new->size = size;
+		ctrl_new->size = PAGE_ALIGN(ioctl_new->size);
 		break;
 	case VIRTWL_IOCTL_NEW_PIPE_READ:
 		ctrl_new->hdr.type = VIRTIO_WL_CMD_VFD_NEW_PIPE;
 		ctrl_new->flags = VIRTIO_WL_VFD_READ;
-		ctrl_new->size = 0;
 		break;
 	case VIRTWL_IOCTL_NEW_PIPE_WRITE:
 		ctrl_new->hdr.type = VIRTIO_WL_CMD_VFD_NEW_PIPE;
 		ctrl_new->flags = VIRTIO_WL_VFD_WRITE;
-		ctrl_new->size = 0;
 		break;
+	case VIRTWL_IOCTL_NEW_DMABUF:
+		/* Make sure ioctl_new contains enough data for NEW_DMABUF. */
+		if (ioctl_new_size == sizeof(*ioctl_new)) {
+			ctrl_new->hdr.type = VIRTIO_WL_CMD_VFD_NEW_DMABUF;
+			/* FIXME: convert from host byte order. */
+			memcpy(&ctrl_new->dmabuf, &ioctl_new->dmabuf,
+			       sizeof(ioctl_new->dmabuf));
+			break;
+		}
+		/* fall-through */
 	default:
 		ret = -EINVAL;
 		goto remove_vfd;
@@ -963,6 +972,12 @@ static struct virtwl_vfd *do_new(struct virtwl_info *vi, uint32_t type,
 
 	mutex_unlock(&vfd->lock);
 
+	if (ioctl_new->type == VIRTWL_IOCTL_NEW_DMABUF) {
+		/* FIXME: convert to host byte order. */
+		memcpy(&ioctl_new->dmabuf, &ctrl_new->dmabuf,
+		       sizeof(ctrl_new->dmabuf));
+	}
+
 	kfree(ctrl_new);
 	return vfd;
 
@@ -1073,26 +1088,25 @@ static long virtwl_vfd_ioctl(struct file *filp, unsigned int cmd,
 	}
 }
 
-static long virtwl_ioctl_new(struct file *filp, void __user *ptr)
+static long virtwl_ioctl_new(struct file *filp, void __user *ptr,
+			     size_t in_size)
 {
 	struct virtwl_info *vi = filp->private_data;
 	struct virtwl_vfd *vfd;
-	struct virtwl_ioctl_new ioctl_new;
+	struct virtwl_ioctl_new ioctl_new = {};
+	size_t size = min(in_size, sizeof(ioctl_new));
 	int ret;
 
 	/* Early check for user error. */
-	ret = !access_ok(VERIFY_WRITE, ptr, sizeof(struct virtwl_ioctl_new));
+	ret = !access_ok(VERIFY_WRITE, ptr, size);
 	if (ret)
 		return -EFAULT;
 
-	ret = copy_from_user(&ioctl_new, ptr, sizeof(struct virtwl_ioctl_new));
+	ret = copy_from_user(&ioctl_new, ptr, size);
 	if (ret)
 		return -EFAULT;
 
-	ioctl_new.size = PAGE_ALIGN(ioctl_new.size);
-
-	vfd = do_new(vi, ioctl_new.type, ioctl_new.size,
-		     filp->f_flags & O_NONBLOCK);
+	vfd = do_new(vi, &ioctl_new, size, filp->f_flags & O_NONBLOCK);
 	if (IS_ERR(vfd))
 		return PTR_ERR(vfd);
 
@@ -1104,7 +1118,7 @@ static long virtwl_ioctl_new(struct file *filp, void __user *ptr)
 	}
 
 	ioctl_new.fd = ret;
-	ret = copy_to_user(ptr, &ioctl_new, sizeof(struct virtwl_ioctl_new));
+	ret = copy_to_user(ptr, &ioctl_new, size);
 	if (ret) {
 		/* The release operation will handle freeing this alloc */
 		sys_close(ioctl_new.fd);
@@ -1120,9 +1134,9 @@ static long virtwl_ioctl_ptr(struct file *filp, unsigned int cmd,
 	if (filp->f_op == &virtwl_vfd_fops)
 		return virtwl_vfd_ioctl(filp, cmd, ptr);
 
-	switch (cmd) {
-	case VIRTWL_IOCTL_NEW:
-		return virtwl_ioctl_new(filp, ptr);
+	switch (_IOC_NR(cmd)) {
+	case _IOC_NR(VIRTWL_IOCTL_NEW):
+		return virtwl_ioctl_new(filp, ptr, _IOC_SIZE(cmd));
 	default:
 		return -ENOTTY;
 	}
diff --git a/include/uapi/linux/virtio_wl.h b/include/uapi/linux/virtio_wl.h
index 2a5a166..89e0e42 100644
--- a/include/uapi/linux/virtio_wl.h
+++ b/include/uapi/linux/virtio_wl.h
@@ -34,9 +34,11 @@ enum virtio_wl_ctrl_type {
 	VIRTIO_WL_CMD_VFD_NEW_CTX, /* virtio_wl_ctrl_vfd_new */
 	VIRTIO_WL_CMD_VFD_NEW_PIPE, /* virtio_wl_ctrl_vfd_new */
 	VIRTIO_WL_CMD_VFD_HUP, /* virtio_wl_ctrl_vfd */
+	VIRTIO_WL_CMD_VFD_NEW_DMABUF, /* virtio_wl_ctrl_vfd_new */
 
 	VIRTIO_WL_RESP_OK = 0x1000,
 	VIRTIO_WL_RESP_VFD_NEW = 0x1001, /* virtio_wl_ctrl_vfd_new */
+	VIRTIO_WL_RESP_VFD_NEW_DMABUF = 0x1002, /* virtio_wl_ctrl_vfd_new */
 
 	VIRTIO_WL_RESP_ERR = 0x1100,
 	VIRTIO_WL_RESP_OUT_OF_MEMORY,
@@ -73,7 +75,19 @@ struct virtio_wl_ctrl_vfd_new {
 	__le32 vfd_id; /* MSB indicates device allocated vfd */
 	__le32 flags; /* virtio_wl_vfd_flags */
 	__le64 pfn; /* first guest physical page frame number if VFD_MAP */
-	__le32 size; /* size in bytes if VIRTIO_WL_VFD_MAP */
+	__le32 size; /* size in bytes if VIRTIO_WL_CMD_VFD_NEW* */
+	/* buffer description if VIRTIO_WL_CMD_VFD_NEW_DMABUF */
+	struct {
+		__le32 width; /* width in pixels */
+		__le32 height; /* height in pixels */
+		__le32 format; /* fourcc format */
+		__le32 stride0; /* return stride0 */
+		__le32 stride1; /* return stride1 */
+		__le32 stride2; /* return stride2 */
+		__le32 offset0; /* return offset0 */
+		__le32 offset1; /* return offset1 */
+		__le32 offset2; /* return offset2 */
+	} dmabuf;
 };
 
 struct virtio_wl_ctrl_vfd_send {
diff --git a/include/uapi/linux/virtwl.h b/include/uapi/linux/virtwl.h
index 617c7ca..b9c0ed7 100644
--- a/include/uapi/linux/virtwl.h
+++ b/include/uapi/linux/virtwl.h
@@ -19,13 +19,30 @@ enum virtwl_ioctl_new_type {
 	VIRTWL_IOCTL_NEW_PIPE_READ,
 	/* create a new virtwl pipe that is writable via the returned fd */
 	VIRTWL_IOCTL_NEW_PIPE_WRITE,
+	/* create a new virtwl dmabuf that is writable via the returned fd */
+	VIRTWL_IOCTL_NEW_DMABUF,
 };
 
 struct virtwl_ioctl_new {
 	__u32 type; /* VIRTWL_IOCTL_NEW_* */
 	int fd; /* return fd */
 	__u32 flags; /* currently always 0 */
-	__u32 size; /* size of allocation if type == VIRTWL_IOCTL_NEW_ALLOC */
+	union {
+		/* size of allocation if type == VIRTWL_IOCTL_NEW_ALLOC */
+		__u32 size;
+		/* buffer description if type == VIRTWL_IOCTL_NEW_DMABUF */
+		struct {
+			__u32 width; /* width in pixels */
+			__u32 height; /* height in pixels */
+			__u32 format; /* fourcc format */
+			__u32 stride0; /* return stride0 */
+			__u32 stride1; /* return stride1 */
+			__u32 stride2; /* return stride2 */
+			__u32 offset0; /* return offset0 */
+			__u32 offset1; /* return offset1 */
+			__u32 offset2; /* return offset2 */
+		} dmabuf;
+	};
 };
 
 struct virtwl_ioctl_txn {