blob: c84bffdbc2d785424b5b47d61ee1bda637adc96f [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;
William A. Kennington III51694c22019-04-24 01:44:44 -0700156 request->payload.trailingOk = false;
Vernon Mauerye7329c72018-10-08 12:05:16 -0700157 ipmi::Cc unpackError = request->unpack(unpackArgs);
158 if (unpackError != ipmi::ccSuccess)
159 {
160 response->cc = unpackError;
161 return response;
162 }
163 /* callbacks can contain an optional first argument of one of:
164 * 1) boost::asio::yield_context
165 * 2) ipmi::Context::ptr
166 * 3) ipmi::message::Request::ptr
167 *
168 * If any of those is part of the callback signature as the first
169 * argument, it will automatically get packed into the parameter pack
170 * here.
171 *
172 * One more special optional argument is an ipmi::message::Payload.
173 * This argument can be in any position, though logically it makes the
174 * most sense if it is the last. If this class is included in the
175 * handler signature, it will allow for the handler to unpack optional
176 * parameters. For example, the Set LAN Configuration Parameters
177 * command takes variable length (and type) values for each of the LAN
178 * parameters. This means that the only fixed data is the channel and
179 * parameter selector. All the remaining data can be extracted using
180 * the Payload class and the unpack API available to the Payload class.
181 */
Vernon Mauerye7329c72018-10-08 12:05:16 -0700182 ResultType result;
183 try
184 {
William A. Kennington IIIf2fd17a2019-04-24 01:53:52 -0700185 std::optional<InputArgsType> inputArgs;
186 if constexpr (std::tuple_size<InputArgsType>::value > 0)
187 {
188 if constexpr (std::is_same<
189 std::tuple_element_t<0, InputArgsType>,
190 boost::asio::yield_context>::value)
191 {
192 inputArgs.emplace(std::tuple_cat(
193 std::forward_as_tuple(*(request->ctx->yield)),
194 std::move(unpackArgs)));
195 }
196 else if constexpr (std::is_same<
197 std::tuple_element_t<0, InputArgsType>,
198 ipmi::Context::ptr>::value)
199 {
200 inputArgs.emplace(
201 std::tuple_cat(std::forward_as_tuple(request->ctx),
202 std::move(unpackArgs)));
203 }
204 else if constexpr (std::is_same<
205 std::tuple_element_t<0, InputArgsType>,
206 ipmi::message::Request::ptr>::value)
207 {
208 inputArgs.emplace(std::tuple_cat(
209 std::forward_as_tuple(request), std::move(unpackArgs)));
210 }
211 else
212 {
213 // no special parameters were requested (but others were)
214 inputArgs.emplace(std::move(unpackArgs));
215 }
216 }
217 else
218 {
219 // no parameters were requested
220 inputArgs = std::move(unpackArgs);
221 }
222
Vernon Mauerye7329c72018-10-08 12:05:16 -0700223 // execute the registered callback function and get the
224 // ipmi::RspType<>
225 result = std::apply(handler_, *inputArgs);
226 }
227 catch (const std::exception& e)
228 {
229 phosphor::logging::log<phosphor::logging::level::ERR>(
230 "Handler failed to catch exception",
231 phosphor::logging::entry("EXCEPTION=%s", e.what()),
232 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
233 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
234 return errorResponse(request, ccUnspecifiedError);
235 }
236 catch (...)
237 {
238 std::exception_ptr eptr;
239 try
240 {
241 eptr = std::current_exception();
242 if (eptr)
243 {
244 std::rethrow_exception(eptr);
245 }
246 }
247 catch (const std::exception& e)
248 {
249 phosphor::logging::log<phosphor::logging::level::ERR>(
250 "Handler failed to catch exception",
251 phosphor::logging::entry("EXCEPTION=%s", e.what()),
252 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
253 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
254 return errorResponse(request, ccUnspecifiedError);
255 }
256 }
257
258 response->cc = std::get<0>(result);
259 auto payload = std::get<1>(result);
260 // check for optional payload
261 if (payload)
262 {
263 response->pack(*payload);
264 }
265 return response;
266 }
267};
268
269#ifdef ALLOW_DEPRECATED_API
270/**
271 * @brief Legacy IPMI handler class
272 *
273 * Legacy IPMI handlers will resolve into this class, which will behave the same
274 * way as the legacy IPMI queue, passing in a big buffer for the request and a
275 * big buffer for the response.
276 *
277 * As soon as all the handlers have been rewritten, this class will be marked as
278 * deprecated and eventually removed.
279 */
280template <>
281class IpmiHandler<ipmid_callback_t> final : public HandlerBase
282{
283 public:
Vernon Mauerybe376302019-03-21 13:02:05 -0700284 explicit IpmiHandler(const ipmid_callback_t& handler, void* ctx = nullptr) :
285 handler_(handler), handlerCtx(ctx)
Vernon Mauerye7329c72018-10-08 12:05:16 -0700286 {
287 }
288
289 private:
290 ipmid_callback_t handler_;
Vernon Mauerybe376302019-03-21 13:02:05 -0700291 void* handlerCtx;
Vernon Mauerye7329c72018-10-08 12:05:16 -0700292
293 /** @brief call the registered handler with the request
294 *
295 * This is called from the running queue context after it has already
296 * created a request object that contains all the information required to
297 * execute the ipmi command. This function will return the response object
298 * pointer that owns the response object that will ultimately get sent back
299 * to the requester.
300 *
301 * Because this is the legacy variety of IPMI handler, this function does
302 * not really have to do much other than pass the payload to the callback
303 * and return response to the caller.
304 *
305 * @param request a shared_ptr to a Request object
306 *
307 * @return a shared_ptr to a Response object
308 */
309 message::Response::ptr
310 executeCallback(message::Request::ptr request) override
311 {
312 message::Response::ptr response = request->makeResponse();
313 size_t len = request->payload.size();
314 // allocate a big response buffer here
315 response->payload.resize(
316 getChannelMaxTransferSize(request->ctx->channel));
317
318 Cc ccRet{ccSuccess};
319 try
320 {
321 ccRet = handler_(request->ctx->netFn, request->ctx->cmd,
322 request->payload.data(), response->payload.data(),
Vernon Mauerybe376302019-03-21 13:02:05 -0700323 &len, handlerCtx);
Vernon Mauerye7329c72018-10-08 12:05:16 -0700324 }
325 catch (const std::exception& e)
326 {
327 phosphor::logging::log<phosphor::logging::level::ERR>(
328 "Legacy Handler failed to catch exception",
329 phosphor::logging::entry("EXCEPTION=%s", e.what()),
330 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
331 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
332 return errorResponse(request, ccUnspecifiedError);
333 }
334 catch (...)
335 {
336 std::exception_ptr eptr;
337 try
338 {
339 eptr = std::current_exception();
340 if (eptr)
341 {
342 std::rethrow_exception(eptr);
343 }
344 }
345 catch (const std::exception& e)
346 {
347 phosphor::logging::log<phosphor::logging::level::ERR>(
348 "Handler failed to catch exception",
349 phosphor::logging::entry("EXCEPTION=%s", e.what()),
350 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
351 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
352 return errorResponse(request, ccUnspecifiedError);
353 }
354 }
355 response->cc = ccRet;
356 response->payload.resize(len);
357 return response;
358 }
359};
360
361/**
Vernon Maueryf984a012018-10-08 12:05:18 -0700362 * @brief Legacy IPMI OEM handler class
363 *
364 * Legacy IPMI OEM handlers will resolve into this class, which will behave the
365 * same way as the legacy IPMI queue, passing in a big buffer for the request
366 * and a big buffer for the response.
367 *
368 * As soon as all the handlers have been rewritten, this class will be marked as
369 * deprecated and eventually removed.
370 */
371template <>
372class IpmiHandler<oem::Handler> final : public HandlerBase
373{
374 public:
375 explicit IpmiHandler(const oem::Handler& handler) : handler_(handler)
376 {
377 }
378
379 private:
380 oem::Handler handler_;
381
382 /** @brief call the registered handler with the request
383 *
384 * This is called from the running queue context after it has already
385 * created a request object that contains all the information required to
386 * execute the ipmi command. This function will return the response object
387 * pointer that owns the response object that will ultimately get sent back
388 * to the requester.
389 *
390 * Because this is the legacy variety of IPMI handler, this function does
391 * not really have to do much other than pass the payload to the callback
392 * and return response to the caller.
393 *
394 * @param request a shared_ptr to a Request object
395 *
396 * @return a shared_ptr to a Response object
397 */
398 message::Response::ptr
399 executeCallback(message::Request::ptr request) override
400 {
401 message::Response::ptr response = request->makeResponse();
402 size_t len = request->payload.size();
403 // allocate a big response buffer here
404 response->payload.resize(
405 getChannelMaxTransferSize(request->ctx->channel));
406
407 Cc ccRet{ccSuccess};
408 try
409 {
410 ccRet = handler_(request->ctx->cmd, request->payload.data(),
411 response->payload.data(), &len);
412 }
413 catch (const std::exception& e)
414 {
415 phosphor::logging::log<phosphor::logging::level::ERR>(
416 "Legacy OEM Handler failed to catch exception",
417 phosphor::logging::entry("EXCEPTION=%s", e.what()),
418 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
419 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
420 return errorResponse(request, ccUnspecifiedError);
421 }
422 catch (...)
423 {
424 std::exception_ptr eptr;
425 try
426 {
427 eptr = std::current_exception();
428 if (eptr)
429 {
430 std::rethrow_exception(eptr);
431 }
432 }
433 catch (const std::exception& e)
434 {
435 phosphor::logging::log<phosphor::logging::level::ERR>(
436 "Handler failed to catch exception",
437 phosphor::logging::entry("EXCEPTION=%s", e.what()),
438 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
439 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
440 return errorResponse(request, ccUnspecifiedError);
441 }
442 }
443 response->cc = ccRet;
444 response->payload.resize(len);
445 return response;
446 }
447};
448
449/**
Vernon Mauerye7329c72018-10-08 12:05:16 -0700450 * @brief create a legacy IPMI handler class and return a shared_ptr
451 *
452 * The queue uses a map of pointers to do the lookup. This function returns the
453 * shared_ptr that owns the Handler object.
454 *
455 * This is called internally via the ipmi_register_callback function.
456 *
457 * @param handler the function pointer to the callback
458 *
459 * @return A shared_ptr to the created handler object
460 */
Vernon Mauerybe376302019-03-21 13:02:05 -0700461inline auto makeLegacyHandler(const ipmid_callback_t& handler,
462 void* ctx = nullptr)
Vernon Mauerye7329c72018-10-08 12:05:16 -0700463{
Vernon Mauerybe376302019-03-21 13:02:05 -0700464 HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx));
Vernon Mauerye7329c72018-10-08 12:05:16 -0700465 return ptr;
466}
467
Vernon Maueryf984a012018-10-08 12:05:18 -0700468/**
469 * @brief create a legacy IPMI OEM handler class and return a shared_ptr
470 *
471 * The queue uses a map of pointers to do the lookup. This function returns the
472 * shared_ptr that owns the Handler object.
473 *
474 * This is called internally via the Router::registerHandler method.
475 *
476 * @param handler the function pointer to the callback
477 *
478 * @return A shared_ptr to the created handler object
479 */
480inline auto makeLegacyHandler(oem::Handler&& handler)
481{
482 HandlerBase::ptr ptr(
483 new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler)));
484 return ptr;
485}
Vernon Mauerye7329c72018-10-08 12:05:16 -0700486#endif // ALLOW_DEPRECATED_API
487
488/**
489 * @brief create an IPMI handler class and return a shared_ptr
490 *
491 * The queue uses a map of pointers to do the lookup. This function returns the
492 * shared_ptr that owns the Handler object.
493 *
494 * This is called internally via the ipmi::registerHandler function.
495 *
496 * @param handler the function pointer to the callback
497 *
498 * @return A shared_ptr to the created handler object
499 */
500template <typename Handler>
501inline auto makeHandler(Handler&& handler)
502{
503 HandlerBase::ptr ptr(
504 new IpmiHandler<Handler>(std::forward<Handler>(handler)));
505 return ptr;
506}
507
Vernon Mauerye08fbff2019-04-03 09:19:34 -0700508namespace impl
509{
510
511// IPMI command handler registration implementation
512bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
513 ::ipmi::HandlerBase::ptr handler);
514bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
515 ::ipmi::HandlerBase::ptr handler);
516bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
517 ::ipmi::HandlerBase::ptr handler);
518
519} // namespace impl
520
521/**
522 * @brief main IPMI handler registration function
523 *
524 * This function should be used to register all new-style IPMI handler
525 * functions. This function just passes the callback to makeHandler, which
526 * creates a new wrapper object that will automatically extract the appropriate
527 * parameters for the callback function as well as pack up the response.
528 *
529 * @param prio - priority at which to register; see api.hpp
530 * @param netFn - the IPMI net function number to register
531 * @param cmd - the IPMI command number to register
532 * @param priv - the IPMI user privilige required for this command
533 * @param handler - the callback function that will handle this request
534 *
535 * @return bool - success of registering the handler
536 */
537template <typename Handler>
538bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
539 Handler&& handler)
540{
541 auto h = ipmi::makeHandler(std::forward<Handler>(handler));
542 return impl::registerHandler(prio, netFn, cmd, priv, h);
543}
544
545/**
546 * @brief register a IPMI OEM group handler
547 *
548 * From IPMI 2.0 spec Network Function Codes Table (Row 2Ch):
549 * The first data byte position in requests and responses under this network
550 * function identifies the defining body that specifies command functionality.
551 * Software assumes that the command and completion code field positions will
552 * hold command and completion code values.
553 *
554 * The following values are used to identify the defining body:
555 * 00h PICMG - PCI Industrial Computer Manufacturer’s Group. (www.picmg.com)
556 * 01h DMTF Pre-OS Working Group ASF Specification (www.dmtf.org)
557 * 02h Server System Infrastructure (SSI) Forum (www.ssiforum.org)
558 * 03h VITA Standards Organization (VSO) (www.vita.com)
559 * DCh DCMI Specifications (www.intel.com/go/dcmi)
560 * all other Reserved
561 *
562 * When this network function is used, the ID for the defining body occupies
563 * the first data byte in a request, and the second data byte (following the
564 * completion code) in a response.
565 *
566 * @tparam Handler - implicitly specified callback function type
567 * @param prio - priority at which to register; see api.hpp
568 * @param netFn - the IPMI net function number to register
569 * @param cmd - the IPMI command number to register
570 * @param priv - the IPMI user privilige required for this command
571 * @param handler - the callback function that will handle this request
572 *
573 * @return bool - success of registering the handler
574 *
575 */
576template <typename Handler>
577void registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
578 Handler&& handler)
579{
580 auto h = ipmi::makeHandler(handler);
581 impl::registerGroupHandler(prio, group, cmd, priv, h);
582}
583
584/**
585 * @brief register a IPMI OEM IANA handler
586 *
587 * From IPMI spec Network Function Codes Table (Row 2Eh):
588 * The first three data bytes of requests and responses under this network
589 * function explicitly identify the OEM or non-IPMI group that specifies the
590 * command functionality. While the OEM or non-IPMI group defines the
591 * functional semantics for the cmd and remaining data fields, the cmd field
592 * is required to hold the same value in requests and responses for a given
593 * operation in order to be supported under the IPMI message handling and
594 * transport mechanisms.
595 *
596 * When this network function is used, the IANA Enterprise Number for the
597 * defining body occupies the first three data bytes in a request, and the
598 * first three data bytes following the completion code position in a
599 * response.
600 *
601 * @tparam Handler - implicitly specified callback function type
602 * @param prio - priority at which to register; see api.hpp
603 * @param netFn - the IPMI net function number to register
604 * @param cmd - the IPMI command number to register
605 * @param priv - the IPMI user privilige required for this command
606 * @param handler - the callback function that will handle this request
607 *
608 * @return bool - success of registering the handler
609 *
610 */
611template <typename Handler>
612void registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
613 Handler&& handler)
614{
615 auto h = ipmi::makeHandler(handler);
616 impl::registerOemHandler(prio, iana, cmd, priv, h);
617}
618
Vernon Mauerye7329c72018-10-08 12:05:16 -0700619} // namespace ipmi
Vernon Mauerye08fbff2019-04-03 09:19:34 -0700620
621#ifdef ALLOW_DEPRECATED_API
622/**
623 * @brief legacy IPMI handler registration function
624 *
625 * This function should be used to register all legacy IPMI handler
626 * functions. This function just behaves just as the legacy registration
627 * mechanism did, silently replacing any existing handler with a new one.
628 *
629 * @param netFn - the IPMI net function number to register
630 * @param cmd - the IPMI command number to register
631 * @param context - ignored
632 * @param handler - the callback function that will handle this request
633 * @param priv - the IPMI user privilige required for this command
634 */
635// [[deprecated("Use ipmi::registerHandler() instead")]]
636void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
637 ipmi_context_t context, ipmid_callback_t handler,
638 ipmi_cmd_privilege_t priv);
639
640#endif /* ALLOW_DEPRECATED_API */