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

raspberrypi: Proper A/B kernel Updates and Atomic Boot Partition Updates #12

Open
om26er opened this issue Apr 7, 2021 · 40 comments
Open

Comments

@om26er
Copy link
Contributor

om26er commented Apr 7, 2021

Currently we have been using meta-rauc-community and it works fine, the A/B partitioning scheme and OTA updates work as well (we use RAUC).

Since there is only a single partition for boot, I wonder how to implement OTA updates for the kernel ?

@leon-anavi
Copy link
Collaborator

Hi @om26er,

You should be able to achieve this using:

IMAGE_INSTALL_append = " kernel-image kernel-devicetree"

Btw meta-mender-raspberrypi for Mender A/B updates could be used as a reference example for such feature.

Best regards, Leon

@timemaster5
Copy link

Hi,
I am also interested in this topic, and I am not sure whether that suggested solution helps.

On raspberry pi, the kernel and devicetree is stored on boot partition, which is not included in update process. The update process using rauc only updates rootfs. So even if you include kernel image and devicetree files in the rootfs, rasperry won't use it after update.

Maybe it is a good first step which could be later on extended about copying these files to the boot partition, maybe using a post update hook. But it is not a recommended way because the update is not atomic. So if there is a problem during update, the raspberry pi will not boot again at all. Also there is no fallback to previously known good kernel image.

What I think is the best way is to implement boot-mbr-switch method described in the rauc documentation here, which consists of two boot partitions stored on the card and switching between them by manipulating partition table. Which is atomic and therefore more safe. This way, we can also implement a fallback in case of boot fail which is probably the most important reason. Because otherwise the only change of fixing the pi is to reflash the sd card or at least the boot partition.

The setup should not be hard, the only thing I have a problem with is where to store the u-boot environment file. This file is currently on the /boot partition, so we need to move it out, somewhere to the bare unallocated space of the sd card. Because we will have two different boot partitions. U-boot can also save two copies of this file to be sure that we won't use a broken one on boot.

@om26er
Copy link
Contributor Author

om26er commented Apr 8, 2021

I found some useful information on this page as well https://github.com/mendersoftware/meta-mender/tree/master/meta-mender-raspberrypi#meta-mender-raspberrypi

It seems mender only supports kernel updates (by putting the kernel on the rootfs) and DTB updates are not supported. For our project, being able to update the kernel and DTB is very important so we'd explore more and see what could be done.

@om26er
Copy link
Contributor Author

om26er commented Apr 8, 2021

maybe if @timemaster5 can find a solution to this stuff, it'll make life easier for us ;-)

@timemaster5
Copy link

timemaster5 commented Apr 8, 2021

Problem with me is that I don't have any time right now. I am working on a project where I must deliver something under the deadline. So for now I end up with extensive research on this topic, and I hope someone else will be able to take over and do the implementation. If not, I will definitely get back at some point, because I would like this feature implemented too, but currently can't say when.

From what I know, I am pretty sure the required changes are:

  1. Add the second /boot into wic, but exclude it from the partition table
part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label bootA --active --align 4096 --size 20
part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label bootB --align 4096 --size 20 --no-table
part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label systemA --align 4096 --size 1536
part / --ondisk mmcblk0 --fstype=ext4 --label systemB --align 4096 --size 1536
  1. Patch u-boot to store env file, and redundant env file on the SD card outside of our /boot partitions. It could even be exactly what mender requires in this patch

  2. Update system.conf to contain newly created boot partitions, something like:

[slot.bootloader.0]
device=/dev/mmcblk0
type=boot-mbr-switch
region-start=164352 <- this value needs to be fixed
region-size=20M

[slot.bootloader.1]
device=/dev/mmcblk0
type=boot-mbr-switch
region-start=164352 <- this value needs to be fixed
region-size=20M
  1. Add bootimg-partition FS into an update bundle. It should be only a matter of adding it into RAUC_BUNDLE_SLOTS in the update bundle recipe

  2. Update u-boot startup script a little bit. Here I have no idea what would be the best to do..
    Maybe it could be taken from another project where updating boot-loader partition works. We probably want to switch to a second boot-loader slot only if the boot process fails after the update of boot-loader partition, otherwise only switch rootfs one

Region start values, corresponding partition sizes and alignments changes in wic file, and in the patch could be based on the suggested layout from the rauc documentation.

And that's probably it.

Sorry I can't help more at this point

@leon-anavi
Copy link
Collaborator

@timemaster5 thank you for the valuable feedback and sharing all these details. I believe this will be very useful in future.

Thanks,
Leon

@timemaster5
Copy link

You are welcome, I hope I will have time for the actual integration soon :)

Anyway, just to let you know. I just successfully updated the device in a remote location in a similar way to what was originally suggested @leon-anavi. I uploaded a rootfs image, did the update of rootfs image and then manually copied over a new content of /boot partition.

Even though it is not my favourite way, it went ok and I rebooted to the new kernel and device tree. But there are no guarantees. So I would consider this way as best-effort only.

@om26er
Copy link
Contributor Author

om26er commented Apr 13, 2021

@timemaster5 thanks for your pointers. This is on my TODO list for this week as well. I got some help from the RAUC upstream as well and they say they have successfully update RPi boot partition as well.

@timemaster5
Copy link

Oh great, if you could share some other information on this topic from other channels it would be appreciated

@om26er
Copy link
Contributor Author

om26er commented Apr 15, 2021

I have this stuff mostly implemented. One thing I need to mention is that we only need a single entry for bootloader slot in the system.conf

[slot.bootloader.0]
device=/dev/mmcblk0
type=boot-mbr-switch
region-start=4194304
region-size=218103808

The main thing to figure out was to create a vfat image based on the files listed in ${IMAGE_BOOT_FILES} variable.

@om26er
Copy link
Contributor Author

om26er commented Apr 15, 2021

So the only missing thing for me currently is that the uboot.env is being written to /boot, which means on updating the boot partition, that files gets removed and gets recreated on next boot. This causes rootfs.0 as default. I have applied this patch https://github.com/mendersoftware/meta-mender/blob/master/meta-mender-raspberrypi/recipes-bsp/u-boot/patches/0001-configs-rpi-enable-mender-requirements.patch however it seems uboot.env is still being written to the /boot partition

@om26er
Copy link
Contributor Author

om26er commented Apr 15, 2021

Here is the fdisk output.

Before

root@raspberrypi4-64:~# fdisk /dev/mmcblk0 -l
Disk /dev/mmcblk0: 14.43 GiB, 15485370368 bytes, 30244864 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x2b883497

