xref: /netbsd-src/sys/net/if_media.c (revision 87d689fb734c654d2486f87f7be32f1b53ecdbec)
1 /*	$NetBSD: if_media.c,v 1.35 2017/11/22 03:03:18 ozaki-r 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.35 2017/11/22 03:03:18 ozaki-r 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/malloc.h>
87 
88 #include <net/if.h>
89 #include <net/if_media.h>
90 #include <net/netisr.h>
91 
92 /*
93  * Compile-time options:
94  * IFMEDIA_DEBUG:
95  *	turn on implementation-level debug printfs.
96  * 	Useful for debugging newly-ported  drivers.
97  */
98 
99 #ifdef IFMEDIA_DEBUG
100 int	ifmedia_debug = 0;
101 static	void ifmedia_printword(int);
102 #endif
103 
104 MALLOC_DEFINE(M_IFMEDIA, "ifmedia", "interface media state");
105 
106 /*
107  * Initialize if_media struct for a specific interface instance.
108  */
109 void
110 ifmedia_init(struct ifmedia *ifm, int dontcare_mask,
111     ifm_change_cb_t change_callback, ifm_stat_cb_t status_callback)
112 {
113 
114 	TAILQ_INIT(&ifm->ifm_list);
115 	ifm->ifm_cur = NULL;
116 	ifm->ifm_media = IFM_NONE;
117 	ifm->ifm_mask = dontcare_mask;		/* IF don't-care bits */
118 	ifm->ifm_change = change_callback;
119 	ifm->ifm_status = status_callback;
120 }
121 
122 int
123 ifmedia_change(struct ifmedia *ifm, struct ifnet *ifp)
124 {
125 	return (*ifm->ifm_change)(ifp);
126 }
127 
128 /*
129  * Add a media configuration to the list of supported media
130  * for a specific interface instance.
131  */
132 void
133 ifmedia_add(struct ifmedia *ifm, int mword, int data, void *aux)
134 {
135 	struct ifmedia_entry *entry;
136 
137 #ifdef IFMEDIA_DEBUG
138 	if (ifmedia_debug) {
139 		if (ifm == NULL) {
140 			printf("ifmedia_add: null ifm\n");
141 			return;
142 		}
143 		printf("Adding entry for ");
144 		ifmedia_printword(mword);
145 	}
146 #endif
147 
148 	entry = malloc(sizeof(*entry), M_IFMEDIA, M_NOWAIT);
149 	if (entry == NULL)
150 		panic("ifmedia_add: can't malloc entry");
151 
152 	entry->ifm_media = mword;
153 	entry->ifm_data = data;
154 	entry->ifm_aux = aux;
155 
156 	TAILQ_INSERT_TAIL(&ifm->ifm_list, entry, ifm_list);
157 }
158 
159 /*
160  * Add an array of media configurations to the list of
161  * supported media for a specific interface instance.
162  */
163 void
164 ifmedia_list_add(struct ifmedia *ifm, struct ifmedia_entry *lp, int count)
165 {
166 	int i;
167 
168 	for (i = 0; i < count; i++)
169 		ifmedia_add(ifm, lp[i].ifm_media, lp[i].ifm_data,
170 		    lp[i].ifm_aux);
171 }
172 
173 /*
174  * Set the default active media.
175  *
176  * Called by device-specific code which is assumed to have already
177  * selected the default media in hardware.  We do _not_ call the
178  * media-change callback.
179  */
180 void
181 ifmedia_set(struct ifmedia *ifm, int target)
182 {
183 	struct ifmedia_entry *match;
184 
185 	match = ifmedia_match(ifm, target, ifm->ifm_mask);
186 
187 	/*
188 	 * If we didn't find the requested media, then we try to fall
189 	 * back to target-type (IFM_ETHER, e.g.) | IFM_NONE.  If that's
190 	 * not on the list, then we add it and set the media to it.
191 	 *
192 	 * Since ifmedia_set is almost always called with IFM_AUTO or
193 	 * with a known-good media, this really should only occur if we:
194 	 *
195 	 * a) didn't find any PHYs, or
196 	 * b) didn't find an autoselect option on the PHY when the
197 	 *    parent ethernet driver expected to.
198 	 *
199 	 * In either case, it makes sense to select no media.
200 	 */
201 	if (match == NULL) {
202 		printf("ifmedia_set: no match for 0x%x/0x%x\n",
203 		    target, ~ifm->ifm_mask);
204 		target = (target & IFM_NMASK) | IFM_NONE;
205 		match = ifmedia_match(ifm, target, ifm->ifm_mask);
206 		if (match == NULL) {
207 			ifmedia_add(ifm, target, 0, NULL);
208 			match = ifmedia_match(ifm, target, ifm->ifm_mask);
209 			if (match == NULL)
210 				panic("ifmedia_set failed");
211 		}
212 	}
213 	ifm->ifm_cur = match;
214 
215 #ifdef IFMEDIA_DEBUG
216 	if (ifmedia_debug) {
217 		printf("ifmedia_set: target ");
218 		ifmedia_printword(target);
219 		printf("ifmedia_set: setting to ");
220 		ifmedia_printword(ifm->ifm_cur->ifm_media);
221 	}
222 #endif
223 }
224 
225 /*
226  * Device-independent media ioctl support function.
227  */
228 static int
229 _ifmedia_ioctl(struct ifnet *ifp, struct ifreq *ifr, struct ifmedia *ifm,
230     u_long cmd)
231 {
232 	struct ifmedia_entry *match;
233 	struct ifmediareq *ifmr = (struct ifmediareq *) ifr;
234 	int error = 0;
235 #ifdef OSIOCSIFMEDIA
236 	struct oifreq *oifr = (struct oifreq *)ifr;
237 #endif
238 
239 	if (ifp == NULL || ifr == NULL || ifm == NULL)
240 		return (EINVAL);
241 
242 	switch (cmd) {
243 
244 #ifdef OSIOCSIFMEDIA
245 	case OSIOCSIFMEDIA:
246 		ifr->ifr_media = oifr->ifr_media;
247 		/*FALLTHROUGH*/
248 #endif
249 	/*
250 	 * Set the current media.
251 	 */
252 	case SIOCSIFMEDIA:
253 	{
254 		struct ifmedia_entry *oldentry;
255 		u_int oldmedia;
256 		u_int newmedia = ifr->ifr_media;
257 
258 		match = ifmedia_match(ifm, newmedia, ifm->ifm_mask);
259 		if (match == NULL) {
260 #ifdef IFMEDIA_DEBUG
261 			if (ifmedia_debug) {
262 				printf(
263 				    "ifmedia_ioctl: no media found for 0x%x\n",
264 				    newmedia);
265 			}
266 #endif
267 			return EINVAL;
268 		}
269 
270 		/*
271 		 * If no change, we're done.
272 		 * XXX Automedia may involve software intervention.
273 		 *     Keep going in case the connected media changed.
274 		 *     Similarly, if best match changed (kernel debugger?).
275 		 */
276 		if ((IFM_SUBTYPE(newmedia) != IFM_AUTO) &&
277 		    (newmedia == ifm->ifm_media) &&
278 		    (match == ifm->ifm_cur))
279 			return 0;
280 
281 		/*
282 		 * We found a match, now make the driver switch to it.
283 		 * Make sure to preserve our old media type in case the
284 		 * driver can't switch.
285 		 */
286 #ifdef IFMEDIA_DEBUG
287 		if (ifmedia_debug) {
288 			printf("ifmedia_ioctl: switching %s to ",
289 			    ifp->if_xname);
290 			ifmedia_printword(match->ifm_media);
291 		}
292 #endif
293 		oldentry = ifm->ifm_cur;
294 		oldmedia = ifm->ifm_media;
295 		ifm->ifm_cur = match;
296 		ifm->ifm_media = newmedia;
297 		error = ifmedia_change(ifm, ifp);
298 		if (error) {
299 			ifm->ifm_cur = oldentry;
300 			ifm->ifm_media = oldmedia;
301 		}
302 		break;
303 	}
304 
305 	/*
306 	 * Get list of available media and current media on interface.
307 	 */
308 	case SIOCGIFMEDIA:
309 	{
310 		struct ifmedia_entry *ep;
311 		size_t nwords;
312 
313 		if (ifmr->ifm_count < 0)
314 			return EINVAL;
315 
316 		ifmr->ifm_active = ifmr->ifm_current = ifm->ifm_cur ?
317 		    ifm->ifm_cur->ifm_media : IFM_NONE;
318 		ifmr->ifm_mask = ifm->ifm_mask;
319 		ifmr->ifm_status = 0;
320 		/* ifmedia_status */
321 		(*ifm->ifm_status)(ifp, ifmr);
322 
323 		/*
324 		 * Count them so we know a-priori how much is the max we'll
325 		 * need.
326 		 */
327 		ep = TAILQ_FIRST(&ifm->ifm_list);
328 		for (nwords = 0; ep != NULL; ep = TAILQ_NEXT(ep, ifm_list))
329 			nwords++;
330 
331 		if (ifmr->ifm_count != 0) {
332 			size_t count;
333 			size_t minwords = nwords > (size_t)ifmr->ifm_count
334 			    ? (size_t)ifmr->ifm_count
335 			    : nwords;
336 			int *kptr = (int *)malloc(minwords * sizeof(int),
337 			    M_TEMP, M_WAITOK);
338 			/*
339 			 * Get the media words from the interface's list.
340 			 */
341 			ep = TAILQ_FIRST(&ifm->ifm_list);
342 			for (count = 0; ep != NULL && count < minwords;
343 			    ep = TAILQ_NEXT(ep, ifm_list), count++)
344 				kptr[count] = ep->ifm_media;
345 
346 			error = copyout(kptr, ifmr->ifm_ulist,
347 			    minwords * sizeof(int));
348 			if (error == 0 && ep != NULL)
349 				error = E2BIG;	/* oops! */
350 			free(kptr, M_TEMP);
351 		}
352 		ifmr->ifm_count = nwords;
353 		break;
354 	}
355 
356 	default:
357 		return EINVAL;
358 	}
359 
360 	return error;
361 }
362 
363 int
364 ifmedia_ioctl(struct ifnet *ifp, struct ifreq *ifr, struct ifmedia *ifm,
365     u_long cmd)
366 {
367 	int e;
368 
369 	/*
370 	 * If if_is_mpsafe(ifp), KERNEL_LOCK isn't held here, but _ifmedia_ioctl
371 	 * isn't MP-safe yet, so we must hold the lock.
372 	 */
373 	KERNEL_LOCK_IF_IFP_MPSAFE(ifp);
374 	e = _ifmedia_ioctl(ifp, ifr, ifm, cmd);
375 	KERNEL_UNLOCK_IF_IFP_MPSAFE(ifp);
376 	return e;
377 }
378 
379 /*
380  * Find media entry matching a given ifm word.
381  */
382 struct ifmedia_entry *
383 ifmedia_match(struct ifmedia *ifm, u_int target, u_int mask)
384 {
385 	struct ifmedia_entry *match, *next;
386 
387 	match = NULL;
388 	mask = ~mask;
389 
390 	for (next = TAILQ_FIRST(&ifm->ifm_list); next != NULL;
391 	     next = TAILQ_NEXT(next, ifm_list)) {
392 		if ((next->ifm_media & mask) == (target & mask)) {
393 			if (match) {
394 #if defined(IFMEDIA_DEBUG) || defined(DIAGNOSTIC)
395 				printf("ifmedia_match: multiple match for "
396 				    "0x%x/0x%x, selected instance %d\n",
397 				    target, mask, IFM_INST(match->ifm_media));
398 #endif
399 				break;
400 			}
401 			match = next;
402 		}
403 	}
404 
405 	return match;
406 }
407 
408 /*
409  * Delete all media for a given instance.
410  */
411 void
412 ifmedia_delete_instance(struct ifmedia *ifm, u_int inst)
413 {
414 	struct ifmedia_entry *ife, *nife;
415 
416 	for (ife = TAILQ_FIRST(&ifm->ifm_list); ife != NULL; ife = nife) {
417 		nife = TAILQ_NEXT(ife, ifm_list);
418 		if (inst == IFM_INST_ANY ||
419 		    inst == IFM_INST(ife->ifm_media)) {
420 			TAILQ_REMOVE(&ifm->ifm_list, ife, ifm_list);
421 			free(ife, M_IFMEDIA);
422 		}
423 	}
424 	if (inst == IFM_INST_ANY) {
425 		ifm->ifm_cur = NULL;
426 		ifm->ifm_media = IFM_NONE;
427 	}
428 }
429 
430 void
431 ifmedia_removeall(struct ifmedia *ifm)
432 {
433 
434 	ifmedia_delete_instance(ifm, IFM_INST_ANY);
435 }
436 
437 
438 /*
439  * Compute the interface `baudrate' from the media, for the interface
440  * metrics (used by routing daemons).
441  */
442 static const struct ifmedia_baudrate ifmedia_baudrate_descriptions[] =
443     IFM_BAUDRATE_DESCRIPTIONS;
444 
445 uint64_t
446 ifmedia_baudrate(int mword)
447 {
448 	int i;
449 
450 	for (i = 0; ifmedia_baudrate_descriptions[i].ifmb_word != 0; i++) {
451 		if ((mword & (IFM_NMASK|IFM_TMASK)) ==
452 		    ifmedia_baudrate_descriptions[i].ifmb_word)
453 			return (ifmedia_baudrate_descriptions[i].ifmb_baudrate);
454 	}
455 
456 	/* Not known. */
457 	return 0;
458 }
459 
460 #ifdef IFMEDIA_DEBUG
461 
462 static const struct ifmedia_description ifm_type_descriptions[] =
463     IFM_TYPE_DESCRIPTIONS;
464 
465 static const struct ifmedia_description ifm_subtype_descriptions[] =
466     IFM_SUBTYPE_DESCRIPTIONS;
467 
468 static const struct ifmedia_description ifm_option_descriptions[] =
469     IFM_OPTION_DESCRIPTIONS;
470 
471 /*
472  * print a media word.
473  */
474 static void
475 ifmedia_printword(int ifmw)
476 {
477 	const struct ifmedia_description *desc;
478 	int seen_option = 0;
479 
480 	/* Print the top-level interface type. */
481 	for (desc = ifm_type_descriptions; desc->ifmt_string != NULL;
482 	     desc++) {
483 		if (IFM_TYPE(ifmw) == desc->ifmt_word)
484 			break;
485 	}
486 	if (desc->ifmt_string == NULL)
487 		printf("<unknown type> ");
488 	else
489 		printf("%s ", desc->ifmt_string);
490 
491 	/* Print the subtype. */
492 	for (desc = ifm_subtype_descriptions; desc->ifmt_string != NULL;
493 	     desc++) {
494 		if (IFM_TYPE_MATCH(desc->ifmt_word, ifmw) &&
495 		    IFM_SUBTYPE(desc->ifmt_word) == IFM_SUBTYPE(ifmw))
496 			break;
497 	}
498 	if (desc->ifmt_string == NULL)
499 		printf("<unknown subtype>");
500 	else
501 		printf("%s", desc->ifmt_string);
502 
503 	/* Print any options. */
504 	for (desc = ifm_option_descriptions; desc->ifmt_string != NULL;
505 	     desc++) {
506 		if (IFM_TYPE_MATCH(desc->ifmt_word, ifmw) &&
507 		    (ifmw & desc->ifmt_word) != 0 &&
508 		    (seen_option & IFM_OPTIONS(desc->ifmt_word)) == 0) {
509 			if (seen_option == 0)
510 				printf(" <");
511 			printf("%s%s", seen_option ? "," : "",
512 			    desc->ifmt_string);
513 			seen_option |= IFM_OPTIONS(desc->ifmt_word);
514 		}
515 	}
516 	printf("%s\n", seen_option ? ">" : "");
517 }
518 
519 #endif /* IFMEDIA_DEBUG */
520