Support IPMI Serial Transport
Support IPMI Serial interface (basic mode) based on Sec 14,
IPMI 2.0 spec. Serial transport tested with meta-evb-arm
platform with socat and ipmitool.
HW transport can be selected by meson configure flasg.
Tested:
1. socat -x pty,raw,echo=0,link=/tmp/ttyHOST2BMC tcp:localhost:5066
2. ipmitool -I serial-basic -D /tmp/ttyHOST2BMC:115200 mc info
Device ID : 0
Device Revision : 0
Firmware Revision : 2.17
IPMI Version : 2.0
Manufacturer ID : 0
Manufacturer Name : Unknown
Product ID : 0 (0x0000)
Product Name : Unknown (0x0)
Device Available : no
Provides Device SDRs : no
Additional Device Support :
Aux Firmware Rev Info :
0x00
0x00
0x00
0x00
Change-Id: I2a56390022fce7a974ed75ed6a72ad3fffed9bb6
Signed-off-by: John Chung <john.chung@arm.com>
diff --git a/transport/serialbridge/serialcmd.cpp b/transport/serialbridge/serialcmd.cpp
new file mode 100644
index 0000000..ec84f5c
--- /dev/null
+++ b/transport/serialbridge/serialcmd.cpp
@@ -0,0 +1,340 @@
+#include "serialcmd.hpp"
+
+#include <fmt/format.h>
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/slot.hpp>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/ops.hpp>
+
+#include <numeric>
+#include <ranges>
+#include <unordered_map>
+
+namespace serialbridge
+{
+
+/**
+ * @brief Table of special characters
+ */
+static const std::unordered_map<uint8_t, uint8_t> characters = {
+ {bmStart, 0xB0}, /* start */
+ {bmStop, 0xB5}, /* stop */
+ {bmHandshake, 0xB6}, /* packet handshake */
+ {bmEscape, 0xBA}, /* data escape */
+ {0x1B, 0x3B} /* escape */
+};
+
+/**
+ * @brief Calculate IPMI checksum
+ */
+uint8_t SerialChannel::calculateChecksum(std::span<uint8_t> data)
+{
+ uint8_t checksum;
+
+ checksum = std::accumulate(data.begin(), data.end(), 0);
+ checksum = (~checksum) + 1;
+
+ // return checksum
+ return checksum;
+}
+
+/**
+ * @brief Return unescaped character for the given one
+ */
+uint8_t SerialChannel::getUnescapedCharacter(uint8_t c)
+{
+ auto search =
+ std::find_if(characters.begin(), characters.end(),
+ [c](const auto& map_set) { return map_set.second == c; });
+
+ if (search == characters.end())
+ {
+ return c;
+ }
+
+ return search->first;
+}
+
+/**
+ * @brief Process IPMI Serial Request State Machine
+ */
+int SerialChannel::consumeIpmiSerialPacket(
+ std::span<uint8_t>& escapedDataBytes,
+ std::vector<uint8_t>& unescapedDataBytes)
+{
+ unescapedDataBytes.reserve(escapedDataBytes.size());
+
+ for (auto c : escapedDataBytes)
+ {
+ if (c == bmStart) // START
+ {
+ msgState = MsgState::msgInProgress;
+ }
+ else if (msgState == MsgState::msgIdle)
+ {
+ continue;
+ }
+ else if (msgState == MsgState::msgInEscape)
+ {
+ uint8_t unescapedCharacter;
+ unescapedCharacter = getUnescapedCharacter(c);
+
+ if (unescapedCharacter == c)
+ {
+ // error, then reset
+ msgState = MsgState::msgIdle;
+ unescapedDataBytes.clear();
+ continue;
+ }
+
+ unescapedDataBytes.push_back(unescapedCharacter);
+ msgState = MsgState::msgInProgress;
+ }
+ else if (c == bmEscape)
+ {
+ msgState = MsgState::msgInEscape;
+ continue;
+ }
+ else if (c == bmStop) // STOP
+ {
+ msgState = MsgState::msgIdle;
+ return true;
+ }
+ else if (c == bmHandshake) // Handshake
+ {
+ unescapedDataBytes.clear();
+ continue;
+ }
+ else if (msgState == MsgState::msgInProgress)
+ {
+ unescapedDataBytes.push_back(c);
+ }
+ }
+
+ return false;
+}
+
+/**
+ * @brief Encapsluate response to avoid escape character
+ */
+uint8_t SerialChannel::processEscapedCharacter(std::vector<uint8_t>& buffer,
+ const std::vector<uint8_t>& data)
+{
+ uint8_t checksum = 0;
+
+ std::ranges::for_each(data.begin(), data.end(),
+ [&buffer, &checksum](const auto& c) {
+ auto search = characters.find(c);
+ if (search != characters.end())
+ {
+ buffer.push_back(bmEscape);
+ buffer.push_back(search->second);
+ }
+ else
+ {
+ buffer.push_back(c);
+ }
+
+ checksum += c;
+ });
+
+ return checksum;
+}
+
+/**
+ * @brief Write function
+ */
+int SerialChannel::write(stdplus::Fd& uart, uint8_t rsAddr, uint8_t rqAddr,
+ uint8_t seq, sdbusplus::message_t&& m)
+{
+ std::span<uint8_t> out;
+ uint8_t checksum;
+
+ try
+ {
+ if (m.is_method_error())
+ {
+ // Extra copy to workaround lack of `const sd_bus_error` constructor
+ auto error = *m.get_error();
+ throw sdbusplus::exception::SdBusError(&error, "ipmid response");
+ }
+
+ uint8_t netFn = 0xff;
+ uint8_t lun = 0xff;
+ uint8_t cmd = 0xff;
+ uint8_t cc = 0xff;
+ std::vector<uint8_t> data;
+
+ m.read(std::tie(netFn, lun, cmd, cc, data));
+
+ uint8_t netFnLun = (netFn << netFnShift) | (lun & lunMask);
+ uint8_t seqLun = (seq << netFnShift) | (lun & lunMask);
+
+ std::vector<uint8_t> connectionHeader = {rqAddr, netFnLun};
+ std::vector<uint8_t> messageHeader = {rsAddr, seqLun, cmd, cc};
+
+ // Reserve the buffer size to avoid relloc and copy
+ responseBuffer.clear();
+ responseBuffer.reserve(
+ sizeof(struct IpmiSerialHeader) + 2 * data.size() +
+ 4); // 4 for bmStart & bmStop & 2 checksums
+
+ // bmStart
+ responseBuffer.push_back(bmStart);
+
+ // Assemble connection header and checksum
+ checksum = processEscapedCharacter(responseBuffer, connectionHeader);
+ responseBuffer.push_back(-checksum); // checksum1
+
+ // Assemble response message and checksum
+ checksum = processEscapedCharacter(responseBuffer, messageHeader);
+ checksum +=
+ processEscapedCharacter(responseBuffer, std::vector<uint8_t>(data));
+ responseBuffer.push_back(-checksum); // checksum2
+
+ // bmStop
+ responseBuffer.push_back(bmStop);
+
+ out = std::span<uint8_t>(responseBuffer.begin(), responseBuffer.end());
+
+ if (verbose)
+ {
+ lg2::info(
+ "Write serial request message with len={LEN}, netfn={NETFN}, "
+ "lun={LUN}, cmd={CMD}, seq={SEQ}",
+ "LEN", responseBuffer.size(), "NETFN", netFn, "LUN", lun, "CMD",
+ cmd, "SEQ", seq);
+
+ std::string msgData = "Tx: ";
+ for (auto c : responseBuffer)
+ {
+ msgData += std::format("{:#x} ", c);
+ }
+ lg2::info(msgData.c_str());
+ }
+
+ stdplus::fd::writeExact(uart, out);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("IPMI Response failure: {MSG}", "MSG", e);
+
+ return -1;
+ }
+
+ return out.size();
+}
+
+/**
+ * @brief Read function
+ */
+void SerialChannel::read(stdplus::Fd& uart, sdbusplus::bus_t& bus,
+ sdbusplus::slot_t& outstanding)
+{
+ std::array<uint8_t, ipmiSerialMaxBufferSize> buffer;
+ auto ipmiSerialPacket = stdplus::fd::read(uart, buffer);
+
+ if (ipmiSerialPacket.empty())
+ {
+ return;
+ }
+
+ if (outstanding)
+ {
+ lg2::error("Canceling outstanding request \n");
+ outstanding = sdbusplus::slot_t(nullptr);
+ }
+
+ // process ipmi serial packet
+ if (!consumeIpmiSerialPacket(ipmiSerialPacket, requestBuffer))
+ {
+ lg2::info("Wait for more data ... \n");
+ return;
+ }
+
+ // validate ipmi serial packet length
+ if (requestBuffer.size() <
+ (sizeof(struct IpmiSerialHeader) + ipmiSerialChecksumSize))
+ {
+ lg2::error("Invalid request length, ignoring \n");
+ requestBuffer.clear();
+ return;
+ }
+
+ // validate checksum1
+ if (calculateChecksum(std::span<uint8_t>(requestBuffer.begin(),
+ ipmiSerialConnectionHeaderLength)))
+ {
+ lg2::error("Invalid request checksum 1 \n");
+ requestBuffer.clear();
+ return;
+ }
+
+ // validate checksum2
+ if (calculateChecksum(std::span<uint8_t>(
+ &requestBuffer[ipmiSerialConnectionHeaderLength],
+ requestBuffer.size() - ipmiSerialConnectionHeaderLength)))
+ {
+ lg2::error("Invalid request checksum 2 \n");
+ requestBuffer.clear();
+ return;
+ }
+
+ auto m = bus.new_method_call("xyz.openbmc_project.Ipmi.Host",
+ "/xyz/openbmc_project/Ipmi",
+ "xyz.openbmc_project.Ipmi.Server", "execute");
+
+ std::map<std::string, std::variant<int>> options;
+ struct IpmiSerialHeader* header =
+ reinterpret_cast<struct IpmiSerialHeader*>(requestBuffer.data());
+
+ uint8_t rsAddr = header->rsAddr;
+ uint8_t netFn = header->rsNetFnLUN >> netFnShift;
+ uint8_t lun = header->rsNetFnLUN & lunMask;
+ uint8_t rqAddr = header->rqAddr;
+ uint8_t seq = header->rqSeqLUN >> netFnShift;
+ uint8_t cmd = header->cmd;
+
+ std::span reqSpan{requestBuffer.begin(),
+ requestBuffer.end() -
+ ipmiSerialChecksumSize}; // remove checksum 2
+ m.append(netFn, lun, cmd, reqSpan.subspan(sizeof(IpmiSerialHeader)),
+ options);
+
+ if (verbose)
+ {
+ lg2::info("Read serial request message with len={LEN}, netFn={NETFN}, "
+ "lun={LUN}, cmd={CMD}, seq={SEQ}",
+ "LEN", requestBuffer.size(), "NETFN", netFn, "LUN", lun,
+ "CMD", cmd, "SEQ", seq);
+
+ std::string msgData = "Rx: ";
+ for (auto c : requestBuffer)
+ {
+ msgData += std::format("{:#x} ", c);
+ }
+ lg2::info(msgData.c_str());
+ }
+
+ outstanding = m.call_async(stdplus::exception::ignore(
+ [&outstanding, this, &uart, _rsAddr{rsAddr}, _rqAddr{rqAddr},
+ _seq{seq}](sdbusplus::message_t&& m) {
+ outstanding = sdbusplus::slot_t(nullptr);
+
+ if (write(uart, _rsAddr, _rqAddr, _seq, std::move(m)) < 0)
+ {
+ lg2::error(
+ "Occur an error while attempting to send the response.");
+ }
+ }));
+
+ requestBuffer.clear();
+
+ return;
+}
+
+} // namespace serialbridge