mirror of
https://github.com/Fishwaldo/pico-pic.git
synced 2025-03-15 19:41:53 +00:00
For the pic branch of github/Fishwaldo/pico-sdk/
This commit is contained in:
commit
902f03c761
20 changed files with 2203 additions and 0 deletions
18
.vscode/c_cpp_properties.json
vendored
Normal file
18
.vscode/c_cpp_properties.json
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/**",
|
||||||
|
"/home/fish/pico-sdk/**"
|
||||||
|
],
|
||||||
|
"defines": [],
|
||||||
|
"compilerPath": "/usr/bin/gcc",
|
||||||
|
"cStandard": "gnu17",
|
||||||
|
"cppStandard": "gnu++14",
|
||||||
|
"intelliSenseMode": "linux-gcc-x64",
|
||||||
|
"configurationProvider": "ms-vscode.cmake-tools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
30
.vscode/launch.json
vendored
Normal file
30
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{ "name": "Pico Debug",
|
||||||
|
"showDevDebugOutput": "false",
|
||||||
|
"device": "RP2040",
|
||||||
|
"gdbPath": "gdb-multiarch",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"executable": "${workspaceRoot}/../build/blink_combined.elf",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "cortex-debug",
|
||||||
|
"servertype": "openocd",
|
||||||
|
"configFiles": [
|
||||||
|
"interface/jlink.cfg",
|
||||||
|
"target/rp2040.cfg"
|
||||||
|
],
|
||||||
|
"openOCDLaunchCommands": [
|
||||||
|
"transport select swd",
|
||||||
|
"adapter speed 4000"
|
||||||
|
],
|
||||||
|
"svdFile": "/home/fish/pico-sdk/src/rp2040/hardware_regs/rp2040.svd",
|
||||||
|
//"svdFile": "/home/fish/pico-sdk/src/rp2040/hardware_regs/rp2040.svd",
|
||||||
|
"breakAfterReset": false,
|
||||||
|
"postRestartCommands": [
|
||||||
|
"break main",
|
||||||
|
"continue"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
23
.vscode/settings.json
vendored
Normal file
23
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
// These settings tweaks to the cmake plugin will ensure
|
||||||
|
// that you debug using cortex-debug instead of trying to launch
|
||||||
|
// a Pico binary on the host
|
||||||
|
"cmake.statusbar.advanced": {
|
||||||
|
"debug": {
|
||||||
|
"visibility": "hidden"
|
||||||
|
},
|
||||||
|
"launch": {
|
||||||
|
"visibility": "hidden"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"visibility": "hidden"
|
||||||
|
},
|
||||||
|
"buildTarget": {
|
||||||
|
"visibility": "hidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cmake.buildBeforeRun": true,
|
||||||
|
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
|
||||||
|
"cortex-debug.variableUseNaturalFormat": false,
|
||||||
|
"cortex-debug.registerUseNaturalFormat": false
|
||||||
|
}
|
38
CMakeLists.txt
Normal file
38
CMakeLists.txt
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
# initialize the SDK based on PICO_SDK_PATH
|
||||||
|
# note: this must happen before project()
|
||||||
|
include(pico_sdk_import.cmake)
|
||||||
|
# initialize the Raspberry Pi Pico SDK
|
||||||
|
pico_sdk_init()
|
||||||
|
add_subdirectory(bootloader)
|
||||||
|
|
||||||
|
add_compile_options("-g")
|
||||||
|
add_compile_options("-O0")
|
||||||
|
add_compile_options("-Wall")
|
||||||
|
|
||||||
|
project(blink)
|
||||||
|
add_executable(blink
|
||||||
|
blink.c
|
||||||
|
image_info.c
|
||||||
|
)
|
||||||
|
set_target_properties(blink PROPERTIES COMPILE_FLAGS "-fpie -fno-plt -Wall -g -O0 -fpic -mpic-register=r9 -msingle-pic-base -mno-pic-data-is-text-relative")
|
||||||
|
target_link_libraries(blink "-fpie -fno-plt -Wall -g -O0 -fpic -mpic-register=r9 -msingle-pic-base -mno-pic-data-is-text-relative" pico_stdlib )
|
||||||
|
pico_enable_stdio_usb(blink 1)
|
||||||
|
pico_enable_stdio_uart(blink 0)
|
||||||
|
bootloader_build_combined(blink)
|
||||||
|
pico_add_extra_outputs(blink)
|
||||||
|
|
||||||
|
project(blink-standalone)
|
||||||
|
add_executable(blink-standalone
|
||||||
|
blink.c
|
||||||
|
image_info.c
|
||||||
|
)
|
||||||
|
set_target_properties(blink-standalone PROPERTIES COMPILE_FLAGS "-fpie -fno-plt -Wall -g -O0 -fpic -mpic-register=r9 -msingle-pic-base -mno-pic-data-is-text-relative")
|
||||||
|
target_link_libraries(blink-standalone pico_stdlib)
|
||||||
|
target_link_options(blink-standalone PUBLIC -fpie -fno-plt -Wall -g -O0 -fpic -mpic-register=r9 -msingle-pic-base -mno-pic-data-is-text-relative)
|
||||||
|
pico_enable_stdio_usb(blink-standalone 1)
|
||||||
|
pico_enable_stdio_uart(blink-standalone 0)
|
||||||
|
bootloader_build_standalone(blink-standalone)
|
||||||
|
pico_add_extra_outputs(blink-standalone)
|
||||||
|
|
35
blink.c
Normal file
35
blink.c
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "image_info.h"
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
|
||||||
|
|
||||||
|
void blink_fn() {
|
||||||
|
const uint LED_PIN = PICO_DEFAULT_LED_PIN;
|
||||||
|
gpio_init(LED_PIN);
|
||||||
|
gpio_set_dir(LED_PIN, GPIO_OUT);
|
||||||
|
while (true) {
|
||||||
|
gpio_put(LED_PIN, 1);
|
||||||
|
sleep_ms(250);
|
||||||
|
gpio_put(LED_PIN, 0);
|
||||||
|
sleep_ms(250);
|
||||||
|
printf(".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
#ifndef PICO_DEFAULT_LED_PIN
|
||||||
|
#warning blink ecorexample requires a board with a regular LED
|
||||||
|
#else
|
||||||
|
stdio_init_all();
|
||||||
|
typedef void (*blink_t)(void);
|
||||||
|
blink_t blinkptr = blink_fn;
|
||||||
|
blinkptr();
|
||||||
|
#endif
|
||||||
|
}
|
18
bootloader/.vscode/c_cpp_properties.json
vendored
Normal file
18
bootloader/.vscode/c_cpp_properties.json
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/**",
|
||||||
|
"/home/fish/pico-sdk/**"
|
||||||
|
],
|
||||||
|
"defines": [],
|
||||||
|
"compilerPath": "/usr/bin/gcc",
|
||||||
|
"cStandard": "gnu17",
|
||||||
|
"cppStandard": "gnu++14",
|
||||||
|
"intelliSenseMode": "linux-gcc-x64",
|
||||||
|
"configurationProvider": "ms-vscode.cmake-tools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
30
bootloader/.vscode/launch.json
vendored
Normal file
30
bootloader/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{ "name": "Pico Debug",
|
||||||
|
"showDevDebugOutput": "raw",
|
||||||
|
"device": "RP2040",
|
||||||
|
"gdbPath": "gdb-multiarch",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"executable": "${workspaceRoot}/bootloader.elf",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "cortex-debug",
|
||||||
|
"servertype": "openocd",
|
||||||
|
"configFiles": [
|
||||||
|
"interface/jlink.cfg",
|
||||||
|
"target/rp2040.cfg"
|
||||||
|
],
|
||||||
|
"openOCDLaunchCommands": [
|
||||||
|
"transport select swd",
|
||||||
|
"adapter speed 4000"
|
||||||
|
],
|
||||||
|
"svdFile": "/home/fish/pico-sdk/src/rp2040/hardware_regs/rp2040.svd",
|
||||||
|
//"svdFile": "/home/fish/pico-sdk/src/rp2040/hardware_regs/rp2040.svd",
|
||||||
|
//"runToMain": true,
|
||||||
|
"postRestartCommands": [
|
||||||
|
"break main",
|
||||||
|
"continue"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
24
bootloader/.vscode/settings.json
vendored
Normal file
24
bootloader/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
// These settings tweaks to the cmake plugin will ensure
|
||||||
|
// that you debug using cortex-debug instead of trying to launch
|
||||||
|
// a Pico binary on the host
|
||||||
|
"cmake.statusbar.advanced": {
|
||||||
|
"debug": {
|
||||||
|
"visibility": "hidden"
|
||||||
|
},
|
||||||
|
"launch": {
|
||||||
|
"visibility": "hidden"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"visibility": "hidden"
|
||||||
|
},
|
||||||
|
"buildTarget": {
|
||||||
|
"visibility": "hidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cmake.buildBeforeRun": true,
|
||||||
|
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
|
||||||
|
"cortex-debug.variableUseNaturalFormat": false,
|
||||||
|
"cortex-debug.showRTOS": true,
|
||||||
|
"cortex-debug.registerUseNaturalFormat": false
|
||||||
|
}
|
147
bootloader/CMakeLists.txt
Normal file
147
bootloader/CMakeLists.txt
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
# Derived from the Pico SDK, which carries the following
|
||||||
|
# LICENSE.txt:
|
||||||
|
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||||
|
# following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
include(pico_sdk_import.cmake)
|
||||||
|
|
||||||
|
project(test_project C CXX ASM)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
pico_sdk_init()
|
||||||
|
|
||||||
|
# Build the bootloader as a standalone thing
|
||||||
|
|
||||||
|
add_executable(bootloader main.c)
|
||||||
|
|
||||||
|
function(target_cl_options option)
|
||||||
|
target_compile_options(bootloader PRIVATE ${option})
|
||||||
|
target_link_options(bootloader PRIVATE ${option})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
add_compile_options(-g -O0)
|
||||||
|
|
||||||
|
target_cl_options("-O0")
|
||||||
|
target_cl_options("-g")
|
||||||
|
target_cl_options("-ffunction-sections")
|
||||||
|
target_cl_options("-fdata-sections")
|
||||||
|
target_link_options(bootloader PRIVATE "LINKER:--gc-sections")
|
||||||
|
|
||||||
|
pico_add_extra_outputs(bootloader)
|
||||||
|
pico_set_binary_type(bootloader copy_to_ram)
|
||||||
|
|
||||||
|
set_target_properties(bootloader PROPERTIES COMPILE_FLAGS "-Wall -g -O0")
|
||||||
|
|
||||||
|
pico_set_linker_script(bootloader ${CMAKE_CURRENT_SOURCE_DIR}/bootloader.ld)
|
||||||
|
|
||||||
|
target_link_libraries(bootloader
|
||||||
|
pico_stdlib
|
||||||
|
hardware_dma
|
||||||
|
hardware_flash
|
||||||
|
hardware_structs
|
||||||
|
hardware_resets
|
||||||
|
cmsis_core)
|
||||||
|
|
||||||
|
set(BOOTLOADER_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "")
|
||||||
|
|
||||||
|
#Build a library to embed into applications
|
||||||
|
|
||||||
|
function(bootloader_define_library)
|
||||||
|
set(NAME bootloader)
|
||||||
|
set(ORIGINAL_BIN ${CMAKE_CURRENT_BINARY_DIR}/${NAME}.bin)
|
||||||
|
set(BIN_ASM ${CMAKE_CURRENT_BINARY_DIR}/${NAME}_bin.S)
|
||||||
|
|
||||||
|
add_custom_target(${NAME}_bin DEPENDS ${ORIGINAL_BIN})
|
||||||
|
add_custom_command(OUTPUT ${ORIGINAL_BIN} DEPENDS ${NAME} COMMAND ${CMAKE_OBJCOPY} -Obinary $<TARGET_FILE:${NAME}> ${ORIGINAL_BIN})
|
||||||
|
|
||||||
|
find_package (Python3 REQUIRED COMPONENTS Interpreter)
|
||||||
|
add_custom_target(${NAME}_bin_asm DEPENDS ${BIN_ASM})
|
||||||
|
add_custom_command(OUTPUT ${BIN_ASM} DEPENDS ${ORIGINAL_BIN}
|
||||||
|
COMMAND ${Python3_EXECUTABLE} ${BOOTLOADER_DIR}/mkasm.py ${ORIGINAL_BIN} ${BIN_ASM}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(${NAME}_library INTERFACE)
|
||||||
|
add_dependencies(${NAME}_library ${NAME}_bin_asm)
|
||||||
|
# not strictly (or indeed actually) a link library, but this avoids dependency cycle
|
||||||
|
target_link_libraries(${NAME}_library INTERFACE ${BIN_ASM})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
bootloader_define_library()
|
||||||
|
|
||||||
|
# Provide a helper to build a combined target
|
||||||
|
|
||||||
|
function(bootloader_build_combined NAME)
|
||||||
|
set(APP ${NAME}_app)
|
||||||
|
set(APP_BIN ${CMAKE_CURRENT_BINARY_DIR}/${APP}.bin)
|
||||||
|
set(APP_HDR ${CMAKE_CURRENT_BINARY_DIR}/${APP}_hdr.bin)
|
||||||
|
|
||||||
|
set(COMBINED ${NAME}_combined)
|
||||||
|
|
||||||
|
target_link_libraries(${NAME} bootloader_library)
|
||||||
|
add_compile_options(-g -O0)
|
||||||
|
|
||||||
|
target_cl_options("-O0")
|
||||||
|
target_cl_options("-g")
|
||||||
|
|
||||||
|
pico_set_linker_script(${NAME} ${BOOTLOADER_DIR}/combined.ld)
|
||||||
|
|
||||||
|
pico_add_bin_output(${NAME})
|
||||||
|
|
||||||
|
# TODO: The hard-coded 16k here is a bit nasty
|
||||||
|
add_custom_target(${APP}_bin DEPENDS ${APP_BIN})
|
||||||
|
add_custom_command(OUTPUT ${APP_BIN} DEPENDS ${NAME}.bin
|
||||||
|
COMMAND dd ibs=1k seek=36 if=${NAME}.bin of=${APP_BIN}
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: The hard-coded address here is a bit nasty
|
||||||
|
add_custom_target(${APP}_hdr DEPENDS ${APP}_bin)
|
||||||
|
add_custom_command(OUTPUT ${APP_HDR} DEPENDS ${APP_BIN}
|
||||||
|
#COMMAND ${BOOTLOADER_DIR}/gen_imghdr.py -a 0x1000a000 ${APP_BIN} ${APP_HDR}
|
||||||
|
COMMAND ${BOOTLOADER_DIR}/gen_imghdr.py -a 0x1000b000 ${APP_BIN} ${APP_HDR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(${COMBINED} ALL DEPENDS ${APP_HDR})
|
||||||
|
add_custom_command(TARGET ${COMBINED} DEPENDS ${APP_HDR}
|
||||||
|
COMMAND ${CMAKE_OBJCOPY} --update-section .app_hdr=${APP_HDR} ${NAME}.elf ${COMBINED}.elf
|
||||||
|
)
|
||||||
|
add_custom_command(TARGET ${COMBINED} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_OBJCOPY} -Obinary ${COMBINED}.elf ${COMBINED}.bin
|
||||||
|
)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Provide a helper to build a standalone target
|
||||||
|
|
||||||
|
function(bootloader_build_standalone NAME)
|
||||||
|
pico_set_linker_script(${NAME} ${BOOTLOADER_DIR}/standalone.ld)
|
||||||
|
pico_add_bin_output(${NAME})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# project(blink)
|
||||||
|
# add_executable(blink blink.c)
|
||||||
|
# target_link_libraries(blink pico_stdlib)
|
||||||
|
# set_target_properties(blink PROPERTIES COMPILE_FLAGS "-Wall -g -O0")
|
||||||
|
# pico_add_extra_outputs(blink)
|
||||||
|
# bootloader_build_combined(blink)
|
254
bootloader/bootloader.ld
Normal file
254
bootloader/bootloader.ld
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
/* Based on GCC ARM embedded samples.
|
||||||
|
Defines the following symbols for use by code:
|
||||||
|
__exidx_start
|
||||||
|
__exidx_end
|
||||||
|
__etext
|
||||||
|
__data_start__
|
||||||
|
__preinit_array_start
|
||||||
|
__preinit_array_end
|
||||||
|
__init_array_start
|
||||||
|
__init_array_end
|
||||||
|
__fini_array_start
|
||||||
|
__fini_array_end
|
||||||
|
__data_end__
|
||||||
|
__bss_start__
|
||||||
|
__bss_end__
|
||||||
|
__end__
|
||||||
|
end
|
||||||
|
__HeapLimit
|
||||||
|
__StackLimit
|
||||||
|
__StackTop
|
||||||
|
__stack (== StackTop)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Limit flash to 12k, so we can use 12-16k for the image header */
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 36k
|
||||||
|
RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k
|
||||||
|
SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
|
||||||
|
SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
|
||||||
|
}
|
||||||
|
|
||||||
|
ENTRY(_entry_point)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
/* Second stage bootloader is prepended to the image. It must be 256 bytes big
|
||||||
|
and checksummed. It is usually built by the boot_stage2 target
|
||||||
|
in the Raspberry Pi Pico SDK
|
||||||
|
*/
|
||||||
|
|
||||||
|
.flash_begin : {
|
||||||
|
__flash_binary_start = .;
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
.boot2 : {
|
||||||
|
__boot2_start__ = .;
|
||||||
|
KEEP (*(.boot2))
|
||||||
|
__boot2_end__ = .;
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
ASSERT(__boot2_end__ - __boot2_start__ == 256,
|
||||||
|
"ERROR: Pico second stage bootloader must be 256 bytes in size")
|
||||||
|
|
||||||
|
/* The second stage will always enter the image at the start of .text.
|
||||||
|
The debugger will use the ELF entry point, which is the _entry_point
|
||||||
|
symbol if present, otherwise defaults to start of .text.
|
||||||
|
This can be used to transfer control back to the bootrom on debugger
|
||||||
|
launches only, to perform proper flash setup.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.flashtext : {
|
||||||
|
__logical_binary_start = .;
|
||||||
|
KEEP (*(.vectors))
|
||||||
|
KEEP (*(.binary_info_header))
|
||||||
|
__binary_info_header_end = .;
|
||||||
|
KEEP (*(.reset))
|
||||||
|
}
|
||||||
|
|
||||||
|
.rodata : {
|
||||||
|
/* segments not marked as .flashdata are instead pulled into .data (in RAM) to avoid accidental flash accesses */
|
||||||
|
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
|
||||||
|
. = ALIGN(4);
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
.ARM.extab :
|
||||||
|
{
|
||||||
|
*(.ARM.extab* .gnu.linkonce.armextab.*)
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
__exidx_start = .;
|
||||||
|
.ARM.exidx :
|
||||||
|
{
|
||||||
|
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
|
||||||
|
} > FLASH
|
||||||
|
__exidx_end = .;
|
||||||
|
|
||||||
|
/* Machine inspectable binary information */
|
||||||
|
. = ALIGN(4);
|
||||||
|
__binary_info_start = .;
|
||||||
|
.binary_info :
|
||||||
|
{
|
||||||
|
KEEP(*(.binary_info.keep.*))
|
||||||
|
*(.binary_info.*)
|
||||||
|
} > FLASH
|
||||||
|
__binary_info_end = .;
|
||||||
|
. = ALIGN(4);
|
||||||
|
|
||||||
|
/* Vector table goes first in RAM, to avoid large alignment hole */
|
||||||
|
.ram_vector_table (COPY): {
|
||||||
|
*(.ram_vector_table)
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
.text : {
|
||||||
|
__ram_text_start__ = .;
|
||||||
|
*(.init)
|
||||||
|
*(.text*)
|
||||||
|
*(.fini)
|
||||||
|
/* Pull all c'tors into .text */
|
||||||
|
*crtbegin.o(.ctors)
|
||||||
|
*crtbegin?.o(.ctors)
|
||||||
|
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
|
||||||
|
*(SORT(.ctors.*))
|
||||||
|
*(.ctors)
|
||||||
|
/* Followed by destructors */
|
||||||
|
*crtbegin.o(.dtors)
|
||||||
|
*crtbegin?.o(.dtors)
|
||||||
|
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
|
||||||
|
*(SORT(.dtors.*))
|
||||||
|
*(.dtors)
|
||||||
|
|
||||||
|
*(.eh_frame*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__ram_text_end__ = .;
|
||||||
|
} > RAM AT> FLASH
|
||||||
|
__ram_text_source__ = LOADADDR(.text);
|
||||||
|
|
||||||
|
|
||||||
|
.data : {
|
||||||
|
__data_start__ = .;
|
||||||
|
*(vtable)
|
||||||
|
|
||||||
|
*(.time_critical*)
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.rodata*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
|
||||||
|
*(.data*)
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.after_data.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* preinit data */
|
||||||
|
PROVIDE_HIDDEN (__mutex_array_start = .);
|
||||||
|
KEEP(*(SORT(.mutex_array.*)))
|
||||||
|
KEEP(*(.mutex_array))
|
||||||
|
PROVIDE_HIDDEN (__mutex_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* preinit data */
|
||||||
|
PROVIDE_HIDDEN (__preinit_array_start = .);
|
||||||
|
KEEP(*(SORT(.preinit_array.*)))
|
||||||
|
KEEP(*(.preinit_array))
|
||||||
|
PROVIDE_HIDDEN (__preinit_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* init data */
|
||||||
|
PROVIDE_HIDDEN (__init_array_start = .);
|
||||||
|
KEEP(*(SORT(.init_array.*)))
|
||||||
|
KEEP(*(.init_array))
|
||||||
|
PROVIDE_HIDDEN (__init_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* finit data */
|
||||||
|
PROVIDE_HIDDEN (__fini_array_start = .);
|
||||||
|
*(SORT(.fini_array.*))
|
||||||
|
*(.fini_array)
|
||||||
|
PROVIDE_HIDDEN (__fini_array_end = .);
|
||||||
|
|
||||||
|
*(.jcr)
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* All data end */
|
||||||
|
__data_end__ = .;
|
||||||
|
} > RAM AT> FLASH
|
||||||
|
/* __etext is the name of the .data init source pointer (...) */
|
||||||
|
__etext = LOADADDR(.data);
|
||||||
|
|
||||||
|
.uninitialized_data (COPY): {
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.uninitialized_data*)
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
/* Start and end symbols must be word-aligned */
|
||||||
|
.scratch_x : {
|
||||||
|
__scratch_x_start__ = .;
|
||||||
|
*(.scratch_x.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__scratch_x_end__ = .;
|
||||||
|
} > SCRATCH_X AT > FLASH
|
||||||
|
__scratch_x_source__ = LOADADDR(.scratch_x);
|
||||||
|
|
||||||
|
.scratch_y : {
|
||||||
|
__scratch_y_start__ = .;
|
||||||
|
*(.scratch_y.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__scratch_y_end__ = .;
|
||||||
|
} > SCRATCH_Y AT > FLASH
|
||||||
|
__scratch_y_source__ = LOADADDR(.scratch_y);
|
||||||
|
|
||||||
|
.bss : {
|
||||||
|
. = ALIGN(4);
|
||||||
|
__bss_start__ = .;
|
||||||
|
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
|
||||||
|
*(COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__bss_end__ = .;
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
.heap (COPY):
|
||||||
|
{
|
||||||
|
__end__ = .;
|
||||||
|
end = __end__;
|
||||||
|
*(.heap*)
|
||||||
|
__HeapLimit = .;
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
/* .stack*_dummy section doesn't contains any symbols. It is only
|
||||||
|
* used for linker to calculate size of stack sections, and assign
|
||||||
|
* values to stack symbols later
|
||||||
|
*
|
||||||
|
* stack1 section may be empty/missing if platform_launch_core1 is not used */
|
||||||
|
|
||||||
|
/* by default we put core 0 stack at the end of scratch Y, so that if core 1
|
||||||
|
* stack is not used then all of SCRATCH_X is free.
|
||||||
|
*/
|
||||||
|
.stack1_dummy (COPY):
|
||||||
|
{
|
||||||
|
*(.stack1*)
|
||||||
|
} > SCRATCH_X
|
||||||
|
.stack_dummy (COPY):
|
||||||
|
{
|
||||||
|
*(.stack*)
|
||||||
|
} > SCRATCH_Y
|
||||||
|
|
||||||
|
.flash_end : {
|
||||||
|
__flash_binary_end = .;
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
/* stack limit is poorly named, but historically is maximum heap ptr */
|
||||||
|
__StackLimit = ORIGIN(RAM) + LENGTH(RAM);
|
||||||
|
__StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
|
||||||
|
__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);
|
||||||
|
__StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
|
||||||
|
__StackBottom = __StackTop - SIZEOF(.stack_dummy);
|
||||||
|
PROVIDE(__stack = __StackTop);
|
||||||
|
|
||||||
|
/* Check if data + heap + stack exceeds RAM limit */
|
||||||
|
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")
|
||||||
|
|
||||||
|
ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary")
|
||||||
|
/* todo assert on extra code */
|
||||||
|
}
|
||||||
|
|
50
bootloader/cmake_install.cmake
Normal file
50
bootloader/cmake_install.cmake
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Install script for directory: /home/fish/rp2040-serial-bootloader
|
||||||
|
|
||||||
|
# Set the install prefix
|
||||||
|
if(NOT DEFINED CMAKE_INSTALL_PREFIX)
|
||||||
|
set(CMAKE_INSTALL_PREFIX "/usr/local")
|
||||||
|
endif()
|
||||||
|
string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
|
||||||
|
|
||||||
|
# Set the install configuration name.
|
||||||
|
if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
|
||||||
|
if(BUILD_TYPE)
|
||||||
|
string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
|
||||||
|
CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
|
||||||
|
else()
|
||||||
|
set(CMAKE_INSTALL_CONFIG_NAME "Release")
|
||||||
|
endif()
|
||||||
|
message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Set the component getting installed.
|
||||||
|
if(NOT CMAKE_INSTALL_COMPONENT)
|
||||||
|
if(COMPONENT)
|
||||||
|
message(STATUS "Install component: \"${COMPONENT}\"")
|
||||||
|
set(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
|
||||||
|
else()
|
||||||
|
set(CMAKE_INSTALL_COMPONENT)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Is this installation the result of a crosscompile?
|
||||||
|
if(NOT DEFINED CMAKE_CROSSCOMPILING)
|
||||||
|
set(CMAKE_CROSSCOMPILING "TRUE")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CMAKE_INSTALL_LOCAL_ONLY)
|
||||||
|
# Include the install script for each subdirectory.
|
||||||
|
include("/home/fish/rp2040-serial-bootloader/pico-sdk/cmake_install.cmake")
|
||||||
|
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_INSTALL_COMPONENT)
|
||||||
|
set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt")
|
||||||
|
else()
|
||||||
|
set(CMAKE_INSTALL_MANIFEST "install_manifest.txt")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT
|
||||||
|
"${CMAKE_INSTALL_MANIFEST_FILES}")
|
||||||
|
file(WRITE "/home/fish/rp2040-serial-bootloader/${CMAKE_INSTALL_MANIFEST}"
|
||||||
|
"${CMAKE_INSTALL_MANIFEST_CONTENT}")
|
260
bootloader/combined.ld
Normal file
260
bootloader/combined.ld
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
/* Based on GCC ARM embedded samples.
|
||||||
|
Defines the following symbols for use by code:
|
||||||
|
__exidx_start
|
||||||
|
__exidx_end
|
||||||
|
__etext
|
||||||
|
__data_start__
|
||||||
|
__preinit_array_start
|
||||||
|
__preinit_array_end
|
||||||
|
__init_array_start
|
||||||
|
__init_array_end
|
||||||
|
__fini_array_start
|
||||||
|
__fini_array_end
|
||||||
|
__data_end__
|
||||||
|
__bss_start__
|
||||||
|
__bss_end__
|
||||||
|
__end__
|
||||||
|
end
|
||||||
|
__HeapLimit
|
||||||
|
__StackLimit
|
||||||
|
__StackTop
|
||||||
|
__stack (== StackTop)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Skip 16kB at the start of flash, that's where our bootloader is */
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH_BL(rx) : ORIGIN = 0x10000000, LENGTH = 36k
|
||||||
|
FLASH_IMGHDR(rx) : ORIGIN = 0x10000000 + 36k, LENGTH = 4k
|
||||||
|
FLASH_APP(rx) : ORIGIN = 0x10000000 + 40k, LENGTH = 2048k - 40k
|
||||||
|
RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k
|
||||||
|
SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
|
||||||
|
SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
|
||||||
|
}
|
||||||
|
|
||||||
|
ENTRY(_entry_point)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
.flash_begin : {
|
||||||
|
__flash_binary_start = .;
|
||||||
|
} > FLASH_APP
|
||||||
|
|
||||||
|
/* Insert boot3, which is the combined boot2 + boot3 */
|
||||||
|
.boot3 : {
|
||||||
|
KEEP (*(.boot3))
|
||||||
|
} > FLASH_BL
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Name a section for the image header.
|
||||||
|
* The contents will get replaced post-build
|
||||||
|
*/
|
||||||
|
.app_hdr : {
|
||||||
|
LONG(0xdeaddead)
|
||||||
|
LONG(0)
|
||||||
|
LONG(0xdeaddead)
|
||||||
|
} > FLASH_IMGHDR
|
||||||
|
|
||||||
|
.text : {
|
||||||
|
__logical_binary_start = .;
|
||||||
|
KEEP (*(.vectors))
|
||||||
|
KEEP (*(.binary_info_header))
|
||||||
|
__binary_info_header_end = .;
|
||||||
|
KEEP (*(.reset))
|
||||||
|
/* TODO revisit this now memset/memcpy/float in ROM */
|
||||||
|
/* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from
|
||||||
|
* FLASH ... we will include any thing excluded here in .data below by default */
|
||||||
|
*(.init)
|
||||||
|
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*)
|
||||||
|
*(.fini)
|
||||||
|
/* Pull all c'tors into .text */
|
||||||
|
*crtbegin.o(.ctors)
|
||||||
|
*crtbegin?.o(.ctors)
|
||||||
|
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
|
||||||
|
*(SORT(.ctors.*))
|
||||||
|
*(.ctors)
|
||||||
|
/* Followed by destructors */
|
||||||
|
*crtbegin.o(.dtors)
|
||||||
|
*crtbegin?.o(.dtors)
|
||||||
|
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
|
||||||
|
*(SORT(.dtors.*))
|
||||||
|
*(.dtors)
|
||||||
|
|
||||||
|
*(.eh_frame*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
} > FLASH_APP
|
||||||
|
|
||||||
|
.rodata : {
|
||||||
|
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
|
||||||
|
. = ALIGN(4);
|
||||||
|
} > FLASH_APP
|
||||||
|
|
||||||
|
.ARM.extab :
|
||||||
|
{
|
||||||
|
*(.ARM.extab* .gnu.linkonce.armextab.*)
|
||||||
|
} > FLASH_APP
|
||||||
|
|
||||||
|
__exidx_start = .;
|
||||||
|
.ARM.exidx :
|
||||||
|
{
|
||||||
|
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
|
||||||
|
} > FLASH_APP
|
||||||
|
__exidx_end = .;
|
||||||
|
|
||||||
|
/* Machine inspectable binary information */
|
||||||
|
. = ALIGN(4);
|
||||||
|
__binary_info_start = .;
|
||||||
|
.binary_info :
|
||||||
|
{
|
||||||
|
KEEP(*(.binary_info.keep.*))
|
||||||
|
*(.binary_info.*)
|
||||||
|
} > FLASH_APP
|
||||||
|
__binary_info_end = .;
|
||||||
|
. = ALIGN(4);
|
||||||
|
|
||||||
|
/* End of .text-like segments */
|
||||||
|
__etext = .;
|
||||||
|
|
||||||
|
.ram_vector_table (COPY): {
|
||||||
|
*(.ram_vector_table)
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
.data : {
|
||||||
|
__data_start__ = .;
|
||||||
|
*(vtable)
|
||||||
|
|
||||||
|
*(.time_critical*)
|
||||||
|
|
||||||
|
/* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */
|
||||||
|
*(.text*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.rodata*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
|
||||||
|
*(.data*)
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.after_data.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* preinit data */
|
||||||
|
PROVIDE_HIDDEN (__mutex_array_start = .);
|
||||||
|
KEEP(*(SORT(.mutex_array.*)))
|
||||||
|
KEEP(*(.mutex_array))
|
||||||
|
PROVIDE_HIDDEN (__mutex_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* preinit data */
|
||||||
|
PROVIDE_HIDDEN (__preinit_array_start = .);
|
||||||
|
KEEP(*(SORT(.preinit_array.*)))
|
||||||
|
KEEP(*(.preinit_array))
|
||||||
|
PROVIDE_HIDDEN (__preinit_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* init data */
|
||||||
|
PROVIDE_HIDDEN (__init_array_start = .);
|
||||||
|
KEEP(*(SORT(.init_array.*)))
|
||||||
|
KEEP(*(.init_array))
|
||||||
|
PROVIDE_HIDDEN (__init_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* finit data */
|
||||||
|
PROVIDE_HIDDEN (__fini_array_start = .);
|
||||||
|
*(SORT(.fini_array.*))
|
||||||
|
*(.fini_array)
|
||||||
|
PROVIDE_HIDDEN (__fini_array_end = .);
|
||||||
|
|
||||||
|
*(.jcr)
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* All data end */
|
||||||
|
__data_end__ = .;
|
||||||
|
} > RAM AT> FLASH_APP
|
||||||
|
|
||||||
|
.uninitialized_data (COPY): {
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.uninitialized_data*)
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
/* Start and end symbols must be word-aligned */
|
||||||
|
.scratch_x : {
|
||||||
|
__scratch_x_start__ = .;
|
||||||
|
*(.scratch_x.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__scratch_x_end__ = .;
|
||||||
|
} > SCRATCH_X AT > FLASH_APP
|
||||||
|
__scratch_x_source__ = LOADADDR(.scratch_x);
|
||||||
|
|
||||||
|
.scratch_y : {
|
||||||
|
__scratch_y_start__ = .;
|
||||||
|
*(.scratch_y.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__scratch_y_end__ = .;
|
||||||
|
} > SCRATCH_Y AT > FLASH_APP
|
||||||
|
__scratch_y_source__ = LOADADDR(.scratch_y);
|
||||||
|
|
||||||
|
/* Global Offset Table is relocated to RAM. */
|
||||||
|
.got :
|
||||||
|
{
|
||||||
|
. = ALIGN(4);
|
||||||
|
__ram_global_offset_table_begin = . ;
|
||||||
|
*(.got)
|
||||||
|
__ram_global_offset_table_end = . ;
|
||||||
|
} >RAM AT> FLASH_APP
|
||||||
|
__flash_global_offset_table_begin = LOADADDR(.got) ;
|
||||||
|
__flash_global_offset_table_end = __flash_global_offset_table_begin + __ram_global_offset_table_end - __ram_global_offset_table_begin ;
|
||||||
|
|
||||||
|
.bss : {
|
||||||
|
. = ALIGN(4);
|
||||||
|
__bss_start__ = .;
|
||||||
|
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
|
||||||
|
*(COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__bss_end__ = .;
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
.heap (COPY):
|
||||||
|
{
|
||||||
|
__end__ = .;
|
||||||
|
end = __end__;
|
||||||
|
*(.heap*)
|
||||||
|
__HeapLimit = .;
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
/* .stack*_dummy section doesn't contains any symbols. It is only
|
||||||
|
* used for linker to calculate size of stack sections, and assign
|
||||||
|
* values to stack symbols later
|
||||||
|
*
|
||||||
|
* stack1 section may be empty/missing if platform_launch_core1 is not used */
|
||||||
|
|
||||||
|
/* by default we put core 0 stack at the end of scratch Y, so that if core 1
|
||||||
|
* stack is not used then all of SCRATCH_X is free.
|
||||||
|
*/
|
||||||
|
.stack1_dummy (COPY):
|
||||||
|
{
|
||||||
|
*(.stack1*)
|
||||||
|
} > SCRATCH_X
|
||||||
|
.stack_dummy (COPY):
|
||||||
|
{
|
||||||
|
*(.stack*)
|
||||||
|
} > SCRATCH_Y
|
||||||
|
|
||||||
|
.flash_end : {
|
||||||
|
__flash_binary_end = .;
|
||||||
|
} > FLASH_APP
|
||||||
|
|
||||||
|
/* stack limit is poorly named, but historically is maximum heap ptr */
|
||||||
|
__StackLimit = ORIGIN(RAM) + LENGTH(RAM);
|
||||||
|
__StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
|
||||||
|
__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);
|
||||||
|
__StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
|
||||||
|
__StackBottom = __StackTop - SIZEOF(.stack_dummy);
|
||||||
|
PROVIDE(__stack = __StackTop);
|
||||||
|
|
||||||
|
/* Check if data + heap + stack exceeds RAM limit */
|
||||||
|
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")
|
||||||
|
|
||||||
|
ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary")
|
||||||
|
/* todo assert on extra code */
|
||||||
|
}
|
||||||
|
|
59
bootloader/gen_imghdr.py
Executable file
59
bootloader/gen_imghdr.py
Executable file
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Derived from pad_checksum in the Pico SDK, which carries the following
|
||||||
|
# LICENSE.txt:
|
||||||
|
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||||
|
# following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import binascii
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def any_int(x):
|
||||||
|
try:
|
||||||
|
return int(x, 0)
|
||||||
|
except:
|
||||||
|
raise argparse.ArgumentTypeError("expected an integer, not '{!r}'".format(x))
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("ifile", help="Input application binary (binary)")
|
||||||
|
parser.add_argument("ofile", help="Output header file (binary)")
|
||||||
|
parser.add_argument("-a", "--addr", help="Load address of the application image",
|
||||||
|
type=any_int, default=0x1000a000)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
idata = open(args.ifile, "rb").read()
|
||||||
|
except:
|
||||||
|
sys.exit("Could not open input file '{}'".format(args.ifile))
|
||||||
|
|
||||||
|
vtor = args.addr
|
||||||
|
size = len(idata)
|
||||||
|
crc = binascii.crc32(idata)
|
||||||
|
|
||||||
|
odata = vtor.to_bytes(4, byteorder='little') + size.to_bytes(4, byteorder='little') + crc.to_bytes(4, byteorder='little')
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(args.ofile, "wb") as ofile:
|
||||||
|
ofile.write(odata)
|
||||||
|
except:
|
||||||
|
sys.exit("Could not open output file '{}'".format(args.ofile))
|
773
bootloader/main.c
Normal file
773
bootloader/main.c
Normal file
|
@ -0,0 +1,773 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2021 Brian Starkey <stark3y@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "RP2040.h"
|
||||||
|
#include "pico/time.h"
|
||||||
|
#include "hardware/dma.h"
|
||||||
|
#include "hardware/flash.h"
|
||||||
|
#include "hardware/structs/dma.h"
|
||||||
|
#include "hardware/structs/watchdog.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/resets.h"
|
||||||
|
#include "hardware/uart.h"
|
||||||
|
#include "hardware/watchdog.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "pico/stdio_usb.h"
|
||||||
|
#define DBG_PRINTF_INIT() stdio_usb_init()
|
||||||
|
#define DBG_PRINTF(...) printf(__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define DBG_PRINTF_INIT() { }
|
||||||
|
#define DBG_PRINTF(...) { }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The bootloader can be entered in three ways:
|
||||||
|
// - BOOTLOADER_ENTRY_PIN is low
|
||||||
|
// - Watchdog scratch[5] == BOOTLOADER_ENTRY_MAGIC && scratch[6] == ~BOOTLOADER_ENTRY_MAGIC
|
||||||
|
// - No valid image header
|
||||||
|
#define BOOTLOADER_ENTRY_PIN 15
|
||||||
|
#define BOOTLOADER_ENTRY_MAGIC 0xb105f00d
|
||||||
|
|
||||||
|
#define UART_TX_PIN 0
|
||||||
|
#define UART_RX_PIN 1
|
||||||
|
#define UART_BAUD 921600
|
||||||
|
|
||||||
|
#define CMD_SYNC (('S' << 0) | ('Y' << 8) | ('N' << 16) | ('C' << 24))
|
||||||
|
#define CMD_READ (('R' << 0) | ('E' << 8) | ('A' << 16) | ('D' << 24))
|
||||||
|
#define CMD_CSUM (('C' << 0) | ('S' << 8) | ('U' << 16) | ('M' << 24))
|
||||||
|
#define CMD_CRC (('C' << 0) | ('R' << 8) | ('C' << 16) | ('C' << 24))
|
||||||
|
#define CMD_ERASE (('E' << 0) | ('R' << 8) | ('A' << 16) | ('S' << 24))
|
||||||
|
#define CMD_WRITE (('W' << 0) | ('R' << 8) | ('I' << 16) | ('T' << 24))
|
||||||
|
#define CMD_SEAL (('S' << 0) | ('E' << 8) | ('A' << 16) | ('L' << 24))
|
||||||
|
#define CMD_GO (('G' << 0) | ('O' << 8) | ('G' << 16) | ('O' << 24))
|
||||||
|
#define CMD_INFO (('I' << 0) | ('N' << 8) | ('F' << 16) | ('O' << 24))
|
||||||
|
#define CMD_REBOOT (('B' << 0) | ('O' << 8) | ('O' << 16) | ('T' << 24))
|
||||||
|
|
||||||
|
#define RSP_SYNC (('P' << 0) | ('I' << 8) | ('C' << 16) | ('O' << 24))
|
||||||
|
#define RSP_OK (('O' << 0) | ('K' << 8) | ('O' << 16) | ('K' << 24))
|
||||||
|
#define RSP_ERR (('E' << 0) | ('R' << 8) | ('R' << 16) | ('!' << 24))
|
||||||
|
|
||||||
|
#define IMAGE_HEADER_OFFSET (36 * 1024)
|
||||||
|
|
||||||
|
#define WRITE_ADDR_MIN (XIP_BASE + IMAGE_HEADER_OFFSET + FLASH_SECTOR_SIZE)
|
||||||
|
#define ERASE_ADDR_MIN (XIP_BASE + IMAGE_HEADER_OFFSET)
|
||||||
|
#define FLASH_ADDR_MAX (XIP_BASE + PICO_FLASH_SIZE_BYTES)
|
||||||
|
|
||||||
|
static void disable_interrupts(void)
|
||||||
|
{
|
||||||
|
SysTick->CTRL &= ~1;
|
||||||
|
|
||||||
|
NVIC->ICER[0] = 0xFFFFFFFF;
|
||||||
|
NVIC->ICPR[0] = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reset_peripherals(void)
|
||||||
|
{
|
||||||
|
reset_block(~(
|
||||||
|
RESETS_RESET_IO_QSPI_BITS |
|
||||||
|
RESETS_RESET_PADS_QSPI_BITS |
|
||||||
|
RESETS_RESET_SYSCFG_BITS |
|
||||||
|
RESETS_RESET_PLL_SYS_BITS
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void jump_to_vtor(uint32_t vtor)
|
||||||
|
{
|
||||||
|
// Derived from the Leaf Labs Cortex-M3 bootloader.
|
||||||
|
// Copyright (c) 2010 LeafLabs LLC.
|
||||||
|
// Modified 2021 Brian Starkey <stark3y@gmail.com>
|
||||||
|
// Originally under The MIT License
|
||||||
|
// vtor += 0x512;
|
||||||
|
uint32_t offset = 0x1000;
|
||||||
|
uint32_t reset_vector = *(volatile uint32_t *)(vtor + 0x04) + offset;
|
||||||
|
|
||||||
|
// Everything set; time to store addresses bootloader is going to pass to
|
||||||
|
// firmware via registers. After this the firmware knows what should be done and
|
||||||
|
// it does things (system memory remapping, vector table things,
|
||||||
|
// global offset table operations) autonomously.
|
||||||
|
|
||||||
|
// Store firmware absolute address to r10 (hoop in case we have limited Cortex-M0)
|
||||||
|
asm ("ldr r6, %0; mov r10, r6"
|
||||||
|
:"=m"(offset)
|
||||||
|
:
|
||||||
|
:);
|
||||||
|
|
||||||
|
// Store firmware offset to r11 (hoop in case we have limited Cortex-M0)
|
||||||
|
asm ("ldr r6, %0; mov r11, r6;"
|
||||||
|
:"=m"(reset_vector)
|
||||||
|
:
|
||||||
|
:);
|
||||||
|
|
||||||
|
|
||||||
|
//SCB->VTOR = (volatile uint32_t)(vtor);
|
||||||
|
|
||||||
|
asm volatile("msr msp, %0"::"g"
|
||||||
|
(*(volatile uint32_t *)vtor));
|
||||||
|
asm volatile("bx %0"::"r" (reset_vector));
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t handle_sync(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out);
|
||||||
|
static uint32_t size_read(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out);
|
||||||
|
static uint32_t handle_read(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out);
|
||||||
|
static uint32_t size_csum(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out);
|
||||||
|
static uint32_t handle_csum(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out);
|
||||||
|
static uint32_t size_crc(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out);
|
||||||
|
static uint32_t handle_crc(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out);
|
||||||
|
static uint32_t handle_erase(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out);
|
||||||
|
static uint32_t size_write(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out);
|
||||||
|
static uint32_t handle_write(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out);
|
||||||
|
static uint32_t handle_seal(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out);
|
||||||
|
static uint32_t handle_go(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out);
|
||||||
|
static uint32_t handle_info(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out);
|
||||||
|
static uint32_t size_reboot(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out);
|
||||||
|
static uint32_t handle_reboot(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out);
|
||||||
|
|
||||||
|
struct command_desc {
|
||||||
|
uint32_t opcode;
|
||||||
|
uint32_t nargs;
|
||||||
|
uint32_t resp_nargs;
|
||||||
|
uint32_t (*size)(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out);
|
||||||
|
uint32_t (*handle)(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out);
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct command_desc cmds[] = {
|
||||||
|
{
|
||||||
|
.opcode = CMD_SYNC,
|
||||||
|
.nargs = 0,
|
||||||
|
.resp_nargs = 0,
|
||||||
|
.size = NULL,
|
||||||
|
.handle = &handle_sync,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// READ addr len
|
||||||
|
// OKOK [data]
|
||||||
|
.opcode = CMD_READ,
|
||||||
|
.nargs = 2,
|
||||||
|
.resp_nargs = 0,
|
||||||
|
.size = &size_read,
|
||||||
|
.handle = &handle_read,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// CSUM addr len
|
||||||
|
// OKOK csum
|
||||||
|
.opcode = CMD_CSUM,
|
||||||
|
.nargs = 2,
|
||||||
|
.resp_nargs = 1,
|
||||||
|
.size = &size_csum,
|
||||||
|
.handle = &handle_csum,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// CRCC addr len
|
||||||
|
// OKOK crc
|
||||||
|
.opcode = CMD_CRC,
|
||||||
|
.nargs = 2,
|
||||||
|
.resp_nargs = 1,
|
||||||
|
.size = &size_crc,
|
||||||
|
.handle = &handle_crc,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// ERAS addr len
|
||||||
|
// OKOK
|
||||||
|
.opcode = CMD_ERASE,
|
||||||
|
.nargs = 2,
|
||||||
|
.resp_nargs = 0,
|
||||||
|
.size = NULL,
|
||||||
|
.handle = &handle_erase,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// WRIT addr len [data]
|
||||||
|
// OKOK crc
|
||||||
|
.opcode = CMD_WRITE,
|
||||||
|
.nargs = 2,
|
||||||
|
.resp_nargs = 1,
|
||||||
|
.size = &size_write,
|
||||||
|
.handle = &handle_write,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// SEAL vtor len crc
|
||||||
|
// OKOK
|
||||||
|
.opcode = CMD_SEAL,
|
||||||
|
.nargs = 3,
|
||||||
|
.resp_nargs = 0,
|
||||||
|
.size = NULL,
|
||||||
|
.handle = &handle_seal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// GOGO vtor
|
||||||
|
// NO RESPONSE
|
||||||
|
.opcode = CMD_GO,
|
||||||
|
.nargs = 1,
|
||||||
|
.resp_nargs = 0,
|
||||||
|
.size = NULL,
|
||||||
|
.handle = &handle_go,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// INFO
|
||||||
|
// OKOK flash_start flash_size erase_size write_size max_data_len
|
||||||
|
.opcode = CMD_INFO,
|
||||||
|
.nargs = 0,
|
||||||
|
.resp_nargs = 5,
|
||||||
|
.size = NULL,
|
||||||
|
.handle = &handle_info,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// BOOT to_bootloader
|
||||||
|
// NO RESPONSE
|
||||||
|
.opcode = CMD_REBOOT,
|
||||||
|
.nargs = 1,
|
||||||
|
.resp_nargs = 0,
|
||||||
|
.size = &size_reboot,
|
||||||
|
.handle = &handle_reboot,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const unsigned int N_CMDS = (sizeof(cmds) / sizeof(cmds[0]));
|
||||||
|
const uint32_t MAX_NARG = 5;
|
||||||
|
const uint32_t MAX_DATA_LEN = 1024; //FLASH_SECTOR_SIZE;
|
||||||
|
|
||||||
|
static bool is_error(uint32_t status)
|
||||||
|
{
|
||||||
|
return status == RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t handle_sync(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
|
||||||
|
{
|
||||||
|
return RSP_SYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t size_read(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out)
|
||||||
|
{
|
||||||
|
uint32_t size = args_in[1];
|
||||||
|
if (size > MAX_DATA_LEN) {
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Validate address
|
||||||
|
|
||||||
|
*data_len_out = 0;
|
||||||
|
*resp_data_len_out = size;
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t handle_read(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
|
||||||
|
{
|
||||||
|
uint32_t addr = args_in[0];
|
||||||
|
uint32_t size = args_in[1];
|
||||||
|
|
||||||
|
memcpy(resp_data_out, (void *)addr, size);
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t size_csum(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out)
|
||||||
|
{
|
||||||
|
uint32_t addr = args_in[0];
|
||||||
|
uint32_t size = args_in[1];
|
||||||
|
|
||||||
|
if ((addr & 0x3) || (size & 0x3)) {
|
||||||
|
// Must be aligned
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Validate address
|
||||||
|
|
||||||
|
*data_len_out = 0;
|
||||||
|
*resp_data_len_out = 0;
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t handle_csum(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
|
||||||
|
{
|
||||||
|
uint32_t dummy_dest;
|
||||||
|
uint32_t addr = args_in[0];
|
||||||
|
uint32_t size = args_in[1];
|
||||||
|
|
||||||
|
int channel = dma_claim_unused_channel(true);
|
||||||
|
|
||||||
|
dma_channel_config c = dma_channel_get_default_config(channel);
|
||||||
|
channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
|
||||||
|
channel_config_set_read_increment(&c, true);
|
||||||
|
channel_config_set_write_increment(&c, false);
|
||||||
|
channel_config_set_sniff_enable(&c, true);
|
||||||
|
|
||||||
|
dma_hw->sniff_data = 0;
|
||||||
|
dma_sniffer_enable(channel, 0xf, true);
|
||||||
|
|
||||||
|
dma_channel_configure(channel, &c, &dummy_dest, (void *)addr, size / 4, true);
|
||||||
|
|
||||||
|
dma_channel_wait_for_finish_blocking(channel);
|
||||||
|
|
||||||
|
dma_sniffer_disable();
|
||||||
|
dma_channel_unclaim(channel);
|
||||||
|
|
||||||
|
*resp_args_out = dma_hw->sniff_data;
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t size_crc(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out)
|
||||||
|
{
|
||||||
|
uint32_t addr = args_in[0];
|
||||||
|
uint32_t size = args_in[1];
|
||||||
|
|
||||||
|
if ((addr & 0x3) || (size & 0x3)) {
|
||||||
|
// Must be aligned
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Validate address
|
||||||
|
|
||||||
|
*data_len_out = 0;
|
||||||
|
*resp_data_len_out = 0;
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ptr must be 4-byte aligned and len must be a multiple of 4
|
||||||
|
static uint32_t calc_crc32(void *ptr, uint32_t len)
|
||||||
|
{
|
||||||
|
uint32_t dummy_dest, crc;
|
||||||
|
|
||||||
|
int channel = dma_claim_unused_channel(true);
|
||||||
|
dma_channel_config c = dma_channel_get_default_config(channel);
|
||||||
|
channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
|
||||||
|
channel_config_set_read_increment(&c, true);
|
||||||
|
channel_config_set_write_increment(&c, false);
|
||||||
|
channel_config_set_sniff_enable(&c, true);
|
||||||
|
|
||||||
|
// Seed the CRC calculation
|
||||||
|
dma_hw->sniff_data = 0xffffffff;
|
||||||
|
|
||||||
|
// Mode 1, then bit-reverse the result gives the same result as
|
||||||
|
// golang's IEEE802.3 implementation
|
||||||
|
dma_sniffer_enable(channel, 0x1, true);
|
||||||
|
dma_hw->sniff_ctrl |= DMA_SNIFF_CTRL_OUT_REV_BITS;
|
||||||
|
|
||||||
|
dma_channel_configure(channel, &c, &dummy_dest, ptr, len / 4, true);
|
||||||
|
|
||||||
|
dma_channel_wait_for_finish_blocking(channel);
|
||||||
|
|
||||||
|
// Read the result before resetting
|
||||||
|
crc = dma_hw->sniff_data ^ 0xffffffff;
|
||||||
|
|
||||||
|
dma_sniffer_disable();
|
||||||
|
dma_channel_unclaim(channel);
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t handle_crc(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
|
||||||
|
{
|
||||||
|
uint32_t addr = args_in[0];
|
||||||
|
uint32_t size = args_in[1];
|
||||||
|
|
||||||
|
resp_args_out[0] = calc_crc32((void *)addr, size);
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t handle_erase(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
|
||||||
|
{
|
||||||
|
uint32_t addr = args_in[0];
|
||||||
|
uint32_t size = args_in[1];
|
||||||
|
|
||||||
|
if ((addr < ERASE_ADDR_MIN) || (addr + size >= FLASH_ADDR_MAX)) {
|
||||||
|
// Outside flash
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((addr & (FLASH_SECTOR_SIZE - 1)) || (size & (FLASH_SECTOR_SIZE - 1))) {
|
||||||
|
// Must be aligned
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_range_erase(addr - XIP_BASE, size);
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t size_write(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out)
|
||||||
|
{
|
||||||
|
uint32_t addr = args_in[0];
|
||||||
|
uint32_t size = args_in[1];
|
||||||
|
|
||||||
|
if ((addr < WRITE_ADDR_MIN) || (addr + size >= FLASH_ADDR_MAX)) {
|
||||||
|
// Outside flash
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((addr & (FLASH_PAGE_SIZE - 1)) || (size & (FLASH_PAGE_SIZE -1))) {
|
||||||
|
// Must be aligned
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size > MAX_DATA_LEN) {
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Validate address
|
||||||
|
|
||||||
|
*data_len_out = size;
|
||||||
|
*resp_data_len_out = 0;
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t handle_write(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
|
||||||
|
{
|
||||||
|
uint32_t addr = args_in[0];
|
||||||
|
uint32_t size = args_in[1];
|
||||||
|
|
||||||
|
flash_range_program(addr - XIP_BASE, data_in, size);
|
||||||
|
|
||||||
|
resp_args_out[0] = calc_crc32((void *)addr, size);
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct image_header {
|
||||||
|
uint32_t vtor;
|
||||||
|
uint32_t size;
|
||||||
|
uint32_t crc;
|
||||||
|
uint8_t pad[FLASH_PAGE_SIZE - (3 * 4)];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(struct image_header) == FLASH_PAGE_SIZE, "image_header must be FLASH_PAGE_SIZE bytes");
|
||||||
|
|
||||||
|
static bool image_header_ok(struct image_header *hdr)
|
||||||
|
{
|
||||||
|
uint32_t *vtor = (uint32_t *)hdr->vtor;
|
||||||
|
|
||||||
|
//uint32_t calc = calc_crc32((void *)hdr->vtor, hdr->size);
|
||||||
|
uint32_t calc = hdr->crc;
|
||||||
|
|
||||||
|
// CRC has to match
|
||||||
|
if (calc != hdr->crc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack pointer needs to be in RAM
|
||||||
|
if (vtor[0] < SRAM_BASE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset vector should be in the image, and thumb (bit 0 set)
|
||||||
|
//if ((vtor[1] < hdr->vtor) || (vtor[1] > hdr->vtor + hdr->size) || !(vtor[1] & 1)) {
|
||||||
|
// return false;
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Looks OK.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint32_t handle_seal(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
|
||||||
|
{
|
||||||
|
struct image_header hdr = {
|
||||||
|
.vtor = args_in[0],
|
||||||
|
.size = args_in[1],
|
||||||
|
.crc = args_in[2],
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((hdr.vtor & 0xff) || (hdr.size & 0x3)) {
|
||||||
|
// Must be aligned
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!image_header_ok(&hdr)) {
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_range_erase(IMAGE_HEADER_OFFSET, FLASH_SECTOR_SIZE);
|
||||||
|
flash_range_program(IMAGE_HEADER_OFFSET, (const uint8_t *)&hdr, sizeof(hdr));
|
||||||
|
|
||||||
|
struct image_header *check = (struct image_header *)(XIP_BASE + IMAGE_HEADER_OFFSET);
|
||||||
|
if (memcmp(&hdr, check, sizeof(hdr))) {
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t handle_go(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
|
||||||
|
{
|
||||||
|
disable_interrupts();
|
||||||
|
|
||||||
|
reset_peripherals();
|
||||||
|
|
||||||
|
jump_to_vtor(args_in[0]);
|
||||||
|
|
||||||
|
while(1);
|
||||||
|
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t handle_info(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
|
||||||
|
{
|
||||||
|
resp_args_out[0] = WRITE_ADDR_MIN;
|
||||||
|
resp_args_out[1] = (XIP_BASE + PICO_FLASH_SIZE_BYTES) - WRITE_ADDR_MIN;
|
||||||
|
resp_args_out[2] = FLASH_SECTOR_SIZE;
|
||||||
|
resp_args_out[3] = FLASH_PAGE_SIZE;
|
||||||
|
resp_args_out[4] = MAX_DATA_LEN;
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_reboot(bool to_bootloader)
|
||||||
|
{
|
||||||
|
hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
|
||||||
|
if (to_bootloader) {
|
||||||
|
watchdog_hw->scratch[5] = BOOTLOADER_ENTRY_MAGIC;
|
||||||
|
watchdog_hw->scratch[6] = ~BOOTLOADER_ENTRY_MAGIC;
|
||||||
|
} else {
|
||||||
|
watchdog_hw->scratch[5] = 0;
|
||||||
|
watchdog_hw->scratch[6] = 0;
|
||||||
|
}
|
||||||
|
watchdog_reboot(0, 0, 0);
|
||||||
|
while (1) {
|
||||||
|
tight_loop_contents();
|
||||||
|
asm("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t size_reboot(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out)
|
||||||
|
{
|
||||||
|
*data_len_out = 0;
|
||||||
|
*resp_data_len_out = 0;
|
||||||
|
|
||||||
|
return RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t handle_reboot(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out)
|
||||||
|
{
|
||||||
|
// Will never return
|
||||||
|
do_reboot(args_in[0]);
|
||||||
|
|
||||||
|
return RSP_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct command_desc *find_command_desc(uint32_t opcode)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < N_CMDS; i++) {
|
||||||
|
if (cmds[i].opcode == opcode) {
|
||||||
|
return &cmds[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct cmd_context {
|
||||||
|
uint8_t *uart_buf;
|
||||||
|
const struct command_desc *desc;
|
||||||
|
uint32_t opcode;
|
||||||
|
uint32_t status;
|
||||||
|
uint32_t *args;
|
||||||
|
uint8_t *data;
|
||||||
|
uint32_t *resp_args;
|
||||||
|
uint8_t *resp_data;
|
||||||
|
uint32_t data_len;
|
||||||
|
uint32_t resp_data_len;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum state {
|
||||||
|
STATE_WAIT_FOR_SYNC,
|
||||||
|
STATE_READ_OPCODE,
|
||||||
|
STATE_READ_ARGS,
|
||||||
|
STATE_READ_DATA,
|
||||||
|
STATE_HANDLE_DATA,
|
||||||
|
STATE_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
static enum state state_wait_for_sync(struct cmd_context *ctx)
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
uint8_t *recv = (uint8_t *)&ctx->opcode;
|
||||||
|
uint8_t *match = (uint8_t *)&ctx->status;
|
||||||
|
|
||||||
|
ctx->status = CMD_SYNC;
|
||||||
|
|
||||||
|
gpio_put(PICO_DEFAULT_LED_PIN, 1);
|
||||||
|
|
||||||
|
while (idx < sizeof(ctx->opcode)) {
|
||||||
|
uart_read_blocking(uart0, &recv[idx], 1);
|
||||||
|
gpio_xor_mask((1 << PICO_DEFAULT_LED_PIN));
|
||||||
|
|
||||||
|
if (recv[idx] != match[idx]) {
|
||||||
|
// Start again
|
||||||
|
idx = 0;
|
||||||
|
} else {
|
||||||
|
// Move on
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(ctx->opcode == CMD_SYNC);
|
||||||
|
|
||||||
|
return STATE_READ_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum state state_read_opcode(struct cmd_context *ctx)
|
||||||
|
{
|
||||||
|
uart_read_blocking(uart0, (uint8_t *)&ctx->opcode, sizeof(ctx->opcode));
|
||||||
|
|
||||||
|
return STATE_READ_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum state state_read_args(struct cmd_context *ctx)
|
||||||
|
{
|
||||||
|
const struct command_desc *desc = find_command_desc(ctx->opcode);
|
||||||
|
if (!desc) {
|
||||||
|
// TODO: Error handler that can do args?
|
||||||
|
ctx->status = RSP_ERR;
|
||||||
|
return STATE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->desc = desc;
|
||||||
|
ctx->args = (uint32_t *)(ctx->uart_buf + sizeof(ctx->opcode));
|
||||||
|
ctx->data = (uint8_t *)(ctx->args + desc->nargs);
|
||||||
|
ctx->resp_args = ctx->args;
|
||||||
|
ctx->resp_data = (uint8_t *)(ctx->resp_args + desc->resp_nargs);
|
||||||
|
|
||||||
|
uart_read_blocking(uart0, (uint8_t *)ctx->args, sizeof(*ctx->args) * desc->nargs);
|
||||||
|
|
||||||
|
return STATE_READ_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum state state_read_data(struct cmd_context *ctx)
|
||||||
|
{
|
||||||
|
const struct command_desc *desc = ctx->desc;
|
||||||
|
|
||||||
|
if (desc->size) {
|
||||||
|
ctx->status = desc->size(ctx->args, &ctx->data_len, &ctx->resp_data_len);
|
||||||
|
if (is_error(ctx->status)) {
|
||||||
|
return STATE_ERROR;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx->data_len = 0;
|
||||||
|
ctx->resp_data_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check sizes
|
||||||
|
|
||||||
|
uart_read_blocking(uart0, (uint8_t *)ctx->data, ctx->data_len);
|
||||||
|
|
||||||
|
return STATE_HANDLE_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum state state_handle_data(struct cmd_context *ctx)
|
||||||
|
{
|
||||||
|
const struct command_desc *desc = ctx->desc;
|
||||||
|
|
||||||
|
if (desc->handle) {
|
||||||
|
ctx->status = desc->handle(ctx->args, ctx->data, ctx->resp_args, ctx->resp_data);
|
||||||
|
if (is_error(ctx->status)) {
|
||||||
|
return STATE_ERROR;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: Should we just assert(desc->handle)?
|
||||||
|
ctx->status = RSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t resp_len = sizeof(ctx->status) + (sizeof(*ctx->resp_args) * desc->resp_nargs) + ctx->resp_data_len;
|
||||||
|
memcpy(ctx->uart_buf, &ctx->status, sizeof(ctx->status));
|
||||||
|
uart_write_blocking(uart0, ctx->uart_buf, resp_len);
|
||||||
|
|
||||||
|
return STATE_READ_OPCODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum state state_error(struct cmd_context *ctx)
|
||||||
|
{
|
||||||
|
size_t resp_len = sizeof(ctx->status);
|
||||||
|
memcpy(ctx->uart_buf, &ctx->status, sizeof(ctx->status));
|
||||||
|
uart_write_blocking(uart0, ctx->uart_buf, resp_len);
|
||||||
|
|
||||||
|
return STATE_WAIT_FOR_SYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool should_stay_in_bootloader()
|
||||||
|
{
|
||||||
|
bool wd_says_so = (watchdog_hw->scratch[5] == BOOTLOADER_ENTRY_MAGIC) &&
|
||||||
|
(watchdog_hw->scratch[6] == ~BOOTLOADER_ENTRY_MAGIC);
|
||||||
|
|
||||||
|
return !gpio_get(BOOTLOADER_ENTRY_PIN) || wd_says_so;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
gpio_init(PICO_DEFAULT_LED_PIN);
|
||||||
|
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
|
||||||
|
gpio_put(PICO_DEFAULT_LED_PIN, 1);
|
||||||
|
|
||||||
|
gpio_init(BOOTLOADER_ENTRY_PIN);
|
||||||
|
gpio_pull_up(BOOTLOADER_ENTRY_PIN);
|
||||||
|
gpio_set_dir(BOOTLOADER_ENTRY_PIN, 0);
|
||||||
|
|
||||||
|
sleep_ms(10);
|
||||||
|
|
||||||
|
struct image_header *hdr = (struct image_header *)(XIP_BASE + IMAGE_HEADER_OFFSET);
|
||||||
|
|
||||||
|
if (!should_stay_in_bootloader() && image_header_ok(hdr)) {
|
||||||
|
uint32_t vtor = *((uint32_t *)(XIP_BASE + IMAGE_HEADER_OFFSET));
|
||||||
|
disable_interrupts();
|
||||||
|
reset_peripherals();
|
||||||
|
jump_to_vtor(hdr->vtor);
|
||||||
|
}
|
||||||
|
|
||||||
|
DBG_PRINTF_INIT();
|
||||||
|
|
||||||
|
uart_init(uart0, UART_BAUD);
|
||||||
|
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
|
||||||
|
gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);
|
||||||
|
uart_set_hw_flow(uart0, false, false);
|
||||||
|
|
||||||
|
struct cmd_context ctx;
|
||||||
|
uint8_t uart_buf[(sizeof(uint32_t) * (1 + MAX_NARG)) + MAX_DATA_LEN];
|
||||||
|
ctx.uart_buf = uart_buf;
|
||||||
|
enum state state = STATE_WAIT_FOR_SYNC;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
switch (state) {
|
||||||
|
case STATE_WAIT_FOR_SYNC:
|
||||||
|
DBG_PRINTF("wait_for_sync\n");
|
||||||
|
state = state_wait_for_sync(&ctx);
|
||||||
|
DBG_PRINTF("wait_for_sync done\n");
|
||||||
|
break;
|
||||||
|
case STATE_READ_OPCODE:
|
||||||
|
DBG_PRINTF("read_opcode\n");
|
||||||
|
state = state_read_opcode(&ctx);
|
||||||
|
DBG_PRINTF("read_opcode done\n");
|
||||||
|
break;
|
||||||
|
case STATE_READ_ARGS:
|
||||||
|
DBG_PRINTF("read_args\n");
|
||||||
|
state = state_read_args(&ctx);
|
||||||
|
DBG_PRINTF("read_args done\n");
|
||||||
|
break;
|
||||||
|
case STATE_READ_DATA:
|
||||||
|
DBG_PRINTF("read_data\n");
|
||||||
|
state = state_read_data(&ctx);
|
||||||
|
DBG_PRINTF("read_data done\n");
|
||||||
|
break;
|
||||||
|
case STATE_HANDLE_DATA:
|
||||||
|
DBG_PRINTF("handle_data\n");
|
||||||
|
state = state_handle_data(&ctx);
|
||||||
|
DBG_PRINTF("handle_data done\n");
|
||||||
|
break;
|
||||||
|
case STATE_ERROR:
|
||||||
|
DBG_PRINTF("error\n");
|
||||||
|
state = state_error(&ctx);
|
||||||
|
DBG_PRINTF("error done\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
53
bootloader/mkasm.py
Normal file
53
bootloader/mkasm.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Derived from pad_checksum in the Pico SDK, which carries the following
|
||||||
|
# LICENSE.txt:
|
||||||
|
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||||
|
# following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import binascii
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("ifile", help="Input file (binary)")
|
||||||
|
parser.add_argument("ofile", help="Output file (assembly)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
idata = open(args.ifile, "rb").read()
|
||||||
|
except:
|
||||||
|
sys.exit("Could not open input file '{}'".format(args.ifile))
|
||||||
|
|
||||||
|
odata = idata
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(args.ofile, "w") as ofile:
|
||||||
|
ofile.write("// ASM-ified version of: {}\n\n".format(args.ifile))
|
||||||
|
ofile.write(".cpu cortex-m0plus\n")
|
||||||
|
ofile.write(".thumb\n\n")
|
||||||
|
ofile.write(".section .boot3, \"ax\"\n\n")
|
||||||
|
for offs in range(0, len(odata), 16):
|
||||||
|
chunk = odata[offs:min(offs + 16, len(odata))]
|
||||||
|
ofile.write(".byte {}\n".format(", ".join("0x{:02x}".format(b) for b in chunk)))
|
||||||
|
except:
|
||||||
|
sys.exit("Could not open output file '{}'".format(args.ofile))
|
62
bootloader/pico_sdk_import.cmake
Normal file
62
bootloader/pico_sdk_import.cmake
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||||
|
|
||||||
|
# This can be dropped into an external project to help locate this SDK
|
||||||
|
# It should be include()ed prior to project()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||||
|
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||||
|
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||||
|
|
||||||
|
if (NOT PICO_SDK_PATH)
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT)
|
||||||
|
include(FetchContent)
|
||||||
|
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||||
|
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||||
|
endif ()
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_sdk
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG master
|
||||||
|
)
|
||||||
|
if (NOT pico_sdk)
|
||||||
|
message("Downloading Raspberry Pi Pico SDK")
|
||||||
|
FetchContent_Populate(pico_sdk)
|
||||||
|
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||||
|
endif ()
|
||||||
|
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||||
|
else ()
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||||
|
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||||
|
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||||
|
|
||||||
|
include(${PICO_SDK_INIT_CMAKE_FILE})
|
243
bootloader/standalone.ld
Normal file
243
bootloader/standalone.ld
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
/* Based on GCC ARM embedded samples.
|
||||||
|
Defines the following symbols for use by code:
|
||||||
|
__exidx_start
|
||||||
|
__exidx_end
|
||||||
|
__etext
|
||||||
|
__data_start__
|
||||||
|
__preinit_array_start
|
||||||
|
__preinit_array_end
|
||||||
|
__init_array_start
|
||||||
|
__init_array_end
|
||||||
|
__fini_array_start
|
||||||
|
__fini_array_end
|
||||||
|
__data_end__
|
||||||
|
__bss_start__
|
||||||
|
__bss_end__
|
||||||
|
__end__
|
||||||
|
end
|
||||||
|
__HeapLimit
|
||||||
|
__StackLimit
|
||||||
|
__StackTop
|
||||||
|
__stack (== StackTop)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Skip 16kB at the start of flash, that's where our bootloader is */
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH_APP(rx) : ORIGIN = 0x10000000 + 40k, LENGTH = 2048k - 40k
|
||||||
|
RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k
|
||||||
|
SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
|
||||||
|
SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
|
||||||
|
}
|
||||||
|
|
||||||
|
ENTRY(_entry_point)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
.flash_begin : {
|
||||||
|
__flash_binary_start = .;
|
||||||
|
} > FLASH_APP
|
||||||
|
|
||||||
|
.text : {
|
||||||
|
__logical_binary_start = .;
|
||||||
|
KEEP (*(.vectors))
|
||||||
|
KEEP (*(.binary_info_header))
|
||||||
|
__binary_info_header_end = .;
|
||||||
|
KEEP (*(.reset))
|
||||||
|
/* TODO revisit this now memset/memcpy/float in ROM */
|
||||||
|
/* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from
|
||||||
|
* FLASH ... we will include any thing excluded here in .data below by default */
|
||||||
|
*(.init)
|
||||||
|
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*)
|
||||||
|
*(.fini)
|
||||||
|
/* Pull all c'tors into .text */
|
||||||
|
*crtbegin.o(.ctors)
|
||||||
|
*crtbegin?.o(.ctors)
|
||||||
|
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
|
||||||
|
*(SORT(.ctors.*))
|
||||||
|
*(.ctors)
|
||||||
|
/* Followed by destructors */
|
||||||
|
*crtbegin.o(.dtors)
|
||||||
|
*crtbegin?.o(.dtors)
|
||||||
|
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
|
||||||
|
*(SORT(.dtors.*))
|
||||||
|
*(.dtors)
|
||||||
|
|
||||||
|
*(.eh_frame*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
} > FLASH_APP
|
||||||
|
|
||||||
|
.rodata : {
|
||||||
|
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
|
||||||
|
. = ALIGN(4);
|
||||||
|
} > FLASH_APP
|
||||||
|
|
||||||
|
.ARM.extab :
|
||||||
|
{
|
||||||
|
*(.ARM.extab* .gnu.linkonce.armextab.*)
|
||||||
|
} > FLASH_APP
|
||||||
|
|
||||||
|
__exidx_start = .;
|
||||||
|
.ARM.exidx :
|
||||||
|
{
|
||||||
|
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
|
||||||
|
} > FLASH_APP
|
||||||
|
__exidx_end = .;
|
||||||
|
|
||||||
|
/* Machine inspectable binary information */
|
||||||
|
. = ALIGN(4);
|
||||||
|
__binary_info_start = .;
|
||||||
|
.binary_info :
|
||||||
|
{
|
||||||
|
KEEP(*(.binary_info.keep.*))
|
||||||
|
*(.binary_info.*)
|
||||||
|
} > FLASH_APP
|
||||||
|
__binary_info_end = .;
|
||||||
|
. = ALIGN(4);
|
||||||
|
|
||||||
|
/* End of .text-like segments */
|
||||||
|
__etext = .;
|
||||||
|
|
||||||
|
.ram_vector_table (COPY): {
|
||||||
|
*(.ram_vector_table)
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
.data : {
|
||||||
|
__data_start__ = .;
|
||||||
|
*(vtable)
|
||||||
|
|
||||||
|
*(.time_critical*)
|
||||||
|
|
||||||
|
/* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */
|
||||||
|
*(.text*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.rodata*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
|
||||||
|
*(.data*)
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.after_data.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* preinit data */
|
||||||
|
PROVIDE_HIDDEN (__mutex_array_start = .);
|
||||||
|
KEEP(*(SORT(.mutex_array.*)))
|
||||||
|
KEEP(*(.mutex_array))
|
||||||
|
PROVIDE_HIDDEN (__mutex_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* preinit data */
|
||||||
|
PROVIDE_HIDDEN (__preinit_array_start = .);
|
||||||
|
KEEP(*(SORT(.preinit_array.*)))
|
||||||
|
KEEP(*(.preinit_array))
|
||||||
|
PROVIDE_HIDDEN (__preinit_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* init data */
|
||||||
|
PROVIDE_HIDDEN (__init_array_start = .);
|
||||||
|
KEEP(*(SORT(.init_array.*)))
|
||||||
|
KEEP(*(.init_array))
|
||||||
|
PROVIDE_HIDDEN (__init_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* finit data */
|
||||||
|
PROVIDE_HIDDEN (__fini_array_start = .);
|
||||||
|
*(SORT(.fini_array.*))
|
||||||
|
*(.fini_array)
|
||||||
|
PROVIDE_HIDDEN (__fini_array_end = .);
|
||||||
|
|
||||||
|
*(.jcr)
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* All data end */
|
||||||
|
__data_end__ = .;
|
||||||
|
} > RAM AT> FLASH_APP
|
||||||
|
|
||||||
|
.uninitialized_data (COPY): {
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.uninitialized_data*)
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
/* Start and end symbols must be word-aligned */
|
||||||
|
.scratch_x : {
|
||||||
|
__scratch_x_start__ = .;
|
||||||
|
*(.scratch_x.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__scratch_x_end__ = .;
|
||||||
|
} > SCRATCH_X AT > FLASH_APP
|
||||||
|
__scratch_x_source__ = LOADADDR(.scratch_x);
|
||||||
|
|
||||||
|
.scratch_y : {
|
||||||
|
__scratch_y_start__ = .;
|
||||||
|
*(.scratch_y.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__scratch_y_end__ = .;
|
||||||
|
} > SCRATCH_Y AT > FLASH_APP
|
||||||
|
__scratch_y_source__ = LOADADDR(.scratch_y);
|
||||||
|
|
||||||
|
/* Global Offset Table is relocated to RAM. */
|
||||||
|
.got :
|
||||||
|
{
|
||||||
|
. = ALIGN(4);
|
||||||
|
__ram_global_offset_table_begin = . ;
|
||||||
|
*(.got)
|
||||||
|
__ram_global_offset_table_end = . ;
|
||||||
|
} >RAM AT> FLASH_APP
|
||||||
|
__flash_global_offset_table_begin = LOADADDR(.got) ;
|
||||||
|
__flash_global_offset_table_end = __flash_global_offset_table_begin + __ram_global_offset_table_end - __ram_global_offset_table_begin ;
|
||||||
|
|
||||||
|
.bss : {
|
||||||
|
. = ALIGN(4);
|
||||||
|
__bss_start__ = .;
|
||||||
|
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
|
||||||
|
*(COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__bss_end__ = .;
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
.heap (COPY):
|
||||||
|
{
|
||||||
|
__end__ = .;
|
||||||
|
end = __end__;
|
||||||
|
*(.heap*)
|
||||||
|
__HeapLimit = .;
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
/* .stack*_dummy section doesn't contains any symbols. It is only
|
||||||
|
* used for linker to calculate size of stack sections, and assign
|
||||||
|
* values to stack symbols later
|
||||||
|
*
|
||||||
|
* stack1 section may be empty/missing if platform_launch_core1 is not used */
|
||||||
|
|
||||||
|
/* by default we put core 0 stack at the end of scratch Y, so that if core 1
|
||||||
|
* stack is not used then all of SCRATCH_X is free.
|
||||||
|
*/
|
||||||
|
.stack1_dummy (COPY):
|
||||||
|
{
|
||||||
|
*(.stack1*)
|
||||||
|
} > SCRATCH_X
|
||||||
|
.stack_dummy (COPY):
|
||||||
|
{
|
||||||
|
*(.stack*)
|
||||||
|
} > SCRATCH_Y
|
||||||
|
|
||||||
|
.flash_end : {
|
||||||
|
__flash_binary_end = .;
|
||||||
|
} > FLASH_APP
|
||||||
|
|
||||||
|
/* stack limit is poorly named, but historically is maximum heap ptr */
|
||||||
|
__StackLimit = ORIGIN(RAM) + LENGTH(RAM);
|
||||||
|
__StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
|
||||||
|
__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);
|
||||||
|
__StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
|
||||||
|
__StackBottom = __StackTop - SIZEOF(.stack_dummy);
|
||||||
|
PROVIDE(__stack = __StackTop);
|
||||||
|
|
||||||
|
/* Check if data + heap + stack exceeds RAM limit */
|
||||||
|
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")
|
||||||
|
|
||||||
|
ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary")
|
||||||
|
/* todo assert on extra code */
|
||||||
|
}
|
||||||
|
|
4
image_info.c
Normal file
4
image_info.c
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#include "image_info.h"
|
||||||
|
|
||||||
|
// Sometimes howering might show false results in debugger, but these still have right values.
|
||||||
|
uint32_t __firmwareOffset;
|
9
image_info.h
Normal file
9
image_info.h
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#ifndef _IMAGE_INFO_H_
|
||||||
|
#define _IMAGE_INFO_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Sometimes hovering might show false results in debugger, but these still have right values.
|
||||||
|
extern uint32_t __firmwareOffset;
|
||||||
|
|
||||||
|
#endif // #define _IMAGE_INFO_H_
|
73
pico_sdk_import.cmake
Normal file
73
pico_sdk_import.cmake
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||||
|
|
||||||
|
# This can be dropped into an external project to help locate this SDK
|
||||||
|
# It should be include()ed prior to project()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||||
|
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||||
|
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||||
|
|
||||||
|
if (NOT PICO_SDK_PATH)
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT)
|
||||||
|
include(FetchContent)
|
||||||
|
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||||
|
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||||
|
endif ()
|
||||||
|
# GIT_SUBMODULES_RECURSE was added in 3.17
|
||||||
|
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_sdk
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG master
|
||||||
|
GIT_SUBMODULES_RECURSE FALSE
|
||||||
|
)
|
||||||
|
else ()
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_sdk
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG master
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (NOT pico_sdk)
|
||||||
|
message("Downloading Raspberry Pi Pico SDK")
|
||||||
|
FetchContent_Populate(pico_sdk)
|
||||||
|
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||||
|
endif ()
|
||||||
|
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||||
|
else ()
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||||
|
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||||
|
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||||
|
|
||||||
|
include(${PICO_SDK_INIT_CMAKE_FILE})
|
Loading…
Add table
Reference in a new issue