Implemented virtio-block
authorKyle Milka <kmilka1995@gmail.com>
Fri, 22 Jul 2016 16:09:51 +0000 (09:09 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 26 Jul 2016 22:35:26 +0000 (18:35 -0400)
We were able to get various Linux kernels to mount a disk image. Used
linux/lguest.c and virtio_net.c as starting points. This included adding
the block device struct and request and init functions.

Change-Id: I91a603d5a6e9c27c87a29d0784de6fe17cc94916
Signed-off-by: Kyle Milka <kmilka@google.com>
Signed-off-by: Gan Shun Lim <ganshun@google.com>
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
tests/vmm/vmrunkernel.c
user/vmm/include/vmm/virtio_blk.h
user/vmm/include/vmm/vmm.h
user/vmm/virtio.c
user/vmm/virtio_blk.c [new file with mode: 0644]

index 034b4fc..609f7d2 100644 (file)
@@ -23,6 +23,7 @@
 #include <vmm/linux_bootparam.h>
 
 #include <vmm/virtio.h>
+#include <vmm/virtio_blk.h>
 #include <vmm/virtio_mmio.h>
 #include <vmm/virtio_ids.h>
 #include <vmm/virtio_config.h>
@@ -252,6 +253,36 @@ static struct virtio_vq_dev net_vqdev = {
        }
 };
 
+static struct virtio_mmio_dev blk_mmio_dev = {
+       .poke_guest = virtio_poke_guest,
+};
+
+static struct virtio_blk_config blk_cfg = {
+};
+
+static struct virtio_blk_config blk_cfg_d = {
+};
+
+static struct virtio_vq_dev blk_vqdev = {
+       .name = "block",
+       .dev_id = VIRTIO_ID_BLOCK,
+       .dev_feat = (1ULL << VIRTIO_F_VERSION_1),
+
+       .num_vqs = 1,
+       .cfg = &blk_cfg,
+       .cfg_d = &blk_cfg_d,
+       .cfg_sz = sizeof(struct virtio_blk_config),
+       .transport_dev = &blk_mmio_dev,
+       .vqs = {
+               {
+                       .name = "blk_request",
+                       .qnum_max = 64,
+                       .srv_fn = blk_request,
+                       .vqdev = &blk_vqdev
+               },
+       }
+};
+
 void lowmem() {
        __asm__ __volatile__ (".section .lowmem, \"aw\"\n\tlow: \n\t.=0x1000\n\t.align 0x100000\n\t.previous\n");
 }
@@ -346,6 +377,7 @@ int main(int argc, char **argv)
        uint64_t tsc_freq_khz;
        char *cmdlinep;
        int cmdlinesz, len;
+       char *disk_image_file = NULL;
 
        fprintf(stderr, "%p %p %p %p\n", PGSIZE, PGSHIFT, PML1_SHIFT,
                        PML1_PTE_REACH);
@@ -384,6 +416,7 @@ int main(int argc, char **argv)
        argc--, argv++;
        // switches ...
        // Sorry, I don't much like the gnu opt parsing code.
