| // 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; |
| } |