Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add swupdate #115

Merged
merged 11 commits into from Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/free-disk-space/action.yml
@@ -0,0 +1,8 @@
name: 'Free Disk Space'
description: 'Remove content of the provided image to free disk space for the build'
runs:
using: "composite"
steps:
- name: Remove /usr/local/* to free disk space
run: sudo rm -rf /usr/local/*
shell: bash
24 changes: 24 additions & 0 deletions .github/workflows/main.yml
Expand Up @@ -9,6 +9,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Free Disk Space
uses: ./.github/workflows/free-disk-space
- name: Build image
run: ./kas-container build kas-iot2050-example.yml
- name: Upload image
Expand All @@ -25,6 +27,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Free Disk Space
uses: ./.github/workflows/free-disk-space
- name: Build image
run: ./kas-container build kas-iot2050-example.yml:kas/opt/preempt-rt.yml
- name: Upload image
gylstorffq marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -35,12 +39,32 @@ jobs:
build/tmp/deploy/images/iot2050/iot2050-image-example-iot2050-debian-iot2050.wic.img
build/tmp/deploy/images/iot2050/iot2050-image-example-iot2050-debian-iot2050.wic.img.bmap

debian-swupdate-image:
name: Debian SWUpdate image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Free Disk Space
uses: ./.github/workflows/free-disk-space
- name: Build image
run: ./kas-container build kas-iot2050-swupdate.yml
- name: Upload image
uses: actions/upload-artifact@v2
with:
name: iot2050-swupdate-image
path: |
build/tmp/deploy/images/iot2050/iot2050-image-swu-example-iot2050-debian-iot2050.wic.img
build/tmp/deploy/images/iot2050/iot2050-image-swu-example-iot2050-debian-iot2050.wic.img.bmap

bootloaders:
name: Bootloaders for both PG1 and PG2
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Free Disk Space
uses: ./.github/workflows/free-disk-space
- name: Build bootloader image for PG1
run: ./kas-container build kas-iot2050-boot-pg1.yml
- name: Build bootloader image for PG2
Expand Down
1 change: 1 addition & 0 deletions .gitignore
@@ -1,4 +1,5 @@
build/
isar/
/cip-core
recipes-core/customizations-base/local.inc
.config.yaml
1 change: 1 addition & 0 deletions .gitlab-ci.yml
Expand Up @@ -23,6 +23,7 @@ all:

- kas build kas-iot2050-example.yml
- kas build kas-iot2050-example.yml:kas/opt/preempt-rt.yml
- kas build kas-iot2050-swupdate.yml
- sudo rm -rf build/tmp
- kas build kas-iot2050-boot-pg1.yml
- sudo rm -rf build/tmp
Expand Down
9 changes: 8 additions & 1 deletion Kconfig
Expand Up @@ -30,6 +30,12 @@ config IMAGE_LXDE
Based on the example image, this adds an LXDE-based graphical user
interface.

config IMAGE_SWUPDATE
bool "Example image with SWUpdate support"
help
Based on the example image, this adds SWUpdate and changes the
partition layout to an A/B rootfs.

config IMAGE_BOOT_PG1
bool "Firmware image for PG1 devices"
help
Expand All @@ -55,11 +61,12 @@ endchoice
config KAS_INCLUDE_MAIN
string
default "kas-iot2050-example.yml" if IMAGE_EXAMPLE
default "kas-iot2050-swupdate.yml" if IMAGE_SWUPDATE
default "kas-iot2050-lxde.yml" if IMAGE_LXDE
default "kas-iot2050-boot-pg1.yml" if IMAGE_BOOT_PG1
default "kas-iot2050-boot-pg2.yml" if IMAGE_BOOT_PG2

if IMAGE_EXAMPLE || IMAGE_LXDE
if IMAGE_EXAMPLE || IMAGE_LXDE || IMAGE_SWUPDATE

comment "Image features"

Expand Down
71 changes: 71 additions & 0 deletions README.md
Expand Up @@ -116,3 +116,74 @@ device.

NOTE: This selection is not persistent. The boot loader will fall back to its
default boot order after reset.

## Building with SWUpdate support

It is possible to create an image with a SWUpdate based
[double copy root file system](https://sbabic.github.io/swupdate/overview.html#double-copy-with-fall-back) for Over-The-Air updates by selecting the option `Example image with SWUpdate support` during the image configuration with `./kas-container menu`. You can also build the image by calling:

```shell
./kas-container build kas-iot2050-example.yml:kas/opt/swupdate-example.yml
```

You can find the final image under `build/tmp/deploy/images/iot2050/iot2050-image-swu-example-iot2050-debian-iot2050.wic.img`. This image holds the necessary partition layout with two root file systems. The image `iot2050-image-swu-example-iot2050-debian-iot2050.wic.img` can be flashed directly to a SD card as described in section [Booting the image from SD card](#booting-the-image-from-sd-card).

NOTE: As the image contains 2 root file systems, it has a size of 7 Gigabytes.

It also will create a binary for updating the system at `build/tmp/deploy/images/iot2050/iot2050-image-swu-example-iot2050-debian-iot2050.swu`

### Update an image with SWUpdate

The following steps are necessary to update an image created with SWUpdate support.
1. Transfer the SWUpdate binary `iot2050-image-swu-example-iot2050-debian-iot2050.swu`
to the target system.
2. Update the system with SWUpdate by executing:
```shell
$ swupdate -i iot2050-image-swu-example-iot2050-debian-iot2050.swu
gylstorffq marked this conversation as resolved.
Show resolved Hide resolved
gylstorffq marked this conversation as resolved.
Show resolved Hide resolved
```

You can find more details and options for the command `swupdate` in the [SWUpdate documentation](https://sbabic.github.io/swupdate/swupdate.html#running-swupdate).

NOTE: The used SWUpdate package does not contain a web-app example.

SWUpdate will write the image to the unused root partition and
sets the necessary U-Boot variables.
gylstorffq marked this conversation as resolved.
Show resolved Hide resolved

3. Reboot the system into the new root file system. The switch between the root file systems
occurs automatically and requires no user interaction.

4. Confirm that the new root file system is correctly booted.

After a reboot, the device boots into the new root file system. If the boot is
successful the update process needs to be completed by calling:

```shell
$ complete_update.sh success
```

The script sets the update state in the U-Boot environment to the initial state.

If the update is deemed failed, resetting the device will select the previous root file system.
Afterwards the failed update is confirmed by calling:

```shell
$ complete_update.sh failed
```

This call reverts the U-Boot environment to the initial state.
gylstorffq marked this conversation as resolved.
Show resolved Hide resolved
After which you have to manually reboot in order to be effective.

### U-Boot environment

The bootloader environment needs to be adapted to select the correct
root file system during boot. This adaptation occurs automatically during the
first boot by executing the `patch-u-boot-env.service`. This systemd-service
writes the necessary variables (ustate, sysselect) to the U-Boot environment.
After writing the U-Boot environment, an update with SWUpdate is possible.

#### Revert to the default environment

If it is necessary to revert to the default U-Boot environment the following command can be used:
```shell
$ fw_setenv -f /etc/u-boot-initial-env
```
6 changes: 6 additions & 0 deletions conf/machine/iot2050.conf
Expand Up @@ -20,3 +20,9 @@ IMAGE_FSTYPES ?= "wic-img"
WKS_FILE ?= "iot2050.wks.in"

IMAGE_INSTALL += "u-boot-script"

#swupdate
SWUPDATE_BOOTLOADER = "u-boot"
U_BOOT_CONFIG_PACKAGE = "1"
PREFERRED_PROVIDER_u-boot-config ?= "u-boot-iot2050-pg2"
PREFERRED_PROVIDER_u-boot-${MACHINE}-config ?= "u-boot-iot2050-pg2"
21 changes: 21 additions & 0 deletions kas-iot2050-swupdate.yml
@@ -0,0 +1,21 @@
#
# Copyright (c) Siemens AG, 2021
#
# This file is subject to the terms and conditions of the MIT License. See
# COPYING.MIT file in the top-level directory.
#

header:
version: 10
includes:
- kas-iot2050-example.yml

build_system: isar

target: iot2050-image-swu-example

# wic-swu-img extracts a partition and generates a cpio image for
# updating with swupdate
local_conf_header:
generate-swu: |
IMAGE_FSTYPES = "wic-swu-img"
4 changes: 4 additions & 0 deletions kas/iot2050.yml
Expand Up @@ -31,6 +31,10 @@ repos:
layers:
meta:

cip-core:
url: https://gitlab.com/cip-project/cip-core/isar-cip-core.git
refspec: 0feddaa6dd65c5a1b56ff6fcb18b4e748e53972f

bblayers_conf_header:
standard: |
LCONF_VERSION = "6"
Expand Down
9 changes: 9 additions & 0 deletions recipes-bsp/patch-u-boot-env/files/patch-u-boot-env.config
@@ -0,0 +1,9 @@
ustate=0
sysselect=1
toggle_active=echo 'Switching active partition ...'; if test $sysselect = 1;then setenv sysselect 2; else setenv sysselect 1; fi; echo Active system is now $sysselect;

update_sysselect=if env exists ustate && env exists sysselect; then if test $ustate = 0; then echo SWUpdate: no update pending.; elif test $ustate = 1; then echo SWUpdate: ustate = INSTALLED ; setenv ustate 2; run toggle_active; saveenv; elif test $ustate = 2; then echo SWUpdate: ustate = FAILED; setenv ustate 3; run toggle_active; saveenv; elif test $ustate = 3; then echo SWUpdate: still in FAILED; else echo SWUpdate: invalid ustate; fi; setenv distro_bootpart ${sysselect}; if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then run scan_dev_for_boot; fi; else echo SWUpdate: some variables are missing!; fi

scan_dev_for_boot_part_fallback=part list ${devtype} ${devnum} -bootable devplist; env exists devplist || setenv devplist 1; for distro_bootpart in ${devplist}; do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then run scan_dev_for_boot; fi; done; setenv devplist
start_watchdog=wdt dev rti@4061000; wdt start
scan_dev_for_boot_part=if env exists sysselect; then run start_watchdog; run update_sysselect; else run scan_dev_for_boot_part_fallback; fi
20 changes: 20 additions & 0 deletions recipes-bsp/patch-u-boot-env/files/patch-u-boot-env.service
@@ -0,0 +1,20 @@
#
# Copyright (c) Siemens AG, 2021
#
# Authors:
# Quirin Gylstorff <quirin.gylstorff@siemens.com>
#
# SPDX-License-Identifier: MIT

[Unit]
Description=update u-boot environment for swupdate
Conflicts=shutdown.target
Before=swupdate.service shutdown.target

[Service]
Type=oneshot
ExecStart=/usr/share/u-boot-env/patch-u-boot-env.sh /usr/share/u-boot-env/patch-u-boot-env.config
ExecStartPost=-/bin/systemctl disable patch-u-boot-env.service
gylstorffq marked this conversation as resolved.
Show resolved Hide resolved

[Install]
WantedBy=multi-user.target
20 changes: 20 additions & 0 deletions recipes-bsp/patch-u-boot-env/files/patch-u-boot-env.sh
@@ -0,0 +1,20 @@
#!/bin/sh
#
# Copyright (c) Siemens AG, 2021
#
# Authors:
# Quirin Gylstorff <quirin.gylstorff@siemens.com>
#
# SPDX-License-Identifier: MIT
SCRIPT="$1"
# use grep and cut to strip away environment status and get the value
# of sysselect
key="sysselect"
sysselect_value=$(fw_printenv "${key}" | grep "${key}" | cut -d= -f2)

if [ -z "${sysselect_value}" ]; then
if ! fw_setenv -s "${SCRIPT}"; then
echo "$0: could not patch u-boot firmware"
exit 1
fi
fi
10 changes: 10 additions & 0 deletions recipes-bsp/patch-u-boot-env/files/postinst
@@ -0,0 +1,10 @@
#!/bin/sh
#
# Copyright (c) Siemens AG, 2021
#
# Authors:
# Quirin Gylstorff <quirin.gylstorff@siemens.com>
#
# SPDX-License-Identifier: MIT

deb-systemd-helper enable patch-u-boot-env.service || true
27 changes: 27 additions & 0 deletions recipes-bsp/patch-u-boot-env/patch-u-boot-env_0.1.bb
@@ -0,0 +1,27 @@
#
# Copyright (c) Siemens AG, 2021
#
# Authors:
# Quirin Gylstorff <quirin.gylstorff@siemens.com>
#
# SPDX-License-Identifier: MIT

inherit dpkg-raw

DESCRIPTION = "prepare u-boot environment for swupdate"

DEBIAN_DEPENDS = "libubootenv-tool"

SRC_URI += "file://postinst \
file://patch-u-boot-env.config \
file://patch-u-boot-env.sh \
file://patch-u-boot-env.service"

do_install () {
install -v -d ${D}/usr/share/u-boot-env
install -v -m 640 ${WORKDIR}/patch-u-boot-env.config ${D}/usr/share/u-boot-env/patch-u-boot-env.config
install -v -m 755 ${WORKDIR}/patch-u-boot-env.sh ${D}/usr/share/u-boot-env/patch-u-boot-env.sh

install -v -d ${D}/usr/lib/systemd/system
install -v -m 666 ${WORKDIR}/patch-u-boot-env.service ${D}/usr/lib/systemd/system/patch-u-boot-env.service
}
31 changes: 31 additions & 0 deletions recipes-core/data-partition/data-partition_0.1.bb
@@ -0,0 +1,31 @@
#
# Copyright (c) Siemens AG, 2021
#
# Authors:
# Quirin Gylstorff <quirin.gylstorff@siemens.com>
#
# SPDX-License-Identifier: MIT

DESCRIPTION = "data systemd-mount"

DEBIAN_DEPENDS = "systemd"

SRC_URI = "file://postinst \
file://data.mount.tmpl"

FS_COMMIT_INTERVAL ?= "20"
TEMPLATE_VARS += "FS_COMMIT_INTERVAL"
TEMPLATE_FILES += "data.mount.tmpl"

inherit dpkg-raw

do_install() {
install -m 0755 -d ${D}/data
touch ${D}/data/.keep

TARGET=${D}/lib/systemd/system
install -m 0755 -d ${TARGET}
install -m 0644 ${WORKDIR}/data.mount ${TARGET}/data.mount
}

addtask do_install after do_transform_template
12 changes: 12 additions & 0 deletions recipes-core/data-partition/files/data.mount.tmpl
@@ -0,0 +1,12 @@
[Unit]
Description=Mount /data partition
After=expand-on-first-boot.service

[Mount]
What=/dev/disk/by-partlabel/data
Where=/data
Type=ext4
Options=data=journal,noatime,nodiratime,nodelalloc,commit=${FS_COMMIT_INTERVAL}

[Install]
WantedBy=local-fs.target
3 changes: 3 additions & 0 deletions recipes-core/data-partition/files/postinst
@@ -0,0 +1,3 @@
#!/bin/sh

deb-systemd-helper enable data.mount || true
25 changes: 25 additions & 0 deletions recipes-core/images/files/sw-description.tmpl
@@ -0,0 +1,25 @@
#
# Copyright (c) Siemens AG, 2021
#
# Authors:
# Quirin Gylstorff <quirin.gylstorff@siemens.com>
#
# This file is subject to the terms and conditions of the MIT License. See
# COPYING.MIT file in the top-level directory.
#

software =
{
version = "0.1";
name = "iot2050 update image"
images: ({
filename = "${ROOTFS_PARTITION_NAME}";
device = "rootfs1,rootfs2";
type = "roundrobin";
compressed = "zlib";
filesystem = "ext4";
properties: {
subtype = "image";
};
});
}