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