1 /*
2 * Copyright (c) 2014, Cisco Systems, Inc. All rights reserved.
3 *
4 * LICENSE_BEGIN
5 *
6 * This software is available to you under a choice of one of two
7 * licenses. You may choose to be licensed under the terms of the GNU
8 * General Public License (GPL) Version 2, available from the file
9 * COPYING in the main directory of this source tree, or the
10 * BSD license below:
11 *
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
14 * conditions are met:
15 *
16 * - Redistributions of source code must retain the above
17 * copyright notice, this list of conditions and the following
18 * disclaimer.
19 *
20 * - Redistributions in binary form must reproduce the above
21 * copyright notice, this list of conditions and the following
22 * disclaimer in the documentation and/or other materials
23 * provided with the distribution.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
35 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 *
38 * LICENSE_END
39 *
40 *
41 */
42
43 #ifndef _USNIC_DIRECT_H_
44 #define _USNIC_DIRECT_H_
45
46 #include <sys/types.h>
47 #include <net/if.h>
48 #include <netinet/in.h>
49 #include <net/ethernet.h>
50 #include <netinet/ip.h>
51 #include <netinet/udp.h>
52
53 #define USD_MAX_DEVICES 8
54 #define USD_MAX_DEVNAME 16
55 #define USD_RECV_MAX_SGE 8
56 #define USD_SEND_MAX_SGE 8
57
58 enum usd_link_state {
59 USD_LINK_DOWN,
60 USD_LINK_UP
61 };
62
63 /* forward structure defs */
64 struct usd_context;
65 struct usd_qp;
66 struct usd_device;
67 struct usd_dest;
68 struct usd_connection;
69 struct usd_mr;
70
71 struct usd_device_attrs {
72 char uda_devname[USD_MAX_DEVNAME];
73 char uda_ifname[IFNAMSIZ];
74 int uda_ifindex;
75 uint8_t uda_mac_addr[ETH_ALEN];
76
77 /* IP config */
78 uint32_t uda_ipaddr_be;
79 uint32_t uda_netmask_be;
80 uint32_t uda_prefixlen; /* netmask length */
81 uint32_t uda_mtu;
82 enum usd_link_state uda_link_state;
83
84 /* HW info */
85 uint32_t uda_vendor_id;
86 uint32_t uda_vendor_part_id;
87 uint32_t uda_device_id;
88 char uda_firmware[64];
89
90 /* usnic config */
91 unsigned uda_num_vf;
92 unsigned uda_cq_per_vf;
93 unsigned uda_qp_per_vf;
94 unsigned uda_intr_per_vf;
95 unsigned uda_num_comp_vectors;
96 unsigned uda_max_cq;
97 unsigned uda_max_qp;
98
99 /* VIC constants */
100 uint32_t uda_bandwidth;
101 unsigned uda_max_cqe;
102 unsigned uda_max_send_credits;
103 unsigned uda_max_recv_credits;
104
105 /* fd that can be used to poll for device events */
106 int uda_event_fd;
107 };
108
109 enum usd_completion_status {
110 USD_COMPSTAT_SUCCESS,
111 USD_COMPSTAT_ERROR_CRC,
112 USD_COMPSTAT_ERROR_TRUNC,
113 USD_COMPSTAT_ERROR_TIMEOUT,
114 USD_COMPSTAT_ERROR_INTERNAL
115 };
116 enum usd_completion_type {
117 USD_COMPTYPE_SEND=0,
118 USD_COMPTYPE_RECV=7,
119 };
120
121 struct usd_completion {
122 enum usd_completion_status uc_status;
123 enum usd_completion_type uc_type;
124 uint32_t uc_bytes;
125 uint16_t uc_rkey;
126 struct usd_qp *uc_qp;
127 void *uc_context;
128 u_int16_t uc_retrans;
129 };
130
131 struct usd_recv_desc {
132 void *urd_context;
133 struct iovec urd_iov[USD_RECV_MAX_SGE];
134 size_t urd_iov_cnt;
135 struct usd_recv_desc *urd_next;
136 };
137
138 /*
139 * Operations that may vary based on transport/QP type
140 */
141 struct usd_qp_ops {
142 int (*qo_post_send_one)(struct usd_qp *qp,
143 struct usd_dest *dest, const void *buf, size_t len,
144 uint32_t flags, void *context);
145 int (*qo_post_send_one_prefixed)(struct usd_qp *qp,
146 struct usd_dest *dest, const void *buf, size_t len,
147 uint32_t flags, void *context);
148 int (*qo_post_send_one_copy)(struct usd_qp *qp,
149 struct usd_dest *dest, const void *buf, size_t len,
150 uint32_t flags, void *context);
151 int (*qo_post_send_two_copy)(struct usd_qp *qp,
152 struct usd_dest *dest, const void *hdr, size_t hdrlen,
153 const void *pkt, size_t pktlen, uint32_t flags, void *context);
154 int (*qo_post_send_iov)(struct usd_qp *qp,
155 struct usd_dest *dest, const struct iovec* iov,
156 size_t iov_count, uint32_t flags, void *context);
157 int (*qo_post_send_one_vlan)(struct usd_qp *qp,
158 struct usd_dest *dest, const void *buf, size_t len,
159 u_int16_t vlan, uint32_t flags, void *context);
160 };
161
162 /*
163 * user's view of a CQ
164 */
165 struct usd_cq {
166 unsigned ucq_num_entries;
167 };
168
169 /*
170 * User's view of a QP
171 */
172 struct usd_qp {
173 struct usd_qp_ops uq_ops;
174 void *uq_context; /* place for user to scribble */
175 };
176
177 /*
178 * Filters for QPs
179 */
180 enum usd_filter_type {
181 USD_FTY_UDP,
182 USD_FTY_UDP_SOCK,
183 USD_FTY_TCP,
184 USD_FTY_MCAST,
185 USD_FTY_8915
186 };
187 struct usd_filter {
188 enum usd_filter_type uf_type;
189 union {
190 struct {
191 uint16_t u_port;
192 } uf_udp;
193 struct {
194 int u_sock;
195 } uf_udp_sock;
196 struct {
197 int t_sock;
198 struct sockaddr_in t_remote;
199 } uf_tcp;
200 struct {
201 struct sockaddr_in m_addr;
202 } uf_mcast;
203 } uf_filter;
204 };
205
206 /*
207 * Local address - much like a filter
208 * Type is defined by transport specified in create_qp
209 */
210 struct usd_local_addr {
211 union {
212 struct {
213 struct sockaddr_in u_addr;
214 } ul_udp;
215 struct {
216 uint32_t qp_num;
217 } ul_8915;
218 } ul_addr;
219 };
220
221 enum usd_qp_transport {
222 USD_QTR_RAW, /* no header added */
223 USD_QTR_UDP /* create UDP header based on dest */
224 };
225
226 enum usd_qp_type {
227 USD_QTY_UD,
228 USD_QTY_UD_PIO,
229 };
230
231 /*
232 * Attributes of a queue pair
233 */
234 struct usd_qp_attrs {
235 enum usd_qp_transport uqa_transport;
236 enum usd_qp_type uqa_qtype;
237 struct usd_local_addr uqa_local_addr;
238
239 unsigned uqa_max_send_credits;
240 unsigned uqa_max_recv_credits;
241 uint64_t uqa_pio_paddr;
242
243 unsigned uqa_max_inline;
244 unsigned uqa_hdr_len; /* length of header for this QP */
245 };
246
247 /*
248 * Description of a device event which has occurred
249 */
250 enum usd_device_event_type {
251 USD_EVENT_LINK_UP,
252 USD_EVENT_LINK_DOWN
253 };
254 struct usd_device_event {
255 union {
256 void *ude_context;
257 } ude_context;
258 enum usd_device_event_type ude_type;
259 };
260
261 /*
262 * Returned form usd_get_available_devices() - array of currently
263 * available usd device names
264 */
265 struct usd_device_entry {
266 char ude_devname[USD_MAX_DEVNAME];
267 };
268
269 /*
270 * Send flags
271 */
272 enum usd_send_flag_shift {
273 USD_SFS_SIGNAL,
274 };
275 #define USD_SF_SIGNAL (1 << USD_SFS_SIGNAL)
276
277 /*
278 * cq creation parameters
279 */
280 struct usd_cq_init_attr {
281 unsigned num_entries; /* number of requested cq elements */
282 int comp_fd; /* completion fd */
283 int comp_vec; /* requested completion vector */
284 int comp_req_notify; /* whether need to request notify for each completion */
285 void *ibv_cq; /* verbs userspace cq object if signaling through uverbs */
286 };
287
288 /*
289 * Headers for defined transport types
290 */
291 struct usd_udp_hdr {
292 struct ether_header uh_eth;
293 struct iphdr uh_ip;
294 struct udphdr uh_udp;
295 } __attribute__ ((__packed__));
296
297 /*
298 * Struct and defines for usd open parameters
299 */
300 #define UOPF_SKIP_LINK_CHECK 0x1
301 #define UOPF_SKIP_PD_ALLOC 0x2
302
303 struct usd_open_params {
304 int flags;
305 int cmd_fd;
306 struct usd_context *context;
307 };
308
309 /*
310 ****************************************************************
311 * Device management
312 ****************************************************************
313 */
314 int usd_get_device_list(struct usd_device_entry *dev_array,
315 int *num_devs);
316
317 int usd_open(const char *devname, struct usd_device **dev_o);
318
319 int usd_open_with_params(const char *dev_name,
320 struct usd_open_params *uop_param,
321 struct usd_device **dev_o);
322
323 int usd_close(struct usd_device *dev);
324
325 int usd_get_device_attrs(struct usd_device *dev,
326 struct usd_device_attrs *attr);
327
328 int usd_get_device_event(struct usd_device *dev,
329 struct usd_device_event *event);
330
331 enum usd_capability {
332 USD_CAP_CQ_SHARING,
333 USD_CAP_MAP_PER_RES,
334 USD_CAP_PIO,
335 USD_CAP_CQ_INTR,
336 USD_CAP_GRP_INTR,
337 USD_CAP_MAX
338 };
339 int usd_get_cap(struct usd_device *dev, enum usd_capability cap);
340
341 /*
342 ****************************************************************
343 * Queue management
344 ****************************************************************
345 */
346
347 /*
348 * Get a file descriptor which can be used to poll for completions. The
349 * returned file descriptor will be different on each call to
350 * usd_get_completion_fd, so that coordination is not needed when using these
351 * fds in syscalls like poll(2).
352 */
353 int usd_get_completion_fd(struct usd_device *dev, int *comp_fd_o);
354
355 int usd_put_completion_fd(struct usd_device *dev, int comp_fd);
356
357 /*
358 * Request a CQ with specified attributes:
359 * dev - device on which to create this CQ
360 * init_attr - CQ creation parameters
361 */
362 int usd_create_cq(struct usd_device *dev, struct usd_cq_init_attr *init_attr,
363 struct usd_cq **cq_o);
364
365 int usd_destroy_cq(struct usd_cq *cq);
366
367 int usd_cq_intr_enable(struct usd_cq *cq);
368 int usd_cq_intr_disable(struct usd_cq *cq);
369
370 /*
371 * Get and set interrupt coalescing delay, units are in microseconds
372 */
373 int usd_cq_set_intr_coal(struct usd_cq *cq, unsigned intr_coal_delay);
374 unsigned usd_cq_get_intr_coal(struct usd_cq *cq);
375
376 /*
377 * IN:
378 * dev - device on which QP is to be created
379 * transport - what transport to use on this queue
380 * type - type of queue to create
381 * wcq - CQ handle for send completions
382 * rcq - CQ handle for receive completions
383 * send_credits - Number of send credits requested
384 * recv_credite - Number of receive buffer credits requested
385 * port - Requested local port for QP (0 lets library choose)
386 * qp_o - Address to receive QP handle on successful completion
387 * OUT:
388 * Returns 0 or code from errno.h
389 * 0 - successful completion
390 * EBUSY - port is in use
391 * XXX
392 */
393 int usd_create_qp(struct usd_device *dev,
394 enum usd_qp_transport transport,
395 enum usd_qp_type qtype,
396 struct usd_cq *wcq, struct usd_cq *rcq,
397 unsigned send_credits, unsigned recv_credits,
398 struct usd_filter *filt, struct usd_qp **qp_o);
399
400 int usd_destroy_qp(struct usd_qp *qp);
401
402 int usd_enable_qp(struct usd_qp *qp);
403 int usd_disable_qp(struct usd_qp *qp);
404
405 int usd_get_qp_attrs(struct usd_qp *qp,
406 struct usd_qp_attrs *qp_attrs_o);
407
408 /*
409 * Add a filter to a QP
410 */
411 int usd_qp_add_filter(struct usd_qp *qp, struct usd_filter *filter);
412
413 /*
414 * Get current send credits
415 */
416 unsigned usd_get_send_credits(struct usd_qp *uqp);
417
418 /*
419 * Get current recv credits
420 */
421 unsigned usd_get_recv_credits(struct usd_qp *uqp);
422
423 /*
424 ****************************************************************
425 * Memory management
426 ****************************************************************
427 */
428
429 int usd_reg_mr(struct usd_device *dev,
430 void *buffer, size_t size, struct usd_mr **mr_o);
431 int usd_dereg_mr(struct usd_mr *mr);
432
433 int usd_alloc_mr(struct usd_device *dev, size_t size, void **vaddr_o);
434 int usd_free_mr(void *vaddr);
435
436 /*
437 ****************************************************************
438 * Destination management
439 ****************************************************************
440 */
441
442 /*
443 * Return the distance metric to a specified IP address
444 * Metric is:
445 * 0 - same VLAN
446 * 1..MAXINT - relative distance metric
447 * -1 - unreachable
448 */
449 int usd_get_dest_distance(struct usd_device *dev, uint32_t daddr_be,
450 int *metric_o);
451
452 /*
453 * Settings for address resolution timeout and retry
454 */
455 struct usd_dest_params {
456 unsigned dp_arp_timeout; /* per-try timeout in ms */
457 unsigned dp_max_arps;
458 };
459
460 /*
461 * Get address resolution settings
462 */
463 int usd_get_dest_params(struct usd_dest_params *params);
464
465 /*
466 * Set address resolution settings
467 * Settings may not be changed while any resolution requests are in progress.
468 */
469 int usd_set_dest_params(struct usd_dest_params *params);
470
471 /*
472 * Used to create a destination with MAC address is already known.
473 */
474 int usd_create_dest_with_mac(struct usd_device *dev, uint32_t daddr_be,
475 uint16_t port_be, uint8_t *dmac, struct usd_dest **dest_o);
476
477 /*
478 * Synchronously creates a destination
479 */
480 int usd_create_dest(struct usd_device *dev, uint32_t daddr_be,
481 uint16_t port_be, struct usd_dest **dest_o);
482
483 /*
484 * Start the necessary ARP resolution to create a destination
485 * Resolution progress is performed in usd_create_dest_query() and
486 * usd_create_dest_poll()
487 */
488 int usd_create_dest_start(struct usd_device *dev, uint32_t daddr_be,
489 uint16_t dport_be, void *context);
490
491 /*
492 * Cancel resolution on a not-yet-completed create_dest request
493 */
494 int usd_create_dest_cancel(struct usd_device *dev, void *context);
495
496 /*
497 * Extract dest port and IP from a destination
498 */
499 int usd_expand_dest(struct usd_dest *dest, uint32_t *dest_ip_be_o,
500 uint16_t *dest_port_be_o);
501
502 /*
503 * Query completion status of a given create_dest request
504 * If complete, newly allocated destination is returned in dest_o
505 * Returns:
506 * 0 - request completed, *status is valid
507 * dest_o valid if *status == 0
508 * -EAGAIN - nothing is complete
509 * other - negative errno code
510 */
511 int usd_create_dest_query(struct usd_device *dev, void *context, int *status,
512 struct usd_dest **dest_o);
513
514 /*
515 * Checks for completed destination creation.
516 * context specified in call to usd_create_dest_start is returned,
517 * newly allocated destination is returned in dest_o
518 * Returns:
519 * 0 - request completed, status and context_o valid
520 * dest_o valid if *status == 0
521 * -EAGAIN - nothing is complete
522 * other - negative errno code
523 */
524 int usd_create_dest_poll(struct usd_device *dev, void **context_o, int *status,
525 struct usd_dest **dest_o);
526
527
528 int usd_destroy_dest(struct usd_dest *dest);
529
530 /*
531 ****************************************************************
532 * Sending, receiving, and completions
533 ****************************************************************
534 */
535
536 /*
537 * Post a receive. The number of receive credits consumed is equal
538 * to the number of entries in the SG list of the recv_desc, or
539 * recv_desc.urd_iov_cnt
540 */
541 int usd_post_recv(struct usd_qp *qp,
542 struct usd_recv_desc *recv_list);
543
544 int usd_poll_cq_multi(struct usd_cq *cq, int max_comps,
545 struct usd_completion *comps);
546 int usd_poll_cq(struct usd_cq *cq, struct usd_completion *comp);
547 int usd_poll_req_notify(struct usd_cq *ucq);
548
549 unsigned usd_get_send_credits(struct usd_qp *qp);
550
551 unsigned usd_get_recv_credits(struct usd_qp *qp);
552
553 /*
554 * post a single-buffer send from registered memory
555 * IN:
556 * qp
557 * dest
558 * buf -
559 * Requires 2 send credits
560 */
561 static inline int
usd_post_send_one(struct usd_qp * qp,struct usd_dest * dest,const void * buf,size_t len,uint32_t flags,void * context)562 usd_post_send_one(
563 struct usd_qp *qp,
564 struct usd_dest *dest,
565 const void *buf,
566 size_t len,
567 uint32_t flags,
568 void *context)
569 {
570 return qp->uq_ops.qo_post_send_one(
571 qp, dest, buf, len, flags, context);
572 }
573
574 /*
575 * post a single-buffer send from registered memory to specified VLAN
576 * IN:
577 * qp
578 * dest
579 * buf -
580 * Requires 2 send credits
581 */
582 static inline int
usd_post_send_one_vlan(struct usd_qp * qp,struct usd_dest * dest,const void * buf,size_t len,u_int16_t vlan,uint32_t flags,void * context)583 usd_post_send_one_vlan(
584 struct usd_qp *qp,
585 struct usd_dest *dest,
586 const void *buf,
587 size_t len,
588 u_int16_t vlan,
589 uint32_t flags,
590 void *context)
591 {
592 return qp->uq_ops.qo_post_send_one_vlan(
593 qp, dest, buf, len, vlan, flags, context);
594 }
595
596 /*
597 * post a single-buffer send from registered memory
598 * Caller must allow sufficient space *before* the packet for usd header
599 * For optimal efficieny, the buffer should be aligned on XXX boundary
600 * IN:
601 * qp
602 * dest
603 * buf -
604 * Requires 1 send credit
605 */
606 static inline int
usd_post_send_one_prefixed(struct usd_qp * qp,struct usd_dest * dest,const void * buf,size_t len,uint32_t flags,void * context)607 usd_post_send_one_prefixed(
608 struct usd_qp *qp,
609 struct usd_dest *dest,
610 const void *buf,
611 size_t len,
612 uint32_t flags,
613 void *context)
614 {
615 return qp->uq_ops.qo_post_send_one_prefixed(
616 qp, dest, buf, len, flags, context);
617 }
618
619 /*
620 * post a single-buffer send from anywhere
621 * Data is copied into registered memory by the lib for sending
622 * IN:
623 * qp
624 * dest
625 * buf -
626 * len - number of bytes in buffer, must be less than max_inline for the QP
627 * Requires 1 send credit
628 */
629 static inline int
usd_post_send_one_copy(struct usd_qp * qp,struct usd_dest * dest,const void * buf,size_t len,uint32_t flags,void * context)630 usd_post_send_one_copy(struct usd_qp *qp, struct usd_dest *dest,
631 const void *buf, size_t len, uint32_t flags, void *context)
632 {
633 return qp->uq_ops.qo_post_send_one_copy(
634 qp, dest, buf, len, flags, context);
635 }
636
637 /*
638 * post a two-buffer send, the first buffer is a usually a header and must
639 * allow space *before* it for our header.
640 * For optimal efficieny, the first buffer should be aligned XXX
641 * Requires 2 send credits
642 */
643 int usd_post_send_two_prefixed(struct usd_qp *qp, struct usd_dest *dest,
644 const void *hdr, size_t hdr_len, const void *pkt, size_t pkt_len,
645 uint32_t flags, void *context);
646
647 /*
648 * post a two-buffer send, the first buffer is a usually a header.
649 * The header and the packet will be both be copied into registered
650 * memory by usnic_direct and sent.
651 * Requires 2 send credits
652 */
653 static inline int
usd_post_send_two_copy(struct usd_qp * qp,struct usd_dest * dest,const void * hdr,size_t hdrlen,const void * pkt,size_t pktlen,uint32_t flags,void * context)654 usd_post_send_two_copy(struct usd_qp *qp, struct usd_dest *dest,
655 const void *hdr, size_t hdrlen, const void *pkt, size_t pktlen,
656 uint32_t flags, void *context)
657 {
658 return qp->uq_ops.qo_post_send_two_copy(
659 qp, dest, hdr, hdrlen, pkt, pktlen, flags, context);
660 }
661
662 /*
663 * Post an N-buffer send
664 * All buffers must be in registered memory.
665 * Requires iov_count + 1 send credits
666 */
667 static inline int
usd_post_send_iov(struct usd_qp * qp,struct usd_dest * dest,const struct iovec * iov,size_t iov_count,uint32_t flags,void * context)668 usd_post_send_iov(struct usd_qp *qp, struct usd_dest *dest,
669 const struct iovec *iov, size_t iov_count, uint32_t flags,
670 void *context)
671 {
672 return qp->uq_ops.qo_post_send_iov(
673 qp, dest, iov, iov_count, flags, context);
674 }
675
676 /****************************************************************
677 * enum-to-string utility functions (for prettyprinting)
678 ****************************************************************/
679
680 const char *usd_link_state_str(enum usd_link_state state);
681
682 const char *usd_completion_status_str(enum usd_completion_status cstatus);
683
684 const char *usd_completion_type_str(enum usd_completion_type ctype);
685
686 const char *usd_filter_type_str(enum usd_filter_type ftype);
687
688 const char *usd_qp_transport_str(enum usd_qp_transport qpt);
689
690 const char *usd_qp_type_str(enum usd_qp_type);
691
692 const char *usd_qp_event_event_type_str(enum usd_device_event_type det);
693
694 const char *usd_send_flag_sift_str(enum usd_send_flag_shift sfs);
695
696 const char *usd_capability(enum usd_capability cap);
697
698 const char *usd_devid_to_nicname(uint32_t vendor_id, uint32_t device_id);
699
700 const char *usd_devid_to_pid(uint32_t vendor_id, uint32_t device_id);
701
702 /****************************************************************
703 * special API holes punched for implementing verbs
704 ****************************************************************/
705 /* open a context, mapped to a verbs open_device call */
706 int usd_open_context(const char *dev_name, int cmd_fd,
707 struct usd_context **ctx_o);
708
709 int usd_close_context(struct usd_context *ctx);
710
711 /* modify the destination UDP port in a usd_dest */
712 void usd_dest_set_udp_ports(struct usd_dest *dest, struct usd_qp *src_qp,
713 uint16_t dest_port_be);
714
715 /* create a dest with only IP addresses set */
716 int usd_create_ip_dest(struct usd_device *dev, uint32_t dest_ip_be,
717 struct usd_dest **dest_o);
718
719 #endif /* _USNIC_DIRECT_H_ */
720