+       // TODO(dcross): Convert this to use getopt()
        while (1) {
                if (*argv[0] != '-')
                        break;
@@ -407,6 +440,10 @@ int main(int argc, char **argv)
                case 's':       /* scp */
                        parlib_wants_to_be_mcp = FALSE;
                        break;
+               case 'f':       /* file to pass to blk_init */
+                       argc--; argv++;
+                       disk_image_file = *argv;
+                       break;
                default:
                        fprintf(stderr, "BMAFR\n");
                        break;
@@ -600,6 +637,14 @@ int main(int argc, char **argv)
        net_mmio_dev.vqdev = &net_vqdev;
        vm->virtio_mmio_devices[VIRTIO_MMIO_NETWORK_DEV] = &net_mmio_dev;
 
+       if (disk_image_file != NULL) {
+               blk_mmio_dev.addr =
+                   virtio_mmio_base_addr + PGSIZE * VIRTIO_MMIO_BLOCK_DEV;
+               blk_mmio_dev.vqdev = &blk_vqdev;
+               vm->virtio_mmio_devices[VIRTIO_MMIO_BLOCK_DEV] = &blk_mmio_dev;
+               blk_init_fn(&blk_vqdev, disk_image_file);
+       }
+
        net_init_fn(&net_vqdev, default_nic);
 
        /* Set the kernel command line parameters */
index dbaaca4..b39d855 100644 (file)
@@ -144,3 +144,6 @@ struct virtio_scsi_inhdr {
 #define VIRTIO_BLK_S_OK                0
 #define VIRTIO_BLK_S_IOERR     1
 #define VIRTIO_BLK_S_UNSUPP    2
+
+void blk_request(void *_vq);
+void blk_init_fn(struct virtio_vq_dev *vqdev, const char *filename);
index ca1d6ec..fdfb24d 100644 (file)
@@ -14,6 +14,7 @@
 enum {
        VIRTIO_MMIO_CONSOLE_DEV,
        VIRTIO_MMIO_NETWORK_DEV,
+       VIRTIO_MMIO_BLOCK_DEV,
 
        /* This should always be the last entry. */
        VIRTIO_MMIO_MAX_NUM_DEV,
index f64d1e2..42e30c1 100644 (file)
@@ -37,6 +37,8 @@ const char *virtio_validate_feat(struct virtio_vq_dev *vqdev, uint64_t feat)
                        // either the device can set its own MAC Address (as it does now)
                        // or the driver can set it using a controller thread.
                        break;
+               case VIRTIO_ID_BLOCK:
+                       break;
                case 0:
                        return "Invalid device id (0x0)! On the MMIO transport, this value indicates that the device is a system memory map with placeholder devices at static, well known addresses. In any case, this is not something you validate features for.";
                default:
diff --git a/user/vmm/virtio_blk.c b/user/vmm/virtio_blk.c
new file mode 100644 (file)
index 0000000..c54592f
--- /dev/null
@@ -0,0 +1,136 @@
+#define _LARGEFILE64_SOURCE /* See feature_test_macros(7) */
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <vmm/virtio.h>
+#include <vmm/virtio_blk.h>
+#include <vmm/virtio_mmio.h>
+
+int debug_virtio_blk;
+
+#define DPRINTF(fmt, ...)                                                      \
+       do {                                                                       \
+       if (debug_virtio_blk) {                                                    \
+               fprintf(stderr, "virtio_blk: " fmt, ##__VA_ARGS__);                    \
+       }                                                                          \
+       } while (0)
+
+/* TODO(ganshun): multiple disks */
+static int diskfd;
+
+void blk_init_fn(struct virtio_vq_dev *vqdev, const char *filename)
+{
+       struct virtio_blk_config *cfg = vqdev->cfg;
+       struct virtio_blk_config *cfg_d = vqdev->cfg_d;
+       uint64_t len;
+       struct stat stat_result;
+
+       diskfd = open(filename, O_RDWR);
+       if (diskfd < 0)
+               VIRTIO_DEV_ERRX(vqdev, "Could not open disk image file %s", filename);
+
+       if (stat(filename, &stat_result) == -1)
+               VIRTIO_DEV_ERRX(vqdev, "Could not stat file %s", filename);
+       len = stat_result.st_size / 512;
+
+       cfg->capacity = len;
+       cfg_d->capacity = len;
+}
+
+void blk_request(void *_vq)
+{
+       struct virtio_vq *vq = _vq;
+
+       assert(vq != NULL);
+
+       struct virtio_mmio_dev *dev = vq->vqdev->transport_dev;
+       struct iovec *iov;
+       uint32_t head;
+       uint32_t olen, ilen;
+       struct virtio_blk_outhdr *out;
+       uint64_t offset;
+       int64_t ret;
+       size_t wlen;
+       uint8_t *status;
+       struct virtio_blk_config *cfg = vq->vqdev->cfg;
+
+       if (vq->qready != 0x1)
+               VIRTIO_DEV_ERRX(vq->vqdev,
+                               "The service function for queue '%s' was launched before the driver set QueueReady to 0x1.",
+                                vq->name);
+
+       if (!dev->poke_guest)
+               VIRTIO_DEV_ERRX(vq->vqdev,
+                               "The 'poke_guest' function pointer was not set.");
+
+       iov = malloc(vq->qnum_max * sizeof(struct iovec));
+       if (iov == NULL)
+               VIRTIO_DEV_ERRX(vq->vqdev,
+                               "malloc returned null trying to allocate iov.\n");
+
+       for (;;) {
+               head = virtio_next_avail_vq_desc(vq, iov, &olen, &ilen);
+               /* There are always three iovecs.
+                * The first is the header.
+                * The second is the actual data.
+                * The third contains just the status byte.
+                */
+
+               status = iov[2].iov_base;
+               if (!status)
+                       VIRTIO_DEV_ERRX(vq->vqdev, "no room for status\n");
+
+               out = iov[0].iov_base;
+               if (out->type & VIRTIO_BLK_T_FLUSH)
+                       VIRTIO_DEV_ERRX(vq->vqdev, "Flush not supported.\n");
+
+               offset = out->sector * 512;
+               if (lseek64(diskfd, offset, SEEK_SET) != offset)
+                       VIRTIO_DEV_ERRX(vq->vqdev, "Bad seek at sector %llu\n",
+                                       out->sector);
+
+               if (out->type & VIRTIO_BLK_T_OUT) {
+
+                       if ((offset + iov[1].iov_len) > (cfg->capacity * 512))
+                               VIRTIO_DEV_ERRX(vq->vqdev, "write past end of file!\n");
+
+                       ret = writev(diskfd, &iov[1], 1);
+
+                       if (ret >= 0 && ret == iov[1].iov_len)
+                               *status = VIRTIO_BLK_S_OK;
+                       else
+                               *status = VIRTIO_BLK_S_IOERR;
+                       wlen = sizeof(*status);
+               } else {
+                       ret = readv(diskfd, &iov[1], 1);
+                       if (ret >= 0) {
+                               wlen = sizeof(*status) + ret;
+                               *status = VIRTIO_BLK_S_OK;
+                       } else {
+                               wlen = sizeof(*status);
+                               *status = VIRTIO_BLK_S_IOERR;
+                       }
+
+                       // Hexdump for debugging.
+                       if (debug_virtio_blk && ret >= 0) {
+                               char *pf = "";
+
+                               for (int i = 0; i < iov[olen].iov_len; i += 2) {
+                                       uint8_t *p = (uint8_t *)iov[olen].iov_base + i;
+
+                                       fprintf(stderr, "%s%02x", pf, *(p + 1));
+                                       fprintf(stderr, "%02x", *p);
+                                       fprintf(stderr, " ");
+                                       pf = ((i + 2) % 16) ? " " : "\n";
+                               }
+                       }
+               }
+
+               virtio_add_used_desc(vq, head, wlen);
+               virtio_mmio_set_vring_irq(dev);
+               dev->poke_guest(dev->vec);
+       }
+}