Commit 49a2a4f22eec755b8c0377b20a5ecbfee089643e
Committed by
Phil Elwell
1 parent
d3760e11
pieeprom-2021-01-16: Update to latest release for BCM2711 XHCI boot
* Update the EEPROM image to the latest/stable release. * Change the default boot-order to 0xf541 so that USB MSD will boot from the type-A sockets on the CM4 IO board. * Add simple update-pieeprom.sh utility with latest rpi-eeprom-config to make it easier to refresh the EEPROM image with a new configuration.
Showing
9 changed files
with
374 additions
and
1 deletions
recovery/boot.conf
0 → 100644
recovery/bootcode4.bin
No preview for this file type
recovery/pieeprom-2021-01-16.bin
0 → 100644
No preview for this file type
recovery/pieeprom.bin
No preview for this file type
recovery/pieeprom.original.bin
0 → 120000
recovery/pieeprom.sig
recovery/rpi-eeprom-config
0 → 100755
| 1 | +#!/usr/bin/env python | |
| 2 | + | |
| 3 | +""" | |
| 4 | +rpi-eeprom-config | |
| 5 | +""" | |
| 6 | + | |
| 7 | +import argparse | |
| 8 | +import atexit | |
| 9 | +import os | |
| 10 | +import subprocess | |
| 11 | +import struct | |
| 12 | +import sys | |
| 13 | +import tempfile | |
| 14 | +import time | |
| 15 | + | |
| 16 | +IMAGE_SIZE = 512 * 1024 | |
| 17 | + | |
| 18 | +MAX_BOOTCONF_SIZE = 2024 | |
| 19 | + | |
| 20 | +# Each section starts with a magic number followed by a 32 bit offset to the | |
| 21 | +# next section (big-endian). | |
| 22 | +# The number, order and size of the sections depends on the bootloader version | |
| 23 | +# but the following mask can be used to test for section headers and skip | |
| 24 | +# unknown data. | |
| 25 | +# | |
| 26 | +# The last 4KB of the EEPROM image is reserved for internal use by the | |
| 27 | +# bootloader and may be overwritten during the update process. | |
| 28 | +MAGIC = 0x55aaf00f | |
| 29 | +MAGIC_MASK = 0xfffff00f | |
| 30 | +FILE_MAGIC = 0x55aaf11f # id for modifiable file, currently only bootconf.txt | |
| 31 | +FILE_HDR_LEN = 20 | |
| 32 | +FILENAME_LEN = 12 | |
| 33 | +TEMP_DIR = None | |
| 34 | + | |
| 35 | +def exit_handler(): | |
| 36 | + """ | |
| 37 | + Delete any temporary files. | |
| 38 | + """ | |
| 39 | + if TEMP_DIR is not None and os.path.exists(TEMP_DIR): | |
| 40 | + tmp_image = os.path.join(TEMP_DIR, 'pieeprom.upd') | |
| 41 | + if os.path.exists(tmp_image): | |
| 42 | + os.remove(tmp_image) | |
| 43 | + tmp_conf = os.path.join(TEMP_DIR, 'boot.conf') | |
| 44 | + if os.path.exists(tmp_conf): | |
| 45 | + os.remove(tmp_conf) | |
| 46 | + os.rmdir(TEMP_DIR) | |
| 47 | + | |
| 48 | +def create_tempdir(): | |
| 49 | + global TEMP_DIR | |
| 50 | + if TEMP_DIR is None: | |
| 51 | + TEMP_DIR = tempfile.mkdtemp() | |
| 52 | + | |
| 53 | +def exit_error(msg): | |
| 54 | + """ | |
| 55 | + Trapped a fatal error, output message to stderr and exit with non-zero | |
| 56 | + return code. | |
| 57 | + """ | |
| 58 | + sys.stderr.write("ERROR: %s\n" % msg) | |
| 59 | + sys.exit(1) | |
| 60 | + | |
| 61 | +def shell_cmd(args): | |
| 62 | + """ | |
| 63 | + Executes a shell command waits for completion returning STDOUT. If an | |
| 64 | + error occurs then exit and output the subprocess stdout, stderr messages | |
| 65 | + for debug. | |
| 66 | + """ | |
| 67 | + start = time.time() | |
| 68 | + arg_str = ' '.join(args) | |
| 69 | + result = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
| 70 | + | |
| 71 | + while time.time() - start < 5: | |
| 72 | + if result.poll() is not None: | |
| 73 | + break | |
| 74 | + | |
| 75 | + if result.poll() is None: | |
| 76 | + exit_error("%s timeout" % arg_str) | |
| 77 | + | |
| 78 | + if result.returncode != 0: | |
| 79 | + exit_error("%s failed: %d\n %s\n %s\n" % | |
| 80 | + (arg_str, result.returncode, result.stdout.read(), result.stderr.read())) | |
| 81 | + else: | |
| 82 | + return result.stdout.read().decode('utf-8') | |
| 83 | + | |
| 84 | +def get_latest_eeprom(): | |
| 85 | + """ | |
| 86 | + Returns the path of the latest EEPROM image file if it exists. | |
| 87 | + """ | |
| 88 | + latest = shell_cmd(['rpi-eeprom-update', '-l']).rstrip() | |
| 89 | + if not os.path.exists(latest): | |
| 90 | + exit_error("EEPROM image '%s' not found" % latest) | |
| 91 | + return latest | |
| 92 | + | |
| 93 | +def apply_update(config, eeprom=None, config_src=None): | |
| 94 | + """ | |
| 95 | + Applies the config file to the latest available EEPROM image and spawns | |
| 96 | + rpi-eeprom-update to schedule the update at the next reboot. | |
| 97 | + """ | |
| 98 | + if eeprom is not None: | |
| 99 | + eeprom_image = eeprom | |
| 100 | + else: | |
| 101 | + eeprom_image = get_latest_eeprom() | |
| 102 | + create_tempdir() | |
| 103 | + tmp_update = os.path.join(TEMP_DIR, 'pieeprom.upd') | |
| 104 | + image = BootloaderImage(eeprom_image, tmp_update) | |
| 105 | + image.write(config) | |
| 106 | + config_str = open(config).read() | |
| 107 | + if config_src is None: | |
| 108 | + config_src = '' | |
| 109 | + sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig_src: %s\nconfig: %s\n%s\n%s\n%s\n" % | |
| 110 | + (eeprom_image, config_src, config, '#' * 80, config_str, '#' * 80)) | |
| 111 | + | |
| 112 | + sys.stdout.write("\n*** To cancel this update run 'sudo rpi-eeprom-update -r' ***\n\n") | |
| 113 | + | |
| 114 | + # Ignore APT package checksums so that this doesn't fail when used | |
| 115 | + # with EEPROMs with configs delivered outside of APT. | |
| 116 | + # The checksums are really just a safety check for automatic updates. | |
| 117 | + args = ['rpi-eeprom-update', '-d', '-i', '-f', tmp_update] | |
| 118 | + resp = shell_cmd(args) | |
| 119 | + sys.stdout.write(resp) | |
| 120 | + | |
| 121 | +def edit_config(eeprom=None): | |
| 122 | + """ | |
| 123 | + Implements something like 'git commit' for editing EEPROM configs. | |
| 124 | + """ | |
| 125 | + # Default to nano if $EDITOR is not defined. | |
| 126 | + editor = 'nano' | |
| 127 | + if 'EDITOR' in os.environ: | |
| 128 | + editor = os.environ['EDITOR'] | |
| 129 | + | |
| 130 | + config_src = '' | |
| 131 | + # If there is a pending update then use the configuration from | |
| 132 | + # that in order to support incremental updates. Otherwise, | |
| 133 | + # use the current EEPROM configuration. | |
| 134 | + bootfs = shell_cmd(['rpi-eeprom-update', '-b']).rstrip() | |
| 135 | + pending = os.path.join(bootfs, 'pieeprom.upd') | |
| 136 | + if os.path.exists(pending): | |
| 137 | + config_src = pending | |
| 138 | + image = BootloaderImage(pending) | |
| 139 | + current_config = image.get_config().decode('utf-8') | |
| 140 | + else: | |
| 141 | + config_src = 'vcgencmd bootloader_config' | |
| 142 | + current_config = read_current_config() | |
| 143 | + | |
| 144 | + create_tempdir() | |
| 145 | + tmp_conf = os.path.join(TEMP_DIR, 'boot.conf') | |
| 146 | + out = open(tmp_conf, 'w') | |
| 147 | + out.write(current_config) | |
| 148 | + out.close() | |
| 149 | + cmd = "\'%s\' \'%s\'" % (editor, tmp_conf) | |
| 150 | + result = os.system(cmd) | |
| 151 | + if result != 0: | |
| 152 | + exit_error("Aborting update because \'%s\' exited with code %d." % (cmd, result)) | |
| 153 | + | |
| 154 | + new_config = open(tmp_conf, 'r').read() | |
| 155 | + if len(new_config.splitlines()) < 2: | |
| 156 | + exit_error("Aborting update because \'%s\' appears to be empty." % tmp_conf) | |
| 157 | + apply_update(tmp_conf, eeprom, config_src) | |
| 158 | + | |
| 159 | +def read_current_config(): | |
| 160 | + """ | |
| 161 | + Reads the configuration used by the current bootloader. | |
| 162 | + """ | |
| 163 | + return shell_cmd(['vcgencmd', 'bootloader_config']) | |
| 164 | + | |
| 165 | +class BootloaderImage(object): | |
| 166 | + def __init__(self, filename, output=None): | |
| 167 | + """ | |
| 168 | + Instantiates a Bootloader image writer with a source eeprom (filename) | |
| 169 | + and optionally an output filename. | |
| 170 | + """ | |
| 171 | + self._filename = filename | |
| 172 | + try: | |
| 173 | + self._bytes = bytearray(open(filename, 'rb').read()) | |
| 174 | + except IOError as err: | |
| 175 | + exit_error("Failed to read \'%s\'\n%s\n" % (filename, str(err))) | |
| 176 | + self._out = None | |
| 177 | + if output is not None: | |
| 178 | + self._out = open(output, 'wb') | |
| 179 | + | |
| 180 | + if len(self._bytes) != IMAGE_SIZE: | |
| 181 | + exit_error("%s: Expected size %d bytes actual size %d bytes" % | |
| 182 | + (filename, IMAGE_SIZE, len(self._bytes))) | |
| 183 | + | |
| 184 | + def find_config(self): | |
| 185 | + offset = 0 | |
| 186 | + magic = 0 | |
| 187 | + while offset < IMAGE_SIZE: | |
| 188 | + magic, length = struct.unpack_from('>LL', self._bytes, offset) | |
| 189 | + if (magic & MAGIC_MASK) != MAGIC: | |
| 190 | + raise Exception('EEPROM is corrupted') | |
| 191 | + | |
| 192 | + if magic == FILE_MAGIC: # Found a file | |
| 193 | + name = self._bytes[offset + 8: offset + FILE_HDR_LEN] | |
| 194 | + if name.decode('utf-8') == 'bootconf.txt': | |
| 195 | + return (offset, length) | |
| 196 | + | |
| 197 | + offset += 8 + length # length + type | |
| 198 | + offset = (offset + 7) & ~7 | |
| 199 | + | |
| 200 | + raise Exception('EEPROM parse error: Bootloader config not found') | |
| 201 | + | |
| 202 | + def write(self, new_config): | |
| 203 | + hdr_offset, length = self.find_config() | |
| 204 | + new_config_bytes = open(new_config, 'rb').read() | |
| 205 | + new_len = len(new_config_bytes) + FILENAME_LEN + 4 | |
| 206 | + if len(new_config_bytes) > MAX_BOOTCONF_SIZE: | |
| 207 | + raise Exception("Config is too large (%d bytes). The maximum size is %d bytes." | |
| 208 | + % (len(new_config_bytes), MAX_BOOTCONF_SIZE)) | |
| 209 | + if hdr_offset + len(new_config_bytes) + FILE_HDR_LEN > IMAGE_SIZE: | |
| 210 | + raise Exception('EEPROM image size exceeded') | |
| 211 | + | |
| 212 | + struct.pack_into('>L', self._bytes, hdr_offset + 4, new_len) | |
| 213 | + struct.pack_into(("%ds" % len(new_config_bytes)), self._bytes, | |
| 214 | + hdr_offset + 4 + FILE_HDR_LEN, new_config_bytes) | |
| 215 | + | |
| 216 | + # If the new config is smaller than the old config then set any old | |
| 217 | + # data which is now unused to all ones (erase value) | |
| 218 | + pad_start = hdr_offset + 4 + FILE_HDR_LEN + len(new_config_bytes) | |
| 219 | + pad = 0 | |
| 220 | + while pad < (length - len(new_config_bytes)): | |
| 221 | + struct.pack_into('B', self._bytes, pad_start + pad, 0xff) | |
| 222 | + pad = pad + 1 | |
| 223 | + | |
| 224 | + if self._out is not None: | |
| 225 | + self._out.write(self._bytes) | |
| 226 | + self._out.close() | |
| 227 | + else: | |
| 228 | + if hasattr(sys.stdout, 'buffer'): | |
| 229 | + sys.stdout.buffer.write(self._bytes) | |
| 230 | + else: | |
| 231 | + sys.stdout.write(self._bytes) | |
| 232 | + | |
| 233 | + def get_config(self): | |
| 234 | + hdr_offset, length = self.find_config() | |
| 235 | + offset = hdr_offset + 4 + FILE_HDR_LEN | |
| 236 | + config_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4] | |
| 237 | + return config_bytes | |
| 238 | + | |
| 239 | + def read(self): | |
| 240 | + config_bytes = self.get_config() | |
| 241 | + if self._out is not None: | |
| 242 | + self._out.write(config_bytes) | |
| 243 | + self._out.close() | |
| 244 | + else: | |
| 245 | + if hasattr(sys.stdout, 'buffer'): | |
| 246 | + sys.stdout.buffer.write(config_bytes) | |
| 247 | + else: | |
| 248 | + sys.stdout.write(config_bytes) | |
| 249 | + | |
| 250 | +def main(): | |
| 251 | + """ | |
| 252 | + Utility for reading and writing the configuration file in the | |
| 253 | + Raspberry Pi 4 bootloader EEPROM image. | |
| 254 | + """ | |
| 255 | + description = """\ | |
| 256 | +Bootloader EEPROM configuration tool for the Raspberry Pi 4. | |
| 257 | +Operating modes: | |
| 258 | + | |
| 259 | +1. Outputs the current bootloader configuration to STDOUT if no arguments are | |
| 260 | + specified OR the given output file if --out is specified. | |
| 261 | + | |
| 262 | + rpi-eeprom-config [--out boot.conf] | |
| 263 | + | |
| 264 | +2. Extracts the configuration file from the given 'eeprom' file and outputs | |
| 265 | + the result to STDOUT or the output file if --output is specified. | |
| 266 | + | |
| 267 | + rpi-eeprom-config pieeprom.bin [--out boot.conf] | |
| 268 | + | |
| 269 | +3. Writes a new EEPROM image replacing the configuration file with the contents | |
| 270 | + of the file specified by --config. | |
| 271 | + | |
| 272 | + rpi-eeprom-config --config boot.conf --out newimage.bin pieeprom.bin | |
| 273 | + | |
| 274 | + The new image file can be installed via rpi-eeprom-update | |
| 275 | + rpi-eeprom-update -d -f newimage.bin | |
| 276 | + | |
| 277 | +4. Applies a given config file to an EEPROM image and invokes rpi-eeprom-update | |
| 278 | + to schedule an update of the bootloader when the system is rebooted. | |
| 279 | + | |
| 280 | + Since this command launches rpi-eeprom-update to schedule the EEPROM update | |
| 281 | + it must be run as root. | |
| 282 | + | |
| 283 | + sudo rpi-eeprom-config --apply boot.conf [pieeprom.bin] | |
| 284 | + | |
| 285 | + If the 'eeprom' argument is not specified then the latest available image | |
| 286 | + is selected by calling 'rpi-eeprom-update -l'. | |
| 287 | + | |
| 288 | +5. The '--edit' parameter behaves the same as '--apply' except that instead of | |
| 289 | + applying a predefined configuration file a text editor is launched with the | |
| 290 | + contents of the current EEPROM configuration. | |
| 291 | + | |
| 292 | + Since this command launches rpi-eeprom-update to schedule the EEPROM update | |
| 293 | + it must be run as root. | |
| 294 | + | |
| 295 | + The configuration file will be taken from: | |
| 296 | + * The cached bootloader configuration 'vcgencmd bootloader_config' | |
| 297 | + * The current pending update - typically /boot/pieeprom.upd | |
| 298 | + | |
| 299 | + sudo -E rpi-eeprom-config --edit [pieeprom.bin] | |
| 300 | + | |
| 301 | + To cancel the pending update run 'sudo rpi-eeprom-update -r' | |
| 302 | + | |
| 303 | + The default text editor is nano and may be overridden by setting the 'EDITOR' | |
| 304 | + environment variable and passing '-E' to 'sudo' to preserve the environment. | |
| 305 | + | |
| 306 | +See 'rpi-eeprom-update -h' for more information about the available EEPROM | |
| 307 | +images. | |
| 308 | +""" | |
| 309 | + parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, | |
| 310 | + description=description) | |
| 311 | + | |
| 312 | + parser.add_argument('-a', '--apply', required=False, | |
| 313 | + help='Updates the bootloader to the given config plus latest available EEPROM release.') | |
| 314 | + parser.add_argument('-c', '--config', help='Name of bootloader configuration file', required=False) | |
| 315 | + parser.add_argument('-e', '--edit', action='store_true', default=False, help='Edit the current EEPROM config') | |
| 316 | + parser.add_argument('-o', '--out', help='Name of output file', required=False) | |
| 317 | + parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input') | |
| 318 | + args = parser.parse_args() | |
| 319 | + | |
| 320 | + | |
| 321 | + if (args.edit or args.apply is not None) and os.getuid() != 0: | |
| 322 | + exit_error("--edit/--apply must be run as root") | |
| 323 | + | |
| 324 | + if args.edit: | |
| 325 | + edit_config(args.eeprom) | |
| 326 | + elif args.apply is not None: | |
| 327 | + if not os.path.exists(args.apply): | |
| 328 | + exit_error("config file '%s' not found" % args.apply) | |
| 329 | + apply_update(args.apply, args.eeprom, args.apply) | |
| 330 | + elif args.eeprom is not None: | |
| 331 | + image = BootloaderImage(args.eeprom, args.out) | |
| 332 | + if args.config is not None: | |
| 333 | + if not os.path.exists(args.config): | |
| 334 | + exit_error("config file '%s' not found" % args.config) | |
| 335 | + image.write(args.config) | |
| 336 | + else: | |
| 337 | + image.read() | |
| 338 | + elif args.config is None and args.eeprom is None: | |
| 339 | + current_config = read_current_config() | |
| 340 | + if args.out is not None: | |
| 341 | + open(args.out, 'w').write(current_config) | |
| 342 | + else: | |
| 343 | + sys.stdout.write(current_config) | |
| 344 | + | |
| 345 | +if __name__ == '__main__': | |
| 346 | + atexit.register(exit_handler) | |
| 347 | + main() | ... | ... |
recovery/update-pieeprom.sh
0 → 100755
| 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 | +${script_dir}/rpi-eeprom-config --config ${script_dir}/boot.conf --out ${script_dir}/pieeprom.bin ${script_dir}/pieeprom.original.bin | |
| 14 | +sha256sum ${script_dir}/pieeprom.bin | awk '{print $1}' > ${script_dir}/pieeprom.sig | |
| 15 | +echo "ts: $(date -u +%s)" >> "${script_dir}/pieeprom.sig" | ... | ... |
win32/rpiboot_setup.exe
No preview for this file type