mii.c revision bbb1277b6ec1b0daad4e3ed1a2b891d3e2ece2eb
/*
* 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
* 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.
*/
/*
*
* Utility module to provide a consistent interface to a MAC driver accross
* different implementations of PHY devices
*/
#include <sys/mac_provider.h>
#include <sys/mac_ether.h>
#include "miipriv.h"
#define MII_SECOND 1000000
/* indices into error array */
enum {
MII_EOK = 0,
};
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_tstate_t;
struct mii_handle {
void *m_private;
int m_flags;
int m_error;
/* device name for printing, e.g. "hme0" */
int m_addr;
/* 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;
};
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;
"802.3 MII support",
};
static struct modlinkage modlinkage = {
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}
int
{
}
void
{
/*
* This dumps an error message, but it avoids filling the log with
* repeated error messages.
*/
}
}
/*
* Known list of specific PHY probes.
*/
phy_probe_t _phy_probes[] = {
};
/*
* MII Interface functions
*/
{
char tqname[16];
return (NULL);
}
/* DDI will prepend the driver name */
return (NULL);
}
/*
* Initialize user prefs by loading properties. Ultimately,
* Brussels interfaces would be superior here.
*/
for (int i = 0; i < 32; i++) {
}
return (mh);
}
{
}
void
{
if (pauseable) {
} else {
}
}
void
{
}
void
{
}
void
{
}
}
void
{
case MII_STATE_PROBE:
break;
case MII_STATE_RESET:
case MII_STATE_START:
case MII_STATE_RUN:
/* let monitor thread deal with this */
break;
case MII_STATE_LOOPBACK:
/* loopback is handled synchronously */
(void) _mii_loopback(mh);
break;
}
}
void
{
DDI_SUCCESS) {
"%s: unable to start MII monitoring task",
}
}
}
void
{
/*
* Reset link state to unknown defaults, since we're not
* monitoring it anymore. We'll reprobe all link state later.
*/
/*
* Notify the MAC driver. This will allow it to call back
* into the MAC framework to clear any previous link state.
*/
}
void
{
_mii_probe(mh);
}
void
{
}
int
{
}
{
return (ph->phy_duplex);
}
{
}
{
return (ph->phy_flowctrl);
}
int
{
int cnt = 0;
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 */
cnt++;
}
if (ph->phy_cap_1000_fdx) {
cnt++;
}
if (ph->phy_cap_100_fdx) {
cnt++;
}
if (ph->phy_cap_10_fdx) {
cnt++;
}
if (modes) {
}
return (cnt);
}
{
return (ph->phy_loopback);
}
int
{
int rv;
return (EINVAL);
}
if (rv == DDI_SUCCESS) {
}
}
{
}
int
{
}
/* GLDv3 helpers */
{
int rv = 0;
int cnt;
int 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);
}
return (B_TRUE);
}
switch (cmd) {
case LB_GET_INFO_SIZE:
} else {
}
break;
case LB_GET_INFO:
} else {
}
break;
case LB_GET_MODE:
} else {
}
break;
case LB_SET_MODE:
if (rv != 0)
break;
break;
}
break;
}
if (rv == 0) {
} else {
}
return (B_TRUE);
}
int
{
int err = 0;
if (sz < 1)
return (EINVAL);
case MAC_PROP_ADV_##PROP: \
perm = MAC_PROP_PERM_READ; \
break; \
\
case MAC_PROP_EN_##PROP: \
perm = MAC_PROP_PERM_READ; \
break;
switch (num) {
case MAC_PROP_DUPLEX:
if (sz >= sizeof (link_duplex_t)) {
} else {
}
break;
case MAC_PROP_SPEED:
} else {
}
break;
case MAC_PROP_AUTONEG:
break;
case MAC_PROP_FLOWCTRL:
if (sz >= sizeof (link_flowctrl_t)) {
sizeof (link_flowctrl_t));
} else {
}
break;
default:
break;
}
if (err == 0) {
}
return (err);
}
int
{
if (sz < 1)
return (EINVAL);
/* we don't support changing parameters while in loopback mode */
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:
break;
case MAC_PROP_EN_1000HDX_CAP:
break;
case MAC_PROP_EN_100FDX_CAP:
break;
case MAC_PROP_EN_100HDX_CAP:
break;
case MAC_PROP_EN_100T4_CAP:
break;
case MAC_PROP_EN_10FDX_CAP:
break;
case MAC_PROP_EN_10HDX_CAP:
break;
case MAC_PROP_AUTONEG:
break;
case MAC_PROP_FLOWCTRL:
if (sz < sizeof (link_flowctrl_t)) {
} else {
switch (fc) {
case LINK_FLOWCTRL_NONE:
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) {
} else {
}
break;
/*
* Tell the other side that we can assert
* pause, but we cannot resend.
*/
case LINK_FLOWCTRL_TX:
if (ph->phy_cap_asmpause) {
} else {
}
break;
default:
break;
}
}
}
break;
default:
break;
}
} else if (*capp) {
}
rv = 0;
}
}
return (rv);
}
int
{
int rv = 0;
switch (stat) {
case MAC_STAT_IFSPEED:
break;
case ETHER_STAT_LINK_DUPLEX:
break;
case ETHER_STAT_LINK_AUTONEG:
break;
case ETHER_STAT_XCVR_ID:
break;
case ETHER_STAT_XCVR_INUSE:
break;
case ETHER_STAT_XCVR_ADDR:
break;
case ETHER_STAT_LINK_ASMPAUSE:
break;
case ETHER_STAT_LINK_PAUSE:
break;
case ETHER_STAT_CAP_1000FDX:
break;
case ETHER_STAT_CAP_1000HDX:
break;
case ETHER_STAT_CAP_100FDX:
break;
case ETHER_STAT_CAP_100HDX:
break;
case ETHER_STAT_CAP_10FDX:
break;
case ETHER_STAT_CAP_10HDX:
break;
case ETHER_STAT_CAP_100T4:
break;
case ETHER_STAT_CAP_AUTONEG:
break;
case ETHER_STAT_CAP_PAUSE:
break;
case ETHER_STAT_CAP_ASMPAUSE:
break;
break;
break;
case ETHER_STAT_LP_CAP_100FDX:
break;
case ETHER_STAT_LP_CAP_100HDX:
break;
case ETHER_STAT_LP_CAP_10FDX:
break;
case ETHER_STAT_LP_CAP_10HDX:
break;
case ETHER_STAT_LP_CAP_100T4:
break;
break;
case ETHER_STAT_LP_CAP_PAUSE:
break;
break;
break;
break;
break;
break;
case ETHER_STAT_ADV_CAP_10FDX:
break;
case ETHER_STAT_ADV_CAP_10HDX:
break;
case ETHER_STAT_ADV_CAP_100T4:
break;
break;
case ETHER_STAT_ADV_CAP_PAUSE:
break;
break;
default:
break;
}
return (rv);
}
/*
* PHY support routines. Private to the MII module and the vendor
* specific PHY implementation code.
*/
{
}
void
{
}
int
{
/*
* For our device, make sure its powered up and unisolated.
*/
/*
* Finally reset it.
*/
/*
* 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) {
/* reset completed */
return (DDI_SUCCESS);
}
drv_usecwait(100);
}
return (DDI_FAILURE);
}
int
{
return (DDI_SUCCESS);
}
int
{
/*
* Disable everything to start... we'll add in modes as we go.
*/
bmcr = 0;
switch (ph->phy_loopback) {
case PHY_LB_NONE:
/* We shouldn't be here */
ASSERT(0);
break;
case PHY_LB_INT_PHY:
if (ph->phy_cap_1000_fdx) {
} else if (ph->phy_cap_100_fdx) {
} else if (ph->phy_cap_10_fdx) {
}
break;
case PHY_LB_EXT_10:
break;
case PHY_LB_EXT_100:
break;
case PHY_LB_EXT_1000:
break;
}
case XCVR_1000T:
case XCVR_1000X:
case XCVR_100T2:
break;
}
return (DDI_SUCCESS);
}
int
{
/*
* No loopback overrides, so try to advertise everything
* that is administratively enabled.
*/
/*
* Limit properties to what the hardware can actually support.
*/
#define FILTER_ADV(CAP) \
/*
* 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)) {
"No valid link mode selected. Powering down PHY.");
return (DDI_SUCCESS);
}
bmcr = 0;
gtcr = 0;
if (ph->phy_adv_aneg) {
}
bmcr |= MII_CONTROL_1GB;
ph->phy_adv_100_t4) {
}
}
/* 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) {
}
if (ph->phy_adv_asmpause) {
}
/* 100BASE-T2 */
anar = 0;
if (ph->phy_adv_100_fdx) {
}
if (ph->phy_adv_100_hdx) {
}
} else {
/* 1000BASE-T or 100BASE-X probably */
if (ph->phy_adv_1000_fdx) {
}
if (ph->phy_adv_1000_hdx) {
}
if (ph->phy_adv_100_fdx) {
}
if (ph->phy_adv_100_hdx) {
}
if (ph->phy_adv_100_t4) {
}
if (ph->phy_adv_10_fdx) {
}
if (ph->phy_adv_10_hdx) {
}
if (ph->phy_adv_pause) {
}
if (ph->phy_adv_asmpause) {
}
}
case XCVR_1000T:
case XCVR_1000X:
case XCVR_100T2:
}
/*
* Finally, this will start up autoneg if it is enabled, or
* force link settings otherwise.
*/
return (DDI_SUCCESS);
}
int
{
int debounces = 100;
if (status & MII_STATUS_EXTENDED) {
} 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);
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.
*/
return (DDI_FAILURE);
}
/* We only respect the link flag if we are not in loopback. */
((status & MII_STATUS_LINKUP) == 0)) {
return (DDI_SUCCESS);
}
if ((control & MII_CONTROL_ANE) == 0) {
/*
* 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) {
} else if (control & MII_CONTROL_100MB) {
} else {
}
return (DDI_SUCCESS);
}
/* 1000BASE-X requires autonegotiation */
/* 100BASE-T2 requires autonegotiation */
} 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
#endif
return (DDI_SUCCESS);
} else {
/*
* 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.
*/
(anexp & MII_AN_EXP_LPCANAN)) {
/* check for gige */
!!(msstat & MII_MSSTATUS_LP1000T);
!!(msstat & MII_MSSTATUS_LP1000T_FD);
}
}
/* resolve link pause */
(ph->phy_lp_pause)) {
(ph->phy_lp_pause)) {
} else {
}
} else {
#ifdef DEBUG
#endif
}
return (DDI_SUCCESS);
}
int
{
}
const char *
{
}
const char *
{
}
void
{
char buf[256];
}
/*
* Internal support routines.
*/
void
{
}
}
void
{
/*
* 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.
*/
/* done twice to clear any latched bits */
return;
}
if (bmsr & MII_STATUS_EXTSTAT) {
} else {
extsr = 0;
}
/* setup default handlers */
/*
* We ignore the non-existent 100baseT2 stuff -- no
* known products for it exist.
*/
if (bmsr & MII_STATUS_10) {
}
if (bmsr & MII_STATUS_10_FD) {
}
if (bmsr & MII_STATUS_100T2) {
}
if (bmsr & MII_STATUS_100T2_FD) {
}
if (bmsr & MII_STATUS_100_BASE_T4) {
}
if (bmsr & MII_STATUS_100_BASEX) {
}
if (bmsr & MII_STATUS_100_BASEX_FD) {
}
if (extsr & MII_EXTSTATUS_1000X) {
}
if (extsr & MII_EXTSTATUS_1000X_FD) {
}
if (extsr & MII_EXTSTATUS_1000T) {
}
if (extsr & MII_EXTSTATUS_1000T_FD) {
}
for (int j = 0; _phy_probes[j] != NULL; j++) {
if ((*_phy_probes[j])(ph)) {
break;
}
}
#define INIT_ENABLE(CAP) \
switch (ph->phy_en_flowctrl) {
case LINK_FLOWCTRL_BI:
case LINK_FLOWCTRL_RX:
break;
case LINK_FLOWCTRL_TX:
break;
default:
break;
}
}
void
{
int pri = 0;
int first;
"phy-addr", -1);
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 = 1;
}
/*
* 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;
if (!ph->phy_present)
continue;
/*
* We always try to honor the user configured phy.
*/
pri = 4;
}
/* two reads to clear latched bits */
(pri < 3)) {
/*
* Link present is good. We prefer this over
* a possibly disconnected link.
*/
pri = 3;
}
/*
* All else being equal, minimize change.
*/
pri = 2;
}
if (pri < 1) {
/*
* But make sure we at least select a present PHY.
*/
pri = 1;
}
}
if (new_addr == 0xff) {
} else {
"?%s: Using %s Ethernet PHY at %d: %s %s\n",
}
}
}
int
{
/*
* Reset logic. We want to isolate all the other
* phys that are not in use.
*/
for (int i = 0; i < 32; i++) {
if (!ph->phy_present)
continue;
/* Don't touch our own phy, yet. */
continue;
}
/* If we're resetting the PHY, then we want to notify loss of link */
return (DDI_FAILURE);
}
/* Perform optional mac layer reset. */
}
/* Perform optional mac layer notification. */
if (notify) {
}
return (DDI_SUCCESS);
}
int
{
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
/* Just force loopback to link up. */
return (DDI_SUCCESS);
}
int
{
return (DDI_FAILURE);
}
/* clear the error state since we got a good startup! */
return (DDI_SUCCESS);
}
int
{
int ospeed;
return (DDI_FAILURE);
}
/* if anything changed, notify! */
}
return (DDI_SUCCESS);
}
void
{
for (;;) {
/* If detaching, exit the thread. */
break;
}
/*
* 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) {
}
if (mh->m_suspended) {
continue;
}
case MII_STATE_PROBE:
_mii_probe(mh);
if (!ph->phy_present) {
/*
* If no PHY is found, wait a bit before
* trying the probe again. 10 seconds ought
* to be enough.
*/
} else {
wait = 0;
}
break;
case MII_STATE_RESET:
wait = 0;
} else {
/*
* If an error occurred, wait a bit and
* try again later.
*/
}
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.
*/
/* reset watchdog to latest */
downtime = ddi_get_lbolt();
} else {
}
wait = 0;
break;
case MII_STATE_LOOPBACK:
/*
* In loopback mode we don't check anything,
* and just wait for some condition to change.
*/
break;
case MII_STATE_RUN:
default:
/*
* On error (PHY removed?), wait a
* short bit before reprobing or
* resetting.
*/
wait = MII_SECOND;
/* 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) >
/*
* If we were down for 10 seconds,
* hard reset the PHY.
*/
wait = 0;
} else {
/*
* Otherwise, if we are still down,
* rescan the link much more
* frequently. We might be trying to
* autonegotiate.
*/
}
break;
}
switch (wait) {
case 0:
break;
case (clock_t)-1:
break;
default:
}
}
}