diff --git a/build.zig b/build.zig index 2d315ba..3f3dda7 100644 --- a/build.zig +++ b/build.zig @@ -4,35 +4,106 @@ const std = @import("std"); -const Board = struct { - name: []const u8, - path: []const u8, - chip: Chip, -}; +pub fn build(b: *std.build.Builder) void { + const mode = b.standardReleaseOptions(); -const Chip = struct { - name: []const u8, - path: []const u8, - cpu: Cpu, -}; + const test_step = b.step("test", "Builds and runs the library test suite"); -const Cpu = struct { - name: []const u8, - path: []const u8, - target: std.zig.CrossTarget, - linker_script: []const u8, -}; + const BuildConfig = struct { name: []const u8, backing: Backing }; + const all_backings = [_]BuildConfig{ + BuildConfig{ .name = "boards.arduino_nano", .backing = Backing{ .board = pkgs.boards.arduino_nano } }, + BuildConfig{ .name = "boards.mbed_lpc1768", .backing = Backing{ .board = pkgs.boards.mbed_lpc1768 } }, + BuildConfig{ .name = "chips.atmega328p", .backing = Backing{ .chip = pkgs.chips.atmega328p } }, + BuildConfig{ .name = "chips.lpc1768", .backing = Backing{ .chip = pkgs.chips.lpc1768 } }, + }; -pub const Backing = union(enum) { - board: Board, - chip: Chip, -}; + inline for (all_backings) |cfg| { + const exe = addEmbeddedExecutable( + b, + "test-minimal-" ++ cfg.name, + "tests/minimal.zig", + cfg.backing, + ); + exe.setBuildMode(mode); + exe.install(); + + test_step.dependOn(&exe.step); + } +} fn addEmbeddedExecutable(builder: *std.build.Builder, name: []const u8, source: []const u8, backing: Backing) *std.build.LibExeObjStep { const Pkg = std.build.Pkg; + const chip = switch (backing) { + .chip => |c| c, + .board => |b| b.chip, + }; + + const chip_package = Pkg{ + .name = "chip", + .path = chip.path, + .dependencies = &[_]Pkg{ + Pkg{ + .name = "cpu", + .path = chip.cpu.path, + }, + Pkg{ + .name = "microzig-linker", + .path = "src/modules/linker/linker.zig", + }, + }, + }; + + const linker_script_name = blk: { + const hash = hash_blk: { + var hasher = std.hash.SipHash128(1, 2).init("abcdefhijklmnopq"); + + hasher.update(chip.name); + hasher.update(chip.path); + hasher.update(chip.cpu.name); + hasher.update(chip.cpu.path); + + var mac: [16]u8 = undefined; + hasher.final(&mac); + break :hash_blk mac; + }; + + const file_prefix = "zig-cache/microzig/"; + const file_suffix = ".ld"; + + var ld_file_name: [file_prefix.len + 2 * hash.len + file_suffix.len]u8 = undefined; + const filename = std.fmt.bufPrint(&ld_file_name, "{s}{}{s}", .{ + file_prefix, + std.fmt.fmtSliceHexLower(&hash), + file_suffix, + }) catch unreachable; + + break :blk builder.dupe(filename); + }; + + const linkerscript_gen = builder.addExecutable("linkerscript-gen", "src/tools/linkerscript-gen.zig"); + linkerscript_gen.addPackage(chip_package); + linkerscript_gen.addPackage(Pkg{ + .name = "microzig-linker", + .path = "src/modules/linker/linker.zig", + }); + linkerscript_gen.addBuildOption([]const u8, "microzig_chip_name", chip.name); + linkerscript_gen.addBuildOption([]const u8, "microzig_cpu_name", chip.cpu.name); + linkerscript_gen.addBuildOption([]const u8, "microzig_target_triple", chip.cpu.target.zigTriple(builder.allocator) catch unreachable); + + const linkerscript_invocation = linkerscript_gen.run(); + linkerscript_invocation.addArg(linker_script_name); + const exe = builder.addExecutable(name, source); + // might not be true for all machines (Pi Pico), but + // for the HAL it's true (it doesn't know the concept of threading) + exe.single_threaded = true; + exe.setTarget(chip.cpu.target); + + exe.setLinkerScriptPath(linker_script_name); + exe.step.dependOn(&linkerscript_invocation.step); + // TODO: // - Generate the linker scripts from the "chip" or "board" package instead of using hardcoded ones. // - This requires building another tool that runs on the host that compiles those files and emits the linker script. @@ -41,55 +112,30 @@ fn addEmbeddedExecutable(builder: *std.build.Builder, name: []const u8, source: exe.bundle_compiler_rt = false; switch (backing) { - .chip => |chip| { + .chip => { exe.addBuildOption(bool, "microzig_has_board", false); exe.addBuildOption([]const u8, "microzig_chip_name", chip.name); exe.addBuildOption([]const u8, "microzig_cpu_name", chip.cpu.name); - exe.setTarget(chip.cpu.target); - exe.setLinkerScriptPath(chip.cpu.linker_script); exe.addPackage(Pkg{ .name = "microzig", .path = "src/core/microzig.zig", - .dependencies = &[_]Pkg{ - Pkg{ - .name = "chip", - .path = chip.path, - .dependencies = &[_]Pkg{ - Pkg{ - .name = "cpu", - .path = chip.cpu.path, - }, - }, - }, - }, + .dependencies = &[_]Pkg{chip_package}, }); }, .board => |board| { exe.addBuildOption(bool, "microzig_has_board", true); exe.addBuildOption([]const u8, "microzig_board_name", board.name); - exe.addBuildOption([]const u8, "microzig_chip_name", board.chip.name); - exe.addBuildOption([]const u8, "microzig_cpu_name", board.chip.cpu.name); - exe.setTarget(board.chip.cpu.target); - exe.setLinkerScriptPath(board.chip.cpu.linker_script); + exe.addBuildOption([]const u8, "microzig_chip_name", chip.name); + exe.addBuildOption([]const u8, "microzig_cpu_name", chip.cpu.name); exe.addPackage(Pkg{ .name = "microzig", .path = "src/core/microzig.zig", .dependencies = &[_]Pkg{ + chip_package, Pkg{ .name = "board", .path = board.path, - .dependencies = &[_]Pkg{ - Pkg{ - .name = "chip", - .path = board.chip.path, - .dependencies = &[_]Pkg{ - Pkg{ - .name = "cpu", - .path = board.chip.cpu.path, - }, - }, - }, - }, + .dependencies = &[_]Pkg{chip_package}, }, }, }); @@ -98,10 +144,34 @@ fn addEmbeddedExecutable(builder: *std.build.Builder, name: []const u8, source: return exe; } +const Board = struct { + name: []const u8, + path: []const u8, + chip: Chip, +}; + +const Chip = struct { + name: []const u8, + path: []const u8, + cpu: Cpu, +}; + +const Cpu = struct { + name: []const u8, + path: []const u8, + target: std.zig.CrossTarget, + linker_script: []const u8, +}; + +pub const Backing = union(enum) { + board: Board, + chip: Chip, +}; + const pkgs = struct { const cpus = struct { const avr5 = Cpu{ - .name = "avr", + .name = "AVR5", .path = "src/modules/cpus/avr/avr5.zig", .linker_script = "src/modules/cpus/avr/linker.ld", .target = std.zig.CrossTarget{ @@ -112,7 +182,7 @@ const pkgs = struct { }, }; const cortex_m3 = Cpu{ - .name = "cortex-m3", + .name = "ARM Cortex-M3", .path = "src/modules/cpus/cortex-m3/cortex-m3.zig", .linker_script = "src/modules/cpus/cortex-m3/linker.ld", .target = std.zig.CrossTarget{ @@ -126,12 +196,12 @@ const pkgs = struct { const chips = struct { const atmega328p = Chip{ - .name = "AtMega328p", + .name = "ATmega328p", .path = "src/modules/chips/atmega328p/atmega328p.zig", .cpu = cpus.avr5, }; const lpc1768 = Chip{ - .name = "mcu", + .name = "NXP LPC1768", .path = "src/modules/chips/lpc1768/lpc1768.zig", .cpu = cpus.cortex_m3, }; @@ -139,7 +209,7 @@ const pkgs = struct { const boards = struct { const arduino_nano = Board{ - .name = "board", + .name = "Arduino Nano", .path = "src/modules/boards/arduino-nano/arduino-nano.zig", .chip = chips.atmega328p, }; @@ -150,29 +220,3 @@ const pkgs = struct { }; }; }; - -pub fn build(b: *std.build.Builder) void { - const mode = b.standardReleaseOptions(); - - const test_step = b.step("test", "Builds and runs the library test suite"); - - const BuildConfig = struct { name: []const u8, backing: Backing }; - const all_backings = [_]BuildConfig{ - BuildConfig{ .name = "boards.arduino_nano", .backing = Backing{ .board = pkgs.boards.arduino_nano } }, - BuildConfig{ .name = "boards.mbed_lpc1768", .backing = Backing{ .board = pkgs.boards.mbed_lpc1768 } }, - BuildConfig{ .name = "chips.atmega328p", .backing = Backing{ .chip = pkgs.chips.atmega328p } }, - BuildConfig{ .name = "chips.lpc1768", .backing = Backing{ .chip = pkgs.chips.lpc1768 } }, - }; - - inline for (all_backings) |cfg| { - const exe = addEmbeddedExecutable( - b, - "test-minimal-" ++ cfg.name, - "tests/minimal.zig", - cfg.backing, - ); - exe.setBuildMode(mode); - - test_step.dependOn(&exe.step); - } -} diff --git a/src/core/interrupts.zig b/src/core/interrupts.zig index 27d158a..8ca66bc 100644 --- a/src/core/interrupts.zig +++ b/src/core/interrupts.zig @@ -20,13 +20,13 @@ pub fn isEnabled(comptime interrupt: anytype) bool { /// *Set Enable Interrupt*, will enable IRQs globally, but keep the masking done via /// `enable` and `disable` intact. pub fn sei() void { - @panic("not implemented yet!"); + micro.chip.cpu.sei(); } /// *Clear Enable Interrupt*, will disable IRQs globally, but keep the masking done via /// `enable` and `disable` intact. pub fn cli() void { - @panic("not implemented yet!"); + micro.chip.cpu.cli(); } /// Returns true, when interrupts are globally enabled via `sei()`. diff --git a/src/core/microzig.zig b/src/core/microzig.zig index 463db09..3c5ee8f 100644 --- a/src/core/microzig.zig +++ b/src/core/microzig.zig @@ -1,4 +1,11 @@ const std = @import("std"); +const root = @import("root"); + +/// Provides access to the low level features of the current microchip. +pub const chip = @import("chip"); + +/// Provides access to the low level features of the CPU. +pub const cpu = chip.cpu; /// Module that helps with interrupt handling. pub const interrupts = @import("interrupts.zig"); @@ -10,6 +17,11 @@ pub const interrupts = @import("interrupts.zig"); /// pub const panic = micro.panic; /// ``` pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { + hang(); +} + +/// Hangs the processor and will stop doing anything useful. Use with caution! +pub fn hang() noreturn { while (true) { interrupts.cli(); @@ -17,3 +29,48 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur asm volatile ("" ::: "memory"); } } + +/// This is the logical entry point for microzig. +/// It will invoke the main function from the root source file +/// and provides error return handling as well as a event loop if requested. +/// +/// Why is this function exported? +/// This is due to the modular design of microzig to allow the "chip" dependency of microzig +/// to call into our main function here. If we would use a normal function call, we'd have a +/// circular dependency between the `microzig` and `chip` package. This function is also likely +/// to be invoked from assembly, so it's also convenient in that regard. +export fn microzig_main() noreturn { + if (!@hasDecl(root, "main")) + @compileError("The root source file must provide a public function main!"); + + const main = @field(root, "main"); + const info: std.builtin.TypeInfo = @typeInfo(@TypeOf(main)); + + const invalid_main_msg = "main must be either 'pub fn main() void' or 'pub fn main() !void'."; + if (info != .Fn or info.Fn.args.len > 0) + @compileError(invalid_main_msg); + + const return_type = info.Fn.return_type orelse @compileError(invalid_main_msg); + + if (info.Fn.calling_convention == .Async) + @compileError("TODO: Embedded event loop not supported yet. Please try again later."); + + if (@typeInfo(return_type) == .ErrorUnion) { + main() catch |err| { + // TODO: + // - Compute maximum size on the type of "err" + // - Do not emit error names when std.builtin.strip is set. + var msg: [64]u8 = undefined; + @panic(std.fmt.bufPrint(&msg, "main() returned error {s}", .{@tagName(err)}) catch @panic("main() returned error.")); + }; + } else { + main(); + } + + // main returned, just hang around here a bit + hang(); +} + +comptime { + _ = cpu.startup_logic; +} diff --git a/src/modules/boards/arduino-nano/arduino-nano.zig b/src/modules/boards/arduino-nano/arduino-nano.zig index e69de29..c622669 100644 --- a/src/modules/boards/arduino-nano/arduino-nano.zig +++ b/src/modules/boards/arduino-nano/arduino-nano.zig @@ -0,0 +1,29 @@ +pub const chip = @import("chip"); + +pub const pin_map = .{ + // Port A + .D0 = "PD0", + .D1 = "PD1", + .D2 = "PD2", + .D3 = "PD3", + .D4 = "PD4", + .D5 = "PD5", + .D6 = "PD6", + .D7 = "PD7", + // Port B + .D8 = "PB0", + .D9 = "PB1", + .D10 = "PB2", + .D11 = "PB3", + .D12 = "PB4", + .D13 = "PB5", + // Port C (Analog) + .A0 = "PC0", + .A1 = "PC1", + .A2 = "PC2", + .A3 = "PC3", + .A4 = "PC4", + .A5 = "PC5", + .A6 = "ADC6", + .A7 = "ADC7", +}; diff --git a/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig b/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig index e69de29..09e63c0 100644 --- a/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig +++ b/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig @@ -0,0 +1,6 @@ +pub const chip = @import("chip"); +pub const chip = @import("chip"); + +pub const pin_map = .{ + // TODO: Fill this +}; diff --git a/src/modules/chips/atmega328p/atmega328p.zig b/src/modules/chips/atmega328p/atmega328p.zig index e69de29..3277db7 100644 --- a/src/modules/chips/atmega328p/atmega328p.zig +++ b/src/modules/chips/atmega328p/atmega328p.zig @@ -0,0 +1,8 @@ +const micro_linker = @import("microzig-linker"); + +pub const cpu = @import("cpu"); + +pub const memory_regions = [_]micro_linker.MemoryRegion{ + micro_linker.MemoryRegion{ .offset = 0x000000, .length = 32 * 1024, .kind = .flash }, + micro_linker.MemoryRegion{ .offset = 0x800100, .length = 2048, .kind = .ram }, +}; diff --git a/src/modules/chips/lpc1768/lpc1768.zig b/src/modules/chips/lpc1768/lpc1768.zig index e69de29..5fa7874 100644 --- a/src/modules/chips/lpc1768/lpc1768.zig +++ b/src/modules/chips/lpc1768/lpc1768.zig @@ -0,0 +1,9 @@ +const micro_linker = @import("microzig-linker"); + +pub const cpu = @import("cpu"); + +pub const memory_regions = [_]micro_linker.MemoryRegion{ + micro_linker.MemoryRegion{ .offset = 0x00000000, .length = 512 * 1024, .kind = .flash }, + micro_linker.MemoryRegion{ .offset = 0x10000000, .length = 32 * 1024, .kind = .ram }, + micro_linker.MemoryRegion{ .offset = 0x2007C000, .length = 32 * 1024, .kind = .ram }, +}; diff --git a/src/modules/cpus/avr/avr5.zig b/src/modules/cpus/avr/avr5.zig index e69de29..9994b74 100644 --- a/src/modules/cpus/avr/avr5.zig +++ b/src/modules/cpus/avr/avr5.zig @@ -0,0 +1,106 @@ +const std = @import("std"); + +pub fn sei() void { + asm volatile ("sei"); +} + +pub fn cli() void { + asm volatile ("cli"); +} + +pub const startup_logic = struct { + comptime { + asm ( + \\.section microzig_flash_start + \\ jmp _start + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + \\ jmp _unhandled_vector + ); + } + + export fn _unhandled_vector() callconv(.Naked) noreturn { + @panic("Unhandled interrupt"); + } + + extern fn microzig_main() noreturn; + + export fn _start() callconv(.Naked) noreturn { + // At startup the stack pointer is at the end of RAM + // so, no need to set it manually! + + copy_data_to_ram(); + clear_bss(); + + microzig_main(); + } + + fn copy_data_to_ram() void { + asm volatile ( + \\ ; load Z register with the address of the data in flash + \\ ldi r30, lo8(microzig_data_load_start) + \\ ldi r31, hi8(microzig_data_load_start) + \\ ; load X register with address of the data in ram + \\ ldi r26, lo8(microzig_data_start) + \\ ldi r27, hi8(microzig_data_start) + \\ ; load address of end of the data in ram + \\ ldi r24, lo8(microzig_data_end) + \\ ldi r25, hi8(microzig_data_end) + \\ rjmp .L2 + \\ + \\.L1: + \\ lpm r18, Z+ ; copy from Z into r18 and increment Z + \\ st X+, r18 ; store r18 at location X and increment X + \\ + \\.L2: + \\ cp r26, r24 + \\ cpc r27, r25 ; check and branch if we are at the end of data + \\ brne .L1 + ); + // Probably a good idea to add clobbers here, but compiler doesn't seem to care + } + + fn clear_bss() void { + asm volatile ( + \\ ; load X register with the beginning of bss section + \\ ldi r26, lo8(microzig_bss_start) + \\ ldi r27, hi8(microzig_bss_start) + \\ ; load end of the bss in registers + \\ ldi r24, lo8(microzig_bss_end) + \\ ldi r25, hi8(microzig_bss_end) + \\ ldi r18, 0x00 + \\ rjmp .L4 + \\ + \\.L3: + \\ st X+, r18 + \\ + \\.L4: + \\ cp r26, r24 + \\ cpc r27, r25 ; check and branch if we are at the end of bss + \\ brne .L3 + ); + // Probably a good idea to add clobbers here, but compiler doesn't seem to care + } +}; diff --git a/src/modules/cpus/avr/linker.ld b/src/modules/cpus/avr/linker.ld deleted file mode 100644 index f17cabd..0000000 --- a/src/modules/cpus/avr/linker.ld +++ /dev/null @@ -1,31 +0,0 @@ -MEMORY -{ - flash (rx) : ORIGIN = 0, LENGTH = 32K - ram (rw!x) : ORIGIN = 0x800100, LENGTH = 2K -} - -SECTIONS -{ - .text : - { - KEEP(*(.vectors)) - *(.text*) - } > flash - - .data : - { - __data_start = .; - *(.rodata*) - *(.data*) - __data_end = .; - } > ram AT> flash - - .bss (NOLOAD) : - { - __bss_start = .; - *(.bss*) - __bss_end = .; - } > ram - - __data_load_start = LOADADDR(.data); -} \ No newline at end of file diff --git a/src/modules/cpus/cortex-m3/cortex-m3.zig b/src/modules/cpus/cortex-m3/cortex-m3.zig index e69de29..b1f6d7a 100644 --- a/src/modules/cpus/cortex-m3/cortex-m3.zig +++ b/src/modules/cpus/cortex-m3/cortex-m3.zig @@ -0,0 +1,85 @@ +const std = @import("std"); + +pub fn sei() void { + __enable_irq(); +} + +pub fn cli() void { + __disable_irq(); +} + +pub fn __enable_irq() void { + asm volatile ("cpsie i"); +} +pub fn __disable_irq() void { + asm volatile ("cpsid i"); +} + +pub fn __enable_fault_irq() void { + asm volatile ("cpsie f"); +} +pub fn __disable_fault_irq() void { + asm volatile ("cpsid f"); +} + +pub fn __NOP() void { + asm volatile ("nop"); +} +pub fn __WFI() void { + asm volatile ("wfi"); +} +pub fn __WFE() void { + asm volatile ("wfe"); +} +pub fn __SEV() void { + asm volatile ("sev"); +} +pub fn __ISB() void { + asm volatile ("isb"); +} +pub fn __DSB() void { + asm volatile ("dsb"); +} +pub fn __DMB() void { + asm volatile ("dmb"); +} +pub fn __CLREX() void { + asm volatile ("clrex"); +} + +pub const startup_logic = struct { + const InterruptVector = fn () callconv(.C) void; + + const VectorTable = extern struct { + initial_stack_pointer: u32, + reset: InterruptVector, + nmi: InterruptVector = unhandledInterrupt, + hard_fault: InterruptVector = unhandledInterrupt, + mpu_fault: InterruptVector = unhandledInterrupt, + bus_fault: InterruptVector = unhandledInterrupt, + usage_fault: InterruptVector = unhandledInterrupt, + + reserved: u32 = 0, + }; + + export const vectors linksection("microzig_flash_start") = VectorTable{ + // TODO: How to compute/get the initial stack pointer? + .initial_stack_pointer = 0x1000_7FFC, // HACK: hardcoded, do not keep! + .reset = _start, + }; + + fn unhandledInterrupt() callconv(.C) noreturn { + @panic("unhandled interrupt"); + } + + extern fn microzig_main() noreturn; + + fn _start() callconv(.C) noreturn { + + // TODO: + // - Load .data + // - Clear .bss + + microzig_main(); + } +}; diff --git a/src/modules/cpus/cortex-m3/linker.ld b/src/modules/cpus/cortex-m3/linker.ld deleted file mode 100644 index 17b070a..0000000 --- a/src/modules/cpus/cortex-m3/linker.ld +++ /dev/null @@ -1,79 +0,0 @@ -MEMORY -{ - flash (rx!w) : ORIGIN = 0x00000000, LENGTH = 512k - ram0 (rw!x) : ORIGIN = 0x10000000, LENGTH = 32k - ram1 (rw!x) : ORIGIN = 0x2007C000, LENGTH = 32k -} -SECTIONS -{ - . = 0; - /* Code-Speicher im Flash ***********/ - .text : - { - __code_start__ = .; - - LONG( ORIGIN(ram1) + LENGTH(ram1) ) - KEEP(*( .isr_vector )); - - *(.text) - *(.text.*) - - . = ALIGN(4); - __code_end__ = .; - - *(.gnu.linkonce.t.*) - *(.glue_7) - *(.glue_7t) - *(.gcc_except_table) - *(.gnu.linkonce.r.*) - - } >flash - - . = ALIGN(4); - - /* contains unwinding information */ - .ARM.exidx : { - . = ALIGN(4); - __exidx_start = .; - *(.ARM.exidx* .gnu.linkonce.armexidx.*) - __exidx_end = .; - . = ALIGN(4); - } >flash - - . = ALIGN(4); - - /* Konstanten im Flash ****************/ - .rodata . : - { - . = ALIGN(4); - *(.rodata) - . = ALIGN(4); - *(.rodata.*) - } >flash - - . = ALIGN(4); - - PROVIDE (__text__end = .); - - .data : AT (__text__end) - { - PROVIDE (__data__start = .); - *(.data) - *(.data.*) - *(.gnu.linkonce.d*) - PROVIDE (__data__end = .); - } >ram0 - - .bss : - { - PROVIDE (__bss__start = .); - *(.bss) - *(.bss.*) - *(.gnu.linkonce.b*) - . = ALIGN(4); - PROVIDE (__bss__end = .); - } >ram0 - - _end = .; - PROVIDE (end = .); -} \ No newline at end of file diff --git a/src/modules/linker/linker.zig b/src/modules/linker/linker.zig new file mode 100644 index 0000000..f6508b2 --- /dev/null +++ b/src/modules/linker/linker.zig @@ -0,0 +1,20 @@ +//! This module is meant to be used to define linking apis + +pub const MemoryRegion = struct { + kind: Kind, + offset: u64, + length: u64, + + pub const Kind = union(enum) { + flash, + ram, + custom: RegionSpec, + }; + + pub const RegionSpec = struct { + name: []const u8, + executable: bool, + readable: bool, + writeable: bool, + }; +}; diff --git a/src/tools/linkerscript-gen.zig b/src/tools/linkerscript-gen.zig index c3266df..a23c272 100644 --- a/src/tools/linkerscript-gen.zig +++ b/src/tools/linkerscript-gen.zig @@ -1,20 +1,138 @@ const std = @import("std"); - +const build_options = @import("build_options"); const chip = @import("chip"); +const micro_linker = @import("microzig-linker"); + +const target = blk: { + @setEvalBranchQuota(20_000); + + const t = std.zig.CrossTarget.parse(std.zig.CrossTarget.ParseOptions{ + .arch_os_abi = build_options.microzig_target_triple, + }) catch unreachable; + std.debug.assert(t.cpu_arch != null); + break :blk t; +}; pub fn main() !u8 { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); - const allocator = &arena.deinit(); + const allocator = &arena.allocator; const args = try std.process.argsAlloc(allocator); + if (args.len < 2) { - std.log.err("Missing CLI argument. Give the output file name!"); + std.log.err("Missing CLI argument. Give the output file name!", .{}); return 1; } + if (std.fs.path.dirname(args[1])) |dir| { + try std.fs.cwd().makePath(dir); + } + var dest_file = try std.fs.cwd().createFile(args[1], .{}); defer dest_file.close(); - try dest_file.writeAll("THIS FILE IS NOT USABLE YET!"); + var writer = dest_file.writer(); + + try writer.writeAll("/*\n * This file was auto-generated by microzig\n *\n"); + try writer.print(" * Target CPU: {s}\n", .{build_options.microzig_cpu_name}); + try writer.print(" * Target Chip: {s}\n", .{build_options.microzig_chip_name}); + try writer.writeAll(" */\n\n"); + + try writer.writeAll( + \\ + // This is not the "true" entry point, but there's no such thing on embedded platforms + // anyways. This is the logical entrypoint that should be invoked when + // stack, .data and .bss are set up and the CPU is ready to be used. + \\ENTRY(microzig_main); + \\ + \\ + ); + + try writer.writeAll("MEMORY\n{\n"); + { + var counters = [2]usize{ 0, 0 }; + for (chip.memory_regions) |region| { + // flash (rx!w) : ORIGIN = 0x00000000, LENGTH = 512k + + switch (region.kind) { + .flash => { + try writer.print(" flash{d} (rx!w)", .{counters[0]}); + counters[0] += 1; + }, + .ram => { + try writer.print(" ram{d} (rw!x)", .{counters[1]}); + counters[1] += 1; + }, + .custom => |custom| { + try writer.print(" {s} (", .{custom.name}); + if (custom.readable) try writer.writeAll("r"); + if (custom.writeable) try writer.writeAll("w"); + if (custom.executable) try writer.writeAll("x"); + + if (!custom.readable or !custom.writeable or !custom.executable) { + try writer.writeAll("!"); + if (!custom.readable) try writer.writeAll("r"); + if (!custom.writeable) try writer.writeAll("w"); + if (!custom.executable) try writer.writeAll("x"); + } + try writer.writeAll(")"); + }, + } + try writer.print(" : ORIGIN = 0x{X:0>8}, LENGTH = 0x{X:0>8}\n", .{ region.offset, region.length }); + } + } + + try writer.writeAll("}\n\nSECTIONS\n{\n"); + { + try writer.writeAll( + \\ .text : + \\ { + \\ KEEP(*(microzig_flash_start)) + \\ *(.text*) + \\ } > flash0 + \\ + \\ + ); + + if (target.cpu_arch.? == .arm or target.cpu_arch.? == .thumb) { + try writer.writeAll( + \\ .ARM.exidx : { + \\ *(.ARM.exidx* .gnu.linkonce.armexidx.*) + \\ } >flash0 + \\ + \\ + ); + } + + try writer.writeAll( + \\ .data : + \\ { + \\ microzig_data_start = .; + \\ *(.rodata*) + \\ *(.data*) + \\ microzig_data_end = .; + \\ } > ram0 AT> flash0 + \\ + \\ .bss (NOLOAD) : + \\ { + \\ microzig_bss_start = .; + \\ *(.bss*) + \\ microzig_bss_end = .; + \\ } > ram0 + \\ + \\ microzig_data_load_start = LOADADDR(.data); + \\ + ); + } + try writer.writeAll("}\n"); + + // TODO: Assert that the flash can actually hold all data! + // try writer.writeAll( + // \\ + // \\ ASSERT( (SIZEOF(.text) + SIZEOF(.data) > LENGTH(flash0)), "Error: .text + .data is too large for flash!" ); + // \\ + // ); + return 0; } diff --git a/tests/minimal.zig b/tests/minimal.zig index 9ad7f13..17a2105 100644 --- a/tests/minimal.zig +++ b/tests/minimal.zig @@ -4,5 +4,5 @@ const micro = @import("microzig"); pub const panic = micro.panic; pub fn main() void { - return; + // This function will contain the application logic. }