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 auto ret = genl_connect(socket.get());
297 if (ret < 0)
298 {
299 std::cerr << "Failed to open the socket , RC : " << ret << std::endl;
300 return ret;
301 }
302
303 auto driverID = genl_ctrl_resolve(socket.get(), "NCSI");
304 if (driverID < 0)
305 {
306 std::cerr << "Failed to resolve, RC : " << ret << std::endl;
307 return driverID;
308 }
309
310 nlMsgPtr msg(nlmsg_alloc(), &::nlmsg_free);
311 if (msg == nullptr)
312 {
313 std::cerr << "Unable to allocate memory for the message" << std::endl;
314 return -ENOMEM;
315 }
316
317 auto msgHdr = genlmsg_put(msg.get(), NL_AUTO_PORT, NL_AUTO_SEQ, driverID, 0,
318 flags, cmd.ncsi_cmd, 0);
319 if (!msgHdr)
320 {
321 std::cerr << "Unable to add the netlink headers , COMMAND : "
322 << cmd.ncsi_cmd << std::endl;
323 return -ENOMEM;
324 }
325
326 if (package != DEFAULT_VALUE)
327 {
328 ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_PACKAGE_ID,
329 package);
330 if (ret < 0)
331 {
332 std::cerr << "Failed to set the attribute , RC : " << ret
333 << " PACKAGE " << package << std::endl;
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 std::cerr << "Failed to set the attribute , RC : " << ret
345 << " CHANNEL : " << channel << std::endl;
346 return ret;
347 }
348 }
349
350 ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_IFINDEX,
351 interface.ifindex);
352 if (ret < 0)
353 {
354 std::cerr << "Failed to set the attribute , RC : " << ret
355 << " INTERFACE : " << interface.ifindex << std::endl;
356 return ret;
357 }
358
359 if ((cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK) ||
360 (cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_CHANNEL_MASK))
361 {
362 if (cmd.payload.size() != sizeof(unsigned int))
363 {
364 std::cerr << "Package/Channel mask must be 32-bits" << std::endl;
365 return -EINVAL;
366 }
367 int maskAttr =
368 cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK
369 ? NCSI_ATTR_PACKAGE_MASK
370 : NCSI_ATTR_CHANNEL_MASK;
371 ret = nla_put_u32(
372 msg.get(), maskAttr,
373 *(reinterpret_cast<const unsigned int*>(cmd.payload.data())));
374 if (ret < 0)
375 {
376 std::cerr << "Failed to set the mask attribute, RC : " << ret
377 << std::endl;
378 return ret;
379 }
380 }
381 else if (cmd.ncsi_cmd == ncsi_nl_commands::NCSI_CMD_SEND_CMD)
382 {
383 std::vector<unsigned char> pl(
384 sizeof(NCSIPacketHeader) + cmd.payload.size());
385 NCSIPacketHeader* hdr = reinterpret_cast<NCSIPacketHeader*>(pl.data());
386
387 std::copy(cmd.payload.begin(), cmd.payload.end(),
388 pl.begin() + sizeof(NCSIPacketHeader));
389
390 hdr->type = cmd.operation;
391 hdr->length = htons(cmd.payload.size());
392
393 ret = nla_put(msg.get(), ncsi_nl_attrs::NCSI_ATTR_DATA, pl.size(),
394 pl.data());
395 if (ret < 0)
396 {
397 std::cerr << "Failed to set the data attribute, RC : " << ret
398 << std::endl;
399 return ret;
400 }
401
402 nl_socket_disable_auto_ack(socket.get());
403 nl_socket_disable_seq_check(socket.get());
404 }
405
406 // Add a callback function to the socket
407 enum nl_cb_kind cb_kind = function ? NL_CB_CUSTOM : NL_CB_DEFAULT;
408 nl_socket_modify_cb(socket.get(), NL_CB_VALID, cb_kind, function, arg);
409
410 ret = nl_send_auto(socket.get(), msg.get());
411 if (ret < 0)
412 {
413 std::cerr << "Failed to send the message , RC : " << ret << std::endl;
414 return ret;
415 }
416
417 ret = nl_recvmsgs_default(socket.get());
418 if (ret < 0)
419 {
420 std::cerr << "Failed to receive the message , RC : " << ret
421 << std::endl;
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 std::cout << "Send Command, CHANNEL : " << std::hex << (int)cmd.getChannel()
445 << " , PACKAGE : " << (int)cmd.package << " , INTERFACE: " << this
446 << std::dec << std::endl;
447
448 internal::sendCallBackContext ctx{};
449
450 internal::NetlinkCommand nl_cmd(ncsi_nl_commands::NCSI_CMD_SEND_CMD,
451 cmd.opcode, cmd.payload);
452
453 int rc = internal::applyCmd(*this, nl_cmd, cmd.package, cmd.getChannel(),
454 NONE, internal::sendCallBack, &ctx);
455
456 if (rc < 0)
457 {
458 return {};
459 }
460
461 return ctx.resp;
462 }
463
setChannel(int package,int channel)464 int NetlinkInterface::setChannel(int package, int channel)
465 {
466 std::cout << "Set CHANNEL : " << std::hex << channel << " , PACKAGE : "
467 << package << " , INTERFACE : " << this << std::dec << std::endl;
468
469 internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_INTERFACE);
470
471 return internal::applyCmd(*this, cmd, package, channel);
472 }
473
clearInterface()474 int NetlinkInterface::clearInterface()
475 {
476 std::cout << "ClearInterface , INTERFACE : " << this << std::endl;
477
478 internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_CLEAR_INTERFACE);
479 return internal::applyCmd(*this, cmd);
480 }
481
getInfo(int package)482 std::optional<InterfaceInfo> NetlinkInterface::getInfo(int package)
483 {
484 int rc, flags = package == DEFAULT_VALUE ? NLM_F_DUMP : NONE;
485 InterfaceInfo info;
486
487 std::cout << "Get Info , PACKAGE : " << std::hex << package
488 << " , INTERFACE: " << this << std::dec << std::endl;
489
490 struct internal::infoCallBackContext ctx = {
491 .info = &info,
492 };
493
494 internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_PKG_INFO);
495
496 rc = internal::applyCmd(*this, cmd, package, DEFAULT_VALUE, flags,
497 internal::infoCallBack, &ctx);
498
499 if (rc < 0)
500 {
501 return {};
502 }
503
504 return info;
505 }
506
setPackageMask(unsigned int mask)507 int NetlinkInterface::setPackageMask(unsigned int mask)
508 {
509 std::cout << "Set Package Mask , INTERFACE: " << this
510 << " MASK: " << std::hex << mask << std::dec << std::endl;
511 auto payload = std::span<const unsigned char>(
512 reinterpret_cast<const unsigned char*>(&mask),
513 reinterpret_cast<const unsigned char*>(&mask) + sizeof(decltype(mask)));
514
515 internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_PACKAGE_MASK, 0,
516 payload);
517 return internal::applyCmd(*this, cmd);
518 }
519
setChannelMask(int package,unsigned int mask)520 int NetlinkInterface::setChannelMask(int package, unsigned int mask)
521 {
522 std::cout << "Set Channel Mask , INTERFACE: " << this
523 << " , PACKAGE : " << std::hex << package << " MASK: " << mask
524 << std::dec << std::endl;
525 auto payload = std::span<const unsigned char>(
526 reinterpret_cast<const unsigned char*>(&mask),
527 reinterpret_cast<const unsigned char*>(&mask) + sizeof(decltype(mask)));
528
529 internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SET_CHANNEL_MASK, 0,
530 payload);
531 return internal::applyCmd(*this, cmd);
532 }
533
parseFullPayload()534 int NCSIResponse::parseFullPayload()
535 {
536 if (this->full_payload.size() < sizeof(internal::NCSIPacketHeader) +
537 sizeof(internal::NCSIResponsePayload))
538 {
539 std::cerr << "Response: Not enough data for a response message"
540 << std::endl;
541 return -1;
542 }
543
544 internal::NCSIPacketHeader* respHeader =
545 reinterpret_cast<decltype(respHeader)>(this->full_payload.data());
546
547 unsigned int payloadLen = ntohs(respHeader->length & htons(0x0fff));
548 /* we have determined that the payload size is larger than *respHeader,
549 * so cannot underflow here */
550 if (payloadLen > this->full_payload.size() - sizeof(*respHeader))
551 {
552 std::cerr << "Invalid header length " << payloadLen << " (vs "
553 << (this->full_payload.size() - sizeof(*respHeader))
554 << ") in response" << std::endl;
555 return -1;
556 }
557
558 this->opcode = respHeader->type;
559 this->payload =
560 std::span(this->full_payload.begin() + sizeof(*respHeader), payloadLen);
561
562 internal::NCSIResponsePayload* respPayload =
563 reinterpret_cast<decltype(respPayload)>(this->payload.data());
564 this->response = ntohs(respPayload->response);
565 this->reason = ntohs(respPayload->reason);
566
567 return 0;
568 }
569
570 static const uint8_t MCTP_TYPE_NCSI = 2;
571
572 struct NCSIResponsePayload
573 {
574 uint16_t response;
575 uint16_t reason;
576 };
577
sendCommand(NCSICommand & cmd)578 std::optional<NCSIResponse> MCTPInterface::sendCommand(NCSICommand& cmd)
579 {
580 static constexpr uint8_t mcid = 0; /* no need to distinguish controllers */
581 static constexpr size_t maxRespLen = 16384;
582 size_t payloadLen, padLen;
583 ssize_t wlen, rlen;
584
585 payloadLen = cmd.payload.size();
586
587 auto tmp = allocateIID();
588 if (!tmp.has_value())
589 {
590 return {};
591 }
592 uint8_t iid = *tmp;
593
594 internal::NCSIPacketHeader cmdHeader{};
595 cmdHeader.MCID = mcid;
596 cmdHeader.revision = 1;
597 cmdHeader.id = iid;
598 cmdHeader.type = cmd.opcode;
599 cmdHeader.channel = (uint8_t)(cmd.package << 5 | cmd.getChannel());
600 cmdHeader.length = htons(payloadLen);
601
602 struct iovec iov[3];
603 iov[0].iov_base = &cmdHeader;
604 iov[0].iov_len = sizeof(cmdHeader);
605 iov[1].iov_base = cmd.payload.data();
606 iov[1].iov_len = payloadLen;
607
608 /* the checksum must appear on a 4-byte boundary */
609 padLen = 4 - (payloadLen & 0x3);
610 if (padLen == 4)
611 {
612 padLen = 0;
613 }
614 uint8_t crc32buf[8] = {};
615 /* todo: set csum; zeros currently indicate no checksum present */
616 uint32_t crc32 = 0;
617
618 memcpy(crc32buf + padLen, &crc32, sizeof(crc32));
619 padLen += sizeof(crc32);
620
621 iov[2].iov_base = crc32buf;
622 iov[2].iov_len = padLen;
623
624 struct sockaddr_mctp addr = {};
625 addr.smctp_family = AF_MCTP;
626 addr.smctp_network = net;
627 addr.smctp_addr.s_addr = eid;
628 addr.smctp_tag = MCTP_TAG_OWNER;
629 addr.smctp_type = MCTP_TYPE_NCSI;
630
631 struct msghdr msg = {};
632 msg.msg_name = &addr;
633 msg.msg_namelen = sizeof(addr);
634 msg.msg_iov = iov;
635 msg.msg_iovlen = 3;
636
637 wlen = sendmsg(sd, &msg, 0);
638 if (wlen < 0)
639 {
640 std::stringstream ss;
641 std::cout << "Failed to send MCTP message, ERRNO: " << -errno
642 << std::endl;
643
644 return {};
645 }
646 else if ((size_t)wlen != sizeof(cmdHeader) + payloadLen + padLen)
647 {
648 std::cout << "Short write sending MCTP message, LEN: " << wlen
649 << std::endl;
650 return {};
651 }
652
653 internal::NCSIPacketHeader* respHeader;
654 NCSIResponsePayload* respPayload = nullptr;
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 std::cerr << "Failed to read MCTP response, ERRNO: " << -rlen
671 << std::endl;
672 return {};
673 }
674 else if ((size_t)rlen < sizeof(*respHeader) + sizeof(*respPayload))
675 {
676 std::cerr << "Short read receiving MCTP message, LEN: " << rlen
677 << std::endl;
678 return {};
679 }
680 else if ((size_t)rlen > maxRespLen)
681 {
682 std::cerr << "MCTP response is too large, LEN: " << rlen << std::endl;
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 std::cerr << "Invalid MCID " << std::hex << (int)respHeader->MCID
695 << std::dec << " in response" << std::endl;
696 return {};
697 }
698
699 if (respHeader->id != iid)
700 {
701 std::cerr << "Invalid IID " << std::hex << (int)respHeader->id
702 << std::dec << " in response" << std::endl;
703 return {};
704 }
705
706 if (respHeader->type != (cmd.opcode | 0x80))
707 {
708 std::cerr << "Invalid opcode " << std::hex << (int)respHeader->type
709 << std::dec << " in response" << std::endl;
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 std::cerr << "Error opening IID database " << mctp_iid_path << ": "
776 << strerror(errno) << std::endl;
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 std::cerr << "Error locking IID database " << mctp_iid_path << ": "
797 << strerror(errno) << std::endl;
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 std::cerr << "Error reading IID database " << mctp_iid_path << ": "
809 << strerror(errno) << std::endl;
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 std::cerr << "Error writing IID database " << mctp_iid_path << ": "
827 << strerror(errno) << std::endl;
828 return {};
829 }
830
831 return iid;
832 }
833
834 } // namespace ncsi
835 } // namespace network
836 } // namespace phosphor
837