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