From 7f4b0075d39284f9a85225954697be9268db5e38 Mon Sep 17 00:00:00 2001 From: Shane Snover Date: Wed, 20 May 2026 22:09:48 -0600 Subject: [PATCH 1/4] Add linker files for AB firmware and implement into app build --- coproc-embassy/link/app-a.x | 6 + coproc-embassy/link/app-b.x | 5 + coproc-embassy/link/app-layout.x | 290 +++++++++++++++++++++++++++ coproc-embassy/link/bootloader.x | 296 ++++++++++++++++++++++++++++ coproc-embassy/{ => link}/memory.x | 7 +- coproc-embassy/pi-pico-rgb/build.rs | 44 +++-- 6 files changed, 630 insertions(+), 18 deletions(-) create mode 100644 coproc-embassy/link/app-a.x create mode 100644 coproc-embassy/link/app-b.x create mode 100644 coproc-embassy/link/app-layout.x create mode 100644 coproc-embassy/link/bootloader.x rename coproc-embassy/{ => link}/memory.x (71%) diff --git a/coproc-embassy/link/app-a.x b/coproc-embassy/link/app-a.x new file mode 100644 index 0000000..92787a7 --- /dev/null +++ b/coproc-embassy/link/app-a.x @@ -0,0 +1,6 @@ +INCLUDE memory.x + +__app_flash_origin = 0x10008000; +__app_flash_length = 992K; + +INCLUDE app-layout.x \ No newline at end of file diff --git a/coproc-embassy/link/app-b.x b/coproc-embassy/link/app-b.x new file mode 100644 index 0000000..93743f5 --- /dev/null +++ b/coproc-embassy/link/app-b.x @@ -0,0 +1,5 @@ +INCLUDE memory.x + +__app_flash_origin = 0x10100000; + +INCLUDE app-layout.x \ No newline at end of file diff --git a/coproc-embassy/link/app-layout.x b/coproc-embassy/link/app-layout.x new file mode 100644 index 0000000..3d260f3 --- /dev/null +++ b/coproc-embassy/link/app-layout.x @@ -0,0 +1,290 @@ +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut __sbss }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all 4-byte aligned. These alignments are assumed by the RAM initialization + routine. There's also a second benefit: 4-byte aligned boundaries means that you won't see + "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +/* # Entry point = reset vector */ +EXTERN(__RESET_VECTOR); +EXTERN(Reset); +ENTRY(Reset); + +/* # Exception vectors */ +/* This is effectively weak aliasing at the linker level */ +/* The user can override any of these aliases by defining the corresponding symbol themselves (cf. + the `exception!` macro) */ +EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */ + +EXTERN(DefaultHandler); + +PROVIDE(NonMaskableInt = DefaultHandler); +EXTERN(HardFaultTrampoline); +PROVIDE(MemoryManagement = DefaultHandler); +PROVIDE(BusFault = DefaultHandler); +PROVIDE(UsageFault = DefaultHandler); +PROVIDE(SecureFault = DefaultHandler); +PROVIDE(SVCall = DefaultHandler); +PROVIDE(DebugMonitor = DefaultHandler); +PROVIDE(PendSV = DefaultHandler); +PROVIDE(SysTick = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultHandler_); +PROVIDE(HardFault = HardFault_); + +/* # Interrupt vectors */ +EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */ + +/* # Pre-initialization function */ +/* If the user overrides this using the `pre_init!` macro or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = DefaultPreInit); + +SECTIONS { + PROVIDE(_ram_start = ORIGIN(RAM)); + PROVIDE(_ram_end = ORIGIN(RAM) + LENGTH(RAM)); + PROVIDE(_stack_start = _ram_end); + + /* Position the start of the application */ + . = __app_flash_origin; + + /* ## Sections in FLASH_APP */ + .image_header : { + KEEP(*(.image_header)) + } > FLASH_APP + + /* ### Vector table */ + .vector_table ALIGN(256) : + { + __vector_table = .; + + /* Initial Stack Pointer (SP) value. + * We mask the bottom three bits to force 8-byte alignment. + * Despite having an assert for this later, it's possible that a separate + * linker script could override _stack_start after the assert is checked. + */ + LONG(_stack_start & 0xFFFFFFF8); + + /* Reset vector */ + KEEP(*(.vector_table.reset_vector)); /* this is the `__RESET_VECTOR` symbol */ + + /* Exceptions */ + __exceptions = .; /* start of exceptions */ + KEEP(*(.vector_table.exceptions)); /* this is the `__EXCEPTIONS` symbol */ + __eexceptions = .; /* end of exceptions */ + + /* Device specific interrupts */ + KEEP(*(.vector_table.interrupts)); /* this is the `__INTERRUPTS` symbol */ + } > FLASH_APP + + PROVIDE(_stext = ADDR(.vector_table) + SIZEOF(.vector_table)); + + /* ### .text */ + .text _stext : + { + __stext = .; + *(.Reset); + + *(.text .text.*); + + /* The HardFaultTrampoline uses the `b` instruction to enter `HardFault`, + so must be placed close to it. */ + *(.HardFaultTrampoline); + *(.HardFault.*); + + . = ALIGN(4); /* Pad .text to the alignment to workaround overlapping load section bug in old lld */ + __etext = .; + } > FLASH_APP + + /* ### .rodata */ + .rodata : ALIGN(4) + { + . = ALIGN(4); + __srodata = .; + *(.rodata .rodata.*); + + /* 4-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(4); + __erodata = .; + } > FLASH_APP + + /* ## Sections in RAM */ + /* ### .data */ + .data : ALIGN(4) + { + . = ALIGN(4); + __sdata = .; + *(.data .data.*); + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM AT>FLASH_APP + /* 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(4); + __edata = .; + + /* LMA of .data */ + __sidata = LOADADDR(.data); + + /* ### .gnu.sgstubs + This section contains the TrustZone-M veneers put there by the Arm GNU linker. */ + /* Security Attribution Unit blocks must be 32 bytes aligned. */ + /* Note that this pads the FLASH_APP usage to 32 byte alignment. */ + .gnu.sgstubs : ALIGN(32) + { + . = ALIGN(32); + __veneer_base = .; + *(.gnu.sgstubs*) + . = ALIGN(32); + } > FLASH_APP + /* Place `__veneer_limit` outside the `.gnu.sgstubs` section because veneers are + * always inserted last in the section, which would otherwise be _after_ the `__veneer_limit` symbol. + */ + . = ALIGN(32); + __veneer_limit = .; + + /* ### .bss */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __sbss = .; + *(.bss .bss.*); + *(COMMON); /* Uninitialized C statics */ + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM + /* 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(4); + __ebss = .; + + /* ### .uninit */ + .uninit (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __suninit = .; + *(.uninit .uninit.*); + . = ALIGN(4); + __euninit = .; + } > RAM + + /* Place the heap right after `.uninit` in RAM */ + PROVIDE(__sheap = __euninit); + + /* Place stack end at the end of allocated RAM */ + PROVIDE(_stack_end = __euninit); + + /* ## .got */ + /* Dynamic relocations are unsupported. This section is only used to detect relocatable code in + the input files and raise an error if relocatable code is found */ + .got (NOLOAD) : + { + KEEP(*(.got .got.*)); + } + + /* ## Discarded sections */ + /DISCARD/ : + { + /* Unused exception related info that only wastes space */ + *(.ARM.exidx); + *(.ARM.exidx.*); + *(.ARM.extab.*); + } +} + +/* Do not exceed this mark in the error messages below | */ +/* # Alignment checks */ +ASSERT(__app_flash_origin % 4 == 0, " +ERROR: the start of the app flash region must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 4 == 0, " +ERROR: the start of the RAM region must be 4-byte aligned"); + +ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, " +BUG: .data is not 4-byte aligned"); + +ASSERT(__sidata % 4 == 0, " +BUG: the LMA of .data is not 4-byte aligned"); + +ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, " +BUG: .bss is not 4-byte aligned"); + +ASSERT(__sheap % 4 == 0, " +BUG: start of .heap is not 4-byte aligned"); + +ASSERT(_stack_start % 8 == 0, " +ERROR: stack start address is not 8-byte aligned. +If you have set _stack_start, check it's set to an address which is a multiple of 8 bytes. +If you haven't, stack starts at the end of RAM by default. Check that both RAM +origin and length are set to multiples of 8 in the `memory.x` file."); + +ASSERT(_stack_end % 4 == 0, " +ERROR: end of stack is not 4-byte aligned"); + +ASSERT(_stack_start >= _stack_end, " +ERROR: stack end address is not below stack start."); + +/* # Position checks */ + +/* ## .vector_table + * + * If the *start* of exception vectors is not 8 bytes past the start of the + * vector table, then we somehow did not place the reset vector, which should + * live 4 bytes past the start of the vector table. + */ +ASSERT(__exceptions == ADDR(.vector_table) + 0x8, " +BUG: the reset vector is missing"); + +ASSERT(__eexceptions == ADDR(.vector_table) + 0x40, " +BUG: the exception vectors are missing"); + +ASSERT(SIZEOF(.vector_table) > 0x40, " +ERROR: The interrupt vectors are missing. +Possible solutions, from most likely to less likely: +- Link to a svd2rust generated device crate +- Check that you actually use the device/hal/bsp crate in your code +- Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency +may be enabling it) +- Supply the interrupt handlers yourself. Check the documentation for details."); + +/* ## .text */ +ASSERT(ADDR(.vector_table) + SIZEOF(.vector_table) <= _stext, " +ERROR: The .text section can't be placed inside the .vector_table section +Set _stext to an address greater than the end of .vector_table (See output of `nm`)"); + +ASSERT(_stext > __app_flash_origin && _stext < __app_flash_origin + __app_flash_length, " +ERROR: The .text section must be placed inside the application memory. +Set _stext to an address within the application region."); + +/* # Other checks */ +ASSERT(SIZEOF(.got) == 0, " +ERROR: .got section detected in the input object files +Dynamic relocations are not supported. If you are linking to C code compiled using +the 'cc' crate then modify your build script to compile the C code _without_ +the -fPIC flag. See the documentation of the `cc::Build.pic` method for details."); +/* Do not exceed this mark in the error messages above | */ + +/* Provides weak aliases (cf. PROVIDED) for device specific interrupt handlers */ +/* This will usually be provided by a device crate generated using svd2rust (see `device.x`) */ +INCLUDE device.x + +ASSERT(SIZEOF(.vector_table) <= 0xc0, " +There can't be more than 32 interrupt handlers. This may be a bug in +your device crate, or you may have registered more than 32 interrupt +handlers."); + diff --git a/coproc-embassy/link/bootloader.x b/coproc-embassy/link/bootloader.x new file mode 100644 index 0000000..cd05ee3 --- /dev/null +++ b/coproc-embassy/link/bootloader.x @@ -0,0 +1,296 @@ +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut __sbss }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all 4-byte aligned. These alignments are assumed by the RAM initialization + routine. There's also a second benefit: 4-byte aligned boundaries means that you won't see + "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +/* Provides information about the memory layout of the device */ +/* This will be provided by the user (see `memory.x`) or by a Board Support Crate */ +INCLUDE memory.x + +/* # Entry point = reset vector */ +EXTERN(__RESET_VECTOR); +EXTERN(Reset); +ENTRY(Reset); + +/* # Exception vectors */ +/* This is effectively weak aliasing at the linker level */ +/* The user can override any of these aliases by defining the corresponding symbol themselves (cf. + the `exception!` macro) */ +EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */ + +EXTERN(DefaultHandler); + +PROVIDE(NonMaskableInt = DefaultHandler); +EXTERN(HardFaultTrampoline); +PROVIDE(MemoryManagement = DefaultHandler); +PROVIDE(BusFault = DefaultHandler); +PROVIDE(UsageFault = DefaultHandler); +PROVIDE(SecureFault = DefaultHandler); +PROVIDE(SVCall = DefaultHandler); +PROVIDE(DebugMonitor = DefaultHandler); +PROVIDE(PendSV = DefaultHandler); +PROVIDE(SysTick = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultHandler_); +PROVIDE(HardFault = HardFault_); + +/* # Interrupt vectors */ +EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */ + +/* # Pre-initialization function */ +/* If the user overrides this using the `pre_init!` macro or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = DefaultPreInit); + +SECTIONS { + PROVIDE(_ram_start = ORIGIN(RAM)); + PROVIDE(_ram_end = ORIGIN(RAM) + LENGTH(RAM)); + PROVIDE(_stack_start = _ram_end); + + /* ## Sections in FLASH_BOOT */ + .boot2 : { + KEEP(*(.boot2)) + } > FLASH_BOOT + + /* ### Vector table */ + .vector_table ALIGN(256) : + { + __vector_table = .; + + /* Initial Stack Pointer (SP) value. + * We mask the bottom three bits to force 8-byte alignment. + * Despite having an assert for this later, it's possible that a separate + * linker script could override _stack_start after the assert is checked. + */ + LONG(_stack_start & 0xFFFFFFF8); + + /* Reset vector */ + KEEP(*(.vector_table.reset_vector)); /* this is the `__RESET_VECTOR` symbol */ + + /* Exceptions */ + __exceptions = .; /* start of exceptions */ + KEEP(*(.vector_table.exceptions)); /* this is the `__EXCEPTIONS` symbol */ + __eexceptions = .; /* end of exceptions */ + + /* Device specific interrupts */ + KEEP(*(.vector_table.interrupts)); /* this is the `__INTERRUPTS` symbol */ + } > FLASH_BOOT + + PROVIDE(_stext = ADDR(.vector_table) + SIZEOF(.vector_table)); + + /* ### .text */ + .text _stext : + { + __stext = .; + *(.Reset); + + *(.text .text.*); + + /* The HardFaultTrampoline uses the `b` instruction to enter `HardFault`, + so must be placed close to it. */ + *(.HardFaultTrampoline); + *(.HardFault.*); + + . = ALIGN(4); /* Pad .text to the alignment to workaround overlapping load section bug in old lld */ + __etext = .; + } > FLASH_BOOT + + /* ### .rodata */ + .rodata : ALIGN(4) + { + . = ALIGN(4); + __srodata = .; + *(.rodata .rodata.*); + + /* 4-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(4); + __erodata = .; + } > FLASH_BOOT + + /* ## Sections in RAM */ + /* ### .data */ + .data : ALIGN(4) + { + . = ALIGN(4); + __sdata = .; + *(.data .data.*); + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM AT>FLASH_BOOT + /* 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(4); + __edata = .; + + /* LMA of .data */ + __sidata = LOADADDR(.data); + + /* ### .gnu.sgstubs + This section contains the TrustZone-M veneers put there by the Arm GNU linker. */ + /* Security Attribution Unit blocks must be 32 bytes aligned. */ + /* Note that this pads the FLASH_BOOT usage to 32 byte alignment. */ + .gnu.sgstubs : ALIGN(32) + { + . = ALIGN(32); + __veneer_base = .; + *(.gnu.sgstubs*) + . = ALIGN(32); + } > FLASH_BOOT + /* Place `__veneer_limit` outside the `.gnu.sgstubs` section because veneers are + * always inserted last in the section, which would otherwise be _after_ the `__veneer_limit` symbol. + */ + . = ALIGN(32); + __veneer_limit = .; + + /* ### .bss */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __sbss = .; + *(.bss .bss.*); + *(COMMON); /* Uninitialized C statics */ + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM + /* 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(4); + __ebss = .; + + /* ### .uninit */ + .uninit (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __suninit = .; + *(.uninit .uninit.*); + . = ALIGN(4); + __euninit = .; + } > RAM + + /* Place the heap right after `.uninit` in RAM */ + PROVIDE(__sheap = __euninit); + + /* Place stack end at the end of allocated RAM */ + PROVIDE(_stack_end = __euninit); + + /* ## .got */ + /* Dynamic relocations are unsupported. This section is only used to detect relocatable code in + the input files and raise an error if relocatable code is found */ + .got (NOLOAD) : + { + KEEP(*(.got .got.*)); + } + + /* ## Discarded sections */ + /DISCARD/ : + { + /* Unused exception related info that only wastes space */ + *(.ARM.exidx); + *(.ARM.exidx.*); + *(.ARM.extab.*); + } + + /* NVS region to allow the bootloader to write to the tables */ + .nvs (NOLOAD) : { + KEEP(*(.nvs)) + } > NVS +} + +/* Do not exceed this mark in the error messages below | */ +/* # Alignment checks */ +ASSERT(ORIGIN(FLASH_BOOT) % 4 == 0, " +ERROR: the start of the FLASH_BOOT region must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 4 == 0, " +ERROR: the start of the RAM region must be 4-byte aligned"); + +ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, " +BUG: .data is not 4-byte aligned"); + +ASSERT(__sidata % 4 == 0, " +BUG: the LMA of .data is not 4-byte aligned"); + +ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, " +BUG: .bss is not 4-byte aligned"); + +ASSERT(__sheap % 4 == 0, " +BUG: start of .heap is not 4-byte aligned"); + +ASSERT(_stack_start % 8 == 0, " +ERROR: stack start address is not 8-byte aligned. +If you have set _stack_start, check it's set to an address which is a multiple of 8 bytes. +If you haven't, stack starts at the end of RAM by default. Check that both RAM +origin and length are set to multiples of 8 in the `memory.x` file."); + +ASSERT(_stack_end % 4 == 0, " +ERROR: end of stack is not 4-byte aligned"); + +ASSERT(_stack_start >= _stack_end, " +ERROR: stack end address is not below stack start."); + +/* # Position checks */ + +/* ## .vector_table + * + * If the *start* of exception vectors is not 8 bytes past the start of the + * vector table, then we somehow did not place the reset vector, which should + * live 4 bytes past the start of the vector table. + */ +ASSERT(__exceptions == ADDR(.vector_table) + 0x8, " +BUG: the reset vector is missing"); + +ASSERT(__eexceptions == ADDR(.vector_table) + 0x40, " +BUG: the exception vectors are missing"); + +ASSERT(SIZEOF(.vector_table) > 0x40, " +ERROR: The interrupt vectors are missing. +Possible solutions, from most likely to less likely: +- Link to a svd2rust generated device crate +- Check that you actually use the device/hal/bsp crate in your code +- Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency +may be enabling it) +- Supply the interrupt handlers yourself. Check the documentation for details."); + +/* ## .text */ +ASSERT(ADDR(.vector_table) + SIZEOF(.vector_table) <= _stext, " +ERROR: The .text section can't be placed inside the .vector_table section +Set _stext to an address greater than the end of .vector_table (See output of `nm`)"); + +ASSERT(_stext > ORIGIN(FLASH_BOOT) && _stext < ORIGIN(FLASH_BOOT) + LENGTH(FLASH_BOOT), " +ERROR: The .text section must be placed inside the FLASH_BOOT memory. +Set _stext to an address within the FLASH_BOOT region."); + +/* # Other checks */ +ASSERT(SIZEOF(.got) == 0, " +ERROR: .got section detected in the input object files +Dynamic relocations are not supported. If you are linking to C code compiled using +the 'cc' crate then modify your build script to compile the C code _without_ +the -fPIC flag. See the documentation of the `cc::Build.pic` method for details."); +/* Do not exceed this mark in the error messages above | */ + +/* Provides weak aliases (cf. PROVIDED) for device specific interrupt handlers */ +/* This will usually be provided by a device crate generated using svd2rust (see `device.x`) */ +INCLUDE device.x + +ASSERT(SIZEOF(.vector_table) <= 0xc0, " +There can't be more than 32 interrupt handlers. This may be a bug in +your device crate, or you may have registered more than 32 interrupt +handlers."); + diff --git a/coproc-embassy/memory.x b/coproc-embassy/link/memory.x similarity index 71% rename from coproc-embassy/memory.x rename to coproc-embassy/link/memory.x index 771b8fa..166883d 100644 --- a/coproc-embassy/memory.x +++ b/coproc-embassy/link/memory.x @@ -1,6 +1,9 @@ MEMORY { - BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 - FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + ROM (rx) : ORIGIN = 0x00000000, LENGTH = 16K + + FLASH_BOOT (rx) : ORIGIN = 0x10000000, LENGTH = 32K + FLASH_APP (rx) : ORIGIN = 0x10008000, LENGTH = 992K + 992K + NVS (rw) : ORIGIN = 0x101f8000, LENGTH = 32K /* Pick one of the two options for RAM layout */ diff --git a/coproc-embassy/pi-pico-rgb/build.rs b/coproc-embassy/pi-pico-rgb/build.rs index a6f2f09..298aa4b 100644 --- a/coproc-embassy/pi-pico-rgb/build.rs +++ b/coproc-embassy/pi-pico-rgb/build.rs @@ -1,26 +1,38 @@ use std::env; -use std::fs::File; -use std::io::Write; use std::path::PathBuf; fn main() { - // Put `memory.x` in our output directory and ensure it's - // on the linker search path. let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - File::create(out.join("memory.x")) - .unwrap() - .write_all(include_bytes!("../memory.x")) - .unwrap(); - //println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rustc-link-search={}", out.display()); - // By default, Cargo will re-run a build script whenever - // any file in the project changes. By specifying `memory.x` - // here, we ensure the build script is only re-run when - // `memory.x` is changed. - //println!("cargo:rerun-if-changed=memory.x"); + copy_linker_files_to_output_directory().unwrap(); println!("cargo:rustc-link-arg-bins=--nmagic"); - println!("cargo:rustc-link-arg-bins=-Tlink.x"); - println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-bins=-Tapp-a.x"); println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); } + +fn copy_linker_files_to_output_directory() -> std::io::Result<()> { + let linker_file_dir = PathBuf::from(format!( + "{}/../link", + env::var_os("CARGO_MANIFEST_DIR") + .unwrap() + .into_string() + .unwrap() + )); + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + + for entry in linker_file_dir.read_dir()? { + let Ok(entry) = entry else { + continue; + }; + if entry.path().is_file() { + std::fs::copy( + entry.path(), + format!("{}/{}", out_dir.display(), entry.file_name().display()), + )?; + } + } + + Ok(()) +} From dd43cab39a286de78bb2da420841414489615322 Mon Sep 17 00:00:00 2001 From: Shane Snover Date: Thu, 21 May 2026 11:42:11 -0600 Subject: [PATCH 2/4] Move pi-pico-rgb to app --- coproc-embassy/Cargo.lock | 26 +++++++++---------- coproc-embassy/Cargo.toml | 2 +- .../{pi-pico-rgb => app}/Cargo.toml | 2 +- coproc-embassy/{pi-pico-rgb => app}/build.rs | 0 .../{pi-pico-rgb => app}/src/main.rs | 0 5 files changed, 15 insertions(+), 15 deletions(-) rename coproc-embassy/{pi-pico-rgb => app}/Cargo.toml (96%) rename coproc-embassy/{pi-pico-rgb => app}/build.rs (100%) rename coproc-embassy/{pi-pico-rgb => app}/src/main.rs (100%) diff --git a/coproc-embassy/Cargo.lock b/coproc-embassy/Cargo.lock index 70ba675..1b5a8d5 100644 --- a/coproc-embassy/Cargo.lock +++ b/coproc-embassy/Cargo.lock @@ -877,39 +877,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "megabit-coproc-common" +name = "megabit-coproc-app" version = "0.1.0" dependencies = [ - "cobs", "cortex-m", + "cortex-m-rt", + "defmt 0.3.100", + "defmt-rtt", "embassy-executor", "embassy-futures", + "embassy-rp", "embassy-sync 0.5.0", "embassy-time", "embassy-usb", - "embassy-usb-driver", - "embedded-hal 1.0.0", - "embedded-hal-async", + "megabit-coproc-common", + "panic-probe", + "portable-atomic", "static_cell", ] [[package]] -name = "megabit-coproc-pi-pico-rgb" +name = "megabit-coproc-common" version = "0.1.0" dependencies = [ + "cobs", "cortex-m", - "cortex-m-rt", - "defmt 0.3.100", - "defmt-rtt", "embassy-executor", "embassy-futures", - "embassy-rp", "embassy-sync 0.5.0", "embassy-time", "embassy-usb", - "megabit-coproc-common", - "panic-probe", - "portable-atomic", + "embassy-usb-driver", + "embedded-hal 1.0.0", + "embedded-hal-async", "static_cell", ] diff --git a/coproc-embassy/Cargo.toml b/coproc-embassy/Cargo.toml index 267e5de..d5e67a2 100644 --- a/coproc-embassy/Cargo.toml +++ b/coproc-embassy/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["coproc-common", "pi-pico-rgb"] +members = ["coproc-common", "app"] [workspace.package] edition = "2024" diff --git a/coproc-embassy/pi-pico-rgb/Cargo.toml b/coproc-embassy/app/Cargo.toml similarity index 96% rename from coproc-embassy/pi-pico-rgb/Cargo.toml rename to coproc-embassy/app/Cargo.toml index 72be54d..0451f8e 100644 --- a/coproc-embassy/pi-pico-rgb/Cargo.toml +++ b/coproc-embassy/app/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "megabit-coproc-pi-pico-rgb" +name = "megabit-coproc-app" version = "0.1.0" license.workspace = true edition.workspace = true diff --git a/coproc-embassy/pi-pico-rgb/build.rs b/coproc-embassy/app/build.rs similarity index 100% rename from coproc-embassy/pi-pico-rgb/build.rs rename to coproc-embassy/app/build.rs diff --git a/coproc-embassy/pi-pico-rgb/src/main.rs b/coproc-embassy/app/src/main.rs similarity index 100% rename from coproc-embassy/pi-pico-rgb/src/main.rs rename to coproc-embassy/app/src/main.rs From edd1d757358795afcb46d3c3be864e686617d880 Mon Sep 17 00:00:00 2001 From: Shane Snover Date: Thu, 28 May 2026 12:47:31 -0600 Subject: [PATCH 3/4] Build a hosted integration test for the bootloader code --- Cargo.toml | 4 +- coproc-embassy/Cargo.lock | 527 +++++++++++++++++- coproc-embassy/Cargo.toml | 20 +- coproc-embassy/app/Cargo.toml | 5 +- coproc-embassy/app/build.rs | 27 +- coproc-embassy/bootloader/Cargo.toml | 19 + coproc-embassy/bootloader/build.rs | 26 + coproc-embassy/bootloader/src/main.rs | 49 ++ coproc-embassy/lib/bootload/Cargo.toml | 12 + coproc-embassy/lib/bootload/src/lib.rs | 40 ++ .../lib/bootload/tests/full_app_cycle.rs | 192 +++++++ coproc-embassy/lib/crc32/Cargo.toml | 7 + coproc-embassy/lib/crc32/src/lib.rs | 16 + coproc-embassy/lib/memory/Cargo.toml | 7 + coproc-embassy/lib/memory/src/lib.rs | 61 ++ coproc-embassy/lib/rw-flash/Cargo.toml | 14 + coproc-embassy/lib/rw-flash/src/flash.rs | 100 ++++ coproc-embassy/lib/rw-flash/src/image.rs | 54 ++ coproc-embassy/lib/rw-flash/src/lib.rs | 6 + coproc-embassy/lib/rw-flash/src/nvs.rs | 236 ++++++++ coproc-embassy/link/bootloader.x | 11 + utils/coproc-build-utils/Cargo.toml | 6 + utils/coproc-build-utils/src/lib.rs | 27 + utils/{ => megabit-utils}/Cargo.toml | 0 utils/{ => megabit-utils}/src/lib.rs | 0 utils/{ => megabit-utils}/src/rgb555.rs | 0 utils/{ => megabit-utils}/src/ws_server.rs | 0 27 files changed, 1410 insertions(+), 56 deletions(-) create mode 100644 coproc-embassy/bootloader/Cargo.toml create mode 100644 coproc-embassy/bootloader/build.rs create mode 100644 coproc-embassy/bootloader/src/main.rs create mode 100644 coproc-embassy/lib/bootload/Cargo.toml create mode 100644 coproc-embassy/lib/bootload/src/lib.rs create mode 100644 coproc-embassy/lib/bootload/tests/full_app_cycle.rs create mode 100644 coproc-embassy/lib/crc32/Cargo.toml create mode 100644 coproc-embassy/lib/crc32/src/lib.rs create mode 100644 coproc-embassy/lib/memory/Cargo.toml create mode 100644 coproc-embassy/lib/memory/src/lib.rs create mode 100644 coproc-embassy/lib/rw-flash/Cargo.toml create mode 100644 coproc-embassy/lib/rw-flash/src/flash.rs create mode 100644 coproc-embassy/lib/rw-flash/src/image.rs create mode 100644 coproc-embassy/lib/rw-flash/src/lib.rs create mode 100644 coproc-embassy/lib/rw-flash/src/nvs.rs create mode 100644 utils/coproc-build-utils/Cargo.toml create mode 100644 utils/coproc-build-utils/src/lib.rs rename utils/{ => megabit-utils}/Cargo.toml (100%) rename utils/{ => megabit-utils}/src/lib.rs (100%) rename utils/{ => megabit-utils}/src/rgb555.rs (100%) rename utils/{ => megabit-utils}/src/ws_server.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index cc12c5a..1813c1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ "serial-protocol", "simulator/backend", "simulator/sim_msgs", - "utils", + "utils/megabit-utils", ] exclude = [ "app-sdk", @@ -21,7 +21,7 @@ exclude = [ # Internal megabit-runner-msgs = { path = "runner/runner_msgs" } megabit-serial-protocol = { path = "serial-protocol" } -megabit-utils = { path = "utils", features = ["web-server"] } +megabit-utils = { path = "utils/megabit-utils", features = ["web-server"] } # External anyhow = { version = "1" } async-channel = { version = "2.1" } diff --git a/coproc-embassy/Cargo.lock b/coproc-embassy/Cargo.lock index 1b5a8d5..81e1ef0 100644 --- a/coproc-embassy/Cargo.lock +++ b/coproc-embassy/Cargo.lock @@ -23,7 +23,16 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" dependencies = [ - "term", + "term 0.7.0", +] + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term 1.2.1", ] [[package]] @@ -62,7 +71,16 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "bit-vec", + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", ] [[package]] @@ -71,12 +89,24 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitfield" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + [[package]] name = "bitflags" version = "1.3.2" @@ -89,6 +119,40 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bootload" +version = "0.1.0" +dependencies = [ + "crc32", + "memory", + "rw-flash", +] + +[[package]] +name = "bootloader" +version = "0.1.0" +dependencies = [ + "bootload", + "coproc-build-utils", + "cortex-m", + "cortex-m-rt", + "crc32", + "memory", + "panic-halt", + "rp-pac 7.0.0", + "rp2040-hal", + "rw-flash", +] + [[package]] name = "bytemuck" version = "1.25.0" @@ -123,6 +187,10 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "coproc-build-utils" +version = "0.1.0" + [[package]] name = "cortex-m" version = "0.7.7" @@ -130,7 +198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" dependencies = [ "bare-metal", - "bitfield", + "bitfield 0.13.2", "embedded-hal 0.2.7", "volatile-register", ] @@ -155,6 +223,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crc-any" version = "2.5.0" @@ -164,6 +241,10 @@ dependencies = [ "debug-helper", ] +[[package]] +name = "crc32" +version = "0.1.0" + [[package]] name = "critical-section" version = "1.2.0" @@ -176,6 +257,16 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "darling" version = "0.20.11" @@ -274,6 +365,16 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -416,17 +517,17 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-hal-nb", - "embedded-io", + "embedded-io 0.6.1", "embedded-io-async", "embedded-storage", "embedded-storage-async", "fixed", "futures", "nb 1.1.0", - "pio", - "pio-proc", - "rand_core", - "rp-pac", + "pio 0.2.1", + "pio-proc 0.2.2", + "rand_core 0.6.4", + "rp-pac 6.0.0", "rp2040-boot2", ] @@ -516,6 +617,15 @@ dependencies = [ "embedded-io-async", ] +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "embedded-hal" version = "0.2.7" @@ -557,13 +667,19 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + [[package]] name = "embedded-io-async" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" dependencies = [ - "embedded-io", + "embedded-io 0.6.1", ] [[package]] @@ -620,12 +736,66 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "frunk" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28aef0f9aa070bce60767c12ba9cb41efeaf1a2bc6427f87b7d83f11239a16d7" +dependencies = [ + "frunk_core", + "frunk_derives", +] + +[[package]] +name = "frunk_core" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476eeaa382e3462b84da5d6ba3da97b5786823c2d0d3a0d04ef088d073da225c" + +[[package]] +name = "frunk_derives" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b4095fc99e1d858e5b8c7125d2638372ec85aa0fe6c807105cf10b0265ca6c" +dependencies = [ + "frunk_proc_macro_helpers", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "frunk_proc_macro_helpers" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1952b802269f2db12ab7c0bd328d0ae8feaabf19f352a7b0af7bb0c5693abfce" +dependencies = [ + "frunk_core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fugit" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" +dependencies = [ + "gcd", +] + [[package]] name = "futures" version = "0.3.32" @@ -698,6 +868,22 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -809,28 +995,68 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lalrpop" version = "0.19.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" dependencies = [ - "ascii-canvas", - "bit-set", + "ascii-canvas 3.0.0", + "bit-set 0.5.3", "diff", "ena", "is-terminal", - "itertools", - "lalrpop-util", - "petgraph", + "itertools 0.10.5", + "lalrpop-util 0.19.12", + "petgraph 0.6.5", "regex", "regex-syntax 0.6.29", "string_cache", - "term", + "term 0.7.0", "tiny-keccak", "unicode-xid", ] +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas 4.0.0", + "bit-set 0.8.0", + "ena", + "itertools 0.14.0", + "lalrpop-util 0.22.2", + "petgraph 0.7.1", + "pico-args", + "regex", + "regex-syntax 0.8.10", + "sha3", + "string_cache", + "term 1.2.1", + "unicode-xid", + "walkdir", +] + [[package]] name = "lalrpop-util" version = "0.19.12" @@ -840,6 +1066,16 @@ dependencies = [ "regex", ] +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "regex-automata", + "rustversion", +] + [[package]] name = "libc" version = "0.2.186" @@ -880,6 +1116,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" name = "megabit-coproc-app" version = "0.1.0" dependencies = [ + "coproc-build-utils", "cortex-m", "cortex-m-rt", "defmt 0.3.100", @@ -919,6 +1156,10 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memory" +version = "0.1.0" + [[package]] name = "nb" version = "0.1.3" @@ -955,7 +1196,17 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ - "num_enum_derive", + "num_enum_derive 0.5.11", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive 0.7.6", + "rustversion", ] [[package]] @@ -969,6 +1220,23 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "panic-halt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" + [[package]] name = "panic-probe" version = "0.3.2" @@ -1014,7 +1282,17 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset", + "fixedbitset 0.4.2", + "indexmap", +] + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset 0.5.7", "indexmap", ] @@ -1027,6 +1305,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1040,7 +1324,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" dependencies = [ "arrayvec", - "num_enum", + "num_enum 0.5.11", + "paste", +] + +[[package]] +name = "pio" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ba4153cee9585abc451271aa437d9e8defdea8b468d48ba6b8f098cbe03d7f" +dependencies = [ + "pio-core", + "pio-proc 0.3.0", +] + +[[package]] +name = "pio-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61d90fddc3d67f21bbf93683bc461b05d6a29c708caf3ffb79947d7ff7095406" +dependencies = [ + "arrayvec", + "num_enum 0.7.6", "paste", ] @@ -1050,12 +1355,23 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77532c2b8279aef98dfc7207ef15298a5a3d6b6cc76ccc8b65913d69f3a8dd6b" dependencies = [ - "lalrpop", - "lalrpop-util", - "pio", + "lalrpop 0.19.12", + "lalrpop-util 0.19.12", + "pio 0.2.1", "regex-syntax 0.6.29", ] +[[package]] +name = "pio-parser" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825266c1eaddf54f636d06eefa4bf3c99d774c14ec46a4a6c6e5128a0f10d205" +dependencies = [ + "lalrpop 0.22.2", + "lalrpop-util 0.22.2", + "pio-core", +] + [[package]] name = "pio-proc" version = "0.2.2" @@ -1063,9 +1379,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b04dc870fb3a4fd8b3e4ca8c61b53bc8ac4eb78b66805d2b3c2e5c4829e0d7a" dependencies = [ "codespan-reporting", - "lalrpop-util", - "pio", - "pio-parser", + "lalrpop-util 0.19.12", + "pio 0.2.1", + "pio-parser 0.2.2", "proc-macro-error", "proc-macro2", "quote", @@ -1073,6 +1389,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "pio-proc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed4a76571f5fe51af43cc80ac870fe0c79cc0cdd686b9002a6c4c84bfdd0176b" +dependencies = [ + "codespan-reporting", + "lalrpop-util 0.22.2", + "pio-core", + "pio-parser 0.3.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -1158,6 +1490,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1213,6 +1551,21 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rp-binary-info" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f582261945fa215d40e2988b4df595d11c0908c0fff97a0fe23df766d117b790" + +[[package]] +name = "rp-hal-common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288358786b1458fb2caac8c4b40fb529ef4200d6c46467e2695b7a8ba573ae8" +dependencies = [ + "fugit", +] + [[package]] name = "rp-pac" version = "6.0.0" @@ -1223,6 +1576,16 @@ dependencies = [ "cortex-m-rt", ] +[[package]] +name = "rp-pac" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8af65855c40b2c35079514c5489abffc0429347fef25d8467ff98ad84b4322d3" +dependencies = [ + "cortex-m", + "cortex-m-rt", +] + [[package]] name = "rp2040-boot2" version = "0.3.0" @@ -1232,6 +1595,60 @@ dependencies = [ "crc-any", ] +[[package]] +name = "rp2040-hal" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b01f11995e606dd13b07f3fb80686149e5b8bbbab21fc4af437a9978f1a783f" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io 0.7.1", + "frunk", + "fugit", + "itertools 0.10.5", + "nb 1.1.0", + "paste", + "pio 0.3.0", + "rand_core 0.9.5", + "rp-binary-info", + "rp-hal-common", + "rp2040-hal-macros", + "rp2040-pac", + "usb-device 0.3.2", + "vcell", + "void", +] + +[[package]] +name = "rp2040-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86479063e497efe1ae81995ef9071f54fd1c7427e04d6c5b84cde545ff672a5e" +dependencies = [ + "cortex-m-rt", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rp2040-pac" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cbcd3f7a0ca7bbe61dc4eb7e202842bee4e27b769a7bf3a4a72fa399d6e404" +dependencies = [ + "cortex-m", + "critical-section", + "vcell", +] + [[package]] name = "rustc_version" version = "0.2.3" @@ -1256,6 +1673,25 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rw-flash" +version = "0.1.0" +dependencies = [ + "cortex-m", + "crc32", + "memory", + "rp2040-hal", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1312,6 +1748,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "siphasher" version = "1.0.3" @@ -1409,6 +1855,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "term" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" +dependencies = [ + "windows-sys", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -1497,6 +1952,16 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless 0.8.0", + "portable-atomic", +] + [[package]] name = "usbd-hid" version = "0.6.2" @@ -1505,7 +1970,7 @@ checksum = "b15e05bf1cfda1e9058cea54fd5b478b11779acf4cf61692a2df4fbb04e6b02a" dependencies = [ "serde", "ssmarshal", - "usb-device", + "usb-device 0.2.9", "usbd-hid-macros", ] @@ -1515,7 +1980,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f2ea77bc56883ecb89e728c8d4f2c6d2239240d2e36589acbd867d1ea13c99c" dependencies = [ - "bitfield", + "bitfield 0.13.2", ] [[package]] @@ -1559,6 +2024,16 @@ dependencies = [ "vcell", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" diff --git a/coproc-embassy/Cargo.toml b/coproc-embassy/Cargo.toml index d5e67a2..56836d6 100644 --- a/coproc-embassy/Cargo.toml +++ b/coproc-embassy/Cargo.toml @@ -1,6 +1,14 @@ [workspace] resolver = "2" -members = ["coproc-common", "app"] +members = [ + "coproc-common", + "app", + "bootloader", + "lib/crc32", + "lib/memory", + "lib/rw-flash", + "lib/bootload", +] [workspace.package] edition = "2024" @@ -8,6 +16,7 @@ license = "MIT OR Apache-2.0" [workspace.dependencies] cortex-m = { version = "0.7.6", features = ["inline-asm"] } +cortex-m-rt = "0.7.0" defmt = "0.3" defmt-rtt = "0.4" embassy-executor = { version = "0.5", features = [ @@ -21,8 +30,17 @@ embassy-sync = { version = "0.5" } embassy-time = { version = "0.3" } embassy-usb = { version = "0.1" } megabit-coproc-common = { path = "coproc-common", features = ["defmt"] } +rp2040-hal = "0.12" +rp-pac = { version = "7.0", features = ["rp2040", "rt"] } static_cell = "2" +bootload = { path = "lib/bootload" } +crc32 = { path = "lib/crc32" } +memory = { path = "lib/memory" } +rw-flash = { path = "lib/rw-flash" } + +coproc-build-utils = { path = "../utils/coproc-build-utils" } + [profile.release] debug = 2 opt-level = "z" diff --git a/coproc-embassy/app/Cargo.toml b/coproc-embassy/app/Cargo.toml index 0451f8e..078d535 100644 --- a/coproc-embassy/app/Cargo.toml +++ b/coproc-embassy/app/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true [dependencies] cortex-m.workspace = true -cortex-m-rt = "0.7.0" +cortex-m-rt.workspace = true defmt.workspace = true defmt-rtt.workspace = true embassy-executor = { workspace = true, features = ["defmt"] } @@ -28,5 +28,8 @@ panic-probe = { version = "0.3", features = ["print-defmt"] } portable-atomic = { version = "1.5", features = ["critical-section"] } static_cell.workspace = true +[build-dependencies] +coproc-build-utils.workspace = true + [lints] clippy.type_complexity = "allow" diff --git a/coproc-embassy/app/build.rs b/coproc-embassy/app/build.rs index 298aa4b..9857aac 100644 --- a/coproc-embassy/app/build.rs +++ b/coproc-embassy/app/build.rs @@ -5,34 +5,9 @@ fn main() { let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); println!("cargo:rustc-link-search={}", out.display()); - copy_linker_files_to_output_directory().unwrap(); + coproc_build_utils::copy_linker_files_to_output_directory().unwrap(); println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tapp-a.x"); println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); } - -fn copy_linker_files_to_output_directory() -> std::io::Result<()> { - let linker_file_dir = PathBuf::from(format!( - "{}/../link", - env::var_os("CARGO_MANIFEST_DIR") - .unwrap() - .into_string() - .unwrap() - )); - let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); - - for entry in linker_file_dir.read_dir()? { - let Ok(entry) = entry else { - continue; - }; - if entry.path().is_file() { - std::fs::copy( - entry.path(), - format!("{}/{}", out_dir.display(), entry.file_name().display()), - )?; - } - } - - Ok(()) -} diff --git a/coproc-embassy/bootloader/Cargo.toml b/coproc-embassy/bootloader/Cargo.toml new file mode 100644 index 0000000..f3bf36b --- /dev/null +++ b/coproc-embassy/bootloader/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bootloader" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] +bootload.workspace = true +cortex-m.workspace = true +cortex-m-rt.workspace = true +crc32.workspace = true +memory.workspace = true +panic-halt = "1.0" +rp-pac.workspace = true +rp2040-hal.workspace = true +rw-flash = { workspace = true, features = ["hw"] } + +[build-dependencies] +coproc-build-utils.workspace = true diff --git a/coproc-embassy/bootloader/build.rs b/coproc-embassy/bootloader/build.rs new file mode 100644 index 0000000..36ee72b --- /dev/null +++ b/coproc-embassy/bootloader/build.rs @@ -0,0 +1,26 @@ +use std::env; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out.display()); + + coproc_build_utils::copy_linker_files_to_output_directory().unwrap(); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tbootloader.x"); + println!("cargo:rustc-link-arg-bins=-Tdevice.x"); + //println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + + let version = env!("CARGO_PKG_VERSION"); + let version_parts = version.split('.').collect::>(); + assert_eq!(version_parts.len(), 3); + let major = version_parts[0].parse::().unwrap(); + let minor = version_parts[1].parse::().unwrap(); + let patch = version_parts[2].parse::().unwrap(); + let version_data = [major, minor, patch, 0x00]; + let mut file = + std::fs::File::create(format!("{}/bootloader_version.bin", out.display())).unwrap(); + file.write_all(&version_data).unwrap(); +} diff --git a/coproc-embassy/bootloader/src/main.rs b/coproc-embassy/bootloader/src/main.rs new file mode 100644 index 0000000..e9b2fb2 --- /dev/null +++ b/coproc-embassy/bootloader/src/main.rs @@ -0,0 +1,49 @@ +#![no_main] +#![no_std] +#![allow(unsafe_op_in_unsafe_fn)] + +use bootload::detect_partition_to_run; +use panic_halt as _; +use rp_pac as _; + +use rw_flash::{flash, nvs}; + +const BOOTLOADER_VERSION: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/bootloader_version.bin")); + +const fn bootloader_version() -> u32 { + (BOOTLOADER_VERSION[0] as u32) << 24 + | (BOOTLOADER_VERSION[1] as u32) << 16 + | (BOOTLOADER_VERSION[2] as u32) << 8 + | (BOOTLOADER_VERSION[3] as u32) +} + +const MAX_BOOT_ATTEMPTS: u8 = 3; + +#[rp2040_hal::entry] +fn main() -> ! { + let flash_storage = flash::PicoFlash::new(nvs::SECTOR_SIZE); + let nvs = nvs::NvsHandle { + flash: &flash_storage, + }; + + let Some(boot_target_addr) = detect_partition_to_run( + &nvs, + &flash_storage, + bootloader_version(), + MAX_BOOT_ATTEMPTS, + ) + .map(|part| part.boot_target_addr()) else { + // We couldn't find a partition, best to reset + rp2040_hal::reset(); + }; + + unsafe { boot_jump_to_addr(boot_target_addr) } +} + +unsafe fn boot_jump_to_addr(addr: u32) -> ! { + let peripherals = cortex_m::Peripherals::steal(); + peripherals.SCB.vtor.write(addr); + + cortex_m::asm::bootload(addr as *const u32) +} diff --git a/coproc-embassy/lib/bootload/Cargo.toml b/coproc-embassy/lib/bootload/Cargo.toml new file mode 100644 index 0000000..9ff1ab5 --- /dev/null +++ b/coproc-embassy/lib/bootload/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bootload" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] +memory.workspace = true +rw-flash.workspace = true + +[dev-dependencies] +crc32.workspace = true diff --git a/coproc-embassy/lib/bootload/src/lib.rs b/coproc-embassy/lib/bootload/src/lib.rs new file mode 100644 index 0000000..ebca6a0 --- /dev/null +++ b/coproc-embassy/lib/bootload/src/lib.rs @@ -0,0 +1,40 @@ +#![no_std] + +use memory::flash_app::AppPartition; +use rw_flash::{ + flash::FlashStorage, + image::{self, verify_image}, + nvs::{self, BootState}, +}; + +pub fn detect_partition_to_run<'a, F: FlashStorage>( + nvs: &nvs::NvsHandle<'a, F>, + flash: &'a F, + bootloader_version: u32, + max_boot_attempts: u8, +) -> Option { + let mut state = nvs + .read_boot_state() + .unwrap_or_else(|| BootState::default(bootloader_version)); + + // Increment the counter prior to boot + state.increment_boot_attempts(); + nvs.write_boot_state(&state); + + if state.boot_attempts > max_boot_attempts { + state.revert(); + nvs.write_boot_state(&state); + } + + let partition = AppPartition::from_u8(state.active_partition); + + // Verify the image by reading the appropriate image header, then checking crc32 + let image_header = image::read_image_header(partition, flash); + if image_header.is_valid() { + if verify_image(partition, &image_header, flash) { + return Some(partition); + } + } + + None +} diff --git a/coproc-embassy/lib/bootload/tests/full_app_cycle.rs b/coproc-embassy/lib/bootload/tests/full_app_cycle.rs new file mode 100644 index 0000000..87d478c --- /dev/null +++ b/coproc-embassy/lib/bootload/tests/full_app_cycle.rs @@ -0,0 +1,192 @@ +#[test] +fn full_app_cycle() -> std::io::Result<()> { + let flash = TestFlash::new(); + let nvs = nvs::NvsHandle { flash: &flash }; + + // Start by verifying that on start up with a completely blank app image that the bootlaoder + // attempts to create a boot state entry + assert!(nvs.read_boot_state().is_none()); + let partition = bootload::detect_partition_to_run(&nvs, &flash, 0x00010000, 3); + // There should be a new boot state value, but no valid image so it will fail to find a partition + assert!( + partition.is_none(), + "App partition should be none, but is: {partition:?}" + ); + // Verify a boot state entry was created + let last_entry = nvs.read_boot_state().unwrap(); + assert_eq!(last_entry.active_partition, 0); + assert_eq!(last_entry.update_pending, false); + assert_eq!(last_entry.boot_attempts, 1); + + // Now let's write an app to partition A + flash.write_app_image(AppPartition::A, 0x00010000); + + let partition = bootload::detect_partition_to_run(&nvs, &flash, 0x00010000, 3).unwrap(); + assert!(matches!(partition, AppPartition::A)); + + // Now let's write another app to partition B + flash.write_app_image(AppPartition::B, 0x00020000); + let partition = bootload::detect_partition_to_run(&nvs, &flash, 0x00010000, 3).unwrap(); + // Verify that writing a new app image is not sufficient + assert!(matches!(partition, AppPartition::A)); + + flash.write_boot_state(&nvs, AppPartition::B); + let partition = bootload::detect_partition_to_run(&nvs, &flash, 0x00010000, 3).unwrap(); + assert!(matches!(partition, AppPartition::B)); + + Ok(()) +} + +use std::{ + cell::RefCell, + cmp::min, + mem::{self, MaybeUninit}, +}; + +use memory::{flash_app::AppPartition, flash_boot}; +use rw_flash::{ + flash::FlashStorage, + image::ImageHeader, + nvs::{self, SECTOR_SIZE, SectorHeader}, +}; + +pub struct TestFlash { + mem: RefCell>, + origin_offset: usize, + sector_size: usize, +} + +impl TestFlash { + pub fn new() -> Self { + Self::initialize_with_blank_nvs() + } + + fn initialize_with_blank_nvs() -> Self { + let flash = Self { + origin_offset: memory::flash_boot::ORIGIN as usize, + mem: RefCell::new(Box::new([0xFFu8; 2 * 1024 * 1024])), + sector_size: SECTOR_SIZE, + }; + // The first page of NVS region should be a sector header + // We'll just initialize one + let header = SectorHeader::new(0); + flash.write_sector_header(header, 0); + flash + } + + fn write_sector_header(&self, header: SectorHeader, sector: u8) { + let bytes = unsafe { + core::slice::from_raw_parts( + &header as *const _ as *const u8, + mem::size_of::(), + ) + }; + let offset = (memory::flash_nvs::ORIGIN - memory::flash_boot::ORIGIN) + + (sector as u32 * self.sector_size as u32); + unsafe { self.write_page(offset, bytes) }; + } + + fn write_boot_state(&self, nvs: &nvs::NvsHandle<'_, Self>, partition: AppPartition) { + let mut boot_state = nvs::BootState::default(0x00010000); + boot_state.active_partition = partition as u8; + nvs.write_boot_state(&boot_state); + } + + fn generate_app_image(&self, version: u32) -> Vec { + const LEN: usize = 1028; + let version = version.to_le_bytes(); + let mut image_bytes = vec![0u8; LEN]; + for idx in (0..image_bytes.len()).step_by(4) { + for byte in 0..4 { + image_bytes[idx + byte] = version[byte]; + } + } + + image_bytes + } + + fn write_app_image(&self, partition: AppPartition, version: u32) { + let image = self.generate_app_image(version); + let crc = crc32::crc32(&image); + let image_header = ImageHeader::new(version, image.len() as u32, crc); + let header_bytes = unsafe { + core::slice::from_raw_parts( + &image_header as *const _ as *const u8, + mem::size_of::(), + ) + }; + unsafe { self.write_page(partition.origin() - flash_boot::ORIGIN, &header_bytes) }; + + let app_offset = partition.boot_target_addr() - flash_boot::ORIGIN; + for page in 0..image.len().div_ceil(256) { + let image_offset = page * 256; + let flash_offset = app_offset + image_offset as u32; + let end_image_section = image_offset + min(256, image.len() - image_offset); + unsafe { self.write_page(flash_offset, &image[image_offset..end_image_section]) }; + } + } +} + +impl FlashStorage for TestFlash { + unsafe fn read_as(&self, offset: u32) -> T { + let offset = offset as usize; + assert!(offset.is_multiple_of(4)); + assert!( + offset < self.mem.borrow().len(), + "Offset: {offset:0x} is greater than {:0x}", + self.mem.borrow().len() + ); + let mut t = MaybeUninit::::uninit(); + let buf = unsafe { + core::slice::from_raw_parts_mut(t.as_mut_ptr() as *mut u8, core::mem::size_of::()) + }; + buf.copy_from_slice(&self.mem.borrow()[offset..offset + buf.len()]); + unsafe { t.assume_init() } + } + + unsafe fn as_slice(&self, offset: u32, len: usize) -> &[u8] { + let offset = offset as usize; + unsafe { + core::slice::from_raw_parts(self.mem.borrow().as_ptr().offset(offset as isize), len) + } + } + + unsafe fn erase_sector(&self, flash_offset: u32) { + const FLASH_RESET_VALUE: u8 = 0xFF; + assert!(flash_offset.is_multiple_of(256)); + let flash_offset = flash_offset as usize - self.origin_offset; + + for byte in self + .mem + .borrow_mut() + .iter_mut() + .skip(flash_offset) + .take(self.sector_size) + { + *byte = FLASH_RESET_VALUE; + } + } + + unsafe fn write_page(&self, flash_offset: u32, data: &[u8]) { + println!("Write page flash offset: {flash_offset:0x}"); + let flash_offset = flash_offset as usize; + const FLASH_PAGE_SIZE: usize = 256; + assert!( + flash_offset < self.mem.borrow().len(), + "Invalid flash offset: {flash_offset:0x} is greater than {:0x}", + self.mem.borrow().len() + ); + assert!(data.len() <= FLASH_PAGE_SIZE); + assert!(flash_offset.rem_euclid(FLASH_PAGE_SIZE) + data.len() <= FLASH_PAGE_SIZE); + for (idx, byte) in self + .mem + .borrow_mut() + .iter_mut() + .skip(flash_offset) + .take(data.len()) + .enumerate() + { + *byte = data[idx]; + } + } +} diff --git a/coproc-embassy/lib/crc32/Cargo.toml b/coproc-embassy/lib/crc32/Cargo.toml new file mode 100644 index 0000000..577eb32 --- /dev/null +++ b/coproc-embassy/lib/crc32/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "crc32" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] diff --git a/coproc-embassy/lib/crc32/src/lib.rs b/coproc-embassy/lib/crc32/src/lib.rs new file mode 100644 index 0000000..a56d028 --- /dev/null +++ b/coproc-embassy/lib/crc32/src/lib.rs @@ -0,0 +1,16 @@ +#![no_std] + +pub fn crc32(data: &[u8]) -> u32 { + let mut crc = 0xffffffffu32; + for &byte in data { + crc ^= byte as u32; + for _ in 0..8 { + if crc & 1 != 0 { + crc = (crc >> 1) ^ 0xedb88320; + } else { + crc >>= 1; + } + } + } + !crc +} diff --git a/coproc-embassy/lib/memory/Cargo.toml b/coproc-embassy/lib/memory/Cargo.toml new file mode 100644 index 0000000..d773f29 --- /dev/null +++ b/coproc-embassy/lib/memory/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "memory" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] diff --git a/coproc-embassy/lib/memory/src/lib.rs b/coproc-embassy/lib/memory/src/lib.rs new file mode 100644 index 0000000..678929f --- /dev/null +++ b/coproc-embassy/lib/memory/src/lib.rs @@ -0,0 +1,61 @@ +#![no_std] + +pub mod flash_boot { + pub const ORIGIN: u32 = 0x10000000; + pub const LENGTH: u32 = 32 * 1024; +} + +pub mod flash_app { + use core::hint::unreachable_unchecked; + + use super::flash_boot; + + pub const APP_LENGTH: u32 = 992 * 1024; + + pub const ORIGIN: u32 = flash_boot::ORIGIN + flash_boot::LENGTH; + pub const LENGTH: u32 = APP_LENGTH * 2; + + // The linker script prepends each application image with an image header + // and then aligns the vector table at 256 byte increments. The image header + // is less than 256 bytes, so the table is offset at just 256 bytes + pub const VECTOR_TABLE_OFFSET: u32 = 256; + + #[repr(u8)] + #[derive(Clone, Copy, Debug)] + pub enum AppPartition { + A = 0, + B = 1, + } + + impl AppPartition { + pub fn from_u8(num: u8) -> Self { + match num { + 0 => AppPartition::A, + 1 => AppPartition::B, + _ => unsafe { unreachable_unchecked() }, + } + } + + pub fn origin(&self) -> u32 { + get_app_origin(*self) + } + + pub fn boot_target_addr(&self) -> u32 { + get_app_origin(*self) + VECTOR_TABLE_OFFSET + } + } + + pub fn get_app_origin(part: AppPartition) -> u32 { + match part { + AppPartition::A => ORIGIN, + AppPartition::B => ORIGIN + APP_LENGTH, + } + } +} + +pub mod flash_nvs { + use super::flash_app; + + pub const ORIGIN: u32 = flash_app::ORIGIN + flash_app::LENGTH; + pub const LENGTH: u32 = 32 * 1024; +} diff --git a/coproc-embassy/lib/rw-flash/Cargo.toml b/coproc-embassy/lib/rw-flash/Cargo.toml new file mode 100644 index 0000000..b5dc76a --- /dev/null +++ b/coproc-embassy/lib/rw-flash/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rw-flash" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] +cortex-m = { workspace = true, optional = true } +crc32.workspace = true +memory.workspace = true +rp2040-hal = { workspace = true, optional = true } + +[features] +hw = ["cortex-m", "rp2040-hal"] diff --git a/coproc-embassy/lib/rw-flash/src/flash.rs b/coproc-embassy/lib/rw-flash/src/flash.rs new file mode 100644 index 0000000..b7e603c --- /dev/null +++ b/coproc-embassy/lib/rw-flash/src/flash.rs @@ -0,0 +1,100 @@ +#[cfg(feature = "hw")] +pub use hw::*; + +pub trait FlashStorage { + /// Read an element of data from flash storage. This requires that the type + /// has a representation that ensures that it is consistent across compiler + /// versions. Types should be primitive or be `#[repr(C, packed)]` + unsafe fn read_as(&self, offset: u32) -> T; + + /// Creates a slice from a region of flash memory. + unsafe fn as_slice(&self, offset: u32, len: usize) -> &[u8]; + + /// Erases an entire sector of NVS flash + unsafe fn erase_sector(&self, flash_offset: u32); + + /// Write data to flash. The `flash_offset` must be word-aligned to 4 byte + /// address. The `data` must not be longer so as to write over the end of + /// the page (256 bytes). + unsafe fn write_page(&self, flash_offset: u32, data: &[u8]); +} + +#[cfg(feature = "hw")] +mod hw { + + use super::FlashStorage; + use core::mem::{self, MaybeUninit}; + use memory::flash_boot; + use rp2040_hal::rom_data; + + pub struct PicoFlash { + sector_size: usize, + } + + impl PicoFlash { + pub fn new(sector_size: usize) -> Self { + Self { sector_size } + } + } + + impl FlashStorage for PicoFlash { + unsafe fn read_as(&self, offset: u32) -> T { + let mut t = MaybeUninit::::uninit(); + let buf = unsafe { + core::slice::from_raw_parts_mut(t.as_mut_ptr() as *mut u8, mem::size_of::()) + }; + read(offset, buf); + unsafe { t.assume_init() } + } + + unsafe fn as_slice(&self, offset: u32, len: usize) -> &[u8] { + core::slice::from_raw_parts((offset + flash_boot::ORIGIN) as *const u8, len) + } + + unsafe fn erase_sector(&self, flash_offset: u32) { + erase_sector(flash_offset, self.sector_size) + } + + unsafe fn write_page(&self, flash_offset: u32, data: &[u8]) { + write_page(flash_offset, data) + } + } + + fn read(offset: u32, buf: &mut [u8]) { + let addr = offset + flash_boot::ORIGIN; + let src = unsafe { core::slice::from_raw_parts(addr as *const u8, buf.len()) }; + buf.copy_from_slice(src); + } + + #[inline(never)] + #[unsafe(link_section = ".time_critical.flash_erase_sector")] + unsafe fn erase_sector(flash_offset: u32, sector_size: usize) { + cortex_m::interrupt::free(|_| unsafe { + rom_data::connect_internal_flash(); + rom_data::flash_exit_xip(); + rom_data::flash_range_erase(flash_offset, sector_size, 1 << 12, 0x20); + rom_data::flash_flush_cache(); + rom_data::flash_enter_cmd_xip(); + }); + } + + #[inline(never)] + #[unsafe(link_section = ".time_critical.flash_write_page")] + unsafe fn write_page(flash_offset: u32, data: &[u8]) { + const FLASH_PAGE_SIZE: usize = 256; + debug_assert!(data.len() <= FLASH_PAGE_SIZE); + debug_assert!((data.as_ptr() as *const u32).is_aligned()); + // Ensures that we don't write over the end of the page + debug_assert!( + (flash_offset as usize).rem_euclid(FLASH_PAGE_SIZE) + data.len() <= FLASH_PAGE_SIZE + ); + + cortex_m::interrupt::free(|_| unsafe { + rom_data::connect_internal_flash(); + rom_data::flash_exit_xip(); + rom_data::flash_range_program(flash_offset, data.as_ptr(), data.len()); + rom_data::flash_flush_cache(); + rom_data::flash_enter_cmd_xip(); + }); + } +} diff --git a/coproc-embassy/lib/rw-flash/src/image.rs b/coproc-embassy/lib/rw-flash/src/image.rs new file mode 100644 index 0000000..d8a8e30 --- /dev/null +++ b/coproc-embassy/lib/rw-flash/src/image.rs @@ -0,0 +1,54 @@ +use core::mem; + +use memory::{ + flash_app::{self, AppPartition, get_app_origin}, + flash_boot, +}; + +use crate::flash::FlashStorage; + +pub const IMAGE_MAGIC: u32 = 0x494d4700; // 'IMG\0' +pub const IMAGE_HEADER_VERSION: u32 = 0; + +#[repr(C, packed)] +pub struct ImageHeader { + magic: u32, + header_version: u32, + image_version: u32, + app_size: u32, + crc32: u32, + _reserved: [u8; 236], +} + +const _: () = assert!(mem::size_of::() == 256); + +impl ImageHeader { + pub fn new(image_version: u32, app_size: u32, crc32: u32) -> Self { + Self { + magic: IMAGE_MAGIC, + header_version: IMAGE_HEADER_VERSION, + image_version, + app_size, + crc32, + _reserved: [0u8; 236], + } + } + + pub fn is_valid(&self) -> bool { + self.magic == IMAGE_MAGIC + && self.app_size < (flash_app::APP_LENGTH - flash_app::VECTOR_TABLE_OFFSET) + } +} + +pub fn read_image_header(partition: AppPartition, f: &impl FlashStorage) -> ImageHeader { + let flash_offset = get_app_origin(partition) - flash_boot::ORIGIN; + unsafe { f.read_as::(flash_offset) } +} + +pub fn verify_image(partition: AppPartition, header: &ImageHeader, f: &impl FlashStorage) -> bool { + let flash_offset = get_app_origin(partition) - flash_boot::ORIGIN; + let image_offset = flash_offset + flash_app::VECTOR_TABLE_OFFSET; + let image_bytes = unsafe { f.as_slice(image_offset, header.app_size as usize) }; + let crc = crc32::crc32(image_bytes); + crc == header.crc32 +} diff --git a/coproc-embassy/lib/rw-flash/src/lib.rs b/coproc-embassy/lib/rw-flash/src/lib.rs new file mode 100644 index 0000000..2295377 --- /dev/null +++ b/coproc-embassy/lib/rw-flash/src/lib.rs @@ -0,0 +1,6 @@ +#![no_std] +#![allow(unsafe_op_in_unsafe_fn)] + +pub mod flash; +pub mod image; +pub mod nvs; diff --git a/coproc-embassy/lib/rw-flash/src/nvs.rs b/coproc-embassy/lib/rw-flash/src/nvs.rs new file mode 100644 index 0000000..c4e27dc --- /dev/null +++ b/coproc-embassy/lib/rw-flash/src/nvs.rs @@ -0,0 +1,236 @@ +use core::mem; + +use crate::flash; +use memory::{flash_boot, flash_nvs}; + +const SECTOR_COUNT: usize = 2; +pub const SECTOR_SIZE: usize = flash_nvs::LENGTH as usize / SECTOR_COUNT; +const RECORD_SIZE: usize = 64; +const SECTOR_MAGIC: u32 = 0x4e565300; // "NVS\0" +const RECORD_MAGIC: u32 = 0x52454300; // "REC\0" + +const RECORDS_PER_SECTOR: usize = (SECTOR_SIZE - mem::size_of::()) / RECORD_SIZE; + +#[repr(C, packed)] +#[derive(Clone, Copy)] +pub struct SectorHeader { + magic: u32, + generation: u32, + crc32: u32, + _reserved: [u8; 256 - 12], +} +// Should be equivalent to the flash page size +const _: () = assert!(mem::size_of::() == 256); + +impl SectorHeader { + pub fn new(generation: u32) -> Self { + let mut header = Self { + magic: SECTOR_MAGIC, + generation, + crc32: 0, + _reserved: [0; 244], + }; + header.crc32 = header.compute_crc(); + header + } + + fn compute_crc(&self) -> u32 { + // Calculate the CRC of all members except the CRC field, which is last + let bytes = unsafe { + core::slice::from_raw_parts( + self as *const _ as *const u8, + mem::offset_of!(SectorHeader, crc32), + ) + }; + crc32::crc32(bytes) + } + + fn is_valid(&self) -> bool { + self.magic == SECTOR_MAGIC && self.crc32 == self.compute_crc() + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy)] +pub struct BootState { + magic: u32, + pub bootloader_version: u32, + pub active_partition: u8, + pub update_pending: bool, + pub boot_attempts: u8, + _pad: u8, + pub update_version: u32, + pub crc32: u32, + _reserved: [u8; 44], +} + +const _: () = assert!(mem::size_of::() == RECORD_SIZE); + +impl BootState { + pub fn default(bootloader_version: u32) -> Self { + let mut state = Self { + magic: RECORD_MAGIC, + bootloader_version, + active_partition: 0, + update_pending: false, + boot_attempts: 0, + _pad: 0, + update_version: 0, + crc32: 0, + _reserved: [0; 44], + }; + state.crc32 = state.compute_crc(); + state + } + + pub fn increment_boot_attempts(&mut self) { + self.boot_attempts += 1; + self.crc32 = self.compute_crc(); + } + + pub fn revert(&mut self) { + if self.active_partition == 0 { + self.active_partition = 1; + } else { + self.active_partition = 0; + } + + self.update_pending = false; + self.boot_attempts = 0; + self.crc32 = self.compute_crc(); + } + + fn is_valid(&self) -> bool { + self.magic == RECORD_MAGIC && self.crc32 == self.compute_crc() + } + + fn compute_crc(&self) -> u32 { + // Calculate the CRC of all members except the CRC field, which is last + let bytes = unsafe { + core::slice::from_raw_parts( + self as *const _ as *const u8, + mem::offset_of!(BootState, crc32), + ) + }; + crc32::crc32(bytes) + } +} + +pub struct NvsHandle<'a, F: flash::FlashStorage> { + pub flash: &'a F, +} + +impl<'a, F: flash::FlashStorage> NvsHandle<'a, F> { + pub fn read_boot_state(&self) -> Option { + let sector = self.find_active_sector()?; + self.read_latest_record(sector) + } + + pub fn write_boot_state(&self, boot_state: &BootState) { + let sector = self.find_active_sector().unwrap_or(0); + let next_entry = self.find_next_free_entry(sector); + + if next_entry >= RECORDS_PER_SECTOR { + let other = 1 - sector; + self.compact(sector, other, boot_state); + } else { + self.append_record(sector, next_entry, boot_state); + } + } + + fn read_sector_header(&self, sector: usize) -> Option { + let base_addr = sector_base_offset(sector); + let header = unsafe { self.flash.read_as::(base_addr) }; + header.is_valid().then_some(header) + } + + fn find_active_sector(&self) -> Option { + let header0 = self.read_sector_header(0); + let header1 = self.read_sector_header(1); + + match (header0, header1) { + (Some(a), Some(b)) => { + if a.generation.wrapping_sub(b.generation) < u32::MAX / 2 { + Some(0) + } else { + Some(1) + } + } + (Some(_), None) => Some(0), + (None, Some(_)) => None, + (None, None) => None, + } + } + + fn find_next_free_entry(&self, sector: usize) -> usize { + for entry in 0..RECORDS_PER_SECTOR { + let offset = record_offset(sector, entry); + // The magic number is the first field + const _: () = assert!(mem::offset_of!(BootState, magic) == 0); + let magic = unsafe { self.flash.read_as::(offset) }; + if magic == 0xffffffff { + return entry; + } + } + // Indicates the sector is full + RECORDS_PER_SECTOR + } + + fn read_record(&self, sector: usize, entry: usize) -> Option { + let offset = record_offset(sector, entry); + let record = unsafe { self.flash.read_as::(offset) }; + record.is_valid().then_some(record) + } + + fn read_latest_record(&self, sector: usize) -> Option { + let mut latest = None; + for entry in 0..RECORDS_PER_SECTOR { + if let Some(record) = self.read_record(sector, entry) { + latest = Some(record); + } + } + + latest + } + + fn append_record(&self, sector: usize, entry: usize, state: &BootState) { + let offset = record_offset(sector, entry); + let bytes = + unsafe { core::slice::from_raw_parts(state as *const _ as *const u8, RECORD_SIZE) }; + unsafe { self.flash.write_page(offset, bytes) }; + } + + fn compact(&self, from_sector: usize, to_sector: usize, new_state: &BootState) { + let old_generation = self + .read_sector_header(from_sector) + .map(|header| header.generation) + .unwrap_or(0); + + let to_base = sector_base_offset(to_sector); + unsafe { self.flash.erase_sector(to_base) }; + + let header = SectorHeader::new(old_generation.wrapping_add(1)); + let header_bytes = unsafe { + core::slice::from_raw_parts( + &header as *const _ as *const u8, + mem::size_of::(), + ) + }; + unsafe { self.flash.write_page(to_base, header_bytes) }; + + self.append_record(to_sector, 0, new_state); + + let from_base = sector_base_offset(from_sector); + unsafe { self.flash.erase_sector(from_base) }; + } +} + +fn sector_base_offset(sector: usize) -> u32 { + (flash_nvs::ORIGIN - flash_boot::ORIGIN) + (sector * SECTOR_SIZE) as u32 +} + +fn record_offset(sector: usize, entry: usize) -> u32 { + sector_base_offset(sector) + + mem::size_of::() as u32 + + (entry * RECORD_SIZE) as u32 +} diff --git a/coproc-embassy/link/bootloader.x b/coproc-embassy/link/bootloader.x index cd05ee3..4810859 100644 --- a/coproc-embassy/link/bootloader.x +++ b/coproc-embassy/link/bootloader.x @@ -125,6 +125,17 @@ SECTIONS { } > FLASH_BOOT /* ## Sections in RAM */ + /* ### .time_critical */ + __time_critical_source = LOADADDR(.time_critical); + .time_critical : ALIGN(4) + { + . = ALIGN(4); + __time_critical_start = .; + *(.time_critical .time_critical.*); + . = ALIGN(4); + } > RAM AT>FLASH_BOOT + __time_critical_end = .; + /* ### .data */ .data : ALIGN(4) { diff --git a/utils/coproc-build-utils/Cargo.toml b/utils/coproc-build-utils/Cargo.toml new file mode 100644 index 0000000..d32654c --- /dev/null +++ b/utils/coproc-build-utils/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "coproc-build-utils" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/utils/coproc-build-utils/src/lib.rs b/utils/coproc-build-utils/src/lib.rs new file mode 100644 index 0000000..0c09da2 --- /dev/null +++ b/utils/coproc-build-utils/src/lib.rs @@ -0,0 +1,27 @@ +use std::env; +use std::path::PathBuf; + +pub fn copy_linker_files_to_output_directory() -> std::io::Result<()> { + let linker_file_dir = PathBuf::from(format!( + "{}/../link", + env::var_os("CARGO_MANIFEST_DIR") + .unwrap() + .into_string() + .unwrap() + )); + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + + for entry in linker_file_dir.read_dir()? { + let Ok(entry) = entry else { + continue; + }; + if entry.path().is_file() { + std::fs::copy( + entry.path(), + format!("{}/{}", out_dir.display(), entry.file_name().display()), + )?; + } + } + + Ok(()) +} diff --git a/utils/Cargo.toml b/utils/megabit-utils/Cargo.toml similarity index 100% rename from utils/Cargo.toml rename to utils/megabit-utils/Cargo.toml diff --git a/utils/src/lib.rs b/utils/megabit-utils/src/lib.rs similarity index 100% rename from utils/src/lib.rs rename to utils/megabit-utils/src/lib.rs diff --git a/utils/src/rgb555.rs b/utils/megabit-utils/src/rgb555.rs similarity index 100% rename from utils/src/rgb555.rs rename to utils/megabit-utils/src/rgb555.rs diff --git a/utils/src/ws_server.rs b/utils/megabit-utils/src/ws_server.rs similarity index 100% rename from utils/src/ws_server.rs rename to utils/megabit-utils/src/ws_server.rs From 0eb5f9a6c4ef6ccf902a4c8f8ecf6ed552f39f03 Mon Sep 17 00:00:00 2001 From: Shane Snover Date: Thu, 28 May 2026 12:57:22 -0600 Subject: [PATCH 4/4] Fix ci that got broken by path changes --- .github/workflows/coproc.yml | 2 +- Cargo.toml | 2 +- coproc-embassy/app/src/main.rs | 39 +++++++++---------- .../src/display/rgb_matrix/driver.rs | 4 +- simulator/backend/Cargo.toml | 6 ++- 5 files changed, 25 insertions(+), 28 deletions(-) diff --git a/.github/workflows/coproc.yml b/.github/workflows/coproc.yml index 89c8ec8..e057ffd 100644 --- a/.github/workflows/coproc.yml +++ b/.github/workflows/coproc.yml @@ -20,7 +20,7 @@ jobs: with: components: rustfmt - name: Format Pico - run: cargo fmt --all --check --manifest-path coproc-embassy/pi-pico-rgb/Cargo.toml + run: cargo fmt --all --check --manifest-path coproc-embassy/Cargo.toml build: name: Build firmwares diff --git a/Cargo.toml b/Cargo.toml index 1813c1e..09a32bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ "serial-protocol", "simulator/backend", "simulator/sim_msgs", - "utils/megabit-utils", + "utils/*", ] exclude = [ "app-sdk", diff --git a/coproc-embassy/app/src/main.rs b/coproc-embassy/app/src/main.rs index 81cb035..b6cc7b6 100644 --- a/coproc-embassy/app/src/main.rs +++ b/coproc-embassy/app/src/main.rs @@ -38,23 +38,23 @@ use panic_probe as _; use static_cell::StaticCell; type DisplayDriver = WaveshareDriver< - ( - Output<'static, PIN_0>, - Output<'static, PIN_1>, - Output<'static, PIN_8>, - Output<'static, PIN_2>, - Output<'static, PIN_3>, - Output<'static, PIN_9>, - Output<'static, PIN_4>, - Output<'static, PIN_10>, - Output<'static, PIN_5>, - Output<'static, PIN_11>, - Output<'static, PIN_6>, - Output<'static, PIN_12>, - Output<'static, PIN_7>, - ), - CriticalSectionRawMutex, - >; + ( + Output<'static, PIN_0>, + Output<'static, PIN_1>, + Output<'static, PIN_8>, + Output<'static, PIN_2>, + Output<'static, PIN_3>, + Output<'static, PIN_9>, + Output<'static, PIN_4>, + Output<'static, PIN_10>, + Output<'static, PIN_5>, + Output<'static, PIN_11>, + Output<'static, PIN_6>, + Output<'static, PIN_12>, + Output<'static, PIN_7>, + ), + CriticalSectionRawMutex, +>; type UsbDriver = usb::Driver<'static, peripherals::USB>; const DEFAULT_MONO_COLOR: (u8, u8, u8) = (0xff, 00, 00); @@ -217,10 +217,7 @@ async fn core0_task( // todo: That generic parameter on Output for the PIN is going away at some point #[embassy_executor::task] -async fn core1_task( - mut waveshare: DisplayDriver, - mut debug_pin: Output<'static, PIN_21>, -) { +async fn core1_task(mut waveshare: DisplayDriver, mut debug_pin: Output<'static, PIN_21>) { loop { embassy_time::Timer::after_micros(500).await; waveshare.render(&mut debug_pin).await; diff --git a/coproc-embassy/coproc-common/src/display/rgb_matrix/driver.rs b/coproc-embassy/coproc-common/src/display/rgb_matrix/driver.rs index e92b3f8..d2f6f2c 100644 --- a/coproc-embassy/coproc-common/src/display/rgb_matrix/driver.rs +++ b/coproc-embassy/coproc-common/src/display/rgb_matrix/driver.rs @@ -156,9 +156,7 @@ impl WaveshareDriver { for pwm_step in 0..(1u8 << 4) { let pwm_step = pwm_step << 1; for row in 0..(ROWS / 2) { - for idx in ((row * COLUMNS)..((row + 1) * COLUMNS)) - .step_by(16) - { + for idx in ((row * COLUMNS)..((row + 1) * COLUMNS)).step_by(16) { for idx_add in 0..16 { let idx = idx + idx_add; let idx2 = idx + pixel_data.len() / 2; diff --git a/simulator/backend/Cargo.toml b/simulator/backend/Cargo.toml index 68e79c0..eeb6765 100644 --- a/simulator/backend/Cargo.toml +++ b/simulator/backend/Cargo.toml @@ -11,7 +11,9 @@ cobs = { version = "0.2" } gif = "0.13" megabit-serial-protocol = { path = "../../serial-protocol" } megabit-sim-msgs = { path = "../sim_msgs" } -megabit-utils = { path = "../../utils", features = ["web-server"] } +megabit-utils = { path = "../../utils/megabit-utils", features = [ + "web-server", +] } nix = { version = "0.27", features = ["term"] } rmp-serde = "1.1" serde = { version = "1", features = ["derive"] } @@ -20,4 +22,4 @@ tokio = { version = "1.0", features = ["full"] } tower = { version = "0.4", features = ["util"] } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } -uuid = { version = "1.7", features = ["v4"] } \ No newline at end of file +uuid = { version = "1.7", features = ["v4"] }