blob: b9f4f8f5697888b9e3d9a814b2f6ace67bcfc7e4 [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 "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