summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Hanson <peterh@google.com>2017-06-07 17:40:45 -0700
committerVernon Mauery <vernon.mauery@linux.intel.com>2018-07-25 17:31:48 +0000
commit4a58985ce0cfb24bc0eb8678a4aa9a0ac42ba524 (patch)
treec267c333630f61e249a1f9fcc4144d8438241d75
parent15309efc2c1cebadbabdb0ef5f74a38fb8b78cfd (diff)
downloadphosphor-host-ipmid-4a58985ce0cfb24bc0eb8678a4aa9a0ac42ba524.tar.gz
phosphor-host-ipmid-4a58985ce0cfb24bc0eb8678a4aa9a0ac42ba524.zip
Add OemRouter facility.
OemRouter adds a facility to register OEM Group Message handlers, then dispatch matching messages to the registered handler. Added as a core source so that any dynamic provider can register its messages without requiring any specific load order. Includes code fixes for x86 portability. Change-Id: I47b8fe7873e3c7fdf35a00d3c8a7e17d30c398c4 Signed-off-by: Peter Hanson <peterh@google.com> Signed-off-by: Patrick Venture <venture@google.com>
-rw-r--r--Makefile.am7
-rw-r--r--configure.ac5
-rw-r--r--host-ipmid/ipmid-api.h1
-rw-r--r--host-ipmid/oemopenbmc.hpp25
-rw-r--r--host-ipmid/oemrouter.hpp83
-rw-r--r--ipmid.cpp4
-rw-r--r--oemrouter.cpp149
-rw-r--r--test/Makefile.am11
-rw-r--r--test/oemrouter_unittest.cpp175
9 files changed, 453 insertions, 7 deletions
diff --git a/Makefile.am b/Makefile.am
index ef1bf14..24fd5e7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -8,7 +8,8 @@ ipmid_SOURCES = \
settings.cpp \
host-cmd-manager.cpp \
timer.cpp \
- utils.cpp
+ utils.cpp \
+ oemrouter.cpp
nodist_ipmid_SOURCES = ipmiwhitelist.cpp
libapphandler_BUILT_LIST = \
@@ -108,7 +109,9 @@ libsysintfcmds_la_CXXFLAGS = $(SYSTEMD_CFLAGS) \
nobase_include_HEADERS = \
host-ipmid/ipmid-api.h \
host-ipmid/ipmid-host-cmd.hpp \
- host-ipmid/ipmid-host-cmd-utils.hpp
+ host-ipmid/ipmid-host-cmd-utils.hpp \
+ host-ipmid/oemopenbmc.hpp \
+ host-ipmid/oemrouter.hpp
# Forcing the build of self and then subdir
SUBDIRS = . test softoff
diff --git a/configure.ac b/configure.ac
index 9007582..918aef7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -29,7 +29,6 @@ PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces],, [AC_M
PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],,\
AC_MSG_ERROR(["Requires sdbusplus package."]))
-
AS_IF([test "x$enable_softoff" != "xno"],
# Check for sdbusplus
PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],, [AC_MSG_ERROR(["sdbusplus packaged required and not found"])])
@@ -50,8 +49,8 @@ LT_INIT([dlopen disable-static shared])
LT_LIB_DLLOAD
# Check/set gtest specific functions.
-AX_PTHREAD([GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=1"],[GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=0"])
-AC_SUBST(GTEST_CPPFLAGS)
+PKG_CHECK_MODULES([GTEST], [gtest], [], [AC_MSG_NOTICE([gtest not found, tests will not build])])
+PKG_CHECK_MODULES([GTEST_MAIN], [gtest_main], [], [AC_MSG_NOTICE([gtest_main not found, tests will not build])])
AC_ARG_ENABLE([oe-sdk],
AS_HELP_STRING([--enable-oe-sdk], [Link testcases absolutely against OE SDK so they can be ran within it.])
diff --git a/host-ipmid/ipmid-api.h b/host-ipmid/ipmid-api.h
index 5a2e81d..1bec851 100644
--- a/host-ipmid/ipmid-api.h
+++ b/host-ipmid/ipmid-api.h
@@ -93,6 +93,7 @@ enum ipmi_net_fns
NETFUN_STORAGE = 0x0a,
NETFUN_TRANSPORT = 0x0c,
NETFUN_GRPEXT = 0x2c,
+ NETFUN_OEM_GROUP = 0x2e,
NETFUN_NONE = 0x30,
NETFUN_OEM = 0x32,
NETFUN_IBM_OEM = 0x3A
diff --git a/host-ipmid/oemopenbmc.hpp b/host-ipmid/oemopenbmc.hpp
new file mode 100644
index 0000000..c3bfaa4
--- /dev/null
+++ b/host-ipmid/oemopenbmc.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "host-ipmid/ipmid-api.h"
+#include "host-ipmid/oemrouter.hpp"
+
+namespace oem
+{
+
+/*
+ * This is the OpenBMC IANA OEM Number
+ */
+constexpr Number obmcOemNumber = 49871;
+
+/*
+ * OpenBMC OEM Extension IPMI Command codes.
+ */
+enum Cmd
+{
+ gpioCmd = 1,
+ i2cCmd = 2,
+ flashCmd = 3,
+ fanManualCmd = 4,
+};
+
+} // namespace oem
diff --git a/host-ipmid/oemrouter.hpp b/host-ipmid/oemrouter.hpp
new file mode 100644
index 0000000..bc2b88c
--- /dev/null
+++ b/host-ipmid/oemrouter.hpp
@@ -0,0 +1,83 @@
+#pragma once
+
+#include <array>
+#include <cstdint>
+#include <functional>
+#include <vector>
+
+#include "host-ipmid/ipmid-api.h"
+
+namespace oem
+{
+constexpr size_t groupMagicSize = 3;
+
+using Group = std::array<uint8_t, groupMagicSize>;
+using Number = uint32_t; // smallest standard size >= 24.
+
+// Handler signature includes ipmi cmd to support wildcard cmd match.
+// Buffers and lengths exclude the OemGroup bytes in the IPMI message.
+// dataLen supplies length of reqBuf upon call, and should be set to the
+// length of replyBuf upon return - conventional in this code base.
+using Handler = std::function<ipmi_ret_t(
+ ipmi_cmd_t, // cmd byte
+ const uint8_t*, // reqBuf
+ uint8_t*, // replyBuf
+ size_t*)>; // dataLen
+
+/// Router Interface class.
+/// @brief Abstract Router Interface
+class Router
+{
+ public:
+ virtual ~Router() {}
+
+ /// Enable message routing to begin.
+ virtual void activate() = 0;
+
+ /// Register a handler for given OEMNumber & cmd.
+ /// Use IPMI_CMD_WILDCARD to catch any unregistered cmd
+ /// for the given OEMNumber.
+ ///
+ /// @param[in] oen - the OEM Number.
+ /// @param[in] cmd - the Command.
+ /// @param[in] handler - the handler to call given that OEN and
+ /// command.
+ virtual void registerHandler(Number oen, ipmi_cmd_t cmd,
+ Handler handler) = 0;
+};
+
+/// Expose mutable Router for configuration & activation.
+///
+/// @returns pointer to OEM Router to use.
+Router* mutableRouter();
+
+/// Convert a group to an OEN.
+///
+/// @param[in] oeg - request buffer for IPMI command.
+/// @return the OEN.
+constexpr Number toOemNumber(const uint8_t oeg[groupMagicSize])
+{
+ return (oeg[2] << 16) | (oeg[1] << 8) | oeg[0];
+}
+
+/// Given a Group convert to an OEN.
+///
+/// @param[in] oeg - OEM Group reference.
+/// @return the OEN.
+constexpr Number toOemNumber(const Group& oeg)
+{
+ return (oeg[2] << 16) | (oeg[1] << 8) | oeg[0];
+}
+
+/// Given an OEN, conver to the OEM Group.
+///
+/// @param[in] oen - the OEM Number.
+/// @return the OEM Group.
+constexpr Group toOemGroup(Number oen)
+{
+ return Group { static_cast<uint8_t>(oen),
+ static_cast<uint8_t>(oen >> 8),
+ static_cast<uint8_t>(oen >> 16) };
+}
+
+} // namespace oem
diff --git a/ipmid.cpp b/ipmid.cpp
index 3d7c663..f664488 100644
--- a/ipmid.cpp
+++ b/ipmid.cpp
@@ -27,6 +27,7 @@
#include <host-cmd-manager.hpp>
#include <host-ipmid/ipmid-host-cmd.hpp>
#include <timer.hpp>
+#include "host-ipmid/oemrouter.hpp"
using namespace phosphor::logging;
namespace sdbusRule = sdbusplus::bus::match::rules;
@@ -616,6 +617,9 @@ int main(int argc, char *argv[])
cmdManager = std::make_unique<phosphor::host::command::Manager>(
*sdbusp, events);
+ // Activate OemRouter.
+ oem::mutableRouter()->activate();
+
// Register all the handlers that provider implementation to IPMI commands.
ipmi_register_callback_handlers(HOST_IPMI_LIB_PATH);
diff --git a/oemrouter.cpp b/oemrouter.cpp
new file mode 100644
index 0000000..273a679
--- /dev/null
+++ b/oemrouter.cpp
@@ -0,0 +1,149 @@
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <utility>
+
+#include "host-ipmid/oemrouter.hpp"
+
+namespace oem
+{
+
+using Key = std::pair<Number, ipmi_cmd_t>;
+
+// Private implementation of OemRouter Interface.
+class RouterImpl : public Router
+{
+ public:
+ RouterImpl() {}
+
+ // Implement OemRouter Interface.
+ void activate() override;
+ void registerHandler(Number oen, ipmi_cmd_t cmd,
+ Handler handler) override;
+
+ // Actual message routing function.
+ ipmi_ret_t routeMsg(ipmi_cmd_t cmd, const uint8_t* reqBuf,
+ uint8_t* replyBuf, size_t* dataLen);
+
+ private:
+ std::map<Key, Handler> handlers;
+};
+
+// Static global instance for simplicity.
+static RouterImpl* globalRouterImpl;
+
+// TODO Refactor ipmid to avoid need for singleton here.
+Router* mutableRouter()
+{
+ if (!globalRouterImpl)
+ {
+ globalRouterImpl = new RouterImpl;
+ }
+ return globalRouterImpl;
+}
+
+ipmi_ret_t RouterImpl::routeMsg(ipmi_cmd_t cmd, const uint8_t* reqBuf,
+ uint8_t* replyBuf, size_t* dataLen)
+{
+ // Not entirely clear we can route reply without complete OEM group.
+ // TODO: consider adding a way to suppress malformed replies.
+ if (*dataLen < groupMagicSize)
+ {
+ fprintf(stderr, "NetFn:[0x2E], OEM:[%zu bytes?], Cmd:[%#04X]\n",
+ *dataLen, cmd);
+ (*dataLen) = 0;
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+
+ // Find registered handler or reject request.
+ auto number = toOemNumber(reqBuf);
+ auto cmdKey = std::make_pair(number, cmd);
+
+ auto iter = handlers.find(cmdKey);
+ if (iter == handlers.end())
+ {
+ auto wildKey = std::make_pair(number, IPMI_CMD_WILDCARD);
+ iter = handlers.find(wildKey);
+ if (iter == handlers.end())
+ {
+ fprintf(stderr, "No Registered handler for NetFn:[0x2E], "
+ "OEM:[%#08X], Cmd:[%#04X]\n", number, cmd);
+ *dataLen = groupMagicSize;
+ return IPMI_CC_INVALID;
+ }
+#ifdef __IPMI_DEBUG__
+ fprintf(stderr, "Wildcard NetFn:[0x2E], OEM:[%#08X], Cmd:[%#04X]\n",
+ number, cmd);
+#endif
+ }
+ else
+ {
+#ifdef __IPMI_DEBUG__
+ fprintf(stderr, "Match NetFn:[0x2E], OEM:[%#08X], Cmd:[%#04X]\n",
+ number, cmd);
+#endif
+ }
+
+ // Copy OEMGroup here, by analogy to IPMI CC code at netfn router;
+ // OemHandler should deal only with optional following data bytes.
+ std::memcpy(replyBuf, reqBuf, groupMagicSize);
+
+ size_t oemDataLen = *dataLen - groupMagicSize;
+ Handler& handler = iter->second;
+
+ auto rc = handler(cmd, reqBuf + groupMagicSize,
+ replyBuf + groupMagicSize, &oemDataLen);
+
+ // Add OEMGroup bytes to nominal reply.
+ *dataLen = oemDataLen + groupMagicSize;
+ return rc;
+}
+
+// Function suitable for use as ipmi_netfn_router() call-back.
+// Translates call-back pointer args to more specific types.
+ipmi_ret_t ipmi_oem_wildcard_handler(ipmi_netfn_t /* netfn */,
+ ipmi_cmd_t cmd, ipmi_request_t request,
+ ipmi_response_t response,
+ ipmi_data_len_t dataLen,
+ ipmi_context_t context)
+{
+ // View requests & responses as byte sequences.
+ const uint8_t* reqBuf = static_cast<uint8_t*>(request);
+ uint8_t* replyBuf = static_cast<uint8_t*>(response);
+
+ // View context as router object, defaulting nullptr to global object.
+ auto router = static_cast<RouterImpl*>(
+ context ? context : mutableRouter());
+
+ // Send message parameters to dispatcher.
+ return router->routeMsg(cmd, reqBuf, replyBuf, dataLen);
+}
+
+// Enable message routing to begin.
+void RouterImpl::activate()
+{
+ // Register netfn 0x2e OEM Group, any (wildcard) command.
+ printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",
+ NETFUN_OEM_GROUP, IPMI_CMD_WILDCARD);
+ ipmi_register_callback(NETFUN_OEM_GROUP, IPMI_CMD_WILDCARD, this,
+ ipmi_oem_wildcard_handler, PRIVILEGE_OEM);
+}
+
+void RouterImpl::registerHandler(Number oen, ipmi_cmd_t cmd,
+ Handler handler)
+{
+ auto cmdKey = std::make_pair(oen, cmd);
+ auto iter = handlers.find(cmdKey);
+ if (iter == handlers.end())
+ {
+ // Add handler if key not already taken.
+ handlers.emplace(cmdKey, handler);
+ }
+ else
+ {
+ fprintf(stderr, "ERROR : Duplicate registration for NetFn:[0x2E], "
+ "OEM:[%#08X], Cmd:[%#04X]\n", oen, cmd);
+ }
+}
+
+} // namespace oem
diff --git a/test/Makefile.am b/test/Makefile.am
index cb5640e..cd42b26 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,8 +1,10 @@
-AM_CPPFLAGS = -I$(top_srcdir) $(CODE_COVERAGE_CPPFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir) $(CODE_COVERAGE_CPPFLAGS) $(GTEST_CFLAGS)
AM_CFLAGS = $(CODE_COVERAGE_CFLAGS)
-check_PROGRAMS =
+AM_CXXFLAGS = $(GTEST_MAIN_CFLAGS) $(GTEST_CFLAGS)
+AM_LDFLAGS = $(GTEST_MAIN_LIBS) $(OESDK_TESTCASE_FLAGS)
# Run all 'check' test programs
+check_PROGRAMS =
TESTS = $(check_PROGRAMS)
# Build/add sample_unittest to test suite
@@ -14,3 +16,8 @@ sample_unittest_LDFLAGS = -lgtest_main -lgtest $(PTHREAD_LIBS) $(OESDK_TESTCASE_
sample_unittest_SOURCES = %reldir%/sample_unittest.cpp
sample_unittest_LDADD = $(top_builddir)/sample.o
check_PROGRAMS += %reldir%/sample_unittest
+
+# Build/add oemrouter_unittest to test suite
+check_PROGRAMS += oemrouter_unittest
+oemrouter_unittest_SOURCES = oemrouter_unittest.cpp
+oemrouter_unittest_LDADD = $(top_builddir)/oemrouter.o
diff --git a/test/oemrouter_unittest.cpp b/test/oemrouter_unittest.cpp
new file mode 100644
index 0000000..a5a79dd
--- /dev/null
+++ b/test/oemrouter_unittest.cpp
@@ -0,0 +1,175 @@
+#include "host-ipmid/ipmid-api.h"
+#include "host-ipmid/oemrouter.hpp"
+#include "sample.h"
+
+#include <cstring>
+#include <gtest/gtest.h>
+
+// Watch for correct singleton behavior.
+static oem::Router* singletonUnderTest;
+
+static ipmid_callback_t wildHandler;
+
+static ipmi_netfn_t lastNetFunction;
+
+// Fake ipmi_register_callback() for this test.
+void ipmi_register_callback(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+ ipmi_context_t context, ipmid_callback_t cb,
+ ipmi_cmd_privilege_t priv)
+{
+ EXPECT_EQ(NETFUN_OEM_GROUP, netfn);
+ EXPECT_EQ(IPMI_CMD_WILDCARD, cmd);
+ EXPECT_EQ(reinterpret_cast<void*>(singletonUnderTest), context);
+ EXPECT_EQ(PRIVILEGE_OEM, priv);
+ lastNetFunction = netfn;
+ wildHandler = cb;
+}
+
+namespace oem
+{
+
+namespace
+{
+void MakeRouter()
+{
+ if (!singletonUnderTest)
+ {
+ singletonUnderTest = mutableRouter();
+ }
+ ASSERT_EQ(singletonUnderTest, mutableRouter());
+}
+
+void ActivateRouter()
+{
+ MakeRouter();
+ singletonUnderTest->activate();
+ ASSERT_EQ(NETFUN_OEM_GROUP, lastNetFunction);
+}
+
+void RegisterWithRouter(Number oen, ipmi_cmd_t cmd, Handler cb)
+{
+ ActivateRouter();
+ singletonUnderTest->registerHandler(oen, cmd, cb);
+}
+
+uint8_t msgPlain[] = { 0x56, 0x34, 0x12 };
+uint8_t replyPlain[] = { 0x56, 0x34, 0x12, 0x31, 0x41 };
+uint8_t msgPlus2[] = { 0x67, 0x45, 0x23, 0x10, 0x20 };
+uint8_t msgBadOen[] = { 0x57, 0x34, 0x12 };
+
+void RegisterTwoWays(ipmi_cmd_t *nextCmd)
+{
+ Handler f = [](ipmi_cmd_t cmd, const uint8_t* reqBuf,
+ uint8_t* replyBuf, size_t* dataLen)
+ {
+ // Check inputs
+ EXPECT_EQ(0x78, cmd);
+ EXPECT_EQ(0, *dataLen); // Excludes OEN
+
+ // Generate reply.
+ *dataLen = 2;
+ std::memcpy(replyBuf, replyPlain + 3, *dataLen);
+ return 0;
+ };
+ RegisterWithRouter(0x123456, 0x78, f);
+
+ *nextCmd = IPMI_CMD_WILDCARD;
+ Handler g = [nextCmd](ipmi_cmd_t cmd, const uint8_t* reqBuf,
+ uint8_t* replyBuf, size_t* dataLen)
+ {
+ // Check inputs
+ EXPECT_EQ(*nextCmd, cmd);
+ EXPECT_EQ(2, *dataLen); // Excludes OEN
+ if (2 != *dataLen)
+ {
+ return 0xE0;
+ }
+ EXPECT_EQ(msgPlus2[3], reqBuf[0]);
+ EXPECT_EQ(msgPlus2[4], reqBuf[1]);
+
+ // Generate reply.
+ *dataLen = 0;
+ return 0;
+ };
+ RegisterWithRouter(0x234567, IPMI_CMD_WILDCARD, g);
+}
+} // namespace
+
+TEST(OemRouterTest, MakeRouterProducesConsistentSingleton) {
+ MakeRouter();
+}
+
+TEST(OemRouterTest, ActivateRouterSetsLastNetToOEMGROUP) {
+ lastNetFunction = 0;
+ ActivateRouter();
+}
+
+TEST(OemRouterTest, VerifiesSpecificCommandMatches) {
+ ipmi_cmd_t cmd;
+ uint8_t reply[256];
+ size_t dataLen;
+
+ RegisterTwoWays(&cmd);
+
+ dataLen = 3;
+ EXPECT_EQ(0,
+ wildHandler(NETFUN_OEM_GROUP, 0x78, msgPlain, reply,
+ &dataLen, nullptr));
+ EXPECT_EQ(5, dataLen);
+ EXPECT_EQ(replyPlain[0], reply[0]);
+ EXPECT_EQ(replyPlain[1], reply[1]);
+ EXPECT_EQ(replyPlain[2], reply[2]);
+ EXPECT_EQ(replyPlain[3], reply[3]);
+ EXPECT_EQ(replyPlain[4], reply[4]);
+}
+
+TEST(OemRouterTest, WildCardMatchesTwoRandomCodes) {
+ ipmi_cmd_t cmd;
+ uint8_t reply[256];
+ size_t dataLen;
+
+ RegisterTwoWays(&cmd);
+
+ // Check two random command codes.
+ dataLen = 5;
+ cmd = 0x89;
+ EXPECT_EQ(0,
+ wildHandler(NETFUN_OEM_GROUP, cmd, msgPlus2, reply,
+ &dataLen, nullptr));
+ EXPECT_EQ(3, dataLen);
+
+ dataLen = 5;
+ cmd = 0x67;
+ EXPECT_EQ(0,
+ wildHandler(NETFUN_OEM_GROUP, cmd, msgPlus2, reply,
+ &dataLen, nullptr));
+ EXPECT_EQ(3, dataLen);
+}
+
+TEST(OemRouterTest, CommandsAreRejectedIfInvalid) {
+ ipmi_cmd_t cmd;
+ uint8_t reply[256];
+ size_t dataLen;
+
+ RegisterTwoWays(&cmd);
+
+ // Message too short to include whole OEN?
+ dataLen = 2;
+ EXPECT_EQ(IPMI_CC_REQ_DATA_LEN_INVALID,
+ wildHandler(NETFUN_OEM_GROUP, 0x78, msgPlain, reply,
+ &dataLen, nullptr));
+
+ // Wrong specific command?
+ dataLen = 3;
+ EXPECT_EQ(IPMI_CC_INVALID,
+ wildHandler(NETFUN_OEM_GROUP, 0x89, msgPlain, reply,
+ &dataLen, nullptr));
+
+ // Wrong OEN?
+ dataLen = 3;
+ EXPECT_EQ(IPMI_CC_INVALID,
+ wildHandler(NETFUN_OEM_GROUP, 0x78, msgBadOen, reply,
+ &dataLen, nullptr));
+}
+
+} // namespace oem
OpenPOWER on IntegriCloud