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