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