diff options
author | Patrick Venture <venture@google.com> | 2019-03-05 14:01:00 -0800 |
---|---|---|
committer | Patrick Venture <venture@google.com> | 2019-03-06 07:51:32 -0800 |
commit | 123b5c0910e000cf9b00a37146aae99a835f3063 (patch) | |
tree | e1aacf85711d9c5f7e8ad87a1c8e671e09f61db8 /src | |
parent | 85e320906546f3e5a0dfe1ab54a826517dae2a0d (diff) | |
download | ipmi-blob-tool-123b5c0910e000cf9b00a37146aae99a835f3063.tar.gz ipmi-blob-tool-123b5c0910e000cf9b00a37146aae99a835f3063.zip |
initial commit
Add initial code from phosphor-ipmi-flash/tools that was not specific to
firmware update over ipmi-blobs.
Change-Id: I360537a7392347fe989397a699f6a712bc36e62c
Signed-off-by: Patrick Venture <venture@google.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 21 | ||||
-rw-r--r-- | src/ipmiblob.pc.in | 10 | ||||
-rw-r--r-- | src/ipmiblob/blob_errors.hpp | 23 | ||||
-rw-r--r-- | src/ipmiblob/blob_handler.cpp | 315 | ||||
-rw-r--r-- | src/ipmiblob/blob_handler.hpp | 108 | ||||
-rw-r--r-- | src/ipmiblob/blob_interface.hpp | 91 | ||||
-rw-r--r-- | src/ipmiblob/crc.cpp | 44 | ||||
-rw-r--r-- | src/ipmiblob/crc.hpp | 20 | ||||
-rw-r--r-- | src/ipmiblob/internal/sys.cpp | 70 | ||||
-rw-r--r-- | src/ipmiblob/internal/sys.hpp | 61 | ||||
-rw-r--r-- | src/ipmiblob/ipmi_errors.hpp | 47 | ||||
-rw-r--r-- | src/ipmiblob/ipmi_handler.cpp | 165 | ||||
-rw-r--r-- | src/ipmiblob/ipmi_handler.hpp | 46 | ||||
-rw-r--r-- | src/ipmiblob/ipmi_interface.hpp | 25 | ||||
-rw-r--r-- | src/ipmiblob/test/ipmi_interface_mock.hpp | 18 |
15 files changed, 1064 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..38a8564 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,21 @@ +nobase_include_HEADERS = +pkgconfig_DATA = ipmiblob.pc +lib_LTLIBRARIES = libipmiblob.la +libipmiblob_la_SOURCES = +libipmiblob_la_LIBADD = $(COMMON_LIBS) + +# Don't install the crc header. +libipmiblob_la_SOURCES += ipmiblob/crc.cpp + +nobase_include_HEADERS += ipmiblob/blob_interface.hpp +nobase_include_HEADERS += ipmiblob/blob_handler.hpp +libipmiblob_la_SOURCES += ipmiblob/blob_handler.cpp + +nobase_include_HEADERS += ipmiblob/ipmi_interface.hpp +nobase_include_HEADERS += ipmiblob/ipmi_handler.hpp +libipmiblob_la_SOURCES += ipmiblob/ipmi_handler.cpp + +nobase_include_HEADERS += ipmiblob/internal/sys.hpp +libipmiblob_la_SOURCES += ipmiblob/internal/sys.cpp + +nobase_include_HEADERS += ipmiblob/test/ipmi_interface_mock.hpp diff --git a/src/ipmiblob.pc.in b/src/ipmiblob.pc.in new file mode 100644 index 0000000..2b8dd0f --- /dev/null +++ b/src/ipmiblob.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: ipmiblob +Description: C++ library for talking to BLOB handlers over IPMI +Version: @VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -lipmiblob diff --git a/src/ipmiblob/blob_errors.hpp b/src/ipmiblob/blob_errors.hpp new file mode 100644 index 0000000..45f0e46 --- /dev/null +++ b/src/ipmiblob/blob_errors.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include <exception> +#include <string> + +namespace host_tool +{ + +class BlobException : public std::exception +{ + public: + explicit BlobException(const std::string& message) : message(message){}; + + virtual const char* what() const noexcept override + { + return message.c_str(); + } + + private: + std::string message; +}; + +} // namespace host_tool diff --git a/src/ipmiblob/blob_handler.cpp b/src/ipmiblob/blob_handler.cpp new file mode 100644 index 0000000..5be0b2d --- /dev/null +++ b/src/ipmiblob/blob_handler.cpp @@ -0,0 +1,315 @@ +/* + * 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 "blob_handler.hpp" + +#include "blob_errors.hpp" +#include "crc.hpp" +#include "ipmi_errors.hpp" + +#include <array> +#include <cstring> + +namespace host_tool +{ + +namespace +{ +const std::array<std::uint8_t, 3> ipmiPhosphorOen = {0xcf, 0xc2, 0x00}; +} + +std::vector<std::uint8_t> + BlobHandler::sendIpmiPayload(BlobOEMCommands command, + const std::vector<std::uint8_t>& payload) +{ + std::vector<std::uint8_t> request, reply, bytes; + + std::copy(ipmiPhosphorOen.begin(), ipmiPhosphorOen.end(), + std::back_inserter(request)); + request.push_back(command); + + if (payload.size() > 0) + { + /* Grow the vector to hold the bytes. */ + request.reserve(request.size() + sizeof(std::uint16_t)); + + /* CRC required. */ + std::uint16_t crc = generateCrc(payload); + auto src = reinterpret_cast<const std::uint8_t*>(&crc); + + std::copy(src, src + sizeof(crc), std::back_inserter(request)); + + /* Copy the payload. */ + std::copy(payload.begin(), payload.end(), std::back_inserter(request)); + } + + try + { + reply = ipmi->sendPacket(request); + } + catch (const IpmiException& e) + { + throw BlobException(e.what()); + } + + /* IPMI_CC was OK, and it returned no bytes, so let's be happy with that for + * now. + */ + if (reply.size() == 0) + { + return reply; + } + + /* This cannot be a response because it's smaller than the smallest + * response. + */ + if (reply.size() < ipmiPhosphorOen.size()) + { + throw BlobException("Invalid response length"); + } + + /* Verify the OEN. */ + if (std::memcmp(ipmiPhosphorOen.data(), reply.data(), + ipmiPhosphorOen.size()) != 0) + { + throw BlobException("Invalid OEN received"); + } + + /* In this case there was no data, as there was no CRC. */ + std::size_t headerSize = ipmiPhosphorOen.size() + sizeof(std::uint16_t); + if (reply.size() < headerSize) + { + return {}; + } + + /* Validate CRC. */ + std::uint16_t crc; + auto ptr = reinterpret_cast<std::uint8_t*>(&crc); + std::memcpy(ptr, &reply[ipmiPhosphorOen.size()], sizeof(crc)); + + for (const auto& byte : reply) + { + std::fprintf(stderr, "0x%02x ", byte); + } + std::fprintf(stderr, "\n"); + + bytes.insert(bytes.begin(), reply.begin() + headerSize, reply.end()); + + auto computed = generateCrc(bytes); + if (crc != computed) + { + std::fprintf(stderr, "Invalid CRC, received: 0x%x, computed: 0x%x\n", + crc, computed); + throw BlobException("Invalid CRC on received data."); + } + + return bytes; +} + +int BlobHandler::getBlobCount() +{ + std::uint32_t count; + try + { + auto resp = sendIpmiPayload(BlobOEMCommands::bmcBlobGetCount, {}); + if (resp.size() != sizeof(count)) + { + return 0; + } + + /* LE to LE (need to make this portable as some point. */ + std::memcpy(&count, resp.data(), sizeof(count)); + } + catch (const BlobException& b) + { + return 0; + } + + std::fprintf(stderr, "BLOB Count: %d\n", count); + return count; +} + +std::string BlobHandler::enumerateBlob(std::uint32_t index) +{ + std::vector<std::uint8_t> payload; + auto data = reinterpret_cast<const std::uint8_t*>(&index); + + std::copy(data, data + sizeof(std::uint32_t), std::back_inserter(payload)); + + try + { + auto resp = sendIpmiPayload(BlobOEMCommands::bmcBlobEnumerate, payload); + return (resp.size() > 0) ? std::string(&resp[0], &resp[resp.size() - 1]) + : ""; + } + catch (const BlobException& b) + { + return ""; + } +} + +void BlobHandler::writeGeneric(BlobOEMCommands command, std::uint16_t session, + std::uint32_t offset, + const std::vector<std::uint8_t>& bytes) +{ + std::vector<std::uint8_t> payload; + + payload.reserve(sizeof(std::uint16_t) + sizeof(std::uint32_t) + + bytes.size()); + + auto data = reinterpret_cast<const std::uint8_t*>(&session); + std::copy(data, data + sizeof(std::uint16_t), std::back_inserter(payload)); + + data = reinterpret_cast<const std::uint8_t*>(&offset); + std::copy(data, data + sizeof(std::uint32_t), std::back_inserter(payload)); + + std::copy(bytes.begin(), bytes.end(), std::back_inserter(payload)); + + auto resp = sendIpmiPayload(command, payload); +} + +void BlobHandler::writeMeta(std::uint16_t session, std::uint32_t offset, + const std::vector<std::uint8_t>& bytes) +{ + return writeGeneric(BlobOEMCommands::bmcBlobWriteMeta, session, offset, + bytes); +} + +void BlobHandler::writeBytes(std::uint16_t session, std::uint32_t offset, + const std::vector<std::uint8_t>& bytes) +{ + return writeGeneric(BlobOEMCommands::bmcBlobWrite, session, offset, bytes); +} + +std::vector<std::string> BlobHandler::getBlobList() +{ + std::vector<std::string> list; + int blobCount = getBlobCount(); + + for (int i = 0; i < blobCount; i++) + { + auto name = enumerateBlob(i); + /* Currently ignore failures. */ + if (!name.empty()) + { + list.push_back(name); + } + } + + return list; +} + +StatResponse BlobHandler::getStat(const std::string& id) +{ + StatResponse meta; + std::vector<std::uint8_t> name, resp; + + std::copy(id.begin(), id.end(), std::back_inserter(name)); + name.push_back(0x00); /* need to add nul-terminator. */ + + try + { + resp = sendIpmiPayload(BlobOEMCommands::bmcBlobStat, name); + } + catch (const BlobException& b) + { + throw; + } + + std::memcpy(&meta.blob_state, &resp[0], sizeof(meta.blob_state)); + std::memcpy(&meta.size, &resp[sizeof(meta.blob_state)], sizeof(meta.size)); + int offset = sizeof(meta.blob_state) + sizeof(meta.size); + std::uint8_t len = resp[offset]; + if (len > 0) + { + std::copy(&resp[offset + 1], &resp[resp.size()], + std::back_inserter(meta.metadata)); + } + + return meta; +} + +std::uint16_t BlobHandler::openBlob(const std::string& id, + std::uint16_t handlerFlags) +{ + std::uint16_t session; + std::vector<std::uint8_t> request, resp; + auto addrFlags = reinterpret_cast<const std::uint8_t*>(&handlerFlags); + + std::copy(addrFlags, addrFlags + sizeof(handlerFlags), + std::back_inserter(request)); + std::copy(id.begin(), id.end(), std::back_inserter(request)); + request.push_back(0x00); /* need to add nul-terminator. */ + + try + { + resp = sendIpmiPayload(BlobOEMCommands::bmcBlobOpen, request); + } + catch (const BlobException& b) + { + throw; + } + + if (resp.size() != sizeof(session)) + { + throw BlobException("Did not receive session."); + } + + std::memcpy(&session, resp.data(), sizeof(session)); + return session; +} + +void BlobHandler::closeBlob(std::uint16_t session) +{ + std::vector<std::uint8_t> request; + auto addrSession = reinterpret_cast<const std::uint8_t*>(&session); + std::copy(addrSession, addrSession + sizeof(session), + std::back_inserter(request)); + + try + { + sendIpmiPayload(BlobOEMCommands::bmcBlobClose, request); + } + catch (const BlobException& b) + { + std::fprintf(stderr, "Received failure on close: %s\n", b.what()); + } + + return; +} + +std::vector<std::uint8_t> BlobHandler::readBytes(std::uint16_t session, + std::uint32_t offset, + std::uint32_t length) +{ + std::vector<std::uint8_t> payload; + + payload.reserve(sizeof(std::uint16_t) + sizeof(std::uint32_t) + + sizeof(std::uint32_t)); + + auto data = reinterpret_cast<const std::uint8_t*>(&session); + std::copy(data, data + sizeof(std::uint16_t), std::back_inserter(payload)); + + data = reinterpret_cast<const std::uint8_t*>(&offset); + std::copy(data, data + sizeof(std::uint32_t), std::back_inserter(payload)); + + data = reinterpret_cast<const std::uint8_t*>(&length); + std::copy(data, data + sizeof(std::uint32_t), std::back_inserter(payload)); + + return sendIpmiPayload(BlobOEMCommands::bmcBlobRead, payload); +} + +} // namespace host_tool diff --git a/src/ipmiblob/blob_handler.hpp b/src/ipmiblob/blob_handler.hpp new file mode 100644 index 0000000..cbac9d4 --- /dev/null +++ b/src/ipmiblob/blob_handler.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include "blob_interface.hpp" +#include "ipmi_interface.hpp" + +namespace host_tool +{ + +class BlobHandler : public BlobInterface +{ + public: + enum BlobOEMCommands + { + bmcBlobGetCount = 0, + bmcBlobEnumerate = 1, + bmcBlobOpen = 2, + bmcBlobRead = 3, + bmcBlobWrite = 4, + bmcBlobCommit = 5, + bmcBlobClose = 6, + bmcBlobDelete = 7, + bmcBlobStat = 8, + bmcBlobSessionStat = 9, + bmcBlobWriteMeta = 10, + }; + + explicit BlobHandler(IpmiInterface* ipmi) : ipmi(ipmi){}; + + /** + * Retrieve the blob count. + * + * @return the number of blob_ids found (0 on failure). + */ + int getBlobCount(); + + /** + * Given an index into the list of blobs, return the name. + * + * @param[in] index - the index into the list of blob ids. + * @return the name as a string or empty on failure. + */ + std::string enumerateBlob(std::uint32_t index); + + /** + * @throws BlobException. + */ + void writeMeta(std::uint16_t session, std::uint32_t offset, + const std::vector<std::uint8_t>& bytes) override; + + /** + * @throw BlobException. + */ + void writeBytes(std::uint16_t session, std::uint32_t offset, + const std::vector<std::uint8_t>& bytes) override; + + std::vector<std::string> getBlobList() override; + + /** + * @throws BlobException. + */ + StatResponse getStat(const std::string& id) override; + + /** + * @throws BlobException. + */ + std::uint16_t openBlob(const std::string& id, + std::uint16_t handlerFlags) override; + + void closeBlob(std::uint16_t session) override; + + /** + * @throws BlobException. + */ + std::vector<std::uint8_t> readBytes(std::uint16_t session, + std::uint32_t offset, + std::uint32_t length) override; + + private: + /** + * Send the contents of the payload to IPMI, this method handles wrapping + * with the OEN, subcommand and CRC. + * + * @param[in] command - the blob command. + * @param[in] payload - the payload bytes. + * @return the bytes returned from the ipmi interface. + * @throws BlobException. + */ + std::vector<std::uint8_t> + sendIpmiPayload(BlobOEMCommands command, + const std::vector<std::uint8_t>& payload); + + /** + * Generic blob byte writer. + * + * @param[in] command - the command associated with this write. + * @param[in] session - the session id. + * @param[in] offset - the offset for the metadata to write. + * @param[in] bytes - the bytes to send. + * @throws BlobException on failure. + */ + void writeGeneric(BlobOEMCommands command, std::uint16_t session, + std::uint32_t offset, + const std::vector<std::uint8_t>& bytes); + + IpmiInterface* ipmi; +}; + +} // namespace host_tool diff --git a/src/ipmiblob/blob_interface.hpp b/src/ipmiblob/blob_interface.hpp new file mode 100644 index 0000000..f85be59 --- /dev/null +++ b/src/ipmiblob/blob_interface.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include <cstdint> +#include <string> +#include <vector> + +namespace host_tool +{ + +struct StatResponse +{ + std::uint16_t blob_state; + std::uint32_t size; + std::vector<std::uint8_t> metadata; +}; + +class BlobInterface +{ + public: + virtual ~BlobInterface() = default; + + /** + * Write metadata to a blob. + * + * @param[in] session - the session id. + * @param[in] offset - the offset for the metadata to write. + * @param[in] bytes - the bytes to send. + * @throws BlobException on failure. + */ + virtual void writeMeta(std::uint16_t session, std::uint32_t offset, + const std::vector<std::uint8_t>& bytes) = 0; + + /** + * Write bytes to a blob. + * + * @param[in] session - the session id. + * @param[in] offset - the offset to which to write the bytes. + * @param[in] bytes - the bytes to send. + * @throws BlobException on failure. + */ + virtual void writeBytes(std::uint16_t session, std::uint32_t offset, + const std::vector<std::uint8_t>& bytes) = 0; + + /** + * Get a list of the blob_ids provided by the BMC. + * + * @return list of strings, each representing a blob_id returned. + */ + virtual std::vector<std::string> getBlobList() = 0; + + /** + * Get the stat() on the blob_id. + * + * @param[in] id - the blob_id. + * @return metadata structure. + */ + virtual StatResponse getStat(const std::string& id) = 0; + + /** + * Attempt to open the file using the specific data interface flag. + * + * @param[in] blob - the blob_id to open. + * @param[in] handlerFlags - the data interface flag, if relevant. + * @return the session id on success. + * @throws BlobException on failure. + */ + virtual std::uint16_t openBlob(const std::string& id, + std::uint16_t handlerFlags) = 0; + + /** + * Attempt to close the open session. + * + * @param[in] session - the session to close. + */ + virtual void closeBlob(std::uint16_t session) = 0; + + /** + * Read bytes from a blob. + * + * @param[in] session - the session id. + * @param[in] offset - the offset to which to write the bytes. + * @param[in] length - the number of bytes to read. + * @return the bytes read + * @throws BlobException on failure. + */ + virtual std::vector<std::uint8_t> readBytes(std::uint16_t session, + std::uint32_t offset, + std::uint32_t length) = 0; +}; + +} // namespace host_tool diff --git a/src/ipmiblob/crc.cpp b/src/ipmiblob/crc.cpp new file mode 100644 index 0000000..d6f59ef --- /dev/null +++ b/src/ipmiblob/crc.cpp @@ -0,0 +1,44 @@ +#include "crc.hpp" + +namespace host_tool +{ + +/* + * This implementation tracks the specification given at + * http://srecord.sourceforge.net/crc16-ccitt.html + * Code copied from internal portable sources. + */ +std::uint16_t generateCrc(const std::vector<std::uint8_t>& data) +{ + const std::uint16_t kPoly = 0x1021; + const std::uint16_t kLeftBit = 0x8000; + const int kExtraRounds = 2; + const std::uint8_t* bytes = data.data(); + std::uint16_t crc = 0xFFFF; + std::size_t i; + std::size_t j; + std::size_t size = data.size(); + + for (i = 0; i < size + kExtraRounds; ++i) + { + for (j = 0; j < 8; ++j) + { + bool xor_flag = (crc & kLeftBit) ? 1 : 0; + crc <<= 1; + // If this isn't an extra round and the current byte's j'th bit from + // the left is set, increment the CRC. + if (i < size && (bytes[i] & (1 << (7 - j)))) + { + crc++; + } + if (xor_flag) + { + crc ^= kPoly; + } + } + } + + return crc; +} + +} // namespace host_tool diff --git a/src/ipmiblob/crc.hpp b/src/ipmiblob/crc.hpp new file mode 100644 index 0000000..c335ed2 --- /dev/null +++ b/src/ipmiblob/crc.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include <cstdint> +#include <vector> + +namespace host_tool +{ + +/** + * Generate the CRC for a payload (really any bytes). + * + * This is meant to only be called on the payload and not the CRC or the OEM + * header, etc. + * + * @param[in] data - the bytes against to run the CRC + * @return the CRC value + */ +std::uint16_t generateCrc(const std::vector<std::uint8_t>& data); + +} // namespace host_tool diff --git a/src/ipmiblob/internal/sys.cpp b/src/ipmiblob/internal/sys.cpp new file mode 100644 index 0000000..46c6642 --- /dev/null +++ b/src/ipmiblob/internal/sys.cpp @@ -0,0 +1,70 @@ +/* + * 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 "sys.hpp" + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <unistd.h> + +namespace internal +{ + +int SysImpl::open(const char* pathname, int flags) const +{ + return ::open(pathname, flags); +} + +int SysImpl::read(int fd, void* buf, std::size_t count) const +{ + return static_cast<int>(::read(fd, buf, count)); +} + +int SysImpl::close(int fd) const +{ + return ::close(fd); +} + +void* SysImpl::mmap(void* addr, std::size_t length, int prot, int flags, int fd, + off_t offset) const +{ + return ::mmap(addr, length, prot, flags, fd, offset); +} + +int SysImpl::munmap(void* addr, std::size_t length) const +{ + return ::munmap(addr, length); +} + +int SysImpl::getpagesize() const +{ + return ::getpagesize(); +} + +int SysImpl::ioctl(int fd, unsigned long request, void* param) const +{ + return ::ioctl(fd, request, param); +} + +int SysImpl::poll(struct pollfd* fds, nfds_t nfds, int timeout) const +{ + return ::poll(fds, nfds, timeout); +} + +SysImpl sys_impl; + +} // namespace internal diff --git a/src/ipmiblob/internal/sys.hpp b/src/ipmiblob/internal/sys.hpp new file mode 100644 index 0000000..2975b8c --- /dev/null +++ b/src/ipmiblob/internal/sys.hpp @@ -0,0 +1,61 @@ +#pragma once + +/* NOTE: IIRC, wak@ is working on exposing some of this in stdplus, so we can + * transition when that's ready. + * + * Copied some from gpioplus to enable unit-testing of lpc nuvoton and later + * other pieces. + */ + +#include <poll.h> +#include <sys/mman.h> + +#include <cinttypes> +#include <cstddef> + +namespace internal +{ + +/** + * @class Sys + * @brief Overridable direct syscall interface + */ +class Sys +{ + public: + virtual ~Sys() = default; + + virtual int open(const char* pathname, int flags) const = 0; + virtual int read(int fd, void* buf, std::size_t count) const = 0; + virtual int close(int fd) const = 0; + virtual void* mmap(void* addr, std::size_t length, int prot, int flags, + int fd, off_t offset) const = 0; + virtual int munmap(void* addr, std::size_t length) const = 0; + virtual int getpagesize() const = 0; + virtual int ioctl(int fd, unsigned long request, void* param) const = 0; + virtual int poll(struct pollfd* fds, nfds_t nfds, int timeout) const = 0; +}; + +/** + * @class SysImpl + * @brief syscall concrete implementation + * @details Passes through all calls to the normal linux syscalls + */ +class SysImpl : public Sys +{ + public: + int open(const char* pathname, int flags) const override; + int read(int fd, void* buf, std::size_t count) const override; + int close(int fd) const override; + void* mmap(void* addr, std::size_t length, int prot, int flags, int fd, + off_t offset) const override; + int munmap(void* addr, std::size_t length) const override; + int getpagesize() const override; + int ioctl(int fd, unsigned long request, void* param) const override; + int poll(struct pollfd* fds, nfds_t nfds, int timeout) const override; +}; + +/** @brief Default instantiation of sys */ +extern SysImpl sys_impl; + +} // namespace internal diff --git a/src/ipmiblob/ipmi_errors.hpp b/src/ipmiblob/ipmi_errors.hpp new file mode 100644 index 0000000..9f1a9f9 --- /dev/null +++ b/src/ipmiblob/ipmi_errors.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include <exception> +#include <map> +#include <sstream> +#include <string> + +namespace host_tool +{ + +class IpmiException : public std::exception +{ + public: + const std::map<int, std::string> commonFailures = { + {0xc0, "busy"}, + {0xc1, "invalid"}, + {0xc3, "timeout"}, + }; + + explicit IpmiException(int cc) + { + std::ostringstream smessage; + + auto search = commonFailures.find(cc); + if (search != commonFailures.end()) + { + smessage << "Received IPMI_CC: " << search->second; + } + else + { + smessage << "Received IPMI_CC: " << cc; + } + + message = smessage.str(); + } + explicit IpmiException(const std::string& message) : message(message){}; + + virtual const char* what() const noexcept override + { + return message.c_str(); + } + + private: + std::string message; +}; + +} // namespace host_tool diff --git a/src/ipmiblob/ipmi_handler.cpp b/src/ipmiblob/ipmi_handler.cpp new file mode 100644 index 0000000..9278338 --- /dev/null +++ b/src/ipmiblob/ipmi_handler.cpp @@ -0,0 +1,165 @@ +/* + * 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 host_tool +{ + +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 host_tool diff --git a/src/ipmiblob/ipmi_handler.hpp b/src/ipmiblob/ipmi_handler.hpp new file mode 100644 index 0000000..1c91bff --- /dev/null +++ b/src/ipmiblob/ipmi_handler.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "internal/sys.hpp" +#include "ipmi_interface.hpp" + +#include <vector> + +namespace host_tool +{ + +class IpmiHandler : public IpmiInterface +{ + public: + explicit IpmiHandler(const internal::Sys* sys = &internal::sys_impl) : + sys(sys){}; + + ~IpmiHandler() = default; + IpmiHandler(const IpmiHandler&) = delete; + IpmiHandler& operator=(const IpmiHandler&) = delete; + IpmiHandler(IpmiHandler&&) = default; + IpmiHandler& operator=(IpmiHandler&&) = default; + + /** + * Attempt to open the device node. + * + * @throws IpmiException on failure. + */ + void open(); + + /** + * @throws IpmiException on failure. + */ + std::vector<std::uint8_t> + sendPacket(std::vector<std::uint8_t>& data) override; + + private: + const internal::Sys* sys; + /** TODO: Use a smart file descriptor when it's ready. Until then only + * allow moving this object. + */ + int fd = -1; + /* The last IPMI sequence number we used. */ + int sequence = 0; +}; + +} // namespace host_tool diff --git a/src/ipmiblob/ipmi_interface.hpp b/src/ipmiblob/ipmi_interface.hpp new file mode 100644 index 0000000..6bad7db --- /dev/null +++ b/src/ipmiblob/ipmi_interface.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include <cstdint> +#include <vector> + +namespace host_tool +{ + +class IpmiInterface +{ + public: + virtual ~IpmiInterface() = default; + + /** + * Send an IPMI packet to the BMC. + * + * @param[in] data - a vector of the IPMI packet contents. + * @return the bytes returned. + * @throws IpmiException on failure. + */ + virtual std::vector<std::uint8_t> + sendPacket(std::vector<std::uint8_t>& data) = 0; +}; + +} // namespace host_tool diff --git a/src/ipmiblob/test/ipmi_interface_mock.hpp b/src/ipmiblob/test/ipmi_interface_mock.hpp new file mode 100644 index 0000000..c3e187e --- /dev/null +++ b/src/ipmiblob/test/ipmi_interface_mock.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include <ipmiblob/ipmi_interface.hpp> + +#include <gmock/gmock.h> + +namespace host_tool +{ + +class IpmiInterfaceMock : public IpmiInterface +{ + public: + virtual ~IpmiInterfaceMock() = default; + MOCK_METHOD1(sendPacket, + std::vector<std::uint8_t>(std::vector<std::uint8_t>&)); +}; + +} // namespace host_tool |