initial drop of phosphor-ipmi-ethstats
initial drop of phosphor-ipmi-ethstats OEM IPMI commands. These
commands return ethernet device statistics from the BMC.
Change-Id: Iece5d8604ef90f48f08ce1ae710dd295288ff23f
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..8831daf
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,15 @@
+AM_DEFAULT_SOURCE_EXT = .cpp
+
+libethstatscmddir = ${libdir}/ipmid-providers
+libethstatscmd_LTLIBRARIES = libethstatscmd.la
+libethstatscmd_la_SOURCES = main.cpp
+
+libethstatscmd_la_LDFLAGS = $(SYSTEMD_LIBS) \
+ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ $(PHOSPHOR_LOGGING_LIBS) \
+ -lstdc++fs \
+ -version-info 0:0:0 -shared
+
+libethstatscmd_la_CXXFLAGS = $(SYSTEMD_CFLAGS) \
+ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+ $(PHOSPHOR_LOGGING_CFLAGS)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..74cb38f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+### Ethernet Statistics Command (0x30)
+
+There is the need to read a specific ethernet-level statistic value from the BMC. This is driven primarily to detect link errors that require hardware swaps during manufacturing.
+
+This command will be well structured such that there is a request and response which mirror to some extent.
+
+The request will specify the ethernet interface by name, as a length-prepended string, and the field they're requesting by identifier (an unsigned byte).
+
+If the command is not supported, the IPMI OEM handler will respond accordingly, however, if the field is not supported or not recognized, the command will return 0xcc (invalid field).
+
+The current ethernet statistics available (all future additions must append):
+
+|Identifier |Human Readable Name
+|-----------|--------------------
+|0 |rx_bytes
+|1 |rx_compressed
+|2 |rx_crc_errors
+|3 |rx_dropped
+|4 |rx_errors
+|5 |rx_fifo_errors
+|6 |rx_frame_errors
+|7 |rx_length_errors
+|8 |rx_missed_errors
+|9 |rx_nohandler
+|10 |rx_over_errors
+|11 |rx_packets
+|12 |tx_aborted_errors
+|13 |tx_bytes
+|14 |tx_carrier_errors
+|15 |tx_compressed
+|16 |tx_dropped
+|17 |tx_errors
+|18 |tx_fifo_errors
+|19 |tx_heartbeat_errors
+|20 |tx_packets
+|21 |tx_window_errors
+
+Request
+
+|Byte(s) |Value |Data
+|--------|------|----
+|0x00 |Statistic ID |The identifier of the desired statistic.
+|0x01 |Length |Length of string (not including null termination).
+|0x02.. |The name |The string, not null-terminated.
+
+Response
+
+|Byte(s) |Value |Data
+|--------|-------|----
+|0x00 |Stat ID|The identifier of the desired statistic.
+|0x01....|Uint64 |The value. Because these are counters we don't anticipate negative values, and we don't expect overflow.
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..d7987c9
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,38 @@
+# Initialization
+AC_PREREQ([2.69])
+AC_INIT([phosphor-ipmi-ethstats], [1.0], [https://www.github.com/openbmc/phosphor-ipmi-ethstats/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."])])
+AC_CHECK_HEADER(experimental/filesystem, ,[AC_MSG_ERROR([Could not find experimental/filesystem...libstdc++fs developement package required])])
+
+# Checks for library functions.
+LT_INIT # Required for systemd linking
+
+# Do you want to install with the Google OEM Number as well.
+AC_ARG_ENABLE([google],
+ AS_HELP_STRING([--enable-google], [Enable registering with Google OEN])
+)
+AC_ARG_VAR(ENABLE_GOOGLE, [Enable registering with Google OEN])
+AS_IF([test "x$enable_google" == "xyes"],
+ [ENABLE_GOOGLE="yes"]
+ AC_DEFINE_UNQUOTED([ENABLE_GOOGLE], ["$ENABLE_GOOGLE"], [Enable registering with Google OEN])
+)
+
+# Create configured output
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/ethstats.hpp b/ethstats.hpp
new file mode 100644
index 0000000..ac170aa
--- /dev/null
+++ b/ethstats.hpp
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <cstdint>
+
+namespace ethstats
+{
+
+/**
+ * @brief Ethstat Request structure.
+ */
+struct EthStatRequest
+{
+ uint8_t statId;
+ uint8_t if_name_len;
+ uint8_t if_name[0];
+} __attribute__((packed));
+
+/**
+ * @brief Ethstat Reply structure.
+ */
+struct EthStatReply
+{
+ uint8_t statId;
+ uint64_t value;
+} __attribute__((packed));
+
+enum EthernetStatisticsIds
+{
+ RX_BYTES = 0,
+ RX_COMPRESSED = 1,
+ RX_CRC_ERRORS = 2,
+ RX_DROPPED = 3,
+ RX_ERRORS = 4,
+ RX_FIFO_ERRORS = 5,
+ RX_FRAME_ERRORS = 6,
+ RX_LENGTH_ERRORS = 7,
+ RX_MISSED_ERRORS = 8,
+ RX_NOHANDLER = 9,
+ RX_OVER_ERRORS = 10,
+ RX_PACKETS = 11,
+ TX_ABORTED_ERRORS = 12,
+ TX_BYTES = 13,
+ TX_CARRIER_ERRORS = 14,
+ TX_COMPRESSED = 15,
+ TX_DROPPED = 16,
+ TX_ERRORS = 17,
+ TX_FIFO_ERRORS = 18,
+ TX_HEARTBEAT_ERRORS = 19,
+ TX_PACKETS = 20,
+ TX_WINDOW_ERRORS = 21,
+};
+
+} // namespace ethstats
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..e58d193
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,204 @@
+/*
+ * 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 "config.h"
+
+#include "ethstats.hpp"
+
+#include <host-ipmid/ipmid-api.h>
+
+#include <cstdint>
+#include <cstring>
+#include <experimental/filesystem>
+#include <fstream>
+#include <host-ipmid/iana.hpp>
+#include <host-ipmid/oemrouter.hpp>
+#include <map>
+#include <sstream>
+#include <string>
+
+/* TODO: Swap out once https://gerrit.openbmc-project.xyz/12958 is merged */
+namespace oem
+{
+constexpr auto ethStatsCmd = 48;
+} // namespace oem
+
+namespace ethstats
+{
+namespace fs = std::experimental::filesystem;
+
+// If this changes in the future, there should be some alternative
+// source for the information if possible to provide continuined functionality.
+static const std::map<uint8_t, std::string> statLookup = {
+ {RX_BYTES, "rx_bytes"},
+ {RX_COMPRESSED, "rx_compressed"},
+ {RX_CRC_ERRORS, "rx_crc_errors"},
+ {RX_DROPPED, "rx_dropped"},
+ {RX_ERRORS, "rx_errors"},
+ {RX_FIFO_ERRORS, "rx_fifo_errors"},
+ {RX_FRAME_ERRORS, "rx_frame_errors"},
+ {RX_LENGTH_ERRORS, "rx_length_errors"},
+ {RX_MISSED_ERRORS, "rx_missed_errors"},
+ {RX_NOHANDLER, "rx_nohandler"},
+ {RX_OVER_ERRORS, "rx_over_errors"},
+ {RX_PACKETS, "rx_packets"},
+ {TX_ABORTED_ERRORS, "tx_aborted_errors"},
+ {TX_BYTES, "tx_bytes"},
+ {TX_CARRIER_ERRORS, "tx_carrier_errors"},
+ {TX_COMPRESSED, "tx_compressed"},
+ {TX_DROPPED, "tx_dropped"},
+ {TX_ERRORS, "tx_errors"},
+ {TX_FIFO_ERRORS, "tx_fifo_errors"},
+ {TX_HEARTBEAT_ERRORS, "tx_heartbeat_errors"},
+ {TX_PACKETS, "tx_packets"},
+ {TX_WINDOW_ERRORS, "tx_window_errors"},
+};
+
+/**
+ * Handle the OEM IPMI EthStat Command.
+ *
+ * @param[in] cmd - the OEM command
+ * @param[in] reqBuf - The IPMI request buffer.
+ * @param[in,out] replyCmdBuf - the IPMI reply buffer.
+ * @param[in,out] dataLen - The length of the request and reply.
+ * @return the IPMI result code.
+ */
+static ipmi_ret_t HandleEthStatCommand(ipmi_cmd_t cmd, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ auto reqLength = (*dataLen);
+
+ // Verify the reqBuf is the minimum length.
+ // [0] == statistics id
+ // [1] == if_name_length
+ // [2..N] == if_name
+ // In theory the smallest can be a one-letter name. (3 bytes).
+ if (reqLength < sizeof(struct EthStatRequest) + sizeof(uint8_t))
+ {
+ std::fprintf(stderr, "*dataLen too small: %u\n",
+ static_cast<uint32_t>(reqLength));
+ return IPMI_CC_INVALID;
+ }
+
+ // using struct prefix due to nature as c-style pod struct.
+ struct EthStatRequest request;
+ std::memcpy(&request, &reqBuf[0], sizeof(request));
+ auto nameLen = static_cast<uint32_t>(request.if_name_len);
+
+ if (reqLength < (sizeof(request) + nameLen))
+ {
+ std::fprintf(stderr, "*dataLen too small: %u\n",
+ static_cast<uint32_t>(reqLength));
+ return IPMI_CC_INVALID;
+ }
+
+ // Check the statistic to see if we recognize it.
+ auto stat = statLookup.find(request.statId);
+ if (stat == statLookup.end())
+ {
+ std::fprintf(stderr, "stat not known: 0x%x\n", request.statId);
+ return IPMI_CC_INVALID_FIELD_REQUEST;
+ }
+
+ // The if_name handling plus a few other things was taken from the
+ // CableCheck command implementation.
+ //
+ // Ok, so we know what statistic they want. Let's validate their
+ // if_name. The string is length delimited (like dns).
+
+ // Copy the string out of the request buffer.
+ // Maximum length is 256 bytes, excluding the nul-terminator.
+ auto name = std::string(
+ reinterpret_cast<const char*>(&reqBuf[0] + sizeof(request)), nameLen);
+
+ // 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.
+ // Decided to make this more robust, although since it appends to the path
+ // it would limit any exposure.
+ if (name.find("/") != std::string::npos)
+ {
+ std::fprintf(stderr, "Invalid or illegal name: '%s'\n", name.c_str());
+ return IPMI_CC_INVALID;
+ }
+
+ // TODO: Transition to using the netlink api.
+ std::ostringstream opath;
+ opath << "/sys/class/net/" << name << "/statistics/" << stat->second;
+ std::string path = opath.str();
+
+ std::error_code ec;
+ if (!fs::exists(path, ec))
+ {
+ std::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.
+ // We read the number as int64, then check to make sure it's positive
+ // before casting to uint64.
+ uint64_t value = 0;
+ std::ifstream ifs;
+ ifs.exceptions(std::ifstream::failbit);
+
+ try
+ {
+ ifs.open(path);
+ ifs >> value;
+ }
+ catch (std::ios_base::failure& fail)
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ struct EthStatReply reply;
+ reply.statId = request.statId;
+ reply.value = value;
+
+ // Store the result.
+ std::memcpy(&replyCmdBuf[0], &reply, sizeof(reply));
+ (*dataLen) = sizeof(reply);
+
+ return IPMI_CC_OK;
+}
+
+void setupGlobalOemEthStats() __attribute__((constructor));
+
+void setupGlobalOemEthStats()
+{
+ oem::Router* oemRouter = oem::mutableRouter();
+
+#ifdef ENABLE_GOOGLE
+ /* Install in Google OEM Namespace when enabled. */
+ std::fprintf(stderr,
+ "Registering OEM:[%#08X], Cmd:[%#04X] for Ethstats Commands\n",
+ oem::googOemNumber, oem::ethStatsCmd);
+
+ oemRouter->registerHandler(oem::googOemNumber, oem::ethStatsCmd,
+ HandleEthStatCommand);
+#endif
+
+ std::fprintf(stderr,
+ "Registering OEM:[%#08X], Cmd:[%#04X] for Ethstats Commands\n",
+ oem::obmcOemNumber, oem::ethStatsCmd);
+
+ oemRouter->registerHandler(oem::obmcOemNumber, oem::ethStatsCmd,
+ HandleEthStatCommand);
+}
+
+} // namespace ethstats