xref: /openbmc/smbios-mdr/src/cpuinfo_utils.cpp (revision 7190f3a3)
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.Host0",
297                         "/xyz/openbmc_project/state/host0",
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