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