xref: /openbmc/phosphor-bmc-code-mgmt/common/src/software_manager.cpp (revision 2e168dba1029d1c5baaf065ad7306150dd3cafc2)
1 #include "software_manager.hpp"
2 
3 #include <boost/container/flat_map.hpp>
4 #include <phosphor-logging/lg2.hpp>
5 #include <sdbusplus/asio/object_server.hpp>
6 #include <sdbusplus/async.hpp>
7 #include <sdbusplus/async/context.hpp>
8 #include <sdbusplus/bus.hpp>
9 #include <sdbusplus/bus/match.hpp>
10 #include <xyz/openbmc_project/Association/Definitions/server.hpp>
11 #include <xyz/openbmc_project/ObjectMapper/client.hpp>
12 #include <xyz/openbmc_project/Software/Version/client.hpp>
13 #include <xyz/openbmc_project/State/Host/client.hpp>
14 
15 #include <cstdint>
16 
17 PHOSPHOR_LOG2_USING;
18 
19 using namespace phosphor::software::manager;
20 
21 using AsyncMatch = sdbusplus::async::match;
22 
23 namespace RulesIntf = sdbusplus::bus::match::rules;
24 static constexpr auto serviceNameEM = "xyz.openbmc_project.EntityManager";
25 
26 const auto matchRuleSender = RulesIntf::sender(serviceNameEM);
27 const auto matchRulePath = RulesIntf::path("/xyz/openbmc_project/inventory");
28 
29 SoftwareManager::SoftwareManager(sdbusplus::async::context& ctx,
30                                  const std::string& serviceNameSuffix) :
31     ctx(ctx),
32     configIntfAddedMatch(ctx, RulesIntf::interfacesAdded() + matchRuleSender),
33     configIntfRemovedMatch(ctx, RulesIntf::interfacesRemoved() + matchRulePath),
34     serviceNameSuffix(serviceNameSuffix),
35     manager(ctx, sdbusplus::client::xyz::openbmc_project::software::Version<>::
36                      namespace_path)
37 {
38     const std::string serviceNameFull =
39         "xyz.openbmc_project.Software." + serviceNameSuffix;
40 
41     debug("requesting dbus name {BUSNAME}", "BUSNAME", serviceNameFull);
42 
43     ctx.request_name(serviceNameFull.c_str());
44 
45     debug("Initialized SoftwareManager");
46 }
47 
48 static sdbusplus::async::task<std::optional<SoftwareConfig>> getConfig(
49     sdbusplus::async::context& ctx, const std::string& service,
50     const std::string& objectPath, const std::string& interfacePrefix)
51 {
52     auto client = sdbusplus::async::proxy()
53                       .service(service)
54                       .path(objectPath)
55                       .interface("org.freedesktop.DBus.Properties");
56 
57     uint64_t vendorIANA = 0;
58     std::string compatible{};
59     std::string configType{};
60     std::string configName{};
61 
62     const std::string interfaceName = interfacePrefix + ".FirmwareInfo";
63 
64     try
65     {
66         {
67             auto propVendorIANA = co_await client.call<std::variant<uint64_t>>(
68                 ctx, "Get", interfaceName, "VendorIANA");
69 
70             vendorIANA = std::get<uint64_t>(propVendorIANA);
71         }
72         {
73             auto propCompatible =
74                 co_await client.call<std::variant<std::string>>(
75                     ctx, "Get", interfaceName, "CompatibleHardware");
76 
77             compatible = std::get<std::string>(propCompatible);
78         }
79         {
80             auto propConfigType =
81                 co_await client.call<std::variant<std::string>>(
82                     ctx, "Get", interfacePrefix, "Type");
83 
84             configType = std::get<std::string>(propConfigType);
85         }
86         {
87             auto propConfigName =
88                 co_await client.call<std::variant<std::string>>(
89                     ctx, "Get", interfacePrefix, "Name");
90 
91             configName = std::get<std::string>(propConfigName);
92         }
93     }
94     catch (std::exception& e)
95     {
96         error("Failed to get config with {ERROR}", "ERROR", e);
97         co_return std::nullopt;
98     }
99 
100     co_return SoftwareConfig(objectPath, vendorIANA, compatible, configType,
101                              configName);
102 }
103 
104 sdbusplus::async::task<> SoftwareManager::initDevices(
105     const std::vector<std::string>& configurationInterfaces)
106 {
107     ctx.spawn(interfaceAddedMatch(configurationInterfaces));
108     ctx.spawn(interfaceRemovedMatch(configurationInterfaces));
109 
110     auto client = sdbusplus::client::xyz::openbmc_project::ObjectMapper<>(ctx)
111                       .service("xyz.openbmc_project.ObjectMapper")
112                       .path("/xyz/openbmc_project/object_mapper");
113 
114     auto res = co_await client.get_sub_tree("/xyz/openbmc_project/inventory", 0,
115                                             configurationInterfaces);
116 
117     for (auto& iface : configurationInterfaces)
118     {
119         debug("[config] looking for dbus interface {INTF}", "INTF", iface);
120     }
121 
122     for (auto& [path, v] : res)
123     {
124         for (auto& [service, interfaceNames] : v)
125         {
126             std::string interfaceFound;
127 
128             for (std::string& interfaceName : interfaceNames)
129             {
130                 for (auto& iface : configurationInterfaces)
131                 {
132                     if (interfaceName == iface)
133                     {
134                         interfaceFound = interfaceName;
135                     }
136                 }
137             }
138 
139             if (interfaceFound.empty())
140             {
141                 continue;
142             }
143 
144             co_await handleInterfaceAdded(service, path, interfaceFound);
145         }
146     }
147 
148     debug("Done with initial configuration");
149 }
150 
151 sdbusplus::async::task<void> SoftwareManager::handleInterfaceAdded(
152     const std::string& service, const std::string& path,
153     const std::string& interface)
154 {
155     debug("Found configuration interface at {SERVICE}, {PATH}", "SERVICE",
156           service, "PATH", path);
157 
158     auto optConfig = co_await getConfig(ctx, service, path, interface);
159 
160     if (!optConfig.has_value())
161     {
162         error("Failed to get configuration from {PATH}", "PATH", path);
163         co_return;
164     }
165 
166     auto& config = optConfig.value();
167 
168     if (devices.contains(config.objectPath))
169     {
170         error("Device configured from {PATH} is already known", "PATH",
171               config.objectPath);
172         co_return;
173     }
174 
175     const bool accepted = co_await initDevice(service, path, config);
176 
177     if (accepted && devices.contains(config.objectPath))
178     {
179         auto& device = devices[config.objectPath];
180 
181         if (device->softwareCurrent)
182         {
183             co_await device->softwareCurrent->createInventoryAssociations(true);
184 
185             device->softwareCurrent->setActivation(
186                 SoftwareActivation::Activations::Active);
187         }
188     }
189 
190     co_return;
191 }
192 
193 using BasicVariantType =
194     std::variant<std::vector<std::string>, std::string, int64_t, uint64_t,
195                  double, int32_t, uint32_t, int16_t, uint16_t, uint8_t, bool>;
196 using InterfacesMap = boost::container::flat_map<std::string, BasicVariantType>;
197 using ConfigMap = boost::container::flat_map<std::string, InterfacesMap>;
198 
199 sdbusplus::async::task<void> SoftwareManager::interfaceAddedMatch(
200     std::vector<std::string> interfaces)
201 {
202     while (!ctx.stop_requested())
203     {
204         std::tuple<std::string, ConfigMap> nextResult("", {});
205         nextResult = co_await configIntfAddedMatch
206                          .next<sdbusplus::message::object_path, ConfigMap>();
207 
208         auto& [objPath, interfacesMap] = nextResult;
209 
210         for (auto& interface : interfaces)
211         {
212             if (interfacesMap.contains(interface))
213             {
214                 debug("detected interface {INTF} added on {PATH}", "INTF",
215                       interface, "PATH", objPath);
216 
217                 co_await handleInterfaceAdded(serviceNameEM, objPath,
218                                               interface);
219             }
220         }
221     }
222 }
223 
224 sdbusplus::async::task<void> SoftwareManager::interfaceRemovedMatch(
225     std::vector<std::string> interfaces)
226 {
227     while (!ctx.stop_requested())
228     {
229         auto nextResult = co_await configIntfRemovedMatch.next<
230             sdbusplus::message::object_path, std::vector<std::string>>();
231 
232         auto& [objPath, interfacesRemoved] = nextResult;
233 
234         debug("detected interface removed on {PATH}", "PATH", objPath);
235 
236         for (auto& interface : interfaces)
237         {
238             if (std::ranges::find(interfacesRemoved, interface) !=
239                 interfacesRemoved.end())
240             {
241                 debug("detected interface {INTF} removed on {PATH}", "INTF",
242                       interface, "PATH", objPath);
243                 co_await handleInterfaceRemoved(objPath);
244             }
245         }
246     }
247 }
248 
249 sdbusplus::async::task<void> SoftwareManager::handleInterfaceRemoved(
250     const sdbusplus::message::object_path& objPath)
251 {
252     if (!devices.contains(objPath))
253     {
254         debug("could not find a device to remove");
255         co_return;
256     }
257 
258     if (devices[objPath]->updateInProgress)
259     {
260         // TODO: This code path needs to be cleaned up in the future to
261         // eventually remove the device.
262         debug(
263             "removal of device at {PATH} ignored because of in-progress update",
264             "PATH", objPath.str);
265         co_return;
266     }
267 
268     debug("removing device at {PATH}", "PATH", objPath.str);
269     devices.erase(objPath);
270 }
271