Device         Boot    Start      End  Sectors  Size Id Type
/dev/mmcblk0p1 *        8192   221183   212992  104M  c W95 FAT32 (LBA)
/dev/mmcblk0p2        434176  6578175  6144000    3G 83 Linux
/dev/mmcblk0p3       6578176 12722175  6144000    3G 83 Linux
/dev/mmcblk0p4      12722176 30244863 17522688  8.4G 83 Linux

Flash the image

root@raspberrypi4-64:~# rauc write-slot bootloader.0 /opt/rpi-ota-packages/boot_bundle.img 
rauc-Message: 20:08:46.772: Config option 'statusfile=<path>/per-slot' unset, falling back to per-slot status
rauc-Message: 20:08:46.776: Using per-slot statusfile
rauc-Message: 20:08:46.776: Checking image type for slot type: boot-mbr-switch
rauc-Message: 20:08:46.776: Image detected as type: *.img
rauc-Message: 20:08:46.776: Found inactive boot partition 1 (pos. 113246208B, size 109051904B)
rauc-Message: 20:08:46.776: Clearing inactive boot partition 1 on /dev/mmcblk0
rauc-Message: 20:08:58.704: Opening inactive boot partition 1 on /dev/mmcblk0
rauc-Message: 20:08:58.705: Copying image to inactive boot partition 1 on /dev/mmcblk0
rauc-Message: 20:09:06.306: Setting MBR to switch boot partition
rauc-Message: 20:09:06.306: Slot written successfully

After flashing

root@raspberrypi4-64:~# fdisk /dev/mmcblk0 -l
Disk /dev/mmcblk0: 14.43 GiB, 15485370368 bytes, 30244864 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x2b883497

Device         Boot    Start      End  Sectors  Size Id Type
/dev/mmcblk0p1 *      221184   434175   212992  104M  c W95 FAT32 (LBA)
/dev/mmcblk0p2        434176  6578175  6144000    3G 83 Linux
/dev/mmcblk0p3       6578176 12722175  6144000    3G 83 Linux
/dev/mmcblk0p4      12722176 30244863 17522688  8.4G 83 Linux

@timemaster5
Copy link

I have this stuff mostly implemented. One thing I need to mention is that we only need a single entry for bootloader slot in the system.conf

[slot.bootloader.0]
device=/dev/mmcblk0
type=boot-mbr-switch
region-start=4194304
region-size=218103808

The main thing to figure out was to create a vfat image based on the files listed in ${IMAGE_BOOT_FILES} variable.

Thank you, could you share your .wks file and maybe some other info on how to build that vfat image?

@timemaster5
Copy link

timemaster5 commented Apr 15, 2021

So the only missing thing for me currently is that the uboot.env is being written to /boot, which means on updating the boot partition, that files gets removed and gets recreated on the next boot. This causes rootfs.0 as default. I have applied this patch https://github.com/mendersoftware/meta-mender/blob/master/meta-mender-raspberrypi/recipes-bsp/u-boot/patches/0001-configs-rpi-enable-mender-requirements.patch however it seems uboot.env is still being written to the /boot partition

This seems to me like the patch has not been applied. Maybe bitbake -c cleansstate u-boot could help? There could be some other reason, but that patch specifically defines sectors on the flash drive to store the env file in and disables storing on VFAT partition. So the only reasonable explanation to me is that u-boot still uses the original settings.

Out of the curiosity, what RaspberryPi do you use? For example, I use Compute module 3 with custom defconfig for the u-boot. So this patch wouldn't work me out of the box.

@timemaster5
Copy link

timemaster5 commented Apr 15, 2021

Here is the fdisk output.

Before

root@raspberrypi4-64:~# fdisk /dev/mmcblk0 -l
Disk /dev/mmcblk0: 14.43 GiB, 15485370368 bytes, 30244864 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x2b883497

Device         Boot    Start      End  Sectors  Size Id Type
/dev/mmcblk0p1 *        8192   221183   212992  104M  c W95 FAT32 (LBA)
/dev/mmcblk0p2        434176  6578175  6144000    3G 83 Linux
/dev/mmcblk0p3       6578176 12722175  6144000    3G 83 Linux
/dev/mmcblk0p4      12722176 30244863 17522688  8.4G 83 Linux

Flash the image

root@raspberrypi4-64:~# rauc write-slot bootloader.0 /opt/rpi-ota-packages/boot_bundle.img 
rauc-Message: 20:08:46.772: Config option 'statusfile=<path>/per-slot' unset, falling back to per-slot status
rauc-Message: 20:08:46.776: Using per-slot statusfile
rauc-Message: 20:08:46.776: Checking image type for slot type: boot-mbr-switch
rauc-Message: 20:08:46.776: Image detected as type: *.img
rauc-Message: 20:08:46.776: Found inactive boot partition 1 (pos. 113246208B, size 109051904B)
rauc-Message: 20:08:46.776: Clearing inactive boot partition 1 on /dev/mmcblk0
rauc-Message: 20:08:58.704: Opening inactive boot partition 1 on /dev/mmcblk0
rauc-Message: 20:08:58.705: Copying image to inactive boot partition 1 on /dev/mmcblk0
rauc-Message: 20:09:06.306: Setting MBR to switch boot partition
rauc-Message: 20:09:06.306: Slot written successfully

After flashing

root@raspberrypi4-64:~# fdisk /dev/mmcblk0 -l
Disk /dev/mmcblk0: 14.43 GiB, 15485370368 bytes, 30244864 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x2b883497

Device         Boot    Start      End  Sectors  Size Id Type
/dev/mmcblk0p1 *      221184   434175   212992  104M  c W95 FAT32 (LBA)
/dev/mmcblk0p2        434176  6578175  6144000    3G 83 Linux
/dev/mmcblk0p3       6578176 12722175  6144000    3G 83 Linux
/dev/mmcblk0p4      12722176 30244863 17522688  8.4G 83 Linux

And this is really cool :)

Also, your region starts and offset values looks good to me. They don't seem to collide with the u-boot env file defined in the patch.

@om26er
Copy link
Contributor Author

om26er commented Apr 15, 2021

Here is what I did to write a vfat image file, these functions are called throughROOTFS_POSTPROCESS_COMMAND

