diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 0000000..dd2e8d2 --- /dev/null +++ b/.bazelignore @@ -0,0 +1 @@ +lib/pico-sdk diff --git a/.github/workflows/bazel_build.yml b/.github/workflows/bazel_build.yml new file mode 100644 index 0000000..2e775c7 --- /dev/null +++ b/.github/workflows/bazel_build.yml @@ -0,0 +1,36 @@ +name: Bazel presubmit checks + +on: + push: + pull_request: + +jobs: + bazel-build-check: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Get Bazel + uses: bazel-contrib/setup-bazel@0.9.0 + with: + # Avoid downloading Bazel every time. + bazelisk-cache: true + # Store build cache per workflow. + disk-cache: ${{ github.workflow }} + # Share repository cache between workflows. + repository-cache: true + - name: Fetch latest Pico SDK + uses: actions/checkout@v4 + with: + repository: raspberrypi/pico-sdk + ref: develop + fetch-depth: 0 + path: lib/pico-sdk + - name: Bazel Picotool with develop pico-sdk + run: bazel build @picotool//:picotool --override_module=pico-sdk=lib/pico-sdk diff --git a/.github/workflows/choco_packages.config b/.github/workflows/choco_packages.config new file mode 100644 index 0000000..b2d3db1 --- /dev/null +++ b/.github/workflows/choco_packages.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..85decb5 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,73 @@ +on: + push: + pull_request: + +jobs: + build: + # Prevent running twice for PRs from same repo + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + name: Build & Test + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + generator: ["Ninja", "Unix Makefiles"] + mbedtls: ["mbedtls", ""] + libusb: ["libusb", ""] + compile: ["compile", ""] + exclude: + - os: 'windows-latest' + generator: "Unix Makefiles" + - libusb: "" + compile: "compile" + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install dependencies (Windows) + if: runner.os == 'Windows' + run: | + choco install -y .github/workflows/choco_packages.config + curl -L https://github.com/libusb/libusb/releases/download/v1.0.27/libusb-1.0.27.7z -o libusb.7z + 7z x libusb.7z -olibusb + - name: Set LIBUSB_ROOT (Windows) + if: runner.os == 'Windows' + shell: bash + run: echo "LIBUSB_ROOT=$(pwd)/libusb" >> "$GITHUB_ENV" + + - name: Install dependencies (macOS) + if: runner.os == 'macOS' + run: | + brew install libusb ninja + brew install --cask gcc-arm-embedded + + - name: Install dependencies (Linux) + if: runner.os == 'Linux' + run: sudo apt install cmake ninja-build python3 build-essential gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib libusb-1.0-0-dev + - name: Checkout Pico SDK + uses: actions/checkout@v4 + with: + repository: raspberrypi/pico-sdk + ref: develop + path: pico-sdk + submodules: ${{ !(!matrix.mbedtls) }} + + - name: Build and Install + run: | + cmake -S . -B build -G "${{ matrix.generator }}" -D PICO_SDK_PATH="${{ github.workspace }}/pico-sdk" ${{ !matrix.libusb && '-D PICOTOOL_NO_LIBUSB=1' || '' }} ${{ matrix.compile && '-D USE_PRECOMPILED=false' || '' }} + cmake --build build + ${{ runner.os != 'Windows' && 'sudo' || '' }} cmake --install build + - name: Add to path (Windows) + if: runner.os == 'Windows' + run: echo "C:\Program Files (x86)\picotool\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Test + run: | + picotool help + curl -L https://datasheets.raspberrypi.com/soft/blink.uf2 -o blink.uf2 + curl -L https://datasheets.raspberrypi.com/soft/hello_world.uf2 -o hello_world.uf2 + curl -L https://datasheets.raspberrypi.com/soft/flash_nuke.uf2 -o flash_nuke.uf2 + picotool info -a blink.uf2 + picotool info -a hello_world.uf2 + picotool info -a flash_nuke.uf2 diff --git a/BUILD.bazel b/BUILD.bazel index 2bc5b2a..d120f50 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -2,6 +2,8 @@ load("//bazel:defs.bzl", "otp_header_parse", "picotool_binary_data_header") package(default_visibility = ["//visibility:public"]) +PICOTOOL_SDK_VERSION_STRING = module_version() if module_version() != None else "0.0.1-WORKSPACE" + picotool_binary_data_header( name = "rp2350_rom", src = "bootrom.end.bin", @@ -15,6 +17,13 @@ picotool_binary_data_header( out = "xip_ram_perms_elf.h", ) +# TODO: Make it possible to build the prebuilt from source. +picotool_binary_data_header( + name = "flash_id_bin", + src = "//picoboot_flash_id:picoboot_flash_id_prebuilt", + out = "flash_id_bin.h", +) + cc_library( name = "xip_ram_perms", srcs = ["xip_ram_perms.cpp"], @@ -37,6 +46,10 @@ otp_header_parse( name = "otp_header", src = "@pico-sdk//src/rp2350/hardware_regs:otp_data_header", out = "rp2350.json.h", + target_compatible_with = select({ + "@rules_cc//cc/compiler:msvc-cl": ["@platforms//:incompatible"], + "//conditions:default": [], + }), ) cc_binary( @@ -47,10 +60,14 @@ cc_binary( "main.cpp", "otp.cpp", "otp.h", - "rp2350.json.h", "rp2350.rom.h", "xip_ram_perms.cpp", - ], + ] + select({ + # MSVC can't handle long strings, so use this manually generated + # header instead. + "@rules_cc//cc/compiler:msvc-cl": [], + "//conditions:default": ["rp2350.json.h"], + }), copts = select({ "@rules_cc//cc/compiler:msvc-cl": [ "/std:c++20", @@ -64,8 +81,7 @@ cc_binary( ], }), defines = [ - # TODO: There's probably a nicer way to share the version with CMake. - 'PICOTOOL_VERSION=\\"2.0.0\\"', + 'PICOTOOL_VERSION=\\"{}\\"'.format(PICOTOOL_SDK_VERSION_STRING), 'SYSTEM_VERSION=\\"host\\"', 'COMPILER_INFO=\\"local\\"', "SUPPORT_A0=0", @@ -98,5 +114,10 @@ cc_binary( "@pico-sdk//src/rp2350/hardware_regs:otp_data", "@pico-sdk//src/rp2_common/pico_bootrom:pico_bootrom_headers", "@pico-sdk//src/rp2_common/pico_stdio_usb:reset_interface_headers", - ], + ] + select({ + # MSVC can't handle long strings, so use this manually generated + # header instead. + "@rules_cc//cc/compiler:msvc-cl": ["//otp_header_parser:pre_generated_otp_header"], + "//conditions:default": [], + }), ) diff --git a/CMakeLists.txt b/CMakeLists.txt index d75982e..adb47ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ if (PICOTOOL_FLAT_INSTALL) set(INSTALL_DATADIR picotool) set(INSTALL_BINDIR picotool) else() - set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/picotool) + set(INSTALL_CONFIGDIR lib/cmake/picotool) set(INSTALL_DATADIR ${CMAKE_INSTALL_DATADIR}/picotool) set(INSTALL_BINDIR ${CMAKE_INSTALL_BINDIR}) endif() @@ -70,6 +70,7 @@ if (NOT PICOTOOL_NO_LIBUSB) "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}" "-DPICO_SDK_PATH:FILEPATH=${PICO_SDK_PATH}" "-DUSE_PRECOMPILED:BOOL=${USE_PRECOMPILED}" + "-DPICO_DEBUG_INFO_IN_RELEASE=OFF" BUILD_ALWAYS 1 # todo remove this INSTALL_COMMAND "" ) @@ -84,6 +85,30 @@ if (NOT PICOTOOL_NO_LIBUSB) DEPENDS xip_ram_perms ) + # compile flash_id + ExternalProject_Add(flash_id + PREFIX picoboot_flash_id + SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/picoboot_flash_id + BINARY_DIR ${CMAKE_BINARY_DIR}/picoboot_flash_id + CMAKE_ARGS + "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}" + "-DPICO_SDK_PATH:FILEPATH=${PICO_SDK_PATH}" + "-DUSE_PRECOMPILED:BOOL=${USE_PRECOMPILED}" + "-DPICO_DEBUG_INFO_IN_RELEASE=OFF" + BUILD_ALWAYS 1 # todo remove this + INSTALL_COMMAND "" + ) + + set(FLASH_ID_BIN ${CMAKE_BINARY_DIR}/picoboot_flash_id/flash_id.bin) + add_executable(flash_id_bin IMPORTED) + add_dependencies(flash_id_bin flash_id) + set_property(TARGET flash_id_bin PROPERTY IMPORTED_LOCATION ${FLASH_ID_BIN}) + # copy flash_id.bin into build directory + add_custom_command(TARGET flash_id + COMMAND ${CMAKE_COMMAND} -E copy ${FLASH_ID_BIN} ${CMAKE_BINARY_DIR}/flash_id.bin + DEPENDS flash_id + ) + # We want to generate headers from WELCOME.HTM etc. ExternalProject_Add(otp_header_parser PREFIX otp_header_parser @@ -144,7 +169,10 @@ if (NOT PICOTOOL_NO_LIBUSB) endif() endif() -add_custom_target(binary_data DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/rp2350.rom.h ${CMAKE_CURRENT_BINARY_DIR}/xip_ram_perms_elf.h) +add_custom_target(binary_data DEPENDS + ${CMAKE_CURRENT_BINARY_DIR}/rp2350.rom.h + ${CMAKE_CURRENT_BINARY_DIR}/xip_ram_perms_elf.h + ${CMAKE_CURRENT_BINARY_DIR}/flash_id_bin.h) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/rp2350.rom.h COMMAND ${CMAKE_COMMAND} -D BINARY_FILE=${CMAKE_CURRENT_LIST_DIR}/bootrom.end.bin @@ -160,6 +188,14 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xip_ram_perms_elf.h DEPENDS xip_ram_perms COMMENT "Configuring xip_ram_perms_elf.h" VERBATIM) +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/flash_id_bin.h + COMMAND ${CMAKE_COMMAND} + -D BINARY_FILE=${FLASH_ID_BIN} + -D OUTPUT_NAME=flash_id_bin + -P ${CMAKE_CURRENT_LIST_DIR}/cmake/binh.cmake + DEPENDS flash_id + COMMENT "Configuring flash_id_bin.h" + VERBATIM) add_subdirectory(errors) @@ -188,10 +224,9 @@ add_subdirectory(${PICO_SDK_PATH}/src/common/boot_uf2_headers boot_uf2_headers) add_subdirectory(${PICO_SDK_PATH}/src/common/boot_picoboot_headers boot_picoboot_headers) add_subdirectory(${PICO_SDK_PATH}/src/common/boot_picobin_headers boot_picobin_headers) add_subdirectory(${PICO_SDK_PATH}/src/common/pico_usb_reset_interface_headers pico_usb_reset_interface_headers) +add_subdirectory(${PICO_SDK_PATH}/src/rp2_common/boot_bootrom_headers boot_bootrom_headers) add_subdirectory(${PICO_SDK_PATH}/src/host/pico_platform pico_platform) -add_library(pico_bootrom_headers INTERFACE) -target_include_directories(pico_bootrom_headers INTERFACE ${PICO_SDK_PATH}/src/rp2_common/pico_bootrom/include) add_library(regs_headers INTERFACE) target_include_directories(regs_headers INTERFACE ${PICO_SDK_PATH}/src/rp2350/hardware_regs/include) @@ -204,8 +239,8 @@ if (NOT PICOTOOL_NO_LIBUSB) target_sources(picotool PRIVATE xip_ram_perms.cpp) add_dependencies(picotool generate_otp_header xip_ram_perms_elf binary_data) endif() -set(PROJECT_VERSION 2.0.0) -set(PICOTOOL_VERSION 2.0.0) +set(PROJECT_VERSION 2.1.0) +set(PICOTOOL_VERSION 2.1.0) set(SYSTEM_VERSION "${CMAKE_SYSTEM_NAME}") set(COMPILER_INFO "${CMAKE_C_COMPILER_ID}-${CMAKE_C_COMPILER_VERSION}, ${CMAKE_BUILD_TYPE}") target_compile_definitions(picotool PRIVATE @@ -224,7 +259,7 @@ target_link_libraries(picotool boot_uf2_headers boot_picoboot_headers boot_picobin_headers - pico_bootrom_headers + boot_bootrom_headers pico_platform_headers pico_usb_reset_interface_headers regs_headers @@ -242,7 +277,11 @@ else() endif() if (NOT LIBUSB_FOUND) - message("libUSB is not found - no USB support will be built") + if (PICOTOOL_NO_LIBUSB) + message("PICOTOOL_NO_LIBUSB is set - no USB support will be built") + else() + message("libUSB is not found - no USB support will be built") + endif() target_compile_definitions(picotool PRIVATE HAS_LIBUSB=0) target_link_libraries(picotool picoboot_connection_header) diff --git a/MODULE.bazel b/MODULE.bazel index 2c22158..eedd667 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,10 +1,10 @@ module( name = "picotool", - version = "2.0.0", + version = "2.1.0", ) bazel_dep(name = "rules_libusb", version = "0.1.0-rc1") -bazel_dep(name = "pico-sdk", version = "2.0.0") +bazel_dep(name = "pico-sdk", version = "2.1.0") bazel_dep(name = "rules_cc", version = "0.0.9") bazel_dep(name = "bazel_skylib", version = "1.6.1") bazel_dep(name = "rules_python", version = "0.22.1") diff --git a/README.md b/README.md index 29231b9..625259f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Use your favorite package tool to install dependencies. For example, on Ubuntu: sudo apt install build-essential pkg-config libusb-1.0-0-dev cmake ``` +> If libusb-1.0-0-dev is not installed, picotool still builds, but it omits all options that deal with managing a pico via USB (load, save, erase, verify, reboot). Builds that do not include USB support can be recognized because these commands also do not appear in the help command. The build output message 'libUSB is not found - no USB support will be built' also appears in the build logs. + Then simply build like a normal CMake project: ```console @@ -66,32 +68,33 @@ No need to download libusb separately or set `LIBUSB_ROOT`. pacman -S $MINGW_PACKAGE_PREFIX-{toolchain,cmake,libusb} mkdir build cd build -MSYS2_ARG_CONV_EXCL=- cmake .. -G"MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX -make -make install DESTDIR=/ # optional +cmake .. -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX +cmake --build . ``` ## Usage by the Raspberry Pi Pico SDK -The Raspberry Pi Pico SDK ([pico-sdk](https://github.com/raspberrypi/pico-sdk)) version 2.0.0 and above, uses `picotool` to do the ELF->UF2 conversion previously handled by the `elf2uf2` tool in the SDK. `picootol` is also used by the SDK for hashing and/or signing binaries. +The Raspberry Pi Pico SDK ([pico-sdk](https://github.com/raspberrypi/pico-sdk)) version 2.0.0 and above uses `picotool` to do the ELF-to-UF2 conversion previously handled by the `elf2uf2` tool in the SDK. The SDK also uses `picotool` to hash and sign binaries. -Whilst the SDK can download picotool on its own per project, if you have multiple projects or build configurations, it is preferable to install a single copy of `picotool` locally. +Whilst the SDK can download picotool on its own per project, if you have multiple projects or build configurations, it is preferable to install a single copy of `picotool` locally. This can be done most simply with `make install` or `cmake --install .`, using `sudo` if required; the SDK will use this installed version by default. -This can be done most simply with `make install`; the SDK will use this installed version by default. +> On some Linux systems, the `~/.local` prefix may be used for an install without `sudo`; from your build directory simply run +> ```console +> cmake -DCMAKE_INSTALL_PREFIX=~/.local .. +> make install +> ``` +> This will only work if `~/.local` is included in your `PATH` -Alternatively you can install in a custom path via: +Alternatively, you can install to a custom path via: -``` +```console cmake -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR -DPICOTOOL_FLAT_INSTALL=1 .. +make install ``` -In order for the SDK to find `picotool` in this custom path, you will need to set the `picotool_DIR` variable in your project, either by passing to `-Dpicotool_DIR=$MY_INSTALL_DIR/picotool` to your SDK `cmake` command, or by adding +In order for the SDK to find `picotool` in this custom folder, you will usually need to set the `picotool_DIR` variable in your project. This can be achieved either by setting the `picotool_DIR` environment variable to `$MY_INSTALL_DIR/picotool`, by passing `-Dpicotool_DIR=$MY_INSTALL_DIR/picotool` to your `cmake` command, or by adding `set(picotool_DIR $MY_INSTALL_DIR/picotool)` to your CMakeLists.txt file. -```CMake -set(picotool_DIR $MY_INSTALL_DIR/picotool) -``` - -to your CMakeLists.txt file. +> See the [find_package documentation](https://cmake.org/cmake/help/latest/command/find_package.html#config-mode-search-procedure) for more details ## Overview @@ -102,20 +105,23 @@ Note for additional documentation see https://rptl.io/pico-get-started ``` $ picotool help PICOTOOL: -Tool for interacting with RP2040/RP2350 device(s) in BOOTSEL mode, or with an RP2040/RP2350 binary + Tool for interacting with RP-series device(s) in BOOTSEL mode, or with an RP-series binary SYNOPSIS: - picotool info [-b] [-p] [-d] [--debug] [-l] [-a] [device-selection] - picotool info [-b] [-p] [-d] [--debug] [-l] [-a] [-t ] + picotool info [-b] [-m] [-p] [-d] [--debug] [-l] [-a] [device-selection] + picotool info [-b] [-m] [-p] [-d] [--debug] [-l] [-a] [-t ] picotool config [-s ] [-g ] [device-selection] picotool config [-s ] [-g ] [-t ] - picotool load [-p] [-n] [-N] [-u] [-v] [-x] [-t ] [-o ] [device-selection] + picotool load [--ignore-partitions] [--family ] [-p ] [-n] [-N] [-u] [-v] [-x] [-t ] [-o ] [device-selection] picotool encrypt [--quiet] [--verbose] [--hash] [--sign] [-t ] [-o ] [-t ] [-t ] [] [-t ] picotool seal [--quiet] [--verbose] [--hash] [--sign] [--clear] [-t ] [-o ] [-t ] [] [-t ] [] [-t ] [--major ] [--minor ] [--rollback [..]] picotool link [--quiet] [--verbose] [-t ] [-t ] [-t ] [] [-t ] [-p] - picotool save [-p] [device-selection] - picotool save -a [device-selection] - picotool save -r [device-selection] + picotool save [-p] [-v] [--family ] [device-selection] + picotool save -a [-v] [--family ] [device-selection] + picotool save -r [-v] [--family ] [device-selection] + picotool erase [-a] [device-selection] + picotool erase -p [device-selection] + picotool erase -r [device-selection] picotool verify [device-selection] picotool reboot [-a] [-u] [-g ] [-c ] [device-selection] picotool otp list|get|set|load|dump|permissions|white-label @@ -127,20 +133,21 @@ SYNOPSIS: COMMANDS: info Display information from the target device(s) or file. - Without any arguments, this will display basic information for all connected RP2040 devices in BOOTSEL mode + Without any arguments, this will display basic information for all connected RP-series devices in BOOTSEL mode config Display or change program configuration settings from the target device(s) or file. load Load the program / memory range stored in a file onto the device. encrypt Encrypt the program. seal Add final metadata to a binary, optionally including a hash and/or signature. link Link multiple binaries into one block loop. save Save the program / memory stored in flash on the device to a file. + erase Erase the program / memory stored in flash on the device. verify Check that the device contents match those in the file. reboot Reboot the device otp Commands related to the RP2350 OTP (One-Time-Programmable) Memory partition Commands related to RP2350 Partition Tables uf2 Commands related to UF2 creation and status version Display picotool version - coprodis Post-process coprocessor instructions in dissassembly files. + coprodis Post-process coprocessor instructions in disassembly files. help Show general help or help for a specific command Use "picotool help " for more info @@ -160,16 +167,18 @@ a file. This file can be an ELF, a UF2 or a BIN file. $ picotool help info INFO: Display information from the target device(s) or file. - Without any arguments, this will display basic information for all connected RP2040 devices in BOOTSEL mode + Without any arguments, this will display basic information for all connected RP-series devices in BOOTSEL mode SYNOPSIS: - picotool info [-b] [-p] [-d] [--debug] [-l] [-a] [device-selection] - picotool info [-b] [-p] [-d] [--debug] [-l] [-a] [-t ] + picotool info [-b] [-m] [-p] [-d] [--debug] [-l] [-a] [device-selection] + picotool info [-b] [-m] [-p] [-d] [--debug] [-l] [-a] [-t ] OPTIONS: Information to display -b, --basic Include basic information. This is the default + -m, --metadata + Include all metadata blocks -p, --pins Include pin information -d, --device @@ -182,7 +191,7 @@ OPTIONS: Include all information TARGET SELECTION: - To target one or more connected RP2040 device(s) in BOOTSEL mode (the default) + To target one or more connected RP-series device(s) in BOOTSEL mode (the default) --bus Filter devices by USB bus number --address @@ -290,7 +299,7 @@ OPTIONS: Filter by feature group TARGET SELECTION: - To target one or more connected RP2040 device(s) in BOOTSEL mode (the default) + To target one or more connected RP-series device(s) in BOOTSEL mode (the default) --bus Filter devices by USB bus number --address @@ -364,7 +373,7 @@ OPTIONS: --family Specify the family ID of the file to load - family id to use for load + family ID to use for load -p, --partition Specify the partition to load into @@ -428,9 +437,9 @@ SAVE: Save the program / memory stored in flash on the device to a file. SYNOPSIS: - picotool save [-p] [device-selection] - picotool save -a [device-selection] - picotool save -r [device-selection] + picotool save [-p] [-v] [--family ] [device-selection] + picotool save -a [-v] [--family ] [device-selection] + picotool save -r [-v] [--family ] [device-selection] OPTIONS: Selection of data to save @@ -445,6 +454,13 @@ OPTIONS: The lower address bound in hex The upper address bound in hex + Other + -v, --verify + Verify the data was saved correctly + --family + Specify the family ID to save the file as + + family id to save file as Source device selection --bus Filter devices by USB bus number @@ -495,6 +511,95 @@ name: lcd_1602_i2c web site: https://github.com/raspberrypi/pico-examples/tree/HEAD/i2c/lcd_1602_i2c ``` +## erase + +`erase` allows you to erase all of flash, a partition of flash, or an explicit range of flash on the device. +It defaults to erasing all of flash. + +```text +$ picotool help erase +ERASE: + Erase the program / memory stored in flash on the device. + +SYNOPSIS: + picotool erase [-a] [device-selection] + picotool erase [-p ] [device-selection] + picotool erase -r [device-selection] + +OPTIONS: + Selection of data to erase + -a, --all + Erase all of flash memory. This is the default + -p, --partition + Erase a partition + + Partition number to erase + -r, --range + Erase a range of memory. Note that erases must be 4096 byte-aligned, so the range is expanded accordingly + + The lower address bound in hex + + The upper address bound in hex + Source device selection + --bus + Filter devices by USB bus number + --address + Filter devices by USB device address + --vid + Filter by vendor id + --pid + Filter by product id + --ser + Filter by serial number + -f, --force + Force a device not in BOOTSEL mode but running compatible code to reset so the command can be executed. After executing the + command (unless the command itself is a 'reboot') the device will be rebooted back to application mode + -F, --force-no-reboot + Force a device not in BOOTSEL mode but running compatible code to reset so the command can be executed. After executing the + command (unless the command itself is a 'reboot') the device will be left connected and accessible to picotool, but without the + RPI-RP2 drive mounted +``` + +e.g. first looking at what is on the device... + +```text +$ picotool info +Partition 0 + Program Information + none + +Partition 1 + Program Information + name: blink + web site: https://github.com/raspberrypi/pico-examples/tree/HEAD/blink + features: UART stdin / stdout + binary start: 0x10000000 + binary end: 0x1000a934 + target chip: RP2350 + image type: ARM Secure +``` + +... then erase partition 1 ... +```text +$ picotool erase -p 1 +Erasing partition 1: + 0007f000->000fc000 +Erasing: [==============================] 100% +Erased 512000 bytes +``` + +... and looking at the device again: +```text +$ picotool info +Partition 0 + Program Information + none + +Partition 1 + Program Information + none +``` + ## seal `seal` allows you to sign and/or hash a binary to run on RP2350. @@ -653,14 +758,14 @@ un-partitioned_space : S(rw) NSBOOT(rw) NS(rw), uf2 { absolute } partitions: 0(A) 00002000->00201000 S(rw) NSBOOT(rw) NS(rw), id=0000000000000000, "A", uf2 { rp2350-arm-s, rp2350-riscv }, arm_boot 1, riscv_boot 1 1(B w/ 0) 00201000->00400000 S(rw) NSBOOT(rw) NS(rw), id=0000000000000001, "B", uf2 { rp2350-arm-s, rp2350-riscv }, arm_boot 1, riscv_boot 1 -Family id 'rp2350-arm-s' can be downloaded in partition 0: +Family ID 'rp2350-arm-s' can be downloaded in partition 0: 00002000->00201000 ``` ### create This command allows you to create partition tables, and additionally embed them into the block loop if ELF files (for example, for bootloaders). -By default, all partition tables are hashed, and you can also sign them. +By default, all partition tables are hashed, and you can also sign them. The schema for this JSON file is [here](json/schemas/partition-table-schema.json). ```text $ picotool help partition create @@ -694,7 +799,7 @@ OPTIONS: --family Specify the family if for UF2 file output - family id for UF2 (default absolute) + family ID for UF2 (default absolute) embed partition table into bootloader ELF The file name @@ -709,7 +814,7 @@ OPTIONS: Don't hash the partition table --singleton Singleton partition table - Errata RP2350-E9 Fix + Errata RP2350-E10 Fix --abs-block Enforce support for an absolute block @@ -718,7 +823,7 @@ OPTIONS: ## uf2 -The `uf2` commands allow for creation of UF2s, and cam provide information when if a UF2 download has failed. +The `uf2` commands allow for creation of UF2s, and can provide information if a UF2 download has failed. ### convert @@ -755,8 +860,8 @@ OPTIONS: Load offset (memory address; default 0x10000000 for BIN file) UF2 Family options - family id for UF2 - Errata RP2350-E9 Fix + family ID for UF2 + Errata RP2350-E10 Fix --abs-block Add an absolute block @@ -804,7 +909,7 @@ The `otp` commands are for interacting with the RP2350 OTP Memory. They are not Note that the OTP Memory is One-Time-Programmable, which means that once a bit has been changed from 0 to 1, it cannot be changed back. Therefore, caution should be used when using these commands, as they risk bricking your RP2350 device. For example, if you set SECURE_BOOT_ENABLE but don't set a boot key, and disable the PICOBOOT interface, then your device will be unusable. -For the `list`, `set`, `get` and `load` commands, you can define your own OTP layout in a JSON file and pass that in with the `-i` argument. These rows will be added to the default rows when parsing. +For the `list`, `set`, `get` and `load` commands, you can define your own OTP layout in a JSON file and pass that in with the `-i` argument. These rows will be added to the default rows when parsing. The schema for this JSON file is [here](json/schemas/otp-contents-schema.json) ```text $ picotool help otp @@ -812,9 +917,9 @@ OTP: Commands related to the RP2350 OTP (One-Time-Programmable) Memory SYNOPSIS: - picotool otp list [-p] [-n] [-i ] [..] + picotool otp list [-p] [-n] [-f] [-i ] [..] picotool otp get [-c ] [-r] [-e] [-n] [-i ] [device-selection] [-z] [..] - picotool otp set [-c ] [-r] [-e] [-i ] [-z] [device-selection] + picotool otp set [-c ] [-r] [-e] [-s] [-i ] [-z] [device-selection] picotool otp load [-r] [-e] [-s ] [-i ] [-t ] [device-selection] picotool otp dump [-r] [-e] [device-selection] picotool otp permissions [-t ] [--led ] [--hash] [--sign] [] [-t ] [device-selection] @@ -837,7 +942,7 @@ These commands will set/get specific rows of OTP. By default, they will write/re ### load -This command allows loading of a range of OTP rows onto the device. The source can be a binary file, or a JSON file such as the one output by `picotool sign`. +This command allows loading of a range of OTP rows onto the device. The source can be a binary file, or a JSON file such as the one output by `picotool sign`. The schema for this JSON file is [here](json/schemas/otp-schema.json) For example, if you wish to sign a binary and then test secure boot with it, you can run the following set of commands: ```text $ picotool sign hello_world.elf hello_world.signed.elf private.pem otp.json @@ -849,7 +954,7 @@ $ picotool reboot ### white-label This command allows for OTP white-labelling, which sets the USB configuration used by the device in BOOTSEL mode. -This can be configured from a JSON file, an example of which is in [sample-wl.json](sample-wl.json). +This can be configured from a JSON file, an example of which is in [sample-wl.json](json/sample-wl.json). The schema for this JSON file is [here](json/schemas/whitelabel-schema.json) ```text $ picotool help otp white-label @@ -887,7 +992,7 @@ OPTIONS: ``` ```text -$ picotool otp white-label -s 0x100 ../sample-wl.json +$ picotool otp white-label -s 0x100 sample-wl.json Setting attributes 20e0 0x2e8b, 0x000e, 0x0215, 0x0c09, 0x1090, 0x200c, 0x2615, 0x20e0, 0x310b, 0x3706, 0x3a04, 0x3c04, 0x3e21, 0x4f15, 0x5a0a, 0x5f0a, 0x007a, 0x00df, 0x6c34, 0xd83c, 0xdf4c, 0x0020, 0x0054, 0x0065, 0x0073, 0x0074, 0x0027, 0x0073, 0x0020, 0x0050, 0x0069, 0x0073, 0x6554, 0x7473, 0x5220, 0x3250, 0x3533, 0x3f30, 0x6f6e, 0x6e74, 0x6365, 0x7365, 0x6173, 0x6972, 0x796c, 0x6e61, 0x6d75, 0x6562, 0x0072, 0x6554, 0x7473, 0x6950, 0x4220, 0x6f6f, 0x0074, 0x6554, @@ -929,7 +1034,7 @@ Device Descriptor: This command will run a binary on your device in order to set the OTP permissions, as these are not directly accessible from `picotool` on due to the default permissions settings required to fix errata XXX on RP2350. Because it runs a binary, the binary needs to be sign it if secure boot is enabled. The binary will print what it is doing over uart, which can be configured using the UART Configuration arguments. You can define your OTP permissions in a json file, an example of which -is in [sample-permissions.json](sample-permissions.json). +is in [sample-permissions.json](json/sample-permissions.json). The schema for this JSON file is [here](json/schemas/permissions-schema.json) ```text $ picotool help otp permissions @@ -978,7 +1083,7 @@ OPTIONS: ``` ```text -$ picotool otp permissions --sign private.pem --tx 46 ../sample-permissions.json +$ picotool otp permissions --sign private.pem --tx 46 sample-permissions.json Picking file ./xip_ram_perms.elf page10 page10 = 0 diff --git a/bazel/mbedtls.BUILD b/bazel/mbedtls.BUILD index c3342b1..1c9fa43 100644 --- a/bazel/mbedtls.BUILD +++ b/bazel/mbedtls.BUILD @@ -9,10 +9,10 @@ cc_library( "library/*.h", ], ), + includes = ["include"], linkopts = select({ "@rules_cc//cc/compiler:msvc-cl": ["-DEFAULTLIB:AdvAPI32.Lib"], "//conditions:default": [], }), - includes = ["include"], deps = ["@picotool//lib:mbedtls_config"], ) diff --git a/bintool/BUILD.bazel b/bintool/BUILD.bazel index 82ede3b..a9f4961 100644 --- a/bintool/BUILD.bazel +++ b/bintool/BUILD.bazel @@ -12,6 +12,7 @@ cc_library( "metadata.h", ], copts = select({ + "@rules_cc//cc/compiler:msvc-cl": ["/std:c++20"], "@platforms//os:windows": [], "//conditions:default": [ "-Wno-unused-variable", diff --git a/bintool/bintool.cpp b/bintool/bintool.cpp index c5462d2..c8fa8dc 100644 --- a/bintool/bintool.cpp +++ b/bintool/bintool.cpp @@ -453,6 +453,120 @@ block place_new_block(std::vector &bin, uint32_t storage_addr, std::uni } +// Checksum stuff +uint32_t poly8_lookup[256] = +{ + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, + 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, + 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, + 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, + 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, + 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, + 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, + 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, + 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, + 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, + 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, + 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, + 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, + 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, + 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D +}; + +uint32_t crc32_byte(const uint8_t *p, uint32_t bytelength) +{ + uint32_t crc = 0xffffffff; + while (bytelength-- !=0) crc = poly8_lookup[((uint8_t) crc ^ *(p++))] ^ (crc >> 8); + return crc; +} + +uint8_t rev_8(uint8_t b) { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; +} + +uint32_t rev_32(uint32_t b) { + uint8_t b0 = rev_8(b >> 24); + uint8_t b1 = rev_8(b >> 16); + uint8_t b2 = rev_8(b >> 8); + uint8_t b3 = rev_8(b); + return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0; +} + +void crc32(const uint8_t *data, size_t len, uint32_t* cs_out) { + uint8_t rev_data[252] = {}; + for (size_t i=0; i < sizeof(rev_data); i++) { + rev_data[i] = rev_8(data[i]); + } + + uint32_t crc = crc32_byte(rev_data, sizeof(rev_data)); + + crc = rev_32(crc); + + *cs_out = crc; +} + +uint32_t calc_checksum(std::vector bin) { + assert(bin.size() == 252); + + uint32_t checksum = 0; + crc32(bin.data(), bin.size(), &checksum); + + return checksum; +} + + #if HAS_MBEDTLS void hash_andor_sign_block(block *new_block, const public_t public_key, const private_t private_key, bool hash_value, bool sign, std::vector to_hash) { std::shared_ptr hash_def = std::make_shared(PICOBIN_HASH_SHA256); @@ -554,21 +668,29 @@ std::vector get_lm_hash_data(elf_file *elf, block *new_block, bool clea for(const auto &entry : load_map->entries) { std::vector data; uint32_t current_storage_address = entry.storage_address; - while (data.size() < entry.size) { - auto seg = elf->segment_from_physical_address(current_storage_address); - if (seg == nullptr) { - fail(ERROR_NOT_POSSIBLE, "The ELF file does not contain the storage address %x", current_storage_address); + if (current_storage_address == 0) { + std::copy( + (uint8_t*)&entry.size, + (uint8_t*)&entry.size + sizeof(entry.size), + std::back_inserter(to_hash)); + DEBUG_LOG("CLEAR %08x + %08x\n", (int)entry.runtime_address, (int)entry.size); + } else { + while (data.size() < entry.size) { + auto seg = elf->segment_from_physical_address(current_storage_address); + if (seg == nullptr) { + fail(ERROR_NOT_POSSIBLE, "The ELF file does not contain the storage address %x", current_storage_address); + } + const auto new_data = elf->content(*seg); + + uint32_t offset = current_storage_address - seg->physical_address(); + + std::copy(new_data.begin()+offset, new_data.end(), std::back_inserter(data)); + current_storage_address += new_data.size(); } - const auto new_data = elf->content(*seg); - - uint32_t offset = current_storage_address - seg->physical_address(); - - std::copy(new_data.begin()+offset, new_data.end(), std::back_inserter(data)); - current_storage_address += new_data.size(); + data.resize(entry.size); + std::copy(data.begin(), data.end(), std::back_inserter(to_hash)); + DEBUG_LOG("HASH %08x + %08x\n", (int)entry.storage_address, (int)data.size()); } - data.resize(entry.size); - std::copy(data.begin(), data.end(), std::back_inserter(to_hash)); - DEBUG_LOG("HASH %08x + %08x\n", (int)entry.storage_address, (int)data.size()); } } @@ -613,7 +735,7 @@ std::vector get_lm_hash_data(std::vector bin, uint32_t storage (uint8_t*)&entry.size, (uint8_t*)&entry.size + sizeof(entry.size), std::back_inserter(to_hash)); - DEBUG_LOG("HASH CLEAR %08x + %08x\n", (int)entry.storage_address, (int)entry.size); + DEBUG_LOG("CLEAR %08x + %08x\n", (int)entry.runtime_address, (int)entry.size); } else { uint32_t rel_addr = entry.storage_address - storage_addr; std::copy( diff --git a/bintool/bintool.h b/bintool/bintool.h index b2c6499..77e89c8 100644 --- a/bintool/bintool.h +++ b/bintool/bintool.h @@ -34,6 +34,7 @@ std::unique_ptr find_first_block(std::vector bin, uint32_t stora std::unique_ptr get_last_block(std::vector &bin, uint32_t storage_addr, std::unique_ptr &first_block, get_more_bin_cb more_cb = nullptr); std::vector> get_all_blocks(std::vector &bin, uint32_t storage_addr, std::unique_ptr &first_block, get_more_bin_cb more_cb = nullptr); block place_new_block(std::vector &bin, uint32_t storage_addr, std::unique_ptr &first_block); +uint32_t calc_checksum(std::vector bin); #if HAS_MBEDTLS std::vector hash_andor_sign(std::vector bin, uint32_t storage_addr, uint32_t runtime_addr, block *new_block, const public_t public_key, const private_t private_key, bool hash_value, bool sign, bool clear_sram = false); std::vector encrypt(std::vector bin, uint32_t storage_addr, uint32_t runtime_addr, block *new_block, const private_t aes_key, const public_t public_key, const private_t private_key, bool hash_value, bool sign); diff --git a/bintool/metadata.h b/bintool/metadata.h index 2a3d6cb..605bf63 100644 --- a/bintool/metadata.h +++ b/bintool/metadata.h @@ -225,8 +225,55 @@ struct partition_table_item : public single_byte_size_item { partition_table_item() = default; explicit partition_table_item(uint32_t unpartitioned_flags, bool singleton) : unpartitioned_flags(unpartitioned_flags), singleton(singleton) {} - template static std::shared_ptr parse(I it, I end, uint32_t header) { - return nullptr; + template static std::shared_ptr parse(I& it, I end, uint32_t header) { + uint32_t size = decode_size(header); + uint8_t singleton_count = header >> 24; + bool singleton = singleton_count & 0x80; + uint8_t partition_count = singleton_count & 0x0f; + uint32_t unpartitioned_flags = *it++; + + auto pt = std::make_shared(unpartitioned_flags, singleton); + + std::vector data; + for (unsigned int i=2; i < size; i++) { + data.push_back(*it++); + } + int i=0; + while (i < data.size()) { + partition new_p; + uint32_t permissions_locations = data[i++]; + new_p.permissions = (permissions_locations & PICOBIN_PARTITION_PERMISSIONS_BITS) >> PICOBIN_PARTITION_PERMISSIONS_LSB; + new_p.first_sector = (permissions_locations & PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_BITS) >> PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_LSB; + new_p.last_sector = (permissions_locations & PICOBIN_PARTITION_LOCATION_LAST_SECTOR_BITS) >> PICOBIN_PARTITION_LOCATION_LAST_SECTOR_LSB; + + uint32_t permissions_flags = data[i++]; + uint8_t permissions2 = (permissions_flags & PICOBIN_PARTITION_PERMISSIONS_BITS) >> PICOBIN_PARTITION_PERMISSIONS_LSB; + if (new_p.permissions != permissions2) { + printf("Permissions mismatch %02x %02x\n", new_p.permissions, permissions2); + assert(false); + } + new_p.flags = permissions_flags & (~PICOBIN_PARTITION_PERMISSIONS_BITS); + + if (new_p.flags & PICOBIN_PARTITION_FLAGS_HAS_ID_BITS) { + new_p.id = (uint64_t)data[i++] | ((uint64_t)data[i++] << 32); + } + + uint8_t num_extra_families = (new_p.flags & PICOBIN_PARTITION_FLAGS_ACCEPTS_NUM_EXTRA_FAMILIES_BITS) >> PICOBIN_PARTITION_FLAGS_ACCEPTS_NUM_EXTRA_FAMILIES_LSB; + for (int fam=0; fam < num_extra_families; fam++) { + new_p.extra_families.push_back(data[i++]); + } + + if (new_p.flags & PICOBIN_PARTITION_FLAGS_HAS_NAME_BITS) { + auto bytes = words_to_lsb_bytes(data.begin() + i++, data.end()); + int name_size = bytes[0]; + // This works neatly - accounts for the size byte at the start + i += name_size / 4; + new_p.name = std::string((char*)(bytes.data() + 1), name_size); + } + + pt->partitions.push_back(new_p); + } + return pt; } std::vector to_words(item_writer_context& ctx) const override { @@ -367,7 +414,11 @@ struct load_map_item : public item { if (absolute) { rc.push_back(entry.storage_address); rc.push_back(entry.runtime_address); - rc.push_back(entry.runtime_address + entry.size); + if (entry.storage_address != 0) { + rc.push_back(entry.runtime_address + entry.size); + } else { + rc.push_back(entry.size); + } } else { if (entry.storage_address != 0) { rc.push_back(entry.storage_address - ctx.base_addr - ctx.word_offset * 4); @@ -570,7 +621,7 @@ struct block { i = image_type_item::parse(it, end, header); break; case PICOBIN_BLOCK_ITEM_PARTITION_TABLE: - i = ignored_item::parse(it, end, header); + i = partition_table_item::parse(it, end, header); break; case PICOBIN_BLOCK_ITEM_1BS_VECTOR_TABLE: i = vector_table_item::parse(it, end, header); diff --git a/cli.h b/cli.h index 1bc766b..6db9b4f 100644 --- a/cli.h +++ b/cli.h @@ -432,13 +432,18 @@ namespace cli { base = 2; } try { - lvalue = std::stoll(value, &pos, base); + if (std::is_signed()) { + lvalue = std::stoll(value, &pos, base); + } else { + lvalue = std::stoull(value, &pos, base); + } if (pos != value.length()) { return "Garbage after integer value: " + value.substr(pos); } } catch (std::invalid_argument&) { return value + " is not a valid integer"; } catch (std::out_of_range&) { + return value + " is out of range"; } if (lvalue != (int64_t)lvalue) { return value + " is too big"; diff --git a/cmake/FindLIBUSB.cmake b/cmake/FindLIBUSB.cmake index ab0d303..e5defef 100644 --- a/cmake/FindLIBUSB.cmake +++ b/cmake/FindLIBUSB.cmake @@ -25,7 +25,9 @@ else (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) if (PKG_CONFIG_FOUND) pkg_check_modules(PC_LIBUSB libusb-1.0) - else () + endif() + + if (NOT PC_LIBUSB_FOUND) # As the pkg-config was not found we are probably building under windows. # Determine the architecture of the host, to choose right library if (NOT DEFINED ARCHITECTURE) @@ -56,5 +58,10 @@ else (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBUSB DEFAULT_MSG LIBUSB_LIBRARIES LIBUSB_INCLUDE_DIR) + + # Don't use .dll.a libraries, as they require the .dll file to be in the correct location + # Replace with .a for static linking instead + string(REPLACE ".dll.a" ".a" LIBUSB_LIBRARIES ${LIBUSB_LIBRARIES}) + MARK_AS_ADVANCED(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES) endif (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) diff --git a/default-pt.json b/default-pt.json deleted file mode 100644 index b057d7a..0000000 --- a/default-pt.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "version": [1, 0], - "unpartitioned": { - "families": ["absolute"], - "permissions": { - "secure": "rw", - "nonsecure": "rw", - "bootloader": "rw" - } - }, - "partitions": [ - { - "name": "A", - "id": 0, - "size": "2044K", - "families": ["rp2350-arm-s", "rp2350-riscv"], - "permissions": { - "secure": "rw", - "nonsecure": "rw", - "bootloader": "rw" - } - }, - { - "name": "B", - "id": 1, - "size": "2044K", - "families": ["rp2350-arm-s", "rp2350-riscv"], - "permissions": { - "secure": "rw", - "nonsecure": "rw", - "bootloader": "rw" - }, - "link": ["a", 0] - } - ] -} \ No newline at end of file diff --git a/elf/elf_file.cpp b/elf/elf_file.cpp index 436e8fe..febb318 100644 --- a/elf/elf_file.cpp +++ b/elf/elf_file.cpp @@ -150,7 +150,10 @@ int rp_check_elf_header(const elf32_header &eh) { if (eh.common.machine != EM_ARM && eh.common.machine != EM_RISCV) { fail(ERROR_FORMAT, "Not an Arm or RISC-V executable"); } - if (eh.common.abi != 0) { + // Accept either ELFOSABI_NONE or ELFOSABI_GNU for EI_OSABI. Compilers may + // set the OS/ABI field to ELFOSABI_GNU when they use GNU features, such as + // the SHF_GNU_RETAIN section flag, but the binary is still compatible. + if (eh.common.abi != 0 /* NONE */ && eh.common.abi != 3 /* GNU */) { fail(ERROR_INCOMPATIBLE, "Unrecognized ABI"); } // todo amy not sure if this should be expected or not - we have HARD float in clang only for now @@ -227,6 +230,15 @@ void elf_file::flatten(void) { } idx++; } + + idx = 0; + for (const auto &ph : ph_entries) { + if (ph.filez) { + elf_bytes.resize(std::max(ph.offset + ph.filez, (uint32_t)elf_bytes.size())); + memcpy(&elf_bytes[ph.offset], &ph_data[idx][0], ph.filez); + } + idx++; + } if (verbose) printf("Elf file size %zu\n", elf_bytes.size()); } @@ -262,6 +274,18 @@ void elf_file::read_sh_data(void) { } } +void elf_file::read_ph_data(void) { + int ph_idx = 0; + ph_data.resize(eh.ph_num); + for (const auto &ph: ph_entries) { + if (ph.filez) { + ph_data[ph_idx].resize(ph.filez); + read_bytes(ph.offset, ph.filez, &ph_data[ph_idx][0]); + } + ph_idx++; + } +} + const std::string elf_file::section_name(uint32_t sh_name) const { if (!eh.sh_str_index || eh.sh_str_index > eh.sh_num) return ""; @@ -369,6 +393,7 @@ int elf_file::read_file(std::shared_ptr file) { read_sh(); } read_sh_data(); + read_ph_data(); } catch (const std::ios_base::failure &e) { std::cerr << "Failed to read elf file" << std::endl; @@ -415,6 +440,7 @@ void elf_file::content(const elf32_ph_entry &ph, const std::vector &con if (verbose) printf("Update segment content offset %x content size %zx physical size %x\n", ph.offset, content.size(), ph.filez); memcpy(&elf_bytes[ph.offset], &content[0], std::min(content.size(), (size_t) ph.filez)); read_sh_data(); // Extract the sections after modifying the content + read_ph_data(); } void elf_file::content(const elf32_sh_entry &sh, const std::vector &content) { @@ -423,6 +449,7 @@ void elf_file::content(const elf32_sh_entry &sh, const std::vector &con if (verbose) printf("Update section content offset %x content size %zx section size %x\n", sh.offset, content.size(), sh.size); memcpy(&elf_bytes[sh.offset], &content[0], std::min(content.size(), (size_t) sh.size)); read_sh_data(); // Extract the sections after modifying the content + read_ph_data(); } const elf32_ph_entry* elf_file::segment_from_physical_address(uint32_t paddr) { @@ -500,6 +527,7 @@ const elf32_ph_entry& elf_file::append_segment(uint32_t vaddr, uint32_t paddr, u sh_entries.push_back(sh); sh_data.push_back(std::vector(size)); ph_entries.back().offset = sh.offset; + ph_data.push_back(std::vector(size)); eh.sh_offset = sh.offset + sh.size; eh.sh_num++; diff --git a/elf/elf_file.h b/elf/elf_file.h index 03b3364..cd4e1f3 100644 --- a/elf/elf_file.h +++ b/elf/elf_file.h @@ -55,6 +55,7 @@ private: void read_ph(void); void read_sh(void); void read_sh_data(void); + void read_ph_data(void); void read_bytes(unsigned offset, unsigned length, void *dest); uint32_t append_section_name(const std::string &sh_name_str); void flatten(void); @@ -65,6 +66,7 @@ private: std::vector ph_entries; std::vector sh_entries; std::vector> sh_data; + std::vector> ph_data; bool verbose; }; int rp_check_elf_header(const elf32_header &eh); diff --git a/elf/portable_endian.h b/elf/portable_endian.h index 4286f91..347dd50 100644 --- a/elf/portable_endian.h +++ b/elf/portable_endian.h @@ -43,27 +43,22 @@ # define __LITTLE_ENDIAN LITTLE_ENDIAN # define __PDP_ENDIAN PDP_ENDIAN -#elif defined(__OpenBSD__) - -# include - -# define __BYTE_ORDER BYTE_ORDER -# define __BIG_ENDIAN BIG_ENDIAN -# define __LITTLE_ENDIAN LITTLE_ENDIAN -# define __PDP_ENDIAN PDP_ENDIAN - -#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) # include +#ifndef be16toh # define be16toh(x) betoh16(x) +#endif +#ifndef le16toh # define le16toh(x) letoh16(x) - +#endif +#ifndef be32toh # define be32toh(x) betoh32(x) +#endif +#ifndef le32toh # define le32toh(x) letoh32(x) - -# define be64toh(x) betoh64(x) -# define le64toh(x) letoh64(x) +#endif #elif defined(__WINDOWS__) @@ -169,4 +164,4 @@ #endif -#endif \ No newline at end of file +#endif diff --git a/elf2uf2/elf2uf2.cpp b/elf2uf2/elf2uf2.cpp index 5a0e495..63ce372 100644 --- a/elf2uf2/elf2uf2.cpp +++ b/elf2uf2/elf2uf2.cpp @@ -129,7 +129,7 @@ uf2_block gen_abs_block(uint32_t abs_block_loc) { uf2_block block; block.magic_start0 = UF2_MAGIC_START0; block.magic_start1 = UF2_MAGIC_START1; - block.flags = UF2_FLAG_FAMILY_ID_PRESENT; + block.flags = UF2_FLAG_FAMILY_ID_PRESENT | UF2_FLAG_EXTENSION_FLAGS_PRESENT; block.payload_size = UF2_PAGE_SIZE; block.num_blocks = 2; block.file_size = ABSOLUTE_FAMILY_ID; @@ -138,6 +138,7 @@ uf2_block gen_abs_block(uint32_t abs_block_loc) { block.block_no = 0; memset(block.data, 0, sizeof(block.data)); memset(block.data, 0xef, UF2_PAGE_SIZE); + *(uint32_t*)&(block.data[UF2_PAGE_SIZE]) = UF2_EXTENSION_RP2_IGNORE_BLOCK; return block; } @@ -145,21 +146,26 @@ bool check_abs_block(uf2_block block) { return std::all_of(block.data, block.data + UF2_PAGE_SIZE, [](uint8_t i) { return i == 0xef; }) && block.magic_start0 == UF2_MAGIC_START0 && block.magic_start1 == UF2_MAGIC_START1 && - block.flags == UF2_FLAG_FAMILY_ID_PRESENT && + (block.flags & ~UF2_FLAG_EXTENSION_FLAGS_PRESENT) == UF2_FLAG_FAMILY_ID_PRESENT && block.payload_size == UF2_PAGE_SIZE && block.num_blocks == 2 && block.file_size == ABSOLUTE_FAMILY_ID && block.magic_end == UF2_MAGIC_END && - block.block_no == 0; + block.block_no == 0 && + !(block.flags & UF2_FLAG_EXTENSION_FLAGS_PRESENT && *(uint32_t*)&(block.data[UF2_PAGE_SIZE]) != UF2_EXTENSION_RP2_IGNORE_BLOCK); } int pages2uf2(std::map>& pages, std::shared_ptr in, std::shared_ptr out, uint32_t family_id, uint32_t abs_block_loc=0) { - // RP2350-E9: add absolute block to start, targeting end of flash by default + // RP2350-E10: add absolute block to start of flash UF2s, targeting end of flash by default if (family_id != ABSOLUTE_FAMILY_ID && family_id != RP2040_FAMILY_ID && abs_block_loc) { - uf2_block block = gen_abs_block(abs_block_loc); - out->write((char*)&block, sizeof(uf2_block)); - if (out->fail()) { - fail_write_error(); + uint32_t base_addr = pages.begin()->first; + address_ranges flash_range = rp2350_address_ranges_flash; + if (is_address_initialized(flash_range, base_addr)) { + uf2_block block = gen_abs_block(abs_block_loc); + out->write((char*)&block, sizeof(uf2_block)); + if (out->fail()) { + fail_write_error(); + } } } uf2_block block; diff --git a/json/default-pt.json b/json/default-pt.json new file mode 100644 index 0000000..83be3dc --- /dev/null +++ b/json/default-pt.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://raw.githubusercontent.com/raspberrypi/picotool/develop/json/schemas/partition-table-schema.json", + "version": [1, 0], + "unpartitioned": { + "families": ["absolute"], + "permissions": { + "secure": "rw", + "nonsecure": "rw", + "bootloader": "rw" + } + }, + "partitions": [ + { + "name": "A", + "id": 0, + "size": "2044K", + "families": ["rp2350-arm-s", "rp2350-riscv"], + "permissions": { + "secure": "rw", + "nonsecure": "rw", + "bootloader": "rw" + } + }, + { + "name": "B", + "id": 1, + "size": "2044K", + "families": ["rp2350-arm-s", "rp2350-riscv"], + "permissions": { + "secure": "rw", + "nonsecure": "rw", + "bootloader": "rw" + }, + "link": ["a", 0] + } + ] +} diff --git a/sample-permissions.json b/json/sample-permissions.json similarity index 79% rename from sample-permissions.json rename to json/sample-permissions.json index fb125d1..0d01ff1 100644 --- a/sample-permissions.json +++ b/json/sample-permissions.json @@ -1,4 +1,5 @@ { + "$schema": "https://raw.githubusercontent.com/raspberrypi/picotool/develop/json/schemas/permissions-schema.json", "10": { "no_key_state": 0, "key_r": 0, diff --git a/sample-wl.json b/json/sample-wl.json similarity index 84% rename from sample-wl.json rename to json/sample-wl.json index 4a74dfd..d48274a 100644 --- a/sample-wl.json +++ b/json/sample-wl.json @@ -1,4 +1,5 @@ { + "$schema": "https://raw.githubusercontent.com/raspberrypi/picotool/develop/json/schemas/whitelabel-schema.json", "device": { "vid": "0x2e8b", "pid": "0x000e", @@ -22,4 +23,4 @@ "model": "My Test Pi", "board_id": "TPI-RP2350" } -} \ No newline at end of file +} diff --git a/json/schemas/otp-contents-schema.json b/json/schemas/otp-contents-schema.json new file mode 100644 index 0000000..aa4f7ac --- /dev/null +++ b/json/schemas/otp-contents-schema.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "OTP Contents", + "description": "Defined contents of the RP-series device OTP", + "type": "array", + "items": { + "description": "OTP Row", + "type": "object", + "properties": { + "crit": { + "description": "Critical Row (use three-of-eight vote encoding)", + "type": "boolean" + }, + "description": { + "description": "Row Description", + "type": "string" + }, + "ecc": { + "description": "ECC Row", + "type": "boolean" + }, + "fields": { + "description": "Fields within row", + "type": "array", + "items": { + "type": "object", + "properties": { + "description": { + "description": "Field Description", + "type": "string" + }, + "mask": { + "description": "Field Bit Mask", + "type": "integer" + }, + "name": { + "description": "Field Name", + "type": "string" + } + }, + "required": ["description", "mask", "name"], + "additionalProperties": false + } + }, + "mask": { + "description": "Row Bit Mask", + "type": "integer" + }, + "name": { + "description": "Row Name", + "type": "string" + }, + "redundancy": { + "description": "Number of redundant rows", + "type": "integer" + }, + "row": { + "description": "OTP Row", + "type": "integer" + }, + "seq_index": { + "description": "Sequence Index", + "type": "integer" + }, + "seq_length": { + "description": "Sequence Length", + "type": "integer" + }, + "seq_prefix": { + "description": "Sequence Prefix", + "type": "string" + } + }, + "required": ["crit", "description"], + "additionalProperties": false + } +} diff --git a/json/schemas/otp-schema.json b/json/schemas/otp-schema.json new file mode 100644 index 0000000..d59bc3a --- /dev/null +++ b/json/schemas/otp-schema.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "OTP Settings", + "description": "OTP Settings", + "type": "object", + "properties": {"$schema": {}}, + "patternProperties": { + "^\\d{1,2}:\\d{1,2}$": { + "description": "Generic OTP Row", + "type": "object", + "properties": { + "ecc": { + "description": "Protect with ECC", + "type": "boolean" + }, + "value": { + "description": "Value to write", + "type": ["array", "string", "integer"], + "pattern": "^0x[0-9a-fA-F]{1,6}$", + "items": { + "description": "Data Byte", + "type": ["string", "integer"], + "pattern": "^0x[0-9a-fA-F]{1,2}$" + } + } + }, + "additionalProperties": false, + "required": ["ecc", "value"] + }, + "^[\\d\\w_]+$": { + "description": "Defined OTP Row", + "type": ["object", "array", "string", "integer"], + "pattern": "^0x[0-9a-fA-F]{1,6}$", + "items": { + "description": "Data Byte", + "type": ["string", "integer"], + "pattern": "^0x[0-9a-fA-F]{1,2}$" + }, + "patternProperties": { + "^[\\d\\w_]+$": { + "description": "OTP Field", + "type": ["string", "integer"], + "pattern": "^0x[0-9a-fA-F]{1,6}$" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/json/schemas/partition-table-schema.json b/json/schemas/partition-table-schema.json new file mode 100644 index 0000000..92eadcd --- /dev/null +++ b/json/schemas/partition-table-schema.json @@ -0,0 +1,158 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Partition Table", + "description": "Layout of the partition table", + "type": "object", + "properties": { + "$schema": {}, + "version": { + "description": "Partition Table Version", + "type": "array", + "prefixItems": [ + { + "description": "Major Version", + "type": "integer", + "minimum": 0 + }, + { + "description": "Minor Version", + "type": "integer", + "minimum": 0 + } + ] + }, + "unpartitioned": { + "description": "Unpartitioned space UF2 families and permissions", + "type": "object", + "properties": { + "families": { + "description": "UF2 families accepted", + "type": "array", + "items": { + "enum": [ + "data", + "absolute", + "rp2040", + "rp2350-arm-s", + "rp2350-arm-ns", + "rp2350-riscv" + ] + } + }, + "permissions": {"$ref": "#/$defs/permissions"} + }, + "required": ["permissions", "families"], + "additionalProperties": false + }, + "partitions": { + "description": "Partitions", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "description": "Partition Name", + "type": "string" + }, + "id": { + "description": "Partition ID", + "type": ["integer", "string"], + "minimum": 0, + "exclusiveMaximum": 18446744073709551616, + "pattern": "^0x[0-9a-fA-F]{1,16}$", + "examples": [ + "0xDED3FFFF01234567", + 29, + "0xdeadbeef" + ] + }, + "start": { + "description": "Partition Start", + "type": ["integer", "string"], + "minimum": 0, + "pattern": "^\\d+(k|K)$" + }, + "size": { + "description": "Partition Size", + "type": ["integer", "string"], + "minimum": 0, + "pattern": "^\\d+(k|K)$" + }, + "families": { + "description": "UF2 families accepted", + "type": "array", + "items": { + "type": "string", + "pattern": "^data|absolute|rp2040|rp2350-arm-s|rp2350-arm-ns|rp2350-riscv|0x[0-9a-fA-F]{1,8}$", + "examples": [ + "data", + "absolute", + "rp2040", + "rp2350-arm-s", + "rp2350-arm-ns", + "rp2350-riscv" + ] + } + }, + "permissions": {"$ref": "#/$defs/permissions"}, + "link": { + "type": "array", + "prefixItems": [ + { + "description": "Link Type", + "enum": ["a", "owner" , "none"] + }, + { + "description": "Link Value", + "type": "integer" + } + ] + }, + "no_reboot_on_uf2_download": { + "description": "Don't reboot after UF2 is downloaded", + "type": "boolean" + }, + "ab_non_bootable_owner_affinity": { + "description": "Pick the non-bootable owner instead", + "type": "boolean" + }, + "ignored_during_riscv_boot": { + "description": "Ignore this partition during Risc-V boot", + "type": "boolean" + }, + "ignored_during_arm_boot": { + "description": "Ignore this partition during Arm boot", + "type": "boolean" + } + }, + "required": ["size", "permissions", "families"], + "additionalProperties": false + } + } + }, + "required": ["unpartitioned", "partitions"], + "additionalProperties": false, + "$defs": { + "permissions": { + "description": "Permissions", + "type": "object", + "properties": { + "secure": { + "description": "Secure Permissions", + "type": "string", + "pattern": "^(r|w){0,2}$" + }, + "nonsecure": { + "description": "Non-Secure Permissions", + "type": "string", + "pattern": "^(r|w){0,2}$" + }, + "bootloader": { + "description": "Bootloader Permissions", + "type": "string", + "pattern": "^(r|w){0,2}$" + } + } + } + } +} diff --git a/json/schemas/permissions-schema.json b/json/schemas/permissions-schema.json new file mode 100644 index 0000000..7956c62 --- /dev/null +++ b/json/schemas/permissions-schema.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "OTP Permissions", + "description": "Setup of OTP page permissions", + "type": "object", + "properties": {"$schema": {}}, + "patternProperties": { + "^[0-6][0-9]$": { + "description": "OTP Page Permissions", + "type": "object", + "properties": { + "no_key_state": { + "description": "State when at least one key is registered for this page and no matching key has been entered: 0 -> read_only, 1 -> inaccessible", + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "key_r": { + "description": "Index 1-6 of a hardware key which must be entered to grant read access, or 0 if no such key is required", + "type": "integer", + "minimum": 0, + "maximum": 6 + }, + "key_w": { + "description": "Index 1-6 of a hardware key which must be entered to grant write access, or 0 if no such key is required", + "type": "integer", + "minimum": 0, + "maximum": 6 + }, + "lock_bl": { + "description": "Dummy lock bits reserved for bootloaders (including the RP2350 USB bootloader) to store their own OTP access permissions: 0 -> read_write, 1 -> read_only, 2 -> Do not use (behaves the same as incaccessible), 3 -> inaccessible", + "type": "integer", + "minimum": 0, + "maximum": 3 + }, + "lock_ns": { + "description": "Lock state for Non-secure accesses to this page: 0 -> read_write, 1 -> read_only, 2 -> Do not use (behaves the same as incaccessible), 3 -> inaccessible", + "type": "integer", + "minimum": 0, + "maximum": 3 + }, + "lock_s": { + "description": "Lock state for Secure accesses to this page: 0 -> read_write, 1 -> read_only, 2 -> Do not use (behaves the same as incaccessible), 3 -> inaccessible", + "type": "integer", + "minimum": 0, + "maximum": 3 + } + } + } + }, + "additionalProperties": false +} diff --git a/json/schemas/whitelabel-schema.json b/json/schemas/whitelabel-schema.json new file mode 100644 index 0000000..d96011a --- /dev/null +++ b/json/schemas/whitelabel-schema.json @@ -0,0 +1,124 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "White Labelling", + "description": "White Labelling Configuration, see section 5.7 in the RP2350 datasheet for more details", + "type": "object", + "properties": { + "$schema": {}, + "device": { + "description": "Device Properties", + "type": "object", + "properties": { + "vid": { + "description": "Vendor ID", + "type": "string", + "pattern": "^0x[0-9a-fA-F]{4}$" + }, + "pid": { + "description": "Product ID", + "type": "string", + "pattern": "^0x[0-9a-fA-F]{4}$" + }, + "bcd": { + "description": "Device Revision", + "type": "number", + "minimum": 0, + "maximum": 99 + }, + "lang_id": { + "description": "Language ID", + "type": "string", + "pattern": "^0x[0-9a-fA-F]{4}$" + }, + "manufacturer": { + "description": "Manufacturer Name (can contain unicode)", + "type": "string", + "maxLength": 30 + }, + "product": { + "description": "Product Name (can contain unicode)", + "type": "string", + "maxLength": 30 + }, + "serial_number": { + "description": "Serial Number (can contain unicode)", + "type": "string", + "maxLength": 30 + }, + "max_power": { + "description": "Max power consumption, in 2mA units", + "type": ["integer", "string"], + "maximum": 255, + "pattern": "^0x[0-9a-fA-F]{1,2}$" + }, + "attributes": { + "description": "Device attributes: bit 7 must be 1, bit 6 is self-powered, bit 5 is remote wakeup, bits 0-4 must be 0", + "type": ["integer", "string"], + "minimum": 128, + "maximum": 224, + "pattern": "^0x[8aceACE]{1}0$" + } + }, + "dependentRequired": { + "max_power": ["attributes"], + "attributes": ["max_power"] + }, + "additionalProperties": false + }, + "scsi": { + "description": "SCSI Inquiry Values", + "type": "object", + "properties": { + "vendor": { + "description": "SCSI Vendor", + "type": "string", + "maxLength": 8 + }, + "product": { + "description": "SCSI Product", + "type": "string", + "maxLength": 16 + }, + "version": { + "description": "SCSI Version", + "type": "string", + "maxLength": 4 + } + }, + "additionalProperties": false + }, + "volume": { + "description": "MSD Volume Configuration", + "type": "object", + "properties": { + "label": { + "description": "Volume Label", + "type": "string", + "maxLength": 11 + }, + "redirect_url": { + "description": "INDEX.HTM Redirect URL", + "type": "string", + "maxLength": 127 + }, + "redirect_name": { + "description": "INDEX.HTM Redirect Name", + "type": "string", + "maxLength": 127 + }, + "model": { + "description": "INFO_UF2.TXT Model Name", + "type": "string", + "maxLength": 127 + }, + "board_id": { + "description": "INFO_UF2.TXT Board ID", + "type": "string", + "maxLength": 127 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/main.cpp b/main.cpp index 4f5b2fe..1f75cd3 100644 --- a/main.cpp +++ b/main.cpp @@ -14,7 +14,7 @@ #include #include #include -#ifndef __APPLE__ +#if !defined(__APPLE__) && !defined(__FreeBSD__) #include #endif #include @@ -42,7 +42,7 @@ #endif #include "bintool.h" #include "elf2uf2.h" -#include "pico/bootrom_constants.h" +#include "boot/bootrom_constants.h" #include "pico/binary_info.h" #include "pico/stdio_usb/reset_interface.h" #include "elf.h" @@ -57,7 +57,7 @@ #endif // missing __builtins on windows -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__clang__) # include # define __builtin_popcount __popcnt static __forceinline int __builtin_ctz(unsigned x) { @@ -75,6 +75,8 @@ static __forceinline int __builtin_ctz(unsigned x) { #define _CRT_SECURE_NO_WARNINGS #endif +#define MAX_REBOOT_TRIES 5 + #define OTP_PAGE_COUNT 64 #define OTP_PAGE_ROWS 64 #define OTP_ROW_COUNT (OTP_PAGE_COUNT * OTP_PAGE_ROWS) @@ -95,25 +97,26 @@ typedef map>> auto memory_names = map{ {memory_type::sram, "RAM"}, - {memory_type::sram_unstriped, "Unstriped RAM"}, {memory_type::flash, "Flash"}, {memory_type::xip_sram, "XIP RAM"}, {memory_type::rom, "ROM"} }; -static string tool_name = "picotool"; +static const string tool_name = "picotool"; -static string hex_string(int64_t value, int width=8, bool prefix=true) { +static const string data_family_name = "data"; +static const string absolute_family_name = "absolute"; +static const string rp2040_family_name = "rp2040"; +static const string rp2350_arm_s_family_name = "rp2350-arm-s"; +static const string rp2350_arm_ns_family_name = "rp2350-arm-ns"; +static const string rp2350_riscv_family_name = "rp2350-riscv"; + +static string hex_string(int64_t value, int width=8, bool prefix=true, bool uppercase=false) { std::stringstream ss; if (prefix) ss << "0x"; - ss << std::setfill('0') << std::setw(width) << std::hex << value; - return ss.str(); -} - -static string HEX_string(int64_t value, int width=8, bool prefix=true) { - std::stringstream ss; - if (prefix) ss << "0x"; - ss << std::setfill('0') << std::setw(width) << std::uppercase << std::hex << value; + ss << std::setfill('0') << std::setw(width); + if (uppercase) ss << std::uppercase; + ss << std::hex << value; return ss.str(); } @@ -139,6 +142,7 @@ std::array, 12> pin_functions_rp2350{{ {"SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO", "SIO"}, {"PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0", "PIO0"}, {"PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1", "PIO1"}, + {"PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2", "PIO2"}, {"XIP CS1", "CORESIGHT TRACECLK","CORESIGHT TRACEDATA0","CORESIGHT TDATA1","CORESIGHT TDATA2","CORESIGHT TDATA3","","","XIP CS1", "", "","", "CLK GPIN", "CLK GPOUT","CLK GPIN", "CLK GPOUT","", "", "", "XIP CS1", "CLK GPIN", "CLK GPOUT","CLK GPIN", "CLK GPOUT","CLK GPOUT","CLK GPOUT","", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "XIP CS1"}, {"USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN", "USB OVCUR DET", "USB VBUS DET", "USB VBUS EN"}, {"", "", "UART0 TX", "UART0 RX", "", "", "UART1 TX", "UART1 RX", "", "", "UART1 TX", "UART1 RX", "", "", "UART0 TX", "UART0 RX", "", "", "UART0 TX", "UART0 RX", "", "", "UART1 TX", "UART1 RX", "", "", "UART1 TX", "UART1 RX", "", "", "UART0 TX", "UART0 RX", "", "", "UART0 TX", "UART0 RX", "", "", "UART1 TX", "UART1 RX", "", "", "UART1 TX", "UART1 RX", "", "", "UART0 TX", "UART0 RX"} @@ -147,8 +151,16 @@ std::array, 12> pin_functions_rp2350{{ std::map otp_regs; #if HAS_LIBUSB -auto bus_device_string = [](struct libusb_device *device) { - return string("Device at bus ") + std::to_string(libusb_get_bus_number(device)) + ", address " + std::to_string(libusb_get_device_address(device)); +auto bus_device_string = [](struct libusb_device *device, model_t model) { + string bus_device; + if (model == rp2040) { + bus_device = string("RP2040 device at bus "); + } else if (model == rp2350) { + bus_device = string("RP2350 device at bus "); + } else { + bus_device = string("Device at bus "); + } + return bus_device + std::to_string(libusb_get_bus_number(device)) + ", address " + std::to_string(libusb_get_device_address(device)); }; #endif @@ -295,35 +307,39 @@ struct family_id : public cli::value_base { // note we cannot capture "this" on_action([&t, nm](string value) { auto ovalue = value; - if (value == "data") { + if (value == data_family_name) { t = DATA_FAMILY_ID; - } else if (value == "absolute") { + } else if (value == absolute_family_name) { t = ABSOLUTE_FAMILY_ID; - } else if (value == "rp2040") { + } else if (value == rp2040_family_name) { t = RP2040_FAMILY_ID; - } else if (value == "rp2350-arm-s") { + } else if (value == rp2350_arm_s_family_name) { t = RP2350_ARM_S_FAMILY_ID; - } else if (value == "rp2350-arm-ns") { + } else if (value == rp2350_arm_s_family_name) { t = RP2350_ARM_NS_FAMILY_ID; - } else if (value == "rp2350-riscv") { + } else if (value == rp2350_riscv_family_name) { t = RP2350_RISCV_FAMILY_ID; } else { - if (value.find("0x") == 0) value = value.substr(2); - size_t pos = 0; - long lvalue = std::numeric_limits::max(); - try { - lvalue = std::stoul(value, &pos, 16); - if (pos != value.length()) { - return "Garbage after hex value: " + value.substr(pos); + if (value.find("0x") == 0) { + value = value.substr(2); + size_t pos = 0; + long lvalue = std::numeric_limits::max(); + try { + lvalue = std::stoul(value, &pos, 16); + if (pos != value.length()) { + return "Garbage after hex value: " + value.substr(pos); + } + } catch (std::invalid_argument &) { + return ovalue + " is not a valid hex value"; + } catch (std::out_of_range &) { } - } catch (std::invalid_argument &) { - return ovalue + " is not a valid hex value"; - } catch (std::out_of_range &) { + if (lvalue != (unsigned int) lvalue) { + return value + " is not a valid 32 bit value"; + } + t = (unsigned int) lvalue; + } else { + return value + " is not a valid family ID"; } - if (lvalue != (unsigned int) lvalue) { - return value + " is not a valid 32 bit value"; - } - t = (unsigned int) lvalue; } return string(""); }); @@ -332,12 +348,12 @@ struct family_id : public cli::value_base { }; string family_name(unsigned int family_id) { - if (family_id == DATA_FAMILY_ID) return "'data'"; - if (family_id == ABSOLUTE_FAMILY_ID) return "'absolute'"; - if (family_id == RP2040_FAMILY_ID) return "'rp2040'"; - if (family_id == RP2350_ARM_S_FAMILY_ID) return "'rp2350-arm-s'"; - if (family_id == RP2350_ARM_NS_FAMILY_ID) return "'rp2350-arm-ns'"; - if (family_id == RP2350_RISCV_FAMILY_ID) return "'rp2350-riscv'"; + if (family_id == DATA_FAMILY_ID) return "'" + data_family_name + "'"; + if (family_id == ABSOLUTE_FAMILY_ID) return "'" + absolute_family_name + "'"; + if (family_id == RP2040_FAMILY_ID) return "'" + rp2040_family_name + "'"; + if (family_id == RP2350_ARM_S_FAMILY_ID) return "'" + rp2350_arm_s_family_name + "'"; + if (family_id == RP2350_ARM_NS_FAMILY_ID) return "'" + rp2350_arm_ns_family_name + "'"; + if (family_id == RP2350_RISCV_FAMILY_ID) return "'" + rp2350_riscv_family_name + "'"; if (!family_id) return "none"; return hex_string(family_id); } @@ -401,6 +417,7 @@ struct _settings { int redundancy = -1; bool raw = false; bool ecc = false; + bool ignore_set = false; bool fuzzy = false; uint32_t value = 0; uint8_t lock0 = 0; @@ -409,6 +426,7 @@ struct _settings { std::vector pages; bool list_pages = false; bool list_no_descriptions = false; + bool list_field_descriptions = false; std::vector selectors; uint32_t row = 0; std::vector extra_files; @@ -417,6 +435,7 @@ struct _settings { struct { bool show_basic = false; bool all = false; + bool show_metadata = false; bool show_pins = false; bool show_device = false; bool show_debug = false; @@ -455,6 +474,7 @@ struct _settings { struct { bool all = false; + bool verify = false; } save; struct { @@ -463,7 +483,11 @@ struct _settings { } version; struct { + #if HAS_MBEDTLS bool hash = true; + #else + bool hash = false; + #endif bool sign = false; bool singleton = false; } partition; @@ -479,6 +503,7 @@ struct _settings { }; _settings settings; std::shared_ptr selected_cmd; +model_t selected_model = unknown; auto device_selection = ( @@ -575,6 +600,7 @@ struct info_command : public cmd { return ( ( option('b', "--basic").set(settings.info.show_basic) % "Include basic information. This is the default" + + option('m', "--metadata").set(settings.info.show_metadata) % "Include all metadata blocks" + option('p', "--pins").set(settings.info.show_pins) % "Include pin information" + option('d', "--device").set(settings.info.show_device) % "Include device information" + option("--debug").set(settings.info.show_debug) % "Include device debug information" + @@ -583,7 +609,7 @@ struct info_command : public cmd { ).min(0).doc_non_optional(true) % "Information to display" + ( #if HAS_LIBUSB - device_selection % "To target one or more connected RP2040 device(s) in BOOTSEL mode (the default)" | + device_selection % "To target one or more connected RP-series device(s) in BOOTSEL mode (the default)" | #endif file_selection % "To target a file" ).major_group("TARGET SELECTION").min(0).doc_non_optional(true) @@ -591,7 +617,7 @@ struct info_command : public cmd { } string get_doc() const override { - return "Display information from the target device(s) or file.\nWithout any arguments, this will display basic information for all connected RP2040 devices in BOOTSEL mode"; + return "Display information from the target device(s) or file.\nWithout any arguments, this will display basic information for all connected RP-series devices in BOOTSEL mode"; } }; @@ -614,7 +640,7 @@ struct config_command : public cmd { (option('g', "--group") & value("group").set(settings.config.group)) % "Filter by feature group" + ( #if HAS_LIBUSB - device_selection % "To target one or more connected RP2040 device(s) in BOOTSEL mode (the default)" | + device_selection % "To target one or more connected RP-series device(s) in BOOTSEL mode (the default)" | #endif file_selection % "To target a file" ).major_group("TARGET SELECTION").min(0).doc_non_optional(true) @@ -665,6 +691,9 @@ struct save_command : public cmd { hex("to").set(settings.to) % "The upper address bound in hex" ).min(0).doc_non_optional(true) ).min(0).doc_non_optional(true).no_match_beats_error(false) % "Selection of data to save" + + option('v', "--verify").set(settings.save.verify) % "Verify the data was saved correctly" + + (option("--family") % "Specify the family ID to save the file as" & + family_id("family_id").set(settings.family_id) % "family ID to save file as").force_expand_help(true) + ( // note this parenthesis seems to help with error messages for say save --foo device_selection % "Source device selection" + file_selection % "File to save to" @@ -685,7 +714,7 @@ struct load_command : public cmd { ( option("--ignore-partitions").set(settings.load.ignore_pt) % "When writing flash data, ignore the partition table and write to absolute space" + (option("--family") % "Specify the family ID of the file to load" & - family_id("family_id").set(settings.family_id) % "family id to use for load").force_expand_help(true) + + family_id("family_id").set(settings.family_id) % "family ID to use for load").force_expand_help(true) + (option('p', "--partition") % "Specify the partition to load into" & integer("partition").set(settings.load.partition) % "partition to load into").force_expand_help(true) + option('n', "--no-overwrite").set(settings.load.no_overwrite) % "When writing flash data, do not overwrite an existing program in flash. If picotool cannot determine the size/presence of the program in flash, the command fails" + @@ -707,6 +736,34 @@ struct load_command : public cmd { return "Load the program / memory range stored in a file onto the device."; } }; + +struct erase_command : public cmd { + erase_command() : cmd("erase") {} + bool execute(device_map &devices) override; + + group get_cli() override { + return ( + ( + option('a', "--all") % "Erase all of flash memory. This is the default" | + ( + option('p', "--partition") % "Erase a partition" & + integer("partition").set(settings.load.partition) % "Partition number to erase" + ).min(0).doc_non_optional(true) | + ( + option('r', "--range").set(settings.range_set) % "Erase a range of memory. Note that erases must be 4096 byte-aligned, so the range is expanded accordingly" & + hex("from").set(settings.from) % "The lower address bound in hex" & + hex("to").set(settings.to) % "The upper address bound in hex" + ).min(0).doc_non_optional(true) + ).min(0).doc_non_optional(true).no_match_beats_error(false) % "Selection of data to erase" + + ( // note this parenthesis seems to help with error messages for say erase --foo + device_selection % "Source device selection" + ) + ); + } + string get_doc() const override { + return "Erase the program / memory stored in flash on the device."; + } +}; #endif #if HAS_MBEDTLS @@ -812,7 +869,7 @@ struct partition_info_command : public cmd { group get_cli() override { return ( - (option('m', "--family") & family_id("family_id").set(settings.family_id)) % "family id (will show target partition for said family)" + + (option('m', "--family") & family_id("family_id").set(settings.family_id)) % "family ID (will show target partition for said family)" + device_selection % "Target device selection" ); } @@ -820,9 +877,6 @@ struct partition_info_command : public cmd { string get_doc() const override { return "Print the device's partition table."; } - - void print_permissions(unsigned int p) const; - void insert_default_families(uint32_t flags_and_permissions, vector &family_ids) const; }; #endif @@ -841,23 +895,25 @@ struct partition_create_command : public cmd { (option('o', "--offset").set(settings.offset_set) % "Specify the load address for UF2 file output" & hex("offset").set(settings.offset) % "Load offset (memory address; default 0x10000000)").force_expand_help(true) + (option("--family") % "Specify the family if for UF2 file output" & - family_id("family_id").set(settings.family_id) % "family id for UF2 (default absolute)").force_expand_help(true) + family_id("family_id").set(settings.family_id) % "family ID for UF2 (default absolute)").force_expand_help(true) ).min(0).force_expand_help(true) % "UF2 output options") + optional_typed_file_selection_x("bootloader", 2, "elf") % "embed partition table into bootloader ELF" + ( + #if HAS_MBEDTLS // todo why doesn't this set settings.partition.sign? ((option("--sign").set(settings.partition.sign) & value("keyfile").with_exclusion_filter([](const string &value) { return value.find_first_of('-') == 0; }).set(settings.filenames[3])) % "The file name" + named_file_types_x("pem", 3)) % "Sign the partition table" + (option("--no-hash").clear(settings.partition.hash) % "Don't hash the partition table") + + #endif (option("--singleton").set(settings.partition.singleton) % "Singleton partition table") ).min(0).force_expand_help(true) % "Partition Table Options" #if SUPPORT_A2 + ( option("--abs-block").set(settings.uf2.abs_block) % "Enforce support for an absolute block" + hex("abs_block_loc").set(settings.uf2.abs_block_loc).min(0) % "absolute block location (default to 0x10ffff00)" - ).force_expand_help(true).min(0) % "Errata RP2350-E9 Fix" + ).force_expand_help(true).min(0) % "Errata RP2350-E10 Fix" #endif ); } @@ -865,9 +921,6 @@ struct partition_create_command : public cmd { string get_doc() const override { return "Create a partition table from json"; } - - void print_permissions(unsigned int p) const; - void insert_default_families(uint32_t flags_and_permissions, vector &family_ids) const; }; @@ -896,6 +949,7 @@ struct otp_list_command : public cmd { ( option('p', "--pages").set(settings.otp.list_pages) % "Show page number/page row number" + option('n', "--no-descriptions").set(settings.otp.list_no_descriptions) % "Don't show descriptions" + + option('f', "--field-descriptions").set(settings.otp.list_field_descriptions) % "Show all field descriptions" + (option('i', "--include") & value("filename").add_to(settings.otp.extra_files)).min(0).max(1) % "Include extra otp definition" + // todo more than 1 (value("selector").add_to(settings.otp.selectors) % "The row/field selector, each of which can select a whole row:\n\n" \ @@ -1014,6 +1068,7 @@ struct otp_set_command : public cmd { (option('c', "--copies") & integer("copies").min(1).set(settings.otp.redundancy)) % "Read multiple redundant values" + option('r', "--raw").set(settings.otp.raw) % "Set raw 24 bit values" + option('e', "--ecc").set(settings.otp.ecc) % "Use error correction" + + option('s', "--set-bits").set(settings.otp.ignore_set) % "Set bits only" + (option('i', "--include") & value("filename").add_to(settings.otp.extra_files)).min(0).max(1) % "Include extra otp definition" // todo more than 1 ).min(0).doc_non_optional(true) % "Redundancy/Error Correction Overrides" + ( @@ -1135,13 +1190,13 @@ struct uf2_convert_command : public cmd { hex("offset").set(settings.offset) % "Load offset (memory address; default 0x10000000 for BIN file)" ).force_expand_help(true) % "Packaging Options" + ( - option("--family") & family_id("family_id").set(settings.family_id) % "family id for UF2" + option("--family") & family_id("family_id").set(settings.family_id) % "family ID for UF2" ).force_expand_help(true) % "UF2 Family options" #if SUPPORT_A2 + ( option("--abs-block").set(settings.uf2.abs_block) % "Add an absolute block" + hex("abs_block_loc").set(settings.uf2.abs_block_loc).min(0) % "absolute block location (default to 0x10ffff00)" - ).force_expand_help(true).min(0) % "Errata RP2350-E9 Fix" + ).force_expand_help(true).min(0) % "Errata RP2350-E10 Fix" #endif ); } @@ -1289,6 +1344,7 @@ vector> commands { std::shared_ptr(new link_command()), #if HAS_LIBUSB std::shared_ptr(new save_command()), + std::shared_ptr(new erase_command()), std::shared_ptr(new verify_command()), reboot_cmd, #endif @@ -1353,9 +1409,9 @@ int parse(const int argc, char **argv) { section_header(tool_name); fos.first_column(tab); #if HAS_LIBUSB - fos << "Tool for interacting with RP2040/RP2350 device(s) in BOOTSEL mode, or with an RP2040/RP2350 binary" << "\n"; + fos << "Tool for interacting with RP-series device(s) in BOOTSEL mode, or with an RP-series binary" << "\n"; #else - fos << "Tool for interacting with an RP2040/RP2350 binary" << "\n"; + fos << "Tool for interacting with an RP-series binary" << "\n"; #endif } vector synopsis; @@ -1598,6 +1654,7 @@ SAFE_MAPPING(binary_info_named_group_t); #define BOOTROM_MAGIC_RP2040 0x01754d #define BOOTROM_MAGIC_RP2350 0x02754d +#define BOOTROM_MAGIC_UNKNOWN 0x000000 #define BOOTROM_MAGIC_ADDR 0x00000010 static inline uint32_t rom_table_code(char c1, char c2) { @@ -1683,11 +1740,13 @@ static model_t get_model(memory_access &raw_access) { } } -bool get_int(const std::string& s, int& out) { +template +bool get_int(const std::string& s, T& out) { return integer::parse_string(s, out).empty(); } -bool get_json_int(json value, int& out) { +template +bool get_json_int(json value, T& out) { if (value.is_string()) { string str = value; if (str.back() == 'k' || str.back() == 'K') { @@ -1733,7 +1792,7 @@ uint32_t bootrom_func_lookup(memory_access& access, uint16_t tag) { uint32_t bootrom_table_lookup_rp2350(memory_access& access, uint16_t tag, uint16_t flags) { model_t model = get_model(access); - // we are only used on RP2040 + // we are only used on RP2350 if (model != rp2350) { fail(ERROR_INCOMPATIBLE, "RP2350 BOOT ROM not found"); } @@ -1854,13 +1913,13 @@ struct picoboot_memory_access : public memory_access { } } - // note this does not automatically erase flash + // note this does not automatically erase flash unless erase is set void write(uint32_t address, uint8_t *buffer, unsigned int size) override { + vector write_data; // used when erasing flash if (flash == get_memory_type(address, model)) { connection.exit_xip(); if (erase) { // Do automatically erase flash, and make it aligned - vector write_data; // we have to erase in whole pages range aligned_range(address & ~(FLASH_SECTOR_ERASE_SIZE - 1), ((address + size) & ~(FLASH_SECTOR_ERASE_SIZE - 1)) + FLASH_SECTOR_ERASE_SIZE); @@ -1925,7 +1984,7 @@ struct iostream_memory_access : public memory_access { } void read(uint32_t address, uint8_t *buffer, uint32_t size, bool zero_fill) override { - if (model != unknown && address == BOOTROM_MAGIC_ADDR && size == 4) { + if (address == BOOTROM_MAGIC_ADDR && size == 4) { // return the memory model if (model == rp2040) { *(uint32_t*)buffer = BOOTROM_MAGIC_RP2040; @@ -1933,6 +1992,9 @@ struct iostream_memory_access : public memory_access { } else if (model == rp2350) { *(uint32_t*)buffer = BOOTROM_MAGIC_RP2350; return; + } else { + *(uint32_t*)buffer = BOOTROM_MAGIC_UNKNOWN; + return; } } while (size) { @@ -2321,7 +2383,13 @@ struct bi_visitor_base { for(unsigned int i=0; i<64; i++) { if (pin_mask & (1ull << i)) { if (func != -1) { - pin(i, pin_functions[func][i]); + if (pin_functions[func][i].empty()) { + std::stringstream sstream; + sstream << "Unknown pin function " << func; + pin(i, sstream.str()); + } else { + pin(i, pin_functions[func][i]); + } } else { auto sep = name.find_first_of('|'); auto cur = name.substr(0, sep); @@ -2589,14 +2657,11 @@ void build_rmap_elf(std::shared_ptrfile, range_map& rmap) } } -bool is_valid_family_id(uint32_t family_id) { - return family_id >= RP2040_FAMILY_ID && family_id <= FAMILY_ID_MAX; -} - -void build_rmap_uf2(std::shared_ptrfile, range_map& rmap) { +uint32_t build_rmap_uf2(std::shared_ptrfile, range_map& rmap, uint32_t family_id=0) { file->seekg(0, ios::beg); uf2_block block; unsigned int pos = 0; + uint32_t next_family_id = 0; do { file->read((char*)&block, sizeof(uf2_block)); if (file->fail()) { @@ -2606,22 +2671,46 @@ void build_rmap_uf2(std::shared_ptrfile, range_map& rmap) if (block.magic_start0 == UF2_MAGIC_START0 && block.magic_start1 == UF2_MAGIC_START1 && block.magic_end == UF2_MAGIC_END) { if (block.flags & UF2_FLAG_FAMILY_ID_PRESENT && - is_valid_family_id(block.file_size) && - !(block.flags & UF2_FLAG_NOT_MAIN_FLASH) && block.payload_size == PAGE_SIZE) { + !(block.flags & UF2_FLAG_NOT_MAIN_FLASH) && block.payload_size == PAGE_SIZE && + (!family_id || block.file_size == family_id)) { #if SUPPORT_A2 - // ignore the absolute block + // ignore the absolute block, but save the address if (check_abs_block(block)) { - DEBUG_LOG("Ignoring RP2350-E9 absolute block\n"); + DEBUG_LOG("Ignoring RP2350-E10 absolute block\n"); + settings.uf2.abs_block_loc = block.target_addr; } else { rmap.insert(range(block.target_addr, block.target_addr + PAGE_SIZE), pos + offsetof(uf2_block, data[0])); + family_id = block.file_size; + next_family_id = 0; } #else rmap.insert(range(block.target_addr, block.target_addr + PAGE_SIZE), pos + offsetof(uf2_block, data[0])); + family_id = block.file_size; + next_family_id = 0; + #endif + } else if (block.file_size != family_id && family_id && !next_family_id) { + #if SUPPORT_A2 + if (!check_abs_block(block)) { + #endif + next_family_id = block.file_size; + #if SUPPORT_A2 + } #endif } } pos += sizeof(uf2_block); } while (true); + + return next_family_id; +} + +void build_rmap_load_map(std::shared_ptrload_map, range_map& rmap) { + for (unsigned int i=0; i < load_map->entries.size(); i++) { + auto e = load_map->entries[i]; + if (e.storage_address != 0) { + rmap.insert(range(e.runtime_address, e.runtime_address + e.size), e.storage_address); + } + } } uint32_t find_binary_start(range_map& rmap) { @@ -2645,9 +2734,11 @@ uint32_t find_binary_start(range_map& rmap) { return binary_start; } -template ACCESS get_iostream_memory_access(std::shared_ptr file, filetype type, bool writeable = false) { +template ACCESS get_iostream_memory_access(std::shared_ptr file, filetype type, bool writeable = false, uint32_t *next_family_id=nullptr) { range_map rmap; uint32_t binary_start = 0; + uint32_t tmp = 0; + if (next_family_id != nullptr) tmp = *next_family_id; switch (type) { case filetype::bin: file->seekg(0, std::ios::end); @@ -2659,7 +2750,12 @@ template ACCESS get_iostream_memory_access(st binary_start = find_binary_start(rmap); break; case filetype::uf2: - build_rmap_uf2(file, rmap); + tmp = build_rmap_uf2(file, rmap, tmp); + if (next_family_id != nullptr) { + *next_family_id = tmp; + } else if (tmp) { + fos << "WARNING: Multiple family IDs in a single UF2 file - only using first one\n"; + } binary_start = find_binary_start(rmap); break; default: @@ -2674,11 +2770,11 @@ template ACCESS get_iostream_memory_access(st return ACCESS(file, rmap, binary_start); } -file_memory_access get_file_memory_access(uint8_t idx, bool writeable = false) { +file_memory_access get_file_memory_access(uint8_t idx, bool writeable = false, uint32_t *next_family_id=nullptr) { ios::openmode mode = (writeable ? ios::out|ios::in : ios::in)|ios::binary; auto file = get_file_idx(mode, idx); try { - return get_iostream_memory_access(file, get_file_type_idx(idx), writeable); + return get_iostream_memory_access(file, get_file_type_idx(idx), writeable, next_family_id); } catch (std::exception&) { file->close(); throw; @@ -2788,6 +2884,74 @@ std::unique_ptr find_best_block(memory_access &raw_access, vector find_last_block(memory_access &raw_access, vector &bin) { + // todo read the right amount + uint32_t read_size = 0x1000; + DEBUG_LOG("Reading from %x size %x\n", raw_access.get_binary_start(), read_size); + bin = raw_access.read_vector(raw_access.get_binary_start(), read_size, true); + + std::unique_ptr first_block = find_first_block(bin, raw_access.get_binary_start()); + if (first_block) { + // verify stuff + get_more_bin_cb more_cb = [&raw_access](std::vector &bin, uint32_t new_size) { + DEBUG_LOG("Now reading from %x size %x\n", raw_access.get_binary_start(), new_size); + bin = raw_access.read_vector(raw_access.get_binary_start(), new_size, true); + }; + auto last_block = get_last_block(bin, raw_access.get_binary_start(), first_block, more_cb); + return last_block; + } + + return nullptr; +} + +std::shared_ptr get_bi_access(memory_access &raw_access) { + vector bin; + std::unique_ptr best_block = find_best_block(raw_access, bin); + range_map rmap; + if (best_block) { + auto load_map = best_block->get_item(); + if (load_map != nullptr) { + // Remap for find_binary_info + build_rmap_load_map(load_map, rmap); + } + } + + return std::make_shared(raw_access, rmap); +} + +string str_permissions(unsigned int p) { + static_assert(PICOBIN_PARTITION_PERMISSION_S_R_BITS == (1u << 26), ""); + static_assert(PICOBIN_PARTITION_PERMISSION_S_W_BITS == (1u << 27), ""); + static_assert(PICOBIN_PARTITION_PERMISSION_NS_W_BITS == (1u << 29), ""); + static_assert(PICOBIN_PARTITION_PERMISSION_NSBOOT_W_BITS == (1u << 31), ""); + + std::stringstream ss; + ss << " S("; + unsigned int r = (p >> 26) & 3; + if (r & 1) ss << "r"; + if (r & 2) ss << "w"; else if (!r) ss << "-"; + ss << ") NSBOOT("; + r = (p >> 30) & 3; + if (r & 1) ss << "r"; + if (r & 2) ss << "w"; else if (!r) ss << "-"; + ss << ") NS("; + r = (p >> 28) & 3; + if (r & 1) ss << "r"; + if (r & 2) ss << "w"; else if (!r) ss << "-"; + ss << ")"; + + return ss.str(); +} + +void insert_default_families(uint32_t flags_and_permissions, vector &family_ids) { + if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_ABSOLUTE_BITS) family_ids.emplace_back(absolute_family_name); + if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2040_BITS) family_ids.emplace_back(rp2040_family_name); + if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2350_ARM_S_BITS) family_ids.emplace_back(rp2350_arm_s_family_name); + if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2350_ARM_NS_BITS) family_ids.emplace_back(rp2350_arm_ns_family_name); + if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2350_RISCV_BITS) family_ids.emplace_back(rp2350_riscv_family_name); + if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_DATA_BITS) family_ids.emplace_back(data_family_name); +} + #if HAS_LIBUSB void info_guts(memory_access &raw_access, picoboot::connection *con) { #else @@ -2803,12 +2967,19 @@ void info_guts(memory_access &raw_access, void *con) { vector groups; string current_group; map>> infos; - auto select_group = [&](const group &g1, bool enabled = true) { + // Set enable to true to enable the selected group + auto select_group = [&](const group &g1, bool enable = false) { if (std::find_if(groups.begin(), groups.end(), [&](const group &g2) { return g1.name == g2.name; }) == groups.end()) { groups.push_back(g1); } + auto enable_changed = std::find_if(groups.begin(), groups.end(), [&](const group &g2) { + return g1.name == g2.name && (enable && !g2.enabled); + }); + if (enable_changed != groups.end()) { + enable_changed->enabled = true; + } current_group = g1.name; }; auto info_pair = [&](const string &name, const string &value) { @@ -2817,15 +2988,221 @@ void info_guts(memory_access &raw_access, void *con) { infos[current_group].emplace_back(std::make_pair(name, value)); } }; + auto info_metadata = [&](std::vector bin, block *current_block, bool verbose_metadata = false) { + verified_t hash_verified = none; + verified_t sig_verified = none; + #if HAS_MBEDTLS + verify_block(bin, raw_access.get_binary_start(), raw_access.get_binary_start(), current_block, hash_verified, sig_verified); + #endif + + // Addresses + if (verbose_metadata) { + info_pair("address", hex_string(current_block->physical_addr)); + info_pair("next block address", hex_string(current_block->next_block_rel + current_block->physical_addr)); + if (current_block->get_item() != nullptr) info_pair("block type", "ignored"); + } + + // Image Def + auto image_def = current_block->get_item(); + if (image_def != nullptr) { + if (verbose_metadata) info_pair("block type", "image def"); + if (image_def->image_type() == type_exe) { + switch (image_def->chip()) { + case chip_rp2040: + info_pair("target chip", "RP2040"); + break; + case chip_rp2350: + info_pair("target chip", "RP2350"); + switch (image_def->cpu()) { + case cpu_riscv: + info_pair("image type", "RISC-V"); + break; + case cpu_varmulet: + info_pair("image type", "Varmulet"); + break; + case cpu_arm: + if (image_def->security() == sec_s) { + info_pair("image type", "ARM Secure"); + } else if (image_def->security() == sec_ns) { + info_pair("image type", "ARM Non-Secure"); + } else if (image_def->security() == sec_unspecified) { + info_pair("image type", "ARM"); + } + } + break; + default: + break; + } + } else if (image_def->image_type() == type_data) { + info_pair("image type", "data"); + } + } + + // Partition Table + auto partition_table = current_block->get_item(); + if (partition_table != nullptr) { + if (verbose_metadata) info_pair("block type", "partition table"); + info_pair("partition table", partition_table->singleton ? "singleton" : "non-singleton"); + std::stringstream unpartitioned; + unpartitioned << str_permissions(partition_table->unpartitioned_flags); + std::vector family_ids; + insert_default_families(partition_table->unpartitioned_flags, family_ids); + unpartitioned << ", uf2 { " << cli::join(family_ids, ", ") << " }"; + info_pair("un-partitioned space", unpartitioned.str()); + + for (int i=0; i < partition_table->partitions.size(); i++) { + std::stringstream pstring; + std::stringstream pname; + auto partition = partition_table->partitions[i]; + uint32_t flags = partition.flags; + uint64_t id = partition.id; + pname << "partition " << i; + if ((flags & PICOBIN_PARTITION_FLAGS_LINK_TYPE_BITS) == + PICOBIN_PARTITION_FLAGS_LINK_TYPE_AS_BITS(A_PARTITION)) { + pname << " (B w/ " << ((flags & PICOBIN_PARTITION_FLAGS_LINK_VALUE_BITS) + >> PICOBIN_PARTITION_FLAGS_LINK_VALUE_LSB) + << ")"; + } else if ((flags & PICOBIN_PARTITION_FLAGS_LINK_TYPE_BITS) == + PICOBIN_PARTITION_FLAGS_LINK_TYPE_AS_BITS(OWNER_PARTITION)) { + pname << " (A ob/ " << ((flags & PICOBIN_PARTITION_FLAGS_LINK_VALUE_BITS) + >> PICOBIN_PARTITION_FLAGS_LINK_VALUE_LSB) + << ")"; + } else { + pname << " (A)"; + } + pstring << hex_string(partition.first_sector * 4096, 8, false) << "->" << hex_string((partition.last_sector + 1) * 4096, 8, false); + unsigned int p = partition.permissions; + pstring << str_permissions(p << PICOBIN_PARTITION_PERMISSIONS_LSB); + if (flags & PICOBIN_PARTITION_FLAGS_HAS_ID_BITS) { + pstring << ", id=" << hex_string(id, 16, false); + } + uint32_t num_extra_families = partition.extra_families.size(); + family_ids.clear(); + insert_default_families(flags, family_ids); + for (auto family : partition.extra_families) { + family_ids.emplace_back(hex_string(family)); + } + if (flags & PICOBIN_PARTITION_FLAGS_HAS_NAME_BITS) { + pstring << ", \""; + pstring << partition.name; + pstring << '"'; + } + pstring << ", uf2 { " << cli::join(family_ids, ", ") << " }"; + pstring << ", arm_boot " << !(flags & PICOBIN_PARTITION_FLAGS_IGNORED_DURING_ARM_BOOT_BITS); + pstring << ", riscv_boot " << !(flags & PICOBIN_PARTITION_FLAGS_IGNORED_DURING_RISCV_BOOT_BITS); + info_pair(pname.str(), pstring.str()); + } + } + + // Version + auto version = current_block->get_item(); + if (version != nullptr) { + info_pair("version", std::to_string(version->major) + "." + std::to_string(version->minor)); + if (version->otp_rows.size() > 0) { + info_pair("rollback version", std::to_string(version->rollback)); + std::stringstream rows; + for (const auto row : version->otp_rows) { rows << hex_string(row, 3) << " "; } + info_pair("rollback rows", rows.str()); + } + } + + if (verbose_metadata) { + // Load Map + // todo what should this really report + auto load_map = current_block->get_item(); + if (load_map != nullptr) { + for (unsigned int i=0; i < load_map->entries.size(); i++) { + std::stringstream ss; + auto e = load_map->entries[i]; + if (e.storage_address == 0) { + ss << "Clear 0x" << std::hex << e.runtime_address; + ss << "->0x" << std::hex << e.runtime_address + e.size; + } else if (e.storage_address != e.runtime_address) { + if (is_address_initialized(rp2350_address_ranges_flash, e.runtime_address)) { + ss << "ERROR: COPY TO FLASH NOT PERMITTED "; + } + ss << "Copy 0x" << std::hex << e.storage_address; + ss << "->0x" << std::hex << e.storage_address + e.size; + ss << " to 0x" << std::hex << e.runtime_address; + ss << "->0x" << std::hex << e.runtime_address + e.size; + } else { + ss << "Load 0x" << std::hex << e.storage_address; + ss << "->0x" << std::hex << e.storage_address + e.size; + } + info_pair("load map entry " + std::to_string(i), ss.str()); + } + } + + // Rolling Window Delta + auto rwd = current_block->get_item(); + if (rwd != nullptr) { + info_pair("rolling window delta", hex_string(rwd->addr)); + } + + // Vector Table + auto vtor = current_block->get_item(); + if (vtor != nullptr) { + info_pair("vector table", hex_string(vtor->addr)); + } + + // Entry Point + auto entry_point = current_block->get_item(); + if (entry_point != nullptr) { + std::stringstream ss; + ss << "EP " << hex_string(entry_point->ep); + ss << ", SP " << hex_string(entry_point->sp); + if (entry_point->splim_set) ss << ", SPLIM " << hex_string(entry_point->splim); + info_pair("entry point", ss.str()); + } + } + + // Hash and Sig + if (hash_verified != none) { + info_pair("hash", hash_verified == passed ? "verified" : "incorrect"); + if (verbose_metadata) { + std::shared_ptr hash_value = current_block->get_item(); + assert(hash_value != nullptr); // verify_block would return none if it's not present + std::stringstream val; + for(uint8_t i : hash_value->hash_bytes) { + val << hex_string(i, 2, false, true); + } + info_pair("hash value", val.str()); + } + } + if (sig_verified != none) { + info_pair("signature", sig_verified == passed ? "verified" : "incorrect"); + if (verbose_metadata) { + std::shared_ptr signature = current_block->get_item(); + assert(signature != nullptr); // verify_block would return none if it's not present + std::stringstream sig; + for(uint8_t i : signature->signature_bytes) { + sig << hex_string(i, 2, false, true); + } + info_pair("signature value", sig.str()); + std::stringstream pkey; + for(uint8_t i : signature->public_key_bytes) { + pkey << hex_string(i, 2, false, true); + } + info_pair("public key", pkey.str()); + } + } + }; // establish core groups and their order - if (!settings.info.show_basic && !settings.info.all && !settings.info.show_pins && !settings.info.show_device && !settings.info.show_debug && !settings.info.show_build) { + if (!settings.info.show_basic && !settings.info.all && !settings.info.show_metadata && !settings.info.show_pins && !settings.info.show_device && !settings.info.show_debug && !settings.info.show_build) { settings.info.show_basic = true; } if (settings.info.show_debug && !settings.info.show_device) { settings.info.show_device = true; } auto program_info = group("Program Information", settings.info.show_basic || settings.info.all); + auto no_metadata_info = group("Metadata Blocks", false); + vector metadata_info; + #define MAX_METADATA_BLOCKS 10 + for (int i=1; i <= MAX_METADATA_BLOCKS; i++) { + // These groups are enabled later, depending on how many metadata blocks the binary has + metadata_info.push_back(group("Metadata Block " + std::to_string(i), false)); + } auto pin_info = group("Fixed Pin Information", settings.info.show_pins || settings.info.all); auto build_info = group("Build Information", settings.info.show_build || settings.info.all); auto device_info = group("Device Information", (settings.info.show_device || settings.info.all) & raw_access.is_device()); @@ -2833,11 +3210,16 @@ void info_guts(memory_access &raw_access, void *con) { select_group(program_info); select_group(pin_info); select_group(build_info); + for (auto mb : metadata_info) { + select_group(mb); + } select_group(device_info); binary_info_header hdr; try { - if (find_binary_info(raw_access, hdr)) { - auto access = remapped_memory_access(raw_access, hdr.reverse_copy_mapping); + auto bi_access = get_bi_access(raw_access); + bool has_binary_info = find_binary_info(*bi_access, hdr); + if (has_binary_info) { + auto access = remapped_memory_access(*bi_access, hdr.reverse_copy_mapping); auto visitor = bi_visitor{}; map output; map> pins; @@ -2948,120 +3330,6 @@ void info_guts(memory_access &raw_access, void *con) { if (binary_end) info_pair("binary end", hex_string(binary_end)); - vector bin; - std::unique_ptr best_block = find_best_block(raw_access, bin); - if (best_block) { - verified_t hash_verified = none; - verified_t sig_verified = none; - #if HAS_MBEDTLS - verify_block(bin, raw_access.get_binary_start(), raw_access.get_binary_start(), best_block.get(), hash_verified, sig_verified); - #endif - - // Image Def - auto image_def = best_block->get_item(); - if (image_def != nullptr) { - if (image_def->image_type() == type_exe) { - switch (image_def->chip()) { - case chip_rp2040: - info_pair("target chip", "RP2040"); - break; - case chip_rp2350: - info_pair("target chip", "RP2350"); - switch (image_def->cpu()) { - case cpu_riscv: - info_pair("image type", "RISC-V"); - break; - case cpu_varmulet: - info_pair("image type", "Varmulet"); - break; - case cpu_arm: - if (image_def->security() == sec_s) { - info_pair("image type", "ARM Secure"); - } else if (image_def->security() == sec_ns) { - info_pair("image type", "ARM Non-Secure"); - } else if (image_def->security() == sec_unspecified) { - info_pair("image type", "ARM"); - } - } - break; - default: - break; - } - } else if (image_def->image_type() == type_data) { - info_pair("image type", "data"); - } - } - - // Version - auto version = best_block->get_item(); - if (version != nullptr) { - info_pair("version", std::to_string(version->major) + "." + std::to_string(version->minor)); - if (version->otp_rows.size() > 0) { - info_pair("rollback version", std::to_string(version->rollback)); - std::stringstream rows; - for (const auto row : version->otp_rows) { rows << hex_string(row, 3) << " "; } - info_pair("rollback rows", rows.str()); - } - } - - // Load Map - // todo what should this really report - auto load_map = best_block->get_item(); - if (load_map != nullptr) { - for (unsigned int i=0; i < load_map->entries.size(); i++) { - std::stringstream ss; - auto e = load_map->entries[i]; - if (e.storage_address == 0) { - ss << "Clear 0x" << std::hex << e.runtime_address; - ss << "+0x" << std::hex << e.size; - } else if (e.storage_address != e.runtime_address) { - if (is_address_initialized(rp2350_address_ranges_flash, e.runtime_address)) { - ss << "ERROR: COPY TO FLASH NOT PERMITTED "; - } - ss << "Copy 0x" << std::hex << e.storage_address; - ss << "+0x" << std::hex << e.size; - ss << " to 0x" << std::hex << e.runtime_address; - } else { - ss << "Load 0x" << std::hex << e.storage_address; - ss << "+0x" << std::hex << e.size; - } - info_pair("load map entry " + std::to_string(i), ss.str()); - } - } - - // Rolling Window Delta - auto rwd = best_block->get_item(); - if (rwd != nullptr) { - info_pair("rolling window delta", hex_string(rwd->addr)); - } - - // Vector Table - auto vtor = best_block->get_item(); - if (vtor != nullptr) { - info_pair("vector table", hex_string(vtor->addr)); - } - - // Entry Point - auto entry_point = best_block->get_item(); - if (entry_point != nullptr) { - std::stringstream ss; - ss << "EP " << hex_string(entry_point->ep); - ss << ", SP " << hex_string(entry_point->sp); - if (entry_point->splim_set) ss << ", SPLIM " << hex_string(entry_point->splim); - info_pair("entry point", ss.str()); - } - - // Hash and Sig - if (hash_verified != none) { - info_pair("hash value", hash_verified == passed ? "verified" : "incorrect"); - } - if (sig_verified != none) { - info_pair("signature", sig_verified == passed ? "verified" : "incorrect"); - } - } else if (get_model(raw_access) == rp2350) { - fos << "WARNING: Binary on RP2350 device does not contain a block loop - this binary will not boot\n"; - } - for (auto &f: deferred) { f(); } @@ -3096,6 +3364,39 @@ void info_guts(memory_access &raw_access, void *con) { info_pair("build attributes", cli::join(build_attributes, "\n")); } } + vector bin; + if (settings.info.show_metadata || settings.info.all) { + uint32_t read_size = 0x1000; + DEBUG_LOG("Reading from %x size %x\n", raw_access.get_binary_start(), read_size); + bin = raw_access.read_vector(raw_access.get_binary_start(), read_size, true); + std::unique_ptr first_block = find_first_block(bin, raw_access.get_binary_start()); + if (first_block) { + // verify stuff + get_more_bin_cb more_cb = [&raw_access](std::vector &bin, uint32_t new_size) { + DEBUG_LOG("Now reading from %x size %x\n", raw_access.get_binary_start(), new_size); + bin = raw_access.read_vector(raw_access.get_binary_start(), new_size, true); + }; + auto all_blocks = get_all_blocks(bin, raw_access.get_binary_start(), first_block, more_cb); + + int block_i = 0; + select_group(metadata_info[block_i++], true); + info_metadata(bin, first_block.get(), true); + for (auto &block : all_blocks) { + select_group(metadata_info[block_i++], true); + info_metadata(bin, block.get(), true); + } + } else { + // This displays that there are no metadata blocks + select_group(no_metadata_info, true); + } + } + std::unique_ptr best_block = find_best_block(raw_access, bin); + if (best_block && (settings.info.show_basic || settings.info.all)) { + select_group(program_info); + info_metadata(bin, best_block.get()); + } else if (!best_block && has_binary_info && get_model(raw_access) == rp2350) { + fos << "WARNING: Binary on RP2350 device does not contain a block loop - this binary will not boot\n"; + } } catch (std::invalid_argument &e) { fos << "Error reading binary info\n"; #if HAS_LIBUSB @@ -3257,7 +3558,7 @@ void info_guts(memory_access &raw_access, void *con) { if (model == rp2040) { uint64_t flash_id = 0; con->flash_id(flash_id); - info_pair("flash id", HEX_string(flash_id, 16)); + info_pair("flash id", hex_string(flash_id, 16, true, true)); } } } catch (picoboot::command_failure &e) { @@ -3274,6 +3575,19 @@ void info_guts(memory_access &raw_access, void *con) { #endif bool first = true; int fr_col = fos.first_column(); + // Standardise indent for whole info printout + int tab = 0; + for(const auto& group : groups) { + if (group.enabled) { + const auto& info = infos[group.name]; + if (!info.empty()) { + tab = std::max(tab, group.min_tab); + for(const auto& item : info) { + tab = std::max(tab, 3 + (int)item.first.length()); // +3 for ": " + } + } + } + } for(const auto& group : groups) { if (group.enabled) { const auto& info = infos[group.name]; @@ -3289,10 +3603,6 @@ void info_guts(memory_access &raw_access, void *con) { if (info.empty()) { fos << "none\n"; } else { - int tab = group.min_tab; - for(const auto& item : info) { - tab = std::max(tab, 3 + (int)item.first.length()); // +3 for ": " - } for(const auto& item : info) { fos.first_column(fr_col + 1); fos << (item.first + ":"); @@ -3321,8 +3631,9 @@ void config_guts(memory_access &raw_access) { } } - if (find_binary_info(raw_access, hdr)) { - auto access = remapped_memory_access(raw_access, hdr.reverse_copy_mapping); + auto bi_access = get_bi_access(raw_access); + if (find_binary_info(*bi_access, hdr)) { + auto access = remapped_memory_access(*bi_access, hdr.reverse_copy_mapping); auto visitor = bi_visitor{}; map, pair> named_feature_groups; @@ -3378,6 +3689,7 @@ void config_guts(memory_access &raw_access) { for (auto n : group_names) { auto ints = named_feature_group_ints[n]; auto strings = named_feature_group_strings[n]; + fos.first_column(fr_col); if (!n.empty()) { fos << n << ":\n"; fos.first_column(fr_col + 1); @@ -3429,7 +3741,7 @@ void config_guts(memory_access &raw_access) { string missing_device_string(bool wasRetry, bool requires_rp2350 = false) { char b[256]; - const char* device_name = requires_rp2350 ? "RP2350" : "RP2040/RP2350"; + const char* device_name = requires_rp2350 ? "RP2350" : "RP-series"; if (wasRetry) { strcpy(b, "Despite the reboot attempt, no "); } else { @@ -3445,7 +3757,7 @@ string missing_device_string(bool wasRetry, bool requires_rp2350 = false) { } } else if (settings.bus != -1) { snprintf(buf, buf_len, "accessible %s devices in BOOTSEL mode were found found on bus %d.", device_name, settings.bus); - } else if (!settings.ser.empty() && !wasRetry) { + } else if (!settings.ser.empty()) { snprintf(buf, buf_len, "accessible %s devices in BOOTSEL mode were found found with serial number %s.", device_name, settings.ser.c_str()); } else { snprintf(buf, buf_len, "accessible %s devices in BOOTSEL mode were found.", device_name); @@ -3458,47 +3770,74 @@ bool help_command::execute(device_map &devices) { return false; } +uint32_t get_access_family_id(memory_access &file_access) { + uint32_t family_id = 0; + vector bin; + std::unique_ptr best_block = find_best_block(file_access, bin); + if (best_block == NULL) { + // No block, so RP2040 or absolute + if (file_access.get_binary_start() == FLASH_START) { + vector checksum_data = {}; + file_access.read_into_vector(FLASH_START, 252, checksum_data); + uint32_t checksum = file_access.read_int(FLASH_START + 252); + if (checksum == calc_checksum(checksum_data)) { + // Checksum is correct, so RP2040 + DEBUG_LOG("Detected family ID %s due to boot2 checksum\n", family_name(RP2040_FAMILY_ID).c_str()); + return RP2040_FAMILY_ID; + } else { + // Checksum incorrect, so absolute + DEBUG_LOG("Assumed family ID %s\n", family_name(ABSOLUTE_FAMILY_ID).c_str()); + return ABSOLUTE_FAMILY_ID; + } + } else { + // no_flash RP2040 binaries have no checksum + DEBUG_LOG("Assumed family ID %s\n", family_name(RP2040_FAMILY_ID).c_str()); + return RP2040_FAMILY_ID; + } + } + auto first_item = best_block->items[0].get(); + if (first_item->type() != PICOBIN_BLOCK_ITEM_1BS_IMAGE_TYPE) { + // This will apply for partition tables + DEBUG_LOG("Assumed family ID %s due to block with no IMAGE_DEF\n", family_name(ABSOLUTE_FAMILY_ID).c_str()); + return ABSOLUTE_FAMILY_ID; + } + auto image_def = dynamic_cast(first_item); + if (image_def->image_type() == type_exe) { + if (image_def->chip() == chip_rp2040) { + family_id = RP2040_FAMILY_ID; + } else if (image_def->chip() == chip_rp2350) { + if (image_def->cpu() == cpu_riscv) { + family_id = RP2350_RISCV_FAMILY_ID; + } else if (image_def->cpu() == cpu_arm) { + if (image_def->security() == sec_s) { + family_id = RP2350_ARM_S_FAMILY_ID; + } else if (image_def->security() == sec_ns) { + family_id = RP2350_ARM_NS_FAMILY_ID; + } else { + fail(ERROR_INCOMPATIBLE, "Cannot autodetect UF2 family: Unsupported security level %x\n", image_def->security()); + } + } else { + fail(ERROR_INCOMPATIBLE, "Cannot autodetect UF2 family: Unsupported cpu %x\n", image_def->cpu()); + } + } else { + fail(ERROR_INCOMPATIBLE, "Cannot autodetect UF2 family: Unsupported chip %x\n", image_def->chip()); + } + } else if (image_def->image_type() == type_data) { + family_id = DATA_FAMILY_ID; + } else { + fail(ERROR_INCOMPATIBLE, "Cannot autodetect UF2 family: Unsupported image type %x\n", image_def->image_type()); + } + + return family_id; +} + uint32_t get_family_id(uint8_t file_idx) { uint32_t family_id = 0; if (settings.family_id) { family_id = settings.family_id; } else if (get_file_type_idx(file_idx) == filetype::elf || get_file_type_idx(file_idx) == filetype::bin) { auto file_access = get_file_memory_access(file_idx); - vector bin; - std::unique_ptr best_block = find_best_block(file_access, bin); - if (best_block == NULL) { - // No block, so RP2040 - DEBUG_LOG("Detected family id %s\n", family_name(RP2040_FAMILY_ID).c_str()); - return RP2040_FAMILY_ID; - } - auto first_item = best_block->items[0].get(); - if (first_item->type() != PICOBIN_BLOCK_ITEM_1BS_IMAGE_TYPE) fail(ERROR_INCOMPATIBLE, "Cannot autodetect UF2 family: No IMAGE_DEF found\n"); - auto image_def = dynamic_cast(first_item); - if (image_def->image_type() == type_exe) { - if (image_def->chip() == chip_rp2040) { - family_id = RP2040_FAMILY_ID; - } else if (image_def->chip() == chip_rp2350) { - if (image_def->cpu() == cpu_riscv) { - family_id = RP2350_RISCV_FAMILY_ID; - } else if (image_def->cpu() == cpu_arm) { - if (image_def->security() == sec_s) { - family_id = RP2350_ARM_S_FAMILY_ID; - } else if (image_def->security() == sec_ns) { - family_id = RP2350_ARM_NS_FAMILY_ID; - } else { - fail(ERROR_INCOMPATIBLE, "Cannot autodetect UF2 family: Unsupported security level %x\n", image_def->security()); - } - } else { - fail(ERROR_INCOMPATIBLE, "Cannot autodetect UF2 family: Unsupported cpu %x\n", image_def->cpu()); - } - } else { - fail(ERROR_INCOMPATIBLE, "Cannot autodetect UF2 family: Unsupported chip %x\n", image_def->chip()); - } - } else if (image_def->image_type() == type_data) { - family_id = DATA_FAMILY_ID; - } else { - fail(ERROR_INCOMPATIBLE, "Cannot autodetect UF2 family: Unsupported image type %x\n", image_def->image_type()); - } + family_id = get_access_family_id(file_access); } else if (get_file_type_idx(file_idx) == filetype::uf2) { auto file = get_file_idx(ios::in|ios::binary, file_idx); uf2_block block; @@ -3506,7 +3845,7 @@ uint32_t get_family_id(uint8_t file_idx) { #if SUPPORT_A2 // ignore the absolute block if (check_abs_block(block)) { - DEBUG_LOG("Ignoring RP2350-E9 absolute block\n"); + DEBUG_LOG("Ignoring RP2350-E10 absolute block\n"); file->read((char*)&block, sizeof(block)); } #endif @@ -3515,7 +3854,7 @@ uint32_t get_family_id(uint8_t file_idx) { // todo this can be done - need to add block search for bin files fail(ERROR_FORMAT, "Cannot autodetect UF2 family - must specify the family\n"); } - DEBUG_LOG("Detected family id %s\n", family_name(family_id).c_str());; + DEBUG_LOG("Detected family ID %s\n", family_name(family_id).c_str());; return family_id; } @@ -3595,12 +3934,13 @@ bool config_command::execute(device_map &devices) { int size = devices[dr_vidpid_bootrom_ok].size(); if (size) { if (size > 1) { - fos << "Multiple RP2040 devices in BOOTSEL mode found:\n"; + fos << "Multiple RP-series devices in BOOTSEL mode found:\n"; } for (auto handles : devices[dr_vidpid_bootrom_ok]) { + selected_model = std::get<0>(handles); fos.first_column(0); fos.hanging_indent(0); if (size > 1) { - auto s = bus_device_string(std::get<1>(handles)); + auto s = bus_device_string(std::get<1>(handles), std::get<0>(handles)); string dashes; std::generate_n(std::back_inserter(dashes), s.length() + 1, [] { return '-'; }); fos << "\n" << s << ":\n" << dashes << "\n"; @@ -3637,7 +3977,8 @@ bool config_command::execute(device_map &devices) { bool info_command::execute(device_map &devices) { fos.first_column(0); fos.hanging_indent(0); if (!settings.filenames[0].empty()) { - auto access = get_file_memory_access(0); + uint32_t next_id = 0; + auto access = get_file_memory_access(0, false, &next_id); uint32_t id = 0; id = get_family_id(0); if (id == RP2040_FAMILY_ID) { @@ -3645,20 +3986,42 @@ bool info_command::execute(device_map &devices) { } else if (id >= RP2350_ARM_S_FAMILY_ID && id <= RP2350_ARM_NS_FAMILY_ID) { access.set_model(rp2350); } - fos << "File " << settings.filenames[0] << ":\n\n"; - info_guts(access, nullptr); + if (next_id) { + next_id = id; + while (next_id) { + fos.first_column(0); fos.hanging_indent(0); + std::stringstream s; + s << "File " << settings.filenames[0] << " family ID " << family_name(next_id) << ":"; + if (next_id != id) { + string dashes; + std::generate_n(std::back_inserter(dashes), s.str().length() + 1, [] { return '-'; }); + fos << "\n" << dashes << "\n"; + } + fos << s.str() << "\n\n"; + auto tmp_access = get_file_memory_access(0, false, &next_id); + info_guts(tmp_access, nullptr); + } + } else { + if (get_file_type() == filetype::uf2) { + fos << "File " << settings.filenames[0] << " family ID " << family_name(id) << ":\n\n"; + } else { + fos << "File " << settings.filenames[0] << ":\n\n"; + } + info_guts(access, nullptr); + } return false; } #if HAS_LIBUSB int size = devices[dr_vidpid_bootrom_ok].size(); if (size) { if (size > 1) { - fos << "Multiple RP2040 devices in BOOTSEL mode found:\n"; + fos << "Multiple RP-series devices in BOOTSEL mode found:\n"; } for (auto handles : devices[dr_vidpid_bootrom_ok]) { + selected_model = std::get<0>(handles); fos.first_column(0); fos.hanging_indent(0); if (size > 1) { - auto s = bus_device_string(std::get<1>(handles)); + auto s = bus_device_string(std::get<1>(handles), std::get<0>(handles)); string dashes; std::generate_n(std::back_inserter(dashes), s.length() + 1, [] { return '-'; }); fos << "\n" << s << ":\n" << dashes << "\n"; @@ -3668,6 +4031,11 @@ bool info_command::execute(device_map &devices) { auto partitions = get_partitions(connection); vector starts; if (partitions) { + // Check if bootloader is present, based on presence of binary info + binary_info_header hdr; + auto bi_access = get_bi_access(access); + bool has_bootloader = find_binary_info(*bi_access, hdr); + // Don't show device, until all partitions done bool device = settings.info.show_device || settings.info.all; bool debug = settings.info.show_debug || settings.info.all; @@ -3675,14 +4043,23 @@ bool info_command::execute(device_map &devices) { settings.info.show_basic = true; settings.info.show_pins = true; settings.info.show_build = true; + settings.info.show_metadata = true; settings.info.all = false; } - if ((settings.info.show_basic || settings.info.show_pins || settings.info.show_build) || !(settings.info.show_device || settings.info.show_debug)) { + if ((settings.info.show_basic || settings.info.show_pins || settings.info.show_build || settings.info.show_metadata) || !(settings.info.show_device || settings.info.show_debug)) { settings.info.show_device = false; settings.info.show_debug = false; for (auto range : *partitions) { starts.push_back(std::get<0>(range)); } + if (has_bootloader && std::none_of(starts.cbegin(), starts.cend(), [](int i) { return i == 0; })) { + // Print bootloader info, only if bootloader is present and not in a partition + fos.first_column(0); fos.hanging_indent(0); + fos << "\nBootloader\n"; + fos.first_column(1); + partition_memory_access part_access(access, 0); + info_guts(part_access, &connection); + } for (unsigned int i=0; i < starts.size(); i++) { uint32_t start = starts[i]; fos.first_column(0); fos.hanging_indent(0); @@ -3698,6 +4075,7 @@ bool info_command::execute(device_map &devices) { settings.info.show_basic = false; settings.info.show_pins = false; settings.info.show_build = false; + settings.info.show_metadata = false; settings.info.show_device = device; settings.info.show_debug = debug; info_guts(access, &connection); @@ -3717,6 +4095,7 @@ bool info_command::execute(device_map &devices) { static picoboot::connection get_single_bootsel_device_connection(device_map& devices, bool exclusive = true) { assert(devices[dr_vidpid_bootrom_ok].size() == 1); auto device = devices[dr_vidpid_bootrom_ok][0]; + selected_model = std::get<0>(device); libusb_device_handle *rc = std::get<2>(device); if (!rc) fail(ERROR_USB, "Unable to connect to device"); return picoboot::connection(rc, std::get<0>(device), exclusive); @@ -3734,7 +4113,16 @@ static picoboot::connection get_single_rp2350_bootsel_device_connection(device_m #endif struct progress_bar { - explicit progress_bar(string prefix, int width = 30) : prefix(std::move(prefix)), width(width) { + explicit progress_bar(string new_prefix, int width = 30) : width(width) { + // Align all bars with the longest possible prefix string + auto longest_mem = std::max_element( + std::begin(memory_names), std::end(memory_names), + [] (const auto & p1, const auto & p2) { + return p1.second.length() < p2.second.length(); + } + ); + string extra_space(string("Loading into " + longest_mem->second + ": ").length() - new_prefix.length(), ' '); + prefix = new_prefix + extra_space; progress(0); } @@ -3760,6 +4148,33 @@ struct progress_bar { }; #if HAS_LIBUSB +vector get_coalesced_ranges(iostream_memory_access &file_access, model_t model) { + auto rmap = file_access.get_rmap(); + auto ranges = rmap.ranges(); + std::sort(ranges.begin(), ranges.end(), [](const range& a, const range &b) { + return a.from < b.from; + }); + // coalesce all the contiguous ranges + for(auto i = ranges.begin(); i < ranges.end(); ) { + if (i != ranges.end() - 1) { + uint32_t erase_size; + // we want to coalesce flash sectors together (this ends up creating ranges that may have holes) + if( get_memory_type(i->from, model) == flash ) { + erase_size = FLASH_SECTOR_ERASE_SIZE; + } else { + erase_size = 1; + } + if (i->to / erase_size == (i+1)->from / erase_size) { + i->to = (i+1)->to; + i = ranges.erase(i+1) - 1; + continue; + } + } + i++; + } + return ranges; +} + bool save_command::execute(device_map &devices) { auto con = get_single_bootsel_device_connection(devices); picoboot_memory_access raw_access(con); @@ -3776,6 +4191,9 @@ bool save_command::execute(device_map &devices) { } else { start = settings.from; end = settings.to; + // Set offset for verifying + settings.offset = start; + settings.offset_set = true; } if (end <= start) { fail(ERROR_ARGS, "Save range is invalid/empty"); @@ -3791,11 +4209,18 @@ bool save_command::execute(device_map &devices) { }); visitor.visit(access, hdr); } - if (binary_end == 0) { + end = binary_end; + vector bin; + std::unique_ptr last_block = find_last_block(raw_access, bin); + if (last_block != nullptr) { + uint32_t new_end = last_block->physical_addr + (last_block->to_words().size())*4; + DEBUG_LOG("Adjusting end to max of %x %x\n", end, new_end); + end = MAX(end, new_end); + } + if (end == 0) { fail(ERROR_NOT_POSSIBLE, "Cannot determine the binary size, so cannot save the program only, try --all."); } - end = binary_end; } } else { end = FLASH_START + guess_flash_size(raw_access); @@ -3807,7 +4232,7 @@ bool save_command::execute(device_map &devices) { model_t model = get_model(raw_access); enum memory_type t1 = get_memory_type(start , model); enum memory_type t2 = get_memory_type(end, model); - if (t1 == invalid || t1 != t2) { + if (t1 != t2 || t1 == invalid || t1 == sram_unstriped) { fail(ERROR_NOT_POSSIBLE, "Save range crosses unmapped memory"); } uint32_t size = end - start; @@ -3836,7 +4261,7 @@ bool save_command::execute(device_map &devices) { block.flags = UF2_FLAG_FAMILY_ID_PRESENT; block.payload_size = PAGE_SIZE; block.num_blocks = (size + PAGE_SIZE - 1)/PAGE_SIZE; - block.file_size = RP2040_FAMILY_ID; + block.file_size = settings.family_id ? settings.family_id : get_access_family_id(raw_access); block.magic_end = UF2_MAGIC_END; writer256 = [&](FILE *out, const uint8_t *buffer, unsigned int size, unsigned int offset) { static_assert(512 == sizeof(block), ""); @@ -3875,38 +4300,113 @@ bool save_command::execute(device_map &devices) { throw; } } + + if (settings.save.verify) { + auto file_access = get_file_memory_access(0); + model_t model = get_model(raw_access); + auto ranges = get_coalesced_ranges(file_access, model); + for (auto mem_range : ranges) { + enum memory_type type = get_memory_type(mem_range.from, model); + bool ok = true; + { + progress_bar bar("Verifying " + memory_names[type] + ": "); + uint32_t batch_size = FLASH_SECTOR_ERASE_SIZE; + vector file_buf; + vector device_buf; + uint32_t pos = mem_range.from; + for (uint32_t base = mem_range.from; base < mem_range.to && ok; base += batch_size) { + uint32_t this_batch = std::min(std::min(mem_range.to, end) - base, batch_size); + // note we pass zero_fill = true in case the file has holes, but this does + // mean that the verification will fail if those holes are not filled with zeros + // on the device + file_access.read_into_vector(base, this_batch, file_buf, true); + raw_access.read_into_vector(base, this_batch, device_buf); + assert(file_buf.size() == device_buf.size()); + for (unsigned int i = 0; i < this_batch; i++) { + if (file_buf[i] != device_buf[i]) { + pos = base + i; + printf("Unmatch file %x, device %x, pos %x\n", file_buf[i], device_buf[i], pos); + ok = false; + break; + } + } + if (ok) { + pos = base + this_batch; + } + bar.progress(pos - mem_range.from, mem_range.to - mem_range.from); + } + } + if (ok) { + std::cout << " OK\n"; + } else { + std::cout << " FAILED\n"; + fail(ERROR_VERIFICATION_FAILED, "The device contents did not match the saved file"); + } + } + } + return false; +} + +bool erase_command::execute(device_map &devices) { + auto con = get_single_bootsel_device_connection(devices); + picoboot_memory_access raw_access(con); + + uint32_t end = 0; + uint32_t binary_end = 0; + binary_info_header hdr; + uint32_t start = FLASH_START; + if (settings.load.partition >= 0) { + auto partitions = get_partitions(con); + if (!partitions) { + fail(ERROR_NOT_POSSIBLE, "There is no partition table on the device"); + } + if (settings.load.partition >= partitions->size()) { + fail(ERROR_NOT_POSSIBLE, "There are only %d partitions on the device", partitions->size()); + } + start = std::get<0>((*partitions)[settings.load.partition]); + end = std::get<1>((*partitions)[settings.load.partition]); + printf("Erasing partition %d:\n", settings.load.partition); + printf(" %08x->%08x\n", start, end); + start += FLASH_START; + end += FLASH_START; + if (end <= start) { + fail(ERROR_ARGS, "Erase range is invalid/empty"); + } + } else if (settings.range_set) { + start = settings.from & ~(FLASH_SECTOR_ERASE_SIZE - 1); + end = (settings.to + (FLASH_SECTOR_ERASE_SIZE - 1)) & ~(FLASH_SECTOR_ERASE_SIZE - 1); + if (end <= start) { + fail(ERROR_ARGS, "Erase range is invalid/empty"); + } + } else { + end = FLASH_START + guess_flash_size(raw_access); + if (end <= FLASH_START) { + fail(ERROR_NOT_POSSIBLE, "Cannot determine the flash size, so cannot erase the entirety of flash, try --range."); + } + } + + model_t model = get_model(raw_access); + enum memory_type t1 = get_memory_type(start , model); + enum memory_type t2 = get_memory_type(end, model); + if (t1 != flash || t1 != t2) { + fail(ERROR_NOT_POSSIBLE, "Erase range not all in flash"); + } + uint32_t size = end - start; + + { + progress_bar bar("Erasing: "); + for (uint32_t addr = start; addr < end; addr += FLASH_SECTOR_ERASE_SIZE) { + bar.progress(addr-start, end-start); + con.flash_erase(addr, FLASH_SECTOR_ERASE_SIZE); + } + bar.progress(100); + } + std::cout << "Erased " << size << " bytes\n"; return false; } #endif #if HAS_LIBUSB -vector get_coalesced_ranges(iostream_memory_access &file_access, model_t model) { - auto rmap = file_access.get_rmap(); - auto ranges = rmap.ranges(); - std::sort(ranges.begin(), ranges.end(), [](const range& a, const range &b) { - return a.from < b.from; - }); - // coalesce all the contiguous ranges - for(auto i = ranges.begin(); i < ranges.end(); ) { - if (i != ranges.end() - 1) { - uint32_t erase_size; - // we want to coalesce flash sectors together (this ends up creating ranges that may have holes) - if( get_memory_type(i->from, model) == flash ) { - erase_size = FLASH_SECTOR_ERASE_SIZE; - } else { - erase_size = 1; - } - if (i->to / erase_size == (i+1)->from / erase_size) { - i->to = (i+1)->to; - i = ranges.erase(i+1) - 1; - continue; - } - } - i++; - } - return ranges; -} - bool get_target_partition(picoboot::connection &con, uint32_t* start = nullptr, uint32_t* end = nullptr) { #if SUPPORT_A2 con.exit_xip(); @@ -3920,13 +4420,13 @@ bool get_target_partition(picoboot::connection &con, uint32_t* start = nullptr, con.get_info(&cmd, loc_flags_id_buf, sizeof(loc_flags_id_buf)); assert(loc_flags_id_buf_32[0] == 3); if ((int)loc_flags_id_buf_32[1] < 0) { - printf("Family id %s cannot be downloaded anywhere\n", family_name(settings.family_id).c_str()); + printf("Family ID %s cannot be downloaded anywhere\n", family_name(settings.family_id).c_str()); return false; } else { if (loc_flags_id_buf_32[1] == PARTITION_TABLE_NO_PARTITION_INDEX) { - printf("Family id %s can be downloaded in absolute space:\n", family_name(settings.family_id).c_str()); + printf("Family ID %s can be downloaded in absolute space:\n", family_name(settings.family_id).c_str()); } else { - printf("Family id %s can be downloaded in partition %d:\n", family_name(settings.family_id).c_str(), loc_flags_id_buf_32[1]); + printf("Family ID %s can be downloaded in partition %d:\n", family_name(settings.family_id).c_str(), loc_flags_id_buf_32[1]); } uint32_t location_and_permissions = loc_flags_id_buf_32[2]; uint32_t saddr = ((location_and_permissions >> PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_LSB) & 0x1fffu) * 4096; @@ -3967,7 +4467,7 @@ bool load_guts(picoboot::connection con, iostream_memory_access &file_access) { for (auto mem_range : ranges) { enum memory_type t1 = get_memory_type(mem_range.from, model); enum memory_type t2 = get_memory_type(mem_range.to, model); - if (t1 != t2 || t1 == invalid || t1 == rom) { + if (t1 != t2 || t1 == invalid || t1 == rom || t1 == sram_unstriped) { fail(ERROR_FORMAT, "File to load contained an invalid memory range 0x%08x-0x%08x", mem_range.from, mem_range.to); } @@ -3994,7 +4494,8 @@ bool load_guts(picoboot::connection con, iostream_memory_access &file_access) { uint32_t flash_start_offset = flash_min - FLASH_START; uint32_t size_guess = guess_flash_size(raw_access); if (size_guess > 0) { - if ((flash_start_offset + flash_data_size) > size_guess) { + // Skip check when targeting PSRAM, which is anything above 0x11000000 + if (flash_start_offset < FLASH_END_RP2040 && (flash_start_offset + flash_data_size) > size_guess) { if (flash_start_offset) { fail(ERROR_NOT_POSSIBLE, "File size 0x%x starting at 0x%x is too big to fit in flash size 0x%x", flash_data_size, flash_start_offset, size_guess); } else { @@ -4057,7 +4558,7 @@ bool load_guts(picoboot::connection con, iostream_memory_access &file_access) { if (settings.load.verify) { bool ok = true; { - progress_bar bar("Verifying " + memory_names[type] + ": "); + progress_bar bar("Verifying " + memory_names[type] + ": "); uint32_t batch_size = FLASH_SECTOR_ERASE_SIZE; vector file_buf; vector device_buf; @@ -4162,7 +4663,13 @@ bool load_command::execute(device_map &devices) { settings.offset_set = true; settings.partition_size = end - start; } else { - fail(ERROR_NOT_POSSIBLE, "This file cannot be loaded into the partition table on the device"); + // Check if partition table is present, for correct error message + auto partitions = get_partitions(con); + if (!partitions) { + fail(ERROR_NOT_POSSIBLE, "This file cannot be loaded onto a device with no partition table"); + } else { + fail(ERROR_NOT_POSSIBLE, "This file cannot be loaded into the partition table on the device"); + } } } } @@ -4290,6 +4797,41 @@ void sign_guts_elf(elf_file* elf, private_t private_key, public_t public_key) { new_block.items.push_back(version); } + // Add entry point when signing Arm images + std::shared_ptr image_type = new_block.get_item(); + if (settings.seal.sign && image_type != nullptr && image_type->image_type() == type_exe && image_type->cpu() == cpu_arm) { + std::shared_ptr entry_point = new_block.get_item(); + if (entry_point == nullptr) { + std::shared_ptr vtor = new_block.get_item(); + uint32_t vtor_loc = 0x10000000; + if (vtor != nullptr) { + vtor_loc = vtor->addr; + } else { + if (elf->header().entry >= SRAM_START) { + vtor_loc = 0x20000000; + } else if (elf->header().entry >= XIP_SRAM_START_RP2350) { + vtor_loc = 0x13ffc000; + } else { + vtor_loc = 0x10000000; + std::shared_ptr rwd = new_block.get_item(); + if (rwd != nullptr) { + vtor_loc += rwd->addr; + } + } + } + auto segment = elf->segment_from_physical_address(vtor_loc); + auto content = elf->content(*segment); + auto offset = vtor_loc - segment->physical_address(); + uint32_t ep; + memcpy(&ep, content.data() + offset + 4, sizeof(ep)); + uint32_t sp; + memcpy(&sp, content.data() + offset, sizeof(sp)); + DEBUG_LOG("Adding entry_point_item: ep %08x, sp %08x\n", ep, sp); + entry_point = std::make_shared(ep, sp); + new_block.items.push_back(entry_point); + } + } + hash_andor_sign( elf, &new_block, public_key, private_key, settings.seal.hash, settings.seal.sign, @@ -4297,13 +4839,7 @@ void sign_guts_elf(elf_file* elf, private_t private_key, public_t public_key) { ); } -vector sign_guts_bin(iostream_memory_access in, private_t private_key, public_t public_key) { - auto rmap = in.get_rmap(); - auto ranges = rmap.ranges(); - assert(ranges.size() == 1); - auto bin_start = ranges[0].from; - auto bin_size = ranges[0].len(); - +vector sign_guts_bin(iostream_memory_access in, private_t private_key, public_t public_key, uint32_t bin_start, uint32_t bin_size) { vector bin = in.read_vector(bin_start, bin_size, false); std::unique_ptr first_block = find_first_block(bin, bin_start); @@ -4332,6 +4868,27 @@ vector sign_guts_bin(iostream_memory_access in, private_t private_key, new_block.items.push_back(version); } + // Add entry point when signing Arm images + std::shared_ptr image_type = new_block.get_item(); + if (settings.seal.sign && image_type != nullptr && image_type->image_type() == type_exe && image_type->cpu() == cpu_arm) { + std::shared_ptr entry_point = new_block.get_item(); + if (entry_point == nullptr) { + std::shared_ptr vtor = new_block.get_item(); + uint32_t vtor_loc = bin_start; + if (vtor != nullptr) { + vtor_loc = vtor->addr; + } + auto offset = vtor_loc - bin_start; + uint32_t ep; + memcpy(&ep, bin.data() + offset + 4, sizeof(ep)); + uint32_t sp; + memcpy(&sp, bin.data() + offset, sizeof(sp)); + DEBUG_LOG("Adding entry_point_item: ep %08x, sp %08x\n", ep, sp); + entry_point = std::make_shared(ep, sp); + new_block.items.push_back(entry_point); + } + } + auto sig_data = hash_andor_sign( bin, bin_start, bin_start, &new_block, public_key, private_key, @@ -4346,12 +4903,15 @@ vector sign_guts_bin(iostream_memory_access in, private_t private_key, bool seal_command::execute(device_map &devices) { bool isElf = false; bool isBin = false; + bool isUf2 = false; if (get_file_type() == filetype::elf) { isElf = true; } else if (get_file_type() == filetype::bin) { isBin = true; + } else if (get_file_type() == filetype::uf2) { + isUf2 = true; } else { - fail(ERROR_ARGS, "Can only sign ELFs or BINs"); + fail(ERROR_ARGS, "Can only sign ELFs, BINs or UF2s"); } if (get_file_type_idx(1) != get_file_type()) { @@ -4408,10 +4968,31 @@ bool seal_command::execute(device_map &devices) { elf->write(out); out->close(); } else if (isBin) { - auto sig_data = sign_guts_bin(get_file_memory_access(0), private_key, public_key); + auto access = get_file_memory_access(0); + auto rmap = access.get_rmap(); + auto ranges = rmap.ranges(); + assert(ranges.size() == 1); + auto bin_start = ranges[0].from; + auto bin_size = ranges[0].len(); + + auto sig_data = sign_guts_bin(access, private_key, public_key, bin_start, bin_size); auto out = get_file_idx(ios::out|ios::binary, 1); out->write((const char *)sig_data.data(), sig_data.size()); out->close(); + } else if (isUf2) { + auto access = get_file_memory_access(0); + auto rmap = access.get_rmap(); + auto ranges = rmap.ranges(); + auto bin_start = ranges.front().from; + auto bin_size = ranges.back().to - bin_start; + auto family_id = get_family_id(0); + + auto sig_data = sign_guts_bin(access, private_key, public_key, bin_start, bin_size); + auto tmp = std::make_shared(); + tmp->write(reinterpret_cast(sig_data.data()), sig_data.size()); + auto out = get_file_idx(ios::out|ios::binary, 1); + bin2uf2(tmp, out, bin_start, family_id, settings.uf2.abs_block_loc); + out->close(); } else { fail(ERROR_ARGS, "Must be ELF or BIN"); } @@ -4604,7 +5185,7 @@ bool verify_command::execute(device_map &devices) { for (auto mem_range : ranges) { enum memory_type t1 = get_memory_type(mem_range.from, model); enum memory_type t2 = get_memory_type(mem_range.to, model); - if (t1 != t2 || t1 == invalid) { + if (t1 != t2 || t1 == invalid || t1 == sram_unstriped) { fail(ERROR_NOT_POSSIBLE, "invalid memory range for verification %08x-%08x", mem_range.from, mem_range.to); } else { bool ok = true; @@ -5009,14 +5590,21 @@ bool otp_get_command::execute(device_map &devices) { uint32_t corrected_val = 0; if (m.reg_row / OTP_PAGE_ROWS != last_page) { // todo pre-check page lock - // todo this is a bit inefficient; we should probably read a page at a time - // struct picoboot_otp_cmd otp_cmd = {0}; - last_page = m.reg_row / OTP_PAGE_ROWS; struct picoboot_otp_cmd otp_cmd; - otp_cmd.wRow = last_page * OTP_PAGE_ROWS; - otp_cmd.wRowCount = OTP_PAGE_ROWS; - otp_cmd.bEcc = 0; - con.otp_read(&otp_cmd, (uint8_t *)raw_buffer, sizeof(raw_buffer)); + if (m.reg_row / OTP_PAGE_ROWS >= 62) { + // Read individual rows for lock words + otp_cmd.wRow = m.reg_row; + otp_cmd.wRowCount = 1; + otp_cmd.bEcc = 0; + con.otp_read(&otp_cmd, (uint8_t *)&(raw_buffer[m.reg_row % OTP_PAGE_ROWS]), sizeof(raw_buffer[0])); + } else { + // Otherwise read a page at a time + last_page = m.reg_row / OTP_PAGE_ROWS; + otp_cmd.wRow = last_page * OTP_PAGE_ROWS; + otp_cmd.wRowCount = OTP_PAGE_ROWS; + otp_cmd.bEcc = 0; + con.otp_read(&otp_cmd, (uint8_t *)raw_buffer, sizeof(raw_buffer)); + } } if (m.reg_row != last_reg_row) { last_reg_row = m.reg_row; @@ -5160,10 +5748,10 @@ bool otp_dump_command::execute(device_map &devices) { otp_cmd.wRow = 0; otp_cmd.wRowCount = OTP_ROW_COUNT; otp_cmd.bEcc = settings.otp.ecc && !settings.otp.raw; - std::unique_ptr unique_raw_buffer(new uint32_t[otp_cmd.wRowCount / (otp_cmd.bEcc ? 2 : 1)]()); - uint32_t* raw_buffer = unique_raw_buffer.get(); + vector raw_buffer; + raw_buffer.resize(otp_cmd.wRowCount * (otp_cmd.bEcc ? 2 : 4)); picoboot_memory_access raw_access(con); - con.otp_read(&otp_cmd, (uint8_t *)raw_buffer, sizeof(raw_buffer)); + con.otp_read(&otp_cmd, raw_buffer.data(), raw_buffer.size()); fos.first_column(0); char buf[256]; for(int i=0;i family_ids; insert_default_families(unpartitioned.permissions_and_flags, family_ids); printf(", uf2 { %s }\n", cli::join(family_ids, ", ").c_str()); @@ -5255,7 +5843,7 @@ bool partition_info_command::execute(device_map &devices) { return -1; } unsigned int p = location_and_permissions & flags_and_permissions; - print_permissions(p); + fos << str_permissions(p); if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_HAS_ID_BITS) { printf(", id=%016" PRIx64, id); } @@ -5320,17 +5908,17 @@ uint32_t permissions_to_flags(json permissions) { uint32_t families_to_flags(std::vector families) { uint32_t ret = 0; for (auto family : families) { - if (family == "data") { + if (family == data_family_name) { ret |= PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_DATA_BITS; - } else if (family == "absolute") { + } else if (family == absolute_family_name) { ret |= PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_ABSOLUTE_BITS; - } else if (family == "rp2040") { + } else if (family == rp2040_family_name) { ret |= PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2040_BITS; - } else if (family == "rp2350-arm-s") { + } else if (family == rp2350_arm_s_family_name) { ret |= PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2350_ARM_S_BITS; - } else if (family == "rp2350-arm-ns") { + } else if (family == rp2350_arm_ns_family_name) { ret |= PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2350_ARM_NS_BITS; - } else if (family == "rp2350-riscv") { + } else if (family == rp2350_riscv_family_name) { ret |= PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2350_RISCV_BITS; } } @@ -5379,7 +5967,7 @@ bool partition_create_command::execute(device_map &devices) { #if SUPPORT_A2 if (!(unpartitioned_flags & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_ABSOLUTE_BITS)) { - fail(ERROR_INCOMPATIBLE, "Unpartitioned space must accept the absolute family, for the RP2350-E9 fix to work"); + fail(ERROR_INCOMPATIBLE, "Unpartitioned space must accept the absolute family, for the RP2350-E10 fix to work"); } #endif @@ -5388,7 +5976,7 @@ bool partition_create_command::execute(device_map &devices) { for (auto p : partitions) { partition_table_item::partition new_p; uint32_t start = cur_pos; - if (p.contains("start")) get_json_int(p["start"], (int&)start); + if (p.contains("start")) get_json_int(p["start"], start); int size; get_json_int(p["size"], size); if (start >= 4096 || size >= 4096) { @@ -5403,7 +5991,7 @@ bool partition_create_command::execute(device_map &devices) { cur_pos = start + size; #if SUPPORT_A2 if (start <= (settings.uf2.abs_block_loc - FLASH_START)/0x1000 && start + size > (settings.uf2.abs_block_loc - FLASH_START)/0x1000) { - fail(ERROR_INCOMPATIBLE, "The address %lx cannot be in a partition for the RP2350-E9 fix to work", settings.uf2.abs_block_loc); + fail(ERROR_INCOMPATIBLE, "The address %lx cannot be in a partition for the RP2350-E10 fix to work", settings.uf2.abs_block_loc); } #endif new_p.first_sector = start; @@ -5445,7 +6033,10 @@ bool partition_create_command::execute(device_map &devices) { new_p.flags |= (link_value << PICOBIN_PARTITION_FLAGS_LINK_VALUE_LSB) & PICOBIN_PARTITION_FLAGS_LINK_VALUE_BITS; } if (p.contains("name")) { new_p.name = p["name"]; new_p.flags |= PICOBIN_PARTITION_FLAGS_HAS_NAME_BITS; } - if (p.contains("id")) { new_p.id = p["id"]; new_p.flags |= PICOBIN_PARTITION_FLAGS_HAS_ID_BITS; } + if (p.contains("id")) { + if (get_json_int(p["id"], new_p.id)) {new_p.flags |= PICOBIN_PARTITION_FLAGS_HAS_ID_BITS;} + else {string p_id = p["id"]; fail(ERROR_INCOMPATIBLE, "Partition ID \"%s\" is not a valid 64bit integer\n", p_id.c_str());} + } if(p.contains("no_reboot_on_uf2_download")) new_p.flags |= PICOBIN_PARTITION_FLAGS_UF2_DOWNLOAD_NO_REBOOT_BITS; if(p.contains("ab_non_bootable_owner_affinity")) new_p.flags |= PICOBIN_PARTITION_FLAGS_UF2_DOWNLOAD_AB_NON_BOOTABLE_OWNER_AFFINITY; @@ -5580,9 +6171,9 @@ bool uf2_convert_command::execute(device_map &devices) { auto in = get_file(ios::in|ios::binary); auto out = get_file_idx(ios::out|ios::binary, 1); #if SUPPORT_A2 - // RP2350-E9 : add absolute block + // RP2350-E10 : add absolute block if (settings.uf2.abs_block) { - fos << "RP2350-E9: Adding absolute block to UF2 targeting " << hex_string(settings.uf2.abs_block_loc) << "\n"; + fos << "RP2350-E10: Adding absolute block to UF2 targeting " << hex_string(settings.uf2.abs_block_loc) << "\n"; } else { settings.uf2.abs_block_loc = 0; } @@ -6294,7 +6885,7 @@ bool coprodis_command::execute(device_map &devices) { string line; fos << "Replacing " << proc_insts.size() << " instructions\n"; while (getline(buffer, line)) { - if (line == std::get<0>(proc_insts[0])) { + if (!proc_insts.empty() && line == std::get<0>(proc_insts[0])) { fos << "\nFound instruction\n"; fos << line; fos << "\n"; @@ -6307,7 +6898,6 @@ bool coprodis_command::execute(device_map &devices) { fos << line; fos << "\n"; proc_insts.erase(proc_insts.begin()); - if (proc_insts.size() == 0) break; } *out << line << "\n"; } @@ -6321,35 +6911,6 @@ bool coprodis_command::execute(device_map &devices) { #if HAS_LIBUSB -void partition_info_command::insert_default_families(uint32_t flags_and_permissions, vector &family_ids) const { - if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_ABSOLUTE_BITS) family_ids.emplace_back("absolute"); - if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2040_BITS) family_ids.emplace_back("rp2040"); - if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2350_ARM_S_BITS) family_ids.emplace_back("rp2350-arm-s"); - if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2350_ARM_NS_BITS) family_ids.emplace_back("rp2350-arm-ns"); - if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_RP2350_RISCV_BITS) family_ids.emplace_back("rp2350-riscv"); - if (flags_and_permissions & PICOBIN_PARTITION_FLAGS_ACCEPTS_DEFAULT_FAMILY_DATA_BITS) family_ids.emplace_back("data"); -} - -void partition_info_command::print_permissions(unsigned int p) const { - static_assert(PICOBIN_PARTITION_PERMISSION_S_R_BITS == (1u << 26), ""); - static_assert(PICOBIN_PARTITION_PERMISSION_S_W_BITS == (1u << 27), ""); - static_assert(PICOBIN_PARTITION_PERMISSION_NS_W_BITS == (1u << 29), ""); - static_assert(PICOBIN_PARTITION_PERMISSION_NSBOOT_W_BITS == (1u << 31), ""); - printf(" S("); - unsigned int r = (p >> 26) & 3; - if (r & 1) printf("r"); - if (r & 2) printf("w"); else if (!r) printf("-"); - printf(") NSBOOT("); - r = (p >> 30) & 3; - if (r & 1) printf("r"); - if (r & 2) printf("w"); else if (!r) printf("-"); - printf(") NS("); - r = (p >> 28) & 3; - if (r & 1) printf("r"); - if (r & 2) printf("w"); else if (!r) printf("-"); - printf(")"); -} - static void check_otp_write_error(picoboot::command_failure &e, bool ecc) { if (e.get_code() == PICOBOOT_UNSUPPORTED_MODIFICATION) { if (ecc) fail(ERROR_NOT_POSSIBLE, "Attempted to modify OTP ECC row(s)\n"); @@ -6540,7 +7101,7 @@ bool otp_load_command::execute(device_map &devices) { uint8_t* file_buffer = unique_file_buffer.get(); file->read((char*)file_buffer, file_size); try { - con.otp_write(&otp_cmd, (uint8_t *)file_buffer, sizeof(file_buffer)); + con.otp_write(&otp_cmd, (uint8_t *)file_buffer, file_size); } catch (picoboot::command_failure &e) { check_otp_write_error(e, otp_cmd.bEcc); throw e; @@ -6632,6 +7193,14 @@ bool otp_list_command::execute(device_map &devices) { } else { fos << " (bits " << low << "-" << high << ")\n"; } + if ((m.field || settings.otp.list_field_descriptions) && !settings.otp.list_no_descriptions && !f.description.empty()) { + // Only print field descriptors if matching a field, or if list_field_descriptions is set + fos.first_column(indent0); + fos.hanging_indent(0); + fos << "\"" << f.description << "\""; + fos.first_column(0); + fos << "\n"; + } } } } @@ -6751,6 +7320,10 @@ bool otp_set_command::execute(device_map &devices) { settings.otp.value &= field->mask; settings.otp.value |= old_raw_value & ~field->mask; } + if (settings.otp.ignore_set) { + // OR with current value, to ignore any already-set bits + settings.otp.value |= old_raw_value; + } // todo check for clearing bits if (old_raw_value && settings.otp.ecc) { fail(ERROR_NOT_POSSIBLE, "Cannot modify OTP ECC row(s)\n"); @@ -6947,19 +7520,20 @@ bool otp_white_label_command::execute(device_map &devices) { // Check for separate max_power and attributes uint16_t val = 0; int hex_val = 0; - if (wl_json["device"].contains("max_power")) { + if (wl_json["device"].contains("max_power") && wl_json["device"].contains("attributes")) { if (!get_json_int(wl_json["device"]["max_power"], hex_val)) { fail(ERROR_FORMAT, "MaxPower must be an integer"); } val |= (hex_val << 8); - } - if (wl_json["device"].contains("attributes")) { + if (!get_json_int(wl_json["device"]["attributes"], hex_val)) { fail(ERROR_FORMAT, "Device Attributes must be an integer"); } else if (hex_val & 0b11111 || ~hex_val & 0x80) { fail(ERROR_FORMAT, "Device Attributes must have bit 7 set (0x80), and bits 4-0 clear"); } val |= hex_val; + } else if (wl_json["device"].contains("max_power") || wl_json["device"].contains("attributes")) { + fail(ERROR_INCOMPATIBLE, "Must specify both max_power and attributes in the JSON file"); } if (val) { fos << "Setting attributes " << hex_string(val, 4) << "\n"; @@ -7090,6 +7664,7 @@ bool reboot_command::execute(device_map &devices) { if (!settings.switch_cpu.empty()) { fail(ERROR_ARGS, "--cpu may not be specified for forced reboot"); } + selected_model = std::get<0>(devices[dr_vidpid_stdio_usb][0]); reboot_device(std::get<1>(devices[dr_vidpid_stdio_usb][0]), std::get<2>(devices[dr_vidpid_stdio_usb][0]), settings.reboot_usb); if (!quiet) { if (settings.reboot_usb) { @@ -7164,7 +7739,7 @@ bool reboot_command::execute(device_map &devices) { #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #define VC_EXTRALEAN -#include +#include #elif defined(__linux__) || defined(__APPLE__) #include #endif @@ -7251,7 +7826,7 @@ int main(int argc, char **argv) { } // we only loop a second time if we want to reboot some devices (which may cause device - for (int tries = 0; !rc && tries < 2; tries++) { + for (int tries = 0; !rc && tries <= MAX_REBOOT_TRIES; tries++) { if (ctx) { if (libusb_get_device_list(ctx, &devs) < 0) { fail(ERROR_USB, "Failed to enumerate USB devices\n"); @@ -7278,53 +7853,65 @@ int main(int argc, char **argv) { case cmd::device_support::one: if (devices[dr_vidpid_bootrom_ok].empty() && (!settings.force || devices[dr_vidpid_stdio_usb].empty())) { - bool had_note = false; - fos << missing_device_string(tries>0, selected_cmd->requires_rp2350()); - if (tries > 0) { - fos << " It is possible the device is not responding, and will have to be manually entered into BOOTSEL mode.\n"; - had_note = true; // suppress "but:" in this case - } - fos << "\n"; - fos.first_column(0); - fos.hanging_indent(4); - auto printer = [&](enum picoboot_device_result r, const string &description) { - if (!had_note && !devices[r].empty()) { - fos << "\nbut:\n\n"; - had_note = true; + if (tries == 0 || tries == MAX_REBOOT_TRIES) { + if (tries) { + fos << "\n\n"; } - for (auto d : devices[r]) { - fos << bus_device_string(std::get<1>(d)) << description << "\n"; + bool had_note = false; + fos << missing_device_string(tries>0, selected_cmd->requires_rp2350()); + if (tries) { + fos << " It is possible the device is not responding, and will have to be manually entered into BOOTSEL mode.\n"; + had_note = true; // suppress "but:" in this case } - }; -#if defined(__linux__) || defined(__APPLE__) - printer(dr_vidpid_bootrom_cant_connect, - " appears to be a RP2040 device in BOOTSEL mode, but picotool was unable to connect. Maybe try 'sudo' or check your permissions."); -#else - printer(dr_vidpid_bootrom_cant_connect, - " appears to be a RP2040 device in BOOTSEL mode, but picotool was unable to connect. You may need to install a driver via Zadig. See \"Getting started with Raspberry Pi Pico\" for more information"); -#endif - printer(dr_vidpid_picoprobe, - " appears to be a RP2040 PicoProbe device not in BOOTSEL mode."); - printer(dr_vidpid_micropython, - " appears to be a RP2040 MicroPython device not in BOOTSEL mode."); - if (selected_cmd->force_requires_pre_reboot()) { -#if defined(_WIN32) - printer(dr_vidpid_stdio_usb, - " appears to be a RP2040 device with a USB serial connection, not in BOOTSEL mode. You can force reboot into BOOTSEL mode via 'picotool reboot -f -u' first."); -#else - printer(dr_vidpid_stdio_usb, - " appears to be a RP2040 device with a USB serial connection, so consider -f (or -F) to force reboot in order to run the command."); -#endif + fos << "\n"; + fos.first_column(0); + fos.hanging_indent(4); + auto printer = [&](enum picoboot_device_result r, const string &description) { + if (!had_note && !devices[r].empty()) { + fos << "\nbut:\n\n"; + had_note = true; + } + for (auto d : devices[r]) { + fos << bus_device_string(std::get<1>(d), std::get<0>(d)) << description << "\n"; + } + }; + #if defined(__linux__) || defined(__APPLE__) + printer(dr_vidpid_bootrom_cant_connect, + " appears to be in BOOTSEL mode, but picotool was unable to connect. Maybe try 'sudo' or check your permissions."); + printer(dr_vidpid_stdio_usb_cant_connect, + " appears to have a USB serial connection, but picotool was unable to connect. Maybe try 'sudo' or check your permissions."); + #else + printer(dr_vidpid_bootrom_cant_connect, + " appears to be in BOOTSEL mode, but picotool was unable to connect. You may need to install a driver via Zadig. See \"Getting started with Raspberry Pi Pico\" for more information"); + printer(dr_vidpid_stdio_usb_cant_connect, + " appears to have a USB serial connection, but picotool was unable to connect."); + #endif + printer(dr_vidpid_picoprobe, + " appears to be an RP-series PicoProbe device not in BOOTSEL mode."); + printer(dr_vidpid_micropython, + " appears to be an RP-series MicroPython device not in BOOTSEL mode."); + if (selected_cmd->force_requires_pre_reboot()) { + #if defined(_WIN32) + printer(dr_vidpid_stdio_usb, + " appears to have a USB serial connection, not in BOOTSEL mode. You can force reboot into BOOTSEL mode via 'picotool reboot -f -u' first."); + #else + printer(dr_vidpid_stdio_usb, + " appears to have a USB serial connection, so consider -f (or -F) to force reboot in order to run the command."); + #endif + } else { + // special case message for what is actually just reboot (the only command that doesn't require reboot first) + printer(dr_vidpid_stdio_usb, + " appears to have a USB serial connection, so consider -f to force the reboot."); + } + rc = ERROR_NO_DEVICE; } else { - // special case message for what is actually just reboot (the only command that doesn't require reboot first) - printer(dr_vidpid_stdio_usb, - " appears to be a RP2040 device with a USB serial connection, so consider -f to force the reboot."); + // waiting for rebooted device to show up + break; } - rc = ERROR_NO_DEVICE; } else if (supported == cmd::device_support::one) { if (devices[dr_vidpid_bootrom_ok].size() > 1 || (devices[dr_vidpid_bootrom_ok].empty() && devices[dr_vidpid_stdio_usb].size() > 1)) { - fail(ERROR_NOT_POSSIBLE, "Command requires a single RP2040 device to be targeted."); + fail(ERROR_NOT_POSSIBLE, "Command requires a single RP-series device to be targeted."); } if (!devices[dr_vidpid_bootrom_ok].empty()) { settings.force = false; // we have a device, so we're not forcing @@ -7341,36 +7928,46 @@ int main(int argc, char **argv) { } if (!rc) { if (settings.force && ctx) { // actually ctx should never be null as we are targeting device if force is set, but still - if (devices[dr_vidpid_stdio_usb].size() != 1) { + if (devices[dr_vidpid_stdio_usb].size() != 1 && !tries) { fail(ERROR_NOT_POSSIBLE, - "Forced command requires a single rebootable RP2040 device to be targeted."); + "Forced command requires a single rebootable RP-series device to be targeted."); } if (selected_cmd->force_requires_pre_reboot()) { - // we reboot into BOOTSEL mode and disable MSC interface (the 1 here) - auto &to_reboot = std::get<1>(devices[dr_vidpid_stdio_usb][0]); - auto &to_reboot_handle = std::get<2>(devices[dr_vidpid_stdio_usb][0]); -#if defined(_WIN32) - { - struct libusb_device_descriptor desc; - libusb_get_device_descriptor(to_reboot, &desc); - if (desc.idProduct == PRODUCT_ID_RP2040_STDIO_USB) { - fail(ERROR_NOT_POSSIBLE, - "Forced commands do not work with RP2040 on Windows - you can force reboot into BOOTSEL mode via 'picotool reboot -f -u' instead."); + if (!tries) { + // we reboot into BOOTSEL mode and disable MSC interface (the 1 here) + auto &to_reboot = std::get<1>(devices[dr_vidpid_stdio_usb][0]); + auto &to_reboot_handle = std::get<2>(devices[dr_vidpid_stdio_usb][0]); + #if defined(_WIN32) + { + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(to_reboot, &desc); + if (desc.idProduct == PRODUCT_ID_RP2040_STDIO_USB) { + fail(ERROR_NOT_POSSIBLE, + "Forced commands do not work with RP2040 on Windows - you can force reboot into BOOTSEL mode via 'picotool reboot -f -u' instead."); + } + } + #endif + if (settings.ser.empty() && to_reboot_handle) { + // store USB serial number, to pick correct device after reboot + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(to_reboot, &desc); + char ser_str[128]; + libusb_get_string_descriptor_ascii(to_reboot_handle, desc.iSerialNumber, (unsigned char*)ser_str, sizeof(ser_str)); + if (strcmp(ser_str, "EEEEEEEEEEEEEEEE") != 0) { + // don't store EEs serial number, as that is an RP2040 running a no_flash binary + settings.ser = ser_str; + fos << "Tracking device serial number " << ser_str << " for reboot\n"; + } } - } -#endif - if (settings.ser.empty() && to_reboot_handle) { - // store USB serial number, to pick correct device after reboot - struct libusb_device_descriptor desc; - libusb_get_device_descriptor(to_reboot, &desc); - char ser_str[128]; - libusb_get_string_descriptor_ascii(to_reboot_handle, desc.iSerialNumber, (unsigned char*)ser_str, sizeof(ser_str)); - settings.ser = ser_str; - fos << "Tracking device serial number " << ser_str << " for reboot\n"; - } - reboot_device(to_reboot, to_reboot_handle, true, 1); - fos << "The device was asked to reboot into BOOTSEL mode so the command can be executed.\n\n"; + reboot_device(to_reboot, to_reboot_handle, true, 1); + fos << "The device was asked to reboot into BOOTSEL mode so the command can be executed."; + } else if (tries == 1) { + fos << "\nWaiting for device to reboot"; + } else { + fos << "..."; + } + fos.flush(); for (const auto &handle : to_close) { libusb_close(handle); } @@ -7380,15 +7977,16 @@ int main(int argc, char **argv) { devices.clear(); sleep_ms(1200); - // we now clear settings.force, because we expect the device to have rebooted and be available. - // we also clear bus/address filters, because the device may have moved, so the only way we can find it + // we now clear bus/address filters, because the device may have moved, so the only way we can find it // again is to assume it has the same serial number. - settings.force = false; settings.address = -1; settings.bus = -1; continue; } } + if (tries) { + fos << "\n\n"; + } if (!selected_cmd->execute(devices) && tries) { if (settings.force_no_reboot) { fos << "\nThe device has been left accessible, but without the drive mounted; use 'picotool reboot' to reboot into regular BOOTSEL mode or application mode.\n"; @@ -7409,11 +8007,23 @@ int main(int argc, char **argv) { rc = e.code(); } catch (picoboot::command_failure& e) { // todo rp2350/rp2040 - std::cout << "ERROR: The RP2040 device returned an error: " << e.what() << "\n"; + string device = "RP-series"; + if (selected_model == rp2040) { + device = "RP2040"; + } else if (selected_model == rp2350) { + device = "RP2350"; + } + std::cout << "ERROR: The " << device << " device returned an error: " << e.what() << "\n"; rc = ERROR_UNKNOWN; } catch (picoboot::connection_error&) { // todo rp2350/rp2040 - std::cout << "ERROR: Communication with RP2040 device failed\n"; + string device = "RP-series"; + if (selected_model == rp2040) { + device = "RP2040"; + } else if (selected_model == rp2350) { + device = "RP2350"; + } + std::cout << "ERROR: Communication with " << device << " device failed\n"; rc = ERROR_CONNECTION; } catch (cancelled_exception&) { rc = ERROR_CANCELLED; diff --git a/otp_header_parser/BUILD.bazel b/otp_header_parser/BUILD.bazel index ae191c0..fdf04f1 100644 --- a/otp_header_parser/BUILD.bazel +++ b/otp_header_parser/BUILD.bazel @@ -1,5 +1,11 @@ package(default_visibility = ["//visibility:public"]) +cc_library( + name = "pre_generated_otp_header", + includes = ["."], + hdrs = ["rp2350.json.h"], +) + cc_binary( name = "otp_header_parser", srcs = ["otp_header_parse.cpp"], diff --git a/otp_header_parser/otp_header_parse.cpp b/otp_header_parser/otp_header_parse.cpp index 04ead13..00e924c 100644 --- a/otp_header_parser/otp_header_parse.cpp +++ b/otp_header_parser/otp_header_parse.cpp @@ -15,7 +15,7 @@ #include "nlohmann/json.hpp" // missing __builtins on windows -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__clang__) # include # define __builtin_popcount __popcnt static __forceinline int __builtin_ctz(unsigned x) { diff --git a/picoboot_connection/BUILD.bazel b/picoboot_connection/BUILD.bazel index 20437b4..b2fac4d 100644 --- a/picoboot_connection/BUILD.bazel +++ b/picoboot_connection/BUILD.bazel @@ -9,6 +9,7 @@ cc_library( hdrs = [ "picoboot_connection.h", "picoboot_connection_cxx.h", + "//:flash_id_bin.h", ], defines = ["HAS_LIBUSB=1"], # Bazel build always has libusb. includes = ["."], @@ -16,6 +17,8 @@ cc_library( "//elf", "@libusb", "@pico-sdk//src/common/boot_picoboot_headers", + "@pico-sdk//src/rp2_common/boot_bootrom_headers", "@pico-sdk//src/rp2_common/pico_bootrom:pico_bootrom_headers", + "@pico-sdk//src/rp2_common/pico_stdio_usb:reset_interface_headers", ], ) diff --git a/picoboot_connection/picoboot_connection.c b/picoboot_connection/picoboot_connection.c index 7a519d1..265608c 100644 --- a/picoboot_connection/picoboot_connection.c +++ b/picoboot_connection/picoboot_connection.c @@ -10,7 +10,8 @@ #include #include "picoboot_connection.h" -#include "pico/bootrom_constants.h" +#include "boot/bootrom_constants.h" +#include "pico/stdio_usb/reset_interface.h" #if ENABLE_DEBUG_LOG #include @@ -87,7 +88,11 @@ enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_d case PRODUCT_ID_PICOPROBE: return dr_vidpid_picoprobe; case PRODUCT_ID_RP2040_STDIO_USB: + *model = rp2040; + res = dr_vidpid_stdio_usb; + break; case PRODUCT_ID_STDIO_USB: + *model = rp2350; res = dr_vidpid_stdio_usb; break; case PRODUCT_ID_RP2040_USBBOOT: @@ -115,15 +120,15 @@ enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_d if (vid == 0 || strlen(ser) != 0) { // didn't check vid or ser, so treat as unknown return dr_vidpid_unknown; - } else if (res != dr_vidpid_unknown) { - return res; + } else if (res == dr_vidpid_stdio_usb) { + return dr_vidpid_stdio_usb_cant_connect; } else { return dr_vidpid_bootrom_cant_connect; } } } - if (res == dr_vidpid_stdio_usb) { + if (!ret && res == dr_vidpid_stdio_usb) { if (strlen(ser) != 0) { // Check USB serial number char ser_str[128]; @@ -138,6 +143,17 @@ enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_d } } + // Runtime reset interface with thirdparty VID + if (!ret) { + for (int i = 0; i < config->bNumInterfaces; i++) { + if (config->interface[i].altsetting[0].bInterfaceClass == 0xff && + config->interface[i].altsetting[0].bInterfaceSubClass == RESET_INTERFACE_SUBCLASS && + config->interface[i].altsetting[0].bInterfaceProtocol == RESET_INTERFACE_PROTOCOL) { + return dr_vidpid_stdio_usb; + } + } + } + if (!ret) { if (config->bNumInterfaces == 1) { interface = 0; @@ -579,36 +595,71 @@ static const uint8_t picoboot_peek_cmd[] = { }; #define PICOBOOT_PEEK_CMD_PROG_SIZE (size_t)(12 + 4) -// todo - compile this - currently taken from github PR #86 -static const size_t picoboot_flash_id_cmd_len = 152; -static const uint8_t picoboot_flash_id_cmd[] = { - // void flash_get_unique_id(void) - 0x02, 0xa0, 0x06, 0xa1, 0x00, 0x4a, 0x11, 0xe0, - // int buflen - 0x0d, 0x00, 0x00, 0x00, - // char txbuf[13] - 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // char rxbuf[13] - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // void flash_do_cmd(txbuf, rxbuf, buflen) - 0x80, 0x23, 0xf0, 0xb5, 0x17, 0x4e, 0x9b, 0x00, - 0x34, 0x68, 0x63, 0x40, 0xc0, 0x24, 0xa4, 0x00, - 0x23, 0x40, 0x15, 0x4c, 0x23, 0x60, 0xc0, 0x24, - 0x13, 0x00, 0x64, 0x05, 0x17, 0x00, 0x1f, 0x43, - 0x06, 0xd1, 0xc0, 0x23, 0x32, 0x68, 0x9b, 0x00, - 0x93, 0x43, 0x0f, 0x4a, 0x13, 0x60, 0xf0, 0xbd, - 0x08, 0x25, 0xa7, 0x6a, 0x3d, 0x40, 0xac, 0x46, - 0x02, 0x25, 0x2f, 0x42, 0x08, 0xd0, 0x00, 0x2a, - 0x06, 0xd0, 0x9f, 0x1a, 0x0d, 0x2f, 0x03, 0xd8, - 0x07, 0x78, 0x01, 0x3a, 0x27, 0x66, 0x01, 0x30, - 0x65, 0x46, 0x00, 0x2d, 0xe2, 0xd0, 0x00, 0x2b, - 0xe0, 0xd0, 0x27, 0x6e, 0x01, 0x3b, 0x0f, 0x70, - 0x01, 0x31, 0xdb, 0xe7, 0x0c, 0x80, 0x01, 0x40, - 0x0c, 0x90, 0x01, 0x40, -}; -#define PICOBOOT_FLASH_ID_CMD_PROG_SIZE (size_t)(152) +// 00000000 : +// 0: a002 add r0, pc, #8 @ (adr r0, c ) +// 2: a106 add r1, pc, #24 @ (adr r1, 1c ) +// 4: 4a00 ldr r2, [pc, #0] @ (8 ) +// 6: e011 b.n 2c +// 8: 0000000d .word 0x0000000d +// c: 0000004b .word 0x0000004b +// ... +// +// 0000002c : +// 2c: 2380 movs r3, #128 @ 0x80 +// 2e: b5f0 push {r4, r5, r6, r7, lr} +// 30: 4e17 ldr r6, [pc, #92] @ (90 ) +// 32: 009b lsls r3, r3, #2 +// 34: 6834 ldr r4, [r6, #0] +// 36: 4063 eors r3, r4 +// 38: 24c0 movs r4, #192 @ 0xc0 +// 3a: 00a4 lsls r4, r4, #2 +// 3c: 4023 ands r3, r4 +// 3e: 4c15 ldr r4, [pc, #84] @ (94 ) +// 40: 6023 str r3, [r4, #0] +// 42: 24c0 movs r4, #192 @ 0xc0 +// 44: 0013 movs r3, r2 +// 46: 0564 lsls r4, r4, #21 +// 48: 0017 movs r7, r2 +// 4a: 431f orrs r7, r3 +// 4c: d106 bne.n 5c +// 4e: 23c0 movs r3, #192 @ 0xc0 +// 50: 6832 ldr r2, [r6, #0] +// 52: 009b lsls r3, r3, #2 +// 54: 4393 bics r3, r2 +// 56: 4a0f ldr r2, [pc, #60] @ (94 ) +// 58: 6013 str r3, [r2, #0] +// 5a: bdf0 pop {r4, r5, r6, r7, pc} +// 5c: 2508 movs r5, #8 +// 5e: 6aa7 ldr r7, [r4, #40] @ 0x28 +// 60: 403d ands r5, r7 +// 62: 46ac mov ip, r5 +// 64: 2502 movs r5, #2 +// 66: 422f tst r7, r5 +// 68: d008 beq.n 7c +// 6a: 2a00 cmp r2, #0 +// 6c: d006 beq.n 7c +// 6e: 1a9f subs r7, r3, r2 +// 70: 2f0d cmp r7, #13 +// 72: d803 bhi.n 7c +// 74: 7807 ldrb r7, [r0, #0] +// 76: 3a01 subs r2, #1 +// 78: 6627 str r7, [r4, #96] @ 0x60 +// 7a: 3001 adds r0, #1 +// 7c: 4665 mov r5, ip +// 7e: 2d00 cmp r5, #0 +// 80: d0e2 beq.n 48 +// 82: 2b00 cmp r3, #0 +// 84: d0e0 beq.n 48 +// 86: 6e27 ldr r7, [r4, #96] @ 0x60 +// 88: 3b01 subs r3, #1 +// 8a: 700f strb r7, [r1, #0] +// 8c: 3101 adds r1, #1 +// 8e: e7db b.n 48 +// 90: 4001800c .word 0x4001800c +// 94: 4001900c .word 0x4001900c + +#include "flash_id_bin.h" +#define PICOBOOT_FLASH_ID_CMD_PROG_SIZE (const size_t)(152) // TODO better place for this e.g. the USB DPRAM location the controller has already put it in #define PEEK_POKE_CODE_LOC 0x20000000u @@ -647,10 +698,11 @@ int picoboot_peek(libusb_device_handle *usb_device, uint32_t addr, uint32_t *dat int picoboot_flash_id(libusb_device_handle *usb_device, uint64_t *data) { picoboot_exclusive_access(usb_device, 1); + assert(PICOBOOT_FLASH_ID_CMD_PROG_SIZE == flash_id_bin_SIZE); uint8_t prog[PICOBOOT_FLASH_ID_CMD_PROG_SIZE]; uint64_t id; output("GET FLASH ID\n"); - memcpy(prog, picoboot_flash_id_cmd, picoboot_flash_id_cmd_len); + memcpy(prog, flash_id_bin, flash_id_bin_SIZE); // ensure XIP is exited before executing int ret = picoboot_exit_xip(usb_device); @@ -676,4 +728,4 @@ flash_id_return: picoboot_exclusive_access(usb_device, 0); return ret; } -#endif \ No newline at end of file +#endif diff --git a/picoboot_connection/picoboot_connection.h b/picoboot_connection/picoboot_connection.h index 875c35c..8c2ca22 100644 --- a/picoboot_connection/picoboot_connection.h +++ b/picoboot_connection/picoboot_connection.h @@ -38,6 +38,7 @@ enum picoboot_device_result { dr_vidpid_unknown, dr_error, dr_vidpid_stdio_usb, + dr_vidpid_stdio_usb_cant_connect, }; typedef enum { @@ -80,6 +81,9 @@ int picoboot_flash_id(libusb_device_handle *usb_device, uint64_t *data); // we require 256 (as this is the page size supported by the device) #define LOG2_PAGE_SIZE 8u +#ifdef PAGE_SIZE +#undef PAGE_SIZE +#endif #define PAGE_SIZE (1u << LOG2_PAGE_SIZE) #define FLASH_SECTOR_ERASE_SIZE 4096u diff --git a/picoboot_flash_id/BUILD.bazel b/picoboot_flash_id/BUILD.bazel new file mode 100644 index 0000000..ace33ad --- /dev/null +++ b/picoboot_flash_id/BUILD.bazel @@ -0,0 +1,8 @@ +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "picoboot_flash_id_prebuilt", + srcs = ["flash_id.bin"], +) + +# TODO: Make it possible to build flash_id.bin from source. diff --git a/picoboot_flash_id/CMakeLists.txt b/picoboot_flash_id/CMakeLists.txt new file mode 100644 index 0000000..1de78ae --- /dev/null +++ b/picoboot_flash_id/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.12) + +if (NOT USE_PRECOMPILED) + set(PICO_NO_PICOTOOL 1) + + # default build type + set(CMAKE_BUILD_TYPE "MinSizeRel" CACHE STRING "build type") + + # If the user set these environment variables to influence the picotool + # build, unset them here so that they do not influence the pico-sdk + # build. This is especially required for flags that are not supported + # by arm-none-eabi compilers. + unset(ENV{CFLAGS}) + unset(ENV{CXXFLAGS}) + unset(ENV{LDFLAGS}) + + include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake) + project(flash_id C CXX ASM) + pico_sdk_init() + + add_executable(flash_id flash_id.c) + target_link_libraries(flash_id PRIVATE + hardware_regs hardware_structs hardware_flash_headers + ) + target_link_options(flash_id PRIVATE -nostartfiles -nodefaultlibs -Ttext=0) + pico_add_bin_output(flash_id) + pico_add_dis_output(flash_id) +else() + project(flash_id C CXX ASM) + message("Using precompiled flash_id.bin") + configure_file(${CMAKE_CURRENT_LIST_DIR}/flash_id.bin ${CMAKE_CURRENT_BINARY_DIR}/flash_id.bin COPYONLY) + # Use manually specified variables + set(NULL ${CMAKE_MAKE_PROGRAM}) + set(NULL ${PICO_SDK_PATH}) + set(NULL ${PICO_DEBUG_INFO_IN_RELEASE}) +endif() diff --git a/picoboot_flash_id/flash_id.bin b/picoboot_flash_id/flash_id.bin new file mode 100755 index 0000000..4143381 Binary files /dev/null and b/picoboot_flash_id/flash_id.bin differ diff --git a/picoboot_flash_id/flash_id.c b/picoboot_flash_id/flash_id.c new file mode 100644 index 0000000..e6e5926 --- /dev/null +++ b/picoboot_flash_id/flash_id.c @@ -0,0 +1,70 @@ +#include "hardware/regs/io_qspi.h" +#include "hardware/structs/ioqspi.h" +#include "hardware/structs/ssi.h" +#include "hardware/flash.h" + +asm( + ".macro static_assert value, msg\n" + ".if !(\\value)\n" + ".err \\msg\n" + ".endif\n" + ".endm\n" + + ".set FLASH_RUID_CMD, 0x4b\n" + ".set FLASH_RUID_DUMMY_BYTES, 4\n" + ".set FLASH_RUID_DATA_BYTES, 8\n" + ".set FLASH_RUID_TOTAL_BYTES, (1 + FLASH_RUID_DUMMY_BYTES + FLASH_RUID_DATA_BYTES)\n" +); + +void flash_do_cmd(const uint8_t * txbuf, uint8_t *rxbuf, size_t count); +void __attribute__((naked)) flash_get_unique_id_raw(void) { + asm( + ".Lflash_get_unique_id_raw:\n" + "adr r0, .Ltxbuf\n" + "adr r1, .Lrxbuf\n" + "ldr r2, .Lbuflen\n" + "b flash_do_cmd\n" + ".Lbuflen:\n" + ".word FLASH_RUID_TOTAL_BYTES\n" + ".Ltxbuf:\n" + ".byte FLASH_RUID_CMD\n" + ".zero (FLASH_RUID_TOTAL_BYTES - 1)\n" + ".zero (16 - FLASH_RUID_TOTAL_BYTES)\n" + ".Lrxbuf:\n" + ".zero FLASH_RUID_TOTAL_BYTES\n" + ".zero (16 - FLASH_RUID_TOTAL_BYTES)\n" + "static_assert ((.Lrxbuf - flash_get_unique_id_raw) == 28), \"rxbuf offset incorrect\"\n" + ); +} + +static inline void __attribute__((always_inline)) flash_cs_force(_Bool high) { + uint32_t field_val = high ? + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH : + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW; + hw_write_masked(&ioqspi_hw->io[1].ctrl, + field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB, + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS + ); +} + +void flash_do_cmd(const uint8_t * txbuf, uint8_t *rxbuf, size_t count) { + flash_cs_force(0); + size_t tx_remaining = count; + size_t rx_remaining = count; + // We may be interrupted -- don't want FIFO to overflow if we're distracted. + const size_t max_in_flight = 16 - 2; + while (tx_remaining || rx_remaining) { + uint32_t flags = ssi_hw->sr; + bool can_put = !!(flags & SSI_SR_TFNF_BITS); + bool can_get = !!(flags & SSI_SR_RFNE_BITS); + if (can_put && tx_remaining && rx_remaining - tx_remaining < max_in_flight) { + ssi_hw->dr0 = *txbuf++; + --tx_remaining; + } + if (can_get && rx_remaining) { + *rxbuf++ = (uint8_t)ssi_hw->dr0; + --rx_remaining; + } + } + flash_cs_force(1); +} diff --git a/xip_ram_perms/CMakeLists.txt b/xip_ram_perms/CMakeLists.txt index 01975dc..601dc2c 100644 --- a/xip_ram_perms/CMakeLists.txt +++ b/xip_ram_perms/CMakeLists.txt @@ -1,42 +1,53 @@ cmake_minimum_required(VERSION 3.12) if (NOT USE_PRECOMPILED) - set(PICO_PLATFORM rp2350-arm-s) + set(PICO_PLATFORM rp2350-arm-s) + + set(PICO_NO_PICOTOOL 1) - # Pull in SDK (must be before project) - include(pico_sdk_import.cmake) + # If the user set these environment variables to influence the picotool + # build, unset them here so that they do not influence the pico-sdk + # build. This is especially required for flags that are not supported + # by arm-none-eabi compilers. + unset(ENV{CFLAGS}) + unset(ENV{CXXFLAGS}) + unset(ENV{LDFLAGS}) - project(xip_ram_perms C CXX ASM) - set(CMAKE_C_STANDARD 11) - set(CMAKE_CXX_STANDARD 17) + # Pull in SDK (must be before project) + include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake) - if (PICO_SDK_VERSION_STRING VERSION_LESS "2.0.0") - message(FATAL_ERROR "Raspberry Pi Pico SDK version 2.0.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") - endif() + project(xip_ram_perms C CXX ASM) + set(CMAKE_C_STANDARD 11) + set(CMAKE_CXX_STANDARD 17) - # Initialize the SDK - pico_sdk_init() + if (PICO_SDK_VERSION_STRING VERSION_LESS "2.0.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 2.0.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") + endif() - # XIP Ram OTP Perm Setter - add_executable(xip_ram_perms - set_perms.c - ) + # Initialize the SDK + pico_sdk_init() - target_link_libraries(xip_ram_perms - pico_stdlib - ) + # XIP Ram OTP Perm Setter + add_executable(xip_ram_perms + set_perms.c + ) - pico_set_binary_type(xip_ram_perms no_flash) - # create linker script to run from 0x20070000 - file(READ ${PICO_LINKER_SCRIPT_PATH}/memmap_no_flash.ld LINKER_SCRIPT) - string(REPLACE "RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k" "RAM(rwx) : ORIGIN = 0x13ffc000, LENGTH = 16k" LINKER_SCRIPT "${LINKER_SCRIPT}") - file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/memmap_xip_ram.ld "${LINKER_SCRIPT}") - pico_set_linker_script(xip_ram_perms ${CMAKE_CURRENT_BINARY_DIR}/memmap_xip_ram.ld) + target_link_libraries(xip_ram_perms + pico_stdlib + ) + + pico_set_binary_type(xip_ram_perms no_flash) + # create linker script to run from 0x20070000 + file(READ ${PICO_LINKER_SCRIPT_PATH}/memmap_no_flash.ld LINKER_SCRIPT) + string(REPLACE "RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k" "RAM(rwx) : ORIGIN = 0x13ffc000, LENGTH = 16k" LINKER_SCRIPT "${LINKER_SCRIPT}") + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/memmap_xip_ram.ld "${LINKER_SCRIPT}") + pico_set_linker_script(xip_ram_perms ${CMAKE_CURRENT_BINARY_DIR}/memmap_xip_ram.ld) else() - project(xip_ram_perms C CXX ASM) - message("Using precompiled xip_ram_perms.elf") - configure_file(${CMAKE_CURRENT_LIST_DIR}/xip_ram_perms.elf ${CMAKE_CURRENT_BINARY_DIR}/xip_ram_perms.elf COPYONLY) - # Use manually specified variables - set(NULL ${CMAKE_MAKE_PROGRAM}) - set(NULL ${PICO_SDK_PATH}) + project(xip_ram_perms C CXX ASM) + message("Using precompiled xip_ram_perms.elf") + configure_file(${CMAKE_CURRENT_LIST_DIR}/xip_ram_perms.elf ${CMAKE_CURRENT_BINARY_DIR}/xip_ram_perms.elf COPYONLY) + # Use manually specified variables + set(NULL ${CMAKE_MAKE_PROGRAM}) + set(NULL ${PICO_SDK_PATH}) + set(NULL ${PICO_DEBUG_INFO_IN_RELEASE}) endif() diff --git a/xip_ram_perms/pico_sdk_import.cmake b/xip_ram_perms/pico_sdk_import.cmake deleted file mode 100644 index 5613261..0000000 --- a/xip_ram_perms/pico_sdk_import.cmake +++ /dev/null @@ -1,62 +0,0 @@ -# This is a copy of /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 git@asic-git.pitowers.org:amethyst/pico-sdk.git - GIT_TAG use-picotool - ) - 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}) diff --git a/xip_ram_perms/xip_ram_perms.elf b/xip_ram_perms/xip_ram_perms.elf index ae083cd..5fd950e 100755 Binary files a/xip_ram_perms/xip_ram_perms.elf and b/xip_ram_perms/xip_ram_perms.elf differ