blob: 3ef4a51e092633b19b35ead28f419a9a2e175eaf [file] [log] [blame]
Alexander Hansencc372352025-01-14 14:15:39 +01001#include "software_manager.hpp"
2
Alexander Hansencec14752025-05-08 13:11:03 +02003#include <boost/container/flat_map.hpp>
Alexander Hansencc372352025-01-14 14:15:39 +01004#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>
Alexander Hansencec14752025-05-08 13:11:03 +02009#include <sdbusplus/bus/match.hpp>
Alexander Hansencc372352025-01-14 14:15:39 +010010#include <xyz/openbmc_project/Association/Definitions/server.hpp>
11#include <xyz/openbmc_project/ObjectMapper/client.hpp>
Alexander Hansende5e76f2025-02-20 16:30:11 +010012#include <xyz/openbmc_project/Software/Version/client.hpp>
Alexander Hansencc372352025-01-14 14:15:39 +010013#include <xyz/openbmc_project/State/Host/client.hpp>
14
15#include <cstdint>
16
17PHOSPHOR_LOG2_USING;
18
19using namespace phosphor::software::manager;
20
Alexander Hansencec14752025-05-08 13:11:03 +020021using AsyncMatch = sdbusplus::async::match;
22
23namespace RulesIntf = sdbusplus::bus::match::rules;
24static constexpr auto serviceNameEM = "xyz.openbmc_project.EntityManager";
25
26const auto matchRuleSender = RulesIntf::sender(serviceNameEM);
27const auto matchRulePath = RulesIntf::path("/xyz/openbmc_project/inventory");
28
Alexander Hansencc372352025-01-14 14:15:39 +010029SoftwareManager::SoftwareManager(sdbusplus::async::context& ctx,
30 const std::string& serviceNameSuffix) :
Alexander Hansencec14752025-05-08 13:11:03 +020031 ctx(ctx),
32 configIntfAddedMatch(ctx, RulesIntf::interfacesAdded() + matchRuleSender),
33 configIntfRemovedMatch(ctx, RulesIntf::interfacesRemoved() + matchRulePath),
Alexander Hansene2cd6752025-08-15 12:44:51 +020034 serviceName("xyz.openbmc_project.Software." + serviceNameSuffix),
Alexander Hansende5e76f2025-02-20 16:30:11 +010035 manager(ctx, sdbusplus::client::xyz::openbmc_project::software::Version<>::
36 namespace_path)
Alexander Hansencc372352025-01-14 14:15:39 +010037{
Alexander Hansene2cd6752025-08-15 12:44:51 +020038 debug("requesting dbus name {BUSNAME}", "BUSNAME", serviceName);
Alexander Hansencc372352025-01-14 14:15:39 +010039
Alexander Hansene2cd6752025-08-15 12:44:51 +020040 ctx.request_name(serviceName.c_str());
Alexander Hansencc372352025-01-14 14:15:39 +010041
Alexander Hansende5e76f2025-02-20 16:30:11 +010042 debug("Initialized SoftwareManager");
Alexander Hansencc372352025-01-14 14:15:39 +010043}
44
Alexander Hansenf3d407b2025-05-08 09:52:31 +020045static 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)
Alexander Hansenf3d407b2025-05-08 09:52:31 +020048{
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
Alexander Hansencc372352025-01-14 14:15:39 +0100101sdbusplus::async::task<> SoftwareManager::initDevices(
102 const std::vector<std::string>& configurationInterfaces)
Alexander Hansencc372352025-01-14 14:15:39 +0100103{
Alexander Hansencec14752025-05-08 13:11:03 +0200104 ctx.spawn(interfaceAddedMatch(configurationInterfaces));
105 ctx.spawn(interfaceRemovedMatch(configurationInterfaces));
106
Alexander Hansencc372352025-01-14 14:15:39 +0100107 auto client = sdbusplus::client::xyz::openbmc_project::ObjectMapper<>(ctx)
108 .service("xyz.openbmc_project.ObjectMapper")
109 .path("/xyz/openbmc_project/object_mapper");
110
Alexander Hansen0a457ff2025-02-25 16:13:47 +0100111 auto res = co_await client.get_sub_tree("/xyz/openbmc_project/inventory", 0,
112 configurationInterfaces);
Alexander Hansencc372352025-01-14 14:15:39 +0100113
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
Alexander Hansen90174792025-05-08 10:14:54 +0200141 co_await handleInterfaceAdded(service, path, interfaceFound);
Alexander Hansencc372352025-01-14 14:15:39 +0100142 }
143 }
144
Alexander Hansenf3d407b2025-05-08 09:52:31 +0200145 debug("Done with initial configuration");
Alexander Hansencc372352025-01-14 14:15:39 +0100146}
Alexander Hansen90174792025-05-08 10:14:54 +0200147
Alexander Hansene2cd6752025-08-15 12:44:51 +0200148std::string SoftwareManager::getBusName()
149{
150 return serviceName;
151}
152
Alexander Hansen90174792025-05-08 10:14:54 +0200153sdbusplus::async::task<void> SoftwareManager::handleInterfaceAdded(
154 const std::string& service, const std::string& path,
155 const std::string& interface)
Alexander Hansen90174792025-05-08 10:14:54 +0200156{
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
Alexander Hansend73d5642025-07-23 14:05:06 +0200168 auto& config = optConfig.value();
169
170 if (devices.contains(config.objectPath))
Alexander Hansencec14752025-05-08 13:11:03 +0200171 {
172 error("Device configured from {PATH} is already known", "PATH",
Alexander Hansend73d5642025-07-23 14:05:06 +0200173 config.objectPath);
Alexander Hansencec14752025-05-08 13:11:03 +0200174 co_return;
175 }
176
Alexander Hansend73d5642025-07-23 14:05:06 +0200177 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);
Alexander Hansen0fdb6122025-07-09 14:33:13 +0200186
187 device->softwareCurrent->setActivation(
188 SoftwareActivation::Activations::Active);
Alexander Hansend73d5642025-07-23 14:05:06 +0200189 }
190 }
Alexander Hansen90174792025-05-08 10:14:54 +0200191
192 co_return;
193}
Alexander Hansencec14752025-05-08 13:11:03 +0200194
195using 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>;
198using InterfacesMap = boost::container::flat_map<std::string, BasicVariantType>;
199using ConfigMap = boost::container::flat_map<std::string, InterfacesMap>;
200
Alexander Hansencec14752025-05-08 13:11:03 +0200201sdbusplus::async::task<void> SoftwareManager::interfaceAddedMatch(
202 std::vector<std::string> interfaces)
Alexander Hansencec14752025-05-08 13:11:03 +0200203{
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
Alexander Hansencec14752025-05-08 13:11:03 +0200226sdbusplus::async::task<void> SoftwareManager::interfaceRemovedMatch(
227 std::vector<std::string> interfaces)
Alexander Hansencec14752025-05-08 13:11:03 +0200228{
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
251sdbusplus::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}