python create_boot_img() {
    import shutil, os
    from pathlib import Path
    deploy_dir = d.getVar('DEPLOY_DIR_IMAGE')
    bundle_dir = os.path.join(deploy_dir, 'boot-bundle')
    shutil.rmtree(bundle_dir, ignore_errors=True)
    Path(bundle_dir).mkdir()
    boot_files = d.getVar('IMAGE_BOOT_FILES')
    for line in boot_files.split():
        if ";" in line:
            k, v = line.split(";")
            src_path = os.path.join(deploy_dir, k)
            dest_path = os.path.join(bundle_dir, v)
            if Path(src_path).is_symlink():
                if not os.path.exists(os.path.dirname(dest_path)):
                    Path(os.path.dirname(dest_path)).mkdir(parents=True)
                shutil.copy2(os.path.realpath(src_path), dest_path)
        elif line.endswith('*'):
            files = os.path.join(deploy_dir, line[:-1])
            shutil.copytree(files, bundle_dir, dirs_exist_ok=True)
        else:
            shutil.copy2(os.path.join(deploy_dir, line), bundle_dir)
}

create_boot_bundle() {
    dd if=/dev/zero of=${DEPLOY_DIR_IMAGE}/boot_bundle.img bs=64M count=1
    mkfs.vfat -F 32 -S 512 ${DEPLOY_DIR_IMAGE}/boot_bundle.img
    mcopy -i ${DEPLOY_DIR_IMAGE}/boot_bundle.img ${DEPLOY_DIR_IMAGE}/boot-bundle/* ::
    mkdir ${IMAGE_ROOTFS}/opt/rpi-ota-packages -p
    cp ${DEPLOY_DIR_IMAGE}/boot_bundle.img ${IMAGE_ROOTFS}/opt/rpi-ota-packages
}

@om26er
Copy link
Contributor Author

om26er commented Apr 15, 2021

@timemaster5 you are right, it seems the patch wasn't really applied. Once I did apply the patch (with a minor fix). I no longer see /boot/uboot.env, however that means rauc no longer really works as it relies on that file.

@om26er
Copy link
Contributor Author

om26er commented Apr 15, 2021

Also here is how my patch to u-boot looks like, maybe something wrong with that ?

From b76713f77fe2e593dcc564992cf439f13ed45e7e Mon Sep 17 00:00:00 2001
From: Omer Akram <[email protected]>
Date: Fri, 16 Apr 2021 01:40:24 +0500
Subject: [PATCH] config redundant env

---
 configs/rpi_4_defconfig | 6 ++++--
 env/mmc.c               | 4 ++++
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/configs/rpi_4_defconfig b/configs/rpi_4_defconfig
index 8cf1bb81..db20e144 100644
--- a/configs/rpi_4_defconfig
+++ b/configs/rpi_4_defconfig
@@ -16,8 +16,6 @@ CONFIG_CMD_GPIO=y
 CONFIG_CMD_MMC=y
 CONFIG_CMD_FS_UUID=y
 CONFIG_OF_BOARD=y
-CONFIG_ENV_FAT_INTERFACE="mmc"
-CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
 CONFIG_SYS_RELOC_GD_ENV_ADDR=y
 CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
 CONFIG_DM_KEYBOARD=y
@@ -33,3 +31,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y
 CONFIG_CONSOLE_SCROLL_LINES=10
 CONFIG_PHYS_TO_BUS=y
 CONFIG_OF_LIBFDT_OVERLAY=y
+CONFIG_ENV_IS_IN_MMC=y
+CONFIG_ENV_OFFSET=0x800000
+CONFIG_ENV_OFFSET_REDUND=0x1000000
+CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
diff --git a/env/mmc.c b/env/mmc.c
index b24c35ce..3e3f8410 100644
--- a/env/mmc.c
+++ b/env/mmc.c
@@ -114,6 +114,10 @@ __weak int mmc_get_env_addr(struct mmc *mmc, int copy, u32 *env_addr)
        return 0;
 }

+if !defined(CONFIG_SYS_MMC_ENV_DEV)
+#define CONFIG_SYS_MMC_ENV_DEV 0
+#endif
+
 __weak int mmc_get_env_dev(void)
 {
        return CONFIG_SYS_MMC_ENV_DEV;
-- 
2.25.1

@om26er
Copy link
Contributor Author

om26er commented Apr 15, 2021

Also tried with the original patch (with minor fix) and that also produced the same result

From 371400a8772d5f6ae4b819ef91cd36b883a8d313 Mon Sep 17 00:00:00 2001
From: Mirza Krak <[email protected]>
Date: Tue, 26 Sep 2017 06:23:52 -0400
Subject: [PATCH 1/1] configs: rpi: enable mender requirements

Which are CONFIG_BOOTCOUNT_ENV and CONFIG_BOOTCOUNT_LIMIT.

Mender also requires that the environment is on MMC
(CONFIG_ENV_IS_IN_MMC)

Signed-off-by: Mirza Krak <[email protected]>
Signed-off-by: Drew Moseley <[email protected]>
---
 configs/rpi_0_w_defconfig      | 3 +--
 configs/rpi_2_defconfig        | 3 +--
 configs/rpi_3_32b_defconfig    | 3 +--
 configs/rpi_3_b_plus_defconfig | 3 +--
 configs/rpi_3_defconfig        | 3 +--
 configs/rpi_4_32b_defconfig    | 3 +--
 configs/rpi_4_defconfig        | 3 +--
 configs/rpi_arm64_defconfig    | 3 +--
 configs/rpi_defconfig          | 3 +--
 env/Kconfig                    | 1 -
 include/configs/rpi.h          | 3 +++
 11 files changed, 12 insertions(+), 19 deletions(-)

diff --git a/configs/rpi_0_w_defconfig b/configs/rpi_0_w_defconfig
index 75c6c9c447..493a99a26d 100644
--- a/configs/rpi_0_w_defconfig
+++ b/configs/rpi_0_w_defconfig
@@ -19,8 +19,6 @@ CONFIG_CMD_USB=y
 CONFIG_CMD_FS_UUID=y
 CONFIG_OF_EMBED=y
 CONFIG_DEFAULT_DEVICE_TREE="bcm2835-rpi-zero-w"
-CONFIG_ENV_FAT_INTERFACE="mmc"
-CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
 CONFIG_SYS_RELOC_GD_ENV_ADDR=y
 CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
 CONFIG_DM_KEYBOARD=y
@@ -44,3 +42,7 @@ CONFIG_CONSOLE_SCROLL_LINES=10
 CONFIG_PHYS_TO_BUS=y
 CONFIG_OF_LIBFDT_OVERLAY=y
 CONFIG_EFI_LOADER=y
+CONFIG_ENV_IS_IN_MMC=y
+CONFIG_ENV_OFFSET=0x800000
+CONFIG_ENV_OFFSET_REDUND=0x1000000
+CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
diff --git a/configs/rpi_2_defconfig b/configs/rpi_2_defconfig
index 4e8204ef88..4f4029e006 100644
--- a/configs/rpi_2_defconfig
+++ b/configs/rpi_2_defconfig
@@ -19,8 +19,6 @@ CONFIG_CMD_USB=y
 CONFIG_CMD_FS_UUID=y
 CONFIG_OF_EMBED=y
 CONFIG_DEFAULT_DEVICE_TREE="bcm2836-rpi-2-b"
-CONFIG_ENV_FAT_INTERFACE="mmc"
-CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
 CONFIG_SYS_RELOC_GD_ENV_ADDR=y
 CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
 CONFIG_DM_KEYBOARD=y
@@ -43,3 +41,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y
 CONFIG_CONSOLE_SCROLL_LINES=10
 CONFIG_PHYS_TO_BUS=y
 CONFIG_OF_LIBFDT_OVERLAY=y
+CONFIG_ENV_IS_IN_MMC=y
+CONFIG_ENV_OFFSET=0x800000
+CONFIG_ENV_OFFSET_REDUND=0x1000000
+CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
diff --git a/configs/rpi_3_32b_defconfig b/configs/rpi_3_32b_defconfig
index d50953287c..76d3abd54d 100644
--- a/configs/rpi_3_32b_defconfig
+++ b/configs/rpi_3_32b_defconfig
@@ -20,8 +20,6 @@ CONFIG_CMD_USB=y
 CONFIG_CMD_FS_UUID=y
 CONFIG_OF_EMBED=y
 CONFIG_DEFAULT_DEVICE_TREE="bcm2837-rpi-3-b"
-CONFIG_ENV_FAT_INTERFACE="mmc"
-CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
 CONFIG_SYS_RELOC_GD_ENV_ADDR=y
 CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
 CONFIG_DM_KEYBOARD=y
@@ -46,3 +44,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y
 CONFIG_CONSOLE_SCROLL_LINES=10
 CONFIG_PHYS_TO_BUS=y
 CONFIG_OF_LIBFDT_OVERLAY=y
+CONFIG_ENV_IS_IN_MMC=y
+CONFIG_ENV_OFFSET=0x800000
+CONFIG_ENV_OFFSET_REDUND=0x1000000
+CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
diff --git a/configs/rpi_3_b_plus_defconfig b/configs/rpi_3_b_plus_defconfig
index e76821450f..a06ccf2d19 100644
--- a/configs/rpi_3_b_plus_defconfig
+++ b/configs/rpi_3_b_plus_defconfig
@@ -20,8 +20,6 @@ CONFIG_CMD_USB=y
 CONFIG_CMD_FS_UUID=y
 CONFIG_OF_EMBED=y
 CONFIG_DEFAULT_DEVICE_TREE="bcm2837-rpi-3-b-plus"
-CONFIG_ENV_FAT_INTERFACE="mmc"
-CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
 CONFIG_SYS_RELOC_GD_ENV_ADDR=y
 CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
 CONFIG_DM_KEYBOARD=y
@@ -46,3 +44,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y
 CONFIG_CONSOLE_SCROLL_LINES=10
 CONFIG_PHYS_TO_BUS=y
 CONFIG_OF_LIBFDT_OVERLAY=y
+CONFIG_ENV_IS_IN_MMC=y
+CONFIG_ENV_OFFSET=0x800000
+CONFIG_ENV_OFFSET_REDUND=0x1000000
+CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
diff --git a/configs/rpi_3_defconfig b/configs/rpi_3_defconfig
index c0c0955131..fd617ae0c2 100644
--- a/configs/rpi_3_defconfig
+++ b/configs/rpi_3_defconfig
@@ -20,8 +20,6 @@ CONFIG_CMD_USB=y
 CONFIG_CMD_FS_UUID=y
 CONFIG_OF_EMBED=y
 CONFIG_DEFAULT_DEVICE_TREE="bcm2837-rpi-3-b"
-CONFIG_ENV_FAT_INTERFACE="mmc"
-CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
 CONFIG_SYS_RELOC_GD_ENV_ADDR=y
 CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
 CONFIG_DM_KEYBOARD=y
@@ -46,3 +44,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y
 CONFIG_CONSOLE_SCROLL_LINES=10
 CONFIG_PHYS_TO_BUS=y
 CONFIG_OF_LIBFDT_OVERLAY=y
+CONFIG_ENV_IS_IN_MMC=y
+CONFIG_ENV_OFFSET=0x800000
+CONFIG_ENV_OFFSET_REDUND=0x1000000
+CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
diff --git a/configs/rpi_4_32b_defconfig b/configs/rpi_4_32b_defconfig
index 00f80f71ad..e0e7795952 100644
--- a/configs/rpi_4_32b_defconfig
+++ b/configs/rpi_4_32b_defconfig
@@ -16,8 +16,6 @@ CONFIG_CMD_GPIO=y
 CONFIG_CMD_MMC=y
 CONFIG_CMD_FS_UUID=y
 CONFIG_OF_BOARD=y
-CONFIG_ENV_FAT_INTERFACE="mmc"
-CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
 CONFIG_SYS_RELOC_GD_ENV_ADDR=y
 CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
 CONFIG_DM_KEYBOARD=y
@@ -33,3 +31,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y
 CONFIG_CONSOLE_SCROLL_LINES=10
 CONFIG_PHYS_TO_BUS=y
 CONFIG_OF_LIBFDT_OVERLAY=y
+CONFIG_ENV_IS_IN_MMC=y
+CONFIG_ENV_OFFSET=0x800000
+CONFIG_ENV_OFFSET_REDUND=0x1000000
+CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
diff --git a/configs/rpi_4_defconfig b/configs/rpi_4_defconfig
index 8cf1bb81ff..a18eba2884 100644
--- a/configs/rpi_4_defconfig
+++ b/configs/rpi_4_defconfig
@@ -16,8 +16,6 @@ CONFIG_CMD_GPIO=y
 CONFIG_CMD_MMC=y
 CONFIG_CMD_FS_UUID=y
 CONFIG_OF_BOARD=y
-CONFIG_ENV_FAT_INTERFACE="mmc"
-CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
 CONFIG_SYS_RELOC_GD_ENV_ADDR=y
 CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
 CONFIG_DM_KEYBOARD=y
@@ -33,3 +31,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y
 CONFIG_CONSOLE_SCROLL_LINES=10
 CONFIG_PHYS_TO_BUS=y
 CONFIG_OF_LIBFDT_OVERLAY=y
+CONFIG_ENV_IS_IN_MMC=y
+CONFIG_ENV_OFFSET=0x800000
+CONFIG_ENV_OFFSET_REDUND=0x1000000
+CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
diff --git a/configs/rpi_arm64_defconfig b/configs/rpi_arm64_defconfig
index 10fbe0db92..ce720769f5 100644
--- a/configs/rpi_arm64_defconfig
+++ b/configs/rpi_arm64_defconfig
@@ -17,8 +17,6 @@ CONFIG_CMD_MMC=y
 CONFIG_CMD_USB=y
 CONFIG_CMD_FS_UUID=y
 CONFIG_OF_BOARD=y
-CONFIG_ENV_FAT_INTERFACE="mmc"
-CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
 CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
 CONFIG_DM_KEYBOARD=y
 CONFIG_DM_MMC=y
@@ -42,3 +40,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y
 CONFIG_CONSOLE_SCROLL_LINES=10
 CONFIG_PHYS_TO_BUS=y
 CONFIG_OF_LIBFDT_OVERLAY=y
+CONFIG_ENV_IS_IN_MMC=y
+CONFIG_ENV_OFFSET=0x800000
+CONFIG_ENV_OFFSET_REDUND=0x1000000
+CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
diff --git a/configs/rpi_defconfig b/configs/rpi_defconfig
index 2f4c7da6dc..de498d0652 100644
--- a/configs/rpi_defconfig
+++ b/configs/rpi_defconfig
@@ -19,8 +19,6 @@ CONFIG_CMD_USB=y
 CONFIG_CMD_FS_UUID=y
 CONFIG_OF_EMBED=y
 CONFIG_DEFAULT_DEVICE_TREE="bcm2835-rpi-b"
-CONFIG_ENV_FAT_INTERFACE="mmc"
-CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
 CONFIG_SYS_RELOC_GD_ENV_ADDR=y
 CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
 CONFIG_DM_KEYBOARD=y
@@ -44,3 +42,7 @@ CONFIG_CONSOLE_SCROLL_LINES=10
 CONFIG_PHYS_TO_BUS=y
 CONFIG_OF_LIBFDT_OVERLAY=y
 CONFIG_EFI_LOADER=y
+CONFIG_ENV_IS_IN_MMC=y
+CONFIG_ENV_OFFSET=0x800000
+CONFIG_ENV_OFFSET_REDUND=0x1000000
+CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
+CONFIG_SYS_MMC_ENV_DEV=0
diff --git a/env/Kconfig b/env/Kconfig
index ed12609f6a..e83c62f6f6 100644
--- a/env/Kconfig
+++ b/env/Kconfig
@@ -49,7 +49,6 @@ config ENV_IS_IN_EEPROM
 config ENV_IS_IN_FAT
 	bool "Environment is in a FAT filesystem"
 	depends on !CHAIN_OF_TRUST
-	default y if ARCH_BCM283X
 	default y if ARCH_SUNXI && MMC
 	default y if MMC_OMAP_HS && TI_COMMON_CMD_OPTIONS
 	select FS_FAT
diff --git a/include/configs/rpi.h b/include/configs/rpi.h
index 83e258a6b9..cf05fbc0d0 100644
--- a/include/configs/rpi.h
+++ b/include/configs/rpi.h
@@ -80,6 +80,9 @@
 /* Environment */
 #define CONFIG_SYS_LOAD_ADDR		0x1000000
 
+#define CONFIG_BOOTCOUNT_ENV
+#define CONFIG_BOOTCOUNT_LIMIT
+
 /* Shell */
 
 /* ATAGs support for bootm/bootz */
-- 
diff --git a/env/mmc.c b/env/mmc.c
index b24c35ce..3e3f8410 100644
--- a/env/mmc.c
+++ b/env/mmc.c
@@ -114,6 +114,10 @@ __weak int mmc_get_env_addr(struct mmc *mmc, int copy, u32 *env_addr)
        return 0;
 }

