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 22 #include <iostream> 23 #include <type_traits> 24 #include <utility> 25 #include <variant> 26 27 namespace cpu_info 28 { 29 30 using namespace sdbusplus::xyz::openbmc_project; 31 using PowerState = State::server::Host::HostState; 32 33 HostState hostState = HostState::off; 34 static PowerState powerState = PowerState::Off; 35 static bool biosDone = false; 36 37 static std::shared_ptr<sdbusplus::asio::connection> dbusConn; 38 39 static void updateHostState() 40 { 41 if (powerState == PowerState::Off) 42 { 43 hostState = HostState::off; 44 // Make sure that we don't inadvertently jump back to PostComplete if 45 // the HW status happens to turn back on before the biosDone goes false, 46 // since the two signals come from different services and there is no 47 // tight guarantee about their relationship. 48 biosDone = false; 49 } 50 else if (!biosDone) 51 { 52 hostState = HostState::postInProgress; 53 } 54 else 55 { 56 hostState = HostState::postComplete; 57 } 58 DEBUG_PRINT << "new host state: " << static_cast<int>(hostState) << "\n"; 59 } 60 61 void updatePowerState(const std::string& newState) 62 { 63 powerState = State::server::Host::convertHostStateFromString(newState); 64 updateHostState(); 65 } 66 67 void updateBiosDone(bool newState) 68 { 69 biosDone = newState; 70 updateHostState(); 71 } 72 73 /** 74 * Register a handler to be called whenever the given property is changed. Also 75 * call the handler once immediately (asynchronously) with the current property 76 * value. 77 * 78 * Since this necessarily reads all properties in the given interface, type 79 * information about the interface may need to be provided via 80 * CustomVariantArgs. 81 * 82 * @tparam CustomVariantTypes Any property types contained in the interface 83 * beyond the base data types (numeric and 84 * string-like types) and Handler's param type. 85 * @tparam Handler Automatically deduced. Must be a callable taking a 86 * single parameter whose type matches the property. 87 * 88 * @param[in] service D-Bus service name. 89 * @param[in] object D-Bus object name. 90 * @param[in] interface D-Bus interface name. 91 * @param[in] propertyName D-Bus property name. 92 * @param[in] handler Callable to be called immediately and upon any 93 * changes in the property value. 94 * @param[out] propertiesChangedMatch Optional pointer to receive a D-Bus 95 * match object, if you need to manage its 96 * lifetime. 97 * @param[out] interfacesAddedMatch Optional pointer to receive a D-Bus 98 * match object, if you need to manage its 99 * lifetime. 100 */ 101 template <typename... CustomVariantTypes, typename Handler> 102 static void subscribeToProperty( 103 const char* service, const char* object, const char* interface, 104 const char* propertyName, Handler&& handler, 105 sdbusplus::bus::match_t** propertiesChangedMatch = nullptr, 106 sdbusplus::bus::match_t** interfacesAddedMatch = nullptr) 107 { 108 // Type of first parameter to Handler, with const/& removed 109 using PropertyType = std::remove_const_t<std::remove_reference_t< 110 std::tuple_element_t<0, boost::callable_traits::args_t<Handler>>>>; 111 // Base data types which we can handle by default 112 using InterfaceVariant = typename sdbusplus::utility::dedup_variant< 113 PropertyType, CustomVariantTypes..., bool, uint8_t, uint16_t, int16_t, 114 uint32_t, int32_t, uint64_t, int64_t, size_t, ssize_t, double, 115 std::string, sdbusplus::message::object_path>; 116 117 sdbusplus::asio::getProperty<PropertyType>( 118 *dbusConn, service, object, interface, propertyName, 119 [handler, propertyName = std::string(propertyName)]( 120 boost::system::error_code ec, const PropertyType& newValue) { 121 if (ec) 122 { 123 std::cerr << "Failed to read property " << propertyName << ": " 124 << ec << "\n"; 125 return; 126 } 127 handler(newValue); 128 }); 129 130 using ChangedPropertiesType = 131 std::vector<std::pair<std::string, InterfaceVariant>>; 132 133 // Define some logic which is common to the two match callbacks, since they 134 // both have to loop through all the properties in the interface. 135 auto commonPropHandler = [propertyName = std::string(propertyName), 136 handler = std::forward<Handler>(handler)]( 137 const ChangedPropertiesType& changedProps) { 138 for (const auto& [changedProp, newValue] : changedProps) 139 { 140 if (changedProp == propertyName) 141 { 142 const auto* actualVal = std::get_if<PropertyType>(&newValue); 143 if (actualVal != nullptr) 144 { 145 DEBUG_PRINT << "New value for " << propertyName << ": " 146 << *actualVal << "\n"; 147 handler(*actualVal); 148 } 149 else 150 { 151 std::cerr << "Property " << propertyName 152 << " had unexpected type\n"; 153 } 154 break; 155 } 156 } 157 }; 158 159 // Set up a match for PropertiesChanged signal 160 auto* propMatch = new sdbusplus::bus::match_t( 161 *dbusConn, 162 sdbusplus::bus::match::rules::sender(service) + 163 sdbusplus::bus::match::rules::propertiesChanged(object, interface), 164 [commonPropHandler](sdbusplus::message::message& reply) { 165 ChangedPropertiesType changedProps; 166 // ignore first param (interface name), it has to be correct 167 reply.read(std::string(), changedProps); 168 169 DEBUG_PRINT << "PropertiesChanged handled\n"; 170 commonPropHandler(changedProps); 171 }); 172 173 // Set up a match for the InterfacesAdded signal from the service's 174 // ObjectManager. This is useful in the case where the object is not added 175 // yet, and when it's added they choose to not emit PropertiesChanged. So in 176 // order to see the initial value when it comes, we need to watch this too. 177 auto* intfMatch = new sdbusplus::bus::match_t( 178 *dbusConn, 179 sdbusplus::bus::match::rules::sender(service) + 180 sdbusplus::bus::match::rules::interfacesAdded(), 181 [object = std::string(object), interface = std::string(interface), 182 commonPropHandler](sdbusplus::message::message& reply) { 183 sdbusplus::message::object_path changedObject; 184 reply.read(changedObject); 185 if (changedObject != object) 186 { 187 return; 188 } 189 190 std::vector<std::pair<std::string, ChangedPropertiesType>> 191 changedInterfaces; 192 reply.read(changedInterfaces); 193 194 for (const auto& [changedInterface, changedProps] : 195 changedInterfaces) 196 { 197 if (changedInterface != interface) 198 { 199 continue; 200 } 201 202 DEBUG_PRINT << "InterfacesAdded handled\n"; 203 commonPropHandler(changedProps); 204 } 205 }); 206 207 if (propertiesChangedMatch != nullptr) 208 { 209 *propertiesChangedMatch = propMatch; 210 } 211 212 if (interfacesAddedMatch != nullptr) 213 { 214 *interfacesAddedMatch = intfMatch; 215 } 216 } 217 218 void hostStateSetup(const std::shared_ptr<sdbusplus::asio::connection>& conn) 219 { 220 static bool initialized = false; 221 if (initialized) 222 { 223 return; 224 } 225 226 dbusConn = conn; 227 228 // Leak the returned match objects. We want them to run forever. 229 subscribeToProperty( 230 "xyz.openbmc_project.State.Host", "/xyz/openbmc_project/state/host0", 231 State::server::Host::interface, "CurrentHostState", updatePowerState); 232 subscribeToProperty("xyz.openbmc_project.Host.Misc.Manager", 233 "/xyz/openbmc_project/misc/platform_state", 234 "xyz.openbmc_project.State.Host.Misc", "CoreBiosDone", 235 updateBiosDone); 236 237 initialized = true; 238 } 239 240 } // namespace cpu_info 241