xref: /freebsd/sys/net/if_media.c (revision cddbc3b40812213ff00041f79174cac0be360a2a)
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_ieee80211_descriptions[] =
403     IFM_SUBTYPE_IEEE80211_DESCRIPTIONS;
404 
405 struct ifmedia_description ifm_subtype_ieee80211_option_descriptions[] =
406     IFM_SUBTYPE_IEEE80211_OPTION_DESCRIPTIONS;
407 
408 struct ifmedia_description ifm_subtype_ieee80211_mode_descriptions[] =
409     IFM_SUBTYPE_IEEE80211_MODE_DESCRIPTIONS;
410 
411 struct ifmedia_description ifm_subtype_atm_descriptions[] =
412     IFM_SUBTYPE_ATM_DESCRIPTIONS;
413 
414 struct ifmedia_description ifm_subtype_atm_option_descriptions[] =
415     IFM_SUBTYPE_ATM_OPTION_DESCRIPTIONS;
416 
417 struct ifmedia_description ifm_subtype_shared_descriptions[] =
418     IFM_SUBTYPE_SHARED_DESCRIPTIONS;
419 
420 struct ifmedia_description ifm_shared_option_descriptions[] =
421     IFM_SHARED_OPTION_DESCRIPTIONS;
422 
423 struct ifmedia_type_to_subtype {
424 	struct ifmedia_description *subtypes;
425 	struct ifmedia_description *options;
426 	struct ifmedia_description *modes;
427 };
428 
429 /* must be in the same order as IFM_TYPE_DESCRIPTIONS */
430 struct ifmedia_type_to_subtype ifmedia_types_to_subtypes[] = {
431 	{
432 	  &ifm_subtype_ethernet_descriptions[0],
433 	  &ifm_subtype_ethernet_option_descriptions[0],
434 	  NULL,
435 	},
436 	{
437 	  &ifm_subtype_ieee80211_descriptions[0],
438 	  &ifm_subtype_ieee80211_option_descriptions[0],
439 	  &ifm_subtype_ieee80211_mode_descriptions[0]
440 	},
441 	{
442 	  &ifm_subtype_atm_descriptions[0],
443 	  &ifm_subtype_atm_option_descriptions[0],
444 	  NULL,
445 	},
446 };
447 
448 /*
449  * print a media word.
450  */
451 static void
452 ifmedia_printword(ifmw)
453 	int ifmw;
454 {
455 	struct ifmedia_description *desc;
456 	struct ifmedia_type_to_subtype *ttos;
457 	int seen_option = 0;
458 
459 	/* Find the top-level interface type. */
460 	for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
461 	    desc->ifmt_string != NULL; desc++, ttos++)
462 		if (IFM_TYPE(ifmw) == desc->ifmt_word)
463 			break;
464 	if (desc->ifmt_string == NULL) {
465 		printf("<unknown type>\n");
466 		return;
467 	}
468 	printf("%s", desc->ifmt_string);
469 
470 	/* Any mode. */
471 	for (desc = ttos->modes; desc && desc->ifmt_string != NULL; desc++)
472 		if (IFM_MODE(ifmw) == desc->ifmt_word) {
473 			if (desc->ifmt_string != NULL)
474 				printf(" mode %s", desc->ifmt_string);
475 			break;
476 		}
477 
478 	/*
479 	 * Check for the shared subtype descriptions first, then the
480 	 * type-specific ones.
481 	 */
482 	for (desc = ifm_subtype_shared_descriptions;
483 	    desc->ifmt_string != NULL; desc++)
484 		if (IFM_SUBTYPE(ifmw) == desc->ifmt_word)
485 			goto got_subtype;
486 
487 	for (desc = ttos->subtypes; desc->ifmt_string != NULL; desc++)
488 		if (IFM_SUBTYPE(ifmw) == desc->ifmt_word)
489 			break;
490 	if (desc->ifmt_string == NULL) {
491 		printf(" <unknown subtype>\n");
492 		return;
493 	}
494 
495  got_subtype:
496 	printf(" %s", desc->ifmt_string);
497 
498 	/*
499 	 * Look for shared options.
500 	 */
501 	for (desc = ifm_shared_option_descriptions;
502 	    desc->ifmt_string != NULL; desc++) {
503 		if (ifmw & desc->ifmt_word) {
504 			if (seen_option == 0)
505 				printf(" <");
506 			printf("%s%s", seen_option++ ? "," : "",
507 			    desc->ifmt_string);
508 		}
509 	}
510 
511 	/*
512 	 * Look for subtype-specific options.
513 	 */
514 	for (desc = ttos->options; desc->ifmt_string != NULL; desc++) {
515 		if (ifmw & desc->ifmt_word) {
516 			if (seen_option == 0)
517 				printf(" <");
518 			printf("%s%s", seen_option++ ? "," : "",
519 			    desc->ifmt_string);
520 		}
521 	}
522 	printf("%s\n", seen_option ? ">" : "");
523 }
524 #endif /* IFMEDIA_DEBUG */
525