tegra-flash-init: add recipe for supporting initrd flashing

* Booted via RCM boot process, running entirely within
  the initramfs
* Accepts command sequence from host for handling specific
  flashing steps
* Programs boot device (SPI flash or mmcblk0bootX devices)
  from signed binaries sent from host
* Exports storage devices via USB gadget to host for
  partitioning/writing

Signed-off-by: Matt Madison <matt@madison.systems>
This commit is contained in:
Matt Madison
2022-12-04 08:09:39 -08:00
committed by Matt Madison
parent b112feca67
commit 5e23c6117b
4 changed files with 452 additions and 0 deletions

View File

@@ -0,0 +1,210 @@
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
mount -t proc proc -o nosuid,nodev,noexec /proc
mount -t devtmpfs none -o nosuid /dev
mount -t sysfs sysfs -o nosuid,nodev,noexec /sys
mount -t configfs configfs -o nosuid,nodev,noexec /sys/kernel/config
[ ! /usr/sbin/wd_keepalive ] || /usr/sbin/wd_keepalive &
sernum=$(cat /sys/devices/platform/efuse-burn/ecid 2>/dev/null)
[ -n "$sernum" ] || sernum=$(cat /sys/module/tegra_fuse/tegra_chip_uid 2>/dev/null)
if [ -n "$sernum" ]; then
# Restricted to 8 characters for the ID_MODEL tag
sernum=$(printf "%x" "$sernum" | tail -c8)
fi
[ -n "$sernum" ] || sernum="UNKNOWN"
UDC=$(ls -1 /sys/class/udc | head -n 1)
wait_for_storage() {
local file_or_dev="$1"
local message="Waiting for $file_or_dev..."
local tries
for tries in $(seq 1 15); do
if [ -e "$file_or_dev" ]; then
if [ "$message" = "." ]; then
echo "[OK]"
else
echo "Found $file_or_dev"
fi
break
fi
echo -n "$message"
message="."
sleep 1
done
if [ $tries -ge 15 ]; then
echo "[FAIL]"
return 1
fi
return 0
}
setup_usb_export() {
local storage_export="$1"
local export_name="$2"
wait_for_storage "$storage_export" || return 1
if [ -e /sys/kernel/config/usb_gadget/l4t ]; then
gadget-vid-pid-remove 1d6b:104
fi
sed -e"s,@SERIALNUMBER@,$sernum," -e"s,@STORAGE_EXPORT@,$storage_export," /etc/initrd-flash/initrd-flash.scheme.in > /run/initrd-flash.scheme
chmod 0644 /run/initrd-flash.scheme
gadget-import l4t /run/initrd-flash.scheme
printf "%-8s%-16s" "$export_name" "$sernum" > /sys/kernel/config/usb_gadget/l4t/functions/mass_storage.l4t_storage/lun.0/inquiry_string
echo "$UDC" > /sys/kernel/config/usb_gadget/l4t/UDC
if [ -e /sys/class/usb_role/usb2-0-role-switch/role ]; then
echo "device" > /sys/class/usb_role/usb2-0-role-switch/role
fi
echo "Exported $storage_export as $export_name"
return 0
}
wait_for_connect() {
local suspended
local count=0
echo -n "Waiting for host to connect..."
while true; do
suspended=$(expr $(cat /sys/class/udc/$UDC/device/gadget/suspended) \+ 0)
if [ $suspended -eq 0 ]; then
echo "[connected]"
break
fi
sleep 1
count=$(expr $count \+ 1)
if [ $count -ge 5 ]; then
echo -n "."
count=0
fi
done
return 0
}
wait_for_disconnect() {
local suspended
local count=0
echo -n "Waiting for host to disconnect..."
while true; do
suspended=$(expr $(cat /sys/class/udc/$UDC/device/gadget/suspended) \+ 0)
if [ $suspended -eq 1 ]; then
echo "[disconnected]"
break
fi
sleep 1
count=$(expr $count \+ 1)
if [ $count -ge 5 ]; then
echo -n "."
count=0
fi
done
echo "" > /sys/kernel/config/usb_gadget/l4t/UDC
return 0
}
get_flash_package() {
rm -rf /tmp/flashpkg_tree
mkdir -p /tmp/flashpkg_tree/flashpkg/logs
# Top of mount point on host will only be writable by root, so
# create a world-writable subdirectory so a user can send us
# the commands and content.
chmod 777 /tmp/flashpkg_tree/flashpkg
echo "PENDING: expecting command sequence from host" > /tmp/flashpkg_tree/flashpkg/status
dd if=/dev/zero of=/tmp/flashpkg.ext4 bs=1M count=128 > /dev/null || return 1
mke2fs -t ext4 -d /tmp/flashpkg_tree /tmp/flashpkg.ext4 > /dev/null || return 1
setup_usb_export /tmp/flashpkg.ext4 flashpkg || return 1
wait_for_connect || return 1
wait_for_disconnect || return 1
return 0
}
process_bootloader_package() {
rm -f /run/bootloader-status
if program-boot-device /tmp/flashpkg/flashpkg/bootloader; then
echo "SUCCESS" > /run/bootloader-status
else
echo "FAILED" > /run/bootloader-status
fi
}
skip_status_report=
reboot_type=
final_status="FAILED"
wait_for_bootloader=
if ! get_flash_package; then
echo "Error retrieving flashing package" >&2
skip_status_report=yes
else
mkdir -p /tmp/flashpkg
mount -t ext4 /tmp/flashpkg.ext4 /tmp/flashpkg
fi
if [ ! -e /tmp/flashpkg/flashpkg/conf/command_sequence ]; then
echo "No command sequence in flash package, nothing to do"
else
exit_early=
while read cmd args; do
[ -z "$exit_early" ] || break
echo "Processing: $cmd $args"
case "$cmd" in
bootloader)
process_bootloader_package 2>&1 > /tmp/flashpkg/flashpkg/logs/bootloader.log &
wait_for_bootloader=yes
;;
erase-mmc)
if [ -b /dev/mmcblk0 ]; then
blkdiscard -f /dev/mmcblk0 2>&1 > /tmp/flashpkg/flashpkg/logs/erase-mmc.log
else
echo "/dev/mmcblk0 does not exist, skipping" > /tmp/flashpkg/flashpkg/logs/erase-mmc.log
fi
;;
export-devices)
for dev in $args; do
if setup_usb_export /dev/$dev $dev 2>&1 > /tmp/flashpkg/flashpkg/logs/export-$dev.log; then
if wait_for_connect 2>&1 >> /tmp/flashpkg/flashpkg/logs/export-$dev.log; then
if wait_for_disconnect 2>&1 >> /tmp/flashpkg/flashpkg/logs/export-$dev.log; then
continue
fi
fi
fi
echo "Export of $dev failed" >&2
exit_early=yes
break
done
;;
reboot)
reboot_type="$args"
final_status="SUCCESS"
# reboot command is expected to be the last in the sequence, if present
break
;;
*)
echo "Unrecognized command: $cmd $args" > /tmp/flashpkg/flashpkg/logs/commandloop.log
exit_early="yes"
;;
esac
done < /tmp/flashpkg/flashpkg/conf/command_sequence
fi
if [ -n "$wait_for_bootloader" ]; then
message="Waiting for boot device programming to complete..."
while [ ! -e /run/bootloader-status ]; do
echo -n "$message"
message="."
sleep 1
done
blstatus=$(cat /run/bootloader-status)
if [ "$blstatus" = "FAILED" ]; then
final_status="FAILED"
fi
fi
echo "$final_status" > /tmp/flashpkg/flashpkg/status
if [ -z "$skip_status_report" ]; then
umount /tmp/flashpkg && setup_usb_export /tmp/flashpkg.ext4 flashpkg && wait_for_connect && wait_for_disconnect
fi
if [ "$reboot_type" = "forced-recovery" ]; then
reboot-recovery
else
reboot -f
fi

