add encryption - sb and encryption now working with rauc ota updates
This commit is contained in:
31
AGENTS.md
31
AGENTS.md
@@ -69,4 +69,33 @@ Expected secure boot log lines: `secure-boot`, `rsa-verify pass (0x0)`, then U-B
|
|||||||
```bash
|
```bash
|
||||||
update-pieeprom.sh -k private.pem && rpiboot -d secure-boot-recovery
|
update-pieeprom.sh -k private.pem && rpiboot -d secure-boot-recovery
|
||||||
```
|
```
|
||||||
> Use existing `private.pem` — never regenerate it.
|
> Use existing `private.pem` — never regenerate it.
|
||||||
|
|
||||||
|
## Disk Encryption (/data partition) ✅ COMPLETE
|
||||||
|
`/dev/mmcblk0p3` is LUKS2-encrypted using the device-unique 256-bit OTP private key.
|
||||||
|
Key is read from OTP at every boot via `/dev/vcio` (VideoCore mailbox).
|
||||||
|
No key ever touches disk — tmpfs only.
|
||||||
|
|
||||||
|
**Self-healing:** If service finds a LUKS header that can't be opened (e.g. stale header
|
||||||
|
surviving a sparse bmaptool flash), it wipes the first 4 MB and re-formats automatically.
|
||||||
|
|
||||||
|
**Note:** `lsblk` is not installed on target — use `cryptsetup status` instead.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check /data is mounted + encrypted:
|
||||||
|
sshpass -p beacon ssh user@$CM4 'sudo cryptsetup status data'
|
||||||
|
# Expected: type: LUKS2, cipher: aes-xts-plain64, keysize: 512 bits, mode: read/write
|
||||||
|
|
||||||
|
# Read OTP key (hex, stable across reboots) — requires root:
|
||||||
|
sshpass -p beacon ssh user@$CM4 'sudo beacon-otp-key'
|
||||||
|
|
||||||
|
# Verify LUKS2 header + keyslots:
|
||||||
|
sshpass -p beacon ssh user@$CM4 'sudo cryptsetup luksDump /dev/mmcblk0p3'
|
||||||
|
```
|
||||||
|
|
||||||
|
**OTA update behavior:** RAUC only writes to rootfs.0/1 — data partition is untouched.
|
||||||
|
On reboot into new slot, service opens LUKS with same OTP key → same data accessible.
|
||||||
|
Verified: A→B and B→A OTA both maintain encrypted /data correctly.
|
||||||
|
|
||||||
|
**Security model:** OTP key is protected by secure boot (only signed boot.img runs).
|
||||||
|
Root processes within the signed OS can still read OTP via `/dev/vcio` (RPi hardware limitation).
|
||||||
@@ -1,2 +1 @@
|
|||||||
# Nothing to see here (yet)
|
source "$BR2_EXTERNAL_BEACON_PATH/package/beacon-otp/Config.in"
|
||||||
#source "$BR2_EXTERNAL_BEACON_PATH/package/blah/Config.in"
|
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
image data.ext4 {
|
|
||||||
name = "Data"
|
|
||||||
mountpoint = /data
|
|
||||||
ext4 {
|
|
||||||
use-mke2fs = true
|
|
||||||
label = "Data"
|
|
||||||
features = "^64bit"
|
|
||||||
}
|
|
||||||
size = 128M
|
|
||||||
}
|
|
||||||
|
|
||||||
image upload.ext4 {
|
image upload.ext4 {
|
||||||
name = "Upload"
|
name = "Upload"
|
||||||
empty = true
|
empty = true
|
||||||
@@ -61,7 +50,6 @@ image sdcard.img {
|
|||||||
|
|
||||||
partition data {
|
partition data {
|
||||||
partition-type = 0x83
|
partition-type = 0x83
|
||||||
image = "data.ext4"
|
|
||||||
size = 128M
|
size = 128M
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ CONFIG_BLK_DEV_LOOP=y
|
|||||||
CONFIG_DM_VERITY=y
|
CONFIG_DM_VERITY=y
|
||||||
CONFIG_SQUASHFS=y
|
CONFIG_SQUASHFS=y
|
||||||
CONFIG_CRYPTO_SHA256=y
|
CONFIG_CRYPTO_SHA256=y
|
||||||
|
CONFIG_CRYPTO_SHA512=y
|
||||||
CONFIG_DM_CRYPT=y
|
CONFIG_DM_CRYPT=y
|
||||||
CONFIG_CRYPTO_AES=y
|
CONFIG_CRYPTO_AES=y
|
||||||
CONFIG_CRYPTO_XTS=y
|
CONFIG_CRYPTO_XTS=y
|
||||||
|
CONFIG_CRYPTO_USER_API_HASH=y
|
||||||
|
CONFIG_CRYPTO_USER_API_SKCIPHER=y
|
||||||
|
|||||||
@@ -24,17 +24,23 @@ fi
|
|||||||
|
|
||||||
|
|
||||||
# Mount persistent data partitions
|
# Mount persistent data partitions
|
||||||
|
# /data is handled by beacon-encrypt-data.service (LUKS2 encrypted, key from OTP)
|
||||||
if [ -e ${TARGET_DIR}/etc/fstab ]; then
|
if [ -e ${TARGET_DIR}/etc/fstab ]; then
|
||||||
# For configuration data
|
# Remove any stale LABEL=Data entry left from previous builds
|
||||||
# WARNING: data=journal is safest, but potentially slow!
|
sed -i '/LABEL=Data/d' ${TARGET_DIR}/etc/fstab
|
||||||
grep -qE 'LABEL=Data' ${TARGET_DIR}/etc/fstab || \
|
# For bulk data (eg: firmware updates) — unencrypted
|
||||||
echo "LABEL=Data /data ext4 defaults,data=journal,noatime 0 0" >> ${TARGET_DIR}/etc/fstab
|
|
||||||
|
|
||||||
# For bulk data (eg: firmware updates)
|
|
||||||
grep -qE 'LABEL=Upload' ${TARGET_DIR}/etc/fstab || \
|
grep -qE 'LABEL=Upload' ${TARGET_DIR}/etc/fstab || \
|
||||||
echo "LABEL=Upload /upload ext4 defaults,noatime 0 0" >> ${TARGET_DIR}/etc/fstab
|
echo "LABEL=Upload /upload ext4 defaults,noatime 0 0" >> ${TARGET_DIR}/etc/fstab
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Enable beacon-encrypt-data.service (runs before local-fs.target to mount /data)
|
||||||
|
mkdir -p "${TARGET_DIR}/etc/systemd/system/local-fs.target.wants"
|
||||||
|
ln -sf ../beacon-encrypt-data.service \
|
||||||
|
"${TARGET_DIR}/etc/systemd/system/local-fs.target.wants/beacon-encrypt-data.service"
|
||||||
|
|
||||||
|
# Ensure the service script is executable
|
||||||
|
chmod 0755 "${TARGET_DIR}/usr/sbin/beacon-encrypt-data.sh" 2>/dev/null || true
|
||||||
|
|
||||||
# Copy custom cmdline.txt file
|
# Copy custom cmdline.txt file
|
||||||
install -D -m 0644 ${BR2_EXTERNAL_BEACON_PATH}/board/beacon-cm4/cmdline.txt ${BINARIES_DIR}/custom/cmdline.txt
|
install -D -m 0644 ${BR2_EXTERNAL_BEACON_PATH}/board/beacon-cm4/cmdline.txt ${BINARIES_DIR}/custom/cmdline.txt
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=LUKS2 encrypted data partition setup
|
||||||
|
Documentation=man:cryptsetup(8)
|
||||||
|
DefaultDependencies=no
|
||||||
|
Conflicts=umount.target
|
||||||
|
After=systemd-udevd.service
|
||||||
|
Before=local-fs.target umount.target
|
||||||
|
# Wait for the block device to appear
|
||||||
|
After=dev-mmcblk0p3.device
|
||||||
|
Wants=dev-mmcblk0p3.device
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
ExecStart=/usr/sbin/beacon-encrypt-data.sh
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=local-fs.target
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# beacon-encrypt-data.sh
|
||||||
|
#
|
||||||
|
# On first boot: LUKS2-format /dev/mmcblk0p3 with device OTP key, create ext4, mount /data
|
||||||
|
# On later boots: open LUKS2 container with device OTP key, mount /data
|
||||||
|
#
|
||||||
|
# If the OTP key is all-zeros (not programmed) the partition is mounted unencrypted
|
||||||
|
# so the system is still usable on a non-secure-boot device during development.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DATA_DEV="/dev/mmcblk0p3"
|
||||||
|
MAPPER_NAME="data"
|
||||||
|
MAPPER_DEV="/dev/mapper/${MAPPER_NAME}"
|
||||||
|
MOUNT_POINT="/data"
|
||||||
|
OTP_TOOL="/usr/sbin/beacon-otp-key"
|
||||||
|
|
||||||
|
log() { echo "beacon-encrypt-data: $*"; }
|
||||||
|
die() { echo "beacon-encrypt-data: ERROR: $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# Block device must exist
|
||||||
|
[ -b "${DATA_DEV}" ] || { log "${DATA_DEV} not found, skipping"; exit 0; }
|
||||||
|
|
||||||
|
# OTP tool must exist
|
||||||
|
[ -x "${OTP_TOOL}" ] || die "OTP tool not found at ${OTP_TOOL}"
|
||||||
|
|
||||||
|
# Write OTP key into a tmpfs file so it never touches disk
|
||||||
|
KEY_FILE="$(mktemp /dev/shm/otp-XXXXXX 2>/dev/null || mktemp /tmp/otp-XXXXXX)"
|
||||||
|
trap 'rm -f "${KEY_FILE}"' EXIT INT TERM
|
||||||
|
|
||||||
|
OTP_READ_OK=1
|
||||||
|
if ! ${OTP_TOOL} -b > "${KEY_FILE}"; then
|
||||||
|
log "WARNING: OTP key read failed — falling back to unencrypted /data"
|
||||||
|
OTP_READ_OK=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if key is all zeros (OTP not programmed — dev/test device)
|
||||||
|
KEY_HEX=""
|
||||||
|
if [ "${OTP_READ_OK}" = "1" ]; then
|
||||||
|
KEY_HEX="$(${OTP_TOOL} 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mount_unencrypted() {
|
||||||
|
if ! blkid "${DATA_DEV}" >/dev/null 2>&1; then
|
||||||
|
log "No filesystem on ${DATA_DEV} — creating ext4 (unencrypted)"
|
||||||
|
mkfs.ext4 -q -L "Data" "${DATA_DEV}" || die "mkfs.ext4 failed"
|
||||||
|
fi
|
||||||
|
mount -t ext4 -o defaults,noatime "${DATA_DEV}" "${MOUNT_POINT}" \
|
||||||
|
|| die "mount ${MOUNT_POINT} failed"
|
||||||
|
log "${MOUNT_POINT} is ready (unencrypted)"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${OTP_READ_OK}" = "0" ] || [ -z "$(echo "${KEY_HEX}" | tr -d '0')" ]; then
|
||||||
|
log "OTP key is all-zeros or unreadable — mounting ${DATA_DEV} unencrypted"
|
||||||
|
mount_unencrypted
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Encrypted path ---
|
||||||
|
luks_format() {
|
||||||
|
log "Formatting /dev/mmcblk0p3 with LUKS2"
|
||||||
|
dd if=/dev/zero of="${DATA_DEV}" bs=1M count=4 status=none 2>/dev/null || true
|
||||||
|
cryptsetup luksFormat \
|
||||||
|
--batch-mode \
|
||||||
|
--type luks2 \
|
||||||
|
--key-file "${KEY_FILE}" \
|
||||||
|
--key-size 512 \
|
||||||
|
--cipher aes-xts-plain64 \
|
||||||
|
--hash sha256 \
|
||||||
|
--pbkdf pbkdf2 \
|
||||||
|
"${DATA_DEV}" \
|
||||||
|
|| die "luksFormat failed"
|
||||||
|
cryptsetup luksOpen "${DATA_DEV}" "${MAPPER_NAME}" \
|
||||||
|
--key-file "${KEY_FILE}" \
|
||||||
|
|| die "luksOpen after format failed"
|
||||||
|
log "Creating ext4 filesystem inside encrypted container"
|
||||||
|
mkfs.ext4 -q -L "DataEnc" "${MAPPER_DEV}" \
|
||||||
|
|| die "mkfs.ext4 failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
if cryptsetup isLuks "${DATA_DEV}" 2>/dev/null; then
|
||||||
|
log "Opening existing LUKS2 container on ${DATA_DEV}"
|
||||||
|
if ! cryptsetup luksOpen "${DATA_DEV}" "${MAPPER_NAME}" \
|
||||||
|
--key-file "${KEY_FILE}" 2>/dev/null; then
|
||||||
|
log "WARNING: luksOpen failed (stale header or wrong key) — re-formatting"
|
||||||
|
luks_format
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "No LUKS header on ${DATA_DEV} — formatting with LUKS2 (first boot)"
|
||||||
|
luks_format
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Mounting ${MAPPER_DEV} at ${MOUNT_POINT}"
|
||||||
|
mount -t ext4 -o defaults,noatime "${MAPPER_DEV}" "${MOUNT_POINT}" \
|
||||||
|
|| die "mount ${MOUNT_POINT} failed"
|
||||||
|
|
||||||
|
log "${MOUNT_POINT} is ready (encrypted)"
|
||||||
@@ -40,6 +40,8 @@ BR2_PACKAGE_RAUC_NETWORK=y
|
|||||||
BR2_PACKAGE_RAUC_JSON=y
|
BR2_PACKAGE_RAUC_JSON=y
|
||||||
BR2_PACKAGE_DROPBEAR=y
|
BR2_PACKAGE_DROPBEAR=y
|
||||||
BR2_PACKAGE_CRYPTSETUP=y
|
BR2_PACKAGE_CRYPTSETUP=y
|
||||||
|
BR2_PACKAGE_E2FSPROGS=y
|
||||||
|
BR2_PACKAGE_BEACON_OTP=y
|
||||||
BR2_PACKAGE_UTIL_LINUX_WDCTL=y
|
BR2_PACKAGE_UTIL_LINUX_WDCTL=y
|
||||||
BR2_TARGET_ROOTFS_EXT2=y
|
BR2_TARGET_ROOTFS_EXT2=y
|
||||||
BR2_TARGET_ROOTFS_EXT2_4=y
|
BR2_TARGET_ROOTFS_EXT2_4=y
|
||||||
|
|||||||
6
package/beacon-otp/Config.in
Normal file
6
package/beacon-otp/Config.in
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
config BR2_PACKAGE_BEACON_OTP
|
||||||
|
bool "beacon-otp"
|
||||||
|
help
|
||||||
|
Reads the device-specific 256-bit private key from RPi OTP
|
||||||
|
via the VideoCore mailbox (/dev/vcio). Used by the
|
||||||
|
beacon-encrypt-data service to unlock the LUKS2 data partition.
|
||||||
22
package/beacon-otp/beacon-otp.mk
Normal file
22
package/beacon-otp/beacon-otp.mk
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# beacon-otp
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
BEACON_OTP_VERSION = local
|
||||||
|
BEACON_OTP_SITE = $(BR2_EXTERNAL_BEACON_PATH)/package/beacon-otp/src
|
||||||
|
BEACON_OTP_SITE_METHOD = local
|
||||||
|
BEACON_OTP_LICENSE = MIT
|
||||||
|
|
||||||
|
define BEACON_OTP_BUILD_CMDS
|
||||||
|
$(TARGET_CC) $(TARGET_CFLAGS) $(TARGET_LDFLAGS) \
|
||||||
|
-o $(@D)/beacon-otp-key $(@D)/beacon-otp-key.c
|
||||||
|
endef
|
||||||
|
|
||||||
|
define BEACON_OTP_INSTALL_TARGET_CMDS
|
||||||
|
$(INSTALL) -D -m 0750 $(@D)/beacon-otp-key \
|
||||||
|
$(TARGET_DIR)/usr/sbin/beacon-otp-key
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(generic-package))
|
||||||
84
package/beacon-otp/src/beacon-otp-key.c
Normal file
84
package/beacon-otp/src/beacon-otp-key.c
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/* beacon-otp-key.c
|
||||||
|
* Read the device-specific private key from RPi OTP via the VideoCore mailbox.
|
||||||
|
* Usage: beacon-otp-key [-b]
|
||||||
|
* (no args) print 64-char hex string + newline
|
||||||
|
* -b write 32 raw bytes to stdout (for use as a key-file)
|
||||||
|
*/
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define IOCTL_MBOX_PROPERTY _IOWR(100, 0, char *)
|
||||||
|
#define TAG_GET_PRIVATE_KEY 0x00030081u
|
||||||
|
#define KEY_WORDS 8 /* 8 x 32-bit = 256-bit key */
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int binary = (argc > 1 && strcmp(argv[1], "-b") == 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mailbox property buffer layout (uint32_t words):
|
||||||
|
* [0] total message size in bytes
|
||||||
|
* [1] process-request code (0)
|
||||||
|
* [2] tag id
|
||||||
|
* [3] value-buffer size in bytes = (2 + KEY_WORDS) * 4
|
||||||
|
* [4] request/response indicator (0 = request)
|
||||||
|
* [5] offset into OTP keystore (0)
|
||||||
|
* [6] number of words to read
|
||||||
|
* [7 .. 6+KEY_WORDS] key data (output)
|
||||||
|
* [7+KEY_WORDS] end tag (0)
|
||||||
|
*/
|
||||||
|
uint32_t buf[7 + KEY_WORDS + 1];
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
buf[0] = (uint32_t)sizeof(buf);
|
||||||
|
buf[1] = 0x00000000;
|
||||||
|
buf[2] = TAG_GET_PRIVATE_KEY;
|
||||||
|
buf[3] = (2 + KEY_WORDS) * 4;
|
||||||
|
buf[4] = 0;
|
||||||
|
buf[5] = 0;
|
||||||
|
buf[6] = KEY_WORDS;
|
||||||
|
buf[7 + KEY_WORDS] = 0;
|
||||||
|
|
||||||
|
int fd = open("/dev/vcio", O_RDWR);
|
||||||
|
if (fd < 0) {
|
||||||
|
perror("beacon-otp-key: open /dev/vcio");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ioctl(fd, IOCTL_MBOX_PROPERTY, buf) < 0) {
|
||||||
|
perror("beacon-otp-key: ioctl MBOX_PROPERTY");
|
||||||
|
close(fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if (buf[1] != 0x80000000u) {
|
||||||
|
fprintf(stderr, "beacon-otp-key: mailbox error 0x%08x\n", buf[1]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < KEY_WORDS; i++) {
|
||||||
|
uint32_t w = buf[7 + i];
|
||||||
|
if (binary) {
|
||||||
|
uint8_t b[4] = {
|
||||||
|
(uint8_t)(w & 0xff),
|
||||||
|
(uint8_t)((w >> 8) & 0xff),
|
||||||
|
(uint8_t)((w >>16) & 0xff),
|
||||||
|
(uint8_t)((w >>24) & 0xff)
|
||||||
|
};
|
||||||
|
if (fwrite(b, 1, 4, stdout) != 4) {
|
||||||
|
perror("beacon-otp-key: fwrite");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("%08x", w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!binary)
|
||||||
|
printf("\n");
|
||||||
|
fflush(stdout);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
96
scripts/flash-cm4-sb.sh
Executable file
96
scripts/flash-cm4-sb.sh
Executable file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# flash-cm4.sh - Flash CM4 eMMC while EMMC_DISABLE jumper is bridged
|
||||||
|
# Usage: ./scripts/flash-cm4.sh [/dev/sdX]
|
||||||
|
# If no device given, auto-detects the CM4 USB mass storage device.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
BEACON_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$BEACON_DIR/.." && pwd)"
|
||||||
|
USBBOOT_DIR="$REPO_ROOT/usbboot"
|
||||||
|
MSD_DIR="$USBBOOT_DIR/secure-boot-msd"
|
||||||
|
IMAGE="$REPO_ROOT/output/images/sdcard.img.xz"
|
||||||
|
PRIVATE_KEY="$REPO_ROOT/private.pem"
|
||||||
|
|
||||||
|
# Build rpiboot from source if not already compiled
|
||||||
|
if [ ! -x "$USBBOOT_DIR/rpiboot" ]; then
|
||||||
|
echo "==> Building rpiboot from source..."
|
||||||
|
make -C "$USBBOOT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure secure-boot-msd boot.img is signed (required for secure-boot-locked CM4)
|
||||||
|
if [ ! -f "$MSD_DIR/boot.sig" ] || [ "$MSD_DIR/boot.img" -nt "$MSD_DIR/boot.sig" ]; then
|
||||||
|
echo "==> Signing secure-boot-msd/boot.img with private.pem..."
|
||||||
|
"$USBBOOT_DIR/tools/rpi-eeprom-digest" -i "$MSD_DIR/boot.img" \
|
||||||
|
-o "$MSD_DIR/boot.sig" -k "$PRIVATE_KEY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
find_removable_sd() {
|
||||||
|
lsblk -dno NAME,RM | awk '$2==1{print $1}' | grep '^sd' | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Step 1: Expose CM4 eMMC as USB mass storage (skip if device already present)
|
||||||
|
if [ -n "${1:-}" ] && [ -b "${1}" ]; then
|
||||||
|
echo "==> $1 already present — skipping rpiboot."
|
||||||
|
elif [ -z "${1:-}" ] && [ -n "$(find_removable_sd)" ]; then
|
||||||
|
echo "==> Removable block device already present — skipping rpiboot."
|
||||||
|
else
|
||||||
|
# NOTE: mass-storage-gadget64 is rejected by secure-boot-locked CM4s.
|
||||||
|
# Use secure-boot-msd (signed boot.img) instead.
|
||||||
|
echo "==> Running rpiboot to expose CM4 eMMC (EMMC_DISABLE jumper must be bridged)..."
|
||||||
|
sudo "$USBBOOT_DIR/rpiboot" -d "$MSD_DIR"
|
||||||
|
echo "==> rpiboot done."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 2: Find the device (explicit arg or auto-detect)
|
||||||
|
if [ -n "${1:-}" ]; then
|
||||||
|
DEVICE="$1"
|
||||||
|
echo "==> Using device: $DEVICE — waiting for it to appear..."
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
[ -b "$DEVICE" ] && break
|
||||||
|
sleep 1
|
||||||
|
printf " waiting for %s... (%ds)\r" "$DEVICE" "$i"
|
||||||
|
done
|
||||||
|
if [ ! -b "$DEVICE" ]; then
|
||||||
|
echo "ERROR: $DEVICE did not appear as a block device within 30s."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
DEVICE=""
|
||||||
|
echo "==> Waiting for removable block device..."
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
DEV=$(find_removable_sd)
|
||||||
|
if [ -n "$DEV" ]; then
|
||||||
|
DEVICE="/dev/$DEV"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
printf " waiting... (%ds)\r" "$i"
|
||||||
|
done
|
||||||
|
if [ -z "$DEVICE" ]; then
|
||||||
|
echo "ERROR: No removable block device found within 30s."
|
||||||
|
echo " Run 'lsblk' to find it, then re-run: $0 /dev/sdX"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "==> Auto-detected CM4 eMMC at $DEVICE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 3: Safety check - refuse to flash the host NVMe disk
|
||||||
|
if echo "$DEVICE" | grep -qE '^/dev/nvme'; then
|
||||||
|
echo "ERROR: $DEVICE looks like the host NVMe. Aborting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 4: Unmount any auto-mounted partitions
|
||||||
|
echo "==> Unmounting $DEVICE partitions..."
|
||||||
|
sudo umount "${DEVICE}"?* 2>/dev/null || true
|
||||||
|
sudo umount "${DEVICE}"[0-9]* 2>/dev/null || true
|
||||||
|
|
||||||
|
# Step 5: Flash via bmaptool
|
||||||
|
echo "==> Flashing $IMAGE -> $DEVICE ..."
|
||||||
|
sudo bmaptool copy "$IMAGE" "$DEVICE"
|
||||||
|
sudo sync
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> Flash complete!"
|
||||||
|
echo " Remove the EMMC_DISABLE jumper, then power-cycle the CM4."
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# flash-cm4.sh - Flash CM4 eMMC while EMMC_DISABLE jumper is bridged
|
|
||||||
# Usage: ./scripts/flash-cm4.sh [/dev/sdX]
|
|
||||||
# If no device given, auto-detects the CM4 USB mass storage device.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
BEACON_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
||||||
REPO_ROOT="$(cd "$BEACON_DIR/.." && pwd)"
|
|
||||||
USBBOOT_DIR="$REPO_ROOT/usbboot"
|
|
||||||
IMAGE="$REPO_ROOT/output/images/sdcard.img.xz"
|
|
||||||
|
|
||||||
# Build rpiboot from source if not already compiled
|
|
||||||
if [ ! -x "$USBBOOT_DIR/rpiboot" ]; then
|
|
||||||
echo "==> Building rpiboot from source..."
|
|
||||||
make -C "$USBBOOT_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 1: Expose CM4 eMMC as USB mass storage
|
|
||||||
echo "==> Running rpiboot to expose CM4 eMMC (EMMC_DISABLE jumper must be bridged)..."
|
|
||||||
sudo "$USBBOOT_DIR/rpiboot" -d "$USBBOOT_DIR/mass-storage-gadget64"
|
|
||||||
echo "==> rpiboot done, waiting for block device..."
|
|
||||||
|
|
||||||
# Step 2: Find the device (explicit arg or auto-detect USB disk ~8 GiB)
|
|
||||||
if [ -n "${1:-}" ]; then
|
|
||||||
DEVICE="$1"
|
|
||||||
echo "==> Using specified device: $DEVICE"
|
|
||||||
else
|
|
||||||
DEVICE=""
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
sleep 1
|
|
||||||
# Detect USB block device of 7-8 GiB (CM4 eMMC)
|
|
||||||
DEVICE=$(lsblk -dno NAME,TRAN,SIZE \
|
|
||||||
| awk '$2=="usb" && ($3~/^7\.[0-9]+G$/ || $3~/^8\.[0-9]+G$/) {print "/dev/"$1}' \
|
|
||||||
| head -1)
|
|
||||||
[ -n "$DEVICE" ] && break
|
|
||||||
printf " waiting... (%ds)\r" "$i"
|
|
||||||
done
|
|
||||||
if [ -z "$DEVICE" ]; then
|
|
||||||
echo "ERROR: CM4 eMMC did not appear as a USB block device within 30s."
|
|
||||||
echo " Run 'lsblk' manually and re-run with explicit device: $0 /dev/sdX"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "==> Auto-detected CM4 eMMC at $DEVICE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 3: Safety check - refuse to flash the host nvme/sata disk
|
|
||||||
if echo "$DEVICE" | grep -qE '^/dev/(nvme|sd[a-z]{2,}|sda$)'; then
|
|
||||||
lsblk -dno TRAN "$DEVICE" | grep -qx usb || {
|
|
||||||
echo "ERROR: $DEVICE does not appear to be a USB device. Aborting."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 4: Unmount any auto-mounted partitions
|
|
||||||
echo "==> Unmounting $DEVICE partitions..."
|
|
||||||
sudo umount "${DEVICE}"?* 2>/dev/null || true
|
|
||||||
sudo umount "${DEVICE}"[0-9]* 2>/dev/null || true
|
|
||||||
|
|
||||||
# Step 5: Flash via bmaptool
|
|
||||||
echo "==> Flashing $IMAGE -> $DEVICE ..."
|
|
||||||
sudo bmaptool copy "$IMAGE" "$DEVICE"
|
|
||||||
sudo sync
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "==> Flash complete!"
|
|
||||||
echo " Remove the EMMC_DISABLE jumper, then power-cycle the CM4."
|
|
||||||
Reference in New Issue
Block a user