xref: /openbmc/ipmbbridge/ipmbbridged.cpp (revision 9898d612)
1 /* Copyright 2018 Intel
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *    http://www.apache.org/licenses/LICENSE-2.0
8  *
9  *  Unless required by applicable law or agreed to in writing, software
10  *  distributed under the License is distributed on an "AS IS" BASIS,
11  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  *  See the License for the specific language governing permissions and
13  *  limitations under the License.
14  */
15 
16 #include "ipmbbridged.hpp"
17 
18 #include "ipmbdefines.hpp"
19 #include "ipmbutils.hpp"
20 
21 #include <boost/algorithm/string/replace.hpp>
22 #include <boost/asio/io_context.hpp>
23 #include <boost/asio/write.hpp>
24 #include <nlohmann/json.hpp>
25 #include <phosphor-logging/log.hpp>
26 
27 #include <filesystem>
28 #include <fstream>
29 #include <list>
30 #include <tuple>
31 #include <unordered_map>
32 
33 /**
34  * @brief Dbus
35  */
36 static constexpr const char* ipmbBus = "xyz.openbmc_project.Ipmi.Channel.Ipmb";
37 static constexpr const char* ipmbObj = "/xyz/openbmc_project/Ipmi/Channel/Ipmb";
38 static constexpr const char* ipmbDbusIntf = "org.openbmc.Ipmb";
39 
40 boost::asio::io_context io;
41 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
42 
43 static std::list<IpmbChannel> ipmbChannels;
44 static const std::unordered_map<std::string, ipmbChannelType>
45     ipmbChannelTypeMap = {{"me", ipmbChannelType::me},
46                           {"ipmb", ipmbChannelType::ipmb}};
47 
48 /**
49  * @brief Ipmb request class methods
50  */
IpmbRequest()51 IpmbRequest::IpmbRequest()
52 {
53     data.reserve(ipmbMaxDataSize);
54 }
55 
IpmbRequest(uint8_t address,uint8_t netFn,uint8_t rsLun,uint8_t rqSA,uint8_t seq,uint8_t rqLun,uint8_t cmd,const std::vector<uint8_t> & inputData)56 IpmbRequest::IpmbRequest(uint8_t address, uint8_t netFn, uint8_t rsLun,
57                          uint8_t rqSA, uint8_t seq, uint8_t rqLun, uint8_t cmd,
58                          const std::vector<uint8_t>& inputData) :
59     address(address), netFn(netFn), rsLun(rsLun), rqSA(rqSA), seq(seq),
60     rqLun(rqLun), cmd(cmd), timer(io)
61 {
62     data.reserve(ipmbMaxDataSize);
63     state = ipmbRequestState::invalid;
64 
65     if (inputData.size() > 0)
66     {
67         data = std::move(inputData);
68     }
69 }
70 
i2cToIpmbConstruct(IPMB_HEADER * ipmbBuffer,size_t bufferLength)71 void IpmbRequest::i2cToIpmbConstruct(IPMB_HEADER* ipmbBuffer,
72                                      size_t bufferLength)
73 {
74     // constructing ipmb request from i2c buffer
75     netFn = ipmbNetFnGet(ipmbBuffer->Header.Req.rsNetFnLUN);
76     rsLun = ipmbLunFromNetFnLunGet(ipmbBuffer->Header.Req.rsNetFnLUN);
77     rqSA = ipmbBuffer->Header.Req.rqSA;
78     seq = ipmbSeqGet(ipmbBuffer->Header.Req.rqSeqLUN);
79     rqLun = ipmbLunFromSeqLunGet(ipmbBuffer->Header.Req.rqSeqLUN);
80     cmd = ipmbBuffer->Header.Req.cmd;
81 
82     size_t dataLength =
83         bufferLength - (ipmbConnectionHeaderLength +
84                         ipmbRequestDataHeaderLength + ipmbChecksumSize);
85 
86     if (dataLength > 0)
87     {
88         data.insert(data.end(), ipmbBuffer->Header.Req.data,
89                     &ipmbBuffer->Header.Req.data[dataLength]);
90     }
91 }
92 
ipmbToi2cConstruct(std::vector<uint8_t> & buffer)93 int IpmbRequest::ipmbToi2cConstruct(std::vector<uint8_t>& buffer)
94 {
95     /* Add one byte for length byte as per required by driver */
96     size_t bufferLength = 1 + data.size() + ipmbRequestDataHeaderLength +
97                           ipmbConnectionHeaderLength + ipmbChecksumSize;
98 
99     if (bufferLength > ipmbMaxFrameLength)
100     {
101         return -1;
102     }
103 
104     buffer.resize(bufferLength);
105     static_assert(ipmbMaxFrameLength >= sizeof(IPMB_HEADER));
106     IPMB_PKT* ipmbPkt = reinterpret_cast<IPMB_PKT*>(buffer.data());
107     ipmbPkt->len = bufferLength - 1;
108     IPMB_HEADER* ipmbBuffer = &(ipmbPkt->hdr);
109 
110     // constructing buffer from ipmb request
111     ipmbBuffer->Header.Req.address = address;
112     ipmbBuffer->Header.Req.rsNetFnLUN = ipmbNetFnLunSet(netFn, rsLun);
113     ipmbBuffer->Header.Req.rqSA = rqSA;
114     ipmbBuffer->Header.Req.rqSeqLUN = ipmbSeqLunSet(seq, rqLun);
115     ipmbBuffer->Header.Req.cmd = cmd;
116 
117     ipmbBuffer->Header.Req.checksum1 = ipmbChecksumCompute(
118         (uint8_t*)ipmbBuffer, ipmbConnectionHeaderLength - ipmbChecksumSize);
119 
120     if (data.size() > 0)
121     {
122         std::copy(data.begin(), data.end(), ipmbBuffer->Header.Req.data);
123     }
124 
125     buffer[bufferLength - ipmbChecksumSize] =
126         ipmbChecksumCompute((uint8_t*)ipmbBuffer + ipmbChecksum2StartOffset,
127                             (ipmbRequestDataHeaderLength + data.size()));
128 
129     return 0;
130 }
131 
132 std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>
returnMatchedResponse()133     IpmbRequest::returnMatchedResponse()
134 {
135     return std::make_tuple(
136         static_cast<int>(ipmbResponseStatus::success), matchedResponse->netFn,
137         matchedResponse->rsLun, matchedResponse->cmd,
138         matchedResponse->completionCode, matchedResponse->data);
139 }
140 
141 static std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>
returnStatus(ipmbResponseStatus status)142     returnStatus(ipmbResponseStatus status)
143 {
144     // we only want to send status here, other fields are not relevant
145     return std::make_tuple(static_cast<int>(status), 0, 0, 0, 0,
146                            std::vector<uint8_t>(0));
147 }
148 
149 /**
150  * @brief Ipmb response class methods
151  */
IpmbResponse()152 IpmbResponse::IpmbResponse()
153 {
154     data.reserve(ipmbMaxDataSize);
155 }
156 
IpmbResponse(uint8_t address,uint8_t netFn,uint8_t rqLun,uint8_t rsSA,uint8_t seq,uint8_t rsLun,uint8_t cmd,uint8_t completionCode,const std::vector<uint8_t> & inputData)157 IpmbResponse::IpmbResponse(uint8_t address, uint8_t netFn, uint8_t rqLun,
158                            uint8_t rsSA, uint8_t seq, uint8_t rsLun,
159                            uint8_t cmd, uint8_t completionCode,
160                            const std::vector<uint8_t>& inputData) :
161     address(address), netFn(netFn), rqLun(rqLun), rsSA(rsSA), seq(seq),
162     rsLun(rsLun), cmd(cmd), completionCode(completionCode)
163 {
164     data.reserve(ipmbMaxDataSize);
165 
166     if (inputData.size() > 0)
167     {
168         data = std::move(inputData);
169     }
170 }
171 
i2cToIpmbConstruct(IPMB_HEADER * ipmbBuffer,size_t bufferLength)172 void IpmbResponse::i2cToIpmbConstruct(IPMB_HEADER* ipmbBuffer,
173                                       size_t bufferLength)
174 {
175     netFn = ipmbNetFnGet(ipmbBuffer->Header.Resp.rqNetFnLUN);
176     rqLun = ipmbLunFromNetFnLunGet(ipmbBuffer->Header.Resp.rqNetFnLUN);
177     rsSA = ipmbBuffer->Header.Resp.rsSA;
178     seq = ipmbSeqGet(ipmbBuffer->Header.Resp.rsSeqLUN);
179     rsLun = ipmbLunFromSeqLunGet(ipmbBuffer->Header.Resp.rsSeqLUN);
180     cmd = ipmbBuffer->Header.Resp.cmd;
181     completionCode = ipmbBuffer->Header.Resp.completionCode;
182 
183     size_t dataLength =
184         bufferLength - (ipmbConnectionHeaderLength +
185                         ipmbResponseDataHeaderLength + ipmbChecksumSize);
186 
187     if (dataLength > 0)
188     {
189         data.insert(data.end(), ipmbBuffer->Header.Resp.data,
190                     &ipmbBuffer->Header.Resp.data[dataLength]);
191     }
192 }
193 
ipmbToi2cConstruct()194 std::shared_ptr<std::vector<uint8_t>> IpmbResponse::ipmbToi2cConstruct()
195 {
196     /* Add one byte for length byte as per required by driver */
197     size_t bufferLength = 1 + data.size() + ipmbResponseDataHeaderLength +
198                           ipmbConnectionHeaderLength + ipmbChecksumSize;
199 
200     if (bufferLength > ipmbMaxFrameLength)
201     {
202         return nullptr;
203     }
204 
205     std::shared_ptr<std::vector<uint8_t>> buffer =
206         std::make_shared<std::vector<uint8_t>>(bufferLength);
207 
208     IPMB_PKT* ipmbPkt = reinterpret_cast<IPMB_PKT*>(buffer->data());
209     ipmbPkt->len = bufferLength - 1;
210     IPMB_HEADER* ipmbBuffer = &(ipmbPkt->hdr);
211 
212     ipmbBuffer->Header.Resp.address = address;
213     ipmbBuffer->Header.Resp.rqNetFnLUN = ipmbNetFnLunSet(netFn, rqLun);
214     ipmbBuffer->Header.Resp.rsSA = rsSA;
215     ipmbBuffer->Header.Resp.rsSeqLUN = ipmbSeqLunSet(seq, rsLun);
216     ipmbBuffer->Header.Resp.cmd = cmd;
217     ipmbBuffer->Header.Resp.completionCode = completionCode;
218 
219     ipmbBuffer->Header.Resp.checksum1 = ipmbChecksumCompute(
220         (uint8_t*)ipmbBuffer, ipmbConnectionHeaderLength - ipmbChecksumSize);
221 
222     if (data.size() > 0)
223     {
224         std::copy(data.begin(), data.end(), ipmbBuffer->Header.Resp.data);
225     }
226 
227     (*buffer)[bufferLength - ipmbChecksumSize] =
228         ipmbChecksumCompute((uint8_t*)ipmbBuffer + ipmbChecksum2StartOffset,
229                             (ipmbResponseDataHeaderLength + data.size()));
230 
231     return buffer;
232 }
233 
isBlocked(const uint8_t reqNetFn,const uint8_t cmd)234 bool IpmbCommandFilter::isBlocked(const uint8_t reqNetFn, const uint8_t cmd)
235 {
236     auto blockedCmd = unhandledCommands.find({reqNetFn, cmd});
237 
238     if (blockedCmd != unhandledCommands.end())
239     {
240         return true;
241     }
242 
243     return false;
244 }
245 
addFilter(const uint8_t reqNetFn,const uint8_t cmd)246 void IpmbCommandFilter::addFilter(const uint8_t reqNetFn, const uint8_t cmd)
247 {
248     if (unhandledCommands.insert({reqNetFn, cmd}).second)
249     {
250         phosphor::logging::log<phosphor::logging::level::INFO>(
251             "addFilter: added command to filter",
252             phosphor::logging::entry("netFn = %d", reqNetFn),
253             phosphor::logging::entry("cmd = %d", cmd));
254     }
255 }
256 
257 /**
258  * @brief Ipmb channel
259  */
ipmbSendI2cFrame(std::shared_ptr<std::vector<uint8_t>> buffer,size_t retriesAttempted=0)260 void IpmbChannel::ipmbSendI2cFrame(std::shared_ptr<std::vector<uint8_t>> buffer,
261                                    size_t retriesAttempted = 0)
262 {
263     IPMB_PKT* ipmbPkt = reinterpret_cast<IPMB_PKT*>(buffer->data());
264     uint8_t targetAddr = ipmbIsResponse(&(ipmbPkt->hdr))
265                              ? ipmbPkt->hdr.Header.Resp.address
266                              : ipmbPkt->hdr.Header.Req.address;
267     boost::asio::async_write(
268         i2cTargetDescriptor, boost::asio::buffer(*buffer),
269         [this, buffer, retriesAttempted, targetAddr](
270             const boost::system::error_code& ec, size_t /* bytesSent */) {
271             if (ec)
272             {
273                 size_t currentRetryCnt = retriesAttempted;
274 
275                 if (currentRetryCnt > ipmbI2cNumberOfRetries)
276                 {
277                     std::string msgToLog =
278                         "ipmbSendI2cFrame: send to I2C failed after retries."
279                         " busId=" +
280                         std::to_string(ipmbBusId) +
281                         ", targetAddr=" + std::to_string(targetAddr) +
282                         ", error=" + ec.message();
283                     phosphor::logging::log<phosphor::logging::level::ERR>(
284                         msgToLog.c_str());
285                     return;
286                 }
287                 currentRetryCnt++;
288                 ipmbSendI2cFrame(buffer, currentRetryCnt);
289             }
290         });
291 }
292 
293 /**
294  * @brief Ipmb Outstanding Requests
295  */
makeRequestInvalid(IpmbRequest & request)296 void IpmbChannel::makeRequestInvalid(IpmbRequest& request)
297 {
298     // change request state to invalid and remove it from outstanding requests
299     // list
300     request.state = ipmbRequestState::invalid;
301     outstandingRequests[request.seq] = nullptr;
302 }
303 
makeRequestValid(std::shared_ptr<IpmbRequest> request)304 void IpmbChannel::makeRequestValid(std::shared_ptr<IpmbRequest> request)
305 {
306     // change request state to valid and add it to outstanding requests list
307     request->state = ipmbRequestState::valid;
308     outstandingRequests[request->seq] = request;
309 }
310 
seqNumGet(uint8_t & seq)311 bool IpmbChannel::seqNumGet(uint8_t& seq)
312 {
313     static uint8_t seqNum = 0;
314 
315     for (int i = 0; i < ipmbMaxOutstandingRequestsCount; i++)
316     {
317         seqNum = (seqNum + 1) % ipmbMaxOutstandingRequestsCount;
318 
319         if (outstandingRequests[seqNum] == nullptr)
320         {
321             seq = seqNum;
322             return true;
323         }
324     }
325 
326     return false;
327 }
328 
responseMatch(std::unique_ptr<IpmbResponse> & response)329 void IpmbChannel::responseMatch(std::unique_ptr<IpmbResponse>& response)
330 {
331     std::shared_ptr<IpmbRequest> request = outstandingRequests[response->seq];
332 
333     if (request != nullptr)
334     {
335         if (((ipmbRespNetFn(request->netFn)) == (response->netFn)) &&
336             ((request->rqLun) == (response->rqLun)) &&
337             ((request->rsLun) == (response->rsLun)) &&
338             ((request->cmd) == (response->cmd)))
339         {
340             // match, response is corresponding to previously sent request
341             request->state = ipmbRequestState::matched;
342             request->timer->cancel();
343             request->matchedResponse = std::move(response);
344         }
345     }
346 }
347 
processI2cEvent()348 void IpmbChannel::processI2cEvent()
349 {
350     std::array<uint8_t, ipmbMaxFrameLength> buffer{};
351     IPMB_PKT* ipmbPkt = reinterpret_cast<IPMB_PKT*>(buffer.data());
352     IPMB_HEADER* ipmbFrame = &(ipmbPkt->hdr);
353 
354     lseek(ipmbi2cTargetFd, 0, SEEK_SET);
355     ssize_t r = read(ipmbi2cTargetFd, buffer.data(), ipmbMaxFrameLength);
356 
357     // Handle error cases.
358     if (r < 0)
359     {
360         goto end;
361     }
362 
363     /* Subtract first byte len size from total frame length */
364     r--;
365 
366     if ((r < ipmbMinFrameLength) || (r > ipmbMaxFrameLength))
367     {
368         goto end;
369     }
370 
371     // validate the frame
372     if (!isFrameValid(ipmbFrame, r))
373     {
374         goto end;
375     }
376 
377     // if it is message received from ipmb channel, send out dbus signal
378     if (getChannelType() == ipmbChannelType::ipmb)
379     {
380         auto ipmbMessageReceived = IpmbRequest();
381         ipmbMessageReceived.i2cToIpmbConstruct(ipmbFrame, r);
382         sdbusplus::message_t msg =
383             conn->new_signal(ipmbObj, ipmbDbusIntf, "receiveBroadcast");
384         msg.append(ipmbMessageReceived.netFn, ipmbMessageReceived.cmd,
385                    ipmbMessageReceived.data);
386         msg.signal_send();
387     }
388 
389     // copy frame to ipmib message buffer
390     if (ipmbIsResponse(ipmbFrame))
391     {
392         std::unique_ptr<IpmbResponse> ipmbMessageReceived =
393             std::make_unique<IpmbResponse>();
394 
395         ipmbMessageReceived->i2cToIpmbConstruct(ipmbFrame, r);
396 
397         // try to match response with outstanding request
398         responseMatch(ipmbMessageReceived);
399     }
400     else
401     {
402         // if command is blocked - respond with 'invalid command'
403         // completion code
404         if (commandFilter)
405         {
406             uint8_t netFn = ipmbNetFnGet(ipmbFrame->Header.Req.rsNetFnLUN);
407             uint8_t cmd = ipmbFrame->Header.Req.cmd;
408             uint8_t rqSA = ipmbFrame->Header.Req.rqSA;
409 
410             if (commandFilter->isBlocked(netFn, cmd))
411             {
412                 uint8_t seq = ipmbSeqGet(ipmbFrame->Header.Req.rqSeqLUN);
413                 uint8_t lun =
414                     ipmbLunFromSeqLunGet(ipmbFrame->Header.Req.rqSeqLUN);
415 
416                 // prepare generic response
417                 auto ipmbResponse = IpmbResponse(
418                     rqSA, ipmbRespNetFn(netFn), lun, ipmbBmcTargetAddress, seq,
419                     ipmbRsLun, cmd, ipmbIpmiInvalidCmd, {});
420 
421                 auto buffer = ipmbResponse.ipmbToi2cConstruct();
422                 if (buffer)
423                 {
424                     ipmbSendI2cFrame(buffer);
425                 }
426 
427                 goto end;
428             }
429         }
430 
431         auto ipmbMessageReceived = IpmbRequest();
432         ipmbMessageReceived.i2cToIpmbConstruct(ipmbFrame, r);
433 
434         int devId = getDevIndex();
435 
436         std::map<std::string, std::variant<int>> options{
437             {"rqSA", ipmbAddressTo7BitSet(ipmbMessageReceived.rqSA)},
438             {"hostId", devId}};
439 
440         using IpmiDbusRspType = std::tuple<uint8_t, uint8_t, uint8_t, uint8_t,
441                                            std::vector<uint8_t>>;
442         conn->async_method_call(
443             [this, rqLun{ipmbMessageReceived.rqLun},
444              seq{ipmbMessageReceived.seq}, address{ipmbMessageReceived.rqSA}](
445                 const boost::system::error_code& ec,
446                 const IpmiDbusRspType& response) {
447                 const auto& [netfn, lun, cmd, cc, payload] = response;
448                 if (ec)
449                 {
450                     phosphor::logging::log<phosphor::logging::level::ERR>(
451                         "processI2cEvent: error getting response from IPMI");
452                     return;
453                 }
454 
455                 uint8_t bmcTargetAddress = getBmcTargetAddress();
456 
457                 if (payload.size() > ipmbMaxDataSize)
458                 {
459                     phosphor::logging::log<phosphor::logging::level::ERR>(
460                         "processI2cEvent: response exceeding maximum size");
461 
462                     // prepare generic response
463                     auto ipmbResponse = IpmbResponse(
464                         address, netfn, rqLun, bmcTargetAddress, seq, ipmbRsLun,
465                         cmd, ipmbIpmiCmdRespNotProvided, {});
466 
467                     auto buffer = ipmbResponse.ipmbToi2cConstruct();
468                     if (buffer)
469                     {
470                         ipmbSendI2cFrame(buffer);
471                     }
472 
473                     return;
474                 }
475 
476                 if (!(netfn & ipmbNetFnResponseMask))
477                 {
478                     // we are not expecting request here
479                     phosphor::logging::log<phosphor::logging::level::ERR>(
480                         "processI2cEvent: got a request instead of response");
481                     return;
482                 }
483 
484                 // if command is not supported, add it to filter
485                 if (cc == ipmbIpmiInvalidCmd)
486                 {
487                     addFilter(ipmbReqNetFnFromRespNetFn(netfn), cmd);
488                 }
489 
490                 // payload is empty after constructor invocation
491                 auto ipmbResponse =
492                     IpmbResponse(address, netfn, rqLun, bmcTargetAddress, seq,
493                                  lun, cmd, cc, payload);
494 
495                 auto buffer = ipmbResponse.ipmbToi2cConstruct();
496                 if (!buffer)
497                 {
498                     phosphor::logging::log<phosphor::logging::level::ERR>(
499                         "processI2cEvent: error constructing a request");
500                     return;
501                 }
502 
503                 ipmbSendI2cFrame(buffer);
504             },
505             "xyz.openbmc_project.Ipmi.Host", "/xyz/openbmc_project/Ipmi",
506             "xyz.openbmc_project.Ipmi.Server", "execute",
507             ipmbMessageReceived.netFn, ipmbMessageReceived.rsLun,
508             ipmbMessageReceived.cmd, ipmbMessageReceived.data, options);
509     }
510 
511 end:
512     i2cTargetDescriptor.async_wait(
513         boost::asio::posix::descriptor_base::wait_read,
514         [this](const boost::system::error_code& ec) {
515             if (ec)
516             {
517                 phosphor::logging::log<phosphor::logging::level::ERR>(
518                     "Error: processI2cEvent()");
519                 return;
520             }
521 
522             processI2cEvent();
523         });
524 }
525 
IpmbChannel(boost::asio::io_context & io,uint8_t ipmbBmcTargetAddress,uint8_t ipmbRqTargetAddress,uint8_t channelIdx,std::shared_ptr<IpmbCommandFilter> commandFilter)526 IpmbChannel::IpmbChannel(boost::asio::io_context& io,
527                          uint8_t ipmbBmcTargetAddress,
528                          uint8_t ipmbRqTargetAddress, uint8_t channelIdx,
529                          std::shared_ptr<IpmbCommandFilter> commandFilter) :
530     i2cTargetDescriptor(io), ipmbBmcTargetAddress(ipmbBmcTargetAddress),
531     ipmbRqTargetAddress(ipmbRqTargetAddress), channelIdx(channelIdx),
532     commandFilter(commandFilter)
533 {}
534 
ipmbChannelInit(const char * ipmbI2cTarget)535 int IpmbChannel::ipmbChannelInit(const char* ipmbI2cTarget)
536 {
537     // extract bus id from target path and save
538     std::string ipmbI2cTargetStr(ipmbI2cTarget);
539     auto findHyphen = ipmbI2cTargetStr.find("-");
540     std::string busStr = ipmbI2cTargetStr.substr(findHyphen + 1);
541     try
542     {
543         ipmbBusId = std::stoi(busStr);
544     }
545     catch (const std::invalid_argument&)
546     {
547         phosphor::logging::log<phosphor::logging::level::ERR>(
548             "ipmbChannelInit: invalid bus id in target-path config");
549         return -1;
550     }
551 
552     // Check if sysfs has device. If not, enable I2C target driver by command
553     // echo "ipmb-dev 0x1010" > /sys/bus/i2c/devices/i2c-0/new_device
554     bool hasSysfs = std::filesystem::exists(ipmbI2cTarget);
555     if (!hasSysfs)
556     {
557         std::string deviceFileName =
558             "/sys/bus/i2c/devices/i2c-" + busStr + "/new_device";
559         std::string para = "ipmb-dev 0x1010"; // init with BMC addr 0x20
560         std::fstream deviceFile;
561         deviceFile.open(deviceFileName, std::ios::out);
562         if (!deviceFile.good())
563         {
564             phosphor::logging::log<phosphor::logging::level::ERR>(
565                 "ipmbChannelInit: error opening deviceFile");
566             return -1;
567         }
568         deviceFile << para;
569         deviceFile.close();
570     }
571 
572     // open fd to i2c target device for read write
573     ipmbi2cTargetFd = open(ipmbI2cTarget, O_RDWR | O_NONBLOCK | O_CLOEXEC);
574     if (ipmbi2cTargetFd < 0)
575     {
576         phosphor::logging::log<phosphor::logging::level::ERR>(
577             "ipmbChannelInit: error opening ipmbI2cTarget");
578         return -1;
579     }
580 
581     i2cTargetDescriptor.assign(ipmbi2cTargetFd);
582 
583     i2cTargetDescriptor.async_wait(
584         boost::asio::posix::descriptor_base::wait_read,
585         [this](const boost::system::error_code& ec) {
586             if (ec)
587             {
588                 phosphor::logging::log<phosphor::logging::level::ERR>(
589                     "Error: processI2cEvent()");
590                 return;
591             }
592 
593             processI2cEvent();
594         });
595 
596     return 0;
597 }
598 
ipmbChannelUpdateTargetAddress(const uint8_t newBmcTargetAddr)599 int IpmbChannel::ipmbChannelUpdateTargetAddress(const uint8_t newBmcTargetAddr)
600 {
601     if (ipmbi2cTargetFd > 0)
602     {
603         i2cTargetDescriptor.close();
604         close(ipmbi2cTargetFd);
605         ipmbi2cTargetFd = 0;
606     }
607 
608     // disable old I2C target driver by command:
609     //     echo "0x1010" > /sys/bus/i2c/devices/i2c-0/delete_device
610     std::string deviceFileName;
611     std::string para;
612     std::fstream deviceFile;
613     deviceFileName = "/sys/bus/i2c/devices/i2c-" + std::to_string(ipmbBusId) +
614                      "/delete_device";
615     para = "0x1010"; // align with removed ipmb0 definition in dts file
616     deviceFile.open(deviceFileName, std::ios::out);
617     if (!deviceFile.good())
618     {
619         phosphor::logging::log<phosphor::logging::level::ERR>(
620             "ipmbChannelUpdateTargetAddress: error opening deviceFile to delete "
621             "sysfs");
622         return -1;
623     }
624     deviceFile << para;
625     deviceFile.close();
626 
627     // enable new I2C target driver by command:
628     //      echo "ipmb-dev 0x1012" > /sys/bus/i2c/devices/i2c-0/new_device
629     deviceFileName =
630         "/sys/bus/i2c/devices/i2c-" + std::to_string(ipmbBusId) + "/new_device";
631     std::ostringstream hex;
632     uint16_t addr = 0x1000 + (newBmcTargetAddr >> 1);
633     hex << std::hex << static_cast<uint16_t>(addr);
634     const std::string& addressHexStr = hex.str();
635     para = "ipmb-dev 0x" + addressHexStr;
636     deviceFile.open(deviceFileName, std::ios::out);
637     if (!deviceFile.good())
638     {
639         phosphor::logging::log<phosphor::logging::level::ERR>(
640             "ipmbChannelUpdateTargetAddress: error opening deviceFile to create "
641             "sysfs");
642         return -1;
643     }
644     deviceFile << para;
645     deviceFile.close();
646 
647     // open fd to i2c target device
648     std::string ipmbI2cTargetStr = "/dev/ipmb-" + std::to_string(ipmbBusId);
649     ipmbi2cTargetFd = open(ipmbI2cTargetStr.c_str(), O_RDWR | O_NONBLOCK);
650     if (ipmbi2cTargetFd < 0)
651     {
652         phosphor::logging::log<phosphor::logging::level::ERR>(
653             "ipmbChannelInit: error opening ipmbI2cTarget");
654         return -1;
655     }
656 
657     // start to receive i2c data as target
658     i2cTargetDescriptor.assign(ipmbi2cTargetFd);
659     i2cTargetDescriptor.async_wait(
660         boost::asio::posix::descriptor_base::wait_read,
661         [this](const boost::system::error_code& ec) {
662             if (ec)
663             {
664                 phosphor::logging::log<phosphor::logging::level::ERR>(
665                     "Error: processI2cEvent()");
666                 return;
667             }
668 
669             processI2cEvent();
670         });
671 
672     ipmbBmcTargetAddress = newBmcTargetAddr;
673 
674     return 0;
675 }
676 
getBusId()677 uint8_t IpmbChannel::getBusId()
678 {
679     return ipmbBusId;
680 }
681 
getBmcTargetAddress()682 uint8_t IpmbChannel::getBmcTargetAddress()
683 {
684     return ipmbBmcTargetAddress;
685 }
686 
getRqTargetAddress()687 uint8_t IpmbChannel::getRqTargetAddress()
688 {
689     return ipmbRqTargetAddress;
690 }
691 
getDevIndex()692 uint8_t IpmbChannel::getDevIndex()
693 {
694     return channelIdx >> 2;
695 }
696 
getChannelIdx()697 uint8_t IpmbChannel::getChannelIdx()
698 {
699     return channelIdx;
700 }
701 
getChannelType()702 ipmbChannelType IpmbChannel::getChannelType()
703 {
704     return static_cast<ipmbChannelType>((channelIdx & 3));
705 }
706 
addFilter(const uint8_t respNetFn,const uint8_t cmd)707 void IpmbChannel::addFilter(const uint8_t respNetFn, const uint8_t cmd)
708 {
709     if (commandFilter)
710     {
711         commandFilter->addFilter(respNetFn, cmd);
712     }
713 }
714 
715 std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>
requestAdd(boost::asio::yield_context & yield,std::shared_ptr<IpmbRequest> request)716     IpmbChannel::requestAdd(boost::asio::yield_context& yield,
717                             std::shared_ptr<IpmbRequest> request)
718 {
719     makeRequestValid(request);
720 
721     std::vector<uint8_t> buffer{};
722     if (request->ipmbToi2cConstruct(buffer) != 0)
723     {
724         return returnStatus(ipmbResponseStatus::error);
725     }
726 
727     for (int i = 0; i < ipmbNumberOfTries; i++)
728     {
729         boost::system::error_code ec;
730         int i2cRetryCnt = 0;
731 
732         for (; i2cRetryCnt < ipmbI2cNumberOfRetries; i2cRetryCnt++)
733         {
734             boost::asio::async_write(i2cTargetDescriptor,
735                                      boost::asio::buffer(buffer), yield[ec]);
736 
737             if (ec)
738             {
739                 continue; // retry
740             }
741             break;
742         }
743 
744         if (i2cRetryCnt == ipmbI2cNumberOfRetries)
745         {
746             std::string msgToLog =
747                 "requestAdd: Sent to I2C failed after retries."
748                 " busId=" +
749                 std::to_string(ipmbBusId) + ", error=" + ec.message();
750             phosphor::logging::log<phosphor::logging::level::INFO>(
751                 msgToLog.c_str());
752         }
753 
754         request->timer->expires_after(
755             std::chrono::milliseconds(ipmbRequestRetryTimeout));
756         request->timer->async_wait(yield[ec]);
757 
758         if (ec && ec != boost::asio::error::operation_aborted)
759         {
760             // unexpected error - invalidate request and return generic error
761             phosphor::logging::log<phosphor::logging::level::ERR>(
762                 "requestAdd: async_wait error");
763             makeRequestInvalid(*request);
764             return returnStatus(ipmbResponseStatus::error);
765         }
766 
767         if (request->state == ipmbRequestState::matched)
768         {
769             // matched response, send it to client application
770             makeRequestInvalid(*request);
771             return request->returnMatchedResponse();
772         }
773     }
774 
775     makeRequestInvalid(*request);
776     return returnStatus(ipmbResponseStatus::timeout);
777 }
778 
getChannel(uint8_t reqChannel)779 static IpmbChannel* getChannel(uint8_t reqChannel)
780 {
781     auto channel =
782         std::find_if(ipmbChannels.begin(), ipmbChannels.end(),
783                      [reqChannel](IpmbChannel& channel) {
784                          return channel.getChannelIdx() == reqChannel;
785                      });
786     if (channel != ipmbChannels.end())
787     {
788         return &(*channel);
789     }
790 
791     return nullptr;
792 }
793 
initializeChannels()794 static int initializeChannels()
795 {
796     std::shared_ptr<IpmbCommandFilter> commandFilter =
797         std::make_shared<IpmbCommandFilter>();
798 
799     constexpr const char* configFilePath =
800         "/usr/share/ipmbbridge/ipmb-channels.json";
801     std::ifstream configFile(configFilePath);
802     if (!configFile.is_open())
803     {
804         phosphor::logging::log<phosphor::logging::level::ERR>(
805             "initializeChannels: Cannot open config path");
806         return -1;
807     }
808     try
809     {
810         uint8_t devIndex = 0;
811         auto data = nlohmann::json::parse(configFile, nullptr);
812         for (const auto& channelConfig : data["channels"])
813         {
814             const std::string& typeConfig = channelConfig["type"];
815             const std::string& targetPath = channelConfig["slave-path"];
816             uint8_t bmcAddr = channelConfig["bmc-addr"];
817             uint8_t reqAddr = channelConfig["remote-addr"];
818 
819             ipmbChannelType type = ipmbChannelTypeMap.at(typeConfig);
820 
821             if (channelConfig.contains("devIndex"))
822             {
823                 devIndex = channelConfig["devIndex"];
824             }
825 
826             auto channel = ipmbChannels.emplace(
827                 ipmbChannels.end(), io, bmcAddr, reqAddr,
828                 ((devIndex << 2) | static_cast<uint8_t>(type)), commandFilter);
829             if (channel->ipmbChannelInit(targetPath.c_str()) < 0)
830             {
831                 phosphor::logging::log<phosphor::logging::level::ERR>(
832                     "initializeChannels: channel initialization failed");
833                 return -1;
834             }
835         }
836     }
837     catch (const nlohmann::json::exception& e)
838     {
839         phosphor::logging::log<phosphor::logging::level::ERR>(
840             "initializeChannels: Error parsing config file");
841         return -1;
842     }
843     catch (const std::out_of_range& e)
844     {
845         phosphor::logging::log<phosphor::logging::level::ERR>(
846             "initializeChannels: Error invalid type");
847         return -1;
848     }
849     return 0;
850 }
851 
852 auto ipmbHandleRequest =
853     [](boost::asio::yield_context yield, uint8_t reqChannel, uint8_t netfn,
__anon377db2e00702(boost::asio::yield_context yield, uint8_t reqChannel, uint8_t netfn, uint8_t lun, uint8_t cmd, std::vector<uint8_t> dataReceived) 854        uint8_t lun, uint8_t cmd, std::vector<uint8_t> dataReceived) {
855         IpmbChannel* channel = getChannel(reqChannel);
856 
857         if (channel == nullptr)
858         {
859             phosphor::logging::log<phosphor::logging::level::ERR>(
860                 "ipmbHandleRequest: requested channel does not exist");
861             return returnStatus(ipmbResponseStatus::invalid_param);
862         }
863 
864         // check outstanding request list for valid sequence number
865         uint8_t seqNum = 0;
866         bool seqValid = channel->seqNumGet(seqNum);
867         if (!seqValid)
868         {
869             phosphor::logging::log<phosphor::logging::level::WARNING>(
870                 "ipmbHandleRequest: cannot add more requests to the list");
871             return returnStatus(ipmbResponseStatus::busy);
872         }
873 
874         uint8_t bmcTargetAddress = channel->getBmcTargetAddress();
875         uint8_t rqTargetAddress = channel->getRqTargetAddress();
876 
877         // construct the request to add it to outstanding request list
878         std::shared_ptr<IpmbRequest> request = std::make_shared<IpmbRequest>(
879             rqTargetAddress, netfn, ipmbRsLun, bmcTargetAddress, seqNum, lun,
880             cmd, dataReceived);
881 
882         if (!request->timer)
883         {
884             phosphor::logging::log<phosphor::logging::level::ERR>(
885                 "ipmbHandleRequest: timer object does not exist");
886             return returnStatus(ipmbResponseStatus::error);
887         }
888 
889         return channel->requestAdd(yield, request);
890     };
891 
addUpdateTargetAddrHandler()892 void addUpdateTargetAddrHandler()
893 {
894     // callback to handle dbus signal of updating target addr
895     std::function<void(sdbusplus::message_t&)> updateTargetAddrHandler =
896         [](sdbusplus::message_t& message) {
897             uint8_t reqChannel, busId, targetAddr;
898 
899             // valid source of signal, check whether from multi-node manager
900             std::string pathName = message.get_path();
901             if (pathName != "/xyz/openbmc_project/MultiNode/Status")
902             {
903                 phosphor::logging::log<phosphor::logging::level::ERR>(
904                     "addUpdateTargetAddrHandler: invalid obj path");
905                 return;
906             }
907 
908             message.read(reqChannel, busId, targetAddr);
909 
910             IpmbChannel* channel = getChannel(reqChannel);
911 
912             if (channel == nullptr ||
913                 channel->getChannelType() != ipmbChannelType::ipmb)
914             {
915                 phosphor::logging::log<phosphor::logging::level::ERR>(
916                     "addUpdateTargetAddrHandler: invalid channel");
917                 return;
918             }
919             if (busId != channel->getBusId())
920             {
921                 phosphor::logging::log<phosphor::logging::level::ERR>(
922                     "addUpdateTargetAddrHandler: invalid busId");
923                 return;
924             }
925             if (channel->getBmcTargetAddress() == targetAddr)
926             {
927                 phosphor::logging::log<phosphor::logging::level::INFO>(
928                     "addUpdateTargetAddrHandler: channel bmc target addr is "
929                     "unchanged, do nothing");
930                 return;
931             }
932 
933             channel->ipmbChannelUpdateTargetAddress(targetAddr);
934         };
935 
936     static auto match = std::make_unique<sdbusplus::bus::match_t>(
937         static_cast<sdbusplus::bus_t&>(*conn),
938         "type='signal',member='updateBmcSlaveAddr',", updateTargetAddrHandler);
939 }
940 
addSendBroadcastHandler()941 void addSendBroadcastHandler()
942 {
943     // callback to handle dbus signal of sending broadcast message
944     std::function<void(sdbusplus::message_t&)> sendBroadcastHandler =
945         [](sdbusplus::message_t& message) {
946             uint8_t reqChannel, netFn, lun, cmd;
947             std::vector<uint8_t> dataReceived;
948             message.read(reqChannel, netFn, lun, cmd, dataReceived);
949 
950             IpmbChannel* channel = getChannel(reqChannel);
951 
952             if (channel == nullptr)
953             {
954                 phosphor::logging::log<phosphor::logging::level::ERR>(
955                     "addSendBroadcastMsgHandler: requested channel does not "
956                     "exist");
957                 return;
958             }
959 
960             uint8_t bmcTargetAddress = channel->getBmcTargetAddress();
961             uint8_t seqNum = 0; // seqNum is not used in broadcast msg
962             uint8_t targetAddr = broadcastAddress;
963 
964             std::shared_ptr<IpmbRequest> request =
965                 std::make_shared<IpmbRequest>(targetAddr, netFn, ipmbRsLun,
966                                               bmcTargetAddress, seqNum, lun,
967                                               cmd, dataReceived);
968 
969             std::shared_ptr<std::vector<uint8_t>> buffer =
970                 std::make_shared<std::vector<uint8_t>>();
971 
972             if (request->ipmbToi2cConstruct(*buffer) != 0)
973             {
974                 return;
975             }
976 
977             channel->ipmbSendI2cFrame(buffer);
978         };
979 
980     static auto match = std::make_unique<sdbusplus::bus::match_t>(
981         static_cast<sdbusplus::bus_t&>(*conn),
982         "type='signal',member='sendBroadcast',", sendBroadcastHandler);
983 }
984 
985 /**
986  * @brief Main
987  */
main()988 int main()
989 {
990     conn->request_name(ipmbBus);
991 
992     auto server = sdbusplus::asio::object_server(conn);
993 
994     std::shared_ptr<sdbusplus::asio::dbus_interface> ipmbIface =
995         server.add_interface(ipmbObj, ipmbDbusIntf);
996 
997     ipmbIface->register_method("sendRequest", std::move(ipmbHandleRequest));
998     ipmbIface->initialize();
999 
1000     if (initializeChannels() < 0)
1001     {
1002         phosphor::logging::log<phosphor::logging::level::ERR>(
1003             "Error initializeChannels");
1004         return -1;
1005     }
1006 
1007     addUpdateTargetAddrHandler();
1008 
1009     addSendBroadcastHandler();
1010 
1011     io.run();
1012     return 0;
1013 }
1014