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 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * Simulated network device (simnet) driver: simulates a pseudo GLDv3 network 28 * device. Can simulate an Ethernet or WiFi network device. In addition, another 29 * simnet instance can be attached as a peer to create a point-to-point link on 30 * the same system. 31 */ 32 33 #include <sys/policy.h> 34 #include <sys/conf.h> 35 #include <sys/modctl.h> 36 #include <sys/priv_names.h> 37 #include <sys/dlpi.h> 38 #include <net/simnet.h> 39 #include <sys/ethernet.h> 40 #include <sys/mac.h> 41 #include <sys/dls.h> 42 #include <sys/mac_ether.h> 43 #include <sys/mac_provider.h> 44 #include <sys/mac_client_priv.h> 45 #include <sys/vlan.h> 46 #include <sys/random.h> 47 #include <sys/sysmacros.h> 48 #include <sys/list.h> 49 #include <sys/strsubr.h> 50 #include <sys/strsun.h> 51 #include <sys/atomic.h> 52 #include <sys/mac_wifi.h> 53 #include <sys/mac_impl.h> 54 #include <inet/wifi_ioctl.h> 55 #include <sys/thread.h> 56 #include <sys/synch.h> 57 #include <sys/sunddi.h> 58 59 #include "simnet_impl.h" 60 61 #define SIMNETINFO "Simulated Network Driver" 62 63 static dev_info_t *simnet_dip; 64 static ddi_taskq_t *simnet_rxq; 65 66 static int simnet_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); 67 static int simnet_attach(dev_info_t *, ddi_attach_cmd_t); 68 static int simnet_detach(dev_info_t *, ddi_detach_cmd_t); 69 static int simnet_ioc_create(void *, intptr_t, int, cred_t *, int *); 70 static int simnet_ioc_delete(void *, intptr_t, int, cred_t *, int *); 71 static int simnet_ioc_info(void *, intptr_t, int, cred_t *, int *); 72 static int simnet_ioc_modify(void *, intptr_t, int, cred_t *, int *); 73 static uint8_t *mcastaddr_lookup(simnet_dev_t *, const uint8_t *); 74 75 static dld_ioc_info_t simnet_ioc_list[] = { 76 {SIMNET_IOC_CREATE, DLDCOPYINOUT, sizeof (simnet_ioc_create_t), 77 simnet_ioc_create, {PRIV_SYS_DL_CONFIG}}, 78 {SIMNET_IOC_DELETE, DLDCOPYIN, sizeof (simnet_ioc_delete_t), 79 simnet_ioc_delete, {PRIV_SYS_DL_CONFIG}}, 80 {SIMNET_IOC_INFO, DLDCOPYINOUT, sizeof (simnet_ioc_info_t), 81 simnet_ioc_info, {NULL}}, 82 {SIMNET_IOC_MODIFY, DLDCOPYIN, sizeof (simnet_ioc_modify_t), 83 simnet_ioc_modify, {PRIV_SYS_DL_CONFIG}}, 84 }; 85 86 DDI_DEFINE_STREAM_OPS(simnet_dev_ops, nulldev, nulldev, simnet_attach, 87 simnet_detach, nodev, simnet_getinfo, D_MP, NULL, 88 ddi_quiesce_not_supported); 89 90 static struct modldrv simnet_modldrv = { 91 &mod_driverops, /* Type of module. This one is a driver */ 92 SIMNETINFO, /* short description */ 93 &simnet_dev_ops /* driver specific ops */ 94 }; 95 96 static struct modlinkage modlinkage = { 97 MODREV_1, &simnet_modldrv, NULL 98 }; 99 100 /* MAC callback function declarations */ 101 static int simnet_m_start(void *); 102 static void simnet_m_stop(void *); 103 static int simnet_m_promisc(void *, boolean_t); 104 static int simnet_m_multicst(void *, boolean_t, const uint8_t *); 105 static int simnet_m_unicst(void *, const uint8_t *); 106 static int simnet_m_stat(void *, uint_t, uint64_t *); 107 static void simnet_m_ioctl(void *, queue_t *, mblk_t *); 108 static mblk_t *simnet_m_tx(void *, mblk_t *); 109 static int simnet_m_setprop(void *, const char *, mac_prop_id_t, 110 uint_t, const void *); 111 static int simnet_m_getprop(void *, const char *, mac_prop_id_t, 112 uint_t, uint_t, void *, uint_t *); 113 114 static mac_callbacks_t simnet_m_callbacks = { 115 (MC_IOCTL | MC_SETPROP | MC_GETPROP), 116 simnet_m_stat, 117 simnet_m_start, 118 simnet_m_stop, 119 simnet_m_promisc, 120 simnet_m_multicst, 121 simnet_m_unicst, 122 simnet_m_tx, 123 simnet_m_ioctl, 124 NULL, 125 NULL, 126 NULL, 127 simnet_m_setprop, 128 simnet_m_getprop 129 }; 130 131 /* 132 * simnet_dev_lock protects the simnet device list. 133 * sd_instlock in each simnet_dev_t protects access to 134 * a single simnet_dev_t. 135 */ 136 static krwlock_t simnet_dev_lock; 137 static list_t simnet_dev_list; 138 static int simnet_count; /* Num of simnet instances */ 139 140 int 141 _init(void) 142 { 143 int status; 144 145 mac_init_ops(&simnet_dev_ops, "simnet"); 146 status = mod_install(&modlinkage); 147 if (status != DDI_SUCCESS) 148 mac_fini_ops(&simnet_dev_ops); 149 150 return (status); 151 } 152 153 int 154 _fini(void) 155 { 156 int status; 157 158 status = mod_remove(&modlinkage); 159 if (status == DDI_SUCCESS) 160 mac_fini_ops(&simnet_dev_ops); 161 162 return (status); 163 } 164 165 int 166 _info(struct modinfo *modinfop) 167 { 168 return (mod_info(&modlinkage, modinfop)); 169 } 170 171 static boolean_t 172 simnet_init(void) 173 { 174 if ((simnet_rxq = ddi_taskq_create(simnet_dip, "simnet", 1, 175 TASKQ_DEFAULTPRI, 0)) == NULL) 176 return (B_FALSE); 177 rw_init(&simnet_dev_lock, NULL, RW_DEFAULT, NULL); 178 list_create(&simnet_dev_list, sizeof (simnet_dev_t), 179 offsetof(simnet_dev_t, sd_listnode)); 180 return (B_TRUE); 181 } 182 183 static void 184 simnet_fini(void) 185 { 186 ASSERT(simnet_count == 0); 187 rw_destroy(&simnet_dev_lock); 188 list_destroy(&simnet_dev_list); 189 ddi_taskq_destroy(simnet_rxq); 190 } 191 192 /*ARGSUSED*/ 193 static int 194 simnet_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, 195 void **result) 196 { 197 switch (infocmd) { 198 case DDI_INFO_DEVT2DEVINFO: 199 *result = simnet_dip; 200 return (DDI_SUCCESS); 201 case DDI_INFO_DEVT2INSTANCE: 202 *result = NULL; 203 return (DDI_SUCCESS); 204 } 205 return (DDI_FAILURE); 206 } 207 208 static int 209 simnet_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 210 { 211 switch (cmd) { 212 case DDI_ATTACH: 213 if (ddi_get_instance(dip) != 0) { 214 /* we only allow instance 0 to attach */ 215 return (DDI_FAILURE); 216 } 217 218 if (dld_ioc_register(SIMNET_IOC, simnet_ioc_list, 219 DLDIOCCNT(simnet_ioc_list)) != 0) 220 return (DDI_FAILURE); 221 222 simnet_dip = dip; 223 if (!simnet_init()) 224 return (DDI_FAILURE); 225 return (DDI_SUCCESS); 226 227 case DDI_RESUME: 228 return (DDI_SUCCESS); 229 230 default: 231 return (DDI_FAILURE); 232 } 233 } 234 235 /*ARGSUSED*/ 236 static int 237 simnet_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 238 { 239 switch (cmd) { 240 case DDI_DETACH: 241 /* 242 * Allow the simnet instance to be detached only if there 243 * are no simnets configured. 244 */ 245 if (simnet_count > 0) 246 return (DDI_FAILURE); 247 248 dld_ioc_unregister(SIMNET_IOC); 249 simnet_fini(); 250 simnet_dip = NULL; 251 return (DDI_SUCCESS); 252 253 case DDI_SUSPEND: 254 return (DDI_SUCCESS); 255 256 default: 257 return (DDI_FAILURE); 258 } 259 } 260 261 /* Caller must hold simnet_dev_lock */ 262 static simnet_dev_t * 263 simnet_dev_lookup(datalink_id_t link_id) 264 { 265 simnet_dev_t *sdev; 266 267 ASSERT(RW_LOCK_HELD(&simnet_dev_lock)); 268 for (sdev = list_head(&simnet_dev_list); sdev != NULL; 269 sdev = list_next(&simnet_dev_list, sdev)) { 270 if (!(sdev->sd_flags & SDF_SHUTDOWN) && 271 (sdev->sd_link_id == link_id)) { 272 atomic_inc_32(&sdev->sd_refcount); 273 return (sdev); 274 } 275 } 276 277 return (NULL); 278 } 279 280 static void 281 simnet_wifidev_free(simnet_dev_t *sdev) 282 { 283 simnet_wifidev_t *wdev = sdev->sd_wifidev; 284 int i; 285 286 for (i = 0; i < wdev->swd_esslist_num; i++) { 287 kmem_free(wdev->swd_esslist[i], 288 sizeof (wl_ess_conf_t)); 289 } 290 kmem_free(wdev, sizeof (simnet_wifidev_t)); 291 } 292 293 static void 294 simnet_dev_unref(simnet_dev_t *sdev) 295 { 296 297 ASSERT(sdev->sd_refcount > 0); 298 if (atomic_dec_32_nv(&sdev->sd_refcount) != 0) 299 return; 300 301 if (sdev->sd_mh != NULL) 302 (void) mac_unregister(sdev->sd_mh); 303 304 if (sdev->sd_wifidev != NULL) { 305 ASSERT(sdev->sd_type == DL_WIFI); 306 simnet_wifidev_free(sdev); 307 } 308 309 mutex_destroy(&sdev->sd_instlock); 310 cv_destroy(&sdev->sd_threadwait); 311 kmem_free(sdev->sd_mcastaddrs, ETHERADDRL * sdev->sd_mcastaddr_count); 312 kmem_free(sdev, sizeof (*sdev)); 313 simnet_count--; 314 } 315 316 static int 317 simnet_init_wifi(simnet_dev_t *sdev, mac_register_t *mac) 318 { 319 wifi_data_t wd = { 0 }; 320 int err; 321 322 sdev->sd_wifidev = kmem_zalloc(sizeof (simnet_wifidev_t), KM_NOSLEEP); 323 if (sdev->sd_wifidev == NULL) 324 return (ENOMEM); 325 326 sdev->sd_wifidev->swd_sdev = sdev; 327 sdev->sd_wifidev->swd_linkstatus = WL_NOTCONNECTED; 328 wd.wd_secalloc = WIFI_SEC_NONE; 329 wd.wd_opmode = IEEE80211_M_STA; 330 mac->m_type_ident = MAC_PLUGIN_IDENT_WIFI; 331 mac->m_max_sdu = IEEE80211_MTU; 332 mac->m_pdata = &wd; 333 mac->m_pdata_size = sizeof (wd); 334 err = mac_register(mac, &sdev->sd_mh); 335 return (err); 336 } 337 338 static int 339 simnet_init_ether(simnet_dev_t *sdev, mac_register_t *mac) 340 { 341 int err; 342 343 mac->m_type_ident = MAC_PLUGIN_IDENT_ETHER; 344 mac->m_max_sdu = SIMNET_MAX_MTU; 345 mac->m_margin = VLAN_TAGSZ; 346 err = mac_register(mac, &sdev->sd_mh); 347 return (err); 348 } 349 350 static int 351 simnet_init_mac(simnet_dev_t *sdev) 352 { 353 mac_register_t *mac; 354 int err; 355 356 if ((mac = mac_alloc(MAC_VERSION)) == NULL) 357 return (ENOMEM); 358 359 mac->m_driver = sdev; 360 mac->m_dip = simnet_dip; 361 mac->m_instance = (uint_t)-1; 362 mac->m_src_addr = sdev->sd_mac_addr; 363 mac->m_callbacks = &simnet_m_callbacks; 364 mac->m_min_sdu = 0; 365 366 if (sdev->sd_type == DL_ETHER) 367 err = simnet_init_ether(sdev, mac); 368 else if (sdev->sd_type == DL_WIFI) 369 err = simnet_init_wifi(sdev, mac); 370 else 371 err = EINVAL; 372 373 mac_free(mac); 374 return (err); 375 } 376 377 /* ARGSUSED */ 378 static int 379 simnet_ioc_create(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) 380 { 381 simnet_ioc_create_t *create_arg = karg; 382 simnet_dev_t *sdev; 383 simnet_dev_t *sdev_tmp; 384 int err = 0; 385 386 sdev = kmem_zalloc(sizeof (*sdev), KM_NOSLEEP); 387 if (sdev == NULL) 388 return (ENOMEM); 389 390 rw_enter(&simnet_dev_lock, RW_WRITER); 391 if ((sdev_tmp = simnet_dev_lookup(create_arg->sic_link_id)) != NULL) { 392 simnet_dev_unref(sdev_tmp); 393 rw_exit(&simnet_dev_lock); 394 kmem_free(sdev, sizeof (*sdev)); 395 return (EEXIST); 396 } 397 398 sdev->sd_type = create_arg->sic_type; 399 sdev->sd_link_id = create_arg->sic_link_id; 400 sdev->sd_refcount++; 401 mutex_init(&sdev->sd_instlock, NULL, MUTEX_DRIVER, NULL); 402 cv_init(&sdev->sd_threadwait, NULL, CV_DRIVER, NULL); 403 simnet_count++; 404 405 /* Simnets created from configuration on boot pass saved MAC address */ 406 if (create_arg->sic_mac_len == 0) { 407 /* Generate random MAC address */ 408 (void) random_get_pseudo_bytes(sdev->sd_mac_addr, ETHERADDRL); 409 /* Ensure MAC address is not multicast and is local */ 410 sdev->sd_mac_addr[0] = (sdev->sd_mac_addr[0] & ~1) | 2; 411 sdev->sd_mac_len = ETHERADDRL; 412 } else { 413 (void) memcpy(sdev->sd_mac_addr, create_arg->sic_mac_addr, 414 create_arg->sic_mac_len); 415 sdev->sd_mac_len = create_arg->sic_mac_len; 416 } 417 418 if ((err = simnet_init_mac(sdev)) != 0) { 419 simnet_dev_unref(sdev); 420 goto exit; 421 } 422 423 if ((err = dls_devnet_create(sdev->sd_mh, sdev->sd_link_id)) != 0) { 424 simnet_dev_unref(sdev); 425 goto exit; 426 } 427 428 mac_link_update(sdev->sd_mh, LINK_STATE_UP); 429 mac_tx_update(sdev->sd_mh); 430 list_insert_tail(&simnet_dev_list, sdev); 431 432 /* Always return MAC address back to caller */ 433 (void) memcpy(create_arg->sic_mac_addr, sdev->sd_mac_addr, 434 sdev->sd_mac_len); 435 create_arg->sic_mac_len = sdev->sd_mac_len; 436 exit: 437 rw_exit(&simnet_dev_lock); 438 return (err); 439 } 440 441 /* Caller must hold writer simnet_dev_lock */ 442 static datalink_id_t 443 simnet_remove_peer(simnet_dev_t *sdev) 444 { 445 simnet_dev_t *sdev_peer; 446 datalink_id_t peer_link_id = DATALINK_INVALID_LINKID; 447 448 ASSERT(RW_WRITE_HELD(&simnet_dev_lock)); 449 if ((sdev_peer = sdev->sd_peer_dev) != NULL) { 450 ASSERT(sdev == sdev_peer->sd_peer_dev); 451 sdev_peer->sd_peer_dev = NULL; 452 sdev->sd_peer_dev = NULL; 453 peer_link_id = sdev_peer->sd_link_id; 454 /* Release previous references held on both simnets */ 455 simnet_dev_unref(sdev_peer); 456 simnet_dev_unref(sdev); 457 } 458 459 return (peer_link_id); 460 } 461 462 /* ARGSUSED */ 463 static int 464 simnet_ioc_modify(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) 465 { 466 simnet_ioc_modify_t *modify_arg = karg; 467 simnet_dev_t *sdev; 468 simnet_dev_t *sdev_peer = NULL; 469 470 rw_enter(&simnet_dev_lock, RW_WRITER); 471 if ((sdev = simnet_dev_lookup(modify_arg->sim_link_id)) == NULL) { 472 rw_exit(&simnet_dev_lock); 473 return (ENOENT); 474 } 475 476 if (sdev->sd_link_id == modify_arg->sim_peer_link_id) { 477 /* Cannot peer with self */ 478 rw_exit(&simnet_dev_lock); 479 simnet_dev_unref(sdev); 480 return (EINVAL); 481 } 482 483 if (sdev->sd_peer_dev != NULL && sdev->sd_peer_dev->sd_link_id == 484 modify_arg->sim_peer_link_id) { 485 /* Nothing to modify */ 486 rw_exit(&simnet_dev_lock); 487 simnet_dev_unref(sdev); 488 return (0); 489 } 490 491 if (modify_arg->sim_peer_link_id != DATALINK_INVALID_LINKID && 492 (sdev_peer = simnet_dev_lookup(modify_arg->sim_peer_link_id)) == 493 NULL) { 494 /* Peer simnet device not available */ 495 rw_exit(&simnet_dev_lock); 496 simnet_dev_unref(sdev); 497 return (ENOENT); 498 } 499 500 /* First remove any previous peer */ 501 (void) simnet_remove_peer(sdev); 502 503 if (sdev_peer != NULL) { 504 /* Remove any previous peer of sdev_peer */ 505 (void) simnet_remove_peer(sdev_peer); 506 /* Update both devices with the new peer */ 507 sdev_peer->sd_peer_dev = sdev; 508 sdev->sd_peer_dev = sdev_peer; 509 /* Hold references on both devices */ 510 } else { 511 /* Release sdev lookup reference */ 512 simnet_dev_unref(sdev); 513 } 514 515 rw_exit(&simnet_dev_lock); 516 return (0); 517 } 518 519 /* ARGSUSED */ 520 static int 521 simnet_ioc_delete(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) 522 { 523 int err; 524 simnet_dev_t *sdev; 525 simnet_dev_t *sdev_peer; 526 simnet_ioc_delete_t *delete_arg = karg; 527 datalink_id_t tmpid; 528 datalink_id_t peerid; 529 530 rw_enter(&simnet_dev_lock, RW_WRITER); 531 if ((sdev = simnet_dev_lookup(delete_arg->sid_link_id)) == NULL) { 532 rw_exit(&simnet_dev_lock); 533 return (ENOENT); 534 } 535 536 if ((err = dls_devnet_destroy(sdev->sd_mh, &tmpid, B_TRUE)) != 0) { 537 rw_exit(&simnet_dev_lock); 538 simnet_dev_unref(sdev); 539 return (err); 540 } 541 542 ASSERT(sdev->sd_link_id == tmpid); 543 /* Remove any attached peer link */ 544 peerid = simnet_remove_peer(sdev); 545 546 /* Prevent new threads from using the instance */ 547 mutex_enter(&sdev->sd_instlock); 548 sdev->sd_flags |= SDF_SHUTDOWN; 549 /* Wait until all active threads using the instance exit */ 550 while (sdev->sd_threadcount > 0) { 551 if (cv_wait_sig(&sdev->sd_threadwait, 552 &sdev->sd_instlock) == 0) { 553 /* Signaled */ 554 mutex_exit(&sdev->sd_instlock); 555 err = EINTR; 556 goto fail; 557 } 558 } 559 mutex_exit(&sdev->sd_instlock); 560 561 /* Try disabling the MAC */ 562 if ((err = mac_disable(sdev->sd_mh)) != 0) 563 goto fail; 564 565 list_remove(&simnet_dev_list, sdev); 566 rw_exit(&simnet_dev_lock); 567 simnet_dev_unref(sdev); /* Release lookup ref */ 568 /* Releasing the last ref performs sdev/mem free */ 569 simnet_dev_unref(sdev); 570 return (err); 571 fail: 572 /* Re-create simnet instance and add any previous peer */ 573 (void) dls_devnet_create(sdev->sd_mh, sdev->sd_link_id); 574 sdev->sd_flags &= ~SDF_SHUTDOWN; 575 576 ASSERT(sdev->sd_peer_dev == NULL); 577 if (peerid != DATALINK_INVALID_LINKID && 578 ((sdev_peer = simnet_dev_lookup(peerid)) != NULL)) { 579 /* Attach peer device back */ 580 ASSERT(sdev_peer->sd_peer_dev == NULL); 581 sdev_peer->sd_peer_dev = sdev; 582 sdev->sd_peer_dev = sdev_peer; 583 /* Hold reference on both devices */ 584 } else { 585 /* 586 * No previous peer or previous peer no longer 587 * available so release lookup reference. 588 */ 589 simnet_dev_unref(sdev); 590 } 591 592 rw_exit(&simnet_dev_lock); 593 return (err); 594 } 595 596 /* ARGSUSED */ 597 static int 598 simnet_ioc_info(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) 599 { 600 simnet_ioc_info_t *info_arg = karg; 601 simnet_dev_t *sdev; 602 603 rw_enter(&simnet_dev_lock, RW_READER); 604 if ((sdev = simnet_dev_lookup(info_arg->sii_link_id)) == NULL) { 605 rw_exit(&simnet_dev_lock); 606 return (ENOENT); 607 } 608 609 (void) memcpy(info_arg->sii_mac_addr, sdev->sd_mac_addr, 610 sdev->sd_mac_len); 611 info_arg->sii_mac_len = sdev->sd_mac_len; 612 info_arg->sii_type = sdev->sd_type; 613 if (sdev->sd_peer_dev != NULL) 614 info_arg->sii_peer_link_id = sdev->sd_peer_dev->sd_link_id; 615 rw_exit(&simnet_dev_lock); 616 simnet_dev_unref(sdev); 617 return (0); 618 } 619 620 static boolean_t 621 simnet_thread_ref(simnet_dev_t *sdev) 622 { 623 mutex_enter(&sdev->sd_instlock); 624 if (sdev->sd_flags & SDF_SHUTDOWN || 625 !(sdev->sd_flags & SDF_STARTED)) { 626 mutex_exit(&sdev->sd_instlock); 627 return (B_FALSE); 628 } 629 sdev->sd_threadcount++; 630 mutex_exit(&sdev->sd_instlock); 631 return (B_TRUE); 632 } 633 634 static void 635 simnet_thread_unref(simnet_dev_t *sdev) 636 { 637 mutex_enter(&sdev->sd_instlock); 638 if (--sdev->sd_threadcount == 0) 639 cv_broadcast(&sdev->sd_threadwait); 640 mutex_exit(&sdev->sd_instlock); 641 } 642 643 static void 644 simnet_rx(void *arg) 645 { 646 mblk_t *mp = arg; 647 mac_header_info_t hdr_info; 648 simnet_dev_t *sdev; 649 650 sdev = (simnet_dev_t *)mp->b_next; 651 mp->b_next = NULL; 652 653 /* Check for valid packet header */ 654 if (mac_header_info(sdev->sd_mh, mp, &hdr_info) != 0) { 655 freemsg(mp); 656 sdev->sd_stats.recv_errors++; 657 goto rx_done; 658 } 659 660 /* 661 * When we are NOT in promiscuous mode we only receive 662 * unicast packets addressed to us and multicast packets that 663 * MAC clients have requested. 664 */ 665 if (!sdev->sd_promisc && 666 hdr_info.mhi_dsttype != MAC_ADDRTYPE_BROADCAST) { 667 if (hdr_info.mhi_dsttype == MAC_ADDRTYPE_UNICAST && 668 bcmp(hdr_info.mhi_daddr, sdev->sd_mac_addr, 669 ETHERADDRL) != 0) { 670 freemsg(mp); 671 goto rx_done; 672 } else if (hdr_info.mhi_dsttype == MAC_ADDRTYPE_MULTICAST) { 673 mutex_enter(&sdev->sd_instlock); 674 if (mcastaddr_lookup(sdev, hdr_info.mhi_daddr) == 675 NULL) { 676 mutex_exit(&sdev->sd_instlock); 677 freemsg(mp); 678 goto rx_done; 679 } 680 mutex_exit(&sdev->sd_instlock); 681 } 682 } 683 684 sdev->sd_stats.recv_count++; 685 sdev->sd_stats.rbytes += msgdsize(mp); 686 mac_rx(sdev->sd_mh, NULL, mp); 687 rx_done: 688 simnet_thread_unref(sdev); 689 } 690 691 static mblk_t * 692 simnet_m_tx(void *arg, mblk_t *mp_chain) 693 { 694 simnet_dev_t *sdev = arg; 695 simnet_dev_t *sdev_rx; 696 mblk_t *mpnext = mp_chain; 697 mblk_t *mp; 698 699 rw_enter(&simnet_dev_lock, RW_READER); 700 if ((sdev_rx = sdev->sd_peer_dev) == NULL) { 701 /* Discard packets when no peer exists */ 702 rw_exit(&simnet_dev_lock); 703 freemsgchain(mp_chain); 704 return (NULL); 705 } 706 707 /* 708 * Discard packets when either device is shutting down or not ready. 709 * Though MAC layer ensures a reference is held on the MAC while we 710 * process the packet chain, there is no guarantee the peer MAC will 711 * remain enabled. So we increment per-instance threadcount to ensure 712 * either MAC instance is not disabled while we handle the chain of 713 * packets. It is okay if the peer device is disconnected while we are 714 * here since we lookup the peer device while holding simnet_dev_lock 715 * (reader lock) and increment the threadcount of the peer, the peer 716 * MAC cannot be disabled in simnet_ioc_delete. 717 */ 718 if (!simnet_thread_ref(sdev_rx)) { 719 rw_exit(&simnet_dev_lock); 720 freemsgchain(mp_chain); 721 return (NULL); 722 } 723 rw_exit(&simnet_dev_lock); 724 725 if (!simnet_thread_ref(sdev)) { 726 simnet_thread_unref(sdev_rx); 727 freemsgchain(mp_chain); 728 return (NULL); 729 } 730 731 while ((mp = mpnext) != NULL) { 732 int len; 733 int size; 734 mblk_t *mp_new; 735 mblk_t *mp_tmp; 736 737 mpnext = mp->b_next; 738 mp->b_next = NULL; 739 len = msgdsize(mp); 740 741 /* Pad packet to minimum Ethernet frame size */ 742 if (len < ETHERMIN) { 743 size = ETHERMIN - len; 744 mp_new = allocb(size, BPRI_HI); 745 if (mp_new == NULL) { 746 sdev->sd_stats.xmit_errors++; 747 freemsg(mp); 748 continue; 749 } 750 bzero(mp_new->b_wptr, size); 751 mp_new->b_wptr += size; 752 753 mp_tmp = mp; 754 while (mp_tmp->b_cont != NULL) 755 mp_tmp = mp_tmp->b_cont; 756 mp_tmp->b_cont = mp_new; 757 len += size; 758 } 759 760 /* Pullup packet into a single mblk */ 761 if (!pullupmsg(mp, -1)) { 762 sdev->sd_stats.xmit_errors++; 763 freemsg(mp); 764 continue; 765 } 766 767 /* Fix mblk checksum as the pkt dest is local */ 768 if ((mp = mac_fix_cksum(mp)) == NULL) { 769 sdev->sd_stats.xmit_errors++; 770 continue; 771 } 772 773 /* Hold reference for taskq receive processing per-pkt */ 774 if (!simnet_thread_ref(sdev_rx)) { 775 freemsg(mp); 776 freemsgchain(mpnext); 777 break; 778 } 779 780 /* Use taskq for pkt receive to avoid kernel stack explosion */ 781 mp->b_next = (mblk_t *)sdev_rx; 782 if (ddi_taskq_dispatch(simnet_rxq, simnet_rx, mp, 783 DDI_NOSLEEP) == DDI_SUCCESS) { 784 sdev->sd_stats.xmit_count++; 785 sdev->sd_stats.obytes += len; 786 } else { 787 simnet_thread_unref(sdev_rx); 788 mp->b_next = NULL; 789 freemsg(mp); 790 sdev_rx->sd_stats.recv_errors++; 791 } 792 } 793 794 simnet_thread_unref(sdev); 795 simnet_thread_unref(sdev_rx); 796 return (NULL); 797 } 798 799 static int 800 simnet_wifi_ioctl(simnet_dev_t *sdev, mblk_t *mp) 801 { 802 int rc = WL_SUCCESS; 803 simnet_wifidev_t *wdev = sdev->sd_wifidev; 804 805 /* LINTED E_BAD_PTR_CAST_ALIGN */ 806 switch (((wldp_t *)mp->b_rptr)->wldp_id) { 807 case WL_DISASSOCIATE: 808 wdev->swd_linkstatus = WL_NOTCONNECTED; 809 break; 810 default: 811 break; 812 } 813 return (rc); 814 } 815 816 static void 817 simnet_m_ioctl(void *arg, queue_t *q, mblk_t *mp) 818 { 819 simnet_dev_t *sdev = arg; 820 struct iocblk *iocp; 821 mblk_t *mp1; 822 uint32_t cmd; 823 int rc; 824 825 if (sdev->sd_type != DL_WIFI) { 826 miocnak(q, mp, 0, ENOTSUP); 827 return; 828 } 829 830 /* LINTED E_BAD_PTR_CAST_ALIGN */ 831 iocp = (struct iocblk *)mp->b_rptr; 832 if (iocp->ioc_count == 0) { 833 miocnak(q, mp, 0, EINVAL); 834 return; 835 } 836 837 /* We only claim support for WiFi operation commands */ 838 cmd = iocp->ioc_cmd; 839 switch (cmd) { 840 default: 841 miocnak(q, mp, 0, EINVAL); 842 return; 843 case WLAN_GET_PARAM: 844 case WLAN_SET_PARAM: 845 case WLAN_COMMAND: 846 break; 847 } 848 849 mp1 = mp->b_cont; 850 freemsg(mp1->b_cont); 851 mp1->b_cont = NULL; 852 /* overwrite everything */ 853 mp1->b_wptr = mp1->b_rptr; 854 rc = simnet_wifi_ioctl(sdev, mp1); 855 miocack(q, mp, msgdsize(mp1), rc); 856 } 857 858 static int 859 simnet_m_stat(void *arg, uint_t stat, uint64_t *val) 860 { 861 int rval = 0; 862 simnet_dev_t *sdev = arg; 863 864 ASSERT(sdev->sd_mh != NULL); 865 866 switch (stat) { 867 case MAC_STAT_IFSPEED: 868 *val = 100 * 1000000ull; /* 100 Mbps */ 869 break; 870 case MAC_STAT_LINK_STATE: 871 *val = LINK_DUPLEX_FULL; 872 break; 873 case MAC_STAT_LINK_UP: 874 if (sdev->sd_flags & SDF_STARTED) 875 *val = LINK_STATE_UP; 876 else 877 *val = LINK_STATE_DOWN; 878 break; 879 case MAC_STAT_PROMISC: 880 case MAC_STAT_MULTIRCV: 881 case MAC_STAT_MULTIXMT: 882 case MAC_STAT_BRDCSTRCV: 883 case MAC_STAT_BRDCSTXMT: 884 rval = ENOTSUP; 885 break; 886 case MAC_STAT_OPACKETS: 887 *val = sdev->sd_stats.xmit_count; 888 break; 889 case MAC_STAT_OBYTES: 890 *val = sdev->sd_stats.obytes; 891 break; 892 case MAC_STAT_IERRORS: 893 *val = sdev->sd_stats.recv_errors; 894 break; 895 case MAC_STAT_OERRORS: 896 *val = sdev->sd_stats.xmit_errors; 897 break; 898 case MAC_STAT_RBYTES: 899 *val = sdev->sd_stats.rbytes; 900 break; 901 case MAC_STAT_IPACKETS: 902 *val = sdev->sd_stats.recv_count; 903 break; 904 case WIFI_STAT_FCS_ERRORS: 905 case WIFI_STAT_WEP_ERRORS: 906 case WIFI_STAT_TX_FRAGS: 907 case WIFI_STAT_MCAST_TX: 908 case WIFI_STAT_RTS_SUCCESS: 909 case WIFI_STAT_RTS_FAILURE: 910 case WIFI_STAT_ACK_FAILURE: 911 case WIFI_STAT_RX_FRAGS: 912 case WIFI_STAT_MCAST_RX: 913 case WIFI_STAT_RX_DUPS: 914 rval = ENOTSUP; 915 break; 916 default: 917 rval = ENOTSUP; 918 break; 919 } 920 921 return (rval); 922 } 923 924 static int 925 simnet_m_start(void *arg) 926 { 927 simnet_dev_t *sdev = arg; 928 929 sdev->sd_flags |= SDF_STARTED; 930 return (0); 931 } 932 933 static void 934 simnet_m_stop(void *arg) 935 { 936 simnet_dev_t *sdev = arg; 937 938 sdev->sd_flags &= ~SDF_STARTED; 939 } 940 941 static int 942 simnet_m_promisc(void *arg, boolean_t on) 943 { 944 simnet_dev_t *sdev = arg; 945 946 sdev->sd_promisc = on; 947 return (0); 948 } 949 950 /* 951 * Returns matching multicast address enabled on the simnet instance. 952 * Assumes simnet instance mutex lock is held. 953 */ 954 static uint8_t * 955 mcastaddr_lookup(simnet_dev_t *sdev, const uint8_t *addrp) 956 { 957 int idx; 958 uint8_t *maddrptr; 959 960 ASSERT(MUTEX_HELD(&sdev->sd_instlock)); 961 maddrptr = sdev->sd_mcastaddrs; 962 for (idx = 0; idx < sdev->sd_mcastaddr_count; idx++) { 963 if (bcmp(maddrptr, addrp, ETHERADDRL) == 0) 964 return (maddrptr); 965 maddrptr += ETHERADDRL; 966 } 967 968 return (NULL); 969 } 970 971 /* Add or remove Multicast addresses on simnet instance */ 972 static int 973 simnet_m_multicst(void *arg, boolean_t add, const uint8_t *addrp) 974 { 975 simnet_dev_t *sdev = arg; 976 uint8_t *maddrptr; 977 uint8_t *newbuf; 978 size_t prevsize; 979 size_t newsize; 980 ptrdiff_t len; 981 ptrdiff_t len2; 982 983 alloc_retry: 984 prevsize = sdev->sd_mcastaddr_count * ETHERADDRL; 985 newsize = prevsize + (add ? ETHERADDRL:-ETHERADDRL); 986 newbuf = kmem_alloc(newsize, KM_SLEEP); 987 988 mutex_enter(&sdev->sd_instlock); 989 if (prevsize != (sdev->sd_mcastaddr_count * ETHERADDRL)) { 990 mutex_exit(&sdev->sd_instlock); 991 kmem_free(newbuf, newsize); 992 goto alloc_retry; 993 } 994 995 maddrptr = mcastaddr_lookup(sdev, addrp); 996 if (!add && maddrptr != NULL) { 997 /* Removing a Multicast address */ 998 if (newbuf != NULL) { 999 /* LINTED: E_PTRDIFF_OVERFLOW */ 1000 len = maddrptr - sdev->sd_mcastaddrs; 1001 (void) memcpy(newbuf, sdev->sd_mcastaddrs, len); 1002 len2 = prevsize - len - ETHERADDRL; 1003 (void) memcpy(newbuf + len, 1004 maddrptr + ETHERADDRL, len2); 1005 } 1006 sdev->sd_mcastaddr_count--; 1007 } else if (add && maddrptr == NULL) { 1008 /* Adding a new Multicast address */ 1009 (void) memcpy(newbuf, sdev->sd_mcastaddrs, prevsize); 1010 (void) memcpy(newbuf + prevsize, addrp, ETHERADDRL); 1011 sdev->sd_mcastaddr_count++; 1012 } else { 1013 /* Error: removing a non-existing Multicast address */ 1014 mutex_exit(&sdev->sd_instlock); 1015 kmem_free(newbuf, newsize); 1016 cmn_err(CE_WARN, "simnet: MAC call to remove a " 1017 "Multicast address failed"); 1018 return (EINVAL); 1019 } 1020 1021 kmem_free(sdev->sd_mcastaddrs, prevsize); 1022 sdev->sd_mcastaddrs = newbuf; 1023 mutex_exit(&sdev->sd_instlock); 1024 return (0); 1025 } 1026 1027 static int 1028 simnet_m_unicst(void *arg, const uint8_t *macaddr) 1029 { 1030 simnet_dev_t *sdev = arg; 1031 1032 (void) memcpy(sdev->sd_mac_addr, macaddr, ETHERADDRL); 1033 return (0); 1034 } 1035 1036 /* Parse WiFi scan list entry arguments and return the arg count */ 1037 static int 1038 parse_esslist_args(const void *pr_val, uint_t pr_valsize, 1039 char args[][MAX_ESSLIST_ARGLEN]) 1040 { 1041 char *sep; 1042 ptrdiff_t len = pr_valsize; 1043 const char *piece = pr_val; 1044 const char *end = (const char *)pr_val + pr_valsize - 1; 1045 int arg = 0; 1046 1047 while (piece < end && (arg < MAX_ESSLIST_ARGS)) { 1048 sep = strchr(piece, ','); 1049 if (sep == NULL) 1050 sep = (char *)end; 1051 /* LINTED E_PTRDIFF_OVERFLOW */ 1052 len = sep - piece; 1053 /* If first arg is zero then return none to delete all */ 1054 if (arg == 0 && strnlen(piece, len) == 1 && piece[0] == '0') 1055 return (0); 1056 if (len > MAX_ESSLIST_ARGLEN) 1057 len = MAX_ESSLIST_ARGLEN - 1; 1058 (void) memcpy(&args[arg][0], piece, len); 1059 args[arg][len] = '\0'; 1060 piece = sep + 1; 1061 arg++; 1062 } 1063 1064 return (arg); 1065 } 1066 1067 /* Set WiFi scan list entry from private property _wl_esslist */ 1068 static int 1069 set_wl_esslist_priv_prop(simnet_wifidev_t *wdev, uint_t pr_valsize, 1070 const void *pr_val) 1071 { 1072 char essargs[MAX_ESSLIST_ARGS][MAX_ESSLIST_ARGLEN]; 1073 wl_ess_conf_t *wls; 1074 long result; 1075 int i; 1076 1077 bzero(essargs, sizeof (essargs)); 1078 if (parse_esslist_args(pr_val, pr_valsize, essargs) == 0) { 1079 for (i = 0; i < wdev->swd_esslist_num; i++) { 1080 kmem_free(wdev->swd_esslist[i], sizeof (wl_ess_conf_t)); 1081 wdev->swd_esslist[i] = NULL; 1082 } 1083 wdev->swd_esslist_num = 0; 1084 return (0); 1085 } 1086 1087 for (i = 0; i < wdev->swd_esslist_num; i++) { 1088 wls = wdev->swd_esslist[i]; 1089 if (strcasecmp(wls->wl_ess_conf_essid.wl_essid_essid, 1090 essargs[0]) == 0) 1091 return (EEXIST); 1092 } 1093 1094 if (wdev->swd_esslist_num >= MAX_SIMNET_ESSCONF) 1095 return (EINVAL); 1096 1097 wls = kmem_zalloc(sizeof (wl_ess_conf_t), KM_SLEEP); 1098 (void) strlcpy(wls->wl_ess_conf_essid.wl_essid_essid, 1099 essargs[0], sizeof (wls->wl_ess_conf_essid.wl_essid_essid)); 1100 wls->wl_ess_conf_essid.wl_essid_length = 1101 strlen(wls->wl_ess_conf_essid.wl_essid_essid); 1102 (void) random_get_pseudo_bytes((uint8_t *) 1103 &wls->wl_ess_conf_bssid, sizeof (wl_bssid_t)); 1104 (void) ddi_strtol(essargs[1], (char **)NULL, 0, &result); 1105 wls->wl_ess_conf_sl = (wl_rssi_t) 1106 ((result > MAX_RSSI || result < 0) ? 0:result); 1107 wdev->swd_esslist[wdev->swd_esslist_num] = wls; 1108 wdev->swd_esslist_num++; 1109 1110 return (0); 1111 } 1112 1113 static int 1114 simnet_set_priv_prop(simnet_dev_t *sdev, const char *pr_name, 1115 uint_t pr_valsize, const void *pr_val) 1116 { 1117 simnet_wifidev_t *wdev = sdev->sd_wifidev; 1118 long result; 1119 1120 if (strcmp(pr_name, "_wl_esslist") == 0) { 1121 if (pr_val == NULL) 1122 return (EINVAL); 1123 return (set_wl_esslist_priv_prop(wdev, pr_valsize, pr_val)); 1124 } else if (strcmp(pr_name, "_wl_connected") == 0) { 1125 if (pr_val == NULL) 1126 return (EINVAL); 1127 (void) ddi_strtol(pr_val, (char **)NULL, 0, &result); 1128 wdev->swd_linkstatus = ((result == 1) ? 1129 WL_CONNECTED:WL_NOTCONNECTED); 1130 return (0); 1131 } 1132 1133 return (EINVAL); 1134 } 1135 1136 static int 1137 simnet_m_setprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, 1138 uint_t wldp_length, const void *wldp_buf) 1139 { 1140 simnet_dev_t *sdev = arg; 1141 simnet_wifidev_t *wdev = sdev->sd_wifidev; 1142 int err = 0; 1143 uint32_t mtu; 1144 1145 switch (wldp_pr_num) { 1146 case MAC_PROP_MTU: 1147 (void) memcpy(&mtu, wldp_buf, sizeof (mtu)); 1148 if (mtu > ETHERMIN && mtu < SIMNET_MAX_MTU) 1149 return (mac_maxsdu_update(sdev->sd_mh, mtu)); 1150 else 1151 return (EINVAL); 1152 default: 1153 break; 1154 } 1155 1156 if (sdev->sd_type == DL_ETHER) 1157 return (ENOTSUP); 1158 1159 /* mac_prop_id */ 1160 switch (wldp_pr_num) { 1161 case MAC_PROP_WL_ESSID: { 1162 int i; 1163 wl_ess_conf_t *wls; 1164 1165 (void) memcpy(&wdev->swd_essid, wldp_buf, 1166 sizeof (wl_essid_t)); 1167 wdev->swd_linkstatus = WL_CONNECTED; 1168 1169 /* Lookup the signal strength of the connected ESSID */ 1170 for (i = 0; i < wdev->swd_esslist_num; i++) { 1171 wls = wdev->swd_esslist[i]; 1172 if (strcasecmp(wls->wl_ess_conf_essid.wl_essid_essid, 1173 wdev->swd_essid.wl_essid_essid) == 0) { 1174 wdev->swd_rssi = wls->wl_ess_conf_sl; 1175 break; 1176 } 1177 } 1178 break; 1179 } 1180 case MAC_PROP_WL_BSSID: { 1181 (void) memcpy(&wdev->swd_bssid, wldp_buf, 1182 sizeof (wl_bssid_t)); 1183 break; 1184 } 1185 case MAC_PROP_WL_PHY_CONFIG: 1186 case MAC_PROP_WL_KEY_TAB: 1187 case MAC_PROP_WL_AUTH_MODE: 1188 case MAC_PROP_WL_ENCRYPTION: 1189 case MAC_PROP_WL_BSSTYPE: 1190 case MAC_PROP_WL_DESIRED_RATES: 1191 break; 1192 case MAC_PROP_PRIVATE: 1193 err = simnet_set_priv_prop(sdev, pr_name, 1194 wldp_length, wldp_buf); 1195 break; 1196 default: 1197 break; 1198 } 1199 1200 return (err); 1201 } 1202 1203 static int 1204 simnet_get_priv_prop(simnet_dev_t *sdev, const char *pr_name, uint_t pr_flags, 1205 uint_t pr_valsize, void *pr_val) 1206 { 1207 simnet_wifidev_t *wdev = sdev->sd_wifidev; 1208 boolean_t is_default = ((pr_flags & MAC_PROP_DEFAULT) != 0); 1209 int err = 0; 1210 int value; 1211 1212 if (strcmp(pr_name, "_wl_esslist") == 0) { 1213 /* Returns num of _wl_ess_conf_t that have been set */ 1214 value = (is_default ? 0:wdev->swd_esslist_num); 1215 } else if (strcmp(pr_name, "_wl_connected") == 0) { 1216 value = ((wdev->swd_linkstatus == WL_CONNECTED) ? 1:0); 1217 } else { 1218 err = ENOTSUP; 1219 } 1220 1221 if (err == 0) 1222 (void) snprintf(pr_val, pr_valsize, "%d", value); 1223 return (err); 1224 } 1225 1226 static int 1227 simnet_m_getprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, 1228 uint_t pr_flags, uint_t wldp_length, void *wldp_buf, uint_t *perm) 1229 { 1230 simnet_dev_t *sdev = arg; 1231 simnet_wifidev_t *wdev = sdev->sd_wifidev; 1232 int err = 0; 1233 int i; 1234 1235 if (sdev->sd_type == DL_ETHER) 1236 return (ENOTSUP); 1237 1238 /* mac_prop_id */ 1239 switch (wldp_pr_num) { 1240 case MAC_PROP_WL_ESSID: 1241 (void) memcpy(wldp_buf, &wdev->swd_essid, 1242 sizeof (wl_essid_t)); 1243 break; 1244 case MAC_PROP_WL_BSSID: 1245 (void) memcpy(wldp_buf, &wdev->swd_bssid, 1246 sizeof (wl_bssid_t)); 1247 break; 1248 case MAC_PROP_WL_PHY_CONFIG: 1249 case MAC_PROP_WL_AUTH_MODE: 1250 case MAC_PROP_WL_ENCRYPTION: 1251 break; 1252 case MAC_PROP_WL_BSSTYPE: 1253 *perm = MAC_PROP_PERM_READ; 1254 break; 1255 case MAC_PROP_WL_LINKSTATUS: 1256 (void) memcpy(wldp_buf, &wdev->swd_linkstatus, 1257 sizeof (wdev->swd_linkstatus)); 1258 break; 1259 case MAC_PROP_WL_ESS_LIST: { 1260 wl_ess_conf_t *w_ess_conf; 1261 1262 *perm = MAC_PROP_PERM_READ; 1263 ((wl_ess_list_t *)wldp_buf)->wl_ess_list_num = 1264 wdev->swd_esslist_num; 1265 /* LINTED E_BAD_PTR_CAST_ALIGN */ 1266 w_ess_conf = (wl_ess_conf_t *)((char *)wldp_buf + 1267 offsetof(wl_ess_list_t, wl_ess_list_ess)); 1268 for (i = 0; i < wdev->swd_esslist_num; i++) { 1269 (void) memcpy(w_ess_conf, wdev->swd_esslist[i], 1270 sizeof (wl_ess_conf_t)); 1271 w_ess_conf++; 1272 } 1273 break; 1274 } 1275 case MAC_PROP_WL_SUPPORTED_RATES: 1276 *perm = MAC_PROP_PERM_READ; 1277 break; 1278 case MAC_PROP_WL_RSSI: 1279 *perm = MAC_PROP_PERM_READ; 1280 *(wl_rssi_t *)wldp_buf = wdev->swd_rssi; 1281 break; 1282 case MAC_PROP_WL_RADIO: 1283 *(wl_radio_t *)wldp_buf = B_TRUE; 1284 break; 1285 case MAC_PROP_WL_POWER_MODE: 1286 break; 1287 case MAC_PROP_WL_DESIRED_RATES: 1288 break; 1289 case MAC_PROP_PRIVATE: 1290 err = simnet_get_priv_prop(sdev, pr_name, pr_flags, 1291 wldp_length, wldp_buf); 1292 break; 1293 default: 1294 err = ENOTSUP; 1295 break; 1296 } 1297 1298 return (err); 1299 } 1300