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 <david.escalona@digi.com>
This commit is contained in:
David Escalona 2023-06-30 17:06:18 +02:00
parent e5392996ed
commit 94d4bbbe9f
7 changed files with 296 additions and 3 deletions

View File

@ -73,3 +73,6 @@ DEPENDS += "${@oe.utils.conditional('TRUSTFENCE_SIGN', '1', 'trustfence-sign-too
# Do not include kernel in rootfs images # Do not include kernel in rootfs images
PACKAGE_EXCLUDE = "kernel-image-*" PACKAGE_EXCLUDE = "kernel-image-*"
# Create 'tar.gz' file for SWUpdate files update mechanism.
inherit swupdate-files

View File

@ -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)}"

View File

@ -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 = "<folder_path> <file_path> ..."
#
# 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)}"

View File

@ -116,6 +116,16 @@ reboot_system() {
} }
swap_active_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 if [ -z "${EMMCROOTFS}" ]; then
fw_setenv mtdbootpart ${ALT_BOOT} fw_setenv mtdbootpart ${ALT_BOOT}
fw_setenv mtdrootfspart ${ALT_ROOTFS} fw_setenv mtdrootfspart ${ALT_ROOTFS}

View File

@ -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";
}
};
}

View File

@ -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

View File

@ -7,15 +7,17 @@ LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-2.0-only;md5=801f80980d171d
SRC_URI = " \ SRC_URI = " \
file://sw-description-images_template \ file://sw-description-images_template \
file://sw-description-files_template \
file://sw-description-uboot \ file://sw-description-uboot \
file://swupdate_uboot_nand.sh \ file://swupdate_uboot_nand.sh \
file://swupdate_uboot_mmc.sh \ file://swupdate_uboot_mmc.sh \
file://image_template_mmc \ file://image_template_mmc \
file://image_template_nand \ file://image_template_nand \
file://update_images.sh \ file://update_images.sh \
file://update_files.sh \
" "
inherit swupdate inherit swupdate swupdate-files-common
IMAGE_DEPENDS = "${@get_baseimg_pn(d)}" 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" INHIBIT_SWUPDATE_ADD_SRC_URI = "true"
SWUPDATE_IMAGES = " \ 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', '${UBOOT_PREFIX}', '')} \
${@oe.utils.ifelse(d.getVar('SWUPDATE_UBOOTIMG') == 'true', '${SWUPDATE_UBOOT_SCRIPT}', '')} \ ${@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)}" IMAGE_TEMPLATE_FILE = "${@bb.utils.contains('STORAGE_MEDIA', 'mmc', '${WORKDIR}/image_template_mmc', '${WORKDIR}/image_template_nand', d)}"
# Update script. # 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_SCRIPT_NAME = "${@os.path.basename(d.getVar('SWUPDATE_SCRIPT'))}"
SWUPDATE_IMAGES += " ${SWUPDATE_SCRIPT_NAME}" 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,##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,##SWUPDATE_UBOOT_SCRIPT##,${SWUPDATE_UBOOT_SCRIPT},g" "${WORKDIR}/sw-description"
sed -i -e "s,##UBOOTIMG_OFFSET##,${UBOOTIMG_OFFSET},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 else
cp ${WORKDIR}/sw-description-images_template ${WORKDIR}/sw-description cp ${WORKDIR}/sw-description-images_template ${WORKDIR}/sw-description
fi fi
@ -100,6 +104,10 @@ fill_description() {
cp "${SWUPDATE_SCRIPT}" "${DEPLOY_DIR_IMAGE}" cp "${SWUPDATE_SCRIPT}" "${DEPLOY_DIR_IMAGE}"
fi fi
if [ "${SWUPDATE_IS_FILES_UPDATE}" = "true" ]; then
return 0
fi
# Build image names. # Build image names.
BOOT_IMAGE_NAME="${IMG_NAME}-${MACHINE}${BOOTFS_EXT}" BOOT_IMAGE_NAME="${IMG_NAME}-${MACHINE}${BOOTFS_EXT}"
ROOTFS_IMAGE_NAME="${IMG_NAME}-${MACHINE}${ROOTFS_EXT}" ROOTFS_IMAGE_NAME="${IMG_NAME}-${MACHINE}${ROOTFS_EXT}"