diff --git a/bsp/espressif/esp/src/cpus/espressif-riscv.zig b/bsp/espressif/esp/src/cpus/espressif-riscv.zig index 5aab92d79..6aa780db3 100644 --- a/bsp/espressif/esp/src/cpus/espressif-riscv.zig +++ b/bsp/espressif/esp/src/cpus/espressif-riscv.zig @@ -101,3 +101,7 @@ pub const startup_logic = struct { _rv32_trap, }; }; + +pub fn export_startup_logic() void { + // no op as it's already being exported +} diff --git a/bsp/raspberrypi/rp2040/src/hal/pins.zig b/bsp/raspberrypi/rp2040/src/hal/pins.zig index c17ada0f1..e3ab75a61 100644 --- a/bsp/raspberrypi/rp2040/src/hal/pins.zig +++ b/bsp/raspberrypi/rp2040/src/hal/pins.zig @@ -44,7 +44,7 @@ pub const Pin = enum { GPIO29, pub const Configuration = struct { - name: ?[]const u8 = null, + name: ?[:0]const u8 = null, function: Function = .SIO, direction: ?gpio.Direction = null, drive_strength: ?gpio.DriveStrength = null, diff --git a/bsp/stmicro/stm32/src/hals/STM32F103/pins.zig b/bsp/stmicro/stm32/src/hals/STM32F103/pins.zig index 021dd6c11..d587aedc5 100644 --- a/bsp/stmicro/stm32/src/hals/STM32F103/pins.zig +++ b/bsp/stmicro/stm32/src/hals/STM32F103/pins.zig @@ -30,7 +30,7 @@ pub const Pin = enum { PIN14, PIN15, pub const Configuration = struct { - name: ?[]const u8 = null, + name: ?[:0]const u8 = null, // function: Function = .SIO, mode: ?gpio.Mode = null, speed: ?gpio.Speed = null, @@ -177,7 +177,7 @@ pub const GlobalConfiguration = struct { comptime var input_gpios: u16 = 0; comptime var output_gpios: u16 = 0; comptime { - inline for (@typeInfo(Port.Configuration).Struct.fields) |field| + for (@typeInfo(Port.Configuration).Struct.fields) |field| if (@field(port_config, field.name)) |pin_config| { const gpio_num = @intFromEnum(@field(Pin, field.name)); diff --git a/build/build.zig b/build/build.zig index 72e593086..4d420461e 100644 --- a/build/build.zig +++ b/build/build.zig @@ -2,6 +2,7 @@ host_build: *Build, self: *Build.Dependency, microzig_core: *Build.Dependency, +generate_linkerscript: *Build.Step.Compile, const std = @import("std"); const Build = std.Build; @@ -45,6 +46,11 @@ pub fn init(b: *Build, opts: struct { .host_build = b, .self = mz_dep, .microzig_core = core_dep, + .generate_linkerscript = mz_dep.builder.addExecutable(.{ + .name = "generate-linkerscript", + .root_source_file = .{ .path = "src/generate_linkerscript.zig" }, + .target = mz_dep.builder.host, + }), }; return ret; @@ -198,10 +204,26 @@ pub fn add_firmware( if (linker_script) |ls| { fw.artifact.setLinkerScriptPath(ls); } else { + const target = mz.host_build.resolveTargetQuery(chip.cpu.target); + const generate_linkerscript_args = GenerateLinkerscriptArgs{ + .cpu_name = target.result.cpu.model.name, + .cpu_arch = target.result.cpu.arch, + .chip_name = chip.name, + .memory_regions = chip.memory_regions, + }; + + const args_str = std.json.stringifyAlloc( + mz.microzig_core.builder.allocator, + generate_linkerscript_args, + .{}, + ) catch @panic("OOM"); + + const generate_linkerscript_run = mz.microzig_core.builder.addRunArtifact(mz.generate_linkerscript); + generate_linkerscript_run.addArg(args_str); + const linkerscript = generate_linkerscript_run.addOutputFileArg("linker.ld"); + // If not specified then generate the linker script - fw.artifact.setLinkerScript( - mz.generate_linkerscript(chip.*) catch @panic("out of memory"), - ); + fw.artifact.setLinkerScript(linkerscript); } if (options.target.configure) |configure| { @@ -211,6 +233,8 @@ pub fn add_firmware( return fw; } +const GenerateLinkerscriptArgs = @import("src/generate_linkerscript.zig").Args; + /// Adds a new dependency to the `install` step that will install the `firmware` into the folder `$prefix/firmware`. pub fn install_firmware( /// The MicroZig instance that was used to create the firmware. @@ -256,133 +280,6 @@ fn dependency(env: *MicroZig, name: []const u8, args: anytype) *Build.Dependency return env.self.builder.dependency(name, args); } -fn generate_linkerscript(mz: *MicroZig, chip: Chip) !Build.LazyPath { - const target = mz.host_build.resolveTargetQuery(chip.cpu.target); - - var contents = std.ArrayList(u8).init(mz.host_build.allocator); - const writer = contents.writer(); - try writer.print( - \\/* - \\ * This file was auto-generated by microzig - \\ * - \\ * Target CPU: {[cpu]s} - \\ * Target Chip: {[chip]s} - \\ */ - \\ - // 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); - \\ - \\ - , .{ - .cpu = target.result.cpu.model.name, - .chip = chip.name, - }); - - try writer.writeAll("MEMORY\n{\n"); - { - var counters = [4]usize{ 0, 0, 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; - }, - - .io => { - try writer.print(" io{d} (rw!x)", .{counters[2]}); - counters[2] += 1; - }, - - .reserved => { - try writer.print(" reserved{d} (rw!x)", .{counters[3]}); - counters[3] += 1; - }, - - .private => |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 - \\ - \\ - ); - - switch (target.result.cpu.arch) { - .arm, .thumb => try writer.writeAll( - \\ .ARM.exidx : { - \\ *(.ARM.exidx* .gnu.linkonce.armexidx.*) - \\ } >flash0 - \\ - \\ - ), - else => {}, - } - - 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!" ); - // \\ - // ); - - const write = mz.host_build.addWriteFiles(); - - return write.add("linker.ld", contents.items); -} - /// The resulting binary format for the firmware file. /// A lot of embedded systems don't use plain ELF files, thus we provide means /// to convert the resulting ELF into other common formats. @@ -489,44 +386,7 @@ pub const Cpu = struct { target: std.Target.Query, }; -/// A descriptor for memory regions in a microcontroller. -pub const MemoryRegion = struct { - /// The type of the memory region for generating a proper linker script. - kind: Kind, - offset: u64, - length: u64, - - pub const Kind = union(enum) { - /// This is a (normally) immutable memory region where the code is stored. - flash, - - /// This is a mutable memory region for data storage. - ram, - - /// This is a memory region that maps MMIO devices. - io, - - /// This is a memory region that exists, but is reserved and must not be used. - reserved, - - /// This is a memory region used for internal linking tasks required by the board support package. - private: PrivateRegion, - }; - - pub const PrivateRegion = struct { - /// The name of the memory region. Will not have an automatic numeric counter and must be unique. - name: []const u8, - - /// Is the memory region executable? - executable: bool, - - /// Is the memory region readable? - readable: bool, - - /// Is the memory region writable? - writeable: bool, - }; -}; +pub const MemoryRegion = @import("src/generate_linkerscript.zig").MemoryRegion; /// Defines a custom microcontroller. pub const Chip = struct { diff --git a/build/src/generate_linkerscript.zig b/build/src/generate_linkerscript.zig new file mode 100644 index 000000000..74bc659f3 --- /dev/null +++ b/build/src/generate_linkerscript.zig @@ -0,0 +1,185 @@ +const std = @import("std"); + +/// A descriptor for memory regions in a microcontroller. +pub const MemoryRegion = struct { + /// The type of the memory region for generating a proper linker script. + kind: Kind, + offset: u64, + length: u64, + + pub const Kind = union(enum) { + /// This is a (normally) immutable memory region where the code is stored. + flash, + + /// This is a mutable memory region for data storage. + ram, + + /// This is a memory region that maps MMIO devices. + io, + + /// This is a memory region that exists, but is reserved and must not be used. + reserved, + + /// This is a memory region used for internal linking tasks required by the board support package. + private: PrivateRegion, + }; + + pub const PrivateRegion = struct { + /// The name of the memory region. Will not have an automatic numeric counter and must be unique. + name: []const u8, + + /// Is the memory region executable? + executable: bool, + + /// Is the memory region readable? + readable: bool, + + /// Is the memory region writable? + writeable: bool, + }; +}; + +pub const Args = struct { + cpu_name: []const u8, + cpu_arch: std.Target.Cpu.Arch, + chip_name: []const u8, + memory_regions: []const MemoryRegion, +}; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); + + const allocator = arena.allocator(); + const args = try std.process.argsAlloc(allocator); + + const json_args = args[1]; + const output_path = args[2]; + + const parsed_args = try std.json.parseFromSlice(Args, allocator, json_args, .{}); + const program_args = parsed_args.value; + + const file = try std.fs.cwd().createFile(output_path, .{}); + defer file.close(); + + const writer = file.writer(); + try writer.print( + \\/* + \\ * This file was auto-generated by microzig + \\ * + \\ * Target CPU: {[cpu]s} + \\ * Target Chip: {[chip]s} + \\ */ + \\ + // 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); + \\ + \\ + , .{ + .cpu = program_args.cpu_name, + .chip = program_args.chip_name, + }); + + try writer.writeAll("MEMORY\n{\n"); + { + var counters = [4]usize{ 0, 0, 0, 0 }; + for (program_args.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; + }, + + .io => { + try writer.print(" io{d} (rw!x)", .{counters[2]}); + counters[2] += 1; + }, + + .reserved => { + try writer.print(" reserved{d} (rw!x)", .{counters[3]}); + counters[3] += 1; + }, + + .private => |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 + \\ + \\ + ); + + switch (program_args.cpu_arch) { + .arm, .thumb => try writer.writeAll( + \\ .ARM.exidx : { + \\ *(.ARM.exidx* .gnu.linkonce.armexidx.*) + \\ } >flash0 + \\ + \\ + ), + else => {}, + } + + 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!" ); + // \\ + // ); +} diff --git a/core/src/cpus/avr5.zig b/core/src/cpus/avr5.zig index c86b936a3..ef57c28db 100644 --- a/core/src/cpus/avr5.zig +++ b/core/src/cpus/avr5.zig @@ -76,6 +76,12 @@ pub const vector_table = blk: { break :blk T._start; }; +pub fn export_startup_logic() void { + @export(vector_table, .{ + .name = "_start", + }); +} + fn make_isr_handler(comptime name: []const u8, comptime func: anytype) type { const calling_convention = switch (@typeInfo(@TypeOf(func))) { .Fn => |info| info.calling_convention, diff --git a/core/src/cpus/cortex-m.zig b/core/src/cpus/cortex-m.zig index 4baec1bc1..58800eeb6 100644 --- a/core/src/cpus/cortex-m.zig +++ b/core/src/cpus/cortex-m.zig @@ -104,6 +104,12 @@ pub const startup_logic = struct { } }; +pub fn export_startup_logic() void { + @export(startup_logic._start, .{ + .name = "_start", + }); +} + fn is_valid_field(field_name: []const u8) bool { return !std.mem.startsWith(u8, field_name, "reserved") and !std.mem.eql(u8, field_name, "initial_stack_pointer") and diff --git a/core/src/cpus/riscv32.zig b/core/src/cpus/riscv32.zig index b7c1b9624..5b58f6693 100644 --- a/core/src/cpus/riscv32.zig +++ b/core/src/cpus/riscv32.zig @@ -81,6 +81,12 @@ pub const startup_logic = struct { } }; +pub fn export_startup_logic() void { + @export(startup_logic._start, .{ + .name = "_start", + }); +} + pub const MSTATUS_UIE = 0x00000001; pub const MSTATUS_SIE = 0x00000002; pub const MSTATUS_HIE = 0x00000004; diff --git a/core/src/start.zig b/core/src/start.zig index ad7d86d54..5a9f6e60b 100644 --- a/core/src/start.zig +++ b/core/src/start.zig @@ -16,45 +16,24 @@ else // defined. Parts of microzig use the stdlib logging facility and // compilations will now fail on freestanding systems that use it but do // not explicitly set `root.std_options.logFn` - -pub const std_options = .{ - .logFn = struct { - fn log( - comptime message_level: std.log.Level, - comptime scope: @Type(.EnumLiteral), - comptime format: []const u8, - args: anytype, - ) void { - _ = message_level; - _ = scope; - _ = format; - _ = args; - } - }.log, -}; - -//pub usingnamespace if (!@hasDecl(app, "std_options")) -// struct { -// pub const std_options = std.Options{ -// .logFn = struct { -// fn log( -// comptime message_level: std.log.Level, -// comptime scope: @Type(.EnumLiteral), -// comptime format: []const u8, -// args: anytype, -// ) void { -// _ = message_level; -// _ = scope; -// _ = format; -// _ = args; -// } -// }.log, -// }; -// } -//else -// struct { -// pub const std_options = app.std_options; -// }; +pub const std_options = if (@hasDecl(app, "std_options")) + app.std_options +else + .{ + .logFn = struct { + fn log( + comptime message_level: std.log.Level, + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: anytype, + ) void { + _ = message_level; + _ = scope; + _ = format; + _ = args; + } + }.log, + }; // Startup logic: comptime { @@ -64,7 +43,7 @@ comptime { // .rodata is not always necessary to be populated (flash based systems // can just index flash, while harvard or flash-less architectures need // to copy .rodata into RAM). - _ = microzig.cpu.startup_logic; + microzig.cpu.export_startup_logic(); // Export the vector table to flash start if we have any. // For a lot of systems, the vector table provides a reset vector