From 94d4bbbe9f3fcb1a06eb134811a5919d10d1a25a Mon Sep 17 00:00:00 2001 From: David Escalona Date: Fri, 30 Jun 2023 17:06:18 +0200 Subject: [PATCH] meta-digi-dey: swupdate-files: add firmware update support based on files Implement a new mechanism to allow users to create update packages based on files and folders to modify the active system. This is done through the new class 'swupdate-files', which creates a tar.gz update file in the image distribution output directory containing all the files and directories to create/update. The 'tar.gz' file is used later by the 'swu-images' recipe to generate the final SWUpdate package. The SWU package installation process extracts the tar.gz file in the root folder ("/") of the active system. Users can specify the list of files and directories to include in the update package using the 'SWUPDATE_FILES_LIST' variable. These files will be directly copied from the generated system rootfs and placed in the tar.gz archive. Additionally, users can provide their custom 'tar.gz' file to use in the update by specifying its location in the 'SWUPDATE_FILES_TARGZ_FILE' variable. In any case, all the paths to include in the update package must be relative to "/", as it is the base directory where tar.gz file contents are extracted. The update process for dual boot systems sets a new u-boot flag so that active bank is not swapped once installation is complete and system reboots. The SWU update mechanism based on files provides a custom update script which takes care of preparing the system for the installation process. Just like in the SWU updates based on images, users can customize this script or override it with the 'SWUPDATE_SCRIPT' variable, specifying the location of the new script to use. If both the 'SWUPDATE_FILES_LIST' and 'SWUPDATE_FILES_TARGZ_FILE' variables are empty, a standard images SWUpdate package will be generated instead. Signed-off-by: David Escalona --- meta-digi-dey/classes/dey-image.bbclass | 3 + .../classes/swupdate-files-common.bbclass | 18 +++ meta-digi-dey/classes/swupdate-files.bbclass | 58 +++++++ .../dualboot/dualboot/update-firmware | 10 ++ .../files/sw-description-files_template | 52 +++++++ .../swu-images/files/update_files.sh | 144 ++++++++++++++++++ meta-digi-dey/recipes-digi/swu-images/swu.inc | 14 +- 7 files changed, 296 insertions(+), 3 deletions(-) create mode 100644 meta-digi-dey/classes/swupdate-files-common.bbclass create mode 100644 meta-digi-dey/classes/swupdate-files.bbclass create mode 100644 meta-digi-dey/recipes-digi/swu-images/files/sw-description-files_template create mode 100755 meta-digi-dey/recipes-digi/swu-images/files/update_files.sh diff --git a/meta-digi-dey/classes/dey-image.bbclass b/meta-digi-dey/classes/dey-image.bbclass index 8d43b7481..21ad42958 100644 --- a/meta-digi-dey/classes/dey-image.bbclass +++ b/meta-digi-dey/classes/dey-image.bbclass @@ -73,3 +73,6 @@ DEPENDS += "${@oe.utils.conditional('TRUSTFENCE_SIGN', '1', 'trustfence-sign-too # Do not include kernel in rootfs images PACKAGE_EXCLUDE = "kernel-image-*" + +# Create 'tar.gz' file for SWUpdate files update mechanism. +inherit swupdate-files diff --git a/meta-digi-dey/classes/swupdate-files-common.bbclass b/meta-digi-dey/classes/swupdate-files-common.bbclass new file mode 100644 index 000000000..4255d9f18 --- /dev/null +++ b/meta-digi-dey/classes/swupdate-files-common.bbclass @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Digi International. +# + +# Variable used to generate the tar.gz file. Do not modify. +SWUPDATE_FILES_TARGZ_FILE_NAME = "swupdate-files.tar.gz" + +# Initialize variable to provide a custom tar.gz file containing files/dirs to install. +SWUPDATE_FILES_TARGZ_FILE ?= "" + +# Initialize variable to store the files/folders that will be part of the SWUpdate package. +SWUPDATE_FILES_LIST ?= "" + +# Checks whether SWU update is based on files or not. +def update_based_on_files(d): + return str(d.getVar('SWUPDATE_FILES_TARGZ_FILE') != "" or d.getVar('SWUPDATE_FILES_LIST') != "").lower() + +# Variable that determines if SWU update is based on files or not. +SWUPDATE_IS_FILES_UPDATE = "${@update_based_on_files(d)}" diff --git a/meta-digi-dey/classes/swupdate-files.bbclass b/meta-digi-dey/classes/swupdate-files.bbclass new file mode 100644 index 000000000..68f3b2849 --- /dev/null +++ b/meta-digi-dey/classes/swupdate-files.bbclass @@ -0,0 +1,58 @@ +# Copyright (C) 2023 Digi International. +# +# Generates a 'tar.gz' file with the files and folders to be included in the update package +# as part of discrete files SWUpdate installation process. +# +# Usage: +# +# In your "local.conf" file, fill the "SWUPDATE_FILES_LIST" variable with the list of +# files/folders to include in the SWUpdate package. Paths must be relative to "/": +# +# SWUPDATE_FILES_LIST = " ..." +# + +# Load commmon variables. +inherit swupdate-files-common + +create_swupdate_targz_file() { + local targzfile="${DEPLOY_DIR_IMAGE}/${SWUPDATE_FILES_TARGZ_FILE_NAME}" + # Clean previous versions of the file. + rm -f "${targzfile}" + + # Create the tar file including the 'sw-versions' file, as it is mandatory. + if [ "${SWUPDATE_FILES_TARGZ_FILE}" != "" ]; then + # User provides a custom tar.gz file. Copy it to distribution dir. + cp "${SWUPDATE_FILES_TARGZ_FILE}" "${targzfile}" + # Uncompress the tar file. + if ! gzip -t "${targzfile}"; then + # File is not correctly compressed, exit with error. + echo "[ERROR] File ${SWUPDATE_FILES_TARGZ_FILE} is not a valid 'tar.gz' file. Aborting..." + exit 1 + fi + gunzip "${targzfile}" + # Add the 'sw-versions' file. + tar -C "${IMAGE_ROOTFS}" -uf "${targzfile%.*}" etc/sw-versions + else + # The tar.gz file is not provided by user. Create it including the 'sw-versions' file + tar -C "${IMAGE_ROOTFS}" -cf "${targzfile%.*}" etc/sw-versions + fi + + # Iterate the list of files and folders. Add all entries directly except paths starting + # with 'mnt/linux'. Those files must be added from the 'DEPLOY_DIR_IMAGE' instead of + # 'IMAGE_ROOTFS', as they are part of the 'boot' image. + for file in ${SWUPDATE_FILES_LIST}; do + case "${file}" in + mnt/linux/*) + FILE_NAME="$(basename "${file}")" + tar -C "${DEPLOY_DIR_IMAGE}" --transform 's,^,mnt/linux/,' -uhf "${targzfile%.*}" "${FILE_NAME}" + ;; + *) + tar -C "${IMAGE_ROOTFS}" -uf "${targzfile%.*}" "${file}" + ;; + esac + done + + # Compress the tar file. + gzip "${targzfile%.*}" +} +ROOTFS_POSTPROCESS_COMMAND:append = "${@oe.utils.conditional('SWUPDATE_IS_FILES_UPDATE', 'true', ' create_swupdate_targz_file;', '', d)}" diff --git a/meta-digi-dey/recipes-digi/dualboot/dualboot/update-firmware b/meta-digi-dey/recipes-digi/dualboot/dualboot/update-firmware index 8c24e896a..2fd76f9b8 100755 --- a/meta-digi-dey/recipes-digi/dualboot/dualboot/update-firmware +++ b/meta-digi-dey/recipes-digi/dualboot/dualboot/update-firmware @@ -116,6 +116,16 @@ reboot_system() { } swap_active_system() { + # Sanity check: Some firmware updates might request not to swap + # active bank after the update. + local SWAP_BANK=$(fw_printenv -n swap_bank) + if [ "${SWAP_BANK}" = "false" ]; then + echo "[WARNING] Active system swap cancelled by update process." + # Reset variable and return without swapping. + fw_setenv swap_bank + return 0 + fi + if [ -z "${EMMCROOTFS}" ]; then fw_setenv mtdbootpart ${ALT_BOOT} fw_setenv mtdrootfspart ${ALT_ROOTFS} diff --git a/meta-digi-dey/recipes-digi/swu-images/files/sw-description-files_template b/meta-digi-dey/recipes-digi/swu-images/files/sw-description-files_template new file mode 100644 index 000000000..3cd479c14 --- /dev/null +++ b/meta-digi-dey/recipes-digi/swu-images/files/sw-description-files_template @@ -0,0 +1,52 @@ +software = +{ + version = "@@DEY_FIRMWARE_VERSION@@"; + description = "@@DESCRIPTION@@"; + + @@SWUPDATE_STORAGE_TYPE@@ = { + primary: { + files: ( + { + filename = "@@SWUPDATE_FILES_TARGZ_FILE_NAME@@"; + type = "archive"; + compressed = "zlib"; + path = "/"; + } + ); + scripts: ( + { + filename = "@@SWUPDATE_SCRIPT_NAME@@"; + type = "shellscript"; + } + ); + uboot: ( + { + name = "swap_bank"; + value = "false"; + } + ); + } + secondary: { + ref = "#./primary"; + } + single: { + files: ( + { + filename = "@@SWUPDATE_FILES_TARGZ_FILE_NAME@@"; + type = "archive"; + compressed = "zlib"; + path = "/system/"; + } + ); + scripts: ( + { + filename = "@@SWUPDATE_SCRIPT_NAME@@"; + type = "shellscript"; + } + ); + } + platform = { + ref = "#./single"; + } + }; +} diff --git a/meta-digi-dey/recipes-digi/swu-images/files/update_files.sh b/meta-digi-dey/recipes-digi/swu-images/files/update_files.sh new file mode 100755 index 000000000..9d28629ec --- /dev/null +++ b/meta-digi-dey/recipes-digi/swu-images/files/update_files.sh @@ -0,0 +1,144 @@ +#!/bin/sh +#=============================================================================== +# +# update_files +# +# Copyright (C) 2023 by Digi International Inc. +# All rights reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 as published by +# the Free Software Foundation. +# +# +# !Description: SWU update files script +# +#=============================================================================== + +# Sanity check. This script should be always executed with at least one argument. +if [ $# -lt 1 ]; then + exit 1; +fi + +# Variables. +FS_TYPE="ext4" +LINUX_DEV_BLOCK="/dev/mmcblk0p1" +LINUX_MOUNT_POINT="/mnt/linux" +ROOTFS_DEV_BLOCK="/dev/mmcblk0p3" +ROOTFS_MOUNT_POINT="/system" + +# Determines whether the file system type is UBI or not. +is_ubifs() { + [ -c "/dev/ubi0" ] +} + +# Retrieves the MTD partition number corresponding to the given partition name. +# +# Args: +# $1: partition name. +# +# Returns: +# The MTD partition number corresponding to the given partition name, -1 if +# not found. +get_mtd_number() { + local mtd_line="$(sed -ne "/${1}/s,^mtd\([0-9]\+\).*,\1,g;T;p" /proc/mtd)" + echo "${mtd_line:--1}" +} + +# Creates the UBI device for the given MTD partition number. +# +# Args: +# $1: the MTD partition number to create the UBI device for. +# +# Returns: +# The created UBI device number for the given MTD partition number, -1 if error. +create_ubi_device() { + local dev_number="$(ubiattach -m "${1}" 2>/dev/null | sed -ne 's,.*device number \([0-9]\).*,\1,g;T;p' 2>/dev/null)" + echo "${dev_number:--1}" +} + +# Retrieves the UBI device number containing the given partition name. If the +# device does not exist, the method attempts to create it based on the MTD dev +# number containing the desired partition. +# +# Args: +# $1: partition name. +# +# Returns: +# The UBI device number containing the given partition name, -1 if not found. +get_ubi_device() { + local ubi_devices="$(ubinfo | grep "Present UBI devices:" | cut -d ":" -f2 | xargs | sed -e 's/,//g')" + for ubi_device in ${ubi_devices}; do + if ubinfo "/dev/${ubi_device}" -a | grep -qe "Name:.*$1"; then + echo "${ubi_device}" | tr -dc '0-9' + return 0 + fi + done + + # Look for the MTD number containing the given partition name. + local mtd_num="$(get_mtd_number "${1}")" + if [ "${mtd_num}" = "-1" ]; then + echo "-1" + return 1 + else + # Create the UBI device. + ubi_device_number="$(create_ubi_device "${mtd_num}")" + echo "${ubi_device_number}" + fi +} + +# Mounts all required partitions to perform the firmware update based on the update +# running source and file system type. +mount_partitions() { + # Determine whether the update is running from recovery partition or not. + BOOT_RECOVERY="$(fw_printenv -n boot_recovery)" + if [ "${BOOT_RECOVERY}" = "yes" ]; then + # Update is running from recovery partition. We need to mount both, + # the rootfs and the kernel partitions. To do so first determine the + # filesystem type, assume it is MMC device. + if is_ubifs; then + FS_TYPE="ubifs" + # Look for the UBI device containing 'linux' partition. + local linux_ubi_device="$(get_ubi_device linux)" + [ "${linux_ubi_device}" = "-1" ] && { echo "Unable to find UBI device containing 'linux' partition."; exit 1; } + LINUX_DEV_BLOCK="ubi${linux_ubi_device}:linux" + # Look for the UBI device containing 'rootfs' partition. + local rootfs_ubi_device="$(get_ubi_device rootfs)" + [ "${rootfs_ubi_device}" = "-1" ] && { echo "Unable to find UBI device containing 'rootfs' partition."; exit 1; } + ROOTFS_DEV_BLOCK="ubi${rootfs_ubi_device}:rootfs" + fi + # Mount 'rootfs' partition. + mkdir -p "${ROOTFS_MOUNT_POINT}" + mount -t "${FS_TYPE}" "${ROOTFS_DEV_BLOCK}" "${ROOTFS_MOUNT_POINT}" + # Mount 'linux' partition. + LINUX_MOUNT_POINT="${ROOTFS_MOUNT_POINT}${LINUX_MOUNT_POINT}" + mkdir -p "${LINUX_MOUNT_POINT}" + if ! is_ubifs; then + FS_TYPE="auto" + fi + mount -t "${FS_TYPE}" "${LINUX_DEV_BLOCK}" "${LINUX_MOUNT_POINT}" + else + # Update is running from the active system. In this case the 'rootfs' and 'linux' + # partitions are already mounted; however 'linux' partition is in R/O mode. Just + # remount 'linux' partition as R/W. + mount -o remount,rw "${LINUX_MOUNT_POINT}" + fi +} + +# Called just before installation process starts. +if [ "${1}" = "preinst" ]; then + mount_partitions + + # TODO: Execute custom code here. For example: + # - Mount additional devices/partitions. + # - Stop services/process before installing files. +fi + +# Called just after installation process ends. +if [ "${1}" = "postinst" ]; then + : + + # TODO: Execute custom code here. For example: + # - Clean directories. + # - Post-process files. +fi diff --git a/meta-digi-dey/recipes-digi/swu-images/swu.inc b/meta-digi-dey/recipes-digi/swu-images/swu.inc index e5cc9e701..e521f03db 100644 --- a/meta-digi-dey/recipes-digi/swu-images/swu.inc +++ b/meta-digi-dey/recipes-digi/swu-images/swu.inc @@ -7,15 +7,17 @@ LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-2.0-only;md5=801f80980d171d SRC_URI = " \ file://sw-description-images_template \ + file://sw-description-files_template \ file://sw-description-uboot \ file://swupdate_uboot_nand.sh \ file://swupdate_uboot_mmc.sh \ file://image_template_mmc \ file://image_template_nand \ file://update_images.sh \ + file://update_files.sh \ " -inherit swupdate +inherit swupdate swupdate-files-common IMAGE_DEPENDS = "${@get_baseimg_pn(d)}" @@ -31,7 +33,7 @@ SWUPDATE_STORAGE_TYPE = "${@oe.utils.conditional('STORAGE_MEDIA', 'mmc', 'mmc', INHIBIT_SWUPDATE_ADD_SRC_URI = "true" SWUPDATE_IMAGES = " \ - ${IMG_NAME} \ + ${@oe.utils.ifelse(d.getVar('SWUPDATE_IS_FILES_UPDATE') == 'true', '${SWUPDATE_FILES_TARGZ_FILE_NAME}', '${IMG_NAME}')} \ ${@oe.utils.ifelse(d.getVar('SWUPDATE_UBOOTIMG') == 'true', '${UBOOT_PREFIX}', '')} \ ${@oe.utils.ifelse(d.getVar('SWUPDATE_UBOOTIMG') == 'true', '${SWUPDATE_UBOOT_SCRIPT}', '')} \ " @@ -73,7 +75,7 @@ ROOTFS_TYPE = "${@bb.utils.contains('IMAGE_FEATURES', 'read-only-rootfs', 'squas IMAGE_TEMPLATE_FILE = "${@bb.utils.contains('STORAGE_MEDIA', 'mmc', '${WORKDIR}/image_template_mmc', '${WORKDIR}/image_template_nand', d)}" # Update script. -SWUPDATE_SCRIPT ?= "${WORKDIR}/update_images.sh" +SWUPDATE_SCRIPT ?= "${@oe.utils.vartrue('SWUPDATE_IS_FILES_UPDATE', '${WORKDIR}/update_files.sh', '${WORKDIR}/update_images.sh', d)}" SWUPDATE_SCRIPT_NAME = "${@os.path.basename(d.getVar('SWUPDATE_SCRIPT'))}" SWUPDATE_IMAGES += " ${SWUPDATE_SCRIPT_NAME}" @@ -91,6 +93,8 @@ fill_description() { sed -i -e "s,##UBOOTIMG_NAME##,${UBOOT_PREFIX}-${MACHINE}${UBOOT_EXT},g" "${WORKDIR}/sw-description" sed -i -e "s,##SWUPDATE_UBOOT_SCRIPT##,${SWUPDATE_UBOOT_SCRIPT},g" "${WORKDIR}/sw-description" sed -i -e "s,##UBOOTIMG_OFFSET##,${UBOOTIMG_OFFSET},g" "${WORKDIR}/sw-description" + elif [ "${SWUPDATE_IS_FILES_UPDATE}" = "true" ]; then + cp ${WORKDIR}/sw-description-files_template ${WORKDIR}/sw-description else cp ${WORKDIR}/sw-description-images_template ${WORKDIR}/sw-description fi @@ -100,6 +104,10 @@ fill_description() { cp "${SWUPDATE_SCRIPT}" "${DEPLOY_DIR_IMAGE}" fi + if [ "${SWUPDATE_IS_FILES_UPDATE}" = "true" ]; then + return 0 + fi + # Build image names. BOOT_IMAGE_NAME="${IMG_NAME}-${MACHINE}${BOOTFS_EXT}" ROOTFS_IMAGE_NAME="${IMG_NAME}-${MACHINE}${ROOTFS_EXT}"