[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