/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * MAC Services Module */ #include #include #include #include #include #include #include #include #include #include #define IMPL_HASHSZ 67 /* prime */ static kmem_cache_t *i_mac_impl_cachep; static ght_t i_mac_impl_hash; /* * Private functions. */ /*ARGSUSED*/ static boolean_t i_mac_ether_unicst_verify(mac_impl_t *mip, const uint8_t *addr) { /* * Check the address is not a group address. */ if (addr[0] & 0x01) return (B_FALSE); return (B_TRUE); } static boolean_t i_mac_ether_multicst_verify(mac_impl_t *mip, const uint8_t *addr) { mac_t *mp = mip->mi_mp; /* * Check the address is a group address. */ if (!(addr[0] & 0x01)) return (B_FALSE); /* * Check the address is not the media broadcast address. */ if (bcmp(addr, mp->m_info.mi_brdcst_addr, mip->mi_addr_length) == 0) return (B_FALSE); return (B_TRUE); } /*ARGSUSED*/ static int i_mac_constructor(void *buf, void *arg, int kmflag) { mac_impl_t *mip = buf; bzero(buf, sizeof (mac_impl_t)); mip->mi_link = LINK_STATE_UNKNOWN; rw_init(&mip->mi_state_lock, NULL, RW_DRIVER, NULL); rw_init(&mip->mi_data_lock, NULL, RW_DRIVER, NULL); rw_init(&mip->mi_notify_lock, NULL, RW_DRIVER, NULL); rw_init(&mip->mi_rx_lock, NULL, RW_DRIVER, NULL); rw_init(&mip->mi_txloop_lock, NULL, RW_DRIVER, NULL); rw_init(&mip->mi_resource_lock, NULL, RW_DRIVER, NULL); mutex_init(&mip->mi_activelink_lock, NULL, MUTEX_DEFAULT, NULL); return (0); } /*ARGSUSED*/ static void i_mac_destructor(void *buf, void *arg) { mac_impl_t *mip = buf; ASSERT(mip->mi_mp == NULL); ASSERT(mip->mi_hte == NULL); ASSERT(mip->mi_ref == 0); ASSERT(mip->mi_active == 0); ASSERT(mip->mi_link == LINK_STATE_UNKNOWN); ASSERT(mip->mi_devpromisc == 0); ASSERT(mip->mi_promisc == 0); ASSERT(mip->mi_mmap == NULL); ASSERT(mip->mi_mnfp == NULL); ASSERT(mip->mi_resource_add == NULL); ASSERT(mip->mi_ksp == NULL); rw_destroy(&mip->mi_state_lock); rw_destroy(&mip->mi_data_lock); rw_destroy(&mip->mi_notify_lock); rw_destroy(&mip->mi_rx_lock); rw_destroy(&mip->mi_txloop_lock); rw_destroy(&mip->mi_resource_lock); mutex_destroy(&mip->mi_activelink_lock); } int i_mac_create(mac_t *mp) { dev_info_t *dip; mac_impl_t *mip; int err; ghte_t hte; dip = mp->m_dip; ASSERT(dip != NULL); ASSERT(ddi_get_instance(dip) >= 0); /* * Allocate a new mac_impl_t. */ mip = kmem_cache_alloc(i_mac_impl_cachep, KM_SLEEP); /* * Construct a name. */ (void) snprintf(mip->mi_dev, MAXNAMELEN - 1, "%s%d", ddi_driver_name(dip), ddi_get_instance(dip)); mip->mi_port = mp->m_port; MAC_NAME(mip->mi_name, mip->mi_dev, mip->mi_port); /* * Set the mac_t/mac_impl_t cross-references. */ mip->mi_mp = mp; mp->m_impl = (void *)mip; /* * Allocate a hash table entry. */ hte = ght_alloc(i_mac_impl_hash, KM_SLEEP); GHT_KEY(hte) = GHT_PTR_TO_KEY(mip->mi_name); GHT_VAL(hte) = GHT_PTR_TO_VAL(mip); /* * Insert the hash table entry. */ ght_lock(i_mac_impl_hash, GHT_WRITE); if ((err = ght_insert(hte)) != 0) { ght_free(hte); kmem_cache_free(i_mac_impl_cachep, mip); goto done; } mip->mi_hte = hte; /* * Copy the fixed 'factory' MAC address from the immutable info. * This is taken to be the MAC address currently in use. */ mip->mi_addr_length = mp->m_info.mi_addr_length; bcopy(mp->m_info.mi_unicst_addr, mip->mi_addr, mip->mi_addr_length); /* * Set up the address verification functions. */ ASSERT(mp->m_info.mi_media == DL_ETHER); mip->mi_unicst_verify = i_mac_ether_unicst_verify; mip->mi_multicst_verify = i_mac_ether_multicst_verify; /* * Set up the two possible transmit routines. */ mip->mi_txinfo.mt_fn = mp->m_tx; mip->mi_txinfo.mt_arg = mp->m_driver; mip->mi_txloopinfo.mt_fn = mac_txloop; mip->mi_txloopinfo.mt_arg = mip; /* * Initialize the kstats for this device. */ mac_stat_create(mip); done: ght_unlock(i_mac_impl_hash); return (err); } static void i_mac_destroy(mac_t *mp) { mac_impl_t *mip = mp->m_impl; ghte_t hte; mac_multicst_addr_t *p, *nextp; ght_lock(i_mac_impl_hash, GHT_WRITE); ASSERT(mip->mi_ref == 0); ASSERT(!mip->mi_activelink); /* * Destroy the kstats. */ mac_stat_destroy(mip); /* * Remove and destroy the hash table entry. */ hte = mip->mi_hte; ght_remove(hte); ght_free(hte); mip->mi_hte = NULL; /* * Free the list of multicast addresses. */ for (p = mip->mi_mmap; p != NULL; p = nextp) { nextp = p->mma_nextp; kmem_free(p, sizeof (mac_multicst_addr_t)); } mip->mi_mmap = NULL; /* * Clean up the mac_impl_t ready to go back into the cache. */ mp->m_impl = NULL; mip->mi_mp = NULL; mip->mi_link = LINK_STATE_UNKNOWN; mip->mi_destroying = B_FALSE; /* * Free the structure back to the cache. */ kmem_cache_free(i_mac_impl_cachep, mip); ght_unlock(i_mac_impl_hash); } static void i_mac_notify(mac_impl_t *mip, mac_notify_type_t type) { mac_notify_fn_t *mnfp; mac_notify_t notify; void *arg; /* * Walk the list of notifications. */ rw_enter(&(mip->mi_notify_lock), RW_READER); for (mnfp = mip->mi_mnfp; mnfp != NULL; mnfp = mnfp->mnf_nextp) { notify = mnfp->mnf_fn; arg = mnfp->mnf_arg; ASSERT(notify != NULL); notify(arg, type); } rw_exit(&(mip->mi_notify_lock)); } /* * Module initialization functions. */ void mac_init(void) { int err; i_mac_impl_cachep = kmem_cache_create("mac_impl_cache", sizeof (mac_impl_t), 0, i_mac_constructor, i_mac_destructor, NULL, NULL, NULL, 0); ASSERT(i_mac_impl_cachep != NULL); err = ght_str_create("mac_impl_hash", IMPL_HASHSZ, &i_mac_impl_hash); ASSERT(err == 0); } int mac_fini(void) { int err; if ((err = ght_destroy(i_mac_impl_hash)) != 0) return (err); kmem_cache_destroy(i_mac_impl_cachep); return (0); } /* * Client functions. */ int mac_open(const char *dev, uint_t port, mac_handle_t *mhp) { char name[MAXNAMELEN]; char driver[MAXNAMELEN]; uint_t instance; major_t major; dev_info_t *dip; mac_impl_t *mip; ghte_t hte; int err; /* * Check the device name length to make sure it won't overflow our * buffer. */ if (strlen(dev) >= MAXNAMELEN) return (EINVAL); /* * Split the device name into driver and instance components. */ if (ddi_parse(dev, driver, &instance) != DDI_SUCCESS) return (EINVAL); /* * Get the major number of the driver. */ if ((major = ddi_name_to_major(driver)) == (major_t)-1) return (EINVAL); /* * Hold the given instance to prevent it from being detached. * (This will also attach it if it is not currently attached). */ if ((dip = ddi_hold_devi_by_instance(major, instance, 0)) == NULL) return (EINVAL); /* * Construct the name of the MAC interface. */ MAC_NAME(name, dev, port); /* * Look up its entry in the global hash table. */ again: ght_lock(i_mac_impl_hash, GHT_WRITE); if ((err = ght_find(i_mac_impl_hash, GHT_PTR_TO_KEY(name), &hte)) != 0) goto failed; mip = (mac_impl_t *)GHT_VAL(hte); ASSERT(mip->mi_mp->m_dip == dip); if (mip->mi_destroying) { ght_unlock(i_mac_impl_hash); goto again; } mip->mi_ref++; ght_unlock(i_mac_impl_hash); *mhp = (mac_handle_t)mip; return (0); failed: ght_unlock(i_mac_impl_hash); ddi_release_devi(dip); return (err); } void mac_close(mac_handle_t mh) { mac_impl_t *mip = (mac_impl_t *)mh; dev_info_t *dip = mip->mi_mp->m_dip; ght_lock(i_mac_impl_hash, GHT_WRITE); ASSERT(mip->mi_ref != 0); if (--mip->mi_ref == 0) { ASSERT(!mip->mi_activelink); } ddi_release_devi(dip); ght_unlock(i_mac_impl_hash); } const mac_info_t * mac_info(mac_handle_t mh) { mac_impl_t *mip = (mac_impl_t *)mh; mac_t *mp = mip->mi_mp; /* * Return a pointer to the mac_info_t embedded in the mac_t. */ return (&(mp->m_info)); } uint64_t mac_stat_get(mac_handle_t mh, enum mac_stat stat) { mac_impl_t *mip = (mac_impl_t *)mh; mac_t *mp = mip->mi_mp; ASSERT(mp->m_info.mi_stat[stat]); ASSERT(mp->m_stat != NULL); /* * Call the driver to get the given statistic. */ return (mp->m_stat(mp->m_driver, stat)); } int mac_start(mac_handle_t mh) { mac_impl_t *mip = (mac_impl_t *)mh; mac_t *mp = mip->mi_mp; int err; ASSERT(mp->m_start != NULL); rw_enter(&(mip->mi_state_lock), RW_WRITER); /* * Check whether the device is already started. */ if (mip->mi_active++ != 0) { /* * It's already started so there's nothing more to do. */ err = 0; goto done; } /* * Start the device. */ if ((err = mp->m_start(mp->m_driver)) != 0) --mip->mi_active; done: rw_exit(&(mip->mi_state_lock)); return (err); } void mac_stop(mac_handle_t mh) { mac_impl_t *mip = (mac_impl_t *)mh; mac_t *mp = mip->mi_mp; ASSERT(mp->m_stop != NULL); rw_enter(&(mip->mi_state_lock), RW_WRITER); /* * Check whether the device is still needed. */ ASSERT(mip->mi_active != 0); if (--mip->mi_active != 0) { /* * It's still needed so there's nothing more to do. */ goto done; } /* * Stop the device. */ mp->m_stop(mp->m_driver); done: rw_exit(&(mip->mi_state_lock)); } int mac_multicst_add(mac_handle_t mh, const uint8_t *addr) { mac_impl_t *mip = (mac_impl_t *)mh; mac_t *mp = mip->mi_mp; mac_multicst_addr_t **pp; mac_multicst_addr_t *p; int err; ASSERT(mp->m_multicst != NULL); /* * Verify the address. */ if (!(mip->mi_multicst_verify(mip, addr))) return (EINVAL); /* * Check whether the given address is already enabled. */ rw_enter(&(mip->mi_data_lock), RW_WRITER); for (pp = &(mip->mi_mmap); (p = *pp) != NULL; pp = &(p->mma_nextp)) { if (bcmp(p->mma_addr, addr, mip->mi_addr_length) == 0) { /* * The address is already enabled so just bump the * reference count. */ p->mma_ref++; err = 0; goto done; } } /* * Allocate a new list entry. */ if ((p = kmem_zalloc(sizeof (mac_multicst_addr_t), KM_NOSLEEP)) == NULL) { err = ENOMEM; goto done; } /* * Enable a new multicast address. */ if ((err = mp->m_multicst(mp->m_driver, B_TRUE, addr)) != 0) { kmem_free(p, sizeof (mac_multicst_addr_t)); goto done; } /* * Add the address to the list of enabled addresses. */ bcopy(addr, p->mma_addr, mip->mi_addr_length); p->mma_ref++; *pp = p; done: rw_exit(&(mip->mi_data_lock)); return (err); } int mac_multicst_remove(mac_handle_t mh, const uint8_t *addr) { mac_impl_t *mip = (mac_impl_t *)mh; mac_t *mp = mip->mi_mp; mac_multicst_addr_t **pp; mac_multicst_addr_t *p; int err; ASSERT(mp->m_multicst != NULL); /* * Find the entry in the list for the given address. */ rw_enter(&(mip->mi_data_lock), RW_WRITER); for (pp = &(mip->mi_mmap); (p = *pp) != NULL; pp = &(p->mma_nextp)) { if (bcmp(p->mma_addr, addr, mip->mi_addr_length) == 0) { if (--p->mma_ref == 0) break; /* * There is still a reference to this address so * there's nothing more to do. */ err = 0; goto done; } } /* * We did not find an entry for the given address so it is not * currently enabled. */ if (p == NULL) { err = ENOENT; goto done; } ASSERT(p->mma_ref == 0); /* * Disable the multicast address. */ if ((err = mp->m_multicst(mp->m_driver, B_FALSE, addr)) != 0) { p->mma_ref++; goto done; } /* * Remove it from the list. */ *pp = p->mma_nextp; kmem_free(p, sizeof (mac_multicst_addr_t)); done: rw_exit(&(mip->mi_data_lock)); return (err); } int mac_unicst_set(mac_handle_t mh, const uint8_t *addr) { mac_impl_t *mip = (mac_impl_t *)mh; mac_t *mp = mip->mi_mp; int err; boolean_t notify = B_FALSE; ASSERT(mp->m_unicst != NULL); /* * Verify the address. */ if (!(mip->mi_unicst_verify(mip, addr))) return (EINVAL); /* * Program the new unicast address. */ rw_enter(&(mip->mi_data_lock), RW_WRITER); /* * If address doesn't change, do nothing. * This check is necessary otherwise it may call into mac_unicst_set * recursively. */ if (bcmp(addr, mip->mi_addr, mip->mi_addr_length) == 0) { err = 0; goto done; } if ((err = mp->m_unicst(mp->m_driver, addr)) != 0) goto done; /* * Save the address and flag that we need to send a notification. */ bcopy(addr, mip->mi_addr, mip->mi_addr_length); notify = B_TRUE; done: rw_exit(&(mip->mi_data_lock)); if (notify) i_mac_notify(mip, MAC_NOTE_UNICST); return (err); } void mac_unicst_get(mac_handle_t mh, uint8_t *addr) { mac_impl_t *mip = (mac_impl_t *)mh; /* * Copy out the current unicast address. */ rw_enter(&(mip->mi_data_lock), RW_READER); bcopy(mip->mi_addr, addr, mip->mi_addr_length); rw_exit(&(mip->mi_data_lock)); } int mac_promisc_set(mac_handle_t mh, boolean_t on, mac_promisc_type_t ptype) { mac_impl_t *mip = (mac_impl_t *)mh; mac_t *mp = mip->mi_mp; int err = 0; ASSERT(mp->m_promisc != NULL); ASSERT(ptype == MAC_DEVPROMISC || ptype == MAC_PROMISC); /* * Determine whether we should enable or disable promiscuous mode. * For details on the distinction between "device promiscuous mode" * and "MAC promiscuous mode", see PSARC/2005/289. */ rw_enter(&(mip->mi_data_lock), RW_WRITER); if (on) { /* * Enable promiscuous mode on the device if not yet enabled. */ if (mip->mi_devpromisc++ == 0) { if ((err = mp->m_promisc(mp->m_driver, B_TRUE)) != 0) { mip->mi_devpromisc--; goto done; } i_mac_notify(mip, MAC_NOTE_DEVPROMISC); } /* * Enable promiscuous mode on the MAC if not yet enabled. */ if (ptype == MAC_PROMISC && mip->mi_promisc++ == 0) i_mac_notify(mip, MAC_NOTE_PROMISC); } else { if (mip->mi_devpromisc == 0) { err = EPROTO; goto done; } /* * Disable promiscuous mode on the device if this is the last * enabling. */ if (--mip->mi_devpromisc == 0) { if ((err = mp->m_promisc(mp->m_driver, B_FALSE)) != 0) { mip->mi_devpromisc++; goto done; } i_mac_notify(mip, MAC_NOTE_DEVPROMISC); } /* * Disable promiscuous mode on the MAC if this is the last * enabling. */ if (ptype == MAC_PROMISC && --mip->mi_promisc == 0) i_mac_notify(mip, MAC_NOTE_PROMISC); } done: rw_exit(&(mip->mi_data_lock)); return (err); } boolean_t mac_promisc_get(mac_handle_t mh, mac_promisc_type_t ptype) { mac_impl_t *mip = (mac_impl_t *)mh; ASSERT(ptype == MAC_DEVPROMISC || ptype == MAC_PROMISC); /* * Return the current promiscuity. */ if (ptype == MAC_DEVPROMISC) return (mip->mi_devpromisc != 0); else return (mip->mi_promisc != 0); } void mac_resources(mac_handle_t mh) { mac_impl_t *mip = (mac_impl_t *)mh; mac_t *mp = mip->mi_mp; ASSERT(mp->m_resources != NULL); /* * Call the driver to register its resources. */ mp->m_resources(mp->m_driver); } void mac_ioctl(mac_handle_t mh, queue_t *wq, mblk_t *bp) { mac_impl_t *mip = (mac_impl_t *)mh; mac_t *mp = mip->mi_mp; ASSERT(mp->m_ioctl != NULL); /* * Call the driver to handle the ioctl. */ mp->m_ioctl(mp->m_driver, wq, bp); } const mac_txinfo_t * mac_tx_get(mac_handle_t mh) { mac_impl_t *mip = (mac_impl_t *)mh; mac_txinfo_t *mtp; /* * Grab the lock to prevent us from racing with MAC_PROMISC being * changed. This is sufficient since MAC clients are careful to always * call mac_txloop_add() prior to enabling MAC_PROMISC, and to disable * MAC_PROMISC prior to calling mac_txloop_remove(). */ rw_enter(&mip->mi_txloop_lock, RW_READER); if (mac_promisc_get(mh, MAC_PROMISC)) { ASSERT(mip->mi_mtfp != NULL); mtp = &mip->mi_txloopinfo; } else { /* * Note that we cannot ASSERT() that mip->mi_mtfp is NULL, * because to satisfy the above ASSERT(), we have to disable * MAC_PROMISC prior to calling mac_txloop_remove(). */ mtp = &mip->mi_txinfo; } rw_exit(&mip->mi_txloop_lock); return (mtp); } link_state_t mac_link_get(mac_handle_t mh) { mac_impl_t *mip = (mac_impl_t *)mh; /* * Return the current link state. */ return (mip->mi_link); } mac_notify_handle_t mac_notify_add(mac_handle_t mh, mac_notify_t notify, void *arg) { mac_impl_t *mip = (mac_impl_t *)mh; mac_notify_fn_t *mnfp; mnfp = kmem_zalloc(sizeof (mac_notify_fn_t), KM_SLEEP); mnfp->mnf_fn = notify; mnfp->mnf_arg = arg; /* * Add it to the head of the 'notify' callback list. */ rw_enter(&(mip->mi_notify_lock), RW_WRITER); mnfp->mnf_nextp = mip->mi_mnfp; mip->mi_mnfp = mnfp; rw_exit(&(mip->mi_notify_lock)); return ((mac_notify_handle_t)mnfp); } void mac_notify_remove(mac_handle_t mh, mac_notify_handle_t mnh) { mac_impl_t *mip = (mac_impl_t *)mh; mac_notify_fn_t *mnfp = (mac_notify_fn_t *)mnh; mac_notify_fn_t **pp; mac_notify_fn_t *p; /* * Search the 'notify' callback list for the function closure. */ rw_enter(&(mip->mi_notify_lock), RW_WRITER); for (pp = &(mip->mi_mnfp); (p = *pp) != NULL; pp = &(p->mnf_nextp)) { if (p == mnfp) break; } ASSERT(p != NULL); /* * Remove it from the list. */ *pp = p->mnf_nextp; rw_exit(&(mip->mi_notify_lock)); /* * Free it. */ kmem_free(mnfp, sizeof (mac_notify_fn_t)); } void mac_notify(mac_handle_t mh) { mac_impl_t *mip = (mac_impl_t *)mh; mac_notify_type_t type; for (type = 0; type < MAC_NNOTE; type++) i_mac_notify(mip, type); } mac_rx_handle_t mac_rx_add(mac_handle_t mh, mac_rx_t rx, void *arg) { mac_impl_t *mip = (mac_impl_t *)mh; mac_rx_fn_t *mrfp; mrfp = kmem_zalloc(sizeof (mac_rx_fn_t), KM_SLEEP); mrfp->mrf_fn = rx; mrfp->mrf_arg = arg; /* * Add it to the head of the 'rx' callback list. */ rw_enter(&(mip->mi_rx_lock), RW_WRITER); mrfp->mrf_nextp = mip->mi_mrfp; mip->mi_mrfp = mrfp; rw_exit(&(mip->mi_rx_lock)); return ((mac_rx_handle_t)mrfp); } /* * Unregister a receive function for this mac. This removes the function * from the list of receive functions for this mac. */ void mac_rx_remove(mac_handle_t mh, mac_rx_handle_t mrh) { mac_impl_t *mip = (mac_impl_t *)mh; mac_rx_fn_t *mrfp = (mac_rx_fn_t *)mrh; mac_rx_fn_t **pp; mac_rx_fn_t *p; /* * Search the 'rx' callback list for the function closure. */ rw_enter(&(mip->mi_rx_lock), RW_WRITER); for (pp = &(mip->mi_mrfp); (p = *pp) != NULL; pp = &(p->mrf_nextp)) { if (p == mrfp) break; } ASSERT(p != NULL); /* Remove it from the list. */ *pp = p->mrf_nextp; kmem_free(mrfp, sizeof (mac_rx_fn_t)); rw_exit(&(mip->mi_rx_lock)); } mac_txloop_handle_t mac_txloop_add(mac_handle_t mh, mac_txloop_t tx, void *arg) { mac_impl_t *mip = (mac_impl_t *)mh; mac_txloop_fn_t *mtfp; mtfp = kmem_zalloc(sizeof (mac_txloop_fn_t), KM_SLEEP); mtfp->mtf_fn = tx; mtfp->mtf_arg = arg; /* * Add it to the head of the 'tx' callback list. */ rw_enter(&(mip->mi_txloop_lock), RW_WRITER); mtfp->mtf_nextp = mip->mi_mtfp; mip->mi_mtfp = mtfp; rw_exit(&(mip->mi_txloop_lock)); return ((mac_txloop_handle_t)mtfp); } /* * Unregister a transmit function for this mac. This removes the function * from the list of transmit functions for this mac. */ void mac_txloop_remove(mac_handle_t mh, mac_txloop_handle_t mth) { mac_impl_t *mip = (mac_impl_t *)mh; mac_txloop_fn_t *mtfp = (mac_txloop_fn_t *)mth; mac_txloop_fn_t **pp; mac_txloop_fn_t *p; /* * Search the 'tx' callback list for the function. */ rw_enter(&(mip->mi_txloop_lock), RW_WRITER); for (pp = &(mip->mi_mtfp); (p = *pp) != NULL; pp = &(p->mtf_nextp)) { if (p == mtfp) break; } ASSERT(p != NULL); /* Remove it from the list. */ *pp = p->mtf_nextp; kmem_free(mtfp, sizeof (mac_txloop_fn_t)); rw_exit(&(mip->mi_txloop_lock)); } void mac_resource_set(mac_handle_t mh, mac_resource_add_t add, void *arg) { mac_impl_t *mip = (mac_impl_t *)mh; /* * Update the 'resource_add' callbacks. */ rw_enter(&(mip->mi_resource_lock), RW_WRITER); mip->mi_resource_add = add; mip->mi_resource_add_arg = arg; rw_exit(&(mip->mi_resource_lock)); } /* * Driver support functions. */ int mac_register(mac_t *mp) { int err; char name[MAXNAMELEN], aggr_name[MAXNAMELEN]; struct devnames *dnp; #ifdef DEBUG if (strcmp(mp->m_ident, MAC_IDENT) != 0) cmn_err(CE_WARN, "%s%d/%d: possible mac interface mismatch", ddi_driver_name(mp->m_dip), ddi_get_instance(mp->m_dip), mp->m_port); #endif /* DEBUG */ ASSERT(!(mp->m_info.mi_addr_length & 1)); /* * Create a new mac_impl_t to pair with the mac_t. */ if ((err = i_mac_create(mp)) != 0) return (err); /* * Create a DDI_NT_MAC minor node such that libdevinfo(3lib) can be * used to search for mac interfaces. */ (void) sprintf(name, "%d", mp->m_port); if (ddi_create_minor_node(mp->m_dip, name, S_IFCHR, mp->m_port, DDI_NT_MAC, 0) != DDI_SUCCESS) { i_mac_destroy(mp); return (EEXIST); } /* * Right now only the "aggr" driver creates nodes at mac_register * time, but it is expected that in the future with some * enhancement of devfs, all the drivers can create nodes here. */ if (strcmp(ddi_driver_name(mp->m_dip), "aggr") == 0) { (void) snprintf(aggr_name, MAXNAMELEN, "aggr%u", mp->m_port); err = dld_ppa_create(aggr_name, AGGR_DEV, mp->m_port, 0); if (err != 0) { ASSERT(err != EEXIST); ddi_remove_minor_node(mp->m_dip, name); i_mac_destroy(mp); return (err); } } /* set the gldv3 flag in dn_flags */ dnp = &devnamesp[ddi_driver_major(mp->m_dip)]; LOCK_DEV_OPS(&dnp->dn_lock); dnp->dn_flags |= DN_GLDV3_DRIVER; UNLOCK_DEV_OPS(&dnp->dn_lock); cmn_err(CE_NOTE, "!%s%d/%d registered", ddi_driver_name(mp->m_dip), ddi_get_instance(mp->m_dip), mp->m_port); return (0); } int mac_unregister(mac_t *mp) { int err; char name[MAXNAMELEN]; mac_impl_t *mip = mp->m_impl; /* * See if there are any other references to this mac_t (e.g., VLAN's). * If not, set mi_destroying to prevent any new VLAN's from being * created before we can perform the i_mac_destroy() below. */ ght_lock(i_mac_impl_hash, GHT_WRITE); if (mip->mi_ref > 0) { ght_unlock(i_mac_impl_hash); return (EBUSY); } mip->mi_destroying = B_TRUE; ght_unlock(i_mac_impl_hash); if (strcmp(ddi_driver_name(mp->m_dip), "aggr") == 0) { (void) snprintf(name, MAXNAMELEN, "aggr%u", mp->m_port); if ((err = dld_ppa_destroy(name)) != 0) return (err); } /* * Destroy the mac_impl_t. */ i_mac_destroy(mp); /* * Remove the minor node. */ (void) sprintf(name, "%d", mp->m_port); ddi_remove_minor_node(mp->m_dip, name); cmn_err(CE_NOTE, "!%s%d/%d unregistered", ddi_driver_name(mp->m_dip), ddi_get_instance(mp->m_dip), mp->m_port); return (0); } void mac_rx(mac_t *mp, mac_resource_handle_t mrh, mblk_t *bp) { mac_impl_t *mip = mp->m_impl; mac_rx_fn_t *mrfp; /* * Call all registered receive functions. */ rw_enter(&mip->mi_rx_lock, RW_READER); mrfp = mip->mi_mrfp; if (mrfp == NULL) { /* There are no registered receive functions. */ freemsgchain(bp); rw_exit(&mip->mi_rx_lock); return; } do { mblk_t *recv_bp; if (mrfp->mrf_nextp != NULL) { /* XXX Do we bump a counter if copymsgchain() fails? */ recv_bp = copymsgchain(bp); } else { recv_bp = bp; } if (recv_bp != NULL) mrfp->mrf_fn(mrfp->mrf_arg, mrh, recv_bp); mrfp = mrfp->mrf_nextp; } while (mrfp != NULL); rw_exit(&mip->mi_rx_lock); } /* * Transmit function -- ONLY used when there are registered loopback listeners. */ mblk_t * mac_txloop(void *arg, mblk_t *bp) { mac_impl_t *mip = arg; mac_t *mp = mip->mi_mp; mac_txloop_fn_t *mtfp; mblk_t *loop_bp, *resid_bp, *next_bp; while (bp != NULL) { next_bp = bp->b_next; bp->b_next = NULL; if ((loop_bp = copymsg(bp)) == NULL) goto noresources; if ((resid_bp = mp->m_tx(mp->m_driver, bp)) != NULL) { ASSERT(resid_bp == bp); freemsg(loop_bp); goto noresources; } rw_enter(&mip->mi_txloop_lock, RW_READER); mtfp = mip->mi_mtfp; while (mtfp != NULL && loop_bp != NULL) { bp = loop_bp; /* XXX counter bump if copymsg() fails? */ if (mtfp->mtf_nextp != NULL) loop_bp = copymsg(bp); else loop_bp = NULL; mtfp->mtf_fn(mtfp->mtf_arg, bp); mtfp = mtfp->mtf_nextp; } rw_exit(&mip->mi_txloop_lock); /* * It's possible we've raced with the disabling of promiscuous * mode, in which case we can discard our copy. */ if (loop_bp != NULL) freemsg(loop_bp); bp = next_bp; } return (NULL); noresources: bp->b_next = next_bp; return (bp); } void mac_link_update(mac_t *mp, link_state_t link) { mac_impl_t *mip = mp->m_impl; ASSERT(mip->mi_mp == mp); /* * Save the link state. */ mip->mi_link = link; /* * Send a MAC_NOTE_LINK notification. */ i_mac_notify(mip, MAC_NOTE_LINK); } void mac_unicst_update(mac_t *mp, const uint8_t *addr) { mac_impl_t *mip = mp->m_impl; ASSERT(mip->mi_mp == mp); /* * Save the address. */ bcopy(addr, mip->mi_addr, mip->mi_addr_length); /* * Send a MAC_NOTE_UNICST notification. */ i_mac_notify(mip, MAC_NOTE_UNICST); } void mac_tx_update(mac_t *mp) { mac_impl_t *mip = mp->m_impl; ASSERT(mip->mi_mp == mp); /* * Send a MAC_NOTE_TX notification. */ i_mac_notify(mip, MAC_NOTE_TX); } void mac_resource_update(mac_t *mp) { mac_impl_t *mip = mp->m_impl; ASSERT(mip->mi_mp == mp); /* * Send a MAC_NOTE_RESOURCE notification. */ i_mac_notify(mip, MAC_NOTE_RESOURCE); } mac_resource_handle_t mac_resource_add(mac_t *mp, mac_resource_t *mrp) { mac_impl_t *mip = mp->m_impl; mac_resource_handle_t mrh; mac_resource_add_t add; void *arg; rw_enter(&mip->mi_resource_lock, RW_READER); add = mip->mi_resource_add; arg = mip->mi_resource_add_arg; mrh = add(arg, mrp); rw_exit(&mip->mi_resource_lock); return (mrh); } void mac_multicst_refresh(mac_t *mp, mac_multicst_t refresh, void *arg, boolean_t add) { mac_impl_t *mip = mp->m_impl; mac_multicst_addr_t *p; /* * If no specific refresh function was given then default to the * driver's m_multicst entry point. */ if (refresh == NULL) { refresh = mp->m_multicst; arg = mp->m_driver; } ASSERT(refresh != NULL); /* * Walk the multicast address list and call the refresh function for * each address. */ rw_enter(&(mip->mi_data_lock), RW_READER); for (p = mip->mi_mmap; p != NULL; p = p->mma_nextp) refresh(arg, add, p->mma_addr); rw_exit(&(mip->mi_data_lock)); } void mac_unicst_refresh(mac_t *mp, mac_unicst_t refresh, void *arg) { mac_impl_t *mip = mp->m_impl; /* * If no specific refresh function was given then default to the * driver's m_unicst entry point. */ if (refresh == NULL) { refresh = mp->m_unicst; arg = mp->m_driver; } ASSERT(refresh != NULL); /* * Call the refresh function with the current unicast address. */ refresh(arg, mip->mi_addr); } void mac_promisc_refresh(mac_t *mp, mac_promisc_t refresh, void *arg) { mac_impl_t *mip = mp->m_impl; /* * If no specific refresh function was given then default to the * driver's m_promisc entry point. */ if (refresh == NULL) { refresh = mp->m_promisc; arg = mp->m_driver; } ASSERT(refresh != NULL); /* * Call the refresh function with the current promiscuity. */ refresh(arg, (mip->mi_devpromisc != 0)); } boolean_t mac_active_set(mac_handle_t mh) { mac_impl_t *mip = (mac_impl_t *)mh; mutex_enter(&mip->mi_activelink_lock); if (mip->mi_activelink) { mutex_exit(&mip->mi_activelink_lock); return (B_FALSE); } mip->mi_activelink = B_TRUE; mutex_exit(&mip->mi_activelink_lock); return (B_TRUE); } void mac_active_clear(mac_handle_t mh) { mac_impl_t *mip = (mac_impl_t *)mh; mutex_enter(&mip->mi_activelink_lock); ASSERT(mip->mi_activelink); mip->mi_activelink = B_FALSE; mutex_exit(&mip->mi_activelink_lock); }