blob: a8fcc91e6c1618eb6cbdb79dbc0b83fbb3d2458e [file] [log] [blame]
Andrew Jeffery275f7c32024-01-31 12:41:14 +10301#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
35PHOSPHOR_LOG2_USING;
36
37static constexpr const char* mctpdBusName = "xyz.openbmc_project.MCTP";
38static constexpr const char* mctpdControlPath = "/xyz/openbmc_project/mctp";
39static constexpr const char* mctpdControlInterface =
40 "au.com.CodeConstruct.MCTP";
41static constexpr const char* mctpdEndpointControlInterface =
42 "au.com.CodeConstruct.MCTP.Endpoint";
43
44MCTPDDevice::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
50void 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
75void 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
91void 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
124void 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
136void MCTPDDevice::remove()
137{
138 if (endpoint)
139 {
140 debug("Removing endpoint @ [ {MCTP_ENDPOINT} ]", "MCTP_ENDPOINT",
141 endpoint->describe());
142 endpoint->remove();
143 }
144}
145
146std::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
162std::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
168void 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
187void 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
210int MCTPDEndpoint::network() const
211{
212 return mctp.network;
213}
214
215uint8_t MCTPDEndpoint::eid() const
216{
217 return mctp.eid;
218}
219
220void 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
287void 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
301void MCTPDEndpoint::removed()
302{
303 if (notifyRemoved)
304 {
305 notifyRemoved(shared_from_this());
306 }
307}
308
309std::string MCTPDEndpoint::describe() const
310{
311 return std::format("network: {}, EID: {} | {}", mctp.network, mctp.eid,
312 dev->describe());
313}
314
315std::shared_ptr<MCTPDevice> MCTPDEndpoint::device() const
316{
317 return dev;
318}
319
320std::optional<SensorBaseConfigMap>
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
331bool I2CMCTPDDevice::match(const std::set<std::string>& interfaces)
332{
333 return interfaces.contains(configInterfaceName(configType));
334}
335
336std::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
393std::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}