blob: 0f9532354b84c02bfa4adc68a9c64cbe8fc96156 [file] [log] [blame]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "activation.hpp"
#include "asset.hpp"
#include "ec_util.hpp"
#include "firmware_mtd_updater.hpp"
#include "firmware_spi_updater.hpp"
#include "host_command.hpp"
#include "hoth.hpp"
#include "hoth_state.hpp"
#include "log_collector_util.hpp"
#include "message_file.hpp"
#include "message_intf.hpp"
#include "message_reinit.hpp"
#include "mtd_util.hpp"
#include "payload_update.hpp"
#include "sys.hpp"
#include "sys_interface.hpp"
#include "version.hpp"
#include <bits/getopt_core.h>
#include <stdlib.h>
#include <string.h>
#include <systemd/sd-daemon.h>
#include <unistd.h>
#include <boost/asio.hpp>
#include <boost/asio/io_context.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <sdbusplus/bus.hpp>
#include <stdplus/print.hpp>
#include <exception>
#include <format>
#include <memory>
#include <string>
#include <string_view>
#ifdef HAVE_USB
#include "libhoth_usb.hpp"
#include "libusb_impl.hpp"
#include "message_hoth_usb.hpp"
#endif
#include <xyz/openbmc_project/Control/Hoth/error.hpp>
const char* HOTH_CONTROL_PATH = "/xyz/openbmc_project/Control/Hoth";
const char* HOTH_CONTROL_BUS = "xyz.openbmc_project.Control.Hoth";
const char* VERSION_BASE_PATH = "/xyz/openbmc_project/software";
const char* ASSET_BASE_PATH = "/xyz/openbmc_project/inventory";
constexpr static const char* kEmService = "xyz.openbmc_project.EntityManager";
constexpr static const char* kConfigPathPrefix =
"xyz.openbmc_project.Configuration.Hoth";
// NOLINTNEXTLINE(google-build-using-namespace)
using namespace std::string_literals;
using sdbusplus::error::xyz::openbmc_project::control::hoth::InterfaceError;
using Value = std::variant<std::monostate>;
using ObjectType =
std::unordered_map<std::string, std::unordered_map<std::string, Value>>;
using ManagedObjectType =
std::vector<std::pair<sdbusplus::message::object_path, ObjectType>>;
static void usage(const char* name)
{
stdplus::print(stderr, "\
Usage: {} [-m <MAILBOX>]\n\
Bridge commands between a D-Bus interface and Hoth's mailbox\n\n\
-b Blocks the use of legacy payload verify commands.\n\
-u <UART_CHANNEL_ID> Collects and streams the device UART logs.\n\n\
-m <MAILBOX> Use the file at <MAILBOX> as the mailbox.\n\
If omitted, it will be located automatically.\n\n\
-n <NAME> Use the name <NAME> for the hoth on DBus.\n\
If omitted, it will use the legacy empty name.\n\n\
-a <ADDRESS_MODE> SPI address mode (address can be 3 or 4 bytes) .\n\
If omitted, it will use 4 bytes by default.\n\n\
-r<TYPE> Resets target before SPI flash. Possible types:\n\
needed: Default if no <TYPE> provided. Put the target into reset\n\
if SPS passthrough enabled or status unknown.\n\
never: Same as no -r. Never put the target in reset.\n\
ignore_fail: Put target in reset if SPS passthrough enabled, but ignore\n\
SPS status command failures.\n\
needed_active: Same as \"needed\" but it tries disabling SPS passthrough\n\
when it is enabled.\n",
name);
}
static std::string findBoardObjPath(const std::string& assetObj,
const std::string& objPath,
const ObjectType& ifcAndProperties)
{
for (const auto& [ifc, properties] : ifcAndProperties)
{
if (ifc.starts_with(kConfigPathPrefix))
{
/* Match the hoth name from the objPath to the hoth name
* obtained from Configuration.Hoth interface */
if (objPath.substr(objPath.find_last_of('/') + 1) ==
assetObj.substr(assetObj.find_last_of('/') + 1))
{
return objPath.substr(0, objPath.find_last_of('/'));
}
}
}
return std::string();
}
uint32_t getUartChannelId(const std::string &uartChannelStr) {
uint32_t uartChannelId = 0;
for (char c : uartChannelStr) {
uartChannelId <<= 8; // or 0x100, shifting 2 hex digits
uartChannelId += static_cast<uint32_t>(c);
}
return uartChannelId;
}
using google::hoth::internal::ResetMode;
ResetMode parseReset(char* str)
{
if (!str || "needed"s == str)
{
return ResetMode::needed;
}
if ("never"s == str)
{
return ResetMode::never;
}
if ("ignore_fail"s == str)
{
return ResetMode::ignore;
}
if ("needed_active"s == str)
{
return ResetMode::needed_active;
}
stdplus::print(stderr, "Unknown reset type {}\n", str);
exit(EXIT_FAILURE);
}
int main(int argc, char* argv[])
{
bool allow_legacy_verify = true;
const char *mailbox = nullptr, *name = "", *uartChannel = "";
uint8_t addressSize = 4;
int opt;
ResetMode targetReset = ResetMode::never;
bool ignoreAddressMode = false;
while ((opt = getopt(argc, argv, "bu:m:n:a:r::i")) != -1)
{
switch (opt)
{
case 'b':
allow_legacy_verify = false;
break;
case 'u':
uartChannel = optarg;
break;
case 'm':
mailbox = optarg;
break;
case 'n':
name = optarg;
break;
case 'a':
addressSize = std::stoi(optarg);
break;
case 'r':
targetReset = parseReset(optarg);
break;
case 'i':
ignoreAddressMode = true;
break;
default:
usage(argv[0]);
exit(EXIT_FAILURE);
}
}
std::unique_ptr<google::hoth::MessageIntf> msg;
std::unique_ptr<google::hoth::internal::HostCommandImpl> hostCmd;
std::unique_ptr<google::hoth::internal::PayloadUpdateImpl> payloadUpdate;
std::unique_ptr<google::hoth::internal::EcUtilImpl> ecUtil;
std::unique_ptr<google::hoth::Hoth> hoth;
std::unique_ptr<google::hoth::HothState> hoth_state;
std::unique_ptr<google::hoth::RoVersion> swVerRo;
std::unique_ptr<google::hoth::RwVersion> swVerRw;
std::unique_ptr<google::hoth::SwActivation> swActivationRo;
std::unique_ptr<google::hoth::SwActivation> swActivationRw;
std::unique_ptr<google::hoth::Asset> asset;
std::unique_ptr<google::hoth::internal::FirmwareUpdater> firmwareUpdater;
google::hoth::internal::RateLimiter rateLimiter(kRateLimiterMilliSeconds);
std::unique_ptr<google::hoth::internal::LogCollectorUtil> logCollectorUtil =
std::make_unique<google::hoth::internal::LogCollectorUtil>(
rateLimiter, kAsyncWaitTimeInSeconds);
google::hoth::internal::Mtd* mtd = &google::hoth::internal::mtdImpl;
google::hoth::internal::Sys* sys = &google::hoth::internal::sys_impl;
boost::asio::io_context io;
std::shared_ptr<sdbusplus::asio::connection> systemBus =
std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objectServer(systemBus, true);
objectServer.add_manager("/");
size_t max_retries = 2;
try
{
constexpr std::string_view usbPrefix = "usb:";
if (!mailbox)
{
msg = std::make_unique<google::hoth::MessageFile>(
google::hoth::internal::mtdImpl.findPartition("hoth-mailbox"));
max_retries = 1;
}
else if (strncmp(mailbox, usbPrefix.data(), usbPrefix.size()) == 0)
{
#ifdef HAVE_USB
msg = std::make_unique<google::hoth::MessageReinit<
google::hoth::MessageHothUSB, std::string_view,
google::hoth::LibusbIntf*, google::hoth::LibHothUsbIntf*>>(
mailbox + usbPrefix.size(), &google::hoth::libusb_impl,
&google::hoth::libhoth_usb);
mtd = nullptr;
#else
throw std::logic_error("Missing USB support");
#endif
}
else
{
msg = std::make_unique<google::hoth::MessageFile>(mailbox);
}
hostCmd = std::make_unique<google::hoth::internal::HostCommandImpl>(
msg.get(), &io, logCollectorUtil.get(), name, max_retries,
allow_legacy_verify, getUartChannelId(uartChannel));
payloadUpdate =
std::make_unique<google::hoth::internal::PayloadUpdateImpl>(
hostCmd.get());
ecUtil =
std::make_unique<google::hoth::internal::EcUtilImpl>(hostCmd.get());
if (mtd)
{
firmwareUpdater =
std::make_unique<google::hoth::internal::FirmwareMtdUpdater>(
mtd, sys);
}
else
{
firmwareUpdater =
std::make_unique<google::hoth::internal::FirmwareSpiUpdater>(
hostCmd.get(), addressSize, targetReset, ignoreAddressMode);
}
// To facilitate debugging Hoth errors, emit a snapshot of its console.
hostCmd->collectHothLogsAsync(true);
}
catch (const std::exception& e)
{
stdplus::print(stderr, "Error setting up Hoth interface: {}\n",
e.what());
exit(EXIT_FAILURE);
}
try
{
std::string obj = HOTH_CONTROL_PATH, svc = HOTH_CONTROL_BUS;
if (name[0] != '\0')
{
obj += "/";
obj += name;
svc += ".";
svc += name;
}
hoth = std::make_unique<google::hoth::Hoth>(
*systemBus, obj.c_str(), hostCmd.get(), payloadUpdate.get(),
ecUtil.get(), firmwareUpdater.get());
hoth_state = std::make_unique<google::hoth::HothState>(
*systemBus, io, obj.c_str(), hostCmd.get(), ecUtil.get());
systemBus->request_name(svc.c_str());
}
catch (const std::exception& e)
{
stdplus::print(stderr,
"Error registering Hoth control object with D-Bus: {}\n",
e.what());
exit(EXIT_FAILURE);
}
try
{
std::string ro_obj, rw_obj;
std::string ver_obj =
std::format("{}/hoth{}", VERSION_BASE_PATH,
name[0] != '\0' ? std::string("_") + name : "");
ro_obj = ver_obj + "_ro";
rw_obj = ver_obj + "_rw";
swVerRo = std::make_unique<google::hoth::RoVersion>(
*systemBus, ro_obj.c_str(), hostCmd.get());
swVerRw = std::make_unique<google::hoth::RwVersion>(
*systemBus, rw_obj.c_str(), hostCmd.get());
swActivationRo = std::make_unique<google::hoth::SwActivation>(
*systemBus, ro_obj.c_str());
swActivationRw = std::make_unique<google::hoth::SwActivation>(
*systemBus, rw_obj.c_str());
}
catch (const std::exception& e)
{
stdplus::print(stderr,
"Error registering Software Version or Activation objects "
"with D-Bus: {}\n",
e.what());
exit(EXIT_FAILURE);
}
sd_notify(0, "READY=1");
std::string assetObj =
std::format("{}/hoth{}", ASSET_BASE_PATH,
name[0] != '\0' ? std::string("_") + name : "");
std::string board_obj;
ManagedObjectType managedObjs;
try
{
auto method = systemBus->new_method_call(
kEmService, "/xyz/openbmc_project/inventory",
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
systemBus->call(method).read(managedObjs);
}
catch (const sdbusplus::exception::SdBusError& e)
{
stdplus::print(
stderr, "Failed to get managed objects from entity manager: {}\n",
e.what());
}
/* Find the board object path */
for (const auto& [path, ifcToProperties] : managedObjs)
{
board_obj = findBoardObjPath(assetObj, path.str, ifcToProperties);
if (!board_obj.empty())
{
try
{
asset = std::make_unique<google::hoth::Asset>(
*systemBus, assetObj, board_obj, hostCmd.get());
}
catch (const std::exception& e)
{
stdplus::print(
stderr, "Error registering Asset Object with D-Bus: {}\n",
e.what());
exit(EXIT_FAILURE);
}
break;
}
}
auto ifcAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
*systemBus,
sdbusplus::bus::match::rules::interfacesAdded() +
sdbusplus::bus::match::rules::sender(kEmService),
[&systemBus, &hostCmd, &asset, &assetObj](sdbusplus::message_t& msg) {
sdbusplus::message::object_path objPath;
ObjectType ifcAndProperties;
try
{
msg.read(objPath, ifcAndProperties);
}
catch (const std::exception& e)
{
stdplus::print(stderr,
"Error reading objects from EM service: {}\n",
e.what());
return;
}
std::string boardObj =
findBoardObjPath(assetObj, objPath.str, ifcAndProperties);
if (!boardObj.empty())
{
try
{
if (asset)
{
asset.reset();
}
asset = std::make_unique<google::hoth::Asset>(
*systemBus, assetObj, boardObj, hostCmd.get());
}
catch (const std::exception& e)
{
stdplus::print(
stderr,
"Error registering Asset Object with D-Bus: {}\n",
e.what());
exit(EXIT_FAILURE);
}
}
});
try
{
io.run();
}
catch (const InterfaceError& e)
{
stdplus::print(stderr,
"Lost connection with Hoth. Shutting down. e={}\n",
e.what());
return EXIT_FAILURE;
}
return 0;
}