View File

@@ -0,0 +1,66 @@
attrs :
{
bcdUSB = 0x210;
bDeviceClass = 0x0;
bDeviceSubClass = 0x0;
bDeviceProtocol = 0x0;
bMaxPacketSize0 = 0x40;
idVendor = 0x1D6B;
idProduct = 0x104;
bcdDevice = 0x1;
};
os_descs :
{
config_id = 1;
use = 1;
qw_sign = "MSFT100";
b_vendor_code = 0xcd;
};
strings = (
{
lang = 0x409;
manufacturer = "NVIDIA";
product = "Linux for Tegra";
serialnumber = "@SERIALNUMBER@";
} );
functions :
{
mass_storage_l4t :
{
instance = "l4t_storage";
type = "mass_storage";
attrs :
{
stall = true;
luns = (
{
cdrom = false;
ro = false;
nofua = false;
removable = true;
file = "@STORAGE_EXPORT@";
});
};
os_descs = ( );
};
};
configs = (
{
id = 1;
name = "L4T";
attrs :
{
bmAttributes = 0x80;
bMaxPower = 0x2;
};
strings = (
{
lang = 0x409;
configuration = "UMS";
} );
functions = (
{
name = "ums.0";
function = "mass_storage_l4t";
} );
} );

View File

@@ -0,0 +1,147 @@
#!/bin/sh
BOOTPART_SIZE=
BOOTDEV_TYPE=unset
program_spi_partition() {
local partname="$1"
local part_offset="$2"
local part_size="$3"
local part_file="$4"
local file_size=0
if [ -n "$part_file" ]; then
file_size=$(stat -c "%s" "$part_file")
if [ -z "$file_size" ]; then
echo "ERR: could not retrieve file size of $part_file" >&2
return 1
fi
fi
if [ $file_size -ne 0 ]; then
echo "Writing $part_file (size=$file_size) to $partname (offset=$part_offset)"
if ! mtd_debug write /dev/mtd0 $part_offset $file_size "$part_file"; then
return 1
fi
fi
# Multiple copies of the BCT get installed at erase-block boundaries
# within the defined BCT partition
if [ "$partname" = "BCT" ]; then
local slotsize=$(cat /sys/class/mtd/mtd0/erasesize)
if [ -z "$slotsize" ]; then
return 1
fi
local rounded_slot_size=$(expr \( \( $slotsize \+ 511 \) / 512 \) \* 512)
local curr_offset=$(expr $part_offset \+ $rounded_slot_size)
local copycount=$(expr $part_size / $rounded_slot_size)
local i=1
while [ $i -lt $copycount ]; do
echo "Writing $part_file to BCT+$i (offset=$curr_offset)"
if ! mtd_debug write /dev/mtd0 $curr_offset $file_size "$part_file"; then
return 1
fi
i=$(expr $i \+ 1)
curr_offset=$(expr $curr_offset \+ $rounded_slot_size)
done
fi
return 0
}
program_mmcboot_partition() {
local partname="$1"
local part_offset="$2"
local part_size="$3"
local part_file="$4"
local file_size=0
local bootpart="/dev/mmcblk0boot0"
if [ -z "$BOOTPART_SIZE" ]; then
echo "ERR: boot partition size not set" >&2
return 1
fi
if [ $part_offset -ge $BOOTPART_SIZE ]; then
part_offset=$(expr $part_offset - $BOOTPART_SIZE)
bootpart="/dev/mmcblk0boot1"
fi
if [ -n "$part_file" ]; then
file_size=$(stat -c "%s" "$part_file")
if [ -z "$file_size" ]; then
echo "ERR: could not retrieve file size of $part_file" >&2
return 1
fi
fi
if [ $file_size -ne 0 ]; then
echo "Writing $part_file (size=$file_size) to $partname on $bootpart (offset=$part_offset)"
if ! dd if="$part_file" of="$bootpart" bs=1 seek=$part_offset count=$file_size > /dev/null; then
return 1
fi
fi
return 0
}
program_boot_partitions() {
local blpkgdir="$1"
local partname part_offset part_size part_file
local rc=0
if [ -z "$blpkgdir" -o ! -d "$blpkgdir" ]; then
echo "ERR: missing or non-directory: $blpkgdir" >&2
return 1
fi
if [ -f "$blpkgdir/boot_device_type" ]; then
BOOTDEV_TYPE=$(cat "$blpkgdir/boot_device_type")
else
echo "ERR: missing boot_device_type file in $blpkgdir" >&2
return 1
fi
if [ ! -f "$blpkgdir/partitions.conf" ]; then
echo "ERR: partition config missing in $blpkgdir" >&2
return 1
fi
if [ "$BOOTDEV_TYPE" = "mmcboot" ]; then
if [ ! -b /dev/mmcblk0boot0 -o ! -b /dev/mmcblk0boot1 ]; then
echo "ERR: eMMC boot device, but mmcblk0bootX devices do not exist" >&2
return 1
fi
BOOTPART_SIZE=$(expr $(cat /sys/block/mmcblk0boot0/size) \* 512)
echo "0" > /sys/block/mmcblk0boot0/force_ro
echo "0" > /sys/block/mmcblk0boot1/force_ro
blkdiscard -f /dev/mmcblk0boot0
blkdiscard -f /dev/mmcblk0boot1
elif [ "$BOOTDEV_TYPE" = "spi" ]; then
if [ ! -e /dev/mtd0 ]; then
echo "ERR: SPI boot device, but mtd0 device does not exist" >&2
return 1
fi
echo "Erasing /dev/mtd0"
flash_erase /dev/mtd0 0 0
else
echo "ERR: unknown boot device type: $BOOTDEV_TYPE" >&2
return 1
fi
oldwd="$PWD"
cd "$blpkgdir"
while IFS=":" read partname part_offset part_size part_file; do
if [ "$BOOTDEV_TYPE" = "spi" ]; then
if ! program_spi_partition "$partname" "$part_offset" "$part_size" "$part_file"; then
rc=1
break
fi
elif [ "$BOOTDEV_TYPE" = "mmcboot" ]; then
if ! program_mmcboot_partition "$partname" "$part_offset" "$part_size" "$part_file"; then
rc=1
break
fi
fi
done < partitions.conf
if [ "$BOOTDEV_TYPE" = "mmcboot" ]; then
echo "1" > /sys/block/mmcblk0boot0/force_ro
echo "1" > /sys/block/mmcblk0boot1/force_ro
fi
cd "$oldwd"
return $rc
}
program_boot_partitions "$@"