+#if !defined(CONFIG_SYS_MMC_ENV_DEV)
+#define CONFIG_SYS_MMC_ENV_DEV 0
+#endif
+
 __weak int mmc_get_env_dev(void)
 {
        return CONFIG_SYS_MMC_ENV_DEV;
2.17.1

@om26er
Copy link
Contributor Author

om26er commented Apr 15, 2021

So it seems I needed to change /etc/fw_env.config to tell fw_printenv where to look for the env

@leon-anavi
Copy link
Collaborator

@om26er have a look how fw_env.config is created with bbappend file in meta-raspberrypi: https://github.com/agherzan/meta-raspberrypi/blob/master/recipes-bsp/u-boot/u-boot_%25.bbappend

@timemaster5
Copy link

timemaster5 commented Apr 16, 2021

So it seems I needed to change /etc/fw_env.config to tell fw_printenv where to look for the env

you're too fast, I will try to respond to the rest during the day :)

But this one should be easy, instead of /boot/uboot.env in the /etc/fw_env.config you should have /dev/mmcblk0 there as you want to access the block device directly to get the environment data. As offset values, I would use exactly what is defined in your patch. So I would try something like:

/dev/mmcblk0 0x800000 0x4000
/dev/mmcblk0 0x1000000 0x4000 # this is for redundant env file

With this change, rauc should be able to work.

@timemaster5
Copy link

Anyway, I need to change my statement above, and maybe we need to change the title of this issue.

According to @om26er 's findings, this is not an A/B deployment as I thought. This is "only" an atomic update of /boot partition. This is not bad as long as we are sure the kernel and rootfs combination works together after the update. It "only" ensures the update doesn't crash the system on fail (power outage, oomkiller, ...).

So It should be good enough for production OTA upgrades because no machine should receive an untested update.
In that case, there should not be any problem like incompatible kernel version with rootfs (/lib/modules content for example).

If we want a real A/B deployment of /boot partition, we would need this bit, which already works for @om26er to atomically update u-boot only, and then let u-boot load the kernel and DT from either bootA or bootB slot.

@timemaster5
Copy link

timemaster5 commented Apr 16, 2021

Here is what I did to write a vfat image file, these functions are called throughROOTFS_POSTPROCESS_COMMAND

python create_boot_img() {
    import shutil, os
    from pathlib import Path
    deploy_dir = d.getVar('DEPLOY_DIR_IMAGE')
    bundle_dir = os.path.join(deploy_dir, 'boot-bundle')
    shutil.rmtree(bundle_dir, ignore_errors=True)
    Path(bundle_dir).mkdir()
    boot_files = d.getVar('IMAGE_BOOT_FILES')
    for line in boot_files.split():
        if ";" in line:
            k, v = line.split(";")
            src_path = os.path.join(deploy_dir, k)
            dest_path = os.path.join(bundle_dir, v)
            if Path(src_path).is_symlink():
                if not os.path.exists(os.path.dirname(dest_path)):
                    Path(os.path.dirname(dest_path)).mkdir(parents=True)
                shutil.copy2(os.path.realpath(src_path), dest_path)
        elif line.endswith('*'):
            files = os.path.join(deploy_dir, line[:-1])
            shutil.copytree(files, bundle_dir, dirs_exist_ok=True)
        else:
            shutil.copy2(os.path.join(deploy_dir, line), bundle_dir)
}

create_boot_bundle() {
    dd if=/dev/zero of=${DEPLOY_DIR_IMAGE}/boot_bundle.img bs=64M count=1
    mkfs.vfat -F 32 -S 512 ${DEPLOY_DIR_IMAGE}/boot_bundle.img
    mcopy -i ${DEPLOY_DIR_IMAGE}/boot_bundle.img ${DEPLOY_DIR_IMAGE}/boot-bundle/* ::
    mkdir ${IMAGE_ROOTFS}/opt/rpi-ota-packages -p
    cp ${DEPLOY_DIR_IMAGE}/boot_bundle.img ${IMAGE_ROOTFS}/opt/rpi-ota-packages
}

So you are re-creating boot partition as a file which you are delivering together with rootfs.
It is good for testing, but the boot image should be bundled as a second (or first) image in the .rauc bundle. So then you can use rauc install, and decide what slot you want to upgrade. Honestly, rauc write-slot as you use it should not work for you according to my understanding of the rauc documentation which says write-slot bypasses all update mechanics like hooks, slot status etc., and MBR switch is an "update mechanics" as far as I can tell.

@om26er
Copy link
Contributor Author

om26er commented Apr 16, 2021

@timemaster5 rauc write-slot bootloader.0 myimage.img works as expected. The initial implementation was for testing purpose.

I am creating a rauc bundle manually by generating a manifest and putting that into the rootfs. The bootloader update is then applied afterwards as recommended here https://rauc.readthedocs.io/en/latest/advanced.html#considerations-when-updating-the-bootloader

@timemaster5
Copy link

timemaster5 commented Apr 17, 2021

Thank you for pointing this out. I haven't seen this part of the documentation.

I believe, that it is not applicable to our case. They are talking specifically about updating the bootloader (u-boot for us). Which is something totally different from what are we doing here. We are updating the whole boot partition which consists of the bootloader, kernel image and device tree plus some device-specific configuration. If there is a problem with any of those, the system will not boot up.

In their case, they expect the system can boot even with an older version of the bootloader. The user is then to check whether the update was successful or not because it might be important for some other part of the update process or recovery.

I've seen this many times - I had to upgrade a bootloader first, then I was able to update to the newest system, it is a quite common example, but not really related to the RaspberryPi ecosystem.

@om26er
Copy link
Contributor Author

om26er commented Jul 8, 2021

If we want a real A/B deployment of /boot partition, we would need this bit, which already works for @om26er to atomically update u-boot only, and then let u-boot load the kernel and DT from either bootA or bootB slot.

I think there are multiple things to consider here. The /boot partition contains the follow at least

  1. raspberry pi boot firmware, downloaded from https://github.com/raspberrypi/firmware/tree/master/boot
  2. The kernel, built from source
  3. DTB, cmdline.txt, config.txt

The raspberrypi expects much of the above in the "first" partition, so I am not sure if we can really do A/B redundancy separate from the updating /boot

Mender loads the kernel from the rootfs, and we might be able to do that as well. But the dtb and other stuff basically has to stay on the first partition https://github.com/mendersoftware/meta-mender/tree/master/meta-mender-raspberrypi#meta-mender-raspberrypi

Thoughts ?

@om26er
Copy link
Contributor Author

om26er commented Jul 8, 2021

The latest raspberry pi EEPROM it seems support a way for A/B partitioning https://www.raspberrypi.org/forums/viewtopic.php?t=297354#p1791107 -- Using that approach could remove the need of u-boot all together.

RAUC would have to be patched to support that though.

@timemaster5
Copy link

If we want a real A/B deployment of /boot partition, we would need this bit, which already works for @om26er to atomically update u-boot only, and then let u-boot load the kernel and DT from either bootA or bootB slot.

I think there are multiple things to consider here. The /boot partition contains the follow at least

  1. raspberry pi boot firmware, downloaded from https://github.com/raspberrypi/firmware/tree/master/boot
  2. The kernel, built from source
  3. DTB, cmdline.txt, config.txt

The raspberrypi expects much of the above in the "first" partition, so I am not sure if we can really do A/B redundancy separate from the updating /boot

Mender loads the kernel from the rootfs, and we might be able to do that as well. But the dtb and other stuff basically has to stay on the first partition https://github.com/mendersoftware/meta-mender/tree/master/meta-mender-raspberrypi#meta-mender-raspberrypi

Thoughts ?

Good to hear from you @om26er
It is as you say, /boot contains too much for us to be able to do reliable A/B deployment, and I thought for a long time that this is just a limitation of our platform. Until this changes, what you have done is probably the best we could do.

But with these recent findings of [REBOOT_FLAGS] (raspberrypi/linux@7576667) the situation is different and more optimistic, this could be a good solution for us really.

Removal of uBoot probably depends on personal preferences, it could still be useful for some other tasks than switching rootfs. But I agree that for a basic idea it should not be needed with this new fw.

@om26er
Copy link
Contributor Author

om26er commented Jul 13, 2021

I have tryboot based redundant fallback mechanism working, this also means I no longer need u-boot.

Here is what I had to do

  1. Applied this patch to the kernel raspberrypi/linux@7576667
  2. Wrote a custom WIC plugin that would populate the boot partition in a different structure than it currently does (fork of https://github.com/mendersoftware/poky/blob/master/scripts/lib/wic/plugins/source/bootimg-partition.py)
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION
# This implements the 'bootimg-partition' source plugin class for
# 'wic'. The plugin creates an image of boot partition, copying over
# files listed in IMAGE_BOOT_FILES bitbake variable.
#
# AUTHORS
# Maciej Borzecki <maciej.borzecki (at] open-rnd.pl>
#

import logging
import os
import re
import shutil
import tempfile

from glob import glob

from wic import WicError
from wic.engine import get_custom_config
from wic.pluginbase import SourcePlugin
from wic.misc import exec_cmd, get_bitbake_var

logger = logging.getLogger('wic')

class BootimgPartitionPlugin(SourcePlugin):
    """
    Create an image of boot partition, copying over files
    listed in IMAGE_BOOT_FILES bitbake variable.
    """

    name = 'rpi4-boot-img'

    @classmethod
    def do_configure_partition(cls, part, source_params, cr, cr_workdir,
                               oe_builddir, bootimg_dir, kernel_dir,
                               native_sysroot):
        """
        Called before do_prepare_partition(), create u-boot specific boot config
        """
        hdddir = "{}/boot.{}".format(cr_workdir, part.lineno)
        install_cmd = "install -d {}".format(hdddir)
        exec_cmd(install_cmd)

        if not kernel_dir:
            kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
            if not kernel_dir:
                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")

        boot_files = None
        for fmt, id in (("_uuid-{}", part.uuid), ("_label-{}", part.label), (None, None)):
            if fmt:
                var = fmt.format(id)
            else:
                var = ""

            boot_files = get_bitbake_var("IMAGE_BOOT_FILES" + var)
            if boot_files is not None:
                break

        if boot_files is None:
            raise WicError('No boot files defined, IMAGE_BOOT_FILES unset for entry #{}'.format(part.lineno))

        logger.debug('Boot files: %s', boot_files)

        # list of tuples (src_name, dst_name)
        deploy_files = []
        for src_entry in re.findall(r'[\w;\-\./\*]+', boot_files):
            if ';' in src_entry:
                dst_entry = tuple(src_entry.split(';'))
                if not dst_entry[0] or not dst_entry[1]:
                    raise WicError('Malformed boot file entry: {}'.format(src_entry))
            else:
                dst_entry = (src_entry, src_entry)

            logger.debug('Destination entry: %r', dst_entry)
            deploy_files.append(dst_entry)

        cls.install_task = [];
        for deploy_entry in deploy_files:
            src, dst = deploy_entry
            if '*' in src:
                # by default install files under their basename
                entry_name_fn = os.path.basename
                if dst != src:
                    # unless a target name was given, then treat name
                    # as a directory and append a basename
                    entry_name_fn = lambda name: \
                                    os.path.join(dst,
                                                 os.path.basename(name))

                srcs = glob(os.path.join(kernel_dir, src))

                logger.debug('Globbed sources: %s', ', '.join(srcs))
                for entry in srcs:
                    src = os.path.relpath(entry, kernel_dir)
                    entry_dst_name = entry_name_fn(entry)
                    cls.install_task.append((src, entry_dst_name))
            else:
                cls.install_task.append((src, dst))

        if source_params.get('loader') != "u-boot":
            return

        configfile = cr.ks.bootloader.configfile
        custom_cfg = None
        if configfile:
            custom_cfg = get_custom_config(configfile)
            if custom_cfg:
                # Use a custom configuration for extlinux.conf
                extlinux_conf = custom_cfg
                logger.debug("Using custom configuration file "
                             "%s for extlinux.cfg", configfile)
            else:
                raise WicError("configfile is specified but failed to "
                               "get it from {}.".format(configfile))

        if not custom_cfg:
            # The kernel types supported by the sysboot of u-boot
            kernel_types = ["zImage", "Image", "fitImage", "uImage", "vmlinux"]
            has_dtb = False
            fdt_dir = '/'
            kernel_name = None

            # Find the kernel image name, from the highest precedence to lowest
            for image in kernel_types:
                for task in cls.install_task:
                    src, dst = task
                    if re.match(image, src):
                        kernel_name = os.path.join('/', dst)
                        break
                if kernel_name:
                    break

            for task in cls.install_task:
                src, dst = task
                # We suppose that all the dtb are in the same directory
                if re.search(r'\.dtb', src) and fdt_dir == '/':
                    has_dtb = True
                    fdt_dir = os.path.join(fdt_dir, os.path.dirname(dst))
                    break

            if not kernel_name:
                raise WicError('No kernel file found')

            # Compose the extlinux.conf
            extlinux_conf = "default Yocto\n"
            extlinux_conf += "label Yocto\n"
            extlinux_conf += "   kernel {}\n".format(kernel_name)
            if has_dtb:
                extlinux_conf += "   fdtdir {}\n".format(fdt_dir)
            bootloader = cr.ks.bootloader
            extlinux_conf += "append root={} rootwait {}\n".format(
                             cr.rootdev, bootloader.append if bootloader.append else '')

        install_cmd = "install -d {}/extlinux/".format(hdddir)
        exec_cmd(install_cmd)
        cfg = open("{}/extlinux/extlinux.conf".format(hdddir), "w")
        cfg.write(extlinux_conf)
        cfg.close()


    @classmethod
    def do_prepare_partition(cls, part, source_params, cr, cr_workdir,
                             oe_builddir, bootimg_dir, kernel_dir,
                             rootfs_dir, native_sysroot):
        """
        Called to do the actual content population for a partition i.e. it
        'prepares' the partition to be incorporated into the image.
        In this case, does the following:
        - sets up a vfat partition
        - copies all files listed in IMAGE_BOOT_FILES variable
        """
        hdddir = "{}/boot.{}".format(cr_workdir, part.lineno)

        if not kernel_dir:
            kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
            if not kernel_dir:
                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")

        logger.debug('Kernel dir: %s', bootimg_dir)


        for task in cls.install_task:
            src_path, dst_path = task
            logger.debug('Install %s as %s', src_path, dst_path)
            install_cmd = "install -m 0644 -D {} {}".format(
                os.path.join(kernel_dir, src_path), os.path.join(hdddir, dst_path))
            exec_cmd(install_cmd)

        temp = tempfile.TemporaryDirectory()
        for item in os.listdir(hdddir):
            shutil.move(os.path.join(hdddir, item), temp.name)

        exec_cmd("mkdir {}/a".format(hdddir))
        for item in os.listdir(temp.name):
            shutil.move(os.path.join(temp.name, item), os.path.join(hdddir, "a"))

        # Move the boot script to the top-level directory
        # This script the fragile because it's not redundant.
        # Only relevant if using u-boot
        if os.path.exists(os.path.join(hdddir, "a", "boot.scr")):
            shutil.move(os.path.join(hdddir, "a", "boot.scr"), hdddir)

        temp.cleanup()
        exec_cmd("cp {}/a/start4.elf {}/a.elf".format(hdddir, hdddir))
        exec_cmd("cp {}/a/fixup4.dat {}/a.dat".format(hdddir, hdddir))
        os.system("rm {}/a/*dat {}/a/*elf".format(hdddir, hdddir))
        exec_cmd("cp {}/a/config.txt {}/config.txt".format(hdddir, hdddir))

        # Prepare b boot
        shutil.copytree("{}/a".format(hdddir), "{}/b".format(hdddir))
        exec_cmd("cp {}/a.elf {}/b.elf".format(hdddir, hdddir))
        exec_cmd("cp {}/a.dat {}/b.dat".format(hdddir, hdddir))
        exec_cmd("cp {}/config.txt {}/tryboot.txt".format(hdddir, hdddir))
        with open("{}/tryboot.txt".format(hdddir), "a") as tryboot:
            tryboot.write("os_prefix=b/\n")
            tryboot.write("start_file=b.elf\n")
            tryboot.write("fixup_file=b.dat\n")

        with open("{}/b/cmdline.txt".format(hdddir), "r+") as file:
            data = file.read().strip()
            data += " root=/dev/mmcblk0p3"
            file.seek(0)
            file.write(data)
            file.write("\n")
            file.truncate()

        # Update cmdline on bootA
        with open("{}/a/cmdline.txt".format(hdddir), "r+") as file:
            data = file.read().strip()
            data += " root=/dev/mmcblk0p2"
            file.seek(0)
            file.write(data)
            file.write("\n")
            file.truncate()

        # Update config.txt to point to bootA
        with open("{}/config.txt".format(hdddir), "a") as config:
            config.write("os_prefix=a/\n")
            config.write("start_file=a.elf\n")
            config.write("fixup_file=a.dat\n")

        logger.debug('Prepare boot partition using rootfs in %s', hdddir)
        part.prepare_rootfs(cr_workdir, oe_builddir, hdddir, native_sysroot, False)

@om26er
Copy link
Contributor Author

om26er commented Jul 13, 2021

  1. Added a post-install script inside the rauc bundle, which makes sure to setup/update the boot partition after update (I include the boot content in the rootfs, to be copied to /boot by the bundle script)

Note that, using tryboot technique, one could update the OS in the background and then tell the user to reboot using our script that calls reboot '0 tryboot', if the boot completes successfully we rename the tryboot.txt to config.txt (mv is an atomic operation so it's safe for power loss)

@nifrhue
Copy link

nifrhue commented Mar 15, 2022

hi @om26er,

very nice possibility to update the boot partition !! 🎉
I try to reproduce your tryboot based 'redundant fallback mechanism' without uboot,
but I have some issues to get it running.

How does your rauc config look like and how does the boot slot detected?

@matthiasklein
Copy link
Contributor

@om26er

I am also interested in using the tryboot feature of the Raspberry bootloader for A/B switching instead of U-Boot.
Is it possible that you share your customizations with us?

@mortenboye
Copy link

I'm very interested in this also.

@om26er care to share some more details?

@dev-0x7C6
Copy link
Contributor

Also interested :)

@mortenboye
Copy link

mortenboye commented Aug 26, 2022

I think I've found a solution myself. Its not road tested and has obvious flaws, but the basics seem to work and can update/switch root images.

Aside from @om26er's custom WIC plugin above, I also wrote a custom bootloader backend (defined in [handlers] in system.conf) and a post-install script that handles the editing/copying/moving of config.txt/tryboot.txt

Current shortcomings is that I'm not sure how to mark a slot good or bad, expect in the case where the system is correctly booting from tryboot.txt, which implies a good slot, and tryboot.txt then becomes config.txt.
Returning slot state always returns "good".

leon-anavi added a commit to leon-anavi/meta-rauc-community that referenced this issue May 2, 2024
Add instructions how to move the Linux kernel to the rootfs on
Raspberry Pi and to update with a RAUC bundle rauc#12

Signed-off-by: Leon Anavi <[email protected]>
@leon-anavi
Copy link
Collaborator

Hi,

I created a GitHub pull request which modifies meta-rauc-raspberrypi/README.rst and adds instructions how to move the Linux kernel to the rootfs partition and after that to update it as part of the RAUC bundle. Please note that the dtb is not updated in the described method.

Please let me know if there are comments. After merging the GitHub pull request I am planning to close this issue.

Best regards, Leon

leon-anavi added a commit to leon-anavi/meta-rauc-community that referenced this issue May 22, 2024
Add instructions how to move the Linux kernel to the rootfs on
Raspberry Pi and to update with a RAUC bundle rauc#12

Signed-off-by: Leon Anavi <[email protected]>
@ejoerns
Copy link
Member

ejoerns commented May 23, 2024

For anyone interested, I can also share the implementation I had for RPI before this layer was created: https://github.com/ejoerns/meta-rauc-demo-wip/tree/gatesgarth-rpi/meta-rauc-raspberrypi

IMHO having the kernel as part of the rootfs is the simplest solution anyway.

Having the boot partition updatable in an atomic manner should be part of this layer, too. I remember having struggled with this, too. My solution back then was:

https://github.com/ejoerns/meta-rauc-demo-wip/blob/gatesgarth-rpi/meta-rauc-raspberrypi/recipes-core/rauc/files/raspberrypi3/system.conf
https://github.com/ejoerns/meta-rauc-demo-wip/blob/gatesgarth-rpi/meta-rauc-raspberrypi/wic/sdimage-rauc-raspberrypi.wks
https://github.com/ejoerns/meta-rauc-demo-wip/blob/gatesgarth-rpi/meta-rauc-raspberrypi/recipes-core/bundles/rpi-demo-bundle.bb

Maybe using a proper wic plugin for the boot partition generation is even better (as suggested above).

Haven't used 'tryboot' back then but having this as a custom (or code-implemented?) bootloader backend would be interesting, too, I guess.

It's fine for me, too, to close this Issue first of all and keep it in mind as a reference once someone wants to implement a solution in a PR.

I'll modify the title (as already suggested) to better match the scope of the rest of the discussion.

@ejoerns ejoerns changed the title A/B Partitioning for Kernel ? raspberrypi: Proper A/B kernel Updates and Atomic Boot Partition Updates May 23, 2024
@gportay
Copy link
Contributor

gportay commented May 31, 2024

Hello, for what it worth, I have an implementation here using tryboot and autobot.txt.

I have a buildroot br2-external example and an oe layer is on its way. And also a c implementation in RAUC as a new bootchooser.

@gportay
Copy link
Contributor

gportay commented May 31, 2024

The implementation is self-documented in the bash script. If anyone wants to have a look at it, reviews are welcome, especially about making a final implementation in RAUC. It can be discussed here, at least for now.

That custom backend is very similar to EFI as there is no boot-attempt counter, and there is a "volatile" flag to switch A/B at next boot.

RAUC status does not guess the status of the non-booted slot. It could use the presence of the tryboot.txt file. Also, I wonder if the persistent RAUC statusfile can be used instead?

The kernel update is performed using a bundle post installation hook to save the device of the rootfs slot being updated, and to append the rootfs= to cmdline.txt after the boot slot update. See the commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants