diff --git a/meta-oniro-core/classes/oniro-image.bbclass b/meta-oniro-core/classes/oniro-image.bbclass
index a21a06b778d5e01a54c41489dd772608ad113c52..d7aa597fab30ea690ed4274c315299cba80febb9 100644
--- a/meta-oniro-core/classes/oniro-image.bbclass
+++ b/meta-oniro-core/classes/oniro-image.bbclass
@@ -31,6 +31,7 @@ IMAGE_FEATURES:append = " read-only-rootfs"
 IMAGE_FSTYPES:append = " squashfs"
 
 WKS_FILE:raspberrypi4-64 ?= "x-raspberrypi.wks.in"
+WKS_FILE:raspberrypi2 ?= "x-raspberrypi.wks.in"
 
 WKS_FILE:seco-intel-b68 ?= "x-gpt-efi-disk.wks.in"
 IMAGE_FSTYPES:append:seco-intel-b68 = " wic.bz2 wic.bmap"
diff --git a/meta-oniro-core/conf/distro/oniro-linux.conf b/meta-oniro-core/conf/distro/oniro-linux.conf
index e46e3a16268aa2ee2516aaa297854df180d715b3..24569ddae5fbf592cbed88f17db89b1373691627 100644
--- a/meta-oniro-core/conf/distro/oniro-linux.conf
+++ b/meta-oniro-core/conf/distro/oniro-linux.conf
@@ -105,5 +105,6 @@ PREFERRED_VERSION_optee-os:qemu-generic-arm64 = "3.14.0"
 ENABLE_UART ?= "1"
 
 GPU_MEM:raspberrypi4-64 = "128"
+GPU_MEM:raspberrypi2 = "32"
 
 SPLASH = "psplash-oniro"
diff --git a/meta-oniro-core/recipes-bsp/grub/files/grub.cfg b/meta-oniro-core/recipes-bsp/grub/files/grub.cfg
index 3d290145cd8f149bffaf3acdd7777980ae85f7d2..6697aacdfbb164ab92fdfa27fa65fe187ec9d070 100644
--- a/meta-oniro-core/recipes-bsp/grub/files/grub.cfg
+++ b/meta-oniro-core/recipes-bsp/grub/files/grub.cfg
@@ -7,6 +7,86 @@
 
 serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
 
