diff --git a/firmware/rust1-bootloader/src/main.rs b/firmware/rust1-bootloader/src/main.rs index 4fb4e7d07dc91fdb3d2b8af6107ded9fded7358f..73e995e50e85882ec5e72dbc8348ecd477cb934a 100644 --- a/firmware/rust1-bootloader/src/main.rs +++ b/firmware/rust1-bootloader/src/main.rs @@ -44,6 +44,12 @@ fn main() -> ! { } } + unsafe { + // dirty trick to keep PROGRAM_INFO despite `--gc-sections` because we cannot add + // KEEP(...) in that part of the predefined linker script + let _ = core::ptr::read_volatile(PROGRAM_INFO.as_ptr()); + } + let mut led_b = Output::new(p.PIN_4, Level::Low); let _led_g = Output::new(p.PIN_5, Level::Low); let mut led_r = Output::new(p.PIN_6, Level::Low); @@ -91,3 +97,27 @@ unsafe fn DefaultHandler(_: i16) -> ! { fn panic(_info: &core::panic::PanicInfo) -> ! { cortex_m::asm::udf(); } + + +// Program information. +// This must be in the first 256 bytes after BOOT2 (including the end marker). The first thing in +// the predefined linker script is .Reset so let's add it to that. +const BINARY_INFO_MARKER_START: u32 = 0x7188ebf2; +const BINARY_INFO_MARKER_END: u32 = 0xe71aa390; +#[used] +#[link_section = ".Reset"] +pub static PROGRAM_INFO: [u32; 5] = { + extern "C" { + static __bootloader_active_program_info_start: u32; + static __bootloader_active_program_info_end: u32; + } + [ + BINARY_INFO_MARKER_START, + //&__bootloader_active_program_info_start as *const u32 as u32, + //&__bootloader_active_program_info_end as *const u32 as u32, + 0x10086000, + 0x10087000, + 0x10087000 - 128, // copy table can have up to 10 entries of 3*u32 each + BINARY_INFO_MARKER_END + ] +}; diff --git a/firmware/rust1/build.rs b/firmware/rust1/build.rs index 52730bc0d2e83371ac5472879a23132a77e7b29b..a6eaa3e0f68d41c3c274d6062fc3ab3e9af9ac1f 100644 --- a/firmware/rust1/build.rs +++ b/firmware/rust1/build.rs @@ -25,6 +25,10 @@ fn main() { .unwrap() .write_all(include_bytes!("clear-bootloader-state.x")) .unwrap(); + File::create(out.join("program-info.x")) + .unwrap() + .write_all(include_bytes!("program-info.x")) + .unwrap(); println!("cargo:rustc-link-search={}", out.display()); // By default, Cargo will re-run a build script whenever @@ -33,10 +37,12 @@ fn main() { // `memory.x` is changed. println!("cargo:rerun-if-changed=memory.x"); println!("cargo:rerun-if-changed=clear-bootloader-state.x"); + println!("cargo:rerun-if-changed=program-info.x"); println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tlink.x"); //println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); // This is for boot2 but the bootloader handles that for us. println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); println!("cargo:rustc-link-arg-bins=-Tclear-bootloader-state.x"); + println!("cargo:rustc-link-arg-bins=-Tprogram-info.x"); } diff --git a/firmware/rust1/build.sh b/firmware/rust1/build.sh index 86f5cded96f2e7289c2e940cbe342e8e0258ac1f..294af8bd3a682f4fbf9d2203ae30301e69b9c025 100755 --- a/firmware/rust1/build.sh +++ b/firmware/rust1/build.sh @@ -15,7 +15,7 @@ if ! [ -e .cargo-tools/bin/probe-rs-debugger ] ; then cargo install --root "$PWD/.cargo-tools/" probe-rs-debugger fi -( cd ../rust1-bootloader && cargo build --release ) +( cd ../rust1-bootloader && cargo build --release && cargo objdump --release -- -xd >target/thumbv6m-none-eabi/release/heizung-bootloader.map ) ./.cargo-tools/bin/elf2uf2-rs ../rust1-bootloader/target/thumbv6m-none-eabi/release/heizung-bootloader cargo build --bin heizung diff --git a/firmware/rust1/memory.x b/firmware/rust1/memory.x index 5e049f023dfbf91dec5785f12439f19d70cb0d28..86cda41f4fea2986bbe646cec5493cd3c9f2bb69 100644 --- a/firmware/rust1/memory.x +++ b/firmware/rust1/memory.x @@ -3,7 +3,8 @@ MEMORY /* NOTE 1 K = 1 KiBi = 1024 bytes */ BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 BOOTLOADER_STATE : ORIGIN = 0x10006000, LENGTH = 4K - FLASH : ORIGIN = 0x10007000, LENGTH = 512K + FLASH : ORIGIN = 0x10007000, LENGTH = 512K - 4K + PROGRAM_INFO : ORIGIN = 0x10086000, LENGTH = 4K DFU : ORIGIN = 0x10087000, LENGTH = 516K RAM : ORIGIN = 0x20000000, LENGTH = 256K } diff --git a/firmware/rust1/src/bin/heizung.rs b/firmware/rust1/src/bin/heizung.rs index 757e1883449990c49da7060bde7c888c155198d3..cd9d7306e060f85335fdbf78a3842b879d1d7c6c 100644 --- a/firmware/rust1/src/bin/heizung.rs +++ b/firmware/rust1/src/bin/heizung.rs @@ -694,3 +694,129 @@ fn init_early() { // release spinlock 31 because we sometimes block on this in the init code unsafe { pac::SIO.spinlock(31).write_value(1); } } + + + +// Add program info. +#[used] +#[link_section = ".program_info"] +pub static PROGRAM_INFO: [u8; 4096] = make_program_info(); + +struct ProgramInfoBuilder { + addr: u32, + buf: [u8; 4096], + info_pos: usize, + data_pos: usize, +} + +impl ProgramInfoBuilder { + const fn new(addr: u32, copy_table_size: usize) -> Self { + ProgramInfoBuilder { + addr, + buf: { + let mut x = [0xff; 4096]; + // mark end of copy table with zero in first entry + x[4096 - copy_table_size + 0] = 0; + x[4096 - copy_table_size + 1] = 0; + x[4096 - copy_table_size + 2] = 0; + x[4096 - copy_table_size + 3] = 0; + x + }, + info_pos: 0, + data_pos: 4096 - copy_table_size, + } + } + + const fn build(self: Self) -> [u8; 4096] { + self.buf + } + + const fn push(mut self: Self, value: u8) -> Self { + self.buf[self.info_pos] = value; + self.info_pos += 1; + if self.info_pos > self.data_pos { + core::panic!("ProgramInfoBuilder: too much data") + } + self + } + + const fn push_u16(self: Self, value: u16) -> Self { + self + .push((value & 0xff) as u8) + .push(((value >> 8) & 0xff) as u8) + } + + const fn push_u32(self: Self, value: u32) -> Self { + self + .push((value & 0xff) as u8) + .push(((value >> 8) & 0xff) as u8) + .push(((value >> 16) & 0xff) as u8) + .push(((value >> 24) & 0xff) as u8) + } + + const fn push_raspberry_tag(self: Self) -> Self { + self.push('R' as u8) + .push('P' as u8) + } + + const fn push_extra_data(mut self: Self, value: &[u8]) -> (Self, u32) { + if self.data_pos - self.info_pos < value.len() { + core::panic!("ProgramInfoBuilder: too much data") + } + let offset = self.data_pos - value.len(); + //self.buf[offset..self.data_pos].copy_from_slice(value); + //for i in 0..value.len() { + // self.buf[offset+i] = value[i]; + //} + if value.len() > 0 { self.buf[offset+0] = value[0]; } + if value.len() > 1 { self.buf[offset+1] = value[1]; } + if value.len() > 2 { self.buf[offset+2] = value[2]; } + if value.len() > 3 { self.buf[offset+3] = value[3]; } + if value.len() > 4 { self.buf[offset+4] = value[4]; } + if value.len() > 5 { self.buf[offset+5] = value[5]; } + if value.len() > 6 { self.buf[offset+6] = value[6]; } + if value.len() > 7 { self.buf[offset+7] = value[7]; } + if value.len() > 8 { core::panic!("too long") } + + self.data_pos -= value.len(); + let addr = self.addr + offset as u32; + (self, addr) + } + + const fn push_string(mut self: Self, value: &str) -> Self { + if self.data_pos - self.info_pos < value.len() { + core::panic!("ProgramInfoBuilder: too much data") + } + (self, _) = self.push_extra_data(&[0]); + let (self2, addr) = self.push_extra_data(value.as_bytes()); + self2.push_u32(addr) + } + + const BINARY_INFO_TYPE_ID_AND_INT: u16 = 5; + const BINARY_INFO_TYPE_ID_AND_STRING: u16 = 6; + const BINARY_INFO_ID_RP_PROGRAM_NAME: u32 = 0x02031c86; + const BINARY_INFO_ID_RP_BINARY_END: u32 = 0x68f465de; + + const fn push_program_name(self: Self, value: &str) -> Self { + self.push_raspberry_tag() + .push_u16(Self::BINARY_INFO_TYPE_ID_AND_STRING) + .push_u32(Self::BINARY_INFO_ID_RP_PROGRAM_NAME) + .push_string(value) + } + + const fn push_binary_end(self: Self, value: &str) -> Self { + self.push_raspberry_tag() + .push_u16(Self::BINARY_INFO_TYPE_ID_AND_INT) + .push_u32(Self::BINARY_INFO_ID_RP_BINARY_END) + .push_string(value) + } +} + +const fn make_program_info() -> [u8; 4096] { + const PROGRAM_INFO_START: u32 = 0x10086000; + const COPY_TABLE_SIZE: usize = 128; // must match bootloader + + ProgramInfoBuilder::new(PROGRAM_INFO_START, COPY_TABLE_SIZE) + .push_program_name("Test 123") + .build() +}