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