#include <sys/types.h>
#include <sys/file.h>
#include <sys/cred.h>
#include <sys/dlpi.h>
#include <sys/mac_provider.h>
#include <sys/disp.h>
#include <sys/sunndi.h>
#include <sys/modhash.h>
#include <sys/stropts.h>
#include <sys/sysmacros.h>
#include <sys/vlan.h>
#include <sys/softmac_impl.h>
#include <sys/softmac.h>
#include <sys/dls.h>
typedef struct {
softmac_t *smw_softmac;
boolean_t smw_retry;
} softmac_walk_t;
static krwlock_t softmac_hash_lock;
static mod_hash_t *softmac_hash;
static kmutex_t smac_global_lock;
static kcondvar_t smac_global_cv;
static kmem_cache_t *softmac_cachep;
#define SOFTMAC_HASHSZ 64
static void softmac_create_task(void *);
static void softmac_mac_register(softmac_t *);
static int softmac_create_datalink(softmac_t *);
static int softmac_m_start(void *);
static void softmac_m_stop(void *);
static int softmac_m_open(void *);
static void softmac_m_close(void *);
static boolean_t softmac_m_getcapab(void *, mac_capab_t, void *);
static int softmac_m_setprop(void *, const char *, mac_prop_id_t,
uint_t, const void *);
static int softmac_m_getprop(void *, const char *, mac_prop_id_t,
uint_t, void *);
static void softmac_m_propinfo(void *, const char *, mac_prop_id_t,
mac_prop_info_handle_t);
#define SOFTMAC_M_CALLBACK_FLAGS \
(MC_IOCTL | MC_GETCAPAB | MC_OPEN | MC_CLOSE | MC_SETPROP | \
MC_GETPROP | MC_PROPINFO)
static mac_callbacks_t softmac_m_callbacks = {
SOFTMAC_M_CALLBACK_FLAGS,
softmac_m_stat,
softmac_m_start,
softmac_m_stop,
softmac_m_promisc,
softmac_m_multicst,
softmac_m_unicst,
softmac_m_tx,
NULL,
softmac_m_ioctl,
softmac_m_getcapab,
softmac_m_open,
softmac_m_close,
softmac_m_setprop,
softmac_m_getprop,
softmac_m_propinfo
};
static int
softmac_constructor(void *buf, void *arg, int kmflag)
{
softmac_t *softmac = buf;
bzero(buf, sizeof (softmac_t));
mutex_init(&softmac->smac_mutex, NULL, MUTEX_DEFAULT, NULL);
mutex_init(&softmac->smac_active_mutex, NULL, MUTEX_DEFAULT, NULL);
mutex_init(&softmac->smac_fp_mutex, NULL, MUTEX_DEFAULT, NULL);
cv_init(&softmac->smac_cv, NULL, CV_DEFAULT, NULL);
cv_init(&softmac->smac_fp_cv, NULL, CV_DEFAULT, NULL);
list_create(&softmac->smac_sup_list, sizeof (softmac_upper_t),
offsetof(softmac_upper_t, su_list_node));
return (0);
}
static void
softmac_destructor(void *buf, void *arg)
{
softmac_t *softmac = buf;
ASSERT(softmac->smac_fp_disable_clients == 0);
ASSERT(!softmac->smac_fastpath_admin_disabled);
ASSERT(!(softmac->smac_flags & SOFTMAC_ATTACH_DONE));
ASSERT(softmac->smac_hold_cnt == 0);
ASSERT(softmac->smac_attachok_cnt == 0);
ASSERT(softmac->smac_mh == NULL);
ASSERT(softmac->smac_softmac[0] == NULL &&
softmac->smac_softmac[1] == NULL);
ASSERT(softmac->smac_lower == NULL);
ASSERT(softmac->smac_active == B_FALSE);
ASSERT(softmac->smac_nactive == 0);
ASSERT(list_is_empty(&softmac->smac_sup_list));
list_destroy(&softmac->smac_sup_list);
mutex_destroy(&softmac->smac_mutex);
mutex_destroy(&softmac->smac_active_mutex);
mutex_destroy(&softmac->smac_fp_mutex);
cv_destroy(&softmac->smac_cv);
cv_destroy(&softmac->smac_fp_cv);
}
void
softmac_init()
{
softmac_hash = mod_hash_create_extended("softmac_hash",
SOFTMAC_HASHSZ, mod_hash_null_keydtor, mod_hash_null_valdtor,
mod_hash_bystr, NULL, mod_hash_strkey_cmp, KM_SLEEP);
rw_init(&softmac_hash_lock, NULL, RW_DEFAULT, NULL);
mutex_init(&smac_global_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&smac_global_cv, NULL, CV_DRIVER, NULL);
softmac_cachep = kmem_cache_create("softmac_cache",
sizeof (softmac_t), 0, softmac_constructor,
softmac_destructor, NULL, NULL, NULL, 0);
ASSERT(softmac_cachep != NULL);
softmac_fp_init();
}
void
softmac_fini()
{
softmac_fp_fini();
kmem_cache_destroy(softmac_cachep);
rw_destroy(&softmac_hash_lock);
mod_hash_destroy_hash(softmac_hash);
mutex_destroy(&smac_global_lock);
cv_destroy(&smac_global_cv);
}
static uint_t
softmac_exist(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
{
boolean_t *pexist = arg;
*pexist = B_TRUE;
return (MH_WALK_TERMINATE);
}
boolean_t
softmac_busy()
{
boolean_t exist = B_FALSE;
rw_enter(&softmac_hash_lock, RW_READER);
mod_hash_walk(softmac_hash, softmac_exist, &exist);
rw_exit(&softmac_hash_lock);
return (exist);
}
#ifdef DEBUG
void
softmac_state_verify(softmac_t *softmac)
{
ASSERT(MUTEX_HELD(&softmac->smac_mutex));
ASSERT(softmac->smac_cnt <= 2 && softmac->smac_attachok_cnt <= 2);
ASSERT(softmac->smac_attachok_cnt == SMAC_NONZERO_NODECNT(softmac));
ASSERT(softmac->smac_state != SOFTMAC_ATTACH_DONE ||
softmac->smac_attachok_cnt == softmac->smac_cnt);
if (softmac->smac_attachok_cnt == 0) {
ASSERT(softmac->smac_state == SOFTMAC_UNINIT);
ASSERT(softmac->smac_mh == NULL);
} else if (softmac->smac_attachok_cnt < softmac->smac_cnt) {
ASSERT(softmac->smac_state == SOFTMAC_ATTACH_INPROG ||
softmac->smac_state == SOFTMAC_DETACH_INPROG);
ASSERT(softmac->smac_mh == NULL);
} else {
ASSERT(softmac->smac_attachok_cnt == softmac->smac_cnt);
ASSERT(softmac->smac_state != SOFTMAC_UNINIT);
}
if (softmac->smac_mh != NULL)
ASSERT(softmac->smac_attachok_cnt == softmac->smac_cnt);
}
#endif
#ifdef DEBUG
#define SOFTMAC_STATE_VERIFY(softmac) softmac_state_verify(softmac)
#else
#define SOFTMAC_STATE_VERIFY(softmac)
#endif
int
softmac_create(dev_info_t *dip, dev_t dev)
{
char devname[MAXNAMELEN];
softmac_t *softmac;
softmac_dev_t *softmac_dev = NULL;
int index;
int ppa, err = 0;
if (i_ddi_attach_pseudo_node(SOFTMAC_DEV_NAME) == NULL) {
cmn_err(CE_WARN, "softmac_create:softmac attach fails");
return (ENXIO);
}
if (GLDV3_DRV(ddi_driver_major(dip))) {
minor_t minor = getminor(dev);
if ((strcmp(ddi_driver_name(dip), "clone") == 0) ||
(getmajor(dev) == ddi_name_to_major("clone")) ||
(minor == 0)) {
return (0);
}
if (minor >= DLS_MAX_MINOR) {
return (ENOTSUP);
}
ppa = DLS_MINOR2INST(minor);
} else {
ppa = ddi_get_instance(dip);
if (i_ddi_minor_node_count(dip, DDI_NT_NET) > 2) {
cmn_err(CE_WARN, "%s has more than 2 minor nodes; "
"unsupported", devname);
return (ENOTSUP);
}
}
(void) snprintf(devname, MAXNAMELEN, "%s%d", ddi_driver_name(dip), ppa);
rw_enter(&softmac_hash_lock, RW_WRITER);
if ((mod_hash_find(softmac_hash, (mod_hash_key_t)devname,
(mod_hash_val_t *)&softmac)) != 0) {
softmac = kmem_cache_alloc(softmac_cachep, KM_SLEEP);
(void) strlcpy(softmac->smac_devname, devname, MAXNAMELEN);
err = mod_hash_insert(softmac_hash,
(mod_hash_key_t)softmac->smac_devname,
(mod_hash_val_t)softmac);
ASSERT(err == 0);
mutex_enter(&smac_global_lock);
cv_broadcast(&smac_global_cv);
mutex_exit(&smac_global_lock);
}
mutex_enter(&softmac->smac_mutex);
SOFTMAC_STATE_VERIFY(softmac);
if (softmac->smac_state != SOFTMAC_ATTACH_DONE)
softmac->smac_state = SOFTMAC_ATTACH_INPROG;
if (softmac->smac_attachok_cnt == 0) {
softmac->smac_flags = 0;
softmac->smac_umajor = ddi_driver_major(dip);
softmac->smac_uppa = ppa;
if (GLDV3_DRV(ddi_driver_major(dip))) {
softmac->smac_flags |= SOFTMAC_GLDV3;
softmac->smac_cnt = 1;
} else {
softmac->smac_cnt =
i_ddi_minor_node_count(dip, DDI_NT_NET);
}
}
index = (getmajor(dev) == ddi_name_to_major("clone"));
if (softmac->smac_softmac[index] != NULL) {
ASSERT(softmac->smac_attached_left != 0);
softmac->smac_attached_left--;
mutex_exit(&softmac->smac_mutex);
rw_exit(&softmac_hash_lock);
return (0);
}
mutex_exit(&softmac->smac_mutex);
rw_exit(&softmac_hash_lock);
softmac_dev = kmem_zalloc(sizeof (softmac_dev_t), KM_SLEEP);
softmac_dev->sd_dev = dev;
mutex_enter(&softmac->smac_mutex);
softmac->smac_softmac[index] = softmac_dev;
if (++softmac->smac_attachok_cnt != softmac->smac_cnt) {
mutex_exit(&softmac->smac_mutex);
return (0);
}
(void) taskq_dispatch(system_taskq, softmac_create_task,
softmac, TQ_SLEEP);
mutex_exit(&softmac->smac_mutex);
return (0);
}
static boolean_t
softmac_m_getcapab(void *arg, mac_capab_t cap, void *cap_data)
{
softmac_t *softmac = arg;
if (!(softmac->smac_capab_flags & cap))
return (B_FALSE);
switch (cap) {
case MAC_CAPAB_HCKSUM: {
uint32_t *txflags = cap_data;
*txflags = softmac->smac_hcksum_txflags;
break;
}
case MAC_CAPAB_LEGACY: {
mac_capab_legacy_t *legacy = cap_data;
if (legacy == NULL)
break;
legacy->ml_unsup_note = ~softmac->smac_notifications &
(DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN | DL_NOTE_SPEED);
legacy->ml_active_set = softmac_active_set;
legacy->ml_active_clear = softmac_active_clear;
legacy->ml_fastpath_disable = softmac_fastpath_disable;
legacy->ml_fastpath_enable = softmac_fastpath_enable;
legacy->ml_dev = makedevice(softmac->smac_umajor,
softmac->smac_uppa + 1);
break;
}
case MAC_CAPAB_NO_ZCOPY:
case MAC_CAPAB_NO_NATIVEVLAN:
default:
break;
}
return (B_TRUE);
}
static int
softmac_update_info(softmac_t *softmac, datalink_id_t *linkidp)
{
datalink_id_t linkid = DATALINK_INVALID_LINKID;
uint32_t media;
int err;
if ((err = dls_mgmt_update(softmac->smac_devname, softmac->smac_media,
softmac->smac_flags & SOFTMAC_NOSUPP, &media, &linkid)) == 0) {
*linkidp = linkid;
}
if (err == EEXIST) {
if (media != softmac->smac_media) {
cmn_err(CE_WARN, "%s device %s conflicts with "
"existing %s device %s.",
dl_mactypestr(softmac->smac_media),
softmac->smac_devname, dl_mactypestr(media),
softmac->smac_devname);
(void) dls_mgmt_destroy(linkid, B_FALSE);
} else {
cmn_err(CE_WARN, "link name %s is already in-use.",
softmac->smac_devname);
(void) dls_mgmt_destroy(linkid, B_TRUE);
}
cmn_err(CE_WARN, "%s device might not be available "
"for use.", softmac->smac_devname);
cmn_err(CE_WARN, "See dladm(8) for more information.");
}
return (err);
}
static int
softmac_create_datalink(softmac_t *softmac)
{
datalink_id_t linkid = DATALINK_INVALID_LINKID;
int err;
err = dls_mgmt_create(softmac->smac_devname,
makedevice(softmac->smac_umajor, softmac->smac_uppa + 1),
DATALINK_CLASS_PHYS, DL_OTHER, B_TRUE, &linkid);
if (err != 0 && err != EBADF)
return (err);
if ((err != EBADF) &&
((err = softmac_update_info(softmac, &linkid)) != 0)) {
return (err);
}
if (!(softmac->smac_flags & SOFTMAC_NOSUPP)) {
err = dls_devnet_create(softmac->smac_mh, linkid,
crgetzoneid(CRED()));
if (err != 0) {
cmn_err(CE_WARN, "dls_devnet_create failed for %s",
softmac->smac_devname);
return (err);
}
}
if (linkid == DATALINK_INVALID_LINKID) {
mutex_enter(&softmac->smac_mutex);
softmac->smac_flags |= SOFTMAC_NEED_RECREATE;
mutex_exit(&softmac->smac_mutex);
}
return (0);
}
static void
softmac_create_task(void *arg)
{
softmac_t *softmac = arg;
mac_handle_t mh;
int err;
if (!GLDV3_DRV(softmac->smac_umajor)) {
softmac_mac_register(softmac);
return;
}
if ((err = mac_open(softmac->smac_devname, &mh)) != 0)
goto done;
mutex_enter(&softmac->smac_mutex);
softmac->smac_media = (mac_info(mh))->mi_nativemedia;
softmac->smac_mh = mh;
mutex_exit(&softmac->smac_mutex);
mac_close(mh);
err = softmac_create_datalink(softmac);
done:
mutex_enter(&softmac->smac_mutex);
if (err != 0)
softmac->smac_mh = NULL;
softmac->smac_attacherr = err;
softmac->smac_state = SOFTMAC_ATTACH_DONE;
cv_broadcast(&softmac->smac_cv);
mutex_exit(&softmac->smac_mutex);
}
static void
softmac_mac_register(softmac_t *softmac)
{
softmac_dev_t *softmac_dev;
dev_t dev;
ldi_handle_t lh = NULL;
ldi_ident_t li = NULL;
int index;
boolean_t native_vlan = B_FALSE;
int err;
ASSERT(softmac != NULL);
ASSERT(softmac->smac_state == SOFTMAC_ATTACH_INPROG &&
softmac->smac_attachok_cnt == softmac->smac_cnt);
if ((err = ldi_ident_from_dip(softmac_dip, &li)) != 0) {
mutex_enter(&softmac->smac_mutex);
goto done;
}
dev = makedevice(ddi_name_to_major("clone"), softmac->smac_umajor);
err = ldi_open_by_dev(&dev, OTYP_CHR, FREAD|FWRITE, kcred, &lh, li);
if (err == 0) {
if (dl_attach(lh, softmac->smac_uppa + 1 * 1000, NULL) == 0)
native_vlan = B_TRUE;
(void) ldi_close(lh, FREAD|FWRITE, kcred);
}
err = EINVAL;
for (index = 0; index < 2; index++) {
dl_info_ack_t dlia;
dl_error_ack_t dlea;
uint32_t notes;
struct strioctl iocb;
uint32_t margin;
int rval;
if ((softmac_dev = softmac->smac_softmac[index]) == NULL)
continue;
softmac->smac_dev = dev = softmac_dev->sd_dev;
if (ldi_open_by_dev(&dev, OTYP_CHR, FREAD|FWRITE, kcred, &lh,
li) != 0) {
continue;
}
while (ldi_ioctl(lh, I_POP, 0, FKIOCTL, kcred, &rval) == 0)
;
if ((rval = dl_info(lh, &dlia, NULL, NULL, &dlea)) != 0) {
if (rval == ENOTSUP) {
cmn_err(CE_NOTE, "softmac: received "
"DL_ERROR_ACK to DL_INFO_ACK; "
"DLPI errno 0x%x, UNIX errno %d",
dlea.dl_errno, dlea.dl_unix_errno);
}
(void) ldi_close(lh, FREAD|FWRITE, kcred);
continue;
}
if ((softmac->smac_media = dlia.dl_mac_type) != DL_ETHER) {
(void) ldi_close(lh, FREAD|FWRITE, kcred);
err = 0;
break;
}
if ((dlia.dl_provider_style == DL_STYLE2) &&
(dl_attach(lh, softmac->smac_uppa, NULL) != 0)) {
(void) ldi_close(lh, FREAD|FWRITE, kcred);
continue;
}
if ((rval = dl_bind(lh, 0, NULL)) != 0) {
if (rval == ENOTSUP) {
cmn_err(CE_NOTE, "softmac: received "
"DL_ERROR_ACK to DL_BIND_ACK; "
"DLPI errno 0x%x, UNIX errno %d",
dlea.dl_errno, dlea.dl_unix_errno);
}
(void) ldi_close(lh, FREAD|FWRITE, kcred);
continue;
}
softmac->smac_addrlen = sizeof (softmac->smac_unicst_addr);
if ((rval = dl_info(lh, &dlia, softmac->smac_unicst_addr,
&softmac->smac_addrlen, &dlea)) != 0) {
if (rval == ENOTSUP) {
cmn_err(CE_NOTE, "softmac: received "
"DL_ERROR_ACK to DL_INFO_ACK; "
"DLPI errno 0x%x, UNIX errno %d",
dlea.dl_errno, dlea.dl_unix_errno);
}
(void) ldi_close(lh, FREAD|FWRITE, kcred);
continue;
}
softmac->smac_style = dlia.dl_provider_style;
softmac->smac_saplen = ABS(dlia.dl_sap_length);
softmac->smac_min_sdu = dlia.dl_min_sdu;
softmac->smac_max_sdu = dlia.dl_max_sdu;
if ((softmac->smac_saplen != sizeof (uint16_t)) ||
(softmac->smac_addrlen != ETHERADDRL) ||
(dlia.dl_brdcst_addr_length != ETHERADDRL) ||
(dlia.dl_brdcst_addr_offset == 0)) {
(void) ldi_close(lh, FREAD|FWRITE, kcred);
continue;
}
softmac->smac_capab_flags =
(MAC_CAPAB_NO_ZCOPY | MAC_CAPAB_LEGACY);
softmac->smac_no_capability_req = B_FALSE;
if (softmac_fill_capab(lh, softmac) != 0)
softmac->smac_no_capability_req = B_TRUE;
margin = 0;
iocb.ic_cmd = DLIOCMARGININFO;
iocb.ic_timout = INFTIM;
iocb.ic_len = sizeof (margin);
iocb.ic_dp = (char *)&margin;
softmac->smac_margin = 0;
if (ldi_ioctl(lh, I_STR, (intptr_t)&iocb, FKIOCTL, kcred,
&rval) == 0) {
softmac->smac_margin = margin;
}
if (native_vlan) {
if (softmac->smac_margin == 0)
softmac->smac_margin = VLAN_TAGSZ;
} else {
softmac->smac_capab_flags |= MAC_CAPAB_NO_NATIVEVLAN;
}
softmac->smac_notifications = 0;
notes = DL_NOTE_PHYS_ADDR | DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN;
switch (dl_notify(lh, ¬es, NULL)) {
case 0:
softmac->smac_notifications = notes;
break;
case ENOTSUP:
break;
default:
(void) ldi_close(lh, FREAD|FWRITE, kcred);
continue;
}
(void) ldi_close(lh, FREAD|FWRITE, kcred);
err = 0;
break;
}
ldi_ident_release(li);
mutex_enter(&softmac->smac_mutex);
if (err != 0)
goto done;
if (softmac->smac_media != DL_ETHER)
softmac->smac_flags |= SOFTMAC_NOSUPP;
if (!(softmac->smac_flags & SOFTMAC_NOSUPP)) {
mac_register_t *macp;
if ((macp = mac_alloc(MAC_VERSION)) == NULL) {
err = ENOMEM;
goto done;
}
macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
macp->m_driver = softmac;
macp->m_dip = softmac_dip;
macp->m_margin = softmac->smac_margin;
macp->m_src_addr = softmac->smac_unicst_addr;
macp->m_min_sdu = softmac->smac_min_sdu;
macp->m_max_sdu = softmac->smac_max_sdu;
macp->m_callbacks = &softmac_m_callbacks;
macp->m_instance = (uint_t)-1;
err = mac_register(macp, &softmac->smac_mh);
mac_free(macp);
if (err != 0) {
cmn_err(CE_WARN, "mac_register failed for %s",
softmac->smac_devname);
goto done;
}
}
mutex_exit(&softmac->smac_mutex);
if ((err = softmac_create_datalink(softmac)) != 0) {
if (!(softmac->smac_flags & SOFTMAC_NOSUPP))
(void) mac_unregister(softmac->smac_mh);
mutex_enter(&softmac->smac_mutex);
softmac->smac_mh = NULL;
goto done;
}
mutex_enter(&softmac->smac_mutex);
if (softmac->smac_mh != NULL) {
softmac->smac_notify_thread = thread_create(NULL, 0,
softmac_notify_thread, softmac, 0, &p0,
TS_RUN, minclsyspri);
}
done:
ASSERT(softmac->smac_state == SOFTMAC_ATTACH_INPROG &&
softmac->smac_attachok_cnt == softmac->smac_cnt);
softmac->smac_state = SOFTMAC_ATTACH_DONE;
softmac->smac_attacherr = err;
cv_broadcast(&softmac->smac_cv);
mutex_exit(&softmac->smac_mutex);
}
int
softmac_destroy(dev_info_t *dip, dev_t dev)
{
char devname[MAXNAMELEN];
softmac_t *softmac;
softmac_dev_t *softmac_dev;
int index;
int ppa, err;
datalink_id_t linkid;
mac_handle_t smac_mh;
uint32_t smac_flags;
if (GLDV3_DRV(ddi_driver_major(dip))) {
minor_t minor = getminor(dev);
if ((strcmp(ddi_driver_name(dip), "clone") == 0) ||
(getmajor(dev) == ddi_name_to_major("clone")) ||
(minor == 0)) {
return (0);
}
if (minor >= DLS_MAX_MINOR) {
return (ENOTSUP);
}
ppa = DLS_MINOR2INST(minor);
} else {
ppa = ddi_get_instance(dip);
}
(void) snprintf(devname, MAXNAMELEN, "%s%d", ddi_driver_name(dip), ppa);
err = mod_hash_find(softmac_hash, (mod_hash_key_t)devname,
(mod_hash_val_t *)&softmac);
ASSERT(err == 0);
mutex_enter(&softmac->smac_mutex);
SOFTMAC_STATE_VERIFY(softmac);
if ((softmac->smac_hold_cnt != 0) ||
(softmac->smac_state == SOFTMAC_ATTACH_INPROG)) {
softmac->smac_attached_left = softmac->smac_attachok_cnt;
mutex_exit(&softmac->smac_mutex);
return (EBUSY);
}
if (softmac->smac_attached_left != 0) {
mutex_exit(&softmac->smac_mutex);
return (EBUSY);
}
smac_mh = softmac->smac_mh;
smac_flags = softmac->smac_flags;
softmac->smac_state = SOFTMAC_DETACH_INPROG;
mutex_exit(&softmac->smac_mutex);
if (smac_mh != NULL) {
ASSERT(softmac->smac_attachok_cnt == softmac->smac_cnt);
if (!(smac_flags & SOFTMAC_NOSUPP)) {
if ((err = dls_devnet_destroy(smac_mh, &linkid,
B_FALSE)) != 0) {
goto error;
}
}
if (!(smac_flags & (SOFTMAC_GLDV3 | SOFTMAC_NOSUPP))) {
if ((err = mac_disable_nowait(smac_mh)) != 0) {
(void) dls_devnet_create(smac_mh, linkid,
crgetzoneid(CRED()));
goto error;
}
mutex_enter(&softmac->smac_mutex);
softmac->smac_flags |= SOFTMAC_NOTIFY_QUIT;
cv_broadcast(&softmac->smac_cv);
while (
(softmac->smac_flags & SOFTMAC_NOTIFY_DONE) == 0) {
cv_wait(&softmac->smac_cv,
&softmac->smac_mutex);
}
thread_join(softmac->smac_notify_thread->t_did);
softmac->smac_notify_thread = NULL;
mutex_exit(&softmac->smac_mutex);
VERIFY(mac_unregister(smac_mh) == 0);
}
softmac->smac_mh = NULL;
}
rw_enter(&softmac_hash_lock, RW_WRITER);
mutex_enter(&softmac->smac_mutex);
ASSERT(softmac->smac_state == SOFTMAC_DETACH_INPROG &&
softmac->smac_attachok_cnt != 0);
softmac->smac_mh = NULL;
index = (getmajor(dev) == ddi_name_to_major("clone"));
softmac_dev = softmac->smac_softmac[index];
ASSERT(softmac_dev != NULL);
softmac->smac_softmac[index] = NULL;
kmem_free(softmac_dev, sizeof (softmac_dev_t));
if (--softmac->smac_attachok_cnt == 0) {
mod_hash_val_t hashval;
softmac->smac_state = SOFTMAC_UNINIT;
if (softmac->smac_hold_cnt != 0) {
mutex_exit(&softmac->smac_mutex);
rw_exit(&softmac_hash_lock);
return (0);
}
err = mod_hash_remove(softmac_hash,
(mod_hash_key_t)devname,
(mod_hash_val_t *)&hashval);
ASSERT(err == 0);
mutex_exit(&softmac->smac_mutex);
rw_exit(&softmac_hash_lock);
ASSERT(softmac->smac_fp_disable_clients == 0);
softmac->smac_fastpath_admin_disabled = B_FALSE;
kmem_cache_free(softmac_cachep, softmac);
return (0);
}
mutex_exit(&softmac->smac_mutex);
rw_exit(&softmac_hash_lock);
return (0);
error:
mutex_enter(&softmac->smac_mutex);
softmac->smac_attached_left = softmac->smac_attachok_cnt;
softmac->smac_state = SOFTMAC_ATTACH_DONE;
cv_broadcast(&softmac->smac_cv);
mutex_exit(&softmac->smac_mutex);
return (err);
}
static uint_t
softmac_mac_recreate(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
{
softmac_t *softmac = (softmac_t *)val;
datalink_id_t linkid;
int err;
softmac_walk_t *smwp = arg;
ASSERT(RW_READ_HELD(&softmac_hash_lock));
smwp->smw_retry = B_FALSE;
mutex_enter(&softmac->smac_mutex);
SOFTMAC_STATE_VERIFY(softmac);
if (softmac->smac_state == SOFTMAC_ATTACH_INPROG) {
smwp->smw_retry = B_TRUE;
smwp->smw_softmac = softmac;
softmac->smac_hold_cnt++;
return (MH_WALK_TERMINATE);
}
if ((softmac->smac_state != SOFTMAC_ATTACH_DONE) ||
!(softmac->smac_flags & SOFTMAC_NEED_RECREATE)) {
mutex_exit(&softmac->smac_mutex);
return (MH_WALK_CONTINUE);
}
softmac->smac_hold_cnt++;
mutex_exit(&softmac->smac_mutex);
if (dls_mgmt_create(softmac->smac_devname,
makedevice(softmac->smac_umajor, softmac->smac_uppa + 1),
DATALINK_CLASS_PHYS, softmac->smac_media, B_TRUE, &linkid) != 0) {
softmac_rele_device((dls_dev_handle_t)softmac);
return (MH_WALK_CONTINUE);
}
if ((err = softmac_update_info(softmac, &linkid)) != 0) {
cmn_err(CE_WARN, "softmac: softmac_update_info() for %s "
"failed (%d)", softmac->smac_devname, err);
softmac_rele_device((dls_dev_handle_t)softmac);
return (MH_WALK_CONTINUE);
}
if (!(softmac->smac_flags & SOFTMAC_NOSUPP)) {
err = dls_devnet_recreate(softmac->smac_mh, linkid);
if (err != 0) {
cmn_err(CE_WARN, "softmac: dls_devnet_recreate() for "
"%s (linkid %d) failed (%d)",
softmac->smac_devname, linkid, err);
}
}
mutex_enter(&softmac->smac_mutex);
softmac->smac_flags &= ~SOFTMAC_NEED_RECREATE;
ASSERT(softmac->smac_hold_cnt != 0);
softmac->smac_hold_cnt--;
mutex_exit(&softmac->smac_mutex);
return (MH_WALK_CONTINUE);
}
void
softmac_recreate()
{
softmac_walk_t smw;
softmac_t *softmac;
do {
smw.smw_retry = B_FALSE;
rw_enter(&softmac_hash_lock, RW_READER);
mod_hash_walk(softmac_hash, softmac_mac_recreate, &smw);
rw_exit(&softmac_hash_lock);
if (smw.smw_retry) {
softmac = smw.smw_softmac;
cv_wait(&softmac->smac_cv, &softmac->smac_mutex);
softmac->smac_hold_cnt--;
mutex_exit(&softmac->smac_mutex);
}
} while (smw.smw_retry);
}
static int
softmac_m_start(void *arg)
{
softmac_t *softmac = arg;
softmac_lower_t *slp = softmac->smac_lower;
int err;
ASSERT(MAC_PERIM_HELD(softmac->smac_mh));
err = softmac_send_bind_req(slp, softmac->smac_media == DL_TPR ? 2 : 0);
if (err != 0)
return (err);
err = softmac_send_promisc_req(slp, DL_PROMISC_SAP, B_TRUE);
if (err != 0) {
(void) softmac_send_unbind_req(slp);
return (err);
}
if ((err = softmac_capab_enable(slp)) != 0) {
(void) softmac_send_promisc_req(slp, DL_PROMISC_SAP, B_FALSE);
(void) softmac_send_unbind_req(slp);
}
return (err);
}
static void
softmac_m_stop(void *arg)
{
softmac_t *softmac = arg;
softmac_lower_t *slp = softmac->smac_lower;
ASSERT(MAC_PERIM_HELD(softmac->smac_mh));
(void) softmac_send_promisc_req(slp, DL_PROMISC_SAP, B_FALSE);
(void) softmac_send_unbind_req(slp);
}
int
softmac_lower_setup(softmac_t *softmac, softmac_upper_t *sup,
softmac_lower_t **slpp)
{
ldi_ident_t li;
dev_t dev;
ldi_handle_t lh = NULL;
softmac_lower_t *slp = NULL;
smac_ioc_start_t start_arg;
struct strioctl strioc;
uint32_t notifications;
int err, rval;
if ((err = ldi_ident_from_dip(softmac_dip, &li)) != 0)
return (err);
dev = softmac->smac_dev;
err = ldi_open_by_dev(&dev, OTYP_CHR, FREAD|FWRITE, kcred, &lh, li);
ldi_ident_release(li);
if (err != 0)
goto done;
while (ldi_ioctl(lh, I_POP, 0, FKIOCTL, kcred, &rval) == 0)
;
if ((softmac->smac_style == DL_STYLE2) &&
((err = dl_attach(lh, softmac->smac_uppa, NULL)) != 0)) {
goto done;
}
if ((sup == NULL) && (err = ldi_ioctl(lh, DLIOCRAW, 0, FKIOCTL,
kcred, &rval)) != 0) {
goto done;
}
if ((err = ldi_ioctl(lh, I_PUSH, (intptr_t)SOFTMAC_DEV_NAME, FKIOCTL,
kcred, &rval)) != 0) {
goto done;
}
strioc.ic_cmd = SMAC_IOC_START;
strioc.ic_timout = INFTIM;
strioc.ic_len = sizeof (start_arg);
strioc.ic_dp = (char *)&start_arg;
if ((err = ldi_ioctl(lh, I_STR, (intptr_t)&strioc, FKIOCTL,
kcred, &rval)) != 0) {
goto done;
}
slp = start_arg.si_slp;
slp->sl_sup = sup;
slp->sl_lh = lh;
slp->sl_softmac = softmac;
*slpp = slp;
if (sup != NULL) {
slp->sl_rxinfo = &sup->su_rxinfo;
} else {
notifications = DL_NOTE_PHYS_ADDR | DL_NOTE_LINK_UP |
DL_NOTE_LINK_DOWN | DL_NOTE_PROMISC_ON_PHYS |
DL_NOTE_PROMISC_OFF_PHYS;
(void) softmac_send_notify_req(slp,
(notifications & softmac->smac_notifications));
}
done:
if (err != 0)
(void) ldi_close(lh, FREAD|FWRITE, kcred);
return (err);
}
static int
softmac_m_open(void *arg)
{
softmac_t *softmac = arg;
softmac_lower_t *slp;
int err;
ASSERT(MAC_PERIM_HELD(softmac->smac_mh));
if ((err = softmac_lower_setup(softmac, NULL, &slp)) != 0)
return (err);
softmac->smac_lower = slp;
return (0);
}
static void
softmac_m_close(void *arg)
{
softmac_t *softmac = arg;
softmac_lower_t *slp;
ASSERT(MAC_PERIM_HELD(softmac->smac_mh));
slp = softmac->smac_lower;
ASSERT(slp != NULL);
(void) ldi_close(slp->sl_lh, FREAD|FWRITE, kcred);
softmac->smac_lower = NULL;
}
static int
softmac_m_setprop(void *arg, const char *name, mac_prop_id_t id,
uint_t valsize, const void *val)
{
softmac_t *softmac = arg;
if (id != MAC_PROP_PRIVATE || strcmp(name, "_disable_fastpath") != 0)
return (ENOTSUP);
if (strcmp(val, "true") == 0)
return (softmac_datapath_switch(softmac, B_TRUE, B_TRUE));
else if (strcmp(val, "false") == 0)
return (softmac_datapath_switch(softmac, B_FALSE, B_TRUE));
else
return (EINVAL);
}
static int
softmac_m_getprop(void *arg, const char *name, mac_prop_id_t id,
uint_t valsize, void *val)
{
softmac_t *softmac = arg;
char *fpstr;
if (id != MAC_PROP_PRIVATE)
return (ENOTSUP);
if (strcmp(name, "_fastpath") == 0) {
mutex_enter(&softmac->smac_fp_mutex);
fpstr = (DATAPATH_MODE(softmac) == SOFTMAC_SLOWPATH) ?
"disabled" : "enabled";
mutex_exit(&softmac->smac_fp_mutex);
} else if (strcmp(name, "_disable_fastpath") == 0) {
fpstr = softmac->smac_fastpath_admin_disabled ?
"true" : "false";
} else if (strcmp(name, "_softmac") == 0) {
fpstr = "true";
} else {
return (ENOTSUP);
}
return (strlcpy(val, fpstr, valsize) >= valsize ? EINVAL : 0);
}
static void
softmac_m_propinfo(void *arg, const char *name, mac_prop_id_t id,
mac_prop_info_handle_t prh)
{
_NOTE(ARGUNUSED(arg));
if (id != MAC_PROP_PRIVATE)
return;
if (strcmp(name, "_fastpath") == 0) {
mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
} else if (strcmp(name, "_disable_fastpath") == 0) {
mac_prop_info_set_default_str(prh, "false");
}
}
int
softmac_hold_device(dev_t dev, dls_dev_handle_t *ddhp)
{
dev_info_t *dip;
char devname[MAXNAMELEN];
softmac_t *softmac;
major_t major;
int ppa, err = 0, inst;
major = getmajor(dev);
ppa = getminor(dev) - 1;
if (ddi_hold_driver(major) == NULL)
return (ENXIO);
if (GLDV3_DRV(major)) {
if ((inst = dev_to_instance(dev)) < 0)
err = ENOENT;
} else {
inst = ppa;
}
ddi_rele_driver(major);
if (err != 0)
return (err);
if ((dip = ddi_hold_devi_by_instance(major, inst, 0)) == NULL)
return (ENOENT);
if (!NETWORK_PHYSDRV(major)) {
ddi_release_devi(dip);
return (ENOENT);
}
(void) snprintf(devname, MAXNAMELEN, "%s%d", ddi_major_to_name(major),
ppa);
again:
rw_enter(&softmac_hash_lock, RW_READER);
if (mod_hash_find(softmac_hash, (mod_hash_key_t)devname,
(mod_hash_val_t *)&softmac) != 0) {
mutex_enter(&smac_global_lock);
rw_exit(&softmac_hash_lock);
cv_wait(&smac_global_cv, &smac_global_lock);
mutex_exit(&smac_global_lock);
goto again;
}
mutex_enter(&softmac->smac_mutex);
softmac->smac_hold_cnt++;
rw_exit(&softmac_hash_lock);
while (softmac->smac_state != SOFTMAC_ATTACH_DONE)
cv_wait(&softmac->smac_cv, &softmac->smac_mutex);
SOFTMAC_STATE_VERIFY(softmac);
if ((err = softmac->smac_attacherr) != 0)
softmac->smac_hold_cnt--;
else
*ddhp = (dls_dev_handle_t)softmac;
mutex_exit(&softmac->smac_mutex);
ddi_release_devi(dip);
return (err);
}
void
softmac_rele_device(dls_dev_handle_t ddh)
{
if (ddh != NULL)
softmac_rele((softmac_t *)ddh);
}
int
softmac_hold(dev_t dev, softmac_t **softmacp)
{
softmac_t *softmac;
char *drv;
mac_handle_t mh;
char mac[MAXNAMELEN];
int err;
if ((drv = ddi_major_to_name(getmajor(dev))) == NULL)
return (EINVAL);
(void) snprintf(mac, MAXNAMELEN, "%s%d", drv, getminor(dev) - 1);
if ((err = mac_open(mac, &mh)) != 0)
return (err);
softmac = (softmac_t *)mac_driver(mh);
mutex_enter(&softmac->smac_mutex);
softmac->smac_hold_cnt++;
mutex_exit(&softmac->smac_mutex);
mac_close(mh);
*softmacp = softmac;
return (0);
}
void
softmac_rele(softmac_t *softmac)
{
mutex_enter(&softmac->smac_mutex);
softmac->smac_hold_cnt--;
mutex_exit(&softmac->smac_mutex);
}