├── .gitignore
├── README.md
├── assets
├── zig-icon.bmp
├── zig-icon.svg
├── zig-logo.bmp
└── zig-logo.svg
├── boot
├── bootcode.bin
├── config.txt
├── fixup.dat
└── start.elf
├── build.zig
├── src
├── bootloader.ld
├── bootloader.zig
├── debug.zig
├── linker.ld
├── main.zig
├── mmio.zig
├── qemu-gdb.ld
├── serial.zig
├── slice_iterator.zig
├── time.zig
├── video_core_frame_buffer.zig
├── video_core_mailboxes.zig
├── video_core_metrics.zig
└── video_core_properties.zig
└── tools
└── send_image.zig
/.gitignore:
--------------------------------------------------------------------------------
1 | /clashos.bin
2 | /zig-cache/
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ClashOS
2 |
3 | A work-in-progress multiplayer arcade game that runs directly on the
4 | Raspberry Pi 3 B+ hardware, written entirely in [Zig](https://ziglang.org/).
5 |
6 | ## Current Status
7 |
8 | "Hello World" OS using the MiniUART. ~Tested and working on real hardware.~
9 | It has regressed on real hardware, and I haven't fixed it yet.
10 |
11 | ## Building
12 |
13 | ```
14 | zig build
15 | ```
16 |
17 | ## Testing
18 |
19 | ### QEMU
20 |
21 | ```
22 | zig build qemu
23 | ```
24 |
25 | #### Debugging with GDB
26 |
27 | ```
28 | zig build qemu -Dgdb
29 | ```
30 |
31 | In another terminal:
32 |
33 | ```
34 | gdb zig-cache/clashos-dbg -ex 'target remote localhost:1234'
35 | ```
36 |
37 | ### Sending a New Kernel Image via Serial
38 |
39 | While the Raspberry Pi is running, you can use
40 |
41 | ```
42 | zig build upload -Dtty=/dev/ttyUSB0
43 | ```
44 |
45 | If using QEMU, use `zig build qemu -Dpty` and note the tty path.
46 | In another terminal window, `cat` the tty path.
47 | In yet another terminal window, you can use the `zig build upload`
48 | command above, with the tty path provided by QEMU.
49 | This is compatible with using GDB with QEMU, just make sure to pass
50 | the `-Dgdb` to both `zig build` commands.
51 |
52 | ### Actual Hardware
53 |
54 | 1. Mount an sdcard with a single FAT32 partition.
55 | 2. Copy `boot/*` to `/path/to/sdcard/*`.
56 | 3. `zig build`
57 | 4. Copy `clashos.bin` to `/path/to/sdcard/kernel7.img`.
58 |
59 | For further changes repeat steps 3 and 4.
60 |
61 | ## Roadmap
62 |
63 | * Interface with the file system
64 | * Get rid of dependency on binutils objcopy
65 | * Interface with the video driver
66 | * Get a simple joystick and button and use GPIO
67 | * Sound (should it be the analog or over HDMI)?
68 | * Make the game
69 | * Build arcade cabinets
70 |
71 | ## Documentation
72 |
73 | ### EZSync 012 USB Cable
74 |
75 | * Black: Pin 6, Ground
76 | * Yellow: Pin 8, BCM 14, TXD / Transmit
77 | * Orange: Pin 10, BCM 15, RXD / Receive
78 |
79 | ### How to view the serial data
80 |
81 | Where `/dev/ttyUSB0` is the device that represents the serial-to-USB cable:
82 |
83 | ```
84 | sudo screen /dev/ttyUSB0 115200 cs8
85 | ```
86 |
87 | ### Memory Layout
88 |
89 | ```
90 | 0x0000000 ( 0 MiB) - boot entry point
91 | 0x0001000 - shortExceptionHandlerAt0x1000 function
92 | 0x0001100 - kernelMainAt0x1100 function
93 | 0x8000000 (128 MiB) - top of kernel stack, and bootloader_main function
94 | 0x8800000 (136 MiB) - top of bootloader stack
95 | ```
96 |
--------------------------------------------------------------------------------
/assets/zig-icon.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewrk/clashos/8ca226eb088d2a29f9a4875fd1245abb9842940b/assets/zig-icon.bmp
--------------------------------------------------------------------------------
/assets/zig-icon.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/assets/zig-logo.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewrk/clashos/8ca226eb088d2a29f9a4875fd1245abb9842940b/assets/zig-logo.bmp
--------------------------------------------------------------------------------
/assets/zig-logo.svg:
--------------------------------------------------------------------------------
1 |
34 |
--------------------------------------------------------------------------------
/boot/bootcode.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewrk/clashos/8ca226eb088d2a29f9a4875fd1245abb9842940b/boot/bootcode.bin
--------------------------------------------------------------------------------
/boot/config.txt:
--------------------------------------------------------------------------------
1 | # This file is the BIOS equivalent on the RPI.
2 | # https://www.raspberrypi.org/documentation/configuration/config-txt/
3 |
4 | disable_commandline_tags=1
5 | kernel_old=1
6 | dtparam=audio=on
7 | arm_control=0x200
8 | disable_splash=1
9 | boot_delay=0
10 | cec_osd_name=Zig!
11 |
--------------------------------------------------------------------------------
/boot/fixup.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewrk/clashos/8ca226eb088d2a29f9a4875fd1245abb9842940b/boot/fixup.dat
--------------------------------------------------------------------------------
/boot/start.elf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewrk/clashos/8ca226eb088d2a29f9a4875fd1245abb9842940b/boot/start.elf
--------------------------------------------------------------------------------
/build.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const Builder = std.build.Builder;
3 | const builtin = @import("builtin");
4 |
5 | pub fn build(b: *Builder) !void {
6 | const mode = b.standardReleaseOptions();
7 | const want_gdb = b.option(bool, "gdb", "Build for using gdb with qemu") orelse false;
8 | const want_pty = b.option(bool, "pty", "Create a separate TTY path") orelse false;
9 | const want_nodisplay = b.option(bool, "nodisplay", "No display for qemu") orelse false;
10 |
11 | const arch = builtin.Arch{ .aarch64 = .v8a };
12 | const environ = builtin.Abi.eabihf;
13 |
14 | // First we build just the bootloader executable, and then we build the actual kernel
15 | // which uses @embedFile on the bootloader.
16 | const bootloader = b.addExecutable("bootloader", "src/bootloader.zig");
17 | bootloader.setLinkerScriptPath("src/bootloader.ld");
18 | bootloader.setBuildMode(builtin.Mode.ReleaseSmall);
19 | bootloader.setTarget(arch, builtin.Os.freestanding, environ);
20 | bootloader.strip = true;
21 | bootloader.setOutputDir("zig-cache");
22 |
23 | const exec_name = if (want_gdb) "clashos-dbg" else "clashos";
24 | const exe = b.addExecutable(exec_name, "src/main.zig");
25 | exe.setOutputDir("zig-cache");
26 | exe.setBuildMode(mode);
27 | exe.setTarget(arch, builtin.Os.freestanding, environ);
28 | const linker_script = if (want_gdb) "src/qemu-gdb.ld" else "src/linker.ld";
29 | exe.setLinkerScriptPath(linker_script);
30 | exe.addBuildOption([]const u8, "bootloader_exe_path", b.fmt("\"{}\"", .{bootloader.getOutputPath()}));
31 | exe.step.dependOn(&bootloader.step);
32 |
33 | const run_objcopy = b.addSystemCommand(&[_][]const u8{
34 | "llvm-objcopy", exe.getOutputPath(),
35 | "-O", "binary",
36 | "clashos.bin",
37 | });
38 | run_objcopy.step.dependOn(&exe.step);
39 |
40 | b.default_step.dependOn(&run_objcopy.step);
41 |
42 | const qemu = b.step("qemu", "Run the OS in qemu");
43 | var qemu_args = std.ArrayList([]const u8).init(b.allocator);
44 | try qemu_args.appendSlice(&[_][]const u8{
45 | "qemu-system-aarch64",
46 | "-kernel",
47 | exe.getOutputPath(),
48 | "-m",
49 | "256",
50 | "-M",
51 | "raspi3",
52 | "-serial",
53 | "null",
54 | "-serial",
55 | if (want_pty) "pty" else "stdio",
56 | "-display",
57 | if (want_nodisplay) "none" else "gtk",
58 | });
59 | if (want_gdb) {
60 | try qemu_args.appendSlice(&[_][]const u8{ "-S", "-s" });
61 | }
62 | const run_qemu = b.addSystemCommand(qemu_args.toSliceConst());
63 | qemu.dependOn(&run_qemu.step);
64 | run_qemu.step.dependOn(&exe.step);
65 |
66 | const send_image_tool = b.addExecutable("send_image", "tools/send_image.zig");
67 |
68 | const run_send_image_tool = send_image_tool.run();
69 | if (b.option([]const u8, "tty", "Specify the TTY to send images to")) |tty_path| {
70 | run_send_image_tool.addArg(tty_path);
71 | }
72 |
73 | const upload = b.step("upload", "Send a new kernel image to a running instance. (See -Dtty option)");
74 | upload.dependOn(&run_objcopy.step);
75 | upload.dependOn(&run_send_image_tool.step);
76 | }
77 |
--------------------------------------------------------------------------------
/src/bootloader.ld:
--------------------------------------------------------------------------------
1 | ENTRY(boot)
2 |
3 | SECTIONS {
4 | kernelMainAt0x1100 = 0x1100; /* Must match value from kernel linker script */
5 |
6 | . = 0x8800000; /* Must match debug.bootloader_address */
7 |
8 | .text : ALIGN(4K) {
9 | KEEP(*(.text.first))
10 | *(.text)
11 | }
12 |
13 | .rodata : ALIGN(4K) {
14 | *(.rodata)
15 | }
16 |
17 | .data : ALIGN(4K) {
18 | *(.data)
19 | }
20 |
21 | .bss : ALIGN(4K) {
22 | __bss_start = .;
23 | *(COMMON)
24 | *(.bss)
25 | __bss_end = .;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/bootloader.zig:
--------------------------------------------------------------------------------
1 | // The bootloader is a separate executable, the hardware does not boot
2 | // directly into it. When the kernel wants to load a new image from the
3 | // serial port, it copies the bootloader executable code into memory at
4 | // address bootloader_address which matches the linker script for the bootloader
5 | // executable. Then the kernel jumps to bootloader_address. The bootloader then
6 | // overwrites the kernel's code, which is why a separate bootloader
7 | // executable is necessary.
8 | const std = @import("std");
9 | const builtin = @import("builtin");
10 | const debug = @import("debug.zig");
11 | const serial = @import("serial.zig");
12 |
13 | export fn bootloader_main(start_addr: [*]u8, len: usize) linksection(".text.first") noreturn {
14 | var i: usize = 0;
15 | while (i < len) : (i += 1) {
16 | start_addr[i] = serial.readByte();
17 | }
18 | asm volatile (
19 | \\mov sp,#0x08000000
20 | \\bl kernelMainAt0x1100
21 | );
22 | unreachable;
23 | }
24 |
25 | pub fn panic(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn {
26 | serial.log("BOOTLOADER PANIC: {}", .{message});
27 | debug.wfe_hang();
28 | }
29 |
--------------------------------------------------------------------------------
/src/debug.zig:
--------------------------------------------------------------------------------
1 | const builtin = @import("builtin");
2 | const std = @import("std");
3 | const assert = std.debug.assert;
4 | const serial = @import("serial.zig");
5 |
6 | extern var __debug_info_start: u8;
7 | extern var __debug_info_end: u8;
8 | extern var __debug_abbrev_start: u8;
9 | extern var __debug_abbrev_end: u8;
10 | extern var __debug_str_start: u8;
11 | extern var __debug_str_end: u8;
12 | extern var __debug_line_start: u8;
13 | extern var __debug_line_end: u8;
14 | extern var __debug_ranges_start: u8;
15 | extern var __debug_ranges_end: u8;
16 |
17 | const source_files = [_][]const u8{
18 | "src/debug.zig",
19 | "src/video_core_mailboxes.zig",
20 | "src/bootloader.zig",
21 | "src/serial.zig",
22 | "src/video_core_metrics.zig",
23 | "src/video_core_properties.zig",
24 | "src/video_core_frame_buffer.zig",
25 | "src/time.zig",
26 | "src/main.zig",
27 | "src/mmio.zig",
28 | "src/slice_iterator.zig",
29 | };
30 |
31 | var already_panicking: bool = false;
32 |
33 | pub fn panic(stack_trace: ?*builtin.StackTrace, comptime fmt: []const u8, args: var) noreturn {
34 | @setCold(true);
35 | if (already_panicking) {
36 | serial.log("\npanicked during kernel panic", .{});
37 | wfe_hang();
38 | }
39 | already_panicking = true;
40 |
41 | serial.log("panic: " ++ fmt, args);
42 |
43 | const first_trace_addr = @returnAddress();
44 | if (stack_trace) |t| {
45 | dumpStackTrace(t);
46 | }
47 | dumpCurrentStackTrace(first_trace_addr);
48 | wfe_hang();
49 | }
50 |
51 | pub fn wfe_hang() noreturn {
52 | while (true) {
53 | asm volatile ("wfe");
54 | }
55 | }
56 |
57 | fn dwarfSectionFromSymbolAbs(start: *u8, end: *u8) std.debug.DwarfInfo.Section {
58 | return std.debug.DwarfInfo.Section{
59 | .offset = 0,
60 | .size = @ptrToInt(end) - @ptrToInt(start),
61 | };
62 | }
63 |
64 | fn dwarfSectionFromSymbol(start: *u8, end: *u8) std.debug.DwarfInfo.Section {
65 | return std.debug.DwarfInfo.Section{
66 | .offset = @ptrToInt(start),
67 | .size = @ptrToInt(end) - @ptrToInt(start),
68 | };
69 | }
70 |
71 | fn getSelfDebugInfo() !*std.debug.DwarfInfo {
72 | const S = struct {
73 | var have_self_debug_info = false;
74 | var self_debug_info: std.debug.DwarfInfo = undefined;
75 |
76 | var in_stream_state = std.io.InStream(anyerror){ .readFn = readFn };
77 | var in_stream_pos: u64 = 0;
78 | const in_stream = &in_stream_state;
79 |
80 | fn readFn(self: *std.io.InStream(anyerror), buffer: []u8) anyerror!usize {
81 | const ptr = @intToPtr([*]const u8, in_stream_pos);
82 | @memcpy(buffer.ptr, ptr, buffer.len);
83 | in_stream_pos += buffer.len;
84 | return buffer.len;
85 | }
86 |
87 | const SeekableStream = std.io.SeekableStream(anyerror, anyerror);
88 | var seekable_stream_state = SeekableStream{
89 | .seekToFn = seekToFn,
90 | .seekByFn = seekByFn,
91 |
92 | .getPosFn = getPosFn,
93 | .getEndPosFn = getEndPosFn,
94 | };
95 | const seekable_stream = &seekable_stream_state;
96 |
97 | fn seekToFn(self: *SeekableStream, pos: u64) anyerror!void {
98 | in_stream_pos = pos;
99 | }
100 | fn seekByFn(self: *SeekableStream, pos: i64) anyerror!void {
101 | in_stream_pos = @bitCast(usize, @bitCast(isize, in_stream_pos) +% pos);
102 | }
103 | fn getPosFn(self: *SeekableStream) anyerror!u64 {
104 | return in_stream_pos;
105 | }
106 | fn getEndPosFn(self: *SeekableStream) anyerror!u64 {
107 | return @as(u64, @ptrToInt(&__debug_ranges_end));
108 | }
109 | };
110 | if (S.have_self_debug_info) return &S.self_debug_info;
111 |
112 | S.self_debug_info = std.debug.DwarfInfo{
113 | .dwarf_seekable_stream = S.seekable_stream,
114 | .dwarf_in_stream = S.in_stream,
115 | .endian = builtin.Endian.Little,
116 | .debug_info = dwarfSectionFromSymbol(&__debug_info_start, &__debug_info_end),
117 | .debug_abbrev = dwarfSectionFromSymbolAbs(&__debug_abbrev_start, &__debug_abbrev_end),
118 | .debug_str = dwarfSectionFromSymbolAbs(&__debug_str_start, &__debug_str_end),
119 | .debug_line = dwarfSectionFromSymbol(&__debug_line_start, &__debug_line_end),
120 | .debug_ranges = dwarfSectionFromSymbolAbs(&__debug_ranges_start, &__debug_ranges_end),
121 | .abbrev_table_list = undefined,
122 | .compile_unit_list = undefined,
123 | .func_list = undefined,
124 | };
125 | try std.debug.openDwarfDebugInfo(&S.self_debug_info, kernel_panic_allocator);
126 | return &S.self_debug_info;
127 | }
128 |
129 | var serial_out_stream_state = std.io.OutStream(anyerror){
130 | .writeFn = struct {
131 | fn logWithSerial(self: *std.io.OutStream(anyerror), bytes: []const u8) anyerror!void {
132 | serial.writeText(bytes);
133 | }
134 | }.logWithSerial,
135 | };
136 | const serial_out_stream = &serial_out_stream_state;
137 | var kernel_panic_allocator_bytes: [5 * 1024 * 1024]u8 = undefined;
138 | var kernel_panic_allocator_state = std.heap.FixedBufferAllocator.init(kernel_panic_allocator_bytes[0..]);
139 | const kernel_panic_allocator = &kernel_panic_allocator_state.allocator;
140 |
141 | pub fn dumpStackTrace(stack_trace: *const builtin.StackTrace) void {
142 | const dwarf_info = getSelfDebugInfo() catch |err| {
143 | serial.log("Unable to dump stack trace: Unable to open debug info: {}", .{@errorName(err)});
144 | return;
145 | };
146 | writeStackTrace(stack_trace, dwarf_info) catch |err| {
147 | serial.log("Unable to dump stack trace: {}", .{@errorName(err)});
148 | return;
149 | };
150 | }
151 |
152 | pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
153 | const dwarf_info = getSelfDebugInfo() catch |err| {
154 | serial.log("Unable to dump stack trace: Unable to open debug info: {}", .{@errorName(err)});
155 | return;
156 | };
157 | writeCurrentStackTrace(dwarf_info, start_addr) catch |err| {
158 | serial.log("Unable to dump stack trace: {}", .{@errorName(err)});
159 | return;
160 | };
161 | }
162 |
163 | fn printLineFromBuffer(out_stream: var, contents: []const u8, line_info: std.debug.LineInfo) anyerror!void {
164 | var line: usize = 1;
165 | var column: usize = 1;
166 | var abs_index: usize = 0;
167 | for (contents) |byte| {
168 | if (line == line_info.line) {
169 | try out_stream.writeByte(byte);
170 | if (byte == '\n') {
171 | return;
172 | }
173 | }
174 | if (byte == '\n') {
175 | line += 1;
176 | column = 1;
177 | } else {
178 | column += 1;
179 | }
180 | }
181 | return error.EndOfFile;
182 | }
183 |
184 | fn printLineFromFile(out_stream: var, line_info: std.debug.LineInfo) anyerror!void {
185 | inline for (source_files) |src_path| {
186 | if (std.mem.endsWith(u8, line_info.file_name, src_path)) {
187 | const contents = @embedFile("../" ++ src_path);
188 | try printLineFromBuffer(out_stream, contents[0..], line_info);
189 | return;
190 | }
191 | }
192 | try out_stream.print("(source file {} not added in std/debug.zig)\n", .{line_info.file_name});
193 | }
194 |
195 | fn writeCurrentStackTrace(dwarf_info: *std.debug.DwarfInfo, start_addr: ?usize) !void {
196 | var it = std.debug.StackIterator.init(start_addr);
197 | while (it.next()) |return_address| {
198 | try dwarf_info.printSourceAtAddress(
199 | serial_out_stream,
200 | return_address,
201 | .escape_codes,
202 | printLineFromFile,
203 | );
204 | }
205 | }
206 |
207 | fn writeStackTrace(stack_trace: *const builtin.StackTrace, dwarf_info: *std.debug.DwarfInfo) !void {
208 | var frame_index: usize = undefined;
209 | var frames_left: usize = undefined;
210 | if (stack_trace.index < stack_trace.instruction_addresses.len) {
211 | frame_index = 0;
212 | frames_left = stack_trace.index;
213 | } else {
214 | frame_index = (stack_trace.index + 1) % stack_trace.instruction_addresses.len;
215 | frames_left = stack_trace.instruction_addresses.len;
216 | }
217 |
218 | while (frames_left != 0) : ({
219 | frames_left -= 1;
220 | frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;
221 | }) {
222 | const return_address = stack_trace.instruction_addresses[frame_index];
223 | try dwarf_info.printSourceAtAddress(
224 | serial_out_stream,
225 | return_address,
226 | .escape_codes,
227 | printLineFromFile,
228 | );
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/src/linker.ld:
--------------------------------------------------------------------------------
1 | ENTRY(_start)
2 |
3 | SECTIONS {
4 | . = 0x0000;
5 |
6 | .text : ALIGN(4K) {
7 | KEEP(*(.text.boot))
8 | __end_init = .;
9 | . = 0x1000;
10 | KEEP(*(.text.exception))
11 | . = 0x1100; /* Must match address from bootloader.ld */
12 | KEEP(*(.text.main))
13 | *(.text)
14 | }
15 |
16 | .rodata : ALIGN(4K) {
17 | *(.rodata)
18 | __debug_info_start = .;
19 | KEEP(*(.debug_info))
20 | __debug_info_end = .;
21 | __debug_abbrev_start = .;
22 | KEEP(*(.debug_abbrev))
23 | __debug_abbrev_end = .;
24 | __debug_str_start = .;
25 | KEEP(*(.debug_str))
26 | __debug_str_end = .;
27 | __debug_line_start = .;
28 | KEEP(*(.debug_line))
29 | __debug_line_end = .;
30 | __debug_ranges_start = .;
31 | KEEP(*(.debug_ranges))
32 | __debug_ranges_end = .;
33 | }
34 |
35 | .data : ALIGN(4K) {
36 | *(.data)
37 | }
38 |
39 | .bss : ALIGN(4K) {
40 | __bss_start = .;
41 | *(COMMON)
42 | *(.bss)
43 | __bss_end = .;
44 | }
45 |
46 | bootloader_main = 0x8800000; /* Must match bootloader linker script */
47 | }
48 |
--------------------------------------------------------------------------------
/src/main.zig:
--------------------------------------------------------------------------------
1 | const builtin = @import("builtin");
2 | const Bitmap = @import("video_core_frame_buffer.zig").Bitmap;
3 | const Color = @import("video_core_frame_buffer.zig").Color;
4 | const debug = @import("debug.zig");
5 | const FrameBuffer = @import("video_core_frame_buffer.zig").FrameBuffer;
6 | const Metrics = @import("video_core_metrics.zig").Metrics;
7 | const serial = @import("serial.zig");
8 | const time = @import("time.zig");
9 | const std = @import("std");
10 |
11 | // The linker will make the address of these global variables equal
12 | // to the value we are interested in. The memory at the address
13 | // could alias any uninitialized global variable in the kernel.
14 | extern var __bss_start: u8;
15 | extern var __bss_end: u8;
16 | extern var __end_init: u8;
17 |
18 | comptime {
19 | // .text.boot to keep this in the first portion of the binary
20 | // Note: this code cannot be changed via the bootloader.
21 | asm (
22 | \\.section .text.boot
23 | \\.globl _start
24 | \\_start:
25 | \\ mrs x0,mpidr_el1
26 | \\ mov x1,#0xC1000000
27 | \\ bic x0,x0,x1
28 | \\ cbz x0,master
29 | \\ b hang
30 | \\master:
31 | \\ mov sp,#0x08000000
32 | \\ mov x0,#0x800 //exception_vector_table
33 | \\ msr vbar_el3,x0
34 | \\ msr vbar_el2,x0
35 | \\ msr vbar_el1,x0
36 | \\ bl kernelMainAt0x1100
37 | \\hang:
38 | \\ wfe
39 | \\ b hang
40 | \\exception_vector_table:
41 | \\.balign 0x800
42 | \\.balign 0x80
43 | \\ b shortExceptionHandlerAt0x1000
44 | \\.balign 0x80
45 | \\ b shortExceptionHandlerAt0x1000
46 | \\.balign 0x80
47 | \\ b shortExceptionHandlerAt0x1000
48 | \\.balign 0x80
49 | \\ b shortExceptionHandlerAt0x1000
50 | \\.balign 0x80
51 | \\ b shortExceptionHandlerAt0x1000
52 | \\.balign 0x80
53 | \\ b shortExceptionHandlerAt0x1000
54 | \\.balign 0x80
55 | \\ b shortExceptionHandlerAt0x1000
56 | \\.balign 0x80
57 | \\ b shortExceptionHandlerAt0x1000
58 | \\.balign 0x80
59 | \\ b shortExceptionHandlerAt0x1000
60 | \\.balign 0x80
61 | \\ b shortExceptionHandlerAt0x1000
62 | \\.balign 0x80
63 | \\ b shortExceptionHandlerAt0x1000
64 | \\.balign 0x80
65 | \\ b shortExceptionHandlerAt0x1000
66 | \\.balign 0x80
67 | \\ b shortExceptionHandlerAt0x1000
68 | \\.balign 0x80
69 | \\ b shortExceptionHandlerAt0x1000
70 | \\.balign 0x80
71 | \\ b shortExceptionHandlerAt0x1000
72 | \\.balign 0x80
73 | \\ b shortExceptionHandlerAt0x1000
74 | );
75 | }
76 |
77 | export fn shortExceptionHandlerAt0x1000() linksection(".text.exception") void {
78 | exceptionHandler();
79 | }
80 |
81 | pub fn panic(message: []const u8, trace: ?*builtin.StackTrace) noreturn {
82 | debug.panic(trace, "KERNEL PANIC: {}", .{message});
83 | }
84 |
85 | fn exceptionHandler() void {
86 | serial.log("arm exception taken", .{});
87 | var current_el = asm ("mrs %[current_el], CurrentEL"
88 | : [current_el] "=r" (-> usize)
89 | );
90 | serial.log("CurrentEL {x} exception level {}", .{ current_el, current_el >> 2 & 0x3 });
91 | var esr_el3 = asm ("mrs %[esr_el3], esr_el3"
92 | : [esr_el3] "=r" (-> usize)
93 | );
94 | serial.log("esr_el3 {x} code 0x{x}", .{ esr_el3, esr_el3 >> 26 & 0x3f });
95 | var elr_el3 = asm ("mrs %[elr_el3], elr_el3"
96 | : [elr_el3] "=r" (-> usize)
97 | );
98 | serial.log("elr_el3 {x}", .{elr_el3});
99 | var spsr_el3 = asm ("mrs %[spsr_el3], spsr_el3"
100 | : [spsr_el3] "=r" (-> usize)
101 | );
102 | serial.log("spsr_el3 {x}", .{spsr_el3});
103 | var far_el3 = asm ("mrs %[far_el3], far_el3"
104 | : [far_el3] "=r" (-> usize)
105 | );
106 | serial.log("far_el3 {x}", .{far_el3});
107 | serial.log("execution is now stopped in arm exception handler", .{});
108 | while (true) {
109 | asm volatile ("wfe");
110 | }
111 | }
112 |
113 | export fn kernelMainAt0x1100() linksection(".text.main") noreturn {
114 | // clear .bss
115 | @memset(@as(*volatile [1]u8, &__bss_start), 0, @ptrToInt(&__bss_end) - @ptrToInt(&__bss_start));
116 |
117 | serial.init();
118 | serial.log("\n{} {} ...", .{ name, version });
119 |
120 | time.init();
121 | metrics.init();
122 |
123 | fb.init(&metrics);
124 | icon.init(&fb, &logo_bmp_file);
125 | logo.init(&fb, &logo_bmp_file);
126 |
127 | screen_activity.init();
128 | serial_activity.init();
129 |
130 | while (true) {
131 | screen_activity.update();
132 | serial_activity.update();
133 | }
134 | }
135 |
136 | const ScreenActivity = struct {
137 | height: u32,
138 | color: Color,
139 | color32: u32,
140 | top: u32,
141 | x: i32,
142 | y: i32,
143 | vel_x: i32,
144 | vel_y: i32,
145 | ref_seconds: f32,
146 | pixel_counter: u32,
147 |
148 | fn init(self: *ScreenActivity) void {
149 | self.color = color_yellow;
150 | self.color32 = self.color.to32();
151 | self.height = logo.height;
152 | self.top = logo.height + margin;
153 | self.x = 0;
154 | self.y = 0;
155 | self.vel_x = 10;
156 | self.vel_y = 10;
157 | time.update();
158 | self.ref_seconds = time.seconds;
159 | self.pixel_counter = 0;
160 | }
161 |
162 | fn update(self: *ScreenActivity) void {
163 | time.update();
164 | const new_ref_secs = self.ref_seconds + 0.05;
165 | if (time.seconds >= new_ref_secs) {
166 | const clear_x = @intCast(u32, self.x);
167 | const clear_y = @intCast(u32, self.y);
168 | fb.clearRect(clear_x, clear_y, logo.width, logo.height, color_black);
169 |
170 | self.ref_seconds = new_ref_secs;
171 | self.x += self.vel_x;
172 | self.y += self.vel_y;
173 |
174 | if (self.x + @as(i32, logo.width) >= @intCast(i32, fb.virtual_width)) {
175 | self.x = @intCast(i32, fb.virtual_width - logo.width);
176 | self.vel_x = -self.vel_x;
177 | }
178 | if (self.y + @as(i32, logo.height) >= @intCast(i32, fb.virtual_height)) {
179 | self.y = @intCast(i32, fb.virtual_height - logo.height);
180 | self.vel_y = -self.vel_y;
181 | }
182 | if (self.x < 0) {
183 | self.x = 0;
184 | self.vel_x = -self.vel_x;
185 | }
186 | if (self.y < 0) {
187 | self.y = 0;
188 | self.vel_y = -self.vel_y;
189 | }
190 | const draw_x = @intCast(u32, self.x);
191 | const draw_y = @intCast(u32, self.y);
192 | logo.drawRect(logo.width, logo.height, 0, 0, draw_x, draw_y);
193 | }
194 | }
195 | };
196 |
197 | const SerialActivity = struct {
198 | boot_magic_index: usize,
199 |
200 | fn init(self: *SerialActivity) void {
201 | self.boot_magic_index = 0;
202 | serial.log("now echoing input on uart1 ...", .{});
203 | }
204 |
205 | fn update(self: *SerialActivity) void {
206 | if (!serial.isReadByteReady()) {
207 | return;
208 | }
209 | const boot_magic = [_]u8{ 6, 6, 6 };
210 | const byte = serial.readByte();
211 | if (byte == boot_magic[self.boot_magic_index]) {
212 | self.boot_magic_index += 1;
213 | if (self.boot_magic_index != boot_magic.len)
214 | return;
215 |
216 | // It's time to receive the new kernel. First
217 | // we skip over the .text.boot bytes, verifying that they
218 | // are unchanged.
219 | const new_kernel_len = serial.in.readIntLittle(u32) catch unreachable;
220 | serial.log("New kernel image detected, {Bi:2}", .{new_kernel_len});
221 | const text_boot = @intToPtr([*]allowzero const u8, 0)[0..@ptrToInt(&__end_init)];
222 | for (text_boot) |text_boot_byte, byte_index| {
223 | const new_byte = serial.readByte();
224 | if (new_byte != text_boot_byte) {
225 | debug.panic(@errorReturnTrace(), "new_kernel[{}] expected: 0x{x} actual: 0x{x}", .{
226 | byte_index,
227 | text_boot_byte,
228 | new_byte,
229 | });
230 | }
231 | }
232 | const start_addr = @ptrToInt(shortExceptionHandlerAt0x1000);
233 | const bytes_left = new_kernel_len - start_addr;
234 | var pad = start_addr - text_boot.len;
235 | while (pad > 0) : (pad -= 1) {
236 | _ = serial.readByte();
237 | }
238 |
239 | // Next we copy the bootloader code to the correct memory address,
240 | // and then jump to it.
241 | // Read the ELF
242 | var bootloader_code_ptr = @as([*]const u8, &bootloader_code); // TODO remove workaround `var`
243 | const ehdr = @ptrCast(*const std.elf.Elf64_Ehdr, bootloader_code_ptr);
244 | var phdr_addr = bootloader_code_ptr + ehdr.e_phoff;
245 | var phdr_i: usize = 0;
246 | while (phdr_i < ehdr.e_phnum) : ({
247 | phdr_i += 1;
248 | phdr_addr += ehdr.e_phentsize;
249 | }) {
250 | const this_ph = @ptrCast(*const std.elf.Elf64_Phdr, @alignCast(@alignOf(std.elf.Elf64_Phdr), phdr_addr));
251 | switch (this_ph.p_type) {
252 | std.elf.PT_LOAD => {
253 | const src_ptr = bootloader_code_ptr + this_ph.p_offset;
254 | const src_len = this_ph.p_filesz;
255 | const dest_ptr = @intToPtr([*]u8, this_ph.p_vaddr);
256 | const dest_len = this_ph.p_memsz;
257 | const pad_len = dest_len - src_len;
258 | const copy_len = dest_len - pad_len;
259 | @memcpy(dest_ptr, src_ptr, copy_len);
260 | @memset(dest_ptr + copy_len, 0, pad_len);
261 | },
262 | std.elf.PT_GNU_STACK => {}, // ignore
263 | else => debug.panic(@errorReturnTrace(), "unexpected ELF Program Header load type: {}", .{this_ph.p_type}),
264 | }
265 | }
266 | serial.log("Loading new image...", .{});
267 | asm volatile (
268 | \\mov sp,#0x08000000
269 | \\bl bootloader_main
270 | :
271 | : [arg0] "{x0}" (start_addr),
272 | [arg1] "{x1}" (bytes_left)
273 | );
274 | unreachable;
275 | }
276 | switch (byte) {
277 | '\r' => {
278 | serial.writeText("\n");
279 | },
280 | else => serial.writeByte(byte),
281 | }
282 | }
283 | };
284 |
285 | const build_options = @import("build_options");
286 | const bootloader_code align(@alignOf(std.elf.Elf64_Ehdr)) = @embedFile("../" ++ build_options.bootloader_exe_path).*;
287 |
288 | var screen_activity: ScreenActivity = undefined;
289 | var serial_activity: SerialActivity = undefined;
290 | var fb: FrameBuffer = undefined;
291 | var metrics: Metrics = undefined;
292 | var icon: Bitmap = undefined;
293 | var logo: Bitmap = undefined;
294 |
295 | var icon_bmp_file align(@alignOf(u32)) = @embedFile("../assets/zig-icon.bmp").*;
296 | var logo_bmp_file align(@alignOf(u32)) = @embedFile("../assets/zig-logo.bmp").*;
297 |
298 | const margin = 10;
299 |
300 | const color_red = Color{ .red = 255, .green = 0, .blue = 0, .alpha = 255 };
301 | const color_green = Color{ .red = 0, .green = 255, .blue = 0, .alpha = 255 };
302 | const color_blue = Color{ .red = 0, .green = 0, .blue = 255, .alpha = 255 };
303 | const color_yellow = Color{ .red = 255, .green = 255, .blue = 0, .alpha = 255 };
304 | const color_white = Color{ .red = 255, .green = 255, .blue = 255, .alpha = 255 };
305 | const color_black = Color{ .red = 0, .green = 0, .blue = 0, .alpha = 255 };
306 |
307 | const name = "ClashOS";
308 | const version = "0.2";
309 |
--------------------------------------------------------------------------------
/src/mmio.zig:
--------------------------------------------------------------------------------
1 | const AtomicOrder = @import("builtin").AtomicOrder;
2 |
3 | pub fn write(reg: usize, data: u32) void {
4 | @fence(AtomicOrder.SeqCst);
5 | @intToPtr(*volatile u32, reg).* = data;
6 | }
7 |
8 | pub fn read(reg: usize) u32 {
9 | @fence(AtomicOrder.SeqCst);
10 | return @intToPtr(*volatile u32, reg).*;
11 | }
12 |
13 | pub fn dsb() void {
14 | asm volatile ("dsb st");
15 | }
16 |
17 | pub fn bigTimeExtraMemoryBarrier() void {
18 | asm volatile (
19 | \\ mcr p15, 0, ip, c7, c5, 0 @ invalidate I cache
20 | \\ mcr p15, 0, ip, c7, c5, 6 @ invalidate BTB
21 | \\ dsb
22 | \\ isb
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/qemu-gdb.ld:
--------------------------------------------------------------------------------
1 | ENTRY(_start)
2 |
3 | SECTIONS {
4 | . = 0x0000;
5 |
6 | .text : ALIGN(4K) {
7 | KEEP(*(.text.boot))
8 | __end_init = .;
9 | . = 0x1000; /* Must match address from bootloader.ld */
10 | KEEP(*(.text.main))
11 | *(.text)
12 | }
13 |
14 | .rodata : ALIGN(4K) {
15 | *(.rodata)
16 | }
17 |
18 | .data : ALIGN(4K) {
19 | *(.data)
20 | }
21 |
22 | .bss : ALIGN(4K) {
23 | __bss_start = .;
24 | *(COMMON)
25 | *(.bss)
26 | __bss_end = .;
27 | __debug_info_start = .;
28 | __debug_info_end = .;
29 | __debug_abbrev_start = .;
30 | __debug_abbrev_end = .;
31 | __debug_str_start = .;
32 | __debug_str_end = .;
33 | __debug_line_start = .;
34 | __debug_line_end = .;
35 | __debug_ranges_start = .;
36 | __debug_ranges_end = .;
37 | }
38 |
39 | bootloader_main = 0x8800000; /* Must match bootloader linker script */
40 | }
41 |
--------------------------------------------------------------------------------
/src/serial.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const fmt = std.fmt;
3 | const mmio = @import("mmio.zig");
4 |
5 | pub const GPFSEL1 = 0x3F200004;
6 | pub const GPSET0 = 0x3F20001C;
7 | pub const GPCLR0 = 0x3F200028;
8 | pub const GPPUD = 0x3F200094;
9 | pub const GPPUDCLK0 = 0x3F200098;
10 |
11 | pub const AUX_ENABLES = 0x3F215004;
12 | pub const AUX_MU_IO_REG = 0x3F215040;
13 | pub const AUX_MU_IER_REG = 0x3F215044;
14 | pub const AUX_MU_IIR_REG = 0x3F215048;
15 | pub const AUX_MU_LCR_REG = 0x3F21504C;
16 | pub const AUX_MU_MCR_REG = 0x3F215050;
17 | pub const AUX_MU_LSR_REG = 0x3F215054;
18 | pub const AUX_MU_MSR_REG = 0x3F215058;
19 | pub const AUX_MU_SCRATCH = 0x3F21505C;
20 | pub const AUX_MU_CNTL_REG = 0x3F215060;
21 | pub const AUX_MU_STAT_REG = 0x3F215064;
22 | pub const AUX_MU_BAUD_REG = 0x3F215068;
23 |
24 | pub const in = &in_stream_state;
25 | pub const out = &out_stream_state;
26 |
27 | const NoError = error{};
28 |
29 | var in_stream_state = std.io.InStream(NoError){
30 | .readFn = struct {
31 | fn readFn(self: *std.io.InStream(NoError), buffer: []u8) NoError!usize {
32 | for (buffer) |*byte| {
33 | byte.* = readByte();
34 | }
35 | return buffer.len;
36 | }
37 | }.readFn,
38 | };
39 |
40 | var out_stream_state = std.io.OutStream(NoError){
41 | .writeFn = struct {
42 | fn writeFn(self: *std.io.OutStream(NoError), bytes: []const u8) NoError!void {
43 | for (bytes) |byte| {
44 | writeByte(byte);
45 | }
46 | return buffer.len;
47 | }
48 | }.writeFn,
49 | };
50 |
51 | pub fn writeByte(byte: u8) void {
52 | // Wait for UART to become ready to transmit.
53 | while ((mmio.read(AUX_MU_LSR_REG) & 0x20) == 0) {}
54 | mmio.write(AUX_MU_IO_REG, byte);
55 | }
56 |
57 | pub fn isReadByteReady() bool {
58 | return mmio.read(AUX_MU_LSR_REG) & 0x01 != 0;
59 | }
60 |
61 | pub fn readByte() u8 {
62 | // Wait for UART to have recieved something.
63 | while (!isReadByteReady()) {}
64 | return @truncate(u8, mmio.read(AUX_MU_IO_REG));
65 | }
66 |
67 | pub fn write(buffer: []const u8) void {
68 | for (buffer) |c|
69 | writeByte(c);
70 | }
71 |
72 | /// Translates \n into \r\n
73 | pub fn writeText(buffer: []const u8) void {
74 | for (buffer) |c| {
75 | switch (c) {
76 | '\n' => {
77 | writeByte('\r');
78 | writeByte('\n');
79 | },
80 | else => writeByte(c),
81 | }
82 | }
83 | }
84 |
85 | pub fn init() void {
86 | mmio.write(AUX_ENABLES, 1);
87 | mmio.write(AUX_MU_IER_REG, 0);
88 |
89 | mmio.write(AUX_MU_CNTL_REG, 0);
90 | mmio.write(AUX_MU_LCR_REG, 3);
91 | mmio.write(AUX_MU_MCR_REG, 0);
92 | mmio.write(AUX_MU_IER_REG, 0);
93 | mmio.write(AUX_MU_IIR_REG, 0xC6);
94 | mmio.write(AUX_MU_BAUD_REG, 270);
95 | var ra = mmio.read(GPFSEL1);
96 | ra &= ~@as(u32, 7 << 12); //gpio14
97 | ra |= 2 << 12; //alt5
98 | ra &= ~@as(u32, 7 << 15); //gpio15
99 | ra |= 2 << 15; //alt5
100 | mmio.write(GPFSEL1, ra);
101 | mmio.write(GPPUD, 0);
102 | delay(150);
103 | mmio.write(GPPUDCLK0, (1 << 14) | (1 << 15));
104 | delay(150);
105 | mmio.write(GPPUDCLK0, 0);
106 | mmio.write(AUX_MU_CNTL_REG, 3);
107 | }
108 |
109 | pub fn log(comptime format: []const u8, args: var) void {
110 | fmt.format({}, NoError, logBytes, format ++ "\n", args) catch |e| switch (e) {};
111 | }
112 |
113 | fn logBytes(context: void, bytes: []const u8) NoError!void {
114 | writeText(bytes);
115 | }
116 |
117 | pub fn dumpMemory(address: usize, size: usize) void {
118 | var i: usize = 0;
119 | while (i < size) : (i += 1) {
120 | const full_addr = address + i;
121 |
122 | if (i % 16 == 0) {
123 | log("\n0x{x8} ", full_addr);
124 | } else if (i % 8 == 0) {
125 | log(" ");
126 | }
127 |
128 | log(" {x2}", ((*const u8)(full_addr)).*);
129 | }
130 | log("\n");
131 | }
132 |
133 | // Loop count times in a way that the compiler won't optimize away.
134 | fn delay(count: usize) void {
135 | var i: usize = 0;
136 | while (i < count) : (i += 1) {
137 | asm volatile ("mov w0, w0");
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/slice_iterator.zig:
--------------------------------------------------------------------------------
1 | pub fn of(comptime T: type) type {
2 | var Iterator = struct {
3 | const Self = @This();
4 | data: []T,
5 | index: u32,
6 |
7 | pub fn init(data: []T) Self {
8 | var self: Self = undefined;
9 | self.data = data;
10 | self.index = 0;
11 | return self;
12 | }
13 |
14 | fn reset(self: *Self) void {
15 | self.index = 0;
16 | }
17 |
18 | fn add(self: *Self, item: T) void {
19 | self.advance();
20 | self.data[self.index - 1] = item;
21 | }
22 |
23 | fn next(self: *Self) u32 {
24 | self.advance();
25 | return self.data[self.index - 1];
26 | }
27 |
28 | fn advance(self: *Self) void {
29 | if (self.index < self.data.len) {
30 | self.index += 1;
31 | } else {
32 | panic(@errorReturnTrace(), "BufferExhausted", .{});
33 | }
34 | }
35 | };
36 |
37 | return Iterator;
38 | }
39 |
40 | const panic = @import("debug.zig").panic;
41 |
--------------------------------------------------------------------------------
/src/time.zig:
--------------------------------------------------------------------------------
1 | pub fn init() void {
2 | cntfrq = asm ("mrs %[cntfrq], cntfrq_el0"
3 | : [cntfrq] "=r" (-> usize)
4 | );
5 | if (cntfrq == 0) {
6 | cntfrq = 1 * 1000 * 1000;
7 | }
8 | cntfrq_f32 = @intToFloat(f32, cntfrq);
9 | update();
10 | }
11 |
12 | pub fn update() void {
13 | cntpct = asm ("mrs %[cntpct], cntpct_el0"
14 | : [cntpct] "=r" (-> usize)
15 | );
16 | seconds = @intToFloat(f32, cntpct) / cntfrq_f32;
17 | milliseconds = cntpct / (cntfrq / 1000);
18 | }
19 |
20 | pub fn sleep(duration: f32) void {
21 | update();
22 | const start = seconds;
23 | while (seconds - start < duration) {
24 | update();
25 | }
26 | }
27 |
28 | pub var seconds: f32 = undefined;
29 | pub var milliseconds: usize = undefined;
30 |
31 | var cntfrq: usize = undefined;
32 | var cntfrq_f32: f32 = undefined;
33 | var cntpct: usize = undefined;
34 |
--------------------------------------------------------------------------------
/src/video_core_frame_buffer.zig:
--------------------------------------------------------------------------------
1 | pub const FrameBuffer = struct {
2 | alignment: u32,
3 | alpha_mode: u32,
4 | depth: u32,
5 | physical_width: u32,
6 | physical_height: u32,
7 | pitch: u32,
8 | pixel_order: u32,
9 | words: [*]volatile u32,
10 | size: u32,
11 | virtual_height: u32,
12 | virtual_width: u32,
13 | virtual_offset_x: u32,
14 | virtual_offset_y: u32,
15 | overscan_top: u32,
16 | overscan_bottom: u32,
17 | overscan_left: u32,
18 | overscan_right: u32,
19 |
20 | fn clear(fb: *FrameBuffer, color: Color) void {
21 | var y: u32 = 0;
22 | while (y < fb.virtual_height) : (y += 1) {
23 | var x: u32 = 0;
24 | while (x < fb.virtual_width) : (x += 1) {
25 | fb.drawPixel(x, y, color);
26 | }
27 | }
28 | }
29 |
30 | fn clearRect(fb: *FrameBuffer, x2: u32, y2: u32, width: u32, height: u32, color: Color) void {
31 | var y: u32 = 0;
32 | while (y < height) : (y += 1) {
33 | var x: u32 = 0;
34 | while (x < width) : (x += 1) {
35 | fb.drawPixel(x + x2, y + y2, color);
36 | }
37 | }
38 | }
39 |
40 | fn drawPixel(fb: *FrameBuffer, x: u32, y: u32, color: Color) void {
41 | assert(x < fb.virtual_width);
42 | assert(y < fb.virtual_height);
43 | const offset = y * fb.pitch + x;
44 | fb.drawPixel32(x, y, color.to32());
45 | }
46 |
47 | fn drawPixel32(fb: *FrameBuffer, x: u32, y: u32, color: u32) void {
48 | if (x >= fb.virtual_width or y >= fb.virtual_height) {
49 | panic(@errorReturnTrace(), "frame buffer index {}, {} does not fit in {}x{}", .{ x, y, fb.virtual_width, fb.virtual_height });
50 | }
51 | fb.words[y * fb.pitch / 4 + x] = color;
52 | }
53 |
54 | pub fn init(fb: *FrameBuffer, metrics: *Metrics) void {
55 | var width: u32 = undefined;
56 | var height: u32 = undefined;
57 | if (metrics.is_qemu) {
58 | width = 1024;
59 | height = 768;
60 | } else {
61 | width = 1920;
62 | height = 1080;
63 | }
64 | fb.alignment = 256;
65 | fb.physical_width = width;
66 | fb.physical_height = height;
67 | fb.virtual_width = width;
68 | fb.virtual_height = height;
69 | fb.virtual_offset_x = 0;
70 | fb.virtual_offset_y = 0;
71 | fb.depth = 32;
72 | fb.pixel_order = 0;
73 | fb.alpha_mode = 0;
74 |
75 | var fb_addr: u32 = undefined;
76 | var arg = [_]PropertiesArg{
77 | tag(TAG_ALLOCATE_FRAME_BUFFER, 8),
78 | in(&fb.alignment),
79 | out(&fb_addr),
80 | out(&fb.size),
81 | tag(TAG_SET_DEPTH, 4),
82 | set(&fb.depth),
83 | tag(TAG_SET_PHYSICAL_WIDTH_HEIGHT, 8),
84 | set(&fb.physical_width),
85 | set(&fb.physical_height),
86 | tag(TAG_SET_PIXEL_ORDER, 4),
87 | set(&fb.pixel_order),
88 | tag(TAG_SET_VIRTUAL_WIDTH_HEIGHT, 8),
89 | set(&fb.virtual_width),
90 | set(&fb.virtual_height),
91 | tag(TAG_SET_VIRTUAL_OFFSET, 8),
92 | set(&fb.virtual_offset_x),
93 | set(&fb.virtual_offset_y),
94 | tag(TAG_SET_ALPHA_MODE, 4),
95 | set(&fb.alpha_mode),
96 | tag(TAG_GET_PITCH, 4),
97 | out(&fb.pitch),
98 | tag(TAG_GET_OVERSCAN, 16),
99 | out(&fb.overscan_top),
100 | out(&fb.overscan_bottom),
101 | out(&fb.overscan_left),
102 | out(&fb.overscan_right),
103 | last_tag_sentinel,
104 | };
105 | callVideoCoreProperties(&arg);
106 |
107 | fb.words = @intToPtr([*]volatile u32, fb_addr & 0x3FFFFFFF);
108 | log("fb align {} addr {x} alpha {} pitch {} order {} size {} physical {}x{} virtual {}x{} offset {},{} overscan t {} b {} l {} r {}", .{ fb.alignment, @ptrToInt(fb.words), fb.alpha_mode, fb.pitch, fb.pixel_order, fb.size, fb.physical_width, fb.physical_height, fb.virtual_width, fb.virtual_height, fb.virtual_offset_x, fb.virtual_offset_y, fb.overscan_top, fb.overscan_bottom, fb.overscan_left, fb.overscan_right });
109 | if (@ptrToInt(fb.words) == 0) {
110 | panic(@errorReturnTrace(), "frame buffer pointer is zero", .{});
111 | }
112 | }
113 | };
114 |
115 | pub const Bitmap = struct {
116 | frame_buffer: *FrameBuffer,
117 | pixel_array: [*]u8,
118 | width: u31,
119 | height: u31,
120 |
121 | fn getU32(base: [*]u8, offset: u32) u32 {
122 | var word: u32 = 0;
123 | var i: u32 = 0;
124 | while (i <= 3) : (i += 1) {
125 | word >>= 8;
126 | word |= @intCast(u32, @intToPtr(*u8, @ptrToInt(base) + offset + i).*) << 24;
127 | }
128 | return word;
129 | }
130 |
131 | pub fn init(bitmap: *Bitmap, frame_buffer: *FrameBuffer, file: []u8) void {
132 | bitmap.frame_buffer = frame_buffer;
133 | bitmap.pixel_array = @intToPtr([*]u8, @ptrToInt(file.ptr) + getU32(file.ptr, 0x0A));
134 | bitmap.width = @intCast(u31, getU32(file.ptr, 0x12));
135 | bitmap.height = @intCast(u31, getU32(file.ptr, 0x16));
136 | }
137 |
138 | fn getPixel(self: *Bitmap, x: u32, y: u32) Color {
139 | const rgba = getU32(self.pixel_array, ((self.height - 1 - y) * self.width + x) * @sizeOf(u32));
140 | return Color{
141 | .red = @intCast(u8, (rgba >> 16) & 0xff),
142 | .green = @intCast(u8, (rgba >> 8) & 0xff),
143 | .blue = @intCast(u8, (rgba >> 0) & 0xff),
144 | .alpha = @intCast(u8, (rgba >> 24) & 0xff),
145 | };
146 | }
147 |
148 | fn drawRect(self: *Bitmap, width: u32, height: u32, x1: u32, y1: u32, x2: u32, y2: u32) void {
149 | var y: u32 = 0;
150 | while (y < height) : (y += 1) {
151 | var x: u32 = 0;
152 | while (x < width) : (x += 1) {
153 | self.frame_buffer.drawPixel(x + x2, y + y2, self.getPixel(x + x1, y + y1));
154 | }
155 | }
156 | }
157 | };
158 |
159 | const TAG_ALLOCATE_FRAME_BUFFER = 0x40001;
160 |
161 | const TAG_GET_OVERSCAN = 0x4000A;
162 | const TAG_GET_PITCH = 0x40008;
163 |
164 | const TAG_SET_ALPHA_MODE = 0x48007;
165 | const TAG_SET_DEPTH = 0x48005;
166 | const TAG_SET_PHYSICAL_WIDTH_HEIGHT = 0x48003;
167 | const TAG_SET_PIXEL_ORDER = 0x48006;
168 | const TAG_SET_VIRTUAL_OFFSET = 0x48009;
169 | const TAG_SET_VIRTUAL_WIDTH_HEIGHT = 0x48004;
170 |
171 | pub const Color = struct {
172 | red: u8,
173 | green: u8,
174 | blue: u8,
175 | alpha: u8,
176 |
177 | fn to32(color: Color) u32 {
178 | return (255 - @intCast(u32, color.alpha) << 24) | @intCast(u32, color.red) << 16 | @intCast(u32, color.green) << 8 | @intCast(u32, color.blue) << 0;
179 | }
180 | };
181 |
182 | const log = @import("serial.zig").log;
183 | const Metrics = @import("video_core_metrics.zig").Metrics;
184 | const panic = @import("debug.zig").panic;
185 | usingnamespace @import("video_core_properties.zig");
186 | const std = @import("std");
187 | const assert = std.debug.assert;
188 |
--------------------------------------------------------------------------------
/src/video_core_mailboxes.zig:
--------------------------------------------------------------------------------
1 | pub const mailboxes = [_]*Mailbox{ Mailbox.init(0), Mailbox.init(1) };
2 |
3 | const Mailbox = packed struct {
4 | push_pull_register: u32,
5 | unused1: u32,
6 | unused2: u32,
7 | unused3: u32,
8 | unused4: u32,
9 | unused5: u32,
10 | status_register: u32,
11 | unused6: u32,
12 |
13 | fn init(index: u32) *Mailbox {
14 | if (index > 1) {
15 | panic(@errorReturnTrace(), "mailbox index {} exceeds 1", .{index});
16 | }
17 | const PERIPHERAL_BASE = 0x3F000000;
18 | const MAILBOXES_OFFSET = 0xB880;
19 | assert(@sizeOf(Mailbox) == 0x20);
20 | return @intToPtr(*Mailbox, PERIPHERAL_BASE + MAILBOXES_OFFSET + index * @sizeOf(Mailbox));
21 | }
22 |
23 | fn pushRequestBlocking(this: *Mailbox, request: u32) void {
24 | blockWhile(this, isFull);
25 | this.push(request);
26 | }
27 |
28 | fn pullResponseBlocking(this: *Mailbox, request: u32) void {
29 | blockWhile(this, isEmpty);
30 | const response = this.pull();
31 | if (response != request) {
32 | panic(@errorReturnTrace(), "buffer address and channel response was {x} expecting {x}", .{ response, request });
33 | }
34 | }
35 |
36 | fn push(this: *Mailbox, word: u32) void {
37 | mmio.dsb();
38 | mmio.write(@ptrToInt(&this.push_pull_register), word);
39 | }
40 |
41 | fn pull(this: *Mailbox) u32 {
42 | return mmio.read(@ptrToInt(&this.push_pull_register));
43 | }
44 |
45 | fn status(this: *Mailbox) u32 {
46 | return mmio.read(@ptrToInt(&this.status_register));
47 | }
48 |
49 | fn blockWhile(this: *Mailbox, conditionFn: fn (*Mailbox) bool) void {
50 | time.update();
51 | const start = time.seconds;
52 | while (conditionFn(this)) {
53 | time.update();
54 | if (time.seconds - start >= 0.1) {
55 | panic(@errorReturnTrace(), "time out waiting for video core mailbox", .{});
56 | }
57 | }
58 | }
59 | };
60 |
61 | fn isFull(this: *Mailbox) bool {
62 | const MAILBOX_IS_FULL = 0x80000000;
63 | return this.status() & MAILBOX_IS_FULL != 0;
64 | }
65 |
66 | fn isEmpty(this: *Mailbox) bool {
67 | const MAILBOX_IS_EMPTY = 0x40000000;
68 | return this.status() & MAILBOX_IS_EMPTY != 0;
69 | }
70 |
71 | const assert = std.debug.assert;
72 | const mmio = @import("mmio.zig");
73 | const panic = @import("debug.zig").panic;
74 | const std = @import("std");
75 | const time = @import("time.zig");
76 |
--------------------------------------------------------------------------------
/src/video_core_metrics.zig:
--------------------------------------------------------------------------------
1 | pub const Metrics = struct {
2 | board_model: u32,
3 | board_revision: u32,
4 | firmware_revision: u32,
5 | is_qemu: bool,
6 | max_temperature: u32,
7 | temperature: u32,
8 | temperature_id: u32,
9 | arm_memory_address: u32,
10 | arm_memory_size: u32,
11 | vc_memory_address: u32,
12 | vc_memory_size: u32,
13 | usable_dma_channels_mask: u32,
14 |
15 | pub fn init(self: *Metrics) void {
16 | self.temperature_id = 0;
17 | var args = [_]PropertiesArg{
18 | tag(TAG_GET_FIRMWARE_REVISION, 4),
19 | out(&self.firmware_revision),
20 | tag(TAG_GET_BOARD_MODEL, 4),
21 | out(&self.board_model),
22 | tag(TAG_GET_BOARD_REVISION, 4),
23 | out(&self.board_revision),
24 | tag(TAG_GET_MAX_TEMPERATURE, 8),
25 | set(&self.temperature_id),
26 | out(&self.max_temperature),
27 | tag(TAG_GET_ARM_MEMORY, 8),
28 | out(&self.arm_memory_address),
29 | out(&self.arm_memory_size),
30 | tag(TAG_GET_VC_MEMORY, 8),
31 | out(&self.vc_memory_address),
32 | out(&self.vc_memory_size),
33 | tag(TAG_GET_USABLE_DMA_CHANNELS_MASK, 4),
34 | out(&self.usable_dma_channels_mask),
35 | last_tag_sentinel,
36 | };
37 | callVideoCoreProperties(&args);
38 | self.is_qemu = self.board_model == 0xaaaaaaaa;
39 | self.update();
40 | }
41 |
42 | pub fn update(m: *Metrics) void {
43 | var args = [_]PropertiesArg{
44 | tag(TAG_GET_TEMPERATURE, 8),
45 | set(&m.temperature_id),
46 | out(&m.temperature),
47 | last_tag_sentinel,
48 | };
49 | callVideoCoreProperties(&args);
50 | }
51 | };
52 |
53 | const TAG_GET_ARM_MEMORY = 0x10005;
54 | const TAG_GET_BOARD_MODEL = 0x10001;
55 | const TAG_GET_BOARD_REVISION = 0x10002;
56 | const TAG_GET_FIRMWARE_REVISION = 0x00001;
57 | const TAG_GET_MAX_TEMPERATURE = 0x3000A;
58 | const TAG_GET_TEMPERATURE = 0x30006;
59 | const TAG_GET_USABLE_DMA_CHANNELS_MASK = 0x60001;
60 | const TAG_GET_VC_MEMORY = 0x10006;
61 |
62 | const panic = @import("debug.zig").panic;
63 | usingnamespace @import("video_core_properties.zig");
64 |
--------------------------------------------------------------------------------
/src/video_core_properties.zig:
--------------------------------------------------------------------------------
1 | pub fn callVideoCoreProperties(args: []PropertiesArg) void {
2 | assert(args[args.len - 1].TagAndLength.tag == TAG_LAST_SENTINEL);
3 |
4 | var words: [1024]u32 align(16) = undefined;
5 | var buf = SliceIterator.of(u32).init(&words);
6 |
7 | var buffer_length_in_bytes: u32 = 0;
8 | buf.add(buffer_length_in_bytes);
9 | const BUFFER_REQUEST = 0;
10 | buf.add(BUFFER_REQUEST);
11 | var next_tag_index = buf.index;
12 | for (args) |arg| {
13 | switch (arg) {
14 | PropertiesArg.TagAndLength => |tag_and_length| {
15 | if (tag_and_length.tag != 0) {
16 | // log("prepare tag {x} length {}", tag_and_length.tag, tag_and_length.length);
17 | }
18 | buf.index = next_tag_index;
19 | buf.add(tag_and_length.tag);
20 | if (tag_and_length.tag != TAG_LAST_SENTINEL) {
21 | buf.add(tag_and_length.length);
22 | const TAG_REQUEST = 0;
23 | buf.add(TAG_REQUEST);
24 | next_tag_index = buf.index + tag_and_length.length / 4;
25 | }
26 | },
27 | PropertiesArg.Out => {},
28 | PropertiesArg.In => |ptr| {
29 | buf.add(ptr.*);
30 | },
31 | PropertiesArg.Set => |ptr| {
32 | buf.add(ptr.*);
33 | },
34 | }
35 | }
36 | buffer_length_in_bytes = buf.index * 4;
37 | buf.reset();
38 | buf.add(buffer_length_in_bytes);
39 |
40 | var buffer_pointer = @ptrToInt(buf.data.ptr);
41 | if (buffer_pointer & 0xF != 0) {
42 | panic(@errorReturnTrace(), "video core mailbox buffer not aligned to 16 bytes", .{});
43 | }
44 | const PROPERTY_CHANNEL = 8;
45 | const request = PROPERTY_CHANNEL | @intCast(u32, buffer_pointer);
46 | mailboxes[1].pushRequestBlocking(request);
47 | // log("pull mailbox response");
48 | mailboxes[0].pullResponseBlocking(request);
49 |
50 | buf.reset();
51 | check(&buf, buffer_length_in_bytes);
52 | const BUFFER_RESPONSE_OK = 0x80000000;
53 | check(&buf, BUFFER_RESPONSE_OK);
54 | next_tag_index = buf.index;
55 | for (args) |arg| {
56 | switch (arg) {
57 | PropertiesArg.TagAndLength => |tag_and_length| {
58 | if (tag_and_length.tag != 0) {
59 | // log("parse tag {x} length {}", tag_and_length.tag, tag_and_length.length);
60 | }
61 | buf.index = next_tag_index;
62 | check(&buf, tag_and_length.tag);
63 | if (tag_and_length.tag != TAG_LAST_SENTINEL) {
64 | check(&buf, tag_and_length.length);
65 | const TAG_RESPONSE_OK = 0x80000000;
66 | check(&buf, TAG_RESPONSE_OK | tag_and_length.length);
67 | next_tag_index = buf.index + tag_and_length.length / 4;
68 | }
69 | },
70 | PropertiesArg.Out => |ptr| {
71 | ptr.* = buf.next();
72 | },
73 | PropertiesArg.In => {},
74 | PropertiesArg.Set => |ptr| {
75 | check(&buf, ptr.*);
76 | },
77 | }
78 | }
79 | // log("properties done");
80 | }
81 |
82 | pub fn out(ptr: *u32) PropertiesArg {
83 | return PropertiesArg{ .Out = ptr };
84 | }
85 |
86 | pub fn in(ptr: *u32) PropertiesArg {
87 | return PropertiesArg{ .In = ptr };
88 | }
89 |
90 | const TAG_LAST_SENTINEL = 0;
91 | pub const last_tag_sentinel = tag(TAG_LAST_SENTINEL, 0);
92 |
93 | pub fn set(ptr: *u32) PropertiesArg {
94 | return PropertiesArg{ .Set = ptr };
95 | }
96 |
97 | pub fn tag(the_tag: u32, length: u32) PropertiesArg {
98 | return PropertiesArg{ .TagAndLength = TagAndLength{ .tag = the_tag, .length = length } };
99 | }
100 |
101 | pub const PropertiesArg = union(enum) {
102 | In: *u32,
103 | Out: *u32,
104 | Set: *u32,
105 | TagAndLength: TagAndLength,
106 | };
107 |
108 | const TagAndLength = struct {
109 | tag: u32,
110 | length: u32,
111 | };
112 |
113 | fn check(buf: *SliceIterator.of(u32), word: u32) void {
114 | const was = buf.next();
115 | if (was != word) {
116 | panic(@errorReturnTrace(), "video core mailbox failed index {} was {}/{x} expected {}/{x}", .{ buf.index - 1, was, was, word, word });
117 | }
118 | }
119 |
120 | const log = @import("serial.zig").log;
121 | const mailboxes = @import("video_core_mailboxes.zig").mailboxes;
122 | const panic = @import("debug.zig").panic;
123 | const SliceIterator = @import("slice_iterator.zig");
124 | const std = @import("std");
125 | const assert = std.debug.assert;
126 |
--------------------------------------------------------------------------------
/tools/send_image.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 |
3 | pub fn main() !void {
4 | var direct_allocator = std.heap.DirectAllocator.init();
5 | defer direct_allocator.deinit();
6 |
7 | var arena = std.heap.ArenaAllocator.init(&direct_allocator.allocator);
8 | defer arena.deinit();
9 |
10 | const allocator = &arena.allocator;
11 | const args = try std.os.argsAlloc(allocator);
12 | if (args.len != 2) {
13 | std.debug.panic("expected 2 args, found {} args", args.len);
14 | }
15 | const file_path = args[1];
16 | const new_image_data = try std.io.readFileAlloc(allocator, "clashos.bin");
17 | const tty_fd = try std.os.posixOpen(file_path, std.os.posix.O_WRONLY, 0);
18 | const tty_file = std.os.File.openHandle(tty_fd);
19 | const out = &tty_file.outStream().stream;
20 | try out.write([]u8{ 6, 6, 6 });
21 | try out.writeIntLittle(u32, @intCast(u32, new_image_data.len));
22 | try out.write(new_image_data);
23 | }
24 |
--------------------------------------------------------------------------------