xref: /onnv-gate/usr/src/uts/common/io/mii/mii.c (revision 11428:30da49fa0630)
19860Sgdamore@opensolaris.org /*
29860Sgdamore@opensolaris.org  * CDDL HEADER START
39860Sgdamore@opensolaris.org  *
49860Sgdamore@opensolaris.org  * The contents of this file are subject to the terms of the
59860Sgdamore@opensolaris.org  * Common Development and Distribution License (the "License").
69860Sgdamore@opensolaris.org  * You may not use this file except in compliance with the License.
79860Sgdamore@opensolaris.org  *
89860Sgdamore@opensolaris.org  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
99860Sgdamore@opensolaris.org  * or http://www.opensolaris.org/os/licensing.
109860Sgdamore@opensolaris.org  * See the License for the specific language governing permissions
119860Sgdamore@opensolaris.org  * and limitations under the License.
129860Sgdamore@opensolaris.org  *
139860Sgdamore@opensolaris.org  * When distributing Covered Code, include this CDDL HEADER in each
149860Sgdamore@opensolaris.org  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
159860Sgdamore@opensolaris.org  * If applicable, add the following below this CDDL HEADER, with the
169860Sgdamore@opensolaris.org  * fields enclosed by brackets "[]" replaced with your own identifying
179860Sgdamore@opensolaris.org  * information: Portions Copyright [yyyy] [name of copyright owner]
189860Sgdamore@opensolaris.org  *
199860Sgdamore@opensolaris.org  * CDDL HEADER END
209860Sgdamore@opensolaris.org  */
219860Sgdamore@opensolaris.org /*
2211414Sgdamore@opensolaris.org  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
239860Sgdamore@opensolaris.org  * Use is subject to license terms.
249860Sgdamore@opensolaris.org  */
259860Sgdamore@opensolaris.org 
269860Sgdamore@opensolaris.org /*
279860Sgdamore@opensolaris.org  * mii - MII/PHY support for MAC drivers
289860Sgdamore@opensolaris.org  *
299860Sgdamore@opensolaris.org  * Utility module to provide a consistent interface to a MAC driver accross
309860Sgdamore@opensolaris.org  * different implementations of PHY devices
319860Sgdamore@opensolaris.org  */
329860Sgdamore@opensolaris.org 
339860Sgdamore@opensolaris.org #include <sys/types.h>
349860Sgdamore@opensolaris.org #include <sys/debug.h>
359860Sgdamore@opensolaris.org #include <sys/errno.h>
369860Sgdamore@opensolaris.org #include <sys/param.h>
379860Sgdamore@opensolaris.org #include <sys/kmem.h>
389860Sgdamore@opensolaris.org #include <sys/conf.h>
399860Sgdamore@opensolaris.org #include <sys/ddi.h>
409860Sgdamore@opensolaris.org #include <sys/sunddi.h>
419860Sgdamore@opensolaris.org #include <sys/modctl.h>
429860Sgdamore@opensolaris.org #include <sys/cmn_err.h>
439860Sgdamore@opensolaris.org #include <sys/policy.h>
449860Sgdamore@opensolaris.org #include <sys/note.h>
459860Sgdamore@opensolaris.org #include <sys/strsun.h>
469860Sgdamore@opensolaris.org #include <sys/miiregs.h>
479860Sgdamore@opensolaris.org #include <sys/mac_provider.h>
489860Sgdamore@opensolaris.org #include <sys/mac_ether.h>
499860Sgdamore@opensolaris.org #include <sys/mii.h>
509860Sgdamore@opensolaris.org #include "miipriv.h"
519860Sgdamore@opensolaris.org 
529860Sgdamore@opensolaris.org #define	MII_SECOND	1000000
539860Sgdamore@opensolaris.org 
549860Sgdamore@opensolaris.org /* indices into error array */
559860Sgdamore@opensolaris.org enum {
569860Sgdamore@opensolaris.org 	MII_EOK = 0,
579860Sgdamore@opensolaris.org 	MII_ERESET,
589860Sgdamore@opensolaris.org 	MII_ESTART,
599860Sgdamore@opensolaris.org 	MII_ENOPHY,
609860Sgdamore@opensolaris.org 	MII_ECHECK,
6110359SGarrett.Damore@Sun.COM 	MII_ELOOP,
629860Sgdamore@opensolaris.org };
639860Sgdamore@opensolaris.org 
649860Sgdamore@opensolaris.org static const char *mii_errors[] = {
659860Sgdamore@opensolaris.org 	"",
669860Sgdamore@opensolaris.org 	"Failure resetting PHY.",
679860Sgdamore@opensolaris.org 	"Failure starting PHY.",
689860Sgdamore@opensolaris.org 	"No Ethernet PHY found.",
6910359SGarrett.Damore@Sun.COM 	"Failure reading PHY (removed?)",
7010359SGarrett.Damore@Sun.COM 	"Failure setting loopback."
719860Sgdamore@opensolaris.org };
729860Sgdamore@opensolaris.org 
739860Sgdamore@opensolaris.org /* Indexed by XCVR_ type */
749860Sgdamore@opensolaris.org static const const char *mii_xcvr_types[] = {
759860Sgdamore@opensolaris.org 	"Undefined",
769860Sgdamore@opensolaris.org 	"Unknown",
779860Sgdamore@opensolaris.org 	"10 Mbps",
789860Sgdamore@opensolaris.org 	"100BASE-T4",
799860Sgdamore@opensolaris.org 	"100BASE-X",
809860Sgdamore@opensolaris.org 	"100BASE-T2",
819860Sgdamore@opensolaris.org 	"1000BASE-X",
829860Sgdamore@opensolaris.org 	"1000BASE-T"
839860Sgdamore@opensolaris.org };
849860Sgdamore@opensolaris.org 
859860Sgdamore@opensolaris.org /* state machine */
869860Sgdamore@opensolaris.org typedef enum {
879860Sgdamore@opensolaris.org 	MII_STATE_PROBE = 0,
889860Sgdamore@opensolaris.org 	MII_STATE_RESET,
899860Sgdamore@opensolaris.org 	MII_STATE_START,
909860Sgdamore@opensolaris.org 	MII_STATE_RUN,
9110359SGarrett.Damore@Sun.COM 	MII_STATE_LOOPBACK,
929860Sgdamore@opensolaris.org } mii_tstate_t;
939860Sgdamore@opensolaris.org 
949860Sgdamore@opensolaris.org struct mii_handle {
959860Sgdamore@opensolaris.org 	dev_info_t	*m_dip;
969860Sgdamore@opensolaris.org 	void		*m_private;
979860Sgdamore@opensolaris.org 	mii_ops_t	m_ops;
989860Sgdamore@opensolaris.org 
999860Sgdamore@opensolaris.org 	kt_did_t	m_tq_id;
1009860Sgdamore@opensolaris.org 	kmutex_t	m_lock;
1019860Sgdamore@opensolaris.org 	kcondvar_t	m_cv;
1029860Sgdamore@opensolaris.org 	ddi_taskq_t	*m_tq;
1039860Sgdamore@opensolaris.org 	int		m_flags;
1049860Sgdamore@opensolaris.org 
1059860Sgdamore@opensolaris.org 	boolean_t	m_started;
1069860Sgdamore@opensolaris.org 	boolean_t	m_suspending;
1079860Sgdamore@opensolaris.org 	boolean_t	m_suspended;
1089860Sgdamore@opensolaris.org 	int		m_error;
1099860Sgdamore@opensolaris.org 	mii_tstate_t	m_tstate;
1109860Sgdamore@opensolaris.org 
1119860Sgdamore@opensolaris.org #define	MII_FLAG_EXIT		0x1	/* exit the thread */
1129860Sgdamore@opensolaris.org #define	MII_FLAG_STOP		0x2	/* shutdown MII monitoring */
1139860Sgdamore@opensolaris.org #define	MII_FLAG_RESET		0x4	/* reset the MII */
1149860Sgdamore@opensolaris.org #define	MII_FLAG_PROBE		0x8	/* probe for PHYs */
1159860Sgdamore@opensolaris.org #define	MII_FLAG_NOTIFY		0x10	/* notify about a change */
1169860Sgdamore@opensolaris.org #define	MII_FLAG_SUSPEND	0x20	/* monitoring suspended */
1179860Sgdamore@opensolaris.org #define	MII_FLAG_MACRESET	0x40	/* send reset to MAC */
1189860Sgdamore@opensolaris.org #define	MII_FLAG_PHYSTART	0x80	/* start up the PHY */
1199860Sgdamore@opensolaris.org 
1209860Sgdamore@opensolaris.org 	/* device name for printing, e.g. "hme0" */
1219860Sgdamore@opensolaris.org 	char		m_name[MODMAXNAMELEN + 16];
1229860Sgdamore@opensolaris.org 
1239860Sgdamore@opensolaris.org 	int		m_addr;
1249860Sgdamore@opensolaris.org 	phy_handle_t	m_phys[32];
1259860Sgdamore@opensolaris.org 	phy_handle_t	m_bogus_phy;
1269860Sgdamore@opensolaris.org 	phy_handle_t	*m_phy;
1279860Sgdamore@opensolaris.org 
1289860Sgdamore@opensolaris.org 	link_state_t	m_link;
1299860Sgdamore@opensolaris.org 
1309860Sgdamore@opensolaris.org 	/* these start out undefined, but get values due to mac_prop_set */
1319860Sgdamore@opensolaris.org 	int		m_en_aneg;
1329860Sgdamore@opensolaris.org 	int		m_en_10_hdx;
1339860Sgdamore@opensolaris.org 	int		m_en_10_fdx;
1349860Sgdamore@opensolaris.org 	int		m_en_100_t4;
1359860Sgdamore@opensolaris.org 	int		m_en_100_hdx;
1369860Sgdamore@opensolaris.org 	int		m_en_100_fdx;
1379860Sgdamore@opensolaris.org 	int		m_en_1000_hdx;
1389860Sgdamore@opensolaris.org 	int		m_en_1000_fdx;
1399860Sgdamore@opensolaris.org 	int		m_en_flowctrl;
1409860Sgdamore@opensolaris.org 
1419860Sgdamore@opensolaris.org 	boolean_t	m_cap_pause;
1429860Sgdamore@opensolaris.org 	boolean_t	m_cap_asmpause;
1439860Sgdamore@opensolaris.org };
1449860Sgdamore@opensolaris.org 
1459860Sgdamore@opensolaris.org 
1469860Sgdamore@opensolaris.org static void _mii_task(void *);
14710359SGarrett.Damore@Sun.COM static void _mii_probe_phy(phy_handle_t *);
14810359SGarrett.Damore@Sun.COM static void _mii_probe(mii_handle_t);
14910359SGarrett.Damore@Sun.COM static int _mii_reset(mii_handle_t);
15010359SGarrett.Damore@Sun.COM static int _mii_loopback(mii_handle_t);
15110359SGarrett.Damore@Sun.COM static void _mii_notify(mii_handle_t);
15210359SGarrett.Damore@Sun.COM static int _mii_check(mii_handle_t);
15310359SGarrett.Damore@Sun.COM static int _mii_start(mii_handle_t);
1549860Sgdamore@opensolaris.org 
1559860Sgdamore@opensolaris.org /*
1569860Sgdamore@opensolaris.org  * Loadable module structures/entrypoints
1579860Sgdamore@opensolaris.org  */
1589860Sgdamore@opensolaris.org 
1599860Sgdamore@opensolaris.org extern struct mod_ops mod_misc_ops;
1609860Sgdamore@opensolaris.org 
1619860Sgdamore@opensolaris.org static struct modlmisc modlmisc = {
1629860Sgdamore@opensolaris.org 	&mod_miscops,
1639860Sgdamore@opensolaris.org 	"802.3 MII support",
1649860Sgdamore@opensolaris.org };
1659860Sgdamore@opensolaris.org 
1669860Sgdamore@opensolaris.org static struct modlinkage modlinkage = {
1679860Sgdamore@opensolaris.org 	MODREV_1, &modlmisc, NULL
1689860Sgdamore@opensolaris.org };
1699860Sgdamore@opensolaris.org 
1709860Sgdamore@opensolaris.org int
1719860Sgdamore@opensolaris.org _init(void)
1729860Sgdamore@opensolaris.org {
1739860Sgdamore@opensolaris.org 	return (mod_install(&modlinkage));
1749860Sgdamore@opensolaris.org }
1759860Sgdamore@opensolaris.org 
1769860Sgdamore@opensolaris.org int
1779860Sgdamore@opensolaris.org _fini(void)
1789860Sgdamore@opensolaris.org {
1799860Sgdamore@opensolaris.org 	return (mod_remove(&modlinkage));
1809860Sgdamore@opensolaris.org }
1819860Sgdamore@opensolaris.org 
1829860Sgdamore@opensolaris.org int
1839860Sgdamore@opensolaris.org _info(struct modinfo *modinfop)
1849860Sgdamore@opensolaris.org {
1859860Sgdamore@opensolaris.org 	return (mod_info(&modlinkage, modinfop));
1869860Sgdamore@opensolaris.org }
1879860Sgdamore@opensolaris.org 
1889860Sgdamore@opensolaris.org void
1899860Sgdamore@opensolaris.org _mii_error(mii_handle_t mh, int errno)
1909860Sgdamore@opensolaris.org {
1919860Sgdamore@opensolaris.org 	/*
1929860Sgdamore@opensolaris.org 	 * This dumps an error message, but it avoids filling the log with
1939860Sgdamore@opensolaris.org 	 * repeated error messages.
1949860Sgdamore@opensolaris.org 	 */
1959860Sgdamore@opensolaris.org 	if (mh->m_error != errno) {
1969860Sgdamore@opensolaris.org 		cmn_err(CE_WARN, "%s: %s", mh->m_name, mii_errors[errno]);
1979860Sgdamore@opensolaris.org 		mh->m_error = errno;
1989860Sgdamore@opensolaris.org 	}
1999860Sgdamore@opensolaris.org }
2009860Sgdamore@opensolaris.org 
2019860Sgdamore@opensolaris.org /*
2029860Sgdamore@opensolaris.org  * Known list of specific PHY probes.
2039860Sgdamore@opensolaris.org  */
2049860Sgdamore@opensolaris.org typedef boolean_t (*phy_probe_t)(phy_handle_t *);
2059860Sgdamore@opensolaris.org phy_probe_t _phy_probes[] = {
2069860Sgdamore@opensolaris.org 	phy_natsemi_probe,
2079860Sgdamore@opensolaris.org 	phy_intel_probe,
2089860Sgdamore@opensolaris.org 	phy_qualsemi_probe,
2099860Sgdamore@opensolaris.org 	phy_cicada_probe,
21010359SGarrett.Damore@Sun.COM 	phy_marvell_probe,
2119860Sgdamore@opensolaris.org 	phy_other_probe,
2129860Sgdamore@opensolaris.org 	NULL
2139860Sgdamore@opensolaris.org };
2149860Sgdamore@opensolaris.org 
2159860Sgdamore@opensolaris.org /*
2169860Sgdamore@opensolaris.org  * MII Interface functions
2179860Sgdamore@opensolaris.org  */
2189860Sgdamore@opensolaris.org 
2199860Sgdamore@opensolaris.org mii_handle_t
2209860Sgdamore@opensolaris.org mii_alloc_instance(void *private, dev_info_t *dip, int inst, mii_ops_t *ops)
2219860Sgdamore@opensolaris.org {
2229860Sgdamore@opensolaris.org 	mii_handle_t	mh;
2239860Sgdamore@opensolaris.org 	char		tqname[16];
2249860Sgdamore@opensolaris.org 
2259860Sgdamore@opensolaris.org 	if (ops->mii_version != MII_OPS_VERSION) {
2269860Sgdamore@opensolaris.org 		cmn_err(CE_WARN, "%s: incompatible MII version (%d)",
2279860Sgdamore@opensolaris.org 		    ddi_driver_name(dip), ops->mii_version);
2289860Sgdamore@opensolaris.org 		return (NULL);
2299860Sgdamore@opensolaris.org 	}
2309860Sgdamore@opensolaris.org 	mh = kmem_zalloc(sizeof (*mh), KM_SLEEP);
2319860Sgdamore@opensolaris.org 
2329860Sgdamore@opensolaris.org 	(void) snprintf(mh->m_name, sizeof (mh->m_name), "%s%d",
2339860Sgdamore@opensolaris.org 	    ddi_driver_name(dip), inst);
2349860Sgdamore@opensolaris.org 
2359860Sgdamore@opensolaris.org 	/* DDI will prepend the driver name */
2369860Sgdamore@opensolaris.org 	(void) snprintf(tqname, sizeof (tqname), "mii%d", inst);
2379860Sgdamore@opensolaris.org 
2389860Sgdamore@opensolaris.org 	mh->m_dip = dip;
2399860Sgdamore@opensolaris.org 	mh->m_ops = *ops;
2409860Sgdamore@opensolaris.org 	mh->m_private = private;
2419860Sgdamore@opensolaris.org 	mh->m_suspended = B_FALSE;
2429860Sgdamore@opensolaris.org 	mh->m_started = B_FALSE;
2439860Sgdamore@opensolaris.org 	mh->m_tstate = MII_STATE_PROBE;
24410479SGarrett.Damore@Sun.COM 	mh->m_link = LINK_STATE_UNKNOWN;
2459860Sgdamore@opensolaris.org 	mh->m_error = MII_EOK;
24610359SGarrett.Damore@Sun.COM 	mh->m_addr = -1;
2479860Sgdamore@opensolaris.org 	mutex_init(&mh->m_lock, NULL, MUTEX_DRIVER, NULL);
2489860Sgdamore@opensolaris.org 	cv_init(&mh->m_cv, NULL, CV_DRIVER, NULL);
2499860Sgdamore@opensolaris.org 
2509860Sgdamore@opensolaris.org 	mh->m_tq = ddi_taskq_create(dip, tqname, 1, TASKQ_DEFAULTPRI, 0);
2519860Sgdamore@opensolaris.org 	if (mh->m_tq == NULL) {
2529860Sgdamore@opensolaris.org 		cmn_err(CE_WARN, "%s: unable to create MII monitoring task",
2539860Sgdamore@opensolaris.org 		    ddi_driver_name(dip));
2549860Sgdamore@opensolaris.org 		cv_destroy(&mh->m_cv);
2559860Sgdamore@opensolaris.org 		mutex_destroy(&mh->m_lock);
2569860Sgdamore@opensolaris.org 		kmem_free(mh, sizeof (*mh));
2579860Sgdamore@opensolaris.org 		return (NULL);
2589860Sgdamore@opensolaris.org 	}
2599860Sgdamore@opensolaris.org 
2609860Sgdamore@opensolaris.org 	/*
2619860Sgdamore@opensolaris.org 	 * Initialize user prefs by loading properties.  Ultimately,
2629860Sgdamore@opensolaris.org 	 * Brussels interfaces would be superior here.
2639860Sgdamore@opensolaris.org 	 */
2649860Sgdamore@opensolaris.org #define	GETPROP(name)	ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0, name, -1)
2659860Sgdamore@opensolaris.org 	mh->m_en_aneg = GETPROP("adv_autoneg_cap");
2669860Sgdamore@opensolaris.org 	mh->m_en_10_hdx = GETPROP("adv_10hdx_cap");
2679860Sgdamore@opensolaris.org 	mh->m_en_10_fdx = GETPROP("adv_10fdx_cap");
2689860Sgdamore@opensolaris.org 	mh->m_en_100_hdx = GETPROP("adv_100hdx_cap");
2699860Sgdamore@opensolaris.org 	mh->m_en_100_fdx = GETPROP("adv_100fdx_cap");
2709860Sgdamore@opensolaris.org 	mh->m_en_100_t4 = GETPROP("adv_100T4_cap");
2719860Sgdamore@opensolaris.org 	mh->m_en_1000_hdx = GETPROP("adv_1000hdx_cap");
2729860Sgdamore@opensolaris.org 	mh->m_en_1000_fdx = GETPROP("adv_1000fdx_cap");
2739860Sgdamore@opensolaris.org 
2749860Sgdamore@opensolaris.org 	mh->m_cap_pause = B_FALSE;
2759860Sgdamore@opensolaris.org 	mh->m_cap_asmpause = B_FALSE;
2769860Sgdamore@opensolaris.org 
2779860Sgdamore@opensolaris.org 	bzero(&mh->m_bogus_phy, sizeof (mh->m_bogus_phy));
2789860Sgdamore@opensolaris.org 	mh->m_bogus_phy.phy_link = LINK_STATE_UNKNOWN;
2799860Sgdamore@opensolaris.org 	mh->m_bogus_phy.phy_duplex = LINK_DUPLEX_UNKNOWN;
2809860Sgdamore@opensolaris.org 	mh->m_bogus_phy.phy_addr = 0xff;
2819860Sgdamore@opensolaris.org 	mh->m_bogus_phy.phy_type = XCVR_NONE;
2829860Sgdamore@opensolaris.org 	mh->m_bogus_phy.phy_id = (uint32_t)-1;
2839860Sgdamore@opensolaris.org 	mh->m_bogus_phy.phy_loopback = PHY_LB_NONE;
2849860Sgdamore@opensolaris.org 	mh->m_bogus_phy.phy_flowctrl = LINK_FLOWCTRL_NONE;
2859860Sgdamore@opensolaris.org 	mh->m_phy = &mh->m_bogus_phy;
2869860Sgdamore@opensolaris.org 
2879860Sgdamore@opensolaris.org 	for (int i = 0; i < 32; i++) {
2889860Sgdamore@opensolaris.org 		mh->m_phys[i].phy_mii = mh;
2899860Sgdamore@opensolaris.org 	}
2909860Sgdamore@opensolaris.org 	mh->m_bogus_phy.phy_mii = mh;
2919860Sgdamore@opensolaris.org 
2929860Sgdamore@opensolaris.org 	return (mh);
2939860Sgdamore@opensolaris.org }
2949860Sgdamore@opensolaris.org 
2959860Sgdamore@opensolaris.org mii_handle_t
2969860Sgdamore@opensolaris.org mii_alloc(void *private, dev_info_t *dip, mii_ops_t *ops)
2979860Sgdamore@opensolaris.org {
2989860Sgdamore@opensolaris.org 	return (mii_alloc_instance(private, dip, ddi_get_instance(dip), ops));
2999860Sgdamore@opensolaris.org }
3009860Sgdamore@opensolaris.org 
3019860Sgdamore@opensolaris.org void
3029860Sgdamore@opensolaris.org mii_set_pauseable(mii_handle_t mh, boolean_t pauseable, boolean_t asymetric)
3039860Sgdamore@opensolaris.org {
3049860Sgdamore@opensolaris.org 	phy_handle_t	*ph;
3059860Sgdamore@opensolaris.org 
3069860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
3079860Sgdamore@opensolaris.org 	ph = mh->m_phy;
3089860Sgdamore@opensolaris.org 	ph->phy_cap_pause = mh->m_cap_pause = pauseable;
3099860Sgdamore@opensolaris.org 	ph->phy_cap_asmpause = mh->m_cap_asmpause = asymetric;
3109860Sgdamore@opensolaris.org 	if (pauseable) {
3119860Sgdamore@opensolaris.org 		mh->m_en_flowctrl = LINK_FLOWCTRL_BI;
3129860Sgdamore@opensolaris.org 	} else {
3139860Sgdamore@opensolaris.org 		mh->m_en_flowctrl = LINK_FLOWCTRL_NONE;
3149860Sgdamore@opensolaris.org 	}
3159860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
3169860Sgdamore@opensolaris.org }
3179860Sgdamore@opensolaris.org 
3189860Sgdamore@opensolaris.org void
3199860Sgdamore@opensolaris.org mii_free(mii_handle_t mh)
3209860Sgdamore@opensolaris.org {
3219860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
3229860Sgdamore@opensolaris.org 	mh->m_started = B_FALSE;
3239860Sgdamore@opensolaris.org 	cv_broadcast(&mh->m_cv);
3249860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
3259860Sgdamore@opensolaris.org 
3269860Sgdamore@opensolaris.org 	ddi_taskq_destroy(mh->m_tq);
3279860Sgdamore@opensolaris.org 	mutex_destroy(&mh->m_lock);
3289860Sgdamore@opensolaris.org 	cv_destroy(&mh->m_cv);
3299860Sgdamore@opensolaris.org 	kmem_free(mh, sizeof (*mh));
3309860Sgdamore@opensolaris.org }
3319860Sgdamore@opensolaris.org 
3329860Sgdamore@opensolaris.org void
3339860Sgdamore@opensolaris.org mii_reset(mii_handle_t mh)
3349860Sgdamore@opensolaris.org {
3359860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
3369860Sgdamore@opensolaris.org 	if (mh->m_tstate > MII_STATE_RESET)
3379860Sgdamore@opensolaris.org 		mh->m_tstate = MII_STATE_RESET;
3389860Sgdamore@opensolaris.org 	cv_broadcast(&mh->m_cv);
3399860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
3409860Sgdamore@opensolaris.org }
3419860Sgdamore@opensolaris.org 
3429860Sgdamore@opensolaris.org void
3439860Sgdamore@opensolaris.org mii_suspend(mii_handle_t mh)
3449860Sgdamore@opensolaris.org {
3459860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
3469860Sgdamore@opensolaris.org 	while ((!mh->m_suspended) && (mh->m_started)) {
3479860Sgdamore@opensolaris.org 		mh->m_suspending = B_TRUE;
3489860Sgdamore@opensolaris.org 		cv_broadcast(&mh->m_cv);
3499860Sgdamore@opensolaris.org 		cv_wait(&mh->m_cv, &mh->m_lock);
3509860Sgdamore@opensolaris.org 	}
3519860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
3529860Sgdamore@opensolaris.org }
3539860Sgdamore@opensolaris.org 
3549860Sgdamore@opensolaris.org void
3559860Sgdamore@opensolaris.org mii_resume(mii_handle_t mh)
3569860Sgdamore@opensolaris.org {
3579860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
35810359SGarrett.Damore@Sun.COM 
35910359SGarrett.Damore@Sun.COM 	switch (mh->m_tstate) {
36010359SGarrett.Damore@Sun.COM 	case MII_STATE_PROBE:
36110359SGarrett.Damore@Sun.COM 		break;
36210359SGarrett.Damore@Sun.COM 	case MII_STATE_RESET:
36310359SGarrett.Damore@Sun.COM 	case MII_STATE_START:
36410359SGarrett.Damore@Sun.COM 	case MII_STATE_RUN:
36510359SGarrett.Damore@Sun.COM 		/* let monitor thread deal with this */
3669860Sgdamore@opensolaris.org 		mh->m_tstate = MII_STATE_RESET;
36710359SGarrett.Damore@Sun.COM 		break;
36810359SGarrett.Damore@Sun.COM 
36910359SGarrett.Damore@Sun.COM 	case MII_STATE_LOOPBACK:
37010359SGarrett.Damore@Sun.COM 		/* loopback is handled synchronously */
37110359SGarrett.Damore@Sun.COM 		(void) _mii_loopback(mh);
37210359SGarrett.Damore@Sun.COM 		break;
3739860Sgdamore@opensolaris.org 	}
37410359SGarrett.Damore@Sun.COM 
3759860Sgdamore@opensolaris.org 	mh->m_suspended = B_FALSE;
3769860Sgdamore@opensolaris.org 	cv_broadcast(&mh->m_cv);
3779860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
3789860Sgdamore@opensolaris.org }
3799860Sgdamore@opensolaris.org 
3809860Sgdamore@opensolaris.org void
3819860Sgdamore@opensolaris.org mii_start(mii_handle_t mh)
3829860Sgdamore@opensolaris.org {
3839860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
3849860Sgdamore@opensolaris.org 	if (!mh->m_started) {
38510359SGarrett.Damore@Sun.COM 		mh->m_tstate = MII_STATE_PROBE;
3869860Sgdamore@opensolaris.org 		mh->m_started = B_TRUE;
3879860Sgdamore@opensolaris.org 		if (ddi_taskq_dispatch(mh->m_tq, _mii_task, mh, DDI_NOSLEEP) !=
3889860Sgdamore@opensolaris.org 		    DDI_SUCCESS) {
3899860Sgdamore@opensolaris.org 			cmn_err(CE_WARN,
3909860Sgdamore@opensolaris.org 			    "%s: unable to start MII monitoring task",
3919860Sgdamore@opensolaris.org 			    mh->m_name);
3929860Sgdamore@opensolaris.org 			mh->m_started = B_FALSE;
3939860Sgdamore@opensolaris.org 		}
3949860Sgdamore@opensolaris.org 	}
3959860Sgdamore@opensolaris.org 	cv_broadcast(&mh->m_cv);
3969860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
3979860Sgdamore@opensolaris.org }
3989860Sgdamore@opensolaris.org 
3999860Sgdamore@opensolaris.org void
4009860Sgdamore@opensolaris.org mii_stop(mii_handle_t mh)
4019860Sgdamore@opensolaris.org {
4029860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
4039860Sgdamore@opensolaris.org 	mh->m_started = B_FALSE;
404*11428Sgdamore@opensolaris.org 	/*
405*11428Sgdamore@opensolaris.org 	 * Reset link state to unknown defaults, since we're not
406*11428Sgdamore@opensolaris.org 	 * monitoring it anymore.  We'll reprobe all link state later.
407*11428Sgdamore@opensolaris.org 	 */
408*11428Sgdamore@opensolaris.org 	mh->m_link = LINK_STATE_UNKNOWN;
409*11428Sgdamore@opensolaris.org 	mh->m_phy = &mh->m_bogus_phy;
4109860Sgdamore@opensolaris.org 	cv_broadcast(&mh->m_cv);
4119860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
412*11428Sgdamore@opensolaris.org 	/*
413*11428Sgdamore@opensolaris.org 	 * Notify the MAC driver.  This will allow it to call back
414*11428Sgdamore@opensolaris.org 	 * into the MAC framework to clear any previous link state.
415*11428Sgdamore@opensolaris.org 	 */
416*11428Sgdamore@opensolaris.org 	_mii_notify(mh);
4179860Sgdamore@opensolaris.org }
4189860Sgdamore@opensolaris.org 
4199860Sgdamore@opensolaris.org void
4209860Sgdamore@opensolaris.org mii_probe(mii_handle_t mh)
4219860Sgdamore@opensolaris.org {
4229860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
42310359SGarrett.Damore@Sun.COM 	_mii_probe(mh);
4249860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
4259860Sgdamore@opensolaris.org }
4269860Sgdamore@opensolaris.org 
4279860Sgdamore@opensolaris.org void
4289860Sgdamore@opensolaris.org mii_check(mii_handle_t mh)
4299860Sgdamore@opensolaris.org {
4309860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
4319860Sgdamore@opensolaris.org 	cv_broadcast(&mh->m_cv);
4329860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
4339860Sgdamore@opensolaris.org }
4349860Sgdamore@opensolaris.org 
4359860Sgdamore@opensolaris.org int
4369860Sgdamore@opensolaris.org mii_get_speed(mii_handle_t mh)
4379860Sgdamore@opensolaris.org {
4389860Sgdamore@opensolaris.org 	phy_handle_t	*ph = mh->m_phy;
4399860Sgdamore@opensolaris.org 
4409860Sgdamore@opensolaris.org 	return (ph->phy_speed);
4419860Sgdamore@opensolaris.org }
4429860Sgdamore@opensolaris.org 
4439860Sgdamore@opensolaris.org link_duplex_t
4449860Sgdamore@opensolaris.org mii_get_duplex(mii_handle_t mh)
4459860Sgdamore@opensolaris.org {
4469860Sgdamore@opensolaris.org 	phy_handle_t	*ph = mh->m_phy;
4479860Sgdamore@opensolaris.org 
4489860Sgdamore@opensolaris.org 	return (ph->phy_duplex);
4499860Sgdamore@opensolaris.org }
4509860Sgdamore@opensolaris.org 
4519860Sgdamore@opensolaris.org link_state_t
4529860Sgdamore@opensolaris.org mii_get_state(mii_handle_t mh)
4539860Sgdamore@opensolaris.org {
4549860Sgdamore@opensolaris.org 	phy_handle_t	*ph = mh->m_phy;
4559860Sgdamore@opensolaris.org 
4569860Sgdamore@opensolaris.org 	return (ph->phy_link);
4579860Sgdamore@opensolaris.org }
4589860Sgdamore@opensolaris.org 
4599860Sgdamore@opensolaris.org link_flowctrl_t
4609860Sgdamore@opensolaris.org mii_get_flowctrl(mii_handle_t mh)
4619860Sgdamore@opensolaris.org {
4629860Sgdamore@opensolaris.org 	phy_handle_t	*ph = mh->m_phy;
4639860Sgdamore@opensolaris.org 
4649860Sgdamore@opensolaris.org 	return (ph->phy_flowctrl);
4659860Sgdamore@opensolaris.org }
4669860Sgdamore@opensolaris.org 
4679860Sgdamore@opensolaris.org int
4689860Sgdamore@opensolaris.org mii_get_loopmodes(mii_handle_t mh, lb_property_t *modes)
4699860Sgdamore@opensolaris.org {
4709860Sgdamore@opensolaris.org 	phy_handle_t	*ph = mh->m_phy;
47110359SGarrett.Damore@Sun.COM 	int		cnt = 0;
47210359SGarrett.Damore@Sun.COM 	lb_property_t	lmodes[MII_LOOPBACK_MAX];
47310359SGarrett.Damore@Sun.COM 
47410359SGarrett.Damore@Sun.COM 	lmodes[cnt].lb_type = normal;
47510363Sgdamore@opensolaris.org 	(void) strlcpy(lmodes[cnt].key, "normal", sizeof (lmodes[cnt].key));
47610359SGarrett.Damore@Sun.COM 	lmodes[cnt].value = PHY_LB_NONE;
47710359SGarrett.Damore@Sun.COM 	cnt++;
47810359SGarrett.Damore@Sun.COM 
47910359SGarrett.Damore@Sun.COM 	if (ph->phy_cap_1000_fdx ||
48010359SGarrett.Damore@Sun.COM 	    ph->phy_cap_100_fdx ||
48110359SGarrett.Damore@Sun.COM 	    ph->phy_cap_10_fdx) {
48210359SGarrett.Damore@Sun.COM 		/* we only support full duplex internal phy testing */
48310359SGarrett.Damore@Sun.COM 		lmodes[cnt].lb_type = internal;
48410363Sgdamore@opensolaris.org 		(void) strlcpy(lmodes[cnt].key, "PHY",
48510363Sgdamore@opensolaris.org 		    sizeof (lmodes[cnt].key));
48610359SGarrett.Damore@Sun.COM 		lmodes[cnt].value = PHY_LB_INT_PHY;
48710359SGarrett.Damore@Sun.COM 		cnt++;
48810359SGarrett.Damore@Sun.COM 	}
4899860Sgdamore@opensolaris.org 
49010359SGarrett.Damore@Sun.COM 	if (ph->phy_cap_1000_fdx) {
49110359SGarrett.Damore@Sun.COM 		lmodes[cnt].lb_type = external;
49210363Sgdamore@opensolaris.org 		(void) strlcpy(lmodes[cnt].key, "1000Mbps",
49310363Sgdamore@opensolaris.org 		    sizeof (lmodes[cnt].key));
49410359SGarrett.Damore@Sun.COM 		lmodes[cnt].value = PHY_LB_EXT_1000;
49510359SGarrett.Damore@Sun.COM 		cnt++;
49610359SGarrett.Damore@Sun.COM 	}
49710359SGarrett.Damore@Sun.COM 
49810359SGarrett.Damore@Sun.COM 	if (ph->phy_cap_100_fdx) {
49910359SGarrett.Damore@Sun.COM 		lmodes[cnt].lb_type = external;
50010363Sgdamore@opensolaris.org 		(void) strlcpy(lmodes[cnt].key, "100Mbps",
50110363Sgdamore@opensolaris.org 		    sizeof (lmodes[cnt].key));
50210359SGarrett.Damore@Sun.COM 		lmodes[cnt].value = PHY_LB_EXT_100;
50310359SGarrett.Damore@Sun.COM 		cnt++;
50410359SGarrett.Damore@Sun.COM 	}
50510359SGarrett.Damore@Sun.COM 
50610359SGarrett.Damore@Sun.COM 	if (ph->phy_cap_10_fdx) {
50710359SGarrett.Damore@Sun.COM 		lmodes[cnt].lb_type = external;
50810363Sgdamore@opensolaris.org 		(void) strlcpy(lmodes[cnt].key, "10Mbps",
50910363Sgdamore@opensolaris.org 		    sizeof (lmodes[cnt].key));
51010359SGarrett.Damore@Sun.COM 		lmodes[cnt].value = PHY_LB_EXT_10;
51110359SGarrett.Damore@Sun.COM 		cnt++;
5129860Sgdamore@opensolaris.org 	}
5139860Sgdamore@opensolaris.org 
5149860Sgdamore@opensolaris.org 	if (modes) {
51510359SGarrett.Damore@Sun.COM 		bcopy(lmodes, modes, sizeof (lb_property_t) * cnt);
5169860Sgdamore@opensolaris.org 	}
51710359SGarrett.Damore@Sun.COM 
5189860Sgdamore@opensolaris.org 	return (cnt);
5199860Sgdamore@opensolaris.org }
5209860Sgdamore@opensolaris.org 
5219860Sgdamore@opensolaris.org uint32_t
5229860Sgdamore@opensolaris.org mii_get_loopback(mii_handle_t mh)
5239860Sgdamore@opensolaris.org {
5249860Sgdamore@opensolaris.org 	phy_handle_t	*ph = mh->m_phy;
5259860Sgdamore@opensolaris.org 
5269860Sgdamore@opensolaris.org 	return (ph->phy_loopback);
5279860Sgdamore@opensolaris.org }
5289860Sgdamore@opensolaris.org 
5299860Sgdamore@opensolaris.org int
5309860Sgdamore@opensolaris.org mii_set_loopback(mii_handle_t mh, uint32_t loop)
5319860Sgdamore@opensolaris.org {
5329860Sgdamore@opensolaris.org 	phy_handle_t	*ph;
53310359SGarrett.Damore@Sun.COM 	int		rv;
5349860Sgdamore@opensolaris.org 
5359860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
5369860Sgdamore@opensolaris.org 	ph = mh->m_phy;
5379860Sgdamore@opensolaris.org 
5389860Sgdamore@opensolaris.org 	if ((!mh->m_started) || (!ph->phy_present) ||
5399860Sgdamore@opensolaris.org 	    (loop >= mii_get_loopmodes(mh, NULL))) {
5409860Sgdamore@opensolaris.org 		return (EINVAL);
5419860Sgdamore@opensolaris.org 	}
5429860Sgdamore@opensolaris.org 
5439860Sgdamore@opensolaris.org 	ph->phy_loopback = loop;
54410359SGarrett.Damore@Sun.COM 	rv = _mii_loopback(mh);
54510359SGarrett.Damore@Sun.COM 	if (rv == DDI_SUCCESS) {
54610359SGarrett.Damore@Sun.COM 		mh->m_tstate = MII_STATE_LOOPBACK;
54710359SGarrett.Damore@Sun.COM 	}
5489860Sgdamore@opensolaris.org 	cv_broadcast(&mh->m_cv);
5499860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
5509860Sgdamore@opensolaris.org 
55110359SGarrett.Damore@Sun.COM 	return (rv == DDI_SUCCESS ? 0 : EIO);
5529860Sgdamore@opensolaris.org }
5539860Sgdamore@opensolaris.org 
5549860Sgdamore@opensolaris.org uint32_t
5559860Sgdamore@opensolaris.org mii_get_id(mii_handle_t mh)
5569860Sgdamore@opensolaris.org {
5579860Sgdamore@opensolaris.org 	phy_handle_t	*ph = mh->m_phy;
5589860Sgdamore@opensolaris.org 
5599860Sgdamore@opensolaris.org 	return (ph->phy_id);
5609860Sgdamore@opensolaris.org }
5619860Sgdamore@opensolaris.org 
5629860Sgdamore@opensolaris.org int
5639860Sgdamore@opensolaris.org mii_get_addr(mii_handle_t mh)
5649860Sgdamore@opensolaris.org {
5659860Sgdamore@opensolaris.org 	return (mh->m_addr);
5669860Sgdamore@opensolaris.org }
5679860Sgdamore@opensolaris.org 
5689860Sgdamore@opensolaris.org /* GLDv3 helpers */
5699860Sgdamore@opensolaris.org 
5709860Sgdamore@opensolaris.org boolean_t
5719860Sgdamore@opensolaris.org mii_m_loop_ioctl(mii_handle_t mh, queue_t *wq, mblk_t *mp)
5729860Sgdamore@opensolaris.org {
5739860Sgdamore@opensolaris.org 	struct iocblk	*iocp;
5749860Sgdamore@opensolaris.org 	int		rv = 0;
5759860Sgdamore@opensolaris.org 	int		cnt;
5769860Sgdamore@opensolaris.org 	lb_property_t	modes[MII_LOOPBACK_MAX];
5779860Sgdamore@opensolaris.org 	lb_info_sz_t	sz;
5789860Sgdamore@opensolaris.org 	int		cmd;
5799860Sgdamore@opensolaris.org 	uint32_t	mode;
5809860Sgdamore@opensolaris.org 
5819860Sgdamore@opensolaris.org 	iocp = (void *)mp->b_rptr;
5829860Sgdamore@opensolaris.org 	cmd = iocp->ioc_cmd;
5839860Sgdamore@opensolaris.org 
5849860Sgdamore@opensolaris.org 	switch (cmd) {
5859860Sgdamore@opensolaris.org 	case LB_SET_MODE:
5869860Sgdamore@opensolaris.org 	case LB_GET_INFO_SIZE:
5879860Sgdamore@opensolaris.org 	case LB_GET_INFO:
5889860Sgdamore@opensolaris.org 	case LB_GET_MODE:
5899860Sgdamore@opensolaris.org 		break;
5909860Sgdamore@opensolaris.org 
5919860Sgdamore@opensolaris.org 	default:
5929860Sgdamore@opensolaris.org 		return (B_FALSE);
5939860Sgdamore@opensolaris.org 	}
5949860Sgdamore@opensolaris.org 
5959860Sgdamore@opensolaris.org 	if (mp->b_cont == NULL) {
5969860Sgdamore@opensolaris.org 		miocnak(wq, mp, 0, EINVAL);
5979860Sgdamore@opensolaris.org 		return (B_TRUE);
5989860Sgdamore@opensolaris.org 	}
5999860Sgdamore@opensolaris.org 
6009860Sgdamore@opensolaris.org 	switch (cmd) {
6019860Sgdamore@opensolaris.org 	case LB_GET_INFO_SIZE:
6029860Sgdamore@opensolaris.org 		cnt = mii_get_loopmodes(mh, modes);
6039860Sgdamore@opensolaris.org 		if (iocp->ioc_count != sizeof (sz)) {
6049860Sgdamore@opensolaris.org 			rv = EINVAL;
6059860Sgdamore@opensolaris.org 		} else {
6069860Sgdamore@opensolaris.org 			sz = cnt * sizeof (lb_property_t);
6079860Sgdamore@opensolaris.org 			bcopy(&sz, mp->b_cont->b_rptr, sizeof (sz));
6089860Sgdamore@opensolaris.org 		}
6099860Sgdamore@opensolaris.org 		break;
6109860Sgdamore@opensolaris.org 
6119860Sgdamore@opensolaris.org 	case LB_GET_INFO:
6129860Sgdamore@opensolaris.org 		cnt = mii_get_loopmodes(mh, modes);
6139860Sgdamore@opensolaris.org 		if (iocp->ioc_count != (cnt * sizeof (lb_property_t))) {
6149860Sgdamore@opensolaris.org 			rv = EINVAL;
6159860Sgdamore@opensolaris.org 		} else {
6169860Sgdamore@opensolaris.org 			bcopy(modes, mp->b_cont->b_rptr, iocp->ioc_count);
6179860Sgdamore@opensolaris.org 		}
6189860Sgdamore@opensolaris.org 		break;
6199860Sgdamore@opensolaris.org 
6209860Sgdamore@opensolaris.org 	case LB_GET_MODE:
6219860Sgdamore@opensolaris.org 		if (iocp->ioc_count != sizeof (mode)) {
6229860Sgdamore@opensolaris.org 			rv = EINVAL;
6239860Sgdamore@opensolaris.org 		} else {
6249860Sgdamore@opensolaris.org 			mode = mii_get_loopback(mh);
6259860Sgdamore@opensolaris.org 			bcopy(&mode, mp->b_cont->b_rptr, sizeof (mode));
6269860Sgdamore@opensolaris.org 		}
6279860Sgdamore@opensolaris.org 		break;
6289860Sgdamore@opensolaris.org 
6299860Sgdamore@opensolaris.org 	case LB_SET_MODE:
6309860Sgdamore@opensolaris.org 		rv = secpolicy_net_config(iocp->ioc_cr, B_FALSE);
6319860Sgdamore@opensolaris.org 		if (rv != 0)
6329860Sgdamore@opensolaris.org 			break;
6339860Sgdamore@opensolaris.org 		if (iocp->ioc_count != sizeof (mode)) {
6349860Sgdamore@opensolaris.org 			rv = EINVAL;
6359860Sgdamore@opensolaris.org 			break;
6369860Sgdamore@opensolaris.org 		}
6379860Sgdamore@opensolaris.org 		bcopy(mp->b_cont->b_rptr, &mode, sizeof (mode));
6389860Sgdamore@opensolaris.org 		rv = mii_set_loopback(mh, mode);
6399860Sgdamore@opensolaris.org 		break;
6409860Sgdamore@opensolaris.org 	}
6419860Sgdamore@opensolaris.org 
6429860Sgdamore@opensolaris.org 	if (rv == 0) {
6439860Sgdamore@opensolaris.org 		miocack(wq, mp, iocp->ioc_count, 0);
6449860Sgdamore@opensolaris.org 	} else {
6459860Sgdamore@opensolaris.org 		miocnak(wq, mp, 0, rv);
6469860Sgdamore@opensolaris.org 	}
6479860Sgdamore@opensolaris.org 	return (B_TRUE);
6489860Sgdamore@opensolaris.org }
6499860Sgdamore@opensolaris.org 
6509860Sgdamore@opensolaris.org int
6519860Sgdamore@opensolaris.org mii_m_getprop(mii_handle_t mh, const char *name, mac_prop_id_t num,
6529860Sgdamore@opensolaris.org     uint_t flags, uint_t sz, void *val, uint_t *permp)
6539860Sgdamore@opensolaris.org {
6549860Sgdamore@opensolaris.org 	phy_handle_t	*ph;
6559860Sgdamore@opensolaris.org 	int		err = 0;
6569860Sgdamore@opensolaris.org 	uint_t		perm;
6579860Sgdamore@opensolaris.org 	boolean_t	dfl = flags & MAC_PROP_DEFAULT;
6589860Sgdamore@opensolaris.org 
6599860Sgdamore@opensolaris.org 	_NOTE(ARGUNUSED(name));
6609860Sgdamore@opensolaris.org 
6619860Sgdamore@opensolaris.org 	if (sz < 1)
6629860Sgdamore@opensolaris.org 		return (EINVAL);
6639860Sgdamore@opensolaris.org 
6649860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
6659860Sgdamore@opensolaris.org 
6669860Sgdamore@opensolaris.org 	ph = mh->m_phy;
6679860Sgdamore@opensolaris.org 	perm = MAC_PROP_PERM_RW;
6689860Sgdamore@opensolaris.org 
6699860Sgdamore@opensolaris.org #define	CASE_PROP_ABILITY(PROP, VAR)					\
6709860Sgdamore@opensolaris.org 	case MAC_PROP_ADV_##PROP:					\
6719860Sgdamore@opensolaris.org 		perm = MAC_PROP_PERM_READ;				\
6729860Sgdamore@opensolaris.org 		*(uint8_t *)val =					\
6739860Sgdamore@opensolaris.org 		    dfl ? ph->phy_cap_##VAR : ph->phy_adv_##VAR;	\
6749860Sgdamore@opensolaris.org 		break;							\
6759860Sgdamore@opensolaris.org 									\
6769860Sgdamore@opensolaris.org 	case MAC_PROP_EN_##PROP:					\
6779860Sgdamore@opensolaris.org 		if (!ph->phy_cap_##VAR)					\
6789860Sgdamore@opensolaris.org 			perm = MAC_PROP_PERM_READ;			\
6799860Sgdamore@opensolaris.org 		*(uint8_t *)val =					\
6809860Sgdamore@opensolaris.org 		    dfl ? ph->phy_cap_##VAR : ph->phy_en_##VAR;		\
6819860Sgdamore@opensolaris.org 		break;
6829860Sgdamore@opensolaris.org 
6839860Sgdamore@opensolaris.org 	switch (num) {
6849860Sgdamore@opensolaris.org 	case MAC_PROP_DUPLEX:
6859860Sgdamore@opensolaris.org 		perm = MAC_PROP_PERM_READ;
6869860Sgdamore@opensolaris.org 		if (sz >= sizeof (link_duplex_t)) {
6879860Sgdamore@opensolaris.org 			bcopy(&ph->phy_duplex, val, sizeof (link_duplex_t));
6889860Sgdamore@opensolaris.org 		} else {
6899860Sgdamore@opensolaris.org 			err = EINVAL;
6909860Sgdamore@opensolaris.org 		}
6919860Sgdamore@opensolaris.org 		break;
6929860Sgdamore@opensolaris.org 
6939860Sgdamore@opensolaris.org 	case MAC_PROP_SPEED:
6949860Sgdamore@opensolaris.org 		perm = MAC_PROP_PERM_READ;
6959860Sgdamore@opensolaris.org 		if (sz >= sizeof (uint64_t)) {
6969860Sgdamore@opensolaris.org 			uint64_t speed = ph->phy_speed * 1000000ull;
6979860Sgdamore@opensolaris.org 			bcopy(&speed, val, sizeof (speed));
6989860Sgdamore@opensolaris.org 		} else {
6999860Sgdamore@opensolaris.org 			err = EINVAL;
7009860Sgdamore@opensolaris.org 		}
7019860Sgdamore@opensolaris.org 		break;
7029860Sgdamore@opensolaris.org 
7039860Sgdamore@opensolaris.org 	case MAC_PROP_AUTONEG:
7049860Sgdamore@opensolaris.org 		*(uint8_t *)val =
7059860Sgdamore@opensolaris.org 		    dfl ? ph->phy_cap_aneg : ph->phy_adv_aneg;
7069860Sgdamore@opensolaris.org 		break;
7079860Sgdamore@opensolaris.org 
7089860Sgdamore@opensolaris.org 	case MAC_PROP_FLOWCTRL:
7099860Sgdamore@opensolaris.org 		if (sz >= sizeof (link_flowctrl_t)) {
7109860Sgdamore@opensolaris.org 			bcopy(&ph->phy_flowctrl, val,
7119860Sgdamore@opensolaris.org 			    sizeof (link_flowctrl_t));
7129860Sgdamore@opensolaris.org 		} else {
7139860Sgdamore@opensolaris.org 			err = EINVAL;
7149860Sgdamore@opensolaris.org 		}
7159860Sgdamore@opensolaris.org 		break;
7169860Sgdamore@opensolaris.org 
7179860Sgdamore@opensolaris.org 	CASE_PROP_ABILITY(1000FDX_CAP, 1000_fdx)
7189860Sgdamore@opensolaris.org 	CASE_PROP_ABILITY(1000HDX_CAP, 1000_hdx)
7199860Sgdamore@opensolaris.org 	CASE_PROP_ABILITY(100T4_CAP, 100_t4)
7209860Sgdamore@opensolaris.org 	CASE_PROP_ABILITY(100FDX_CAP, 100_fdx)
7219860Sgdamore@opensolaris.org 	CASE_PROP_ABILITY(100HDX_CAP, 100_hdx)
7229860Sgdamore@opensolaris.org 	CASE_PROP_ABILITY(10FDX_CAP, 10_fdx)
7239860Sgdamore@opensolaris.org 	CASE_PROP_ABILITY(10HDX_CAP, 10_hdx)
7249860Sgdamore@opensolaris.org 
7259860Sgdamore@opensolaris.org 	default:
7269860Sgdamore@opensolaris.org 		err = ENOTSUP;
7279860Sgdamore@opensolaris.org 		break;
7289860Sgdamore@opensolaris.org 	}
7299860Sgdamore@opensolaris.org 
7309860Sgdamore@opensolaris.org 	if (err == 0) {
7319860Sgdamore@opensolaris.org 		*permp = perm;
7329860Sgdamore@opensolaris.org 	}
7339860Sgdamore@opensolaris.org 
7349860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
7359860Sgdamore@opensolaris.org 
7369860Sgdamore@opensolaris.org 	return (err);
7379860Sgdamore@opensolaris.org }
7389860Sgdamore@opensolaris.org 
7399860Sgdamore@opensolaris.org int
7409860Sgdamore@opensolaris.org mii_m_setprop(mii_handle_t mh, const char *name, mac_prop_id_t num,
7419860Sgdamore@opensolaris.org     uint_t sz, const void *valp)
7429860Sgdamore@opensolaris.org {
7439860Sgdamore@opensolaris.org 	phy_handle_t	*ph;
7449860Sgdamore@opensolaris.org 	boolean_t	*advp = NULL;
7459860Sgdamore@opensolaris.org 	boolean_t	*capp = NULL;
7469860Sgdamore@opensolaris.org 	int		*macpp = NULL;
7479860Sgdamore@opensolaris.org 	int		rv = ENOTSUP;
7489860Sgdamore@opensolaris.org 
7499860Sgdamore@opensolaris.org 	_NOTE(ARGUNUSED(name));
7509860Sgdamore@opensolaris.org 
7519860Sgdamore@opensolaris.org 	if (sz < 1)
7529860Sgdamore@opensolaris.org 		return (EINVAL);
7539860Sgdamore@opensolaris.org 
7549860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
7559860Sgdamore@opensolaris.org 
7569860Sgdamore@opensolaris.org 	ph = mh->m_phy;
7579860Sgdamore@opensolaris.org 
7589860Sgdamore@opensolaris.org 	/* we don't support changing parameters while in loopback mode */
7599860Sgdamore@opensolaris.org 	if (ph->phy_loopback != PHY_LB_NONE) {
7609860Sgdamore@opensolaris.org 		switch (num) {
7619860Sgdamore@opensolaris.org 		case MAC_PROP_EN_1000FDX_CAP:
7629860Sgdamore@opensolaris.org 		case MAC_PROP_EN_1000HDX_CAP:
7639860Sgdamore@opensolaris.org 		case MAC_PROP_EN_100FDX_CAP:
7649860Sgdamore@opensolaris.org 		case MAC_PROP_EN_100HDX_CAP:
7659860Sgdamore@opensolaris.org 		case MAC_PROP_EN_100T4_CAP:
7669860Sgdamore@opensolaris.org 		case MAC_PROP_EN_10FDX_CAP:
7679860Sgdamore@opensolaris.org 		case MAC_PROP_EN_10HDX_CAP:
7689860Sgdamore@opensolaris.org 		case MAC_PROP_AUTONEG:
7699860Sgdamore@opensolaris.org 		case MAC_PROP_FLOWCTRL:
7709860Sgdamore@opensolaris.org 			return (EBUSY);
7719860Sgdamore@opensolaris.org 		}
7729860Sgdamore@opensolaris.org 	}
7739860Sgdamore@opensolaris.org 
7749860Sgdamore@opensolaris.org 	switch (num) {
7759860Sgdamore@opensolaris.org 	case MAC_PROP_EN_1000FDX_CAP:
7769860Sgdamore@opensolaris.org 		capp = &ph->phy_cap_1000_fdx;
7779860Sgdamore@opensolaris.org 		advp = &ph->phy_en_1000_fdx;
7789860Sgdamore@opensolaris.org 		macpp = &mh->m_en_1000_fdx;
7799860Sgdamore@opensolaris.org 		break;
7809860Sgdamore@opensolaris.org 	case MAC_PROP_EN_1000HDX_CAP:
7819860Sgdamore@opensolaris.org 		capp = &ph->phy_cap_1000_hdx;
7829860Sgdamore@opensolaris.org 		advp = &ph->phy_en_1000_hdx;
7839860Sgdamore@opensolaris.org 		macpp = &mh->m_en_1000_hdx;
7849860Sgdamore@opensolaris.org 		break;
7859860Sgdamore@opensolaris.org 	case MAC_PROP_EN_100FDX_CAP:
7869860Sgdamore@opensolaris.org 		capp = &ph->phy_cap_100_fdx;
7879860Sgdamore@opensolaris.org 		advp = &ph->phy_en_100_fdx;
7889860Sgdamore@opensolaris.org 		macpp = &mh->m_en_100_fdx;
7899860Sgdamore@opensolaris.org 		break;
7909860Sgdamore@opensolaris.org 	case MAC_PROP_EN_100HDX_CAP:
7919860Sgdamore@opensolaris.org 		capp = &ph->phy_cap_100_hdx;
7929860Sgdamore@opensolaris.org 		advp = &ph->phy_en_100_hdx;
7939860Sgdamore@opensolaris.org 		macpp = &mh->m_en_100_hdx;
7949860Sgdamore@opensolaris.org 		break;
7959860Sgdamore@opensolaris.org 	case MAC_PROP_EN_100T4_CAP:
7969860Sgdamore@opensolaris.org 		capp = &ph->phy_cap_100_t4;
7979860Sgdamore@opensolaris.org 		advp = &ph->phy_en_100_t4;
7989860Sgdamore@opensolaris.org 		macpp = &mh->m_en_100_t4;
7999860Sgdamore@opensolaris.org 		break;
8009860Sgdamore@opensolaris.org 	case MAC_PROP_EN_10FDX_CAP:
8019860Sgdamore@opensolaris.org 		capp = &ph->phy_cap_10_fdx;
8029860Sgdamore@opensolaris.org 		advp = &ph->phy_en_10_fdx;
8039860Sgdamore@opensolaris.org 		macpp = &mh->m_en_10_fdx;
8049860Sgdamore@opensolaris.org 		break;
8059860Sgdamore@opensolaris.org 	case MAC_PROP_EN_10HDX_CAP:
8069860Sgdamore@opensolaris.org 		capp = &ph->phy_cap_10_hdx;
8079860Sgdamore@opensolaris.org 		advp = &ph->phy_en_10_hdx;
8089860Sgdamore@opensolaris.org 		macpp = &mh->m_en_10_hdx;
8099860Sgdamore@opensolaris.org 		break;
8109860Sgdamore@opensolaris.org 	case MAC_PROP_AUTONEG:
8119860Sgdamore@opensolaris.org 		capp = &ph->phy_cap_aneg;
8129860Sgdamore@opensolaris.org 		advp = &ph->phy_en_aneg;
8139860Sgdamore@opensolaris.org 		macpp = &mh->m_en_aneg;
8149860Sgdamore@opensolaris.org 		break;
8159860Sgdamore@opensolaris.org 	case MAC_PROP_FLOWCTRL:
8169860Sgdamore@opensolaris.org 		if (sz < sizeof (link_flowctrl_t)) {
8179860Sgdamore@opensolaris.org 			rv = EINVAL;
8189860Sgdamore@opensolaris.org 		} else {
8199860Sgdamore@opensolaris.org 			link_flowctrl_t	fc;
8209860Sgdamore@opensolaris.org 			boolean_t chg;
8219860Sgdamore@opensolaris.org 
8229860Sgdamore@opensolaris.org 			bcopy(valp, &fc, sizeof (fc));
8239860Sgdamore@opensolaris.org 
8249860Sgdamore@opensolaris.org 			chg = fc == ph->phy_en_flowctrl ? B_FALSE : B_TRUE;
8259860Sgdamore@opensolaris.org 			switch (fc) {
8269860Sgdamore@opensolaris.org 			case LINK_FLOWCTRL_NONE:
8279860Sgdamore@opensolaris.org 				ph->phy_en_pause = B_FALSE;
8289860Sgdamore@opensolaris.org 				ph->phy_en_asmpause = B_FALSE;
8299860Sgdamore@opensolaris.org 				ph->phy_en_flowctrl = fc;
8309860Sgdamore@opensolaris.org 				break;
8319860Sgdamore@opensolaris.org 			/*
8329860Sgdamore@opensolaris.org 			 * Note that while we don't have a way to
8339860Sgdamore@opensolaris.org 			 * advertise that we can RX pause (we just
8349860Sgdamore@opensolaris.org 			 * won't send pause frames), we advertise full
8359860Sgdamore@opensolaris.org 			 * support.  The MAC driver will learn of the
8369860Sgdamore@opensolaris.org 			 * configuration via the saved value of the
8379860Sgdamore@opensolaris.org 			 * tunable.
8389860Sgdamore@opensolaris.org 			 */
8399860Sgdamore@opensolaris.org 			case LINK_FLOWCTRL_BI:
8409860Sgdamore@opensolaris.org 			case LINK_FLOWCTRL_RX:
8419860Sgdamore@opensolaris.org 				if (ph->phy_cap_pause) {
8429860Sgdamore@opensolaris.org 					ph->phy_en_pause = B_TRUE;
8439860Sgdamore@opensolaris.org 					ph->phy_en_asmpause = B_TRUE;
8449860Sgdamore@opensolaris.org 					ph->phy_en_flowctrl = fc;
8459860Sgdamore@opensolaris.org 				} else {
8469860Sgdamore@opensolaris.org 					rv = EINVAL;
8479860Sgdamore@opensolaris.org 				}
8489860Sgdamore@opensolaris.org 				break;
8499860Sgdamore@opensolaris.org 
8509860Sgdamore@opensolaris.org 			/*
8519860Sgdamore@opensolaris.org 			 * Tell the other side that we can assert
8529860Sgdamore@opensolaris.org 			 * pause, but we cannot resend.
8539860Sgdamore@opensolaris.org 			 */
8549860Sgdamore@opensolaris.org 			case LINK_FLOWCTRL_TX:
8559860Sgdamore@opensolaris.org 				if (ph->phy_cap_asmpause) {
8569860Sgdamore@opensolaris.org 					ph->phy_en_pause = B_FALSE;
8579860Sgdamore@opensolaris.org 					ph->phy_en_flowctrl = fc;
8589860Sgdamore@opensolaris.org 					ph->phy_en_asmpause = B_TRUE;
8599860Sgdamore@opensolaris.org 				} else {
8609860Sgdamore@opensolaris.org 					rv = EINVAL;
8619860Sgdamore@opensolaris.org 				}
8629860Sgdamore@opensolaris.org 				break;
8639860Sgdamore@opensolaris.org 			default:
8649860Sgdamore@opensolaris.org 				rv = EINVAL;
8659860Sgdamore@opensolaris.org 				break;
8669860Sgdamore@opensolaris.org 			}
8679860Sgdamore@opensolaris.org 			if ((rv == 0) && chg) {
8689860Sgdamore@opensolaris.org 				mh->m_en_flowctrl = fc;
8699860Sgdamore@opensolaris.org 				mh->m_tstate = MII_STATE_RESET;
8709860Sgdamore@opensolaris.org 				cv_broadcast(&mh->m_cv);
8719860Sgdamore@opensolaris.org 			}
8729860Sgdamore@opensolaris.org 		}
8739860Sgdamore@opensolaris.org 		break;
8749860Sgdamore@opensolaris.org 
8759860Sgdamore@opensolaris.org 	default:
8769860Sgdamore@opensolaris.org 		rv = ENOTSUP;
8779860Sgdamore@opensolaris.org 		break;
8789860Sgdamore@opensolaris.org 	}
8799860Sgdamore@opensolaris.org 
8809860Sgdamore@opensolaris.org 	if (capp && advp && macpp) {
8819860Sgdamore@opensolaris.org 		if (sz < sizeof (uint8_t)) {
8829860Sgdamore@opensolaris.org 			rv = EINVAL;
8839860Sgdamore@opensolaris.org 
8849860Sgdamore@opensolaris.org 		} else if (*capp) {
8859860Sgdamore@opensolaris.org 			if (*advp != *(uint8_t *)valp) {
8869860Sgdamore@opensolaris.org 				*advp = *(uint8_t *)valp;
8879860Sgdamore@opensolaris.org 				*macpp = *(uint8_t *)valp;
8889860Sgdamore@opensolaris.org 				mh->m_tstate = MII_STATE_RESET;
8899860Sgdamore@opensolaris.org 				cv_broadcast(&mh->m_cv);
8909860Sgdamore@opensolaris.org 			}
8919860Sgdamore@opensolaris.org 			rv = 0;
8929860Sgdamore@opensolaris.org 		}
8939860Sgdamore@opensolaris.org 	}
8949860Sgdamore@opensolaris.org 
8959860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
8969860Sgdamore@opensolaris.org 	return (rv);
8979860Sgdamore@opensolaris.org }
8989860Sgdamore@opensolaris.org 
8999860Sgdamore@opensolaris.org int
9009860Sgdamore@opensolaris.org mii_m_getstat(mii_handle_t mh, uint_t stat, uint64_t *val)
9019860Sgdamore@opensolaris.org {
9029860Sgdamore@opensolaris.org 	phy_handle_t	*ph;
9039860Sgdamore@opensolaris.org 	int		rv = 0;
9049860Sgdamore@opensolaris.org 
9059860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
9069860Sgdamore@opensolaris.org 
9079860Sgdamore@opensolaris.org 	ph = mh->m_phy;
9089860Sgdamore@opensolaris.org 
9099860Sgdamore@opensolaris.org 	switch (stat) {
9109860Sgdamore@opensolaris.org 	case MAC_STAT_IFSPEED:
9119860Sgdamore@opensolaris.org 		*val = ph->phy_speed * 1000000ull;
9129860Sgdamore@opensolaris.org 		break;
9139860Sgdamore@opensolaris.org 	case ETHER_STAT_LINK_DUPLEX:
9149860Sgdamore@opensolaris.org 		*val = ph->phy_duplex;
9159860Sgdamore@opensolaris.org 		break;
9169860Sgdamore@opensolaris.org 	case ETHER_STAT_LINK_AUTONEG:
9179860Sgdamore@opensolaris.org 		*val = !!(ph->phy_adv_aneg && ph->phy_lp_aneg);
9189860Sgdamore@opensolaris.org 		break;
9199860Sgdamore@opensolaris.org 	case ETHER_STAT_XCVR_ID:
9209860Sgdamore@opensolaris.org 		*val = ph->phy_id;
9219860Sgdamore@opensolaris.org 		break;
9229860Sgdamore@opensolaris.org 	case ETHER_STAT_XCVR_INUSE:
9239860Sgdamore@opensolaris.org 		*val = ph->phy_type;
9249860Sgdamore@opensolaris.org 		break;
9259860Sgdamore@opensolaris.org 	case ETHER_STAT_XCVR_ADDR:
9269860Sgdamore@opensolaris.org 		*val = ph->phy_addr;
9279860Sgdamore@opensolaris.org 		break;
9289860Sgdamore@opensolaris.org 	case ETHER_STAT_LINK_ASMPAUSE:
9299860Sgdamore@opensolaris.org 		*val = ph->phy_adv_asmpause && ph->phy_lp_asmpause &&
9309860Sgdamore@opensolaris.org 		    ph->phy_adv_pause != ph->phy_lp_pause;
9319860Sgdamore@opensolaris.org 		break;
9329860Sgdamore@opensolaris.org 	case ETHER_STAT_LINK_PAUSE:
9339860Sgdamore@opensolaris.org 		*val = (ph->phy_flowctrl == LINK_FLOWCTRL_BI) ||
9349860Sgdamore@opensolaris.org 		    (ph->phy_flowctrl == LINK_FLOWCTRL_RX);
9359860Sgdamore@opensolaris.org 		break;
9369860Sgdamore@opensolaris.org 	case ETHER_STAT_CAP_1000FDX:
9379860Sgdamore@opensolaris.org 		*val = ph->phy_cap_1000_fdx;
9389860Sgdamore@opensolaris.org 		break;
9399860Sgdamore@opensolaris.org 	case ETHER_STAT_CAP_1000HDX:
9409860Sgdamore@opensolaris.org 		*val = ph->phy_cap_1000_hdx;
9419860Sgdamore@opensolaris.org 		break;
9429860Sgdamore@opensolaris.org 	case ETHER_STAT_CAP_100FDX:
9439860Sgdamore@opensolaris.org 		*val = ph->phy_cap_100_fdx;
9449860Sgdamore@opensolaris.org 		break;
9459860Sgdamore@opensolaris.org 	case ETHER_STAT_CAP_100HDX:
9469860Sgdamore@opensolaris.org 		*val = ph->phy_cap_100_hdx;
9479860Sgdamore@opensolaris.org 		break;
9489860Sgdamore@opensolaris.org 	case ETHER_STAT_CAP_10FDX:
9499860Sgdamore@opensolaris.org 		*val = ph->phy_cap_10_fdx;
9509860Sgdamore@opensolaris.org 		break;
9519860Sgdamore@opensolaris.org 	case ETHER_STAT_CAP_10HDX:
9529860Sgdamore@opensolaris.org 		*val = ph->phy_cap_10_hdx;
9539860Sgdamore@opensolaris.org 		break;
9549860Sgdamore@opensolaris.org 	case ETHER_STAT_CAP_100T4:
9559860Sgdamore@opensolaris.org 		*val = ph->phy_cap_100_t4;
9569860Sgdamore@opensolaris.org 		break;
9579860Sgdamore@opensolaris.org 	case ETHER_STAT_CAP_AUTONEG:
9589860Sgdamore@opensolaris.org 		*val = ph->phy_cap_aneg;
9599860Sgdamore@opensolaris.org 		break;
9609860Sgdamore@opensolaris.org 	case ETHER_STAT_CAP_PAUSE:
9619860Sgdamore@opensolaris.org 		*val = ph->phy_cap_pause;
9629860Sgdamore@opensolaris.org 		break;
9639860Sgdamore@opensolaris.org 	case ETHER_STAT_CAP_ASMPAUSE:
9649860Sgdamore@opensolaris.org 		*val = ph->phy_cap_asmpause;
9659860Sgdamore@opensolaris.org 		break;
9669860Sgdamore@opensolaris.org 
9679860Sgdamore@opensolaris.org 	case ETHER_STAT_LP_CAP_1000FDX:
9689860Sgdamore@opensolaris.org 		*val = ph->phy_lp_1000_fdx;
9699860Sgdamore@opensolaris.org 		break;
9709860Sgdamore@opensolaris.org 	case ETHER_STAT_LP_CAP_1000HDX:
9719860Sgdamore@opensolaris.org 		*val = ph->phy_lp_1000_hdx;
9729860Sgdamore@opensolaris.org 		break;
9739860Sgdamore@opensolaris.org 	case ETHER_STAT_LP_CAP_100FDX:
9749860Sgdamore@opensolaris.org 		*val = ph->phy_lp_100_fdx;
9759860Sgdamore@opensolaris.org 		break;
9769860Sgdamore@opensolaris.org 	case ETHER_STAT_LP_CAP_100HDX:
9779860Sgdamore@opensolaris.org 		*val = ph->phy_lp_100_hdx;
9789860Sgdamore@opensolaris.org 		break;
9799860Sgdamore@opensolaris.org 	case ETHER_STAT_LP_CAP_10FDX:
9809860Sgdamore@opensolaris.org 		*val = ph->phy_lp_10_fdx;
9819860Sgdamore@opensolaris.org 		break;
9829860Sgdamore@opensolaris.org 	case ETHER_STAT_LP_CAP_10HDX:
9839860Sgdamore@opensolaris.org 		*val = ph->phy_lp_10_hdx;
9849860Sgdamore@opensolaris.org 		break;
9859860Sgdamore@opensolaris.org 	case ETHER_STAT_LP_CAP_100T4:
9869860Sgdamore@opensolaris.org 		*val = ph->phy_lp_100_t4;
9879860Sgdamore@opensolaris.org 		break;
9889860Sgdamore@opensolaris.org 	case ETHER_STAT_LP_CAP_AUTONEG:
9899860Sgdamore@opensolaris.org 		*val = ph->phy_lp_aneg;
9909860Sgdamore@opensolaris.org 		break;
9919860Sgdamore@opensolaris.org 	case ETHER_STAT_LP_CAP_PAUSE:
9929860Sgdamore@opensolaris.org 		*val = ph->phy_lp_pause;
9939860Sgdamore@opensolaris.org 		break;
9949860Sgdamore@opensolaris.org 	case ETHER_STAT_LP_CAP_ASMPAUSE:
9959860Sgdamore@opensolaris.org 		*val = ph->phy_lp_asmpause;
9969860Sgdamore@opensolaris.org 		break;
9979860Sgdamore@opensolaris.org 
9989860Sgdamore@opensolaris.org 	case ETHER_STAT_ADV_CAP_1000FDX:
9999860Sgdamore@opensolaris.org 		*val = ph->phy_adv_1000_fdx;
10009860Sgdamore@opensolaris.org 		break;
10019860Sgdamore@opensolaris.org 	case ETHER_STAT_ADV_CAP_1000HDX:
10029860Sgdamore@opensolaris.org 		*val = ph->phy_adv_1000_hdx;
10039860Sgdamore@opensolaris.org 		break;
10049860Sgdamore@opensolaris.org 	case ETHER_STAT_ADV_CAP_100FDX:
10059860Sgdamore@opensolaris.org 		*val = ph->phy_adv_100_fdx;
10069860Sgdamore@opensolaris.org 		break;
10079860Sgdamore@opensolaris.org 	case ETHER_STAT_ADV_CAP_100HDX:
10089860Sgdamore@opensolaris.org 		*val = ph->phy_adv_100_hdx;
10099860Sgdamore@opensolaris.org 		break;
10109860Sgdamore@opensolaris.org 	case ETHER_STAT_ADV_CAP_10FDX:
10119860Sgdamore@opensolaris.org 		*val = ph->phy_adv_10_fdx;
10129860Sgdamore@opensolaris.org 		break;
10139860Sgdamore@opensolaris.org 	case ETHER_STAT_ADV_CAP_10HDX:
10149860Sgdamore@opensolaris.org 		*val = ph->phy_adv_10_hdx;
10159860Sgdamore@opensolaris.org 		break;
10169860Sgdamore@opensolaris.org 	case ETHER_STAT_ADV_CAP_100T4:
10179860Sgdamore@opensolaris.org 		*val = ph->phy_adv_100_t4;
10189860Sgdamore@opensolaris.org 		break;
10199860Sgdamore@opensolaris.org 	case ETHER_STAT_ADV_CAP_AUTONEG:
10209860Sgdamore@opensolaris.org 		*val = ph->phy_adv_aneg;
10219860Sgdamore@opensolaris.org 		break;
10229860Sgdamore@opensolaris.org 	case ETHER_STAT_ADV_CAP_PAUSE:
10239860Sgdamore@opensolaris.org 		*val = ph->phy_adv_pause;
10249860Sgdamore@opensolaris.org 		break;
10259860Sgdamore@opensolaris.org 	case ETHER_STAT_ADV_CAP_ASMPAUSE:
10269860Sgdamore@opensolaris.org 		*val = ph->phy_adv_asmpause;
10279860Sgdamore@opensolaris.org 		break;
10289860Sgdamore@opensolaris.org 
10299860Sgdamore@opensolaris.org 	default:
10309860Sgdamore@opensolaris.org 		rv = ENOTSUP;
10319860Sgdamore@opensolaris.org 		break;
10329860Sgdamore@opensolaris.org 	}
10339860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
10349860Sgdamore@opensolaris.org 
10359860Sgdamore@opensolaris.org 	return (rv);
10369860Sgdamore@opensolaris.org }
10379860Sgdamore@opensolaris.org 
10389860Sgdamore@opensolaris.org /*
10399860Sgdamore@opensolaris.org  * PHY support routines.  Private to the MII module and the vendor
10409860Sgdamore@opensolaris.org  * specific PHY implementation code.
10419860Sgdamore@opensolaris.org  */
10429860Sgdamore@opensolaris.org uint16_t
10439860Sgdamore@opensolaris.org phy_read(phy_handle_t *ph, uint8_t reg)
10449860Sgdamore@opensolaris.org {
10459860Sgdamore@opensolaris.org 	mii_handle_t	mh = ph->phy_mii;
10469860Sgdamore@opensolaris.org 
10479860Sgdamore@opensolaris.org 	return ((*mh->m_ops.mii_read)(mh->m_private, ph->phy_addr, reg));
10489860Sgdamore@opensolaris.org }
10499860Sgdamore@opensolaris.org 
10509860Sgdamore@opensolaris.org void
10519860Sgdamore@opensolaris.org phy_write(phy_handle_t *ph, uint8_t reg, uint16_t val)
10529860Sgdamore@opensolaris.org {
10539860Sgdamore@opensolaris.org 	mii_handle_t	mh = ph->phy_mii;
10549860Sgdamore@opensolaris.org 
10559860Sgdamore@opensolaris.org 	(*mh->m_ops.mii_write)(mh->m_private, ph->phy_addr, reg, val);
10569860Sgdamore@opensolaris.org }
10579860Sgdamore@opensolaris.org 
10589860Sgdamore@opensolaris.org int
10599860Sgdamore@opensolaris.org phy_reset(phy_handle_t *ph)
10609860Sgdamore@opensolaris.org {
10619860Sgdamore@opensolaris.org 	ASSERT(mutex_owned(&ph->phy_mii->m_lock));
10629860Sgdamore@opensolaris.org 
10639860Sgdamore@opensolaris.org 	/*
106410359SGarrett.Damore@Sun.COM 	 * For our device, make sure its powered up and unisolated.
10659860Sgdamore@opensolaris.org 	 */
10669860Sgdamore@opensolaris.org 	PHY_CLR(ph, MII_CONTROL,
106710359SGarrett.Damore@Sun.COM 	    MII_CONTROL_PWRDN | MII_CONTROL_ISOLATE);
10689860Sgdamore@opensolaris.org 
10699860Sgdamore@opensolaris.org 	/*
10709860Sgdamore@opensolaris.org 	 * Finally reset it.
10719860Sgdamore@opensolaris.org 	 */
10729860Sgdamore@opensolaris.org 	PHY_SET(ph, MII_CONTROL, MII_CONTROL_RESET);
10739860Sgdamore@opensolaris.org 
10749860Sgdamore@opensolaris.org 	/*
10759860Sgdamore@opensolaris.org 	 * Apparently some devices (DP83840A) like to have a little
10769860Sgdamore@opensolaris.org 	 * bit of a wait before we start accessing anything else on
10779860Sgdamore@opensolaris.org 	 * the PHY.
10789860Sgdamore@opensolaris.org 	 */
10799860Sgdamore@opensolaris.org 	drv_usecwait(500);
10809860Sgdamore@opensolaris.org 
10819860Sgdamore@opensolaris.org 	/*
10829860Sgdamore@opensolaris.org 	 * Wait for reset to complete - probably very fast, but no
10839860Sgdamore@opensolaris.org 	 * more than 0.5 sec according to spec.  It would be nice if
10849860Sgdamore@opensolaris.org 	 * we could use delay() here, but MAC drivers may call
10859860Sgdamore@opensolaris.org 	 * functions which hold this lock in interrupt context, so
10869860Sgdamore@opensolaris.org 	 * sleeping would be a definite no-no.  The good news here is
10879860Sgdamore@opensolaris.org 	 * that it seems to be the case that most devices come back
10889860Sgdamore@opensolaris.org 	 * within only a few hundred usec.
10899860Sgdamore@opensolaris.org 	 */
10909860Sgdamore@opensolaris.org 	for (int i = 500000; i; i -= 100) {
10919860Sgdamore@opensolaris.org 		if ((phy_read(ph, MII_CONTROL) & MII_CONTROL_RESET) == 0) {
10929860Sgdamore@opensolaris.org 			/* reset completed */
10939860Sgdamore@opensolaris.org 			return (DDI_SUCCESS);
10949860Sgdamore@opensolaris.org 		}
10959860Sgdamore@opensolaris.org 		drv_usecwait(100);
10969860Sgdamore@opensolaris.org 	}
10979860Sgdamore@opensolaris.org 
10989860Sgdamore@opensolaris.org 	return (DDI_FAILURE);
10999860Sgdamore@opensolaris.org }
11009860Sgdamore@opensolaris.org 
11019860Sgdamore@opensolaris.org int
11029860Sgdamore@opensolaris.org phy_stop(phy_handle_t *ph)
11039860Sgdamore@opensolaris.org {
110410066Sgdamore@opensolaris.org 	phy_write(ph, MII_CONTROL, MII_CONTROL_ISOLATE);
110510066Sgdamore@opensolaris.org 
11069860Sgdamore@opensolaris.org 	return (DDI_SUCCESS);
11079860Sgdamore@opensolaris.org }
11089860Sgdamore@opensolaris.org 
11099860Sgdamore@opensolaris.org int
111010359SGarrett.Damore@Sun.COM phy_loop(phy_handle_t *ph)
11119860Sgdamore@opensolaris.org {
111210359SGarrett.Damore@Sun.COM 	uint16_t	bmcr, gtcr;
111310359SGarrett.Damore@Sun.COM 
11149860Sgdamore@opensolaris.org 	ASSERT(mutex_owned(&ph->phy_mii->m_lock));
11159860Sgdamore@opensolaris.org 
11169860Sgdamore@opensolaris.org 	/*
11179860Sgdamore@opensolaris.org 	 * Disable everything to start... we'll add in modes as we go.
11189860Sgdamore@opensolaris.org 	 */
11199860Sgdamore@opensolaris.org 	ph->phy_adv_aneg = B_FALSE;
11209860Sgdamore@opensolaris.org 	ph->phy_adv_1000_fdx = B_FALSE;
11219860Sgdamore@opensolaris.org 	ph->phy_adv_1000_hdx = B_FALSE;
11229860Sgdamore@opensolaris.org 	ph->phy_adv_100_fdx = B_FALSE;
11239860Sgdamore@opensolaris.org 	ph->phy_adv_100_t4 = B_FALSE;
11249860Sgdamore@opensolaris.org 	ph->phy_adv_100_hdx = B_FALSE;
11259860Sgdamore@opensolaris.org 	ph->phy_adv_10_fdx = B_FALSE;
11269860Sgdamore@opensolaris.org 	ph->phy_adv_10_hdx = B_FALSE;
11279860Sgdamore@opensolaris.org 	ph->phy_adv_pause = B_FALSE;
11289860Sgdamore@opensolaris.org 	ph->phy_adv_asmpause = B_FALSE;
11299860Sgdamore@opensolaris.org 
113010359SGarrett.Damore@Sun.COM 	bmcr = 0;
113110359SGarrett.Damore@Sun.COM 	gtcr = MII_MSCONTROL_MANUAL | MII_MSCONTROL_MASTER;
113210359SGarrett.Damore@Sun.COM 
11339860Sgdamore@opensolaris.org 	switch (ph->phy_loopback) {
11349860Sgdamore@opensolaris.org 	case PHY_LB_NONE:
113510359SGarrett.Damore@Sun.COM 		/* We shouldn't be here */
113610359SGarrett.Damore@Sun.COM 		ASSERT(0);
11379860Sgdamore@opensolaris.org 		break;
11389860Sgdamore@opensolaris.org 
11399860Sgdamore@opensolaris.org 	case PHY_LB_INT_PHY:
114010359SGarrett.Damore@Sun.COM 		bmcr |= MII_CONTROL_LOOPBACK;
114110359SGarrett.Damore@Sun.COM 		ph->phy_duplex = LINK_DUPLEX_FULL;
11429860Sgdamore@opensolaris.org 		if (ph->phy_cap_1000_fdx) {
114310359SGarrett.Damore@Sun.COM 			bmcr |= MII_CONTROL_1GB | MII_CONTROL_FDUPLEX;
114410359SGarrett.Damore@Sun.COM 			ph->phy_speed = 1000;
11459860Sgdamore@opensolaris.org 		} else if (ph->phy_cap_100_fdx) {
114610359SGarrett.Damore@Sun.COM 			bmcr |= MII_CONTROL_100MB | MII_CONTROL_FDUPLEX;
114710359SGarrett.Damore@Sun.COM 			ph->phy_speed = 100;
11489860Sgdamore@opensolaris.org 		} else if (ph->phy_cap_10_fdx) {
114910359SGarrett.Damore@Sun.COM 			bmcr |= MII_CONTROL_FDUPLEX;
115010359SGarrett.Damore@Sun.COM 			ph->phy_speed = 10;
11519860Sgdamore@opensolaris.org 		}
11529860Sgdamore@opensolaris.org 		break;
11539860Sgdamore@opensolaris.org 
11549860Sgdamore@opensolaris.org 	case PHY_LB_EXT_10:
115510359SGarrett.Damore@Sun.COM 		bmcr = MII_CONTROL_FDUPLEX;
115610359SGarrett.Damore@Sun.COM 		ph->phy_speed = 10;
115710359SGarrett.Damore@Sun.COM 		ph->phy_duplex = LINK_DUPLEX_FULL;
11589860Sgdamore@opensolaris.org 		break;
11599860Sgdamore@opensolaris.org 
11609860Sgdamore@opensolaris.org 	case PHY_LB_EXT_100:
116110359SGarrett.Damore@Sun.COM 		bmcr = MII_CONTROL_100MB | MII_CONTROL_FDUPLEX;
116210359SGarrett.Damore@Sun.COM 		ph->phy_speed = 100;
116310359SGarrett.Damore@Sun.COM 		ph->phy_duplex = LINK_DUPLEX_FULL;
11649860Sgdamore@opensolaris.org 		break;
11659860Sgdamore@opensolaris.org 
11669860Sgdamore@opensolaris.org 	case PHY_LB_EXT_1000:
116710359SGarrett.Damore@Sun.COM 		bmcr = MII_CONTROL_1GB | MII_CONTROL_FDUPLEX;
116810359SGarrett.Damore@Sun.COM 		ph->phy_speed = 1000;
116910359SGarrett.Damore@Sun.COM 		ph->phy_duplex = LINK_DUPLEX_FULL;
117010359SGarrett.Damore@Sun.COM 		break;
117110359SGarrett.Damore@Sun.COM 	}
117210359SGarrett.Damore@Sun.COM 
117310359SGarrett.Damore@Sun.COM 	ph->phy_link = LINK_STATE_UP;	/* force up for loopback */
117410359SGarrett.Damore@Sun.COM 	ph->phy_flowctrl = LINK_FLOWCTRL_NONE;
117510359SGarrett.Damore@Sun.COM 
117610359SGarrett.Damore@Sun.COM 	switch (ph->phy_type) {
117710359SGarrett.Damore@Sun.COM 	case XCVR_1000T:
117810359SGarrett.Damore@Sun.COM 	case XCVR_1000X:
117910359SGarrett.Damore@Sun.COM 	case XCVR_100T2:
118010359SGarrett.Damore@Sun.COM 		phy_write(ph, MII_MSCONTROL, gtcr);
11819860Sgdamore@opensolaris.org 		break;
11829860Sgdamore@opensolaris.org 	}
11839860Sgdamore@opensolaris.org 
118410359SGarrett.Damore@Sun.COM 	phy_write(ph, MII_CONTROL, bmcr);
118510359SGarrett.Damore@Sun.COM 
118610359SGarrett.Damore@Sun.COM 	return (DDI_SUCCESS);
118710359SGarrett.Damore@Sun.COM }
118810359SGarrett.Damore@Sun.COM 
118910359SGarrett.Damore@Sun.COM int
119010359SGarrett.Damore@Sun.COM phy_start(phy_handle_t *ph)
119110359SGarrett.Damore@Sun.COM {
119210359SGarrett.Damore@Sun.COM 	uint16_t	bmcr, anar, gtcr;
119310359SGarrett.Damore@Sun.COM 	ASSERT(mutex_owned(&ph->phy_mii->m_lock));
119410359SGarrett.Damore@Sun.COM 
119510359SGarrett.Damore@Sun.COM 	ASSERT(ph->phy_loopback == PHY_LB_NONE);
119610359SGarrett.Damore@Sun.COM 
119710359SGarrett.Damore@Sun.COM 	/*
119810359SGarrett.Damore@Sun.COM 	 * No loopback overrides, so try to advertise everything
119910359SGarrett.Damore@Sun.COM 	 * that is administratively enabled.
120010359SGarrett.Damore@Sun.COM 	 */
120110359SGarrett.Damore@Sun.COM 	ph->phy_adv_aneg = ph->phy_en_aneg;
120210359SGarrett.Damore@Sun.COM 	ph->phy_adv_1000_fdx = ph->phy_en_1000_fdx;
120310359SGarrett.Damore@Sun.COM 	ph->phy_adv_1000_hdx = ph->phy_en_1000_hdx;
120410359SGarrett.Damore@Sun.COM 	ph->phy_adv_100_fdx = ph->phy_en_100_fdx;
120510359SGarrett.Damore@Sun.COM 	ph->phy_adv_100_t4 = ph->phy_en_100_t4;
120610359SGarrett.Damore@Sun.COM 	ph->phy_adv_100_hdx = ph->phy_en_100_hdx;
120710359SGarrett.Damore@Sun.COM 	ph->phy_adv_10_fdx = ph->phy_en_10_fdx;
120810359SGarrett.Damore@Sun.COM 	ph->phy_adv_10_hdx = ph->phy_en_10_hdx;
120910359SGarrett.Damore@Sun.COM 	ph->phy_adv_pause = ph->phy_en_pause;
121010359SGarrett.Damore@Sun.COM 	ph->phy_adv_asmpause = ph->phy_en_asmpause;
121110359SGarrett.Damore@Sun.COM 
12129860Sgdamore@opensolaris.org 	/*
12139860Sgdamore@opensolaris.org 	 * Limit properties to what the hardware can actually support.
12149860Sgdamore@opensolaris.org 	 */
12159860Sgdamore@opensolaris.org #define	FILTER_ADV(CAP)		\
12169860Sgdamore@opensolaris.org 	if (!ph->phy_cap_##CAP)	\
12179860Sgdamore@opensolaris.org 	    ph->phy_adv_##CAP = 0
12189860Sgdamore@opensolaris.org 
12199860Sgdamore@opensolaris.org 	FILTER_ADV(aneg);
12209860Sgdamore@opensolaris.org 	FILTER_ADV(1000_fdx);
12219860Sgdamore@opensolaris.org 	FILTER_ADV(1000_hdx);
12229860Sgdamore@opensolaris.org 	FILTER_ADV(100_fdx);
12239860Sgdamore@opensolaris.org 	FILTER_ADV(100_t4);
12249860Sgdamore@opensolaris.org 	FILTER_ADV(100_hdx);
12259860Sgdamore@opensolaris.org 	FILTER_ADV(10_fdx);
12269860Sgdamore@opensolaris.org 	FILTER_ADV(10_hdx);
12279860Sgdamore@opensolaris.org 	FILTER_ADV(pause);
12289860Sgdamore@opensolaris.org 	FILTER_ADV(asmpause);
12299860Sgdamore@opensolaris.org 
12309860Sgdamore@opensolaris.org #undef	FILTER_ADV
12319860Sgdamore@opensolaris.org 
12329860Sgdamore@opensolaris.org 	/*
12339860Sgdamore@opensolaris.org 	 * We need at least one valid mode.
12349860Sgdamore@opensolaris.org 	 */
12359860Sgdamore@opensolaris.org 	if ((!ph->phy_adv_1000_fdx) &&
12369860Sgdamore@opensolaris.org 	    (!ph->phy_adv_1000_hdx) &&
12379860Sgdamore@opensolaris.org 	    (!ph->phy_adv_100_t4) &&
12389860Sgdamore@opensolaris.org 	    (!ph->phy_adv_100_fdx) &&
12399860Sgdamore@opensolaris.org 	    (!ph->phy_adv_100_hdx) &&
12409860Sgdamore@opensolaris.org 	    (!ph->phy_adv_10_fdx) &&
12419860Sgdamore@opensolaris.org 	    (!ph->phy_adv_10_hdx)) {
12429860Sgdamore@opensolaris.org 
12439860Sgdamore@opensolaris.org 		phy_warn(ph,
12449860Sgdamore@opensolaris.org 		    "No valid link mode selected.  Powering down PHY.");
12459860Sgdamore@opensolaris.org 
12469860Sgdamore@opensolaris.org 		PHY_SET(ph, MII_CONTROL, MII_CONTROL_PWRDN);
12479860Sgdamore@opensolaris.org 
12489860Sgdamore@opensolaris.org 		ph->phy_link = LINK_STATE_DOWN;
12499860Sgdamore@opensolaris.org 		return (DDI_SUCCESS);
12509860Sgdamore@opensolaris.org 	}
12519860Sgdamore@opensolaris.org 
125210359SGarrett.Damore@Sun.COM 	bmcr = 0;
125310359SGarrett.Damore@Sun.COM 	gtcr = 0;
12549860Sgdamore@opensolaris.org 
12559860Sgdamore@opensolaris.org 	if (ph->phy_adv_aneg) {
12569860Sgdamore@opensolaris.org 		bmcr |= MII_CONTROL_ANE | MII_CONTROL_RSAN;
12579860Sgdamore@opensolaris.org 	}
12589860Sgdamore@opensolaris.org 
12599860Sgdamore@opensolaris.org 	if ((ph->phy_adv_1000_fdx) || (ph->phy_adv_1000_hdx)) {
12609860Sgdamore@opensolaris.org 		bmcr |= MII_CONTROL_1GB;
12619860Sgdamore@opensolaris.org 
12629860Sgdamore@opensolaris.org 	} else if (ph->phy_adv_100_fdx || ph->phy_adv_100_hdx ||
12639860Sgdamore@opensolaris.org 	    ph->phy_adv_100_t4) {
12649860Sgdamore@opensolaris.org 		bmcr |= MII_CONTROL_100MB;
12659860Sgdamore@opensolaris.org 	}
12669860Sgdamore@opensolaris.org 
12679860Sgdamore@opensolaris.org 	if (ph->phy_adv_1000_fdx || ph->phy_adv_100_fdx || ph->phy_adv_10_fdx) {
12689860Sgdamore@opensolaris.org 		bmcr |= MII_CONTROL_FDUPLEX;
12699860Sgdamore@opensolaris.org 	}
12709860Sgdamore@opensolaris.org 
12719860Sgdamore@opensolaris.org 	if (ph->phy_type == XCVR_1000X) {
12729860Sgdamore@opensolaris.org 		/* 1000BASE-X (usually fiber) */
12739860Sgdamore@opensolaris.org 		anar = 0;
12749860Sgdamore@opensolaris.org 		if (ph->phy_adv_1000_fdx) {
12759860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_X_FD;
12769860Sgdamore@opensolaris.org 		}
12779860Sgdamore@opensolaris.org 		if (ph->phy_adv_1000_hdx) {
12789860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_X_HD;
12799860Sgdamore@opensolaris.org 		}
12809860Sgdamore@opensolaris.org 		if (ph->phy_adv_pause) {
12819860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_X_PAUSE;
12829860Sgdamore@opensolaris.org 		}
12839860Sgdamore@opensolaris.org 		if (ph->phy_adv_asmpause) {
12849860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_X_ASMPAUSE;
12859860Sgdamore@opensolaris.org 		}
12869860Sgdamore@opensolaris.org 
12879860Sgdamore@opensolaris.org 	} else if (ph->phy_type == XCVR_100T2) {
12889860Sgdamore@opensolaris.org 		/* 100BASE-T2 */
12899860Sgdamore@opensolaris.org 		anar = 0;
12909860Sgdamore@opensolaris.org 		if (ph->phy_adv_100_fdx) {
12919860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_T2_FD;
12929860Sgdamore@opensolaris.org 		}
12939860Sgdamore@opensolaris.org 		if (ph->phy_adv_100_hdx) {
12949860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_T2_HD;
12959860Sgdamore@opensolaris.org 		}
12969860Sgdamore@opensolaris.org 
12979860Sgdamore@opensolaris.org 	} else {
12989860Sgdamore@opensolaris.org 		anar = MII_AN_SELECTOR_8023;
12999860Sgdamore@opensolaris.org 
13009860Sgdamore@opensolaris.org 		/* 1000BASE-T or 100BASE-X probably  */
13019860Sgdamore@opensolaris.org 		if (ph->phy_adv_1000_fdx) {
13029860Sgdamore@opensolaris.org 			gtcr |= MII_MSCONTROL_1000T_FD;
13039860Sgdamore@opensolaris.org 		}
13049860Sgdamore@opensolaris.org 		if (ph->phy_adv_1000_hdx) {
13059860Sgdamore@opensolaris.org 			gtcr |= MII_MSCONTROL_1000T;
13069860Sgdamore@opensolaris.org 		}
13079860Sgdamore@opensolaris.org 		if (ph->phy_adv_100_fdx) {
13089860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_100BASE_TX_FD;
13099860Sgdamore@opensolaris.org 		}
13109860Sgdamore@opensolaris.org 		if (ph->phy_adv_100_hdx) {
13119860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_100BASE_TX;
13129860Sgdamore@opensolaris.org 		}
13139860Sgdamore@opensolaris.org 		if (ph->phy_adv_100_t4) {
13149860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_100BASE_T4;
13159860Sgdamore@opensolaris.org 		}
13169860Sgdamore@opensolaris.org 		if (ph->phy_adv_10_fdx) {
13179860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_10BASE_T_FD;
13189860Sgdamore@opensolaris.org 		}
131911414Sgdamore@opensolaris.org 		if (ph->phy_adv_10_hdx) {
13209860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_10BASE_T;
13219860Sgdamore@opensolaris.org 		}
13229860Sgdamore@opensolaris.org 		if (ph->phy_adv_pause) {
13239860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_PAUSE;
13249860Sgdamore@opensolaris.org 		}
13259860Sgdamore@opensolaris.org 		if (ph->phy_adv_asmpause) {
13269860Sgdamore@opensolaris.org 			anar |= MII_ABILITY_ASMPAUSE;
13279860Sgdamore@opensolaris.org 		}
13289860Sgdamore@opensolaris.org 	}
13299860Sgdamore@opensolaris.org 
13309860Sgdamore@opensolaris.org 	ph->phy_link = LINK_STATE_DOWN;
13319860Sgdamore@opensolaris.org 	ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
13329860Sgdamore@opensolaris.org 	ph->phy_speed = 0;
13339860Sgdamore@opensolaris.org 
13349860Sgdamore@opensolaris.org 	phy_write(ph, MII_AN_ADVERT, anar);
133510291Sgdamore@opensolaris.org 	phy_write(ph, MII_CONTROL, bmcr & ~(MII_CONTROL_RSAN));
133610291Sgdamore@opensolaris.org 
13379860Sgdamore@opensolaris.org 	switch (ph->phy_type) {
13389860Sgdamore@opensolaris.org 	case XCVR_1000T:
13399860Sgdamore@opensolaris.org 	case XCVR_1000X:
13409860Sgdamore@opensolaris.org 	case XCVR_100T2:
13419860Sgdamore@opensolaris.org 		phy_write(ph, MII_MSCONTROL, gtcr);
13429860Sgdamore@opensolaris.org 	}
13439860Sgdamore@opensolaris.org 
13449860Sgdamore@opensolaris.org 	/*
13459860Sgdamore@opensolaris.org 	 * Finally, this will start up autoneg if it is enabled, or
13469860Sgdamore@opensolaris.org 	 * force link settings otherwise.
13479860Sgdamore@opensolaris.org 	 */
13489860Sgdamore@opensolaris.org 	phy_write(ph, MII_CONTROL, bmcr);
13499860Sgdamore@opensolaris.org 
13509860Sgdamore@opensolaris.org 	return (DDI_SUCCESS);
13519860Sgdamore@opensolaris.org }
13529860Sgdamore@opensolaris.org 
13539860Sgdamore@opensolaris.org 
13549860Sgdamore@opensolaris.org int
13559860Sgdamore@opensolaris.org phy_check(phy_handle_t *ph)
13569860Sgdamore@opensolaris.org {
13579860Sgdamore@opensolaris.org 	uint16_t control, status, lpar, msstat, anexp;
13589860Sgdamore@opensolaris.org 	int debounces = 100;
13599860Sgdamore@opensolaris.org 
13609860Sgdamore@opensolaris.org 	ASSERT(mutex_owned(&ph->phy_mii->m_lock));
13619860Sgdamore@opensolaris.org 
13629860Sgdamore@opensolaris.org debounce:
13639860Sgdamore@opensolaris.org 	status = phy_read(ph, MII_STATUS);
13649860Sgdamore@opensolaris.org 	control = phy_read(ph, MII_CONTROL);
13659860Sgdamore@opensolaris.org 
13669860Sgdamore@opensolaris.org 	if (status & MII_STATUS_EXTENDED) {
13679860Sgdamore@opensolaris.org 		lpar = phy_read(ph, MII_AN_LPABLE);
13689860Sgdamore@opensolaris.org 		anexp = phy_read(ph, MII_AN_EXPANSION);
13699860Sgdamore@opensolaris.org 	} else {
13709860Sgdamore@opensolaris.org 		lpar = 0;
13719860Sgdamore@opensolaris.org 		anexp = 0;
13729860Sgdamore@opensolaris.org 	}
13739860Sgdamore@opensolaris.org 
13749860Sgdamore@opensolaris.org 	/*
13759860Sgdamore@opensolaris.org 	 * We reread to clear any latched bits.  This also debounces
13769860Sgdamore@opensolaris.org 	 * any state that might be in transition.
13779860Sgdamore@opensolaris.org 	 */
13789860Sgdamore@opensolaris.org 	drv_usecwait(10);
13799860Sgdamore@opensolaris.org 	if ((status != phy_read(ph, MII_STATUS)) && debounces) {
13809860Sgdamore@opensolaris.org 		debounces--;
13819860Sgdamore@opensolaris.org 		goto debounce;
13829860Sgdamore@opensolaris.org 	}
13839860Sgdamore@opensolaris.org 
13849860Sgdamore@opensolaris.org 	/*
13859860Sgdamore@opensolaris.org 	 * Detect the situation where the PHY is removed or has died.
13869860Sgdamore@opensolaris.org 	 * According to spec, at least one bit of status must be set,
13879860Sgdamore@opensolaris.org 	 * and at least one bit must be clear.
13889860Sgdamore@opensolaris.org 	 */
13899860Sgdamore@opensolaris.org 	if ((status == 0xffff) || (status == 0)) {
13909860Sgdamore@opensolaris.org 		ph->phy_speed = 0;
13919860Sgdamore@opensolaris.org 		ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
13929860Sgdamore@opensolaris.org 		ph->phy_link = LINK_STATE_UNKNOWN;
13939860Sgdamore@opensolaris.org 		ph->phy_present = B_FALSE;
13949860Sgdamore@opensolaris.org 		return (DDI_FAILURE);
13959860Sgdamore@opensolaris.org 	}
13969860Sgdamore@opensolaris.org 
13979860Sgdamore@opensolaris.org 	/* We only respect the link flag if we are not in loopback. */
13989860Sgdamore@opensolaris.org 	if ((ph->phy_loopback != PHY_LB_INT_PHY) &&
13999860Sgdamore@opensolaris.org 	    ((status & MII_STATUS_LINKUP) == 0)) {
14009860Sgdamore@opensolaris.org 		ph->phy_speed = 0;
14019860Sgdamore@opensolaris.org 		ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
14029860Sgdamore@opensolaris.org 		ph->phy_link = LINK_STATE_DOWN;
14039860Sgdamore@opensolaris.org 		return (DDI_SUCCESS);
14049860Sgdamore@opensolaris.org 	}
14059860Sgdamore@opensolaris.org 
14069860Sgdamore@opensolaris.org 	ph->phy_link = LINK_STATE_UP;
14079860Sgdamore@opensolaris.org 
14089860Sgdamore@opensolaris.org 	if ((control & MII_CONTROL_ANE) == 0) {
14099860Sgdamore@opensolaris.org 
14109860Sgdamore@opensolaris.org 		ph->phy_lp_aneg = B_FALSE;
14119860Sgdamore@opensolaris.org 		ph->phy_lp_10_hdx = B_FALSE;
14129860Sgdamore@opensolaris.org 		ph->phy_lp_10_fdx = B_FALSE;
14139860Sgdamore@opensolaris.org 		ph->phy_lp_100_t4 = B_FALSE;
14149860Sgdamore@opensolaris.org 		ph->phy_lp_100_hdx = B_FALSE;
14159860Sgdamore@opensolaris.org 		ph->phy_lp_100_fdx = B_FALSE;
14169860Sgdamore@opensolaris.org 		ph->phy_lp_1000_hdx = B_FALSE;
14179860Sgdamore@opensolaris.org 		ph->phy_lp_1000_fdx = B_FALSE;
14189860Sgdamore@opensolaris.org 
14199860Sgdamore@opensolaris.org 		/*
14209860Sgdamore@opensolaris.org 		 * We have no idea what our link partner might or might
14219860Sgdamore@opensolaris.org 		 * not be able to support, except that it appears to
14229860Sgdamore@opensolaris.org 		 * support the same mode that we have forced.
14239860Sgdamore@opensolaris.org 		 */
14249860Sgdamore@opensolaris.org 		if (control & MII_CONTROL_1GB) {
14259860Sgdamore@opensolaris.org 			ph->phy_speed = 1000;
14269860Sgdamore@opensolaris.org 		} else if (control & MII_CONTROL_100MB) {
14279860Sgdamore@opensolaris.org 			ph->phy_speed = 100;
14289860Sgdamore@opensolaris.org 		} else {
14299860Sgdamore@opensolaris.org 			ph->phy_speed = 10;
14309860Sgdamore@opensolaris.org 		}
14319860Sgdamore@opensolaris.org 		ph->phy_duplex = control & MII_CONTROL_FDUPLEX ?
14329860Sgdamore@opensolaris.org 		    LINK_DUPLEX_FULL : LINK_DUPLEX_HALF;
14339860Sgdamore@opensolaris.org 
14349860Sgdamore@opensolaris.org 		return (DDI_SUCCESS);
14359860Sgdamore@opensolaris.org 	}
14369860Sgdamore@opensolaris.org 
14379860Sgdamore@opensolaris.org 	if (ph->phy_type == XCVR_1000X) {
14389860Sgdamore@opensolaris.org 
14399860Sgdamore@opensolaris.org 		ph->phy_lp_10_hdx = B_FALSE;
14409860Sgdamore@opensolaris.org 		ph->phy_lp_10_fdx = B_FALSE;
14419860Sgdamore@opensolaris.org 		ph->phy_lp_100_t4 = B_FALSE;
14429860Sgdamore@opensolaris.org 		ph->phy_lp_100_hdx = B_FALSE;
14439860Sgdamore@opensolaris.org 		ph->phy_lp_100_fdx = B_FALSE;
14449860Sgdamore@opensolaris.org 
14459860Sgdamore@opensolaris.org 		/* 1000BASE-X requires autonegotiation */
14469860Sgdamore@opensolaris.org 		ph->phy_lp_aneg = B_TRUE;
14479860Sgdamore@opensolaris.org 		ph->phy_lp_1000_fdx = !!(lpar & MII_ABILITY_X_FD);
14489860Sgdamore@opensolaris.org 		ph->phy_lp_1000_hdx = !!(lpar & MII_ABILITY_X_HD);
14499860Sgdamore@opensolaris.org 		ph->phy_lp_pause = !!(lpar & MII_ABILITY_X_PAUSE);
14509860Sgdamore@opensolaris.org 		ph->phy_lp_asmpause = !!(lpar & MII_ABILITY_X_ASMPAUSE);
14519860Sgdamore@opensolaris.org 
14529860Sgdamore@opensolaris.org 	} else if (ph->phy_type == XCVR_100T2) {
14539860Sgdamore@opensolaris.org 		ph->phy_lp_10_hdx = B_FALSE;
14549860Sgdamore@opensolaris.org 		ph->phy_lp_10_fdx = B_FALSE;
14559860Sgdamore@opensolaris.org 		ph->phy_lp_100_t4 = B_FALSE;
14569860Sgdamore@opensolaris.org 		ph->phy_lp_1000_hdx = B_FALSE;
14579860Sgdamore@opensolaris.org 		ph->phy_lp_1000_fdx = B_FALSE;
14589860Sgdamore@opensolaris.org 		ph->phy_lp_pause = B_FALSE;
14599860Sgdamore@opensolaris.org 		ph->phy_lp_asmpause = B_FALSE;
14609860Sgdamore@opensolaris.org 
14619860Sgdamore@opensolaris.org 		/* 100BASE-T2 requires autonegotiation */
14629860Sgdamore@opensolaris.org 		ph->phy_lp_aneg = B_TRUE;
14639860Sgdamore@opensolaris.org 		ph->phy_lp_100_fdx = !!(lpar & MII_ABILITY_T2_FD);
14649860Sgdamore@opensolaris.org 		ph->phy_lp_100_hdx = !!(lpar & MII_ABILITY_T2_HD);
14659860Sgdamore@opensolaris.org 
14669860Sgdamore@opensolaris.org 	} else if (anexp & MII_AN_EXP_PARFAULT) {
14679860Sgdamore@opensolaris.org 		/*
14689860Sgdamore@opensolaris.org 		 * Parallel detection fault!  This happens when the
14699860Sgdamore@opensolaris.org 		 * peer does not use autonegotiation, and the
14709860Sgdamore@opensolaris.org 		 * detection logic reports more than one type of legal
14719860Sgdamore@opensolaris.org 		 * link is available.  Note that parallel detection
14729860Sgdamore@opensolaris.org 		 * can only happen with half duplex 10, 100, and
14739860Sgdamore@opensolaris.org 		 * 100TX4.  We also should not have got here, because
14749860Sgdamore@opensolaris.org 		 * the link state bit should have failed.
14759860Sgdamore@opensolaris.org 		 */
14769860Sgdamore@opensolaris.org #ifdef	DEBUG
14779860Sgdamore@opensolaris.org 		phy_warn(ph, "Parallel detection fault!");
14789860Sgdamore@opensolaris.org #endif
14799860Sgdamore@opensolaris.org 		ph->phy_lp_10_hdx = B_FALSE;
14809860Sgdamore@opensolaris.org 		ph->phy_lp_10_fdx = B_FALSE;
14819860Sgdamore@opensolaris.org 		ph->phy_lp_100_t4 = B_FALSE;
14829860Sgdamore@opensolaris.org 		ph->phy_lp_100_hdx = B_FALSE;
14839860Sgdamore@opensolaris.org 		ph->phy_lp_100_fdx = B_FALSE;
14849860Sgdamore@opensolaris.org 		ph->phy_lp_1000_hdx = B_FALSE;
14859860Sgdamore@opensolaris.org 		ph->phy_lp_1000_fdx = B_FALSE;
14869860Sgdamore@opensolaris.org 		ph->phy_lp_pause = B_FALSE;
14879860Sgdamore@opensolaris.org 		ph->phy_lp_asmpause = B_FALSE;
14889860Sgdamore@opensolaris.org 		ph->phy_speed = 0;
14899860Sgdamore@opensolaris.org 		ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
14909860Sgdamore@opensolaris.org 		return (DDI_SUCCESS);
14919860Sgdamore@opensolaris.org 
14929860Sgdamore@opensolaris.org 	} else {
14939860Sgdamore@opensolaris.org 		ph->phy_lp_aneg = !!(anexp & MII_AN_EXP_LPCANAN);
14949860Sgdamore@opensolaris.org 
14959860Sgdamore@opensolaris.org 		/*
14969860Sgdamore@opensolaris.org 		 * Note: If the peer doesn't support autonegotiation, then
14979860Sgdamore@opensolaris.org 		 * according to clause 28.5.4.5, the link partner ability
14989860Sgdamore@opensolaris.org 		 * register will still have the right bits set.  However,
14999860Sgdamore@opensolaris.org 		 * gigabit modes cannot use legacy parallel detection.
15009860Sgdamore@opensolaris.org 		 */
15019860Sgdamore@opensolaris.org 
15029860Sgdamore@opensolaris.org 		if ((ph->phy_type == XCVR_1000T) &
15039860Sgdamore@opensolaris.org 		    (anexp & MII_AN_EXP_LPCANAN)) {
15049860Sgdamore@opensolaris.org 
15059860Sgdamore@opensolaris.org 			/* check for gige */
15069860Sgdamore@opensolaris.org 			msstat = phy_read(ph, MII_MSSTATUS);
15079860Sgdamore@opensolaris.org 
15089860Sgdamore@opensolaris.org 			ph->phy_lp_1000_hdx =
15099860Sgdamore@opensolaris.org 			    !!(msstat & MII_MSSTATUS_LP1000T);
15109860Sgdamore@opensolaris.org 
15119860Sgdamore@opensolaris.org 			ph->phy_lp_1000_fdx =
15129860Sgdamore@opensolaris.org 			    !!(msstat & MII_MSSTATUS_LP1000T_FD);
15139860Sgdamore@opensolaris.org 		}
15149860Sgdamore@opensolaris.org 
15159860Sgdamore@opensolaris.org 		ph->phy_lp_100_fdx = !!(lpar & MII_ABILITY_100BASE_TX_FD);
15169860Sgdamore@opensolaris.org 		ph->phy_lp_100_hdx = !!(lpar & MII_ABILITY_100BASE_TX);
15179860Sgdamore@opensolaris.org 		ph->phy_lp_100_t4 = !!(lpar & MII_ABILITY_100BASE_T4);
15189860Sgdamore@opensolaris.org 		ph->phy_lp_10_fdx = !!(lpar & MII_ABILITY_10BASE_T_FD);
15199860Sgdamore@opensolaris.org 		ph->phy_lp_10_hdx = !!(lpar & MII_ABILITY_10BASE_T);
15209860Sgdamore@opensolaris.org 		ph->phy_lp_pause = !!(lpar & MII_ABILITY_PAUSE);
15219860Sgdamore@opensolaris.org 		ph->phy_lp_asmpause = !!(lpar & MII_ABILITY_ASMPAUSE);
15229860Sgdamore@opensolaris.org 	}
15239860Sgdamore@opensolaris.org 
15249860Sgdamore@opensolaris.org 	/* resolve link pause */
15259860Sgdamore@opensolaris.org 	if ((ph->phy_en_flowctrl == LINK_FLOWCTRL_BI) &&
15269860Sgdamore@opensolaris.org 	    (ph->phy_lp_pause)) {
15279860Sgdamore@opensolaris.org 		ph->phy_flowctrl = LINK_FLOWCTRL_BI;
15289860Sgdamore@opensolaris.org 	} else if ((ph->phy_en_flowctrl == LINK_FLOWCTRL_RX) &&
15299860Sgdamore@opensolaris.org 	    (ph->phy_lp_pause || ph->phy_lp_asmpause)) {
15309860Sgdamore@opensolaris.org 		ph->phy_flowctrl = LINK_FLOWCTRL_RX;
15319860Sgdamore@opensolaris.org 	} else if ((ph->phy_en_flowctrl == LINK_FLOWCTRL_TX) &&
15329860Sgdamore@opensolaris.org 	    (ph->phy_lp_pause)) {
15339860Sgdamore@opensolaris.org 		ph->phy_flowctrl = LINK_FLOWCTRL_TX;
15349860Sgdamore@opensolaris.org 	} else {
15359860Sgdamore@opensolaris.org 		ph->phy_flowctrl = LINK_FLOWCTRL_NONE;
15369860Sgdamore@opensolaris.org 	}
15379860Sgdamore@opensolaris.org 
15389860Sgdamore@opensolaris.org 	if (ph->phy_adv_1000_fdx && ph->phy_lp_1000_fdx) {
15399860Sgdamore@opensolaris.org 		ph->phy_speed = 1000;
15409860Sgdamore@opensolaris.org 		ph->phy_duplex = LINK_DUPLEX_FULL;
15419860Sgdamore@opensolaris.org 
15429860Sgdamore@opensolaris.org 	} else if (ph->phy_adv_1000_hdx && ph->phy_lp_1000_hdx) {
15439860Sgdamore@opensolaris.org 		ph->phy_speed = 1000;
15449860Sgdamore@opensolaris.org 		ph->phy_duplex = LINK_DUPLEX_HALF;
15459860Sgdamore@opensolaris.org 
15469860Sgdamore@opensolaris.org 	} else if (ph->phy_adv_100_fdx && ph->phy_lp_100_fdx) {
15479860Sgdamore@opensolaris.org 		ph->phy_speed = 100;
15489860Sgdamore@opensolaris.org 		ph->phy_duplex = LINK_DUPLEX_FULL;
15499860Sgdamore@opensolaris.org 
15509860Sgdamore@opensolaris.org 	} else if (ph->phy_adv_100_t4 && ph->phy_lp_100_t4) {
15519860Sgdamore@opensolaris.org 		ph->phy_speed = 100;
15529860Sgdamore@opensolaris.org 		ph->phy_duplex = LINK_DUPLEX_HALF;
15539860Sgdamore@opensolaris.org 
15549860Sgdamore@opensolaris.org 	} else if (ph->phy_adv_100_hdx && ph->phy_lp_100_hdx) {
15559860Sgdamore@opensolaris.org 		ph->phy_speed = 100;
15569860Sgdamore@opensolaris.org 		ph->phy_duplex = LINK_DUPLEX_HALF;
15579860Sgdamore@opensolaris.org 
15589860Sgdamore@opensolaris.org 	} else if (ph->phy_adv_10_fdx && ph->phy_lp_10_fdx) {
15599860Sgdamore@opensolaris.org 		ph->phy_speed = 10;
15609860Sgdamore@opensolaris.org 		ph->phy_duplex = LINK_DUPLEX_FULL;
15619860Sgdamore@opensolaris.org 
15629860Sgdamore@opensolaris.org 	} else if (ph->phy_adv_10_hdx && ph->phy_lp_10_hdx) {
15639860Sgdamore@opensolaris.org 		ph->phy_speed = 10;
15649860Sgdamore@opensolaris.org 		ph->phy_duplex = LINK_DUPLEX_HALF;
15659860Sgdamore@opensolaris.org 
15669860Sgdamore@opensolaris.org 	} else {
15679860Sgdamore@opensolaris.org #ifdef	DEBUG
15689860Sgdamore@opensolaris.org 		phy_warn(ph, "No common abilities.");
15699860Sgdamore@opensolaris.org #endif
15709860Sgdamore@opensolaris.org 		ph->phy_speed = 0;
15719860Sgdamore@opensolaris.org 		ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
15729860Sgdamore@opensolaris.org 	}
15739860Sgdamore@opensolaris.org 
15749860Sgdamore@opensolaris.org 	return (DDI_SUCCESS);
15759860Sgdamore@opensolaris.org }
15769860Sgdamore@opensolaris.org 
15779860Sgdamore@opensolaris.org int
15789860Sgdamore@opensolaris.org phy_get_prop(phy_handle_t *ph, char *prop, int dflt)
15799860Sgdamore@opensolaris.org {
15809860Sgdamore@opensolaris.org 	mii_handle_t	mh = ph->phy_mii;
15819860Sgdamore@opensolaris.org 
15829860Sgdamore@opensolaris.org 	return (ddi_prop_get_int(DDI_DEV_T_ANY, mh->m_dip, 0, prop, dflt));
15839860Sgdamore@opensolaris.org }
15849860Sgdamore@opensolaris.org 
15859860Sgdamore@opensolaris.org const char *
15869860Sgdamore@opensolaris.org phy_get_name(phy_handle_t *ph)
15879860Sgdamore@opensolaris.org {
15889860Sgdamore@opensolaris.org 	mii_handle_t	mh = ph->phy_mii;
15899860Sgdamore@opensolaris.org 
15909860Sgdamore@opensolaris.org 	return (mh->m_name);
15919860Sgdamore@opensolaris.org }
15929860Sgdamore@opensolaris.org 
15939860Sgdamore@opensolaris.org const char *
15949860Sgdamore@opensolaris.org phy_get_driver(phy_handle_t *ph)
15959860Sgdamore@opensolaris.org {
15969860Sgdamore@opensolaris.org 	mii_handle_t	mh = ph->phy_mii;
15979860Sgdamore@opensolaris.org 
15989860Sgdamore@opensolaris.org 	return (ddi_driver_name(mh->m_dip));
15999860Sgdamore@opensolaris.org }
16009860Sgdamore@opensolaris.org 
16019860Sgdamore@opensolaris.org void
16029860Sgdamore@opensolaris.org phy_warn(phy_handle_t *ph, const char *fmt, ...)
16039860Sgdamore@opensolaris.org {
16049860Sgdamore@opensolaris.org 	va_list	va;
16059860Sgdamore@opensolaris.org 	char buf[256];
16069860Sgdamore@opensolaris.org 
16079860Sgdamore@opensolaris.org 	(void) snprintf(buf, sizeof (buf), "%s: %s", phy_get_name(ph), fmt);
16089860Sgdamore@opensolaris.org 
16099860Sgdamore@opensolaris.org 	va_start(va, fmt);
16109860Sgdamore@opensolaris.org 	vcmn_err(CE_WARN, buf, va);
16119860Sgdamore@opensolaris.org 	va_end(va);
16129860Sgdamore@opensolaris.org }
16139860Sgdamore@opensolaris.org 
16149860Sgdamore@opensolaris.org /*
16159860Sgdamore@opensolaris.org  * Internal support routines.
16169860Sgdamore@opensolaris.org  */
16179860Sgdamore@opensolaris.org 
16189860Sgdamore@opensolaris.org void
161910359SGarrett.Damore@Sun.COM _mii_notify(mii_handle_t mh)
162010359SGarrett.Damore@Sun.COM {
162110359SGarrett.Damore@Sun.COM 	if (mh->m_ops.mii_notify != NULL) {
162210359SGarrett.Damore@Sun.COM 		mh->m_ops.mii_notify(mh->m_private, mh->m_link);
162310359SGarrett.Damore@Sun.COM 	}
162410359SGarrett.Damore@Sun.COM }
162510359SGarrett.Damore@Sun.COM 
162610359SGarrett.Damore@Sun.COM void
16279860Sgdamore@opensolaris.org _mii_probe_phy(phy_handle_t *ph)
16289860Sgdamore@opensolaris.org {
16299860Sgdamore@opensolaris.org 	uint16_t	bmsr;
16309860Sgdamore@opensolaris.org 	uint16_t	extsr;
16319860Sgdamore@opensolaris.org 	mii_handle_t	mh = ph->phy_mii;
16329860Sgdamore@opensolaris.org 
16339860Sgdamore@opensolaris.org 
16349860Sgdamore@opensolaris.org 	/*
16359860Sgdamore@opensolaris.org 	 * Apparently, PHY 0 is less likely to be physically
16369860Sgdamore@opensolaris.org 	 * connected, and should always be the last one tried.  Most
16379860Sgdamore@opensolaris.org 	 * single solution NICs use PHY1 for their built-in
16389860Sgdamore@opensolaris.org 	 * transceiver.  NICs with an external MII will often place
16399860Sgdamore@opensolaris.org 	 * the external PHY at address 1, and use address 0 for the
16409860Sgdamore@opensolaris.org 	 * internal PHY.
16419860Sgdamore@opensolaris.org 	 */
16429860Sgdamore@opensolaris.org 
16439860Sgdamore@opensolaris.org 	ph->phy_id = 0;
16449860Sgdamore@opensolaris.org 	ph->phy_model = "PHY";
16459860Sgdamore@opensolaris.org 	ph->phy_vendor = "Unknown Vendor";
16469860Sgdamore@opensolaris.org 
16479860Sgdamore@opensolaris.org 	/* done twice to clear any latched bits */
16489860Sgdamore@opensolaris.org 	bmsr = phy_read(ph, MII_STATUS);
16499860Sgdamore@opensolaris.org 	bmsr = phy_read(ph, MII_STATUS);
16509860Sgdamore@opensolaris.org 	if ((bmsr == 0) || (bmsr == 0xffff)) {
16519860Sgdamore@opensolaris.org 		ph->phy_present = B_FALSE;
16529860Sgdamore@opensolaris.org 		return;
16539860Sgdamore@opensolaris.org 	}
16549860Sgdamore@opensolaris.org 
16559860Sgdamore@opensolaris.org 	if (bmsr & MII_STATUS_EXTSTAT) {
16569860Sgdamore@opensolaris.org 		extsr = phy_read(ph, MII_EXTSTATUS);
16579860Sgdamore@opensolaris.org 	} else {
16589860Sgdamore@opensolaris.org 		extsr = 0;
16599860Sgdamore@opensolaris.org 	}
16609860Sgdamore@opensolaris.org 
16619860Sgdamore@opensolaris.org 	ph->phy_present = B_TRUE;
16629860Sgdamore@opensolaris.org 	ph->phy_id = ((uint32_t)phy_read(ph, MII_PHYIDH) << 16) |
16639860Sgdamore@opensolaris.org 	    phy_read(ph, MII_PHYIDL);
16649860Sgdamore@opensolaris.org 
16659860Sgdamore@opensolaris.org 	/* setup default handlers */
16669860Sgdamore@opensolaris.org 	ph->phy_reset = phy_reset;
16679860Sgdamore@opensolaris.org 	ph->phy_start = phy_start;
16689860Sgdamore@opensolaris.org 	ph->phy_stop = phy_stop;
16699860Sgdamore@opensolaris.org 	ph->phy_check = phy_check;
167010359SGarrett.Damore@Sun.COM 	ph->phy_loop = phy_loop;
16719860Sgdamore@opensolaris.org 
16729860Sgdamore@opensolaris.org 	/*
16739860Sgdamore@opensolaris.org 	 * We ignore the non-existent 100baseT2 stuff -- no
16749860Sgdamore@opensolaris.org 	 * known products for it exist.
16759860Sgdamore@opensolaris.org 	 */
16769860Sgdamore@opensolaris.org 	ph->phy_cap_aneg =	!!(bmsr & MII_STATUS_CANAUTONEG);
16779860Sgdamore@opensolaris.org 	ph->phy_cap_100_t4 =	!!(bmsr & MII_STATUS_100_BASE_T4);
16789860Sgdamore@opensolaris.org 	ph->phy_cap_100_fdx =	!!(bmsr & MII_STATUS_100_BASEX_FD);
16799860Sgdamore@opensolaris.org 	ph->phy_cap_100_hdx =	!!(bmsr & MII_STATUS_100_BASEX);
16809860Sgdamore@opensolaris.org 	ph->phy_cap_10_fdx =	!!(bmsr & MII_STATUS_10_FD);
16819860Sgdamore@opensolaris.org 	ph->phy_cap_10_hdx =	!!(bmsr & MII_STATUS_10);
16829860Sgdamore@opensolaris.org 	ph->phy_cap_1000_fdx =
16839860Sgdamore@opensolaris.org 	    !!(extsr & (MII_EXTSTATUS_1000X_FD|MII_EXTSTATUS_1000T_FD));
16849860Sgdamore@opensolaris.org 	ph->phy_cap_1000_hdx =
16859860Sgdamore@opensolaris.org 	    !!(extsr & (MII_EXTSTATUS_1000X | MII_EXTSTATUS_1000T));
16869860Sgdamore@opensolaris.org 	ph->phy_cap_pause =	mh->m_cap_pause;
16879860Sgdamore@opensolaris.org 	ph->phy_cap_asmpause =	mh->m_cap_asmpause;
16889860Sgdamore@opensolaris.org 
16899860Sgdamore@opensolaris.org 	if (bmsr & MII_STATUS_10) {
16909860Sgdamore@opensolaris.org 		ph->phy_cap_10_hdx = B_TRUE;
16919860Sgdamore@opensolaris.org 		ph->phy_type = XCVR_10;
16929860Sgdamore@opensolaris.org 	}
16939860Sgdamore@opensolaris.org 	if (bmsr & MII_STATUS_10_FD) {
16949860Sgdamore@opensolaris.org 		ph->phy_cap_10_fdx = B_TRUE;
16959860Sgdamore@opensolaris.org 		ph->phy_type = XCVR_10;
16969860Sgdamore@opensolaris.org 	}
16979860Sgdamore@opensolaris.org 	if (bmsr & MII_STATUS_100T2) {
16989860Sgdamore@opensolaris.org 		ph->phy_cap_100_hdx = B_TRUE;
16999860Sgdamore@opensolaris.org 		ph->phy_type = XCVR_100T2;
17009860Sgdamore@opensolaris.org 	}
17019860Sgdamore@opensolaris.org 	if (bmsr & MII_STATUS_100T2_FD) {
17029860Sgdamore@opensolaris.org 		ph->phy_cap_100_fdx = B_TRUE;
17039860Sgdamore@opensolaris.org 		ph->phy_type = XCVR_100T2;
17049860Sgdamore@opensolaris.org 	}
17059860Sgdamore@opensolaris.org 	if (bmsr & MII_STATUS_100_BASE_T4) {
17069860Sgdamore@opensolaris.org 		ph->phy_cap_100_hdx = B_TRUE;
17079860Sgdamore@opensolaris.org 		ph->phy_type = XCVR_100T4;
17089860Sgdamore@opensolaris.org 	}
17099860Sgdamore@opensolaris.org 	if (bmsr & MII_STATUS_100_BASEX) {
17109860Sgdamore@opensolaris.org 		ph->phy_cap_100_hdx = B_TRUE;
17119860Sgdamore@opensolaris.org 		ph->phy_type = XCVR_100X;
17129860Sgdamore@opensolaris.org 	}
17139860Sgdamore@opensolaris.org 	if (bmsr & MII_STATUS_100_BASEX_FD) {
17149860Sgdamore@opensolaris.org 		ph->phy_cap_100_fdx = B_TRUE;
17159860Sgdamore@opensolaris.org 		ph->phy_type = XCVR_100X;
17169860Sgdamore@opensolaris.org 	}
17179860Sgdamore@opensolaris.org 	if (extsr & MII_EXTSTATUS_1000X) {
17189860Sgdamore@opensolaris.org 		ph->phy_cap_1000_hdx = B_TRUE;
17199860Sgdamore@opensolaris.org 		ph->phy_type = XCVR_1000X;
17209860Sgdamore@opensolaris.org 	}
17219860Sgdamore@opensolaris.org 	if (extsr & MII_EXTSTATUS_1000X_FD) {
17229860Sgdamore@opensolaris.org 		ph->phy_cap_1000_fdx = B_TRUE;
17239860Sgdamore@opensolaris.org 		ph->phy_type = XCVR_1000X;
17249860Sgdamore@opensolaris.org 	}
17259860Sgdamore@opensolaris.org 	if (extsr & MII_EXTSTATUS_1000T) {
17269860Sgdamore@opensolaris.org 		ph->phy_cap_1000_hdx = B_TRUE;
17279860Sgdamore@opensolaris.org 		ph->phy_type = XCVR_1000T;
17289860Sgdamore@opensolaris.org 	}
17299860Sgdamore@opensolaris.org 	if (extsr & MII_EXTSTATUS_1000T_FD) {
17309860Sgdamore@opensolaris.org 		ph->phy_cap_1000_fdx = B_TRUE;
17319860Sgdamore@opensolaris.org 		ph->phy_type = XCVR_1000T;
17329860Sgdamore@opensolaris.org 	}
17339860Sgdamore@opensolaris.org 
17349860Sgdamore@opensolaris.org 	for (int j = 0; _phy_probes[j] != NULL; j++) {
17359860Sgdamore@opensolaris.org 		if ((*_phy_probes[j])(ph)) {
17369860Sgdamore@opensolaris.org 			break;
17379860Sgdamore@opensolaris.org 		}
17389860Sgdamore@opensolaris.org 	}
17399860Sgdamore@opensolaris.org 
17409860Sgdamore@opensolaris.org #define	INIT_ENABLE(CAP)	\
17419860Sgdamore@opensolaris.org 	ph->phy_en_##CAP = (mh->m_en_##CAP > 0) ? \
17429860Sgdamore@opensolaris.org 	    mh->m_en_##CAP : ph->phy_cap_##CAP
17439860Sgdamore@opensolaris.org 
17449860Sgdamore@opensolaris.org 	INIT_ENABLE(aneg);
17459860Sgdamore@opensolaris.org 	INIT_ENABLE(1000_fdx);
17469860Sgdamore@opensolaris.org 	INIT_ENABLE(1000_hdx);
17479860Sgdamore@opensolaris.org 	INIT_ENABLE(100_fdx);
17489860Sgdamore@opensolaris.org 	INIT_ENABLE(100_t4);
17499860Sgdamore@opensolaris.org 	INIT_ENABLE(100_hdx);
17509860Sgdamore@opensolaris.org 	INIT_ENABLE(10_fdx);
17519860Sgdamore@opensolaris.org 	INIT_ENABLE(10_hdx);
17529860Sgdamore@opensolaris.org 
17539860Sgdamore@opensolaris.org #undef	INIT_ENABLE
17549860Sgdamore@opensolaris.org 	ph->phy_en_flowctrl = mh->m_en_flowctrl;
17559860Sgdamore@opensolaris.org 	switch (ph->phy_en_flowctrl) {
17569860Sgdamore@opensolaris.org 	case LINK_FLOWCTRL_BI:
17579860Sgdamore@opensolaris.org 	case LINK_FLOWCTRL_RX:
17589860Sgdamore@opensolaris.org 		ph->phy_en_pause = B_TRUE;
17599860Sgdamore@opensolaris.org 		ph->phy_en_asmpause = B_TRUE;
17609860Sgdamore@opensolaris.org 		break;
17619860Sgdamore@opensolaris.org 	case LINK_FLOWCTRL_TX:
17629860Sgdamore@opensolaris.org 		ph->phy_en_pause = B_FALSE;
17639860Sgdamore@opensolaris.org 		ph->phy_en_asmpause = B_TRUE;
17649860Sgdamore@opensolaris.org 		break;
17659860Sgdamore@opensolaris.org 	default:
17669860Sgdamore@opensolaris.org 		ph->phy_en_pause = B_FALSE;
17679860Sgdamore@opensolaris.org 		ph->phy_en_asmpause = B_FALSE;
17689860Sgdamore@opensolaris.org 		break;
17699860Sgdamore@opensolaris.org 	}
17709860Sgdamore@opensolaris.org }
17719860Sgdamore@opensolaris.org 
17729860Sgdamore@opensolaris.org void
177310359SGarrett.Damore@Sun.COM _mii_probe(mii_handle_t mh)
17749860Sgdamore@opensolaris.org {
17759860Sgdamore@opensolaris.org 	uint8_t		new_addr;
17769860Sgdamore@opensolaris.org 	uint8_t		old_addr;
17779860Sgdamore@opensolaris.org 	uint8_t		user_addr;
17789860Sgdamore@opensolaris.org 	uint8_t		curr_addr;
17799860Sgdamore@opensolaris.org 	phy_handle_t	*ph;
17809860Sgdamore@opensolaris.org 	int		pri = 0;
178111419Sgdamore@opensolaris.org 	int		first;
17829860Sgdamore@opensolaris.org 
17839860Sgdamore@opensolaris.org 	user_addr = ddi_prop_get_int(DDI_DEV_T_ANY, mh->m_dip, 0,
17849860Sgdamore@opensolaris.org 	    "phy-addr", -1);
17859860Sgdamore@opensolaris.org 	old_addr = mh->m_addr;
17869860Sgdamore@opensolaris.org 	new_addr = 0xff;
17879860Sgdamore@opensolaris.org 
17889860Sgdamore@opensolaris.org 	/*
17899860Sgdamore@opensolaris.org 	 * Apparently, PHY 0 is less likely to be physically
17909860Sgdamore@opensolaris.org 	 * connected, and should always be the last one tried.  Most
17919860Sgdamore@opensolaris.org 	 * single solution NICs use PHY1 for their built-in
17929860Sgdamore@opensolaris.org 	 * transceiver.  NICs with an external MII will often place
17939860Sgdamore@opensolaris.org 	 * the external PHY at address 1, and use address 0 for the
17949860Sgdamore@opensolaris.org 	 * internal PHY.
179511419Sgdamore@opensolaris.org 	 *
179611419Sgdamore@opensolaris.org 	 * Some devices have a different preference however.  They can
179711419Sgdamore@opensolaris.org 	 * override the default starting point of the search by
179811419Sgdamore@opensolaris.org 	 * exporting a "first-phy" property.
17999860Sgdamore@opensolaris.org 	 */
18009860Sgdamore@opensolaris.org 
180111419Sgdamore@opensolaris.org 	first = ddi_prop_get_int(DDI_DEV_T_ANY, mh->m_dip, 0, "first-phy", 1);
180211419Sgdamore@opensolaris.org 	if ((first < 0) || (first > 31)) {
180311419Sgdamore@opensolaris.org 		first = 1;
180411419Sgdamore@opensolaris.org 	}
180511419Sgdamore@opensolaris.org 	for (int i = first; i < (first + 32); i++) {
18069860Sgdamore@opensolaris.org 
18079860Sgdamore@opensolaris.org 		/*
180811419Sgdamore@opensolaris.org 		 * This is tricky: it lets us start searching at an
180911419Sgdamore@opensolaris.org 		 * arbitrary address instead of 0, dealing with the
181011419Sgdamore@opensolaris.org 		 * wrap-around at address 31 properly.
18119860Sgdamore@opensolaris.org 		 */
18129860Sgdamore@opensolaris.org 		curr_addr = i % 32;
18139860Sgdamore@opensolaris.org 
18149860Sgdamore@opensolaris.org 		ph = &mh->m_phys[curr_addr];
18159860Sgdamore@opensolaris.org 
18169860Sgdamore@opensolaris.org 		bzero(ph, sizeof (*ph));
18179860Sgdamore@opensolaris.org 		ph->phy_addr = curr_addr;
18189860Sgdamore@opensolaris.org 		ph->phy_mii = mh;
18199860Sgdamore@opensolaris.org 
18209860Sgdamore@opensolaris.org 		_mii_probe_phy(ph);
18219860Sgdamore@opensolaris.org 
18229860Sgdamore@opensolaris.org 		if (!ph->phy_present)
18239860Sgdamore@opensolaris.org 			continue;
18249860Sgdamore@opensolaris.org 
18259860Sgdamore@opensolaris.org 		if (curr_addr == user_addr) {
18269860Sgdamore@opensolaris.org 			/*
18279860Sgdamore@opensolaris.org 			 * We always try to honor the user configured phy.
18289860Sgdamore@opensolaris.org 			 */
18299860Sgdamore@opensolaris.org 			new_addr = curr_addr;
18309860Sgdamore@opensolaris.org 			pri = 4;
18319860Sgdamore@opensolaris.org 
18329860Sgdamore@opensolaris.org 		}
18339860Sgdamore@opensolaris.org 
18349860Sgdamore@opensolaris.org 		/* two reads to clear latched bits */
18359860Sgdamore@opensolaris.org 		if ((phy_read(ph, MII_STATUS) & MII_STATUS_LINKUP) &&
18369860Sgdamore@opensolaris.org 		    (phy_read(ph, MII_STATUS) & MII_STATUS_LINKUP) &&
18379860Sgdamore@opensolaris.org 		    (pri < 3)) {
18389860Sgdamore@opensolaris.org 			/*
18399860Sgdamore@opensolaris.org 			 * Link present is good.  We prefer this over
18409860Sgdamore@opensolaris.org 			 * a possibly disconnected link.
18419860Sgdamore@opensolaris.org 			 */
18429860Sgdamore@opensolaris.org 			new_addr = curr_addr;
18439860Sgdamore@opensolaris.org 			pri = 3;
18449860Sgdamore@opensolaris.org 		}
18459860Sgdamore@opensolaris.org 		if ((curr_addr == old_addr) && (pri < 2)) {
18469860Sgdamore@opensolaris.org 			/*
18479860Sgdamore@opensolaris.org 			 * All else being equal, minimize change.
18489860Sgdamore@opensolaris.org 			 */
18499860Sgdamore@opensolaris.org 			new_addr = curr_addr;
18509860Sgdamore@opensolaris.org 			pri = 2;
18519860Sgdamore@opensolaris.org 
18529860Sgdamore@opensolaris.org 		}
18539860Sgdamore@opensolaris.org 		if (pri < 1) {
18549860Sgdamore@opensolaris.org 			/*
18559860Sgdamore@opensolaris.org 			 * But make sure we at least select a present PHY.
18569860Sgdamore@opensolaris.org 			 */
18579860Sgdamore@opensolaris.org 			new_addr = curr_addr;
18589860Sgdamore@opensolaris.org 			pri = 1;
18599860Sgdamore@opensolaris.org 		}
18609860Sgdamore@opensolaris.org 	}
18619860Sgdamore@opensolaris.org 
18629860Sgdamore@opensolaris.org 	if (new_addr == 0xff) {
18639860Sgdamore@opensolaris.org 		mh->m_addr = -1;
18649860Sgdamore@opensolaris.org 		mh->m_phy = &mh->m_bogus_phy;
18659860Sgdamore@opensolaris.org 		_mii_error(mh, MII_ENOPHY);
18669860Sgdamore@opensolaris.org 	} else {
18679860Sgdamore@opensolaris.org 		mh->m_addr = new_addr;
18689860Sgdamore@opensolaris.org 		mh->m_phy = &mh->m_phys[new_addr];
18699860Sgdamore@opensolaris.org 		mh->m_tstate = MII_STATE_RESET;
187010359SGarrett.Damore@Sun.COM 		if (new_addr != old_addr) {
187110359SGarrett.Damore@Sun.COM 			cmn_err(CE_CONT,
187210359SGarrett.Damore@Sun.COM 			    "?%s: Using %s Ethernet PHY at %d: %s %s\n",
187310359SGarrett.Damore@Sun.COM 			    mh->m_name, mii_xcvr_types[mh->m_phy->phy_type],
187410359SGarrett.Damore@Sun.COM 			    mh->m_addr, mh->m_phy->phy_vendor,
187510359SGarrett.Damore@Sun.COM 			    mh->m_phy->phy_model);
187610479SGarrett.Damore@Sun.COM 			mh->m_link = LINK_STATE_UNKNOWN;
187710359SGarrett.Damore@Sun.COM 		}
18789860Sgdamore@opensolaris.org 	}
18799860Sgdamore@opensolaris.org }
18809860Sgdamore@opensolaris.org 
188110359SGarrett.Damore@Sun.COM int
188210359SGarrett.Damore@Sun.COM _mii_reset(mii_handle_t mh)
18839860Sgdamore@opensolaris.org {
18849860Sgdamore@opensolaris.org 	phy_handle_t	*ph;
188510359SGarrett.Damore@Sun.COM 	boolean_t	notify;
18869860Sgdamore@opensolaris.org 
18879860Sgdamore@opensolaris.org 	ASSERT(mutex_owned(&mh->m_lock));
18889860Sgdamore@opensolaris.org 
18899860Sgdamore@opensolaris.org 	/*
18909860Sgdamore@opensolaris.org 	 * Reset logic.  We want to isolate all the other
18919860Sgdamore@opensolaris.org 	 * phys that are not in use.
18929860Sgdamore@opensolaris.org 	 */
18939860Sgdamore@opensolaris.org 	for (int i = 0; i < 32; i++) {
18949860Sgdamore@opensolaris.org 		ph = &mh->m_phys[i];
18959860Sgdamore@opensolaris.org 
18969860Sgdamore@opensolaris.org 		if (!ph->phy_present)
18979860Sgdamore@opensolaris.org 			continue;
18989860Sgdamore@opensolaris.org 
189910359SGarrett.Damore@Sun.COM 		/* Don't touch our own phy, yet. */
19009860Sgdamore@opensolaris.org 		if (ph == mh->m_phy)
19019860Sgdamore@opensolaris.org 			continue;
19029860Sgdamore@opensolaris.org 
19039860Sgdamore@opensolaris.org 		ph->phy_stop(ph);
19049860Sgdamore@opensolaris.org 	}
19059860Sgdamore@opensolaris.org 
19069860Sgdamore@opensolaris.org 	ph = mh->m_phy;
19079860Sgdamore@opensolaris.org 
19089860Sgdamore@opensolaris.org 	ASSERT(ph->phy_present);
19099860Sgdamore@opensolaris.org 
191010359SGarrett.Damore@Sun.COM 	/* If we're resetting the PHY, then we want to notify loss of link */
191110479SGarrett.Damore@Sun.COM 	notify = (mh->m_link != LINK_STATE_DOWN);
191210359SGarrett.Damore@Sun.COM 	mh->m_link = LINK_STATE_DOWN;
191310359SGarrett.Damore@Sun.COM 	ph->phy_link = LINK_STATE_DOWN;
191410359SGarrett.Damore@Sun.COM 	ph->phy_speed = 0;
191510359SGarrett.Damore@Sun.COM 	ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
19169860Sgdamore@opensolaris.org 
19179860Sgdamore@opensolaris.org 	if (ph->phy_reset(ph) != DDI_SUCCESS) {
19189860Sgdamore@opensolaris.org 		_mii_error(mh, MII_ERESET);
19199860Sgdamore@opensolaris.org 		return (DDI_FAILURE);
19209860Sgdamore@opensolaris.org 	}
19219860Sgdamore@opensolaris.org 
192210359SGarrett.Damore@Sun.COM 	/* Perform optional mac layer reset. */
192310359SGarrett.Damore@Sun.COM 	if (mh->m_ops.mii_reset != NULL) {
192410359SGarrett.Damore@Sun.COM 		mh->m_ops.mii_reset(mh->m_private);
192510359SGarrett.Damore@Sun.COM 	}
192610359SGarrett.Damore@Sun.COM 
192710359SGarrett.Damore@Sun.COM 	/* Perform optional mac layer notification. */
192810359SGarrett.Damore@Sun.COM 	if (notify) {
192910359SGarrett.Damore@Sun.COM 		_mii_notify(mh);
193010359SGarrett.Damore@Sun.COM 	}
19319860Sgdamore@opensolaris.org 	return (DDI_SUCCESS);
19329860Sgdamore@opensolaris.org }
19339860Sgdamore@opensolaris.org 
193410359SGarrett.Damore@Sun.COM int
193510359SGarrett.Damore@Sun.COM _mii_loopback(mii_handle_t mh)
193610359SGarrett.Damore@Sun.COM {
193710359SGarrett.Damore@Sun.COM 	phy_handle_t	*ph;
193810359SGarrett.Damore@Sun.COM 
193910359SGarrett.Damore@Sun.COM 	ASSERT(mutex_owned(&mh->m_lock));
194010359SGarrett.Damore@Sun.COM 
194110359SGarrett.Damore@Sun.COM 	ph = mh->m_phy;
194210359SGarrett.Damore@Sun.COM 
194310359SGarrett.Damore@Sun.COM 	if (_mii_reset(mh) != DDI_SUCCESS) {
194410359SGarrett.Damore@Sun.COM 		return (DDI_FAILURE);
194510359SGarrett.Damore@Sun.COM 	}
194610359SGarrett.Damore@Sun.COM 	if (ph->phy_loopback == PHY_LB_NONE) {
194710359SGarrett.Damore@Sun.COM 		mh->m_tstate = MII_STATE_START;
194810359SGarrett.Damore@Sun.COM 		return (DDI_SUCCESS);
194910359SGarrett.Damore@Sun.COM 	}
195010359SGarrett.Damore@Sun.COM 	if (ph->phy_loop(ph) != DDI_SUCCESS) {
195110359SGarrett.Damore@Sun.COM 		_mii_error(mh, MII_ELOOP);
195210359SGarrett.Damore@Sun.COM 		return (DDI_FAILURE);
195310359SGarrett.Damore@Sun.COM 	}
195410359SGarrett.Damore@Sun.COM 
195510359SGarrett.Damore@Sun.COM 	/* Just force loopback to link up. */
195610359SGarrett.Damore@Sun.COM 	mh->m_link = ph->phy_link = LINK_STATE_UP;
195710359SGarrett.Damore@Sun.COM 	_mii_notify(mh);
195810359SGarrett.Damore@Sun.COM 
195910359SGarrett.Damore@Sun.COM 	return (DDI_SUCCESS);
196010359SGarrett.Damore@Sun.COM }
196110359SGarrett.Damore@Sun.COM 
196210359SGarrett.Damore@Sun.COM int
196310359SGarrett.Damore@Sun.COM _mii_start(mii_handle_t mh)
19649860Sgdamore@opensolaris.org {
19659860Sgdamore@opensolaris.org 	phy_handle_t		*ph;
19669860Sgdamore@opensolaris.org 
19679860Sgdamore@opensolaris.org 	ph = mh->m_phy;
19689860Sgdamore@opensolaris.org 
19699860Sgdamore@opensolaris.org 	ASSERT(mutex_owned(&mh->m_lock));
19709860Sgdamore@opensolaris.org 	ASSERT(ph->phy_present);
197110359SGarrett.Damore@Sun.COM 	ASSERT(ph->phy_loopback == PHY_LB_NONE);
19729860Sgdamore@opensolaris.org 
197310359SGarrett.Damore@Sun.COM 	if (ph->phy_start(ph) != DDI_SUCCESS) {
19749860Sgdamore@opensolaris.org 		_mii_error(mh, MII_ESTART);
197510359SGarrett.Damore@Sun.COM 		return (DDI_FAILURE);
19769860Sgdamore@opensolaris.org 	}
19779860Sgdamore@opensolaris.org 	/* clear the error state since we got a good startup! */
19789860Sgdamore@opensolaris.org 	mh->m_error = MII_EOK;
197910359SGarrett.Damore@Sun.COM 	return (DDI_SUCCESS);
19809860Sgdamore@opensolaris.org }
19819860Sgdamore@opensolaris.org 
198210359SGarrett.Damore@Sun.COM int
198310359SGarrett.Damore@Sun.COM _mii_check(mii_handle_t mh)
19849860Sgdamore@opensolaris.org {
19859860Sgdamore@opensolaris.org 	link_state_t	olink;
19869860Sgdamore@opensolaris.org 	int		ospeed;
19879860Sgdamore@opensolaris.org 	link_duplex_t	oduplex;
19889860Sgdamore@opensolaris.org 	link_flowctrl_t	ofctrl;
19899860Sgdamore@opensolaris.org 	phy_handle_t	*ph;
19909860Sgdamore@opensolaris.org 
19919860Sgdamore@opensolaris.org 	ph = mh->m_phy;
19929860Sgdamore@opensolaris.org 
19939860Sgdamore@opensolaris.org 	olink = mh->m_link;
19949860Sgdamore@opensolaris.org 	ospeed = ph->phy_speed;
19959860Sgdamore@opensolaris.org 	oduplex = ph->phy_duplex;
19969860Sgdamore@opensolaris.org 	ofctrl = ph->phy_flowctrl;
19979860Sgdamore@opensolaris.org 
19989860Sgdamore@opensolaris.org 	ASSERT(ph->phy_present);
19999860Sgdamore@opensolaris.org 
20009860Sgdamore@opensolaris.org 	if (ph->phy_check(ph) == DDI_FAILURE) {
20019860Sgdamore@opensolaris.org 		_mii_error(mh, MII_ECHECK);
20029860Sgdamore@opensolaris.org 		mh->m_link = LINK_STATE_UNKNOWN;
200310359SGarrett.Damore@Sun.COM 		_mii_notify(mh);
20049860Sgdamore@opensolaris.org 		return (DDI_FAILURE);
20059860Sgdamore@opensolaris.org 	}
20069860Sgdamore@opensolaris.org 
20079860Sgdamore@opensolaris.org 	mh->m_link = ph->phy_link;
20089860Sgdamore@opensolaris.org 
20099860Sgdamore@opensolaris.org 	/* if anything changed, notify! */
20109860Sgdamore@opensolaris.org 	if ((mh->m_link != olink) ||
20119860Sgdamore@opensolaris.org 	    (ph->phy_speed != ospeed) ||
20129860Sgdamore@opensolaris.org 	    (ph->phy_duplex != oduplex) ||
20139860Sgdamore@opensolaris.org 	    (ph->phy_flowctrl != ofctrl)) {
201410359SGarrett.Damore@Sun.COM 		_mii_notify(mh);
20159860Sgdamore@opensolaris.org 	}
20169860Sgdamore@opensolaris.org 
20179860Sgdamore@opensolaris.org 	return (DDI_SUCCESS);
20189860Sgdamore@opensolaris.org }
20199860Sgdamore@opensolaris.org 
202010359SGarrett.Damore@Sun.COM void
20219860Sgdamore@opensolaris.org _mii_task(void *_mh)
20229860Sgdamore@opensolaris.org {
20239860Sgdamore@opensolaris.org 	mii_handle_t	mh = _mh;
20249860Sgdamore@opensolaris.org 	phy_handle_t	*ph;
20259860Sgdamore@opensolaris.org 	clock_t		wait;
20269860Sgdamore@opensolaris.org 	clock_t		downtime;
20279860Sgdamore@opensolaris.org 
20289860Sgdamore@opensolaris.org 	mutex_enter(&mh->m_lock);
20299860Sgdamore@opensolaris.org 
20309860Sgdamore@opensolaris.org 	for (;;) {
20319860Sgdamore@opensolaris.org 
20329860Sgdamore@opensolaris.org 		/* If detaching, exit the thread. */
20339860Sgdamore@opensolaris.org 		if (!mh->m_started) {
20349860Sgdamore@opensolaris.org 			break;
20359860Sgdamore@opensolaris.org 		}
20369860Sgdamore@opensolaris.org 
20379860Sgdamore@opensolaris.org 		ph = mh->m_phy;
20389860Sgdamore@opensolaris.org 
20399860Sgdamore@opensolaris.org 		/*
20409860Sgdamore@opensolaris.org 		 * If we're suspended or otherwise not supposed to be
20419860Sgdamore@opensolaris.org 		 * monitoring the link, just go back to sleep.
20429860Sgdamore@opensolaris.org 		 *
20439860Sgdamore@opensolaris.org 		 * Theoretically we could power down the PHY, but we
20449860Sgdamore@opensolaris.org 		 * don't bother.  (The link might be used for
20459860Sgdamore@opensolaris.org 		 * wake-on-lan!)  Another option would be to reduce
20469860Sgdamore@opensolaris.org 		 * power on the PHY if both it and the link partner
20479860Sgdamore@opensolaris.org 		 * support 10 Mbps mode.
20489860Sgdamore@opensolaris.org 		 */
20499860Sgdamore@opensolaris.org 		if (mh->m_suspending) {
20509860Sgdamore@opensolaris.org 			mh->m_suspended = B_TRUE;
20519860Sgdamore@opensolaris.org 			cv_broadcast(&mh->m_cv);
20529860Sgdamore@opensolaris.org 		}
20539860Sgdamore@opensolaris.org 		if (mh->m_suspended) {
20549860Sgdamore@opensolaris.org 			mh->m_suspending = B_FALSE;
20559860Sgdamore@opensolaris.org 			cv_wait(&mh->m_cv, &mh->m_lock);
20569860Sgdamore@opensolaris.org 			continue;
20579860Sgdamore@opensolaris.org 		}
20589860Sgdamore@opensolaris.org 
20599860Sgdamore@opensolaris.org 		switch (mh->m_tstate) {
20609860Sgdamore@opensolaris.org 		case MII_STATE_PROBE:
206110359SGarrett.Damore@Sun.COM 			_mii_probe(mh);
20629860Sgdamore@opensolaris.org 			ph = mh->m_phy;
20639860Sgdamore@opensolaris.org 			if (!ph->phy_present) {
20649860Sgdamore@opensolaris.org 				/*
20659860Sgdamore@opensolaris.org 				 * If no PHY is found, wait a bit before
20669860Sgdamore@opensolaris.org 				 * trying the probe again.  10 seconds ought
20679860Sgdamore@opensolaris.org 				 * to be enough.
20689860Sgdamore@opensolaris.org 				 */
20699860Sgdamore@opensolaris.org 				wait = 10 * MII_SECOND;
20709860Sgdamore@opensolaris.org 			} else {
20719860Sgdamore@opensolaris.org 				wait = 0;
20729860Sgdamore@opensolaris.org 			}
20739860Sgdamore@opensolaris.org 			break;
20749860Sgdamore@opensolaris.org 
20759860Sgdamore@opensolaris.org 		case MII_STATE_RESET:
207610359SGarrett.Damore@Sun.COM 			if (_mii_reset(mh) == DDI_SUCCESS) {
207710359SGarrett.Damore@Sun.COM 				mh->m_tstate = MII_STATE_START;
20789860Sgdamore@opensolaris.org 				wait = 0;
20799860Sgdamore@opensolaris.org 			} else {
20809860Sgdamore@opensolaris.org 				/*
20819860Sgdamore@opensolaris.org 				 * If an error occurred, wait a bit and
20829860Sgdamore@opensolaris.org 				 * try again later.
20839860Sgdamore@opensolaris.org 				 */
20849860Sgdamore@opensolaris.org 				wait = 10 * MII_SECOND;
20859860Sgdamore@opensolaris.org 			}
20869860Sgdamore@opensolaris.org 			break;
20879860Sgdamore@opensolaris.org 
20889860Sgdamore@opensolaris.org 		case MII_STATE_START:
20899860Sgdamore@opensolaris.org 			/*
20909860Sgdamore@opensolaris.org 			 * If an error occurs, we're going to go back to
20919860Sgdamore@opensolaris.org 			 * probe or reset state.  Otherwise we go to run
20929860Sgdamore@opensolaris.org 			 * state.  In all cases we want to wait 1 second
20939860Sgdamore@opensolaris.org 			 * before doing anything else - either for link to
20949860Sgdamore@opensolaris.org 			 * settle, or to give other code a chance to run
20959860Sgdamore@opensolaris.org 			 * while we reset.
20969860Sgdamore@opensolaris.org 			 */
209710359SGarrett.Damore@Sun.COM 			if (_mii_start(mh) == DDI_SUCCESS) {
209810359SGarrett.Damore@Sun.COM 				/* reset watchdog to latest */
209910359SGarrett.Damore@Sun.COM 				downtime = ddi_get_lbolt();
210010359SGarrett.Damore@Sun.COM 				mh->m_tstate = MII_STATE_RUN;
210110359SGarrett.Damore@Sun.COM 			} else {
210210359SGarrett.Damore@Sun.COM 				mh->m_tstate = MII_STATE_PROBE;
210310359SGarrett.Damore@Sun.COM 			}
210410359SGarrett.Damore@Sun.COM 			wait = 0;
210510359SGarrett.Damore@Sun.COM 			break;
210610359SGarrett.Damore@Sun.COM 
210710359SGarrett.Damore@Sun.COM 		case MII_STATE_LOOPBACK:
210810359SGarrett.Damore@Sun.COM 			/*
210910359SGarrett.Damore@Sun.COM 			 * In loopback mode we don't check anything,
211010359SGarrett.Damore@Sun.COM 			 * and just wait for some condition to change.
211110359SGarrett.Damore@Sun.COM 			 */
211210359SGarrett.Damore@Sun.COM 			wait = (clock_t)-1;
21139860Sgdamore@opensolaris.org 			break;
21149860Sgdamore@opensolaris.org 
21159860Sgdamore@opensolaris.org 		case MII_STATE_RUN:
21169860Sgdamore@opensolaris.org 		default:
211710359SGarrett.Damore@Sun.COM 			if (_mii_check(mh) == DDI_FAILURE) {
21189860Sgdamore@opensolaris.org 				/*
21199860Sgdamore@opensolaris.org 				 * On error (PHY removed?), wait a
21209860Sgdamore@opensolaris.org 				 * short bit before reprobing or
21219860Sgdamore@opensolaris.org 				 * resetting.
21229860Sgdamore@opensolaris.org 				 */
21239860Sgdamore@opensolaris.org 				wait = MII_SECOND;
212410359SGarrett.Damore@Sun.COM 				mh->m_tstate = MII_STATE_PROBE;
21259860Sgdamore@opensolaris.org 
21269860Sgdamore@opensolaris.org 			} else if (mh->m_link == LINK_STATE_UP) {
21279860Sgdamore@opensolaris.org 				/* got goood link, so reset the watchdog */
21289860Sgdamore@opensolaris.org 				downtime = ddi_get_lbolt();
21299860Sgdamore@opensolaris.org 				/* rescan again in a second */
21309860Sgdamore@opensolaris.org 				wait = MII_SECOND;
21319860Sgdamore@opensolaris.org 
21329860Sgdamore@opensolaris.org 			} else if ((ddi_get_lbolt() - downtime) >
21339860Sgdamore@opensolaris.org 			    (drv_usectohz(MII_SECOND * 10))) {
21349860Sgdamore@opensolaris.org 
21359860Sgdamore@opensolaris.org 				/*
21369860Sgdamore@opensolaris.org 				 * If we were down for 10 seconds,
21379860Sgdamore@opensolaris.org 				 * hard reset the PHY.
21389860Sgdamore@opensolaris.org 				 */
21399860Sgdamore@opensolaris.org 				mh->m_tstate = MII_STATE_RESET;
21409860Sgdamore@opensolaris.org 				wait = 0;
21419860Sgdamore@opensolaris.org 
21429860Sgdamore@opensolaris.org 			} else {
21439860Sgdamore@opensolaris.org 				/*
21449860Sgdamore@opensolaris.org 				 * Otherwise, if we are still down,
21459860Sgdamore@opensolaris.org 				 * rescan the link much more
21469860Sgdamore@opensolaris.org 				 * frequently.  We might be trying to
21479860Sgdamore@opensolaris.org 				 * autonegotiate.
21489860Sgdamore@opensolaris.org 				 */
21499860Sgdamore@opensolaris.org 				wait = MII_SECOND / 4;
21509860Sgdamore@opensolaris.org 			}
21519860Sgdamore@opensolaris.org 			break;
21529860Sgdamore@opensolaris.org 		}
21539860Sgdamore@opensolaris.org 
215410359SGarrett.Damore@Sun.COM 		switch (wait) {
215510359SGarrett.Damore@Sun.COM 		case 0:
215610359SGarrett.Damore@Sun.COM 			break;
21579860Sgdamore@opensolaris.org 
215810359SGarrett.Damore@Sun.COM 		case (clock_t)-1:
215910359SGarrett.Damore@Sun.COM 			cv_wait(&mh->m_cv, &mh->m_lock);
216010359SGarrett.Damore@Sun.COM 			break;
21619860Sgdamore@opensolaris.org 
216210359SGarrett.Damore@Sun.COM 		default:
216311066Srafael.vanoni@sun.com 			(void) cv_reltimedwait(&mh->m_cv, &mh->m_lock,
216411066Srafael.vanoni@sun.com 			    drv_usectohz(wait), TR_CLOCK_TICK);
216510359SGarrett.Damore@Sun.COM 		}
21669860Sgdamore@opensolaris.org 	}
21679860Sgdamore@opensolaris.org 
21689860Sgdamore@opensolaris.org 	mutex_exit(&mh->m_lock);
21699860Sgdamore@opensolaris.org }
2170