[PATCH RFC 0/3] Virtio draft III

Rusty Russell
2007-06-16T13:12:32Z

In this episode, Rusty tries to NAPI-ize the driver and discovers that
virtio callbacks are a bad idea: NAPI needs to turn interrupts off and
still be able to query for new incoming packets.

Changes to core:
1) Back to "interrupt" model with get_inbuf()/get_outbuf() calls.
2) Clearer rules for locking: in calls cannot overlap, out calls cannot
overlap, but in can overlap out.
3) Methods for suppressing/enabling "interrupt" calls.

Changes to example net driver:
1) NAPI, locking is now correct (and there is none)

Changes to example block driver:
1) Relay SCSI ioctls (particularly CDROMEJECT) for optional server
support (VIRTIO_BLK_T_SCSI_CMD).
2) /dev/vb -> /dev/vd.
3) Barrier support.
4) Header w/ definitions can be included from userspace.

Here's the inter-diff for the three:

diff -u b/include/linux/virtio.h b/include/linux/virtio.h
--- b/include/linux/virtio.h
+++ b/include/linux/virtio.h
@@ -7,69 +7,109 @@
/**
* virtio_device - description and routines to drive a virtual device.
- * @lock: the lock to hold before calling any functions.
* @dev: the underlying struct device.
* @ops: the operations for this virtual device.
+ * @driver_ops: set by the driver for callbacks.
* @priv: private pointer for the driver to use.
*/
struct virtio_device {
- spinlock_t lock;
struct device *dev;
struct virtio_ops *ops;
+ struct virtio_driver_ops *driver_ops;
void *priv;
};

