1 #include "MCTPEndpoint.hpp" 2 3 #include "Utils.hpp" 4 #include "VariantVisitors.hpp" 5 6 #include <bits/fs_dir.h> 7 8 #include <boost/system/detail/errc.hpp> 9 #include <phosphor-logging/lg2.hpp> 10 #include <sdbusplus/asio/connection.hpp> 11 #include <sdbusplus/bus.hpp> 12 #include <sdbusplus/bus/match.hpp> 13 #include <sdbusplus/exception.hpp> 14 #include <sdbusplus/message.hpp> 15 #include <sdbusplus/message/native_types.hpp> 16 17 #include <cassert> 18 #include <charconv> 19 #include <cstdint> 20 #include <exception> 21 #include <filesystem> 22 #include <format> 23 #include <functional> 24 #include <map> 25 #include <memory> 26 #include <optional> 27 #include <set> 28 #include <stdexcept> 29 #include <string> 30 #include <system_error> 31 #include <utility> 32 #include <variant> 33 #include <vector> 34 35 PHOSPHOR_LOG2_USING; 36 37 static constexpr const char* mctpdBusName = "xyz.openbmc_project.MCTP"; 38 static constexpr const char* mctpdControlPath = "/xyz/openbmc_project/mctp"; 39 static constexpr const char* mctpdControlInterface = 40 "au.com.CodeConstruct.MCTP"; 41 static constexpr const char* mctpdEndpointControlInterface = 42 "au.com.CodeConstruct.MCTP.Endpoint"; 43 44 MCTPDDevice::MCTPDDevice( 45 const std::shared_ptr<sdbusplus::asio::connection>& connection, 46 const std::string& interface, const std::vector<uint8_t>& physaddr) : 47 connection(connection), interface(interface), physaddr(physaddr) 48 {} 49 50 void MCTPDDevice::onEndpointInterfacesRemoved( 51 const std::weak_ptr<MCTPDDevice>& weak, const std::string& objpath, 52 sdbusplus::message_t& msg) 53 { 54 auto path = msg.unpack<sdbusplus::message::object_path>(); 55 assert(path.str == objpath); 56 57 auto removedIfaces = msg.unpack<std::set<std::string>>(); 58 if (!removedIfaces.contains(mctpdEndpointControlInterface)) 59 { 60 return; 61 } 62 63 if (auto self = weak.lock()) 64 { 65 self->endpointRemoved(); 66 } 67 else 68 { 69 info( 70 "Device for inventory at '{INVENTORY_PATH}' was destroyed concurrent to endpoint removal", 71 "INVENTORY_PATH", objpath); 72 } 73 } 74 75 void MCTPDDevice::finaliseEndpoint( 76 const std::string& objpath, uint8_t eid, int network, 77 std::function<void(const std::error_code& ec, 78 const std::shared_ptr<MCTPEndpoint>& ep)>& added) 79 { 80 const auto matchSpec = 81 sdbusplus::bus::match::rules::interfacesRemovedAtPath(objpath); 82 removeMatch = std::make_unique<sdbusplus::bus::match_t>( 83 *connection, matchSpec, 84 std::bind_front(MCTPDDevice::onEndpointInterfacesRemoved, 85 weak_from_this(), objpath)); 86 endpoint = std::make_shared<MCTPDEndpoint>(shared_from_this(), connection, 87 objpath, network, eid); 88 added({}, endpoint); 89 } 90 91 void MCTPDDevice::setup( 92 std::function<void(const std::error_code& ec, 93 const std::shared_ptr<MCTPEndpoint>& ep)>&& added) 94 { 95 // Use a lambda to separate state validation from business logic, 96 // where the business logic for a successful setup() is encoded in 97 // MctpdDevice::finaliseEndpoint() 98 auto onSetup = [weak{weak_from_this()}, added{std::move(added)}]( 99 const boost::system::error_code& ec, uint8_t eid, 100 int network, const std::string& objpath, 101 bool allocated [[maybe_unused]]) mutable { 102 if (ec) 103 { 104 added(ec, {}); 105 return; 106 } 107 108 if (auto self = weak.lock()) 109 { 110 self->finaliseEndpoint(objpath, eid, network, added); 111 } 112 else 113 { 114 info( 115 "Device object for inventory at '{INVENTORY_PATH}' was destroyed concurrent to completion of its endpoint setup", 116 "INVENTORY_PATH", objpath); 117 } 118 }; 119 connection->async_method_call(onSetup, mctpdBusName, mctpdControlPath, 120 mctpdControlInterface, "AssignEndpoint", 121 interface, physaddr); 122 } 123 124 void MCTPDDevice::endpointRemoved() 125 { 126 if (endpoint) 127 { 128 debug("Endpoint removed @ [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT", 129 endpoint->describe()); 130 removeMatch.reset(); 131 endpoint->removed(); 132 endpoint.reset(); 133 } 134 } 135 136 void MCTPDDevice::remove() 137 { 138 if (endpoint) 139 { 140 debug("Removing endpoint @ [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT", 141 endpoint->describe()); 142 endpoint->remove(); 143 } 144 } 145 146 std::string MCTPDDevice::describe() const 147 { 148 std::string description = std::format("interface: {}", interface); 149 if (!physaddr.empty()) 150 { 151 description.append(", address: 0x [ "); 152 auto it = physaddr.begin(); 153 for (; it != physaddr.end() - 1; it++) 154 { 155 description.append(std::format("{:02x} ", *it)); 156 } 157 description.append(std::format("{:02x} ]", *it)); 158 } 159 return description; 160 } 161 162 std::string MCTPDEndpoint::path(const std::shared_ptr<MCTPEndpoint>& ep) 163 { 164 return std::format("/xyz/openbmc_project/mctp/{}/{}", ep->network(), 165 ep->eid()); 166 } 167 168 void MCTPDEndpoint::onMctpEndpointChange(sdbusplus::message_t& msg) 169 { 170 auto [iface, changed, _] = 171 msg.unpack<std::string, std::map<std::string, BasicVariantType>, 172 std::vector<std::string>>(); 173 if (iface != mctpdEndpointControlInterface) 174 { 175 return; 176 } 177 178 auto it = changed.find("Connectivity"); 179 if (it == changed.end()) 180 { 181 return; 182 } 183 184 updateEndpointConnectivity(std::get<std::string>(it->second)); 185 } 186 187 void MCTPDEndpoint::updateEndpointConnectivity(const std::string& connectivity) 188 { 189 if (connectivity == "Degraded") 190 { 191 if (notifyDegraded) 192 { 193 notifyDegraded(shared_from_this()); 194 } 195 } 196 else if (connectivity == "Available") 197 { 198 if (notifyAvailable) 199 { 200 notifyAvailable(shared_from_this()); 201 } 202 } 203 else 204 { 205 debug("Unrecognised connectivity state: '{CONNECTIVITY_STATE}'", 206 "CONNECTIVITY_STATE", connectivity); 207 } 208 } 209 210 int MCTPDEndpoint::network() const 211 { 212 return mctp.network; 213 } 214 215 uint8_t MCTPDEndpoint::eid() const 216 { 217 return mctp.eid; 218 } 219 220 void MCTPDEndpoint::subscribe(Event&& degraded, Event&& available, 221 Event&& removed) 222 { 223 const auto matchSpec = 224 sdbusplus::bus::match::rules::propertiesChangedNamespace( 225 objpath.str, mctpdEndpointControlInterface); 226 227 this->notifyDegraded = std::move(degraded); 228 this->notifyAvailable = std::move(available); 229 this->notifyRemoved = std::move(removed); 230 231 try 232 { 233 connectivityMatch.emplace( 234 static_cast<sdbusplus::bus_t&>(*connection), matchSpec, 235 [weak{weak_from_this()}, 236 path{objpath.str}](sdbusplus::message_t& msg) { 237 if (auto self = weak.lock()) 238 { 239 self->onMctpEndpointChange(msg); 240 } 241 else 242 { 243 info( 244 "The endpoint for the device at inventory path '{INVENTORY_PATH}' was destroyed concurrent to the removal of its state change match", 245 "INVENTORY_PATH", path); 246 } 247 }); 248 connection->async_method_call( 249 [weak{weak_from_this()}, 250 path{objpath.str}](const boost::system::error_code& ec, 251 const std::variant<std::string>& value) { 252 if (ec) 253 { 254 debug( 255 "Failed to get current connectivity state: {ERROR_MESSAGE}", 256 "ERROR_MESSAGE", ec.message(), "ERROR_CATEGORY", 257 ec.category().name(), "ERROR_CODE", ec.value()); 258 return; 259 } 260 261 if (auto self = weak.lock()) 262 { 263 const std::string& connectivity = 264 std::get<std::string>(value); 265 self->updateEndpointConnectivity(connectivity); 266 } 267 else 268 { 269 info( 270 "The endpoint for the device at inventory path '{INVENTORY_PATH}' was destroyed concurrent to the completion of its connectivity state query", 271 "INVENTORY_PATH", path); 272 } 273 }, 274 mctpdBusName, objpath.str, "org.freedesktop.DBus.Properties", "Get", 275 mctpdEndpointControlInterface, "Connectivity"); 276 } 277 catch (const sdbusplus::exception::SdBusError& err) 278 { 279 this->notifyDegraded = nullptr; 280 this->notifyAvailable = nullptr; 281 this->notifyRemoved = nullptr; 282 std::throw_with_nested( 283 MCTPException("Failed to register connectivity signal match")); 284 } 285 } 286 287 void MCTPDEndpoint::remove() 288 { 289 connection->async_method_call( 290 [self{shared_from_this()}](const boost::system::error_code& ec) { 291 if (ec) 292 { 293 debug("Failed to remove endpoint @ [ {MCTP_ENDPOINT} ]", 294 "MCTP_ENDPOINT", self->describe()); 295 return; 296 } 297 }, 298 mctpdBusName, objpath.str, mctpdEndpointControlInterface, "Remove"); 299 } 300 301 void MCTPDEndpoint::removed() 302 { 303 if (notifyRemoved) 304 { 305 notifyRemoved(shared_from_this()); 306 } 307 } 308 309 std::string MCTPDEndpoint::describe() const 310 { 311 return std::format("network: {}, EID: {} | {}", mctp.network, mctp.eid, 312 dev->describe()); 313 } 314 315 std::shared_ptr<MCTPDevice> MCTPDEndpoint::device() const 316 { 317 return dev; 318 } 319 320 std::optional<SensorBaseConfigMap> 321 I2CMCTPDDevice::match(const SensorData& config) 322 { 323 auto iface = config.find(configInterfaceName(configType)); 324 if (iface == config.end()) 325 { 326 return std::nullopt; 327 } 328 return iface->second; 329 } 330 331 bool I2CMCTPDDevice::match(const std::set<std::string>& interfaces) 332 { 333 return interfaces.contains(configInterfaceName(configType)); 334 } 335 336 std::shared_ptr<I2CMCTPDDevice> I2CMCTPDDevice::from( 337 const std::shared_ptr<sdbusplus::asio::connection>& connection, 338 const SensorBaseConfigMap& iface) 339 { 340 auto mType = iface.find("Type"); 341 if (mType == iface.end()) 342 { 343 throw std::invalid_argument( 344 "No 'Type' member found for provided configuration object"); 345 } 346 347 auto type = std::visit(VariantToStringVisitor(), mType->second); 348 if (type != configType) 349 { 350 throw std::invalid_argument("Not an SMBus device"); 351 } 352 353 auto mAddress = iface.find("Address"); 354 auto mBus = iface.find("Bus"); 355 auto mName = iface.find("Name"); 356 if (mAddress == iface.end() || mBus == iface.end() || mName == iface.end()) 357 { 358 throw std::invalid_argument( 359 "Configuration object violates MCTPI2CTarget schema"); 360 } 361 362 auto sAddress = std::visit(VariantToStringVisitor(), mAddress->second); 363 std::uint8_t address{}; 364 auto [aptr, aec] = std::from_chars( 365 sAddress.data(), sAddress.data() + sAddress.size(), address); 366 if (aec != std::errc{}) 367 { 368 throw std::invalid_argument("Bad device address"); 369 } 370 371 auto sBus = std::visit(VariantToStringVisitor(), mBus->second); 372 int bus{}; 373 auto [bptr, 374 bec] = std::from_chars(sBus.data(), sBus.data() + sBus.size(), bus); 375 if (bec != std::errc{}) 376 { 377 throw std::invalid_argument("Bad bus index"); 378 } 379 380 try 381 { 382 return std::make_shared<I2CMCTPDDevice>(connection, bus, address); 383 } 384 catch (const MCTPException& ex) 385 { 386 warning( 387 "Failed to create I2CMCTPDDevice at [ bus: {I2C_BUS}, address: {I2C_ADDRESS} ]: {EXCEPTION}", 388 "I2C_BUS", bus, "I2C_ADDRESS", address, "EXCEPTION", ex); 389 return {}; 390 } 391 } 392 393 std::string I2CMCTPDDevice::interfaceFromBus(int bus) 394 { 395 std::filesystem::path netdir = 396 std::format("/sys/bus/i2c/devices/i2c-{}/net", bus); 397 std::error_code ec; 398 std::filesystem::directory_iterator it(netdir, ec); 399 if (ec || it == std::filesystem::end(it)) 400 { 401 error("No net device associated with I2C bus {I2C_BUS} at {NET_DEVICE}", 402 "I2C_BUS", bus, "NET_DEVICE", netdir); 403 throw MCTPException("Bus is not configured as an MCTP interface"); 404 } 405 406 return it->path().filename(); 407 } 408