TPM code updater
This commit introduces a TPM code updater that currently supports
reading the firmware version for both Infineon and Nuvoton TPM 2.0.
Support for firmware updates will be introduced in a future patch.
The updater's configuration are managed by the EM [1].
[1] https://gerrit.openbmc.org/c/openbmc/entity-manager/+/82416
Tested on Yosemite5 with the following steps:
1. Display the fw inventory:
```
curl --silent $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
"@odata.type": "#SoftwareInventoryCollection.SoftwareInventoryCollection",
"Members": [
{...},
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Yosemite5_TPM_4945"
},
{...}
],
"Members@odata.count": 4,
"Name": "Software Inventory Collection"
}
```
2. Query TPM version:
```
curl --silent $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory/Yosemite5_TPM_4945
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Yosemite5_TPM_4945",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "Yosemite5_TPM_4945",
"Name": "Software Inventory",
"Status": {
"Health": "Warning",
"HealthRollup": "OK",
"State": "Disabled"
},
"Updateable": false,
"Version": "15.23"
}
```
Change-Id: I42568242356d55fe005ba1f41ddf8aaf9f682fc8
Signed-off-by: Kevin Tung <kevin.tung.openbmc@gmail.com>
diff --git a/common/include/utils.hpp b/common/include/utils.hpp
index 928b136..65d7022 100644
--- a/common/include/utils.hpp
+++ b/common/include/utils.hpp
@@ -2,11 +2,15 @@
#include <sdbusplus/async.hpp>
+#include <functional>
+#include <optional>
+
/**
* @brief Asynchronously executes a shell command.
* @param ctx Async context for monitoring the pipe.
* @param cmd Shell command to execute.
* @return Task resolving to true on success (exit code 0), false otherwise.
*/
-sdbusplus::async::task<bool> asyncSystem(sdbusplus::async::context& ctx,
- const std::string& cmd);
+sdbusplus::async::task<bool> asyncSystem(
+ sdbusplus::async::context& ctx, const std::string& cmd,
+ std::optional<std::reference_wrapper<std::string>> result = std::nullopt);
diff --git a/common/src/utils.cpp b/common/src/utils.cpp
index 683ca84..2e6d147 100644
--- a/common/src/utils.cpp
+++ b/common/src/utils.cpp
@@ -4,11 +4,14 @@
PHOSPHOR_LOG2_USING;
-sdbusplus::async::task<bool> asyncSystem(sdbusplus::async::context& ctx,
- const std::string& cmd)
+sdbusplus::async::task<bool> asyncSystem(
+ sdbusplus::async::context& ctx, const std::string& cmd,
+ std::optional<std::reference_wrapper<std::string>> result)
{
- int pipefd[2];
- if (pipe(pipefd) == -1)
+ int exitPipefd[2];
+ int resultPipefd[2];
+
+ if (pipe(exitPipefd) == -1 || (result && pipe(resultPipefd) == -1))
{
error("Failed to create pipe for command: {CMD}", "CMD", cmd);
co_return false;
@@ -17,32 +20,59 @@
pid_t pid = fork();
if (pid == 0)
{
- close(pipefd[0]);
+ close(exitPipefd[0]);
+
+ if (result)
+ {
+ close(resultPipefd[0]);
+ dup2(resultPipefd[1], STDOUT_FILENO);
+ dup2(resultPipefd[1], STDERR_FILENO);
+ close(resultPipefd[1]);
+ }
int exitCode = std::system(cmd.c_str());
- ssize_t status = write(pipefd[1], &exitCode, sizeof(exitCode));
+ ssize_t status = write(exitPipefd[1], &exitCode, sizeof(exitCode));
- close(pipefd[1]);
+ close(exitPipefd[1]);
_exit((status == sizeof(exitCode)) ? 0 : 1);
}
else if (pid > 0)
{
- close(pipefd[1]);
+ close(exitPipefd[1]);
- auto fdio = std::make_unique<sdbusplus::async::fdio>(ctx, pipefd[0]);
+ if (result)
+ {
+ close(resultPipefd[1]);
+ }
+
+ auto fdio =
+ std::make_unique<sdbusplus::async::fdio>(ctx, exitPipefd[0]);
if (!fdio)
{
error("Failed to create fdio for command: {CMD}", "CMD", cmd);
- close(pipefd[0]);
+ close(exitPipefd[0]);
co_return false;
}
co_await fdio->next();
+ if (result)
+ {
+ auto& resStr = result->get();
+ resStr.clear();
+ char buffer[1024];
+ ssize_t n;
+ while ((n = read(resultPipefd[0], buffer, sizeof(buffer))) > 0)
+ {
+ resStr.append(buffer, n);
+ }
+ close(resultPipefd[0]);
+ }
+
int exitCode = -1;
- ssize_t bytesRead = read(pipefd[0], &exitCode, sizeof(exitCode));
- close(pipefd[0]);
+ ssize_t bytesRead = read(exitPipefd[0], &exitCode, sizeof(exitCode));
+ close(exitPipefd[0]);
if (bytesRead != sizeof(exitCode))
{
@@ -72,8 +102,13 @@
else
{
error("Fork failed for command: {CMD}", "CMD", cmd);
- close(pipefd[0]);
- close(pipefd[1]);
+ close(exitPipefd[0]);
+ close(exitPipefd[1]);
+ if (result)
+ {
+ close(resultPipefd[0]);
+ close(resultPipefd[1]);
+ }
co_return false;
}
}
diff --git a/meson.build b/meson.build
index e8a2f4f..4c25e15 100644
--- a/meson.build
+++ b/meson.build
@@ -80,6 +80,7 @@
'deps': ['libgpiod'],
},
'i2cvr-software-update': {'dirs' : ['common/i2c', 'i2c-vr']},
+ 'tpm-software-update': {'dirs': ['tpm']},
}
optioned_subdirs = []
diff --git a/meson.options b/meson.options
index 6c09029..32c88c2 100644
--- a/meson.options
+++ b/meson.options
@@ -84,6 +84,13 @@
description: 'Enable EEPROM device update support.',
)
+option(
+ 'tpm-software-update',
+ type: 'feature',
+ value: 'enabled',
+ description: 'Enable TPM software update support.',
+)
+
# Variables
option(
'active-bmc-max-allowed',
diff --git a/tpm/README.md b/tpm/README.md
new file mode 100644
index 0000000..5e055ee
--- /dev/null
+++ b/tpm/README.md
@@ -0,0 +1,30 @@
+# TPM Update Daemon
+
+This daemon handles firmware version retrieval and firmware update processes for
+TPM devices. Currently, it supports reading the firmware version of both
+Infineon and Nuvoton TPM 2.0 chips. Firmware update support will be added in a
+future patch.
+
+## Entity Manager Configuration Example
+
+The snippet below demonstrates how to configure a TPM device in Entity Manager.
+
+```json
+{
+ "FirmwareInfo": {
+ "CompatibleHardware": "com.meta.Hardware.Yosemite5.TPM",
+ "VendorIANA": 40981
+ },
+ "Name": "Yosemite5_TPM",
+ "TPMIndex": 0,
+ "Type": "TPM2Firmware"
+}
+```
+
+## Entity Manager Interface
+
+The D-Bus interface name for TPM configuration will be as follows:
+
+```bash
+xyz.openbmc_project.Configuration.TPM2Firmware
+```
diff --git a/tpm/meson.build b/tpm/meson.build
new file mode 100644
index 0000000..04ab0f4
--- /dev/null
+++ b/tpm/meson.build
@@ -0,0 +1,18 @@
+tpm_device_include = include_directories('.')
+
+executable(
+ 'phosphor-tpm-software-update',
+ 'tpm_software_manager.cpp',
+ 'tpm_device.cpp',
+ 'tpm2/tpm2.cpp',
+ include_directories: [common_include, tpm_device_include],
+ dependencies: [libpldm_dep, phosphor_logging_dep, sdbusplus_dep],
+ link_with: [software_common_lib, libpldmutil],
+ install: true,
+ install_dir: get_option('libexecdir') / 'phosphor-code-mgmt',
+)
+
+install_data(
+ 'xyz.openbmc_project.Software.TPM.service',
+ install_dir: systemd_system_unit_dir,
+)
diff --git a/tpm/tpm2/tpm2.cpp b/tpm/tpm2/tpm2.cpp
new file mode 100644
index 0000000..bd3ca86
--- /dev/null
+++ b/tpm/tpm2/tpm2.cpp
@@ -0,0 +1,142 @@
+#include "tpm2.hpp"
+
+#include "common/include/utils.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <cstdio>
+#include <regex>
+#include <sstream>
+
+PHOSPHOR_LOG2_USING;
+
+static constexpr std::string_view getCapPropertiesCmd =
+ "/usr/bin/tpm2_getcap properties-fixed";
+static constexpr std::string_view fwVer1Property = "TPM2_PT_FIRMWARE_VERSION_1";
+static constexpr std::string_view fwVer2Property = "TPM2_PT_FIRMWARE_VERSION_2";
+static constexpr std::string_view manufacturerProperty = "TPM2_PT_MANUFACTURER";
+
+static constexpr std::string_view hexPattern = R"(^\s*raw:\s+0x([0-9a-fA-F]+))";
+
+enum class Tpm2Vendor
+{
+ IFX,
+ Nuvoton
+};
+
+// Reference: https://trustedcomputinggroup.org/resource/vendor-id-registry/
+static const std::unordered_map<uint32_t, Tpm2Vendor> validManufactureIDs = {
+ {0x49465800, Tpm2Vendor::IFX}, {0x4E544300, Tpm2Vendor::Nuvoton}};
+
+static std::string getTPMResourceManagerPath(uint8_t tpmIndex)
+{
+ return "/dev/tpmrm" + std::to_string(tpmIndex);
+}
+
+sdbusplus::async::task<bool> TPM2Interface::getProperty(
+ std::string_view property, uint32_t& value)
+{
+ // Reference: https://tpm2-tools.readthedocs.io/en/latest/man/common/tcti/
+ // The TCTI or "Transmission Interface" is the communication mechanism
+ // with the TPM. TCTIs can be changed for communication with TPMs across
+ // different mediums.
+ auto tcti = "device:" + getTPMResourceManagerPath(tpmIndex);
+ auto cmd = std::string(getCapPropertiesCmd) + " --tcti " + tcti +
+ " | grep -A1 " + std::string(property);
+
+ std::string output;
+ if (!co_await asyncSystem(ctx, cmd, output))
+ {
+ error("Failed to run command: {CMD}", "CMD", cmd);
+ co_return false;
+ }
+
+ const std::regex regexPattern{std::string(hexPattern)};
+ std::smatch match;
+ std::istringstream stream(output);
+ std::string line;
+
+ while (std::getline(stream, line))
+ {
+ if (std::regex_search(line, match, regexPattern) && match.size() >= 2)
+ {
+ try
+ {
+ value = std::stoul(match[1].str(), nullptr, 16);
+ co_return true;
+ }
+ catch (const std::exception& e)
+ {
+ error("Failed to parse hex value for property {PT}: {ERR}",
+ "PT", property, "ERR", e.what());
+ co_return false;
+ }
+ }
+ }
+
+ error("No matching hex value found for property: {PT}", "PT", property);
+ co_return false;
+}
+
+sdbusplus::async::task<bool> TPM2Interface::updateFirmware(const uint8_t* image,
+ size_t image_size)
+{
+ (void)image;
+ (void)image_size;
+
+ error("TPM2 firmware update is not supported");
+ co_return false;
+}
+
+sdbusplus::async::task<bool> TPM2Interface::getVersion(std::string& version)
+{
+ uint32_t manufacturerId = 0;
+ uint32_t fwVer = 0;
+ std::string tpmVer1;
+ std::string tpmVer2;
+
+ if (!co_await getProperty(manufacturerProperty, manufacturerId))
+ {
+ error("Failed to retrieve TPM manufacturer ID");
+ co_return false;
+ }
+
+ auto it = validManufactureIDs.find(manufacturerId);
+
+ if (it == validManufactureIDs.end())
+ {
+ error("Invalid TPM manufacturer ID: {ID}", "ID", lg2::hex,
+ manufacturerId);
+ co_return false;
+ }
+
+ auto vendor = it->second;
+
+ if (!co_await getProperty(fwVer1Property, fwVer))
+ {
+ error("Failed to retrieve TPM firmware version 1");
+ co_return false;
+ }
+
+ tpmVer1 = std::to_string(fwVer >> 16) + "." +
+ std::to_string(fwVer & 0xFFFF);
+
+ if (vendor == Tpm2Vendor::Nuvoton)
+ {
+ if (!co_await getProperty(fwVer2Property, fwVer))
+ {
+ error("Failed to retrieve TPM firmware version 2");
+ co_return false;
+ }
+
+ tpmVer2 = std::to_string(fwVer >> 16) + "." +
+ std::to_string(fwVer & 0xFFFF);
+ version = tpmVer1 + "." + tpmVer2;
+ }
+ else
+ {
+ version = tpmVer1;
+ }
+
+ co_return true;
+}
diff --git a/tpm/tpm2/tpm2.hpp b/tpm/tpm2/tpm2.hpp
new file mode 100644
index 0000000..cf3c097
--- /dev/null
+++ b/tpm/tpm2/tpm2.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "tpm/tpm_device.hpp"
+
+#include <string_view>
+
+class TPM2Interface : public TPMInterface
+{
+ public:
+ TPM2Interface(sdbusplus::async::context& ctx, uint8_t tpmIndex) :
+ TPMInterface(ctx, tpmIndex)
+ {}
+
+ bool isUpdateSupported() const final
+ {
+ // Currently, we do not support TPM2 firmware updates
+ return false;
+ }
+
+ sdbusplus::async::task<bool> updateFirmware(const uint8_t* image,
+ size_t image_size) final;
+
+ sdbusplus::async::task<bool> getVersion(std::string& version) final;
+
+ private:
+ sdbusplus::async::task<bool> getProperty(std::string_view property,
+ uint32_t& value);
+};
diff --git a/tpm/tpm_device.cpp b/tpm/tpm_device.cpp
new file mode 100644
index 0000000..43f8853
--- /dev/null
+++ b/tpm/tpm_device.cpp
@@ -0,0 +1,66 @@
+#include "tpm_device.hpp"
+
+#include "tpm2/tpm2.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+
+PHOSPHOR_LOG2_USING;
+
+TPMDevice::TPMDevice(sdbusplus::async::context& ctx, enum TPMType tpmType,
+ uint8_t tpmIndex, SoftwareConfig& config,
+ ManagerInf::SoftwareManager* parent) :
+ Device(ctx, config, parent, {RequestedApplyTimes::OnReset})
+{
+ switch (tpmType)
+ {
+ case TPMType::TPM2:
+ tpmInterface = std::make_unique<TPM2Interface>(ctx, tpmIndex);
+ break;
+ default:
+ tpmInterface = nullptr;
+ error("Unsupported TPM type: {TYPE}", "TYPE",
+ static_cast<int>(tpmType));
+ break;
+ }
+}
+
+sdbusplus::async::task<bool> TPMDevice::updateDevice(const uint8_t* image,
+ size_t imageSize)
+{
+ if (tpmInterface == nullptr)
+ {
+ error("TPM interface is not initialized");
+ co_return false;
+ }
+
+ setUpdateProgress(10);
+
+ if (!co_await tpmInterface->updateFirmware(image, imageSize))
+ {
+ error("Failed to update TPM firmware");
+ co_return false;
+ }
+
+ setUpdateProgress(100);
+ debug("Successfully updated TPM");
+ co_return true;
+}
+
+sdbusplus::async::task<std::string> TPMDevice::getVersion()
+{
+ std::string version = "Unknown";
+
+ if (tpmInterface == nullptr)
+ {
+ error("TPM interface is not initialized");
+ co_return version;
+ }
+
+ if (!co_await tpmInterface->getVersion(version))
+ {
+ error("Failed to get TPM version");
+ }
+
+ co_return version;
+}
diff --git a/tpm/tpm_device.hpp b/tpm/tpm_device.hpp
new file mode 100644
index 0000000..a6e220e
--- /dev/null
+++ b/tpm/tpm_device.hpp
@@ -0,0 +1,76 @@
+#pragma once
+
+#include "common/include/device.hpp"
+#include "common/include/software_manager.hpp"
+
+#include <sdbusplus/async.hpp>
+
+#include <unordered_map>
+
+namespace SoftwareInf = phosphor::software;
+namespace ManagerInf = SoftwareInf::manager;
+
+enum class TPMType
+{
+ TPM2,
+};
+
+class TPMInterface
+{
+ public:
+ TPMInterface(sdbusplus::async::context& ctx, uint8_t tpmIndex) :
+ ctx(ctx), tpmIndex(tpmIndex)
+ {}
+ virtual ~TPMInterface() = default;
+ TPMInterface(const TPMInterface&) = delete;
+ TPMInterface& operator=(const TPMInterface&) = delete;
+ TPMInterface(TPMInterface&&) = delete;
+ TPMInterface& operator=(TPMInterface&&) = delete;
+
+ virtual bool isUpdateSupported() const = 0;
+ virtual sdbusplus::async::task<bool> getVersion(std::string& version) = 0;
+ virtual sdbusplus::async::task<bool> updateFirmware(const uint8_t* image,
+ size_t imageSize) = 0;
+
+ protected:
+ sdbusplus::async::context& ctx;
+ uint8_t tpmIndex;
+};
+
+class TPMDevice : public Device
+{
+ public:
+ using Device::softwareCurrent;
+
+ TPMDevice(sdbusplus::async::context& ctx, enum TPMType tpmType,
+ uint8_t tpmIndex, SoftwareConfig& config,
+ ManagerInf::SoftwareManager* parent);
+
+ sdbusplus::async::task<bool> updateDevice(const uint8_t* image,
+ size_t image_size) final;
+
+ sdbusplus::async::task<std::string> getVersion();
+
+ bool isUpdateSupported() const
+ {
+ return tpmInterface ? tpmInterface->isUpdateSupported() : false;
+ }
+
+ private:
+ std::unique_ptr<TPMInterface> tpmInterface;
+};
+
+inline bool stringToTPMType(const std::string& type, TPMType& tpmType)
+{
+ static const std::unordered_map<std::string, TPMType> tpmTypeMap = {
+ {"TPM2Firmware", TPMType::TPM2},
+ };
+
+ auto it = tpmTypeMap.find(type);
+ if (it != tpmTypeMap.end())
+ {
+ tpmType = it->second;
+ return true;
+ }
+ return false;
+}
diff --git a/tpm/tpm_software_manager.cpp b/tpm/tpm_software_manager.cpp
new file mode 100644
index 0000000..227cd84
--- /dev/null
+++ b/tpm/tpm_software_manager.cpp
@@ -0,0 +1,87 @@
+#include "tpm_software_manager.hpp"
+
+#include "common/include/dbus_helper.hpp"
+#include "tpm_device.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+PHOSPHOR_LOG2_USING;
+
+namespace SoftwareInf = phosphor::software;
+
+void TPMSoftwareManager::start()
+{
+ std::vector<std::string> configIntfs = {
+ "xyz.openbmc_project.Configuration.TPM2Firmware",
+ };
+
+ ctx.spawn(initDevices(configIntfs));
+ ctx.run();
+}
+
+sdbusplus::async::task<bool> TPMSoftwareManager::initDevice(
+ const std::string& service, const std::string& path, SoftwareConfig& config)
+{
+ const std::string configIface =
+ "xyz.openbmc_project.Configuration." + config.configType;
+
+ std::optional<uint8_t> tpmIndex = co_await dbusGetRequiredProperty<uint8_t>(
+ ctx, service, path, configIface, "TPMIndex");
+
+ if (!tpmIndex.has_value())
+ {
+ error("Missing property: TPMIndex");
+ co_return false;
+ }
+
+ std::optional<std::string> type =
+ co_await dbusGetRequiredProperty<std::string>(ctx, service, path,
+ configIface, "Type");
+ if (!type.has_value())
+ {
+ error("Missing property: Type");
+ co_return false;
+ }
+
+ TPMType tpmType;
+ if (!stringToTPMType(type.value(), tpmType))
+ {
+ error("Invalid TPM type: {TYPE}", "TYPE", type.value());
+ co_return false;
+ }
+
+ debug("TPM device: TPM Index={INDEX}, Type={TYPE}", "INDEX",
+ tpmIndex.value(), "TYPE", type.value());
+
+ auto tpmDevice = std::make_unique<TPMDevice>(ctx, tpmType, tpmIndex.value(),
+ config, this);
+
+ std::unique_ptr<SoftwareInf::Software> software =
+ std::make_unique<SoftwareInf::Software>(ctx, *tpmDevice);
+
+ software->setVersion(co_await tpmDevice->getVersion());
+
+ if (tpmDevice->isUpdateSupported())
+ {
+ std::set<RequestedApplyTimes> allowedApplyTimes = {
+ RequestedApplyTimes::OnReset};
+
+ software->enableUpdate(allowedApplyTimes);
+ }
+
+ tpmDevice->softwareCurrent = std::move(software);
+
+ devices.insert({config.objectPath, std::move(tpmDevice)});
+
+ co_return true;
+}
+
+int main()
+{
+ sdbusplus::async::context ctx;
+
+ TPMSoftwareManager tpmSoftwareManager(ctx);
+
+ tpmSoftwareManager.start();
+ return 0;
+}
diff --git a/tpm/tpm_software_manager.hpp b/tpm/tpm_software_manager.hpp
new file mode 100644
index 0000000..8154e35
--- /dev/null
+++ b/tpm/tpm_software_manager.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "common/include/software_manager.hpp"
+
+namespace ManagerInf = phosphor::software::manager;
+
+const std::string configTypeTPM = "TPM";
+
+class TPMSoftwareManager : public ManagerInf::SoftwareManager
+{
+ public:
+ TPMSoftwareManager(sdbusplus::async::context& ctx) :
+ SoftwareManager(ctx, configTypeTPM)
+ {}
+
+ void start();
+
+ sdbusplus::async::task<bool> initDevice(const std::string& service,
+ const std::string& path,
+ SoftwareConfig& config) final;
+};
diff --git a/tpm/xyz.openbmc_project.Software.TPM.service b/tpm/xyz.openbmc_project.Software.TPM.service
new file mode 100644
index 0000000..9598b91
--- /dev/null
+++ b/tpm/xyz.openbmc_project.Software.TPM.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=TPM Device Code Update Daemon
+Requires=xyz.openbmc_project.EntityManager.service
+After=xyz.openbmc_project.EntityManager.service
+
+[Service]
+ExecStart=/usr/libexec/phosphor-code-mgmt/phosphor-tpm-software-update
+Restart=always
+Type=dbus
+BusName=xyz.openbmc_project.Software.TPM
+
+[Install]
+WantedBy=multi-user.target