blob: 77fe4c4c21439ffd9de22f3b2c8bd6b86e51306d [file] [log] [blame]
#include "systemd_handler.hpp"
#include "log.hpp"
#include <fmt/printf.h>
#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <regex>
namespace boot_time_monitor
{
namespace systemd
{
namespace btm = boot_time_monitor;
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";
constexpr int64_t kMillisecondPerSecond = 1000;
struct SystemdTimestamp
{
uint64_t firmware;
uint64_t loader;
uint64_t kernel;
uint64_t initRD;
uint64_t userspace;
uint64_t finish;
};
struct SystemdDuration
{
int64_t firmware;
int64_t loader;
int64_t kernel;
int64_t initRD;
int64_t userspace;
};
namespace
{
// 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
inline SystemdDuration CalculateSystemdDuration(SystemdTimestamp times)
{
int64_t firmware = static_cast<int64_t>(times.firmware);
int64_t loader = static_cast<int64_t>(times.loader);
int64_t initRD = static_cast<int64_t>(times.initRD);
int64_t userspace = static_cast<int64_t>(times.userspace);
int64_t finish = static_cast<int64_t>(times.finish);
SystemdDuration durations;
durations.kernel = userspace;
if (firmware != 0)
{
durations.firmware = firmware - loader;
}
if (loader != 0)
{
durations.loader = loader;
}
if (initRD > 0)
{
durations.kernel = initRD;
}
if (initRD != 0)
{
durations.initRD = userspace - initRD;
}
if (userspace != 0)
{
durations.userspace = finish - userspace;
}
return durations;
}
inline std::string convertName(std::string_view name)
{
return std::regex_replace(std::string(name),
std::regex("TimestampMonotonic"), "");
}
} // namespace
Handler::Handler(std::shared_ptr<sdbusplus::asio::connection> conn,
std::shared_ptr<btm::api::IBoottimeApi> api) :
mApi(std::move(api)), mBMCFinishTimer(conn->get_io_context())
{
mBMCFinishTimer.expires_after(std::chrono::seconds(0));
mBMCFinishTimer.async_wait(
[this, &conn](const boost::system::error_code& ec) {
CheckBmcFinish(ec, conn);
});
}
void Handler::CheckBmcFinish(const boost::system::error_code& ec,
std::shared_ptr<sdbusplus::asio::connection>& conn)
{
if (ec == boost::asio::error::operation_aborted)
{
fmt::print(stderr,
"[CheckBmcFinish] BmcFinishTimer is being "
"canceled, Error: {}\n",
ec.message().c_str());
return;
}
if (ec)
{
fmt::print(stderr, "[CheckBmcFinish] Timer Error: {}\n",
ec.message().c_str());
return;
}
// 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, uint64_t*>, 6> bootTimestamp = {
std::make_pair( // Firmware
std::string(kSystemdFirmwareTime), &times.firmware),
std::make_pair( // Loader
std::string(kSystemdLoaderTime), &times.loader),
std::make_pair( // Kernel
std::string(kSystemdKernelTime), &times.kernel),
std::make_pair( // InitRD
std::string(kSystemdInitRDTime), &times.initRD),
std::make_pair( // Userspace
std::string(kSystemdUserspaceTime), &times.userspace),
std::make_pair( // Finish
std::string(kSystemdFinishTime), &times.finish)};
for (const auto& timestamp : bootTimestamp)
{
auto method = conn->new_method_call(
"org.freedesktop.systemd1", // Systemd Service
"/org/freedesktop/systemd1", // Systemd Path
"org.freedesktop.DBus.Properties", // Systemd Iface
"Get"); // Systemd Func
method.append("org.freedesktop.systemd1.Manager",
std::string(timestamp.first));
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",
std::string(timestamp.first), e.what());
// Restart the timer to keep polling the APML_ALERT status
mBMCFinishTimer.expires_after(kCheckBmcFinishInterval);
mBMCFinishTimer.async_wait(
[this, &conn](const boost::system::error_code& ec) {
CheckBmcFinish(ec, conn);
});
}
*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");
mBMCFinishTimer.expires_after(kCheckBmcFinishInterval);
mBMCFinishTimer.async_wait(
[this, &conn](const boost::system::error_code& ec) {
CheckBmcFinish(ec, conn);
});
}
absl::Status status;
SystemdDuration durations = CalculateSystemdDuration(times);
status = mApi->SetNodesDurationByTag(
btm::kBootTimeTagBMC, convertName(kSystemdFirmwareTime),
durations.firmware / kMillisecondPerSecond);
btm::log::LogIfError(status);
status = mApi->SetNodesDurationByTag(
btm::kBootTimeTagBMC, convertName(kSystemdLoaderTime),
durations.loader / kMillisecondPerSecond);
btm::log::LogIfError(status);
status = mApi->SetNodesDurationByTag(
btm::kBootTimeTagBMC, convertName(kSystemdKernelTime),
durations.kernel / kMillisecondPerSecond);
btm::log::LogIfError(status);
status = mApi->SetNodesDurationByTag(
btm::kBootTimeTagBMC, convertName(kSystemdInitRDTime),
durations.initRD / kMillisecondPerSecond);
btm::log::LogIfError(status);
status = mApi->SetNodesDurationByTag(
btm::kBootTimeTagBMC, convertName(kSystemdUserspaceTime),
durations.userspace / kMillisecondPerSecond);
btm::log::LogIfError(status);
#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 = readMem4Bytes(SEC_CNT_ADDR);
auto upTimeMS = getUpTimeInMs();
if (powerOnSec != std::nullopt && upTimeMS != std::nullopt)
{
status = mApi->SetNodesDurationByTag(
btm::kBootTimeTagBMC, "FirmwarePlusLoader",
powerOnSec.value() * 1000 - upTimeMS.value());
btm::log::LogIfError(status);
}
#endif
// BMC marks the boot process as `completed` automatically if we do *NOT* have
// external daemon to do so.
#ifdef AUTO_BMC_COMPLETE
status = mApi->SetNodesCheckpointByTag(btm::kBootTimeTagBMC, "UserspaceEnd",
0, 0);
btm::log::LogIfError(status);
status = mApi->NotifyNodesCompleteByTag(btm::kBootTimeTagBMC);
btm::log::LogIfError(status);
#endif
}
#ifdef NPCM7XX_OR_NEWER
std::optional<uint32_t> Handler::readMem4Bytes(uint32_t target)
{
// Typically the pageSize will be 4K/8K for 32 bit operating systems
uint32_t pageSize = boost::interprocess::mapped_region::get_page_size();
const uint32_t kPageMask = pageSize - 1;
uint32_t pageOffset = target & (~kPageMask);
uint32_t offsetInPage = target & kPageMask;
// Map `/dev/mem` to a region.
std::unique_ptr<boost::interprocess::file_mapping> fileMapping;
std::unique_ptr<boost::interprocess::mapped_region> MappedRegion;
try
{
fileMapping = std::make_unique<boost::interprocess::file_mapping>(
"/dev/mem", boost::interprocess::read_only);
// No need to unmap in the end.
MappedRegion = std::make_unique<boost::interprocess::mapped_region>(
*fileMapping, boost::interprocess::read_only, pageOffset,
pageSize * 2);
// MappedRegion->get_address() returns (void*) which needs to covert
// into (char*) to make `+ offsetInPage` work.
// Then converts it again into (uint32_t*) since we read 4 bytes.
return *(reinterpret_cast<uint32_t*>(
static_cast<char*>(MappedRegion->get_address()) + offsetInPage));
}
catch (const std::exception& e)
{
fmt::print(stderr, "[{}]: Throw exception: %s\n", __FUNCTION__,
e.what());
return std::nullopt;
}
fmt::print(stderr, "[{}]: Shouldn't go to this line.\n", __FUNCTION__);
return std::nullopt;
}
std::optional<int64_t> Handler::getUpTimeInMs()
{
constexpr int32_t kMillisecondPerSecond = 1000;
std::ifstream fin("/proc/uptime");
if (!fin.is_open())
{
fmt::print(stderr, "[{}]: Can not read \"/proc/uptime\"\n",
__FUNCTION__);
return std::nullopt;
}
double uptimeSec;
fin >> uptimeSec;
fin.close();
return static_cast<int64_t>(uptimeSec * kMillisecondPerSecond);
}
#endif // NPCM7XX_OR_NEWER
} // namespace systemd
} // namespace boot_time_monitor