xref: /haiku/src/libs/compat/freebsd_network/fbsd_mii.c (revision 922e7ba1f3228e6f28db69b0ded8f86eb32dea17)
1 /*	$NetBSD: mii.c,v 1.12 1999/08/03 19:41:49 drochner 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 #ifdef __HAIKU__
33 #include "device.h"
34 
35 #define malloc(size, tag, flags) kernel_malloc(size, tag, flags)
36 #define free(pointer, tag) kernel_free(pointer, tag)
37 #endif
38 
39 #include <sys/cdefs.h>
40 __FBSDID("$FreeBSD$");
41 
42 /*
43  * MII bus layer, glues MII-capable network interface drivers to sharable
44  * PHY drivers.  This exports an interface compatible with BSD/OS 3.0's,
45  * plus some NetBSD extensions.
46  */
47 
48 #include <sys/param.h>
49 #include <sys/systm.h>
50 #include <sys/socket.h>
51 #include <sys/malloc.h>
52 #include <sys/module.h>
53 #include <sys/bus.h>
54 
55 #include <net/if.h>
56 #include <net/if_media.h>
57 #include <net/route.h>
58 
59 #include <dev/mii/mii.h>
60 #include <dev/mii/miivar.h>
61 
62 MODULE_VERSION(miibus, 1);
63 
64 #include "miibus_if.h"
65 
66 static int miibus_print_child(device_t dev, device_t child);
67 static int miibus_read_ivar(device_t dev, device_t child, int which,
68     uintptr_t *result);
69 static int miibus_child_location_str(device_t bus, device_t child, char *buf,
70     size_t buflen);
71 static int miibus_child_pnpinfo_str(device_t bus, device_t child, char *buf,
72     size_t buflen);
73 static int miibus_readreg(device_t, int, int);
74 static int miibus_writereg(device_t, int, int, int);
75 static void miibus_statchg(device_t);
76 static void miibus_linkchg(device_t);
77 static void miibus_mediainit(device_t);
78 
79 static device_method_t miibus_methods[] = {
80 	/* device interface */
81 	DEVMETHOD(device_probe,		miibus_probe),
82 	DEVMETHOD(device_attach,	miibus_attach),
83 	DEVMETHOD(device_detach,	miibus_detach),
84 	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
85 
86 	/* bus interface */
87 	DEVMETHOD(bus_print_child,	miibus_print_child),
88 	DEVMETHOD(bus_read_ivar,	miibus_read_ivar),
89 	DEVMETHOD(bus_driver_added,	bus_generic_driver_added),
90 	DEVMETHOD(bus_child_pnpinfo_str, miibus_child_pnpinfo_str),
91 	DEVMETHOD(bus_child_location_str, miibus_child_location_str),
92 
93 	/* MII interface */
94 	DEVMETHOD(miibus_readreg,	miibus_readreg),
95 	DEVMETHOD(miibus_writereg,	miibus_writereg),
96 	DEVMETHOD(miibus_statchg,	miibus_statchg),
97 	DEVMETHOD(miibus_linkchg,	miibus_linkchg),
98 	DEVMETHOD(miibus_mediainit,	miibus_mediainit),
99 
100 	{ 0, 0 }
101 };
102 
103 devclass_t miibus_devclass;
104 
105 driver_t miibus_driver = {
106 	"miibus",
107 	miibus_methods,
108 	sizeof(struct mii_data)
109 };
110 
111 struct miibus_ivars {
112 	struct ifnet	*ifp;
113 	ifm_change_cb_t	ifmedia_upd;
114 	ifm_stat_cb_t	ifmedia_sts;
115 	int		mii_flags;
116 };
117 
118 int
119 miibus_probe(device_t dev)
120 {
121 
122 	device_set_desc(dev, "MII bus");
123 
124 	return (BUS_PROBE_SPECIFIC);
125 }
126 
127 int
128 miibus_attach(device_t dev)
129 {
130 	struct miibus_ivars	*ivars;
131 	struct mii_attach_args	*ma;
132 	struct mii_data		*mii;
133 	device_t		*children;
134 	int			i, nchildren;
135 
136 	mii = device_get_softc(dev);
137 	nchildren = 0;
138 	if (device_get_children(dev, &children, &nchildren) == 0) {
139 		for (i = 0; i < nchildren; i++) {
140 			ma = device_get_ivars(children[i]);
141 			ma->mii_data = mii;
142 		}
143 		free(children, M_TEMP);
144 	}
145 	if (nchildren == 0) {
146 		device_printf(dev, "cannot get children\n");
147 		return (ENXIO);
148 	}
149 	ivars = device_get_ivars(dev);
150 	ifmedia_init(&mii->mii_media, IFM_IMASK, ivars->ifmedia_upd,
151 	    ivars->ifmedia_sts);
152 	mii->mii_ifp = ivars->ifp;
153 	mii->mii_ifp->if_capabilities |= IFCAP_LINKSTATE;
154 	mii->mii_ifp->if_capenable |= IFCAP_LINKSTATE;
155 	LIST_INIT(&mii->mii_phys);
156 
157 	return (bus_generic_attach(dev));
158 }
159 
160 int
161 miibus_detach(device_t dev)
162 {
163 	struct mii_data		*mii;
164 
165 	bus_generic_detach(dev);
166 	mii = device_get_softc(dev);
167 	ifmedia_removeall(&mii->mii_media);
168 	mii->mii_ifp = NULL;
169 
170 	return (0);
171 }
172 
173 static int
174 miibus_print_child(device_t dev, device_t child)
175 {
176 	struct mii_attach_args *ma;
177 	int retval;
178 
179 	ma = device_get_ivars(child);
180 	retval = bus_print_child_header(dev, child);
181 	retval += printf(" PHY %d", ma->mii_phyno);
182 	retval += bus_print_child_footer(dev, child);
183 
184 	return (retval);
185 }
186 
187 static int
188 miibus_read_ivar(device_t dev, device_t child __unused, int which,
189     uintptr_t *result)
190 {
191 	struct miibus_ivars *ivars;
192 
193 	/*
194 	 * NB: this uses the instance variables of the miibus rather than
195 	 * its PHY children.
196 	 */
197 	ivars = device_get_ivars(dev);
198 	switch (which) {
199 	case MIIBUS_IVAR_FLAGS:
200 		*result = ivars->mii_flags;
201 		break;
202 	default:
203 		return (ENOENT);
204 	}
205 	return (0);
206 }
207 
208 static int
209 miibus_child_pnpinfo_str(device_t bus __unused, device_t child, char *buf,
210     size_t buflen)
211 {
212 	struct mii_attach_args *ma;
213 
214 	ma = device_get_ivars(child);
215 	snprintf(buf, buflen, "oui=0x%x model=0x%x rev=0x%x",
216 	    MII_OUI(ma->mii_id1, ma->mii_id2),
217 	    MII_MODEL(ma->mii_id2), MII_REV(ma->mii_id2));
218 	return (0);
219 }
220 
221 static int
222 miibus_child_location_str(device_t bus __unused, device_t child, char *buf,
223     size_t buflen)
224 {
225 	struct mii_attach_args *ma;
226 
227 	ma = device_get_ivars(child);
228 	snprintf(buf, buflen, "phyno=%d", ma->mii_phyno);
229 	return (0);
230 }
231 
232 static int
233 miibus_readreg(device_t dev, int phy, int reg)
234 {
235 	device_t		parent;
236 
237 	parent = device_get_parent(dev);
238 	return (MIIBUS_READREG(parent, phy, reg));
239 }
240 
241 static int
242 miibus_writereg(device_t dev, int phy, int reg, int data)
243 {
244 	device_t		parent;
245 
246 	parent = device_get_parent(dev);
247 	return (MIIBUS_WRITEREG(parent, phy, reg, data));
248 }
249 
250 static void
251 miibus_statchg(device_t dev)
252 {
253 	device_t		parent;
254 	struct mii_data		*mii;
255 	struct ifnet		*ifp;
256 
257 	parent = device_get_parent(dev);
258 	MIIBUS_STATCHG(parent);
259 
260 	mii = device_get_softc(dev);
261 
262 	/*
263 	 * Note that each NIC's softc must start with an ifnet pointer.
264 	 * XXX: EVIL HACK!
265 	 */
266 	ifp = *(struct ifnet **)device_get_softc(parent);
267 	ifp->if_baudrate = ifmedia_baudrate(mii->mii_media_active);
268 }
269 
270 static void
271 miibus_linkchg(device_t dev)
272 {
273 	struct mii_data		*mii;
274 	device_t		parent;
275 	int			link_state;
276 
277 	parent = device_get_parent(dev);
278 	MIIBUS_LINKCHG(parent);
279 
280 	mii = device_get_softc(dev);
281 
282 	if (mii->mii_media_status & IFM_AVALID) {
283 		if (mii->mii_media_status & IFM_ACTIVE)
284 			link_state = LINK_STATE_UP;
285 		else
286 			link_state = LINK_STATE_DOWN;
287 	} else
288 		link_state = LINK_STATE_UNKNOWN;
289 	/*
290 	 * Note that each NIC's softc must start with an ifnet pointer.
291 	 * XXX: EVIL HACK!
292 	 */
293 	if_link_state_change(*(struct ifnet**)device_get_softc(parent), link_state);
294 }
295 
296 static void
297 miibus_mediainit(device_t dev)
298 {
299 	struct mii_data		*mii;
300 	struct ifmedia_entry	*m;
301 	int			media = 0;
302 
303 	/* Poke the parent in case it has any media of its own to add. */
304 	MIIBUS_MEDIAINIT(device_get_parent(dev));
305 
306 	mii = device_get_softc(dev);
307 	LIST_FOREACH(m, &mii->mii_media.ifm_list, ifm_list) {
308 		media = m->ifm_media;
309 		if (media == (IFM_ETHER | IFM_AUTO))
310 			break;
311 	}
312 
313 	ifmedia_set(&mii->mii_media, media);
314 }
315 
316 /*
317  * Helper function used by network interface drivers, attaches the miibus and
318  * the PHYs to the network interface driver parent.
319  */
320 int
321 mii_attach(device_t dev, device_t *miibus, struct ifnet *ifp,
322     ifm_change_cb_t ifmedia_upd, ifm_stat_cb_t ifmedia_sts, int capmask,
323     int phyloc, int offloc, int flags)
324 {
325 	struct miibus_ivars *ivars;
326 	struct mii_attach_args ma, *args;
327 	device_t *children, phy;
328 	int bmsr, first, i, nchildren, offset, phymax, phymin, rv;
329 
330 	if (phyloc != MII_PHY_ANY && offloc != MII_OFFSET_ANY) {
331 		printf("%s: phyloc and offloc specified\n", __func__);
332 		return (EINVAL);
333 	}
334 
335 	if (offloc != MII_OFFSET_ANY && (offloc < 0 || offloc >= MII_NPHY)) {
336 		printf("%s: ivalid offloc %d\n", __func__, offloc);
337 		return (EINVAL);
338 	}
339 
340 	if (phyloc == MII_PHY_ANY) {
341 		phymin = 0;
342 		phymax = MII_NPHY - 1;
343 	} else {
344 		if (phyloc < 0 || phyloc >= MII_NPHY) {
345 			printf("%s: ivalid phyloc %d\n", __func__, phyloc);
346 			return (EINVAL);
347 		}
348 		phymin = phymax = phyloc;
349 	}
350 
351 	first = 0;
352 	if (*miibus == NULL) {
353 		first = 1;
354 		ivars = malloc(sizeof(*ivars), M_DEVBUF, M_NOWAIT);
355 		if (ivars == NULL)
356 			return (ENOMEM);
357 		ivars->ifp = ifp;
358 		ivars->ifmedia_upd = ifmedia_upd;
359 		ivars->ifmedia_sts = ifmedia_sts;
360 		ivars->mii_flags = flags;
361 		*miibus = device_add_child(dev, "miibus", -1);
362 		if (*miibus == NULL) {
363 			rv = ENXIO;
364 			goto fail;
365 		}
366 		device_set_ivars(*miibus, ivars);
367 	} else {
368 		ivars = device_get_ivars(*miibus);
369 		if (ivars->ifp != ifp || ivars->ifmedia_upd != ifmedia_upd ||
370 		    ivars->ifmedia_sts != ifmedia_sts ||
371 		    ivars->mii_flags != flags) {
372 			printf("%s: non-matching invariant\n", __func__);
373 			return (EINVAL);
374 		}
375 		/*
376 		 * Assignment of the attach arguments mii_data for the first
377 		 * pass is done in miibus_attach(), i.e. once the miibus softc
378 		 * has been allocated.
379 		 */
380 		ma.mii_data = device_get_softc(*miibus);
381 	}
382 
383 	ma.mii_capmask = capmask;
384 
385 	phy = NULL;
386 	offset = 0;
387 	for (ma.mii_phyno = phymin; ma.mii_phyno <= phymax; ma.mii_phyno++) {
388 		/*
389 		 * Make sure we haven't already configured a PHY at this
390 		 * address.  This allows mii_attach() to be called
391 		 * multiple times.
392 		 */
393 		if (device_get_children(*miibus, &children, &nchildren) == 0) {
394 			for (i = 0; i < nchildren; i++) {
395 				args = device_get_ivars(children[i]);
396 				if (args->mii_phyno == ma.mii_phyno) {
397 					/*
398 					 * Yes, there is already something
399 					 * configured at this address.
400 					 */
401 					free(children, M_TEMP);
402 					goto skip;
403 				}
404 			}
405 			free(children, M_TEMP);
406 		}
407 
408 		/*
409 		 * Check to see if there is a PHY at this address.  Note,
410 		 * many braindead PHYs report 0/0 in their ID registers,
411 		 * so we test for media in the BMSR.
412 	 	 */
413 		bmsr = MIIBUS_READREG(dev, ma.mii_phyno, MII_BMSR);
414 		if (bmsr == 0 || bmsr == 0xffff ||
415 		    (bmsr & (BMSR_EXTSTAT | BMSR_MEDIAMASK)) == 0) {
416 			/* Assume no PHY at this address. */
417 			continue;
418 		}
419 
420 		/*
421 		 * There is a PHY at this address.  If we were given an
422 		 * `offset' locator, skip this PHY if it doesn't match.
423 		 */
424 		if (offloc != MII_OFFSET_ANY && offloc != offset)
425 			goto skip;
426 
427 		/*
428 		 * Extract the IDs. Braindead PHYs will be handled by
429 		 * the `ukphy' driver, as we have no ID information to
430 		 * match on.
431 	 	 */
432 		ma.mii_id1 = MIIBUS_READREG(dev, ma.mii_phyno, MII_PHYIDR1);
433 		ma.mii_id2 = MIIBUS_READREG(dev, ma.mii_phyno, MII_PHYIDR2);
434 
435 		args = malloc(sizeof(struct mii_attach_args), M_DEVBUF,
436 		    M_NOWAIT);
437 		if (args == NULL)
438 			goto skip;
439 		bcopy((char *)&ma, (char *)args, sizeof(ma));
440 		phy = device_add_child(*miibus, NULL, -1);
441 		if (phy == NULL) {
442 			free(args, M_DEVBUF);
443 			goto skip;
444 		}
445 		device_set_ivars(phy, args);
446  skip:
447 		offset++;
448 	}
449 
450 	if (first != 0) {
451 		if (phy == NULL) {
452 			rv = ENXIO;
453 			goto fail;
454 		}
455 		rv = bus_generic_attach(dev);
456 		if (rv != 0)
457 			goto fail;
458 
459 		/* Attaching of the PHY drivers is done in miibus_attach(). */
460 		return (0);
461 	}
462 	rv = bus_generic_attach(*miibus);
463 	if (rv != 0)
464 		goto fail;
465 
466 	return (0);
467 
468  fail:
469 	if (*miibus != NULL)
470 		device_delete_child(dev, *miibus);
471 	free(ivars, M_DEVBUF);
472 	if (first != 0)
473 		*miibus = NULL;
474 	return (rv);
475 }
476 
477 int
478 mii_phy_probe(device_t dev, device_t *child, ifm_change_cb_t ifmedia_upd,
479     ifm_stat_cb_t ifmedia_sts)
480 {
481 	struct ifnet *ifp;
482 
483 	/*
484 	 * Note that each NIC's softc must start with an ifnet pointer.
485 	 * XXX: EVIL HACK!
486 	 */
487 	ifp = *(struct ifnet **)device_get_softc(dev);
488 	return (mii_attach(dev, child, ifp, ifmedia_upd, ifmedia_sts,
489 	    BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, 0));
490 }
491 
492 /*
493  * Media changed; notify all PHYs.
494  */
495 int
496 mii_mediachg(struct mii_data *mii)
497 {
498 	struct mii_softc *child;
499 	struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
500 	int rv;
501 
502 	mii->mii_media_status = 0;
503 	mii->mii_media_active = IFM_NONE;
504 
505 	LIST_FOREACH(child, &mii->mii_phys, mii_list) {
506 		/*
507 		 * If the media indicates a different PHY instance,
508 		 * isolate this one.
509 		 */
510 		if (IFM_INST(ife->ifm_media) != child->mii_inst) {
511 			if ((child->mii_flags & MIIF_NOISOLATE) != 0) {
512 				device_printf(child->mii_dev, "%s: "
513 				    "can't handle non-zero PHY instance %d\n",
514 				    __func__, child->mii_inst);
515 				continue;
516 			}
517 			PHY_WRITE(child, MII_BMCR, PHY_READ(child, MII_BMCR) |
518 			    BMCR_ISO);
519 			continue;
520 		}
521 		rv = (*child->mii_service)(child, mii, MII_MEDIACHG);
522 		if (rv)
523 			return (rv);
524 	}
525 	return (0);
526 }
527 
528 /*
529  * Call the PHY tick routines, used during autonegotiation.
530  */
531 void
532 mii_tick(struct mii_data *mii)
533 {
534 	struct mii_softc *child;
535 	struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
536 
537 	LIST_FOREACH(child, &mii->mii_phys, mii_list) {
538 		/*
539 		 * If this PHY instance isn't currently selected, just skip
540 		 * it.
541 		 */
542 		if (IFM_INST(ife->ifm_media) != child->mii_inst)
543 			continue;
544 		(void)(*child->mii_service)(child, mii, MII_TICK);
545 	}
546 }
547 
548 /*
549  * Get media status from PHYs.
550  */
551 void
552 mii_pollstat(struct mii_data *mii)
553 {
554 	struct mii_softc *child;
555 	struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
556 
557 	mii->mii_media_status = 0;
558 	mii->mii_media_active = IFM_NONE;
559 
560 	LIST_FOREACH(child, &mii->mii_phys, mii_list) {
561 		/*
562 		 * If we're not polling this PHY instance, just skip it.
563 		 */
564 		if (IFM_INST(ife->ifm_media) != child->mii_inst)
565 			continue;
566 		(void)(*child->mii_service)(child, mii, MII_POLLSTAT);
567 	}
568 }
569 
570 /*
571  * Inform the PHYs that the interface is down.
572  */
573 void
574 mii_down(struct mii_data *mii)
575 {
576 	struct mii_softc *child;
577 
578 	LIST_FOREACH(child, &mii->mii_phys, mii_list)
579 		mii_phy_down(child);
580 }
581