/**
+ * virtio_driver_ops - driver callbacks for a virtual device.
+ * @in: inbufs have been completed.
+ * Usually called from an interrupt handler.
+ * Return false to suppress further inbuf callbacks.
+ * @out: outbufs have been completed.
+ * Usually called from an interrupt handler.
+ * Return false to suppress further outbuf callbacks.
+ */
+struct virtio_driver_ops {
+ bool (*in)(struct virtio_device *dev);
+ bool (*out)(struct virtio_device *dev);
+};
+
+enum virtio_dir {
+ VIRTIO_IN = 0x1,
+ VIRTIO_OUT = 0x2,
+};
+
+/**
* virtio_ops - virtio abstraction layer
* @add_outbuf: prepare to send data to the other end:
* vdev: the virtio_device
* sg: the description of the buffer(s).
* num: the size of the sg array.
- * cb: the function to call once the outbuf is finished & detached.
- * data: the token to hand to the cb function.
- * Returns a unique id or an error. Note that the callback will be
- * called with the lock held, and possibly in an interrupt handler.
+ * data: the token returned by the get_outbuf function.
+ * Returns a unique id or an error.
* @add_inbuf: prepare to receive data from the other end:
* vdev: the virtio_device
* sg: the description of the buffer(s).
* num: the size of the sg array.
- * cb: the function to call once the inbuf is finished & detached.
- * data: the token to hand to the cb function.
- * Returns a unique id or an error (eg. -ENOSPC). Note that the
- * callback will be called with the lock held, and possibly in an
- * interrupt handler.
- * @sync: update after add_inbuf/add_outbuf
+ * data: the token returned by the get_inbuf function.
+ * Returns a unique id or an error (eg. -ENOSPC).
+ * @sync: update after add_inbuf and/or add_outbuf
* vdev: the virtio_device we're talking about.
+ * inout: VIRTIO_IN and/or VIRTIO_OUT
* After one or more add_inbuf/add_outbuf calls, invoke this to kick
* the virtio layer.
+ * @get_outbuf: get the next used outbuf.
+ * vdev: the virtio_device we're talking about.
+ * len: the length written into the outbuf
+ * Returns NULL or the "data" token handed to add_outbuf (which has been
+ * detached).
+ * @get_inbuf: get the next used inbuf.
+ * vdev: the virtio_device we're talking about.
+ * len: the length read from the inbuf
+ * Returns NULL or the "data" token handed to add_inbuf (which has been
+ * detached).
* @detach_outbuf: make sure sent sg can no longer be read.
* vdev: the virtio_device we're talking about.
* id: the id returned from add_outbuf.
- * This is not necessary (or valid!) if the outbuf callback has
- * already fired.
+ * This is usually used for shutdown. Don't try to detach twice.
* @detach_inbuf: make sure sent sg can no longer be written to.
* vdev: the virtio_device we're talking about.
* id: the id returned from add_inbuf.
- * This is not necessary (or valid!) if the outbuf callback has
- * already fired.
+ * This is usually used for shutdown. Don't try to detach twice.
+ * @restart_in: restart calls to driver_ops->in after it returned false.
+ * vdev: the virtio_device we're talking about.
+ * This returns "false" (and doesn't re-enable) if there are pending
+ * inbufs, to avoid a race.
+ * @restart_out: restart calls to driver_ops->out after it returned false.
+ * vdev: the virtio_device we're talking about.
+ * This returns "false" (and doesn't re-enable) if there are pending
+ * outbufs, to avoid a race.
+ *
+ * Locking rules are straightforward: the driver is responsible for
+ * locking. Outbuf operations can be called in parallel to inbuf
+ * operations, but no two outbuf operations nor two inbuf operations
+ * may be invoked simultaneously.
+ *
+ * All operations can be called in any context.
*/
struct virtio_ops {
unsigned long (*add_outbuf)(struct virtio_device *vdev,
const struct scatterlist sg[],
unsigned int num,
- void (*cb)(struct virtio_device *vdev,
- void *data, unsigned len),
void *data);

unsigned long (*add_inbuf)(struct virtio_device *vdev,
struct scatterlist sg[],
unsigned int num,
- void (*cb)(struct virtio_device *vdev,
- void *data, unsigned len),
void *data);

- void (*sync)(struct virtio_device *vdev);
+ void (*sync)(struct virtio_device *vdev, enum virtio_dir inout);
+
+ void *(*get_outbuf)(struct virtio_device *vdev, unsigned int *len);
+ void *(*get_inbuf)(struct virtio_device *vdev, unsigned int *len);

void (*detach_outbuf)(struct virtio_device *vdev, unsigned long id);
void (*detach_inbuf)(struct virtio_device *vdev, unsigned long id);
+
+ bool (*restart_in)(struct virtio_device *vdev);
+ bool (*restart_out)(struct virtio_device *vdev);
};
#endif /* _LINUX_VIRTIO_H */
diff -u b/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
--- b/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -33,29 +33,21 @@
struct virtio_device *vdev;
struct net_device *ndev;

+ /* Number of input buffers, and max we've ever had. */
+ unsigned int num, max;
+
/* Receive & send queues. */
struct sk_buff_head recv;
struct sk_buff_head send;
-
- /* Transmitted packets waiting to be freed */
- struct sk_buff_head free;
};

-static void skb_xmit_done(struct virtio_device *vdev, void *_skb, unsigned len)
+static bool skb_xmit_done(struct virtio_device *vdev)
{
struct virtnet_info *vi = vdev->priv;
- struct sk_buff *skb = _skb;
-
- assert_spin_locked(&vdev->lock);
-
- __skb_unlink(skb, &vi->send);
- vi->ndev->stats.tx_bytes += len;
- vi->ndev->stats.tx_packets++;
- __skb_queue_head(&vi->free, skb);

- pr_debug("Sent skb %p\n", skb);
/* In case we were waiting for output buffers. */
netif_wake_queue(vi->ndev);
+ return true;
}

static void receive_skb(struct net_device *dev, struct sk_buff *skb,
@@ -78,48 +70,90 @@
netif_rx(skb);
}

