blob: 217345845b78601ec07fc0d89b75265e522956e1 [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),
34 serviceNameSuffix(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{
38 const std::string serviceNameFull =
39 "xyz.openbmc_project.Software." + serviceNameSuffix;
40
41 debug("requesting dbus name {BUSNAME}", "BUSNAME", serviceNameFull);
42
Alexander Hansende5e76f2025-02-20 16:30:11 +010043 ctx.request_name(serviceNameFull.c_str());
Alexander Hansencc372352025-01-14 14:15:39 +010044
Alexander Hansende5e76f2025-02-20 16:30:11 +010045 debug("Initialized SoftwareManager");
Alexander Hansencc372352025-01-14 14:15:39 +010046}
47
48// NOLINTBEGIN(readability-static-accessed-through-instance)
Alexander Hansenf3d407b2025-05-08 09:52:31 +020049static sdbusplus::async::task<std::optional<SoftwareConfig>> getConfig(
50 sdbusplus::async::context& ctx, const std::string& service,
51 const std::string& objectPath, const std::string& interfacePrefix)
52// NOLINTEND(readability-static-accessed-through-instance)
53{
54 auto client = sdbusplus::async::proxy()
55 .service(service)
56 .path(objectPath)
57 .interface("org.freedesktop.DBus.Properties");
58
59 uint64_t vendorIANA = 0;
60 std::string compatible{};
61 std::string configType{};
62 std::string configName{};
63
64 const std::string interfaceName = interfacePrefix + ".FirmwareInfo";
65
66 try
67 {
68 {
69 auto propVendorIANA = co_await client.call<std::variant<uint64_t>>(
70 ctx, "Get", interfaceName, "VendorIANA");
71
72 vendorIANA = std::get<uint64_t>(propVendorIANA);
73 }
74 {
75 auto propCompatible =
76 co_await client.call<std::variant<std::string>>(
77 ctx, "Get", interfaceName, "CompatibleHardware");
78
79 compatible = std::get<std::string>(propCompatible);
80 }
81 {
82 auto propConfigType =
83 co_await client.call<std::variant<std::string>>(
84 ctx, "Get", interfacePrefix, "Type");
85
86 configType = std::get<std::string>(propConfigType);
87 }
88 {
89 auto propConfigName =
90 co_await client.call<std::variant<std::string>>(
91 ctx, "Get", interfacePrefix, "Name");
92
93 configName = std::get<std::string>(propConfigName);
94 }
95 }
96 catch (std::exception& e)
97 {
98 error("Failed to get config with {ERROR}", "ERROR", e);
99 co_return std::nullopt;
100 }
101
102 co_return SoftwareConfig(objectPath, vendorIANA, compatible, configType,
103 configName);
104}
105
106// NOLINTBEGIN(readability-static-accessed-through-instance)
Alexander Hansencc372352025-01-14 14:15:39 +0100107sdbusplus::async::task<> SoftwareManager::initDevices(
108 const std::vector<std::string>& configurationInterfaces)
109// NOLINTEND(readability-static-accessed-through-instance)
110{
Alexander Hansencec14752025-05-08 13:11:03 +0200111 ctx.spawn(interfaceAddedMatch(configurationInterfaces));
112 ctx.spawn(interfaceRemovedMatch(configurationInterfaces));
113
Alexander Hansencc372352025-01-14 14:15:39 +0100114 auto client = sdbusplus::client::xyz::openbmc_project::ObjectMapper<>(ctx)
115 .service("xyz.openbmc_project.ObjectMapper")
116 .path("/xyz/openbmc_project/object_mapper");
117
Alexander Hansen0a457ff2025-02-25 16:13:47 +0100118 auto res = co_await client.get_sub_tree("/xyz/openbmc_project/inventory", 0,
119 configurationInterfaces);
Alexander Hansencc372352025-01-14 14:15:39 +0100120
121 for (auto& iface : configurationInterfaces)
122 {
123 debug("[config] looking for dbus interface {INTF}", "INTF", iface);
124 }
125
126 for (auto& [path, v] : res)
127 {
128 for (auto& [service, interfaceNames] : v)
129 {
130 std::string interfaceFound;
131
132 for (std::string& interfaceName : interfaceNames)
133 {
134 for (auto& iface : configurationInterfaces)
135 {
136 if (interfaceName == iface)
137 {
138 interfaceFound = interfaceName;
139 }
140 }
141 }
142
143 if (interfaceFound.empty())
144 {
145 continue;
146 }
147
Alexander Hansen90174792025-05-08 10:14:54 +0200148 co_await handleInterfaceAdded(service, path, interfaceFound);
Alexander Hansencc372352025-01-14 14:15:39 +0100149 }
150 }
151
Alexander Hansenf3d407b2025-05-08 09:52:31 +0200152 debug("Done with initial configuration");
Alexander Hansencc372352025-01-14 14:15:39 +0100153}
Alexander Hansen90174792025-05-08 10:14:54 +0200154
155// NOLINTBEGIN(readability-static-accessed-through-instance)
156sdbusplus::async::task<void> SoftwareManager::handleInterfaceAdded(
157 const std::string& service, const std::string& path,
158 const std::string& interface)
159// NOLINTEND(readability-static-accessed-through-instance)
160{
161 debug("Found configuration interface at {SERVICE}, {PATH}", "SERVICE",
162 service, "PATH", path);
163
164 auto optConfig = co_await getConfig(ctx, service, path, interface);
165
166 if (!optConfig.has_value())
167 {
168 error("Failed to get configuration from {PATH}", "PATH", path);
169 co_return;
170 }
171
Alexander Hansencec14752025-05-08 13:11:03 +0200172 if (devices.contains(optConfig.value().objectPath))
173 {
174 error("Device configured from {PATH} is already known", "PATH",
175 optConfig.value().objectPath);
176 co_return;
177 }
178
Alexander Hansen90174792025-05-08 10:14:54 +0200179 co_await initDevice(service, path, optConfig.value());
180
181 co_return;
182}
Alexander Hansencec14752025-05-08 13:11:03 +0200183
184using BasicVariantType =
185 std::variant<std::vector<std::string>, std::string, int64_t, uint64_t,
186 double, int32_t, uint32_t, int16_t, uint16_t, uint8_t, bool>;
187using InterfacesMap = boost::container::flat_map<std::string, BasicVariantType>;
188using ConfigMap = boost::container::flat_map<std::string, InterfacesMap>;
189
190// NOLINTBEGIN(readability-static-accessed-through-instance)
191sdbusplus::async::task<void> SoftwareManager::interfaceAddedMatch(
192 std::vector<std::string> interfaces)
193// NOLINTEND(readability-static-accessed-through-instance)
194{
195 while (!ctx.stop_requested())
196 {
197 std::tuple<std::string, ConfigMap> nextResult("", {});
198 nextResult = co_await configIntfAddedMatch
199 .next<sdbusplus::message::object_path, ConfigMap>();
200
201 auto& [objPath, interfacesMap] = nextResult;
202
203 for (auto& interface : interfaces)
204 {
205 if (interfacesMap.contains(interface))
206 {
207 debug("detected interface {INTF} added on {PATH}", "INTF",
208 interface, "PATH", objPath);
209
210 co_await handleInterfaceAdded(serviceNameEM, objPath,
211 interface);
212 }
213 }
214 }
215}
216
217// NOLINTBEGIN(readability-static-accessed-through-instance)
218sdbusplus::async::task<void> SoftwareManager::interfaceRemovedMatch(
219 std::vector<std::string> interfaces)
220// NOLINTEND(readability-static-accessed-through-instance)
221{
222 while (!ctx.stop_requested())
223 {
224 auto nextResult = co_await configIntfRemovedMatch.next<
225 sdbusplus::message::object_path, std::vector<std::string>>();
226
227 auto& [objPath, interfacesRemoved] = nextResult;
228
229 debug("detected interface removed on {PATH}", "PATH", objPath);
230
231 for (auto& interface : interfaces)
232 {
233 if (std::ranges::find(interfacesRemoved, interface) !=
234 interfacesRemoved.end())
235 {
236 debug("detected interface {INTF} removed on {PATH}", "INTF",
237 interface, "PATH", objPath);
238 co_await handleInterfaceRemoved(objPath);
239 }
240 }
241 }
242}
243
244sdbusplus::async::task<void> SoftwareManager::handleInterfaceRemoved(
245 const sdbusplus::message::object_path& objPath)
246{
247 if (!devices.contains(objPath))
248 {
249 debug("could not find a device to remove");
250 co_return;
251 }
252
253 if (devices[objPath]->updateInProgress)
254 {
255 // TODO: This code path needs to be cleaned up in the future to
256 // eventually remove the device.
257 debug(
258 "removal of device at {PATH} ignored because of in-progress update",
259 "PATH", objPath.str);
260 co_return;
261 }
262
263 debug("removing device at {PATH}", "PATH", objPath.str);
264 devices.erase(objPath);
265}