Note that as part of this commit we rename the legacy 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@ilande.co.uk --- drivers/build.xml | 1 + drivers/pci.c | 63 ++++++ drivers/pci_database.c | 14 +- drivers/pci_database.h | 1 + drivers/virtio.c | 555 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/virtio.h | 370 +++++++++++++++++++++++++++++++ include/drivers/drivers.h | 5 + include/drivers/pci.h | 6 + 8 files changed, 1012 insertions(+), 3 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..43eb913 100644 --- a/drivers/pci.c +++ b/drivers/pci.c @@ -37,6 +37,9 @@ #ifdef CONFIG_DRIVER_USB #include "drivers/usb.h" #endif +#ifdef CONFIG_DRIVER_VIRTIO_BLK +#include "virtio.h" +#endif
#if defined (CONFIG_DEBUG_PCI) # define PCI_DPRINTF(format, ...) printk(format, ## __VA_ARGS__) @@ -770,6 +773,66 @@ 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, cap_idx, cap_vndr; + uint8_t cfg_type, bar; + uint16_t status; + uint32_t offset, notify_mult = 0; + uint64_t common_cfg = 0, device_cfg = 0, notify_base = 0; + + 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; + + /* Check PCI capabilties: if they don't exist then we're certainly not + a 1.0 device */ + status = pci_config_read16(addr, PCI_STATUS); + if (!(status & PCI_STATUS_CAP_LIST)) { + return 0; + } + + /* Locate VIRTIO_PCI_CAP_COMMON_CFG and VIRTIO_PCI_CAP_DEVICE_CFG */ + cap_idx = pci_config_read8(addr, PCI_CAPABILITY_LIST); + while ((cap_vndr = pci_config_read8(addr, cap_idx)) != 0) { + if (cap_vndr == PCI_CAP_ID_VNDR) { + cfg_type = pci_config_read8(addr, cap_idx + 0x3); + bar = pci_config_read8(addr, cap_idx + 0x4); + offset = pci_config_read32(addr, cap_idx + 0x8); + + switch (cfg_type) { + case VIRTIO_PCI_CAP_COMMON_CFG: + common_cfg = arch->host_pci_base + (config->assigned[bar] & ~0x0000000F) + offset; + break; + case VIRTIO_PCI_CAP_NOTIFY_CFG: + notify_base = arch->host_pci_base + (config->assigned[bar] & ~0x0000000F) + offset; + notify_mult = pci_config_read32(addr, cap_idx + 16); + break; + case VIRTIO_PCI_CAP_DEVICE_CFG: + device_cfg = arch->host_pci_base + (config->assigned[bar] & ~0x0000000F) + offset; + break; + } + } + + cap_idx = pci_config_read8(addr, cap_idx + 1); + } + + /* If we didn't find the required configuration then exit */ + if (common_cfg == 0 || device_cfg == 0 || notify_base == 0) { + return 0; + } + + ob_virtio_init(config->path, "virtio-blk", common_cfg, device_cfg, + notify_base, notify_mult, 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..14ebd29 100644 --- a/drivers/pci_database.c +++ b/drivers/pci_database.c @@ -47,12 +47,20 @@ static const pci_subclass_t undef_subclass[] = {
static const pci_dev_t scsi_devices[] = { { - /* Virtio-block controller */ + /* Legacy 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, + }, + { + /* Modern virtio-block controller */ + PCI_VENDOR_ID_REDHAT_QUMRANET, PCI_DEVICE_ID_VIRTIO_BLOCK + 0x41, + NULL, "scsi", NULL, + "pci1af4,1042\0pci1af4,1042\0pciclass,01018f\0", + 0, 0, 0, + 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..171db5a --- /dev/null +++ b/drivers/virtio.c @@ -0,0 +1,555 @@ +/* + * OpenBIOS virtio-1.0 virtio-blk driver + * + * Copyright (c) 2013 Alexander Graf agraf@suse.de + * Copyright (c) 2018 Mark Cave-Ayland mark.cave-ayland@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(uint64_t cfg_addr, int addr) +{ + return in_8((uint8_t *)(uintptr_t)(cfg_addr + addr)); +} + +static void virtio_cfg_write8(uint64_t cfg_addr, int addr, uint8_t value) +{ + out_8((uint8_t *)(uintptr_t)(cfg_addr + addr), value); +} + +static uint16_t virtio_cfg_read16(uint64_t cfg_addr, int addr) +{ + return in_le16((uint16_t *)(uintptr_t)(cfg_addr + addr)); +} + +static void virtio_cfg_write16(uint64_t cfg_addr, int addr, uint16_t value) +{ + out_le16((uint16_t *)(uintptr_t)(cfg_addr + addr), value); +} + +static uint32_t virtio_cfg_read32(uint64_t cfg_addr, int addr) +{ + return in_le32((uint32_t *)(uintptr_t)(cfg_addr + addr)); +} + +static void virtio_cfg_write32(uint64_t cfg_addr, int addr, uint32_t value) +{ + out_le32((uint32_t *)(uintptr_t)(cfg_addr + addr), value); +} + +static uint64_t virtio_cfg_read64(uint64_t cfg_addr, int addr) +{ + uint64_t q = ((uint64_t)virtio_cfg_read32(cfg_addr + 4, addr) << 32); + q |= virtio_cfg_read32(cfg_addr, addr); + + return q; +} + +static void virtio_cfg_write64(uint64_t cfg_addr, int addr, uint64_t value) +{ + virtio_cfg_write32(cfg_addr, addr, (value & 0xffffffff)); + virtio_cfg_write32(cfg_addr, addr + 4, ((value >> 32) & 0xffffffff)); +} + +static long virtio_notify(VDev *vdev, int vq_idx, long cookie) +{ + uint16_t notify_offset = virtio_cfg_read16(vdev->common_cfg, VIRTIO_PCI_COMMON_Q_NOFF); + + virtio_cfg_write16(vdev->notify_base, notify_offset + + vq_idx * vdev->notify_mult, 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 = __cpu_to_le16(0); + vr->avail->idx = __cpu_to_le16(0); + + /* We're running with interrupts off anyways, so don't bother */ + vr->used->flags = __cpu_to_le16(VRING_USED_F_NO_NOTIFY); + vr->used->idx = __cpu_to_le16(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, uint64_t 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[__le16_to_cpu(vr->avail->idx) % vr->num] = __cpu_to_le16(vr->next_idx); + } + + vr->desc[vr->next_idx].addr = __cpu_to_le64(p); + vr->desc[vr->next_idx].len = __cpu_to_le32(len); + vr->desc[vr->next_idx].flags = __cpu_to_le16(flags & ~VRING_HIDDEN_IS_CHAIN); + vr->desc[vr->next_idx].next = __cpu_to_le16(vr->next_idx); + vr->desc[vr->next_idx].next = __cpu_to_le16(__le16_to_cpu(vr->desc[vr->next_idx].next) + 1); + vr->next_idx++; + + /* Chains only have a single ID */ + if (!(flags & VRING_DESC_F_NEXT)) { + vr->avail->idx = __cpu_to_le16(__le16_to_cpu(vr->avail->idx) + 1); + } +} + +static int vr_poll(VDev *vdev, VRing *vr) +{ + if (__le16_to_cpu(vr->used->idx) == vr->used_idx) { + vring_notify(vdev, vr); + return 0; + } + + vr->used_idx = __le16_to_cpu(vr->used->idx); + vr->next_idx = 0; + vr->desc[0].len = __cpu_to_le32(0); + vr->desc[0].flags = __cpu_to_le16(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 uint64_t vring_addr_translate(VDev *vdev, void *p) +{ + ucell mode; + uint64_t iova; + + iova = ofmem_translate(pointer2cell(p), &mode); + return iova; +} + +/*********************************************** + * 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 = __cpu_to_le32(VIRTIO_BLK_T_IN); + out_hdr.ioprio = __cpu_to_le32(99); + out_hdr.sector = __cpu_to_le64(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_configure_device(VDev *vdev) +{ + uint32_t feature; + uint8_t status; + int i; + + /* Indicate we recognise the device */ + status = virtio_cfg_read8(vdev->common_cfg, VIRTIO_PCI_COMMON_STATUS); + status |= VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER; + virtio_cfg_write8(vdev->common_cfg, VIRTIO_PCI_COMMON_STATUS, status); + + /* Negotiate features: acknowledge VIRTIO_F_VERSION_1 for 1.0 specification + little-endian access */ + virtio_cfg_write32(vdev->common_cfg, VIRTIO_PCI_COMMON_DFSELECT, 0x1); + virtio_cfg_write32(vdev->common_cfg, VIRTIO_PCI_COMMON_GFSELECT, 0x1); + feature = virtio_cfg_read32(vdev->common_cfg, VIRTIO_PCI_COMMON_DF); + feature &= (1ULL << (VIRTIO_F_VERSION_1 - 32)); + virtio_cfg_write32(vdev->common_cfg, VIRTIO_PCI_COMMON_GF, feature); + + status = virtio_cfg_read8(vdev->common_cfg, VIRTIO_PCI_COMMON_STATUS); + status |= VIRTIO_CONFIG_S_FEATURES_OK; + virtio_cfg_write8(vdev->common_cfg, VIRTIO_PCI_COMMON_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; + + 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->common_cfg, VIRTIO_PCI_COMMON_Q_SELECT, i); + info.num = virtio_cfg_read16(vdev->common_cfg, VIRTIO_PCI_COMMON_Q_SIZE); + + 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->device_cfg, 0); + + /* Set queue addresses */ + virtio_cfg_write64(vdev->common_cfg, VIRTIO_PCI_COMMON_Q_DESCLO, + vring_addr_translate(vdev, &vdev->vrings[i].desc[0])); + virtio_cfg_write64(vdev->common_cfg, VIRTIO_PCI_COMMON_Q_AVAILLO, + vring_addr_translate(vdev, &vdev->vrings[i].avail[0])); + virtio_cfg_write64(vdev->common_cfg, VIRTIO_PCI_COMMON_Q_USEDLO, + vring_addr_translate(vdev, &vdev->vrings[i].used[0])); + + /* Enable queue */ + virtio_cfg_write16(vdev->common_cfg, VIRTIO_PCI_COMMON_Q_ENABLE, 1); + } + + /* Initialisation complete */ + status |= VIRTIO_CONFIG_S_DRIVER_OK; + virtio_cfg_write8(vdev->common_cfg, VIRTIO_PCI_COMMON_STATUS, status); + + vdev->configured = 1; +} + +static void +ob_virtio_disk_open(VDev **_vdev) +{ + VDev *vdev = *_vdev; + phandle_t ph; + + vdev->pos = 0; + + if (!vdev->configured) { + ob_virtio_configure_device(vdev); + } + + /* 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 common_cfg, + uint64_t device_cfg, uint64_t notify_base, uint32_t notify_mult, + int idx) +{ + char buf[256]; + phandle_t ph; + ucell addr; + 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->common_cfg = common_cfg; + vdev->device_cfg = device_cfg; + vdev->notify_base = notify_base; + vdev->notify_mult = notify_mult; + vdev->configured = 0; + + 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); + + *_vdev = vdev; + feval("to my-self"); + + 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..64b49e7 --- /dev/null +++ b/drivers/virtio.h @@ -0,0 +1,370 @@ +/* + * Virtio driver bits + * + * Copyright (c) 2013 Alexander Graf agraf@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 +/* Driver has finished configuring features */ +#define VIRTIO_CONFIG_S_FEATURES_OK 8 +/* We've given up on this device. */ +#define VIRTIO_CONFIG_S_FAILED 0x80 + +/* v1.0 compliant. */ +#define VIRTIO_F_VERSION_1 32 + +/* Common configuration */ +#define VIRTIO_PCI_CAP_COMMON_CFG 1 +/* Notifications */ +#define VIRTIO_PCI_CAP_NOTIFY_CFG 2 +/* ISR Status */ +#define VIRTIO_PCI_CAP_ISR_CFG 3 +/* Device specific configuration */ +#define VIRTIO_PCI_CAP_DEVICE_CFG 4 +/* PCI configuration access */ +#define VIRTIO_PCI_CAP_PCI_CFG 5 + +#define VIRTIO_PCI_COMMON_DFSELECT 0 +#define VIRTIO_PCI_COMMON_DF 4 +#define VIRTIO_PCI_COMMON_GFSELECT 8 +#define VIRTIO_PCI_COMMON_GF 12 +#define VIRTIO_PCI_COMMON_MSIX 16 +#define VIRTIO_PCI_COMMON_NUMQ 18 +#define VIRTIO_PCI_COMMON_STATUS 20 +#define VIRTIO_PCI_COMMON_CFGGENERATION 21 +#define VIRTIO_PCI_COMMON_Q_SELECT 22 +#define VIRTIO_PCI_COMMON_Q_SIZE 24 +#define VIRTIO_PCI_COMMON_Q_MSIX 26 +#define VIRTIO_PCI_COMMON_Q_ENABLE 28 +#define VIRTIO_PCI_COMMON_Q_NOFF 30 +#define VIRTIO_PCI_COMMON_Q_DESCLO 32 +#define VIRTIO_PCI_COMMON_Q_DESCHI 36 +#define VIRTIO_PCI_COMMON_Q_AVAILLO 40 +#define VIRTIO_PCI_COMMON_Q_AVAILHI 44 +#define VIRTIO_PCI_COMMON_Q_USEDLO 48 +#define VIRTIO_PCI_COMMON_Q_USEDHI 52 + +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 { + uint64_t common_cfg; + uint64_t device_cfg; + uint64_t notify_base; + uint32_t notify_mult; + uint64_t pos; + int configured; + 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..d162901 100644 --- a/include/drivers/drivers.h +++ b/include/drivers/drivers.h @@ -139,6 +139,11 @@ 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 common_cfg, + uint64_t device_cfg, uint64_t notify_base, uint32_t notify_mult, + int idx); +#endif int macio_get_nvram_size(void); void macio_nvram_put(char *buf); void macio_nvram_get(char *buf); diff --git a/include/drivers/pci.h b/include/drivers/pci.h index fc7573a..990d071 100644 --- a/include/drivers/pci.h +++ b/include/drivers/pci.h @@ -40,6 +40,12 @@ struct pci_arch_t {
extern const pci_arch_t *arch;
+/* Vendor-Specific */ +#define PCI_CAP_ID_VNDR 0x9 + +/* Offset of first capability list entry */ +#define PCI_CAPABILITY_LIST 0x34 + /* Device tree offsets */
#define PCI_INT_MAP_PCI0 0