summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVernon Mauery <vernon.mauery@linux.intel.com>2018-04-03 13:34:05 -0700
committerGunnar Mills <gmills@us.ibm.com>2018-06-21 19:45:38 +0000
commitd981e4ffa57d1e474c4d36ea8d09d1e4bbfa13e5 (patch)
tree458d21b6bf703359a5fe17a8f1d98886fc19f740
parent773fee0509415ad7522b35f4133779e1e30e5336 (diff)
downloadopenbmc-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.md239
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)
+
+
OpenPOWER on IntegriCloud