diff --git a/firmware-2712/latest/pieeprom-2024-04-05.bin b/firmware-2712/latest/pieeprom-2024-04-05.bin new file mode 100644 index 0000000..ad15f67 Binary files /dev/null and b/firmware-2712/latest/pieeprom-2024-04-05.bin differ diff --git a/firmware-2712/latest/recovery.bin b/firmware-2712/latest/recovery.bin index 427942a..c85ba28 100644 Binary files a/firmware-2712/latest/recovery.bin and b/firmware-2712/latest/recovery.bin differ diff --git a/firmware-2712/release-notes.md b/firmware-2712/release-notes.md index c86a534..9deaca2 100644 --- a/firmware-2712/release-notes.md +++ b/firmware-2712/release-notes.md @@ -1,5 +1,23 @@ # Raspberry Pi5 bootloader EEPROM release notes +2024-04-05: HAT+ fixes for max-current, custom CA cert for net install and enable over-clocking to > 3GHz (latest) +* bootloader: clock_2712: Remove restriction on arm_freq <= 3000 + See: https://github.com/raspberrypi/firmware/issues/1876 +* arm_dt: Update max_current to match HAT value +* arm_dt: Remove unused legacy parameters (core_freq, arm_freq, uart0_clkrate and cache_line_size) +* Add support for custom CA cert for network install + You need to specify + HTTP_HOST=myhost.com + HTTP_PATH=/path/to/files + HTTP_CACERT_HASH= + + where is a sha256 hash of the der encoded ca certificate. + CA cert is added using rpi-eeprom-config. +* Optimise Vbat current draw with charging disabled +* Display OTP boot status in UART log messages. +* Preliminary support for secure-boot OTP provisioning. +* Update PCIE DET_WAKE pinmux for D0 products + 2024-02-16: u-boot loading and thermal throttling fixes (latest) (default) * arm_loader: Move non-kernels back to 512KB See: https://github.com/raspberrypi/firmware/issues/1868 @@ -97,7 +115,7 @@ * Update board-name - "Raspberry Pi 5" 2023-09-28: vcgencmd pmic_read_adcs fixes (automatic update) - + * Fix the LDO names and current scaling codes * Manufacturing test updates diff --git a/rpi-eeprom-config b/rpi-eeprom-config index 9c4f5f5..8ba5fb3 100755 --- a/rpi-eeprom-config +++ b/rpi-eeprom-config @@ -19,6 +19,8 @@ VALID_IMAGE_SIZES = [512 * 1024, 2 * 1024 * 1024] BOOTCONF_TXT = 'bootconf.txt' BOOTCONF_SIG = 'bootconf.sig' PUBKEY_BIN = 'pubkey.bin' +CACERT_DER = 'cacert.der' +BOOTCODE_BIN = 'bootcode.bin' # Each section starts with a magic number followed by a 32 bit offset to the # next section (big-endian). @@ -296,14 +298,22 @@ class BootloaderImage(object): length = -1 is_last = False - next_offset = self._image_size - ERASE_ALIGN_SIZE # Don't create padding inside the bootloader scratch page - for i in range(0, len(self._sections)): + if filename == BOOTCODE_BIN: + next_offset = 0 + dst_filename = filename + i = 0 s = self._sections[i] - if s.magic == FILE_MAGIC and s.filename == filename: - is_last = (i == len(self._sections) - 1) - offset = s.offset - length = s.length - break + offset = s.offset + length = s.length + else: + next_offset = self._image_size - ERASE_ALIGN_SIZE # Don't create padding inside the bootloader scratch page + for i in range(0, len(self._sections)): + s = self._sections[i] + if s.magic == FILE_MAGIC and s.filename == filename: + is_last = (i == len(self._sections) - 1) + offset = s.offset + length = s.length + break # Find the start of the next non padding section i += 1 @@ -317,30 +327,43 @@ class BootloaderImage(object): debug('%s offset %d length %d is-last %d next %d' % (filename, ret[0], ret[1], ret[2], ret[3])) return ret - def update(self, src_bytes, dst_filename): + def update(self, src_bytes, dst_filename, bootcode = False): """ Replaces a modifiable file with specified byte array. """ - hdr_offset, length, is_last, next_offset = self.find_file(dst_filename) - update_len = len(src_bytes) + FILE_HDR_LEN + if bootcode: + hdr_offset, length, is_last, next_offset = self.find_file(dst_filename) + struct.pack_into('>L', self._bytes, hdr_offset + 4, len(src_bytes)) + struct.pack_into(("%ds" % len(src_bytes)), self._bytes, hdr_offset + 8, src_bytes) + pad_start = hdr_offset + len(src_bytes) + 8 + is_last = False + debug("bootcode padded to %d" % next_offset); + if next_offset < 128 * 1024: + raise Exception("update-bootcode: Can't update image - 128K must be reserved for bootcode") + if next_offset < 0: + raise Exception("update-bootcode: Failed to find next section") - if hdr_offset + update_len > self._image_size - ERASE_ALIGN_SIZE: - raise Exception('No space available - image past EOF.') + else: + hdr_offset, length, is_last, next_offset = self.find_file(dst_filename) + update_len = len(src_bytes) + FILE_HDR_LEN - if hdr_offset < 0: - raise Exception('Update target %s not found' % dst_filename) + if hdr_offset + update_len > self._image_size - ERASE_ALIGN_SIZE: + raise Exception('No space available - image past EOF.') - if hdr_offset + update_len > next_offset: - raise Exception('Update %d bytes is larger than section size %d' % (update_len, next_offset - hdr_offset)) + if hdr_offset < 0: + raise Exception('Update target %s not found' % dst_filename) - new_len = len(src_bytes) + FILENAME_LEN + 4 - struct.pack_into('>L', self._bytes, hdr_offset + 4, new_len) - struct.pack_into(("%ds" % len(src_bytes)), self._bytes, - hdr_offset + 4 + FILE_HDR_LEN, src_bytes) + if hdr_offset + update_len > next_offset: + raise Exception('Update %d bytes is larger than section size %d' % (update_len, next_offset - hdr_offset)) - # If the new file is smaller than the old file then set any old - # data which is now unused to all ones (erase value) - pad_start = hdr_offset + 4 + FILE_HDR_LEN + len(src_bytes) + new_len = len(src_bytes) + FILENAME_LEN + 4 + struct.pack_into('>L', self._bytes, hdr_offset + 4, new_len) + struct.pack_into(("%ds" % len(src_bytes)), self._bytes, + hdr_offset + 4 + FILE_HDR_LEN, src_bytes) + + # If the new file is smaller than the old file then set any old + # data which is now unused to all ones (erase value) + pad_start = hdr_offset + 4 + FILE_HDR_LEN + len(src_bytes) # Add padding up to 8-byte boundary while pad_start % 8 != 0: @@ -379,10 +402,11 @@ class BootloaderImage(object): Replaces the contents of dst_filename in the EEPROM with the contents of src_file. """ src_bytes = open(src_filename, 'rb').read() - if len(src_bytes) > MAX_FILE_SIZE: + bootcode = dst_filename == BOOTCODE_BIN + if not bootcode and len(src_bytes) > MAX_FILE_SIZE: raise Exception("src file %s is too large (%d bytes). The maximum size is %d bytes." % (src_filename, len(src_bytes), MAX_FILE_SIZE)) - self.update(src_bytes, dst_filename) + self.update(src_bytes, dst_filename, bootcode) def set_timestamp(self, timestamp): """ @@ -408,14 +432,22 @@ class BootloaderImage(object): def get_file(self, filename): hdr_offset, length, is_last, next_offset = self.find_file(filename) - offset = hdr_offset + 4 + FILE_HDR_LEN - file_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4] + if filename == BOOTCODE_BIN: + offset = hdr_offset + 8 + file_bytes = self._bytes[offset:offset+length] + else: + offset = hdr_offset + 4 + FILE_HDR_LEN + file_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4] + return file_bytes def extract_files(self): for i in range(0, len(self._sections)): s = self._sections[i] - if s.magic == FILE_MAGIC: + if s.magic == MAGIC and s.offset == 0: + file_bytes = self.get_file(BOOTCODE_BIN) + open(BOOTCODE_BIN, 'wb').write(file_bytes) + elif s.magic == FILE_MAGIC: file_bytes = self.get_file(s.filename) open(s.filename, 'wb').write(file_bytes) @@ -514,7 +546,9 @@ See 'rpi-eeprom-update -h' for more information about the available EEPROM image 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) parser.add_argument('-p', '--pubkey', help='Signed boot only. The name of the RSA public key file to store in the EEPROM', required=False) parser.add_argument('-x', '--extract', action='store_true', default=False, help='Extract the modifiable files (boot.conf, pubkey, signature)', required=False) + parser.add_argument('-b', '--bootcode', help='Signed boot 2712 only. The name of the customer signed bootcode.bin file to store in the EEPROM', required=False) parser.add_argument('-t', '--timestamp', help='Set the timestamp in the EEPROM image file', required=False) + parser.add_argument('--cacertder', help='The name of a CA Certificate DER encoded file to store in the EEPROM', required=False) parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input') args = parser.parse_args() @@ -537,7 +571,10 @@ See 'rpi-eeprom-update -h' for more information about the available EEPROM image image = BootloaderImage(args.eeprom, args.out) if args.timestamp is not None: image.set_timestamp(args.timestamp) - if args.config is not None: + if args.bootcode is not None: + image.update_file(args.bootcode, BOOTCODE_BIN) + image.write() + elif args.config is not None: if not os.path.exists(args.config): exit_error("config file '%s' not found" % args.config) image.update_file(args.config, BOOTCONF_TXT) @@ -545,6 +582,8 @@ See 'rpi-eeprom-update -h' for more information about the available EEPROM image image.update_file(args.digest, BOOTCONF_SIG) if args.pubkey is not None: image.update_key(args.pubkey, PUBKEY_BIN) + if args.cacertder is not None: + image.update_file(args.cacertder, CACERT_DER) image.write() elif args.config is None and args.timestamp is not None: image.write() diff --git a/rpi-eeprom-digest b/rpi-eeprom-digest index 3499752..dfb0f72 100755 --- a/rpi-eeprom-digest +++ b/rpi-eeprom-digest @@ -34,18 +34,27 @@ checkDependencies() { if ! command -v xxd > /dev/null; then die "xxd not found. Try installing the xxd package." fi - fi + fi } usage() { cat <&2 +} + getBootloaderConfig() { # Prefer extracting bootloader's config from DT. # @@ -192,9 +196,9 @@ applyRecoveryUpdate() getBootloaderCurrentVersion BOOTLOADER_UPDATE_VERSION=$(strings "${BOOTLOADER_UPDATE_IMAGE}" | grep BUILD_TIMESTAMP | sed 's/.*=//g') if [ "${BOOTLOADER_CURRENT_VERSION}" -gt "${BOOTLOADER_UPDATE_VERSION}" ]; then - echo " WARNING: Installing an older bootloader version." - echo " Update the rpi-eeprom package to fetch the latest bootloader images." - echo + warn " WARNING: Installing an older bootloader version." + warn " Update the rpi-eeprom package to fetch the latest bootloader images." + warn fi echo " CURRENT: $(date -u "-d@${BOOTLOADER_CURRENT_VERSION}") (${BOOTLOADER_CURRENT_VERSION})" echo " UPDATE: $(date -u "-d@${BOOTLOADER_UPDATE_VERSION}") (${BOOTLOADER_UPDATE_VERSION})" @@ -254,9 +258,10 @@ applyRecoveryUpdate() # of power loss. if [ "${RPI_EEPROM_USE_FLASHROM}" = 1 ]; then echo - echo "UPDATING bootloader." + echo "UPDATING bootloader. This could take up to a minute. Please wait" + echo + echo "*** Do not disconnect the power until the update is complete ***" echo - echo "*** WARNING: Do not disconnect the power until the update is complete ***" echo "If a problem occurs then the Raspberry Pi Imager may be used to create" echo "a bootloader rescue SD card image which restores the default bootloader image." echo @@ -310,11 +315,11 @@ applyUpdate() { if [ "${RPI_EEPROM_USE_FLASHROM}" = 1 ]; then flashrom_probe_ok=0 if ! [ -e "${SPIDEV}" ]; then - echo "WARNING: SPI device ${SPIDEV} not found. Setting RPI_EEPROM_USE_FLASHROM to 0" + warn "WARNING: SPI device ${SPIDEV} not found. Setting RPI_EEPROM_USE_FLASHROM to 0" fi if ! flashrom -p linux_spi:dev=${SPIDEV},spispeed=16000 > /dev/null 2>&1; then - echo "WARNING: Flashrom probe of ${SPIDEV} failed" + warn "WARNING: Flashrom probe of ${SPIDEV} failed" else flashrom_probe_ok=1 fi @@ -405,10 +410,12 @@ checkDependencies() { # Default to off - in the future Raspberry Pi 5 may default to using flashrom if # flashrom is available. - [ -z "${RPI_EEPROM_USE_FLASHROM}" ] && RPI_EEPROM_USE_FLASHROM=0 - if [ "${RPI_EEPROM_USE_FLASHROM}" -eq 1 ] && ! command -v flashrom > /dev/null; then - echo "WARNING: flashrom not found. Setting RPI_EEPROM_USE_FLASHROM to 0" - RPI_EEPROM_USE_FLASHROM=0 + if [ "${AUTO_UPDATE_BOOTLOADER}" = 1 ] || [ -n "${BOOTLOADER_UPDATE_IMAGE}" ]; then + [ -z "${RPI_EEPROM_USE_FLASHROM}" ] && RPI_EEPROM_USE_FLASHROM=0 + if [ "${RPI_EEPROM_USE_FLASHROM}" -eq 1 ] && ! command -v flashrom > /dev/null; then + warn "WARNING: flashrom not found. Setting RPI_EEPROM_USE_FLASHROM to 0" + RPI_EEPROM_USE_FLASHROM=0 + fi fi FIRMWARE_IMAGE_DIR="${FIRMWARE_ROOT}-${BCM_CHIP}/${FIRMWARE_RELEASE_STATUS}" @@ -710,11 +717,11 @@ findBootFS() if [ "${BCM_CHIP}" = 2712 ]; then if ! [ -e "${BOOTFS}/config.txt" ]; then - echo "WARNING: BOOTFS: \"${BOOTFS}/config.txt\" not found. Please check boot directory" + warn "WARNING: BOOTFS: \"${BOOTFS}/config.txt\" not found. Please check boot directory" fi else if [ "$(find "${BOOTFS}/" -name "*.elf" | wc -l)" = 0 ]; then - echo "WARNING: BOOTFS: \"${BOOTFS}\" contains no .elf files. Please check boot directory" + warn "WARNING: BOOTFS: \"${BOOTFS}\" contains no .elf files. Please check boot directory" fi fi } diff --git a/tools/rpi-otp-private-key b/tools/rpi-otp-private-key index 3a17a17..68e8013 100755 --- a/tools/rpi-otp-private-key +++ b/tools/rpi-otp-private-key @@ -6,6 +6,8 @@ FORCE=0 READ_KEY="" WRITE_KEY="" OUTPUT_BINARY=0 +ROW_COUNT=8 +ROW_OFFSET=0 die() { echo "$@" >&2 @@ -25,10 +27,12 @@ usage() { N.B. OTP bits can never change from 1 to 0. -w Writes the new key to OTP memory. -y Skip the confirmation prompt when writing to OTP. + -l Specify key length in words. Defaults to 8 words (32 bytes). Pi 5 supports up to 16 words (64 bytes). + -o word Offset into the keystore to use, e.g. 0-7 for Pi 4, 0-15 for Pi 5. Defaults to zero. - is a 64 digit hex number (256 bit) e.g. to generate a 256 random number run 'openssl rand -hex 32' + is usually a 64 digit hex number (256 bit) e.g. to generate a 256 random number run 'openssl rand -hex 32' - IMPORTANT: Raspberry Pi 4 and earlier revisions do not have a hardware secure key store. These OTP rows are visible + IMPORTANT: Raspberry Pi 5 and earlier revisions do not have a hardware secure key store. These OTP rows are visible to any user in the 'video' group via vcmailbox. Therefore this functionality is only suitable for key storage if the OS has already been restricted using the signed boot functionality. @@ -46,8 +50,8 @@ check_key_set() { } read_key() { - 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" - READ_KEY="$(echo "${out}" | sed 's/0x//g' | awk '{for(i=8;i<16;i++) printf $i; print ""}')" + out=READ_KEY="$(vcmailbox 0x00030081 $((8 + $ROW_COUNT * 4)) $((8 + $ROW_COUNT * 4)) $ROW_OFFSET $ROW_COUNT 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)" || die "Failed to read the current key from OTP" + READ_KEY="$(echo "${out}" | sed 's/0x//g' | awk -v last=$((8 + $ROW_COUNT)) '{for(i=8;i /dev/null; then + BOARD_INFO="$(vcgencmd otp_dump | grep '30:' | sed 's/.*://')" +else + die "No Raspberry Pi board info found" +fi +if [ $(((0x$BOARD_INFO >> 23) & 1)) = 0 ]; then + die "Chip not supported" +fi +if [ $(((0x$BOARD_INFO >> 12) & 15)) = 3 ]; then + MAX_ROW_COUNT=8 +elif [ $(((0x$BOARD_INFO >> 12) & 15)) = 4 ]; then + MAX_ROW_COUNT=16 +else + die "Chip not supported" +fi +if [ -z "$ROW_COUNT" ] || [ "$ROW_COUNT" -ne "$ROW_COUNT" ] 2>/dev/null; then + die "Key length not a number" +fi +if [ $ROW_COUNT -lt 1 ]; then + die "Length too small" +fi +if [ $ROW_COUNT -gt $MAX_ROW_COUNT ]; then + die "Length too big" +fi +if [ -z "$ROW_OFFSET" ] || [ "$ROW_OFFSET" -ne "$ROW_OFFSET" ] 2>/dev/null; then + die "Offset is not a number" +fi +if [ $ROW_OFFSET -lt 0 ]; then + die "Offset too small" +fi +if [ ${ROW_OFFSET} -gt $(($MAX_ROW_COUNT - $ROW_COUNT)) ]; then + die "Offset too big" +fi +if [ -z $(which vcmailbox) ]; then + die "vcmailbox command missing" +fi +if [ -z $(which xxd) ] && [ "$OUTPUT_BINARY" -eq "1" ]; then + die "xxd command missing" +fi + if [ -n "${WRITE_KEY}" ]; then if [ "${FORCE}" = 0 ] && check_key_set; then die "Current key is non-zero. Specify -f to write anyway" diff --git a/tools/rpi-sign-bootcode b/tools/rpi-sign-bootcode new file mode 100755 index 0000000..af87243 --- /dev/null +++ b/tools/rpi-sign-bootcode @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 + +import argparse +import base64 +import struct +import sys + +# python3 -m pip install pycryptodomex +from Cryptodome.Hash import HMAC, SHA1, SHA256 +from Cryptodome.PublicKey import RSA +from Cryptodome.Signature import pkcs1_15 + +_CONFIG = {'DEBUG': False} +MAX_BIN_SIZE = 110 * 1024 + +def debug(msg): + """ + Outputs the msg string to stdout if DEBUG is enabled (via -d) + """ + if _CONFIG['DEBUG']: + sys.stderr.write(str(msg) + '\n') + +class ImageFile: + """ + Signed binary image + """ + def __init__(self, filename, max_size_kb): + self._filename = filename + self._bytes_written = 0 + if self._filename is None: + self._of = sys.stdout + else: + self._of = open(self._filename, "wb") + self._max_size_kb = max_size_kb + self._bytes = bytearray() + + debug("%8s %20s: [%6s] %s" % ('OFFSET', 'TYPE', 'SIZE', 'DESCRIPTION')) + debug("") + + def append(self, data): + """ + Appends a blob of binary data to the image + """ + self._bytes.extend(data) + + def append_file(self, source_file): + """ + Appends the binary contents of source_file to the current image. If + source_file is None then a base64 encoded blob is read from stdin. + """ + if source_file is None: + b64 = "" + for l in sys.stdin.readlines(): + b64 += l + file_bytes = base64.b64decode(b64) + else: + file_bytes = bytearray(open(source_file, 'rb').read()) + size = len(file_bytes) + debug("%08x %20s: [%6d] %s" % (self.pos(), 'FILE', size, source_file)) + self.append(file_bytes) + + def append_keynum(self, keynum): + """ + Appends a given key number as a 32-bit LE integer. + """ + if (keynum < 0 or keynum > 4) and keynum != 16: + raise Exception("Bad key number %d" % keynum) + debug("%08x %20s: [%6d] %d" % (self.pos(), "KEYNUM", 4, keynum)) + self.append(struct.pack(' 32: + raise Exception("Bad version number %d must be between 0-32" % version) + debug("%08x %20s: [%6d] %d" % (self.pos(), "VERSION", 4, version)) + self.append(struct.pack(' self._max_size_kb: + raise Exception("Signed binary size %d is too large. Max size %d" % (len(self._bytes), MAX_BIN_SIZE)) + debug("Image size %d" % len(self._bytes)) + if self._filename is None: + self._of.buffer.write(base64.b64encode(self._bytes)) + else: + self._of.write(self._bytes) + + def close(self): + self._of.close() + +def create_2711_image(output, bootcode, private_key, private_keynum, hmac): + """ + Create a 2711 C0 secure-boot compatible seconds stage signed binary. + """ + image = ImageFile(output, MAX_BIN_SIZE) + image.append_file(bootcode) + image.append_length() + image.append_keynum(private_keynum) + image.append_rsa_signature('sha1', private_key) + image.append_digest('hmac-sha1', hmac) + image.write() + image.close() + +def create_2712_image(output, bootcode, private_key, private_keynum, private_version): + """ + Create 2712 signed bootloader. The HMAC is removed and the full public key is appended. + """ + image = ImageFile(output, MAX_BIN_SIZE) + image.append_file(bootcode) + image.append_length() + image.append_keynum(private_keynum) + image.append_version(private_version) + image.append_rsa_signature('sha256', private_key) + image.append_public_key(private_key) + image.write() + image.close() + +def main(): + help_text = """ + Signs a second stage bootloader image. + + Examples: + 2711 mode: + rpi-sign-bootcode --debug -c 2711 -i bootcode.bin.clr -o bootcode.bin -k 2711_rsa_priv_0.pem -n 0 -m bootcode-production.key + + 2712 C1 and D0 mode: + * HMAC not included on 2712 + * RSA public key included - ROM just contains the hashes of the RPi public keys. + + Customer counter-signed signed: + * Exactly the same as Raspberry Pi signing but the input is the Raspberry Pi signed bootcode.bin + * The key number will probably always be 16 to indicate a customer signing + + rpi-sign-bootcode --debug -c 2712 -i bootcode.bin.sign2 -o bootcode.bin -k customer.pem + """ + parser = argparse.ArgumentParser(help_text) + parser.add_argument('-o', '--output', required=False, help='Output filename . If not specified the signed images is written to stdout in base64 format') + parser.add_argument('-c', '--chip', required=True, type=int, help='Chip number') + parser.add_argument('-i', '--input', required=False, help='Path of the unsigned bootcode.bin file OR RPi signed bootcode file sign with the customer key. If NULLL the binary is read from stdin in base64 format') + parser.add_argument('-m', '--hmac', required=False, help='Path of the HMAC key file') + parser.add_argument('-k', '--private-key', dest='private_key', required=True, help='Path of RSA private key (PEM format)') + parser.add_argument('-n', '--private-keynum', dest='private_keynum', required=False, default=0, type=int, help='ROM key index for RPi signing stage') + parser.add_argument('-d', '--debug', action='store_true') + parser.add_argument('-v', '--private-version', dest='private_version', required=True, type=int, help='Version of firmware, stops firmware rollback, only valid 0-31') + + args = parser.parse_args() + _CONFIG['DEBUG'] = args.debug + if args.chip == 2711: + if args.hmac is None: + raise Exception("HMAC key requried for 2711") + create_2711_image(args.output, args.input, args.private_key, args.private_keynum, args.hmac) + elif args.chip == 2712: + create_2712_image(args.output, args.input, args.private_key, args.private_keynum, args.private_version) + +if __name__ == '__main__': + main()