blob: a8fcc91e6c1618eb6cbdb79dbc0b83fbb3d2458e [file] [log] [blame]
#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 <cassert>
#include <charconv>
#include <cstdint>
#include <exception>
#include <filesystem>
#include <format>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <stdexcept>
#include <string>
#include <system_error>
#include <utility>
#include <variant>
#include <vector>
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<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::message::object_path>();
assert(path.str == 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::bus::match::rules::interfacesRemovedAtPath(objpath);
removeMatch = std::make_unique<sdbusplus::bus::match_t>(
*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,
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<MCTPEndpoint>& 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::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::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<sdbusplus::bus_t&>(*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<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.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<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;
}
bool I2CMCTPDDevice::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::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();
}