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