diff --git a/CMakeLists.txt b/CMakeLists.txt index a9a5deb..bd179a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ add_subdirectory(piolib) add_subdirectory(raspinfo) add_subdirectory(rpieepromab) add_subdirectory(rpi-gpu-usage) -add_subdirectory(rpi-ram-authenticity-check) +add_subdirectory(rpi-manufacture-info) add_subdirectory(vcgencmd) add_subdirectory(vclog) add_subdirectory(vcmailbox) diff --git a/README.md b/README.md index 64fcb15..911f735 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ A collection of scripts and simple applications secure-boot provisioner. * [rpi-gpu-usage](rpi-gpu-usage/) - A tool for showing the per-process usage of the V3D GPU on Raspberry Pi 4 and 5. -* [rpi-ram-authenticity-check](rpi-ram-authenticity-check/) - A read-only RAM authenticity - check for BCM2712 devices, comparing reported capacity with the factory configuration. +* [rpi-manufacture-info](rpi-manufacture-info/) - Displays the manufacturing information + provisioned in BCM2711 and BCM2712 OTP, including the SDRAM configuration. * [splashasm](splashasm/) - A tool for writing spi & i2c dumps for fast display at boot * [vcgencmd](vcgencmd/) - A tool to send commands to the VideoCore firmware and display the results. diff --git a/raspinfo/README.md b/raspinfo/README.md index 143d7f7..5c4440f 100644 --- a/raspinfo/README.md +++ b/raspinfo/README.md @@ -1,7 +1,5 @@ # raspinfo Short script to dump information about the Pi. Use it for annotating bug reports. -On BCM2712 devices, includes the output of -[`rpi-ram-authenticity-check`](../rpi-ram-authenticity-check/) when installed. -RAM mismatch warnings and unavailable checks are recorded in `raspinfo.txt` -without stopping the rest of the report. +On BCM2711 and BCM2712 devices, includes the output of +[`rpi-manufacture-info`](../rpi-manufacture-info/) when installed. diff --git a/raspinfo/raspinfo b/raspinfo/raspinfo index 99b1965..38ec8d9 100755 --- a/raspinfo/raspinfo +++ b/raspinfo/raspinfo @@ -235,15 +235,15 @@ cat /proc/cpuinfo | tail -3 echo "Throttled flag : "`vcgencmd get_throttled` -if grep -q bcm2712 /proc/device-tree/compatible 2>/dev/null; then +if grep -q -e bcm2711 -e bcm2712 /proc/device-tree/compatible 2>/dev/null; then echo - echo "RAM authenticity" - echo "----------------" + echo "Manufacturing information" + echo "-------------------------" echo - if command -v rpi-ram-authenticity-check > /dev/null; then - rpi-ram-authenticity-check 2>&1 || : + if command -v rpi-manufacture-info > /dev/null; then + rpi-manufacture-info 2>&1 || : else - echo "rpi-ram-authenticity-check not found" + echo "rpi-manufacture-info not found" fi echo fi diff --git a/rpi-manufacture-info/CMakeLists.txt b/rpi-manufacture-info/CMakeLists.txt new file mode 100644 index 0000000..fa518bc --- /dev/null +++ b/rpi-manufacture-info/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.10...3.27) + +include(GNUInstallDirs) + +project(rpi-manufacture-info) + +install(PROGRAMS rpi-manufacture-info DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/rpi-manufacture-info/README.md b/rpi-manufacture-info/README.md new file mode 100644 index 0000000..8d5ae16 --- /dev/null +++ b/rpi-manufacture-info/README.md @@ -0,0 +1,38 @@ +# rpi-manufacture-info + +Displays the manufacturing information provisioned in the OTP of BCM2711 +(Raspberry Pi 4 family) and BCM2712 (Raspberry Pi 5 family) devices during +production. + +| Field | BCM2711 | BCM2712 | +|----------------------|-----------------|---------------------------| +| Serial number | row 28 | rows 35 and 31 | +| Board revision | row 30 | row 32 | +| QrCode | - | `/chosen/rpi-duid` (from rows 109-114) | +| MAC addresses | rows 64-65 | rows 50-55 (Ethernet, Wi-Fi, Bluetooth) | +| Board attributes | row 33 | row 33 | +| SDRAM_CONFIG | row 33 bits 16:8 | row 34 bits 11:0, override in bits 27:16 | + +SDRAM_CONFIG is decoded to the total SDRAM size and the number of ranks. The +size is converted to the equivalent board revision memory size field and +compared with the memory size in the OTP board revision and the running board +revision. Sizes which are not a power of two (i.e. 3GB) are encoded as +"Other" in the board revision. Older devices were manufactured without +SDRAM_CONFIG, in which case the memory size is taken from the board revision. + +On Compute Modules (CM4, CM4S, CM5 and CM5 Lite) the board attributes are +decoded to show whether eMMC and Wi-Fi are fitted. These bits were not +programmed on older devices, so a CM5 Lite is also identified from the board +type in the board revision. + +Run `rpi-manufacture-info` after installing utils, or directly from this +repository: + +```sh +./rpi-manufacture-info/rpi-manufacture-info +``` + +Requires `vcgencmd` and access to `/dev/vcio`; run with `sudo` if access is +denied. + +When installed, `raspinfo` includes the output on BCM2711 and BCM2712 devices. diff --git a/rpi-manufacture-info/rpi-manufacture-info b/rpi-manufacture-info/rpi-manufacture-info new file mode 100755 index 0000000..aea5481 --- /dev/null +++ b/rpi-manufacture-info/rpi-manufacture-info @@ -0,0 +1,258 @@ +#!/bin/sh + +# Display the manufacturing information provisioned in BCM2711 / BCM2712 OTP. +set -ef + +usage() { + cat <&2 + exit 1 +} + +# Memory size of a board revision memory field (bits 22:20) +memsizeName() { + case "$1" in + [01]) echo "$((256 << $1))MB" ;; + [2-6]) echo "$((1 << ($1 - 2)))GB" ;; + 7) echo "Other" ;; + *) echo "Unknown" ;; + esac +} + +# Total SDRAM size in Gbit from the SDRAM_CONFIG size enum (bits 2:0) +# and non-power-of-two flag (bit 5). 24Gbit is the only valid size which is +# not a power of two. +sdramSizeGbit() { + case $(($1 & 0x27)) in + 7) echo 8 ;; + 6) echo 16 ;; + 38) echo 24 ;; # 0x26: 16Gbit enum with the non-power-of-two flag + 5) echo 32 ;; + 4) echo 64 ;; + 3) echo 128 ;; + *) echo 0 ;; + esac +} + +# Board revision memory field for a size in Gbit. Sizes which are not a power +# of two do not fit in the board revision and are encoded as Other. +gbitToMemsize() { + case "$1" in + 2) echo 0 ;; + 4) echo 1 ;; + 8) echo 2 ;; + 16) echo 3 ;; + 32) echo 4 ;; + 64) echo 5 ;; + 128) echo 6 ;; + *) echo 7 ;; + esac +} + +gbitName() { + if [ "$1" -ge 8 ] && [ $(($1 % 8)) -eq 0 ]; then + echo "$(($1 / 8))GB" + else + echo "$(($1 * 128))MB" + fi +} + +otpRow() { + printf '%s\n' "${otp_dump}" | sed -n "s/^$1:\([0-9a-fA-F]\{8\}\)\$/\1/p" +} + +macFromRows() { + lo=$(otpRow "$1") + hi=$(otpRow "$(($1 + 1))") + if [ -z "${lo}" ] || [ -z "${hi}" ]; then + echo "Unavailable" + elif [ $((0x${lo} | 0x${hi})) -eq 0 ]; then + echo "Not programmed" + else + printf '%s%s\n' "${hi}" "${lo%????}" | sed 's/../&:/g; s/:$//' | tr 'A-F' 'a-f' + fi +} + +decodeSdramConfig() { + gbit=$(sdramSizeGbit "$1") + if [ "${gbit}" -eq 0 ]; then + size="unknown size" + else + size="${gbit}Gbit ($(gbitName "${gbit}"))" + fi + if [ $((($1 >> 4) & 1)) -eq 1 ]; then + rank="dual-rank" + else + rank="single-rank" + fi + printf '0x%03x %s %s\n' "$1" "${size}" "${rank}" +} + +# The QrCode is the code scanned in the factory. It is stored C40 encoded in +# OTP rows 109 to 114 and the firmware exports the decoded value as rpi-duid. +qrCode() { + if [ -r /proc/device-tree/chosen/rpi-duid ]; then + tr -d '\0' < /proc/device-tree/chosen/rpi-duid + echo + else + echo "Not available" + fi +} + +if [ "$#" -ne 0 ]; then + if [ "$#" -eq 1 ] && { [ "$1" = -h ] || [ "$1" = --help ]; }; then + usage + exit 0 + fi + usage >&2 + exit 1 +fi + +if grep -q 'brcm,bcm2712' /proc/device-tree/compatible 2>/dev/null; then + chip=2712 + row_serial=31 + row_revision=32 + mac_rows="Ethernet:50 Wi-Fi:52 Bluetooth:54" +elif grep -q 'brcm,bcm2711' /proc/device-tree/compatible 2>/dev/null; then + chip=2711 + row_serial=28 + row_revision=30 + mac_rows="Ethernet:64" +else + die "Only BCM2711 and BCM2712 devices are supported." +fi +row_attributes=33 + +command -v vcgencmd > /dev/null || die "vcgencmd is required to read OTP." +otp_dump=$(vcgencmd otp_dump 2>/dev/null) || die "Failed to read OTP; check access to /dev/vcio." + +serial=$(otpRow "${row_serial}") +otp_revision=$(otpRow "${row_revision}") +attributes=$(otpRow "${row_attributes}") +if [ -z "${serial}" ] || [ -z "${otp_revision}" ] || [ -z "${attributes}" ]; then + die "Failed to parse OTP dump." +fi + +if [ "${chip}" = 2712 ]; then + sdram_row=34 + sdram_otp=$(otpRow "${sdram_row}") + [ -n "${sdram_otp}" ] || die "Failed to parse OTP dump." + serial="$(otpRow 35)${serial}" + # Bits 11:0 are the configuration programmed during manufacture. + # Bits 27:16, if set, replace it e.g. after the SDRAM has been reworked. + sdram_config0=$((0x${sdram_otp} & 0xfff)) + sdram_config1=$(((0x${sdram_otp} >> 16) & 0xfff)) +else + # BCM2711 stores a 9-bit SDRAM_CONFIG in bits 16:8 of the board + # attributes row. + sdram_row=${row_attributes} + sdram_otp=${attributes} + sdram_config0=$(((0x${sdram_otp} >> 8) & 0x1ff)) + sdram_config1=0 +fi + +# Running board revision, as used by the OS. +dt_revision=/proc/device-tree/system/linux,revision +if [ -r "${dt_revision}" ]; then + running_revision=$(od -v -An -t x1 "${dt_revision}" | tr -d ' \n') +else + running_revision=$(sed -n 's/^Revision[[:space:]]*: *\([0-9a-fA-F]*\)$/\1/p' /proc/cpuinfo) +fi + +echo "OTP manufacturing information" +echo +echo "Serial number : ${serial}" +[ "${chip}" = 2712 ] && echo "QrCode : $(qrCode)" +echo "Board revision (OTP) : ${otp_revision}" +otp_memsize=$(((0x${otp_revision} >> 20) & 7)) +echo " Memory size field : ${otp_memsize} ($(memsizeName "${otp_memsize}"))" +if [ -n "${running_revision}" ]; then + running_memsize=$(((0x${running_revision} >> 20) & 7)) + echo "Board revision (OS) : ${running_revision}" + echo " Memory size field : ${running_memsize} ($(memsizeName "${running_memsize}"))" +fi +for mac in ${mac_rows}; do + printf '%-23s: %s\n' "${mac%:*} MAC" "$(macFromRows "${mac#*:}")" +done +echo + +# Board attributes. The Wi-Fi and eMMC bits are only used on Compute Modules. +# They were not programmed on older devices, where the board type in the +# board revision is the only indication of a Lite (no eMMC) module. +echo "Board attrs (OTP ${row_attributes}) : ${attributes}" +board_type=$(((0x${otp_revision} >> 4) & 0xff)) +case "${board_type}" in + 20|21|24|26) # CM4, CM4S, CM5, CM5 Lite + if [ $((0x${attributes} & 0x80000000)) -ne 0 ]; then + emmc="Not fitted (Lite)" + elif [ "${board_type}" -eq 26 ]; then + emmc="Not fitted (CM5 Lite board revision)" + else + emmc="Fitted" + fi + echo " eMMC : ${emmc}" + if [ "${board_type}" -ne 21 ]; then + if [ $((0x${attributes} & 0x40000000)) -ne 0 ]; then + wifi="Not fitted" + else + wifi="Fitted" + fi + echo " Wi-Fi : ${wifi}" + fi + ;; +esac +echo + +echo "SDRAM_CONFIG (OTP ${sdram_row}) : ${sdram_otp}" +if [ "${sdram_config0}" -eq 0 ]; then + echo " Manufacture config : Not programmed" +else + echo " Manufacture config : $(decodeSdramConfig "${sdram_config0}")" +fi +if [ "${sdram_config1}" -ne 0 ]; then + echo " Override config : $(decodeSdramConfig "${sdram_config1}")" + sdram_config=${sdram_config1} +else + sdram_config=${sdram_config0} +fi + +gbit=0 +[ "${sdram_config}" -eq 0 ] || gbit=$(sdramSizeGbit "${sdram_config}") +if [ "${gbit}" -eq 0 ]; then + echo " Memory size : $(memsizeName "${otp_memsize}") (from board revision)" + exit 0 +fi + +memsize=$(gbitToMemsize "${gbit}") +echo " Memory size field : ${memsize} ($(memsizeName "${memsize}")) equivalent" +for src in OTP OS; do + if [ "${src}" = OTP ]; then + rev_memsize=${otp_memsize} + else + [ -n "${running_revision}" ] || continue + rev_memsize=${running_memsize} + fi + if [ "${memsize}" -eq "${rev_memsize}" ]; then + result="matches" + else + result="differs from" + fi + echo " SDRAM_CONFIG size ${result} board revision (${src}) memory size" +done diff --git a/rpi-ram-authenticity-check/CMakeLists.txt b/rpi-ram-authenticity-check/CMakeLists.txt deleted file mode 100644 index 99fc7e8..0000000 --- a/rpi-ram-authenticity-check/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -cmake_minimum_required(VERSION 3.10...3.27) - -include(GNUInstallDirs) - -project(rpi-ram-authenticity-check) - -install(PROGRAMS rpi-ram-authenticity-check DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/rpi-ram-authenticity-check/README.md b/rpi-ram-authenticity-check/README.md deleted file mode 100644 index 202fb3e..0000000 --- a/rpi-ram-authenticity-check/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# rpi-ram-authenticity-check - -Read-only RAM authenticity check for BCM2712 devices (the Pi 5 family). -Compares the RAM capacity in the running board revision (from device tree or -`/proc/cpuinfo`) with the factory revision in OTP row 32. - -Run `rpi-ram-authenticity-check` after installing utils, or directly from this -repository: - -```sh -./rpi-ram-authenticity-check/rpi-ram-authenticity-check -``` - -Requires `vcgencmd` and access to `/dev/vcio`; run with `sudo` if access is denied. -Use `--help` for usage information. - -A mismatch warns of possible RAM tampering or replacement and advises contacting -the seller to arrange a return. Matching capacity is reported as "RAM appears -authentic", but cannot detect a replacement with the same capacity. - -Exit status: - -* `0`: capacity matches. -* `1`: capacity differs. -* `2`: unable to check, including unsupported hardware or missing/invalid data. - -When installed, the checker is also called by `raspinfo` on BCM2712 devices. -Its output, including warnings, is included in `raspinfo.txt`. diff --git a/rpi-ram-authenticity-check/rpi-ram-authenticity-check b/rpi-ram-authenticity-check/rpi-ram-authenticity-check deleted file mode 100755 index ead65bb..0000000 --- a/rpi-ram-authenticity-check/rpi-ram-authenticity-check +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/sh - -# Compare BCM2712's reported RAM capacity with its factory revision in OTP. -set -ef - -usage() { - cat <&2 - exit 2 -} - -ramSizeFromRevision() { - case "$1" in - ''|*[!0-9a-fA-F]*) return 1 ;; - esac - [ "${#1}" -le 8 ] || return 1 - # Require a new-style BCM2712 revision before interpreting its RAM field. - [ $(((0x$1 >> 23) & 1)) -eq 1 ] && - [ $(((0x$1 >> 12) & 15)) -eq 4 ] || return 1 - ram_code=$(((0x$1 >> 20) & 7)) - # Code 7 means "Other", not 32 GiB. - [ "${ram_code}" -lt 7 ] || return 1 - echo "$((256 << ram_code))" -} - -if [ "$#" -ne 0 ]; then - if [ "$#" -eq 1 ] && { [ "$1" = -h ] || [ "$1" = --help ]; }; then - usage - exit 0 - fi - usage >&2 - exit 2 -fi - -# Use the same running revision sources as rpi-eeprom-update. Do not fall -# back to OTP here: comparing the factory value with itself would miss changes. -dt_revision=/sys/firmware/devicetree/base/system/linux,revision -if [ -r "${dt_revision}" ]; then - [ "$(wc -c < "${dt_revision}")" -eq 4 ] || die "Invalid device-tree board revision." - board_revision=$(od -v -An -t x1 "${dt_revision}" | tr -d ' \n') -elif [ -r /proc/cpuinfo ]; then - board_revision=$(sed -n '/^Revision/s/^.*: \(.*\)/\1/p' /proc/cpuinfo) -else - die "No running board revision found." -fi -reported_mib=$(ramSizeFromRevision "${board_revision}") || - die "The running board revision must describe a BCM2712 device with a known RAM capacity." - -command -v vcgencmd > /dev/null || die "vcgencmd is required to read the factory RAM capacity." -otp_dump=$(vcgencmd otp_dump) || die "Failed to read OTP; check access to /dev/vcio." -# Row 32 is the BCM2712 factory revision; BCM2711 uses row 30. -factory_revision=$(printf '%s\n' "${otp_dump}" | sed -n 's/^32:\([0-9a-fA-F]\{8\}\)$/\1/p') -factory_mib=$(ramSizeFromRevision "${factory_revision}") || - die "OTP row 32 does not contain a valid BCM2712 revision with a known RAM capacity." - -echo "Factory RAM: ${factory_mib} MiB; reported RAM: ${reported_mib} MiB." -if [ "${factory_mib}" -eq "${reported_mib}" ]; then - echo "RAM appears authentic: capacity matches the factory configuration." - exit 0 -fi - -echo "WARNING: RAM capacity mismatch detected: possible RAM tampering or replacement." >&2 -echo "Contact the seller to arrange a return." >&2 -exit 1