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
addHostStateCallback(HostStateHandler cb)43 void addHostStateCallback(HostStateHandler cb)
44 {
45 hostStateCallbacks.push_back(cb);
46 }
47
updateHostState()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
updatePowerState(const std::string & newState)87 void updatePowerState(const std::string& newState)
88 {
89 powerState = state::Host::convertHostStateFromString(newState);
90 updateHostState();
91 }
92
updateBiosDone(bool newState)93 void updateBiosDone(bool newState)
94 {
95 biosDone = newState;
96 updateHostState();
97 }
98
updateOsState(const std::string & newState)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>
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)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] :
251 changedInterfaces)
252 {
253 if (changedInterface != interface)
254 {
255 continue;
256 }
257
258 DEBUG_PRINT << "InterfacesAdded handled\n";
259 commonPropHandler(changedProps);
260 }
261 });
262
263 if (propertiesChangedMatch != nullptr)
264 {
265 *propertiesChangedMatch = propMatch;
266 }
267
268 if (interfacesAddedMatch != nullptr)
269 {
270 *interfacesAddedMatch = intfMatch;
271 }
272 }
273
hostStateSetup(const std::shared_ptr<sdbusplus::asio::connection> & conn)274 void hostStateSetup(const std::shared_ptr<sdbusplus::asio::connection>& conn)
275 {
276 static bool initialized = false;
277 if (initialized)
278 {
279 return;
280 }
281
282 dbusConn = conn;
283
284 // Leak the returned match objects. We want them to run forever.
285 subscribeToProperty(
286 "xyz.openbmc_project.State.Host", "/xyz/openbmc_project/state/host0",
287 state::Host::interface, "CurrentHostState", updatePowerState);
288 subscribeToProperty("xyz.openbmc_project.Host.Misc.Manager",
289 "/xyz/openbmc_project/misc/platform_state",
290 "xyz.openbmc_project.State.Host.Misc", "CoreBiosDone",
291 updateBiosDone);
292 // xyz.openbmc_project.Host.Misc.Manager has Intel specific dependencies.
293 // If it is not available, then we can use the OperatingSystemState in
294 // xyz.openbmc_project.State.OperatingSystem. According to x86-power-control
295 // repo, OperatingSystemState should return "standby" once the POST is
296 // asserted.
297 subscribeToProperty("xyz.openbmc_project.State.Host0",
298 "/xyz/openbmc_project/state/host0",
299 "xyz.openbmc_project.State.OperatingSystem.Status",
300 "OperatingSystemState", updateOsState);
301
302 initialized = true;
303 }
304
305 namespace dbus
306 {
getIOContext()307 boost::asio::io_context& getIOContext()
308 {
309 static boost::asio::io_context ioc;
310 return ioc;
311 }
getConnection()312 std::shared_ptr<sdbusplus::asio::connection> getConnection()
313 {
314 static auto conn =
315 std::make_shared<sdbusplus::asio::connection>(getIOContext());
316 return conn;
317 }
318 } // namespace dbus
319 } // namespace cpu_info
320