blob: 25f2c9b5fb5aee5bff5c617c91966fea2e3a7879 [file] [log] [blame]
#include "bmc_monitor_app.hpp"
#include "boot_manager.hpp"
#include "dbus_handler.hpp"
#include "utils.hpp"
#include <fmt/printf.h>
#include <boost/asio/steady_timer.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/bus.hpp>
#include <array>
#include <memory>
#include <regex>
#include <string_view>
namespace boot_time_monitor
{
constexpr std::string_view kSystemdService = "org.freedesktop.systemd1";
constexpr std::string_view kSystemdPath = "/org/freedesktop/systemd1";
constexpr std::string_view kSystemdIface = "org.freedesktop.DBus.Properties";
constexpr std::string_view kSystemdFunc = "Get";
constexpr std::string_view kSystemdParam1 = "org.freedesktop.systemd1.Manager";
constexpr std::string_view kSystemdFirmwareTime = "FirmwareTimestampMonotonic";
constexpr std::string_view kSystemdLoaderTime = "LoaderTimestampMonotonic";
constexpr std::string_view kSystemdKernelTime = "KernelTimestampMonotonic";
constexpr std::string_view kSystemdInitRDTime = "InitRDTimestampMonotonic";
constexpr std::string_view kSystemdUserspaceTime =
"UserspaceTimestampMonotonic";
constexpr std::string_view kSystemdFinishTime = "FinishTimestampMonotonic";
struct SystemdTimestamp
{
uint64_t firmware;
uint64_t loader;
uint64_t kernel;
uint64_t initRD;
uint64_t userspace;
uint64_t finish;
};
namespace
{
inline std::string convertName(std::string_view name)
{
return std::regex_replace(name.data(), std::regex("TimestampMonotonic"),
"");
}
} // namespace
BMCMonitorApp::BMCMonitorApp(
sdbusplus::bus::bus& bus,
std::shared_ptr<sdbusplus::asio::connection> conn) :
objManager(bus, kObjPath.data()), util(std::make_shared<Util>()),
cpCSV(std::make_shared<FileUtil>(util->getCPPath(kNodeName, false))),
durCSV(std::make_shared<FileUtil>(util->getDurPath(kNodeName, false))),
bootManager(std::make_shared<BootManager>(util, cpCSV, durCSV)),
dbusHandler(
std::make_shared<DbusHandler>(bus, kObjPath.data(), bootManager, util))
{
constexpr auto kCheckBmcFinishInterval = std::chrono::seconds(10);
static boost::asio::steady_timer BmcFinishTimer(conn->get_io_context());
static std::function<void(const boost::system::error_code&)>
checkBmcFinish = [this, conn, kCheckBmcFinishInterval](
const boost::system::error_code& ec) {
if (ec == boost::asio::error::operation_aborted)
{
fmt::print(stderr, "[checkBmcFinish] BmcFinishTimer is being "
"canceled\n");
// we're being canceled
return;
}
else if (ec)
{
fmt::print(stderr, "[checkBmcFinish] Timer Error: {}\n",
ec.message().c_str());
return;
}
constexpr uint64_t kMillisecondPerSecond = 1000;
// The raw pointer here is safe since it points to an existing instance
// instead of allocating a memory space.
// We can't use `std::share_ptr<uint64_t>(&times.firmware)` because the
// `times` will free its space while `share_ptr` will also free the same
// space again when this constructor completed. It will cause
// `free(): double free detected` or `free(): invalid pointer` error.
SystemdTimestamp times;
std::array<std::pair<std::string_view, uint64_t*>, 6> bootTimestamp = {
std::make_pair<std::string_view, uint64_t*>(
kSystemdFirmwareTime.data(), &times.firmware),
std::make_pair<std::string_view, uint64_t*>(
kSystemdLoaderTime.data(), &times.loader),
std::make_pair<std::string_view, uint64_t*>(
kSystemdKernelTime.data(), &times.kernel),
std::make_pair<std::string_view, uint64_t*>(
kSystemdInitRDTime.data(), &times.initRD),
std::make_pair<std::string_view, uint64_t*>(
kSystemdUserspaceTime.data(), &times.userspace),
std::make_pair<std::string_view, uint64_t*>(
kSystemdFinishTime.data(), &times.finish)};
for (const auto& timestamp : bootTimestamp)
{
auto method = conn->new_method_call(
kSystemdService.data(), kSystemdPath.data(),
kSystemdIface.data(), kSystemdFunc.data());
method.append(kSystemdParam1.data(), timestamp.first.data());
std::variant<uint64_t> result;
try
{
conn->call(method).read(result);
}
catch (const sdbusplus::exception::SdBusError& e)
{
fmt::print(
stderr,
"[checkBmcFinish] Can't get systemd property {}. ERROR={}\n",
timestamp.first.data(), e.what());
// Restart the timer to keep polling the APML_ALERT status
BmcFinishTimer.expires_after(kCheckBmcFinishInterval);
BmcFinishTimer.async_wait(checkBmcFinish);
return;
}
*timestamp.second = std::get<uint64_t>(result);
}
// This daemon may start before the userspace is fully ready. So we need
// to confirm if userspace is fully ready by checking `times.finish`
// equals zero or not.
if (times.finish == 0)
{
fmt::print(
stderr,
"[checkBmcFinish] `FinishTimestampMonotonic` is not ready yet\n");
BmcFinishTimer.expires_after(kCheckBmcFinishInterval);
BmcFinishTimer.async_wait(checkBmcFinish);
return;
}
// How to calculate duration for each stage:
// https://github.com/systemd/systemd/blob/82b7bf8c1c8c6ded6f56b43998c803843a3b944b/src/analyze/analyze-time-data.c#L176-L186
// Special calculation for kernel duration:
// https://github.com/systemd/systemd/blob/82b7bf8c1c8c6ded6f56b43998c803843a3b944b/src/analyze/analyze-time-data.c#L84-L87
bootManager->setDuration(convertName(kSystemdFirmwareTime),
times.firmware != 0
? (times.firmware - times.loader) /
kMillisecondPerSecond
: 0);
bootManager->setDuration(
convertName(kSystemdLoaderTime),
times.loader != 0 ? times.loader / kMillisecondPerSecond : 0);
bootManager->setDuration(
convertName(kSystemdKernelTime),
(times.initRD > 0 ? times.initRD : times.userspace) /
kMillisecondPerSecond);
bootManager->setDuration(convertName(kSystemdInitRDTime),
times.initRD != 0
? (times.userspace - times.initRD) /
kMillisecondPerSecond
: 0);
bootManager->setDuration(convertName(kSystemdUserspaceTime),
times.userspace != 0
? (times.finish - times.userspace) /
kMillisecondPerSecond
: 0);
#ifdef NPCM7XX_OR_NEWER
// NPCM7XX or newer Nuvoton BMC has a register that starts counting from
// SoC power on. Also uptime starts counting when kernel is up. Thus we
// can get (Firmware + Loader) time by `value[SEC_CNT_ADDR] - uptime`.
constexpr uint32_t SEC_CNT_ADDR = 0xF0801068;
auto powerOnSec = util->readMem4Bytes(SEC_CNT_ADDR);
auto upTimeMS = util->getUpTimeInMs();
if (powerOnSec != std::nullopt && upTimeMS != std::nullopt)
{
bootManager->setDuration("FirmwarePlusLoader",
powerOnSec.value() * 1000 -
upTimeMS.value());
}
#endif
// BMC marks the boot process as `completed` automatically if we do *NOT* have
// external daemon to do so.
#ifdef AUTO_BMC_COMPLETE
bootManager->setCheckpoint("UserspaceEnd", 0, 0);
bootManager->notifyComplete();
#endif
};
BmcFinishTimer.expires_after(std::chrono::seconds(0));
BmcFinishTimer.async_wait(checkBmcFinish);
}
} // namespace boot_time_monitor