[OpenBIOS] [PATCH v2 3/4] drivers: add legacy virtio-blk driver

Mark Cave-Ayland mark.cave-ayland at ilande.co.uk
Sun Aug 19 12:55:05 CEST 2018


Note that as part of this commit we rename the virtio-blk device node from
"virtio-blk" to "scsi" to match up with the QEMU fw path generator.

Signed-off-by: Mark Cave-Ayland <mark.cave-ayland at ilande.co.uk>
---
 drivers/build.xml         |   1 +
 drivers/pci.c             |  18 ++
 drivers/pci_database.c    |   4 +-
 drivers/pci_database.h    |   1 +
 drivers/virtio.c          | 516 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/virtio.h          | 330 +++++++++++++++++++++++++++++
 include/drivers/drivers.h |   4 +
 7 files changed, 872 insertions(+), 2 deletions(-)
 create mode 100644 drivers/virtio.c
 create mode 100644 drivers/virtio.h

diff --git a/drivers/build.xml b/drivers/build.xml
index 8df074b..5a28bc2 100644
--- a/drivers/build.xml
+++ b/drivers/build.xml
@@ -28,6 +28,7 @@
   <object source="usbohci.c" condition="DRIVER_USB"/>
   <object source="usbohci_rh.c" condition="DRIVER_USB"/>
   <object source="lsi.c" condition="DRIVER_LSI_53C810"/>
+  <object source="virtio.c" condition="DRIVER_VIRTIO_BLK"/>
  </library>
 
  <dictionary name="openbios" target="forth">
diff --git a/drivers/pci.c b/drivers/pci.c
index 7a174b4..0168393 100644
--- a/drivers/pci.c
+++ b/drivers/pci.c
@@ -770,6 +770,24 @@ int sungem_config_cb (const pci_config_t *config)
 	return 0;
 }
 
+int virtio_blk_config_cb(const pci_config_t *config)
+{
+#ifdef CONFIG_DRIVER_VIRTIO_BLK
+	pci_addr addr;
+	uint8_t idx;
+
+	addr = PCI_ADDR(
+		PCI_BUS(config->dev),
+		PCI_DEV(config->dev),
+		PCI_FN(config->dev));
+
+	idx = (uint8_t)(pci_config_read16(addr, PCI_DEVICE_ID) & 0xff) - 1;
+
+	ob_virtio_init(config->path, "virtio-blk", arch->io_base, config->assigned[0] & ~0x0000000F, idx);
+#endif
+	return 0;
+}
+
 /*
  * "Designing PCI Cards and Drivers for Power Macintosh Computers", p. 454
  *
diff --git a/drivers/pci_database.c b/drivers/pci_database.c
index 8288acd..40fdeb6 100644
--- a/drivers/pci_database.c
+++ b/drivers/pci_database.c
@@ -49,10 +49,10 @@ static const pci_dev_t scsi_devices[] = {
     {
         /* Virtio-block controller */
         PCI_VENDOR_ID_REDHAT_QUMRANET, PCI_DEVICE_ID_VIRTIO_BLOCK,
-        NULL, "virtio-blk", NULL,
+        NULL, "scsi", NULL,
         "pci1af4,1001\0pci1af4,1001\0pciclass,01018f\0",
         0, 0, 0,
