mirror of
https://gitlab.com/qemu-project/qemu.git
synced 2026-10-28 14:04:39 +08:00
Previously only the Ascalon console UART was enabled, this enables all 5 UARTs on the SoC. Only UART1 is added to the serial interface for now. Signed-off-by: Portia Stephens <portias@oss.tenstorrent.com> Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Message-ID: <20261015002849.1640109-3-portias@oss.tenstorrent.com> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
816 lines
31 KiB
C
816 lines
31 KiB
C
/*
|
|
* Tenstorrent Atlantis RISC-V System on Chip
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*
|
|
* Copyright 2025 Tenstorrent, Joel Stanley <joel@jms.id.au>
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu/cutils.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qemu/guest-random.h"
|
|
#include "qemu/units.h"
|
|
|
|
#include "hw/core/boards.h"
|
|
#include "hw/core/loader.h"
|
|
#include "hw/core/qdev-properties.h"
|
|
#include "hw/core/sysbus.h"
|
|
|
|
#include "target/riscv/cpu.h"
|
|
|
|
#include "hw/riscv/boot.h"
|
|
#include "hw/riscv/fdt-common.h"
|
|
#include "hw/riscv/riscv_hart.h"
|
|
|
|
#include "hw/char/dw8250.h"
|
|
#include "hw/intc/riscv_aclint.h"
|
|
#include "hw/misc/tt_atlantis_prcm.h"
|
|
|
|
#include "system/system.h"
|
|
#include "system/device_tree.h"
|
|
|
|
#include "hw/riscv/tt_atlantis.h"
|
|
|
|
#include "aia.h"
|
|
|
|
#define TT_IRQCHIP_NUM_MSIS 255
|
|
#define TT_IRQCHIP_NUM_SOURCES 128
|
|
#define TT_IRQCHIP_NUM_PRIO_BITS 3
|
|
#define TT_IMSIC_GUESTS 5
|
|
#define TT_IMSIC_STRIDE 0x40000 /* Same stride for M and S */
|
|
#define TT_IMSIC_GUEST_BITS 6
|
|
|
|
/* Stride is fixed by hardware, check it's consistent with guest bits. */
|
|
QEMU_BUILD_BUG_ON(TT_IMSIC_STRIDE != (0x1000 << TT_IMSIC_GUEST_BITS));
|
|
|
|
#define TT_ACLINT_MTIME_SIZE 0x8050
|
|
#define TT_ACLINT_MTIME 0x0
|
|
#define TT_ACLINT_MTIMECMP 0x8000
|
|
#define TT_ACLINT_TIMEBASE_FREQ 1000000000
|
|
|
|
static const MemMapEntry tt_atlantis_memmap[] = {
|
|
/* Keep sorted with :'<,'>!sort -g -k 4 */
|
|
[TT_ATL_DDR_LO] = { 0x00000000, 0x80000000 },
|
|
[TT_ATL_BOOTROM] = { 0x80000000, 0x2000 },
|
|
[TT_ATL_MIMSIC] = { 0xa0000000, 0x200000 },
|
|
[TT_ATL_ACLINT] = { 0xa2180000, 0x10000 },
|
|
[TT_ATL_SIMSIC] = { 0xa4000000, 0x200000 },
|
|
[TT_ATL_MAPLIC] = { 0xcc000000, 0x4000000 },
|
|
[TT_ATL_PRCM_RCPU] = { 0xd0000000, 0x10000 },
|
|
[TT_ATL_I2C0] = { 0xd4040000, 0x10000 },
|
|
[TT_ATL_I2C1] = { 0xd4050000, 0x10000 },
|
|
[TT_ATL_I2C2] = { 0xd4060000, 0x10000 },
|
|
[TT_ATL_I2C3] = { 0xd4070000, 0x10000 },
|
|
[TT_ATL_I2C4] = { 0xd4080000, 0x10000 },
|
|
[TT_ATL_UART0] = { 0xd4100000, 0x10000 },
|
|
[TT_ATL_UART1] = { 0xd4110000, 0x10000 },
|
|
[TT_ATL_UART2] = { 0xd4120000, 0x10000 },
|
|
[TT_ATL_UART3] = { 0xd4130000, 0x10000 },
|
|
[TT_ATL_UART4] = { 0xd4140000, 0x10000 },
|
|
[TT_ATL_PRCM_PCIE] = { 0xd8000000, 0x100 },
|
|
[TT_ATL_PRCM_MM] = { 0xdc000000, 0x1000 },
|
|
[TT_ATL_PRCM_HSIO] = { 0xe00c0000, 0x510 },
|
|
[TT_ATL_SAPLIC] = { 0xe8000000, 0x4000000 },
|
|
[TT_ATL_DDR_HI] = { 0x100000000, 0x1000000000 },
|
|
};
|
|
|
|
static I2CBus *i2c_get_bus(TTAtlantisSoCState *s, unsigned busnr)
|
|
{
|
|
assert(busnr < TT_ATL_NUM_I2C);
|
|
|
|
return s->i2c[busnr].bus;
|
|
}
|
|
|
|
static uint32_t fdt_phandle = 1;
|
|
static uint32_t next_phandle(void)
|
|
{
|
|
return fdt_phandle++;
|
|
}
|
|
|
|
static void create_fdt_memory(void *fdt, TTAtlantisSoCState *s)
|
|
{
|
|
hwaddr ram_size = memory_region_size(s->dram);
|
|
hwaddr size_lo = ram_size;
|
|
hwaddr size_hi = 0;
|
|
|
|
if (size_lo > s->memmap[TT_ATL_DDR_LO].size) {
|
|
size_lo = s->memmap[TT_ATL_DDR_LO].size;
|
|
size_hi = ram_size - size_lo;
|
|
}
|
|
|
|
riscv_create_fdt_socket_memory(fdt, s->memmap[TT_ATL_DDR_LO].base,
|
|
size_lo, 0, false);
|
|
if (size_hi) {
|
|
/*
|
|
* The first part of the HI address is aliased at the LO address
|
|
* so do not include that as usable memory. Is there any way
|
|
* (or good reason) to describe that aliasing 2GB with DT?
|
|
*/
|
|
riscv_create_fdt_socket_memory(fdt,
|
|
s->memmap[TT_ATL_DDR_HI].base + size_lo,
|
|
size_hi, 0, false);
|
|
}
|
|
}
|
|
|
|
static void create_fdt_aclint(void *fdt, TTAtlantisSoCState *s,
|
|
uint32_t *intc_phandles)
|
|
{
|
|
g_autofree char *name = NULL;
|
|
g_autofree uint32_t *aclint_mtimer_cells = NULL;
|
|
uint32_t aclint_cells_size;
|
|
hwaddr addr;
|
|
|
|
aclint_mtimer_cells = g_new0(uint32_t, s->cpus.num_harts * 2);
|
|
|
|
for (int cpu = 0; cpu < s->cpus.num_harts; cpu++) {
|
|
aclint_mtimer_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
|
|
aclint_mtimer_cells[cpu * 2 + 1] = cpu_to_be32(IRQ_M_TIMER);
|
|
}
|
|
aclint_cells_size = s->cpus.num_harts * sizeof(uint32_t) * 2;
|
|
|
|
addr = s->memmap[TT_ATL_ACLINT].base;
|
|
|
|
name = g_strdup_printf("/soc/mtimer@%"HWADDR_PRIX, addr);
|
|
qemu_fdt_add_subnode(fdt, name);
|
|
qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,aclint-mtimer");
|
|
qemu_fdt_setprop_sized_cells(fdt, name, "reg",
|
|
2, addr + TT_ACLINT_MTIME,
|
|
2, 0x1000,
|
|
2, addr + TT_ACLINT_MTIMECMP,
|
|
2, 0x1000);
|
|
qemu_fdt_setprop(fdt, name, "interrupts-extended",
|
|
aclint_mtimer_cells, aclint_cells_size);
|
|
}
|
|
|
|
static void create_fdt_one_imsic(void *fdt, const MemMapEntry *mem, int cpus,
|
|
uint32_t *intc_phandles, uint32_t msi_phandle,
|
|
int irq_line, uint32_t imsic_guest_bits)
|
|
{
|
|
g_autofree char *name = NULL;
|
|
g_autofree uint32_t *imsic_cells = g_new0(uint32_t, cpus * 2);
|
|
|
|
for (int cpu = 0; cpu < cpus; cpu++) {
|
|
imsic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
|
|
imsic_cells[cpu * 2 + 1] = cpu_to_be32(irq_line);
|
|
}
|
|
|
|
name = g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIX, mem->base);
|
|
qemu_fdt_add_subnode(fdt, name);
|
|
qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,imsics");
|
|
|
|
qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", 0);
|
|
qemu_fdt_setprop(fdt, name, "interrupt-controller", NULL, 0);
|
|
qemu_fdt_setprop(fdt, name, "msi-controller", NULL, 0);
|
|
qemu_fdt_setprop(fdt, name, "interrupts-extended",
|
|
imsic_cells, sizeof(uint32_t) * cpus * 2);
|
|
qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
|
|
qemu_fdt_setprop_cell(fdt, name, "riscv,num-ids", TT_IRQCHIP_NUM_MSIS);
|
|
|
|
if (imsic_guest_bits) {
|
|
qemu_fdt_setprop_cell(fdt, name, "riscv,guest-index-bits",
|
|
imsic_guest_bits);
|
|
}
|
|
qemu_fdt_setprop_cell(fdt, name, "phandle", msi_phandle);
|
|
}
|
|
|
|
static void create_fdt_one_aplic(void *fdt,
|
|
const MemMapEntry *mem,
|
|
uint32_t msi_phandle,
|
|
uint32_t *intc_phandles,
|
|
uint32_t aplic_phandle,
|
|
uint32_t aplic_child_phandle,
|
|
int irq_line, int num_harts)
|
|
{
|
|
g_autofree char *name =
|
|
g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIX, mem->base);
|
|
g_autofree uint32_t *aplic_cells = g_new0(uint32_t, num_harts * 2);
|
|
|
|
for (int cpu = 0; cpu < num_harts; cpu++) {
|
|
aplic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
|
|
aplic_cells[cpu * 2 + 1] = cpu_to_be32(irq_line);
|
|
}
|
|
|
|
qemu_fdt_add_subnode(fdt, name);
|
|
qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,aplic");
|
|
qemu_fdt_setprop_cell(fdt, name, "#address-cells", 0);
|
|
qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", 2);
|
|
qemu_fdt_setprop(fdt, name, "interrupt-controller", NULL, 0);
|
|
|
|
qemu_fdt_setprop(fdt, name, "interrupts-extended",
|
|
aplic_cells, num_harts * sizeof(uint32_t) * 2);
|
|
qemu_fdt_setprop_cell(fdt, name, "msi-parent", msi_phandle);
|
|
|
|
qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
|
|
qemu_fdt_setprop_cell(fdt, name, "riscv,num-sources",
|
|
TT_IRQCHIP_NUM_SOURCES);
|
|
|
|
if (aplic_child_phandle) {
|
|
qemu_fdt_setprop_cell(fdt, name, "riscv,children",
|
|
aplic_child_phandle);
|
|
qemu_fdt_setprop_cells(fdt, name, "riscv,delegation",
|
|
aplic_child_phandle, 1, TT_IRQCHIP_NUM_SOURCES);
|
|
}
|
|
|
|
qemu_fdt_setprop_cell(fdt, name, "phandle", aplic_phandle);
|
|
}
|
|
|
|
static void create_fdt_pmu(void *fdt, TTAtlantisSoCState *s)
|
|
{
|
|
char pmu_name[] = "/pmu";
|
|
RISCVCPU *hart = &s->cpus.harts[0];
|
|
|
|
qemu_fdt_add_subnode(fdt, pmu_name);
|
|
qemu_fdt_setprop_string(fdt, pmu_name, "compatible", "riscv,pmu");
|
|
riscv_pmu_generate_fdt_node(fdt, hart->pmu_avail_ctrs, pmu_name);
|
|
}
|
|
|
|
static void create_fdt_cpu(void *fdt, TTAtlantisSoCState *s,
|
|
uint32_t aplic_s_phandle,
|
|
uint32_t imsic_s_phandle)
|
|
{
|
|
g_autofree uint32_t *intc_phandles = g_new0(uint32_t, s->cpus.num_harts);
|
|
int num_harts = s->cpus.num_harts;
|
|
|
|
riscv_fdt_create_cpu_socket_subnode(fdt, TT_ACLINT_TIMEBASE_FREQ);
|
|
|
|
riscv_create_fdt_socket_cpus(fdt, s->cpus.harts, 0, num_harts,
|
|
s->cpus.hartid_base, &fdt_phandle,
|
|
intc_phandles, false, false);
|
|
|
|
create_fdt_aclint(fdt, s, intc_phandles);
|
|
|
|
/* M-level IMSIC node */
|
|
uint32_t msi_m_phandle = next_phandle();
|
|
create_fdt_one_imsic(fdt, &s->memmap[TT_ATL_MIMSIC], num_harts,
|
|
intc_phandles, msi_m_phandle,
|
|
IRQ_M_EXT, TT_IMSIC_GUEST_BITS);
|
|
|
|
/* S-level IMSIC node */
|
|
create_fdt_one_imsic(fdt, &s->memmap[TT_ATL_SIMSIC], num_harts,
|
|
intc_phandles, imsic_s_phandle,
|
|
IRQ_S_EXT, TT_IMSIC_GUEST_BITS);
|
|
|
|
uint32_t aplic_m_phandle = next_phandle();
|
|
|
|
/* M-level APLIC node */
|
|
create_fdt_one_aplic(fdt, &s->memmap[TT_ATL_MAPLIC],
|
|
msi_m_phandle, intc_phandles,
|
|
aplic_m_phandle, aplic_s_phandle,
|
|
IRQ_M_EXT, num_harts);
|
|
|
|
/* S-level APLIC node */
|
|
create_fdt_one_aplic(fdt, &s->memmap[TT_ATL_SAPLIC],
|
|
imsic_s_phandle, intc_phandles,
|
|
aplic_s_phandle, 0,
|
|
IRQ_S_EXT, num_harts);
|
|
}
|
|
|
|
static void create_fdt_uart(void *fdt, const MemMapEntry *mem, int irq,
|
|
int irqchip_phandle, uint32_t clk_parent_phandle,
|
|
uint32_t clk_phandle)
|
|
{
|
|
static const char * const compat[2] = {
|
|
"snps,dw-apb-uart", "ns16550a"
|
|
};
|
|
g_autofree char *name = g_strdup_printf("/soc/serial@%"HWADDR_PRIX,
|
|
mem->base);
|
|
|
|
qemu_fdt_add_subnode(fdt, name);
|
|
qemu_fdt_setprop_string_array(fdt, name, "compatible",
|
|
(char **)&compat, ARRAY_SIZE(compat));
|
|
qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
|
|
qemu_fdt_setprop_cell(fdt, name, "reg-shift", 2);
|
|
qemu_fdt_setprop_cell(fdt, name, "reg-io-width", 4);
|
|
qemu_fdt_setprop_cells(fdt, name, "clocks", clk_parent_phandle,
|
|
clk_phandle);
|
|
qemu_fdt_setprop_cell(fdt, name, "clock-frequency", 3686400);
|
|
qemu_fdt_setprop_cell(fdt, name, "interrupt-parent", irqchip_phandle);
|
|
qemu_fdt_setprop_cells(fdt, name, "interrupts", irq, 0x4);
|
|
|
|
qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", name);
|
|
qemu_fdt_setprop_string(fdt, "/aliases", "serial0", name);
|
|
}
|
|
|
|
static void create_fdt_rng(void *fdt)
|
|
{
|
|
uint8_t rng_seed[32];
|
|
|
|
qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed));
|
|
qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed));
|
|
}
|
|
|
|
static void create_fdt_clk(void *fdt, const char *clock_name,
|
|
uint32_t freq, uint32_t phandle)
|
|
{
|
|
g_autofree char *name = g_strdup_printf("/clocks/%s", clock_name);
|
|
|
|
qemu_fdt_add_path(fdt, name);
|
|
qemu_fdt_setprop_string(fdt, name, "compatible", "fixed-clock");
|
|
qemu_fdt_setprop_string(fdt, name, "clock-output-names", clock_name);
|
|
qemu_fdt_setprop_cell(fdt, name, "#clock-cells", 0);
|
|
qemu_fdt_setprop_cell(fdt, name, "clock-frequency", freq);
|
|
qemu_fdt_setprop_cell(fdt, name, "phandle", phandle);
|
|
}
|
|
|
|
static void create_fdt_i2c(void *fdt, const MemMapEntry *mem, uint32_t irq,
|
|
uint32_t irqchip_phandle,
|
|
uint32_t clk_parent_phandle,
|
|
uint32_t clk_phandle)
|
|
{
|
|
g_autofree char *name = g_strdup_printf("/soc/i2c@%"HWADDR_PRIX, mem->base);
|
|
|
|
qemu_fdt_add_subnode(fdt, name);
|
|
qemu_fdt_setprop_string(fdt, name, "compatible", "snps,designware-i2c");
|
|
qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
|
|
qemu_fdt_setprop_cell(fdt, name, "interrupt-parent", irqchip_phandle);
|
|
qemu_fdt_setprop_cells(fdt, name, "interrupts", irq, 0x4);
|
|
qemu_fdt_setprop_cells(fdt, name, "clocks", clk_parent_phandle,
|
|
clk_phandle);
|
|
qemu_fdt_setprop_cell(fdt, name, "clock-frequency", 100000);
|
|
qemu_fdt_setprop_cell(fdt, name, "#address-cells", 1);
|
|
qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0);
|
|
}
|
|
|
|
static void create_fdt_i2c_device(void *fdt, TTAtlantisSoCState *s, int bus,
|
|
const char *compat, int addr)
|
|
{
|
|
hwaddr base = s->memmap[TT_ATL_I2C0 + bus].base;
|
|
g_autofree char *name = g_strdup_printf("/soc/i2c@%"HWADDR_PRIX"/sensor@%x",
|
|
base, addr);
|
|
|
|
qemu_fdt_add_subnode(fdt, name);
|
|
qemu_fdt_setprop_string(fdt, name, "compatible", compat);
|
|
qemu_fdt_setprop_cell(fdt, name, "reg", addr);
|
|
}
|
|
|
|
static char *create_fdt_prcm(void *fdt, const MemMapEntry *mem,
|
|
const char *prcm_name)
|
|
{
|
|
hwaddr base = mem->base;
|
|
hwaddr size = mem->size;
|
|
char *name = g_strdup_printf("/soc/clock-controller@%" PRIx64,
|
|
base);
|
|
g_autofree char *compatible =
|
|
g_strdup_printf("tenstorrent,atlantis-prcm-%s", prcm_name);
|
|
|
|
qemu_fdt_add_subnode(fdt, name);
|
|
qemu_fdt_setprop_string(fdt, name, "compatible", compatible);
|
|
qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, base, 2, size);
|
|
qemu_fdt_setprop_cell(fdt, name, "#clock-cells", 1);
|
|
qemu_fdt_setprop_cell(fdt, name, "#reset-cells", 1);
|
|
qemu_fdt_setprop_cell(fdt, name, "phandle", next_phandle());
|
|
|
|
return name;
|
|
}
|
|
|
|
static void finalize_fdt(void *fdt, TTAtlantisSoCState *s)
|
|
{
|
|
uint32_t aplic_s_phandle = next_phandle();
|
|
uint32_t imsic_s_phandle = next_phandle();
|
|
uint32_t osc_24m_phandle = next_phandle();
|
|
uint32_t prcm_rcpu_phandle;
|
|
g_autofree char *rcpu_name, *hsio_name, *pcie_name, *mm_name;
|
|
|
|
create_fdt_cpu(fdt, s, aplic_s_phandle, imsic_s_phandle);
|
|
|
|
create_fdt_memory(fdt, s);
|
|
|
|
/*
|
|
* We want to do this, but the Linux aplic driver was broken before v6.16
|
|
*
|
|
* qemu_fdt_setprop_cell(MACHINE(s)->fdt, "/soc", "interrupt-parent",
|
|
* aplic_s_phandle);
|
|
*/
|
|
|
|
create_fdt_clk(fdt, "osc-24m", 24000000, osc_24m_phandle);
|
|
|
|
rcpu_name = create_fdt_prcm(fdt, &s->memmap[TT_ATL_PRCM_RCPU], "rcpu");
|
|
prcm_rcpu_phandle = qemu_fdt_get_phandle(fdt, rcpu_name);
|
|
qemu_fdt_setprop_cells(fdt, rcpu_name, "clocks", osc_24m_phandle);
|
|
qemu_fdt_setprop_cells(fdt, rcpu_name, "assigned-clocks",
|
|
prcm_rcpu_phandle, TT_ATL_CLK_RCPU_ROOT,
|
|
prcm_rcpu_phandle, TT_ATL_CLK_NOCC_CLK);
|
|
qemu_fdt_setprop_cells(fdt, rcpu_name, "assigned-clock-parents",
|
|
prcm_rcpu_phandle, TT_ATL_CLK_RCPU_PLL,
|
|
prcm_rcpu_phandle, TT_ATL_CLK_NOC_PLL);
|
|
|
|
hsio_name = create_fdt_prcm(fdt, &s->memmap[TT_ATL_PRCM_HSIO], "hsio");
|
|
qemu_fdt_setprop_cells(fdt, hsio_name, "clocks", osc_24m_phandle,
|
|
prcm_rcpu_phandle, TT_ATL_CLK_HSIO_PLL);
|
|
|
|
pcie_name = create_fdt_prcm(fdt, &s->memmap[TT_ATL_PRCM_PCIE], "pcie");
|
|
qemu_fdt_setprop_cells(fdt, pcie_name, "clocks", osc_24m_phandle,
|
|
prcm_rcpu_phandle, TT_ATL_CLK_PCIE_PLL);
|
|
|
|
mm_name = create_fdt_prcm(fdt, &s->memmap[TT_ATL_PRCM_MM], "mm");
|
|
qemu_fdt_setprop_cells(fdt, mm_name, "clocks", osc_24m_phandle,
|
|
prcm_rcpu_phandle, TT_ATL_CLK_MM_PLL0,
|
|
prcm_rcpu_phandle, TT_ATL_CLK_MM_PLL1);
|
|
|
|
create_fdt_uart(fdt, &s->memmap[TT_ATL_UART1], TT_ATL_UART1_IRQ,
|
|
aplic_s_phandle, prcm_rcpu_phandle, TT_ATL_CLK_UART1_PCLK);
|
|
|
|
for (int i = 0; i < TT_ATL_NUM_I2C; i++) {
|
|
create_fdt_i2c(fdt,
|
|
&s->memmap[TT_ATL_I2C0 + i],
|
|
TT_ATL_I2C0_IRQ + i,
|
|
aplic_s_phandle, prcm_rcpu_phandle,
|
|
TT_ATL_CLK_I2C0_PCLK + i);
|
|
}
|
|
|
|
/* I2C peripherals: qemu specific */
|
|
create_fdt_i2c_device(fdt, s, 0, "dallas,ds1338", 0x6f);
|
|
create_fdt_i2c_device(fdt, s, 4, "ti,tmp105", 0x48);
|
|
}
|
|
|
|
static void create_fdt(TTAtlantisState *ams)
|
|
{
|
|
MachineState *ms = MACHINE(ams);
|
|
int fdt_size = 0;
|
|
|
|
ms->fdt = riscv_create_board_device_tree("Tenstorrent Atlantis RISC-V Machine",
|
|
"tenstorrent,atlantis", &fdt_size);
|
|
|
|
qemu_fdt_add_subnode(ms->fdt, "/chosen");
|
|
|
|
create_fdt_rng(ms->fdt);
|
|
|
|
qemu_fdt_add_subnode(ms->fdt, "/aliases");
|
|
|
|
create_fdt_pmu(ms->fdt, &ams->soc);
|
|
}
|
|
|
|
static void load_fdt(TTAtlantisState *ams)
|
|
{
|
|
MachineState *ms = MACHINE(ams);
|
|
char **node_path;
|
|
Error *err = NULL;
|
|
int fdt_size = 0;
|
|
|
|
ms->fdt = load_device_tree(ms->dtb, &fdt_size);
|
|
if (!ms->fdt) {
|
|
error_report("load_device_tree() failed");
|
|
exit(1);
|
|
}
|
|
|
|
qemu_fdt_add_path(ms->fdt, "/chosen");
|
|
|
|
/* Clear memory nodes and update with the specified RAM size */
|
|
node_path = qemu_fdt_node_unit_path(ms->fdt, "memory", &err);
|
|
if (err) {
|
|
warn_report_err(err);
|
|
} else {
|
|
for (int i = 0; node_path[i]; i++) {
|
|
warn_report("Replacing device tree %s with the requested RAM size",
|
|
node_path[i]);
|
|
qemu_fdt_nop_node(ms->fdt, node_path[i]);
|
|
}
|
|
g_strfreev(node_path);
|
|
}
|
|
|
|
create_fdt_memory(ms->fdt, &ams->soc);
|
|
}
|
|
|
|
static void tt_atlantis_machine_done(Notifier *n, void *data)
|
|
{
|
|
TTAtlantisState *ams = container_of(n, TTAtlantisState, machine_done);
|
|
TTAtlantisSoCState *s = &ams->soc;
|
|
MachineState *machine = MACHINE(ams);
|
|
hwaddr start_addr = s->memmap[TT_ATL_DDR_LO].base;
|
|
hwaddr mem_size;
|
|
target_ulong firmware_end_addr, kernel_start_addr;
|
|
const char *firmware_name = riscv_default_firmware_name(&s->cpus);
|
|
uint64_t fdt_load_addr;
|
|
uint64_t kernel_entry;
|
|
RISCVBootInfo boot_info;
|
|
|
|
/*
|
|
* A user provided dtb must include everything, including
|
|
* dynamic sysbus devices. Our FDT needs to be finalized.
|
|
*/
|
|
if (machine->dtb == NULL) {
|
|
finalize_fdt(machine->fdt, s);
|
|
}
|
|
|
|
mem_size = machine->ram_size;
|
|
if (mem_size > s->memmap[TT_ATL_DDR_LO].size) {
|
|
mem_size = s->memmap[TT_ATL_DDR_LO].size;
|
|
}
|
|
riscv_boot_info_init_discontig_mem(&boot_info, &s->cpus,
|
|
s->memmap[TT_ATL_DDR_LO].base,
|
|
mem_size);
|
|
|
|
firmware_end_addr = riscv_find_and_load_firmware(machine, &boot_info,
|
|
firmware_name,
|
|
&start_addr, NULL);
|
|
|
|
kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info,
|
|
firmware_end_addr);
|
|
if (machine->kernel_filename) {
|
|
riscv_load_kernel(machine, &boot_info, kernel_start_addr,
|
|
true, NULL);
|
|
kernel_entry = boot_info.image_low_addr;
|
|
} else {
|
|
/* If we aren't loading a payload, OpenSBI thinks we are trying to boot
|
|
* address 0, which fails `sbi_domain_check_addr()` as that is where
|
|
* OpenSBI is running. Instead point OpenSBI to the end of the region
|
|
* where it was loaded, which avoids the early hang, allowing the
|
|
* system to proceed with the OpenSBI boot output.
|
|
*/
|
|
kernel_entry = kernel_start_addr;
|
|
}
|
|
|
|
fdt_load_addr = riscv_compute_fdt_addr(s->memmap[TT_ATL_DDR_LO].base,
|
|
s->memmap[TT_ATL_DDR_LO].size,
|
|
machine, &boot_info);
|
|
riscv_load_fdt(fdt_load_addr, machine->fdt);
|
|
|
|
/* load the reset vector */
|
|
riscv_setup_rom_reset_vec(machine, &s->cpus, start_addr,
|
|
s->memmap[TT_ATL_BOOTROM].base,
|
|
s->memmap[TT_ATL_BOOTROM].size,
|
|
kernel_entry,
|
|
fdt_load_addr);
|
|
}
|
|
|
|
static inline int tt_atlantis_uart_index(int uart_dev)
|
|
{
|
|
return uart_dev - TT_ATL_UART0;
|
|
}
|
|
|
|
static void tt_atlantis_soc_init(Object *obj)
|
|
{
|
|
TTAtlantisSoCState *s = TT_ATLANTIS_SOC(obj);
|
|
|
|
object_initialize_child(obj, "cpus", &s->cpus, TYPE_RISCV_HART_ARRAY);
|
|
|
|
for (int i = 0; i < TT_ATL_NUM_UARTS; i++) {
|
|
object_initialize_child(obj, "uart[*]", &s->uart[i],
|
|
TYPE_DW8250);
|
|
}
|
|
|
|
object_initialize_child(obj, "prcm-rcpu",
|
|
&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_RCPU],
|
|
TYPE_TT_ATLANTIS_PRCM_RCPU);
|
|
object_initialize_child(obj, "prcm-hsio",
|
|
&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_HSIO],
|
|
TYPE_TT_ATLANTIS_PRCM_HSIO);
|
|
object_initialize_child(obj, "prcm-pcie",
|
|
&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_PCIE],
|
|
TYPE_TT_ATLANTIS_PRCM_PCIE);
|
|
object_initialize_child(obj, "prcm-mm",
|
|
&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_MM],
|
|
TYPE_TT_ATLANTIS_PRCM_MM);
|
|
|
|
for (int i = 0; i < TT_ATL_NUM_I2C; i++) {
|
|
object_initialize_child(obj, "i2c[*]", &s->i2c[i],
|
|
TYPE_DESIGNWARE_I2C);
|
|
}
|
|
}
|
|
|
|
static void tt_atlantis_soc_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
TTAtlantisSoCState *s = TT_ATLANTIS_SOC(dev);
|
|
ram_addr_t lo_ram_size, ram_size;
|
|
int hart_count = s->num_harts;
|
|
|
|
if (!s->memory) {
|
|
error_setg(errp, "'memory' link is not set");
|
|
return;
|
|
}
|
|
if (!s->dram) {
|
|
error_setg(errp, "'dram' link is not set");
|
|
return;
|
|
}
|
|
ram_size = memory_region_size(s->dram);
|
|
|
|
s->memmap = tt_atlantis_memmap;
|
|
|
|
/* CPUs */
|
|
object_property_set_str(OBJECT(&s->cpus), "cpu-type", s->cpu_type,
|
|
&error_abort);
|
|
object_property_set_int(OBJECT(&s->cpus), "hartid-base", 0,
|
|
&error_abort);
|
|
object_property_set_int(OBJECT(&s->cpus), "num-harts", hart_count,
|
|
&error_abort);
|
|
object_property_set_int(OBJECT(&s->cpus), "resetvec",
|
|
s->memmap[TT_ATL_BOOTROM].base,
|
|
&error_abort);
|
|
object_property_set_link(OBJECT(&s->cpus), "memory", OBJECT(s->memory),
|
|
&error_abort);
|
|
if (!sysbus_realize(SYS_BUS_DEVICE(&s->cpus), errp)) {
|
|
return;
|
|
}
|
|
|
|
s->irqchip = riscv_create_aia(s->memory,
|
|
true, TT_IMSIC_GUESTS,
|
|
TT_IMSIC_STRIDE,
|
|
TT_IMSIC_STRIDE,
|
|
TT_IRQCHIP_NUM_SOURCES,
|
|
&s->memmap[TT_ATL_MAPLIC],
|
|
&s->memmap[TT_ATL_SAPLIC],
|
|
&s->memmap[TT_ATL_MIMSIC],
|
|
&s->memmap[TT_ATL_SIMSIC],
|
|
0, 0, hart_count,
|
|
TT_IRQCHIP_NUM_MSIS,
|
|
TT_IRQCHIP_NUM_PRIO_BITS);
|
|
|
|
riscv_aclint_mtimer_create(s->memory,
|
|
s->memmap[TT_ATL_ACLINT].base,
|
|
TT_ACLINT_MTIME_SIZE,
|
|
0, hart_count,
|
|
TT_ACLINT_MTIMECMP,
|
|
TT_ACLINT_MTIME,
|
|
TT_ACLINT_TIMEBASE_FREQ, true);
|
|
|
|
/*
|
|
* DDR
|
|
*
|
|
* The high address is where RAM lives. It is always present and may be
|
|
* up to 64GB. The low address is an alias of the first 2GB of that RAM.
|
|
*/
|
|
if (ram_size > s->memmap[TT_ATL_DDR_HI].size) {
|
|
g_autofree char *sz = size_to_str(s->memmap[TT_ATL_DDR_HI].size);
|
|
error_setg(errp, "RAM size is too large, maximum is %s", sz);
|
|
return;
|
|
}
|
|
|
|
memory_region_init_alias(&s->ram_hi, OBJECT(s), "ram.high", s->dram,
|
|
0, ram_size);
|
|
memory_region_add_subregion(s->memory,
|
|
s->memmap[TT_ATL_DDR_HI].base, &s->ram_hi);
|
|
|
|
lo_ram_size = MIN(ram_size, s->memmap[TT_ATL_DDR_LO].size);
|
|
memory_region_init_alias(&s->ram_lo, OBJECT(s), "ram.low", s->dram,
|
|
0, lo_ram_size);
|
|
memory_region_add_subregion(s->memory,
|
|
s->memmap[TT_ATL_DDR_LO].base, &s->ram_lo);
|
|
|
|
/* Boot ROM */
|
|
if (!memory_region_init_rom(&s->bootrom, OBJECT(s), "tt-atlantis.bootrom",
|
|
s->memmap[TT_ATL_BOOTROM].size, errp)) {
|
|
return;
|
|
}
|
|
memory_region_add_subregion(s->memory, s->memmap[TT_ATL_BOOTROM].base,
|
|
&s->bootrom);
|
|
|
|
/* 4 PRCM blocks */
|
|
if (!sysbus_realize(SYS_BUS_DEVICE(&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_RCPU]),
|
|
errp)) {
|
|
return;
|
|
}
|
|
memory_region_add_subregion(
|
|
s->memory, s->memmap[TT_ATL_PRCM_RCPU].base,
|
|
sysbus_mmio_get_region(
|
|
SYS_BUS_DEVICE(&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_RCPU]), 0));
|
|
|
|
if (!sysbus_realize(SYS_BUS_DEVICE(&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_HSIO]),
|
|
errp)) {
|
|
return;
|
|
}
|
|
memory_region_add_subregion(
|
|
s->memory, s->memmap[TT_ATL_PRCM_HSIO].base,
|
|
sysbus_mmio_get_region(
|
|
SYS_BUS_DEVICE(&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_HSIO]), 0));
|
|
|
|
if (!sysbus_realize(SYS_BUS_DEVICE(&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_PCIE]),
|
|
errp)) {
|
|
return;
|
|
}
|
|
memory_region_add_subregion(
|
|
s->memory, s->memmap[TT_ATL_PRCM_PCIE].base,
|
|
sysbus_mmio_get_region(
|
|
SYS_BUS_DEVICE(&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_PCIE]), 0));
|
|
|
|
if (!sysbus_realize(SYS_BUS_DEVICE(&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_MM]),
|
|
errp)) {
|
|
return;
|
|
}
|
|
memory_region_add_subregion(
|
|
s->memory, s->memmap[TT_ATL_PRCM_MM].base,
|
|
sysbus_mmio_get_region(
|
|
SYS_BUS_DEVICE(&s->prcm[TT_ATLANTIS_PRCM_DOMAIN_MM]), 0));
|
|
|
|
/* UART1, the soc console (UART0 is for the boot microcontroller) */
|
|
qdev_prop_set_chr(DEVICE(&s->uart[tt_atlantis_uart_index(TT_ATL_UART1)]),
|
|
"chardev", serial_hd(0));
|
|
|
|
for (int i = 0; i < TT_ATL_NUM_UARTS; i++) {
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(&s->uart[i]);
|
|
if (!sysbus_realize(sbd, errp)) {
|
|
return;
|
|
}
|
|
|
|
memory_region_add_subregion(s->memory,
|
|
s->memmap[TT_ATL_UART0 + i].base,
|
|
sysbus_mmio_get_region(sbd, 0));
|
|
sysbus_connect_irq(sbd, 0,
|
|
qdev_get_gpio_in(s->irqchip, TT_ATL_UART0_IRQ + i));
|
|
}
|
|
|
|
/* I2C */
|
|
for (int i = 0; i < TT_ATL_NUM_I2C; i++) {
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(&s->i2c[i]);
|
|
|
|
if (!sysbus_realize(sbd, errp)) {
|
|
return;
|
|
}
|
|
memory_region_add_subregion(s->memory,
|
|
s->memmap[TT_ATL_I2C0 + i].base,
|
|
sysbus_mmio_get_region(sbd, 0));
|
|
sysbus_connect_irq(sbd, 0,
|
|
qdev_get_gpio_in(s->irqchip, TT_ATL_I2C0_IRQ + i));
|
|
}
|
|
}
|
|
|
|
static const Property tt_atlantis_soc_props[] = {
|
|
DEFINE_PROP_STRING("cpu-type", TTAtlantisSoCState, cpu_type),
|
|
DEFINE_PROP_UINT32("num-harts", TTAtlantisSoCState, num_harts, 8),
|
|
DEFINE_PROP_LINK("memory", TTAtlantisSoCState, memory,
|
|
TYPE_MEMORY_REGION, MemoryRegion *),
|
|
DEFINE_PROP_LINK("dram", TTAtlantisSoCState, dram,
|
|
TYPE_MEMORY_REGION, MemoryRegion *),
|
|
};
|
|
|
|
static void tt_atlantis_soc_class_init(ObjectClass *oc, const void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(oc);
|
|
|
|
dc->realize = tt_atlantis_soc_realize;
|
|
device_class_set_props(dc, tt_atlantis_soc_props);
|
|
/* The SoC can only be instantiated from the machine */
|
|
dc->user_creatable = false;
|
|
}
|
|
|
|
static void tt_atlantis_machine_init(MachineState *machine)
|
|
{
|
|
TTAtlantisState *ams = TT_ATLANTIS_MACHINE(machine);
|
|
TTAtlantisSoCState *s = &ams->soc;
|
|
|
|
memory_region_init(&ams->soc_memory, OBJECT(machine),
|
|
"tt-atlantis.soc-memory", UINT64_MAX);
|
|
memory_region_add_subregion(get_system_memory(), 0, &ams->soc_memory);
|
|
|
|
object_initialize_child(OBJECT(machine), "soc", &ams->soc,
|
|
TYPE_TT_ATLANTIS_SOC);
|
|
object_property_set_str(OBJECT(&ams->soc), "cpu-type", machine->cpu_type,
|
|
&error_abort);
|
|
object_property_set_int(OBJECT(&ams->soc), "num-harts", machine->smp.cpus,
|
|
&error_abort);
|
|
|
|
object_property_set_link(OBJECT(&ams->soc), "memory",
|
|
OBJECT(&ams->soc_memory), &error_abort);
|
|
object_property_set_link(OBJECT(&ams->soc), "dram", OBJECT(machine->ram),
|
|
&error_abort);
|
|
|
|
qdev_realize(DEVICE(&ams->soc), NULL, &error_fatal);
|
|
|
|
/* I2C peripherals: qemu specific */
|
|
i2c_slave_create_simple(i2c_get_bus(s, 0), "ds1338", 0x6f);
|
|
i2c_slave_create_simple(i2c_get_bus(s, 4), "tmp105", 0x48);
|
|
|
|
/* Load or create device tree */
|
|
if (machine->dtb) {
|
|
load_fdt(ams);
|
|
} else {
|
|
create_fdt(ams);
|
|
}
|
|
|
|
ams->machine_done.notify = tt_atlantis_machine_done;
|
|
qemu_add_machine_init_done_notifier(&ams->machine_done);
|
|
}
|
|
|
|
static void tt_atlantis_machine_class_init(ObjectClass *oc, const void *data)
|
|
{
|
|
MachineClass *mc = MACHINE_CLASS(oc);
|
|
|
|
mc->desc = "Tenstorrent Atlantis RISC-V SoC (Experimental)";
|
|
mc->init = tt_atlantis_machine_init;
|
|
mc->max_cpus = 8;
|
|
mc->default_cpus = 8;
|
|
mc->default_ram_size = 4 * GiB;
|
|
mc->default_cpu_type = TYPE_RISCV_CPU_TT_ASCALON;
|
|
mc->block_default_type = IF_VIRTIO;
|
|
mc->no_cdrom = 1;
|
|
mc->default_ram_id = "tt_atlantis.ram";
|
|
}
|
|
|
|
static const TypeInfo tt_atlantis_types[] = {
|
|
{
|
|
.name = TYPE_TT_ATLANTIS_SOC,
|
|
.parent = TYPE_DEVICE,
|
|
.instance_size = sizeof(TTAtlantisSoCState),
|
|
.instance_init = tt_atlantis_soc_init,
|
|
.class_init = tt_atlantis_soc_class_init,
|
|
}, {
|
|
.name = MACHINE_TYPE_NAME("tt-atlantis"),
|
|
.parent = TYPE_MACHINE,
|
|
.class_init = tt_atlantis_machine_class_init,
|
|
.instance_size = sizeof(TTAtlantisState),
|
|
},
|
|
};
|
|
|
|
DEFINE_TYPES(tt_atlantis_types)
|