xref: /illumos-gate/usr/src/uts/common/io/softmac/softmac_ctl.c (revision 67dbe2be0c0f1e2eb428b89088bb5667e8f0b9f6)
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 2009 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_unbind_req(softmac_lower_t *slp)
74 {
75 	mblk_t			*reqmp;
76 
77 	/*
78 	 * create unbind req message and send it down
79 	 */
80 	reqmp = mexchange(NULL, NULL, DL_UNBIND_REQ_SIZE, M_PROTO,
81 	    DL_UNBIND_REQ);
82 	if (reqmp == NULL)
83 		return (ENOMEM);
84 
85 	return (softmac_proto_tx(slp, reqmp, NULL));
86 }
87 
88 int
89 softmac_send_promisc_req(softmac_lower_t *slp, t_uscalar_t level, boolean_t on)
90 {
91 	mblk_t		*reqmp;
92 	size_t		size;
93 	t_uscalar_t	dl_prim;
94 
95 	/*
96 	 * create promisc message and send it down
97 	 */
98 	if (on) {
99 		dl_prim = DL_PROMISCON_REQ;
100 		size = DL_PROMISCON_REQ_SIZE;
101 	} else {
102 		dl_prim = DL_PROMISCOFF_REQ;
103 		size = DL_PROMISCOFF_REQ_SIZE;
104 	}
105 
106 	reqmp = mexchange(NULL, NULL, size, M_PROTO, dl_prim);
107 	if (reqmp == NULL)
108 		return (ENOMEM);
109 
110 	if (on)
111 		((dl_promiscon_req_t *)reqmp->b_rptr)->dl_level = level;
112 	else
113 		((dl_promiscoff_req_t *)reqmp->b_rptr)->dl_level = level;
114 
115 	return (softmac_proto_tx(slp, reqmp, NULL));
116 }
117 
118 int
119 softmac_m_promisc(void *arg, boolean_t on)
120 {
121 	softmac_t		*softmac = arg;
122 	softmac_lower_t		*slp = softmac->smac_lower;
123 
124 	ASSERT(MAC_PERIM_HELD(softmac->smac_mh));
125 	ASSERT(slp != NULL);
126 	return (softmac_send_promisc_req(slp, DL_PROMISC_PHYS, on));
127 }
128 
129 int
130 softmac_m_multicst(void *arg, boolean_t add, const uint8_t *mca)
131 {
132 	softmac_t		*softmac = arg;
133 	softmac_lower_t		*slp;
134 	dl_enabmulti_req_t	*enabmulti;
135 	dl_disabmulti_req_t	*disabmulti;
136 	mblk_t			*reqmp;
137 	t_uscalar_t		dl_prim;
138 	uint32_t		size, addr_length;
139 
140 	ASSERT(MAC_PERIM_HELD(softmac->smac_mh));
141 	/*
142 	 * create multicst message and send it down
143 	 */
144 	addr_length = softmac->smac_addrlen;
145 	if (add) {
146 		size = sizeof (dl_enabmulti_req_t) + addr_length;
147 		dl_prim = DL_ENABMULTI_REQ;
148 	} else {
149 		size = sizeof (dl_disabmulti_req_t) + addr_length;
150 		dl_prim = DL_DISABMULTI_REQ;
151 	}
152 
153 	reqmp = mexchange(NULL, NULL, size, M_PROTO, dl_prim);
154 	if (reqmp == NULL)
155 		return (ENOMEM);
156 
157 	if (add) {
158 		enabmulti = (dl_enabmulti_req_t *)reqmp->b_rptr;
159 		enabmulti->dl_addr_offset = sizeof (dl_enabmulti_req_t);
160 		enabmulti->dl_addr_length = addr_length;
161 		(void) memcpy(&enabmulti[1], mca, addr_length);
162 	} else {
163 		disabmulti = (dl_disabmulti_req_t *)reqmp->b_rptr;
164 		disabmulti->dl_addr_offset = sizeof (dl_disabmulti_req_t);
165 		disabmulti->dl_addr_length = addr_length;
166 		(void) memcpy(&disabmulti[1], mca, addr_length);
167 	}
168 
169 	slp = softmac->smac_lower;
170 	ASSERT(slp != NULL);
171 	return (softmac_proto_tx(slp, reqmp, NULL));
172 }
173 
174 int
175 softmac_m_unicst(void *arg, const uint8_t *macaddr)
176 {
177 	softmac_t		*softmac = arg;
178 	softmac_lower_t		*slp;
179 	dl_set_phys_addr_req_t	*phyaddr;
180 	mblk_t			*reqmp;
181 	size_t			size;
182 
183 	ASSERT(MAC_PERIM_HELD(softmac->smac_mh));
184 	/*
185 	 * create set_phys_addr message and send it down
186 	 */
187 	size = DL_SET_PHYS_ADDR_REQ_SIZE + softmac->smac_addrlen;
188 	reqmp = mexchange(NULL, NULL, size, M_PROTO, DL_SET_PHYS_ADDR_REQ);
189 	if (reqmp == NULL)
190 		return (ENOMEM);
191 
192 	phyaddr = (dl_set_phys_addr_req_t *)reqmp->b_rptr;
193 	phyaddr->dl_addr_offset = sizeof (dl_set_phys_addr_req_t);
194 	phyaddr->dl_addr_length = softmac->smac_addrlen;
195 	(void) memcpy(&phyaddr[1], macaddr, softmac->smac_addrlen);
196 
197 	slp = softmac->smac_lower;
198 	ASSERT(slp != NULL);
199 	return (softmac_proto_tx(slp, reqmp, NULL));
200 }
201 
202 void
203 softmac_m_ioctl(void *arg, queue_t *wq, mblk_t *mp)
204 {
205 	softmac_lower_t *slp = ((softmac_t *)arg)->smac_lower;
206 	mblk_t *ackmp;
207 
208 	ASSERT(slp != NULL);
209 	softmac_ioctl_tx(slp, mp, &ackmp);
210 	qreply(wq, ackmp);
211 }
212 
213 static void
214 softmac_process_notify_ind(softmac_t *softmac, mblk_t *mp)
215 {
216 	dl_notify_ind_t	*dlnip = (dl_notify_ind_t *)mp->b_rptr;
217 	uint_t		addroff, addrlen;
218 
219 	ASSERT(dlnip->dl_primitive == DL_NOTIFY_IND);
220 
221 	switch (dlnip->dl_notification) {
222 	case DL_NOTE_PHYS_ADDR:
223 		if (dlnip->dl_data != DL_CURR_PHYS_ADDR)
224 			break;
225 
226 		addroff = dlnip->dl_addr_offset;
227 		addrlen = dlnip->dl_addr_length - softmac->smac_saplen;
228 		if (addroff == 0 || addrlen != softmac->smac_addrlen ||
229 		    !MBLKIN(mp, addroff, addrlen)) {
230 			cmn_err(CE_NOTE, "softmac: got malformed "
231 			    "DL_NOTIFY_IND; length/offset %d/%d",
232 			    addrlen, addroff);
233 			break;
234 		}
235 
236 		mac_unicst_update(softmac->smac_mh, mp->b_rptr + addroff);
237 		break;
238 
239 	case DL_NOTE_LINK_UP:
240 		mac_link_update(softmac->smac_mh, LINK_STATE_UP);
241 		break;
242 
243 	case DL_NOTE_LINK_DOWN:
244 		mac_link_update(softmac->smac_mh, LINK_STATE_DOWN);
245 		break;
246 	}
247 
248 	freemsg(mp);
249 }
250 
251 void
252 softmac_notify_thread(void *arg)
253 {
254 	softmac_t	*softmac = arg;
255 	callb_cpr_t	cprinfo;
256 
257 	CALLB_CPR_INIT(&cprinfo, &softmac->smac_mutex, callb_generic_cpr,
258 	    "softmac_notify_thread");
259 
260 	mutex_enter(&softmac->smac_mutex);
261 
262 	/*
263 	 * Quit the thread if smac_mh is unregistered.
264 	 */
265 	while (softmac->smac_mh != NULL &&
266 	    !(softmac->smac_flags & SOFTMAC_NOTIFY_QUIT)) {
267 		mblk_t		*mp, *nextmp;
268 
269 		if ((mp = softmac->smac_notify_head) == NULL) {
270 			CALLB_CPR_SAFE_BEGIN(&cprinfo);
271 			cv_wait(&softmac->smac_cv, &softmac->smac_mutex);
272 			CALLB_CPR_SAFE_END(&cprinfo, &softmac->smac_mutex);
273 			continue;
274 		}
275 
276 		softmac->smac_notify_head = softmac->smac_notify_tail = NULL;
277 		mutex_exit(&softmac->smac_mutex);
278 
279 		while (mp != NULL) {
280 			nextmp = mp->b_next;
281 			mp->b_next = NULL;
282 			softmac_process_notify_ind(softmac, mp);
283 			mp = nextmp;
284 		}
285 		mutex_enter(&softmac->smac_mutex);
286 	}
287 
288 	/*
289 	 * The softmac is being destroyed, simply free all of the DL_NOTIFY_IND
290 	 * messages left in the queue which did not have the chance to be
291 	 * processed.
292 	 */
293 	freemsgchain(softmac->smac_notify_head);
294 	softmac->smac_notify_head = softmac->smac_notify_tail = NULL;
295 	softmac->smac_notify_thread = NULL;
296 	cv_broadcast(&softmac->smac_cv);
297 	CALLB_CPR_EXIT(&cprinfo);
298 	thread_exit();
299 }
300 
301 static void
302 softmac_enqueue_notify_ind(queue_t *rq, mblk_t *mp)
303 {
304 	softmac_lower_t	*slp = rq->q_ptr;
305 	softmac_t	*softmac = slp->sl_softmac;
306 
307 	mutex_enter(&softmac->smac_mutex);
308 	if (softmac->smac_notify_tail == NULL) {
309 		softmac->smac_notify_head = softmac->smac_notify_tail = mp;
310 	} else {
311 		softmac->smac_notify_tail->b_next = mp;
312 		softmac->smac_notify_tail = mp;
313 	}
314 	cv_broadcast(&softmac->smac_cv);
315 	mutex_exit(&softmac->smac_mutex);
316 }
317 
318 static void
319 softmac_process_dlpi(softmac_lower_t *slp, mblk_t *mp, uint_t minlen,
320     t_uscalar_t reqprim)
321 {
322 	const char *ackname;
323 
324 	ackname = dl_primstr(((union DL_primitives *)mp->b_rptr)->dl_primitive);
325 
326 	if (MBLKL(mp) < minlen) {
327 		cmn_err(CE_WARN, "softmac: got short %s", ackname);
328 		freemsg(mp);
329 		return;
330 	}
331 
332 	mutex_enter(&slp->sl_mutex);
333 	if (slp->sl_pending_prim != reqprim) {
334 		cmn_err(CE_NOTE, "softmac: got unexpected %s", ackname);
335 		mutex_exit(&slp->sl_mutex);
336 		freemsg(mp);
337 		return;
338 	}
339 
340 	slp->sl_pending_prim = DL_PRIM_INVAL;
341 	slp->sl_ack_mp = mp;
342 	cv_signal(&slp->sl_cv);
343 	mutex_exit(&slp->sl_mutex);
344 }
345 
346 void
347 softmac_rput_process_proto(queue_t *rq, mblk_t *mp)
348 {
349 	softmac_lower_t		*slp = rq->q_ptr;
350 	union DL_primitives	*dlp = (union DL_primitives *)mp->b_rptr;
351 	ssize_t			len = MBLKL(mp);
352 	const char		*primstr;
353 
354 	if (len < sizeof (t_uscalar_t)) {
355 		cmn_err(CE_WARN, "softmac: got runt DLPI message");
356 		goto exit;
357 	}
358 
359 	primstr = dl_primstr(dlp->dl_primitive);
360 
361 	switch (dlp->dl_primitive) {
362 	case DL_OK_ACK:
363 		if (len < DL_OK_ACK_SIZE)
364 			goto runt;
365 
366 		softmac_process_dlpi(slp, mp, DL_OK_ACK_SIZE,
367 		    dlp->ok_ack.dl_correct_primitive);
368 		return;
369 
370 	case DL_ERROR_ACK:
371 		if (len < DL_ERROR_ACK_SIZE)
372 			goto runt;
373 
374 		softmac_process_dlpi(slp, mp, DL_ERROR_ACK_SIZE,
375 		    dlp->error_ack.dl_error_primitive);
376 		return;
377 
378 	case DL_NOTIFY_IND:
379 		if (len < DL_NOTIFY_IND_SIZE)
380 			goto runt;
381 
382 		/*
383 		 * Enqueue all the DL_NOTIFY_IND messages and process them
384 		 * in another separate thread to avoid deadlock. Here is an
385 		 * example of the deadlock scenario:
386 		 *
387 		 * Thread A: mac_promisc_set()->softmac_m_promisc()
388 		 *
389 		 *   The softmac driver waits for the ACK of the
390 		 *   DL_PROMISC_PHYS request with the MAC perimeter;
391 		 *
392 		 * Thread B:
393 		 *
394 		 *   The driver handles the DL_PROMISC_PHYS request. Before
395 		 *   it sends back the ACK, it could first send a
396 		 *   DL_NOTE_PROMISC_ON_PHYS notification.
397 		 *
398 		 * Since DL_NOTIFY_IND could eventually cause softmac to call
399 		 * mac_xxx_update(), which requires MAC perimeter, this would
400 		 * cause deadlock between the two threads. Enqueuing the
401 		 * DL_NOTIFY_IND message and defer its processing would
402 		 * avoid the potential deadlock.
403 		 */
404 		softmac_enqueue_notify_ind(rq, mp);
405 		return;
406 
407 	case DL_NOTIFY_ACK:
408 		softmac_process_dlpi(slp, mp, DL_NOTIFY_ACK_SIZE,
409 		    DL_NOTIFY_REQ);
410 		return;
411 
412 	case DL_CAPABILITY_ACK:
413 		softmac_process_dlpi(slp, mp, DL_CAPABILITY_ACK_SIZE,
414 		    DL_CAPABILITY_REQ);
415 		return;
416 
417 	case DL_BIND_ACK:
418 		softmac_process_dlpi(slp, mp, DL_BIND_ACK_SIZE, DL_BIND_REQ);
419 		return;
420 
421 	case DL_CONTROL_ACK:
422 		softmac_process_dlpi(slp, mp, DL_CONTROL_ACK_SIZE,
423 		    DL_CONTROL_REQ);
424 		return;
425 
426 	case DL_UNITDATA_IND:
427 	case DL_PHYS_ADDR_ACK:
428 		/*
429 		 * a. Because the stream is in DLIOCRAW mode,
430 		 *    DL_UNITDATA_IND messages are not expected.
431 		 * b. The lower stream should not receive DL_PHYS_ADDR_REQ,
432 		 *    so DL_PHYS_ADDR_ACK messages are also unexpected.
433 		 */
434 	default:
435 		cmn_err(CE_WARN, "softmac: got unexpected %s", primstr);
436 		break;
437 	}
438 exit:
439 	freemsg(mp);
440 	return;
441 runt:
442 	cmn_err(CE_WARN, "softmac: got runt %s", primstr);
443 	freemsg(mp);
444 }
445 
446 void
447 softmac_rput_process_notdata(queue_t *rq, softmac_upper_t *sup, mblk_t *mp)
448 {
449 	softmac_lower_t		*slp = rq->q_ptr;
450 	union DL_primitives	*dlp;
451 	ssize_t			len = MBLKL(mp);
452 
453 	switch (DB_TYPE(mp)) {
454 	case M_PROTO:
455 	case M_PCPROTO:
456 		/*
457 		 * If this is a shared-lower-stream, pass it to softmac to
458 		 * process.
459 		 */
460 		if (sup == NULL) {
461 			softmac_rput_process_proto(rq, mp);
462 			break;
463 		}
464 
465 		/*
466 		 * Dedicated-lower-stream.
467 		 */
468 		dlp = (union DL_primitives *)mp->b_rptr;
469 		ASSERT(len >= sizeof (dlp->dl_primitive));
470 		switch (dlp->dl_primitive) {
471 		case DL_OK_ACK:
472 			if (len < DL_OK_ACK_SIZE)
473 				goto runt;
474 
475 			/*
476 			 * If this is a DL_OK_ACK for a DL_UNBIND_REQ, pass it
477 			 * to softmac to process, otherwise directly pass it to
478 			 * the upper stream.
479 			 */
480 			if (dlp->ok_ack.dl_correct_primitive == DL_UNBIND_REQ) {
481 				softmac_rput_process_proto(rq, mp);
482 				break;
483 			}
484 
485 			putnext(sup->su_rq, mp);
486 			break;
487 		case DL_ERROR_ACK:
488 			if (len < DL_ERROR_ACK_SIZE)
489 				goto runt;
490 
491 			/*
492 			 * If this is a DL_ERROR_ACK for a DL_UNBIND_REQ, pass
493 			 * it to softmac to process, otherwise directly pass it
494 			 * to the upper stream.
495 			 */
496 			if (dlp->error_ack.dl_error_primitive ==
497 			    DL_UNBIND_REQ) {
498 				softmac_rput_process_proto(rq, mp);
499 				break;
500 			}
501 
502 			putnext(sup->su_rq, mp);
503 			break;
504 		case DL_BIND_ACK:
505 		case DL_CAPABILITY_ACK:
506 			softmac_rput_process_proto(rq, mp);
507 			break;
508 		default:
509 			putnext(sup->su_rq, mp);
510 			break;
511 		}
512 		break;
513 	case M_FLUSH:
514 		if (*mp->b_rptr & FLUSHR)
515 			flushq(rq, FLUSHDATA);
516 		if (*mp->b_rptr & FLUSHW)
517 			flushq(OTHERQ(rq), FLUSHDATA);
518 		putnext(rq, mp);
519 		break;
520 
521 	case M_IOCACK:
522 	case M_IOCNAK:
523 	case M_COPYIN:
524 	case M_COPYOUT:
525 		if (sup != NULL) {
526 			putnext(sup->su_rq, mp);
527 			break;
528 		}
529 
530 		mutex_enter(&slp->sl_mutex);
531 		if (!slp->sl_pending_ioctl) {
532 			mutex_exit(&slp->sl_mutex);
533 			cmn_err(CE_NOTE, "softmac: got unexpected mblk "
534 			    "type 0x%x", DB_TYPE(mp));
535 			freemsg(mp);
536 			break;
537 		}
538 
539 		slp->sl_pending_ioctl = B_FALSE;
540 		slp->sl_ack_mp = mp;
541 		cv_broadcast(&slp->sl_cv);
542 		mutex_exit(&slp->sl_mutex);
543 		break;
544 
545 	default:
546 		cmn_err(CE_NOTE, "softmac: got unsupported mblk type 0x%x",
547 		    DB_TYPE(mp));
548 		freemsg(mp);
549 		break;
550 	}
551 	return;
552 runt:
553 	cmn_err(CE_WARN, "softmac: got runt %s", dl_primstr(dlp->dl_primitive));
554 	freemsg(mp);
555 }
556