1 // Copyright (c) 2021 Intel Corporation 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "cpuinfo_utils.hpp" 16 17 // Include the server headers to get the enum<->string conversion functions 18 #include <boost/algorithm/string/predicate.hpp> 19 #include <sdbusplus/asio/property.hpp> 20 #include <xyz/openbmc_project/State/Host/server.hpp> 21 #include <xyz/openbmc_project/State/OperatingSystem/Status/server.hpp> 22 23 #include <iostream> 24 #include <type_traits> 25 #include <utility> 26 #include <variant> 27 28 namespace cpu_info 29 { 30 31 using namespace sdbusplus::server::xyz::openbmc_project; 32 using PowerState = state::Host::HostState; 33 using OsState = state::operating_system::Status::OSStatus; 34 35 HostState hostState = HostState::off; 36 static PowerState powerState = PowerState::Off; 37 static OsState osState = OsState::Inactive; 38 static bool biosDone = false; 39 static std::vector<HostStateHandler> hostStateCallbacks; 40 41 static std::shared_ptr<sdbusplus::asio::connection> dbusConn; 42 43 void addHostStateCallback(HostStateHandler cb) 44 { 45 hostStateCallbacks.push_back(cb); 46 } 47 48 static void updateHostState() 49 { 50 HostState prevState = hostState; 51 if (powerState == PowerState::Off) 52 { 53 hostState = HostState::off; 54 // Make sure that we don't inadvertently jump back to PostComplete if 55 // the HW status happens to turn back on before the biosDone goes false, 56 // since the two signals come from different services and there is no 57 // tight guarantee about their relationship. 58 biosDone = false; 59 // Setting osState to inactive for the same reason as above. 60 osState = OsState::Inactive; 61 } 62 // Both biosDone and OsState tell us about the POST done status. At least 63 // one of them should indicate that the POST is done. 64 // According to openbmc_project/State/OperatingSystem/Status.interface.yaml 65 // Only "Inactive" indicates that the POST is not done. All the other 66 // statuses (CBoot, PXEBoot, DiagBoot, CDROMBoot, ROMBoot, BootComplete, 67 // Standby) indicate that the POST is done. 68 else if ((!biosDone) && (osState == OsState::Inactive)) 69 { 70 hostState = HostState::postInProgress; 71 } 72 else 73 { 74 hostState = HostState::postComplete; 75 } 76 DEBUG_PRINT << "new host state: " << static_cast<int>(hostState) << "\n"; 77 78 if (prevState != hostState) 79 { 80 for (const auto& cb : hostStateCallbacks) 81 { 82 cb(prevState, hostState); 83 } 84 } 85 } 86 87 void updatePowerState(const std::string& newState) 88 { 89 powerState = state::Host::convertHostStateFromString(newState); 90 updateHostState(); 91 } 92 93 void updateBiosDone(bool newState) 94 { 95 biosDone = newState; 96 updateHostState(); 97 } 98 99 void updateOsState(const std::string& newState) 100 { 101 // newState might not contain the full path. It might just contain the enum 102 // string (By the time I am writing this, its not returning the full path). 103 // Full string: 104 // "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.Standby". Just 105 // the string for enum: "Standby". If the newState doesn't contain the full 106 // string, convertOSStatusFromString will fail. Prepend the full path if 107 // needed. 108 std::string full_path = newState; 109 if (newState.find("xyz.") == std::string::npos) 110 { 111 full_path = 112 "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus." + 113 newState; 114 } 115 116 try 117 { 118 osState = state::operating_system::Status::convertOSStatusFromString( 119 full_path); 120 } 121 catch (const sdbusplus::exception::InvalidEnumString& ex) 122 { 123 std::cerr << "Invalid OperatingSystem Status: " << full_path << "\n"; 124 osState = OsState::Inactive; 125 } 126 updateHostState(); 127 } 128 129 /** 130 * Register a handler to be called whenever the given property is changed. Also 131 * call the handler once immediately (asynchronously) with the current property 132 * value. 133 * 134 * Since this necessarily reads all properties in the given interface, type 135 * information about the interface may need to be provided via 136 * CustomVariantArgs. 137 * 138 * @tparam CustomVariantTypes Any property types contained in the interface 139 * beyond the base data types (numeric and 140 * string-like types) and Handler's param type. 141 * @tparam Handler Automatically deduced. Must be a callable taking a 142 * single parameter whose type matches the property. 143 * 144 * @param[in] service D-Bus service name. 145 * @param[in] object D-Bus object name. 146 * @param[in] interface D-Bus interface name. 147 * @param[in] propertyName D-Bus property name. 148 * @param[in] handler Callable to be called immediately and upon any 149 * changes in the property value. 150 * @param[out] propertiesChangedMatch Optional pointer to receive a D-Bus 151 * match object, if you need to manage its 152 * lifetime. 153 * @param[out] interfacesAddedMatch Optional pointer to receive a D-Bus 154 * match object, if you need to manage its 155 * lifetime. 156 */ 157 template <typename... CustomVariantTypes, typename Handler> 158 static void subscribeToProperty( 159 const char* service, const char* object, const char* interface, 160 const char* propertyName, Handler&& handler, 161 sdbusplus::bus::match_t** propertiesChangedMatch = nullptr, 162 sdbusplus::bus::match_t** interfacesAddedMatch = nullptr) 163 { 164 // Type of first parameter to Handler, with const/& removed 165 using PropertyType = std::remove_const_t<std::remove_reference_t< 166 std::tuple_element_t<0, boost::callable_traits::args_t<Handler>>>>; 167 // Base data types which we can handle by default 168 using InterfaceVariant = typename sdbusplus::utility::dedup_variant_t< 169 PropertyType, CustomVariantTypes..., bool, uint8_t, uint16_t, int16_t, 170 uint32_t, int32_t, uint64_t, int64_t, size_t, ssize_t, double, 171 std::string, sdbusplus::message::object_path>; 172 173 sdbusplus::asio::getProperty<PropertyType>( 174 *dbusConn, service, object, interface, propertyName, 175 [handler, propertyName = std::string(propertyName)]( 176 boost::system::error_code ec, const PropertyType& newValue) { 177 if (ec) 178 { 179 std::cerr << "Failed to read property " << propertyName << ": " 180 << ec << "\n"; 181 return; 182 } 183 handler(newValue); 184 }); 185 186 using ChangedPropertiesType = 187 std::vector<std::pair<std::string, InterfaceVariant>>; 188 189 // Define some logic which is common to the two match callbacks, since they 190 // both have to loop through all the properties in the interface. 191 auto commonPropHandler = [propertyName = std::string(propertyName), 192 handler = std::forward<Handler>(handler)]( 193 const ChangedPropertiesType& changedProps) { 194 for (const auto& [changedProp, newValue] : changedProps) 195 { 196 if (changedProp == propertyName) 197 { 198 const auto* actualVal = std::get_if<PropertyType>(&newValue); 199 if (actualVal != nullptr) 200 { 201 DEBUG_PRINT << "New value for " << propertyName << ": " 202 << *actualVal << "\n"; 203 handler(*actualVal); 204 } 205 else 206 { 207 std::cerr << "Property " << propertyName 208 << " had unexpected type\n"; 209 } 210 break; 211 } 212 } 213 }; 214 215 // Set up a match for PropertiesChanged signal 216 auto* propMatch = new sdbusplus::bus::match_t( 217 *dbusConn, 218 sdbusplus::bus::match::rules::sender(service) + 219 sdbusplus::bus::match::rules::propertiesChanged(object, interface), 220 [commonPropHandler](sdbusplus::message_t& reply) { 221 ChangedPropertiesType changedProps; 222 // ignore first param (interface name), it has to be correct 223 reply.read(std::string(), changedProps); 224 225 DEBUG_PRINT << "PropertiesChanged handled\n"; 226 commonPropHandler(changedProps); 227 }); 228 229 // Set up a match for the InterfacesAdded signal from the service's 230 // ObjectManager. This is useful in the case where the object is not added 231 // yet, and when it's added they choose to not emit PropertiesChanged. So in 232 // order to see the initial value when it comes, we need to watch this too. 233 auto* intfMatch = new sdbusplus::bus::match_t( 234 *dbusConn, 235 sdbusplus::bus::match::rules::sender(service) + 236 sdbusplus::bus::match::rules::interfacesAdded(), 237 [object = std::string(object), interface = std::string(interface), 238 commonPropHandler](sdbusplus::message_t& reply) { 239 sdbusplus::message::object_path changedObject; 240 reply.read(changedObject); 241 if (changedObject != object) 242 { 243 return; 244 } 245 246 std::vector<std::pair<std::string, ChangedPropertiesType>> 247 changedInterfaces; 248 reply.read(changedInterfaces); 249 250 for (const auto& [changedInterface, changedProps] : changedInterfaces) 251 { 252 if (changedInterface != interface) 253 { 254 continue; 255 } 256 257 DEBUG_PRINT << "InterfacesAdded handled\n"; 258 commonPropHandler(changedProps); 259 } 260 }); 261 262 if (propertiesChangedMatch != nullptr) 263 { 264 *propertiesChangedMatch = propMatch; 265 } 266 267 if (interfacesAddedMatch != nullptr) 268 { 269 *interfacesAddedMatch = intfMatch; 270 } 271 } 272 273 void hostStateSetup(const std::shared_ptr<sdbusplus::asio::connection>& conn) 274 { 275 static bool initialized = false; 276 if (initialized) 277 { 278 return; 279 } 280 281 dbusConn = conn; 282 283 // Leak the returned match objects. We want them to run forever. 284 subscribeToProperty( 285 "xyz.openbmc_project.State.Host", "/xyz/openbmc_project/state/host0", 286 state::Host::interface, "CurrentHostState", updatePowerState); 287 subscribeToProperty("xyz.openbmc_project.Host.Misc.Manager", 288 "/xyz/openbmc_project/misc/platform_state", 289 "xyz.openbmc_project.State.Host.Misc", "CoreBiosDone", 290 updateBiosDone); 291 // xyz.openbmc_project.Host.Misc.Manager has Intel specific dependencies. 292 // If it is not available, then we can use the OperatingSystemState in 293 // xyz.openbmc_project.State.OperatingSystem. According to x86-power-control 294 // repo, OperatingSystemState should return "standby" once the POST is 295 // asserted. 296 subscribeToProperty("xyz.openbmc_project.State.OperatingSystem", 297 "/xyz/openbmc_project/state/os", 298 "xyz.openbmc_project.State.OperatingSystem.Status", 299 "OperatingSystemState", updateOsState); 300 301 initialized = true; 302 } 303 304 namespace dbus 305 { 306 boost::asio::io_context& getIOContext() 307 { 308 static boost::asio::io_context ioc; 309 return ioc; 310 } 311 std::shared_ptr<sdbusplus::asio::connection> getConnection() 312 { 313 static auto conn = 314 std::make_shared<sdbusplus::asio::connection>(getIOContext()); 315 return conn; 316 } 317 } // namespace dbus 318 } // namespace cpu_info 319