aboutsummaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
authorAnthony Liguori <aliguori@us.ibm.com>2013-07-22 10:14:24 -0500
committerAnthony Liguori <aliguori@us.ibm.com>2013-07-22 10:14:24 -0500
commit549c272b3c7c2aac69155be2e158c970828b2844 (patch)
tree18c407ca7c3c83b3e069987921d8b5e068f8742e /hw
parentc9fea5d701f8fd33f0843728ec264d95cee3ed37 (diff)
parentfd76663e3fe59dc7f7d5c439561b74f472727137 (diff)
Merge remote-tracking branch 'pmaydell/tags/pull-arm-devs-20130722' into staging
arm-devs queue # gpg: Signature made Mon 22 Jul 2013 06:38:52 AM CDT using RSA key ID 14360CDE # gpg: Can't check signature: public key not found # By Peter Maydell (8) and Soren Brinkmann (2) # Via Peter Maydell * pmaydell/tags/pull-arm-devs-20130722: hw/arm: Use 'load_ramdisk()' for loading ramdisks w/ U-Boot header hw/loader: Support ramdisk with u-boot header vexpress: Add virtio-mmio transports vexpress: Make VEDBoardInfo extend arm_boot_info arm/boot: Allow boards to modify the FDT blob virtio: Implement MMIO based virtio transport virtio: Support transports which can specify the vring alignment virtio: Add support for guest setting of queue size arm/boot: Use qemu_devtree_setprop_sized_cells() device_tree: Add qemu_devtree_setprop_sized_cells() utility functions Message-id: 1374493427-3254-1-git-send-email-peter.maydell@linaro.org Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Diffstat (limited to 'hw')
-rw-r--r--hw/arm/boot.c48
-rw-r--r--hw/arm/vexpress.c128
-rw-r--r--hw/core/loader.c84
-rw-r--r--hw/virtio/Makefile.objs1
-rw-r--r--hw/virtio/virtio-mmio.c421
-rw-r--r--hw/virtio/virtio.c40
6 files changed, 654 insertions, 68 deletions
diff --git a/hw/arm/boot.c b/hw/arm/boot.c
index a2e4032f42..2cbeefdcba 100644
--- a/hw/arm/boot.c
+++ b/hw/arm/boot.c
@@ -227,12 +227,10 @@ static void set_kernel_args_old(const struct arm_boot_info *info)
static int load_dtb(hwaddr addr, const struct arm_boot_info *binfo)
{
- uint32_t *mem_reg_property;
- uint32_t mem_reg_propsize;
void *fdt = NULL;
char *filename;
int size, rc;
- uint32_t acells, scells, hival;
+ uint32_t acells, scells;
filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, binfo->dtb_filename);
if (!filename) {
@@ -255,29 +253,18 @@ static int load_dtb(hwaddr addr, const struct arm_boot_info *binfo)
goto fail;
}
- mem_reg_propsize = acells + scells;
- mem_reg_property = g_new0(uint32_t, mem_reg_propsize);
- mem_reg_property[acells - 1] = cpu_to_be32(binfo->loader_start);
- hival = cpu_to_be32(binfo->loader_start >> 32);
- if (acells > 1) {
- mem_reg_property[acells - 2] = hival;
- } else if (hival != 0) {
- fprintf(stderr, "qemu: dtb file not compatible with "
- "RAM start address > 4GB\n");
- goto fail;
- }
- mem_reg_property[acells + scells - 1] = cpu_to_be32(binfo->ram_size);
- hival = cpu_to_be32(binfo->ram_size >> 32);
- if (scells > 1) {
- mem_reg_property[acells + scells - 2] = hival;
- } else if (hival != 0) {
+ if (scells < 2 && binfo->ram_size >= (1ULL << 32)) {
+ /* This is user error so deserves a friendlier error message
+ * than the failure of setprop_sized_cells would provide
+ */
fprintf(stderr, "qemu: dtb file not compatible with "
"RAM size > 4GB\n");
goto fail;
}
- rc = qemu_devtree_setprop(fdt, "/memory", "reg", mem_reg_property,
- mem_reg_propsize * sizeof(uint32_t));
+ rc = qemu_devtree_setprop_sized_cells(fdt, "/memory", "reg",
+ acells, binfo->loader_start,
+ scells, binfo->ram_size);
if (rc < 0) {
fprintf(stderr, "couldn't set /memory/reg\n");
goto fail;
@@ -307,6 +294,11 @@ static int load_dtb(hwaddr addr, const struct arm_boot_info *binfo)
goto fail;
}
}
+
+ if (binfo->modify_dtb) {
+ binfo->modify_dtb(binfo, fdt);
+ }
+
qemu_devtree_dumpdtb(fdt, size);
cpu_physical_memory_write(addr, fdt, size);
@@ -419,10 +411,16 @@ void arm_load_kernel(ARMCPU *cpu, struct arm_boot_info *info)
info->entry = entry;
if (is_linux) {
if (info->initrd_filename) {
- initrd_size = load_image_targphys(info->initrd_filename,
- info->initrd_start,
- info->ram_size -
- info->initrd_start);
+ initrd_size = load_ramdisk(info->initrd_filename,
+ info->initrd_start,
+ info->ram_size -
+ info->initrd_start);
+ if (initrd_size < 0) {
+ initrd_size = load_image_targphys(info->initrd_filename,
+ info->initrd_start,
+ info->ram_size -
+ info->initrd_start);
+ }
if (initrd_size < 0) {
fprintf(stderr, "qemu: could not load initrd '%s'\n",
info->initrd_filename);
diff --git a/hw/arm/vexpress.c b/hw/arm/vexpress.c
index 7d1b8233cd..9586e3880e 100644
--- a/hw/arm/vexpress.c
+++ b/hw/arm/vexpress.c
@@ -31,12 +31,17 @@
#include "exec/address-spaces.h"
#include "sysemu/blockdev.h"
#include "hw/block/flash.h"
+#include "sysemu/device_tree.h"
+#include <libfdt.h>
#define VEXPRESS_BOARD_ID 0x8e0
#define VEXPRESS_FLASH_SIZE (64 * 1024 * 1024)
#define VEXPRESS_FLASH_SECT_SIZE (256 * 1024)
-static struct arm_boot_info vexpress_binfo;
+/* Number of virtio transports to create (0..8; limited by
+ * number of available IRQ lines).
+ */
+#define NUM_VIRTIO_TRANSPORTS 4
/* Address maps for peripherals:
* the Versatile Express motherboard has two possible maps,
@@ -73,6 +78,7 @@ enum {
VE_ETHERNET,
VE_USB,
VE_DAPROM,
+ VE_VIRTIO,
};
static hwaddr motherboard_legacy_map[] = {
@@ -91,6 +97,7 @@ static hwaddr motherboard_legacy_map[] = {
[VE_WDT] = 0x1000f000,
[VE_TIMER01] = 0x10011000,
[VE_TIMER23] = 0x10012000,
+ [VE_VIRTIO] = 0x10013000,
[VE_SERIALDVI] = 0x10016000,
[VE_RTC] = 0x10017000,
[VE_COMPACTFLASH] = 0x1001a000,
@@ -137,6 +144,7 @@ static hwaddr motherboard_aseries_map[] = {
[VE_WDT] = 0x1c0f0000,
[VE_TIMER01] = 0x1c110000,
[VE_TIMER23] = 0x1c120000,
+ [VE_VIRTIO] = 0x1c130000,
[VE_SERIALDVI] = 0x1c160000,
[VE_RTC] = 0x1c170000,
[VE_COMPACTFLASH] = 0x1c1a0000,
@@ -153,6 +161,7 @@ typedef void DBoardInitFn(const VEDBoardInfo *daughterboard,
qemu_irq *pic);
struct VEDBoardInfo {
+ struct arm_boot_info bootinfo;
const hwaddr *motherboard_map;
hwaddr loader_start;
const hwaddr gic_cpu_if_addr;
@@ -272,7 +281,7 @@ static const uint32_t a9_clocks[] = {
66670000, /* Test chip reference clock: 66.67MHz */
};
-static const VEDBoardInfo a9_daughterboard = {
+static VEDBoardInfo a9_daughterboard = {
.motherboard_map = motherboard_legacy_map,
.loader_start = 0x60000000,
.gic_cpu_if_addr = 0x1e000100,
@@ -384,7 +393,7 @@ static const uint32_t a15_clocks[] = {
40000000, /* OSCCLK8: 40MHz : DDR2 PLL reference */
};
-static const VEDBoardInfo a15_daughterboard = {
+static VEDBoardInfo a15_daughterboard = {
.motherboard_map = motherboard_aseries_map,
.loader_start = 0x80000000,
.gic_cpu_if_addr = 0x2c002000,
@@ -396,7 +405,86 @@ static const VEDBoardInfo a15_daughterboard = {
.init = a15_daughterboard_init,
};
-static void vexpress_common_init(const VEDBoardInfo *daughterboard,
+static int add_virtio_mmio_node(void *fdt, uint32_t acells, uint32_t scells,
+ hwaddr addr, hwaddr size, uint32_t intc,
+ int irq)
+{
+ /* Add a virtio_mmio node to the device tree blob:
+ * virtio_mmio@ADDRESS {
+ * compatible = "virtio,mmio";
+ * reg = <ADDRESS, SIZE>;
+ * interrupt-parent = <&intc>;
+ * interrupts = <0, irq, 1>;
+ * }
+ * (Note that the format of the interrupts property is dependent on the
+ * interrupt controller that interrupt-parent points to; these are for
+ * the ARM GIC and indicate an SPI interrupt, rising-edge-triggered.)
+ */
+ int rc;
+ char *nodename = g_strdup_printf("/virtio_mmio@%" PRIx64, addr);
+
+ rc = qemu_devtree_add_subnode(fdt, nodename);
+ rc |= qemu_devtree_setprop_string(fdt, nodename,
+ "compatible", "virtio,mmio");
+ rc |= qemu_devtree_setprop_sized_cells(fdt, nodename, "reg",
+ acells, addr, scells, size);
+ qemu_devtree_setprop_cells(fdt, nodename, "interrupt-parent", intc);
+ qemu_devtree_setprop_cells(fdt, nodename, "interrupts", 0, irq, 1);
+ g_free(nodename);
+ if (rc) {
+ return -1;
+ }
+ return 0;
+}
+
+static uint32_t find_int_controller(void *fdt)
+{
+ /* Find the FDT node corresponding to the interrupt controller
+ * for virtio-mmio devices. We do this by scanning the fdt for
+ * a node with the right compatibility, since we know there is
+ * only one GIC on a vexpress board.
+ * We return the phandle of the node, or 0 if none was found.
+ */
+ const char *compat = "arm,cortex-a9-gic";
+ int offset;
+
+ offset = fdt_node_offset_by_compatible(fdt, -1, compat);
+ if (offset >= 0) {
+ return fdt_get_phandle(fdt, offset);
+ }
+ return 0;
+}
+
+static void vexpress_modify_dtb(const struct arm_boot_info *info, void *fdt)
+{
+ uint32_t acells, scells, intc;
+ const VEDBoardInfo *daughterboard = (const VEDBoardInfo *)info;
+
+ acells = qemu_devtree_getprop_cell(fdt, "/", "#address-cells");
+ scells = qemu_devtree_getprop_cell(fdt, "/", "#size-cells");
+ intc = find_int_controller(fdt);
+ if (!intc) {
+ /* Not fatal, we just won't provide virtio. This will
+ * happen with older device tree blobs.
+ */
+ fprintf(stderr, "QEMU: warning: couldn't find interrupt controller in "
+ "dtb; will not include virtio-mmio devices in the dtb.\n");
+ } else {
+ int i;
+ const hwaddr *map = daughterboard->motherboard_map;
+
+ /* We iterate backwards here because adding nodes
+ * to the dtb puts them in last-first.
+ */
+ for (i = NUM_VIRTIO_TRANSPORTS - 1; i >= 0; i--) {
+ add_virtio_mmio_node(fdt, acells, scells,
+ map[VE_VIRTIO] + 0x200 * i,
+ 0x200, intc, 40 + i);
+ }
+ }
+}
+
+static void vexpress_common_init(VEDBoardInfo *daughterboard,
QEMUMachineInitArgs *args)
{
DeviceState *dev, *sysctl, *pl041;
@@ -524,17 +612,27 @@ static void vexpress_common_init(const VEDBoardInfo *daughterboard,
/* VE_DAPROM: not modelled */
- vexpress_binfo.ram_size = args->ram_size;
- vexpress_binfo.kernel_filename = args->kernel_filename;
- vexpress_binfo.kernel_cmdline = args->kernel_cmdline;
- vexpress_binfo.initrd_filename = args->initrd_filename;
- vexpress_binfo.nb_cpus = smp_cpus;
- vexpress_binfo.board_id = VEXPRESS_BOARD_ID;
- vexpress_binfo.loader_start = daughterboard->loader_start;
- vexpress_binfo.smp_loader_start = map[VE_SRAM];
- vexpress_binfo.smp_bootreg_addr = map[VE_SYSREGS] + 0x30;
- vexpress_binfo.gic_cpu_if_addr = daughterboard->gic_cpu_if_addr;
- arm_load_kernel(ARM_CPU(first_cpu), &vexpress_binfo);
+ /* Create mmio transports, so the user can create virtio backends
+ * (which will be automatically plugged in to the transports). If
+ * no backend is created the transport will just sit harmlessly idle.
+ */
+ for (i = 0; i < NUM_VIRTIO_TRANSPORTS; i++) {
+ sysbus_create_simple("virtio-mmio", map[VE_VIRTIO] + 0x200 * i,
+ pic[40 + i]);
+ }
+
+ daughterboard->bootinfo.ram_size = args->ram_size;
+ daughterboard->bootinfo.kernel_filename = args->kernel_filename;
+ daughterboard->bootinfo.kernel_cmdline = args->kernel_cmdline;
+ daughterboard->bootinfo.initrd_filename = args->initrd_filename;
+ daughterboard->bootinfo.nb_cpus = smp_cpus;
+ daughterboard->bootinfo.board_id = VEXPRESS_BOARD_ID;
+ daughterboard->bootinfo.loader_start = daughterboard->loader_start;
+ daughterboard->bootinfo.smp_loader_start = map[VE_SRAM];
+ daughterboard->bootinfo.smp_bootreg_addr = map[VE_SYSREGS] + 0x30;
+ daughterboard->bootinfo.gic_cpu_if_addr = daughterboard->gic_cpu_if_addr;
+ daughterboard->bootinfo.modify_dtb = vexpress_modify_dtb;
+ arm_load_kernel(ARM_CPU(first_cpu), &daughterboard->bootinfo);
}
static void vexpress_a9_init(QEMUMachineInitArgs *args)
diff --git a/hw/core/loader.c b/hw/core/loader.c
index 44311ff43d..c3c28cf6af 100644
--- a/hw/core/loader.c
+++ b/hw/core/loader.c
@@ -434,15 +434,17 @@ static ssize_t gunzip(void *dst, size_t dstlen, uint8_t *src,
}
/* Load a U-Boot image. */
-int load_uimage(const char *filename, hwaddr *ep,
- hwaddr *loadaddr, int *is_linux)
+static int load_uboot_image(const char *filename, hwaddr *ep, hwaddr *loadaddr,
+ int *is_linux, uint8_t image_type)
{
int fd;
int size;
+ hwaddr address;
uboot_image_header_t h;
uboot_image_header_t *hdr = &h;
uint8_t *data = NULL;
int ret = -1;
+ int do_uncompress = 0;
fd = open(filename, O_RDONLY | O_BINARY);
if (fd < 0)
@@ -457,32 +459,55 @@ int load_uimage(const char *filename, hwaddr *ep,
if (hdr->ih_magic != IH_MAGIC)
goto out;
- /* TODO: Implement other image types. */
- if (hdr->ih_type != IH_TYPE_KERNEL) {
- fprintf(stderr, "Can only load u-boot image type \"kernel\"\n");
+ if (hdr->ih_type != image_type) {
+ fprintf(stderr, "Wrong image type %d, expected %d\n", hdr->ih_type,
+ image_type);
goto out;
}
- switch (hdr->ih_comp) {
- case IH_COMP_NONE:
- case IH_COMP_GZIP:
+ /* TODO: Implement other image types. */
+ switch (hdr->ih_type) {
+ case IH_TYPE_KERNEL:
+ address = hdr->ih_load;
+ if (loadaddr) {
+ *loadaddr = hdr->ih_load;
+ }
+
+ switch (hdr->ih_comp) {
+ case IH_COMP_NONE:
+ break;
+ case IH_COMP_GZIP:
+ do_uncompress = 1;
+ break;
+ default:
+ fprintf(stderr,
+ "Unable to load u-boot images with compression type %d\n",
+ hdr->ih_comp);
+ goto out;
+ }
+
+ if (ep) {
+ *ep = hdr->ih_ep;
+ }
+
+ /* TODO: Check CPU type. */
+ if (is_linux) {
+ if (hdr->ih_os == IH_OS_LINUX) {
+ *is_linux = 1;
+ } else {
+ *is_linux = 0;
+ }
+ }
+
+ break;
+ case IH_TYPE_RAMDISK:
+ address = *loadaddr;
break;
default:
- fprintf(stderr,
- "Unable to load u-boot images with compression type %d\n",
- hdr->ih_comp);
+ fprintf(stderr, "Unsupported u-boot image type %d\n", hdr->ih_type);
goto out;
}
- /* TODO: Check CPU type. */
- if (is_linux) {
- if (hdr->ih_os == IH_OS_LINUX)
- *is_linux = 1;
- else
- *is_linux = 0;
- }
-
- *ep = hdr->ih_ep;
data = g_malloc(hdr->ih_size);
if (read(fd, data, hdr->ih_size) != hdr->ih_size) {
@@ -490,7 +515,7 @@ int load_uimage(const char *filename, hwaddr *ep,
goto out;
}
- if (hdr->ih_comp == IH_COMP_GZIP) {
+ if (do_uncompress) {
uint8_t *compressed_data;
size_t max_bytes;
ssize_t bytes;
@@ -508,10 +533,7 @@ int load_uimage(const char *filename, hwaddr *ep,
hdr->ih_size = bytes;
}
- rom_add_blob_fixed(filename, data, hdr->ih_size, hdr->ih_load);
-
- if (loadaddr)
- *loadaddr = hdr->ih_load;
+ rom_add_blob_fixed(filename, data, hdr->ih_size, address);
ret = hdr->ih_size;
@@ -522,6 +544,18 @@ out:
return ret;
}
+int load_uimage(const char *filename, hwaddr *ep, hwaddr *loadaddr,
+ int *is_linux)
+{
+ return load_uboot_image(filename, ep, loadaddr, is_linux, IH_TYPE_KERNEL);
+}
+
+/* Load a ramdisk. */
+int load_ramdisk(const char *filename, hwaddr addr, uint64_t max_sz)
+{
+ return load_uboot_image(filename, NULL, &addr, NULL, IH_TYPE_RAMDISK);
+}
+
/*
* Functions for reboot-persistent memory regions.
* - used for vga bios and option roms.
diff --git a/hw/virtio/Makefile.objs b/hw/virtio/Makefile.objs
index cbe6d51f7a..1ba53d9cc3 100644
--- a/hw/virtio/Makefile.objs
+++ b/hw/virtio/Makefile.objs
@@ -1,6 +1,7 @@
common-obj-y += virtio-rng.o
common-obj-$(CONFIG_VIRTIO_PCI) += virtio-pci.o
common-obj-y += virtio-bus.o
+common-obj-y += virtio-mmio.o
common-obj-$(CONFIG_VIRTIO_BLK_DATA_PLANE) += dataplane/
obj-y += virtio.o virtio-balloon.o
diff --git a/hw/virtio/virtio-mmio.c b/hw/virtio/virtio-mmio.c
new file mode 100644
index 0000000000..54d6679516
--- /dev/null
+++ b/hw/virtio/virtio-mmio.c
@@ -0,0 +1,421 @@
+/*
+ * Virtio MMIO bindings
+ *
+ * Copyright (c) 2011 Linaro Limited
+ *
+ * Author:
+ * Peter Maydell <peter.maydell@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/virtio/virtio.h"
+#include "qemu/host-utils.h"
+#include "hw/virtio/virtio-bus.h"
+
+/* #define DEBUG_VIRTIO_MMIO */
+
+#ifdef DEBUG_VIRTIO_MMIO
+
+#define DPRINTF(fmt, ...) \
+do { printf("virtio_mmio: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+/* QOM macros */
+/* virtio-mmio-bus */
+#define TYPE_VIRTIO_MMIO_BUS "virtio-mmio-bus"
+#define VIRTIO_MMIO_BUS(obj) \
+ OBJECT_CHECK(VirtioBusState, (obj), TYPE_VIRTIO_MMIO_BUS)
+#define VIRTIO_MMIO_BUS_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(VirtioBusClass, (obj), TYPE_VIRTIO_MMIO_BUS)
+#define VIRTIO_MMIO_BUS_CLASS(klass) \
+ OBJECT_CLASS_CHECK(VirtioBusClass, (klass), TYPE_VIRTIO_MMIO_BUS)
+
+/* virtio-mmio */
+#define TYPE_VIRTIO_MMIO "virtio-mmio"
+#define VIRTIO_MMIO(obj) \
+ OBJECT_CHECK(VirtIOMMIOProxy, (obj), TYPE_VIRTIO_MMIO)
+
+/* Memory mapped register offsets */
+#define VIRTIO_MMIO_MAGIC 0x0
+#define VIRTIO_MMIO_VERSION 0x4
+#define VIRTIO_MMIO_DEVICEID 0x8
+#define VIRTIO_MMIO_VENDORID 0xc
+#define VIRTIO_MMIO_HOSTFEATURES 0x10
+#define VIRTIO_MMIO_HOSTFEATURESSEL 0x14
+#define VIRTIO_MMIO_GUESTFEATURES 0x20
+#define VIRTIO_MMIO_GUESTFEATURESSEL 0x24
+#define VIRTIO_MMIO_GUESTPAGESIZE 0x28
+#define VIRTIO_MMIO_QUEUESEL 0x30
+#define VIRTIO_MMIO_QUEUENUMMAX 0x34
+#define VIRTIO_MMIO_QUEUENUM 0x38
+#define VIRTIO_MMIO_QUEUEALIGN 0x3c
+#define VIRTIO_MMIO_QUEUEPFN 0x40
+#define VIRTIO_MMIO_QUEUENOTIFY 0x50
+#define VIRTIO_MMIO_INTERRUPTSTATUS 0x60
+#define VIRTIO_MMIO_INTERRUPTACK 0x64
+#define VIRTIO_MMIO_STATUS 0x70
+/* Device specific config space starts here */
+#define VIRTIO_MMIO_CONFIG 0x100
+
+#define VIRT_MAGIC 0x74726976 /* 'virt' */
+#define VIRT_VERSION 1
+#define VIRT_VENDOR 0x554D4551 /* 'QEMU' */
+
+typedef struct {
+ /* Generic */
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+ qemu_irq irq;
+ uint32_t host_features;
+ /* Guest accessible state needing migration and reset */
+ uint32_t host_features_sel;
+ uint32_t guest_features_sel;
+ uint32_t guest_page_shift;
+ /* virtio-bus */
+ VirtioBusState bus;
+} VirtIOMMIOProxy;
+
+static void virtio_mmio_bus_new(VirtioBusState *bus, VirtIOMMIOProxy *dev);
+
+static uint64_t virtio_mmio_read(void *opaque, hwaddr offset, unsigned size)
+{
+ VirtIOMMIOProxy *proxy = (VirtIOMMIOProxy *)opaque;
+ VirtIODevice *vdev = proxy->bus.vdev;
+
+ DPRINTF("virtio_mmio_read offset 0x%x\n", (int)offset);
+
+ if (!vdev) {
+ /* If no backend is present, we treat most registers as
+ * read-as-zero, except for the magic number, version and
+ * vendor ID. This is not strictly sanctioned by the virtio
+ * spec, but it allows us to provide transports with no backend
+ * plugged in which don't confuse Linux's virtio code: the
+ * probe won't complain about the bad magic number, but the
+ * device ID of zero means no backend will claim it.
+ */
+ switch (offset) {
+ case VIRTIO_MMIO_MAGIC:
+ return VIRT_MAGIC;
+ case VIRTIO_MMIO_VERSION:
+ return VIRT_VERSION;
+ case VIRTIO_MMIO_VENDORID:
+ return VIRT_VENDOR;
+ default:
+ return 0;
+ }
+ }
+
+ if (offset >= VIRTIO_MMIO_CONFIG) {
+ offset -= VIRTIO_MMIO_CONFIG;
+ switch (size) {
+ case 1:
+ return virtio_config_readb(vdev, offset);
+ case 2:
+ return virtio_config_readw(vdev, offset);
+ case 4:
+ return virtio_config_readl(vdev, offset);
+ default:
+ abort();
+ }
+ }
+ if (size != 4) {
+ DPRINTF("wrong size access to register!\n");
+ return 0;
+ }
+ switch (offset) {
+ case VIRTIO_MMIO_MAGIC:
+ return VIRT_MAGIC;
+ case VIRTIO_MMIO_VERSION:
+ return VIRT_VERSION;
+ case VIRTIO_MMIO_DEVICEID:
+ return vdev->device_id;
+ case VIRTIO_MMIO_VENDORID:
+ return VIRT_VENDOR;
+ case VIRTIO_MMIO_HOSTFEATURES:
+ if (proxy->host_features_sel) {
+ return 0;
+ }
+ return proxy->host_features;
+ case VIRTIO_MMIO_QUEUENUMMAX:
+ return VIRTQUEUE_MAX_SIZE;
+ case VIRTIO_MMIO_QUEUEPFN:
+ return virtio_queue_get_addr(vdev, vdev->queue_sel)
+ >> proxy->guest_page_shift;
+ case VIRTIO_MMIO_INTERRUPTSTATUS:
+ return vdev->isr;
+ case VIRTIO_MMIO_STATUS:
+ return vdev->status;
+ case VIRTIO_MMIO_HOSTFEATURESSEL:
+ case VIRTIO_MMIO_GUESTFEATURES:
+ case VIRTIO_MMIO_GUESTFEATURESSEL:
+ case VIRTIO_MMIO_GUESTPAGESIZE:
+ case VIRTIO_MMIO_QUEUESEL:
+ case VIRTIO_MMIO_QUEUENUM:
+ case VIRTIO_MMIO_QUEUEALIGN:
+ case VIRTIO_MMIO_QUEUENOTIFY:
+ case VIRTIO_MMIO_INTERRUPTACK:
+ DPRINTF("read of write-only register\n");
+ return 0;
+ default:
+ DPRINTF("bad register offset\n");
+ return 0;
+ }
+ return 0;
+}
+
+static void virtio_mmio_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ VirtIOMMIOProxy *proxy = (VirtIOMMIOProxy *)opaque;
+ VirtIODevice *vdev = proxy->bus.vdev;
+
+ DPRINTF("virtio_mmio_write offset 0x%x value 0x%" PRIx64 "\n",
+ (int)offset, value);
+
+ if (!vdev) {
+ /* If no backend is present, we just make all registers
+ * write-ignored. This allows us to provide transports with
+ * no backend plugged in.
+ */
+ return;
+ }
+
+ if (offset >= VIRTIO_MMIO_CONFIG) {
+ offset -= VIRTIO_MMIO_CONFIG;
+ switch (size) {
+ case 1:
+ virtio_config_writeb(vdev, offset, value);
+ break;
+ case 2:
+ virtio_config_writew(vdev, offset, value);
+ break;
+ case 4:
+ virtio_config_writel(vdev, offset, value);
+ break;
+ default:
+ abort();
+ }
+ return;
+ }
+ if (size != 4) {
+ DPRINTF("wrong size access to register!\n");
+ return;
+ }
+ switch (offset) {
+ case VIRTIO_MMIO_HOSTFEATURESSEL:
+ proxy->host_features_sel = value;
+ break;
+ case VIRTIO_MMIO_GUESTFEATURES:
+ if (!proxy->guest_features_sel) {
+ virtio_set_features(vdev, value);
+ }
+ break;
+ case VIRTIO_MMIO_GUESTFEATURESSEL:
+ proxy->guest_features_sel = value;
+ break;
+ case VIRTIO_MMIO_GUESTPAGESIZE:
+ proxy->guest_page_shift = ctz32(value);
+ if (proxy->guest_page_shift > 31) {
+ proxy->guest_page_shift = 0;
+ }
+ DPRINTF("guest page size %" PRIx64 " shift %d\n", value,
+ proxy->guest_page_shift);
+ break;
+ case VIRTIO_MMIO_QUEUESEL:
+ if (value < VIRTIO_PCI_QUEUE_MAX) {
+ vdev->queue_sel = value;
+ }
+ break;
+ case VIRTIO_MMIO_QUEUENUM:
+ DPRINTF("mmio_queue write %d max %d\n", (int)value, VIRTQUEUE_MAX_SIZE);
+ virtio_queue_set_num(vdev, vdev->queue_sel, value);
+ break;
+ case VIRTIO_MMIO_QUEUEALIGN:
+ virtio_queue_set_align(vdev, vdev->queue_sel, value);
+ break;
+ case VIRTIO_MMIO_QUEUEPFN:
+ if (value == 0) {
+ virtio_reset(vdev);
+ } else {
+ virtio_queue_set_addr(vdev, vdev->queue_sel,
+ value << proxy->guest_page_shift);
+ }
+ break;
+ case VIRTIO_MMIO_QUEUENOTIFY:
+ if (value < VIRTIO_PCI_QUEUE_MAX) {
+ virtio_queue_notify(vdev, value);
+ }
+ break;
+ case VIRTIO_MMIO_INTERRUPTACK:
+ vdev->isr &= ~value;
+ virtio_update_irq(vdev);
+ break;
+ case VIRTIO_MMIO_STATUS:
+ virtio_set_status(vdev, value & 0xff);
+ if (vdev->status == 0) {
+ virtio_reset(vdev);
+ }
+ break;
+ case VIRTIO_MMIO_MAGIC:
+ case VIRTIO_MMIO_VERSION:
+ case VIRTIO_MMIO_DEVICEID:
+ case VIRTIO_MMIO_VENDORID:
+ case VIRTIO_MMIO_HOSTFEATURES:
+ case VIRTIO_MMIO_QUEUENUMMAX:
+ case VIRTIO_MMIO_INTERRUPTSTATUS:
+ DPRINTF("write to readonly register\n");
+ break;
+
+ default:
+ DPRINTF("bad register offset\n");
+ }
+}
+
+static const MemoryRegionOps virtio_mem_ops = {
+ .read = virtio_mmio_read,
+ .write = virtio_mmio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void virtio_mmio_update_irq(DeviceState *opaque, uint16_t vector)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
+ int level;
+
+ if (!proxy->bus.vdev) {
+ return;
+ }
+ level = (proxy->bus.vdev->isr != 0);
+ DPRINTF("virtio_mmio setting IRQ %d\n", level);
+ qemu_set_irq(proxy->irq, level);
+}
+
+static unsigned int virtio_mmio_get_features(DeviceState *opaque)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
+
+ return proxy->host_features;
+}
+
+static int virtio_mmio_load_config(DeviceState *opaque, QEMUFile *f)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
+
+ proxy->host_features_sel = qemu_get_be32(f);
+ proxy->guest_features_sel = qemu_get_be32(f);
+ proxy->guest_page_shift = qemu_get_be32(f);
+ return 0;
+}
+
+static void virtio_mmio_save_config(DeviceState *opaque, QEMUFile *f)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
+
+ qemu_put_be32(f, proxy->host_features_sel);
+ qemu_put_be32(f, proxy->guest_features_sel);
+ qemu_put_be32(f, proxy->guest_page_shift);
+}
+
+static void virtio_mmio_reset(DeviceState *d)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(d);
+
+ virtio_bus_reset(&proxy->bus);
+ proxy->host_features_sel = 0;
+ proxy->guest_features_sel = 0;
+ proxy->guest_page_shift = 0;
+}
+
+/* virtio-mmio device */
+
+/* This is called by virtio-bus just after the device is plugged. */
+static void virtio_mmio_device_plugged(DeviceState *opaque)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
+
+ proxy->host_features |= (0x1 << VIRTIO_F_NOTIFY_ON_EMPTY);
+ proxy->host_features = virtio_bus_get_vdev_features(&proxy->bus,
+ proxy->host_features);
+}
+
+static void virtio_mmio_realizefn(DeviceState *d, Error **errp)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(d);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(d);
+
+ virtio_mmio_bus_new(&proxy->bus, proxy);
+ sysbus_init_irq(sbd, &proxy->irq);
+ memory_region_init_io(&proxy->iomem, OBJECT(d), &virtio_mem_ops, proxy,
+ TYPE_VIRTIO_MMIO, 0x200);
+ sysbus_init_mmio(sbd, &proxy->iomem);
+}
+
+static void virtio_mmio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = virtio_mmio_realizefn;
+ dc->reset = virtio_mmio_reset;
+}
+
+static const TypeInfo virtio_mmio_info = {
+ .name = TYPE_VIRTIO_MMIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(VirtIOMMIOProxy),
+ .class_init = virtio_mmio_class_init,
+};
+
+/* virtio-mmio-bus. */
+
+static void virtio_mmio_bus_new(VirtioBusState *bus, VirtIOMMIOProxy *dev)
+{
+ DeviceState *qdev = DEVICE(dev);
+ BusState *qbus;
+
+ qbus_create_inplace((BusState *)bus, TYPE_VIRTIO_MMIO_BUS, qdev, NULL);
+ qbus = BUS(bus);
+ qbus->allow_hotplug = 0;
+}
+
+static void virtio_mmio_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *bus_class = BUS_CLASS(klass);
+ VirtioBusClass *k = VIRTIO_BUS_CLASS(klass);
+
+ k->notify = virtio_mmio_update_irq;
+ k->save_config = virtio_mmio_save_config;
+ k->load_config = virtio_mmio_load_config;
+ k->get_features = virtio_mmio_get_features;
+ k->device_plugged = virtio_mmio_device_plugged;
+ k->has_variable_vring_alignment = true;
+ bus_class->max_dev = 1;
+}
+
+static const TypeInfo virtio_mmio_bus_info = {
+ .name = TYPE_VIRTIO_MMIO_BUS,
+ .parent = TYPE_VIRTIO_BUS,
+ .instance_size = sizeof(VirtioBusState),
+ .class_init = virtio_mmio_bus_class_init,
+};
+
+static void virtio_mmio_register_types(void)
+{
+ type_register_static(&virtio_mmio_bus_info);
+ type_register_static(&virtio_mmio_info);
+}
+
+type_init(virtio_mmio_register_types)
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
index 8176c147e1..09f62c6c70 100644
--- a/hw/virtio/virtio.c
+++ b/hw/virtio/virtio.c
@@ -19,8 +19,11 @@
#include "qemu/atomic.h"
#include "hw/virtio/virtio-bus.h"
-/* The alignment to use between consumer and producer parts of vring.
- * x86 pagesize again. */
+/*
+ * The alignment to use between consumer and producer parts of vring.
+ * x86 pagesize again. This is the default, used by transports like PCI
+ * which don't provide a means for the guest to tell the host the alignment.
+ */
#define VIRTIO_PCI_VRING_ALIGN 4096
typedef struct VRingDesc
@@ -54,6 +57,7 @@ typedef struct VRingUsed
typedef struct VRing
{
unsigned int num;
+ unsigned int align;
hwaddr desc;
hwaddr avail;
hwaddr used;
@@ -93,7 +97,7 @@ static void virtqueue_init(VirtQueue *vq)
vq->vring.avail = pa + vq->vring.num * sizeof(VRingDesc);
vq->vring.used = vring_align(vq->vring.avail +
offsetof(VRingAvail, ring[vq->vring.num]),
- VIRTIO_PCI_VRING_ALIGN);
+ vq->vring.align);
}
static inline uint64_t vring_desc_addr(hwaddr desc_pa, int i)
@@ -667,6 +671,14 @@ hwaddr virtio_queue_get_addr(VirtIODevice *vdev, int n)
return vdev->vq[n].pa;
}
+void virtio_queue_set_num(VirtIODevice *vdev, int n, int num)
+{
+ if (num <= VIRTQUEUE_MAX_SIZE) {
+ vdev->vq[n].vring.num = num;
+ virtqueue_init(&vdev->vq[n]);
+ }
+}
+
int virtio_queue_get_num(VirtIODevice *vdev, int n)
{
return vdev->vq[n].vring.num;
@@ -679,6 +691,21 @@ int virtio_queue_get_id(VirtQueue *vq)
return vq - &vdev->vq[0];
}
+void virtio_queue_set_align(VirtIODevice *vdev, int n, int align)
+{
+ BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+
+ /* Check that the transport told us it was going to do this
+ * (so a buggy transport will immediately assert rather than
+ * silently failing to migrate this state)
+ */
+ assert(k->has_variable_vring_alignment);
+
+ vdev->vq[n].vring.align = align;
+ virtqueue_init(&vdev->vq[n]);
+}
+
void virtio_queue_notify_vq(VirtQueue *vq)
{
if (vq->vring.desc) {
@@ -719,6 +746,7 @@ VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size,
abort();
vdev->vq[i].vring.num = queue_size;
+ vdev->vq[i].vring.align = VIRTIO_PCI_VRING_ALIGN;
vdev->vq[i].handle_output = handle_output;
return &vdev->vq[i];
@@ -825,6 +853,9 @@ void virtio_save(VirtIODevice *vdev, QEMUFile *f)
break;
qemu_put_be32(f, vdev->vq[i].vring.num);
+ if (k->has_variable_vring_alignment) {
+ qemu_put_be32(f, vdev->vq[i].vring.align);
+ }
qemu_put_be64(f, vdev->vq[i].pa);
qemu_put_be16s(f, &vdev->vq[i].last_avail_idx);
if (k->save_queue) {
@@ -881,6 +912,9 @@ int virtio_load(VirtIODevice *vdev, QEMUFile *f)
for (i = 0; i < num; i++) {
vdev->vq[i].vring.num = qemu_get_be32(f);
+ if (k->has_variable_vring_alignment) {
+ vdev->vq[i].vring.align = qemu_get_be32(f);
+ }
vdev->vq[i].pa = qemu_get_be64(f);
qemu_get_be16s(f, &vdev->vq[i].last_avail_idx);
vdev->vq[i].signalled_used_valid = false;