diff options
author | Vernon Mauery <vernon.mauery@linux.intel.com> | 2018-04-03 13:34:05 -0700 |
---|---|---|
committer | Gunnar Mills <gmills@us.ibm.com> | 2018-06-21 19:45:38 +0000 |
commit | d981e4ffa57d1e474c4d36ea8d09d1e4bbfa13e5 (patch) | |
tree | 458d21b6bf703359a5fe17a8f1d98886fc19f740 | |
parent | 773fee0509415ad7522b35f4133779e1e30e5336 (diff) | |
download | openbmc-docs-d981e4ffa57d1e474c4d36ea8d09d1e4bbfa13e5.tar.gz openbmc-docs-d981e4ffa57d1e474c4d36ea8d09d1e4bbfa13e5.zip |
Add documentation for the future of ipmi architecture
This documents the radical change for the ipmi daemon/handler
architecture, moving it from a c-like callback type un-safe mechanism to
something that looks and feels like c++ with compiler-generated
serialization and deserialization.
Change-Id: I9edf60d8f4fa56846f84ee639845d4d40f85ca5e
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
-rw-r--r-- | ipmi-architecture.md | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/ipmi-architecture.md b/ipmi-architecture.md new file mode 100644 index 0000000..fa33bcb --- /dev/null +++ b/ipmi-architecture.md @@ -0,0 +1,239 @@ +# IPMI Architecture + +IPMI is a hard problem to solve. New bits bolted onto legacy stuff makes for +quite the Frankensteinian monster. If we break it apart again and look at each +piece, maybe we can sleep with fewer nightmares. + + +## High-Level Overview + +IPMI is all about commands and responses. Channels provide a mechanism for +transporting the data, each with a slightly different protocol and transport +layer, but ultimately, the highest level data is a raw IPMI command consisting +of a NetFn/LUN, Command, and optional data. Each response is likewise a +Completion Code and optional data. So the first step is to break apart +channels and the IPMI queue. + + +``` +___________ ___________________ +| KCS/BT | | | +| Channel | <------> | | +----------/ | IPMI Daemon | +----------- | (ipmid) | +| RMCP+ | | | +| Channel | <------> | | +----------/ ------------------/ +``` + + +The IPMI messages that get passed to and from the IPMI daemon (ipmid) are +basically the equivalent of ipmitool's "raw" commands with a little more +information about the message. + +```less +Message Data: + byte:userID - IPMI user ID of caller (0 for session-less channels) + enum:privilege - ADMIN, USER, OPERATOR, CALLBACK; will be less than or + equal to the privilege of the user and less than or equal to the max + privilege of this channel + enum:channel - what channel the request came in on (LAN0, LAN1, KCS/BT, + IPMB0, etc.), also used to route the response back to the caller. + integer:msgID - identifier for the message to match with the response + byte:LUN - LUN from netFn/LUN pair (0-3, as per the IPMI spec) + byte:netFn - netFn from netFn/LUN pair (as per the IPMI spec) + byte:cmd - IPMI command ID (as per the IPMI spec) + array<byte>:data - optional command data (as per the IPMI spec) +``` + +```less +Response Data: + enum:channel - what channel the request came in on + integer:msgID - what request this response matches + byte:CC - IPMI completion code + array<byte>:data - optional response data +``` + +The next part is to provide a higher-level, strongly-typed, modern C++ +mechanism for registering handlers. Each handler will specify exactly what +arguments are needed for the command and what types will be returned in the +response. This way, the ipmid queue can unpack requests and pack responses in a +safe manner. + +To be able to operate in a manner like the current IPMI provider system works, +the registration mechanism will need to be able to either be mostly header-only +or otherwise runtime linkable so that external provider libraries can be used +to add IPMI commands. + + +## **Details and Implementation** + +For session-less channels (like BT, KCS, and IPMB), the only privilege check +will be to see that the requested privilege is less than or equal to the +channel's maximum privilege. If the channel has a session and authenticates +users, the privilege must be less than or equal to the channel's maximum +privilege and the user's maximum privilege. + +Ipmid takes the LUN/netFN/Cmd tuple and looks up the corresponding handler +function. If the requested privilege is less than or equal to the required +privilege for the given registered command, the request may proceed. If any of +these checks fail, ipmid returns with _Insufficient Privilege_. + +At this point, the IPMI command is run through the filter hooks. The default +hook is ACCEPT, where the command just passes onto the execution phase. But +OEMs and providers can register hooks that would ultimately block IPMI commands +from executing, much like the IPMI 2.0 Spec's Firmware Firewall. The hook would +be passed in the context of the IPMI call and the raw content of the call and +has the opportunity to return any valid IPMI completion code. Any non-zero +completion code would prevent the command from executing and would be returned +to the caller. + +The next phase is parameter unpacking and validation. This is done by +compiler-generated code with variadic templates at handler registration time. +The registration function is a templated function that allows any type of +handler to be passed in so that the types of the handler can be extracted and +unpacked. + +This can be done with something along these lines: + +```cpp +class ipmiQueue { + template <typename MessageHandler, typename... InputArgs> + auto register_ipmi_handler( + const std::vector<enum ipmiChannel>& channelList, + uint8_t lun, uint8_t netFn, uint8_t cmd, + MessageHandler handler) { + ... + } + template <typename MessageHandler, typename... InputArgs> + auto register_ipmi_handler_async( + const std::vector<enum ipmiChannel>& channelList, + uint8_t lun, uint8_t netFn, uint8_t cmd, + MessageHandler handler) { + ... + } + template <typename MessageHandler, typename... ReplyArgs> + auto async_reply(ipmi::handlerContext&, ReplyArgs) { + ... + } +}; + ... + namespace ipmi { + constexpr uint8_t appNetFn = 6; + class handlerContext { + enum ipmiChannel channel; + uint32_t msgId; + uint8_t userId; + enum ipmiPriv privilege; + }; + namespace app { + constexpr uint8_t setUserAccessCmd = 0x43; + } + } + +std::tuple<ipmi::compCode> setUserAccess( + ipmi::handlerContext& context, + uint1_t changeBit, // one bit integer type + uint1_t callbackRestricted, + uint1_t linkAuth, + uint1_t ipmiEnable, + uint4_t channelNumber, + uint2_t reserved1, + uint6_t userId, + uint4_t reserved2, + uint4_t privLimit, + std::optional<uint4_t> reserved3, + std::optional<uint4_t> userSessionLimit) { + ... + return std::tuple<ipmi::compCodeNormal>; +} + +ipmi::register_ipmi_handler(ipmi::lun0, ipmi::appNetFn, + ipmi::app::setUserAccessCmd, + setUserAccess); +void getUserAccess( + ipmi::handlerContext& context, + uint4_t reserved1, + uint4_t channelNumber, + uint2_t reserved2, + uint6_t userId) { + ... + auto reply = std::make_shared<std::tuple<ipmi::compCode, + uint2_t, uint6_t, uint2_t, uint6_t, uint2_t, uint6_t, uint1_t, + uint1_t, uint4_t>>(); + async_call([&]() { + std::get<0>(*reply) = ipmi::compCodeNormal; + std::get<2>(*reply) = ...; + ipmi::async_reply(context, *reply); + }); + ... + } + +ipmi::register_ipmi_handler_async(ipmi::lun0, ipmi::appNetFn, + ipmi::app::setUserAccessCmd, + setUserAccess); +``` + + +Ideally, we would have support for asynchronous handling of IPMI calls. This +means that the queue could have multiple in-flight calls that are waiting on +another D-Bus call to return. With asynchronous calls, this will not block the +rest of the queue's operation, allowing for maximum throughput and minimum +delay. Synchronous calls would emit warnings if they hold up the queue for too +long. If it is possible to do, it would be nice to abstract the D-Bus interface +call interface so that it could put off returning the result to a function and +handle other stuff while waiting. Then if the IPMI method only had D-Bus calls, +it could be written in a synchronous method but still allow the rest of the +queue to behave as if it was written as an asynchronous callback. + +Passing the reply tuple in as a shared pointer allows for multiple levels of +nested lambdas so that the owner is never destroyed and the lifetime of the +object is preserved. This is helpful if multiple D-Bus calls need to be made to +gather information. Alternately, it might only need to be generated in the last +stage when something of value is generated. Either way, the tuple is ultimately +passed into the templated async_reply function that packs the parameters back +into a vector to send back to the requester. The context is guaranteed to be +valid until either a synchronous call returns or async_reply is called. + +Using templates, it is possible to extract the return type and argument types +of the handlers and use that to unpack (and validate) the arguments from the +incoming request and then pack the result back into a vector of bytes to send +back to the caller. The deserializer will keep track of the number of bits it +has unpacked and then compare it with the total number of bits that the method +is requesting. In the example, we are assuming that the non-full-byte integers +are packed bits in the message in most-significant-bit first order (same order +the specification describes them in). Optional arguments can be used easily +with C++17's std::optional (or using boost::optional for earlier C++). Actually +calling the handler with the extracted tuple of arguments is easy with C++17's +std::apply (or can be written by hand if necessary). The moral of the story +here is that we should use C++17 since it is available with Yocto 2.4. + +For multi-byte parameters, endianness matters, so we should define some types +that can denote that: be_int32_t be_uint32_t, le_int32_t, le_uint32_t. +Alternately, we could only specify big-endian variants because most of the IPMI +spec uses little-endian representations. + +To start with, we can implement the templated registration scheme, but still +allow for a legacy registration method so that all the currently implemented +IPMI handlers can still work until they have been rewritten to use the new +mechanism. When all the current commands have been rewritten, we can remove the +legacy interface. All commands registering with the legacy interface will get +logged with a message saying that interface is deprecated. + +Things that would be nice to have are as follows: + + + +* nested types for arguments: e.g., std::array<std::tuple<uint1_t, uint1_t, + uint2_t, uint4_t>, 4> +* a nested callback mechanism (the one that comes to mind is set/get lan + parameters) where the handler is really ultimately split into subhandlers + with different trailing parameters by examining the first few bytes. In + this case, you read a few common bytes and then need to re-interpret the + large trailing buffer. If we can provide the message parser to the + handlers, then they can re-parse the big buffer using compiler-generated + code rather than re-writing their own sub-parser. +* C++17 (as noted above for std::apply and std::optional (and possibly other + shiny goodness) + + |