/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * The softmac driver is used to "unify" non-GLDv3 drivers to the GLDv3 * framework. It also creates the kernel datalink structure for each * physical network device. * * Specifically, a softmac will be created for each physical network device * (dip) during the device's post-attach process. When this softmac is * created, the following will also be done: * - create the device's mapping; * - register the mac if this is a non-GLDv3 device and the media type is * supported by the GLDv3 framework; * - create the kernel data-link structure for this physical device; * * This softmac will be destroyed during the device's pre-detach process, * and all the above will be undone. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Used as a parameter to the mod hash walk of softmac structures */ typedef struct { softmac_t *smw_softmac; boolean_t smw_retry; } softmac_walk_t; /* * Softmac hash table including softmacs for both style-2 and style-1 devices. */ static krwlock_t softmac_hash_lock; static mod_hash_t *softmac_hash; static kmutex_t smac_global_lock; static kcondvar_t smac_global_cv; #define SOFTMAC_HASHSZ 64 static void softmac_create_task(void *); static void softmac_mac_register(softmac_t *); static int softmac_create_datalink(softmac_t *); static int softmac_m_start(void *); static void softmac_m_stop(void *); static int softmac_m_open(void *); static void softmac_m_close(void *); static boolean_t softmac_m_getcapab(void *, mac_capab_t, void *); #define SOFTMAC_M_CALLBACK_FLAGS \ (MC_IOCTL | MC_GETCAPAB | MC_OPEN | MC_CLOSE) static mac_callbacks_t softmac_m_callbacks = { SOFTMAC_M_CALLBACK_FLAGS, softmac_m_stat, softmac_m_start, softmac_m_stop, softmac_m_promisc, softmac_m_multicst, softmac_m_unicst, softmac_m_tx, softmac_m_ioctl, softmac_m_getcapab, softmac_m_open, softmac_m_close }; void softmac_init() { softmac_hash = mod_hash_create_extended("softmac_hash", SOFTMAC_HASHSZ, mod_hash_null_keydtor, mod_hash_null_valdtor, mod_hash_bystr, NULL, mod_hash_strkey_cmp, KM_SLEEP); rw_init(&softmac_hash_lock, NULL, RW_DEFAULT, NULL); mutex_init(&smac_global_lock, NULL, MUTEX_DRIVER, NULL); cv_init(&smac_global_cv, NULL, CV_DRIVER, NULL); } void softmac_fini() { rw_destroy(&softmac_hash_lock); mod_hash_destroy_hash(softmac_hash); mutex_destroy(&smac_global_lock); cv_destroy(&smac_global_cv); } /* ARGSUSED */ static uint_t softmac_exist(mod_hash_key_t key, mod_hash_val_t *val, void *arg) { boolean_t *pexist = arg; *pexist = B_TRUE; return (MH_WALK_TERMINATE); } boolean_t softmac_busy() { boolean_t exist = B_FALSE; rw_enter(&softmac_hash_lock, RW_READER); mod_hash_walk(softmac_hash, softmac_exist, &exist); rw_exit(&softmac_hash_lock); return (exist); } /* * * softmac_create() is called for each minor node during the post-attach of * each DDI_NT_NET device instance. Note that it is possible that a device * instance has two minor nodes (DLPI style-1 and style-2), so that for that * specific device, softmac_create() could be called twice. * * A softmac_t is used to track each DDI_NT_NET device, and a softmac_dev_t * is created to track each minor node. * * For each minor node of a legacy device, a taskq is started to finish * softmac_mac_register(), which will finish the rest of work (see comments * above softmac_mac_register()). * * softmac state machine * -------------------------------------------------------------------------- * OLD STATE EVENT NEW STATE * -------------------------------------------------------------------------- * UNINIT attach of 1st minor node ATTACH_INPROG * okcnt = 0 net_postattach -> softmac_create okcnt = 1 * * ATTACH_INPROG attach of 2nd minor node (GLDv3) ATTACH_DONE * okcnt = 1 net_postattach -> softmac_create okcnt = 2 * * ATTACH_INPROG attach of 2nd minor node (legacy) ATTACH_INPROG * okcnt = 1 net_postattach -> softmac_create okcnt = 2 * schedule softmac_mac_register * * ATTACH_INPROG legacy device node ATTACH_DONE * okcnt = 2 softmac_mac_register okcnt = 2 * * ATTACH_DONE detach of 1st minor node DETACH_INPROG * okcnt = 2 (success) okcnt = 1 * * DETACH_INPROG detach of 2nd minor node UNINIT (or free) * okcnt = 1 (success) okcnt = 0 * * ATTACH_DONE detach failure state unchanged * DETACH_INPROG left = okcnt * * DETACH_INPROG reattach ATTACH_INPROG * okcnt = 0,1 net_postattach -> softmac_create * * ATTACH_DONE reattach ATTACH_DONE * left != 0 net_postattach -> softmac_create left = 0 * * Abbreviation notes: * states have SOFTMAC_ prefix, * okcnt - softmac_attach_okcnt, * left - softmac_attached_left */ #ifdef DEBUG void softmac_state_verify(softmac_t *softmac) { ASSERT(MUTEX_HELD(&softmac->smac_mutex)); /* * There are at most 2 minor nodes, one per DLPI style */ ASSERT(softmac->smac_cnt <= 2 && softmac->smac_attachok_cnt <= 2); /* * The smac_attachok_cnt represents the number of attaches i.e. the * number of times net_postattach -> softmac_create() has been called * for a device instance. */ ASSERT(softmac->smac_attachok_cnt == SMAC_NONZERO_NODECNT(softmac)); /* * softmac_create (or softmac_mac_register) -> softmac_create_datalink * happens only after all minor nodes have been attached */ ASSERT(softmac->smac_state != SOFTMAC_ATTACH_DONE || softmac->smac_attachok_cnt == softmac->smac_cnt); if (softmac->smac_attachok_cnt == 0) { ASSERT(softmac->smac_state == SOFTMAC_UNINIT); ASSERT(softmac->smac_mh == NULL); } else if (softmac->smac_attachok_cnt < softmac->smac_cnt) { ASSERT(softmac->smac_state == SOFTMAC_ATTACH_INPROG || softmac->smac_state == SOFTMAC_DETACH_INPROG); ASSERT(softmac->smac_mh == NULL); } else { /* * In the stable condition the state whould be * SOFTMAC_ATTACH_DONE. But there is a small transient window * in softmac_destroy where we change the state to * SOFTMAC_DETACH_INPROG and drop the lock before doing * the link destroy */ ASSERT(softmac->smac_attachok_cnt == softmac->smac_cnt); ASSERT(softmac->smac_state != SOFTMAC_UNINIT); } if (softmac->smac_mh != NULL) ASSERT(softmac->smac_attachok_cnt == softmac->smac_cnt); } #endif #ifdef DEBUG #define SOFTMAC_STATE_VERIFY(softmac) softmac_state_verify(softmac) #else #define SOFTMAC_STATE_VERIFY(softmac) #endif int softmac_create(dev_info_t *dip, dev_t dev) { char devname[MAXNAMELEN]; softmac_t *softmac; softmac_dev_t *softmac_dev = NULL; int index; int ppa, err = 0; /* * Force the softmac driver to be attached. */ if (i_ddi_attach_pseudo_node(SOFTMAC_DEV_NAME) == NULL) { cmn_err(CE_WARN, "softmac_create:softmac attach fails"); return (ENXIO); } ppa = ddi_get_instance(dip); (void) snprintf(devname, MAXNAMELEN, "%s%d", ddi_driver_name(dip), ppa); /* * We expect legacy devices have at most two minor nodes - one style-1 * and one style-2. */ if (!GLDV3_DRV(ddi_driver_major(dip)) && i_ddi_minor_node_count(dip, DDI_NT_NET) > 2) { cmn_err(CE_WARN, "%s has more than 2 minor nodes; unsupported", devname); return (ENOTSUP); } /* * Check whether the softmac for the specified device already exists */ rw_enter(&softmac_hash_lock, RW_WRITER); if ((err = mod_hash_find(softmac_hash, (mod_hash_key_t)devname, (mod_hash_val_t *)&softmac)) != 0) { softmac = kmem_zalloc(sizeof (softmac_t), KM_SLEEP); mutex_init(&softmac->smac_mutex, NULL, MUTEX_DRIVER, NULL); cv_init(&softmac->smac_cv, NULL, CV_DRIVER, NULL); (void) strlcpy(softmac->smac_devname, devname, MAXNAMELEN); /* * Insert the softmac into the hash table. */ err = mod_hash_insert(softmac_hash, (mod_hash_key_t)softmac->smac_devname, (mod_hash_val_t)softmac); ASSERT(err == 0); mutex_enter(&smac_global_lock); cv_broadcast(&smac_global_cv); mutex_exit(&smac_global_lock); } mutex_enter(&softmac->smac_mutex); SOFTMAC_STATE_VERIFY(softmac); if (softmac->smac_state != SOFTMAC_ATTACH_DONE) softmac->smac_state = SOFTMAC_ATTACH_INPROG; if (softmac->smac_attachok_cnt == 0) { /* * Initialize the softmac if this is the post-attach of the * first minor node. */ softmac->smac_flags = 0; softmac->smac_umajor = ddi_driver_major(dip); softmac->smac_uppa = ppa; /* * Note that for GLDv3 devices, we create devfs minor nodes * for VLANs as well. Assume a GLDv3 driver on which only * a VLAN is created. During the detachment of this device * instance, the following would happen: * a. the pre-detach callback softmac_destroy() succeeds. * Because the physical link itself is not in use, * softmac_destroy() succeeds and destroys softmac_t; * b. the device detach fails in mac_unregister() because * this MAC is still used by a VLAN. * c. the post-attach callback is then called which leads * us here. Note that ddi_minor_node_count() returns 3 * (including the minior node of the VLAN). In that case, * we must correct the minor node count to 2 as that is * the count of minor nodes that go through post-attach. */ if (GLDV3_DRV(ddi_driver_major(dip))) { softmac->smac_flags |= SOFTMAC_GLDV3; softmac->smac_cnt = 2; } else { softmac->smac_cnt = i_ddi_minor_node_count(dip, DDI_NT_NET); } } index = (getmajor(dev) == ddi_name_to_major("clone")); if (softmac->smac_softmac[index] != NULL) { /* * This is possible if the post_attach() is called after * pre_detach() fails. This seems to be a defect of the DACF * framework. We work around it by using a smac_attached_left * field that tracks this */ ASSERT(softmac->smac_attached_left != 0); softmac->smac_attached_left--; mutex_exit(&softmac->smac_mutex); rw_exit(&softmac_hash_lock); return (0); } mutex_exit(&softmac->smac_mutex); rw_exit(&softmac_hash_lock); softmac_dev = kmem_zalloc(sizeof (softmac_dev_t), KM_SLEEP); softmac_dev->sd_dev = dev; mutex_enter(&softmac->smac_mutex); softmac->smac_softmac[index] = softmac_dev; /* * Continue to register the mac and create the datalink only when all * the minor nodes are attached. */ if (++softmac->smac_attachok_cnt != softmac->smac_cnt) { mutex_exit(&softmac->smac_mutex); return (0); } /* * All of the minor nodes have been attached; start a taskq * to do the rest of the work. We use a taskq instead of * doing the work here because: * * We could be called as a result of a open() system call * where spec_open() already SLOCKED the snode. Using a taskq * sidesteps the risk that our ldi_open_by_dev() call would * deadlock trying to set SLOCKED on the snode again. * * The devfs design requires that the downcalls don't use any * interruptible cv_wait which happens when we do door upcalls. * Otherwise the downcalls which may be holding devfs resources * may cause a deadlock if the thread is stopped. Also we need to make * sure these downcalls into softmac_create or softmac_destroy * don't cv_wait on any devfs related condition. Thus softmac_destroy * returns EBUSY if the asynchronous threads started in softmac_create * haven't finished. */ (void) taskq_dispatch(system_taskq, softmac_create_task, softmac, TQ_SLEEP); mutex_exit(&softmac->smac_mutex); return (0); } static boolean_t softmac_m_getcapab(void *arg, mac_capab_t cap, void *cap_data) { softmac_t *softmac = arg; if (!(softmac->smac_capab_flags & cap)) return (B_FALSE); switch (cap) { case MAC_CAPAB_HCKSUM: { uint32_t *txflags = cap_data; *txflags = softmac->smac_hcksum_txflags; break; } case MAC_CAPAB_LEGACY: { mac_capab_legacy_t *legacy = cap_data; legacy->ml_unsup_note = ~softmac->smac_notifications & (DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN | DL_NOTE_SPEED); legacy->ml_dev = makedevice(softmac->smac_umajor, softmac->smac_uppa + 1); break; } /* * For the capabilities below, there's nothing for us to fill in; * simply return B_TRUE if we support it. */ case MAC_CAPAB_NO_ZCOPY: case MAC_CAPAB_NO_NATIVEVLAN: default: break; } return (B_TRUE); } static int softmac_update_info(softmac_t *softmac, datalink_id_t *linkidp) { datalink_id_t linkid = DATALINK_INVALID_LINKID; uint32_t media; int err; if ((err = dls_mgmt_update(softmac->smac_devname, softmac->smac_media, softmac->smac_flags & SOFTMAC_NOSUPP, &media, &linkid)) == 0) { *linkidp = linkid; } if (err == EEXIST) { /* * There is a link name conflict. Either: * * - An existing link with the same device name with a * different media type from of the given type. * Mark this link back to persistent only; or * * - We cannot assign the "suggested" name because * GLDv3 and therefore vanity naming is not supported * for this link type. Delete this link's mapping. */ if (media != softmac->smac_media) { cmn_err(CE_WARN, "%s device %s conflicts with " "existing %s device %s.", dl_mactypestr(softmac->smac_media), softmac->smac_devname, dl_mactypestr(media), softmac->smac_devname); (void) dls_mgmt_destroy(linkid, B_FALSE); } else { cmn_err(CE_WARN, "link name %s is already in-use.", softmac->smac_devname); (void) dls_mgmt_destroy(linkid, B_TRUE); } cmn_err(CE_WARN, "%s device might not be available " "for use.", softmac->smac_devname); cmn_err(CE_WARN, "See dladm(1M) for more information."); } return (err); } /* * This function: * 1. provides the link's media type to dlmgmtd. * 2. creates the GLDv3 datalink if the media type is supported by GLDv3. */ static int softmac_create_datalink(softmac_t *softmac) { datalink_id_t linkid = DATALINK_INVALID_LINKID; int err; /* * Inform dlmgmtd of this link so that softmac_hold_device() is able * to know the existence of this link. If this failed with EBADF, * it might be because dlmgmtd was not started in time (e.g., * diskless boot); ignore the failure and continue to create * the GLDv3 datalink if needed. */ err = dls_mgmt_create(softmac->smac_devname, makedevice(softmac->smac_umajor, softmac->smac_uppa + 1), DATALINK_CLASS_PHYS, DL_OTHER, B_TRUE, &linkid); if (err != 0 && err != EBADF) return (err); /* * Provide the media type of the physical link to dlmgmtd. */ if ((err != EBADF) && ((err = softmac_update_info(softmac, &linkid)) != 0)) { return (err); } /* * Create the GLDv3 datalink. */ if ((!(softmac->smac_flags & SOFTMAC_NOSUPP)) && ((err = dls_devnet_create(softmac->smac_mh, linkid)) != 0)) { cmn_err(CE_WARN, "dls_devnet_create failed for %s", softmac->smac_devname); return (err); } if (linkid == DATALINK_INVALID_LINKID) { mutex_enter(&softmac->smac_mutex); softmac->smac_flags |= SOFTMAC_NEED_RECREATE; mutex_exit(&softmac->smac_mutex); } return (0); } static void softmac_create_task(void *arg) { softmac_t *softmac = arg; mac_handle_t mh; int err; if (!GLDV3_DRV(softmac->smac_umajor)) { softmac_mac_register(softmac); return; } if ((err = mac_open(softmac->smac_devname, &mh)) != 0) goto done; mutex_enter(&softmac->smac_mutex); softmac->smac_media = (mac_info(mh))->mi_nativemedia; softmac->smac_mh = mh; mutex_exit(&softmac->smac_mutex); /* * We can safely release the reference on the mac because * this mac will only be unregistered and destroyed when * the device detaches, and the softmac will be destroyed * before then (in the pre-detach routine of the device). */ mac_close(mh); /* * Create the GLDv3 datalink for this mac. */ err = softmac_create_datalink(softmac); done: mutex_enter(&softmac->smac_mutex); if (err != 0) softmac->smac_mh = NULL; softmac->smac_attacherr = err; softmac->smac_state = SOFTMAC_ATTACH_DONE; cv_broadcast(&softmac->smac_cv); mutex_exit(&softmac->smac_mutex); } /* * This function is only called for legacy devices. It: * 1. registers the MAC for the legacy devices whose media type is supported * by the GLDv3 framework. * 2. creates the GLDv3 datalink if the media type is supported by GLDv3. */ static void softmac_mac_register(softmac_t *softmac) { softmac_dev_t *softmac_dev; dev_t dev; ldi_handle_t lh = NULL; ldi_ident_t li = NULL; int index; boolean_t native_vlan = B_FALSE; int err; /* * Note that we do not need any locks to access this softmac pointer, * as softmac_destroy() will wait until this function is called. */ ASSERT(softmac != NULL); ASSERT(softmac->smac_state == SOFTMAC_ATTACH_INPROG && softmac->smac_attachok_cnt == softmac->smac_cnt); if ((err = ldi_ident_from_dip(softmac_dip, &li)) != 0) { mutex_enter(&softmac->smac_mutex); goto done; } /* * Determine whether this legacy device support VLANs by opening * the style-2 device node (if it exists) and attaching to a VLAN * PPA (1000 + ppa). */ dev = makedevice(ddi_name_to_major("clone"), softmac->smac_umajor); err = ldi_open_by_dev(&dev, OTYP_CHR, FREAD|FWRITE, kcred, &lh, li); if (err == 0) { if (dl_attach(lh, softmac->smac_uppa + 1 * 1000, NULL) == 0) native_vlan = B_TRUE; (void) ldi_close(lh, FREAD|FWRITE, kcred); } err = EINVAL; for (index = 0; index < 2; index++) { dl_info_ack_t dlia; dl_error_ack_t dlea; uint32_t notes; struct strioctl iocb; uint32_t margin; int rval; if ((softmac_dev = softmac->smac_softmac[index]) == NULL) continue; softmac->smac_dev = dev = softmac_dev->sd_dev; if (ldi_open_by_dev(&dev, OTYP_CHR, FREAD|FWRITE, kcred, &lh, li) != 0) { continue; } /* * Pop all the intermediate modules in order to negotiate * capabilities correctly. */ while (ldi_ioctl(lh, I_POP, 0, FKIOCTL, kcred, &rval) == 0) ; /* DLPI style-1 or DLPI style-2? */ if ((rval = dl_info(lh, &dlia, NULL, NULL, &dlea)) != 0) { if (rval == ENOTSUP) { cmn_err(CE_NOTE, "softmac: received " "DL_ERROR_ACK to DL_INFO_ACK; " "DLPI errno 0x%x, UNIX errno %d", dlea.dl_errno, dlea.dl_unix_errno); } (void) ldi_close(lh, FREAD|FWRITE, kcred); continue; } /* * Currently only DL_ETHER has GLDv3 mac plugin support. * For media types that GLDv3 does not support, create a * link id for it. */ if ((softmac->smac_media = dlia.dl_mac_type) != DL_ETHER) { (void) ldi_close(lh, FREAD|FWRITE, kcred); err = 0; break; } if ((dlia.dl_provider_style == DL_STYLE2) && (dl_attach(lh, softmac->smac_uppa, NULL) != 0)) { (void) ldi_close(lh, FREAD|FWRITE, kcred); continue; } if ((rval = dl_bind(lh, 0, NULL)) != 0) { if (rval == ENOTSUP) { cmn_err(CE_NOTE, "softmac: received " "DL_ERROR_ACK to DL_BIND_ACK; " "DLPI errno 0x%x, UNIX errno %d", dlea.dl_errno, dlea.dl_unix_errno); } (void) ldi_close(lh, FREAD|FWRITE, kcred); continue; } /* * Call dl_info() after dl_bind() because some drivers only * provide correct information (e.g. MAC address) once bound. */ softmac->smac_addrlen = sizeof (softmac->smac_unicst_addr); if ((rval = dl_info(lh, &dlia, softmac->smac_unicst_addr, &softmac->smac_addrlen, &dlea)) != 0) { if (rval == ENOTSUP) { cmn_err(CE_NOTE, "softmac: received " "DL_ERROR_ACK to DL_INFO_ACK; " "DLPI errno 0x%x, UNIX errno %d", dlea.dl_errno, dlea.dl_unix_errno); } (void) ldi_close(lh, FREAD|FWRITE, kcred); continue; } softmac->smac_style = dlia.dl_provider_style; softmac->smac_saplen = ABS(dlia.dl_sap_length); softmac->smac_min_sdu = dlia.dl_min_sdu; softmac->smac_max_sdu = dlia.dl_max_sdu; if ((softmac->smac_saplen != sizeof (uint16_t)) || (softmac->smac_addrlen != ETHERADDRL) || (dlia.dl_brdcst_addr_length != ETHERADDRL) || (dlia.dl_brdcst_addr_offset == 0)) { (void) ldi_close(lh, FREAD|FWRITE, kcred); continue; } /* * Check other DLPI capabilities. Note that this must be after * dl_bind() because some drivers return DL_ERROR_ACK if the * stream is not bound. It is also before mac_register(), so * we don't need any lock protection here. */ softmac->smac_capab_flags = (MAC_CAPAB_NO_ZCOPY | MAC_CAPAB_LEGACY); softmac->smac_no_capability_req = B_FALSE; if (softmac_fill_capab(lh, softmac) != 0) softmac->smac_no_capability_req = B_TRUE; /* * Check the margin of the underlying driver. */ margin = 0; iocb.ic_cmd = DLIOCMARGININFO; iocb.ic_timout = INFTIM; iocb.ic_len = sizeof (margin); iocb.ic_dp = (char *)&margin; softmac->smac_margin = 0; if (ldi_ioctl(lh, I_STR, (intptr_t)&iocb, FKIOCTL, kcred, &rval) == 0) { softmac->smac_margin = margin; } /* * If the legacy driver doesn't support DLIOCMARGININFO, but * it can support native VLAN, correct its margin value to 4. */ if (native_vlan) { if (softmac->smac_margin == 0) softmac->smac_margin = VLAN_TAGSZ; } else { softmac->smac_capab_flags |= MAC_CAPAB_NO_NATIVEVLAN; } /* * Not all drivers support DL_NOTIFY_REQ, so ignore ENOTSUP. */ softmac->smac_notifications = 0; notes = DL_NOTE_PHYS_ADDR | DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN; switch (dl_notify(lh, ¬es, NULL)) { case 0: softmac->smac_notifications = notes; break; case ENOTSUP: break; default: (void) ldi_close(lh, FREAD|FWRITE, kcred); continue; } (void) ldi_close(lh, FREAD|FWRITE, kcred); err = 0; break; } ldi_ident_release(li); mutex_enter(&softmac->smac_mutex); if (err != 0) goto done; if (softmac->smac_media != DL_ETHER) softmac->smac_flags |= SOFTMAC_NOSUPP; /* * Finally, we're ready to register ourselves with the MAC layer * interface; if this succeeds, we're all ready to start() */ if (!(softmac->smac_flags & SOFTMAC_NOSUPP)) { mac_register_t *macp; if ((macp = mac_alloc(MAC_VERSION)) == NULL) { err = ENOMEM; goto done; } macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER; macp->m_driver = softmac; macp->m_dip = softmac_dip; macp->m_margin = softmac->smac_margin; macp->m_src_addr = softmac->smac_unicst_addr; macp->m_min_sdu = softmac->smac_min_sdu; macp->m_max_sdu = softmac->smac_max_sdu; macp->m_callbacks = &softmac_m_callbacks; macp->m_instance = (uint_t)-1; err = mac_register(macp, &softmac->smac_mh); mac_free(macp); if (err != 0) { cmn_err(CE_WARN, "mac_register failed for %s", softmac->smac_devname); goto done; } } mutex_exit(&softmac->smac_mutex); /* * Try to create the datalink for this softmac. */ if ((err = softmac_create_datalink(softmac)) != 0) { if (!(softmac->smac_flags & SOFTMAC_NOSUPP)) { (void) mac_unregister(softmac->smac_mh); softmac->smac_mh = NULL; } } /* * If succeed, create the thread which handles the DL_NOTIFY_IND from * the lower stream. */ if (softmac->smac_mh != NULL) { softmac->smac_notify_thread = thread_create(NULL, 0, softmac_notify_thread, softmac, 0, &p0, TS_RUN, minclsyspri); } mutex_enter(&softmac->smac_mutex); done: ASSERT(softmac->smac_state == SOFTMAC_ATTACH_INPROG && softmac->smac_attachok_cnt == softmac->smac_cnt); softmac->smac_state = SOFTMAC_ATTACH_DONE; softmac->smac_attacherr = err; cv_broadcast(&softmac->smac_cv); mutex_exit(&softmac->smac_mutex); } int softmac_destroy(dev_info_t *dip, dev_t dev) { char devname[MAXNAMELEN]; softmac_t *softmac; softmac_dev_t *softmac_dev; int index; int ppa, err; datalink_id_t linkid; mac_handle_t smac_mh; uint32_t smac_flags; ppa = ddi_get_instance(dip); (void) snprintf(devname, MAXNAMELEN, "%s%d", ddi_driver_name(dip), ppa); /* * We are called only from the predetach entry point. The DACF * framework ensures there can't be a concurrent postattach call * for the same softmac. The softmac found out from the modhash * below can't vanish beneath us since this is the only place where * it is deleted. */ err = mod_hash_find(softmac_hash, (mod_hash_key_t)devname, (mod_hash_val_t *)&softmac); ASSERT(err == 0); mutex_enter(&softmac->smac_mutex); SOFTMAC_STATE_VERIFY(softmac); /* * Fail the predetach routine if this softmac is in-use. * Make sure these downcalls into softmac_create or softmac_destroy * don't cv_wait on any devfs related condition. Thus softmac_destroy * returns EBUSY if the asynchronous thread started in softmac_create * hasn't finished */ if ((softmac->smac_hold_cnt != 0) || (softmac->smac_state == SOFTMAC_ATTACH_INPROG)) { softmac->smac_attached_left = softmac->smac_attachok_cnt; mutex_exit(&softmac->smac_mutex); return (EBUSY); } /* * Even if the predetach of one minor node has already failed * (smac_attached_left is not 0), the DACF framework will continue * to call the predetach routines of the other minor nodes, * so we fail these calls here. */ if (softmac->smac_attached_left != 0) { mutex_exit(&softmac->smac_mutex); return (EBUSY); } smac_mh = softmac->smac_mh; smac_flags = softmac->smac_flags; softmac->smac_state = SOFTMAC_DETACH_INPROG; mutex_exit(&softmac->smac_mutex); if (smac_mh != NULL) { /* * This is the first minor node that is being detached for this * softmac. */ ASSERT(softmac->smac_attachok_cnt == softmac->smac_cnt); if (!(smac_flags & SOFTMAC_NOSUPP)) { if ((err = dls_devnet_destroy(smac_mh, &linkid, B_FALSE)) != 0) { goto error; } } /* * If softmac_mac_register() succeeds in registering the mac * of the legacy device, unregister it. */ if (!(smac_flags & (SOFTMAC_GLDV3 | SOFTMAC_NOSUPP))) { if ((err = mac_disable_nowait(smac_mh)) != 0) { (void) dls_devnet_create(smac_mh, linkid); goto error; } /* * Ask softmac_notify_thread to quit, and wait for * that to be done. */ mutex_enter(&softmac->smac_mutex); softmac->smac_flags |= SOFTMAC_NOTIFY_QUIT; cv_broadcast(&softmac->smac_cv); while (softmac->smac_notify_thread != NULL) { cv_wait(&softmac->smac_cv, &softmac->smac_mutex); } mutex_exit(&softmac->smac_mutex); VERIFY(mac_unregister(smac_mh) == 0); } softmac->smac_mh = NULL; } /* * Free softmac_dev */ rw_enter(&softmac_hash_lock, RW_WRITER); mutex_enter(&softmac->smac_mutex); ASSERT(softmac->smac_state == SOFTMAC_DETACH_INPROG && softmac->smac_attachok_cnt != 0); softmac->smac_mh = NULL; index = (getmajor(dev) == ddi_name_to_major("clone")); softmac_dev = softmac->smac_softmac[index]; ASSERT(softmac_dev != NULL); softmac->smac_softmac[index] = NULL; kmem_free(softmac_dev, sizeof (softmac_dev_t)); if (--softmac->smac_attachok_cnt == 0) { mod_hash_val_t hashval; softmac->smac_state = SOFTMAC_UNINIT; if (softmac->smac_hold_cnt != 0) { /* * Someone did a softmac_hold_device while we dropped * the locks. Leave the softmac itself intact which * will be reused by the reattach */ mutex_exit(&softmac->smac_mutex); rw_exit(&softmac_hash_lock); return (0); } err = mod_hash_remove(softmac_hash, (mod_hash_key_t)devname, (mod_hash_val_t *)&hashval); ASSERT(err == 0); mutex_exit(&softmac->smac_mutex); rw_exit(&softmac_hash_lock); mutex_destroy(&softmac->smac_mutex); cv_destroy(&softmac->smac_cv); kmem_free(softmac, sizeof (softmac_t)); return (0); } mutex_exit(&softmac->smac_mutex); rw_exit(&softmac_hash_lock); return (0); error: mutex_enter(&softmac->smac_mutex); softmac->smac_attached_left = softmac->smac_attachok_cnt; softmac->smac_state = SOFTMAC_ATTACH_DONE; cv_broadcast(&softmac->smac_cv); mutex_exit(&softmac->smac_mutex); return (err); } /* * This function is called as the result of a newly started dlmgmtd daemon. * * We walk through every softmac that was created but failed to notify * dlmgmtd about it (whose SOFTMAC_NEED_RECREATE flag is set). This occurs * when softmacs are created before dlmgmtd is ready. For example, during * diskless boot, a network device is used (and therefore attached) before * the datalink-management service starts dlmgmtd. */ /* ARGSUSED */ static uint_t softmac_mac_recreate(mod_hash_key_t key, mod_hash_val_t *val, void *arg) { softmac_t *softmac = (softmac_t *)val; datalink_id_t linkid; int err; softmac_walk_t *smwp = arg; /* * The framework itself must not hold any locks across calls to the * mac perimeter. Thus this function does not call any framework * function that needs to grab the mac perimeter. */ ASSERT(RW_READ_HELD(&softmac_hash_lock)); smwp->smw_retry = B_FALSE; mutex_enter(&softmac->smac_mutex); SOFTMAC_STATE_VERIFY(softmac); if (softmac->smac_state == SOFTMAC_ATTACH_INPROG) { /* * Wait till softmac_create or softmac_mac_register finishes * Hold the softmac to ensure it stays around. The wait itself * is done in the caller, since we need to drop all locks * including the mod hash's internal lock before calling * cv_wait. */ smwp->smw_retry = B_TRUE; smwp->smw_softmac = softmac; softmac->smac_hold_cnt++; return (MH_WALK_TERMINATE); } if ((softmac->smac_state != SOFTMAC_ATTACH_DONE) || !(softmac->smac_flags & SOFTMAC_NEED_RECREATE)) { mutex_exit(&softmac->smac_mutex); return (MH_WALK_CONTINUE); } /* * Bumping up the smac_hold_cnt allows us to drop the lock. It also * makes softmac_destroy() return failure on an attempted device detach. * We don't want to hold the lock across calls to other subsystems * like kstats, which will happen in the call to dls_devnet_recreate */ softmac->smac_hold_cnt++; mutex_exit(&softmac->smac_mutex); if (dls_mgmt_create(softmac->smac_devname, makedevice(softmac->smac_umajor, softmac->smac_uppa + 1), DATALINK_CLASS_PHYS, softmac->smac_media, B_TRUE, &linkid) != 0) { softmac_rele_device((dls_dev_handle_t)softmac); return (MH_WALK_CONTINUE); } if ((err = softmac_update_info(softmac, &linkid)) != 0) { cmn_err(CE_WARN, "softmac: softmac_update_info() for %s " "failed (%d)", softmac->smac_devname, err); softmac_rele_device((dls_dev_handle_t)softmac); return (MH_WALK_CONTINUE); } /* * Create a link for this MAC. The link name will be the same * as the MAC name. */ if (!(softmac->smac_flags & SOFTMAC_NOSUPP)) { err = dls_devnet_recreate(softmac->smac_mh, linkid); if (err != 0) { cmn_err(CE_WARN, "softmac: dls_devnet_recreate() for " "%s (linkid %d) failed (%d)", softmac->smac_devname, linkid, err); } } mutex_enter(&softmac->smac_mutex); softmac->smac_flags &= ~SOFTMAC_NEED_RECREATE; ASSERT(softmac->smac_hold_cnt != 0); softmac->smac_hold_cnt--; mutex_exit(&softmac->smac_mutex); return (MH_WALK_CONTINUE); } /* * See comments above softmac_mac_recreate(). */ void softmac_recreate() { softmac_walk_t smw; softmac_t *softmac; /* * Walk through the softmac_hash table. Request to create the * [link name, linkid] mapping if we failed to do so. */ do { smw.smw_retry = B_FALSE; rw_enter(&softmac_hash_lock, RW_READER); mod_hash_walk(softmac_hash, softmac_mac_recreate, &smw); rw_exit(&softmac_hash_lock); if (smw.smw_retry) { /* * softmac_create or softmac_mac_register hasn't yet * finished and the softmac is not yet in the * SOFTMAC_ATTACH_DONE state. */ softmac = smw.smw_softmac; cv_wait(&softmac->smac_cv, &softmac->smac_mutex); softmac->smac_hold_cnt--; mutex_exit(&softmac->smac_mutex); } } while (smw.smw_retry); } /* ARGSUSED */ static int softmac_m_start(void *arg) { return (0); } /* ARGSUSED */ static void softmac_m_stop(void *arg) { } /* * Set up the lower stream above the legacy device which is shared by * GLDv3 MAC clients. Put the lower stream into DLIOCRAW mode to send * and receive the raw data. Further, put the lower stream into * DL_PROMISC_SAP mode to receive all packets of interest. */ static int softmac_lower_setup(softmac_t *softmac, softmac_lower_t **slpp) { ldi_ident_t li; dev_t dev; ldi_handle_t lh = NULL; softmac_lower_t *slp = NULL; smac_ioc_start_t start_arg; struct strioctl strioc; uint32_t notifications; int err, rval; if ((err = ldi_ident_from_dip(softmac_dip, &li)) != 0) return (err); dev = softmac->smac_dev; err = ldi_open_by_dev(&dev, OTYP_CHR, FREAD|FWRITE, kcred, &lh, li); ldi_ident_release(li); if (err != 0) goto done; /* * Pop all the intermediate modules. The autopushed modules will * be pushed when the softmac node is opened. */ while (ldi_ioctl(lh, I_POP, 0, FKIOCTL, kcred, &rval) == 0) ; if ((softmac->smac_style == DL_STYLE2) && ((err = dl_attach(lh, softmac->smac_uppa, NULL)) != 0)) { goto done; } /* * Put the lower stream into DLIOCRAW mode to send/receive raw data. */ if ((err = ldi_ioctl(lh, DLIOCRAW, 0, FKIOCTL, kcred, &rval)) != 0) goto done; /* * Then push the softmac shim layer atop the lower stream. */ if ((err = ldi_ioctl(lh, I_PUSH, (intptr_t)SOFTMAC_DEV_NAME, FKIOCTL, kcred, &rval)) != 0) { goto done; } /* * Send the ioctl to get the slp pointer. */ strioc.ic_cmd = SMAC_IOC_START; strioc.ic_timout = INFTIM; strioc.ic_len = sizeof (start_arg); strioc.ic_dp = (char *)&start_arg; if ((err = ldi_ioctl(lh, I_STR, (intptr_t)&strioc, FKIOCTL, kcred, &rval)) != 0) { goto done; } slp = start_arg.si_slp; slp->sl_lh = lh; slp->sl_softmac = softmac; *slpp = slp; /* * Bind to SAP 2 on token ring, 0 on other interface types. * (SAP 0 has special significance on token ring). * Note that the receive-side packets could come anytime after bind. */ if (softmac->smac_media == DL_TPR) err = softmac_send_bind_req(slp, 2); else err = softmac_send_bind_req(slp, 0); if (err != 0) goto done; /* * Put the lower stream into DL_PROMISC_SAP mode to receive all * packets of interest. * * Some drivers (e.g. the old legacy eri driver) incorrectly pass up * packets to DL_PROMISC_SAP stream when the lower stream is not bound, * so we send DL_PROMISON_REQ after DL_BIND_REQ. */ if ((err = softmac_send_promisc_req(slp, DL_PROMISC_SAP, B_TRUE)) != 0) goto done; /* * Enable the capabilities the underlying driver claims to support. * Some drivers require this to be called after the stream is bound. */ if ((err = softmac_capab_enable(slp)) != 0) goto done; /* * Send the DL_NOTIFY_REQ to enable certain DL_NOTIFY_IND. * We don't have to wait for the ack. */ notifications = DL_NOTE_PHYS_ADDR | DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN | DL_NOTE_PROMISC_ON_PHYS | DL_NOTE_PROMISC_OFF_PHYS; (void) softmac_send_notify_req(slp, (notifications & softmac->smac_notifications)); done: if (err != 0) (void) ldi_close(lh, FREAD|FWRITE, kcred); return (err); } static int softmac_m_open(void *arg) { softmac_t *softmac = arg; softmac_lower_t *slp; int err; ASSERT(MAC_PERIM_HELD(softmac->smac_mh)); ASSERT(softmac->smac_lower_state == SOFTMAC_INITIALIZED); if ((err = softmac_lower_setup(softmac, &slp)) != 0) return (err); softmac->smac_lower = slp; softmac->smac_lower_state = SOFTMAC_READY; return (0); } static void softmac_m_close(void *arg) { softmac_t *softmac = arg; softmac_lower_t *slp; ASSERT(MAC_PERIM_HELD(softmac->smac_mh)); ASSERT(softmac->smac_lower_state == SOFTMAC_READY); slp = softmac->smac_lower; ASSERT(slp != NULL); /* * Note that slp is destroyed when lh is closed. */ (void) ldi_close(slp->sl_lh, FREAD|FWRITE, kcred); softmac->smac_lower_state = SOFTMAC_INITIALIZED; softmac->smac_lower = NULL; } int softmac_hold_device(dev_t dev, dls_dev_handle_t *ddhp) { dev_info_t *dip; const char *drvname; char devname[MAXNAMELEN]; softmac_t *softmac; int ppa, err; if ((ppa = getminor(dev) - 1) > 1000) return (ENOENT); /* * First try to hold this device instance to force the MAC * to be registered. */ if ((dip = ddi_hold_devi_by_instance(getmajor(dev), ppa, 0)) == NULL) return (ENOENT); drvname = ddi_driver_name(dip); /* * Exclude non-physical network device instances, for example, aggr0. */ if ((ddi_driver_major(dip) != getmajor(dev)) || !NETWORK_DRV(getmajor(dev)) || (strcmp(drvname, "aggr") == 0) || (strcmp(drvname, "vnic") == 0)) { ddi_release_devi(dip); return (ENOENT); } /* * This is a network device; wait for its softmac to be registered. */ (void) snprintf(devname, MAXNAMELEN, "%s%d", drvname, ppa); again: rw_enter(&softmac_hash_lock, RW_READER); if (mod_hash_find(softmac_hash, (mod_hash_key_t)devname, (mod_hash_val_t *)&softmac) != 0) { /* * This is rare but possible. It could happen when pre-detach * routine of the device succeeds. But the softmac will then * be recreated when device fails to detach (as this device * is held). */ mutex_enter(&smac_global_lock); rw_exit(&softmac_hash_lock); cv_wait(&smac_global_cv, &smac_global_lock); mutex_exit(&smac_global_lock); goto again; } /* * Bump smac_hold_cnt to prevent device detach. */ mutex_enter(&softmac->smac_mutex); softmac->smac_hold_cnt++; rw_exit(&softmac_hash_lock); /* * Wait till the device is fully attached. */ while (softmac->smac_state != SOFTMAC_ATTACH_DONE) cv_wait(&softmac->smac_cv, &softmac->smac_mutex); SOFTMAC_STATE_VERIFY(softmac); if ((err = softmac->smac_attacherr) != 0) softmac->smac_hold_cnt--; else *ddhp = (dls_dev_handle_t)softmac; mutex_exit(&softmac->smac_mutex); ddi_release_devi(dip); return (err); } void softmac_rele_device(dls_dev_handle_t ddh) { softmac_t *softmac; if (ddh == NULL) return; softmac = (softmac_t *)ddh; mutex_enter(&softmac->smac_mutex); softmac->smac_hold_cnt--; mutex_exit(&softmac->smac_mutex); }