mmc: Update and remove functions for the kernel and rootfs

Add support for update and remove of eMMC images.

The code update tarball contains an image-kernel and image-rofs
files. These can be flashed directly to the eMMC partitions since
they're filesystem files compressed with zstd:

https://gerrit.openbmc-project.xyz/c/openbmc/meta-phosphor/+/30781
https://gerrit.openbmc-project.xyz/c/openbmc/meta-phosphor/+/34334

The image-rofs contains the BMC rootfs files, and the image-kernel
contains the fitImage file which is loaded by U-Boot.

Tested: Verified the non-running rofs and kernel partitions were updated.

Change-Id: Ic983dec1df389d56f11f12dc2e82589d1a2b9dcc
Signed-off-by: Adriana Kobylak <anoo@us.ibm.com>
diff --git a/activation.cpp b/activation.cpp
index 291ce7c..6b9dfb8 100644
--- a/activation.cpp
+++ b/activation.cpp
@@ -146,11 +146,11 @@
 
         flashWrite();
 
-#ifdef UBIFS_LAYOUT
+#if defined UBIFS_LAYOUT || defined MMC_LAYOUT
 
         return softwareServer::Activation::activation(value);
 
-#else // !UBIFS_LAYOUT
+#else // STATIC_LAYOUT
 
         onFlashWriteSuccess();
         return softwareServer::Activation::activation(
diff --git a/meson.build b/meson.build
index f39520e..8bac461 100644
--- a/meson.build
+++ b/meson.build
@@ -49,6 +49,7 @@
 # Supported BMC layout types
 conf.set('STATIC_LAYOUT', get_option('bmc-layout').contains('static'))
 conf.set('UBIFS_LAYOUT', get_option('bmc-layout').contains('ubi'))
+conf.set('MMC_LAYOUT', get_option('bmc-layout').contains('mmc'))
 
 # Configurable features
 conf.set('HOST_BIOS_UPGRADE', get_option('host-bios-upgrade').enabled())
@@ -129,6 +130,11 @@
         'mmc/flash.cpp',
         'mmc/item_updater_helper.cpp'
     )
+
+    unit_files += [
+        'mmc/obmc-flash-mmc@.service.in',
+        'mmc/obmc-flash-mmc-remove@.service.in',
+    ]
 endif
 
 if get_option('host-bios-upgrade').enabled()
diff --git a/mmc/flash.cpp b/mmc/flash.cpp
index afaadfb..dd12b0d 100644
--- a/mmc/flash.cpp
+++ b/mmc/flash.cpp
@@ -1,3 +1,5 @@
+#include "config.h"
+
 #include "flash.hpp"
 
 #include "activation.hpp"
@@ -9,14 +11,49 @@
 namespace updater
 {
 
+namespace softwareServer = sdbusplus::xyz::openbmc_project::Software::server;
+
 void Activation::flashWrite()
 {
-    // Empty
+    auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
+                                      SYSTEMD_INTERFACE, "StartUnit");
+    auto serviceFile = "obmc-flash-mmc@" + versionId + ".service";
+    method.append(serviceFile, "replace");
+    bus.call_noreply(method);
 }
 
-void Activation::onStateChanges(sdbusplus::message::message& /*msg*/)
+void Activation::onStateChanges(sdbusplus::message::message& msg)
 {
-    // Empty
+    uint32_t newStateID{};
+    sdbusplus::message::object_path newStateObjPath;
+    std::string newStateUnit{};
+    std::string newStateResult{};
+
+    // Read the msg and populate each variable
+    msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
+
+    auto mmcServiceFile = "obmc-flash-mmc@" + versionId + ".service";
+
+    if (newStateUnit == mmcServiceFile && newStateResult == "done")
+    {
+        roVolumeCreated = true;
+        activationProgress->progress(activationProgress->progress() + 1);
+    }
+
+    if (newStateUnit == mmcServiceFile)
+    {
+        if (newStateResult == "failed" || newStateResult == "dependency")
+        {
+            Activation::activation(
+                softwareServer::Activation::Activations::Failed);
+        }
+        else if (roVolumeCreated)
+        {
+            Activation::onFlashWriteSuccess();
+        }
+    }
+
+    return;
 }
 
 } // namespace updater
diff --git a/mmc/item_updater_helper.cpp b/mmc/item_updater_helper.cpp
index 8ef5749..0eb910e 100644
--- a/mmc/item_updater_helper.cpp
+++ b/mmc/item_updater_helper.cpp
@@ -1,3 +1,5 @@
+#include "config.h"
+
 #include "item_updater_helper.hpp"
 
 namespace phosphor
@@ -27,9 +29,13 @@
     // Empty
 }
 
