diff mbox

[v8,18/23] RISC-V VirtIO Machine

Message ID 1519998711-73430-19-git-send-email-mjc@sifive.com (mailing list archive)
State New, archived
Headers show

Commit Message

Michael Clark March 2, 2018, 1:51 p.m. UTC
RISC-V machine with device-tree, 16550a UART and VirtIO MMIO.
The following machine is implemented:

- 'virt'; CLINT, PLIC, 16550A UART, VirtIO MMIO, device-tree

Acked-by: Richard Henderson <richard.henderson@linaro.org>
Signed-off-by: Palmer Dabbelt <palmer@sifive.com>
Signed-off-by: Michael Clark <mjc@sifive.com>
---
 hw/riscv/virt.c         | 420 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/hw/riscv/virt.h |  74 +++++++++
 2 files changed, 494 insertions(+)
 create mode 100644 hw/riscv/virt.c
 create mode 100644 include/hw/riscv/virt.h

Comments

Peter Maydell April 27, 2018, 2:17 p.m. UTC | #1
On 2 March 2018 at 13:51, Michael Clark <mjc@sifive.com> wrote:
> RISC-V machine with device-tree, 16550a UART and VirtIO MMIO.
> The following machine is implemented:
>
> - 'virt'; CLINT, PLIC, 16550A UART, VirtIO MMIO, device-tree
>
> Acked-by: Richard Henderson <richard.henderson@linaro.org>
> Signed-off-by: Palmer Dabbelt <palmer@sifive.com>
> Signed-off-by: Michael Clark <mjc@sifive.com>

Hi; Coverity spots (CID 1390606) that in this function you
leak a little bit of memory:

