#include <sys/types.h>
#include <sys/disp.h>
#include <sys/callb.h>
#include <sys/sysmacros.h>
#include <sys/file.h>
#include <sys/vlan.h>
#include <sys/dld.h>
#include <sys/sockio.h>
#include <sys/softmac_impl.h>
#include <net/if.h>
static kmutex_t softmac_taskq_lock;
static kthread_t *softmac_taskq_thread;
static kcondvar_t softmac_taskq_cv;
static list_t softmac_taskq_list;
boolean_t softmac_taskq_quit;
boolean_t softmac_taskq_done;
static void softmac_taskq_dispatch();
static int softmac_fastpath_setup(softmac_upper_t *);
static mac_tx_cookie_t softmac_fastpath_wput_data(softmac_upper_t *, mblk_t *,
uintptr_t, uint16_t);
static void softmac_datapath_switch_done(softmac_upper_t *);
void
softmac_fp_init()
{
mutex_init(&softmac_taskq_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&softmac_taskq_cv, NULL, CV_DRIVER, NULL);
softmac_taskq_quit = B_FALSE;
softmac_taskq_done = B_FALSE;
list_create(&softmac_taskq_list, sizeof (softmac_upper_t),
offsetof(softmac_upper_t, su_taskq_list_node));
softmac_taskq_thread = thread_create(NULL, 0, softmac_taskq_dispatch,
NULL, 0, &p0, TS_RUN, minclsyspri);
}
void
softmac_fp_fini()
{
mutex_enter(&softmac_taskq_lock);
softmac_taskq_quit = B_TRUE;
cv_signal(&softmac_taskq_cv);
while (!softmac_taskq_done)
cv_wait(&softmac_taskq_cv, &softmac_taskq_lock);
mutex_exit(&softmac_taskq_lock);
thread_join(softmac_taskq_thread->t_did);
list_destroy(&softmac_taskq_list);
mutex_destroy(&softmac_taskq_lock);
cv_destroy(&softmac_taskq_cv);
}
static boolean_t
check_ip_above(queue_t *q)
{
queue_t *next_q;
boolean_t ret = B_TRUE;
claimstr(q);
next_q = q->q_next;
if (strcmp(next_q->q_qinfo->qi_minfo->mi_idname, "ip") != 0)
ret = B_FALSE;
releasestr(q);
return (ret);
}
static int
softmac_capab_perim(softmac_upper_t *sup, void *data, uint_t flags)
{
switch (flags) {
case DLD_ENABLE:
mutex_enter(&sup->su_mutex);
break;
case DLD_DISABLE:
mutex_exit(&sup->su_mutex);
break;
case DLD_QUERY:
return (MUTEX_HELD(&sup->su_mutex));
}
return (0);
}
static mac_tx_notify_handle_t
softmac_client_tx_notify(softmac_upper_t *sup, mac_tx_notify_t func, void *arg)
{
ASSERT(MUTEX_HELD(&sup->su_mutex));
if (func != NULL) {
sup->su_tx_notify_func = func;
sup->su_tx_notify_arg = arg;
} else {
while (sup->su_tx_inprocess != 0)
cv_wait(&sup->su_cv, &sup->su_mutex);
sup->su_tx_notify_func = NULL;
sup->su_tx_notify_arg = NULL;
}
return ((mac_tx_notify_handle_t)sup);
}
static boolean_t
softmac_tx_is_flow_blocked(softmac_upper_t *sup, mac_tx_cookie_t cookie)
{
ASSERT(cookie == (mac_tx_cookie_t)sup);
return (sup->su_tx_busy);
}
static int
softmac_capab_direct(softmac_upper_t *sup, void *data, uint_t flags)
{
dld_capab_direct_t *direct = data;
softmac_lower_t *slp = sup->su_slp;
ASSERT(MUTEX_HELD(&sup->su_mutex));
ASSERT(sup->su_mode == SOFTMAC_FASTPATH);
switch (flags) {
case DLD_ENABLE:
if (sup->su_direct)
return (0);
sup->su_direct_rxinfo.slr_rx = (softmac_rx_t)direct->di_rx_cf;
sup->su_direct_rxinfo.slr_arg = direct->di_rx_ch;
slp->sl_rxinfo = &sup->su_direct_rxinfo;
direct->di_tx_df = (uintptr_t)softmac_fastpath_wput_data;
direct->di_tx_dh = sup;
direct->di_tx_fctl_df = (uintptr_t)softmac_tx_is_flow_blocked;
direct->di_tx_fctl_dh = sup;
direct->di_tx_cb_df = (uintptr_t)softmac_client_tx_notify;
direct->di_tx_cb_dh = sup;
sup->su_direct = B_TRUE;
return (0);
case DLD_DISABLE:
if (!sup->su_direct)
return (0);
slp->sl_rxinfo = &sup->su_rxinfo;
sup->su_direct = B_FALSE;
return (0);
}
return (ENOTSUP);
}
static int
softmac_dld_capab(softmac_upper_t *sup, uint_t type, void *data, uint_t flags)
{
int err;
if ((flags == DLD_ENABLE && type != DLD_CAPAB_PERIM) &&
!check_ip_above(sup->su_rq)) {
return (ENOTSUP);
}
switch (type) {
case DLD_CAPAB_DIRECT:
err = softmac_capab_direct(sup, data, flags);
break;
case DLD_CAPAB_PERIM:
err = softmac_capab_perim(sup, data, flags);
break;
default:
err = ENOTSUP;
break;
}
return (err);
}
static void
softmac_capability_advertise(softmac_upper_t *sup, mblk_t *mp)
{
dl_capability_ack_t *dlap;
dl_capability_sub_t *dlsp;
t_uscalar_t subsize;
uint8_t *ptr;
queue_t *q = sup->su_wq;
mblk_t *mp1;
softmac_t *softmac = sup->su_softmac;
boolean_t dld_capable = B_FALSE;
boolean_t hcksum_capable = B_FALSE;
boolean_t zcopy_capable = B_FALSE;
ASSERT(sup->su_mode == SOFTMAC_FASTPATH);
subsize = 0;
if (check_ip_above(sup->su_rq)) {
dld_capable = B_TRUE;
subsize += sizeof (dl_capability_sub_t) +
sizeof (dl_capab_dld_t);
}
if (softmac->smac_capab_flags & MAC_CAPAB_HCKSUM) {
hcksum_capable = B_TRUE;
subsize += sizeof (dl_capability_sub_t) +
sizeof (dl_capab_hcksum_t);
}
if (!(softmac->smac_capab_flags & MAC_CAPAB_NO_ZCOPY)) {
zcopy_capable = B_TRUE;
subsize += sizeof (dl_capability_sub_t) +
sizeof (dl_capab_zerocopy_t);
}
if ((subsize == 0) || (mp1 = reallocb(mp,
sizeof (dl_capability_ack_t) + subsize, 0)) == NULL) {
dlerrorack(q, mp, DL_CAPABILITY_REQ, DL_NOTSUPPORTED, 0);
return;
}
mp = mp1;
DB_TYPE(mp) = M_PROTO;
mp->b_wptr = mp->b_rptr + sizeof (dl_capability_ack_t) + subsize;
bzero(mp->b_rptr, MBLKL(mp));
dlap = (dl_capability_ack_t *)mp->b_rptr;
dlap->dl_primitive = DL_CAPABILITY_ACK;
dlap->dl_sub_offset = sizeof (dl_capability_ack_t);
dlap->dl_sub_length = subsize;
ptr = (uint8_t *)&dlap[1];
if (dld_capable) {
dl_capab_dld_t dld;
dlsp = (dl_capability_sub_t *)ptr;
dlsp->dl_cap = DL_CAPAB_DLD;
dlsp->dl_length = sizeof (dl_capab_dld_t);
ptr += sizeof (dl_capability_sub_t);
bzero(&dld, sizeof (dl_capab_dld_t));
dld.dld_version = DLD_CURRENT_VERSION;
dld.dld_capab = (uintptr_t)softmac_dld_capab;
dld.dld_capab_handle = (uintptr_t)sup;
dlcapabsetqid(&(dld.dld_mid), sup->su_rq);
bcopy(&dld, ptr, sizeof (dl_capab_dld_t));
ptr += sizeof (dl_capab_dld_t);
}
if (hcksum_capable) {
dl_capab_hcksum_t hcksum;
dlsp = (dl_capability_sub_t *)ptr;
dlsp->dl_cap = DL_CAPAB_HCKSUM;
dlsp->dl_length = sizeof (dl_capab_hcksum_t);
ptr += sizeof (dl_capability_sub_t);
bzero(&hcksum, sizeof (dl_capab_hcksum_t));
hcksum.hcksum_version = HCKSUM_VERSION_1;
hcksum.hcksum_txflags = softmac->smac_hcksum_txflags;
dlcapabsetqid(&(hcksum.hcksum_mid), sup->su_rq);
bcopy(&hcksum, ptr, sizeof (dl_capab_hcksum_t));
ptr += sizeof (dl_capab_hcksum_t);
}
if (zcopy_capable) {
dl_capab_zerocopy_t zcopy;
dlsp = (dl_capability_sub_t *)ptr;
dlsp->dl_cap = DL_CAPAB_ZEROCOPY;
dlsp->dl_length = sizeof (dl_capab_zerocopy_t);
ptr += sizeof (dl_capability_sub_t);
bzero(&zcopy, sizeof (dl_capab_zerocopy_t));
zcopy.zerocopy_version = ZEROCOPY_VERSION_1;
zcopy.zerocopy_flags = DL_CAPAB_VMSAFE_MEM;
dlcapabsetqid(&(zcopy.zerocopy_mid), sup->su_rq);
bcopy(&zcopy, ptr, sizeof (dl_capab_zerocopy_t));
ptr += sizeof (dl_capab_zerocopy_t);
}
ASSERT(ptr == mp->b_rptr + sizeof (dl_capability_ack_t) + subsize);
qreply(q, mp);
}
static void
softmac_capability_req(softmac_upper_t *sup, mblk_t *mp)
{
dl_capability_req_t *dlp = (dl_capability_req_t *)mp->b_rptr;
dl_capability_sub_t *sp;
size_t size, len;
offset_t off, end;
t_uscalar_t dl_err;
queue_t *q = sup->su_wq;
ASSERT(sup->su_mode == SOFTMAC_FASTPATH);
if (MBLKL(mp) < sizeof (dl_capability_req_t)) {
dl_err = DL_BADPRIM;
goto failed;
}
if (!sup->su_bound) {
dl_err = DL_OUTSTATE;
goto failed;
}
if (dlp->dl_sub_length == 0) {
softmac_capability_advertise(sup, mp);
return;
}
if (!MBLKIN(mp, dlp->dl_sub_offset, dlp->dl_sub_length)) {
dl_err = DL_BADPRIM;
goto failed;
}
dlp->dl_primitive = DL_CAPABILITY_ACK;
off = dlp->dl_sub_offset;
len = dlp->dl_sub_length;
for (end = off + len; off < end; ) {
sp = (dl_capability_sub_t *)(mp->b_rptr + off);
size = sizeof (dl_capability_sub_t) + sp->dl_length;
if (off + size > end ||
!IS_P2ALIGNED(off, sizeof (uint32_t))) {
dl_err = DL_BADPRIM;
goto failed;
}
switch (sp->dl_cap) {
case DL_CAPAB_HCKSUM: {
dl_capab_hcksum_t *hcksump;
dl_capab_hcksum_t hcksum;
hcksump = (dl_capab_hcksum_t *)&sp[1];
bcopy(hcksump, &hcksum, sizeof (dl_capab_hcksum_t));
dlcapabsetqid(&(hcksum.hcksum_mid), sup->su_rq);
bcopy(&hcksum, hcksump, sizeof (dl_capab_hcksum_t));
break;
}
default:
break;
}
off += size;
}
qreply(q, mp);
return;
failed:
dlerrorack(q, mp, DL_CAPABILITY_REQ, dl_err, 0);
}
static void
softmac_bind_req(softmac_upper_t *sup, mblk_t *mp)
{
softmac_lower_t *slp = sup->su_slp;
softmac_t *softmac = sup->su_softmac;
mblk_t *ackmp, *mp1;
int err;
if (MBLKL(mp) < DL_BIND_REQ_SIZE) {
freemsg(mp);
return;
}
if ((mp1 = allocb(sizeof (dl_error_ack_t), BPRI_HI)) == NULL) {
dlerrorack(sup->su_wq, mp, DL_BIND_REQ, DL_SYSERR, ENOMEM);
return;
}
err = softmac_output(slp, mp, DL_BIND_REQ, DL_BIND_ACK, &ackmp);
if (ackmp != NULL) {
freemsg(mp1);
} else {
ASSERT(err == ENOMSG);
ackmp = mp1;
}
if (err != 0)
goto failed;
if ((err = softmac_capab_enable(slp)) != 0)
goto failed;
mutex_enter(&softmac->smac_active_mutex);
if (softmac->smac_active) {
mutex_exit(&softmac->smac_active_mutex);
err = EBUSY;
goto failed;
}
softmac->smac_nactive++;
sup->su_active = B_TRUE;
mutex_exit(&softmac->smac_active_mutex);
sup->su_bound = B_TRUE;
qreply(sup->su_wq, ackmp);
return;
failed:
if (err != 0) {
dlerrorack(sup->su_wq, ackmp, DL_BIND_REQ, DL_SYSERR, err);
return;
}
}
static void
softmac_unbind_req(softmac_upper_t *sup, mblk_t *mp)
{
softmac_lower_t *slp = sup->su_slp;
softmac_t *softmac = sup->su_softmac;
mblk_t *ackmp, *mp1;
int err;
if (MBLKL(mp) < DL_UNBIND_REQ_SIZE) {
freemsg(mp);
return;
}
if (!sup->su_bound) {
dlerrorack(sup->su_wq, mp, DL_UNBIND_REQ, DL_OUTSTATE, 0);
return;
}
if ((mp1 = allocb(sizeof (dl_error_ack_t), BPRI_HI)) == NULL) {
dlerrorack(sup->su_wq, mp, DL_UNBIND_REQ, DL_SYSERR, ENOMEM);
return;
}
err = softmac_output(slp, mp, DL_UNBIND_REQ, DL_OK_ACK, &ackmp);
if (ackmp != NULL) {
freemsg(mp1);
} else {
ASSERT(err == ENOMSG);
ackmp = mp1;
}
if (err != 0) {
dlerrorack(sup->su_wq, ackmp, DL_UNBIND_REQ, DL_SYSERR, err);
return;
}
sup->su_bound = B_FALSE;
mutex_enter(&softmac->smac_active_mutex);
if (sup->su_active) {
ASSERT(!softmac->smac_active);
softmac->smac_nactive--;
sup->su_active = B_FALSE;
}
mutex_exit(&softmac->smac_active_mutex);
qreply(sup->su_wq, ackmp);
}
static void
softmac_wput_single_nondata(softmac_upper_t *sup, mblk_t *mp)
{
softmac_t *softmac = sup->su_softmac;
softmac_lower_t *slp = sup->su_slp;
unsigned char dbtype;
t_uscalar_t prim;
dbtype = DB_TYPE(mp);
sup->su_is_arp = 0;
switch (dbtype) {
case M_CTL:
sup->su_is_arp = 1;
case M_IOCTL: {
uint32_t expected_mode;
if (((struct iocblk *)(mp->b_rptr))->ioc_cmd != SIOCSLIFNAME)
break;
if (dbtype == M_IOCTL)
miocnak(sup->su_wq, mp, 0, EINVAL);
else
freemsg(mp);
mutex_enter(&softmac->smac_fp_mutex);
expected_mode = DATAPATH_MODE(softmac);
if (expected_mode == SOFTMAC_SLOWPATH)
sup->su_mode = SOFTMAC_SLOWPATH;
list_insert_head(&softmac->smac_sup_list, sup);
mutex_exit(&softmac->smac_fp_mutex);
if (expected_mode == SOFTMAC_FASTPATH)
(void) softmac_fastpath_setup(sup);
return;
}
case M_PROTO:
case M_PCPROTO:
if (MBLKL(mp) < sizeof (t_uscalar_t)) {
freemsg(mp);
return;
}
prim = ((union DL_primitives *)mp->b_rptr)->dl_primitive;
switch (prim) {
case DL_NOTIFY_IND:
if (MBLKL(mp) < sizeof (dl_notify_ind_t) ||
((dl_notify_ind_t *)mp->b_rptr)->dl_notification !=
DL_NOTE_REPLUMB) {
freemsg(mp);
return;
}
qreply(sup->su_wq, mp);
return;
case DL_NOTIFY_CONF:
if (MBLKL(mp) < sizeof (dl_notify_conf_t) ||
((dl_notify_conf_t *)mp->b_rptr)->dl_notification !=
DL_NOTE_REPLUMB_DONE) {
freemsg(mp);
return;
}
freemsg(mp);
softmac_datapath_switch_done(sup);
return;
}
break;
}
if (sup->su_mode != SOFTMAC_FASTPATH) {
(void) dld_wput(sup->su_wq, mp);
return;
}
switch (dbtype) {
case M_PROTO:
case M_PCPROTO:
switch (prim) {
case DL_BIND_REQ:
softmac_bind_req(sup, mp);
break;
case DL_UNBIND_REQ:
softmac_unbind_req(sup, mp);
break;
case DL_CAPABILITY_REQ:
softmac_capability_req(sup, mp);
break;
default:
putnext(slp->sl_wq, mp);
break;
}
break;
default:
putnext(slp->sl_wq, mp);
break;
}
}
static void
softmac_wput_nondata_task(void *arg)
{
softmac_upper_t *sup = arg;
mblk_t *mp;
mutex_enter(&sup->su_disp_mutex);
while (sup->su_pending_head != NULL) {
if (sup->su_closing)
break;
SOFTMAC_DQ_PENDING(sup, &mp);
mutex_exit(&sup->su_disp_mutex);
softmac_wput_single_nondata(sup, mp);
mutex_enter(&sup->su_disp_mutex);
}
freemsgchain(sup->su_pending_head);
sup->su_pending_head = sup->su_pending_tail = NULL;
sup->su_dlpi_pending = B_FALSE;
cv_signal(&sup->su_disp_cv);
mutex_exit(&sup->su_disp_mutex);
}
static void
softmac_taskq_dispatch(void)
{
callb_cpr_t cprinfo;
softmac_upper_t *sup;
CALLB_CPR_INIT(&cprinfo, &softmac_taskq_lock, callb_generic_cpr,
"softmac_taskq_dispatch");
mutex_enter(&softmac_taskq_lock);
while (!softmac_taskq_quit) {
sup = list_head(&softmac_taskq_list);
while (sup != NULL) {
list_remove(&softmac_taskq_list, sup);
sup->su_taskq_scheduled = B_FALSE;
mutex_exit(&softmac_taskq_lock);
VERIFY(taskq_dispatch(system_taskq,
softmac_wput_nondata_task, sup, TQ_SLEEP) !=
TASKQID_INVALID);
mutex_enter(&softmac_taskq_lock);
sup = list_head(&softmac_taskq_list);
}
CALLB_CPR_SAFE_BEGIN(&cprinfo);
cv_wait(&softmac_taskq_cv, &softmac_taskq_lock);
CALLB_CPR_SAFE_END(&cprinfo, &softmac_taskq_lock);
}
softmac_taskq_done = B_TRUE;
cv_signal(&softmac_taskq_cv);
CALLB_CPR_EXIT(&cprinfo);
thread_exit();
}
void
softmac_wput_nondata(softmac_upper_t *sup, mblk_t *mp)
{
mutex_enter(&sup->su_disp_mutex);
if (sup->su_closing) {
mutex_exit(&sup->su_disp_mutex);
freemsg(mp);
return;
}
SOFTMAC_EQ_PENDING(sup, mp);
if (sup->su_dlpi_pending) {
mutex_exit(&sup->su_disp_mutex);
return;
}
sup->su_dlpi_pending = B_TRUE;
mutex_exit(&sup->su_disp_mutex);
if (taskq_dispatch(system_taskq, softmac_wput_nondata_task,
sup, TQ_NOSLEEP) != TASKQID_INVALID) {
return;
}
mutex_enter(&softmac_taskq_lock);
if (!sup->su_taskq_scheduled) {
list_insert_tail(&softmac_taskq_list, sup);
cv_signal(&softmac_taskq_cv);
}
sup->su_taskq_scheduled = B_TRUE;
mutex_exit(&softmac_taskq_lock);
}
static int
softmac_fastpath_setup(softmac_upper_t *sup)
{
softmac_t *softmac = sup->su_softmac;
softmac_lower_t *slp;
int err;
err = softmac_lower_setup(softmac, sup, &slp);
mutex_enter(&sup->su_mutex);
while (sup->su_tx_inprocess != 0)
cv_wait(&sup->su_cv, &sup->su_mutex);
ASSERT(sup->su_mode != SOFTMAC_FASTPATH);
ASSERT(sup->su_slp == NULL);
if (err != 0) {
sup->su_mode = SOFTMAC_SLOWPATH;
} else {
sup->su_slp = slp;
sup->su_mode = SOFTMAC_FASTPATH;
}
mutex_exit(&sup->su_mutex);
return (err);
}
static void
softmac_fastpath_tear(softmac_upper_t *sup)
{
mutex_enter(&sup->su_mutex);
while (sup->su_tx_inprocess != 0)
cv_wait(&sup->su_cv, &sup->su_mutex);
sup->su_tx_notify_func = NULL;
sup->su_tx_notify_arg = NULL;
if (sup->su_tx_busy) {
ASSERT(sup->su_tx_flow_mp == NULL);
VERIFY((sup->su_tx_flow_mp = getq(sup->su_wq)) != NULL);
sup->su_tx_busy = B_FALSE;
}
sup->su_mode = SOFTMAC_SLOWPATH;
(void) ldi_close(sup->su_slp->sl_lh, FREAD|FWRITE, kcred);
sup->su_slp = NULL;
mutex_exit(&sup->su_mutex);
}
void
softmac_wput_data(softmac_upper_t *sup, mblk_t *mp)
{
if (sup->su_mode != SOFTMAC_FASTPATH)
(void) dld_wput(sup->su_wq, mp);
else
(void) softmac_fastpath_wput_data(sup, mp, (uintptr_t)NULL, 0);
}
static mac_tx_cookie_t
softmac_fastpath_wput_data(softmac_upper_t *sup, mblk_t *mp, uintptr_t f_hint,
uint16_t flag)
{
queue_t *wq = sup->su_slp->sl_wq;
ASSERT((flag & ~MAC_DROP_ON_NO_DESC) == 0);
ASSERT(mp->b_next == NULL);
if (SOFTMAC_CANPUTNEXT(wq)) {
putnext(wq, mp);
return ((mac_tx_cookie_t)NULL);
}
if (sup->su_tx_busy) {
if ((flag & MAC_DROP_ON_NO_DESC) != 0)
freemsg(mp);
else
putnext(wq, mp);
return ((mac_tx_cookie_t)sup);
}
mutex_enter(&sup->su_mutex);
if (!sup->su_tx_busy) {
ASSERT(sup->su_tx_flow_mp != NULL);
(void) putq(sup->su_wq, sup->su_tx_flow_mp);
sup->su_tx_flow_mp = NULL;
sup->su_tx_busy = B_TRUE;
qenable(wq);
}
mutex_exit(&sup->su_mutex);
if ((flag & MAC_DROP_ON_NO_DESC) != 0)
freemsg(mp);
else
putnext(wq, mp);
return ((mac_tx_cookie_t)sup);
}
boolean_t
softmac_active_set(void *arg)
{
softmac_t *softmac = arg;
mutex_enter(&softmac->smac_active_mutex);
if (softmac->smac_nactive != 0) {
mutex_exit(&softmac->smac_active_mutex);
return (B_FALSE);
}
softmac->smac_active = B_TRUE;
mutex_exit(&softmac->smac_active_mutex);
return (B_TRUE);
}
void
softmac_active_clear(void *arg)
{
softmac_t *softmac = arg;
mutex_enter(&softmac->smac_active_mutex);
ASSERT(softmac->smac_active && (softmac->smac_nactive == 0));
softmac->smac_active = B_FALSE;
mutex_exit(&softmac->smac_active_mutex);
}
int
softmac_datapath_switch(softmac_t *softmac, boolean_t disable, boolean_t admin)
{
softmac_upper_t *sup;
mblk_t *head = NULL, *tail = NULL, *mp;
list_t reqlist;
softmac_switch_req_t *req;
uint32_t current_mode, expected_mode;
int err = 0;
mutex_enter(&softmac->smac_fp_mutex);
current_mode = DATAPATH_MODE(softmac);
if (admin) {
if (softmac->smac_fastpath_admin_disabled == disable) {
mutex_exit(&softmac->smac_fp_mutex);
return (0);
}
softmac->smac_fastpath_admin_disabled = disable;
} else if (disable) {
softmac->smac_fp_disable_clients++;
} else {
ASSERT(softmac->smac_fp_disable_clients != 0);
softmac->smac_fp_disable_clients--;
}
expected_mode = DATAPATH_MODE(softmac);
if (current_mode == expected_mode) {
mutex_exit(&softmac->smac_fp_mutex);
return (0);
}
list_create(&reqlist, sizeof (softmac_switch_req_t),
offsetof(softmac_switch_req_t, ssq_req_list_node));
for (sup = list_head(&softmac->smac_sup_list); sup != NULL;
sup = list_next(&softmac->smac_sup_list, sup)) {
dl_notify_ind_t *dlip;
req = kmem_alloc(sizeof (softmac_switch_req_t), KM_NOSLEEP);
if (req == NULL)
break;
req->ssq_expected_mode = expected_mode;
if (sup->su_is_arp) {
list_insert_tail(&reqlist, req);
continue;
}
if ((mp = allocb(sizeof (dl_notify_ind_t), BPRI_LO)) == NULL) {
kmem_free(req, sizeof (softmac_switch_req_t));
break;
}
list_insert_tail(&reqlist, req);
mp->b_wptr = mp->b_rptr + sizeof (dl_notify_ind_t);
mp->b_datap->db_type = M_PROTO;
bzero(mp->b_rptr, sizeof (dl_notify_ind_t));
dlip = (dl_notify_ind_t *)mp->b_rptr;
dlip->dl_primitive = DL_NOTIFY_IND;
dlip->dl_notification = DL_NOTE_REPLUMB;
if (head == NULL) {
head = tail = mp;
} else {
tail->b_next = mp;
tail = mp;
}
}
if (sup != NULL && expected_mode == SOFTMAC_SLOWPATH) {
err = ENOMEM;
goto fail;
}
for (sup = list_head(&softmac->smac_sup_list); sup != NULL;
sup = list_next(&softmac->smac_sup_list, sup)) {
if (!sup->su_is_arp) {
mp = head->b_next;
head->b_next = NULL;
softmac_wput_nondata(sup, head);
head = mp;
}
req = list_head(&reqlist);
ASSERT(req != NULL);
list_remove(&reqlist, req);
list_insert_tail(&sup->su_req_list, req);
}
mutex_exit(&softmac->smac_fp_mutex);
ASSERT(list_is_empty(&reqlist));
list_destroy(&reqlist);
return (0);
fail:
if (admin) {
softmac->smac_fastpath_admin_disabled = !disable;
} else if (disable) {
softmac->smac_fp_disable_clients--;
} else {
softmac->smac_fp_disable_clients++;
}
mutex_exit(&softmac->smac_fp_mutex);
while ((req = list_head(&reqlist)) != NULL) {
list_remove(&reqlist, req);
kmem_free(req, sizeof (softmac_switch_req_t));
}
freemsgchain(head);
list_destroy(&reqlist);
return (err);
}
int
softmac_fastpath_disable(void *arg)
{
return (softmac_datapath_switch((softmac_t *)arg, B_TRUE, B_FALSE));
}
void
softmac_fastpath_enable(void *arg)
{
VERIFY(softmac_datapath_switch((softmac_t *)arg, B_FALSE,
B_FALSE) == 0);
}
void
softmac_upperstream_close(softmac_upper_t *sup)
{
softmac_t *softmac = sup->su_softmac;
softmac_switch_req_t *req;
mutex_enter(&softmac->smac_fp_mutex);
if (sup->su_mode == SOFTMAC_FASTPATH)
softmac_fastpath_tear(sup);
if (sup->su_mode != SOFTMAC_UNKNOWN) {
list_remove(&softmac->smac_sup_list, sup);
sup->su_mode = SOFTMAC_UNKNOWN;
}
while ((req = list_head(&sup->su_req_list)) != NULL) {
list_remove(&sup->su_req_list, req);
kmem_free(req, sizeof (softmac_switch_req_t));
}
mutex_exit(&softmac->smac_fp_mutex);
}
static void
softmac_datapath_switch_done(softmac_upper_t *sup)
{
softmac_t *softmac = sup->su_softmac;
softmac_switch_req_t *req;
uint32_t expected_mode;
mutex_enter(&softmac->smac_fp_mutex);
req = list_head(&sup->su_req_list);
list_remove(&sup->su_req_list, req);
expected_mode = req->ssq_expected_mode;
kmem_free(req, sizeof (softmac_switch_req_t));
if (expected_mode == sup->su_mode) {
mutex_exit(&softmac->smac_fp_mutex);
return;
}
ASSERT(!sup->su_bound);
mutex_exit(&softmac->smac_fp_mutex);
if (expected_mode == SOFTMAC_SLOWPATH)
softmac_fastpath_tear(sup);
else
(void) softmac_fastpath_setup(sup);
}