├── .gitignore ├── LICENSE ├── README.md ├── build.zig └── src ├── blob.ld ├── io ├── io.zig ├── status_uart_mmio_32.zig └── uart_mmio_32.zig ├── lib ├── dtb.zig ├── elf.zig ├── pmm.zig ├── stivale.zig └── util.zig ├── platform ├── acpi.zig ├── cache.zig ├── drivers │ ├── fw_cfg.zig │ ├── ramfb.zig │ └── virt.zig ├── paging.zig ├── pci.zig ├── pi3_aarch64 │ ├── display.zig │ ├── entry.S │ ├── linker.ld │ ├── main.zig │ └── regs.zig ├── pine_aarch64 │ ├── boot.sh │ ├── ccu.zig │ ├── display.zig │ ├── dram.zig │ ├── entry.S │ ├── identity.S │ ├── keyadc.zig │ ├── led.zig │ ├── linker.ld │ ├── main.zig │ ├── regs.zig │ ├── smp.zig │ └── uart.zig ├── psci.zig ├── sdm665_aarch64 │ ├── entry.S │ ├── linker.ld │ └── main.zig ├── timer.zig ├── uefi_aarch64 │ ├── acpi.zig │ ├── framebuffer.zig │ ├── fs.zig │ └── main.zig ├── virt_aarch64 │ ├── display.zig │ ├── entry.S │ ├── linker.ld │ └── main.zig ├── virt_riscv64 │ ├── entry.S │ ├── linker.ld │ └── main.zig └── vision_five_v1_riscv64 │ ├── entry.S │ ├── linker.ld │ └── main.zig └── sabaton.zig /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/* 2 | zig-out/* 3 | test/* 4 | uefidir/image/kernel.elf 5 | uefidir/image/EFI/BOOT/BOOTAA64.* 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2021 by Hannes Bredberg and FlorenceOS contributors 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sabaton bootloader 2 | 3 | [Discord server](https://discord.gg/uaXtZVku2E) 4 | 5 | Sabaton is a Stivale2 bootloader targeting different aarch64 enviroments. 6 | 7 | ## Differences from stivale2 8 | Due to the memory layout of aarch64 devices being so far from standardized, a few changes have been made: 9 | * Your kernel has to be located in the top 2G. 10 | * All your kernel sections need to be 64K aligned, you don't know the page size (4K, 16K or 64K) ahead of time. 11 | 12 | Zig compiler support: 13 | Oldest confirmed working compiler: 0.9.0-dev+9354ad82 14 | Newest confirmed working compiler: 0.9.0-dev+9354ad82 15 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Builder = std.build.Builder; 3 | const builtin = std.builtin; 4 | const assert = std.debug.assert; 5 | 6 | fn here() []const u8 { 7 | return std.fs.path.dirname(@src().file) orelse "."; 8 | } 9 | 10 | fn cpu_features(arch: std.Target.Cpu.Arch, ctarget: std.zig.CrossTarget) std.zig.CrossTarget { 11 | var disabled_features = std.Target.Cpu.Feature.Set.empty; 12 | var enabled_feautres = std.Target.Cpu.Feature.Set.empty; 13 | 14 | if (arch == .aarch64) { 15 | const features = std.Target.aarch64.Feature; 16 | // This is equal to -mgeneral-regs-only 17 | disabled_features.addFeature(@enumToInt(features.fp_armv8)); 18 | disabled_features.addFeature(@enumToInt(features.crypto)); 19 | disabled_features.addFeature(@enumToInt(features.neon)); 20 | } 21 | 22 | return std.zig.CrossTarget{ 23 | .cpu_arch = arch, 24 | .os_tag = ctarget.os_tag, 25 | .abi = ctarget.abi, 26 | .cpu_features_sub = disabled_features, 27 | .cpu_features_add = enabled_feautres, 28 | }; 29 | } 30 | 31 | fn freestanding_target(elf: *std.build.LibExeObjStep, arch: std.Target.Cpu.Arch, do_code_model: bool) void { 32 | if (arch == .aarch64) { 33 | // We don't need the code model in asm blobs 34 | if (do_code_model) 35 | elf.code_model = .tiny; 36 | } 37 | 38 | if(arch == .riscv64) { 39 | // Where are the riscv code models?? 40 | elf.code_model = .medium; 41 | } 42 | 43 | elf.setTarget(cpu_features(arch, .{ 44 | .os_tag = std.Target.Os.Tag.freestanding, 45 | .abi = std.Target.Abi.none, 46 | })); 47 | } 48 | 49 | fn executable_common(b: *Builder, exec: *std.build.LibExeObjStep, board_name: []const u8) void { 50 | var options = b.addOptions(); 51 | options.addOption([]const u8, "board_name", board_name); 52 | exec.addOptions("build_options", options); 53 | 54 | exec.setBuildMode(.ReleaseSmall); 55 | if (@hasField(@TypeOf(exec.*), "want_lto")) 56 | exec.want_lto = false; 57 | 58 | exec.setMainPkgPath(comptime here() ++ "/src/"); 59 | exec.setOutputDir(b.cache_root); 60 | 61 | exec.install(); 62 | 63 | exec.disable_stack_probing = true; 64 | } 65 | 66 | pub fn build_uefi(b: *Builder, arch: std.Target.Cpu.Arch) !*std.build.LibExeObjStep { 67 | const filename = "BOOTAA64"; 68 | const platform_path = b.fmt(comptime here() ++ "/src/platform/uefi_{s}", .{@tagName(arch)}); 69 | 70 | const exec = b.addExecutable(filename, b.fmt("{s}/main.zig", .{platform_path})); 71 | executable_common(b, exec, "UEFI"); 72 | 73 | exec.code_model = .small; 74 | 75 | exec.setTarget(cpu_features(arch, .{ 76 | .os_tag = std.Target.Os.Tag.uefi, 77 | .abi = std.Target.Abi.msvc, 78 | })); 79 | 80 | exec.setOutputDir(comptime here() ++ "/uefidir/image/EFI/BOOT/"); 81 | 82 | return exec; 83 | } 84 | 85 | fn build_elf(b: *Builder, arch: std.Target.Cpu.Arch, target_name: []const u8) !*std.build.LibExeObjStep { 86 | const elf_filename = b.fmt("Sabaton_{s}_{s}.elf", .{ target_name, @tagName(arch) }); 87 | const platform_path = b.fmt(comptime here() ++ "/src/platform/{s}_{s}", .{ target_name, @tagName(arch) }); 88 | 89 | const elf = b.addExecutable(elf_filename, b.fmt("{s}/main.zig", .{platform_path})); 90 | elf.setLinkerScriptPath(.{ .path = b.fmt("{s}/linker.ld", .{platform_path}) }); 91 | elf.addAssemblyFile(b.fmt("{s}/entry.S", .{platform_path})); 92 | executable_common(b, elf, target_name); 93 | 94 | freestanding_target(elf, arch, true); 95 | 96 | return elf; 97 | } 98 | 99 | fn assembly_blob(b: *Builder, arch: std.Target.Cpu.Arch, name: []const u8, asm_file: []const u8) !*std.build.InstallRawStep { 100 | const elf_filename = b.fmt("{s}_{s}.elf", .{ name, @tagName(arch) }); 101 | 102 | const elf = b.addExecutable(elf_filename, null); 103 | elf.setLinkerScriptPath(.{ .path = "src/blob.ld" }); 104 | elf.addAssemblyFile(asm_file); 105 | 106 | freestanding_target(elf, arch, false); 107 | elf.setBuildMode(.ReleaseSafe); 108 | 109 | elf.setMainPkgPath("src/"); 110 | elf.setOutputDir(b.cache_root); 111 | 112 | elf.install(); 113 | 114 | return elf.installRaw(b.fmt("{s}.bin", .{elf_filename}), .{ 115 | .format = .bin, 116 | .only_section = ".blob", 117 | .pad_to = null, 118 | }); 119 | } 120 | 121 | pub fn aarch64VirtBlob(b: *Builder) *std.build.InstallRawStep { 122 | const elf = try build_elf(b, .aarch64, "virt"); 123 | return elf.installRaw(b.fmt("{s}.bin", .{elf.out_filename}), .{ 124 | .format = .bin, 125 | .only_section = ".blob", 126 | .pad_to = 64 * 1024 * 1024, // 64M 127 | }); 128 | } 129 | 130 | pub fn riscvVirtBlob(b: *Builder) *std.build.InstallRawStep { 131 | const elf = try build_elf(b, .riscv64, "virt"); 132 | return elf.installRaw(b.fmt("{s}.bin", .{elf.out_filename}), .{ 133 | .format = .bin, 134 | .only_section = ".blob", 135 | .pad_to = 32 * 1024 * 1024, // 64M 136 | }); 137 | } 138 | 139 | fn qemu_aarch64(b: *Builder, board_name: []const u8, desc: []const u8) !void { 140 | const command_step = b.step(board_name, desc); 141 | const blob = aarch64VirtBlob(b); 142 | 143 | const build_step = b.step("qemu-virt-build", "Build the qemu virt blob"); 144 | build_step.dependOn(&blob.step); 145 | 146 | const blob_path = b.getInstallPath(blob.dest_dir, blob.dest_filename); 147 | 148 | const run_step = b.addSystemCommand(&[_][]const u8{ 149 | // zig fmt: off 150 | "qemu-system-aarch64", 151 | "-M", board_name, 152 | "-cpu", "cortex-a57", 153 | "-m", "4G", 154 | "-serial", "stdio", 155 | //"-S", "-s", 156 | "-d", "int", 157 | "-smp", "8", 158 | "-device", "ramfb", 159 | "-kernel", "test/Flork_stivale2_aarch64", 160 | "-drive", b.fmt("if=pflash,format=raw,file={s},readonly=on", .{blob_path}), 161 | // zig fmt: on 162 | }); 163 | 164 | run_step.step.dependOn(&blob.step); 165 | command_step.dependOn(&run_step.step); 166 | } 167 | 168 | fn qemu_riscv(b: *Builder, desc: []const u8) !void { 169 | const command_step = b.step("riscv-virt", desc); 170 | const blob = riscvVirtBlob(b); 171 | 172 | const build_step = b.step("qemu-riscv-virt-build", "Build the qemu riscv virt blob"); 173 | build_step.dependOn(&blob.step); 174 | 175 | const blob_path = b.getInstallPath(blob.dest_dir, blob.dest_filename); 176 | 177 | const run_step = b.addSystemCommand(&[_][]const u8{ 178 | // zig fmt: off 179 | "qemu-system-riscv64", 180 | "-M", "virt", 181 | //"-cpu", "cortex-a57", 182 | "-m", "4G", 183 | "-serial", "stdio", 184 | //"-S", "-s", 185 | //"-d", "int", 186 | //"-smp", "8", 187 | "-device", "ramfb", 188 | "-fw_cfg", "opt/Sabaton/kernel,file=test/uart_riscv.elf", 189 | "-bios", blob_path, 190 | // zig fmt: on 191 | }); 192 | 193 | run_step.step.dependOn(&blob.step); 194 | command_step.dependOn(&run_step.step); 195 | } 196 | 197 | fn qemu_pi3_aarch64(b: *Builder, desc: []const u8, elf: *std.build.LibExeObjStep) !void { 198 | const command_step = b.step("pi3", desc); 199 | 200 | const blob = elf.installRaw(b.fmt("{s}.bin", .{elf.out_filename}), .{ 201 | .format = .bin, 202 | .only_section = ".blob", 203 | .pad_to = null, 204 | }); 205 | 206 | const blob_path = b.getInstallPath(blob.dest_dir, blob.dest_filename); 207 | 208 | const run_step = b.addSystemCommand(&[_][]const u8{ 209 | // zig fmt: off 210 | "qemu-system-aarch64", 211 | "-M", "raspi3", 212 | "-device", "loader,file=test/Flork_stivale2_aarch64,addr=0x200000,force-raw=on", 213 | "-serial", "null", 214 | "-serial", "stdio", 215 | "-d", "int", 216 | "-kernel", blob_path, 217 | // zig fmt: off 218 | }); 219 | 220 | run_step.step.dependOn(&blob.step); 221 | command_step.dependOn(&run_step.step); 222 | } 223 | 224 | fn qemu_uefi_aarch64(b: *Builder, desc: []const u8, dep: *std.build.LibExeObjStep) !void { 225 | const command_step = b.step("uefi", desc); 226 | 227 | const run_step = b.addSystemCommand(&[_][]const u8{ 228 | // zig fmt: off 229 | "qemu-system-aarch64", 230 | "-M", "virt", 231 | "-m", "4G", 232 | "-cpu", "cortex-a57", 233 | "-serial", "stdio", 234 | "-device", "ramfb", 235 | "-drive", b.fmt("if=pflash,format=raw,file={s}/QEMU_EFI.fd,readonly=on", .{std.os.getenv("AARCH64_EDK_PATH").?}), 236 | "-drive", b.fmt("if=pflash,format=raw,file={s}/QEMU_VARS.fd", .{std.os.getenv("AARCH64_EDK_PATH").?}), 237 | "-hdd", "fat:rw:uefidir/image", 238 | "-usb", 239 | "-device", "usb-ehci", 240 | "-device", "usb-kbd", 241 | // zig fmt: off 242 | }); 243 | 244 | run_step.step.dependOn(&dep.install_step.?.step); 245 | command_step.dependOn(&run_step.step); 246 | } 247 | 248 | const Device = struct { 249 | name: []const u8, 250 | arch: std.Target.Cpu.Arch, 251 | }; 252 | 253 | const AssemblyBlobSpec = struct { 254 | name: []const u8, 255 | arch: std.Target.Cpu.Arch, 256 | path: []const u8, 257 | }; 258 | 259 | pub fn build(b: *Builder) !void { 260 | //make_source_blob(b); 261 | 262 | try qemu_aarch64( 263 | b, 264 | "virt", 265 | "Run aarch64 sabaton for the qemu virt board", 266 | ); 267 | 268 | try qemu_riscv(b, 269 | "Run riscv64 sabaton on for the qemu virt board", 270 | ); 271 | 272 | try qemu_pi3_aarch64( 273 | b, 274 | "Run aarch64 sabaton for the qemu raspi3 board", 275 | try build_elf(b, .aarch64, "pi3"), 276 | ); 277 | 278 | try qemu_uefi_aarch64( 279 | b, 280 | "Run aarch64 sabaton for UEFI", 281 | try build_uefi(b, .aarch64), 282 | ); 283 | 284 | { 285 | const assembly_blobs = &[_]AssemblyBlobSpec{ 286 | .{ .path = "src/platform/pine_aarch64/identity.S", .name = "identity_pine", .arch = .aarch64 }, 287 | }; 288 | 289 | for (assembly_blobs) |spec| { 290 | const blob_file = try assembly_blob(b, spec.arch, spec.name, spec.path); 291 | b.default_step.dependOn(&blob_file.step); 292 | } 293 | } 294 | 295 | { 296 | const elf_devices = &[_]Device{}; 297 | 298 | for (elf_devices) |dev| { 299 | const elf_file = try build_elf(b, .aarch64, dev.name); 300 | const s = b.step(dev.name, b.fmt("Build the blob for {s}", .{dev.name})); 301 | s.dependOn(&elf_file.step); 302 | b.default_step.dependOn(s); 303 | } 304 | } 305 | 306 | { 307 | const blob_devices = &[_]Device{ 308 | .{ .name = "pine", .arch = .aarch64 }, 309 | .{ .name = "sdm665", .arch = .aarch64 }, 310 | .{ .name = "vision_five_v1", .arch = .riscv64 }, 311 | }; 312 | 313 | for (blob_devices) |dev| { 314 | const elf = try build_elf(b, dev.arch, dev.name); 315 | const blob_file = elf.installRaw(b.fmt("{s}_{s}.bin", .{dev.name, @tagName(dev.arch)}), .{ 316 | .format = .bin, 317 | .only_section = ".blob", 318 | .pad_to = null, 319 | }); 320 | const s = b.step(dev.name, b.fmt("Build the blob for {s}", .{dev.name})); 321 | s.dependOn(&blob_file.step); 322 | b.default_step.dependOn(s); 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/blob.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | 3 | SECTIONS { 4 | .blob : { 5 | *(.blob*) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/io/io.zig: -------------------------------------------------------------------------------- 1 | pub const uart_mmio_32 = @import("uart_mmio_32.zig"); 2 | pub const status_uart_mmio_32 = @import("status_uart_mmio_32.zig"); 3 | 4 | const sabaton = @import("root").sabaton; 5 | 6 | const fmt = @import("std").fmt; 7 | 8 | pub const putchar = sabaton.platform.io.putchar; 9 | pub const getchar = sabaton.platform.io.getchar; 10 | pub const putchar_bin = sabaton.platform.io.putchar_bin; 11 | pub const getchar_bin = sabaton.platform.io.getchar_bin; 12 | 13 | const Printer = struct { 14 | pub fn writeAll(self: *const Printer, str: []const u8) !void { 15 | _ = self; 16 | print_str(str); 17 | } 18 | 19 | pub fn print(self: *const Printer, comptime format: []const u8, args: anytype) !void { 20 | _ = self; 21 | log(format, args); 22 | } 23 | 24 | pub fn writeByteNTimes(self: *const Printer, val: u8, num: usize) !void { 25 | _ = self; 26 | var i: usize = 0; 27 | while (i < num) : (i += 1) { 28 | putchar(val); 29 | } 30 | } 31 | pub const Error = anyerror; 32 | }; 33 | 34 | pub const log = if (sabaton.debug) struct { 35 | pub fn log(comptime format: []const u8, args: anytype) void { 36 | var printer = Printer{}; 37 | fmt.format(printer, format, args) catch unreachable; 38 | } 39 | }.log else struct { 40 | pub fn log(comptime format: []const u8, args: anytype) void { 41 | _ = args; 42 | _ = format; 43 | @compileError("Log called!"); 44 | } 45 | }.log; 46 | 47 | pub fn print_chars(ptr: [*]const u8, num: usize) void { 48 | var i: usize = 0; 49 | while (i < num) : (i += 1) { 50 | putchar(ptr[i]); 51 | } 52 | } 53 | 54 | fn wrapped_print_hex(num: u64, nibbles: isize) void { 55 | var i: isize = nibbles - 1; 56 | while (i >= 0) : (i -= 1) { 57 | putchar("0123456789ABCDEF"[(num >> @intCast(u6, i * 4)) & 0xF]); 58 | } 59 | } 60 | 61 | pub fn print_hex(num: anytype) void { 62 | switch (@typeInfo(@TypeOf(num))) { 63 | else => @compileError("Unknown print_hex type!"), 64 | 65 | .Int => @call(.{ .modifier = .never_inline }, wrapped_print_hex, .{ num, (@bitSizeOf(@TypeOf(num)) + 3) / 4 }), 66 | .Pointer => @call(.{ .modifier = .always_inline }, print_hex, .{@ptrToInt(num)}), 67 | .ComptimeInt => @call(.{ .modifier = .always_inline }, print_hex, .{@as(usize, num)}), 68 | } 69 | } 70 | 71 | pub fn log_hex(str: [*:0]const u8, val: anytype) void { 72 | puts(str); 73 | print_hex(val); 74 | putchar('\n'); 75 | } 76 | 77 | fn wrapped_puts(str_c: [*:0]const u8) void { 78 | var str = str_c; 79 | while (str[0] != 0) : (str += 1) 80 | @call(.{ .modifier = .never_inline }, putchar, .{str[0]}); 81 | } 82 | 83 | pub fn puts(str_c: [*:0]const u8) void { 84 | @call(.{ .modifier = .never_inline }, wrapped_puts, .{str_c}); 85 | } 86 | 87 | fn wrapped_print_str(str: []const u8) void { 88 | for (str) |c| 89 | putchar(c); 90 | } 91 | 92 | pub fn print_str(str: []const u8) void { 93 | @call(.{ .modifier = .never_inline }, wrapped_print_str, .{str}); 94 | } 95 | -------------------------------------------------------------------------------- /src/io/status_uart_mmio_32.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | 3 | inline fn flush_impl( 4 | status_reg: *volatile u32, 5 | mask: u32, 6 | value: u32, 7 | ) void { 8 | // Wait until output is ready 9 | while ((status_reg.* & mask) != value) {} 10 | } 11 | 12 | pub fn flush() void { 13 | const uart_info: Info = sabaton.platform.get_uart_info(); 14 | flush_impl(uart_info.status, uart_info.mask, uart_info.value); 15 | } 16 | 17 | fn putchar_impl( 18 | char: u8, 19 | uart_reg: *volatile u32, 20 | status_reg: *volatile u32, 21 | mask: u32, 22 | value: u32, 23 | ) void { 24 | if (char == '\n') 25 | putchar_impl('\r', uart_reg, status_reg, mask, value); 26 | 27 | flush_impl(status_reg, mask, value); 28 | 29 | sabaton.io_impl.uart_mmio_32.write_reg(char, uart_reg); 30 | } 31 | 32 | pub fn putchar(char: u8) void { 33 | const uart_info: Info = sabaton.platform.get_uart_info(); 34 | 35 | putchar_impl(char, uart_info.uart, uart_info.status, uart_info.mask, uart_info.value); 36 | } 37 | 38 | pub fn putchar_bin(char: u8) void { 39 | const uart_info: Info = sabaton.platform.get_uart_info(); 40 | while ((uart_info.status.* & uart_info.mask) != uart_info.value) {} 41 | uart_info.uart.* = char; 42 | } 43 | 44 | pub fn getchar_bin() u8 { 45 | const uart_info: Info = sabaton.platform.get_uart_reader(); 46 | while ((uart_info.status.* & uart_info.mask) != uart_info.value) {} 47 | return @truncate(u8, uart_info.uart.*); 48 | } 49 | 50 | pub const Info = struct { 51 | uart: *volatile u32, 52 | status: *volatile u32, 53 | mask: u32, 54 | value: u32, 55 | }; 56 | -------------------------------------------------------------------------------- /src/io/uart_mmio_32.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | 3 | pub fn write_reg(char: u8, reg: *volatile u32) void { 4 | reg.* = @as(u32, char); 5 | } 6 | 7 | pub fn putchar(char: u8) void { 8 | const uart_info: Info = sabaton.platform.get_uart_info(); 9 | 10 | if (char == '\n') 11 | write_reg('\r', uart_info.uart); 12 | write_reg(char, uart_info.uart); 13 | } 14 | 15 | pub const Info = struct { 16 | uart: *volatile u32, 17 | }; 18 | -------------------------------------------------------------------------------- /src/lib/dtb.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | const std = @import("std"); 3 | 4 | var data: []u8 = undefined; 5 | 6 | const BE = sabaton.util.BigEndian; 7 | 8 | const Header = packed struct { 9 | magic: BE(u32), 10 | totalsize: BE(u32), 11 | off_dt_struct: BE(u32), 12 | off_dt_strings: BE(u32), 13 | off_mem_rsvmap: BE(u32), 14 | version: BE(u32), 15 | last_comp_version: BE(u32), 16 | boot_cpuid_phys: BE(u32), 17 | size_dt_strings: BE(u32), 18 | size_dt_struct: BE(u32), 19 | }; 20 | 21 | pub fn find_cpu_id(dtb_id: usize) ?u32 { 22 | var cpu_name_buf = [_]u8{undefined} ** 32; 23 | cpu_name_buf[0] = 'c'; 24 | cpu_name_buf[1] = 'p'; 25 | cpu_name_buf[2] = 'u'; 26 | cpu_name_buf[3] = '@'; 27 | 28 | const buf_len = sabaton.util.write_int_decimal(cpu_name_buf[4..], dtb_id) + 4; 29 | const id_bytes = (find(cpu_name_buf[0..buf_len], "reg") catch return null)[0..4]; 30 | return std.mem.readIntBig(u32, id_bytes[0..4]); 31 | } 32 | 33 | comptime { 34 | switch(sabaton.arch) { 35 | .aarch64 => { 36 | asm ( 37 | \\.section .text.smp_stub 38 | \\.global smp_stub 39 | \\smp_stub: 40 | \\ DSB SY 41 | \\ LDR X1, [X0, #8] 42 | \\ MOV SP, X1 43 | ); 44 | }, 45 | 46 | .riscv64 => { 47 | asm( 48 | \\.section .text.smp_stub 49 | \\.global smp_stub 50 | \\smp_stub: 51 | \\ J smp_stub 52 | ); 53 | }, 54 | 55 | else => @compileError("Implement smp_stub for " ++ @tagName(sabaton.arch)), 56 | } 57 | } 58 | 59 | export fn smp_entry(context: u64) linksection(".text.smp_entry") noreturn { 60 | @call(.{ .modifier = .always_inline }, sabaton.stivale2_smp_ready, .{context}); 61 | } 62 | 63 | pub fn psci_smp(comptime methods: ?sabaton.psci.Mode) void { 64 | const psci_method = blk: { 65 | if (comptime (methods == null)) { 66 | const method_str = (sabaton.dtb.find("psci", "method") catch return)[0..3]; 67 | sabaton.puts("PSCI method: "); 68 | sabaton.print_str(method_str); 69 | sabaton.putchar('\n'); 70 | break :blk method_str; 71 | } 72 | break :blk undefined; 73 | }; 74 | 75 | const num_cpus = blk: { 76 | var count: u32 = 1; 77 | 78 | while (true) : (count += 1) { 79 | _ = find_cpu_id(count) orelse break :blk count; 80 | } 81 | }; 82 | 83 | sabaton.log_hex("Number of CPUs found: ", num_cpus); 84 | 85 | if (num_cpus == 1) 86 | return; 87 | 88 | const smp_tag = sabaton.pmm.alloc_aligned(40 + num_cpus * @sizeOf(sabaton.stivale.SMPTagEntry), .Hole); 89 | const entry = @ptrToInt(sabaton.near("smp_stub").addr(u32)); 90 | const smp_header = @intToPtr(*sabaton.stivale.SMPTagHeader, @ptrToInt(smp_tag.ptr)); 91 | smp_header.tag.ident = 0x34d1d96339647025; 92 | smp_header.cpu_count = num_cpus; 93 | 94 | var cpu_num: u32 = 1; 95 | while (cpu_num < num_cpus) : (cpu_num += 1) { 96 | const id = find_cpu_id(cpu_num) orelse unreachable; 97 | const tag_addr = @ptrToInt(smp_tag.ptr) + 40 + cpu_num * @sizeOf(sabaton.stivale.SMPTagEntry); 98 | const tag_entry = @intToPtr(*sabaton.stivale.SMPTagEntry, tag_addr); 99 | tag_entry.acpi_id = cpu_num; 100 | tag_entry.cpu_id = id; 101 | const stack = sabaton.pmm.alloc_aligned(0x1000, .Hole); 102 | tag_entry.stack = @ptrToInt(stack.ptr) + 0x1000; 103 | 104 | switch(comptime(sabaton.arch)) { 105 | .aarch64 => { 106 | // Make sure we've written everything we need to memory before waking this CPU up 107 | asm volatile ("DSB ST\n" ::: "memory"); 108 | 109 | if (comptime (methods == null)) { 110 | if (std.mem.eql(u8, psci_method, "smc")) { 111 | _ = sabaton.psci.wake_cpu(entry, id, tag_addr, .SMC); 112 | continue; 113 | } 114 | 115 | if (std.mem.eql(u8, psci_method, "hvc")) { 116 | _ = sabaton.psci.wake_cpu(entry, id, tag_addr, .HVC); 117 | continue; 118 | } 119 | } else { 120 | _ = sabaton.psci.wake_cpu(entry, id, tag_addr, comptime (methods.?)); 121 | continue; 122 | } 123 | 124 | if (comptime !sabaton.safety) 125 | unreachable; 126 | 127 | @panic("Unknown PSCI method!"); 128 | }, 129 | 130 | .riscv64 => { 131 | // TODO 132 | }, 133 | 134 | else => @compileError("Implement CPU waking on " ++ @tagName(sabaton.arch)), 135 | } 136 | } 137 | 138 | sabaton.add_tag(&smp_header.tag); 139 | } 140 | 141 | pub fn find(node_prefix: []const u8, prop_name: []const u8) ![]u8 { 142 | const dtb = sabaton.platform.get_dtb(); 143 | 144 | const header = @ptrCast(*Header, dtb.ptr); 145 | 146 | std.debug.assert(header.magic.read() == 0xD00DFEED); 147 | std.debug.assert(header.totalsize.read() == dtb.len); 148 | 149 | var curr = @ptrCast([*]BE(u32), dtb.ptr + header.off_dt_struct.read()); 150 | 151 | var current_depth: usize = 0; 152 | var found_at_depth: ?usize = null; 153 | 154 | while (true) { 155 | const opcode = curr[0].read(); 156 | curr += 1; 157 | switch (opcode) { 158 | 0x00000001 => { // FDT_BEGIN_NODE 159 | const name = @ptrCast([*:0]u8, curr); 160 | const namelen = sabaton.util.strlen(name); 161 | 162 | if (sabaton.debug) 163 | sabaton.log("FDT_BEGIN_NODE(\"{s}\", {})\n", .{ name[0..namelen], namelen }); 164 | 165 | current_depth += 1; 166 | if (found_at_depth == null and namelen >= node_prefix.len) { 167 | if (std.mem.eql(u8, name[0..node_prefix.len], node_prefix)) { 168 | found_at_depth = current_depth; 169 | } 170 | } 171 | 172 | curr += (namelen + 4) / 4; 173 | }, 174 | 0x00000002 => { // FDT_END_NODE 175 | if (sabaton.debug) 176 | sabaton.log("FDT_END_NODE\n", .{}); 177 | if (found_at_depth) |d| { 178 | if (d == current_depth) { 179 | found_at_depth = null; 180 | } 181 | } 182 | current_depth -= 1; 183 | }, 184 | 0x00000003 => { // FDT_PROP 185 | const nameoff = curr[1].read(); 186 | var len = curr[0].read(); 187 | 188 | const name = @ptrCast([*:0]u8, dtb.ptr + header.off_dt_strings.read() + nameoff); 189 | if (sabaton.debug) 190 | sabaton.log("FDT_PROP(\"{s}\"), len 0x{X}\n", .{ name, len }); 191 | 192 | if (found_at_depth) |d| { 193 | if (d == current_depth) { 194 | // DID WE FIND IT?? 195 | if (std.mem.eql(u8, name[0..prop_name.len], prop_name) and name[prop_name.len] == 0) 196 | return @ptrCast([*]u8, curr + 2)[0..len]; 197 | } 198 | } 199 | 200 | len += 3; 201 | curr += len / 4 + 2; 202 | }, 203 | 0x00000004 => {}, // FDT_NOP 204 | 0x00000009 => break, // FDT_END 205 | else => { 206 | if (sabaton.safety) { 207 | sabaton.log_hex("Unknown DTB opcode: ", opcode); 208 | } 209 | unreachable; 210 | }, 211 | } 212 | } 213 | 214 | return error.NotFound; 215 | } 216 | -------------------------------------------------------------------------------- /src/lib/elf.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sabaton = @import("root").sabaton; 3 | 4 | const LoadType = enum { 5 | MakePageTables, 6 | LoadDataPages, 7 | }; 8 | 9 | const addr = u64; 10 | const off = u64; 11 | const half = u16; 12 | const word = u32; 13 | const xword = u64; 14 | 15 | pub const elf64hdr = packed struct { 16 | ident: [16]u8, 17 | elf_type: half, 18 | machine: half, 19 | version: word, 20 | entry: addr, 21 | phoff: off, 22 | shoff: off, 23 | flags: word, 24 | ehsize: half, 25 | phentsize: half, 26 | phnum: half, 27 | shentsize: half, 28 | shnum: half, 29 | shstrndx: half, 30 | }; 31 | 32 | pub const elf64shdr = packed struct { 33 | name: word, 34 | stype: word, 35 | flags: xword, 36 | vaddr: addr, 37 | offset: off, 38 | size: xword, 39 | link: word, 40 | info: word, 41 | addralign: xword, 42 | entsize: xword, 43 | }; 44 | 45 | pub const elf64phdr = packed struct { 46 | phtype: word, 47 | flags: word, 48 | offset: off, 49 | vaddr: addr, 50 | paddr: addr, 51 | filesz: xword, 52 | memsz: xword, 53 | alignment: xword, 54 | }; 55 | 56 | pub const Elf = struct { 57 | data: [*]u8, 58 | shstrtab: ?[]u8 = undefined, 59 | 60 | pub fn init(self: *@This()) void { 61 | if (!std.mem.eql(u8, self.data[0..4], "\x7FELF")) { 62 | @panic("Invalid kernel ELF magic!"); 63 | } 64 | 65 | const shshstrtab = self.shdr(self.header().shstrndx); 66 | self.shstrtab = (self.data + shshstrtab.offset)[0..shshstrtab.size]; 67 | } 68 | 69 | pub fn section_name(self: *@This(), offset: usize) [*:0]u8 { 70 | return @ptrCast([*:0]u8, &self.shstrtab.?[offset]); 71 | } 72 | 73 | pub fn header(self: *const @This()) *elf64hdr { 74 | return @ptrCast(*elf64hdr, self.data); 75 | } 76 | 77 | pub fn shdr(self: *@This(), num: usize) *elf64shdr { 78 | const h = self.header(); 79 | return @ptrCast(*elf64shdr, self.data + h.shoff + h.shentsize * num); 80 | } 81 | 82 | pub fn phdr(self: *@This(), num: usize) *elf64phdr { 83 | const h = self.header(); 84 | return @ptrCast(*elf64phdr, self.data + h.phoff + h.phentsize * num); 85 | } 86 | 87 | pub fn load_section(self: *@This(), name: []const u8, buf: []u8) !void { 88 | var snum: usize = 0; 89 | const h = self.header(); 90 | while (snum < h.shnum) : (snum += 1) { 91 | const s = self.shdr(snum); 92 | const sname = self.section_name(s.name); 93 | 94 | if (std.mem.eql(u8, sname[0..name.len], name) and sname[name.len] == 0) { 95 | var load_size = s.size; 96 | if (load_size > buf.len) 97 | load_size = buf.len; 98 | 99 | @memcpy(buf.ptr, self.data + s.offset, load_size); 100 | 101 | return; 102 | // TODO: Relocations 103 | } 104 | } 105 | return error.HeaderNotFound; 106 | } 107 | 108 | pub fn load(self: *@This(), mempool_c: []align(4096) u8, root: *sabaton.paging.Root) void { 109 | const page_size = sabaton.platform.get_page_size(); 110 | var mempool = mempool_c; 111 | 112 | var phnum: usize = 0; 113 | const h = self.header(); 114 | while (phnum < h.phnum) : (phnum += 1) { 115 | const ph = self.phdr(phnum); 116 | 117 | if (ph.phtype != 1) 118 | continue; 119 | 120 | if (sabaton.debug) { 121 | sabaton.log("Loading 0x{X} bytes at ELF offset 0x{X}\n", .{ ph.filesz, ph.offset }); 122 | sabaton.log("Memory size is 0x{X} and is backed by physical memory at 0x{X}\n", .{ ph.memsz, @ptrToInt(mempool.ptr) }); 123 | } 124 | 125 | @memcpy(mempool.ptr, self.data + ph.offset, ph.filesz); 126 | @memset(mempool.ptr + ph.filesz, 0, ph.memsz - ph.filesz); 127 | 128 | const perms = @intToEnum(sabaton.paging.Perms, @intCast(u3, ph.flags & 0x7)); 129 | sabaton.paging.map(ph.vaddr, @ptrToInt(mempool.ptr), ph.memsz, perms, .writeback, root); 130 | 131 | // TODO: Relocations 132 | 133 | var used_bytes = ph.memsz; 134 | used_bytes += page_size - 1; 135 | used_bytes &= ~(page_size - 1); 136 | 137 | mempool.ptr = @alignCast(4096, mempool.ptr + used_bytes); 138 | mempool.len -= used_bytes; 139 | } 140 | 141 | if (sabaton.debug and mempool.len != 0) { 142 | sabaton.puts("Kernel overallocated??\n"); 143 | } 144 | } 145 | 146 | pub fn paged_bytes(self: *@This()) usize { 147 | const page_size = sabaton.platform.get_page_size(); 148 | var result: usize = 0; 149 | 150 | var phnum: usize = 0; 151 | const h = self.header(); 152 | while (phnum < h.phnum) : (phnum += 1) { 153 | const ph = self.phdr(phnum); 154 | 155 | if (ph.phtype != 1) 156 | continue; 157 | 158 | result += ph.memsz; 159 | result += page_size - 1; 160 | result &= ~(page_size - 1); 161 | } 162 | 163 | return result; 164 | } 165 | 166 | pub fn entry(self: *const @This()) usize { 167 | const h = self.header(); 168 | return h.entry; 169 | } 170 | }; 171 | -------------------------------------------------------------------------------- /src/lib/pmm.zig: -------------------------------------------------------------------------------- 1 | comptime { 2 | if (@import("builtin").target.os.tag != .uefi) { 3 | asm ( 4 | \\.extern __pmm_base 5 | \\.extern __dram_base 6 | \\ 7 | \\.global dram_base 8 | \\.global pmm_head 9 | \\ 10 | \\ .section .data.memmap 11 | \\ // Prekernel reclaimable 12 | \\ dram_base: 13 | \\ .8byte __dram_base 14 | \\ prekernel_size: 15 | \\ .8byte 0 16 | \\ .8byte 0x1000 // Reclaimable 17 | \\ 18 | \\ // Kernel and module pages 19 | \\ stivalehdr_pages_base: 20 | \\ .8byte 0 21 | \\ stivalehdr_pages_size: 22 | \\ .8byte 0 23 | \\ .8byte 0x1001 // Kernel and modules 24 | \\ 25 | \\ // Page tables 26 | \\ pt_base: 27 | \\ .8byte 0 28 | \\ pt_size: 29 | \\ .8byte 0 30 | \\ .8byte 0x1000 // Reclaimable 31 | \\ 32 | \\ // Usable region 33 | \\ pmm_head: 34 | \\ .8byte __pmm_base 35 | \\ usable_size: 36 | \\ .8byte 0 37 | \\ .8byte 1 // Usable 38 | ); 39 | } 40 | } 41 | 42 | const sabaton = @import("root").sabaton; 43 | const std = @import("std"); 44 | 45 | const pmm_state = enum(u2) { 46 | Prekernel = 0, 47 | KernelPages, 48 | PageTables, 49 | Sealed, 50 | }; 51 | 52 | var current_state: pmm_state = .Prekernel; 53 | 54 | pub fn verify_transition(s: pmm_state) void { 55 | if (sabaton.safety and (current_state == .Sealed or (@enumToInt(current_state) + 1 != @enumToInt(s)))) { 56 | sabaton.puts("Unexpected pmm sate: "); 57 | sabaton.print_str(@tagName(s)); 58 | sabaton.puts(" while in state: "); 59 | sabaton.print_str(@tagName(current_state)); 60 | sabaton.putchar('\n'); 61 | unreachable; 62 | } 63 | } 64 | 65 | pub fn switch_state(new_state: pmm_state) void { 66 | verify_transition(new_state); 67 | 68 | // Transition out of current state and apply changes 69 | const page_size = sabaton.platform.get_page_size(); 70 | 71 | // Page align the addresses and sizes 72 | var current_base = sabaton.near("pmm_head").read(u64); 73 | current_base += page_size - 1; 74 | current_base &= ~(page_size - 1); 75 | 76 | const eff_idx = @as(usize, @enumToInt(current_state)) * 3; 77 | const current_entry = sabaton.near("dram_base").addr(u64); 78 | 79 | // Size = head - base 80 | current_entry[eff_idx + 1] = current_base - current_entry[eff_idx + 0]; 81 | // next_base = head 82 | current_entry[eff_idx + 3] = current_base; 83 | 84 | current_state = new_state; 85 | } 86 | 87 | const purpose = enum { 88 | ReclaimableData, 89 | KernelPage, 90 | PageTable, 91 | Hole, 92 | }; 93 | 94 | fn verify_purpose(p: purpose) void { 95 | if (sabaton.safety and switch (current_state) { 96 | // When we're doing page tables, only allow that 97 | .PageTables => p != .PageTable, 98 | 99 | // When loading the kernel, only allow it 100 | .KernelPages => p != .KernelPage, 101 | 102 | // Before and after kernel loading we can do normal allocations 103 | .Prekernel => p != .ReclaimableData, 104 | 105 | // When we're sealed we don't want to allocate anything anymore 106 | .Sealed => p != .Hole, 107 | }) { 108 | sabaton.puts("Allocation purpose "); 109 | sabaton.print_str(@tagName(p)); 110 | sabaton.puts(" not valid in state "); 111 | sabaton.print_str(@tagName(current_state)); 112 | sabaton.putchar('\n'); 113 | unreachable; 114 | } 115 | } 116 | 117 | fn alloc_impl(num_bytes: u64, comptime aligned: bool, p: purpose) []u8 { 118 | if (comptime (@import("builtin").target.os.tag == .uefi)) { 119 | const mem = sabaton.vital(@import("root").allocator.alignedAlloc(u8, 0x1000, num_bytes), "PMM allocation on UEFI", true); 120 | std.mem.set(u8, mem, 0); 121 | return mem; 122 | } else { 123 | var current_base = sabaton.near("pmm_head").read(u64); 124 | 125 | verify_purpose(p); 126 | 127 | if (aligned) { 128 | const page_size = sabaton.platform.get_page_size(); 129 | 130 | current_base += page_size - 1; 131 | current_base &= ~(page_size - 1); 132 | } 133 | 134 | sabaton.near("pmm_head").write(current_base + num_bytes); 135 | const ptr = @intToPtr([*]u8, current_base); 136 | @memset(ptr, 0, num_bytes); 137 | return ptr[0..num_bytes]; 138 | } 139 | } 140 | 141 | pub fn alloc_aligned(num_bytes: u64, p: purpose) []align(0x1000) u8 { 142 | return @alignCast(0x1000, alloc_impl(num_bytes, true, p)); 143 | } 144 | 145 | pub fn write_dram_size(dram_end: u64) void { 146 | if (sabaton.safety and current_state != .Sealed) { 147 | sabaton.puts("Unexpected pmm sate: "); 148 | sabaton.print_str(@tagName(current_state)); 149 | sabaton.puts("while writing dram size\n"); 150 | unreachable; 151 | } 152 | 153 | // Align the current base 154 | const current_head = @ptrToInt(alloc_aligned(0, .Hole).ptr); 155 | sabaton.near("usable_size").write(dram_end - current_head); 156 | } 157 | -------------------------------------------------------------------------------- /src/lib/stivale.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | const std = @import("std"); 3 | 4 | pub const SMPTagHeader = struct { 5 | tag: sabaton.Stivale2tag, 6 | flags: u64, 7 | boot_cpu: u32, 8 | pad: u32, 9 | cpu_count: u64, 10 | }; 11 | 12 | pub const SMPTagEntry = struct { 13 | acpi_id: u32, 14 | cpu_id: u32, 15 | stack: u64, 16 | goto: u64, 17 | arg: u64, 18 | }; 19 | 20 | comptime { 21 | std.debug.assert(@sizeOf(sabaton.stivale.SMPTagEntry) == 32); 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/util.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | const std = @import("std"); 3 | 4 | pub fn upper_half(vaddr: u64) bool { 5 | return vaddr >= 0x8000000000000000; 6 | } 7 | 8 | pub fn BigEndian(comptime T: type) type { 9 | return packed struct { 10 | val: T, 11 | 12 | pub fn read(self: *@This()) T { 13 | return std.mem.readIntBig(T, @ptrCast([*]u8, &self.val)[0..4]); 14 | } 15 | }; 16 | } 17 | 18 | pub fn vital(val: anytype, comptime context: []const u8, comptime reachable: bool) @TypeOf(val catch unreachable) { 19 | return val catch |err| { 20 | if (reachable) { 21 | sabaton.puts("Fatal error: "); 22 | sabaton.print_str(@errorName(err)); 23 | sabaton.puts(" while " ++ context); 24 | @panic(""); 25 | } 26 | unreachable; 27 | }; 28 | } 29 | 30 | pub fn strlen(str: [*:0]u8) usize { 31 | var len: usize = 0; 32 | while (str[len] != 0) 33 | len += 1; 34 | return len; 35 | } 36 | 37 | pub fn near(comptime name: []const u8) type { 38 | return switch(sabaton.arch) { 39 | .aarch64 => struct { 40 | pub fn read(comptime t: type) t { 41 | return asm ("LDR %[out], " ++ name ++ "\n\t" 42 | : [out] "=r" (-> t) 43 | : 44 | : "memory" 45 | ); 46 | } 47 | 48 | pub fn addr(comptime t: type) [*]t { 49 | return asm ("ADR %[out], " ++ name ++ "\n\t" 50 | : [out] "=r" (-> [*]t) 51 | ); 52 | } 53 | 54 | pub fn write(val: anytype) void { 55 | addr(@TypeOf(val))[0] = val; 56 | } 57 | }, 58 | 59 | .riscv64 => struct { 60 | pub fn read(comptime t: type) t { 61 | return addr(t)[0]; 62 | } 63 | 64 | pub fn addr(comptime t: type) [*]t { 65 | return asm( 66 | "LA %[out], " ++ name ++ "\n\t" 67 | : [out] "=r" (-> [*]t) 68 | : 69 | : "memory" 70 | ); 71 | } 72 | 73 | pub fn write(val: anytype) void { 74 | addr(@TypeOf(val))[0] = val; 75 | } 76 | }, 77 | 78 | else => @compileError("Implement near for arch " ++ @tagName(sabaton.arch)), 79 | }; 80 | } 81 | 82 | pub fn to_byte_slice(val: anytype) []u8 { 83 | return @ptrCast([*]u8, val)[0..@sizeOf(@TypeOf(val.*))]; 84 | } 85 | 86 | /// Writes the int i into the buffer, returns the number 87 | /// of characters written. 88 | pub fn write_int_decimal(buf: []u8, i: usize) usize { 89 | const current = '0' + @intCast(u8, i % 10); 90 | const next = i / 10; 91 | if (next != 0) { 92 | const written = write_int_decimal(buf, next); 93 | buf[written] = current; 94 | return written + 1; 95 | } else { 96 | buf[0] = current; 97 | return 1; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/platform/acpi.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | const std = @import("std"); 3 | 4 | const RSDP = packed struct { 5 | signature: [8]u8, 6 | checksum: u8, 7 | oemid: [6]u8, 8 | revision: u8, 9 | rsdt_addr: u32, 10 | 11 | extended_length: u32, 12 | xsdt_addr: u64, 13 | extended_checksum: u8, 14 | }; 15 | 16 | fn signature(name: []const u8) u32 { 17 | return std.mem.readInt(u32, name[0..4], sabaton.endian); 18 | } 19 | 20 | fn print_sig(table: []const u8) void { 21 | var offset: usize = 0; 22 | while (offset < 4) : (offset += 1) { 23 | sabaton.putchar(table[offset]); 24 | } 25 | } 26 | 27 | fn fixup_root(comptime T: type, root_table: []u8, acpi_tables_c: []u8) void { 28 | var acpi_tables = acpi_tables_c; 29 | var offset: u64 = 36; 30 | 31 | while (acpi_tables.len > 8) { 32 | const len = std.mem.readInt(u32, acpi_tables[4..8], sabaton.endian); 33 | if (len == 0) break; 34 | const sig = signature(acpi_tables); 35 | 36 | if (sabaton.debug) { 37 | sabaton.puts("Got table with signature "); 38 | print_sig(acpi_tables); 39 | sabaton.putchar('\n'); 40 | } 41 | 42 | switch (sig) { 43 | // Ignore root tables 44 | signature("RSDT"), signature("XSDT") => {}, 45 | 46 | // Nope, we don't point to this one either apparently. 47 | // https://github.com/qemu/qemu/blob/d0dddab40e472ba62b5f43f11cc7dba085dabe71/hw/arm/virt-acpi-build.c#L693 48 | signature("DSDT") => {}, 49 | 50 | else => { 51 | // We add everything else 52 | // sabaton.log_hex("At offset ", offset); 53 | if (offset + @sizeOf(T) > root_table.len) { 54 | if (sabaton.debug) { 55 | sabaton.log_hex("Root table size is ", root_table.len); 56 | sabaton.puts("Can't fit this table pointer! :(\n"); 57 | break; 58 | } 59 | } else { 60 | const ptr_bytes = root_table[offset..][0..@sizeOf(T)]; 61 | const table_ptr = @intCast(u32, @ptrToInt(acpi_tables.ptr)); 62 | std.mem.writeInt(T, ptr_bytes, table_ptr, sabaton.endian); 63 | offset += @sizeOf(T); 64 | } 65 | }, 66 | } 67 | acpi_tables = acpi_tables[len..]; 68 | } 69 | 70 | std.mem.writeInt(u32, root_table[4..][0..4], @intCast(u32, offset), sabaton.endian); 71 | } 72 | 73 | fn fixup_fadt(fadt: []u8, dsdt: []u8) void { 74 | // We have both a FADT and DSDT on ACPI >= 2, so 75 | // The FADT needs to point to the DSDT 76 | const dsdt_addr = @ptrToInt(dsdt.ptr); 77 | // Offsets: https://gcc.godbolt.org/z/KPWMaMqe5 78 | std.mem.writeIntNative(u64, fadt[140..][0..8], dsdt_addr); 79 | if (dsdt_addr < (1 << 32)) 80 | std.mem.writeIntNative(u32, fadt[40..][0..4], @truncate(u32, dsdt_addr)); 81 | } 82 | 83 | pub fn init(rsdp: []u8, tables_c: []u8) void { 84 | if (rsdp.len < @sizeOf(RSDP)) { 85 | if (sabaton.debug) 86 | sabaton.puts("RSDP too small, can't add."); 87 | return; 88 | } 89 | 90 | // Fixup RSDP 91 | const rsdp_val = @intToPtr(*RSDP, @ptrToInt(rsdp.ptr)); 92 | 93 | var got_root = false; 94 | 95 | var fadt_opt: ?[]u8 = null; 96 | var dsdt_opt: ?[]u8 = null; 97 | 98 | var tables = tables_c; 99 | while (tables.len > 8) { 100 | const len = std.mem.readInt(u32, tables[4..8], sabaton.endian); 101 | if (len == 0) break; 102 | const table = tables[0..len]; 103 | const sig = signature(table); 104 | 105 | switch (sig) { 106 | signature("RSDT") => { 107 | sabaton.puts("Found RSDT!\n"); 108 | rsdp_val.rsdt_addr = @intCast(u32, @ptrToInt(table.ptr)); 109 | got_root = true; 110 | fixup_root(u32, table, tables_c); 111 | }, 112 | signature("XSDT") => { 113 | sabaton.puts("Found XSDT!\n"); 114 | rsdp_val.xsdt_addr = @intCast(u64, @ptrToInt(table.ptr)); 115 | got_root = true; 116 | fixup_root(u64, table, tables_c); 117 | }, 118 | signature("FACP") => fadt_opt = table, 119 | signature("DSDT") => dsdt_opt = table, 120 | else => {}, 121 | } 122 | 123 | tables = tables[len..]; 124 | } 125 | 126 | if (got_root) { 127 | if (sabaton.debug) { 128 | sabaton.puts("Adding RSDP\n"); 129 | sabaton.log("RSDP: {}\n", .{rsdp_val.*}); 130 | } 131 | sabaton.add_rsdp(@ptrToInt(rsdp.ptr)); 132 | 133 | if (rsdp_val.revision >= 2) { 134 | if (fadt_opt) |fadt| { 135 | if (dsdt_opt) |dsdt| { 136 | fixup_fadt(fadt, dsdt); 137 | } 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/platform/cache.zig: -------------------------------------------------------------------------------- 1 | pub fn flushCacheline(comptime icache: bool, comptime dcache: bool, addr: u64) void { 2 | if (comptime (icache)) { 3 | asm volatile ( 4 | \\ IC IVAU, %[ptr] 5 | : 6 | : [ptr] "r" (addr) 7 | ); 8 | } 9 | 10 | if (comptime (dcache)) { 11 | asm volatile ( 12 | \\ DC CIVAC, %[ptr] 13 | : 14 | : [ptr] "r" (addr) 15 | ); 16 | } 17 | } 18 | 19 | const cacheline_size = @import("root").cacheline_size; 20 | 21 | pub fn flush(comptime icache: bool, comptime dcache: bool, addr_c: u64, size_c: u64) void { 22 | var addr = addr_c & ~@as(u64, cacheline_size - 1); 23 | var size = size_c + cacheline_size - 1; 24 | 25 | while (size >= cacheline_size) : ({ 26 | addr += cacheline_size; 27 | size -= cacheline_size; 28 | }) { 29 | flushCacheline(icache, dcache, addr); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/platform/drivers/fw_cfg.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | const std = @import("std"); 3 | 4 | var base: ?u64 = null; 5 | 6 | const File = packed struct { 7 | size: u32, 8 | select: u16, 9 | reserved: u16, 10 | name: [56]u8, 11 | 12 | pub fn write(self: *const @This(), buffer: []const u8) void { 13 | do_dma(@intToPtr([*]u8, @ptrToInt(buffer.ptr))[0..buffer.len], (1 << 4) | (1 << 3) | (@as(u32, self.select) << 16)); 14 | } 15 | 16 | pub fn read(self: *const @This(), buffer: []u8) void { 17 | do_dma(buffer, (1 << 1) | (1 << 3) | (@as(u32, self.select) << 16)); 18 | } 19 | }; 20 | 21 | pub fn init_from_dtb() void { 22 | const fw_cfg = sabaton.dtb.find("fw-cfg@", "reg") catch return; 23 | 24 | base = std.mem.readIntBig(u64, fw_cfg[0..8]); 25 | 26 | if (sabaton.debug) 27 | sabaton.log_hex("fw_cfg base is stored at ", &base); 28 | 29 | const data = @intToPtr(*volatile u64, base.?); 30 | const selector = @intToPtr(*volatile u16, base.? + 8); 31 | const dma_addr = @intToPtr(*volatile u64, base.? + 16); 32 | 33 | if (sabaton.safety) { 34 | selector.* = std.mem.nativeToBig(u16, 0); 35 | std.debug.assert(@truncate(u32, data.*) == 0x554D4551); // 'QEMU' 36 | 37 | selector.* = std.mem.nativeToBig(u16, 1); 38 | std.debug.assert(@truncate(u32, data.*) & 2 != 0); // DMA bit 39 | std.debug.assert(std.mem.bigToNative(u64, dma_addr.*) == 0x51454d5520434647); 40 | } 41 | } 42 | 43 | const DMAAccess = packed struct { 44 | control: u32, 45 | length: u32, 46 | addr: u64, 47 | }; 48 | 49 | pub fn do_dma(buffer: []u8, control: u32) void { 50 | const dma_addr = @intToPtr(*volatile u64, base.? + 16); 51 | var access_bytes: [@sizeOf(DMAAccess)]u8 = undefined; 52 | var access = @ptrCast(*volatile DMAAccess, &access_bytes[0]); 53 | access.* = .{ 54 | .control = std.mem.nativeToBig(u32, control), 55 | .length = std.mem.nativeToBig(u32, @intCast(u32, buffer.len)), 56 | .addr = std.mem.nativeToBig(u64, @ptrToInt(buffer.ptr)), 57 | }; 58 | dma_addr.* = std.mem.nativeToBig(u64, @ptrToInt(access)); 59 | asm volatile ("" ::: "memory"); 60 | if (sabaton.safety) { 61 | while (true) { 62 | const ctrl = std.mem.bigToNative(u32, access.control); 63 | if (ctrl & 1 != 0) 64 | @panic("fw_cfg dma error!"); 65 | if (ctrl == 0) 66 | return; 67 | 68 | sabaton.puts("Still waiting...\n"); 69 | } 70 | } 71 | } 72 | 73 | pub fn get_variable(comptime T: type, selector: u16) T { 74 | var result: T = undefined; 75 | get_bytes(std.mem.asBytes(&result), selector); 76 | return result; 77 | } 78 | 79 | pub fn get_more_variable(comptime T: type) T { 80 | var result: T = undefined; 81 | get_more_bytes(std.mem.asBytes(&result)); 82 | return result; 83 | } 84 | 85 | pub fn get_bytes(buffer: []u8, selector: u16) void { 86 | do_dma(buffer, (1 << 1) | (1 << 3) | @as(u32, selector) << 16); 87 | } 88 | 89 | pub fn get_more_bytes(buffer: []u8) void { 90 | do_dma(buffer, (1 << 1)); 91 | } 92 | 93 | pub fn find_file(filename: []const u8) ?File { 94 | // Get number of files 95 | const num_files = std.mem.bigToNative(u32, get_variable(u32, 0x0019)); 96 | 97 | var current_file: u32 = 0; 98 | while (current_file < num_files) : (current_file += 1) { 99 | // Get a file at a time 100 | 101 | var f = get_more_variable(File); 102 | f.size = std.mem.bigToNative(u32, f.size); 103 | f.select = std.mem.bigToNative(u16, f.select); 104 | 105 | if (sabaton.debug) { 106 | sabaton.puts("fw cfg file:\n"); 107 | sabaton.log_hex(" size: ", f.size); 108 | sabaton.puts(" filename: "); 109 | sabaton.puts(@ptrCast([*:0]u8, &f.name[0])); 110 | sabaton.putchar('\n'); 111 | } 112 | 113 | if (std.mem.eql(u8, filename, f.name[0..filename.len]) and f.name[filename.len] == 0) { 114 | return f; 115 | } 116 | } 117 | 118 | return null; 119 | } 120 | -------------------------------------------------------------------------------- /src/platform/drivers/ramfb.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | const std = @import("std"); 3 | 4 | fn combine(a: u8, b: u8, c: u8, d: u8) u32 { 5 | // zig fmt: off 6 | return 0 7 | | (@as(u32, a) << 0) 8 | | (@as(u32, b) << 8) 9 | | (@as(u32, c) << 16) 10 | | (@as(u32, d) << 24) 11 | ; 12 | // zig fmt: on 13 | } 14 | 15 | pub fn init() bool { 16 | if (sabaton.fw_cfg.find_file("etc/ramfb")) |ramfb| { 17 | const framebuffer = @ptrToInt(sabaton.pmm.alloc_aligned(sabaton.fb_bytes, .Hole).ptr); 18 | 19 | var cfg: packed struct { 20 | addr: u64 = undefined, 21 | fourcc: u32 = std.mem.nativeToBig(u32, combine('X', 'R', '2', '4')), 22 | flags: u32 = std.mem.nativeToBig(u32, 0), 23 | width: u32 = std.mem.nativeToBig(u32, sabaton.fb_width), 24 | height: u32 = std.mem.nativeToBig(u32, sabaton.fb_height), 25 | stride: u32 = std.mem.nativeToBig(u32, sabaton.fb_pitch), 26 | } = .{}; 27 | 28 | if (sabaton.safety) { 29 | if (ramfb.size != @sizeOf(@TypeOf(cfg))) { 30 | sabaton.log_hex("Bad ramfb file size: ", ramfb.size); 31 | unreachable; 32 | } 33 | } 34 | 35 | cfg.addr = std.mem.nativeToBig(u64, framebuffer); 36 | 37 | var cfg_bytes = @intToPtr([*]u8, @ptrToInt(&cfg))[0..@sizeOf(@TypeOf(cfg))]; 38 | ramfb.write(cfg_bytes); 39 | cfg.addr = 0; 40 | ramfb.read(cfg_bytes); 41 | 42 | const resaddr = std.mem.bigToNative(u64, cfg.addr); 43 | 44 | if (sabaton.safety) { 45 | if (resaddr != framebuffer) { 46 | sabaton.log_hex("ramfb: Unable to set framebuffer: ", resaddr); 47 | return false; 48 | } 49 | } 50 | 51 | sabaton.add_framebuffer(framebuffer); 52 | 53 | return true; 54 | } 55 | 56 | return false; 57 | } 58 | -------------------------------------------------------------------------------- /src/platform/drivers/virt.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | const std = @import("std"); 3 | 4 | pub const io = sabaton.io_impl.uart_mmio_32; 5 | 6 | pub const acpi = struct { 7 | pub fn init() void { 8 | if (sabaton.fw_cfg.find_file("etc/acpi/tables")) |tables| { 9 | if (sabaton.fw_cfg.find_file("etc/acpi/rsdp")) |rsdp| { 10 | const rsdp_bytes = sabaton.pmm.alloc_aligned(rsdp.size, .Hole); 11 | const table_bytes = sabaton.pmm.alloc_aligned(tables.size, .Hole); 12 | 13 | rsdp.read(rsdp_bytes); 14 | tables.read(table_bytes); 15 | 16 | sabaton.acpi.init(rsdp_bytes, table_bytes); 17 | } 18 | } 19 | } 20 | }; 21 | 22 | pub const display = struct { 23 | fn try_find(comptime f: anytype, comptime name: []const u8) bool { 24 | const retval = f(); 25 | if (retval) { 26 | sabaton.puts("Found " ++ name ++ "!\n"); 27 | } else { 28 | sabaton.puts("Couldn't find " ++ name ++ "\n"); 29 | } 30 | return retval; 31 | } 32 | 33 | pub fn init() void { 34 | // First, try to find a ramfb 35 | if (try_find(sabaton.ramfb.init, "ramfb")) 36 | return; 37 | 38 | sabaton.puts("Kernel requested framebuffer but we could not provide one!\n"); 39 | } 40 | }; 41 | 42 | pub const smp = struct { 43 | pub fn init() void { 44 | sabaton.dtb.psci_smp(.HVC); 45 | } 46 | }; 47 | 48 | const FW_CFG_KERNEL_SIZE = 0x08; 49 | const FW_CFG_KERNEL_DATA = 0x11; 50 | 51 | pub fn get_kernel() [*]u8 { 52 | if (sabaton.fw_cfg.find_file("opt/Sabaton/kernel")) |kernel| { 53 | sabaton.log_hex("fw_cfg reported opt/sabaton/kernel size of ", kernel.size); 54 | const kernel_bytes = sabaton.pmm.alloc_aligned(kernel.size, .ReclaimableData); 55 | sabaton.puts("Reading kernel file into allocated buffer!\n"); 56 | kernel.read(kernel_bytes); 57 | sabaton.puts("Kernel get!\n"); 58 | return kernel_bytes.ptr; 59 | } 60 | 61 | { 62 | const ksize = sabaton.fw_cfg.get_variable(u32, FW_CFG_KERNEL_SIZE); 63 | sabaton.log_hex("fw_cfg reported -kernel file size ", ksize); 64 | const kernel_bytes = sabaton.pmm.alloc_aligned(ksize, .ReclaimableData); 65 | sabaton.puts("Reading kernel file into allocated buffer!\n"); 66 | sabaton.fw_cfg.get_bytes(kernel_bytes, FW_CFG_KERNEL_DATA); 67 | sabaton.puts("Kernel get!\n"); 68 | return kernel_bytes.ptr; 69 | } 70 | @panic("Kernel not found using fw_cfg!"); 71 | } 72 | 73 | pub fn get_dram() []u8 { 74 | return sabaton.near("dram_base").read([*]u8)[0..get_dram_size()]; 75 | } 76 | 77 | // Dram size varies as you can set different amounts of RAM for your VM 78 | fn get_dram_size() u64 { 79 | const memory_blob = sabaton.vital(sabaton.dtb.find("memory@", "reg"), "Cannot find memory in dtb", false); 80 | const base = std.mem.readIntBig(u64, memory_blob[0..8]); 81 | const size = std.mem.readIntBig(u64, memory_blob[8..16]); 82 | 83 | if (sabaton.safety and base != sabaton.near("dram_base").read(u64)) { 84 | sabaton.log_hex("dtb has wrong memory base: ", base); 85 | unreachable; 86 | } 87 | 88 | return size; 89 | } 90 | 91 | pub fn add_platform_tags(kernel_header: *sabaton.Stivale2hdr) void { 92 | _ = kernel_header; 93 | sabaton.add_tag(&sabaton.near("uart_tag").addr(sabaton.Stivale2tag)[0]); 94 | sabaton.add_tag(&sabaton.near("devicetree_tag").addr(sabaton.Stivale2tag)[0]); 95 | } 96 | -------------------------------------------------------------------------------- /src/platform/paging.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | 3 | const pte = u64; 4 | const table_ptr = [*]pte; 5 | 6 | pub const Perms = enum(u3) { 7 | none = 0, 8 | x = 1, 9 | w = 2, 10 | r = 4, 11 | 12 | rw = 6, 13 | rwx = 7, 14 | rx = 5, 15 | }; 16 | 17 | pub const MemoryType = enum { 18 | writeback, 19 | mmio, 20 | writethrough, 21 | }; 22 | 23 | pub const Root = switch(sabaton.arch) { 24 | .aarch64 => struct { 25 | ttbr0: table_ptr, 26 | ttbr1: table_ptr, 27 | }, 28 | else => struct { 29 | root: table_ptr, 30 | }, 31 | }; 32 | 33 | fn make_table_at(e: *pte) table_ptr { 34 | switch (decode(e.*, false)) { 35 | .Mapping => unreachable, 36 | .Table => { 37 | switch(comptime sabaton.arch) { 38 | .riscv64 => return @intToPtr(table_ptr, (e.* & 0x0000FFFFFFFFFC00) << 2), 39 | else => return @intToPtr(table_ptr, e.* & 0x0000FFFFFFFFF000), 40 | } 41 | }, 42 | .Empty => { 43 | const page_size = sabaton.platform.get_page_size(); 44 | const ret = sabaton.pmm.alloc_aligned(page_size, .PageTable); 45 | //sabaton.log_hex("Allocated new table at ", ret.ptr); 46 | e.* = switch(comptime(sabaton.arch)) { 47 | .aarch64 => @ptrToInt(ret.ptr) | 1 << 63 | 3, 48 | .riscv64 => (@ptrToInt(ret.ptr) >> 2) | 1, 49 | else => @compileError("implement make_table_at for " ++ @tagName(sabaton.arch)), 50 | }; 51 | return @ptrCast(table_ptr, ret.ptr); 52 | }, 53 | } 54 | } 55 | 56 | fn get_index(vaddr: u64, base_bits: u6, level: u64) usize { 57 | const shift_bits = @intCast(u6, base_bits + (base_bits - 3) * level); 58 | return (vaddr >> shift_bits) & ((@as(u64, 1) << (base_bits - 3)) - 1); 59 | } 60 | 61 | fn extra_bits(perm: Perms, mt: MemoryType, page_size: usize, botlevel: bool) u64 { 62 | switch(comptime(sabaton.arch)) { 63 | .aarch64 => { 64 | var bits: u64 = 0x1 | (1 << 5) | (1 << 10); 65 | 66 | // Set the walk bit 67 | if (page_size < 0x10000 and botlevel) bits |= 2; 68 | 69 | if (@enumToInt(perm) & @enumToInt(Perms.w) == 0) bits |= 1 << 7; 70 | if (@enumToInt(perm) & @enumToInt(Perms.x) == 0) bits |= 1 << 54; 71 | bits |= switch (mt) { 72 | .writeback => @as(u64, 0 << 2 | 2 << 8 | 1 << 11), 73 | .mmio => @as(u64, 1 << 2 | 2 << 8), 74 | .writethrough => @as(u64, 2 << 2 | 2 << 8 | 1 << 11), 75 | }; 76 | return bits; 77 | }, 78 | 79 | .riscv64 => { 80 | var bits: u64 = 0x1; 81 | 82 | if(@enumToInt(perm) & @enumToInt(Perms.r) != 0) bits |= 1 << 1; 83 | if(@enumToInt(perm) & @enumToInt(Perms.w) != 0) bits |= 1 << 2; 84 | if(@enumToInt(perm) & @enumToInt(Perms.x) != 0) bits |= 1 << 3; 85 | 86 | if(sabaton.safety) { 87 | if(bits == 1) 88 | @panic("No permission bits were set!"); 89 | } 90 | 91 | return bits; 92 | }, 93 | 94 | else => @compileError("implement extra_bits for " ++ @tagName(sabaton.arch)), 95 | } 96 | } 97 | 98 | fn make_mapping_at(ent: *pte, paddr: u64, bits: u64) void { 99 | switch(comptime(sabaton.arch)) { 100 | .riscv64 => ent.* = (paddr >> 2) | bits, 101 | else => ent.* = paddr | bits, 102 | } 103 | } 104 | 105 | pub fn detect_page_size() u64 { 106 | var aa64mmfr0 = asm volatile ("MRS %[reg], ID_AA64MMFR0_EL1\n\t" 107 | : [reg] "=r" (-> u64) 108 | ); 109 | 110 | var psz: u64 = undefined; 111 | 112 | if (((aa64mmfr0 >> 28) & 0x0F) == 0b0000) { 113 | psz = 0x1000; 114 | } else if (((aa64mmfr0 >> 20) & 0x0F) == 0b0001) { 115 | psz = 0x4000; 116 | } else if (((aa64mmfr0 >> 24) & 0x0F) == 0b0000) { 117 | psz = 0x10000; 118 | } else if (sabaton.safety) { 119 | @panic("Unknown page size!"); 120 | } else { 121 | unreachable; 122 | } 123 | return psz; 124 | } 125 | 126 | pub fn init_paging() Root { 127 | const page_size = sabaton.platform.get_page_size(); 128 | return switch(comptime(sabaton.arch)) { 129 | .aarch64 => .{ 130 | .ttbr0 = @ptrCast(table_ptr, sabaton.pmm.alloc_aligned(page_size, .PageTable)), 131 | .ttbr1 = @ptrCast(table_ptr, sabaton.pmm.alloc_aligned(page_size, .PageTable)), 132 | }, 133 | else => .{ 134 | .root = @ptrCast(table_ptr, sabaton.pmm.alloc_aligned(page_size, .PageTable)), 135 | }, 136 | }; 137 | } 138 | 139 | fn can_map(size: u64, vaddr: u64, paddr: u64, large_step: u64) bool { 140 | switch(comptime(sabaton.arch)) { 141 | .aarch64 => { 142 | if (large_step > 0x40000000) 143 | return false; 144 | }, 145 | else => {}, 146 | } 147 | 148 | if (size < large_step) 149 | return false; 150 | const mask = large_step - 1; 151 | if (vaddr & mask != 0) 152 | return false; 153 | if (paddr & mask != 0) 154 | return false; 155 | return true; 156 | } 157 | 158 | fn choose_root(r: *const Root, vaddr: u64) table_ptr { 159 | return switch(comptime(sabaton.arch)) { 160 | .aarch64 => { 161 | if (sabaton.util.upper_half(vaddr)) { 162 | return r.ttbr1; 163 | } else { 164 | return r.ttbr0; 165 | } 166 | }, 167 | else => r.root, 168 | }; 169 | } 170 | 171 | pub fn current_root() Root { 172 | return switch(comptime(sabaton.arch)) { 173 | .aarch64 => .{ 174 | .ttbr0 = asm ("MRS %[br0], TTBR0_EL1" 175 | : [br0] "=r" (-> table_ptr) 176 | ), 177 | .ttbr1 = asm ("MRS %[br1], TTBR1_EL1" 178 | : [br1] "=r" (-> table_ptr) 179 | ), 180 | }, 181 | 182 | .riscv64 => .{ 183 | .root = @intToPtr(table_ptr, @as(u64, asm("mcrr %[root], satp" 184 | : [root] "=r" (->u44) 185 | )) << 12), 186 | }, 187 | 188 | else => @compileError("Implement current_root for " + @tagName(sabaton.arch)), 189 | }; 190 | } 191 | 192 | const Decoded = enum { 193 | Mapping, 194 | Table, 195 | Empty, 196 | }; 197 | 198 | pub fn decode(e: pte, bottomlevel: bool) Decoded { 199 | switch(comptime(sabaton.arch)) { 200 | .aarch64 => { 201 | if (e & 1 == 0) 202 | return .Empty; 203 | if (bottomlevel or e & 2 == 0) 204 | return .Mapping; 205 | return .Table; 206 | }, 207 | 208 | .riscv64 => { 209 | if(e & 1 == 0) 210 | return .Empty; 211 | if((e & (0b111 << 1)) == 0) // RWX = 000 212 | return .Table; 213 | return .Mapping; 214 | }, 215 | 216 | else => @compileError("Implement decode for " + @tagName(sabaton.arch)), 217 | } 218 | } 219 | 220 | pub fn map(vaddr_c: u64, paddr_c: u64, size_c: u64, perm: Perms, mt: MemoryType, in_root: *Root) void { 221 | const page_size = sabaton.platform.get_page_size(); 222 | var vaddr = vaddr_c; 223 | var paddr = paddr_c; 224 | var size = size_c; 225 | size += page_size - 1; 226 | size &= ~(page_size - 1); 227 | 228 | const root = choose_root(in_root, vaddr); 229 | 230 | const levels: usize = switch(comptime(sabaton.arch)) { 231 | .aarch64 => @as(u64, switch (page_size) { 232 | 0x1000, 0x4000 => 4, 233 | 0x10000 => 3, 234 | else => unreachable, 235 | }), 236 | .riscv64 => 4, 237 | else => @compileError("Implement levels for " + @tagName(sabaton.arch)), 238 | }; 239 | 240 | const base_bits = @intCast(u6, @ctz(u64, page_size)); 241 | 242 | const small_bits = extra_bits(perm, mt, page_size, true); 243 | const large_bits = extra_bits(perm, mt, page_size, false); 244 | 245 | while (size != 0) { 246 | var current_step_size = page_size << @intCast(u6, (base_bits - 3) * (levels - 1)); 247 | var level = levels - 1; 248 | var current_table = root; 249 | 250 | while (true) { 251 | const ind = get_index(vaddr, base_bits, level); 252 | // We can only map at this level if it's not a table 253 | switch (decode(current_table[ind], level == 0)) { 254 | .Mapping => { 255 | sabaton.log_hex("Overlapping mapping at ", vaddr); 256 | sabaton.log_hex("PTE is ", current_table[ind]); 257 | @panic("Overlapping mapping"); 258 | }, 259 | .Table => {}, // Just iterate to the next level 260 | .Empty => { 261 | // If we can map at this level, do so 262 | if (can_map(size, vaddr, paddr, current_step_size)) { 263 | const bits = if (level == 0) small_bits else large_bits; 264 | make_mapping_at(¤t_table[ind], paddr, bits); 265 | break; 266 | } 267 | // Otherwise, just iterate to the next level 268 | }, 269 | } 270 | 271 | if (level == 0) 272 | unreachable; 273 | 274 | current_table = make_table_at(¤t_table[ind]); 275 | current_step_size >>= (base_bits - 3); 276 | level -= 1; 277 | } 278 | 279 | vaddr += current_step_size; 280 | paddr += current_step_size; 281 | size -= current_step_size; 282 | } 283 | } 284 | 285 | pub fn apply_paging(r: *Root) void { 286 | switch(comptime (sabaton.arch)) { 287 | .aarch64 => { 288 | var sctlr = asm ( 289 | \\MRS %[sctlr], SCTLR_EL1 290 | : [sctlr] "=r" (-> u64) 291 | ); 292 | var aa64mmfr0 = asm ( 293 | \\MRS %[id], ID_AA64MMFR0_EL1 294 | : [id] "=r" (-> u64) 295 | ); 296 | 297 | // Documentation? Nah, be a professional guesser. 298 | sctlr |= 1; 299 | 300 | aa64mmfr0 &= 0x0F; 301 | if (aa64mmfr0 > 5) 302 | aa64mmfr0 = 5; 303 | 304 | var paging_granule_br0: u64 = undefined; 305 | var paging_granule_br1: u64 = undefined; 306 | var region_size_offset: u64 = undefined; 307 | 308 | switch (sabaton.platform.get_page_size()) { 309 | 0x1000 => { 310 | paging_granule_br0 = 0b00; 311 | paging_granule_br1 = 0b10; 312 | region_size_offset = 16; 313 | }, 314 | 0x4000 => { 315 | paging_granule_br0 = 0b10; 316 | paging_granule_br1 = 0b01; 317 | region_size_offset = 8; 318 | }, 319 | 0x10000 => { 320 | paging_granule_br0 = 0b01; 321 | paging_granule_br1 = 0b11; 322 | region_size_offset = 0; 323 | }, 324 | else => unreachable, 325 | } 326 | 327 | // zig fmt: off 328 | const tcr: u64 = 0 329 | | (region_size_offset << 0) // T0SZ 330 | | (region_size_offset << 16) // T1SZ 331 | | (1 << 8) // TTBR0 Inner WB RW-Allocate 332 | | (1 << 10) // TTBR0 Outer WB RW-Allocate 333 | | (1 << 24) // TTBR1 Inner WB RW-Allocate 334 | | (1 << 26) // TTBR1 Outer WB RW-Allocate 335 | | (2 << 12) // TTBR0 Inner shareable 336 | | (2 << 28) // TTBR1 Inner shareable 337 | | (aa64mmfr0 << 32) // intermediate address size 338 | | (paging_granule_br0 << 14) // TTBR0 granule 339 | | (paging_granule_br1 << 30) // TTBR1 granule 340 | | (1 << 56) // Fault on TTBR1 access from EL0 341 | | (0 << 55) // Don't fault on TTBR0 access from EL0 342 | ; 343 | 344 | const mair: u64 = 0 345 | | (0b11111111 << 0) // Normal, Write-back RW-Allocate non-transient 346 | | (0b00000000 << 8) // Device, nGnRnE 347 | ; 348 | // zig fmt: on 349 | 350 | if (sabaton.debug) { 351 | sabaton.log("Enabling paging... ", .{}); 352 | } 353 | 354 | asm volatile ( 355 | \\MSR TTBR0_EL1, %[ttbr0] 356 | \\MSR TTBR1_EL1, %[ttbr1] 357 | \\MSR MAIR_EL1, %[mair] 358 | \\MSR TCR_EL1, %[tcr] 359 | \\MSR SCTLR_EL1, %[sctlr] 360 | \\DSB SY 361 | \\ISB SY 362 | : 363 | : [ttbr0] "r" (r.ttbr0), 364 | [ttbr1] "r" (r.ttbr1), 365 | [sctlr] "r" (sctlr), 366 | [tcr] "r" (tcr), 367 | [mair] "r" (mair) 368 | : "memory" 369 | ); 370 | }, 371 | 372 | .riscv64 => { 373 | const mode = 9; // 48-bit virtual addressing 374 | 375 | const satp = 0 376 | | @ptrToInt(r.root) >> 12 377 | | mode << 60 378 | ; 379 | 380 | asm volatile ( 381 | \\CSRW satp, %[satp] 382 | : 383 | : [satp] "r" (satp), 384 | : "memory" 385 | ); 386 | }, 387 | 388 | else => @compileError("Implement apply_paging for " ++ @tagName(sabaton.arch)), 389 | } 390 | 391 | if (sabaton.debug) { 392 | sabaton.log("Paging enabled!\n", .{}); 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /src/platform/pci.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | const std = @import("std"); 3 | 4 | var bus0base: usize = undefined; 5 | var bus0size: usize = undefined; 6 | var bar32base: usize = undefined; 7 | var bar64base: usize = undefined; 8 | 9 | fn pci_bars_callback(dev: Addr) bool { 10 | const header_type = dev.get_header_type(); 11 | const num_bars: u8 = switch (header_type & 0x7F) { 12 | 0x00 => 6, 13 | 0x01 => 2, 14 | else => 0, 15 | }; 16 | 17 | var bar_idx: u8 = 0; 18 | while (bar_idx < num_bars) : (bar_idx += 1) { 19 | const bar_bits = dev.read(u32, 0x10 + bar_idx * 4); 20 | dev.write(u32, 0x10 + bar_idx * 4, 0xFFFFFFFF); 21 | const bar_value = dev.read(u32, 0x10 + bar_idx * 4); 22 | 23 | if (bar_bits & 1 != 0) 24 | continue; // Not a memory BAR 25 | 26 | const is64 = ((bar_value & 0b110) >> 1) == 2; 27 | 28 | var bar_size = @as(u64, bar_value & 0xFFFFFFF0); 29 | if (is64) { 30 | dev.write(u32, 0x10 + (bar_idx + 1) * 4, 0xFFFFFFFF); 31 | bar_size |= @as(u64, dev.read(u32, 0x10 + (bar_idx + 1) * 4)) << 32; 32 | } 33 | 34 | // Negate BAR size 35 | bar_size = ~bar_size +% 1; 36 | 37 | if (!is64) { 38 | bar_size &= (1 << 32) - 1; 39 | } 40 | 41 | if (bar_size == 0) 42 | continue; 43 | 44 | var base = if (is64) &bar64base else &bar32base; 45 | 46 | // Align to BAR size 47 | base.* += bar_size - 1; 48 | base.* &= ~(bar_size - 1); 49 | 50 | if (sabaton.debug) { 51 | if (is64) { 52 | sabaton.puts("64 bit BAR: \n"); 53 | } else { 54 | sabaton.puts("32 bit BAR: \n"); 55 | } 56 | sabaton.log_hex(" BAR index: ", bar_idx); 57 | sabaton.log_hex(" BAR bits: ", bar_bits); 58 | sabaton.log_hex(" BAR size: ", bar_size); 59 | sabaton.log_hex(" BAR addr: ", base.*); 60 | } 61 | 62 | // Write BAR 63 | dev.write(u32, 0x10 + bar_idx * 4, @truncate(u32, base.*) | bar_bits); 64 | if (is64) { 65 | dev.write(u32, 0x10 + (bar_idx + 1) * 4, @truncate(u32, base.* >> 32)); 66 | } 67 | dev.write(u16, 4, 1 << 1); 68 | 69 | // Increment BAR pointer 70 | base.* += bar_size; 71 | 72 | bar_idx += @boolToInt(is64); 73 | } 74 | 75 | // We never want to stop iterating 76 | return false; 77 | } 78 | 79 | pub fn init_from_dtb(root: *sabaton.paging.Root) void { 80 | const pci_node_prefix = comptime switch(sabaton.arch) { 81 | .aarch64 => "pcie@", 82 | .riscv64 => "pci@", 83 | else => @compileError("Implement pci.init_from_dtb for " ++ @tagName(sabaton.arch)), 84 | }; 85 | const pci_blob = sabaton.vital(sabaton.dtb.find(pci_node_prefix, "reg"), "Cannot find pci base dtb", true); 86 | 87 | bus0base = std.mem.readIntBig(u64, pci_blob[0..][0..8]); 88 | bus0size = std.mem.readIntBig(u64, pci_blob[8..][0..8]); 89 | 90 | if (sabaton.debug) { 91 | sabaton.log_hex("PCI config space base: ", bus0base); 92 | sabaton.log_hex("PCI config space size: ", bus0size); 93 | } 94 | 95 | sabaton.paging.map(bus0base, bus0base, bus0size, .rw, .mmio, root); 96 | sabaton.paging.map(bus0base + sabaton.upper_half_phys_base, bus0base, bus0size, .rw, .mmio, root); 97 | 98 | const bar_blob = sabaton.vital(sabaton.dtb.find(pci_node_prefix, "ranges"), "Cannot find pci ranges dtb", true); 99 | bar32base = std.mem.readIntBig(u64, bar_blob[0x28..][0..8]); 100 | bar64base = std.mem.readIntBig(u64, bar_blob[0x3C..][0..8]); 101 | 102 | const bar32size = std.mem.readIntBig(u64, bar_blob[0x30..][0..8]); 103 | const bar64size = std.mem.readIntBig(u64, bar_blob[0x44..][0..8]); 104 | 105 | if (sabaton.debug) { 106 | sabaton.log_hex("PCI BAR32 base: ", bar32base); 107 | sabaton.log_hex("PCI BAR32 size: ", bar32size); 108 | sabaton.log_hex("PCI BAR64 base: ", bar64base); 109 | sabaton.log_hex("PCI BAR64 size: ", bar64size); 110 | } 111 | 112 | // This should already be present in mmio region, if it's not, open an issue. 113 | // sabaton.paging.map(bar32base, bar32base, bar32size, .rw, .mmio, root); 114 | // sabaton.paging.map(bar32base + sabaton.upper_half_phys_base, bar32base, bar32size, .rw, .mmio, root); 115 | 116 | sabaton.paging.map(bar64base, bar64size, bus0size, .rw, .mmio, root); 117 | sabaton.paging.map(bar64base + sabaton.upper_half_phys_base, bar64size, bar64size, .rw, .mmio, root); 118 | 119 | _ = scan(pci_bars_callback); 120 | } 121 | 122 | pub const Addr = struct { 123 | bus: u8, 124 | device: u5, 125 | function: u3, 126 | 127 | fn mmio(self: @This(), offset: u8) u64 { 128 | return bus0base + (@as(u64, self.device) << 15 | @as(u64, self.function) << 12 | @as(u64, offset)); 129 | } 130 | 131 | pub fn read(self: @This(), comptime T: type, offset: u8) T { 132 | return @intToPtr(*volatile T, self.mmio(offset)).*; 133 | } 134 | 135 | pub fn write(self: @This(), comptime T: type, offset: u8, value: T) void { 136 | @intToPtr(*volatile T, self.mmio(offset)).* = value; 137 | } 138 | 139 | pub fn get_vendor_id(self: @This()) u16 { 140 | return self.read(u16, 0x00); 141 | } 142 | pub fn get_product_id(self: @This()) u16 { 143 | return self.read(u16, 0x02); 144 | } 145 | pub fn get_class(self: @This()) u8 { 146 | return self.read(u8, 0x0B); 147 | } 148 | pub fn get_subclass(self: @This()) u8 { 149 | return self.read(u8, 0x0A); 150 | } 151 | pub fn get_progif(self: @This()) u8 { 152 | return self.read(u8, 0x09); 153 | } 154 | pub fn get_header_type(self: @This()) u8 { 155 | return self.read(u8, 0x0E); 156 | } 157 | }; 158 | 159 | fn device_scan(callback: fn (Addr) bool, bus: u8, device: u5) bool { 160 | var addr: Addr = .{ 161 | .bus = bus, 162 | .device = device, 163 | .function = 0, 164 | }; 165 | 166 | if (addr.get_vendor_id() == 0xFFFF) 167 | return false; // Device not present, ignore 168 | 169 | if (callback(addr)) 170 | return true; 171 | 172 | if (addr.get_header_type() & 0x80 == 0) 173 | return false; // Not multifunction device, ignore 174 | 175 | addr.function += 1; 176 | 177 | while (addr.function < (1 << 3)) : (addr.function += 1) { 178 | if (callback(addr)) 179 | return true; 180 | } 181 | 182 | return false; 183 | } 184 | 185 | fn bus_scan(callback: fn (Addr) bool, bus: u8) bool { 186 | var device: usize = 0; 187 | while (device < (1 << 5)) : (device += 1) { 188 | if (device_scan(callback, bus, @truncate(u5, device))) 189 | return true; 190 | } 191 | return false; 192 | } 193 | 194 | pub fn scan(callback: fn (Addr) bool) bool { 195 | return bus_scan(callback, 0); 196 | } 197 | -------------------------------------------------------------------------------- /src/platform/pi3_aarch64/display.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | const regs = @import("regs.zig"); 3 | 4 | pub fn init() void { 5 | var slice: [35]u32 align(16) = undefined; 6 | var mbox = @intToPtr([*]volatile u32, @ptrToInt(&slice)); 7 | mbox[0] = 35 * 4; // size 8 | mbox[1] = 0; // req 9 | 10 | mbox[2] = 0x48004; // virtual width (ideally for scrolling and double buffering) 11 | mbox[3] = 8; // buffer size 12 | mbox[4] = 8; // req/resp code 13 | mbox[5] = sabaton.fb_width; // base 14 | mbox[6] = sabaton.fb_height; // size 15 | 16 | mbox[7] = 0x48009; // virtual offset 17 | mbox[8] = 8; // buffer size 18 | mbox[9] = 8; // req/resp code 19 | mbox[10] = 0; // x 20 | mbox[11] = 0; // y 21 | 22 | mbox[12] = 0x48003; // physical width 23 | mbox[13] = 8; // buffer size 24 | mbox[14] = 8; // req/resp code 25 | mbox[15] = sabaton.fb_width; // base 26 | mbox[16] = sabaton.fb_height; // size 27 | 28 | mbox[17] = 0x48005; // set bpp 29 | mbox[18] = 4; // buffer size 30 | mbox[19] = 4; // req/resp code 31 | mbox[20] = 32; // the only good bit depth 32 | 33 | mbox[21] = 0x48006; // pixel format 34 | mbox[22] = 4; // buffer size 35 | mbox[23] = 4; // req/resp code 36 | mbox[24] = 0; 37 | 38 | mbox[25] = 0x40001; // fb addr 39 | mbox[26] = 8; // buffer size 40 | mbox[27] = 8; // req/resp code 41 | mbox[28] = 4096; // req: alignment, resp: addr 42 | mbox[29] = 0; // size 43 | 44 | mbox[30] = 0x40008; // fb pitch 45 | mbox[31] = 4; // buffer size 46 | mbox[32] = 4; // req/resp code 47 | mbox[33] = 0; // pitch 48 | 49 | mbox[34] = 0; // terminator 50 | 51 | regs.mbox_call(8, @ptrToInt(mbox)); 52 | 53 | sabaton.fb.pitch = @truncate(u16, mbox[33]); 54 | sabaton.fb.width = @truncate(u16, mbox[15]); 55 | sabaton.fb.height = @truncate(u16, mbox[16]); 56 | sabaton.fb.red_mask_size = 8; 57 | sabaton.fb.green_mask_size = 8; 58 | sabaton.fb.blue_mask_size = 8; 59 | sabaton.fb.bpp = 32; 60 | 61 | sabaton.fb.red_mask_shift = 16; 62 | sabaton.fb.green_mask_shift = 8; 63 | sabaton.fb.blue_mask_shift = 0; 64 | const bus_addr = mbox[28]; 65 | const arm_addr = bus_addr & 0x3FFFFFFF; 66 | 67 | sabaton.add_framebuffer(arm_addr); 68 | } 69 | -------------------------------------------------------------------------------- /src/platform/pi3_aarch64/entry.S: -------------------------------------------------------------------------------- 1 | .global _start 2 | 3 | .section .text.entry 4 | _start: 5 | // Copy page settings to EL1 6 | MRS X1, SCTLR_EL2 7 | MSR SCTLR_EL1, X1 8 | MRS X1, TCR_EL2 9 | MSR TCR_EL1, X1 10 | MRS X1, MAIR_EL2 11 | MSR MAIR_EL1, X1 12 | MRS X1, TTBR0_EL2 13 | MSR TTBR0_EL1, X1 14 | MSR TTBR1_EL1, XZR 15 | 16 | TLBI VMALLE1 17 | 18 | BL el2_to_el1 19 | 20 | // Relocate ourselves 21 | ADR X0, _start 22 | LDR X1, relocation_base 23 | LDR X2, relocation_end 24 | 25 | .relocate_loop: 26 | CMP X1, X2 27 | B.EQ .relocate_done 28 | 29 | // I also know how to write bzero like this :) 30 | LDP X3, X4, [X0, #0x00] 31 | STP X3, X4, [X1, #0x00] 32 | 33 | ADD X0, X0, #0x10 34 | ADD X1, X1, #0x10 35 | 36 | B .relocate_loop 37 | .relocate_done: 38 | 39 | // Jump to relocated code 40 | LDR X1, relocation_base 41 | ADD X1, X1, .cont - _start 42 | BR X1 43 | 44 | .cont: 45 | MSR SPSel, #0 46 | ADR X18, _start 47 | MOV SP, X18 48 | B _main 49 | 50 | .section .text 51 | .global el2_to_el1 52 | el2_to_el1: 53 | // aarch64 in EL1 54 | ORR X1, XZR, #(1 << 31) 55 | ORR X1, X1, #(1 << 1) 56 | MSR HCR_EL2, X1 57 | 58 | // Counters in EL1 59 | MRS X1, CNTHCTL_EL2 60 | ORR X1, X1, #3 61 | MSR CNTHCTL_EL2, X1 62 | MSR CNTVOFF_EL2, XZR 63 | 64 | // FP/SIMD in EL1 65 | MOV X1, #0x33FF 66 | MSR CPTR_EL2, X1 67 | MSR HSTR_EL2, XZR 68 | MOV X1, #0x300000 69 | MSR CPACR_EL1, X1 70 | 71 | // Get the fuck out of EL2 into EL1 72 | MSR ELR_EL2, LR 73 | MOV X1, #0x3C5 74 | MSR SPSR_EL2, X1 75 | ERET 76 | 77 | .global devicetree_tag 78 | .global uart_tag 79 | .global uart_reg 80 | .section .data.stivale_tags 81 | .balign 8 82 | platform_tags: 83 | 84 | .balign 8 85 | uart_tag: 86 | .8byte 0xf77485dbfeb260f9 // u32 MMIO UART with status 87 | .8byte 0 88 | uart_reg: 89 | .8byte 0x3f215040 90 | uart_status: 91 | .8byte 0x3f215054 92 | uart_status_mask: 93 | .4byte 0x20 94 | uart_status_value: 95 | .4byte 0x20 96 | 97 | .global kernel_file_loc 98 | .section .rodata 99 | .balign 8 100 | relocation_base: 101 | .8byte __blob_base 102 | relocation_end: 103 | .8byte __blob_end 104 | -------------------------------------------------------------------------------- /src/platform/pi3_aarch64/linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | OUTPUT_FORMAT(elf64-littleaarch64) 3 | OUTPUT_ARCH(aarch64) 4 | 5 | PHDRS { 6 | none PT_NULL FLAGS(0); 7 | } 8 | __dram_base = 0; 9 | 10 | SECTIONS { 11 | /* 12 | This is actually loaded at 0x80000, but the first thing we do is relocating. 13 | It's at 0x1000 instead of 0 because the stack is allocated before _start 14 | */ 15 | . = 0x1000; 16 | .blob : { 17 | __blob_base = .; 18 | *(.text.entry) 19 | KEEP(*(.text.main)) 20 | *(.text.smp_stub) 21 | KEEP(*(.text.smp_entry)) 22 | *(.text*) 23 | 24 | . = ALIGN(8); 25 | PROVIDE(memmap_tag = .); 26 | QUAD(0x2187F79E8612DE07); /* Stivale2 memmap identifier */ 27 | QUAD(0); /* Next */ 28 | QUAD(5); /* Memory map entries */ 29 | 30 | *(.data.memmap); 31 | 32 | QUAD(0x3C000000); /* MMIO base region */ 33 | QUAD(0x04000000); 34 | LONG(2); 35 | LONG(0); 36 | 37 | *(.data*) 38 | *(.rodata*) 39 | *(.bss*) 40 | . = ALIGN(16); 41 | __blob_end = .; 42 | } 43 | 44 | . = ALIGN(4K); 45 | __pmm_base = 0x200000 + 16M; 46 | 47 | .eh_frame : { 48 | *(.eh_frame*) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/platform/pi3_aarch64/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | pub const sabaton = @import("../../sabaton.zig"); 3 | pub const io = sabaton.io_impl.status_uart_mmio_32; 4 | pub const panic = sabaton.panic; 5 | pub const display = @import("display.zig"); 6 | const regs = @import("regs.zig"); 7 | 8 | var page_size: u64 = 0x1000; 9 | pub fn get_page_size() u64 { 10 | return page_size; 11 | } 12 | 13 | export fn _main() linksection(".text.main") noreturn { 14 | regs.miniuart_init(); 15 | @call(.{ .modifier = .always_inline }, sabaton.main, .{}); 16 | } 17 | 18 | pub fn get_dram() []allowzero u8 { 19 | var slice: [8]u32 align(16) = undefined; 20 | var mbox = @intToPtr([*]volatile u32, @ptrToInt(&slice)); 21 | mbox[0] = 8 * 4; // size 22 | mbox[1] = 0; // req 23 | 24 | mbox[2] = 0x10005; // tag 25 | mbox[3] = 8; // buffer size 26 | mbox[4] = 0; // req/resp code 27 | mbox[5] = 0; // base 28 | mbox[6] = 0; // size 29 | mbox[7] = 0; // terminator 30 | 31 | regs.mbox_call(8, @ptrToInt(mbox)); 32 | const size = mbox[6]; 33 | const addr = mbox[5]; 34 | return @intToPtr([*]allowzero u8, addr)[0..size]; 35 | } 36 | 37 | pub fn get_uart_info() io.Info { 38 | return .{ 39 | .uart = regs.AUX_MU_IO, 40 | .status = regs.AUX_MU_LSR, 41 | .mask = 0x20, 42 | .value = 0x20, 43 | }; 44 | } 45 | 46 | pub fn get_kernel() [*]u8 { 47 | // TODO: this relies on the config.txt/qemu setup, replace it with a real SD driver 48 | return @intToPtr([*]u8, 0x200000); 49 | } 50 | 51 | pub fn add_platform_tags(kernel_header: *sabaton.Stivale2hdr) void { 52 | _ = kernel_header; 53 | sabaton.add_tag(&sabaton.near("uart_tag").addr(sabaton.Stivale2tag)[0]); 54 | } 55 | 56 | pub fn map_platform(root: *sabaton.paging.Root) void { 57 | sabaton.paging.map(0x3C000000, 0x3C000000, 0x4000000, .rw, .mmio, root); 58 | sabaton.paging.map(sabaton.upper_half_phys_base + 0x3C000000, 0x3C000000, 0x4000000, .rw, .mmio, root); 59 | } 60 | -------------------------------------------------------------------------------- /src/platform/pi3_aarch64/regs.zig: -------------------------------------------------------------------------------- 1 | pub const MMIO_BASE = 0x3F000000; 2 | const MMIO_BANK = RegBank.base(0x3F000000); 3 | 4 | const AUX_BANK = MMIO_BANK.sub(0x215000); 5 | pub const AUX_ENABLES = AUX_BANK.reg(0x04); 6 | pub const AUX_MU_IO = AUX_BANK.reg(0x40); 7 | pub const AUX_MU_IER = AUX_BANK.reg(0x44); 8 | pub const AUX_MU_IIR = AUX_BANK.reg(0x48); 9 | pub const AUX_MU_LCR = AUX_BANK.reg(0x4C); 10 | pub const AUX_MU_MCR = AUX_BANK.reg(0x50); 11 | pub const AUX_MU_LSR = AUX_BANK.reg(0x54); 12 | pub const AUX_MU_MSR = AUX_BANK.reg(0x58); 13 | pub const AUX_MU_SCRATCH = AUX_BANK.reg(0x5C); 14 | pub const AUX_MU_CNTL = AUX_BANK.reg(0x60); 15 | pub const AUX_MU_STAT = AUX_BANK.reg(0x64); 16 | pub const AUX_MU_BAUD = AUX_BANK.reg(0x68); 17 | 18 | const GPIO_BANK = MMIO_BANK.sub(0x200000); 19 | pub const GPFSEL0 = GPIO_BANK.reg(0x00); 20 | pub const GPFSEL1 = GPIO_BANK.reg(0x04); 21 | pub const GPPUD = GPIO_BANK.reg(0x94); 22 | pub const GPPUDCLK0 = GPIO_BANK.reg(0x98); 23 | 24 | const MBOX_BANK = MMIO_BANK.sub(0xB880); 25 | pub const MBOX_READ = MBOX_BANK.reg(0x00); 26 | pub const MBOX_CONFIG = MBOX_BANK.reg(0x1C); 27 | pub const MBOX_WRITE = MBOX_BANK.reg(0x20); 28 | pub const MBOX_STATUS = MBOX_BANK.reg(0x18); 29 | 30 | const RegBank = struct { 31 | bank_base: usize, 32 | 33 | /// Create the base register bank 34 | pub fn base(comptime bank_base: comptime_int) @This() { 35 | return .{ .bank_base = bank_base }; 36 | } 37 | 38 | /// Make a smaller register sub-bank out of a bigger one 39 | pub fn sub(self: @This(), comptime offset: comptime_int) @This() { 40 | return .{ .bank_base = self.bank_base + offset }; 41 | } 42 | 43 | /// Define a single register 44 | pub fn reg(self: @This(), comptime offset: comptime_int) *volatile u32 { 45 | return @intToPtr(*volatile u32, self.bank_base + offset); 46 | } 47 | }; 48 | 49 | pub fn mbox_call(channel: u4, ptr: usize) void { 50 | while ((MBOX_STATUS.* & 0x80000000) != 0) {} 51 | const addr = @truncate(u32, ptr) | @as(u32, channel); 52 | MBOX_WRITE.* = addr; 53 | } 54 | 55 | // This is the miniUART, it requires enable_uart=1 in config.txt 56 | pub fn miniuart_init() void { 57 | // set pins 14-15 to alt5 (miniuart). gpfsel1 handles pins 10 to 19 58 | GPFSEL1.* = gpio_fsel(14 - 10, .Alt5, gpio_fsel(15 - 10, .Alt5, GPFSEL1.*)); 59 | GPPUD.* = 0; // disable pullup, pulldown for the clocked regs 60 | delay(150); 61 | GPPUDCLK0.* = (1 << 14) | (1 << 15); // clock pins 14-15 62 | delay(150); 63 | GPPUDCLK0.* = 0; // clear clock for next usage 64 | delay(150); 65 | 66 | AUX_ENABLES.* |= 1; // enable the uart regs 67 | AUX_MU_CNTL.* = 0; // disable uart functionality to set the regs 68 | AUX_MU_IER.* = 0; // disable uart interrupts 69 | AUX_MU_LCR.* = 0b11; // 8-bit mode 70 | AUX_MU_MCR.* = 0; // RTS always high 71 | AUX_MU_IIR.* = 0xc6; 72 | AUX_MU_BAUD.* = minuart_calculate_baud(115200); 73 | AUX_MU_CNTL.* = 0b11; // enable tx and rx fifos 74 | } 75 | 76 | const VC4_CLOCK = 250 * 1000 * 1000; // 250 MHz 77 | 78 | fn minuart_calculate_baud(baudrate: u32) u32 { 79 | return VC4_CLOCK / (8 * baudrate) - 1; // the bcm2835 spec gives this formula: baudrate = vc4_clock / (8*(reg + 1)) 80 | } 81 | 82 | fn delay(cycles: usize) void { 83 | var i: u32 = 0; 84 | while (i < cycles) : (i += 1) { 85 | asm volatile ("nop"); 86 | } 87 | } 88 | 89 | const GpioMode = enum(u3) { 90 | // zig fmt: off 91 | Input = 0b000, 92 | Output = 0b001, 93 | Alt5 = 0b010, 94 | Alt4 = 0b011, 95 | Alt0 = 0b100, 96 | Alt1 = 0b101, 97 | Alt2 = 0b110, 98 | Alt3 = 0b111, 99 | // zig fmt: on 100 | }; 101 | 102 | fn gpio_fsel(pin: u5, mode: GpioMode, val: u32) u32 { 103 | const mode_int: u32 = @enumToInt(mode); 104 | const bit = pin * 3; 105 | var temp = val; 106 | temp &= ~(@as(u32, 0b111) << bit); 107 | temp |= mode_int << bit; 108 | return temp; 109 | } 110 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/boot.sh: -------------------------------------------------------------------------------- 1 | setenv autostart no 2 | 3 | echo "[SABATON] Setting up identity map" 4 | load mmc "1:4" 80000000 /identity 5 | go 80000000 6 | 7 | setexpr load_addr 40000000 8 | echo "[SABATON] Loading Sabaton" 9 | load mmc "1:4" ${load_addr} /Sabaton.bin 10 | 11 | setexpr load_addr ${load_addr} '+' ${filesize} 12 | setexpr load_addr ${load_addr} '+' 00004fff # 0x4000 bytes for stacks, 0x1000 per core 13 | setexpr load_addr ${load_addr} '&' fffff000 14 | 15 | echo "[SABATON] Loading DTB" 16 | mw.l 40000010 ${load_addr} 17 | load mmc "1:4" ${load_addr} /dtb 18 | mw.l 40000018 ${filesize} 19 | 20 | setexpr load_addr ${load_addr} '+' ${filesize} 21 | setexpr load_addr ${load_addr} '+' 00000fff 22 | setexpr load_addr ${load_addr} '&' fffff000 23 | 24 | echo "[SABATON] Loading your kernel" 25 | mw.l 40000020 ${load_addr} 26 | load mmc "1:4" ${load_addr} /Kernel.elf 27 | 28 | setexpr load_addr ${load_addr} '+' ${filesize} 29 | setexpr load_addr ${load_addr} '+' 00000fff 30 | setexpr load_addr ${load_addr} '&' fffff000 31 | 32 | echo "[SABATON] Starting Sabaton..." 33 | mw.l 40000028 ${load_addr} 34 | go 40000030 35 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/ccu.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | 3 | fn ccu(offset: u16) *volatile u32 { 4 | return @intToPtr(*volatile u32, @as(usize, 0x01C2_0000) + offset); 5 | } 6 | 7 | const pll_ddr0 = ccu(0x0020); 8 | const pll_ddr1 = ccu(0x004C); 9 | const cpux_axi_cfg = ccu(0x0050); 10 | const ahb1_apb1_cfg = ccu(0x0054); 11 | const apb2_cfg = ccu(0x0058); 12 | const ahb2_cfg = ccu(0x005C); 13 | const bus_clk_gate0 = ccu(0x0060); 14 | const bus_clk_gate1 = ccu(0x0064); 15 | const bus_clk_gate2 = ccu(0x0068); 16 | const bus_clk_gate3 = ccu(0x006C); 17 | const bus_clk_gate4 = ccu(0x0070); 18 | const ce_clk = ccu(0x009C); 19 | const dram_cfg = ccu(0x00F4); 20 | const mbus_reset = ccu(0x00FC); 21 | const mbus_clk_gating = ccu(0x015C); 22 | const mbus2_clk = ccu(0x0160); 23 | const bus_soft_rst0 = ccu(0x02C0); 24 | const bus_soft_rst1 = ccu(0x02C4); 25 | const bus_soft_rst2 = ccu(0x02C8); 26 | const bus_soft_rst3 = ccu(0x02D0); 27 | const bus_soft_rst4 = ccu(0x02D8); 28 | const pll_lock_ctrl = ccu(0x0320); 29 | const mipi_dsi_clk = ccu(0x0168); 30 | 31 | fn waitPllStable(pll_addr: *volatile u32) void { 32 | while ((pll_addr.* & (1 << 28)) == 0) {} 33 | } 34 | 35 | fn initPll(offset: u16, pll_value: u32) void { 36 | const reg = ccu(offset); 37 | reg.* = pll_value; 38 | waitPllStable(reg); 39 | } 40 | 41 | fn setPllCpux(clk: u32) void { 42 | const k: u32 = 1 + @boolToInt(clk >= 768000000); 43 | const n = clk / (24000000 * k); 44 | 45 | // zig fmt: off 46 | 47 | // Temporarily toggle CPUX clock to OSC24M while we reconfigure PLL_CPUX 48 | cpux_axi_cfg.* = 0 49 | | (1 << 16) // CPUX_CLK_SRC_SEL = OSC24M 50 | | (1 << 8) // CPU_APB_CLK_DIV = /2 51 | | (2 << 0) // AXI_CLK_DIV_RATIO = /3 52 | ; 53 | 54 | sabaton.timer.sleep_us(2); 55 | 56 | initPll(0x0000, 0 // PLL_CPUX 57 | | (1 << 31) // PLL_ENABLE 58 | | (0 << 16) // PLL_OUT_EXT_DIVP 59 | | ((n-1) << 8) // PLL_FACTOR_N 60 | | ((k-1) << 4) // PLL_FACTOR_K 61 | | (0 << 0) // PLL_FACTOR_M 62 | ); 63 | 64 | // Switch CPUX back to PLL_CPUX 65 | cpux_axi_cfg.* = 0 66 | | (2 << 16) // CPUX_CLK_SRC_SEL = PLL_CPUX 67 | | (1 << 8) // CPU_APB_CLK_DIV = /2 68 | | (2 << 0) // AXI_CLK_DIV_RATIO = /3 69 | ; 70 | // zig fmt: on 71 | 72 | sabaton.timer.sleep_us(2); 73 | } 74 | 75 | pub fn upclock() void { 76 | setPllCpux(816000000); 77 | 78 | // zig fmt: off 79 | ahb1_apb1_cfg.* = 0 80 | | (0 << 12) // AHB_1_CLK_SRC_SEL = LOSC 81 | | (1 << 8) // APB1_CLK_RATIO = /2 82 | | (2 << 6) // AHB1_PRE_DIV = /3 83 | | (0 << 4) // AHB1_CLK_DIV_RATIO = /1 84 | ; 85 | // zig fmt: on 86 | 87 | ahb2_cfg.* = 1; // AHB2_CLK_CFG = PLL_PERIPH0(1X)/2 88 | } 89 | 90 | pub fn init() void { 91 | pll_lock_ctrl.* = 0x1FFF; 92 | 93 | setPllCpux(408000000); 94 | 95 | // zig fmt: off 96 | 97 | initPll(0x0028, 0 // PLL_PERIPH0 98 | | (1 << 31) // PLL_ENABLE 99 | | (1 << 24) // PLL_CLK_OUT_EN 100 | | (1 << 18) // Reserved 1 101 | | (24 << 8) // PLL_FACTOR_N, Factor=24, N=25 102 | | (1 << 4) // PLL_FACTOR_K 103 | | (1 << 0) // PLL_FACTOR_M 104 | ); 105 | 106 | ahb1_apb1_cfg.* = 0 107 | | (0 << 12) // AHB_1_CLK_SRC_SEL = LOSC 108 | | (1 << 8) // APB1_CLK_RATIO = /2 109 | | (2 << 6) // AHB1_PRE_DIV = /3 110 | | (1 << 4) // AHB1_CLK_DIV_RATIO = /2 111 | ; 112 | 113 | mbus_clk_gating.* = 0 114 | | (1 << 31) // MBUS_SCLK_GATING = ON 115 | | (1 << 24) // MBUS_SCLK_SRC = PLL_PERIPH0(2X) 116 | | (2 << 0) // MBUS_SCLK_RATIO_M = /3 117 | ; 118 | 119 | apb2_cfg.* = 0 120 | | (1 << 24) // APB2_CLK_SRC_SEL = OSC24M 121 | | (0 << 16) // CLK_RAT_N = /2 122 | | (0 << 0) // CLK_RAT_M = /1 123 | ; 124 | // zig fmt: on 125 | } 126 | 127 | pub fn clockUarts() void { 128 | bus_clk_gate3.* |= (1 << 16); 129 | bus_soft_rst4.* |= (1 << 16); 130 | } 131 | 132 | pub fn clockDram() void { 133 | mbus_clk_gating.* &= ~@as(u32, 1 << 31); // Clear MBUS_SCLK_GATING 134 | 135 | mbus_reset.* &= ~@as(u32, 1 << 31); // Clear MBUS_RESET 136 | 137 | bus_clk_gate0.* &= ~@as(u32, 1 << 14); // Clear DRAM_GATING 138 | 139 | bus_soft_rst0.* &= ~@as(u32, 1 << 14); // Clear SDRAM_RST 140 | 141 | pll_ddr0.* &= ~@as(u32, 1 << 31); // Clear PLL_ENABLE 142 | pll_ddr1.* &= ~@as(u32, 1 << 31); // Clear PLL_ENABLE 143 | 144 | sabaton.timer.sleep_us(10); 145 | 146 | dram_cfg.* &= ~@as(u32, 1 << 31); // Clear DRAM_CTR_RST 147 | 148 | // zig fmt: off 149 | initPll(0x004C, 0 // pll_ddr1 150 | | (1 << 31) // PLL_ENABLE 151 | | (1 << 30) // SDRPLL_UPD 152 | | ((46-1) << 8) // 552 * 2 * 1000000 / 24000000 = 46 153 | ); 154 | 155 | dram_cfg.* &= ~@as(u32, 0 156 | | (0x3 << 0) // Clear DRAM_DIV_M 157 | | (0x3 << 20) // Clear DDR_SRC_SELECT 158 | ); 159 | dram_cfg.* |= @as(u32, 0 160 | | (1 << 0) // Set DRAM_DIV_M, M = 2 161 | | (1 << 20) // Set DDR_SRC_SELECT = PLL_DDR1 162 | | (1 << 16) // Set SDRCLK_UPD, validate config 163 | ); 164 | // zig fmt: on 165 | 166 | while (dram_cfg.* & (1 << 16) != 0) {} // Wait for config validation 167 | 168 | bus_soft_rst0.* |= 1 << 14; // Set SDRAM_RST 169 | bus_clk_gate0.* |= 1 << 14; // Set DRAM_GATING 170 | mbus_reset.* |= 1 << 31; // Set MBUS_RESET 171 | mbus_clk_gating.* |= 1 << 31; // Set MBUS_SCLK_GATING 172 | 173 | dram_cfg.* |= 1 << 31; // Set DRAM_CTR_RST 174 | 175 | sabaton.timer.sleep_us(10); 176 | } 177 | 178 | // // Cock and ball torture? 179 | // // Clock and PLL torture. 180 | // init_pll(0x0010, 0x83001801); // PLL_VIDEO0 181 | // init_pll(0x0028, 0x80041811); // PLL_PERIPH0 182 | // init_pll(0x0040, 0x80C0041A); // PLL_MIPI 183 | // init_pll(0x0048, 0x83006207); // PLL_DE 184 | 185 | // // Cock gating registers? 186 | // // Clock gating registers. 187 | // ccu(0x0060).* = 0x33800040; // BUS_CLK_GATING_REG0 188 | // ccu(0x0064).* = 0x00201818; // BUS_CLK_GATING_REG1 189 | // ccu(0x0068).* = 0x00000020; // BUS_CLK_GATING_REG2 190 | // ccu(0x006C).* = 0x00010000; // BUS_CLK_GATING_REG3 191 | // //ccu(0x0070).* = 0x00000000; // BUS_CLK_GATING_REG4 192 | 193 | // ccu(0x0088).* = 0x0100000B; // SMHC0_CLK_REG 194 | // ccu(0x008C).* = 0x0001000E; // SMHC0_CLK_REG 195 | // ccu(0x0090).* = 0x01000005; // SMHC0_CLK_REG 196 | 197 | // ccu(0x00CC).* = 0x00030303; // USBPHY_CFG_REG 198 | 199 | // ccu(0x0104).* = 0x81000000; // DE_CLK_REG 200 | // ccu(0x0118).* = 0x80000000; // TCON0_CLK_REG 201 | // ccu(0x0150).* = 0x80000000; // HDMI_CLK_REG 202 | // ccu(0x0154).* = 0x80000000; // HDMI_SLOW_CLK_REG 203 | // ccu(0x0168).* = 0x00008001; // MIPI_DSI_CLK_REG 204 | 205 | // ccu(0x0224).* = 0x10040000; // PLL_AUDIO_BIAS_REG 206 | 207 | // ---------------------- 208 | 209 | // // zig fmt: off 210 | // const reg0_devs: u32 = 0 211 | // | (1 << 29) // USB-OHCI0 212 | // | (1 << 28) // USB-OTG-OHCI 213 | // | (1 << 25) // USB-EHCI0 214 | // | (1 << 24) // USB-OTG-EHCI0 215 | // | (1 << 23) // USB-OTG-Device 216 | // | (1 << 13) // NAND 217 | // | (1 << 1) // MIPI_DSI 218 | // ; 219 | 220 | // const reg1_devs: u32 = 0 221 | // | (1 << 22) // SPINLOCK 222 | // | (1 << 21) // MSGBOX 223 | // | (1 << 20) // GPU 224 | // | (1 << 12) // DE 225 | // | (1 << 11) // HDMI1 226 | // | (1 << 10) // HDMI0 227 | // | (1 << 5) // DEINTERLACE 228 | // | (1 << 4) // TCON1 229 | // | (1 << 3) // TCON0 230 | // ; 231 | // // zig fmt: on 232 | 233 | // ccu(0x02C0).* &= ~reg0_devs; 234 | // ccu(0x02C4).* &= ~reg1_devs; 235 | 236 | // ccu(0x02C0).* |= reg0_devs; 237 | // ccu(0x02C4).* |= reg1_devs; 238 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/display.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("../../sabaton.zig"); 2 | 3 | const regs = @import("regs.zig"); 4 | 5 | const width = 720; 6 | const height = 1440; 7 | const bpp = 32; 8 | 9 | // Cant't do input/output constraints on global asm, so this is what we have to do. 10 | export fn tag() callconv(.Naked) void { 11 | asm volatile( 12 | \\ .section .data 13 | \\ .balign 8 14 | \\ framebuffer_tag: 15 | \\ .8byte 0x506461d2950408fa // framebuffer identifier 16 | \\ .8byte 0 // next 17 | \\ .8byte 0 // addr 18 | \\ .2byte %[width] // width 19 | \\ .2byte %[height]// height 20 | \\ .2byte %[width] * %[bpp]/8 // pitch 21 | \\ .2byte %[bpp] // bpp 22 | \\ .byte 1 // memory model, 1 = RGB 23 | \\ .byte 8 // red mask size 24 | \\ .byte 16 // red_mask_shift 25 | \\ .byte 8 // green_mask_size 26 | \\ .byte 8 // green_mask_shift 27 | \\ .byte 8 // blue_mask_size 28 | \\ .byte 0 // blue_mask_shift 29 | : 30 | : [width] "i" (@as(usize, width)) 31 | , [height] "i" (@as(usize, height)) 32 | , [bpp] "i" (@as(usize, bpp)) 33 | ); 34 | } 35 | 36 | pub fn init() void { 37 | const fb_tag = sabaton.near("framebuffer_tag"); 38 | sabaton.add_tag(&fb_tag.addr(sabaton.Stivale2tag)[0]); 39 | 40 | // We have a 32 bit physical address space on this device, this has to work 41 | const fb_bytes = width * bpp/8 * height; 42 | const fb_addr = @intCast(u32, @ptrToInt(sabaton.pmm.alloc_aligned(fb_bytes, .Hole).ptr)); 43 | fb_tag.addr(u64)[2] = @as(u64, fb_addr); 44 | 45 | // Backlight brightness PWM, we just do a digital high lol 46 | regs.output_port('L', 10, true); 47 | 48 | // Enable backlight 49 | regs.output_port('H', 10, true); 50 | } 51 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/dram.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | 3 | const num_lanes = 4; 4 | const lines_per_lane = 11; 5 | 6 | const read_delays = [num_lanes][lines_per_lane]u8{ 7 | [_]u8{ 16, 16, 16, 16, 17, 16, 16, 17, 16, 1, 0 }, 8 | [_]u8{ 17, 17, 17, 17, 17, 17, 17, 17, 17, 1, 0 }, 9 | [_]u8{ 16, 17, 17, 16, 16, 16, 16, 16, 16, 0, 0 }, 10 | [_]u8{ 17, 17, 17, 17, 17, 17, 17, 17, 17, 1, 0 }, 11 | }; 12 | 13 | const write_delays = [num_lanes][lines_per_lane]u8{ 14 | [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15 }, 15 | [_]u8{ 0, 0, 0, 0, 1, 1, 1, 1, 0, 10, 10 }, 16 | [_]u8{ 1, 0, 1, 1, 1, 1, 1, 1, 0, 11, 11 }, 17 | [_]u8{ 1, 0, 0, 1, 1, 1, 1, 1, 0, 12, 12 }, 18 | }; 19 | 20 | const ac_delays = [31]u8{ 5, 5, 13, 10, 2, 5, 3, 3, 0, 3, 3, 3, 1, 0, 0, 0, 3, 4, 0, 3, 4, 1, 4, 0, 1, 1, 0, 1, 13, 5, 4 }; 21 | 22 | pub fn ccm(offset: usize) *volatile u32 { 23 | return @intToPtr(*volatile u32, @as(usize, 0x01C2_0000) + offset); 24 | } 25 | 26 | pub fn com(offset: usize) *volatile u32 { 27 | return @intToPtr(*volatile u32, @as(usize, 0x01C6_2000) + offset); 28 | } 29 | 30 | pub fn ctl0(offset: usize) *volatile u32 { 31 | return @intToPtr(*volatile u32, @as(usize, 0x01C6_3000) + offset); 32 | } 33 | 34 | pub fn ctl1(offset: usize) *volatile u32 { 35 | return @intToPtr(*volatile u32, @as(usize, 0x01C6_4000) + offset); 36 | } 37 | 38 | pub fn phy0(offset: usize) *volatile u32 { 39 | return @intToPtr(*volatile u32, @as(usize, 0x01C6_5000) + offset); 40 | } 41 | 42 | pub fn phy1(offset: usize) *volatile u32 { 43 | return @intToPtr(*volatile u32, @as(usize, 0x01C6_6000) + offset); 44 | } 45 | 46 | fn init_cr() void { 47 | // zig fmt: off 48 | const common_value = 0 49 | | (1 << 12) // Full width 50 | | ((12 - 5) << 8) // PAGE_SHFT - 5, 4096 page size 51 | | ((15 - 1) << 4) // Row bits = 15 52 | | (1 << 2) // 8 Banks 53 | | (1 << 0) // Dual ranks 54 | ; 55 | com(0x0000).* = 0 56 | | common_value 57 | | (0 << 15) // Interleaved 58 | | (7 << 16) // LPDDR3 59 | | (1 << 19) // 1T 60 | | ((8-1) << 20) // BL = 8 61 | ; 62 | com(0x0004).* = common_value; 63 | // zig fmt: on 64 | } 65 | 66 | fn init_timings() void {} 67 | 68 | pub fn init() usize { 69 | sabaton.platform.ccu.clockDram(); 70 | 71 | ctl0(0x000C).* = 0xC00E; 72 | 73 | sabaton.timer.sleep_us(500); 74 | 75 | init_cr(); 76 | init_timings(); 77 | 78 | @import("root").debugBreak(); 79 | 80 | // zig fmt: off 81 | ctl0(0x0120).* = 0 // ODTMap 82 | | (3 << 8) 83 | | (3 << 0) 84 | ; 85 | // zig fmt: on 86 | 87 | sabaton.timer.sleep_us(1); 88 | 89 | // VTF enable 90 | ctl0(0x00B8).* |= (3 << 8); 91 | 92 | // DQ hold disable 93 | ctl0(0x0108).* &= ~@as(u32, 1 << 13); 94 | 95 | com(0x00D0).* |= (1 << 31); 96 | 97 | sabaton.timer.sleep_us(10); 98 | 99 | // // Detect dram size 100 | // // set_cr 101 | 102 | return 0; 103 | } 104 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/entry.S: -------------------------------------------------------------------------------- 1 | .global _start 2 | 3 | .section .text.entry 4 | _start: 5 | // We enter this fucking beast of a platform in arm mode, not aarch64. 6 | // Let's do something about that. 7 | .byte 0x17, 0x16, 0xA0, 0xE3 // mov.w r1, #0x1700000 8 | .byte 0x1C, 0x00, 0x8F, 0xE2 // addw r0, pc, #0x1c // ADR R0, go64 9 | .byte 0xA0, 0x00, 0x81, 0xE5 // str.w r0, [r1, #0xa0] 10 | .byte 0x4E, 0xF0, 0x7F, 0xF5 // dsb st 11 | .byte 0x50, 0x0F, 0x1C, 0xEE // mrc p15, #0, r0, c12, c0, #2 12 | .byte 0x03, 0x00, 0x80, 0xE3 // orr r0, r0, #3 13 | .byte 0x50, 0x0F, 0x0C, 0xEE // mcr p15, #0, r0, c12, c0, #2 14 | .byte 0x6F, 0xF0, 0x7F, 0xF5 // isb sy 15 | //1: 16 | .byte 0x03, 0xF0, 0x20, 0xE3 // wfi 17 | .byte 0xFD, 0xFF, 0xFF, 0xEA // b 1b 18 | go64: 19 | MOVZ X0, #0x3600 20 | MOVK X0, #0x016E, LSL #16 21 | MSR CNTFRQ_EL0, X0 22 | 23 | // NS IRQ FIQ EA RES1 SMD HCE SIF RW ST TWI TWE 24 | MOV X0, #(0<<1)|(1<<1)|(1<<2)|(1<<3)|(3<<4)|(0<<7)|(1<<8)|(0<<9)|(1<<10)|(0<<11)|(0<<12)|(0<<13) 25 | MSR SCR_EL3, X0 26 | 27 | MRS X0, SCTLR_EL3 28 | ORR X0, X0, #(1 << 12) // icache 29 | ORR X0, X0, #(1 << 2) // dcache 30 | MSR SCTLR_EL3, X0 31 | 32 | MSR CPTR_EL3, XZR // Enable FP/SIMD 33 | 34 | // Stacc ptr 35 | MSR SPSel, #0 36 | ADR X0, __boot_stack 37 | MOV SP, X0 38 | 39 | // .global devicetree_tag 40 | // devicetree_tag: 41 | // .8byte 0xabb29bd49a2833fa // DeviceTree 42 | // .8byte 0 43 | // dtb_loc: 44 | // .8byte 0 45 | // .8byte 0 46 | // .global kernel_file_loc 47 | // kernel_file_loc: 48 | // .8byte 0 49 | // memhead: 50 | // .8byte 0 51 | 52 | // // Next usable physmem 53 | // LDR X0, memhead 54 | // ADR X5, pmm_head 55 | // STR X0, [X5] 56 | 57 | // // Copy page settings to EL1 58 | // MRS X1, SCTLR_EL2 59 | // MSR SCTLR_EL1, X1 60 | // MRS X1, TCR_EL2 61 | // MSR TCR_EL1, X1 62 | // MRS X1, MAIR_EL2 63 | // MSR MAIR_EL1, X1 64 | // MRS X1, TTBR0_EL2 65 | // MSR TTBR0_EL1, X1 66 | // MSR TTBR1_EL1, XZR 67 | 68 | // TLBI VMALLE1 69 | 70 | // BL el2_to_el1 71 | 72 | // // Stacc 73 | // MSR SPSel, #0 74 | // ADR X1, __boot_stack 75 | // MOV SP, X1 76 | 77 | // // Fall through to main 78 | 79 | // .section .text 80 | // .global el2_to_el1 81 | // el2_to_el1: 82 | // // aarch64 in EL1 83 | // ORR X1, XZR, #(1 << 31) 84 | // ORR X1, X1, #(1 << 1) 85 | // MSR HCR_EL2, X1 86 | 87 | // // Counters in EL1 88 | // MRS X1, CNTHCTL_EL2 89 | // ORR X1, X1, #3 90 | // MSR CNTHCTL_EL2, X1 91 | // MSR CNTVOFF_EL2, XZR 92 | 93 | // // FP/SIMD in EL1 94 | // MOV X1, #0x33FF 95 | // MSR CPTR_EL2, X1 96 | // MSR HSTR_EL2, XZR 97 | // MOV X1, #0x300000 98 | // MSR CPACR_EL1, X1 99 | 100 | // // Get the fuck out of EL2 into EL1 101 | // MSR ELR_EL2, LR 102 | // MOV X1, #0x3C5 103 | // MSR SPSR_EL2, X1 104 | // ERET 105 | 106 | // .section .data 107 | // // Allwinner A64 user manual: 108 | // // 7.3.4: UART Controller Register List 109 | // // 7.3.5: UART Register Description 110 | 111 | // .global uart_tag 112 | // .global uart_reg 113 | // .global uart_status 114 | // .global uart_status_mask 115 | // .global uart_status_value 116 | // .balign 8 117 | // uart_tag: 118 | // .8byte 0xf77485dbfeb260f9 // u32 MMIO UART with status 119 | // .8byte 0 120 | // uart_reg: 121 | // .8byte __uart_base + 0x00 // TX Holding Register 122 | // uart_status: 123 | // .8byte __uart_base + 0x14 // Line Status Register 124 | // uart_status_mask: 125 | // .4byte 0x00000040 // TX Holding Register Empty 126 | // uart_status_value: 127 | // .4byte 0x00000040 // TX Holding Register Empty is set 128 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/identity.S: -------------------------------------------------------------------------------- 1 | .section .blob, "ax" 2 | .global _start 3 | _start: 4 | // Disable paging 5 | MRS X5, SCTLR_EL2 6 | AND X0, X5, #~1 7 | MSR SCTLR_EL2, X0 8 | DSB SY 9 | ISB SY 10 | 11 | MRS X0, TTBR0_EL2 12 | 13 | LDR X0, [X0] 14 | AND X0, X0, #0x0000FFFFFFFFF000 15 | LDR X0, [X0] 16 | AND X0, X0, #0x0000FFFFFFFFF000 17 | 18 | // MAIR should be FF440C0400 at this point (don't ask) 19 | // Identity map first 1G of dram 20 | // 1G PTE, memory, AttrIdx = 4 21 | MOVZ X2, (1024 * 1024 * 1024) >> 16, LSL #16 22 | MOVK X2, (1 << 0) | (4 << 2) | (1 << 5) | (1 << 10) | (2 << 8), LSL #0 23 | // 1G PTE, device, AttrIdx = 0 24 | MOVZ X1, (1 << 0) | (0 << 2) | (1 << 5) | (1 << 10) | (2 << 8) 25 | STP X1, X2, [X0] 26 | 27 | // Reenable paging 28 | MSR SCTLR_EL2, X5 29 | RET 30 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/keyadc.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | 3 | // KEYADC is an ADC for the physical buttons on the phone 4 | pub fn keyadc(offset: usize) *volatile u32 { 5 | return @intToPtr(*volatile u32, @as(usize, 0x01C2_1800) + offset); 6 | } 7 | 8 | const ctrl = keyadc(0x0000); 9 | const intc = keyadc(0x0004); 10 | const ints = keyadc(0x0008); 11 | const data0 = keyadc(0x000C); 12 | const data1 = keyadc(0x0010); 13 | 14 | pub fn init() void { 15 | // zig fmt: off 16 | ints.* = 0x1F; // Clear all interrupts 17 | 18 | intc.* = 0; // Disable all interrupts 19 | 20 | ctrl.* = 0 21 | | (0 << 24) // First convert delay, number of samples 22 | | (0 << 16) // Continue time select, N/A 23 | | (0 << 12) // Key mode select = Normal 24 | | (0 << 8) // Level A to Level B time threshold select 25 | | (0 << 7) // Hold key enable 26 | | (0 << 6) // Sample hold enable 27 | | (0 << 4) // Level B = 1.9V 28 | | (0 << 2) // Sample rage = 250Hz 29 | | (1 << 0) // Enable KEYADC 30 | ; 31 | // zig fmt: on 32 | } 33 | 34 | pub fn disable() void { 35 | ctrl.* = 0; 36 | } 37 | 38 | pub fn getPressedKey() enum { 39 | Up, 40 | Down, 41 | // Is the power button separate?? 42 | // Probably?? 43 | //Power, 44 | None, 45 | } { 46 | const val = data0.* & 0x3F; 47 | 48 | // if (val != 0x3F) 49 | // sabaton.log_hex("KEYADC: ", val); 50 | 51 | return switch (val) { 52 | 5 => .Up, 53 | 54 | 11 => .Down, 55 | 56 | else => .None, 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/led.zig: -------------------------------------------------------------------------------- 1 | const regs = @import("regs.zig"); 2 | 3 | // One-shot the LED port operations instead of using the ports api 4 | // which does a volatile read and write for each color 5 | const single_led_write = true; 6 | 7 | // Assume that there are no current outputs on the same 8 | // register on the D port pins 9 | const assume_no_outputs = true; 10 | 11 | pub fn output( 12 | // The color of the front-facing LED 13 | // You can combine colors like all three making white. 14 | col: struct { red: bool, green: bool, blue: bool }, 15 | ) void { 16 | if (comptime single_led_write) { 17 | const op = @intToPtr(*volatile u32, 0x1C2087C); 18 | 19 | var op_val: u32 = if (comptime assume_no_outputs) 20 | 0 21 | else 22 | op.* & ~@as(u32, 0x1C0000); 23 | 24 | if (col.red) op_val |= 0x080000; 25 | if (col.blue) op_val |= 0x100000; 26 | if (col.green) op_val |= 0x040000; 27 | 28 | op.* = op_val; 29 | } else { 30 | regs.write_port('D', 18, col.green); 31 | regs.write_port('D', 19, col.red); 32 | regs.write_port('D', 20, col.blue); 33 | } 34 | } 35 | 36 | // Configure all three led pins in a single operation 37 | // instead of 2 per (like above) 38 | const single_config_write = true; 39 | 40 | // Assume no other pins on the D port in the same configuration 41 | // regsister are active 42 | const assume_ports_unconfigured = true; 43 | 44 | pub fn configureLed() void { 45 | if (comptime single_config_write) { 46 | const config = @intToPtr(*volatile u32, 0x1C20874); 47 | 48 | const config_val: u32 = if (comptime assume_ports_unconfigured) 49 | 0x77700077 50 | else 51 | config.* & 0xFFF000FF; 52 | 53 | config.* = config_val | 0x00011100; 54 | } else { 55 | regs.configure_port('D', 18, .Output); 56 | regs.configure_port('D', 19, .Output); 57 | regs.configure_port('D', 20, .Output); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | OUTPUT_FORMAT(elf64-littleaarch64) 3 | OUTPUT_ARCH(aarch64) 4 | 5 | PHDRS { 6 | none PT_NULL FLAGS(0); 7 | blob PT_LOAD FLAGS(7); 8 | } 9 | 10 | __uart_base = 0x1C28000; 11 | __dram_base = 0x40000000; 12 | 13 | SECTIONS { 14 | . = 0x10000; 15 | . += 0x30; /* Boot image header size, thanks... */ 16 | __blob_base = .; 17 | .blob : { 18 | *(.text.entry) 19 | KEEP(*(.text.main)) 20 | *(.text.smp_stub) 21 | KEEP(*(.text.smp_entry)) 22 | *(.text*) 23 | 24 | . = ALIGN(8); 25 | PROVIDE(memmap_tag = .); 26 | QUAD(0x2187F79E8612DE07); /* Stivale2 memmap identifier */ 27 | QUAD(0); /* Next */ 28 | QUAD(5); /* Memory map entries */ 29 | 30 | QUAD(0); /* MMIO base region */ 31 | QUAD(1024M); 32 | LONG(2); 33 | LONG(0); 34 | 35 | *(.data.memmap); 36 | 37 | *(.data*) 38 | *(.rodata*) 39 | *(.bss*) 40 | *(.COMMON) 41 | . = ALIGN(0x800); 42 | *(.debugger*) 43 | }: blob 44 | __blob_end = .; 45 | . = ALIGN(4K); 46 | . += 4K; 47 | __boot_stack = .; 48 | . += 4K; 49 | __debugger_stack = .; 50 | __pmm_base = .; 51 | 52 | /DISCARD/ : { 53 | *(.eh_frame*) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/main.zig: -------------------------------------------------------------------------------- 1 | pub const sabaton = @import("../../sabaton.zig"); 2 | pub const io = sabaton.io_impl.status_uart_mmio_32; 3 | pub const panic = sabaton.panic; 4 | 5 | pub const ccu = @import("ccu.zig"); 6 | pub const display = @import("display.zig"); 7 | pub const dram = @import("dram.zig"); 8 | pub const keyadc = @import("keyadc.zig"); 9 | pub const led = @import("led.zig"); 10 | pub const smp = @import("smp.zig"); 11 | pub const uart = @import("uart.zig"); 12 | 13 | const std = @import("std"); 14 | 15 | const enable_debugger = true; 16 | 17 | pub const cacheline_size = 64; 18 | 19 | const debugger_entry linksection(".debugger") = @embedFile("../host_aarch64_EL3.bin").*; 20 | 21 | pub fn debugBreak() void { 22 | if (comptime (enable_debugger)) { 23 | asm volatile ( 24 | \\SMC #5 25 | ); 26 | } 27 | } 28 | 29 | // We know the page size is 0x1000 30 | pub fn get_page_size() u64 { 31 | return 0x1000; 32 | } 33 | 34 | fn debuggerSendUart(ptr: [*c]const u8, sz: usize) callconv(.C) void { 35 | // No need to flush anything since we're only reading from dcache 36 | for (ptr[0..sz]) |c| { 37 | sabaton.io_impl.putchar_bin(c); 38 | } 39 | } 40 | 41 | fn debuggerRecvUart(ptr: [*c]u8, sz: usize) callconv(.C) void { 42 | // Flush icache after recv, no need to flush dcache since we're not doing dma 43 | for (ptr[0..sz]) |*c| { 44 | c.* = sabaton.io_impl.getchar_bin(); 45 | } 46 | sabaton.cache.flush(true, false, @ptrToInt(ptr), sz); 47 | } 48 | 49 | export fn _main() linksection(".text.main") noreturn { 50 | ccu.init(); 51 | ccu.upclock(); 52 | 53 | uart.init(); 54 | 55 | const SCTLR_EL3 = asm ("MRS %[out], SCTLR_EL3" 56 | : [out] "=r" (-> u64) 57 | ); 58 | sabaton.log_hex("SCTLR_EL3: ", SCTLR_EL3); 59 | 60 | // sabaton.log_hex("PT base: ", sabaton.pmm.alloc_aligned(0x1000, .ReclaimableData).ptr); 61 | 62 | // // Enable MMU 63 | // asm volatile ("MSR SCTLR_EL3, %[sctlr]" 64 | // : 65 | // : [sctlr] "r" (SCTLR_EL3 | 1) 66 | // ); 67 | 68 | // sabaton.puts("Paging enabled"); 69 | 70 | if (comptime enable_debugger) { 71 | const debugger_stack = asm ( 72 | \\ADR %[stk], __debugger_stack 73 | : [stk] "=r" (-> u64) 74 | ); 75 | 76 | asm volatile ( 77 | \\ MSR SPSel, #1 78 | \\ MOV SP, %[stk] 79 | \\ MSR SPSel, #0 80 | : 81 | : [stk] "r" (debugger_stack) 82 | ); 83 | 84 | sabaton.log_hex("Set debugger stack to ", debugger_stack); 85 | 86 | const debugger_init = @ptrCast( 87 | fn ( 88 | fn ([*c]const u8, usize) callconv(.C) void, 89 | fn ([*c]u8, usize) callconv(.C) void, 90 | ) callconv(.C) void, 91 | &debugger_entry[0], 92 | ); 93 | 94 | sabaton.log_hex("Initializing debugger at ", @ptrToInt(debugger_init)); 95 | 96 | debugger_init( 97 | debuggerSendUart, 98 | debuggerRecvUart, 99 | ); 100 | } 101 | 102 | led.configureLed(); 103 | led.output(.{ .green = true, .red = true, .blue = false }); 104 | 105 | const dram_size = dram.init(); 106 | _ = dram_size; 107 | 108 | led.output(.{ .green = true, .red = false, .blue = true }); 109 | 110 | keyadc.init(); 111 | while (true) { 112 | switch (keyadc.getPressedKey()) { 113 | .Up => sabaton.puts("Up button pressed\n"), 114 | .Down => sabaton.puts("Down button pressed\n"), 115 | else => {}, 116 | } 117 | } 118 | 119 | //sabaton.timer.sleep_us(1_000_000); 120 | 121 | //@call(.{ .modifier = .always_inline }, sabaton.main, .{}); 122 | } 123 | 124 | pub fn panic_hook() void { 125 | // Red 126 | led.output(.{ .green = false, .red = true, .blue = false }); 127 | } 128 | 129 | pub fn launch_kernel_hook() void { 130 | // Blue 131 | led.output(.{ .green = false, .red = false, .blue = true }); 132 | } 133 | 134 | pub fn get_kernel() [*]u8 { 135 | return sabaton.near("kernel_file_loc").read([*]u8); 136 | } 137 | 138 | // pub fn get_dtb() []u8 { 139 | // return sabaton.near("dram_base").read([*]u8)[0..0x100000]; 140 | // } 141 | 142 | pub fn get_dram() []u8 { 143 | return sabaton.near("dram_base").read([*]u8)[0..get_dram_size()]; 144 | } 145 | 146 | fn get_dram_size() u64 { 147 | return 0x80000000; 148 | } 149 | 150 | pub fn map_platform(root: *sabaton.paging.Root) void { 151 | // MMIO area 152 | sabaton.paging.map(0, 0, 1024 * 1024 * 1024, .rw, .mmio, root); 153 | sabaton.paging.map(sabaton.upper_half_phys_base, 0, 1024 * 1024 * 1024, .rw, .mmio, root); 154 | } 155 | 156 | pub fn add_platform_tags(kernel_header: *sabaton.Stivale2hdr) void { 157 | _ = kernel_header; 158 | sabaton.add_tag(&sabaton.near("uart_tag").addr(sabaton.Stivale2tag)[0]); 159 | sabaton.add_tag(&sabaton.near("devicetree_tag").addr(sabaton.Stivale2tag)[0]); 160 | } 161 | 162 | pub fn get_uart_info() io.Info { 163 | const base = 0x1C28000; 164 | return .{ 165 | .uart = @intToPtr(*volatile u32, base), 166 | .status = @intToPtr(*volatile u32, base + 0x14), 167 | .mask = 0x20, 168 | .value = 0x20, 169 | }; 170 | } 171 | 172 | pub fn get_uart_reader() io.Info { 173 | const base = 0x1C28000; 174 | return .{ 175 | .uart = @intToPtr(*volatile u32, base), 176 | .status = @intToPtr(*volatile u32, base + 0x14), 177 | .mask = 0x1, 178 | .value = 0x1, 179 | }; 180 | } 181 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/regs.zig: -------------------------------------------------------------------------------- 1 | pub fn portio_cpux(offset: usize) *volatile u32 { 2 | return @intToPtr(*volatile u32, @as(usize, 0x01C2_0800) + offset); 3 | } 4 | 5 | pub fn portio_cpus(offset: usize) *volatile u32 { 6 | return @intToPtr(*volatile u32, @as(usize, 0x01F0_2C00) + offset); 7 | } 8 | 9 | fn verify_port_pin(comptime port: u8, comptime pin: u5) void { 10 | switch (port) { 11 | 'B' => if (pin >= 10) @compileError("Pin out of range!"), 12 | 'C' => if (pin >= 17) @compileError("Pin out of range!"), 13 | 'D' => if (pin >= 25) @compileError("Pin out of range!"), 14 | 'E' => if (pin >= 18) @compileError("Pin out of range!"), 15 | 'F' => if (pin >= 7) @compileError("Pin out of range!"), 16 | 'G' => if (pin >= 14) @compileError("Pin out of range!"), 17 | 'H' => if (pin >= 12) @compileError("Pin out of range!"), 18 | 'L' => if (pin >= 13) @compileError("Pin out of range!"), 19 | else => @compileError("Unknown port!"), 20 | } 21 | } 22 | 23 | fn get_config(comptime port: u8, comptime pin: u5) *volatile u32 { 24 | const offset = @as(u16, @divTrunc(pin, 8) * 4); 25 | return switch (port) { 26 | 'B' => portio_cpux(0x0024 + offset), 27 | 'C' => portio_cpux(0x0048 + offset), 28 | 'D' => portio_cpux(0x006C + offset), 29 | 'E' => portio_cpux(0x0090 + offset), 30 | 'F' => portio_cpux(0x00B4 + offset), 31 | 'G' => portio_cpux(0x00D8 + offset), 32 | 'H' => portio_cpux(0x00FC + offset), 33 | 'L' => portio_cpus(0x0000 + offset), 34 | else => @compileError("Unknown port!"), 35 | }; 36 | } 37 | 38 | fn get_data(comptime port: u8) *volatile u32 { 39 | return switch (port) { 40 | 'B' => portio_cpux(0x0034), 41 | 'C' => portio_cpux(0x0058), 42 | 'D' => portio_cpux(0x007C), 43 | 'E' => portio_cpux(0x00A0), 44 | 'F' => portio_cpux(0x00C4), 45 | 'G' => portio_cpux(0x00E8), 46 | 'H' => portio_cpux(0x010C), 47 | 'L' => portio_cpus(0x0010), 48 | else => @compileError("Unknown port!"), 49 | }; 50 | } 51 | 52 | fn get_pull(comptime port: u8, comptime pin: u5) *volatile u32 { 53 | const offset = @as(u16, @divTrunc(pin, 16) * 4); 54 | return switch (port) { 55 | 'B' => portio_cpux(0x0038 + offset), 56 | 'C' => portio_cpux(0x0064 + offset), 57 | 'D' => portio_cpux(0x0088 + offset), 58 | 'E' => portio_cpux(0x00AC + offset), 59 | 'F' => portio_cpux(0x00D0 + offset), 60 | 'G' => portio_cpux(0x00F4 + offset), 61 | 'H' => portio_cpux(0x0118 + offset), 62 | 'L' => portio_cpus(0x001C + offset), 63 | else => @compileError("Unknown port!"), 64 | }; 65 | } 66 | 67 | const PortMode = enum(u3) { 68 | Input = 0, 69 | Output = 1, 70 | Uart = 4, 71 | }; 72 | 73 | pub fn configure_port(comptime port: u8, comptime pin: u5, io: PortMode) void { 74 | comptime { 75 | verify_port_pin(port, pin); 76 | } 77 | 78 | const field: u32 = @enumToInt(io); 79 | const config = comptime get_config(port, pin); 80 | const start_bit = comptime @as(u8, (pin % 8)) * 4; 81 | config.* = (config.* & ~@as(u32, 0x7 << start_bit)) | (field << start_bit); 82 | } 83 | 84 | // Sets a port to output and writes the value to the pin 85 | pub fn output_port(comptime port: u8, comptime pin: u5, value: bool) void { 86 | configure_port(port, pin, .Output); 87 | write_port(port, pin, value); 88 | } 89 | 90 | // Sets a port to input and reads the value from the pin 91 | pub fn input_port(comptime port: u8, comptime pin: u5) bool { 92 | configure_port(port, pin, .Input); 93 | return read_port(port, pin); 94 | } 95 | 96 | pub fn pull_port(comptime port: u8, comptime pin: u5, dir: enum { Up, Down }) void { 97 | comptime { 98 | verify_port_pin(port, pin); 99 | } 100 | 101 | const reg = get_pull(port, pin); 102 | const bit_idx = @truncate(u4, pin); 103 | const shift = @as(u5, bit_idx) * 2; 104 | 105 | const curr_val: u32 = @as(u32, switch (dir) { 106 | .Up => 1, 107 | .Down => 2, 108 | }) << shift; 109 | 110 | const curr_mask = @as(u32, 0x3) << shift; 111 | const other_mask = ~curr_mask; 112 | 113 | reg.* = (reg.* & other_mask) | curr_val; 114 | } 115 | 116 | pub fn write_port(comptime port: u8, comptime pin: u5, value: bool) void { 117 | comptime { 118 | verify_port_pin(port, pin); 119 | } 120 | 121 | const curr_bit: u32 = 1 << pin; 122 | const d = comptime get_data(port); 123 | if (value) { 124 | d.* |= curr_bit; 125 | } else { 126 | d.* &= ~curr_bit; 127 | } 128 | } 129 | 130 | pub fn read_port(comptime port: u8, comptime pin: u5) bool { 131 | comptime { 132 | verify_port_pin(port, pin); 133 | } 134 | 135 | const d = comptime get_data(port); 136 | return (d.* & (1 << pin)) != 0; 137 | } 138 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/smp.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | 3 | comptime { 4 | asm ( 5 | // We pack the CPU boot trampolines into the stack slots, because reasons. 6 | \\ .section .data 7 | \\ .balign 8 8 | \\ .global smp_tag 9 | \\smp_tag: 10 | \\ .8byte 0x34d1d96339647025 // smp identifier 11 | \\ .8byte 0 // next 12 | \\ .8byte 0 // flags 13 | \\ .4byte 0 // boot cpu id 14 | \\ .4byte 0 // pad 15 | \\ .8byte 4 // cpu_count 16 | \\ 17 | \\ // CPU 0 18 | \\ .4byte 0 // ACPI UID 19 | \\ .4byte 0 // CPU ID 20 | \\ .8byte 0 // target stack 21 | \\ .8byte 0 // goto address 22 | \\ .8byte 0 // extra argument 23 | \\ 24 | \\ // CPU 1 25 | \\ .4byte 1 // ACPI UID 26 | \\ .4byte 1 // CPU ID 27 | \\ .8byte __boot_stack + 0x1000 // target stack 28 | \\ .8byte 0 // goto address 29 | \\ .8byte 0 // extra argument 30 | \\ 31 | \\ // CPU 2 32 | \\ .4byte 2 // ACPI UID 33 | \\ .4byte 2 // CPU ID 34 | \\ .8byte __boot_stack + 0x2000 // target stack 35 | \\ .8byte 0 // goto address 36 | \\ .8byte 0 // extra argument 37 | \\ 38 | \\ // CPU 3 39 | \\ .4byte 3 // ACPI UID 40 | \\ .4byte 3 // CPU ID 41 | \\ .8byte __boot_stack + 0x3000 // target stack 42 | \\ .8byte 0 // goto address 43 | \\ .8byte 0 // extra argument 44 | \\ 45 | \\ .section .text.smp_stub 46 | \\ .global smp_stub 47 | \\smp_stub: 48 | \\ BL el2_to_el1 49 | \\ MSR SPSel, #0 50 | \\ LDR X1, [X0, #8] // Load stack 51 | \\ MOV SP, X1 52 | \\ // Fall through to smp_entry 53 | ); 54 | } 55 | 56 | export fn smp_entry(context: u64) linksection(".text.smp_entry") noreturn { 57 | @call(.{ .modifier = .always_inline }, sabaton.stivale2_smp_ready, .{context}); 58 | } 59 | 60 | fn cpucfg(offset: u16) *volatile u32 { 61 | return @intToPtr(*volatile u32, @as(usize, 0x0170_0C00) + offset); 62 | } 63 | 64 | fn ccu(offset: u64) *volatile u32 { 65 | return @intToPtr(*volatile u32, @as(usize, 0x01C2_0000) + offset); 66 | } 67 | 68 | pub fn init() void { 69 | const smp_tag = sabaton.near("smp_tag").addr(sabaton.Stivale2tag); 70 | sabaton.add_tag(&smp_tag[0]); 71 | 72 | var core: u64 = 1; 73 | while (core < 4) : (core += 1) { 74 | const ap_x0 = @ptrToInt(smp_tag) + 40 + core * 32; 75 | _ = sabaton.psci.wake_cpu(@ptrToInt(sabaton.near("smp_stub").addr(u32)), core, ap_x0, .SMC); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/platform/pine_aarch64/uart.zig: -------------------------------------------------------------------------------- 1 | const ccu = @import("ccu.zig"); 2 | const regs = @import("regs.zig"); 3 | 4 | pub fn uart(offset: usize) *volatile u32 { 5 | return @intToPtr(*volatile u32, @as(usize, 0x01C2_8000) + offset); 6 | } 7 | 8 | const dll = uart(0x0000); 9 | const dlh = uart(0x0004); 10 | const fcr = uart(0x0008); 11 | const lcr = uart(0x000C); 12 | 13 | pub fn init() void { 14 | ccu.clockUarts(); 15 | 16 | regs.configure_port('B', 8, .Uart); 17 | regs.configure_port('B', 9, .Uart); 18 | regs.pull_port('B', 9, .Up); 19 | 20 | // zig fmt: off 21 | lcr.* = 0 22 | | (1 << 7) // DLAB 23 | // Leave the rest undefined for now 24 | ; 25 | 26 | fcr.* = 0 27 | | (1 << 0) // Enable FIFOs, required for getchar() 28 | ; 29 | 30 | // Baud rate: 24 * 1000 * 1000 / 16 / 115200 => Divisor should be 13 31 | dlh.* = 0 32 | | (0 << 0) // Divisor Latch High 33 | ; 34 | dll.* = 0 35 | | (13 << 0) // Divisor Latch Low 36 | ; 37 | 38 | lcr.* = 0 39 | | (0 << 8) // DLAB 40 | | (0 << 6) // Break Control 41 | | (0 << 4) // Even parity = N/A 42 | | (0 << 3) // Parity enable = No 43 | | (0 << 2) // Number of stop bits = 1 44 | | (3 << 0) // Data length = 8 bits 45 | ; 46 | // zig fmt: on 47 | } 48 | -------------------------------------------------------------------------------- /src/platform/psci.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | 3 | pub const Mode = enum { 4 | HVC, 5 | SMC, 6 | }; 7 | 8 | /// Wake up cpu `cpunum`. 9 | /// It will boot at `entry`, with X0 = `context`. 10 | pub fn wake_cpu(entry: u64, cpunum: u64, context: u64, comptime mode: Mode) u64 { 11 | if (sabaton.debug) { 12 | sabaton.puts("Waking up CPU\n"); 13 | sabaton.log_hex("cpunum: ", cpunum); 14 | sabaton.log_hex("entry: ", entry); 15 | sabaton.log_hex("context: ", context); 16 | } 17 | // zig fmt: off 18 | const result = asm volatile ( 19 | switch (mode) { 20 | .HVC => "HVC #0", 21 | .SMC => "SMC #0", 22 | } 23 | : [_] "={X0}" (-> u64) 24 | : [_] "{X0}" (@as(u64, 0xC4000003)) 25 | , [_] "{X1}" (cpunum) 26 | , [_] "{X2}" (entry) 27 | , [_] "{X3}" (context) 28 | ); 29 | // zig fmt: on 30 | if (sabaton.debug) { 31 | sabaton.log_hex("Wakeup result: ", result); 32 | } 33 | return result; 34 | } 35 | -------------------------------------------------------------------------------- /src/platform/sdm665_aarch64/entry.S: -------------------------------------------------------------------------------- 1 | .global _start 2 | 3 | .section .text.entry 4 | _start: 5 | //ADR X0, evt 6 | //MSR VBAR_EL3, X0 7 | B _main 8 | -------------------------------------------------------------------------------- /src/platform/sdm665_aarch64/linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | OUTPUT_FORMAT(elf64-littleaarch64) 3 | OUTPUT_ARCH(aarch64) 4 | 5 | PHDRS { 6 | none PT_NULL FLAGS(0); 7 | blob PT_LOAD FLAGS(7); 8 | } 9 | 10 | __sram_base = 0xC100000; 11 | __sram_size = 0x0200000; 12 | 13 | __dram_base = 0x45E10000; 14 | __dram_size = 0x20000000; 15 | 16 | __pmm_base = __dram_base; 17 | 18 | SECTIONS { 19 | . = __sram_base; 20 | __blob_base = .; 21 | .blob : { 22 | *(.text.entry) 23 | 24 | . = ALIGN(8); 25 | PROVIDE(memmap_tag = .); 26 | QUAD(0x2187F79E8612DE07); /* Stivale2 memmap identifier */ 27 | QUAD(0); /* Next */ 28 | QUAD(5); /* Memory map entries */ 29 | 30 | QUAD(0); /* MMIO base region */ 31 | QUAD(0xC000000); 32 | LONG(2); 33 | LONG(0); 34 | 35 | QUAD(__sram_base); /* SRAM region */ 36 | QUAD(__sram_base + __sram_size); 37 | LONG(0x1000); /* Reclaimable (Should it be?) */ 38 | LONG(0); 39 | 40 | *(.data.memmap); 41 | 42 | *(.data*) 43 | *(.rodata*) 44 | *(.bss*) 45 | *(.COMMON) 46 | . = ALIGN(0x800); 47 | vbar = .; 48 | *(.text.vbar) 49 | *(.text*) 50 | #. = ALIGN(0x800); 51 | #*(.debugger*) 52 | }: blob 53 | __blob_end = .; 54 | . = ALIGN(4K); 55 | . += 4K; 56 | __boot_stack = .; 57 | . += 4K; 58 | __debugger_stack = .; 59 | 60 | /DISCARD/ : { 61 | *(.eh_frame*) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/platform/sdm665_aarch64/main.zig: -------------------------------------------------------------------------------- 1 | pub const sabaton = @import("../../sabaton.zig"); 2 | 3 | fn uartReg(offset: usize) *volatile u32 { 4 | return @intToPtr(*volatile u32, 0x4A90000 + offset); 5 | } 6 | 7 | pub const io = struct { 8 | pub fn putchar(ch: u8) void { 9 | uartReg(0x600).* = 0x08000000; 10 | uartReg(0x270).* = 1; 11 | uartReg(0x700).* = ch; 12 | uartReg(0x61C).* = 0x40000000; 13 | uartReg(0x620).* = 0x40000000; 14 | } 15 | }; 16 | 17 | pub export fn _main() callconv(.C) noreturn { 18 | sabaton.main(); 19 | } 20 | 21 | fn get_sram() []u8 { 22 | return @intToPtr([*]u8, 0xC100000)[0..0x200000]; 23 | } 24 | 25 | pub fn get_dram() []u8 { 26 | return @intToPtr([*]u8, 0x45E10000)[0..0x20000000]; 27 | } 28 | 29 | pub fn get_kernel() [*]u8 { 30 | return @intToPtr([*]u8, 0x42424242); 31 | } 32 | 33 | pub fn add_platform_tags(kernel_header: *sabaton.Stivale2hdr) void { 34 | _ = kernel_header; 35 | } 36 | 37 | pub fn map_platform(root: *sabaton.paging.Root) void { 38 | _ = root; 39 | } 40 | 41 | pub fn get_page_size() usize { 42 | return 0x1000; 43 | } 44 | -------------------------------------------------------------------------------- /src/platform/timer.zig: -------------------------------------------------------------------------------- 1 | // aarch64 generic timer 2 | 3 | const sabaton = @import("root").sabaton; 4 | const platform = sabaton.platform; 5 | 6 | pub fn init() void {} 7 | 8 | pub fn get_ticks() usize { 9 | return asm volatile ("MRS %[out], CNTPCT_EL0" 10 | : [out] "=r" (-> usize) 11 | ); 12 | } 13 | 14 | pub fn get_freq() usize { 15 | return asm ("MRS %[out], CNTFRQ_EL0" 16 | : [out] "=r" (-> usize) 17 | ); 18 | } 19 | 20 | pub fn get_us() usize { 21 | return (get_ticks() * 1000000) / get_freq(); 22 | } 23 | 24 | pub fn sleep_us(us: usize) void { 25 | const start_us = get_us(); 26 | while (start_us + us > get_us()) {} 27 | } 28 | -------------------------------------------------------------------------------- /src/platform/uefi_aarch64/acpi.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const uefi = std.os.uefi; 3 | 4 | const sabaton = @import("root").sabaton; 5 | 6 | const locateConfiguration = @import("root").locateConfiguration; 7 | 8 | pub fn init() callconv(.Inline) void { 9 | if (locateConfiguration(uefi.tables.ConfigurationTable.acpi_20_table_guid)) |tbl| { 10 | sabaton.add_rsdp(@ptrToInt(tbl)); 11 | return; 12 | } 13 | 14 | if (locateConfiguration(uefi.tables.ConfigurationTable.acpi_10_table_guid)) |tbl| { 15 | sabaton.add_rsdp(@ptrToInt(tbl)); 16 | return; 17 | } 18 | 19 | sabaton.puts("No ACPI table found!\n"); 20 | } 21 | -------------------------------------------------------------------------------- /src/platform/uefi_aarch64/framebuffer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const uefi = std.os.uefi; 3 | 4 | const sabaton = @import("root").sabaton; 5 | 6 | const root = @import("root"); 7 | 8 | pub fn init(page_root: *sabaton.paging.Root) callconv(.Inline) void { 9 | const graphicsProto = root.locateProtocol(uefi.protocols.GraphicsOutputProtocol) orelse { 10 | sabaton.puts("No graphics protocol found!\n"); 11 | return; 12 | }; 13 | 14 | sabaton.add_framebuffer(graphicsProto.mode.frame_buffer_base); 15 | 16 | const mode = graphicsProto.mode.info; 17 | 18 | switch (mode.pixel_format) { 19 | .PixelRedGreenBlueReserved8BitPerColor, 20 | .PixelBlueGreenRedReserved8BitPerColor, 21 | => { 22 | sabaton.fb.bpp = 32; 23 | sabaton.fb.red_mask_size = 8; 24 | sabaton.fb.blue_mask_size = 8; 25 | sabaton.fb.green_mask_size = 8; 26 | }, 27 | .PixelBitMask => { 28 | const mask = mode.pixel_information; 29 | sabaton.fb.bpp = if (mask.reserved_mask == 0) 24 else 32; 30 | }, 31 | else => unreachable, 32 | } 33 | 34 | switch (mode.pixel_format) { 35 | .PixelRedGreenBlueReserved8BitPerColor => { 36 | sabaton.fb.red_mask_shift = 0; 37 | sabaton.fb.green_mask_shift = 8; 38 | sabaton.fb.blue_mask_shift = 16; 39 | }, 40 | .PixelBlueGreenRedReserved8BitPerColor => { 41 | sabaton.fb.blue_mask_shift = 0; 42 | sabaton.fb.green_mask_shift = 8; 43 | sabaton.fb.red_mask_shift = 16; 44 | }, 45 | .PixelBitMask => { 46 | const mask = mode.pixel_information; 47 | sabaton.fb.red_mask_shift = @ctz(u32, mask.red_mask); 48 | sabaton.fb.red_mask_size = @popCount(u32, mask.red_mask); 49 | sabaton.fb.green_mask_shift = @ctz(u32, mask.green_mask); 50 | sabaton.fb.green_mask_size = @popCount(u32, mask.green_mask); 51 | sabaton.fb.blue_mask_shift = @ctz(u32, mask.blue_mask); 52 | sabaton.fb.blue_mask_size = @popCount(u32, mask.blue_mask); 53 | }, 54 | else => unreachable, 55 | } 56 | 57 | sabaton.fb.width = @intCast(u16, mode.horizontal_resolution); 58 | sabaton.fb.height = @intCast(u16, mode.vertical_resolution); 59 | sabaton.fb.pitch = @intCast(u16, mode.pixels_per_scan_line * sabaton.fb.bpp / 8); 60 | 61 | if (!root.memmap.containsAddr(sabaton.fb.addr)) { 62 | // We need to map it ourselves 63 | const fbsz = @as(u64, sabaton.fb.pitch) * @as(u64, sabaton.fb.height); 64 | sabaton.paging.map(sabaton.fb.addr, sabaton.fb.addr, fbsz, .rw, .writethrough, page_root); 65 | sabaton.paging.map(sabaton.upper_half_phys_base + sabaton.fb.addr, sabaton.fb.addr, fbsz, .rw, .writethrough, page_root); 66 | } 67 | 68 | sabaton.puts("Mapped framebuffer!\n"); 69 | } 70 | -------------------------------------------------------------------------------- /src/platform/uefi_aarch64/fs.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const uefi = std.os.uefi; 3 | 4 | const root = @import("root"); 5 | const sabaton = root.sabaton; 6 | const toUtf16 = root.toUtf16; 7 | 8 | pub fn loadFile(filesystem: *uefi.protocols.FileProtocol, comptime path: []const u8) ![]u8 { 9 | const utf16_path = comptime toUtf16(path); 10 | 11 | var file: *const uefi.protocols.FileProtocol = undefined; 12 | root.uefiVital(filesystem.open(&file, &utf16_path, uefi.protocols.FileProtocol.efi_file_mode_read, 0), "loadFile: open"); 13 | 14 | var position = uefi.protocols.FileProtocol.efi_file_position_end_of_file; 15 | root.uefiVital(file.setPosition(position), "loadFile: setPosition(EOF)"); 16 | root.uefiVital(file.getPosition(&position), "loadFile: getPosition(file size)"); 17 | root.uefiVital(file.setPosition(0), "loadFile: setPosition(0)"); 18 | 19 | const file_space = sabaton.vital(root.allocator.alloc(u8, position), "loadFile: Allocating file memory", true); 20 | var bufsz = file_space.len; 21 | 22 | root.uefiVital(file.read(&bufsz, file_space.ptr), "loadFile: read"); 23 | 24 | return file_space; 25 | } 26 | -------------------------------------------------------------------------------- /src/platform/uefi_aarch64/main.zig: -------------------------------------------------------------------------------- 1 | pub const sabaton = @import("../../sabaton.zig"); 2 | 3 | const std = @import("std"); 4 | const uefi = std.os.uefi; 5 | 6 | const fs = @import("fs.zig"); 7 | 8 | var conout: ?*uefi.protocols.SimpleTextOutputProtocol = null; 9 | 10 | pub const panic = sabaton.panic; 11 | 12 | pub const io = struct { 13 | pub fn putchar(ch: u8) void { 14 | if (conout) |co| { 15 | if (ch == '\n') 16 | putchar('\r'); 17 | const chrarr = [2]u16{ ch, 0 }; 18 | _ = co.outputString(@ptrCast(*const [1:0]u16, &chrarr)); 19 | } 20 | } 21 | }; 22 | 23 | pub inline fn locateProtocol(comptime T: type) ?*T { 24 | var ptr: *T = undefined; 25 | const guid: std.os.uefi.Guid align(8) = T.guid; 26 | if (uefi.system_table.boot_services.?.locateProtocol(&guid, null, @ptrCast(*?*anyopaque, &ptr)) != .Success) { 27 | return null; 28 | } 29 | return ptr; 30 | } 31 | 32 | pub inline fn handleProtocol(handle: uefi.Handle, comptime T: type) ?*T { 33 | var ptr: *T = undefined; 34 | const guid: std.os.uefi.Guid align(8) = T.guid; 35 | if (uefi.system_table.boot_services.?.handleProtocol(handle, &guid, @ptrCast(*?*anyopaque, &ptr)) != .Success) { 36 | return null; 37 | } 38 | return ptr; 39 | } 40 | 41 | pub fn locateConfiguration(guid: uefi.Guid) ?*anyopaque { 42 | const entries = uefi.system_table.configuration_table[0..uefi.system_table.number_of_table_entries]; 43 | for (entries) |e| { 44 | if (e.vendor_guid.eql(guid)) 45 | return e.vendor_table; 46 | } 47 | return null; 48 | } 49 | 50 | pub fn toUtf16(comptime ascii: []const u8) [ascii.len:0]u16 { 51 | const curr = [1:0]u16{ascii[0]}; 52 | if (ascii.len == 1) return curr; 53 | return curr ++ toUtf16(ascii[1..]); 54 | } 55 | 56 | pub fn uefiVital(status: uefi.Status, context: [*:0]const u8) void { 57 | switch (status) { 58 | .Success => {}, 59 | else => |t| { 60 | sabaton.puts("Fatal error: "); 61 | sabaton.print_str(@tagName(t)); 62 | sabaton.puts(", while: "); 63 | sabaton.puts(context); 64 | @panic(""); 65 | }, 66 | } 67 | } 68 | 69 | pub fn uefiVitalFail(status: uefi.Status, context: [*:0]const u8) noreturn { 70 | uefiVital(status, context); 71 | unreachable; 72 | } 73 | 74 | pub const allocator_impl = struct { 75 | vtab: std.mem.Allocator.VTable = .{ 76 | .alloc = allocate, 77 | .resize = resize, 78 | .free = free, 79 | }, 80 | 81 | fn allocate(_: *anyopaque, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) std.mem.Allocator.Error![]u8 { 82 | _ = ret_addr; 83 | _ = len_align; 84 | 85 | var ptr: [*]u8 = undefined; 86 | 87 | if (ptr_align > 8) { 88 | uefiVital(uefi.system_table.boot_services.?.allocatePages(.AllocateAnyPages, .LoaderData, (len + 0xFFF) / 0x1000, @ptrCast(*[*]align(0x1000) u8, &ptr)), "Allocating pages"); 89 | } else { 90 | uefiVital(uefi.system_table.boot_services.?.allocatePool(.LoaderData, len, @ptrCast(*[*]align(8) u8, &ptr)), "Allocating memory"); 91 | } 92 | 93 | return ptr[0..len]; 94 | } 95 | 96 | fn resize(_: *anyopaque, old_mem: []u8, old_align: u29, new_size: usize, len_align: u29, ret_addr: usize) ?usize { 97 | _ = ret_addr; 98 | _ = len_align; 99 | _ = new_size; 100 | _ = old_align; 101 | _ = old_mem; 102 | sabaton.puts("allocator resize!!\n"); 103 | @panic(""); 104 | } 105 | 106 | fn free(_: *anyopaque, old_mem: []u8, old_align: u29, ret_addr: usize) void { 107 | _ = ret_addr; 108 | _ = old_align; 109 | _ = old_mem; 110 | sabaton.puts("allocator free!!\n"); 111 | @panic(""); 112 | } 113 | }{}; 114 | 115 | pub var allocator = std.mem.Allocator{ 116 | .ptr = undefined, 117 | .vtable = &allocator_impl.vtab, 118 | }; 119 | 120 | fn findFSRoot() *uefi.protocols.FileProtocol { 121 | // zig fmt: off 122 | const loaded_image = handleProtocol(uefi.handle, uefi.protocols.LoadedImageProtocol) 123 | orelse @panic("findFSRoot(): Could not get loaded image protocol"); 124 | 125 | const device = loaded_image.device_handle orelse @panic("findFSRoot(): No loaded file device handle!"); 126 | 127 | const simple_file_proto = handleProtocol(device, uefi.protocols.SimpleFileSystemProtocol) 128 | orelse @panic("findFSRoot(): Could not get simple file system"); 129 | // zig fmt: on 130 | 131 | var file_proto: *uefi.protocols.FileProtocol = undefined; 132 | switch (simple_file_proto.openVolume(&file_proto)) { 133 | .Success => return file_proto, 134 | else => @panic("findFSRoot(): openVolume failed!"), 135 | } 136 | } 137 | 138 | var page_size: u64 = 0x1000; 139 | 140 | pub fn get_page_size() u64 { 141 | return page_size; 142 | } 143 | 144 | var paging_root: sabaton.paging.Root = undefined; 145 | 146 | // O(n^2) but who cares, it's really small code 147 | fn sortStivale2Memmap(data_c: []align(8) u8) void { 148 | var data = data_c; 149 | 150 | while (true) { 151 | const num_entries = data.len / 0x18; 152 | if (num_entries < 2) 153 | return; 154 | 155 | var curr_min_i: usize = 0; 156 | var curr_min_addr = std.mem.readIntNative(u64, data[0..8]); 157 | 158 | // First let's find the smallest addr among the rest 159 | var i: usize = 1; 160 | 161 | while (i < num_entries) : (i += 1) { 162 | const curr_addr = std.mem.readIntNative(u64, data[i * 0x18 ..][0..8]); 163 | if (curr_addr < curr_min_addr) { 164 | curr_min_addr = curr_addr; 165 | curr_min_i = i; 166 | } 167 | } 168 | 169 | // Swap the current entry with the smallest one 170 | std.mem.swap([0x18]u8, data[0..0x18], data[curr_min_i * 0x18 ..][0..0x18]); 171 | 172 | data = data[0x18..]; 173 | } 174 | } 175 | 176 | const MemoryMap = struct { 177 | memory_map: []align(8) u8, 178 | key: usize, 179 | desc_size: usize, 180 | desc_version: u32, 181 | 182 | const memory_map_size = 64 * 1024; 183 | 184 | const Iterator = struct { 185 | map: *const MemoryMap, 186 | curr_offset: usize = 0, 187 | 188 | fn next(self: *@This()) ?*uefi.tables.MemoryDescriptor { 189 | if (self.curr_offset + @offsetOf(uefi.tables.MemoryDescriptor, "attribute") >= self.map.memory_map.len) 190 | return null; 191 | 192 | const result = @ptrCast(*uefi.tables.MemoryDescriptor, @alignCast(8, self.map.memory_map.ptr + self.curr_offset)); 193 | self.curr_offset += self.map.desc_size; 194 | return result; 195 | } 196 | }; 197 | 198 | fn fetch(self: *@This()) void { 199 | self.memory_map.len = memory_map_size; 200 | uefiVital(uefi.system_table.boot_services.?.getMemoryMap( 201 | &self.memory_map.len, 202 | @ptrCast([*]uefi.tables.MemoryDescriptor, @alignCast(8, self.memory_map.ptr)), // Cast is workaround for the wrong zig type annotation 203 | &self.key, 204 | &self.desc_size, 205 | &self.desc_version, 206 | ), "Getting UEFI memory map"); 207 | } 208 | 209 | pub fn containsAddr(self: *const @This(), addr: usize) bool { 210 | var iter = Iterator{ .map = self }; 211 | 212 | while (iter.next()) |e| { 213 | const start_addr = e.physical_start; 214 | const end_addr = start_addr + e.number_of_pages * page_size; 215 | if (start_addr <= addr and addr < end_addr) 216 | return true; 217 | } 218 | 219 | return false; 220 | } 221 | 222 | fn parse_to_stivale2(self: *const @This(), stivale2buf: []align(8) u8) void { 223 | var iter = Iterator{ .map = self }; 224 | 225 | std.mem.writeIntNative(u64, stivale2buf[0x00..0x08], 0x2187F79E8612DE07); 226 | //std.mem.writeIntNative(u64, stivale2buf[0x08..0x10], 0); // Next ptr 227 | const num_entries = @ptrCast(*u64, &stivale2buf[0x10]); 228 | num_entries.* = 0; 229 | 230 | var stivale2ents = stivale2buf[0x18..]; 231 | 232 | while (iter.next()) |e| : ({ 233 | num_entries.* += 1; 234 | stivale2ents = stivale2ents[0x18..]; 235 | }) { 236 | std.mem.writeIntNative(u64, stivale2ents[0x00..0x08], e.physical_start); 237 | std.mem.writeIntNative(u64, stivale2ents[0x08..0x10], e.number_of_pages * page_size); 238 | //std.mem.writeIntNative(u32, stivale2ents[0x14..0x18], stiavle2_reserved); 239 | std.mem.writeIntNative(u32, stivale2ents[0x10..0x14], @as(u32, switch (e.type) { 240 | .ReservedMemoryType, 241 | .UnusableMemory, 242 | .MemoryMappedIO, 243 | .MemoryMappedIOPortSpace, 244 | .PalCode, 245 | .PersistentMemory, 246 | .RuntimeServicesCode, 247 | .RuntimeServicesData, 248 | => 2, // RESERVED 249 | 250 | // We load all kernel code segments as LoaderData, should probably be changed to reclaim more memory here 251 | .LoaderData => 0x1001, // KERNEL_AND_MODULES 252 | .LoaderCode => 0x1000, // BOOTLOADER_RECLAIMABLE 253 | 254 | // Boot services entries are marked as usable since we've 255 | // already exited boot services when we enter the kernel 256 | .BootServicesCode, 257 | .BootServicesData, 258 | => 1, // USABLE 259 | 260 | .ConventionalMemory => 1, // USABLE 261 | 262 | .ACPIReclaimMemory => 3, // ACPI_RECLAIMABLE 263 | 264 | .ACPIMemoryNVS => 4, // ACPI_NVS 265 | 266 | else => @panic("Bad memory map type"), 267 | })); 268 | } 269 | 270 | sortStivale2Memmap(stivale2buf[0x18..0x18 * (num_entries.* + 1)]); 271 | } 272 | 273 | fn map_everything(self: *const @This(), root: *sabaton.paging.Root) void { 274 | var iter = Iterator{ .map = self }; 275 | 276 | while (iter.next()) |e| { 277 | if (sabaton.safety) { 278 | sabaton.print_hex(e.physical_start); 279 | sabaton.puts(", "); 280 | sabaton.print_hex(e.physical_start + e.number_of_pages * page_size); 281 | sabaton.puts(": "); 282 | sabaton.print_str(@tagName(e.type)); 283 | sabaton.puts("\n"); 284 | } 285 | 286 | const memory_type: sabaton.paging.MemoryType = switch (e.type) { 287 | .ReservedMemoryType, 288 | .LoaderCode, 289 | .LoaderData, 290 | .BootServicesCode, 291 | .BootServicesData, 292 | .RuntimeServicesCode, 293 | .RuntimeServicesData, 294 | .ConventionalMemory, 295 | => .writeback, 296 | .UnusableMemory, 297 | .ACPIReclaimMemory, 298 | .PersistentMemory, 299 | => .writethrough, 300 | .ACPIMemoryNVS, 301 | .MemoryMappedIO, 302 | .MemoryMappedIOPortSpace, 303 | => .mmio, 304 | else => continue, 305 | }; 306 | const perms: sabaton.paging.Perms = switch (e.type) { 307 | .ReservedMemoryType, 308 | .LoaderData, 309 | .BootServicesCode, 310 | .BootServicesData, 311 | .RuntimeServicesData, 312 | .ConventionalMemory, 313 | .UnusableMemory, 314 | .ACPIReclaimMemory, 315 | .PersistentMemory, 316 | .ACPIMemoryNVS, 317 | .MemoryMappedIO, 318 | .MemoryMappedIOPortSpace, 319 | => .rw, 320 | .LoaderCode, 321 | .RuntimeServicesCode, 322 | => .rwx, 323 | else => continue, 324 | }; 325 | 326 | sabaton.paging.map(e.physical_start, e.physical_start, e.number_of_pages * page_size, perms, memory_type, root); 327 | sabaton.paging.map(sabaton.upper_half_phys_base + e.physical_start, e.physical_start, e.number_of_pages * page_size, perms, memory_type, root); 328 | } 329 | } 330 | 331 | fn init(self: *@This()) void { 332 | self.memory_map.ptr = @alignCast(8, sabaton.vital(allocator.alloc(u8, memory_map_size), "Allocating for UEFI memory map", true).ptr); 333 | self.fetch(); 334 | } 335 | }; 336 | 337 | comptime { 338 | asm ( 339 | // zig fmt: off 340 | \\switch_el2_to_el1: 341 | \\ MRS X1, SCTLR_EL2 342 | \\ MSR SCTLR_EL1, X1 343 | 344 | \\ // aarch64 in EL1 345 | \\ ORR X1, XZR, #(1 << 31) 346 | \\ ORR X1, X1, #(1 << 1) 347 | \\ MSR HCR_EL2, X1 348 | 349 | \\ // Counters in EL1 350 | \\ MRS X1, CNTHCTL_EL2 351 | \\ ORR X1, X1, #3 352 | \\ MSR CNTHCTL_EL2, X1 353 | \\ MSR CNTVOFF_EL2, XZR 354 | 355 | \\ // FP/SIMD in EL1 356 | \\ MOV X1, #0x33FF 357 | \\ MSR CPTR_EL2, X1 358 | \\ MSR HSTR_EL2, XZR 359 | \\ MOV X1, #0x300000 360 | \\ MSR CPACR_EL1, X1 361 | 362 | \\ // Get the fuck out of EL2 into EL1 363 | \\ ADR X1, EL1 364 | \\ MSR ELR_EL2, X1 365 | \\ MOV X1, #0x3C5 366 | \\ MSR SPSR_EL2, X1 367 | \\ MOV X1, SP 368 | \\ ERET 369 | \\EL1: 370 | \\ MOV SP, X1 371 | \\ RET 372 | // zig fmt: on 373 | ); 374 | } 375 | 376 | extern fn switch_el2_to_el1() void; 377 | 378 | fn maybe_switch_EL() void { 379 | const current_el = (asm volatile ("MRS %[el], CurrentEL" 380 | : [el] "=r" (-> u64) 381 | ) >> 2) & 0x3; 382 | 383 | if (current_el == 3) 384 | unreachable; // Todo: implement 385 | 386 | if (current_el > 1) 387 | switch_el2_to_el1(); 388 | } 389 | 390 | pub var memmap: MemoryMap = undefined; 391 | 392 | pub fn main() noreturn { 393 | if (locateProtocol(uefi.protocols.SimpleTextOutputProtocol)) |proto| { 394 | conout = proto; 395 | } 396 | 397 | page_size = sabaton.paging.detect_page_size(); 398 | 399 | // Find RSDP 400 | @import("acpi.zig").init(); 401 | 402 | // Find the root FS we booted from 403 | const root = findFSRoot(); 404 | 405 | const kernel_file_bytes = sabaton.vital(fs.loadFile(root, "kernel.elf"), "Loading kernel ELF (esp\\kernel.elf)", true); 406 | 407 | // Create the stivale2 tag for the kernel ELF file 408 | sabaton.kernel_file_tag.kernel_addr = @ptrToInt(kernel_file_bytes.ptr); 409 | sabaton.add_tag(&sabaton.kernel_file_tag.tag); 410 | 411 | var kernel_elf_file = sabaton.Elf{ 412 | .data = kernel_file_bytes.ptr, 413 | }; 414 | kernel_elf_file.init(); 415 | 416 | var kernel_stivale2_header: sabaton.Stivale2hdr = undefined; 417 | _ = sabaton.vital( 418 | kernel_elf_file.load_section(".stivale2hdr", sabaton.util.to_byte_slice(&kernel_stivale2_header)), 419 | "loading .stivale2hdr", 420 | true, 421 | ); 422 | 423 | const kernel_memory_bytes = sabaton.vital(allocator.alignedAlloc(u8, 0x1000, kernel_elf_file.paged_bytes()), "Allocating kernel memory", true); 424 | 425 | // Prepare a paging root for the kernel 426 | paging_root = sabaton.paging.init_paging(); 427 | 428 | // Load the kernel into memory 429 | kernel_elf_file.load(kernel_memory_bytes, &paging_root); 430 | 431 | // Ought to be enough for any firmwares crappy memory layout, right? 432 | const stivale2_memmap_bytes = @alignCast(8, sabaton.vital(allocator.alloc(u8, 64 * 1024), "Allocating for stivale2 memory map", true)); 433 | 434 | // Get the memory map to calculate a max address used by UEFI 435 | memmap.init(); 436 | 437 | // Get a framebuffer 438 | @import("framebuffer.zig").init(&paging_root); 439 | 440 | sabaton.log_hex("Framebuffer at ", sabaton.fb.addr); 441 | 442 | memmap.map_everything(&paging_root); 443 | 444 | // Now we need a memory map to exit boot services 445 | memmap.fetch(); 446 | 447 | memmap.parse_to_stivale2(stivale2_memmap_bytes); 448 | sabaton.add_tag(@ptrCast(*sabaton.Stivale2tag, stivale2_memmap_bytes.ptr)); 449 | 450 | uefiVital(uefi.system_table.boot_services.?.exitBootServices(uefi.handle, memmap.key), "Exiting boot services"); 451 | 452 | // We can't call UEFI after exiting boot services 453 | conout = null; 454 | uefi.system_table.boot_services = null; 455 | 456 | sabaton.paging.apply_paging(&paging_root); 457 | 458 | maybe_switch_EL(); 459 | 460 | sabaton.enterKernel(&kernel_elf_file, kernel_stivale2_header.stack); 461 | } 462 | -------------------------------------------------------------------------------- /src/platform/virt_aarch64/display.zig: -------------------------------------------------------------------------------- 1 | const sabaton = @import("root").sabaton; 2 | 3 | fn try_find(comptime f: anytype, comptime name: []const u8) bool { 4 | const retval = f(); 5 | if (retval) { 6 | sabaton.puts("Found " ++ name ++ "!\n"); 7 | } else { 8 | sabaton.puts("Couldn't find " ++ name ++ "\n"); 9 | } 10 | return retval; 11 | } 12 | 13 | pub fn init() void { 14 | // First, try to find a ramfb 15 | if (try_find(sabaton.ramfb.init, "ramfb")) 16 | return; 17 | 18 | sabaton.puts("Kernel requested framebuffer but we could not provide one!\n"); 19 | } 20 | -------------------------------------------------------------------------------- /src/platform/virt_aarch64/entry.S: -------------------------------------------------------------------------------- 1 | .global _start 2 | 3 | .section .text.entry 4 | _start: 5 | // Disable interrupts 6 | MSR DAIFSET, #0xF 7 | 8 | // Enable UART 9 | LDR X0, uart_reg 10 | 11 | // IBRD 12 | MOV W1, 0x10 13 | STR W1, [X0, #0x24] 14 | 15 | // CR 16 | MOV W1, 0xC301 17 | STR W1, [X0, #0x30] 18 | 19 | // Relocate ourselves 20 | ADR X0, _start 21 | LDR X1, relocation_base 22 | LDR X2, relocation_end 23 | 24 | .relocate_loop: 25 | CMP X1, X2 26 | B.EQ .relocate_done 27 | 28 | // I also know how to write bzero like this :) 29 | LDP X3, X4, [X0, #0x00] 30 | STP X3, X4, [X1, #0x00] 31 | 32 | ADD X0, X0, #0x10 33 | ADD X1, X1, #0x10 34 | 35 | B .relocate_loop 36 | .relocate_done: 37 | 38 | // Jump to relocated code 39 | LDR X1, relocation_base 40 | ADD X1, X1, .cont - _start 41 | BR X1 42 | 43 | .cont: 44 | // Set up an early stack 45 | MSR SPSel, #0 46 | ADR X18, _start 47 | MOV SP, X18 48 | 49 | B _main 50 | 51 | .global devicetree_tag 52 | .global uart_tag 53 | .global uart_reg 54 | .section .data.stivale_tags 55 | .balign 8 56 | platform_tags: 57 | devicetree_tag: 58 | .8byte 0xabb29bd49a2833fa // DeviceTree 59 | .8byte 0 60 | .8byte __dram_base 61 | .8byte 0x100000 62 | 63 | .balign 8 64 | uart_tag: 65 | .8byte 0xb813f9b8dbc78797 // u32 MMIO UART 66 | .8byte 0 67 | uart_reg: 68 | .8byte 0x9000000 69 | 70 | .global kernel_file_loc 71 | .section .rodata 72 | .balign 8 73 | kernel_file_loc: 74 | .8byte __kernel_file_loc 75 | relocation_base: 76 | .8byte __blob_base 77 | relocation_end: 78 | .8byte __blob_end 79 | -------------------------------------------------------------------------------- /src/platform/virt_aarch64/linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | OUTPUT_FORMAT(elf64-littleaarch64) 3 | OUTPUT_ARCH(aarch64) 4 | 5 | PHDRS { 6 | none PT_NULL FLAGS(0); 7 | } 8 | 9 | __kernel_file_loc = 0x4000000; 10 | __dram_base = 1024M; 11 | 12 | SECTIONS { 13 | . = 0x40100000; 14 | .blob : { 15 | __blob_base = .; 16 | *(.text.entry) 17 | KEEP(*(.text.main)) 18 | *(.text.smp_stub) 19 | KEEP(*(.text.smp_entry)) 20 | *(.text*) 21 | 22 | . = ALIGN(8); 23 | PROVIDE(memmap_tag = .); 24 | QUAD(0x2187F79E8612DE07); /* Stivale2 memmap identifier */ 25 | QUAD(0); /* Next */ 26 | QUAD(7); /* Memory map entries */ 27 | 28 | QUAD(0); /* MMIO base region */ 29 | QUAD(__dram_base); 30 | LONG(2); 31 | LONG(0); 32 | 33 | *(.data.memmap); 34 | 35 | QUAD(0x4010000000); /* PCI ECAM */ 36 | QUAD(0x10000000); 37 | LONG(2); 38 | LONG(0); 39 | 40 | QUAD(0x8000000000); /* PCI-E high mmio (64 bit bar space) */ 41 | QUAD(0x8000000000); 42 | LONG(2); 43 | LONG(0); 44 | 45 | *(.data*) 46 | *(.rodata*) 47 | *(.bss*) 48 | . = ALIGN(16); 49 | __blob_end = .; 50 | } 51 | 52 | . = ALIGN(4K); 53 | __pmm_base = .; 54 | 55 | .eh_frame : { 56 | *(.eh_frame*) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/platform/virt_aarch64/main.zig: -------------------------------------------------------------------------------- 1 | pub const sabaton = @import("../../sabaton.zig"); 2 | pub const panic = sabaton.panic; 3 | 4 | pub usingnamespace @import("../drivers/virt.zig"); 5 | 6 | pub fn get_uart_info() @This().io.Info { 7 | const base = 0x9000000; 8 | return .{ 9 | .uart = @intToPtr(*volatile u32, base), 10 | }; 11 | } 12 | 13 | pub fn get_dtb() []u8 { 14 | return sabaton.near("dram_base").read([*]u8)[0..0x100000]; 15 | } 16 | 17 | var page_size: u64 = 0x1000; 18 | 19 | pub fn get_page_size() u64 { 20 | return page_size; 21 | } 22 | 23 | export fn _main() linksection(".text.main") noreturn { 24 | page_size = sabaton.paging.detect_page_size(); 25 | sabaton.fw_cfg.init_from_dtb(); 26 | @call(.{ .modifier = .always_inline }, sabaton.main, .{}); 27 | } 28 | 29 | pub fn map_platform(root: *sabaton.paging.Root) void { 30 | sabaton.paging.map(0, 0, 1024 * 1024 * 1024, .rw, .mmio, root); 31 | sabaton.paging.map(sabaton.upper_half_phys_base, 0, 1024 * 1024 * 1024, .rw, .mmio, root); 32 | sabaton.puts("Initing pci!\n"); 33 | sabaton.pci.init_from_dtb(root); 34 | } -------------------------------------------------------------------------------- /src/platform/virt_riscv64/entry.S: -------------------------------------------------------------------------------- 1 | .global _start 2 | 3 | .section .text.entry 4 | 5 | _start: 6 | AUIPC t0, %pcrel_hi(_start) 7 | 8 | // Disable interrupts 9 | //MSR DAIFSET, #0xF 10 | 11 | // Check if non-first core 12 | loop: 13 | BNE a0, zero, loop 14 | 15 | // Relocate ourselves 16 | LD t1, %lo(relocation_base)(t0) 17 | MV sp, t1 18 | LD t2, %lo(relocation_end)(t0) 19 | 20 | reloc_loop: 21 | LD t3, (t0) 22 | SD t3, (t1) 23 | ADDI t0, t0, 8 24 | ADDI t1, t1, 8 25 | BLTU t1, t2, reloc_loop 26 | 27 | ADDI t0, sp, %lo(.cont) 28 | JR t0 29 | 30 | .cont: 31 | J _main 32 | 33 | .balign 8 34 | relocation_base: 35 | .8byte __blob_base 36 | relocation_end: 37 | .8byte __blob_end 38 | 39 | .global devicetree_tag 40 | .section .data.stivale_tags 41 | .balign 8 42 | platform_tags: 43 | devicetree_tag: 44 | .8byte 0xabb29bd49a2833fa // DeviceTree 45 | .8byte 0 46 | .8byte __dram_base 47 | .8byte 0x100000 48 | 49 | .balign 8 50 | .global uart_tag 51 | .global uart_reg 52 | uart_tag: 53 | .8byte 0xb813f9b8dbc78797 // u32 MMIO UART 54 | .8byte 0 55 | uart_reg: 56 | .8byte 0x10000000 57 | -------------------------------------------------------------------------------- /src/platform/virt_riscv64/linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | 3 | PHDRS { 4 | none PT_NULL FLAGS(0); 5 | } 6 | 7 | __kernel_file_loc = 0x4000000; 8 | __dram_base = 0x80000000; 9 | 10 | SECTIONS { 11 | . = 0x80100000; 12 | .blob : { 13 | __blob_base = .; 14 | *(.text.entry) 15 | KEEP(*(.text.main)) 16 | *(.text.smp_stub) 17 | KEEP(*(.text.smp_entry)) 18 | *(.text*) 19 | 20 | . = ALIGN(8); 21 | PROVIDE(memmap_tag = .); 22 | QUAD(0x2187F79E8612DE07); /* Stivale2 memmap identifier */ 23 | QUAD(0); /* Next */ 24 | QUAD(7); /* Memory map entries */ 25 | 26 | QUAD(0); /* MMIO base region */ 27 | QUAD(__dram_base); 28 | LONG(2); 29 | LONG(0); 30 | 31 | *(.data.memmap); 32 | 33 | QUAD(0x30000000); /* PCI ECAM */ 34 | QUAD(0x10000000); 35 | LONG(2); 36 | LONG(0); 37 | 38 | QUAD(0x40000000); /* PCI-E high mmio (64 bit bar space) */ 39 | QUAD(0x40000000); 40 | LONG(2); 41 | LONG(0); 42 | 43 | *(.data*) 44 | *(.sdata*) 45 | *(.rodata*) 46 | *(.bss*) 47 | *(.sbss*) 48 | . = ALIGN(16); 49 | __blob_end = .; 50 | } 51 | 52 | . = ALIGN(4K); 53 | __pmm_base = .; 54 | 55 | .eh_frame : { 56 | *(.eh_frame*) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/platform/virt_riscv64/main.zig: -------------------------------------------------------------------------------- 1 | pub const sabaton = @import("../../sabaton.zig"); 2 | pub const panic = sabaton.panic; 3 | 4 | pub usingnamespace @import("../drivers/virt.zig"); 5 | 6 | pub fn get_uart_info() @This().io.Info { 7 | const base = 0x10000000; 8 | return .{ 9 | .uart = @intToPtr(*volatile u32, base), 10 | }; 11 | } 12 | 13 | var dtb_base: u64 = undefined; 14 | 15 | pub fn get_dtb() []u8 { 16 | return @intToPtr([*]u8, dtb_base)[0..0x100000]; 17 | } 18 | 19 | pub fn get_page_size() u64 { 20 | return 0x1000; 21 | } 22 | 23 | var had_exception = false; 24 | 25 | fn handle_hiccup_supervisor() noreturn { 26 | asm volatile( 27 | \\ NOP 28 | ); 29 | sabaton.puts("Sabaton caught an exception!\n"); 30 | sabaton.log_hex("mstatus: ", asm volatile( 31 | \\ CSRR %[mstatus], mstatus 32 | : [mstatus] "=r" (->u64) 33 | )); 34 | sabaton.log_hex("scause: ", asm volatile( 35 | \\ CSRR %[scause], scause 36 | : [scause] "=r" (->u64) 37 | )); 38 | sabaton.log_hex("sepc: ", asm volatile( 39 | \\ CSRR %[sepc], sepc 40 | : [sepc] "=r" (->u64) 41 | )); 42 | sabaton.log_hex("stval: ", asm volatile( 43 | \\ CSRR %[stval], stval 44 | : [stval] "=r" (->u64) 45 | )); 46 | while(true) {} 47 | } 48 | 49 | fn handle_hiccup_machine() noreturn { 50 | asm volatile( 51 | \\ NOP 52 | ); 53 | sabaton.puts("Sabaton caught an exception!\n"); 54 | sabaton.log_hex("mcause: ", asm volatile( 55 | \\ CSRR %[mcause], mcause 56 | : [mcause] "=r" (->u64) 57 | )); 58 | sabaton.log_hex("mepc: ", asm volatile( 59 | \\ CSRR %[mepc], mepc 60 | : [mepc] "=r" (->u64) 61 | )); 62 | sabaton.log_hex("mtval: ", asm volatile( 63 | \\ CSRR %[mtval], mtval 64 | : [mtval] "=r" (->u64) 65 | )); 66 | while(true) {} 67 | } 68 | 69 | export fn _main(hart_id: u64, dtb_base_arg: u64) linksection(".text.main") noreturn { 70 | asm volatile( 71 | \\ CSRW pmpcfg0, %[pmp_config] 72 | : 73 | : [pmp_config] "r" (@as(u64, 0b00011111)) 74 | ); 75 | 76 | asm volatile( 77 | \\ CSRW pmpaddr0, %[pmp_addr] 78 | : 79 | : [pmp_addr] "r" (~@as(u64, 0)) 80 | ); 81 | 82 | asm volatile( 83 | \\ CSRW mtvec, %[vec] 84 | : 85 | : [vec] "r" (((@ptrToInt(handle_hiccup_machine) + 2) / 4) * 4) 86 | ); 87 | 88 | asm volatile( 89 | \\ CSRW stvec, %[vec] 90 | : 91 | : [vec] "r" (((@ptrToInt(handle_hiccup_supervisor) + 2) / 4) * 4) 92 | ); 93 | 94 | asm volatile( 95 | \\ CSRW mideleg, %[lmao] 96 | \\ CSRW medeleg, %[lmao] 97 | : 98 | : [lmao] "r" (~@as(u64, 0)) 99 | ); 100 | 101 | _ = hart_id; 102 | dtb_base = dtb_base_arg; 103 | sabaton.fw_cfg.init_from_dtb(); 104 | @call(.{ .modifier = .always_inline }, sabaton.main, .{}); 105 | } 106 | 107 | pub fn map_platform(root: *sabaton.paging.Root) void { 108 | sabaton.paging.map(0, 0, 0x3000_0000, .rw, .mmio, root); 109 | sabaton.paging.map(sabaton.upper_half_phys_base, 0, 0x3000_0000, .rw, .mmio, root); 110 | sabaton.pci.init_from_dtb(root); 111 | } -------------------------------------------------------------------------------- /src/platform/vision_five_v1_riscv64/entry.S: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorenceOS/Sabaton/3c295459f8e7970e36fa6fc480ce0fe281f4357f/src/platform/vision_five_v1_riscv64/entry.S -------------------------------------------------------------------------------- /src/platform/vision_five_v1_riscv64/linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | 3 | PHDRS { 4 | none PT_NULL FLAGS(0); 5 | } 6 | 7 | SECTIONS { 8 | . = 0x18000000; 9 | .blob : { 10 | __blob_base = .; 11 | *(.text.entry) 12 | KEEP(*(.text.main)) 13 | *(.text.smp_stub) 14 | KEEP(*(.text.smp_entry)) 15 | *(.text*) 16 | 17 | #. = ALIGN(8); 18 | #PROVIDE(memmap_tag = .); 19 | #QUAD(0x2187F79E8612DE07); /* Stivale2 memmap identifier */ 20 | #QUAD(0); /* Next */ 21 | #QUAD(4); /* Memory map entries */ 22 | 23 | #*(.data.memmap); 24 | 25 | *(.data*) 26 | *(.rodata*) 27 | *(.bss*) 28 | . = ALIGN(16); 29 | __blob_end = .; 30 | } 31 | 32 | . = ALIGN(4K); 33 | __pmm_base = .; 34 | 35 | .eh_frame : { 36 | *(.eh_frame*) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/platform/vision_five_v1_riscv64/main.zig: -------------------------------------------------------------------------------- 1 | pub const sabaton = @import("../../sabaton.zig"); 2 | pub const io = sabaton.io_impl.status_uart_mmio_32; 3 | //pub const panic = sabaton.panic; 4 | 5 | const uart_regs = @intToPtr([*]volatile u32, 0x12440000); 6 | 7 | const uart_clock = 100000000; 8 | 9 | pub fn get_uart_info() io.Info { 10 | return .{ 11 | .uart = &uart_regs[0], 12 | .status = &uart_regs[5], 13 | .mask = 0x20, 14 | .value = 0x20, 15 | }; 16 | } 17 | 18 | export fn _start() linksection(".text.entry") void { 19 | sabaton.print_str("Hello, world!\n"); 20 | } 21 | -------------------------------------------------------------------------------- /src/sabaton.zig: -------------------------------------------------------------------------------- 1 | pub const platform = @import("root"); 2 | pub const io_impl = @import("io/io.zig"); 3 | pub const util = @import("lib/util.zig"); 4 | pub const dtb = @import("lib/dtb.zig"); 5 | pub const pmm = @import("lib/pmm.zig"); 6 | pub const stivale = @import("lib/stivale.zig"); 7 | 8 | pub const acpi = @import("platform/acpi.zig"); 9 | pub const cache = @import("platform/cache.zig"); 10 | pub const timer = @import("platform/timer.zig"); 11 | pub const paging = @import("platform/paging.zig"); 12 | pub const pci = @import("platform/pci.zig"); 13 | pub const psci = @import("platform/psci.zig"); 14 | pub const fw_cfg = @import("platform/drivers/fw_cfg.zig"); 15 | pub const ramfb = @import("platform/drivers/ramfb.zig"); 16 | 17 | pub const puts = io_impl.puts; 18 | pub const log_hex = io_impl.log_hex; 19 | pub const print_hex = io_impl.print_hex; 20 | pub const print_str = io_impl.print_str; 21 | pub const log = io_impl.log; 22 | pub const putchar = io_impl.putchar; 23 | pub const near = util.near; 24 | pub const vital = util.vital; 25 | pub const io = platform.io; 26 | 27 | pub const debug = @import("builtin").mode == .Debug; 28 | pub const safety = std.debug.runtime_safety; 29 | 30 | pub const arch = @import("builtin").target.cpu.arch; 31 | pub const endian = arch.endian(); 32 | 33 | pub const upper_half_phys_base = 0xFFFF800000000000; 34 | 35 | const std = @import("std"); 36 | 37 | pub fn panic(reason: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn { 38 | if (@hasDecl(platform, "panic_hook")) 39 | platform.panic_hook(); 40 | puts("PANIC!"); 41 | if (reason.len != 0) { 42 | puts(" Reason: "); 43 | print_str(reason); 44 | } 45 | 46 | if (sabaton.debug) { 47 | if (stacktrace) |t| { 48 | log("\nTrace:\n", .{}); 49 | for (t.instruction_addresses) |addr| { 50 | log(" 0x{X}\n", .{addr}); 51 | } 52 | } else { 53 | log("\nNo trace.\n", .{}); 54 | } 55 | } 56 | 57 | switch(comptime(arch)) { 58 | .aarch64 => { 59 | asm volatile ( 60 | \\ // Disable interrupts 61 | \\ MSR DAIFSET, 0xF 62 | ); 63 | }, 64 | 65 | .riscv64 => { 66 | asm volatile( 67 | \\ // IDFK how to disable interrupts so hello 68 | ); 69 | }, 70 | 71 | else => @compileError("Disable interrupts on " ++ arch), 72 | } 73 | 74 | while(true) { 75 | std.atomic.spinLoopHint(); 76 | } 77 | 78 | unreachable; 79 | } 80 | 81 | pub const Elf = @import("lib/elf.zig").Elf; 82 | const sabaton = @This(); 83 | 84 | pub const Stivale2tag = packed struct { 85 | ident: u64, 86 | next: ?*@This(), 87 | }; 88 | 89 | const InfoStruct = struct { 90 | brand: [64]u8 = pad_str("Sabaton - Forged in Valhalla by the hammer of Thor", 64), 91 | version: [64]u8 = pad_str(@tagName(arch) ++ " " ++ @import("build_options").board_name ++ " - " ++ @tagName(@import("builtin").mode), 64), 92 | tags: ?*Stivale2tag = null, 93 | }; 94 | 95 | pub const Stivale2hdr = struct { 96 | entry_point: u64, 97 | stack: u64, 98 | flags: u64, 99 | tags: ?*Stivale2tag, 100 | }; 101 | 102 | fn pad_str(str: []const u8, comptime len: usize) [len]u8 { 103 | var ret = [1]u8{0} ** len; 104 | // Check that we fit the string and a null terminator 105 | if (str.len >= len) unreachable; 106 | @memcpy(@ptrCast([*]u8, &ret[0]), str.ptr, str.len); 107 | return ret; 108 | } 109 | 110 | pub var stivale2_info: InfoStruct = .{}; 111 | 112 | pub fn add_tag(tag: *Stivale2tag) void { 113 | tag.next = stivale2_info.tags; 114 | stivale2_info.tags = tag; 115 | } 116 | 117 | var paging_root: paging.Root = undefined; 118 | 119 | comptime { 120 | if (sabaton.safety) { 121 | switch(arch) { 122 | .aarch64 => { 123 | asm ( 124 | \\.section .text 125 | \\.balign 0x800 126 | \\evt_base: 127 | \\.balign 0x80; B fatal_error // curr_el_sp0_sync 128 | \\.balign 0x80; B fatal_error // curr_el_sp0_irq 129 | \\.balign 0x80; B fatal_error // curr_el_sp0_fiq 130 | \\.balign 0x80; B fatal_error // curr_el_sp0_serror 131 | \\.balign 0x80; B fatal_error // curr_el_spx_sync 132 | \\.balign 0x80; B fatal_error // curr_el_spx_irq 133 | \\.balign 0x80; B fatal_error // curr_el_spx_fiq 134 | \\.balign 0x80; B fatal_error // curr_el_spx_serror 135 | \\.balign 0x80; B fatal_error // lower_el_aarch64_sync 136 | \\.balign 0x80; B fatal_error // lower_el_aarch64_irq 137 | \\.balign 0x80; B fatal_error // lower_el_aarch64_fiq 138 | \\.balign 0x80; B fatal_error // lower_el_aarch64_serror 139 | \\.balign 0x80; B fatal_error // lower_el_aarch32_sync 140 | \\.balign 0x80; B fatal_error // lower_el_aarch32_irq 141 | \\.balign 0x80; B fatal_error // lower_el_aarch32_fiq 142 | \\.balign 0x80; B fatal_error // lower_el_aarch32_serror 143 | ); 144 | }, 145 | else => {}, 146 | } 147 | } 148 | } 149 | 150 | export fn fatal_error() noreturn { 151 | if (comptime (sabaton.safety and arch == .aarch64)) { 152 | const error_count = asm volatile ( 153 | \\ MRS %[res], TPIDR_EL1 154 | \\ ADD %[res], %[res], 1 155 | \\ MSR TPIDR_EL1, %[res] 156 | : [res] "=r" (-> u64) 157 | ); 158 | 159 | if (error_count != 1) { 160 | while (true) {} 161 | } 162 | 163 | const elr = asm ( 164 | \\MRS %[elr], ELR_EL1 165 | : [elr] "=r" (-> u64) 166 | ); 167 | sabaton.log_hex("ELR: ", elr); 168 | const esr = asm ( 169 | \\MRS %[elr], ESR_EL1 170 | : [elr] "=r" (-> u64) 171 | ); 172 | sabaton.log_hex("ESR: ", esr); 173 | const ec = @truncate(u6, esr >> 26); 174 | switch (ec) { 175 | 0b000000 => sabaton.puts("Unknown reason\n"), 176 | 0b100001 => sabaton.puts("Instruction fault\n"), 177 | 0b001110 => sabaton.puts("Illegal execution state\n"), 178 | 0b100101 => { 179 | sabaton.puts("Data abort\n"); 180 | const far = asm ( 181 | \\MRS %[elr], FAR_EL1 182 | : [elr] "=r" (-> u64) 183 | ); 184 | sabaton.log_hex("FAR: ", far); 185 | }, 186 | else => sabaton.log_hex("Unknown ec: ", ec), 187 | } 188 | @panic("Fatal error"); 189 | } else { 190 | while(true) { } 191 | } 192 | } 193 | 194 | pub fn install_evt() void { 195 | switch(comptime(arch)) { 196 | .aarch64 => { 197 | asm volatile ( 198 | \\ MSR VBAR_EL1, %[evt] 199 | \\ MSR TPIDR_EL1, XZR 200 | : 201 | : [evt] "r" (sabaton.near("evt_base").addr(u8)) 202 | ); 203 | }, 204 | 205 | else => {}, 206 | } 207 | sabaton.puts("Installed EVT\n"); 208 | } 209 | 210 | pub var kernel_file_tag: packed struct { 211 | tag: Stivale2tag = .{ 212 | .ident = 0xe599d90c2975584a, 213 | .next = null, 214 | }, 215 | kernel_addr: u64 = undefined, 216 | } = .{}; 217 | 218 | pub fn main() noreturn { 219 | if (comptime sabaton.safety) { 220 | install_evt(); 221 | } 222 | 223 | const dram = platform.get_dram(); 224 | 225 | var kernel_elf = Elf{ 226 | .data = platform.get_kernel(), 227 | }; 228 | 229 | kernel_elf.init(); 230 | 231 | var kernel_header: Stivale2hdr = undefined; 232 | _ = vital( 233 | kernel_elf.load_section(".stivale2hdr", util.to_byte_slice(&kernel_header)), 234 | "loading .stivale2hdr", 235 | true, 236 | ); 237 | 238 | kernel_file_tag.kernel_addr = @ptrToInt(kernel_elf.data); 239 | add_tag(&kernel_file_tag.tag); 240 | 241 | platform.add_platform_tags(&kernel_header); 242 | 243 | // Allocate space for backing pages of the kernel 244 | pmm.switch_state(.KernelPages); 245 | sabaton.puts("Allocating kernel memory\n"); 246 | const kernel_memory_pool = pmm.alloc_aligned(kernel_elf.paged_bytes(), .KernelPage); 247 | sabaton.log_hex("Bytes allocated for kernel: ", kernel_memory_pool.len); 248 | 249 | // TODO: Allocate and put modules here 250 | 251 | pmm.switch_state(.PageTables); 252 | paging_root = paging.init_paging(); 253 | platform.map_platform(&paging_root); 254 | { 255 | const dram_base = @ptrToInt(dram.ptr); 256 | sabaton.paging.map(dram_base, dram_base, dram.len, .rwx, .writeback, &paging_root); 257 | sabaton.paging.map(dram_base + upper_half_phys_base, dram_base, dram.len, .rwx, .writeback, &paging_root); 258 | } 259 | 260 | paging.apply_paging(&paging_root); 261 | // Check the flags in the stivale2 header 262 | sabaton.puts("Loading kernel into memory\n"); 263 | kernel_elf.load(kernel_memory_pool, &paging_root); 264 | 265 | if (sabaton.debug) 266 | sabaton.puts("Sealing PMM\n"); 267 | 268 | pmm.switch_state(.Sealed); 269 | 270 | // Maybe do these conditionally one day once we parse stivale2 kernel tags? 271 | if (@hasDecl(platform, "display")) { 272 | sabaton.puts("Starting display\n"); 273 | platform.display.init(); 274 | } 275 | 276 | if (@hasDecl(platform, "smp")) { 277 | sabaton.puts("Starting SMP\n"); 278 | platform.smp.init(); 279 | } 280 | 281 | if (@hasDecl(platform, "acpi")) { 282 | platform.acpi.init(); 283 | } 284 | 285 | pmm.write_dram_size(@ptrToInt(dram.ptr) + dram.len); 286 | 287 | add_tag(&near("memmap_tag").addr(Stivale2tag)[0]); 288 | 289 | if (@hasDecl(platform, "launch_kernel_hook")) 290 | platform.launch_kernel_hook(); 291 | 292 | sabaton.puts("Entering kernel...\n"); 293 | 294 | enterKernel(&kernel_elf, kernel_header.stack); 295 | } 296 | 297 | pub inline fn enterKernel(kernel_elf: *const Elf, stack: u64) noreturn { 298 | switch(comptime(arch)) { 299 | .aarch64 => { 300 | asm volatile ( 301 | \\ MSR SPSel, XZR 302 | \\ DMB SY 303 | \\ CBZ %[stack], 1f 304 | \\ MOV SP, %[stack] 305 | \\1:BR %[entry] 306 | : 307 | : [entry] "r" (kernel_elf.entry()), 308 | [stack] "r" (stack), 309 | [info] "{X0}" (&stivale2_info) 310 | ); 311 | }, 312 | 313 | .riscv64 => { 314 | asm volatile( 315 | \\ CSRW mstatus, %[mstatus] 316 | : 317 | : [mstatus] "r" (@as(u64, (1 << 11))) 318 | ); 319 | 320 | asm volatile ( 321 | \\ BEQ %[stack], zero, 1f 322 | \\ MV sp, %[stack] 323 | \\1:CSRW mepc, %[entry] 324 | \\ MRET 325 | : 326 | : [entry] "r" (kernel_elf.entry()), 327 | [stack] "r" (stack), 328 | [info] "{a0}" (&stivale2_info) 329 | ); 330 | }, 331 | 332 | else => @compileError("Implement enterKernel on " ++ arch), 333 | } 334 | 335 | unreachable; 336 | } 337 | 338 | pub fn stivale2_smp_ready(context: u64) noreturn { 339 | paging.apply_paging(&paging_root); 340 | 341 | const cpu_tag = @intToPtr(*stivale.SMPTagEntry, context); 342 | 343 | var goto: u64 = undefined; 344 | while (true) { 345 | goto = @atomicLoad(u64, &cpu_tag.goto, .Acquire); 346 | if (goto != 0) 347 | break; 348 | 349 | std.atomic.spinLoopHint(); 350 | } 351 | 352 | switch(comptime(arch)) { 353 | .aarch64 => { 354 | asm volatile ("DSB SY\n" ::: "memory"); 355 | 356 | asm volatile ( 357 | \\ MSR SPSel, #0 358 | \\ MOV SP, %[stack] 359 | \\ MOV LR, #~0 360 | \\ BR %[goto] 361 | : 362 | : [stack] "r" (cpu_tag.stack), 363 | [arg] "{X0}" (cpu_tag), 364 | [goto] "r" (goto) 365 | : "memory" 366 | ); 367 | }, 368 | 369 | .riscv64 => { 370 | asm volatile ( 371 | \\ MV sp, %[stack] 372 | \\ LI ra, ~0 373 | \\ JR %[goto] 374 | : 375 | : [stack] "r" (cpu_tag.stack), 376 | [arg] "{X0}" (cpu_tag), 377 | [goto] "r" (goto) 378 | : "memory" 379 | ); 380 | }, 381 | 382 | else => @compileError("Implement stivale2_smp_ready for " ++ @tagName(arch)), 383 | } 384 | unreachable; 385 | } 386 | 387 | pub const fb_width = 1024; 388 | pub const fb_height = 768; 389 | pub const fb_bpp = 4; 390 | pub const fb_pitch = fb_width * fb_bpp; 391 | pub const fb_bytes = fb_pitch * fb_height; 392 | 393 | pub var fb: packed struct { 394 | tag: Stivale2tag = .{ 395 | .ident = 0x506461d2950408fa, 396 | .next = null, 397 | }, 398 | addr: u64 = undefined, 399 | width: u16 = fb_width, 400 | height: u16 = fb_height, 401 | pitch: u16 = fb_pitch, 402 | bpp: u16 = fb_bpp * 8, 403 | mmodel: u8 = 1, 404 | red_mask_size: u8 = 8, 405 | red_mask_shift: u8 = 0, 406 | green_mask_size: u8 = 8, 407 | green_mask_shift: u8 = 8, 408 | blue_mask_size: u8 = 8, 409 | blue_mask_shift: u8 = 16, 410 | } = .{}; 411 | 412 | pub fn add_framebuffer(addr: u64) void { 413 | add_tag(&fb.tag); 414 | fb.addr = addr; 415 | } 416 | 417 | var rsdp: packed struct { 418 | tag: Stivale2tag = .{ 419 | .ident = 0x9e1786930a375e78, 420 | .next = null, 421 | }, 422 | rsdp: u64 = undefined, 423 | } = .{}; 424 | 425 | pub fn add_rsdp(addr: u64) void { 426 | add_tag(&rsdp.tag); 427 | rsdp.rsdp = addr; 428 | } 429 | --------------------------------------------------------------------------------