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