xref: /openbmc/smbios-mdr/src/cpuinfo_utils.cpp (revision 16a2ced3)
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 dbus
292 {
293 boost::asio::io_context& getIOContext()
294 {
295     static boost::asio::io_context ioc;
296     return ioc;
297 }
298 std::shared_ptr<sdbusplus::asio::connection> getConnection()
299 {
300     static auto conn =
301         std::make_shared<sdbusplus::asio::connection>(getIOContext());
302     return conn;
303 }
304 } // namespace dbus
305 } // namespace cpu_info
306