diff --git a/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager.bb b/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager.bb new file mode 100644 index 00000000..08993358 --- /dev/null +++ b/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager.bb @@ -0,0 +1,42 @@ +FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:" + +DESCRIPTION = "Jetson Orin QSPI manager service" +LICENSE = "Apache-2.0" +LIC_FILES_CHKSUM = "file://${BALENA_COREBASE}/COPYING.Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10" + +SRC_URI = " \ + file://jetson-qspi-manager \ + file://jetson-qspi-helpers \ + file://jetson-qspi-manager.service \ + " + +S = "${WORKDIR}" + +RDEPENDS:${PN} += " bash " + +inherit allarch systemd + +SYSTEMD_SERVICE:${PN} = " \ + jetson-qspi-manager.service \ + " + +do_patch[noexec] = "1" +do_compile[noexec] = "1" +do_build[noexec] = "1" + +do_install() { + install -d ${D}${bindir}/ + install -d ${D}/${libexecdir}/ + install -m 0755 ${WORKDIR}/jetson-qspi-manager ${D}${bindir}/ + install -m 0644 ${WORKDIR}/jetson-qspi-helpers ${D}/${libexecdir}/ + + if ${@bb.utils.contains('DISTRO_FEATURES','systemd','true','false',d)}; then + install -d ${D}${systemd_unitdir}/system/ + install -d ${D}${sysconfdir}/systemd/system/multi-user.target.wants/ + install -m 0644 ${WORKDIR}/jetson-qspi-manager.service ${D}${systemd_unitdir}/system/ + + sed -i -e 's,@BASE_BINDIR@,${base_bindir},g' \ + -e 's,@BINDIR@,${bindir},g' \ + ${D}${systemd_unitdir}/system/*.service + fi +} diff --git a/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager/jetson-qspi-helpers b/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager/jetson-qspi-helpers new file mode 100644 index 00000000..dbc245f5 --- /dev/null +++ b/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager/jetson-qspi-helpers @@ -0,0 +1,175 @@ +#!/bin/bash + +set -e + +. /usr/libexec/os-helpers-logging +. /usr/libexec/os-helpers-fs + +source /usr/bin/uefi_common.func + +MAX_CAPSULE_UPDATE_RETRIES=3 +RETRY_COUNT_FILE="jetson-qspi-retry-count" +CAPSULE_TARGET_PATH="/EFI/UpdateCapsule/TEGRA_BL.Cap" +BOOT_MOUNTPOINT="/mnt/boot/" +UEFI_CAPSULE_TARGET_MOUNTPOINT="$BOOT_MOUNTPOINT" + +efivars_dir="/sys/firmware/efi/efivars/" +# See https://github.com/OE4T/meta-tegra/blob/master/recipes-bsp/tools/setup-nv-boot-control/setup-nv-boot-control.sh.in +platform_spec_efivar="${efivars_dir}TegraPlatformSpec-781e084c-a330-417c-b678-38e696380cb9" +platform_compat_spec_efivar="${efivars_dir}TegraPlatformCompatSpec-781e084c-a330-417c-b678-38e696380cb9" +os_indications_efivar="${efivars_dir}OsIndications-8be4df61-93ca-11d2-aa0d-00e098032b8c" +tmp_file="/tmp/platformspecfile.bin" + +device_type="jetson-orin-nano-devkit" +boardspec=$(tegra-boardspec 2>/dev/null) +TegraPlatformSpec="${boardspec}-${device_type}-" +compatspec=$(echo "$boardspec" | gen_compat_spec) +TegraPlatformCompatSpec="${compatspec}-${device_type}-" + +qspi_accessible() { + if [ -e /dev/mtd0 ]; then + return 0 + else + return 1 + fi +} + +check_qspi_accessible() { + if qspi_accessible ; then + info "QSPI is accessible" + exit 0 + else + info "QSPI is inaccessible" + exit 1 + fi +} + +capsule_update_prepared() { + if [ -e ${os_indications_efivar} ] && [ -e ${CAPSULE_TARGET_PATH} ]; then + return 0 + else + return 1 + fi +} + +write_jetson_update_efivars() { + if [ -d $efivars_dir ]; then + # If the file already exists, writing to it will fail + # causing the entire hook to fail + if [ ! -e ${platform_spec_efivar} ]; then + printf "\x07\x00\x00\x00" > ${tmp_file} + printf "%s" "${TegraPlatformSpec}" >> ${tmp_file} + tmp_file_size=$(stat -c%s ${tmp_file}) + dd if=${tmp_file} of=${platform_spec_efivar} bs=${tmp_file_size} + fi + + if [ ! -e ${platform_compat_spec_efivar} ]; then + printf "\x07\x00\x00\x00" > ${tmp_file} + printf "%s" "${TegraPlatformCompatSpec}" >> ${tmp_file} + tmp_file_size=$(stat -c%s ${tmp_file}) + dd if=${tmp_file} of=${platform_compat_spec_efivar} bs=${tmp_file_size} + #info "PlatformCompatSpec variable created" + else + #info "PlatformCompatSpec variable already exists" + if [[ ${kernel_l4t} == "36.3" ]]; then + #info "PlatformCompatSpec variable already exists and we are running in L4T 36.3, it needs to be re-written." + chattr -i ${platform_compat_spec_efivar} + printf "\x07\x00\x00\x00" > ${tmp_file} + printf "%s" "${TegraPlatformCompatSpec}" >> ${tmp_file} + tmp_file_size=$(stat -c%s ${tmp_file}) + dd if=${tmp_file} of=${platform_compat_spec_efivar} bs=${tmp_file_size} + fi + fi + + printf "%b" '\x07\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00' > ${tmp_file} + dd if=${tmp_file} of=${os_indications_efivar}; bs=12; + else + fail "sysfs not accessible!" + return 1 + fi + + return 0 +} + +# $1 - target path for unpacking UEFI capsule +prepare_capsule() { + uefi_capsule=$(find / -xdev -type f -name "TEGRA_BL*.Cap.gz") + + # Unzip capsule to the boot partition + #info "Will extract UEFI Capsule..." + mkdir -p ${1}/EFI/UpdateCapsule/ + gunzip -k -f -c ${uefi_capsule} | dd of=${1}/${CAPSULE_TARGET_PATH} + sync + #info "Extracted UEFI Capsule." + write_jetson_update_efivars +} + +write_retries_file() { + tmpfile=$(mktemp) + echo "jetson_capsule_retries=$1" > $tmpfile + mv $tmpfile ${2}/${RETRY_COUNT_FILE} && sync + info "Attempt $jetson_capsule_retries: The QSPI will be updated on the next boot" + info "Please do not power off or reset the device during the next boot, until the device shows up online." +} + +# $1 - directory which stores the retries file +remove_retries_file() { + if [ ! -z "${1}" ]; then + retry_count_file_path="${1}/$RETRY_COUNT_FILE" + else + retry_count_file_path="${BOOT_MOUNTPOINT}/${RETRY_COUNT_FILE}" + fi + + if [ -f "${retry_count_file_path}" ]; then + #info "Retry count file found, will be removed" + rm $retry_count_file_path + fi +} + +# $1 - boot partition mountpoint - if unspecified, defaults to /mnt/boot/ +# $2 - UEFI capsule target mountpoint - if unspecified, defaults to /mnt/boot/ +try_capsule_update() { + if [ ! -z "${1}" ]; then + BOOT_MOUNTPOINT=${1} + fi + + if [ ! -z "${2}" ]; then + UEFI_CAPSULE_TARGET_MOUNTPOINT=${2} + fi + + if qspi_accessible; then + info "OK: QSPI is accessible" + remove_retries_file $BOOT_MOUNTPOINT + + exit 0 + fi + + if capsule_update_prepared; then + info "UEFI capsule already triggered externally" + remove_retries_file $BOOT_MOUNTPOINT + + exit 0 + else + if [ -f ${BOOT_MOUNTPOINT}/${RETRY_COUNT_FILE} ]; then + source ${BOOT_MOUNTPOINT}/${RETRY_COUNT_FILE} + + if [ ! -z $jetson_capsule_retries ]; then + if [ $jetson_capsule_retries -le 3 ]; then + prepare_capsule $UEFI_CAPSULE_TARGET_MOUNTPOINT + jetson_capsule_retries=$(( $jetson_capsule_retries + 1 )) + write_retries_file $jetson_capsule_retries $BOOT_MOUNTPOINT + elif [ $jetson_capsule_retries -gt 3 ]; then + warn "Reached max number of capsule update retries!" + warn "Please re-flash your QSPI, or issue 'jetson-qspi-manager -r -u'" + exit 1 + fi + else + warn "Jetson QSPI retry count file empty or corrupt!" + exit 1 + fi + else + prepare_capsule $UEFI_CAPSULE_TARGET_MOUNTPOINT + write_retries_file 1 $BOOT_MOUNTPOINT + fi + fi +} diff --git a/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager/jetson-qspi-manager b/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager/jetson-qspi-manager new file mode 100755 index 00000000..c39d3f4d --- /dev/null +++ b/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager/jetson-qspi-manager @@ -0,0 +1,57 @@ +#!/bin/bash + +set -e + +. /usr/libexec/jetson-qspi-helpers + +help () { + cat << EOF +Script for QSPI management on Jetson Orin Devices +jetson-qspi-manager [options] + +Options: + -h, --help + Display this help and exit. + + -r, --reset + Remove the retries count file, and thus reset the number of capsule updates + + -u, --prepare-update + Prepare the UEFI capsule update to be applied on the next boot + + -c, --check + Check if the QSPI is accessible +EOF +} + +# Parse arguments +if [ "$#" -eq "0" ]; then + help + exit 1 +else + while [ "$#" -gt "0" ]; do + key=$1 + case $key in + -h|--help) + help + exit 0 + ;; + -c|--check) + check_qspi_accessible + shift + ;; + -u|--prepare-update) + try_capsule_update + shift + ;; + -r|--reset) + remove_retries_file + shift + ;; + *) + echo "[WARNING] $0 : Argument '$1' unknown." + exit 1 + ;; + esac + done +fi diff --git a/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager/jetson-qspi-manager.service b/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager/jetson-qspi-manager.service new file mode 100644 index 00000000..c7464a0a --- /dev/null +++ b/layers/meta-balena-jetson/recipes-support/jetson-qspi-manager/jetson-qspi-manager/jetson-qspi-manager.service @@ -0,0 +1,14 @@ +[Unit] +Description=Jetson Orin QSPI management service +DefaultDependencies=no +After=resin-boot.service +Before=umount.target +Conflicts=umount.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@BASE_BINDIR@/sh -c '@BINDIR@/jetson-qspi-manager -u' + +[Install] +WantedBy=multi-user.target