Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

Commit

Permalink
Merge branch 'uboot'
Browse files Browse the repository at this point in the history
  • Loading branch information
kivikakk committed Mar 2, 2021
2 parents c8ecf01 + 992d7df commit 867b660
Show file tree
Hide file tree
Showing 31 changed files with 1,581 additions and 56 deletions.
50 changes: 24 additions & 26 deletions Makefile
@@ -1,11 +1,20 @@
.PHONY: qemu mk-ovmf-vars mk-disk
.PHONY: qemu

QEMU_ACCEL := tcg
ifeq ($OS),Windows NT)
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
QEMU_ACCEL := hvf
endif
endif

QEMU_CMD = qemu-system-aarch64 \
-accel hvf \
-dtb dtb/qemu.dtb \
-accel $(QEMU_ACCEL) \
-m 512 \
-cpu cortex-a53 -M virt,highmem=off \
-drive file=/opt/homebrew/share/qemu/edk2-aarch64-code.fd,if=pflash,format=raw,readonly=on \
-drive file=ovmf_vars.fd,if=pflash,format=raw \
-bios roms/u-boot-arm64-ramfb.bin \
-serial stdio \
-drive file=fat:rw:target/disk,format=raw \
-device virtio-net-device,netdev=net0 \
Expand All @@ -17,25 +26,21 @@ QEMU_CMD = qemu-system-aarch64 \
-device usb-mouse \
-usb \

tftp: dainboot/zig-cache/bin/BOOTAA64.rockpro64.efi os/zig-cache/bin/dainkrnl.rockpro64
tftp: dainboot/zig-cache/bin/BOOTAA64.rockpro64.efi dainkrnl/zig-cache/bin/dainkrnl.rockpro64
tools/update-tftp

qemu: ovmf_vars.fd target/disk/EFI/BOOT/BOOTAA64.efi target/disk/dainkrnl target/disk/dtb
qemu: target/disk/EFI/BOOT/BOOTAA64.efi target/disk/dainkrnl
$(QEMU_CMD) -s $$EXTRA_ARGS

dtb/qemu.dtb:
$(QEMU_CMD) -machine dumpdtb=$@
dtc $@ -o $@

OS_FILES=$(shell find os -name zig-cache -prune -o -type f) $(shell find common -type f)
os/zig-cache/bin/dainkrnl.%: $(OS_FILES)
cd os && zig build -Dboard=$*

target/disk/dainkrnl: os/zig-cache/bin/dainkrnl.qemu
mkdir -p $(@D)
cp $< $@
OS_FILES=$(shell find dainkrnl -name zig-cache -prune -o -type f) $(shell find common -type f)
dainkrnl/zig-cache/bin/dainkrnl.%: $(OS_FILES)
cd dainkrnl && zig build -Dboard=$*

target/disk/dtb: dtb/qemu.dtb
target/disk/dainkrnl: dainkrnl/zig-cache/bin/dainkrnl.qemu
mkdir -p $(@D)
cp $< $@

Expand All @@ -47,22 +52,15 @@ target/disk/EFI/BOOT/BOOTAA64.efi: dainboot/zig-cache/bin/BOOTAA64.qemu.efi
mkdir -p $(@D)
cp $< $@

ovmf_vars.fd:
dd if=/dev/zero conv=sync bs=1048576 count=64 of=ovmf_vars.fd

CI_EDK2=/usr/local/share/qemu/edk2-aarch64-code.fd
CI_QEMU_ACCEL=tcg

ci: dainboot/zig-cache/bin/BOOTAA64.qemu.efi \
dainboot/zig-cache/bin/BOOTAA64.rockpro64.efi \
os/zig-cache/bin/dainkrnl.qemu \
os/zig-cache/bin/dainkrnl.rockpro64 \
ovmf_vars.fd \
target/disk/dainkrnl target/disk/dtb target/disk/EFI/BOOT/BOOTAA64.efi
env CI_EDK2=$(CI_EDK2) CI_QEMU_ACCEL="$(CI_QEMU_ACCEL)" tools/ci-expect
dainkrnl/zig-cache/bin/dainkrnl.qemu \
dainkrnl/zig-cache/bin/dainkrnl.rockpro64 \
target/disk/dainkrnl target/disk/EFI/BOOT/BOOTAA64.efi
tools/ci-expect

