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