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