tools: implement method to send ipmi packet to BMC
Implement method to send ipmi packets to BMC. Only currently supports
the system interface.
Tested: Verified on a host that has the BLOB handler installed on the
BMC, that it received a blob count of 0, as there were no handlers
installed at that test. Further functional tests will verify cases with
the firmware handler installed, however, it is verified that this code
correctly speaks IPMI to the BLOB handler registered.
Change-Id: I696f3915930cc9fa5e768fbdeed484ea6cb707a2
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/tools/ipmi_handler.cpp b/tools/ipmi_handler.cpp
index e1e0529..9278338 100644
--- a/tools/ipmi_handler.cpp
+++ b/tools/ipmi_handler.cpp
@@ -16,8 +16,16 @@
#include "ipmi_handler.hpp"
-#include <fcntl.h>
+#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>
@@ -25,7 +33,7 @@
namespace host_tool
{
-bool IpmiHandler::open()
+void IpmiHandler::open()
{
const int device = 0;
const std::vector<std::string> formats = {"/dev/ipmi", "/dev/ipmi/",
@@ -44,16 +52,114 @@
break;
}
- /* Should we throw an exception here, since we exhausted these options?
- * Maybe. :D
- */
- return !(fd < 0);
+ if (fd < 0)
+ {
+ throw IpmiException("Unable to open any ipmi devices");
+ }
}
std::vector<std::uint8_t>
- IpmiHandler::sendPacket(const std::vector<std::uint8_t>& data)
+ IpmiHandler::sendPacket(std::vector<std::uint8_t>& data)
{
- return {};
+ 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 host_tool