/* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include int softmac_send_notify_req(softmac_lower_t *slp, uint32_t notifications) { mblk_t *reqmp; /* * create notify req message and send it down */ reqmp = mexchange(NULL, NULL, DL_NOTIFY_REQ_SIZE, M_PROTO, DL_NOTIFY_REQ); if (reqmp == NULL) return (ENOMEM); ((dl_notify_req_t *)reqmp->b_rptr)->dl_notifications = notifications; return (softmac_proto_tx(slp, reqmp, NULL)); } int softmac_send_bind_req(softmac_lower_t *slp, uint_t sap) { dl_bind_req_t *bind; mblk_t *reqmp; /* * create bind req message and send it down */ reqmp = mexchange(NULL, NULL, DL_BIND_REQ_SIZE, M_PROTO, DL_BIND_REQ); if (reqmp == NULL) return (ENOMEM); bind = (dl_bind_req_t *)reqmp->b_rptr; bind->dl_sap = sap; bind->dl_conn_mgmt = 0; bind->dl_max_conind = 0; bind->dl_xidtest_flg = 0; bind->dl_service_mode = DL_CLDLS; return (softmac_proto_tx(slp, reqmp, NULL)); } int softmac_send_promisc_req(softmac_lower_t *slp, t_uscalar_t level, boolean_t on) { mblk_t *reqmp; size_t size; t_uscalar_t dl_prim; /* * create promisc message and send it down */ if (on) { dl_prim = DL_PROMISCON_REQ; size = DL_PROMISCON_REQ_SIZE; } else { dl_prim = DL_PROMISCOFF_REQ; size = DL_PROMISCOFF_REQ_SIZE; } reqmp = mexchange(NULL, NULL, size, M_PROTO, dl_prim); if (reqmp == NULL) return (ENOMEM); if (on) ((dl_promiscon_req_t *)reqmp->b_rptr)->dl_level = level; else ((dl_promiscoff_req_t *)reqmp->b_rptr)->dl_level = level; return (softmac_proto_tx(slp, reqmp, NULL)); } int softmac_m_promisc(void *arg, boolean_t on) { softmac_t *softmac = arg; softmac_lower_t *slp = softmac->smac_lower; ASSERT(slp != NULL); return (softmac_send_promisc_req(slp, DL_PROMISC_PHYS, on)); } int softmac_m_multicst(void *arg, boolean_t add, const uint8_t *mca) { softmac_t *softmac = arg; softmac_lower_t *slp; dl_enabmulti_req_t *enabmulti; dl_disabmulti_req_t *disabmulti; mblk_t *reqmp; t_uscalar_t dl_prim; uint32_t size, addr_length; /* * create multicst message and send it down */ addr_length = softmac->smac_addrlen; if (add) { size = sizeof (dl_enabmulti_req_t) + addr_length; dl_prim = DL_ENABMULTI_REQ; } else { size = sizeof (dl_disabmulti_req_t) + addr_length; dl_prim = DL_DISABMULTI_REQ; } reqmp = mexchange(NULL, NULL, size, M_PROTO, dl_prim); if (reqmp == NULL) return (ENOMEM); if (add) { enabmulti = (dl_enabmulti_req_t *)reqmp->b_rptr; enabmulti->dl_addr_offset = sizeof (dl_enabmulti_req_t); enabmulti->dl_addr_length = addr_length; (void) memcpy(&enabmulti[1], mca, addr_length); } else { disabmulti = (dl_disabmulti_req_t *)reqmp->b_rptr; disabmulti->dl_addr_offset = sizeof (dl_disabmulti_req_t); disabmulti->dl_addr_length = addr_length; (void) memcpy(&disabmulti[1], mca, addr_length); } slp = softmac->smac_lower; ASSERT(slp != NULL); return (softmac_proto_tx(slp, reqmp, NULL)); } int softmac_m_unicst(void *arg, const uint8_t *macaddr) { softmac_t *softmac = arg; softmac_lower_t *slp; dl_set_phys_addr_req_t *phyaddr; mblk_t *reqmp; size_t size; /* * create set_phys_addr message and send it down */ size = DL_SET_PHYS_ADDR_REQ_SIZE + softmac->smac_addrlen; reqmp = mexchange(NULL, NULL, size, M_PROTO, DL_SET_PHYS_ADDR_REQ); if (reqmp == NULL) return (ENOMEM); phyaddr = (dl_set_phys_addr_req_t *)reqmp->b_rptr; phyaddr->dl_addr_offset = sizeof (dl_set_phys_addr_req_t); phyaddr->dl_addr_length = softmac->smac_addrlen; (void) memcpy(&phyaddr[1], macaddr, softmac->smac_addrlen); slp = softmac->smac_lower; ASSERT(slp != NULL); return (softmac_proto_tx(slp, reqmp, NULL)); } void softmac_m_ioctl(void *arg, queue_t *wq, mblk_t *mp) { softmac_lower_t *slp = ((softmac_t *)arg)->smac_lower; mblk_t *ackmp; ASSERT(slp != NULL); softmac_ioctl_tx(slp, mp, &ackmp); qreply(wq, ackmp); } static void softmac_process_notify_ind(softmac_t *softmac, mblk_t *mp) { dl_notify_ind_t *dlnip = (dl_notify_ind_t *)mp->b_rptr; uint_t addroff, addrlen; ASSERT(dlnip->dl_primitive == DL_NOTIFY_IND); switch (dlnip->dl_notification) { case DL_NOTE_PHYS_ADDR: if (dlnip->dl_data != DL_CURR_PHYS_ADDR) break; addroff = dlnip->dl_addr_offset; addrlen = dlnip->dl_addr_length - softmac->smac_saplen; if (addroff == 0 || addrlen != softmac->smac_addrlen || !MBLKIN(mp, addroff, addrlen)) { cmn_err(CE_NOTE, "softmac: got malformed " "DL_NOTIFY_IND; length/offset %d/%d", addrlen, addroff); break; } mac_unicst_update(softmac->smac_mh, mp->b_rptr + addroff); break; case DL_NOTE_LINK_UP: mac_link_update(softmac->smac_mh, LINK_STATE_UP); break; case DL_NOTE_LINK_DOWN: mac_link_update(softmac->smac_mh, LINK_STATE_DOWN); break; } freemsg(mp); } void softmac_notify_thread(void *arg) { softmac_t *softmac = arg; callb_cpr_t cprinfo; CALLB_CPR_INIT(&cprinfo, &softmac->smac_mutex, callb_generic_cpr, "softmac_notify_thread"); mutex_enter(&softmac->smac_mutex); /* * Quit the thread if smac_mh is unregistered. */ while (softmac->smac_mh != NULL && !(softmac->smac_flags & SOFTMAC_NOTIFY_QUIT)) { mblk_t *mp, *nextmp; if ((mp = softmac->smac_notify_head) == NULL) { CALLB_CPR_SAFE_BEGIN(&cprinfo); cv_wait(&softmac->smac_cv, &softmac->smac_mutex); CALLB_CPR_SAFE_END(&cprinfo, &softmac->smac_mutex); continue; } softmac->smac_notify_head = softmac->smac_notify_tail = NULL; mutex_exit(&softmac->smac_mutex); while (mp != NULL) { nextmp = mp->b_next; mp->b_next = NULL; softmac_process_notify_ind(softmac, mp); mp = nextmp; } mutex_enter(&softmac->smac_mutex); } /* * The softmac is being destroyed, simply free all of the DL_NOTIFY_IND * messages left in the queue which did not have the chance to be * processed. */ freemsgchain(softmac->smac_notify_head); softmac->smac_notify_head = softmac->smac_notify_tail = NULL; softmac->smac_notify_thread = NULL; cv_broadcast(&softmac->smac_cv); CALLB_CPR_EXIT(&cprinfo); thread_exit(); } static void softmac_enqueue_notify_ind(queue_t *rq, mblk_t *mp) { softmac_lower_t *slp = rq->q_ptr; softmac_t *softmac = slp->sl_softmac; mutex_enter(&softmac->smac_mutex); if (softmac->smac_notify_tail == NULL) { softmac->smac_notify_head = softmac->smac_notify_tail = mp; } else { softmac->smac_notify_tail->b_next = mp; softmac->smac_notify_tail = mp; } cv_broadcast(&softmac->smac_cv); mutex_exit(&softmac->smac_mutex); } static void softmac_process_dlpi(softmac_lower_t *slp, mblk_t *mp, uint_t minlen, t_uscalar_t reqprim) { const char *ackname; ackname = dl_primstr(((union DL_primitives *)mp->b_rptr)->dl_primitive); if (MBLKL(mp) < minlen) { cmn_err(CE_WARN, "softmac: got short %s", ackname); freemsg(mp); return; } mutex_enter(&slp->sl_mutex); if (slp->sl_pending_prim != reqprim) { cmn_err(CE_NOTE, "softmac: got unexpected %s", ackname); mutex_exit(&slp->sl_mutex); freemsg(mp); return; } slp->sl_pending_prim = DL_PRIM_INVAL; slp->sl_ack_mp = mp; cv_signal(&slp->sl_cv); mutex_exit(&slp->sl_mutex); } void softmac_rput_process_proto(queue_t *rq, mblk_t *mp) { softmac_lower_t *slp = rq->q_ptr; union DL_primitives *dlp = (union DL_primitives *)mp->b_rptr; ssize_t len = MBLKL(mp); const char *primstr; if (len < sizeof (t_uscalar_t)) { cmn_err(CE_WARN, "softmac: got runt DLPI message"); goto exit; } primstr = dl_primstr(dlp->dl_primitive); switch (dlp->dl_primitive) { case DL_OK_ACK: if (len < DL_OK_ACK_SIZE) goto runt; softmac_process_dlpi(slp, mp, DL_OK_ACK_SIZE, dlp->ok_ack.dl_correct_primitive); return; case DL_ERROR_ACK: if (len < DL_ERROR_ACK_SIZE) goto runt; softmac_process_dlpi(slp, mp, DL_ERROR_ACK_SIZE, dlp->error_ack.dl_error_primitive); return; case DL_NOTIFY_IND: if (len < DL_NOTIFY_IND_SIZE) goto runt; /* * Enqueue all the DL_NOTIFY_IND messages and process them * in another separate thread to avoid deadlock. Here is an * example of the deadlock scenario: * * Thread A: mac_promisc_set()->softmac_m_promisc() * * The softmac driver waits for the ACK of the * DL_PROMISC_PHYS request with the MAC perimeter; * * Thread B: * * The driver handles the DL_PROMISC_PHYS request. Before * it sends back the ACK, it could first send a * DL_NOTE_PROMISC_ON_PHYS notification. * * Since DL_NOTIFY_IND could eventually cause softmac to call * mac_xxx_update(), which requires MAC perimeter, this would * cause deadlock between the two threads. Enqueuing the * DL_NOTIFY_IND message and defer its processing would * avoid the potential deadlock. */ softmac_enqueue_notify_ind(rq, mp); return; case DL_NOTIFY_ACK: softmac_process_dlpi(slp, mp, DL_NOTIFY_ACK_SIZE, DL_NOTIFY_REQ); return; case DL_CAPABILITY_ACK: softmac_process_dlpi(slp, mp, DL_CAPABILITY_ACK_SIZE, DL_CAPABILITY_REQ); return; case DL_BIND_ACK: softmac_process_dlpi(slp, mp, DL_BIND_ACK_SIZE, DL_BIND_REQ); return; case DL_CONTROL_ACK: softmac_process_dlpi(slp, mp, DL_CONTROL_ACK_SIZE, DL_CONTROL_REQ); return; case DL_UNITDATA_IND: case DL_PHYS_ADDR_ACK: /* * a. Because the stream is in DLIOCRAW mode, * DL_UNITDATA_IND messages are not expected. * b. The lower stream should not receive DL_PHYS_ADDR_REQ, * so DL_PHYS_ADDR_ACK messages are also unexpected. */ default: cmn_err(CE_WARN, "softmac: got unexpected %s", primstr); break; } exit: freemsg(mp); return; runt: cmn_err(CE_WARN, "softmac: got runt %s", primstr); freemsg(mp); } void softmac_rput_process_notdata(queue_t *rq, mblk_t *mp) { softmac_lower_t *slp = rq->q_ptr; switch (DB_TYPE(mp)) { case M_PROTO: case M_PCPROTO: softmac_rput_process_proto(rq, mp); break; case M_FLUSH: if (*mp->b_rptr & FLUSHR) flushq(rq, FLUSHDATA); if (*mp->b_rptr & FLUSHW) flushq(OTHERQ(rq), FLUSHDATA); putnext(rq, mp); break; case M_IOCACK: case M_IOCNAK: case M_COPYIN: case M_COPYOUT: mutex_enter(&slp->sl_mutex); if (!slp->sl_pending_ioctl) { mutex_exit(&slp->sl_mutex); cmn_err(CE_NOTE, "softmac: got unexpected mblk " "type 0x%x", DB_TYPE(mp)); freemsg(mp); break; } slp->sl_pending_ioctl = B_FALSE; slp->sl_ack_mp = mp; cv_broadcast(&slp->sl_cv); mutex_exit(&slp->sl_mutex); return; default: cmn_err(CE_NOTE, "softmac: got unsupported mblk type 0x%x", DB_TYPE(mp)); freemsg(mp); break; } }