1 /*-
2 * Copyright (c) 2020 Mellanox Technologies. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS `AS IS' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26 #include "opt_inet.h"
27 #include "opt_inet6.h"
28
29 #include <sys/cdefs.h>
30 #include <sys/param.h>
31 #include <sys/systm.h>
32 #include <sys/devctl.h>
33 #include <sys/eventhandler.h>
34 #include <sys/kernel.h>
35 #include <sys/mbuf.h>
36 #include <sys/module.h>
37 #include <sys/socket.h>
38 #include <sys/sysctl.h>
39
40 #include <net/bpf.h>
41 #include <net/ethernet.h>
42 #include <net/infiniband.h>
43 #include <net/if.h>
44 #include <net/if_var.h>
45 #include <net/if_dl.h>
46 #include <net/if_media.h>
47 #include <net/if_lagg.h>
48 #include <net/if_llatbl.h>
49 #include <net/if_types.h>
50 #include <net/netisr.h>
51 #include <net/route.h>
52 #include <netinet/if_ether.h>
53 #include <netinet/in.h>
54 #include <netinet/ip6.h>
55 #include <netinet6/in6_var.h>
56 #include <netinet6/nd6.h>
57
58 #include <security/mac/mac_framework.h>
59
60 /* if_lagg(4) support */
61 struct mbuf *(*lagg_input_infiniband_p)(struct ifnet *, struct mbuf *);
62
63 #ifdef INET
64 static inline void
infiniband_ipv4_multicast_map(uint32_t addr,const uint8_t * broadcast,uint8_t * buf)65 infiniband_ipv4_multicast_map(uint32_t addr,
66 const uint8_t *broadcast, uint8_t *buf)
67 {
68 uint8_t scope;
69
70 addr = ntohl(addr);
71 scope = broadcast[5] & 0xF;
72
73 buf[0] = 0;
74 buf[1] = 0xff;
75 buf[2] = 0xff;
76 buf[3] = 0xff;
77 buf[4] = 0xff;
78 buf[5] = 0x10 | scope;
79 buf[6] = 0x40;
80 buf[7] = 0x1b;
81 buf[8] = broadcast[8];
82 buf[9] = broadcast[9];
83 buf[10] = 0;
84 buf[11] = 0;
85 buf[12] = 0;
86 buf[13] = 0;
87 buf[14] = 0;
88 buf[15] = 0;
89 buf[16] = (addr >> 24) & 0xff;
90 buf[17] = (addr >> 16) & 0xff;
91 buf[18] = (addr >> 8) & 0xff;
92 buf[19] = addr & 0xff;
93 }
94 #endif
95
96 #ifdef INET6
97 static inline void
infiniband_ipv6_multicast_map(const struct in6_addr * addr,const uint8_t * broadcast,uint8_t * buf)98 infiniband_ipv6_multicast_map(const struct in6_addr *addr,
99 const uint8_t *broadcast, uint8_t *buf)
100 {
101 uint8_t scope;
102
103 scope = broadcast[5] & 0xF;
104
105 buf[0] = 0;
106 buf[1] = 0xff;
107 buf[2] = 0xff;
108 buf[3] = 0xff;
109 buf[4] = 0xff;
110 buf[5] = 0x10 | scope;
111 buf[6] = 0x60;
112 buf[7] = 0x1b;
113 buf[8] = broadcast[8];
114 buf[9] = broadcast[9];
115 memcpy(&buf[10], &addr->s6_addr[6], 10);
116 }
117 #endif
118
119 /*
120 * This is for clients that have an infiniband_header in the mbuf.
121 */
122 void
infiniband_bpf_mtap(struct ifnet * ifp,struct mbuf * mb)123 infiniband_bpf_mtap(struct ifnet *ifp, struct mbuf *mb)
124 {
125 struct infiniband_header *ibh;
126 struct ether_header eh;
127
128 if (mb->m_len < sizeof(*ibh))
129 return;
130
131 ibh = mtod(mb, struct infiniband_header *);
132 eh.ether_type = ibh->ib_protocol;
133 memset(eh.ether_shost, 0, ETHER_ADDR_LEN);
134 memcpy(eh.ether_dhost, ibh->ib_hwaddr + 4, ETHER_ADDR_LEN);
135 mb->m_data += sizeof(*ibh);
136 mb->m_len -= sizeof(*ibh);
137 mb->m_pkthdr.len -= sizeof(*ibh);
138 bpf_mtap2(ifp->if_bpf, &eh, sizeof(eh), mb);
139 mb->m_data -= sizeof(*ibh);
140 mb->m_len += sizeof(*ibh);
141 mb->m_pkthdr.len += sizeof(*ibh);
142 }
143
144 static void
update_mbuf_csumflags(struct mbuf * src,struct mbuf * dst)145 update_mbuf_csumflags(struct mbuf *src, struct mbuf *dst)
146 {
147 int csum_flags = 0;
148
149 if (src->m_pkthdr.csum_flags & CSUM_IP)
150 csum_flags |= (CSUM_IP_CHECKED|CSUM_IP_VALID);
151 if (src->m_pkthdr.csum_flags & CSUM_DELAY_DATA)
152 csum_flags |= (CSUM_DATA_VALID|CSUM_PSEUDO_HDR);
153 if (src->m_pkthdr.csum_flags & CSUM_SCTP)
154 csum_flags |= CSUM_SCTP_VALID;
155 dst->m_pkthdr.csum_flags |= csum_flags;
156 if (csum_flags & CSUM_DATA_VALID)
157 dst->m_pkthdr.csum_data = 0xffff;
158 }
159
160 /*
161 * Handle link-layer encapsulation requests.
162 */
163 static int
infiniband_requestencap(struct ifnet * ifp,struct if_encap_req * req)164 infiniband_requestencap(struct ifnet *ifp, struct if_encap_req *req)
165 {
166 struct infiniband_header *ih;
167 struct arphdr *ah;
168 uint16_t etype;
169 const uint8_t *lladdr;
170
171 if (req->rtype != IFENCAP_LL)
172 return (EOPNOTSUPP);
173
174 if (req->bufsize < INFINIBAND_HDR_LEN)
175 return (ENOMEM);
176
177 ih = (struct infiniband_header *)req->buf;
178 lladdr = req->lladdr;
179 req->lladdr_off = 0;
180
181 switch (req->family) {
182 case AF_INET:
183 etype = htons(ETHERTYPE_IP);
184 break;
185 case AF_INET6:
186 etype = htons(ETHERTYPE_IPV6);
187 break;
188 case AF_ARP:
189 ah = (struct arphdr *)req->hdata;
190 ah->ar_hrd = htons(ARPHRD_INFINIBAND);
191
192 switch (ntohs(ah->ar_op)) {
193 case ARPOP_REVREQUEST:
194 case ARPOP_REVREPLY:
195 etype = htons(ETHERTYPE_REVARP);
196 break;
197 case ARPOP_REQUEST:
198 case ARPOP_REPLY:
199 default:
200 etype = htons(ETHERTYPE_ARP);
201 break;
202 }
203
204 if (req->flags & IFENCAP_FLAG_BROADCAST)
205 lladdr = ifp->if_broadcastaddr;
206 break;
207 default:
208 return (EAFNOSUPPORT);
209 }
210
211 ih->ib_protocol = etype;
212 ih->ib_reserved = 0;
213 memcpy(ih->ib_hwaddr, lladdr, INFINIBAND_ADDR_LEN);
214 req->bufsize = sizeof(struct infiniband_header);
215
216 return (0);
217 }
218
219 static int
infiniband_resolve_addr(struct ifnet * ifp,struct mbuf * m,const struct sockaddr * dst,struct route * ro,uint8_t * phdr,uint32_t * pflags,struct llentry ** plle)220 infiniband_resolve_addr(struct ifnet *ifp, struct mbuf *m,
221 const struct sockaddr *dst, struct route *ro, uint8_t *phdr,
222 uint32_t *pflags, struct llentry **plle)
223 {
224 struct infiniband_header *ih;
225 uint32_t lleflags = 0;
226 int error = 0;
227
228 if (plle)
229 *plle = NULL;
230 ih = (struct infiniband_header *)phdr;
231
232 switch (dst->sa_family) {
233 #ifdef INET
234 case AF_INET:
235 if ((m->m_flags & (M_BCAST | M_MCAST)) == 0) {
236 error = arpresolve(ifp, 0, m, dst, phdr, &lleflags, plle);
237 } else {
238 if (m->m_flags & M_BCAST) {
239 memcpy(ih->ib_hwaddr, ifp->if_broadcastaddr,
240 INFINIBAND_ADDR_LEN);
241 } else {
242 infiniband_ipv4_multicast_map(
243 ((const struct sockaddr_in *)dst)->sin_addr.s_addr,
244 ifp->if_broadcastaddr, ih->ib_hwaddr);
245 }
246 ih->ib_protocol = htons(ETHERTYPE_IP);
247 ih->ib_reserved = 0;
248 }
249 break;
250 #endif
251 #ifdef INET6
252 case AF_INET6:
253 if ((m->m_flags & M_MCAST) == 0) {
254 int af = RO_GET_FAMILY(ro, dst);
255 error = nd6_resolve(ifp, LLE_SF(af, 0), m, dst, phdr,
256 &lleflags, plle);
257 } else {
258 infiniband_ipv6_multicast_map(
259 &((const struct sockaddr_in6 *)dst)->sin6_addr,
260 ifp->if_broadcastaddr, ih->ib_hwaddr);
261 ih->ib_protocol = htons(ETHERTYPE_IPV6);
262 ih->ib_reserved = 0;
263 }
264 break;
265 #endif
266 default:
267 if_printf(ifp, "can't handle af%d\n", dst->sa_family);
268 if (m != NULL)
269 m_freem(m);
270 return (EAFNOSUPPORT);
271 }
272
273 if (error == EHOSTDOWN) {
274 if (ro != NULL && (ro->ro_flags & RT_HAS_GW) != 0)
275 error = EHOSTUNREACH;
276 }
277
278 if (error != 0)
279 return (error);
280
281 *pflags = RT_MAY_LOOP;
282 if (lleflags & LLE_IFADDR)
283 *pflags |= RT_L2_ME;
284
285 return (0);
286 }
287
288 /*
289 * Infiniband output routine.
290 */
291 static int
infiniband_output(struct ifnet * ifp,struct mbuf * m,const struct sockaddr * dst,struct route * ro)292 infiniband_output(struct ifnet *ifp, struct mbuf *m,
293 const struct sockaddr *dst, struct route *ro)
294 {
295 uint8_t linkhdr[INFINIBAND_HDR_LEN];
296 uint8_t *phdr;
297 struct llentry *lle = NULL;
298 struct infiniband_header *ih;
299 int error = 0;
300 int hlen; /* link layer header length */
301 uint32_t pflags;
302 bool addref;
303
304 NET_EPOCH_ASSERT();
305
306 addref = false;
307 phdr = NULL;
308 pflags = 0;
309 if (ro != NULL) {
310 /* XXX BPF uses ro_prepend */
311 if (ro->ro_prepend != NULL) {
312 phdr = ro->ro_prepend;
313 hlen = ro->ro_plen;
314 } else if (!(m->m_flags & (M_BCAST | M_MCAST))) {
315 if ((ro->ro_flags & RT_LLE_CACHE) != 0) {
316 lle = ro->ro_lle;
317 if (lle != NULL &&
318 (lle->la_flags & LLE_VALID) == 0) {
319 LLE_FREE(lle);
320 lle = NULL; /* redundant */
321 ro->ro_lle = NULL;
322 }
323 if (lle == NULL) {
324 /* if we lookup, keep cache */
325 addref = 1;
326 } else
327 /*
328 * Notify LLE code that
329 * the entry was used
330 * by datapath.
331 */
332 llentry_provide_feedback(lle);
333 }
334 if (lle != NULL) {
335 phdr = lle->r_linkdata;
336 hlen = lle->r_hdrlen;
337 pflags = lle->r_flags;
338 }
339 }
340 }
341
342 #ifdef MAC
343 error = mac_ifnet_check_transmit(ifp, m);
344 if (error)
345 goto bad;
346 #endif
347
348 M_PROFILE(m);
349 if (ifp->if_flags & IFF_MONITOR) {
350 error = ENETDOWN;
351 goto bad;
352 }
353 if (!((ifp->if_flags & IFF_UP) &&
354 (ifp->if_drv_flags & IFF_DRV_RUNNING))) {
355 error = ENETDOWN;
356 goto bad;
357 }
358
359 if (phdr == NULL) {
360 /* No prepend data supplied. Try to calculate ourselves. */
361 phdr = linkhdr;
362 hlen = INFINIBAND_HDR_LEN;
363 error = infiniband_resolve_addr(ifp, m, dst, ro, phdr, &pflags,
364 addref ? &lle : NULL);
365 if (addref && lle != NULL)
366 ro->ro_lle = lle;
367 if (error != 0)
368 return (error == EWOULDBLOCK ? 0 : error);
369 }
370
371 if ((pflags & RT_L2_ME) != 0) {
372 update_mbuf_csumflags(m, m);
373 return (if_simloop(ifp, m, RO_GET_FAMILY(ro, dst), 0));
374 }
375
376 /*
377 * Add local infiniband header. If no space in first mbuf,
378 * allocate another.
379 */
380 M_PREPEND(m, INFINIBAND_HDR_LEN, M_NOWAIT);
381 if (m == NULL) {
382 error = ENOBUFS;
383 goto bad;
384 }
385 if ((pflags & RT_HAS_HEADER) == 0) {
386 ih = mtod(m, struct infiniband_header *);
387 memcpy(ih, phdr, hlen);
388 }
389
390 /*
391 * Queue message on interface, update output statistics if
392 * successful, and start output if interface not yet active.
393 */
394 return (ifp->if_transmit(ifp, m));
395 bad:
396 if (m != NULL)
397 m_freem(m);
398 return (error);
399 }
400
401 /*
402 * Process a received Infiniband packet.
403 */
404 static void
infiniband_input(struct ifnet * ifp,struct mbuf * m)405 infiniband_input(struct ifnet *ifp, struct mbuf *m)
406 {
407 struct infiniband_header *ibh;
408 struct epoch_tracker et;
409 int isr;
410 bool needs_epoch;
411
412 needs_epoch = (ifp->if_flags & IFF_KNOWSEPOCH) == 0;
413
414 CURVNET_SET_QUIET(ifp->if_vnet);
415 if (__predict_false(needs_epoch))
416 NET_EPOCH_ENTER(et);
417
418 if ((ifp->if_flags & IFF_UP) == 0) {
419 if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
420 m_freem(m);
421 goto done;
422 }
423
424 ibh = mtod(m, struct infiniband_header *);
425
426 /*
427 * Reset layer specific mbuf flags to avoid confusing upper
428 * layers:
429 */
430 m->m_flags &= ~M_VLANTAG;
431 m_clrprotoflags(m);
432
433 if (INFINIBAND_IS_MULTICAST(ibh->ib_hwaddr)) {
434 if (memcmp(ibh->ib_hwaddr, ifp->if_broadcastaddr,
435 ifp->if_addrlen) == 0)
436 m->m_flags |= M_BCAST;
437 else
438 m->m_flags |= M_MCAST;
439 if_inc_counter(ifp, IFCOUNTER_IMCASTS, 1);
440 }
441
442 /* Let BPF have it before we strip the header. */
443 INFINIBAND_BPF_MTAP(ifp, m);
444
445 /* Allow monitor mode to claim this frame, after stats are updated. */
446 if (ifp->if_flags & IFF_MONITOR) {
447 m_freem(m);
448 goto done;
449 }
450
451 /* Direct packet to correct FIB based on interface config. */
452 M_SETFIB(m, ifp->if_fib);
453
454 /* Handle input from a lagg<N> port */
455 if (ifp->if_type == IFT_INFINIBANDLAG) {
456 KASSERT(lagg_input_infiniband_p != NULL,
457 ("%s: if_lagg not loaded!", __func__));
458 m = (*lagg_input_infiniband_p)(ifp, m);
459 if (__predict_false(m == NULL))
460 goto done;
461 ifp = m->m_pkthdr.rcvif;
462 }
463
464 /*
465 * Dispatch frame to upper layer.
466 */
467 switch (ibh->ib_protocol) {
468 #ifdef INET
469 case htons(ETHERTYPE_IP):
470 isr = NETISR_IP;
471 break;
472
473 case htons(ETHERTYPE_ARP):
474 if (ifp->if_flags & IFF_NOARP) {
475 /* Discard packet if ARP is disabled on interface */
476 m_freem(m);
477 goto done;
478 }
479 isr = NETISR_ARP;
480 break;
481 #endif
482 #ifdef INET6
483 case htons(ETHERTYPE_IPV6):
484 isr = NETISR_IPV6;
485 break;
486 #endif
487 default:
488 if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
489 m_freem(m);
490 goto done;
491 }
492
493 /* Strip off the Infiniband header. */
494 m_adj(m, INFINIBAND_HDR_LEN);
495
496 #ifdef MAC
497 /*
498 * Tag the mbuf with an appropriate MAC label before any other
499 * consumers can get to it.
500 */
501 mac_ifnet_create_mbuf(ifp, m);
502 #endif
503 /* Allow monitor mode to claim this frame, after stats are updated. */
504 netisr_dispatch(isr, m);
505 done:
506 if (__predict_false(needs_epoch))
507 NET_EPOCH_EXIT(et);
508 CURVNET_RESTORE();
509 }
510
511 static int
infiniband_resolvemulti(struct ifnet * ifp,struct sockaddr ** llsa,struct sockaddr * sa)512 infiniband_resolvemulti(struct ifnet *ifp, struct sockaddr **llsa,
513 struct sockaddr *sa)
514 {
515 struct sockaddr_dl *sdl;
516 #ifdef INET
517 struct sockaddr_in *sin;
518 #endif
519 #ifdef INET6
520 struct sockaddr_in6 *sin6;
521 #endif
522 uint8_t *e_addr;
523
524 switch (sa->sa_family) {
525 case AF_LINK:
526 /*
527 * No mapping needed. Just check that it's a valid MC address.
528 */
529 sdl = (struct sockaddr_dl *)sa;
530 e_addr = LLADDR(sdl);
531 if (!INFINIBAND_IS_MULTICAST(e_addr))
532 return (EADDRNOTAVAIL);
533 *llsa = NULL;
534 return 0;
535
536 #ifdef INET
537 case AF_INET:
538 sin = (struct sockaddr_in *)sa;
539 if (!IN_MULTICAST(ntohl(sin->sin_addr.s_addr)))
540 return (EADDRNOTAVAIL);
541 sdl = link_init_sdl(ifp, *llsa, IFT_INFINIBAND);
542 sdl->sdl_alen = INFINIBAND_ADDR_LEN;
543 e_addr = LLADDR(sdl);
544 infiniband_ipv4_multicast_map(
545 sin->sin_addr.s_addr, ifp->if_broadcastaddr, e_addr);
546 *llsa = (struct sockaddr *)sdl;
547 return (0);
548 #endif
549 #ifdef INET6
550 case AF_INET6:
551 sin6 = (struct sockaddr_in6 *)sa;
552 /*
553 * An IP6 address of 0 means listen to all of the
554 * multicast address used for IP6. This has no meaning
555 * in infiniband.
556 */
557 if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
558 return (EADDRNOTAVAIL);
559 if (!IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr))
560 return (EADDRNOTAVAIL);
561 sdl = link_init_sdl(ifp, *llsa, IFT_INFINIBAND);
562 sdl->sdl_alen = INFINIBAND_ADDR_LEN;
563 e_addr = LLADDR(sdl);
564 infiniband_ipv6_multicast_map(
565 &sin6->sin6_addr, ifp->if_broadcastaddr, e_addr);
566 *llsa = (struct sockaddr *)sdl;
567 return (0);
568 #endif
569 default:
570 return (EAFNOSUPPORT);
571 }
572 }
573
574 void
infiniband_ifattach(struct ifnet * ifp,const uint8_t * lla,const uint8_t * llb)575 infiniband_ifattach(struct ifnet *ifp, const uint8_t *lla, const uint8_t *llb)
576 {
577 struct sockaddr_dl *sdl;
578 struct ifaddr *ifa;
579 int i;
580
581 ifp->if_addrlen = INFINIBAND_ADDR_LEN;
582 ifp->if_hdrlen = INFINIBAND_HDR_LEN;
583 ifp->if_mtu = INFINIBAND_MTU;
584 if_attach(ifp);
585 ifp->if_output = infiniband_output;
586 ifp->if_input = infiniband_input;
587 ifp->if_resolvemulti = infiniband_resolvemulti;
588 ifp->if_requestencap = infiniband_requestencap;
589
590 if (ifp->if_baudrate == 0)
591 ifp->if_baudrate = IF_Gbps(10); /* default value */
592 if (llb != NULL)
593 ifp->if_broadcastaddr = llb;
594
595 ifa = ifp->if_addr;
596 KASSERT(ifa != NULL, ("%s: no lladdr!\n", __func__));
597 sdl = (struct sockaddr_dl *)ifa->ifa_addr;
598 sdl->sdl_type = IFT_INFINIBAND;
599 sdl->sdl_alen = ifp->if_addrlen;
600
601 if (lla != NULL) {
602 memcpy(LLADDR(sdl), lla, ifp->if_addrlen);
603
604 if (ifp->if_hw_addr != NULL)
605 memcpy(ifp->if_hw_addr, lla, ifp->if_addrlen);
606 } else {
607 lla = LLADDR(sdl);
608 }
609
610 /* Attach ethernet compatible network device */
611 bpfattach(ifp, DLT_EN10MB, ETHER_HDR_LEN);
612
613 /* Announce Infiniband MAC address if non-zero. */
614 for (i = 0; i < ifp->if_addrlen; i++)
615 if (lla[i] != 0)
616 break;
617 if (i != ifp->if_addrlen)
618 if_printf(ifp, "Infiniband address: %20D\n", lla, ":");
619
620 /* Add necessary bits are setup; announce it now. */
621 EVENTHANDLER_INVOKE(infiniband_ifattach_event, ifp);
622
623 if (IS_DEFAULT_VNET(curvnet))
624 devctl_notify("INFINIBAND", ifp->if_xname, "IFATTACH", NULL);
625 }
626
627 /*
628 * Perform common duties while detaching an Infiniband interface
629 */
630 void
infiniband_ifdetach(struct ifnet * ifp)631 infiniband_ifdetach(struct ifnet *ifp)
632 {
633 bpfdetach(ifp);
634 if_detach(ifp);
635 }
636
637 static int
infiniband_modevent(module_t mod,int type,void * data)638 infiniband_modevent(module_t mod, int type, void *data)
639 {
640 switch (type) {
641 case MOD_LOAD:
642 case MOD_UNLOAD:
643 return (0);
644 default:
645 return (EOPNOTSUPP);
646 }
647 }
648
649 static moduledata_t infiniband_mod = {
650 .name = "if_infiniband",
651 .evhand = &infiniband_modevent,
652 };
653
654 DECLARE_MODULE(if_infiniband, infiniband_mod, SI_SUB_INIT_IF, SI_ORDER_ANY);
655 MODULE_VERSION(if_infiniband, 1);
656