mii.c revision 0dc2366f7b9f9f36e10909b1e95edbf2a261c2ac
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* mii - MII/PHY support for MAC drivers
*
* Utility module to provide a consistent interface to a MAC driver accross
* different implementations of PHY devices
*/
#include <sys/types.h>
#include <sys/debug.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/cmn_err.h>
#include <sys/policy.h>
#include <sys/note.h>
#include <sys/strsun.h>
#include <sys/miiregs.h>
#include <sys/mac_provider.h>
#include <sys/mac_ether.h>
#include <sys/mii.h>
#include "miipriv.h"
#define MII_SECOND 1000000
/* indices into error array */
enum {
MII_EOK = 0,
MII_ERESET,
MII_ESTART,
MII_ENOPHY,
MII_ECHECK,
MII_ELOOP,
};
static const char *mii_errors[] = {
"",
"Failure resetting PHY.",
"Failure starting PHY.",
"No Ethernet PHY found.",
"Failure reading PHY (removed?)",
"Failure setting loopback."
};
/* Indexed by XCVR_ type */
static const const char *mii_xcvr_types[] = {
"Undefined",
"Unknown",
"10 Mbps",
"100BASE-T4",
"100BASE-X",
"100BASE-T2",
"1000BASE-X",
"1000BASE-T"
};
/* state machine */
typedef enum {
MII_STATE_PROBE = 0,
MII_STATE_RESET,
MII_STATE_START,
MII_STATE_RUN,
MII_STATE_LOOPBACK,
} mii_tstate_t;
struct mii_handle {
dev_info_t *m_dip;
void *m_private;
mii_ops_t m_ops;
kt_did_t m_tq_id;
kmutex_t m_lock;
kcondvar_t m_cv;
ddi_taskq_t *m_tq;
int m_flags;
boolean_t m_started;
boolean_t m_suspending;
boolean_t m_suspended;
int m_error;
mii_tstate_t m_tstate;
#define MII_FLAG_EXIT 0x1 /* exit the thread */
#define MII_FLAG_STOP 0x2 /* shutdown MII monitoring */
#define MII_FLAG_RESET 0x4 /* reset the MII */
#define MII_FLAG_PROBE 0x8 /* probe for PHYs */
#define MII_FLAG_NOTIFY 0x10 /* notify about a change */
#define MII_FLAG_SUSPEND 0x20 /* monitoring suspended */
#define MII_FLAG_MACRESET 0x40 /* send reset to MAC */
#define MII_FLAG_PHYSTART 0x80 /* start up the PHY */
/* device name for printing, e.g. "hme0" */
char m_name[MODMAXNAMELEN + 16];
int m_addr;
phy_handle_t m_phys[32];
phy_handle_t m_bogus_phy;
phy_handle_t *m_phy;
link_state_t m_link;
/* these start out undefined, but get values due to mac_prop_set */
int m_en_aneg;
int m_en_10_hdx;
int m_en_10_fdx;
int m_en_100_t4;
int m_en_100_hdx;
int m_en_100_fdx;
int m_en_1000_hdx;
int m_en_1000_fdx;
int m_en_flowctrl;
boolean_t m_cap_pause;
boolean_t m_cap_asmpause;
};
static void _mii_task(void *);
static void _mii_probe_phy(phy_handle_t *);
static void _mii_probe(mii_handle_t);
static int _mii_reset(mii_handle_t);
static int _mii_loopback(mii_handle_t);
static void _mii_notify(mii_handle_t);
static int _mii_check(mii_handle_t);
static int _mii_start(mii_handle_t);
/*
* Loadable module structures/entrypoints
*/
extern struct mod_ops mod_misc_ops;
static struct modlmisc modlmisc = {
&mod_miscops,
"802.3 MII support",
};
static struct modlinkage modlinkage = {
MODREV_1, &modlmisc, NULL
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
void
_mii_error(mii_handle_t mh, int errno)
{
/*
* This dumps an error message, but it avoids filling the log with
* repeated error messages.
*/
if (mh->m_error != errno) {
cmn_err(CE_WARN, "%s: %s", mh->m_name, mii_errors[errno]);
mh->m_error = errno;
}
}
/*
* Known list of specific PHY probes.
*/
typedef boolean_t (*phy_probe_t)(phy_handle_t *);
phy_probe_t _phy_probes[] = {
phy_natsemi_probe,
phy_intel_probe,
phy_qualsemi_probe,
phy_cicada_probe,
phy_marvell_probe,
phy_realtek_probe,
phy_other_probe,
NULL
};
/*
* MII Interface functions
*/
mii_handle_t
mii_alloc_instance(void *private, dev_info_t *dip, int inst, mii_ops_t *ops)
{
mii_handle_t mh;
char tqname[16];
if (ops->mii_version != MII_OPS_VERSION) {
cmn_err(CE_WARN, "%s: incompatible MII version (%d)",
ddi_driver_name(dip), ops->mii_version);
return (NULL);
}
mh = kmem_zalloc(sizeof (*mh), KM_SLEEP);
(void) snprintf(mh->m_name, sizeof (mh->m_name), "%s%d",
ddi_driver_name(dip), inst);
/* DDI will prepend the driver name */
(void) snprintf(tqname, sizeof (tqname), "mii%d", inst);
mh->m_dip = dip;
mh->m_ops = *ops;
mh->m_private = private;
mh->m_suspended = B_FALSE;
mh->m_started = B_FALSE;
mh->m_tstate = MII_STATE_PROBE;
mh->m_link = LINK_STATE_UNKNOWN;
mh->m_error = MII_EOK;
mh->m_addr = -1;
mutex_init(&mh->m_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&mh->m_cv, NULL, CV_DRIVER, NULL);
mh->m_tq = ddi_taskq_create(dip, tqname, 1, TASKQ_DEFAULTPRI, 0);
if (mh->m_tq == NULL) {
cmn_err(CE_WARN, "%s: unable to create MII monitoring task",
ddi_driver_name(dip));
cv_destroy(&mh->m_cv);
mutex_destroy(&mh->m_lock);
kmem_free(mh, sizeof (*mh));
return (NULL);
}
/*
* Initialize user prefs by loading properties. Ultimately,
* Brussels interfaces would be superior here.
*/
#define GETPROP(name) ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0, name, -1)
mh->m_en_aneg = GETPROP("adv_autoneg_cap");
mh->m_en_10_hdx = GETPROP("adv_10hdx_cap");
mh->m_en_10_fdx = GETPROP("adv_10fdx_cap");
mh->m_en_100_hdx = GETPROP("adv_100hdx_cap");
mh->m_en_100_fdx = GETPROP("adv_100fdx_cap");
mh->m_en_100_t4 = GETPROP("adv_100T4_cap");
mh->m_en_1000_hdx = GETPROP("adv_1000hdx_cap");
mh->m_en_1000_fdx = GETPROP("adv_1000fdx_cap");
mh->m_cap_pause = B_FALSE;
mh->m_cap_asmpause = B_FALSE;
bzero(&mh->m_bogus_phy, sizeof (mh->m_bogus_phy));
mh->m_bogus_phy.phy_link = LINK_STATE_UNKNOWN;
mh->m_bogus_phy.phy_duplex = LINK_DUPLEX_UNKNOWN;
mh->m_bogus_phy.phy_addr = 0xff;
mh->m_bogus_phy.phy_type = XCVR_NONE;
mh->m_bogus_phy.phy_id = (uint32_t)-1;
mh->m_bogus_phy.phy_loopback = PHY_LB_NONE;
mh->m_bogus_phy.phy_flowctrl = LINK_FLOWCTRL_NONE;
mh->m_phy = &mh->m_bogus_phy;
for (int i = 0; i < 32; i++) {
mh->m_phys[i].phy_mii = mh;
}
mh->m_bogus_phy.phy_mii = mh;
return (mh);
}
mii_handle_t
mii_alloc(void *private, dev_info_t *dip, mii_ops_t *ops)
{
return (mii_alloc_instance(private, dip, ddi_get_instance(dip), ops));
}
void
mii_set_pauseable(mii_handle_t mh, boolean_t pauseable, boolean_t asymetric)
{
phy_handle_t *ph;
mutex_enter(&mh->m_lock);
ph = mh->m_phy;
ph->phy_cap_pause = mh->m_cap_pause = pauseable;
ph->phy_cap_asmpause = mh->m_cap_asmpause = asymetric;
if (pauseable) {
mh->m_en_flowctrl = LINK_FLOWCTRL_BI;
} else {
mh->m_en_flowctrl = LINK_FLOWCTRL_NONE;
}
mutex_exit(&mh->m_lock);
}
void
mii_free(mii_handle_t mh)
{
mutex_enter(&mh->m_lock);
mh->m_started = B_FALSE;
cv_broadcast(&mh->m_cv);
mutex_exit(&mh->m_lock);
ddi_taskq_destroy(mh->m_tq);
mutex_destroy(&mh->m_lock);
cv_destroy(&mh->m_cv);
kmem_free(mh, sizeof (*mh));
}
void
mii_reset(mii_handle_t mh)
{
mutex_enter(&mh->m_lock);
if (mh->m_tstate > MII_STATE_RESET)
mh->m_tstate = MII_STATE_RESET;
cv_broadcast(&mh->m_cv);
mutex_exit(&mh->m_lock);
}
void
mii_suspend(mii_handle_t mh)
{
mutex_enter(&mh->m_lock);
while ((!mh->m_suspended) && (mh->m_started)) {
mh->m_suspending = B_TRUE;
cv_broadcast(&mh->m_cv);
cv_wait(&mh->m_cv, &mh->m_lock);
}
mutex_exit(&mh->m_lock);
}
void
mii_resume(mii_handle_t mh)
{
mutex_enter(&mh->m_lock);
switch (mh->m_tstate) {
case MII_STATE_PROBE:
break;
case MII_STATE_RESET:
case MII_STATE_START:
case MII_STATE_RUN:
/* let monitor thread deal with this */
mh->m_tstate = MII_STATE_RESET;
break;
case MII_STATE_LOOPBACK:
/* loopback is handled synchronously */
(void) _mii_loopback(mh);
break;
}
mh->m_suspended = B_FALSE;
cv_broadcast(&mh->m_cv);
mutex_exit(&mh->m_lock);
}
void
mii_start(mii_handle_t mh)
{
mutex_enter(&mh->m_lock);
if (!mh->m_started) {
mh->m_tstate = MII_STATE_PROBE;
mh->m_started = B_TRUE;
if (ddi_taskq_dispatch(mh->m_tq, _mii_task, mh, DDI_NOSLEEP) !=
DDI_SUCCESS) {
cmn_err(CE_WARN,
"%s: unable to start MII monitoring task",
mh->m_name);
mh->m_started = B_FALSE;
}
}
cv_broadcast(&mh->m_cv);
mutex_exit(&mh->m_lock);
}
void
mii_stop(mii_handle_t mh)
{
mutex_enter(&mh->m_lock);
mh->m_started = B_FALSE;
/*
* Reset link state to unknown defaults, since we're not
* monitoring it anymore. We'll reprobe all link state later.
*/
mh->m_link = LINK_STATE_UNKNOWN;
mh->m_phy = &mh->m_bogus_phy;
cv_broadcast(&mh->m_cv);
mutex_exit(&mh->m_lock);
/*
* Notify the MAC driver. This will allow it to call back
* into the MAC framework to clear any previous link state.
*/
_mii_notify(mh);
}
void
mii_probe(mii_handle_t mh)
{
mutex_enter(&mh->m_lock);
_mii_probe(mh);
mutex_exit(&mh->m_lock);
}
void
mii_check(mii_handle_t mh)
{
mutex_enter(&mh->m_lock);
cv_broadcast(&mh->m_cv);
mutex_exit(&mh->m_lock);
}
int
mii_get_speed(mii_handle_t mh)
{
phy_handle_t *ph = mh->m_phy;
return (ph->phy_speed);
}
link_duplex_t
mii_get_duplex(mii_handle_t mh)
{
phy_handle_t *ph = mh->m_phy;
return (ph->phy_duplex);
}
link_state_t
mii_get_state(mii_handle_t mh)
{
phy_handle_t *ph = mh->m_phy;
return (ph->phy_link);
}
link_flowctrl_t
mii_get_flowctrl(mii_handle_t mh)
{
phy_handle_t *ph = mh->m_phy;
return (ph->phy_flowctrl);
}
int
mii_get_loopmodes(mii_handle_t mh, lb_property_t *modes)
{
phy_handle_t *ph = mh->m_phy;
int cnt = 0;
lb_property_t lmodes[MII_LOOPBACK_MAX];
lmodes[cnt].lb_type = normal;
(void) strlcpy(lmodes[cnt].key, "normal", sizeof (lmodes[cnt].key));
lmodes[cnt].value = PHY_LB_NONE;
cnt++;
if (ph->phy_cap_1000_fdx ||
ph->phy_cap_100_fdx ||
ph->phy_cap_10_fdx) {
/* we only support full duplex internal phy testing */
lmodes[cnt].lb_type = internal;
(void) strlcpy(lmodes[cnt].key, "PHY",
sizeof (lmodes[cnt].key));
lmodes[cnt].value = PHY_LB_INT_PHY;
cnt++;
}
if (ph->phy_cap_1000_fdx) {
lmodes[cnt].lb_type = external;
(void) strlcpy(lmodes[cnt].key, "1000Mbps",
sizeof (lmodes[cnt].key));
lmodes[cnt].value = PHY_LB_EXT_1000;
cnt++;
}
if (ph->phy_cap_100_fdx) {
lmodes[cnt].lb_type = external;
(void) strlcpy(lmodes[cnt].key, "100Mbps",
sizeof (lmodes[cnt].key));
lmodes[cnt].value = PHY_LB_EXT_100;
cnt++;
}
if (ph->phy_cap_10_fdx) {
lmodes[cnt].lb_type = external;
(void) strlcpy(lmodes[cnt].key, "10Mbps",
sizeof (lmodes[cnt].key));
lmodes[cnt].value = PHY_LB_EXT_10;
cnt++;
}
if (modes) {
bcopy(lmodes, modes, sizeof (lb_property_t) * cnt);
}
return (cnt);
}
uint32_t
mii_get_loopback(mii_handle_t mh)
{
phy_handle_t *ph = mh->m_phy;
return (ph->phy_loopback);
}
int
mii_set_loopback(mii_handle_t mh, uint32_t loop)
{
phy_handle_t *ph;
int rv;
mutex_enter(&mh->m_lock);
ph = mh->m_phy;
if ((!mh->m_started) || (!ph->phy_present) ||
(loop >= mii_get_loopmodes(mh, NULL))) {
return (EINVAL);
}
ph->phy_loopback = loop;
rv = _mii_loopback(mh);
if (rv == DDI_SUCCESS) {
mh->m_tstate = MII_STATE_LOOPBACK;
}
cv_broadcast(&mh->m_cv);
mutex_exit(&mh->m_lock);
return (rv == DDI_SUCCESS ? 0 : EIO);
}
uint32_t
mii_get_id(mii_handle_t mh)
{
phy_handle_t *ph = mh->m_phy;
return (ph->phy_id);
}
int
mii_get_addr(mii_handle_t mh)
{
return (mh->m_addr);
}
/* GLDv3 helpers */
boolean_t
mii_m_loop_ioctl(mii_handle_t mh, queue_t *wq, mblk_t *mp)
{
struct iocblk *iocp;
int rv = 0;
int cnt;
lb_property_t modes[MII_LOOPBACK_MAX];
lb_info_sz_t sz;
int cmd;
uint32_t mode;
iocp = (void *)mp->b_rptr;
cmd = iocp->ioc_cmd;
switch (cmd) {
case LB_SET_MODE:
case LB_GET_INFO_SIZE:
case LB_GET_INFO:
case LB_GET_MODE:
break;
default:
return (B_FALSE);
}
if (mp->b_cont == NULL) {
miocnak(wq, mp, 0, EINVAL);
return (B_TRUE);
}
switch (cmd) {
case LB_GET_INFO_SIZE:
cnt = mii_get_loopmodes(mh, modes);
if (iocp->ioc_count != sizeof (sz)) {
rv = EINVAL;
} else {
sz = cnt * sizeof (lb_property_t);
bcopy(&sz, mp->b_cont->b_rptr, sizeof (sz));
}
break;
case LB_GET_INFO:
cnt = mii_get_loopmodes(mh, modes);
if (iocp->ioc_count != (cnt * sizeof (lb_property_t))) {
rv = EINVAL;
} else {
bcopy(modes, mp->b_cont->b_rptr, iocp->ioc_count);
}
break;
case LB_GET_MODE:
if (iocp->ioc_count != sizeof (mode)) {
rv = EINVAL;
} else {
mode = mii_get_loopback(mh);
bcopy(&mode, mp->b_cont->b_rptr, sizeof (mode));
}
break;
case LB_SET_MODE:
rv = secpolicy_net_config(iocp->ioc_cr, B_FALSE);
if (rv != 0)
break;
if (iocp->ioc_count != sizeof (mode)) {
rv = EINVAL;
break;
}
bcopy(mp->b_cont->b_rptr, &mode, sizeof (mode));
rv = mii_set_loopback(mh, mode);
break;
}
if (rv == 0) {
miocack(wq, mp, iocp->ioc_count, 0);
} else {
miocnak(wq, mp, 0, rv);
}
return (B_TRUE);
}
int
mii_m_getprop(mii_handle_t mh, const char *name, mac_prop_id_t num,
uint_t sz, void *val)
{
phy_handle_t *ph;
int err = 0;
_NOTE(ARGUNUSED(name));
if (sz < 1)
return (EINVAL);
mutex_enter(&mh->m_lock);
ph = mh->m_phy;
#define CASE_PROP_ABILITY(PROP, VAR) \
case MAC_PROP_ADV_##PROP: \
*(uint8_t *)val = ph->phy_adv_##VAR; \
break; \
\
case MAC_PROP_EN_##PROP: \
*(uint8_t *)val = ph->phy_en_##VAR; \
break;
switch (num) {
case MAC_PROP_DUPLEX:
ASSERT(sz >= sizeof (link_duplex_t));
bcopy(&ph->phy_duplex, val, sizeof (link_duplex_t));
break;
case MAC_PROP_SPEED: {
uint64_t speed = ph->phy_speed * 1000000ull;
ASSERT(sz >= sizeof (uint64_t));
bcopy(&speed, val, sizeof (speed));
break;
}
case MAC_PROP_AUTONEG:
*(uint8_t *)val = ph->phy_adv_aneg;
break;
case MAC_PROP_FLOWCTRL:
ASSERT(sz >= sizeof (link_flowctrl_t));
bcopy(&ph->phy_flowctrl, val, sizeof (link_flowctrl_t));
break;
CASE_PROP_ABILITY(1000FDX_CAP, 1000_fdx)
CASE_PROP_ABILITY(1000HDX_CAP, 1000_hdx)
CASE_PROP_ABILITY(100T4_CAP, 100_t4)
CASE_PROP_ABILITY(100FDX_CAP, 100_fdx)
CASE_PROP_ABILITY(100HDX_CAP, 100_hdx)
CASE_PROP_ABILITY(10FDX_CAP, 10_fdx)
CASE_PROP_ABILITY(10HDX_CAP, 10_hdx)
default:
err = ENOTSUP;
break;
}
mutex_exit(&mh->m_lock);
return (err);
}
void
mii_m_propinfo(mii_handle_t mh, const char *name, mac_prop_id_t num,
mac_prop_info_handle_t prh)
{
phy_handle_t *ph;
_NOTE(ARGUNUSED(name));
mutex_enter(&mh->m_lock);
ph = mh->m_phy;
switch (num) {
case MAC_PROP_DUPLEX:
case MAC_PROP_SPEED:
mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
break;
case MAC_PROP_AUTONEG:
mac_prop_info_set_default_uint8(prh, ph->phy_cap_aneg);
break;
#define CASE_PROP_PERM(PROP, VAR) \
case MAC_PROP_ADV_##PROP: \
mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); \
mac_prop_info_set_default_uint8(prh, ph->phy_cap_##VAR); \
break; \
\
case MAC_PROP_EN_##PROP: \
if (!ph->phy_cap_##VAR) \
mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); \
mac_prop_info_set_default_uint8(prh, ph->phy_cap_##VAR); \
break;
CASE_PROP_PERM(1000FDX_CAP, 1000_fdx)
CASE_PROP_PERM(1000HDX_CAP, 1000_hdx)
CASE_PROP_PERM(100T4_CAP, 100_t4)
CASE_PROP_PERM(100FDX_CAP, 100_fdx)
CASE_PROP_PERM(100HDX_CAP, 100_hdx)
CASE_PROP_PERM(10FDX_CAP, 10_fdx)
CASE_PROP_PERM(10HDX_CAP, 10_hdx)
}
mutex_exit(&mh->m_lock);
}
int
mii_m_setprop(mii_handle_t mh, const char *name, mac_prop_id_t num,
uint_t sz, const void *valp)
{
phy_handle_t *ph;
boolean_t *advp = NULL;
boolean_t *capp = NULL;
int *macpp = NULL;
int rv = ENOTSUP;
_NOTE(ARGUNUSED(name));
if (sz < 1)
return (EINVAL);
mutex_enter(&mh->m_lock);
ph = mh->m_phy;
/* we don't support changing parameters while in loopback mode */
if (ph->phy_loopback != PHY_LB_NONE) {
switch (num) {
case MAC_PROP_EN_1000FDX_CAP:
case MAC_PROP_EN_1000HDX_CAP:
case MAC_PROP_EN_100FDX_CAP:
case MAC_PROP_EN_100HDX_CAP:
case MAC_PROP_EN_100T4_CAP:
case MAC_PROP_EN_10FDX_CAP:
case MAC_PROP_EN_10HDX_CAP:
case MAC_PROP_AUTONEG:
case MAC_PROP_FLOWCTRL:
return (EBUSY);
}
}
switch (num) {
case MAC_PROP_EN_1000FDX_CAP:
capp = &ph->phy_cap_1000_fdx;
advp = &ph->phy_en_1000_fdx;
macpp = &mh->m_en_1000_fdx;
break;
case MAC_PROP_EN_1000HDX_CAP:
capp = &ph->phy_cap_1000_hdx;
advp = &ph->phy_en_1000_hdx;
macpp = &mh->m_en_1000_hdx;
break;
case MAC_PROP_EN_100FDX_CAP:
capp = &ph->phy_cap_100_fdx;
advp = &ph->phy_en_100_fdx;
macpp = &mh->m_en_100_fdx;
break;
case MAC_PROP_EN_100HDX_CAP:
capp = &ph->phy_cap_100_hdx;
advp = &ph->phy_en_100_hdx;
macpp = &mh->m_en_100_hdx;
break;
case MAC_PROP_EN_100T4_CAP:
capp = &ph->phy_cap_100_t4;
advp = &ph->phy_en_100_t4;
macpp = &mh->m_en_100_t4;
break;
case MAC_PROP_EN_10FDX_CAP:
capp = &ph->phy_cap_10_fdx;
advp = &ph->phy_en_10_fdx;
macpp = &mh->m_en_10_fdx;
break;
case MAC_PROP_EN_10HDX_CAP:
capp = &ph->phy_cap_10_hdx;
advp = &ph->phy_en_10_hdx;
macpp = &mh->m_en_10_hdx;
break;
case MAC_PROP_AUTONEG:
capp = &ph->phy_cap_aneg;
advp = &ph->phy_en_aneg;
macpp = &mh->m_en_aneg;
break;
case MAC_PROP_FLOWCTRL: {
link_flowctrl_t fc;
boolean_t chg;
ASSERT(sz >= sizeof (link_flowctrl_t));
bcopy(valp, &fc, sizeof (fc));
chg = fc == ph->phy_en_flowctrl ? B_FALSE : B_TRUE;
switch (fc) {
case LINK_FLOWCTRL_NONE:
ph->phy_en_pause = B_FALSE;
ph->phy_en_asmpause = B_FALSE;
ph->phy_en_flowctrl = fc;
break;
/*
* Note that while we don't have a way to advertise
* that we can RX pause (we just won't send pause
* frames), we advertise full support. The MAC driver
* will learn of the configuration via the saved value
* of the tunable.
*/
case LINK_FLOWCTRL_BI:
case LINK_FLOWCTRL_RX:
if (ph->phy_cap_pause) {
ph->phy_en_pause = B_TRUE;
ph->phy_en_asmpause = B_TRUE;
ph->phy_en_flowctrl = fc;
} else {
rv = EINVAL;
}
break;
/*
* Tell the other side that we can assert pause, but
* we cannot resend.
*/
case LINK_FLOWCTRL_TX:
if (ph->phy_cap_asmpause) {
ph->phy_en_pause = B_FALSE;
ph->phy_en_flowctrl = fc;
ph->phy_en_asmpause = B_TRUE;
} else {
rv = EINVAL;
}
break;
default:
rv = EINVAL;
break;
}
if ((rv == 0) && chg) {
mh->m_en_flowctrl = fc;
mh->m_tstate = MII_STATE_RESET;
cv_broadcast(&mh->m_cv);
}
break;
}
default:
rv = ENOTSUP;
break;
}
if (capp && advp && macpp) {
if (sz < sizeof (uint8_t)) {
rv = EINVAL;
} else if (*capp) {
if (*advp != *(uint8_t *)valp) {
*advp = *(uint8_t *)valp;
*macpp = *(uint8_t *)valp;
mh->m_tstate = MII_STATE_RESET;
cv_broadcast(&mh->m_cv);
}
rv = 0;
}
}
mutex_exit(&mh->m_lock);
return (rv);
}
int
mii_m_getstat(mii_handle_t mh, uint_t stat, uint64_t *val)
{
phy_handle_t *ph;
int rv = 0;
mutex_enter(&mh->m_lock);
ph = mh->m_phy;
switch (stat) {
case MAC_STAT_IFSPEED:
*val = ph->phy_speed * 1000000ull;
break;
case ETHER_STAT_LINK_DUPLEX:
*val = ph->phy_duplex;
break;
case ETHER_STAT_LINK_AUTONEG:
*val = !!(ph->phy_adv_aneg && ph->phy_lp_aneg);
break;
case ETHER_STAT_XCVR_ID:
*val = ph->phy_id;
break;
case ETHER_STAT_XCVR_INUSE:
*val = ph->phy_type;
break;
case ETHER_STAT_XCVR_ADDR:
*val = ph->phy_addr;
break;
case ETHER_STAT_LINK_ASMPAUSE:
*val = ph->phy_adv_asmpause && ph->phy_lp_asmpause &&
ph->phy_adv_pause != ph->phy_lp_pause;
break;
case ETHER_STAT_LINK_PAUSE:
*val = (ph->phy_flowctrl == LINK_FLOWCTRL_BI) ||
(ph->phy_flowctrl == LINK_FLOWCTRL_RX);
break;
case ETHER_STAT_CAP_1000FDX:
*val = ph->phy_cap_1000_fdx;
break;
case ETHER_STAT_CAP_1000HDX:
*val = ph->phy_cap_1000_hdx;
break;
case ETHER_STAT_CAP_100FDX:
*val = ph->phy_cap_100_fdx;
break;
case ETHER_STAT_CAP_100HDX:
*val = ph->phy_cap_100_hdx;
break;
case ETHER_STAT_CAP_10FDX:
*val = ph->phy_cap_10_fdx;
break;
case ETHER_STAT_CAP_10HDX:
*val = ph->phy_cap_10_hdx;
break;
case ETHER_STAT_CAP_100T4:
*val = ph->phy_cap_100_t4;
break;
case ETHER_STAT_CAP_AUTONEG:
*val = ph->phy_cap_aneg;
break;
case ETHER_STAT_CAP_PAUSE:
*val = ph->phy_cap_pause;
break;
case ETHER_STAT_CAP_ASMPAUSE:
*val = ph->phy_cap_asmpause;
break;
case ETHER_STAT_LP_CAP_1000FDX:
*val = ph->phy_lp_1000_fdx;
break;
case ETHER_STAT_LP_CAP_1000HDX:
*val = ph->phy_lp_1000_hdx;
break;
case ETHER_STAT_LP_CAP_100FDX:
*val = ph->phy_lp_100_fdx;
break;
case ETHER_STAT_LP_CAP_100HDX:
*val = ph->phy_lp_100_hdx;
break;
case ETHER_STAT_LP_CAP_10FDX:
*val = ph->phy_lp_10_fdx;
break;
case ETHER_STAT_LP_CAP_10HDX:
*val = ph->phy_lp_10_hdx;
break;
case ETHER_STAT_LP_CAP_100T4:
*val = ph->phy_lp_100_t4;
break;
case ETHER_STAT_LP_CAP_AUTONEG:
*val = ph->phy_lp_aneg;
break;
case ETHER_STAT_LP_CAP_PAUSE:
*val = ph->phy_lp_pause;
break;
case ETHER_STAT_LP_CAP_ASMPAUSE:
*val = ph->phy_lp_asmpause;
break;
case ETHER_STAT_ADV_CAP_1000FDX:
*val = ph->phy_adv_1000_fdx;
break;
case ETHER_STAT_ADV_CAP_1000HDX:
*val = ph->phy_adv_1000_hdx;
break;
case ETHER_STAT_ADV_CAP_100FDX:
*val = ph->phy_adv_100_fdx;
break;
case ETHER_STAT_ADV_CAP_100HDX:
*val = ph->phy_adv_100_hdx;
break;
case ETHER_STAT_ADV_CAP_10FDX:
*val = ph->phy_adv_10_fdx;
break;
case ETHER_STAT_ADV_CAP_10HDX:
*val = ph->phy_adv_10_hdx;
break;
case ETHER_STAT_ADV_CAP_100T4:
*val = ph->phy_adv_100_t4;
break;
case ETHER_STAT_ADV_CAP_AUTONEG:
*val = ph->phy_adv_aneg;
break;
case ETHER_STAT_ADV_CAP_PAUSE:
*val = ph->phy_adv_pause;
break;
case ETHER_STAT_ADV_CAP_ASMPAUSE:
*val = ph->phy_adv_asmpause;
break;
default:
rv = ENOTSUP;
break;
}
mutex_exit(&mh->m_lock);
return (rv);
}
/*
* PHY support routines. Private to the MII module and the vendor
* specific PHY implementation code.
*/
uint16_t
phy_read(phy_handle_t *ph, uint8_t reg)
{
mii_handle_t mh = ph->phy_mii;
return ((*mh->m_ops.mii_read)(mh->m_private, ph->phy_addr, reg));
}
void
phy_write(phy_handle_t *ph, uint8_t reg, uint16_t val)
{
mii_handle_t mh = ph->phy_mii;
(*mh->m_ops.mii_write)(mh->m_private, ph->phy_addr, reg, val);
}
int
phy_reset(phy_handle_t *ph)
{
ASSERT(mutex_owned(&ph->phy_mii->m_lock));
/*
* For our device, make sure its powered up and unisolated.
*/
PHY_CLR(ph, MII_CONTROL,
MII_CONTROL_PWRDN | MII_CONTROL_ISOLATE);
/*
* Finally reset it.
*/
PHY_SET(ph, MII_CONTROL, MII_CONTROL_RESET);
/*
* Apparently some devices (DP83840A) like to have a little
* bit of a wait before we start accessing anything else on
* the PHY.
*/
drv_usecwait(500);
/*
* Wait for reset to complete - probably very fast, but no
* more than 0.5 sec according to spec. It would be nice if
* we could use delay() here, but MAC drivers may call
* functions which hold this lock in interrupt context, so
* sleeping would be a definite no-no. The good news here is
* that it seems to be the case that most devices come back
* within only a few hundred usec.
*/
for (int i = 500000; i; i -= 100) {
if ((phy_read(ph, MII_CONTROL) & MII_CONTROL_RESET) == 0) {
/* reset completed */
return (DDI_SUCCESS);
}
drv_usecwait(100);
}
return (DDI_FAILURE);
}
int
phy_stop(phy_handle_t *ph)
{
phy_write(ph, MII_CONTROL, MII_CONTROL_ISOLATE);
return (DDI_SUCCESS);
}
int
phy_loop(phy_handle_t *ph)
{
uint16_t bmcr, gtcr;
ASSERT(mutex_owned(&ph->phy_mii->m_lock));
/*
* Disable everything to start... we'll add in modes as we go.
*/
ph->phy_adv_aneg = B_FALSE;
ph->phy_adv_1000_fdx = B_FALSE;
ph->phy_adv_1000_hdx = B_FALSE;
ph->phy_adv_100_fdx = B_FALSE;
ph->phy_adv_100_t4 = B_FALSE;
ph->phy_adv_100_hdx = B_FALSE;
ph->phy_adv_10_fdx = B_FALSE;
ph->phy_adv_10_hdx = B_FALSE;
ph->phy_adv_pause = B_FALSE;
ph->phy_adv_asmpause = B_FALSE;
bmcr = 0;
gtcr = MII_MSCONTROL_MANUAL | MII_MSCONTROL_MASTER;
switch (ph->phy_loopback) {
case PHY_LB_NONE:
/* We shouldn't be here */
ASSERT(0);
break;
case PHY_LB_INT_PHY:
bmcr |= MII_CONTROL_LOOPBACK;
ph->phy_duplex = LINK_DUPLEX_FULL;
if (ph->phy_cap_1000_fdx) {
bmcr |= MII_CONTROL_1GB | MII_CONTROL_FDUPLEX;
ph->phy_speed = 1000;
} else if (ph->phy_cap_100_fdx) {
bmcr |= MII_CONTROL_100MB | MII_CONTROL_FDUPLEX;
ph->phy_speed = 100;
} else if (ph->phy_cap_10_fdx) {
bmcr |= MII_CONTROL_FDUPLEX;
ph->phy_speed = 10;
}
break;
case PHY_LB_EXT_10:
bmcr = MII_CONTROL_FDUPLEX;
ph->phy_speed = 10;
ph->phy_duplex = LINK_DUPLEX_FULL;
break;
case PHY_LB_EXT_100:
bmcr = MII_CONTROL_100MB | MII_CONTROL_FDUPLEX;
ph->phy_speed = 100;
ph->phy_duplex = LINK_DUPLEX_FULL;
break;
case PHY_LB_EXT_1000:
bmcr = MII_CONTROL_1GB | MII_CONTROL_FDUPLEX;
ph->phy_speed = 1000;
ph->phy_duplex = LINK_DUPLEX_FULL;
break;
}
ph->phy_link = LINK_STATE_UP; /* force up for loopback */
ph->phy_flowctrl = LINK_FLOWCTRL_NONE;
switch (ph->phy_type) {
case XCVR_1000T:
case XCVR_1000X:
case XCVR_100T2:
phy_write(ph, MII_MSCONTROL, gtcr);
break;
}
phy_write(ph, MII_CONTROL, bmcr);
return (DDI_SUCCESS);
}
int
phy_start(phy_handle_t *ph)
{
uint16_t bmcr, anar, gtcr;
ASSERT(mutex_owned(&ph->phy_mii->m_lock));
ASSERT(ph->phy_loopback == PHY_LB_NONE);
/*
* No loopback overrides, so try to advertise everything
* that is administratively enabled.
*/
ph->phy_adv_aneg = ph->phy_en_aneg;
ph->phy_adv_1000_fdx = ph->phy_en_1000_fdx;
ph->phy_adv_1000_hdx = ph->phy_en_1000_hdx;
ph->phy_adv_100_fdx = ph->phy_en_100_fdx;
ph->phy_adv_100_t4 = ph->phy_en_100_t4;
ph->phy_adv_100_hdx = ph->phy_en_100_hdx;
ph->phy_adv_10_fdx = ph->phy_en_10_fdx;
ph->phy_adv_10_hdx = ph->phy_en_10_hdx;
ph->phy_adv_pause = ph->phy_en_pause;
ph->phy_adv_asmpause = ph->phy_en_asmpause;
/*
* Limit properties to what the hardware can actually support.
*/
#define FILTER_ADV(CAP) \
if (!ph->phy_cap_##CAP) \
ph->phy_adv_##CAP = 0
FILTER_ADV(aneg);
FILTER_ADV(1000_fdx);
FILTER_ADV(1000_hdx);
FILTER_ADV(100_fdx);
FILTER_ADV(100_t4);
FILTER_ADV(100_hdx);
FILTER_ADV(10_fdx);
FILTER_ADV(10_hdx);
FILTER_ADV(pause);
FILTER_ADV(asmpause);
#undef FILTER_ADV
/*
* We need at least one valid mode.
*/
if ((!ph->phy_adv_1000_fdx) &&
(!ph->phy_adv_1000_hdx) &&
(!ph->phy_adv_100_t4) &&
(!ph->phy_adv_100_fdx) &&
(!ph->phy_adv_100_hdx) &&
(!ph->phy_adv_10_fdx) &&
(!ph->phy_adv_10_hdx)) {
phy_warn(ph,
"No valid link mode selected. Powering down PHY.");
PHY_SET(ph, MII_CONTROL, MII_CONTROL_PWRDN);
ph->phy_link = LINK_STATE_DOWN;
return (DDI_SUCCESS);
}
bmcr = 0;
gtcr = 0;
if (ph->phy_adv_aneg) {
bmcr |= MII_CONTROL_ANE | MII_CONTROL_RSAN;
}
if ((ph->phy_adv_1000_fdx) || (ph->phy_adv_1000_hdx)) {
bmcr |= MII_CONTROL_1GB;
} else if (ph->phy_adv_100_fdx || ph->phy_adv_100_hdx ||
ph->phy_adv_100_t4) {
bmcr |= MII_CONTROL_100MB;
}
if (ph->phy_adv_1000_fdx || ph->phy_adv_100_fdx || ph->phy_adv_10_fdx) {
bmcr |= MII_CONTROL_FDUPLEX;
}
if (ph->phy_type == XCVR_1000X) {
/* 1000BASE-X (usually fiber) */
anar = 0;
if (ph->phy_adv_1000_fdx) {
anar |= MII_ABILITY_X_FD;
}
if (ph->phy_adv_1000_hdx) {
anar |= MII_ABILITY_X_HD;
}
if (ph->phy_adv_pause) {
anar |= MII_ABILITY_X_PAUSE;
}
if (ph->phy_adv_asmpause) {
anar |= MII_ABILITY_X_ASMPAUSE;
}
} else if (ph->phy_type == XCVR_100T2) {
/* 100BASE-T2 */
anar = 0;
if (ph->phy_adv_100_fdx) {
anar |= MII_ABILITY_T2_FD;
}
if (ph->phy_adv_100_hdx) {
anar |= MII_ABILITY_T2_HD;
}
} else {
anar = MII_AN_SELECTOR_8023;
/* 1000BASE-T or 100BASE-X probably */
if (ph->phy_adv_1000_fdx) {
gtcr |= MII_MSCONTROL_1000T_FD;
}
if (ph->phy_adv_1000_hdx) {
gtcr |= MII_MSCONTROL_1000T;
}
if (ph->phy_adv_100_fdx) {
anar |= MII_ABILITY_100BASE_TX_FD;
}
if (ph->phy_adv_100_hdx) {
anar |= MII_ABILITY_100BASE_TX;
}
if (ph->phy_adv_100_t4) {
anar |= MII_ABILITY_100BASE_T4;
}
if (ph->phy_adv_10_fdx) {
anar |= MII_ABILITY_10BASE_T_FD;
}
if (ph->phy_adv_10_hdx) {
anar |= MII_ABILITY_10BASE_T;
}
if (ph->phy_adv_pause) {
anar |= MII_ABILITY_PAUSE;
}
if (ph->phy_adv_asmpause) {
anar |= MII_ABILITY_ASMPAUSE;
}
}
ph->phy_link = LINK_STATE_DOWN;
ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
ph->phy_speed = 0;
phy_write(ph, MII_AN_ADVERT, anar);
phy_write(ph, MII_CONTROL, bmcr & ~(MII_CONTROL_RSAN));
switch (ph->phy_type) {
case XCVR_1000T:
case XCVR_1000X:
case XCVR_100T2:
phy_write(ph, MII_MSCONTROL, gtcr);
}
/*
* Finally, this will start up autoneg if it is enabled, or
* force link settings otherwise.
*/
phy_write(ph, MII_CONTROL, bmcr);
return (DDI_SUCCESS);
}
int
phy_check(phy_handle_t *ph)
{
uint16_t control, status, lpar, msstat, anexp;
int debounces = 100;
ASSERT(mutex_owned(&ph->phy_mii->m_lock));
debounce:
status = phy_read(ph, MII_STATUS);
control = phy_read(ph, MII_CONTROL);
if (status & MII_STATUS_EXTENDED) {
lpar = phy_read(ph, MII_AN_LPABLE);
anexp = phy_read(ph, MII_AN_EXPANSION);
} else {
lpar = 0;
anexp = 0;
}
/*
* We reread to clear any latched bits. This also debounces
* any state that might be in transition.
*/
drv_usecwait(10);
if ((status != phy_read(ph, MII_STATUS)) && debounces) {
debounces--;
goto debounce;
}
/*
* Detect the situation where the PHY is removed or has died.
* According to spec, at least one bit of status must be set,
* and at least one bit must be clear.
*/
if ((status == 0xffff) || (status == 0)) {
ph->phy_speed = 0;
ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
ph->phy_link = LINK_STATE_UNKNOWN;
ph->phy_present = B_FALSE;
return (DDI_FAILURE);
}
/* We only respect the link flag if we are not in loopback. */
if ((ph->phy_loopback != PHY_LB_INT_PHY) &&
((status & MII_STATUS_LINKUP) == 0)) {
ph->phy_speed = 0;
ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
ph->phy_link = LINK_STATE_DOWN;
return (DDI_SUCCESS);
}
ph->phy_link = LINK_STATE_UP;
if ((control & MII_CONTROL_ANE) == 0) {
ph->phy_lp_aneg = B_FALSE;
ph->phy_lp_10_hdx = B_FALSE;
ph->phy_lp_10_fdx = B_FALSE;
ph->phy_lp_100_t4 = B_FALSE;
ph->phy_lp_100_hdx = B_FALSE;
ph->phy_lp_100_fdx = B_FALSE;
ph->phy_lp_1000_hdx = B_FALSE;
ph->phy_lp_1000_fdx = B_FALSE;
/*
* We have no idea what our link partner might or might
* not be able to support, except that it appears to
* support the same mode that we have forced.
*/
if (control & MII_CONTROL_1GB) {
ph->phy_speed = 1000;
} else if (control & MII_CONTROL_100MB) {
ph->phy_speed = 100;
} else {
ph->phy_speed = 10;
}
ph->phy_duplex = control & MII_CONTROL_FDUPLEX ?
LINK_DUPLEX_FULL : LINK_DUPLEX_HALF;
return (DDI_SUCCESS);
}
if (ph->phy_type == XCVR_1000X) {
ph->phy_lp_10_hdx = B_FALSE;
ph->phy_lp_10_fdx = B_FALSE;
ph->phy_lp_100_t4 = B_FALSE;
ph->phy_lp_100_hdx = B_FALSE;
ph->phy_lp_100_fdx = B_FALSE;
/* 1000BASE-X requires autonegotiation */
ph->phy_lp_aneg = B_TRUE;
ph->phy_lp_1000_fdx = !!(lpar & MII_ABILITY_X_FD);
ph->phy_lp_1000_hdx = !!(lpar & MII_ABILITY_X_HD);
ph->phy_lp_pause = !!(lpar & MII_ABILITY_X_PAUSE);
ph->phy_lp_asmpause = !!(lpar & MII_ABILITY_X_ASMPAUSE);
} else if (ph->phy_type == XCVR_100T2) {
ph->phy_lp_10_hdx = B_FALSE;
ph->phy_lp_10_fdx = B_FALSE;
ph->phy_lp_100_t4 = B_FALSE;
ph->phy_lp_1000_hdx = B_FALSE;
ph->phy_lp_1000_fdx = B_FALSE;
ph->phy_lp_pause = B_FALSE;
ph->phy_lp_asmpause = B_FALSE;
/* 100BASE-T2 requires autonegotiation */
ph->phy_lp_aneg = B_TRUE;
ph->phy_lp_100_fdx = !!(lpar & MII_ABILITY_T2_FD);
ph->phy_lp_100_hdx = !!(lpar & MII_ABILITY_T2_HD);
} else if (anexp & MII_AN_EXP_PARFAULT) {
/*
* Parallel detection fault! This happens when the
* peer does not use autonegotiation, and the
* detection logic reports more than one type of legal
* link is available. Note that parallel detection
* can only happen with half duplex 10, 100, and
* 100TX4. We also should not have got here, because
* the link state bit should have failed.
*/
#ifdef DEBUG
phy_warn(ph, "Parallel detection fault!");
#endif
ph->phy_lp_10_hdx = B_FALSE;
ph->phy_lp_10_fdx = B_FALSE;
ph->phy_lp_100_t4 = B_FALSE;
ph->phy_lp_100_hdx = B_FALSE;
ph->phy_lp_100_fdx = B_FALSE;
ph->phy_lp_1000_hdx = B_FALSE;
ph->phy_lp_1000_fdx = B_FALSE;
ph->phy_lp_pause = B_FALSE;
ph->phy_lp_asmpause = B_FALSE;
ph->phy_speed = 0;
ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
return (DDI_SUCCESS);
} else {
ph->phy_lp_aneg = !!(anexp & MII_AN_EXP_LPCANAN);
/*
* Note: If the peer doesn't support autonegotiation, then
* according to clause 28.5.4.5, the link partner ability
* register will still have the right bits set. However,
* gigabit modes cannot use legacy parallel detection.
*/
if ((ph->phy_type == XCVR_1000T) &
(anexp & MII_AN_EXP_LPCANAN)) {
/* check for gige */
msstat = phy_read(ph, MII_MSSTATUS);
ph->phy_lp_1000_hdx =
!!(msstat & MII_MSSTATUS_LP1000T);
ph->phy_lp_1000_fdx =
!!(msstat & MII_MSSTATUS_LP1000T_FD);
}
ph->phy_lp_100_fdx = !!(lpar & MII_ABILITY_100BASE_TX_FD);
ph->phy_lp_100_hdx = !!(lpar & MII_ABILITY_100BASE_TX);
ph->phy_lp_100_t4 = !!(lpar & MII_ABILITY_100BASE_T4);
ph->phy_lp_10_fdx = !!(lpar & MII_ABILITY_10BASE_T_FD);
ph->phy_lp_10_hdx = !!(lpar & MII_ABILITY_10BASE_T);
ph->phy_lp_pause = !!(lpar & MII_ABILITY_PAUSE);
ph->phy_lp_asmpause = !!(lpar & MII_ABILITY_ASMPAUSE);
}
/* resolve link pause */
if ((ph->phy_en_flowctrl == LINK_FLOWCTRL_BI) &&
(ph->phy_lp_pause)) {
ph->phy_flowctrl = LINK_FLOWCTRL_BI;
} else if ((ph->phy_en_flowctrl == LINK_FLOWCTRL_RX) &&
(ph->phy_lp_pause || ph->phy_lp_asmpause)) {
ph->phy_flowctrl = LINK_FLOWCTRL_RX;
} else if ((ph->phy_en_flowctrl == LINK_FLOWCTRL_TX) &&
(ph->phy_lp_pause)) {
ph->phy_flowctrl = LINK_FLOWCTRL_TX;
} else {
ph->phy_flowctrl = LINK_FLOWCTRL_NONE;
}
if (ph->phy_adv_1000_fdx && ph->phy_lp_1000_fdx) {
ph->phy_speed = 1000;
ph->phy_duplex = LINK_DUPLEX_FULL;
} else if (ph->phy_adv_1000_hdx && ph->phy_lp_1000_hdx) {
ph->phy_speed = 1000;
ph->phy_duplex = LINK_DUPLEX_HALF;
} else if (ph->phy_adv_100_fdx && ph->phy_lp_100_fdx) {
ph->phy_speed = 100;
ph->phy_duplex = LINK_DUPLEX_FULL;
} else if (ph->phy_adv_100_t4 && ph->phy_lp_100_t4) {
ph->phy_speed = 100;
ph->phy_duplex = LINK_DUPLEX_HALF;
} else if (ph->phy_adv_100_hdx && ph->phy_lp_100_hdx) {
ph->phy_speed = 100;
ph->phy_duplex = LINK_DUPLEX_HALF;
} else if (ph->phy_adv_10_fdx && ph->phy_lp_10_fdx) {
ph->phy_speed = 10;
ph->phy_duplex = LINK_DUPLEX_FULL;
} else if (ph->phy_adv_10_hdx && ph->phy_lp_10_hdx) {
ph->phy_speed = 10;
ph->phy_duplex = LINK_DUPLEX_HALF;
} else {
#ifdef DEBUG
phy_warn(ph, "No common abilities.");
#endif
ph->phy_speed = 0;
ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
}
return (DDI_SUCCESS);
}
int
phy_get_prop(phy_handle_t *ph, char *prop, int dflt)
{
mii_handle_t mh = ph->phy_mii;
return (ddi_prop_get_int(DDI_DEV_T_ANY, mh->m_dip, 0, prop, dflt));
}
const char *
phy_get_name(phy_handle_t *ph)
{
mii_handle_t mh = ph->phy_mii;
return (mh->m_name);
}
const char *
phy_get_driver(phy_handle_t *ph)
{
mii_handle_t mh = ph->phy_mii;
return (ddi_driver_name(mh->m_dip));
}
void
phy_warn(phy_handle_t *ph, const char *fmt, ...)
{
va_list va;
char buf[256];
(void) snprintf(buf, sizeof (buf), "%s: %s", phy_get_name(ph), fmt);
va_start(va, fmt);
vcmn_err(CE_WARN, buf, va);
va_end(va);
}
/*
* Internal support routines.
*/
void
_mii_notify(mii_handle_t mh)
{
if (mh->m_ops.mii_notify != NULL) {
mh->m_ops.mii_notify(mh->m_private, mh->m_link);
}
}
void
_mii_probe_phy(phy_handle_t *ph)
{
uint16_t bmsr;
uint16_t extsr;
mii_handle_t mh = ph->phy_mii;
/*
* Apparently, PHY 0 is less likely to be physically
* connected, and should always be the last one tried. Most
* single solution NICs use PHY1 for their built-in
* transceiver. NICs with an external MII will often place
* the external PHY at address 1, and use address 0 for the
* internal PHY.
*/
ph->phy_id = 0;
ph->phy_model = "PHY";
ph->phy_vendor = "Unknown Vendor";
/* done twice to clear any latched bits */
bmsr = phy_read(ph, MII_STATUS);
bmsr = phy_read(ph, MII_STATUS);
if ((bmsr == 0) || (bmsr == 0xffff)) {
ph->phy_present = B_FALSE;
return;
}
if (bmsr & MII_STATUS_EXTSTAT) {
extsr = phy_read(ph, MII_EXTSTATUS);
} else {
extsr = 0;
}
ph->phy_present = B_TRUE;
ph->phy_id = ((uint32_t)phy_read(ph, MII_PHYIDH) << 16) |
phy_read(ph, MII_PHYIDL);
/* setup default handlers */
ph->phy_reset = phy_reset;
ph->phy_start = phy_start;
ph->phy_stop = phy_stop;
ph->phy_check = phy_check;
ph->phy_loop = phy_loop;
/*
* We ignore the non-existent 100baseT2 stuff -- no
* known products for it exist.
*/
ph->phy_cap_aneg = !!(bmsr & MII_STATUS_CANAUTONEG);
ph->phy_cap_100_t4 = !!(bmsr & MII_STATUS_100_BASE_T4);
ph->phy_cap_100_fdx = !!(bmsr & MII_STATUS_100_BASEX_FD);
ph->phy_cap_100_hdx = !!(bmsr & MII_STATUS_100_BASEX);
ph->phy_cap_10_fdx = !!(bmsr & MII_STATUS_10_FD);
ph->phy_cap_10_hdx = !!(bmsr & MII_STATUS_10);
ph->phy_cap_1000_fdx =
!!(extsr & (MII_EXTSTATUS_1000X_FD|MII_EXTSTATUS_1000T_FD));
ph->phy_cap_1000_hdx =
!!(extsr & (MII_EXTSTATUS_1000X | MII_EXTSTATUS_1000T));
ph->phy_cap_pause = mh->m_cap_pause;
ph->phy_cap_asmpause = mh->m_cap_asmpause;
if (bmsr & MII_STATUS_10) {
ph->phy_cap_10_hdx = B_TRUE;
ph->phy_type = XCVR_10;
}
if (bmsr & MII_STATUS_10_FD) {
ph->phy_cap_10_fdx = B_TRUE;
ph->phy_type = XCVR_10;
}
if (bmsr & MII_STATUS_100T2) {
ph->phy_cap_100_hdx = B_TRUE;
ph->phy_type = XCVR_100T2;
}
if (bmsr & MII_STATUS_100T2_FD) {
ph->phy_cap_100_fdx = B_TRUE;
ph->phy_type = XCVR_100T2;
}
if (bmsr & MII_STATUS_100_BASE_T4) {
ph->phy_cap_100_hdx = B_TRUE;
ph->phy_type = XCVR_100T4;
}
if (bmsr & MII_STATUS_100_BASEX) {
ph->phy_cap_100_hdx = B_TRUE;
ph->phy_type = XCVR_100X;
}
if (bmsr & MII_STATUS_100_BASEX_FD) {
ph->phy_cap_100_fdx = B_TRUE;
ph->phy_type = XCVR_100X;
}
if (extsr & MII_EXTSTATUS_1000X) {
ph->phy_cap_1000_hdx = B_TRUE;
ph->phy_type = XCVR_1000X;
}
if (extsr & MII_EXTSTATUS_1000X_FD) {
ph->phy_cap_1000_fdx = B_TRUE;
ph->phy_type = XCVR_1000X;
}
if (extsr & MII_EXTSTATUS_1000T) {
ph->phy_cap_1000_hdx = B_TRUE;
ph->phy_type = XCVR_1000T;
}
if (extsr & MII_EXTSTATUS_1000T_FD) {
ph->phy_cap_1000_fdx = B_TRUE;
ph->phy_type = XCVR_1000T;
}
for (int j = 0; _phy_probes[j] != NULL; j++) {
if ((*_phy_probes[j])(ph)) {
break;
}
}
#define INIT_ENABLE(CAP) \
ph->phy_en_##CAP = (mh->m_en_##CAP > 0) ? \
mh->m_en_##CAP : ph->phy_cap_##CAP
INIT_ENABLE(aneg);
INIT_ENABLE(1000_fdx);
INIT_ENABLE(1000_hdx);
INIT_ENABLE(100_fdx);
INIT_ENABLE(100_t4);
INIT_ENABLE(100_hdx);
INIT_ENABLE(10_fdx);
INIT_ENABLE(10_hdx);
#undef INIT_ENABLE
ph->phy_en_flowctrl = mh->m_en_flowctrl;
switch (ph->phy_en_flowctrl) {
case LINK_FLOWCTRL_BI:
case LINK_FLOWCTRL_RX:
ph->phy_en_pause = B_TRUE;
ph->phy_en_asmpause = B_TRUE;
break;
case LINK_FLOWCTRL_TX:
ph->phy_en_pause = B_FALSE;
ph->phy_en_asmpause = B_TRUE;
break;
default:
ph->phy_en_pause = B_FALSE;
ph->phy_en_asmpause = B_FALSE;
break;
}
}
void
_mii_probe(mii_handle_t mh)
{
uint8_t new_addr;
uint8_t old_addr;
uint8_t user_addr;
uint8_t curr_addr;
phy_handle_t *ph;
int pri = 0;
int first;
user_addr = ddi_prop_get_int(DDI_DEV_T_ANY, mh->m_dip, 0,
"phy-addr", -1);
old_addr = mh->m_addr;
new_addr = 0xff;
/*
* Apparently, PHY 0 is less likely to be physically
* connected, and should always be the last one tried. Most
* single solution NICs use PHY1 for their built-in
* transceiver. NICs with an external MII will often place
* the external PHY at address 1, and use address 0 for the
* internal PHY.
*
* Some devices have a different preference however. They can
* override the default starting point of the search by
* exporting a "first-phy" property.
*/
first = ddi_prop_get_int(DDI_DEV_T_ANY, mh->m_dip, 0, "first-phy", 1);
if ((first < 0) || (first > 31)) {
first = 1;
}
for (int i = first; i < (first + 32); i++) {
/*
* This is tricky: it lets us start searching at an
* arbitrary address instead of 0, dealing with the
* wrap-around at address 31 properly.
*/
curr_addr = i % 32;
ph = &mh->m_phys[curr_addr];
bzero(ph, sizeof (*ph));
ph->phy_addr = curr_addr;
ph->phy_mii = mh;
_mii_probe_phy(ph);
if (!ph->phy_present)
continue;
if (curr_addr == user_addr) {
/*
* We always try to honor the user configured phy.
*/
new_addr = curr_addr;
pri = 4;
}
/* two reads to clear latched bits */
if ((phy_read(ph, MII_STATUS) & MII_STATUS_LINKUP) &&
(phy_read(ph, MII_STATUS) & MII_STATUS_LINKUP) &&
(pri < 3)) {
/*
* Link present is good. We prefer this over
* a possibly disconnected link.
*/
new_addr = curr_addr;
pri = 3;
}
if ((curr_addr == old_addr) && (pri < 2)) {
/*
* All else being equal, minimize change.
*/
new_addr = curr_addr;
pri = 2;
}
if (pri < 1) {
/*
* But make sure we at least select a present PHY.
*/
new_addr = curr_addr;
pri = 1;
}
}
if (new_addr == 0xff) {
mh->m_addr = -1;
mh->m_phy = &mh->m_bogus_phy;
_mii_error(mh, MII_ENOPHY);
} else {
mh->m_addr = new_addr;
mh->m_phy = &mh->m_phys[new_addr];
mh->m_tstate = MII_STATE_RESET;
if (new_addr != old_addr) {
cmn_err(CE_CONT,
"?%s: Using %s Ethernet PHY at %d: %s %s\n",
mh->m_name, mii_xcvr_types[mh->m_phy->phy_type],
mh->m_addr, mh->m_phy->phy_vendor,
mh->m_phy->phy_model);
mh->m_link = LINK_STATE_UNKNOWN;
}
}
}
int
_mii_reset(mii_handle_t mh)
{
phy_handle_t *ph;
boolean_t notify;
ASSERT(mutex_owned(&mh->m_lock));
/*
* Reset logic. We want to isolate all the other
* phys that are not in use.
*/
for (int i = 0; i < 32; i++) {
ph = &mh->m_phys[i];
if (!ph->phy_present)
continue;
/* Don't touch our own phy, yet. */
if (ph == mh->m_phy)
continue;
ph->phy_stop(ph);
}
ph = mh->m_phy;
ASSERT(ph->phy_present);
/* If we're resetting the PHY, then we want to notify loss of link */
notify = (mh->m_link != LINK_STATE_DOWN);
mh->m_link = LINK_STATE_DOWN;
ph->phy_link = LINK_STATE_DOWN;
ph->phy_speed = 0;
ph->phy_duplex = LINK_DUPLEX_UNKNOWN;
if (ph->phy_reset(ph) != DDI_SUCCESS) {
_mii_error(mh, MII_ERESET);
return (DDI_FAILURE);
}
/* Perform optional mac layer reset. */
if (mh->m_ops.mii_reset != NULL) {
mh->m_ops.mii_reset(mh->m_private);
}
/* Perform optional mac layer notification. */
if (notify) {
_mii_notify(mh);
}
return (DDI_SUCCESS);
}
int
_mii_loopback(mii_handle_t mh)
{
phy_handle_t *ph;
ASSERT(mutex_owned(&mh->m_lock));
ph = mh->m_phy;
if (_mii_reset(mh) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
if (ph->phy_loopback == PHY_LB_NONE) {
mh->m_tstate = MII_STATE_START;
return (DDI_SUCCESS);
}
if (ph->phy_loop(ph) != DDI_SUCCESS) {
_mii_error(mh, MII_ELOOP);
return (DDI_FAILURE);
}
/* Just force loopback to link up. */
mh->m_link = ph->phy_link = LINK_STATE_UP;
_mii_notify(mh);
return (DDI_SUCCESS);
}
int
_mii_start(mii_handle_t mh)
{
phy_handle_t *ph;
ph = mh->m_phy;
ASSERT(mutex_owned(&mh->m_lock));
ASSERT(ph->phy_present);
ASSERT(ph->phy_loopback == PHY_LB_NONE);
if (ph->phy_start(ph) != DDI_SUCCESS) {
_mii_error(mh, MII_ESTART);
return (DDI_FAILURE);
}
/* clear the error state since we got a good startup! */
mh->m_error = MII_EOK;
return (DDI_SUCCESS);
}
int
_mii_check(mii_handle_t mh)
{
link_state_t olink;
int ospeed;
link_duplex_t oduplex;
link_flowctrl_t ofctrl;
phy_handle_t *ph;
ph = mh->m_phy;
olink = mh->m_link;
ospeed = ph->phy_speed;
oduplex = ph->phy_duplex;
ofctrl = ph->phy_flowctrl;
ASSERT(ph->phy_present);
if (ph->phy_check(ph) == DDI_FAILURE) {
_mii_error(mh, MII_ECHECK);
mh->m_link = LINK_STATE_UNKNOWN;
_mii_notify(mh);
return (DDI_FAILURE);
}
mh->m_link = ph->phy_link;
/* if anything changed, notify! */
if ((mh->m_link != olink) ||
(ph->phy_speed != ospeed) ||
(ph->phy_duplex != oduplex) ||
(ph->phy_flowctrl != ofctrl)) {
_mii_notify(mh);
}
return (DDI_SUCCESS);
}
void
_mii_task(void *_mh)
{
mii_handle_t mh = _mh;
phy_handle_t *ph;
clock_t wait;
clock_t downtime;
mutex_enter(&mh->m_lock);
for (;;) {
/* If detaching, exit the thread. */
if (!mh->m_started) {
break;
}
ph = mh->m_phy;
/*
* If we're suspended or otherwise not supposed to be
* monitoring the link, just go back to sleep.
*
* Theoretically we could power down the PHY, but we
* don't bother. (The link might be used for
* wake-on-lan!) Another option would be to reduce
* power on the PHY if both it and the link partner
* support 10 Mbps mode.
*/
if (mh->m_suspending) {
mh->m_suspended = B_TRUE;
cv_broadcast(&mh->m_cv);
}
if (mh->m_suspended) {
mh->m_suspending = B_FALSE;
cv_wait(&mh->m_cv, &mh->m_lock);
continue;
}
switch (mh->m_tstate) {
case MII_STATE_PROBE:
_mii_probe(mh);
ph = mh->m_phy;
if (!ph->phy_present) {
/*
* If no PHY is found, wait a bit before
* trying the probe again. 10 seconds ought
* to be enough.
*/
wait = 10 * MII_SECOND;
} else {
wait = 0;
}
break;
case MII_STATE_RESET:
if (_mii_reset(mh) == DDI_SUCCESS) {
mh->m_tstate = MII_STATE_START;
wait = 0;
} else {
/*
* If an error occurred, wait a bit and
* try again later.
*/
wait = 10 * MII_SECOND;
}
break;
case MII_STATE_START:
/*
* If an error occurs, we're going to go back to
* probe or reset state. Otherwise we go to run
* state. In all cases we want to wait 1 second
* before doing anything else - either for link to
* settle, or to give other code a chance to run
* while we reset.
*/
if (_mii_start(mh) == DDI_SUCCESS) {
/* reset watchdog to latest */
downtime = ddi_get_lbolt();
mh->m_tstate = MII_STATE_RUN;
} else {
mh->m_tstate = MII_STATE_PROBE;
}
wait = 0;
break;
case MII_STATE_LOOPBACK:
/*
* In loopback mode we don't check anything,
* and just wait for some condition to change.
*/
wait = (clock_t)-1;
break;
case MII_STATE_RUN:
default:
if (_mii_check(mh) == DDI_FAILURE) {
/*
* On error (PHY removed?), wait a
* short bit before reprobing or
* resetting.
*/
wait = MII_SECOND;
mh->m_tstate = MII_STATE_PROBE;
} else if (mh->m_link == LINK_STATE_UP) {
/* got goood link, so reset the watchdog */
downtime = ddi_get_lbolt();
/* rescan again in a second */
wait = MII_SECOND;
} else if ((ddi_get_lbolt() - downtime) >
(drv_usectohz(MII_SECOND * 10))) {
/*
* If we were down for 10 seconds,
* hard reset the PHY.
*/
mh->m_tstate = MII_STATE_RESET;
wait = 0;
} else {
/*
* Otherwise, if we are still down,
* rescan the link much more
* frequently. We might be trying to
* autonegotiate.
*/
wait = MII_SECOND / 4;
}
break;
}
switch (wait) {
case 0:
break;
case (clock_t)-1:
cv_wait(&mh->m_cv, &mh->m_lock);
break;
default:
(void) cv_reltimedwait(&mh->m_cv, &mh->m_lock,
drv_usectohz(wait), TR_CLOCK_TICK);
}
}
mutex_exit(&mh->m_lock);
}