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/README.md b/bios-spi/README.md
new file mode 100644
index 0000000..78ba6f6
--- /dev/null
+++ b/bios-spi/README.md
@@ -0,0 +1,100 @@
+# SPI Device Update Daemon
+
+This daemon is for updating SPI flash chips commonly used for Host Bios.
+
+## Configuration Example 1 (Tyan S8030)
+
+This is an example EM Exposes record which can appear on dbus as
+
+```
+xyz.openbmc_project.Configuration.SPIFlash
+```
+
+This config is untested.
+
+```json
+{
+ "Name": "HostSPIFlash",
+ "Path": "1e630000.spi",
+ "HasME": false,
+ "Layout": "Flat",
+ "MuxGpios": [
+ {
+ "Name": "BMC_SPI_SEL",
+ "Polarity": "High"
+ }
+ ],
+ "VendorIANA": "6653",
+ "Compatible": "com.tyan.Hardware.S8030.SPI.Host",
+ "Type": "SPIFlash"
+}
+```
+
+- 'HasME' is referring to the Intel Management Engine.
+
+## Configuration Example 2 (Tyan S5549)
+
+Here we are writing a previously dumped flash image using flashrom.
+
+```json
+{
+ "Name": "HostSPIFlash",
+ "Path": "1e630000.spi",
+ "HasME": true,
+ "Layout": "Flat",
+ "Tool": "flashrom",
+ "MuxGpios": [
+ {
+ "Name": "FM_BIOS_SPI_SWITCH",
+ "Polarity": "High"
+ },
+ {
+ "Name": "FM_BMC_FLASH_SEC_OVRD_N",
+ "Polarity": "High"
+ }
+ ],
+ "VendorIANA": "6653",
+ "Compatible": "com.tyan.Hardware.S5549.SPI.Host",
+ "Type": "SPIFlash"
+}
+```
+
+## Layout information
+
+Sometimes another tool is needed if one does not have a flat image.
+Configuration fragments below.
+
+No tool, flat image. This can be used for example when we want to write a flash
+image which was previously dumped.
+
+```json
+{
+ "Layout": "Flat"
+}
+```
+
+Use flashrom to write with information from an intel flash descriptor
+
+```json
+{
+ "Layout": "IFD"
+}
+```
+
+## Tool information
+
+We can directly write to the mtd device or use flashrom to do the writing. In
+case you want to use Intel Flash Descriptor (not flat layout) or do additional
+verification, flashrom can be used.
+
+```json
+{
+ "Tool": "flashrom"
+}
+```
+
+```json
+{
+ "Tool": "None"
+}
+```
diff --git a/bios-spi/main.cpp b/bios-spi/main.cpp
new file mode 100644
index 0000000..0a63d92
--- /dev/null
+++ b/bios-spi/main.cpp
@@ -0,0 +1,136 @@
+#include "spi_device_code_updater.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/server.hpp>
+
+#include <fstream>
+#include <iostream>
+
+// implementing the design
+// https://github.com/openbmc/docs/blob/377ed14df4913010752ee2faff994a50e12a6316/designs/code-update.md
+
+// NOLINTNEXTLINE
+sdbusplus::async::task<> startManualUpdate(sdbusplus::async::context& ctx,
+ SPIDeviceCodeUpdater& spidcu,
+ const std::string& imageFilename)
+{
+ if (spidcu.devices.empty())
+ {
+ lg2::error("no device available for manual update");
+ co_return;
+ }
+
+ const std::unique_ptr<Device>& device = *spidcu.devices.begin();
+
+ std::ifstream file(imageFilename, std::ios::binary | std::ios::ate);
+
+ if (!file.good())
+ {
+ lg2::error("error opening file {FILENAME}", "FILENAME", imageFilename);
+ co_return;
+ }
+
+ std::streamsize size = file.tellg();
+ file.seekg(0, std::ios::beg);
+
+ auto buffer = std::make_unique<uint8_t[]>(size);
+
+ if (!file.read(reinterpret_cast<char*>(buffer.get()), size))
+ {
+ throw std::runtime_error("Error reading file: " + imageFilename);
+ }
+
+ // TODO: find the proper object path here
+ auto sap =
+ std::make_unique<SoftwareActivationProgress>(ctx, "/dummyActivation");
+
+ co_await device->updateDevice(buffer.get(), size, sap);
+
+ co_return;
+}
+
+// NOLINTNEXTLINE
+sdbusplus::async::task<> start(sdbusplus::async::context& ctx,
+ SPIDeviceCodeUpdater& spidcu, bool manual,
+ const std::string& imageFilename)
+{
+ std::vector<std::string> configIntfs = {
+ "xyz.openbmc_project.Configuration." + configTypeSPIDevice};
+
+ co_await spidcu.getInitialConfiguration(configIntfs);
+
+ if (manual)
+ {
+ co_await startManualUpdate(ctx, spidcu, imageFilename);
+ }
+
+ co_return;
+}
+
+void printHelpText()
+{
+ std::cout << "--help : print help" << std::endl;
+ std::cout << "--manual : start a manual update" << std::endl;
+ std::cout << "--image <filename> : filename for manual update"
+ << std::endl;
+}
+
+int main(int argc, char* argv[])
+{
+ // getting a really unspecific error from clang-tidy here
+ // about an uninitialized / garbage branch. Happy to discuss.
+
+ // NOLINTBEGIN
+
+ sdbusplus::async::context ctx;
+
+ bool manualUpdate = false;
+ bool printHelp = false;
+ bool dryRun = false;
+ bool debug = false;
+ std::string imageFilename = "";
+
+ for (int i = 1; i < argc; i++)
+ {
+ std::string arg = std::string(argv[i]);
+ if (arg == "--manual")
+ {
+ manualUpdate = true;
+ }
+ if (arg == "--image" && i < argc - 1)
+ {
+ imageFilename = std::string(argv[i + 1]);
+ i++;
+ }
+ if (arg == "--help")
+ {
+ printHelp = true;
+ }
+ if (arg == "--dryrun")
+ {
+ dryRun = true;
+ }
+ if (arg == "-debug")
+ {
+ debug = true;
+ }
+ }
+
+ if (printHelp)
+ {
+ printHelpText();
+ }
+
+ SPIDeviceCodeUpdater spidcu(ctx, dryRun, debug);
+
+ ctx.spawn(start(ctx, spidcu, manualUpdate, imageFilename));
+
+ ctx.run();
+
+ // NOLINTEND
+
+ return 0;
+}
diff --git a/bios-spi/meson.build b/bios-spi/meson.build
new file mode 100644
index 0000000..e2d6853
--- /dev/null
+++ b/bios-spi/meson.build
@@ -0,0 +1,27 @@
+
+bios_spi_src = files(
+ 'spi_device.cpp',
+ 'spi_device_code_updater.cpp'
+)
+
+bios_spi_include = include_directories('.')
+
+executable(
+ 'phosphor-fw-update-bios',
+ 'main.cpp',
+ bios_spi_src,
+ include_directories: [
+ common_include,
+ bios_spi_include,
+ ],
+ dependencies: [
+ sdbusplus_dep,
+ phosphor_logging_dep,
+ pdi_dep,
+ boost_dep,
+ libgpiod,
+ libpldm_dep,
+ ],
+ link_with: [libpldmutil, software_common_lib],
+ install: true
+)
diff --git a/bios-spi/spi_device.cpp b/bios-spi/spi_device.cpp
new file mode 100644
index 0000000..46fe1dc
--- /dev/null
+++ b/bios-spi/spi_device.cpp
@@ -0,0 +1,405 @@
+#include "spi_device.hpp"
+
+#include "common/include/device.hpp"
+#include "common/include/software_manager.hpp"
+
+#include <gpiod.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/async/context.hpp>
+
+#include <fstream>
+
+using namespace std::literals;
+
+SPIDevice::SPIDevice(
+ sdbusplus::async::context& ctx, const std::string& spiDevName, bool dryRun,
+ bool hasME, const std::vector<std::string>& gpioLines,
+ const std::vector<uint8_t>& gpioValues, DeviceConfig& config,
+ SoftwareManager* parent, bool layoutFlat, bool toolFlashrom, bool debug) :
+ Device(ctx, dryRun, config, parent), hasManagementEngine(hasME),
+ gpioLines(gpioLines), gpioValues(gpioValues), spiDev(spiDevName),
+ layoutFlat(layoutFlat), toolFlashrom(toolFlashrom), debug(debug)
+{
+ lg2::debug("initialized SPI Device instance on dbus");
+}
+
+// NOLINTBEGIN
+sdbusplus::async::task<std::string> SPIDevice::getInventoryItemObjectPath()
+// NOLINTEND
+{
+ // TODO: we currently do not know how to access the object path of the
+ // inventory item here
+ co_return "";
+}
+
+// NOLINTBEGIN
+sdbusplus::async::task<bool> SPIDevice::updateDevice(
+ const uint8_t* image, size_t image_size,
+ std::unique_ptr<SoftwareActivationProgress>& activationProgress)
+// NOLINTEND
+{
+ // NOLINTBEGIN
+ bool success =
+ co_await this->writeSPIFlash(image, image_size, activationProgress);
+ // NOLINTEND
+ co_return success;
+}
+
+constexpr const char* IPMB_SERVICE = "xyz.openbmc_project.Ipmi.Channel.Ipmb";
+constexpr const char* IPMB_PATH = "/xyz/openbmc_project/Ipmi/Channel/Ipmb";
+constexpr const char* IPMB_INTF = "org.openbmc.Ipmb";
+
+// NOLINTBEGIN
+sdbusplus::async::task<> SPIDevice::setManagementEngineRecoveryMode()
+// NOLINTEND
+{
+ lg2::info("[ME] setting Management Engine to recovery mode");
+ auto m = ctx.get_bus().new_method_call(IPMB_SERVICE, IPMB_PATH, IPMB_INTF,
+ "sendRequest");
+
+ // me address, 0x2e oen, 0x00 - lun, 0xdf - force recovery
+ uint8_t cmd_recover[] = {0x1, 0x2e, 0x0, 0xdf};
+ for (unsigned int i = 0; i < sizeof(cmd_recover); i++)
+ {
+ m.append(cmd_recover[i]);
+ }
+ std::vector<uint8_t> remainder = {0x04, 0x57, 0x01, 0x00, 0x01};
+ m.append(remainder);
+
+ m.call();
+
+ co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(5));
+
+ co_return;
+}
+
+// NOLINTBEGIN
+sdbusplus::async::task<> SPIDevice::resetManagementEngine()
+// NOLINTEND
+{
+ lg2::info("[ME] resetting Management Engine");
+ auto m = ctx.get_bus().new_method_call(IPMB_SERVICE, IPMB_PATH, IPMB_INTF,
+ "sendRequest");
+
+ // me address, 0x6 App Fn, 0x00 - lun, 0x2 - cold reset
+ uint8_t cmd_recover[] = {0x1, 0x6, 0x0, 0x2};
+ for (unsigned int i = 0; i < sizeof(cmd_recover); i++)
+ {
+ m.append(cmd_recover[i]);
+ }
+ std::vector<uint8_t> remainder;
+ m.append(remainder);
+
+ m.call();
+
+ co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(5));
+
+ co_return;
+}
+
+const std::string spiAspeedSMCPath = "/sys/bus/platform/drivers/spi-aspeed-smc";
+
+// NOLINTBEGIN
+sdbusplus::async::task<bool> SPIDevice::bindSPIFlash()
+// NOLINTEND
+{
+ lg2::info("[SPI] binding flash to SMC");
+ std::ofstream ofbind(spiAspeedSMCPath + "/bind", std::ofstream::out);
+ ofbind << this->spiDev;
+ ofbind.close();
+
+ // wait for kernel
+ co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
+
+ co_return isSPIFlashBound();
+}
+
+// NOLINTBEGIN
+sdbusplus::async::task<bool> SPIDevice::unbindSPIFlash()
+// NOLINTEND
+{
+ lg2::info("[SPI] unbinding flash from SMC");
+ std::ofstream ofunbind(spiAspeedSMCPath + "/unbind", std::ofstream::out);
+ ofunbind << this->spiDev;
+ ofunbind.close();
+
+ // wait for kernel
+ co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
+
+ co_return !isSPIFlashBound();
+}
+
+bool SPIDevice::isSPIFlashBound()
+{
+ std::string path = spiAspeedSMCPath + "/" + this->spiDev;
+ lg2::debug("[SPI] checking {PATH}", "PATH", path);
+
+ return std::filesystem::exists(path);
+}
+
+// NOLINTBEGIN
+sdbusplus::async::task<bool> SPIDevice::writeSPIFlash(
+ const uint8_t* image, size_t image_size,
+ const std::unique_ptr<SoftwareActivationProgress>& activationProgress)
+// NOLINTEND
+{
+ auto currentPowerstateOpt = co_await parent->getHostPowerstate();
+
+ if (!currentPowerstateOpt.has_value())
+ {
+ co_return false;
+ }
+
+ const bool prevPowerstate = currentPowerstateOpt.value();
+
+ // NOLINTBEGIN
+ bool success = co_await parent->setHostPowerstate(false);
+ // NOLINTEND
+ if (!success)
+ {
+ lg2::error("error changing host power state");
+ co_return false;
+ }
+ activationProgress->progress(10);
+
+ if (hasManagementEngine)
+ {
+ co_await setManagementEngineRecoveryMode();
+ }
+ activationProgress->progress(20);
+
+ success = co_await writeSPIFlashHostOff(image, image_size);
+
+ if (success)
+ {
+ activationProgress->progress(70);
+ }
+
+ if (hasManagementEngine)
+ {
+ co_await resetManagementEngine();
+ }
+
+ if (success)
+ {
+ activationProgress->progress(90);
+ }
+
+ // restore the previous powerstate
+ const bool powerstate_restore =
+ co_await parent->setHostPowerstate(prevPowerstate);
+ if (!powerstate_restore)
+ {
+ lg2::error("error changing host power state");
+ co_return false;
+ }
+
+ // return value here is only describing if we successfully wrote to the
+ // SPI flash. Restoring powerstate can still fail.
+ co_return success;
+}
+
+// NOLINTBEGIN
+sdbusplus::async::task<bool>
+ SPIDevice::writeSPIFlashHostOff(const uint8_t* image, size_t image_size)
+// NOLINTEND
+{
+ gpiod::chip chip;
+ try
+ {
+ // TODO: make it work for multiple chips
+ chip = gpiod::chip("/dev/gpiochip0");
+ }
+ catch (std::exception& e)
+ {
+ lg2::error(e.what());
+ co_return false;
+ }
+
+ std::vector<unsigned int> offsets;
+
+ for (const std::string& lineName : gpioLines)
+ {
+ const ::gpiod::line line = ::gpiod::find_line(lineName);
+
+ if (line.is_used())
+ {
+ lg2::error("gpio line {LINE} was still used", "LINE", lineName);
+ co_return false;
+ }
+ offsets.push_back(line.offset());
+ }
+
+ auto lines = chip.get_lines(offsets);
+
+ ::gpiod::line_request config{"", ::gpiod::line_request::DIRECTION_OUTPUT,
+ 0};
+ std::vector<int> values;
+ std::vector<int> valuesInverted;
+ values.reserve(gpioValues.size());
+
+ for (uint8_t value : gpioValues)
+ {
+ values.push_back(value);
+ valuesInverted.push_back(value ? 0 : 1);
+ }
+
+ lg2::debug("[gpio] requesting gpios to mux SPI to BMC");
+ lines.request(config, values);
+
+ co_await writeSPIFlashHostOffGPIOSet(image, image_size);
+
+ lines.release();
+
+ // switch bios flash back to host via mux / GPIO
+ // (not assume there is a pull to the default value)
+ lg2::debug("[gpio] requesting gpios to mux SPI to Host");
+ lines.request(config, valuesInverted);
+
+ lines.release();
+
+ co_return true;
+}
+
+// NOLINTBEGIN
+sdbusplus::async::task<bool> SPIDevice::writeSPIFlashHostOffGPIOSet(
+ const uint8_t* image, size_t image_size)
+// NOLINTEND
+{
+ bool success = true;
+
+ if (SPIDevice::isSPIFlashBound())
+ {
+ lg2::debug("[SPI] flash was already bound, unbinding it now");
+ success = co_await SPIDevice::unbindSPIFlash();
+
+ if (!success)
+ {
+ lg2::error("[SPI] error unbinding spi flash");
+ co_return false;
+ }
+ }
+
+ success = co_await SPIDevice::bindSPIFlash();
+
+ if (!success)
+ {
+ lg2::error("[SPI] failed to bind spi device");
+ co_await SPIDevice::unbindSPIFlash();
+ co_return false;
+ }
+
+ if (dryRun)
+ {
+ lg2::info("[SPI] dry run, NOT writing to the chip");
+ }
+ else
+ {
+ if (this->toolFlashrom)
+ {
+ co_await SPIDevice::writeSPIFlashFlashromHostOffGPIOSetDeviceBound(
+ image, image_size);
+ }
+ else
+ {
+ co_await SPIDevice::writeSPIFlashHostOffGPIOSetDeviceBound(
+ image, image_size);
+ }
+ }
+
+ success = co_await SPIDevice::unbindSPIFlash();
+
+ co_return success;
+}
+
+// NOLINTBEGIN
+sdbusplus::async::task<bool>
+ SPIDevice::writeSPIFlashFlashromHostOffGPIOSetDeviceBound(
+ const uint8_t* image, size_t image_size)
+// NOLINTEND
+{
+ // TODO: randomize the name to enable parallel updates
+ std::string path = "/tmp/spi-device-image.bin";
+ int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
+ if (fd < 0)
+ {
+ lg2::error("[SPI] Failed to open file: {PATH}", "PATH", path);
+ co_return false;
+ }
+
+ const ssize_t bytesWritten = write(fd, image, image_size);
+
+ close(fd);
+
+ if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size)
+ {
+ lg2::error("[SPI] Failed to write image to file");
+ co_return false;
+ }
+
+ // TODO: do not hardcode the mtd device
+ std::string cmd = "flashrom -p linux_mtd:dev=6 ";
+
+ if (this->layoutFlat)
+ {
+ cmd += "-w " + path;
+ }
+ else
+ {
+ cmd += "--ifd -i fd -i bios -i me -w " + path;
+ }
+
+ lg2::info("[flashrom] running {CMD}", "CMD", cmd);
+
+ const int exitCode = std::system(cmd.c_str());
+
+ if (exitCode != 0)
+ {
+ lg2::error("[SPI] error running flaashrom");
+ }
+
+ // in debug mode we do not delete the raw component image
+ if (!this->debug)
+ {
+ std::filesystem::remove(path);
+ }
+
+ co_return exitCode;
+}
+
+// NOLINTBEGIN
+sdbusplus::async::task<bool> SPIDevice::writeSPIFlashHostOffGPIOSetDeviceBound(
+ const uint8_t* image, size_t image_size)
+// NOLINTEND
+{
+ // TODO: not hardcode the mtd device
+ std::string devPath = "/dev/mtd6";
+ int fd = open(devPath.c_str(), O_WRONLY);
+ if (fd < 0)
+ {
+ lg2::error("[SPI] Failed to open device: {PATH}", "PATH", devPath);
+ co_return false;
+ }
+
+ ssize_t bytesWritten = write(fd, image, image_size);
+
+ close(fd);
+
+ if (bytesWritten < 0)
+ {
+ lg2::error("[SPI] Failed to write to device");
+ co_return false;
+ }
+
+ if (static_cast<size_t>(bytesWritten) != image_size)
+ {
+ lg2::error("[SPI] Incomplete write to device");
+ co_return false;
+ }
+
+ lg2::info("[SPI] Successfully wrote {NBYTES} bytes to {PATH}", "NBYTES",
+ bytesWritten, "PATH", devPath);
+
+ co_return true;
+}
diff --git a/bios-spi/spi_device.hpp b/bios-spi/spi_device.hpp
new file mode 100644
index 0000000..f28c4ac
--- /dev/null
+++ b/bios-spi/spi_device.hpp
@@ -0,0 +1,102 @@
+#pragma once
+
+#include "common/include/device.hpp"
+#include "common/include/software_manager.hpp"
+
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/async/context.hpp>
+
+#include <string>
+
+class Software;
+
+class SPIDevice : public Device
+{
+ public:
+ SPIDevice(sdbusplus::async::context& ctx, const std::string& spiDevName,
+ bool dryRun, bool hasME,
+ const std::vector<std::string>& gpioLines,
+ const std::vector<uint8_t>& gpioValues, DeviceConfig& config,
+ SoftwareManager* parent, bool layoutFlat, bool toolFlashrom,
+ bool debug);
+
+ sdbusplus::async::task<bool> updateDevice(
+ const uint8_t* image, size_t image_size,
+ std::unique_ptr<SoftwareActivationProgress>& activationProgress) final;
+
+ sdbusplus::async::task<std::string> getInventoryItemObjectPath() final;
+
+ private:
+ // Management Engine specific members and functions
+ bool hasManagementEngine;
+ sdbusplus::async::task<> setManagementEngineRecoveryMode();
+ sdbusplus::async::task<> resetManagementEngine();
+
+ std::vector<std::string> gpioLines;
+ std::vector<uint8_t> gpioValues;
+
+ // SPI specific members and functions
+ std::string spiDev;
+
+ // does the spi flash have a flat layout?
+ // Otherwise, we have to use Intel Flash Descriptor
+ // or another descriptor to figure out which regions should be written
+ bool layoutFlat;
+
+ // do we use flashrom?
+ // if not, write directly to the mtd device.
+ bool toolFlashrom;
+
+ // @param spi_dev e.g. "1e630000.spi"
+ // @returns true on success
+ sdbusplus::async::task<bool> bindSPIFlash();
+
+ // @param spi_dev e.g. "1e630000.spi"
+ // @returns true on success
+ sdbusplus::async::task<bool> unbindSPIFlash();
+
+ // @param spi_dev e.g. "1e630000.spi"
+ bool isSPIFlashBound();
+
+ bool debug;
+
+ sdbusplus::async::task<bool> writeSPIFlash(
+ const uint8_t* image, size_t image_size,
+ const std::unique_ptr<SoftwareActivationProgress>& activationProgress);
+
+ // this function assumes:
+ // - host is powered off
+ sdbusplus::async::task<bool>
+ writeSPIFlashHostOff(const uint8_t* image, size_t image_size);
+
+ // this function assumes:
+ // - host is powered off
+ // - gpio / mux is set
+ sdbusplus::async::task<bool>
+ writeSPIFlashHostOffGPIOSet(const uint8_t* image, size_t image_size);
+
+ // this function assumes:
+ // - host is powered off
+ // - gpio / mux is set
+ // - spi device is bound to the driver
+ // we write the flat image here
+ // @param image the component image
+ // @param image_size size of 'image'
+ // @returns true on success
+ sdbusplus::async::task<bool> writeSPIFlashHostOffGPIOSetDeviceBound(
+ const uint8_t* image, size_t image_size);
+
+ // this function assumes:
+ // - host is powered off
+ // - gpio / mux is set
+ // - spi device is bound to the driver
+ // we use 'flashrom' here to write the image since it can deal with
+ // Intel Flash Descriptor
+ // TODO: look into using libflashrom instead
+ // @param image the component image
+ // @param image_size size of 'image'
+ // @returns true on success
+ sdbusplus::async::task<bool> writeSPIFlashFlashromHostOffGPIOSetDeviceBound(
+ const uint8_t* image, size_t image_size);
+};
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));
+}
diff --git a/bios-spi/spi_device_code_updater.hpp b/bios-spi/spi_device_code_updater.hpp
new file mode 100644
index 0000000..fbe39f9
--- /dev/null
+++ b/bios-spi/spi_device_code_updater.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "common/include/software_manager.hpp"
+#include "sdbusplus/async/context.hpp"
+
+#include <sdbusplus/async.hpp>
+
+const std::string configTypeSPIDevice = "SPIFlash";
+
+class SPIDeviceCodeUpdater : public SoftwareManager
+{
+ public:
+ SPIDeviceCodeUpdater(sdbusplus::async::context& ctx, bool isDryRun,
+ bool debug);
+
+ sdbusplus::async::task<> getInitialConfigurationSingleDevice(
+ const std::string& service, const std::string& path,
+ DeviceConfig& config) final;
+
+ bool debug;
+
+ private:
+};
diff --git a/meson.build b/meson.build
index 8db7902..370b489 100644
--- a/meson.build
+++ b/meson.build
@@ -66,7 +66,7 @@
common_include = include_directories('.')
-common_build = build_tests.allowed()
+common_build = build_tests.allowed() or get_option('code-updater-spi-device').enabled()
if common_build
libpldm_dep = dependency('libpldm')
@@ -80,7 +80,10 @@
subdir('common')
endif
+if get_option('code-updater-spi-device').enabled()
+ subdir('bios-spi')
+endif
+
if build_tests.allowed()
subdir('test')
endif
-
diff --git a/meson.options b/meson.options
index 605786d..b477342 100644
--- a/meson.options
+++ b/meson.options
@@ -133,3 +133,8 @@
value: '/run/media/rwfs-alt/cow',
description: 'The dir for alt-rwfs partition.',
)
+
+option(
+ 'code-updater-spi-device', type: 'feature', value: 'enabled',
+ description: 'enable update of spi device / host fw',
+)
diff --git a/test/bios-spi/meson.build b/test/bios-spi/meson.build
new file mode 100644
index 0000000..149f4a0
--- /dev/null
+++ b/test/bios-spi/meson.build
@@ -0,0 +1,23 @@
+e = executable(
+ 'test_bios_spi',
+ 'test_bios_spi.cpp',
+ bios_spi_src,
+ include_directories: [
+ common_include,
+ bios_spi_include,
+ ],
+ dependencies: [
+ libgpiod,
+ libpldm_dep,
+ sdbusplus_dep,
+ pdi_dep,
+ phosphor_logging_dep,
+ ],
+ link_with: [libpldmutil, software_common_lib],
+)
+
+test(
+ 'test_bios_spi',
+ e,
+ workdir: meson.current_source_dir(),
+)
diff --git a/test/bios-spi/test_bios_spi.cpp b/test/bios-spi/test_bios_spi.cpp
new file mode 100644
index 0000000..aea6ebc
--- /dev/null
+++ b/test/bios-spi/test_bios_spi.cpp
@@ -0,0 +1,43 @@
+#include "bios-spi/spi_device.hpp"
+#include "spi_device_code_updater.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Software/Update/server.hpp>
+
+#include <iostream>
+#include <memory>
+
+int main()
+{
+ sdbusplus::async::context ctx;
+
+ try
+ {
+ SPIDeviceCodeUpdater spidcu(ctx, true, false);
+ uint32_t vendorIANA = 0x0000a015;
+ std::string compatible = "com.testvendor.testcomponent";
+ SPIDeviceCodeUpdater* cu = &spidcu;
+ std::vector<std::string> gpioNames;
+ std::vector<uint8_t> gpioValues;
+
+ DeviceConfig config(vendorIANA, compatible, "SPIFlash", "HostSPI");
+
+ auto sd = std::make_unique<SPIDevice>(
+ ctx, "1e630000.spi", true, true, gpioNames, gpioValues, config, cu,
+ true, true, false);
+
+ spidcu.devices.insert(std::move(sd));
+ }
+ catch (std::exception& e)
+ {
+ std::cerr << e.what() << std::endl;
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/test/meson.build b/test/meson.build
index 6c436e2..bf0ca44 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -7,3 +7,4 @@
)
subdir('common')
+subdir('bios-spi')