-static void skb_recv_done(struct virtio_device *, void *, unsigned);
-static int try_fill_recv(struct virtnet_info *vi)
+static void try_fill_recv(struct virtnet_info *vi)
{
struct sk_buff *skb;
struct scatterlist sg[MAX_SKB_FRAGS];
- unsigned long num, id;
-
- assert_spin_locked(&vi->vdev->lock);
+ unsigned long sgnum, id;

- skb = netdev_alloc_skb(vi->ndev, MAX_PACKET_LEN);
- if (unlikely(!skb))
- return -ENOMEM;
+ for (;;) {
+ skb = netdev_alloc_skb(vi->ndev, MAX_PACKET_LEN);
+ if (unlikely(!skb))
+ break;
+
+ skb_put(skb, MAX_PACKET_LEN);
+ sgnum = skb_to_sgvec(skb, sg, 0, skb->len);
+ skb_queue_head(&vi->recv, skb);
+
+ id = vi->vdev->ops->add_inbuf(vi->vdev, sg, sgnum, skb);
+ if (IS_ERR_VALUE(id)) {
+ skb_unlink(skb, &vi->recv);
+ kfree_skb(skb);
+ break;
+ }
+ vi->num++;
+ }
+ if (unlikely(vi->num > vi->max))
+ vi->max = vi->num;
+ vi->vdev->ops->sync(vi->vdev, VIRTIO_IN);
+}

- skb_put(skb, MAX_PACKET_LEN);
- num = skb_to_sgvec(skb, sg, 0, skb->len);
- skb_queue_head(&vi->recv, skb);
+static bool skb_recv_done(struct virtio_device *vdev)
+{
+ struct virtnet_info *vi = vdev->priv;

- id = vi->vdev->ops->add_inbuf(vi->vdev, sg, num, skb_recv_done, skb);
- if (IS_ERR_VALUE(id)) {
- skb_unlink(skb, &vi->recv);
- kfree_skb(skb);
- return id;
- }
- return 0;
+ netif_rx_schedule(vi->ndev);
+ /* Suppress further interrupts. */
+ return false;
}

-static void skb_recv_done(struct virtio_device *vdev, void *_skb, unsigned len)
+static int virtnet_poll(struct net_device *dev, int *budget)
{
- struct virtnet_info *vi = vdev->priv;
- struct sk_buff *skb = _skb;
+ struct virtnet_info *vi = netdev_priv(dev);
+ struct sk_buff *skb = NULL;
+ unsigned int len, received = 0;

- assert_spin_locked(&vdev->lock);
- __skb_unlink(skb, &vi->recv);
- receive_skb(vi->ndev, skb, len);
- try_fill_recv(vi);
+again:
+ while (received < dev->quota &&
+ (skb = vi->vdev->ops->get_inbuf(vi->vdev, &len)) != NULL) {
+ __skb_unlink(skb, &vi->recv);
+ receive_skb(vi->ndev, skb, len);
+ vi->num--;
+ received++;
+ }
+
+ dev->quota -= received;
+ *budget -= received;
+
+ /* FIXME: If we oom and completely run out of inbufs, we need
+ * to start a timer trying to fill more. */
+ if (vi->num < vi->max / 2)
+ try_fill_recv(vi);
+
+ /* Still more work to do? */
+ if (skb)
+ return 1; /* not done */
+
+ netif_rx_complete(dev);
+ if (unlikely(!vi->vdev->ops->restart_in(vi->vdev))
+ && netif_rx_reschedule(dev, received))
+ goto again;
+
+ return 0;
}

-static void free_old_skbs(struct sk_buff_head *free)
+static void free_old_xmit_skbs(struct virtnet_info *vi)
{
struct sk_buff *skb;
- while ((skb = __skb_dequeue(free)) != NULL)
+ unsigned int len;
+
+ while ((skb = vi->vdev->ops->get_outbuf(vi->vdev, &len)) != NULL) {
+ pr_debug("Sent skb %p\n", skb);
+ __skb_unlink(skb, &vi->send);
+ vi->ndev->stats.tx_bytes += len;
+ vi->ndev->stats.tx_packets++;
kfree_skb(skb);
+ }
}

static int start_xmit(struct sk_buff *skb, struct net_device *dev)
@@ -128,19 +162,16 @@
unsigned long num, id;
struct scatterlist sg[MAX_SKB_FRAGS];
const unsigned char *dest = ((struct ethhdr *)skb->data)->h_dest;
- unsigned long flags;

pr_debug("%s: xmit %p %02x:%02x:%02x:%02x:%02x:%02x\n",
dev->name, skb,
dest[0], dest[1], dest[2], dest[3], dest[4], dest[5]);

- spin_lock_irqsave(&vi->vdev->lock, flags);
- /* Free any transmitted packets: not supposed to do it in interrupt */
- free_old_skbs(&vi->free);
+ free_old_xmit_skbs(vi);

num = skb_to_sgvec(skb, sg, 0, skb->len);
__skb_queue_head(&vi->send, skb);
- id = vi->vdev->ops->add_outbuf(vi->vdev, sg, num, skb_xmit_done, skb);
+ id = vi->vdev->ops->add_outbuf(vi->vdev, sg, num, skb);
if (IS_ERR_VALUE(id)) {
pr_debug("%s: virtio not prepared to send\n", dev->name);
skb_unlink(skb, &vi->send);
@@ -148,8 +179,7 @@
return NETDEV_TX_BUSY;
}
SKB_ID(skb) = id;
- vi->vdev->ops->sync(vi->vdev);
- spin_unlock_irqrestore(&vi->vdev->lock, flags);
+ vi->vdev->ops->sync(vi->vdev, VIRTIO_OUT);

return 0;
}
@@ -157,16 +187,12 @@
static int virtnet_open(struct net_device *dev)
{
struct virtnet_info *vi = netdev_priv(dev);
- int i, err;

- spin_lock_irq(&vi->vdev->lock);
- for (i = 0; (err = try_fill_recv(vi)) == 0; i++);
- vi->vdev->ops->sync(vi->vdev);
- spin_unlock_irq(&vi->vdev->lock);
+ try_fill_recv(vi);

/* If we didn't even get one input buffer, we're useless. */
- if (i == 0)
- return err;
+ if (vi->num == 0)
+ return -ENOMEM;

return 0;
}
@@ -176,20 +202,26 @@
struct virtnet_info *vi = netdev_priv(dev);
struct sk_buff *skb;

- spin_lock_irq(&vi->vdev->lock);
+ /* networking core has neutered skb_xmit_done/skb_recv_done, so don't
+ * worry about races vs. get_buf(). */
while ((skb = __skb_dequeue(&vi->recv)) != NULL) {
vi->vdev->ops->detach_inbuf(vi->vdev, SKB_ID(skb));
kfree_skb(skb);
+ vi->num--;
}
while ((skb = __skb_dequeue(&vi->send)) != NULL) {
vi->vdev->ops->detach_outbuf(vi->vdev, SKB_ID(skb));
kfree_skb(skb);
}
- free_old_skbs(&vi->free);
- spin_unlock_irq(&vi->vdev->lock);
+ BUG_ON(vi->num != 0);
return 0;
}

+static struct virtio_driver_ops virtnet_ops = {
+ .in = skb_recv_done,
+ .out = skb_xmit_done,
+};
+
struct net_device *virtnet_probe(struct virtio_device *vdev,
const u8 mac[ETH_ALEN])
{
@@ -207,16 +239,18 @@
memcpy(dev->dev_addr, mac, ETH_ALEN);
dev->open = virtnet_open;
dev->stop = virtnet_close;
+ dev->poll = virtnet_poll;
dev->hard_start_xmit = start_xmit;
+ dev->weight = 16;
SET_NETDEV_DEV(dev, vdev->dev);

vi = netdev_priv(dev);
vi->vdev = vdev;
vi->ndev = dev;
vdev->priv = vi;
+ vdev->driver_ops = &virtnet_ops;
skb_queue_head_init(&vi->recv);
skb_queue_head_init(&vi->send);
- skb_queue_head_init(&vi->free);

err = register_netdev(dev);
if (err) {
diff -u b/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c
--- b/drivers/block/virtio_blk.c
+++ b/drivers/block/virtio_blk.c
@@ -1,4 +1,4 @@
-//#define DEBUG
+#define DEBUG
#include <linux/spinlock.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
@@ -8,6 +8,8 @@
static unsigned char virtblk_index = 'a';
struct virtio_blk
{
+ spinlock_t lock;
+
struct virtio_device *vdev;

/* The disk structure for the kernel. */
@@ -19,7 +21,7 @@
mempool_t *pool;

/* Scatterlist: can be too big for stack. */
- struct scatterlist sg[1+MAX_PHYS_SEGMENTS];
+ struct scatterlist sg[2+MAX_PHYS_SEGMENTS];
};

struct virtblk_req
@@ -28,68 +30,94 @@
struct request *req;
unsigned long out_id;
bool out_done, in_done;
- bool failed;
+ int uptodate;
struct virtio_blk_outhdr out_hdr;
struct virtio_blk_inhdr in_hdr;
};

-/* Jens gave me this nice helper to end all chunks of a request. */
-static void end_dequeued_request(struct request *req, int uptodate)
+static void end_dequeued_request(struct request *req,
+ request_queue_t *q, int uptodate)
{
- if (end_that_request_first(req, uptodate, req->hard_nr_sectors))
+ /* And so the insanity of the block layer infects us here. */
+ int nsectors = req->hard_nr_sectors;
+
+ if (blk_pc_request(req)) {
+ nsectors = (req->data_len + 511) >> 9;
+ if (!nsectors)
+ nsectors = 1;
+ printk("uptodate = %i\n", uptodate);
+ }
+ if (end_that_request_first(req, uptodate, nsectors))
BUG();
add_disk_randomness(req->rq_disk);
end_that_request_last(req, uptodate);
}

-static void finish(struct virtio_blk *vblk, struct virtblk_req *vbr)
+static bool finish(struct virtio_blk *vblk, struct virtblk_req *vbr)
{
- end_dequeued_request(vbr->req, !vbr->failed);
+ if (!vbr->in_done || !vbr->out_done)
+ return false;
+ end_dequeued_request(vbr->req, vblk->disk->queue, vbr->uptodate);
list_del(&vbr->list);
mempool_free(vbr, vblk->pool);
- /* In case queue is stopped waiting for more buffers. */
- blk_start_queue(vblk->disk->queue);
+ return true;
}

/* We make sure they finished both the input and output buffers: otherwise
* they might still have read access after we free them. */
-static void blk_out_done(struct virtio_device *vdev, void *_vbr, unsigned len)
+static bool blk_out_done(struct virtio_device *vdev)
{
- struct virtblk_req *vbr = _vbr;
struct virtio_blk *vblk = vdev->priv;
+ struct virtblk_req *vbr;
+ unsigned int len, finished = 0;
+ unsigned long flags;

- assert_spin_locked(&vblk->vdev->lock);
-
- BUG_ON(vbr->out_done);
- vbr->out_done = true;
- if (vbr->in_done)
- finish(vblk, vbr);
+ spin_lock_irqsave(&vblk->lock, flags);
+ while ((vbr = vdev->ops->get_outbuf(vdev, &len)) != NULL) {
+ BUG_ON(vbr->out_done);
+ vbr->out_done = true;
+ finished += finish(vblk, vbr);
+ }
+ /* In case queue is stopped waiting for more buffers. */
+ if (finished)
+ blk_start_queue(vblk->disk->queue);
+ spin_unlock_irqrestore(&vblk->lock, flags);
+ return true;
}

-static void blk_in_done(struct virtio_device *vdev, void *_vbr, unsigned len)
+static bool blk_in_done(struct virtio_device *vdev)
{
- struct virtblk_req *vbr = _vbr;
struct virtio_blk *vblk = vdev->priv;
- unsigned long expected_len;
+ struct virtblk_req *vbr;
+ unsigned int len, finished = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vblk->lock, flags);

- assert_spin_locked(&vblk->vdev->lock);
+ while ((vbr = vdev->ops->get_inbuf(vdev, &len)) != NULL) {
+ BUG_ON(vbr->in_done);

- expected_len = sizeof(vbr->in_hdr);
- if (vbr->out_hdr.type == READ)
- expected_len += vbr->req->hard_nr_sectors*512;
-
- if (unlikely(len != expected_len)) {
- dev_err(vblk->vdev->dev, "short reply %u not %lu",
- len, expected_len);
- vbr->failed = true;
- } else if (unlikely(vbr->in_hdr.status != 1)) {
- vbr->failed = true;
+ switch (vbr->in_hdr.status) {
+ case VIRTIO_BLK_S_OK:
+ vbr->uptodate = 1;
+ break;
+ case VIRTIO_BLK_S_UNSUPP:
+ printk("Request was unsupported\n");
+ vbr->uptodate = -ENOTTY;
+ break;
+ default:
+ vbr->uptodate = 0;
+ break;
+ }
+ vbr->in_done = true;
+ finished += finish(vblk, vbr);
}

- BUG_ON(vbr->in_done);
- vbr->in_done = true;
- if (vbr->out_done)
- finish(vblk, vbr);
+ /* In case queue is stopped waiting for more buffers. */
+ if (finished)
+ blk_start_queue(vblk->disk->queue);
+ spin_unlock_irqrestore(&vblk->lock, flags);
+ return true;
}

static bool do_write(request_queue_t *q, struct virtio_blk *vblk,
@@ -97,12 +125,14 @@
{
unsigned long num;

+ vbr->out_hdr.type |= VIRTIO_BLK_T_WRITE;
+
/* Set up for reply. */
vblk->sg[0].page = virt_to_page(&vbr->in_hdr);
vblk->sg[0].offset = offset_in_page(&vbr->in_hdr);
vblk->sg[0].length = sizeof(vbr->in_hdr);
vbr->out_hdr.id = vblk->vdev->ops->add_inbuf(vblk->vdev, vblk->sg, 1,
- blk_in_done, vbr);
+ vbr);
if (IS_ERR_VALUE(vbr->out_hdr.id))
goto full;

@@ -112,15 +142,13 @@
vblk->sg[0].length = sizeof(vbr->out_hdr);

num = blk_rq_map_sg(q, vbr->req, vblk->sg+1);
- vbr->out_done = vbr->in_done = false;
- vbr->failed = false;
vbr->out_id = vblk->vdev->ops->add_outbuf(vblk->vdev, vblk->sg, 1+num,
- blk_out_done, vbr);
+ vbr);
if (IS_ERR_VALUE(vbr->out_id))
goto detach_inbuf_full;

pr_debug("Write: %p in=%lu out=%lu\n", vbr,
- vbr->out_hdr.id, vbr->out_id);
+ (long)vbr->out_hdr.id, (long)vbr->out_id);
list_add_tail(&vbr->list, &vblk->reqs);
return true;

