Files
u-boot/board/emulation/qemu-sbsa/qemu-sbsa.c
Patrick Rudolph 6d722894fd board: emulation: Add QEMU sbsa support
Add support for Arm sbsa [1] v0.3+ that is supported by QEMU [2].

Unlike other Arm based platforms the machine only provides a minimal
FDT that contains number of CPUs, ammount of memory and machine-version.
The boot firmware has to provide ACPI tables to the OS.
Due to this design a full DTB is added here as well that allows U-Boot's
driver to properly function. The DTB is appended at the end of the U-Boot
image and will be merged with the QEMU provided DTB.

In addition provide documentation how to use, enable binman to fabricate both
ROMs that are required to boot and add ACPI tables to make it full compatible
to the EDK2 reference implementation.

The board was tested using Fedora 40 Aarch64 Workstation. It's able
to boot from USB and AHCI or network.

Tested and found working:
- serial
- PCI
- xHCI
- Bochs display
- AHCI
- network using e1000e
- CPU init
- Booting Fedora 40

1: Server Base System Architecture (SBSA)
2: https://www.qemu.org/docs/master/system/arm/sbsa.html

Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Cc: Peter Robinson <pbrobinson@gmail.com>
Cc: Simon Glass <sjg@chromium.org>
Cc: Tom Rini <trini@konsulko.com>
2024-10-27 17:24:13 -06:00

273 lines
6.4 KiB
C