-        NULL, NULL,
+        virtio_blk_config_cb, NULL,
     },
     {
         /* lsi53c810 controller */
diff --git a/drivers/pci_database.h b/drivers/pci_database.h
index 6f5eb39..e39ebfb 100644
--- a/drivers/pci_database.h
+++ b/drivers/pci_database.h
@@ -29,6 +29,7 @@ struct pci_dev_t {
 };
 
 extern int ide_config_cb2(const pci_config_t *config);
+extern int virtio_blk_config_cb(const pci_config_t *config);
 extern int eth_config_cb(const pci_config_t *config);
 extern int macio_heathrow_config_cb(const pci_config_t *config);
 extern int macio_keylargo_config_cb(const pci_config_t *config);
diff --git a/drivers/virtio.c b/drivers/virtio.c
new file mode 100644
index 0000000..3bc127a
--- /dev/null
+++ b/drivers/virtio.c
@@ -0,0 +1,516 @@
+/*
+ * OpenBIOS Legacy Virtio driver
+ *
+ * Copyright (c) 2013 Alexander Graf <agraf at suse.de>
+ * Copyright (c) 2016 Mark Cave-Ayland <mark.cave-ayland at ilande.co.uk>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "config.h"
+#include "libc/byteorder.h"
+#include "libc/vsprintf.h"
+#include "libopenbios/bindings.h"
+#include "libopenbios/ofmem.h"
+#include "kernel/kernel.h"
+#include "drivers/drivers.h"
+
+#include "virtio.h"
+
+#define VRING_WAIT_REPLY_TIMEOUT 10000
+
+
+static uint8_t virtio_cfg_read8(VDev *vdev, int addr)
+{
+    return inb((uint32_t)(vdev->io_base + addr));
+}
+
+static void virtio_cfg_write8(VDev *vdev, int addr, uint8_t value)
+{
+    outb(value, vdev->io_base + addr);
+}
+
+static uint16_t virtio_cfg_read16(VDev *vdev, int addr)
+{
+    return inw(vdev->io_base + addr);
+}
+
+static void virtio_cfg_write16(VDev *vdev, int addr, uint16_t value)
+{
+    outw(value, vdev->io_base + addr);
+}
+
+static uint32_t virtio_cfg_read32(VDev *vdev, int addr)
+{
+    return inl(vdev->io_base + addr);
+}
+
+static void virtio_cfg_write32(VDev *vdev, int addr, uint32_t value)
+{
+    outl(value, vdev->io_base + addr);
+}
+
+static uint64_t virtio_cfg_read64(VDev *vdev, int addr)
+{
+    uint64_t q;
+    uint8_t *p;
+    int i;
+
+    for (i = 0, p = (uint8_t *)&q; i < 8; i++, p++) {
+        *p = virtio_cfg_read8(vdev, addr + i);
+    }
+
+    return q;
+}
+
+static long virtio_notify(VDev *vdev, int vq_idx, long cookie)
+{
+    virtio_cfg_write16(vdev, VIRTIO_PCI_QUEUE_NOTIFY, vq_idx);
+
+    return 0;
+}
+
+/***********************************************
+ *             Virtio functions                *
+ ***********************************************/
+
+static void vring_init(VRing *vr, VqInfo *info)
+{
+    void *p = (void *) (uintptr_t)info->queue;
+
+    vr->id = info->index;
+    vr->num = info->num;
+    vr->desc = p;
+    vr->avail = (void *)((uintptr_t)p + info->num * sizeof(VRingDesc));
+    vr->used = (void *)(((unsigned long)&vr->avail->ring[info->num]
+               + info->align - 1) & ~(info->align - 1));
+
+    /* Zero out all relevant field */
+    vr->avail->flags = 0;
+    vr->avail->idx = 0;
+
+    /* We're running with interrupts off anyways, so don't bother */
+    vr->used->flags = VRING_USED_F_NO_NOTIFY;
+    vr->used->idx = 0;
+    vr->used_idx = 0;
+    vr->next_idx = 0;
+    vr->cookie = 0;
+}
+
+static int vring_notify(VDev *vdev, VRing *vr)
+{
+    return virtio_notify(vdev, vr->id, vr->cookie);
+}
+
+static void vring_send_buf(VRing *vr, void *p, int len, int flags)
+{
+    /* For follow-up chains we need to keep the first entry point */
+    if (!(flags & VRING_HIDDEN_IS_CHAIN)) {
+        vr->avail->ring[vr->avail->idx % vr->num] = vr->next_idx;
+    }
+
+    vr->desc[vr->next_idx].addr = (uintptr_t)p;
+    vr->desc[vr->next_idx].len = len;
+    vr->desc[vr->next_idx].flags = flags & ~VRING_HIDDEN_IS_CHAIN;
+    vr->desc[vr->next_idx].next = vr->next_idx;
+    vr->desc[vr->next_idx].next++;
+    vr->next_idx++;
+
+    /* Chains only have a single ID */
+    if (!(flags & VRING_DESC_F_NEXT)) {
+        vr->avail->idx++;
+    }
+}
+
+static int vr_poll(VDev *vdev, VRing *vr)
+{
+    if (vr->used->idx == vr->used_idx) {
+        vring_notify(vdev, vr);
+        return 0;
+    }
+
+    vr->used_idx = vr->used->idx;
+    vr->next_idx = 0;
+    vr->desc[0].len = 0;
+    vr->desc[0].flags = 0;
+    return 1; /* vr has been updated */
+}
+
+/*
+ * Wait for the host to reply.
+ *
+ * timeout is in msecs if > 0.
+ *
+ * Returns 0 on success, 1 on timeout.
+ */
+static int vring_wait_reply(VDev *vdev)
+{
+    ucell target_ms, get_ms;
+
+    fword("get-msecs");
+    target_ms = POP();
+    target_ms += vdev->wait_reply_timeout;
+
+    /* Wait for any queue to be updated by the host */
+    do {
+        int i, r = 0;
+
+        for (i = 0; i < vdev->nr_vqs; i++) {
+            r += vr_poll(vdev, &vdev->vrings[i]);
+        }
+
+        if (r) {
+            return 0;
+        }
+
+        fword("get-msecs");
+        get_ms = POP();
+
+    } while (!vdev->wait_reply_timeout || (get_ms < target_ms));
+
+    return 1;
+}
+
+static void *vring_addr_translate(VDev *vdev, void *p)
+{
+    ucell phys, mode;
+
+    phys = ofmem_translate(pointer2cell(p), &mode);
+    return cell2pointer(phys);
+}
+
+/***********************************************
+ *               Virtio block                  *
+ ***********************************************/
+
+static int virtio_blk_read_many(VDev *vdev,
+                                uint64_t offset, void *load_addr, int len)
+{
+    VirtioBlkOuthdr out_hdr;
+    u8 status;
+    VRing *vr = &vdev->vrings[vdev->cmd_vr_idx];
+    uint8_t discard[VIRTIO_SECTOR_SIZE];
+
+    uint64_t start_sector = offset / virtio_get_block_size(vdev);
+    int head_len = offset & (virtio_get_block_size(vdev) - 1);
+    uint64_t end_sector = (offset + len + virtio_get_block_size(vdev) - 1) /
+                            virtio_get_block_size(vdev);
+    int tail_len = end_sector * virtio_get_block_size(vdev) - (offset + len);
+
+    /* Tell the host we want to read */
+    out_hdr.type = VIRTIO_BLK_T_IN;
+    out_hdr.ioprio = 99;
+    out_hdr.sector = virtio_sector_adjust(vdev, start_sector);
+
+    vring_send_buf(vr, vring_addr_translate(vdev, &out_hdr), sizeof(out_hdr),
+                   VRING_DESC_F_NEXT);
+
+    /* Discarded head */
+    if (head_len) {
+        vring_send_buf(vr, vring_addr_translate(vdev, &discard), head_len,
+                       VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN |
+                       VRING_DESC_F_NEXT);
+    }
+
+    /* This is where we want to receive data */
+    vring_send_buf(vr, vring_addr_translate(vdev, load_addr), len,
+                   VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN |
+                   VRING_DESC_F_NEXT);
+
+    /* Discarded tail */
+    if (tail_len) {
+        vring_send_buf(vr, vring_addr_translate(vdev, &discard), tail_len,
+                       VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN |
+                       VRING_DESC_F_NEXT);
+    }
+
+    /* status field */
+    vring_send_buf(vr, vring_addr_translate(vdev, &status), sizeof(u8),
+                   VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN);
+
+    /* Now we can tell the host to read */
+    vring_wait_reply(vdev);
+
+    return status;
+}
+
+int virtio_read_many(VDev *vdev, uint64_t offset, void *load_addr, int len)
+{
+    switch (vdev->senseid) {
+    case VIRTIO_ID_BLOCK:
+        return virtio_blk_read_many(vdev, offset, load_addr, len);
+    }
+    return -1;
+}
+
+static int virtio_read(VDev *vdev, uint64_t offset, void *load_addr, int len)
+{
+    return virtio_read_many(vdev, offset, load_addr, len);
+}
+
+int virtio_get_block_size(VDev *vdev)
+{
+    switch (vdev->senseid) {
+    case VIRTIO_ID_BLOCK:
+        return vdev->config.blk.blk_size << vdev->config.blk.physical_block_exp;
+    }
+    return 0;
+}
+
+static void
+ob_virtio_disk_open(VDev **_vdev)
+{
+    VDev *vdev = *_vdev;
+    phandle_t ph;
+
+    vdev->pos = 0;
+
+    /* interpose disk-label */
+    ph = find_dev("/packages/disk-label");
+    fword("my-args");
+    PUSH_ph( ph );
+    fword("interpose");
+
+    RET(-1);
+}
+
+static void
+ob_virtio_disk_close(VDev **_vdev)
+{
+    return;
+}
+
+/* ( pos.d -- status ) */
+static void
+ob_virtio_disk_seek(VDev **_vdev)
+{
+    VDev *vdev = *_vdev;
+    uint64_t pos;
+
+    pos = ((uint64_t)POP()) << 32;
+    pos |= POP();
+
+    /* Make sure we are within the physical limits */
+    if (pos < (vdev->config.blk.capacity * virtio_get_block_size(vdev))) {
+        vdev->pos = pos;
+        PUSH(0);
+    } else {
+        PUSH(1);
+    }
+
+    return;
+}
+
+/* ( addr len -- actual ) */
+static void
+ob_virtio_disk_read(VDev **_vdev)
+{
+    VDev *vdev = *_vdev;
+    ucell len = POP();
+    uint8_t *addr = (uint8_t *)POP();
+
+    virtio_read(vdev, vdev->pos, addr, len);
+
+    vdev->pos += len;
+
+    PUSH(len);
+}
+
+static void set_virtio_alias(const char *path, int idx)
+{
+    phandle_t aliases;
+    char name[9];
+
+    aliases = find_dev("/aliases");
+
+    snprintf(name, sizeof(name), "virtio%d", idx);
+
+    set_property(aliases, name, path, strlen(path) + 1);
+}
+
+static void
+ob_virtio_disk_initialize(VDev **_vdev)
+{
+    phandle_t ph = get_cur_dev();
+    VDev *vdev;
+    int len;
+
+    vdev = cell2pointer(get_int_property(ph, "_vdev", &len));
+    push_str("_vdev");
+    feval("delete-property");
+
+    *_vdev = vdev;
+}
+
+DECLARE_UNNAMED_NODE(ob_virtio_disk, 0, sizeof(VDev *));
+
+NODE_METHODS(ob_virtio_disk) = {
+    { NULL,        ob_virtio_disk_initialize    },
+    { "open",      ob_virtio_disk_open          },
+    { "close",     ob_virtio_disk_close         },
+    { "seek",      ob_virtio_disk_seek          },
+    { "read",      ob_virtio_disk_read          },
+};
+
+static void
+ob_virtio_open(VDev **_vdev)
+{
+    PUSH(-1);
+}
+
+static void
+ob_virtio_close(VDev **_vdev)
+{
+    return;
+}
+
+static void
+ob_virtio_vdev(VDev **_vdev)
+{
+    PUSH(pointer2cell(_vdev));
+}
+
+static void
+ob_virtio_dma_alloc(__attribute__((unused)) VDev **_vdev)
+{
+    call_parent_method("dma-alloc");
+}
+
+static void
+ob_virtio_dma_free(__attribute__((unused)) VDev **_vdev)
+{
+    call_parent_method("dma-free");
+}
+
+static void
+ob_virtio_dma_map_in(__attribute__((unused)) VDev **_vdev)
+{
+    call_parent_method("dma-map-in");
+}
+
+static void
+ob_virtio_dma_map_out(__attribute__((unused)) VDev **_vdev)
+{
+    call_parent_method("dma-map-out");
+}
+
+static void
+ob_virtio_dma_sync(__attribute__((unused)) VDev **_vdev)
+{
+    call_parent_method("dma-sync");
+}
+
+DECLARE_UNNAMED_NODE(ob_virtio, 0, sizeof(VDev *));
+
+NODE_METHODS(ob_virtio) = {
+    { "open",          ob_virtio_open        },
+    { "close",         ob_virtio_close       },
+    { "vdev",          ob_virtio_vdev        },
+    { "dma-alloc",     ob_virtio_dma_alloc   },
+    { "dma-free",      ob_virtio_dma_free    },
+    { "dma-map-in",    ob_virtio_dma_map_in  },
+    { "dma-map-out",   ob_virtio_dma_map_out },
+    { "dma-sync",      ob_virtio_dma_sync    },
+};
+
+void ob_virtio_init(const char *path, const char *dev_name, uint64_t base,
+                    uint64_t offset, int idx)
+{
+    char buf[256];
+    phandle_t ph;
+    ucell addr;
+    int i;
+    uint8_t status;
+    VDev *vdev, **_vdev;
+
+    REGISTER_NODE_METHODS(ob_virtio, path);
+
+    /* Open ob_virtio */
+    fword("my-self");
+    push_str(path);
+    feval("open-dev to my-self");
+
+    ph = find_ih_method("vdev", my_self());
+    PUSH(ph);
+    fword("execute");
+    _vdev = cell2pointer(POP());
+
+    vdev = malloc(sizeof(VDev));
+    vdev->io_base = offset;
+
+    /* Indicate we recognise the device */
+    status = virtio_cfg_read8(vdev, VIRTIO_PCI_STATUS);
+    status |= VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER;
+    virtio_cfg_write8(vdev, VIRTIO_PCI_STATUS, status);
+
+    vdev->senseid = VIRTIO_ID_BLOCK;
+    vdev->nr_vqs = 1;
+    vdev->cmd_vr_idx = 0;
+    vdev->wait_reply_timeout = VRING_WAIT_REPLY_TIMEOUT;
+    vdev->scsi_block_size = VIRTIO_SCSI_BLOCK_SIZE;
+    vdev->blk_factor = 1;
+
+    PUSH(sizeof(VRing) * VIRTIO_MAX_VQS);
+    feval("dma-alloc");
+    addr = POP();
+    vdev->vrings = cell2pointer(addr);
+
+    PUSH(VIRTIO_RING_SIZE * VIRTIO_MAX_VQS);
+    feval("dma-alloc");
+    addr = POP();
+    vdev->ring_area = cell2pointer(addr);
+
+    for (i = 0; i < vdev->nr_vqs; i++) {
+        VqInfo info = {
+            .queue = (uintptr_t) vdev->ring_area + (i * VIRTIO_RING_SIZE),
+            .align = VIRTIO_PCI_VRING_ALIGN,
+            .index = i,
+            .num = 0,
+        };
+
+        virtio_cfg_write16(vdev, VIRTIO_PCI_QUEUE_SEL, i);
+        info.num = virtio_cfg_read16(vdev, VIRTIO_PCI_QUEUE_NUM);
+
+        vring_init(&vdev->vrings[i], &info);
+
+        /* Set block information */
+        vdev->guessed_disk_nature = VIRTIO_GDN_NONE;
+        vdev->config.blk.blk_size = VIRTIO_SECTOR_SIZE;
+        vdev->config.blk.physical_block_exp = 0;
+
+        /* Read sectors */
+        vdev->config.blk.capacity = virtio_cfg_read64(vdev, 0x14);
+
+        /* Set queue address */
+        virtio_cfg_read32(vdev, VIRTIO_PCI_QUEUE_PFN);
+        virtio_cfg_write32(vdev, VIRTIO_PCI_QUEUE_PFN,
+                           va2pa(info.queue) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT);
+    }
+
+    *_vdev = vdev;
+    feval("to my-self");
+
+    /* Initialisation complete */
+    status |= VIRTIO_CONFIG_S_DRIVER_OK;
+    virtio_cfg_write8(vdev, VIRTIO_PCI_STATUS, status);
+
+    fword("new-device");
+    push_str("disk");
+    fword("device-name");
+    push_str("block");
+    fword("device-type");
+
+    PUSH(pointer2cell(vdev));
+    fword("encode-int");
+    push_str("_vdev");
+    fword("property");
+
+    fword("finish-device");
+
+    snprintf(buf, sizeof(buf), "%s/disk", path);
+    REGISTER_NODE_METHODS(ob_virtio_disk, buf);
+
+    set_virtio_alias(buf, idx);
+}
diff --git a/drivers/virtio.h b/drivers/virtio.h
new file mode 100644
index 0000000..414d5f9
--- /dev/null
+++ b/drivers/virtio.h
@@ -0,0 +1,330 @@
+/*
+ * Virtio driver bits
+ *
+ * Copyright (c) 2013 Alexander Graf <agraf at suse.de>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#ifndef VIRTIO_H
+#define VIRTIO_H
+
+/* A 32-bit r/o bitmask of the features supported by the host */
+#define VIRTIO_PCI_HOST_FEATURES        0
+
+/* A 32-bit r/w bitmask of features activated by the guest */
+#define VIRTIO_PCI_GUEST_FEATURES       4
+
+/* A 32-bit r/w PFN for the currently selected queue */
+#define VIRTIO_PCI_QUEUE_PFN            8
+
+/* A 16-bit r/o queue size for the currently selected queue */
+#define VIRTIO_PCI_QUEUE_NUM            12
+
+/* A 16-bit r/w queue selector */
+#define VIRTIO_PCI_QUEUE_SEL            14
+
+/* A 16-bit r/w queue notifier */
+#define VIRTIO_PCI_QUEUE_NOTIFY         16
+
+/* An 8-bit device status register.  */
+#define VIRTIO_PCI_STATUS               18
+
+/* An 8-bit r/o interrupt status register.  Reading the value will return the
+ * current contents of the ISR and will also clear it.  This is effectively
+ * a read-and-acknowledge. */
+#define VIRTIO_PCI_ISR                  19
+
+/* MSI-X registers: only enabled if MSI-X is enabled. */
+/* A 16-bit vector for configuration changes. */
+#define VIRTIO_MSI_CONFIG_VECTOR        20
+/* A 16-bit vector for selected queue notifications. */
+#define VIRTIO_MSI_QUEUE_VECTOR         22
+
+/* How many bits to shift physical queue address written to QUEUE_PFN.
+ * 12 is historical, and due to x86 page size. */
+#define VIRTIO_PCI_QUEUE_ADDR_SHIFT     12
+
+/* The alignment to use between consumer and producer parts of vring.
+ * x86 pagesize again. */
+#define VIRTIO_PCI_VRING_ALIGN          4096
+
+/* Status byte for guest to report progress, and synchronize features. */
+/* We have seen device and processed generic fields (VIRTIO_CONFIG_F_VIRTIO) */
+#define VIRTIO_CONFIG_S_ACKNOWLEDGE     1
+/* We have found a driver for the device. */
+#define VIRTIO_CONFIG_S_DRIVER          2
+/* Driver has used its parts of the config, and is happy */
+#define VIRTIO_CONFIG_S_DRIVER_OK       4
+/* We've given up on this device. */
+#define VIRTIO_CONFIG_S_FAILED          0x80
+
+enum VirtioDevType {
+    VIRTIO_ID_NET = 1,
+    VIRTIO_ID_BLOCK = 2,
+    VIRTIO_ID_CONSOLE = 3,
+    VIRTIO_ID_BALLOON = 5,
+    VIRTIO_ID_SCSI = 8,
+};
+typedef enum VirtioDevType VirtioDevType;
+
+struct VirtioDevHeader {
+    VirtioDevType type:8;
+    uint8_t num_vq;
+    uint8_t feature_len;
+    uint8_t config_len;
+    uint8_t status;
+    uint8_t vqconfig[];
+} __attribute__((packed));
+typedef struct VirtioDevHeader VirtioDevHeader;
+
+struct VirtioVqConfig {
+    uint64_t token;
+    uint64_t address;
+    uint16_t num;
+    uint8_t pad[6];
+} __attribute__((packed));
+typedef struct VirtioVqConfig VirtioVqConfig;
+
+struct VqInfo {
+    uint32_t queue;
+    uint32_t align;
+    uint16_t index;
+    uint16_t num;
+} __attribute__((packed));
+typedef struct VqInfo VqInfo;
+
+struct VqConfig {
+    uint16_t index;
+    uint16_t num;
+} __attribute__((packed));
+typedef struct VqConfig VqConfig;
+
+struct VirtioDev {
+    VirtioDevHeader *header;
+    VirtioVqConfig *vqconfig;
+    char *host_features;
+    char *guest_features;
+    char *config;
+};
+typedef struct VirtioDev VirtioDev;
+
+#define VIRTIO_RING_SIZE            (PAGE_SIZE * 8)
+#define VIRTIO_MAX_VQS              3
+#define KVM_S390_VIRTIO_RING_ALIGN  4096
+
+#define VRING_USED_F_NO_NOTIFY  1
+
+/* This marks a buffer as continuing via the next field. */
+#define VRING_DESC_F_NEXT       1
+/* This marks a buffer as write-only (otherwise read-only). */
+#define VRING_DESC_F_WRITE      2
+/* This means the buffer contains a list of buffer descriptors. */
+#define VRING_DESC_F_INDIRECT   4
+
+/* Internal flag to mark follow-up segments as such */
+#define VRING_HIDDEN_IS_CHAIN   256
+
+/* Virtio ring descriptors: 16 bytes.  These can chain together via "next". */
+struct VRingDesc {
+    /* Address (guest-physical). */
+    uint64_t addr;
+    /* Length. */
+    uint32_t len;
+    /* The flags as indicated above. */
+    uint16_t flags;
+    /* We chain unused descriptors via this, too */
+    uint16_t next;
+} __attribute__((packed));
+typedef struct VRingDesc VRingDesc;
+
+struct VRingAvail {
+    uint16_t flags;
+    uint16_t idx;
+    uint16_t ring[];
+} __attribute__((packed));
+typedef struct VRingAvail VRingAvail;
+
+/* uint32_t is used here for ids for padding reasons. */
+struct VRingUsedElem {
+    /* Index of start of used descriptor chain. */
+    uint32_t id;
+    /* Total length of the descriptor chain which was used (written to) */
+    uint32_t len;
+} __attribute__((packed));
+typedef struct VRingUsedElem VRingUsedElem;
+
+struct VRingUsed {
+    uint16_t flags;
+    uint16_t idx;
+    VRingUsedElem ring[];
+} __attribute__((packed));
+typedef struct VRingUsed VRingUsed;
+
+struct VRing {
+    unsigned int num;
+    int next_idx;
+    int used_idx;
+    VRingDesc *desc;
+    VRingAvail *avail;
+    VRingUsed *used;
+    long cookie;
+    int id;
+};
+typedef struct VRing VRing;
+
+
+/***********************************************
+ *               Virtio block                  *
+ ***********************************************/
+
+/*
+ * Command types
+ *
+ * Usage is a bit tricky as some bits are used as flags and some are not.
+ *
+ * Rules:
+ *   VIRTIO_BLK_T_OUT may be combined with VIRTIO_BLK_T_SCSI_CMD or
+ *   VIRTIO_BLK_T_BARRIER.  VIRTIO_BLK_T_FLUSH is a command of its own
+ *   and may not be combined with any of the other flags.
+ */
+
+/* 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
+
+/* Cache flush command */
+#define VIRTIO_BLK_T_FLUSH      4
+
+/* Barrier before this op. */
+#define VIRTIO_BLK_T_BARRIER    0x80000000
+
+/* This is the first element of the read scatter-gather list. */
+struct VirtioBlkOuthdr {
+        /* VIRTIO_BLK_T* */
+        uint32_t type;
+        /* io priority. */
+        uint32_t ioprio;
+        /* Sector (ie. 512 byte offset) */
+        uint64_t sector;
+};
+typedef struct VirtioBlkOuthdr VirtioBlkOuthdr;
+
+struct VirtioBlkConfig {
+    uint64_t capacity; /* in 512-byte sectors */
+    uint32_t size_max; /* max segment size (if VIRTIO_BLK_F_SIZE_MAX) */
+    uint32_t seg_max;  /* max number of segments (if VIRTIO_BLK_F_SEG_MAX) */
+
+    struct VirtioBlkGeometry {
+        uint16_t cylinders;
+        uint8_t heads;
+        uint8_t sectors;
+    } geometry; /* (if VIRTIO_BLK_F_GEOMETRY) */
+
+    uint32_t blk_size; /* block size of device (if VIRTIO_BLK_F_BLK_SIZE) */
+
+    /* the next 4 entries are guarded by VIRTIO_BLK_F_TOPOLOGY  */
+    uint8_t physical_block_exp; /* exponent for physical blk per logical blk */
+    uint8_t alignment_offset;   /* alignment offset in logical blocks */
+    uint16_t min_io_size;       /* min I/O size without performance penalty
+                              in logical blocks */
+    uint32_t opt_io_size;       /* optimal sustained I/O size in logical blks */
+
+    uint8_t wce; /* writeback mode (if VIRTIO_BLK_F_CONFIG_WCE) */
+} __attribute__((packed));
+typedef struct VirtioBlkConfig VirtioBlkConfig;
+
+enum guessed_disk_nature_type {
+    VIRTIO_GDN_NONE     = 0,
+    VIRTIO_GDN_DASD     = 1,
+    VIRTIO_GDN_CDROM    = 2,
+    VIRTIO_GDN_SCSI     = 3,
+};
+typedef enum guessed_disk_nature_type VirtioGDN;
+
+#define VIRTIO_SECTOR_SIZE 512
+#define VIRTIO_ISO_BLOCK_SIZE 2048
+#define VIRTIO_SCSI_BLOCK_SIZE 512
+
+struct VirtioScsiConfig {
+    uint32_t num_queues;
+    uint32_t seg_max;
+    uint32_t max_sectors;
+    uint32_t cmd_per_lun;
+    uint32_t event_info_size;
+    uint32_t sense_size;
+    uint32_t cdb_size;
+    uint16_t max_channel;
+    uint16_t max_target;
+    uint32_t max_lun;
+} __attribute__((packed));
+typedef struct VirtioScsiConfig VirtioScsiConfig;
+
+struct ScsiDevice {
+    uint16_t channel;   /* Always 0 in QEMU     */
+    uint16_t target;    /* will be scanned over */
+    uint32_t lun;       /* will be reported     */
+};
+typedef struct ScsiDevice ScsiDevice;
+
+struct VDev {
+    uint32_t io_base;
+    uint64_t pos;
+    int nr_vqs;
+    VRing *vrings;
+    int cmd_vr_idx;
+    void *ring_area;
+    long wait_reply_timeout;
+    VirtioGDN guessed_disk_nature;
+    int senseid;
+    union {
+        VirtioBlkConfig blk;
+        VirtioScsiConfig scsi;
+    } config;
+    ScsiDevice *scsi_device;
+    int is_cdrom;
+    int scsi_block_size;
+    int blk_factor;
+    uint64_t scsi_last_block;
+    uint32_t scsi_dev_cyls;
+    uint8_t scsi_dev_heads;
+    int scsi_device_selected;
+    ScsiDevice selected_scsi_device;
+};
+typedef struct VDev VDev;
+
+extern int virtio_get_block_size(VDev *vdev);
+extern uint8_t virtio_get_heads(VDev *vdev);
+extern uint8_t virtio_get_sectors(VDev *vdev);
+extern uint64_t virtio_get_blocks(VDev *vdev);
+
+static inline uint64_t virtio_sector_adjust(VDev *vdev, uint64_t sector)
+{
+    return sector * (virtio_get_block_size(vdev) / VIRTIO_SECTOR_SIZE);
+}
+
+VirtioGDN virtio_guessed_disk_nature(VDev *vdev);
+void virtio_assume_scsi(VDev *vdev);
+void virtio_assume_eckd(VDev *vdev);
+void virtio_assume_iso9660(VDev *vdev);
+
+extern int virtio_disk_is_scsi(VDev *vdev);
+extern int virtio_disk_is_eckd(VDev *vdev);
+
+int virtio_read_many(VDev *vdev, uint64_t sector, void *load_addr, int sec_num);
+VDev *virtio_get_device(void);
+VirtioDevType virtio_get_device_type(void);
+
+struct VirtioCmd {
+    void *data;
+    int size;
+    int flags;
+};
+typedef struct VirtioCmd VirtioCmd;
+
+#endif /* VIRTIO_H */
diff --git a/include/drivers/drivers.h b/include/drivers/drivers.h
index 117429e..75172c2 100644
--- a/include/drivers/drivers.h
+++ b/include/drivers/drivers.h
@@ -139,6 +139,10 @@ int keyboard_dataready(void);
 unsigned char keyboard_readdata(void);
 #endif
 #endif
+#ifdef CONFIG_DRIVER_VIRTIO_BLK
+void ob_virtio_init(const char *path, const char *dev_name, uint64_t base,
+                    uint64_t offset, int idx);
+#endif
 int macio_get_nvram_size(void);
 void macio_nvram_put(char *buf);
 void macio_nvram_get(char *buf);
-- 
2.11.0




More information about the OpenBIOS mailing list