├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.md └── workflows │ └── validate-hsm-wrapper.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── Readme.md ├── bin2c.c ├── bootfiles.c ├── bootfiles.h ├── decode_duid.c ├── decode_duid.h ├── docs ├── secure-boot-chain-of-trust-2711.pdf └── secure-boot-chain-of-trust-2712.pdf ├── eeprom-erase ├── README.md ├── bootcode4.bin └── config.txt ├── firmware ├── 2711 │ ├── bootcode4.bin │ ├── pieeprom.bin │ └── recovery.bin ├── 2712 │ ├── pieeprom.bin │ └── recovery.bin └── bootfiles.bin ├── fmemopen.c ├── main.c ├── mass-storage-gadget ├── mass-storage-gadget64-cm3 └── bootfiles.bin ├── mass-storage-gadget64 ├── .gitignore ├── README.md ├── boot.img ├── bootfiles.bin ├── config.txt ├── reset.sh └── sign.sh ├── msd ├── .gitignore ├── README.md ├── bootcode.bin ├── bootcode4.bin └── start.elf ├── recovery ├── .gitignore ├── README.md ├── boot.conf ├── bootcode4.bin ├── config.txt ├── pieeprom.original.bin ├── rpi-eeprom-config └── update-pieeprom.sh ├── recovery5 ├── .gitignore ├── README.md ├── boot.conf ├── bootcode5.bin ├── config.txt ├── pieeprom.original.bin └── update-pieeprom.sh ├── rpi-imager-embedded ├── .gitignore ├── README.md ├── bootfiles.bin └── config.txt ├── secure-boot-example ├── README.md ├── boot.img ├── boot.sig ├── bootfiles.bin ├── config.txt ├── example-hsm-wrapper ├── example-private.pem ├── example-public.pem └── sign.sh ├── secure-boot-msd ├── .gitignore ├── README.md ├── boot.img ├── bootcode4.bin └── config.txt ├── secure-boot-recovery ├── .gitignore ├── README.md ├── boot.conf ├── bootcode4.bin ├── config.txt └── pieeprom.original.bin ├── secure-boot-recovery5 ├── .gitignore ├── README.md ├── boot.conf ├── config.txt ├── pieeprom.original.bin └── recovery.original.bin ├── test ├── README.md └── validate-hsm-wrapper.sh ├── tools ├── make-boot-image ├── rpi-bootloader-key-convert ├── rpi-create-tags ├── rpi-eeprom-config ├── rpi-eeprom-digest ├── rpi-make-boot-image ├── rpi-otp-private-key ├── rpi-sign-bootcode └── update-pieeprom.sh └── win32 ├── LICENSE.txt ├── Raspberry_Pi_Logo.ico ├── Readme.md ├── cygusb-1.0.dll ├── cygwin1.dll ├── install_script.nsi ├── redist └── wdi-simple.exe ├── rpi-mass-storage-gadget64.bat └── rpiboot_setup.exe /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "Bug report" 2 | description: Create a report to help us fix your issue 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | **Is this the right place for my bug report?** 8 | 9 | This repository contains the host tools for booting a Raspberry Pi computer into mass-storage device mode. It also provides tools for updating the bootloader EEPROM and enabling secure-boot on CM4. 10 | 11 | * Please check the [rpiboot troubleshooting guide](https://github.com/raspberrypi/usbboot#troubleshooting) for advice about diagnosing problems with rpiboot. 12 | * If you simply have a question, then [the Raspberry Pi forums](https://www.raspberrypi.org/forums) are the best place to ask it. 13 | 14 | - type: textarea 15 | id: description 16 | attributes: 17 | label: Describe the bug 18 | description: | 19 | Add a clear and concise description of what you think the bug is. 20 | validations: 21 | required: true 22 | 23 | - type: textarea 24 | id: reproduce 25 | attributes: 26 | label: Steps to reproduce the behaviour 27 | description: | 28 | List the steps required to reproduce the issue. 29 | validations: 30 | required: true 31 | 32 | - type: dropdown 33 | id: model 34 | attributes: 35 | label: Device(s) 36 | description: On which device(s) you are facing the bug? Please see the ["Product Information Portal"](https://pip.raspberrypi.com/) for information about products and hardware revisions. 37 | 38 | multiple: true 39 | options: 40 | - Raspberry Pi CM1 41 | - Raspberry Pi CM3 42 | - Raspberry Pi CM3+ 43 | - Raspberry Pi CM4 44 | - Raspberry Pi CM4 Lite 45 | - Raspberry Pi CM4 Revision 5 46 | - Raspberry Pi CM4 Revision 5 Lite 47 | - Raspberry Pi CM4S 48 | - Other 49 | validations: 50 | required: true 51 | 52 | - type: textarea 53 | id: io_board 54 | attributes: 55 | label: Compute Module IO board. 56 | description: | 57 | For Compute Module issues please specify the type of the IO board. 58 | validations: 59 | required: false 60 | 61 | - type: textarea 62 | id: rpiboot_logs 63 | attributes: 64 | label: RPIBOOT logs 65 | description: | 66 | Please paste the output of the rpiboot command here. 67 | validations: 68 | required: false 69 | 70 | - type: textarea 71 | id: kernel_logs 72 | attributes: 73 | label: Kernel logs 74 | description: | 75 | In order to diagnose USB connection issues please check the Linux `dmesg` log for USB messages 76 | * `sudo dmesg | grep -i usb` 77 | validations: 78 | required: false 79 | 80 | - type: textarea 81 | id: device_logs 82 | attributes: 83 | label: Device UART logs 84 | description: | 85 | Compute Module 4 will typically output debug information to the UART when updating the EEPROM or loading the mass-storage gadget. Please add any device logs here. 86 | * The UART pins are `14` and `15` on the [40-pin GPIO header](https://www.raspberrypi.com/documentation/computers/os.html#gpio-and-the-40-pin-header) 87 | validations: 88 | required: false 89 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: "⛔ Question" 5 | url: https://www.raspberrypi.org/forums 6 | about: "Please do not use GitHub for asking questions. If you simply have a question, then the Raspberry Pi forums are the best place to ask it. Thanks in advance for helping us keep the issue tracker clean!" 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/validate-hsm-wrapper.yml: -------------------------------------------------------------------------------- 1 | name: Raspberry Pi USB Boot tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | validate: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | submodules: true 19 | submodule-recursive: true 20 | submodule-shallow: true 21 | 22 | - name: Set up Python 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: '3.x' 26 | 27 | - name: Install system dependencies 28 | run: | 29 | sudo apt-get update 30 | sudo apt-get install -y \ 31 | build-essential \ 32 | git \ 33 | tar \ 34 | libusb-1.0-0-dev \ 35 | pkg-config 36 | 37 | - name: Set up Python virtual environment 38 | run: | 39 | python3 -m venv .venv 40 | source .venv/bin/activate 41 | python3 -m pip install --upgrade pip 42 | pip3 install pycryptodomex 43 | 44 | - name: Build rpiboot 45 | run: | 46 | make 47 | 48 | - name: Run HSM Wrapper unit test 49 | run: | 50 | source .venv/bin/activate 51 | cd test 52 | chmod +x validate-hsm-wrapper.sh 53 | ./validate-hsm-wrapper.sh -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | rpiboot 2 | bin2c 3 | *.exe 4 | *.swp 5 | temp/ 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rpi-eeprom"] 2 | path = rpi-eeprom 3 | url = https://github.com/raspberrypi/rpi-eeprom 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DATE ?= $(shell date "+%Y/%m/%d") 2 | PKG_VER=$(shell if [ -f debian/changelog ]; then grep rpiboot debian/changelog | head -n1 | sed 's/.*(\(.*\)).*/\1/g'; else echo local; fi) 3 | GIT_VER=$(shell git rev-parse HEAD 2>/dev/null | cut -c1-8 || echo "") 4 | HAVE_XXD=$(shell xxd -v >/dev/null 2>/dev/null && echo y) 5 | INSTALL_PREFIX?=/usr 6 | 7 | rpiboot: main.c bootfiles.c decode_duid.c msd/bootcode.h msd/start.h msd/bootcode4.h 8 | $(CC) -Wall -Wextra -g $(CPPFLAGS) $(CFLAGS) -o $@ main.c bootfiles.c decode_duid.c `pkg-config --cflags --libs libusb-1.0` -DGIT_VER="\"$(GIT_VER)\"" -DPKG_VER="\"$(PKG_VER)\"" -DBUILD_DATE="\"$(BUILD_DATE)\"" -DINSTALL_PREFIX=\"$(INSTALL_PREFIX)\" $(LDFLAGS) 9 | 10 | ifeq ($(HAVE_XXD),y) 11 | %.h: %.bin 12 | xxd -i $< > $@ 13 | 14 | %.h: %.elf 15 | xxd -i $< > $@ 16 | else 17 | CC_FOR_BUILD ?= $(CC) 18 | 19 | %.h: %.bin ./bin2c 20 | ./bin2c $< $@ 21 | 22 | %.h: %.elf ./bin2c 23 | ./bin2c $< $@ 24 | 25 | bin2c: bin2c.c 26 | $(CC_FOR_BUILD) -Wall -Wextra -g -o $@ $< 27 | 28 | endif 29 | 30 | install: rpiboot 31 | install -m 755 rpiboot $(INSTALL_PREFIX)/bin/ 32 | install -d $(INSTALL_PREFIX)/share/rpiboot 33 | install -d $(INSTALL_PREFIX)/share/rpiboot/msd 34 | install -d $(INSTALL_PREFIX)/share/rpiboot/mass-storage-gadget64 35 | install -m 644 msd/bootcode.bin $(INSTALL_PREFIX)/share/rpiboot/msd 36 | install -m 644 msd/bootcode4.bin $(INSTALL_PREFIX)/share/rpiboot/msd 37 | install -m 644 msd/start.elf $(INSTALL_PREFIX)/share/rpiboot/msd 38 | install -m 644 mass-storage-gadget64/boot.img $(INSTALL_PREFIX)/share/rpiboot/mass-storage-gadget64 39 | install -m 644 mass-storage-gadget64/config.txt $(INSTALL_PREFIX)/share/rpiboot/mass-storage-gadget64 40 | install -m 644 mass-storage-gadget64/bootfiles.bin $(INSTALL_PREFIX)/share/rpiboot/mass-storage-gadget64 41 | 42 | uninstall: 43 | rm -f $(INSTALL_PREFIX)/bin/rpiboot 44 | rm -rf $(INSTALL_PREFIX)/share/rpiboot 45 | 46 | clean: 47 | rm -f rpiboot msd/*.h bin2c 48 | 49 | .PHONY: uninstall clean 50 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # USB Device Boot Code 2 | 3 | This is the USB device boot code which supports the Raspberry Pi 1A, 3A+, Compute Module, Compute 4 | Module 3, 3+ 4S, 4 and 5, Raspberry Pi Zero and Zero 2 W. 5 | 6 | The default behaviour when run with no arguments is to boot the Raspberry Pi with 7 | special firmware so that it emulates USB Mass Storage Device (MSD). The host OS 8 | will treat this as a normal USB mass storage device allowing the file system 9 | to be accessed. If the storage has not been formatted yet (default for Compute Module) 10 | then the [Raspberry Pi Imager App](https://www.raspberrypi.com/software/) can be 11 | used to install a new operating system. 12 | 13 | Since `RPIBOOT` is a generic firmware loading interface, it is possible to load 14 | other versions of the firmware by passing the `-d` flag to specify the directory 15 | where the firmware should be loaded from. 16 | E.g. The firmware in the [msd](msd/README.md) can be replaced with newer/older versions. 17 | 18 | From Raspberry Pi 4 onwards the MSD VPU firmware has been replaced with the Linux based mass storage gadget. 19 | 20 | For more information run `rpiboot -h`. 21 | 22 | ## Building 23 | 24 | Once compiled, rpiboot can either be run locally from the source directory by specifying 25 | the directory of the boot image e.g. `sudo ./rpiboot -d mass-storage-gadget`. 26 | If no arguments are specified rpiboot will attempt to boot the mass-storage-gadget 27 | from `INSTALL_PREFIX/share/mass-storage-gadget64`. 28 | 29 | ### Linux / Cygwin / WSL 30 | Clone this repository on your Pi or other Linux machine. 31 | Make sure that the system date is set correctly, otherwise Git may produce an error. 32 | 33 | * This git repository uses symlinks. For Windows builds clone the repository under Cygwin. 34 | * Instead of duplicating the EEPROM binaries and tools the rpi-eeprom repository 35 | is included as a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) 36 | 37 | ```bash 38 | sudo apt install git libusb-1.0-0-dev pkg-config build-essential 39 | git clone --recurse-submodules --shallow-submodules --depth=1 https://github.com/raspberrypi/usbboot 40 | cd usbboot 41 | make 42 | # Either 43 | sudo ./rpiboot -d mass-storage-gadget64 44 | # Or, install rpiboot to /usr/bin and boot images to /usr/share 45 | sudo make install 46 | sudo rpiboot 47 | 48 | ``` 49 | 50 | `sudo` isn't required if you have write permissions for the `/dev/bus/usb` device. 51 | 52 | ### macOS 53 | From a macOS machine, you can also run usbboot, just follow the same steps: 54 | 55 | 1. Clone the `usbboot` repository 56 | 2. Install `libusb` (`brew install libusb`) 57 | 3. Install `pkg-config` (`brew install pkg-config`) 58 | 4. (Optional) Export the `PKG_CONFIG_PATH` so that it includes the directory enclosing `libusb-1.0.pc` 59 | 5. Build using make - installing to /usr/local rather than /usr/bin is recommended on macOS 60 | 6. Run the binary 61 | 62 | ```bash 63 | git clone --recurse-submodules --shallow-submodules --depth=1 https://github.com/raspberrypi/usbboot 64 | cd usbboot 65 | brew install libusb 66 | brew install pkg-config 67 | make INSTALL_PREFIX=/usr/local 68 | # Either 69 | sudo ./rpiboot -d mass-storage-gadget64 70 | # Or, install rpiboot to /usr/local/bin and boot images to /usr/local/share 71 | sudo make INSTALL_PREFIX=/usr/local install 72 | sudo rpiboot 73 | ``` 74 | 75 | If the build is unable to find the header file `libusb.h` then most likely the `PKG_CONFIG_PATH` is not set properly. 76 | This should be set via `export PKG_CONFIG_PATH="$(brew --prefix libusb)/lib/pkgconfig"`. 77 | 78 | If the build fails on an ARM-based Mac with a linker error such as `ld: warning: ignoring file '/usr/local/Cellar/libusb/1.0.27/lib/libusb-1.0.0.dylib': found architecture 'x86_64', required architecture 'arm64'` then you may need to build and install `libusb-1.0` yourself: 79 | ``` 80 | curl -OL https://github.com/libusb/libusb/releases/download/v1.0.27/libusb-1.0.27.tar.bz2 81 | tar -xf libusb-1.0.27.tar.bz2 82 | cd libusb-1.0.27 83 | ./configure 84 | make 85 | make check 86 | sudo make INSTALL_PREFIX=/usr/local install 87 | cd .. 88 | ``` 89 | Running `make` again should now succeed. 90 | 91 | ### Updating the rpi-eeprom submodule 92 | After updating the usbboot repo (`git pull --rebase origin master`) update the 93 | submodules by running 94 | 95 | ```bash 96 | git submodule update --init 97 | ``` 98 | 99 | ## Running 100 | 101 | ### Compute Module 3 102 | Fit the `EMMC-DISABLE` jumper on the Compute Module IO board before powering on the board 103 | or connecting the USB cable. 104 | 105 | ### Compute Module 4 106 | On Compute Module 4 EMMC-DISABLE / nRPIBOOT (GPIO 40) must be fitted to switch the ROM to usbboot mode. 107 | Otherwise, the SPI EEPROM bootloader image will be loaded instead. 108 | 109 | ### Compute Module 5 110 | On Compute Module 5 EMMC-DISABLE / nRPIBOOT (BCM2712 GPIO 20) must be fitted to switch the ROM to usbboot mode. 111 | Otherwise, the SPI EEPROM bootloader image will be loaded instead. 112 | 113 | ### Raspberry Pi 5 114 | * Disconnect the USB-C cable. Power must be removed rather than just running "sudo shutdown now" 115 | * Hold the power button down 116 | * Connect the USB-C cable (from the `RPIBOOT` host to the Pi 5) 117 | 118 | 119 | ## Compute Module provisioning extensions 120 | In addition to the MSD functionality, there are a number of other utilities that can be loaded 121 | via RPIBOOT on Compute Module 4 and Compute Module 5. 122 | 123 | | Directory | Description | 124 | | ----------| ----------- | 125 | | [recovery](recovery/README.md) | Updates the bootloader EEPROM on a Compute Module 4 | 126 | | [recovery5](recovery5/README.md) | Updates the bootloader EEPROM on a Raspberry Pi 5 | 127 | | [mass-storage-gadget64](mass-storage-gadget64/README.md) | Mass storage gadget with 64-bit Kernel for BCM2711 and BCM2712 | 128 | | [secure-boot-recovery](secure-boot-recovery/README.md) | Pi4 secure-boot bootloader flash and OTP provisioning | 129 | | [secure-boot-recovery5](secure-boot-recovery5/README.md) | Pi5 secure-boot bootloader flash and OTP provisioning | 130 | | [rpi-imager-embedded](rpi-imager-embedded/README.md) | Runs the embedded version of Raspberry Pi Imager on the target device | 131 | | [secure-boot-example](secure-boot-example/README.md) | Simple Linux initrd with a UART console. | 132 | 133 | ## Booting Linux 134 | The `RPIBOOT` protocol provides a virtual file system to the Raspberry Pi bootloader and GPU firmware. It's therefore possible to 135 | boot Linux. To do this, you will need to copy all of the files from a Raspberry Pi boot partition plus create your own 136 | initramfs. 137 | On Raspberry Pi 4 / CM4 the recommended approach is to use a `boot.img` which is a FAT disk image containing 138 | the minimal set of files required from the boot partition. 139 | 140 | ## Troubleshooting 141 | 142 | This section describes how to diagnose common `rpiboot` failures for Compute Modules. Whilst `rpiboot` is tested on every Compute Module during manufacture the system relies on multiple hardware and software elements. The aim of this guide is to make it easier to identify which component is failing. 143 | 144 | ### Product Information Portal 145 | The [Product Information Portal](https://pip.raspberrypi.com/) contains the official documentation for hardware revision changes for Raspberry Pi computers. 146 | Please check this first to check that the software is up to date. 147 | 148 | ### Hardware 149 | * Inspect the Compute Module pins and connector for signs of damage and verify that the socket is free from debris. 150 | * Check that the Compute Module is fully inserted. 151 | * Check that `nRPIBOOT` / EMMC disable is pulled low BEFORE powering on the device. 152 | * On BCM2711, if the USB cable is disconnected and the nRPIBOOT jumper is fitted then the green LED should be OFF. If the LED is on then the ROM is detecting that the GPIO for nRPIBOOT is high. 153 | * Remove any hubs between the Compute Module and the host. 154 | * Disconnect all other peripherals from the IO board. 155 | * Verify that the red power LED switches on when the IO board is powered. 156 | * Use another computer to verify that the USB cable for `rpiboot` can reliably transfer data. For example, connect it to a Raspberry Pi keyboard with other devices connected to the keyboard USB hub. 157 | 158 | #### Hardware - CM4 / CM5 159 | * The CM5 EEPROM supports MMC, USB-MSD, USB 2.0 (CM4 only), Network and NVMe boot by default. Try booting to Linux from an alternate boot mode (e.g. network) to verify the `nRPIBOOT` GPIO can be pulled low and that the USB 2.0 interface is working. 160 | * If `rpiboot` is running but the mass storage device does not appear then try running the `rpiboot -d mass-storage-gadget64` because this uses Linux instead of a custom VPU firmware to implement the mass-storage gadget. This also provides a login console on UART and HDMI. 161 | 162 | #### Hardware - Raspberry Pi 5 / Compute Module 5 163 | * Press, and hold the power button before supplying power to the device. 164 | * Release the power button immediately after supplying power to the device. 165 | * Remove any non-essential USB peripherals or HATs. 166 | * Use a USB-3 port capable of supplying at least 900mA and use a high quality USB-C cable OR supply additional power via the 40-pin header. 167 | 168 | ### Software 169 | The recommended host setup is Raspberry Pi with Raspberry Pi OS. Alternatively, most Linux X86 builds are also suitable. Windows adds some extra complexity for the USB drivers so we recommend debugging on Linux first. 170 | 171 | * Update to the latest software release using `apt update rpiboot` or download and rebuild this repository from Github. 172 | * Run `rpiboot -v | tee log` to capture verbose log output. N.B. This can be very verbose on some systems. 173 | 174 | #### Boot flow 175 | The `rpiboot` system runs in multiple stages. The ROM, bootcode.bin, the VPU firmware (start.elf) and for the `mass-storage-gadget64` or `rpi-imager` a Linux initramfs. Each stage disconnects the USB device and presents a different USB descriptor. Each stage will appears as a new USB device connect in the `dmesg` log. 176 | 177 | See also: [EEPROM boot flow](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#eeprom-boot-flow) 178 | 179 | #### bootcode.bin 180 | Be careful not to overwrite `bootcode.bin` or `bootcode4.bin` with the executable from a different subdirectory. The `rpiboot` process simply looks for a file called `bootcode.bin` (or `bootcode4.bin` on BCM2711). However, the file in `recovery`/`secure-boot-recovery` directories is actually the `recovery.bin` EEPROM flashing tool. 181 | 182 | ### Diagnostics 183 | * Monitor the Linux `dmesg` output and verify that a BCM boot device is detected immediately after powering on the device. If not, please check the `hardware` section. 184 | * Check the green activity LED. On Compute Module 4 this is activated by the software bootloader and should remain on. If not, then it's likely that the initial USB transfer to the ROM failed. 185 | * On Compute Module 4 connect a HDMI monitor for additional debug output. Flashing the EEPROM using `recovery.bin` will show a green screen and the `mass-storage-gadget64` enables a console on the HDMI display. 186 | * If `rpiboot` starts to download `bootcode4.bin` but the transfer fails then can indicate a cable issue OR a corrupted file. Check the hash of `bootcode.bin` file against this repository and check `dmesg` for USB error. 187 | * If `bootcode.bin` or the `start.elf` detects an error then [error-code](https://www.raspberrypi.com/documentation/computers/configuration.html#led-warning-flash-codes) will be indicated by flashing the green activity LED. 188 | * Add `uart_2ndstage=1` to the `config.txt` file in `msd/` or `recovery/` directories to enable UART debug output. 189 | * Add `recovery_metadata=1` to the `config.txt` file in `recovery/` or `recovery5/` directory to enable metadata JSON output. 190 | 191 | ## Reading device metadata from OTP via rpiboot 192 | The `rpiboot` "recovery" modules provide a facility to read the device OTP information. This can be run either as a provisioning step or as a standalone operation. 193 | 194 | To enable this make sure that `recovery_metadata=1` is set in the recovery `config.txt` file and pass the `-j metadata` flag to `rpiboot`. 195 | 196 | See [board revision](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#new-style-revision-codes-in-use) documentation to decode the `BOARD_ATTR` field. 197 | 198 | Example command to extract the OTP metadata from a Compute Module 4: 199 | ```bash 200 | cd recovery 201 | mkdir -p metadata 202 | sudo rpiboot -j metadata -d . 203 | ``` 204 | 205 | Example metadata file contents written to `metadata/SERIAL_NUMBER.json` 206 | ```json 207 | { 208 | "MAC_ADDR" : "d8:3a:dd:05:ee:78", 209 | "CUSTOMER_KEY_HASH" : "8251a63a2edee9d8f710d63e9da5d639064929ce15a2238986a189ac6fcd3cee", 210 | "BOOT_ROM" : "0000c8b0", 211 | "BOARD_ATTR" : "00000000", 212 | "USER_BOARDREV" : "c03141", 213 | "JTAG_LOCKED" : "0", 214 | "ADVANCED_BOOT" : "0000e8e8" 215 | } 216 | ``` 217 | 218 | 219 | ## Secure Boot 220 | This repository contains the low-level tools and firmware images for enabling secure-boot/verified boot on Compute Module 4 and Compute Module 5. 221 | 222 | ### Tutorial 223 | 224 | Creating a secure-boot system with encrypted file-system support from scratch can be a complicated process. 225 | 226 | The recommended starting point is the [Raspberry Pi Secure Boot Provisioner](https://github.com/raspberrypi/rpi-sb-provisioner) 227 | which provides an automated mechanism for installing [Raspberry Pi OS - pi-gen](https://github.com/RPi-Distro/pi-gen) images 228 | with secure-boot and root file-system encryption. 229 | 230 | If you are porting an existing Buildroot/Yocto image then please see the 231 | [secure boot code signing tutorial](secure-boot-example/README.md) uses a minimal buildroot initramfs OS image 232 | to demonstrate the low-level code-signing aspects. 233 | 234 | ### Additional documentation 235 | 236 | * Secure boot BCM2711 [chain of trust diagram](docs/secure-boot-chain-of-trust-2711.pdf). 237 | * Secure boot BCM2712 [chain of trust diagram](docs/secure-boot-chain-of-trust-2712.pdf). 238 | * Secure boot [configuration properties](https://www.raspberrypi.com/documentation/computers/config_txt.html#secure-boot-configuration-properties). 239 | * Device tree [bootloader signed-boot property](https://www.raspberrypi.com/documentation/computers/configuration.html#bcm2711-and-bcm2712-specific-bootloader-properties-chosenbootloader). 240 | * Device tree [public key - NVMEM property](https://www.raspberrypi.com/documentation/computers/configuration.html#nvmem-nodes). 241 | * Raspberry Pi [OTP registers](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#otp-register-and-bit-definitions). 242 | * Raspberry Pi [device specific private key](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#device-specific-private-key). 243 | 244 | ### Host Setup 245 | Secure boot require a 2048 bit RSA asymmetric keypair and the Python `pycrytodome` module to sign the bootloader EEPROM config and boot image. 246 | 247 | #### Install Python Crypto Support (the pycryptodomex module) 248 | ```bash 249 | sudo apt install python3-pycryptodome 250 | ``` 251 | 252 | #### Create an RSA key-pair using OpenSSL. Must be 2048 bits 253 | ```bash 254 | cd $HOME 255 | openssl genrsa 2048 > private.pem 256 | ``` 257 | 258 | ### Secure Boot - configuration 259 | * Please see the [secure boot EEPROM guide](secure-boot-recovery/README.md) to enable via rpiboot `recovery.bin`. 260 | * Please see the [secure boot MSD guide](mass-storage-gadget64/README.md) for instructions about to mount the EMMC via USB mass-storage once secure-boot has been enabled. 261 | 262 | ## Secure Boot - image creation 263 | Secure Boot requires self-contained ramdisk (`boot.img`) FAT image to be created containing the GPU 264 | firmware, kernel and any other dependencies that would normally be loaded from the boot partition. 265 | 266 | This plus a signature file (`boot.sig`) must be placed in the boot partition of the Raspberry Pi 267 | or network download location. 268 | 269 | The `boot.img` file should contain:- 270 | * The kernel 271 | * Device tree overlays 272 | * GPU firmware (start.elf and fixup.dat) 273 | * Linux initramfs containing the application OR scripts to mount/create an encrypted file-system. 274 | 275 | 276 | ### Disk encryption 277 | Secure-boot is responsible for loading the Kernel + initramfs and loads all of the data 278 | from a single `boot.img` file stored on an unencrypted FAT/EFI partition. 279 | 280 | **There is no support in the ROM or firmware for full-disk encryption.** 281 | 282 | If a custom OS image needs to use an encrypted file-system then this would normally be implemented 283 | via scripts within the initramfs. 284 | 285 | Raspberry Pi computers do not have a secure enclave, however, it's possible to store a 256 bit 286 | [device specific private key](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#device-specific-private-key) 287 | in OTP. The key is accessible to any process with access to `/dev/vcio` (`vcmailbox`), therefore, the 288 | secure-boot OS must ensure that access to this interface is restricted. 289 | 290 | **It is not possible to prevent code running in ARM supervisor mode (e.g. kernel code) from accessing OTP hardware directly** 291 | 292 | See also: 293 | * [LUKS](https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup) 294 | * [cryptsetup FAQ](https://gitlab.com/cryptsetup/cryptsetup/-/wikis/FrequentlyAskedQuestions) 295 | * [rpi-otp-private-key](./tools/rpi-otp-private-key) 296 | 297 | The [secure boot tutorial](secure-boot-example/README.md) contains a `boot.img` that supports cryptsetup and a simple example. 298 | 299 | ### Building `boot.img` using buildroot 300 | 301 | The `secure-boot-example` directory contains a simple `boot.img` example with working HDMI, 302 | network, UART console and common tools in an initramfs. 303 | 304 | This was generated from the [raspberrypi-signed-boot](https://github.com/raspberrypi/buildroot/blob/raspberrypi-signed-boot/README.md) 305 | buildroot config. Whilst not a generic fully featured configuration it should be relatively 306 | straightforward to cherry-pick the `raspberrypi-secure-boot` package and helper scripts into 307 | other buildroot configurations. 308 | 309 | #### Minimum firmware version 310 | The firmware must be new enough to support secure boot. The latest firmware APT 311 | package supports secure boot. To download the firmware files directly. 312 | 313 | ```bash 314 | git clone --depth 1 --branch stable https://github.com/raspberrypi/firmware 315 | ``` 316 | 317 | To check the version information within a `start4.elf` firmware file run 318 | ```bash 319 | strings start4.elf | grep VC_BUILD_ 320 | ``` 321 | 322 | #### Verifying the contents of a `boot.img` file 323 | To verify that the boot image has been created correctly use losetup to mount the .img file. 324 | 325 | ```bash 326 | sudo su 327 | mkdir -p boot-mount 328 | LOOP=$(losetup -f) 329 | losetup -f boot.img 330 | mount ${LOOP} boot-mount/ 331 | 332 | echo boot.img contains 333 | find boot-mount/ 334 | 335 | umount boot-mount 336 | losetup -d ${LOOP} 337 | rmdir boot-mount 338 | ``` 339 | 340 | #### Signing the boot image 341 | For secure-boot, `rpi-eeprom-digest` extends the current `.sig` format of 342 | sha256 + timestamp to include an hex format RSA bit PKCS#1 v1.5 signature. The key length 343 | must be 2048 bits. 344 | 345 | ```bash 346 | ../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}" 347 | ``` 348 | 349 | To verify the signature of an existing image set the `PUBLIC_KEY_FILE` environment variable 350 | to the path of the public key file in PEM format. 351 | 352 | ```bash 353 | ../tools/rpi-eeprom-digest -i boot.img -k "${PUBLIC_KEY_FILE}" -v boot.sig 354 | ``` 355 | 356 | 357 | #### Hardware security modules 358 | `rpi-eeprom-digest` is a shell script that wraps a call to `openssl dgst -sign`. 359 | If the private key is stored within a hardware security module instead of 360 | a .PEM file the `openssl` command will need to be replaced with the appropriate call to the HSM. 361 | 362 | `rpi-eeprom-digest` called by `update-pieeprom.sh` to sign the EEPROM config file. 363 | 364 | The RSA public key must be stored within the EEPROM so that it can be used by the bootloader. 365 | By default, the RSA public key is automatically extracted from the private key PEM file. Alternatively, 366 | the public key may be specified separately via the `-p` argument to `update-pieeprom.sh` and `rpi-eeprom-config`. 367 | 368 | To extract the public key in PEM format from a private key PEM file, run: 369 | ```bash 370 | openssl rsa -in private.pem -pubout -out public.pem 371 | ``` 372 | 373 | #### Copy the secure boot image to the boot partition on the Raspberry Pi. 374 | Copy `boot.img` and `boot.sig` to the boot filesystem. 375 | Secure boot images can be loaded from any of the normal boot modes (e.g. SD, USB, Network). 376 | -------------------------------------------------------------------------------- /bin2c.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main(int argc, char * argv[]) 8 | { 9 | FILE * fp_in, * fp_out; 10 | unsigned int length, left; 11 | char fname[256], * p; 12 | uint8_t buffer[256]; 13 | 14 | if(argc != 3) 15 | { 16 | printf("Usage: %s \n", argv[0]); 17 | exit(-1); 18 | } 19 | 20 | fp_in = fopen(argv[1], "rb"); 21 | if(fp_in == NULL) 22 | { 23 | printf("Failed to open file %s for reading\n", argv[1]); 24 | exit(-1); 25 | } 26 | 27 | fp_out = fopen(argv[2], "wt"); 28 | if(fp_out == NULL) 29 | { 30 | printf("Failed to open file %s for output\n", argv[2]); 31 | exit(-1); 32 | } 33 | 34 | fseek(fp_in, 0, SEEK_END); 35 | length = ftell(fp_in); 36 | fseek(fp_in, 0, SEEK_SET); 37 | left = length; 38 | 39 | fprintf(fp_out, "/* Automatically generated file from %s */\n", argv[1]); 40 | strcpy(fname, argv[1]); 41 | for(p = fname; *p; p++) 42 | if(!isalnum((int) *p)) 43 | *p = '_'; 44 | fprintf(fp_out, "unsigned int %s_len = %d;\n", fname, length); 45 | fprintf(fp_out, "unsigned char %s[] = {\n\t", fname); 46 | 47 | while(left) 48 | { 49 | int to_read = left < sizeof(buffer) ? left : sizeof(buffer); 50 | int bytes = fread(buffer, 1, to_read, fp_in); 51 | int i; 52 | 53 | for(i = 0; i < bytes; i++) 54 | fprintf(fp_out, "0x%02x%s", buffer[i], (i % 16) == 15 ? ",\n\t" : ", "); 55 | 56 | left -= bytes; 57 | } 58 | 59 | fprintf(fp_out, "\n\t};\n"); 60 | 61 | fclose(fp_in); 62 | fclose(fp_out); 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /bootfiles.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Reads bootloader files (e.g. DDR init) from a single packaged file 8 | // to ensure that the DDR init code, firmware and next stage are in sync. 9 | // For simplicity the implementation uses .tar and other files e.g. config.txt 10 | // maybe added to the package. 11 | 12 | extern int verbose; 13 | #define BLOCK_SIZE 512 14 | 15 | struct tar_header{ 16 | char filename[100]; 17 | char mode[8]; 18 | char uid[8]; 19 | char gid[8]; 20 | char size[12]; 21 | char mtime[12]; 22 | char csum[8]; 23 | char link[1]; 24 | char lname[100]; 25 | } __attribute__((packed)); 26 | 27 | unsigned char *bootfiles_read(const char *archive, const char *filename, unsigned long *psize) 28 | { 29 | FILE *fp = NULL; 30 | struct tar_header hdr; 31 | unsigned char *data = NULL; 32 | long archive_size; 33 | 34 | fp = fopen(archive, "rb"); 35 | if (!fp){ 36 | goto fail; 37 | } 38 | if (fseek(fp, 0, SEEK_END) < 0) 39 | goto fail; 40 | archive_size = ftell(fp); 41 | 42 | if (fseek(fp, 0, SEEK_SET) < 0) 43 | goto fail; 44 | do 45 | { 46 | unsigned long size; 47 | long offset; 48 | 49 | offset = ftell(fp); 50 | if (fread(&hdr, sizeof(hdr), 1, fp) != 1) 51 | goto fail; 52 | 53 | if (fseek(fp, BLOCK_SIZE - sizeof(hdr), SEEK_CUR) < 0) 54 | goto fail; 55 | offset = ftell(fp); 56 | 57 | if (offset == archive_size) 58 | break; 59 | 60 | size = strtoul(hdr.size, NULL, 8); 61 | if (offset + size > (unsigned long) archive_size) 62 | { 63 | fprintf(stderr, "Corrupted archive"); 64 | goto fail; 65 | } 66 | hdr.filename[sizeof(hdr.filename) - 1] = 0; 67 | if (verbose > 1) 68 | printf("%s position %08lx size %lu\n", hdr.filename, ftell(fp), size); 69 | 70 | if (strcasecmp(hdr.filename, filename) == 0) 71 | { 72 | data = malloc(size); 73 | if (fread(data, 1, size, fp) != size) 74 | goto fail; 75 | *psize = size; 76 | goto end; 77 | } 78 | else 79 | { 80 | if (fseek(fp, (size + BLOCK_SIZE -1) & ~(BLOCK_SIZE -1), SEEK_CUR) < 0) 81 | goto fail; 82 | } 83 | } while (!feof(fp)); 84 | 85 | if (verbose > 1) 86 | printf("File %s not found in %s\n", filename, archive); 87 | 88 | goto end; 89 | 90 | fail: 91 | if (data) 92 | free(data); 93 | printf("read_file: Failed to read \"%s\" from \"%s\" - \%s\n", filename, archive, strerror(errno)); 94 | end: 95 | if (fp) 96 | fclose(fp); 97 | if (verbose && data) 98 | printf("Completed file-read %s in archive %s length %lu\n", filename, archive, *psize); 99 | return data; 100 | } 101 | -------------------------------------------------------------------------------- /bootfiles.h: -------------------------------------------------------------------------------- 1 | #ifndef BOOTFILE_H 2 | #define BOOTFILE_H 3 | unsigned char *bootfiles_read(const char *archive, const char *filename, unsigned long *psize); 4 | #endif 5 | -------------------------------------------------------------------------------- /decode_duid.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define DUID_LENGTH 36 6 | 7 | // Return the c40 value for a character 8 | int char_to_c40(char val) 9 | { 10 | if ((val >= 'a') && (val <= 'z')) 11 | val = val - 32; 12 | if ((val >= '0') && (val <= '9')) 13 | return 4 + val - '0'; 14 | else if ((val >= 'A') && (val <= 'Z')) 15 | return 14 + val - 'A'; 16 | return -1; 17 | } 18 | 19 | // Assign the character for a c40 value 20 | char c40_to_char(int val) 21 | { 22 | if ((val >= char_to_c40('0')) && (val <= char_to_c40('9'))) 23 | return '0' + val - char_to_c40('0'); 24 | else if ((val >= char_to_c40('A')) && (val <= char_to_c40('Z'))) 25 | return 'A' + val - char_to_c40('A'); 26 | return '\0'; 27 | } 28 | 29 | // Add to a list of c40 values a half word encoding 30 | void decode_half_word(uint16_t half_word, int *c40_list, int *index) 31 | { 32 | c40_list[*index] = (int)((half_word - 1) / 1600); 33 | half_word -= c40_list[(*index)++] * 1600; 34 | c40_list[*index] = (int)((half_word - 1) / 40); 35 | half_word -= c40_list[(*index)++] * 40; 36 | c40_list[(*index)++] = half_word - 1; 37 | } 38 | 39 | // Decode a duid from a list of words 40 | int duid_decode_c40(char * str_of_words, char *c40_str) 41 | { 42 | int c40_list[DUID_LENGTH], i = 0, c; 43 | uint32_t word; 44 | uint16_t msig; 45 | 46 | char *word_str = strtok(str_of_words, "_"); 47 | while (word_str != NULL) 48 | { 49 | word = strtoul(word_str, NULL, 16); 50 | if (word == 0) break; 51 | decode_half_word(word & 0xFFFF, c40_list, &i); 52 | 53 | msig = word >> 16; 54 | if (msig > 0) 55 | decode_half_word(msig, c40_list, &i); 56 | 57 | word_str = strtok(NULL, "_"); 58 | } 59 | 60 | for (c = 0; c < i; c++) 61 | { 62 | c40_str[c] = c40_to_char(c40_list[c]); 63 | if (!c40_str[c]) return -1; 64 | } 65 | c40_str[i] = '\0'; 66 | return 0; 67 | } -------------------------------------------------------------------------------- /decode_duid.h: -------------------------------------------------------------------------------- 1 | #ifndef DECODE_DUID_H 2 | #define DECODE_DUID_H 3 | int duid_decode_c40(char * str_of_words, char *c40_str); 4 | #endif -------------------------------------------------------------------------------- /docs/secure-boot-chain-of-trust-2711.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/docs/secure-boot-chain-of-trust-2711.pdf -------------------------------------------------------------------------------- /docs/secure-boot-chain-of-trust-2712.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/docs/secure-boot-chain-of-trust-2712.pdf -------------------------------------------------------------------------------- /eeprom-erase/README.md: -------------------------------------------------------------------------------- 1 | The `erase_eeprom` `config.txt` option causes `recovery.bin` to execute a chip-erase operation on the bootloader SPI EEPROM. 2 | This is a test/debug option and there is no need to manually erase an EEPROM before flashing it. 3 | 4 | If the SPI EEPROM is erased then the Raspberry Pi will not boot until a new EEPROM image has been written via `RPIBOOT` 5 | or the Raspberry Pi Imager (Pi4 and Pi400 only). 6 | 7 | ```bash 8 | cd erase-eeprom 9 | ../rpiboot -d . 10 | ``` 11 | -------------------------------------------------------------------------------- /eeprom-erase/bootcode4.bin: -------------------------------------------------------------------------------- 1 | ../firmware/2711/bootcode4.bin -------------------------------------------------------------------------------- /eeprom-erase/config.txt: -------------------------------------------------------------------------------- 1 | erase_eeprom=1 2 | uart_2ndstage=1 3 | -------------------------------------------------------------------------------- /firmware/2711/bootcode4.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/firmware/2711/bootcode4.bin -------------------------------------------------------------------------------- /firmware/2711/pieeprom.bin: -------------------------------------------------------------------------------- 1 | ../../rpi-eeprom/firmware-2711/latest/pieeprom-2025-05-08.bin -------------------------------------------------------------------------------- /firmware/2711/recovery.bin: -------------------------------------------------------------------------------- 1 | ../../rpi-eeprom/firmware-2711/latest/recovery.bin -------------------------------------------------------------------------------- /firmware/2712/pieeprom.bin: -------------------------------------------------------------------------------- 1 | ../../rpi-eeprom/firmware-2712/latest/pieeprom-2025-05-08.bin -------------------------------------------------------------------------------- /firmware/2712/recovery.bin: -------------------------------------------------------------------------------- 1 | ../../rpi-eeprom/firmware-2712/latest/recovery.bin -------------------------------------------------------------------------------- /firmware/bootfiles.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/firmware/bootfiles.bin -------------------------------------------------------------------------------- /fmemopen.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2011-2014 NimbusKit 3 | // Originally ported from https://github.com/ingenuitas/python-tesseract/blob/master/fmemopen.c 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | struct fmem { 24 | size_t pos; 25 | size_t size; 26 | char *buffer; 27 | }; 28 | typedef struct fmem fmem_t; 29 | 30 | static int readfn(void *handler, char *buf, int size) { 31 | fmem_t *mem = handler; 32 | size_t available = mem->size - mem->pos; 33 | 34 | if (size > (int)available) { 35 | size = available; 36 | } 37 | memcpy(buf, mem->buffer + mem->pos, sizeof(char) * size); 38 | mem->pos += size; 39 | 40 | return size; 41 | } 42 | 43 | static int writefn(void *handler, const char *buf, int size) { 44 | fmem_t *mem = handler; 45 | size_t available = mem->size - mem->pos; 46 | 47 | if (size > (int)available) { 48 | size = available; 49 | } 50 | memcpy(mem->buffer + mem->pos, buf, sizeof(char) * size); 51 | mem->pos += size; 52 | 53 | return size; 54 | } 55 | 56 | static fpos_t seekfn(void *handler, fpos_t offset, int whence) { 57 | size_t pos; 58 | fmem_t *mem = handler; 59 | 60 | switch (whence) { 61 | case SEEK_SET: { 62 | if (offset >= 0) { 63 | pos = (size_t)offset; 64 | } else { 65 | pos = 0; 66 | } 67 | break; 68 | } 69 | case SEEK_CUR: { 70 | if (offset >= 0 || (size_t)(-offset) <= mem->pos) { 71 | pos = mem->pos + (size_t)offset; 72 | } else { 73 | pos = 0; 74 | } 75 | break; 76 | } 77 | case SEEK_END: pos = mem->size + (size_t)offset; break; 78 | default: return -1; 79 | } 80 | 81 | if (pos > mem->size) { 82 | return -1; 83 | } 84 | 85 | mem->pos = pos; 86 | return (fpos_t)pos; 87 | } 88 | 89 | static int closefn(void *handler) { 90 | free(handler); 91 | return 0; 92 | } 93 | 94 | FILE *fmemopen(void *buf, size_t size, const char *mode) { 95 | #pragma unused(mode) 96 | // This data is released on fclose. 97 | fmem_t* mem = (fmem_t *) malloc(sizeof(fmem_t)); 98 | 99 | // Zero-out the structure. 100 | memset(mem, 0, sizeof(fmem_t)); 101 | 102 | mem->size = size; 103 | mem->buffer = buf; 104 | 105 | // funopen's man page: https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/funopen.3.html 106 | return funopen(mem, readfn, writefn, seekfn, closefn); 107 | } 108 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "bootfiles.h" 9 | #include "decode_duid.h" 10 | #include "msd/bootcode.h" 11 | #include "msd/start.h" 12 | #include "msd/bootcode4.h" 13 | // 2712 doesn't use start5.elf 14 | 15 | /* 16 | * Old OS X/BSD do not implement fmemopen(). If the version of POSIX 17 | * supported is old enough that fmemopen() isn't included, assume 18 | * we're on a BSD compatible system and define a fallback fmemopen() 19 | * that depends on funopen(). 20 | */ 21 | #if _POSIX_VERSION <= 200112L 22 | #include "fmemopen.c" 23 | #endif 24 | 25 | #define SELECTION_MODE_VID 0 26 | #define SELECTION_MODE_SERIAL 1 27 | 28 | int selection_mode = SELECTION_MODE_VID; 29 | char * target_serialno = NULL; 30 | int signed_boot = 0; 31 | int verbose = 0; 32 | int metadata = 0; 33 | int loop = 0; 34 | int overlay = 0; 35 | long delay = 500; 36 | char * directory = NULL; 37 | char * metadata_path = NULL; 38 | char pathname[18] = {0}; 39 | char * targetpathname = NULL; 40 | uint8_t targetPortNo = 99; 41 | 42 | int out_ep; 43 | int in_ep; 44 | int bcm2711; 45 | int bcm2712; 46 | 47 | #define MAX_PATH_LEN 256 48 | #define FILE_NAME_LENGTH 250 49 | #define DUID_LENGTH 36 50 | 51 | unsigned char serial_num[MAX_PATH_LEN]; 52 | static char bootfiles_path[MAX_PATH_LEN]; 53 | static int use_bootfiles; 54 | static void *bootfile_data; 55 | static FILE * check_file(const char * dir, const char *fname, int use_fmem); 56 | static int second_stage_prep(FILE *fp, FILE *fp_sig); 57 | 58 | typedef struct MESSAGE_S { 59 | int length; 60 | unsigned char signature[20]; 61 | } boot_message_t; 62 | 63 | void usage(int error) 64 | { 65 | FILE * dest = error ? stderr : stdout; 66 | 67 | fprintf(dest, "Usage: rpiboot\n"); 68 | fprintf(dest, " or: rpiboot -d [directory]\n"); 69 | fprintf(dest, "Boot a Raspberry Pi in device mode either directly into a mass storage device\n"); 70 | fprintf(dest, "or provide a set of boot files in a directory from which to boot. This can\n"); 71 | fprintf(dest, "then contain a initramfs to boot through to linux kernel\n\n"); 72 | fprintf(dest, "To flash the default bootloader EEPROM image on Compute Module 4 run\n"); 73 | fprintf(dest, "rpiboot -d recovery\n\n"); 74 | fprintf(dest, "For more information about the bootloader EEPROM please see:\n"); 75 | fprintf(dest, "https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-bootloader-configuration\n\n"); 76 | fprintf(dest, "rpiboot : Boot the device into mass storage device\n"); 77 | fprintf(dest, "rpiboot -d [directory] : Boot the device using the boot files in 'directory'\n"); 78 | fprintf(dest, "Further options:\n"); 79 | fprintf(dest, " -l : Loop forever\n"); 80 | fprintf(dest, " -o : Use files from overlay subdirectory if they exist (when using a custom directory)\n"); 81 | fprintf(dest, " USB Path (1-1.3.2 for example) is shown in verbose mode.\n"); 82 | fprintf(dest, " (bootcode.bin is always preloaded from the base directory)\n"); 83 | fprintf(dest, " -m delay : Microseconds delay between checking for new devices (default 500)\n"); 84 | fprintf(dest, " -v : Verbose\n"); 85 | fprintf(dest, " -V : Displays the version string and exits\n"); 86 | fprintf(dest, " -s : Signed using bootsig.bin\n"); 87 | fprintf(dest, " -0/1/2/3/4/5/6 : Only look for CMs attached to USB port number 0-6\n"); 88 | fprintf(dest, " -p [pathname] : Only look for CM with USB pathname\n"); 89 | fprintf(dest, " -i [serialno] : Only look for a Raspberry Pi Device with a given serialno\n"); 90 | fprintf(dest, " -j [path] : Enable output of metadata JSON files in a given directory for BCM2712/2711\n"); 91 | fprintf(dest, " -h : This help\n"); 92 | 93 | exit(error ? -1 : 0); 94 | } 95 | 96 | libusb_device_handle * LIBUSB_CALL open_device_with_serialno( 97 | libusb_context *ctx, char *serialno) 98 | { 99 | struct libusb_device **devices; 100 | struct libusb_device *cursor; 101 | struct libusb_device_handle *handle = NULL; 102 | int r = 0; 103 | 104 | if (libusb_get_device_list(ctx, &devices) < 0) 105 | return NULL; 106 | 107 | uint32_t device_index = 0; 108 | unsigned char *serial_buffer = calloc(33, sizeof(uint8_t)); 109 | if (serial_buffer == NULL) 110 | goto out_serialno; 111 | 112 | while ((cursor = devices[device_index++]) != NULL) { 113 | struct libusb_device_descriptor desc; 114 | r = libusb_get_device_descriptor(cursor, &desc); 115 | if (r < 0) 116 | goto out_serialno; 117 | 118 | r = libusb_open(cursor, &handle); 119 | if (r < 0) 120 | goto out_serialno; 121 | 122 | if (handle != NULL) { 123 | if (libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, serial_buffer, 31) >= 0) { 124 | if (strncmp(serialno, (char *)serial_buffer, 32)) { 125 | libusb_close(handle); 126 | handle = NULL; 127 | continue; 128 | } 129 | 130 | // Match the magic numbers for Raspberry Pi generations 131 | if (desc.idVendor == 0x0a5c) { 132 | if (desc.idProduct == 0x2763 || 133 | desc.idProduct == 0x2764 || 134 | desc.idProduct == 0x2711 || 135 | desc.idProduct == 0x2712) 136 | { 137 | FILE *fp_second_stage = NULL; 138 | FILE *fp_sign = NULL; 139 | const char *second_stage; 140 | 141 | bcm2711 = (desc.idProduct == 0x2711); 142 | bcm2712 = (desc.idProduct == 0x2712); 143 | if (bcm2711) 144 | second_stage = "bootcode4.bin"; 145 | else if (bcm2712) 146 | second_stage = "bootcode5.bin"; 147 | else 148 | second_stage = "bootcode.bin"; 149 | 150 | fp_second_stage = check_file(directory, second_stage, 1); 151 | if (!fp_second_stage) { 152 | fprintf(stderr, "Failed to open %s\n", second_stage); 153 | exit(EXIT_FAILURE); 154 | } 155 | 156 | if (signed_boot && !bcm2711 && !bcm2712) { // Signed boot uses a different mechanism on BCM2711 and BCM2712 157 | const char *sig_file = "bootcode.sig"; 158 | fp_sign = check_file(directory, sig_file, 1); 159 | if (!fp_sign) 160 | { 161 | fprintf(stderr, "Unable to open '%s'\n", sig_file); 162 | usage(1); 163 | exit(EXIT_FAILURE); 164 | } 165 | } 166 | 167 | if (second_stage_prep(fp_second_stage, fp_sign) != 0) { 168 | fprintf(stderr, "Failed to prepare the second stage bootcode\n"); 169 | exit(EXIT_FAILURE); 170 | } 171 | 172 | if (fp_second_stage) 173 | fclose(fp_second_stage); 174 | 175 | if (fp_sign) 176 | fclose(fp_sign); 177 | 178 | break; 179 | } else { 180 | // Serial number matches, VID matches, but we don't know about this product. Abort. 181 | fprintf(stderr, "Unknown Raspberry Pi Product, wanted 2763, 2764, 2711 or 2712. Got: %04x\n", desc.idProduct); 182 | libusb_close(handle); 183 | handle = NULL; 184 | continue; 185 | } 186 | } else { 187 | // Serial number matches, but the VID doesn't. Invalid action. 188 | fprintf(stderr, "Unknown USB Vendor ID. Wanted 0a5c. Got: %04x\n", desc.idVendor); 189 | libusb_close(handle); 190 | handle = NULL; 191 | continue; 192 | } 193 | } else { 194 | // No serial number specified, not a good sign at all. 195 | libusb_close(handle); 196 | handle = NULL; 197 | continue; 198 | } 199 | } 200 | } 201 | 202 | out_serialno: 203 | if (serial_buffer) 204 | free(serial_buffer); 205 | 206 | libusb_free_device_list(devices, 1); 207 | return handle; 208 | } 209 | 210 | libusb_device_handle * LIBUSB_CALL open_device_with_vid( 211 | libusb_context *ctx, uint16_t vendor_id) 212 | { 213 | struct libusb_device **devs; 214 | struct libusb_device *found = NULL; 215 | struct libusb_device *dev; 216 | struct libusb_device_handle *handle = NULL; 217 | uint32_t i = 0; 218 | int r, j, len; 219 | uint8_t path[8]; // Needed for libusb_get_port_numbers 220 | uint8_t portNo = 0; 221 | 222 | if (libusb_get_device_list(ctx, &devs) < 0) 223 | return NULL; 224 | 225 | while ((dev = devs[i++]) != NULL) { 226 | len = 0; 227 | struct libusb_device_descriptor desc; 228 | r = libusb_get_device_descriptor(dev, &desc); 229 | if (r < 0) 230 | goto out; 231 | 232 | if(overlay || verbose == 2 || targetpathname!=NULL) 233 | { 234 | r = libusb_get_port_numbers(dev, path, sizeof(path)); 235 | len = snprintf(&pathname[len], 18-len, "%d", libusb_get_bus_number(dev)); 236 | if (r > 0) { 237 | len += snprintf(&pathname[len], 18-len, "-"); 238 | len += snprintf(&pathname[len], 18-len, "%d", path[0]); 239 | for (j = 1; j < r; j++) 240 | { 241 | len += snprintf(&pathname[len], 18-len, ".%d", path[j]); 242 | } 243 | } 244 | } 245 | 246 | /* 247 | http://libusb.sourceforge.net/api-1.0/group__dev.html#ga14879a0ea7daccdcddb68852d86c00c4 248 | 249 | The port number returned by this call is usually guaranteed to be uniquely tied to a physical port, 250 | meaning that different devices plugged on the same physical port should return the same port number. 251 | */ 252 | portNo = libusb_get_port_number(dev); 253 | 254 | if(verbose == 2) 255 | { 256 | printf("Found device %u idVendor=0x%04x idProduct=0x%04x\n", i, desc.idVendor, desc.idProduct); 257 | printf("Bus: %d, Device: %d Path: %s\n",libusb_get_bus_number(dev), libusb_get_device_address(dev), pathname); 258 | } 259 | 260 | if (desc.idVendor == vendor_id) { 261 | if(desc.idProduct == 0x2763 || 262 | desc.idProduct == 0x2764 || 263 | desc.idProduct == 0x2711 || 264 | desc.idProduct == 0x2712) 265 | { 266 | FILE *fp_second_stage = NULL; 267 | FILE *fp_sign = NULL; 268 | const char *second_stage; 269 | 270 | if(verbose == 2) 271 | printf("Found candidate Compute Module...\n"); 272 | 273 | // Check if we should match against a specific port number or path 274 | if ((targetPortNo == 99 || portNo == targetPortNo) && 275 | (targetpathname == NULL || strcmp(targetpathname, pathname) == 0)) 276 | { 277 | if(verbose) 278 | printf("Device located successfully\n"); 279 | found = dev; 280 | } 281 | else 282 | { 283 | if(verbose == 2) 284 | printf("Device port / path does not match, trying again\n"); 285 | 286 | continue; 287 | } 288 | 289 | bcm2711 = (desc.idProduct == 0x2711); 290 | bcm2712 = (desc.idProduct == 0x2712); 291 | if (bcm2711) 292 | second_stage = "bootcode4.bin"; 293 | else if (bcm2712) 294 | second_stage = "bootcode5.bin"; 295 | else 296 | second_stage = "bootcode.bin"; 297 | 298 | if ((bcm2711 || bcm2712) && !directory) { 299 | directory = INSTALL_PREFIX "/share/rpiboot/mass-storage-gadget64/"; 300 | use_bootfiles = 1; 301 | snprintf(bootfiles_path, sizeof(bootfiles_path),"%s%s", directory, "bootfiles.bin"); 302 | printf("Directory not specified - trying default %s\n", directory); 303 | 304 | fp_second_stage = check_file(directory, second_stage, 1); 305 | if (!fp_second_stage) 306 | { 307 | directory = "mass-storage-gadget64/"; 308 | snprintf(bootfiles_path, sizeof(bootfiles_path),"%s%s", directory, "bootfiles.bin"); 309 | printf("Trying local path %s\n", directory); 310 | fp_second_stage = check_file(directory, second_stage, 1); 311 | } 312 | } 313 | else { 314 | fp_second_stage = check_file(directory, second_stage, 1); 315 | } 316 | 317 | if (!fp_second_stage) 318 | { 319 | fprintf(stderr, "Failed to open second stage bootloader (%s)\n", second_stage); 320 | fprintf(stderr, "\nPlease try specifying the directory e.g. rpiboot -d mass-storage-gadget64\n"); 321 | exit(1); 322 | } 323 | 324 | if (signed_boot && !bcm2711 && !bcm2712) // Signed boot use a different mechanism on BCM2711 and BCM2712 325 | { 326 | const char *sig_file = "bootcode.sig"; 327 | fp_sign = check_file(directory, sig_file, 1); 328 | if (!fp_sign) 329 | { 330 | fprintf(stderr, "Unable to open '%s'\n", sig_file); 331 | usage(1); 332 | } 333 | } 334 | 335 | if (second_stage_prep(fp_second_stage, fp_sign) != 0) 336 | { 337 | fprintf(stderr, "Failed to prepare the second stage bootcode\n"); 338 | exit(-1); 339 | } 340 | if (fp_second_stage) 341 | fclose(fp_second_stage); 342 | 343 | if (fp_sign) 344 | fclose(fp_sign); 345 | 346 | if (found) 347 | break; 348 | } 349 | } 350 | } 351 | 352 | if (found) { 353 | sleep(1); 354 | r = libusb_open(found, &handle); 355 | if (r == LIBUSB_ERROR_ACCESS) 356 | { 357 | printf("Permission to access USB device denied. Make sure you are a member of the plugdev group.\n"); 358 | exit(-1); 359 | } 360 | else if (r < 0) 361 | { 362 | if(verbose) printf("Failed to open the requested device\n"); 363 | handle = NULL; 364 | } 365 | } 366 | 367 | out: 368 | libusb_free_device_list(devs, 1); 369 | return handle; 370 | 371 | } 372 | 373 | int Initialize_Device(libusb_context ** ctx, libusb_device_handle ** usb_device) 374 | { 375 | int ret = 0; 376 | int interface; 377 | struct libusb_config_descriptor *config; 378 | 379 | switch (selection_mode) { 380 | case SELECTION_MODE_SERIAL: { 381 | *usb_device = open_device_with_serialno(*ctx, target_serialno); 382 | if (*usb_device == NULL) 383 | { 384 | usleep(200); 385 | return -1; 386 | } 387 | } 388 | break; 389 | case SELECTION_MODE_VID: { 390 | *usb_device = open_device_with_vid(*ctx, 0x0a5c); 391 | if (*usb_device == NULL) 392 | { 393 | usleep(200); 394 | return -1; 395 | } 396 | } 397 | break; 398 | } 399 | 400 | libusb_get_active_config_descriptor(libusb_get_device(*usb_device), &config); 401 | if(config == NULL) 402 | { 403 | printf("Failed to read config descriptor\n"); 404 | exit(-1); 405 | } 406 | 407 | // Handle 2837 where it can start with two interfaces, the first is mass storage 408 | // the second is the vendor interface for programming 409 | if(config->bNumInterfaces == 1) 410 | { 411 | interface = 0; 412 | out_ep = 1; 413 | in_ep = 2; 414 | } 415 | else 416 | { 417 | interface = 1; 418 | out_ep = 3; 419 | in_ep = 4; 420 | } 421 | 422 | ret = libusb_claim_interface(*usb_device, interface); 423 | if (ret) 424 | { 425 | libusb_close(*usb_device); 426 | printf("Failed to claim interface\n"); 427 | return ret; 428 | } 429 | 430 | if(verbose) printf("Initialised device correctly\n"); 431 | 432 | return ret; 433 | } 434 | 435 | #define LIBUSB_MAX_TRANSFER (16 * 1024) 436 | 437 | int ep_write(void *buf, int len, libusb_device_handle * usb_device) 438 | { 439 | int a_len = 0; 440 | int sending, sent; 441 | int ret = 442 | libusb_control_transfer(usb_device, LIBUSB_REQUEST_TYPE_VENDOR, 0, 443 | len & 0xffff, len >> 16, NULL, 0, 1000); 444 | 445 | if(ret != 0) 446 | { 447 | printf("Failed control transfer (%d,%d)\n", ret, len); 448 | return ret; 449 | } 450 | 451 | while(len > 0) 452 | { 453 | sending = len < LIBUSB_MAX_TRANSFER ? len : LIBUSB_MAX_TRANSFER; 454 | ret = libusb_bulk_transfer(usb_device, out_ep, buf, sending, &sent, 5000); 455 | if (ret) 456 | break; 457 | a_len += sent; 458 | buf += sent; 459 | len -= sent; 460 | } 461 | if(verbose) 462 | printf("libusb_bulk_transfer sent %d bytes; returned %d\n", a_len, ret); 463 | 464 | return a_len; 465 | } 466 | 467 | int ep_read(void *buf, int len, libusb_device_handle * usb_device) 468 | { 469 | int ret = 470 | libusb_control_transfer(usb_device, 471 | LIBUSB_REQUEST_TYPE_VENDOR | 472 | LIBUSB_ENDPOINT_IN, 0, len & 0xffff, 473 | len >> 16, buf, len, 20000); 474 | if(ret >= 0) 475 | return len; 476 | else 477 | return ret; 478 | } 479 | 480 | void print_version(void) 481 | { 482 | printf("RPIBOOT: build-date %s pkg-version %s %s\n", BUILD_DATE, PKG_VER, GIT_VER); 483 | } 484 | 485 | void get_options(int argc, char *argv[]) 486 | { 487 | // Skip the command name 488 | argv++; argc--; 489 | while(*argv) 490 | { 491 | if(strcmp(*argv, "-d") == 0) 492 | { 493 | argv++; argc--; 494 | if(argc < 1) 495 | usage(1); 496 | directory = *argv; 497 | } 498 | else if(strcmp(*argv, "-p") == 0) 499 | { 500 | argv++; argc--; 501 | if(argc < 1) 502 | usage(1); 503 | targetpathname = *argv; 504 | } 505 | else if(strcmp(*argv, "-h") == 0 || strcmp(*argv, "--help") == 0) 506 | { 507 | usage(0); 508 | } 509 | else if(strcmp(*argv, "-l") == 0) 510 | { 511 | loop = 1; 512 | } 513 | else if(strcmp(*argv, "-v") == 0) 514 | { 515 | verbose = 1; 516 | } 517 | else if((strcmp(*argv, "-V") == 0) || (strcmp(*argv, "--version")) == 0) 518 | { 519 | print_version(); 520 | exit(0); 521 | } 522 | else if(strcmp(*argv, "-o") == 0) 523 | { 524 | overlay = 1; 525 | } 526 | else if(strcmp(*argv, "-m") == 0) 527 | { 528 | argv++; argc--; 529 | if(argc < 1) 530 | usage(1); 531 | delay = atol(*argv); 532 | } 533 | else if(strcmp(*argv, "-vv") == 0) 534 | { 535 | verbose = 2; 536 | } 537 | else if(strcmp(*argv, "-s") == 0) 538 | { 539 | signed_boot = 1; 540 | } 541 | else if(strcmp(*argv, "-j") == 0) 542 | { 543 | argv++; argc--; 544 | if(argc < 1) 545 | usage(1); 546 | metadata_path = *argv; 547 | metadata = 1; 548 | } 549 | else if (strcmp(*argv, "-i") == 0) 550 | { 551 | selection_mode = SELECTION_MODE_SERIAL; 552 | argv++; argc--; 553 | if (argc < 1) { 554 | usage(1); 555 | } else { 556 | target_serialno = *argv; 557 | } 558 | } 559 | else if(strcmp(*argv, "-0") == 0) 560 | { 561 | targetPortNo = 0; 562 | } 563 | else if(strcmp(*argv, "-1") == 0) 564 | { 565 | targetPortNo = 1; 566 | } 567 | else if(strcmp(*argv, "-2") == 0) 568 | { 569 | targetPortNo = 2; 570 | } 571 | else if(strcmp(*argv, "-3") == 0) 572 | { 573 | targetPortNo = 3; 574 | } 575 | else if(strcmp(*argv, "-4") == 0) 576 | { 577 | targetPortNo = 4; 578 | } 579 | else if(strcmp(*argv, "-5") == 0) 580 | { 581 | targetPortNo = 5; 582 | } 583 | else if(strcmp(*argv, "-6") == 0) 584 | { 585 | targetPortNo = 6; 586 | } 587 | else 588 | { 589 | usage(1); 590 | } 591 | 592 | argv++; argc--; 593 | } 594 | if(overlay&&!directory) 595 | { 596 | usage(1); 597 | } 598 | if(!delay) 599 | { 600 | usage(1); 601 | } 602 | if((targetPortNo != 99) && (targetpathname != NULL)) 603 | { 604 | usage(1); 605 | } 606 | } 607 | 608 | boot_message_t boot_message; 609 | void *second_stage_txbuf; 610 | 611 | int second_stage_prep(FILE *fp, FILE *fp_sig) 612 | { 613 | int size; 614 | 615 | fseek(fp, 0, SEEK_END); 616 | boot_message.length = ftell(fp); 617 | fseek(fp, 0, SEEK_SET); 618 | 619 | if(fp_sig != NULL) 620 | { 621 | size = fread(boot_message.signature, 1, sizeof(boot_message.signature), fp_sig); 622 | if (size != sizeof(boot_message.signature)) 623 | { 624 | fprintf(stderr, "Failed to read bootcode signature \n"); 625 | return -1; 626 | } 627 | } 628 | 629 | if (second_stage_txbuf) 630 | free(second_stage_txbuf); 631 | second_stage_txbuf = NULL; 632 | 633 | second_stage_txbuf = (uint8_t *) malloc(boot_message.length); 634 | if (second_stage_txbuf == NULL) 635 | { 636 | fprintf(stderr, "Failed to allocate memory\n"); 637 | return -1; 638 | } 639 | 640 | size = fread(second_stage_txbuf, 1, boot_message.length, fp); 641 | if(size != boot_message.length) 642 | { 643 | fprintf(stderr, "Failed to read second stage\n"); 644 | return -1; 645 | } 646 | 647 | return 0; 648 | } 649 | 650 | int second_stage_boot(libusb_device_handle *usb_device) 651 | { 652 | int size, retcode = 0; 653 | 654 | size = ep_write(&boot_message, sizeof(boot_message), usb_device); 655 | if (size != sizeof(boot_message)) 656 | { 657 | printf("Failed to write correct length, returned %d\n", size); 658 | return -1; 659 | } 660 | 661 | if(verbose) printf("Writing %d bytes\n", boot_message.length); 662 | size = ep_write(second_stage_txbuf, boot_message.length, usb_device); 663 | if (size != boot_message.length) 664 | { 665 | printf("Failed to read correct length, returned %d\n", size); 666 | return -1; 667 | } 668 | 669 | sleep(1); 670 | size = ep_read((unsigned char *)&retcode, sizeof(retcode), usb_device); 671 | 672 | if (size > 0 && retcode == 0) 673 | { 674 | printf("Successful read %d bytes \n", size); 675 | } 676 | else 677 | { 678 | printf("Failed : 0x%x\n", retcode); 679 | } 680 | 681 | return retcode; 682 | 683 | } 684 | 685 | 686 | FILE * check_file(const char * dir, const char *fname, int use_fmem) 687 | { 688 | FILE * fp = NULL; 689 | char path[MAX_PATH_LEN]; 690 | 691 | // Prevent USB device from requesting files in parent directories 692 | if(strstr(fname, "..")) 693 | { 694 | printf("Denying request for filename containing .. to prevent path traversal\n"); 695 | return NULL; 696 | } 697 | 698 | if (use_bootfiles && use_fmem) 699 | { 700 | const char *prefix = bcm2712 ? "2712" : bcm2711 ? "2711" : "2710"; 701 | unsigned long length = 0; 702 | 703 | // If 'dir' is specified and the file exists then load this in preference 704 | // to the file in bootfiles.bin e.g. use a custom config.txt or cmdline.txt 705 | // to override settings in the mass-storage-gadget 706 | if (dir) 707 | { 708 | snprintf(path, sizeof(path), "%s/%s/%s", dir, prefix, fname); 709 | path[sizeof(path) - 1] = 0; 710 | fp = fopen(path, "rb"); 711 | 712 | if (fp) 713 | { 714 | printf("Loading bootfiles.bin overlay: %s\n", path); 715 | return fp; 716 | } 717 | } 718 | 719 | snprintf(path, sizeof(path), "%s/%s", prefix, fname); 720 | path[sizeof(path) - 1] = 0; 721 | if (bootfile_data) 722 | free(bootfile_data); 723 | bootfile_data = bootfiles_read(bootfiles_path, path, &length); 724 | if (bootfile_data) 725 | fp = fmemopen(bootfile_data, length, "rb"); 726 | if (fp) 727 | return fp; 728 | } 729 | 730 | if(dir) 731 | { 732 | if(overlay && (pathname[0] != 0) && 733 | (strcmp(fname, "bootcode5.bin") != 0) && 734 | (strcmp(fname, "bootcode4.bin") != 0) && 735 | (strcmp(fname, "bootcode.bin") != 0)) 736 | { 737 | snprintf(path, sizeof(path), "%s/%s/%s", dir, pathname, fname); 738 | path[sizeof(path) - 1] = 0; 739 | fp = fopen(path, "rb"); 740 | if (fp) 741 | printf("Loading: %s\n", path); 742 | memset(path, 0, sizeof(path)); 743 | } 744 | 745 | if (fp == NULL) 746 | { 747 | snprintf(path, sizeof(path), "%s/%s", dir, fname); 748 | path[sizeof(path) - 1] = 0; 749 | fp = fopen(path, "rb"); 750 | if (fp) 751 | printf("Loading: %s\n", path); 752 | } 753 | } 754 | 755 | // Failover to fmem unless use_fmem is zero in which case this function 756 | // is being used to check if a file exists. 757 | if(fp == NULL && use_fmem) 758 | { 759 | if (bcm2711) 760 | { 761 | if(strcmp(fname, "bootcode4.bin") == 0) 762 | fp = fmemopen(msd_bootcode4_bin, msd_bootcode4_bin_len, "rb"); 763 | else if(strcmp(fname, "start4.elf") == 0) 764 | fp = fmemopen(msd_start_elf, msd_start_elf_len, "rb"); 765 | } 766 | else 767 | { 768 | if(strcmp(fname, "bootcode.bin") == 0) 769 | fp = fmemopen(msd_bootcode_bin, msd_bootcode_bin_len, "rb"); 770 | else if(strcmp(fname, "start.elf") == 0) 771 | fp = fmemopen(msd_start_elf, msd_start_elf_len, "rb"); 772 | } 773 | if (fp) 774 | printf("Loading embedded: %s\n", fname); 775 | } 776 | 777 | return fp; 778 | } 779 | 780 | void close_metadata_file(FILE ** fp){ 781 | fprintf(*fp, "\n}"); 782 | fclose(*fp); 783 | } 784 | 785 | void write_metadata_file(char *metadata_str, FILE **fp, int index) 786 | { 787 | char *token, *property, *value; 788 | 789 | token = strtok(metadata_str, "*"); 790 | if(!token) return; 791 | property = strdup(token); 792 | token = strtok(NULL, "*"); 793 | 794 | if(token) 795 | { 796 | value = strdup(token); 797 | if (index != 0) 798 | fprintf(*fp, ","); 799 | 800 | if (strcmp(property, "FACTORY_UUID") == 0) 801 | { 802 | char c40_str[DUID_LENGTH]; 803 | if (duid_decode_c40(value, c40_str) == -1) 804 | fprintf(stderr, "Failed to decode a FACTORY_UUID: invalid input\n"); 805 | else 806 | fprintf(*fp, "\n\t\"%s\" : \"%s\"", property, c40_str); 807 | } 808 | else 809 | { 810 | fprintf(*fp, "\n\t\"%s\" : \"%s\"", property, value); 811 | } 812 | free(value); 813 | } 814 | free(property); 815 | } 816 | 817 | void create_metadata_file(FILE ** fp) 818 | { 819 | char fname[MAX_PATH_LEN + FILE_NAME_LENGTH + 5]; // 5 for extension .json 820 | snprintf(fname, sizeof(fname), "%s/%s.json", metadata_path, (char *)serial_num); 821 | 822 | *fp = fopen(fname, "w"); 823 | if (*fp) 824 | { 825 | printf("Created metadata file: %s\n", fname); 826 | fprintf(*fp, "{"); 827 | } 828 | else 829 | { 830 | fprintf(stderr, "Failed to create metadata file: %s\n", fname); 831 | metadata = 0; 832 | } 833 | } 834 | 835 | int file_server(libusb_device_handle * usb_device) 836 | { 837 | int going = 1; 838 | struct file_message { 839 | int command; 840 | char fname[MAX_PATH_LEN]; 841 | } message; 842 | static FILE * fp = NULL; 843 | FILE * metadata_fp = NULL; 844 | char metadata_fname[FILE_NAME_LENGTH]; 845 | int metadata_index = 0; 846 | 847 | if (metadata) 848 | { 849 | if (bcm2711 || bcm2712) 850 | { 851 | create_metadata_file(&metadata_fp); 852 | } 853 | else 854 | { 855 | fprintf(stderr, "Failed to create metadata file: expected BCM2712/2711"); 856 | metadata = 0; 857 | } 858 | } 859 | 860 | while(going) 861 | { 862 | char message_name[][20] = {"GetFileSize", "ReadFile", "Done"}; 863 | int i = ep_read(&message, sizeof(message), usb_device); 864 | if(i < 0) 865 | { 866 | // Drop out if the device goes away 867 | if(i == LIBUSB_ERROR_NO_DEVICE || i == LIBUSB_ERROR_IO) 868 | break; 869 | sleep(1); 870 | continue; 871 | } 872 | if(verbose) printf("Received message %s: %s\n", message_name[message.command], message.fname); 873 | 874 | // Done can also just be null filename 875 | if(strlen(message.fname) == 0) 876 | { 877 | ep_write(NULL, 0, usb_device); 878 | break; 879 | } 880 | 881 | // Metadata files 882 | if ((message.fname[0] == '*') && (message.command != 2)) 883 | { 884 | if (metadata) 885 | { 886 | strcpy(metadata_fname, message.fname); 887 | write_metadata_file(metadata_fname + 1, &metadata_fp, metadata_index++); 888 | } 889 | ep_write(NULL, 0, usb_device); 890 | continue; 891 | } 892 | 893 | switch(message.command) 894 | { 895 | case 0: // Get file size 896 | if(fp) 897 | fclose(fp); 898 | fp = check_file(directory, message.fname, 1); 899 | if(strlen(message.fname) && fp != NULL) 900 | { 901 | int file_size; 902 | 903 | fseek(fp, 0, SEEK_END); 904 | file_size = ftell(fp); 905 | fseek(fp, 0, SEEK_SET); 906 | 907 | if(verbose || !file_size) 908 | printf("File size = %d bytes\n", file_size); 909 | 910 | int sz = libusb_control_transfer(usb_device, LIBUSB_REQUEST_TYPE_VENDOR, 0, 911 | file_size & 0xffff, file_size >> 16, NULL, 0, 1000); 912 | 913 | if(sz < 0) 914 | return -1; 915 | } 916 | else 917 | { 918 | ep_write(NULL, 0, usb_device); 919 | printf("Cannot open file %s\n", message.fname); 920 | break; 921 | } 922 | break; 923 | 924 | case 1: // Read file 925 | if(fp != NULL) 926 | { 927 | int file_size; 928 | void *buf; 929 | 930 | printf("File read: %s\n", message.fname); 931 | 932 | fseek(fp, 0, SEEK_END); 933 | file_size = ftell(fp); 934 | fseek(fp, 0, SEEK_SET); 935 | 936 | if (!file_size) 937 | printf("WARNING: %s is empty\n", message.fname); 938 | 939 | buf = malloc(file_size); 940 | if(buf == NULL) 941 | { 942 | printf("Failed to allocate buffer for file %s\n", message.fname); 943 | return -1; 944 | } 945 | int read = fread(buf, 1, file_size, fp); 946 | if(read != file_size) 947 | { 948 | printf("Failed to read from input file\n"); 949 | free(buf); 950 | return -1; 951 | } 952 | 953 | int sz = ep_write(buf, file_size, usb_device); 954 | 955 | free(buf); 956 | fclose(fp); 957 | fp = NULL; 958 | 959 | if(sz != file_size) 960 | { 961 | printf("Failed to write complete file to USB device\n"); 962 | return -1; 963 | } 964 | } 965 | else 966 | { 967 | if(verbose) printf("No file %s found\n", message.fname); 968 | ep_write(NULL, 0, usb_device); 969 | } 970 | break; 971 | 972 | case 2: // Done, exit file server 973 | if(verbose) printf("CMD exit\n"); 974 | going = 0; 975 | break; 976 | 977 | default: 978 | printf("Unknown message\n"); 979 | return -1; 980 | } 981 | } 982 | 983 | if (metadata) 984 | close_metadata_file(&metadata_fp); 985 | 986 | printf("Second stage boot server done\n"); 987 | return 0; 988 | } 989 | 990 | int main(int argc, char *argv[]) 991 | { 992 | libusb_context *ctx; 993 | libusb_device_handle *usb_device; 994 | struct libusb_device_descriptor desc; 995 | struct libusb_config_descriptor *config; 996 | 997 | get_options(argc, argv); 998 | print_version(); 999 | printf("\nPlease fit the EMMC_DISABLE / nRPIBOOT jumper before connecting the power and USB cables to the target device.\n"); 1000 | printf("If the device fails to connect then please see https://rpltd.co/rpiboot for debugging tips.\n\n"); 1001 | 1002 | // flush immediately 1003 | setbuf(stdout, NULL); 1004 | 1005 | // If the boot directory is specified then check that it contains bootcode files. 1006 | if (directory) 1007 | { 1008 | FILE *f, *f4, *f5; 1009 | 1010 | if (verbose) 1011 | printf("Boot directory '%s'\n", directory); 1012 | 1013 | f = check_file(directory, "bootfiles.bin", 0); 1014 | if (f) 1015 | { 1016 | snprintf(bootfiles_path, sizeof(bootfiles_path),"%s/%s", directory, "bootfiles.bin"); 1017 | printf("Using %s\n", bootfiles_path); 1018 | bootfiles_path[sizeof(bootfiles_path) - 1] = 0; 1019 | use_bootfiles = 1; 1020 | fclose(f); 1021 | f = NULL; 1022 | } 1023 | else 1024 | { 1025 | f = check_file(directory, "bootcode.bin", 0); 1026 | f4 = check_file(directory, "bootcode4.bin", 0); 1027 | f5 = check_file(directory, "bootcode5.bin", 0); 1028 | if (!f && !f4 && !f5) 1029 | { 1030 | fprintf(stderr, "No 'bootcode' files found in '%s'\n", directory); 1031 | usage(1); 1032 | } 1033 | if (f) 1034 | fclose(f); 1035 | if (f4) 1036 | fclose(f4); 1037 | if (f5) 1038 | fclose(f5); 1039 | } 1040 | 1041 | if (signed_boot) 1042 | { 1043 | f = check_file(directory, "bootsig.bin", 0); 1044 | if (!f) 1045 | { 1046 | fprintf(stderr, "Unable to open 'bootsig.bin' from %s\n", directory); 1047 | usage(1); 1048 | } 1049 | fclose(f); 1050 | } 1051 | } 1052 | 1053 | int ret = libusb_init(&ctx); 1054 | if (ret) 1055 | { 1056 | printf("Failed to initialise libUSB\n"); 1057 | exit(-1); 1058 | } 1059 | 1060 | #if LIBUSBX_API_VERSION < 0x01000106 1061 | libusb_set_debug(ctx, (verbose == 2)? LIBUSB_LOG_LEVEL_WARNING : 0); 1062 | #else 1063 | libusb_set_option( 1064 | ctx, 1065 | LIBUSB_OPTION_LOG_LEVEL, 1066 | verbose ? verbose == 2 ? LIBUSB_LOG_LEVEL_INFO : LIBUSB_LOG_LEVEL_WARNING : 0 1067 | ); 1068 | #endif 1069 | 1070 | do 1071 | { 1072 | int last_serial = -1; 1073 | 1074 | printf("Waiting for BCM2835/6/7/2711/2712...\n\n"); 1075 | 1076 | // Wait for a device to get plugged in 1077 | do 1078 | { 1079 | ret = Initialize_Device(&ctx, &usb_device); 1080 | if(ret == 0) 1081 | { 1082 | libusb_get_device_descriptor(libusb_get_device(usb_device), &desc); 1083 | 1084 | if(verbose) 1085 | printf("Found serial number %d\n", desc.iSerialNumber); 1086 | 1087 | // Make sure we've re-enumerated since the last time 1088 | if(desc.iSerialNumber == last_serial) 1089 | { 1090 | ret = -1; 1091 | libusb_close(usb_device); 1092 | } 1093 | 1094 | libusb_get_active_config_descriptor(libusb_get_device(usb_device), &config); 1095 | } 1096 | 1097 | if (ret) 1098 | { 1099 | usleep(delay); 1100 | } 1101 | } 1102 | while (ret); 1103 | 1104 | ret = libusb_get_string_descriptor_ascii(usb_device, desc.iSerialNumber, serial_num, sizeof(serial_num)); 1105 | // if metadata output is enabled and could not get serial number 1106 | if (metadata && (ret <= 0)) { 1107 | metadata = 0; // disable metadata 1108 | } 1109 | 1110 | if (verbose) printf("last_serial %d serial %d\n", last_serial, desc.iSerialNumber); 1111 | last_serial = desc.iSerialNumber; 1112 | if(desc.iSerialNumber == 0 || desc.iSerialNumber == 3) 1113 | { 1114 | printf("Sending bootcode.bin\n"); 1115 | second_stage_boot(usb_device); 1116 | } 1117 | else 1118 | { 1119 | printf("Second stage boot server\n"); 1120 | file_server(usb_device); 1121 | } 1122 | 1123 | libusb_close(usb_device); 1124 | sleep(1); 1125 | 1126 | } 1127 | while(loop || desc.iSerialNumber == 0 || desc.iSerialNumber == 3); 1128 | 1129 | libusb_exit(ctx); 1130 | 1131 | return 0; 1132 | } 1133 | 1134 | -------------------------------------------------------------------------------- /mass-storage-gadget: -------------------------------------------------------------------------------- 1 | mass-storage-gadget64 -------------------------------------------------------------------------------- /mass-storage-gadget64-cm3/bootfiles.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/mass-storage-gadget64-cm3/bootfiles.bin -------------------------------------------------------------------------------- /mass-storage-gadget64/.gitignore: -------------------------------------------------------------------------------- 1 | *.h 2 | boot.sig 3 | bootfiles.original.bin 4 | -------------------------------------------------------------------------------- /mass-storage-gadget64/README.md: -------------------------------------------------------------------------------- 1 | # USB mass-storage gadget for BCM2711 and BCM2712 2 | 3 | This directory provides a bootloader image that loads a Linux 4 | initramfs that exports common block devices (EMMC, NVMe) as 5 | USB mass storage devices using the Linux gadget-fs drivers. 6 | 7 | This allows Raspberry Pi Imager to be run on the host computer 8 | and write OS images to the Raspberry Pi or Compute Module block devices. 9 | 10 | ## Running 11 | To run load the USB MSD device drivers via RPIBOOT run 12 | ```bash 13 | rpiboot -d mass-storage-gadget64 14 | 15 | ``` 16 | 17 | ### Debug 18 | The mass-storage-gadget image automatically enables a UART console for debugging (user `root` empty password). 19 | 20 | ## Secure boot 21 | Once secure-boot has been enable the OS `boot.img` file must be signed with the customer private key. 22 | On Pi5 firmware must also be counter-signed with this key. 23 | 24 | The `sign.sh` script wraps the command do this on Pi4 and Pi5. 25 | ```bash 26 | KEY_FILE=$HOME/private.pem 27 | ./sign.sh ${KEY_FILE} 28 | ``` 29 | or as follows if using a HSM wrapper script. 30 | ```bash 31 | ./sign.sh -H hsm-wrapper public.pem 32 | ``` 33 | 34 | WARNING: The signed images will not be bootable on a Pi5 without secure-boot enabled. Run `./reset.sh` to reset the signed images to the default unsigned state. 35 | 36 | ## Source code 37 | The buildroot configuration and supporting patches is available on 38 | the [mass-storage-gadget64](https://github.com/raspberrypi/buildroot/tree/mass-storage-gadget64) 39 | branch of the Raspberry Pi [buildroot](https://github.com/raspberrypi/buildroot) repo. 40 | 41 | ### Building 42 | 43 | In order to build directly on a Linux host that has the needed dependencies, run: 44 | ```bash 45 | git clone --branch mass-storage-gadget64 git@github.com:raspberrypi/buildroot.git 46 | cd buildroot 47 | make raspberrypi64-mass-storage-gadget_defconfig 48 | make 49 | ``` 50 | 51 | The output is written to `output/images/sdcard.img` and can be copied to `boot.img` 52 | 53 | Alternatively, if you have docker installed and would like to use the upstream buildroot CI docker image for a build environment, use its `utils/docker-run` helper script: 54 | ```bash 55 | $ git clone --branch mass-storage-gadget64 git@github.com:raspberrypi/buildroot.git 56 | $ cd buildroot 57 | $ ./utils/docker-run /bin/bash -c "make raspberrypi64-mass-storage-gadget_defconfig && make" 58 | ``` 59 | -------------------------------------------------------------------------------- /mass-storage-gadget64/boot.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/mass-storage-gadget64/boot.img -------------------------------------------------------------------------------- /mass-storage-gadget64/bootfiles.bin: -------------------------------------------------------------------------------- 1 | ../firmware/bootfiles.bin -------------------------------------------------------------------------------- /mass-storage-gadget64/config.txt: -------------------------------------------------------------------------------- 1 | # Load boot.img containing the Linux initramfs for the mass-storage-gadget 2 | # In signed-boot or secure-boot mode the bootloader checks the 3 | # RSA signature of the ramdisk. The signature is located in boot.sig 4 | boot_ramdisk=1 5 | uart_2ndstage=1 6 | -------------------------------------------------------------------------------- /mass-storage-gadget64/reset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -f boot.sig bootfiles.bin bootfiles.original.bin 4 | ln -sf ../firmware/bootfiles.bin . 5 | -------------------------------------------------------------------------------- /mass-storage-gadget64/sign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | script_dir="$(cd "$(dirname "$0")" && pwd)" 6 | 7 | TMP_DIR="" 8 | SIGN_ARGS="" 9 | PUBLIC_KEY="" 10 | 11 | die() { 12 | echo "$@" >&2 13 | exit 1 14 | } 15 | 16 | cleanup() { 17 | if [ -d "${TMP_DIR}" ]; then rm -rf "${TMP_DIR}"; fi 18 | } 19 | 20 | sign_firmware_blob() { 21 | echo "Signing firmware in ${1} using $(which rpi-sign-bootcode)" 22 | rpi-sign-bootcode \ 23 | -c 2712 \ 24 | -i "${1}" \ 25 | -o "${2}" \ 26 | -n 16 \ 27 | -v 0 \ 28 | ${SIGN_ARGS} ${PUBLIC_KEY} 29 | } 30 | 31 | sign_bootfiles() { 32 | echo "Signing OS image ${1}" 33 | input="${1}" 34 | output="${2}" 35 | ( 36 | cd "${TMP_DIR}" 37 | tar -xf "${input}" 38 | echo "Signing 2712/bootcode5.bin" 39 | sign_firmware_blob 2712/bootcode5.bin 2712/bootcode5.bin.signed || die "Failed to sign bootcode5.bin" 40 | mv -f "2712/bootcode5.bin.signed" "2712/bootcode5.bin" 41 | tar -cf "${output}" * 42 | find . 43 | ) 44 | } 45 | 46 | trap cleanup EXIT 47 | 48 | if [ "${1}" = "-H" ]; then 49 | HSM_WRAPPER="${2}" 50 | PUBLIC_KEY="${3}" 51 | [ -f "${PUBLIC_KEY}" ] || die "HSM requires a public key file in PEM format. Public key \"${PUBLIC_KEY}\" not found." 52 | PUBLIC_KEY="-p ${3}" 53 | if ! command -v "${HSM_WRAPPER}"; then 54 | die "HSM wrapper script \"${HSM_WRAPPER}\" not found" 55 | fi 56 | SIGN_ARGS="-H ${HSM_WRAPPER}" 57 | else 58 | KEY_FILE="${1}" 59 | [ -f "${KEY_FILE}" ] || die "KEY_FILE: ${KEY_FILE} not found" 60 | SIGN_ARGS="-k ${KEY_FILE}" 61 | fi 62 | 63 | PATH="${script_dir}/../tools:${PATH}" 64 | KEY_FILE="${1}" 65 | TMP_DIR="$(mktemp -d)" 66 | rm -f bootfiles.bin 67 | ln -sf ../firmware/bootfiles.bin bootfiles.original.bin 68 | sign_bootfiles "$(pwd)/bootfiles.original.bin" "$(pwd)/bootfiles.bin" 69 | 70 | echo "Signing boot.img with ${SIGN_ARGS}" 71 | rpi-eeprom-digest -i boot.img -o boot.sig ${SIGN_ARGS} 72 | -------------------------------------------------------------------------------- /msd/.gitignore: -------------------------------------------------------------------------------- 1 | *.h 2 | -------------------------------------------------------------------------------- /msd/README.md: -------------------------------------------------------------------------------- 1 | This directory contains the second stage bootloader and firmware for 2 | booting the Raspberry Pi as a mass-storage device. The files here are 3 | embedded into the `rpiboot` executable as part of the build process. 4 | 5 | ************************************************************************ 6 | This is not supported on Pi5 and deprecated on CM4 7 | please use the mass-storage-gadget64 instead. 8 | 9 | ```bash 10 | sudo rpiboot -d mass-storage-gadget64 11 | ``` 12 | 13 | ************************************************************************ 14 | 15 | To load the files from this directory directly, run: 16 | 17 | ```bash 18 | cd msd 19 | ../rpiboot -d . 20 | ``` 21 | -------------------------------------------------------------------------------- /msd/bootcode.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/msd/bootcode.bin -------------------------------------------------------------------------------- /msd/bootcode4.bin: -------------------------------------------------------------------------------- 1 | ../firmware/2711/bootcode4.bin -------------------------------------------------------------------------------- /msd/start.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/msd/start.elf -------------------------------------------------------------------------------- /recovery/.gitignore: -------------------------------------------------------------------------------- 1 | pieeprom.bin 2 | pieeprom.sig 3 | -------------------------------------------------------------------------------- /recovery/README.md: -------------------------------------------------------------------------------- 1 | To update the SPI EEPROM bootloader on a Compute Module 4. 2 | 3 | * Modify the EEPROM configuration in `boot.conf` as desired 4 | * Optionally, replace pieeprom.original.bin with a custom version. The default 5 | version here is the latest stable release recommended for use on Compute Module 4. 6 | * Run `update-eeprom.sh` to create the image to flash 7 | 8 | ```bash 9 | cd recovery 10 | ./update-pieeprom.sh 11 | ../rpiboot -d . 12 | ``` 13 | 14 | N.B The `bootcode4.bin` file in this directory is actually the `recovery.bin` 15 | file used on Raspberry Pi 4 bootloader update cards. 16 | 17 | -------------------------------------------------------------------------------- /recovery/boot.conf: -------------------------------------------------------------------------------- 1 | [all] 2 | BOOT_UART=0 3 | WAKE_ON_GPIO=1 4 | POWER_OFF_ON_HALT=0 5 | 6 | # Boot Order Codes, from https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#BOOT_ORDER 7 | # Try SD first (1), followed by, USB PCIe, NVMe PCIe, USB SoC XHCI then network 8 | BOOT_ORDER=0xf25641 9 | 10 | # Set to 0 to prevent bootloader updates from USB/Network boot 11 | # For remote units EEPROM hardware write protection should be used. 12 | ENABLE_SELF_UPDATE=1 13 | 14 | -------------------------------------------------------------------------------- /recovery/bootcode4.bin: -------------------------------------------------------------------------------- 1 | ../firmware/2711/recovery.bin -------------------------------------------------------------------------------- /recovery/config.txt: -------------------------------------------------------------------------------- 1 | # Uncomment to enable more UART debug logs 2 | #uart_2ndstage=1 3 | 4 | # Uncomment to instruct recovery.bin to reboot the Pi after flashing the bootloader image 5 | #recovery_reboot=1 6 | 7 | # Uncomment to instruct recovery.bin to send metadata including OTP fields 8 | # Specify -j dirname on the command line to specify the directory where 9 | # metadata should be stored (JSON format) 10 | recovery_metadata=1 11 | -------------------------------------------------------------------------------- /recovery/pieeprom.original.bin: -------------------------------------------------------------------------------- 1 | ../firmware/2711/pieeprom.bin -------------------------------------------------------------------------------- /recovery/rpi-eeprom-config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | rpi-eeprom-config 5 | """ 6 | 7 | import argparse 8 | import atexit 9 | import os 10 | import subprocess 11 | import string 12 | import struct 13 | import sys 14 | import tempfile 15 | import time 16 | 17 | IMAGE_SIZE = 512 * 1024 18 | 19 | # Larger files won't with with "vcgencmd bootloader_config" 20 | MAX_FILE_SIZE = 2024 21 | ALIGN_SIZE = 4096 22 | BOOTCONF_TXT = 'bootconf.txt' 23 | BOOTCONF_SIG = 'bootconf.sig' 24 | PUBKEY_BIN = 'pubkey.bin' 25 | 26 | # Each section starts with a magic number followed by a 32 bit offset to the 27 | # next section (big-endian). 28 | # The number, order and size of the sections depends on the bootloader version 29 | # but the following mask can be used to test for section headers and skip 30 | # unknown data. 31 | # 32 | # The last 4KB of the EEPROM image is reserved for internal use by the 33 | # bootloader and may be overwritten during the update process. 34 | MAGIC = 0x55aaf00f 35 | PAD_MAGIC = 0x55aafeef 36 | MAGIC_MASK = 0xfffff00f 37 | FILE_MAGIC = 0x55aaf11f # id for modifiable files 38 | FILE_HDR_LEN = 20 39 | FILENAME_LEN = 12 40 | TEMP_DIR = None 41 | 42 | DEBUG = False 43 | def debug(s): 44 | if DEBUG: 45 | sys.stderr.write(s + '\n') 46 | 47 | def rpi4(): 48 | compatible_path = "/sys/firmware/devicetree/base/compatible" 49 | if os.path.exists(compatible_path): 50 | with open(compatible_path, "rb") as f: 51 | compatible = f.read().decode('utf-8') 52 | if "bcm2711" in compatible: 53 | return True 54 | return False 55 | 56 | def exit_handler(): 57 | """ 58 | Delete any temporary files. 59 | """ 60 | if TEMP_DIR is not None and os.path.exists(TEMP_DIR): 61 | tmp_image = os.path.join(TEMP_DIR, 'pieeprom.upd') 62 | if os.path.exists(tmp_image): 63 | os.remove(tmp_image) 64 | tmp_conf = os.path.join(TEMP_DIR, 'boot.conf') 65 | if os.path.exists(tmp_conf): 66 | os.remove(tmp_conf) 67 | os.rmdir(TEMP_DIR) 68 | 69 | def create_tempdir(): 70 | global TEMP_DIR 71 | if TEMP_DIR is None: 72 | TEMP_DIR = tempfile.mkdtemp() 73 | 74 | def pemtobin(infile): 75 | """ 76 | Converts an RSA public key into the format expected by the bootloader. 77 | """ 78 | # Import the package here to make this a weak dependency. 79 | from Cryptodome.PublicKey import RSA 80 | 81 | arr = bytearray() 82 | f = open(infile,'r') 83 | key = RSA.importKey(f.read()) 84 | 85 | if key.size_in_bits() != 2048: 86 | raise Exception("RSA key size must be 2048") 87 | 88 | # Export N and E in little endian format 89 | arr.extend(key.n.to_bytes(256, byteorder='little')) 90 | arr.extend(key.e.to_bytes(8, byteorder='little')) 91 | return arr 92 | 93 | def exit_error(msg): 94 | """ 95 | Trapped a fatal error, output message to stderr and exit with non-zero 96 | return code. 97 | """ 98 | sys.stderr.write("ERROR: %s\n" % msg) 99 | sys.exit(1) 100 | 101 | def shell_cmd(args): 102 | """ 103 | Executes a shell command waits for completion returning STDOUT. If an 104 | error occurs then exit and output the subprocess stdout, stderr messages 105 | for debug. 106 | """ 107 | start = time.time() 108 | arg_str = ' '.join(args) 109 | result = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 110 | 111 | while time.time() - start < 5: 112 | if result.poll() is not None: 113 | break 114 | 115 | if result.poll() is None: 116 | exit_error("%s timeout" % arg_str) 117 | 118 | if result.returncode != 0: 119 | exit_error("%s failed: %d\n %s\n %s\n" % 120 | (arg_str, result.returncode, result.stdout.read(), result.stderr.read())) 121 | else: 122 | return result.stdout.read().decode('utf-8') 123 | 124 | def get_latest_eeprom(): 125 | """ 126 | Returns the path of the latest EEPROM image file if it exists. 127 | """ 128 | latest = shell_cmd(['rpi-eeprom-update', '-l']).rstrip() 129 | if not os.path.exists(latest): 130 | exit_error("EEPROM image '%s' not found" % latest) 131 | return latest 132 | 133 | def apply_update(config, eeprom=None, config_src=None): 134 | """ 135 | Applies the config file to the latest available EEPROM image and spawns 136 | rpi-eeprom-update to schedule the update at the next reboot. 137 | """ 138 | if eeprom is not None: 139 | eeprom_image = eeprom 140 | else: 141 | eeprom_image = get_latest_eeprom() 142 | create_tempdir() 143 | 144 | # Replace the contents of bootconf.txt with the contents of the config file 145 | tmp_update = os.path.join(TEMP_DIR, 'pieeprom.upd') 146 | image = BootloaderImage(eeprom_image, tmp_update) 147 | image.update_file(config, BOOTCONF_TXT) 148 | image.write() 149 | 150 | config_str = open(config).read() 151 | if config_src is None: 152 | config_src = '' 153 | sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig_src: %s\nconfig: %s\n%s\n%s\n%s\n" % 154 | (eeprom_image, config_src, config, '#' * 80, config_str, '#' * 80)) 155 | 156 | sys.stdout.write("\n*** To cancel this update run 'sudo rpi-eeprom-update -r' ***\n\n") 157 | 158 | # Ignore APT package checksums so that this doesn't fail when used 159 | # with EEPROMs with configs delivered outside of APT. 160 | # The checksums are really just a safety check for automatic updates. 161 | args = ['rpi-eeprom-update', '-d', '-i', '-f', tmp_update] 162 | resp = shell_cmd(args) 163 | sys.stdout.write(resp) 164 | 165 | def edit_config(eeprom=None): 166 | """ 167 | Implements something like 'git commit' for editing EEPROM configs. 168 | """ 169 | # Default to nano if $EDITOR is not defined. 170 | editor = 'nano' 171 | if 'EDITOR' in os.environ: 172 | editor = os.environ['EDITOR'] 173 | 174 | config_src = '' 175 | # If there is a pending update then use the configuration from 176 | # that in order to support incremental updates. Otherwise, 177 | # use the current EEPROM configuration. 178 | bootfs = shell_cmd(['rpi-eeprom-update', '-b']).rstrip() 179 | pending = os.path.join(bootfs, 'pieeprom.upd') 180 | if os.path.exists(pending): 181 | config_src = pending 182 | image = BootloaderImage(pending) 183 | current_config = image.get_file(BOOTCONF_TXT).decode('utf-8') 184 | else: 185 | current_config, config_src = read_current_config() 186 | 187 | create_tempdir() 188 | tmp_conf = os.path.join(TEMP_DIR, 'boot.conf') 189 | out = open(tmp_conf, 'w') 190 | out.write(current_config) 191 | out.close() 192 | cmd = "\'%s\' \'%s\'" % (editor, tmp_conf) 193 | result = os.system(cmd) 194 | if result != 0: 195 | exit_error("Aborting update because \'%s\' exited with code %d." % (cmd, result)) 196 | 197 | new_config = open(tmp_conf, 'r').read() 198 | if len(new_config.splitlines()) < 2: 199 | exit_error("Aborting update because \'%s\' appears to be empty." % tmp_conf) 200 | apply_update(tmp_conf, eeprom, config_src) 201 | 202 | def read_current_config(): 203 | """ 204 | Reads the configuration used by the current bootloader. 205 | """ 206 | fw_base = "/sys/firmware/devicetree/base/" 207 | nvmem_base = "/sys/bus/nvmem/devices/" 208 | 209 | if os.path.exists(fw_base + "/aliases/blconfig"): 210 | with open(fw_base + "/aliases/blconfig", "rb") as f: 211 | nvmem_ofnode_path = fw_base + f.read().decode('utf-8') 212 | for d in os.listdir(nvmem_base): 213 | if os.path.realpath(nvmem_base + d + "/of_node") in os.path.normpath(nvmem_ofnode_path): 214 | return (open(nvmem_base + d + "/nvmem", "rb").read().decode('utf-8'), "blconfig device") 215 | 216 | return (shell_cmd(['vcgencmd', 'bootloader_config']), "vcgencmd bootloader_config") 217 | 218 | class ImageSection: 219 | def __init__(self, magic, offset, length, filename=''): 220 | self.magic = magic 221 | self.offset = offset 222 | self.length = length 223 | self.filename = filename 224 | debug("ImageSection %x %x %x %s" % (magic, offset, length, filename)) 225 | 226 | class BootloaderImage(object): 227 | def __init__(self, filename, output=None): 228 | """ 229 | Instantiates a Bootloader image writer with a source eeprom (filename) 230 | and optionally an output filename. 231 | """ 232 | self._filename = filename 233 | self._sections = [] 234 | try: 235 | self._bytes = bytearray(open(filename, 'rb').read()) 236 | except IOError as err: 237 | exit_error("Failed to read \'%s\'\n%s\n" % (filename, str(err))) 238 | self._out = None 239 | if output is not None: 240 | self._out = open(output, 'wb') 241 | 242 | if len(self._bytes) != IMAGE_SIZE: 243 | exit_error("%s: Expected size %d bytes actual size %d bytes" % 244 | (filename, IMAGE_SIZE, len(self._bytes))) 245 | self.parse() 246 | 247 | def parse(self): 248 | """ 249 | Builds a table of offsets to the different sections in the EEPROM. 250 | """ 251 | offset = 0 252 | magic = 0 253 | found = False 254 | while offset < IMAGE_SIZE: 255 | magic, length = struct.unpack_from('>LL', self._bytes, offset) 256 | if magic == 0x0 or magic == 0xffffffff: 257 | break # EOF 258 | elif (magic & MAGIC_MASK) != MAGIC: 259 | raise Exception('EEPROM is corrupted %x %x %x' % (magic, magic & MAGIC_MASK, MAGIC)) 260 | 261 | filename = '' 262 | if magic == FILE_MAGIC: # Found a file 263 | # Discard trailing null characters used to pad filename 264 | filename = self._bytes[offset + 8: offset + FILE_HDR_LEN].decode('utf-8').replace('\0', '') 265 | self._sections.append(ImageSection(magic, offset, length, filename)) 266 | 267 | offset += 8 + length # length + type 268 | offset = (offset + 7) & ~7 269 | 270 | def find_file(self, filename): 271 | """ 272 | Returns the offset, length and whether this is the last section in the 273 | EEPROM for a modifiable file within the image. 274 | """ 275 | ret = (-1, -1, False) 276 | for i in range(0, len(self._sections)): 277 | s = self._sections[i] 278 | if s.magic == FILE_MAGIC and s.filename == filename: 279 | is_last = (i == len(self._sections) - 1) 280 | ret = (s.offset, s.length, is_last) 281 | break 282 | debug('%s offset %d length %d last %s' % (filename, ret[0], ret[1], ret[2])) 283 | return ret 284 | 285 | def update(self, src_bytes, dst_filename): 286 | """ 287 | Replaces a modifiable file with specified byte array. 288 | """ 289 | hdr_offset, length, is_last = self.find_file(dst_filename) 290 | if hdr_offset < 0: 291 | raise Exception('Update target %s not found' % dst_filename) 292 | 293 | if hdr_offset + len(src_bytes) + FILE_HDR_LEN > IMAGE_SIZE: 294 | raise Exception('EEPROM image size exceeded') 295 | 296 | new_len = len(src_bytes) + FILENAME_LEN + 4 297 | struct.pack_into('>L', self._bytes, hdr_offset + 4, new_len) 298 | struct.pack_into(("%ds" % len(src_bytes)), self._bytes, 299 | hdr_offset + 4 + FILE_HDR_LEN, src_bytes) 300 | 301 | # If the new file is smaller than the old file then set any old 302 | # data which is now unused to all ones (erase value) 303 | pad_start = hdr_offset + 4 + FILE_HDR_LEN + len(src_bytes) 304 | 305 | # Add padding up to 8-byte boundary 306 | while pad_start % 8 != 0: 307 | struct.pack_into('B', self._bytes, pad_start, 0xff) 308 | pad_start += 1 309 | 310 | # Create a padding section unless the padding size is smaller than the 311 | # size of a section head. Padding is allowed in the last section but 312 | # by convention bootconf.txt is the last section and there's no need to 313 | # pad to the end of the sector. This also ensures that the loopback 314 | # config read/write tests produce identical binaries. 315 | pad_bytes = ALIGN_SIZE - (pad_start % ALIGN_SIZE) 316 | if pad_bytes > 8 and not is_last: 317 | pad_bytes -= 8 318 | struct.pack_into('>i', self._bytes, pad_start, PAD_MAGIC) 319 | pad_start += 4 320 | struct.pack_into('>i', self._bytes, pad_start, pad_bytes) 321 | pad_start += 4 322 | 323 | debug("pad %d" % pad_bytes) 324 | pad = 0 325 | while pad < pad_bytes: 326 | struct.pack_into('B', self._bytes, pad_start + pad, 0xff) 327 | pad = pad + 1 328 | 329 | def update_key(self, src_pem, dst_filename): 330 | """ 331 | Replaces the specified public key entry with the public key values extracted 332 | from the source PEM file. 333 | """ 334 | pubkey_bytes = pemtobin(src_pem) 335 | self.update(pubkey_bytes, dst_filename) 336 | 337 | def update_file(self, src_filename, dst_filename): 338 | """ 339 | Replaces the contents of dst_filename in the EEPROM with the contents of src_file. 340 | """ 341 | src_bytes = open(src_filename, 'rb').read() 342 | if len(src_bytes) > MAX_FILE_SIZE: 343 | raise Exception("src file %s is too large (%d bytes). The maximum size is %d bytes." 344 | % (src_filename, len(src_bytes), MAX_FILE_SIZE)) 345 | self.update(src_bytes, dst_filename) 346 | 347 | def write(self): 348 | """ 349 | Writes the updated EEPROM image to stdout or the specified output file. 350 | """ 351 | if self._out is not None: 352 | self._out.write(self._bytes) 353 | self._out.close() 354 | else: 355 | if hasattr(sys.stdout, 'buffer'): 356 | sys.stdout.buffer.write(self._bytes) 357 | else: 358 | sys.stdout.write(self._bytes) 359 | 360 | def get_file(self, filename): 361 | hdr_offset, length, is_last = self.find_file(filename) 362 | offset = hdr_offset + 4 + FILE_HDR_LEN 363 | config_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4] 364 | return config_bytes 365 | 366 | def read(self): 367 | config_bytes = self.get_file('bootconf.txt') 368 | if self._out is not None: 369 | self._out.write(config_bytes) 370 | self._out.close() 371 | else: 372 | if hasattr(sys.stdout, 'buffer'): 373 | sys.stdout.buffer.write(config_bytes) 374 | else: 375 | sys.stdout.write(config_bytes) 376 | 377 | def main(): 378 | """ 379 | Utility for reading and writing the configuration file in the 380 | Raspberry Pi 4 bootloader EEPROM image. 381 | """ 382 | description = """\ 383 | Bootloader EEPROM configuration tool for the Raspberry Pi 4. 384 | Operating modes: 385 | 386 | 1. Outputs the current bootloader configuration to STDOUT if no arguments are 387 | specified OR the given output file if --out is specified. 388 | 389 | rpi-eeprom-config [--out boot.conf] 390 | 391 | 2. Extracts the configuration file from the given 'eeprom' file and outputs 392 | the result to STDOUT or the output file if --output is specified. 393 | 394 | rpi-eeprom-config pieeprom.bin [--out boot.conf] 395 | 396 | 3. Writes a new EEPROM image replacing the configuration file with the contents 397 | of the file specified by --config. 398 | 399 | rpi-eeprom-config --config boot.conf --out newimage.bin pieeprom.bin 400 | 401 | The new image file can be installed via rpi-eeprom-update 402 | rpi-eeprom-update -d -f newimage.bin 403 | 404 | 4. Applies a given config file to an EEPROM image and invokes rpi-eeprom-update 405 | to schedule an update of the bootloader when the system is rebooted. 406 | 407 | Since this command launches rpi-eeprom-update to schedule the EEPROM update 408 | it must be run as root. 409 | 410 | sudo rpi-eeprom-config --apply boot.conf [pieeprom.bin] 411 | 412 | If the 'eeprom' argument is not specified then the latest available image 413 | is selected by calling 'rpi-eeprom-update -l'. 414 | 415 | 5. The '--edit' parameter behaves the same as '--apply' except that instead of 416 | applying a predefined configuration file a text editor is launched with the 417 | contents of the current EEPROM configuration. 418 | 419 | Since this command launches rpi-eeprom-update to schedule the EEPROM update 420 | it must be run as root. 421 | 422 | The configuration file will be taken from: 423 | * The blconfig reserved memory nvmem device 424 | * The cached bootloader configuration 'vcgencmd bootloader_config' 425 | * The current pending update - typically /boot/pieeprom.upd 426 | 427 | sudo -E rpi-eeprom-config --edit [pieeprom.bin] 428 | 429 | To cancel the pending update run 'sudo rpi-eeprom-update -r' 430 | 431 | The default text editor is nano and may be overridden by setting the 'EDITOR' 432 | environment variable and passing '-E' to 'sudo' to preserve the environment. 433 | 434 | 6. Signing the bootloader config file. 435 | Updates an EEPROM binary with a signed config file (created by rpi-eeprom-digest) plus 436 | the corresponding RSA public key. 437 | 438 | Requires Python Cryptodomex libraries and OpenSSL. To install on Raspberry Pi OS run:- 439 | sudo apt install openssl python-pip 440 | sudo python3 -m pip install cryptodomex 441 | 442 | rpi-eeprom-digest -k private.pem -i bootconf.txt -o bootconf.sig 443 | rpi-eeprom-config --config bootconf.txt --digest bootconf.sig --pubkey public.pem --out pieeprom-signed.bin pieeprom.bin 444 | 445 | Currently, the signing process is a separate step so can't be used with the --edit or --apply modes. 446 | 447 | 448 | See 'rpi-eeprom-update -h' for more information about the available EEPROM images. 449 | """ 450 | parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, 451 | description=description) 452 | 453 | parser.add_argument('-a', '--apply', required=False, 454 | help='Updates the bootloader to the given config plus latest available EEPROM release.') 455 | parser.add_argument('-c', '--config', help='Name of bootloader configuration file', required=False) 456 | parser.add_argument('-e', '--edit', action='store_true', default=False, help='Edit the current EEPROM config') 457 | parser.add_argument('-o', '--out', help='Name of output file', required=False) 458 | parser.add_argument('-d', '--digest', help='Signed boot only. The name of the .sig file generated by rpi-eeprom-dgst for config.txt ', required=False) 459 | parser.add_argument('-p', '--pubkey', help='Signed boot only. The name of the RSA public key file to store in the EEPROM', required=False) 460 | parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input') 461 | args = parser.parse_args() 462 | 463 | if (args.edit or args.apply is not None) and os.getuid() != 0: 464 | exit_error("--edit/--apply must be run as root") 465 | 466 | if (args.edit or args.apply is not None) and not rpi4(): 467 | exit_error("--edit/--apply must run on a Raspberry Pi 4") 468 | 469 | if args.edit: 470 | edit_config(args.eeprom) 471 | elif args.apply is not None: 472 | if not os.path.exists(args.apply): 473 | exit_error("config file '%s' not found" % args.apply) 474 | apply_update(args.apply, args.eeprom, args.apply) 475 | elif args.eeprom is not None: 476 | image = BootloaderImage(args.eeprom, args.out) 477 | if args.config is not None: 478 | if not os.path.exists(args.config): 479 | exit_error("config file '%s' not found" % args.config) 480 | image.update_file(args.config, BOOTCONF_TXT) 481 | if args.digest is not None: 482 | image.update_file(args.digest, BOOTCONF_SIG) 483 | if args.pubkey is not None: 484 | image.update_key(args.pubkey, PUBKEY_BIN) 485 | image.write() 486 | else: 487 | image.read() 488 | elif args.config is None and args.eeprom is None: 489 | current_config, config_src = read_current_config() 490 | if args.out is not None: 491 | open(args.out, 'w').write(current_config) 492 | else: 493 | sys.stdout.write(current_config) 494 | 495 | if __name__ == '__main__': 496 | atexit.register(exit_handler) 497 | main() 498 | -------------------------------------------------------------------------------- /recovery/update-pieeprom.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Utility to update the EEPROM image (pieeprom.bin) and signature 4 | # (pieeprom.sig) with a new EEPROM config. 5 | # 6 | # This script is now a thin wrapper for the new version in ../tools 7 | # 8 | # pieeprom.original.bin - The source EEPROM from rpi-eeprom repo 9 | # boot.conf - The bootloader config file to apply. 10 | 11 | set -e 12 | 13 | ../tools/update-pieeprom.sh "$@" 14 | -------------------------------------------------------------------------------- /recovery5/.gitignore: -------------------------------------------------------------------------------- 1 | pieeprom.bin 2 | pieeprom.sig 3 | -------------------------------------------------------------------------------- /recovery5/README.md: -------------------------------------------------------------------------------- 1 | To update the SPI EEPROM bootloader on Raspberry Pi 5. 2 | 3 | * Modify the EEPROM configuration in `boot.conf` as desired 4 | * Optionally, replace pieeprom.original.bin with a custom version. The default 5 | version here is the latest stable release recommended for use on Raspberry Pi 5. 6 | * Run `update-eeprom.sh` to create the image to flash 7 | 8 | ```bash 9 | cd recovery5 10 | ./update-pieeprom.sh 11 | ../rpiboot -d . 12 | ``` 13 | 14 | N.B The `bootcode5.bin` file in this directory is actually the `recovery.bin` 15 | file used on Raspberry Pi 5 bootloader update cards. 16 | 17 | -------------------------------------------------------------------------------- /recovery5/boot.conf: -------------------------------------------------------------------------------- 1 | [all] 2 | BOOT_UART=1 3 | POWER_OFF_ON_HALT=1 4 | 5 | # Boot Order Codes, from https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#BOOT_ORDER 6 | # Try SD first (1), followed by, USB PCIe, NVMe PCIe, then network 7 | BOOT_ORDER=0xf2461 8 | -------------------------------------------------------------------------------- /recovery5/bootcode5.bin: -------------------------------------------------------------------------------- 1 | ../firmware/2712/recovery.bin -------------------------------------------------------------------------------- /recovery5/config.txt: -------------------------------------------------------------------------------- 1 | # Uncomment to enable more UART debug logs 2 | uart_2ndstage=1 3 | 4 | # Uncomment to instruct recovery.bin to reboot the Pi after flashing the bootloader image 5 | #recovery_reboot=1 6 | 7 | # Uncomment to instruct recovery.bin to send metadata including OTP fields 8 | # Specify -j dirname on the command line to specify the directory where 9 | # metadata should be stored (JSON format) 10 | recovery_metadata=1 -------------------------------------------------------------------------------- /recovery5/pieeprom.original.bin: -------------------------------------------------------------------------------- 1 | ../firmware/2712/pieeprom.bin -------------------------------------------------------------------------------- /recovery5/update-pieeprom.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Utility to update the EEPROM image (pieeprom.bin) and signature 4 | # (pieeprom.sig) with a new EEPROM config. 5 | # 6 | # This script is now a thin wrapper for the new version in ../tools 7 | # 8 | # pieeprom.original.bin - The source EEPROM from rpi-eeprom repo 9 | # boot.conf - The bootloader config file to apply. 10 | 11 | set -e 12 | 13 | ../tools/update-pieeprom.sh "$@" 14 | -------------------------------------------------------------------------------- /rpi-imager-embedded/.gitignore: -------------------------------------------------------------------------------- 1 | boot.sig 2 | -------------------------------------------------------------------------------- /rpi-imager-embedded/README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi Imager - Embedded 2 | 3 | This directory contains the configuration files required to launch the 4 | embedded version of the Raspberry Pi imager via rpiboot. 5 | This requires a Raspberry Pi 4 or newer. 6 | 7 | The `boot.img` file is no longer stored in this repository. 8 | 9 | To download the latest version, run: 10 | ```bash 11 | wget https://downloads.raspberrypi.com/net_install/boot.img 12 | ``` 13 | 14 | To run: 15 | ```bash 16 | cd rpi-imager-embedded 17 | ../rpiboot -d . 18 | ``` 19 | 20 | Make sure that the HDMI display is connected. Once Linux has started 21 | you will need to unplug the micro-USB cable (when prompted) and connect 22 | a keyboard and mouse. 23 | -------------------------------------------------------------------------------- /rpi-imager-embedded/bootfiles.bin: -------------------------------------------------------------------------------- 1 | ../firmware/bootfiles.bin -------------------------------------------------------------------------------- /rpi-imager-embedded/config.txt: -------------------------------------------------------------------------------- 1 | boot_ramdisk=1 2 | uart_2ndstage=1 3 | -------------------------------------------------------------------------------- /secure-boot-example/README.md: -------------------------------------------------------------------------------- 1 | # Secure Boot Quickstart 2 | 3 | ## Overview 4 | 5 | This example demonstrates how the low level code signing and provisioning tools can be used to enable 6 | signed boot on Compute Module 4 or Compute Module 5. For simplicity, the example is based on the 7 | mass-storage-gadget which small buildroot image. 8 | 9 | For production systems we recommend using the higher level [Raspberry Pi Secure Boot Provisioner](https://github.com/raspberrypi/rpi-sb-provisioner) 10 | 11 | See also: EEPROM and OTP provisioning guides for secure boot [secure-boot-recovery CM4](../secure-boot-recovery/README.md) secure boot [secure-boot-recovery CM5](../secure-boot-recovery5/README.md) 12 | 13 | **WARNING: Enabling signed boot modifies the OTP memory and is irreversible. ** 14 | 15 | ### Requirements for running this example 16 | * A Raspberry Pi Compute Module 5 or Compute Module 4 / 4S and the relevant IO board. 17 | * Micro USB cable for `rpiboot` connection 18 | * USB serial cable (for debug logs) 19 | * Linux, WSL or Cygwin (Windows 11) 20 | * OpenSSL 21 | * Python3 22 | * Python `cryptodomex` 23 | 24 | ```bash 25 | python3 -m pip install pycryptodomex 26 | # or 27 | pip install pycryptodomex 28 | ``` 29 | 30 | ### Clean configuration 31 | Before starting it's advisable to create a fresh clone of the `usbboot` repo 32 | to ensure that there are no stale configuration files. 33 | 34 | ```bash 35 | git clone https://github.com/raspberrypi/usbboot secure-boot 36 | cd secure-boot 37 | git submodule update --init 38 | make 39 | ``` 40 | See the top-level [README](../Readme.md) for full build instructions. 41 | 42 | ### Hardware setup for `rpiboot` mode 43 | Prepare the Compute Module for `rpiboot` mode: 44 | 45 | #### Compute Module 4 46 | * Set the `nRPIBOOT` jumper which is labelled `Fit jumper to disable eMMC Boot' on the Compute Module 4 IO board. 47 | * Connect the micro USB cable to the `USB slave` port on the Compute Module IO board. 48 | * Power cycle the Compute Module IO board. 49 | * Connect the USB serial adapter to [GPIO 14/15](https://www.raspberrypi.com/documentation/computers/os.html#gpio-and-the-40-pin-header) on the 40-pin header. 50 | 51 | #### Compute Module 5 52 | * Set the `nRPIBOOT` jumper which is labelled `Fit jumper to disable eMMC Boot' on the Compute Module 5 IO board. 53 | * Disconnect any USB peripherals from the IO board in order to reduce power consumption. 54 | * Connect USB-A to USB-C cable from the rpiboot host to the IO board. 55 | * Connect the USB serial adapter to [GPIO 14/15](https://www.raspberrypi.com/documentation/computers/os.html#gpio-and-the-40-pin-header) on the 40-pin header. 56 | * If the Compute Module has the dedicate boot UART connector fitted then this will provide additional debug. 57 | * Power cycle the Compute Module IO board. 58 | 59 | ### Generate a signing key 60 | Secure boot requires a 2048 bit RSA private key. You can either use a pre-existing 61 | key or generate an specific key for this example. The `KEY_FILE` environment variable 62 | used in the following instructions must contain the absolute path to the RSA private key in 63 | PEM format. 64 | 65 | ```bash 66 | openssl genrsa 2048 > private.pem 67 | export KEY_FILE=$(pwd)/private.pem 68 | ``` 69 | 70 | **In a production environment it's essential that this key file is stored privately and securely.** 71 | 72 | ### Update the EEPROM to require signed OS images 73 | Enable `rpiboot` mode and flash the bootloader EEPROM with updated setting enables code signing. 74 | 75 | Running `update-pieeprom.sh` generates the signed `pieeprom.bin` image. 76 | 77 | ```bash 78 | cd secure-boot-recovery 79 | # Generate the signed EEPROM image. 80 | ../tools/update-pieeprom.sh -k "${KEY_FILE}" 81 | cd .. 82 | # On Compute Modeule 4 or 4S 83 | ./rpiboot -d secure-boot-recovery 84 | # On Compute Module 5 85 | ./rpiboot -d secure-boot-recovery5 86 | ``` 87 | 88 | ## Sign the example image 89 | Once secure-boot has been enable the OS `boot.img` file must be signed with the customer private key. 90 | On Compute Module 5 the Raspberry Pi 5 firmware must also be counter-signed with this key. 91 | 92 | The `sign.sh` script wraps the low level commands to do this:- 93 | ```bash 94 | ./sign.sh ${KEY_FILE} 95 | ``` 96 | 97 | ### Launch the signed OS image 98 | Enable `rpiboot` mode and run the example OS. If the `boot.sig` signature does not match `boot.img`, 99 | the bootloader will refuse to load the OS. 100 | 101 | ```bash 102 | ./rpiboot -d secure-boot-example 103 | ``` 104 | Login as `root` with the empty password. 105 | 106 | #### Disk encryption example 107 | Example script which uses a device-specific private key to create/mount an encrypted file-system. 108 | 109 | Generating a 256-bit random key for test purposes. 110 | ```bash 111 | export KEY_FILE=$(mktemp -d)/key.bin 112 | openssl rand -hex 32 | xxd -rp > ${KEY_FILE} 113 | ``` 114 | 115 | Using [rpi-otp-private-key](../tools/rpi-otp-private-key) to extract the device private key (if programmed). 116 | ```bash 117 | export KEY_FILE=$(mktemp -d)/key.bin 118 | rpi-otp-private-key -b > "${KEY_FILE}" 119 | ``` 120 | 121 | Creating an encrypted disk on a specified block device. 122 | ```bash 123 | export BLK_DEV=/dev/mmcblk0p3 124 | cryptsetup luksFormat --key-file="${KEY_FILE}" --key-size=256 --type=luks2 ${BLK_DEV} 125 | 126 | cryptsetup luksOpen ${BLK_DEV} encrypted-disk --key-file="${KEY_FILE}" 127 | mkfs /dev/mapper/encrypted-disk 128 | mkdir -p /mnt/application-data 129 | mount /dev/mapper/encrypted-disk /mnt/application-data 130 | rm "${KEY_FILE}" 131 | ``` 132 | 133 | ### Mount the Compute Module SD/EMMC after enabling secure-boot 134 | Once signed-boot is enabled the bootloader will only load images signed with private key generated earlier. 135 | To boot the Compute Module in mass storage mode a signed version of this code must be generated. 136 | 137 | **This signed image MUST NOT be distributed because it gives access to the EMMC.** 138 | 139 | 140 | #### Sign the mass storage firmware image 141 | Sign the mass storage drivers in the `secure-boot-msd` directory. Please see the [top level README](../Readme.md#compute-module-4-extensions) for a description of the different `usbboot` firmware drivers. 142 | ```bash 143 | cd secure-boot-msd 144 | ../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}" 145 | cd .. 146 | ``` 147 | 148 | #### Enable MSD mode 149 | A new mass storage device should now be visible on the host OS. On Linux check `dmesg` for something like '/dev/sda'. 150 | ```bash 151 | ./rpiboot -d secure-boot-msd 152 | ``` 153 | 154 | ### Loading `boot.img` from SD/EMMC 155 | The bootloader can load a ramdisk `boot.img` from any of the bootable modes defined by the [BOOT_ORDER](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#BOOT_ORDER) EEPROM config setting. 156 | 157 | For example: 158 | 159 | * Boot the Compute Module in MSD mode as explained in the previous step. 160 | * Copy the `boot.img` and `boot.sig` files from the `secure-boot-example` stage to the mass storage drive: No other files are required. 161 | * Remove the `nRPIBOOT` jumper. 162 | * Power cycle the Compute Module IO board. 163 | * The system should now boot into the OS. 164 | 165 | ### Modifying / rebuilding `boot.img` 166 | The secure-boot example image can be rebuilt and modified using buildroot. See [raspberrypi-signed-boot](https://github.com/raspberrypi/buildroot/blob/raspberrypi-signed-boot64/README.md) buildroot configuration. 167 | -------------------------------------------------------------------------------- /secure-boot-example/boot.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/secure-boot-example/boot.img -------------------------------------------------------------------------------- /secure-boot-example/boot.sig: -------------------------------------------------------------------------------- 1 | 902be6c484ad71bb9f88b2fce51a2baf01efcb031c9daa0557d3b722c3951959 2 | ts: 1734010524 3 | rsa2048: 285da4d24eead52ebba556e58638fdbd04191fd30071e596c7c15906738c67b0c13fc24f0e5ef94e2ceea7bf11dcf8cc0dda06f6aa0f00ab86bad228c05cd6b873e91f8630ef71b8f7aa8cc9ee3ecc12a52a61fdb3b888038f9025cae5ad49d6c692b1838ffabaf758aa6f8a94b6643f154b8874040f98e23254a52c3a96c05119202c4563fa52c6b26932ee5d3209267e3d4513567b3d88ca0c2b14c42a3bd7462cc1a2a504fba2e1e4aa8c7ea9ce44d19af87bb35bb69a792801fc4b12c5d493562d326b41f228a00ad845ff822babe7ec82ba7651984a7a13a124d22b0938ab10bc44312e79b754b57dbae09418e8e7d07c4708773979954ad0b0dc8b12e0 4 | -------------------------------------------------------------------------------- /secure-boot-example/bootfiles.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/secure-boot-example/bootfiles.bin -------------------------------------------------------------------------------- /secure-boot-example/config.txt: -------------------------------------------------------------------------------- 1 | # Load the boot files from the ramdisk which is signed in secure-boot mode 2 | boot_ramdisk=1 3 | uart_2ndstage=1 4 | -------------------------------------------------------------------------------- /secure-boot-example/example-hsm-wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | 6 | script_dir=$(cd "$(dirname "$0")" && pwd) 7 | KEY="${script_dir}/example-private.pem" 8 | ALGORITHM="" 9 | OPENSSL=${OPENSSL:-openssl} 10 | 11 | die() { 12 | echo "$@" >&2 13 | exit 1 14 | } 15 | 16 | usage() { 17 | cat << EOF 18 | Example HSM Wrapper Interface 19 | ============================ 20 | This is an example program to demonstrate the HSM wrapper interface for secure boot signing. 21 | It signs the provided input file using RSA2048 with SHA256 and outputs the PKCS#1 v1.5 signature in hex format. 22 | 23 | This program should be used as a template to implement a HSM wrapper for a specific HSM 24 | and be in the PATH of the user running rpi-eeprom-digest / rpi-sign-bootcode. 25 | 26 | Usage: $(basename $0) -a ALGORITHM INPUT_FILE 27 | 28 | Options: 29 | -a ALGORITHM The signing algorithm to use (currently only supports rsa2048-sha256) 30 | INPUT_FILE The input file to sign 31 | -h Display this help message 32 | 33 | Output: 34 | The script outputs the RSA signature in hexadecimal format to stdout. 35 | This is the format expected by the rpi-sign-bootcode tool when using an HSM wrapper. 36 | 37 | Example: 38 | $(basename $0) -a rsa2048-sha256 input.bin > signature.hex 39 | EOF 40 | exit 1 41 | } 42 | 43 | while getopts "a:h" opt; do 44 | case "${opt}" in 45 | a) ALGORITHM="${OPTARG}" 46 | ;; 47 | h) usage 48 | ;; 49 | *) usage 50 | ;; 51 | esac 52 | done 53 | 54 | # Shift past the options 55 | shift $((OPTIND-1)) 56 | 57 | # Check for mandatory arguments 58 | [ -n "${ALGORITHM}" ] || die "$(basename $0): Algorithm (-a) is required" 59 | [ $# -eq 1 ] || die "$(basename $0): Input file must be specified as a positional argument" 60 | 61 | # Get the input file from positional argument 62 | IMAGE="$1" 63 | 64 | # Validate algorithm 65 | if [ "${ALGORITHM}" != "rsa2048-sha256" ]; then 66 | die "$(basename $0): Unsupported algorithm '${ALGORITHM}'. Only rsa2048-sha256 is supported" 67 | fi 68 | 69 | # Validate input file 70 | [ -f "${IMAGE}" ] || die "$(basename $0): Input file ${IMAGE} not found" 71 | 72 | # Sign the file using the specified algorithm 73 | ${OPENSSL} dgst -hex -sign "${KEY}" -sha256 "${IMAGE}" | awk '{print $2}' 74 | -------------------------------------------------------------------------------- /secure-boot-example/example-private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA+l3E+h/QNjrIR1cG6NpzP0fBwp2UDpuQAafXDS5yryrfCPDY 3 | TO9DvzAfOk9Dz/putDfHV0RTOFXv1tmc4nqOgU6nKx7tTdsjTiY4CgG3vXRMuAmD 4 | GX5ssJFCVmljGuILt1INlCmtun7Ow35VTxOcRDDfrBDKnSitzOTf6KTR7xJhqFFh 5 | dMpIg8hW4bDBKMavyt38pRvDaO1o01qaQT/GgAPmJm27y5RKNAe6iVTqsm4TMAhK 6 | C6P4XyRAbe6OMdFZyEWEk7Asexuc7uZlVHsUI6pebSW/07O+5l/U7/3k6r//hO/H 7 | DFOBUUW55EjzzC1BhTlWHWfZNI+5+NdN8o323QIDAQABAoIBAByQGZKSkhG5w5MV 8 | ++ERWQARaurNyPAgsb1qnUdw8t8GlFLkDT07t74mWo2vsNQXpU0Upv6O+jKNZVMc 9 | 2P/ijQL2Cu7JtLeC5mR6Sj7kAscPr1f4p9b+/B3puIh8tfSBcOY9a3Spi5sg7+xQ 10 | K6HdoiCKdd4evUrQMwHS47OaKCQuuibm46LWbXO1nk9QkymUy6zyaT5IuNpfKYKD 11 | UdFqV1FNwZ9A2Yb89rweBgU4DWdbjgVqBc23vS9l913rqd2LHN/4+XDBOGrovu5r 12 | mJy4WsyXuT0twuqi7FzhtbCdN/zhLo2od1XK6uA65EKdA9rrRMkNeGvxts6q3fPE 13 | i6tj7OECgYEA/YbIR8n8Vvb5XPAav/aAon4qjXyhkUTjnJfVT0yA+6T1AJwvQ+O4 14 | AhYgN4ld7msKRDJLcJs0EU8CmWUKJRt5Ai+JsOCbPuBNo+VGEFSsdG0mrSjFZf2e 15 | Bjm41lnvAEWReGwr9MVIf/prDE2/3aUl9irkNdu5q6NpG9M0N7AhzGECgYEA/M8Y 16 | Ew9Nv+XqEVKvOzxKRZBa6yzlOUj5PQ3cD7jl1aUNK4rTucvr3sJZAsgm5j+0XG99 17 | AJ447zdDEdcQbsOSaBR69pccdHYEaRSiIxWaCAir2BBS5DxYtgB6BLrIfBd1cKHv 18 | qB6u4M6FRJ5BcQa6VYlizAfG2yXoJv0xFrlQ2/0CgYEAwq0Alb+QOOckzCzDHayX 19 | Ui83VbXiCr6vWMtuTJoeYR1l1LYZxTPTVCbRTlP5AN7I310PeMR00uWsxUVE6QGT 20 | hg4i2ONf0oRCmhuwFVIvqqc2D7lC+vIoqfcg69fbIoZJEgNeLXJgHYWZNbVuIzBx 21 | WfnNi13R0O6GA4vGiQyCp4ECgYB1ZTG3wBaJsxlDnBLVPgT7UrJ1nO6A8HsUt/fl 22 | sSXBVRjNjHUPRTutwLAW050EtLZrajYw8EheBVp20VjHJrg47rG/CqLjDd60cSlt 23 | g114t5YdCk+DvuYu9f+zbI0m2rnlaL1iY4UvzZcjKx4Wf1pN2DNxrXbRU0P/vvlp 24 | pPqAfQKBgDZnxWuvRsT9rztGrEottifchfrStZx7u/2+iBtjFeFXr7L4MI14fNm2 25 | HkoThCpfFXCJFpRxy+kYi6xbPK/Om/hFNs3J5xqheTW8hFx7KN/zPg7jc0MlZ2R/ 26 | uuOgZU9kkzLOamDyP85Doah7kAyA2PnLUno2k4IirbNVoH3aV++G 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /secure-boot-example/example-public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+l3E+h/QNjrIR1cG6Npz 3 | P0fBwp2UDpuQAafXDS5yryrfCPDYTO9DvzAfOk9Dz/putDfHV0RTOFXv1tmc4nqO 4 | gU6nKx7tTdsjTiY4CgG3vXRMuAmDGX5ssJFCVmljGuILt1INlCmtun7Ow35VTxOc 5 | RDDfrBDKnSitzOTf6KTR7xJhqFFhdMpIg8hW4bDBKMavyt38pRvDaO1o01qaQT/G 6 | gAPmJm27y5RKNAe6iVTqsm4TMAhKC6P4XyRAbe6OMdFZyEWEk7Asexuc7uZlVHsU 7 | I6pebSW/07O+5l/U7/3k6r//hO/HDFOBUUW55EjzzC1BhTlWHWfZNI+5+NdN8o32 8 | 3QIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /secure-boot-example/sign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | script_dir="$(cd "$(dirname "$0")" && pwd)" 5 | 6 | TMP_DIR="" 7 | 8 | die() { 9 | echo "$@" >&2 10 | exit 1 11 | } 12 | 13 | cleanup() { 14 | if [ -d "${TMP_DIR}" ]; then rm -rf "${TMP_DIR}"; fi 15 | } 16 | 17 | sign_firmware_blob() { 18 | echo "Signing firmware in ${1}" 19 | [ -f "${KEY_FILE}" ] || die "sign-firmware: key-file ${KEY_FILE} not found" 20 | rpi-sign-bootcode \ 21 | -c 2712 \ 22 | -i "${1}" \ 23 | -o "${2}" \ 24 | -n 16 \ 25 | -v 0 \ 26 | -k "${KEY_FILE}" 27 | } 28 | 29 | sign_bootfiles() { 30 | echo "Signing OS image ${1}" 31 | input="${1}" 32 | output="${2}" 33 | ( 34 | cd "${TMP_DIR}" 35 | tar -xf "${input}" 36 | echo "Signing 2712/bootcode5.bin" 37 | sign_firmware_blob 2712/bootcode5.bin 2712/bootcode5.bin.signed "${KEY_FILE}" || die "Failed to sign bootcode5.bin" 38 | mv -f "2712/bootcode5.bin.signed" "2712/bootcode5.bin" 39 | tar -cf "${output}" * 40 | find . 41 | ) 42 | 43 | } 44 | 45 | trap cleanup EXIT 46 | 47 | KEY_FILE="${1}" 48 | [ -f "${KEY_FILE}" ] || die "KEY_FILE: ${KEY_FILE} not found" 49 | 50 | PATH="${script_dir}/../tools:${PATH}" 51 | KEY_FILE="${1}" 52 | TMP_DIR="$(mktemp -d)" 53 | rm -f bootfiles.bin 54 | ln -sf ../firmware/bootfiles.bin bootfiles.original.bin 55 | sign_bootfiles "$(pwd)/bootfiles.original.bin" "$(pwd)/bootfiles.bin" "${KEY_FILE}" 56 | 57 | echo "Signing boot.img with ${KEY_FILE}" 58 | rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}" 59 | -------------------------------------------------------------------------------- /secure-boot-msd/.gitignore: -------------------------------------------------------------------------------- 1 | *.h 2 | boot.sig 3 | -------------------------------------------------------------------------------- /secure-boot-msd/README.md: -------------------------------------------------------------------------------- 1 | # USB MSD FIRMWARE device mode drivers for signed-boot 2 | 3 | NOTE: This module is deprecated. Please use the Linux based 4 | mass-storage-gadget64 which provides better performance and 5 | support for NVMe and MSD devices. 6 | 7 | If secure-boot mode has been locked (via OTP) then both the 8 | bootloader and rpiboot `bootcode4.bin` will only load `boot.img` 9 | files signed with the customer's private key. Therefore, access 10 | to rpiboot mass storage mode is disabled. 11 | 12 | Mass storage mode can be re-enabled by signing a boot image 13 | containing the firmware mass storage drivers. 14 | 15 | N.B. The signed image should normally be kept secure because can 16 | be used on any device signed with the same customer key. 17 | 18 | WARNING: This image is NOT compatible with Pi5. 19 | 20 | To sign the mass storage mode boot image run:- 21 | ```bash 22 | KEY_FILE=$HOME/private.pem 23 | rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}" 24 | ``` 25 | 26 | To run load the USB MSD device drivers via RPIBOOT run 27 | ```bash 28 | ../rpiboot -d . 29 | ``` 30 | -------------------------------------------------------------------------------- /secure-boot-msd/boot.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/secure-boot-msd/boot.img -------------------------------------------------------------------------------- /secure-boot-msd/bootcode4.bin: -------------------------------------------------------------------------------- 1 | ../firmware/2711/bootcode4.bin -------------------------------------------------------------------------------- /secure-boot-msd/config.txt: -------------------------------------------------------------------------------- 1 | # Load boot.img which contains usb.elf 2 | # In signed-boot or secure-boot mode the bootloader checks the 3 | # RSA signature of the ramdisk. The signature is located in boot.sig 4 | boot_ramdisk=1 5 | uart_2ndstage=1 6 | -------------------------------------------------------------------------------- /secure-boot-recovery/.gitignore: -------------------------------------------------------------------------------- 1 | pieeprom.bin 2 | pieeprom.sig 3 | metadata/ 4 | -------------------------------------------------------------------------------- /secure-boot-recovery/README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi 4 - secure boot 2 | 3 | This directory contains the latest stable versions of the bootloader EEPROM 4 | and recovery.bin files that support secure-boot. 5 | 6 | Steps for enabling secure boot: 7 | 8 | ## Extra steps for Raspberry Pi 4B & Pi 400 9 | Raspberry Pi 4B and Pi400 do not have a dedicated RPIBOOT jumper so a different GPIO 10 | must be used to enable RPIBOOT if pulled low. The available GPIOs are `2,4,5,6,7,8` 11 | since these are high by default. 12 | 13 | Pi4 Model B rev 1.3 and older use the BCM2711B0 processor which does not support secure-boot. 14 | All CM4, CM4S and Pi400 boards use BCM2711C0 which supports secure-boot. 15 | 16 | ### Step 1 - Erase the EEPROM 17 | In order to avoid this OTP configuration being accidentally set on Pi 4B / Pi 400 18 | this option can only be set via RPIBOOT. To force RPIBOOT on a Pi 4B / Pi 400 19 | erase the SPI EEPROM. 20 | 21 | * Use `Raspberry Pi Imager` to flash a bootloader image to a spare SD card. 22 | * Remove `pieeprom.bin` and `pieeprom.sig` from the SD card image. 23 | * Add a `config.txt` file to the SD card with the following entries then boot the Pi with this card. 24 | 25 | ``` 26 | erase_eeprom=1 27 | uart_2ndstage=1 28 | ``` 29 | 30 | ### Step 2 - Select the nRPIBOOT GPIO 31 | Edit the `secure-boot-recovery/config.txt` file to specify the GPIO to use for nRPIBOOT. For example: 32 | ``` 33 | program_rpiboot_gpio=8 34 | ``` 35 | 36 | This can either be programmed in isolation or combined with the steps to program the secure-boot OTP settings. 37 | 38 | ## Optional. Specify the private key file in an environment variable. 39 | Alternatively, specify the path when invoking the helper scripts. 40 | ```bash 41 | export KEY_FILE="${HOME}/private.pem" 42 | ``` 43 | 44 | ## Optional. Customize the EEPROM config. 45 | Custom with the desired bootloader settings. 46 | See: [Bootloader configuration](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-bootloader-configuration) 47 | 48 | Setting `SIGNED_BOOT=1` enables signed-boot mode so that the bootloader will only 49 | boot.img files signed with the specified RSA key. Since this is an EEPROM config 50 | option secure-boot can be tested and reverted via `RPIBOOT` at this stage. 51 | 52 | ## Generate the signed bootloader image 53 | ```bash 54 | cd secure-boot-recovery 55 | ../tools/update-pieeprom.sh -k "${KEY_FILE}" 56 | ``` 57 | 58 | `pieeprom.bin` can then be flashed to the bootloader EEPROM via `rpiboot`. 59 | 60 | ## Program the EEPROM image using rpiboot 61 | * Power off CM4 62 | * Set nRPIBOOT jumper and remove EEPROM WP protection 63 | * If possible connect a UART to the CM4 and capture the output for debug 64 | 65 | ```bash 66 | cd secure-boot-recovery 67 | mkdir -p metadata 68 | ../rpiboot -d . -j metadata 69 | ``` 70 | 71 | ### Example UART output 72 | ``` 73 | 74 | 148.84 Verify BOOT EEPROM 75 | 148.85 Reading EEPROM: 524288 bytes 0xc0b60000 76 | 148.35 645ms 77 | 149.89 BOOT-EEPROM: UPDATED 78 | 149.08 secure_boot_provision program_pubkey 1 79 | 149.09 bootconf.sig 80 | 149.09 hash: b503f8ad7f8aea93a272a5ba5248cc5222d0c55af28a4345d0a496bdba9d16bf 81 | 149.10 rsa2048: 612bda4eee969ef9f3e1a651cc4ae6f8b5c5e1eef436e0ff80a4bea2e70bb163b1a54e1376be04a248d7a1f52256bcf0f3dab71b93fb344d7200b61f1020f4620e7ad587cf7fbc35a10b7cd928fe6e3e239d6a7b17a0fd62ddf49ac5fc667686fb43be4b24811a8e1b6e31de525dd2f31ac851f5a19815aa85f9755456610161e034ff7672fd69d567c159d84f703bfbdd76c9a6ec3804236d3dd5550f09d083521d4f6cb6f50ab1ada7c37090a6bc8e306690f04d06dab02b0b80027c9cd27bef18be14f771bb4841bf5a285fadc2731278fc73efccbab1f60fd58c3ada1b35f6a11e9862b5eacdcff420c827f33b498fc2782659e1bcf35bf1ef02adb46c28 82 | 149.14 RSA verify 83 | 149.81 rsa-verify pass (0x0) 84 | 149.18 Public key hash 8251a63a2edee9d8f710d63e9da5d639064929ce15a2238986a189ac6fcd3cee 85 | 149.19 OTP-WR: boot-mode 000048b0 86 | 149.19 OTP-WR: boot-mode 000048b0 87 | 149.19 OTP-WR: flags 00000081 88 | 149.19 Write OTP key 89 | 149.22 OTP updated for key 8251a63a2edee9d8f710d63e9da5d639064929ce15a2238986a189ac6fcd3cee 90 | 149.23 Revoke development key 91 | ``` 92 | 93 | ### Metadata 94 | The optional metadata argument causes rpiboot to readback the OTP information and write it to a JSON file in the given directory. 95 | This can be useful for debug or for storing in a provisioning database. 96 | 97 | Example metadata: 98 | ```json 99 | { 100 | "MAC_ADDR" : "d8:3a:dd:05:ee:78", 101 | "CUSTOMER_KEY_HASH" : "8251a63a2edee9d8f710d63e9da5d639064929ce15a2238986a189ac6fcd3cee", 102 | "BOOT_ROM" : "0000c8b0", 103 | "BOARD_ATTR" : "00000000", 104 | "USER_BOARDREV" : "c03141", 105 | "JTAG_LOCKED" : "0", 106 | "ADVANCED_BOOT" : "0000e8e8" 107 | } 108 | ``` 109 | * Power ON CM4 110 | 111 | ## Locking secure-boot mode 112 | After verifying that the signed OS image boots successfully the system 113 | can be locked into secure-boot mode. This writes the hash of the 114 | customer public key to "one time programmable" (OTP) bits. From then 115 | onwards: 116 | 117 | * The bootloader will only load OS images signed with the customer private key. 118 | * The EEPROM configuration file must be signed with the customer private key. 119 | * It is not possible to downgrade to an old version of the bootloader that doesn't support secure boot. 120 | 121 | To enable this edit the `config.txt` file in this directory and set `program_pubkey=1` 122 | 123 | ## Disabling VideoCore JTAG 124 | 125 | VideoCore JTAG may be permanently disabled by setting `program_jtag_lock` in 126 | `config.txt`. This option has no effect unless secure-boot has been enabled. 127 | 128 | See [config.txt](config.txt) 129 | -------------------------------------------------------------------------------- /secure-boot-recovery/boot.conf: -------------------------------------------------------------------------------- 1 | [all] 2 | BOOT_UART=1 3 | WAKE_ON_GPIO=0 4 | POWER_OFF_ON_HALT=1 5 | HDMI_DELAY=0 6 | 7 | # Boot Order Codes, from https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#BOOT_ORDER 8 | # Try SD first (1), followed by, USB PCIe, NVMe PCIe, USB SoC XHCI then network 9 | BOOT_ORDER=0xf25641 10 | 11 | # Disable self-update mode 12 | ENABLE_SELF_UPDATE=0 13 | 14 | # Setting SIGNED_BOOT=1 causes the bootloader to required signed boot.img files 15 | # which can be used to test that boot.img files are signed correctly. 16 | # 17 | # This setting does NOT enable secure-boot and can be switched off. 18 | # 19 | # If secure-boot is enabled via program_pubkey=1 then SIGNED_BOOT=1 is implicitly set 20 | # and cannot be unset. 21 | SIGNED_BOOT=1 22 | 23 | -------------------------------------------------------------------------------- /secure-boot-recovery/bootcode4.bin: -------------------------------------------------------------------------------- 1 | ../firmware/2711/recovery.bin -------------------------------------------------------------------------------- /secure-boot-recovery/config.txt: -------------------------------------------------------------------------------- 1 | uart_2ndstage=1 2 | 3 | # Uncomment to mark the EEPROM as write protected when the EEPROM /WIP pin is pulled low. 4 | # See https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#eeprom-write-protect 5 | #eeprom_write_protect=1 6 | 7 | # Uncomment to write to enable secure-boot by writing. This 8 | # locks the device to the public key in the EEPROM by storing the 9 | # sha256 hash of the public key in OTP. 10 | # 11 | # This option also prevents the ROM from loading recovery.bin from SD/EMMC 12 | # which means that the bootloader can only be updated via RPIBOOT or self-update. 13 | # 14 | # recovery.bin version to 2025-05-16 and newer automatically sets 15 | # revoke_devkey=1 if program_pubkey is non-zero. 16 | # 17 | # Uncomment program_pubkey=1 to enable this 18 | # WARNING: THIS OPTION MODIFIES THE BCM2711 CHIP AND IS IRREVERSIBLE. 19 | #program_pubkey=1 20 | 21 | # Pi 4B and Pi400 do not have a dedicated RPIBOOT jumper so a different GPIO 22 | # must be used to enable RPIBOOT if pulled low. The options are 2,4,5,6,7,8. 23 | # 24 | # This option has no effect on CM4. 25 | 26 | # WARNING: THIS OPTION MODIFIES THE BCM2711 CHIP AND IS IRREVERSIBLE. 27 | #program_rpiboot_gpio=8 28 | 29 | # Permanently disable VideoCore JTAG access. 30 | # Warning: This option limits the ability to do failure analysis on 31 | # boards returned to resellers or Raspberry Pi Trading Ltd. 32 | #program_jtag_lock=1 33 | 34 | # rpiboot - only 35 | # If recovery_reboot is set then the Pi will disconnect USB and 36 | # reboot after the flashing the firmware. 37 | #recovery_reboot=1 38 | 39 | # Uncomment to instruct recovery.bin to send metadata including OTP fields 40 | # Specify -j dirname on the command line to specify the directory where 41 | # metadata should be stored (JSON format) 42 | recovery_metadata=1 43 | -------------------------------------------------------------------------------- /secure-boot-recovery/pieeprom.original.bin: -------------------------------------------------------------------------------- 1 | ../firmware/2711/pieeprom.bin -------------------------------------------------------------------------------- /secure-boot-recovery5/.gitignore: -------------------------------------------------------------------------------- 1 | pieeprom.bin 2 | pieeprom.sig 3 | bootcode5.bin 4 | *signed_boot* 5 | metadata/ 6 | -------------------------------------------------------------------------------- /secure-boot-recovery5/README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi 5 - secure boot 2 | 3 | This directory contains the beta bootcode5.bin (recovery.bin) and a pre-release pieeprom.bin 4 | bootloader release. Older bootloader and recovery.bin releases do not support secure boot. 5 | 6 | # Required packages 7 | ```bash 8 | sudo apt install xxd python3-pycryptodome 9 | ``` 10 | 11 | ## Optional. Specify the private key file in an environment variable. 12 | Alternatively, specify the path when invoking the helper scripts. 13 | ```bash 14 | export KEY_FILE="${HOME}/private.pem" 15 | ``` 16 | 17 | ## Optional. Customise the EEPROM config. 18 | Customise with the desired bootloader settings. 19 | See: [Bootloader configuration](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-bootloader-configuration) 20 | 21 | ## Sign the EEPROM and the second stage bootloader 22 | The BCM2712 boot ROM requires the second-stage firmware (recovery.bin / bootcode5.bin) to be counter-signed with the customer private key in secure-boot mode. 23 | Pass the `f` flag to enable firmware counter-signing. 24 | 25 | ### Using a private key file 26 | ``` 27 | ../tools/update-pieeprom.sh -f -k "${KEY_FILE}" 28 | ``` 29 | 30 | If secure-boot has already been enabled on the device, then `recovery.bin` must also be counter-signed. 31 | However, booting a counter-signed `recovery.bin` image on a fresh board without secure-boot enabled will fail 32 | because the ROM is effectively checking this against a key hash of zero. 33 | ``` 34 | ../tools/update-pieeprom.sh -fr -k "${KEY_FILE}" 35 | ``` 36 | 37 | ### Using an HSM wrapper 38 | When using a Hardware Security Module (HSM), you need to provide both the HSM wrapper script and the corresponding public key: 39 | 40 | ``` 41 | ../tools/update-pieeprom.sh -f -H hsm-wrapper -p public.pem 42 | ``` 43 | 44 | For recovery.bin signing with HSM: 45 | ``` 46 | ../tools/update-pieeprom.sh -fr -H hsm-wrapper -p public.pem 47 | ``` 48 | 49 | The HSM wrapper script should: 50 | - Take a single argument which is a temporary filename containing the data to sign 51 | - Output the PKCS#1 v1.5 signature in hex format 52 | - Return a non-zero exit code if signing fails 53 | 54 | `pieeprom.bin` can then be flashed to the bootloader EEPROM via `rpiboot`. 55 | 56 | ## Programming the EEPROM image using rpiboot 57 | * Power off the device 58 | * Set nRPIBOOT jumper (or hold power button before power on) and remove EEPROM WP protection 59 | ```bash 60 | cd secure-boot-recovery5 61 | mkdir -p metadata 62 | ../rpiboot -d . -j metadata 63 | ``` 64 | 65 | ### Example UART output 66 | ``` 67 | 3.04 OTP boardrev b04170 bootrom a a 68 | 3.06 Customer key hash 8251a63a2edee9d8f710d63e9da5d639064929ce15a2238986a189ac6fcd3cee 69 | 3.13 VC-JTAG unlocked 70 | 3.36 RP1_BOOT chip ID: 0x20001927 71 | 3.41 bootconf.sig 72 | 3.41 hash: f71ede8fad8bea2f853bcff41173ffedde48c5b76ed46bc38fa057ce46e5d58b 73 | 3.47 rsa2048: 3f215305d5aff620219da94f6f1294787e3a407102a507da96c28e9195d3ccb2f144cac66919f9d86ba9f54a8d20ff57c80d6d269e6e49a16dc23553974489947fe05bf3b7df5cd2c5040a9eebadca754ff4be50600b06fd9f565639adc859d88052e15e0ff6eecf7fec0386d41f81e5d009b04520bb83f17663b62b1271b9d27ec2344c73a20d42dfd68facd741d48c0453e8149448537abfed1d4805872c16182a3e9f25c0b86e002e88949d62c148a561aa8137c257ce0d3e0ae5761aa64c225e9c9782b2bb613de7d90499567c56218bb18a239d4347967b68b3ebd06eaa48215f16316d2a697bb2e67cb3883068f6284e2ca71d25ce0099a1ceb37a85c9 74 | 3.94 RSA verify 75 | 3.10 rsa-verify pass (0x0) 76 | 77 | ``` 78 | 79 | ### Metadata 80 | The optional metadata argument causes rpiboot to readback the OTP information and write it to a JSON file in the given directory. 81 | This can be useful for debug or for storing in a provisioning database. 82 | 83 | Example metadata: 84 | ```json 85 | { 86 | "USER_SERIAL_NUM" : "a7eb274c", 87 | "MAC_ADDR" : "2c:cf:67:70:76:f3", 88 | "CUSTOMER_KEY_HASH" : "8251a63a2edee9d8f710d63e9da5d639064929ce15a2238986a189ac6fcd3cee", 89 | "BOOT_ROM" : "0000000a", 90 | "BOARD_ATTR" : "00000000", 91 | "USER_BOARDREV" : "b04170", 92 | "JTAG_LOCKED" : "0", 93 | "MAC_WIFI_ADDR" : "2c:cf:67:70:76:f4", 94 | "MAC_BT_ADDR" : "2c:cf:67:70:76:f5", 95 | "FACTORY_UUID" : "001000911006186073" 96 | } 97 | ``` 98 | 99 | * Power ON the DUT 100 | 101 | ## Locking secure-boot mode 102 | After verifying that the signed OS image boots successfully the system 103 | can be locked into secure-boot mode. This writes the hash of the 104 | customer public key to "one time programmable" (OTP) bits. From then 105 | onwards: 106 | 107 | * The 2712 boot ROM verifies that the second-stage firmware (recovery.bin / bootsys) is 108 | signed with the customer private key in addition to the Raspberry Pi private key. 109 | * The bootloader will only load OS images signed with the customer private key. 110 | * The EEPROM configuration file must be signed with the customer private key. 111 | * It is not possible to downgrade to an old version of the bootloader that doesn't 112 | support secure boot. 113 | 114 | 115 | To enable this, edit the `config.txt` file in this directory and set `program_pubkey=1` 116 | 117 | ## Disabling VideoCore JTAG 118 | 119 | VideoCore JTAG may be permanently disabled by setting `program_jtag_lock` in 120 | `config.txt`. This option has no effect unless the public key hash (`program_pubkey`) has been successfully programmed. 121 | 122 | See [config.txt](config.txt) 123 | -------------------------------------------------------------------------------- /secure-boot-recovery5/boot.conf: -------------------------------------------------------------------------------- 1 | [all] 2 | BOOT_UART=1 3 | POWER_OFF_ON_HALT=1 4 | 5 | # Boot Order Codes, from https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#BOOT_ORDER 6 | # Try SD first (1), followed by, USB (RP1), NVMe PCIe, then network 7 | BOOT_ORDER=0xf2461 8 | 9 | # Disable self-update mode - to prevent automatic updates of unsigned bootloader images 10 | ENABLE_SELF_UPDATE=0 11 | -------------------------------------------------------------------------------- /secure-boot-recovery5/config.txt: -------------------------------------------------------------------------------- 1 | uart_2ndstage=1 2 | 3 | # Mark the EEPROM as write protected when the EEPROM /WIP pin is pulled low. 4 | # See https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#eeprom-write-protect 5 | #eeprom_write_protect=1 6 | 7 | # Uncomment to write to enable secure-boot by writing. This 8 | # locks the device to the public key in the EEPROM by storing the 9 | # sha256 hash of the public key in OTP. 10 | # 11 | # This option also prevents the ROM from loading recovery.bin from SD/EMMC 12 | # which means that the bootloader can only be updated via RPIBOOT or self-update. 13 | # 14 | # Uncomment program_pubkey=1 to enable this 15 | # WARNING: THIS OPTION MODIFIES THE BCM2712 CHIP AND IS IRREVERSIBLE. 16 | # 17 | #program_pubkey=1 18 | 19 | # Permanently disable VideoCore JTAG access. 20 | # Warning: This option limits the ability to do failure analysis on 21 | # boards returned to resellers or Raspberry Pi. 22 | #program_jtag_lock=1 23 | 24 | # If recovery_reboot is set then the Pi will disconnect USB and 25 | # reboot after the flashing the firmware. 26 | #recovery_reboot=1 27 | 28 | # Uncomment to instruct recovery.bin to send metadata including OTP fields 29 | # Specify -j dirname on the command line to specify the directory where 30 | # metadata should be stored (JSON format) 31 | recovery_metadata=1 32 | -------------------------------------------------------------------------------- /secure-boot-recovery5/pieeprom.original.bin: -------------------------------------------------------------------------------- 1 | ../recovery5/pieeprom.original.bin -------------------------------------------------------------------------------- /secure-boot-recovery5/recovery.original.bin: -------------------------------------------------------------------------------- 1 | ../firmware/2712/recovery.bin -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi usbboot and code signing tests 2 | 3 | ## validate-hsm-wrapper 4 | Verifies that the HSM wrapper (example) script and the direct private key API produce identical results. 5 | -------------------------------------------------------------------------------- /test/validate-hsm-wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Script to validate that the example-hsm-wrapper produces bitwise identical 4 | # signatures to direct signing with the private key 5 | 6 | set -e 7 | set -u 8 | 9 | script_dir="$(cd "$(dirname "$0")" && pwd)" 10 | export PATH="${script_dir}/../tools:${PATH}" 11 | 12 | # Set SOURCE_DATE_EPOCH to current timestamp to ensure consistent timestamps 13 | export SOURCE_DATE_EPOCH=$(date +%s) 14 | echo "Using timestamp: ${SOURCE_DATE_EPOCH}" 15 | 16 | sign_firmware_blob() { 17 | echo "Signing firmware in ${1} using $(which rpi-sign-bootcode)" 18 | rpi-sign-bootcode \ 19 | -c 2712 \ 20 | -i "${1}" \ 21 | -o "${2}" \ 22 | -n 16 \ 23 | -v 0 \ 24 | ${SIGN_ARGS} ${PUBLIC_KEY} 25 | } 26 | 27 | cleanup() { 28 | if [ -n "${TMP_DIR:-}" ] && [ -d "${TMP_DIR}" ]; then 29 | echo "Cleaning up temporary directory: ${TMP_DIR}" 30 | rm -rf "${TMP_DIR}" 31 | fi 32 | } 33 | 34 | # Get the script directory 35 | SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) 36 | USBBOOT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd) 37 | HSM_WRAPPER="${USBBOOT_DIR}/secure-boot-example/example-hsm-wrapper" 38 | PRIVATE_KEY="${USBBOOT_DIR}/secure-boot-example/example-private.pem" 39 | PUBLIC_KEY="-p ${USBBOOT_DIR}/secure-boot-example/example-public.pem" 40 | FIRMWARE_DIR="${USBBOOT_DIR}/firmware" 41 | RECOVERY_DIR="${USBBOOT_DIR}/secure-boot-recovery5" 42 | 43 | # Check if bootfiles.bin exists 44 | if [ ! -f "${FIRMWARE_DIR}/bootfiles.bin" ]; then 45 | echo "ERROR: bootfiles.bin not found in ${FIRMWARE_DIR}" 46 | echo "Please make sure the firmware directory contains bootfiles.bin" 47 | exit 1 48 | fi 49 | 50 | # Check if pieeprom.original.bin exists 51 | if [ ! -e "${RECOVERY_DIR}/pieeprom.original.bin" ]; then 52 | echo "ERROR: pieeprom.original.bin not found in ${RECOVERY_DIR}" 53 | echo "Please make sure the secure-boot-recovery5 directory contains pieeprom.original.bin" 54 | exit 1 55 | fi 56 | 57 | # Create a temporary directory for our test files 58 | TMP_DIR=$(mktemp -d) 59 | trap cleanup EXIT 60 | 61 | test_sign_bootcode() { 62 | echo "" 63 | echo "======================================================================" 64 | echo "TEST: BOOTCODE SIGNING COMPARISON" 65 | echo "======================================================================" 66 | echo "" 67 | 68 | # Copy bootfiles.bin to our test directory 69 | BOOTFILES="${TMP_DIR}/bootfiles.bin" 70 | cp "${FIRMWARE_DIR}/bootfiles.bin" "${BOOTFILES}" 71 | 72 | # Extract bootcode5.bin from bootfiles.bin using tar 73 | echo "Extracting 2712/bootcode5.bin from bootfiles.bin" 74 | cd "${TMP_DIR}" 75 | tar -xf "${BOOTFILES}" 2712/bootcode5.bin 76 | BOOTCODE="${TMP_DIR}/2712/bootcode5.bin" 77 | 78 | if [ ! -f "${BOOTCODE}" ]; then 79 | echo "ERROR: Failed to extract 2712/bootcode5.bin from bootfiles.bin" 80 | exit 1 81 | fi 82 | 83 | # Sign using private key mode 84 | echo "" 85 | echo "----------------------------------------------------------------------" 86 | echo "PRIVATE KEY SIGNING MODE" 87 | echo "----------------------------------------------------------------------" 88 | echo "" 89 | SIGN_ARGS="-k ${PRIVATE_KEY}" 90 | PRIVATE_SIGNED="${TMP_DIR}/bootcode5.private.signed" 91 | sign_firmware_blob "${BOOTCODE}" "${PRIVATE_SIGNED}" 92 | 93 | # Sign using HSM wrapper mode 94 | echo "" 95 | echo "----------------------------------------------------------------------" 96 | echo "HSM WRAPPER SIGNING MODE" 97 | echo "----------------------------------------------------------------------" 98 | echo "" 99 | SIGN_ARGS="-H ${HSM_WRAPPER}" 100 | HSM_SIGNED="${TMP_DIR}/bootcode5.hsm.signed" 101 | sign_firmware_blob "${BOOTCODE}" "${HSM_SIGNED}" 102 | 103 | # Compare the signed files 104 | echo "" 105 | echo "----------------------------------------------------------------------" 106 | echo "COMPARING SIGNED FILES" 107 | echo "----------------------------------------------------------------------" 108 | echo "" 109 | if cmp -s "${PRIVATE_SIGNED}" "${HSM_SIGNED}"; then 110 | echo "SUCCESS: HSM wrapper and private key signing produce identical output" 111 | return 0 112 | else 113 | echo "ERROR: HSM wrapper and private key signing produce different output" 114 | echo "Files differ. Check the following files for details:" 115 | echo " Private key signed: ${PRIVATE_SIGNED}" 116 | echo " HSM wrapper signed: ${HSM_SIGNED}" 117 | return 1 118 | fi 119 | } 120 | 121 | test_sign_eeprom() { 122 | echo "" 123 | echo "======================================================================" 124 | echo "TEST: EEPROM SIGNING COMPARISON" 125 | echo "======================================================================" 126 | echo "" 127 | 128 | # Create a test directory for EEPROM signing 129 | EEPROM_TEST_DIR="${TMP_DIR}/eeprom_test" 130 | mkdir -p "${EEPROM_TEST_DIR}" 131 | 132 | # Copy pieeprom.original.bin to our test directory 133 | cp "${RECOVERY_DIR}/pieeprom.original.bin" "${EEPROM_TEST_DIR}/" 134 | cp "${RECOVERY_DIR}/recovery.original.bin" "${EEPROM_TEST_DIR}/" 135 | 136 | # Create a minimal boot.conf file 137 | cat > "${EEPROM_TEST_DIR}/boot.conf" << EOF 138 | [all] 139 | BOOT_UART=1 140 | POWER_OFF_ON_HALT=1 141 | EOF 142 | 143 | # Sign using private key mode 144 | echo "" 145 | echo "----------------------------------------------------------------------" 146 | echo "PRIVATE KEY SIGNING MODE" 147 | echo "----------------------------------------------------------------------" 148 | echo "" 149 | cd "${EEPROM_TEST_DIR}" 150 | update-pieeprom.sh -c boot.conf -k "${PRIVATE_KEY}" -f -o pieeprom.private.bin 151 | PRIVATE_SIGNED="${EEPROM_TEST_DIR}/pieeprom.private.bin" 152 | 153 | # Sleep to verify that SOURCE_DATE_EPOCH is used 154 | sleep 1 155 | 156 | # Sign using HSM wrapper mode 157 | echo "" 158 | echo "----------------------------------------------------------------------" 159 | echo "HSM WRAPPER SIGNING MODE" 160 | echo "----------------------------------------------------------------------" 161 | echo "" 162 | cd "${EEPROM_TEST_DIR}" 163 | update-pieeprom.sh -c boot.conf -H "${HSM_WRAPPER}" -p "${USBBOOT_DIR}/secure-boot-example/example-public.pem" -f -o pieeprom.hsm.bin 164 | HSM_SIGNED="${EEPROM_TEST_DIR}/pieeprom.hsm.bin" 165 | 166 | # Compare the signed files 167 | echo "" 168 | echo "----------------------------------------------------------------------" 169 | echo "COMPARING SIGNED FILES" 170 | echo "----------------------------------------------------------------------" 171 | echo "" 172 | if cmp -s "${PRIVATE_SIGNED}" "${HSM_SIGNED}"; then 173 | echo "SUCCESS: HSM wrapper and private key signing produce identical EEPROM output" 174 | return 0 175 | else 176 | echo "ERROR: HSM wrapper and private key signing produce different EEPROM output" 177 | echo "Files differ. Check the following files for details:" 178 | echo " Private key signed: ${PRIVATE_SIGNED}" 179 | echo " HSM wrapper signed: ${HSM_SIGNED}" 180 | return 1 181 | fi 182 | } 183 | 184 | # Run the tests 185 | test_sign_bootcode 186 | test_sign_eeprom -------------------------------------------------------------------------------- /tools/make-boot-image: -------------------------------------------------------------------------------- 1 | rpi-make-boot-image -------------------------------------------------------------------------------- /tools/rpi-bootloader-key-convert: -------------------------------------------------------------------------------- 1 | ../rpi-eeprom/tools/rpi-bootloader-key-convert -------------------------------------------------------------------------------- /tools/rpi-create-tags: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Create git tags that match the APT releases 4 | 5 | git blame debian/changelog | egrep "rpiboot .*urgency=" | sed 's/[()]//g' | sed 's/~/-/g' | awk '{print "git tag " $9 " " $1}' 6 | -------------------------------------------------------------------------------- /tools/rpi-eeprom-config: -------------------------------------------------------------------------------- 1 | ../rpi-eeprom/rpi-eeprom-config -------------------------------------------------------------------------------- /tools/rpi-eeprom-digest: -------------------------------------------------------------------------------- 1 | ../rpi-eeprom/rpi-eeprom-digest -------------------------------------------------------------------------------- /tools/rpi-make-boot-image: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | TMP_DIR="" 6 | TMP_IMAGE="" 7 | IMAGE_SIZE=${IMAGE_SIZE:-0} 8 | if [ "$IMAGE_SIZE" -ne 0 ]; then 9 | IMAGE_SIZE="$((IMAGE_SIZE * 1024))" 10 | fi 11 | BOOT_MOUNT="" 12 | LOOP="" 13 | NAME="$(basename "$0")" 14 | 15 | # Define these environment variables to override mkfs options 16 | SECTOR_SIZE=${SECTOR_SIZE:-512} 17 | SECTORS_PER_CLUSTER=${SECTORS_PER_CLUSTER:-1} 18 | DISK_GEOMETRY=${DISK_GEOMETRY:-1/1} 19 | ROOT_DIR_ENTRIES=${ROOT_DIR_ENTRIES:-256} 20 | 21 | # Add 16k to the size calculation to reserve some space for the FAT, 22 | # directory entries and rounding up files to cluster sizes. 23 | FAT_OVERHEAD=${FAT_OVERHEAD:-16} 24 | 25 | HAVE_MCOPY=false 26 | 27 | cleanup() { 28 | unmount_image 29 | 30 | if [ -d "${TMP_DIR}" ]; then 31 | rm -rf "${TMP_DIR}" 32 | fi 33 | } 34 | 35 | die() { 36 | echo "$@" >&2 37 | exit 1 38 | } 39 | 40 | createfs() { 41 | size_kib="$1" 42 | image="$2" 43 | 44 | volume_label="BOOT" 45 | if [ -n "${SECTORS_PER_CLUSTER}" ]; then 46 | SECTORS_PER_CLUSTER="-s ${SECTORS_PER_CLUSTER}" 47 | fi 48 | 49 | if [ -n "${DISK_GEOMETRY}" ]; then 50 | DISK_GEOMETRY="-g ${DISK_GEOMETRY}" 51 | fi 52 | 53 | if [ -n "${FAT_SIZE}" ]; then 54 | fat_size="-F ${FAT_SIZE}" 55 | fi 56 | 57 | mkfs.fat -C -f 1 \ 58 | ${SECTORS_PER_CLUSTER} -n "${volume_label}" \ 59 | ${fat_size} ${DISK_GEOMETRY} \ 60 | -S "${SECTOR_SIZE}" -r "${ROOT_DIR_ENTRIES}" "${image}" ${size_kib} || \ 61 | die "Failed to create FAT filesystem" 62 | } 63 | 64 | mountfs() { 65 | image="$1" 66 | 67 | LOOP=$(losetup -f) 68 | losetup "${LOOP}" "${image}" 69 | [ -e "${LOOP}" ] || die "Failed to create loop device ${LOOP}" 70 | 71 | BOOT_MOUNT=$(mktemp -d) 72 | mount "${LOOP}" "${BOOT_MOUNT}" 73 | [ -d "${BOOT_MOUNT}" ] || die "Failed to mount bootfs @ ${BOOT_MOUNT}" 74 | 75 | echo "Mounted ${LOOP} @ ${BOOT_MOUNT}" 76 | } 77 | 78 | unmount_image() { 79 | if [ -d "${BOOT_MOUNT}" ]; then 80 | umount "${BOOT_MOUNT}" > /dev/null 2>&1 || true 81 | rmdir "${BOOT_MOUNT}" 82 | BOOT_MOUNT="" 83 | fi 84 | 85 | if [ -n "${LOOP}" ]; then 86 | losetup -d "${LOOP}" 87 | LOOP="" 88 | fi 89 | } 90 | 91 | copyfiles() { 92 | image="$1" 93 | shift 94 | if ${HAVE_MCOPY} ; then 95 | mcopy -i "${image}" -vsmpQ "$@" ::/ 96 | else 97 | mountfs "${image}" 98 | cp -rpv "$@" "${BOOT_MOUNT}" 99 | sync 100 | 101 | echo "Sync" 102 | sync 103 | 104 | echo "Unmount" 105 | unmount_image 106 | fi 107 | } 108 | 109 | estimate_fat_clusters() { 110 | local cluster_size 111 | cluster_size="$1" 112 | local directory 113 | directory="$2" 114 | 115 | local clusters 116 | clusters="0" 117 | local subdir 118 | 119 | for subdir in $(find "$directory" -mindepth 1 -maxdepth 1 -type d); do 120 | local sd_clusters 121 | sd_clusters="$(estimate_fat_clusters "$cluster_size" "$subdir")" 122 | clusters="$((clusters + sd_clusters))" 123 | done 124 | 125 | # Determine number of clusters required for file contents 126 | local file_clusters 127 | file_clusters="$(find "$directory" -maxdepth 1 -type f -exec du --apparent-size --block-size="${cluster_size}" {} + | awk '{clust=clust+$1} END {print clust}')" 128 | clusters="$((clusters + file_clusters))" 129 | 130 | # Determine number of clusters required for directory entries 131 | # Two additional entries are required for "." and ".." 132 | local dir_entries 133 | dir_entries="2" 134 | local file_name 135 | local file_base_name 136 | 137 | for file_name in "${directory}"/*; do 138 | file_base_name="$(basename "${file_name}")" 139 | 140 | # Always at least one entry 141 | dir_entries="$((dir_entries + 1))" 142 | 143 | # MSDOS 8.3 Filename Requirements 144 | # A-Z 145 | # 0-9 146 | # Space 147 | # ! # $ % & ' ( ) - @ ^ _ ` { } ~ 148 | # Values 128-255 149 | VALID_MSDOS_FILENAME_CHAR='[A-Z0-9 \!#\$%\&'"'"'\(\)\-@\^_`\{\}~\x80-\xff]' 150 | VALID_MSDOS_FILENAME="^${VALID_MSDOS_FILENAME_CHAR}{1,8}(?:\.${VALID_MSDOS_FILENAME_CHAR}{1,3})$" 151 | 152 | local msdos_compatible_filename 153 | msdos_compatible_filename="0" 154 | printf "%s" "${file_base_name}" | \ 155 | grep --silent --perl-regexp "${VALID_MSDOS_FILENAME}" || msdos_compatible_filename=$? 156 | 157 | if [ "$msdos_compatible_filename" -ne "0" ]; then 158 | local ucs2_bytes 159 | ucs2_bytes="$(printf "%s" "${file_name}" | iconv --to-code=UCS2 | wc --bytes)" 160 | dir_entries="$((dir_entries + ((ucs2_bytes + 25)/26)))" 161 | fi 162 | done 163 | 164 | # 32-bytes are required for each entry 165 | clusters="$((clusters + ((dir_entries * 32) + cluster_size - 1)/cluster_size))" 166 | echo "$clusters" 167 | } 168 | 169 | createstaging() { 170 | source_dir="$1" 171 | staging="$2" 172 | board="$3" 173 | 174 | mkdir -p "${staging}" || die "Failed to create ${staging}" 175 | cp -rp "${source_dir}/"* "${staging}" 176 | 177 | # Remove files for previous hardware version 178 | if [ "${board}" = "pi4" ] || [ "${board}" = "pi400" ] || [ "${board}" = "cm4" ]; then 179 | ( 180 | cd "${staging}" 181 | rm -f kernel.img kernel7.img bootcode.bin 182 | rm -f start.elf fixup.dat start_cd.elf fixup_cd.dat start_db.elf fixup_db.dat start_x.elf fixup_x.dat 183 | rm -f start4cd.elf fixup4cd.dat 184 | rm -f start4db.elf fixup4db.dat 185 | rm -f start4x.elf fixup4x.dat 186 | rm -f bcm2708* bcm2709* bcm2710* 187 | rm -f kernel_2712.img initramfs_2712 188 | rm -f bootcode.bin 189 | ) 190 | fi 191 | 192 | if [ "${ARCH}" = 32 ]; then 193 | rm -f "${staging}/kernel8.img" 194 | elif [ "${ARCH}" = 64 ]; then 195 | rm -f "${staging}/kernel7l.img" 196 | fi 197 | 198 | if [ "${board}" = pi400 ]; then 199 | rm -f "${staging}/start4x.elf" 200 | rm -f "${staging}/fixup4x.dat" 201 | fi 202 | 203 | if [ "${IMAGE_SIZE}" = 0 ]; then 204 | # Estimate the size of the image in clusters 205 | cluster_size="$((SECTOR_SIZE * SECTORS_PER_CLUSTER))" 206 | clusters="$(estimate_fat_clusters "${cluster_size}" "${staging}")" 207 | 208 | IMAGE_SIZE="$((clusters * cluster_size))" 209 | 210 | # FAT32/FAT16 determined by number of clusters 211 | if [ "$clusters" -gt "65526" ]; then 212 | FAT_SIZE="32" 213 | fat_table_sectors="$(((((clusters + 2)*4) + SECTOR_SIZE - 1)/SECTOR_SIZE))" 214 | else 215 | FAT_SIZE="16" 216 | fat_table_sectors="$(((((clusters + 2)*2) + SECTOR_SIZE - 1)/SECTOR_SIZE))" 217 | # Add some sectors based on ROOT_DIR_ENTRIES 218 | root_dir_sectors="$((((ROOT_DIR_ENTRIES * 32) + cluster_size - 1)/cluster_size))" 219 | fat_table_sectors="$((fat_table_sectors + root_dir_sectors))" 220 | fi 221 | IMAGE_SIZE="$((IMAGE_SIZE + fat_table_sectors * SECTOR_SIZE))" 222 | IMAGE_SIZE="$(((IMAGE_SIZE + 1023)/1024))" 223 | 224 | # Add a little padding for FAT etc 225 | IMAGE_SIZE=$((IMAGE_SIZE + FAT_OVERHEAD)) 226 | fi 227 | 228 | echo "Using IMAGE_SIZE of ${IMAGE_SIZE}" 229 | 230 | if [ "${IMAGE_SIZE}" -gt "$((20 * 1024))" ]; then 231 | echo "Warning: Large image size detected. Try removing unused files." 232 | fi 233 | } 234 | 235 | checkDependencies() { 236 | if ! mkfs.fat --help > /dev/null 2> /dev/null ; then 237 | die "mkfs.fat is required. Run this script on Linux" 238 | fi 239 | if mcopy --help > /dev/null 2> /dev/null ; then 240 | HAVE_MCOPY=true 241 | fi 242 | } 243 | 244 | usage() { 245 | cat <&2 30 | exit 1 31 | } 32 | 33 | cleanup() { 34 | if [ -f "${TMP_CONFIG_SIG}" ]; then rm -f "${TMP_CONFIG_SIG}"; fi 35 | if [ -d "${TMP_DIR}" ]; then rm -rf "${TMP_DIR}"; fi 36 | } 37 | 38 | usage() { 39 | cat < /dev/null; then 240 | die "HSM wrapper script \"${HSM_WRAPPER}\" not found" 241 | fi 242 | [ -n "${PUBLIC_PEM_FILE}" ] || die "Public key (-p public.pem) must specified in HSM mode" 243 | [ -f "${PUBLIC_PEM_FILE}" ] || die "Public key \"${PUBLIC_PEM_FILE}\" not found" 244 | fi 245 | 246 | # If a public key is specified then use it. Otherwise, if just the private 247 | # key is specified then let rpi-eeprom-config automatically extract the 248 | # public key from the private key PEM file. 249 | if [ -z "${PUBLIC_PEM_FILE}" ]; then 250 | PUBLIC_PEM_FILE="${PEM_FILE}" 251 | fi 252 | 253 | DST_IMAGE_SIG="$(echo "${DST_IMAGE}" | sed 's/\.[^./]*$//').sig" 254 | TMP_DIR="$(mktemp -d)" 255 | rm -f "${DST_IMAGE}" "${DST_IMAGE_SIG}" 256 | sign_firmware "${SRC_IMAGE}" 257 | update_eeprom "${SRC_IMAGE}" "${CONFIG}" "${DST_IMAGE}" "${PEM_FILE}" "${PUBLIC_PEM_FILE}" 258 | image_digest "${DST_IMAGE}" "${DST_IMAGE_SIG}" 259 | -------------------------------------------------------------------------------- /win32/LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/win32/LICENSE.txt -------------------------------------------------------------------------------- /win32/Raspberry_Pi_Logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/win32/Raspberry_Pi_Logo.ico -------------------------------------------------------------------------------- /win32/Readme.md: -------------------------------------------------------------------------------- 1 | Compute Module Boot Installer 2 | ============================= 3 | 4 | Installs the drivers and utilities for programming the EMMC on the Raspberry Pi Compute Module. 5 | 6 | For more information about its use, visit the [Raspberry Pi Website here.](https://www.raspberrypi.org/documentation/hardware/computemodule/cm-emmc-flashing.md) 7 | 8 | This installer uses Peter Batard's [libwdi](https://github.com/pbatard/libwdi) to bind the 2708's VID and PID to WinUSB. 9 | 10 | To build this installer for yourself, use NSIS v2.51. Other versions may also work. -------------------------------------------------------------------------------- /win32/cygusb-1.0.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/win32/cygusb-1.0.dll -------------------------------------------------------------------------------- /win32/cygwin1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/win32/cygwin1.dll -------------------------------------------------------------------------------- /win32/install_script.nsi: -------------------------------------------------------------------------------- 1 | !include "x64.nsh" 2 | 3 | ;-------------------------------- 4 | ;Include Modern UI 5 | 6 | !include "MUI2.nsh" 7 | 8 | ;-------------------------------- 9 | ;General 10 | 11 | ;Name and file 12 | Name "Raspberry Pi USB boot" 13 | OutFile "rpiboot_setup.exe" 14 | 15 | ;Default installation folder 16 | InstallDir "$PROGRAMFILES\Raspberry Pi" 17 | 18 | ;Get installation folder from registry if available 19 | InstallDirRegKey HKCU "Software\Raspberry Pi" "" 20 | 21 | ;Request application privileges for Windows Vista 22 | RequestExecutionLevel admin 23 | 24 | ;-------------------------------- 25 | 26 | ;Interface Settings 27 | 28 | ShowInstDetails show 29 | !define MUI_FINISHPAGE_NOAUTOCLOSE 30 | !define MUI_ABORTWARNING 31 | !define MUI_ICON "Raspberry_Pi_Logo.ico" 32 | !define MUI_UNICON "Raspberry_Pi_Logo.ico" 33 | 34 | ;-------------------------------- 35 | ;Pages 36 | 37 | !insertmacro MUI_PAGE_WELCOME 38 | !insertmacro MUI_PAGE_LICENSE "LICENSE.txt" 39 | !insertmacro MUI_PAGE_COMPONENTS 40 | !insertmacro MUI_PAGE_DIRECTORY 41 | !insertmacro MUI_PAGE_INSTFILES 42 | !insertmacro MUI_PAGE_FINISH 43 | 44 | !insertmacro MUI_UNPAGE_WELCOME 45 | !insertmacro MUI_UNPAGE_CONFIRM 46 | !insertmacro MUI_UNPAGE_INSTFILES 47 | !insertmacro MUI_UNPAGE_FINISH 48 | 49 | ;-------------------------------- 50 | ;Languages 51 | 52 | !insertmacro MUI_LANGUAGE "English" 53 | 54 | ;-------------------------------- 55 | ; Initialisation functions 56 | Function .onInit 57 | 58 | ReadRegStr $R0 HKCU "Software\Raspberry Pi" "" 59 | StrCmp $R0 "" done 60 | 61 | MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \ 62 | "'Raspberry Pi USB boot' is already installed. $\n$\nClick `OK` to remove the \ 63 | previous version or `Cancel` to cancel this upgrade." \ 64 | IDOK uninst 65 | Abort 66 | 67 | ;Run the uninstaller 68 | uninst: 69 | ClearErrors 70 | 71 | ; Remove the left over usb_driver directory 72 | RmDir /r /REBOOTOK $R0\usb_driver 73 | 74 | ExecWait '$R0\Uninstall.exe _?=$R0' 75 | 76 | IfErrors no_remove_uninstaller done 77 | ;You can either use Delete /REBOOTOK in the uninstaller or add some code 78 | ;here to remove the uninstaller. Use a registry key to check 79 | ;whether the user has chosen to uninstall. If you are using an uninstaller 80 | ;components page, make sure all sections are uninstalled. 81 | no_remove_uninstaller: 82 | 83 | done: 84 | 85 | RmDir /r /REBOOTOK $R0 86 | 87 | FunctionEnd 88 | 89 | ;-------------------------------- 90 | ;Installer Sections 91 | 92 | Section "Raspberry Pi USB Boot" Sec_rpiboot 93 | 94 | SetOutPath "$INSTDIR" 95 | File /r redist 96 | 97 | SetOutPath "$INSTDIR\msd" 98 | File /r /x bootcode4.bin ..\msd\*.* 99 | File ..\firmware\2711\bootcode4.bin 100 | 101 | SetOutPath "$INSTDIR\mass-storage-gadget64" 102 | File /r /x bootfiles.bin ..\mass-storage-gadget64\*.* 103 | File ..\firmware\bootfiles.bin 104 | 105 | SetOutPath "$INSTDIR" 106 | DetailPrint "Installing BCM2708 driver..." 107 | ExecWait '"$INSTDIR\redist\wdi-simple.exe" -n "Raspberry Pi USB boot" -v 0x0a5c -p 0x2763 -t 0' $0 108 | DetailPrint "Driver install returned $0" 109 | 110 | DetailPrint "Installing BCM2710 driver..." 111 | ExecWait '"$INSTDIR\redist\wdi-simple.exe" -n "Raspberry Pi USB boot" -v 0x0a5c -p 0x2764 -t 0' $0 112 | DetailPrint "Driver install returned $0" 113 | 114 | DetailPrint "Installing BCM2711 driver..." 115 | ExecWait '"$INSTDIR\redist\wdi-simple.exe" -n "Raspberry Pi USB boot" -v 0x0a5c -p 0x2711 -t 0' $0 116 | DetailPrint "Driver install returned $0" 117 | 118 | DetailPrint "Installing BCM2712 driver..." 119 | ExecWait '"$INSTDIR\redist\wdi-simple.exe" -n "Raspberry Pi USB boot" -v 0x0a5c -p 0x2712 -t 0' $0 120 | DetailPrint "Driver install returned $0" 121 | 122 | File cygusb-1.0.dll 123 | File cygwin1.dll 124 | File ..\rpiboot.exe 125 | File rpi-mass-storage-gadget64.bat 126 | 127 | CreateDirectory "$SMPROGRAMS\Raspberry Pi" 128 | CreateShortcut "$SMPROGRAMS\Raspberry Pi\rpiboot-CM-CM2-CM3.lnk" "$INSTDIR\rpiboot.exe" 129 | CreateShortcut "$SMPROGRAMS\Raspberry Pi\rpiboot-CM4-CM5 - Mass Storage Gadget.lnk" "$INSTDIR\rpi-mass-storage-gadget64.bat" 130 | CreateShortcut "$SMPROGRAMS\Raspberry Pi\Uninstall rpiboot.lnk" "$INSTDIR\Uninstall.exe" 131 | 132 | ;Store installation folder 133 | WriteRegStr HKCU "Software\Raspberry Pi" "" $INSTDIR 134 | 135 | ;Create uninstaller 136 | WriteUninstaller "$INSTDIR\Uninstall.exe" 137 | 138 | SectionEnd 139 | 140 | ;-------------------------------- 141 | ;Descriptions 142 | 143 | ;Language strings 144 | LangString DESC_SecDummy ${LANG_ENGLISH} "Install drivers for flashing Compute Module." 145 | 146 | ;Assign language strings to sections 147 | !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN 148 | !insertmacro MUI_DESCRIPTION_TEXT ${Sec_rpiboot} $(DESC_SecDummy) 149 | !insertmacro MUI_FUNCTION_DESCRIPTION_END 150 | 151 | ;-------------------------------- 152 | ;Uninstaller Section 153 | 154 | Section "Uninstall" 155 | 156 | RmDir /r /REBOOTOK $INSTDIR\redist 157 | RmDir /r /REBOOTOK $INSTDIR\mass-storage-gadget64 158 | RmDir /r /REBOOTOK $INSTDIR\msd 159 | RmDir /r /REBOOTOK $INSTDIR\usb_driver 160 | 161 | Delete $INSTDIR\Uninstall.exe 162 | Delete $INSTDIR\cygusb-1.0.dll 163 | Delete $INSTDIR\cygwin1.dll 164 | Delete $INSTDIR\rpiboot.exe 165 | 166 | RmDir /REBOOTOK $INSTDIR 167 | 168 | RmDir /r "$SMPROGRAMS\Raspberry Pi" 169 | 170 | DeleteRegKey /ifempty HKCU "Software\Raspberry Pi" 171 | 172 | SectionEnd 173 | -------------------------------------------------------------------------------- /win32/redist/wdi-simple.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/win32/redist/wdi-simple.exe -------------------------------------------------------------------------------- /win32/rpi-mass-storage-gadget64.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | @echo USB mass storage gadget for Raspberry Pi 5 3 | rpiboot -d mass-storage-gadget64 4 | @echo: 5 | @echo Raspberry Pi Mass Storage Gadget started 6 | @echo EMMC/NVMe devices should be visible in the Raspberry Pi Imager in a few seconds. 7 | @echo For debug, you can login to the device using the USB serial gadget - see COM ports in Device Manager. 8 | @echo: 9 | set /p dummy=Press a key to close this window. -------------------------------------------------------------------------------- /win32/rpiboot_setup.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypi/usbboot/efc5da6085f4d062a4aa4d30a6aaa08e81febe85/win32/rpiboot_setup.exe --------------------------------------------------------------------------------