m1n1/src/kboot.c

356 lines
10 KiB
C
Raw Normal View History

/* SPDX-License-Identifier: MIT */
#include "kboot.h"
#include "adt.h"
#include "assert.h"
#include "exception.h"
#include "malloc.h"
#include "memory.h"
#include "pcie.h"
#include "smp.h"
#include "types.h"
#include "usb.h"
#include "utils.h"
#include "xnuboot.h"
#include "libfdt/libfdt.h"
static void *dt = NULL;
static int dt_bufsize = 0;
static char *bootargs = NULL;
static void *initrd_start = NULL;
static size_t initrd_size = 0;
#define DT_ALIGN 16384
#define bail(...) \
do { \
printf(__VA_ARGS__); \
return -1; \
} while (0)
static int dt_set_chosen(void)
{
int node = fdt_path_offset(dt, "/chosen");
if (node < 0)
bail("FDT: /chosen node not found in devtree\n");
if (bootargs) {
if (fdt_setprop(dt, node, "bootargs", bootargs, strlen(bootargs) + 1) < 0)
bail("FDT: couldn't set chosen.bootargs property\n");
printf("FDT: bootargs = '%s'\n", bootargs);
}
if (initrd_start && initrd_size) {
if (fdt_setprop_u64(dt, node, "linux,initrd-start", (u64)initrd_start))
bail("FDT: couldn't set chosen.linux,initrd-start property\n");
u64 end = ((u64)initrd_start) + initrd_size;
if (fdt_setprop_u64(dt, node, "linux,initrd-end", end))
bail("FDT: couldn't set chosen.linux,initrd-end property\n");
if (fdt_add_mem_rsv(dt, (u64)initrd_start, initrd_size))
bail("FDT: couldn't add reservation for the initrd\n");
printf("FDT: initrd at %p size 0x%lx\n", initrd_start, initrd_size);
}
if (cur_boot_args.video.base) {
int fb = fdt_path_offset(dt, "/chosen/framebuffer");
if (fb < 0)
bail("FDT: /chosen node not found in devtree\n");
u64 fb_base = cur_boot_args.video.base;
u64 fb_size = cur_boot_args.video.stride * cur_boot_args.video.height;
u64 fbreg[2] = {cpu_to_fdt64(fb_base), cpu_to_fdt64(fb_size)};
char fbname[32];
sprintf(fbname, "framebuffer@%lx", fb_base);
if (fdt_setprop(dt, fb, "reg", fbreg, sizeof(fbreg)))
bail("FDT: couldn't set framebuffer.reg property\n");
if (fdt_set_name(dt, fb, fbname))
bail("FDT: couldn't set framebuffer name\n");
if (fdt_setprop_u32(dt, fb, "width", cur_boot_args.video.width))
bail("FDT: couldn't set framebuffer width\n");
if (fdt_setprop_u32(dt, fb, "height", cur_boot_args.video.height))
bail("FDT: couldn't set framebuffer height\n");
if (fdt_setprop_u32(dt, fb, "stride", cur_boot_args.video.stride))
bail("FDT: couldn't set framebuffer stride\n");
const char *format = NULL;
switch (cur_boot_args.video.depth & 0xff) {
case 32:
format = "x8r8g8b8";
break;
case 30:
format = "x2r10g10b10";
break;
case 16:
format = "r5g6b5";
break;
default:
printf("FDT: unsupported fb depth %lu, not enabling\n", cur_boot_args.video.depth);
return 0; // Do not error out, but don't set the FB
}
if (fdt_setprop_string(dt, fb, "format", format))
bail("FDT: couldn't set framebuffer format\n");
fdt_delprop(dt, fb, "status"); // may fail if it does not exist
printf("FDT: %s base 0x%lx size 0x%lx\n", fbname, fb_base, fb_size);
// We do not need to reserve the framebuffer, as it will be excluded from the usable RAM
// range already.
}
int anode = adt_path_offset(adt, "/chosen");
if (anode < 0)
bail("ADT: /chosen not found\n");
const uint8_t *random_seed;
u32 seed_length;
random_seed = adt_getprop(adt, anode, "random-seed", &seed_length);
if (random_seed) {
printf("ADT: %d bytes of random seed available\n", seed_length);
if (seed_length >= sizeof(u64)) {
u64 kaslr_seed;
memcpy(&kaslr_seed, random_seed, sizeof(kaslr_seed));
// Ideally we would throw away the kaslr_seed part of random_seed
// and avoid reusing it. However, Linux wants 64 bytes of bootloader
// random seed to consider its CRNG initialized, which is exactly
// how much iBoot gives us. This probably doesn't matter, since
// that entropy is going to get shuffled together and Linux makes
// sure to clear the FDT randomness after using it anyway, but just
// in case let's mix in a few bits from our own KASLR base to make
// kaslr_seed unique.
kaslr_seed ^= (u64)cur_boot_args.virt_base;
if (fdt_setprop_u64(dt, node, "kaslr-seed", kaslr_seed))
bail("FDT: couldn't set kaslr-seed\n");
printf("FDT: KASLR seed initialized\n");
} else {
printf("ADT: not enough random data for kaslr-seed\n");
}
if (seed_length) {
if (fdt_setprop(dt, node, "rng-seed", random_seed, seed_length))
bail("FDT: couldn't set rng-seed\n");
printf("FDT: Passing %d bytes of random seed\n", seed_length);
}
} else {
printf("ADT: no random-seed available!\n");
}
return 0;
}
static int dt_set_memory(void)
{
int anode = adt_path_offset(adt, "/chosen");
if (anode < 0)
bail("ADT: /chosen not found\n");
u64 dram_base, dram_size;
if (ADT_GETPROP(adt, anode, "dram-base", &dram_base) < 0)
bail("ADT: Failed to get dram-base\n");
if (ADT_GETPROP(adt, anode, "dram-size", &dram_size) < 0)
bail("ADT: Failed to get dram-size\n");
// Tell the kernel our usable memory range. We cannot declare all of DRAM, and just reserve the
// bottom and top, because the kernel would still map it (and just not use it), which breaks
// ioremap (e.g. simplefb).
u64 dram_min = cur_boot_args.phys_base;
u64 dram_max = cur_boot_args.phys_base + cur_boot_args.mem_size;
printf("FDT: DRAM at 0x%lx size 0x%lx\n", dram_base, dram_size);
printf("FDT: Usable memory is 0x%lx..0x%lx (0x%lx)\n", dram_min, dram_max, dram_max - dram_min);
u64 memreg[2] = {cpu_to_fdt64(dram_min), cpu_to_fdt64(dram_max - dram_min)};
int node = fdt_path_offset(dt, "/memory");
if (node < 0)
bail("FDT: /memory node not found in devtree\n");
if (fdt_setprop(dt, node, "reg", memreg, sizeof(memreg)))
bail("FDT: couldn't set memory.reg property\n");
return 0;
}
static int dt_set_cpus(void)
{
int cpus = fdt_path_offset(dt, "/cpus");
if (cpus < 0)
bail("FDT: /cpus node not found in devtree\n");
int node, cpu = 0;
fdt_for_each_subnode(node, dt, cpus)
{
const fdt64_t *prop = fdt_getprop(dt, node, "reg", NULL);
if (!prop)
bail("FDT: failed to get reg property of CPU\n");
u64 dt_mpidr = fdt64_ld(prop);
if (dt_mpidr == (mrs(MPIDR_EL1) & 0xFFFFFF))
goto next;
if (!smp_is_alive(cpu)) {
printf("FDT: CPU %d is not alive, disabling...\n", cpu);
if (fdt_setprop_string(dt, node, "status", "disabled"))
bail("FDT: couldn't set status property\n");
goto next;
}
u64 mpidr = smp_get_mpidr(cpu);
if (dt_mpidr != mpidr)
bail("FDT: DT CPU %d MPIDR mismatch: 0x%lx != 0x%lx\n", cpu, dt_mpidr, mpidr);
u64 release_addr = smp_get_release_addr(cpu);
if (fdt_setprop_u64(dt, node, "cpu-release-addr", release_addr))
bail("FDT: couldn't set cpu-release-addr property\n");
printf("FDT: CPU %d MPIDR=0x%lx release-addr=0x%lx\n", cpu, mpidr, release_addr);
next:
cpu++;
}
if ((node < 0) && (node != -FDT_ERR_NOTFOUND)) {
bail("FDT: error iterating through CPUs\n");
}
return 0;
}
static const char *aliases[] = {
"bluetooth0",
"ethernet0",
"wifi0",
};
static int dt_set_mac_addresses(void)
{
int anode = adt_path_offset(adt, "/chosen");
if (anode < 0)
bail("ADT: /chosen not found\n");
for (size_t i = 0; i < sizeof(aliases) / sizeof(*aliases); i++) {
char propname[32];
sprintf(propname, "mac-address-%s", aliases[i]);
uint8_t addr[6];
if (ADT_GETPROP_ARRAY(adt, anode, propname, addr) < 0)
continue;
const char *path = fdt_get_alias(dt, aliases[i]);
if (path == NULL)
continue;
int node = fdt_path_offset(dt, path);
if (node < 0)
continue;
fdt_setprop(dt, node, "local-mac-address", addr, sizeof(addr));
}
return 0;
}
void kboot_set_initrd(void *start, size_t size)
{
initrd_start = start;
initrd_size = size;
}
void kboot_set_bootargs(const char *ba)
{
if (bootargs)
free(bootargs);
if (!ba) {
bootargs = NULL;
return;
}
bootargs = malloc(strlen(ba) + 1);
strcpy(bootargs, ba);
}
int kboot_prepare_dt(void *fdt)
{
if (dt) {
free(dt);
dt = NULL;
}
dt_bufsize = fdt_totalsize(fdt);
assert(dt_bufsize);
dt_bufsize += 64 * 1024; // Add 64K of buffer for modifications
dt = memalign(DT_ALIGN, dt_bufsize);
if (fdt_open_into(fdt, dt, dt_bufsize) < 0)
bail("FDT: fdt_open_into() failed\n");
if (fdt_add_mem_rsv(dt, (u64)dt, dt_bufsize))
bail("FDT: couldn't add reservation for the devtree\n");
if (fdt_add_mem_rsv(dt, (u64)_base, ((u64)_end) - ((u64)_base)))
bail("FDT: couldn't add reservation for m1n1\n");
if (dt_set_chosen())
return -1;
if (dt_set_memory())
return -1;
if (dt_set_cpus())
return -1;
if (dt_set_mac_addresses())
return -1;
if (fdt_pack(dt))
bail("FDT: fdt_pack() failed\n");
printf("FDT prepared at %p\n", dt);
return 0;
}
int kboot_boot(void *kernel)
{
usb_init();
pcie_init();
printf("Preparing to boot kernel at %p with fdt at %p\n", kernel, dt);
next_stage.entry = kernel;
next_stage.args[0] = (u64)dt;
next_stage.args[1] = 0;
next_stage.args[2] = 0;
next_stage.args[3] = 0;
return 0;
}