| #include "MCTPEndpoint.hpp" |
| |
| #include "Utils.hpp" |
| #include "VariantVisitors.hpp" |
| |
| #include <bits/fs_dir.h> |
| |
| #include <boost/system/detail/errc.hpp> |
| #include <phosphor-logging/lg2.hpp> |
| #include <sdbusplus/asio/connection.hpp> |
| #include <sdbusplus/bus.hpp> |
| #include <sdbusplus/bus/match.hpp> |
| #include <sdbusplus/exception.hpp> |
| #include <sdbusplus/message.hpp> |
| #include <sdbusplus/message/native_types.hpp> |
| |
| #include <array> |
| #include <cassert> |
| #include <charconv> |
| #include <cstddef> |
| #include <cstdint> |
| #include <exception> |
| #include <filesystem> |
| #include <format> |
| #include <fstream> |
| #include <functional> |
| #include <iterator> |
| #include <map> |
| #include <memory> |
| #include <numeric> |
| #include <optional> |
| #include <set> |
| #include <span> |
| #include <stdexcept> |
| #include <string> |
| #include <string_view> |
| #include <system_error> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| PHOSPHOR_LOG2_USING; |
| |
| static constexpr const char* mctpdBusName = "au.com.codeconstruct.MCTP1"; |
| static constexpr const char* mctpdControlPath = "/au/com/codeconstruct/mctp1"; |
| static constexpr const char* mctpdControlInterface = |
| "au.com.codeconstruct.MCTP.BusOwner1"; |
| static constexpr const char* mctpdEndpointControlInterface = |
| "au.com.codeconstruct.MCTP.Endpoint1"; |
| |
| MCTPDDevice::MCTPDDevice( |
| const std::shared_ptr<sdbusplus::asio::connection>& connection, |
| const std::string& interface, const std::vector<uint8_t>& physaddr) : |
| connection(connection), interface(interface), physaddr(physaddr) |
| {} |
| |
| void MCTPDDevice::onEndpointInterfacesRemoved( |
| const std::weak_ptr<MCTPDDevice>& weak, const std::string& objpath, |
| sdbusplus::message_t& msg) |
| { |
| auto path = msg.unpack<sdbusplus::object_path>(); |
| assert(path.string() == objpath); |
| |
| auto removedIfaces = msg.unpack<std::set<std::string>>(); |
| 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<void(const std::error_code& ec, |
| const std::shared_ptr<MCTPEndpoint>& ep)>& added) |
| { |
| const auto matchSpec = |
| sdbusplus::match_rules::interfacesRemovedAtPath(objpath); |
| removeMatch = std::make_unique<sdbusplus::match>( |
| *connection, matchSpec, |
| std::bind_front(MCTPDDevice::onEndpointInterfacesRemoved, |
| weak_from_this(), objpath)); |
| endpoint = std::make_shared<MCTPDEndpoint>(shared_from_this(), connection, |
| objpath, network, eid); |
| added({}, endpoint); |
| } |
| |
| void MCTPDDevice::setup( |
| std::function<void(const std::error_code& ec, |
| const std::shared_ptr<MCTPEndpoint>& 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 + std::string("/interfaces/") + interface, |
| mctpdControlInterface, "AssignEndpoint", 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; |
| } |
| |
| // https://en.cppreference.com/w/cpp/utility/hash/operator().html |
| // |
| // https://en.wikipedia.org/w/index.php?title=Fowler%E2%80%93Noll%E2%80%93Vo_hash_function&oldid=1312413750#FNV_hash_parameters |
| template <typename T, typename V, T p> |
| static std::size_t fnv1a(T h, V e); |
| |
| template <std::size_t S> |
| static std::size_t fnv1a(const std::vector<std::uint8_t>& d); |
| |
| template <typename V> |
| static std::size_t fnv1a(std::uint32_t h, V v) |
| { |
| constexpr std::uint32_t p = 0x01000193; |
| return (h ^ v) * p; |
| } |
| |
| template <> |
| [[maybe_unused]] std::size_t fnv1a<4UL>(const std::vector<std::uint8_t>& d) |
| { |
| std::uint32_t h = 0x811c9dc5; |
| for (const auto& v : d) |
| { |
| h = fnv1a(h, v); |
| } |
| return h; |
| } |
| |
| template <typename V> |
| static std::size_t fnv1a(std::uint64_t h, V v) |
| { |
| constexpr std::uint64_t p = 0x00000100000001b3; |
| return (h ^ v) * p; |
| } |
| |
| template <> |
| [[maybe_unused]] std::size_t fnv1a<8UL>(const std::vector<std::uint8_t>& d) |
| { |
| std::uint64_t h = 0xcbf29ce484222325; |
| for (const auto& v : d) |
| { |
| h = fnv1a(h, v); |
| } |
| return h; |
| } |
| |
| static std::size_t fnv1aHash(const std::vector<std::uint8_t>& d) |
| { |
| return fnv1a<sizeof(std::size_t)>(d); |
| } |
| |
| std::size_t MCTPDDevice::id() const |
| { |
| std::size_t h1 = std::hash<std::string>{}(interface); |
| std::size_t h2 = fnv1aHash(physaddr); |
| |
| return h1 ^ (h2 << 1); |
| } |
| |
| std::string MCTPDEndpoint::path(const std::shared_ptr<MCTPEndpoint>& ep) |
| { |
| return std::format("{}/networks/{}/endpoints/{}", mctpdControlPath, |
| ep->network(), ep->eid()); |
| } |
| |
| void MCTPDEndpoint::onMctpEndpointChange(sdbusplus::message_t& msg) |
| { |
| auto [iface, changed, _] = |
| msg.unpack<std::string, std::map<std::string, BasicVariantType>, |
| std::vector<std::string>>(); |
| if (iface != mctpdEndpointControlInterface) |
| { |
| return; |
| } |
| |
| auto it = changed.find("Connectivity"); |
| if (it == changed.end()) |
| { |
| return; |
| } |
| |
| updateEndpointConnectivity(std::get<std::string>(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::match_rules::propertiesChangedNamespace( |
| objpath.string(), mctpdEndpointControlInterface); |
| |
| this->notifyDegraded = std::move(degraded); |
| this->notifyAvailable = std::move(available); |
| this->notifyRemoved = std::move(removed); |
| |
| try |
| { |
| connectivityMatch.emplace( |
| static_cast<sdbusplus::bus_t&>(*connection), matchSpec, |
| [weak{weak_from_this()}, |
| path{objpath.string()}](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.string()}](const boost::system::error_code& ec, |
| const std::variant<std::string>& 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<std::string>(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.string(), "org.freedesktop.DBus.Properties", |
| "Get", mctpdEndpointControlInterface, "Connectivity"); |
| } |
| catch (const sdbusplus::exception::internal_exception& 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.string(), 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<MCTPDevice> MCTPDEndpoint::device() const |
| { |
| return dev; |
| } |
| |
| std::optional<SensorBaseConfigMap> I2CMCTPDDevice::match( |
| const SensorData& config) |
| { |
| auto iface = config.find(configInterfaceName(configType)); |
| if (iface == config.end()) |
| { |
| return std::nullopt; |
| } |
| return iface->second; |
| } |
| |
| std::optional<SensorBaseConfigMap> I3CMCTPDDevice::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<std::string>& interfaces) |
| { |
| return interfaces.contains(configInterfaceName(configType)); |
| } |
| |
| bool I3CMCTPDDevice::match(const std::set<std::string>& interfaces) |
| { |
| return interfaces.contains(configInterfaceName(configType)); |
| } |
| |
| std::shared_ptr<I2CMCTPDDevice> I2CMCTPDDevice::from( |
| const std::shared_ptr<sdbusplus::asio::connection>& 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<I2CMCTPDDevice>(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::shared_ptr<I3CMCTPDDevice> I3CMCTPDDevice::from( |
| const std::shared_ptr<sdbusplus::asio::connection>& 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 I3C 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 MCTPI3CTarget schema"); |
| } |
| |
| auto address = std::visit(VariantToNumArrayVisitor<uint8_t, uint64_t>(), |
| mAddress->second); |
| if (address.empty()) |
| { |
| 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<I3CMCTPDDevice>(connection, bus, address); |
| } |
| catch (const MCTPException& ex) |
| { |
| warning( |
| "Failed to create I3CMCTPDDevice at [ bus: {I3C_BUS} ]: {EXCEPTION}", |
| "I3C_BUS", bus, "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(); |
| } |
| |
| std::string I3CMCTPDDevice::interfaceFromBus(int bus) |
| { |
| std::filesystem::path netdir = std::format("/sys/devices/virtual/net"); |
| std::error_code ec; |
| std::filesystem::directory_iterator it(netdir, ec); |
| if (ec || it == std::filesystem::end(it)) |
| { |
| error("No net device associated with I3C bus {I3C_BUS} at {NET_DEVICE}", |
| "I3C_BUS", bus, "NET_DEVICE", netdir); |
| throw MCTPException("Bus is not configured as an MCTP interface"); |
| } |
| |
| std::string targetInterface = std::format("mctpi3c{}", bus); |
| for (const auto& entry : std::filesystem::directory_iterator(netdir)) |
| { |
| if (entry.is_directory() && entry.path().filename() == targetInterface) |
| { |
| return targetInterface; |
| } |
| } |
| |
| error("No matching net device found for I3C bus {I3C_BUS} at {NET_DEVICE}", |
| "I3C_BUS", bus, "NET_DEVICE", netdir); |
| throw MCTPException("No matching net device found for the specified bus"); |
| } |
| |
| // The addresses of the USB host controllers belong to the SOC, so a root hub |
| // position only names one once the SOC is known. |
| static std::span<const std::string_view> rootHubsForSoc( |
| const std::string& socFamily) |
| { |
| // The AST2620 is the AST2600 with the same controller addresses. The UHCI |
| // companion at 1e6b0000 is not listed: it serves the same ports at lower |
| // speed rather than being a host controller of its own. |
| static constexpr std::array<std::string_view, 2> ast2600RootHubs = { |
| "/sys/devices/platform/ahb/1e6a1000.usb", |
| "/sys/devices/platform/ahb/1e6a3000.usb"}; |
| |
| // The AST2750 is the AST2700 with the same controller addresses. |
| static constexpr std::array<std::string_view, 4> ast2700RootHubs = { |
| "/sys/devices/platform/soc@10000000/12061000.usb", |
| "/sys/devices/platform/soc@10000000/12063000.usb", |
| "/sys/devices/platform/soc@14000000/14121000.usb", |
| "/sys/devices/platform/soc@14000000/14123000.usb", |
| }; |
| |
| if (socFamily.find("AST2600") != std::string::npos || |
| socFamily.find("AST2620") != std::string::npos) |
| { |
| return ast2600RootHubs; |
| } |
| if (socFamily.find("AST2700") != std::string::npos || |
| socFamily.find("AST2750") != std::string::npos) |
| { |
| return ast2700RootHubs; |
| } |
| |
| return {}; |
| } |
| |
| // Resolve a root hub position to its host controller's sysfs path. Returns |
| // nullopt, having logged why, if the SOC or the position is not supported. |
| static std::optional<std::string_view> rootHubPathForPosition(size_t position) |
| { |
| std::ifstream socFamilyFile("/sys/bus/soc/devices/soc0/family"); |
| if (!socFamilyFile) |
| { |
| error("Unable to read SOC family information"); |
| return std::nullopt; |
| } |
| std::string socFamily; |
| std::getline(socFamilyFile, socFamily); |
| |
| const std::span<const std::string_view> rootHubs = |
| rootHubsForSoc(socFamily); |
| if (rootHubs.empty()) |
| { |
| error("Unsupported SOC for USB MCTP: {SOC_FAMILY}", "SOC_FAMILY", |
| socFamily); |
| return std::nullopt; |
| } |
| |
| if (position >= rootHubs.size()) |
| { |
| error( |
| "RootHubPosition {POSITION} names no host controller on {SOC_FAMILY}", |
| "POSITION", position, "SOC_FAMILY", socFamily); |
| return std::nullopt; |
| } |
| |
| return rootHubs[position]; |
| } |
| |
| /* |
| * Given the sysfs path for a USB root hub, determines the bus number. An |
| * example root hub path on an AST2600 is |
| * /sys/devices/platform/ahb/1e6a3000.usb. This function identifies the |
| * correct bus device directory (e.g. "usb1") by looking for a "busnum" |
| * attribute file within it, and returns the content of that file. |
| */ |
| std::string USBMCTPDDevice::busFromRootHubPath( |
| const std::filesystem::path& rootHubPath) |
| { |
| std::error_code ec; |
| std::filesystem::directory_iterator it(rootHubPath, ec); |
| if (ec) |
| { |
| error("Unable to open RootHubPath {PATH}", "PATH", rootHubPath); |
| throw MCTPException("Invalid RootHubPath"); |
| } |
| |
| for (const auto& entry : it) |
| { |
| const auto& path = entry.path(); |
| if (!entry.is_directory() || |
| !path.filename().string().starts_with("usb")) |
| { |
| continue; |
| } |
| |
| std::filesystem::path busnumPath = path / "busnum"; |
| if (!std::filesystem::exists(busnumPath)) |
| { |
| continue; |
| } |
| std::ifstream busnumFile(busnumPath); |
| std::string busnum; |
| if (busnumFile >> busnum && !busnum.empty()) |
| { |
| return busnum; |
| } |
| } |
| |
| error( |
| "No directory containing a 'busnum' file found under RootHubPath {PATH}", |
| "PATH", rootHubPath); |
| throw MCTPException("Could not determine bus number"); |
| } |
| |
| /* |
| * Given the components of a USB device's sysfs path, construct the full path |
| * and determine the network interface name associated with it. |
| */ |
| std::string USBMCTPDDevice::interfaceFromSysfs( |
| const std::string& bus, const std::string& port, uint8_t configuration, |
| uint8_t interfaceNum) |
| { |
| std::filesystem::path netdir = |
| std::format("/sys/bus/usb/devices/{}-{}/{}-{}:{}.{}/net", bus, port, |
| bus, port, configuration, interfaceNum); |
| std::error_code ec; |
| std::filesystem::directory_iterator it(netdir, ec); |
| if (ec || it == std::filesystem::end(it)) |
| { |
| error("No net device associated with USB device at {NET_DEVICE}", |
| "NET_DEVICE", netdir); |
| throw MCTPException("Device is not configured as an MCTP interface"); |
| } |
| |
| return it->path().filename(); |
| } |
| |
| std::optional<SensorBaseConfigMap> USBMCTPDDevice::match( |
| const SensorData& config) |
| { |
| auto iface = config.find(configInterfaceName(configType)); |
| if (iface == config.end()) |
| { |
| return std::nullopt; |
| } |
| return iface->second; |
| } |
| |
| bool USBMCTPDDevice::match(const std::set<std::string>& interfaces) |
| { |
| return interfaces.contains(configInterfaceName(configType)); |
| } |
| |
| std::shared_ptr<USBMCTPDDevice> USBMCTPDDevice::from( |
| const std::shared_ptr<sdbusplus::asio::connection>& connection, |
| const SensorBaseConfigMap& iface) |
| { |
| auto mName = iface.find("Name"); |
| 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 USB device"); |
| } |
| |
| auto mRootHubPosition = iface.find("RootHubPosition"); |
| auto mPort = iface.find("Port"); |
| auto mConfiguration = iface.find("Configuration"); |
| auto mInterface = iface.find("Interface"); |
| |
| if (mName == iface.end() || mRootHubPosition == iface.end() || |
| mPort == iface.end() || mConfiguration == iface.end() || |
| mInterface == iface.end()) |
| { |
| throw std::invalid_argument( |
| "Configuration object violates MCTPUSBDevice schema"); |
| } |
| |
| auto sRootHubPosition = |
| std::visit(VariantToStringVisitor(), mRootHubPosition->second); |
| uint8_t rootHubPosition{}; |
| auto [rptr, rec] = std::from_chars( |
| sRootHubPosition.data(), |
| sRootHubPosition.data() + sRootHubPosition.size(), rootHubPosition); |
| if (rec != std::errc{} || |
| rptr != sRootHubPosition.data() + sRootHubPosition.size()) |
| { |
| throw std::invalid_argument("Bad RootHubPosition value"); |
| } |
| |
| auto portArray = std::visit(VariantToNumArrayVisitor<uint8_t, uint64_t>(), |
| mPort->second); |
| if (portArray.empty()) |
| { |
| throw std::invalid_argument("Bad Port value"); |
| } |
| |
| // Convert port array to dot-separated string (e.g., [1, 2, 1] -> "1.2.1") |
| std::string port = std::accumulate( |
| std::next(portArray.begin()), portArray.end(), |
| std::to_string(portArray[0]), [](const std::string& acc, uint8_t val) { |
| return acc + "." + std::to_string(val); |
| }); |
| |
| auto sConfiguration = |
| std::visit(VariantToStringVisitor(), mConfiguration->second); |
| uint8_t configuration{}; |
| auto [cptr, cec] = std::from_chars( |
| sConfiguration.data(), sConfiguration.data() + sConfiguration.size(), |
| configuration); |
| if (cec != std::errc{} || |
| cptr != sConfiguration.data() + sConfiguration.size()) |
| { |
| throw std::invalid_argument("Bad Configuration value"); |
| } |
| |
| auto sInterface = std::visit(VariantToStringVisitor(), mInterface->second); |
| uint8_t interfaceNum{}; |
| auto [iptr, iec] = std::from_chars( |
| sInterface.data(), sInterface.data() + sInterface.size(), interfaceNum); |
| if (iec != std::errc{} || iptr != sInterface.data() + sInterface.size()) |
| { |
| throw std::invalid_argument("Bad Interface value"); |
| } |
| |
| std::optional<std::string_view> rootHubPath = |
| rootHubPathForPosition(rootHubPosition); |
| if (!rootHubPath) |
| { |
| return {}; |
| } |
| |
| try |
| { |
| std::string bus = busFromRootHubPath(std::string(*rootHubPath)); |
| std::string interface = |
| interfaceFromSysfs(bus, port, configuration, interfaceNum); |
| return std::make_shared<USBMCTPDDevice>(connection, interface); |
| } |
| catch (const MCTPException& ex) |
| { |
| warning( |
| "Failed to create MCTPUSBDevice at [ RootHubPosition: {POSITION}, port: {USB_PORT} ]: {EXCEPTION}", |
| "POSITION", sRootHubPosition, "USB_PORT", port, "EXCEPTION", ex); |
| return {}; |
| } |
| } |