-# Boot the "A" slot all the time. This file is a stub.
-linux "(hd0,gpt2)/boot/bzImage" root=/dev/sda2 rauc.slot=A $CMDLINE
+set CMDLINE="console=ttyS0,115200 panic=60 quiet"
+
+# Find the boot partition. We need to know where to load from and save the
+# environment to. The boot partition is saved into the ONIRO_GRUB_BOOT
+# variable.
+#
+# TODO: switch to out-of-tree search by partition type uuid.
+#
+# XXX: the boot string is hard-coded but the .wks file allows customization via
+# BOOT_PARTITION_LABEL, which is unsupported here.
+if search --label --set ONIRO_BOOT_PART --no-floppy --hint-efi=hd0,gpt1 boot; then
+    echo "note: found ONIRO_BOOT_PART=$ONIRO_BOOT_PART"
+else
+    echo "error: cannot find boot partition, halting"
+    halt
+fi
+
+# Load a specific list of GRUB variables from the grubenv file. Ideally we
+# would never have this and we would use EFI variables instead but this is not
+# supported by RAUC without opting out of using GRUB itself.
+load_env --skip-sig --file "(${ONIRO_BOOT_PART})/EFI/BOOT/grubenv" SYSOTA_BOOT_ACTIVE SYSOTA_BOOT_TRY SYSOTA_BOOT_TRIED
+echo "note: loaded SYSOTA_BOOT_ACTIVE=$SYSOTA_BOOT_ACTIVE SYSOTA_BOOT_TRY=$SYSOTA_BOOT_TRY SYSOTA_BOOT_TRIED=$SYSOTA_BOOT_TRIED"
+
+# Recover active slot if the file is corrupted.
+if [ -z "$SYSOTA_BOOT_ACTIVE" ]; then
+    echo "warning: SYSOTA_BOOT_ACTIVE is unset, attempting recovery"
+    if [ -e "(hd0,gpt2)/boot/bzImage" -a -e "(hd0,gpt3)/boot/bzImage" ]; then
+        # Both slots are available, pick the more recent slot.
+        # TODO: handle rollback prevention flags (how?)
+        if [ "(hd0,gpt2)/boot/bzImage" -nt "(hd0,gpt3)/boot/bzImage" ]; then
+            echo "note: recovered SYSOTA_BOOT_ACTIVE=A (slot A kernel is newer than slot B)"
+            set SYSOTA_BOOT_ACTIVE="A"
+        else
+            echo "note: recovered SYSOTA_BOOT_ACTIVE=B (slot B kernel is newer than slot A)"
+            set SYSOTA_BOOT_ACTIVE="B"
+        fi
+    elif [ -e "(hd0,gpt2)/boot/bzImage" ]; then
+        echo "note: recovered SYSOTA_BOOT_ACTIVE=A"
+        set SYSOTA_BOOT_ACTIVE="A"
+    elif [ -e "(hd0,gpt3)/boot/bzImage" ]; then
+        echo "note: recovered SYSOTA_BOOT_ACTIVE=B"
+        set SYSOTA_BOOT_ACTIVE="B"
+    else
+        echo "error: cannot recover GRUB environment block: both slots are corrupted, halting"
+        halt
+    fi
+
+    echo "note: saving recovered GRUB environment block"
+    save_env --file "($ONIRO_BOOT_PART)/EFI/BOOT/grubenv" SYSOTA_BOOT_ACTIVE SYSOTA_BOOT_TRY SYSOTA_BOOT_TRIED
+fi
+
+# Find the slot to boot from.
+if [ -n "$SYSOTA_BOOT_TRY" ]; then
+    echo "note: performing one-time boot from slot $SYSOTA_BOOT_TRY"
+    set SLOT="$SYSOTA_BOOT_TRY"
+    set SYSOTA_BOOT_TRIED="$SYSOTA_BOOT_TRY"
+    unset SYSOTA_BOOT_TRY
+
+    echo "note: saving modified GRUB environment block"
+    save_env --file "($ONIRO_BOOT_PART)/EFI/BOOT/grubenv" SYSOTA_BOOT_ACTIVE SYSOTA_BOOT_TRY SYSOTA_BOOT_TRIED
+else
+    echo "note: performing normal boot from slot $SYSOTA_BOOT_ACTIVE"
+    set SLOT="$SYSOTA_BOOT_ACTIVE"
+fi
+
+# Load the kernel from a fixed location of the slot selected for booting.
+# The partition identifiers below refer to the fixed sys-a and sys-b
+# partitions.
+#
+# For a description of how to package the kernel into the rootfs image, refer
+# to the wiki page:
+# https://gitlab.eclipse.org/eclipse/oniro-core/sysota/-/wikis/Boot%20Assets%20Specification#efigrub
+if [ "$SLOT" == "A" ]; then
+    # TODO pass root partition by partuuid.
+    linux "(hd0,gpt2)/boot/bzImage" root=/dev/sda2 rauc.slot=A $CMDLINE
+elif [ "$SLOT" == "B" ]; then
+    linux "(hd0,gpt3)/boot/bzImage" root=/dev/sda3 rauc.slot=B $CMDLINE
+else
+    echo "error: cannot select slot to boot from, halting"
+    halt
+fi
+
 boot