@@ -135,13 +163,15 @@
{
unsigned long num;

+ vbr->out_hdr.type |= VIRTIO_BLK_T_READ;
+
/* Set up for reply. */
vblk->sg[0].page = virt_to_page(&vbr->in_hdr);
vblk->sg[0].offset = offset_in_page(&vbr->in_hdr);
vblk->sg[0].length = sizeof(vbr->in_hdr);
num = blk_rq_map_sg(q, vbr->req, vblk->sg+1);
vbr->out_hdr.id = vblk->vdev->ops->add_inbuf(vblk->vdev, vblk->sg,
- 1+num, blk_in_done, vbr);
+ 1+num, vbr);
if (IS_ERR_VALUE(vbr->out_hdr.id))
goto full;

@@ -149,15 +179,53 @@
vblk->sg[0].offset = offset_in_page(&vbr->out_hdr);
vblk->sg[0].length = sizeof(vbr->out_hdr);

- vbr->out_done = vbr->in_done = false;
- vbr->failed = false;
vbr->out_id = vblk->vdev->ops->add_outbuf(vblk->vdev, vblk->sg, 1,
- blk_out_done, vbr);
+ vbr);
if (IS_ERR_VALUE(vbr->out_id))
goto detach_inbuf_full;

pr_debug("Read: %p in=%lu out=%lu\n", vbr,
- vbr->out_hdr.id, vbr->out_id);
+ (long)vbr->out_hdr.id, (long)vbr->out_id);
+ list_add_tail(&vbr->list, &vblk->reqs);
+ return true;
+
+detach_inbuf_full:
+ vblk->vdev->ops->detach_inbuf(vblk->vdev, vbr->out_hdr.id);
+full:
+ return false;
+}
+
+static bool do_scsi(request_queue_t *q, struct virtio_blk *vblk,
+ struct virtblk_req *vbr)
+{
+ unsigned long num;
+
+ vbr->out_hdr.type |= VIRTIO_BLK_T_SCSI_CMD;
+
+ /* Set up for reply. */
+ vblk->sg[0].page = virt_to_page(&vbr->in_hdr);
+ vblk->sg[0].offset = offset_in_page(&vbr->in_hdr);
+ vblk->sg[0].length = sizeof(vbr->in_hdr);
+ vbr->out_hdr.id = vblk->vdev->ops->add_inbuf(vblk->vdev, vblk->sg, 1,
+ vbr);
+ if (IS_ERR_VALUE(vbr->out_hdr.id))
+ goto full;
+
+ vblk->sg[0].page = virt_to_page(&vbr->out_hdr);
+ vblk->sg[0].offset = offset_in_page(&vbr->out_hdr);
+ vblk->sg[0].length = sizeof(vbr->out_hdr);
+ vblk->sg[1].page = virt_to_page(vbr->req->cmd);
+ vblk->sg[1].offset = offset_in_page(vbr->req->cmd);
+ vblk->sg[1].length = vbr->req->cmd_len;
+
+ num = blk_rq_map_sg(q, vbr->req, vblk->sg+1);
+ vbr->out_id = vblk->vdev->ops->add_outbuf(vblk->vdev, vblk->sg, 2+num,
+ vbr);
+ if (IS_ERR_VALUE(vbr->out_id))
+ goto detach_inbuf_full;
+
+ pr_debug("Scsi: %p in=%lu out=%lu\n", vbr,
+ (long)vbr->out_hdr.id, (long)vbr->out_id);
list_add_tail(&vbr->list, &vblk->reqs);
return true;

@@ -176,37 +244,38 @@
while ((req = elv_next_request(q)) != NULL) {
vblk = req->rq_disk->private_data;

- /* FIXME: handle these iff capable. */
- if (!blk_fs_request(req)) {
- pr_debug("Got non-command 0x%08x\n", req->cmd_type);
- req->errors++;
- blkdev_dequeue_request(req);
- end_dequeued_request(req, 0);
- continue;
- }
-
vbr = mempool_alloc(vblk->pool, GFP_ATOMIC);
if (!vbr)
goto stop;

BUG_ON(req->nr_phys_segments > ARRAY_SIZE(vblk->sg));
vbr->req = req;
- vbr->out_hdr.type = rq_data_dir(req);
+ /* Actual type gets or'ed in do_scsi/do_write/do_read */
+ vbr->out_hdr.type = blk_barrier_rq(req)?VIRTIO_BLK_T_BARRIER:0;
vbr->out_hdr.sector = req->sector;
+ vbr->out_hdr.ioprio = req->ioprio;
+ vbr->out_done = vbr->in_done = false;

- if (rq_data_dir(req) == WRITE) {
- if (!do_write(q, vblk, vbr))
- goto stop;
- } else {
- if (!do_read(q, vblk, vbr))
+ if (blk_pc_request(req)) {
+ if (!do_scsi(q, vblk, vbr))
goto stop;
- }
+ } else if (blk_fs_request(req)) {
+ if (rq_data_dir(req) == WRITE) {
+ if (!do_write(q, vblk, vbr))
+ goto stop;
+ } else {
+ if (!do_read(q, vblk, vbr))
+ goto stop;
+ }
+ } else
+ /* We don't put anything else in the queue. */
+ BUG();
blkdev_dequeue_request(req);
}

sync:
if (vblk)
- vblk->vdev->ops->sync(vblk->vdev);
+ vblk->vdev->ops->sync(vblk->vdev, VIRTIO_IN|VIRTIO_OUT);
return;

stop:
@@ -216,7 +285,21 @@
goto sync;
}

