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