├── .gitignore
├── LICENSE
├── Makefile
├── Readme.md
├── bin2c.c
├── bootcode4.bin
├── debian
├── 70-rpiboot.rules
├── changelog
├── compat
├── control
├── copyright
├── rpiboot.install
├── rpiboot.lintian-overrides
├── rules
├── source.lintian-overrides
└── source
│ └── format
├── eeprom-erase
├── README.md
├── bootcode4.bin
└── config.txt
├── fmemopen.c
├── main.c
├── mass-storage-gadget
├── .gitignore
├── README.md
├── boot.img
├── bootcode4.bin
└── config.txt
├── msd
├── .gitignore
├── README.md
├── bootcode.bin
├── bootcode4.bin
├── start.elf
└── start4.elf
├── recovery.bin
├── recovery
├── README.md
├── boot.conf
├── bootcode4.bin
├── config.txt
├── pieeprom.bin
├── pieeprom.original.bin
├── pieeprom.sig
├── rpi-eeprom-config
└── update-pieeprom.sh
├── rpi-imager-embedded
├── README.md
├── boot.img
├── bootcode4.bin
└── config.txt
├── secure-boot-example
├── README.md
├── boot.img
├── boot.sig
├── bootcode4.bin
├── config.txt
├── example-private.pem
└── example-public.pem
├── 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
├── tools
├── make-boot-image
├── rpi-bootloader-key-convert
├── rpi-eeprom-config
├── rpi-eeprom-digest
├── rpi-otp-private-key
└── update-pieeprom.sh
└── win32
├── LICENSE.txt
├── Raspberry_Pi_Logo.ico
├── Readme.md
├── cyggcc_s-1.dll
├── cygusb-1.0.dll
├── cygwin1.dll
├── install_script.nsi
├── redist
└── wdi-simple.exe
├── rpi-mass-storage-gadget.bat
└── rpiboot_setup.exe
/.gitignore:
--------------------------------------------------------------------------------
1 | rpiboot
2 | bin2c
3 | *.exe
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 | PKG_VER=$(shell grep rpiboot debian/changelog | head -n1 | sed 's/.*(\(.*\)).*/\1/g')
2 | GIT_VER=$(shell git rev-parse HEAD 2>/dev/null | cut -c1-8 || echo "")
3 | rpiboot: main.c msd/bootcode.h msd/start.h msd/bootcode4.h msd/start4.h
4 | $(CC) -Wall -Wextra -g -o $@ $< `pkg-config --cflags --libs libusb-1.0` -DGIT_VER="\"$(GIT_VER)\"" -DPKG_VER="\"$(PKG_VER)\""
5 |
6 | %.h: %.bin ./bin2c
7 | ./bin2c $< $@
8 |
9 | %.h: %.elf ./bin2c
10 | ./bin2c $< $@
11 |
12 | bin2c: bin2c.c
13 | $(CC) -Wall -Wextra -g -o $@ $<
14 |
15 | install: rpiboot
16 | install -m 755 rpiboot /usr/bin/
17 | install -d /usr/share/rpiboot
18 | install -m 644 msd/bootcode.bin /usr/share/rpiboot/
19 | install -m 644 msd/bootcode4.bin /usr/share/rpiboot/
20 | install -m 644 msd/start.elf /usr/share/rpiboot/
21 | install -m 644 msd/start4.elf /usr/share/rpiboot/
22 |
23 | uninstall:
24 | rm -f /usr/bin/rpiboot
25 | rm -f /usr/share/rpiboot/bootcode.bin
26 | rm -f /usr/share/rpiboot/bootcode4.bin
27 | rm -f /usr/share/rpiboot/start.elf
28 | rm -f /usr/share/rpiboot/start4.elf
29 | rmdir --ignore-fail-on-non-empty /usr/share/rpiboot/
30 |
31 | clean:
32 | rm -f rpiboot msd/*.h bin2c
33 |
34 | .PHONY: uninstall clean
35 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # USB Device Boot Code
2 |
3 | This is the USB MSD boot code which supports the Raspberry Pi 1A, 3A+, Compute Module, Compute
4 | Module 3, 3+ and 4, 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 | For more information run `rpiboot -h`.
19 |
20 | ## Building
21 |
22 | ### Linux / Cygwin / WSL
23 | Clone this repository on your Pi or other Linux machine.
24 | Make sure that the system date is set correctly, otherwise Git may produce an error.
25 |
26 | **This git repository uses symlinks. For Windows builds clone the repository under Cygwin**
27 |
28 | ```
29 | sudo apt install git libusb-1.0-0-dev pkg-config
30 | git clone --depth=1 https://github.com/raspberrypi/usbboot
31 | cd usbboot
32 | make
33 | sudo ./rpiboot
34 | ```
35 |
36 | `sudo` isn't required if you have write permissions for the `/dev/bus/usb` device.
37 |
38 | ### macOS
39 | From a macOS machine, you can also run usbboot, just follow the same steps:
40 |
41 | 1. Clone the `usbboot` repository
42 | 2. Install `libusb` (`brew install libusb`)
43 | 3. Install `pkg-config` (`brew install pkg-config`)
44 | 4. Build using make
45 | 5. Run the binary
46 |
47 | ```
48 | git clone --depth=1 https://github.com/raspberrypi/usbboot
49 | cd usbboot
50 | brew install libusb
51 | brew install pkg-config
52 | make
53 | sudo ./rpiboot
54 | ```
55 |
56 | ## Running
57 |
58 | ### Compute Module 3
59 | Fit the `EMMC-DISABLE` jumper on the Compute Module IO board before powering on the board
60 | or connecting the USB cable.
61 |
62 | ### Compute Module 4
63 | On Compute Module 4 EMMC-DISABLE / nRPIBOOT (GPIO 40) must be fitted to switch the ROM to usbboot mode.
64 | Otherwise, the SPI EEPROM bootloader image will be loaded instead.
65 |
66 |
67 |
68 | ## Compute Module 4 Extensions
69 | In addition to the MSD functionality, there are a number of other utilities that can be loaded
70 | via RPIBOOT on Compute Module 4.
71 |
72 | | Directory | Description |
73 | | ----------| ----------- |
74 | | [recovery](recovery/README.md) | Updates the bootloader EEPROM on a Compute Module 4 |
75 | | [rpi-imager-embedded](rpi-imager-embedded/README.md) | Runs the embedded version of Raspberry Pi Imager on the target device |
76 | | [mass-storage-gadget](mass-storage-gadget/README.md) | Replacement for MSD firmware. Uses Linux USB gadgetfs drivers to export all block devices (e.g. NVMe, EMMC) as MSD devices |
77 | | [secure-boot-recovery](secure-boot-recovery/README.md) | Scripts that extend the `recovery` process to enable secure-boot, sign images etc |
78 | | [secure-boot-msd](secure-boot-msd/README.md) | Scripts for signing the MSD firmware so that it can be used on a secure-boot device |
79 | | [secure-boot-example](secure-boot-example/README.md) | Simple Linux initrd with a UART console.
80 |
81 | **The `secure-boot-msd`, `rpi-imager-embedded` and `mass-storage-gadget` extensions require that the `2022-04-26` (or newer) bootloader EEPROM release has already been written to the EEPROM using `recovery.bin`**
82 |
83 | ## Booting Linux
84 | The `RPIBOOT` protocol provides a virtual file system to the Raspberry Pi bootloader and GPU firmware. It's therefore possible to
85 | boot Linux. To do this, you will need to copy all of the files from a Raspberry Pi boot partition plus create your own
86 | initramfs.
87 | On Raspberry Pi 4 / CM4 the recommended approach is to use a `boot.img` which is a FAT disk image containing
88 | the minimal set of files required from the boot partition.
89 |
90 | ## Troubleshooting
91 |
92 | 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.
93 |
94 | ### Product Information Portal
95 | The [Product Information Portal](https://pip.raspberrypi.com/) contains the official documentation for hardware revision changes for Raspberry Pi computers.
96 | Please check this first to check that the software is up to date.
97 |
98 | ### Hardware
99 | * Inspect the Compute Module pins and connector for signs of damage and verify that the socket is free from debris.
100 | * Check that the Compute Module is fully inserted.
101 | * Check that `nRPIBOOT` / EMMC disable is pulled low BEFORE powering on the device.
102 | * Remove any hubs between the Compute Module and the host.
103 | * Disconnect all other peripherals from the IO board.
104 | * Verify that the red power LED switches on when the IO board is powered.
105 | * 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.
106 |
107 | #### Hardware - CM4
108 | * The CM4 EEPROM supports MMC, USB-MSD, USB 2.0, 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.
109 | * If `rpiboot` is running but the mass storage device does not appear then try running the `rpiboot -d mass-storage-gadget` 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.
110 |
111 | ### Software
112 | 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.
113 |
114 | * Update to the latest software release using `apt update rpiboot` or download and rebuild this repository from Github.
115 | * Run `rpiboot -v | tee log` to capture verbose log output. N.B. This can be very verbose on some systems.
116 |
117 | #### Boot flow
118 | The `rpiboot` system runs in multiple stages. The ROM, bootcode.bin, the VPU firmware (start.elf) and for the `mass-storage-gadget` 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.
119 |
120 | See also: [Raspberry Pi4 Boot Flow](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-4-boot-flow)
121 |
122 | #### bootcode.bin
123 | 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.
124 |
125 | ### Diagnostics
126 | * 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.
127 | * 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.
128 | * 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-gadget` enables a console on the HDMI display.
129 | * 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.
130 | * 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.
131 | * Add `uart_2ndstage=1` to the `config.txt` file in `msd/` or `recovery/` directories to enable UART debug output.
132 |
133 |
134 | ## Secure Boot
135 | Secure Boot requires a recent bootloader stable image e.g. the version in this repository.
136 |
137 | ### Tutorial
138 | Creating a secure boot system from scratch can be quite complex. The [secure boot tutorial](secure-boot-example/README.md) uses a minimal example OS image to demonstrate how the Raspberry Pi-specific aspects of secure boot work.
139 |
140 | ### Host Setup
141 | Secure boot require a 2048 bit RSA asymmetric keypair and the Python `pycrytodomex` module to sign the bootloader EEPROM config and boot image.
142 |
143 | #### Install Python Crypto Support (the pycryptodomex module)
144 | ```bash
145 | python3 -m pip install pycryptodomex
146 | # or
147 | pip install pycryptodomex
148 | ```
149 |
150 | #### Create an RSA key-pair using OpenSSL. Must be 2048 bits
151 | ```bash
152 | cd $HOME
153 | openssl genrsa 2048 > private.pem
154 | ```
155 |
156 | ### Secure Boot - configuration
157 | * Please see the [secure boot EEPROM guide](secure-boot-recovery/README.md) to enable via rpiboot `recovery.bin`.
158 | * Please see the [secure boot MSD guide](secure-boot-msd/README.md) for instructions about to mount the EMMC via USB mass-storage once secure-boot has been enabled.
159 |
160 | ## Secure Boot - image creation
161 | Secure Boot requires self-contained ramdisk (`boot.img`) FAT image to be created containing the GPU
162 | firmware, kernel and any other dependencies that would normally be loaded from the boot partition.
163 |
164 | This plus a signature file (`boot.sig`) must be placed in the boot partition of the Raspberry Pi
165 | or network download location.
166 |
167 | The `boot.img` file should contain:-
168 | * The kernel
169 | * Device tree overlays
170 | * GPU firmware (start.elf and fixup.dat)
171 | * Linux initramfs containing the application OR scripts to mount/create an encrypted file-system.
172 |
173 |
174 | ### Disk encryption
175 | Secure-boot is responsible for loading the Kernel + initramfs and loads all of the data
176 | from a single `boot.img` file stored on an unencrypted FAT/EFI partition.
177 |
178 | **There is no support in the ROM or firmware for full-disk encryption.**
179 |
180 | If a custom OS image needs to use an encrypted file-system then this would normally be implemented
181 | via scripts within the initramfs.
182 |
183 | Raspberry Pi computers do not have a secure enclave, however, it's possible to store a 256 bit
184 | [device specific private key](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#device-specific-private-key)
185 | in OTP. The key is accessible to any process with access to `/dev/vcio` (`vcmailbox`), therefore, the
186 | secure-boot OS must ensure that access to this interface is restricted.
187 |
188 | **It is not possible to prevent code running in ARM supervisor mode (e.g. kernel code) from accessing OTP hardware directly**
189 |
190 | See also:-
191 | * [LUKS](https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup)
192 | * [cryptsetup FAQ](https://gitlab.com/cryptsetup/cryptsetup/-/wikis/FrequentlyAskedQuestions)
193 | * [rpi-otp-private-key](../tools/rpi-otp-private-key)
194 |
195 | The [secure boot tutorial](secure-boot-example/README.md) contains a `boot.img` that supports cryptsetup and a simple example.
196 |
197 | ### Building `boot.img` using buildroot
198 |
199 | The `secure-boot-example` directory contains a simple `boot.img` example with working HDMI,
200 | network, UART console and common tools in an initramfs.
201 |
202 | This was generated from the [raspberrypi-signed-boot](https://github.com/raspberrypi/buildroot/blob/raspberrypi-signed-boot/README.md)
203 | buildroot config. Whilst not a generic fully featured configuration it should be relatively
204 | straightforward to cherry-pick the `raspberrypi-secure-boot` package and helper scripts into
205 | other buildroot configurations.
206 |
207 | #### Minimum firmware version
208 | The firmware must be new enough to support secure boot. The latest firmware APT
209 | package supports secure boot. To download the firmware files directly.
210 |
211 | ```bash
212 | git clone --depth 1 --branch stable https://github.com/raspberrypi/firmware
213 | ```
214 |
215 | To check the version information within a `start4.elf` firmware file run
216 | ```bash
217 | strings start4.elf | grep VC_BUILD_
218 | ```
219 |
220 | #### Verifying the contents of a `boot.img` file
221 | To verify that the boot image has been created correctly use losetup to mount the .img file.
222 |
223 | ```bash
224 | sudo su
225 | mkdir -p boot-mount
226 | LOOP=$(losetup -f)
227 | losetup -f boot.img
228 | mount ${LOOP} boot-mount/
229 |
230 | echo boot.img contains
231 | find boot-mount/
232 |
233 | umount boot-mount
234 | losetup -d ${LOOP}
235 | rmdir boot-mount
236 | ```
237 |
238 | #### Signing the boot image
239 | For secure-boot, `rpi-eeprom-digest` extends the current `.sig` format of
240 | sha256 + timestamp to include an hex format RSA bit PKCS#1 v1.5 signature. The key length
241 | must be 2048 bits.
242 |
243 | ```bash
244 | ../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}"
245 | ```
246 |
247 | To verify the signature of an existing image set the `PUBLIC_KEY_FILE` environment variable
248 | to the path of the public key file in PEM format.
249 |
250 | ```bash
251 | ../tools/rpi-eeprom-digest -i boot.img -k "${PUBLIC_KEY_FILE}" -v boot.sig
252 | ```
253 |
254 |
255 | #### Hardware security modules
256 | `rpi-eeprom-digest` is a shell script that wraps a call to `openssl dgst -sign`.
257 | If the private key is stored within a hardware security module instead of
258 | a .PEM file the `openssl` command will need to be replaced with the appropriate call to the HSM.
259 |
260 | `rpi-eeprom-digest` called by `update-pieeprom.sh` to sign the EEPROM config file.
261 |
262 | The RSA public key must be stored within the EEPROM so that it can be used by the bootloader.
263 | By default, the RSA public key is automatically extracted from the private key PEM file. Alternatively,
264 | the public key may be specified separately via the `-p` argument to `update-pieeprom.sh` and `rpi-eeprom-config`.
265 |
266 | To extract the public key in PEM format from a private key PEM file, run:
267 | ```bash
268 | openssl rsa -in private.pem -pubout -out public.pem
269 | ```
270 |
271 | #### Copy the secure boot image to the boot partition on the Raspberry Pi.
272 | Copy `boot.img` and `boot.sig` to the boot filesystem.
273 | Secure boot images can be loaded from any of the normal boot modes (e.g. SD, USB, Network).
274 |
--------------------------------------------------------------------------------
/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(*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 |
--------------------------------------------------------------------------------
/bootcode4.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/usbboot/ec8294ffa46931e5b1b322b04ca26863a98a40cb/bootcode4.bin
--------------------------------------------------------------------------------
/debian/70-rpiboot.rules:
--------------------------------------------------------------------------------
1 | ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0a5c", ATTR{idProduct}=="27[16][134]", TAG+="uaccess"
2 |
--------------------------------------------------------------------------------
/debian/changelog:
--------------------------------------------------------------------------------
1 | rpiboot (20221104~153421) bullseye; urgency=medium
2 |
3 | [ Tim Gover ]
4 | * win32: Update windows installer and make it smaller
5 | * Revert pieeprom.bin to previous stable release (2022-09-02)
6 | * Update pieeprom to 2022-11-04
7 |
8 | -- Serge Schneider Fri, 04 Nov 2022 19:07:17 +0000
9 |
10 | rpiboot (20221031~085458) bullseye; urgency=medium
11 |
12 | [ Tim Gover ]
13 | * mass-storage-gadget: Remove activity LED for CM3 compatibility
14 | * secure-boot-msd: Update start4.elf to the same version as in /msd
15 | * rpi-eeprom-digest: Add an option to verify RSA signed files.
16 | * rpi-eeprom-digest: Fix cleanup of TMP_DIR
17 | * rpi-eeprom-digest: Fix typo in default env var for OPENSSL executable
18 | * readme.md: Add a troubleshooting section
19 | * tools: Add rpi-bootloader-key-convert
20 | * secure-boot: Update example to use the raspberrypi-secure-boot buildroot example
21 | * secure-boot-example: Update the images to the new buildroot example
22 | * mass-storage-gadget: Update & move source repo to official Raspberry Pi buildroot fork
23 | * mass-storage-gadget: Update image and instructions
24 | * Update EEPROM to 2022-10-18
25 | * tools: vcmailbox wrappers for managing 256 bit OTP private key
26 | * Update mass-storage-gadget build instructions
27 |
28 | -- Serge Schneider Mon, 31 Oct 2022 11:49:39 +0000
29 |
30 | rpiboot (20220815~145439) bullseye; urgency=medium
31 |
32 | [ Chris Burton ]
33 | * Add option to look for CM with specific USB path.
34 |
35 | [ Tim Gover ]
36 | * mass-storage-gadget: Switch DWC mode from OTG to peripheral
37 |
38 | -- Serge Schneider Mon, 15 Aug 2022 16:57:53 +0100
39 |
40 | rpiboot (20220804~165403) bullseye; urgency=medium
41 |
42 | [ Tim Gover ]
43 | * Bump Win32 install for 20220718~085937 release
44 | * Update the EEPROM release to 2022-08-02
45 |
46 | -- Serge Schneider Mon, 08 Aug 2022 08:44:43 +0100
47 |
48 | rpiboot (20220718~085937) bullseye; urgency=medium
49 |
50 | [ Tim Gover ]
51 | * Replace duplicated bootcode4.bin with links to bootcode4.bin / recovery.bin
52 |
53 | -- Serge Schneider Mon, 08 Aug 2022 08:43:54 +0100
54 |
55 | rpiboot (20220715~091537) bullseye; urgency=medium
56 |
57 | [ Tim Gover ]
58 | * Replace duplicated bootcode4.bin with links to bootcode4.bin / recovery.bin
59 |
60 | -- Serge Schneider Mon, 18 Jul 2022 09:31:34 +0100
61 |
62 | rpiboot (20220708~133956) bullseye; urgency=medium
63 |
64 | [ Tim Gover ]
65 | * readme: Fix links/formatting
66 | * secure-boot-example: Add step by step example / quick start
67 | * bootcode4: Improved DDR setup for old (or missing) EEPROM images
68 | * Update recovery/rpi-eeprom-config
69 |
70 | [ andrum99 ]
71 | * Copy edit
72 |
73 | [ Phil Elwell ]
74 | * README: pkg-config is also a dependency
75 |
76 | -- Serge Schneider Mon, 11 Jul 2022 08:43:19 +0100
77 |
78 | rpiboot (20220504~214218) bullseye; urgency=medium
79 |
80 | [ Tim Gover ]
81 | * Update the Windows release to 20220427~095022
82 | * msd: Fix support for older EEPROM release in on 2711
83 | * Revert msd/bootcode4.bin to earlier release
84 |
85 | -- Serge Schneider Thu, 05 May 2022 09:15:30 +0100
86 |
87 | rpiboot (20220427~095022) bullseye; urgency=medium
88 |
89 | [ Wojtek Porczyk ]
90 | * tools/update-pieeprom.sh: Fix cleanup exiting 1
91 |
92 | [ Daniel Cousens ]
93 | * add sudo hint
94 | * fix typo
95 |
96 | [ Dan Pastusek ]
97 | * Add inline comments about boot order
98 | * Update boot.conf
99 |
100 | [ Evan Gates ]
101 | * main: check _POSIX_VERSION for fmemopen
102 |
103 | [ Tim Gover ]
104 | * Update EEPROM, recovery.bin and bootcode4.bin to 2022-04-26
105 |
106 | -- Serge Schneider Wed, 27 Apr 2022 14:26:11 +0100
107 |
108 | rpiboot (20220315~121405) bullseye; urgency=medium
109 |
110 | [ Tim Gover ]
111 | * pieeprom-2022-02-22: Fix EMMC secure-boot
112 | * secure-boot-example: Replace example boot.img with minimal 64bit busybox initrd
113 | * Add mass-storage-gadget replacement for msd on 2711
114 | * Add Raspberry Pi Imager - embedded
115 | * Update the top-level Readme.md to describe the '-d' programs
116 | * Update packaging for win32/debian installers
117 | * Simplify debian install file (#123)
118 |
119 | [ Yago ]
120 | * Fix -c and -i default option
121 |
122 | -- Serge Schneider Tue, 15 Mar 2022 10:31:33 +0000
123 |
124 | rpiboot (20220208~181027) bullseye; urgency=medium
125 |
126 | [ Tim Gover ]
127 | * pieeprom-2022-02-08: Fix boot failure in locked secure-boot
128 |
129 | -- Serge Schneider Wed, 09 Feb 2022 10:31:22 +0000
130 |
131 | rpiboot (20220131~103329) bullseye; urgency=medium
132 |
133 | * Add python3 dependency
134 |
135 | -- Serge Schneider Mon, 31 Jan 2022 10:33:51 +0000
136 |
137 | rpiboot (20220131~101805) bullseye; urgency=medium
138 |
139 | [ Tim Gover ]
140 | * Update EEPROM to latest stable release 2022-01-25
141 | * secure-boot: Specify the minimum version for secure-boot mode.
142 | * eeprom-erase: Add option to erase the SPI EEPROM
143 | * Add eeprom-erase to the APT package
144 | * secure-boot: Remove unnecessary warning
145 |
146 | [ katiefaith ]
147 | * Fixed regex for sig file to replace only extension
148 | * Update tools/update-pieeprom.sh to not truncate a filepath with '.'s
149 | but no file extension e.g. previously, ../../filename would generate ../.sig
150 |
151 | -- Serge Schneider Mon, 31 Jan 2022 10:21:24 +0000
152 |
153 | rpiboot (20220111~130126) bullseye; urgency=medium
154 |
155 | [ Tim Gover ]
156 | * make-boot-image: Use losetup instead of udisksctl
157 | * secure-boot BETA documentation updates
158 | * beta: Add support for secure-boot - see Readme.md
159 | * secure-boot: Added example boot.img
160 | * make-boot-image: Fix typo in docs AFT_SIZE -> FAT_SIZE
161 | * secure-boot: Move the example PEM file to secure-boot-example
162 | * secure-boot: Update recovery,bootcode4 + EEPROM to latest beta - 2021-10-27
163 | * secure-boot: Update Readme to update max ramdisk size to 96MB
164 | * Readme: Add docs for examining the contents of boot.img
165 | * secure-boot: Add more documentation about the RSA signatures and add optional public key argument
166 | * make-boot-image: Fix unmount and set GID
167 | * make-boot-image: Enable the image size to be specified
168 | * Update the default EEPROM image to 2021-11-22
169 | * rpi-eeprom-config: Pull in --edit fixes from rpi-eeprom repo
170 | * secure-boot-recovery: Update to latest 2021-11-22 EEPROM
171 | * Remove imager beta release
172 | * bootloader: Enable revoke_devkey in secure-boot recovery.bin
173 | * rpiboot: Add build date and version
174 |
175 | [ Peter Harper ]
176 | * bootloader: Remove NVMe beta files
177 | * Update bootcode4.bin
178 | * Update the default EEPROM image to 2021-12-02
179 |
180 | [ Phil Elwell ]
181 | * Bump bootcode.bin
182 | * msd: Fix non-2711 MSD support
183 |
184 | [ Nils Werner ]
185 | * Update udev rules to use uaccess tag
186 |
187 | [ Christophe Blaess ]
188 | * Fix `install` and `uninstall` targets in Makefile.
189 |
190 | [ Dodain ]
191 | * Corrected the board flag
192 | * Corrected the -b flag in the readme.md
193 |
194 | [ Mathew Wicks ]
195 | * Use pkg-config to locate libusb-1.0
196 |
197 | -- Serge Schneider Tue, 11 Jan 2022 11:45:02 +0000
198 |
199 | rpiboot (20210714~083443) buster; urgency=medium
200 |
201 | [ Tim Gover ]
202 | * Don't load bootcode from the overlay directory
203 | * pieeprom-2021-01-16: Update to latest release for BCM2711 XHCI boot
204 | * Update EEPROM image to latest CM4 manufacturing image + configuration
205 | * msd: CM4: Fix occasional USB errors
206 | * Add native Raspberry Pi Imager port - beta
207 | * Update Raspberry Pi 4 EEPROM to pieeprom-2021-07-06
208 |
209 | [ Peter Harper ]
210 | * Add nvme folder to enable testing nvme beta changes
211 | * Update nvme beta bootloader
212 |
213 | [ Ramon Roche ]
214 | * readme: add macOS build step
215 |
216 | -- Serge Schneider Wed, 14 Jul 2021 15:58:23 +0100
217 |
218 | rpiboot (20201016~124509) buster; urgency=medium
219 |
220 | [ Tim Gover ]
221 | * Update rpiboot for CM4 and rationlise -d behaviour.
222 |
223 | -- Serge Schneider Fri, 16 Oct 2020 14:03:28 +0100
224 |
225 | rpiboot (20200217~075142) buster; urgency=medium
226 |
227 | [ Phil Elwell ]
228 | * Smallpacket (#26)
229 |
230 | [ Petr Tesarik ]
231 | * Add support for Pi4 (#42)
232 |
233 | [ Tim Gover ]
234 | * Fix serial check for BCM2711 (#46)
235 |
236 | [ maxnet ]
237 | * Allow members of plugdev group to execute rpiboot without root (#27)
238 |
239 | [ Douglas Huff ]
240 | * Fixup for recent firmware inclusion changes (#34)
241 |
242 | [ Phil Elwell ]
243 | * Fix cross-platform building
244 |
245 | [ Chris Burton ]
246 | * Add missing newline to print. (#48)
247 | * Change timeout in ep_read to 3 seconds (#50)
248 |
249 | -- Serge Schneider Mon, 02 Mar 2020 12:27:11 +0000
250 |
251 | rpiboot (20180627~140218) stretch; urgency=medium
252 |
253 | * Switch "r" to "rb" for fmemopen
254 | * Add cross platform method of building in a binary
255 | * Compile bootcode.bin and start.elf into rpiboot
256 | * Change USB device settings to better work with all devices
257 | * usbboot: Add support for multiple instances
258 | * Add extra delay between disconnecting and reconnecting
259 | * Fixed numerous problems with enumerating CM3 / Pi Zero
260 |
261 | -- Serge Schneider Wed, 04 Jul 2018 17:46:29 +0100
262 |
263 | rpiboot (20171023~154601) stretch; urgency=medium
264 |
265 | * Directory overlay support
266 |
267 | -- Serge Schneider Fri, 27 Oct 2017 10:21:19 +0100
268 |
269 | rpiboot (20170926+2) stretch; urgency=medium
270 |
271 | * Change rpiboot behaviour to check /usr/share/rpiboot/msd
272 | * Add lintian-overrides
273 |
274 | -- Serge Schneider Tue, 26 Sep 2017 16:11:13 +0100
275 |
276 | rpiboot (20170926+1) stretch; urgency=medium
277 |
278 | * Install msd files without creating a subdirectory
279 |
280 | -- Serge Schneider Tue, 26 Sep 2017 14:55:50 +0100
281 |
282 | rpiboot (20170926) stretch; urgency=medium
283 |
284 | * Initial Release.
285 |
286 | -- Serge Schneider Tue, 26 Sep 2017 13:31:20 +0100
287 |
--------------------------------------------------------------------------------
/debian/compat:
--------------------------------------------------------------------------------
1 | 10
2 |
--------------------------------------------------------------------------------
/debian/control:
--------------------------------------------------------------------------------
1 | Source: rpiboot
2 | Section: utils
3 | Priority: optional
4 | Maintainer: Serge Schneider
5 | Build-Depends: debhelper (>= 9), libusb-1.0-0-dev, pkg-config
6 | Standards-Version: 4.5.1
7 | Homepage: https://github.com/raspberrypi/usbboot
8 |
9 | Package: rpiboot
10 | Architecture: any
11 | Depends: ${shlibs:Depends}, ${misc:Depends}, python3
12 | Description: Raspberry Pi USB booting code
13 | USB MSD boot code which should work on the Raspberry Pi model A,
14 | Compute Module, Compute Module 3, Compute Module 4 and Raspberry Pi Zero.
15 |
--------------------------------------------------------------------------------
/debian/copyright:
--------------------------------------------------------------------------------
1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Upstream-Name: rpiboot
3 | Source: https://github.com/raspberrypi/usbboot
4 |
5 | Files: *
6 | Copyright: 2016 Raspberry Pi (Trading) Ltd.
7 | License: Apache-2.0
8 |
9 | Files: msd/*
10 | Copyright:
11 | 2006, Broadcom Corporation
12 | 2015, Raspberry Pi (Trading) Ltd
13 | License: custom
14 |
15 | License: Apache-2.0
16 | Licensed under the Apache License, Version 2.0 (the "License");
17 | you may not use this file except in compliance with the License.
18 | You may obtain a copy of the License at
19 | .
20 | https://www.apache.org/licenses/LICENSE-2.0
21 | .
22 | Unless required by applicable law or agreed to in writing, software
23 | distributed under the License is distributed on an "AS IS" BASIS,
24 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | See the License for the specific language governing permissions and
26 | limitations under the License.
27 | .
28 | On Debian systems, the complete text of the Apache version 2.0 license
29 | can be found in "/usr/share/common-licenses/Apache-2.0".
30 |
31 | License: custom
32 | All rights reserved.
33 | .
34 | Redistribution. Redistribution and use in binary form, without
35 | modification, are permitted provided that the following conditions are
36 | met:
37 | .
38 | * This software may only be used for the purposes of developing for,
39 | running or using a Raspberry Pi device.
40 | * Redistributions must reproduce the above copyright notice and the
41 | following disclaimer in the documentation and/or other materials
42 | provided with the distribution.
43 | * Neither the name of Broadcom Corporation nor the names of its suppliers
44 | may be used to endorse or promote products derived from this software
45 | without specific prior written permission.
46 | .
47 | DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
48 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
49 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
50 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
51 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
52 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
53 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
54 | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
55 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
56 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
57 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
58 | DAMAGE.
59 |
--------------------------------------------------------------------------------
/debian/rpiboot.install:
--------------------------------------------------------------------------------
1 | rpiboot usr/bin
2 | msd/*.elf usr/share/rpiboot/msd/
3 | msd/*.bin usr/share/rpiboot/msd/
4 | recovery usr/share/rpiboot/
5 | eeprom-erase usr/share/rpiboot
6 | rpi-imager-embedded usr/share/rpiboot/
7 | mass-storage-gadget usr/share/rpiboot/
8 | secure-boot-recovery usr/share/rpiboot/
9 | secure-boot-msd usr/share/rpiboot/
10 | secure-boot-example usr/share/rpiboot/
11 | tools usr/share/rpiboot/
12 | debian/70-rpiboot.rules /lib/udev/rules.d
13 | recovery.bin usr/share/rpiboot/
14 | bootcode4.bin usr/share/rpiboot/
15 |
--------------------------------------------------------------------------------
/debian/rpiboot.lintian-overrides:
--------------------------------------------------------------------------------
1 | rpiboot: appstream-metadata-missing-modalias-provide lib/udev/rules.d/70-rpiboot.rules
2 | rpiboot: arch-dependent-file-in-usr-share usr/share/rpiboot/msd/start4.elf
3 | rpiboot: arch-dependent-file-in-usr-share usr/share/rpiboot/msd/start.elf
4 | rpiboot: binary-from-other-architecture usr/share/rpiboot/msd/start4.elf
5 | rpiboot: binary-from-other-architecture usr/share/rpiboot/msd/start.elf
6 | rpiboot: binary-without-manpage usr/bin/rpiboot
7 | rpiboot: executable-not-elf-or-script usr/share/rpiboot/recovery/pieeprom.original.bin
8 | rpiboot: hardening-no-pie usr/bin/rpiboot
9 | rpiboot: statically-linked-binary usr/share/rpiboot/msd/start4.elf
10 | rpiboot: statically-linked-binary usr/share/rpiboot/msd/start.elf
11 | rpiboot: unstripped-binary-or-object usr/share/rpiboot/msd/start4.elf
12 | rpiboot: unstripped-binary-or-object usr/share/rpiboot/msd/start.elf
13 |
--------------------------------------------------------------------------------
/debian/rules:
--------------------------------------------------------------------------------
1 | #!/usr/bin/make -f
2 |
3 | %:
4 | dh $@
5 |
6 | # Makefile does not follow any conventions
7 | override_dh_auto_install:
8 | true
9 |
10 | override_dh_strip:
11 | dh_strip -Xstart.elf -Xstart4.elf
12 |
13 | override_dh_install:
14 | dh_install
15 | find $(CURDIR)/debian -name .gitignore -delete
16 |
--------------------------------------------------------------------------------
/debian/source.lintian-overrides:
--------------------------------------------------------------------------------
1 | rpiboot: source-is-missing msd/start.elf
2 | rpiboot: source-is-missing msd/start4.elf
3 | rpiboot: source-contains-prebuilt-windows-binary win32/*.dll
4 | rpiboot: source-contains-prebuilt-windows-binary win32/*.exe
5 |
--------------------------------------------------------------------------------
/debian/source/format:
--------------------------------------------------------------------------------
1 | 3.0 (native)
2 |
--------------------------------------------------------------------------------
/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 | ../recovery.bin
--------------------------------------------------------------------------------
/eeprom-erase/config.txt:
--------------------------------------------------------------------------------
1 | erase_eeprom=1
2 | uart_2ndstage=1
3 |
--------------------------------------------------------------------------------
/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 "msd/bootcode.h"
9 | #include "msd/start.h"
10 | #include "msd/bootcode4.h"
11 | #include "msd/start4.h"
12 |
13 | /*
14 | * Old OS X/BSD do not implement fmemopen(). If the version of POSIX
15 | * supported is old enough that fmemopen() isn't included, assume
16 | * we're on a BSD compatible system and define a fallback fmemopen()
17 | * that depends on funopen().
18 | */
19 | #if _POSIX_VERSION <= 200112L
20 | #include "fmemopen.c"
21 | #endif
22 |
23 | int signed_boot = 0;
24 | int verbose = 0;
25 | int loop = 0;
26 | int overlay = 0;
27 | long delay = 500;
28 | char * directory = NULL;
29 | char pathname[18] = {0};
30 | char * targetpathname = NULL;
31 | uint8_t targetPortNo = 99;
32 |
33 | int out_ep;
34 | int in_ep;
35 | int bcm2711;
36 |
37 | static FILE * check_file(const char * dir, const char *fname, int use_fmem);
38 | static int second_stage_prep(FILE *fp, FILE *fp_sig);
39 |
40 | typedef struct MESSAGE_S {
41 | int length;
42 | unsigned char signature[20];
43 | } boot_message_t;
44 |
45 | void usage(int error)
46 | {
47 | FILE * dest = error ? stderr : stdout;
48 |
49 | fprintf(dest, "Usage: rpiboot\n");
50 | fprintf(dest, " or: rpiboot -d [directory]\n");
51 | fprintf(dest, "Boot a Raspberry Pi in device mode either directly into a mass storage device\n");
52 | fprintf(dest, "or provide a set of boot files in a directory from which to boot. This can\n");
53 | fprintf(dest, "then contain a initramfs to boot through to linux kernel\n\n");
54 | fprintf(dest, "To flash the default bootloader EEPROM image on Compute Module 4 run\n");
55 | fprintf(dest, "rpiboot -d recovery\n\n");
56 | fprintf(dest, "For more information about the bootloader EEPROM please see:\n");
57 | fprintf(dest, "https://www.raspberrypi.org/documentation/hardware/raspberrypi/booteeprom.md\n\n");
58 | fprintf(dest, "rpiboot : Boot the device into mass storage device\n");
59 | fprintf(dest, "rpiboot -d [directory] : Boot the device using the boot files in 'directory'\n");
60 | fprintf(dest, "Further options:\n");
61 | fprintf(dest, " -l : Loop forever\n");
62 | fprintf(dest, " -o : Use files from overlay subdirectory if they exist (when using a custom directory)\n");
63 | fprintf(dest, " USB Path (1-1.3.2 for example) is shown in verbose mode.\n");
64 | fprintf(dest, " (bootcode.bin is always preloaded from the base directory)\n");
65 | fprintf(dest, " -m delay : Microseconds delay between checking for new devices (default 500)\n");
66 | fprintf(dest, " -v : Verbose\n");
67 | fprintf(dest, " -s : Signed using bootsig.bin\n");
68 | fprintf(dest, " -0/1/2/3/4/5/6 : Only look for CMs attached to USB port number 0-6\n");
69 | fprintf(dest, " -p [pathname] : Only look for CM with USB pathname\n");
70 | fprintf(dest, " -h : This help\n");
71 |
72 | exit(error ? -1 : 0);
73 | }
74 |
75 | libusb_device_handle * LIBUSB_CALL open_device_with_vid(
76 | libusb_context *ctx, uint16_t vendor_id)
77 | {
78 | struct libusb_device **devs;
79 | struct libusb_device *found = NULL;
80 | struct libusb_device *dev;
81 | struct libusb_device_handle *handle = NULL;
82 | uint32_t i = 0;
83 | int r, j, len;
84 | uint8_t path[8]; // Needed for libusb_get_port_numbers
85 | uint8_t portNo = 0;
86 |
87 | if (libusb_get_device_list(ctx, &devs) < 0)
88 | return NULL;
89 |
90 | while ((dev = devs[i++]) != NULL) {
91 | len = 0;
92 | struct libusb_device_descriptor desc;
93 | r = libusb_get_device_descriptor(dev, &desc);
94 | if (r < 0)
95 | goto out;
96 |
97 | if(overlay || verbose == 2 || targetpathname!=NULL)
98 | {
99 | r = libusb_get_port_numbers(dev, path, sizeof(path));
100 | len = snprintf(&pathname[len], 18-len, "%d", libusb_get_bus_number(dev));
101 | if (r > 0) {
102 | len += snprintf(&pathname[len], 18-len, "-");
103 | len += snprintf(&pathname[len], 18-len, "%d", path[0]);
104 | for (j = 1; j < r; j++)
105 | {
106 | len += snprintf(&pathname[len], 18-len, ".%d", path[j]);
107 | }
108 | }
109 | }
110 |
111 | /*
112 | http://libusb.sourceforge.net/api-1.0/group__dev.html#ga14879a0ea7daccdcddb68852d86c00c4
113 |
114 | The port number returned by this call is usually guaranteed to be uniquely tied to a physical port,
115 | meaning that different devices plugged on the same physical port should return the same port number.
116 | */
117 | portNo = libusb_get_port_number(dev);
118 |
119 | if(verbose == 2)
120 | {
121 | printf("Found device %u idVendor=0x%04x idProduct=0x%04x\n", i, desc.idVendor, desc.idProduct);
122 | printf("Bus: %d, Device: %d Path: %s\n",libusb_get_bus_number(dev), libusb_get_device_address(dev), pathname);
123 | }
124 |
125 | if (desc.idVendor == vendor_id) {
126 | if(desc.idProduct == 0x2763 ||
127 | desc.idProduct == 0x2764 ||
128 | desc.idProduct == 0x2711)
129 | {
130 | FILE *fp_second_stage = NULL;
131 | FILE *fp_sign = NULL;
132 | const char *second_stage;
133 |
134 | if(verbose == 2)
135 | printf("Found candidate Compute Module...");
136 |
137 | bcm2711 = (desc.idProduct == 0x2711);
138 | if (bcm2711)
139 | second_stage = "bootcode4.bin";
140 | else
141 | second_stage = "bootcode.bin";
142 |
143 | fp_second_stage = check_file(directory, second_stage, 1);
144 | if (!fp_second_stage)
145 | {
146 | fprintf(stderr, "Failed to open %s\n", second_stage);
147 | exit(1);
148 | }
149 |
150 | if (signed_boot && !bcm2711) // Signed boot use a different mechanism on BCM2711
151 | {
152 | const char *sig_file = "bootcode.sig";
153 | fp_sign = check_file(directory, sig_file, 1);
154 | if (!fp_sign)
155 | {
156 | fprintf(stderr, "Unable to open '%s'\n", sig_file);
157 | usage(1);
158 | }
159 | }
160 |
161 | if (second_stage_prep(fp_second_stage, fp_sign) != 0)
162 | {
163 | fprintf(stderr, "Failed to prepare the second stage bootcode\n");
164 | exit(-1);
165 | }
166 | if (fp_second_stage)
167 | fclose(fp_second_stage);
168 |
169 | if (fp_sign)
170 | fclose(fp_sign);
171 |
172 | ///////////////////////////////////////////////////////////////////////
173 | // Check if we should match against a specific port number
174 | ///////////////////////////////////////////////////////////////////////
175 | if ((targetPortNo == 99 || portNo == targetPortNo) &&
176 | (targetpathname==NULL||strcmp(targetpathname,pathname)==0))
177 | {
178 | if(verbose) printf("Device located successfully\n");
179 | found = dev;
180 | break;
181 | }
182 | else
183 | {
184 | if(verbose == 2)
185 | printf("...Wrong Port/Path, Trying again\n");
186 | }
187 | }
188 | }
189 | }
190 |
191 | if (found) {
192 | sleep(1);
193 | r = libusb_open(found, &handle);
194 | if (r == LIBUSB_ERROR_ACCESS)
195 | {
196 | printf("Permission to access USB device denied. Make sure you are a member of the plugdev group.\n");
197 | exit(-1);
198 | }
199 | else if (r < 0)
200 | {
201 | if(verbose) printf("Failed to open the requested device\n");
202 | handle = NULL;
203 | }
204 | }
205 |
206 | out:
207 | libusb_free_device_list(devs, 1);
208 | return handle;
209 |
210 | }
211 |
212 | int Initialize_Device(libusb_context ** ctx, libusb_device_handle ** usb_device)
213 | {
214 | int ret = 0;
215 | int interface;
216 | struct libusb_config_descriptor *config;
217 |
218 | *usb_device = open_device_with_vid(*ctx, 0x0a5c);
219 | if (*usb_device == NULL)
220 | {
221 | usleep(200);
222 | return -1;
223 | }
224 |
225 | libusb_get_active_config_descriptor(libusb_get_device(*usb_device), &config);
226 | if(config == NULL)
227 | {
228 | printf("Failed to read config descriptor\n");
229 | exit(-1);
230 | }
231 |
232 | // Handle 2837 where it can start with two interfaces, the first is mass storage
233 | // the second is the vendor interface for programming
234 | if(config->bNumInterfaces == 1)
235 | {
236 | interface = 0;
237 | out_ep = 1;
238 | in_ep = 2;
239 | }
240 | else
241 | {
242 | interface = 1;
243 | out_ep = 3;
244 | in_ep = 4;
245 | }
246 |
247 | ret = libusb_claim_interface(*usb_device, interface);
248 | if (ret)
249 | {
250 | libusb_close(*usb_device);
251 | printf("Failed to claim interface\n");
252 | return ret;
253 | }
254 |
255 | if(verbose) printf("Initialised device correctly\n");
256 |
257 | return ret;
258 | }
259 |
260 | #define LIBUSB_MAX_TRANSFER (16 * 1024)
261 |
262 | int ep_write(void *buf, int len, libusb_device_handle * usb_device)
263 | {
264 | int a_len = 0;
265 | int sending, sent;
266 | int ret =
267 | libusb_control_transfer(usb_device, LIBUSB_REQUEST_TYPE_VENDOR, 0,
268 | len & 0xffff, len >> 16, NULL, 0, 1000);
269 |
270 | if(ret != 0)
271 | {
272 | printf("Failed control transfer (%d,%d)\n", ret, len);
273 | return ret;
274 | }
275 |
276 | while(len > 0)
277 | {
278 | sending = len < LIBUSB_MAX_TRANSFER ? len : LIBUSB_MAX_TRANSFER;
279 | ret = libusb_bulk_transfer(usb_device, out_ep, buf, sending, &sent, 5000);
280 | if (ret)
281 | break;
282 | a_len += sent;
283 | buf += sent;
284 | len -= sent;
285 | }
286 | if(verbose)
287 | printf("libusb_bulk_transfer sent %d bytes; returned %d\n", a_len, ret);
288 |
289 | return a_len;
290 | }
291 |
292 | int ep_read(void *buf, int len, libusb_device_handle * usb_device)
293 | {
294 | int ret =
295 | libusb_control_transfer(usb_device,
296 | LIBUSB_REQUEST_TYPE_VENDOR |
297 | LIBUSB_ENDPOINT_IN, 0, len & 0xffff,
298 | len >> 16, buf, len, 3000);
299 | if(ret >= 0)
300 | return len;
301 | else
302 | return ret;
303 | }
304 |
305 | void get_options(int argc, char *argv[])
306 | {
307 | // Skip the command name
308 | argv++; argc--;
309 | while(*argv)
310 | {
311 | if(strcmp(*argv, "-d") == 0)
312 | {
313 | argv++; argc--;
314 | if(argc < 1)
315 | usage(1);
316 | directory = *argv;
317 | }
318 | else if(strcmp(*argv, "-p") == 0)
319 | {
320 | argv++; argc--;
321 | if(argc < 1)
322 | usage(1);
323 | targetpathname = *argv;
324 | }
325 | else if(strcmp(*argv, "-h") == 0 || strcmp(*argv, "--help") == 0)
326 | {
327 | usage(0);
328 | }
329 | else if(strcmp(*argv, "-l") == 0)
330 | {
331 | loop = 1;
332 | }
333 | else if(strcmp(*argv, "-v") == 0)
334 | {
335 | verbose = 1;
336 | }
337 | else if(strcmp(*argv, "-o") == 0)
338 | {
339 | overlay = 1;
340 | }
341 | else if(strcmp(*argv, "-m") == 0)
342 | {
343 | argv++; argc--;
344 | if(argc < 1)
345 | usage(1);
346 | delay = atol(*argv);
347 | }
348 | else if(strcmp(*argv, "-vv") == 0)
349 | {
350 | verbose = 2;
351 | }
352 | else if(strcmp(*argv, "-s") == 0)
353 | {
354 | signed_boot = 1;
355 | }
356 | else if(strcmp(*argv, "-0") == 0)
357 | {
358 | targetPortNo = 0;
359 | }
360 | else if(strcmp(*argv, "-1") == 0)
361 | {
362 | targetPortNo = 1;
363 | }
364 | else if(strcmp(*argv, "-2") == 0)
365 | {
366 | targetPortNo = 2;
367 | }
368 | else if(strcmp(*argv, "-3") == 0)
369 | {
370 | targetPortNo = 3;
371 | }
372 | else if(strcmp(*argv, "-4") == 0)
373 | {
374 | targetPortNo = 4;
375 | }
376 | else if(strcmp(*argv, "-5") == 0)
377 | {
378 | targetPortNo = 5;
379 | }
380 | else if(strcmp(*argv, "-6") == 0)
381 | {
382 | targetPortNo = 6;
383 | }
384 | else
385 | {
386 | usage(1);
387 | }
388 |
389 | argv++; argc--;
390 | }
391 | if(overlay&&!directory)
392 | {
393 | usage(1);
394 | }
395 | if(!delay)
396 | {
397 | usage(1);
398 | }
399 | if((targetPortNo != 99) && (targetpathname != NULL))
400 | {
401 | usage(1);
402 | }
403 | }
404 |
405 | boot_message_t boot_message;
406 | void *second_stage_txbuf;
407 |
408 | int second_stage_prep(FILE *fp, FILE *fp_sig)
409 | {
410 | int size;
411 |
412 | fseek(fp, 0, SEEK_END);
413 | boot_message.length = ftell(fp);
414 | fseek(fp, 0, SEEK_SET);
415 |
416 | if(fp_sig != NULL)
417 | {
418 | fread(boot_message.signature, 1, sizeof(boot_message.signature), fp_sig);
419 | }
420 |
421 | if (second_stage_txbuf)
422 | free(second_stage_txbuf);
423 | second_stage_txbuf = NULL;
424 |
425 | second_stage_txbuf = (uint8_t *) malloc(boot_message.length);
426 | if (second_stage_txbuf == NULL)
427 | {
428 | printf("Failed to allocate memory\n");
429 | return -1;
430 | }
431 |
432 | size = fread(second_stage_txbuf, 1, boot_message.length, fp);
433 | if(size != boot_message.length)
434 | {
435 | printf("Failed to read second stage\n");
436 | return -1;
437 | }
438 |
439 | return 0;
440 | }
441 |
442 | int second_stage_boot(libusb_device_handle *usb_device)
443 | {
444 | int size, retcode = 0;
445 |
446 | size = ep_write(&boot_message, sizeof(boot_message), usb_device);
447 | if (size != sizeof(boot_message))
448 | {
449 | printf("Failed to write correct length, returned %d\n", size);
450 | return -1;
451 | }
452 |
453 | if(verbose) printf("Writing %d bytes\n", boot_message.length);
454 | size = ep_write(second_stage_txbuf, boot_message.length, usb_device);
455 | if (size != boot_message.length)
456 | {
457 | printf("Failed to read correct length, returned %d\n", size);
458 | return -1;
459 | }
460 |
461 | sleep(1);
462 | size = ep_read((unsigned char *)&retcode, sizeof(retcode), usb_device);
463 |
464 | if (size > 0 && retcode == 0)
465 | {
466 | printf("Successful read %d bytes \n", size);
467 | }
468 | else
469 | {
470 | printf("Failed : 0x%x\n", retcode);
471 | }
472 |
473 | return retcode;
474 |
475 | }
476 |
477 |
478 | FILE * check_file(const char * dir, const char *fname, int use_fmem)
479 | {
480 | FILE * fp = NULL;
481 | char path[256];
482 |
483 | // Prevent USB device from requesting files in parent directories
484 | if(strstr(fname, ".."))
485 | {
486 | printf("Denying request for filename containing .. to prevent path traversal\n");
487 | return NULL;
488 | }
489 |
490 | if(dir)
491 | {
492 | if(overlay && (pathname[0] != 0) &&
493 | (strcmp(fname, "bootcode4.bin") != 0) &&
494 | (strcmp(fname, "bootcode.bin") != 0))
495 | {
496 | strcpy(path, dir);
497 | strcat(path, "/");
498 | strcat(path, pathname);
499 | strcat(path, "/");
500 | strcat(path, fname);
501 | fp = fopen(path, "rb");
502 | if (fp)
503 | printf("Loading: %s\n", path);
504 | memset(path, 0, sizeof(path));
505 | }
506 |
507 | if (fp == NULL)
508 | {
509 | strcpy(path, dir);
510 | strcat(path, "/");
511 | strcat(path, fname);
512 | fp = fopen(path, "rb");
513 | if (fp)
514 | printf("Loading: %s\n", path);
515 | }
516 | }
517 |
518 | // Failover to fmem unless use_fmem is zero in which case this function
519 | // is being used to check if a file exists.
520 | if(fp == NULL && use_fmem)
521 | {
522 | if (bcm2711)
523 | {
524 | if(strcmp(fname, "bootcode4.bin") == 0)
525 | fp = fmemopen(msd_bootcode4_bin, msd_bootcode4_bin_len, "rb");
526 | else if(strcmp(fname, "start4.elf") == 0)
527 | fp = fmemopen(msd_start4_elf, msd_start4_elf_len, "rb");
528 | }
529 | else
530 | {
531 | if(strcmp(fname, "bootcode.bin") == 0)
532 | fp = fmemopen(msd_bootcode_bin, msd_bootcode_bin_len, "rb");
533 | else if(strcmp(fname, "start.elf") == 0)
534 | fp = fmemopen(msd_start_elf, msd_start_elf_len, "rb");
535 | }
536 | if (fp)
537 | printf("Loading embedded: %s\n", fname);
538 | }
539 |
540 | return fp;
541 | }
542 |
543 | int file_server(libusb_device_handle * usb_device)
544 | {
545 | int going = 1;
546 | struct file_message {
547 | int command;
548 | char fname[256];
549 | } message;
550 | static FILE * fp = NULL;
551 |
552 | while(going)
553 | {
554 | char message_name[][20] = {"GetFileSize", "ReadFile", "Done"};
555 | int i = ep_read(&message, sizeof(message), usb_device);
556 | if(i < 0)
557 | {
558 | // Drop out if the device goes away
559 | if(i == LIBUSB_ERROR_NO_DEVICE || i == LIBUSB_ERROR_IO)
560 | break;
561 | sleep(1);
562 | continue;
563 | }
564 | if(verbose) printf("Received message %s: %s\n", message_name[message.command], message.fname);
565 |
566 | // Done can also just be null filename
567 | if(strlen(message.fname) == 0)
568 | {
569 | ep_write(NULL, 0, usb_device);
570 | break;
571 | }
572 |
573 | switch(message.command)
574 | {
575 | case 0: // Get file size
576 | if(fp)
577 | fclose(fp);
578 | fp = check_file(directory, message.fname, 1);
579 | if(strlen(message.fname) && fp != NULL)
580 | {
581 | int file_size;
582 |
583 | fseek(fp, 0, SEEK_END);
584 | file_size = ftell(fp);
585 | fseek(fp, 0, SEEK_SET);
586 |
587 | if(verbose) printf("File size = %d bytes\n", file_size);
588 |
589 | int sz = libusb_control_transfer(usb_device, LIBUSB_REQUEST_TYPE_VENDOR, 0,
590 | file_size & 0xffff, file_size >> 16, NULL, 0, 1000);
591 |
592 | if(sz < 0)
593 | return -1;
594 | }
595 | else
596 | {
597 | ep_write(NULL, 0, usb_device);
598 | if(verbose) printf("Cannot open file %s\n", message.fname);
599 | break;
600 | }
601 | break;
602 |
603 | case 1: // Read file
604 | if(fp != NULL)
605 | {
606 | int file_size;
607 | void *buf;
608 |
609 | printf("File read: %s\n", message.fname);
610 |
611 | fseek(fp, 0, SEEK_END);
612 | file_size = ftell(fp);
613 | fseek(fp, 0, SEEK_SET);
614 |
615 | buf = malloc(file_size);
616 | if(buf == NULL)
617 | {
618 | printf("Failed to allocate buffer for file %s\n", message.fname);
619 | return -1;
620 | }
621 | int read = fread(buf, 1, file_size, fp);
622 | if(read != file_size)
623 | {
624 | printf("Failed to read from input file\n");
625 | free(buf);
626 | return -1;
627 | }
628 |
629 | int sz = ep_write(buf, file_size, usb_device);
630 |
631 | free(buf);
632 | fclose(fp);
633 | fp = NULL;
634 |
635 | if(sz != file_size)
636 | {
637 | printf("Failed to write complete file to USB device\n");
638 | return -1;
639 | }
640 | }
641 | else
642 | {
643 | if(verbose) printf("No file %s found\n", message.fname);
644 | ep_write(NULL, 0, usb_device);
645 | }
646 | break;
647 |
648 | case 2: // Done, exit file server
649 | going = 0;
650 | break;
651 |
652 | default:
653 | printf("Unknown message\n");
654 | return -1;
655 | }
656 | }
657 |
658 | printf("Second stage boot server done\n");
659 | return 0;
660 | }
661 |
662 | int main(int argc, char *argv[])
663 | {
664 | libusb_context *ctx;
665 | libusb_device_handle *usb_device;
666 | struct libusb_device_descriptor desc;
667 | struct libusb_config_descriptor *config;
668 |
669 | get_options(argc, argv);
670 |
671 | printf("RPIBOOT: build-date %s version %s %s\n", __DATE__, PKG_VER, GIT_VER);
672 |
673 | // flush immediately
674 | setbuf(stdout, NULL);
675 |
676 | // If the boot directory is specified then check that it contains bootcode files.
677 | if (directory)
678 | {
679 | FILE *f, *f4;
680 |
681 | if (verbose)
682 | printf("Boot directory '%s'\n", directory);
683 |
684 | f = check_file(directory, "bootcode.bin", 0);
685 | f4 = check_file(directory, "bootcode4.bin", 0);
686 | if (!f && !f4)
687 | {
688 | fprintf(stderr, "No 'bootcode' files found in '%s'\n", directory);
689 | usage(1);
690 | }
691 |
692 | if (f)
693 | fclose(f);
694 | if (f4)
695 | fclose(f4);
696 |
697 | if (signed_boot)
698 | {
699 | f = check_file(directory, "bootsig.bin", 0);
700 | if (!f)
701 | {
702 | fprintf(stderr, "Unable to open 'bootsig.bin' from %s\n", directory);
703 | usage(1);
704 | }
705 | fclose(f);
706 | }
707 | }
708 |
709 | int ret = libusb_init(&ctx);
710 | if (ret)
711 | {
712 | printf("Failed to initialise libUSB\n");
713 | exit(-1);
714 | }
715 |
716 | #if LIBUSBX_API_VERSION < 0x01000106
717 | libusb_set_debug(ctx, verbose ? LIBUSB_LOG_LEVEL_WARNING : 0);
718 | #else
719 | libusb_set_option(
720 | ctx,
721 | LIBUSB_OPTION_LOG_LEVEL,
722 | verbose ? verbose == 2 ? LIBUSB_LOG_LEVEL_INFO : LIBUSB_LOG_LEVEL_WARNING : 0
723 | );
724 | #endif
725 |
726 | do
727 | {
728 | int last_serial = -1;
729 |
730 | printf("Waiting for BCM2835/6/7/2711...\n");
731 |
732 | // Wait for a device to get plugged in
733 | do
734 | {
735 | ret = Initialize_Device(&ctx, &usb_device);
736 | if(ret == 0)
737 | {
738 | libusb_get_device_descriptor(libusb_get_device(usb_device), &desc);
739 |
740 | if(verbose)
741 | printf("Found serial number %d\n", desc.iSerialNumber);
742 |
743 | // Make sure we've re-enumerated since the last time
744 | if(desc.iSerialNumber == last_serial)
745 | {
746 | ret = -1;
747 | libusb_close(usb_device);
748 | }
749 |
750 | libusb_get_active_config_descriptor(libusb_get_device(usb_device), &config);
751 | }
752 |
753 | if (ret)
754 | {
755 | usleep(delay);
756 | }
757 | }
758 | while (ret);
759 |
760 | last_serial = desc.iSerialNumber;
761 | if(desc.iSerialNumber == 0 || desc.iSerialNumber == 3)
762 | {
763 | printf("Sending bootcode.bin\n");
764 | second_stage_boot(usb_device);
765 | }
766 | else
767 | {
768 | printf("Second stage boot server\n");
769 | file_server(usb_device);
770 | }
771 |
772 | libusb_close(usb_device);
773 | sleep(1);
774 | }
775 | while(loop || desc.iSerialNumber == 0 || desc.iSerialNumber == 3);
776 |
777 | libusb_exit(ctx);
778 |
779 | return 0;
780 | }
781 |
782 |
--------------------------------------------------------------------------------
/mass-storage-gadget/.gitignore:
--------------------------------------------------------------------------------
1 | *.h
2 | boot.sig
3 |
--------------------------------------------------------------------------------
/mass-storage-gadget/README.md:
--------------------------------------------------------------------------------
1 | # USB mass-storage drivers for Compute Module 4
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 Compute Module block devices.
9 |
10 | ## Running
11 | To run load the USB MSD device drivers via RPIBOOT run
12 | ```bash
13 | cd mass-storage-gadget
14 | ../rpiboot -d .
15 |
16 | ```
17 | N.B. This takes a few seconds longer to initialise than the
18 | previous mass storage implementation. However, the write speed
19 | should be much faster now that all of the file-system code
20 | is running on the ARM processors.
21 |
22 | ### Debug
23 | The mass-storage-gadget image automatically enables a UART console for debugging (user `root` empty password).
24 |
25 | ## secure-boot
26 | If secure-boot mode has been locked (via OTP) then both the
27 | bootloader and rpiboot `bootcode4.bin` will only load `boot.img`
28 | files signed with the customer's private key. Therefore, access
29 | to rpiboot mass storage mode is disabled.
30 |
31 | Mass storage mode can be re-enabled by signing a boot image
32 | containing the firmware mass storage drivers.
33 |
34 | N.B. The signed image should normally be kept secure because can
35 | be used on any device signed with the same customer key.
36 |
37 | To sign the mass storage mode boot image run:-
38 | ```bash
39 | KEY_FILE=$HOME/private.pem
40 | ../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}"
41 | ```
42 |
43 | ## Source code
44 | The buildroot configuration and supporting patches is available on
45 | the [mass-storage-gadget](https://github.com/raspberrypi/buildroot/tree/mass-storage-gadget)
46 | branch of the Raspberry Pi [buildroot](https://github.com/raspberrypi/buildroot) repo.
47 |
48 | ### Building
49 | ```bash
50 | git clone --branch mass-storage-gadget git@github.com:raspberrypi/buildroot.git
51 | cd buildroot
52 | make raspberrypicm4io_initrd_defconfig
53 | make
54 | ```
55 |
56 | The output is written to `output/target/images/sdcard.img` and can be copied
57 | to `boot.img`
58 |
--------------------------------------------------------------------------------
/mass-storage-gadget/boot.img:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/usbboot/ec8294ffa46931e5b1b322b04ca26863a98a40cb/mass-storage-gadget/boot.img
--------------------------------------------------------------------------------
/mass-storage-gadget/bootcode4.bin:
--------------------------------------------------------------------------------
1 | ../bootcode4.bin
--------------------------------------------------------------------------------
/mass-storage-gadget/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 |
--------------------------------------------------------------------------------
/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 | To load the files from this directory directly, run:
6 |
7 | ```bash
8 | cd msd
9 | ../rpiboot -d .
10 | ```
11 |
--------------------------------------------------------------------------------
/msd/bootcode.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/usbboot/ec8294ffa46931e5b1b322b04ca26863a98a40cb/msd/bootcode.bin
--------------------------------------------------------------------------------
/msd/bootcode4.bin:
--------------------------------------------------------------------------------
1 | ../bootcode4.bin
--------------------------------------------------------------------------------
/msd/start.elf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/usbboot/ec8294ffa46931e5b1b322b04ca26863a98a40cb/msd/start.elf
--------------------------------------------------------------------------------
/msd/start4.elf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/usbboot/ec8294ffa46931e5b1b322b04ca26863a98a40cb/msd/start4.elf
--------------------------------------------------------------------------------
/recovery.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/usbboot/ec8294ffa46931e5b1b322b04ca26863a98a40cb/recovery.bin
--------------------------------------------------------------------------------
/recovery/README.md:
--------------------------------------------------------------------------------
1 | To update the SPI EEPROM bootloader on a Compute Module 4.
2 |
3 | * Modify the EEPROM configuration 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 |
7 | N.B The `bootcode4.bin` file in this directory is actually the `recovery.bin`
8 | file used on Raspberry Pi 4 bootloader update cards.
9 |
10 | ```bash
11 | cd recovery
12 | ./update-pieeprom.sh
13 | ../rpiboot -d .
14 | ```
15 |
--------------------------------------------------------------------------------
/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 | ../recovery.bin
--------------------------------------------------------------------------------
/recovery/config.txt:
--------------------------------------------------------------------------------
1 | uart_2ndstage=1
2 |
--------------------------------------------------------------------------------
/recovery/pieeprom.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/usbboot/ec8294ffa46931e5b1b322b04ca26863a98a40cb/recovery/pieeprom.bin
--------------------------------------------------------------------------------
/recovery/pieeprom.original.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/usbboot/ec8294ffa46931e5b1b322b04ca26863a98a40cb/recovery/pieeprom.original.bin
--------------------------------------------------------------------------------
/recovery/pieeprom.sig:
--------------------------------------------------------------------------------
1 | 8b9a37cd4c33c650ad3b44dd25963ffa681f63b9cd2cdf204560fe74d436ea84
2 | ts: 1666773225
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/rpi-imager-embedded/README.md:
--------------------------------------------------------------------------------
1 | This directory contains the embedded Raspberry Pi Imager application.
2 | Running RPi Imager on the target device is probably the simplest way
3 | to install the operating system on a Compute Module 4. However, it
4 | does not export the CM4 file-system as a mass-storage device.
5 |
6 | To download the latest version, run:
7 | ```bash
8 | wget https://downloads.raspberrypi.com/net_install/boot.img
9 | ```
10 |
11 | To run:
12 | ```bash
13 | cd rpi-imager-embedded
14 | ../rpiboot -d .
15 | ```
16 |
17 | Make sure that the HDMI display is connected. Once Linux has started
18 | you will need to unplug the micro-USB cable (when prompted) and connect
19 | a keyboard and mouse.
20 |
--------------------------------------------------------------------------------
/rpi-imager-embedded/boot.img:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/usbboot/ec8294ffa46931e5b1b322b04ca26863a98a40cb/rpi-imager-embedded/boot.img
--------------------------------------------------------------------------------
/rpi-imager-embedded/bootcode4.bin:
--------------------------------------------------------------------------------
1 | ../bootcode4.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 | Secure boot is a mechanism for verifying the integrity of the kernel+initramfs and
5 | other files required during boot by storing them in a signed ramdisk image.
6 | These files include the GPU firmware (start.elf etc), kernel, initrd, Device Tree
7 | and overlays.
8 |
9 | Secure boot does not depend on a particular OS, nor does it provide services
10 | to the OS after the Kernel + initramfs has started.
11 |
12 | N.B. The memory for the secure ramdisk is reclaimed as soon as the CPU is started.
13 |
14 | ## Example OS
15 | This example includes a simple buildroot image for Compute Module 4 in order
16 | to demonstrate secure boot on a simple but functional OS.
17 |
18 | The example does NOT modify the OTP or make other permanent changes to the system;
19 | the code signing can be disabled by reflashing the default bootloader EEPROM.
20 |
21 | Please see the top level [README](../Readme.md#building) and [secure-boot-recovery/README](../secure-boot-recovery/README.md) guides for
22 | instructions about how to permanently enable secure-boot by programming OTP.
23 |
24 | ### Requirements for running this example
25 | * A Raspberry Pi Compute Module 4
26 | * Micro USB cable for `rpiboot` connection
27 | * USB serial cable (for debug logs)
28 | * Linux, WSL or Cygwin (Windows 10)
29 | * OpenSSL
30 | * Python3
31 | * Python `cryptodomex`
32 |
33 | ```bash
34 | python3 -m pip install pycryptodomex
35 | # or
36 | pip install pycryptodomex
37 | ```
38 |
39 | ### Clean configuration
40 | Before starting it's advisable to create a fresh clone of the `usbboot` repo
41 | to ensure that there are no stale configuration files.
42 |
43 | ```bash
44 | git clone https://github.com/raspberrypi/usbboot secure-boot
45 | cd secure-boot
46 | make
47 | ```
48 | See the top-level [README](../Readme.md) for build instructions.
49 |
50 | ### Hardware setup for `rpiboot` mode
51 | Prepare the Compute Module for `rpiboot` mode:
52 |
53 | * Set the `nRPIBOOT` jumper which is labelled `Fit jumper to disable eMMC Boot' on the Compute Module 4 IO board.
54 | * Connect the micro USB cable to the `USB slave` port on the CM4 IO board.
55 | * Power cycle the CM4 IO board.
56 | * 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.
57 |
58 | ### Reset the Compute Module EEPROM
59 | Enable `rpiboot` mode and flash the latest EEPROM image:
60 | ```bash
61 | ./rpiboot -d recovery
62 | ```
63 |
64 | ### Boot the example image
65 | Enable `rpiboot` mode and load the OS via `rpiboot` without enabling code-signing:
66 | ```bash
67 | ./rpiboot -d secure-boot-example
68 | ```
69 | The OS should load and show activity on the boot UART and HDMI console.
70 |
71 | ### Generate a signing key
72 | Secure boot requires a 2048 bit RSA private key. You can either use a pre-existing
73 | key or generate an specific key for this example. The `KEY_FILE` environment variable
74 | used in the following instructions must contain the absolute path to the RSA private key in
75 | PEM format.
76 |
77 | ```bash
78 | openssl genrsa 2048 > private.pem
79 | export KEY_FILE=$(pwd)/private.pem
80 | ```
81 |
82 | **In a production environment it's essential that this key file is stored privately and securely.**
83 |
84 | ### Update the EEPROM to require signed OS images
85 | Enable `rpiboot` mode and flash the bootloader EEPROM with updated setting enables code signing.
86 |
87 | The `boot.conf` file sets the `SIGNED_BOOT` property `1` which instructs the bootloader to only
88 | load files (firmware, kernel, overlays etc) from `boot.img` instead of the normal boot partition and verify the `boot.img` signature `boot.sig` using the public key in the EEPROM.
89 |
90 | The `update-pieeprom.sh` generates the signed `pieeprom.bin` image.
91 |
92 | ```bash
93 | cd secure-boot-recovery
94 | # Generate the signed EEPROM image.
95 | ../tools/update-pieeprom.sh -k "${KEY_FILE}"
96 | cd ..
97 | ./rpiboot -d secure-boot-recovery
98 | ```
99 |
100 | At this stage OTP has not been modified and the signed image requirement can be reverted by flashing a default, unsigned image.
101 | However, once the [OTP secure-boot flags](../secure-boot-recovery/README.md#locking-secure-boot-mode) are set then `SIGNED_BOOT` is permanently enabled and cannot be overridden via the EEPROM config.
102 |
103 |
104 | ### Update the signature for the example OS image
105 | ```bash
106 | cd secure-boot-example
107 | ../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}"
108 | cd ..
109 | ```
110 |
111 | ### Launch the signed OS image
112 | Enable `rpiboot` mode and run the example OS as before. However, if the
113 | `boot.sig` signature does not match `boot.img`, the bootloader will refuse to
114 | load the OS.
115 |
116 | ```bash
117 | ./rpiboot -d secure-boot-example
118 | ```
119 |
120 | This example OS image is minimal Linux ramdisk image. Login as `root` with the empty password.
121 |
122 | #### Disk encryption example
123 | Example script which uses a device-specific private key to create/mount an encrypted file-system.
124 |
125 | Generating a 256-bit random key for test purposes.
126 | ```bash
127 | export KEY_FILE=$(mktemp -d)/key.bin
128 | openssl rand -hex 32 | xxd -rp > ${KEY_FILE}
129 | ```
130 |
131 | Using [rpi-otp-private-key](../tools/rpi-otp-private-key) to extract the device private key (if programmed).
132 | ```bash
133 | export KEY_FILE=$(mktemp -d)/key.bin
134 | rpi-otp-private-key -b > "${KEY_FILE}"
135 | ```
136 |
137 | Creating an encrypted disk on a specified block device.
138 | ```bash
139 | export BLK_DEV=/dev/mmcblk0p3
140 | cryptsetup luksFormat --key-file="${KEY_FILE}" --key-size=256 --type=luks2 ${BLK_DEV}
141 |
142 | cryptsetup luksOpen ${BLK_DEV} encrypted-disk --key-file="${KEY_FILE}"
143 | mkfs /dev/mapper/encrypted-disk
144 | mkdir -p /mnt/application-data
145 | mount /dev/mapper/encrypted-disk /mnt/application-data
146 | rm "${KEY_FILE}"
147 | ```
148 |
149 | ### Mount the CM4 SD/EMMC after enabling secure-boot
150 | Now that `SIGNED_BOOT` is enabled the bootloader will only load images signed with private key generated earlier.
151 | To boot the Compute Module in mass storage mode a signed version of this code must be generated.
152 |
153 | **This signed image should not be made available for download because it gives access to the EMMC as a block device.**
154 |
155 |
156 | #### Sign the mass storage firmware image
157 | 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.
158 | ```bash
159 | cd secure-boot-msd
160 | ../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}"
161 | cd ..
162 | ```
163 |
164 | #### Enable MSD mode
165 | A new mass storage device should now be visible on the host OS. On Linux check `dmesg` for something like '/dev/sda'.
166 | ```bash
167 | ./rpiboot -d secure-boot-msd
168 | ```
169 |
170 | ### Loading `boot.img` from SD/EMMC
171 | 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.
172 |
173 | For example:
174 |
175 | * Boot the CM4 in MSD mode as explained in the previous step.
176 | * Copy the `boot.img` and `boot.sig` files from the `secure-boot-example` stage to the mass storage drive: No other files are required.
177 | * Remove the `nRPIBOOT` jumper.
178 | * Power cycle the CM4 IO board.
179 | * The system should now boot into the OS.
180 |
181 | ### Modifying / rebuilding `boot.img`
182 | The secure-boot example image can be rebuilt and modified using buildroot. See [raspberrypi-signed-boot](https://github.com/raspberrypi/buildroot/blob/raspberrypi-signed-boot/README.md) buildroot configuration.
183 |
--------------------------------------------------------------------------------
/secure-boot-example/boot.img:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/usbboot/ec8294ffa46931e5b1b322b04ca26863a98a40cb/secure-boot-example/boot.img
--------------------------------------------------------------------------------
/secure-boot-example/boot.sig:
--------------------------------------------------------------------------------
1 | 946e19fa1396e99aaee32259deeaeb130396f2fc32b240d0a690bee7e25c5a0a
2 | ts: 1668104408
3 | rsa2048: afe8c8a8b150d8d317858c24175a942ca10ee47df9e3f546a0fa1bee9fb0e276036c1ed03151de15b048f782e98800a0c17ccda4a7605be9c8585e88c007fd0803545e138d3b537afc7b85696b330617322605a91fa066ae9cdcbd6946ef14be9f00ef0539dcd490af0a87c91e02c82e6fde122fdf3f904c09a6fd9cfbc5c31f0ab4fd5f5aa584c1c9f7a3d46dc298ad164a86c853eaedc3d8822f9a0102e7d4929579d8c90e000dba5b69ca2d52d18e2a5239d86a178ffd07521821dda626a517c7368ad12d44c42df22e7fe589d5b9cd0f49ecd4367e270f2891043fd313ea0bcfcda28ef87f8e2a0ada7db32ae04d2aa44dc03cf9ce6e83dda339deba8241
4 |
--------------------------------------------------------------------------------
/secure-boot-example/bootcode4.bin:
--------------------------------------------------------------------------------
1 | ../bootcode4.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-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-msd/.gitignore:
--------------------------------------------------------------------------------
1 | *.h
2 | boot.sig
3 |
--------------------------------------------------------------------------------
/secure-boot-msd/README.md:
--------------------------------------------------------------------------------
1 | # USB MSD device mode drivers for signed-boot
2 |
3 | If secure-boot mode has been locked (via OTP) then both the
4 | bootloader and rpiboot `bootcode4.bin` will only load `boot.img`
5 | files signed with the customer's private key. Therefore, access
6 | to rpiboot mass storage mode is disabled.
7 |
8 | Mass storage mode can be re-enabled by signing a boot image
9 | containing the firmware mass storage drivers.
10 |
11 | N.B. The signed image should normally be kept secure because can
12 | be used on any device signed with the same customer key.
13 |
14 | To sign the mass storage mode boot image run:-
15 | ```bash
16 | KEY_FILE=$HOME/private.pem
17 | ../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}"
18 | ```
19 |
20 | To run load the USB MSD device drivers via RPIBOOT run
21 | ```bash
22 | ../rpiboot -d .
23 | ```
24 |
--------------------------------------------------------------------------------
/secure-boot-msd/boot.img:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/usbboot/ec8294ffa46931e5b1b322b04ca26863a98a40cb/secure-boot-msd/boot.img
--------------------------------------------------------------------------------
/secure-boot-msd/bootcode4.bin:
--------------------------------------------------------------------------------
1 | ../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 |
--------------------------------------------------------------------------------
/secure-boot-recovery/README.md:
--------------------------------------------------------------------------------
1 | # Raspberry Pi 4 - secure boot
2 |
3 | This directory contains the beta bootcode4.bin (recovery.bin) and pieeprom-2021-05-19.bin
4 | bootloader release. Older bootloader and recovery.bin releases do not 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 | ### Step 1 - Erase the EEPROM
14 | In order to avoid this OTP configuration being accidentally set on Pi 4B / Pi 400
15 | this option can only be set via RPIBOOT. To force RPIBOOT on a Pi 4B / Pi 400
16 | erase the SPI EEPROM.
17 |
18 | Copy recovery.bin to a blank FAT32 formatted SD card with the following `config.txt` file.
19 | Then insert the SD card and boot the Pi and wait at least 10 seconds for the green
20 | LED to flash rapidly.
21 | ```
22 | erase_eeprom=1
23 | ```
24 |
25 | ### Step 2 - Select the nRPIBOOT GPIO
26 | Then use rpiboot config.txt specify the GPIO to use for nRPIBOOT. For example:
27 | ```
28 | program_rpiboot_gpio=8
29 | ```
30 |
31 | The OTP setting for nRPIBOOT will then be set in the next steps when the
32 | EEPROM / secure-boot configuration is programmed.
33 |
34 | ## Optional. Specify the private key file in an environment variable.
35 | Alternatively, specify the path when invoking the helper scripts.
36 | ```bash
37 | export KEY_FILE="${HOME}/private.pem"
38 | ```
39 |
40 | ## Optional. Customize the EEPROM config.
41 | Custom with the desired bootloader settings.
42 | See: [Bootloader configuration](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711_bootloader_config.md)
43 |
44 | Setting `SIGNED_BOOT=1` enables signed-boot mode so that the bootloader will only
45 | boot.img files signed with the specified RSA key. Since this is an EEPROM config
46 | option secure-boot can be tested and reverted via `RPIBOOT` at this stage.
47 |
48 | ## Generate the signed bootloader image
49 | ```bash
50 | cd secure-boot-recovery
51 | ../tools/update-pieeprom.sh -k "${KEY_FILE}"
52 | ```
53 |
54 | `pieeprom.bin` can then be flashed to the bootloader EEPROM via `rpiboot`.
55 |
56 | ## Program the EEPROM image using rpiboot
57 | * Power off CM4
58 | * Set nRPIBOOT jumper and remove EEPROM WP protection
59 | ```bash
60 | cd secure-boot-recovery
61 | ../rpiboot -d .
62 | ```
63 | * Power ON CM4
64 |
65 | ## Locking secure-boot mode
66 | After verifying that the signed OS image boots successfully the system
67 | can be locked into secure-boot mode. This writes the hash of the
68 | customer public key to "one time programmable" (OTP) bits. From then
69 | onwards:
70 |
71 | * The bootloader will only load OS images signed with the customer private key.
72 | * The EEPROM configuration file must be signed with the customer private key.
73 | * It is not possible to install an old version of the bootloader that does
74 | support secure boot.
75 | * This option requires EEPROM version 2022-01-06 or newer.
76 | * BETA bootloader releases are not signed with the ROM secure boot key and will
77 | not boot on a system where `revoke_devkey` has been set.
78 |
79 | **WARNING: Modifications to OTP are irreversible. Once `revoke_devkey` has been set it is not possible to unlock secure-boot mode or use a different private key.**
80 |
81 | To enable this edit the `config.txt` file in this directory and set
82 | `program_pubkey=1`
83 |
84 | * `program_pubkey` - If 1, write the hash of the customer's public key to OTP.
85 | * `revoke_devkey` - If 1, revoke the ROM bootloader development key which
86 | requires secure-boot mode and prevents downgrades to bootloader versions that
87 | don't support secure boot.
88 |
89 | ## Disabling VideoCore JTAG
90 |
91 | VideoCore JTAG may be permentantly disabled by setting `program_jtag_lock` in
92 | `config.txt`. This option has no effect unless `revoke_revkey=1` is set and
93 | the EEPROM and customer OTP key were programmed successfully.
94 |
95 | See [config.txt](config.txt)
96 |
--------------------------------------------------------------------------------
/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 | # SD, USB-MSD, BCM-USB-MSD, Network
8 | BOOT_ORDER=0xf2541
9 |
10 | # Disable self-update mode
11 | ENABLE_SELF_UPDATE=0
12 |
13 | # Select signed-boot mode in the EEPROM. This can be used to during development
14 | # to test the signed boot image. Once secure boot is enabled via OTP this setting
15 | # has no effect i.e. it is always 1.
16 | SIGNED_BOOT=1
17 |
18 |
--------------------------------------------------------------------------------
/secure-boot-recovery/bootcode4.bin:
--------------------------------------------------------------------------------
1 | ../recovery.bin
--------------------------------------------------------------------------------
/secure-boot-recovery/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.org/documentation/hardware/raspberrypi/bcm2711_bootloader_config.md#eeprom_write_protect
5 |
6 | eeprom_write_protect=1
7 |
8 | # Uncomment to write to enable secure-boot by writing. This
9 | # locks the device to the public key in the EEPROM by storing the
10 | # sha256 hash of the public key in OTP.
11 | #
12 | # This option also prevents the ROM from loading recovery.bin from SD/EMMC
13 | # which means that the bootloader can only be updated via RPIBOOT or self-update.
14 | #
15 | # Uncomment program_pubkey=1 to enable this
16 | # WARNING: THIS OPTION MODIFIES THE BCM2711 CHIP AND IS IRREVERSIBLE.
17 |
18 | #program_pubkey=1
19 |
20 | # Uncomment to revoke the ROM development key via OTP preventing older
21 | # bootloader or recovery.bin releases from running on this Pi
22 | # WARNING: THIS OPTION MODIFIES THE BCM2711 CHIP AND IS IRREVERSIBLE.
23 | #
24 | # DO NOT SET THIS OPTION UNTIL THE BOOTLOADER IS SIGNED WITH THE SECURE
25 | # BOOT KEY. IT WILL PREVENT THE PI FROM BOOTING.
26 | #revoke_devkey=1
27 |
28 | # Pi 4B and Pi400 do not have a dedicated RPIBOOT jumper so a different GPIO
29 | # must be used to enable RPIBOOT if pulled low. The options are 2,4,5,6,7,8.
30 | #
31 | # This option has no effect on CM4.
32 |
33 | # WARNING: THIS OPTION MODIFIES THE BCM2711 CHIP AND IS IRREVERSIBLE.
34 | #program_rpiboot_gpio=8
35 |
36 | # Permanently disable VideoCore JTAG access.
37 | # Warning: This option limits the ability to do failure analysis on
38 | # boards returned to resellers or Raspberry Pi Trading Ltd.
39 | #program_jtag_lock=1
40 |
--------------------------------------------------------------------------------
/secure-boot-recovery/pieeprom.original.bin:
--------------------------------------------------------------------------------
1 | ../recovery/pieeprom.original.bin
--------------------------------------------------------------------------------
/tools/make-boot-image:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | TMP_DIR=""
6 | TMP_IMAGE=""
7 | IMAGE_SIZE=${IMAGE_SIZE:-0}
8 | MEGABYTE=$((1024 * 1024))
9 | BOOT_MOUNT=""
10 | LOOP=""
11 | NAME="$(basename "$0")"
12 |
13 | # Define these environment variables to override mkfs options
14 | SECTOR_SIZE=${SECTOR_SIZE:-512}
15 | ROOT_DIR_ENTRIES=${ROOT_DIR_ENTRIES:-256}
16 |
17 | # Add 64k to the size calculation to reserve some space for the FAT,
18 | # directory entries and rounding up files to cluster sizes.
19 | FAT_OVERHEAD=${FAT_OVERHEAD:-64}
20 |
21 | cleanup() {
22 | unmount_image
23 |
24 | if [ -d "${TMP_DIR}" ]; then
25 | rm -rf "${TMP_DIR}"
26 | fi
27 | }
28 |
29 | die() {
30 | echo "$@" >&2
31 | exit 1
32 | }
33 |
34 | createfs() {
35 | size_mb="$1"
36 | image="$2"
37 |
38 | volume_label="BOOT"
39 | if [ -n "${SECTORS_PER_CLUSTER}" ]; then
40 | SECTORS_PER_CLUSTER="-s ${SECTORS_PER_CLUSTER}"
41 | fi
42 |
43 | if [ -n "${FAT_SIZE}" ]; then
44 | fat_size="-F ${FAT_SIZE}"
45 | fi
46 |
47 | sectors=$((size_mb * MEGABYTE / SECTOR_SIZE))
48 | sectors=$((sectors / 2))
49 | /sbin/mkfs.fat -C -f 1 \
50 | ${SECTORS_PER_CLUSTER} -n "${volume_label}" \
51 | ${fat_size} \
52 | -S "${SECTOR_SIZE}" -r "${ROOT_DIR_ENTRIES}" "${image}" ${sectors} || \
53 | die "Failed to create FAT filesystem"
54 | }
55 |
56 | mountfs() {
57 | image="$1"
58 |
59 | LOOP=$(losetup -f)
60 | losetup "${LOOP}" "${image}"
61 | [ -e "${LOOP}" ] || die "Failed to create loop device ${LOOP}"
62 |
63 | BOOT_MOUNT=$(mktemp -d)
64 | mount "${LOOP}" "${BOOT_MOUNT}"
65 | [ -d "${BOOT_MOUNT}" ] || die "Failed to mount bootfs @ ${BOOT_MOUNT}"
66 |
67 | echo "Mounted ${LOOP} @ ${BOOT_MOUNT}"
68 | }
69 |
70 | unmount_image() {
71 | if [ -d "${BOOT_MOUNT}" ]; then
72 | umount "${BOOT_MOUNT}" > /dev/null 2>&1 || true
73 | rmdir "${BOOT_MOUNT}"
74 | BOOT_MOUNT=""
75 | fi
76 |
77 | if [ -n "${LOOP}" ]; then
78 | losetup -d "${LOOP}"
79 | LOOP=""
80 | fi
81 | }
82 |
83 |
84 | createstaging() {
85 | source_dir="$1"
86 | staging="$2"
87 | board="$3"
88 |
89 | mkdir -p "${staging}" || die "Failed to create ${staging}"
90 | cp -r "${source_dir}/"* "${staging}"
91 |
92 | # Remove files for previous hardware version
93 | if [ "${board}" = "pi4" ] || [ "${board}" = "pi400" ] || [ "${board}" = "cm4" ]; then
94 | (
95 | cd "${staging}"
96 | rm -f kernel.img kernel7.img bootcode.bin
97 | rm -f start.elf fixup.dat start_cd.elf fixup_cd.dat start_db.elf fixup_db.dat start_x.elf fixup_x.dat
98 | rm -f start4cd.elf fixup4cd.dat
99 | rm -f start4db.elf fixup4db.dat
100 | rm -f bcm2708* bcm2709* bcm2710*
101 | rm -f bootcode.bin
102 | )
103 | fi
104 |
105 | if [ "${ARCH}" = 32 ]; then
106 | rm -f "${staging}/kernel8.img"
107 | elif [ "${ARCH}" = 64 ]; then
108 | rm -f "${staging}/kernel7l.img"
109 | fi
110 |
111 | if [ "${board}" = pi400 ]; then
112 | rm -f "${staging}/start4x.elf"
113 | rm -f "${staging}/fixup4x.dat"
114 | fi
115 | # Estimate the size of the image in KBs
116 | content="${TMP_DIR}/content.tar"
117 | echo "$(cd "${staging}"; ls -R)"
118 | tar -cf "${content}" "${staging}" > /dev/null 2>&1
119 | if [ "${IMAGE_SIZE}" = 0 ]; then
120 | IMAGE_SIZE=$(stat --printf "%s" "${content}")
121 | IMAGE_SIZE=$(((IMAGE_SIZE + 1023) / 1024))
122 | rm -f "${content}"
123 |
124 | # Add a little padding for FAT etc and convert to megabytes
125 | IMAGE_SIZE=$((IMAGE_SIZE + FAT_OVERHEAD))
126 | IMAGE_SIZE=$(((IMAGE_SIZE + 1023) / 1024))
127 | fi
128 |
129 | echo "Using IMAGE_SIZE of ${IMAGE_SIZE}"
130 |
131 | if [ "${IMAGE_SIZE}" -gt 20 ]; then
132 | echo "Warning: Large image size detected. Try removing unused files."
133 | fi
134 | }
135 |
136 | checkDependencies() {
137 | if [ ! -f /sbin/mkfs.fat ]; then
138 | die "mkfs.fat is required. Run this script on Linux"
139 | fi
140 | }
141 |
142 | usage() {
143 | cat <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 |
--------------------------------------------------------------------------------
/tools/rpi-eeprom-digest:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Helper script to generate .sig files for use with the Raspberry Pi bootloader.
4 |
5 | # This has been implemented in a separate script in order to have avoid having
6 | # a hard dependency on OpenSSL.
7 |
8 | set -e
9 |
10 | OPENSSL=${OPENSSL:-openssl}
11 |
12 | die() {
13 | echo "$@" >&2
14 | exit 1
15 | }
16 |
17 | TMP_DIR=""
18 | cleanup() {
19 | if [ -d "${TMP_DIR}" ]; then
20 | rm -rf "${TMP_DIR}"
21 | fi
22 | }
23 |
24 | checkDependencies() {
25 | if ! command -v sha256sum > /dev/null; then
26 | die "sha256sum not found. Try installing the coreutilities package."
27 | fi
28 |
29 | if [ -n "${KEY}" ] || [ "${VERIFY}" = 1 ]; then
30 | if ! command -v openssl > /dev/null; then
31 | die "openssl not found. Try installing the openssl package."
32 | fi
33 |
34 | if ! command -v xxd > /dev/null; then
35 | die "xxd not found. Try installing the xxd package."
36 | fi
37 | fi
38 | }
39 |
40 | usage() {
41 | cat < "${OUTPUT}"
80 |
81 | # Include the update-timestamp
82 | echo "ts: $(date -u +%s)" >> "${OUTPUT}"
83 |
84 | if [ -n "${KEY}" ]; then
85 | [ -f "${KEY}" ] || die "RSA private \"${KEY}\" not found"
86 | "${OPENSSL}" dgst -sign "${KEY}" -keyform PEM -sha256 -out "${SIG_TMP}" "${IMAGE}"
87 | echo "rsa2048: $(xxd -c 4096 -p < "${SIG_TMP}")" >> "${OUTPUT}"
88 | fi
89 | }
90 |
91 | verifySig() {
92 | TMP_DIR=$(mktemp -d)
93 | sig_file="${1}"
94 | [ -f "${sig_file}" ] || die "Signature file ${sig_file} not found"
95 | sig_hex="$(grep rsa2048 "${sig_file}" | cut -f 2 -d ' ')"
96 | [ -n "${sig_hex}" ] || die "No RSA signature in ${sig_file}"
97 |
98 | echo ${sig_hex} | xxd -c 4096 -p -r > "${TMP_DIR}/sig.bin"
99 | "${OPENSSL}" dgst -verify "${KEY}" -signature "${TMP_DIR}/sig.bin" "${IMAGE}" || die "${IMAGE} not verified"
100 | }
101 |
102 | OUTPUT=""
103 | VERIFY=0
104 | while getopts i:k:ho:v: option; do
105 | case "${option}" in
106 | i) IMAGE="${OPTARG}"
107 | ;;
108 | k) KEY="${OPTARG}"
109 | ;;
110 | o) OUTPUT="${OPTARG}"
111 | ;;
112 | v) SIGNATURE="${OPTARG}"
113 | VERIFY=1
114 | ;;
115 | h) usage
116 | ;;
117 | *) echo "Unknown argument \"${option}\""
118 | usage
119 | ;;
120 | esac
121 | done
122 |
123 | trap cleanup EXIT
124 | checkDependencies
125 |
126 | [ -n "${IMAGE}" ] || usage
127 | [ -f "${IMAGE}" ] || die "Source image \"${IMAGE}\" not found"
128 | if [ "${VERIFY}" = 1 ]; then
129 | verifySig "${SIGNATURE}"
130 | else
131 | [ -n "${OUTPUT}" ] || usage
132 | writeSig
133 | fi
134 |
135 |
--------------------------------------------------------------------------------
/tools/rpi-otp-private-key:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | FORCE=0
6 | READ_KEY=""
7 | WRITE_KEY=""
8 | OUTPUT_BINARY=0
9 |
10 | die() {
11 | echo "$@" >&2
12 | exit 1
13 | }
14 |
15 | usage() {
16 | cat <
18 |
19 | No args - reads the current private key from OTP. These values are NOT visible via 'vcgencmd otp_dump'
20 |
21 | -b Output the key in binary format.
22 | -c Reads key and exits with 1 if it is all zeros i.e. not set.
23 | -f Force write (if OTP is non-zero).
24 | The vcmailbox API checks that the new key is equal to the bitwise OR of the current OTP and the new key.
25 | N.B. OTP bits can never change from 1 to 0.
26 | -w Writes the new key to OTP memory.
27 |
28 | is a 64 digit hex number (256 bit) e.g. to generate a 256 random number run 'openssl rand -hex 32'
29 |
30 | IMPORTANT: Raspberry Pi 4 and earlier revisions do not have a hardware secure key store. These OTP rows are visible
31 | to any user in the 'video' group via vcmailbox. Therefore this functionality is only suitable for key
32 | storage if the OS has already been restricted using the signed boot functionality.
33 |
34 | WARNING: Changes to OTP memory are permenant and cannot be undone.
35 | EOF
36 | exit 1
37 | }
38 |
39 | check_key_set() {
40 | read_key
41 | if [ -z "$(echo "${READ_KEY}" | sed s/0//g)" ]; then
42 | return 1
43 | fi
44 | return 0
45 | }
46 |
47 | read_key() {
48 | out=READ_KEY="$(vcmailbox 0x00030081 40 40 0 8 0 0 0 0 0 0 0 0)" || die "Failed to read the current key from OTP"
49 | READ_KEY="$(echo "${out}" | sed 's/0x//g' | awk '{for(i=8;i<16;i++) printf $i; print ""}')"
50 | }
51 |
52 | write_key() {
53 | key="${1}"
54 | # Normalize formatting and check the length
55 | key="$(echo "${key}" | tr 'A-Z' 'a-z')"
56 | key="$(echo "${key}" | sed 's/[^a-f0-9]//g')"
57 | [ "$(echo -n "${key}" | wc -c)" = 64 ] || die "Invalid key parameter"
58 |
59 | count=0
60 | key_params=""
61 | while [ ${count} -lt 8 ]; do
62 | start=$(((count * 8) + 1))
63 | end=$((start + 7))
64 | key_params="${key_params} 0x$(echo -n "${key}" | cut -c${start}-${end})"
65 | count=$((count + 1))
66 | done
67 | vcmailbox 0x38081 40 40 0 8 ${key_params} || die "Failed to write key"
68 | read_key
69 | [ "${READ_KEY}" = "${key}" ] || die "Key readback check failed. ${out}"
70 | }
71 |
72 | while getopts bcfhw: option; do
73 | case "${option}" in
74 | b) OUTPUT_BINARY=1
75 | ;;
76 | c)
77 | if check_key_set; then
78 | exit 0
79 | fi
80 | exit 1
81 | ;;
82 | f) FORCE=1
83 | ;;
84 | h) usage
85 | ;;
86 | w) WRITE_KEY="${OPTARG}"
87 | ;;
88 | *) echo "Unknown argument \"${option}\""
89 | usage
90 | ;;
91 | esac
92 | done
93 |
94 | if [ -n "${WRITE_KEY}" ]; then
95 | if [ "${FORCE}" = 0 ] && check_key_set; then
96 | die "Current key is non-zero. Specify -f to write anyway"
97 | fi
98 | write_key "${WRITE_KEY}"
99 | else
100 | read_key
101 | if [ "${OUTPUT_BINARY}" = 1 ]; then
102 | echo "${READ_KEY}" | xxd -r -p
103 | else
104 | echo "${READ_KEY}"
105 | fi
106 | fi
107 |
--------------------------------------------------------------------------------
/tools/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 | # pieeprom.original.bin - The source EEPROM from rpi-eeprom repo
7 | # boot.conf - The bootloader config file to apply.
8 |
9 | set -e
10 |
11 | script_dir="$(cd "$(dirname "$0")" && pwd)"
12 |
13 | # Minimum version for secure-boot support
14 | BOOTLOADER_SECURE_BOOT_MIN_VERSION=1632136573
15 | SRC_IMAGE="pieeprom.original.bin"
16 | CONFIG="boot.conf"
17 | DST_IMAGE="pieeprom.bin"
18 | PEM_FILE=""
19 | PUBLIC_PEM_FILE=""
20 | TMP_CONFIG_SIG=""
21 |
22 | die() {
23 | echo "$@" >&2
24 | exit ${EXIT_FAILED}
25 | }
26 |
27 | cleanup() {
28 | if [ -f "${TMP_CONFIG}" ]; then rm -f "${TMP_CONFIG}"; fi
29 | }
30 |
31 | usage() {
32 | cat <