[coreboot] [PATCH] Adding Virtio block device support
Himanshu Chauhan
hschauhan at nulltrace.org
Wed Sep 17 05:28:24 CEST 2014
Coreboot+Filo can be used in virtualized environments
where the Virtio support is needed sometimes.
Signed-off-by: Himanshu Chauhan <hschauhan at nulltrace.org>
---
Config.in | 13 +++
drivers/Makefile.inc | 2 +
drivers/virtio-blk.c | 266 ++++++++++++++++++++++++++++++++++++++++++++++++++
drivers/virtio-blk.h | 46 +++++++++
drivers/virtio-pci.c | 125 ++++++++++++++++++++++++
drivers/virtio-pci.h | 110 +++++++++++++++++++++
drivers/virtio-ring.c | 158 ++++++++++++++++++++++++++++++
drivers/virtio-ring.h | 130 ++++++++++++++++++++++++
fs/blockdev.c | 30 +++++-
include/fs.h | 6 ++
10 files changed, 885 insertions(+), 1 deletion(-)
create mode 100644 drivers/virtio-blk.c
create mode 100644 drivers/virtio-blk.h
create mode 100644 drivers/virtio-pci.c
create mode 100644 drivers/virtio-pci.h
create mode 100644 drivers/virtio-ring.c
create mode 100644 drivers/virtio-ring.h
diff --git a/Config.in b/Config.in
index cc48bd3..6f13b96 100644
--- a/Config.in
+++ b/Config.in
@@ -143,6 +143,19 @@ config IDE_NEW_DISK
help
Jens Axboe's fine IDE driver
+config SUPPORT_VIRTIO
+ bool "Support Virtio Drivers"
+ default n
+ help
+ Enable the support for Virtio devices.
+
+config VIRTIO_DISK
+ bool "Support virtio block device"
+ default n
+ depends on SUPPORT_VIRTIO
+ help
+ Enable support for virtio block devices
+
config LIBPAYLOAD_STORAGE
bool "Use libpayload's storage drivers"
default n
diff --git a/drivers/Makefile.inc b/drivers/Makefile.inc
index 941c151..a407baf 100644
--- a/drivers/Makefile.inc
+++ b/drivers/Makefile.inc
@@ -24,3 +24,5 @@ TARGETS-$(CONFIG_VIA_SOUND) += drivers/via-sound.o
TARGETS-$(CONFIG_USB_DISK) += drivers/usb.o
TARGETS-$(CONFIG_TARGET_I386) += drivers/intel.o
TARGETS-$(CONFIG_TARGET_I386) += drivers/amd.o drivers/sb600.o
+TARGETS-$(CONFIG_SUPPORT_VIRTIO) += drivers/virtio-pci.o drivers/virtio-ring.o
+TARGETS-$(CONFIG_VIRTIO_DISK) += drivers/virtio-blk.o
\ No newline at end of file
diff --git a/drivers/virtio-blk.c b/drivers/virtio-blk.c
new file mode 100644
index 0000000..f31758d
--- /dev/null
+++ b/drivers/virtio-blk.c
@@ -0,0 +1,266 @@
+// Virtio block boot support.
+//
+// Copyright (C) 2010 Red Hat Inc.
+// Copyright (c) 2014 Himanshu chauhan <hschauhan at nulltrace.org>
+//
+// Authors:
+// Gleb Natapov <gnatapov at redhat.com>
+//
+// This file may be distributed under the terms of the GNU LGPLv3 license.
+//
+// Himanshu: Ported from seabios virtio.
+//
+
+#include <libpayload.h>
+#include <config.h>
+#include <lib.h>
+#include <fs.h>
+#include <arch/timer.h>
+#include <arch/virtual.h>
+#include <arch/io.h>
+#ifdef CONFIG_SUPPORT_PCI
+#include <pci.h>
+#endif
+
+#define DEBUG_THIS CONFIG_DEBUG_VIRTIO_PCI
+#include <debug.h>
+
+#include "virtio-pci.h"
+#include "virtio-ring.h"
+#include "virtio-blk.h"
+
+struct disk_info {
+ uint16_t type;
+ uint16_t heads;
+ uint16_t cylinders;
+ uint16_t sectors_per_track;
+ uint8_t model_number[41];
+ uint8_t slave;
+ sector_t sectors;
+ int address_mode;
+#define ADDRESS_MODE_CHS 0
+#define ADDRESS_MODE_LBA 1
+#define ADDRESS_MODE_LBA48 2
+#define ADDRESS_MODE_PACKET 3
+ uint32_t blksize;
+ unsigned present : 1;
+};
+
+struct virtiodrive_s {
+ struct disk_info drive;
+ struct vring_virtqueue *vq;
+ pcidev_t bdf;
+ uint16_t ioaddr;
+ int init_done;
+};
+
+#define MAX_VIRTIO_DEVICES 4
+
+struct virtio_drives {
+ int scan_done;
+ int found;
+ struct virtiodrive_s disks[MAX_VIRTIO_DEVICES];
+} vdrives = { .scan_done = 0, .found = 0 };
+
+static int
+virtio_blk_op(struct virtiodrive_s *vdrive_g, sector_t sector, void *buf_fl,
+ int write)
+{
+ struct vring_virtqueue *vq = vdrive_g->vq;
+ struct virtio_blk_outhdr hdr = {
+ .type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN,
+ .ioprio = 0,
+ .sector = sector,
+ };
+
+ u8 status = VIRTIO_BLK_S_UNSUPP;
+ struct vring_list sg[] = {
+ {
+ .addr = (char *)&hdr,
+ .length = sizeof(hdr),
+ },
+ {
+ .addr = buf_fl,
+ .length = vdrive_g->drive.blksize,
+ },
+ {
+ .addr = (char *)&status,
+ .length = sizeof(status),
+ },
+ };
+
+ /* Add to virtqueue and kick host */
+ if (write) {
+ vring_add_buf(vq, sg, 2, 1, 0, 0);
+ } else {
+ vring_add_buf(vq, sg, 1, 2, 0, 0);
+ }
+
+ vring_kick(vdrive_g->ioaddr, vq, 1);
+
+ /* Wait for reply */
+ while (!vring_more_used(vq))
+ mdelay(5);
+
+ barrier();
+
+ /* Reclaim virtqueue element */
+ vring_get_buf(vq, NULL);
+
+ /* Clear interrupt status register. Avoid leaving interrupts stuck if
+ * VRING_AVAIL_F_NO_INTERRUPT was ignored and interrupts were raised.
+ */
+ vp_get_isr(vdrive_g->ioaddr);
+
+ if (status != VIRTIO_BLK_S_OK)
+ return -1;
+
+ return 0;
+}
+
+int virtio_blk_read(int drive, sector_t sector, void *buffer)
+{
+ int ret;
+
+ if (drive > MAX_VIRTIO_DEVICES || drive >= vdrives.found)
+ return 0;
+
+ ret = virtio_blk_op(&vdrives.disks[drive], sector, buffer, 0);
+ return ret;
+}
+
+/**/
+/* FIXME: FILO Will Never Write it seems, if it does later add write routine */
+/**/
+static int init_virtio_blk(struct virtiodrive_s *vdisk)
+{
+ u16 bdf = vdisk->bdf;
+ debug("found virtio-blk at %x:%x\n", PCI_BUS(bdf), PCI_SLOT(bdf));
+
+ if (vdisk->init_done) return 0;
+
+ memset(vdisk, 0, sizeof(struct virtiodrive_s));
+ vdisk->drive.type = DTYPE_VIRTIO_BLK;
+
+ u16 ioaddr = vp_init_simple(bdf);
+ vdisk->ioaddr = ioaddr;
+ if (vp_find_vq(ioaddr, 0, &vdisk->vq) < 0 ) {
+ printf("fail to find vq for virtio-blk %x:%x\n",
+ PCI_BUS(bdf), PCI_SLOT(bdf));
+ goto fail;
+ }
+
+ struct virtio_blk_config cfg;
+ vp_get(ioaddr, 0, &cfg, sizeof(cfg));
+
+ u32 f = vp_get_features(ioaddr);
+ vdisk->drive.blksize = (f & (1 << VIRTIO_BLK_F_BLK_SIZE)) ?
+ cfg.blk_size : DISK_SECTOR_SIZE;
+
+ vdisk->drive.sectors = cfg.capacity;
+ printf("virtio-blk %x:%x blksize=%d sectors=%u\n",
+ PCI_BUS(bdf), PCI_SLOT(bdf),
+ vdisk->drive.blksize, (u32)vdisk->drive.sectors);
+
+ if (vdisk->drive.blksize != DISK_SECTOR_SIZE) {
+ printf("virtio-blk %x:%x block size %d is unsupported\n",
+ PCI_BUS(bdf), PCI_SLOT(bdf),
+ vdisk->drive.blksize);
+ goto fail;
+ }
+
+ vdisk->drive.cylinders = cfg.cylinders;
+ vdisk->drive.heads = cfg.heads;
+ vdisk->drive.sectors = cfg.sectors;
+ vdisk->drive.present = 1;
+ vdisk->init_done = 1;
+
+ vp_set_status(ioaddr, VIRTIO_CONFIG_S_ACKNOWLEDGE |
+ VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_DRIVER_OK);
+
+ return 0;
+
+fail:
+ free(vdisk->vq);
+
+ return -1;
+}
+
+#ifdef CONFIG_SUPPORT_PCI
+static int pci_find_virtio_disk_on_bus(int bus)
+{
+ int device, func;
+ u32 vendor, devid;
+ u32 val;
+
+ for (device = 0; device < 32; device++) {
+ for (func = 0; func < 8; func++) {
+ pcidev_t currdev = PCI_DEV(bus, device, func);
+
+ val = pci_read_config32(currdev, REG_VENDOR_ID);
+ vendor = val & 0xFFFF;
+ devid = val >> 16;
+
+ if (vendor == PCI_VENDOR_ID_REDHAT_QUMRANET
+ && devid == PCI_DEVICE_ID_VIRTIO_BLK) {
+ if (vdrives.found < MAX_VIRTIO_DEVICES) {
+ vdrives.disks[vdrives.found].bdf = currdev;
+ vdrives.found++;
+ }
+ }
+ }
+ }
+
+ vdrives.scan_done = 1;
+
+ if (vdrives.found)
+ return vdrives.found;
+
+ return -1;
+}
+
+int pci_find_virtio_disks(int bus)
+{
+ debug(" Scanning for Virtio Disks on Bus %d\n", bus);
+ return pci_find_virtio_disk_on_bus(bus);
+}
+
+static int find_virtio_disks(void)
+{
+ /* Find a Virtio storage device */
+ if (!pci_find_virtio_disks(0)) {
+ debug("PCI Virtio Disks not found\n");
+ return -1;
+ }
+
+ return 0;
+}
+#else /* !CONFIG_SUPPORT_PCI */
+# define find_virtio_disks() (-1)
+#endif
+
+int virtio_disk_probe(int drive)
+{
+ if (drive >= MAX_VIRTIO_DEVICES) {
+ printf("Unsupported drive number\n");
+ return -1;
+ }
+
+ if (!vdrives.scan_done) {
+ if (find_virtio_disks() != 0) {
+ return -1;
+ }
+ }
+
+ if (drive >= vdrives.found) {
+ printf("No drive at number %d\n", drive);
+ return -1;
+ }
+
+ if (init_virtio_blk(&vdrives.disks[drive]) != 0) {
+ printf("No drive detected at virtio index %d\n", drive);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/drivers/virtio-blk.h b/drivers/virtio-blk.h
new file mode 100644
index 0000000..626c4c0
--- /dev/null
+++ b/drivers/virtio-blk.h
@@ -0,0 +1,46 @@
+#ifndef _VIRTIO_BLK_H
+#define _VIRTIO_BLK_H
+
+struct virtio_blk_config
+{
+ u64 capacity;
+ u32 size_max;
+ u32 seg_max;
+ u16 cylinders;
+ u8 heads;
+ u8 sectors;
+ u32 blk_size;
+ u8 physical_block_exp;
+ u8 alignment_offset;
+ u16 min_io_size;
+ u32 opt_io_size;
+} __attribute__((packed));
+
+#define VIRTIO_BLK_F_BLK_SIZE 6
+
+/* These two define direction. */
+#define VIRTIO_BLK_T_IN 0
+#define VIRTIO_BLK_T_OUT 1
+
+/* This is the first element of the read scatter-gather list. */
+struct virtio_blk_outhdr {
+ /* VIRTIO_BLK_T* */
+ u32 type;
+ /* io priority. */
+ u32 ioprio;
+ /* Sector (ie. 512 byte offset) */
+ u64 sector;
+};
+
+#define VIRTIO_BLK_S_OK 0
+#define VIRTIO_BLK_S_IOERR 1
+#define VIRTIO_BLK_S_UNSUPP 2
+
+#define DTYPE_VIRTIO_BLK 0x09
+#define DISK_SECTOR_SIZE 512
+
+struct disk_op_s;
+int process_virtio_blk_op(struct disk_op_s *op);
+void virtio_blk_setup(void);
+
+#endif /* _VIRTIO_BLK_H */
diff --git a/drivers/virtio-pci.c b/drivers/virtio-pci.c
new file mode 100644
index 0000000..e61c959
--- /dev/null
+++ b/drivers/virtio-pci.c
@@ -0,0 +1,125 @@
+/* virtio-pci.c - pci interface for virtio interface
+ *
+ * (c) Copyright 2008 Bull S.A.S.
+ *
+ * Author: Laurent Vivier <Laurent.Vivier at bull.net>
+ *
+ * some parts from Linux Virtio PCI driver
+ *
+ * Copyright IBM Corp. 2007
+ * Authors: Anthony Liguori <aliguori at us.ibm.com>
+ *
+ * Adopted for Seabios: Gleb Natapov <gleb at redhat.com>
+ *
+ * Further adopted to FILO: Himanshu Chauhan <hschauhan at nulltrace.org>
+ *
+ * This work is licensed under the terms of the GNU LGPLv3
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <libpayload.h>
+#include <config.h>
+#include <lib.h>
+#include <fs.h>
+#include <arch/timer.h>
+#include <arch/virtual.h>
+#include <arch/io.h>
+#ifdef CONFIG_SUPPORT_PCI
+#include <pci.h>
+#endif
+
+#define DEBUG_THIS CONFIG_DEBUG_VIRTIO_PCI
+#include <debug.h>
+
+#include "virtio-ring.h"
+#include "virtio-pci.h"
+
+/*
+ * Base addresses specify locations in memory or I/O space.
+ * Decoded size can be determined by writing a value of
+ * 0xffffffff to the register, and reading it back. Only
+ * 1 bits are decoded.
+ */
+#define PCI_BASE_ADDRESS_0 0x10 /* 32 bits */
+#define PCI_BASE_ADDRESS_1 0x14 /* 32 bits [htype 0,1 only] */
+#define PCI_BASE_ADDRESS_2 0x18 /* 32 bits [htype 0 only] */
+#define PCI_BASE_ADDRESS_3 0x1c /* 32 bits */
+#define PCI_BASE_ADDRESS_4 0x20 /* 32 bits */
+#define PCI_BASE_ADDRESS_5 0x24 /* 32 bits */
+#define PCI_BASE_ADDRESS_SPACE 0x01 /* 0 = memory, 1 = I/O */
+#define PCI_BASE_ADDRESS_SPACE_IO 0x01
+#define PCI_BASE_ADDRESS_SPACE_MEMORY 0x00
+#define PCI_BASE_ADDRESS_MEM_TYPE_MASK 0x06
+#define PCI_BASE_ADDRESS_MEM_TYPE_32 0x00 /* 32 bit address */
+#define PCI_BASE_ADDRESS_MEM_TYPE_1M 0x02 /* Below 1M [obsolete] */
+#define PCI_BASE_ADDRESS_MEM_TYPE_64 0x04 /* 64 bit address */
+#define PCI_BASE_ADDRESS_MEM_PREFETCH 0x08 /* prefetchable? */
+#define PCI_BASE_ADDRESS_MEM_MASK (~0x0fUL)
+#define PCI_BASE_ADDRESS_IO_MASK (~0x03UL)
+
+int vp_find_vq(unsigned int ioaddr, int queue_index,
+ struct vring_virtqueue **p_vq)
+{
+ uint16_t num;
+
+ struct vring_virtqueue *vq = *p_vq = malloc(sizeof(*vq));
+ if (!vq) {
+ goto fail;
+ }
+ memset(vq, 0, sizeof(*vq));
+
+ /* select the queue */
+ outw(queue_index, ioaddr + VIRTIO_PCI_QUEUE_SEL);
+
+ /* check if the queue is available */
+ num = inw(ioaddr + VIRTIO_PCI_QUEUE_NUM);
+ if (!num) {
+ debug("ERROR: queue size is 0\n");
+ goto fail;
+ }
+
+ if (num > MAX_QUEUE_NUM) {
+ debug("ERROR: queue size %d > %d\n", num, MAX_QUEUE_NUM);
+ goto fail;
+ }
+
+ /* check if the queue is already active */
+ if (inl(ioaddr + VIRTIO_PCI_QUEUE_PFN)) {
+ debug("ERROR: queue already active\n");
+ goto fail;
+ }
+
+ vq->queue_index = queue_index;
+
+ /* initialize the queue */
+
+ struct vring * vr = &vq->vring;
+ vring_init(vr, num, (unsigned char*)&vq->queue);
+
+ /* activate the queue
+ *
+ * NOTE: vr->desc is initialized by vring_init()
+ */
+
+ outl((unsigned long)virt_to_phys(vr->desc) >> PAGE_SHIFT,
+ ioaddr + VIRTIO_PCI_QUEUE_PFN);
+
+ return num;
+
+fail:
+ free(vq);
+ *p_vq = NULL;
+ return -1;
+}
+
+uint16_t vp_init_simple(uint16_t bdf)
+{
+ uint16_t ioaddr = pci_read_config32((0x80000000 | bdf), PCI_BASE_ADDRESS_0) &
+ PCI_BASE_ADDRESS_IO_MASK;
+
+ debug("%s: ioaddr: 0x%x\n", __func__, ioaddr);
+ vp_reset(ioaddr);
+ vp_set_status(ioaddr, VIRTIO_CONFIG_S_ACKNOWLEDGE |
+ VIRTIO_CONFIG_S_DRIVER );
+ return ioaddr;
+}
diff --git a/drivers/virtio-pci.h b/drivers/virtio-pci.h
new file mode 100644
index 0000000..19966b4
--- /dev/null
+++ b/drivers/virtio-pci.h
@@ -0,0 +1,110 @@
+#ifndef _VIRTIO_PCI_H
+#define _VIRTIO_PCI_H
+
+#include <arch/types.h>
+#include <arch/virtual.h>
+#include <arch/io.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
+
+/* The bit of the ISR which indicates a device configuration change. */
+#define VIRTIO_PCI_ISR_CONFIG 0x2
+
+/* The remaining space is defined by each driver as the per-driver
+ * configuration space */
+#define VIRTIO_PCI_CONFIG 20
+
+/* Virtio ABI version, this must match exactly */
+#define VIRTIO_PCI_ABI_VERSION 0
+
+#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4
+#define PCI_DEVICE_ID_VIRTIO_BLK 0x1001
+
+static inline uint32_t vp_get_features(unsigned int ioaddr)
+{
+ return inl(ioaddr + VIRTIO_PCI_HOST_FEATURES);
+}
+
+static inline void vp_set_features(unsigned int ioaddr, uint32_t features)
+{
+ outl(features, ioaddr + VIRTIO_PCI_GUEST_FEATURES);
+}
+
+static inline void vp_get(unsigned int ioaddr, unsigned offset,
+ void *buf, unsigned len)
+{
+ uint8_t *ptr = buf;
+ unsigned i;
+
+ for (i = 0; i < len; i++)
+ ptr[i] = inb(ioaddr + VIRTIO_PCI_CONFIG + offset + i);
+}
+
+static inline uint8_t vp_get_status(unsigned int ioaddr)
+{
+ return inb(ioaddr + VIRTIO_PCI_STATUS);
+}
+
+static inline void vp_set_status(unsigned int ioaddr, uint8_t status)
+{
+ if (status == 0) /* reset */
+ return;
+ outb(status, ioaddr + VIRTIO_PCI_STATUS);
+}
+
+static inline uint8_t vp_get_isr(unsigned int ioaddr)
+{
+ return inb(ioaddr + VIRTIO_PCI_ISR);
+}
+
+static inline void vp_reset(unsigned int ioaddr)
+{
+ outb(0, ioaddr + VIRTIO_PCI_STATUS);
+ (void)inb(ioaddr + VIRTIO_PCI_ISR);
+}
+
+static inline void vp_notify(unsigned int ioaddr, int queue_index)
+{
+ outw(queue_index, ioaddr + VIRTIO_PCI_QUEUE_NOTIFY);
+}
+
+static inline void vp_del_vq(unsigned int ioaddr, int queue_index)
+{
+ /* select the queue */
+
+ outw(queue_index, ioaddr + VIRTIO_PCI_QUEUE_SEL);
+
+ /* deactivate the queue */
+
+ outl(0, ioaddr + VIRTIO_PCI_QUEUE_PFN);
+}
+
+struct vring_virtqueue;
+uint16_t vp_init_simple(uint16_t bdf);
+int vp_find_vq(unsigned int ioaddr, int queue_index,
+ struct vring_virtqueue **p_vq);
+#endif /* _VIRTIO_PCI_H_ */
diff --git a/drivers/virtio-ring.c b/drivers/virtio-ring.c
new file mode 100644
index 0000000..2edaa06
--- /dev/null
+++ b/drivers/virtio-ring.c
@@ -0,0 +1,158 @@
+/* virtio-pci.c - virtio ring management
+ *
+ * (c) Copyright 2008 Bull S.A.S.
+ *
+ * Author: Laurent Vivier <Laurent.Vivier at bull.net>
+ *
+ * some parts from Linux Virtio Ring
+ *
+ * Copyright Rusty Russell IBM Corporation 2007
+ *
+ * Adopted for Seabios: Gleb Natapov <gleb at redhat.com>
+ *
+ * Further adopted to FILO: Himanshu Chauhan <hschauhan at nulltrace.org>
+ *
+ * This work is licensed under the terms of the GNU LGPLv3
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <libpayload.h>
+#include <config.h>
+#include <lib.h>
+#include <fs.h>
+#include <arch/timer.h>
+#include <arch/virtual.h>
+#include <arch/io.h>
+
+#include "virtio-ring.h"
+#include "virtio-pci.h"
+
+#define barrier() asm volatile("":::"memory");
+
+#define BUG() do { \
+ printf("BUG: failure at %d/%s()!\n", __LINE__, __func__); \
+ while(1); \
+ } while (0)
+#define BUG_ON(condition) do { if (condition) BUG(); } while (0)
+
+/*
+ * vring_more_used
+ *
+ * is there some used buffers ?
+ *
+ */
+
+int vring_more_used(struct vring_virtqueue *vq)
+{
+ struct vring_used *used = vq->vring.used;
+ int more = vq->last_used_idx != used->idx;
+ /* Make sure ring reads are done after idx read above. */
+ smp_rmb();
+ return more;
+}
+
+/*
+ * vring_free
+ *
+ * put at the begin of the free list the current desc[head]
+ */
+
+void vring_detach(struct vring_virtqueue *vq, unsigned int head)
+{
+ struct vring *vr = &vq->vring;
+ struct vring_desc *desc = vr->desc;
+ unsigned int i;
+
+ /* find end of given descriptor */
+
+ i = head;
+ while (desc[i].flags & VRING_DESC_F_NEXT)
+ i = desc[i].next;
+
+ /* link it with free list and point to it */
+
+ desc[i].next = vq->free_head;
+ vq->free_head = head;
+}
+
+/*
+ * vring_get_buf
+ *
+ * get a buffer from the used list
+ *
+ */
+
+int vring_get_buf(struct vring_virtqueue *vq, unsigned int *len)
+{
+ struct vring *vr = &vq->vring;
+ struct vring_used_elem *elem;
+ struct vring_used *used = vq->vring.used;
+ uint32_t id;
+ int ret;
+
+ BUG_ON(!vring_more_used(vq));
+
+ elem = &used->ring[vq->last_used_idx % vr->num];
+ id = elem->id;
+ if (len != NULL)
+ *len = elem->len;
+
+ ret = vq->vdata[id];
+
+ vring_detach(vq, id);
+
+ vq->last_used_idx = vq->last_used_idx + 1;
+
+ return ret;
+}
+
+void vring_add_buf(struct vring_virtqueue *vq,
+ struct vring_list list[],
+ unsigned int out, unsigned int in,
+ int index, int num_added)
+{
+ struct vring *vr = &vq->vring;
+ int i, av, head, prev; //, j;
+ struct vring_desc *desc = vr->desc;
+ struct vring_avail *avail = vr->avail;
+
+ BUG_ON(out + in == 0);
+
+ prev = 0;
+ head = vq->free_head;
+ for (i = head; out; i = desc[i].next, out--) {
+ desc[i].flags = VRING_DESC_F_NEXT;
+ desc[i].addr = (uint64_t)virt_to_phys(list->addr);
+ desc[i].len = list->length;
+ prev = i;
+ list++;
+ }
+ for ( ; in; i = desc[i].next, in--) {
+ desc[i].flags = (VRING_DESC_F_NEXT|VRING_DESC_F_WRITE);
+ desc[i].addr = (u64)virt_to_phys(list->addr);
+ desc[i].len = list->length;
+ prev = i;
+ list++;
+ }
+ desc[prev].flags =
+ (desc[prev].flags & ~VRING_DESC_F_NEXT);
+
+ vq->free_head = i;
+
+ vq->vdata[head] = index;
+
+ av = (avail->idx + num_added) % (vr->num);
+ avail->ring[av] = head;
+}
+
+void vring_kick(unsigned int ioaddr, struct vring_virtqueue *vq, int num_added)
+{
+ struct vring *vr = &vq->vring;
+ struct vring_avail *avail = vr->avail;
+
+ /* Make sure idx update is done after ring write. */
+ smp_wmb();
+ avail->idx = (avail->idx + num_added);
+
+ vp_notify(ioaddr, vq->queue_index);
+}
diff --git a/drivers/virtio-ring.h b/drivers/virtio-ring.h
new file mode 100644
index 0000000..bef7926
--- /dev/null
+++ b/drivers/virtio-ring.h
@@ -0,0 +1,130 @@
+#ifndef _VIRTIO_RING_H
+#define _VIRTIO_RING_H
+
+#include <arch/types.h>
+#include <arch/virtual.h>
+#include <arch/io.h>
+
+#define PAGE_SIZE 4096
+#define PAGE_SHIFT 12
+#define PAGE_MASK (PAGE_SIZE-1)
+
+#define barrier() asm volatile("":::"memory");
+
+/* Compiler barrier is enough as an x86 CPU does not reorder reads or writes */
+#define smp_rmb() barrier()
+#define smp_wmb() barrier()
+
+/* 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
+
+#define MAX_QUEUE_NUM (128)
+
+#define VRING_DESC_F_NEXT 1
+#define VRING_DESC_F_WRITE 2
+
+#define VRING_AVAIL_F_NO_INTERRUPT 1
+
+#define VRING_USED_F_NO_NOTIFY 1
+
+struct vring_desc
+{
+ uint64_t addr;
+ uint32_t len;
+ uint16_t flags;
+ uint16_t next;
+};
+
+struct vring_avail
+{
+ uint16_t flags;
+ uint16_t idx;
+ uint16_t ring[0];
+};
+
+struct vring_used_elem
+{
+ uint32_t id;
+ uint32_t len;
+};
+
+struct vring_used
+{
+ uint16_t flags;
+ uint16_t idx;
+ struct vring_used_elem ring[];
+};
+
+struct vring {
+ unsigned int num;
+ struct vring_desc *desc;
+ struct vring_avail *avail;
+ struct vring_used *used;
+};
+
+#define vring_size(num) \
+ (((((sizeof(struct vring_desc) * num) + \
+ (sizeof(struct vring_avail) + sizeof(u16) * num)) \
+ + PAGE_MASK) & ~PAGE_MASK) + \
+ (sizeof(struct vring_used) + sizeof(struct vring_used_elem) * num))
+
+typedef unsigned char virtio_queue_t[vring_size(MAX_QUEUE_NUM)];
+
+struct vring_virtqueue {
+ struct vring vring;
+ uint16_t free_head;
+ uint16_t last_used_idx;
+ uint16_t vdata[MAX_QUEUE_NUM];
+ /* PCI */
+ int queue_index;
+ virtio_queue_t queue;
+};
+
+struct vring_list {
+ char *addr;
+ unsigned int length;
+};
+
+static inline void vring_init(struct vring *vr,
+ unsigned int num, unsigned char *queue)
+{
+ unsigned int i;
+ unsigned long pa;
+
+ vr->num = num;
+
+ /* physical address of desc must be page aligned */
+ pa = virt_to_phys(queue);
+ pa = (pa + PAGE_MASK) & ~PAGE_MASK;
+ vr->desc = phys_to_virt(pa);
+
+ vr->avail = (struct vring_avail *)&vr->desc[num];
+ /* disable interrupts */
+ vr->avail->flags |= VRING_AVAIL_F_NO_INTERRUPT;
+
+ /* physical address of used must be page aligned */
+ pa = virt_to_phys(&vr->avail->ring[num]);
+ pa = (pa + PAGE_MASK) & ~PAGE_MASK;
+ vr->used = phys_to_virt(pa);
+
+ for (i = 0; i < num - 1; i++)
+ vr->desc[i].next = i + 1;
+ vr->desc[i].next = 0;
+}
+
+int vring_more_used(struct vring_virtqueue *vq);
+void vring_detach(struct vring_virtqueue *vq, unsigned int head);
+int vring_get_buf(struct vring_virtqueue *vq, unsigned int *len);
+void vring_add_buf(struct vring_virtqueue *vq, struct vring_list list[],
+ unsigned int out, unsigned int in,
+ int index, int num_added);
+void vring_kick(unsigned int ioaddr, struct vring_virtqueue *vq, int num_added);
+
+#endif /* _VIRTIO_RING_H_ */
diff --git a/fs/blockdev.c b/fs/blockdev.c
index 0105712..be5719e 100644
--- a/fs/blockdev.c
+++ b/fs/blockdev.c
@@ -190,7 +190,16 @@ static int parse_device_name(const char *name, int *type, int *drive,
*type = DISK_MEM;
name += 3;
*drive = 0;
- } else {
+ } else if (memcmp(name, "vd", 2) == 0) {
+ *type = DISK_VIRTIO;
+ name += 2;
+ if (*name < 'a' || *name > 'z') {
+ printf("Invalid drive\n");
+ return 0;
+ }
+ *drive = *name - 'a';
+ name++;
+ }else {
printf("Unknown device type: %s\n", name);
return 0;
}
@@ -283,6 +292,16 @@ int devopen(const char *name, int *reopen)
break;
#endif
+#ifdef CONFIG_VIRTIO_DISK
+ case DISK_VIRTIO:
+ if (virtio_disk_probe(drive) != 0) {
+ debug("Failed to open virtio disk.\n");
+ return 0;
+ }
+ disk_size = (uint32_t) -1;
+ break;
+#endif
+
#ifdef CONFIG_FLASH_DISK
case DISK_FLASH:
if (flash_probe(drive) != 0) {
@@ -447,6 +466,15 @@ static void *read_sector(unsigned long sector)
}
#endif
+#ifdef CONFIG_VIRTIO_DISK
+ case DISK_VIRTIO:
+ {
+ if (virtio_blk_read(dev_drive, sector, buf) != 0)
+ goto readerr;
+ break;
+ }
+#endif
+
#ifdef CONFIG_FLASH_DISK
case DISK_FLASH:
if (flash_read(dev_drive, sector, buf) != 0)
diff --git a/include/fs.h b/include/fs.h
index c91ace7..9b35ca7 100644
--- a/include/fs.h
+++ b/include/fs.h
@@ -48,10 +48,16 @@ int flash_read(int drive, sector_t sector, void *buffer);
void NAND_close(void);
#endif
+#ifdef CONFIG_VIRTIO_DISK
+int virtio_disk_probe(int drive);
+int virtio_blk_read(int drive, sector_t sector, void *buffer);
+#endif
+
#define DISK_IDE 1
#define DISK_MEM 2
#define DISK_USB 3
#define DISK_FLASH 4
+#define DISK_VIRTIO 5
int devopen(const char *name, int *reopen);
void devclose(void);
--
1.9.1
More information about the coreboot
mailing list