if_media.c revision 1.50 1 /* $NetBSD: if_media.c,v 1.50 2020/01/31 00:49:18 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 1998 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
9 * NASA Ames Research Center.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 /*
34 * Copyright (c) 1997
35 * Jonathan Stone and Jason R. Thorpe. All rights reserved.
36 *
37 * This software is derived from information provided by Matt Thomas.
38 *
39 * Redistribution and use in source and binary forms, with or without
40 * modification, are permitted provided that the following conditions
41 * are met:
42 * 1. Redistributions of source code must retain the above copyright
43 * notice, this list of conditions and the following disclaimer.
44 * 2. Redistributions in binary form must reproduce the above copyright
45 * notice, this list of conditions and the following disclaimer in the
46 * documentation and/or other materials provided with the distribution.
47 * 3. All advertising materials mentioning features or use of this software
48 * must display the following acknowledgement:
49 * This product includes software developed by Jonathan Stone
50 * and Jason R. Thorpe for the NetBSD Project.
51 * 4. The names of the authors may not be used to endorse or promote products
52 * derived from this software without specific prior written permission.
53 *
54 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
55 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
56 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
57 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
58 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
59 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
60 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
61 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
62 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
63 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
64 * SUCH DAMAGE.
65 */
66
67 /*
68 * BSD/OS-compatible network interface media selection.
69 *
70 * Where it is safe to do so, this code strays slightly from the BSD/OS
71 * design. Software which uses the API (device drivers, basically)
72 * shouldn't notice any difference.
73 *
74 * Many thanks to Matt Thomas for providing the information necessary
75 * to implement this interface.
76 */
77
78 #include <sys/cdefs.h>
79 __KERNEL_RCSID(0, "$NetBSD: if_media.c,v 1.50 2020/01/31 00:49:18 thorpej Exp $");
80
81 #include <sys/param.h>
82 #include <sys/systm.h>
83 #include <sys/errno.h>
84 #include <sys/ioctl.h>
85 #include <sys/socket.h>
86 #include <sys/kmem.h>
87
88 #include <net/if.h>
89 #include <net/if_media.h>
90 #include <net/netisr.h>
91
92 static void ifmedia_status(struct ifmedia *, struct ifnet *,
93 struct ifmediareq *);
94 static int ifmedia_ioctl_locked(struct ifnet *, struct ifreq *,
95 struct ifmedia *, u_long);
96
97 /*
98 * Compile-time options:
99 * IFMEDIA_DEBUG:
100 * Turn on implementation-level debug printfs.
101 * Useful for debugging newly-ported drivers.
102 */
103
104 #ifdef IFMEDIA_DEBUG
105 int ifmedia_debug = 0;
106 static void ifmedia_printword(int);
107 #endif
108
109 /*
110 * Initialize if_media struct for a specific interface instance.
111 */
112 void
113 ifmedia_init(struct ifmedia *ifm, int dontcare_mask,
114 ifm_change_cb_t change_callback, ifm_stat_cb_t status_callback)
115 {
116
117 TAILQ_INIT(&ifm->ifm_list);
118 ifm->ifm_cur = NULL;
119 ifm->ifm_media = IFM_NONE;
120 ifm->ifm_mask = dontcare_mask; /* IF don't-care bits */
121 ifm->ifm_change = change_callback;
122 ifm->ifm_status = status_callback;
123 }
124
125 int
126 ifmedia_change(struct ifmedia *ifm, struct ifnet *ifp)
127 {
128
129 if (ifm->ifm_change == NULL)
130 return -1;
131 return (*ifm->ifm_change)(ifp);
132 }
133
134 static void
135 ifmedia_status(struct ifmedia *ifm, struct ifnet *ifp, struct ifmediareq *ifmr)
136 {
137
138 if (ifm->ifm_status == NULL)
139 return;
140 (*ifm->ifm_status)(ifp, ifmr);
141 }
142
143 /*
144 * Add a media configuration to the list of supported media
145 * for a specific interface instance.
146 */
147 void
148 ifmedia_add(struct ifmedia *ifm, int mword, int data, void *aux)
149 {
150 struct ifmedia_entry *entry;
151
152 #ifdef IFMEDIA_DEBUG
153 if (ifmedia_debug) {
154 if (ifm == NULL) {
155 printf("ifmedia_add: null ifm\n");
156 return;
157 }
158 printf("Adding entry for ");
159 ifmedia_printword(mword);
160 }
161 #endif
162
163 entry = kmem_zalloc(sizeof(*entry), KM_SLEEP);
164 entry->ifm_media = mword;
165 entry->ifm_data = data;
166 entry->ifm_aux = aux;
167 TAILQ_INSERT_TAIL(&ifm->ifm_list, entry, ifm_list);
168 }
169
170 /*
171 * Add an array of media configurations to the list of
172 * supported media for a specific interface instance.
173 */
174 void
175 ifmedia_list_add(struct ifmedia *ifm, struct ifmedia_entry *lp, int count)
176 {
177 int i;
178
179 for (i = 0; i < count; i++)
180 ifmedia_add(ifm, lp[i].ifm_media, lp[i].ifm_data,
181 lp[i].ifm_aux);
182 }
183
184 /*
185 * Set the default active media.
186 *
187 * Called by device-specific code which is assumed to have already
188 * selected the default media in hardware. We do _not_ call the
189 * media-change callback.
190 */
191 void
192 ifmedia_set(struct ifmedia *ifm, int target)
193 {
194 struct ifmedia_entry *match;
195
196 match = ifmedia_match(ifm, target, ifm->ifm_mask);
197
198 /*
199 * If we didn't find the requested media, then we try to fall
200 * back to target-type (IFM_ETHER, e.g.) | IFM_NONE. If that's
201 * not on the list, then we add it and set the media to it.
202 *
203 * Since ifmedia_set is almost always called with IFM_AUTO or
204 * with a known-good media, this really should only occur if we:
205 *
206 * a) didn't find any PHYs, or
207 * b) didn't find an autoselect option on the PHY when the
208 * parent ethernet driver expected to.
209 *
210 * In either case, it makes sense to select no media.
211 */
212 if (match == NULL) {
213 printf("ifmedia_set: no match for 0x%x/0x%x\n",
214 target, ~ifm->ifm_mask);
215 target = (target & IFM_NMASK) | IFM_NONE;
216 match = ifmedia_match(ifm, target, ifm->ifm_mask);
217 if (match == NULL) {
218 ifmedia_add(ifm, target, 0, NULL);
219 match = ifmedia_match(ifm, target, ifm->ifm_mask);
220 if (match == NULL)
221 panic("ifmedia_set failed");
222 }
223 }
224 ifm->ifm_cur = match;
225
226 #ifdef IFMEDIA_DEBUG
227 if (ifmedia_debug) {
228 printf("ifmedia_set: target ");
229 ifmedia_printword(target);
230 printf("ifmedia_set: setting to ");
231 ifmedia_printword(ifm->ifm_cur->ifm_media);
232 }
233 #endif
234 }
235
236 static int
237 ifmedia_getwords(struct ifmedia * const ifm, int *words, int maxwords)
238 {
239 struct ifmedia_entry *ep;
240 int nwords = 0;
241
242 TAILQ_FOREACH(ep, &ifm->ifm_list, ifm_list) {
243 if (words != NULL && nwords < maxwords) {
244 words[nwords] = ep->ifm_media;
245 }
246 nwords++;
247 }
248
249 return nwords;
250 }
251
252 /*
253 * Device-independent media ioctl support function.
254 */
255 static int
256 ifmedia_ioctl_locked(struct ifnet *ifp, struct ifreq *ifr, struct ifmedia *ifm,
257 u_long cmd)
258 {
259 struct ifmedia_entry *match;
260 struct ifmediareq *ifmr = (struct ifmediareq *)ifr;
261 int error = 0;
262
263 if (ifp == NULL || ifr == NULL || ifm == NULL)
264 return EINVAL;
265
266 switch (cmd) {
267 case SIOCSIFMEDIA: /* Set the current media. */
268 {
269 struct ifmedia_entry *oldentry;
270 u_int oldmedia;
271 u_int newmedia = ifr->ifr_media;
272
273 match = ifmedia_match(ifm, newmedia, ifm->ifm_mask);
274 if (match == NULL) {
275 #ifdef IFMEDIA_DEBUG
276 if (ifmedia_debug) {
277 printf("ifmedia_ioctl: no media found for "
278 "0x%08x\n", newmedia);
279 }
280 #endif
281 return EINVAL;
282 }
283
284 /*
285 * If no change, we're done.
286 * XXX Automedia may involve software intervention.
287 * Keep going in case the connected media changed.
288 * Similarly, if best match changed (kernel debugger?).
289 */
290 if ((IFM_SUBTYPE(newmedia) != IFM_AUTO) &&
291 (newmedia == ifm->ifm_media) && (match == ifm->ifm_cur))
292 return 0;
293
294 /*
295 * We found a match, now make the driver switch to it.
296 * Make sure to preserve our old media type in case the
297 * driver can't switch.
298 */
299 #ifdef IFMEDIA_DEBUG
300 if (ifmedia_debug) {
301 printf("ifmedia_ioctl: switching %s to ",
302 ifp->if_xname);
303 ifmedia_printword(match->ifm_media);
304 }
305 #endif
306 oldentry = ifm->ifm_cur;
307 oldmedia = ifm->ifm_media;
308 ifm->ifm_cur = match;
309 ifm->ifm_media = newmedia;
310 error = ifmedia_change(ifm, ifp);
311 if (error) {
312 ifm->ifm_cur = oldentry;
313 ifm->ifm_media = oldmedia;
314 }
315 break;
316 }
317
318 /* Get list of available media and current media on interface. */
319 case SIOCGIFMEDIA:
320 {
321 int nwords1, nwords2;
322
323 if (ifmr->ifm_count < 0)
324 return EINVAL;
325
326 ifmr->ifm_active = ifmr->ifm_current = ifm->ifm_cur ?
327 ifm->ifm_cur->ifm_media : IFM_NONE;
328 ifmr->ifm_mask = ifm->ifm_mask;
329 ifmr->ifm_status = 0;
330 ifmedia_status(ifm, ifp, ifmr);
331
332 /*
333 * Count them so we know a-priori how much is the max we'll
334 * need.
335 */
336 nwords1 = nwords2 = ifmedia_getwords(ifm, NULL, 0);
337
338 if (ifmr->ifm_count != 0) {
339 int maxwords = MIN(nwords1, ifmr->ifm_count);
340 int *kptr = kmem_zalloc(maxwords * sizeof(int),
341 KM_SLEEP);
342
343 nwords2 = ifmedia_getwords(ifm, kptr, maxwords);
344 error = copyout(kptr, ifmr->ifm_ulist,
345 maxwords * sizeof(int));
346 if (error == 0 && nwords2 > nwords1)
347 error = E2BIG; /* oops! */
348 kmem_free(kptr, maxwords * sizeof(int));
349 }
350 /* Update with the real number */
351 ifmr->ifm_count = nwords2;
352 break;
353 }
354
355 default:
356 return EINVAL;
357 }
358
359 return error;
360 }
361
362 int
363 ifmedia_ioctl(struct ifnet *ifp, struct ifreq *ifr, struct ifmedia *ifm,
364 u_long cmd)
365 {
366 int e;
367
368 /*
369 * If if_is_mpsafe(ifp), KERNEL_LOCK isn't held here and
370 * ipl will not have been raised (well, maybe it has, but
371 * it doesn't matter), but ifmedia_ioctl_locked isn't MP-safe
372 * yet, so we go to splnet and grab the KERNEL_LOCK.
373 *
374 * In the non-mpsafe case, the interface's ioctl routine
375 * will already be running at splnet() and so raising it
376 * again is redundant, but also harmless.
377 */
378 int s = splnet();
379 KERNEL_LOCK_IF_IFP_MPSAFE(ifp);
380 e = ifmedia_ioctl_locked(ifp, ifr, ifm, cmd);
381 KERNEL_UNLOCK_IF_IFP_MPSAFE(ifp);
382 splx(s);
383
384 return e;
385 }
386
387 /*
388 * Find media entry matching a given ifm word.
389 */
390 struct ifmedia_entry *
391 ifmedia_match(struct ifmedia *ifm, u_int target, u_int mask)
392 {
393 struct ifmedia_entry *match, *next;
394
395 match = NULL;
396 mask = ~mask;
397
398 TAILQ_FOREACH(next, &ifm->ifm_list, ifm_list) {
399 if ((next->ifm_media & mask) == (target & mask)) {
400 if (match) {
401 #if defined(IFMEDIA_DEBUG) || defined(DIAGNOSTIC)
402 printf("ifmedia_match: multiple match for "
403 "0x%x/0x%x, selected instance %d\n",
404 target, mask, IFM_INST(match->ifm_media));
405 #endif
406 break;
407 }
408 match = next;
409 }
410 }
411
412 return match;
413 }
414
415 /*
416 * Delete all media for a given instance.
417 */
418 void
419 ifmedia_delete_instance(struct ifmedia *ifm, u_int inst)
420 {
421 struct ifmedia_entry *ife, *nife;
422
423 TAILQ_FOREACH_SAFE(ife, &ifm->ifm_list, ifm_list, nife) {
424 if (inst == IFM_INST_ANY ||
425 inst == IFM_INST(ife->ifm_media)) {
426 TAILQ_REMOVE(&ifm->ifm_list, ife, ifm_list);
427 kmem_free(ife, sizeof(*ife));
428 }
429 }
430 if (inst == IFM_INST_ANY) {
431 ifm->ifm_cur = NULL;
432 ifm->ifm_media = IFM_NONE;
433 }
434 }
435
436 void
437 ifmedia_removeall(struct ifmedia *ifm)
438 {
439
440 ifmedia_delete_instance(ifm, IFM_INST_ANY);
441 }
442
443
444 /*
445 * Compute the interface `baudrate' from the media, for the interface
446 * metrics (used by routing daemons).
447 */
448 static const struct ifmedia_baudrate ifmedia_baudrate_descriptions[] =
449 IFM_BAUDRATE_DESCRIPTIONS;
450
451 uint64_t
452 ifmedia_baudrate(int mword)
453 {
454 int i;
455
456 for (i = 0; ifmedia_baudrate_descriptions[i].ifmb_word != 0; i++) {
457 if (IFM_TYPE_SUBTYPE_MATCH(mword,
458 ifmedia_baudrate_descriptions[i].ifmb_word))
459 return ifmedia_baudrate_descriptions[i].ifmb_baudrate;
460 }
461
462 /* Not known. */
463 return 0;
464 }
465
466 #ifdef IFMEDIA_DEBUG
467
468 static const struct ifmedia_description ifm_type_descriptions[] =
469 IFM_TYPE_DESCRIPTIONS;
470
471 static const struct ifmedia_description ifm_subtype_descriptions[] =
472 IFM_SUBTYPE_DESCRIPTIONS;
473
474 static const struct ifmedia_description ifm_option_descriptions[] =
475 IFM_OPTION_DESCRIPTIONS;
476
477 /*
478 * print a media word.
479 */
480 static void
481 ifmedia_printword(int ifmw)
482 {
483 const struct ifmedia_description *desc;
484 int seen_option = 0;
485
486 /* Print the top-level interface type. */
487 for (desc = ifm_type_descriptions; desc->ifmt_string != NULL;
488 desc++) {
489 if (IFM_TYPE(ifmw) == desc->ifmt_word)
490 break;
491 }
492 if (desc->ifmt_string == NULL)
493 printf("<unknown type> ");
494 else
495 printf("%s ", desc->ifmt_string);
496
497 /* Print the subtype. */
498 for (desc = ifm_subtype_descriptions; desc->ifmt_string != NULL;
499 desc++) {
500 if (IFM_TYPE_MATCH(desc->ifmt_word, ifmw) &&
501 IFM_SUBTYPE(desc->ifmt_word) == IFM_SUBTYPE(ifmw))
502 break;
503 }
504 if (desc->ifmt_string == NULL)
505 printf("<unknown subtype>");
506 else
507 printf("%s", desc->ifmt_string);
508
509 /* Print any options. */
510 for (desc = ifm_option_descriptions; desc->ifmt_string != NULL;
511 desc++) {
512 if (IFM_TYPE_MATCH(desc->ifmt_word, ifmw) &&
513 (ifmw & desc->ifmt_word) != 0 &&
514 (seen_option & IFM_OPTIONS(desc->ifmt_word)) == 0) {
515 if (seen_option == 0)
516 printf(" <");
517 printf("%s%s", seen_option ? "," : "",
518 desc->ifmt_string);
519 seen_option |= IFM_OPTIONS(desc->ifmt_word);
520 }
521 }
522 printf("%s\n", seen_option ? ">" : "");
523 }
524
525 #endif /* IFMEDIA_DEBUG */
526