google-ipmi-sys: initial commit: OEM IPMI handler

This implements a set of OEM IPMI commands built as sub-commands under
one OEM IPMI command registered in the Google OEM Namespace.

Change-Id: I65ff6f35838b501ac6ac67f7765bbc474b808660
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..5b2db07
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,17 @@
+AM_DEFAULT_SOURCE_EXT = .cpp
+
+libsyscmdsdir = ${libdir}/ipmid-providers
+libsyscmds_LTLIBRARIES = libsyscmds.la
+libsyscmds_la_SOURCES = main.cpp cable.cpp cpld.cpp eth.cpp psu.cpp
+
+libsyscmds_la_LDFLAGS = $(SYSTEMD_LIBS) \
+                          $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+                          $(PHOSPHOR_LOGGING_LIBS) \
+                          -lstdc++fs \
+                          -version-info 0:0:0 -shared
+
+libsyscmds_la_CXXFLAGS = $(SYSTEMD_CFLAGS) \
+                           $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+                           $(PHOSPHOR_LOGGING_CFLAGS)
+
+SUBDIRS = .
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/cable.cpp b/cable.cpp
new file mode 100644
index 0000000..025b228
--- /dev/null
+++ b/cable.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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 "cable.hpp"
+
+#include "main.hpp"
+
+#include <cstdint>
+#include <experimental/filesystem>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <system_error>
+
+namespace google
+{
+namespace ipmi
+{
+namespace fs = std::experimental::filesystem;
+
+struct CableRequest
+{
+    uint8_t subcommand;
+    uint8_t if_name_len;
+    uint8_t if_name[0];
+} __attribute__((packed));
+
+struct CableReply
+{
+    uint8_t subcommand;
+    uint8_t value;
+} __attribute__((packed));
+
+ipmi_ret_t CableCheck(const uint8_t* reqBuf, uint8_t* replyBuf, size_t* dataLen)
+{
+    // There is an IPMI LAN channel statistics command which could be used for
+    // this type of check, however, we're not able to wait for the OpenBMC
+    // implementation to stabilize related to the network management.
+    //
+    // There is a link status file, but it is "unknown" to start with...
+    // The path we're checking: /sys/class/net/eth1/statistics/rx_packets
+
+    // This command is expecting: [0x00][len][if_name]
+    if ((*dataLen) < sizeof(struct CableRequest) + sizeof(uint8_t))
+    {
+        fprintf(stderr, "Invalid command length: %lu\n", (*dataLen));
+        return IPMI_CC_INVALID;
+    }
+
+    const auto request =
+        reinterpret_cast<const struct CableRequest*>(&reqBuf[0]);
+
+    // Sanity check the object contents.
+    if (request->if_name_len == 0)
+    {
+        fprintf(stderr, "Invalid string length: %d\n", request->if_name_len);
+        return IPMI_CC_INVALID;
+    }
+
+    // Verify the request buffer contains the object and the string.
+    if ((*dataLen) < (sizeof(struct CableRequest) + request->if_name_len))
+    {
+        fprintf(stderr, "*dataLen too small: %lu\n", (*dataLen));
+        return IPMI_CC_INVALID;
+    }
+
+    // Maximum length one can specify, plus null terminator.
+    char nameBuf[256] = {};
+    std::ostringstream opath;
+
+    // Copy the string out of the request buffer.
+    memcpy(&nameBuf[0], request->if_name, request->if_name_len);
+    std::string name = nameBuf;
+
+    // Minor sanity & security check (of course, I'm less certain if unicode
+    // comes into play here.
+    //
+    // Basically you can't easily inject ../ or /../ into the path below.
+    if (name.find("/") != std::string::npos)
+    {
+        fprintf(stderr, "Invalid or illegal name: '%s'\n", nameBuf);
+        return IPMI_CC_INVALID;
+    }
+
+    opath << "/sys/class/net/" << name << "/statistics/rx_packets";
+    std::string path = opath.str();
+
+    std::error_code ec;
+    if (!fs::exists(path, ec))
+    {
+        fprintf(stderr, "Path: '%s' doesn't exist.\n", path.c_str());
+        return IPMI_CC_INVALID;
+    }
+    // We're uninterested in the state of ec.
+
+    // Read the file and check the result.
+    int64_t count = 0;
+    std::ifstream ifs;
+    ifs.exceptions(std::ifstream::failbit);
+    try
+    {
+        ifs.open(path);
+        ifs >> count;
+    }
+    catch (std::ios_base::failure& fail)
+    {
+        return IPMI_CC_INVALID;
+    }
+
+    struct CableReply reply;
+    reply.subcommand = SysCableCheck;
+    reply.value = 0x00; // Default false
+
+    // If we have received packets then there is a cable present.
+    reply.value = (count > 0) ? 1 : 0;
+
+    // Return the subcommand and the result.
+    memcpy(&replyBuf[0], &reply, sizeof(struct CableReply));
+    (*dataLen) = sizeof(struct CableReply);
+
+    return IPMI_CC_OK;
+}
+
+} // namespace ipmi
+} // namespace google
diff --git a/cable.hpp b/cable.hpp
new file mode 100644
index 0000000..6ed5418
--- /dev/null
+++ b/cable.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <host-ipmid/ipmid-api.h>
+
+namespace google
+{
+namespace ipmi
+{
+
+//
+// Handle the cablecheck.  Sys must supply which ethernet device they're
+// interested in.
+ipmi_ret_t CableCheck(const uint8_t* reqBuf, uint8_t* replyBuf,
+                      size_t* dataLen);
+
+} // namespace ipmi
+} // namespace google
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..039060f
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,32 @@
+# Initialization
+AC_PREREQ([2.69])
+AC_INIT([sys-ipmi], [1.0], [https://www.github.com/google-ipmi-sys/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_14([noext])
+AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
+
+# Checks for libraries.
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221], [], [AC_MSG_ERROR(["systemd required and not found"])])
+PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus], ,[AC_MSG_ERROR([The openbmc/sdbusplus package is required])])
+PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging], ,[AC_MSG_ERROR([The openbmc/phosphor-logging package is required])])
+PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces], [], [AC_MSG_ERROR(["phosphor-dbus-interfaces required and not found."])])
+AC_CHECK_HEADER(experimental/any, ,[AC_MSG_ERROR([Could not find experimental/any...libstdc++fs developement package required])])
+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/cpld.cpp b/cpld.cpp
new file mode 100644
index 0000000..6194278
--- /dev/null
+++ b/cpld.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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 "cpld.hpp"
+
+#include "main.hpp"
+
+#include <experimental/filesystem>
+#include <fstream>
+#include <sstream>
+
+namespace google
+{
+namespace ipmi
+{
+namespace fs = std::experimental::filesystem;
+
+struct CpldRequest
+{
+    uint8_t subcommand;
+    uint8_t id;
+} __attribute__((packed));
+
+struct CpldReply
+{
+    uint8_t subcommand;
+    uint8_t major;
+    uint8_t minor;
+    uint8_t point;
+    uint8_t subpoint;
+} __attribute__((packed));
+
+//
+// Handle reading the cpld version from the tmpfs.
+//
+ipmi_ret_t CpldVersion(const uint8_t* reqBuf, uint8_t* replyBuf,
+                       size_t* dataLen)
+{
+    if ((*dataLen) < sizeof(struct CpldRequest))
+    {
+        fprintf(stderr, "Invalid command length: %lu\n", (*dataLen));
+        return IPMI_CC_INVALID;
+    }
+
+    // reqBuf[0] is the subcommand.
+    // reqBuf[1] is the CPLD id. "/run/cpld{id}.version" is what we read.
+    // Verified that this cast actually returns the value 255 and not something
+    // negative in the case where reqBuf[1] is 0xff.  However, it looks weird
+    // since I would expect int(uint8(0xff)) to be -1.  So, just cast it
+    // unsigned. we're casting to an int width to avoid it thinking it's a
+    // letter, because it does that.
+
+    const auto request =
+        reinterpret_cast<const struct CpldRequest*>(&reqBuf[0]);
+
+    std::ostringstream opath;
+    opath << "/run/cpld" << static_cast<unsigned int>(request->id)
+          << ".version";
+    // Check for file
+
+    std::error_code ec;
+    if (!fs::exists(opath.str(), ec))
+    {
+        fprintf(stderr, "Path: '%s' doesn't exist.\n", opath.str().c_str());
+        return IPMI_CC_INVALID;
+    }
+    // We're uninterested in the state of ec.
+
+    // If file exists, read.
+    std::ifstream ifs;
+    ifs.exceptions(std::ifstream::failbit);
+    std::string value;
+    try
+    {
+        ifs.open(opath.str());
+        ifs >> value;
+    }
+    catch (std::ios_base::failure& fail)
+    {
+        return IPMI_CC_INVALID;
+    }
+
+    // If value parses as expected, return version.
+    int major = 0;
+    int minor = 0;
+    int point = 0;
+    int subpoint = 0;
+
+    int num_fields =
+        sscanf(value.c_str(), "%d.%d.%d.%d", &major, &minor, &point, &subpoint);
+    if (num_fields == 0)
+    {
+        fprintf(stderr, "Invalid version.\n");
+        return IPMI_CC_INVALID;
+    }
+
+    // Truncate if the version is too high (documented).
+    struct CpldReply reply;
+    reply.subcommand = SysCpldVersion;
+    reply.major = static_cast<uint8_t>(major);
+    reply.minor = static_cast<uint8_t>(minor);
+    reply.point = static_cast<uint8_t>(point);
+    reply.subpoint = static_cast<uint8_t>(subpoint);
+
+    memcpy(&replyBuf[0], &reply, sizeof(struct CpldReply));
+    (*dataLen) = sizeof(struct CpldReply);
+
+    return IPMI_CC_OK;
+}
+
+} // namespace ipmi
+} // namespace google
diff --git a/cpld.hpp b/cpld.hpp
new file mode 100644
index 0000000..8c8604f
--- /dev/null
+++ b/cpld.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <host-ipmid/ipmid-api.h>
+
+namespace google
+{
+namespace ipmi
+{
+
+// Given a cpld identifier, return a version if available.
+ipmi_ret_t CpldVersion(const uint8_t* reqBuf, uint8_t* replyBuf,
+                       size_t* dataLen);
+
+} // namespace ipmi
+} // namespace google
diff --git a/eth.cpp b/eth.cpp
new file mode 100644
index 0000000..32dbe89
--- /dev/null
+++ b/eth.cpp
@@ -0,0 +1,103 @@
+/*
+ * 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 "eth.hpp"
+
+#include "main.hpp"
+
+#include <cstdint>
+#include <string>
+
+namespace google
+{
+namespace ipmi
+{
+
+struct EthDeviceRequest
+{
+    uint8_t subcommand;
+} __attribute__((packed));
+
+// The reply to the ethdevice command specifies the
+// IPMI channel number and the if_name used for the
+// ncis connection.
+struct EthDeviceReply
+{
+    uint8_t subcommand;
+    uint8_t channel;
+    // if_name_len doesn't include the null-terminator.
+    uint8_t if_name_len;
+    uint8_t if_name[0];
+} __attribute__((packed));
+
+// The phosphor-host-ipmi daemon requires a configuration that maps
+// the if_name to the IPMI LAN channel.  However, that doesn't strictly
+// define which is meant to be used for NCSI.
+#ifndef NCSI_IPMI_CHANNEL
+#define NCSI_IPMI_CHANNEL 1
+#endif
+
+#ifndef NCSI_IF_NAME
+#define NCSI_IF_NAME eth0
+#endif
+
+// TOOD(venture): The ipmid.h has this macro, which is a header we
+// can't normally access.
+#ifndef MAX_IPMI_BUFFER
+#define MAX_IPMI_BUFFER 64
+#endif
+
+// To deal with receiving a string without quotes.
+#define QUOTE(name) #name
+#define STR(macro) QUOTE(macro)
+#define NCSI_IF_NAME_STR STR(NCSI_IF_NAME)
+
+ipmi_ret_t GetEthDevice(const uint8_t* reqBuf, uint8_t* replyBuf,
+                        size_t* dataLen)
+{
+    if ((*dataLen) < sizeof(struct EthDeviceRequest))
+    {
+        fprintf(stderr, "Invalid command length: %lu\n", (*dataLen));
+        return IPMI_CC_INVALID;
+    }
+
+    std::string device = NCSI_IF_NAME_STR;
+    if (device.length() == 0)
+    {
+        fprintf(stderr, "Invalid eth string\n");
+        return IPMI_CC_INVALID;
+    }
+
+    if ((sizeof(struct EthDeviceReply) + device.length()) > MAX_IPMI_BUFFER)
+    {
+        fprintf(stderr, "Response would overflow response buffer\n");
+        return IPMI_CC_INVALID;
+    }
+
+    // Fill in the response buffer.
+    auto reply = reinterpret_cast<struct EthDeviceReply*>(&replyBuf[0]);
+    reply->subcommand = SysGetEthDevice;
+    reply->channel = NCSI_IPMI_CHANNEL;
+    reply->if_name_len = device.length();
+    memcpy(reply->if_name, device.c_str(), device.length());
+
+    (*dataLen) = sizeof(struct EthDeviceReply) + device.length();
+
+    return IPMI_CC_OK;
+}
+
+} // namespace ipmi
+} // namespace google
diff --git a/eth.hpp b/eth.hpp
new file mode 100644
index 0000000..d9ddf95
--- /dev/null
+++ b/eth.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <host-ipmid/ipmid-api.h>
+
+namespace google
+{
+namespace ipmi
+{
+
+// Handle the eth query command.
+// Sys can query the if_name and IPMI channel of the BMC's NCSI ethernet
+// device.
+ipmi_ret_t GetEthDevice(const uint8_t* reqBuf, uint8_t* replyBuf,
+                        size_t* dataLen);
+
+} // namespace ipmi
+} // namespace google
diff --git a/gbmc-psu-hardreset.target b/gbmc-psu-hardreset.target
new file mode 100644
index 0000000..c076830
--- /dev/null
+++ b/gbmc-psu-hardreset.target
@@ -0,0 +1,3 @@
+[Unit]
+Description=Sys PSU Hard Reset
+
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..ef6508b
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,88 @@
+/*
+ * 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 "main.hpp"
+
+#include "cable.hpp"
+#include "cpld.hpp"
+#include "eth.hpp"
+#include "psu.hpp"
+
+#include <host-ipmid/ipmid-api.h>
+
+#include <cstdint>
+#include <experimental/filesystem>
+#include <fstream>
+#include <host-ipmid/iana.hpp>
+#include <host-ipmid/oemrouter.hpp>
+#include <sstream>
+#include <string>
+#include <system_error>
+
+namespace oem
+{
+namespace google
+{
+constexpr int sysCmd = 50;
+} // namespace google
+} // namespace oem
+
+namespace google
+{
+namespace ipmi
+{
+
+static ipmi_ret_t HandleSysCommand(ipmi_cmd_t cmd, const uint8_t* reqBuf,
+                                   uint8_t* replyCmdBuf, size_t* dataLen)
+{
+    // Verify it's at least as long as it needs to be for a subcommand.
+    if ((*dataLen) < 1)
+    {
+        fprintf(stderr, "*dataLen too small: %lu\n", (*dataLen));
+        return IPMI_CC_INVALID;
+    }
+
+    switch (reqBuf[0])
+    {
+        case SysCableCheck:
+            return CableCheck(reqBuf, replyCmdBuf, dataLen);
+        case SysCpldVersion:
+            return CpldVersion(reqBuf, replyCmdBuf, dataLen);
+        case SysGetEthDevice:
+            return GetEthDevice(reqBuf, replyCmdBuf, dataLen);
+        case SysPsuHardReset:
+            return PsuHardReset(reqBuf, replyCmdBuf, dataLen);
+        default:
+            fprintf(stderr, "Invalid subcommand: 0x%x\n", reqBuf[0]);
+            return IPMI_CC_INVALID;
+    }
+}
+
+void setupGlobalOemCableCheck() __attribute__((constructor));
+
+void setupGlobalOemCableCheck()
+{
+    oem::Router* oemRouter = oem::mutableRouter();
+
+    fprintf(stderr, "Registering OEM:[%#08X], Cmd:[%#04X] for Sys Commands\n",
+            oem::googOemNumber, oem::google::sysCmd);
+
+    oemRouter->registerHandler(oem::googOemNumber, oem::google::sysCmd,
+                               HandleSysCommand);
+}
+
+} // namespace ipmi
+} // namespace google
diff --git a/main.hpp b/main.hpp
new file mode 100644
index 0000000..f34e2a3
--- /dev/null
+++ b/main.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+namespace google
+{
+namespace ipmi
+{
+
+enum SysOEMCommands
+{
+    // The Sys cable check command.
+    SysCableCheck = 0,
+    // The Sys cpld version over ipmi command.
+    SysCpldVersion = 1,
+    // The Sys get eth device command.
+    SysGetEthDevice = 2,
+    // The Sys psu hard reset command.
+    SysPsuHardReset = 3,
+};
+
+} // namespace ipmi
+} // namespace google
diff --git a/psu.cpp b/psu.cpp
new file mode 100644
index 0000000..1a4d556
--- /dev/null
+++ b/psu.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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 "psu.hpp"
+
+#include "main.hpp"
+
+#include <cstdint>
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+
+namespace google
+{
+namespace ipmi
+{
+
+using namespace phosphor::logging;
+
+struct PsuResetRequest
+{
+    uint8_t subcommand;
+    // Delay in seconds.
+    uint32_t delay;
+} __attribute__((packed));
+
+static constexpr auto TIME_DELAY_FILENAME = "/run/psu_timedelay";
+static constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
+static constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1";
+static constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
+static constexpr auto PSU_HARDRESET_TARGET = "gbmc-psu-hardreset.target";
+
+ipmi_ret_t PsuHardReset(const uint8_t* reqBuf, uint8_t* replyBuf,
+                        size_t* dataLen)
+{
+    if ((*dataLen) < sizeof(struct PsuResetRequest))
+    {
+        fprintf(stderr, "Invalid command length: %lu\n", (*dataLen));
+        return IPMI_CC_INVALID;
+    }
+
+    struct PsuResetRequest request;
+    memcpy(&request, &reqBuf[0], sizeof(struct PsuResetRequest));
+
+    std::ofstream ofs;
+    ofs.open(TIME_DELAY_FILENAME, std::ofstream::out);
+    if (!ofs.good())
+    {
+        fprintf(stderr, "Unable to open file for output.\n");
+        return IPMI_CC_INVALID;
+    }
+
+    ofs << "PSU_HARDRESET_DELAY=" << request.delay << std::endl;
+    if (ofs.fail())
+    {
+        fprintf(stderr, "Write failed\n");
+        ofs.close();
+        return IPMI_CC_INVALID;
+    }
+
+    // Write succeeded, please continue.
+    ofs.flush();
+    ofs.close();
+
+    auto bus = sdbusplus::bus::new_default();
+    auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT,
+                                      SYSTEMD_INTERFACE, "StartUnit");
+
+    method.append(PSU_HARDRESET_TARGET);
+    method.append("replace");
+
+    try
+    {
+        bus.call_noreply(method);
+    }
+    catch (const sdbusplus::exception::SdBusError& ex)
+    {
+        log<level::ERR>("Failed to call PSU hard reset");
+        return IPMI_CC_INVALID;
+    }
+
+    replyBuf[0] = SysPsuHardReset;
+    (*dataLen) = sizeof(uint8_t);
+
+    return IPMI_CC_OK;
+}
+
+} // namespace ipmi
+} // namespace google
diff --git a/psu.hpp b/psu.hpp
new file mode 100644
index 0000000..b2c7fbb
--- /dev/null
+++ b/psu.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <host-ipmid/ipmid-api.h>
+
+namespace google
+{
+namespace ipmi
+{
+
+// Set a time-delayed PSU hard reset.
+ipmi_ret_t PsuHardReset(const uint8_t* reqBuf, uint8_t* replyBuf,
+                        size_t* dataLen);
+
+} // namespace ipmi
+} // namespace google