add emmc writer

This commit is contained in:
2025-06-27 15:24:30 +02:00
committed by l_samenv
parent 871c65ff9e
commit fe95372127
10 changed files with 289 additions and 0 deletions

3
rpi/mass-storage-gadget64/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.h
boot.sig
bootfiles.original.bin

View File

@ -0,0 +1,58 @@
# USB mass-storage gadget for BCM2711 and BCM2712
This directory provides a bootloader image that loads a Linux
initramfs that exports common block devices (EMMC, NVMe) as
USB mass storage devices using the Linux gadget-fs drivers.
This allows Raspberry Pi Imager to be run on the host computer
and write OS images to the Raspberry Pi or Compute Module block devices.
## Running
To run load the USB MSD device drivers via RPIBOOT run
```bash
rpiboot -d mass-storage-gadget64
```
### Debug
The mass-storage-gadget image automatically enables a UART console for debugging (user `root` empty password).
## Secure boot
Once secure-boot has been enable the OS `boot.img` file must be signed with the customer private key.
On Pi5 firmware must also be counter-signed with this key.
The `sign.sh` script wraps the command do this on Pi4 and Pi5.
```bash
KEY_FILE=$HOME/private.pem
./sign.sh ${KEY_FILE}
```
or as follows if using a HSM wrapper script.
```bash
./sign.sh -H hsm-wrapper public.pem
```
WARNING: The signed images will not be bootable on a Pi5 without secure-boot enabled. Run `./reset.sh` to reset the signed images to the default unsigned state.
## Source code
The buildroot configuration and supporting patches is available on
the [mass-storage-gadget64](https://github.com/raspberrypi/buildroot/tree/mass-storage-gadget64)
branch of the Raspberry Pi [buildroot](https://github.com/raspberrypi/buildroot) repo.
### Building
In order to build directly on a Linux host that has the needed dependencies, run:
```bash
git clone --branch mass-storage-gadget64 git@github.com:raspberrypi/buildroot.git
cd buildroot
make raspberrypi64-mass-storage-gadget_defconfig
make
```
The output is written to `output/images/sdcard.img` and can be copied to `boot.img`
Alternatively, if you have docker installed and would like to use the upstream buildroot CI docker image for a build environment, use its `utils/docker-run` helper script:
```bash
$ git clone --branch mass-storage-gadget64 git@github.com:raspberrypi/buildroot.git
$ cd buildroot
$ ./utils/docker-run /bin/bash -c "make raspberrypi64-mass-storage-gadget_defconfig && make"
```

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
# Load boot.img containing the Linux initramfs for the mass-storage-gadget
# In signed-boot or secure-boot mode the bootloader checks the
# RSA signature of the ramdisk. The signature is located in boot.sig
boot_ramdisk=1
uart_2ndstage=1

View File

@ -0,0 +1,4 @@
#!/bin/sh
rm -f boot.sig bootfiles.bin bootfiles.original.bin
ln -sf ../firmware/bootfiles.bin .

View File

@ -0,0 +1,71 @@
#!/bin/sh
set -e
set -u
script_dir="$(cd "$(dirname "$0")" && pwd)"
TMP_DIR=""
SIGN_ARGS=""
PUBLIC_KEY=""
die() {
echo "$@" >&2
exit 1
}
cleanup() {
if [ -d "${TMP_DIR}" ]; then rm -rf "${TMP_DIR}"; fi
}
sign_firmware_blob() {
echo "Signing firmware in ${1} using $(which rpi-sign-bootcode)"
rpi-sign-bootcode \
-c 2712 \
-i "${1}" \
-o "${2}" \
-n 16 \
-v 0 \
${SIGN_ARGS} ${PUBLIC_KEY}
}
sign_bootfiles() {
echo "Signing OS image ${1}"
input="${1}"
output="${2}"
(
cd "${TMP_DIR}"
tar -xf "${input}"
echo "Signing 2712/bootcode5.bin"
sign_firmware_blob 2712/bootcode5.bin 2712/bootcode5.bin.signed || die "Failed to sign bootcode5.bin"
mv -f "2712/bootcode5.bin.signed" "2712/bootcode5.bin"
tar -cf "${output}" *
find .
)
}
trap cleanup EXIT
if [ "${1}" = "-H" ]; then
HSM_WRAPPER="${2}"
PUBLIC_KEY="${3}"
[ -f "${PUBLIC_KEY}" ] || die "HSM requires a public key file in PEM format. Public key \"${PUBLIC_KEY}\" not found."
PUBLIC_KEY="-p ${3}"
if ! command -v "${HSM_WRAPPER}"; then
die "HSM wrapper script \"${HSM_WRAPPER}\" not found"
fi
SIGN_ARGS="-H ${HSM_WRAPPER}"
else
KEY_FILE="${1}"
[ -f "${KEY_FILE}" ] || die "KEY_FILE: ${KEY_FILE} not found"
SIGN_ARGS="-k ${KEY_FILE}"
fi
PATH="${script_dir}/../tools:${PATH}"
KEY_FILE="${1}"
TMP_DIR="$(mktemp -d)"
rm -f bootfiles.bin
ln -sf ../firmware/bootfiles.bin bootfiles.original.bin
sign_bootfiles "$(pwd)/bootfiles.original.bin" "$(pwd)/bootfiles.bin"
echo "Signing boot.img with ${SIGN_ARGS}"
rpi-eeprom-digest -i boot.img -o boot.sig ${SIGN_ARGS}

BIN
rpi/rpiboot Executable file

Binary file not shown.

148
rpi/write_from_rpi Executable file
View File

@ -0,0 +1,148 @@
#!/usr/bin/env python3
import sys
import os
import time
import re
from select import select
from glob import glob
from subprocess import check_output, STDOUT, PIPE, Popen, TimeoutExpired
IMAGES_HOST = 'l_samenv@linse-c'
IMAGES_DIR = 'boxes/rpi_images'
IMAGES_PAT = '*.lz4'
extdisks = {}
def prt(text):
print(time.strftime('%H:%M:%S'), text)
def cmd(command, verbose=False):
if verbose:
print(f'> {command}')
return check_output(command.split(), stderr=STDOUT).decode('latin-1')
def get_disks():
return [line for line in cmd('lsblk -d').split('\n') if line]
def rpiboot():
lines = []
with Popen(['sudo', './rpiboot', '-d', 'mass-storage-gadget64'], stdout=PIPE) as p:
tmo = 1
t = 0
while select([p.stdout], [], [], tmo)[0]:
rawline = p.stdout.readline()
if not rawline:
return tmo != 1
line = rawline.decode('latin-1').strip()
print(line)
if lines is None:
pass # print(line)
elif 'Waiting' in line:
tmo = 10
lines.append(line)
t = time.time()
elif tmo == 1:
lines.append(line)
else:
print('\n'.join(lines))
print(line)
lines = None
else:
p.terminate()
return False
def list_external():
print('--- external disks:')
for disk, info in extdisks.items():
print(disk, info)
rescue = """
Remark: not all cables might work.
Example on Markus Mac:
- does not work: black USB C to USB C cable
- works: white USB A to USB C cable plugged into docking station.
Please unpower the box with compute module,
start this script again and put power immediately."""
def wait_disks(prevset, delay):
for i in range(int(delay)):
disks = get_disks()
diskset = set(disks)
if diskset != prevset:
print('\n'.join(disks))
if i:
print('')
return diskset - prevset
print('.', end='')
time.sleep(1)
return None
def find_raspi(kind='emmc'):
input('make sure device is not connected [confirm]')
prevset = set(get_disks())
input('now connect [confirm]')
disks = wait_disks(prevset, 5)
if not disks:
print('can not yet detect disk, try to run rpiboot')
print('if is blocked at "Waiting for BCM...", turn off and on power of target rpi')
# cmd('sudo ./rpiboot -d mass-storage-gadget64')
rpiboot()
disks = wait_disks(prevset, 15)
if not disks:
raise RuntimeError('raspi disk does not appear')
return disks
def write_image(kind):
proposed = [v.split()[0] for v in find_raspi(kind)]
list_external()
if len(proposed) == 1:
dev = proposed[0]
elif len(proposed) > 1:
if kind == 'emmc':
print('several potential devices:', proposed)
return
dev = input('select device to write to {" ".join(proposed)}: ')
else:
print('not found')
return
images = check_output(['sudo', '-u', 'l_samenv', 'ssh', IMAGES_HOST, f'cd {IMAGES_DIR} ; ls {IMAGES_PAT}']).decode('latin-1').split('\n')
for file in sorted(images):
print(file)
print('')
image = input(f'select image to write to {dev} [{file}]: ') or file
prt(f'unmount {dev} ...')
print('you may be prompted for your account password for sudo')
os.system(f'sudo umount /dev/{dev}')
print('')
prt('get, uncompress and write... (takes about 15 min)')
os.system(f'ssh {IMAGES_HOST} "dd if={IMAGES_DIR}/{image} bs=512k" | lz4 -d | sudo dd of=/dev/{dev} bs=512k')
prt('unmount bootfs')
for _ in range(3):
if os.path.exists('/Volumes/bootfs'):
prt('unmount bootfs')
os.system('sudo umount bootfs')
break
time.sleep(1.0)
prt('done')
os.system(f'sudo umount /dev/{dev}')
if len(sys.argv) == 2:
write_image(sys.argv[1])
else:
print("""
Usage:
> write_to emmc # write to compute module using PoE board"
> write_to sd # write to sd card
""")