+static int virtblk_ioctl(struct inode *inode, struct file *filp,
+ unsigned cmd, unsigned long data)
+{
+ return scsi_cmd_ioctl(filp, inode->i_bdev->bd_disk, cmd,
+ (void __user *)data);
+}
+
+static struct virtio_driver_ops virtblk_ops = {
+ .in = blk_in_done,
+ .out = blk_out_done,
+};
+
+
static struct block_device_operations virtblk_fops = {
+ .ioctl = virtblk_ioctl,
.owner = THIS_MODULE,
};

@@ -232,8 +315,10 @@
}

INIT_LIST_HEAD(&vblk->reqs);
+ spin_lock_init(&vblk->lock);
vblk->vdev = vdev;
vdev->priv = vblk;
+ vdev->driver_ops = &virtblk_ops;

vblk->pool = mempool_create_kmalloc_pool(1,sizeof(struct virtblk_req));
if (!vblk->pool) {
@@ -254,19 +339,20 @@
goto out_unregister_blkdev;
}

- vblk->disk->queue = blk_init_queue(do_virtblk_request,
- &vblk->vdev->lock);
+ vblk->disk->queue = blk_init_queue(do_virtblk_request, &vblk->lock);
if (!vblk->disk->queue) {
err = -ENOMEM;
goto out_put_disk;
}

