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 = "xyz.openbmc_project.MCTP";
38 static constexpr const char* mctpdControlPath = "/xyz/openbmc_project/mctp";
39 static constexpr const char* mctpdControlInterface =
40     "au.com.CodeConstruct.MCTP";
41 static constexpr const char* mctpdEndpointControlInterface =
42     "au.com.CodeConstruct.MCTP.Endpoint";
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(onSetup, mctpdBusName, mctpdControlPath,
120                                   mctpdControlInterface, "AssignEndpoint",
121                                   interface, physaddr);
122 }
123 
endpointRemoved()124 void MCTPDDevice::endpointRemoved()
125 {
126     if (endpoint)
127     {
128         debug("Endpoint removed @ [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT",
129               endpoint->describe());
130         removeMatch.reset();
131         endpoint->removed();
132         endpoint.reset();
133     }
134 }
135 
remove()136 void MCTPDDevice::remove()
137 {
138     if (endpoint)
139     {
140         debug("Removing endpoint @ [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT",
141               endpoint->describe());
142         endpoint->remove();
143     }
144 }
145 
describe() const146 std::string MCTPDDevice::describe() const
147 {
148     std::string description = std::format("interface: {}", interface);
149     if (!physaddr.empty())
150     {
151         description.append(", address: 0x [ ");
152         auto it = physaddr.begin();
153         for (; it != physaddr.end() - 1; it++)
154         {
155             description.append(std::format("{:02x} ", *it));
156         }
157         description.append(std::format("{:02x} ]", *it));
158     }
159     return description;
160 }
161 
path(const std::shared_ptr<MCTPEndpoint> & ep)162 std::string MCTPDEndpoint::path(const std::shared_ptr<MCTPEndpoint>& ep)
163 {
164     return std::format("/xyz/openbmc_project/mctp/{}/{}", ep->network(),
165                        ep->eid());
166 }
167 
onMctpEndpointChange(sdbusplus::message_t & msg)168 void MCTPDEndpoint::onMctpEndpointChange(sdbusplus::message_t& msg)
169 {
170     auto [iface, changed, _] =
171         msg.unpack<std::string, std::map<std::string, BasicVariantType>,
172                    std::vector<std::string>>();
173     if (iface != mctpdEndpointControlInterface)
174     {
175         return;
176     }
177 
178     auto it = changed.find("Connectivity");
179     if (it == changed.end())
180     {
181         return;
182     }
183 
184     updateEndpointConnectivity(std::get<std::string>(it->second));
185 }
186 
updateEndpointConnectivity(const std::string & connectivity)187 void MCTPDEndpoint::updateEndpointConnectivity(const std::string& connectivity)
188 {
189     if (connectivity == "Degraded")
190     {
191         if (notifyDegraded)
192         {
193             notifyDegraded(shared_from_this());
194         }
195     }
196     else if (connectivity == "Available")
197     {
198         if (notifyAvailable)
199         {
200             notifyAvailable(shared_from_this());
201         }
202     }
203     else
204     {
205         debug("Unrecognised connectivity state: '{CONNECTIVITY_STATE}'",
206               "CONNECTIVITY_STATE", connectivity);
207     }
208 }
209 
network() const210 int MCTPDEndpoint::network() const
211 {
212     return mctp.network;
213 }
214 
eid() const215 uint8_t MCTPDEndpoint::eid() const
216 {
217     return mctp.eid;
218 }
219 
subscribe(Event && degraded,Event && available,Event && removed)220 void MCTPDEndpoint::subscribe(Event&& degraded, Event&& available,
221                               Event&& removed)
222 {
223     const auto matchSpec =
224         sdbusplus::bus::match::rules::propertiesChangedNamespace(
225             objpath.str, mctpdEndpointControlInterface);
226 
227     this->notifyDegraded = std::move(degraded);
228     this->notifyAvailable = std::move(available);
229     this->notifyRemoved = std::move(removed);
230 
231     try
232     {
233         connectivityMatch.emplace(
234             static_cast<sdbusplus::bus_t&>(*connection), matchSpec,
235             [weak{weak_from_this()},
236              path{objpath.str}](sdbusplus::message_t& msg) {
237                 if (auto self = weak.lock())
238                 {
239                     self->onMctpEndpointChange(msg);
240                 }
241                 else
242                 {
243                     info(
244                         "The endpoint for the device at inventory path '{INVENTORY_PATH}' was destroyed concurrent to the removal of its state change match",
245                         "INVENTORY_PATH", path);
246                 }
247             });
248         connection->async_method_call(
249             [weak{weak_from_this()},
250              path{objpath.str}](const boost::system::error_code& ec,
251                                 const std::variant<std::string>& value) {
252                 if (ec)
253                 {
254                     debug(
255                         "Failed to get current connectivity state: {ERROR_MESSAGE}",
256                         "ERROR_MESSAGE", ec.message(), "ERROR_CATEGORY",
257                         ec.category().name(), "ERROR_CODE", ec.value());
258                     return;
259                 }
260 
261                 if (auto self = weak.lock())
262                 {
263                     const std::string& connectivity =
264                         std::get<std::string>(value);
265                     self->updateEndpointConnectivity(connectivity);
266                 }
267                 else
268                 {
269                     info(
270                         "The endpoint for the device at inventory path '{INVENTORY_PATH}' was destroyed concurrent to the completion of its connectivity state query",
271                         "INVENTORY_PATH", path);
272                 }
273             },
274             mctpdBusName, objpath.str, "org.freedesktop.DBus.Properties", "Get",
275             mctpdEndpointControlInterface, "Connectivity");
276     }
277     catch (const sdbusplus::exception::SdBusError& err)
278     {
279         this->notifyDegraded = nullptr;
280         this->notifyAvailable = nullptr;
281         this->notifyRemoved = nullptr;
282         std::throw_with_nested(
283             MCTPException("Failed to register connectivity signal match"));
284     }
285 }
286 
remove()287 void MCTPDEndpoint::remove()
288 {
289     connection->async_method_call(
290         [self{shared_from_this()}](const boost::system::error_code& ec) {
291             if (ec)
292             {
293                 debug("Failed to remove endpoint @ [ {MCTP_ENDPOINT} ]",
294                       "MCTP_ENDPOINT", self->describe());
295                 return;
296             }
297         },
298         mctpdBusName, objpath.str, mctpdEndpointControlInterface, "Remove");
299 }
300 
removed()301 void MCTPDEndpoint::removed()
302 {
303     if (notifyRemoved)
304     {
305         notifyRemoved(shared_from_this());
306     }
307 }
308 
describe() const309 std::string MCTPDEndpoint::describe() const
310 {
311     return std::format("network: {}, EID: {} | {}", mctp.network, mctp.eid,
312                        dev->describe());
313 }
314 
device() const315 std::shared_ptr<MCTPDevice> MCTPDEndpoint::device() const
316 {
317     return dev;
318 }
319 
320 std::optional<SensorBaseConfigMap>
match(const SensorData & config)321     I2CMCTPDDevice::match(const SensorData& config)
322 {
323     auto iface = config.find(configInterfaceName(configType));
324     if (iface == config.end())
325     {
326         return std::nullopt;
327     }
328     return iface->second;
329 }
330 
match(const std::set<std::string> & interfaces)331 bool I2CMCTPDDevice::match(const std::set<std::string>& interfaces)
332 {
333     return interfaces.contains(configInterfaceName(configType));
334 }
335 
from(const std::shared_ptr<sdbusplus::asio::connection> & connection,const SensorBaseConfigMap & iface)336 std::shared_ptr<I2CMCTPDDevice> I2CMCTPDDevice::from(
337     const std::shared_ptr<sdbusplus::asio::connection>& connection,
338     const SensorBaseConfigMap& iface)
339 {
340     auto mType = iface.find("Type");
341     if (mType == iface.end())
342     {
343         throw std::invalid_argument(
344             "No 'Type' member found for provided configuration object");
345     }
346 
347     auto type = std::visit(VariantToStringVisitor(), mType->second);
348     if (type != configType)
349     {
350         throw std::invalid_argument("Not an SMBus device");
351     }
352 
353     auto mAddress = iface.find("Address");
354     auto mBus = iface.find("Bus");
355     auto mName = iface.find("Name");
356     if (mAddress == iface.end() || mBus == iface.end() || mName == iface.end())
357     {
358         throw std::invalid_argument(
359             "Configuration object violates MCTPI2CTarget schema");
360     }
361 
362     auto sAddress = std::visit(VariantToStringVisitor(), mAddress->second);
363     std::uint8_t address{};
364     auto [aptr, aec] = std::from_chars(
365         sAddress.data(), sAddress.data() + sAddress.size(), address);
366     if (aec != std::errc{})
367     {
368         throw std::invalid_argument("Bad device address");
369     }
370 
371     auto sBus = std::visit(VariantToStringVisitor(), mBus->second);
372     int bus{};
373     auto [bptr,
374           bec] = std::from_chars(sBus.data(), sBus.data() + sBus.size(), bus);
375     if (bec != std::errc{})
376     {
377         throw std::invalid_argument("Bad bus index");
378     }
379 
380     try
381     {
382         return std::make_shared<I2CMCTPDDevice>(connection, bus, address);
383     }
384     catch (const MCTPException& ex)
385     {
386         warning(
387             "Failed to create I2CMCTPDDevice at [ bus: {I2C_BUS}, address: {I2C_ADDRESS} ]: {EXCEPTION}",
388             "I2C_BUS", bus, "I2C_ADDRESS", address, "EXCEPTION", ex);
389         return {};
390     }
391 }
392 
interfaceFromBus(int bus)393 std::string I2CMCTPDDevice::interfaceFromBus(int bus)
394 {
395     std::filesystem::path netdir =
396         std::format("/sys/bus/i2c/devices/i2c-{}/net", bus);
397     std::error_code ec;
398     std::filesystem::directory_iterator it(netdir, ec);
399     if (ec || it == std::filesystem::end(it))
400     {
401         error("No net device associated with I2C bus {I2C_BUS} at {NET_DEVICE}",
402               "I2C_BUS", bus, "NET_DEVICE", netdir);
403         throw MCTPException("Bus is not configured as an MCTP interface");
404     }
405 
406     return it->path().filename();
407 }
408