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