xref: /openbmc/ipmbbridge/ipmbbridged.cpp (revision 3ef588d54cddafc1f5777fef8653aa0388ca6d44)
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                 scheduleFrameResend(buffer, currentRetryCnt);
288             }
289         });
290 }
291 
scheduleFrameResend(std::shared_ptr<std::vector<uint8_t>> buffer,size_t retryCount)292 void IpmbChannel::scheduleFrameResend(
293     std::shared_ptr<std::vector<uint8_t>> buffer, size_t retryCount)
294 {
295     // SMBus timeout: 30 ms (spec range 25-35 ms)
296     static constexpr size_t resendDelay = 30;
297 
298     // Delay increases exponentially with 30 ms multiplied by 2 raised
299     // to the power of retries, e.g.
300     // 1st retry: 30 ms, 2nd: 60 ms, 3rd: 120 ms ...
301     retryTimer.expires_after(
302         std::chrono::milliseconds(resendDelay * (1 << retryCount)));
303     retryTimer.async_wait(
304         [this, buffer, retryCount](const boost::system::error_code& ec) {
305             if (!ec)
306             {
307                 ipmbSendI2cFrame(buffer, retryCount + 1);
308             }
309         });
310 }
311 
312 /**
313  * @brief Ipmb Outstanding Requests
314  */
makeRequestInvalid(IpmbRequest & request)315 void IpmbChannel::makeRequestInvalid(IpmbRequest& request)
316 {
317     // change request state to invalid and remove it from outstanding requests
318     // list
319     request.state = ipmbRequestState::invalid;
320     outstandingRequests[request.seq] = nullptr;
321 }
322 
makeRequestValid(std::shared_ptr<IpmbRequest> request)323 void IpmbChannel::makeRequestValid(std::shared_ptr<IpmbRequest> request)
324 {
325     // change request state to valid and add it to outstanding requests list
326     request->state = ipmbRequestState::valid;
327     outstandingRequests[request->seq] = request;
328 }
329 
seqNumGet(uint8_t & seq)330 bool IpmbChannel::seqNumGet(uint8_t& seq)
331 {
332     static uint8_t seqNum = 0;
333 
334     for (int i = 0; i < ipmbMaxOutstandingRequestsCount; i++)
335     {
336         seqNum = (seqNum + 1) % ipmbMaxOutstandingRequestsCount;
337 
338         if (outstandingRequests[seqNum] == nullptr)
339         {
340             seq = seqNum;
341             return true;
342         }
343     }
344 
345     return false;
346 }
347 
responseMatch(std::unique_ptr<IpmbResponse> & response)348 void IpmbChannel::responseMatch(std::unique_ptr<IpmbResponse>& response)
349 {
350     std::shared_ptr<IpmbRequest> request = outstandingRequests[response->seq];
351 
352     if (request != nullptr)
353     {
354         if (((ipmbRespNetFn(request->netFn)) == (response->netFn)) &&
355             ((request->rqLun) == (response->rqLun)) &&
356             ((request->rsLun) == (response->rsLun)) &&
357             ((request->cmd) == (response->cmd)))
358         {
359             // match, response is corresponding to previously sent request
360             request->state = ipmbRequestState::matched;
361             request->timer->cancel();
362             request->matchedResponse = std::move(response);
363         }
364     }
365 }
366 
processI2cEvent()367 void IpmbChannel::processI2cEvent()
368 {
369     std::array<uint8_t, ipmbMaxFrameLength> buffer{};
370     IPMB_PKT* ipmbPkt = reinterpret_cast<IPMB_PKT*>(buffer.data());
371     IPMB_HEADER* ipmbFrame = &(ipmbPkt->hdr);
372 
373     lseek(ipmbi2cTargetFd, 0, SEEK_SET);
374     ssize_t r = read(ipmbi2cTargetFd, buffer.data(), ipmbMaxFrameLength);
375 
376     // Handle error cases.
377     if (r < 0)
378     {
379         goto end;
380     }
381 
382     /* Subtract first byte len size from total frame length */
383     r--;
384 
385     if ((r < ipmbMinFrameLength) || (r > ipmbMaxFrameLength))
386     {
387         goto end;
388     }
389 
390     // validate the frame
391     if (!isFrameValid(ipmbFrame, r))
392     {
393         goto end;
394     }
395 
396     // if it is message received from ipmb channel, send out dbus signal
397     if (getChannelType() == ipmbChannelType::ipmb)
398     {
399         auto ipmbMessageReceived = IpmbRequest();
400         ipmbMessageReceived.i2cToIpmbConstruct(ipmbFrame, r);
401         sdbusplus::message_t msg =
402             conn->new_signal(ipmbObj, ipmbDbusIntf, "receiveBroadcast");
403         msg.append(ipmbMessageReceived.netFn, ipmbMessageReceived.cmd,
404                    ipmbMessageReceived.data);
405         msg.signal_send();
406     }
407 
408     // copy frame to ipmib message buffer
409     if (ipmbIsResponse(ipmbFrame))
410     {
411         std::unique_ptr<IpmbResponse> ipmbMessageReceived =
412             std::make_unique<IpmbResponse>();
413 
414         ipmbMessageReceived->i2cToIpmbConstruct(ipmbFrame, r);
415 
416         // try to match response with outstanding request
417         responseMatch(ipmbMessageReceived);
418     }
419     else
420     {
421         // if command is blocked - respond with 'invalid command'
422         // completion code
423         if (commandFilter)
424         {
425             uint8_t netFn = ipmbNetFnGet(ipmbFrame->Header.Req.rsNetFnLUN);
426             uint8_t cmd = ipmbFrame->Header.Req.cmd;
427             uint8_t rqSA = ipmbFrame->Header.Req.rqSA;
428 
429             if (commandFilter->isBlocked(netFn, cmd))
430             {
431                 uint8_t seq = ipmbSeqGet(ipmbFrame->Header.Req.rqSeqLUN);
432                 uint8_t lun =
433                     ipmbLunFromSeqLunGet(ipmbFrame->Header.Req.rqSeqLUN);
434 
435                 // prepare generic response
436                 auto ipmbResponse = IpmbResponse(
437                     rqSA, ipmbRespNetFn(netFn), lun, ipmbBmcTargetAddress, seq,
438                     ipmbRsLun, cmd, ipmbIpmiInvalidCmd, {});
439 
440                 auto buffer = ipmbResponse.ipmbToi2cConstruct();
441                 if (buffer)
442                 {
443                     ipmbSendI2cFrame(buffer);
444                 }
445 
446                 goto end;
447             }
448         }
449 
450         auto ipmbMessageReceived = IpmbRequest();
451         ipmbMessageReceived.i2cToIpmbConstruct(ipmbFrame, r);
452 
453         int devId = getDevIndex();
454 
455         std::map<std::string, std::variant<int>> options{
456             {"rqSA", ipmbAddressTo7BitSet(ipmbMessageReceived.rqSA)},
457             {"hostId", devId}};
458 
459         using IpmiDbusRspType = std::tuple<uint8_t, uint8_t, uint8_t, uint8_t,
460                                            std::vector<uint8_t>>;
461         conn->async_method_call(
462             [this, rqLun{ipmbMessageReceived.rqLun},
463              seq{ipmbMessageReceived.seq}, address{ipmbMessageReceived.rqSA}](
464                 const boost::system::error_code& ec,
465                 const IpmiDbusRspType& response) {
466                 const auto& [netfn, lun, cmd, cc, payload] = response;
467                 if (ec)
468                 {
469                     phosphor::logging::log<phosphor::logging::level::ERR>(
470                         "processI2cEvent: error getting response from IPMI");
471                     return;
472                 }
473 
474                 uint8_t bmcTargetAddress = getBmcTargetAddress();
475 
476                 if (payload.size() > ipmbMaxDataSize)
477                 {
478                     phosphor::logging::log<phosphor::logging::level::ERR>(
479                         "processI2cEvent: response exceeding maximum size");
480 
481                     // prepare generic response
482                     auto ipmbResponse = IpmbResponse(
483                         address, netfn, rqLun, bmcTargetAddress, seq, ipmbRsLun,
484                         cmd, ipmbIpmiCmdRespNotProvided, {});
485 
486                     auto buffer = ipmbResponse.ipmbToi2cConstruct();
487                     if (buffer)
488                     {
489                         ipmbSendI2cFrame(buffer);
490                     }
491 
492                     return;
493                 }
494 
495                 if (!(netfn & ipmbNetFnResponseMask))
496                 {
497                     // we are not expecting request here
498                     phosphor::logging::log<phosphor::logging::level::ERR>(
499                         "processI2cEvent: got a request instead of response");
500                     return;
501                 }
502 
503                 // if command is not supported, add it to filter
504                 if (cc == ipmbIpmiInvalidCmd)
505                 {
506                     addFilter(ipmbReqNetFnFromRespNetFn(netfn), cmd);
507                 }
508 
509                 // payload is empty after constructor invocation
510                 auto ipmbResponse =
511                     IpmbResponse(address, netfn, rqLun, bmcTargetAddress, seq,
512                                  lun, cmd, cc, payload);
513 
514                 auto buffer = ipmbResponse.ipmbToi2cConstruct();
515                 if (!buffer)
516                 {
517                     phosphor::logging::log<phosphor::logging::level::ERR>(
518                         "processI2cEvent: error constructing a request");
519                     return;
520                 }
521 
522                 ipmbSendI2cFrame(buffer);
523             },
524             "xyz.openbmc_project.Ipmi.Host", "/xyz/openbmc_project/Ipmi",
525             "xyz.openbmc_project.Ipmi.Server", "execute",
526             ipmbMessageReceived.netFn, ipmbMessageReceived.rsLun,
527             ipmbMessageReceived.cmd, ipmbMessageReceived.data, options);
528     }
529 
530 end:
531     i2cTargetDescriptor.async_wait(
532         boost::asio::posix::descriptor_base::wait_read,
533         [this](const boost::system::error_code& ec) {
534             if (ec)
535             {
536                 phosphor::logging::log<phosphor::logging::level::ERR>(
537                     "Error: processI2cEvent()");
538                 return;
539             }
540 
541             processI2cEvent();
542         });
543 }
544 
IpmbChannel(boost::asio::io_context & io,uint8_t ipmbBmcTargetAddress,uint8_t ipmbRqTargetAddress,uint8_t channelIdx,std::shared_ptr<IpmbCommandFilter> commandFilter)545 IpmbChannel::IpmbChannel(boost::asio::io_context& io,
546                          uint8_t ipmbBmcTargetAddress,
547                          uint8_t ipmbRqTargetAddress, uint8_t channelIdx,
548                          std::shared_ptr<IpmbCommandFilter> commandFilter) :
549     i2cTargetDescriptor(io), retryTimer(io),
550     ipmbBmcTargetAddress(ipmbBmcTargetAddress),
551     ipmbRqTargetAddress(ipmbRqTargetAddress), channelIdx(channelIdx),
552     commandFilter(commandFilter)
553 {}
554 
ipmbChannelInit(const char * ipmbI2cTarget)555 int IpmbChannel::ipmbChannelInit(const char* ipmbI2cTarget)
556 {
557     // extract bus id from target path and save
558     std::string ipmbI2cTargetStr(ipmbI2cTarget);
559     auto findHyphen = ipmbI2cTargetStr.find("-");
560     std::string busStr = ipmbI2cTargetStr.substr(findHyphen + 1);
561     try
562     {
563         ipmbBusId = std::stoi(busStr);
564     }
565     catch (const std::invalid_argument&)
566     {
567         phosphor::logging::log<phosphor::logging::level::ERR>(
568             "ipmbChannelInit: invalid bus id in target-path config");
569         return -1;
570     }
571 
572     // Check if sysfs has device. If not, enable I2C target driver by command
573     // echo "ipmb-dev 0x1010" > /sys/bus/i2c/devices/i2c-0/new_device
574     bool hasSysfs = std::filesystem::exists(ipmbI2cTarget);
575     if (!hasSysfs)
576     {
577         std::string deviceFileName =
578             "/sys/bus/i2c/devices/i2c-" + busStr + "/new_device";
579         std::string para = "ipmb-dev 0x1010"; // init with BMC addr 0x20
580         std::fstream deviceFile;
581         deviceFile.open(deviceFileName, std::ios::out);
582         if (!deviceFile.good())
583         {
584             phosphor::logging::log<phosphor::logging::level::ERR>(
585                 "ipmbChannelInit: error opening deviceFile");
586             return -1;
587         }
588         deviceFile << para;
589         deviceFile.close();
590     }
591 
592     // open fd to i2c target device for read write
593     ipmbi2cTargetFd = open(ipmbI2cTarget, O_RDWR | O_NONBLOCK | O_CLOEXEC);
594     if (ipmbi2cTargetFd < 0)
595     {
596         phosphor::logging::log<phosphor::logging::level::ERR>(
597             "ipmbChannelInit: error opening ipmbI2cTarget");
598         return -1;
599     }
600 
601     i2cTargetDescriptor.assign(ipmbi2cTargetFd);
602 
603     i2cTargetDescriptor.async_wait(
604         boost::asio::posix::descriptor_base::wait_read,
605         [this](const boost::system::error_code& ec) {
606             if (ec)
607             {
608                 phosphor::logging::log<phosphor::logging::level::ERR>(
609                     "Error: processI2cEvent()");
610                 return;
611             }
612 
613             processI2cEvent();
614         });
615 
616     return 0;
617 }
618 
ipmbChannelUpdateTargetAddress(const uint8_t newBmcTargetAddr)619 int IpmbChannel::ipmbChannelUpdateTargetAddress(const uint8_t newBmcTargetAddr)
620 {
621     if (ipmbi2cTargetFd > 0)
622     {
623         i2cTargetDescriptor.close();
624         close(ipmbi2cTargetFd);
625         ipmbi2cTargetFd = 0;
626     }
627 
628     // disable old I2C target driver by command:
629     //     echo "0x1010" > /sys/bus/i2c/devices/i2c-0/delete_device
630     std::string deviceFileName;
631     std::string para;
632     std::fstream deviceFile;
633     deviceFileName = "/sys/bus/i2c/devices/i2c-" + std::to_string(ipmbBusId) +
634                      "/delete_device";
635     para = "0x1010"; // align with removed ipmb0 definition in dts file
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 delete "
641             "sysfs");
642         return -1;
643     }
644     deviceFile << para;
645     deviceFile.close();
646 
647     // enable new I2C target driver by command:
648     //      echo "ipmb-dev 0x1012" > /sys/bus/i2c/devices/i2c-0/new_device
649     deviceFileName =
650         "/sys/bus/i2c/devices/i2c-" + std::to_string(ipmbBusId) + "/new_device";
651     std::ostringstream hex;
652     uint16_t addr = 0x1000 + (newBmcTargetAddr >> 1);
653     hex << std::hex << static_cast<uint16_t>(addr);
654     const std::string& addressHexStr = hex.str();
655     para = "ipmb-dev 0x" + addressHexStr;
656     deviceFile.open(deviceFileName, std::ios::out);
657     if (!deviceFile.good())
658     {
659         phosphor::logging::log<phosphor::logging::level::ERR>(
660             "ipmbChannelUpdateTargetAddress: error opening deviceFile to create "
661             "sysfs");
662         return -1;
663     }
664     deviceFile << para;
665     deviceFile.close();
666 
667     // open fd to i2c target device
668     std::string ipmbI2cTargetStr = "/dev/ipmb-" + std::to_string(ipmbBusId);
669     ipmbi2cTargetFd = open(ipmbI2cTargetStr.c_str(), O_RDWR | O_NONBLOCK);
670     if (ipmbi2cTargetFd < 0)
671     {
672         phosphor::logging::log<phosphor::logging::level::ERR>(
673             "ipmbChannelInit: error opening ipmbI2cTarget");
674         return -1;
675     }
676 
677     // start to receive i2c data as target
678     i2cTargetDescriptor.assign(ipmbi2cTargetFd);
679     i2cTargetDescriptor.async_wait(
680         boost::asio::posix::descriptor_base::wait_read,
681         [this](const boost::system::error_code& ec) {
682             if (ec)
683             {
684                 phosphor::logging::log<phosphor::logging::level::ERR>(
685                     "Error: processI2cEvent()");
686                 return;
687             }
688 
689             processI2cEvent();
690         });
691 
692     ipmbBmcTargetAddress = newBmcTargetAddr;
693 
694     return 0;
695 }
696 
getBusId()697 uint8_t IpmbChannel::getBusId()
698 {
699     return ipmbBusId;
700 }
701 
getBmcTargetAddress()702 uint8_t IpmbChannel::getBmcTargetAddress()
703 {
704     return ipmbBmcTargetAddress;
705 }
706 
getRqTargetAddress()707 uint8_t IpmbChannel::getRqTargetAddress()
708 {
709     return ipmbRqTargetAddress;
710 }
711 
getDevIndex()712 uint8_t IpmbChannel::getDevIndex()
713 {
714     return channelIdx >> 2;
715 }
716 
getChannelIdx()717 uint8_t IpmbChannel::getChannelIdx()
718 {
719     return channelIdx;
720 }
721 
getChannelType()722 ipmbChannelType IpmbChannel::getChannelType()
723 {
724     return static_cast<ipmbChannelType>((channelIdx & 3));
725 }
726 
addFilter(const uint8_t respNetFn,const uint8_t cmd)727 void IpmbChannel::addFilter(const uint8_t respNetFn, const uint8_t cmd)
728 {
729     if (commandFilter)
730     {
731         commandFilter->addFilter(respNetFn, cmd);
732     }
733 }
734 
735 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)736     IpmbChannel::requestAdd(boost::asio::yield_context& yield,
737                             std::shared_ptr<IpmbRequest> request)
738 {
739     makeRequestValid(request);
740 
741     std::vector<uint8_t> buffer{};
742     if (request->ipmbToi2cConstruct(buffer) != 0)
743     {
744         makeRequestInvalid(*request);
745         return returnStatus(ipmbResponseStatus::error);
746     }
747 
748     for (int i = 0; i < ipmbNumberOfTries; i++)
749     {
750         boost::system::error_code ec;
751         int i2cRetryCnt = 0;
752 
753         for (; i2cRetryCnt < ipmbI2cNumberOfRetries; i2cRetryCnt++)
754         {
755             boost::asio::async_write(i2cTargetDescriptor,
756                                      boost::asio::buffer(buffer), yield[ec]);
757 
758             if (ec)
759             {
760                 continue; // retry
761             }
762             break;
763         }
764 
765         if (i2cRetryCnt == ipmbI2cNumberOfRetries)
766         {
767             std::string msgToLog =
768                 "requestAdd: Sent to I2C failed after retries."
769                 " busId=" +
770                 std::to_string(ipmbBusId) + ", error=" + ec.message();
771             phosphor::logging::log<phosphor::logging::level::INFO>(
772                 msgToLog.c_str());
773         }
774 
775         request->timer->expires_after(
776             std::chrono::milliseconds(ipmbRequestRetryTimeout));
777         request->timer->async_wait(yield[ec]);
778 
779         if (ec && ec != boost::asio::error::operation_aborted)
780         {
781             // unexpected error - invalidate request and return generic error
782             phosphor::logging::log<phosphor::logging::level::ERR>(
783                 "requestAdd: async_wait error");
784             makeRequestInvalid(*request);
785             return returnStatus(ipmbResponseStatus::error);
786         }
787 
788         if (request->state == ipmbRequestState::matched)
789         {
790             // matched response, send it to client application
791             makeRequestInvalid(*request);
792             return request->returnMatchedResponse();
793         }
794     }
795 
796     makeRequestInvalid(*request);
797     return returnStatus(ipmbResponseStatus::timeout);
798 }
799 
getChannel(uint8_t reqChannel)800 static IpmbChannel* getChannel(uint8_t reqChannel)
801 {
802     auto channel =
803         std::find_if(ipmbChannels.begin(), ipmbChannels.end(),
804                      [reqChannel](IpmbChannel& channel) {
805                          return channel.getChannelIdx() == reqChannel;
806                      });
807     if (channel != ipmbChannels.end())
808     {
809         return &(*channel);
810     }
811 
812     return nullptr;
813 }
814 
initializeChannels()815 static int initializeChannels()
816 {
817     std::shared_ptr<IpmbCommandFilter> commandFilter =
818         std::make_shared<IpmbCommandFilter>();
819 
820     constexpr const char* configFilePath =
821         "/usr/share/ipmbbridge/ipmb-channels.json";
822     std::ifstream configFile(configFilePath);
823     if (!configFile.is_open())
824     {
825         phosphor::logging::log<phosphor::logging::level::ERR>(
826             "initializeChannels: Cannot open config path");
827         return -1;
828     }
829     try
830     {
831         uint8_t devIndex = 0;
832         auto data = nlohmann::json::parse(configFile, nullptr);
833         for (const auto& channelConfig : data["channels"])
834         {
835             const std::string& typeConfig = channelConfig["type"];
836             const std::string& targetPath = channelConfig["slave-path"];
837             uint8_t bmcAddr = channelConfig["bmc-addr"];
838             uint8_t reqAddr = channelConfig["remote-addr"];
839 
840             ipmbChannelType type = ipmbChannelTypeMap.at(typeConfig);
841 
842             if (channelConfig.contains("devIndex"))
843             {
844                 devIndex = channelConfig["devIndex"];
845             }
846 
847             auto channel = ipmbChannels.emplace(
848                 ipmbChannels.end(), io, bmcAddr, reqAddr,
849                 ((devIndex << 2) | static_cast<uint8_t>(type)), commandFilter);
850             if (channel->ipmbChannelInit(targetPath.c_str()) < 0)
851             {
852                 phosphor::logging::log<phosphor::logging::level::ERR>(
853                     "initializeChannels: channel initialization failed");
854                 return -1;
855             }
856         }
857     }
858     catch (const nlohmann::json::exception& e)
859     {
860         phosphor::logging::log<phosphor::logging::level::ERR>(
861             "initializeChannels: Error parsing config file");
862         return -1;
863     }
864     catch (const std::out_of_range& e)
865     {
866         phosphor::logging::log<phosphor::logging::level::ERR>(
867             "initializeChannels: Error invalid type");
868         return -1;
869     }
870     return 0;
871 }
872 
873 auto ipmbHandleRequest =
874     [](boost::asio::yield_context yield, uint8_t reqChannel, uint8_t netfn,
__anon377db2e00802(boost::asio::yield_context yield, uint8_t reqChannel, uint8_t netfn, uint8_t lun, uint8_t cmd, std::vector<uint8_t> dataReceived) 875        uint8_t lun, uint8_t cmd, std::vector<uint8_t> dataReceived) {
876         IpmbChannel* channel = getChannel(reqChannel);
877 
878         if (channel == nullptr)
879         {
880             phosphor::logging::log<phosphor::logging::level::ERR>(
881                 "ipmbHandleRequest: requested channel does not exist");
882             return returnStatus(ipmbResponseStatus::invalid_param);
883         }
884 
885         // check outstanding request list for valid sequence number
886         uint8_t seqNum = 0;
887         bool seqValid = channel->seqNumGet(seqNum);
888         if (!seqValid)
889         {
890             phosphor::logging::log<phosphor::logging::level::WARNING>(
891                 "ipmbHandleRequest: cannot add more requests to the list");
892             return returnStatus(ipmbResponseStatus::busy);
893         }
894 
895         uint8_t bmcTargetAddress = channel->getBmcTargetAddress();
896         uint8_t rqTargetAddress = channel->getRqTargetAddress();
897 
898         // construct the request to add it to outstanding request list
899         std::shared_ptr<IpmbRequest> request = std::make_shared<IpmbRequest>(
900             rqTargetAddress, netfn, ipmbRsLun, bmcTargetAddress, seqNum, lun,
901             cmd, dataReceived);
902 
903         if (!request->timer)
904         {
905             phosphor::logging::log<phosphor::logging::level::ERR>(
906                 "ipmbHandleRequest: timer object does not exist");
907             return returnStatus(ipmbResponseStatus::error);
908         }
909 
910         return channel->requestAdd(yield, request);
911     };
912 
addUpdateTargetAddrHandler()913 void addUpdateTargetAddrHandler()
914 {
915     // callback to handle dbus signal of updating target addr
916     std::function<void(sdbusplus::message_t&)> updateTargetAddrHandler =
917         [](sdbusplus::message_t& message) {
918             uint8_t reqChannel, busId, targetAddr;
919 
920             // valid source of signal, check whether from multi-node manager
921             std::string pathName = message.get_path();
922             if (pathName != "/xyz/openbmc_project/MultiNode/Status")
923             {
924                 phosphor::logging::log<phosphor::logging::level::ERR>(
925                     "addUpdateTargetAddrHandler: invalid obj path");
926                 return;
927             }
928 
929             message.read(reqChannel, busId, targetAddr);
930 
931             IpmbChannel* channel = getChannel(reqChannel);
932 
933             if (channel == nullptr ||
934                 channel->getChannelType() != ipmbChannelType::ipmb)
935             {
936                 phosphor::logging::log<phosphor::logging::level::ERR>(
937                     "addUpdateTargetAddrHandler: invalid channel");
938                 return;
939             }
940             if (busId != channel->getBusId())
941             {
942                 phosphor::logging::log<phosphor::logging::level::ERR>(
943                     "addUpdateTargetAddrHandler: invalid busId");
944                 return;
945             }
946             if (channel->getBmcTargetAddress() == targetAddr)
947             {
948                 phosphor::logging::log<phosphor::logging::level::INFO>(
949                     "addUpdateTargetAddrHandler: channel bmc target addr is "
950                     "unchanged, do nothing");
951                 return;
952             }
953 
954             channel->ipmbChannelUpdateTargetAddress(targetAddr);
955         };
956 
957     static auto match = std::make_unique<sdbusplus::bus::match_t>(
958         static_cast<sdbusplus::bus_t&>(*conn),
959         "type='signal',member='updateBmcSlaveAddr',", updateTargetAddrHandler);
960 }
961 
addSendBroadcastHandler()962 void addSendBroadcastHandler()
963 {
964     // callback to handle dbus signal of sending broadcast message
965     std::function<void(sdbusplus::message_t&)> sendBroadcastHandler =
966         [](sdbusplus::message_t& message) {
967             uint8_t reqChannel, netFn, lun, cmd;
968             std::vector<uint8_t> dataReceived;
969             message.read(reqChannel, netFn, lun, cmd, dataReceived);
970 
971             IpmbChannel* channel = getChannel(reqChannel);
972 
973             if (channel == nullptr)
974             {
975                 phosphor::logging::log<phosphor::logging::level::ERR>(
976                     "addSendBroadcastMsgHandler: requested channel does not "
977                     "exist");
978                 return;
979             }
980 
981             uint8_t bmcTargetAddress = channel->getBmcTargetAddress();
982             uint8_t seqNum = 0; // seqNum is not used in broadcast msg
983             uint8_t targetAddr = broadcastAddress;
984 
985             std::shared_ptr<IpmbRequest> request =
986                 std::make_shared<IpmbRequest>(targetAddr, netFn, ipmbRsLun,
987                                               bmcTargetAddress, seqNum, lun,
988                                               cmd, dataReceived);
989 
990             std::shared_ptr<std::vector<uint8_t>> buffer =
991                 std::make_shared<std::vector<uint8_t>>();
992 
993             if (request->ipmbToi2cConstruct(*buffer) != 0)
994             {
995                 return;
996             }
997 
998             channel->ipmbSendI2cFrame(buffer);
999         };
1000 
1001     static auto match = std::make_unique<sdbusplus::bus::match_t>(
1002         static_cast<sdbusplus::bus_t&>(*conn),
1003         "type='signal',member='sendBroadcast',", sendBroadcastHandler);
1004 }
1005 
1006 /**
1007  * @brief Main
1008  */
main()1009 int main()
1010 {
1011     conn->request_name(ipmbBus);
1012 
1013     auto server = sdbusplus::asio::object_server(conn);
1014 
1015     std::shared_ptr<sdbusplus::asio::dbus_interface> ipmbIface =
1016         server.add_interface(ipmbObj, ipmbDbusIntf);
1017 
1018     ipmbIface->register_method("sendRequest", std::move(ipmbHandleRequest));
1019     ipmbIface->initialize();
1020 
1021     if (initializeChannels() < 0)
1022     {
1023         phosphor::logging::log<phosphor::logging::level::ERR>(
1024             "Error initializeChannels");
1025         return -1;
1026     }
1027 
1028     addUpdateTargetAddrHandler();
1029 
1030     addSendBroadcastHandler();
1031 
1032     io.run();
1033     return 0;
1034 }
1035