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{};
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