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::xyz::openbmc_project; 32 using PowerState = State::server::Host::HostState; 33 using OsState = State::OperatingSystem::server::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::server::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 = 119 State::OperatingSystem::server::Status::convertOSStatusFromString( 120 full_path); 121 } 122 catch (const sdbusplus::exception::InvalidEnumString& ex) 123 { 124 std::cerr << "Invalid OperatingSystem Status: " << full_path << "\n"; 125 osState = OsState::Inactive; 126 } 127 updateHostState(); 128 } 129 130 /** 131 * Register a handler to be called whenever the given property is changed. Also 132 * call the handler once immediately (asynchronously) with the current property 133 * value. 134 * 135 * Since this necessarily reads all properties in the given interface, type 136 * information about the interface may need to be provided via 137 * CustomVariantArgs. 138 * 139 * @tparam CustomVariantTypes Any property types contained in the interface 140 * beyond the base data types (numeric and 141 * string-like types) and Handler's param type. 142 * @tparam Handler Automatically deduced. Must be a callable taking a 143 * single parameter whose type matches the property. 144 * 145 * @param[in] service D-Bus service name. 146 * @param[in] object D-Bus object name. 147 * @param[in] interface D-Bus interface name. 148 * @param[in] propertyName D-Bus property name. 149 * @param[in] handler Callable to be called immediately and upon any 150 * changes in the property value. 151 * @param[out] propertiesChangedMatch Optional pointer to receive a D-Bus 152 * match object, if you need to manage its 153 * lifetime. 154 * @param[out] interfacesAddedMatch Optional pointer to receive a D-Bus 155 * match object, if you need to manage its 156 * lifetime. 157 */ 158 template <typename... CustomVariantTypes, typename Handler> 159 static void subscribeToProperty( 160 const char* service, const char* object, const char* interface, 161 const char* propertyName, Handler&& handler, 162 sdbusplus::bus::match_t** propertiesChangedMatch = nullptr, 163 sdbusplus::bus::match_t** interfacesAddedMatch = nullptr) 164 { 165 // Type of first parameter to Handler, with const/& removed 166 using PropertyType = std::remove_const_t<std::remove_reference_t< 167 std::tuple_element_t<0, boost::callable_traits::args_t<Handler>>>>; 168 // Base data types which we can handle by default 169 using InterfaceVariant = typename sdbusplus::utility::dedup_variant_t< 170 PropertyType, CustomVariantTypes..., bool, uint8_t, uint16_t, int16_t, 171 uint32_t, int32_t, uint64_t, int64_t, size_t, ssize_t, double, 172 std::string, sdbusplus::message::object_path>; 173 174 sdbusplus::asio::getProperty<PropertyType>( 175 *dbusConn, service, object, interface, propertyName, 176 [handler, propertyName = std::string(propertyName)]( 177 boost::system::error_code ec, const PropertyType& newValue) { 178 if (ec) 179 { 180 std::cerr << "Failed to read property " << propertyName << ": " 181 << ec << "\n"; 182 return; 183 } 184 handler(newValue); 185 }); 186 187 using ChangedPropertiesType = 188 std::vector<std::pair<std::string, InterfaceVariant>>; 189 190 // Define some logic which is common to the two match callbacks, since they 191 // both have to loop through all the properties in the interface. 192 auto commonPropHandler = [propertyName = std::string(propertyName), 193 handler = std::forward<Handler>(handler)]( 194 const ChangedPropertiesType& changedProps) { 195 for (const auto& [changedProp, newValue] : changedProps) 196 { 197 if (changedProp == propertyName) 198 { 199 const auto* actualVal = std::get_if<PropertyType>(&newValue); 200 if (actualVal != nullptr) 201 { 202 DEBUG_PRINT << "New value for " << propertyName << ": " 203 << *actualVal << "\n"; 204 handler(*actualVal); 205 } 206 else 207 { 208 std::cerr << "Property " << propertyName 209 << " had unexpected type\n"; 210 } 211 break; 212 } 213 } 214 }; 215 216 // Set up a match for PropertiesChanged signal 217 auto* propMatch = new sdbusplus::bus::match_t( 218 *dbusConn, 219 sdbusplus::bus::match::rules::sender(service) + 220 sdbusplus::bus::match::rules::propertiesChanged(object, interface), 221 [commonPropHandler](sdbusplus::message_t& reply) { 222 ChangedPropertiesType changedProps; 223 // ignore first param (interface name), it has to be correct 224 reply.read(std::string(), changedProps); 225 226 DEBUG_PRINT << "PropertiesChanged handled\n"; 227 commonPropHandler(changedProps); 228 }); 229 230 // Set up a match for the InterfacesAdded signal from the service's 231 // ObjectManager. This is useful in the case where the object is not added 232 // yet, and when it's added they choose to not emit PropertiesChanged. So in 233 // order to see the initial value when it comes, we need to watch this too. 234 auto* intfMatch = new sdbusplus::bus::match_t( 235 *dbusConn, 236 sdbusplus::bus::match::rules::sender(service) + 237 sdbusplus::bus::match::rules::interfacesAdded(), 238 [object = std::string(object), interface = std::string(interface), 239 commonPropHandler](sdbusplus::message_t& reply) { 240 sdbusplus::message::object_path changedObject; 241 reply.read(changedObject); 242 if (changedObject != object) 243 { 244 return; 245 } 246 247 std::vector<std::pair<std::string, ChangedPropertiesType>> 248 changedInterfaces; 249 reply.read(changedInterfaces); 250 251 for (const auto& [changedInterface, changedProps] : 252 changedInterfaces) 253 { 254 if (changedInterface != interface) 255 { 256 continue; 257 } 258 259 DEBUG_PRINT << "InterfacesAdded handled\n"; 260 commonPropHandler(changedProps); 261 } 262 }); 263 264 if (propertiesChangedMatch != nullptr) 265 { 266 *propertiesChangedMatch = propMatch; 267 } 268 269 if (interfacesAddedMatch != nullptr) 270 { 271 *interfacesAddedMatch = intfMatch; 272 } 273 } 274 275 void hostStateSetup(const std::shared_ptr<sdbusplus::asio::connection>& conn) 276 { 277 static bool initialized = false; 278 if (initialized) 279 { 280 return; 281 } 282 283 dbusConn = conn; 284 285 // Leak the returned match objects. We want them to run forever. 286 subscribeToProperty( 287 "xyz.openbmc_project.State.Host", "/xyz/openbmc_project/state/host0", 288 State::server::Host::interface, "CurrentHostState", updatePowerState); 289 subscribeToProperty("xyz.openbmc_project.Host.Misc.Manager", 290 "/xyz/openbmc_project/misc/platform_state", 291 "xyz.openbmc_project.State.Host.Misc", "CoreBiosDone", 292 updateBiosDone); 293 // xyz.openbmc_project.Host.Misc.Manager has Intel specific dependencies. 294 // If it is not available, then we can use the OperatingSystemState in 295 // xyz.openbmc_project.State.OperatingSystem. According to x86-power-control 296 // repo, OperatingSystemState should return "standby" once the POST is 297 // asserted. 298 subscribeToProperty("xyz.openbmc_project.State.OperatingSystem", 299 "/xyz/openbmc_project/state/os", 300 "xyz.openbmc_project.State.OperatingSystem.Status", 301 "OperatingSystemState", updateOsState); 302 303 initialized = true; 304 } 305 306 namespace dbus 307 { 308 boost::asio::io_context& getIOContext() 309 { 310 static boost::asio::io_context ioc; 311 return ioc; 312 } 313 std::shared_ptr<sdbusplus::asio::connection> getConnection() 314 { 315 static auto conn = 316 std::make_shared<sdbusplus::asio::connection>(getIOContext()); 317 return conn; 318 } 319 } // namespace dbus 320 } // namespace cpu_info 321