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