- sprintf(vblk->disk->disk_name, "vb%c", virtblk_index++);
+ sprintf(vblk->disk->disk_name, "vd%c", virtblk_index++);
vblk->disk->major = major;
vblk->disk->first_minor = 0;
vblk->disk->private_data = vblk;
vblk->disk->fops = &virtblk_fops;

+ blk_queue_ordered(vblk->disk->queue, QUEUE_ORDERED_TAG, NULL);
+
/* Caller can do blk_queue_max_hw_segments(), set_capacity()
* etc then add_disk(). */
return vblk->disk;
diff -u b/include/linux/virtio_blk.h b/include/linux/virtio_blk.h
--- b/include/linux/virtio_blk.h
+++ b/include/linux/virtio_blk.h
@@ -3,26 +3,37 @@
#include <linux/types.h>
-struct gendisk;
-struct virtio_device;
-struct hd_geometry;
+
+#define VIRTIO_BLK_T_READ 0
+#define VIRTIO_BLK_T_WRITE 1
+#define VIRTIO_BLK_T_SCSI_CMD 3
+#define VIRTIO_BLK_T_BARRIER 0x80000000 /* Barrier before this op. */

/* This is the first element of the scatter-gather list. */
struct virtio_blk_outhdr
{
- /* 0 == read, 1 == write */
- u32 type;
+ /* VIRTIO_BLK_T* */
+ __u32 type;
+ /* io priority. */
+ __u32 ioprio;
/* Sector (ie. 512 byte offset) */
- unsigned long sector;
+ __u64 sector;
/* Where to put reply. */
- unsigned long id;
+ __u64 id;
};

+#define VIRTIO_BLK_S_OK 0
+#define VIRTIO_BLK_S_IOERR 1
+#define VIRTIO_BLK_S_UNSUPP 2
+
struct virtio_blk_inhdr
{
- /* 1 = OK, 0 = not ok. */
- unsigned long status;
+ unsigned char status;
};

+#ifdef __KERNEL__
+struct gendisk;
+struct virtio_device;
+
struct gendisk *virtblk_probe(struct virtio_device *vdev);
void virtblk_remove(struct gendisk *disk);
-
+#endif /* __KERNEL__ */
#endif /* _LINUX_VIRTIO_BLK_H */
only in patch2:
unchanged:
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -341,6 +341,7 @@ unifdef-y += utsname.h
unifdef-y += utsname.h
unifdef-y += videodev2.h
unifdef-y += videodev.h
+unifdef-y += virtio_blk.h
unifdef-y += wait.h
unifdef-y += wanrouter.h
unifdef-y += watchdog.h