diff --git a/riscv-rt/Cargo.toml b/riscv-rt/Cargo.toml index 761cd52b..fc3c2b6e 100644 --- a/riscv-rt/Cargo.toml +++ b/riscv-rt/Cargo.toml @@ -22,6 +22,7 @@ targets = [ [build-dependencies] riscv-target-parser = { path = "../riscv-target-parser", version = "0.1.2" } +minilink = "0.2" [dependencies] riscv = { path = "../riscv", version = "0.14.0" } diff --git a/riscv-rt/build.rs b/riscv-rt/build.rs index e1da2d15..0e8ceab4 100644 --- a/riscv-rt/build.rs +++ b/riscv-rt/build.rs @@ -1,49 +1,19 @@ // NOTE: Adapted from cortex-m/build.rs -use riscv_target_parser::RiscvTarget; -use std::{env, fs, io, path::PathBuf}; +use riscv_target_parser::{RiscvTarget, Width}; +use std::env; // List of all possible RISC-V configurations to check for in risv-rt const RISCV_CFG: [&str; 4] = ["riscvi", "riscvm", "riscvf", "riscvd"]; -fn add_linker_script(arch_width: u32) -> io::Result<()> { - // Read the file to a string and replace all occurrences of ${ARCH_WIDTH} with the arch width - let mut content = fs::read_to_string("link.x.in")?; - content = content.replace("${ARCH_WIDTH}", &arch_width.to_string()); - - // Get target-dependent linker configuration and replace ${INCLUDE_LINKER_FILES} with it - let mut include_content = String::new(); - - // If no-exceptions is disabled, include the exceptions.x files - if env::var_os("CARGO_FEATURE_NO_EXCEPTIONS").is_none() { - let exceptions_content = fs::read_to_string("exceptions.x")?; - include_content.push_str(&(exceptions_content + "\n")); - } - // If no-interrupts is disabled, include the interrupts.x files - if env::var_os("CARGO_FEATURE_NO_INTERRUPTS").is_none() { - let interrupts_content = fs::read_to_string("interrupts.x")?; - include_content.push_str(&(interrupts_content + "\n")); - } - // If device is enabled, include the device.x file (usually, provided by PACs) - if env::var_os("CARGO_FEATURE_DEVICE").is_some() { - include_content.push_str("/* Device-specific exception and interrupt handlers */\n"); - include_content.push_str("INCLUDE device.x\n"); +fn add_linker_script(arch_width: Width) { + // `CARGO_CFG_TARGET_POINTER_WIDTH` technically be used, but instruction + // alignment and pointer width aren't necessarily the same things. + unsafe { + std::env::set_var("CARGO_CFG_ARCH_WIDTH", arch_width.to_string()); } - // If memory is enabled, include the memory.x file (usually, provided by BSPs) - if env::var_os("CARGO_FEATURE_MEMORY").is_some() { - include_content.push_str("/* Device-specific memory layout */\n"); - include_content.push_str("INCLUDE memory.x\n"); - } - - content = content.replace("${INCLUDE_LINKER_FILES}", &include_content); - // Put the linker script somewhere the linker can find it - let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - fs::write(out_dir.join("link.x"), content)?; - println!("cargo:rustc-link-search={}", out_dir.display()); - println!("cargo:rerun-if-changed=link.x"); - - Ok(()) + minilink::register_template("./link.x.in", "link.x"); } fn main() { @@ -86,6 +56,7 @@ fn main() { println!("cargo:rustc-cfg={flag}"); } } - add_linker_script(width.into()).unwrap(); + + add_linker_script(width); } } diff --git a/riscv-rt/exceptions.x b/riscv-rt/exceptions.x deleted file mode 100644 index e9d3f8fb..00000000 --- a/riscv-rt/exceptions.x +++ /dev/null @@ -1,23 +0,0 @@ -/* # EXCEPTION HANDLERS DESCRIBED IN THE STANDARD RISC-V ISA - - If the `no-exceptions` feature is DISABLED, this file will be included in link.x.in. - If the `no-exceptions` feature is ENABLED, this file will be ignored. -*/ - -/* It is possible to define a special handler for each exception type. - By default, all exceptions are handled by ExceptionHandler. However, - users can override these alias by defining the symbol themselves */ -PROVIDE(InstructionMisaligned = ExceptionHandler); -PROVIDE(InstructionFault = ExceptionHandler); -PROVIDE(IllegalInstruction = ExceptionHandler); -PROVIDE(Breakpoint = ExceptionHandler); -PROVIDE(LoadMisaligned = ExceptionHandler); -PROVIDE(LoadFault = ExceptionHandler); -PROVIDE(StoreMisaligned = ExceptionHandler); -PROVIDE(StoreFault = ExceptionHandler); -PROVIDE(UserEnvCall = ExceptionHandler); -PROVIDE(SupervisorEnvCall = ExceptionHandler); -PROVIDE(MachineEnvCall = ExceptionHandler); -PROVIDE(InstructionPageFault = ExceptionHandler); -PROVIDE(LoadPageFault = ExceptionHandler); -PROVIDE(StorePageFault = ExceptionHandler); diff --git a/riscv-rt/interrupts.x b/riscv-rt/interrupts.x deleted file mode 100644 index 2440be77..00000000 --- a/riscv-rt/interrupts.x +++ /dev/null @@ -1,25 +0,0 @@ -/* # CORE INTERRUPT HANDLERS DESCRIBED IN THE STANDARD RISC-V ISA - - If the `no-interrupts` feature is DISABLED, this file will be included in link.x.in. - If the `no-interrupts` feature is ENABLED, this file will be ignored. -*/ - -/* It is possible to define a special handler for each interrupt type. - By default, all interrupts are handled by DefaultHandler. However, users can - override these alias by defining the symbol themselves */ -PROVIDE(SupervisorSoft = DefaultHandler); -PROVIDE(MachineSoft = DefaultHandler); -PROVIDE(SupervisorTimer = DefaultHandler); -PROVIDE(MachineTimer = DefaultHandler); -PROVIDE(SupervisorExternal = DefaultHandler); -PROVIDE(MachineExternal = DefaultHandler); - -/* When vectored trap mode is enabled, each interrupt source must implement its own - trap entry point. By default, all interrupts start in _DefaultHandler_trap. - However, users can override these alias by defining the symbol themselves */ -PROVIDE(_start_SupervisorSoft_trap = _start_DefaultHandler_trap); -PROVIDE(_start_MachineSoft_trap = _start_DefaultHandler_trap); -PROVIDE(_start_SupervisorTimer_trap = _start_DefaultHandler_trap); -PROVIDE(_start_MachineTimer_trap = _start_DefaultHandler_trap); -PROVIDE(_start_SupervisorExternal_trap = _start_DefaultHandler_trap); -PROVIDE(_start_MachineExternal_trap = _start_DefaultHandler_trap); diff --git a/riscv-rt/link.x.in b/riscv-rt/link.x.in index 0ef9bb02..55ba1ba0 100644 --- a/riscv-rt/link.x.in +++ b/riscv-rt/link.x.in @@ -12,13 +12,9 @@ - `PROVIDE` is used to provide default values that can be overridden by a user linker script -- In this linker script, you may find symbols that look like `${...}` (e.g., `${ARCH_WIDTH}`). - These are wildcards used by the `build.rs` script to adapt to different target particularities. - Check `build.rs` for more details about these symbols. - - On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* - the LMA of .data are all `${ARCH_WIDTH}`-byte aligned. These alignments are assumed by the RAM - initialization routine. There's also a second benefit: `${ARCH_WIDTH}`-byte aligned boundaries + the LMA of .data are all `{{ cfg.arch_width }}`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `{{ cfg.arch_width }}`-byte aligned boundaries means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. */ @@ -67,7 +63,63 @@ PROVIDE(DefaultHandler = abort); to avoid compilation errors in direct mode, not to allow users to overwrite the symbol. */ PROVIDE(_start_DefaultHandler_trap = _start_trap); -${INCLUDE_LINKER_FILES} +{% if not contains(cfg.feature, "no-exceptions") %} +/* # EXCEPTION HANDLERS DESCRIBED IN THE STANDARD RISC-V ISA + + It is possible to define a special handler for each exception type. + By default, all exceptions are handled by ExceptionHandler. However, + users can override these alias by defining the symbol themselves +*/ +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); +{% endif %} + +{% if not contains(cfg.feature, "no-interrupts") %} +/* # CORE INTERRUPT HANDLERS DESCRIBED IN THE STANDARD RISC-V ISA + + It is possible to define a special handler for each interrupt type. + By default, all interrupts are handled by DefaultHandler. However, users can + override these alias by defining the symbol themselves +*/ +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +/* When vectored trap mode is enabled, each interrupt source must implement its own + trap entry point. By default, all interrupts start in _DefaultHandler_trap. + However, users can override these alias by defining the symbol themselves */ +PROVIDE(_start_SupervisorSoft_trap = _start_DefaultHandler_trap); +PROVIDE(_start_MachineSoft_trap = _start_DefaultHandler_trap); +PROVIDE(_start_SupervisorTimer_trap = _start_DefaultHandler_trap); +PROVIDE(_start_MachineTimer_trap = _start_DefaultHandler_trap); +PROVIDE(_start_SupervisorExternal_trap = _start_DefaultHandler_trap); +PROVIDE(_start_MachineExternal_trap = _start_DefaultHandler_trap); +{% endif %} + +{% if contains(cfg.feature, "device")%} +/* Device-specific exception and interrupt handlers, usually provided by PACs */ +INCLUDE device.x +{% endif %} + +{% if contains(cfg.feature, "memory")%} +/* Device-specific memory layout, usually provided by BSPs */ +INCLUDE memory.x +{% endif %} PROVIDE(_stext = ORIGIN(REGION_TEXT)); PROVIDE(_stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK)); @@ -108,16 +160,16 @@ SECTIONS *(.srodata .srodata.*); *(.rodata .rodata.*); - /* ${ARCH_WIDTH}-byte align the end (VMA) of this section. + /* {{ cfg.arch_width }}-byte align the end (VMA) of this section. This is required by LLD to ensure the LMA of the following .data section will have the correct alignment. */ - . = ALIGN(${ARCH_WIDTH}); + . = ALIGN({{ cfg.arch_width }}); __erodata = .; } > REGION_RODATA - .data : ALIGN(${ARCH_WIDTH}) + .data : ALIGN({{ cfg.arch_width }}) { - . = ALIGN(${ARCH_WIDTH}); + . = ALIGN({{ cfg.arch_width }}); __sdata = .; /* Must be called __global_pointer$ for linker relaxations to work. */ @@ -130,15 +182,15 @@ SECTIONS /* Allow sections from user `memory.x` injected using `INSERT AFTER .data` to * use the .data loading mechanism by pushing __edata. Note: do not change * output region or load region in those user sections! */ - . = ALIGN(${ARCH_WIDTH}); + . = ALIGN({{ cfg.arch_width }}); __edata = .; /* LMA of .data */ __sidata = LOADADDR(.data); - .bss (NOLOAD) : ALIGN(${ARCH_WIDTH}) + .bss (NOLOAD) : ALIGN({{ cfg.arch_width }}) { - . = ALIGN(${ARCH_WIDTH}); + . = ALIGN({{ cfg.arch_width }}); __sbss = .; *(.sbss .sbss.* .bss .bss.*); @@ -147,7 +199,7 @@ SECTIONS /* Allow sections from user `memory.x` injected using `INSERT AFTER .bss` to * use the .bss zeroing mechanism by pushing __ebss. Note: do not change * output region or load region in those user sections! */ - . = ALIGN(${ARCH_WIDTH}); + . = ALIGN({{ cfg.arch_width }}); __ebss = .; /* fictitious region that represents the memory available for the heap */ @@ -184,8 +236,8 @@ ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned"); ASSERT(ORIGIN(REGION_RODATA) % 4 == 0, " ERROR(riscv-rt): the start of the REGION_RODATA must be 4-byte aligned"); -ASSERT(ORIGIN(REGION_DATA) % ${ARCH_WIDTH} == 0, " -ERROR(riscv-rt): the start of the REGION_DATA must be ${ARCH_WIDTH}-byte aligned"); +ASSERT(ORIGIN(REGION_DATA) % {{ cfg.arch_width }} == 0, " +ERROR(riscv-rt): the start of the REGION_DATA must be {{ cfg.arch_width }}-byte aligned"); ASSERT(ORIGIN(REGION_HEAP) % 4 == 0, " ERROR(riscv-rt): the start of the REGION_HEAP must be 4-byte aligned"); @@ -196,14 +248,14 @@ ERROR(riscv-rt): the start of the REGION_STACK must be 4-byte aligned"); ASSERT(_stext % 4 == 0, " ERROR(riscv-rt): `_stext` must be 4-byte aligned"); -ASSERT(__sdata % ${ARCH_WIDTH} == 0 && __edata % ${ARCH_WIDTH} == 0, " -BUG(riscv-rt): .data is not ${ARCH_WIDTH}-byte aligned"); +ASSERT(__sdata % {{ cfg.arch_width }} == 0 && __edata % {{ cfg.arch_width }} == 0, " +BUG(riscv-rt): .data is not {{ cfg.arch_width }}-byte aligned"); -ASSERT(__sidata % ${ARCH_WIDTH} == 0, " -BUG(riscv-rt): the LMA of .data is not ${ARCH_WIDTH}-byte aligned"); +ASSERT(__sidata % {{ cfg.arch_width }} == 0, " +BUG(riscv-rt): the LMA of .data is not {{ cfg.arch_width }}-byte aligned"); -ASSERT(__sbss % ${ARCH_WIDTH} == 0 && __ebss % ${ARCH_WIDTH} == 0, " -BUG(riscv-rt): .bss is not ${ARCH_WIDTH}-byte aligned"); +ASSERT(__sbss % {{ cfg.arch_width }} == 0 && __ebss % {{ cfg.arch_width }} == 0, " +BUG(riscv-rt): .bss is not {{ cfg.arch_width }}-byte aligned"); ASSERT(__sheap % 4 == 0, " BUG(riscv-rt): start of .heap is not 4-byte aligned"); diff --git a/tests-build/Cargo.toml b/tests-build/Cargo.toml index 4798fa37..30ba7b57 100644 --- a/tests-build/Cargo.toml +++ b/tests-build/Cargo.toml @@ -8,6 +8,9 @@ panic-halt = "1.0" riscv = { path = "../riscv" } riscv-rt = { path = "../riscv-rt" } +[build-dependencies] +minilink = "0.2" + [features] pre-init = ["riscv-rt/pre-init"] single-hart = ["riscv-rt/single-hart"] diff --git a/tests-build/build.rs b/tests-build/build.rs index 0a4ea37e..64bb9c0c 100644 --- a/tests-build/build.rs +++ b/tests-build/build.rs @@ -1,22 +1,6 @@ use std::{env, fs::File, io::Write, path::PathBuf}; fn main() { - // Put device.x somewhere the linker can find it - let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - File::create(out.join("device.x")) - .unwrap() - .write_all(include_bytes!("device.x")) - .unwrap(); - println!("cargo:rustc-link-search={}", out.display()); - println!("cargo:rerun-if-changed=device.x"); - - // Put memory.x somewhere the linker can find it - File::create(out.join("memory.x")) - .unwrap() - .write_all(include_bytes!("memory.x")) - .unwrap(); - println!("cargo:rustc-link-search={}", out.display()); - println!("cargo:rerun-if-changed=memory.x"); - - println!("cargo:rerun-if-changed=build.rs"); + minilink::register_template("device.x", "device.x"); + minilink::register_template("memory.x", "memory.x"); }