mirror of
https://github.com/qemu/qemu.git
synced 2025-07-29 06:26:06 +00:00

Currently, the fdt is copied to the ROM after the reset vector. The firmware has to copy it to DRAM. Instead of this, directly copy the device tree to a pre-computed dram address. The device tree load address should be as far as possible from kernel and initrd images. That's why it is kept at the end of the DRAM or 4GB whichever is lesser. Signed-off-by: Atish Patra <atish.patra@wdc.com> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Reviewed-by: Bin Meng <bin.meng@windriver.com> Tested-by: Bin Meng <bin.meng@windriver.com> Message-Id: <20200701183949.398134-3-atish.patra@wdc.com> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
634 lines
24 KiB
C
634 lines
24 KiB
C
/*
|
|
* 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/units.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qapi/error.h"
|
|
#include "hw/boards.h"
|
|
#include "hw/loader.h"
|
|
#include "hw/sysbus.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/char/serial.h"
|
|
#include "target/riscv/cpu.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 "hw/riscv/boot.h"
|
|
#include "chardev/char.h"
|
|
#include "sysemu/arch_init.h"
|
|
#include "sysemu/device_tree.h"
|
|
#include "sysemu/sysemu.h"
|
|
#include "hw/pci/pci.h"
|
|
#include "hw/pci-host/gpex.h"
|
|
|
|
#if defined(TARGET_RISCV32)
|
|
# define BIOS_FILENAME "opensbi-riscv32-virt-fw_jump.bin"
|
|
#else
|
|
# define BIOS_FILENAME "opensbi-riscv64-virt-fw_jump.bin"
|
|
#endif
|
|
|
|
static const struct MemmapEntry {
|
|
hwaddr base;
|
|
hwaddr size;
|
|
} virt_memmap[] = {
|
|
[VIRT_DEBUG] = { 0x0, 0x100 },
|
|
[VIRT_MROM] = { 0x1000, 0x11000 },
|
|
[VIRT_TEST] = { 0x100000, 0x1000 },
|
|
[VIRT_RTC] = { 0x101000, 0x1000 },
|
|
[VIRT_CLINT] = { 0x2000000, 0x10000 },
|
|
[VIRT_PCIE_PIO] = { 0x3000000, 0x10000 },
|
|
[VIRT_PLIC] = { 0xc000000, 0x4000000 },
|
|
[VIRT_UART0] = { 0x10000000, 0x100 },
|
|
[VIRT_VIRTIO] = { 0x10001000, 0x1000 },
|
|
[VIRT_FLASH] = { 0x20000000, 0x4000000 },
|
|
[VIRT_PCIE_ECAM] = { 0x30000000, 0x10000000 },
|
|
[VIRT_PCIE_MMIO] = { 0x40000000, 0x40000000 },
|
|
[VIRT_DRAM] = { 0x80000000, 0x0 },
|
|
};
|
|
|
|
#define VIRT_FLASH_SECTOR_SIZE (256 * KiB)
|
|
|
|
static PFlashCFI01 *virt_flash_create1(RISCVVirtState *s,
|
|
const char *name,
|
|
const char *alias_prop_name)
|
|
{
|
|
/*
|
|
* Create a single flash device. We use the same parameters as
|
|
* the flash devices on the ARM virt board.
|
|
*/
|
|
DeviceState *dev = qdev_new(TYPE_PFLASH_CFI01);
|
|
|
|
qdev_prop_set_uint64(dev, "sector-length", VIRT_FLASH_SECTOR_SIZE);
|
|
qdev_prop_set_uint8(dev, "width", 4);
|
|
qdev_prop_set_uint8(dev, "device-width", 2);
|
|
qdev_prop_set_bit(dev, "big-endian", false);
|
|
qdev_prop_set_uint16(dev, "id0", 0x89);
|
|
qdev_prop_set_uint16(dev, "id1", 0x18);
|
|
qdev_prop_set_uint16(dev, "id2", 0x00);
|
|
qdev_prop_set_uint16(dev, "id3", 0x00);
|
|
qdev_prop_set_string(dev, "name", name);
|
|
|
|
object_property_add_child(OBJECT(s), name, OBJECT(dev));
|
|
object_property_add_alias(OBJECT(s), alias_prop_name,
|
|
OBJECT(dev), "drive");
|
|
|
|
return PFLASH_CFI01(dev);
|
|
}
|
|
|
|
static void virt_flash_create(RISCVVirtState *s)
|
|
{
|
|
s->flash[0] = virt_flash_create1(s, "virt.flash0", "pflash0");
|
|
s->flash[1] = virt_flash_create1(s, "virt.flash1", "pflash1");
|
|
}
|
|
|
|
static void virt_flash_map1(PFlashCFI01 *flash,
|
|
hwaddr base, hwaddr size,
|
|
MemoryRegion *sysmem)
|
|
{
|
|
DeviceState *dev = DEVICE(flash);
|
|
|
|
assert(QEMU_IS_ALIGNED(size, VIRT_FLASH_SECTOR_SIZE));
|
|
assert(size / VIRT_FLASH_SECTOR_SIZE <= UINT32_MAX);
|
|
qdev_prop_set_uint32(dev, "num-blocks", size / VIRT_FLASH_SECTOR_SIZE);
|
|
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
|
|
|
|
memory_region_add_subregion(sysmem, base,
|
|
sysbus_mmio_get_region(SYS_BUS_DEVICE(dev),
|
|
0));
|
|
}
|
|
|
|
static void virt_flash_map(RISCVVirtState *s,
|
|
MemoryRegion *sysmem)
|
|
{
|
|
hwaddr flashsize = virt_memmap[VIRT_FLASH].size / 2;
|
|
hwaddr flashbase = virt_memmap[VIRT_FLASH].base;
|
|
|
|
virt_flash_map1(s->flash[0], flashbase, flashsize,
|
|
sysmem);
|
|
virt_flash_map1(s->flash[1], flashbase + flashsize, flashsize,
|
|
sysmem);
|
|
}
|
|
|
|
static void create_pcie_irq_map(void *fdt, char *nodename,
|
|
uint32_t plic_phandle)
|
|
{
|
|
int pin, dev;
|
|
uint32_t
|
|
full_irq_map[GPEX_NUM_IRQS * GPEX_NUM_IRQS * FDT_INT_MAP_WIDTH] = {};
|
|
uint32_t *irq_map = full_irq_map;
|
|
|
|
/* This code creates a standard swizzle of interrupts such that
|
|
* each device's first interrupt is based on it's PCI_SLOT number.
|
|
* (See pci_swizzle_map_irq_fn())
|
|
*
|
|
* We only need one entry per interrupt in the table (not one per
|
|
* possible slot) seeing the interrupt-map-mask will allow the table
|
|
* to wrap to any number of devices.
|
|
*/
|
|
for (dev = 0; dev < GPEX_NUM_IRQS; dev++) {
|
|
int devfn = dev * 0x8;
|
|
|
|
for (pin = 0; pin < GPEX_NUM_IRQS; pin++) {
|
|
int irq_nr = PCIE_IRQ + ((pin + PCI_SLOT(devfn)) % GPEX_NUM_IRQS);
|
|
int i = 0;
|
|
|
|
irq_map[i] = cpu_to_be32(devfn << 8);
|
|
|
|
i += FDT_PCI_ADDR_CELLS;
|
|
irq_map[i] = cpu_to_be32(pin + 1);
|
|
|
|
i += FDT_PCI_INT_CELLS;
|
|
irq_map[i++] = cpu_to_be32(plic_phandle);
|
|
|
|
i += FDT_PLIC_ADDR_CELLS;
|
|
irq_map[i] = cpu_to_be32(irq_nr);
|
|
|
|
irq_map += FDT_INT_MAP_WIDTH;
|
|
}
|
|
}
|
|
|
|
qemu_fdt_setprop(fdt, nodename, "interrupt-map",
|
|
full_irq_map, sizeof(full_irq_map));
|
|
|
|
qemu_fdt_setprop_cells(fdt, nodename, "interrupt-map-mask",
|
|
0x1800, 0, 0, 0x7);
|
|
}
|
|
|
|
static void create_fdt(RISCVVirtState *s, const struct MemmapEntry *memmap,
|
|
uint64_t mem_size, const char *cmdline)
|
|
{
|
|
void *fdt;
|
|
int cpu, i;
|
|
uint32_t *cells;
|
|
char *nodename;
|
|
uint32_t plic_phandle, test_phandle, phandle = 1;
|
|
hwaddr flashsize = virt_memmap[VIRT_FLASH].size / 2;
|
|
hwaddr flashbase = virt_memmap[VIRT_FLASH].base;
|
|
|
|
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", "simple-bus");
|
|
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",
|
|
SIFIVE_CLINT_TIMEBASE_FREQ);
|
|
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++;
|
|
int intc_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);
|
|
#if defined(TARGET_RISCV32)
|
|
qemu_fdt_setprop_string(fdt, nodename, "mmu-type", "riscv,sv32");
|
|
#else
|
|
qemu_fdt_setprop_string(fdt, nodename, "mmu-type", "riscv,sv48");
|
|
#endif
|
|
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_setprop_cell(fdt, nodename, "phandle", cpu_phandle);
|
|
intc_phandle = phandle++;
|
|
qemu_fdt_add_subnode(fdt, intc);
|
|
qemu_fdt_setprop_cell(fdt, intc, "phandle", intc_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);
|
|
}
|
|
|
|
/* Add cpu-topology node */
|
|
qemu_fdt_add_subnode(fdt, "/cpus/cpu-map");
|
|
qemu_fdt_add_subnode(fdt, "/cpus/cpu-map/cluster0");
|
|
for (cpu = s->soc.num_harts - 1; cpu >= 0; cpu--) {
|
|
char *core_nodename = g_strdup_printf("/cpus/cpu-map/cluster0/core%d",
|
|
cpu);
|
|
char *cpu_nodename = g_strdup_printf("/cpus/cpu@%d", cpu);
|
|
uint32_t intc_phandle = qemu_fdt_get_phandle(fdt, cpu_nodename);
|
|
qemu_fdt_add_subnode(fdt, core_nodename);
|
|
qemu_fdt_setprop_cell(fdt, core_nodename, "cpu", intc_phandle);
|
|
g_free(core_nodename);
|
|
g_free(cpu_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, "#address-cells",
|
|
FDT_PLIC_ADDR_CELLS);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells",
|
|
FDT_PLIC_INT_CELLS);
|
|
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_cell(fdt, nodename, "riscv,ndev", VIRTIO_NDEV);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "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_cell(fdt, nodename, "interrupt-parent", plic_phandle);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "interrupts", VIRTIO_IRQ + i);
|
|
g_free(nodename);
|
|
}
|
|
|
|
nodename = g_strdup_printf("/soc/pci@%lx",
|
|
(long) memmap[VIRT_PCIE_ECAM].base);
|
|
qemu_fdt_add_subnode(fdt, nodename);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "#address-cells",
|
|
FDT_PCI_ADDR_CELLS);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells",
|
|
FDT_PCI_INT_CELLS);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "#size-cells", 0x2);
|
|
qemu_fdt_setprop_string(fdt, nodename, "compatible",
|
|
"pci-host-ecam-generic");
|
|
qemu_fdt_setprop_string(fdt, nodename, "device_type", "pci");
|
|
qemu_fdt_setprop_cell(fdt, nodename, "linux,pci-domain", 0);
|
|
qemu_fdt_setprop_cells(fdt, nodename, "bus-range", 0,
|
|
memmap[VIRT_PCIE_ECAM].size /
|
|
PCIE_MMCFG_SIZE_MIN - 1);
|
|
qemu_fdt_setprop(fdt, nodename, "dma-coherent", NULL, 0);
|
|
qemu_fdt_setprop_cells(fdt, nodename, "reg", 0, memmap[VIRT_PCIE_ECAM].base,
|
|
0, memmap[VIRT_PCIE_ECAM].size);
|
|
qemu_fdt_setprop_sized_cells(fdt, nodename, "ranges",
|
|
1, FDT_PCI_RANGE_IOPORT, 2, 0,
|
|
2, memmap[VIRT_PCIE_PIO].base, 2, memmap[VIRT_PCIE_PIO].size,
|
|
1, FDT_PCI_RANGE_MMIO,
|
|
2, memmap[VIRT_PCIE_MMIO].base,
|
|
2, memmap[VIRT_PCIE_MMIO].base, 2, memmap[VIRT_PCIE_MMIO].size);
|
|
create_pcie_irq_map(fdt, nodename, plic_phandle);
|
|
g_free(nodename);
|
|
|
|
test_phandle = phandle++;
|
|
nodename = g_strdup_printf("/test@%lx",
|
|
(long)memmap[VIRT_TEST].base);
|
|
qemu_fdt_add_subnode(fdt, nodename);
|
|
{
|
|
const char compat[] = "sifive,test1\0sifive,test0\0syscon";
|
|
qemu_fdt_setprop(fdt, nodename, "compatible", compat, sizeof(compat));
|
|
}
|
|
qemu_fdt_setprop_cells(fdt, nodename, "reg",
|
|
0x0, memmap[VIRT_TEST].base,
|
|
0x0, memmap[VIRT_TEST].size);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "phandle", test_phandle);
|
|
test_phandle = qemu_fdt_get_phandle(fdt, nodename);
|
|
g_free(nodename);
|
|
|
|
nodename = g_strdup_printf("/reboot");
|
|
qemu_fdt_add_subnode(fdt, nodename);
|
|
qemu_fdt_setprop_string(fdt, nodename, "compatible", "syscon-reboot");
|
|
qemu_fdt_setprop_cell(fdt, nodename, "regmap", test_phandle);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "offset", 0x0);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "value", FINISHER_RESET);
|
|
g_free(nodename);
|
|
|
|
nodename = g_strdup_printf("/poweroff");
|
|
qemu_fdt_add_subnode(fdt, nodename);
|
|
qemu_fdt_setprop_string(fdt, nodename, "compatible", "syscon-poweroff");
|
|
qemu_fdt_setprop_cell(fdt, nodename, "regmap", test_phandle);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "offset", 0x0);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "value", FINISHER_PASS);
|
|
g_free(nodename);
|
|
|
|
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_cell(fdt, nodename, "interrupt-parent", plic_phandle);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "interrupts", UART0_IRQ);
|
|
|
|
qemu_fdt_add_subnode(fdt, "/chosen");
|
|
qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", nodename);
|
|
if (cmdline) {
|
|
qemu_fdt_setprop_string(fdt, "/chosen", "bootargs", cmdline);
|
|
}
|
|
g_free(nodename);
|
|
|
|
nodename = g_strdup_printf("/rtc@%lx",
|
|
(long)memmap[VIRT_RTC].base);
|
|
qemu_fdt_add_subnode(fdt, nodename);
|
|
qemu_fdt_setprop_string(fdt, nodename, "compatible",
|
|
"google,goldfish-rtc");
|
|
qemu_fdt_setprop_cells(fdt, nodename, "reg",
|
|
0x0, memmap[VIRT_RTC].base,
|
|
0x0, memmap[VIRT_RTC].size);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "interrupt-parent", plic_phandle);
|
|
qemu_fdt_setprop_cell(fdt, nodename, "interrupts", RTC_IRQ);
|
|
g_free(nodename);
|
|
|
|
nodename = g_strdup_printf("/flash@%" PRIx64, flashbase);
|
|
qemu_fdt_add_subnode(s->fdt, nodename);
|
|
qemu_fdt_setprop_string(s->fdt, nodename, "compatible", "cfi-flash");
|
|
qemu_fdt_setprop_sized_cells(s->fdt, nodename, "reg",
|
|
2, flashbase, 2, flashsize,
|
|
2, flashbase + flashsize, 2, flashsize);
|
|
qemu_fdt_setprop_cell(s->fdt, nodename, "bank-width", 4);
|
|
g_free(nodename);
|
|
}
|
|
|
|
|
|
static inline DeviceState *gpex_pcie_init(MemoryRegion *sys_mem,
|
|
hwaddr ecam_base, hwaddr ecam_size,
|
|
hwaddr mmio_base, hwaddr mmio_size,
|
|
hwaddr pio_base,
|
|
DeviceState *plic, bool link_up)
|
|
{
|
|
DeviceState *dev;
|
|
MemoryRegion *ecam_alias, *ecam_reg;
|
|
MemoryRegion *mmio_alias, *mmio_reg;
|
|
qemu_irq irq;
|
|
int i;
|
|
|
|
dev = qdev_new(TYPE_GPEX_HOST);
|
|
|
|
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
|
|
|
|
ecam_alias = g_new0(MemoryRegion, 1);
|
|
ecam_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
|
|
memory_region_init_alias(ecam_alias, OBJECT(dev), "pcie-ecam",
|
|
ecam_reg, 0, ecam_size);
|
|
memory_region_add_subregion(get_system_memory(), ecam_base, ecam_alias);
|
|
|
|
mmio_alias = g_new0(MemoryRegion, 1);
|
|
mmio_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1);
|
|
memory_region_init_alias(mmio_alias, OBJECT(dev), "pcie-mmio",
|
|
mmio_reg, mmio_base, mmio_size);
|
|
memory_region_add_subregion(get_system_memory(), mmio_base, mmio_alias);
|
|
|
|
sysbus_mmio_map(SYS_BUS_DEVICE(dev), 2, pio_base);
|
|
|
|
for (i = 0; i < GPEX_NUM_IRQS; i++) {
|
|
irq = qdev_get_gpio_in(plic, PCIE_IRQ + i);
|
|
|
|
sysbus_connect_irq(SYS_BUS_DEVICE(dev), i, irq);
|
|
gpex_set_irq_num(GPEX_HOST(dev), i, PCIE_IRQ + i);
|
|
}
|
|
|
|
return dev;
|
|
}
|
|
|
|
static void virt_machine_init(MachineState *machine)
|
|
{
|
|
const struct MemmapEntry *memmap = virt_memmap;
|
|
RISCVVirtState *s = RISCV_VIRT_MACHINE(machine);
|
|
MemoryRegion *system_memory = get_system_memory();
|
|
MemoryRegion *main_mem = g_new(MemoryRegion, 1);
|
|
MemoryRegion *mask_rom = g_new(MemoryRegion, 1);
|
|
char *plic_hart_config;
|
|
size_t plic_hart_config_len;
|
|
target_ulong start_addr = memmap[VIRT_DRAM].base;
|
|
uint32_t fdt_load_addr;
|
|
int i;
|
|
unsigned int smp_cpus = machine->smp.cpus;
|
|
|
|
/* Initialize SOC */
|
|
object_initialize_child(OBJECT(machine), "soc", &s->soc,
|
|
TYPE_RISCV_HART_ARRAY);
|
|
object_property_set_str(OBJECT(&s->soc), "cpu-type", machine->cpu_type,
|
|
&error_abort);
|
|
object_property_set_int(OBJECT(&s->soc), "num-harts", smp_cpus,
|
|
&error_abort);
|
|
sysbus_realize(SYS_BUS_DEVICE(&s->soc), &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 */
|
|
create_fdt(s, memmap, machine->ram_size, machine->kernel_cmdline);
|
|
|
|
/* boot rom */
|
|
memory_region_init_rom(mask_rom, NULL, "riscv_virt_board.mrom",
|
|
memmap[VIRT_MROM].size, &error_fatal);
|
|
memory_region_add_subregion(system_memory, memmap[VIRT_MROM].base,
|
|
mask_rom);
|
|
|
|
riscv_find_and_load_firmware(machine, BIOS_FILENAME,
|
|
memmap[VIRT_DRAM].base, NULL);
|
|
|
|
if (machine->kernel_filename) {
|
|
uint64_t kernel_entry = riscv_load_kernel(machine->kernel_filename,
|
|
NULL);
|
|
|
|
if (machine->initrd_filename) {
|
|
hwaddr start;
|
|
hwaddr end = riscv_load_initrd(machine->initrd_filename,
|
|
machine->ram_size, kernel_entry,
|
|
&start);
|
|
qemu_fdt_setprop_cell(s->fdt, "/chosen",
|
|
"linux,initrd-start", start);
|
|
qemu_fdt_setprop_cell(s->fdt, "/chosen", "linux,initrd-end",
|
|
end);
|
|
}
|
|
}
|
|
|
|
if (drive_get(IF_PFLASH, 0, 0)) {
|
|
/*
|
|
* Pflash was supplied, let's overwrite the address we jump to after
|
|
* reset to the base of the flash.
|
|
*/
|
|
start_addr = virt_memmap[VIRT_FLASH].base;
|
|
}
|
|
|
|
/* Compute the fdt load address in dram */
|
|
fdt_load_addr = riscv_load_fdt(memmap[VIRT_DRAM].base,
|
|
machine->ram_size, s->fdt);
|
|
/* load the reset vector */
|
|
riscv_setup_rom_reset_vec(start_addr, virt_memmap[VIRT_MROM].base,
|
|
virt_memmap[VIRT_MROM].size,
|
|
fdt_load_addr, s->fdt);
|
|
|
|
/* 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, true);
|
|
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,
|
|
qdev_get_gpio_in(DEVICE(s->plic), VIRTIO_IRQ + i));
|
|
}
|
|
|
|
gpex_pcie_init(system_memory,
|
|
memmap[VIRT_PCIE_ECAM].base,
|
|
memmap[VIRT_PCIE_ECAM].size,
|
|
memmap[VIRT_PCIE_MMIO].base,
|
|
memmap[VIRT_PCIE_MMIO].size,
|
|
memmap[VIRT_PCIE_PIO].base,
|
|
DEVICE(s->plic), true);
|
|
|
|
serial_mm_init(system_memory, memmap[VIRT_UART0].base,
|
|
0, qdev_get_gpio_in(DEVICE(s->plic), UART0_IRQ), 399193,
|
|
serial_hd(0), DEVICE_LITTLE_ENDIAN);
|
|
|
|
sysbus_create_simple("goldfish_rtc", memmap[VIRT_RTC].base,
|
|
qdev_get_gpio_in(DEVICE(s->plic), RTC_IRQ));
|
|
|
|
virt_flash_create(s);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(s->flash); i++) {
|
|
/* Map legacy -drive if=pflash to machine properties */
|
|
pflash_cfi01_legacy_drive(s->flash[i],
|
|
drive_get(IF_PFLASH, 0, i));
|
|
}
|
|
virt_flash_map(s, system_memory);
|
|
|
|
g_free(plic_hart_config);
|
|
}
|
|
|
|
static void virt_machine_instance_init(Object *obj)
|
|
{
|
|
}
|
|
|
|
static void virt_machine_class_init(ObjectClass *oc, void *data)
|
|
{
|
|
MachineClass *mc = MACHINE_CLASS(oc);
|
|
|
|
mc->desc = "RISC-V VirtIO board";
|
|
mc->init = virt_machine_init;
|
|
mc->max_cpus = 8;
|
|
mc->default_cpu_type = VIRT_CPU;
|
|
mc->pci_allow_0_address = true;
|
|
}
|
|
|
|
static const TypeInfo virt_machine_typeinfo = {
|
|
.name = MACHINE_TYPE_NAME("virt"),
|
|
.parent = TYPE_MACHINE,
|
|
.class_init = virt_machine_class_init,
|
|
.instance_init = virt_machine_instance_init,
|
|
.instance_size = sizeof(RISCVVirtState),
|
|
};
|
|
|
|
static void virt_machine_init_register_types(void)
|
|
{
|
|
type_register_static(&virt_machine_typeinfo);
|
|
}
|
|
|
|
type_init(virt_machine_init_register_types)
|