CHROMIUM: virtwl: fix deadlock while receiving VFDs

TEST=insert 10ms delay after vq_handle_recv's first lock;
     deadlocks will happen before this change but not after
BUG=chromium:780395

Change-Id: I8e6237f7d3a9c5ef8fa105411fb8d69744f9011a
Signed-off-by: Zach Reizner <zachr@google.com>
Reviewed-on: https://chromium-review.googlesource.com/756136
Commit-Ready: Zach Reizner <zachr@chromium.org>
Tested-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Stephen Barber <smbarber@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/780815
Commit-Ready: Dylan Reid <dgreid@chromium.org>
Tested-by: Dylan Reid <dgreid@chromium.org>
Reviewed-by: Zach Reizner <zachr@chromium.org>
diff --git a/drivers/virtio/virtio_wl.c b/drivers/virtio/virtio_wl.c
index 0ed18556..df9f190 100644
--- a/drivers/virtio/virtio_wl.c
+++ b/drivers/virtio/virtio_wl.c
@@ -487,6 +487,7 @@
 	return read_count;
 }
 
+/* must hold both vfd->lock and vi->vfds_lock */
 static size_t vfd_out_vfds_locked(struct virtwl_vfd *vfd,
 				  struct virtwl_vfd **vfds, size_t count)
 {
@@ -515,17 +516,7 @@
 
 		for (i = 0; i < vfds_to_read; i++) {
 			uint32_t vfd_id = le32_to_cpu(vfds_le[i]);
-			/*
-			 * This is an inversion of the typical locking order
-			 * (vi->vfds_lock before vfd->lock). The reason this is
-			 * safe from deadlocks is because the lock held as a
-			 * precondition of this function call is always for a
-			 * different vfd than the one received on this vfd's
-			 * queue.
-			 */
-			mutex_lock(&vi->vfds_lock);
 			vfds[read_count] = idr_find(&vi->vfds, vfd_id);
-			mutex_unlock(&vi->vfds_lock);
 			if (vfds[read_count]) {
 				read_count++;
 			} else {
@@ -586,14 +577,17 @@
 			       size_t *vfd_count)
 {
 	struct virtwl_vfd *vfd = filp->private_data;
+	struct virtwl_info *vi = vfd->vi;
 	ssize_t read_count = 0;
 	size_t vfd_read_count = 0;
 
+	mutex_lock(&vi->vfds_lock);
 	mutex_lock(&vfd->lock);
 
 	while (read_count == 0 && vfd_read_count == 0) {
 		while (list_empty(&vfd->in_queue)) {
 			mutex_unlock(&vfd->lock);
+			mutex_unlock(&vi->vfds_lock);
 			if (filp->f_flags & O_NONBLOCK)
 				return -EAGAIN;
 
@@ -601,6 +595,7 @@
 				!list_empty(&vfd->in_queue)))
 				return -ERESTARTSYS;
 
+			mutex_lock(&vi->vfds_lock);
 			mutex_lock(&vfd->lock);
 		}
 
@@ -616,6 +611,7 @@
 
 out_unlock:
 	mutex_unlock(&vfd->lock);
+	mutex_unlock(&vi->vfds_lock);
 	return read_count;
 }