Categories
Embedded Systems Engineering

How is the Sparkfun Artemis Configured to Boot?

tl;dr INFO0 is configured by Sparkfun. SRAM is cleared, GPIO47 is used to select whether ASB runs and accept data over UART, and if not the bootloader jumps to 0xC000.

I’ve been working with the Sparkfun Artemis module and the Redboard Artemis ATP board for research, due to the Ambiq Apollo3 microcontroller in use. There is relatively little documentation on the requirements of user programs to successfully take over after the Apollo3 secure bootloader finishes running. There’s also not much out there on what this secure bootloader actually does (and I wish we could shut it off and just jump immediately to user code…). The goal of this post is to summarize a few of my findings.

The secure bootloader can be configured to some extend through the use of parameters that are stored in a special region of flash that’s treated as an OTP during normal operation (completely read-only). This region is called INFO0. I’m assuming it’s Sparkfun that’s providing something reasonable here, or maybe it’s the default from the factory. In any case, the Sparkfun Apollo3s are configured to jump to address 0xC0001 or with a logic high on GPIO472 to use the ASB bootloader for “OTA” updates. The asb.py script (also found in the Ambiq SDK) can be used to upload programs through this interface over UART03 (also configured in INFO0).

The bootloaders (both ASB and Sparkfun’s own SVL) check for the validity of the application’s stack pointer (the first 32-bit value of the program). On this MCU, SRAM spans from 0x10000000 to 0x10005FFF. The stack is full descending, so in theory using the address 0x10006000 as the initial value of the stack should be valid, but it turns out both ASB and SVL reject this value. Valid stack values must be less than or equal to 0x10005FFF.

After some testing trying to have some SRAM survive reboots, it seems like the secure bootloader completely erases all of SRAM. It seems that the code to do this is copied from bootrom to SRAM, oddly enough (I was able to place a watchpoint on SRAM for when it was cleared, and it paused on some code in SRAM while the bootloader was executing). Apparently register 0x50020050 holds a value indicating “the amount of SRAM to keep reserved for application scratch space”4. So it may be possible to prevent some SRAM from being cleared with a modified INFO0. Unfortunately for me, this only protects memory at the high end of SRAM (I would have liked to retain some memory in the first 4KB of SRAM).

I think this is everything I’ve found thus far. I’ve reprogrammed the SVL bootloader, optimizing it and reducing it in size. It also provides some compile-time options for skipping the SVL bootloader completely or not based on the value of some GPIO (currently only lightly configurable). I also wrote svl-tools (in Rust) to replace the svl.py program from Sparkfun and to leverage the extra functionality of reading from memory on request that I’ve added to the SVL.

If I find anything else noteworthy about the boot process, I’ll edit this post and make note of it.

  1. Register 0x50020C00 is set to 0xC000, which is the address to jump to after the bootloader is done. ↩︎
  2. Register 0x50020020 configures the pin used to force ASB boot programming. It is set to 0x000000af, pin 47 with polarity set to high. ↩︎
  3. Register 0x5002002C configures the pins to use for UART, it is set to 0xffff3031, so pin 48 and 49 for TX and RX respectively. Register 0x50020028 configures the UART, it is set to 0x1c200c0, meaning using UART module 0, data 8 bits, no parity, 1 stop bit, and a baud rate of 115200. ↩︎
  4. From the Apollo3 datasheet: https://ambiq.com/wp-content/uploads/2020/10/Apollo3-Blue-SoC-Datasheet.pdf ↩︎