1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * Xen network backend - mac client edition. 29 * 30 * A driver that sits above an existing GLDv3/Nemo MAC driver and 31 * relays packets to/from that driver from/to a guest domain. 32 */ 33 34 #ifdef DEBUG 35 #define XNBO_DEBUG 1 36 #endif /* DEBUG */ 37 38 #include "xnb.h" 39 40 #include <sys/sunddi.h> 41 #include <sys/ddi.h> 42 #include <sys/modctl.h> 43 #include <sys/strsubr.h> 44 #include <sys/mac_client.h> 45 #include <sys/mac_provider.h> 46 #include <sys/mac_client_priv.h> 47 #include <sys/mac.h> 48 #include <net/if.h> 49 #include <sys/dlpi.h> 50 #include <sys/pattr.h> 51 #include <xen/sys/xenbus_impl.h> 52 #include <xen/sys/xendev.h> 53 #include <sys/sdt.h> 54 #include <sys/note.h> 55 56 #ifdef XNBO_DEBUG 57 boolean_t xnbo_cksum_offload_to_peer = B_TRUE; 58 boolean_t xnbo_cksum_offload_from_peer = B_TRUE; 59 #endif /* XNBO_DEBUG */ 60 61 /* Track multicast addresses. */ 62 typedef struct xmca { 63 struct xmca *next; 64 ether_addr_t addr; 65 } xmca_t; 66 67 /* State about this device instance. */ 68 typedef struct xnbo { 69 mac_handle_t o_mh; 70 mac_client_handle_t o_mch; 71 mac_unicast_handle_t o_mah; 72 mac_promisc_handle_t o_mphp; 73 boolean_t o_running; 74 boolean_t o_promiscuous; 75 uint32_t o_hcksum_capab; 76 xmca_t *o_mca; 77 char o_link_name[LIFNAMSIZ]; 78 boolean_t o_need_rx_filter; 79 boolean_t o_need_setphysaddr; 80 boolean_t o_multicast_control; 81 } xnbo_t; 82 83 static void xnbo_close_mac(xnb_t *); 84 static void i_xnbo_close_mac(xnb_t *, boolean_t); 85 86 /* 87 * Packets from the peer come here. We pass them to the mac device. 88 */ 89 static void 90 xnbo_to_mac(xnb_t *xnbp, mblk_t *mp) 91 { 92 xnbo_t *xnbop = xnbp->xnb_flavour_data; 93 94 ASSERT(mp != NULL); 95 96 if (!xnbop->o_running) { 97 xnbp->xnb_stat_tx_too_early++; 98 goto fail; 99 } 100 101 if (mac_tx(xnbop->o_mch, mp, 0, 102 MAC_DROP_ON_NO_DESC, NULL) != NULL) { 103 xnbp->xnb_stat_mac_full++; 104 } 105 106 return; 107 108 fail: 109 freemsgchain(mp); 110 } 111 112 /* 113 * Process the checksum flags `flags' provided by the peer for the 114 * packet `mp'. 115 */ 116 static mblk_t * 117 xnbo_cksum_from_peer(xnb_t *xnbp, mblk_t *mp, uint16_t flags) 118 { 119 xnbo_t *xnbop = xnbp->xnb_flavour_data; 120 121 ASSERT(mp->b_next == NULL); 122 123 if ((flags & NETTXF_csum_blank) != 0) { 124 uint32_t capab = xnbop->o_hcksum_capab; 125 126 #ifdef XNBO_DEBUG 127 if (!xnbo_cksum_offload_from_peer) 128 capab = 0; 129 #endif /* XNBO_DEBUG */ 130 131 /* 132 * The checksum in the packet is blank. Determine 133 * whether we can do hardware offload and, if so, 134 * update the flags on the mblk according. If not, 135 * calculate and insert the checksum using software. 136 */ 137 mp = xnb_process_cksum_flags(xnbp, mp, capab); 138 } 139 140 return (mp); 141 } 142 143 /* 144 * Calculate the checksum flags to be relayed to the peer for the 145 * packet `mp'. 146 */ 147 static uint16_t 148 xnbo_cksum_to_peer(xnb_t *xnbp, mblk_t *mp) 149 { 150 _NOTE(ARGUNUSED(xnbp)); 151 uint16_t r = 0; 152 uint32_t pflags, csum; 153 154 #ifdef XNBO_DEBUG 155 if (!xnbo_cksum_offload_to_peer) 156 return (0); 157 #endif /* XNBO_DEBUG */ 158 159 /* 160 * We might also check for HCK_PARTIALCKSUM here and, 161 * providing that the partial checksum covers the TCP/UDP 162 * payload, return NETRXF_data_validated. 163 * 164 * It seems that it's probably not worthwhile, as even MAC 165 * devices which advertise HCKSUM_INET_PARTIAL in their 166 * capabilities tend to use HCK_FULLCKSUM on the receive side 167 * - they are actually saying that in the output path the 168 * caller must use HCK_PARTIALCKSUM. 169 * 170 * Then again, if a NIC supports HCK_PARTIALCKSUM in its' 171 * output path, the host IP stack will use it. If such packets 172 * are destined for the peer (i.e. looped around) we would 173 * gain some advantage. 174 */ 175 176 hcksum_retrieve(mp, NULL, NULL, NULL, NULL, 177 NULL, &csum, &pflags); 178 179 /* 180 * If the MAC driver has asserted that the checksum is 181 * good, let the peer know. 182 */ 183 if (((pflags & HCK_FULLCKSUM) != 0) && 184 (((pflags & HCK_FULLCKSUM_OK) != 0) || 185 (csum == 0xffff))) 186 r |= NETRXF_data_validated; 187 188 return (r); 189 } 190 191 /* 192 * Packets from the mac device come here. We pass them to the peer. 193 */ 194 /*ARGSUSED*/ 195 static void 196 xnbo_from_mac(void *arg, mac_resource_handle_t mrh, mblk_t *mp, 197 boolean_t loopback) 198 { 199 xnb_t *xnbp = arg; 200 201 mp = xnb_copy_to_peer(xnbp, mp); 202 203 if (mp != NULL) 204 freemsgchain(mp); 205 } 206 207 /* 208 * Packets from the mac device come here. We pass them to the peer if 209 * the destination mac address matches or it's a multicast/broadcast 210 * address. 211 */ 212 static void 213 xnbo_from_mac_filter(void *arg, mac_resource_handle_t mrh, mblk_t *mp, 214 boolean_t loopback) 215 { 216 _NOTE(ARGUNUSED(loopback)); 217 xnb_t *xnbp = arg; 218 xnbo_t *xnbop = xnbp->xnb_flavour_data; 219 mblk_t *next, *keep, *keep_head, *free, *free_head; 220 221 keep = keep_head = free = free_head = NULL; 222 223 #define ADD(list, bp) \ 224 if (list != NULL) \ 225 list->b_next = bp; \ 226 else \ 227 list##_head = bp; \ 228 list = bp; 229 230 for (; mp != NULL; mp = next) { 231 mac_header_info_t hdr_info; 232 233 next = mp->b_next; 234 mp->b_next = NULL; 235 236 if (mac_header_info(xnbop->o_mh, mp, &hdr_info) != 0) { 237 ADD(free, mp); 238 continue; 239 } 240 241 if ((hdr_info.mhi_dsttype == MAC_ADDRTYPE_BROADCAST) || 242 (hdr_info.mhi_dsttype == MAC_ADDRTYPE_MULTICAST)) { 243 ADD(keep, mp); 244 continue; 245 } 246 247 if (bcmp(hdr_info.mhi_daddr, xnbp->xnb_mac_addr, 248 sizeof (xnbp->xnb_mac_addr)) == 0) { 249 ADD(keep, mp); 250 continue; 251 } 252 253 ADD(free, mp); 254 } 255 #undef ADD 256 257 if (keep_head != NULL) 258 xnbo_from_mac(xnbp, mrh, keep_head, B_FALSE); 259 260 if (free_head != NULL) 261 freemsgchain(free_head); 262 } 263 264 static boolean_t 265 xnbo_open_mac(xnb_t *xnbp, char *mac) 266 { 267 xnbo_t *xnbop = xnbp->xnb_flavour_data; 268 int err; 269 const mac_info_t *mi; 270 void (*rx_fn)(void *, mac_resource_handle_t, mblk_t *, boolean_t); 271 struct ether_addr ea; 272 uint_t max_sdu; 273 mac_diag_t diag; 274 275 if ((err = mac_open_by_linkname(mac, &xnbop->o_mh)) != 0) { 276 cmn_err(CE_WARN, "xnbo_open_mac: " 277 "cannot open mac for link %s (%d)", mac, err); 278 return (B_FALSE); 279 } 280 ASSERT(xnbop->o_mh != NULL); 281 282 mi = mac_info(xnbop->o_mh); 283 ASSERT(mi != NULL); 284 285 if (mi->mi_media != DL_ETHER) { 286 cmn_err(CE_WARN, "xnbo_open_mac: " 287 "device is not DL_ETHER (%d)", mi->mi_media); 288 i_xnbo_close_mac(xnbp, B_TRUE); 289 return (B_FALSE); 290 } 291 if (mi->mi_media != mi->mi_nativemedia) { 292 cmn_err(CE_WARN, "xnbo_open_mac: " 293 "device media and native media mismatch (%d != %d)", 294 mi->mi_media, mi->mi_nativemedia); 295 i_xnbo_close_mac(xnbp, B_TRUE); 296 return (B_FALSE); 297 } 298 299 mac_sdu_get(xnbop->o_mh, NULL, &max_sdu); 300 if (max_sdu > XNBMAXPKT) { 301 cmn_err(CE_WARN, "xnbo_open_mac: mac device SDU too big (%d)", 302 max_sdu); 303 i_xnbo_close_mac(xnbp, B_TRUE); 304 return (B_FALSE); 305 } 306 307 /* 308 * MAC_OPEN_FLAGS_MULTI_PRIMARY is relevant when we are migrating a 309 * guest on the localhost itself. In this case we would have the MAC 310 * client open for the guest being migrated *and* also for the 311 * migrated guest (i.e. the former will be active till the migration 312 * is complete when the latter will be activated). This flag states 313 * that it is OK for mac_unicast_add to add the primary MAC unicast 314 * address multiple times. 315 */ 316 if (mac_client_open(xnbop->o_mh, &xnbop->o_mch, NULL, 317 MAC_OPEN_FLAGS_USE_DATALINK_NAME | 318 MAC_OPEN_FLAGS_MULTI_PRIMARY) != 0) { 319 cmn_err(CE_WARN, "xnbo_open_mac: " 320 "error (%d) opening mac client", err); 321 i_xnbo_close_mac(xnbp, B_TRUE); 322 return (B_FALSE); 323 } 324 325 if (xnbop->o_need_rx_filter) 326 rx_fn = xnbo_from_mac_filter; 327 else 328 rx_fn = xnbo_from_mac; 329 330 err = mac_unicast_add_set_rx(xnbop->o_mch, NULL, MAC_UNICAST_PRIMARY, 331 &xnbop->o_mah, 0, &diag, xnbop->o_multicast_control ? rx_fn : NULL, 332 xnbp); 333 if (err != 0) { 334 cmn_err(CE_WARN, "xnbo_open_mac: failed to get the primary " 335 "MAC address of %s: %d", mac, err); 336 i_xnbo_close_mac(xnbp, B_TRUE); 337 return (B_FALSE); 338 } 339 if (!xnbop->o_multicast_control) { 340 err = mac_promisc_add(xnbop->o_mch, MAC_CLIENT_PROMISC_ALL, 341 rx_fn, xnbp, &xnbop->o_mphp, MAC_PROMISC_FLAGS_NO_TX_LOOP | 342 MAC_PROMISC_FLAGS_VLAN_TAG_STRIP); 343 if (err != 0) { 344 cmn_err(CE_WARN, "xnbo_open_mac: " 345 "cannot enable promiscuous mode of %s: %d", 346 mac, err); 347 i_xnbo_close_mac(xnbp, B_TRUE); 348 return (B_FALSE); 349 } 350 xnbop->o_promiscuous = B_TRUE; 351 } 352 353 if (xnbop->o_need_setphysaddr) { 354 err = mac_unicast_primary_set(xnbop->o_mh, xnbp->xnb_mac_addr); 355 /* Warn, but continue on. */ 356 if (err != 0) { 357 bcopy(xnbp->xnb_mac_addr, ea.ether_addr_octet, 358 ETHERADDRL); 359 cmn_err(CE_WARN, "xnbo_open_mac: " 360 "cannot set MAC address of %s to " 361 "%s: %d", mac, ether_sprintf(&ea), err); 362 } 363 } 364 365 if (!mac_capab_get(xnbop->o_mh, MAC_CAPAB_HCKSUM, 366 &xnbop->o_hcksum_capab)) 367 xnbop->o_hcksum_capab = 0; 368 369 xnbop->o_running = B_TRUE; 370 371 return (B_TRUE); 372 } 373 374 static void 375 xnbo_close_mac(xnb_t *xnbp) 376 { 377 i_xnbo_close_mac(xnbp, B_FALSE); 378 } 379 380 static void 381 i_xnbo_close_mac(xnb_t *xnbp, boolean_t locked) 382 { 383 xnbo_t *xnbop = xnbp->xnb_flavour_data; 384 xmca_t *loop; 385 386 ASSERT(!locked || MUTEX_HELD(&xnbp->xnb_state_lock)); 387 388 if (xnbop->o_mh == NULL) 389 return; 390 391 if (xnbop->o_running) 392 xnbop->o_running = B_FALSE; 393 394 if (!locked) 395 mutex_enter(&xnbp->xnb_state_lock); 396 loop = xnbop->o_mca; 397 xnbop->o_mca = NULL; 398 if (!locked) 399 mutex_exit(&xnbp->xnb_state_lock); 400 401 while (loop != NULL) { 402 xmca_t *next = loop->next; 403 404 DTRACE_PROBE3(mcast_remove, 405 (char *), "close", 406 (void *), xnbp, 407 (etheraddr_t *), loop->addr); 408 (void) mac_multicast_remove(xnbop->o_mch, loop->addr); 409 kmem_free(loop, sizeof (*loop)); 410 loop = next; 411 } 412 413 if (xnbop->o_promiscuous) { 414 if (xnbop->o_mphp != NULL) { 415 mac_promisc_remove(xnbop->o_mphp); 416 xnbop->o_mphp = NULL; 417 } 418 xnbop->o_promiscuous = B_FALSE; 419 } else { 420 if (xnbop->o_mch != NULL) 421 mac_rx_clear(xnbop->o_mch); 422 } 423 424 if (xnbop->o_mah != NULL) { 425 (void) mac_unicast_remove(xnbop->o_mch, xnbop->o_mah); 426 xnbop->o_mah = NULL; 427 } 428 429 if (xnbop->o_mch != NULL) { 430 mac_client_close(xnbop->o_mch, 0); 431 xnbop->o_mch = NULL; 432 } 433 434 mac_close(xnbop->o_mh); 435 xnbop->o_mh = NULL; 436 } 437 438 /* 439 * Hotplug has completed and we are connected to the peer. We have all 440 * the information we need to exchange traffic, so open the MAC device 441 * and configure it appropriately. 442 */ 443 static boolean_t 444 xnbo_start_connect(xnb_t *xnbp) 445 { 446 xnbo_t *xnbop = xnbp->xnb_flavour_data; 447 448 return (xnbo_open_mac(xnbp, xnbop->o_link_name)); 449 } 450 451 /* 452 * The guest has successfully synchronize with this instance. We read 453 * the configuration of the guest from xenstore to check whether the 454 * guest requests multicast control. If not (the default) we make a 455 * note that the MAC device needs to be used in promiscious mode. 456 */ 457 static boolean_t 458 xnbo_peer_connected(xnb_t *xnbp) 459 { 460 char *oename; 461 int request; 462 xnbo_t *xnbop = xnbp->xnb_flavour_data; 463 464 oename = xvdi_get_oename(xnbp->xnb_devinfo); 465 466 if (xenbus_scanf(XBT_NULL, oename, 467 "request-multicast-control", "%d", &request) != 0) 468 request = 0; 469 xnbop->o_multicast_control = (request > 0); 470 471 return (B_TRUE); 472 } 473 474 /* 475 * The guest domain has closed down the inter-domain connection. We 476 * close the underlying MAC device. 477 */ 478 static void 479 xnbo_peer_disconnected(xnb_t *xnbp) 480 { 481 xnbo_close_mac(xnbp); 482 } 483 484 /* 485 * The hotplug script has completed. We read information from xenstore 486 * about our configuration, most notably the name of the MAC device we 487 * should use. 488 */ 489 static boolean_t 490 xnbo_hotplug_connected(xnb_t *xnbp) 491 { 492 char *xsname; 493 xnbo_t *xnbop = xnbp->xnb_flavour_data; 494 int need; 495 496 xsname = xvdi_get_xsname(xnbp->xnb_devinfo); 497 498 if (xenbus_scanf(XBT_NULL, xsname, 499 "nic", "%s", xnbop->o_link_name) != 0) { 500 cmn_err(CE_WARN, "xnbo_connect: " 501 "cannot read nic name from %s", xsname); 502 return (B_FALSE); 503 } 504 505 if (xenbus_scanf(XBT_NULL, xsname, 506 "SUNW-need-rx-filter", "%d", &need) != 0) 507 need = 0; 508 xnbop->o_need_rx_filter = (need > 0); 509 510 if (xenbus_scanf(XBT_NULL, xsname, 511 "SUNW-need-set-physaddr", "%d", &need) != 0) 512 need = 0; 513 xnbop->o_need_setphysaddr = (need > 0); 514 515 return (B_TRUE); 516 } 517 518 /* 519 * Find the multicast address `addr', return B_TRUE if it is one that 520 * we receive. If `remove', remove it from the set received. 521 */ 522 static boolean_t 523 xnbo_mcast_find(xnb_t *xnbp, ether_addr_t *addr, boolean_t remove) 524 { 525 xnbo_t *xnbop = xnbp->xnb_flavour_data; 526 xmca_t *prev, *del, *this; 527 528 ASSERT(MUTEX_HELD(&xnbp->xnb_state_lock)); 529 ASSERT(xnbop->o_promiscuous == B_FALSE); 530 531 prev = del = NULL; 532 533 this = xnbop->o_mca; 534 535 while (this != NULL) { 536 if (bcmp(&this->addr, addr, sizeof (this->addr)) == 0) { 537 del = this; 538 if (remove) { 539 if (prev == NULL) 540 xnbop->o_mca = this->next; 541 else 542 prev->next = this->next; 543 } 544 break; 545 } 546 547 prev = this; 548 this = this->next; 549 } 550 551 if (del == NULL) 552 return (B_FALSE); 553 554 if (remove) { 555 DTRACE_PROBE3(mcast_remove, 556 (char *), "remove", 557 (void *), xnbp, 558 (etheraddr_t *), del->addr); 559 mac_multicast_remove(xnbop->o_mch, del->addr); 560 kmem_free(del, sizeof (*del)); 561 } 562 563 return (B_TRUE); 564 } 565 566 /* 567 * Add the multicast address `addr' to the set received. 568 */ 569 static boolean_t 570 xnbo_mcast_add(xnb_t *xnbp, ether_addr_t *addr) 571 { 572 xnbo_t *xnbop = xnbp->xnb_flavour_data; 573 boolean_t r = B_FALSE; 574 575 ASSERT(xnbop->o_promiscuous == B_FALSE); 576 577 mutex_enter(&xnbp->xnb_state_lock); 578 579 if (xnbo_mcast_find(xnbp, addr, B_FALSE)) { 580 r = B_TRUE; 581 } else if (mac_multicast_add(xnbop->o_mch, 582 (const uint8_t *)addr) == 0) { 583 xmca_t *mca; 584 585 DTRACE_PROBE3(mcast_add, 586 (char *), "add", 587 (void *), xnbp, 588 (etheraddr_t *), addr); 589 590 mca = kmem_alloc(sizeof (*mca), KM_SLEEP); 591 bcopy(addr, &mca->addr, sizeof (mca->addr)); 592 593 mca->next = xnbop->o_mca; 594 xnbop->o_mca = mca; 595 596 r = B_TRUE; 597 } 598 599 mutex_exit(&xnbp->xnb_state_lock); 600 601 return (r); 602 } 603 604 /* 605 * Remove the multicast address `addr' from the set received. 606 */ 607 static boolean_t 608 xnbo_mcast_del(xnb_t *xnbp, ether_addr_t *addr) 609 { 610 boolean_t r; 611 612 mutex_enter(&xnbp->xnb_state_lock); 613 r = xnbo_mcast_find(xnbp, addr, B_TRUE); 614 mutex_exit(&xnbp->xnb_state_lock); 615 616 return (r); 617 } 618 619 static int 620 xnbo_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 621 { 622 static xnb_flavour_t flavour = { 623 xnbo_to_mac, xnbo_peer_connected, xnbo_peer_disconnected, 624 xnbo_hotplug_connected, xnbo_start_connect, 625 xnbo_cksum_from_peer, xnbo_cksum_to_peer, 626 xnbo_mcast_add, xnbo_mcast_del, 627 }; 628 xnbo_t *xnbop; 629 630 switch (cmd) { 631 case DDI_ATTACH: 632 break; 633 case DDI_RESUME: 634 return (DDI_SUCCESS); 635 default: 636 return (DDI_FAILURE); 637 } 638 639 xnbop = kmem_zalloc(sizeof (*xnbop), KM_SLEEP); 640 641 if (xnb_attach(dip, &flavour, xnbop) != DDI_SUCCESS) { 642 kmem_free(xnbop, sizeof (*xnbop)); 643 return (DDI_FAILURE); 644 } 645 646 return (DDI_SUCCESS); 647 } 648 649 static int 650 xnbo_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 651 { 652 xnb_t *xnbp = ddi_get_driver_private(dip); 653 xnbo_t *xnbop = xnbp->xnb_flavour_data; 654 655 switch (cmd) { 656 case DDI_DETACH: 657 break; 658 case DDI_SUSPEND: 659 return (DDI_SUCCESS); 660 default: 661 return (DDI_FAILURE); 662 } 663 664 mutex_enter(&xnbp->xnb_tx_lock); 665 mutex_enter(&xnbp->xnb_rx_lock); 666 667 if (!xnbp->xnb_detachable || xnbp->xnb_connected || 668 (xnbp->xnb_tx_buf_count > 0)) { 669 mutex_exit(&xnbp->xnb_rx_lock); 670 mutex_exit(&xnbp->xnb_tx_lock); 671 672 return (DDI_FAILURE); 673 } 674 675 mutex_exit(&xnbp->xnb_rx_lock); 676 mutex_exit(&xnbp->xnb_tx_lock); 677 678 xnbo_close_mac(xnbp); 679 kmem_free(xnbop, sizeof (*xnbop)); 680 681 xnb_detach(dip); 682 683 return (DDI_SUCCESS); 684 } 685 686 static struct cb_ops cb_ops = { 687 nulldev, /* open */ 688 nulldev, /* close */ 689 nodev, /* strategy */ 690 nodev, /* print */ 691 nodev, /* dump */ 692 nodev, /* read */ 693 nodev, /* write */ 694 nodev, /* ioctl */ 695 nodev, /* devmap */ 696 nodev, /* mmap */ 697 nodev, /* segmap */ 698 nochpoll, /* poll */ 699 ddi_prop_op, /* cb_prop_op */ 700 0, /* streamtab */ 701 D_NEW | D_MP | D_64BIT /* Driver compatibility flag */ 702 }; 703 704 static struct dev_ops ops = { 705 DEVO_REV, /* devo_rev */ 706 0, /* devo_refcnt */ 707 nulldev, /* devo_getinfo */ 708 nulldev, /* devo_identify */ 709 nulldev, /* devo_probe */ 710 xnbo_attach, /* devo_attach */ 711 xnbo_detach, /* devo_detach */ 712 nodev, /* devo_reset */ 713 &cb_ops, /* devo_cb_ops */ 714 (struct bus_ops *)0, /* devo_bus_ops */ 715 NULL, /* devo_power */ 716 ddi_quiesce_not_needed, /* devo_quiesce */ 717 }; 718 719 static struct modldrv modldrv = { 720 &mod_driverops, "xnbo driver", &ops, 721 }; 722 723 static struct modlinkage modlinkage = { 724 MODREV_1, &modldrv, NULL 725 }; 726 727 int 728 _init(void) 729 { 730 return (mod_install(&modlinkage)); 731 } 732 733 int 734 _info(struct modinfo *modinfop) 735 { 736 return (mod_info(&modlinkage, modinfop)); 737 } 738 739 int 740 _fini(void) 741 { 742 return (mod_remove(&modlinkage)); 743 } 744