> +static void riscv_virt_board_init(MachineState *machine)
> +{

> +    /* create PLIC hart topology configuration string */
> +    plic_hart_config_len = (strlen(VIRT_PLIC_HART_CONFIG) + 1) * smp_cpus;
> +    plic_hart_config = g_malloc0(plic_hart_config_len);

Here we allocate memory into plic_hart_config...

> +    for (i = 0; i < smp_cpus; i++) {
> +        if (i != 0) {
> +            strncat(plic_hart_config, ",", plic_hart_config_len);
> +        }
> +        strncat(plic_hart_config, VIRT_PLIC_HART_CONFIG, plic_hart_config_len);
> +        plic_hart_config_len -= (strlen(VIRT_PLIC_HART_CONFIG) + 1);
> +    }
> +
> +    /* MMIO */
> +    s->plic = sifive_plic_create(memmap[VIRT_PLIC].base,
> +        plic_hart_config,

(and this function doesn't take ownership of the string)

> +        VIRT_PLIC_NUM_SOURCES,
> +        VIRT_PLIC_NUM_PRIORITIES,
> +        VIRT_PLIC_PRIORITY_BASE,
> +        VIRT_PLIC_PENDING_BASE,
> +        VIRT_PLIC_ENABLE_BASE,
> +        VIRT_PLIC_ENABLE_STRIDE,
> +        VIRT_PLIC_CONTEXT_BASE,
> +        VIRT_PLIC_CONTEXT_STRIDE,
> +        memmap[VIRT_PLIC].size);
> +    sifive_clint_create(memmap[VIRT_CLINT].base,
> +        memmap[VIRT_CLINT].size, smp_cpus,
> +        SIFIVE_SIP_BASE, SIFIVE_TIMECMP_BASE, SIFIVE_TIME_BASE);
> +    sifive_test_create(memmap[VIRT_TEST].base);
> +
> +    for (i = 0; i < VIRTIO_COUNT; i++) {
> +        sysbus_create_simple("virtio-mmio",
> +            memmap[VIRT_VIRTIO].base + i * memmap[VIRT_VIRTIO].size,
> +            SIFIVE_PLIC(s->plic)->irqs[VIRTIO_IRQ + i]);
> +    }
> +
> +    serial_mm_init(system_memory, memmap[VIRT_UART0].base,
> +        0, SIFIVE_PLIC(s->plic)->irqs[UART0_IRQ], 399193,
> +        serial_hds[0], DEVICE_LITTLE_ENDIAN);

...but we don't free the memory before leaving.

> +}

Not a big deal since this function is only run once, but adding
in the necessary g_free(plic_hart_config) will placate Coverity.

thanks
-- PMM
Michael Clark April 30, 2018, 12:18 a.m. UTC | #2
On Sat, Apr 28, 2018 at 2:17 AM, Peter Maydell <peter.maydell@linaro.org>
wrote:

> On 2 March 2018 at 13:51, Michael Clark <mjc@sifive.com> wrote:
> > RISC-V machine with device-tree, 16550a UART and VirtIO MMIO.
> > The following machine is implemented:
> >
> > - 'virt'; CLINT, PLIC, 16550A UART, VirtIO MMIO, device-tree
> >
> > Acked-by: Richard Henderson <richard.henderson@linaro.org>
> > Signed-off-by: Palmer Dabbelt <palmer@sifive.com>
> > Signed-off-by: Michael Clark <mjc@sifive.com>
>
> Hi; Coverity spots (CID 1390606) that in this function you
> leak a little bit of memory:
>
> > +static void riscv_virt_board_init(MachineState *machine)
> > +{
>
> > +    /* create PLIC hart topology configuration string */
> > +    plic_hart_config_len = (strlen(VIRT_PLIC_HART_CONFIG) + 1) *
> smp_cpus;
> > +    plic_hart_config = g_malloc0(plic_hart_config_len);
>
> Here we allocate memory into plic_hart_config...
>
> > +    for (i = 0; i < smp_cpus; i++) {
> > +        if (i != 0) {
> > +            strncat(plic_hart_config, ",", plic_hart_config_len);
> > +        }
> > +        strncat(plic_hart_config, VIRT_PLIC_HART_CONFIG,
> plic_hart_config_len);
> > +        plic_hart_config_len -= (strlen(VIRT_PLIC_HART_CONFIG) + 1);
> > +    }
> > +
> > +    /* MMIO */
> > +    s->plic = sifive_plic_create(memmap[VIRT_PLIC].base,
> > +        plic_hart_config,
>
> (and this function doesn't take ownership of the string)
>
> > +        VIRT_PLIC_NUM_SOURCES,
> > +        VIRT_PLIC_NUM_PRIORITIES,
> > +        VIRT_PLIC_PRIORITY_BASE,
> > +        VIRT_PLIC_PENDING_BASE,
> > +        VIRT_PLIC_ENABLE_BASE,
> > +        VIRT_PLIC_ENABLE_STRIDE,
> > +        VIRT_PLIC_CONTEXT_BASE,
> > +        VIRT_PLIC_CONTEXT_STRIDE,
> > +        memmap[VIRT_PLIC].size);
> > +    sifive_clint_create(memmap[VIRT_CLINT].base,
> > +        memmap[VIRT_CLINT].size, smp_cpus,
> > +        SIFIVE_SIP_BASE, SIFIVE_TIMECMP_BASE, SIFIVE_TIME_BASE);
> > +    sifive_test_create(memmap[VIRT_TEST].base);
> > +
> > +    for (i = 0; i < VIRTIO_COUNT; i++) {
> > +        sysbus_create_simple("virtio-mmio",
> > +            memmap[VIRT_VIRTIO].base + i * memmap[VIRT_VIRTIO].size,
> > +            SIFIVE_PLIC(s->plic)->irqs[VIRTIO_IRQ + i]);
> > +    }
> > +
> > +    serial_mm_init(system_memory, memmap[VIRT_UART0].base,
> > +        0, SIFIVE_PLIC(s->plic)->irqs[UART0_IRQ], 399193,
> > +        serial_hds[0], DEVICE_LITTLE_ENDIAN);
>
> ...but we don't free the memory before leaving.
>
> > +}
>
> Not a big deal since this function is only run once, but adding
> in the necessary g_free(plic_hart_config) will placate Coverity.
>
>
Didn't mean to go off list. I'm adding Alastair as he is looking at
refactoring the machines to use QOM for an SOC holding the devices,
separate from the machine state structure.

Quite a bit of our initialization code in several QOM classes allocate
memory that is not freed. e.g. the PLIC. Usually these functions are only
run once, but ideally all of the code should be memory clean. i.e. exit
without leaks. Many programs don't bother with this but I think it is a
good practice.

Should we use dc->unrealize to point to an unrelalize function that calls
g_free? Is unrealize the QOM destructor?

As an aside, for Alastair's sake. We intend to implement and generalize ISA
string parsing so that RISCVHartArray is indeed heterogeneous and the PLIC
can figure out its dimensions from RISCVHartArray. Once the CPUs are
realized, we can derive the modes that a CPU/hart supports from 'misa'. The
RTL designers for the SiFive PLIC made a decision to save address space
versus using 2-bits in the interrupt configuration address space to encode
mode (with reserved areas for unsupported modes). With the current compact
address space layout, we need to know which modes a hart (hardware thread)
supports to see how much address space it uses in the PLIC configuration
aperture, and each CPU can have a variable sized aperture depending on the
modes it supports. This complicates dynamic address decode as we can't
simply use ranges of bits to get mode and hart. The RTL generates the
address decode statically from the core complex configuration so it's not
an issue in hardware, although one would wonder whether it might use more
comparators in its address decode logic. Also of note, is that we may add
an option to the PLIC that inverts the dimensions from { hart, mode } to {
mode, hart } so that M mode can use PMP to protect its interrupt routing
configuration. Presently the modes are interleaved instead of having per
per mode apertures (which is required for virtualization of the PLIC). That
requirement (dimension order) would dictate a more regular address space
layout.
Peter Maydell April 30, 2018, 7:49 a.m. UTC | #3
On 30 April 2018 at 01:18, Michael Clark <mjc@sifive.com> wrote:
> Quite a bit of our initialization code in several QOM classes allocate
> memory that is not freed. e.g. the PLIC. Usually these functions are only
> run once, but ideally all of the code should be memory clean. i.e. exit
> without leaks. Many programs don't bother with this but I think it is a good
> practice.
>
> Should we use dc->unrealize to point to an unrelalize function that calls
> g_free? Is unrealize the QOM destructor?

unrealize is the destructor, yes, but we currently only use it for
devices that are hot-pluggable (and thus unpluggable). For non-pluggable
devices we currently generally don't worry about giving the objects
a destructor, since it can never be called.

This is a bit weird though, and I think there's a thread on-list
somewhere where there's a proposal that we should (a) document
better the best way to divide tasks between init/realize and
(b) perhaps suggest that devices should init unrealize too.

Board model code is also rather prone to "allocate and never free",
because historically boards didn't have a QOM object to own the
memory they allocated. This is why board code often does
g_new0(MemoryRegion, 1) to allocate MemoryRegions, where device
code would just have a MemoryRegion field in its device struct.
Now that boards have real MachineState objects associated with
them, it is possible to write them the way you write a device --
hw/arm/mps2.c has an example of this. Since again it doesn't have
a destructor there's not much difference in practice, though:
we still rely on the memory being freed on program exit.

thanks
-- PMM
diff mbox

Patch

diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
new file mode 100644
index 0000000..e2c214e
--- /dev/null
+++ b/hw/riscv/virt.c
@@ -0,0 +1,420 @@ 
+/*
+ * QEMU RISC-V VirtIO Board
+ *
+ * Copyright (c) 2017 SiFive, Inc.
+ *
+ * RISC-V machine with 16550a UART and VirtIO MMIO
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "hw/hw.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "hw/sysbus.h"
+#include "hw/char/serial.h"
+#include "target/riscv/cpu.h"
+#include "hw/riscv/riscv_htif.h"
+#include "hw/riscv/riscv_hart.h"
+#include "hw/riscv/sifive_plic.h"
+#include "hw/riscv/sifive_clint.h"
+#include "hw/riscv/sifive_test.h"
+#include "hw/riscv/virt.h"
+#include "chardev/char.h"
+#include "sysemu/arch_init.h"
+#include "sysemu/device_tree.h"
+#include "exec/address-spaces.h"
+#include "elf.h"
+
+static const struct MemmapEntry {
+    hwaddr base;
+    hwaddr size;
+} virt_memmap[] = {
+    [VIRT_DEBUG] =    {        0x0,      0x100 },
+    [VIRT_MROM] =     {     0x1000,     0x2000 },
+    [VIRT_TEST] =     {     0x4000,     0x1000 },
+    [VIRT_CLINT] =    {  0x2000000,    0x10000 },
+    [VIRT_PLIC] =     {  0xc000000,  0x4000000 },
+    [VIRT_UART0] =    { 0x10000000,      0x100 },
+    [VIRT_VIRTIO] =   { 0x10001000,     0x1000 },
+    [VIRT_DRAM] =     { 0x80000000,        0x0 },
+};
+
+static void copy_le32_to_phys(hwaddr pa, uint32_t *rom, size_t len)
+{
+    int i;
+    for (i = 0; i < (len >> 2); i++) {
+        stl_phys(&address_space_memory, pa + (i << 2), rom[i]);
+    }
+}
+
+static uint64_t identity_translate(void *opaque, uint64_t addr)
+{
+    return addr;
+}
+
+static uint64_t load_kernel(const char *kernel_filename)
+{
+    uint64_t kernel_entry, kernel_high;
+
+    if (load_elf(kernel_filename, identity_translate, NULL,
+                 &kernel_entry, NULL, &kernel_high,
+                 0, ELF_MACHINE, 1, 0) < 0) {
+        error_report("qemu: could not load kernel '%s'", kernel_filename);
+        exit(1);
+    }
+    return kernel_entry;
+}
+
+static hwaddr load_initrd(const char *filename, uint64_t mem_size,
+                          uint64_t kernel_entry, hwaddr *start)
+{
+    int size;
+
+    /* We want to put the initrd far enough into RAM that when the
+     * kernel is uncompressed it will not clobber the initrd. However
+     * on boards without much RAM we must ensure that we still leave
+     * enough room for a decent sized initrd, and on boards with large
+     * amounts of RAM we must avoid the initrd being so far up in RAM
+     * that it is outside lowmem and inaccessible to the kernel.
+     * So for boards with less  than 256MB of RAM we put the initrd
+     * halfway into RAM, and for boards with 256MB of RAM or more we put
+     * the initrd at 128MB.
+     */
+    *start = kernel_entry + MIN(mem_size / 2, 128 * 1024 * 1024);
+
+    size = load_ramdisk(filename, *start, mem_size - *start);
+    if (size == -1) {
+        size = load_image_targphys(filename, *start, mem_size - *start);
+        if (size == -1) {
+            error_report("qemu: could not load ramdisk '%s'", filename);
+            exit(1);
+        }
+    }
+    return *start + size;
+}
+
+static void *create_fdt(RISCVVirtState *s, const struct MemmapEntry *memmap,
+    uint64_t mem_size, const char *cmdline)
+{
+    void *fdt;
+    int cpu;
+    uint32_t *cells;
+    char *nodename;
+    uint32_t plic_phandle, phandle = 1;
+    int i;
+
+    fdt = s->fdt = create_device_tree(&s->fdt_size);
+    if (!fdt) {
+        error_report("create_device_tree() failed");
+        exit(1);
+    }
+
+    qemu_fdt_setprop_string(fdt, "/", "model", "riscv-virtio,qemu");
+    qemu_fdt_setprop_string(fdt, "/", "compatible", "riscv-virtio");
+    qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x2);
+    qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x2);
+
+    qemu_fdt_add_subnode(fdt, "/soc");
+    qemu_fdt_setprop(fdt, "/soc", "ranges", NULL, 0);
+    qemu_fdt_setprop_string(fdt, "/soc", "compatible", "riscv-virtio-soc");
+    qemu_fdt_setprop_cell(fdt, "/soc", "#size-cells", 0x2);
+    qemu_fdt_setprop_cell(fdt, "/soc", "#address-cells", 0x2);
+
+    nodename = g_strdup_printf("/memory@%lx",
+        (long)memmap[VIRT_DRAM].base);
+    qemu_fdt_add_subnode(fdt, nodename);
+    qemu_fdt_setprop_cells(fdt, nodename, "reg",
+        memmap[VIRT_DRAM].base >> 32, memmap[VIRT_DRAM].base,
+        mem_size >> 32, mem_size);
+    qemu_fdt_setprop_string(fdt, nodename, "device_type", "memory");
+    g_free(nodename);
+
+    qemu_fdt_add_subnode(fdt, "/cpus");
+    qemu_fdt_setprop_cell(fdt, "/cpus", "timebase-frequency", 10000000);
+    qemu_fdt_setprop_cell(fdt, "/cpus", "#size-cells", 0x0);
+    qemu_fdt_setprop_cell(fdt, "/cpus", "#address-cells", 0x1);
+
+    for (cpu = s->soc.num_harts - 1; cpu >= 0; cpu--) {
+        int cpu_phandle = phandle++;
+        nodename = g_strdup_printf("/cpus/cpu@%d", cpu);
+        char *intc = g_strdup_printf("/cpus/cpu@%d/interrupt-controller", cpu);
+        char *isa = riscv_isa_string(&s->soc.harts[cpu]);
+        qemu_fdt_add_subnode(fdt, nodename);
+        qemu_fdt_setprop_cell(fdt, nodename, "clock-frequency", 1000000000);
+        qemu_fdt_setprop_string(fdt, nodename, "mmu-type", "riscv,sv48");
+        qemu_fdt_setprop_string(fdt, nodename, "riscv,isa", isa);
+        qemu_fdt_setprop_string(fdt, nodename, "compatible", "riscv");
+        qemu_fdt_setprop_string(fdt, nodename, "status", "okay");
+        qemu_fdt_setprop_cell(fdt, nodename, "reg", cpu);
+        qemu_fdt_setprop_string(fdt, nodename, "device_type", "cpu");
+        qemu_fdt_add_subnode(fdt, intc);
+        qemu_fdt_setprop_cell(fdt, intc, "phandle", cpu_phandle);
+        qemu_fdt_setprop_cell(fdt, intc, "linux,phandle", cpu_phandle);
+        qemu_fdt_setprop_string(fdt, intc, "compatible", "riscv,cpu-intc");
+        qemu_fdt_setprop(fdt, intc, "interrupt-controller", NULL, 0);
+        qemu_fdt_setprop_cell(fdt, intc, "#interrupt-cells", 1);
+        g_free(isa);
+        g_free(intc);
+        g_free(nodename);
+    }
+
+    cells =  g_new0(uint32_t, s->soc.num_harts * 4);
+    for (cpu = 0; cpu < s->soc.num_harts; cpu++) {
+        nodename =
+            g_strdup_printf("/cpus/cpu@%d/interrupt-controller", cpu);
+        uint32_t intc_phandle = qemu_fdt_get_phandle(fdt, nodename);
+        cells[cpu * 4 + 0] = cpu_to_be32(intc_phandle);
+        cells[cpu * 4 + 1] = cpu_to_be32(IRQ_M_SOFT);
+        cells[cpu * 4 + 2] = cpu_to_be32(intc_phandle);
+        cells[cpu * 4 + 3] = cpu_to_be32(IRQ_M_TIMER);
+        g_free(nodename);
+    }
+    nodename = g_strdup_printf("/soc/clint@%lx",
+        (long)memmap[VIRT_CLINT].base);
+    qemu_fdt_add_subnode(fdt, nodename);
+    qemu_fdt_setprop_string(fdt, nodename, "compatible", "riscv,clint0");
+    qemu_fdt_setprop_cells(fdt, nodename, "reg",
+        0x0, memmap[VIRT_CLINT].base,
+        0x0, memmap[VIRT_CLINT].size);
+    qemu_fdt_setprop(fdt, nodename, "interrupts-extended",
+        cells, s->soc.num_harts * sizeof(uint32_t) * 4);
+    g_free(cells);
+    g_free(nodename);
+
+    plic_phandle = phandle++;
+    cells =  g_new0(uint32_t, s->soc.num_harts * 4);
+    for (cpu = 0; cpu < s->soc.num_harts; cpu++) {
+        nodename =
+            g_strdup_printf("/cpus/cpu@%d/interrupt-controller", cpu);
+        uint32_t intc_phandle = qemu_fdt_get_phandle(fdt, nodename);
+        cells[cpu * 4 + 0] = cpu_to_be32(intc_phandle);
+        cells[cpu * 4 + 1] = cpu_to_be32(IRQ_M_EXT);
+        cells[cpu * 4 + 2] = cpu_to_be32(intc_phandle);
+        cells[cpu * 4 + 3] = cpu_to_be32(IRQ_S_EXT);
+        g_free(nodename);
+    }
+    nodename = g_strdup_printf("/soc/interrupt-controller@%lx",
+        (long)memmap[VIRT_PLIC].base);
+    qemu_fdt_add_subnode(fdt, nodename);
+    qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells", 1);
+    qemu_fdt_setprop_string(fdt, nodename, "compatible", "riscv,plic0");
+    qemu_fdt_setprop(fdt, nodename, "interrupt-controller", NULL, 0);
+    qemu_fdt_setprop(fdt, nodename, "interrupts-extended",
+        cells, s->soc.num_harts * sizeof(uint32_t) * 4);
+    qemu_fdt_setprop_cells(fdt, nodename, "reg",
+        0x0, memmap[VIRT_PLIC].base,
+        0x0, memmap[VIRT_PLIC].size);
+    qemu_fdt_setprop_string(fdt, nodename, "reg-names", "control");
+    qemu_fdt_setprop_cell(fdt, nodename, "riscv,max-priority", 7);
+    qemu_fdt_setprop_cell(fdt, nodename, "riscv,ndev", VIRTIO_NDEV);
+    qemu_fdt_setprop_cells(fdt, nodename, "phandle", plic_phandle);
+    qemu_fdt_setprop_cells(fdt, nodename, "linux,phandle", plic_phandle);
+    plic_phandle = qemu_fdt_get_phandle(fdt, nodename);
+    g_free(cells);
+    g_free(nodename);
+
+    for (i = 0; i < VIRTIO_COUNT; i++) {
+        nodename = g_strdup_printf("/virtio_mmio@%lx",
+            (long)(memmap[VIRT_VIRTIO].base + i * memmap[VIRT_VIRTIO].size));
+        qemu_fdt_add_subnode(fdt, nodename);
+        qemu_fdt_setprop_string(fdt, nodename, "compatible", "virtio,mmio");
+        qemu_fdt_setprop_cells(fdt, nodename, "reg",
+            0x0, memmap[VIRT_VIRTIO].base + i * memmap[VIRT_VIRTIO].size,
+            0x0, memmap[VIRT_VIRTIO].size);
+        qemu_fdt_setprop_cells(fdt, nodename, "interrupt-parent", plic_phandle);
+        qemu_fdt_setprop_cells(fdt, nodename, "interrupts", VIRTIO_IRQ + i);
+        g_free(nodename);
+    }
+
+    nodename = g_strdup_printf("/test@%lx",
+        (long)memmap[VIRT_TEST].base);
+    qemu_fdt_add_subnode(fdt, nodename);
+    qemu_fdt_setprop_string(fdt, nodename, "compatible", "sifive,test0");
+    qemu_fdt_setprop_cells(fdt, nodename, "reg",
+        0x0, memmap[VIRT_TEST].base,
+        0x0, memmap[VIRT_TEST].size);
+
+    nodename = g_strdup_printf("/uart@%lx",
+        (long)memmap[VIRT_UART0].base);
+    qemu_fdt_add_subnode(fdt, nodename);
+    qemu_fdt_setprop_string(fdt, nodename, "compatible", "ns16550a");
+    qemu_fdt_setprop_cells(fdt, nodename, "reg",
+        0x0, memmap[VIRT_UART0].base,
+        0x0, memmap[VIRT_UART0].size);
+    qemu_fdt_setprop_cell(fdt, nodename, "clock-frequency", 3686400);
+        qemu_fdt_setprop_cells(fdt, nodename, "interrupt-parent", plic_phandle);
+        qemu_fdt_setprop_cells(fdt, nodename, "interrupts", UART0_IRQ);
+
+    qemu_fdt_add_subnode(fdt, "/chosen");
+    qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", nodename);
+    qemu_fdt_setprop_string(fdt, "/chosen", "bootargs", cmdline);
+    g_free(nodename);
+
+    return fdt;
+}
+
+static void riscv_virt_board_init(MachineState *machine)
+{
+    const struct MemmapEntry *memmap = virt_memmap;
+
+    RISCVVirtState *s = g_new0(RISCVVirtState, 1);
+    MemoryRegion *system_memory = get_system_memory();
+    MemoryRegion *main_mem = g_new(MemoryRegion, 1);
+    MemoryRegion *boot_rom = g_new(MemoryRegion, 1);
+    char *plic_hart_config;
+    size_t plic_hart_config_len;
+    int i;
+    void *fdt;
+
+    /* Initialize SOC */
+    object_initialize(&s->soc, sizeof(s->soc), TYPE_RISCV_HART_ARRAY);
+    object_property_add_child(OBJECT(machine), "soc", OBJECT(&s->soc),
+                              &error_abort);
+    object_property_set_str(OBJECT(&s->soc), VIRT_CPU, "cpu-type",
+                            &error_abort);
+    object_property_set_int(OBJECT(&s->soc), smp_cpus, "num-harts",
+                            &error_abort);
+    object_property_set_bool(OBJECT(&s->soc), true, "realized",
+                            &error_abort);
+
+    /* register system main memory (actual RAM) */
+    memory_region_init_ram(main_mem, NULL, "riscv_virt_board.ram",
+                           machine->ram_size, &error_fatal);
+    memory_region_add_subregion(system_memory, memmap[VIRT_DRAM].base,
+        main_mem);
+
+    /* create device tree */
+    fdt = create_fdt(s, memmap, machine->ram_size, machine->kernel_cmdline);
+
+    /* boot rom */
+    memory_region_init_ram(boot_rom, NULL, "riscv_virt_board.bootrom",
+                           s->fdt_size + 0x2000, &error_fatal);
+    memory_region_add_subregion(system_memory, 0x0, boot_rom);
+
+    if (machine->kernel_filename) {
+        uint64_t kernel_entry = load_kernel(machine->kernel_filename);
+
+        if (machine->initrd_filename) {
+            hwaddr start;
+            hwaddr end = load_initrd(machine->initrd_filename,
+                                     machine->ram_size, kernel_entry,
+                                     &start);
+            qemu_fdt_setprop_cell(fdt, "/chosen",
+                                  "linux,initrd-start", start);
+            qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-end",
+                                  end);
+        }
+    }
+
+    /* reset vector */
+    uint32_t reset_vec[8] = {
+        0x00000297,                  /* 1:  auipc  t0, %pcrel_hi(dtb) */
+        0x02028593,                  /*     addi   a1, t0, %pcrel_lo(1b) */
+        0xf1402573,                  /*     csrr   a0, mhartid  */
+#if defined(TARGET_RISCV32)
+        0x0182a283,                  /*     lw     t0, 24(t0) */
+#elif defined(TARGET_RISCV64)
+        0x0182b283,                  /*     ld     t0, 24(t0) */
+#endif
+        0x00028067,                  /*     jr     t0 */
+        0x00000000,
+        memmap[VIRT_DRAM].base,      /* start: .dword memmap[VIRT_DRAM].base */
+        0x00000000,
+                                     /* dtb: */
+    };
+
+    /* copy in the reset vector */
+    copy_le32_to_phys(ROM_BASE, reset_vec, sizeof(reset_vec));
+
+    /* copy in the device tree */
+    qemu_fdt_dumpdtb(s->fdt, s->fdt_size);
+    cpu_physical_memory_write(ROM_BASE + sizeof(reset_vec),
+        s->fdt, s->fdt_size);
+
+    /* create PLIC hart topology configuration string */
+    plic_hart_config_len = (strlen(VIRT_PLIC_HART_CONFIG) + 1) * smp_cpus;
+    plic_hart_config = g_malloc0(plic_hart_config_len);
+    for (i = 0; i < smp_cpus; i++) {
+        if (i != 0) {
+            strncat(plic_hart_config, ",", plic_hart_config_len);
+        }
+        strncat(plic_hart_config, VIRT_PLIC_HART_CONFIG, plic_hart_config_len);
+        plic_hart_config_len -= (strlen(VIRT_PLIC_HART_CONFIG) + 1);
+    }
+
+    /* MMIO */
+    s->plic = sifive_plic_create(memmap[VIRT_PLIC].base,
+        plic_hart_config,
+        VIRT_PLIC_NUM_SOURCES,
+        VIRT_PLIC_NUM_PRIORITIES,
+        VIRT_PLIC_PRIORITY_BASE,
+        VIRT_PLIC_PENDING_BASE,
+        VIRT_PLIC_ENABLE_BASE,
+        VIRT_PLIC_ENABLE_STRIDE,
+        VIRT_PLIC_CONTEXT_BASE,
+        VIRT_PLIC_CONTEXT_STRIDE,
+        memmap[VIRT_PLIC].size);
+    sifive_clint_create(memmap[VIRT_CLINT].base,
+        memmap[VIRT_CLINT].size, smp_cpus,
+        SIFIVE_SIP_BASE, SIFIVE_TIMECMP_BASE, SIFIVE_TIME_BASE);
+    sifive_test_create(memmap[VIRT_TEST].base);
+
+    for (i = 0; i < VIRTIO_COUNT; i++) {
+        sysbus_create_simple("virtio-mmio",
+            memmap[VIRT_VIRTIO].base + i * memmap[VIRT_VIRTIO].size,
+            SIFIVE_PLIC(s->plic)->irqs[VIRTIO_IRQ + i]);
+    }
+
+    serial_mm_init(system_memory, memmap[VIRT_UART0].base,
+        0, SIFIVE_PLIC(s->plic)->irqs[UART0_IRQ], 399193,
+        serial_hds[0], DEVICE_LITTLE_ENDIAN);
+}
+
+static int riscv_virt_board_sysbus_device_init(SysBusDevice *sysbusdev)
+{
+    return 0;
+}
+
+static void riscv_virt_board_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    k->init = riscv_virt_board_sysbus_device_init;
+}
+
+static const TypeInfo riscv_virt_board_device = {
+    .name          = TYPE_RISCV_VIRT_BOARD,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(RISCVVirtState),
+    .class_init    = riscv_virt_board_class_init,
+};
+
+static void riscv_virt_board_machine_init(MachineClass *mc)
+{
+    mc->desc = "RISC-V VirtIO Board (Privileged spec v1.10)";
+    mc->init = riscv_virt_board_init;
+    mc->max_cpus = 8; /* hardcoded limit in BBL */
+}
+
+DEFINE_MACHINE("virt", riscv_virt_board_machine_init)
+
+static void riscv_virt_board_register_types(void)
+{
+    type_register_static(&riscv_virt_board_device);
+}
+
+type_init(riscv_virt_board_register_types);
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
new file mode 100644
index 0000000..7525647
--- /dev/null
+++ b/include/hw/riscv/virt.h
@@ -0,0 +1,74 @@ 
+/*
+ * SiFive VirtIO Board
+ *
+ * Copyright (c) 2017 SiFive, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/>.
+ */
+
+#ifndef HW_VIRT_H
+#define HW_VIRT_H
+
+#define TYPE_RISCV_VIRT_BOARD "riscv.virt"
+#define VIRT(obj) \
+    OBJECT_CHECK(RISCVVirtState, (obj), TYPE_RISCV_VIRT_BOARD)
+
+enum { ROM_BASE = 0x1000 };
+
+typedef struct {
+    /*< private >*/
+    SysBusDevice parent_obj;
+
+    /*< public >*/
+    RISCVHartArrayState soc;
+    DeviceState *plic;
+    void *fdt;
+    int fdt_size;
+} RISCVVirtState;
+
+enum {
+    VIRT_DEBUG,
+    VIRT_MROM,
+    VIRT_TEST,
+    VIRT_CLINT,
+    VIRT_PLIC,
+    VIRT_UART0,
+    VIRT_VIRTIO,
+    VIRT_DRAM
+};
+
+
+enum {
+    UART0_IRQ = 10,
+    VIRTIO_IRQ = 1, /* 1 to 8 */
+    VIRTIO_COUNT = 8,
+    VIRTIO_NDEV = 10
+};
+
+#define VIRT_PLIC_HART_CONFIG "MS"
+#define VIRT_PLIC_NUM_SOURCES 127
+#define VIRT_PLIC_NUM_PRIORITIES 7
+#define VIRT_PLIC_PRIORITY_BASE 0x0
+#define VIRT_PLIC_PENDING_BASE 0x1000
+#define VIRT_PLIC_ENABLE_BASE 0x2000
+#define VIRT_PLIC_ENABLE_STRIDE 0x80
+#define VIRT_PLIC_CONTEXT_BASE 0x200000
+#define VIRT_PLIC_CONTEXT_STRIDE 0x1000
+
+#if defined(TARGET_RISCV32)
+#define VIRT_CPU TYPE_RISCV_CPU_RV32GCSU_V1_10_0
+#elif defined(TARGET_RISCV64)
+#define VIRT_CPU TYPE_RISCV_CPU_RV64GCSU_V1_10_0
+#endif
+
+#endif