xref: /openbmc/smbios-mdr/src/cpuinfo_utils.cpp (revision 77b9c478)
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