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