View File

@@ -0,0 +1,29 @@
DESCRIPTION = "Minimal initramfs init script for initrd flashing"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "\
file://init-flash.sh \
file://program-boot-device.sh \
file://initrd-flash.scheme.in \
"
COMPATIBLE_MACHINE = "(tegra)"
S = "${WORKDIR}"
do_install() {
install -m 0755 ${WORKDIR}/init-flash.sh ${D}/init
install -m 0555 -d ${D}/proc ${D}/sys
install -m 0755 -d ${D}/dev ${D}/mnt ${D}/run ${D}/usr
install -m 1777 -d ${D}/tmp
mknod -m 622 ${D}/dev/console c 5 1
install -d ${D}${bindir}
install -m 0755 ${WORKDIR}/program-boot-device.sh ${D}${bindir}/program-boot-device
install -d ${D}${sysconfdir}/initrd-flash
install -m 0644 ${WORKDIR}/initrd-flash.scheme.in ${D}${sysconfdir}/initrd-flash/
}
FILES:${PN} = "/"
RDEPENDS:${PN} = "util-linux-blkdiscard tegra-flash-reboot mtd-utils e2fsprogs-mke2fs libusbgx-tegra-initrd-flash watchdog-keepalive"
RRECOMMENDS:${PN} = "kernel-module-spi-tegra114 kernel-module-loop"