blob: 53b0bb8dff9dc5fa6690b81fc255c0b7d4406ae3 [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();
Vernon Mauerye7329c72018-10-08 12:05:16 -0700313 // allocate a big response buffer here
314 response->payload.resize(
315 getChannelMaxTransferSize(request->ctx->channel));
316
William A. Kennington III5d06cc62019-04-25 02:10:55 -0700317 size_t len = request->payload.size() - request->payload.rawIndex;
Vernon Mauerye7329c72018-10-08 12:05:16 -0700318 Cc ccRet{ccSuccess};
319 try
320 {
William A. Kennington III5d06cc62019-04-25 02:10:55 -0700321 ccRet =
322 handler_(request->ctx->netFn, request->ctx->cmd,
323 request->payload.data() + request->payload.rawIndex,
324 response->payload.data(), &len, handlerCtx);
Vernon Mauerye7329c72018-10-08 12:05:16 -0700325 }
326 catch (const std::exception& e)
327 {
328 phosphor::logging::log<phosphor::logging::level::ERR>(
329 "Legacy Handler failed to catch exception",
330 phosphor::logging::entry("EXCEPTION=%s", e.what()),
331 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
332 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
333 return errorResponse(request, ccUnspecifiedError);
334 }
335 catch (...)
336 {
337 std::exception_ptr eptr;
338 try
339 {
340 eptr = std::current_exception();
341 if (eptr)
342 {
343 std::rethrow_exception(eptr);
344 }
345 }
346 catch (const std::exception& e)
347 {
348 phosphor::logging::log<phosphor::logging::level::ERR>(
349 "Handler failed to catch exception",
350 phosphor::logging::entry("EXCEPTION=%s", e.what()),
351 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
352 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
353 return errorResponse(request, ccUnspecifiedError);
354 }
355 }
356 response->cc = ccRet;
357 response->payload.resize(len);
358 return response;
359 }
360};
361
362/**
Vernon Maueryf984a012018-10-08 12:05:18 -0700363 * @brief Legacy IPMI OEM handler class
364 *
365 * Legacy IPMI OEM handlers will resolve into this class, which will behave the
366 * same way as the legacy IPMI queue, passing in a big buffer for the request
367 * and a big buffer for the response.
368 *
369 * As soon as all the handlers have been rewritten, this class will be marked as
370 * deprecated and eventually removed.
371 */
372template <>
373class IpmiHandler<oem::Handler> final : public HandlerBase
374{
375 public:
376 explicit IpmiHandler(const oem::Handler& handler) : handler_(handler)
377 {
378 }
379
380 private:
381 oem::Handler handler_;
382
383 /** @brief call the registered handler with the request
384 *
385 * This is called from the running queue context after it has already
386 * created a request object that contains all the information required to
387 * execute the ipmi command. This function will return the response object
388 * pointer that owns the response object that will ultimately get sent back
389 * to the requester.
390 *
391 * Because this is the legacy variety of IPMI handler, this function does
392 * not really have to do much other than pass the payload to the callback
393 * and return response to the caller.
394 *
395 * @param request a shared_ptr to a Request object
396 *
397 * @return a shared_ptr to a Response object
398 */
399 message::Response::ptr
400 executeCallback(message::Request::ptr request) override
401 {
402 message::Response::ptr response = request->makeResponse();
Vernon Maueryf984a012018-10-08 12:05:18 -0700403 // allocate a big response buffer here
404 response->payload.resize(
405 getChannelMaxTransferSize(request->ctx->channel));
406
William A. Kennington III5d06cc62019-04-25 02:10:55 -0700407 size_t len = request->payload.size() - request->payload.rawIndex;
Vernon Maueryf984a012018-10-08 12:05:18 -0700408 Cc ccRet{ccSuccess};
409 try
410 {
William A. Kennington III5d06cc62019-04-25 02:10:55 -0700411 ccRet =
412 handler_(request->ctx->cmd,
413 request->payload.data() + request->payload.rawIndex,
414 response->payload.data(), &len);
Vernon Maueryf984a012018-10-08 12:05:18 -0700415 }
416 catch (const std::exception& e)
417 {
418 phosphor::logging::log<phosphor::logging::level::ERR>(
419 "Legacy OEM Handler failed to catch exception",
420 phosphor::logging::entry("EXCEPTION=%s", e.what()),
421 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
422 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
423 return errorResponse(request, ccUnspecifiedError);
424 }
425 catch (...)
426 {
427 std::exception_ptr eptr;
428 try
429 {
430 eptr = std::current_exception();
431 if (eptr)
432 {
433 std::rethrow_exception(eptr);
434 }
435 }
436 catch (const std::exception& e)
437 {
438 phosphor::logging::log<phosphor::logging::level::ERR>(
439 "Handler failed to catch exception",
440 phosphor::logging::entry("EXCEPTION=%s", e.what()),
441 phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
442 phosphor::logging::entry("CMD=%x", request->ctx->cmd));
443 return errorResponse(request, ccUnspecifiedError);
444 }
445 }
446 response->cc = ccRet;
447 response->payload.resize(len);
448 return response;
449 }
450};
451
452/**
Vernon Mauerye7329c72018-10-08 12:05:16 -0700453 * @brief create a legacy IPMI handler class and return a shared_ptr
454 *
455 * The queue uses a map of pointers to do the lookup. This function returns the
456 * shared_ptr that owns the Handler object.
457 *
458 * This is called internally via the ipmi_register_callback function.
459 *
460 * @param handler the function pointer to the callback
461 *
462 * @return A shared_ptr to the created handler object
463 */
Vernon Mauerybe376302019-03-21 13:02:05 -0700464inline auto makeLegacyHandler(const ipmid_callback_t& handler,
465 void* ctx = nullptr)
Vernon Mauerye7329c72018-10-08 12:05:16 -0700466{
Vernon Mauerybe376302019-03-21 13:02:05 -0700467 HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx));
Vernon Mauerye7329c72018-10-08 12:05:16 -0700468 return ptr;
469}
470
Vernon Maueryf984a012018-10-08 12:05:18 -0700471/**
472 * @brief create a legacy IPMI OEM handler class and return a shared_ptr
473 *
474 * The queue uses a map of pointers to do the lookup. This function returns the
475 * shared_ptr that owns the Handler object.
476 *
477 * This is called internally via the Router::registerHandler method.
478 *
479 * @param handler the function pointer to the callback
480 *
481 * @return A shared_ptr to the created handler object
482 */
483inline auto makeLegacyHandler(oem::Handler&& handler)
484{
485 HandlerBase::ptr ptr(
486 new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler)));
487 return ptr;
488}
Vernon Mauerye7329c72018-10-08 12:05:16 -0700489#endif // ALLOW_DEPRECATED_API
490
491/**
492 * @brief create an IPMI handler class and return a shared_ptr
493 *
494 * The queue uses a map of pointers to do the lookup. This function returns the
495 * shared_ptr that owns the Handler object.
496 *
497 * This is called internally via the ipmi::registerHandler function.
498 *
499 * @param handler the function pointer to the callback
500 *
501 * @return A shared_ptr to the created handler object
502 */
503template <typename Handler>
504inline auto makeHandler(Handler&& handler)
505{
506 HandlerBase::ptr ptr(
507 new IpmiHandler<Handler>(std::forward<Handler>(handler)));
508 return ptr;
509}
510
Vernon Mauerye08fbff2019-04-03 09:19:34 -0700511namespace impl
512{
513
514// IPMI command handler registration implementation
515bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
516 ::ipmi::HandlerBase::ptr handler);
517bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
518 ::ipmi::HandlerBase::ptr handler);
519bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
520 ::ipmi::HandlerBase::ptr handler);
521
522} // namespace impl
523
524/**
525 * @brief main IPMI handler registration function
526 *
527 * This function should be used to register all new-style IPMI handler
528 * functions. This function just passes the callback to makeHandler, which
529 * creates a new wrapper object that will automatically extract the appropriate
530 * parameters for the callback function as well as pack up the response.
531 *
532 * @param prio - priority at which to register; see api.hpp
533 * @param netFn - the IPMI net function number to register
534 * @param cmd - the IPMI command number to register
535 * @param priv - the IPMI user privilige required for this command
536 * @param handler - the callback function that will handle this request
537 *
538 * @return bool - success of registering the handler
539 */
540template <typename Handler>
541bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
542 Handler&& handler)
543{
544 auto h = ipmi::makeHandler(std::forward<Handler>(handler));
545 return impl::registerHandler(prio, netFn, cmd, priv, h);
546}
547
548/**
549 * @brief register a IPMI OEM group handler
550 *
551 * From IPMI 2.0 spec Network Function Codes Table (Row 2Ch):
552 * The first data byte position in requests and responses under this network
553 * function identifies the defining body that specifies command functionality.
554 * Software assumes that the command and completion code field positions will
555 * hold command and completion code values.
556 *
557 * The following values are used to identify the defining body:
558 * 00h PICMG - PCI Industrial Computer Manufacturer’s Group. (www.picmg.com)
559 * 01h DMTF Pre-OS Working Group ASF Specification (www.dmtf.org)
560 * 02h Server System Infrastructure (SSI) Forum (www.ssiforum.org)
561 * 03h VITA Standards Organization (VSO) (www.vita.com)
562 * DCh DCMI Specifications (www.intel.com/go/dcmi)
563 * all other Reserved
564 *
565 * When this network function is used, the ID for the defining body occupies
566 * the first data byte in a request, and the second data byte (following the
567 * completion code) in a response.
568 *
569 * @tparam Handler - implicitly specified callback function type
570 * @param prio - priority at which to register; see api.hpp
571 * @param netFn - the IPMI net function number to register
572 * @param cmd - the IPMI command number to register
573 * @param priv - the IPMI user privilige required for this command
574 * @param handler - the callback function that will handle this request
575 *
576 * @return bool - success of registering the handler
577 *
578 */
579template <typename Handler>
580void registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
581 Handler&& handler)
582{
583 auto h = ipmi::makeHandler(handler);
584 impl::registerGroupHandler(prio, group, cmd, priv, h);
585}
586
587/**
588 * @brief register a IPMI OEM IANA handler
589 *
590 * From IPMI spec Network Function Codes Table (Row 2Eh):
591 * The first three data bytes of requests and responses under this network
592 * function explicitly identify the OEM or non-IPMI group that specifies the
593 * command functionality. While the OEM or non-IPMI group defines the
594 * functional semantics for the cmd and remaining data fields, the cmd field
595 * is required to hold the same value in requests and responses for a given
596 * operation in order to be supported under the IPMI message handling and
597 * transport mechanisms.
598 *
599 * When this network function is used, the IANA Enterprise Number for the
600 * defining body occupies the first three data bytes in a request, and the
601 * first three data bytes following the completion code position in a
602 * response.
603 *
604 * @tparam Handler - implicitly specified callback function type
605 * @param prio - priority at which to register; see api.hpp
606 * @param netFn - the IPMI net function number to register
607 * @param cmd - the IPMI command number to register
608 * @param priv - the IPMI user privilige required for this command
609 * @param handler - the callback function that will handle this request
610 *
611 * @return bool - success of registering the handler
612 *
613 */
614template <typename Handler>
615void registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
616 Handler&& handler)
617{
618 auto h = ipmi::makeHandler(handler);
619 impl::registerOemHandler(prio, iana, cmd, priv, h);
620}
621
Vernon Mauerye7329c72018-10-08 12:05:16 -0700622} // namespace ipmi
Vernon Mauerye08fbff2019-04-03 09:19:34 -0700623
624#ifdef ALLOW_DEPRECATED_API
625/**
626 * @brief legacy IPMI handler registration function
627 *
628 * This function should be used to register all legacy IPMI handler
629 * functions. This function just behaves just as the legacy registration
630 * mechanism did, silently replacing any existing handler with a new one.
631 *
632 * @param netFn - the IPMI net function number to register
633 * @param cmd - the IPMI command number to register
634 * @param context - ignored
635 * @param handler - the callback function that will handle this request
636 * @param priv - the IPMI user privilige required for this command
637 */
638// [[deprecated("Use ipmi::registerHandler() instead")]]
639void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
640 ipmi_context_t context, ipmid_callback_t handler,
641 ipmi_cmd_privilege_t priv);
642
643#endif /* ALLOW_DEPRECATED_API */