-void Helper::removeVersion(const std::string& /* versionId */)
+void Helper::removeVersion(const std::string& versionId)
 {
-    // Empty
+    auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
+                                      SYSTEMD_INTERFACE, "StartUnit");
+    auto serviceFile = "obmc-flash-mmc-remove@" + versionId + ".service";
+    method.append(serviceFile, "replace");
+    bus.call_noreply(method);
 }
 
 void Helper::updateUbootVersionId(const std::string& /* versionId */)
diff --git a/mmc/obmc-flash-mmc-remove@.service.in b/mmc/obmc-flash-mmc-remove@.service.in
new file mode 100644
index 0000000..0fb4311
--- /dev/null
+++ b/mmc/obmc-flash-mmc-remove@.service.in
@@ -0,0 +1,7 @@
+[Unit]
+Description=Delete image %I from BMC storage
+
+[Service]
+Type=oneshot
+RemainAfterExit=no
+ExecStart=/usr/bin/obmc-flash-bmc mmc-remove %i
diff --git a/mmc/obmc-flash-mmc@.service.in b/mmc/obmc-flash-mmc@.service.in
new file mode 100644
index 0000000..f8b4262
--- /dev/null
+++ b/mmc/obmc-flash-mmc@.service.in
@@ -0,0 +1,8 @@
+[Unit]
+Description=Write image %I to BMC storage
+OnFailure=obmc-flash-mmc-remove@%i.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=no
+ExecStart=/usr/bin/obmc-flash-bmc mmc %i @IMG_UPLOAD_DIR@
diff --git a/obmc-flash-bmc b/obmc-flash-bmc
index 0b1b16e..fec50d0 100644
--- a/obmc-flash-bmc
+++ b/obmc-flash-bmc
@@ -446,6 +446,62 @@
   fi
 }
 
+# The eMMC partition labels for the kernel and rootfs are boot-a/b and rofs-a/b.
+# Return the label (a or b) for the non-running partition.
+mmc_get_secondary_label() {
+  rootmatch=" on / "
+  root="$(mount -l | grep "${rootmatch}")"
+  if [[ "${root}" == *"rofs-a"* ]]; then
+    echo "b"
+  else
+    echo "a"
+  fi
+}
+
+mmc_update() {
+  # Update the secondary (non-running) boot and rofs partitions.
+  label="$(mmc_get_secondary_label)"
+
+  # Update the boot and rootfs partitions, restore their labels after the update
+  # by getting the partition number mmcblk0pX from their label.
+  zstd -d -c ${imgpath}/${version}/image-kernel | dd of="/dev/disk/by-partlabel/boot-${label}"
+  number="$(readlink -f /dev/disk/by-partlabel/boot-${label})"
+  number="${number##*mmcblk0p}"
+  sgdisk --change-name=${number}:boot-${label} /dev/mmcblk0 1>/dev/null
+
+  zstd -d -c ${imgpath}/${version}/image-rofs | dd of="/dev/disk/by-partlabel/rofs-${label}"
+  number="$(readlink -f /dev/disk/by-partlabel/rofs-${label})"
+  number="${number##*mmcblk0p}"
+  sgdisk --change-name=${number}:rofs-${label} /dev/mmcblk0 1>/dev/null
+
+  # Run this after sgdisk for labels to take effect.
+  partprobe
+
+  # Store the label where the other properties like purpose and priority are
+  # preserved via the storePriority() function in the serialize files, so that
+  # it can be used for the remove function.
+  label_dir="/var/lib/phosphor-bmc-code-mgmt/${version}"
+  label_file="${label_dir}/partlabel"
+  mkdir -p "${label_dir}"
+  echo "${label}" > "${label_file}"
+}
+
+mmc_remove() {
+  # Render the filesystem unbootable by wiping out the first 1MB, this
+  # invalidates the filesystem header.
+  # If the label property does not exist, assume it's the secondary
+  # (non-running) one since the running device should not be erased.
+  label=""
+  label_file="/var/lib/phosphor-bmc-code-mgmt/${version}/partlabel"
+  if [ -f "${label_file}" ]; then
+    label="$(cat "${label_file}")"
+  else
+    label="$(mmc_get_secondary_label)"
+  fi
+  dd if=/dev/zero of=/dev/disk/by-partlabel/boot-${label} count=2048
+  dd if=/dev/zero of=/dev/disk/by-partlabel/rofs-${label} count=2048
+}
+
 case "$1" in
   mtduboot)
     reqmtd="$2"
@@ -509,6 +565,15 @@
   mirroruboot)
     mirroruboot
     ;;
+  mmc)
+    version="$2"
+    imgpath="$3"
+    mmc_update
+    ;;
+  mmc-remove)
+    version="$2"
+    mmc_remove
+    ;;
   *)
     echo "Invalid argument"
     exit 1