├── .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
--------------------------------------------------------------------------------