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