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 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 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 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 148 std::string SoftwareManager::getBusName() 149 { 150 return serviceName; 151 } 152 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 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 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 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