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