blob: ba714fe4b8fdf4da486964969c8e51a942162572 [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 "pfr.hpp"
#include "pfr_mgr.hpp"
#include <systemd/sd-journal.h>
#include <boost/asio.hpp>
namespace pfr
{
// Caches the last Recovery/Panic Count to
// identify any new Recovery/panic actions.
/* TODO: When BMC Reset's, these values will be lost
* Persist this info using settingsd */
static uint8_t lastRecoveryCount = 0;
static uint8_t lastPanicCount = 0;
static uint8_t lastMajorErr = 0;
static uint8_t lastMinorErr = 0;
static bool stateTimerRunning = false;
bool finishedSettingChkPoint = false;
static constexpr uint8_t bmcBootFinishedChkPoint = 0x09;
std::unique_ptr<boost::asio::steady_timer> stateTimer = nullptr;
std::unique_ptr<boost::asio::steady_timer> initTimer = nullptr;
std::vector<std::unique_ptr<PfrVersion>> pfrVersionObjects;
std::unique_ptr<PfrConfig> pfrConfigObject;
// List holds <ObjPath> <ImageType> <VersionPurpose>
static std::vector<std::tuple<std::string, ImageType, std::string>>
verComponentList = {
std::make_tuple("bmc_recovery", ImageType::bmcRecovery,
versionPurposeBMC),
std::make_tuple("bios_recovery", ImageType::biosRecovery,
versionPurposeHost),
std::make_tuple("cpld_recovery", ImageType::cpldRecovery,
versionPurposeOther),
};
// Recovery reason map.
// {<CPLD association>,{<Redfish MessageID>, <Recovery Reason>}}
static const boost::container::flat_map<uint8_t,
std::pair<std::string, std::string>>
recoveryReasonMap = {
{0x01,
{"BIOSFirmwareRecoveryReason",
"BIOS active image authentication failure"}},
{0x02,
{"BIOSFirmwareRecoveryReason",
"BIOS recovery image authentication failure"}},
{0x03, {"MEFirmwareRecoveryReason", "ME launch failure"}},
{0x04, {"BIOSFirmwareRecoveryReason", "ACM launch failure"}},
{0x05, {"BIOSFirmwareRecoveryReason", "IBB launch failure"}},
{0x06, {"BIOSFirmwareRecoveryReason", "OBB launch failure"}},
{0x07,
{"BMCFirmwareRecoveryReason",
"BMC active image authentication failure"}},
{0x08,
{"BMCFirmwareRecoveryReason",
"BMC recovery image authentication failure"}},
{0x09, {"BMCFirmwareRecoveryReason", "BMC launch failure"}},
{0x0A, {"CPLDFirmwareRecoveryReason", "CPLD watchdog expired"}}};
// Panic Reason map.
// {<CPLD association>, {<Redfish MessageID>, <Panic reason> })
static const boost::container::flat_map<uint8_t,
std::pair<std::string, std::string>>
panicReasonMap = {
{0x01, {"BIOSFirmwarePanicReason", "BIOS update intent"}},
{0x02, {"BMCFirmwarePanicReason", "BMC update intent"}},
{0x03, {"BMCFirmwarePanicReason", "BMC reset detected"}},
{0x04, {"BMCFirmwarePanicReason", "BMC watchdog expired"}},
{0x05, {"MEFirmwarePanicReason", "ME watchdog expired"}},
{0x06, {"BIOSFirmwarePanicReason", "ACM watchdog expired"}},
{0x07, {"BIOSFirmwarePanicReason", "IBB watchdog expired"}},
{0x08, {"BIOSFirmwarePanicReason", "OBB watchdog expired"}},
{0x09,
{"BIOSFirmwarePanicReason",
"ACM or IBB or OBB authentication failure"}}};
// Firmware resiliency major map.
// {<CPLD association>, {<Redfish MessageID>, <Error reason> })
static const boost::container::flat_map<uint8_t,
std::pair<std::string, std::string>>
majorErrorCodeMap = {
{0x01,
{"BMCFirmwareResiliencyError", "BMC image authentication failed"}},
{0x02,
{"BIOSFirmwareResiliencyError", "BIOS image authentication failed"}},
{0x03, {"BIOSFirmwareResiliencyError", "Update from BIOS failed"}},
{0x04, {"BMCFirmwareResiliencyError", "Update from BMC failed"}}};
static void updateDbusPropertiesCache()
{
for (const auto& pfrVerObj : pfrVersionObjects)
{
pfrVerObj->updateVersion();
}
// Update provisoningStatus properties
pfrConfigObject->updateProvisioningStatus();
phosphor::logging::log<phosphor::logging::level::INFO>(
"PFR Manager service cache data updated.");
}
static void logLastRecoveryEvent()
{
uint8_t reason = 0;
if (0 != readCpldReg(ActionType::recoveryReason, reason))
{
return;
}
auto it = recoveryReasonMap.find(reason);
if (it == recoveryReasonMap.end())
{
// No matching found. So just return without logging event.
return;
}
std::string msgId = "OpenBMC.0.1." + it->second.first;
sd_journal_send("MESSAGE=%s", "Platform firmware recovery occurred.",
"PRIORITY=%i", LOG_WARNING, "REDFISH_MESSAGE_ID=%s",
msgId.c_str(), "REDFISH_MESSAGE_ARGS=%s",
it->second.second.c_str(), NULL);
}
static void logLastPanicEvent()
{
uint8_t reason = 0;
if (0 != readCpldReg(ActionType::panicReason, reason))
{
return;
}
auto it = panicReasonMap.find(reason);
if (it == panicReasonMap.end())
{
// No matching found. So just return without logging event.
return;
}
std::string msgId = "OpenBMC.0.1." + it->second.first;
sd_journal_send("MESSAGE=%s", "Platform firmware panic occurred.",
"PRIORITY=%i", LOG_WARNING, "REDFISH_MESSAGE_ID=%s",
msgId.c_str(), "REDFISH_MESSAGE_ARGS=%s",
it->second.second.c_str(), NULL);
}
static void logResiliencyErrorEvent(const uint8_t majorErrorCode,
const uint8_t minorErrorCode)
{
auto it = majorErrorCodeMap.find(majorErrorCode);
if (it == majorErrorCodeMap.end())
{
// No matching found. So just return without logging event.
return;
}
std::string errorStr =
it->second.second + "(MinorCode:0x" + toHexString(minorErrorCode) + ")";
std::string msgId = "OpenBMC.0.1." + it->second.first;
sd_journal_send(
"MESSAGE=%s", "Platform firmware resiliency error occurred.",
"PRIORITY=%i", LOG_ERR, "REDFISH_MESSAGE_ID=%s", msgId.c_str(),
"REDFISH_MESSAGE_ARGS=%s", errorStr.c_str(), NULL);
}
static void checkAndLogEvents()
{
uint8_t currPanicCount = 0;
if (0 == readCpldReg(ActionType::panicCount, currPanicCount))
{
if (lastPanicCount != currPanicCount)
{
// Update cached data and log redfish event by reading reason.
lastPanicCount = currPanicCount;
logLastPanicEvent();
}
}
uint8_t currRecoveryCount = 0;
if (0 == readCpldReg(ActionType::recoveryCount, currRecoveryCount))
{
if (lastRecoveryCount != currRecoveryCount)
{
// Update cached data and log redfish event by reading reason.
lastRecoveryCount = currRecoveryCount;
logLastRecoveryEvent();
}
}
uint8_t majorErr = 0;
uint8_t minorErr = 0;
if ((0 == readCpldReg(ActionType::majorError, majorErr)) &&
(0 == readCpldReg(ActionType::minorError, minorErr)))
{
if ((lastMajorErr != majorErr) || (lastMinorErr != minorErr))
{
lastMajorErr = majorErr;
lastMinorErr = minorErr;
logResiliencyErrorEvent(majorErr, minorErr);
}
}
}
static void monitorPlatformStateChange(
sdbusplus::asio::object_server& server,
std::shared_ptr<sdbusplus::asio::connection>& conn)
{
constexpr size_t pollTimeout = 10; // seconds
stateTimer->expires_after(std::chrono::seconds(pollTimeout));
stateTimer->async_wait(
[&server, &conn](const boost::system::error_code& ec) {
if (ec == boost::asio::error::operation_aborted)
{
// Timer reset.
return;
}
if (ec)
{
// Platform State Monitor - Timer cancelled.
return;
}
checkAndLogEvents();
monitorPlatformStateChange(server, conn);
});
}
void checkAndSetCheckpoint(sdbusplus::asio::object_server& server,
std::shared_ptr<sdbusplus::asio::connection>& conn)
{
// Check whether systemd completed all the loading.
conn->async_method_call(
[&server, &conn](boost::system::error_code ec,
const std::variant<uint64_t>& value) {
if (!ec)
{
if (std::get<uint64_t>(value))
{
phosphor::logging::log<phosphor::logging::level::INFO>(
"PFR: BMC boot completed. Setting checkpoint 9.");
if (!finishedSettingChkPoint)
{
finishedSettingChkPoint = true;
setBMCBootCheckpoint(bmcBootFinishedChkPoint);
}
return;
}
}
else
{
// Failed to get data from systemd. System might not
// be ready yet. Attempt again for data.
phosphor::logging::log<phosphor::logging::level::ERR>(
"PFR: aync call failed to get FinishTimestamp.",
phosphor::logging::entry("MSG=%s", ec.message().c_str()));
}
// FIX-ME: Latest up-stream sync caused issue in receiving
// StartupFinished signal. Unable to get StartupFinished signal
// from systemd1 hence using poll method too, to trigger it
// properly.
constexpr size_t pollTimeout = 10; // seconds
initTimer->expires_after(std::chrono::seconds(pollTimeout));
initTimer->async_wait([&server,
&conn](const boost::system::error_code& ec) {
if (ec == boost::asio::error::operation_aborted)
{
// Timer reset.
phosphor::logging::log<phosphor::logging::level::INFO>(
"PFR: Set boot Checkpoint - Timer aborted or stopped.");
return;
}
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"PFR: Set boot Checkpoint - async wait error.");
return;
}
checkAndSetCheckpoint(server, conn);
});
},
"org.freedesktop.systemd1", "/org/freedesktop/systemd1",
"org.freedesktop.DBus.Properties", "Get",
"org.freedesktop.systemd1.Manager", "FinishTimestamp");
}
void monitorSignals(sdbusplus::asio::object_server& server,
std::shared_ptr<sdbusplus::asio::connection>& conn)
{
// Monitor Boot finished signal and set the checkpoint 9 to
// notify CPLD about BMC boot finish.
auto bootFinishedSignal = std::make_unique<sdbusplus::bus::match::match>(
static_cast<sdbusplus::bus::bus&>(*conn),
"type='signal',"
"member='StartupFinished',path='/org/freedesktop/systemd1',"
"interface='org.freedesktop.systemd1.Manager'",
[&server, &conn](sdbusplus::message::message& msg) {
if (!finishedSettingChkPoint)
{
phosphor::logging::log<phosphor::logging::level::INFO>(
"PFR: BMC boot completed(StartupFinished). Setting "
"checkpoint 9.");
finishedSettingChkPoint = true;
setBMCBootCheckpoint(bmcBootFinishedChkPoint);
}
});
checkAndSetCheckpoint(server, conn);
// Capture the Chassis state and Start the monitor timer
// if state changed to 'On'. Run timer until OS boot.
// Stop timer if state changed to 'Off'.
static auto matchChassisState = sdbusplus::bus::match::match(
static_cast<sdbusplus::bus::bus&>(*conn),
"type='signal',member='PropertiesChanged', "
"interface='org.freedesktop.DBus.Properties', "
"sender='xyz.openbmc_project.State.Chassis', "
"arg0namespace='xyz.openbmc_project.State.Chassis'",
[&server, &conn](sdbusplus::message::message& message) {
std::string intfName;
std::map<std::string, std::variant<std::string>> properties;
message.read(intfName, properties);
const auto it = properties.find("CurrentPowerState");
if (it != properties.end())
{
const std::string* state =
std::get_if<std::string>(&it->second);
if (state != nullptr)
{
if ((*state ==
"xyz.openbmc_project.State.Chassis.PowerState.On") &&
(!stateTimerRunning))
{
stateTimerRunning = true;
monitorPlatformStateChange(server, conn);
}
else if ((*state == "xyz.openbmc_project.State.Chassis."
"PowerState.Off") &&
(stateTimerRunning))
{
stateTimer->cancel();
checkAndLogEvents();
stateTimerRunning = false;
}
}
// Update the D-Bus properties when chassis state changes.
updateDbusPropertiesCache();
}
});
// Capture the Host state and Start the monitor timer
// if state changed to 'Running'. Run timer until OS boot.
// Stop timer if state changed to 'Off'.
static auto matchHostState = sdbusplus::bus::match::match(
static_cast<sdbusplus::bus::bus&>(*conn),
"type='signal',member='PropertiesChanged', "
"interface='org.freedesktop.DBus.Properties', "
"sender='xyz.openbmc_project.State.Chassis', "
"arg0namespace='xyz.openbmc_project.State.Host'",
[&server, &conn](sdbusplus::message::message& message) {
std::string intfName;
std::map<std::string, std::variant<std::string>> properties;
message.read(intfName, properties);
const auto it = properties.find("CurrentHostState");
if (it != properties.end())
{
const std::string* state =
std::get_if<std::string>(&it->second);
if (state != nullptr)
{
if ((*state ==
"xyz.openbmc_project.State.Host.HostState.Running") &&
(!stateTimerRunning))
{
stateTimerRunning = true;
monitorPlatformStateChange(server, conn);
}
else if (((*state == "xyz.openbmc_project.State.Host."
"HostState.Off") ||
(*state == "xyz.openbmc_project.State.Host."
"HostState.Quiesced")) &&
(stateTimerRunning))
{
stateTimer->cancel();
checkAndLogEvents();
stateTimerRunning = false;
}
}
// Update the D-Bus properties when host state changes.
updateDbusPropertiesCache();
}
});
// Capture the OS state change and stop monitor timer
// if OS boots completly or becomes Inactive.
// start timer in other cases to mnitor states.
static auto matchOsState = sdbusplus::bus::match::match(
static_cast<sdbusplus::bus::bus&>(*conn),
"type='signal',member='PropertiesChanged', "
"interface='org.freedesktop.DBus.Properties', "
"sender='xyz.openbmc_project.State.Chassis', "
"arg0namespace='xyz.openbmc_project.State.OperatingSystem.Status'",
[&server, &conn](sdbusplus::message::message& message) {
std::string intfName;
std::map<std::string, std::variant<std::string>> properties;
message.read(intfName, properties);
const auto it = properties.find("OperatingSystemState");
if (it != properties.end())
{
const std::string* state =
std::get_if<std::string>(&it->second);
if (state != nullptr)
{
if (((*state == "BootComplete") ||
(*state == "Inactive")) &&
(stateTimerRunning))
{
stateTimer->cancel();
checkAndLogEvents();
stateTimerRunning = false;
}
else if (!stateTimerRunning)
{
stateTimerRunning = true;
monitorPlatformStateChange(server, conn);
}
}
}
});
// First time, check and log events if any.
checkAndLogEvents();
}
static void updateCPLDversion(std::shared_ptr<sdbusplus::asio::connection> conn)
{
std::string cpldVersion = pfr::readCPLDVersion();
conn->async_method_call(
[](const boost::system::error_code ec) {
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Unable to update cpld_active version",
phosphor::logging::entry("MSG=%s", ec.message().c_str()));
return;
}
},
"xyz.openbmc_project.Settings",
"/xyz/openbmc_project/software/cpld_active",
"org.freedesktop.DBus.Properties", "Set",
"xyz.openbmc_project.Software.Version", "Version",
std::variant<std::string>(cpldVersion));
return;
}
} // namespace pfr
int main()
{
// setup connection to dbus
boost::asio::io_service io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
pfr::stateTimer = std::make_unique<boost::asio::steady_timer>(io);
pfr::initTimer = std::make_unique<boost::asio::steady_timer>(io);
auto server = sdbusplus::asio::object_server(conn, true);
pfr::monitorSignals(server, conn);
// Update CPLD Version to cpld_active object in settings.
pfr::updateCPLDversion(conn);
server.add_manager("/xyz/openbmc_project/pfr");
// Create PFR attributes object and interface
pfr::pfrConfigObject = std::make_unique<pfr::PfrConfig>(server, conn);
// Create Software objects using Versions interface
for (const auto& entry : pfr::verComponentList)
{
pfr::pfrVersionObjects.emplace_back(std::make_unique<pfr::PfrVersion>(
server, conn, std::get<0>(entry), std::get<1>(entry),
std::get<2>(entry)));
}
conn->request_name("xyz.openbmc_project.PFR.Manager");
phosphor::logging::log<phosphor::logging::level::INFO>(
"Intel PFR service started successfully");
io.run();
return 0;
}