blob: 1421c3d85f7ae6c0c73315a5d0af9356dd1cc360 [file] [log] [blame]
Vernon Mauerye7329c72018-10-08 12:05:16 -07001/**
2 * Copyright © 2018 Intel Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16#pragma once
17#include <algorithm>
18#include <boost/asio/spawn.hpp>
19#include <boost/callable_traits.hpp>
20#include <cstdint>
21#include <exception>
Vernon Mauerye08fbff2019-04-03 09:19:34 -070022#include <ipmid/api-types.hpp>
Vernon Mauerye7329c72018-10-08 12:05:16 -070023#include <ipmid/message.hpp>
24#include <memory>
25#include <optional>
26#include <phosphor-logging/log.hpp>
27#include <tuple>
28#include <user_channel/channel_layer.hpp>
29#include <utility>
30
31#ifdef ALLOW_DEPRECATED_API
32#include <ipmid/api.h>
33
34#include <ipmid/oemrouter.hpp>
35#endif /* ALLOW_DEPRECATED_API */
36
37namespace ipmi
38{
39
40template <typename... Args>
41static inline message::Response::ptr
42 errorResponse(message::Request::ptr request, ipmi::Cc cc, Args&&... args)
43{
44 message::Response::ptr response = request->makeResponse();
Vernon Mauerybfe55a12019-03-08 13:48:40 -080045 response->cc = cc;
46 response->pack(args...);
Vernon Mauerye7329c72018-10-08 12:05:16 -070047 return response;
48}
49static inline message::Response::ptr
50 errorResponse(message::Request::ptr request, ipmi::Cc cc)
51{
52 message::Response::ptr response = request->makeResponse();
Vernon Mauerybfe55a12019-03-08 13:48:40 -080053 response->cc = cc;
Vernon Mauerye7329c72018-10-08 12:05:16 -070054 return response;
55}
56
57/**
58 * @brief Handler base class for dealing with IPMI request/response
59 *
60 * The subclasses are all templated so they can provide access to any type
61 * of command callback functions.
62 */
63class HandlerBase
64{
65 public:
66 using ptr = std::shared_ptr<HandlerBase>;
67
68 /** @brief wrap the call to the registered handler with the request
69 *
70 * This is called from the running queue context after it has already
71 * created a request object that contains all the information required to
72 * execute the ipmi command. This function will return the response object
73 * pointer that owns the response object that will ultimately get sent back
74 * to the requester.
75 *
76 * This is a non-virtual function wrapper to the virtualized executeCallback
77 * function that actually does the work. This is required because of how
78 * templates and virtualization work together.
79 *
80 * @param request a shared_ptr to a Request object
81 *
82 * @return a shared_ptr to a Response object
83 */
84 message::Response::ptr call(message::Request::ptr request)
85 {
86 return executeCallback(request);
87 }
88
89 private:
90 /** @brief call the registered handler with the request
91 *
92 * This is called from the running queue context after it has already
93 * created a request object that contains all the information required to
94 * execute the ipmi command. This function will return the response object
95 * pointer that owns the response object that will ultimately get sent back
96 * to the requester.
97 *
98 * @param request a shared_ptr to a Request object
99 *
100 * @return a shared_ptr to a Response object
101 */
102 virtual message::Response::ptr
103 executeCallback(message::Request::ptr request) = 0;
104};
105
106/**
107 * @brief Main IPMI handler class
108 *
109 * New IPMI handlers will resolve into this class, which will read the signature
110 * of the registering function, attempt to extract the appropriate arguments
111 * from a request, pass the arguments to the function, and then pack the
112 * response of the function back into an IPMI response.
113 */
114template <typename Handler>
115class IpmiHandler final : public HandlerBase
116{
117 public:
118 explicit IpmiHandler(Handler&& handler) :
119 handler_(std::forward<Handler>(handler))
120 {
121 }
122
123 private:
124 Handler handler_;
125
126 /** @brief call the registered handler with the request
127 *
128 * This is called from the running queue context after it has already
129 * created a request object that contains all the information required to
130 * execute the ipmi command. This function will return the response object
131 * pointer that owns the response object that will ultimately get sent back
132 * to the requester.
133 *
134 * Because this is the new variety of IPMI handler, this is the function
135 * that attempts to extract the requested parameters in order to pass them
136 * onto the callback function and then packages up the response into a plain
137 * old vector to pass back to the caller.
138 *
139 * @param request a shared_ptr to a Request object
140 *
141 * @return a shared_ptr to a Response object
142 */
143 message::Response::ptr
144 executeCallback(message::Request::ptr request) override
145 {
146 message::Response::ptr response = request->makeResponse();
147
148 using CallbackSig = boost::callable_traits::args_t<Handler>;
149 using InputArgsType = typename utility::DecayTuple<CallbackSig>::type;
150 using UnpackArgsType = typename utility::StripFirstArgs<
151 utility::NonIpmiArgsCount<InputArgsType>::size(),
152 InputArgsType>::type;
153 using ResultType = boost::callable_traits::return_type_t<Handler>;
154
155 UnpackArgsType unpackArgs;
156 ipmi::Cc unpackError = request->unpack(unpackArgs);
157 if (unpackError != ipmi::ccSuccess)
158 {
159 response->cc = unpackError;
160 return response;
161 }
162 /* callbacks can contain an optional first argument of one of:
163 * 1) boost::asio::yield_context
164 * 2) ipmi::Context::ptr
165 * 3) ipmi::message::Request::ptr
166 *
167 * If any of those is part of the callback signature as the first
168 * argument, it will automatically get packed into the parameter pack
169 * here.
170 *
171 * One more special optional argument is an ipmi::message::Payload.
172 * This argument can be in any position, though logically it makes the
173 * most sense if it is the last. If this class is included in the
174 * handler signature, it will allow for the handler to unpack optional
175 * parameters. For example, the Set LAN Configuration Parameters
176 * command takes variable length (and type) values for each of the LAN
177 * parameters. This means that the only fixed data is the channel and
178 * parameter selector. All the remaining data can be extracted using
179 * the Payload class and the unpack API available to the Payload class.
180 */
181 std::optional<InputArgsType> inputArgs;
182 if constexpr (std::tuple_size<InputArgsType>::value > 0)
183 {
184 if constexpr (std::is_same<std::tuple_element_t<0, InputArgsType>,
185 boost::asio::yield_context>::value)
186 {
187 inputArgs.emplace(std::tuple_cat(
188 std::forward_as_tuple(*(request->ctx->yield)),
189 std::move(unpackArgs)));
190 }
191 else if constexpr (std::is_same<
192 std::tuple_element_t<0, InputArgsType>,
193 ipmi::Context::ptr>::value)
194 {
195 inputArgs.emplace(
196 std::tuple_cat(std::forward_as_tuple(request->ctx),
197 std::move(unpackArgs)));
198 }
199 else if constexpr (std::is_same<
200 std::tuple_element_t<0, InputArgsType>,
201 ipmi::message::Request::ptr>::value)
202 {
203 inputArgs.emplace(std::tuple_cat(std::forward_as_tuple(request),
204 std::move(unpackArgs)));
205 }
206 else
207 {
208 // no special parameters were requested (but others were)
209 inputArgs.emplace(std::move(unpackArgs));
210 }
211 }
212 else
213 {
214 // no parameters were requested
215 inputArgs = std::move(unpackArgs);
216 }
217 ResultType result;
218 try
219 {
220 // execute the registered callback function and get the
221 // ipmi::RspType<>
222 result = std::apply(handler_, *inputArgs);
223 }
224 catch (const std::exception& e)
225 {
226 phosphor::logging::log<phosphor::logging::level::ERR>(
227 "Handler failed to catch exception",
228 phosphor::logging::entry("EXCEPTION=%s", e.what()),
229 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
230 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
231 return errorResponse(request, ccUnspecifiedError);
232 }
233 catch (...)
234 {
235 std::exception_ptr eptr;
236 try
237 {
238 eptr = std::current_exception();
239 if (eptr)
240 {
241 std::rethrow_exception(eptr);
242 }
243 }
244 catch (const std::exception& e)
245 {
246 phosphor::logging::log<phosphor::logging::level::ERR>(
247 "Handler failed to catch exception",
248 phosphor::logging::entry("EXCEPTION=%s", e.what()),
249 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
250 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
251 return errorResponse(request, ccUnspecifiedError);
252 }
253 }
254
255 response->cc = std::get<0>(result);
256 auto payload = std::get<1>(result);
257 // check for optional payload
258 if (payload)
259 {
260 response->pack(*payload);
261 }
262 return response;
263 }
264};
265
266#ifdef ALLOW_DEPRECATED_API
267/**
268 * @brief Legacy IPMI handler class
269 *
270 * Legacy IPMI handlers will resolve into this class, which will behave the same
271 * way as the legacy IPMI queue, passing in a big buffer for the request and a
272 * big buffer for the response.
273 *
274 * As soon as all the handlers have been rewritten, this class will be marked as
275 * deprecated and eventually removed.
276 */
277template <>
278class IpmiHandler<ipmid_callback_t> final : public HandlerBase
279{
280 public:
Vernon Mauerybe376302019-03-21 13:02:05 -0700281 explicit IpmiHandler(const ipmid_callback_t& handler, void* ctx = nullptr) :
282 handler_(handler), handlerCtx(ctx)
Vernon Mauerye7329c72018-10-08 12:05:16 -0700283 {
284 }
285
286 private:
287 ipmid_callback_t handler_;
Vernon Mauerybe376302019-03-21 13:02:05 -0700288 void* handlerCtx;
Vernon Mauerye7329c72018-10-08 12:05:16 -0700289
290 /** @brief call the registered handler with the request
291 *
292 * This is called from the running queue context after it has already
293 * created a request object that contains all the information required to
294 * execute the ipmi command. This function will return the response object
295 * pointer that owns the response object that will ultimately get sent back
296 * to the requester.
297 *
298 * Because this is the legacy variety of IPMI handler, this function does
299 * not really have to do much other than pass the payload to the callback
300 * and return response to the caller.
301 *
302 * @param request a shared_ptr to a Request object
303 *
304 * @return a shared_ptr to a Response object
305 */
306 message::Response::ptr
307 executeCallback(message::Request::ptr request) override
308 {
309 message::Response::ptr response = request->makeResponse();
310 size_t len = request->payload.size();
311 // allocate a big response buffer here
312 response->payload.resize(
313 getChannelMaxTransferSize(request->ctx->channel));
314
315 Cc ccRet{ccSuccess};
316 try
317 {
318 ccRet = handler_(request->ctx->netFn, request->ctx->cmd,
319 request->payload.data(), response->payload.data(),
Vernon Mauerybe376302019-03-21 13:02:05 -0700320 &len, handlerCtx);
Vernon Mauerye7329c72018-10-08 12:05:16 -0700321 }
322 catch (const std::exception& e)
323 {
324 phosphor::logging::log<phosphor::logging::level::ERR>(
325 "Legacy Handler failed to catch exception",
326 phosphor::logging::entry("EXCEPTION=%s", e.what()),
327 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
328 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
329 return errorResponse(request, ccUnspecifiedError);
330 }
331 catch (...)
332 {
333 std::exception_ptr eptr;
334 try
335 {
336 eptr = std::current_exception();
337 if (eptr)
338 {
339 std::rethrow_exception(eptr);
340 }
341 }
342 catch (const std::exception& e)
343 {
344 phosphor::logging::log<phosphor::logging::level::ERR>(
345 "Handler failed to catch exception",
346 phosphor::logging::entry("EXCEPTION=%s", e.what()),
347 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
348 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
349 return errorResponse(request, ccUnspecifiedError);
350 }
351 }
352 response->cc = ccRet;
353 response->payload.resize(len);
354 return response;
355 }
356};
357
358/**
Vernon Maueryf984a012018-10-08 12:05:18 -0700359 * @brief Legacy IPMI OEM handler class
360 *
361 * Legacy IPMI OEM handlers will resolve into this class, which will behave the
362 * same way as the legacy IPMI queue, passing in a big buffer for the request
363 * and a big buffer for the response.
364 *
365 * As soon as all the handlers have been rewritten, this class will be marked as
366 * deprecated and eventually removed.
367 */
368template <>
369class IpmiHandler<oem::Handler> final : public HandlerBase
370{
371 public:
372 explicit IpmiHandler(const oem::Handler& handler) : handler_(handler)
373 {
374 }
375
376 private:
377 oem::Handler handler_;
378
379 /** @brief call the registered handler with the request
380 *
381 * This is called from the running queue context after it has already
382 * created a request object that contains all the information required to
383 * execute the ipmi command. This function will return the response object
384 * pointer that owns the response object that will ultimately get sent back
385 * to the requester.
386 *
387 * Because this is the legacy variety of IPMI handler, this function does
388 * not really have to do much other than pass the payload to the callback
389 * and return response to the caller.
390 *
391 * @param request a shared_ptr to a Request object
392 *
393 * @return a shared_ptr to a Response object
394 */
395 message::Response::ptr
396 executeCallback(message::Request::ptr request) override
397 {
398 message::Response::ptr response = request->makeResponse();
399 size_t len = request->payload.size();
400 // allocate a big response buffer here
401 response->payload.resize(
402 getChannelMaxTransferSize(request->ctx->channel));
403
404 Cc ccRet{ccSuccess};
405 try
406 {
407 ccRet = handler_(request->ctx->cmd, request->payload.data(),
408 response->payload.data(), &len);
409 }
410 catch (const std::exception& e)
411 {
412 phosphor::logging::log<phosphor::logging::level::ERR>(
413 "Legacy OEM Handler failed to catch exception",
414 phosphor::logging::entry("EXCEPTION=%s", e.what()),
415 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
416 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
417 return errorResponse(request, ccUnspecifiedError);
418 }
419 catch (...)
420 {
421 std::exception_ptr eptr;
422 try
423 {
424 eptr = std::current_exception();
425 if (eptr)
426 {
427 std::rethrow_exception(eptr);
428 }
429 }
430 catch (const std::exception& e)
431 {
432 phosphor::logging::log<phosphor::logging::level::ERR>(
433 "Handler failed to catch exception",
434 phosphor::logging::entry("EXCEPTION=%s", e.what()),
435 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
436 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
437 return errorResponse(request, ccUnspecifiedError);
438 }
439 }
440 response->cc = ccRet;
441 response->payload.resize(len);
442 return response;
443 }
444};
445
446/**
Vernon Mauerye7329c72018-10-08 12:05:16 -0700447 * @brief create a legacy IPMI handler class and return a shared_ptr
448 *
449 * The queue uses a map of pointers to do the lookup. This function returns the
450 * shared_ptr that owns the Handler object.
451 *
452 * This is called internally via the ipmi_register_callback function.
453 *
454 * @param handler the function pointer to the callback
455 *
456 * @return A shared_ptr to the created handler object
457 */
Vernon Mauerybe376302019-03-21 13:02:05 -0700458inline auto makeLegacyHandler(const ipmid_callback_t& handler,
459 void* ctx = nullptr)
Vernon Mauerye7329c72018-10-08 12:05:16 -0700460{
Vernon Mauerybe376302019-03-21 13:02:05 -0700461 HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx));
Vernon Mauerye7329c72018-10-08 12:05:16 -0700462 return ptr;
463}
464
Vernon Maueryf984a012018-10-08 12:05:18 -0700465/**
466 * @brief create a legacy IPMI OEM handler class and return a shared_ptr
467 *
468 * The queue uses a map of pointers to do the lookup. This function returns the
469 * shared_ptr that owns the Handler object.
470 *
471 * This is called internally via the Router::registerHandler method.
472 *
473 * @param handler the function pointer to the callback
474 *
475 * @return A shared_ptr to the created handler object
476 */
477inline auto makeLegacyHandler(oem::Handler&& handler)
478{
479 HandlerBase::ptr ptr(
480 new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler)));
481 return ptr;
482}
Vernon Mauerye7329c72018-10-08 12:05:16 -0700483#endif // ALLOW_DEPRECATED_API
484
485/**
486 * @brief create an IPMI handler class and return a shared_ptr
487 *
488 * The queue uses a map of pointers to do the lookup. This function returns the
489 * shared_ptr that owns the Handler object.
490 *
491 * This is called internally via the ipmi::registerHandler function.
492 *
493 * @param handler the function pointer to the callback
494 *
495 * @return A shared_ptr to the created handler object
496 */
497template <typename Handler>
498inline auto makeHandler(Handler&& handler)
499{
500 HandlerBase::ptr ptr(
501 new IpmiHandler<Handler>(std::forward<Handler>(handler)));
502 return ptr;
503}
504
Vernon Mauerye08fbff2019-04-03 09:19:34 -0700505namespace impl
506{
507
508// IPMI command handler registration implementation
509bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
510 ::ipmi::HandlerBase::ptr handler);
511bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
512 ::ipmi::HandlerBase::ptr handler);
513bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
514 ::ipmi::HandlerBase::ptr handler);
515
516} // namespace impl
517
518/**
519 * @brief main IPMI handler registration function
520 *
521 * This function should be used to register all new-style IPMI handler
522 * functions. This function just passes the callback to makeHandler, which
523 * creates a new wrapper object that will automatically extract the appropriate
524 * parameters for the callback function as well as pack up the response.
525 *
526 * @param prio - priority at which to register; see api.hpp
527 * @param netFn - the IPMI net function number to register
528 * @param cmd - the IPMI command number to register
529 * @param priv - the IPMI user privilige required for this command
530 * @param handler - the callback function that will handle this request
531 *
532 * @return bool - success of registering the handler
533 */
534template <typename Handler>
535bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
536 Handler&& handler)
537{
538 auto h = ipmi::makeHandler(std::forward<Handler>(handler));
539 return impl::registerHandler(prio, netFn, cmd, priv, h);
540}
541
542/**
543 * @brief register a IPMI OEM group handler
544 *
545 * From IPMI 2.0 spec Network Function Codes Table (Row 2Ch):
546 * The first data byte position in requests and responses under this network
547 * function identifies the defining body that specifies command functionality.
548 * Software assumes that the command and completion code field positions will
549 * hold command and completion code values.
550 *
551 * The following values are used to identify the defining body:
552 * 00h PICMG - PCI Industrial Computer Manufacturer’s Group. (www.picmg.com)
553 * 01h DMTF Pre-OS Working Group ASF Specification (www.dmtf.org)
554 * 02h Server System Infrastructure (SSI) Forum (www.ssiforum.org)
555 * 03h VITA Standards Organization (VSO) (www.vita.com)
556 * DCh DCMI Specifications (www.intel.com/go/dcmi)
557 * all other Reserved
558 *
559 * When this network function is used, the ID for the defining body occupies
560 * the first data byte in a request, and the second data byte (following the
561 * completion code) in a response.
562 *
563 * @tparam Handler - implicitly specified callback function type
564 * @param prio - priority at which to register; see api.hpp
565 * @param netFn - the IPMI net function number to register
566 * @param cmd - the IPMI command number to register
567 * @param priv - the IPMI user privilige required for this command
568 * @param handler - the callback function that will handle this request
569 *
570 * @return bool - success of registering the handler
571 *
572 */
573template <typename Handler>
574void registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
575 Handler&& handler)
576{
577 auto h = ipmi::makeHandler(handler);
578 impl::registerGroupHandler(prio, group, cmd, priv, h);
579}
580
581/**
582 * @brief register a IPMI OEM IANA handler
583 *
584 * From IPMI spec Network Function Codes Table (Row 2Eh):
585 * The first three data bytes of requests and responses under this network
586 * function explicitly identify the OEM or non-IPMI group that specifies the
587 * command functionality. While the OEM or non-IPMI group defines the
588 * functional semantics for the cmd and remaining data fields, the cmd field
589 * is required to hold the same value in requests and responses for a given
590 * operation in order to be supported under the IPMI message handling and
591 * transport mechanisms.
592 *
593 * When this network function is used, the IANA Enterprise Number for the
594 * defining body occupies the first three data bytes in a request, and the
595 * first three data bytes following the completion code position in a
596 * response.
597 *
598 * @tparam Handler - implicitly specified callback function type
599 * @param prio - priority at which to register; see api.hpp
600 * @param netFn - the IPMI net function number to register
601 * @param cmd - the IPMI command number to register
602 * @param priv - the IPMI user privilige required for this command
603 * @param handler - the callback function that will handle this request
604 *
605 * @return bool - success of registering the handler
606 *
607 */
608template <typename Handler>
609void registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
610 Handler&& handler)
611{
612 auto h = ipmi::makeHandler(handler);
613 impl::registerOemHandler(prio, iana, cmd, priv, h);
614}
615
Vernon Mauerye7329c72018-10-08 12:05:16 -0700616} // namespace ipmi
Vernon Mauerye08fbff2019-04-03 09:19:34 -0700617
618#ifdef ALLOW_DEPRECATED_API
619/**
620 * @brief legacy IPMI handler registration function
621 *
622 * This function should be used to register all legacy IPMI handler
623 * functions. This function just behaves just as the legacy registration
624 * mechanism did, silently replacing any existing handler with a new one.
625 *
626 * @param netFn - the IPMI net function number to register
627 * @param cmd - the IPMI command number to register
628 * @param context - ignored
629 * @param handler - the callback function that will handle this request
630 * @param priv - the IPMI user privilige required for this command
631 */
632// [[deprecated("Use ipmi::registerHandler() instead")]]
633void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
634 ipmi_context_t context, ipmid_callback_t handler,
635 ipmi_cmd_privilege_t priv);
636
637#endif /* ALLOW_DEPRECATED_API */