// 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 #include #include #include #include #include #include #include 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 hostStateCallbacks; static std::shared_ptr 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(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 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>>>; // 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( *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>; // 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)]( const ChangedPropertiesType& changedProps) { for (const auto& [changedProp, newValue] : changedProps) { if (changedProp == propertyName) { const auto* actualVal = std::get_if(&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> 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& 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 getConnection() { static auto conn = std::make_shared(getIOContext()); return conn; } } // namespace dbus } // namespace cpu_info