Commit 49a2a4f22eec755b8c0377b20a5ecbfee089643e

Authored by tim
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.
recovery/boot.conf 0 → 100644
  1 +[all]
  2 +BOOT_UART=0
  3 +WAKE_ON_GPIO=1
  4 +POWER_OFF_ON_HALT=0
  5 +# Disable bootloader updates from USB MSD & network.
  6 +ENABLE_SELF_UPDATE=0
  7 +# SD, PCIe USB MSD, BCM2711 USB MSD, LOOP
  8 +BOOT_ORDER=0xf541
  9 +
... ...
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
  1 +pieeprom-2021-01-16.bin
0 2 \ No newline at end of file
... ...
recovery/pieeprom.sig
1   -14362fccbe836696bffea7e3b6eeb1b52c29fe9361a2851d08cd11869b43cb91
  1 +4fe4dee06b4401da28cec09c9bae1ab76b7994525f4e5c36a06adb9a2ffb5429
  2 +ts: 1610965456
... ...
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