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