blob: 9548d534855ce71aac7bab1f80edda929fea6b15 [file]
#include "MCTPReactor.hpp"
#include "MCTPDeviceRepository.hpp"
#include "MCTPEndpoint.hpp"
#include "Utils.hpp"
#include <boost/system/detail/error_code.hpp>
#include <phosphor-logging/lg2.hpp>
#include <phosphor-logging/lg2/flags.hpp>
#include <sdbusplus/message/native_types.hpp>
#include <memory>
#include <optional>
#include <string>
#include <system_error>
#include <utility>
#include <vector>
PHOSPHOR_LOG2_USING;
void MCTPReactor::untrackEndpoint(const std::shared_ptr<MCTPEndpoint>& ep)
{
server.disassociate(MCTPDEndpoint::path(ep));
}
void MCTPReactor::trackEndpoint(const std::shared_ptr<MCTPEndpoint>& ep)
{
info("Added MCTP endpoint to device: [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT",
ep->describe());
ep->subscribe(
// Degraded
[](const std::shared_ptr<MCTPEndpoint>& ep) {
debug("Endpoint entered degraded state: [ {MCTP_ENDPOINT} ]",
"MCTP_ENDPOINT", ep->describe());
},
// Available
[](const std::shared_ptr<MCTPEndpoint>& ep) {
debug("Endpoint entered available state: [ {MCTP_ENDPOINT} ]",
"MCTP_ENDPOINT", ep->describe());
},
// Removed
[weak{weak_from_this()}](const std::shared_ptr<MCTPEndpoint>& ep) {
info("Removed MCTP endpoint from device: [ {MCTP_ENDPOINT} ]",
"MCTP_ENDPOINT", ep->describe());
if (auto self = weak.lock())
{
self->untrackEndpoint(ep);
switch (self->states[ep->device()->id()])
{
case MCTPDeviceState::Unmanaged:
case MCTPDeviceState::Assigning:
case MCTPDeviceState::Unassigned:
break;
case MCTPDeviceState::Assigned:
self->next(ep->device(), MCTPDeviceState::Lost);
break;
case MCTPDeviceState::Quarantine:
self->terminate(ep->device());
break;
case MCTPDeviceState::Lost:
case MCTPDeviceState::Recovering:
break;
case MCTPDeviceState::Recovered:
self->next(ep->device(), MCTPDeviceState::Lost);
break;
case MCTPDeviceState::Removing:
// If the configuration has been replaced then we've
// already terminated the state tracking
if (self->devices.contains(ep->device()))
{
self->terminate(ep->device());
}
break;
case MCTPDeviceState::Pending:
self->next(ep->device(), MCTPDeviceState::Unassigned);
break;
}
}
else
{
info(
"The reactor object was destroyed concurrent to the removal of the remove match for the endpoint '{MCTP_ENDPOINT}'",
"MCTP_ENDPOINT", ep->describe());
}
});
switch (states[ep->device()->id()])
{
case MCTPDeviceState::Unmanaged:
return;
case MCTPDeviceState::Assigning:
next(ep->device(), MCTPDeviceState::Assigned);
break;
case MCTPDeviceState::Unassigned:
case MCTPDeviceState::Assigned:
case MCTPDeviceState::Quarantine:
next(ep->device(), MCTPDeviceState::Recovered);
break;
case MCTPDeviceState::Lost:
return;
case MCTPDeviceState::Recovering:
next(ep->device(), MCTPDeviceState::Recovered);
break;
case MCTPDeviceState::Recovered:
case MCTPDeviceState::Removing:
case MCTPDeviceState::Pending:
return;
}
// Proxy-host the association back to the inventory at the same path as the
// endpoint in mctpd.
//
// clang-format off
// ```
// # busctl call xyz.openbmc_project.ObjectMapper /xyz/openbmc_project/object_mapper xyz.openbmc_project.ObjectMapper GetAssociatedSubTree ooias /au/com/codeconstruct/mctp1/networks/1/endpoints/9/configured_by / 0 1 xyz.openbmc_project.Configuration.MCTPDevice
// a{sa{sas}} 1 "/xyz/openbmc_project/inventory/system/nvme/NVMe_1/NVMe_1_Temp" 1 "xyz.openbmc_project.EntityManager" 1 "xyz.openbmc_project.Configuration.MCTPDevice"
// ```
// clang-format on
std::optional<std::string> item = devices.inventoryFor(ep->device());
if (!item)
{
error("Inventory missing for endpoint: [ {MCTP_ENDPOINT} ]",
"MCTP_ENDPOINT", ep->describe());
return;
}
std::vector<Association> associations{
{"configured_by", "configures", *item}};
server.associate(MCTPDEndpoint::path(ep), associations);
}
void MCTPReactor::setupEndpoint(const std::shared_ptr<MCTPDevice>& dev)
{
debug(
"Attempting to setup up MCTP endpoint for device at [ {MCTP_DEVICE} ]",
"MCTP_DEVICE", dev->describe());
dev->setup([weak{weak_from_this()}, wdev = std::weak_ptr<MCTPDevice>(dev)](
const std::error_code& ec,
const std::shared_ptr<MCTPEndpoint>& ep) mutable {
auto self = weak.lock();
if (!self)
{
info(
"The reactor object was destroyed concurrent to the completion of the endpoint setup");
return;
}
auto dev = wdev.lock();
if (!dev)
{
info(
"The device was destroyed concurrent to the completion of endpoint setup");
return;
}
if (ec)
{
debug(
"Setup failed for MCTP device at [ {MCTP_DEVICE} ], deferring: {ERROR_MESSAGE}",
"MCTP_DEVICE", dev->describe(), "ERROR_MESSAGE", ec.message());
switch (self->states[dev->id()])
{
case MCTPDeviceState::Unmanaged:
break;
case MCTPDeviceState::Assigning:
self->next(dev, MCTPDeviceState::Unassigned);
break;
case MCTPDeviceState::Unassigned:
case MCTPDeviceState::Assigned:
break;
case MCTPDeviceState::Quarantine:
self->terminate(dev);
break;
case MCTPDeviceState::Lost:
break;
case MCTPDeviceState::Recovering:
self->next(dev, MCTPDeviceState::Lost);
break;
case MCTPDeviceState::Recovered:
case MCTPDeviceState::Removing:
case MCTPDeviceState::Pending:
break;
}
return;
}
try
{
self->trackEndpoint(ep);
}
catch (const MCTPException& e)
{
error("Failed to track endpoint '{MCTP_ENDPOINT}': {EXCEPTION}",
"MCTP_ENDPOINT", ep->describe(), "EXCEPTION", e);
self->next(dev, MCTPDeviceState::Quarantine);
}
});
}
void MCTPReactor::tick()
{
for (const auto& entry : devices)
{
switch (states[entry.second->id()])
{
case MCTPDeviceState::Unmanaged:
case MCTPDeviceState::Assigning:
break;
case MCTPDeviceState::Unassigned:
next(entry.second, MCTPDeviceState::Assigning);
setupEndpoint(entry.second);
break;
case MCTPDeviceState::Assigned:
case MCTPDeviceState::Quarantine:
break;
case MCTPDeviceState::Lost:
next(entry.second, MCTPDeviceState::Recovering);
setupEndpoint(entry.second);
break;
case MCTPDeviceState::Recovering:
case MCTPDeviceState::Recovered:
case MCTPDeviceState::Removing:
case MCTPDeviceState::Pending:
break;
}
}
}
void MCTPReactor::manageMCTPDevice(const std::string& path,
const std::shared_ptr<MCTPDevice>& device)
{
if (!device)
{
return;
}
debug("MCTP device inventory added at '{INVENTORY_PATH}'", "INVENTORY_PATH",
path);
if (!states.contains(device->id()))
{
debug(
"Initialising state for device {DEVICE_ID} ([ {DEVICE_DESCRIPTION} ])) as {INITIAL_STATE}",
"DEVICE_ID", lg2::hex, device->id(), "DEVICE_DESCRIPTION",
device->describe(), "INITIAL_STATE", MCTPDeviceState::Unmanaged);
states[device->id()] = MCTPDeviceState::Unmanaged;
}
switch (states[device->id()])
{
case MCTPDeviceState::Unmanaged:
devices.add(path, device);
next(device, MCTPDeviceState::Assigning);
setupEndpoint(device);
break;
case MCTPDeviceState::Assigning:
case MCTPDeviceState::Unassigned:
break;
case MCTPDeviceState::Assigned:
{
// EM may publish property changes without removal. Replace the
// device so its state reflects EM's configuration.
auto current = devices.deviceFor(path);
if (!current)
{
warning(
"Invalid state: Failed to manage device for inventory at '{INVENTORY_PATH}', but the inventory item is unrecognised",
"INVENTORY_PATH", path);
return;
}
// Unsynchronised termination so we can configure the new device.
// Paired with presence test when handling MCTPDeviceState::Removing
// in the subscribed endpoint removed handler
terminate(current);
current->remove();
manageMCTPDevice(path, device);
break;
}
case MCTPDeviceState::Quarantine:
next(device, MCTPDeviceState::Assigning);
break;
case MCTPDeviceState::Lost:
case MCTPDeviceState::Recovering:
break;
case MCTPDeviceState::Recovered:
next(device, MCTPDeviceState::Assigned);
break;
case MCTPDeviceState::Removing:
next(device, MCTPDeviceState::Pending);
break;
case MCTPDeviceState::Pending:
break;
}
}
void MCTPReactor::unmanageMCTPDevice(const sdbusplus::object_path& path)
{
auto device = devices.deviceFor(path);
if (!device)
{
debug("Unrecognised inventory item: {INVENTORY_PATH}", "INVENTORY_PATH",
path);
return;
}
debug("MCTP device inventory removed at '{INVENTORY_PATH}'",
"INVENTORY_PATH", path);
switch (states[device->id()])
{
case MCTPDeviceState::Unmanaged:
break;
case MCTPDeviceState::Assigning:
next(device, MCTPDeviceState::Quarantine);
break;
case MCTPDeviceState::Unassigned:
terminate(device);
break;
case MCTPDeviceState::Assigned:
debug("Stopping management of MCTP device at [ {MCTP_DEVICE} ]",
"MCTP_DEVICE", device->describe());
next(device, MCTPDeviceState::Removing);
device->remove();
break;
case MCTPDeviceState::Quarantine:
break;
case MCTPDeviceState::Lost:
terminate(device);
break;
case MCTPDeviceState::Recovering:
next(device, MCTPDeviceState::Quarantine);
break;
case MCTPDeviceState::Recovered:
case MCTPDeviceState::Removing:
break;
case MCTPDeviceState::Pending:
next(device, MCTPDeviceState::Removing);
break;
}
}
void MCTPReactor::next(const std::shared_ptr<MCTPDevice>& dev,
const MCTPDeviceState next)
{
debug(
"Device {DEVICE_ID} ([ {DEVICE_DESCRIPTION} ]) transitioning from {CURRENT_STATE} to {NEXT_STATE}",
"DEVICE_ID", lg2::hex, dev->id(), "DEVICE_DESCRIPTION", dev->describe(),
"CURRENT_STATE", states[dev->id()], "NEXT_STATE", next);
states[dev->id()] = next;
}
void MCTPReactor::terminate(const std::shared_ptr<MCTPDevice>& dev)
{
debug(
"Device {DEVICE_ID} ([ {DEVICE_DESCRIPTION} ]) terminated from {CURRENT_STATE}",
"DEVICE_ID", lg2::hex, dev->id(), "DEVICE_DESCRIPTION", dev->describe(),
"CURRENT_STATE", states[dev->id()]);
devices.remove(dev);
states.erase(dev->id());
}