xref: /openbmc/dbus-sensors/src/mctp/MCTPEndpoint.cpp (revision bd815c7f79301fe3932093bfd799122879122680)
1 #include "MCTPEndpoint.hpp"
2 
3 #include "Utils.hpp"
4 #include "VariantVisitors.hpp"
5 
6 #include <bits/fs_dir.h>
7 
8 #include <boost/system/detail/errc.hpp>
9 #include <phosphor-logging/lg2.hpp>
10 #include <sdbusplus/asio/connection.hpp>
11 #include <sdbusplus/bus.hpp>
12 #include <sdbusplus/bus/match.hpp>
13 #include <sdbusplus/exception.hpp>
14 #include <sdbusplus/message.hpp>
15 #include <sdbusplus/message/native_types.hpp>
16 
17 #include <cassert>
18 #include <charconv>
19 #include <cstdint>
20 #include <exception>
21 #include <filesystem>
22 #include <format>
23 #include <functional>
24 #include <map>
25 #include <memory>
26 #include <optional>
27 #include <set>
28 #include <stdexcept>
29 #include <string>
30 #include <system_error>
31 #include <utility>
32 #include <variant>
33 #include <vector>
34 
35 PHOSPHOR_LOG2_USING;
36 
37 static constexpr const char* mctpdBusName = "au.com.codeconstruct.MCTP1";
38 static constexpr const char* mctpdControlPath = "/au/com/codeconstruct/mctp1";
39 static constexpr const char* mctpdControlInterface =
40     "au.com.codeconstruct.MCTP.BusOwner1";
41 static constexpr const char* mctpdEndpointControlInterface =
42     "au.com.codeconstruct.MCTP.Endpoint1";
43 
MCTPDDevice(const std::shared_ptr<sdbusplus::asio::connection> & connection,const std::string & interface,const std::vector<uint8_t> & physaddr)44 MCTPDDevice::MCTPDDevice(
45     const std::shared_ptr<sdbusplus::asio::connection>& connection,
46     const std::string& interface, const std::vector<uint8_t>& physaddr) :
47     connection(connection), interface(interface), physaddr(physaddr)
48 {}
49 
onEndpointInterfacesRemoved(const std::weak_ptr<MCTPDDevice> & weak,const std::string & objpath,sdbusplus::message_t & msg)50 void MCTPDDevice::onEndpointInterfacesRemoved(
51     const std::weak_ptr<MCTPDDevice>& weak, const std::string& objpath,
52     sdbusplus::message_t& msg)
53 {
54     auto path = msg.unpack<sdbusplus::message::object_path>();
55     assert(path.str == objpath);
56 
57     auto removedIfaces = msg.unpack<std::set<std::string>>();
58     if (!removedIfaces.contains(mctpdEndpointControlInterface))
59     {
60         return;
61     }
62 
63     if (auto self = weak.lock())
64     {
65         self->endpointRemoved();
66     }
67     else
68     {
69         info(
70             "Device for inventory at '{INVENTORY_PATH}' was destroyed concurrent to endpoint removal",
71             "INVENTORY_PATH", objpath);
72     }
73 }
74 
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)75 void MCTPDDevice::finaliseEndpoint(
76     const std::string& objpath, uint8_t eid, int network,
77     std::function<void(const std::error_code& ec,
78                        const std::shared_ptr<MCTPEndpoint>& ep)>& added)
79 {
80     const auto matchSpec =
81         sdbusplus::bus::match::rules::interfacesRemovedAtPath(objpath);
82     removeMatch = std::make_unique<sdbusplus::bus::match_t>(
83         *connection, matchSpec,
84         std::bind_front(MCTPDDevice::onEndpointInterfacesRemoved,
85                         weak_from_this(), objpath));
86     endpoint = std::make_shared<MCTPDEndpoint>(shared_from_this(), connection,
87                                                objpath, network, eid);
88     added({}, endpoint);
89 }
90 
setup(std::function<void (const std::error_code & ec,const std::shared_ptr<MCTPEndpoint> & ep)> && added)91 void MCTPDDevice::setup(
92     std::function<void(const std::error_code& ec,
93                        const std::shared_ptr<MCTPEndpoint>& ep)>&& added)
94 {
95     // Use a lambda to separate state validation from business logic,
96     // where the business logic for a successful setup() is encoded in
97     // MctpdDevice::finaliseEndpoint()
98     auto onSetup = [weak{weak_from_this()}, added{std::move(added)}](
99                        const boost::system::error_code& ec, uint8_t eid,
100                        int network, const std::string& objpath,
101                        bool allocated [[maybe_unused]]) mutable {
102         if (ec)
103         {
104             added(ec, {});
105             return;
106         }
107 
108         if (auto self = weak.lock())
109         {
110             self->finaliseEndpoint(objpath, eid, network, added);
111         }
112         else
113         {
114             info(
115                 "Device object for inventory at '{INVENTORY_PATH}' was destroyed concurrent to completion of its endpoint setup",
116                 "INVENTORY_PATH", objpath);
117         }
118     };
119     connection->async_method_call(
120         onSetup, mctpdBusName,
121         mctpdControlPath + std::string("/interfaces/") + interface,
122         mctpdControlInterface, "AssignEndpoint", physaddr);
123 }
124 
endpointRemoved()125 void MCTPDDevice::endpointRemoved()
126 {
127     if (endpoint)
128     {
129         debug("Endpoint removed @ [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT",
130               endpoint->describe());
131         removeMatch.reset();
132         endpoint->removed();
133         endpoint.reset();
134     }
135 }
136 
remove()137 void MCTPDDevice::remove()
138 {
139     if (endpoint)
140     {
141         debug("Removing endpoint @ [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT",
142               endpoint->describe());
143         endpoint->remove();
144     }
145 }
146 
describe() const147 std::string MCTPDDevice::describe() const
148 {
149     std::string description = std::format("interface: {}", interface);
150     if (!physaddr.empty())
151     {
152         description.append(", address: 0x [ ");
153         auto it = physaddr.begin();
154         for (; it != physaddr.end() - 1; it++)
155         {
156             description.append(std::format("{:02x} ", *it));
157         }
158         description.append(std::format("{:02x} ]", *it));
159     }
160     return description;
161 }
162 
path(const std::shared_ptr<MCTPEndpoint> & ep)163 std::string MCTPDEndpoint::path(const std::shared_ptr<MCTPEndpoint>& ep)
164 {
165     return std::format("{}/networks/{}/endpoints/{}", mctpdControlPath,
166                        ep->network(), ep->eid());
167 }
168 
onMctpEndpointChange(sdbusplus::message_t & msg)169 void MCTPDEndpoint::onMctpEndpointChange(sdbusplus::message_t& msg)
170 {
171     auto [iface, changed, _] =
172         msg.unpack<std::string, std::map<std::string, BasicVariantType>,
173                    std::vector<std::string>>();
174     if (iface != mctpdEndpointControlInterface)
175     {
176         return;
177     }
178 
179     auto it = changed.find("Connectivity");
180     if (it == changed.end())
181     {
182         return;
183     }
184 
185     updateEndpointConnectivity(std::get<std::string>(it->second));
186 }
187 
updateEndpointConnectivity(const std::string & connectivity)188 void MCTPDEndpoint::updateEndpointConnectivity(const std::string& connectivity)
189 {
190     if (connectivity == "Degraded")
191     {
192         if (notifyDegraded)
193         {
194             notifyDegraded(shared_from_this());
195         }
196     }
197     else if (connectivity == "Available")
198     {
199         if (notifyAvailable)
200         {
201             notifyAvailable(shared_from_this());
202         }
203     }
204     else
205     {
206         debug("Unrecognised connectivity state: '{CONNECTIVITY_STATE}'",
207               "CONNECTIVITY_STATE", connectivity);
208     }
209 }
210 
network() const211 int MCTPDEndpoint::network() const
212 {
213     return mctp.network;
214 }
215 
eid() const216 uint8_t MCTPDEndpoint::eid() const
217 {
218     return mctp.eid;
219 }
220 
subscribe(Event && degraded,Event && available,Event && removed)221 void MCTPDEndpoint::subscribe(Event&& degraded, Event&& available,
222                               Event&& removed)
223 {
224     const auto matchSpec =
225         sdbusplus::bus::match::rules::propertiesChangedNamespace(
226             objpath.str, mctpdEndpointControlInterface);
227 
228     this->notifyDegraded = std::move(degraded);
229     this->notifyAvailable = std::move(available);
230     this->notifyRemoved = std::move(removed);
231 
232     try
233     {
234         connectivityMatch.emplace(
235             static_cast<sdbusplus::bus_t&>(*connection), matchSpec,
236             [weak{weak_from_this()},
237              path{objpath.str}](sdbusplus::message_t& msg) {
238                 if (auto self = weak.lock())
239                 {
240                     self->onMctpEndpointChange(msg);
241                 }
242                 else
243                 {
244                     info(
245                         "The endpoint for the device at inventory path '{INVENTORY_PATH}' was destroyed concurrent to the removal of its state change match",
246                         "INVENTORY_PATH", path);
247                 }
248             });
249         connection->async_method_call(
250             [weak{weak_from_this()},
251              path{objpath.str}](const boost::system::error_code& ec,
252                                 const std::variant<std::string>& value) {
253                 if (ec)
254                 {
255                     debug(
256                         "Failed to get current connectivity state: {ERROR_MESSAGE}",
257                         "ERROR_MESSAGE", ec.message(), "ERROR_CATEGORY",
258                         ec.category().name(), "ERROR_CODE", ec.value());
259                     return;
260                 }
261 
262                 if (auto self = weak.lock())
263                 {
264                     const std::string& connectivity =
265                         std::get<std::string>(value);
266                     self->updateEndpointConnectivity(connectivity);
267                 }
268                 else
269                 {
270                     info(
271                         "The endpoint for the device at inventory path '{INVENTORY_PATH}' was destroyed concurrent to the completion of its connectivity state query",
272                         "INVENTORY_PATH", path);
273                 }
274             },
275             mctpdBusName, objpath.str, "org.freedesktop.DBus.Properties", "Get",
276             mctpdEndpointControlInterface, "Connectivity");
277     }
278     catch (const sdbusplus::exception::SdBusError& err)
279     {
280         this->notifyDegraded = nullptr;
281         this->notifyAvailable = nullptr;
282         this->notifyRemoved = nullptr;
283         std::throw_with_nested(
284             MCTPException("Failed to register connectivity signal match"));
285     }
286 }
287 
remove()288 void MCTPDEndpoint::remove()
289 {
290     connection->async_method_call(
291         [self{shared_from_this()}](const boost::system::error_code& ec) {
292             if (ec)
293             {
294                 debug("Failed to remove endpoint @ [ {MCTP_ENDPOINT} ]",
295                       "MCTP_ENDPOINT", self->describe());
296                 return;
297             }
298         },
299         mctpdBusName, objpath.str, mctpdEndpointControlInterface, "Remove");
300 }
301 
removed()302 void MCTPDEndpoint::removed()
303 {
304     if (notifyRemoved)
305     {
306         notifyRemoved(shared_from_this());
307     }
308 }
309 
describe() const310 std::string MCTPDEndpoint::describe() const
311 {
312     return std::format("network: {}, EID: {} | {}", mctp.network, mctp.eid,
313                        dev->describe());
314 }
315 
device() const316 std::shared_ptr<MCTPDevice> MCTPDEndpoint::device() const
317 {
318     return dev;
319 }
320 
match(const SensorData & config)321 std::optional<SensorBaseConfigMap> I2CMCTPDDevice::match(
322     const SensorData& config)
323 {
324     auto iface = config.find(configInterfaceName(configType));
325     if (iface == config.end())
326     {
327         return std::nullopt;
328     }
329     return iface->second;
330 }
331 
match(const SensorData & config)332 std::optional<SensorBaseConfigMap> I3CMCTPDDevice::match(
333     const SensorData& config)
334 {
335     auto iface = config.find(configInterfaceName(configType));
336     if (iface == config.end())
337     {
338         return std::nullopt;
339     }
340     return iface->second;
341 }
342 
match(const std::set<std::string> & interfaces)343 bool I2CMCTPDDevice::match(const std::set<std::string>& interfaces)
344 {
345     return interfaces.contains(configInterfaceName(configType));
346 }
347 
match(const std::set<std::string> & interfaces)348 bool I3CMCTPDDevice::match(const std::set<std::string>& interfaces)
349 {
350     return interfaces.contains(configInterfaceName(configType));
351 }
352 
from(const std::shared_ptr<sdbusplus::asio::connection> & connection,const SensorBaseConfigMap & iface)353 std::shared_ptr<I2CMCTPDDevice> I2CMCTPDDevice::from(
354     const std::shared_ptr<sdbusplus::asio::connection>& connection,
355     const SensorBaseConfigMap& iface)
356 {
357     auto mType = iface.find("Type");
358     if (mType == iface.end())
359     {
360         throw std::invalid_argument(
361             "No 'Type' member found for provided configuration object");
362     }
363 
364     auto type = std::visit(VariantToStringVisitor(), mType->second);
365     if (type != configType)
366     {
367         throw std::invalid_argument("Not an SMBus device");
368     }
369 
370     auto mAddress = iface.find("Address");
371     auto mBus = iface.find("Bus");
372     auto mName = iface.find("Name");
373     if (mAddress == iface.end() || mBus == iface.end() || mName == iface.end())
374     {
375         throw std::invalid_argument(
376             "Configuration object violates MCTPI2CTarget schema");
377     }
378 
379     auto sAddress = std::visit(VariantToStringVisitor(), mAddress->second);
380     std::uint8_t address{};
381     auto [aptr, aec] = std::from_chars(
382         sAddress.data(), sAddress.data() + sAddress.size(), address);
383     if (aec != std::errc{})
384     {
385         throw std::invalid_argument("Bad device address");
386     }
387 
388     auto sBus = std::visit(VariantToStringVisitor(), mBus->second);
389     int bus{};
390     auto [bptr,
391           bec] = std::from_chars(sBus.data(), sBus.data() + sBus.size(), bus);
392     if (bec != std::errc{})
393     {
394         throw std::invalid_argument("Bad bus index");
395     }
396 
397     try
398     {
399         return std::make_shared<I2CMCTPDDevice>(connection, bus, address);
400     }
401     catch (const MCTPException& ex)
402     {
403         warning(
404             "Failed to create I2CMCTPDDevice at [ bus: {I2C_BUS}, address: {I2C_ADDRESS} ]: {EXCEPTION}",
405             "I2C_BUS", bus, "I2C_ADDRESS", address, "EXCEPTION", ex);
406         return {};
407     }
408 }
409 
from(const std::shared_ptr<sdbusplus::asio::connection> & connection,const SensorBaseConfigMap & iface)410 std::shared_ptr<I3CMCTPDDevice> I3CMCTPDDevice::from(
411     const std::shared_ptr<sdbusplus::asio::connection>& connection,
412     const SensorBaseConfigMap& iface)
413 {
414     auto mType = iface.find("Type");
415     if (mType == iface.end())
416     {
417         throw std::invalid_argument(
418             "No 'Type' member found for provided configuration object");
419     }
420 
421     auto type = std::visit(VariantToStringVisitor(), mType->second);
422     if (type != configType)
423     {
424         throw std::invalid_argument("Not an I3C device");
425     }
426 
427     auto mAddress = iface.find("Address");
428     auto mBus = iface.find("Bus");
429     auto mName = iface.find("Name");
430     if (mAddress == iface.end() || mBus == iface.end() || mName == iface.end())
431     {
432         throw std::invalid_argument(
433             "Configuration object violates MCTPI3CTarget schema");
434     }
435 
436     auto address = std::visit(VariantToNumArrayVisitor<uint8_t, uint64_t>(),
437                               mAddress->second);
438     if (address.empty())
439     {
440         throw std::invalid_argument("Bad device address");
441     }
442 
443     auto sBus = std::visit(VariantToStringVisitor(), mBus->second);
444     int bus{};
445     auto [bptr,
446           bec] = std::from_chars(sBus.data(), sBus.data() + sBus.size(), bus);
447     if (bec != std::errc{})
448     {
449         throw std::invalid_argument("Bad bus index");
450     }
451 
452     try
453     {
454         return std::make_shared<I3CMCTPDDevice>(connection, bus, address);
455     }
456     catch (const MCTPException& ex)
457     {
458         warning(
459             "Failed to create I3CMCTPDDevice at [ bus: {I3C_BUS} ]: {EXCEPTION}",
460             "I3C_BUS", bus, "EXCEPTION", ex);
461         return {};
462     }
463 }
464 
interfaceFromBus(int bus)465 std::string I2CMCTPDDevice::interfaceFromBus(int bus)
466 {
467     std::filesystem::path netdir =
468         std::format("/sys/bus/i2c/devices/i2c-{}/net", bus);
469     std::error_code ec;
470     std::filesystem::directory_iterator it(netdir, ec);
471     if (ec || it == std::filesystem::end(it))
472     {
473         error("No net device associated with I2C bus {I2C_BUS} at {NET_DEVICE}",
474               "I2C_BUS", bus, "NET_DEVICE", netdir);
475         throw MCTPException("Bus is not configured as an MCTP interface");
476     }
477 
478     return it->path().filename();
479 }
480 
interfaceFromBus(int bus)481 std::string I3CMCTPDDevice::interfaceFromBus(int bus)
482 {
483     std::filesystem::path netdir = std::format("/sys/devices/virtual/net");
484     std::error_code ec;
485     std::filesystem::directory_iterator it(netdir, ec);
486     if (ec || it == std::filesystem::end(it))
487     {
488         error("No net device associated with I3C bus {I3C_BUS} at {NET_DEVICE}",
489               "I3C_BUS", bus, "NET_DEVICE", netdir);
490         throw MCTPException("Bus is not configured as an MCTP interface");
491     }
492 
493     std::string targetInterface = std::format("mctpi3c{}", bus);
494     for (const auto& entry : std::filesystem::directory_iterator(netdir))
495     {
496         if (entry.is_directory() && entry.path().filename() == targetInterface)
497         {
498             return targetInterface;
499         }
500     }
501 
502     error("No matching net device found for I3C bus {I3C_BUS} at {NET_DEVICE}",
503           "I3C_BUS", bus, "NET_DEVICE", netdir);
504     throw MCTPException("No matching net device found for the specified bus");
505 }
506