virtio: Fix scheduling while atomic in virtio_balloon stats
[firefly-linux-kernel-4.4.55.git] / drivers / virtio / virtio_balloon.c
index cd778b1752b596f0d9cbd09e543033c6897e2a87..3db3d242c3ee2f2502bf15c00c1b4fe8f67aa0a0 100644 (file)
@@ -51,6 +51,7 @@ struct virtio_balloon
        u32 pfns[256];
 
        /* Memory statistics */
+       int need_stats_update;
        struct virtio_balloon_stat stats[VIRTIO_BALLOON_S_NR];
 };
 
@@ -193,20 +194,30 @@ static void update_balloon_stats(struct virtio_balloon *vb)
  * the stats queue operates in reverse.  The driver initializes the virtqueue
  * with a single buffer.  From that point forward, all conversations consist of
  * a hypervisor request (a call to this function) which directs us to refill
- * the virtqueue with a fresh stats buffer.
+ * the virtqueue with a fresh stats buffer.  Since stats collection can sleep,
+ * we notify our kthread which does the actual work via stats_handle_request().
  */
-static void stats_ack(struct virtqueue *vq)
+static void stats_request(struct virtqueue *vq)
 {
        struct virtio_balloon *vb;
        unsigned int len;
-       struct scatterlist sg;
 
        vb = vq->vq_ops->get_buf(vq, &len);
        if (!vb)
                return;
+       vb->need_stats_update = 1;
+       wake_up(&vb->config_change);
+}
+
+static void stats_handle_request(struct virtio_balloon *vb)
+{
+       struct virtqueue *vq;
+       struct scatterlist sg;
 
+       vb->need_stats_update = 0;
        update_balloon_stats(vb);
 
+       vq = vb->stats_vq;
        sg_init_one(&sg, vb->stats, sizeof(vb->stats));
        if (vq->vq_ops->add_buf(vq, &sg, 1, 0, vb) < 0)
                BUG();
@@ -249,8 +260,11 @@ static int balloon(void *_vballoon)
                try_to_freeze();
                wait_event_interruptible(vb->config_change,
                                         (diff = towards_target(vb)) != 0
+                                        || vb->need_stats_update
                                         || kthread_should_stop()
                                         || freezing(current));
+               if (vb->need_stats_update)
+                       stats_handle_request(vb);
                if (diff > 0)
                        fill_balloon(vb, diff);
                else if (diff < 0)
@@ -264,7 +278,7 @@ static int virtballoon_probe(struct virtio_device *vdev)
 {
        struct virtio_balloon *vb;
        struct virtqueue *vqs[3];
-       vq_callback_t *callbacks[] = { balloon_ack, balloon_ack, stats_ack };
+       vq_callback_t *callbacks[] = { balloon_ack, balloon_ack, stats_request };
        const char *names[] = { "inflate", "deflate", "stats" };
        int err, nvqs;