blob: e4c59fc1616555e39e98aaf4b2c6d0af81cc35ee [file] [log] [blame]
/*
* Copyright 2018 Google Inc.
*
* 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 "ipmi_handler.hpp"
#include "ipmi_errors.hpp"
#include <fcntl.h>
#include <linux/ipmi.h>
#include <linux/ipmi_msgdefs.h>
#include <sys/ioctl.h>
#include <array>
#include <cstdint>
#include <cstring>
#include <sstream>
#include <string>
#include <vector>
namespace ipmiblob
{
void IpmiHandler::open()
{
const int device = 0;
const std::vector<std::string> formats = {"/dev/ipmi", "/dev/ipmi/",
"/dev/ipmidev/"};
for (const auto& format : formats)
{
std::ostringstream path;
path << format << device;
fd = sys->open(path.str().c_str(), O_RDWR);
if (fd < 0)
{
continue;
}
break;
}
if (fd < 0)
{
throw IpmiException("Unable to open any ipmi devices");
}
}
std::vector<std::uint8_t>
IpmiHandler::sendPacket(std::vector<std::uint8_t>& data)
{
if (fd < 0)
{
open();
}
constexpr int ipmiOEMNetFn = 46;
constexpr int ipmiOEMLun = 0;
/* /openbmc/phosphor-host-ipmid/blob/master/host-ipmid/oemopenbmc.hpp */
constexpr int ipmiOEMBlobCmd = 128;
constexpr int fifteenMs = 15 * 1000;
constexpr int ipmiReadTimeout = fifteenMs;
constexpr int ipmiResponseBufferLen = IPMI_MAX_MSG_LENGTH;
constexpr int ipmiOk = 0;
/* We have a handle to the IPMI device. */
std::array<std::uint8_t, ipmiResponseBufferLen> responseBuffer;
/* Build address. */
struct ipmi_system_interface_addr systemAddress;
systemAddress.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
systemAddress.channel = IPMI_BMC_CHANNEL;
systemAddress.lun = ipmiOEMLun;
/* Build request. */
struct ipmi_req request;
std::memset(&request, 0, sizeof(request));
request.addr = reinterpret_cast<unsigned char*>(&systemAddress);
request.addr_len = sizeof(systemAddress);
request.msgid = sequence++;
request.msg.data = reinterpret_cast<unsigned char*>(data.data());
request.msg.data_len = data.size();
request.msg.netfn = ipmiOEMNetFn;
request.msg.cmd = ipmiOEMBlobCmd;
struct ipmi_recv reply;
reply.addr = reinterpret_cast<unsigned char*>(&systemAddress);
reply.addr_len = sizeof(systemAddress);
reply.msg.data = reinterpret_cast<unsigned char*>(responseBuffer.data());
reply.msg.data_len = responseBuffer.size();
/* Try to send request. */
int rc = sys->ioctl(fd, IPMICTL_SEND_COMMAND, &request);
if (rc < 0)
{
throw IpmiException("Unable to send IPMI request.");
}
/* Could use sdeventplus, but for only one type of event is it worth it? */
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLIN;
do
{
rc = sys->poll(&pfd, 1, ipmiReadTimeout);
if (rc < 0)
{
if (errno == EINTR)
{
continue;
}
throw IpmiException("Error occurred.");
}
else if (rc == 0)
{
throw IpmiException("Timeout waiting for reply.");
}
/* Yay, happy case! */
rc = sys->ioctl(fd, IPMICTL_RECEIVE_MSG_TRUNC, &reply);
if (rc < 0)
{
throw IpmiException("Unable to read reply.");
}
if (request.msgid != reply.msgid)
{
std::fprintf(stderr, "Received wrong message, trying again.\n");
}
} while (request.msgid != reply.msgid);
if (responseBuffer[0] != ipmiOk)
{
throw IpmiException(static_cast<int>(responseBuffer[0]));
}
std::vector<std::uint8_t> returning;
auto dataLen = reply.msg.data_len - 1;
returning.insert(returning.begin(), responseBuffer.begin() + 1,
responseBuffer.begin() + dataLen + 1);
for (const auto& byte : returning)
{
std::fprintf(stderr, "0x%02x ", byte);
}
std::fprintf(stderr, "\n");
return returning;
}
} // namespace ipmiblob