initial commit of source materials
Initial commit of source materials packaged into separate repository.
Change-Id: I94f416202b1f45813d3f3984c3b8dc81410b59ff
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..ea71ad6
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,99 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: false
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: true
+ AfterStruct: true
+ AfterUnion: true
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+PointerAlignment: Left
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
+IncludeBlocks: Regroup
+IncludeCategories:
+ - Regex: '^[<"](gtest|gmock)'
+ Priority: 5
+ - Regex: '^"config.h"'
+ Priority: -1
+ - Regex: '^".*\.hpp"'
+ Priority: 1
+ - Regex: '^<.*\.h>'
+ Priority: 2
+ - Regex: '^<.*'
+ Priority: 3
+ - Regex: '.*'
+ Priority: 4
+IndentCaseLabels: true
+IndentWidth: 4
+IndentWrappedFunctionNames: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 4
+UseTab: Never
+...
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dbd5f0a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,64 @@
+# Template from:
+# https://github.com/github/gitignore/blob/master/Autotools.gitignore
+
+# http://www.gnu.org/software/automake
+
+Makefile.in
+/ar-lib
+/mdate-sh
+/py-compile
+/test-driver
+/ylwrap
+
+# http://www.gnu.org/software/autoconf
+
+/autom4te.cache
+/autoscan.log
+/autoscan-*.log
+/aclocal.m4
+/compile
+/config.guess
+/config.h.in
+/config.sub
+/configure
+/configure.scan
+/depcomp
+/install-sh
+/missing
+/stamp-h1
+
+# https://www.gnu.org/software/libtool/
+
+/ltmain.sh
+
+# http://www.gnu.org/software/texinfo
+
+/texinfo.tex
+
+# Repo Specific Items
+*.o
+/config.h
+/config.h.in~
+/config.log
+/config.status
+Makefile
+.deps
+.dirstamp
+/lib*
+.libs/
+/*-libtool
+/ipmid
+.project
+/test/*_unittest
+/test/*.log
+/test/*.trs
+
+# ignore vim swap files
+.*.sw*
+# failures from patch
+*.orig
+*.rej
+# backup files from some editors
+*~
+.cscope/
+build/
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..b918af2
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1,45 @@
+How to use this list:
+ Find the most specific section entry (described below) that matches where
+ your change lives and add the reviewers (R) and maintainers (M) as
+ reviewers. You can use the same method to track down who knows a particular
+ code base best.
+
+ Your change/query may span multiple entries; that is okay.
+
+ If you do not find an entry that describes your request at all, someone
+ forgot to update this list; please at least file an issue or send an email
+ to a maintainer, but preferably you should just update this document.
+
+Description of section entries:
+
+ Section entries are structured according to the following scheme:
+
+ X: NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>
+ X: ...
+ .
+ .
+ .
+
+ Where REPO_NAME is the name of the repository within the OpenBMC GitHub
+ organization; FILE_PATH is a file path within the repository, possibly with
+ wildcards; X is a tag of one of the following types:
+
+ M: Denotes maintainer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+ if omitted from an entry, assume one of the maintainers from the
+ MAINTAINERS entry.
+ R: Denotes reviewer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+ these people are to be added as reviewers for a change matching the repo
+ path.
+ F: Denotes forked from an external repository; has fields URL.
+
+ Line comments are to be denoted "# SOME COMMENT" (typical shell style
+ comment); it is important to follow the correct syntax and semantics as we
+ may want to use automated tools with this file in the future.
+
+ A change cannot be added to an OpenBMC repository without a MAINTAINER's
+ approval; thus, a MAINTAINER should always be listed as a reviewer.
+
+START OF MAINTAINERS LIST
+-------------------------
+
+M: Patrick Venture <venture@google.com> <venture!>
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..3cd6fff
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,10 @@
+AM_DEFAULT_SOURCE_EXT = .cpp
+
+libi2ccmdsdir = ${libdir}/ipmid-providers
+libi2ccmds_LTLIBRARIES = libi2ccmds.la
+libi2ccmds_la_SOURCES = i2c.cpp
+
+libi2ccmds_la_LDFLAGS = \
+ -version-info 0:0:0 -shared
+
+libi2ccmds_la_CXXFLAGS =
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 0000000..50b75b7
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+AUTOCONF_FILES="Makefile.in aclocal.m4 ar-lib autom4te.cache compile \
+ config.guess config.h.in config.sub configure depcomp install-sh \
+ ltmain.sh missing *libtool test-driver"
+
+case $1 in
+ clean)
+ test -f Makefile && make maintainer-clean
+ for file in ${AUTOCONF_FILES}; do
+ find -name "$file" | xargs -r rm -rf
+ done
+ exit 0
+ ;;
+esac
+
+autoreconf -i
+echo 'Run "./configure ${CONFIGURE_FLAGS} && make"'
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..b239894
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,27 @@
+# Initialization
+AC_PREREQ([2.69])
+AC_INIT([i2c-ipmi], [1.0], [https://www.github.com/google-ipmi-i2c/issues])
+AC_LANG([C++])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign dist-xz])
+AM_SILENT_RULES([yes])
+
+# Checks for programs.
+AC_PROG_CXX
+AM_PROG_AR
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+
+# Checks for typedefs, structures, and compiler characteristics.
+AX_CXX_COMPILE_STDCXX_17([noext])
+AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
+
+# Checks for libraries.
+AC_CHECK_HEADER([host-ipmid], [AC_MSG_ERROR(["phosphor-host-ipmid required and not found."])])
+
+# Checks for library functions.
+LT_INIT # Required for systemd linking
+
+# Create configured output
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/i2c.cpp b/i2c.cpp
new file mode 100644
index 0000000..0a33b65
--- /dev/null
+++ b/i2c.cpp
@@ -0,0 +1,323 @@
+#include "i2c.hpp"
+
+#include <fcntl.h>
+#include <linux/i2c-dev.h>
+#include <linux/i2c.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <array>
+#include <cerrno>
+#include <cstring>
+#include <host-ipmid/iana.hpp>
+#include <host-ipmid/oemopenbmc.hpp>
+#include <memory>
+
+namespace oem
+{
+namespace i2c
+{
+
+// Instance object.
+std::unique_ptr<I2c> globalOemI2c;
+
+// Block read (I2C_M_RECV_LEN) reads count byte, then that many bytes,
+// then possibly a checksum byte. The specified byte count limit,
+// I2C_SMBUS_BLOCK_MAX, is 32, but it seems intractible to prove
+// every I2C implementation uses that limit. So to prevent overflow,
+// allocate buffers based on the largest possible count byte of 255.
+constexpr size_t maxRecvLenBuf = 1 + 255 + 1;
+typedef std::array<uint8_t, maxRecvLenBuf> BlockBuf;
+
+struct ParsedStep
+{
+ const uint8_t* reqData;
+ size_t length;
+ DevAddr devAddr;
+ bool isRead;
+ // When nonzero, device supplies count for this step, as in SMBUS block
+ // mode. Value will tell driver how many extra bytes to read for entire
+ // block: 1 for count byte w/o PEC, 2 if there is also a PEC byte.
+ uint16_t blockExtra;
+ bool noStart;
+};
+
+struct ParsedReq
+{
+ BusId localbus;
+ bool usePec;
+ size_t numSteps;
+ std::array<ParsedStep, maxSteps> step;
+};
+
+static ipmi_ret_t parseReqHdr(const uint8_t* reqBuf, size_t reqLen,
+ size_t* bytesUsed, i2c::ParsedReq* req)
+{
+ // Request header selects bus & flags for operation;
+ // additional bytes beyond are to be interpreted as steps.
+ if (reqLen < *bytesUsed + requestHeaderLen)
+ {
+ std::fprintf(stderr, "i2c::parse reqLen=%zu?\n", reqLen);
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+ // Deserialize request header bytes.
+ req->localbus = reqBuf[requestHeaderBus];
+ auto reqFlags = reqBuf[requestHeaderFlags];
+ *bytesUsed += requestHeaderLen;
+
+ // Decode flags.
+ req->usePec = !!(reqFlags & requestFlagsUsePec);
+ return IPMI_CC_OK;
+}
+
+static ipmi_ret_t parseReqStep(const uint8_t* reqBuf, size_t reqLen,
+ size_t* bytesUsed, i2c::ParsedReq* req)
+{
+ size_t bytesLeft = reqLen - *bytesUsed;
+ if (req->numSteps >= maxSteps || bytesLeft < stepHeaderLen)
+ {
+ std::fprintf(stderr, "i2c::parse[%zu] bytesLeft=%zu?\n", req->numSteps,
+ bytesLeft);
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+ const uint8_t* stepHdr = reqBuf + *bytesUsed;
+ auto step = &req->step[req->numSteps++];
+
+ // Deserialize request step header bytes.
+ uint8_t devAndDir = stepHdr[stepHeaderDevAndDir];
+ uint8_t stepFlags = stepHdr[stepHeaderFlags];
+ step->length = stepHdr[stepHeaderParm];
+ bytesLeft -= stepHeaderLen;
+ *bytesUsed += stepHeaderLen;
+
+ // Decode device addr & direction.
+ step->devAddr = devAndDir >> 1;
+ step->isRead = !!(devAndDir & 1);
+
+ // Decode step flags.
+ step->noStart = !!(stepFlags & stepFlagsNoStart);
+
+ if (step->isRead)
+ {
+ // Read could select blockExtra.
+ if (stepFlags & stepFlagsRecvLen)
+ {
+ step->blockExtra = req->usePec ? 2 : 1;
+ }
+ }
+ else
+ {
+ // For write, requested byte count must follow.
+ if (bytesLeft < step->length)
+ {
+ std::fprintf(stderr, "i2c::parse[%zu] bytesLeft=%zu, parm=%zu?\n",
+ req->numSteps, bytesLeft, step->length);
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+ step->reqData = reqBuf + *bytesUsed;
+ *bytesUsed += step->length;
+ }
+ return IPMI_CC_OK;
+}
+
+// Parse i2c request.
+static ipmi_ret_t parse(const uint8_t* reqBuf, size_t reqLen,
+ i2c::ParsedReq* req)
+{
+ size_t bytesUsed = 0;
+ auto rc = parseReqHdr(reqBuf, reqLen, &bytesUsed, req);
+ if (rc != IPMI_CC_OK)
+ {
+ return rc;
+ }
+ do
+ {
+ rc = parseReqStep(reqBuf, reqLen, &bytesUsed, req);
+ if (rc != IPMI_CC_OK)
+ {
+ return rc;
+ }
+ } while (bytesUsed < reqLen);
+ return IPMI_CC_OK;
+}
+
+// Convert parsed request to I2C messages.
+static ipmi_ret_t buildI2cMsgs(const i2c::ParsedReq& req,
+ std::unique_ptr<i2c::BlockBuf> rxBuf[],
+ struct i2c_msg msgs[],
+ struct i2c_rdwr_ioctl_data* msgset)
+{
+ size_t minReplyLen = 0;
+
+ for (size_t i = 0; i < req.numSteps; ++msgset->nmsgs, ++i)
+ {
+ const auto& step = req.step[i];
+ auto* msg = &msgs[i];
+ msg->addr = step.devAddr;
+
+ if (!step.isRead)
+ {
+ msg->flags = 0;
+ msg->len = step.length;
+ msg->buf = const_cast<uint8_t*>(step.reqData);
+ continue;
+ }
+ rxBuf[i] = std::make_unique<i2c::BlockBuf>();
+ msg->buf = rxBuf[i]->data();
+
+ if (step.blockExtra == 0)
+ {
+ msg->flags = I2C_M_RD;
+ msg->len = step.length;
+ minReplyLen += msg->len;
+ }
+ else
+ {
+ // Special buffer setup needed for block read:
+ // . 1. msg len must allow for maximum possible transfer,
+ // . 2. blockExtra must be preloaded into buf[0]
+ // The internal i2c_transfer API is slightly different;
+ // the rdwr ioctl handler adapts by moving blockExtra
+ // into msg.len where the driver will expect to find it.
+ //
+ // References:
+ // drivers/i2c/i2c-dev.c: i2cdev_ioctl_rdwr()
+ msg->flags = I2C_M_RD | I2C_M_RECV_LEN;
+ msg->len = maxRecvLenBuf;
+ msg->buf[0] = step.blockExtra;
+ minReplyLen += step.blockExtra;
+ }
+ }
+
+ if (minReplyLen > i2c::largestReply)
+ {
+ std::fprintf(stderr, "I2c::transfer minReplyLen=%zu?\n", minReplyLen);
+ return IPMI_CC_RESPONSE_ERROR; // Won't fit in response message
+ }
+
+#ifdef __IPMI_DEBUG__
+ for (size_t i = 0; i < req.numSteps; ++i)
+ {
+ auto* msg = &msgs[i];
+ std::fprintf(stderr, "I2c::transfer msg[%zu]: %02x %04x %d\n", i,
+ msg->addr, msg->flags, msg->len);
+ }
+#endif
+
+ return IPMI_CC_OK;
+}
+
+static int openBus(BusId localbus)
+{
+ char busCharDev[16];
+ std::snprintf(busCharDev, sizeof(busCharDev) - 1, "/dev/i2c-%d", localbus);
+ int busFd = open(busCharDev, O_RDWR);
+ if (busFd < 0)
+ {
+ std::fprintf(stderr,
+ "NetFn:[0x2E], OEM:[0x002B79], Cmd:[0x02], "
+ "I2C Bus Open(\"%s\"): \"%s\"\n",
+ busCharDev, strerror(-busFd));
+ }
+ return busFd;
+}
+
+} // namespace i2c
+
+ipmi_ret_t I2c::transfer(ipmi_cmd_t cmd, const uint8_t* reqBuf,
+ uint8_t* replyBuf, size_t* dataLen)
+{
+ // Parse message header.
+ auto reqLen = *dataLen;
+ *dataLen = 0;
+ i2c::ParsedReq req = {};
+ auto rc = parse(reqBuf, reqLen, &req);
+ if (rc != IPMI_CC_OK)
+ {
+ return rc;
+ }
+
+ // Build full msgset
+ std::unique_ptr<i2c::BlockBuf> rxBuf[i2c::maxSteps];
+ struct i2c_msg msgs[i2c::maxSteps] = {};
+ struct i2c_rdwr_ioctl_data msgset = {
+ .msgs = msgs,
+ .nmsgs = 0,
+ };
+ rc = buildI2cMsgs(req, rxBuf, msgs, &msgset);
+ if (rc != IPMI_CC_OK)
+ {
+ return rc;
+ }
+
+ // Try to open i2c bus
+ int busFd = i2c::openBus(req.localbus);
+ if (busFd < 0)
+ {
+ return IPMI_CC_UNSPECIFIED_ERROR;
+ }
+ int ioError = ioctl(busFd, I2C_RDWR, &msgset);
+
+ // Done with busFd, so close it immediately to avoid leaking it.
+ (void)close(busFd);
+ if (ioError < 0)
+ {
+ std::fprintf(stderr, "I2c::transfer I2C_RDWR ioError=%d?\n", ioError);
+ return IPMI_CC_UNSPECIFIED_ERROR; // I2C_RDWR I/O error
+ }
+
+ // If we read any data, append it, in the order we read it.
+ uint8_t* nextReplyByte = replyBuf;
+ size_t replyLen = 0;
+ for (size_t i = 0; i < req.numSteps; ++i)
+ {
+ const auto& step = req.step[i];
+ if (step.isRead)
+ {
+ const auto* msg = &msgs[i];
+ size_t lenRead =
+ step.blockExtra ? *msg->buf + step.blockExtra : msg->len;
+ replyLen += lenRead;
+ if (replyLen > i2c::largestReply)
+ {
+ std::fprintf(stderr, "I2c::transfer[%zu] replyLen=%zu?\n", i,
+ replyLen);
+ return IPMI_CC_RESPONSE_ERROR; // Won't fit in response message
+ }
+ std::memcpy(nextReplyByte, msg->buf, lenRead);
+ nextReplyByte += lenRead;
+ }
+ }
+ *dataLen = replyLen;
+ return IPMI_CC_OK;
+}
+
+void I2c::registerWith(Router* oemRouter)
+{
+ Handler f = [this](ipmi_cmd_t cmd, const uint8_t* reqBuf, uint8_t* replyBuf,
+ size_t* dataLen) {
+ return transfer(cmd, reqBuf, replyBuf, dataLen);
+ };
+
+ std::fprintf(stderr, "Registering OEM:[%#08X], Cmd:[%#04X] for I2C\n",
+ googOemNumber, Cmd::i2cCmd);
+ oemRouter->registerHandler(googOemNumber, Cmd::i2cCmd, f);
+
+ std::fprintf(stderr, "Registering OEM:[%#08X], Cmd:[%#04X] for I2C\n",
+ obmcOemNumber, Cmd::i2cCmd);
+ oemRouter->registerHandler(obmcOemNumber, Cmd::i2cCmd, f);
+}
+
+namespace i2c
+{
+// Currently ipmid dynamically loads providers such as these;
+// this creates our singleton upon load.
+void setupGlobalOemI2c() __attribute__((constructor));
+
+void setupGlobalOemI2c()
+{
+ globalOemI2c = std::make_unique<I2c>();
+ globalOemI2c->registerWith(oem::mutableRouter());
+}
+} // namespace i2c
+} // namespace oem
diff --git a/i2c.hpp b/i2c.hpp
new file mode 100644
index 0000000..105dfbe
--- /dev/null
+++ b/i2c.hpp
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <host-ipmid/ipmid-api.h>
+
+#include <cstdint>
+#include <host-ipmid/oemrouter.hpp>
+
+using std::uint8_t;
+
+namespace oem
+{
+namespace i2c
+{
+/*
+ * Request header
+ */
+constexpr size_t requestHeaderBus = 0;
+constexpr size_t requestHeaderFlags = 1;
+constexpr size_t requestHeaderLen = 2;
+
+typedef uint8_t BusId;
+typedef uint8_t ReqFlags;
+
+constexpr ReqFlags requestFlagsUsePec = (1 << 7);
+
+/*
+ * Request step.
+ */
+constexpr size_t stepHeaderDevAndDir = 0;
+constexpr size_t stepHeaderFlags = 1;
+constexpr size_t stepHeaderParm = 2;
+constexpr size_t stepHeaderLen = 3;
+
+typedef uint8_t DevAddr;
+typedef uint8_t StepFlags;
+constexpr StepFlags stepFlagsRecvLen = (1 << 7);
+constexpr StepFlags stepFlagsNoStart = (1 << 6);
+
+// So far 2 steps suffics, so 4 should be safe.
+constexpr size_t maxSteps = 4;
+
+// Currently we specify 32 byte payload limit;
+// but for block read with PEC that entails 34 total bytes.
+constexpr size_t largestReply = 34;
+
+} // namespace i2c
+
+/**
+ * I2c is a global i2c-via-ipmi manager and IPMI handler.
+ */
+class I2c
+{
+ public:
+ /**
+ * Allows specification of the mechanism to register OEM IPMI handler.
+ *
+ * @param[in] oemRouter - A pointer to a router instance.
+ */
+ void registerWith(Router* oemRouter);
+
+ /**
+ * The i2c-via-ipmi commands go through this method.
+ *
+ * @param[in] cmd - the IPMI command.
+ * @param[in] reqBuf - the IPMI command buffer.
+ * @param[in,out] replyBuf - the IPMI response buffer.
+ * @param[in,out] dataLen - pointer to request length, set to reply length.
+ * @return IPMI return code.
+ */
+ ipmi_ret_t transfer(ipmi_cmd_t cmd, const uint8_t* reqBuf,
+ uint8_t* replyBuf, size_t* dataLen);
+};
+
+} // namespace oem