Skip to content

Commit dd8544d

Browse files
authored
Linker script generation update (#588)
1 parent e64a887 commit dd8544d

File tree

32 files changed

+5610
-5447
lines changed

32 files changed

+5610
-5447
lines changed

build-internals/build.zig

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ pub const Target = struct {
5959
/// if present.
6060
board: ?Board = null,
6161

62-
/// (optional) Provide a custom linker script for the hardware or define a custom generation.
63-
linker_script: ?LazyPath = null,
62+
/// Provide a custom linker script for the hardware or define a custom generation.
63+
linker_script: LinkerScript = .{},
6464

65-
/// (Optional) Explicitly set the entry point
65+
/// (optional) Explicitly set the entry point.
6666
entry: ?Build.Step.Compile.Entry = null,
6767

6868
/// (optional) Post processing step that will patch up and modify the elf file if necessary.
@@ -79,7 +79,7 @@ pub const Target = struct {
7979
ram_image: ?bool = null,
8080
hal: ?HardwareAbstractionLayer = null,
8181
board: ?Board = null,
82-
linker_script: ?LazyPath = null,
82+
linker_script: ?LinkerScript = null,
8383
entry: ?Build.Step.Compile.Entry = null,
8484
patch_elf: ?*const fn (*Build.Dependency, LazyPath) LazyPath = null,
8585
};
@@ -206,11 +206,10 @@ pub const BinaryFormat = union(enum) {
206206
pub fn get_extension(format: BinaryFormat) []const u8 {
207207
return switch (format) {
208208
.elf => ".elf",
209-
.bin => ".bin",
209+
.bin, .esp => ".bin",
210210
.hex => ".hex",
211211
.dfu => ".dfu",
212212
.uf2 => ".uf2",
213-
.esp => ".bin",
214213

215214
.custom => |c| c.extension,
216215
};
@@ -225,41 +224,74 @@ pub const BinaryFormat = union(enum) {
225224
};
226225
};
227226

227+
pub const LinkerScript = struct {
228+
/// Will anything be auto-generated for this linker script?
229+
generate: GenerateOptions = .{ .memory_regions_and_sections = .{} },
230+
/// Linker script path. Will be appended after what is auto-generated if it's not null.
231+
file: ?LazyPath = null,
232+
233+
pub const GenerateOptions = union(enum) {
234+
/// Only generates a comment with target info.
235+
none,
236+
/// Only generates memory regions.
237+
memory_regions,
238+
/// Generates memory regions and default sections based on the provided options.
239+
memory_regions_and_sections: struct {
240+
/// Where should rodata go?
241+
rodata_location: enum {
242+
/// Place rodata in the first region tagged as flash.
243+
flash,
244+
/// Place rodata in the first region tagged as ram.
245+
ram,
246+
} = .flash,
247+
},
248+
};
249+
};
250+
228251
/// A descriptor for memory regions in a microcontroller.
229252
pub const MemoryRegion = struct {
230-
/// The type of the memory region for generating a proper linker script.
231-
kind: Kind,
253+
name: ?[]const u8 = null,
254+
tag: Tag = .none,
232255
offset: u64,
233256
length: u64,
257+
access: Access,
258+
259+
pub fn validate_tag(region: MemoryRegion) void {
260+
switch (region.tag) {
261+
.flash => if (!region.access.read or !region.access.execute)
262+
@panic("memory regions tagged as `flash` must be executable"),
263+
.ram => if (!region.access.read or !region.access.write)
264+
@panic("memory regions tagged as `ram` must be both readable and writable"),
265+
else => {},
266+
}
267+
}
234268

235-
pub const Kind = union(enum) {
269+
pub const Tag = enum {
236270
/// This is a (normally) immutable memory region where the code is stored.
237271
flash,
238272

239273
/// This is a mutable memory region for data storage.
240274
ram,
241275

242-
/// This is a memory region that maps MMIO devices.
243-
io,
244-
245-
/// This is a memory region that exists, but is reserved and must not be used.
246-
reserved,
247-
248-
/// This is a memory region used for internal linking tasks required by the board support package.
249-
private: PrivateRegion,
276+
/// No tag.
277+
none,
250278
};
251279

252-
pub const PrivateRegion = struct {
253-
/// The name of the memory region. Will not have an automatic numeric counter and must be unique.
254-
name: []const u8,
255-
256-
/// Is the memory region executable?
257-
executable: bool,
280+
pub const Access = struct {
281+
pub const r: Access = .{ .read = true };
282+
pub const w: Access = .{ .write = true };
283+
pub const x: Access = .{ .execute = true };
284+
pub const rw: Access = .{ .read = true, .write = true };
285+
pub const rx: Access = .{ .read = true, .execute = true };
286+
pub const rwx: Access = .{ .read = true, .write = true, .execute = true };
258287

259288
/// Is the memory region readable?
260-
readable: bool,
289+
read: bool = false,
261290

262291
/// Is the memory region writable?
263-
writeable: bool,
292+
write: bool = false,
293+
294+
/// Is the memory region executable?
295+
execute: bool = false,
264296
};
265297
};

build.zig

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub const Chip = internals.Chip;
99
pub const HardwareAbstractionLayer = internals.HardwareAbstractionLayer;
1010
pub const Board = internals.Board;
1111
pub const BinaryFormat = internals.BinaryFormat;
12+
pub const LinkerScript = internals.LinkerScript;
1213
pub const MemoryRegion = internals.MemoryRegion;
1314

1415
const regz = @import("tools/regz");
@@ -330,7 +331,7 @@ pub fn MicroBuild(port_select: PortSelect) type {
330331
board: ?Board = null,
331332

332333
/// If set, overrides the `linker_script` property of the target.
333-
linker_script: ?LazyPath = null,
334+
linker_script: ?LinkerScript = null,
334335

335336
/// If set, overrides the default `entry` property of the arget.
336337
entry: ?Build.Step.Compile.Entry = null,
@@ -370,11 +371,16 @@ pub fn MicroBuild(port_select: PortSelect) type {
370371

371372
const target = options.target;
372373

374+
// validate that tagged memory regions meet the requirements
375+
for (target.chip.memory_regions) |region| {
376+
region.validate_tag();
377+
}
378+
373379
// TODO: let the user override which ram section to use the stack on,
374380
// for now just using the first ram section in the memory region list
375381
const first_ram = blk: {
376382
for (target.chip.memory_regions) |region| {
377-
if (region.kind == .ram)
383+
if (region.tag == .ram)
378384
break :blk region;
379385
} else @panic("no ram memory region found for setting the end-of-stack address");
380386
};
@@ -547,17 +553,22 @@ pub fn MicroBuild(port_select: PortSelect) type {
547553
fw.artifact.root_module.addImport("microzig", core_mod);
548554
fw.artifact.root_module.addImport("app", app_mod);
549555

550-
// If not specified then generate the linker script
551-
const linker_script = options.linker_script orelse target.linker_script orelse blk: {
556+
const linker_script_options = options.linker_script orelse target.linker_script;
557+
const linker_script = blk: {
552558
const GenerateLinkerScriptArgs = @import("tools/generate_linker_script.zig").Args;
553559

560+
if (linker_script_options.file == null and linker_script_options.generate != .memory_regions_and_sections) {
561+
@panic("linker script: no file provided and no sections are auto-generated");
562+
}
563+
554564
const generate_linker_script_exe = mb.dep.artifact("generate_linker_script");
555565

556566
const generate_linker_script_args: GenerateLinkerScriptArgs = .{
557-
.cpu_name = zig_resolved_target.result.cpu.model.name,
567+
.cpu_name = cpu.name,
558568
.cpu_arch = zig_resolved_target.result.cpu.arch,
559569
.chip_name = target.chip.name,
560570
.memory_regions = target.chip.memory_regions,
571+
.generate = linker_script_options.generate,
561572
};
562573

563574
const args_str = std.json.stringifyAlloc(
@@ -568,7 +579,11 @@ pub fn MicroBuild(port_select: PortSelect) type {
568579

569580
const generate_linker_script_run = b.addRunArtifact(generate_linker_script_exe);
570581
generate_linker_script_run.addArg(args_str);
571-
break :blk generate_linker_script_run.addOutputFileArg("linker.ld");
582+
const output = generate_linker_script_run.addOutputFileArg("linker.ld");
583+
if (linker_script_options.file) |file| {
584+
generate_linker_script_run.addFileArg(file);
585+
}
586+
break :blk output;
572587
};
573588
fw.artifact.setLinkerScript(linker_script);
574589

docs/design.md

Lines changed: 166 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ pub const stm32f103: microzig.Target = .{
7575
.zig = b.path("/path/to/file.zig"),
7676
},
7777
.memory_regions = &.{
78-
.{ .offset = 0x08000000, .length = 64 * 1024, .kind = .flash },
79-
.{ .offset = 0x20000000, .length = 20 * 1024, .kind = .ram },
78+
.{ .tag = .flash, .offset = 0x08000000, .length = 64 * 1024, .access = .rx },
79+
.{ .tag = .ram, .offset = 0x20000000, .length = 20 * 1024, .access = .rwx },
8080
},
8181
},
8282
.hal = .{
@@ -105,9 +105,171 @@ TODO
105105

106106
TODO
107107

108-
## Linkerscript Generation
108+
## Linker Script Generation
109109

110-
TODO
110+
Every firmware needs a linker script that places stuff where it belongs in
111+
memory. When porting microzig to a new target you must face the challenge of
112+
dealing with a linker script. But fear not as microzig has your back (in most
113+
cases). Let's checkout the `linker_script` field in `Target`.
114+
115+
```zig
116+
linker_script: LinkerScript = .{},
117+
```
118+
119+
```zig
120+
pub const LinkerScript = struct {
121+
/// Will anything be auto-generated for this linker script?
122+
generate: GenerateOptions = .{ .memory_regions_and_sections = .{} },
123+
/// Linker script path. Will be appended after what is auto-generated if it's not null.
124+
file: ?LazyPath = null,
125+
126+
pub const GenerateOptions = union(enum) {
127+
/// Only generates a comment with target info.
128+
none,
129+
/// Only generates memory regions.
130+
memory_regions,
131+
/// Generates memory regions and default sections based on the provided options.
132+
memory_regions_and_sections: struct {
133+
/// Where should rodata go?
134+
rodata_location: enum {
135+
/// Place rodata in the first region tagged as flash.
136+
flash,
137+
/// Place rodata in the first region tagged as ram.
138+
ram,
139+
} = .flash,
140+
},
141+
};
142+
};
143+
```
144+
145+
For an example let's look at the target definition of rp2040. In this case we
146+
need a linker script that should also place the bootrom at the beginning of
147+
flash. Fortunately, we can still mostly auto-generate one and just patch it up
148+
a bit.
149+
150+
```zig
151+
// port/raspberrypi/rp2xxx/build.zig
152+
153+
const chip_rp2040: microzig.Target = .{
154+
// ...
155+
.chip = .{
156+
// ...
157+
.memory_regions = &.{
158+
.{ .tag = .flash, .offset = 0x10000000, .length = 2048 * 1024, .access = .rx },
159+
.{ .tag = .ram, .offset = 0x20000000, .length = 256 * 1024, .access = .rwx },
160+
},
161+
},
162+
.linker_script = .{
163+
// The `generate` field defaults to `.memory_regions_and_sections`.
164+
165+
// This will be appended at the end of the auto-generated linker
166+
// script.
167+
.file = b.path("ld/rp2040/sections.ld"),
168+
},
169+
};
170+
```
171+
172+
```ld
173+
/* port/raspberrypi/rp2xxx/ld/rp2040/sections.ld */
174+
175+
SECTIONS
176+
{
177+
.boot2 : {
178+
__boot2_start__ = .;
179+
KEEP (*(.boot2))
180+
__boot2_end__ = .;
181+
} > flash0
182+
183+
ASSERT(__boot2_end__ - __boot2_start__ == 256,
184+
"ERROR: Pico second stage bootloader must be 256 bytes in size")
185+
}
186+
INSERT BEFORE .flash_start;
187+
```
188+
189+
This is the generated linker script:
190+
191+
```ld
192+
/*
193+
* Target CPU: cortex_m0plus
194+
* Target Chip: RP2040
195+
*/
196+
197+
/*
198+
* This section was auto-generated by microzig.
199+
*/
200+
MEMORY
201+
{
202+
flash0 (rx!w) : ORIGIN = 0x10000000, LENGTH = 0x00200000
203+
ram0 (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00040000
204+
}
205+
SECTIONS
206+
{
207+
.flash_start :
208+
{
209+
KEEP(*(microzig_flash_start))
210+
} > flash0
211+
212+
.text :
213+
{
214+
*(.text*)
215+
*(.srodata*)
216+
*(.rodata*)
217+
} > flash0
218+
219+
.ARM.extab : {
220+
*(.ARM.extab* .gnu.linkonce.armextab.*)
221+
} > flash0
222+
223+
.ARM.exidx : {
224+
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
225+
} > flash0
226+
227+
.data :
228+
{
229+
microzig_data_start = .;
230+
*(.sdata*)
231+
*(.data*)
232+
KEEP(*(.ram_text))
233+
microzig_data_end = .;
234+
} > ram0 AT> flash0
235+
236+
.bss (NOLOAD) :
237+
{
238+
microzig_bss_start = .;
239+
*(.sbss*)
240+
*(.bss*)
241+
microzig_bss_end = .;
242+
} > ram0
243+
244+
.flash_end :
245+
{
246+
microzig_flash_end = .;
247+
} > flash0
248+
249+
microzig_data_load_start = LOADADDR(.data);
250+
}
251+
/*
252+
* End of auto-generated section.
253+
*/
254+
SECTIONS
255+
{
256+
.boot2 : {
257+
__boot2_start__ = .;
258+
KEEP (*(.boot2))
259+
__boot2_end__ = .;
260+
} > flash0
261+
262+
ASSERT(__boot2_end__ - __boot2_start__ == 256,
263+
"ERROR: Pico second stage bootloader must be 256 bytes in size")
264+
}
265+
INSERT BEFORE .flash_start;
266+
```
267+
268+
### FYI
269+
- If the ram memory region used by the linker script generator is executable, a
270+
`.ram_text` section will be included for code that should be placed in ram.
271+
This applies to the rp2040 target where the section tagged as ram is
272+
executable.
111273

112274
## JSON register schema
113275

examples/espressif/esp/src/systimer.zig

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ pub const microzig_options: microzig.Options = .{
1616

1717
const alarm = systimer.alarm(0);
1818

19-
// the `.trap` link section is placed in iram in image boot mode or irom in direct boot mode.
20-
fn timer_interrupt(_: *microzig.cpu.TrapFrame) linksection(".trap") callconv(.c) void {
19+
fn timer_interrupt(_: *microzig.cpu.TrapFrame) linksection(".ram_text") callconv(.c) void {
2120
std.log.info("timer interrupt!", .{});
2221

2322
alarm.clear_interrupt();

0 commit comments

Comments
 (0)