xref: /freebsd/sys/net/if_media.c (revision eb69d1f144a6fcc765d1b9d44a5ae8082353e70b)
1 /*	$NetBSD: if_media.c,v 1.1 1997/03/17 02:55:15 thorpej Exp $	*/
2 /* $FreeBSD$ */
3 
4 /*-
5  * SPDX-License-Identifier: BSD-4-Clause
6  *
7  * Copyright (c) 1997
8  *	Jonathan Stone and Jason R. Thorpe.  All rights reserved.
9  *
10  * This software is derived from information provided by Matt Thomas.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. All advertising materials mentioning features or use of this software
21  *    must display the following acknowledgement:
22  *      This product includes software developed by Jonathan Stone
23  *	and Jason R. Thorpe for the NetBSD Project.
24  * 4. The names of the authors may not be used to endorse or promote products
25  *    derived from this software without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
28  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
29  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
30  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
31  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
32  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
34  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
35  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37  * SUCH DAMAGE.
38  */
39 
40 /*
41  * BSD/OS-compatible network interface media selection.
42  *
43  * Where it is safe to do so, this code strays slightly from the BSD/OS
44  * design.  Software which uses the API (device drivers, basically)
45  * shouldn't notice any difference.
46  *
47  * Many thanks to Matt Thomas for providing the information necessary
48  * to implement this interface.
49  */
50 
51 #include "opt_ifmedia.h"
52 
53 #include <sys/param.h>
54 #include <sys/systm.h>
55 #include <sys/socket.h>
56 #include <sys/sockio.h>
57 #include <sys/malloc.h>
58 #include <sys/module.h>
59 #include <sys/sysctl.h>
60 
61 #include <net/if.h>
62 #include <net/if_media.h>
63 
64 /*
65  * Compile-time options:
66  * IFMEDIA_DEBUG:
67  *	turn on implementation-level debug printfs.
68  * 	Useful for debugging newly-ported  drivers.
69  */
70 
71 static struct ifmedia_entry *ifmedia_match(struct ifmedia *ifm,
72     int flags, int mask);
73 
74 #ifdef IFMEDIA_DEBUG
75 #include <net/if_var.h>
76 int	ifmedia_debug = 0;
77 SYSCTL_INT(_debug, OID_AUTO, ifmedia, CTLFLAG_RW, &ifmedia_debug,
78 	    0, "if_media debugging msgs");
79 static	void ifmedia_printword(int);
80 #endif
81 
82 /*
83  * Initialize if_media struct for a specific interface instance.
84  */
85 void
86 ifmedia_init(ifm, dontcare_mask, change_callback, status_callback)
87 	struct ifmedia *ifm;
88 	int dontcare_mask;
89 	ifm_change_cb_t change_callback;
90 	ifm_stat_cb_t status_callback;
91 {
92 
93 	LIST_INIT(&ifm->ifm_list);
94 	ifm->ifm_cur = NULL;
95 	ifm->ifm_media = 0;
96 	ifm->ifm_mask = dontcare_mask;		/* IF don't-care bits */
97 	ifm->ifm_change = change_callback;
98 	ifm->ifm_status = status_callback;
99 }
100 
101 void
102 ifmedia_removeall(ifm)
103 	struct ifmedia *ifm;
104 {
105 	struct ifmedia_entry *entry;
106 
107 	for (entry = LIST_FIRST(&ifm->ifm_list); entry;
108 	     entry = LIST_FIRST(&ifm->ifm_list)) {
109 		LIST_REMOVE(entry, ifm_list);
110 		free(entry, M_IFADDR);
111 	}
112 	ifm->ifm_cur = NULL;
113 }
114 
115 /*
116  * Add a media configuration to the list of supported media
117  * for a specific interface instance.
118  */
119 void
120 ifmedia_add(ifm, mword, data, aux)
121 	struct ifmedia *ifm;
122 	int mword;
123 	int data;
124 	void *aux;
125 {
126 	struct ifmedia_entry *entry;
127 
128 #ifdef IFMEDIA_DEBUG
129 	if (ifmedia_debug) {
130 		if (ifm == NULL) {
131 			printf("ifmedia_add: null ifm\n");
132 			return;
133 		}
134 		printf("Adding entry for ");
135 		ifmedia_printword(mword);
136 	}
137 #endif
138 
139 	entry = malloc(sizeof(*entry), M_IFADDR, M_NOWAIT);
140 	if (entry == NULL)
141 		panic("ifmedia_add: can't malloc entry");
142 
143 	entry->ifm_media = mword;
144 	entry->ifm_data = data;
145 	entry->ifm_aux = aux;
146 
147 	LIST_INSERT_HEAD(&ifm->ifm_list, entry, ifm_list);
148 }
149 
150 /*
151  * Add an array of media configurations to the list of
152  * supported media for a specific interface instance.
153  */
154 void
155 ifmedia_list_add(ifm, lp, count)
156 	struct ifmedia *ifm;
157 	struct ifmedia_entry *lp;
158 	int count;
159 {
160 	int i;
161 
162 	for (i = 0; i < count; i++)
163 		ifmedia_add(ifm, lp[i].ifm_media, lp[i].ifm_data,
164 		    lp[i].ifm_aux);
165 }
166 
167 /*
168  * Set the default active media.
169  *
170  * Called by device-specific code which is assumed to have already
171  * selected the default media in hardware.  We do _not_ call the
172  * media-change callback.
173  */
174 void
175 ifmedia_set(ifm, target)
176 	struct ifmedia *ifm;
177 	int target;
178 
179 {
180 	struct ifmedia_entry *match;
181 
182 	match = ifmedia_match(ifm, target, ifm->ifm_mask);
183 
184 	if (match == NULL) {
185 		printf("ifmedia_set: no match for 0x%x/0x%x\n",
186 		    target, ~ifm->ifm_mask);
187 		panic("ifmedia_set");
188 	}
189 	ifm->ifm_cur = match;
190 
191 #ifdef IFMEDIA_DEBUG
192 	if (ifmedia_debug) {
193 		printf("ifmedia_set: target ");
194 		ifmedia_printword(target);
195 		printf("ifmedia_set: setting to ");
196 		ifmedia_printword(ifm->ifm_cur->ifm_media);
197 	}
198 #endif
199 }
200 
201 /*
202  * Given a media word, return one suitable for an application
203  * using the original encoding.
204  */
205 static int
206 compat_media(int media)
207 {
208 
209 	if (IFM_TYPE(media) == IFM_ETHER && IFM_SUBTYPE(media) > IFM_OTHER) {
210 		media &= ~(IFM_ETH_XTYPE|IFM_TMASK);
211 		media |= IFM_OTHER;
212 	}
213 	return (media);
214 }
215 
216 /*
217  * Device-independent media ioctl support function.
218  */
219 int
220 ifmedia_ioctl(ifp, ifr, ifm, cmd)
221 	struct ifnet *ifp;
222 	struct ifreq *ifr;
223 	struct ifmedia *ifm;
224 	u_long cmd;
225 {
226 	struct ifmedia_entry *match;
227 	struct ifmediareq *ifmr = (struct ifmediareq *) ifr;
228 	int error = 0;
229 
230 	if (ifp == NULL || ifr == NULL || ifm == NULL)
231 		return(EINVAL);
232 
233 	switch (cmd) {
234 
235 	/*
236 	 * Set the current media.
237 	 */
238 	case  SIOCSIFMEDIA:
239 	{
240 		struct ifmedia_entry *oldentry;
241 		int oldmedia;
242 		int newmedia = ifr->ifr_media;
243 
244 		match = ifmedia_match(ifm, newmedia, ifm->ifm_mask);
245 		if (match == NULL) {
246 #ifdef IFMEDIA_DEBUG
247 			if (ifmedia_debug) {
248 				printf(
249 				    "ifmedia_ioctl: no media found for 0x%x\n",
250 				    newmedia);
251 			}
252 #endif
253 			return (ENXIO);
254 		}
255 
256 		/*
257 		 * If no change, we're done.
258 		 * XXX Automedia may invole software intervention.
259 		 *     Keep going in case the connected media changed.
260 		 *     Similarly, if best match changed (kernel debugger?).
261 		 */
262 		if ((IFM_SUBTYPE(newmedia) != IFM_AUTO) &&
263 		    (newmedia == ifm->ifm_media) &&
264 		    (match == ifm->ifm_cur))
265 			return 0;
266 
267 		/*
268 		 * We found a match, now make the driver switch to it.
269 		 * Make sure to preserve our old media type in case the
270 		 * driver can't switch.
271 		 */
272 #ifdef IFMEDIA_DEBUG
273 		if (ifmedia_debug) {
274 			printf("ifmedia_ioctl: switching %s to ",
275 			    ifp->if_xname);
276 			ifmedia_printword(match->ifm_media);
277 		}
278 #endif
279 		oldentry = ifm->ifm_cur;
280 		oldmedia = ifm->ifm_media;
281 		ifm->ifm_cur = match;
282 		ifm->ifm_media = newmedia;
283 		error = (*ifm->ifm_change)(ifp);
284 		if (error) {
285 			ifm->ifm_cur = oldentry;
286 			ifm->ifm_media = oldmedia;
287 		}
288 		break;
289 	}
290 
291 	/*
292 	 * Get list of available media and current media on interface.
293 	 */
294 	case  SIOCGIFMEDIA:
295 	case  SIOCGIFXMEDIA:
296 	{
297 		struct ifmedia_entry *ep;
298 		int i;
299 
300 		if (ifmr->ifm_count < 0)
301 			return (EINVAL);
302 
303 		if (cmd == SIOCGIFMEDIA) {
304 			ifmr->ifm_active = ifmr->ifm_current = ifm->ifm_cur ?
305 			    compat_media(ifm->ifm_cur->ifm_media) : IFM_NONE;
306 		} else {
307 			ifmr->ifm_active = ifmr->ifm_current = ifm->ifm_cur ?
308 			    ifm->ifm_cur->ifm_media : IFM_NONE;
309 		}
310 		ifmr->ifm_mask = ifm->ifm_mask;
311 		ifmr->ifm_status = 0;
312 		(*ifm->ifm_status)(ifp, ifmr);
313 
314 		/*
315 		 * If there are more interfaces on the list, count
316 		 * them.  This allows the caller to set ifmr->ifm_count
317 		 * to 0 on the first call to know how much space to
318 		 * allocate.
319 		 */
320 		i = 0;
321 		LIST_FOREACH(ep, &ifm->ifm_list, ifm_list)
322 			if (i++ < ifmr->ifm_count) {
323 				error = copyout(&ep->ifm_media,
324 				    ifmr->ifm_ulist + i - 1, sizeof(int));
325 				if (error)
326 					break;
327 			}
328 		if (error == 0 && i > ifmr->ifm_count)
329 			error = ifmr->ifm_count ? E2BIG : 0;
330 		ifmr->ifm_count = i;
331 		break;
332 	}
333 
334 	default:
335 		return (EINVAL);
336 	}
337 
338 	return (error);
339 }
340 
341 /*
342  * Find media entry matching a given ifm word.
343  *
344  */
345 static struct ifmedia_entry *
346 ifmedia_match(ifm, target, mask)
347 	struct ifmedia *ifm;
348 	int target;
349 	int mask;
350 {
351 	struct ifmedia_entry *match, *next;
352 
353 	match = NULL;
354 	mask = ~mask;
355 
356 	LIST_FOREACH(next, &ifm->ifm_list, ifm_list) {
357 		if ((next->ifm_media & mask) == (target & mask)) {
358 #if defined(IFMEDIA_DEBUG) || defined(DIAGNOSTIC)
359 			if (match) {
360 				printf("ifmedia_match: multiple match for "
361 				    "0x%x/0x%x\n", target, mask);
362 			}
363 #endif
364 			match = next;
365 		}
366 	}
367 
368 	return match;
369 }
370 
371 /*
372  * Compute the interface `baudrate' from the media, for the interface
373  * metrics (used by routing daemons).
374  */
375 static const struct ifmedia_baudrate ifmedia_baudrate_descriptions[] =
376     IFM_BAUDRATE_DESCRIPTIONS;
377 
378 uint64_t
379 ifmedia_baudrate(int mword)
380 {
381 	int i;
382 
383 	for (i = 0; ifmedia_baudrate_descriptions[i].ifmb_word != 0; i++) {
384 		if (IFM_TYPE_MATCH(mword, ifmedia_baudrate_descriptions[i].ifmb_word))
385 			return (ifmedia_baudrate_descriptions[i].ifmb_baudrate);
386 	}
387 
388 	/* Not known. */
389 	return (0);
390 }
391 
392 #ifdef IFMEDIA_DEBUG
393 struct ifmedia_description ifm_type_descriptions[] =
394     IFM_TYPE_DESCRIPTIONS;
395 
396 struct ifmedia_description ifm_subtype_ethernet_descriptions[] =
397     IFM_SUBTYPE_ETHERNET_DESCRIPTIONS;
398 
399 struct ifmedia_description ifm_subtype_ethernet_option_descriptions[] =
400     IFM_SUBTYPE_ETHERNET_OPTION_DESCRIPTIONS;
401 
402 struct ifmedia_description ifm_subtype_tokenring_descriptions[] =
403     IFM_SUBTYPE_TOKENRING_DESCRIPTIONS;
404 
405 struct ifmedia_description ifm_subtype_tokenring_option_descriptions[] =
406     IFM_SUBTYPE_TOKENRING_OPTION_DESCRIPTIONS;
407 
408 struct ifmedia_description ifm_subtype_fddi_descriptions[] =
409     IFM_SUBTYPE_FDDI_DESCRIPTIONS;
410 
411 struct ifmedia_description ifm_subtype_fddi_option_descriptions[] =
412     IFM_SUBTYPE_FDDI_OPTION_DESCRIPTIONS;
413 
414 struct ifmedia_description ifm_subtype_ieee80211_descriptions[] =
415     IFM_SUBTYPE_IEEE80211_DESCRIPTIONS;
416 
417 struct ifmedia_description ifm_subtype_ieee80211_option_descriptions[] =
418     IFM_SUBTYPE_IEEE80211_OPTION_DESCRIPTIONS;
419 
420 struct ifmedia_description ifm_subtype_ieee80211_mode_descriptions[] =
421     IFM_SUBTYPE_IEEE80211_MODE_DESCRIPTIONS;
422 
423 struct ifmedia_description ifm_subtype_atm_descriptions[] =
424     IFM_SUBTYPE_ATM_DESCRIPTIONS;
425 
426 struct ifmedia_description ifm_subtype_atm_option_descriptions[] =
427     IFM_SUBTYPE_ATM_OPTION_DESCRIPTIONS;
428 
429 struct ifmedia_description ifm_subtype_shared_descriptions[] =
430     IFM_SUBTYPE_SHARED_DESCRIPTIONS;
431 
432 struct ifmedia_description ifm_shared_option_descriptions[] =
433     IFM_SHARED_OPTION_DESCRIPTIONS;
434 
435 struct ifmedia_type_to_subtype {
436 	struct ifmedia_description *subtypes;
437 	struct ifmedia_description *options;
438 	struct ifmedia_description *modes;
439 };
440 
441 /* must be in the same order as IFM_TYPE_DESCRIPTIONS */
442 struct ifmedia_type_to_subtype ifmedia_types_to_subtypes[] = {
443 	{
444 	  &ifm_subtype_ethernet_descriptions[0],
445 	  &ifm_subtype_ethernet_option_descriptions[0],
446 	  NULL,
447 	},
448 	{
449 	  &ifm_subtype_tokenring_descriptions[0],
450 	  &ifm_subtype_tokenring_option_descriptions[0],
451 	  NULL,
452 	},
453 	{
454 	  &ifm_subtype_fddi_descriptions[0],
455 	  &ifm_subtype_fddi_option_descriptions[0],
456 	  NULL,
457 	},
458 	{
459 	  &ifm_subtype_ieee80211_descriptions[0],
460 	  &ifm_subtype_ieee80211_option_descriptions[0],
461 	  &ifm_subtype_ieee80211_mode_descriptions[0]
462 	},
463 	{
464 	  &ifm_subtype_atm_descriptions[0],
465 	  &ifm_subtype_atm_option_descriptions[0],
466 	  NULL,
467 	},
468 };
469 
470 /*
471  * print a media word.
472  */
473 static void
474 ifmedia_printword(ifmw)
475 	int ifmw;
476 {
477 	struct ifmedia_description *desc;
478 	struct ifmedia_type_to_subtype *ttos;
479 	int seen_option = 0;
480 
481 	/* Find the top-level interface type. */
482 	for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
483 	    desc->ifmt_string != NULL; desc++, ttos++)
484 		if (IFM_TYPE(ifmw) == desc->ifmt_word)
485 			break;
486 	if (desc->ifmt_string == NULL) {
487 		printf("<unknown type>\n");
488 		return;
489 	}
490 	printf("%s", desc->ifmt_string);
491 
492 	/* Any mode. */
493 	for (desc = ttos->modes; desc && desc->ifmt_string != NULL; desc++)
494 		if (IFM_MODE(ifmw) == desc->ifmt_word) {
495 			if (desc->ifmt_string != NULL)
496 				printf(" mode %s", desc->ifmt_string);
497 			break;
498 		}
499 
500 	/*
501 	 * Check for the shared subtype descriptions first, then the
502 	 * type-specific ones.
503 	 */
504 	for (desc = ifm_subtype_shared_descriptions;
505 	    desc->ifmt_string != NULL; desc++)
506 		if (IFM_SUBTYPE(ifmw) == desc->ifmt_word)
507 			goto got_subtype;
508 
509 	for (desc = ttos->subtypes; desc->ifmt_string != NULL; desc++)
510 		if (IFM_SUBTYPE(ifmw) == desc->ifmt_word)
511 			break;
512 	if (desc->ifmt_string == NULL) {
513 		printf(" <unknown subtype>\n");
514 		return;
515 	}
516 
517  got_subtype:
518 	printf(" %s", desc->ifmt_string);
519 
520 	/*
521 	 * Look for shared options.
522 	 */
523 	for (desc = ifm_shared_option_descriptions;
524 	    desc->ifmt_string != NULL; desc++) {
525 		if (ifmw & desc->ifmt_word) {
526 			if (seen_option == 0)
527 				printf(" <");
528 			printf("%s%s", seen_option++ ? "," : "",
529 			    desc->ifmt_string);
530 		}
531 	}
532 
533 	/*
534 	 * Look for subtype-specific options.
535 	 */
536 	for (desc = ttos->options; desc->ifmt_string != NULL; desc++) {
537 		if (ifmw & desc->ifmt_word) {
538 			if (seen_option == 0)
539 				printf(" <");
540 			printf("%s%s", seen_option++ ? "," : "",
541 			    desc->ifmt_string);
542 		}
543 	}
544 	printf("%s\n", seen_option ? ">" : "");
545 }
546 #endif /* IFMEDIA_DEBUG */
547