lguest: make Launcher see device status updates

This brings us closer to Real Life, where we'd examine the device
features once it's set the DRIVER_OK status bit.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
---
 Documentation/lguest/lguest.c  |   50 ++++++++++++++++++++++++++++-------------
 drivers/lguest/lguest_device.c |   20 ++++++++++------
 2 files changed, 48 insertions(+), 22 deletions(-)

diff -r a6d34677592b Documentation/lguest/lguest.c
--- a/Documentation/lguest/lguest.c	Tue Apr 22 13:03:44 2008 +1000
+++ b/Documentation/lguest/lguest.c	Tue Apr 22 13:11:05 2008 +1000
@@ -130,6 +130,9 @@ struct device
 
 	/* Any queues attached to this device */
 	struct virtqueue *vq;
+
+	/* Handle status being finalized (ie. feature bits stable). */
+	void (*ready)(struct device *me);
 
 	/* Device-specific data. */
 	void *priv;
@@ -925,24 +928,40 @@ static void enable_fd(int fd, struct vir
 	write(waker_fd, &vq->dev->fd, sizeof(vq->dev->fd));
 }
 
-/* When the Guest asks us to reset a device, it's is fairly easy. */
-static void reset_device(struct device *dev)
+/* When the Guest tells us they updated the status field, we handle it. */
+static void update_device_status(struct device *dev)
 {
 	struct virtqueue *vq;
 
-	verbose("Resetting device %s\n", dev->name);
-	/* Clear the status. */
-	dev->desc->status = 0;
+	/* This is a reset. */
+	if (dev->desc->status == 0) {
+		verbose("Resetting device %s\n", dev->name);
 
-	/* Clear any features they've acked. */
-	memset(get_feature_bits(dev) + dev->desc->feature_len, 0,
-	       dev->desc->feature_len);
+		/* Clear any features they've acked. */
+		memset(get_feature_bits(dev) + dev->desc->feature_len, 0,
+		       dev->desc->feature_len);
 
-	/* Zero out the virtqueues. */
-	for (vq = dev->vq; vq; vq = vq->next) {
-		memset(vq->vring.desc, 0,
-		       vring_size(vq->config.num, getpagesize()));
-		vq->last_avail_idx = 0;
+		/* Zero out the virtqueues. */
+		for (vq = dev->vq; vq; vq = vq->next) {
+			memset(vq->vring.desc, 0,
+			       vring_size(vq->config.num, getpagesize()));
+			vq->last_avail_idx = 0;
+		}
+	} else if (dev->desc->status & VIRTIO_CONFIG_S_FAILED) {
+		warnx("Device %s configuration FAILED", dev->name);
+	} else if (dev->desc->status & VIRTIO_CONFIG_S_DRIVER_OK) {
+		unsigned int i;
+
+		verbose("Device %s OK: offered", dev->name);
+		for (i = 0; i < dev->desc->feature_len; i++)
+			verbose(" %08x", get_feature_bits(dev)[i]);
+		verbose(", accepted");
+		for (i = 0; i < dev->desc->feature_len; i++)
+			verbose(" %08x", get_feature_bits(dev)
+				[dev->desc->feature_len+i]);
+
+		if (dev->ready)
+			dev->ready(dev);
 	}
 }
 
@@ -954,9 +973,9 @@ static void handle_output(int fd, unsign
 
 	/* Check each device and virtqueue. */
 	for (i = devices.dev; i; i = i->next) {
-		/* Notifications to device descriptors reset the device. */
+		/* Notifications to device descriptors update device status. */
 		if (from_guest_phys(addr) == i->desc) {
-			reset_device(i);
+			update_device_status(i);
 			return;
 		}
 
@@ -1170,6 +1189,7 @@ static struct device *new_device(const c
 	dev->handle_input = handle_input;
 	dev->name = name;
 	dev->vq = NULL;
+	dev->ready = NULL;
 
 	/* Append to device list.  Prepending to a single-linked list is
 	 * easier, but the user expects the devices to be arranged on the bus
diff -r a6d34677592b drivers/lguest/lguest_device.c
--- a/drivers/lguest/lguest_device.c	Tue Apr 22 13:03:44 2008 +1000
+++ b/drivers/lguest/lguest_device.c	Tue Apr 22 13:11:05 2008 +1000
@@ -137,20 +137,26 @@ static u8 lg_get_status(struct virtio_de
 	return to_lgdev(vdev)->desc->status;
 }
 
+/* To notify on status updates, we (ab)use the NOTIFY hypercall, with the
+ * descriptor address of the device.  A zero status means "reset". */
+static void set_status(struct virtio_device *vdev, u8 status)
+{
+	unsigned long offset = (void *)to_lgdev(vdev)->desc - lguest_devices;
+
+	/* We set the status. */
+	to_lgdev(vdev)->desc->status = status;
+	hcall(LHCALL_NOTIFY, (max_pfn<<PAGE_SHIFT) + offset, 0, 0);
+}
+
 static void lg_set_status(struct virtio_device *vdev, u8 status)
 {
 	BUG_ON(!status);
-	to_lgdev(vdev)->desc->status = status;
+	set_status(vdev, status);
 }
 
-/* To reset the device, we (ab)use the NOTIFY hypercall, with the descriptor
- * address of the device.  The Host will zero the status and all the
- * features. */
 static void lg_reset(struct virtio_device *vdev)
 {
-	unsigned long offset = (void *)to_lgdev(vdev)->desc - lguest_devices;
-
-	hcall(LHCALL_NOTIFY, (max_pfn<<PAGE_SHIFT) + offset, 0, 0);
+	set_status(vdev, 0);
 }
 
 /*