clean:
-rm -rf dtb/zig-cache os/zig-cache dainboot/zig-cache target
-rm -rf dtb/zig-cache dainkrnl/zig-cache dainboot/zig-cache target

%.dts:
dtc -I dtb -O dts $*
44 changes: 35 additions & 9 deletions README.md
Expand Up @@ -2,26 +2,46 @@

An ARMv8-A operating system, plus a UEFI bootloader, all written in Zig. Currently targetting and testing on:

- QEMU (using HVF acceleration on macOS), with EDK2
- ROCKPro64, with U-Boot ([patch required](https://patchwork.ozlabs.org/project/uboot/patch/20210209062150.mmshhxissljf6fak@talia.n4wrvuuuhszuhem3na2pm5saea.px.internal.cloudapp.net/))
- QEMU (using HVF acceleration on macOS), with U-Boot
- The U-Boot build is included in the repository, and is based on
[patch series adding QFW and QEMU ramfb support on Arm](https://git.src.kameliya.ee/~kameliya/u-boot/log/qfw-ramfb).
I'm hoping to land this in the coming weeks.
- ROCKPro64, with U-Boot
- A mainline build is okay, but it must contain this
[EFI loader fix](https://source.denx.de/u-boot/u-boot/-/commit/9d30a941cce5ed055da18398f4deba18830d00d6).
At time of writing it has not been included in any release.

There's a little [dev blog](https://github.com/kivikakk/daintree/discussions/1) I hope to maintain as I go.
There's a little [dev blog](https://github.com/kivikakk/daintree/discussions/1)
I hope to maintain as I go. See also [my personal blog](https://kivikakk.ee):

- 2021-02-28: [Knowing when to look past your code](https://kivikakk.ee/2021/02/28/loader/)
- 2021-02-13: [DTB parser implementing notes](https://kivikakk.ee/2021/02/13/dtb-parser-implementing-notes/)

## dainboot

A gentle introduction to Zig's UEFI support. Boots like this:

- Checks loaded image options.
- You can pass `kernel 0x12345678 0x1234` to give it the location of the kernel already loaded in RAM. Useful for TFTP boot, which itself is handy for faster development cycles on bare metal.
- You may also pass `dtb 0x12345678 0x1234` to give information about a DTB/FDT (device tree blob/flattened device tree) already in memory.
- Separate successive options with spaces, i.e. `kernel <addr> <len> dtb <addr> <len>`.
- If kernel or DTB (or both) were not loaded from memory, scans filesystems the UEFI system knows about, looking in the root directories for files named `dainkrnl` and `dtb`.
- Picks the biggest unused slab of conventional memory and places the kernel there.
- You can pass `kernel 0x12345678 0x1234` to give it the location of the
kernel already loaded in RAM. Useful for TFTP boot, which itself is handy
for faster development cycles on bare metal.
- You may also pass `dtb 0x12345678 0x1234` to give information about a
DTB/FDT (device tree blob/flattened device tree) already in memory.
- Separate successive options with spaces, i.e. `kernel <addr> <len> dtb
<addr> <len>`.
- If DTB wasn't loaded from memory, checks to see if one was passed in via
UEFI.
- If kernel or DTB (or both) were not loaded from memory, scans filesystems the
UEFI system knows about, looking in the root directories for files named
`dainkrnl` and `dtb`.
- Picks the biggest unused slab of conventional memory and places the kernel
there.
- Clears data and instruction caches for loaded memory.
- Parses the DTB and attempts to locate the serial UART port.
- Exits UEFI boot services.
- If necessary, disables a whole lot of traps and goes to EL1.
- Jumps to the kernel, passing the memory map, UART port, and framebuffer prepared by UEFI.
- Jumps to the kernel, passing the memory map, UART port, and framebuffer
prepared by UEFI.

| qemu | rockpro64 |
| :----------------------------: | :---------------------------------: |
Expand All @@ -38,3 +58,9 @@ Right now, this just sets up the MMU and implements a small console.
## license

MIT, per [Zig](https://github.com/ziglang/zig).

The [`roms/`](roms/) directory contains a build of
[U-Boot](http://www.denx.de/wiki/U-Boot/WebHome), (C) Wolfgang Denk and
licensed under GPL 2. See [U-Boot's Licensing page](https://www.denx.de/wiki/U-Boot/Licensing)
for details. The source can be found at
<https://git.src.kameliya.ee/~kameliya/u-boot/log/qfw-ramfb>.
14 changes: 13 additions & 1 deletion dainboot/src/dainboot.zig
Expand Up @@ -30,7 +30,9 @@ pub fn main() void {
load_context.handleOptions(options);
}

load_context.searchEfiFdt();
if (load_context.dtb == null) {
load_context.searchEfiFdt();
}

if (load_context.dtb == null or load_context.dainkrnl == null) {
load_context.searchFileSystems();
Expand Down Expand Up @@ -417,6 +419,16 @@ fn exitBootServices(dainkrnl: []const u8, dtb: []const u8) noreturn {

check("exitBootServices", boot_services.exitBootServices(uefi.handle, memory_map_key));

if (fb) |base| {
var x: usize = 0;
while (x < 16) : (x += 1) {
var y: usize = 0;
while (y < 16) : (y += 1) {
base[y * fb_horiz + x] = 0x0000ff00;
}
}
}

// Check for EL2: we get
// and pass to DAINKRNL.
asm volatile (
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
41 changes: 31 additions & 10 deletions os/src/entry.zig → dainkrnl/src/entry.zig
Expand Up @@ -18,7 +18,8 @@ comptime {
var TTBR0_IDENTITY: *[INDEX_SIZE]u64 = undefined;
var TTBR1_L1: *[INDEX_SIZE]u64 = undefined;
var TTBR1_L2: *[INDEX_SIZE]u64 = undefined;
var TTBR1_L3: *[INDEX_SIZE]u64 = undefined;
var TTBR1_L3_1: *[INDEX_SIZE]u64 = undefined;
var TTBR1_L3_2: *[INDEX_SIZE]u64 = undefined;

/// dainboot passes control here. MMU is **off**. We are in EL1.
pub export fn daintree_mmu_start(entry_data: *dcommon.EntryData) noreturn {
Expand Down Expand Up @@ -93,7 +94,8 @@ pub export fn daintree_mmu_start(entry_data: *dcommon.EntryData) noreturn {
TTBR0_IDENTITY = @intToPtr(*[INDEX_SIZE]u64, daintree_end + PAGE_SIZE * 0);
TTBR1_L1 = @intToPtr(*[INDEX_SIZE]u64, daintree_end + PAGE_SIZE * 1);
TTBR1_L2 = @intToPtr(*[INDEX_SIZE]u64, daintree_end + PAGE_SIZE * 2);
TTBR1_L3 = @intToPtr(*[INDEX_SIZE]u64, daintree_end + PAGE_SIZE * 3);
TTBR1_L3_1 = @intToPtr(*[INDEX_SIZE]u64, daintree_end + PAGE_SIZE * 3);
TTBR1_L3_2 = @intToPtr(*[INDEX_SIZE]u64, daintree_end + PAGE_SIZE * 4);

const ttbr0_el1 = @ptrToInt(TTBR0_IDENTITY) | 1;
const ttbr1_el1 = @ptrToInt(TTBR1_L1) | 1;
Expand All @@ -115,12 +117,12 @@ pub export fn daintree_mmu_start(entry_data: *dcommon.EntryData) noreturn {
}

tableSet(TTBR1_L1, 0, @ptrToInt(TTBR1_L2), KERNEL_DATA_TABLE.toU64());
tableSet(TTBR1_L2, 0, @ptrToInt(TTBR1_L3), KERNEL_DATA_TABLE.toU64());
tableSet(TTBR1_L2, 0, @ptrToInt(TTBR1_L3_1), KERNEL_DATA_TABLE.toU64());
tableSet(TTBR1_L2, 1, @ptrToInt(TTBR1_L3_2), KERNEL_DATA_TABLE.toU64());

var end: u64 = (daintree_end - daintree_base) >> PAGE_BITS;
if (end > 512) {
uart.carefully(.{"end got too big (1)\r\n"});

while (true) {}
}

Expand All @@ -133,21 +135,21 @@ pub export fn daintree_mmu_start(entry_data: *dcommon.EntryData) noreturn {
} else if (address >= daintree_rodata_base) {
flags = KERNEL_RODATA_TABLE.toU64();
}
tableSet(TTBR1_L3, i, address, flags);
tableSet(TTBR1_L3_1, i, address, flags);
address += PAGE_SIZE;
}

// i = end
end += 4;
end += 5;
while (i < end) : (i += 1) {
uart.carefully(.{ "MAP: null at ", KERNEL_BASE | (i << PAGE_BITS), "\r\n" });

tableSet(TTBR1_L3, i, 0, 0);
tableSet(TTBR1_L3_1, i, 0, 0);
}
end = i + STACK_PAGES;
while (i < end) : (i += 1) {
uart.carefully(.{ "MAP: stack at ", KERNEL_BASE | (i << PAGE_BITS), "\r\n" });
tableSet(TTBR1_L3, i, address, KERNEL_DATA_TABLE.toU64());
tableSet(TTBR1_L3_1, i, address, KERNEL_DATA_TABLE.toU64());
address += PAGE_SIZE;
}

Expand All @@ -158,7 +160,7 @@ pub export fn daintree_mmu_start(entry_data: *dcommon.EntryData) noreturn {

// Let's hackily put UART at wherever's next.
uart.carefully(.{ "MAP: UART at ", KERNEL_BASE | (i << PAGE_BITS), "\r\n" });
tableSet(TTBR1_L3, i, entry_data.uart_base, PERIPHERAL_TABLE.toU64());
tableSet(TTBR1_L3_1, i, entry_data.uart_base, PERIPHERAL_TABLE.toU64());

// address now points to the stack. make space for common.EntryData, align.
var entry_address = address - @sizeOf(dcommon.EntryData);
Expand Down Expand Up @@ -199,7 +201,26 @@ pub export fn daintree_mmu_start(entry_data: *dcommon.EntryData) noreturn {

while (i < new_end) : (i += 1) {
uart.carefully(.{ "MAP: DTB at ", KERNEL_BASE | (i << PAGE_BITS), "\r\n" });
tableSet(TTBR1_L3, i, address, KERNEL_RODATA_TABLE.toU64());
tableSet(TTBR1_L3_1, i, address, KERNEL_RODATA_TABLE.toU64());
address += PAGE_SIZE;
}
}

// Map framebuffer as device. Put in second TTBR1_L3 as it tends to be
// huge.
if (new_entry.fb) |base| {
i = 512;
address = @ptrToInt(base);
new_entry.fb = @intToPtr([*]u32, KERNEL_BASE | (i << PAGE_BITS));
var new_end = i + (new_entry.fb_vert * new_entry.fb_horiz * 4 + PAGE_SIZE - 1) / PAGE_SIZE;
if (new_end > 512 + 512) {
uart.carefully(.{ "end got too big (4): ", new_end, "\r\n" });
while (true) {}
}

while (i < new_end) : (i += 1) {
uart.carefully(.{ "MAP: FB at ", KERNEL_BASE | (i << PAGE_BITS), "\r\n" });
tableSet(TTBR1_L3_2, i - 512, address, PERIPHERAL_TABLE.toU64());
address += PAGE_SIZE;
}
}
Expand Down
File renamed without changes.
3 changes: 2 additions & 1 deletion os/src/exception.s → dainkrnl/src/exception.s
Expand Up @@ -25,7 +25,8 @@
mrs x0, elr_el1
mrs x1, spsr_el1
stp x0, x1, [sp, #16 * 15]
str x30, [sp, #16 * 16]
mrs x0, far_el1
stp x30, x0, [sp, #16 * 16]

mov x0, sp
// Hack: throw ELR_EL1 and ESR_EL1 in as secondary arguments.
Expand Down
9 changes: 4 additions & 5 deletions os/src/exception.zig → dainkrnl/src/exception.zig
@@ -1,20 +1,19 @@
// Please see notes in entry/uart.zig on how it's used here.
const entry = @import("entry.zig");
const arch = @import("arch.zig");

const ExceptionContext = packed struct {
regs: [30]u64,
elr_el1: u64,
spsr_el1: u64,
lr: u64,
far_el1: u64,
};

export fn test_naked() callconv(.Naked) void {
asm volatile (
\\br x10
:
:
: "memory"
);
::: "memory");
}

fn handle(ctx: *ExceptionContext, comptime name: []const u8) callconv(.Inline) noreturn {
Expand Down Expand Up @@ -43,7 +42,7 @@ fn dumpRegs(ctx: *ExceptionContext) void {
entry.uart.carefully(.{ "x26 ", ctx.regs[26], " x27 ", ctx.regs[27], "\r\n" });
entry.uart.carefully(.{ "x28 ", ctx.regs[28], " x29 ", ctx.regs[29], "\r\n" });
entry.uart.carefully(.{ "elr ", ctx.elr_el1, " sps ", ctx.spsr_el1, "\r\n" });
entry.uart.carefully(.{ "lr ", ctx.lr, "\r\n\r\n" });
entry.uart.carefully(.{ "lr ", ctx.lr, " far ", ctx.far_el1, "\r\n\r\n" });
}

export fn el1_sp0_sync(ctx: *ExceptionContext) void {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions os/src/main.zig → dainkrnl/src/main.zig
Expand Up @@ -15,6 +15,7 @@ export fn daintree_main(entry_data: *dcommon.EntryData) void {
entry.uart.carefully(.{ "daintree_main using uart_base ", entry_data.uart_base, "\r\n" });

if (entry_data.fb) |fb_addr| {
entry.uart.carefully(.{ "initting fb at ", @ptrToInt(fb_addr), "\r\n" });
fb.init(fb_addr, entry_data.fb_vert, entry_data.fb_horiz);
}

Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion debug.sh
@@ -1,2 +1,2 @@
cd "$(dirname "$0")"
gdb os/zig-cache/bin/dainkrnl.qemu -ex 'set substitute-path /Users/kameliya/Code/daintree/os os' -ex 'set arch aarch64' -ex 'target remote :1234'
gdb dainkrnl/zig-cache/bin/dainkrnl.qemu -ex 'set substitute-path /Users/kameliya/Code/daintree/dainkrnl dainkrnl' -ex 'set arch aarch64' -ex 'target remote :1234'
2 changes: 1 addition & 1 deletion objdump.sh
@@ -1,2 +1,2 @@
cd "$(dirname "$0")"
objdump -dSl --prefix=. --prefix-strip=4 os/zig-cache/bin/dainkrnl|less
objdump -dSl --prefix=. --prefix-strip=4 dainkrnl/zig-cache/bin/dainkrnl|less
5 changes: 5 additions & 0 deletions roms/license.txt
@@ -0,0 +1,5 @@
u-boot-arm64-ramfb.bin is a build of U-Boot
(http://www.denx.de/wiki/U-Boot/WebHome), (C) Wolfgang Denk and contributors
under the GPL 2. See http://www.denx.de/wiki/U-Boot/Licensing for more details
on its license, and find its original source at
https://git.src.kameliya.ee/~kameliya/u-boot/log/qfw-ramfb.
Binary file added roms/u-boot-arm64-ramfb.bin
Binary file not shown.

0 comments on commit 867b660

Please sign in to comment.