xref: /openbmc/docs/architecture/ipmi-architecture.md (revision f4febd002df578bad816239b70950f84ea4567e8)
1fb8d806aSGunnar Mills# IPMI Architecture
2fb8d806aSGunnar Mills
3*f4febd00SPatrick WilliamsIPMI is made up of commands and channels. Commands are provided by providers and
4*f4febd00SPatrick Williamschannels are processes called bridges. Commands are received from external
5fb8d806aSGunnar Millssources (system admin users) via the bridges and routed to the providers'
6fb8d806aSGunnar Millscommand handlers via the main IPMI daemon.
7fb8d806aSGunnar Mills
8fb8d806aSGunnar Mills## High-Level Overview
9fb8d806aSGunnar Mills
10fb8d806aSGunnar MillsIPMI is all about commands and responses. Channels provide a mechanism for
11fb8d806aSGunnar Millstransporting the data, each with a slightly different protocol and transport
12fb8d806aSGunnar Millslayer, but ultimately, the highest level data is a raw IPMI command consisting
13fb8d806aSGunnar Millsof a NetFn/LUN, Command, and optional data. Each response is likewise a
14*f4febd00SPatrick WilliamsCompletion Code and optional data. So the first step is to break apart channels
15*f4febd00SPatrick Williamsand the IPMI queue.
16fb8d806aSGunnar Mills
17fb8d806aSGunnar Mills```
18fb8d806aSGunnar Mills                                                     /------------------\
19fb8d806aSGunnar Mills    /----------------------------\                   |                  |
20fb8d806aSGunnar Mills    |      KCS/BT - Host         | <-All IPMI cmds-> |                  |
21fb8d806aSGunnar Mills    |                            |                   |                  |
22fb8d806aSGunnar Mills    \----------------------------/                   |   IPMI Daemon    |
23fb8d806aSGunnar Mills                                                     |    (ipmid)       |
24fb8d806aSGunnar Mills                                                     |                  |
25fb8d806aSGunnar Mills   /-----------------------------\                   |                  |
26fb8d806aSGunnar Mills   |            LAN - RMCP+      |                   |                  |
27fb8d806aSGunnar Mills   | /--------------------------\|                   |                  |
28fb8d806aSGunnar Mills   | |*Process the Session and  || <-All IPMI cmds-> |                  |
29fb8d806aSGunnar Mills   | | SOL commands.            ||  except session   |                  |
30fb8d806aSGunnar Mills   | |*Create Session Objs      ||  and SOL cmds     |                  |
31fb8d806aSGunnar Mills   | \--------------------------/|                   |                  |
32fb8d806aSGunnar Mills   \-----------------------------/                   \------------------/
33fb8d806aSGunnar Mills                :                                             ^     ^
34fb8d806aSGunnar Mills                :                                             |     |
35fb8d806aSGunnar Mills                :                                             |     |
36fb8d806aSGunnar Mills     /-------------------------\                              |     |
37fb8d806aSGunnar Mills     | Active session/SOL Objs | <---------Query the session-/      |
38fb8d806aSGunnar Mills     | - Properties            |           and SOL data via Dbus    |
39fb8d806aSGunnar Mills     \-------------------------/                                    |
40fb8d806aSGunnar Mills                                                                    V
41fb8d806aSGunnar Mills                                                     ---------------------\
42fb8d806aSGunnar Mills                                                     | D-Bus services     |
43fb8d806aSGunnar Mills                                                     | ------------------ |
44fb8d806aSGunnar Mills                                                     | Do work on behalf  |
45fb8d806aSGunnar Mills                                                     | of IPMI commands   |
46fb8d806aSGunnar Mills                                                     ---------------------/
47fb8d806aSGunnar Mills```
48fb8d806aSGunnar Mills
49fb8d806aSGunnar MillsThe IPMI messages that get passed to and from the IPMI daemon (ipmid) are
50fb8d806aSGunnar Millsbasically the equivalent of ipmitool's "raw" commands with a little more
51fb8d806aSGunnar Millsinformation about the message.
52fb8d806aSGunnar Mills
53fb8d806aSGunnar Mills```less
54fb8d806aSGunnar MillsMessage Data:
55fb8d806aSGunnar Mills  byte:LUN - LUN from netFn/LUN pair (0-3, as per the IPMI spec)
56fb8d806aSGunnar Mills  byte:netFn - netFn from netFn/LUN pair (as per the IPMI spec)
57fb8d806aSGunnar Mills  byte:cmd - IPMI command ID (as per the IPMI spec)
58fb8d806aSGunnar Mills  array<byte>:data - optional command data (as per the IPMI spec)
59fb8d806aSGunnar Mills  dict<string:variant>:options - optional additional meta-data
60fb8d806aSGunnar Mills    "userId":int - IPMI user ID of caller (0 for session-less channels)
61fb8d806aSGunnar Mills    "privilege": enum:privilege - ADMIN, USER, OPERATOR, CALLBACK;
62fb8d806aSGunnar Mills      must be less than or equal to the privilege of the user and less
63fb8d806aSGunnar Mills      than or equal to the max privilege of this channel
64fb8d806aSGunnar Mills```
65fb8d806aSGunnar Mills
66fb8d806aSGunnar Mills```less
67fb8d806aSGunnar MillsResponse Data:
68fb8d806aSGunnar Mills  byte:CC - IPMI completion code
69fb8d806aSGunnar Mills  array<byte>:data - optional response data
70fb8d806aSGunnar Mills```
71fb8d806aSGunnar Mills
72*f4febd00SPatrick WilliamsA channel bridge, upon receiving a new IPMI command, will extract the necessary
73*f4febd00SPatrick Williamsfields from whatever transport format (RMCP+, IPMB, KCS, etc.) For session-based
74*f4febd00SPatrick Williamschannels (RMCP+) the bridge is responsible for establishing the session with
75*f4febd00SPatrick Williamscredentials and determining the maximum privilege available for this session.
76*f4febd00SPatrick WilliamsThe bridge then takes the extracted command, data, and possibly user and
77*f4febd00SPatrick Williamsprivilge information, and encodes them in a D-Bus method call to send to the
78*f4febd00SPatrick WilliamsIPMI daemon, ipmid. The daemon will take the message, and attempt to find an
79*f4febd00SPatrick Williamsappropriate handler in its handler tables. If a handler is found, it will
80*f4febd00SPatrick Williamsattempt to extract the required parameters for the handler and pass them along.
81*f4febd00SPatrick WilliamsThe handler will return a tuple of response parameters, which will get packed
82*f4febd00SPatrick Williamsback into a D-Bus response message and sent back to the calling channel's
83*f4febd00SPatrick Williamsbridge. The bridge will then re-package the response into its transport protocol
84*f4febd00SPatrick Williamsand send it off.
85fb8d806aSGunnar Mills
86*f4febd00SPatrick WilliamsThe next part is to provide a higher-level, strongly-typed, modern C++ mechanism
87*f4febd00SPatrick Williamsfor registering handlers. Each handler will specify exactly what arguments are
88*f4febd00SPatrick Williamsneeded for the command and what types will be returned in the response. This
89*f4febd00SPatrick Williamsway, the ipmid queue can unpack requests and pack responses in a safe manner.
90*f4febd00SPatrick WilliamsBecause the handler packing and unpacking code is templated, it is written
91*f4febd00SPatrick Williamsmostly in headers.
92fb8d806aSGunnar Mills
93fb8d806aSGunnar Mills## Details and Implementation
94fb8d806aSGunnar Mills
95fb8d806aSGunnar MillsFor session-less channels (like BT, KCS, and IPMB), the only privilege check
96fb8d806aSGunnar Millswill be to see that the requested privilege is less than or equal to the
97fb8d806aSGunnar Millschannel's maximum privilege. If the channel has a session and authenticates
98fb8d806aSGunnar Millsusers, the privilege must be less than or equal to the channel's maximum
99fb8d806aSGunnar Millsprivilege and the user's maximum privilege.
100fb8d806aSGunnar Mills
101fb8d806aSGunnar MillsIpmid takes the LUN/netFN/Cmd tuple and looks up the corresponding handler
102fb8d806aSGunnar Millsfunction. If the requested privilege is less than or equal to the required
103fb8d806aSGunnar Millsprivilege for the given registered command, the request may proceed. If any of
104fb8d806aSGunnar Millsthese checks fail, ipmid returns with _Insufficient Privilege_.
105fb8d806aSGunnar Mills
106fb8d806aSGunnar MillsAt this point, the IPMI command is run through the filter hooks. The default
107*f4febd00SPatrick Williamshook is ACCEPT, where the command just passes onto the execution phase. But OEMs
108*f4febd00SPatrick Williamsand providers can register hooks that would ultimately block IPMI commands from
109*f4febd00SPatrick Williamsexecuting, much like the IPMI 2.0 Spec's Firmware Firewall. The hook would be
110*f4febd00SPatrick Williamspassed in the context of the IPMI call and the raw content of the call and has
111*f4febd00SPatrick Williamsthe opportunity to return any valid IPMI completion code. Any non-zero
112fb8d806aSGunnar Millscompletion code would prevent the command from executing and would be returned
113fb8d806aSGunnar Millsto the caller.
114fb8d806aSGunnar Mills
115fb8d806aSGunnar MillsThe network channel bridges (netipmid), executing one process per interface,
116*f4febd00SPatrick Williamshandle session (RMCP+) and SOL commands and responses to those commands. Get/Set
117*f4febd00SPatrick WilliamsSOL configuration, Get session info, close session commands can also be
118fb8d806aSGunnar Millsrequested through KCS/BT interface, ipmid daemon must also need details about
119*f4febd00SPatrick Williamssession and SOL. In order to maintain sync between ipmid and netipmid daemon,
120*f4febd00SPatrick Williamssession and SOL are exposed in D-Bus, which ipmid can query and respond to
121*f4febd00SPatrick Williamscommands issued through host interface (KCS/BT).
122fb8d806aSGunnar Mills
123fb8d806aSGunnar MillsThe next phase is parameter unpacking and validation. This is done by
124fb8d806aSGunnar Millscompiler-generated code with variadic templates at handler registration time.
125fb8d806aSGunnar MillsThe registration function is a templated function that allows any type of
126fb8d806aSGunnar Millshandler to be passed in so that the types of the handler can be extracted and
127fb8d806aSGunnar Millsunpacked.
128fb8d806aSGunnar Mills
129fb8d806aSGunnar MillsThis is done in the core IPMI library (from a high level) like this:
130*f4febd00SPatrick Williams
131fb8d806aSGunnar Mills```cpp
132fb8d806aSGunnar Millstemplate <typename Handler>
133fb8d806aSGunnar Millsclass IpmiHandler
134fb8d806aSGunnar Mills{
135fb8d806aSGunnar Mills  public:
136fb8d806aSGunnar Mills    explicit IpmiHandler(Handler&& handler) :
137fb8d806aSGunnar Mills        handler_(std::forward<Handler>(handler))
138fb8d806aSGunnar Mills    {
139fb8d806aSGunnar Mills    }
140fb8d806aSGunnar Mills    message::Response::ptr call(message::Request::ptr request)
141fb8d806aSGunnar Mills    {
142fb8d806aSGunnar Mills        message::Response::ptr response = request->makeResponse();
143fb8d806aSGunnar Mills        // < code to deduce UnpackArgsType and ResultType from Handler >
144fb8d806aSGunnar Mills        UnpackArgsType unpackArgs;
145fb8d806aSGunnar Mills        ipmi::Cc unpackError = request->unpack(unpackArgs);
146fb8d806aSGunnar Mills        if (unpackError != ipmi::ccSuccess)
147fb8d806aSGunnar Mills        {
148fb8d806aSGunnar Mills            response->cc = unpackError;
149fb8d806aSGunnar Mills            return response;
150fb8d806aSGunnar Mills        }
151fb8d806aSGunnar Mills        ResultType result = std::apply(handler_, unpackArgs);
152fb8d806aSGunnar Mills        response->cc = std::get<0>(result);
153fb8d806aSGunnar Mills        auto payload = std::get<1>(result);
154fb8d806aSGunnar Mills        response->pack(*payload);
155fb8d806aSGunnar Mills        return response;
156fb8d806aSGunnar Mills    }
157fb8d806aSGunnar Mills  private:
158fb8d806aSGunnar Mills    Handler handler_;
159fb8d806aSGunnar Mills};
160fb8d806aSGunnar Mills ...
161fb8d806aSGunnar Mills namespace ipmi {
162fb8d806aSGunnar Mills   constexpr NetFn netFnApp = 0x06;
163fb8d806aSGunnar Mills namespace app {
164fb8d806aSGunnar Mills   constexpr Cmd cmdSetUserAccessCommand = 0x43;
165fb8d806aSGunnar Mills }
166fb8d806aSGunnar Mills  class Context {
167fb8d806aSGunnar Mills     NetFn netFn;
168fb8d806aSGunnar Mills     Cmd cmd;
169fb8d806aSGunnar Mills     int channel;
170fb8d806aSGunnar Mills     int userId;
171fb8d806aSGunnar Mills     Privilege priv;
172fb8d806aSGunnar Mills     boost::asio::yield_context* yield; // for async operations
173fb8d806aSGunnar Mills   };
174fb8d806aSGunnar Mills }
175fb8d806aSGunnar Mills```
176*f4febd00SPatrick Williams
177fb8d806aSGunnar MillsWhile some IPMI handlers would look like this:
178*f4febd00SPatrick Williams
179fb8d806aSGunnar Mills```cpp
180fb8d806aSGunnar Millsipmi::RspType<> setUserAccess(
181fb8d806aSGunnar Mills    std::shared_ptr<ipmi::Context> context,
182fb8d806aSGunnar Mills    uint4_t channelNumber,
183fb8d806aSGunnar Mills    bool ipmiEnable,
184fb8d806aSGunnar Mills    bool linkAuth,
185fb8d806aSGunnar Mills    bool callbackRestricted,
186fb8d806aSGunnar Mills    bool changeBit,
187fb8d806aSGunnar Mills    uint6_t userId,
188fb8d806aSGunnar Mills    uint2_t reserved1,
189fb8d806aSGunnar Mills    uint4_t privLimit,
190fb8d806aSGunnar Mills    uint4_t reserved2,
191fb8d806aSGunnar Mills    std::optional<uint4_t> userSessionLimit,
192fb8d806aSGunnar Mills    std::optional<uint4_t> reserved3) {
193fb8d806aSGunnar Mills  ...
194fb8d806aSGunnar Mills  return ipmi::ResponseSuccess();
195fb8d806aSGunnar Mills}
196fb8d806aSGunnar Mills
197fb8d806aSGunnar Millsipmi::RspType<uint8_t, // max user IDs
198fb8d806aSGunnar Mills              uint6_t, // count of enabled user IDs
199fb8d806aSGunnar Mills              uint2_t, // user ID enable status
200fb8d806aSGunnar Mills              uint8_t, // count of fixed user IDs
201fb8d806aSGunnar Mills              uint4_t  // user privilege for given channel
202fb8d806aSGunnar Mills              bool,    // ipmi messaging enabled
203fb8d806aSGunnar Mills              bool,    // link authentication enabled
204fb8d806aSGunnar Mills              bool,    // callback access
205fb8d806aSGunnar Mills              bool,    // reserved bit
206fb8d806aSGunnar Mills              >
207fb8d806aSGunnar Mills    getUserAccess(std::shared_ptr<ipmi::Context> context, uint8_t channelNumber,
208fb8d806aSGunnar Mills                  uint8_t userId)
209fb8d806aSGunnar Mills{
210fb8d806aSGunnar Mills    if (<some error condition>)
211fb8d806aSGunnar Mills    {
212fb8d806aSGunnar Mills        return ipmi::response(ipmi::ccParmOutOfRange);
213fb8d806aSGunnar Mills    }
214fb8d806aSGunnar Mills    // code to get
215fb8d806aSGunnar Mills    const auto& [usersMax, status, usersEnabled, usersFixed, access, priv] =
216fb8d806aSGunnar Mills        getSdBus()->yield_method_call(*context->yield, ...);
217fb8d806aSGunnar Mills    return ipmi::responseSuccess(usersMax, usersEnabled, status, usersFixed,
218fb8d806aSGunnar Mills                                 priv, access.messaging, access.linkAuth,
219fb8d806aSGunnar Mills                                 access.callback, false);
220fb8d806aSGunnar Mills}
221fb8d806aSGunnar Mills
222fb8d806aSGunnar Millsvoid providerInitFn()
223fb8d806aSGunnar Mills{
224fb8d806aSGunnar Mills    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
225fb8d806aSGunnar Mills                          ipmi::app::cmdSetUserAccessCommand,
226fb8d806aSGunnar Mills                          ipmi::Privilege::Admin,
227fb8d806aSGunnar Mills                          setUserAccess);
228fb8d806aSGunnar Mills    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
229fb8d806aSGunnar Mills                          ipmi::app::cmdGetUserAccessCommand,
230fb8d806aSGunnar Mills                          ipmi::Privilege::Operator,
231fb8d806aSGunnar Mills                          getUserAccess);
232fb8d806aSGunnar Mills}
233fb8d806aSGunnar Mills```
234fb8d806aSGunnar Mills
235fb8d806aSGunnar MillsIpmid providers are all executed as boost::asio::coroutines. This means that
236fb8d806aSGunnar Millsthey can take advantage of any of the boost::asio async method calls in a way
237fb8d806aSGunnar Millsthat looks like synchronous code, but will execute asynchronously by yielding
238*f4febd00SPatrick Williamscontrol of the processor to the run loop via the yield_context object. Use the
239*f4febd00SPatrick Williamsyield_context object by passing it as a 'callback' which will then cause the
240*f4febd00SPatrick Williamscalling coroutine to yield the processor until its IO is ready, at which point
241*f4febd00SPatrick Williamsit will 'return' much like a synchronous call.
242fb8d806aSGunnar Mills
243fb8d806aSGunnar MillsIdeally, all handlers would take advantage of the asynchronous capabilities of
244fb8d806aSGunnar Millsipmid via the boost::asio::yield_context. This means that the queue could have
245fb8d806aSGunnar Millsmultiple in-flight calls that are waiting on another D-Bus call to return. With
246*f4febd00SPatrick Williamsasynchronous calls, this will not block the rest of the queue's operation, The
247*f4febd00SPatrick Williamssdbusplus::asio::connection that is available to all providers via the
248*f4febd00SPatrick WilliamsgetSdBus() function provides yield_method_call() which is an asynchronous D-Bus
249*f4febd00SPatrick Williamscall mechanism that 'looks' like a synchronous call. It is important that any
250*f4febd00SPatrick Williamsglobal data that an asynchronous handler uses is protected as if the handler is
251*f4febd00SPatrick Williamsmulti-threaded. Since many of the resources used in IPMI handlers are actually
252*f4febd00SPatrick WilliamsD-Bus objects, this is not likely a common issue because of the serialization
253*f4febd00SPatrick Williamsthat happens via the D-Bus calls.
254fb8d806aSGunnar Mills
255*f4febd00SPatrick WilliamsUsing templates, it is possible to extract the return type and argument types of
256*f4febd00SPatrick Williamsthe handlers and use that to unpack (and validate) the arguments from the
257fb8d806aSGunnar Millsincoming request and then pack the result back into a vector of bytes to send
258fb8d806aSGunnar Millsback to the caller. The deserializer will keep track of the number of bits it
259fb8d806aSGunnar Millshas unpacked and then compare it with the total number of bits that the method
260fb8d806aSGunnar Millsis requesting. In the example, we are assuming that the non-full-byte integers
261fb8d806aSGunnar Millsare packed bits in the message in most-significant-bit first order (same order
262*f4febd00SPatrick Williamsthe specification describes them in). Optional arguments can be used easily with
263*f4febd00SPatrick WilliamsC++17's std::optional.
264fb8d806aSGunnar Mills
265fb8d806aSGunnar MillsSome types that are supported are as follows:
266*f4febd00SPatrick Williams
267*f4febd00SPatrick Williams- standard integer types (uint8_t, uint16_t, uint32_t, int, long, etc.)
268*f4febd00SPatrick Williams- bool (extracts a single bit, same as uint1_t)
269*f4febd00SPatrick Williams- multi-precision integers (uint<N>)
270*f4febd00SPatrick Williams- std::bitset<N>
271*f4febd00SPatrick Williams- std::optional<T> - extracts T if there are enough remaining bits/bytes
272*f4febd00SPatrick Williams- std::array<T, N> - extracts N elements of T if possible
273*f4febd00SPatrick Williams- std::vector<T> - extracts elements of T from the remaining bytes
274*f4febd00SPatrick Williams- any additional types can be created by making a pack/unpack template
275fb8d806aSGunnar Mills
276fb8d806aSGunnar MillsFor partial byte types, the least-significant bits of the next full byte are
277fb8d806aSGunnar Millsextracted first. While this is opposite of the order the IPMI specification is
278fb8d806aSGunnar Millswritten, it makes the code simple. Multi-byte fields are extracted as LSByte
279fb8d806aSGunnar Millsfirst (little-endian) to match the IPMI specification.
280fb8d806aSGunnar Mills
281fb8d806aSGunnar MillsWhen returning partial byte types, they are also packed into the reply in the
282fb8d806aSGunnar Millsleast-significant bit first order. As each byte fills up, the full byte gets
283fb8d806aSGunnar Millspushed onto the byte stream. For examples of how the packing and unpacking is
284fb8d806aSGunnar Millsused, see the unit test cases in test/message/pack.cpp and
285fb8d806aSGunnar Millstest/message/unpack.cpp.
286fb8d806aSGunnar Mills
287fb8d806aSGunnar MillsAs an example this is how a bitset is unpacked
288*f4febd00SPatrick Williams
289fb8d806aSGunnar Mills```cpp
290fb8d806aSGunnar Mills/** @brief Specialization of UnpackSingle for std::bitset<N>
291fb8d806aSGunnar Mills */
292fb8d806aSGunnar Millstemplate <size_t N>
293fb8d806aSGunnar Millsstruct UnpackSingle<std::bitset<N>>
294fb8d806aSGunnar Mills{
295fb8d806aSGunnar Mills    static int op(Payload& p, std::bitset<N>& t)
296fb8d806aSGunnar Mills    {
297fb8d806aSGunnar Mills        static_assert(N <= (details::bitStreamSize - CHAR_BIT));
298fb8d806aSGunnar Mills        size_t count = N;
299fb8d806aSGunnar Mills        // acquire enough bits in the stream to fulfill the Payload
300fb8d806aSGunnar Mills        if (p.fillBits(count))
301fb8d806aSGunnar Mills        {
302fb8d806aSGunnar Mills            return -1;
303fb8d806aSGunnar Mills        }
304fb8d806aSGunnar Mills        fixed_uint_t<details::bitStreamSize> bitmask = ((1 << count) - 1);
305fb8d806aSGunnar Mills        t |= (p.bitStream & bitmask).convert_to<unsigned long long>();
306fb8d806aSGunnar Mills        p.bitStream >>= count;
307fb8d806aSGunnar Mills        p.bitCount -= count;
308fb8d806aSGunnar Mills        return 0;
309fb8d806aSGunnar Mills    }
310fb8d806aSGunnar Mills};
311fb8d806aSGunnar Mills```
312fb8d806aSGunnar Mills
313*f4febd00SPatrick WilliamsIf a handler needs to unpack a variable payload, it is possible to request the
314*f4febd00SPatrick WilliamsPayload parameter. When the Payload parameter is present, any remaining,
315*f4febd00SPatrick Williamsunpacked bytes are available for inspection. Using the same unpacking calls that
316*f4febd00SPatrick Williamswere used to unpack the other parameters, the remaining parameters can be
317*f4febd00SPatrick Williamsmanually unpacked. This is helpful for multi-function commands like Set LAN
318*f4febd00SPatrick WilliamsConfiguration Parameters, where the first two bytes are fixed, but the remaining
319*f4febd00SPatrick Williams3:N bytes vary based on the selected parameter.
320