1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <sys/stropts.h>
27 #include <sys/strsubr.h>
28 #include <sys/callb.h>
29 #include <sys/softmac_impl.h>
30 
31 int
32 softmac_send_notify_req(softmac_lower_t *slp, uint32_t notifications)
33 {
34 	mblk_t		*reqmp;
35 
36 	/*
37 	 * create notify req message and send it down
38 	 */
39 	reqmp = mexchange(NULL, NULL, DL_NOTIFY_REQ_SIZE, M_PROTO,
40 	    DL_NOTIFY_REQ);
41 	if (reqmp == NULL)
42 		return (ENOMEM);
43 
44 	((dl_notify_req_t *)reqmp->b_rptr)->dl_notifications = notifications;
45 
46 	return (softmac_proto_tx(slp, reqmp, NULL));
47 }
48 
49 int
50 softmac_send_bind_req(softmac_lower_t *slp, uint_t sap)
51 {
52 	dl_bind_req_t	*bind;
53 	mblk_t		*reqmp;
54 
55 	/*
56 	 * create bind req message and send it down
57 	 */
58 	reqmp = mexchange(NULL, NULL, DL_BIND_REQ_SIZE, M_PROTO, DL_BIND_REQ);
59 	if (reqmp == NULL)
60 		return (ENOMEM);
61 
62 	bind = (dl_bind_req_t *)reqmp->b_rptr;
63 	bind->dl_sap = sap;
64 	bind->dl_conn_mgmt = 0;
65 	bind->dl_max_conind = 0;
66 	bind->dl_xidtest_flg = 0;
67 	bind->dl_service_mode = DL_CLDLS;
68 
69 	return (softmac_proto_tx(slp, reqmp, NULL));
70 }
71 
72 int
73 softmac_send_promisc_req(softmac_lower_t *slp, t_uscalar_t level, boolean_t on)
74 {
75 	mblk_t		*reqmp;
76 	size_t		size;
77 	t_uscalar_t	dl_prim;
78 
79 	/*
80 	 * create promisc message and send it down
81 	 */
82 	if (on) {
83 		dl_prim = DL_PROMISCON_REQ;
84 		size = DL_PROMISCON_REQ_SIZE;
85 	} else {
86 		dl_prim = DL_PROMISCOFF_REQ;
87 		size = DL_PROMISCOFF_REQ_SIZE;
88 	}
89 
90 	reqmp = mexchange(NULL, NULL, size, M_PROTO, dl_prim);
91 	if (reqmp == NULL)
92 		return (ENOMEM);
93 
94 	if (on)
95 		((dl_promiscon_req_t *)reqmp->b_rptr)->dl_level = level;
96 	else
97 		((dl_promiscoff_req_t *)reqmp->b_rptr)->dl_level = level;
98 
99 	return (softmac_proto_tx(slp, reqmp, NULL));
100 }
101 
102 int
103 softmac_m_promisc(void *arg, boolean_t on)
104 {
105 	softmac_t		*softmac = arg;
106 	softmac_lower_t		*slp = softmac->smac_lower;
107 
108 	ASSERT(slp != NULL);
109 	return (softmac_send_promisc_req(slp, DL_PROMISC_PHYS, on));
110 }
111 
112 int
113 softmac_m_multicst(void *arg, boolean_t add, const uint8_t *mca)
114 {
115 	softmac_t		*softmac = arg;
116 	softmac_lower_t		*slp;
117 	dl_enabmulti_req_t	*enabmulti;
118 	dl_disabmulti_req_t	*disabmulti;
119 	mblk_t			*reqmp;
120 	t_uscalar_t		dl_prim;
121 	uint32_t		size, addr_length;
122 
123 	/*
124 	 * create multicst message and send it down
125 	 */
126 	addr_length = softmac->smac_addrlen;
127 	if (add) {
128 		size = sizeof (dl_enabmulti_req_t) + addr_length;
129 		dl_prim = DL_ENABMULTI_REQ;
130 	} else {
131 		size = sizeof (dl_disabmulti_req_t) + addr_length;
132 		dl_prim = DL_DISABMULTI_REQ;
133 	}
134 
135 	reqmp = mexchange(NULL, NULL, size, M_PROTO, dl_prim);
136 	if (reqmp == NULL)
137 		return (ENOMEM);
138 
139 	if (add) {
140 		enabmulti = (dl_enabmulti_req_t *)reqmp->b_rptr;
141 		enabmulti->dl_addr_offset = sizeof (dl_enabmulti_req_t);
142 		enabmulti->dl_addr_length = addr_length;
143 		(void) memcpy(&enabmulti[1], mca, addr_length);
144 	} else {
145 		disabmulti = (dl_disabmulti_req_t *)reqmp->b_rptr;
146 		disabmulti->dl_addr_offset = sizeof (dl_disabmulti_req_t);
147 		disabmulti->dl_addr_length = addr_length;
148 		(void) memcpy(&disabmulti[1], mca, addr_length);
149 	}
150 
151 	slp = softmac->smac_lower;
152 	ASSERT(slp != NULL);
153 	return (softmac_proto_tx(slp, reqmp, NULL));
154 }
155 
156 int
157 softmac_m_unicst(void *arg, const uint8_t *macaddr)
158 {
159 	softmac_t		*softmac = arg;
160 	softmac_lower_t		*slp;
161 	dl_set_phys_addr_req_t	*phyaddr;
162 	mblk_t			*reqmp;
163 	size_t			size;
164 
165 	/*
166 	 * create set_phys_addr message and send it down
167 	 */
168 	size = DL_SET_PHYS_ADDR_REQ_SIZE + softmac->smac_addrlen;
169 	reqmp = mexchange(NULL, NULL, size, M_PROTO, DL_SET_PHYS_ADDR_REQ);
170 	if (reqmp == NULL)
171 		return (ENOMEM);
172 
173 	phyaddr = (dl_set_phys_addr_req_t *)reqmp->b_rptr;
174 	phyaddr->dl_addr_offset = sizeof (dl_set_phys_addr_req_t);
175 	phyaddr->dl_addr_length = softmac->smac_addrlen;
176 	(void) memcpy(&phyaddr[1], macaddr, softmac->smac_addrlen);
177 
178 	slp = softmac->smac_lower;
179 	ASSERT(slp != NULL);
180 	return (softmac_proto_tx(slp, reqmp, NULL));
181 }
182 
183 void
184 softmac_m_ioctl(void *arg, queue_t *wq, mblk_t *mp)
185 {
186 	softmac_lower_t *slp = ((softmac_t *)arg)->smac_lower;
187 	mblk_t *ackmp;
188 
189 	ASSERT(slp != NULL);
190 	softmac_ioctl_tx(slp, mp, &ackmp);
191 	qreply(wq, ackmp);
192 }
193 
194 static void
195 softmac_process_notify_ind(softmac_t *softmac, mblk_t *mp)
196 {
197 	dl_notify_ind_t	*dlnip = (dl_notify_ind_t *)mp->b_rptr;
198 	uint_t		addroff, addrlen;
199 
200 	ASSERT(dlnip->dl_primitive == DL_NOTIFY_IND);
201 
202 	switch (dlnip->dl_notification) {
203 	case DL_NOTE_PHYS_ADDR:
204 		if (dlnip->dl_data != DL_CURR_PHYS_ADDR)
205 			break;
206 
207 		addroff = dlnip->dl_addr_offset;
208 		addrlen = dlnip->dl_addr_length - softmac->smac_saplen;
209 		if (addroff == 0 || addrlen != softmac->smac_addrlen ||
210 		    !MBLKIN(mp, addroff, addrlen)) {
211 			cmn_err(CE_NOTE, "softmac: got malformed "
212 			    "DL_NOTIFY_IND; length/offset %d/%d",
213 			    addrlen, addroff);
214 			break;
215 		}
216 
217 		mac_unicst_update(softmac->smac_mh, mp->b_rptr + addroff);
218 		break;
219 
220 	case DL_NOTE_LINK_UP:
221 		mac_link_update(softmac->smac_mh, LINK_STATE_UP);
222 		break;
223 
224 	case DL_NOTE_LINK_DOWN:
225 		mac_link_update(softmac->smac_mh, LINK_STATE_DOWN);
226 		break;
227 	}
228 
229 	freemsg(mp);
230 }
231 
232 void
233 softmac_notify_thread(void *arg)
234 {
235 	softmac_t	*softmac = arg;
236 	callb_cpr_t	cprinfo;
237 
238 	CALLB_CPR_INIT(&cprinfo, &softmac->smac_mutex, callb_generic_cpr,
239 	    "softmac_notify_thread");
240 
241 	mutex_enter(&softmac->smac_mutex);
242 
243 	/*
244 	 * Quit the thread if smac_mh is unregistered.
245 	 */
246 	while (softmac->smac_mh != NULL &&
247 	    !(softmac->smac_flags & SOFTMAC_NOTIFY_QUIT)) {
248 		mblk_t		*mp, *nextmp;
249 
250 		if ((mp = softmac->smac_notify_head) == NULL) {
251 			CALLB_CPR_SAFE_BEGIN(&cprinfo);
252 			cv_wait(&softmac->smac_cv, &softmac->smac_mutex);
253 			CALLB_CPR_SAFE_END(&cprinfo, &softmac->smac_mutex);
254 			continue;
255 		}
256 
257 		softmac->smac_notify_head = softmac->smac_notify_tail = NULL;
258 		mutex_exit(&softmac->smac_mutex);
259 
260 		while (mp != NULL) {
261 			nextmp = mp->b_next;
262 			mp->b_next = NULL;
263 			softmac_process_notify_ind(softmac, mp);
264 			mp = nextmp;
265 		}
266 		mutex_enter(&softmac->smac_mutex);
267 	}
268 
269 	/*
270 	 * The softmac is being destroyed, simply free all of the DL_NOTIFY_IND
271 	 * messages left in the queue which did not have the chance to be
272 	 * processed.
273 	 */
274 	freemsgchain(softmac->smac_notify_head);
275 	softmac->smac_notify_head = softmac->smac_notify_tail = NULL;
276 	softmac->smac_notify_thread = NULL;
277 	cv_broadcast(&softmac->smac_cv);
278 	CALLB_CPR_EXIT(&cprinfo);
279 	thread_exit();
280 }
281 
282 static void
283 softmac_enqueue_notify_ind(queue_t *rq, mblk_t *mp)
284 {
285 	softmac_lower_t	*slp = rq->q_ptr;
286 	softmac_t	*softmac = slp->sl_softmac;
287 
288 	mutex_enter(&softmac->smac_mutex);
289 	if (softmac->smac_notify_tail == NULL) {
290 		softmac->smac_notify_head = softmac->smac_notify_tail = mp;
291 	} else {
292 		softmac->smac_notify_tail->b_next = mp;
293 		softmac->smac_notify_tail = mp;
294 	}
295 	cv_broadcast(&softmac->smac_cv);
296 	mutex_exit(&softmac->smac_mutex);
297 }
298 
299 static void
300 softmac_process_dlpi(softmac_lower_t *slp, mblk_t *mp, uint_t minlen,
301     t_uscalar_t reqprim)
302 {
303 	const char *ackname;
304 
305 	ackname = dl_primstr(((union DL_primitives *)mp->b_rptr)->dl_primitive);
306 
307 	if (MBLKL(mp) < minlen) {
308 		cmn_err(CE_WARN, "softmac: got short %s", ackname);
309 		freemsg(mp);
310 		return;
311 	}
312 
313 	mutex_enter(&slp->sl_mutex);
314 	if (slp->sl_pending_prim != reqprim) {
315 		cmn_err(CE_NOTE, "softmac: got unexpected %s", ackname);
316 		mutex_exit(&slp->sl_mutex);
317 		freemsg(mp);
318 		return;
319 	}
320 
321 	slp->sl_pending_prim = DL_PRIM_INVAL;
322 	slp->sl_ack_mp = mp;
323 	cv_signal(&slp->sl_cv);
324 	mutex_exit(&slp->sl_mutex);
325 }
326 
327 void
328 softmac_rput_process_proto(queue_t *rq, mblk_t *mp)
329 {
330 	softmac_lower_t		*slp = rq->q_ptr;
331 	union DL_primitives	*dlp = (union DL_primitives *)mp->b_rptr;
332 	ssize_t			len = MBLKL(mp);
333 	const char		*primstr;
334 
335 	if (len < sizeof (t_uscalar_t)) {
336 		cmn_err(CE_WARN, "softmac: got runt DLPI message");
337 		goto exit;
338 	}
339 
340 	primstr = dl_primstr(dlp->dl_primitive);
341 
342 	switch (dlp->dl_primitive) {
343 	case DL_OK_ACK:
344 		if (len < DL_OK_ACK_SIZE)
345 			goto runt;
346 
347 		softmac_process_dlpi(slp, mp, DL_OK_ACK_SIZE,
348 		    dlp->ok_ack.dl_correct_primitive);
349 		return;
350 
351 	case DL_ERROR_ACK:
352 		if (len < DL_ERROR_ACK_SIZE)
353 			goto runt;
354 
355 		softmac_process_dlpi(slp, mp, DL_ERROR_ACK_SIZE,
356 		    dlp->error_ack.dl_error_primitive);
357 		return;
358 
359 	case DL_NOTIFY_IND:
360 		if (len < DL_NOTIFY_IND_SIZE)
361 			goto runt;
362 
363 		/*
364 		 * Enqueue all the DL_NOTIFY_IND messages and process them
365 		 * in another separate thread to avoid deadlock. Here is an
366 		 * example of the deadlock scenario:
367 		 *
368 		 * Thread A: mac_promisc_set()->softmac_m_promisc()
369 		 *
370 		 *   The softmac driver waits for the ACK of the
371 		 *   DL_PROMISC_PHYS request with the MAC perimeter;
372 		 *
373 		 * Thread B:
374 		 *
375 		 *   The driver handles the DL_PROMISC_PHYS request. Before
376 		 *   it sends back the ACK, it could first send a
377 		 *   DL_NOTE_PROMISC_ON_PHYS notification.
378 		 *
379 		 * Since DL_NOTIFY_IND could eventually cause softmac to call
380 		 * mac_xxx_update(), which requires MAC perimeter, this would
381 		 * cause deadlock between the two threads. Enqueuing the
382 		 * DL_NOTIFY_IND message and defer its processing would
383 		 * avoid the potential deadlock.
384 		 */
385 		softmac_enqueue_notify_ind(rq, mp);
386 		return;
387 
388 	case DL_NOTIFY_ACK:
389 		softmac_process_dlpi(slp, mp, DL_NOTIFY_ACK_SIZE,
390 		    DL_NOTIFY_REQ);
391 		return;
392 
393 	case DL_CAPABILITY_ACK:
394 		softmac_process_dlpi(slp, mp, DL_CAPABILITY_ACK_SIZE,
395 		    DL_CAPABILITY_REQ);
396 		return;
397 
398 	case DL_BIND_ACK:
399 		softmac_process_dlpi(slp, mp, DL_BIND_ACK_SIZE, DL_BIND_REQ);
400 		return;
401 
402 	case DL_CONTROL_ACK:
403 		softmac_process_dlpi(slp, mp, DL_CONTROL_ACK_SIZE,
404 		    DL_CONTROL_REQ);
405 		return;
406 
407 	case DL_UNITDATA_IND:
408 	case DL_PHYS_ADDR_ACK:
409 		/*
410 		 * a. Because the stream is in DLIOCRAW mode,
411 		 *    DL_UNITDATA_IND messages are not expected.
412 		 * b. The lower stream should not receive DL_PHYS_ADDR_REQ,
413 		 *    so DL_PHYS_ADDR_ACK messages are also unexpected.
414 		 */
415 	default:
416 		cmn_err(CE_WARN, "softmac: got unexpected %s", primstr);
417 		break;
418 	}
419 exit:
420 	freemsg(mp);
421 	return;
422 runt:
423 	cmn_err(CE_WARN, "softmac: got runt %s", primstr);
424 	freemsg(mp);
425 }
426 
427 void
428 softmac_rput_process_notdata(queue_t *rq, mblk_t *mp)
429 {
430 	softmac_lower_t	*slp = rq->q_ptr;
431 
432 	switch (DB_TYPE(mp)) {
433 	case M_PROTO:
434 	case M_PCPROTO:
435 		softmac_rput_process_proto(rq, mp);
436 		break;
437 
438 	case M_FLUSH:
439 		if (*mp->b_rptr & FLUSHR)
440 			flushq(rq, FLUSHDATA);
441 		if (*mp->b_rptr & FLUSHW)
442 			flushq(OTHERQ(rq), FLUSHDATA);
443 		putnext(rq, mp);
444 		break;
445 
446 	case M_IOCACK:
447 	case M_IOCNAK:
448 	case M_COPYIN:
449 	case M_COPYOUT:
450 		mutex_enter(&slp->sl_mutex);
451 		if (!slp->sl_pending_ioctl) {
452 			mutex_exit(&slp->sl_mutex);
453 			cmn_err(CE_NOTE, "softmac: got unexpected mblk "
454 			    "type 0x%x", DB_TYPE(mp));
455 			freemsg(mp);
456 			break;
457 		}
458 
459 		slp->sl_pending_ioctl = B_FALSE;
460 		slp->sl_ack_mp = mp;
461 		cv_broadcast(&slp->sl_cv);
462 		mutex_exit(&slp->sl_mutex);
463 		return;
464 
465 	default:
466 		cmn_err(CE_NOTE, "softmac: got unsupported mblk type 0x%x",
467 		    DB_TYPE(mp));
468 		freemsg(mp);
469 		break;
470 	}
471 }
472