├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE.TXT ├── README.md ├── bin2hex.py ├── make-combined-bootrom.sh ├── scripts ├── bin2hex ├── check_function_returns ├── check_rom_table_duplicates ├── check_sg_symbols ├── check_useless_canaries ├── check_useless_rcps ├── count_function_calls ├── find_wasteful_riscv_j_jal ├── fixup_asm_hooks ├── inject_p16 ├── inject_rcp_consts ├── make_import_header ├── make_import_ld ├── overwrite_prolog ├── scramble_canary_tags └── xh3bextm_dis └── src ├── CMakeLists.txt ├── bootrom_layout.template.h ├── common ├── CMakeLists.txt ├── bootrom_assert.h ├── bootrom_common.h ├── native_exports.h ├── nsboot_config.h ├── nsboot_secure_calls.h └── varm_to_riscv_hints.h ├── main ├── CMakeLists.txt ├── arm │ ├── CMakeLists.txt │ ├── arm8_bootrom_rt0.S │ ├── arm8_misc.S │ ├── arm8_nsboot_vm.c │ ├── arm8_s_from_ns_wrappers.c │ ├── arm8_secure_gateways.S │ ├── arm8_sig.c │ ├── arm8_sig.h │ ├── arm8_validate_ns_buffer.c │ ├── arm8_validate_ns_buffer.h │ ├── bootrom_arm.template.ld │ ├── varm_apis.c │ ├── varm_blocks.c │ ├── varm_boot_path.c │ ├── varm_boot_path.h │ ├── varm_checked_flash.c │ ├── varm_checked_flash.h │ ├── varm_flash_boot.c │ ├── varm_flash_permissions.c │ ├── varm_flash_permissions.h │ ├── varm_generic_flash.c │ ├── varm_launch_image.c │ ├── varm_misc.S │ ├── varm_nsboot.c │ ├── varm_otp.c │ ├── varm_resets.h │ └── varm_s_from_nsboot_wrappers.c ├── native │ ├── CMakeLists.txt │ ├── bootram.h │ ├── bootrom.h │ ├── bootrom_error.h │ ├── bootrom_otp.h │ ├── hardening.h │ ├── native_generic_flash.c │ ├── native_generic_flash.h │ └── rcp_tags.h └── riscv │ ├── CMakeLists.txt │ ├── bootrom_riscv.template.ld │ ├── bootrom_riscv_asm_macros.inc.S │ ├── riscv_apis.c │ ├── riscv_bootrom_rt0.S │ ├── riscv_misc.S │ ├── riscv_nsboot_vm.c │ ├── riscv_varm_wrapper.c │ ├── rom_table.S │ ├── varmulet_hooks_bootrom.S │ └── varmulet_hooks_bootrom.h ├── mini_printf ├── CMakeLists.txt ├── mini_printf.c └── mini_printf.h └── nsboot ├── CMakeLists.txt ├── fat_dir_entries.h ├── generator ├── CMakeLists.txt └── main.c ├── index_html.h ├── info_uf2_txt.h ├── ms_os_20_descriptor_set_headers.h ├── native ├── CMakeLists.txt ├── nsboot.h ├── nsboot_arch_adapter.h ├── usb_common.h ├── usb_device.c ├── usb_device.h ├── usb_device_private.h ├── usb_stream_helper.c └── usb_stream_helper.h ├── nsboot.c ├── nsboot.template.ld ├── nsboot_asm.S ├── nsboot_async_task.c ├── nsboot_async_task.h ├── nsboot_uart.h ├── nsboot_uart_client.S ├── nsboot_usb_client.c ├── nsboot_usb_client.h ├── scsi.h ├── scsi_ir.h ├── usb_msc.c ├── usb_msc.h ├── usb_virtual_disk.c └── usb_virtual_disk.h /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmake-* 3 | generated.h 4 | git_info.h 5 | build 6 | build-* 7 | .tags 8 | .nfs* 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pico-sdk"] 2 | path = lib/pico-sdk 3 | url = ../pico-sdk.git 4 | [submodule "armulet"] 5 | path = lib/armulet 6 | url = ../armulet.git 7 | [submodule "sweet-b"] 8 | path = lib/sweet-b 9 | url = ../sweet-b.git 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | set(CMAKE_C_STANDARD 11) 3 | 4 | set(PICO_NO_UF2 1) # turn off SDK UF2 output 5 | if (NSBOOT_BUILD AND NOT PICO_COMPILER) 6 | set(PICO_COMPILER "pico_arm_cortex_m23_gcc") 7 | endif() 8 | 9 | if (NOT INCLUDE_TESTS AND NOT SB_TEST) 10 | set(PICO_BARE_METAL 1) 11 | else() 12 | # SDK versions conflict with code to be tested 13 | set(SKIP_PICO_BIT_OPS 1) 14 | if (NSBOOT_BUILD) 15 | set(SKIP_PICO_MEM_OPS 1) 16 | endif() 17 | endif() 18 | set(PICO_NO_CMSE 1) # override default RP2350 behavior 19 | 20 | # PICO_SDK_PATH must be specified on CMake command line if overriden to make sure we don't pick up random environment 21 | if (NOT PICO_SDK_PATH) # so we pick up SDK path set above on re-configure 22 | set(PICO_SDK_PATH ${CMAKE_CURRENT_LIST_DIR}/lib/pico-sdk) 23 | endif() 24 | include(${PICO_SDK_PATH}/pico_sdk_init.cmake) 25 | project(amy-bootrom) 26 | 27 | pico_sdk_init() 28 | 29 | add_subdirectory(src) 30 | 31 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | NOTE: this license applies to the contents of this repository, EXCLUDING the contents of the files bootrom/mufplib.S 2 | and bootrom/mufplib-double.S, which are licensed separately: 3 | 4 | Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 7 | following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 10 | disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /bin2hex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import re 5 | 6 | if __name__ == "__main__": 7 | if len(sys.argv) < 3: 8 | sys.exit("Usage: bin2hex.py [-] \ne.g. bin2hex.py -32 rom.bin rom.hex") 9 | size = 32 10 | argn = 1; 11 | m = re.match(r"-(\d+)", sys.argv[argn]) 12 | if m: 13 | size = int(m.group(1)) 14 | argn = argn + 1 15 | 16 | if size <= 0 or size % 8 != 0: 17 | sys.exit("Size must be a multiple of 8 (got {})".format(size)) 18 | 19 | if sys.argv[argn] == '-': 20 | ifile = sys.stdin.buffer 21 | else: 22 | ifile = open(sys.argv[argn], "rb") 23 | argn = argn + 1 24 | if sys.argv[argn] == '-': 25 | ofile = sys.stdout 26 | else: 27 | ofile = open(sys.argv[argn], "w") 28 | argn = argn + 1 29 | 30 | bytes_per_word = size // 8 31 | 32 | while True: 33 | bytes = ifile.read(bytes_per_word) 34 | if not bytes: 35 | break 36 | word = '' 37 | for i in range(bytes_per_word): 38 | word = ("%02X" % (bytes[i] if i < len(bytes) else 0)) + word 39 | ofile.write("%s\n" % word) 40 | 41 | ifile.close() 42 | ofile.close() 43 | -------------------------------------------------------------------------------- /make-combined-bootrom.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | # Config 5 | 6 | # Using prebuilt CORE-V toolchain for RISC-V bootrom development, as Zcb/Zcmp 7 | # are not yet available in mainline GCC. The prebuilt toolchains have a 8 | # slightly wonky multilib setup, so not suitable for general RISC-V software 9 | # development (use mainline GCC12), but we need the better code density of 10 | # Zcb/Zcmp in the bootrom, and don't care about stdlib. 11 | 12 | 13 | if [ -z ${RISCV_PICO_COMPILER} ]; then 14 | RISCV_PICO_COMPILER=pico_riscv_gcc_zcb_zcmp 15 | fi 16 | 17 | if [[ "$ASIC_ENV" == 1 ]]; then 18 | echo "Building for ASIC" 19 | CMAKE=/opt/compilers/riscv/cmake/CMake/bin/cmake 20 | ARM_TOOLCHAIN_PATH=/opt/compilers/arm-gnu-toolchain-12.2.rel1-x86_64-arm-none-eabi/bin 21 | ARM_TOOLCHAIN="-DPICO_TOOLCHAIN_PATH=$ARM_TOOLCHAIN_PATH" 22 | if [ -z ${RISCV_TOOLCHAIN_PATH} ]; then 23 | RISCV_TOOLCHAIN_PATH="/opt/compilers/riscv/corev-openhw-gcc-centos7-20240114/bin" 24 | fi 25 | ARM_NONE_EABI_OBJCOPY="$ARM_TOOLCHAIN_PATH/arm-none-eabi-objcopy" 26 | ARM_NONE_EABI_OBJDUMP="$ARM_TOOLCHAIN_PATH/arm-none-eabi-objdump" 27 | if [ -z ${ROM_SRC_DIR} ]; then 28 | ROM_SRC_DIR="$PROJ_ROOT/software/amy-bootrom" 29 | fi 30 | else 31 | CMAKE=cmake 32 | ARM_TOOLCHAIN_PATH="" 33 | ROM_BUILD_DIR=$(pwd) 34 | ROM_SRC_DIR=$(pwd) 35 | if [ -z ${RISCV_TOOLCHAIN_PATH} ]; then 36 | RISCV_TOOLCHAIN_PATH=/opt/compilers/riscv/riscv32-unknown-elf/bin 37 | fi 38 | ARM_NONE_EABI_OBJCOPY=arm-none-eabi-objcopy 39 | ARM_NONE_EABI_OBJDUMP=arm-none-eabi-objdump 40 | fi 41 | 42 | if [ -z ${BIN2HEX} ]; then 43 | BIN2HEX=${ROM_SRC_DIR}/scripts/bin2hex 44 | fi 45 | 46 | RISCV_OBJCOPY=$(ls $RISCV_TOOLCHAIN_PATH/*objcopy) 47 | 48 | if [ -z ${ROM_BUILD_DIR} ]; then 49 | echo "Must define ROM_BUILD_DIR" 50 | exit 1 51 | fi 52 | 53 | if [ -z ${ROM_SRC_DIR} ]; then 54 | echo "Must define ROM_SRC_DIR" 55 | exit 1 56 | fi 57 | 58 | # Ensure build directories exist. Blow them away first if a clean build is requested. 59 | 60 | BUILD_V8M_DIR="$ROM_BUILD_DIR/build-v8m" 61 | BUILD_RISCV_DIR="$ROM_BUILD_DIR/build-riscv" 62 | BUILD_V6M_DIR="$ROM_BUILD_DIR/build-v6m" 63 | BUILD_COMBINED_DIR="$ROM_BUILD_DIR/build-combined" 64 | 65 | if [[ "$CLEAN_BUILD" == 1 ]]; then 66 | rm -rf $BUILD_V8M_DIR || true 67 | rm -rf $BUILD_RISCV_DIR || true 68 | rm -rf $BUILD_V6M_DIR || true 69 | rm -rf $BUILD_COMBINED_DIR 70 | fi 71 | 72 | mkdir -p $BUILD_V8M_DIR 73 | mkdir -p $BUILD_RISCV_DIR 74 | mkdir -p $BUILD_V6M_DIR 75 | mkdir -p $BUILD_COMBINED_DIR 76 | 77 | # Build raw images for various architectures 78 | 79 | cd $BUILD_V8M_DIR 80 | $CMAKE -DPICO_PLATFORM=rp2350 -DPICO_BOARD=amethyst_fpga $ARM_TOOLCHAIN $ROM_SRC_DIR 81 | make bootrom 82 | cd .. 83 | 84 | cd $BUILD_V6M_DIR 85 | $CMAKE -DPICO_PLATFORM=rp2350 -DPICO_BOARD=amethyst_fpga $ARM_TOOLCHAIN -DNSBOOT_BUILD=1 -DBOOTROM_ARM_SYM_FILE="$BUILD_V8M_DIR/bootrom.sym" $ROM_SRC_DIR 86 | make nsboot 87 | cd .. 88 | 89 | cd $BUILD_RISCV_DIR 90 | $CMAKE -DPICO_PLATFORM=rp2350-riscv -DPICO_BOARD=amethyst_fpga -DPICO_TOOLCHAIN_PATH=${RISCV_TOOLCHAIN_PATH} -DPICO_COMPILER=${RISCV_PICO_COMPILER} -DBOOTROM_ARM_SYM_FILE="$BUILD_V8M_DIR/bootrom.sym" -DNSBOOT_SYM_FILE="$BUILD_V6M_DIR/nsboot.sym" $ROM_SRC_DIR 91 | make bootrom 92 | cd .. 93 | 94 | # Extract the two bread slices from the v8-M bootrom. Note the SGs must go at 95 | # one end of ROM because the IDAU NSC boundary is fixed, and putting them at 96 | # the far end allows the v8-M vector table to remain at offset +0x0. 97 | 98 | $ARM_NONE_EABI_OBJCOPY $BUILD_V8M_DIR/bootrom.elf -j .text -O binary $BUILD_COMBINED_DIR/v8m-text.bin 99 | $ARM_NONE_EABI_OBJCOPY $BUILD_V8M_DIR/bootrom.elf -j .secure_gateways -O binary $BUILD_COMBINED_DIR/v8m-secure-gateways.bin 100 | $BIN2HEX $BUILD_COMBINED_DIR/v8m-text.bin $BUILD_COMBINED_DIR/v8m-text.h32 101 | $BIN2HEX $BUILD_COMBINED_DIR/v8m-secure-gateways.bin $BUILD_COMBINED_DIR/v8m-secure-gateways.h32 102 | 103 | # Also need to split off the RISC-V size hack region from the main binary, as 104 | # these straddle the hole that the SGs need to end up in, at 0x7e00 105 | $RISCV_OBJCOPY -O binary -j .text -j .romtable -j .entry $BUILD_RISCV_DIR/bootrom.elf $BUILD_COMBINED_DIR/riscv-text.bin 106 | $RISCV_OBJCOPY -O binary -j .riscv_space_saving_temp $BUILD_RISCV_DIR/bootrom.elf $BUILD_COMBINED_DIR/riscv-post-sg-hack.bin 107 | $BIN2HEX $BUILD_COMBINED_DIR/riscv-text.bin $BUILD_COMBINED_DIR/riscv-text.h32 108 | $BIN2HEX $BUILD_COMBINED_DIR/riscv-post-sg-hack.bin $BUILD_COMBINED_DIR/riscv-post-sg-hack.h32 109 | 110 | # The v6-M and RISC-V images then form the bacon and tomatoes (respectively) 111 | # of our sandwich. 112 | 113 | cat $BUILD_COMBINED_DIR/v8m-text.bin $BUILD_V6M_DIR/nsboot.bin $BUILD_COMBINED_DIR/riscv-text.bin $BUILD_COMBINED_DIR/v8m-secure-gateways.bin $BUILD_COMBINED_DIR/riscv-post-sg-hack.bin > $BUILD_COMBINED_DIR/bootrom-combined.bin 114 | cat $BUILD_COMBINED_DIR/v8m-text.h32 $BUILD_V6M_DIR/nsboot.h32 $BUILD_COMBINED_DIR/riscv-text.h32 $BUILD_COMBINED_DIR/v8m-secure-gateways.h32 $BUILD_COMBINED_DIR/riscv-post-sg-hack.h32 > $BUILD_COMBINED_DIR/bootrom-combined.h32 115 | 116 | final_bin_size=$(du -b $BUILD_COMBINED_DIR/bootrom-combined.bin | awk '{print $1;}') 117 | # Permitted sizes for: 32k chip ROM, 64k FPGA dev ROM, 80k RAM-ROM 118 | if [[ ${final_bin_size} != 32768 && ${final_bin_size} != 65536 && ${final_bin_size} != 81920 ]]; then 119 | echo "Wrong final bootrom size! Check the .bin pasting" && false 120 | fi 121 | 122 | # Instruction scanning checks on final binary image 123 | ${ROM_SRC_DIR}/scripts/check_sg_symbols $BUILD_COMBINED_DIR/bootrom-combined.bin $BUILD_V8M_DIR/bootrom.elf $ARM_NONE_EABI_OBJDUMP 124 | 125 | # Check for duplicate ROM table codes 126 | ${ROM_SRC_DIR}/scripts/check_rom_table_duplicates ${ROM_SRC_DIR}/lib/pico-sdk/src/rp2_common/pico_bootrom/include/pico/bootrom_constants.h 127 | 128 | # Check for useless RCPs of a register with itself 129 | ${ROM_SRC_DIR}/scripts/check_useless_rcps $BUILD_V8M_DIR/bootrom.dis 130 | 131 | # Check for RCP tags which have count_set without count_check, or only one of canary_get/canary_set 132 | ${ROM_SRC_DIR}/scripts/check_useless_canaries $BUILD_V8M_DIR/bootrom.dis 133 | 134 | # Check for function returns where there isn't a canary function since the beginning of the function 135 | ${ROM_SRC_DIR}/scripts/check_function_returns $BUILD_V8M_DIR/bootrom.dis 136 | -------------------------------------------------------------------------------- /scripts/bin2hex: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | help_str = """bin2hex: Convert flat binary files to Verilog-style hex files. 4 | 5 | Output is little-endian, i.e. lower-addressed bytes are less significant in 6 | the output words (further to the right). The word size can be specified, but 7 | must be a multiple of 8 bits. 8 | 9 | Start and end addresses can be specified, and the input will be padded if the 10 | end address is past the end of the file. The default is to process the entire 11 | file into hex. Address 0 is assumed to be the start of the file. 12 | 13 | If multiple output files are specified, the output will be striped across them 14 | word-for-word. Suitable for initialising striped SRAM like on RP2040. 15 | """ 16 | 17 | import sys 18 | import re 19 | import argparse 20 | 21 | def bin2hex(wordsize, startaddr, endaddr, infile, outfiles): 22 | if wordsize <= 0 or wordsize % 8 != 0: 23 | sys.exit("Width must be a multiple of 8 (got {})".format(wordsize)) 24 | bytes_per_word = wordsize // 8 25 | 26 | binimg = open(infile, 'rb').read() 27 | if endaddr < 0: 28 | endaddr = len(binimg) 29 | 30 | if startaddr > endaddr: 31 | sys.exit("Start address must not be greater than end address.") 32 | if endaddr > len(binimg): 33 | binimg = binimg + bytes(endaddr - len(binimg)) 34 | 35 | # Output is striped across multiple output files, with the first word 36 | # going to the first file in the file list. 37 | ofiles = [open(f,'w') for f in outfiles] 38 | ofile_idx = 0 39 | 40 | for chunk in range(startaddr, endaddr, bytes_per_word): 41 | 42 | word = '' 43 | for i in range(bytes_per_word): 44 | word = ("%02X" % (binimg[chunk + i] if chunk + i < len(binimg) else 0)) + word 45 | 46 | # Write to the output file, then rotate to next output file 47 | ofiles[ofile_idx].write("%s\n" % word) 48 | ofile_idx = (ofile_idx + 1) % len(outfiles) 49 | 50 | for o in ofiles: 51 | o.close() 52 | 53 | # Allow hex, binary, decimal numbers etc on cmdline 54 | def anyint(x): 55 | try: 56 | return int(x, 0) 57 | except: 58 | raise ValueError() 59 | 60 | if __name__ == "__main__": 61 | parser = argparse.ArgumentParser(epilog=help_str, formatter_class=argparse.RawDescriptionHelpFormatter) 62 | parser.add_argument("-width", default=32, type=anyint, 63 | help="Data per output line. Default is 32 bits.") 64 | parser.add_argument("-start", type=anyint, default=0, 65 | help="Address to start at. Default is 0, i.e. start of bin file.") 66 | parser.add_argument("-end", type=anyint, default=-1, 67 | help="Address to end at. Default is -1, meaning process the whole file.") 68 | parser.add_argument("infile", help="input.bin") 69 | parser.add_argument("outfile", nargs='+', 70 | help="output hex files, can optionally stripe across multiple files") 71 | args = parser.parse_args() 72 | bin2hex(args.width, args.start, args.end, args.infile, args.outfile) 73 | -------------------------------------------------------------------------------- /scripts/check_function_returns: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Scan the arm8 disassembly for function returns (bx lr or pop {..., pc}) and 4 | # assert that there is at least one rcp_count_check or rcp_canary_check in 5 | # between the function entry and the return. This is necessarily quite a 6 | # loose check because of myriad forms of epilogue, but it catches things that 7 | # the compiler has suddenly decided to outline based on some internal cost 8 | # heuristic reaching a tipping point. 9 | 10 | import re 11 | import sys 12 | 13 | if len(sys.argv) != 2: 14 | sys.exit("Usage: check_function_returns bootrom.dis") 15 | 16 | # !!! 17 | # When you add an exception to this list, ADD A COMMENT WITH A REASON 18 | # !!! 19 | KNOWN_EXCEPTIONS = [ 20 | 21 | # This is the final bx lr in the ROM table lookup function, used as an 22 | # empty function when we need to stub a function pointer. The actual 23 | # function body ends with an rcp_check_step and is handwritten: 24 | "varm_noop", 25 | 26 | # This is actually s_varm_riscv_hx_get_boot_flag_impl, which has been 27 | # punted into .rodata (XN and NS) because it's only executed on RISC-V 28 | "__start_of_secure_xn", 29 | 30 | # This is 3-instruction function that spins until r0 wraps 0. As it 31 | # doesn't pop the stack it's not easy to use as part of a chain. 32 | # Decided it is uninteresting (maybe this will bite us) 33 | "s_native_busy_wait_at_least_cycles", 34 | 35 | # there are no instructions other than a push in the prolog and a pop in 36 | # the epilogue, and our canaries don't help against protecting regular 37 | # prologue pushes/epilogue pops in other places. 38 | "s_varm_api_reboot", 39 | ] 40 | 41 | seen_known_exceptions = set() 42 | seen_check = False 43 | entry_line = "" 44 | function_name = "" 45 | bad_exit_count = 0 46 | for l in open(sys.argv[1]).readlines(): 47 | l = l.strip() 48 | m = re.match(r"^[0-9a-f]+\s+<([^<]+)>:$", l) 49 | if m: 50 | entry_line = l 51 | function_name = m.group(1) 52 | seen_check = False 53 | elif "rcp_count_check" in l or "rcp_canary_check" in l: 54 | seen_check = True 55 | elif l.endswith("bx\tlr") or l.endswith("pc}"): 56 | if not seen_check and function_name in KNOWN_EXCEPTIONS: 57 | #print("Ignoring known unprotected exit: " + function_name) 58 | seen_known_exceptions.add(function_name) 59 | elif not seen_check: 60 | print("Unprotected exit from: " + entry_line) 61 | print(" ^^^ exit at: " + " " * (8 - l.index(":")) + l) 62 | bad_exit_count += 1 63 | # In case of multiple exits, they ought to all be protected 64 | seen_check = False 65 | 66 | for e in KNOWN_EXCEPTIONS: 67 | if e not in seen_known_exceptions: 68 | print("Note: function " + e + " was marked as a known unprotected exit, but no such exit was found") 69 | 70 | if bad_exit_count > 0: 71 | sys.exit("Found {} unprotected exits".format(bad_exit_count)) 72 | else: 73 | print("No unprotected exits found") 74 | -------------------------------------------------------------------------------- /scripts/check_rom_table_duplicates: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Little script to find duplicate rom table codes 4 | 5 | import re 6 | import sys 7 | 8 | if len(sys.argv) != 2: 9 | sys.exit("Usage: check_rom_table_duplicates (path/to/bootrom_constants.h)") 10 | 11 | with open(sys.argv[1], "r") as f: 12 | txt = f.read() 13 | codes = re.findall("ROM_TABLE_CODE\(['\"](\w)['\"], ['\"](\w)['\"]\)", txt) 14 | 15 | parsed_codes = set() 16 | for code in codes: 17 | if code not in parsed_codes: 18 | parsed_codes.add(code) 19 | else: 20 | print(code, "Is a duplicate ROM table code!!") 21 | 22 | if len(parsed_codes) == len(codes): 23 | print("No duplicate ROM table codes") -------------------------------------------------------------------------------- /scripts/check_sg_symbols: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Scan a .bin for SG bit patterns, and make sure they all have a matching 4 | # symbol starting with sg_ in the corresponding ELF file (avoid accidental 5 | # SGs) 6 | 7 | import sys 8 | import subprocess 9 | import re 10 | import struct 11 | 12 | args = sys.argv[1:] 13 | if len(args) != 3: 14 | sys.exit("Usage: check_sg_symbols (bin) (elf) (objdump executable)") 15 | 16 | bindata = open(args[0], "rb").read() 17 | 18 | objdump_stdout = subprocess.check_output([args[2], "-t", args[1]]).decode("utf-8") 19 | 20 | # Example objdump -t line: 21 | # 00007f38 g F .secure_gateways 0000003a s_from_ns_arm8_api_flash_runtime_to_storage_addr 22 | known_sg_addrs = set() 23 | for l in objdump_stdout.splitlines(): 24 | m = re.match(r"([0-9a-f]{8}).*\b[0-9a-f]{8}\s*(\w+)", l) 25 | if not m: 26 | continue 27 | if m.group(2).startswith("sg_"): 28 | known_sg_addrs.add(int(m.group(1), 16)) 29 | 30 | sg_count = 0 31 | for i in range(0, len(bindata) - 2, 2): 32 | word = struct.unpack("]+)>", dis): 16 | name = "{:>4} {}".format(*m) 17 | if name not in counts: 18 | counts[name] = 0 19 | counts[name] += 1 20 | 21 | for count, name in reversed(sorted(list((v, k) for k, v in counts.items()))): 22 | print("{:<3} {}".format(count, name)) 23 | -------------------------------------------------------------------------------- /scripts/find_wasteful_riscv_j_jal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # It's easy to get a 32-bit j/jal when a 16-bit one would have sufficed, due 4 | # to some toolchain limitations. Scan for such instructions, so we can 5 | # manually fix them up whenever we really need to scrape a few bytes. 6 | 7 | import re 8 | import sys 9 | 10 | if len(sys.argv) != 2: 11 | sys.exit("Usage: find_wasteful_riscv_j_jal ") 12 | 13 | count = 0 14 | last_label = "" 15 | for l in open(sys.argv[1]).readlines(): 16 | if l.endswith(">:\n"): 17 | last_label = l.strip() 18 | # e.g. 7264: f25ff06f j 7188 19 | m = re.match(r"^\s*([0-9a-f]+):\s+([0-9a-f]+)\s+(?:j|jal)\s+([0-9a-f]+)", l) 20 | if not m: 21 | continue 22 | pc = int(m.group(1), 16) 23 | size_bytes = len(m.group(2)) // 2 24 | target = int(m.group(3), 16) 25 | if size_bytes == 4 and target >= pc - 2048 and target <= pc + 2046: 26 | print(last_label) 27 | print(l.rstrip()) 28 | count += 1 29 | 30 | print("Potential to save {} bytes".format(count * 2)) 31 | -------------------------------------------------------------------------------- /scripts/fixup_asm_hooks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse, sys 3 | 4 | parser = argparse.ArgumentParser(description="Replace 8 bit asm_hook offsets in ELF") 5 | 6 | parser.add_argument("input", help="Input ELF") 7 | parser.add_argument("symbols", help="Input symbol file") 8 | parser.add_argument("output", help="Output ELF") 9 | parser.add_argument("asm_hooks", help="asm_hook list", nargs="*") 10 | 11 | args = parser.parse_args() 12 | 13 | fin = open(args.input, mode="rb") 14 | # Reading file data with read() method 15 | contents = fin.read() 16 | fin.close() 17 | 18 | symbol_addresses = {} 19 | fin = open(args.symbols, "r") 20 | for line in fin: 21 | values = line.split() 22 | if len(values) > 2: 23 | symbol_addresses[values[2]] = int(values[0], 16) 24 | fin.close() 25 | 26 | varmulet_nsboot_asm_hooks = symbol_addresses["varmulet_nsboot_asm_hooks"] 27 | varmulet_preboot_asm_hooks = symbol_addresses["varmulet_preboot_asm_hooks"] 28 | first_hook_addr = symbol_addresses["first_hook_addr"] 29 | hooks_size = varmulet_preboot_asm_hooks - varmulet_nsboot_asm_hooks 30 | if hooks_size < 0: 31 | raise SystemExit("asm_hooks_in_wrong_order") 32 | 33 | found = 0 34 | for i in range(0,len(contents) - hooks_size * 2,2): 35 | if contents[i] == 0 and contents[i+1] == 1 and contents[i+2] == 2 and contents[i+3] == 3 and contents[hooks_size + i] == 0 and contents[hooks_size + i + 1] == 1 and contents[hooks_size + i + 2] == 2 and contents[hooks_size + i + 3] == 3: 36 | # print("Found at {}".format(hex(i))) 37 | found = found + 1 38 | offsets = bytearray() 39 | for j in range(0,hooks_size * 2): 40 | hook_name = args.asm_hooks[contents[i+j]] 41 | # print(" want {}".format(hook_name)) 42 | addr = symbol_addresses[hook_name] 43 | delta = addr - first_hook_addr 44 | if delta & 1: 45 | raise SystemExit("{} is mis-aligned ".format(hook_name)) 46 | # print(" = {} or {}".format(hex(addr), hex(delta >> 1))) 47 | offsets = offsets + (delta >> 1).to_bytes(1, 'little') 48 | contents = contents[:i] + offsets + contents[i+32:] 49 | 50 | if found == 0: 51 | raise SystemExit("didn't find asm_hooks") 52 | elif found > 1: 53 | raise SystemExit("found something else like asm hooks") 54 | fout = open(args.output, mode="wb") 55 | fout.write(contents) 56 | fout.close() 57 | 58 | -------------------------------------------------------------------------------- /scripts/inject_p16: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse, sys 3 | 4 | parser = argparse.ArgumentParser(description="Replace 16 bit pointers in ELF") 5 | 6 | parser.add_argument("input", help="Input ELF") 7 | parser.add_argument("symbols", help="Input symbol file") 8 | parser.add_argument("output", help="Output ELF") 9 | #parser.add_argument("prefix", help="Prefix added to names names of exported symbols (pass NO_PREFIX for none)") 10 | parser.add_argument("p16_symbols", help="symbol name(s) required", nargs="*") 11 | 12 | args = parser.parse_args() 13 | 14 | fin = open(args.input, mode="rb") 15 | # Reading file data with read() method 16 | contents = fin.read() 17 | fin.close() 18 | 19 | symbol_counts = {} 20 | ordered_symbol_names = [] 21 | data_symbol_names = {} 22 | for symbol in args.p16_symbols: 23 | parts = symbol.split("/") 24 | attr = parts[1] 25 | if attr[0] == 'd': 26 | data_symbol_names[parts[0]] = True 27 | attr = attr[1:] 28 | else: 29 | data_symbol_names[parts[0]] = False 30 | symbol_counts[parts[0]] = int(attr) 31 | ordered_symbol_names = ordered_symbol_names + [parts[0]] 32 | 33 | symbol_addresses = {} 34 | fin = open(args.symbols, "r") 35 | for line in fin: 36 | values = line.split() 37 | if len(values) > 2: 38 | symbol_addresses[values[2]] = int(values[0], 16) 39 | fin.close() 40 | 41 | for i in range(0,len(contents)-4,2): 42 | if contents[i] == 0x4b and contents[i+1] == 0xf6 and contents[i+3] >= 0x30 and contents[i+3] <= 0x3f: 43 | symbol_name = ordered_symbol_names[contents[i+2]] 44 | addr = symbol_addresses[symbol_name] 45 | if addr > 0xffff: 46 | raise SystemExit("address {} is out of range".format(hex(addr))) 47 | if not data_symbol_names[symbol_name]: 48 | addr = addr + 1 49 | # print("{} is '{}' {}".format(i, symbol_name, hex(addr))) 50 | word0 = 0xf240 + (addr >> 12) + (0x400 if addr & (1 << 11) else 0) 51 | word1 = ((contents[i+3] & 0xf) << 8) + (addr & 0xff) + (((addr >> 8) & 7) << 12) 52 | contents = contents[:i] + word0.to_bytes( 2, 'little') + word1.to_bytes(2, 'little') + contents[i+4:] 53 | symbol_counts[symbol_name] = symbol_counts[symbol_name] - 1 54 | 55 | symbol_counts = dict(filter(lambda item: item[1] != 0, symbol_counts.items())) 56 | # we print mismatched-count symbols in a2-64k rather than asserting because the answers are different 57 | print(symbol_counts) 58 | #if len(symbol_counts) > 0: 59 | # print(symbol_counts) 60 | # raise SystemExit("unexpected usage counts for p16s") 61 | fout = open(args.output, mode="wb") 62 | fout.write(contents) 63 | fout.close() 64 | 65 | -------------------------------------------------------------------------------- /scripts/inject_rcp_consts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse, sys 3 | 4 | parser = argparse.ArgumentParser(description="Convert fake RCP placeholders into MOVW for RCP constants") 5 | 6 | parser.add_argument("input", help="Input ELF") 7 | parser.add_argument("output", help="Output ELF") 8 | 9 | args = parser.parse_args() 10 | 11 | fin = open(args.input, mode="rb") 12 | # Reading file data with read() method 13 | contents = fin.read() 14 | fin.close() 15 | 16 | for i in range(0,len(contents)-4,2): 17 | if contents[i] == 0xf0 and contents[i+1] == 0xee and (contents[i+2] >> 4) == 1 and (contents[i+3] & 15) == 7: 18 | type = contents[i+2] & 0xf 19 | reg = contents[i+3] >> 4 20 | # print("{} reg {} = {}".format(hex(i), reg, type)) 21 | word0 = 0 22 | word1 = 0 23 | if type == 0: 24 | word0 = 0xf04f 25 | word1 = 0x20a5 + (reg << 8) 26 | elif type == 1: 27 | word0 = 0xf04f 28 | word1 = 0x10c3 + (reg << 8) 29 | elif type == 2: 30 | word0 = 0xf04f 31 | word1 = 0x2096 + (reg << 8) 32 | elif type == 3: 33 | word0 = 0xf04f 34 | word1 = 0x10e1 + (reg << 8) 35 | elif type == 4: 36 | word0 = 0xf04f 37 | word1 = 0x101e + (reg << 8) 38 | elif type == 5: 39 | word0 = 0xf04f 40 | word1 = 0x1013 + (reg << 8) 41 | elif type == 6: 42 | word0 = 0xf04f 43 | word1 = 0x10d0 + (reg << 8) 44 | elif type == 7: 45 | word0 = 0xf04f 46 | word1 = 0x1054 + (reg << 8) 47 | elif type == 8: 48 | word0 = 0xf04f 49 | word1 = 0x1097 + (reg << 8) 50 | elif type == 9: 51 | word0 = 0xf04f 52 | word1 = 0x208a + (reg << 8) 53 | elif type == 10: 54 | word0 = 0xf04f 55 | word1 = 0x202f + (reg << 8) 56 | elif type == 11: 57 | word0 = 0xf04f 58 | word1 = 0x30c3 + (reg << 8) 59 | else: 60 | raise SystemExit("unknown RCP constant {}".format(type)) 61 | 62 | contents = contents[:i] + word0.to_bytes( 2, 'little') + word1.to_bytes(2, 'little') + contents[i+4:] 63 | 64 | fout = open(args.output, mode="wb") 65 | fout.write(contents) 66 | fout.close() 67 | 68 | -------------------------------------------------------------------------------- /scripts/make_import_header: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse, sys 3 | 4 | parser = argparse.ArgumentParser(description="Make Linker Script Import for symbols") 5 | 6 | parser.add_argument("input", help="Input symbols (nm output)") 7 | parser.add_argument("output", help="Output .h file suitable for include") 8 | parser.add_argument("prefix", help="Prefix added to names names of exported symbols (pass NO_PREFIX for none)") 9 | parser.add_argument("symbols", help="symbol name(s) required", nargs="*") 10 | args = parser.parse_args() 11 | 12 | # This is clearly the wrong way to do it but I don't want to fight argparse 13 | # misparsing mixtures of keyword and optional positional args (also CMake 14 | # likes to strip empty quote pairs) 15 | prefix = "" if args.prefix == "NO_PREFIX" else args.prefix 16 | 17 | infile = open(args.input, "r") 18 | 19 | symbols_and_types = {} 20 | for symbol in args.symbols: 21 | parts = symbol.split("/") 22 | symbols_and_types[parts[0]] = "" if len(parts) == 1 else parts[1] 23 | 24 | with open(args.output, 'w') as f: 25 | print("#pragma once", file=f) 26 | for line in infile: 27 | values = line.split() 28 | if len(values) > 2 and str(values[2]) in symbols_and_types.keys(): 29 | addr = int(values[0], 16) 30 | if not "d" in symbols_and_types[values[2]]: 31 | addr += 1 # thumb 32 | # we may want to make this optional, but for now we are just doing functions so need thumb bit 33 | print("#define {}{}_addr 0x{:X}".format(prefix, values[2], addr), file=f) 34 | symbols_and_types.pop(str(values[2])) 35 | 36 | not_found_symbols = dict(filter(lambda item: not "o" in item[1], symbols_and_types.items())) 37 | 38 | if len(not_found_symbols.keys()) > 0: 39 | print("The following symbols were not found:", ', '.join(not_found_symbols.keys())) 40 | sys.exit(1) 41 | 42 | infile.close() -------------------------------------------------------------------------------- /scripts/make_import_ld: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse, sys 3 | 4 | parser = argparse.ArgumentParser(description="Make Linker Script Import for symbols") 5 | 6 | parser.add_argument("input", help="Input symbols (nm output)") 7 | parser.add_argument("output", help="Output .ld file suitable for include") 8 | parser.add_argument("prefix", help="Prefix added to names names of exported symbols (pass NO_PREFIX for none)") 9 | parser.add_argument("symbols", help="symbol name(s) required", nargs="*") 10 | args = parser.parse_args() 11 | 12 | # This is clearly the wrong way to do it but I don't want to fight argparse 13 | # misparsing mixtures of keyword and optional positional args (also CMake 14 | # likes to strip empty quote pairs) 15 | prefix = "" if args.prefix == "NO_PREFIX" else args.prefix 16 | 17 | infile = open(args.input, "r") 18 | 19 | symbols_and_types = {} 20 | for symbol in args.symbols: 21 | parts = symbol.split("/") 22 | symbols_and_types[parts[0]] = "" if len(parts) == 1 else parts[1] 23 | 24 | with open(args.output, 'w') as f: 25 | for line in infile: 26 | values = line.split() 27 | if len(values) > 2 and str(values[2]) in symbols_and_types.keys(): 28 | addr = int(values[0], 16) 29 | if not "d" in symbols_and_types[values[2]]: 30 | addr += 1 # thumb 31 | # we may want to make this optional, but for now we are just doing functions so need thumb bit 32 | print("{}{} = 0x{:X};".format(prefix, values[2], addr), file=f) 33 | symbols_and_types.pop(str(values[2])) 34 | 35 | not_found_symbols = dict(filter(lambda item: not "o" in item[1], symbols_and_types.items())) 36 | 37 | if len(not_found_symbols.keys()) > 0: 38 | print("The following symbols were not found:", ', '.join(not_found_symbols.keys())) 39 | sys.exit(1) 40 | 41 | infile.close() -------------------------------------------------------------------------------- /scripts/overwrite_prolog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse, sys 3 | 4 | parser = argparse.ArgumentParser(description="Convert fake RCP placeholders into MOVW for RCP constants") 5 | 6 | parser.add_argument("input", help="Input ELF") 7 | parser.add_argument("symbols", help="Input symbol file") 8 | parser.add_argument("output", help="Output ELF") 9 | 10 | args = parser.parse_args() 11 | 12 | fin = open(args.input, mode="rb") 13 | # Reading file data with read() method 14 | contents = fin.read() 15 | fin.close() 16 | 17 | # sonly_varm_make_hx_bool_impl: 18 | # fe15 c736 rcp_canary_get ip, 0x56 (86), nodelay 19 | # b50c push {r2, r3, lr} 20 | # f04f 121e mov.w r2, #1966110 ; 0x1e001e 21 | # 4082 lsls r2, r0 22 | # 4308 orrs r0, r1 23 | # 0843 lsrs r3, r0, #1 24 | # f04f 10e1 mov.w r0, #14745825 ; 0xe100e1 25 | # 00c9 lsls r1, r1, #3 26 | # 1a80 subs r0, r0, r2 27 | # 4088 lsls r0, r1 28 | # ---- these 6 hwords go over the beginning of core0_boot_path_prolog 29 | # 4058 eors r0, r3 30 | # ee20 0710 rcp_bvalid r0, delay 31 | # fe05 c736 rcp_canary_check ip, 0x56 (86), nodelay 32 | # bd0c pop {r2, r3, pc} 33 | 34 | matches = 0 35 | 36 | make_hx_bool_impl_pos = 0 37 | for i in range(0,len(contents)-32,2): 38 | if contents[i] == 0x15 and contents[i+1] == 0xfe and contents[i+2] == 0x36 and contents[i+3] == 0xc7: 39 | make_hx_bool_impl_pos = i 40 | # print("{} reg {} = {}".format(hex(i), reg, type)) 41 | contents = contents[:i+26] + 0xee204058.to_bytes( 4, 'little') + 0xfe050710.to_bytes(4, 'little') + 0xbd0cc736.to_bytes(4, 'little') + contents[i+38:] 42 | matches += 1 43 | 44 | if matches == 0: 45 | raise SystemExit("did not match sonly_varm_make_hx2_bool_impl") 46 | 47 | if matches > 1: 48 | raise SystemExit("matched multiple sonly_varm_make_hx2_bool_impl") 49 | 50 | fin = open(args.symbols, "r") 51 | 52 | prolog_p2_addr = 0 53 | make_hx_bool_impl_addr = 0 54 | for line in fin: 55 | values = line.split() 56 | if len(values) > 2: 57 | if values[2] == "s_varm_crit_core0_boot_path_entry_p2": 58 | prolog_p2_addr = int(values[0], 16) 59 | if values[2] == "sonly_varm_make_hx_bool_impl": 60 | make_hx_bool_impl_addr = int(values[0], 16) 61 | 62 | fin.close() 63 | if prolog_p2_addr == 0 or make_hx_bool_impl_addr == 0: 64 | raise SystemExit("Didn't find s_varm_crit_core0_boot_path_entry_p2 or sonly_varm_make_hx_bool_impl") 65 | 66 | # move file location relative to known symbol 67 | prolog_p2_pos = prolog_p2_addr + make_hx_bool_impl_pos - make_hx_bool_impl_addr 68 | if contents[prolog_p2_pos-1] != 0xb0 or contents[prolog_p2_pos-2] < 0x80: 69 | raise SystemExit("Expected 'sub sp, #' instruction before s_varm_crit_core0_boot_path_entry_p2") 70 | 71 | fout = open(args.output, mode="wb") 72 | fout.write(contents) 73 | fout.close() 74 | 75 | -------------------------------------------------------------------------------- /scripts/scramble_canary_tags: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import random 4 | import re 5 | import sys 6 | 7 | MIN_TAG = 64 8 | MAX_TAG = 191 9 | 10 | FIXED_VALUES = { 11 | "CTAG_S_VARM_MAKE_HX2_BOOL_IMPL": 0x56 12 | } 13 | 14 | order = [i for i in range(MIN_TAG, MAX_TAG + 1) if i not in FIXED_VALUES.values()] 15 | random.shuffle(order) 16 | iter_tags = iter(order) 17 | 18 | if len(sys.argv) != 2: 19 | sys.exit("Usage: scramble_canary_tags header_name.h") 20 | 21 | text_in = open(sys.argv[1]).read() 22 | ofile = open(sys.argv[1], "w") 23 | for l in text_in.splitlines(): 24 | m = re.match(r"^#define\s+(CTAG_\w+)\s+\w+$", l) 25 | if m: 26 | if m.group(1) in FIXED_VALUES: 27 | tag = FIXED_VALUES[m.group(1)] 28 | else: 29 | tag = next(iter_tags, None) 30 | if tag is None: 31 | sys.exit("Ran out of tags to assign") 32 | l = "#define {:<60} 0x{:02x}".format(m.group(1), tag) 33 | ofile.write(l + "\n") 34 | 35 | -------------------------------------------------------------------------------- /scripts/xh3bextm_dis: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse, re 3 | 4 | parser = argparse.ArgumentParser(description="Disassemble Xh3bextm bitmanip instructions in DIS file") 5 | 6 | parser.add_argument("input", help="Input DIS") 7 | parser.add_argument("output", help="Output DIS") 8 | 9 | args = parser.parse_args() 10 | 11 | fin = open(args.input, mode="r") 12 | contents = fin.read() 13 | fin.close() 14 | 15 | abi_regname = [ 16 | "zero", "ra", "sp", "gp", 17 | "tp", "t0", "t1", "t2", 18 | "s0", "s1", "a0", "a1", 19 | "a2", "a3", "a4", "a5", 20 | "a6", "a7", "s2", "s3", 21 | "s4", "s5", "s6", "s7", 22 | "s8", "s9", "s10", "s11", 23 | "t3", "t4", "t5", "t6" 24 | ] 25 | 26 | def xh3bm_dis(m): 27 | opcode = int(m.group(1), 16) 28 | if (opcode & 0xe100307f) != 0x0000000b: 29 | return m.group(0) 30 | elif (opcode & 0x00004000): 31 | return "h3.bextmi {rd}, {rs1}, {shamt}, {size}".format( 32 | rd = abi_regname[(opcode >> 7) & 0x1f], 33 | rs1 = abi_regname[(opcode >> 15) & 0x1f], 34 | shamt = (opcode >> 20) & 0x1f, 35 | size = ((opcode >> 26) & 0x7) + 1 36 | ) 37 | else: 38 | return "h3.bextm {rd}, {rs1}, {rs2}, {size}".format( 39 | rd = abi_regname[(opcode >> 7) & 0x1f], 40 | rs1 = abi_regname[(opcode >> 15) & 0x1f], 41 | rs2 = abi_regname[(opcode >> 20) & 0x1f], 42 | size = ((opcode >> 26) & 0x7) + 1 43 | ) 44 | 45 | contents = re.sub(r"\.insn\t4, 0x([0-9a-f]+)", xh3bm_dis, contents) 46 | 47 | fout = open(args.output, mode="w") 48 | fout.write(contents) 49 | fout.close() 50 | -------------------------------------------------------------------------------- /src/bootrom_layout.template.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #define BOOTROM_ARM_SIZE ${BOOTROM_ARM_SIZE} 10 | #define NSBOOT_SIZE ${NSBOOT_SIZE} 11 | #define BOOTROM_RISCV_SIZE ${BOOTROM_RISCV_SIZE} 12 | #define BOOTROM_SG_SIZE ${BOOTROM_SG_SIZE} 13 | 14 | #define BOOTROM_ARM_START ${BOOTROM_ARM_START} 15 | #define BOOTROM_ARM_END ${BOOTROM_ARM_END} 16 | #define NSBOOT_START ${NSBOOT_START} 17 | #define NSBOOT_END ${NSBOOT_END} 18 | #define NSBOOT_RAM_START ${NSBOOT_RAM_START} 19 | #define NSBOOT_RAM_END ${NSBOOT_RAM_END} 20 | #define NSBOOT_VTOR_OFFSET ${NSBOOT_VTOR_OFFSET} 21 | // this always has thumb bit clear, as varmulet doesn't want one, and this is a BXNS target for ARM 22 | #define NSBOOT_ENTRY_POINT NSBOOT_START 23 | #define BOOTROM_RISCV_START ${BOOTROM_RISCV_START} 24 | #define BOOTROM_RISCV_END ${BOOTROM_RISCV_END} 25 | #define BOOTROM_ROMTABLE_SIZE ${BOOTROM_ROMTABLE_SIZE} 26 | #define BOOTROM_RISCV_END_SIZE ${BOOTROM_RISCV_END_SIZE} 27 | #define BOOTROM_ROMTABLE_START (BOOTROM_RISCV_END - (BOOTROM_RISCV_END_SIZE + BOOTROM_ROMTABLE_SIZE)) 28 | #define BOOTROM_SG_START ${BOOTROM_SG_START} 29 | #define BOOTROM_SG_END ${BOOTROM_SG_END} 30 | #define BOOTROM_SIZE ${BOOTROM_SIZE} 31 | 32 | #define BOOTRAM_RISCV_STATIC_DATA_START ${BOOTRAM_RISCV_STATIC_DATA_START} 33 | #define BOOTRAM_RISCV_STATIC_DATA_SIZE ${BOOTRAM_RISCV_STATIC_DATA_SIZE} 34 | 35 | #define BOOTRAM_ARM_STATIC_DATA_START ${BOOTRAM_ARM_STATIC_DATA_START} 36 | #define BOOTRAM_ARM_STATIC_DATA_SIZE ${BOOTRAM_ARM_STATIC_DATA_SIZE} 37 | #define BOOTROM_IDAU_EXEMPT_END ${BOOTROM_IDAU_EXEMPT_END} 38 | 39 | #define BOOTRAM_PREBOOT_STACK_SIZE ${BOOTRAM_PREBOOT_STACK_SIZE} 40 | #define MAX_XIP_SETUP_SIZE ${MAX_XIP_SETUP_SIZE} 41 | #define BOOTRAM_ALWAYS_SIZE ${BOOTRAM_ALWAYS_SIZE} 42 | #define BOOTRAM_RUNTIME_PER_CORE_SIZE ${BOOTRAM_RUNTIME_PER_CORE_SIZE} 43 | 44 | #define BOOTRAM_PREBOOT_STATE_SIZE 0x08 45 | #define VARMULET_CPU_STATE_SIZE 0x50 // sizeof (armulet_cpu_t) 46 | 47 | //#define BOOTRAM_RISCV_PREBOOT_STACK_SIZE (BOOTRAM_ARM_PREBOOT_STACK_SIZE) 48 | #define BOOTRAM_RUNTIME_CORE0_OFFSET MAX_XIP_SETUP_SIZE 49 | #define BOOTRAM_RUNTIME_CORE1_OFFSET (BOOTRAM_RUNTIME_CORE0_OFFSET + BOOTRAM_RUNTIME_PER_CORE_SIZE) 50 | #define BOOTRAM_PREBOOT_STACK_TOP (BOOTRAM_BASE + BOOTRAM_PREBOOT_STACK_SIZE) 51 | #define BOOTRAM_PREBOOT_STACK_TOP_DWORD_ALIGNED (BOOTRAM_PREBOOT_STACK_TOP & ~7) 52 | #define BOOTRAM_RISCV_PREBOOT_VARMULET_CPU_STATE_OFFSET 0 53 | #define BOOTRAM_XIP_SETUP_CODE_OFFSET 0 54 | #define CORE0_BOOT_USBRAM_WORKSPACE_SIZE ${CORE0_BOOT_USBRAM_WORKSPACE_SIZE} 55 | #define CORE0_BOOT_USBRAM_MAX_WORKSPACE_SIZE ${CORE0_BOOT_USBRAM_MAX_WORKSPACE_SIZE} 56 | #define PARSED_BLOCK_LOOP_SIZE ${PARSED_BLOCK_LOOP_SIZE} 57 | #define LARGEST_BOOTSCAN_CONTEXT_SIZE ${LARGEST_BOOTSCAN_CONTEXT_SIZE} 58 | #define CORE0_BOOT_USBRAM_WORKSPACE_SIZE_PADDING_WORDS ${CORE0_BOOT_USBRAM_WORKSPACE_SIZE_PADDING_WORDS} 59 | #define BLOCK_BUFFER_OR_SIGNATURE_WORKSPACE_SIZE ${BLOCK_BUFFER_OR_SIGNATURE_WORKSPACE_SIZE} 60 | #define HACK_STACK_WORDS ${HACK_STACK_WORDS} 61 | #ifndef __ASSEMBLER__ 62 | #include 63 | #include "hardware/regs/addressmap.h" 64 | static_assert(${BOOTRAM_BASE} == BOOTRAM_BASE, ""); 65 | static_assert(BOOTROM_SG_END == BOOTROM_ARM_START + BOOTROM_SIZE, ""); 66 | 67 | #define CMAKE_PICOBIN_MAX_BLOCK_SIZE ${PICOBIN_MAX_BLOCK_SIZE} 68 | #define CMAKE_PICOBIN_MAX_IMAGE_DEF_BLOCK_SIZE ${PICOBIN_MAX_IMAGE_DEF_BLOCK_SIZE} 69 | #define CMAKE_PICOBIN_MAX_PARTITION_TABLE_BLOCK_SIZE ${PICOBIN_MAX_PARTITION_TABLE_BLOCK_SIZE} 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /src/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(bootrom_shared_apis INTERFACE) 2 | target_include_directories(bootrom_shared_apis INTERFACE ${CMAKE_CURRENT_LIST_DIR}) -------------------------------------------------------------------------------- /src/common/bootrom_assert.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | #pragma once 7 | 8 | #ifndef BOOTROM_ASSERT_DEFAULT_ENABLE 9 | #define BOOTROM_ASSERT_DEFAULT_ENABLE 0 10 | #endif 11 | 12 | #ifndef BOOTROM_ASSERT_ALL_ENABLE 13 | #define BOOTROM_ASSERT_ALL_ENABLE 0 14 | #endif 15 | 16 | #ifndef BOOTROM_ASSERT_ALL_DISABLE 17 | #define BOOTROM_ASSERT_ALL_DISABLE 0 18 | #endif 19 | 20 | #define BOOTROM_ASSERTIONS_ENABLED(x) ((BOOTROM_ASSERT_ ## x ## _ENABLE || BOOTROM_ASSERT_ALL_ENABLE) && !BOOTROM_ASSERT_ALL_DISABLE) 21 | 22 | #ifndef BOOTROM_ASSERT_USB_ENABLE 23 | #define BOOTROM_ASSERT_USB_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 24 | #endif 25 | 26 | #ifndef BOOTROM_ASSERT_GENERIC_FLASH_ENABLE 27 | #define BOOTROM_ASSERT_GENERIC_FLASH_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 28 | #endif 29 | 30 | #ifndef BOOTROM_ASSERT_FLASH_BOOT_ENABLE 31 | #define BOOTROM_ASSERT_FLASH_BOOT_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 32 | #endif 33 | 34 | #ifndef BOOTROM_ASSERT_RAM_BOOT_ENABLE 35 | #define BOOTROM_ASSERT_RAM_BOOT_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 36 | #endif 37 | 38 | #ifndef BOOTROM_ASSERT_BLOCK_SCAN_ENABLE 39 | #define BOOTROM_ASSERT_BLOCK_SCAN_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 40 | #endif 41 | 42 | #ifndef BOOTROM_ASSERT_MISC_ENABLE 43 | #define BOOTROM_ASSERT_MISC_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 44 | #endif 45 | 46 | #ifndef BOOTROM_ASSERT_UF2_ENABLE 47 | #define BOOTROM_ASSERT_UF2_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 48 | #endif 49 | 50 | #ifndef BOOTROM_ASSERT_NSBOOT_ENABLE 51 | #define BOOTROM_ASSERT_NSBOOT_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 52 | #endif 53 | 54 | #ifndef BOOTROM_ASSERT_PICOBOOT_ENABLE 55 | #define BOOTROM_ASSERT_PICOBOOT_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 56 | #endif 57 | 58 | #ifndef BOOTROM_ASSERT_FLASH_ENABLE 59 | #define BOOTROM_ASSERT_FLASH_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 60 | #endif 61 | 62 | #ifndef BOOTROM_ASSERT_SWEETB_ENABLE 63 | #define BOOTROM_ASSERT_SWEETB_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 64 | #endif 65 | 66 | #ifndef BOOTROM_ASSERT_PARTITION_TABLE_ENABLE 67 | #define BOOTROM_ASSERT_PARTITION_TABLE_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 68 | #endif 69 | 70 | #ifndef BOOTROM_ASSERT_IMAGE_BOOT_ENABLE 71 | #define BOOTROM_ASSERT_IMAGE_BOOT_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 72 | #endif 73 | 74 | #ifndef BOOTROM_ASSERT_FLASH_PERMISSIONS_ENABLE 75 | #define BOOTROM_ASSERT_FLASH_PERMISSIONS_ENABLE BOOTROM_ASSERT_DEFAULT_ENABLE 76 | #endif 77 | #ifndef __ASSEMBLER__ 78 | #include 79 | #if !SB_TEST 80 | #undef assert 81 | void dont_use_regular_assert(bool); 82 | #define assert dont_use_regular_assert 83 | #endif 84 | #if !BOOTROM_ASSERT_DISABLED 85 | void __attribute__((noreturn)) bootrom_assertion_failure(const char *fn, uint line); 86 | 87 | #define bootrom_assert(w, condition) ({ \ 88 | if (BOOTROM_ASSERTIONS_ENABLED(w) && !(condition)) { \ 89 | bootrom_assertion_failure(__FILE__, __LINE__); \ 90 | } \ 91 | }) 92 | #else 93 | #define bootrom_assert(w, condition) ((void)0) 94 | #endif 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /src/common/bootrom_common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #define NSBOOT_STACK_WORDS 236 10 | 11 | #if !USE_16BIT_POINTERS || defined(__riscv) 12 | #define P16_RAW(x) x 13 | #define P16(x) x 14 | #define P16_D(x) (&x) 15 | #define P16_F(x) x 16 | #define P16_A(x) x 17 | #else 18 | #define P16_PREFIX 0xbb00 19 | #define P16_CONSTANT(x) (P16_PREFIX | P16_ ## x) 20 | #ifndef __ASSEMBLER__ 21 | static __force_inline uintptr_t get_fp16(uint constant) { 22 | uintptr_t v; 23 | pico_default_asm( 24 | "movw %0, %1\n" 25 | : "=r" (v) 26 | : "i" (constant) 27 | ); 28 | return v; 29 | } 30 | #define P16_TYPED(t, x) ((t)get_fp16(P16_CONSTANT(x))) 31 | //#define P16_TYPED(t, x) ((t)P16_CONSTANT(x)) 32 | #define P16(x) P16_TYPED(typeof(x),x) 33 | // replaces &data 34 | #define P16_D(x) P16_TYPED(typeof(&(x)),x) 35 | // replaces func 36 | #define P16_F(x) P16_TYPED(typeof(&(x)),x) 37 | // replaces array 38 | #define P16_A(x) P16_TYPED(typeof(&(x)[0]),x) 39 | #else 40 | #define P16(x) P16_CONSTANT(x) 41 | #endif 42 | #endif 43 | 44 | #ifndef __ASSEMBLER__ 45 | #include "hardware/rcp.h" 46 | 47 | typedef __aligned(4) uint8_t aligned4_uint8_t; 48 | typedef __aligned(2) uint8_t aligned2_uint8_t; 49 | 50 | typedef struct { 51 | uint32_t e[2]; 52 | } uint32_pair_t; 53 | 54 | typedef struct { 55 | uint32_t e[4]; 56 | } uint32_quad_t; 57 | 58 | typedef struct { 59 | uint32_t e[6]; 60 | } uint32_sext_t; 61 | 62 | // Stop the compiler from constant-folding a hardware base pointer into the 63 | // pointers to individual registers, in cases where constant folding has 64 | // produced redundant 32-bit pointer literals that could have been load/store 65 | // offsets. (Note typeof(ptr+0) gives non-const, for +r constraint.) E.g. 66 | // uart_hw_t *uart0 = __get_opaque_ptr(uart0_hw); 67 | #define __get_opaque_ptr(ptr) ({ \ 68 | typeof((ptr)+0) __opaque_ptr = (ptr); \ 69 | asm ("" : "+r"(__opaque_ptr)); \ 70 | __opaque_ptr; \ 71 | }) 72 | 73 | // Similarly, for other constants that are prone to producing a new 32-bit 74 | // constant every time some foldable operation is done on them: 75 | #define __get_opaque_value(val) __get_opaque_ptr(val) 76 | 77 | // Clone a value (in a more efficient way than __get_opaque_ptr 78 | #define __clone_value(v) ({ \ 79 | typeof((v)+0) __rc; \ 80 | asm ("mov %0, %1\n" : "=&r"(__rc) : "r" (v)); \ 81 | __rc; \ 82 | }) 83 | 84 | // We have two chip selects, with a 24-bit address window for each. The actual 85 | // limit may be lower based on the sizes configured in FLASH_DEVINFO, but 86 | // this is the hard stop. 87 | #define MAX_FLASH_ADDR_OFFSET (0x2u << 24) 88 | 89 | #define FLASH_SECTOR_SHIFT 12u 90 | #define FLASH_SECTOR_SIZE (1ul << FLASH_SECTOR_SHIFT) 91 | #define FLASH_SECTOR_REMAINDER_MASK (FLASH_SECTOR_SIZE - 1u) 92 | 93 | #define PICOBIN_PARTITION_LOCATION_SECTOR_BIT_MASK 0x1fffu 94 | 95 | #ifndef __ARM_ARCH_8M_MAIN__ 96 | #define branch_under_varmulet(label) ({ rcp_asm("mrc p7, #1, r15, c0, c0, #0\n"); asm goto ("bvs %l[" __XSTRING(label) "]\n" : : : : label); }) 97 | #define branch_under_non_varmulet(label) ({ rcp_asm("mrc p7, #1, r15, c0, c0, #0\n"); asm goto ("bvc %l[" __XSTRING(label) "]\n" : : : : label); }) 98 | #define branch_under_varmulet_far(label) ({ asm goto ( \ 99 | ".cpu cortex-m33\n"\ 100 | "mrc p7, #1, r15, c0, c0, #0\n"\ 101 | "bvc 1f\n"\ 102 | "b %l[" __XSTRING(label) "]\n"\ 103 | ".cpu cortex-m23\n"\ 104 | "1:\n"\ 105 | : : : : label);\ 106 | }) 107 | 108 | #endif 109 | 110 | #if !defined(__riscv) && !defined(__ARM_ARCH_8M_MAIN__) 111 | #define varm_callable(x) varm_to_##x 112 | #define varm_and_native(x) varm_##x 113 | #else 114 | #define varm_callable(x) x 115 | #define varm_and_native(x) native_##x 116 | #endif 117 | 118 | #if MINI_PRINTF || USE_64K_BOOTROM 119 | static __force_inline uint32_t get_sp(void) { 120 | uint32_t rc; 121 | pico_default_asm( 122 | #ifdef __riscv 123 | "mv %0, sp\n" 124 | #else 125 | "mov %0, sp\n" 126 | #endif 127 | : "=r" (rc)); 128 | return rc; 129 | } 130 | #else 131 | // declared but unimplemented so that it doesn't break compilation of asserts that use it 132 | uint32_t get_sp(void); 133 | #endif 134 | 135 | #ifndef __riscv 136 | static __force_inline void disable_irqs(void) { 137 | pico_default_asm_volatile("cpsid i"); 138 | } 139 | 140 | static __force_inline void enable_irqs(void) { 141 | pico_default_asm_volatile("cpsie i"); 142 | } 143 | #endif 144 | 145 | #endif 146 | -------------------------------------------------------------------------------- /src/common/native_exports.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | #include "bootrom_common.h" 9 | #include 10 | 11 | void *varm_callable(native_memcpy)(void *a, const void *b, uint32_t len); 12 | void *varm_callable(native_memset0)(void *a, uint32_t len); 13 | void *varm_callable(native_memset)(void *a, uint v, uint32_t len); 14 | 15 | #define varm_or_native_memcpy varm_callable(native_memcpy) 16 | #define varm_or_native_memset0 varm_callable(native_memset0) 17 | 18 | void varm_noop(void); 19 | bool varm_is_sram_or_xip_ram(uint32_t addr); 20 | 21 | -------------------------------------------------------------------------------- /src/common/nsboot_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pico.h" 10 | #include "bootrom_layout.h" 11 | 12 | typedef enum { 13 | BOOTSEL_MODE_USB = 0x0, // SD1 pulled low 14 | BOOTSEL_MODE_UART = 0x1 // SD1 driven high 15 | } bootsel_serialmode_t; 16 | 17 | typedef struct { 18 | uint32_t public_rand_id[2]; 19 | } chip_id_t; 20 | 21 | // NOTE THIS IS ALSO THE NS VECTOR TABLE! 22 | typedef struct { 23 | // config settings 24 | int8_t usb_activity_pin; 25 | uint8_t bootsel_flags; 26 | uint8_t serial_mode_and_inst; 27 | chip_id_t chip_id; 28 | } __aligned(4) nsboot_config_t; 29 | 30 | #define nsboot_config ((nsboot_config_t *)NSBOOT_RAM_START) 31 | -------------------------------------------------------------------------------- /src/common/nsboot_secure_calls.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pico.h" 10 | 11 | // note the order of these is important as it is mirrored in asm tables in arm8_bootrom_rt0.S and varmulet_hooks_bootrom.S 12 | 13 | // these SC_ calls (SC_foo is implemented by sc_foo stub) will pass r0, r1, r2 (and SC_ number in r3) to SG handler in 14 | // ARM secure code. On RISC-V the SG is trapped, and the secure ARM code is executed directly 15 | 16 | #define SC_connect_internal_flash 0x0 17 | #define SC_flash_page_program 0x1 18 | #define SC_flash_sector_erase 0x2 19 | #define SC_flash_read_data 0x3 20 | #define SC_flash_abort 0x4 21 | #define SC_reboot 0x5 22 | #define SC_otp_access 0x6 23 | #define SC_ram_trash_get_uf2_target_partition 0x7 24 | #define SC_get_partition_table_info 0x8 25 | #define SC_get_sys_info 0x9 26 | #if FEATURE_EXEC2 27 | #define SC_picoboot_exec2 0xa 28 | #define SC_max_secure_call_num 0xa 29 | #else 30 | #define SC_max_secure_call_num 0x9 31 | #endif 32 | 33 | 34 | #ifndef __ASSEMBLER__ 35 | #include "nsboot_config.h" 36 | #include "boot/picoboot.h" 37 | #include "pico/bootrom_constants.h" 38 | 39 | // sc_or_varm_ is a SG into secure on ARM and a continuation to an ARM func still under varmulet on RISC-V 40 | // sc_or_native_ is a SG into secure on ARM and a call into a native RISC-V function on RISC-V 41 | 42 | // note all of these methods return PICOBOOT_ return codes 43 | #ifndef __riscv 44 | // Multiple callers, so routed through trampoline to save space on the ordinal: 45 | void sc_or_varm_flash_exit_xip(void); 46 | void sc_or_varm_flash_enter_cmd_xip(void); 47 | void sc_or_varm_flash_abort(void); 48 | int sc_or_varm_reboot(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1); 49 | int sc_or_varm_otp_access(aligned4_uint8_t *buf, uint32_t buf_len, otp_cmd_t cmd); 50 | int sc_or_varm_ram_trash_get_uf2_target_partition(resident_partition_t *partition_out, uint32_t family_id); 51 | #if FEATURE_EXEC2 52 | int sc_or_varm_picoboot_exec2(struct picoboot_exec2_cmd *cmd); 53 | #endif 54 | 55 | // Only called once, so no trampoline (this nonsense is all to get a call with 56 | // r3 set just before the bl, to handle the multiplexing of the shared entry 57 | // point -- we have an asm structure for this already, but it costs an extra 58 | // b.n per entry point) 59 | 60 | __force_inline void sc_or_varm_connect_internal_flash(void) { 61 | pico_default_asm_volatile ( 62 | "movs r3, %0\n" 63 | "bl sc_or_varm_common\n" 64 | : 65 | : "i" (SC_connect_internal_flash) 66 | : "r0", "r1", "r2", "r3", "lr", "ip", "cc" 67 | ); 68 | } 69 | 70 | __force_inline int sc_or_varm_flash_sector_erase(uint32_t addr) { 71 | register uint32_t r0 asm("r0") = addr; 72 | pico_default_asm_volatile ( 73 | "movs r3, %1\n" 74 | "bl sc_or_varm_common\n" 75 | : "+r" (r0) 76 | : "i" (SC_flash_sector_erase) 77 | : "r1", "r2", "r3", "lr", "ip", "cc" 78 | ); 79 | return (int)r0; 80 | } 81 | 82 | __force_inline int sc_or_varm_flash_read_data(uint8_t *rx, uint32_t addr, size_t count) { 83 | register uint8_t *r0 asm("r0") = rx; 84 | register uint32_t r1 asm("r1") = addr; 85 | register size_t r2 asm("r2") = count; 86 | pico_default_asm_volatile ( 87 | "movs r3, %3\n" 88 | "bl sc_or_varm_common\n" 89 | : "+r" (r0), "+r" (r1), "+r" (r2) 90 | : "i" (SC_flash_read_data) 91 | : "r3", "lr", "ip", "cc" 92 | ); 93 | return (int)r0; 94 | } 95 | 96 | __force_inline int sc_or_varm_flash_page_program(const uint8_t *data, uint32_t addr) { 97 | register const uint8_t *r0 asm("r0") = data; 98 | register uint32_t r1 asm("r1") = addr; 99 | pico_default_asm_volatile ( 100 | "movs r3, %2\n" 101 | "bl sc_or_varm_common\n" 102 | : "+r" (r0), "+r" (r1) 103 | : "i" (SC_flash_page_program) 104 | : "r2", "r3", "lr", "ip", "cc" 105 | ); 106 | return (int)r0; 107 | } 108 | 109 | __force_inline int sc_or_varm_get_partition_table_info(uint32_t *out_buffer, uint32_t out_buffer_word_size, uint32_t flags) { 110 | register uint32_t *r0 asm("r0") = out_buffer; 111 | register uint32_t r1 asm("r1") = out_buffer_word_size; 112 | register uint32_t r2 asm("r2") = flags; 113 | pico_default_asm_volatile ( 114 | "movs r3, %3\n" 115 | "bl sc_or_varm_common\n" 116 | : "+r" (r0), "+r" (r1), "+r" (r2) 117 | : "i" (SC_get_partition_table_info) 118 | : "r3", "lr", "ip", "cc" 119 | ); 120 | return (int)r0; 121 | 122 | } 123 | 124 | __force_inline int sc_or_varm_get_sys_info(uint32_t *out_buffer, uint32_t out_buffer_word_size, uint32_t flags) { 125 | register uint32_t *r0 asm("r0") = out_buffer; 126 | register uint32_t r1 asm("r1") = out_buffer_word_size; 127 | register uint32_t r2 asm("r2") = flags; 128 | pico_default_asm_volatile ( 129 | "movs r3, %3\n" 130 | "bl sc_or_varm_common\n" 131 | : "+r" (r0), "+r" (r1), "+r" (r2) 132 | : "i" (SC_get_sys_info) 133 | : "r3", "lr", "ip", "cc" 134 | ); 135 | return (int)r0; 136 | } 137 | #endif //!__riscv 138 | 139 | #endif // __ASSEMBLER__ 140 | -------------------------------------------------------------------------------- /src/common/varm_to_riscv_hints.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #define HINT_OPCODE_BASE 0xbf00 10 | 11 | // The first _n_ of these just run some local RISC-V code, extending the emulator 12 | #define HINT_WFE 2 13 | #define HINT_WFI 3 14 | #define HINT_SEV 4 15 | 16 | #define HINT_RELOCATE_VARM_REGISTERS 5 17 | #define HINT_MULTIPLEX 6 // this does decode then continues into a native call 18 | #define HINT_INVALIDATE_NATIVE_SP 7 // this just sets RISCV-V sp to INVALID_STACK_PTR 19 | #define HINT_TRNG_SHOVELLING 8 // hot code fragment from boot path 20 | // Special case: replacing an Arm implementation with a different Arm 21 | // implementation under RISC-V: 22 | #define HINT_s_native_step_safe_hx_get_boot_flag_impl 9 23 | 24 | // Beyond this point, we are performing a call from an Arm fn entry into a 25 | // native RISC-V fn, so need marshalling of args and return value 26 | #define HINT_FIRST_TO_REQUIRE_MARSHALLING 10 27 | 28 | #define HINT_s_native_crit_flash_put_get 10 29 | #define HINT_s_native_busy_wait_at_least_cycles 11 30 | #define HINT_s_native_crit_init_default_xip_setup_and_enter_image_thunk 12 31 | #define HINT_s_native_api_validate_ns_buffer 13 32 | #define HINT_native_memcpy 14 33 | #define HINT_native_memset 15 34 | // these could have been regular hints above, but we have run out, so these 35 | // are passed in r3 to HINT_MULTIPLEX (therefore max of 3 args): 36 | #define MULTIPLEX_native_nsboot_init 0x0 37 | #define MULTIPLEX_native_usb_irq_enable 0x1 38 | #define MULTIPLEX_native_usb_packet_done 0x2 39 | #define MULTIPLEX_s_native_crit_xip_cache_maintenance 0x3 40 | #define MULTIPLEX_s_native_secure_call_pc_sp 0x4 41 | #define MULTIPLEX_s_native_crit_launch_nsboot 0x5 42 | #define NUM_MULTIPLEX 6 43 | 44 | #ifdef __ASSEMBLER__ 45 | .macro varm_hint num 46 | .hword HINT_OPCODE_BASE + \num * 16 47 | .endm 48 | 49 | #define RISCV_REDIRECT_HINT(func) varm_hint HINT_##func 50 | 51 | .macro VARM_TO_PREAMBLE_raw func, num 52 | .section .text.varm_to_\()\func 53 | .global varm_to_\()\func 54 | .thumb_func 55 | varm_to_\()\func: 56 | varm_hint \num 57 | .p2align 2 58 | // fall thru to impl 59 | .endm 60 | 61 | #define VARM_TO_PREAMBLE(func) VARM_TO_PREAMBLE_raw func, HINT_##func 62 | 63 | .macro VARM_TO_MULTIPLEX_PREAMBLE_raw func, num 64 | .section .text.varm_to_\()\func 65 | .global varm_to_\()\func 66 | .thumb_func 67 | varm_to_\()\func: 68 | movs r3, #\num 69 | varm_hint HINT_MULTIPLEX 70 | .p2align 2 71 | // fall thru to impl 72 | .endm 73 | 74 | #define VARM_TO_MULTIPLEX_PREAMBLE(func) VARM_TO_MULTIPLEX_PREAMBLE_raw func, MULTIPLEX_##func 75 | 76 | #endif // __ASSEMBLER__ 77 | -------------------------------------------------------------------------------- /src/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # code that is compiled for both arm and riscv 2 | add_subdirectory(native) 3 | 4 | if (PICO_RISCV) 5 | add_subdirectory(riscv) 6 | else() 7 | add_subdirectory(arm) 8 | endif() 9 | -------------------------------------------------------------------------------- /src/main/arm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(bootrom_raw) 2 | 3 | # we want the binary in the root 4 | set_target_properties(bootrom_raw 5 | PROPERTIES 6 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} 7 | ) 8 | 9 | set_bootrom_exe_flags(bootrom_raw) 10 | 11 | set(VARM_SOURCE_FILES 12 | ${CMAKE_CURRENT_LIST_DIR}/varm_apis.c 13 | ${CMAKE_CURRENT_LIST_DIR}/varm_blocks.c 14 | ${CMAKE_CURRENT_LIST_DIR}/varm_boot_path.c 15 | ${CMAKE_CURRENT_LIST_DIR}/varm_flash_boot.c 16 | ${CMAKE_CURRENT_LIST_DIR}/varm_flash_permissions.c 17 | ${CMAKE_CURRENT_LIST_DIR}/varm_nsboot.c 18 | ${CMAKE_CURRENT_LIST_DIR}/varm_checked_flash.c 19 | ${CMAKE_CURRENT_LIST_DIR}/varm_generic_flash.c 20 | ${CMAKE_CURRENT_LIST_DIR}/varm_misc.S 21 | ${CMAKE_CURRENT_LIST_DIR}/varm_otp.c 22 | ${CMAKE_CURRENT_LIST_DIR}/varm_launch_image.c 23 | ${CMAKE_CURRENT_LIST_DIR}/varm_s_from_nsboot_wrappers.c 24 | ) 25 | set(ARM8_SOURCE_FILES 26 | ${CMAKE_CURRENT_LIST_DIR}/arm8_bootrom_rt0.S 27 | ${CMAKE_CURRENT_LIST_DIR}/arm8_misc.S 28 | ${CMAKE_CURRENT_LIST_DIR}/arm8_s_from_ns_wrappers.c 29 | ${CMAKE_CURRENT_LIST_DIR}/arm8_secure_gateways.S 30 | ${CMAKE_CURRENT_LIST_DIR}/arm8_sig.c 31 | ${CMAKE_CURRENT_LIST_DIR}/arm8_nsboot_vm.c 32 | ${CMAKE_CURRENT_LIST_DIR}/arm8_validate_ns_buffer.c 33 | ) 34 | target_sources(bootrom_raw PRIVATE 35 | ${VARM_SOURCE_FILES} 36 | ${ARM8_SOURCE_FILES} 37 | ) 38 | 39 | list(APPEND VARM_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/../../mini_printf/mini_printf.c) 40 | list(APPEND VARM_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/../../../lib/sweet-b/src/sb_sha256.c) 41 | foreach(VARM_SOURCE IN LISTS VARM_SOURCE_FILES) 42 | if (NOT EXISTS ${VARM_SOURCE}) 43 | message(FATAL_ERROR "${VARM_SOURCE} not found") 44 | endif() 45 | set_source_files_properties(${VARM_SOURCE} PROPERTIES COMPILE_FLAGS " -march=armv8-m.base -mcpu=cortex-m23") 46 | endforeach () 47 | 48 | target_include_directories(bootrom_raw PRIVATE 49 | ${CMAKE_CURRENT_LIST_DIR} 50 | ) 51 | 52 | # these are the functions/data we want 16 bit pointers to 53 | # we will generate p16.h with P16_go == 0x00 P16_write_uf2_page_complete == 0x01 etc. 54 | # we will generate p16_asserts.ld which checks they are all included in the binary 55 | # we will use the ordering here along with the nsboot_raw.sym output to replace 56 | # 'movw rx, 0xbb00 + ord` with `movw rx, 16_bit_address_of(ord)` 57 | set(P16_TARGETS 58 | __start_of_secure_xn_plus_5/d1 59 | sonly_text_end/d2 60 | s_native_default_xip_setup/d1 61 | _end_of_core1_boot_path_roundup_32_plus_1/d1 62 | #bootrom_xip_mode_cfgs/d1 63 | otp_ecc_parity_table/d1 64 | sg_api_table/d1 65 | rq_cq_seq_table/d1 66 | sb_fe_one/d1 67 | ) 68 | 69 | set(P16_ASSERTS "") 70 | set(P16_INCLUDE "#pragma once\n") 71 | set(P16_INDEX "0") 72 | foreach(P16_TARGET IN LISTS P16_TARGETS) 73 | string(FIND "${P16_TARGET}" "/" TARGET_LEN) 74 | string(SUBSTRING "${P16_TARGET}" 0 ${TARGET_LEN} P16_TARGET) 75 | target_link_options(bootrom_raw PRIVATE "LINKER:--undefined=${P16_TARGET}") 76 | string(CONCAT P16_ASSERTS "${P16_ASSERTS}" "ASSERT (${P16_TARGET} >= 0, \"${P16_TARGET} does not exist\")\n") 77 | math(EXPR P16_INDEX_HEX "${P16_INDEX}" OUTPUT_FORMAT HEXADECIMAL) 78 | string(CONCAT P16_INCLUDE "${P16_INCLUDE}" "#define P16_${P16_TARGET} ${P16_INDEX_HEX}\n") 79 | math(EXPR P16_INDEX "${P16_INDEX} + 1") 80 | endforeach() 81 | # provide these two from ARM as software_git_revision is needed by nsboot, and 82 | # having them defined is easier for the rom_table 83 | target_link_options(bootrom_raw PRIVATE "LINKER:--undefined=software_git_revision") 84 | target_link_options(bootrom_raw PRIVATE "LINKER:--undefined=partition_table_ptr") 85 | 86 | set(P16_ASSERTS_LINKER_SCRIPT ${CMAKE_BINARY_DIR}/p16_asserts.ld) 87 | set(P16_INCLUDE_FILE ${CMAKE_BINARY_DIR}/p16.h) 88 | file(GENERATE OUTPUT "${P16_ASSERTS_LINKER_SCRIPT}" CONTENT "${P16_ASSERTS}") 89 | file(GENERATE OUTPUT "${P16_INCLUDE_FILE}" CONTENT "${P16_INCLUDE}") 90 | configure_file(bootrom_arm.template.ld ${CMAKE_BINARY_DIR}/bootrom_arm.ld) 91 | 92 | target_compile_definitions(bootrom_raw PRIVATE 93 | BREAKPOINT_AT_DEAD=1 94 | GENERAL_SIZE_HACKS=1 95 | ASM_SIZE_HACKS=1 96 | TAIL_CALL_HACKS=1 97 | PICO_SECURE=1 98 | PICO_NO_FPGA_CHECK=0 99 | ) 100 | 101 | if (BOOTROM_HARDENING) 102 | target_compile_definitions(bootrom_raw PRIVATE BOOTROM_HARDENING=1) 103 | endif() 104 | if (ARM_BOOTROM_ASSERT_DEFAULT_ENABLE) 105 | target_compile_definitions(bootrom_raw PRIVATE BOOTROM_ASSERT_DEFAULT_ENABLE=1) 106 | endif() 107 | 108 | if (NOT DEFINED PRINTF_IN_ARM) 109 | set(PRINTF_IN_ARM PRINTF_IN_NATIVE) 110 | endif() 111 | if (PRINTF_IN_ARM) 112 | target_compile_definitions(bootrom_raw PRIVATE 113 | MINI_PRINTF=1 114 | ) 115 | endif() 116 | if (NO_SWEETB) 117 | target_compile_definitions(bootrom_raw PRIVATE 118 | NO_SWEETB=1 119 | ) 120 | endif() 121 | 122 | set(SB_TEST_FEATURE_CANARIES 1) 123 | 124 | add_subdirectory(../../../lib/sweet-b sweet-b) 125 | target_link_libraries(bootrom_raw PRIVATE 126 | bootrom_common 127 | sweet_b) 128 | target_compile_definitions(bootrom_raw PRIVATE 129 | SB_UNROLL=0 130 | SB_SW_P256_SUPPORT=0 131 | SB_USE_RP2350_SHA256=1 132 | ) 133 | if (HACK_STACK_WORDS) 134 | target_compile_definitions(bootrom_raw PRIVATE 135 | HACK_STACK_WORDS=${HACK_STACK_WORDS} 136 | ) 137 | endif() 138 | 139 | target_link_options(bootrom_raw PRIVATE "LINKER:--script=${CMAKE_BINARY_DIR}/bootrom_arm.ld") 140 | set_target_properties(bootrom_raw PROPERTIES LINK_DEPENDS ${CMAKE_BINARY_DIR}/bootrom_arm.ld) 141 | add_custom_command(TARGET bootrom_raw POST_BUILD 142 | COMMAND ${CMAKE_NM} $ >${CMAKE_BINARY_DIR}/bootrom.sym 143 | ) 144 | 145 | pico_add_extra_outputs(bootrom_raw) 146 | 147 | target_link_options(bootrom_raw PRIVATE -save-temps -fverbose-asm) 148 | 149 | find_package (Python3 REQUIRED COMPONENTS Interpreter) 150 | 151 | set(BOOTROM_ELF "${CMAKE_BINARY_DIR}/bootrom.elf") 152 | add_custom_command(OUTPUT ${BOOTROM_ELF} 153 | DEPENDS $ 154 | COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/inject_p16 $ ${CMAKE_BINARY_DIR}/bootrom.sym ${BOOTROM_ELF} ${P16_TARGETS} 155 | COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/inject_rcp_consts ${BOOTROM_ELF} ${BOOTROM_ELF} 156 | COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/overwrite_prolog ${BOOTROM_ELF} ${CMAKE_BINARY_DIR}/bootrom.sym ${BOOTROM_ELF} 157 | VERBATIM) 158 | 159 | add_custom_target(bootrom ALL DEPENDS ${BOOTROM_ELF}) 160 | add_custom_command(TARGET bootrom POST_BUILD COMMAND ${CMAKE_OBJCOPY} -Obinary ${BOOTROM_ELF} ${CMAKE_BINARY_DIR}/bootrom.bin VERBATIM) 161 | add_custom_command(TARGET bootrom POST_BUILD COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/bin2hex.py -32 ${CMAKE_BINARY_DIR}/bootrom.bin ${CMAKE_BINARY_DIR}/bootrom.h32) 162 | add_custom_command(TARGET bootrom POST_BUILD 163 | COMMAND ${CMAKE_OBJDUMP} -h ${BOOTROM_ELF} > ${CMAKE_BINARY_DIR}/bootrom.dis 164 | COMMAND ${CMAKE_OBJDUMP} -d ${BOOTROM_ELF} >> ${CMAKE_BINARY_DIR}/bootrom.dis 165 | COMMAND ${Python3_EXECUTABLE} ${PICO_TOOLS_DIR}/copro_dis.py ${CMAKE_BINARY_DIR}/bootrom.dis ${CMAKE_BINARY_DIR}/bootrom.dis 166 | VERBATIM 167 | ) 168 | add_custom_command(TARGET bootrom POST_BUILD 169 | COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/bootrom_raw.elf.map ${CMAKE_BINARY_DIR}/bootrom.elf.map 170 | ) 171 | -------------------------------------------------------------------------------- /src/main/arm/arm8_nsboot_vm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "bootrom.h" 8 | #include "nsboot_secure_calls.h" 9 | #include "bootram.h" 10 | #include "varm_boot_path.h" 11 | #include "hardware/regs/intctrl.h" 12 | #include "hardware/structs/nvic.h" 13 | #include "hardware/structs/scb.h" 14 | #include "hardware/structs/accessctrl.h" 15 | #include "hardware/gpio.h" 16 | #include "bootrom_layout.h" 17 | #include "hardware/structs/sau.h" 18 | #include "hardware/sync.h" 19 | 20 | // Careful when adding to this function: anything non-Arm-specific added here must also be added to 21 | // the RISC-V version of this function in riscv_nsboot_vm.c. It's better to hoist things into the 22 | // common preamble in varm_nsboot.c if possible. 23 | void __attribute__((used, noreturn)) s_native_crit_launch_nsboot(void) { 24 | // this is cleared here rather than in common calling code as the varmulet regs are at the bottom of it on RISC-V 25 | #if !HACK_RAM_BOOTROM_AT 26 | s_native_crit_step_safe_mem_erase_by_words(SRAM_BASE, SRAM_END - SRAM_BASE); 27 | #endif 28 | // install the NS vector table 29 | // must be 0x80 - NSBOOT_VTOR_OFFSET aligned (chosen 32 byte boundary based on size of nsboot + riscv) 30 | // note it is a bit of a pain to move, as the usb_irq vector must be shifted down by this amount 31 | // NS VTOR is at the start of the NS code offset by the offset, so we don't have to have NSBOOT itself 0x80 aligned (it just has irq_usbctrl in it); 32 | static_assert(!((NSBOOT_START + NSBOOT_VTOR_OFFSET)&0x7f), ""); // vtor must be 0x80 aligned 33 | scb_ns_hw->vtor = (uintptr_t)(NSBOOT_START + NSBOOT_VTOR_OFFSET); 34 | 35 | // note we remove secure ability to write here, since we don't expect secure code to write to USB RAM which NS can see all of 36 | // BEWARE this makes it IMPOSSIBLE to stack an exception frame in NS mode, but we don't need to do that. 37 | uint32_t pass_core0_sp_nsp_dbg_bits = __get_opaque_value(ACCESSCTRL_PASSWORD_BITS | ACCESSCTRL_UART0_CORE0_BITS | ACCESSCTRL_UART0_SP_BITS | 38 | ACCESSCTRL_UART0_NSP_BITS | ACCESSCTRL_USBCTRL_DBG_BITS); 39 | accessctrl_hw->usbctrl = pass_core0_sp_nsp_dbg_bits; 40 | #if MINI_PRINTF 41 | accessctrl_hw->uart[0] = pass_core0_sp_nsp_dbg_bits; 42 | #endif 43 | static_assert(!(BOOTROM_SG_START & 0x1fu), ""); 44 | static_assert(!(BOOTROM_SG_END & 0x1fu), ""); 45 | // SAU is enabled, and NSC is setup in arm8_bootrom_rt0.S (since we make whole bootrom NS exposing IDAU settings) 46 | // give NS access to all of SRAM and XIP SRAM 47 | #if !ASM_SIZE_HACKS 48 | INIT_SAU_REGION(2, SRAM_BASE, SRAM_END, false, true); 49 | INIT_SAU_REGION(3, XIP_SRAM_BASE, XIP_SRAM_END, false, true); 50 | #else 51 | register uintptr_t rnr = (uintptr_t) &sau_hw->rnr; 52 | pico_default_asm_volatile( 53 | "movs r0, #2\n" 54 | "lsls r1, r0, #28\n" 55 | "ldr r2, = %c[sram_rbar]\n" 56 | "stmia %[p]!, {r0-r2}\n" 57 | "subs %[p], #12\n" 58 | "movs r0, #3\n" 59 | "ldr r1, = %c[xip_sram_base]\n" 60 | "ldr r2, = %c[xip_sram_rbar]\n" 61 | "stmia %[p]!, {r0-r2}\n" 62 | : [p] "+&l" (rnr) 63 | : [sram_rbar] "i" (SRAM_END - 32 + 0 * M33_SAU_RLAR_NSC_BITS + 1 * M33_SAU_RLAR_ENABLE_BITS), 64 | [xip_sram_base] "i" (XIP_SRAM_BASE), 65 | [xip_sram_rbar] "i" (XIP_SRAM_END - 32 + 0 * M33_SAU_RLAR_NSC_BITS + 1 * M33_SAU_RLAR_ENABLE_BITS) 66 | : "r0", "r1", "r2", "cc" 67 | ); 68 | #endif 69 | 70 | // want USB IRQ to be non-secure 71 | nvic_hw->itns[USBCTRL_IRQ/32] = 1u << USBCTRL_IRQ; 72 | 73 | if (nsboot_config->usb_activity_pin >= 0) { 74 | accessctrl_hw->pads_bank0 = pass_core0_sp_nsp_dbg_bits; 75 | accessctrl_hw->io_bank[0] = pass_core0_sp_nsp_dbg_bits; 76 | gpio_assign_to_ns((uint)nsboot_config->usb_activity_pin, true); 77 | } 78 | s_arm8_usb_client_ns_call_thunk(bootram->nsboot.arm.secure_stack, sizeof(bootram->nsboot.arm.secure_stack)); 79 | } 80 | -------------------------------------------------------------------------------- /src/main/arm/arm8_s_from_ns_wrappers.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "bootrom.h" 8 | #include "hardening.h" 9 | #include "varm_checked_flash.h" 10 | 11 | // The functions in this file are ARM8 because they are only used by SG on 12 | // ARM. If the API is available on RISC-V under varmulet, these wrappers are 13 | // bypassed and the varm_ function is called directly 14 | 15 | #include "arm8_validate_ns_buffer.h" 16 | 17 | // There is no functional difference between an SG implementation in main text 18 | // vs in NSC text, since the actual SG instruction is separated from the 19 | // implementation due to shared permission/return code. So, push them to 20 | // whichever section to satisfy code layout constraints. 21 | #define __sg_impl_exempt 22 | #define __sg_impl_nsc __attribute__((noinline, section(".secure_gateways"))) 23 | 24 | int __sg_impl_nsc s_from_ns_arm8_api_checked_flash_op(cflash_flags_t flags, uintptr_t addr, uint32_t size_bytes, uint8_t *buf) { 25 | int rc; 26 | canary_entry(S_FROM_NS_ARM8_API_CHECKED_FLASH_OP); 27 | hx_bool buffer_ok = hx_bool_invalid(); 28 | // Call from NonSecure: effective security level of the flash access must also be NS. This is 29 | // checked against the permissions in the resident partition table. 30 | uint seclevel = (flags.flags & CFLASH_SECLEVEL_BITS) >> CFLASH_SECLEVEL_LSB; 31 | uint op = (flags.flags & CFLASH_OP_BITS) >> CFLASH_OP_LSB; 32 | if (seclevel != CFLASH_SECLEVEL_VALUE_NONSECURE) { 33 | rc = BOOTROM_ERROR_INVALID_ARG; 34 | goto checked_flash_op_done; 35 | } 36 | if ((op == CFLASH_OP_VALUE_ERASE) != (buf == NULL)) { 37 | rc = BOOTROM_ERROR_INVALID_ARG; 38 | goto checked_flash_op_done; 39 | } 40 | // NS flash operations with RAM buffers must point to NS-accessible RAM 41 | hx_bool write = make_hx_bool(op != CFLASH_OP_VALUE_READ); 42 | if (buf != NULL) { 43 | buf = s_native_api_validate_ns_buffer(buf, size_bytes, write, &buffer_ok); 44 | if (hx_is_false(buffer_ok)) { 45 | rc = (int)buf; // will be BOOTROM_ERROR_INVALID_ADDRESS; 46 | goto checked_flash_op_done; 47 | } 48 | } 49 | hx_assert_true(buffer_ok); 50 | rc = s_varm_api_checked_flash_op(flags, addr, size_bytes, buf); 51 | checked_flash_op_done: 52 | canary_exit_return(S_FROM_NS_ARM8_API_CHECKED_FLASH_OP, rc); 53 | } 54 | 55 | int __sg_impl_nsc s_from_ns_arm8_api_flash_runtime_to_storage_addr(uintptr_t addr) { 56 | canary_entry(S_FROM_NS_ARM8_API_FLASH_RUNTIME_TO_STORAGE_ADDR); 57 | // Only allow SAU-NonSecure addresses to be translated via this gateway 58 | hx_bool addr_ok = hx_bool_invalid(); 59 | addr = (uintptr_t)s_native_api_validate_ns_buffer((const void *) addr, 1, hx_false(), &addr_ok); 60 | if (hx_is_false(addr_ok)) { 61 | // already the case 62 | // addr = (uintptr_t)BOOTROM_ERROR_INVALID_ADDRESS; 63 | goto runtime_to_storage_addr_done; 64 | } 65 | hx_assert_true(addr_ok); 66 | addr = s_varm_api_flash_runtime_to_storage_addr(addr); 67 | if ((int32_t)addr < 0) { 68 | addr = (uintptr_t)BOOTROM_ERROR_INVALID_ADDRESS; 69 | } 70 | runtime_to_storage_addr_done: 71 | canary_exit_return(S_FROM_NS_ARM8_API_FLASH_RUNTIME_TO_STORAGE_ADDR, (int)addr); 72 | } 73 | -------------------------------------------------------------------------------- /src/main/arm/arm8_secure_gateways.S: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "bootrom.h" 8 | #include "bootram.h" 9 | #include "pico/bootrom_constants.h" 10 | 11 | .cpu cortex-m33 12 | .thumb 13 | .section .secure_gateways.first, "ax" 14 | .syntax unified 15 | 16 | // returns a 32 bit value (note all args must be in registers) 17 | .macro sg_uint32_func name 18 | .global sg_\name 19 | sg_\name: 20 | sg 21 | #if !FEATURE_CANARIES 22 | push {r1, lr} // save r1 for stack alignment 23 | #else 24 | rcp_canary_get_nodelay ip, CTAG_SG_CALL 25 | push {ip, lr} 26 | #endif 27 | adr lr, return_to_ns_preserve_r0 + 1 28 | b.w s_from_ns_\name 29 | .endm 30 | 31 | .set num_of_sg_table_funcs, 0 32 | .macro sg_table_func name idx prefix 33 | // The order is important here because the entry PC is used to get the 34 | // table index 35 | .if num_of_sg_table_funcs != \idx 36 | .error "Wrong SG table func order, compare the bootrom_constants.h BOOTROM_NS_API constants to the asm table" 37 | .endif 38 | 39 | // Insert function table entry 40 | .section .rodata.keep, "a" 41 | .if num_of_sg_table_funcs == 0 42 | .global sg_api_table 43 | sg_api_table: 44 | .endif 45 | sg_functable_\name: 46 | #if BOOTROM_32BIT_FUNC_POINTERS 47 | .word s_from_ns_\prefix\()_api_\name + 1 48 | #else 49 | .hword s_from_ns_\prefix\()_api_\name + 1 50 | #endif 51 | .if num_of_sg_table_funcs == BOOTROM_NS_API_COUNT - 1 52 | .global sg_api_table_end 53 | sg_api_table_end: 54 | .endif 55 | 56 | // Insert gateway with same index 57 | .section .secure_gateways.first, "ax" 58 | // 8 bytes per gateway: 32-bit SG, 16-bit mov, 16-bit branch. Note the mov 59 | // points ip at the *next slot*, not the one we entered. 60 | .if num_of_sg_table_funcs == 0 61 | .p2align 2 62 | .endif 63 | .global sg_api_\name 64 | sg_api_\name: 65 | .if num_of_sg_table_funcs == 1 66 | .global sg_second_table_entry_point 67 | sg_second_table_entry_point: 68 | .endif 69 | sg 70 | mov ip, pc 71 | b.n sg_table_entry_longbranch 72 | 73 | .set num_of_sg_table_funcs, num_of_sg_table_funcs + 1 74 | .endm 75 | 76 | // catch all entry point for nsboot - it is permanently disabled (until bootram 77 | // reset, which implies proc reset) if nsboot path is not taken 78 | sg_uint32_func nsboot_service_call 79 | 80 | // The BOOTROM_NS_API_xxx numbers here are defined in bootrom_constants.h 81 | #define SG_TABLE_FUNC(name, prefix) sg_table_func name BOOTROM_NS_API_ ## name prefix 82 | 83 | // The order here must match the SG_API_xxx numbers defined in bootrom.h 84 | // (and we check this and .error on mismatch) 85 | // NOTE: these functions must all return a value, or secure r0 is exposed 86 | SG_TABLE_FUNC(get_sys_info, varm) 87 | SG_TABLE_FUNC(checked_flash_op, arm8) 88 | SG_TABLE_FUNC(flash_runtime_to_storage_addr, arm8) 89 | SG_TABLE_FUNC(get_partition_table_info, varm) 90 | SG_TABLE_FUNC(secure_call, arm8) 91 | SG_TABLE_FUNC(otp_access, varm) 92 | SG_TABLE_FUNC(reboot, varm) 93 | SG_TABLE_FUNC(get_b_partition, varm) 94 | 95 | sg_table_entry_longbranch: 96 | // b.w sg_table_entry 97 | // fall-through: 98 | 99 | // ip has table index 100 | .global sg_table_entry 101 | sg_table_entry: 102 | #if FEATURE_CANARIES 103 | rcp_count_set_nodelay STEPTAG_SG_CALL 104 | #endif 105 | push {r1, lr} // save r1 for stack alignment (and for canary space) 106 | // The tabled SGs are 8 bytes apart, in SG_API index order, so we can 107 | // calculate the API index from the PC which was stashed in IP. Note the 108 | // captured PC actually points at slot + 1 (because reading PC does not 109 | // point to the current instruction on Arm) so we take the difference 110 | // starting at the *second* slot to cancel out the off-by-one. 111 | adr lr, sg_second_table_entry_point 112 | subs ip, lr 113 | #if FEATURE_CANARIES 114 | rcp_canary_get_nodelay lr, CTAG_SG_CALL 115 | str lr, [sp, #0] 116 | #endif 117 | lsrs ip, #3 118 | cmp ip, #BOOTROM_NS_API_COUNT 119 | bhs 1f // bhs is unsigned-greater-or-equal 120 | 121 | // permission check 122 | ldr lr, =BOOTRAM_BASE + BOOTRAM_NS_API_PERMISSIONS_OFFSET 123 | ldrb lr, [lr, ip] 124 | orrs lr, lr, lr, lsl #16 125 | // lr = true ? 0xa500a5 : 0xc300c3 126 | tst lr, #2 127 | bne 2f 128 | lsls lr, lr, #8 129 | // lr is 0xa500a500 if permission was 0xa5 130 | rcp_btrue_nodelay lr 131 | 132 | ldr lr, =P16(sg_api_table) 133 | #if BOOTROM_HARDENING 134 | cmp ip, #BOOTROM_NS_API_COUNT 135 | bhs 1f // bhs is unsigned-greater-or-equal 136 | #endif 137 | #if BOOTROM_32BIT_FUNC_POINTERS 138 | ldr ip, [lr, ip, lsl #2] 139 | #else 140 | ldrh ip, [lr, ip, lsl #1] 141 | #endif 142 | adr lr, return_to_ns_preserve_r0 + 1 143 | #if FEATURE_CANARIES 144 | rcp_count_check_nodelay STEPTAG_SG_CALL 145 | #endif 146 | bx ip 147 | 1: 148 | movs r0, #-BOOTROM_ERROR_INVALID_ARG 149 | b 3f 150 | 2: 151 | movs r0, #-BOOTROM_ERROR_NOT_PERMITTED 152 | 3: 153 | negs r0, r0 154 | // fall thru since clear4 no longer used 155 | // b return_to_ns_preserve_r0 156 | //clear4: 157 | // mov r0, r4 158 | .global return_to_ns_preserve_r0 159 | .thumb_func 160 | return_to_ns_preserve_r0: 161 | // we clear the non-callee saved regs on exit from secure code (other than r0 which is the return code) 162 | pop {r1, lr} 163 | mov r2, lr 164 | mov r3, lr 165 | mov ip, lr 166 | msr APSR_nzcvqg, lr 167 | #if FEATURE_CANARIES 168 | rcp_canary_check_nodelay r1, CTAG_SG_CALL 169 | mov r1, lr 170 | #endif 171 | bxns lr 172 | 173 | // Assert that the bootrom.h constant matches the number of table entries, as 174 | // it's used for bounds checking in arm8_bootrom_rt0.S 175 | .if num_of_sg_table_funcs != BOOTROM_NS_API_COUNT 176 | .error "Number of SG table funcs does not match BOOTROM_NS_API_COUNT from bootrom_constants.h" 177 | .endif 178 | -------------------------------------------------------------------------------- /src/main/arm/arm8_sig.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "bootrom.h" 8 | #include "arm8_sig.h" 9 | #include "sb_sw_lib.h" 10 | 11 | hx_xbool s_arm8_verify_signature_secp256k1( 12 | uint32_t context_buffer[SIG_CONTEXT_SIZE/4], 13 | const sb_sw_public_t public_key[1], 14 | const sb_sw_message_digest_t digest[1], 15 | const sb_sw_signature_t signature[1]) { 16 | canary_entry(S_ARM8_VERIFY_SIGNATURE_SECP256K1); 17 | // note we pass a buffer, as this is too big to go on the stack 18 | // note: could be < but may as well be exact, so we don't waste space 19 | static_assert(sizeof(sb_sw_context_t) == SIG_CONTEXT_SIZE, ""); 20 | sb_sw_context_t *context = (sb_sw_context_t *)context_buffer; 21 | sb_verify_result_t res = sb_sw_verify_signature(context, 22 | signature, 23 | public_key, 24 | digest, 25 | NULL, 26 | SB_SW_CURVE_SECP256K1); 27 | #if !BOOTROM_HARDENING 28 | // sweet-b by default has zero for SUCCESS 29 | res = !res; 30 | #endif 31 | canary_exit_return(S_ARM8_VERIFY_SIGNATURE_SECP256K1, res); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/arm/arm8_sig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pico.h" 10 | #include "hardware/structs/sha256.h" 11 | #include "hardening.h" 12 | #include "sb_types.h" 13 | 14 | typedef sb_double_t sb_sw_public_t; 15 | typedef sb_double_t sb_sw_signature_t; 16 | typedef sb_single_t sb_sw_message_digest_t; 17 | 18 | #define SIG_CONTEXT_SIZE 0x200 19 | static_assert(0 == (SIG_CONTEXT_SIZE & 3), ""); 20 | // note this returns an XORed hx_bool with HX_XOR_SIG_VERIFIED 21 | hx_xbool s_arm8_verify_signature_secp256k1(uint32_t context_buffer[SIG_CONTEXT_SIZE/4], 22 | const sb_sw_public_t public_key[1], 23 | const sb_sw_message_digest_t digest[1], 24 | const sb_sw_signature_t signature[1]); 25 | 26 | -------------------------------------------------------------------------------- /src/main/arm/arm8_validate_ns_buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "bootrom.h" 10 | 11 | // Validate a NonSecure buffer. 12 | // 13 | // Entire buffer must fit in range XIP_BASE -> SRAM_END, and must be 14 | // accessible from NS caller according to SAU + NS MPU (privileged or not 15 | // based on current processor IPSR and NS CONTROL flag). We also allow 16 | // buffers in USB RAM if this is granted to NS via ACCESSCTRL -- note USB RAM 17 | // is IDAU-Exempt so will fail tt* checks. 18 | // 19 | // Note this is a arm6-to-native call so that it can be stubbed on RISC-V, 20 | // avoiding execution of Armv8-M instructions under emulation. This allows 21 | // nsboot-to-secure shims (accessed via nsboot's service SG) to be shared. 22 | 23 | void *varm_callable(s_native_api_validate_ns_buffer)(const void *addr, uint32_t size, hx_bool write, hx_bool *ok); 24 | -------------------------------------------------------------------------------- /src/main/arm/bootrom_arm.template.ld: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ROM(rx) : ORIGIN = ${BOOTROM_ARM_START}, LENGTH = ${BOOTROM_ARM_SIZE} 3 | ROMSG(rx): ORIGIN = ${BOOTROM_SG_START}, LENGTH = ${BOOTROM_SG_SIZE} 4 | SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 520K 5 | USBRAM(rw) : ORIGIN = 0x50100000 + 0x1000 - ${CORE0_BOOT_USBRAM_WORKSPACE_SIZE}, LENGTH = ${CORE0_BOOT_USBRAM_WORKSPACE_SIZE} 6 | BOOTRAM(rw) : ORIGIN = ${BOOTRAM_ARM_STATIC_DATA_START}, LENGTH = ${BOOTRAM_ARM_STATIC_DATA_SIZE} 7 | } 8 | 9 | SECTIONS { 10 | . = ORIGIN(ROM); 11 | ENTRY(s_arm8_entry_point) 12 | .text : { 13 | exempt_start = .; 14 | KEEP(*(.vectors)) 15 | /* functions with hint prefixes need to be glued together (hint prefix and body) because they are in C code */ 16 | *(.text.s_varm_flash_put_get_nodata) /* falls thru */ 17 | KEEP(*(.text.s_native_crit_flash_put_get)) 18 | 19 | *(.text.varm_to_s_native_crit_launch_nsboot) 20 | KEEP(*(.text.s_native_crit_launch_nsboot)) 21 | KEEP(*(.text.varm_to_s_native_crit_init_default_xip_setup_and_enter_image_thunk)) 22 | KEEP(*(.text.s_native_crit_init_default_xip_setup_and_enter_image_thunk)) 23 | *(.text.varm_to_s_native_api_validate_ns_buffer) 24 | KEEP(*(.text.s_native_api_validate_ns_buffer)) 25 | *(.text.s_varm_api_flash_enter_cmd_xip) /* falls thru */ 26 | *(.text.s_varm_api_crit_flash_select_xip_read_mode) /* falls thru */ 27 | *(.text.s_varm_crit_redo_last_reboot) /* needs to be near s_varm_api_reboot */ 28 | KEEP(*(.text.s_from_nsboot_varm_reboot)) /* needs to be near s_varm_api_reboot */ 29 | KEEP(*(.text.s_varm_api_reboot)) /* falls thru */ 30 | *(.text.s_varm_hx_reboot) 31 | KEEP(*(.text.s_varm_flash_sector_erase)) /* falls thru */ 32 | KEEP(*(.text.s_varm_flash_erase_or_program)) 33 | KEEP(*(.text.s_from_nsboot_varm_flash_sector_erase)) /* falls thru */ 34 | KEEP(*(.text.s_varm_api_checked_flash_op)) 35 | *(.text.s_varm_api_hx_otp_access) 36 | *(.text.s_varm_api_otp_access) /* does b.n to previous func */ 37 | 38 | /* Mark APIs as KEEP because they may only be referred to by the ROM 39 | table, and those references are not visible to the Arm link step 40 | (since it's linked in the RISC-V binary using Arm post-link 41 | exports). Note __attribute__((used)) is only for the compiler. */ 42 | KEEP(*(.text.s_varm_api_* .text.s_arm8_api_*)) 43 | 44 | /* Main Secure/Exempt text entries */ 45 | *(SORT_BY_ALIGNMENT(.text.s_*)) 46 | 47 | /* ideally these should be inline (and they do now seem to be, so commented out): */ 48 | /**(SORT_BY_ALIGNMENT(.text.rcp_*))*/ 49 | 50 | /* These debug routines should only exist in development builds. We 51 | will know if we included them by accident, due to code size. */ 52 | *(.text.mini_printf*) 53 | *(.text.mini_puts*) 54 | *(.text.dump_*) 55 | *(.text.print_*) 56 | *(.text.bootrom_assertion_failure) 57 | *(.text.uart_putc_raw) 58 | *(.text.printf) 59 | *(.text.debug_real_addr) 60 | *(.text.uart_putc) 61 | *(.text.puts) 62 | 63 | exempt_end = .; 64 | 65 | /* Exempt is followed by Secure-only text, which is used during the 66 | boot path but not called back into. (The sandwich goes Exempt -> 67 | Secure -> NonSecure -> NSC, with Exempt/Secure boundary fixed at 68 | 0x4300, Secure/NonSecure boundary defined by the bootrom SAU 69 | region, and NonSecure/NSC fixed at 0x7e00 by the IDAU.) */ 70 | sonly_text_start = .; 71 | *(.text.sonly_varm_make_hx_bool_impl) 72 | *(.text.sonly_varm_crit_core0_boot_path_prolog) 73 | *(SORT_BY_ALIGNMENT(.text.sb_*)) 74 | *(SORT_BY_ALIGNMENT(.text.sonly_*)) 75 | /* Must align the end symbol because the bootrom NS SAU region must 76 | start strictly after the end of Secure-executable text. There's no 77 | need to align the start. */ 78 | . = ALIGN(32); 79 | sonly_text_end = .; 80 | 81 | ns_start = .; 82 | ns_text_start = .; 83 | /* (Note this should be empty, so the ALIGN costs nothing -- 84 | however __start_of_secure_xn does need to be aligned) */ 85 | *(SORT_BY_ALIGNMENT(.text.ns_*)) 86 | . = ALIGN(32); 87 | ns_text_end = .; 88 | 89 | /* Text not marked by one of the known prefixes */ 90 | unknown_text_start = .; 91 | /* (Note this should be empty, so the ALIGN costs nothing -- 92 | however __start_of_secure_xn does need to be aligned) */ 93 | *(SORT_BY_ALIGNMENT(.text*)) 94 | . = ALIGN(32); 95 | unknown_text_end = .; 96 | 97 | PROVIDE(__start_of_secure_xn = .); 98 | KEEP(*(.rodata.keep*)) 99 | *(.rodata*) 100 | this_is_the_end_my_only_friend_the_end = .; 101 | . = ${BOOTROM_ARM_SIZE}; /* ALIGN(LENGTH(ROM)); note hack to BOOTRAM_ARM_SIZE to allow RAM builds */ 102 | } >ROM =0x00be 103 | ASSERT(ns_text_end == ns_text_start, "seem to have some ns text - possibly not bad, but havent had any yet") 104 | 105 | ASSERT(exempt_end <= ${BOOTROM_ARM_START} + ${BOOTROM_IDAU_EXEMPT_END}, "Exempt text beyond end of IDAU region, try marking more stuff as secure-only") 106 | 107 | ASSERT(unknown_text_end == unknown_text_start, "Found some text neither s nor ns. This will crash if executed from Secure code, as it ends up past the SAU NS base. Mark it explicitly as the correct section. If it's inline_ then force its inlining. To find out what code was caught, search for unknown_text_start in the .map file.") 108 | 109 | ASSERT((sonly_text_end & 0x1f) == 0, "End of secure-only text must be 32-byte-aligned for SAU") 110 | ASSERT((__start_of_secure_xn & 0x1f) == 0, "Start of Secure XN must be 32-byte-aligned for MPU") 111 | 112 | ASSERT(s_native_crit_flash_put_get == varm_to_s_native_crit_flash_put_get + 2, "varm_to_s_native failure 1") 113 | ASSERT(s_native_crit_launch_nsboot == varm_to_s_native_crit_launch_nsboot + 4, "varm_to_s_native failure 2") 114 | ASSERT(s_native_api_validate_ns_buffer == varm_to_s_native_api_validate_ns_buffer + 4, "varm_to_s_native failure 3") 115 | ASSERT(s_native_crit_init_default_xip_setup_and_enter_image_thunk == varm_to_s_native_crit_init_default_xip_setup_and_enter_image_thunk + 4, "varm_to_s_native failure 4") 116 | ASSERT(s_varm_api_crit_flash_select_xip_read_mode == s_varm_api_flash_enter_cmd_xip + 4, "fall thru failure 1") 117 | ASSERT(s_varm_hx_reboot == s_varm_api_reboot_end, "fall thru failure 2") 118 | ASSERT(varm_to_s_native_crit_flash_put_get == s_varm_flash_put_get_nodata + 2, "fall thru failure 4") 119 | ASSERT(s_varm_api_checked_flash_op == s_from_nsboot_varm_flash_sector_erase_end, "fall thru failure 5") 120 | ASSERT(sonly_varm_make_hx_bool_impl != sonly_varm_make_hx_bool_impl + 16, "prolog overlap error") 121 | ASSERT(boot_path_rnd_to_sha_end == boot_path_rnd_to_sha_start + 12, "boot path rnd to sha size != 12") 122 | 123 | .secure_gateways : { 124 | ASSERT((. & 31) == 0, "ERRROR secure_gateways not 32 byte aligned"); 125 | PROVIDE(__start_of_secure_gateways = .); 126 | KEEP(*(.secure_gateways.first)) 127 | *(.secure_gateways .secure_gateways.*) 128 | KEEP(*(.sg_fillers)) 129 | . = ORIGIN(ROMSG) + LENGTH(ROMSG); 130 | PROVIDE(__end_of_secure_gateways = .); 131 | } >ROMSG =0x0000 132 | 133 | .data : { 134 | *(.data*) 135 | } >USBRAM 136 | 137 | .bss : { 138 | *(.bss*) 139 | } >USBRAM 140 | 141 | .fake (NOLOAD) : { 142 | PROVIDE(core0_boot_usbram_workspace = .); 143 | } >USBRAM 144 | .allowed_bss (COPY) : { 145 | *(.allowed_bss*) 146 | } >BOOTRAM 147 | 148 | _end_of_core1_boot_path_roundup_32_plus_1 = ((_end_of_core1_boot_path + 31) & ~31) + 1; 149 | 150 | ASSERT(SIZEOF(.data) == 0, 151 | "ERROR: do not use static memory in bootrom! (.data)") 152 | ASSERT(SIZEOF(.bss) == 0, 153 | "ERROR: do not use BSS memory in bootrom! (.bss)") 154 | ASSERT(SIZEOF(.allowed_bss) == ${BOOTRAM_ARM_STATIC_DATA_SIZE}, "allowed_bss size mismatch") 155 | 156 | INCLUDE "${P16_ASSERTS_LINKER_SCRIPT}" 157 | 158 | /* Leave room above the stack for stage 2 load, so that stage 2 159 | can image SRAM from its beginning */ 160 | _stacktop = ORIGIN(SRAM) + LENGTH(SRAM) - 256; 161 | otp_data = 0x40130000; 162 | otp_data_raw = 0x40134000; 163 | otp_data_guarded = 0x40138000; 164 | otp_data_raw_guarded = 0x4013c000; 165 | sha256_hw_inst = 0x400f8000; 166 | software_git_revision = ${BOOTROM_RISCV_END} - ${BOOTROM_RISCV_END_SIZE}; 167 | sb_fe_one = SB_CURVE_SECP256K1_P+0x22*4; 168 | __start_of_secure_xn_plus_5 = __start_of_secure_xn + 5; 169 | /* alias, as there is no difference in implementation */ 170 | s_from_ns_varm_api_get_b_partition = s_varm_api_crit_get_b_partition; 171 | } 172 | -------------------------------------------------------------------------------- /src/main/arm/varm_checked_flash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "bootrom.h" 10 | #include "varm_flash_permissions.h" 11 | 12 | static_assert(CFLASH_FLAGS_BITS == (CFLASH_ASPACE_BITS | CFLASH_SECLEVEL_BITS | CFLASH_OP_BITS), ""); 13 | 14 | // Apply the address translation currently specified in QMI_ATRANSx ("rolling window" hardware 15 | // translation). Need to take care using this on the boot path, as the QMI may not yet have been 16 | // set up, but this should be suitable for translating system bus addresses into flash storage 17 | // addresses in user callbacks. Returns all-ones for an invalid address, which is also an invalid 18 | // flash storage address, so invalidity is propagated. 19 | storage_addr_t s_varm_api_flash_runtime_to_storage_addr(uintptr_t flash_runtime_addr); 20 | 21 | // Perform the specified erase/program/read operation, translating addresses according to 22 | // QMI_ATRANSx if necessary, and checking flash permissions based on the resident partition table 23 | // and the specified effective security level. `addr` may be either a flash runtime address or a 24 | // flash storage address, depending on the ASPACE given in `flags`. 25 | // 26 | // NOTE: This function does not validate the buffer for NS access. This must be validated before 27 | // calling if the caller is reachable from a Secure Gateway. 28 | int s_varm_api_checked_flash_op(cflash_flags_t flags, uintptr_t addr, uint32_t size_bytes, uint8_t *buf); 29 | -------------------------------------------------------------------------------- /src/main/arm/varm_flash_permissions.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "varm_flash_permissions.h" 8 | #include "mini_printf.h" 9 | #include "varm_boot_path.h" 10 | 11 | // Walk the resident partition table and return the index of the first partition which matches a 12 | // query address. Return PARTITION_TABLE_MAX_PARTITIONS if no matching partition was found, and 13 | // return -1 if the query address is invalid or if the table is not yet loaded. 14 | int __noinline s_varm_flashperm_get_partition_num_from_storage_address(storage_addr_t addr) { 15 | canary_entry(S_VARM_FLASHPERM_GET_PARTITION_NUM_FROM_STORAGE_ADDRESS); 16 | // We are dealing with flash storage addresses, i.e., the SPI wire-level address plus the XIP 17 | // hardware base address for that chip select. So, subtract the XIP hardware base address. 18 | addr -= XIP_BASE; 19 | int rc; 20 | 21 | // If the address is outside the bounds of known flash hardware (as configured by 22 | // OTP_DATA_FLASH_DEVINFO) then don't even bother to walk the table. The default if the 23 | // FLASH_DEVINFO_ENABLE flag is not set is 16 MiB for the first chip select, and nothing on the 24 | // second chip select. 25 | if (!s_varm_flash_check_in_bounds_single_addr(addr)) { 26 | rc = -1; 27 | goto from_storage_address_done; 28 | } 29 | 30 | // If we don't know anything then we can't give permission for anything 31 | if (!inline_s_is_resident_partition_table_loaded()) { 32 | rc = -1; 33 | goto from_storage_address_done; 34 | } 35 | 36 | // Read the partition count only once, and order the partition reads against it, to avoid 37 | // reading partially populated entries when the table is being appended. Assume that, on 38 | // append, entries are populated before incrementing the count. (A compiler memory barrier is 39 | // sufficient here because bootram should be strongly-ordered) 40 | const resident_partition_table_t *table = &bootram->always.partition_table; 41 | uint8_t partition_count = table->permission_partition_count; 42 | __compiler_memory_barrier(); 43 | 44 | uint32_t addr_sector_num = (addr & FLASH_SECTOR_NUM_MASK) >> FLASH_SECTOR_SHIFT; 45 | 46 | bootrom_assert(PARTITION_TABLE, partition_count <= PARTITION_TABLE_MAX_PARTITIONS); 47 | for (uint i = 0; i < partition_count; ++i) { 48 | uint start_sector_num = ( 49 | table->partitions[i].permissions_and_location & PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_BITS 50 | ) >> PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_LSB; 51 | uint end_sector_num = ( 52 | table->partitions[i].permissions_and_location & PICOBIN_PARTITION_LOCATION_LAST_SECTOR_BITS 53 | ) >> PICOBIN_PARTITION_LOCATION_LAST_SECTOR_LSB; 54 | 55 | // Return first partition which matches the test address 56 | if (addr_sector_num >= start_sector_num && addr_sector_num <= end_sector_num) { 57 | rc = (int)i; 58 | goto from_storage_address_done; 59 | } 60 | } 61 | 62 | // Loop fell through: no partition was found, so apply the default permissions 63 | rc = PARTITION_TABLE_NO_PARTITION_INDEX; 64 | from_storage_address_done: 65 | canary_exit_return(S_VARM_FLASHPERM_GET_PARTITION_NUM_FROM_STORAGE_ADDRESS, rc); 66 | } 67 | -------------------------------------------------------------------------------- /src/main/arm/varm_flash_permissions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "bootrom.h" 10 | #include "bootram.h" 11 | #include "native_generic_flash.h" 12 | #include "boot/picobin.h" 13 | #include "hardware/regs/addressmap.h" 14 | #include "hardware/sync.h" 15 | 16 | // Helper functions for checking the permissions of flash storage addresses, based on the resident 17 | // partition table. 18 | // 19 | // Note: A flash storage address is the QSPI bus address, plus the XIP hardware base address at 20 | // which that QSPI device is first mapped in memory. Equivalently, it is the result of applying the 21 | // XIP address translation configured by the QMI_ATRANSx registers to a system bus address. When 22 | // the XIP address translation is disabled (or rather, configured to an identity mapping), the 23 | // flash storage address is the same as the system bus address that a processor will perform a 24 | // load/store on. 25 | // 26 | // This is different from the raw QSPI addresses ("flash offsets") used by the low-level flash 27 | // programming code. A flash storage address is converted to a flash offset by subtracting 28 | // XIP_BASE. For example a flash offset of 0x100, 256 bytes into the first QSPI device, has a flash 29 | // storage address of 0x10000100. A flash offset of 0x01001234, which is 0x1234 bytes into 30 | // the *second* chip select, has a flash storage address of 0x11001234. 31 | // 32 | // A program linked to run at address 0x10000000 may not actually be stored at that address in 33 | // flash. For example, when A/B images are in play (for phased upgrades), there are two possible 34 | // flash locations where the first sector of the currently running program may actually be stored, 35 | // and neither of them will be at flash storage address 0x10000000, since in this example there is 36 | // necessarily a partition table (to identify the two A/B image partitions) which occupies the 37 | // first sector(s) of flash. In this case, *you must translate your flash addresses* using 38 | // s_varm_flash_translate_physaddr_to_storage_addr(). 39 | 40 | typedef uint32_t flash_permission_mask_t; 41 | 42 | typedef uintptr_t storage_addr_t; 43 | 44 | static inline flash_offset_t s_varm_flash_translate_storage_addr_to_offset(storage_addr_t addr) { 45 | return (flash_offset_t)(addr - XIP_BASE); 46 | } 47 | 48 | // Walk the resident partition table and return the index of the first partition which matches a 49 | // query address. Return PARTITION_TABLE_MAX_PARTITIONS if no matching partition was found, and 50 | // return -1 if the query address is invalid. 51 | int s_varm_flashperm_get_partition_num_from_storage_address(storage_addr_t addr); 52 | 53 | // Default partition entry when nothing was found, defines the permissions of unpartitioned space 54 | static __force_inline resident_partition_t s_varm_flashperm_get_default_partition(void) { 55 | // we expect it to have been loaded when this is called 56 | bootrom_assert(MISC, bootram->always.partition_table.loaded); 57 | const resident_partition_table_t *pt = &bootram->always.partition_table; 58 | return (resident_partition_t) { 59 | // Default partition is always max possible size -- don't worry about chip select address 60 | // hole as that is checked separately when validating address spans 61 | .permissions_and_location = ( 62 | pt->unpartitioned_space_permissions_and_flags & PICOBIN_PARTITION_PERMISSIONS_BITS 63 | ) | PICOBIN_PARTITION_LOCATION_LAST_SECTOR_BITS, 64 | .permissions_and_flags = pt->unpartitioned_space_permissions_and_flags 65 | }; 66 | } 67 | 68 | //// Walk the resident partition table and return, by value, the first partition which matches a query 69 | //// address. Return an all-zeroes entry if no matching partition was found. 70 | //static __force_inline resident_partition_t s_varm_flashperm_get_partition_from_storage_address(storage_addr_t addr) { 71 | // int partition_num = s_varm_flashperm_get_partition_num_from_storage_address(addr); 72 | // if (partition_num < 0) { 73 | // return (resident_partition_t) { 74 | // .location_and_permissions = 0, 75 | // .flags_and_permissions = 0 76 | // }; 77 | // } else if (partition_num == PARTITION_TABLE_NO_PARTITION_INDEX) { 78 | // return s_varm_flashperm_get_default_partition(); 79 | // } else { 80 | // return bootram->always.partition_table.partitions[partition_num]; 81 | // } 82 | //} 83 | 84 | // Check that a given partition has ALL the specified permission flags 85 | static __force_inline bool s_varm_flashperm_partition_has_permissions(resident_partition_t partition, flash_permission_mask_t required_permissions) { 86 | // The permissions are stored redundantly in the two halves of the entry, and when a bit 87 | // differs, we take the less-permissive value (bitwise AND) 88 | uint32_t actual_permissions = ( 89 | partition.permissions_and_location & 90 | partition.permissions_and_flags & 91 | PICOBIN_PARTITION_PERMISSIONS_BITS 92 | ); 93 | bool permissions_lacking = required_permissions & ~actual_permissions; 94 | return !permissions_lacking; 95 | } 96 | 97 | #if BOOTROM_ASSERT_FLASH_PERMISSIONS_ENABLE 98 | #include "varm_boot_path.h" 99 | #endif 100 | 101 | // Check that a given partition number in the resident table has ALL the specified permission flags 102 | static __force_inline bool s_varm_flashperm_partition_num_has_permissions(int partition_num, flash_permission_mask_t required_permissions) { 103 | if (partition_num == PARTITION_TABLE_NO_PARTITION_INDEX) { 104 | return s_varm_flashperm_partition_has_permissions(s_varm_flashperm_get_default_partition(), required_permissions); 105 | } else { 106 | #if BOOTROM_ASSERT_FLASH_PERMISSIONS_ENABLE // because of need for include 107 | bootrom_assert(FLASH_PERMISSIONS, inline_s_is_resident_partition_table_loaded()); 108 | #endif 109 | if (partition_num >= 0 && partition_num < bootram->always.partition_table.permission_partition_count) { 110 | return s_varm_flashperm_partition_has_permissions(bootram->always.partition_table.partitions[partition_num], 111 | required_permissions); 112 | } else { 113 | return false; 114 | } 115 | } 116 | } 117 | 118 | //// Check that a given single flash storage address has ALL the specified permission flags 119 | //static __force_inline bool s_varm_flashperm_storage_addr_has_permissions(uint32_t addr, flash_permission_mask_t required_permissions) { 120 | // return s_varm_flashperm_partition_num_has_permissions( 121 | // s_varm_flashperm_get_partition_num_from_storage_address(addr), 122 | // required_permissions 123 | // ); 124 | //} 125 | 126 | // Check that a given span of flash storage addresses has ALL the specified permission flags, and 127 | // does not start/end in different partitions. 128 | static __force_inline bool s_varm_flashperm_storage_addr_span_has_permissions(storage_addr_t start_addr, uint32_t size, flash_permission_mask_t required_permissions) { 129 | // The span itself must be valid, e.g. no crossing of an address hole between two flash devices. 130 | if (!s_varm_crit_flash_check_in_bounds_addr_span(start_addr - XIP_BASE, size)) { 131 | return false; 132 | } 133 | // Assumption: if a span's endpoints are in the same partition, all addresses in between have at 134 | // least the permissions of that partition. This assumption is violated if you embed a 135 | // Secure-RW partitition inside of a NonSecure-RW partition (for example), so don't do that 136 | int pnum_at_start = s_varm_flashperm_get_partition_num_from_storage_address(start_addr); 137 | int pnum_at_end = s_varm_flashperm_get_partition_num_from_storage_address(start_addr + size - 1); 138 | 139 | // Bridging between two different partitions: not valid 140 | if (pnum_at_start != pnum_at_end) { 141 | return false; 142 | } 143 | 144 | // Permissions of the span are the permissions of the startpoint 145 | return s_varm_flashperm_partition_num_has_permissions(pnum_at_start, required_permissions); 146 | } 147 | -------------------------------------------------------------------------------- /src/main/arm/varm_resets.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pico/types.h" 10 | #include "hardware/resets.h" 11 | 12 | void s_varm_reset_block_noinline(uint32_t mask); 13 | void s_varm_step_safe_unreset_block_wait_noinline(uint32_t mask); 14 | void s_varm_step_safe_reset_unreset_block_wait_noinline(uint32_t mask); 15 | 16 | static __force_inline void inline_s_varm_reset_unreset_block_wait(uint32_t mask) { 17 | hw_set_bits(&resets_hw->reset, mask); 18 | hw_clear_bits(&resets_hw->reset, mask); 19 | while (!(resets_hw->reset_done & mask)); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/native/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(bootrom_common INTERFACE) 2 | 3 | target_sources(bootrom_common INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/native_generic_flash.c 5 | ) 6 | 7 | target_compile_definitions(bootrom_common INTERFACE 8 | USE_BOOTROM_GPIO=${USE_BOOTROM_GPIO} 9 | BOOTROM_BUILD=1 10 | ) 11 | 12 | target_link_libraries(bootrom_common INTERFACE 13 | hardware_regs 14 | hardware_structs 15 | hardware_resets_headers 16 | hardware_sync_headers 17 | hardware_gpio_headers 18 | hardware_irq_headers 19 | hardware_exception_headers 20 | boot_uf2_headers 21 | boot_picoboot_headers 22 | boot_picobin_headers 23 | pico_bootrom_headers 24 | pico_platform_headers 25 | ) 26 | 27 | target_compile_definitions(bootrom_common INTERFACE 28 | NDEBUG) 29 | 30 | target_include_directories(bootrom_common INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 31 | target_link_libraries(bootrom_common INTERFACE 32 | bootrom_layout 33 | bootrom_shared_apis 34 | mini_printf) 35 | -------------------------------------------------------------------------------- /src/main/native/bootrom.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pico.h" 10 | #include "bootrom_common.h" 11 | #include "bootrom_assert.h" 12 | #include "hardening.h" 13 | #include "rcp_tags.h" 14 | #include "varm_to_riscv_hints.h" 15 | #include "pico/bootrom_constants.h" 16 | #include "boot/picobin.h" 17 | #ifndef SB_TEST 18 | #include "bootrom_layout.h" 19 | #ifndef __riscv 20 | #include "p16.h" 21 | #endif 22 | #endif 23 | #ifndef __ASSEMBLER__ 24 | #include "bootrom_error.h" 25 | #include "boot/picoboot.h" 26 | #include "hardware/structs/bootram.h" 27 | #include "hardware/structs/otp.h" 28 | #include "hardware/structs/sau.h" 29 | #include "hardware/structs/mpu.h" 30 | #include "hardware/sync.h" 31 | #include "native_exports.h" 32 | #endif 33 | 34 | // CLK_SYS FREQ ON STARTUP (in MHz) 35 | // +----------------------- 36 | // | min | 3.6 | 37 | // | typ | 13.0 | 38 | // | max | 22.6 | 39 | // +----------------------+ 40 | #define ROSC_MHZ_MAX 23 41 | #define ROSC_MHZ_TYP 13 42 | 43 | #define BOOTROM_SHORT_REBOOT_MS 1 44 | 45 | #define BOOT_ONCE_NSBOOT_API_DISABLED 0 46 | // detect that OTP boot was used (nice to know if someone has injected some code in the boot path) 47 | #define BOOT_ONCE_OTP_BOOT_TAKEN 1 48 | 49 | #define INVALID_STACK_PTR 0xf0000000 // chosen to make things easier for varmulet_hooks_bootrom which has this in a reg already 50 | 51 | #define VECTORED_BOOT_MAGIC 0xb007c0d3 52 | // we reuse the same pattern to save on code/data space 53 | #define REBOOT_TO_MAGIC_PC VECTORED_BOOT_MAGIC 54 | 55 | // Must match the definition of s_native_default_xip_setup in varm_misc.S 56 | #define DEFAULT_ARM_XIP_SETUP_SIZE_BYTES 12 57 | 58 | // Must match the definition of s_native_default_xip_setup in riscv_bootrom_rt0.S 59 | #define DEFAULT_RISCV_XIP_SETUP_SIZE_BYTES 16 60 | 61 | // Static region assignment: 0=SRAM, 1=XIP, 2=ROM:rodata+ns, 3=bootram_core1 62 | #define BOOTROM_MPU_REGION_RAM 0 63 | #define BOOTROM_MPU_REGION_FLASH 1 64 | #define BOOTROM_MPU_REGION_SECURE_XN 2 65 | #define BOOTROM_MPU_REGION_BOOTRAM_CORE1 3 66 | 67 | #define BOOTROM_MPU_REGION_COUNT 4 68 | 69 | #ifndef __ASSEMBLER__ 70 | 71 | #if !USE_64K_BOOTROM 72 | #define __sg_filler __attribute__((section(".sg_fillers"))) 73 | #else 74 | #define __sg_filler 75 | #endif 76 | 77 | #define __exported_from_arm __used __noinline 78 | 79 | #if TAIL_CALL_HACKS 80 | // need to manually mark symbols tail-called from inline asm as used 81 | #define __used_if_tail_call_hacks __used 82 | #else 83 | #define __used_if_tail_call_hacks 84 | #endif 85 | 86 | #define debug_label(l) ({asm volatile ( "___" __STRING(l) ":");}) 87 | 88 | // note:: we chose a small number to keep this small, though we still want to be relatively confident that 89 | // the pt data in flash hasn't changed. 32 bits seems fine for this 90 | #define PARTITION_TABLE_SHA256_HASH_WORDS 1 91 | 92 | // rp2040, rp2350 arm, rp2350 riscv, global, data 93 | static_assert((int8_t)PARTITION_TABLE_NO_PARTITION_INDEX == -1, ""); 94 | 95 | typedef struct uf2_target_workarea uf2_target_workarea_t; 96 | 97 | // sp can be 0 to use current stack 98 | void varm_to_s_native_secure_call_pc_sp(uint32_t pc, uint32_t sp); 99 | void s_varm_secure_call(uint32_t pc, uint32_t sp); 100 | 101 | int s_varm_api_get_partition_table_info(uint32_t *out_buffer, uint32_t out_buffer_word_size, uint32_t flags); 102 | 103 | // this is the internal method used by the previous 104 | int s_varm_crit_get_pt_partition_info(uint32_t *out_buffer, uint32_t out_buffer_word_size, uint32_t flags_and_partition, const uint32_t *pt_item_data, uint partition_count, bool first_load_from_buffer); 105 | int s_varm_ram_trash_get_uf2_target_partition(uint32_t family_id, resident_partition_t *partition_out); 106 | int s_varm_ram_trash_get_uf2_target_partition_workarea(uint32_t family_id, resident_partition_t *partition_out, uf2_target_workarea_t *uf2_target_workarea); 107 | 108 | int s_varm_api_get_sys_info(uint32_t *buffer, uint32_t buffer_size_words, uint32_t flags); 109 | int s_varm_api_reboot(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1); 110 | uint s_varm_step_safe_api_crit_bootrom_state_reset(uint reset_flags); 111 | 112 | void varm_callable(s_native_busy_wait_at_least_cycles)(uint32_t cycles); 113 | void varm_callable(s_native_crit_init_default_xip_setup_and_enter_image_thunk)(/*bootrom_xip_mode_t*/int8_t mode, uint clkdiv, uint32_t pc, uint32_t sp, uint32_t sp_lim, uint32_t vector_table); 114 | void __attribute__((noreturn)) varm_callable(s_native_crit_launch_nsboot)(void); 115 | 116 | void __attribute__((noreturn)) varm_and_native(dead)(void); 117 | void __attribute__((noreturn)) varm_and_native(wait_rescue)(void); 118 | 119 | static __force_inline int inline_s_lock_check(uint lock_type) { 120 | bootrom_assert(MISC, lock_type <= BOOTROM_LOCK_ENABLE); 121 | // note bits are 1 for unowned, 0 for owned 122 | uint stat = (~bootram_hw->bootlock_stat) & ((1u << BOOTROM_LOCK_ENABLE) | (1u << lock_type)); 123 | if (stat < (1u << BOOTROM_LOCK_ENABLE) || stat == ((1u << BOOTROM_LOCK_ENABLE) | (1u << lock_type))) { 124 | return BOOTROM_OK; 125 | } 126 | // don't ask me how i figured this out, but this saves 28 bytes ;-) 127 | pico_default_asm_volatile("" : : : "memory"); 128 | return BOOTROM_ERROR_LOCK_REQUIRED; 129 | } 130 | 131 | static __force_inline void __attribute__((noreturn)) sudden_death(void) { 132 | #ifndef __riscv 133 | pico_default_asm_volatile( 134 | ".cpu cortex-m33\n" 135 | "cdp p7, #0, c0, c0, c0, #1\n" // rcp_panic 136 | ".cpu cortex-m23\n" 137 | "b.w native_dead\n" 138 | ); 139 | #else 140 | native_dead(); 141 | #endif 142 | __builtin_unreachable(); 143 | } 144 | 145 | #endif // __ASSEMBLER__ 146 | -------------------------------------------------------------------------------- /src/main/native/bootrom_error.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pico/bootrom_constants.h" 10 | 11 | static_assert(BOOTROM_OK == PICO_OK, ""); 12 | //static_assert(BOOTROM_ERROR_TIMEOUT == PICO_ERROR_TIMEOUT, ""); 13 | //static_assert(BOOTROM_ERROR_GENERIC == PICO_ERROR_GENERIC, ""); 14 | //static_assert(BOOTROM_ERROR_NO_DATA == PICO_ERROR_NO_DATA, ""); 15 | static_assert(BOOTROM_ERROR_NOT_PERMITTED == PICO_ERROR_NOT_PERMITTED, ""); 16 | static_assert(BOOTROM_ERROR_INVALID_ARG == PICO_ERROR_INVALID_ARG, ""); 17 | //static_assert(BOOTROM_ERROR_IO == PICO_ERROR_IO, ""); 18 | //static_assert(BOOTROM_ERROR_BADAUTH == PICO_ERROR_BADAUTH, ""); 19 | //static_assert(BOOTROM_ERROR_CONNECT_FAILED == PICO_ERROR_CONNECT_FAILED, ""); 20 | //static_assert(BOOTROM_ERROR_INSUFFICIENT_RESOURCES == PICO_ERROR_INSUFFICIENT_RESOURCES, ""); 21 | static_assert(BOOTROM_ERROR_INVALID_ADDRESS == PICO_ERROR_INVALID_ADDRESS , ""); 22 | static_assert(BOOTROM_ERROR_BAD_ALIGNMENT == PICO_ERROR_BAD_ALIGNMENT , ""); 23 | static_assert(BOOTROM_ERROR_INVALID_STATE == PICO_ERROR_INVALID_STATE , ""); 24 | static_assert(BOOTROM_ERROR_BUFFER_TOO_SMALL == PICO_ERROR_BUFFER_TOO_SMALL, ""); 25 | static_assert(BOOTROM_ERROR_PRECONDITION_NOT_MET == PICO_ERROR_PRECONDITION_NOT_MET, ""); 26 | static_assert(BOOTROM_ERROR_MODIFIED_DATA == PICO_ERROR_MODIFIED_DATA, ""); 27 | static_assert(BOOTROM_ERROR_INVALID_DATA == PICO_ERROR_INVALID_DATA, ""); 28 | static_assert(BOOTROM_ERROR_NOT_FOUND == PICO_ERROR_NOT_FOUND, ""); 29 | static_assert(BOOTROM_ERROR_UNSUPPORTED_MODIFICATION == PICO_ERROR_UNSUPPORTED_MODIFICATION, ""); 30 | 31 | // not allowed in bootrom 32 | #define PICO_OK use_BOOTROM_variant_instead 33 | #define PICO_ERROR_TIMEOUT use_BOOTROM_variant_instead 34 | #define PICO_ERROR_GENERIC use_BOOTROM_variant_instead 35 | #define PICO_ERROR_NO_DATA use_BOOTROM_variant_instead 36 | #define PICO_ERROR_NOT_PERMITTED use_BOOTROM_variant_instead 37 | #define PICO_ERROR_INVALID_ARG use_BOOTROM_variant_instead 38 | #define PICO_ERROR_IO use_BOOTROM_variant_instead 39 | #define PICO_ERROR_BADAUTH use_BOOTROM_variant_instead 40 | #define PICO_ERROR_CONNECT_FAILED use_BOOTROM_variant_instead 41 | #define PICO_ERROR_INSUFFICIENT_RESOURCES use_BOOTROM_variant_instead 42 | #define PICO_ERROR_INVALID_ADDRESS use_BOOTROM_variant_instead 43 | #define PICO_ERROR_BAD_ALIGNMENT use_BOOTROM_variant_instead 44 | #define PICO_ERROR_INVALID_STATE use_BOOTROM_variant_instead 45 | #define PICO_ERROR_BUFFER_TOO_SMALL use_BOOTROM_variant_instead 46 | #define PICO_ERROR_PRECONDITION_NOT_MET use_BOOTROM_variant_instead 47 | #define PICO_ERROR_MODIFIED_DATA use_BOOTROM_variant_instead 48 | #define PICO_ERROR_INVALID_DATA use_BOOTROM_variant_instead 49 | #define PICO_ERROR_NOT_FOUND use_BOOTROM_variant_instead 50 | #define PICO_ERROR_UNSUPPORTED_MODIFICATION use_BOOTROM_variant_instead 51 | -------------------------------------------------------------------------------- /src/main/native/bootrom_otp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #ifndef __riscv 10 | #include "bootrom.h" 11 | #include "hardware/address_mapped.h" 12 | #include "hardware/regs/otp_data.h" 13 | #include "hardware/structs/otp.h" 14 | #include "hardening.h" 15 | 16 | // Offsets are all scaled so that OTP rows are 2 bytes apart (so that the same 17 | // offset is passed into all functions). This means the _raw functions will 18 | // scale the offset by 2 internally. 19 | 20 | #define NUM_OTP_ROWS_LOG2 12u 21 | #define NUM_OTP_PAGE_ROWS_LOG2 6u 22 | #define OTP_ROW_MASK (NUM_OTP_ROWS - 1u) 23 | static_assert((1u << NUM_OTP_ROWS_LOG2) == NUM_OTP_ROWS, ""); 24 | static_assert((1u << NUM_OTP_PAGE_ROWS_LOG2) == NUM_OTP_PAGE_ROWS, ""); 25 | static_assert(NUM_OTP_PAGES == NUM_OTP_ROWS / NUM_OTP_PAGE_ROWS, ""); 26 | 27 | #define OTP_CMD_BITS (OTP_CMD_ROW_BITS | OTP_CMD_WRITE_BITS | OTP_CMD_ECC_BITS) 28 | 29 | // these are defined in the linker script as it makes GCC slightly less dumb 30 | typedef const uint16_t otp_ecc_row_value_t; 31 | typedef const uint32_t otp_ecc_row_value2_t; 32 | typedef const uint32_t otp_raw_row_value_t; 33 | extern otp_ecc_row_value_t otp_data[NUM_OTP_ROWS]; 34 | extern otp_ecc_row_value_t otp_data_guarded[NUM_OTP_ROWS]; 35 | extern otp_raw_row_value_t otp_data_raw[NUM_OTP_ROWS]; 36 | extern otp_raw_row_value_t otp_data_raw_guarded[NUM_OTP_ROWS]; 37 | 38 | // ---------------------------------------------------------------------------- 39 | // SBPI register constants 40 | 41 | #define OTP_TARGET_DAP 0x02u 42 | #define OTP_TARGET_PMC 0x3au 43 | 44 | #define OTP_REG_READ 0x80u // 10nn_nnnn: read register n 45 | #define OTP_REG_WRITE 0xc0u // 11nn_nnnn: 46 | 47 | #define OTP_DAP_DR0 0x00u // Data 7:0 48 | #define OTP_DAP_DR1 0x01u // Data 15:8 49 | #define OTP_DAP_ECC 0x20u // Data 23:16 50 | #define OTP_DAP_RQ0_RFMR 0x30u // Read Mode Control, Charge Pump Control 51 | #define OTP_DAP_RQ1_VRMR 0x31u // Read Voltage Control (VRR), CP enable 52 | #define OTP_DAP_RQ2_OVLR 0x32u // IPS VQQ and VPP Control 53 | #define OTP_DAP_RQ3_IPCR 0x33u // VDD detect, Ext. Ref. enable, ISP enable, OSC. Output Mode, Ext Ck enable, Ref Bias Disable 54 | #define OTP_DAP_RQ4_OSCR 0x34u // Reserved for Test 55 | #define OTP_DAP_RQ5_ORCR 0x35u // OTP ROM control, Test Mode Controls 56 | #define OTP_DAP_RQ6_ODCR 0x36u // Read Timer Control 57 | #define OTP_DAP_RQ7_IPCR2 0x37u // IPS CP sync. Input Control, IPS reserved Control 58 | #define OTP_DAP_RQ8_OCER 0x38u // OTP Bank Selection, PD control 59 | #define OTP_DAP_RQ9_RES0 0x39u // Reserved 60 | #define OTP_DAP_RQ10_DPCR 0x3au // DATAPATH Control: (msb - lsb) {MUXQ[1:0], PASS, brpGEN. brpDIS, eccTST, eccGEN, eccDIS} 61 | #define OTP_DAP_RQ11_DPCR_2 0x3bu // DATAPATH Control 2 – multi-bit prog. control {5’b00000, MBPC[2:0]} 62 | #define OTP_DAP_CQ0 0x3cu // OTP address LSBs 63 | #define OTP_DAP_CQ1 0x3du // OTP address MSBs 64 | 65 | #define OTP_PMC_MODE_0 0x30u // Bytes: 2 ; Default Read Conditions 0 66 | #define OTP_PMC_MODE_1 0x32u // Bytes: 2 ; Read Conditions 1 67 | #define OTP_PMC_MODE_2 0x34u // Bytes: 2 ; Read Conditions 2 68 | #define OTP_PMC_MODE_3 0x36u // Bytes: 2 ; Specific Function Usage 69 | #define OTP_PMC_TIMING_0 0x38u // Bytes: 1 ; Timing Control 0 70 | #define OTP_PMC_TIMING_1 0x39u // Bytes: 1 ; Timing Control 1 71 | #define OTP_PMC_TIMING_2 0x3au // Bytes: 1 ; Timing Control 2 72 | #define OTP_PMC_DAP_ADDR 0x3bu // Bytes: 1 ; DAP ID Address 73 | #define OTP_PMC_CQ 0x3cu // Bytes: 2 ; Function Control 74 | #define OTP_PMC_DFSR 0x3eu // Bytes: 1 ; Flag Selection (Read Only) 75 | #define OTP_PMC_CTRL_STATUS 0x3fu // Bytes: 1 ; Control Register (Write Only), STATUS (Read Only) 76 | 77 | // ---------------------------------------------------------------------------- 78 | // Read functions 79 | 80 | #define bootrom_otp_inline static inline 81 | 82 | // Read 16-bit ECC-protected value from OTP 83 | bootrom_otp_inline uint16_t inline_s_otp_read_ecc(uint row) { 84 | return otp_data[row & OTP_ROW_MASK]; 85 | } 86 | 87 | // Read 16-bit ECC-protected value from OTP, and fault if instability detected 88 | bootrom_otp_inline uint32_t inline_s_otp_read_ecc_guarded(uint row) { 89 | #if ASM_SIZE_HACKS 90 | if (__builtin_constant_p(row)) { 91 | uint32_t rc; 92 | row = (row & OTP_ROW_MASK) * 2 - 0xa0; 93 | pico_default_asm( 94 | "ldrh %0, [%1, %2]" 95 | : "=l" (rc) 96 | : "l" (otp_data_guarded + 0x50), "i" (row) 97 | ); 98 | return rc; 99 | } 100 | #endif 101 | return otp_data_guarded[row & OTP_ROW_MASK]; 102 | } 103 | 104 | // Read 2 aligned 16-bit ECC-protected values from OTP, and fault if instability detected 105 | bootrom_otp_inline uint32_t inline_s_otp_read_ecc2_guarded(uint row) { 106 | #if ASM_SIZE_HACKS 107 | if (__builtin_constant_p(row)) { 108 | uint32_t rc; 109 | row = (row & OTP_ROW_MASK) * 2 - 0xa0; 110 | pico_default_asm( 111 | "ldr %0, [%1, %2]" 112 | : "=l" (rc) 113 | : "l" (otp_data_guarded + 0x50), "i" (row) 114 | ); 115 | return rc; 116 | } 117 | #endif 118 | return *(otp_ecc_row_value2_t *)__builtin_assume_aligned(&otp_data_guarded[row & OTP_ROW_MASK], 4); 119 | } 120 | 121 | // Read raw 24-bit value from OTP 122 | bootrom_otp_inline uint32_t inline_s_otp_read_raw(uint row) { 123 | return otp_data_raw[row & OTP_ROW_MASK]; 124 | } 125 | 126 | // Read raw 24-bit value from OTP 127 | bootrom_otp_inline uint32_t inline_s_otp_read_raw_guarded(uint row) { 128 | return otp_data_raw_guarded[row & OTP_ROW_MASK]; 129 | } 130 | 131 | // Read a 24-bit raw value with bitwise majority vote across 3 rows: 132 | //uint32_t otp_read_rbit3(uint offset); 133 | // 3-way bitwise majority vote across 3 rows: 134 | uint32_t s_varm_step_safe_otp_read_rbit3_guarded(uint row); 135 | 136 | // ---------------------------------------------------------------------------- 137 | // Write functions 138 | 139 | // note only two byte alignment is actually required for ECC reads 140 | int s_varm_api_otp_access(aligned4_uint8_t *buf, uint32_t buf_len, otp_cmd_t cmd); 141 | int s_varm_api_hx_otp_access(aligned4_uint8_t *buf, uint32_t buf_len, otp_cmd_t cmd, hx_xbool secure); 142 | 143 | uint32_t s_otp_advance_bl_to_s_value(uint32_t ignored, uint32_t page); 144 | #endif 145 | -------------------------------------------------------------------------------- /src/main/native/native_generic_flash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "hardware/regs/io_qspi.h" 8 | #include "hardware/structs/pads_qspi.h" 9 | #include "hardware/structs/qmi.h" 10 | #include "native_generic_flash.h" 11 | #include "bootrom.h" 12 | 13 | // Sanity check 14 | #undef static_assert 15 | #define static_assert(cond, x) extern int __CONCAT(static_assert,__LINE__)[(cond)?1:-1] 16 | check_hw_layout(qmi_hw_t, direct_csr, QMI_DIRECT_CSR_OFFSET); 17 | check_hw_layout(qmi_hw_t, direct_tx, QMI_DIRECT_TX_OFFSET); 18 | 19 | static_assert(IO_QSPI_GPIO_QSPI_SCLK_CTRL_OFFSET == 0x14, "QSPI IOs have moved"); 20 | static_assert(IO_QSPI_GPIO_QSPI_SD3_CTRL_OFFSET == 0x3c, "QSPI IOs have moved"); 21 | 22 | static_assert(PADS_QSPI_GPIO_QSPI_SCLK_OFFSET == 0x00000004, "QSPI PAD has moved"); 23 | static_assert(PADS_QSPI_GPIO_QSPI_SD0_OFFSET == 0x00000008, "QSPI PAD has moved"); 24 | static_assert(PADS_QSPI_GPIO_QSPI_SD1_OFFSET == 0x0000000c, "QSPI PAD has moved"); 25 | static_assert(PADS_QSPI_GPIO_QSPI_SD2_OFFSET == 0x00000010, "QSPI PAD has moved"); 26 | static_assert(PADS_QSPI_GPIO_QSPI_SD3_OFFSET == 0x00000014, "QSPI PAD has moved"); 27 | static_assert(PADS_QSPI_GPIO_QSPI_SS_OFFSET == 0x00000018, "QSPI PAD has moved"); 28 | 29 | // Put bytes from one buffer, and get bytes into another buffer. 30 | // These can be the same buffer. 31 | // If tx is NULL then send zeroes. 32 | // If rx is NULL then all read data will be dropped. 33 | // Returns cs (first arg), so that it can be preserved over calls without 34 | // using a callee save. 35 | uint __noinline __attribute__((used)) s_native_crit_flash_put_get(uint cs, const uint8_t *tx, uint8_t *rx, size_t count) { 36 | canary_entry(S_NATIVE_CRIT_FLASH_PUT_GET); 37 | 38 | // Assert chip select, and enable direct mode. Anything queued in TX FIFO will start now. 39 | #if !GENERAL_SIZE_HACKS 40 | uint32_t csr_toggle_mask = (QMI_DIRECT_CSR_ASSERT_CS0N_BITS << cs) | QMI_DIRECT_CSR_EN_BITS; 41 | #else 42 | // Slightly smaller, works for 0/1 only 43 | bootrom_assert(FLASH, cs == 0 || cs == 1); 44 | uint32_t csr_toggle_mask = (QMI_DIRECT_CSR_ASSERT_CS0N_BITS | QMI_DIRECT_CSR_EN_BITS) + (cs << QMI_DIRECT_CSR_ASSERT_CS0N_LSB); 45 | #endif 46 | hw_xor_bits(&qmi_hw->direct_csr, csr_toggle_mask); 47 | 48 | size_t tx_count = count; 49 | size_t rx_count = count; 50 | while (tx_count || rx_count) { 51 | uint32_t status = qmi_hw->direct_csr; 52 | if (tx_count && !(status & QMI_DIRECT_CSR_TXFULL_BITS)) { 53 | qmi_hw->direct_tx = (uint32_t) (tx ? *tx++ : 0); 54 | --tx_count; 55 | } 56 | if (rx_count && !(status & QMI_DIRECT_CSR_RXEMPTY_BITS)) { 57 | uint8_t rxbyte = (uint8_t) qmi_hw->direct_rx; 58 | if (rx) 59 | *rx++ = rxbyte; 60 | --rx_count; 61 | } 62 | } 63 | 64 | // Wait for BUSY as there may be no RX data at all, e.g. for single-byte SPI commands 65 | while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) 66 | ; 67 | 68 | // Disable direct-mode interface and deassert chip select 69 | hw_xor_bits(&qmi_hw->direct_csr, csr_toggle_mask); 70 | canary_exit_return(S_NATIVE_CRIT_FLASH_PUT_GET, cs); 71 | } 72 | -------------------------------------------------------------------------------- /src/main/native/native_generic_flash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "bootrom.h" 10 | #include "bootrom_otp.h" 11 | #include "bootram.h" 12 | #include "hardware/structs/qmi.h" 13 | 14 | #include 15 | #include 16 | 17 | #define FLASH_PAGE_SHIFT 8u 18 | #define FLASH_PAGE_SIZE (1ul << FLASH_PAGE_SHIFT) 19 | #define FLASH_PAGE_REMAINDER_MASK (FLASH_PAGE_SIZE - 1u) 20 | 21 | // Get non-sector parts of a flash storage address, assuming 2x16MiB windows: 22 | #define FLASH_SECTOR_NUM_MASK ((1ul << 25) - (1ul << FLASH_SECTOR_SHIFT)) 23 | 24 | // The SCK divisor set by flash_init_spi, is the default used for programming, 25 | // reads from the debugger post-programming, etc: (1 MHz @ 12 MHz clk_sys) 26 | // Quite conservative as clk_sys may be rather high when this is used, and 27 | // 03h reads may support quite low maximum SCK frequency. 28 | #define BOOTROM_SPI_CLKDIV_DEFAULT 12u 29 | // The SCK divisor used by nsboot (the USB/UART bootloader), which runs at a 30 | // fixed 48 MHz clk_sys -> 8 MHz SCK, the same as RP2040 in BOOTSEL mode. 31 | #define BOOTROM_SPI_CLKDIV_NSBOOT 6u 32 | // The first divisor to attempt to use to read flash during flash boot: 33 | // (-> 50 MHz at max clk_sys of 150 MHz, ~4 MHz at expected ROSC freq) 34 | #define BOOTROM_SPI_CLKDIV_FLASH_BOOT_MIN 3u 35 | // After trying all modes, flash boot will fall back to progressively slower 36 | // divisors (doubling each time) up to this maximum: 37 | // (-> 6.25 MHz at max clk_sys, snail's pace at expected ROSC boot freq) 38 | #define BOOTROM_SPI_CLKDIV_FLASH_BOOT_MAX 24u 39 | 40 | #define NUM_FLASH_BOOT_CLOCK_DIVS 4 41 | 42 | typedef uint32_t flash_offset_t; 43 | 44 | static_assert(BOOTROM_SPI_CLKDIV_FLASH_BOOT_MAX == BOOTROM_SPI_CLKDIV_FLASH_BOOT_MIN << (NUM_FLASH_BOOT_CLOCK_DIVS-1), ""); 45 | // Try each clock divisor in each mode before giving up. 46 | #define FLASH_SETTING_COMBINATION_COUNT (BOOTROM_XIP_MODE_N_MODES * NUM_FLASH_BOOT_CLOCK_DIVS) 47 | 48 | void s_varm_api_crit_connect_internal_flash(void); 49 | void s_varm_api_crit_flash_reset_address_trans(void); 50 | void s_varm_api_crit_flash_select_xip_read_mode(bootrom_xip_mode_t mode, uint8_t clkdiv); 51 | uint varm_callable(s_native_crit_flash_put_get)(uint cs, const uint8_t *tx, uint8_t *rx, size_t count); 52 | uint s_varm_flash_do_cmd(uint cs, uint8_t cmd, const uint8_t *tx, uint8_t *rx, size_t count); 53 | void s_varm_api_crit_flash_exit_xip(void); 54 | void s_varm_flash_page_program(flash_offset_t offset, const uint8_t *data); 55 | void s_varm_api_flash_range_program(flash_offset_t offset, const uint8_t *data, size_t count); 56 | void s_varm_flash_sector_erase(flash_offset_t offset); 57 | void s_varm_flash_user_erase(flash_offset_t offset, uint8_t cmd); 58 | void s_varm_api_flash_range_erase(flash_offset_t offset, size_t count, uint32_t block_size, uint8_t block_cmd); 59 | void s_varm_crit_flash_read_data(uint8_t *rx, uint32_t offset, size_t count); 60 | void s_varm_api_flash_enter_cmd_xip(void); 61 | void s_varm_flash_abort(void); 62 | void s_varm_flash_abort_clear(void); 63 | int s_varm_flash_was_aborted(void); 64 | 65 | #if 0 66 | void s_varm_api_crit_flash_flush_cache(void); 67 | void s_varm_crit_pin_xip_ram(void); 68 | #else 69 | // Annotated with correct (minimal) clobbers to save spill/fill at call site 70 | static __force_inline void s_varm_api_crit_flash_flush_cache(void) { 71 | pico_default_asm_volatile ( 72 | "bl s_varm_api_crit_flash_flush_cache_impl\n" 73 | : : : "r0", "r3", "ip", "lr", "cc" 74 | ); 75 | } 76 | static __force_inline void s_varm_crit_pin_xip_ram(void) { 77 | pico_default_asm_volatile ( 78 | "bl s_varm_crit_pin_xip_ram_impl\n" 79 | : : : "r0", "r3", "ip", "lr", "cc" 80 | ); 81 | } 82 | #endif 83 | 84 | static __force_inline uint inline_s_varm_flash_cs_from_offset(flash_offset_t offset) { 85 | bootrom_assert(GENERIC_FLASH, offset < MAX_FLASH_ADDR_OFFSET); 86 | return (offset >> 24) & 0x1u; 87 | } 88 | 89 | #ifndef __riscv 90 | // OTP-related getters are not used in the RISC-V parts of the bootrom 91 | // (they should be in shared arm6 code) 92 | 93 | // Default flash device info used if OTP is not marked as valid: 94 | #define FLASH_DEFAULT_DEVINFO ( \ 95 | OTP_DATA_FLASH_DEVINFO_CS0_SIZE_VALUE_16M << OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB | \ 96 | OTP_DATA_FLASH_DEVINFO_CS1_SIZE_VALUE_NONE << OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB \ 97 | ) 98 | 99 | static __force_inline uint32_t s_varm_flash_devinfo_get_size(uint cs) { 100 | uint shamt = OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB + cs * ( 101 | OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB - OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB 102 | ); 103 | uint32_t mask = OTP_DATA_FLASH_DEVINFO_CS0_SIZE_BITS >> OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB; 104 | // Calculate based on the cached devinfo which was read out during early boot 105 | uint size_bits = (bootram->always.zero_init.flash_devinfo >> shamt) & mask; 106 | // Encoded as log2(4k sector count): 107 | return size_bits == 0u ? 0u : (0x1000u << size_bits); 108 | } 109 | 110 | static __force_inline bool s_varm_flash_devinfo_get_d8h_supported(void) { 111 | return bootram->always.zero_init.flash_devinfo & OTP_DATA_FLASH_DEVINFO_D8H_ERASE_SUPPORTED_BITS; 112 | } 113 | 114 | // Return -1 if no CS1 device, otherwise return GPIO number 115 | static __force_inline int inline_s_varm_flash_devinfo_get_cs1_gpio(void) { 116 | uint16_t devinfo = bootram->always.zero_init.flash_devinfo; 117 | if ((devinfo & OTP_DATA_FLASH_DEVINFO_CS1_SIZE_BITS) == 0) { 118 | return -1; 119 | } else { 120 | return (devinfo & OTP_DATA_FLASH_DEVINFO_CS1_GPIO_BITS) >> OTP_DATA_FLASH_DEVINFO_CS1_GPIO_LSB; 121 | } 122 | } 123 | 124 | // Return true if the given flash offset is within the bounds of known flash devices 125 | bool s_varm_flash_check_in_bounds_single_addr(flash_offset_t offset); 126 | 127 | // Return true if all flash offsets within a range are within the bounds of known flash devices 128 | bool s_varm_crit_flash_check_in_bounds_addr_span(flash_offset_t start_addr, uint32_t size); 129 | 130 | #endif 131 | -------------------------------------------------------------------------------- /src/main/riscv/bootrom_riscv.template.ld: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ROM(rx) : ORIGIN = ${BOOTROM_RISCV_START}, LENGTH = ${BOOTROM_RISCV_END} - ${BOOTROM_RISCV_END_SIZE} - ${BOOTROM_ROMTABLE_SIZE} 3 | ROMTABLE(rx) : ORIGIN = ${BOOTROM_RISCV_END} - ${BOOTROM_RISCV_END_SIZE} - ${BOOTROM_ROMTABLE_SIZE}, LENGTH = ${BOOTROM_ROMTABLE_SIZE} 4 | ROMEND(rx) : ORIGIN = ${BOOTROM_RISCV_END} - ${BOOTROM_RISCV_END_SIZE}, LENGTH = ${BOOTROM_RISCV_END_SIZE} 5 | SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 520K 6 | BOOTRAM(rw) : ORIGIN = ${BOOTRAM_RISCV_STATIC_DATA_START}, LENGTH = ${BOOTRAM_RISCV_STATIC_DATA_SIZE} 7 | USBRAM(rw) : ORIGIN = 0x50100000 + 0x1000 - ${CORE0_BOOT_USBRAM_WORKSPACE_SIZE}, LENGTH = ${CORE0_BOOT_USBRAM_WORKSPACE_SIZE} 8 | } 9 | 10 | INCLUDE "${ARM_SYMBOL_LINKER_SCRIPT}" 11 | INCLUDE "${NSBOOT_SYMBOL_LINKER_SCRIPT}" 12 | 13 | SECTIONS { 14 | . = ORIGIN(ROM); 15 | ENTRY(__reset_vector) 16 | 17 | .text : { 18 | riscv_start = .; 19 | /* Start with varmulet, so that the decode table is at a known 20 | address (workaround for lack of 16-bit data relocs) */ 21 | KEEP(*(.text.varmulet_armv6m_core)) 22 | *(.rodata.varmulet_usbboot_asm_hooks) 23 | *(.text.varmulet_run_adapter) 24 | /* falls thru to */ 25 | KEEP(*(.text.varmulet_run)) 26 | 27 | /* keep these together so we can use an 8 bit offset asm_hook table */ 28 | first_hook_addr = .; 29 | *(.text.varmulet_hook_default_enter_fn) 30 | *(.text.varmulet_hook_default_exit_fn) 31 | *(.text.varmulet_hook_default_save_regs_fn) 32 | *(.text.varmulet_hook_default_restore_regs_fn) 33 | /* #if !VARMULET_USE_ENTER_HOOK_TO_OVERRIDE_REGISTER_STORED_HOOKS 34 | *(.text.varmulet_hook_default_execute_instruction) 35 | *(.text.varmulet_main_decode_table) 36 | *(.text.varmulet_dp_decode_table) 37 | #endif*/ 38 | *(.text.varmulet_halt) 39 | *(.text.varmulet_hook_default_bkpt_instr) 40 | *(.text.varmulet_hook_default_cps_instr) 41 | *(.text.varmulet_hook_default_mrs_instr) 42 | *(.text.varmulet_hook_default_msr_instr) 43 | *(.text.varmulet_hook_default_misc_control_instr) 44 | *(.text.varmulet_hook_default_exc_return) 45 | *(.text.varmulet_hook_default_call_return) 46 | *(.text.bootrom_asm_hooks) 47 | 48 | KEEP(*(.text.load_a4_goto_varm_wrapper)) /* fall through: */ 49 | KEEP(*(.text.varm_wrapper)) 50 | 51 | last_hook_addr = bootrom_hint_instr; 52 | ASSERT( last_hook_addr - first_hook_addr < 0x200, "need hints to fit into 0x200 bytes for 8 bit table"); 53 | 54 | KEEP(*(.vectors)) 55 | *(SORT_BY_ALIGNMENT(.text*)) 56 | KEEP(*(.rodata.keep*)) 57 | 58 | *(.rodata*) 59 | *(.srodata*) 60 | riscv_end = .; 61 | this_is_the_end_my_only_friend_the_end = .; 62 | } >ROM =0x0290 /* c.ebreak */ 63 | 64 | /* The entry point and ROM tables are in different sections because 65 | attempting to set the location counter to end-of-ROM can give a link 66 | error like: 67 | 68 | cannot move location counter backwards (from 000000000000880c to 0000000000007ffc) 69 | 70 | Which seems related to the fact that the code is oversize until linker 71 | relaxations are applied. */ 72 | 73 | .romtable : { 74 | KEEP(*(.romtable)) 75 | } > ROMTABLE =0x0290 /* c.ebreak */ 76 | 77 | .entry : { 78 | KEEP(*(.entry)) 79 | } > ROMEND 80 | 81 | ASSERT(varmulet_run == varmulet_run_adapter_end, "varmulet_run_adapter does not fall into varmulet_run") 82 | ASSERT(SIZEOF(.entry) == ${BOOTROM_RISCV_END_SIZE}, ".entry is the wrong size, should be 10 bytes") 83 | ASSERT(__reset_vector == ORIGIN(ROMEND) + LENGTH(ROMEND) - 4, 84 | "__reset_vector was not in the expected location") 85 | .data : { 86 | *(.data*) 87 | *(.sdata*) 88 | } >SRAM 89 | 90 | .bss : { 91 | *(.bss*) 92 | *(COMMON) 93 | *(.sbss*) 94 | } >SRAM 95 | 96 | .allowed_bss (COPY) : { 97 | PROVIDE(__global_pointer$ = . ); 98 | *(.allowed_bss*) 99 | . = ALIGN(4); 100 | } >BOOTRAM 101 | 102 | .fake (NOLOAD) : { 103 | PROVIDE(core0_flash_boot_usbram_workspace = .); 104 | } >USBRAM 105 | 106 | ASSERT(SIZEOF(.data) == 0, 107 | "ERROR: do not use static memory in bootrom! (.data)") 108 | 109 | ASSERT(SIZEOF(.bss) == 0, 110 | "ERROR: do not use bss memory in bootrom! (.bss)") 111 | ASSERT(SIZEOF(.allowed_bss) == ${BOOTRAM_RISCV_STATIC_DATA_SIZE}, "allowed_bss size mismatch") 112 | 113 | /* Leave room above the stack for stage 2 load, so that stage 2 114 | can image SRAM from its beginning */ 115 | _stacktop = ORIGIN(SRAM) + LENGTH(SRAM) - 256; 116 | 117 | /* aliases with garbage for unused parameters */ 118 | call_armv6m_0 = call_armv6m_2; 119 | call_armv6m_1 = call_armv6m_2; 120 | 121 | /* For some reason, RISC-V gas/ld can't relocate .hword sym_x, but *can* 122 | relocate .hword sym_x - sym_y (relative), so use this magic zero to 123 | transform absolute relocations into relative ones: */ 124 | PROVIDE(__opaque_zero_symbol = 0); 125 | } 126 | 127 | otp_data_raw_guarded = 0x4013c000; 128 | -------------------------------------------------------------------------------- /src/main/riscv/bootrom_riscv_asm_macros.inc.S: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | // For stack guards: 10 | .macro riscv_disable_32byte_memregion trashreg, pmpnum, addr 11 | li \trashreg, ((\addr & -32) | 0x0f) >> 2 12 | csrw RVCSR_PMPADDR0_OFFSET + \pmpnum, \trashreg 13 | // Clearing of permissions here can be skipped if this code is known to be run after a core reset 14 | .if \pmpnum % 4 == 0 15 | // (use immediate instruction if within range of 5-bit immediate) 16 | csrci RVCSR_PMPCFG0_OFFSET + \pmpnum / 4, RVCSR_PMPCFG0_R0_X_BITS | RVCSR_PMPCFG0_R0_W_BITS | RVCSR_PMPCFG0_R0_R_BITS 17 | .else 18 | li \trashreg, (RVCSR_PMPCFG0_R0_X_BITS | RVCSR_PMPCFG0_R0_W_BITS | RVCSR_PMPCFG0_R0_R_BITS) << (8 * (\pmpnum % 4)) 19 | csrc RVCSR_PMPCFG0_OFFSET + \pmpnum / 4, \trashreg 20 | .endif 21 | // Enable the region 22 | .if \pmpnum % 4 == 0 23 | csrsi RVCSR_PMPCFG0_OFFSET + \pmpnum / 4, (RVCSR_PMPCFG0_R0_A_VALUE_NAPOT << RVCSR_PMPCFG0_R0_A_LSB) 24 | .else 25 | li \trashreg, (RVCSR_PMPCFG0_R0_A_VALUE_NAPOT << RVCSR_PMPCFG0_R0_A_LSB) << (8 * (\pmpnum % 4)) 26 | csrs RVCSR_PMPCFG0_OFFSET + \pmpnum / 4, \trashreg 27 | .endif 28 | // Make it enabled for M-mode as well as U-mode 29 | .if \pmpnum < 5 30 | csrsi RVCSR_PMPCFGM0_OFFSET, 1 << \pmpnum 31 | .else 32 | li \trashreg, 1 << \pmpnum 33 | csrs RVCSR_PMPCFGM0_OFFSET, \trashreg 34 | .endif 35 | .endm 36 | 37 | // Even with medlow code model, `la` generates an auipc; addi sequence after 38 | // relaxation. As all ROM addresses are less than 60k, a 16-bit lui + 32-bit 39 | // addi always suffices, so force the correct sequence using this macro: 40 | .macro la_romaddr rd, sym 41 | #if 0 42 | la \rd, \sym 43 | #else 44 | lui \rd, %hi(\sym) 45 | addi \rd, \rd, %lo(\sym) 46 | #endif 47 | .endm 48 | 49 | .macro h3.block 50 | slt x0, x0, x0 51 | .endm 52 | 53 | .macro h3.unblock 54 | slt x0, x0, x1 55 | .endm 56 | 57 | // I've tried j sym; c.j sym; insn c.j xxx sym; but all result in 32-bit j 58 | // when sym is in a different section (note this is an *assembler* limitation 59 | // only -- I imagine the usual R_RISCV_RELAX can't be used because the 60 | // assembler can't allocate a register.) So, time for violence: 61 | 62 | .macro j_force_rvc sym 63 | #if USE_64K_BOOTROM 64 | // use a regular j as this may get pushed out of range for larger images 65 | j \sym 66 | #else 67 | .reloc ., R_RISCV_RVC_JUMP, \sym 68 | .insn 0xa001 69 | #endif 70 | .endm 71 | 72 | // For jal, a `call` pseudo would do (auipc ra, 0; jalr 0(ra) with 73 | // R_RISCV_CALL + R_RISCV_RELAX) but: 74 | // 75 | // - There is no clobberless equivalent for j, so no point in making the jal 76 | // behaviour different 77 | // 78 | // - If something we expect to be a short call is actually long, we want to 79 | // be told so we can review why 80 | // 81 | // Fun fact, the combination R_RISCV_RELAX + R_RISCV_JAL *is* defined but only 82 | // for the cursed Zcmt, it doesn't relax jal -> c.jal like you might expect 83 | 84 | .macro jal_force_rvc sym 85 | #if USE_64K_BOOTROM 86 | jal \sym 87 | #else 88 | .reloc ., R_RISCV_RVC_JUMP, \sym 89 | .insn 0x2001 90 | #endif 91 | .endm 92 | -------------------------------------------------------------------------------- /src/main/riscv/riscv_apis.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "pico.h" 8 | #include "bootram.h" 9 | #include "mini_printf.h" 10 | 11 | void __attribute__((noreturn)) bootrom_assertion_failure(__unused const char *fn, __unused uint line) { 12 | #if MINI_PRINTF 13 | printf("ASSERTION FAILURE %s:%d\n", fn, line); 14 | #endif 15 | __breakpoint(); 16 | __builtin_unreachable(); 17 | } 18 | 19 | int __used s_native_set_varmulet_user_stack(uint32_pair_t *base_size) { 20 | int rc; 21 | // commented this out, as bootrom was built with older SDK for which restore_interrupts was a CSR write not set 22 | // uint32_t save = save_and_disable_interrupts(); 23 | uint32_t save; 24 | pico_default_asm_volatile ( 25 | "csrrci %0, mstatus, 0x8\n" 26 | : "=r" (save) 27 | ); 28 | 29 | uint core_num = riscv_read_csr(mhartid); 30 | if (bootram->runtime.core[core_num].riscv.varmulet_enclosing_cpu) { 31 | rc = BOOTROM_ERROR_INVALID_STATE; 32 | } else { 33 | uint32_pair_t *current_base_size = &bootram->runtime.core[core_num].riscv.varmulet_user_stack_pair; 34 | uint32_pair_t tmp = *current_base_size; 35 | *current_base_size = *base_size; 36 | *base_size = tmp; 37 | rc = BOOTROM_OK; 38 | } 39 | // This flag may be clear if we left the bootrom through a PC/SP vector 40 | // which didn't return. Now that the user has called back and explicitly 41 | // given us a stack, it seems polite to let them use it. 42 | if (core_num == 0) { 43 | bootram->always.zero_init.allow_core0_autovarm = 1; 44 | } 45 | 46 | // commented this out, as bootrom was build with old code that just did read_csr/write_csr 47 | // restore_interrupts(save); 48 | riscv_write_csr(mstatus, save); 49 | return rc; 50 | } 51 | -------------------------------------------------------------------------------- /src/main/riscv/riscv_misc.S: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "pico.h" 8 | 9 | .section .text.crc, "ax" 10 | 11 | // ---------------------------------------------------------------------------- 12 | // memcpy 13 | 14 | // Note we expose native_memcpy and native_memset as memcpy and memset on 15 | // RISC-V as the compiler seems to use them 16 | 17 | // Replace the C function in riscv_nsboot_vm.c 18 | #if !MINI_PRINTF 19 | .global native_nsboot_init 20 | native_nsboot_init: 21 | mv a1, a0 22 | la a0, armv6m_pointers // should be gp-relative 23 | li a2, 12 24 | // fall through 25 | #endif 26 | 27 | #if FANCY_MEMCPY 28 | #error "No fancy memcpy available" 29 | // (removed as not well-tested and we're unlikely to have room for it) 30 | #elif TINY_MEMCPY 31 | 32 | .p2align 2 33 | .global __memcpy_44 34 | __memcpy_44: 35 | .global native_memcpy, memcpy 36 | native_memcpy: 37 | memcpy: 38 | add a2, a2, a1 39 | mv a3, a0 40 | j 2f 41 | 1: 42 | lbu a5, (a3) 43 | sb a5, (a1) 44 | addi a3, a3, 1 45 | addi a1, a1, 1 46 | 2: 47 | bltu a1, a2, 1b 48 | ret 49 | 50 | #else // Balanced memcpy (default) 51 | 52 | 53 | // This is meant to be fairly small but still transfer a word at a time for 54 | // the case where both buffers are word-aligned. __memcpy_44 is merged as it 55 | // would only save 4 cycles, and adding a second entry point which skips the 56 | // alignment check is not free, due to requirement to save a0 for return value 57 | .global native_memcpy, __memcpy_44, memcpy 58 | native_memcpy: 59 | __memcpy_44: 60 | memcpy: 61 | mv a3, a0 62 | add a2, a2, a3 63 | or a4, a1, a3 64 | andi a4, a4, 0x3 65 | bnez a4, 5f 66 | 67 | addi a2, a2, -3 68 | bgeu a3, a2, 3f 69 | 70 | // Word loop 71 | 1: 72 | lw a4, (a1) 73 | sw a4, (a3) 74 | addi a1, a1, 4 75 | addi a3, a3, 4 76 | 2: 77 | bltu a3, a2, 1b 78 | 3: 79 | addi a2, a2, 3 80 | bgeu a3, a2, 6f 81 | 82 | // Byte loop 83 | 4: 84 | lbu a4, (a1) 85 | sb a4, (a3) 86 | addi a1, a1, 1 87 | addi a3, a3, 1 88 | 5: 89 | bltu a3, a2, 4b 90 | 6: 91 | ret 92 | 93 | #endif 94 | 95 | // ---------------------------------------------------------------------------- 96 | // memset 97 | 98 | .global native_memset0, varm_to_native_memset0 99 | varm_to_native_memset0: // we do this as the riscv_subset of USB boot uses this 100 | native_memset0: 101 | mv a2, a1 102 | li a1, 0 103 | // fall-thru 104 | 105 | #if FANCY_MEMSET 106 | #error "No fancy memset available" 107 | #elif TINY_MEMSET 108 | #error "usb_device.c usb_device_start requires aligned word clears" 109 | .global native_memset, memset 110 | native_memset: 111 | memset: 112 | .global __memset_4 113 | __memset_4: 114 | add a2, a2, a0 115 | mv a3, a0 116 | j 2f 117 | 1: 118 | sb a1, (a3) 119 | addi a3, a3, 1 120 | 2: 121 | bltu a3, a2, 1b 122 | ret 123 | 124 | #else // Balanced memset (default) 125 | 126 | // __memset 4 is merged because it's not a good trade: saves 3 cycles, but 127 | // makes regular memset 1 cycle slower due to redundant save of a0. 128 | 129 | .global native_memset, __memset_4, memset 130 | native_memset: 131 | __memset_4: 132 | memset: 133 | mv a3, a0 134 | add a2, a2, a3 135 | andi a4, a3, 0x3 136 | bnez a4, 5f 137 | 138 | addi a2, a2, -3 139 | bgeu a3, a2, 3f 140 | // Replicate byte x4 for word loop 141 | packh a1, a1, a1 142 | pack a1, a1, a1 143 | 144 | // Word loop 145 | 1: 146 | sw a1, (a3) 147 | addi a3, a3, 4 148 | 2: 149 | bltu a3, a2, 1b 150 | 3: 151 | addi a2, a2, 3 152 | bgeu a3, a2, 6f 153 | 154 | // Byte loop 155 | 4: 156 | sb a1, (a3) 157 | addi a3, a3, 1 158 | 5: 159 | bltu a3, a2, 4b 160 | 6: 161 | ret 162 | 163 | #endif 164 | 165 | // ---------------------------------------------------------------------------- 166 | 167 | // void __exported_from_arm *s_native_api_validate_ns_buffer( 168 | // const void *addr, uint32_t size, hx_bool write, hx_bool *ok); 169 | // 170 | // On RISC-V there is no such thing as S/NS, so this function is stubbed out 171 | // to always return true. We support it as a stub (overloaded for Arm/RISC-V 172 | // via an emulator hint) so that the nsboot-to-Secure shims can be run under 173 | // emulation, avoiding duplicated code which is a waste of size and has been 174 | // a source of bugs. 175 | 176 | // Not available usually when __riscv is defined: 177 | #define RCP_MASK_TRUE 0xa500a500u 178 | 179 | .global s_native_api_validate_ns_buffer 180 | s_native_api_validate_ns_buffer: 181 | // This should really be RCP_MASK_TRUE but we only branch on the sign bit. 182 | // The full bit pattern is only checked by real RCP instructions. 183 | li a4, -1 184 | sw a4, (a3) 185 | // Return the addr that was passed in. This is just a0 -> a0, so nothing to do. 186 | ret 187 | 188 | // This is just the loop part, which is punted to native code for performance 189 | // reasons. a0 contains top of maintenance region, plus maintenance op 0-7, 190 | // minus cache size in bytes. 191 | .global s_native_crit_xip_cache_maintenance 192 | s_native_crit_xip_cache_maintenance: 193 | lui a1, (XIP_SRAM_END + (XIP_MAINTENANCE_BASE - XIP_BASE)) >> 12 194 | 1: 195 | sb a0,0(a0) 196 | addi a0,a0,8 197 | bltu a0, a1, 1b 198 | ret 199 | -------------------------------------------------------------------------------- /src/main/riscv/riscv_varm_wrapper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "pico.h" 8 | #include "varmulet.h" 9 | #include "bootrom.h" 10 | #include "bootrom_layout.h" 11 | #include "bootram.h" 12 | #include "varmulet_hooks_bootrom.h" 13 | #include "mini_printf.h" 14 | 15 | static_assert(VARMULET_CPU_STATE_SIZE == sizeof(armulet_cpu_t), ""); 16 | 17 | // this is a wrapper used for autovarmed RISC-V bootrom APIs. 18 | // Marked explicitly as used, as it's usually entered via fallthrough from an asm fragment. 19 | int __used varm_wrapper(uint32_t p0, uint32_t p1, uint32_t p2, uint32_t p3, uintptr_t func_address) { 20 | uint c = riscv_read_csr(mhartid); 21 | if (!(c || bootram->always.zero_init.allow_core0_autovarm)) { 22 | printf("BLOCKING VARM WRAPPER as not allowed on core %d\n", c); 23 | return BOOTROM_ERROR_INVALID_STATE; 24 | } 25 | armulet_cpu_t *enclosing = bootram->runtime.core[c].riscv.varmulet_enclosing_cpu; 26 | // special value 1 means varmulet is not yet allowed 27 | armulet_cpu_t cpu; // use on stack CPU since we expect to return 28 | 29 | #if !GENERAL_SIZE_HACKS 30 | // This turns into a memset, which is assumed to trash our args, and leads 31 | // to this function having an enormous prolog. We still initialise those 32 | // registers that are used by called varm code: r0-r3, pc, lr. 33 | armulet_reset_cpu(&cpu); 34 | #endif 35 | 36 | if (enclosing) { 37 | // we continue below our small existing ARM stack; we could have used the native stack via additional shared stack support in varmulet, 38 | // however realistically we don't expect to use too much (most things we call use a handful of words), and the only time we're re-entrant (i.e. in this code path) 39 | // is if we are already were executing an ARM bootrom function, so must have taken a RISC-V IRQ, and then called another such 40 | // varm_wrapped API function (nested multiple times). 41 | // note also we now also provie a function to set the ARM stack to something bigger 42 | cpu.regs[ARM_REG_SP] = enclosing->regs[ARM_REG_SP]; 43 | } else if (bootram->runtime.core[c].riscv.varmulet_user_stack_base) { 44 | cpu.splim = bootram->runtime.core[c].riscv.varmulet_user_stack_base; 45 | cpu.regs[ARM_REG_SP] = bootram->runtime.core[c].riscv.varmulet_user_stack_base + bootram->runtime.core[c].riscv.varmulet_user_stack_size; 46 | } else { 47 | cpu.splim = (uintptr_t)bootram->runtime.core[c].riscv.varmulet_stack; 48 | cpu.regs[ARM_REG_SP] = ((uintptr_t) bootram->runtime.core[c].riscv.varmulet_stack) + 49 | sizeof(bootram->runtime.core[c].riscv.varmulet_stack); 50 | } 51 | cpu.regs[ARM_REG_R0] = p0; 52 | cpu.regs[ARM_REG_R1] = p1; 53 | cpu.regs[ARM_REG_R2] = p2; 54 | cpu.regs[ARM_REG_R3] = p3; 55 | cpu.regs[ARM_REG_PC] = func_address&~1u; 56 | cpu.regs[ARM_REG_LR] = ARMULET_CALL_RETURN_ADDRESS; 57 | bootram->runtime.core[c].riscv.varmulet_enclosing_cpu = &cpu; 58 | 59 | varmulet_asm_hooks_t hooks = varmulet_preboot_asm_hooks; 60 | 61 | // ok we need the HINT instructions 62 | //hooks.undefined32 = (uintptr_t)&bootrom_undefined32_canary_check; 63 | // bkpt causing ebreak is ok 64 | // hooks.bkpt_instr = (uintptr_t)&varmulet_halt; 65 | // already halt 66 | // hooks.svc_instr = (uintptr_t)&varmulet_halt; 67 | // hooks.hint_instr = (uintptr_t)&varmulet_hook_default_hint_instr; 68 | // we don't expect/support any such instructions in the wrapped functions 69 | //hooks.cps_instr = (uintptr_t)&varmulet_halt; 70 | //hooks.mrs_instr = (uintptr_t)&varmulet_halt; 71 | //hooks.msr_instr = (uintptr_t)&varmulet_halt; 72 | // hooks.update_primask_fn = (uintptr_t)&varmulet_halt; 73 | // misc control is ok as it just ignores dmb, dsb, isb 74 | // hooks.misc_control_instr = (uintptr_t)&varmulet_halt; 75 | // exc_return is ok, as all it does is implement the ARMULET_CALL_RETURN_ADDRESS support 76 | // hooks.exc_return = (uintptr_t)&varmulet_halt; 77 | 78 | // we need call return which also needs exc_return 79 | 80 | #if !FEATURE_BYTE_ASM_HOOKS 81 | hooks.exc_return = (asm_hook_t)(uintptr_t)&varmulet_hook_default_exc_return; 82 | hooks.call_return = (asm_hook_t)(uintptr_t)&varmulet_hook_default_call_return; 83 | #else 84 | static_assert(!(offsetof(varmulet_asm_hooks_t, exc_return) & 1), ""); 85 | *(uint16_t *)__builtin_assume_aligned(&hooks.exc_return,2) = *(uint16_t *)__builtin_assume_aligned(varmulet_hooks_default_exc_and_call_return,2); 86 | #endif 87 | 88 | int rc = varmulet_run_adapter(&cpu, &hooks); 89 | // restore enclosing ptr 90 | bootram->runtime.core[c].riscv.varmulet_enclosing_cpu = enclosing; 91 | return rc; 92 | } 93 | -------------------------------------------------------------------------------- /src/main/riscv/varmulet_hooks_bootrom.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "varmulet.h" 10 | 11 | int varmulet_run_adapter(armulet_cpu_t *cpu, const varmulet_asm_hooks_t *hooks); 12 | 13 | extern varmulet_asm_hooks_t varmulet_nsboot_asm_hooks; 14 | extern varmulet_asm_hooks_t varmulet_preboot_asm_hooks; 15 | extern asm_hook_t varmulet_hooks_default_exc_and_call_return[2]; 16 | 17 | extern const void bootrom_undefined32_canary_check; 18 | -------------------------------------------------------------------------------- /src/mini_printf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(mini_printf INTERFACE) 2 | 3 | target_sources(mini_printf INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/mini_printf.c 5 | ) 6 | 7 | if (PRINTF_TO_TB) 8 | target_compile_definitions(mini_printf INTERFACE PRINTF_TO_TB=1) 9 | endif() 10 | 11 | target_include_directories(mini_printf INTERFACE ${CMAKE_CURRENT_LIST_DIR}) -------------------------------------------------------------------------------- /src/mini_printf/mini_printf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #if MINI_PRINTF 14 | 15 | #include "hardware/structs/uart.h" 16 | #include 17 | #include 18 | void mini_printf_init(void); 19 | void mini_printf_flush(void); 20 | #define printf mini_printf 21 | #define puts mini_puts 22 | void __noinline mini_printf(const char *fmt, ...); 23 | int __noinline mini_puts(const char *str); 24 | 25 | #else 26 | 27 | #define mini_printf_init() ((void)0) 28 | #define mini_printf_flush() ((void)0) 29 | #define printf(fmt...) ((void)0) 30 | #define puts(s) ((void)0) 31 | 32 | #endif 33 | 34 | #ifdef __cplusplus 35 | } 36 | #endif 37 | -------------------------------------------------------------------------------- /src/nsboot/fat_dir_entries.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | // NOTE THIS IS IN A SEPARATE HEADER AS IT IS COMPRESSED WHEN USING COMPRESS_TEXT 10 | #include 11 | #include 12 | 13 | // Fri, 05 Sep 2008 16:20:51 14 | #define RASPBERRY_PI_TIME_FRAC 100 15 | #define RASPBERRY_PI_TIME ((16u << 11u) | (20u << 5u) | (51u >> 1u)) 16 | static_assert(RASPBERRY_PI_TIME == 0x8299, ""); 17 | #define RASPBERRY_PI_DATE ((28u << 9u) | (9u << 5u) | (5u)) 18 | static_assert(RASPBERRY_PI_DATE == 0x3925, ""); 19 | 20 | #define ATTR_READONLY 0x01u 21 | #define ATTR_HIDDEN 0x02u 22 | #define ATTR_SYSTEM 0x04u 23 | #define ATTR_VOLUME_LABEL 0x08u 24 | #define ATTR_DIR 0x10u 25 | #define ATTR_ARCHIVE 0x20u 26 | 27 | 28 | struct dir_entry { 29 | uint8_t name[11]; 30 | uint8_t attr; 31 | uint8_t reserved; 32 | uint8_t creation_time_frac; 33 | uint16_t creation_time; 34 | uint16_t creation_date; 35 | uint16_t last_access_date; 36 | uint16_t cluster_hi; 37 | uint16_t last_modified_time; 38 | uint16_t last_modified_date; 39 | uint16_t cluster_lo; 40 | uint32_t size; 41 | }; 42 | static_assert(sizeof(struct dir_entry) == 32, ""); 43 | 44 | #if !COMPRESS_TEXT 45 | // note we fill in values whose individual bytes are all <0x80 (for poor_mans_text_decompress) 46 | static struct dir_entry fat_dir_entries[3] = { 47 | { 48 | .name = " ", // note this is blanked with spaces so we can overwrite it with possibly shorter volume label 49 | .creation_time_frac = RASPBERRY_PI_TIME_FRAC, 50 | .creation_date = RASPBERRY_PI_DATE, 51 | .last_modified_date = RASPBERRY_PI_DATE, 52 | .attr = ATTR_VOLUME_LABEL | ATTR_ARCHIVE, 53 | }, 54 | { 55 | .name = "INDEX HTM", 56 | .creation_time_frac = RASPBERRY_PI_TIME_FRAC, 57 | .creation_date = RASPBERRY_PI_DATE, 58 | .last_modified_date = RASPBERRY_PI_DATE, 59 | .cluster_lo = 2, 60 | .attr = ATTR_READONLY | ATTR_ARCHIVE, 61 | }, 62 | { 63 | .name = "INFO_UF2TXT", 64 | .creation_time_frac = RASPBERRY_PI_TIME_FRAC, 65 | .creation_date = RASPBERRY_PI_DATE, 66 | .last_modified_date = RASPBERRY_PI_DATE, 67 | .cluster_lo = 3, 68 | .attr = ATTR_READONLY | ATTR_ARCHIVE, 69 | }, 70 | }; 71 | #endif 72 | -------------------------------------------------------------------------------- /src/nsboot/generator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | PROJECT(generate C) 2 | set(CMAKE_C_STANDARD 11) 3 | #set (CMAKE_C_FLAGS "--std=gnu99") 4 | add_executable(generate main.c) 5 | -------------------------------------------------------------------------------- /src/nsboot/index_html.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | // NOTE THIS IS IN A SEPARATE HEADER AS IT IS COMPRESSED WHEN USING COMPRESS_TEXT 10 | #include 11 | #include 12 | 13 | // top bit is "unicode" flag, so max length is 127 14 | #define MAIL_MERGE_MAX_ITEM_LENGTH 127 15 | 16 | #if !COMPRESS_TEXT 17 | // because of mail merge we no longer have raw source for INDEX.HTM 18 | static const char *index_html_template = "Redirecting to "; 19 | #endif 20 | 21 | #define INDEX_HTML_INSERT_COUNT 3 22 | 23 | // count, length of template, reverse order list of run lengths between before inserts 24 | const uint8_t index_html_metadata[INDEX_HTML_INSERT_COUNT + 2] = {INDEX_HTML_INSERT_COUNT, 116, 18, 2, 41} ; 25 | -------------------------------------------------------------------------------- /src/nsboot/info_uf2_txt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | // NOTE THIS IS IN A SEPARATE HEADER AS IT IS COMPRESSED WHEN USING COMPRESS_TEXT 10 | #include 11 | #include 12 | 13 | #if !COMPRESS_TEXT 14 | // because of mail merge we no longer have raw source for INDEX.HTM 15 | static const char *info_uf2_txt_template = 16 | "UF2 Bootloader v1.0\n" 17 | "Model: \n" 18 | "Board-ID: \n"; 19 | #endif 20 | 21 | #define INFO_UF2_TXT_INSERT_COUNT 2 22 | 23 | // count, length of template, reverse order list of run lengths between before inserts 24 | const uint8_t info_uf2_txt_metadata[INFO_UF2_TXT_INSERT_COUNT + 2] = {INFO_UF2_TXT_INSERT_COUNT, 39, 1, 11} ; 25 | -------------------------------------------------------------------------------- /src/nsboot/ms_os_20_descriptor_set_headers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | // NOTE THIS IS IN A SEPARATE HEADER AS IT IS COMPRESSED WHEN USING COMPRESS_TEXT 10 | #include 11 | 12 | #define LE32(a,b,c,d) ((a)|((b)<<8)|((c)<<16)|((d)<<24)) 13 | struct five_words { 14 | uint32_t a, b, c, d, e; 15 | }; 16 | #if !COMPRESS_TEXT 17 | static const struct five_words ms_os_20_descriptor_set_headers = { 18 | LE32(0x0A, 0x00, // Descriptor size (10 bytes) 19 | 0x00, 0x00), // MS OS 2.0 descriptor set header 20 | LE32(0x00, 0x00, 0x03, 0x06), // Windows version (8.1) (0x06030000) 21 | LE32(0x00, 0x00, //lsb_hword(MS_OS_20_DESCRIPTOR_SIZE), // Size, MS OS 2.0 descriptor set (158 bytes) 22 | 23 | // Function subset descriptor 24 | 0x08, 0x00), // Descriptor size (8 bytes) 25 | LE32(0x02, 0x00, // Function subset header 26 | 0x01, 0x00), // itf_num, reserved 27 | LE32(0x9c, 0x00, // size 28 | 0x00, 0x00), // padding 29 | }; 30 | #endif 31 | -------------------------------------------------------------------------------- /src/nsboot/native/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(nsboot_native INTERFACE) 2 | target_sources(nsboot_native INTERFACE 3 | ${CMAKE_CURRENT_LIST_DIR}/usb_device.c 4 | ${CMAKE_CURRENT_LIST_DIR}/usb_stream_helper.c 5 | ) 6 | target_include_directories(nsboot_native INTERFACE 7 | ${CMAKE_CURRENT_LIST_DIR} 8 | ) 9 | target_link_libraries(nsboot_native INTERFACE 10 | nsboot_config) -------------------------------------------------------------------------------- /src/nsboot/native/nsboot.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pico.h" 10 | #include "bootrom_common.h" 11 | #ifndef __riscv 12 | #include "p16.h" 13 | #endif 14 | 15 | #ifndef __ASSEMBLER__ 16 | #include "pico/bootrom_constants.h" 17 | #include "boot/picoboot.h" 18 | #include "bootrom_assert.h" 19 | #include "native_exports.h" 20 | 21 | #if !USE_16BIT_POINTERS 22 | #define RP2350_STRING "RP2350" 23 | #define RPI_STRING "RPI" 24 | #define ONE_STRING "1" 25 | #else 26 | #define RP2350_STRING P16_TYPED(const char *, _str_rp2350) 27 | #define RPI_STRING P16_TYPED(const char *, _str_rpi_1) 28 | #define ONE_STRING (P16_TYPED(const char *, _str_rpi_1) + 4) 29 | #endif 30 | 31 | #if USE_PICOBOOT 32 | // allow nsboot to not include all interfaces 33 | #define NSBOOT_WITH_SUBSET_OF_INTERFACES 1 34 | // use a fixed number of interfaces to save code 35 | #define USB_FIXED_INTERFACE_COUNT 2 36 | #else 37 | #define USB_FIXED_INTERFACE_COUNT 1 38 | #endif 39 | 40 | void poor_mans_text_decompress(const uint8_t *src_end, uint32_t size, uint8_t *dest); 41 | // is a bool, but making it clear it isn't 1 or 0 42 | uint rebooting(void); 43 | 44 | #if USE_BOOTROM_GPIO 45 | void gpio_setup(void); 46 | void nsboot_set_gpio(bool on); 47 | #endif 48 | 49 | #include "mini_printf.h" 50 | 51 | #if 1 52 | #define usb_trace(format, ...) ((void)0) 53 | #if MINI_PRINTF 54 | //#define usb_debug printf 55 | #define usb_debug(format, ...) ((void)0) 56 | //#define usb_warn printf 57 | #define usb_warn(format, ...) ((void)0) 58 | //#define uf2_debug(format, ...) ((void)0) 59 | #define uf2_debug printf 60 | //#define uf2_info(format, ...) ((void)0) 61 | #define uf2_info printf 62 | #else 63 | #define usb_debug(format, ...) ((void)0) 64 | #define usb_warn(format, ...) ((void)0) 65 | #define uf2_debug(format, ...) ((void)0) 66 | #define uf2_info(format, ...) ((void)0) 67 | #endif 68 | #define usb_panic(format, ...) __breakpoint() 69 | #else 70 | #include "debug.h" 71 | extern void uart_init(int i, int rate, int hwflow); 72 | extern void panic(const char *fmt, ...); 73 | extern int printf(const char *fmt, ...); 74 | extern int puts(const char *str); 75 | #define usb_panic(format,args...) panic(format, ## args) 76 | #define usb_warn(format,args...) ({printf("WARNING: "); printf(format, ## args); }) 77 | #if false && !defined(NDEBUG) 78 | #define usb_debug(format,args...) printf(format, ## args) 79 | #else 80 | #define usb_debug(format,...) ((void)0) 81 | #endif 82 | #if false && !defined(NDEBUG) 83 | #define usb_trace(format,args...) printf(format, ## args) 84 | #else 85 | #define usb_trace(format,...) ((void)0) 86 | #endif 87 | #if true && !defined(NDEBUG) 88 | #define uf2_debug(format,args...) printf(format, ## args) 89 | #else 90 | #define uf2_debug(format,...) ((void)0) 91 | #endif 92 | #define ctz32 __builtin_ctz 93 | #endif 94 | 95 | /** 96 | * will copy a possible overriden (white label) in OTP string into a buffer. 97 | * 98 | * Note that the buffer is always hword aligned, and that always an integer number of hwords is overwritten, 99 | * so if unicode_flag_and_buf_len_hwords is 11 (0:11 meaning 11 non unicode characters), up to 12 bytes will be overwritten, but 100 | * the return code will be no more than 11. 101 | * 102 | * @param buf hword aligned buffer 103 | * @param unicode_flag_and_buf_len_hwords 1:7 unicode_allowed:max_chars (size of char is 2 for unicode one for ASCII) 104 | * @param str_def_indext index of string descriptor from the white label structure 105 | * @param default_value the default value in ascii 106 | * @return 1:7 is_unicode:length in characters 107 | */ 108 | uint white_label_copy_string(aligned2_uint8_t *buf, uint unicode_flag_and_buf_len_hwords, uint str_def_index, const char *default_value); 109 | uint white_label_copy_ascii(uint8_t *buf, uint max_len_bytes, uint str_def_index, const char *default_value); 110 | #define wl_is_unicode(unicode_flags_and_char_count) ((unicode_flags_and_char_count) >= 128) 111 | 112 | char * write_msb_hex_chars(char *dest, uint32_t val, int count); 113 | 114 | static inline otp_cmd_t row_read_ecc_cmd(uint16_t row) { 115 | otp_cmd_t cmd = {.flags = ((uint) row << OTP_CMD_ROW_LSB) | OTP_CMD_ECC_BITS}; 116 | return cmd; 117 | } 118 | 119 | #endif -------------------------------------------------------------------------------- /src/nsboot/native/nsboot_arch_adapter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pico.h" 10 | 11 | struct armv6m_pointers { 12 | struct usb_endpoint **endpoints; 13 | struct usb_endpoint *usb_control_endpoints; // in followed by out 14 | void (* usb_stream_packet_handler)(struct usb_endpoint *ep); 15 | }; 16 | 17 | void varm_callable(native_nsboot_init)(const struct armv6m_pointers *p); 18 | void varm_callable(native_usb_irq_enable)(void); // when we want to turn on the IRQ 19 | #if FEATURE_RISCV_USB_BOOT 20 | void varm_callable(native_usb_packet_done)(struct usb_endpoint *ep); 21 | #else 22 | void native_usb_packet_done(struct usb_endpoint *ep); 23 | #define varm_to_native_usb_packet_done native_usb_packet_done 24 | #endif 25 | 26 | struct usb_endpoint **native_usb_get_endpoints(void); 27 | // array of in then out 28 | struct usb_endpoint *native_usb_get_control_endpoints(void); 29 | #define CONTROL_IN_ENDPOINT_INDEX 0 30 | #define CONTROL_OUT_ENDPOINT_INDEX 1 31 | #define native_usb_get_control_in_endpoint() (&native_usb_get_control_endpoints()[CONTROL_IN_ENDPOINT_INDEX]) 32 | #define native_usb_get_control_out_endpoint() (&native_usb_get_control_endpoints()[CONTROL_OUT_ENDPOINT_INDEX]) 33 | 34 | #ifdef __riscv 35 | // as we don't care about the values passed in unused r0 or r1 to an ARM function, 36 | // call_armv6m_0 and call_armv6m_1 are just aliases for call_armv6m_2 37 | uint32_t call_armv6m_0(uintptr_t address); 38 | uint32_t call_armv6m_1(uintptr_t address, uint32_t p1); 39 | uint32_t call_armv6m_2(uintptr_t address, uint32_t p1, uint32_t p2); 40 | #define call_arm_fp0(f) call_armv6m_0((uintptr_t) f) 41 | #define call_arm_fp1(f, p) call_armv6m_1((uintptr_t) f, (uintptr_t) p) 42 | #define call_arm_fp2(f, p1, p2) call_armv6m_2((uintptr_t) f, (uintptr_t) p1, (uintptr_t) p2) 43 | extern struct armv6m_pointers armv6m_pointers; 44 | 45 | static __force_inline struct usb_endpoint **arch_usb_get_endpoints(void) { 46 | return armv6m_pointers.endpoints; 47 | } 48 | 49 | static __force_inline struct usb_endpoint *arch_usb_get_control_endpoints(void) { 50 | return armv6m_pointers.usb_control_endpoints; 51 | } 52 | 53 | static __force_inline struct usb_endpoint *arch_usb_get_control_in_endpoint(void) { 54 | return &armv6m_pointers.usb_control_endpoints[0]; 55 | } 56 | 57 | static __force_inline struct usb_endpoint *arch_usb_get_control_out_endpoint(void) { 58 | return &armv6m_pointers.usb_control_endpoints[0]; 59 | } 60 | #else // arm 61 | #define call_arm_fp0(f) f() 62 | #define call_arm_fp1(f, p) f(p) 63 | #define call_arm_fp2(f, p1, p2) f(p1, p2) 64 | 65 | #define arch_usb_handle_buffer native_usb_handle_buffer 66 | #define arch_usb_get_endpoints native_usb_get_endpoints 67 | #define arch_usb_get_control_endpoints native_usb_get_control_endpoints 68 | #define arch_usb_get_control_in_endpoint native_usb_get_control_in_endpoint 69 | #define arch_usb_get_control_out_endpoint native_usb_get_control_out_endpoint 70 | #endif 71 | 72 | #ifdef __riscv 73 | #define arm_or_riscv_impl(x) riscv_ ## x 74 | #else 75 | #define arm_or_riscv_impl(x) varm_ ## x 76 | #endif 77 | -------------------------------------------------------------------------------- /src/nsboot/native/usb_common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "hardware/structs/usb.h" 10 | 11 | // bmRequestType bit definitions 12 | #define USB_REQ_TYPE_STANDARD 0x00u 13 | #define USB_REQ_TYPE_TYPE_MASK 0x60u 14 | #define USB_REQ_TYPE_TYPE_CLASS 0x20u 15 | #define USB_REQ_TYPE_TYPE_VENDOR 0x40u 16 | 17 | #define USB_REQ_TYPE_RECIPIENT_MASK 0x1fu 18 | #define USB_REQ_TYPE_RECIPIENT_DEVICE 0x00u 19 | #define USB_REQ_TYPE_RECIPIENT_INTERFACE 0x01u 20 | #define USB_REQ_TYPE_RECIPIENT_ENDPOINT 0x02u 21 | 22 | #define USB_DIR_OUT 0x00u 23 | #define USB_DIR_IN 0x80u 24 | 25 | #define USB_TRANSFER_TYPE_CONTROL 0x0 26 | #define USB_TRANSFER_TYPE_ISOCHRONOUS 0x1 27 | #define USB_TRANSFER_TYPE_BULK 0x2 28 | #define USB_TRANSFER_TYPE_INTERRUPT 0x3 29 | #define USB_TRANSFER_TYPE_BITS 0x3 30 | 31 | // Descriptor types 32 | #define USB_DT_DEVICE 0x01 33 | #define USB_DT_CONFIG 0x02 34 | #define USB_DT_STRING 0x03 35 | #define USB_DT_INTERFACE 0x04 36 | #define USB_DT_ENDPOINT 0x05 37 | #define USB_DT_BOS 0x0f 38 | 39 | #define USB_REQUEST_GET_STATUS 0x0 40 | #define USB_REQUEST_CLEAR_FEATURE 0x01 41 | #define USB_REQUEST_SET_FEATURE 0x03 42 | #define USB_REQUEST_SET_ADDRESS 0x05 43 | #define USB_REQUEST_GET_DESCRIPTOR 0x06 44 | #define USB_REQUEST_SET_DESCRIPTOR 0x07 45 | #define USB_REQUEST_GET_CONFIGURATION 0x08 46 | #define USB_REQUEST_SET_CONFIGURATION 0x09 47 | #define USB_REQUEST_GET_INTERFACE 0x0a 48 | #define USB_REQUEST_SET_INTERFACE 0x0b 49 | #define USB_REQUEST_SYNC_FRAME 0x0c 50 | 51 | #define USB_REQUEST_MSC_GET_MAX_LUN 0xfe 52 | #define USB_REQUEST_MSC_RESET 0xff 53 | 54 | #define USB_FEAT_ENDPOINT_HALT 0x00 55 | #define USB_FEAT_DEVICE_REMOTE_WAKEUP 0x01 56 | #define USB_FEAT_TEST_MODE 0x02 57 | 58 | #define USB_DESCRIPTOR_TYPE_ENDPOINT 0x05 59 | 60 | struct usb_setup_packet { 61 | uint8_t bmRequestType; 62 | uint8_t bRequest; 63 | uint16_t wValue; 64 | uint16_t wIndex; 65 | uint16_t wLength; 66 | } __packed; 67 | 68 | struct usb_descriptor { 69 | uint8_t bLength; 70 | uint8_t bDescriptorType; 71 | }; 72 | 73 | struct usb_device_descriptor { 74 | uint8_t bLength; 75 | uint8_t bDescriptorType; 76 | uint16_t bcdUSB; 77 | uint8_t bDeviceClass; 78 | uint8_t bDeviceSubClass; 79 | uint8_t bDeviceProtocol; 80 | uint8_t bMaxPacketSize0; 81 | uint16_t idVendor; 82 | uint16_t idProduct; 83 | uint16_t bcdDevice; 84 | uint8_t iManufacturer; 85 | uint8_t iProduct; 86 | uint8_t iSerialNumber; 87 | uint8_t bNumConfigurations; 88 | } __packed; 89 | 90 | struct usb_configuration_descriptor { 91 | uint8_t bLength; 92 | uint8_t bDescriptorType; 93 | uint16_t wTotalLength; 94 | uint8_t bNumInterfaces; 95 | uint8_t bConfigurationValue; 96 | uint8_t iConfiguration; 97 | uint8_t bmAttributes; 98 | uint8_t bMaxPower; 99 | } __packed; 100 | 101 | struct usb_interface_descriptor { 102 | uint8_t bLength; 103 | uint8_t bDescriptorType; 104 | uint8_t bInterfaceNumber; 105 | uint8_t bAlternateSetting; 106 | uint8_t bNumEndpoints; 107 | uint8_t bInterfaceClass; 108 | uint8_t bInterfaceSubClass; 109 | uint8_t bInterfaceProtocol; 110 | uint8_t iInterface; 111 | } __packed; 112 | 113 | struct usb_endpoint_descriptor { 114 | uint8_t bLength; 115 | uint8_t bDescriptorType; 116 | uint8_t bEndpointAddress; 117 | uint8_t bmAttributes; 118 | uint16_t wMaxPacketSize; 119 | uint8_t bInterval; 120 | } __packed; 121 | 122 | struct usb_endpoint_descriptor_long { 123 | uint8_t bLength; 124 | uint8_t bDescriptorType; 125 | uint8_t bEndpointAddress; 126 | uint8_t bmAttributes; 127 | uint16_t wMaxPacketSize; 128 | uint8_t bInterval; 129 | uint8_t bRefresh; 130 | uint8_t bSyncAddr; 131 | } __attribute__((packed)); 132 | -------------------------------------------------------------------------------- /src/nsboot/native/usb_device_private.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pico.h" 10 | #include "usb_common.h" 11 | 12 | #if 1 13 | #define STRUCT_CHECK(x) static_assert(x, "") 14 | #endif 15 | struct usb_transfer { 16 | #if GENERAL_SIZE_HACKS 17 | union { 18 | struct { 19 | #endif 20 | // number of packets which require usb_packet_done() 21 | bool outstanding_packet; 22 | // received a packet which we couldn't deliver because there was one outstanding 23 | bool packet_queued; 24 | bool started; 25 | bool completed; 26 | #if GENERAL_SIZE_HACKS 27 | }; 28 | uint32_t all_flags; 29 | }; 30 | #endif 31 | // prototype 32 | usb_transfer_type_ref_t type; 33 | usb_transfer_completed_func on_complete; 34 | // total number of buffers (packets) that still need to be handed over to the hardware 35 | // during the remaining course of the transfer (with data for IN, empty for data for out) 36 | uint32_t remaining_packets_to_submit; 37 | // total number of buffers when we will expect to receive IRQ/handle_buffer for during 38 | // the remaining course of the transfer 39 | uint32_t remaining_packets_to_handle; 40 | }; 41 | 42 | struct usb_interface { 43 | const struct usb_interface_descriptor *descriptor; 44 | #if !USB_NO_INTERFACE_ENDPOINTS_MEMBER 45 | struct usb_endpoint *const *endpoints; 46 | uint8_t endpoint_count; 47 | #endif 48 | bool (*setup_request_handler)(struct usb_interface *interface, struct usb_setup_packet *setup); 49 | #if !USB_NO_INTERFACE_ALTERNATES 50 | bool (*set_alternate_handler)(struct usb_interface *interface, uint alt); 51 | uint8_t alt; 52 | #endif 53 | }; 54 | 55 | struct usb_configuration { 56 | const struct usb_configuration_descriptor *descriptor; 57 | struct usb_interface *const *interfaces; 58 | #ifndef USB_FIXED_INTERFACE_COUNT 59 | uint8_t interface_count; 60 | #endif 61 | }; 62 | #ifdef USB_FIXED_INTERFACE_COUNT 63 | #define _usb_interface_count(config) USB_FIXED_INTERFACE_COUNT 64 | #else 65 | #define _usb_interface_count(config) config->interface_count 66 | #endif 67 | 68 | struct usb_device { 69 | #if !GENERAL_SIZE_HACKS 70 | uint8_t current_address; // 0 if unaddressed 71 | #endif 72 | uint8_t current_config_num; // 0 if unconfigured 73 | uint8_t pending_address; // address to set on completion of SET_ADDRESS CSW 74 | uint16_t next_buffer_offset; 75 | const struct usb_device_descriptor *descriptor; 76 | #if !USB_NO_DEVICE_SETUP_HANDLER 77 | bool (*setup_request_handler)(struct usb_device *dev, struct usb_setup_packet *setup); 78 | #endif 79 | #if !USB_USE_GLOBAL_DEVICE_ON_CONFIGURE_CB 80 | void (*on_configure)(struct usb_device *dev, bool configured); 81 | #endif 82 | #if !USB_USE_GLOBAL_DEVICE_GET_DESCRIPTOR_STRING_CB 83 | const char *(*get_descriptor_string)(uint index); 84 | #endif 85 | // only support one config for now 86 | struct usb_configuration config; 87 | // bool started; 88 | }; 89 | 90 | enum usb_halt_state { 91 | HS_NONE = 0, 92 | HS_NON_HALT_STALL = 1, // just stalled 93 | HS_HALTED = 2, // halted or command halted 94 | HS_HALTED_ON_CONDITION = 3 // halted that cannot be simply cleared by CLEAR_FEATURE 95 | }; 96 | 97 | struct usb_endpoint { 98 | union { 99 | struct { 100 | uint8_t num; 101 | bool double_buffered; 102 | bool in; 103 | uint8_t buffer_bit_index; 104 | }; 105 | uint32_t init_word; 106 | }; 107 | uint8_t next_pid; 108 | uint8_t owned_buffer_count; 109 | union { 110 | struct { 111 | uint8_t current_take_buffer; 112 | uint8_t current_give_buffer; 113 | }; 114 | uint8_t current_buffers[2]; 115 | }; 116 | uint8_t halt_state; 117 | bool first_buffer_after_reset; 118 | const struct usb_endpoint_descriptor *descriptor; 119 | struct usb_transfer *default_transfer; 120 | struct usb_transfer *current_transfer; 121 | struct usb_transfer *chain_transfer; 122 | void (*on_stall_change)(struct usb_endpoint *ep); 123 | #if !USB_NO_ENDPOINT_SETUP_HANDLER 124 | bool (*setup_request_handler)(struct usb_endpoint *ep, struct usb_setup_packet *setup); 125 | #endif 126 | uint16_t dpram_buffer_offset; 127 | #if !USB_ALL_ENDPOINTS_MAX_PACKET_SIZE 128 | uint16_t buffer_size; // for an individual buffer 129 | #endif 130 | struct usb_buffer current_hw_buffer; 131 | #if !USB_BULK_ONLY_EP1_THRU_16 132 | uint16_t buffer_stride; 133 | #endif 134 | }; 135 | STRUCT_CHECK(sizeof(struct usb_endpoint) == 44); 136 | STRUCT_CHECK(offsetof(struct usb_endpoint, next_pid) == 4); 137 | static inline uint usb_endpoint_buffer_size(struct usb_endpoint *ep) { 138 | #if USB_ALL_ENDPOINTS_MAX_PACKET_SIZE 139 | (void)ep; 140 | return USB_ALL_ENDPOINTS_MAX_PACKET_SIZE; 141 | #else 142 | return ep->buffer_size; 143 | #endif 144 | } 145 | static inline uint usb_endpoint_number(struct usb_endpoint *ep) { 146 | bootrom_assert(USB, ep); 147 | return ep->descriptor ? ep->descriptor->bEndpointAddress & 0xfu : 0; 148 | } 149 | 150 | static inline bool usb_is_endpoint_stalled(struct usb_endpoint *endpoint) { 151 | return endpoint->halt_state != HS_NONE; 152 | } 153 | 154 | const char *usb_endpoint_dir_string(struct usb_endpoint *ep); 155 | 156 | void usb_transfer_current_packet_only(struct usb_endpoint *ep); 157 | 158 | #if !USB_ALL_ENDPOINTS_MAX_PACKET_SIZE 159 | #define ep_buffer_size(ep) ((ep)->buffer_size) 160 | #else 161 | #define ep_buffer_size(ep) USB_ALL_ENDPOINTS_MAX_PACKET_SIZE 162 | #endif 163 | -------------------------------------------------------------------------------- /src/nsboot/native/usb_stream_helper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "usb_device.h" 10 | 11 | struct usb_stream_transfer { 12 | struct usb_transfer core; 13 | uint32_t offset; // offset within the stream 14 | uint32_t transfer_length; 15 | uint32_t chunk_size; 16 | uint8_t *chunk_buffer; 17 | struct usb_endpoint *ep; 18 | const struct usb_stream_transfer_funcs *funcs; 19 | #ifndef NDEBUG 20 | bool packet_handler_complete_expected; 21 | #endif 22 | }; 23 | 24 | typedef void (*stream_on_packet_complete_function)(__removed_for_space_only(struct usb_stream_transfer *transfer)); 25 | typedef bool (*stream_on_chunk_function)(uint32_t chunk_len 26 | __comma_removed_for_space(struct usb_stream_transfer *transfer)); 27 | 28 | #if !USB_USE_TINY_STREAM_TRANSFER_FUNCS 29 | struct usb_stream_transfer_funcs { 30 | stream_on_packet_complete_function on_packet_complete; 31 | // returns whether processing async 32 | stream_on_chunk_function on_chunk; 33 | }; 34 | #define usb_stream_transfer_on_packet_complete(tf) ((tf)->on_packet_complete) 35 | #define usb_stream_transfer_on_chunk(tf) ((tf)->on_chunk) 36 | #else 37 | struct usb_stream_transfer_funcs { 38 | uint16_t on_packet_complete; 39 | // returns whether processing async 40 | uint16_t on_chunk; 41 | }; 42 | #define usb_stream_transfer_on_packet_complete(tf) ((stream_on_packet_complete_function)(uintptr_t)((tf)->on_packet_complete)) 43 | #define usb_stream_transfer_on_chunk(tf) ((stream_on_chunk_function)(uintptr_t)((tf)->on_chunk)) 44 | #endif 45 | 46 | void usb_stream_setup_transfer(struct usb_stream_transfer *transfer, const struct usb_stream_transfer_funcs *funcs, 47 | uint8_t *chunk_buffer, uint32_t chunk_size, uint32_t transfer_length, 48 | usb_transfer_completed_func on_complete); 49 | 50 | void usb_stream_chunk_done(struct usb_stream_transfer *transfer); 51 | 52 | void native_usb_stream_packet_handler(struct usb_endpoint *ep); // public so we can call it directly in RISC-V mode 53 | 54 | #define usb_stream_noop_on_packet_complete ((stream_on_packet_complete_function)varm_noop) 55 | #define usb_stream_noop_on_chunk needs_work 56 | -------------------------------------------------------------------------------- /src/nsboot/nsboot.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "pico.h" 8 | #include "hardware/structs/nvic.h" 9 | #include "nsboot_async_task.h" 10 | #include "nsboot_usb_client.h" 11 | #include "usb_stream_helper.h" 12 | #include "hardware/regs/intctrl.h" 13 | #include "hardware/structs/scb.h" 14 | #if USE_BOOTROM_GPIO 15 | #include "hardware/structs/iobank0.h" 16 | #include "hardware/structs/padsbank0.h" 17 | #include "hardware/gpio.h" 18 | #include "boot/picoboot.h" 19 | #endif 20 | #include "nsboot_secure_calls.h" 21 | #include "nsboot_config.h" 22 | #include "nsboot_arch_adapter.h" 23 | #include "mini_printf.h" 24 | #if MINI_PRINTF 25 | #include "hardware/structs/resets.h" 26 | #endif 27 | 28 | #if defined(__ARM_ARCH_8M_MAIN__) || !defined(__ARM_ARCH_8M_BASE__) 29 | //#error this must be compiled with armv8m-base 30 | #endif 31 | 32 | // note this is at address NSBOOT_RAM_START (and assumed to be!) 33 | __attribute__((section(".bss.first"))) nsboot_config_t nsboot_config_inst; 34 | 35 | #if FEATURE_UART_BOOT_SELECTABLE_INSTANCE 36 | __attribute__((noreturn)) void nsboot_uart_client(uint inst); 37 | #else 38 | __attribute__((noreturn)) void nsboot_uart_client(void); 39 | #endif 40 | 41 | //__noinline __attribute__((noreturn)) void nsboot_i2c_client(uint inst); 42 | 43 | static __noinline __attribute__((noreturn)) void _nsboot_usb_client(void) { 44 | nsboot_usb_device_init(nsboot_config->bootsel_flags); 45 | 46 | #if 0 47 | // worker to run tasks on this thread (never returns); Note: USB code is IRQ driven 48 | // this thunk switches stack into USB DPRAM then calls async_task_worker 49 | gpio_init(4); 50 | gpio_set_dir(4, 1); 51 | while(true) { 52 | static int foo; 53 | foo = !foo; 54 | gpio_put(4, foo); 55 | printf("BIP %d\n", foo); 56 | busy_wait_ms(200); 57 | } 58 | #else 59 | async_task_worker(); 60 | // nsboot_client_stack_switch_thunk(); 61 | #endif 62 | } 63 | 64 | typedef void (*irq_handler_t)(void); 65 | bool __used rebooting_flag; 66 | 67 | static inline irq_handler_t *get_vtable(void) { 68 | return (irq_handler_t *) scb_hw->vtor; 69 | } 70 | 71 | static void native_interrupt_enable(uint32_t num, bool enabled) { 72 | if (enabled) { 73 | #if !GENERAL_SIZE_HACKS 74 | // We should handle an initial spurious interrupt with no USB status 75 | // bits set, so don't bother to clear the pending latch 76 | nvic_hw->icpr[num / 32] = 1u << (num % 32); 77 | #endif 78 | nvic_hw->iser[num / 32] = 1u << (num % 32); 79 | } else { 80 | nvic_hw->icer[num / 32] = 1u << (num % 32); 81 | } 82 | } 83 | 84 | void __attribute__((used)) native_usb_irq_enable(void) { 85 | native_interrupt_enable(USBCTRL_IRQ, true); 86 | } 87 | 88 | extern struct usb_endpoint control_endpoints[2]; 89 | extern struct usb_endpoint *non_control_endpoints[USB_MAX_ENDPOINTS]; 90 | // ARM pointers are the real compiled in versions (so no arch_) 91 | const struct armv6m_pointers nsboot_init_armv6m_pointers = { 92 | // .endpoints = native_usb_get_endpoints(), 93 | .endpoints = non_control_endpoints, 94 | // .usb_control_endpoints = native_usb_get_control_endpoints(), 95 | .usb_control_endpoints = &control_endpoints[0], 96 | .usb_stream_packet_handler = native_usb_stream_packet_handler 97 | }; 98 | 99 | void __attribute__((noreturn)) go(void) { 100 | #if MINI_PRINTF 101 | //mini_printf_init(); 102 | #endif 103 | //if ((watchdog_hw->scratch[6] ^ SANDBOX_MAGIC) == watchdog_hw->scratch[7] && watchdog_hw->scratch[6] == 0x1) { 104 | varm_to_native_nsboot_init(P16_D(nsboot_init_armv6m_pointers)); 105 | 106 | #if FEATURE_UART_BOOT_SELECTABLE_INSTANCE 107 | uint inst = nsboot_config_inst.serial_mode_and_inst >> 4; 108 | uint mode = nsboot_config_inst.serial_mode_and_inst & 0xf; 109 | #else 110 | __unused uint inst = 0; 111 | uint mode = nsboot_config_inst.serial_mode_and_inst; 112 | #endif 113 | if (mode == BOOTSEL_MODE_UART) { 114 | #if MINI_PRINTF 115 | printf("Resetting UART now for UART boot\n"); 116 | mini_printf_flush(); 117 | // UART would normally be reset during preamble in varm_nsboot.c, but 118 | // we deferred it to this point to keep printf as long as possible. 119 | uint32_t mask = RESETS_RESET_UART0_BITS << inst; 120 | hw_set_bits(&resets_hw->reset, mask); 121 | hw_clear_bits(&resets_hw->reset, mask); 122 | while (!(resets_hw->reset_done & mask)) 123 | ; 124 | #endif 125 | #if FEATURE_UART_BOOT_SELECTABLE_INSTANCE 126 | nsboot_uart_client(inst); 127 | #else 128 | nsboot_uart_client(); 129 | #endif 130 | } 131 | _nsboot_usb_client(); 132 | } 133 | 134 | #if USE_BOOTROM_GPIO 135 | 136 | void gpio_setup(void) { 137 | if (nsboot_config->usb_activity_pin >= 0) { 138 | uint gpio = (uint)nsboot_config->usb_activity_pin; 139 | 140 | gpio_set_dir(gpio, 1); 141 | // Set input enable on, output disable off 142 | hw_write_masked(&padsbank0_hw->io[gpio], 143 | PADS_BANK0_GPIO0_IE_BITS, 144 | PADS_BANK0_GPIO0_IE_BITS | PADS_BANK0_GPIO0_OD_BITS 145 | ); 146 | // Zero all fields apart from fsel; we want this IO to do what the peripheral tells it. 147 | // This doesn't affect e.g. pullup/pulldown, as these are in pad controls. 148 | uint ctrl = GPIO_FUNC_SIO << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB; 149 | if (nsboot_config->bootsel_flags & BOOTSEL_FLAG_GPIO_PIN_ACTIVE_LOW) { 150 | ctrl |= IO_BANK0_GPIO0_CTRL_OUTOVER_VALUE_INVERT << IO_BANK0_GPIO0_CTRL_OUTOVER_LSB; 151 | } 152 | iobank0_hw->io[gpio].ctrl = ctrl; 153 | // Remove pad isolation now that the correct peripheral is in control of the pad 154 | hw_clear_bits(&padsbank0_hw->io[gpio], PADS_BANK0_GPIO0_ISO_BITS); 155 | } 156 | #ifndef NDEBUG 157 | // Set to RIO for debug 158 | for (int i = 19; i < 23; i++) { 159 | gpio_init(i); 160 | gpio_set_dir_out_masked(1 << i); 161 | } 162 | #endif 163 | } 164 | 165 | #endif 166 | 167 | void __attribute__((noreturn)) bootrom_assertion_failure(__unused const char *fn, __unused uint line) { 168 | #if MINI_PRINTF 169 | printf("ASSERTION FAILURE %s:%d\n", fn, line); 170 | #endif 171 | __breakpoint(); 172 | __builtin_unreachable(); 173 | } 174 | -------------------------------------------------------------------------------- /src/nsboot/nsboot.template.ld: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ROM(rx) : ORIGIN = ${NSBOOT_START}, LENGTH = ${NSBOOT_SIZE} 3 | USBRAM(rw) : ORIGIN = ${NSBOOT_RAM_START}, LENGTH = ${NSBOOT_RAM_END} - ${NSBOOT_RAM_START} 4 | } 5 | 6 | INCLUDE "${ARM_SYMBOL_LINKER_SCRIPT}" 7 | 8 | SECTIONS { 9 | ASSERT((${NSBOOT_RAM_START} & 0x7f) == 0, "USB RAM must be 0x80 aligned for VTOR") 10 | . = ORIGIN(ROM); 11 | ENTRY(_start) 12 | .text : { 13 | *(.entry) 14 | *(.text.varm_to_native_nsboot_init) 15 | KEEP(*(.text.native_nsboot_init)) 16 | *(.text.varm_to_native_usb_packet_done) 17 | KEEP(*(.text.native_usb_packet_done)) 18 | *(.text.varm_to_native_usb_irq_enable) 19 | KEEP(*(.text.native_usb_irq_enable)) 20 | *(.text.usb_halt_endpoint) /* fall thru */ 21 | *(.text.usb_stall_endpoint) 22 | *(.text.usb_hard_reset_endpoint_callback) /* fall thru */ 23 | KEEP(*(.text.usb_reset_endpoint)) 24 | /* following for for b.n closeness in tail calls */ 25 | *(.text.tf_picoboot_wait_command) 26 | *(.text.usb_start_default_transfer_if_not_already_running_or_halted) 27 | *(.text.usb_start_empty_control_in_transfer_null_completion) 28 | *(.text.tf_send_control_in_ack) 29 | *(.text.usb_start_empty_transfer) 30 | *(.text.usb_reset_and_start_transfer) 31 | *(.text.usb_start_transfer) 32 | *(.text._usb_give_as_many_buffers_as_possible) 33 | 34 | *(SORT_BY_ALIGNMENT(.text*)) 35 | /* merge & template_z must being contiguous */ 36 | KEEP(*(.rodata.keep*)) 37 | KEEP(*(.rodata.index_html_mail_merge)) 38 | KEEP(*(.rodata.index_html_template_z)) 39 | KEEP(*(.rodata.info_uf2_txt_mail_merge)) 40 | KEEP(*(.rodata.info_uf2_txt_template_z)) 41 | *(SORT_BY_ALIGNMENT(.rodata*)) 42 | this_is_the_end_my_only_friend_the_end = .; 43 | . = ORIGIN(ROM) + LENGTH(ROM); 44 | } >ROM =0x00be 45 | 46 | ASSERT(native_nsboot_init == varm_to_native_nsboot_init + 4, "varm_to_native failure 1") 47 | ASSERT(native_usb_packet_done == varm_to_native_usb_packet_done + 4, "varm_to_native failure 2") 48 | ASSERT(native_usb_irq_enable == varm_to_native_usb_irq_enable + 4, "varm_to_native failure 3") 49 | /* ASSERT(usb_stall_endpoint == usb_halt_endpoint + 4, "fall thru failure 1") 50 | ASSERT(usb_reset_endpoint == usb_hard_reset_endpoint_callback + 4, "fall thru failure 2")*/ 51 | ASSERT(usb_start_empty_transfer_end == usb_reset_and_start_transfer, "fall thru failure 3") 52 | ASSERT(usb_reset_and_start_transfer_end == usb_start_transfer, "fall thru failure 4") 53 | ASSERT(usb_start_transfer_end == _usb_give_as_many_buffers_as_possible, "fall thru failure 5") 54 | ASSERT(index_html_template_z == index_html_mail_merge + 6, "mail merge failure 1") 55 | ASSERT(info_uf2_txt_template_z == info_uf2_txt_mail_merge + 6, "mail merge failure 2") 56 | .data : { 57 | __usb_ram_begin = .; 58 | *(SORT_BY_ALIGNMENT(.data*)) 59 | } >USBRAM 60 | 61 | .bss : { 62 | __bss_begin = .; 63 | *(.bss.first) 64 | *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) 65 | *(COMMON) 66 | } >USBRAM 67 | 68 | .stack : { 69 | . = ALIGN(4); 70 | KEEP(*(.stack_space)) 71 | __usb_ram_end = .; 72 | } >USBRAM 73 | 74 | ASSERT(SIZEOF(.data) == 0, 75 | "ERROR: do not use static memory in nsboot! (.data)") 76 | 77 | ASSERT((${NSBOOT_START} & 0x1f) == 0, "NSBOOT must be 32 byte aligned") 78 | /* ASSERT(__irq14_vector - ((${NSBOOT_START} + ${NSBOOT_VTOR_OFFSET})&~0x7f) == (16 + 14) * 4, "USB irq vector entry is in the wrong place")*/ 79 | ASSERT(__usb_ram_end - __usb_ram_begin <= LENGTH(USBRAM), "USB RAM overflow") 80 | ASSERT(__usb_ram_end - __usb_ram_begin == LENGTH(USBRAM), "Increase stack space to fill gap") 81 | INCLUDE "${P16_ASSERTS_LINKER_SCRIPT}" 82 | } 83 | -------------------------------------------------------------------------------- /src/nsboot/nsboot_asm.S: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "bootrom_layout.h" 8 | #include "varm_to_riscv_hints.h" 9 | #include "nsboot_secure_calls.h" 10 | #include "usb_device.h" 11 | 12 | .syntax unified 13 | .cpu cortex-m23 14 | .thumb 15 | 16 | .section .entry, "ax" 17 | 18 | .global _start 19 | 20 | _start: 21 | // note we dont use + 2 since go() actually overwrites its own stack !! 22 | b.w go // + 2 // skip first push instruction as it doesn't return 23 | 24 | .macro sc_or_varm_impl name ord 25 | .global \name 26 | .thumb_func 27 | \name: 28 | movs r3, #\ord 29 | .endm 30 | 31 | #define SC_OR_VARM_STUB(name) sc_or_varm_impl sc_or_varm_##name SC_##name 32 | 33 | // inlined, as called once only: 34 | // SC_OR_VARM_STUB(connect_internal_flash) 35 | // SC_OR_VARM_STUB(get_partition_table_info) 36 | // SC_OR_VARM_STUB(flash_page_program) 37 | // SC_OR_VARM_STUB(flash_sector_erase) 38 | // SC_OR_VARM_STUB(flash_read_data) 39 | // SC_OR_VARM_STUB(get_sys_info) 40 | 41 | SC_OR_VARM_STUB(ram_trash_get_uf2_target_partition) 42 | // fall through 43 | .global sc_or_varm_common 44 | sc_or_varm_common: 45 | b.w BOOTROM_SG_START 46 | 47 | // macro is "inlined" to allow hword alignment 48 | .global varm_to_native_nsboot_init 49 | .thumb_func 50 | varm_to_native_nsboot_init: 51 | movs r3, #MULTIPLEX_native_nsboot_init 52 | varm_hint HINT_MULTIPLEX 53 | .global native_nsboot_init 54 | .thumb_func 55 | native_nsboot_init: 56 | bx lr 57 | 58 | #if COMPRESS_TEXT 59 | .global poor_mans_text_decompress 60 | .type poor_mans_text_decompress,%function 61 | .thumb_func 62 | poor_mans_text_decompress: 63 | push {r4, r5, lr} 64 | adds r0, r1 65 | negs r1, r1 66 | 1: 67 | // r3 = char 68 | ldrb r3, [r0, r1] 69 | adds r2, #1 70 | lsrs r4, r3, #8 71 | sbcs r5, r5 72 | // r5 is -1 for regular character 73 | bne 3f 74 | 75 | sxtb r5, r3 76 | adds r1, #1 77 | ldrb r4, [r0, r1] 78 | // note we expect clear carry (so this subtracts an extra 1), which is fine, because r1 is < 0 still 79 | sbcs r2, r5 80 | subs r4, r2, r4 81 | // do copy 82 | 2: 83 | ldrb r3, [r4, r5] 84 | 3: 85 | strb r3, [r2, r5] 86 | adds r5, #1 87 | bne 2b 88 | 89 | adds r1, #1 90 | bne 1b 91 | 92 | pop {r4, r5, pc} 93 | #else 94 | #error WHY NOT? 95 | #endif 96 | 97 | // this is where the USB IRQ is in the vector table 98 | // (and must be at offset 0x78 from a 128 byte aligned boundary) 99 | .p2align 2 100 | .global __irq14_vector 101 | __irq14_vector: 102 | .word usb_irq_handler 103 | 104 | SC_OR_VARM_STUB(flash_abort) 105 | b.n sc_or_varm_common 106 | SC_OR_VARM_STUB(otp_access) 107 | b.n sc_or_varm_common 108 | 109 | // char *write_msb_hex_chars(__unused char *dest, __unused uint32_t val, __unused int count) 110 | .global write_msb_hex_chars 111 | .thumb_func 112 | write_msb_hex_chars: 113 | adds r2, r0, r2 114 | 1: 115 | lsrs r3, r1, #28 116 | adds r3, r3, #55 117 | cmp r3, #64 118 | bhi.n 2f 119 | subs r3, r3, #7 120 | 2: 121 | strb r3, [r0] 122 | adds r0, r0, #1 123 | lsls r1, r1, #4 124 | cmp r0, r2 125 | bne 1b 126 | bx lr 127 | 128 | .thumb_func 129 | .global rebooting 130 | rebooting: 131 | ldr r0, =rebooting_flag 132 | ldrb r0, [r0] 133 | bx lr 134 | 135 | // reboot is handled specially as it has 4 args (we pass the third in r4) 136 | .global sc_or_varm_reboot 137 | .thumb_func 138 | sc_or_varm_reboot: 139 | push {r3, r4, r5, lr} 140 | movs r3, #SC_reboot 141 | ldr r5, =rebooting_flag 142 | strb r3, [r5] 143 | // note 3rd arg passed on R4 144 | pop {r4} 145 | bl sc_or_varm_common 146 | // clear rebooting flag on unsuccessful call 147 | cbz r0, 1f 148 | movs r1, #0 149 | strb r1, [r5] 150 | 1: 151 | pop {r4, r5, pc} 152 | 153 | 154 | VARM_TO_MULTIPLEX_PREAMBLE(native_usb_irq_enable) 155 | VARM_TO_MULTIPLEX_PREAMBLE(native_usb_packet_done) 156 | 157 | .section .rodata.keep.packed_structs 158 | 159 | #if USB_USE_TINY_STREAM_TRANSFER_FUNCS 160 | .global msc_sector_funcs 161 | msc_sector_funcs: 162 | .hword msc_on_sector_stream_packet_complete + 1 163 | .hword msc_on_sector_stream_chunk + 1 164 | 165 | .global picoboot_stream_funcs 166 | picoboot_stream_funcs: 167 | // note varm_noop comes from main bootrom imports, so already has thumb bit set 168 | .hword varm_noop // usb_stream_noop_on_packet_complete + 1 169 | .hword picoboot_on_stream_chunk + 1 170 | #endif 171 | 172 | #if USB_USE_TINY_TRANSFER_TYPE 173 | .global usb_transfer_types 174 | usb_transfer_types: 175 | //#define USB_DEVICE_TRANSFER_TYPE_ms_os_20_descriptor_set_transfer_type 0 176 | .hword usb_device_ms_os_20_descriptor_set_on_packet_cb + 1 177 | //#define USB_DEVICE_TRANSFER_TYPE_usb_current_packet_only_transfer_type 1 178 | .hword varm_to_native_usb_packet_done + 1 //usb_transfer_current_packet_only + 1 179 | //#define USB_DEVICE_TRANSFER_TYPE__usb_stream_transfer_type 2 180 | .hword native_usb_stream_packet_handler + 1 181 | //#define USB_DEVICE_TRANSFER_TYPE__picoboot_cmd_transfer_type 3 182 | .hword _picoboot_cmd_packet + 1 183 | //#define USB_DEVICE_TRANSFER_TYPE__msc_cmd_transfer_type 4 184 | .hword _msc_cmd_packet + 1 185 | 186 | .global usb_transfer_type_transfer_counts 187 | usb_transfer_type_transfer_counts: 188 | // initial transfer counts for the above 189 | .byte GLOBAL_MS_OS_20_DESCRIPTOR_SET_PACKET_COUNT 190 | .byte 1 191 | .byte 0 192 | .byte 1 193 | .byte 1 194 | #endif 195 | 196 | #if USE_16BIT_POINTERS 197 | // these come from sb_virtual_disk.c 198 | _str_rpicom: 199 | .asciz "raspberrypi.com" 200 | _str_device_rp2: 201 | .asciz "https://raspberrypi.com/device/RP2?version=\01xxxxxxxxxxx" // 01 is marker for serial number 202 | _str_raspberry_pi_rp2350: 203 | .ascii "Raspberry Pi " 204 | // fall thru 205 | .global _str_rp2350 206 | _str_rp2350: 207 | .asciz "RP2350" 208 | .global _str_rpi_1 209 | _str_rpi_1: 210 | .asciz "RPI" 211 | .asciz "1" 212 | .byte 'c', 'r', 'i', 'v', 'e', 'n', 's', 0 213 | #endif 214 | 215 | .align 2 216 | .global index_html_defaults 217 | index_html_defaults: 218 | // note these are in reverse order 219 | .hword _str_rpicom 220 | .hword _str_device_rp2 221 | .hword _str_device_rp2 222 | 223 | // note these are in reverse order 224 | .global info_uf2_txt_defaults 225 | info_uf2_txt_defaults: 226 | .hword _str_rp2350 227 | .hword _str_raspberry_pi_rp2350 228 | 229 | .section .rodata.index_html_mail_merge 230 | .global index_html_mail_merge 231 | index_html_mail_merge: 232 | .hword index_html_defaults 233 | .hword index_html_str_defs 234 | .hword index_html_metadata 235 | 236 | .section .rodata.info_uf2_txt_mail_merge 237 | .global info_uf2_txt_mail_merge 238 | info_uf2_txt_mail_merge: 239 | .hword info_uf2_txt_defaults 240 | .hword info_uf2_txt_str_defs 241 | .hword info_uf2_txt_metadata 242 | 243 | // just here to cause linker errors for now if we use too much BSS 244 | .section .stack_space 245 | .align 2 246 | nsboot_stack: 247 | .space NSBOOT_STACK_WORDS * 4 248 | nsboot_stack_end: 249 | -------------------------------------------------------------------------------- /src/nsboot/nsboot_async_task.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | // Simple async task without real locking... tasks execute in thread mode, they are queued by IRQs and state management and completion callbacks called with IRQs disabled 10 | // which effectively means everything but the task execution is single threaded; as such the task should used async_task structure for all input/output 11 | #include "nsboot.h" 12 | 13 | #define ASYNC_TASK_REQUIRE_TASK_CALLBACK 1 14 | // bit field for task type 15 | #define AT_EXCLUSIVE 0x01u 16 | #define AT_ENTER_CMD_XIP 0x02u 17 | #define AT_EXEC 0x03u 18 | #define AT_VECTORIZE_FLASH 0x04u 19 | #define AT_GET_INFO 0x05u 20 | #define AT_OTP_READ 0x06u 21 | #define AT_OTP_WRITE 0x07u 22 | #define AT_EXEC2 0x08u 23 | 24 | // these three are checked by AND with mask, so are given their own bits 25 | // (they can be combined with eachother, but not any of the above) 26 | #define AT_MASKABLE_EXIT_XIP 0x10u 27 | #define AT_MASKABLE_FLASH_ERASE 0x20u 28 | #define AT_MASKABLE_READ 0x40u 29 | #define AT_MASKABLE_WRITE 0x80u 30 | 31 | struct async_task; 32 | 33 | typedef void (*async_task_callback)(struct async_task *task); 34 | 35 | #define FLASH_PAGE_SIZE 256u 36 | #define FLASH_PAGE_MASK (FLASH_PAGE_SIZE - 1u) 37 | #define FLASH_SECTOR_ERASE_SIZE 4096u 38 | 39 | enum task_source { 40 | TASK_SOURCE_VIRTUAL_DISK = 1, 41 | TASK_SOURCE_PICOBOOT, 42 | }; 43 | 44 | // copy by value task definition 45 | struct async_task { 46 | uint32_t token; 47 | uint32_t result; 48 | async_task_callback callback; 49 | 50 | // we only have one task type now, so inlining all fields since the task building 51 | // code does not know which fields are for which tasks (it just populates them all 52 | // speculatively (except get_info, otp and exec2 which are handled specially, so can overlap some other fields) 53 | uint32_t transfer_addr; 54 | union { 55 | // note these 2 are used both by the "erase" cmd itself, or by erases happening as a result of flash writes 56 | struct { 57 | uint32_t erase_size; 58 | uint32_t erase_addr; 59 | }; 60 | uint32_quad_t raw_cmd; // we need space for any type 61 | struct picoboot_otp_cmd otp_cmd; 62 | struct picoboot_exec2_cmd exec2_cmd; 63 | struct picoboot_get_info_cmd get_info_cmd; 64 | }; 65 | aligned4_uint8_t *data; 66 | uint32_t data_length; 67 | uint32_t picoboot_user_token; 68 | uint8_t type; 69 | uint8_t exclusive_param; 70 | // an identifier for the logical source of the task 71 | uint8_t source; 72 | // if true, fail the task if the source isn't the same as the last source that did a mutation 73 | bool check_last_mutation_source; 74 | }; 75 | 76 | // arguably a very short queue; there is only one up "next" item which is set by queue_task... 77 | // attempt to queue multiple items will overwrite (so generally use multiple queues) 78 | // 79 | // this purely allows us to queue one task from the IRQ scope to the worker scope while 80 | // that worker scope may still be executing the last one 81 | struct async_task_queue { 82 | struct async_task task; 83 | volatile bool full; 84 | volatile bool disable; 85 | }; 86 | 87 | // called by irq handler to queue a task 88 | void queue_task(uint queue_index, struct async_task *task, async_task_callback callback); 89 | 90 | // called from thread to dequeue a task 91 | bool dequeue_task(uint queue_index, struct async_task *task_out); 92 | 93 | // runs forever dispatch tasks 94 | void __attribute__((noreturn)) async_task_worker(void); 95 | 96 | void reset_task(struct async_task *task); 97 | 98 | #define QUEUE_PICOBOOT 0 99 | #define QUEUE_VIRTUAL_DISK 1 100 | extern struct async_task_queue queues[2]; 101 | #define deref_queue(x) (&queues[x]) 102 | static inline void async_disable_queue(uint queue_index, bool disable) { 103 | deref_queue(queue_index)->disable = disable; 104 | } 105 | 106 | static inline void reset_queue(uint queue_index) { 107 | deref_queue(queue_index)->full = false; 108 | async_disable_queue(queue_index, false); 109 | } 110 | 111 | // async task needs to know where the flash bitmap is so it can avoid it 112 | #define FLASH_VALID_BLOCKS_BASE XIP_SRAM_BASE 113 | #define FLASH_BITMAPS_SIZE (XIP_SRAM_END - XIP_SRAM_BASE) 114 | -------------------------------------------------------------------------------- /src/nsboot/nsboot_uart.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pico.h" 10 | #include "hardware/structs/uart.h" 11 | #include "hardware/structs/resets.h" 12 | 13 | // Minimal UART support, for exposing PicoBoot interface over UART 14 | 15 | static inline uart_hw_t *boot_uart_getinst(uint inst) { 16 | return (uart_hw_t *)(UART0_BASE + inst * (UART1_BASE - UART0_BASE)); 17 | } 18 | 19 | static inline void boot_uart_init(uint inst) { 20 | // We should be able to access this RESETS bit as the peripheral itself 21 | // has been granted to NonSecure 22 | uint32_t mask = RESETS_WDSEL_UART0_BITS << inst; 23 | hw_set_bits(&resets_hw->reset, mask); 24 | hw_clear_bits(&resets_hw->reset, mask); 25 | while (!(resets_hw->reset_done & mask)) 26 | ; 27 | 28 | uart_hw_t *uart = boot_uart_getinst(inst); 29 | // 1 Mbaud from an assumed 48 MHz: there is a fixed division of 16 due to 30 | // UART oversampling, so program an additional divide by 3. 31 | uart->ibrd = 3; 32 | uart->fbrd = 0; 33 | // Set 8n1 format, enable FIFOs, and latch baud divisor 34 | uart->lcr_h = UART_UARTLCR_H_FEN_BITS | (8u - 5u) << UART_UARTLCR_H_WLEN_LSB; 35 | // Enable the UART 36 | uart->cr = UART_UARTCR_UARTEN_BITS | UART_UARTCR_TXE_BITS | UART_UARTCR_RXE_BITS; 37 | } 38 | 39 | static inline bool boot_uart_is_writable(uint inst) { 40 | return !(boot_uart_getinst(inst)->fr & UART_UARTFR_TXFF_BITS); 41 | } 42 | 43 | static inline bool boot_uart_is_readable(uint inst) { 44 | return !(boot_uart_getinst(inst)->fr & UART_UARTFR_RXFE_BITS); 45 | } 46 | 47 | static inline void boot_uart_putc_blocking(uint inst, uint8_t wdata) { 48 | while (!boot_uart_is_writable(inst)) 49 | ; 50 | boot_uart_getinst(inst)->dr = wdata; 51 | } 52 | 53 | static inline void boot_uart_write_blocking(uint inst, const uint8_t *wdata, size_t len) { 54 | for (size_t i = 0; i < len; ++i) { 55 | boot_uart_putc_blocking(inst, wdata[i]); 56 | } 57 | } 58 | 59 | static inline uint8_t boot_uart_getc_blocking(uint inst) { 60 | while (!(boot_uart_is_readable(inst))) 61 | ; 62 | return (uint8_t)boot_uart_getinst(inst)->dr; 63 | } 64 | 65 | static inline void boot_uart_read_blocking(uint inst, uint8_t *rdata, size_t len) { 66 | for (size_t i = 0; i < len; ++i) { 67 | rdata[i] = boot_uart_getc_blocking(inst); 68 | } 69 | } 70 | 71 | static inline void boot_uart_wait_tx_idle(uint inst) { 72 | while (boot_uart_getinst(inst)->fr & UART_UARTFR_BUSY_BITS); 73 | } 74 | -------------------------------------------------------------------------------- /src/nsboot/nsboot_uart_client.S: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // UART boot shell 3 | // Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 4 | // SPDX-License-Identifier: BSD-3-Clause 5 | // ---------------------------------------------------------------------------- 6 | // 7 | // Commands sent to the device consist of one ASCII character, optionally 8 | // followed by 32 bytes of raw data. 9 | // 10 | // n nop 11 | // w write 32 bytes to SRAM, increment read/write pointer by 32 12 | // r read 32 bytes from SRAM, increment read/write pointer by 32 13 | // c clear read/write pointer 14 | // x execute image at start of SRAM 15 | // 16 | // The command byte is echoed back upon the completion of each command. You 17 | // should wait for the echo before sending the next command. If the device 18 | // seems to be stuck, issue NOP commands until you start getting echos back, 19 | // then wait for UART RX to go idle again, then issue a clear command and 20 | // retry from the beginning. 21 | // 22 | // (Note the size of 32 is chosen for the depth of the FIFOs -- it's 23 | // conceivable under emulation on RISC-V that we struggle to keep up with the 24 | // UART.) 25 | // 26 | // An invalid command will be echoed back, with no other effect. 27 | // 28 | // The device starts by printing the splash string, "RP2350". It then remains 29 | // silent until the correct knock sequence has been entered. The required 30 | // knock sequence is the bytes 0x56, 0xff, 0x8b, 0xe4. 31 | // 32 | // The loaded data is expected to be a valid RP2350 executable with an 33 | // appropriate IMAGE_DEF. If secure boot is enabled, the image must be signed 34 | // and have an appropriate rollback version. This is checked after reboot -- 35 | // the UART bootloader does not verify the image for you, though it does 36 | // allow you to read back for your own verification. 37 | 38 | // varmulet dialect: v6-M plus movw, movt, cbz, cbnz, b.w, msplim 39 | // -> reminiscent of v8-M baseline 40 | .cpu cortex-m23 41 | .syntax unified 42 | 43 | .section .text.nsboot_uart_client, "ax" 44 | #include "hardware/regs/addressmap.h" 45 | #include "hardware/regs/uart.h" 46 | #include "boot/picoboot_constants.h" 47 | #include "nsboot.h" 48 | 49 | // Note this happens to be the RP2040 UF2 family ID (expected to be sent in 50 | // little-endian byte order) -- not particularly meaningful, we just need 51 | // some well-known magic number to avoid responding to random GPIO toggles. 52 | // The binary type (including Arm vs RISC-V etc) is determined from the 53 | // binary's IMAGE_DEF, upon reboot after the `x` command is invoked. 54 | #define MAGIC_KNOCK_PATTERN 0xe48bff56 55 | 56 | // Note this code does not return, so we are free to trash all GPRs without 57 | // stacking them. r7 always contains a pointer to the UART hardware. r5, r6 58 | // always hold putc, getc (so we can call with just a 16-bit `blx`) 59 | 60 | .macro blx_putc 61 | blx r5 62 | .endm 63 | .macro blx_getc 64 | blx r6 65 | .endm 66 | 67 | #if FEATURE_UART_BOOT_SELECTABLE_INSTANCE 68 | #error not supported 69 | #endif 70 | .global nsboot_uart_client 71 | .p2align 2 72 | nsboot_uart_client: 73 | // The UART is assumed to have already been reset in the varm_nsboot 74 | // Secure preamble, or (for dev builds with printf) in early nsboot code. 75 | 76 | // r7 always holds the UART regs pointer. 77 | ldr r7, =UART0_BASE 78 | ldr r5, =P16(nsboot_uart_putc) 79 | ldr r6, =P16(nsboot_uart_getc) 80 | 81 | // Assuming a 48 MHz nsboot clock, and given the PL011's oversampling by 82 | // 16, we want an integer baud divisor of 3 to achieve a 1 Mbaud 83 | // signalling rate. 84 | movs r0, #3 85 | str r0, [r7, #UART_UARTIBRD_OFFSET] 86 | // (FBRD should already be 0 from reset) 87 | 88 | // Set 8n1 format, enable FIFOs, and latch baud divisor (triggered on LCR write): 89 | movs r0, #UART_UARTLCR_H_FEN_BITS | (8 - 5) << UART_UARTLCR_H_WLEN_LSB 90 | str r0, [r7, #UART_UARTLCR_H_OFFSET] 91 | 92 | // Enable the UART 93 | movw r0, #UART_UARTCR_UARTEN_BITS | UART_UARTCR_TXE_BITS | UART_UARTCR_RXE_BITS 94 | str r0, [r7, UART_UARTCR_OFFSET] 95 | // Fall through 96 | 97 | // Print out string so the host knows we're ready 98 | nsboot_uart_splash: 99 | ldr r3, =P16(_str_rp2350) 100 | 1: 101 | ldrb r0, [r3] 102 | adds r3, #1 103 | cbz r0, 2f 104 | blx_putc 105 | b 1b 106 | 2: 107 | // Fall through 108 | 109 | // Remain silent until we receive the magic word 110 | nsboot_uart_await_knock: 111 | // Byte-swap the constant to save an instruction: we expect little-endian 112 | ldr r3, =(\ 113 | ((MAGIC_KNOCK_PATTERN & 0xff000000) >> 24) | \ 114 | ((MAGIC_KNOCK_PATTERN & 0x00ff0000) >> 8 ) | \ 115 | ((MAGIC_KNOCK_PATTERN & 0x0000ff00) << 8 ) | \ 116 | ((MAGIC_KNOCK_PATTERN & 0x000000ff) << 24)) 117 | movs r2, #0 118 | 1: 119 | blx_getc 120 | // Implement 4-byte shift register to test for knock pattern: 121 | lsls r2, #8 122 | add r2, r0 123 | cmp r2, r3 124 | bne 1b 125 | // Fall through 126 | 127 | nsboot_uart_start_cmd_loop: 128 | // From now on: 129 | // r0/r1/r2 are trashable 130 | // r3 contains ram read/write pointer 131 | // r4 contains saved command (for echo-back) 132 | // r5-r7 are preserved, as before 133 | ldr r3, =SRAM_BASE 134 | 135 | nsboot_uart_get_cmd: 136 | blx_getc 137 | // Save r0 for echo after completion 138 | movs r4, r0 139 | cmp r0, #'w' 140 | beq nsboot_uart_do_write_cmd 141 | cmp r0, #'r' 142 | beq nsboot_uart_do_read_cmd 143 | // Remaining commands perform echo immediately 144 | blx_putc 145 | cmp r0, #'c' 146 | beq nsboot_uart_start_cmd_loop 147 | cmp r0, #'x' 148 | bne nsboot_uart_get_cmd // unrecognised (including nop) 149 | // fall through 150 | nsboot_uart_do_exec_cmd: 151 | // flags 152 | movw r0, #REBOOT2_FLAG_REBOOT_TYPE_RAM_IMAGE | REBOOT2_FLAG_NO_RETURN_ON_SUCCESS 153 | // delay_ms 154 | movs r1, #1 155 | // ram_image_start 156 | ldr r2, =SRAM_BASE 157 | // ram_image_size 158 | #if (((SRAM_END - SRAM_BASE) >> 13) << 13) != (SRAM_END - SRAM_BASE) || \ 159 | ((SRAM_END - SRAM_BASE) >> 13) > 255 160 | #error "Fix this" 161 | #endif 162 | movs r3, #(SRAM_END - SRAM_BASE) >> 13 163 | lsls r3, #13 164 | b sc_or_varm_reboot 165 | 166 | nsboot_uart_do_read_cmd: 167 | 1: 168 | ldrb r0, [r3] 169 | adds r3, #1 170 | blx_putc 171 | // 32 bytes per chunk, pointer always starts 32-byte-aligned 172 | lsls r0, r3, #27 173 | bne 1b 174 | b nsboot_uart_echo_cmd 175 | nsboot_uart_do_write_cmd: 176 | 1: 177 | blx_getc 178 | strb r0, [r3] 179 | adds r3, #1 180 | // 32 bytes per chunk, pointer always starts 32-byte-aligned 181 | lsls r0, r3, #27 182 | bne 1b 183 | // Fall through 184 | nsboot_uart_echo_cmd: 185 | movs r0, r4 186 | blx_putc 187 | b nsboot_uart_get_cmd 188 | 189 | // Args: character in r0 190 | // Clobbers: r1 191 | // Returns: character in r0 (preserved) 192 | .global nsboot_uart_putc 193 | nsboot_uart_putc: 194 | ldr r1, [r7, #UART_UARTFR_OFFSET] 195 | lsrs r1, #(UART_UARTFR_TXFF_LSB + 1) 196 | bcs nsboot_uart_putc 197 | strb r0, [r7, #UART_UARTDR_OFFSET] 198 | bx lr 199 | 200 | // Args: none 201 | // Clobbers: none 202 | // Returns: character in r0 203 | .global nsboot_uart_getc 204 | nsboot_uart_getc: 205 | ldr r0, [r7, #UART_UARTFR_OFFSET] 206 | lsrs r0, #(UART_UARTFR_RXFE_LSB + 1) 207 | bcs nsboot_uart_getc 208 | ldrb r0, [r7, #UART_UARTDR_OFFSET] 209 | bx lr 210 | 211 | nsboot_uart_literals: 212 | .ltorg 213 | -------------------------------------------------------------------------------- /src/nsboot/nsboot_usb_client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "usb_device.h" 10 | 11 | #define VENDOR_ID 0x2e8au 12 | #define PRODUCT_ID 0x000fu 13 | 14 | void nsboot_usb_device_init(uint32_t bootsel_flags); 15 | 16 | static inline bool is_address_flash(uint32_t addr) { 17 | #if PICO_RP2350 18 | return (addr >= XIP_BASE && addr <= XIP_BASE + 32 * 1024 * 1024); 19 | #else 20 | return (addr >= XIP_BASE && addr <= XIP_NOALLOC_BASE); 21 | #endif 22 | } 23 | -------------------------------------------------------------------------------- /src/nsboot/scsi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #define CBW_SIG 0x43425355 14 | struct __aligned(4) __packed scsi_cbw { 15 | uint32_t sig; 16 | uint32_t tag; 17 | uint32_t data_transfer_length; 18 | uint8_t flags; 19 | uint8_t lun; 20 | uint8_t cb_length; 21 | uint8_t cb[16]; 22 | }; 23 | 24 | #define CSW_SIG 0x53425355 25 | struct __packed scsi_csw { 26 | uint32_t sig; 27 | uint32_t tag; 28 | uint32_t residue; 29 | uint8_t status; 30 | }; 31 | 32 | struct __packed scsi_capacity { 33 | uint32_t lba; // last block addr 34 | uint32_t block_len; // probably 512 35 | }; 36 | 37 | struct __packed scsi_read_cb { 38 | uint8_t opcode; 39 | uint8_t flags; 40 | uint32_t lba; 41 | uint8_t reserved; 42 | uint16_t blocks; 43 | uint8_t control; 44 | }; 45 | 46 | enum csw_status { 47 | CSW_STATUS_COMMAND_PASSED = 0x00, 48 | CSW_STATUS_COMMAND_FAILED = 0x01, 49 | CSW_STATUS_PHASE_ERROR = 0x02, 50 | }; 51 | 52 | enum scsi_cmd { 53 | INQUIRY = 0x12, 54 | MODE_SELECT_6 = 0x15, 55 | MODE_SELECT_10 = 0x55, 56 | MODE_SENSE_6 = 0x1a, 57 | MODE_SENSE_10 = 0x5a, 58 | PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1e, 59 | READ_6 = 0x08, 60 | READ_10 = 0x28, 61 | READ_12 = 0xa8, 62 | READ_FORMAT_CAPACITIES = 0x23, 63 | READ_CAPACITY_10 = 0x25, 64 | REPORT_LUNS = 0xa0, 65 | REQUEST_SENSE = 0x03, 66 | SEND_DIAGNOSTIC = 0x1d, 67 | START_STOP_UNIT = 0x1b, 68 | SYNCHRONIZE_CACHE = 0x35, 69 | TEST_UNIT_READY = 0x00, 70 | VERIFY = 0x2f, 71 | WRITE_6 = 0x0a, 72 | WRITE_10 = 0x2a, 73 | WRITE_12 = 0xaa, 74 | }; 75 | 76 | enum scsi_sense_key { 77 | SK_OK = 0x00, 78 | SK_NOT_READY = 0x02, 79 | SK_ILLEGAL_REQUEST = 0x05, 80 | SK_UNIT_ATTENTION = 0x06, 81 | SK_DATA_PROTECT = 0x07 82 | }; 83 | 84 | enum scsi_additional_sense_code { 85 | ASC_NONE = 0x00, 86 | ASC_INVALID_COMMAND_OPERATION_CODE = 0x20, 87 | ASC_PERIPHERAL_DEVICE_WRITE_FAULT = 0x03, 88 | ASC_ACCESS_DENIED = 0x20, 89 | ASC_LBA_OUT_OF_RANGE = 0x21, 90 | ASC_WRITE_PROTECTED = 0x27, 91 | ASC_NOT_READY_TO_READY_CHANGE = 0x28, 92 | ASC_MEDIUM_NOT_PRESENT = 0x3a, 93 | }; 94 | 95 | enum scsi_additional_sense_code_qualifier { 96 | ASCQ_NA = 0x00, 97 | }; 98 | -------------------------------------------------------------------------------- /src/nsboot/scsi_ir.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | // NOTE THIS IS IN A SEPARATE HEADER AS IT IS COMPRESSED WHEN USING COMPRESS_TEXT 10 | typedef unsigned char uint8_t; 11 | 12 | struct scsi_inquiry_response { 13 | uint8_t pdt; 14 | uint8_t rmb; 15 | uint8_t spc_version; 16 | uint8_t rdf; 17 | uint8_t additional_length; 18 | uint8_t inquiry5; 19 | uint8_t inquiry6; 20 | uint8_t inquiry7; 21 | char vendor[8]; 22 | char product[16]; 23 | char version[4]; 24 | } __packed; 25 | 26 | #if !COMPRESS_TEXT 27 | static const struct scsi_inquiry_response scsi_ir = { 28 | .rmb = 0x80, 29 | .spc_version = 2, 30 | .rdf = 2, 31 | .additional_length = sizeof(struct scsi_inquiry_response) - 5, 32 | // note these are now spaces because they compress well, and this allows to not 33 | // need to fill with spaces later if the strings are shorter 34 | .vendor = " ", 35 | .product = " ", 36 | .version = " ", 37 | }; 38 | #endif 39 | -------------------------------------------------------------------------------- /src/nsboot/usb_msc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "nsboot.h" 10 | 11 | #define SECTOR_SIZE 512u 12 | 13 | bool msc_setup_request_handler(struct usb_interface *interface, struct usb_setup_packet *setup); 14 | void msc_on_configure(__unused struct usb_device *device, bool configured); 15 | //struct usb_endpoint msc_in, msc_out; 16 | extern struct usb_endpoint msc_endpoints[2]; 17 | 18 | // provided by the hosting code 19 | uint32_t msc_get_serial_number32(void); 20 | void msc_eject(void); 21 | 22 | -------------------------------------------------------------------------------- /src/nsboot/usb_virtual_disk.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "usb_msc.h" 10 | 11 | #define USE_INFO_UF2 1 12 | 13 | void vd_init(void); 14 | void vd_reset(void); 15 | 16 | // return true for async operation 17 | bool vd_read_block(uint32_t token, uint32_t lba, aligned4_uint8_t *buf __comma_removed_for_space(uint32_t buf_size)); 18 | bool vd_write_block(uint32_t token, uint32_t lba, aligned4_uint8_t *buf __comma_removed_for_space(uint32_t buf_size)); 19 | 20 | uint32_quad_t *get_uf2_status(void); 21 | 22 | // give us ourselves 16M which should strictly be the minimum for FAT16 - Note Win10 doesn't like FAT12 - go figure! 23 | // upped to 64M which allows us to download a 32M UF2 24 | #define CLUSTER_UP_SHIFT 0u 25 | #define CLUSTER_UP_MUL (1u << CLUSTER_UP_SHIFT) 26 | #define VOLUME_SIZE (CLUSTER_UP_MUL * 128u * 1024u * 1024u) 27 | 28 | #define SECTOR_COUNT (VOLUME_SIZE / SECTOR_SIZE) 29 | 30 | //#if !GENERAL_SIZE_HACKS 31 | //static inline uint32_t vd_sector_count() { 32 | // return SECTOR_COUNT; 33 | //} 34 | //#else 35 | // needs to be a compile time constant 36 | #define vd_sector_count() SECTOR_COUNT 37 | //#endif 38 | 39 | void vd_async_complete(uint32_t token, uint32_t result); 40 | --------------------------------------------------------------------------------