blob: 7157894589cb733d3c201abe2ce5e2ffdaa5a796 [file] [log] [blame] [edit]
#include "MctpReactorDevice.hpp"
#include "MctpEndpoint.hpp"
#include "Utils.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/system/detail/errc.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/bus/match.hpp>
#include <sdbusplus/exception.hpp>
#include <sdbusplus/message.hpp>
#include <sdbusplus/message/native_types.hpp>
#include <exception>
#include <limits>
#include <memory>
#include <optional>
#include <string_view>
#include <system_error>
#include <variant>
namespace
{
constexpr const char* objectMapperServiceName =
"xyz.openbmc_project.ObjectMapper";
constexpr const char* configuresObjectPathSuffix = "/configures";
constexpr const char* dbusPropertiesInterface =
"org.freedesktop.DBus.Properties";
constexpr const char* associationInterface = "xyz.openbmc_project.Association";
constexpr const char* endpointPropertyName = "endpoints";
// Returns the token after the last '/' character.
std::string extractSuffixFromObjectPath(const std::string& path)
{
std::vector<std::string> tokens;
boost::split(tokens, path, boost::is_any_of("/"));
if (tokens.empty())
{
return "";
}
return tokens.back();
}
// Returns a pair consisting of the network number and endpoint ID parsed from
// an MCTP endpoint object path such as
// "/au/com/codeconstruct/mctp1/networks/1/endpoints/17" for example.
// Assumes that the last number is EID and the first number is the network.
std::optional<std::pair<int, uint8_t>>
extractNetworkAndEid(std::string_view endpointObjectPath)
{
std::vector<std::string> tokens;
boost::split(tokens, endpointObjectPath, boost::is_any_of("/"));
auto it = tokens.rbegin();
// Find EID.
int eid = -1;
for (; it != tokens.rend(); ++it)
{
try
{
eid = std::stoi(*it);
}
catch (...)
{
continue;
}
break;
}
// Make sure the next (preceding) token is "enpdoints".
if (it == tokens.rend() || it + 1 == tokens.rend() ||
*(it + 1) != "endpoints")
{
return std::nullopt;
}
// Number validity check.
if (eid < std::numeric_limits<uint8_t>::min() ||
eid > std::numeric_limits<uint8_t>::max())
{
return std::nullopt;
}
// Find network.
int network = -1;
for (++it; it != tokens.rend(); ++it)
{
try
{
network = std::stoi(*it);
}
catch (...)
{
continue;
}
break;
}
// Make sure the next (preceding) token is "networks".
if (it == tokens.rend() || it + 1 == tokens.rend() ||
*(it + 1) != "networks")
{
return std::nullopt;
}
// Number validity check.
if (network < 0)
{
return std::nullopt;
}
return std::make_pair(network, eid);
}
} // namespace
MctpReactorDevice::MctpReactorDevice(
const std::shared_ptr<sdbusplus::asio::connection>& connection,
const std::string& deviceObjectPath, std::optional<int> busNumber,
std::optional<int> address) :
connection(connection), deviceObjectPath(deviceObjectPath),
prettyObjectName(extractSuffixFromObjectPath(deviceObjectPath)),
isI2cAccessible(busNumber.has_value() && address.has_value()),
i2cBusNumber(isI2cAccessible ? busNumber.value() : -1),
i2cAddress(isI2cAccessible ? address.value() : -1)
{}
void MctpReactorDevice::setup(
std::function<void(const std::error_code& ec,
const std::shared_ptr<MctpEndpoint>& ep)>&& action)
{
auto onGetPropertyReturned =
[weak{weak_from_this()}, action{std::move(action)}](
const boost::system::error_code& ec,
const std::variant<std::vector<std::string>>& value) mutable {
if (ec)
{
action(ec, {});
return;
}
const auto* const endpointObjectPaths =
std::get_if<std::vector<std::string>>(&value);
if (!endpointObjectPaths || endpointObjectPaths->size() != 1)
{
auto errc = std::errc::invalid_argument;
auto ec = std::make_error_code(errc);
action(ec, {});
return;
}
if (auto self = weak.lock())
{
self->finaliseEndpoint(endpointObjectPaths->front(),
std::move(action));
}
};
try
{
const std::string configuresObjectPath = deviceObjectPath +
configuresObjectPathSuffix;
connection->async_method_call(
onGetPropertyReturned, objectMapperServiceName,
configuresObjectPath, dbusPropertiesInterface, "Get",
std::string(associationInterface),
std::string(endpointPropertyName));
}
catch (const sdbusplus::exception::SdBusError& err)
{
auto errc = std::errc::no_such_device_or_address;
auto ec = std::make_error_code(errc);
action(ec, {});
}
}
void MctpReactorDevice::remove()
{
if (endpoint)
{
endpoint->remove();
}
}
std::string MctpReactorDevice::describe() const
{
return "MctpReactorDev: " + prettyObjectName;
}
void MctpReactorDevice::onEndpointInterfacesRemoved(
const std::weak_ptr<MctpReactorDevice>& weak,
const std::string& endpointObjectPath, sdbusplus::message_t& msg)
{
auto objectPath = msg.unpack<sdbusplus::message::object_path>();
if (objectPath.str != endpointObjectPath)
{
return;
}
auto removedInterfaces = msg.unpack<std::set<std::string>>();
if (!removedInterfaces.contains(mctpdEndpointControlInterface))
{
return;
}
if (auto self = weak.lock())
{
self->endpointRemoved();
}
}
void MctpReactorDevice::finaliseEndpoint(
const std::string& endpointObjectPath,
std::function<void(const std::error_code& ec,
const std::shared_ptr<MctpEndpoint>& ep)>&& action)
{
const auto matchSpec =
std::string(sdbusplus::bus::match::rules::interfacesRemoved())
.append(
sdbusplus::bus::match::rules::argNpath(0, endpointObjectPath));
removeMatch = std::make_unique<sdbusplus::bus::match_t>(
*connection, matchSpec,
std::bind_front(MctpReactorDevice::onEndpointInterfacesRemoved,
weak_from_this(), endpointObjectPath));
const auto networkAndEid = extractNetworkAndEid(endpointObjectPath);
if (!networkAndEid)
{
action(std::make_error_code(std::errc::invalid_argument), nullptr);
return;
}
const auto [network, eid] = networkAndEid.value();
endpoint = std::make_shared<MctpdEndpoint>(shared_from_this(), connection,
endpointObjectPath, network, eid,
isI2cAccessible);
action({}, endpoint);
}
void MctpReactorDevice::endpointRemoved()
{
if (endpoint)
{
removeMatch.reset();
endpoint->remove();
endpoint.reset();
}
}