[PATCH 2/6] virtio block driver for QEMU (v2)

Anthony Liguori
2007-11-11T03:20:36Z

This patch implements a very naive virtio block device backend in QEMU.
There's a lot of room for future optimization. We need to merge a -disk patch
before we can provide a mechanism to expose this to users.

Signed-off-by: Anthony Liguori <aliguori-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>

diff --git a/qemu/Makefile.target b/qemu/Makefile.target
index c7686b2..49c0fc7 100644
--- a/qemu/Makefile.target
+++ b/qemu/Makefile.target
@@ -449,7 +449,7 @@ VL_OBJS += rtl8139.o
VL_OBJS+= hypercall.o

# virtio devices
-VL_OBJS += virtio.o
+VL_OBJS += virtio.o virtio-blk.o

ifeq ($(TARGET_BASE_ARCH), i386)
# Hardware support
diff --git a/qemu/hw/pc.c b/qemu/hw/pc.c
index 8aae814..c7483e8 100644
--- a/qemu/hw/pc.c
+++ b/qemu/hw/pc.c
@@ -943,6 +943,14 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size, int boot_device,
#ifdef USE_HYPERCALL
pci_hypercall_init(pci_bus);
#endif
+
+ if (1) {
+ BlockDriverState *bs = bdrv_new("vda");
+ if (bdrv_open(bs, "/home/anthony/images/linux.img", BDRV_O_SNAPSHOT))
+ exit(1);
+ virtio_blk_init(pci_bus, 0x5002, 0x2258, bs);
+ }
+
if (pci_enabled) {
pci_piix3_ide_init(pci_bus, bs_table, piix3_devfn + 1, i8259);
} else {
diff --git a/qemu/hw/virtio-blk.c b/qemu/hw/virtio-blk.c
new file mode 100644
index 0000000..a1d5ea6
--- /dev/null
+++ b/qemu/hw/virtio-blk.c
@@ -0,0 +1,162 @@
+/*
+ * Virtio Block Device
+ *
+ * Copyright IBM, Corp. 2007
+ *
+ * Authors:
+ * Anthony Liguori <aliguori-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "vl.h"
+#include "virtio.h"
+
+/* from Linux's linux/virtio_blk.h */
+
+/* The ID for virtio_block */
+#define VIRTIO_ID_BLOCK 2
+
+/* Feature bits */
+#define VIRTIO_BLK_F_BARRIER 0 /* Does host support barriers? */
+#define VIRTIO_BLK_F_SIZE_MAX 1 /* Indicates maximum segment size */
+#define VIRTIO_BLK_F_SEG_MAX 2 /* Indicates maximum # of segments */
+
+/* The capacity (in 512-byte sectors). 8 bytes. */
+#define VIRTIO_CONFIG_BLK_F_CAPACITY 0
+/* The maximum segment size. 4 bytes. */
+#define VIRTIO_CONFIG_BLK_F_SIZE_MAX 0x08
+/* The maximum number of segments. 4 bytes. */
+#define VIRTIO_CONFIG_BLK_F_SEG_MAX 0x0A
+
+/* These two define direction. */
+#define VIRTIO_BLK_T_IN 0
+#define VIRTIO_BLK_T_OUT 1
+
+/* This bit says it's a scsi command, not an actual read or write. */
+#define VIRTIO_BLK_T_SCSI_CMD 2
+
+/* Barrier before this op. */
+#define VIRTIO_BLK_T_BARRIER 0x80000000
+
+/* This is the first element of the read scatter-gather list. */
+struct virtio_blk_outhdr
+{
+ /* VIRTIO_BLK_T* */
+ uint32_t type;
+ /* io priority. */
+ uint32_t ioprio;
+ /* Sector (ie. 512 byte offset) */
+ uint64_t sector;
+ /* Where to put reply. */
+ uint64_t id;
+};
+
+#define VIRTIO_BLK_S_OK 0
+#define VIRTIO_BLK_S_IOERR 1
+#define VIRTIO_BLK_S_UNSUPP 2
+
+/* This is the first element of the write scatter-gather list */
+struct virtio_blk_inhdr
+{
+ unsigned char status;
+};
+
+typedef struct VirtIOBlock
+{
+ VirtIODevice vdev;
+ BlockDriverState *bs;
+} VirtIOBlock;
+
+static VirtIOBlock *to_virtio_blk(VirtIODevice *vdev)
+{
+ return (VirtIOBlock *)vdev;
+}
+
+static void virtio_blk_handle_output(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOBlock *s = to_virtio_blk(vdev);
+ VirtQueueElement elem;
+ unsigned int count;
+
+ while ((count = virtqueue_pop(vq, &elem)) != 0) {
+ struct virtio_blk_inhdr *in;
+ struct virtio_blk_outhdr *out;
+ unsigned int wlen;
+ off_t off;
+ int i;
+
+ out = (void *)elem.out_sg[0].iov_base;
+ in = (void *)elem.in_sg[elem.in_num - 1].iov_base;
+ off = out->sector;
+
+ if (out->type & VIRTIO_BLK_T_SCSI_CMD) {
+ wlen = sizeof(*in);
+ in->status = VIRTIO_BLK_S_UNSUPP;
+ } else if (out->type & VIRTIO_BLK_T_OUT) {
+ wlen = sizeof(*in);
+
+ for (i = 1; i < elem.out_num; i++) {
+ bdrv_write(s->bs, off,
+ elem.out_sg[i].iov_base,
+ elem.out_sg[i].iov_len / 512);
+ off += elem.out_sg[i].iov_len / 512;
+ }
+
+ in->status = VIRTIO_BLK_S_OK;
+ } else {
+ wlen = sizeof(*in);
+
+ for (i = 0; i < elem.in_num - 1; i++) {
+ bdrv_read(s->bs, off,
+ elem.in_sg[i].iov_base,
+ elem.in_sg[i].iov_len / 512);
+ off += elem.in_sg[i].iov_len / 512;
+ wlen += elem.in_sg[i].iov_len;
+ }
+
+ in->status = VIRTIO_BLK_S_OK;
+ }
+
+ virtqueue_push(vq, &elem, wlen);
+ virtio_notify(vdev, vq);
+ }
+}
+
+static void virtio_blk_update_config(VirtIODevice *vdev, uint8_t *config)
+{
+ VirtIOBlock *s = to_virtio_blk(vdev);
+ int64_t capacity;
+ uint32_t v;
+
+ bdrv_get_geometry(s->bs, &capacity);
+ memcpy(config + VIRTIO_CONFIG_BLK_F_CAPACITY, &capacity, sizeof(capacity));
+
+ v = VIRTQUEUE_MAX_SIZE - 2;
+ memcpy(config + VIRTIO_CONFIG_BLK_F_SEG_MAX, &v, sizeof(v));
+}
+
+static uint32_t virtio_blk_get_features(VirtIODevice *vdev)
+{
+ return (1 << VIRTIO_BLK_F_SEG_MAX);
+}
+
+VirtIODevice *virtio_blk_init(PCIBus *bus, uint16_t vendor, uint16_t device,
+ BlockDriverState *bs)
+{
+ VirtIOBlock *s;
+
+ s = (VirtIOBlock *)virtio_init_pci(bus, "virtio-blk", vendor, device,
+ vendor, VIRTIO_ID_BLOCK,
+ 16, sizeof(VirtIOBlock));
+
+ s->vdev.update_config = virtio_blk_update_config;
+ s->vdev.get_features = virtio_blk_get_features;
+ s->bs = bs;
+
+ virtio_add_queue(&s->vdev, virtio_blk_handle_output);
+
+ return &s->vdev;
+}
diff --git a/qemu/vl.h b/qemu/vl.h
index fafcf09..249ede2 100644
--- a/qemu/vl.h
+++ b/qemu/vl.h
@@ -1396,6 +1396,9 @@ void vmchannel_init(CharDriverState *hd, uint32_t deviceid, uint32_t index);

typedef struct VirtIODevice VirtIODevice;

+VirtIODevice *virtio_blk_init(PCIBus *bus, uint16_t vendor, uint16_t device,
+ BlockDriverState *bs);
+
/* buf = NULL means polling */
typedef int ADBDeviceRequest(ADBDevice *d, uint8_t *buf_out,
const uint8_t *buf, int len);