| // 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 "hoth_dbus.hpp" |
| |
| #include "google3/ec_commands.h" |
| #include "google3/host_commands.h" |
| |
| #include "message_util.hpp" |
| #include "payload_update.hpp" |
| #include "payload_update_common.hpp" |
| |
| #include <boost/endian/conversion.hpp> |
| #include <sdbusplus/bus.hpp> |
| #include <sdbusplus/message.hpp> |
| #include <stdplus/raw.hpp> |
| #include <xyz/openbmc_project/Control/Hoth/server.hpp> |
| |
| #include <chrono> |
| #include <cstdlib> |
| #include <cstring> |
| #include <exception> |
| #include <functional> |
| #include <iostream> |
| #include <span> |
| #include <string> |
| #include <thread> |
| #include <utility> |
| #include <vector> |
| |
| namespace google::hoth::tools |
| { |
| using FirmwareUpdateStatus = sdbusplus::xyz::openbmc_project::Control::server:: |
| Hoth::FirmwareUpdateStatus; |
| constexpr uint8_t kStructVersion = |
| google::hoth::internal::SUPPORTED_STRUCT_VERSION; |
| constexpr uint32_t kSectorSizeBytes = 4096; |
| constexpr uint32_t kEraseChunkSizeBytes = kSectorSizeBytes * 32; |
| using namespace std::chrono_literals; |
| constexpr sdbusplus::SdBusDuration kCommandTimeout = 60s; |
| constexpr char kHothInterface[] = "xyz.openbmc_project.Control.Hoth"; |
| constexpr char kHothObject[] = "/xyz/openbmc_project/Control/Hoth"; |
| |
| template <typename... Vargs> |
| sdbusplus::message::message HothDBus::callMethod(const char* method, |
| const std::string& hothId, |
| Vargs&&... vargs) |
| { |
| std::string service{kHothInterface}; |
| std::string object{kHothObject}; |
| if (!hothId.empty()) |
| { |
| service += "."; |
| service += hothId; |
| object += "/"; |
| object += hothId; |
| } |
| sdbusplus::message::message msg = dbus.new_method_call( |
| service.c_str(), object.c_str(), kHothInterface, method); |
| msg.append(std::forward<Vargs>(vargs)...); |
| return dbus.call(msg, kCommandTimeout); |
| } |
| |
| template <typename T, typename... Vargs> |
| void HothDBus::callMethodAndRead(const char* method, const std::string& hothId, |
| T& value, Vargs&&... vargs) |
| { |
| sdbusplus::message::message reply = |
| callMethod(method, hothId, std::forward<Vargs>(vargs)...); |
| reply.read(value); |
| } |
| |
| FirmwareUpdateStatus HothDBus::getVerifyPayloadStatus(const std::string& hothId) |
| { |
| std::string response; |
| callMethodAndRead("GetVerifyPayloadStatus", hothId, response); |
| return sdbusplus::xyz::openbmc_project::Control::server::Hoth:: |
| convertFirmwareUpdateStatusFromString(response); |
| } |
| |
| FirmwareUpdateStatus HothDBus::getSendPayloadStatus(const std::string& hothId) |
| { |
| std::string response; |
| callMethodAndRead("GetSendPayloadStatus", hothId, response); |
| return sdbusplus::xyz::openbmc_project::Control::server::Hoth:: |
| convertFirmwareUpdateStatusFromString(response); |
| } |
| |
| bool HothDBus::verifyPayload(const std::string& hothId) |
| { |
| callMethod("VerifyPayload", hothId); |
| auto timeout = std::chrono::steady_clock::now() + 10s; |
| |
| FirmwareUpdateStatus status = getVerifyPayloadStatus(hothId); |
| while (status == FirmwareUpdateStatus::InProgress) |
| { |
| if (std::chrono::steady_clock::now() > timeout) |
| { |
| std::cerr << "Verify payload timed out." << '\n'; |
| return false; |
| } |
| std::this_thread::sleep_for(100ms); |
| status = getVerifyPayloadStatus(hothId); |
| } |
| if (status != FirmwareUpdateStatus::Done) |
| { |
| std::cerr << "Verify payload failed." << '\n'; |
| return false; |
| } |
| return true; |
| } |
| |
| std::optional<std::string> |
| HothDBus::getPayloadVersion(const std::string& hothId, |
| payload_update::VersionType type) |
| { |
| google::hoth::internal::ReqHeader req; |
| memset(&req, 0, sizeof(req)); |
| req.struct_version = kStructVersion; |
| req.command = EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_PAYLOAD_STATUS; |
| using google::hoth::internal::calculateChecksum; |
| auto spanReq = stdplus::raw::asSpan<uint8_t>(req); |
| uint8_t checksumNegated = calculateChecksum(spanReq); |
| req.checksum = ~checksumNegated + 1; |
| std::vector<uint8_t> bytes; |
| callMethodAndRead("SendHostCommand", hothId, bytes, spanReq); |
| if (bytes.size() < sizeof(PayloadVersionResponse)) |
| { |
| std::cerr << "Invalid response." << '\n'; |
| return {}; |
| } |
| auto response = stdplus::raw::copyFrom<PayloadVersionResponse>(bytes); |
| if (response.header.struct_version != kStructVersion) |
| { |
| std::cerr << "Response structure version not supported: " |
| << response.header.struct_version << '\n'; |
| return {}; |
| } |
| if (response.header.result != 0) |
| { |
| std::cerr << "Failed to get payload version" << '\n'; |
| return {}; |
| } |
| const uint8_t activeHalf = response.status.header.active_half; |
| const uint8_t regionCount = response.status.header.region_count; |
| if (regionCount == 0 || regionCount > PAYLOAD_STATUS_MAX_REGION_STATES) |
| { |
| std::cerr << "Payload status region count invalid." << '\n'; |
| return {}; |
| } |
| std::string versionStrings[PAYLOAD_STATUS_MAX_REGION_STATES]; |
| for (uint8_t i = 0; i < regionCount; i++) |
| { |
| const payload_region_state& region = response.status.states[i]; |
| if (region.validation_state == PAYLOAD_IMAGE_VALID || |
| region.validation_state == PAYLOAD_DESCRIPTOR_VALID) |
| { |
| versionStrings[i] = std::to_string(region.version_major) + "." + |
| std::to_string(region.version_minor) + "." + |
| std::to_string(region.version_point) + "." + |
| std::to_string(region.version_subpoint); |
| } |
| else |
| { |
| versionStrings[i] = ""; |
| } |
| } |
| std::cerr << "Active half: " << static_cast<unsigned int>(activeHalf) |
| << '\n'; |
| std::cerr << "Version[0]: " << versionStrings[0] << '\n'; |
| std::cerr << "Version[1]: " << versionStrings[1] << '\n'; |
| if (type == payload_update::VersionType::kActiveHalf) |
| { |
| return versionStrings[activeHalf]; |
| } |
| return versionStrings[1 - activeHalf]; |
| } |
| |
| bool HothDBus::sendPayloadAndVerify(const std::string& hothId, |
| const std::string& payloadFile) |
| { |
| std::cerr << hothId << ": Sending payload" << '\n'; |
| callMethod("SendPayload", hothId, payloadFile); |
| auto timeout = std::chrono::steady_clock::now() + 10min; |
| FirmwareUpdateStatus status = getSendPayloadStatus(hothId); |
| while (status == FirmwareUpdateStatus::InProgress) |
| { |
| if (std::chrono::steady_clock::now() > timeout) { |
| std::cerr << "Send payload timed out." << '\n'; |
| return false; |
| } |
| std::this_thread::sleep_for(1s); |
| status = getSendPayloadStatus(hothId); |
| } |
| if (status != FirmwareUpdateStatus::Done) |
| { |
| std::cerr << "Send payload failed." << '\n'; |
| return false; |
| } |
| return verifyPayload(hothId); |
| } |
| |
| bool HothDBus::sendStaticWPPayloadAndVerify(const std::string& hothId, |
| const std::string& payloadFile) |
| { |
| std::cerr << hothId << ": Erasing & Sending Static & WP" << '\n'; |
| callMethod("EraseAndSendStaticWPPayload", hothId, payloadFile); |
| auto timeout = std::chrono::steady_clock::now() + 10min; |
| FirmwareUpdateStatus status = getSendPayloadStatus(hothId); |
| while (status == FirmwareUpdateStatus::InProgress) |
| { |
| if (std::chrono::steady_clock::now() > timeout) { |
| std::cerr << "Send payload timed out." << '\n'; |
| return false; |
| } |
| std::this_thread::sleep_for(1s); |
| status = getSendPayloadStatus(hothId); |
| } |
| if (status != FirmwareUpdateStatus::Done) |
| { |
| std::cerr << "Send payload failed." << '\n'; |
| return false; |
| } |
| return verifyPayload(hothId); |
| } |
| |
| bool HothDBus::activatePayload(const std::string& hothId) |
| { |
| sdbusplus::message::message msg = |
| callMethod("ActivatePayload", hothId, true); |
| if (msg.is_method_error()) |
| { |
| const sd_bus_error* err = msg.get_error(); |
| const std::string name(err->name); |
| const std::string message(err->message); |
| std::cerr << "Activate payload error. " << name << ": " << message |
| << '\n'; |
| return false; |
| } |
| return true; |
| } |
| |
| bool HothDBus::deactivatePayload(const std::string& hothId) |
| { |
| sdbusplus::message::message msg = |
| callMethod("DeactivatePayload", hothId, true); |
| if (msg.is_method_error()) |
| { |
| const sd_bus_error* err = msg.get_error(); |
| const std::string name(err->name); |
| const std::string message(err->message); |
| std::cerr << "Activate payload error. " << name << ": " << message |
| << '\n'; |
| return false; |
| } |
| return true; |
| } |
| |
| bool HothDBus::confirmPayload(const std::string& hothId) |
| { |
| // Need to make sure the ipmi system tool is working before confirming |
| // NOLINTNEXTLINE(cert-env33-c) |
| int ret = system("ipmitool mc info"); |
| if (ret) |
| { |
| std::cerr << "ipmitool not working. Error code: " << ret << ". Aborted." |
| << '\n'; |
| return false; |
| } |
| sdbusplus::message::message msg = callMethod("Confirm", hothId); |
| if (msg.is_method_error()) |
| { |
| const sd_bus_error* err = msg.get_error(); |
| const std::string name(err->name); |
| const std::string message(err->message); |
| std::cerr << "Confirm payload error. " << name << ": " << message |
| << '\n'; |
| return false; |
| } |
| return true; |
| } |
| |
| bool HothDBus::eraseStagingArea(const std::string& hothId) |
| { |
| uint32_t toEraseSize; |
| callMethodAndRead("GetPayloadSize", hothId, toEraseSize); |
| std::cerr << hothId << ": Erasing staging area" << '\n'; |
| if (toEraseSize == 0 || (toEraseSize % kSectorSizeBytes) != 0) |
| { |
| std::cerr << "Invalid staging area size" << '\n'; |
| return false; |
| } |
| uint32_t offset = 0; |
| while (toEraseSize >= kEraseChunkSizeBytes) |
| { |
| callMethod("ErasePayload", hothId, offset, kEraseChunkSizeBytes); |
| offset += kEraseChunkSizeBytes; |
| toEraseSize -= kEraseChunkSizeBytes; |
| } |
| if (toEraseSize > 0) |
| { |
| callMethod("ErasePayload", hothId, offset, toEraseSize); |
| } |
| return true; |
| } |
| |
| bool HothDBus::setTargetResetOption(const std::string& hothId, |
| ec_target_reset_option option) |
| { |
| TargetResetRequest req; |
| memset(&req, 0, sizeof(req)); |
| req.header.struct_version = kStructVersion; |
| req.header.command = |
| EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_RESET_TARGET; |
| req.header.data_len = sizeof(req.request); |
| req.request.reset_option = option; |
| using google::hoth::internal::calculateChecksum; |
| auto spanReq = stdplus::raw::asSpan<uint8_t>(req); |
| uint8_t checksumNegated = calculateChecksum(spanReq); |
| req.header.checksum = ~checksumNegated + 1; |
| if (option == EC_TARGET_RESET_OPTION_SET) |
| { |
| std::cerr << hothId << ": Put target in reset" << '\n'; |
| } |
| else |
| { |
| std::cerr << hothId << ": Release target from reset" << '\n'; |
| } |
| std::vector<uint8_t> bytes; |
| callMethodAndRead("SendHostCommand", hothId, bytes, spanReq); |
| using google::hoth::internal::RspHeader; |
| auto response = stdplus::raw::copyFrom<RspHeader>(bytes); |
| if (response.struct_version != kStructVersion) |
| { |
| std::cerr << "Response structure version not supported: " |
| << response.struct_version << '\n'; |
| return false; |
| } |
| if (response.result != 0) |
| { |
| std::cerr << "Failed to issue reset command" << '\n'; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool HothDBus::setSpsPassthrough(const std::string& hothId, const bool enable) |
| { |
| using google::hoth::internal::ReqHeader; |
| using google::hoth::internal::RspHeader; |
| |
| ReqHeader req; |
| memset(&req, 0, sizeof(req)); |
| req.struct_version = kStructVersion; |
| if (enable) |
| { |
| req.command = EC_CMD_BOARD_SPECIFIC_BASE | |
| EC_PRV_CMD_HOTH_SPS_PASSTHROUGH_ENABLE; |
| std::cerr << hothId << ": Enable SPS passthrough" << '\n'; |
| } |
| else |
| { |
| req.command = EC_CMD_BOARD_SPECIFIC_BASE | |
| EC_PRV_CMD_HOTH_SPS_PASSTHROUGH_DISABLE; |
| std::cerr << hothId << ": Disable SPS passthrough" << '\n'; |
| } |
| using google::hoth::internal::calculateChecksum; |
| auto spanReq = stdplus::raw::asSpan<uint8_t>(req); |
| uint8_t checksumNegated = calculateChecksum(spanReq); |
| req.checksum = ~checksumNegated + 1; |
| std::vector<uint8_t> bytes; |
| callMethodAndRead("SendHostCommand", hothId, bytes, spanReq); |
| auto response = stdplus::raw::copyFrom<RspHeader>(bytes); |
| if (response.struct_version != kStructVersion) |
| { |
| std::cerr << "Response structure version not supported: " |
| << response.struct_version << '\n'; |
| return false; |
| } |
| if (response.result != 0) |
| { |
| std::cerr << "Failed to issue SPS command" << '\n'; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::optional<bool> HothDBus::getSpsPassthrough(const std::string& hothId) |
| { |
| using google::hoth::internal::ReqHeader; |
| using google::hoth::internal::RspHeader; |
| |
| ReqHeader req; |
| memset(&req, 0, sizeof(req)); |
| req.struct_version = kStructVersion; |
| req.command = EC_CMD_BOARD_SPECIFIC_BASE | |
| EC_PRV_CMD_HOTH_GET_SPS_PASSTHROUGH_STATUS; |
| using google::hoth::internal::calculateChecksum; |
| auto spanReq = stdplus::raw::asSpan<uint8_t>(req); |
| uint8_t checksumNegated = calculateChecksum(spanReq); |
| req.checksum = ~checksumNegated + 1; |
| std::vector<uint8_t> bytes; |
| callMethodAndRead("SendHostCommand", hothId, bytes, spanReq); |
| |
| auto header = stdplus::raw::copyFrom<RspHeader>(bytes); |
| if (header.struct_version != kStructVersion) |
| { |
| std::cerr << "Response structure version not supported: " |
| << header.struct_version << '\n'; |
| return std::nullopt; |
| } |
| if (header.result != 0) |
| { |
| std::cerr << "Failed to issue SPS get passthrough status command" << '\n'; |
| return std::nullopt; |
| } |
| |
| auto response = stdplus::raw::copyFrom<SpsPassthroughStatusResponse>(bytes); |
| return response.status.sps_passthrough_enabled; |
| } |
| |
| bool HothDBus::expectConfirmPayload() |
| { |
| PayloadConfirmRequest req; |
| memset(&req, 0, sizeof(req)); |
| req.header.struct_version = kStructVersion; |
| req.header.command = |
| EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_PAYLOAD_UPDATE; |
| req.header.data_len = sizeof(req.request); |
| req.request.header.offset = 0; |
| req.request.header.len = sizeof(req.request.confirm); |
| req.request.header.type = PAYLOAD_UPDATE_CONFIRM; |
| req.request.confirm.option = payload_update_confirm_option::Enable; |
| req.request.confirm.timeout_value = 0; |
| auto now = std::chrono::steady_clock::now(); |
| auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now); |
| auto cookie = now_ms.time_since_epoch(); |
| req.request.confirm.confirmation_cookie = |
| boost::endian::native_to_little(cookie.count()); |
| auto spanReq = stdplus::raw::asSpan<uint8_t>(req); |
| using google::hoth::internal::calculateChecksum; |
| uint8_t checksumNegated = calculateChecksum(spanReq); |
| req.header.checksum = ~checksumNegated + 1; |
| std::cerr << ": Expect confirm payload" << '\n'; |
| std::vector<uint8_t> bytes; |
| callMethodAndRead("SendHostCommand", "", bytes, spanReq); |
| |
| std::span<uint8_t> response = bytes; |
| using google::hoth::internal::RspHeader; |
| auto header = stdplus::raw::extract<RspHeader>(response); |
| if (header.struct_version != kStructVersion) |
| { |
| std::cerr << "Response structure version not supported: " |
| << header.struct_version << '\n'; |
| return false; |
| } |
| if (header.result == google::hoth::internal::EC_RES_INVALID_PARAM || |
| header.result == google::hoth::internal::EC_RES_ACCESS_DENIED) |
| { |
| std::cerr << "Ignoring expected payload confirm issue:" << header.result |
| << '\n'; |
| return true; |
| } |
| if (header.result != google::hoth::internal::EC_RES_SUCCESS) |
| { |
| std::cerr << "Failed to enable payload confirm: " << header.result |
| << header.result << '\n'; |
| return false; |
| } |
| auto confirmResponse = |
| stdplus::raw::copyFrom<payload_update_confirm_response>(response); |
| payload_update_confirm_timeout_values& val = confirmResponse.timeout_values; |
| using boost::endian::little_to_native; |
| std::cerr << " min:" << little_to_native(val.min) << '\n'; |
| std::cerr << " max:" << little_to_native(val.max) << '\n'; |
| std::cerr << " default:" << little_to_native(val.default_val) << '\n'; |
| std::cerr << " current:" << little_to_native(val.current) << '\n'; |
| |
| return true; |
| } |
| |
| } // namespace google::hoth::tools |