From 240b186cf7fa639de698104d6853b73c81f4ec39 Mon Sep 17 00:00:00 2001 From: Vernon Mauery Date: Mon, 8 Oct 2018 12:05:16 -0700 Subject: ipmid: Rewrite ipmid to use the new architecture New architecture highlights: * The new registration detects handler type for argument unpacking. * Upon completion the response is automatically packed. * Handlers can make use of the new async/yield sdbusplus mechanism. * The queue exports a new dbus interface for method-based IPMI calls. * The legacy handler registration is still supported for now. * The legacy dbus interface is still supported for now. Change-Id: Iae8342d9771ccebd3a0834e35597c14be4cc39cf Signed-off-by: Vernon Mauery --- Makefile.am | 10 +- include/ipmid-host/cmd.hpp | 4 - ipmid-new.cpp | 459 +++++++++++++++++++++++++++++++++++++++++++++ sensorhandler.cpp | 10 - systemintfcmds.cpp | 23 ++- 5 files changed, 482 insertions(+), 24 deletions(-) create mode 100644 ipmid-new.cpp diff --git a/Makefile.am b/Makefile.am index 890b9f6..ce96af7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,11 +10,11 @@ sbin_PROGRAMS = \ ipmid ipmid_SOURCES = \ - ipmid.cpp \ + ipmid-new.cpp \ settings.cpp \ host-cmd-manager.cpp \ - utils.cpp \ - oemrouter.cpp + utils.cpp + nodist_ipmid_SOURCES = ipmiwhitelist.cpp libipmi20_BUILT_LIST = \ @@ -46,6 +46,7 @@ COMMON_CXX = \ ipmid_CXXFLAGS = $(COMMON_CXX) ipmid_LDADD = \ libipmid/libipmid.la \ + user_channel/libchannellayer.la \ libipmid-host/libipmid-host.la ipmid_LDFLAGS = \ $(SYSTEMD_LIBS) \ @@ -54,8 +55,8 @@ ipmid_LDFLAGS = \ $(PHOSPHOR_LOGGING_LIBS) \ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \ $(CRYPTO_LIBS) \ + -lboost_coroutine \ -lstdc++fs \ - -pthread \ -export-dynamic # TODO: Rather than use -export-dynamic, we should use -export-symbol to have a @@ -116,6 +117,7 @@ libipmi20_la_LDFLAGS = \ $(PHOSPHOR_LOGGING_LIBS) \ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \ -lstdc++fs \ + -lboost_coroutine \ -version-info 0:0:0 -shared libipmi20_la_CXXFLAGS = $(COMMON_CXX) diff --git a/include/ipmid-host/cmd.hpp b/include/ipmid-host/cmd.hpp index adea960..e46c485 100644 --- a/include/ipmid-host/cmd.hpp +++ b/include/ipmid-host/cmd.hpp @@ -2,9 +2,5 @@ #include #include -// Need this to use new sdbusplus compatible interfaces -using sdbusPtr = std::unique_ptr; -extern sdbusPtr& ipmid_get_sdbus_plus_handler(); - // Global Host Bound Command manager extern void ipmid_send_cmd_to_host(phosphor::host::command::CommandHandler&&); diff --git a/ipmid-new.cpp b/ipmid-new.cpp new file mode 100644 index 0000000..fb57666 --- /dev/null +++ b/ipmid-new.cpp @@ -0,0 +1,459 @@ +/** + * Copyright © 2018 Intel Corporation + * + * 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 "settings.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __has_include() +#include +#elif __has_include() +#include +namespace std +{ +// splice experimental::filesystem into std +namespace filesystem = std::experimental::filesystem; +} // namespace std +#else +#error filesystem not available +#endif + +namespace fs = std::filesystem; + +using namespace phosphor::logging; + +// Global timer for network changes +std::unique_ptr networkTimer = nullptr; + +// IPMI Spec, shared Reservation ID. +static unsigned short selReservationID = 0xFFFF; +static bool selReservationValid = false; + +unsigned short reserveSel(void) +{ + // IPMI spec, Reservation ID, the value simply increases against each + // execution of the Reserve SEL command. + if (++selReservationID == 0) + { + selReservationID = 1; + } + selReservationValid = true; + return selReservationID; +} + +bool checkSELReservation(unsigned short id) +{ + return (selReservationValid && selReservationID == id); +} + +void cancelSELReservation(void) +{ + selReservationValid = false; +} + +EInterfaceIndex getInterfaceIndex(void) +{ + return interfaceKCS; +} + +sd_bus* bus; +sd_event* events = nullptr; +sd_event* ipmid_get_sd_event_connection(void) +{ + return events; +} +sd_bus* ipmid_get_sd_bus_connection(void) +{ + return bus; +} + +namespace ipmi +{ + +static inline unsigned int makeCmdKey(unsigned int cluster, unsigned int cmd) +{ + return (cluster << 8) | cmd; +} + +using HandlerTuple = std::tuple; + +/* map to handle standard registered commands */ +static std::unordered_map + handlerMap; + +namespace impl +{ +/* common function to register all standard IPMI handlers */ +bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv, + HandlerBase::ptr handler) +{ + // check for valid NetFn: even; 00-0Ch, 30-3Eh + if (netFn & 1 || (netFn > netFnTransport && netFn < netFnGroup) || + netFn > netFnOemEight) + { + return false; + } + + // create key and value for this handler + unsigned int netFnCmd = makeCmdKey(netFn, cmd); + HandlerTuple item(prio, priv, handler); + + // consult the handler map and look for a match + auto& mapCmd = handlerMap[netFnCmd]; + if (!std::get(mapCmd) || std::get(mapCmd) <= prio) + { + mapCmd = item; + return true; + } + return false; +} + +} // namespace impl + +message::Response::ptr executeIpmiCommandCommon( + std::unordered_map& handlers, + unsigned int keyCommon, message::Request::ptr request) +{ + Cmd cmd = request->ctx->cmd; + unsigned int key = makeCmdKey(keyCommon, cmd); + auto cmdIter = handlers.find(key); + if (cmdIter != handlers.end()) + { + HandlerTuple& chosen = cmdIter->second; + if (request->ctx->priv < std::get(chosen)) + { + return errorResponse(request, ccInsufficientPrivilege); + } + return std::get(chosen)->call(request); + } + else + { + unsigned int wildcard = makeCmdKey(keyCommon, cmdWildcard); + cmdIter = handlers.find(wildcard); + if (cmdIter != handlers.end()) + { + HandlerTuple& chosen = cmdIter->second; + if (request->ctx->priv < std::get(chosen)) + { + return errorResponse(request, ccInsufficientPrivilege); + } + return std::get(chosen)->call(request); + } + } + return errorResponse(request, ccInvalidCommand); +} + +message::Response::ptr executeIpmiCommand(message::Request::ptr request) +{ + NetFn netFn = request->ctx->netFn; + // TODO: handler OEM and group OEM commands here + return executeIpmiCommandCommon(handlerMap, netFn, request); +} + +/* called from sdbus async server context */ +auto executionEntry(boost::asio::yield_context yield, NetFn netFn, uint8_t lun, + Cmd cmd, std::vector& data, + std::map& options) +{ + auto ctx = std::make_shared(netFn, cmd, 0, 0, + ipmi::Privilege::Admin, &yield); + auto request = std::make_shared( + ctx, std::forward>(data)); + message::Response::ptr response = executeIpmiCommand(request); + + // Responses in IPMI require a bit set. So there ya go... + netFn |= 0x01; + return std::make_tuple(netFn, lun, cmd, response->cc, + response->payload.raw); +} + +/** @struct IpmiProvider + * + * RAII wrapper for dlopen so that dlclose gets called on exit + */ +struct IpmiProvider +{ + public: + /** @brief address of the opened library */ + void* addr; + std::string name; + + IpmiProvider() = delete; + IpmiProvider(const IpmiProvider&) = delete; + IpmiProvider& operator=(const IpmiProvider&) = delete; + IpmiProvider(IpmiProvider&&) = delete; + IpmiProvider& operator=(IpmiProvider&&) = delete; + + /** @brief dlopen a shared object file by path + * @param[in] filename - path of shared object to open + */ + explicit IpmiProvider(const char* fname) : addr(nullptr), name(fname) + { + log("Open IPMI provider library", + entry("PROVIDER=%s", name.c_str())); + try + { + addr = dlopen(name.c_str(), RTLD_NOW); + } + catch (std::exception& e) + { + log("ERROR opening IPMI provider", + entry("PROVIDER=%s", name.c_str()), + entry("ERROR=%s", e.what())); + } + catch (...) + { + std::exception_ptr eptr = std::current_exception(); + try + { + std::rethrow_exception(eptr); + } + catch (std::exception& e) + { + log("ERROR opening IPMI provider", + entry("PROVIDER=%s", name.c_str()), + entry("ERROR=%s", e.what())); + } + } + if (!isOpen()) + { + log("ERROR opening IPMI provider", + entry("PROVIDER=%s", name.c_str()), + entry("ERROR=%s", dlerror())); + } + } + + ~IpmiProvider() + { + if (isOpen()) + { + dlclose(addr); + } + } + bool isOpen() const + { + return (nullptr != addr); + } +}; + +// Plugin libraries need to contain .so either at the end or in the middle +constexpr const char ipmiPluginExtn[] = ".so"; + +/* return a list of self-closing library handles */ +std::forward_list loadProviders(const fs::path& ipmiLibsPath) +{ + std::vector libs; + for (const auto& libPath : fs::directory_iterator(ipmiLibsPath)) + { + fs::path fname = libPath.path(); + while (fname.has_extension()) + { + fs::path extn = fname.extension(); + if (extn == ipmiPluginExtn) + { + libs.push_back(libPath.path()); + break; + } + fname.replace_extension(); + } + } + std::sort(libs.begin(), libs.end()); + + std::forward_list handles; + for (auto& lib : libs) + { +#ifdef __IPMI_DEBUG__ + log("Registering handler", + entry("HANDLER=%s", lib.c_str())); +#endif + handles.emplace_front(lib.c_str()); + } + return handles; +} + +} // namespace ipmi + +static std::shared_ptr io; +std::shared_ptr getIoService() +{ + return io; +} + +static std::shared_ptr sdbusp; +std::shared_ptr getSdBus() +{ + return sdbusp; +} + +#ifdef ALLOW_DEPRECATED_API +/* legacy registration */ +void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd, + ipmi_context_t context, ipmid_callback_t handler, + ipmi_cmd_privilege_t priv) +{ + auto h = ipmi::makeLegacyHandler(handler); + // translate priv from deprecated enum to current + ipmi::Privilege realPriv; + switch (priv) + { + case PRIVILEGE_CALLBACK: + realPriv = ipmi::Privilege::Callback; + break; + case PRIVILEGE_USER: + realPriv = ipmi::Privilege::User; + break; + case PRIVILEGE_OPERATOR: + realPriv = ipmi::Privilege::Operator; + break; + case PRIVILEGE_ADMIN: + realPriv = ipmi::Privilege::Admin; + break; + case PRIVILEGE_OEM: + realPriv = ipmi::Privilege::Oem; + break; + case SYSTEM_INTERFACE: + realPriv = ipmi::Privilege::Admin; + break; + default: + realPriv = ipmi::Privilege::Admin; + break; + } + ipmi::impl::registerHandler(ipmi::prioOpenBmcBase, netFn, cmd, realPriv, h); +} + +/* legacy alternative to executionEntry */ +void handleLegacyIpmiCommand(sdbusplus::message::message& m) +{ + unsigned char seq, netFn, lun, cmd; + std::vector data; + + m.read(seq, netFn, lun, cmd, data); + + auto ctx = std::make_shared(netFn, cmd, 0, 0, + ipmi::Privilege::Admin); + auto request = std::make_shared( + ctx, std::forward>(data)); + ipmi::message::Response::ptr response = ipmi::executeIpmiCommand(request); + + // Responses in IPMI require a bit set. So there ya go... + netFn |= 0x01; + + const char *dest, *path; + constexpr const char* DBUS_INTF = "org.openbmc.HostIpmi"; + + dest = m.get_sender(); + path = m.get_path(); + sdbusp->async_method_call([](boost::system::error_code ec) {}, dest, path, + DBUS_INTF, "sendMessage", seq, netFn, lun, cmd, + response->cc, response->payload.raw); +} + +#endif /* ALLOW_DEPRECATED_API */ + +// Calls host command manager to do the right thing for the command +using CommandHandler = phosphor::host::command::CommandHandler; +std::unique_ptr cmdManager; +void ipmid_send_cmd_to_host(CommandHandler&& cmd) +{ + return cmdManager->execute(std::move(cmd)); +} + +std::unique_ptr& ipmid_get_host_cmd_manager() +{ + return cmdManager; +} + +int main(int argc, char* argv[]) +{ + // Connect to system bus + io = std::make_shared(); + if (argc > 1 && std::string(argv[1]) == "-session") + { + sd_bus_default_user(&bus); + } + else + { + sd_bus_default_system(&bus); + } + sdbusp = std::make_shared(*io, bus); + sdbusp->request_name("xyz.openbmc_project.Ipmi.Host"); + + // TODO: Hack to keep the sdEvents running.... Not sure why the sd_event + // queue stops running if we don't have a timer that keeps re-arming + phosphor::Timer t2([]() { ; }); + t2.start(std::chrono::microseconds(500000), true); + + // TODO: Remove all vestiges of sd_event from phosphor-host-ipmid + // until that is done, add the sd_event wrapper to the io object + sdbusplus::asio::sd_event_wrapper sdEvents(*io); + + cmdManager = std::make_unique(*sdbusp); + + // Register all command providers and filters + auto handles = ipmi::loadProviders(HOST_IPMI_LIB_PATH); + + // Add bindings for inbound IPMI requests + auto server = sdbusplus::asio::object_server(sdbusp); + auto iface = server.add_interface("/xyz/openbmc_project/Ipmi", + "xyz.openbmc_project.Ipmi.Server"); + iface->register_method("execute", ipmi::executionEntry); + iface->initialize(); + +#ifdef ALLOW_DEPRECATED_API + // listen on deprecated signal interface for kcs/bt commands + constexpr const char* FILTER = "type='signal',interface='org.openbmc." + "HostIpmi',member='ReceivedMessage'"; + sdbusplus::bus::match::match oldIpmiInterface(*sdbusp, FILTER, + handleLegacyIpmiCommand); +#endif /* ALLOW_DEPRECATED_API */ + + io->run(); + + // This avoids a warning about unused variables + handles.clear(); + return 0; +} diff --git a/sensorhandler.cpp b/sensorhandler.cpp index 9d0364d..051c4fd 100644 --- a/sensorhandler.cpp +++ b/sensorhandler.cpp @@ -139,11 +139,6 @@ int set_sensor_dbus_state_s(uint8_t number, const char* method, sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus_message* m = NULL; - std::fprintf(ipmidbus, - "Attempting to set a dbus Variant Sensor 0x%02x via %s with a " - "value of %s\n", - number, method, value); - r = find_openbmc_path(number, &a); if (r < 0) @@ -190,11 +185,6 @@ int set_sensor_dbus_state_y(uint8_t number, const char* method, sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus_message* m = NULL; - std::fprintf(ipmidbus, - "Attempting to set a dbus Variant Sensor 0x%02x via %s with a " - "value of 0x%02x\n", - number, method, value); - r = find_openbmc_path(number, &a); if (r < 0) diff --git a/systemintfcmds.cpp b/systemintfcmds.cpp index d044030..564cc38 100644 --- a/systemintfcmds.cpp +++ b/systemintfcmds.cpp @@ -5,10 +5,10 @@ #include "host-cmd-manager.hpp" #include "host-interface.hpp" -#include - #include #include +#include +#include void register_netfn_app_functions() __attribute__((constructor)); @@ -109,6 +109,8 @@ std::unique_ptr host __attribute__((init_priority(101))); std::unique_ptr objManager __attribute__((init_priority(101))); +std::unique_ptr sdbusp + __attribute__((init_priority(101))); } // namespace #include @@ -130,14 +132,23 @@ void register_netfn_app_functions() // Create new xyz.openbmc_project.host object on the bus auto objPath = std::string{CONTROL_HOST_OBJ_MGR} + '/' + HOST_NAME + '0'; + // Create a new sdbus connection so it can have a well-known name + sd_bus* bus = nullptr; + sd_bus_open_system(&bus); + if (!bus) + { + return; + } + auto io = getIoService(); + sdbusp = std::make_unique(*io, bus); + // Add sdbusplus ObjectManager. - auto& sdbusPlusHandler = ipmid_get_sdbus_plus_handler(); objManager = std::make_unique( - *sdbusPlusHandler, CONTROL_HOST_OBJ_MGR); + *sdbusp, CONTROL_HOST_OBJ_MGR); - host = std::make_unique(*sdbusPlusHandler, + host = std::make_unique(*sdbusp, objPath.c_str()); - sdbusPlusHandler->request_name(CONTROL_HOST_BUSNAME); + sdbusp->request_name(CONTROL_HOST_BUSNAME); return; } -- cgit v1.2.1