/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (c) 2017 Tuomas Tynkkynen
*/
#include <cpu_func.h>
#include <dm.h>
#include <env.h>
#include <fdtdec.h>
#include <fdt_support.h>
#include <init.h>
#include <log.h>
#include <usb.h>
#include <asm/armv8/mmu.h>
#include "qemu-sbsa.h"
/* Assigned in lowlevel_init.S
* Push the variable into the .data section so that it
* does not get cleared later.
*/
unsigned long __section(".data") fw_dtb_pointer;
static struct mm_region qemu_sbsa_mem_map[] = {
{
/* Secure flash */
.virt = SBSA_SECURE_FLASH_BASE_ADDR,
.phys = SBSA_SECURE_FLASH_BASE_ADDR,
.size = SBSA_SECURE_FLASH_LENGTH,
.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
PTE_BLOCK_INNER_SHARE |
PTE_BLOCK_PXN | PTE_BLOCK_UXN
}, {
/* Flash */
.virt = SBSA_FLASH_BASE_ADDR,
.phys = SBSA_FLASH_BASE_ADDR,
.size = SBSA_FLASH_LENGTH,
.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
PTE_BLOCK_INNER_SHARE
}, {
/* Lowmem peripherals */
.virt = SBSA_PERIPH_BASE_ADDR,
.phys = SBSA_PERIPH_BASE_ADDR,
.size = SBSA_PCIE_MMIO_BASE_ADDR - SBSA_PERIPH_BASE_ADDR,
.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
PTE_BLOCK_NON_SHARE |
PTE_BLOCK_PXN | PTE_BLOCK_UXN
}, {
/* 32-bit address PCIE MMIO space */
.virt = SBSA_PCIE_MMIO_BASE_ADDR,
.phys = SBSA_PCIE_MMIO_BASE_ADDR,
.size = SBSA_PCIE_MMIO_LENGTH,
.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
PTE_BLOCK_NON_SHARE |
PTE_BLOCK_PXN | PTE_BLOCK_UXN
}, {
/* PCI-E ECAM memory area */
.virt = SBSA_PCIE_ECAM_BASE_ADDR,
.phys = SBSA_PCIE_ECAM_BASE_ADDR,
.size = SBSA_PCIE_ECAM_LENGTH,
.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
PTE_BLOCK_NON_SHARE |
PTE_BLOCK_PXN | PTE_BLOCK_UXN
}, {
/* Highmem PCI-E MMIO memory area */
.virt = SBSA_PCIE_MMIO_HIGH_BASE_ADDR,
.phys = SBSA_PCIE_MMIO_HIGH_BASE_ADDR,
.size = SBSA_PCIE_MMIO_HIGH_LENGTH,
.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
PTE_BLOCK_NON_SHARE |
PTE_BLOCK_PXN | PTE_BLOCK_UXN
}, {
/* DRAM */
.virt = SBSA_MEM_BASE_ADDR,
.phys = SBSA_MEM_BASE_ADDR,
.size = 0x800000000000ULL,
.attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) |
PTE_BLOCK_INNER_SHARE
}, {
/* List terminator */
0,
}
};
struct mm_region *mem_map = qemu_sbsa_mem_map;
int board_late_init(void)
{
/* start usb so that usb keyboard can be used as input device */
if (CONFIG_IS_ENABLED(USB_KEYBOARD))
usb_init();
return 0;
}
int board_init(void)
{
return 0;
}
/**
* dtb_dt_qemu - Return the address of the QEMU provided FDT.
*
* @return: Pointer to FDT or NULL on failure
*/
static void *dtb_dt_qemu(void)
{
/* FDT might be at start of DRAM */
if (fdt_magic(SBSA_MEM_BASE_ADDR) == FDT_MAGIC)
return (void *)(u64)SBSA_MEM_BASE_ADDR;
/* When ARM_LINUX_KERNEL_AS_BL33 is enabled in ATF, it's passed in x0 */
if (fw_dtb_pointer >= SBSA_MEM_BASE_ADDR &&
fdt_magic(fw_dtb_pointer) == FDT_MAGIC) {
return (void *)fw_dtb_pointer;
}
return NULL;
}
/*
* QEMU doesn't set compatible on cpus.
* Add them to make sure the U-Boot driver properly bind.
*/
static int fdtdec_fix_cpus(void *fdt_blob)
{
int cpus_offset, off, ret;
u64 mpidr, i = 0;
cpus_offset = fdt_path_offset(fdt_blob, "/cpus");
if (cpus_offset < 0) {
puts("couldn't find /cpus node\n");
return cpus_offset;
}
fdt_for_each_subnode(off, fdt_blob, cpus_offset) {
if (strncmp(fdt_get_name(fdt_blob, off, NULL), "cpu@", 4))
continue;
mpidr = 0;
ret = smc_get_mpidr(i, &mpidr);
if (ret) {
log_warning("Failed to get MPIDR for processor %lld from SMC: %d\n",
i, ret);
mpidr = i;
}
ret = fdt_setprop_string(fdt_blob, off, "compatible", "arm,armv8");
if (ret < 0)
return ret;
ret = fdt_setprop_string(fdt_blob, off, "device_type", "cpu");
if (ret < 0)
return ret;
ret = fdt_setprop_u64(fdt_blob, off, "reg", mpidr);
if (ret < 0)
return ret;
i++;
}
return 0;
}
/*
* Update the GIC node when necessary and add optional ITS when it has a
* non zero base-address.
*/
static int fdtdec_fix_gic(void *fdt)
{
u64 gic_dist_base = SBSA_GIC_DIST_BASE_ADDR;
u64 gic_redist_base = SBSA_GIC_REDIST_BASE_ADDR;
u64 gic_its_base = 0;
int offs, ret;
u64 reg[10];
/* Invoke SMC to get real base-address */
smc_get_gic_dist_base(&gic_dist_base);
smc_get_gic_redist_base(&gic_redist_base);
if ((gic_dist_base != SBSA_GIC_DIST_BASE_ADDR) ||
(gic_redist_base != SBSA_GIC_REDIST_BASE_ADDR)) {
offs = fdt_path_offset(fdt, "/interrupt-controller");
if (offs < 0) {
puts("couldn't find /interrupt-controller node\n");
return offs;
}
reg[0] = cpu_to_fdt64(gic_dist_base);
reg[1] = cpu_to_fdt64((u64)SBSA_GIC_DIST_LENGTH);
reg[2] = cpu_to_fdt64(gic_redist_base);
reg[3] = cpu_to_fdt64((u64)SBSA_GIC_REDIST_LENGTH);
reg[4] = cpu_to_fdt64(0);
reg[5] = cpu_to_fdt64(0);
reg[6] = cpu_to_fdt64(SBSA_GIC_HBASE_ADDR);
reg[7] = cpu_to_fdt64((u64)SBSA_GIC_HBASE_LENGTH);
reg[8] = cpu_to_fdt64(SBSA_GIC_VBASE_ADDR);
reg[9] = cpu_to_fdt64((u64)SBSA_GIC_VBASE_LENGTH);
ret = fdt_setprop_inplace(fdt, offs, "reg", reg, sizeof(reg));
}
smc_get_gic_its_base(&gic_its_base);
if (gic_its_base != 0) {
offs = fdt_path_offset(fdt, "/its");
if (offs < 0)
return offs;
ret = fdt_setprop_string(fdt, offs, "status", "okay");
if (ret < 0)
return ret;
reg[0] = cpu_to_fdt64(gic_its_base);
reg[1] = 0;
ret = fdt_setprop(fdt, offs, "reg", reg, sizeof(u64) * 2);
if (ret < 0)
return ret;
}
return 0;
}
int fdtdec_board_setup(const void *fdt_blob)
{
void *qemu_fdt;
int ret;
/*
* Locate the QEMU provided DTB that contains the CPUs and amount of DRAM.
*/
qemu_fdt = dtb_dt_qemu();
if (!qemu_fdt) {
log_err("QEMU FDT not found\n");
return -ENODEV;
}
ret = fdt_increase_size((void *)fdt_blob, 1024 + fdt_totalsize(qemu_fdt));
if (ret)
return -ENOMEM;
/*
* Merge the QEMU DTB as overlay into the U-Boot provided DTB.
*/
ret = fdt_overlay_apply_node((void *)fdt_blob, 0, qemu_fdt, 0);
if (ret < 0)
log_err("Failed to apply overlay: %d\n", ret);
/* Fix QEMU nodes to make sure U-Boot drivers are properly working */
ret = fdtdec_fix_cpus((void *)fdt_blob);
if (ret < 0)
log_err("Failed to fix CPUs in FDT: %d\n", ret);
ret = fdtdec_fix_gic((void *)fdt_blob);
if (ret < 0)
log_err("Failed to fix INTC in FDT: %d\n", ret);
return 0;
}
int misc_init_r(void)
{
return env_set_hex("fdt_addr", (uintptr_t)gd->fdt_blob);
}
void reset_cpu(void)
{
}
int dram_init(void)
{
return fdtdec_setup_mem_size_base();
}