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