1 #include "MCTPReactor.hpp"
2 
3 #include "MCTPDeviceRepository.hpp"
4 #include "MCTPEndpoint.hpp"
5 #include "Utils.hpp"
6 
7 #include <boost/system/detail/error_code.hpp>
8 #include <phosphor-logging/lg2.hpp>
9 
10 #include <cstdlib>
11 #include <memory>
12 #include <optional>
13 #include <string>
14 #include <system_error>
15 #include <utility>
16 #include <vector>
17 
18 PHOSPHOR_LOG2_USING;
19 
deferSetup(const std::shared_ptr<MCTPDevice> & dev)20 void MCTPReactor::deferSetup(const std::shared_ptr<MCTPDevice>& dev)
21 {
22     debug("Deferring setup for MCTP device at [ {MCTP_DEVICE} ]", "MCTP_DEVICE",
23           dev->describe());
24 
25     deferred.emplace(dev);
26 }
27 
untrackEndpoint(const std::shared_ptr<MCTPEndpoint> & ep)28 void MCTPReactor::untrackEndpoint(const std::shared_ptr<MCTPEndpoint>& ep)
29 {
30     server.disassociate(MCTPDEndpoint::path(ep));
31 }
32 
trackEndpoint(const std::shared_ptr<MCTPEndpoint> & ep)33 void MCTPReactor::trackEndpoint(const std::shared_ptr<MCTPEndpoint>& ep)
34 {
35     info("Added MCTP endpoint to device: [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT",
36          ep->describe());
37 
38     ep->subscribe(
39         // Degraded
40         [](const std::shared_ptr<MCTPEndpoint>& ep) {
41             debug("Endpoint entered degraded state: [ {MCTP_ENDPOINT} ]",
42                   "MCTP_ENDPOINT", ep->describe());
43         },
44         // Available
45         [](const std::shared_ptr<MCTPEndpoint>& ep) {
46             debug("Endpoint entered available state: [ {MCTP_ENDPOINT} ]",
47                   "MCTP_ENDPOINT", ep->describe());
48         },
49         // Removed
50         [weak{weak_from_this()}](const std::shared_ptr<MCTPEndpoint>& ep) {
51             info("Removed MCTP endpoint from device: [ {MCTP_ENDPOINT} ]",
52                  "MCTP_ENDPOINT", ep->describe());
53             if (auto self = weak.lock())
54             {
55                 self->untrackEndpoint(ep);
56                 // Only defer the setup if we know inventory is still present
57                 if (self->devices.contains(ep->device()))
58                 {
59                     self->deferSetup(ep->device());
60                 }
61             }
62             else
63             {
64                 info(
65                     "The reactor object was destroyed concurrent to the removal of the remove match for the endpoint '{MCTP_ENDPOINT}'",
66                     "MCTP_ENDPOINT", ep->describe());
67             }
68         });
69 
70     // Proxy-host the association back to the inventory at the same path as the
71     // endpoint in mctpd.
72     //
73     // clang-format off
74     // ```
75     // # busctl call xyz.openbmc_project.ObjectMapper /xyz/openbmc_project/object_mapper xyz.openbmc_project.ObjectMapper GetAssociatedSubTree ooias /xyz/openbmc_project/mctp/1/9/configured_by / 0 1 xyz.openbmc_project.Configuration.MCTPDevice
76     // 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"
77     // ```
78     // clang-format on
79     std::optional<std::string> item = devices.inventoryFor(ep->device());
80     if (!item)
81     {
82         error("Inventory missing for endpoint: [ {MCTP_ENDPOINT} ]",
83               "MCTP_ENDPOINT", ep->describe());
84         return;
85     }
86     std::vector<Association> associations{
87         {"configured_by", "configures", *item}};
88     server.associate(MCTPDEndpoint::path(ep), associations);
89 }
90 
setupEndpoint(const std::shared_ptr<MCTPDevice> & dev)91 void MCTPReactor::setupEndpoint(const std::shared_ptr<MCTPDevice>& dev)
92 {
93     debug(
94         "Attempting to setup up MCTP endpoint for device at [ {MCTP_DEVICE} ]",
95         "MCTP_DEVICE", dev->describe());
96     dev->setup([weak{weak_from_this()},
97                 dev](const std::error_code& ec,
98                      const std::shared_ptr<MCTPEndpoint>& ep) mutable {
99         auto self = weak.lock();
100         if (!self)
101         {
102             info(
103                 "The reactor object was destroyed concurrent to the completion of the endpoint setup for '{MCTP_ENDPOINT}'",
104                 "MCTP_ENDPOINT", ep->describe());
105             return;
106         }
107 
108         if (ec)
109         {
110             debug(
111                 "Setup failed for MCTP device at [ {MCTP_DEVICE} ]: {ERROR_MESSAGE}",
112                 "MCTP_DEVICE", dev->describe(), "ERROR_MESSAGE", ec.message());
113 
114             self->deferSetup(dev);
115             return;
116         }
117 
118         try
119         {
120             self->trackEndpoint(ep);
121         }
122         catch (const MCTPException& e)
123         {
124             error("Failed to track endpoint '{MCTP_ENDPOINT}': {EXCEPTION}",
125                   "MCTP_ENDPOINT", ep->describe(), "EXCEPTION", e);
126             self->deferSetup(dev);
127         }
128     });
129 }
130 
tick()131 void MCTPReactor::tick()
132 {
133     auto toSetup = std::exchange(deferred, {});
134     for (const auto& entry : toSetup)
135     {
136         setupEndpoint(entry);
137     }
138 }
139 
manageMCTPDevice(const std::string & path,const std::shared_ptr<MCTPDevice> & device)140 void MCTPReactor::manageMCTPDevice(const std::string& path,
141                                    const std::shared_ptr<MCTPDevice>& device)
142 {
143     if (!device)
144     {
145         return;
146     }
147 
148     try
149     {
150         devices.add(path, device);
151         debug("MCTP device inventory added at '{INVENTORY_PATH}'",
152               "INVENTORY_PATH", path);
153         setupEndpoint(device);
154     }
155     catch (const std::system_error& e)
156     {
157         if (e.code() != std::errc::device_or_resource_busy)
158         {
159             throw e;
160         }
161 
162         auto current = devices.deviceFor(path);
163         if (!current)
164         {
165             warning(
166                 "Invalid state: Failed to manage device for inventory at '{INVENTORY_PATH}', but the inventory item is unrecognised",
167                 "INVENTORY_PATH", path);
168             return;
169         }
170 
171         // TODO: Ensure remove completion happens-before add. For now this
172         // happens unsynchronised. Make some noise about it.
173         warning(
174             "Unsynchronised endpoint reinitialsation due to configuration change at '{INVENTORY_PATH}': Removing '{MCTP_DEVICE}'",
175             "INVENTORY_PATH", path, "MCTP_DEVICE", current->describe());
176 
177         unmanageMCTPDevice(path);
178 
179         devices.add(path, device);
180 
181         // Pray (this is the unsynchronised bit)
182         deferSetup(device);
183     }
184 }
185 
unmanageMCTPDevice(const std::string & path)186 void MCTPReactor::unmanageMCTPDevice(const std::string& path)
187 {
188     auto device = devices.deviceFor(path);
189     if (!device)
190     {
191         debug("Unrecognised inventory item: {INVENTORY_PATH}", "INVENTORY_PATH",
192               path);
193         return;
194     }
195 
196     debug("MCTP device inventory removed at '{INVENTORY_PATH}'",
197           "INVENTORY_PATH", path);
198 
199     deferred.erase(device);
200 
201     // Remove the device from the repository before notifying the device itself
202     // of removal so we don't defer its setup
203     devices.remove(device);
204 
205     debug("Stopping management of MCTP device at [ {MCTP_DEVICE} ]",
206           "MCTP_DEVICE", device->describe());
207 
208     device->remove();
209 }
210