blob: eed991f0c17e4a466f79e6d520d87485bb3cb23b [file] [log] [blame]
/* Copyright 2018 Intel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ipmbbridged.hpp"
#include "ipmbdefines.hpp"
#include "ipmbutils.hpp"
#include <boost/algorithm/string/replace.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/write.hpp>
#include <nlohmann/json.hpp>
#include <phosphor-logging/log.hpp>
#include <filesystem>
#include <fstream>
#include <list>
#include <tuple>
#include <unordered_map>
/**
* @brief Dbus
*/
static constexpr const char* ipmbBus = "xyz.openbmc_project.Ipmi.Channel.Ipmb";
static constexpr const char* ipmbObj = "/xyz/openbmc_project/Ipmi/Channel/Ipmb";
static constexpr const char* ipmbDbusIntf = "org.openbmc.Ipmb";
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
static std::list<IpmbChannel> ipmbChannels;
static const std::unordered_map<std::string, ipmbChannelType>
ipmbChannelTypeMap = {{"me", ipmbChannelType::me},
{"ipmb", ipmbChannelType::ipmb}};
/**
* @brief Ipmb request class methods
*/
IpmbRequest::IpmbRequest()
{
data.reserve(ipmbMaxDataSize);
}
IpmbRequest::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) :
address(address),
netFn(netFn), rsLun(rsLun), rqSA(rqSA), seq(seq), rqLun(rqLun), cmd(cmd),
timer(io)
{
data.reserve(ipmbMaxDataSize);
state = ipmbRequestState::invalid;
if (inputData.size() > 0)
{
data = std::move(inputData);
}
}
void IpmbRequest::i2cToIpmbConstruct(IPMB_HEADER* ipmbBuffer,
size_t bufferLength)
{
// constructing ipmb request from i2c buffer
netFn = ipmbNetFnGet(ipmbBuffer->Header.Req.rsNetFnLUN);
rsLun = ipmbLunFromNetFnLunGet(ipmbBuffer->Header.Req.rsNetFnLUN);
rqSA = ipmbBuffer->Header.Req.rqSA;
seq = ipmbSeqGet(ipmbBuffer->Header.Req.rqSeqLUN);
rqLun = ipmbLunFromSeqLunGet(ipmbBuffer->Header.Req.rqSeqLUN);
cmd = ipmbBuffer->Header.Req.cmd;
size_t dataLength = bufferLength -
(ipmbConnectionHeaderLength +
ipmbRequestDataHeaderLength + ipmbChecksumSize);
if (dataLength > 0)
{
data.insert(data.end(), ipmbBuffer->Header.Req.data,
&ipmbBuffer->Header.Req.data[dataLength]);
}
}
int IpmbRequest::ipmbToi2cConstruct(std::vector<uint8_t>& buffer)
{
/* Add one byte for length byte as per required by driver */
size_t bufferLength = 1 + data.size() + ipmbRequestDataHeaderLength +
ipmbConnectionHeaderLength + ipmbChecksumSize;
if (bufferLength > ipmbMaxFrameLength)
{
return -1;
}
buffer.resize(bufferLength);
static_assert(ipmbMaxFrameLength >= sizeof(IPMB_HEADER));
IPMB_PKT* ipmbPkt = reinterpret_cast<IPMB_PKT*>(buffer.data());
ipmbPkt->len = bufferLength - 1;
IPMB_HEADER* ipmbBuffer = &(ipmbPkt->hdr);
// constructing buffer from ipmb request
ipmbBuffer->Header.Req.address = address;
ipmbBuffer->Header.Req.rsNetFnLUN = ipmbNetFnLunSet(netFn, rsLun);
ipmbBuffer->Header.Req.rqSA = rqSA;
ipmbBuffer->Header.Req.rqSeqLUN = ipmbSeqLunSet(seq, rqLun);
ipmbBuffer->Header.Req.cmd = cmd;
ipmbBuffer->Header.Req.checksum1 = ipmbChecksumCompute(
(uint8_t*)ipmbBuffer, ipmbConnectionHeaderLength - ipmbChecksumSize);
if (data.size() > 0)
{
std::copy(data.begin(), data.end(), ipmbBuffer->Header.Req.data);
}
buffer[bufferLength - ipmbChecksumSize] =
ipmbChecksumCompute((uint8_t*)ipmbBuffer + ipmbChecksum2StartOffset,
(ipmbRequestDataHeaderLength + data.size()));
return 0;
}
std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>
IpmbRequest::returnMatchedResponse()
{
return std::make_tuple(
static_cast<int>(ipmbResponseStatus::success), matchedResponse->netFn,
matchedResponse->rsLun, matchedResponse->cmd,
matchedResponse->completionCode, matchedResponse->data);
}
static std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>
returnStatus(ipmbResponseStatus status)
{
// we only want to send status here, other fields are not relevant
return std::make_tuple(static_cast<int>(status), 0, 0, 0, 0,
std::vector<uint8_t>(0));
}
/**
* @brief Ipmb response class methods
*/
IpmbResponse::IpmbResponse()
{
data.reserve(ipmbMaxDataSize);
}
IpmbResponse::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) :
address(address),
netFn(netFn), rqLun(rqLun), rsSA(rsSA), seq(seq), rsLun(rsLun), cmd(cmd),
completionCode(completionCode)
{
data.reserve(ipmbMaxDataSize);
if (inputData.size() > 0)
{
data = std::move(inputData);
}
}
void IpmbResponse::i2cToIpmbConstruct(IPMB_HEADER* ipmbBuffer,
size_t bufferLength)
{
netFn = ipmbNetFnGet(ipmbBuffer->Header.Resp.rqNetFnLUN);
rqLun = ipmbLunFromNetFnLunGet(ipmbBuffer->Header.Resp.rqNetFnLUN);
rsSA = ipmbBuffer->Header.Resp.rsSA;
seq = ipmbSeqGet(ipmbBuffer->Header.Resp.rsSeqLUN);
rsLun = ipmbLunFromSeqLunGet(ipmbBuffer->Header.Resp.rsSeqLUN);
cmd = ipmbBuffer->Header.Resp.cmd;
completionCode = ipmbBuffer->Header.Resp.completionCode;
size_t dataLength = bufferLength -
(ipmbConnectionHeaderLength +
ipmbResponseDataHeaderLength + ipmbChecksumSize);
if (dataLength > 0)
{
data.insert(data.end(), ipmbBuffer->Header.Resp.data,
&ipmbBuffer->Header.Resp.data[dataLength]);
}
}
std::shared_ptr<std::vector<uint8_t>> IpmbResponse::ipmbToi2cConstruct()
{
/* Add one byte for length byte as per required by driver */
size_t bufferLength = 1 + data.size() + ipmbResponseDataHeaderLength +
ipmbConnectionHeaderLength + ipmbChecksumSize;
if (bufferLength > ipmbMaxFrameLength)
{
return nullptr;
}
std::shared_ptr<std::vector<uint8_t>> buffer =
std::make_shared<std::vector<uint8_t>>(bufferLength);
IPMB_PKT* ipmbPkt = reinterpret_cast<IPMB_PKT*>(buffer->data());
ipmbPkt->len = bufferLength - 1;
IPMB_HEADER* ipmbBuffer = &(ipmbPkt->hdr);
ipmbBuffer->Header.Resp.address = address;
ipmbBuffer->Header.Resp.rqNetFnLUN = ipmbNetFnLunSet(netFn, rqLun);
ipmbBuffer->Header.Resp.rsSA = rsSA;
ipmbBuffer->Header.Resp.rsSeqLUN = ipmbSeqLunSet(seq, rsLun);
ipmbBuffer->Header.Resp.cmd = cmd;
ipmbBuffer->Header.Resp.completionCode = completionCode;
ipmbBuffer->Header.Resp.checksum1 = ipmbChecksumCompute(
(uint8_t*)ipmbBuffer, ipmbConnectionHeaderLength - ipmbChecksumSize);
if (data.size() > 0)
{
std::copy(data.begin(), data.end(), ipmbBuffer->Header.Resp.data);
}
(*buffer)[bufferLength - ipmbChecksumSize] =
ipmbChecksumCompute((uint8_t*)ipmbBuffer + ipmbChecksum2StartOffset,
(ipmbResponseDataHeaderLength + data.size()));
return buffer;
}
bool IpmbCommandFilter::isBlocked(const uint8_t reqNetFn, const uint8_t cmd)
{
auto blockedCmd = unhandledCommands.find({reqNetFn, cmd});
if (blockedCmd != unhandledCommands.end())
{
return true;
}
return false;
}
void IpmbCommandFilter::addFilter(const uint8_t reqNetFn, const uint8_t cmd)
{
if (unhandledCommands.insert({reqNetFn, cmd}).second)
{
phosphor::logging::log<phosphor::logging::level::INFO>(
"addFilter: added command to filter",
phosphor::logging::entry("netFn = %d", reqNetFn),
phosphor::logging::entry("cmd = %d", cmd));
}
}
/**
* @brief Ipmb channel
*/
void IpmbChannel::ipmbSendI2cFrame(std::shared_ptr<std::vector<uint8_t>> buffer,
size_t retriesAttempted = 0)
{
IPMB_PKT* ipmbPkt = reinterpret_cast<IPMB_PKT*>(buffer->data());
uint8_t targetAddr = ipmbIsResponse(&(ipmbPkt->hdr))
? ipmbPkt->hdr.Header.Resp.address
: ipmbPkt->hdr.Header.Req.address;
boost::asio::async_write(i2cSlaveDescriptor, boost::asio::buffer(*buffer),
[this, buffer, retriesAttempted,
targetAddr](const boost::system::error_code& ec,
size_t /* bytesSent */) {
if (ec)
{
size_t currentRetryCnt = retriesAttempted;
if (currentRetryCnt > ipmbI2cNumberOfRetries)
{
std::string msgToLog =
"ipmbSendI2cFrame: send to I2C failed after retries."
" busId=" +
std::to_string(ipmbBusId) +
", targetAddr=" + std::to_string(targetAddr) +
", error=" + ec.message();
phosphor::logging::log<phosphor::logging::level::ERR>(
msgToLog.c_str());
return;
}
currentRetryCnt++;
ipmbSendI2cFrame(buffer, currentRetryCnt);
}
});
}
/**
* @brief Ipmb Outstanding Requests
*/
void IpmbChannel::makeRequestInvalid(IpmbRequest& request)
{
// change request state to invalid and remove it from outstanding requests
// list
request.state = ipmbRequestState::invalid;
outstandingRequests[request.seq] = nullptr;
}
void IpmbChannel::makeRequestValid(std::shared_ptr<IpmbRequest> request)
{
// change request state to valid and add it to outstanding requests list
request->state = ipmbRequestState::valid;
outstandingRequests[request->seq] = request;
}
bool IpmbChannel::seqNumGet(uint8_t& seq)
{
static uint8_t seqNum = 0;
for (int i = 0; i < ipmbMaxOutstandingRequestsCount; i++)
{
seqNum = (seqNum + 1) % ipmbMaxOutstandingRequestsCount;
if (outstandingRequests[seqNum] == nullptr)
{
seq = seqNum;
return true;
}
}
return false;
}
void IpmbChannel::responseMatch(std::unique_ptr<IpmbResponse>& response)
{
std::shared_ptr<IpmbRequest> request = outstandingRequests[response->seq];
if (request != nullptr)
{
if (((ipmbRespNetFn(request->netFn)) == (response->netFn)) &&
((request->rqLun) == (response->rqLun)) &&
((request->rsLun) == (response->rsLun)) &&
((request->cmd) == (response->cmd)))
{
// match, response is corresponding to previously sent request
request->state = ipmbRequestState::matched;
request->timer->cancel();
request->matchedResponse = std::move(response);
}
}
}
void IpmbChannel::processI2cEvent()
{
std::array<uint8_t, ipmbMaxFrameLength> buffer{};
IPMB_PKT* ipmbPkt = reinterpret_cast<IPMB_PKT*>(buffer.data());
IPMB_HEADER* ipmbFrame = &(ipmbPkt->hdr);
lseek(ipmbi2cSlaveFd, 0, SEEK_SET);
ssize_t r = read(ipmbi2cSlaveFd, buffer.data(), ipmbMaxFrameLength);
// Handle error cases.
if (r < 0)
{
goto end;
}
/* Substract first byte len size from total frame length */
r--;
if ((r < ipmbMinFrameLength) || (r > ipmbMaxFrameLength))
{
goto end;
}
// valiate the frame
if (!isFrameValid(ipmbFrame, r))
{
goto end;
}
// if it is message received from ipmb channel, send out dbus signal
if (getChannelType() == ipmbChannelType::ipmb)
{
auto ipmbMessageReceived = IpmbRequest();
ipmbMessageReceived.i2cToIpmbConstruct(ipmbFrame, r);
sdbusplus::message_t msg = conn->new_signal(ipmbObj, ipmbDbusIntf,
"receiveBroadcast");
msg.append(ipmbMessageReceived.netFn, ipmbMessageReceived.cmd,
ipmbMessageReceived.data);
msg.signal_send();
}
// copy frame to ipmib message buffer
if (ipmbIsResponse(ipmbFrame))
{
std::unique_ptr<IpmbResponse> ipmbMessageReceived =
std::make_unique<IpmbResponse>();
ipmbMessageReceived->i2cToIpmbConstruct(ipmbFrame, r);
// try to match response with outstanding request
responseMatch(ipmbMessageReceived);
}
else
{
// if command is blocked - respond with 'invalid command'
// completion code
if (commandFilter)
{
uint8_t netFn = ipmbNetFnGet(ipmbFrame->Header.Req.rsNetFnLUN);
uint8_t cmd = ipmbFrame->Header.Req.cmd;
uint8_t rqSA = ipmbFrame->Header.Req.rqSA;
if (commandFilter->isBlocked(netFn, cmd))
{
uint8_t seq = ipmbSeqGet(ipmbFrame->Header.Req.rqSeqLUN);
uint8_t lun =
ipmbLunFromSeqLunGet(ipmbFrame->Header.Req.rqSeqLUN);
// prepare generic response
auto ipmbResponse = IpmbResponse(
rqSA, ipmbRespNetFn(netFn), lun, ipmbBmcSlaveAddress, seq,
ipmbRsLun, cmd, ipmbIpmiInvalidCmd, {});
auto buffer = ipmbResponse.ipmbToi2cConstruct();
if (buffer)
{
ipmbSendI2cFrame(buffer);
}
goto end;
}
}
auto ipmbMessageReceived = IpmbRequest();
ipmbMessageReceived.i2cToIpmbConstruct(ipmbFrame, r);
int devId = getDevIndex();
std::map<std::string, std::variant<int>> options{
{"rqSA", ipmbAddressTo7BitSet(ipmbMessageReceived.rqSA)},
{"hostId", devId}};
using IpmiDbusRspType = std::tuple<uint8_t, uint8_t, uint8_t, uint8_t,
std::vector<uint8_t>>;
conn->async_method_call(
[this, rqLun{ipmbMessageReceived.rqLun},
seq{ipmbMessageReceived.seq}, address{ipmbMessageReceived.rqSA}](
const boost::system::error_code& ec,
const IpmiDbusRspType& response) {
const auto& [netfn, lun, cmd, cc, payload] = response;
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"processI2cEvent: error getting response from IPMI");
return;
}
uint8_t bmcSlaveAddress = getBmcSlaveAddress();
if (payload.size() > ipmbMaxDataSize)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"processI2cEvent: response exceeding maximum size");
// prepare generic response
auto ipmbResponse = IpmbResponse(
address, netfn, rqLun, bmcSlaveAddress, seq, ipmbRsLun, cmd,
ipmbIpmiCmdRespNotProvided, {});
auto buffer = ipmbResponse.ipmbToi2cConstruct();
if (buffer)
{
ipmbSendI2cFrame(buffer);
}
return;
}
if (!(netfn & ipmbNetFnResponseMask))
{
// we are not expecting request here
phosphor::logging::log<phosphor::logging::level::ERR>(
"processI2cEvent: got a request instead of response");
return;
}
// if command is not supported, add it to filter
if (cc == ipmbIpmiInvalidCmd)
{
addFilter(ipmbReqNetFnFromRespNetFn(netfn), cmd);
}
// payload is empty after constructor invocation
auto ipmbResponse = IpmbResponse(address, netfn, rqLun,
bmcSlaveAddress, seq, lun, cmd, cc,
payload);
auto buffer = ipmbResponse.ipmbToi2cConstruct();
if (!buffer)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"processI2cEvent: error constructing a request");
return;
}
ipmbSendI2cFrame(buffer);
},
"xyz.openbmc_project.Ipmi.Host", "/xyz/openbmc_project/Ipmi",
"xyz.openbmc_project.Ipmi.Server", "execute",
ipmbMessageReceived.netFn, ipmbMessageReceived.rsLun,
ipmbMessageReceived.cmd, ipmbMessageReceived.data, options);
}
end:
i2cSlaveDescriptor.async_wait(
boost::asio::posix::descriptor_base::wait_read,
[this](const boost::system::error_code& ec) {
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Error: processI2cEvent()");
return;
}
processI2cEvent();
});
}
IpmbChannel::IpmbChannel(boost::asio::io_context& io,
uint8_t ipmbBmcSlaveAddress,
uint8_t ipmbRqSlaveAddress, uint8_t channelIdx,
std::shared_ptr<IpmbCommandFilter> commandFilter) :
i2cSlaveDescriptor(io),
ipmbBmcSlaveAddress(ipmbBmcSlaveAddress),
ipmbRqSlaveAddress(ipmbRqSlaveAddress), channelIdx(channelIdx),
commandFilter(commandFilter)
{}
int IpmbChannel::ipmbChannelInit(const char* ipmbI2cSlave)
{
// extract bus id from slave path and save
std::string ipmbI2cSlaveStr(ipmbI2cSlave);
auto findHyphen = ipmbI2cSlaveStr.find("-");
std::string busStr = ipmbI2cSlaveStr.substr(findHyphen + 1);
try
{
ipmbBusId = std::stoi(busStr);
}
catch (const std::invalid_argument&)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"ipmbChannelInit: invalid bus id in slave-path config");
return -1;
}
// Check if sysfs has device. If not, enable I2C slave driver by command
// echo "ipmb-dev 0x1010" > /sys/bus/i2c/devices/i2c-0/new_device
bool hasSysfs = std::filesystem::exists(ipmbI2cSlave);
if (!hasSysfs)
{
std::string deviceFileName = "/sys/bus/i2c/devices/i2c-" + busStr +
"/new_device";
std::string para = "ipmb-dev 0x1010"; // init with BMC addr 0x20
std::fstream deviceFile;
deviceFile.open(deviceFileName, std::ios::out);
if (!deviceFile.good())
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"ipmbChannelInit: error opening deviceFile");
return -1;
}
deviceFile << para;
deviceFile.close();
}
// open fd to i2c slave device for read write
ipmbi2cSlaveFd = open(ipmbI2cSlave, O_RDWR | O_NONBLOCK | O_CLOEXEC);
if (ipmbi2cSlaveFd < 0)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"ipmbChannelInit: error opening ipmbI2cSlave");
return -1;
}
i2cSlaveDescriptor.assign(ipmbi2cSlaveFd);
i2cSlaveDescriptor.async_wait(
boost::asio::posix::descriptor_base::wait_read,
[this](const boost::system::error_code& ec) {
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Error: processI2cEvent()");
return;
}
processI2cEvent();
});
return 0;
}
int IpmbChannel::ipmbChannelUpdateSlaveAddress(const uint8_t newBmcSlaveAddr)
{
if (ipmbi2cSlaveFd > 0)
{
i2cSlaveDescriptor.close();
close(ipmbi2cSlaveFd);
ipmbi2cSlaveFd = 0;
}
// disable old I2C slave driver by command:
// echo "0x1010" > /sys/bus/i2c/devices/i2c-0/delete_device
std::string deviceFileName;
std::string para;
std::fstream deviceFile;
deviceFileName = "/sys/bus/i2c/devices/i2c-" + std::to_string(ipmbBusId) +
"/delete_device";
para = "0x1010"; // align with removed ipmb0 definition in dts file
deviceFile.open(deviceFileName, std::ios::out);
if (!deviceFile.good())
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"ipmbChannelUpdateSlaveAddress: error opening deviceFile to delete "
"sysfs");
return -1;
}
deviceFile << para;
deviceFile.close();
// enable new I2C slave driver by command:
// echo "ipmb-dev 0x1012" > /sys/bus/i2c/devices/i2c-0/new_device
deviceFileName = "/sys/bus/i2c/devices/i2c-" + std::to_string(ipmbBusId) +
"/new_device";
std::ostringstream hex;
uint16_t addr = 0x1000 + (newBmcSlaveAddr >> 1);
hex << std::hex << static_cast<uint16_t>(addr);
const std::string& addressHexStr = hex.str();
para = "ipmb-dev 0x" + addressHexStr;
deviceFile.open(deviceFileName, std::ios::out);
if (!deviceFile.good())
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"ipmbChannelUpdateSlaveAddress: error opening deviceFile to create "
"sysfs");
return -1;
}
deviceFile << para;
deviceFile.close();
// open fd to i2c slave device
std::string ipmbI2cSlaveStr = "/dev/ipmb-" + std::to_string(ipmbBusId);
ipmbi2cSlaveFd = open(ipmbI2cSlaveStr.c_str(), O_RDWR | O_NONBLOCK);
if (ipmbi2cSlaveFd < 0)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"ipmbChannelInit: error opening ipmbI2cSlave");
return -1;
}
// start to receive i2c data as slave
i2cSlaveDescriptor.assign(ipmbi2cSlaveFd);
i2cSlaveDescriptor.async_wait(
boost::asio::posix::descriptor_base::wait_read,
[this](const boost::system::error_code& ec) {
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Error: processI2cEvent()");
return;
}
processI2cEvent();
});
ipmbBmcSlaveAddress = newBmcSlaveAddr;
return 0;
}
uint8_t IpmbChannel::getBusId()
{
return ipmbBusId;
}
uint8_t IpmbChannel::getBmcSlaveAddress()
{
return ipmbBmcSlaveAddress;
}
uint8_t IpmbChannel::getRqSlaveAddress()
{
return ipmbRqSlaveAddress;
}
uint8_t IpmbChannel::getDevIndex()
{
return channelIdx >> 2;
}
uint8_t IpmbChannel::getChannelIdx()
{
return channelIdx;
}
ipmbChannelType IpmbChannel::getChannelType()
{
return static_cast<ipmbChannelType>((channelIdx & 3));
}
void IpmbChannel::addFilter(const uint8_t respNetFn, const uint8_t cmd)
{
if (commandFilter)
{
commandFilter->addFilter(respNetFn, cmd);
}
}
std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>
IpmbChannel::requestAdd(boost::asio::yield_context& yield,
std::shared_ptr<IpmbRequest> request)
{
makeRequestValid(request);
std::vector<uint8_t> buffer(0);
if (request->ipmbToi2cConstruct(buffer) != 0)
{
return returnStatus(ipmbResponseStatus::error);
}
for (int i = 0; i < ipmbNumberOfTries; i++)
{
boost::system::error_code ec;
int i2cRetryCnt = 0;
for (; i2cRetryCnt < ipmbI2cNumberOfRetries; i2cRetryCnt++)
{
boost::asio::async_write(i2cSlaveDescriptor,
boost::asio::buffer(buffer), yield[ec]);
if (ec)
{
continue; // retry
}
break;
}
if (i2cRetryCnt == ipmbI2cNumberOfRetries)
{
std::string msgToLog =
"requestAdd: Sent to I2C failed after retries."
" busId=" +
std::to_string(ipmbBusId) + ", error=" + ec.message();
phosphor::logging::log<phosphor::logging::level::INFO>(
msgToLog.c_str());
}
request->timer->expires_after(
std::chrono::milliseconds(ipmbRequestRetryTimeout));
request->timer->async_wait(yield[ec]);
if (ec && ec != boost::asio::error::operation_aborted)
{
// unexpected error - invalidate request and return generic error
phosphor::logging::log<phosphor::logging::level::ERR>(
"requestAdd: async_wait error");
makeRequestInvalid(*request);
return returnStatus(ipmbResponseStatus::error);
}
if (request->state == ipmbRequestState::matched)
{
// matched response, send it to client application
makeRequestInvalid(*request);
return request->returnMatchedResponse();
}
}
makeRequestInvalid(*request);
return returnStatus(ipmbResponseStatus::timeout);
}
static IpmbChannel* getChannel(uint8_t reqChannel)
{
auto channel = std::find_if(ipmbChannels.begin(), ipmbChannels.end(),
[reqChannel](IpmbChannel& channel) {
return channel.getChannelIdx() == reqChannel;
});
if (channel != ipmbChannels.end())
{
return &(*channel);
}
return nullptr;
}
static int initializeChannels()
{
std::shared_ptr<IpmbCommandFilter> commandFilter =
std::make_shared<IpmbCommandFilter>();
constexpr const char* configFilePath =
"/usr/share/ipmbbridge/ipmb-channels.json";
std::ifstream configFile(configFilePath);
if (!configFile.is_open())
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"initializeChannels: Cannot open config path");
return -1;
}
try
{
uint8_t devIndex = 0;
auto data = nlohmann::json::parse(configFile, nullptr);
for (const auto& channelConfig : data["channels"])
{
const std::string& typeConfig = channelConfig["type"];
const std::string& slavePath = channelConfig["slave-path"];
uint8_t bmcAddr = channelConfig["bmc-addr"];
uint8_t reqAddr = channelConfig["remote-addr"];
ipmbChannelType type = ipmbChannelTypeMap.at(typeConfig);
if (channelConfig.contains("devIndex"))
{
devIndex = channelConfig["devIndex"];
}
auto channel = ipmbChannels.emplace(
ipmbChannels.end(), io, bmcAddr, reqAddr,
((devIndex << 2) | static_cast<uint8_t>(type)), commandFilter);
if (channel->ipmbChannelInit(slavePath.c_str()) < 0)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"initializeChannels: channel initialization failed");
return -1;
}
}
}
catch (const nlohmann::json::exception& e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"initializeChannels: Error parsing config file");
return -1;
}
catch (const std::out_of_range& e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"initializeChannels: Error invalid type");
return -1;
}
return 0;
}
auto ipmbHandleRequest = [](boost::asio::yield_context yield,
uint8_t reqChannel, uint8_t netfn, uint8_t lun,
uint8_t cmd, std::vector<uint8_t> dataReceived) {
IpmbChannel* channel = getChannel(reqChannel);
if (channel == nullptr)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"ipmbHandleRequest: requested channel does not exist");
return returnStatus(ipmbResponseStatus::invalid_param);
}
// check outstanding request list for valid sequence number
uint8_t seqNum = 0;
bool seqValid = channel->seqNumGet(seqNum);
if (!seqValid)
{
phosphor::logging::log<phosphor::logging::level::WARNING>(
"ipmbHandleRequest: cannot add more requests to the list");
return returnStatus(ipmbResponseStatus::busy);
}
uint8_t bmcSlaveAddress = channel->getBmcSlaveAddress();
uint8_t rqSlaveAddress = channel->getRqSlaveAddress();
// construct the request to add it to outstanding request list
std::shared_ptr<IpmbRequest> request = std::make_shared<IpmbRequest>(
rqSlaveAddress, netfn, ipmbRsLun, bmcSlaveAddress, seqNum, lun, cmd,
dataReceived);
if (!request->timer)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"ipmbHandleRequest: timer object does not exist");
return returnStatus(ipmbResponseStatus::error);
}
return channel->requestAdd(yield, request);
};
void addUpdateSlaveAddrHandler()
{
// callback to handle dbus signal of updating slave addr
std::function<void(sdbusplus::message_t&)> updateSlaveAddrHandler =
[](sdbusplus::message_t& message) {
uint8_t reqChannel, busId, slaveAddr;
// valid source of signal, check whether from multi-node manager
std::string pathName = message.get_path();
if (pathName != "/xyz/openbmc_project/MultiNode/Status")
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"addUpdateSlaveAddrHandler: invalid obj path");
return;
}
message.read(reqChannel, busId, slaveAddr);
IpmbChannel* channel = getChannel(reqChannel);
if (channel == nullptr ||
channel->getChannelType() != ipmbChannelType::ipmb)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"addUpdateSlaveAddrHandler: invalid channel");
return;
}
if (busId != channel->getBusId())
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"addUpdateSlaveAddrHandler: invalid busId");
return;
}
if (channel->getBmcSlaveAddress() == slaveAddr)
{
phosphor::logging::log<phosphor::logging::level::INFO>(
"addUpdateSlaveAddrHandler: channel bmc slave addr is "
"unchanged, do nothing");
return;
}
channel->ipmbChannelUpdateSlaveAddress(slaveAddr);
};
static auto match = std::make_unique<sdbusplus::bus::match_t>(
static_cast<sdbusplus::bus_t&>(*conn),
"type='signal',member='updateBmcSlaveAddr',", updateSlaveAddrHandler);
}
void addSendBroadcastHandler()
{
// callback to handle dbus signal of sending broadcast message
std::function<void(sdbusplus::message_t&)> sendBroadcastHandler =
[](sdbusplus::message_t& message) {
uint8_t reqChannel, netFn, lun, cmd;
std::vector<uint8_t> dataReceived;
message.read(reqChannel, netFn, lun, cmd, dataReceived);
IpmbChannel* channel = getChannel(reqChannel);
if (channel == nullptr)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"addSendBroadcastMsgHandler: requested channel does not "
"exist");
return;
}
uint8_t bmcSlaveAddress = channel->getBmcSlaveAddress();
uint8_t seqNum = 0; // seqNum is not used in broadcast msg
uint8_t targetAddr = broadcastAddress;
std::shared_ptr<IpmbRequest> request = std::make_shared<IpmbRequest>(
targetAddr, netFn, ipmbRsLun, bmcSlaveAddress, seqNum, lun, cmd,
dataReceived);
std::shared_ptr<std::vector<uint8_t>> buffer =
std::make_shared<std::vector<uint8_t>>();
if (request->ipmbToi2cConstruct(*buffer) != 0)
{
return;
}
channel->ipmbSendI2cFrame(buffer);
};
static auto match = std::make_unique<sdbusplus::bus::match_t>(
static_cast<sdbusplus::bus_t&>(*conn),
"type='signal',member='sendBroadcast',", sendBroadcastHandler);
}
/**
* @brief Main
*/
int main()
{
conn->request_name(ipmbBus);
auto server = sdbusplus::asio::object_server(conn);
std::shared_ptr<sdbusplus::asio::dbus_interface> ipmbIface =
server.add_interface(ipmbObj, ipmbDbusIntf);
ipmbIface->register_method("sendRequest", std::move(ipmbHandleRequest));
ipmbIface->initialize();
if (initializeChannels() < 0)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Error initializeChannels");
return -1;
}
addUpdateSlaveAddrHandler();
addSendBroadcastHandler();
io.run();
return 0;
}