mac.c revision f4b3ec61df05330d25f55a36b975b4d7519fdeb1
/*
* 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 2007 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/modhash.h>
#include <sys/mac.h>
#include <sys/mac_impl.h>
#include <sys/dls.h>
#include <sys/dld.h>
#include <sys/modctl.h>
#include <sys/fs/dv_node.h>
#include <sys/atomic.h>
#define IMPL_HASHSZ 67 /* prime */
static kmem_cache_t *i_mac_impl_cachep;
static mod_hash_t *i_mac_impl_hash;
krwlock_t i_mac_impl_lock;
uint_t i_mac_impl_count;
#define MACTYPE_KMODDIR "mac"
#define MACTYPE_HASHSZ 67
static mod_hash_t *i_mactype_hash;
/*
* i_mactype_lock synchronizes threads that obtain references to mactype_t
* structures through i_mactype_getplugin().
*/
static kmutex_t i_mactype_lock;
static void i_mac_notify_task(void *);
/*
* Private functions.
*/
/*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_linkstate = 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);
mutex_init(&mip->mi_notify_ref_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&mip->mi_notify_cv, NULL, CV_DRIVER, NULL);
return (0);
}
/*ARGSUSED*/
static void
i_mac_destructor(void *buf, void *arg)
{
mac_impl_t *mip = buf;
ASSERT(mip->mi_ref == 0);
ASSERT(mip->mi_active == 0);
ASSERT(mip->mi_linkstate == 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);
ASSERT(mip->mi_kstat_count == 0);
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);
mutex_destroy(&mip->mi_notify_ref_lock);
cv_destroy(&mip->mi_notify_cv);
}
static void
i_mac_notify(mac_impl_t *mip, mac_notify_type_t type)
{
mac_notify_task_arg_t *mnta;
rw_enter(&i_mac_impl_lock, RW_READER);
if (mip->mi_disabled)
goto exit;
if ((mnta = kmem_alloc(sizeof (*mnta), KM_NOSLEEP)) == NULL) {
cmn_err(CE_WARN, "i_mac_notify(%s, 0x%x): memory "
"allocation failed", mip->mi_name, type);
goto exit;
}
mnta->mnt_mip = mip;
mnta->mnt_type = type;
mutex_enter(&mip->mi_notify_ref_lock);
mip->mi_notify_ref++;
mutex_exit(&mip->mi_notify_ref_lock);
rw_exit(&i_mac_impl_lock);
if (taskq_dispatch(system_taskq, i_mac_notify_task, mnta,
TQ_NOSLEEP) == NULL) {
cmn_err(CE_WARN, "i_mac_notify(%s, 0x%x): taskq dispatch "
"failed", mip->mi_name, type);
mutex_enter(&mip->mi_notify_ref_lock);
if (--mip->mi_notify_ref == 0)
cv_signal(&mip->mi_notify_cv);
mutex_exit(&mip->mi_notify_ref_lock);
kmem_free(mnta, sizeof (*mnta));
}
return;
exit:
rw_exit(&i_mac_impl_lock);
}
static void
i_mac_notify_task(void *notify_arg)
{
mac_notify_task_arg_t *mnta = (mac_notify_task_arg_t *)notify_arg;
mac_impl_t *mip;
mac_notify_type_t type;
mac_notify_fn_t *mnfp;
mac_notify_t notify;
void *arg;
mip = mnta->mnt_mip;
type = mnta->mnt_type;
kmem_free(mnta, sizeof (*mnta));
/*
* 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);
mutex_enter(&mip->mi_notify_ref_lock);
if (--mip->mi_notify_ref == 0)
cv_signal(&mip->mi_notify_cv);
mutex_exit(&mip->mi_notify_ref_lock);
}
static mactype_t *
i_mactype_getplugin(const char *pname)
{
mactype_t *mtype = NULL;
boolean_t tried_modload = B_FALSE;
mutex_enter(&i_mactype_lock);
find_registered_mactype:
if (mod_hash_find(i_mactype_hash, (mod_hash_key_t)pname,
(mod_hash_val_t *)&mtype) != 0) {
if (!tried_modload) {
/*
* If the plugin has not yet been loaded, then
* attempt to load it now. If modload() succeeds,
* the plugin should have registered using
* mactype_register(), in which case we can go back
* and attempt to find it again.
*/
if (modload(MACTYPE_KMODDIR, (char *)pname) != -1) {
tried_modload = B_TRUE;
goto find_registered_mactype;
}
}
} else {
/*
* Note that there's no danger that the plugin we've loaded
* could be unloaded between the modload() step and the
* reference count bump here, as we're holding
* i_mactype_lock, which mactype_unregister() also holds.
*/
atomic_inc_32(&mtype->mt_ref);
}
mutex_exit(&i_mactype_lock);
return (mtype);
}
/*
* Module initialization functions.
*/
void
mac_init(void)
{
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);
i_mac_impl_hash = mod_hash_create_extended("mac_impl_hash",
IMPL_HASHSZ, mod_hash_null_keydtor, mod_hash_null_valdtor,
mod_hash_bystr, NULL, mod_hash_strkey_cmp, KM_SLEEP);
rw_init(&i_mac_impl_lock, NULL, RW_DEFAULT, NULL);
i_mac_impl_count = 0;
i_mactype_hash = mod_hash_create_extended("mactype_hash",
MACTYPE_HASHSZ,
mod_hash_null_keydtor, mod_hash_null_valdtor,
mod_hash_bystr, NULL, mod_hash_strkey_cmp, KM_SLEEP);
}
int
mac_fini(void)
{
if (i_mac_impl_count > 0)
return (EBUSY);
mod_hash_destroy_hash(i_mac_impl_hash);
rw_destroy(&i_mac_impl_lock);
kmem_cache_destroy(i_mac_impl_cachep);
mod_hash_destroy_hash(i_mactype_hash);
return (0);
}
/*
* Client functions.
*/
int
mac_open(const char *macname, uint_t ddi_instance, mac_handle_t *mhp)
{
char driver[MAXNAMELEN];
uint_t instance;
major_t major;
dev_info_t *dip;
mac_impl_t *mip;
int err;
/*
* Check the device name length to make sure it won't overflow our
* buffer.
*/
if (strlen(macname) >= MAXNAMELEN)
return (EINVAL);
/*
* Split the device name into driver and instance components.
*/
if (ddi_parse(macname, 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 the instance if it is not currently attached.
* Currently we ensure that mac_register() (called by the driver's
* attach entry point) and all code paths under it cannot possibly
* call mac_open() because this would lead to a recursive attach
* panic.
*/
if ((dip = ddi_hold_devi_by_instance(major, ddi_instance, 0)) == NULL)
return (EINVAL);
/*
* Look up its entry in the global hash table.
*/
again:
rw_enter(&i_mac_impl_lock, RW_WRITER);
err = mod_hash_find(i_mac_impl_hash, (mod_hash_key_t)macname,
(mod_hash_val_t *)&mip);
if (err != 0) {
err = ENOENT;
goto failed;
}
if (mip->mi_disabled) {
rw_exit(&i_mac_impl_lock);
goto again;
}
mip->mi_ref++;
rw_exit(&i_mac_impl_lock);
*mhp = (mac_handle_t)mip;
return (0);
failed:
rw_exit(&i_mac_impl_lock);
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_dip;
rw_enter(&i_mac_impl_lock, RW_WRITER);
ASSERT(mip->mi_ref != 0);
if (--mip->mi_ref == 0) {
ASSERT(!mip->mi_activelink);
}
ddi_release_devi(dip);
rw_exit(&i_mac_impl_lock);
}
const mac_info_t *
mac_info(mac_handle_t mh)
{
return (&((mac_impl_t *)mh)->mi_info);
}
dev_info_t *
mac_devinfo_get(mac_handle_t mh)
{
return (((mac_impl_t *)mh)->mi_dip);
}
uint64_t
mac_stat_get(mac_handle_t mh, uint_t stat)
{
mac_impl_t *mip = (mac_impl_t *)mh;
uint64_t val;
int ret;
/*
* The range of stat determines where it is maintained. Stat
* values from 0 up to (but not including) MAC_STAT_MIN are
* mainteined by the mac module itself. Everything else is
* maintained by the driver.
*/
if (stat < MAC_STAT_MIN) {
/* These stats are maintained by the mac module itself. */
switch (stat) {
case MAC_STAT_LINK_STATE:
return (mip->mi_linkstate);
case MAC_STAT_LINK_UP:
return (mip->mi_linkstate == LINK_STATE_UP);
case MAC_STAT_PROMISC:
return (mip->mi_devpromisc != 0);
default:
ASSERT(B_FALSE);
}
}
/*
* Call the driver to get the given statistic.
*/
ret = mip->mi_getstat(mip->mi_driver, stat, &val);
if (ret != 0) {
/*
* The driver doesn't support this statistic. Get the
* statistic's default value.
*/
val = mac_stat_default(mip, stat);
}
return (val);
}
int
mac_start(mac_handle_t mh)
{
mac_impl_t *mip = (mac_impl_t *)mh;
int err;
ASSERT(mip->mi_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 = mip->mi_start(mip->mi_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;
ASSERT(mip->mi_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.
*/
mip->mi_stop(mip->mi_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_multicst_addr_t **pp;
mac_multicst_addr_t *p;
int err;
ASSERT(mip->mi_multicst != NULL);
/*
* Verify the address.
*/
if ((err = mip->mi_type->mt_ops.mtops_multicst_verify(addr,
mip->mi_pdata)) != 0) {
return (err);
}
/*
* 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_type->mt_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 = mip->mi_multicst(mip->mi_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_type->mt_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_multicst_addr_t **pp;
mac_multicst_addr_t *p;
int err;
ASSERT(mip->mi_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_type->mt_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 = mip->mi_multicst(mip->mi_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);
}
/*
* mac_unicst_verify: Verifies the passed address. It fails
* if the passed address is a group address or has incorrect length.
*/
boolean_t
mac_unicst_verify(mac_handle_t mh, const uint8_t *addr, uint_t len)
{
mac_impl_t *mip = (mac_impl_t *)mh;
/*
* Verify the address.
*/
if ((len != mip->mi_type->mt_addr_length) ||
(mip->mi_type->mt_ops.mtops_unicst_verify(addr,
mip->mi_pdata)) != 0) {
return (B_FALSE);
} else {
return (B_TRUE);
}
}
int
mac_unicst_set(mac_handle_t mh, const uint8_t *addr)
{
mac_impl_t *mip = (mac_impl_t *)mh;
int err;
boolean_t notify = B_FALSE;
ASSERT(mip->mi_unicst != NULL);
/*
* Verify the address.
*/
if ((err = mip->mi_type->mt_ops.mtops_unicst_verify(addr,
mip->mi_pdata)) != 0) {
return (err);
}
/*
* 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_type->mt_addr_length) == 0) {
err = 0;
goto done;
}
if ((err = mip->mi_unicst(mip->mi_driver, addr)) != 0)
goto done;
/*
* Save the address and flag that we need to send a notification.
*/
bcopy(addr, mip->mi_addr, mip->mi_type->mt_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 source address.
*/
rw_enter(&(mip->mi_data_lock), RW_READER);
bcopy(mip->mi_addr, addr, mip->mi_type->mt_addr_length);
rw_exit(&(mip->mi_data_lock));
}
void
mac_dest_get(mac_handle_t mh, uint8_t *addr)
{
mac_impl_t *mip = (mac_impl_t *)mh;
/*
* Copy out the current destination address.
*/
rw_enter(&(mip->mi_data_lock), RW_READER);
bcopy(mip->mi_dstaddr, addr, mip->mi_type->mt_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;
int err = 0;
ASSERT(mip->mi_setpromisc != 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) {
err = mip->mi_setpromisc(mip->mi_driver, B_TRUE);
if (err != 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) {
err = mip->mi_setpromisc(mip->mi_driver, B_FALSE);
if (err != 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;
/*
* If the driver supports resource registration, call the driver to
* ask it to register its resources.
*/
if (mip->mi_callbacks->mc_callbacks & MC_RESOURCES)
mip->mi_resources(mip->mi_driver);
}
void
mac_ioctl(mac_handle_t mh, queue_t *wq, mblk_t *bp)
{
mac_impl_t *mip = (mac_impl_t *)mh;
/*
* Call the driver to handle the ioctl. The driver may not support
* any ioctls, in which case we reply with a NAK on its behalf.
*/
if (mip->mi_callbacks->mc_callbacks & MC_IOCTL)
mip->mi_ioctl(mip->mi_driver, wq, bp);
else
miocnak(wq, bp, 0, EINVAL);
}
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)
{
return (((mac_impl_t *)mh)->mi_linkstate);
}
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.
*/
mac_register_t *
mac_alloc(uint_t mac_version)
{
mac_register_t *mregp;
/*
* Make sure there isn't a version mismatch between the driver and
* the framework. In the future, if multiple versions are
* supported, this check could become more sophisticated.
*/
if (mac_version != MAC_VERSION)
return (NULL);
mregp = kmem_zalloc(sizeof (mac_register_t), KM_SLEEP);
mregp->m_version = mac_version;
return (mregp);
}
void
mac_free(mac_register_t *mregp)
{
kmem_free(mregp, sizeof (mac_register_t));
}
/*
* mac_register() is how drivers register new MACs with the GLDv3
* framework. The mregp argument is allocated by drivers using the
* mac_alloc() function, and can be freed using mac_free() immediately upon
* return from mac_register(). Upon success (0 return value), the mhp
* opaque pointer becomes the driver's handle to its MAC interface, and is
* the argument to all other mac module entry points.
*/
int
mac_register(mac_register_t *mregp, mac_handle_t *mhp)
{
mac_impl_t *mip;
mactype_t *mtype;
int err;
struct devnames *dnp;
minor_t minor;
mod_hash_val_t val;
boolean_t style1_created = B_FALSE, style2_created = B_FALSE;
/* Find the required MAC-Type plugin. */
if ((mtype = i_mactype_getplugin(mregp->m_type_ident)) == NULL)
return (EINVAL);
/* Create a mac_impl_t to represent this MAC. */
mip = kmem_cache_alloc(i_mac_impl_cachep, KM_SLEEP);
/*
* The mac is not ready for open yet.
*/
mip->mi_disabled = B_TRUE;
mip->mi_drvname = ddi_driver_name(mregp->m_dip);
/*
* Some drivers such as aggr need to register multiple MACs. Such
* drivers must supply a non-zero "instance" argument so that each
* MAC can be assigned a unique MAC name and can have unique
* kstats.
*/
mip->mi_instance = ((mregp->m_instance == 0) ?
ddi_get_instance(mregp->m_dip) : mregp->m_instance);
/* Construct the MAC name as <drvname><instance> */
(void) snprintf(mip->mi_name, sizeof (mip->mi_name), "%s%d",
mip->mi_drvname, mip->mi_instance);
rw_enter(&i_mac_impl_lock, RW_WRITER);
if (mod_hash_insert(i_mac_impl_hash,
(mod_hash_key_t)mip->mi_name, (mod_hash_val_t)mip) != 0) {
kmem_cache_free(i_mac_impl_cachep, mip);
rw_exit(&i_mac_impl_lock);
return (EEXIST);
}
atomic_inc_32(&i_mac_impl_count);
mip->mi_driver = mregp->m_driver;
mip->mi_type = mtype;
mip->mi_info.mi_media = mtype->mt_type;
mip->mi_info.mi_nativemedia = mtype->mt_nativetype;
mip->mi_info.mi_sdu_min = mregp->m_min_sdu;
if (mregp->m_max_sdu <= mregp->m_min_sdu) {
err = EINVAL;
goto fail;
}
mip->mi_info.mi_sdu_max = mregp->m_max_sdu;
mip->mi_info.mi_addr_length = mip->mi_type->mt_addr_length;
/*
* If the media supports a broadcast address, cache a pointer to it
* in the mac_info_t so that upper layers can use it.
*/
mip->mi_info.mi_brdcst_addr = mip->mi_type->mt_brdcst_addr;
/*
* Copy the unicast source address into the mac_info_t, but only if
* the MAC-Type defines a non-zero address length. We need to
* handle MAC-Types that have an address length of 0
* (point-to-point protocol MACs for example).
*/
if (mip->mi_type->mt_addr_length > 0) {
if (mregp->m_src_addr == NULL) {
err = EINVAL;
goto fail;
}
mip->mi_info.mi_unicst_addr =
kmem_alloc(mip->mi_type->mt_addr_length, KM_SLEEP);
bcopy(mregp->m_src_addr, mip->mi_info.mi_unicst_addr,
mip->mi_type->mt_addr_length);
/*
* Copy the fixed 'factory' MAC address from the immutable
* info. This is taken to be the MAC address currently in
* use.
*/
bcopy(mip->mi_info.mi_unicst_addr, mip->mi_addr,
mip->mi_type->mt_addr_length);
/* Copy the destination address if one is provided. */
if (mregp->m_dst_addr != NULL) {
bcopy(mregp->m_dst_addr, mip->mi_dstaddr,
mip->mi_type->mt_addr_length);
}
} else if (mregp->m_src_addr != NULL) {
err = EINVAL;
goto fail;
}
/*
* The format of the m_pdata is specific to the plugin. It is
* passed in as an argument to all of the plugin callbacks. The
* driver can update this information by calling
* mac_pdata_update().
*/
if (mregp->m_pdata != NULL) {
/*
* Verify that the plugin supports MAC plugin data and that
* the supplied data is valid.
*/
if (!(mip->mi_type->mt_ops.mtops_ops & MTOPS_PDATA_VERIFY)) {
err = EINVAL;
goto fail;
}
if (!mip->mi_type->mt_ops.mtops_pdata_verify(mregp->m_pdata,
mregp->m_pdata_size)) {
err = EINVAL;
goto fail;
}
mip->mi_pdata = kmem_alloc(mregp->m_pdata_size, KM_SLEEP);
bcopy(mregp->m_pdata, mip->mi_pdata, mregp->m_pdata_size);
mip->mi_pdata_size = mregp->m_pdata_size;
}
/*
* Stash the driver callbacks into the mac_impl_t, but first sanity
* check to make sure all mandatory callbacks are set.
*/
if (mregp->m_callbacks->mc_getstat == NULL ||
mregp->m_callbacks->mc_start == NULL ||
mregp->m_callbacks->mc_stop == NULL ||
mregp->m_callbacks->mc_setpromisc == NULL ||
mregp->m_callbacks->mc_multicst == NULL ||
mregp->m_callbacks->mc_unicst == NULL ||
mregp->m_callbacks->mc_tx == NULL) {
err = EINVAL;
goto fail;
}
mip->mi_callbacks = mregp->m_callbacks;
mip->mi_dip = mregp->m_dip;
/*
* Set up the two possible transmit routines.
*/
mip->mi_txinfo.mt_fn = mip->mi_tx;
mip->mi_txinfo.mt_arg = mip->mi_driver;
mip->mi_txloopinfo.mt_fn = mac_txloop;
mip->mi_txloopinfo.mt_arg = mip;
/*
* Initialize the kstats for this device.
*/
mac_stat_create(mip);
err = EEXIST;
/* Create a style-2 DLPI device */
if (ddi_create_minor_node(mip->mi_dip, (char *)mip->mi_drvname,
S_IFCHR, 0, DDI_NT_NET, CLONE_DEV) != DDI_SUCCESS)
goto fail;
style2_created = B_TRUE;
/* Create a style-1 DLPI device */
minor = (minor_t)mip->mi_instance + 1;
if (ddi_create_minor_node(mip->mi_dip, mip->mi_name, S_IFCHR, minor,
DDI_NT_NET, 0) != DDI_SUCCESS)
goto fail;
style1_created = B_TRUE;
/*
* Create a link for this MAC. The link name will be the same as
* the MAC name.
*/
err = dls_create(mip->mi_name, mip->mi_name,
ddi_get_instance(mip->mi_dip));
if (err != 0)
goto fail;
/* set the gldv3 flag in dn_flags */
dnp = &devnamesp[ddi_driver_major(mip->mi_dip)];
LOCK_DEV_OPS(&dnp->dn_lock);
dnp->dn_flags |= DN_GLDV3_DRIVER;
UNLOCK_DEV_OPS(&dnp->dn_lock);
/*
* Mark the MAC to be ready for open.
*/
mip->mi_disabled = B_FALSE;
cmn_err(CE_NOTE, "!%s registered", mip->mi_name);
rw_exit(&i_mac_impl_lock);
*mhp = (mac_handle_t)mip;
return (0);
fail:
(void) mod_hash_remove(i_mac_impl_hash, (mod_hash_key_t)mip->mi_name,
&val);
ASSERT(mip == (mac_impl_t *)val);
atomic_dec_32(&i_mac_impl_count);
if (mip->mi_info.mi_unicst_addr != NULL) {
kmem_free(mip->mi_info.mi_unicst_addr,
mip->mi_type->mt_addr_length);
mip->mi_info.mi_unicst_addr = NULL;
}
if (style1_created)
ddi_remove_minor_node(mip->mi_dip, mip->mi_name);
if (style2_created)
ddi_remove_minor_node(mip->mi_dip, (char *)mip->mi_drvname);
mac_stat_destroy(mip);
if (mip->mi_type != NULL) {
atomic_dec_32(&mip->mi_type->mt_ref);
mip->mi_type = NULL;
}
if (mip->mi_pdata != NULL) {
kmem_free(mip->mi_pdata, mip->mi_pdata_size);
mip->mi_pdata = NULL;
mip->mi_pdata_size = 0;
}
kmem_cache_free(i_mac_impl_cachep, mip);
rw_exit(&i_mac_impl_lock);
return (err);
}
int
mac_unregister(mac_handle_t mh)
{
int err;
mac_impl_t *mip = (mac_impl_t *)mh;
mod_hash_val_t val;
mac_multicst_addr_t *p, *nextp;
/*
* See if there are any other references to this mac_t (e.g., VLAN's).
* If not, set mi_disabled to prevent any new VLAN's from being
* created while we're destroying this mac.
*/
rw_enter(&i_mac_impl_lock, RW_WRITER);
if (mip->mi_ref > 0) {
rw_exit(&i_mac_impl_lock);
return (EBUSY);
}
mip->mi_disabled = B_TRUE;
rw_exit(&i_mac_impl_lock);
/*
* Wait for all taskqs which process the mac notifications to finish.
*/
mutex_enter(&mip->mi_notify_ref_lock);
while (mip->mi_notify_ref != 0)
cv_wait(&mip->mi_notify_cv, &mip->mi_notify_ref_lock);
mutex_exit(&mip->mi_notify_ref_lock);
if ((err = dls_destroy(mip->mi_name)) != 0) {
rw_enter(&i_mac_impl_lock, RW_WRITER);
mip->mi_disabled = B_FALSE;
rw_exit(&i_mac_impl_lock);
return (err);
}
/*
* Remove both style 1 and style 2 minor nodes
*/
ddi_remove_minor_node(mip->mi_dip, (char *)mip->mi_drvname);
ddi_remove_minor_node(mip->mi_dip, mip->mi_name);
ASSERT(!mip->mi_activelink);
mac_stat_destroy(mip);
(void) mod_hash_remove(i_mac_impl_hash, (mod_hash_key_t)mip->mi_name,
&val);
ASSERT(mip == (mac_impl_t *)val);
ASSERT(i_mac_impl_count > 0);
atomic_dec_32(&i_mac_impl_count);
if (mip->mi_pdata != NULL)
kmem_free(mip->mi_pdata, mip->mi_pdata_size);
mip->mi_pdata = NULL;
mip->mi_pdata_size = 0;
/*
* 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;
mip->mi_linkstate = LINK_STATE_UNKNOWN;
kmem_free(mip->mi_info.mi_unicst_addr, mip->mi_type->mt_addr_length);
mip->mi_info.mi_unicst_addr = NULL;
atomic_dec_32(&mip->mi_type->mt_ref);
mip->mi_type = NULL;
cmn_err(CE_NOTE, "!%s unregistered", mip->mi_name);
kmem_cache_free(i_mac_impl_cachep, mip);
return (0);
}
void
mac_rx(mac_handle_t mh, mac_resource_handle_t mrh, mblk_t *bp)
{
mac_impl_t *mip = (mac_impl_t *)mh;
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_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 = mip->mi_tx(mip->mi_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_handle_t mh, link_state_t link)
{
mac_impl_t *mip = (mac_impl_t *)mh;
/*
* Save the link state.
*/
mip->mi_linkstate = link;
/*
* Send a MAC_NOTE_LINK notification.
*/
i_mac_notify(mip, MAC_NOTE_LINK);
}
void
mac_unicst_update(mac_handle_t mh, const uint8_t *addr)
{
mac_impl_t *mip = (mac_impl_t *)mh;
if (mip->mi_type->mt_addr_length == 0)
return;
/*
* Save the address.
*/
bcopy(addr, mip->mi_addr, mip->mi_type->mt_addr_length);
/*
* Send a MAC_NOTE_UNICST notification.
*/
i_mac_notify(mip, MAC_NOTE_UNICST);
}
void
mac_tx_update(mac_handle_t mh)
{
/*
* Send a MAC_NOTE_TX notification.
*/
i_mac_notify((mac_impl_t *)mh, MAC_NOTE_TX);
}
void
mac_resource_update(mac_handle_t mh)
{
/*
* Send a MAC_NOTE_RESOURCE notification.
*/
i_mac_notify((mac_impl_t *)mh, MAC_NOTE_RESOURCE);
}
mac_resource_handle_t
mac_resource_add(mac_handle_t mh, mac_resource_t *mrp)
{
mac_impl_t *mip = (mac_impl_t *)mh;
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;
if (add != NULL)
mrh = add(arg, mrp);
else
mrh = NULL;
rw_exit(&mip->mi_resource_lock);
return (mrh);
}
int
mac_pdata_update(mac_handle_t mh, void *mac_pdata, size_t dsize)
{
mac_impl_t *mip = (mac_impl_t *)mh;
/*
* Verify that the plugin supports MAC plugin data and that the
* supplied data is valid.
*/
if (!(mip->mi_type->mt_ops.mtops_ops & MTOPS_PDATA_VERIFY))
return (EINVAL);
if (!mip->mi_type->mt_ops.mtops_pdata_verify(mac_pdata, dsize))
return (EINVAL);
if (mip->mi_pdata != NULL)
kmem_free(mip->mi_pdata, mip->mi_pdata_size);
mip->mi_pdata = kmem_alloc(dsize, KM_SLEEP);
bcopy(mac_pdata, mip->mi_pdata, dsize);
mip->mi_pdata_size = dsize;
/*
* Since the MAC plugin data is used to construct MAC headers that
* were cached in fast-path headers, we need to flush fast-path
* information for links associated with this mac.
*/
i_mac_notify(mip, MAC_NOTE_FASTPATH_FLUSH);
return (0);
}
void
mac_multicst_refresh(mac_handle_t mh, mac_multicst_t refresh, void *arg,
boolean_t add)
{
mac_impl_t *mip = (mac_impl_t *)mh;
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 = mip->mi_multicst;
arg = mip->mi_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_handle_t mh, mac_unicst_t refresh, void *arg)
{
mac_impl_t *mip = (mac_impl_t *)mh;
/*
* If no specific refresh function was given then default to the
* driver's mi_unicst entry point.
*/
if (refresh == NULL) {
refresh = mip->mi_unicst;
arg = mip->mi_driver;
}
ASSERT(refresh != NULL);
/*
* Call the refresh function with the current unicast address.
*/
refresh(arg, mip->mi_addr);
}
void
mac_promisc_refresh(mac_handle_t mh, mac_setpromisc_t refresh, void *arg)
{
mac_impl_t *mip = (mac_impl_t *)mh;
/*
* If no specific refresh function was given then default to the
* driver's m_promisc entry point.
*/
if (refresh == NULL) {
refresh = mip->mi_setpromisc;
arg = mip->mi_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);
}
/*
* mac_info_get() is used for retrieving the mac_info when a DL_INFO_REQ is
* issued before a DL_ATTACH_REQ. we walk the i_mac_impl_hash table and find
* the first mac_impl_t with a matching driver name; then we copy its mac_info_t
* to the caller. we do all this with i_mac_impl_lock held so the mac_impl_t
* cannot disappear while we are accessing it.
*/
typedef struct i_mac_info_state_s {
const char *mi_name;
mac_info_t *mi_infop;
} i_mac_info_state_t;
/*ARGSUSED*/
static uint_t
i_mac_info_walker(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
{
i_mac_info_state_t *statep = arg;
mac_impl_t *mip = (mac_impl_t *)val;
if (mip->mi_disabled)
return (MH_WALK_CONTINUE);
if (strcmp(statep->mi_name,
ddi_driver_name(mip->mi_dip)) != 0)
return (MH_WALK_CONTINUE);
statep->mi_infop = &mip->mi_info;
return (MH_WALK_TERMINATE);
}
boolean_t
mac_info_get(const char *name, mac_info_t *minfop)
{
i_mac_info_state_t state;
rw_enter(&i_mac_impl_lock, RW_READER);
state.mi_name = name;
state.mi_infop = NULL;
mod_hash_walk(i_mac_impl_hash, i_mac_info_walker, &state);
if (state.mi_infop == NULL) {
rw_exit(&i_mac_impl_lock);
return (B_FALSE);
}
*minfop = *state.mi_infop;
rw_exit(&i_mac_impl_lock);
return (B_TRUE);
}
boolean_t
mac_capab_get(mac_handle_t mh, mac_capab_t cap, void *cap_data)
{
mac_impl_t *mip = (mac_impl_t *)mh;
if (mip->mi_callbacks->mc_callbacks & MC_GETCAPAB)
return (mip->mi_getcapab(mip->mi_driver, cap, cap_data));
else
return (B_FALSE);
}
boolean_t
mac_sap_verify(mac_handle_t mh, uint32_t sap, uint32_t *bind_sap)
{
mac_impl_t *mip = (mac_impl_t *)mh;
return (mip->mi_type->mt_ops.mtops_sap_verify(sap, bind_sap,
mip->mi_pdata));
}
mblk_t *
mac_header(mac_handle_t mh, const uint8_t *daddr, uint32_t sap, mblk_t *payload,
size_t extra_len)
{
mac_impl_t *mip = (mac_impl_t *)mh;
return (mip->mi_type->mt_ops.mtops_header(mip->mi_addr, daddr, sap,
mip->mi_pdata, payload, extra_len));
}
int
mac_header_info(mac_handle_t mh, mblk_t *mp, mac_header_info_t *mhip)
{
mac_impl_t *mip = (mac_impl_t *)mh;
return (mip->mi_type->mt_ops.mtops_header_info(mp, mip->mi_pdata,
mhip));
}
mblk_t *
mac_header_cook(mac_handle_t mh, mblk_t *mp)
{
mac_impl_t *mip = (mac_impl_t *)mh;
if (mip->mi_type->mt_ops.mtops_ops & MTOPS_HEADER_COOK) {
if (DB_REF(mp) > 1) {
mblk_t *newmp = copymsg(mp);
if (newmp == NULL)
return (NULL);
freemsg(mp);
mp = newmp;
}
return (mip->mi_type->mt_ops.mtops_header_cook(mp,
mip->mi_pdata));
}
return (mp);
}
mblk_t *
mac_header_uncook(mac_handle_t mh, mblk_t *mp)
{
mac_impl_t *mip = (mac_impl_t *)mh;
if (mip->mi_type->mt_ops.mtops_ops & MTOPS_HEADER_UNCOOK) {
if (DB_REF(mp) > 1) {
mblk_t *newmp = copymsg(mp);
if (newmp == NULL)
return (NULL);
freemsg(mp);
mp = newmp;
}
return (mip->mi_type->mt_ops.mtops_header_uncook(mp,
mip->mi_pdata));
}
return (mp);
}
void
mac_init_ops(struct dev_ops *ops, const char *name)
{
dld_init_ops(ops, name);
}
void
mac_fini_ops(struct dev_ops *ops)
{
dld_fini_ops(ops);
}
/*
* MAC Type Plugin functions.
*/
mactype_register_t *
mactype_alloc(uint_t mactype_version)
{
mactype_register_t *mtrp;
/*
* Make sure there isn't a version mismatch between the plugin and
* the framework. In the future, if multiple versions are
* supported, this check could become more sophisticated.
*/
if (mactype_version != MACTYPE_VERSION)
return (NULL);
mtrp = kmem_zalloc(sizeof (mactype_register_t), KM_SLEEP);
mtrp->mtr_version = mactype_version;
return (mtrp);
}
void
mactype_free(mactype_register_t *mtrp)
{
kmem_free(mtrp, sizeof (mactype_register_t));
}
int
mactype_register(mactype_register_t *mtrp)
{
mactype_t *mtp;
mactype_ops_t *ops = mtrp->mtr_ops;
/* Do some sanity checking before we register this MAC type. */
if (mtrp->mtr_ident == NULL || ops == NULL || mtrp->mtr_addrlen == 0)
return (EINVAL);
/*
* Verify that all mandatory callbacks are set in the ops
* vector.
*/
if (ops->mtops_unicst_verify == NULL ||
ops->mtops_multicst_verify == NULL ||
ops->mtops_sap_verify == NULL ||
ops->mtops_header == NULL ||
ops->mtops_header_info == NULL) {
return (EINVAL);
}
mtp = kmem_zalloc(sizeof (*mtp), KM_SLEEP);
mtp->mt_ident = mtrp->mtr_ident;
mtp->mt_ops = *ops;
mtp->mt_type = mtrp->mtr_mactype;
mtp->mt_nativetype = mtrp->mtr_nativetype;
mtp->mt_addr_length = mtrp->mtr_addrlen;
if (mtrp->mtr_brdcst_addr != NULL) {
mtp->mt_brdcst_addr = kmem_alloc(mtrp->mtr_addrlen, KM_SLEEP);
bcopy(mtrp->mtr_brdcst_addr, mtp->mt_brdcst_addr,
mtrp->mtr_addrlen);
}
mtp->mt_stats = mtrp->mtr_stats;
mtp->mt_statcount = mtrp->mtr_statcount;
if (mod_hash_insert(i_mactype_hash,
(mod_hash_key_t)mtp->mt_ident, (mod_hash_val_t)mtp) != 0) {
kmem_free(mtp->mt_brdcst_addr, mtp->mt_addr_length);
kmem_free(mtp, sizeof (*mtp));
return (EEXIST);
}
return (0);
}
int
mactype_unregister(const char *ident)
{
mactype_t *mtp;
mod_hash_val_t val;
int err;
/*
* Let's not allow MAC drivers to use this plugin while we're
* trying to unregister it. Holding i_mactype_lock also prevents a
* plugin from unregistering while a MAC driver is attempting to
* hold a reference to it in i_mactype_getplugin().
*/
mutex_enter(&i_mactype_lock);
if ((err = mod_hash_find(i_mactype_hash, (mod_hash_key_t)ident,
(mod_hash_val_t *)&mtp)) != 0) {
/* A plugin is trying to unregister, but it never registered. */
err = ENXIO;
goto done;
}
if (mtp->mt_ref != 0) {
err = EBUSY;
goto done;
}
err = mod_hash_remove(i_mactype_hash, (mod_hash_key_t)ident, &val);
ASSERT(err == 0);
if (err != 0) {
/* This should never happen, thus the ASSERT() above. */
err = EINVAL;
goto done;
}
ASSERT(mtp == (mactype_t *)val);
kmem_free(mtp->mt_brdcst_addr, mtp->mt_addr_length);
kmem_free(mtp, sizeof (mactype_t));
done:
mutex_exit(&i_mactype_lock);
return (err);
}
int
mac_vlan_create(mac_handle_t mh, const char *name, minor_t minor)
{
mac_impl_t *mip = (mac_impl_t *)mh;
/* Create a style-1 DLPI device */
if (ddi_create_minor_node(mip->mi_dip, (char *)name, S_IFCHR, minor,
DDI_NT_NET, 0) != DDI_SUCCESS) {
return (-1);
}
return (0);
}
void
mac_vlan_remove(mac_handle_t mh, const char *name)
{
mac_impl_t *mip = (mac_impl_t *)mh;
dev_info_t *dipp;
ddi_remove_minor_node(mip->mi_dip, (char *)name);
dipp = ddi_get_parent(mip->mi_dip);
(void) devfs_clean(dipp, NULL, 0);
}