mac.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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 <sys/types.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/strsubr.h>
#include <sys/dlpi.h>
#include <sys/mac.h>
#include <sys/mac_impl.h>
#include <sys/dld_impl.h>
#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;
/*
* 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);
}
void
mac_tx_get(mac_handle_t mh, mac_tx_t *txp, void **argp)
{
mac_impl_t *mip = (mac_impl_t *)mh;
mac_t *mp = mip->mi_mp;
ASSERT(mp->m_tx != NULL);
*txp = mp->m_tx;
*argp = mp->m_driver;
}
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;
for (; mtfp != NULL; mtfp = mtfp->mtf_nextp) {
bp = loop_bp;
if (mtfp->mtf_nextp != NULL) {
loop_bp = copymsg(bp);
/* XXX counter bump if copymsg() fails? */
}
mtfp->mtf_fn(mtfp->mtf_arg, bp);
if (loop_bp == NULL)
break;
}
rw_exit(&mip->mi_txloop_lock);
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);
}