From 7b59c685be0167b67effbeda3eda9fc5c8509116 Mon Sep 17 00:00:00 2001 From: William Vinnicombe Date: Tue, 12 Nov 2024 18:50:44 +0000 Subject: [PATCH 1/5] Add simple UART bootloader The sample binary flashes the LED and prints Hello, world back over the UART interface --- README.md | 1 + bootloaders/CMakeLists.txt | 2 + bootloaders/uart/CMakeLists.txt | 32 +++++++ bootloaders/uart/uart-pt.json | 23 +++++ bootloaders/uart/uart_binary.c | 65 ++++++++++++++ bootloaders/uart/uart_boot.c | 152 ++++++++++++++++++++++++++++++++ 6 files changed, 275 insertions(+) create mode 100644 bootloaders/uart/CMakeLists.txt create mode 100644 bootloaders/uart/uart-pt.json create mode 100644 bootloaders/uart/uart_binary.c create mode 100644 bootloaders/uart/uart_boot.c diff --git a/README.md b/README.md index 5a436a348..cbdfaff48 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ App|Description App|Description ---|--- [enc_bootloader](bootloaders/encrypted) | A bootloader which decrypts binaries from flash into SRAM. See the separate [README](bootloaders/encrypted/README.md) for more information +[uart_boot](bootloaders/uart) | A bootloader which boots a separate RP2350 using the UART boot interface. See section 5.8 in the datasheet for more details, including the wiring requirements ### Clocks diff --git a/bootloaders/CMakeLists.txt b/bootloaders/CMakeLists.txt index fff349887..849138ab6 100644 --- a/bootloaders/CMakeLists.txt +++ b/bootloaders/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory_exclude_platforms(uart host rp2040) + if (TARGET pico_mbedtls) # older clang seem to have a segment overlap issue that confuses picotool if (PICO_C_COMPILER_IS_CLANG AND CMAKE_C_COMPILER_VERSION VERSION_LESS "17.0.0") diff --git a/bootloaders/uart/CMakeLists.txt b/bootloaders/uart/CMakeLists.txt new file mode 100644 index 000000000..cc0e38caa --- /dev/null +++ b/bootloaders/uart/CMakeLists.txt @@ -0,0 +1,32 @@ +add_executable(uart_boot + uart_boot.c + ) + +# pull in common dependencies +target_link_libraries(uart_boot pico_stdlib hardware_flash) + +pico_embed_pt_in_binary(uart_boot ${CMAKE_CURRENT_LIST_DIR}/uart-pt.json) +pico_set_uf2_family(uart_boot "absolute") + +# create map/bin/hex file etc. +pico_add_extra_outputs(uart_boot) + +# add url via pico_set_program_url +example_auto_set_url(uart_boot) + + +add_executable(uart_binary + uart_binary.c + ) + +# pull in common dependencies +target_link_libraries(uart_binary pico_stdlib) + +pico_set_binary_type(uart_binary no_flash) +pico_package_uf2_output(uart_binary 0x10000000) + +# create map/bin/hex/uf2 file etc. +pico_add_extra_outputs(uart_binary) + +# call pico_set_program_url to set path to example on github, so users can find the source for an example via picotool +example_auto_set_url(uart_binary) diff --git a/bootloaders/uart/uart-pt.json b/bootloaders/uart/uart-pt.json new file mode 100644 index 000000000..d6ce01299 --- /dev/null +++ b/bootloaders/uart/uart-pt.json @@ -0,0 +1,23 @@ +{ + "version": [1, 0], + "unpartitioned": { + "families": ["absolute"], + "permissions": { + "secure": "rw", + "nonsecure": "rw", + "bootloader": "rw" + } + }, + "partitions": [ + { + "start": "128K", + "size": "32K", + "families": ["rp2350-arm-s"], + "permissions": { + "secure": "rw", + "nonsecure": "rw", + "bootloader": "rw" + } + } + ] +} \ No newline at end of file diff --git a/bootloaders/uart/uart_binary.c b/bootloaders/uart/uart_binary.c new file mode 100644 index 000000000..e3f2f535b --- /dev/null +++ b/bootloaders/uart/uart_binary.c @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico/stdlib.h" +#include "hardware/uart.h" +#include "hardware/structs/pads_qspi.h" +#include "hardware/structs/io_qspi.h" + +#ifndef LED_DELAY_MS +#define LED_DELAY_MS 500 +#endif + +// Initialize the GPIO for the LED +void pico_led_init(void) { +#ifdef PICO_DEFAULT_LED_PIN + // A device like Pico that uses a GPIO for the LED will define PICO_DEFAULT_LED_PIN + // so we can use normal GPIO functionality to turn the led on and off + gpio_init(PICO_DEFAULT_LED_PIN); + gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); +#endif +} + +// Turn the LED on or off +void pico_set_led(bool led_on) { +#if defined(PICO_DEFAULT_LED_PIN) + // Just set the GPIO on or off + gpio_put(PICO_DEFAULT_LED_PIN, led_on); +#endif +} + +// Set function for QSPI GPIO pin +void qspi_gpio_set_function(uint gpio, gpio_function_t fn) { + // Set input enable on, output disable off + hw_write_masked(&pads_qspi_hw->io[gpio], + PADS_QSPI_GPIO_QSPI_SD2_IE_BITS, + PADS_QSPI_GPIO_QSPI_SD2_IE_BITS | PADS_QSPI_GPIO_QSPI_SD2_OD_BITS + ); + // Zero all fields apart from fsel; we want this IO to do what the peripheral tells it. + // This doesn't affect e.g. pullup/pulldown, as these are in pad controls. + io_qspi_hw->io[gpio].ctrl = fn << IO_QSPI_GPIO_QSPI_SD2_CTRL_FUNCSEL_LSB; + + // Remove pad isolation now that the correct peripheral is in control of the pad + hw_clear_bits(&pads_qspi_hw->io[gpio], PADS_QSPI_GPIO_QSPI_SD2_ISO_BITS); +} + +int main() { + pico_led_init(); + + // SD2 is QSPI GPIO 3, SD3 is QSPI GPIO 4 + qspi_gpio_set_function(3, GPIO_FUNC_UART_AUX); + qspi_gpio_set_function(4, GPIO_FUNC_UART_AUX); + + uart_init(uart0, 1000000); + + while (true) { + uart_puts(uart0, "Hello, world\n"); + pico_set_led(true); + sleep_ms(LED_DELAY_MS); + pico_set_led(false); + sleep_ms(LED_DELAY_MS); + } +} diff --git a/bootloaders/uart/uart_boot.c b/bootloaders/uart/uart_boot.c new file mode 100644 index 000000000..caeed6113 --- /dev/null +++ b/bootloaders/uart/uart_boot.c @@ -0,0 +1,152 @@ +#include +#include +#include "pico/stdlib.h" +#include "hardware/uart.h" +#include "pico/bootrom.h" +#include "boot/picobin.h" +#include "hardware/flash.h" + +// UART defines for uart boot +#define UART_ID uart1 +#define BAUD_RATE 1000000 + +// Use pins 4 and 5 for uart boot +#define UART_TX_PIN 4 +#define UART_RX_PIN 5 + +// Use pin 3 for the RUN pin on the other chip +#define RUN_PIN 3 + + +void reset_chip() { + // Toggle run pin + gpio_put(RUN_PIN, false); + sleep_ms(1); + gpio_put(RUN_PIN, true); +} + + +void uart_boot() { + uint knocks = 0; + while (true) { + // Send the knock sequence + uart_putc_raw(UART_ID, 0x56); + uart_putc_raw(UART_ID, 0xff); + uart_putc_raw(UART_ID, 0x8b); + uart_putc_raw(UART_ID, 0xe4); + uart_putc_raw(UART_ID, 'n'); + + if (uart_is_readable_within_us(UART_ID, 1000)) { + char in = uart_getc(UART_ID); + printf("%c\n", in); + break; + } else { + if (knocks > 10) { + printf("No response - resetting\n"); + reset_chip(); + return; + } + printf("No response - knocking again\n"); + knocks++; + } + } + + printf("Boot starting\n"); + + // Get partition location in flash + const int buf_words = (16 * 4) + 1; // maximum of 16 partitions, each with maximum of 4 words returned, plus 1 + uint32_t* buffer = malloc(buf_words * 4); + + int ret = rom_get_partition_table_info(buffer, buf_words, PT_INFO_PARTITION_LOCATION_AND_FLAGS | PT_INFO_SINGLE_PARTITION | (0 << 24)); + assert(buffer[0] == (PT_INFO_PARTITION_LOCATION_AND_FLAGS | PT_INFO_SINGLE_PARTITION)); + assert(ret == 3); + + uint32_t location_and_permissions = buffer[1]; + uint32_t saddr = XIP_BASE + ((location_and_permissions >> PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_LSB) & 0x1fffu) * FLASH_SECTOR_SIZE; + uint32_t eaddr = XIP_BASE + (((location_and_permissions >> PICOBIN_PARTITION_LOCATION_LAST_SECTOR_LSB) & 0x1fffu) + 1) * FLASH_SECTOR_SIZE; + printf("Start %08x, end %08x\n", saddr, eaddr); + + free(buffer); + + printf("Writing binary\n"); + uint32_t tstart = time_us_32(); + uint32_t caddr = saddr; + while (caddr < eaddr) { + uart_putc_raw(UART_ID, 'w'); + char *buf = (char*)caddr; + for (int i=0; i < 32; i++) { + uart_putc_raw(UART_ID, buf[i]); + } + if (!uart_is_readable_within_us(UART_ID, 500)) { + // Detect hangs and reset the chip + printf("Write has hung - resetting\n"); + reset_chip(); + return; + } + char in = uart_getc(UART_ID); + printf("%c\n", in); + caddr += 32; + } + + uint32_t tend = time_us_32(); + printf("Write took %dus\n", tend - tstart); + printf("Write complete - executing\n"); + uart_putc_raw(UART_ID, 'x'); + if (!uart_is_readable_within_us(UART_ID, 500)) { + // Detect hangs and reset the chip + printf("Execute has hung - resetting\n"); + reset_chip(); + return; + } + char in = uart_getc(UART_ID); + printf("%c\n", in); +} + + +int main() +{ + stdio_init_all(); + + // Set up our UART for booting the other device + uart_init(UART_ID, BAUD_RATE); + gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART); + gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART); + + // Set up run pin + gpio_init(RUN_PIN); + gpio_set_dir(RUN_PIN, GPIO_OUT); + + // Reset chip + reset_chip(); + + + while (true) { + char splash[] = "RP2350"; + char hello[] = "Hello"; + char buf[500] = {0}; + int i = 0; + while (uart_is_readable(UART_ID) && i < sizeof(buf)) { + char in = uart_getc(UART_ID); + printf("%c", in); + buf[i] = in; + i++; + } + if (i > 0) { + printf(" ...Read done\n"); + } + char *ptr = memchr(buf, 'R', sizeof(buf)); + if (ptr && strncmp(ptr, splash, sizeof(splash) - 1) == 0) { + printf("Splash found\n"); + uart_boot(); + } else { + ptr = memchr(buf, 'H', sizeof(buf)); + if (ptr && strncmp(ptr, hello, sizeof(hello) - 1) == 0) { + printf("Device is running\n"); + } else { + printf("Device not running - attempting reset\n"); + reset_chip(); + } + } + sleep_ms(1000); + } +} From d323d12fdf9cf7169df208e366468b3a7e46c3ff Mon Sep 17 00:00:00 2001 From: William Vinnicombe Date: Thu, 14 Nov 2024 17:10:56 +0000 Subject: [PATCH 2/5] Hardcode baud rate, as it's fixed by the bootrom --- bootloaders/uart/uart_boot.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bootloaders/uart/uart_boot.c b/bootloaders/uart/uart_boot.c index caeed6113..186bddd9f 100644 --- a/bootloaders/uart/uart_boot.c +++ b/bootloaders/uart/uart_boot.c @@ -8,7 +8,6 @@ // UART defines for uart boot #define UART_ID uart1 -#define BAUD_RATE 1000000 // Use pins 4 and 5 for uart boot #define UART_TX_PIN 4 @@ -108,7 +107,7 @@ int main() stdio_init_all(); // Set up our UART for booting the other device - uart_init(UART_ID, BAUD_RATE); + uart_init(UART_ID, 1000000); gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART); gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART); From a30a1406a2bfa7d8e8bd34bec790abf35155da57 Mon Sep 17 00:00:00 2001 From: William Vinnicombe Date: Fri, 15 Nov 2024 13:29:10 +0000 Subject: [PATCH 3/5] Tidy up variable names, add assets on correct chars, and improve documentation --- README.md | 3 +++ bootloaders/uart/CMakeLists.txt | 6 ++++++ bootloaders/uart/uart_boot.c | 27 +++++++++++++++------------ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index cbdfaff48..282944f55 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,9 @@ App|Description [hello_anything](binary_info/hello_anything) | Uses `bi_ptr` variables to create a configurable hello_world binary - see the separate [README](binary_info/README.md) for more details ### Bootloaders (RP235x Only) + +These examples all produce multiple UF2s - a bootloader UF2, and then a separate UF2 of the program that the bootloader will load and boot. To load them onto a device with empty flash, first load the bootloader UF2, then reset to BOOTSEL mode and load the program UF2. This ordering is required because the bootloaders contain embedded partition tables - see section 5.10.6 in the RP2350 datasheet for more details on those. + App|Description ---|--- [enc_bootloader](bootloaders/encrypted) | A bootloader which decrypts binaries from flash into SRAM. See the separate [README](bootloaders/encrypted/README.md) for more information diff --git a/bootloaders/uart/CMakeLists.txt b/bootloaders/uart/CMakeLists.txt index cc0e38caa..6c9561f12 100644 --- a/bootloaders/uart/CMakeLists.txt +++ b/bootloaders/uart/CMakeLists.txt @@ -5,7 +5,10 @@ add_executable(uart_boot # pull in common dependencies target_link_libraries(uart_boot pico_stdlib hardware_flash) +# add partition table pico_embed_pt_in_binary(uart_boot ${CMAKE_CURRENT_LIST_DIR}/uart-pt.json) + +# create absolute UF2 pico_set_uf2_family(uart_boot "absolute") # create map/bin/hex file etc. @@ -15,6 +18,7 @@ pico_add_extra_outputs(uart_boot) example_auto_set_url(uart_boot) +# Create separate binary to be loaded onto other device add_executable(uart_binary uart_binary.c ) @@ -23,6 +27,8 @@ add_executable(uart_binary target_link_libraries(uart_binary pico_stdlib) pico_set_binary_type(uart_binary no_flash) + +# package uf2 in flash pico_package_uf2_output(uart_binary 0x10000000) # create map/bin/hex/uf2 file etc. diff --git a/bootloaders/uart/uart_boot.c b/bootloaders/uart/uart_boot.c index 186bddd9f..97c72e95f 100644 --- a/bootloaders/uart/uart_boot.c +++ b/bootloaders/uart/uart_boot.c @@ -37,6 +37,7 @@ void uart_boot() { if (uart_is_readable_within_us(UART_ID, 1000)) { char in = uart_getc(UART_ID); + assert(in == 'n'); printf("%c\n", in); break; } else { @@ -61,18 +62,18 @@ void uart_boot() { assert(ret == 3); uint32_t location_and_permissions = buffer[1]; - uint32_t saddr = XIP_BASE + ((location_and_permissions >> PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_LSB) & 0x1fffu) * FLASH_SECTOR_SIZE; - uint32_t eaddr = XIP_BASE + (((location_and_permissions >> PICOBIN_PARTITION_LOCATION_LAST_SECTOR_LSB) & 0x1fffu) + 1) * FLASH_SECTOR_SIZE; - printf("Start %08x, end %08x\n", saddr, eaddr); + uint32_t start_addr = XIP_BASE + ((location_and_permissions & PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_BITS) >> PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_LSB) * FLASH_SECTOR_SIZE; + uint32_t end_addr = XIP_BASE + (((location_and_permissions & PICOBIN_PARTITION_LOCATION_LAST_SECTOR_BITS) >> PICOBIN_PARTITION_LOCATION_LAST_SECTOR_LSB) + 1) * FLASH_SECTOR_SIZE; + printf("Start %08x, end %08x\n", start_addr, end_addr); free(buffer); printf("Writing binary\n"); - uint32_t tstart = time_us_32(); - uint32_t caddr = saddr; - while (caddr < eaddr) { + uint32_t time_start = time_us_32(); + uint32_t current_addr = start_addr; + while (current_addr < end_addr) { uart_putc_raw(UART_ID, 'w'); - char *buf = (char*)caddr; + char *buf = (char*)current_addr; for (int i=0; i < 32; i++) { uart_putc_raw(UART_ID, buf[i]); } @@ -84,11 +85,12 @@ void uart_boot() { } char in = uart_getc(UART_ID); printf("%c\n", in); - caddr += 32; + assert(in == 'w'); + current_addr += 32; } - uint32_t tend = time_us_32(); - printf("Write took %dus\n", tend - tstart); + uint32_t time_end = time_us_32(); + printf("Write took %dus\n", time_end - time_start); printf("Write complete - executing\n"); uart_putc_raw(UART_ID, 'x'); if (!uart_is_readable_within_us(UART_ID, 500)) { @@ -99,6 +101,7 @@ void uart_boot() { } char in = uart_getc(UART_ID); printf("%c\n", in); + assert(in == 'x'); } @@ -133,12 +136,12 @@ int main() if (i > 0) { printf(" ...Read done\n"); } - char *ptr = memchr(buf, 'R', sizeof(buf)); + char *ptr = memchr(buf, splash[0], sizeof(buf)); if (ptr && strncmp(ptr, splash, sizeof(splash) - 1) == 0) { printf("Splash found\n"); uart_boot(); } else { - ptr = memchr(buf, 'H', sizeof(buf)); + ptr = memchr(buf, hello[0], sizeof(buf)); if (ptr && strncmp(ptr, hello, sizeof(hello) - 1) == 0) { printf("Device is running\n"); } else { From d1819cc8ef3c9984c6d73812a94b86f7fbcf91b0 Mon Sep 17 00:00:00 2001 From: William Vinnicombe Date: Fri, 15 Nov 2024 14:25:11 +0000 Subject: [PATCH 4/5] Add verify, support Risc-V, and replace asserts on returned chars with reset_chips --- bootloaders/uart/uart-pt.json | 4 +- bootloaders/uart/uart_boot.c | 82 +++++++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/bootloaders/uart/uart-pt.json b/bootloaders/uart/uart-pt.json index d6ce01299..c42cc1098 100644 --- a/bootloaders/uart/uart-pt.json +++ b/bootloaders/uart/uart-pt.json @@ -12,7 +12,7 @@ { "start": "128K", "size": "32K", - "families": ["rp2350-arm-s"], + "families": ["rp2350-arm-s", "rp2350-riscv"], "permissions": { "secure": "rw", "nonsecure": "rw", @@ -20,4 +20,4 @@ } } ] -} \ No newline at end of file +} diff --git a/bootloaders/uart/uart_boot.c b/bootloaders/uart/uart_boot.c index 97c72e95f..120f712de 100644 --- a/bootloaders/uart/uart_boot.c +++ b/bootloaders/uart/uart_boot.c @@ -27,6 +27,7 @@ void reset_chip() { void uart_boot() { uint knocks = 0; + char in = 0; while (true) { // Send the knock sequence uart_putc_raw(UART_ID, 0x56); @@ -36,13 +37,17 @@ void uart_boot() { uart_putc_raw(UART_ID, 'n'); if (uart_is_readable_within_us(UART_ID, 1000)) { - char in = uart_getc(UART_ID); - assert(in == 'n'); + in = uart_getc(UART_ID); + if (in != 'n') { + printf("Incorrect response - resetting\n"); + reset_chip(); + return; + } printf("%c\n", in); break; } else { if (knocks > 10) { - printf("No response - resetting\n"); + printf("No response - resetting\n"); reset_chip(); return; } @@ -83,15 +88,70 @@ void uart_boot() { reset_chip(); return; } - char in = uart_getc(UART_ID); - printf("%c\n", in); - assert(in == 'w'); + in = uart_getc(UART_ID); + if (in != 'w') { + printf("Incorrect response - resetting\n"); + reset_chip(); + return; + } current_addr += 32; } uint32_t time_end = time_us_32(); printf("Write took %dus\n", time_end - time_start); - printf("Write complete - executing\n"); + printf("Write complete - resetting pointer\n"); + + uart_putc_raw(UART_ID, 'c'); + if (!uart_is_readable_within_us(UART_ID, 500)) { + // Detect hangs and reset the chip + printf("Clear has hung - resetting\n"); + reset_chip(); + return; + } + in = uart_getc(UART_ID); + printf("%c\n", in); + if (in != 'c') { + printf("Incorrect response - resetting\n"); + reset_chip(); + return; + } + + printf("Verifying binary\n"); + time_start = time_us_32(); + current_addr = start_addr; + while (current_addr < end_addr) { + uart_putc_raw(UART_ID, 'r'); + char *buf = (char*)current_addr; + if (!uart_is_readable_within_us(UART_ID, 500)) { + // Detect hangs and reset the chip + printf("Verify has hung - resetting\n"); + reset_chip(); + return; + } + int i = 0; + while (uart_is_readable_within_us(UART_ID, 10) && i < 32) { + in = uart_getc(UART_ID); + if (in != buf[i]) { + printf("Verify has incorrect data at 0x%08x - resetting\n", current_addr - start_addr + SRAM_BASE); + } + i++; + } + if (i != 32) { + printf("Verify has incorrect data size - resetting\n"); + } + in = uart_getc(UART_ID); + if (in != 'r') { + printf("Incorrect response - resetting\n"); + reset_chip(); + return; + } + current_addr += 32; + } + + time_end = time_us_32(); + printf("Verify took %dus\n", time_end - time_start); + printf("Verify complete - executing\n"); + uart_putc_raw(UART_ID, 'x'); if (!uart_is_readable_within_us(UART_ID, 500)) { // Detect hangs and reset the chip @@ -99,9 +159,13 @@ void uart_boot() { reset_chip(); return; } - char in = uart_getc(UART_ID); + in = uart_getc(UART_ID); printf("%c\n", in); - assert(in == 'x'); + if (in != 'x') { + printf("Incorrect response - resetting\n"); + reset_chip(); + return; + } } From d7170caf56b529c4c048dfb786a8b149694883a6 Mon Sep 17 00:00:00 2001 From: William Vinnicombe Date: Tue, 15 Apr 2025 18:03:39 +0100 Subject: [PATCH 5/5] Add note as to why bootloader should be absolute --- bootloaders/uart/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloaders/uart/CMakeLists.txt b/bootloaders/uart/CMakeLists.txt index 6c9561f12..d14109b57 100644 --- a/bootloaders/uart/CMakeLists.txt +++ b/bootloaders/uart/CMakeLists.txt @@ -8,7 +8,7 @@ target_link_libraries(uart_boot pico_stdlib hardware_flash) # add partition table pico_embed_pt_in_binary(uart_boot ${CMAKE_CURRENT_LIST_DIR}/uart-pt.json) -# create absolute UF2 +# create absolute UF2, as it's a bootloader so shouldn't go in a partition pico_set_uf2_family(uart_boot "absolute") # create map/bin/hex file etc.