From 4a58985ce0cfb24bc0eb8678a4aa9a0ac42ba524 Mon Sep 17 00:00:00 2001 From: Peter Hanson Date: Wed, 7 Jun 2017 17:40:45 -0700 Subject: 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 Signed-off-by: Patrick Venture --- Makefile.am | 7 +- configure.ac | 5 +- host-ipmid/ipmid-api.h | 1 + host-ipmid/oemopenbmc.hpp | 25 +++++++ host-ipmid/oemrouter.hpp | 83 +++++++++++++++++++++ ipmid.cpp | 4 + oemrouter.cpp | 149 +++++++++++++++++++++++++++++++++++++ test/Makefile.am | 11 ++- test/oemrouter_unittest.cpp | 175 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 453 insertions(+), 7 deletions(-) create mode 100644 host-ipmid/oemopenbmc.hpp create mode 100644 host-ipmid/oemrouter.hpp create mode 100644 oemrouter.cpp create mode 100644 test/oemrouter_unittest.cpp 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 +#include +#include +#include + +#include "host-ipmid/ipmid-api.h" + +namespace oem +{ +constexpr size_t groupMagicSize = 3; + +using Group = std::array; +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; // 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(oen), + static_cast(oen >> 8), + static_cast(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 #include #include +#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( *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 +#include +#include +#include + +#include "host-ipmid/oemrouter.hpp" + +namespace oem +{ + +using Key = std::pair; + +// 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 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(request); + uint8_t* replyBuf = static_cast(response); + + // View context as router object, defaulting nullptr to global object. + auto router = static_cast( + 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 +#include + +// 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(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 -- cgit v1.2.1