#include <inet/common.h>
#include <sys/strsubr.h>
#include <sys/stropts.h>
#include <sys/strsun.h>
#include <sys/vlan.h>
#include <sys/dld_impl.h>
#include <sys/cpuvar.h>
#include <sys/callb.h>
#include <sys/list.h>
#include <sys/mac_client.h>
#include <sys/mac_client_priv.h>
#include <sys/mac_flow.h>
static int str_constructor(void *, void *, int);
static void str_destructor(void *, void *);
static mblk_t *str_unitdata_ind(dld_str_t *, mblk_t *, boolean_t);
static void str_notify_promisc_on_phys(dld_str_t *);
static void str_notify_promisc_off_phys(dld_str_t *);
static void str_notify_phys_addr(dld_str_t *, uint_t, const uint8_t *);
static void str_notify_link_up(dld_str_t *);
static void str_notify_link_down(dld_str_t *);
static void str_notify_capab_reneg(dld_str_t *);
static void str_notify_speed(dld_str_t *, uint32_t);
static void ioc_native(dld_str_t *, mblk_t *);
static void ioc_margin(dld_str_t *, mblk_t *);
static void ioc_raw(dld_str_t *, mblk_t *);
static void ioc_fast(dld_str_t *, mblk_t *);
static void ioc_lowlink(dld_str_t *, mblk_t *);
static void ioc(dld_str_t *, mblk_t *);
static void dld_ioc(dld_str_t *, mblk_t *);
static void dld_wput_nondata(dld_str_t *, mblk_t *);
static void str_mdata_raw_put(dld_str_t *, mblk_t *);
static mblk_t *i_dld_ether_header_update_tag(mblk_t *, uint_t, uint16_t,
link_tagmode_t);
static mblk_t *i_dld_ether_header_strip_tag(mblk_t *, boolean_t);
static uint32_t str_count;
static kmem_cache_t *str_cachep;
static mod_hash_t *str_hashp;
#define STR_HASHSZ 64
#define STR_HASH_KEY(key) ((mod_hash_key_t)(uintptr_t)(key))
#define dld_taskq system_taskq
static kmutex_t dld_taskq_lock;
static kcondvar_t dld_taskq_cv;
static list_t dld_taskq_list;
boolean_t dld_taskq_quit;
boolean_t dld_taskq_done;
static void dld_taskq_dispatch(void);
typedef struct i_dld_str_state_s {
major_t ds_major;
minor_t ds_minor;
int ds_instance;
dev_info_t *ds_dip;
} i_dld_str_state_t;
static uint_t
i_dld_str_walker(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
{
i_dld_str_state_t *statep = arg;
dld_str_t *dsp = (dld_str_t *)val;
mac_handle_t mh;
if (statep->ds_major != dsp->ds_major)
return (MH_WALK_CONTINUE);
ASSERT(statep->ds_minor != 0);
mh = dsp->ds_mh;
if (statep->ds_minor == dsp->ds_minor) {
if (mh != NULL) {
statep->ds_dip = mac_devinfo_get(mh);
statep->ds_instance = DLS_MINOR2INST(mac_minor(mh));
}
return (MH_WALK_TERMINATE);
}
return (MH_WALK_CONTINUE);
}
static dev_info_t *
dld_finddevinfo(dev_t dev)
{
dev_info_t *dip;
i_dld_str_state_t state;
if (getminor(dev) == 0)
return (NULL);
if ((dip = dls_link_devinfo(dev)) != NULL)
return (dip);
state.ds_minor = getminor(dev);
state.ds_major = getmajor(dev);
state.ds_dip = NULL;
state.ds_instance = -1;
mod_hash_walk(str_hashp, i_dld_str_walker, &state);
return (state.ds_dip);
}
int
dld_devt_to_instance(dev_t dev)
{
minor_t minor;
i_dld_str_state_t state;
if ((minor = getminor(dev)) == 0)
return (-1);
if (minor > 0 && minor <= DLS_MAX_MINOR) {
return (DLS_MINOR2INST(minor));
}
state.ds_minor = getminor(dev);
state.ds_major = getmajor(dev);
state.ds_dip = NULL;
state.ds_instance = -1;
mod_hash_walk(str_hashp, i_dld_str_walker, &state);
return (state.ds_instance);
}
int
dld_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resp)
{
dev_info_t *devinfo;
minor_t minor = getminor((dev_t)arg);
int rc = DDI_FAILURE;
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
if ((devinfo = dld_finddevinfo((dev_t)arg)) != NULL) {
*(dev_info_t **)resp = devinfo;
rc = DDI_SUCCESS;
}
break;
case DDI_INFO_DEVT2INSTANCE:
if (minor > 0 && minor <= DLS_MAX_MINOR) {
*resp = (void *)(uintptr_t)DLS_MINOR2INST(minor);
rc = DDI_SUCCESS;
} else if (minor > DLS_MAX_MINOR &&
(devinfo = dld_finddevinfo((dev_t)arg)) != NULL) {
*resp = (void *)(uintptr_t)ddi_get_instance(devinfo);
rc = DDI_SUCCESS;
}
break;
}
return (rc);
}
void *
dld_str_private(queue_t *q)
{
return (((dld_str_t *)(q->q_ptr))->ds_private);
}
int
dld_str_open(queue_t *rq, dev_t *devp, void *private)
{
dld_str_t *dsp;
major_t major;
minor_t minor;
int err;
major = getmajor(*devp);
minor = getminor(*devp);
if (minor >= mac_private_minor())
return (ENOSTR);
dsp = dld_str_create(rq, DLD_DLPI, major,
((minor == 0) ? DL_STYLE2 : DL_STYLE1));
if (dsp == NULL)
return (ENOSR);
ASSERT(dsp->ds_dlstate == DL_UNATTACHED);
dsp->ds_private = private;
if (minor != 0) {
if ((err = dld_str_attach(dsp, (t_uscalar_t)minor - 1)) != 0)
goto failed;
ASSERT(dsp->ds_dlstate == DL_UNBOUND);
} else {
(void) qassociate(rq, -1);
}
qprocson(rq);
*devp = makedevice(getmajor(*devp), dsp->ds_minor);
return (0);
failed:
dld_str_destroy(dsp);
return (err);
}
int
dld_str_close(queue_t *rq)
{
dld_str_t *dsp = rq->q_ptr;
ASSERT(dsp->ds_datathr_cnt == 0);
mutex_enter(&dsp->ds_lock);
while (dsp->ds_dlpi_pending)
cv_wait(&dsp->ds_dlpi_pending_cv, &dsp->ds_lock);
mutex_exit(&dsp->ds_lock);
if (dsp->ds_dlstate != DL_UNATTACHED) {
dld_str_detach(dsp);
}
dld_str_destroy(dsp);
return (0);
}
int
dld_open(queue_t *rq, dev_t *devp, int flag, int sflag, cred_t *credp)
{
if (sflag == MODOPEN)
return (ENOTSUP);
if (rq->q_ptr != NULL)
return (EBUSY);
return (dld_str_open(rq, devp, NULL));
}
int
dld_close(queue_t *rq, int flags __unused, cred_t *credp __unused)
{
qprocsoff(rq);
return (dld_str_close(rq));
}
int
dld_wput(queue_t *wq, mblk_t *mp)
{
dld_str_t *dsp = (dld_str_t *)wq->q_ptr;
dld_str_mode_t mode;
switch (DB_TYPE(mp)) {
case M_DATA:
mutex_enter(&dsp->ds_lock);
mode = dsp->ds_mode;
if ((dsp->ds_dlstate != DL_IDLE) ||
(mode != DLD_FASTPATH && mode != DLD_RAW)) {
mutex_exit(&dsp->ds_lock);
freemsg(mp);
break;
}
DLD_DATATHR_INC(dsp);
mutex_exit(&dsp->ds_lock);
if (mode == DLD_FASTPATH) {
if (dsp->ds_mip->mi_media == DL_ETHER &&
(MBLKL(mp) < sizeof (struct ether_header))) {
freemsg(mp);
} else {
(void) str_mdata_fastpath_put(dsp, mp, 0, 0);
}
} else {
str_mdata_raw_put(dsp, mp);
}
DLD_DATATHR_DCR(dsp);
break;
case M_PROTO:
case M_PCPROTO: {
t_uscalar_t prim;
if (MBLKL(mp) < sizeof (t_uscalar_t))
break;
prim = ((union DL_primitives *)mp->b_rptr)->dl_primitive;
if (prim == DL_UNITDATA_REQ) {
proto_unitdata_req(dsp, mp);
} else {
dld_wput_nondata(dsp, mp);
}
break;
}
case M_IOCTL:
dld_wput_nondata(dsp, mp);
break;
case M_FLUSH:
if (*mp->b_rptr & FLUSHW) {
DLD_CLRQFULL(dsp);
*mp->b_rptr &= ~FLUSHW;
}
if (*mp->b_rptr & FLUSHR) {
qreply(wq, mp);
} else {
freemsg(mp);
}
break;
default:
freemsg(mp);
break;
}
return (0);
}
int
dld_wsrv(queue_t *wq)
{
dld_str_t *dsp = wq->q_ptr;
DLD_CLRQFULL(dsp);
return (0);
}
void
dld_init_ops(struct dev_ops *ops, const char *name)
{
struct streamtab *stream;
struct qinit *rq, *wq;
struct module_info *modinfo;
modinfo = kmem_zalloc(sizeof (struct module_info), KM_SLEEP);
modinfo->mi_idname = kmem_zalloc(FMNAMESZ, KM_SLEEP);
(void) snprintf(modinfo->mi_idname, FMNAMESZ, "%s", name);
modinfo->mi_minpsz = 0;
modinfo->mi_maxpsz = 64*1024;
modinfo->mi_hiwat = 1;
modinfo->mi_lowat = 0;
rq = kmem_zalloc(sizeof (struct qinit), KM_SLEEP);
rq->qi_qopen = dld_open;
rq->qi_qclose = dld_close;
rq->qi_minfo = modinfo;
wq = kmem_zalloc(sizeof (struct qinit), KM_SLEEP);
wq->qi_putp = (pfi_t)dld_wput;
wq->qi_srvp = (pfi_t)dld_wsrv;
wq->qi_minfo = modinfo;
stream = kmem_zalloc(sizeof (struct streamtab), KM_SLEEP);
stream->st_rdinit = rq;
stream->st_wrinit = wq;
ops->devo_cb_ops->cb_str = stream;
if (ops->devo_getinfo == NULL)
ops->devo_getinfo = &dld_getinfo;
}
void
dld_fini_ops(struct dev_ops *ops)
{
struct streamtab *stream;
struct qinit *rq, *wq;
struct module_info *modinfo;
stream = ops->devo_cb_ops->cb_str;
rq = stream->st_rdinit;
wq = stream->st_wrinit;
modinfo = rq->qi_minfo;
ASSERT(wq->qi_minfo == modinfo);
kmem_free(stream, sizeof (struct streamtab));
kmem_free(wq, sizeof (struct qinit));
kmem_free(rq, sizeof (struct qinit));
kmem_free(modinfo->mi_idname, FMNAMESZ);
kmem_free(modinfo, sizeof (struct module_info));
}
void
dld_str_init(void)
{
str_cachep = kmem_cache_create("dld_str_cache", sizeof (dld_str_t),
0, str_constructor, str_destructor, NULL, NULL, NULL, 0);
ASSERT(str_cachep != NULL);
str_hashp = mod_hash_create_idhash("dld_str_hash", STR_HASHSZ,
mod_hash_null_valdtor);
mutex_init(&dld_taskq_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&dld_taskq_cv, NULL, CV_DRIVER, NULL);
dld_taskq_quit = B_FALSE;
dld_taskq_done = B_FALSE;
list_create(&dld_taskq_list, sizeof (dld_str_t),
offsetof(dld_str_t, ds_tqlist));
(void) thread_create(NULL, 0, dld_taskq_dispatch, NULL, 0,
&p0, TS_RUN, minclsyspri);
}
int
dld_str_fini(void)
{
if (str_count != 0)
return (EBUSY);
mutex_enter(&dld_taskq_lock);
dld_taskq_quit = B_TRUE;
cv_signal(&dld_taskq_cv);
while (!dld_taskq_done)
cv_wait(&dld_taskq_cv, &dld_taskq_lock);
mutex_exit(&dld_taskq_lock);
list_destroy(&dld_taskq_list);
kmem_cache_destroy(str_cachep);
mod_hash_destroy_idhash(str_hashp);
return (0);
}
dld_str_t *
dld_str_create(queue_t *rq, uint_t type, major_t major, t_uscalar_t style)
{
dld_str_t *dsp;
int err;
atomic_inc_32(&str_count);
dsp = kmem_cache_alloc(str_cachep, KM_SLEEP);
dsp->ds_tx_flow_mp = allocb(1, BPRI_HI);
if (dsp->ds_tx_flow_mp == NULL) {
kmem_cache_free(str_cachep, dsp);
atomic_dec_32(&str_count);
return (NULL);
}
dsp->ds_type = type;
dsp->ds_major = major;
dsp->ds_style = style;
ASSERT(RD(rq) == rq);
dsp->ds_rq = rq;
dsp->ds_wq = WR(rq);
rq->q_ptr = WR(rq)->q_ptr = (void *)dsp;
noenable(WR(rq));
err = mod_hash_insert(str_hashp, STR_HASH_KEY(dsp->ds_minor),
(mod_hash_val_t)dsp);
ASSERT(err == 0);
return (dsp);
}
void
dld_str_destroy(dld_str_t *dsp)
{
queue_t *rq;
queue_t *wq;
mod_hash_val_t val;
rq = dsp->ds_rq;
wq = dsp->ds_wq;
ASSERT(wq == WR(rq));
rq->q_ptr = wq->q_ptr = NULL;
dsp->ds_rq = dsp->ds_wq = NULL;
ASSERT(dsp->ds_dlstate == DL_UNATTACHED);
ASSERT(dsp->ds_sap == 0);
ASSERT(dsp->ds_mh == NULL);
ASSERT(dsp->ds_mch == NULL);
ASSERT(dsp->ds_promisc == 0);
ASSERT(dsp->ds_mph == NULL);
ASSERT(dsp->ds_mip == NULL);
ASSERT(dsp->ds_mnh == NULL);
ASSERT(dsp->ds_polling == B_FALSE);
ASSERT(dsp->ds_direct == B_FALSE);
ASSERT(dsp->ds_lso == B_FALSE);
ASSERT(dsp->ds_lso_max == 0);
ASSERT(dsp->ds_passivestate != DLD_ACTIVE);
dsp->ds_notifications = 0;
dsp->ds_passivestate = DLD_UNINITIALIZED;
dsp->ds_mode = DLD_UNITDATA;
dsp->ds_native = B_FALSE;
dsp->ds_nonip = B_FALSE;
ASSERT(dsp->ds_datathr_cnt == 0);
ASSERT(dsp->ds_pending_head == NULL);
ASSERT(dsp->ds_pending_tail == NULL);
ASSERT(!dsp->ds_dlpi_pending);
ASSERT(dsp->ds_dlp == NULL);
ASSERT(dsp->ds_dmap == NULL);
ASSERT(dsp->ds_rx == NULL);
ASSERT(dsp->ds_rx_arg == NULL);
ASSERT(dsp->ds_next == NULL);
ASSERT(dsp->ds_head == NULL);
if (dsp->ds_tx_flow_mp != NULL) {
freeb(dsp->ds_tx_flow_mp);
dsp->ds_tx_flow_mp = NULL;
}
(void) mod_hash_remove(str_hashp, STR_HASH_KEY(dsp->ds_minor), &val);
ASSERT(dsp == (dld_str_t *)val);
kmem_cache_free(str_cachep, dsp);
atomic_dec_32(&str_count);
}
static int
str_constructor(void *buf, void *cdrarg, int kmflags)
{
dld_str_t *dsp = buf;
bzero(buf, sizeof (dld_str_t));
if ((dsp->ds_minor = mac_minor_hold(kmflags == KM_SLEEP)) == 0)
return (-1);
dsp->ds_dlstate = DL_UNATTACHED;
mutex_init(&dsp->ds_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&dsp->ds_datathr_cv, NULL, CV_DRIVER, NULL);
cv_init(&dsp->ds_dlpi_pending_cv, NULL, CV_DRIVER, NULL);
return (0);
}
static void
str_destructor(void *buf, void *cdrarg)
{
dld_str_t *dsp = buf;
mac_minor_rele(dsp->ds_minor);
ASSERT(dsp->ds_tx_flow_mp == NULL);
mutex_destroy(&dsp->ds_lock);
cv_destroy(&dsp->ds_datathr_cv);
cv_destroy(&dsp->ds_dlpi_pending_cv);
}
static mblk_t *
i_dld_ether_header_update_tag(mblk_t *mp, uint_t pri, uint16_t vid,
link_tagmode_t tagmode)
{
mblk_t *hmp;
struct ether_vlan_header *evhp;
struct ether_header *ehp;
uint16_t old_tci = 0;
size_t len;
ASSERT(pri != 0 || vid != VLAN_ID_NONE);
evhp = (struct ether_vlan_header *)mp->b_rptr;
if (ntohs(evhp->ether_tpid) == ETHERTYPE_VLAN) {
len = sizeof (struct ether_vlan_header);
if ((DB_REF(mp) > 1) || (MBLKL(mp) < len)) {
hmp = msgpullup(mp, -1);
if ((hmp == NULL) || (MBLKL(hmp) < len)) {
freemsg(hmp);
return (NULL);
} else {
freemsg(mp);
mp = hmp;
}
}
evhp = (struct ether_vlan_header *)mp->b_rptr;
old_tci = ntohs(evhp->ether_tci);
} else {
if (vid == VLAN_ID_NONE && tagmode == LINK_TAGMODE_VLANONLY)
return (mp);
hmp = allocb(sizeof (struct ether_vlan_header), BPRI_MED);
if (hmp == NULL)
return (NULL);
evhp = (struct ether_vlan_header *)hmp->b_rptr;
ehp = (struct ether_header *)mp->b_rptr;
bcopy(ehp, evhp, (ETHERADDRL * 2));
evhp->ether_type = ehp->ether_type;
evhp->ether_tpid = htons(ETHERTYPE_VLAN);
hmp->b_wptr += sizeof (struct ether_vlan_header);
mp->b_rptr += sizeof (struct ether_header);
if (MBLKL(mp) == 0) {
hmp->b_cont = mp->b_cont;
freeb(mp);
} else {
hmp->b_cont = mp;
}
mp = hmp;
}
if (pri == 0)
pri = VLAN_PRI(old_tci);
if (vid == VLAN_ID_NONE)
vid = VLAN_ID(old_tci);
evhp->ether_tci = htons(VLAN_TCI(pri, VLAN_CFI(old_tci), vid));
return (mp);
}
mac_tx_cookie_t
str_mdata_fastpath_put(dld_str_t *dsp, mblk_t *mp, uintptr_t f_hint,
uint16_t flag)
{
boolean_t is_ethernet = (dsp->ds_mip->mi_media == DL_ETHER);
mblk_t *newmp;
uint_t pri;
mac_tx_cookie_t cookie;
if (is_ethernet) {
pri = (VLAN_MBLKPRI(mp) == 0) ? dsp->ds_pri : VLAN_MBLKPRI(mp);
if (pri != 0) {
newmp = i_dld_ether_header_update_tag(mp, pri,
VLAN_ID_NONE, dsp->ds_dlp->dl_tagmode);
if (newmp == NULL)
goto discard;
mp = newmp;
}
}
if ((cookie = DLD_TX(dsp, mp, f_hint, flag)) != 0) {
DLD_SETQFULL(dsp);
}
return (cookie);
discard:
freemsg(mp);
return (0);
}
static void
str_mdata_raw_put(dld_str_t *dsp, mblk_t *mp)
{
boolean_t is_ethernet = (dsp->ds_mip->mi_media == DL_ETHER);
mblk_t *bp, *newmp;
size_t size;
mac_header_info_t mhi;
uint_t pri, vid, dvid;
uint_t max_sdu;
if (!dsp->ds_native) {
if ((newmp = mac_header_cook(dsp->ds_mh, mp)) == NULL)
goto discard;
mp = newmp;
}
size = MBLKL(mp);
for (bp = mp->b_cont; bp != NULL; bp = bp->b_cont) {
if (DB_TYPE(bp) != M_DATA)
goto discard;
size += MBLKL(bp);
}
if (mac_vlan_header_info(dsp->ds_mh, mp, &mhi) != 0)
goto discard;
mac_sdu_get(dsp->ds_mh, NULL, &max_sdu);
max_sdu = dsp->ds_lso ? dsp->ds_lso_max : max_sdu;
if (size > max_sdu + mhi.mhi_hdrsize)
goto discard;
if (is_ethernet) {
dvid = mac_client_vid(dsp->ds_mch);
vid = VLAN_ID(mhi.mhi_tci);
if ((dvid != VLAN_ID_NONE) && (vid != VLAN_ID_NONE))
goto discard;
pri = VLAN_PRI(mhi.mhi_tci);
if (mhi.mhi_istagged && !mhi.mhi_ispvid && pri == 0 &&
vid == VLAN_ID_NONE)
goto discard;
pri = (pri == 0) ? dsp->ds_pri : 0;
if ((pri != 0) || (dvid != VLAN_ID_NONE)) {
if ((newmp = i_dld_ether_header_update_tag(mp, pri,
dvid, dsp->ds_dlp->dl_tagmode)) == NULL) {
goto discard;
}
mp = newmp;
}
}
if (DLD_TX(dsp, mp, 0, 0) != 0) {
DLD_SETQFULL(dsp);
}
return;
discard:
freemsg(mp);
}
int
dld_str_attach(dld_str_t *dsp, t_uscalar_t ppa)
{
dev_t dev;
int err;
const char *drvname;
mac_perim_handle_t mph = NULL;
boolean_t qassociated = B_FALSE;
dls_link_t *dlp = NULL;
dls_dl_handle_t ddp = NULL;
if ((drvname = ddi_major_to_name(dsp->ds_major)) == NULL)
return (EINVAL);
if (dsp->ds_style == DL_STYLE2 && ppa > DLS_MAX_PPA)
return (ENOTSUP);
if ((dsp->ds_style == DL_STYLE2) && (strcmp(drvname, "aggr") != 0) &&
(strcmp(drvname, "vnic") != 0)) {
if (qassociate(dsp->ds_wq, DLS_PPA2INST(ppa)) != 0)
return (EINVAL);
qassociated = B_TRUE;
}
dev = makedevice(dsp->ds_major, (minor_t)ppa + 1);
if ((err = dls_devnet_hold_by_dev(dev, &ddp)) != 0)
goto failed;
if ((err = mac_perim_enter_by_macname(dls_devnet_mac(ddp), &mph)) != 0)
goto failed;
if ((err = dls_link_hold(dls_devnet_mac(ddp), &dlp)) != 0)
goto failed;
if ((err = dls_open(dlp, ddp, dsp)) != 0)
goto failed;
dsp->ds_pri = 0;
dsp->ds_mnh = mac_notify_add(dsp->ds_mh, str_notify, dsp);
dsp->ds_dlstate = DL_UNBOUND;
mac_perim_exit(mph);
return (0);
failed:
if (dlp != NULL)
dls_link_rele(dlp);
if (mph != NULL)
mac_perim_exit(mph);
if (ddp != NULL)
dls_devnet_rele(ddp);
if (qassociated)
(void) qassociate(dsp->ds_wq, -1);
return (err);
}
void
dld_str_detach(dld_str_t *dsp)
{
mac_perim_handle_t mph;
int err;
ASSERT(dsp->ds_datathr_cnt == 0);
mac_perim_enter_by_mh(dsp->ds_mh, &mph);
err = mac_notify_remove(dsp->ds_mnh, B_FALSE);
dsp->ds_mnh = NULL;
dld_capabilities_disable(dsp);
dsp->ds_lso = B_FALSE;
dsp->ds_lso_max = 0;
dls_close(dsp);
mac_perim_exit(mph);
if (err != 0)
mac_notify_remove_wait(dsp->ds_mh);
dls_devnet_rele(dsp->ds_ddh);
dsp->ds_sap = 0;
dsp->ds_mh = NULL;
dsp->ds_mch = NULL;
dsp->ds_mip = NULL;
if (dsp->ds_style == DL_STYLE2)
(void) qassociate(dsp->ds_wq, -1);
dsp->ds_dlstate = DL_UNATTACHED;
}
static mblk_t *
i_dld_ether_header_strip_tag(mblk_t *mp, boolean_t keep_pri)
{
mblk_t *newmp;
struct ether_vlan_header *evhp;
uint16_t tci, new_tci;
ASSERT(MBLKL(mp) >= sizeof (struct ether_vlan_header));
if (DB_REF(mp) > 1) {
newmp = copymsg(mp);
if (newmp == NULL)
return (NULL);
freemsg(mp);
mp = newmp;
}
evhp = (struct ether_vlan_header *)mp->b_rptr;
tci = ntohs(evhp->ether_tci);
if (VLAN_PRI(tci) == 0 || !keep_pri) {
ovbcopy(mp->b_rptr, mp->b_rptr + VLAN_TAGSZ, 2 * ETHERADDRL);
mp->b_rptr += VLAN_TAGSZ;
} else {
new_tci = VLAN_TCI(VLAN_PRI(tci), VLAN_CFI(tci), VLAN_ID_NONE);
evhp->ether_tci = htons(new_tci);
}
return (mp);
}
void
dld_str_rx_raw(void *arg, mac_resource_handle_t mrh, mblk_t *mp,
mac_header_info_t *mhip)
{
dld_str_t *dsp = (dld_str_t *)arg;
boolean_t is_ethernet = (dsp->ds_mip->mi_media == DL_ETHER);
mblk_t *next, *newmp;
ASSERT(mp != NULL);
do {
next = mp->b_next;
mp->b_next = NULL;
ASSERT(mp->b_rptr >= DB_BASE(mp) + mhip->mhi_hdrsize);
mp->b_rptr -= mhip->mhi_hdrsize;
if (!dsp->ds_native) {
newmp = mac_header_uncook(dsp->ds_mh, mp);
if (newmp == NULL) {
freemsg(mp);
goto next;
}
mp = newmp;
}
if (is_ethernet &&
mac_client_vid(dsp->ds_mch) != VLAN_ID_NONE) {
newmp = i_dld_ether_header_strip_tag(mp,
mac_client_is_vlan_vnic(dsp->ds_mch));
if (newmp == NULL) {
freemsg(mp);
goto next;
}
mp = newmp;
}
if (canputnext(dsp->ds_rq))
putnext(dsp->ds_rq, mp);
else
freemsg(mp);
next:
mp = next;
} while (mp != NULL);
}
void
dld_str_rx_fastpath(void *arg, mac_resource_handle_t mrh, mblk_t *mp,
mac_header_info_t *mhip)
{
dld_str_t *dsp = (dld_str_t *)arg;
mblk_t *next;
size_t offset = 0;
if (mhip->mhi_istagged &&
(mac_client_vid(dsp->ds_mch) == VLAN_ID_NONE) &&
((dsp->ds_sap == ETHERTYPE_VLAN) ||
(dsp->ds_promisc & DLS_PROMISC_SAP))) {
offset = VLAN_TAGSZ;
}
ASSERT(mp != NULL);
do {
next = mp->b_next;
mp->b_next = NULL;
ASSERT(mp->b_rptr >= DB_BASE(mp) + offset);
mp->b_rptr -= offset;
if (canputnext(dsp->ds_rq))
putnext(dsp->ds_rq, mp);
else
freemsg(mp);
mp = next;
} while (mp != NULL);
}
void
dld_str_rx_unitdata(void *arg, mac_resource_handle_t mrh, mblk_t *mp,
mac_header_info_t *mhip)
{
dld_str_t *dsp = (dld_str_t *)arg;
mblk_t *ud_mp;
mblk_t *next;
size_t offset = 0;
boolean_t strip_vlan = B_TRUE;
if (mhip->mhi_istagged &&
(mac_client_vid(dsp->ds_mch) == VLAN_ID_NONE) &&
((dsp->ds_sap == ETHERTYPE_VLAN) ||
(dsp->ds_promisc & DLS_PROMISC_SAP))) {
offset = VLAN_TAGSZ;
strip_vlan = B_FALSE;
}
ASSERT(mp != NULL);
do {
next = mp->b_next;
mp->b_next = NULL;
ASSERT(mp->b_rptr >= DB_BASE(mp) + mhip->mhi_hdrsize);
mp->b_rptr -= mhip->mhi_hdrsize;
if ((ud_mp = str_unitdata_ind(dsp, mp, strip_vlan)) == NULL) {
freemsgchain(mp);
return;
}
mp->b_rptr += (mhip->mhi_hdrsize - offset);
ud_mp->b_cont = mp;
if (canputnext(dsp->ds_rq))
putnext(dsp->ds_rq, ud_mp);
else
freemsg(ud_mp);
mp = next;
} while (mp != NULL);
}
static void
str_notify_sdu_size(dld_str_t *dsp, uint_t max_sdu, uint_t multicast_sdu)
{
mblk_t *mp;
dl_notify_ind_t *dlip;
if (!(dsp->ds_notifications & (DL_NOTE_SDU_SIZE|DL_NOTE_SDU_SIZE2)))
return;
if ((mp = mexchange(dsp->ds_wq, NULL, sizeof (dl_notify_ind_t),
M_PROTO, 0)) == NULL)
return;
bzero(mp->b_rptr, sizeof (dl_notify_ind_t));
dlip = (dl_notify_ind_t *)mp->b_rptr;
dlip->dl_primitive = DL_NOTIFY_IND;
if (dsp->ds_notifications & DL_NOTE_SDU_SIZE2) {
dlip->dl_notification = DL_NOTE_SDU_SIZE2;
dlip->dl_data1 = max_sdu;
dlip->dl_data2 = multicast_sdu;
} else {
dlip->dl_notification = DL_NOTE_SDU_SIZE;
dlip->dl_data = max_sdu;
}
qreply(dsp->ds_wq, mp);
}
void
dld_str_notify_ind(dld_str_t *dsp)
{
mac_notify_type_t type;
for (type = 0; type < MAC_NNOTE; type++)
str_notify(dsp, type);
}
typedef struct dl_unitdata_ind_wrapper {
dl_unitdata_ind_t dl_unitdata;
uint8_t dl_dest_addr[MAXMACADDRLEN + sizeof (uint16_t)];
uint8_t dl_src_addr[MAXMACADDRLEN + sizeof (uint16_t)];
} dl_unitdata_ind_wrapper_t;
static mblk_t *
str_unitdata_ind(dld_str_t *dsp, mblk_t *mp, boolean_t strip_vlan)
{
mblk_t *nmp;
dl_unitdata_ind_wrapper_t *dlwp;
dl_unitdata_ind_t *dlp;
mac_header_info_t mhi;
uint_t addr_length;
uint8_t *daddr;
uint8_t *saddr;
if (mac_vlan_header_info(dsp->ds_mh, mp, &mhi) != 0)
return (NULL);
if ((nmp = mexchange(dsp->ds_wq, NULL,
sizeof (dl_unitdata_ind_wrapper_t), M_PROTO,
DL_UNITDATA_IND)) == NULL)
return (NULL);
dlwp = (dl_unitdata_ind_wrapper_t *)nmp->b_rptr;
dlp = &(dlwp->dl_unitdata);
ASSERT(dlp == (dl_unitdata_ind_t *)nmp->b_rptr);
ASSERT(dlp->dl_primitive == DL_UNITDATA_IND);
addr_length = dsp->ds_mip->mi_addr_length;
daddr = dlwp->dl_dest_addr;
dlp->dl_dest_addr_offset = (uintptr_t)daddr - (uintptr_t)dlp;
bcopy(mhi.mhi_daddr, daddr, addr_length);
if (mhi.mhi_istagged && !strip_vlan)
*(uint16_t *)(daddr + addr_length) = ETHERTYPE_VLAN;
else
*(uint16_t *)(daddr + addr_length) = mhi.mhi_bindsap;
dlp->dl_dest_addr_length = addr_length + sizeof (uint16_t);
dlp->dl_group_address = (mhi.mhi_dsttype == MAC_ADDRTYPE_MULTICAST) ||
(mhi.mhi_dsttype == MAC_ADDRTYPE_BROADCAST);
if (mhi.mhi_saddr == NULL) {
dlp->dl_src_addr_offset = dlp->dl_src_addr_length = 0;
} else {
saddr = dlwp->dl_src_addr;
dlp->dl_src_addr_offset = (uintptr_t)saddr - (uintptr_t)dlp;
bcopy(mhi.mhi_saddr, saddr, addr_length);
*(uint16_t *)(saddr + addr_length) = mhi.mhi_origsap;
dlp->dl_src_addr_length = addr_length + sizeof (uint16_t);
}
return (nmp);
}
static void
str_notify_promisc_on_phys(dld_str_t *dsp)
{
mblk_t *mp;
dl_notify_ind_t *dlip;
if (!(dsp->ds_notifications & DL_NOTE_PROMISC_ON_PHYS))
return;
if ((mp = mexchange(dsp->ds_wq, NULL, sizeof (dl_notify_ind_t),
M_PROTO, 0)) == NULL)
return;
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_PROMISC_ON_PHYS;
qreply(dsp->ds_wq, mp);
}
static void
str_notify_promisc_off_phys(dld_str_t *dsp)
{
mblk_t *mp;
dl_notify_ind_t *dlip;
if (!(dsp->ds_notifications & DL_NOTE_PROMISC_OFF_PHYS))
return;
if ((mp = mexchange(dsp->ds_wq, NULL, sizeof (dl_notify_ind_t),
M_PROTO, 0)) == NULL)
return;
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_PROMISC_OFF_PHYS;
qreply(dsp->ds_wq, mp);
}
static void
str_notify_phys_addr(dld_str_t *dsp, uint_t addr_type, const uint8_t *addr)
{
mblk_t *mp;
dl_notify_ind_t *dlip;
uint_t addr_length;
uint16_t ethertype;
if (!(dsp->ds_notifications & DL_NOTE_PHYS_ADDR))
return;
addr_length = dsp->ds_mip->mi_addr_length;
if ((mp = mexchange(dsp->ds_wq, NULL,
sizeof (dl_notify_ind_t) + addr_length + sizeof (uint16_t),
M_PROTO, 0)) == NULL)
return;
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_PHYS_ADDR;
dlip->dl_data = addr_type;
dlip->dl_addr_offset = sizeof (dl_notify_ind_t);
dlip->dl_addr_length = addr_length + sizeof (uint16_t);
bcopy(addr, &dlip[1], addr_length);
ethertype = (dsp->ds_sap < ETHERTYPE_802_MIN) ? 0 : dsp->ds_sap;
*(uint16_t *)((uchar_t *)(dlip + 1) + addr_length) = ethertype;
qreply(dsp->ds_wq, mp);
}
static void
str_notify_link_up(dld_str_t *dsp)
{
mblk_t *mp;
dl_notify_ind_t *dlip;
if (!(dsp->ds_notifications & DL_NOTE_LINK_UP))
return;
if ((mp = mexchange(dsp->ds_wq, NULL, sizeof (dl_notify_ind_t),
M_PROTO, 0)) == NULL)
return;
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_LINK_UP;
qreply(dsp->ds_wq, mp);
}
static void
str_notify_link_down(dld_str_t *dsp)
{
mblk_t *mp;
dl_notify_ind_t *dlip;
if (!(dsp->ds_notifications & DL_NOTE_LINK_DOWN))
return;
if ((mp = mexchange(dsp->ds_wq, NULL, sizeof (dl_notify_ind_t),
M_PROTO, 0)) == NULL)
return;
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_LINK_DOWN;
qreply(dsp->ds_wq, mp);
}
static void
str_notify_speed(dld_str_t *dsp, uint32_t speed)
{
mblk_t *mp;
dl_notify_ind_t *dlip;
if (!(dsp->ds_notifications & DL_NOTE_SPEED))
return;
if ((mp = mexchange(dsp->ds_wq, NULL, sizeof (dl_notify_ind_t),
M_PROTO, 0)) == NULL)
return;
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_SPEED;
dlip->dl_data = speed;
qreply(dsp->ds_wq, mp);
}
static void
str_notify_capab_reneg(dld_str_t *dsp)
{
mblk_t *mp;
dl_notify_ind_t *dlip;
if (!(dsp->ds_notifications & DL_NOTE_CAPAB_RENEG))
return;
if ((mp = mexchange(dsp->ds_wq, NULL, sizeof (dl_notify_ind_t),
M_PROTO, 0)) == NULL)
return;
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_CAPAB_RENEG;
qreply(dsp->ds_wq, mp);
}
static void
str_notify_fastpath_flush(dld_str_t *dsp)
{
mblk_t *mp;
dl_notify_ind_t *dlip;
if (!(dsp->ds_notifications & DL_NOTE_FASTPATH_FLUSH))
return;
if ((mp = mexchange(dsp->ds_wq, NULL, sizeof (dl_notify_ind_t),
M_PROTO, 0)) == NULL)
return;
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_FASTPATH_FLUSH;
qreply(dsp->ds_wq, mp);
}
static void
str_notify_allowed_ips(dld_str_t *dsp)
{
mblk_t *mp;
dl_notify_ind_t *dlip;
size_t mp_size;
mac_protect_t *mrp;
if (!(dsp->ds_notifications & DL_NOTE_ALLOWED_IPS))
return;
mp_size = sizeof (mac_protect_t) + sizeof (dl_notify_ind_t);
if ((mp = mexchange(dsp->ds_wq, NULL, mp_size, M_PROTO, 0)) == NULL)
return;
mrp = mac_protect_get(dsp->ds_mh);
bzero(mp->b_rptr, mp_size);
dlip = (dl_notify_ind_t *)mp->b_rptr;
dlip->dl_primitive = DL_NOTIFY_IND;
dlip->dl_notification = DL_NOTE_ALLOWED_IPS;
dlip->dl_data = 0;
dlip->dl_addr_offset = sizeof (dl_notify_ind_t);
dlip->dl_addr_length = sizeof (mac_protect_t);
bcopy(mrp, mp->b_rptr + sizeof (dl_notify_ind_t),
sizeof (mac_protect_t));
qreply(dsp->ds_wq, mp);
}
void
str_notify(void *arg, mac_notify_type_t type)
{
dld_str_t *dsp = (dld_str_t *)arg;
queue_t *q = dsp->ds_wq;
mac_handle_t mh = dsp->ds_mh;
mac_client_handle_t mch = dsp->ds_mch;
uint8_t addr[MAXMACADDRLEN];
switch (type) {
case MAC_NOTE_TX:
qenable(q);
break;
case MAC_NOTE_DEVPROMISC:
if (mac_promisc_get(mh))
str_notify_promisc_on_phys(dsp);
else
str_notify_promisc_off_phys(dsp);
break;
case MAC_NOTE_UNICST:
mac_unicast_primary_get(mh, addr);
str_notify_phys_addr(dsp, DL_CURR_PHYS_ADDR, addr);
break;
case MAC_NOTE_DEST:
if (mac_dst_get(dsp->ds_mh, addr))
str_notify_phys_addr(dsp, DL_CURR_DEST_ADDR, addr);
break;
case MAC_NOTE_LOWLINK:
case MAC_NOTE_LINK:
if ((type == MAC_NOTE_LOWLINK) != dsp->ds_lowlink)
break;
switch (mac_client_stat_get(mch, dsp->ds_lowlink ?
MAC_STAT_LOWLINK_STATE : MAC_STAT_LINK_STATE)) {
case LINK_STATE_UP: {
uint64_t speed;
str_notify_link_up(dsp);
speed = mac_stat_get(mh, MAC_STAT_IFSPEED);
str_notify_speed(dsp, (uint32_t)(speed / 1000ull));
break;
}
case LINK_STATE_DOWN:
str_notify_link_down(dsp);
break;
default:
break;
}
break;
case MAC_NOTE_CAPAB_CHG:
str_notify_capab_reneg(dsp);
break;
case MAC_NOTE_SDU_SIZE: {
uint_t max_sdu;
uint_t multicast_sdu;
mac_sdu_get2(dsp->ds_mh, NULL, &max_sdu, &multicast_sdu);
str_notify_sdu_size(dsp, max_sdu, multicast_sdu);
break;
}
case MAC_NOTE_FASTPATH_FLUSH:
str_notify_fastpath_flush(dsp);
break;
case MAC_NOTE_MARGIN:
break;
case MAC_NOTE_ALLOWED_IPS:
str_notify_allowed_ips(dsp);
break;
default:
ASSERT(B_FALSE);
break;
}
}
static void
dld_wput_nondata_task(void *arg)
{
dld_str_t *dsp = arg;
mblk_t *mp;
mutex_enter(&dsp->ds_lock);
while (dsp->ds_pending_head != NULL) {
mp = dsp->ds_pending_head;
dsp->ds_pending_head = mp->b_next;
mp->b_next = NULL;
if (dsp->ds_pending_head == NULL)
dsp->ds_pending_tail = NULL;
mutex_exit(&dsp->ds_lock);
switch (DB_TYPE(mp)) {
case M_PROTO:
case M_PCPROTO:
dld_proto(dsp, mp);
break;
case M_IOCTL:
dld_ioc(dsp, mp);
break;
default:
ASSERT(0);
}
mutex_enter(&dsp->ds_lock);
}
ASSERT(dsp->ds_pending_tail == NULL);
dsp->ds_dlpi_pending = 0;
cv_broadcast(&dsp->ds_dlpi_pending_cv);
mutex_exit(&dsp->ds_lock);
}
static void
dld_taskq_dispatch(void)
{
callb_cpr_t cprinfo;
dld_str_t *dsp;
CALLB_CPR_INIT(&cprinfo, &dld_taskq_lock, callb_generic_cpr,
"dld_taskq_dispatch");
mutex_enter(&dld_taskq_lock);
while (!dld_taskq_quit) {
dsp = list_head(&dld_taskq_list);
while (dsp != NULL) {
list_remove(&dld_taskq_list, dsp);
mutex_exit(&dld_taskq_lock);
VERIFY(taskq_dispatch(dld_taskq, dld_wput_nondata_task,
dsp, TQ_SLEEP) != TASKQID_INVALID);
mutex_enter(&dld_taskq_lock);
dsp = list_head(&dld_taskq_list);
}
CALLB_CPR_SAFE_BEGIN(&cprinfo);
cv_wait(&dld_taskq_cv, &dld_taskq_lock);
CALLB_CPR_SAFE_END(&cprinfo, &dld_taskq_lock);
}
dld_taskq_done = B_TRUE;
cv_signal(&dld_taskq_cv);
CALLB_CPR_EXIT(&cprinfo);
thread_exit();
}
static void
dld_wput_nondata(dld_str_t *dsp, mblk_t *mp)
{
ASSERT(mp->b_next == NULL);
mutex_enter(&dsp->ds_lock);
if (dsp->ds_pending_head != NULL) {
ASSERT(dsp->ds_dlpi_pending);
dsp->ds_pending_tail->b_next = mp;
dsp->ds_pending_tail = mp;
mutex_exit(&dsp->ds_lock);
return;
}
ASSERT(dsp->ds_pending_tail == NULL);
dsp->ds_pending_head = dsp->ds_pending_tail = mp;
if (dsp->ds_dlpi_pending) {
mutex_exit(&dsp->ds_lock);
return;
}
dsp->ds_dlpi_pending = 1;
mutex_exit(&dsp->ds_lock);
if (taskq_dispatch(dld_taskq, dld_wput_nondata_task, dsp,
TQ_NOSLEEP) != TASKQID_INVALID)
return;
mutex_enter(&dld_taskq_lock);
list_insert_tail(&dld_taskq_list, dsp);
cv_signal(&dld_taskq_cv);
mutex_exit(&dld_taskq_lock);
}
static void
dld_ioc(dld_str_t *dsp, mblk_t *mp)
{
uint_t cmd;
cmd = ((struct iocblk *)mp->b_rptr)->ioc_cmd;
ASSERT(dsp->ds_type == DLD_DLPI);
switch (cmd) {
case DLIOCNATIVE:
ioc_native(dsp, mp);
break;
case DLIOCMARGININFO:
ioc_margin(dsp, mp);
break;
case DLIOCRAW:
ioc_raw(dsp, mp);
break;
case DLIOCHDRINFO:
ioc_fast(dsp, mp);
break;
case DLIOCLOWLINK:
ioc_lowlink(dsp, mp);
break;
default:
ioc(dsp, mp);
}
}
static void
ioc_native(dld_str_t *dsp, mblk_t *mp)
{
queue_t *q = dsp->ds_wq;
const mac_info_t *mip = dsp->ds_mip;
if (!dsp->ds_native && mip->mi_media != mip->mi_nativemedia)
dsp->ds_native = B_TRUE;
if (dsp->ds_native)
miocack(q, mp, 0, mip->mi_nativemedia);
else
miocnak(q, mp, 0, ENOTSUP);
}
static void
ioc_margin(dld_str_t *dsp, mblk_t *mp)
{
queue_t *q = dsp->ds_wq;
uint32_t margin;
int err;
if (dsp->ds_dlstate == DL_UNATTACHED) {
err = EINVAL;
goto failed;
}
if ((err = miocpullup(mp, sizeof (uint32_t))) != 0)
goto failed;
mac_margin_get(dsp->ds_mh, &margin);
*((uint32_t *)mp->b_cont->b_rptr) = margin;
miocack(q, mp, sizeof (uint32_t), 0);
return;
failed:
miocnak(q, mp, 0, err);
}
static void
ioc_raw(dld_str_t *dsp, mblk_t *mp)
{
queue_t *q = dsp->ds_wq;
mac_perim_handle_t mph;
if (dsp->ds_mh == NULL) {
dsp->ds_mode = DLD_RAW;
miocack(q, mp, 0, 0);
return;
}
mac_perim_enter_by_mh(dsp->ds_mh, &mph);
if (dsp->ds_polling || dsp->ds_direct) {
mac_perim_exit(mph);
miocnak(q, mp, 0, EPROTO);
return;
}
if (dsp->ds_mode != DLD_RAW && dsp->ds_dlstate == DL_IDLE) {
dls_rx_set(dsp, dld_str_rx_raw, dsp);
}
dsp->ds_mode = DLD_RAW;
mac_perim_exit(mph);
miocack(q, mp, 0, 0);
}
static void
ioc_fast(dld_str_t *dsp, mblk_t *mp)
{
dl_unitdata_req_t *dlp;
off_t off;
size_t len;
const uint8_t *addr;
uint16_t sap;
mblk_t *nmp;
mblk_t *hmp;
uint_t addr_length;
queue_t *q = dsp->ds_wq;
int err;
mac_perim_handle_t mph;
if (dld_opt & DLD_OPT_NO_FASTPATH) {
err = ENOTSUP;
goto failed;
}
if (((struct iocblk *)mp->b_rptr)->ioc_cr != kcred) {
err = EINVAL;
goto failed;
}
nmp = mp->b_cont;
if (nmp == NULL || MBLKL(nmp) < sizeof (dl_unitdata_req_t) ||
(dlp = (dl_unitdata_req_t *)nmp->b_rptr,
dlp->dl_primitive != DL_UNITDATA_REQ)) {
err = EINVAL;
goto failed;
}
off = dlp->dl_dest_addr_offset;
len = dlp->dl_dest_addr_length;
if (!MBLKIN(nmp, off, len)) {
err = EINVAL;
goto failed;
}
if (dsp->ds_dlstate != DL_IDLE) {
err = ENOTSUP;
goto failed;
}
addr_length = dsp->ds_mip->mi_addr_length;
if (len != addr_length + sizeof (uint16_t)) {
err = EINVAL;
goto failed;
}
addr = nmp->b_rptr + off;
sap = *(uint16_t *)(nmp->b_rptr + off + addr_length);
if ((hmp = dls_header(dsp, addr, sap, 0, NULL)) == NULL) {
err = ENOMEM;
goto failed;
}
mac_perim_enter_by_mh(dsp->ds_mh, &mph);
if (dsp->ds_mode != DLD_FASTPATH) {
if (!dsp->ds_polling && !dsp->ds_direct)
dls_rx_set(dsp, dld_str_rx_fastpath, dsp);
dsp->ds_mode = DLD_FASTPATH;
}
mac_perim_exit(mph);
freemsg(nmp->b_cont);
nmp->b_cont = hmp;
miocack(q, mp, MBLKL(nmp) + MBLKL(hmp), 0);
return;
failed:
miocnak(q, mp, 0, err);
}
static void
ioc_lowlink(dld_str_t *dsp, mblk_t *mp)
{
queue_t *q = dsp->ds_wq;
int err;
if ((err = miocpullup(mp, sizeof (int))) != 0) {
miocnak(q, mp, 0, err);
} else {
dsp->ds_lowlink = *(boolean_t *)mp->b_cont->b_rptr;
miocack(q, mp, 0, 0);
}
}
static void
ioc(dld_str_t *dsp, mblk_t *mp)
{
queue_t *q = dsp->ds_wq;
if (dsp->ds_dlstate == DL_UNATTACHED) {
miocnak(q, mp, 0, EINVAL);
return;
}
mac_ioctl(dsp->ds_mh, q, mp);
}