blob: edad39dfa97b9f670082e64bd632c5cde693f6d9 [file] [log] [blame]
/*
// Copyright (c) 2019 Intel Corporation
//
// 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 "post_code.hpp"
#include <cereal/access.hpp>
#include <cereal/archives/binary.hpp>
#include <cereal/cereal.hpp>
#include <cereal/types/map.hpp>
#include <cereal/types/tuple.hpp>
#include <cereal/types/vector.hpp>
#include <phosphor-logging/commit.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/exception.hpp>
#include <iomanip>
using nlohmann::json;
const static constexpr auto timeoutMicroSeconds = 1000000;
/* systemd service to kick start a target. */
constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1";
constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
void PostCodeEvent::raise() const
{
json j = {{name, args}};
try
{
sdbusplus::exception::throw_via_json(j);
}
catch (sdbusplus::exception::generated_event_base& e)
{
auto path = lg2::commit(std::move(e));
std::cout << path.str << std::endl;
}
}
void from_json(const json& j, PostCodeEvent& event)
{
j.at("name").get_to(event.name);
for (const auto& entry : j.at("arguments").items())
{
if (entry.value().is_string())
{
event.args[entry.key()] = entry.value().get<std::string>();
}
else if (entry.value().is_number_integer())
{
event.args[entry.key()] = entry.value().get<int>();
}
}
}
void from_json(const json& j, PostCodeHandler& handler)
{
j.at("primary").get_to(handler.primary);
if (j.contains("secondary"))
{
secondarycode_t secondary;
j.at("secondary").get_to(secondary);
handler.secondary = secondary;
}
if (j.contains("targets"))
{
j.at("targets").get_to(handler.targets);
}
if (j.contains("event"))
{
PostCodeEvent event;
j.at("event").get_to(event);
handler.event = event;
}
}
const PostCodeHandler* PostCodeHandlers::find(postcode_t code)
{
for (const auto& handler : handlers)
{
if (handler.primary == std::get<0>(code) &&
(!handler.secondary || *handler.secondary == std::get<1>(code)))
{
return &handler;
}
}
return nullptr;
}
void PostCodeHandlers::handle(postcode_t code)
{
const PostCodeHandler* handler = find(code);
if (!handler)
{
return;
}
for (const auto& target : handler->targets)
{
auto bus = sdbusplus::bus::new_default();
auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT,
SYSTEMD_INTERFACE, "StartUnit");
method.append(target);
method.append("replace");
bus.call_noreply(method);
}
if (handler->event)
{
(*(handler->event)).raise();
}
}
void PostCodeHandlers::load(const std::string& path)
{
std::ifstream ifs(path);
handlers = json::parse(ifs).template get<std::vector<PostCodeHandler>>();
ifs.close();
}
void PostCode::deleteAll()
{
std::uintmax_t n = fs::remove_all(postCodeListPath);
std::cerr << "clearPostCodes deleted " << n << " files in "
<< postCodeListPath << std::endl;
fs::create_directories(postCodeListPath);
postCodes.clear();
currentBootCycleIndex = 0;
currentBootCycleCount(0);
}
std::vector<postcode_t> PostCode::getPostCodes(uint16_t index)
{
std::vector<postcode_t> codesVec;
if (1 == index && !postCodes.empty())
{
std::transform(postCodes.begin(), postCodes.end(),
std::back_inserter(codesVec),
[](const auto& kv) { return kv.second; });
}
else
{
uint16_t bootNum = getBootNum(index);
decltype(postCodes) codes;
deserializePostCodes(postCodeListPath / std::to_string(bootNum), codes);
std::transform(codes.begin(), codes.end(), std::back_inserter(codesVec),
[](const auto& kv) { return kv.second; });
}
return codesVec;
}
std::map<uint64_t, postcode_t> PostCode::getPostCodesWithTimeStamp(
uint16_t index)
{
if (1 == index && !postCodes.empty())
{
return postCodes;
}
uint16_t bootNum = getBootNum(index);
decltype(postCodes) codes;
deserializePostCodes(postCodeListPath / std::to_string(bootNum), codes);
return codes;
}
void PostCode::savePostCodes(postcode_t code)
{
if (!timer)
{
timer = std::make_unique<sdbusplus::Timer>(event.get(), [this]() {
serialize(postCodeListPath);
});
}
// steady_clock is a monotonic clock that is guaranteed to never be adjusted
auto postCodeTimeSteady = std::chrono::steady_clock::now();
uint64_t tsUS = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
if (postCodes.empty())
{
firstPostCodeTimeSteady = postCodeTimeSteady;
firstPostCodeUsSinceEpoch = tsUS; // uS since epoch for 1st post code
incrBootCycle();
}
else
{
// calculating tsUS so it is monotonic within the same boot
tsUS = firstPostCodeUsSinceEpoch +
std::chrono::duration_cast<std::chrono::microseconds>(
postCodeTimeSteady - firstPostCodeTimeSteady)
.count();
}
postCodes.insert(std::make_pair(tsUS, code));
if (postCodes.size() > MAX_POST_CODE_SIZE_PER_CYCLE)
{
postCodes.erase(postCodes.begin());
}
if (!timer->isRunning())
{
timer->start(std::chrono::microseconds(timeoutMicroSeconds));
}
if (strlen(POSTCODE_DISPLAY_PATH) > 0)
{
std::string postCodeDisplayPath =
POSTCODE_DISPLAY_PATH + std::to_string(node);
std::ofstream postCodeDisplayFile(postCodeDisplayPath);
postCodeDisplayFile << "0x" << std::setfill('0') << std::hex;
for (const auto& byte : std::get<0>(code))
{
postCodeDisplayFile << std::setw(2) << static_cast<int>(byte);
}
postCodeDisplayFile.close();
}
#ifdef ENABLE_BIOS_POST_CODE_LOG
uint64_t usTimeOffset = tsUS - firstPostCodeUsSinceEpoch;
std::ostringstream hexCode;
hexCode << "0x" << std::setfill('0') << std::hex;
for (const auto& byte : std::get<0>(code))
{
hexCode << std::setw(2) << static_cast<int>(byte);
}
std::ostringstream timeOffsetStr;
// Set Fixed-Point Notation
timeOffsetStr << std::fixed;
// Set precision to 4 digits
timeOffsetStr << std::setprecision(4);
// Add double to stream
timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000;
phosphor::logging::log<phosphor::logging::level::INFO>(
"BIOS POST Code",
phosphor::logging::entry("REDFISH_MESSAGE_ID=%s",
"OpenBMC.0.2.BIOSPOSTCode"),
phosphor::logging::entry(
"REDFISH_MESSAGE_ARGS=%d,%s,%s", currentBootCycleIndex,
timeOffsetStr.str().c_str(), hexCode.str().c_str()));
#endif
postCodeHandlers.handle(code);
return;
}
fs::path PostCode::serialize(const fs::path& path)
{
try
{
std::ofstream osIdx(path / CurrentBootCycleIndexName, std::ios::binary);
cereal::BinaryOutputArchive idxArchive(osIdx);
idxArchive(currentBootCycleIndex);
uint16_t count = currentBootCycleCount();
std::ofstream osCnt(path / CurrentBootCycleCountName, std::ios::binary);
cereal::BinaryOutputArchive cntArchive(osCnt);
cntArchive(count);
std::ofstream osPostCodes(path / std::to_string(currentBootCycleIndex));
cereal::BinaryOutputArchive oarchivePostCodes(osPostCodes);
oarchivePostCodes(postCodes);
}
catch (const cereal::Exception& e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
return "";
}
catch (const fs::filesystem_error& e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
return "";
}
return path;
}
bool PostCode::deserialize(const fs::path& path, uint16_t& index)
{
try
{
if (fs::exists(path))
{
std::ifstream is(path, std::ios::in | std::ios::binary);
cereal::BinaryInputArchive iarchive(is);
iarchive(index);
return true;
}
return false;
}
catch (const cereal::Exception& e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
return false;
}
catch (const fs::filesystem_error& e)
{
return false;
}
return false;
}
bool PostCode::deserializePostCodes(const fs::path& path,
std::map<uint64_t, postcode_t>& codes)
{
try
{
if (fs::exists(path))
{
std::ifstream is(path, std::ios::in | std::ios::binary);
cereal::BinaryInputArchive iarchive(is);
iarchive(codes);
return true;
}
return false;
}
catch (const cereal::Exception& e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
return false;
}
catch (const fs::filesystem_error& e)
{
return false;
}
return false;
}
void PostCode::incrBootCycle()
{
if (currentBootCycleIndex >= maxBootCycleNum())
{
currentBootCycleIndex = 1;
}
else
{
currentBootCycleIndex++;
}
currentBootCycleCount(std::min(
maxBootCycleNum(), static_cast<uint16_t>(currentBootCycleCount() + 1)));
}
uint16_t PostCode::getBootNum(const uint16_t index) const
{
// bootNum assumes the oldest archive is boot number 1
// and the current boot number equals bootCycleCount
// map bootNum back to bootIndex that was used to archive postcode
uint16_t bootNum = currentBootCycleIndex;
if (index > bootNum) // need to wrap around
{
bootNum = (maxBootCycleNum() + currentBootCycleIndex) - index + 1;
}
else
{
bootNum = currentBootCycleIndex - index + 1;
}
return bootNum;
}