1 #include "ncsi_util.hpp"
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <linux/mctp.h>
6 #include <linux/ncsi.h>
7 #include <netlink/genl/ctrl.h>
8 #include <netlink/genl/genl.h>
9 #include <netlink/netlink.h>
10 #include <unistd.h>
11
12 #include <phosphor-logging/lg2.hpp>
13
14 #include <optional>
15 #include <span>
16 #include <system_error>
17 #include <vector>
18
19 namespace phosphor
20 {
21 namespace network
22 {
23 namespace ncsi
24 {
25
26 static const char* mctp_iid_path = "/run/ncsi-mctp-iids";
27
NCSICommand(uint8_t opcode,uint8_t package,std::optional<uint8_t> channel,std::span<unsigned char> payload)28 NCSICommand::NCSICommand(uint8_t opcode, uint8_t package,
29 std::optional<uint8_t> channel,
30 std::span<unsigned char> payload) :
31 opcode(opcode), package(package), channel(channel)
32 {
33 this->payload.assign(payload.begin(), payload.end());
34 }
35
getChannel()36 uint8_t NCSICommand::getChannel()
37 {
38 return channel.value_or(CHANNEL_ID_NONE);
39 }
40
41 using CallBack = int (*)(struct nl_msg* msg, void* arg);
42
43 namespace internal
44 {
45
46 struct NCSIPacketHeader
47 {
48 uint8_t MCID;
49 uint8_t revision;
50 uint8_t reserved;
51 uint8_t id;
52 uint8_t type;
53 uint8_t channel;
54 uint16_t length;
55 uint32_t rsvd[2];
56 };
57
58 struct NCSIResponsePayload
59 {
60 uint16_t response;
61 uint16_t reason;
62 };
63
64 class NetlinkCommand
65 {
66 public:
67 NetlinkCommand() = delete;
68 ~NetlinkCommand() = default;
69 NetlinkCommand(const NetlinkCommand&) = delete;
70 NetlinkCommand& operator=(const NetlinkCommand&) = delete;
71 NetlinkCommand(NetlinkCommand&&) = default;
72 NetlinkCommand& operator=(NetlinkCommand&&) = default;
NetlinkCommand(int ncsiCmd,int operation=DEFAULT_VALUE,std::span<const unsigned char> p=std::span<const unsigned char> ())73 NetlinkCommand(
74 int ncsiCmd, int operation = DEFAULT_VALUE,
75 std::span<const unsigned char> p = std::span<const unsigned char>()) :
76 ncsi_cmd(ncsiCmd), operation(operation), payload(p)
77 {}
78
79 int ncsi_cmd;
80 int operation;
81 std::span<const unsigned char> payload;
82 };
83
84 using nlMsgPtr = std::unique_ptr<nl_msg, decltype(&::nlmsg_free)>;
85 using nlSocketPtr = std::unique_ptr<nl_sock, decltype(&::nl_socket_free)>;
86
87 struct infoCallBackContext
88 {
89 InterfaceInfo* info;
90 };
91
__anon66112f010102(struct nl_msg* msg, void* arg) 92 CallBack infoCallBack = [](struct nl_msg* msg, void* arg) {
93 if (arg == nullptr)
94 {
95 lg2::error("Internal error: invalid info callback context");
96 return -1;
97 }
98
99 struct infoCallBackContext* info = (struct infoCallBackContext*)arg;
100 using namespace phosphor::network::ncsi;
101 auto nlh = nlmsg_hdr(msg);
102
103 struct nlattr* tb[NCSI_ATTR_MAX + 1] = {nullptr};
104 struct nla_policy ncsiPolicy[NCSI_ATTR_MAX + 1] = {
105 {NLA_UNSPEC, 0, 0}, {NLA_U32, 0, 0}, {NLA_NESTED, 0, 0},
106 {NLA_U32, 0, 0}, {NLA_U32, 0, 0},
107 };
108
109 struct nlattr* packagetb[NCSI_PKG_ATTR_MAX + 1] = {nullptr};
110 struct nla_policy packagePolicy[NCSI_PKG_ATTR_MAX + 1] = {
111 {NLA_UNSPEC, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_U32, 0, 0},
112 {NLA_FLAG, 0, 0}, {NLA_NESTED, 0, 0},
113 };
114
115 struct nlattr* channeltb[NCSI_CHANNEL_ATTR_MAX + 1] = {nullptr};
116 struct nla_policy channelPolicy[NCSI_CHANNEL_ATTR_MAX + 1] = {
117 {NLA_UNSPEC, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_U32, 0, 0},
118 {NLA_FLAG, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_UNSPEC, 0, 0},
119 };
120
121 auto ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsiPolicy);
122 if (!tb[NCSI_ATTR_PACKAGE_LIST])
123 {
124 lg2::error("No Packages");
125 return -1;
126 }
127
128 auto attrTgt = static_cast<nlattr*>(nla_data(tb[NCSI_ATTR_PACKAGE_LIST]));
129 if (!attrTgt)
130 {
131 lg2::error("Package list attribute is null");
132 return -1;
133 }
134
135 auto rem = nla_len(tb[NCSI_ATTR_PACKAGE_LIST]);
136 nla_for_each_nested(attrTgt, tb[NCSI_ATTR_PACKAGE_LIST], rem)
137 {
138 ret = nla_parse_nested(packagetb, NCSI_PKG_ATTR_MAX, attrTgt,
139 packagePolicy);
140 if (ret < 0)
141 {
142 lg2::error("Failed to parse package nested");
143 return -1;
144 }
145
146 PackageInfo pkg;
147
148 if (packagetb[NCSI_PKG_ATTR_ID])
149 {
150 auto attrID = nla_get_u32(packagetb[NCSI_PKG_ATTR_ID]);
151 pkg.id = attrID;
152 }
153 else
154 {
155 lg2::debug("Package with no id");
156 }
157
158 if (packagetb[NCSI_PKG_ATTR_FORCED])
159 {
160 pkg.forced = true;
161 }
162
163 auto channelListTarget = static_cast<nlattr*>(
164 nla_data(packagetb[NCSI_PKG_ATTR_CHANNEL_LIST]));
165
166 auto channelrem = nla_len(packagetb[NCSI_PKG_ATTR_CHANNEL_LIST]);
167 nla_for_each_nested(channelListTarget,
168 packagetb[NCSI_PKG_ATTR_CHANNEL_LIST], channelrem)
169 {
170 ret = nla_parse_nested(channeltb, NCSI_CHANNEL_ATTR_MAX,
171 channelListTarget, channelPolicy);
172 if (ret < 0)
173 {
174 lg2::error("Failed to parse channel nested");
175 continue;
176 }
177
178 ChannelInfo chan;
179
180 if (channeltb[NCSI_CHANNEL_ATTR_ID])
181 {
182 chan.id = nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_ID]);
183 chan.active = !!channeltb[NCSI_CHANNEL_ATTR_ACTIVE];
184 chan.forced = !!channeltb[NCSI_CHANNEL_ATTR_FORCED];
185 }
186 else
187 {
188 lg2::debug("Channel with no ID");
189 continue;
190 }
191
192 if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR])
193 {
194 chan.version_major =
195 nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR]);
196 }
197 if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR])
198 {
199 chan.version_minor =
200 nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR]);
201 }
202 if (channeltb[NCSI_CHANNEL_ATTR_VERSION_STR])
203 {
204 chan.version =
205 nla_get_string(channeltb[NCSI_CHANNEL_ATTR_VERSION_STR]);
206 }
207 if (channeltb[NCSI_CHANNEL_ATTR_LINK_STATE])
208 {
209 chan.link_state =
210 nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_LINK_STATE]);
211 }
212 if (channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST])
213 {
214 auto vids = channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST];
215 auto vid = static_cast<nlattr*>(nla_data(vids));
216 auto len = nla_len(vids);
217 while (nla_ok(vid, len))
218 {
219 auto id = nla_get_u16(vid);
220 chan.vlan_ids.push_back(id);
221 vid = nla_next(vid, &len);
222 }
223 }
224 pkg.channels.push_back(chan);
225 }
226
227 info->info->packages.push_back(pkg);
228 }
229 return static_cast<int>(NL_STOP);
230 };
231
232 struct sendCallBackContext
233 {
234 NCSIResponse resp;
235 };
236
__anon66112f010202(struct nl_msg* msg, void* arg) 237 CallBack sendCallBack = [](struct nl_msg* msg, void* arg) {
238 using namespace phosphor::network::ncsi;
239 auto nlh = nlmsg_hdr(msg);
240 struct nlattr* tb[NCSI_ATTR_MAX + 1] = {nullptr};
241 static struct nla_policy ncsiPolicy[NCSI_ATTR_MAX + 1] = {
242 {NLA_UNSPEC, 0, 0}, {NLA_U32, 0, 0}, {NLA_NESTED, 0, 0},
243 {NLA_U32, 0, 0}, {NLA_U32, 0, 0}, {NLA_BINARY, 0, 0},
244 {NLA_FLAG, 0, 0}, {NLA_U32, 0, 0}, {NLA_U32, 0, 0},
245 };
246
247 if (arg == nullptr)
248 {
249 lg2::error("Internal error: invalid send callback context");
250 return -1;
251 }
252
253 struct sendCallBackContext* ctx = (struct sendCallBackContext*)arg;
254
255 auto ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsiPolicy);
256 if (ret)
257 {
258 lg2::error("Failed to parse message");
259 return ret;
260 }
261
262 if (tb[NCSI_ATTR_DATA] == nullptr)
263 {
264 lg2::error("Response: No data");
265 return -1;
266 }
267
268 size_t data_len = nla_len(tb[NCSI_ATTR_DATA]);
269 unsigned char* data = (unsigned char*)nla_data(tb[NCSI_ATTR_DATA]);
270
271 ctx->resp.full_payload.assign(data, data + data_len);
272
273 int rc = ctx->resp.parseFullPayload();
274 if (rc)
275 {
276 return -1;
277 }
278
279 return static_cast<int>(NL_STOP);
280 };
281
applyCmd(NetlinkInterface & interface,const NetlinkCommand & cmd,int package=DEFAULT_VALUE,int channel=DEFAULT_VALUE,int flags=NONE,CallBack function=nullptr,void * arg=nullptr)282 int applyCmd(NetlinkInterface& interface, const NetlinkCommand& cmd,
283 int package = DEFAULT_VALUE, int channel = DEFAULT_VALUE,
284 int flags = NONE, CallBack function = nullptr, void* arg = nullptr)
285 {
286 nlSocketPtr socket(nl_socket_alloc(), &::nl_socket_free);
287 if (socket == nullptr)
288 {
289 lg2::error("Unable to allocate memory for the socket");
290 return -ENOMEM;
291 }
292
293 nl_socket_disable_auto_ack(socket.get());
294
295 auto ret = genl_connect(socket.get());
296 if (ret < 0)
297 {
298 lg2::error("Failed to open the socket , RC : {RC}", "RC", ret);
299 return ret;
300 }
301
302 auto driverID = genl_ctrl_resolve(socket.get(), "NCSI");
303 if (driverID < 0)
304 {
305 lg2::error("Failed to resolve, RC : {RC}", "RC", ret);
306 return driverID;
307 }
308
309 nlMsgPtr msg(nlmsg_alloc(), &::nlmsg_free);
310 if (msg == nullptr)
311 {
312 lg2::error("Unable to allocate memory for the message");
313 return -ENOMEM;
314 }
315
316 auto msgHdr = genlmsg_put(msg.get(), NL_AUTO_PORT, NL_AUTO_SEQ, driverID, 0,
317 flags, cmd.ncsi_cmd, 0);
318 if (!msgHdr)
319 {
320 lg2::error("Unable to add the netlink headers , COMMAND : {COMMAND}",
321 "COMMAND", cmd.ncsi_cmd);
322 return -ENOMEM;
323 }
324
325 if (package != DEFAULT_VALUE)
326 {
327 ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_PACKAGE_ID,
328 package);
329 if (ret < 0)
330 {
331 lg2::error("Failed to set the attribute , RC : {RC} PACKAGE "
332 "{PACKAGE}",
333 "RC", ret, "PACKAGE", lg2::hex, package);
334 return ret;
335 }
336 }
337
338 if (channel != DEFAULT_VALUE)
339 {
340 ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_CHANNEL_ID,
341 channel);
342 if (ret < 0)
343 {
344 lg2::error("Failed to set the attribute , RC : {RC} CHANNEL : "
345 "{CHANNEL}",
346 "RC", ret, "CHANNEL", lg2::hex, channel);
347 return ret;
348 }
349 }
350
351 ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_IFINDEX,
352 interface.ifindex);
353 if (ret < 0)
354 {
355 lg2::error("Failed to set the attribute , RC : {RC} INTERFACE : "
356 "{INTERFACE}",
357 "RC", ret, "INTERFACE", interface);
358 return ret;
359 }
360
361 if ((cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK) ||
362 (cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_CHANNEL_MASK))
363 {
364 if (cmd.payload.size() != sizeof(unsigned int))
365 {
366 lg2::error("Package/Channel mask must be 32-bits");
367 return -EINVAL;
368 }
369 int maskAttr =
370 cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK
371 ? NCSI_ATTR_PACKAGE_MASK
372 : NCSI_ATTR_CHANNEL_MASK;
373 ret = nla_put_u32(
374 msg.get(), maskAttr,
375 *(reinterpret_cast<const unsigned int*>(cmd.payload.data())));
376 if (ret < 0)
377 {
378 lg2::error("Failed to set the mask attribute, RC : {RC}", "RC",
379 ret);
380 return ret;
381 }
382 }
383 else if (cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SEND_CMD)
384 {
385 std::vector<unsigned char> pl(
386 sizeof(NCSIPacketHeader) + cmd.payload.size());
387 NCSIPacketHeader* hdr = (NCSIPacketHeader*)pl.data();
388
389 std::copy(cmd.payload.begin(), cmd.payload.end(),
390 pl.begin() + sizeof(NCSIPacketHeader));
391
392 hdr->type = cmd.operation;
393 hdr->length = htons(cmd.payload.size());
394
395 ret = nla_put(msg.get(), ncsi_nl_attrs::NCSI_ATTR_DATA, pl.size(),
396 pl.data());
397 if (ret < 0)
398 {
399 lg2::error("Failed to set the data attribute, RC : {RC}", "RC",
400 ret);
401 return ret;
402 }
403
404 nl_socket_disable_seq_check(socket.get());
405 }
406
407 // Add a callback function to the socket
408 enum nl_cb_kind cb_kind = function ? NL_CB_CUSTOM : NL_CB_DEFAULT;
409 nl_socket_modify_cb(socket.get(), NL_CB_VALID, cb_kind, function, arg);
410
411 ret = nl_send_auto(socket.get(), msg.get());
412 if (ret < 0)
413 {
414 lg2::error("Failed to send the message , RC : {RC}", "RC", ret);
415 return ret;
416 }
417
418 ret = nl_recvmsgs_default(socket.get());
419 if (ret < 0)
420 {
421 lg2::error("Failed to receive the message , RC : {RC}", "RC", ret);
422 return ret;
423 }
424
425 return 0;
426 }
427
428 } // namespace internal
429
to_string(Interface & interface)430 std::string to_string(Interface& interface)
431 {
432 return interface.toString();
433 }
434
NetlinkInterface(int ifindex)435 NetlinkInterface::NetlinkInterface(int ifindex) : ifindex(ifindex) {}
436
toString()437 std::string NetlinkInterface::toString()
438 {
439 return std::to_string(ifindex);
440 }
441
sendCommand(NCSICommand & cmd)442 std::optional<NCSIResponse> NetlinkInterface::sendCommand(NCSICommand& cmd)
443 {
444 lg2::debug("Send Command, CHANNEL : {CHANNEL} , PACKAGE : {PACKAGE}, "
445 "INTERFACE: {INTERFACE}",
446 "CHANNEL", lg2::hex, cmd.getChannel(), "PACKAGE", lg2::hex,
447 cmd.package, "INTERFACE", this);
448
449 internal::sendCallBackContext ctx{};
450
451 internal::NetlinkCommand nl_cmd(ncsi_nl_commands::NCSI_CMD_SEND_CMD,
452 cmd.opcode, cmd.payload);
453
454 int rc = internal::applyCmd(*this, nl_cmd, cmd.package, cmd.getChannel(),
455 NONE, internal::sendCallBack, &ctx);
456
457 if (rc < 0)
458 {
459 return {};
460 }
461
462 return ctx.resp;
463 }
464
setChannel(int package,int channel)465 int NetlinkInterface::setChannel(int package, int channel)
466 {
467 lg2::debug("Set CHANNEL : {CHANNEL} , PACKAGE : {PACKAGE}, INTERFACE : "
468 "{INTERFACE}",
469 "CHANNEL", lg2::hex, channel, "PACKAGE", lg2::hex, package,
470 "INTERFACE", this);
471
472 internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_INTERFACE);
473
474 return internal::applyCmd(*this, cmd, package, channel);
475 }
476
clearInterface()477 int NetlinkInterface::clearInterface()
478 {
479 lg2::debug("ClearInterface , INTERFACE : {INTERFACE}", "INTERFACE", this);
480
481 internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_CLEAR_INTERFACE);
482 return internal::applyCmd(*this, cmd);
483 }
484
getInfo(int package)485 std::optional<InterfaceInfo> NetlinkInterface::getInfo(int package)
486 {
487 int rc, flags = package == DEFAULT_VALUE ? NLM_F_DUMP : NONE;
488 InterfaceInfo info;
489
490 lg2::debug("Get Info , PACKAGE : {PACKAGE}, INTERFACE: {INTERFACE}",
491 "PACKAGE", lg2::hex, package, "INTERFACE", this);
492
493 struct internal::infoCallBackContext ctx = {
494 .info = &info,
495 };
496
497 internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_PKG_INFO);
498
499 rc = internal::applyCmd(*this, cmd, package, DEFAULT_VALUE, flags,
500 internal::infoCallBack, &ctx);
501
502 if (rc < 0)
503 {
504 return {};
505 }
506
507 return info;
508 }
509
setPackageMask(unsigned int mask)510 int NetlinkInterface::setPackageMask(unsigned int mask)
511 {
512 lg2::debug("Set Package Mask , INTERFACE: {INTERFACE} MASK: {MASK}",
513 "INTERFACE", this, "MASK", lg2::hex, mask);
514 auto payload = std::span<const unsigned char>(
515 reinterpret_cast<const unsigned char*>(&mask),
516 reinterpret_cast<const unsigned char*>(&mask) + sizeof(decltype(mask)));
517
518 internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK, 0,
519 payload);
520 return internal::applyCmd(*this, cmd);
521 }
522
setChannelMask(int package,unsigned int mask)523 int NetlinkInterface::setChannelMask(int package, unsigned int mask)
524 {
525 lg2::debug(
526 "Set Channel Mask , INTERFACE: {INTERFACE}, PACKAGE : {PACKAGE} MASK: {MASK}",
527 "INTERFACE", this, "PACKAGE", lg2::hex, package, "MASK", lg2::hex,
528 mask);
529 auto payload = std::span<const unsigned char>(
530 reinterpret_cast<const unsigned char*>(&mask),
531 reinterpret_cast<const unsigned char*>(&mask) + sizeof(decltype(mask)));
532
533 internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_CHANNEL_MASK, 0,
534 payload);
535 return internal::applyCmd(*this, cmd);
536 }
537
parseFullPayload()538 int NCSIResponse::parseFullPayload()
539 {
540 if (this->full_payload.size() < sizeof(internal::NCSIPacketHeader) +
541 sizeof(internal::NCSIResponsePayload))
542 {
543 lg2::error("Response: Not enough data for a response message");
544 return -1;
545 }
546
547 internal::NCSIPacketHeader* respHeader =
548 reinterpret_cast<decltype(respHeader)>(this->full_payload.data());
549
550 unsigned int payloadLen = ntohs(respHeader->length & htons(0x0fff));
551 /* we have determined that the payload size is larger than *respHeader,
552 * so cannot underflow here */
553 if (payloadLen > this->full_payload.size() - sizeof(*respHeader))
554 {
555 lg2::error("Invalid header length {HDRLEN} (vs {LEN}) in response",
556 "HDRLEN", payloadLen, "LEN",
557 this->full_payload.size() - sizeof(*respHeader));
558 return -1;
559 }
560
561 this->opcode = respHeader->type;
562 this->payload =
563 std::span(this->full_payload.begin() + sizeof(*respHeader), payloadLen);
564
565 internal::NCSIResponsePayload* respPayload =
566 reinterpret_cast<decltype(respPayload)>(this->payload.data());
567 this->response = ntohs(respPayload->response);
568 this->reason = ntohs(respPayload->reason);
569
570 return 0;
571 }
572
573 static const uint8_t MCTP_TYPE_NCSI = 2;
574
575 struct NCSIResponsePayload
576 {
577 uint16_t response;
578 uint16_t reason;
579 };
580
sendCommand(NCSICommand & cmd)581 std::optional<NCSIResponse> MCTPInterface::sendCommand(NCSICommand& cmd)
582 {
583 static constexpr uint8_t mcid = 0; /* no need to distinguish controllers */
584 static constexpr size_t maxRespLen = 16384;
585 size_t payloadLen, padLen;
586 ssize_t wlen, rlen;
587
588 payloadLen = cmd.payload.size();
589
590 auto tmp = allocateIID();
591 if (!tmp.has_value())
592 {
593 return {};
594 }
595 uint8_t iid = *tmp;
596
597 internal::NCSIPacketHeader cmdHeader{};
598 cmdHeader.MCID = mcid;
599 cmdHeader.revision = 1;
600 cmdHeader.id = iid;
601 cmdHeader.type = cmd.opcode;
602 cmdHeader.channel = (uint8_t)(cmd.package << 5 | cmd.getChannel());
603 cmdHeader.length = htons(payloadLen);
604
605 struct iovec iov[3];
606 iov[0].iov_base = &cmdHeader;
607 iov[0].iov_len = sizeof(cmdHeader);
608 iov[1].iov_base = cmd.payload.data();
609 iov[1].iov_len = payloadLen;
610
611 /* the checksum must appear on a 4-byte boundary */
612 padLen = 4 - (payloadLen & 0x3);
613 if (padLen == 4)
614 {
615 padLen = 0;
616 }
617 uint8_t crc32buf[8] = {};
618 /* todo: set csum; zeros currently indicate no checksum present */
619 uint32_t crc32 = 0;
620
621 memcpy(crc32buf + padLen, &crc32, sizeof(crc32));
622 padLen += sizeof(crc32);
623
624 iov[2].iov_base = crc32buf;
625 iov[2].iov_len = padLen;
626
627 struct sockaddr_mctp addr = {};
628 addr.smctp_family = AF_MCTP;
629 addr.smctp_network = net;
630 addr.smctp_addr.s_addr = eid;
631 addr.smctp_tag = MCTP_TAG_OWNER;
632 addr.smctp_type = MCTP_TYPE_NCSI;
633
634 struct msghdr msg = {};
635 msg.msg_name = &addr;
636 msg.msg_namelen = sizeof(addr);
637 msg.msg_iov = iov;
638 msg.msg_iovlen = 3;
639
640 wlen = sendmsg(sd, &msg, 0);
641 if (wlen < 0)
642 {
643 lg2::error("Failed to send MCTP message, ERRNO: {ERRNO}", "ERRNO",
644 -wlen);
645 return {};
646 }
647 else if ((size_t)wlen != sizeof(cmdHeader) + payloadLen + padLen)
648 {
649 lg2::error("Short write sending MCTP message, LEN: {LEN}", "LEN", wlen);
650 return {};
651 }
652
653 internal::NCSIPacketHeader* respHeader;
654 NCSIResponsePayload* respPayload;
655 NCSIResponse resp{};
656
657 resp.full_payload.resize(maxRespLen);
658 iov[0].iov_len = resp.full_payload.size();
659 iov[0].iov_base = resp.full_payload.data();
660
661 msg.msg_name = &addr;
662 msg.msg_namelen = sizeof(addr);
663 msg.msg_iov = iov;
664 msg.msg_iovlen = 1;
665
666 /* we have set SO_RCVTIMEO, so this won't block forever... */
667 rlen = recvmsg(sd, &msg, MSG_TRUNC);
668 if (rlen < 0)
669 {
670 lg2::error("Failed to read MCTP response, ERRNO: {ERRNO}", "ERRNO",
671 -rlen);
672 return {};
673 }
674 else if ((size_t)rlen < sizeof(*respHeader) + sizeof(*respPayload))
675 {
676 lg2::error("Short read receiving MCTP message, LEN: {LEN}", "LEN",
677 rlen);
678 return {};
679 }
680 else if ((size_t)rlen > maxRespLen)
681 {
682 lg2::error("MCTP response is too large, LEN: {LEN}", "LEN", rlen);
683 return {};
684 }
685
686 resp.full_payload.resize(rlen);
687
688 respHeader =
689 reinterpret_cast<decltype(respHeader)>(resp.full_payload.data());
690
691 /* header validation */
692 if (respHeader->MCID != mcid)
693 {
694 lg2::error("Invalid MCID {MCID} in response", "MCID", lg2::hex,
695 respHeader->MCID);
696 return {};
697 }
698
699 if (respHeader->id != iid)
700 {
701 lg2::error("Invalid IID {IID} in response", "IID", lg2::hex,
702 respHeader->id);
703 return {};
704 }
705
706 if (respHeader->type != (cmd.opcode | 0x80))
707 {
708 lg2::error("Invalid opcode {OPCODE} in response", "OPCODE", lg2::hex,
709 respHeader->type);
710 return {};
711 }
712
713 int rc = resp.parseFullPayload();
714 if (rc)
715 {
716 return {};
717 }
718
719 return resp;
720 }
721
toString()722 std::string MCTPInterface::toString()
723 {
724 return std::to_string(net) + "," + std::to_string(eid);
725 }
726
MCTPInterface(int net,uint8_t eid)727 MCTPInterface::MCTPInterface(int net, uint8_t eid) : net(net), eid(eid)
728 {
729 static const struct timeval receiveTimeout = {
730 .tv_sec = 1,
731 .tv_usec = 0,
732 };
733
734 int _sd = socket(AF_MCTP, SOCK_DGRAM, 0);
735 if (_sd < 0)
736 {
737 throw std::system_error(errno, std::system_category(),
738 "Can't create MCTP socket");
739 }
740
741 int rc = setsockopt(_sd, SOL_SOCKET, SO_RCVTIMEO, &receiveTimeout,
742 sizeof(receiveTimeout));
743 if (rc != 0)
744 {
745 throw std::system_error(errno, std::system_category(),
746 "Can't set socket receive timemout");
747 }
748
749 sd = _sd;
750 }
751
~MCTPInterface()752 MCTPInterface::~MCTPInterface()
753 {
754 close(sd);
755 }
756
757 /* Small fd wrapper to provide RAII semantics, closing the IID file descriptor
758 * when we go out of scope.
759 */
760 struct IidFd
761 {
762 int fd;
IidFdphosphor::network::ncsi::IidFd763 IidFd(int _fd) : fd(_fd) {};
~IidFdphosphor::network::ncsi::IidFd764 ~IidFd()
765 {
766 close(fd);
767 };
768 };
769
allocateIID()770 std::optional<uint8_t> MCTPInterface::allocateIID()
771 {
772 int fd = open(mctp_iid_path, O_RDWR | O_CREAT, 0600);
773 if (fd < 0)
774 {
775 lg2::warning("Error opening IID database {FILE}: {ERROR}", "FILE",
776 mctp_iid_path, "ERROR", strerror(errno));
777 return {};
778 }
779
780 IidFd iidFd(fd);
781
782 /* lock while we read/modity/write; the lock will be short-lived, so
783 * we keep it simple and lock the entire file range
784 */
785 struct flock flock = {
786 .l_type = F_WRLCK,
787 .l_whence = SEEK_SET,
788 .l_start = 0,
789 .l_len = 0,
790 .l_pid = 0,
791 };
792
793 int rc = fcntl(iidFd.fd, F_OFD_SETLKW, &flock);
794 if (rc)
795 {
796 lg2::warning("Error locking IID database {FILE}: {ERROR}", "FILE",
797 mctp_iid_path, "ERROR", strerror(errno));
798 return {};
799 }
800
801 /* An EOF (rc == 0) would indicate that we don't yet have an entry for that
802 * eid, which we handle as iid = 0.
803 */
804 uint8_t iid = 0;
805 rc = pread(iidFd.fd, &iid, sizeof(iid), eid);
806 if (rc < 0)
807 {
808 lg2::warning("Error reading IID database {FILE}: {ERROR}", "FILE",
809 mctp_iid_path, "ERROR", strerror(errno));
810 return {};
811 }
812
813 /* DSP0222 defines valid IIDs in the range [1, 0xff], so manually wrap */
814 if (iid == 0xff)
815 {
816 iid = 1;
817 }
818 else
819 {
820 iid++;
821 }
822
823 rc = pwrite(iidFd.fd, &iid, sizeof(iid), eid);
824 if (rc != sizeof(iid))
825 {
826 lg2::warning("Error writing IID database {FILE}: {ERROR}", "FILE",
827 mctp_iid_path, "ERROR", strerror(errno));
828 return {};
829 }
830
831 return iid;
832 }
833
834 } // namespace ncsi
835 } // namespace network
836 } // namespace phosphor
837