mmc: Set updated version to primary

Once the update is successful, mark the version as primary so that
it boots from the updated version upon BMC reboot.

Add a sleep to wait for the service file to complete setting the
primary version. Otherwise, the BMC could mark a Delete or
Priority value change as complete and the service is not done yet,
and if the BMC is rebooted it could try to boot from a non-existent
version.

As backgroung, reference issue openbmc/openbmc#2857 that attempted
to create a 'wait for service' function but was not successful.
This could be investigated further at a later time, which would
benefit other functions like Factory Reset that are also using
sleep as a workaround to wait for systemd service files.

Tested: Verified the version was set to primary during code update
        and delete, and that 3s passed (service file finished in
        about 1s) before the delete/update continued.

Change-Id: I4f9afdb020d8cc7c18cfdafe468dbff2dc8046c1
Signed-off-by: Adriana Kobylak <anoo@us.ibm.com>
diff --git a/meson.build b/meson.build
index 8bac461..fbb21a0 100644
--- a/meson.build
+++ b/meson.build
@@ -134,6 +134,7 @@
     unit_files += [
         'mmc/obmc-flash-mmc@.service.in',
         'mmc/obmc-flash-mmc-remove@.service.in',
+        'mmc/obmc-flash-mmc-setprimary@.service.in',
     ]
 endif
 
diff --git a/mmc/flash.cpp b/mmc/flash.cpp
index dd12b0d..3bd67fa 100644
--- a/mmc/flash.cpp
+++ b/mmc/flash.cpp
@@ -33,6 +33,7 @@
     msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
 
     auto mmcServiceFile = "obmc-flash-mmc@" + versionId + ".service";
+    auto mmcSetPrimary = "obmc-flash-mmc-setprimary@" + versionId + ".service";
 
     if (newStateUnit == mmcServiceFile && newStateResult == "done")
     {
@@ -40,7 +41,12 @@
         activationProgress->progress(activationProgress->progress() + 1);
     }
 
-    if (newStateUnit == mmcServiceFile)
+    if (newStateUnit == mmcSetPrimary && newStateResult == "done")
+    {
+        ubootEnvVarsUpdated = true;
+    }
+
+    if (newStateUnit == mmcServiceFile || newStateUnit == mmcSetPrimary)
     {
         if (newStateResult == "failed" || newStateResult == "dependency")
         {
@@ -49,7 +55,23 @@
         }
         else if (roVolumeCreated)
         {
-            Activation::onFlashWriteSuccess();
+            if (!ubootEnvVarsUpdated)
+            {
+                activationProgress->progress(90);
+
+                // Set the priority which triggers the service that updates the
+                // environment variables.
+                if (!Activation::redundancyPriority)
+                {
+                    Activation::redundancyPriority =
+                        std::make_unique<RedundancyPriority>(bus, path, *this,
+                                                             0);
+                }
+            }
+            else // Environment variables were updated
+            {
+                Activation::onFlashWriteSuccess();
+            }
         }
     }
 
diff --git a/mmc/item_updater_helper.cpp b/mmc/item_updater_helper.cpp
index 0eb910e..29b4e58 100644
--- a/mmc/item_updater_helper.cpp
+++ b/mmc/item_updater_helper.cpp
@@ -2,6 +2,8 @@
 
 #include "item_updater_helper.hpp"
 
+#include <thread>
+
 namespace phosphor
 {
 namespace software
@@ -38,9 +40,18 @@
     bus.call_noreply(method);
 }
 
-void Helper::updateUbootVersionId(const std::string& /* versionId */)
+void Helper::updateUbootVersionId(const std::string& versionId)
 {
-    // Empty
+    auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
+                                      SYSTEMD_INTERFACE, "StartUnit");
+    auto serviceFile = "obmc-flash-mmc-setprimary@" + versionId + ".service";
+    method.append(serviceFile, "replace");
+    bus.call_noreply(method);
+
+    // Wait a few seconds for the service file to finish, otherwise the BMC may
+    // be rebooted while pointing to a non-existent version.
+    constexpr auto setPrimaryWait = std::chrono::seconds(3);
+    std::this_thread::sleep_for(setPrimaryWait);
 }
 
 void Helper::mirrorAlt()
diff --git a/mmc/obmc-flash-mmc-setprimary@.service.in b/mmc/obmc-flash-mmc-setprimary@.service.in
new file mode 100644
index 0000000..69feafb
--- /dev/null
+++ b/mmc/obmc-flash-mmc-setprimary@.service.in
@@ -0,0 +1,7 @@
+[Unit]
+Description=Set %I as primary version
+
+[Service]
+Type=oneshot
+RemainAfterExit=no
+ExecStart=/usr/bin/obmc-flash-bmc mmc-setprimary %i
diff --git a/obmc-flash-bmc b/obmc-flash-bmc
index fec50d0..4611d91 100644
--- a/obmc-flash-bmc
+++ b/obmc-flash-bmc
@@ -447,6 +447,18 @@
 }
 
 # 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 running partition.
+mmc_get_primary_label() {
+  rootmatch=" on / "
+  root="$(mount -l | grep "${rootmatch}")"
+  if [[ "${root}" == *"rofs-a"* ]]; then
+    echo "a"
+  else
+    echo "b"
+  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 / "
@@ -502,6 +514,28 @@
   dd if=/dev/zero of=/dev/disk/by-partlabel/rofs-${label} count=2048
 }
 
+# Set the requested version as primary for the BMC to boot from upon reboot.
+mmc_setprimary() {
+  # Point root to the label of the requested BMC rootfs. If the label property
+  # does not exist, determine if the requested version is functional or not.
+  label=""
+  label_file="/var/lib/phosphor-bmc-code-mgmt/${version}/partlabel"
+  if [ -f "${label_file}" ]; then
+    label="$(cat "${label_file}")"
+  else
+    functional="$(busctl call xyz.openbmc_project.ObjectMapper \
+                  /xyz/openbmc_project/software/functional \
+                  org.freedesktop.DBus.Properties Get ss \
+                  xyz.openbmc_project.Association endpoints)"
+    if [[ "${functional}" =~ "${version}" ]]; then
+      label="$(mmc_get_primary_label)"
+    else
+      label="$(mmc_get_secondary_label)"
+    fi
+  fi
+  fw_setenv bootside "${label}"
+}
+
 case "$1" in
   mtduboot)
     reqmtd="$2"
@@ -574,6 +608,10 @@
     version="$2"
     mmc_remove
     ;;
+  mmc-setprimary)
+    version="$2"
+    mmc_setprimary
+    ;;
   *)
     echo "Invalid argument"
     exit 1