fw update: spi device code updater

This code updater is for updating spi flash devices.
It can for example update the host firmware on different server boards
and has following features:

- power down the host before update
- set mux gpios to access spi flash
- (very limited) communication with ME (Management Engine)
- use flashrom to utilize fw with IFD (Intel Flash Descriptor)
- otherwise directly write to the flash chip.

The behavior of this code updater can be configured via EM.

Tested: on Tyan S5549 Board

Change-Id: I27803b7fded71af2364c2f55fad841a410603dec
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/bios-spi/spi_device_code_updater.cpp b/bios-spi/spi_device_code_updater.cpp
new file mode 100644
index 0000000..888d5c8
--- /dev/null
+++ b/bios-spi/spi_device_code_updater.cpp
@@ -0,0 +1,157 @@
+#include "spi_device_code_updater.hpp"
+
+#include "common/include/software_manager.hpp"
+#include "spi_device.hpp"
+
+#include <gpiod.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/ObjectMapper/client.hpp>
+
+SPIDeviceCodeUpdater::SPIDeviceCodeUpdater(sdbusplus::async::context& ctx,
+                                           bool isDryRun, bool debug) :
+    SoftwareManager(ctx, configTypeSPIDevice, isDryRun), debug(debug)
+{}
+
+// NOLINTBEGIN
+sdbusplus::async::task<>
+    SPIDeviceCodeUpdater::getInitialConfigurationSingleDevice(
+        const std::string& service, const std::string& path,
+        DeviceConfig& config)
+// NOLINTEND
+{
+    std::optional<std::string> optSpiPath =
+        co_await SoftwareManager::dbusGetRequiredConfigurationProperty<
+            std::string>(service, path, "Path", config);
+
+    std::optional<bool> optHasME =
+        co_await SoftwareManager::dbusGetRequiredConfigurationProperty<bool>(
+            service, path, "HasME", config);
+
+    std::optional<std::string> optLayout =
+        co_await SoftwareManager::dbusGetRequiredConfigurationProperty<
+            std::string>(service, path, "Layout", config);
+
+    std::optional<std::string> optTool =
+        co_await SoftwareManager::dbusGetRequiredConfigurationProperty<
+            std::string>(service, path, "Tool", config);
+
+    if (!optSpiPath.has_value() || !optHasME.has_value())
+    {
+        co_return;
+    }
+
+    std::string spiPath = optSpiPath.value();
+    bool hasME = optHasME.value();
+
+    lg2::debug("[config] spi device: {SPIDEV}", "SPIDEV", spiPath);
+
+    std::vector<std::string> gpioLines;
+    std::vector<uint8_t> gpioValues;
+
+    std::string configIntfMuxGpios;
+
+    std::vector<std::string> configIntfs = {
+        "xyz.openbmc_project.Configuration." + configTypeSPIDevice};
+
+    for (auto& iface : configIntfs)
+    {
+        configIntfMuxGpios = iface + ".MuxGpios";
+    }
+
+    for (size_t i = 0; true; i++)
+    {
+        std::string intf = configIntfMuxGpios + std::to_string(i);
+        std::optional<std::string> optGpioName =
+            co_await SoftwareManager::dbusGetRequiredProperty<std::string>(
+                service, path, intf, "Name");
+        std::optional<std::string> optGpioPolarity =
+            co_await SoftwareManager::dbusGetRequiredProperty<std::string>(
+                service, path, intf, "Polarity");
+
+        if (!optGpioName.has_value() || !optGpioPolarity.has_value())
+        {
+            break;
+        }
+
+        gpioLines.push_back(optGpioName.value());
+        gpioValues.push_back((optGpioPolarity.value() == "High") ? 1 : 0);
+
+        lg2::debug("[config] gpio {NAME} = {VALUE}", "NAME",
+                   optGpioName.value(), "VALUE", optGpioPolarity.value());
+    }
+
+    lg2::debug("[config] extracted {N} gpios from EM config", "N",
+               gpioLines.size());
+
+    bool layoutFlat;
+
+    if (!optLayout.has_value())
+    {
+        lg2::info("[config] error: no flash layout chosen (property 'Layout')");
+        co_return;
+    }
+
+    const std::string& layout = optLayout.value();
+    if (layout == "Flat")
+    {
+        layoutFlat = true;
+    }
+    else if (layout == "IFD")
+    {
+        layoutFlat = false;
+    }
+    else
+    {
+        lg2::error("[config] unsupported flash layout config: {OPTION}",
+                   "OPTION", layout);
+        lg2::info("supported options: 'Flat', 'IFD'");
+        co_return;
+    }
+
+    bool toolFlashrom;
+    if (!optTool.has_value())
+    {
+        lg2::error("[config] error: no tool chose (property 'Tool')");
+        co_return;
+    }
+
+    const std::string& tool = optTool.value();
+
+    if (tool == "flashrom")
+    {
+        toolFlashrom = true;
+    }
+    else if (tool == "None")
+    {
+        toolFlashrom = false;
+    }
+    else
+    {
+        lg2::error("[config] unsupported Tool: {OPTION}", "OPTION", tool);
+        co_return;
+    }
+
+    auto spiDevice = std::make_unique<SPIDevice>(
+        ctx, spiPath, dryRun, hasME, gpioLines, gpioValues, config, this,
+        layoutFlat, toolFlashrom, this->debug);
+
+    // we do not know the version on startup, it becomes known on update
+    std::string version = "unknown";
+
+    std::unique_ptr<Software> bsws =
+        std::make_unique<Software>(ctx, "spi_swid_unknown", *spiDevice);
+
+    bsws->setVersion("unknown");
+
+    // enable this software to be updated
+    std::set<RequestedApplyTimes> allowedApplyTimes = {
+        RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset};
+
+    bsws->enableUpdate(allowedApplyTimes);
+
+    spiDevice->softwareCurrent = std::move(bsws);
+
+    devices.insert(std::move(spiDevice));
+}