diff --git a/meta-oniro-core/recipes-core/rauc/files/raspberrypi2/system.conf b/meta-oniro-core/recipes-core/rauc/files/raspberrypi2/system.conf
new file mode 100644
index 0000000000000000000000000000000000000000..5eb4ef71f631ca4780df4ad92ab8ffdbb8669b1b
--- /dev/null
+++ b/meta-oniro-core/recipes-core/rauc/files/raspberrypi2/system.conf
@@ -0,0 +1,39 @@
+# SPDX-FileCopyrightText: Huawei Inc.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+[system]
+compatible=Raspberry Pi 2
+# Use the custom boot loader backend. The handler program is set in the
+# [handlers] section below. This loops in SystemOTA into the update process and
+# delegates slot status and slot active flag responsibilities to it.
+bootloader=custom
+# Keep the RAUC status file in the system data partition, in a directory that
+# is common across revisions of the operating system. In other words, this file
+# is explicitly exempt from the A/B update process.
+statusfile=/run/mount/sysdata/common/status.raucs
+
+# Description of A/B slots used on the Raspberry Pi 2.
+# Refer to meta-oniro-core/wic/x-raspberrypi.wks.in for details.
+[slot.system.0]
+device=/dev/mmcblk0p2
+bootname=A
+
+[slot.system.1]
+device=/dev/mmcblk0p3
+bootname=B
+
+[keyring]
+path=/etc/rauc/oniro-insecure-cert.pem
+
+[handlers]
+# Use SystemOTA for RAUC pre-install and post-install handlers. This is
+# required for correct operation of the custom boot backend as well as for the
+# operation of the state management handlers as provided by SystemOTA.
+pre-install=/usr/libexec/sysota/rauc-pre-install-handler
+post-install=/usr/libexec/sysota/rauc-post-install-handler
+
+# Use SystemOTA to implement the custom RAUC boot backend. On platforms where
+# RAUC manages the boot loader directly remove this line and set the correct
+# bootloader= in the [system] section.
+bootloader-custom-backend=/usr/libexec/sysota/rauc-custom-boot-handler
diff --git a/meta-oniro-core/recipes-core/rauc/rauc_%.bbappend b/meta-oniro-core/recipes-core/rauc/rauc_%.bbappend
index fe05c1a1aa839e4bf46ee48f6064d1130cc9481f..5cbb8925b4b084085cc33dd7f8b6e4de945cd691 100644
--- a/meta-oniro-core/recipes-core/rauc/rauc_%.bbappend
+++ b/meta-oniro-core/recipes-core/rauc/rauc_%.bbappend
@@ -9,6 +9,7 @@
 # FIXME(zyga): The file defines RAUC compatible string which is technically
 # something that SystemOTA should be responsible for (make/model and remodel
 # operations). This should be addressed before re-model is supported.
+FILESEXTRAPATHS:prepend:raspberrypi2 := "${THISDIR}/files/raspberrypi2:"
 FILESEXTRAPATHS:prepend:raspberrypi4-64 := "${THISDIR}/files/raspberrypi4:"
 
 # Make the RAUC package machine-specific. This lets us put the specific configuration
diff --git a/meta-oniro-core/recipes-core/sysota/files/raspberrypi2/sysotad.conf b/meta-oniro-core/recipes-core/sysota/files/raspberrypi2/sysotad.conf
new file mode 100644
index 0000000000000000000000000000000000000000..e75dea31d190e0753426773918bc53b9dedcac98
--- /dev/null
+++ b/meta-oniro-core/recipes-core/sysota/files/raspberrypi2/sysotad.conf
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: Huawei Inc.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+[OTA]
+BootLoaderType=pi-boot
+BootPartitionMountDir=/run/mount/boot
+
+[Device]
+Maker=Oniro Project
+Model=Raspberry Pi 2B
+
+[Compatible]
+Machine=raspberrypi2
+
+[Quirks]
+RebootDelay=180
diff --git a/meta-oniro-core/recipes-core/sysota/sysota_git.bb b/meta-oniro-core/recipes-core/sysota/sysota_git.bb
index 41802ffadf22f1112a8f0bdf5a6024fb79e879a2..d77596a87ce31877c4cd8b7a296a12a8f35245e8 100644
--- a/meta-oniro-core/recipes-core/sysota/sysota_git.bb
+++ b/meta-oniro-core/recipes-core/sysota/sysota_git.bb
@@ -117,6 +117,7 @@ SYSTEMD_SERVICE:${PN} = "sysotad.service"
 
 # Specific MACHINE configurations have sysotad.conf which provides the right
 # settings, like the boot loader type.
+FILESEXTRAPATHS:prepend:raspberrypi2 := "${THISDIR}/files/raspberrypi2:"
 FILESEXTRAPATHS:prepend:raspberrypi4-64 := "${THISDIR}/files/raspberrypi4:"
 
 # Make the SystemOTA package machine-specific. This lets us put the specific