#include "MCTPEndpoint.hpp" #include "Utils.hpp" #include "VariantVisitors.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include PHOSPHOR_LOG2_USING; static constexpr const char* mctpdBusName = "xyz.openbmc_project.MCTP"; static constexpr const char* mctpdControlPath = "/xyz/openbmc_project/mctp"; static constexpr const char* mctpdControlInterface = "au.com.CodeConstruct.MCTP"; static constexpr const char* mctpdEndpointControlInterface = "au.com.CodeConstruct.MCTP.Endpoint"; MCTPDDevice::MCTPDDevice( const std::shared_ptr& connection, const std::string& interface, const std::vector& physaddr) : connection(connection), interface(interface), physaddr(physaddr) {} void MCTPDDevice::onEndpointInterfacesRemoved( const std::weak_ptr& weak, const std::string& objpath, sdbusplus::message_t& msg) { auto path = msg.unpack(); assert(path.str == objpath); auto removedIfaces = msg.unpack>(); if (!removedIfaces.contains(mctpdEndpointControlInterface)) { return; } if (auto self = weak.lock()) { self->endpointRemoved(); } else { info( "Device for inventory at '{INVENTORY_PATH}' was destroyed concurrent to endpoint removal", "INVENTORY_PATH", objpath); } } void MCTPDDevice::finaliseEndpoint( const std::string& objpath, uint8_t eid, int network, std::function& ep)>& added) { const auto matchSpec = sdbusplus::bus::match::rules::interfacesRemovedAtPath(objpath); removeMatch = std::make_unique( *connection, matchSpec, std::bind_front(MCTPDDevice::onEndpointInterfacesRemoved, weak_from_this(), objpath)); endpoint = std::make_shared(shared_from_this(), connection, objpath, network, eid); added({}, endpoint); } void MCTPDDevice::setup( std::function& ep)>&& added) { // Use a lambda to separate state validation from business logic, // where the business logic for a successful setup() is encoded in // MctpdDevice::finaliseEndpoint() auto onSetup = [weak{weak_from_this()}, added{std::move(added)}]( const boost::system::error_code& ec, uint8_t eid, int network, const std::string& objpath, bool allocated [[maybe_unused]]) mutable { if (ec) { added(ec, {}); return; } if (auto self = weak.lock()) { self->finaliseEndpoint(objpath, eid, network, added); } else { info( "Device object for inventory at '{INVENTORY_PATH}' was destroyed concurrent to completion of its endpoint setup", "INVENTORY_PATH", objpath); } }; connection->async_method_call(onSetup, mctpdBusName, mctpdControlPath, mctpdControlInterface, "AssignEndpoint", interface, physaddr); } void MCTPDDevice::endpointRemoved() { if (endpoint) { debug("Endpoint removed @ [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT", endpoint->describe()); removeMatch.reset(); endpoint->removed(); endpoint.reset(); } } void MCTPDDevice::remove() { if (endpoint) { debug("Removing endpoint @ [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT", endpoint->describe()); endpoint->remove(); } } std::string MCTPDDevice::describe() const { std::string description = std::format("interface: {}", interface); if (!physaddr.empty()) { description.append(", address: 0x [ "); auto it = physaddr.begin(); for (; it != physaddr.end() - 1; it++) { description.append(std::format("{:02x} ", *it)); } description.append(std::format("{:02x} ]", *it)); } return description; } std::string MCTPDEndpoint::path(const std::shared_ptr& ep) { return std::format("/xyz/openbmc_project/mctp/{}/{}", ep->network(), ep->eid()); } void MCTPDEndpoint::onMctpEndpointChange(sdbusplus::message_t& msg) { auto [iface, changed, _] = msg.unpack, std::vector>(); if (iface != mctpdEndpointControlInterface) { return; } auto it = changed.find("Connectivity"); if (it == changed.end()) { return; } updateEndpointConnectivity(std::get(it->second)); } void MCTPDEndpoint::updateEndpointConnectivity(const std::string& connectivity) { if (connectivity == "Degraded") { if (notifyDegraded) { notifyDegraded(shared_from_this()); } } else if (connectivity == "Available") { if (notifyAvailable) { notifyAvailable(shared_from_this()); } } else { debug("Unrecognised connectivity state: '{CONNECTIVITY_STATE}'", "CONNECTIVITY_STATE", connectivity); } } int MCTPDEndpoint::network() const { return mctp.network; } uint8_t MCTPDEndpoint::eid() const { return mctp.eid; } void MCTPDEndpoint::subscribe(Event&& degraded, Event&& available, Event&& removed) { const auto matchSpec = sdbusplus::bus::match::rules::propertiesChangedNamespace( objpath.str, mctpdEndpointControlInterface); this->notifyDegraded = std::move(degraded); this->notifyAvailable = std::move(available); this->notifyRemoved = std::move(removed); try { connectivityMatch.emplace( static_cast(*connection), matchSpec, [weak{weak_from_this()}, path{objpath.str}](sdbusplus::message_t& msg) { if (auto self = weak.lock()) { self->onMctpEndpointChange(msg); } else { info( "The endpoint for the device at inventory path '{INVENTORY_PATH}' was destroyed concurrent to the removal of its state change match", "INVENTORY_PATH", path); } }); connection->async_method_call( [weak{weak_from_this()}, path{objpath.str}](const boost::system::error_code& ec, const std::variant& value) { if (ec) { debug( "Failed to get current connectivity state: {ERROR_MESSAGE}", "ERROR_MESSAGE", ec.message(), "ERROR_CATEGORY", ec.category().name(), "ERROR_CODE", ec.value()); return; } if (auto self = weak.lock()) { const std::string& connectivity = std::get(value); self->updateEndpointConnectivity(connectivity); } else { info( "The endpoint for the device at inventory path '{INVENTORY_PATH}' was destroyed concurrent to the completion of its connectivity state query", "INVENTORY_PATH", path); } }, mctpdBusName, objpath.str, "org.freedesktop.DBus.Properties", "Get", mctpdEndpointControlInterface, "Connectivity"); } catch (const sdbusplus::exception::SdBusError& err) { this->notifyDegraded = nullptr; this->notifyAvailable = nullptr; this->notifyRemoved = nullptr; std::throw_with_nested( MCTPException("Failed to register connectivity signal match")); } } void MCTPDEndpoint::remove() { connection->async_method_call( [self{shared_from_this()}](const boost::system::error_code& ec) { if (ec) { debug("Failed to remove endpoint @ [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT", self->describe()); return; } }, mctpdBusName, objpath.str, mctpdEndpointControlInterface, "Remove"); } void MCTPDEndpoint::removed() { if (notifyRemoved) { notifyRemoved(shared_from_this()); } } std::string MCTPDEndpoint::describe() const { return std::format("network: {}, EID: {} | {}", mctp.network, mctp.eid, dev->describe()); } std::shared_ptr MCTPDEndpoint::device() const { return dev; } std::optional I2CMCTPDDevice::match(const SensorData& config) { auto iface = config.find(configInterfaceName(configType)); if (iface == config.end()) { return std::nullopt; } return iface->second; } bool I2CMCTPDDevice::match(const std::set& interfaces) { return interfaces.contains(configInterfaceName(configType)); } std::shared_ptr I2CMCTPDDevice::from( const std::shared_ptr& connection, const SensorBaseConfigMap& iface) { auto mType = iface.find("Type"); if (mType == iface.end()) { throw std::invalid_argument( "No 'Type' member found for provided configuration object"); } auto type = std::visit(VariantToStringVisitor(), mType->second); if (type != configType) { throw std::invalid_argument("Not an SMBus device"); } auto mAddress = iface.find("Address"); auto mBus = iface.find("Bus"); auto mName = iface.find("Name"); if (mAddress == iface.end() || mBus == iface.end() || mName == iface.end()) { throw std::invalid_argument( "Configuration object violates MCTPI2CTarget schema"); } auto sAddress = std::visit(VariantToStringVisitor(), mAddress->second); std::uint8_t address{}; auto [aptr, aec] = std::from_chars( sAddress.data(), sAddress.data() + sAddress.size(), address); if (aec != std::errc{}) { throw std::invalid_argument("Bad device address"); } auto sBus = std::visit(VariantToStringVisitor(), mBus->second); int bus{}; auto [bptr, bec] = std::from_chars(sBus.data(), sBus.data() + sBus.size(), bus); if (bec != std::errc{}) { throw std::invalid_argument("Bad bus index"); } try { return std::make_shared(connection, bus, address); } catch (const MCTPException& ex) { warning( "Failed to create I2CMCTPDDevice at [ bus: {I2C_BUS}, address: {I2C_ADDRESS} ]: {EXCEPTION}", "I2C_BUS", bus, "I2C_ADDRESS", address, "EXCEPTION", ex); return {}; } } std::string I2CMCTPDDevice::interfaceFromBus(int bus) { std::filesystem::path netdir = std::format("/sys/bus/i2c/devices/i2c-{}/net", bus); std::error_code ec; std::filesystem::directory_iterator it(netdir, ec); if (ec || it == std::filesystem::end(it)) { error("No net device associated with I2C bus {I2C_BUS} at {NET_DEVICE}", "I2C_BUS", bus, "NET_DEVICE", netdir); throw MCTPException("Bus is not configured as an MCTP interface"); } return it->path().filename(); }