blob: c291718f72919f9e1a02d9ade70476e4ba8e3e6b [file] [log] [blame]
// Copyright (c) 2021 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 "cpuinfo_utils.hpp"
// Include the server headers to get the enum<->string conversion functions
#include <boost/algorithm/string/predicate.hpp>
#include <sdbusplus/asio/property.hpp>
#include <xyz/openbmc_project/State/Host/server.hpp>
#include <xyz/openbmc_project/State/OperatingSystem/Status/server.hpp>
#include <iostream>
#include <type_traits>
#include <utility>
#include <variant>
namespace cpu_info
{
using namespace sdbusplus::server::xyz::openbmc_project;
using PowerState = state::Host::HostState;
using OsState = state::operating_system::Status::OSStatus;
HostState hostState = HostState::off;
static PowerState powerState = PowerState::Off;
static OsState osState = OsState::Inactive;
static bool biosDone = false;
static std::vector<HostStateHandler> hostStateCallbacks;
static std::shared_ptr<sdbusplus::asio::connection> dbusConn;
void addHostStateCallback(HostStateHandler cb)
{
hostStateCallbacks.push_back(cb);
}
static void updateHostState()
{
HostState prevState = hostState;
if (powerState == PowerState::Off)
{
hostState = HostState::off;
// Make sure that we don't inadvertently jump back to PostComplete if
// the HW status happens to turn back on before the biosDone goes false,
// since the two signals come from different services and there is no
// tight guarantee about their relationship.
biosDone = false;
// Setting osState to inactive for the same reason as above.
osState = OsState::Inactive;
}
// Both biosDone and OsState tell us about the POST done status. At least
// one of them should indicate that the POST is done.
// According to openbmc_project/State/OperatingSystem/Status.interface.yaml
// Only "Inactive" indicates that the POST is not done. All the other
// statuses (CBoot, PXEBoot, DiagBoot, CDROMBoot, ROMBoot, BootComplete,
// Standby) indicate that the POST is done.
else if ((!biosDone) && (osState == OsState::Inactive))
{
hostState = HostState::postInProgress;
}
else
{
hostState = HostState::postComplete;
}
DEBUG_PRINT << "new host state: " << static_cast<int>(hostState) << "\n";
if (prevState != hostState)
{
for (const auto& cb : hostStateCallbacks)
{
cb(prevState, hostState);
}
}
}
void updatePowerState(const std::string& newState)
{
powerState = state::Host::convertHostStateFromString(newState);
updateHostState();
}
void updateBiosDone(bool newState)
{
biosDone = newState;
updateHostState();
}
void updateOsState(const std::string& newState)
{
// newState might not contain the full path. It might just contain the enum
// string (By the time I am writing this, its not returning the full path).
// Full string:
// "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.Standby". Just
// the string for enum: "Standby". If the newState doesn't contain the full
// string, convertOSStatusFromString will fail. Prepend the full path if
// needed.
std::string full_path = newState;
if (newState.find("xyz.") == std::string::npos)
{
full_path =
"xyz.openbmc_project.State.OperatingSystem.Status.OSStatus." +
newState;
}
try
{
osState = state::operating_system::Status::convertOSStatusFromString(
full_path);
}
catch (const sdbusplus::exception::InvalidEnumString& ex)
{
std::cerr << "Invalid OperatingSystem Status: " << full_path << "\n";
osState = OsState::Inactive;
}
updateHostState();
}
/**
* Register a handler to be called whenever the given property is changed. Also
* call the handler once immediately (asynchronously) with the current property
* value.
*
* Since this necessarily reads all properties in the given interface, type
* information about the interface may need to be provided via
* CustomVariantArgs.
*
* @tparam CustomVariantTypes Any property types contained in the interface
* beyond the base data types (numeric and
* string-like types) and Handler's param type.
* @tparam Handler Automatically deduced. Must be a callable taking a
* single parameter whose type matches the property.
*
* @param[in] service D-Bus service name.
* @param[in] object D-Bus object name.
* @param[in] interface D-Bus interface name.
* @param[in] propertyName D-Bus property name.
* @param[in] handler Callable to be called immediately and upon any
* changes in the property value.
* @param[out] propertiesChangedMatch Optional pointer to receive a D-Bus
* match object, if you need to manage its
* lifetime.
* @param[out] interfacesAddedMatch Optional pointer to receive a D-Bus
* match object, if you need to manage its
* lifetime.
*/
template <typename... CustomVariantTypes, typename Handler>
static void subscribeToProperty(
const char* service, const char* object, const char* interface,
const char* propertyName, Handler&& handler,
sdbusplus::bus::match_t** propertiesChangedMatch = nullptr,
sdbusplus::bus::match_t** interfacesAddedMatch = nullptr)
{
// Type of first parameter to Handler, with const/& removed
using PropertyType = std::remove_const_t<std::remove_reference_t<
std::tuple_element_t<0, boost::callable_traits::args_t<Handler>>>>;
// Base data types which we can handle by default
using InterfaceVariant = typename sdbusplus::utility::dedup_variant_t<
PropertyType, CustomVariantTypes..., bool, uint8_t, uint16_t, int16_t,
uint32_t, int32_t, uint64_t, int64_t, size_t, ssize_t, double,
std::string, sdbusplus::message::object_path>;
sdbusplus::asio::getProperty<PropertyType>(
*dbusConn, service, object, interface, propertyName,
[handler, propertyName = std::string(propertyName)](
boost::system::error_code ec, const PropertyType& newValue) {
if (ec)
{
std::cerr << "Failed to read property " << propertyName << ": "
<< ec << "\n";
return;
}
handler(newValue);
});
using ChangedPropertiesType =
std::vector<std::pair<std::string, InterfaceVariant>>;
// Define some logic which is common to the two match callbacks, since they
// both have to loop through all the properties in the interface.
auto commonPropHandler = [propertyName = std::string(propertyName),
handler = std::forward<Handler>(handler)](
const ChangedPropertiesType& changedProps) {
for (const auto& [changedProp, newValue] : changedProps)
{
if (changedProp == propertyName)
{
const auto* actualVal = std::get_if<PropertyType>(&newValue);
if (actualVal != nullptr)
{
DEBUG_PRINT << "New value for " << propertyName << ": "
<< *actualVal << "\n";
handler(*actualVal);
}
else
{
std::cerr << "Property " << propertyName
<< " had unexpected type\n";
}
break;
}
}
};
// Set up a match for PropertiesChanged signal
auto* propMatch = new sdbusplus::bus::match_t(
*dbusConn,
sdbusplus::bus::match::rules::sender(service) +
sdbusplus::bus::match::rules::propertiesChanged(object, interface),
[commonPropHandler](sdbusplus::message_t& reply) {
ChangedPropertiesType changedProps;
// ignore first param (interface name), it has to be correct
reply.read(std::string(), changedProps);
DEBUG_PRINT << "PropertiesChanged handled\n";
commonPropHandler(changedProps);
});
// Set up a match for the InterfacesAdded signal from the service's
// ObjectManager. This is useful in the case where the object is not added
// yet, and when it's added they choose to not emit PropertiesChanged. So in
// order to see the initial value when it comes, we need to watch this too.
auto* intfMatch = new sdbusplus::bus::match_t(
*dbusConn,
sdbusplus::bus::match::rules::sender(service) +
sdbusplus::bus::match::rules::interfacesAdded(),
[object = std::string(object), interface = std::string(interface),
commonPropHandler](sdbusplus::message_t& reply) {
sdbusplus::message::object_path changedObject;
reply.read(changedObject);
if (changedObject != object)
{
return;
}
std::vector<std::pair<std::string, ChangedPropertiesType>>
changedInterfaces;
reply.read(changedInterfaces);
for (const auto& [changedInterface, changedProps] : changedInterfaces)
{
if (changedInterface != interface)
{
continue;
}
DEBUG_PRINT << "InterfacesAdded handled\n";
commonPropHandler(changedProps);
}
});
if (propertiesChangedMatch != nullptr)
{
*propertiesChangedMatch = propMatch;
}
if (interfacesAddedMatch != nullptr)
{
*interfacesAddedMatch = intfMatch;
}
}
void hostStateSetup(const std::shared_ptr<sdbusplus::asio::connection>& conn)
{
static bool initialized = false;
if (initialized)
{
return;
}
dbusConn = conn;
// Leak the returned match objects. We want them to run forever.
subscribeToProperty(
"xyz.openbmc_project.State.Host", "/xyz/openbmc_project/state/host0",
state::Host::interface, "CurrentHostState", updatePowerState);
subscribeToProperty("xyz.openbmc_project.Host.Misc.Manager",
"/xyz/openbmc_project/misc/platform_state",
"xyz.openbmc_project.State.Host.Misc", "CoreBiosDone",
updateBiosDone);
// xyz.openbmc_project.Host.Misc.Manager has Intel specific dependencies.
// If it is not available, then we can use the OperatingSystemState in
// xyz.openbmc_project.State.OperatingSystem. According to x86-power-control
// repo, OperatingSystemState should return "standby" once the POST is
// asserted.
subscribeToProperty("xyz.openbmc_project.State.OperatingSystem",
"/xyz/openbmc_project/state/os",
"xyz.openbmc_project.State.OperatingSystem.Status",
"OperatingSystemState", updateOsState);
initialized = true;
}
namespace dbus
{
boost::asio::io_context& getIOContext()
{
static boost::asio::io_context ioc;
return ioc;
}
std::shared_ptr<sdbusplus::asio::connection> getConnection()
{
static auto conn =
std::make_shared<sdbusplus::asio::connection>(getIOContext());
return conn;
}
} // namespace dbus
} // namespace cpu_info