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