rge_chip.c revision 5ca61e50c68a7a60dc35cd76831471faa4974d71
/*
* 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.
*/
#include "rge.h"
/*
* Patchable globals:
*
* rge_autorecover
*/
/*
* globals:
*/
/*
*/
#pragma inline(rge_reg_get32)
static uint32_t
{
RGE_TRACE(("rge_reg_get32($%p, 0x%lx)",
}
#pragma inline(rge_reg_put32)
static void
{
RGE_TRACE(("rge_reg_put32($%p, 0x%lx, 0x%x)",
}
#pragma inline(rge_reg_set32)
static void
{
RGE_TRACE(("rge_reg_set32($%p, 0x%lx, 0x%x)",
}
#pragma inline(rge_reg_clr32)
static void
{
RGE_TRACE(("rge_reg_clr32($%p, 0x%lx, 0x%x)",
}
#pragma inline(rge_reg_get16)
static uint16_t
{
RGE_TRACE(("rge_reg_get16($%p, 0x%lx)",
}
#pragma inline(rge_reg_put16)
static void
{
RGE_TRACE(("rge_reg_put16($%p, 0x%lx, 0x%x)",
}
#pragma inline(rge_reg_get8)
static uint8_t
{
RGE_TRACE(("rge_reg_get8($%p, 0x%lx)",
}
#pragma inline(rge_reg_put8)
static void
{
RGE_TRACE(("rge_reg_put8($%p, 0x%lx, 0x%x)",
}
#pragma inline(rge_reg_set8)
static void
{
RGE_TRACE(("rge_reg_set8($%p, 0x%lx, 0x%x)",
}
#pragma inline(rge_reg_clr8)
static void
{
RGE_TRACE(("rge_reg_clr8($%p, 0x%lx, 0x%x)",
}
#pragma no_inline(rge_mii_get16)
{
uint32_t i;
/*
* Waiting for PHY reading OK
*/
for (i = 0; i < PHY_RESET_LOOP; i++) {
drv_usecwait(1000);
if (val32 & PHY_ACCESS_WR_FLAG)
}
return ((uint16_t)~0u);
}
#pragma no_inline(rge_mii_put16)
void
{
uint32_t i;
/*
* Waiting for PHY writing OK
*/
for (i = 0; i < PHY_RESET_LOOP; i++) {
drv_usecwait(1000);
if (!(val32 & PHY_ACCESS_WR_FLAG))
return;
}
}
#pragma no_inline(rge_ephy_put16)
void
{
uint32_t i;
/*
* Waiting for PHY writing OK
*/
for (i = 0; i < PHY_RESET_LOOP; i++) {
drv_usecwait(1000);
if (!(val32 & EPHY_ACCESS_WR_FLAG))
return;
}
}
/*
* Atomically shift a 32-bit word left, returning
* the value it had *before* the shift was applied
*/
#pragma inline(rge_mii_put16)
static uint32_t
{
/* ATOMICALLY */
do {
return (oldval);
}
/*
* PHY operation routines
*/
#if RGE_DEBUGGING
void
{
int i;
for (i = 0; i < 32; ++i) {
}
for (i = 0; i < 32; i += 8)
RGE_DEBUG(("rge_phydump: "
"0x%04x %04x %04x %04x %04x %04x %04x %04x",
}
#endif /* RGE_DEBUGGING */
static void
{
/*
* RTL8169S/8110S PHY has the "PCS bug". Need reset PHY
* every 15 seconds whin link down & advertise is 1000.
*/
if (gig_ctl & MII_1000BT_CTL_ADV_FDX) {
rgep->link_down_count++;
(void) rge_phy_reset(rgep);
rgep->link_down_count = 0;
}
}
}
} else {
rgep->link_down_count = 0;
}
}
/*
* Basic low-level function to reset the PHY.
* Doesn't incorporate any special-case workarounds.
*
* Returns TRUE on success, FALSE if the RESET bit doesn't clear
*/
{
/*
* Set the PHY RESET bit, then wait up to 5 ms for it to self-clear
*/
drv_usecwait(100);
return (B_TRUE);
}
return (B_FALSE);
}
/*
* Synchronise the PHY's speed/duplex/autonegotiation capabilities
* and advertisements with the required settings as specified by the various
* param_* variables that can be poked via the NDD interface.
*
* We always reset the PHY and reprogram *all* the relevant registers,
* not just those changed. This should cause the link to go down, and then
* back up again once the link is stable and autonegotiation (if enabled)
* is complete. We should get a link state change interrupt somewhere along
* the way ...
*
* NOTE: <genlock> must already be held by the caller
*/
void
{
RGE_DEBUG(("rge_phy_update: autoneg %d "
"pause %d asym_pause %d "
"1000fdx %d 1000hdx %d "
"100fdx %d 100hdx %d "
"10fdx %d 10hdx %d ",
/*
* PHY settings are normally based on the param_* variables,
* but if any loopback mode is in effect, that takes precedence.
*
* RGE supports MAC-internal loopback, PHY-internal loopback,
* and External loopback at a variety of speeds (with a special
* cable). In all cases, autoneg is turned OFF, full-duplex
* is turned ON, and the speed/mastership is forced.
*/
switch (rgep->param_loop_mode) {
case RGE_LOOP_NONE:
default:
break;
case RGE_LOOP_INTERNAL_PHY:
case RGE_LOOP_INTERNAL_MAC:
switch (rgep->param_loop_mode) {
case RGE_LOOP_INTERNAL_PHY:
} else {
adv_100fdx = B_TRUE;
}
break;
case RGE_LOOP_INTERNAL_MAC:
} else {
adv_100fdx = B_TRUE;
break;
}
}
RGE_DEBUG(("rge_phy_update: autoneg %d "
"pause %d asym_pause %d "
"1000fdx %d 1000hdx %d "
"100fdx %d 100hdx %d "
"10fdx %d 10hdx %d ",
/*
* We should have at least one technology capability set;
* if not, we select a default of 1000Mb/s full-duplex
*/
} else {
adv_100fdx = B_TRUE;
}
}
/*
* Now transform the adv_* variables into the proper settings
* of the PHY registers ...
*
* If autonegotiation is (now) enabled, we want to trigger
* a new autonegotiation cycle once the PHY has been
* programmed with the capabilities to be advertised.
*
* RTL8169/8110 doesn't support 1000Mb/s half-duplex.
*/
if (adv_autoneg)
if (adv_1000fdx)
else if (adv_1000hdx)
else if (adv_100fdx)
else if (adv_100hdx)
else if (adv_10fdx)
else if (adv_10hdx)
control |= 0;
else
if (adv_1000fdx) {
/*
* Chipset limitation: need set other capabilities to true
*/
adv_100fdx = B_TRUE;
adv_100hdx = B_TRUE;
}
if (adv_1000hdx)
if (adv_100fdx)
if (adv_100hdx)
if (adv_10fdx)
if (adv_10hdx)
if (adv_pause)
if (adv_asym_pause)
/*
* Munge in any other fixed bits we require ...
*/
/*
* Restart the PHY and write the new values. Note the
* time, so that we can say whether subsequent link state
* changes can be attributed to our reprogramming the PHY
*/
/* power up PHY for RTL8168B chipset */
}
}
#pragma no_inline(rge_phy_init)
void
{
/*
* Below phy config steps are copied from the Programming Guide
* (there's no detail comments for these steps.)
*/
case MAC_VER_8169S_D:
case MAC_VER_8169S_E :
break;
case MAC_VER_8169SB:
break;
case MAC_VER_8169SC:
break;
case MAC_VER_8168:
break;
case MAC_VER_8168B_B:
case MAC_VER_8168B_C:
break;
}
}
#pragma no_inline(rge_chip_ident)
void
{
/*
* Read and record MAC version
*/
/*
* Workaround for 8101E_C
*/
}
/*
* Read and record PHY version
*/
val16 &= PHY_VER_MASK;
/* set pci latency timer */
val16 &= 0x0300;
}
/*
* PCIE chipset require the Rx buffer start address must be
* 8-byte alignment and the Rx buffer size must be multiple of 8.
* We'll just use bcopy in receive procedure for the PCIE chipset.
*/
"for this PCIE chipset");
}
}
else
/*
* Initialize other variables.
*/
} else {
}
/* interval to update statistics for polling mode */
/* ensure we are not in polling mode */
RGE_TRACE(("%s: MAC version = %x, PHY version = %x",
}
/*
* Perform first-stage chip (re-)initialisation, using only config-space
* accesses:
*
* + Read the vendor/device/revision/subsystem/cache-line-size registers,
* returning the data in the structure pointed to by <idp>.
* + Enable Memory Space accesses.
* + Enable Bus Mastering according.
*/
#pragma no_inline(rge_chip_cfg_init)
void
{
/*
* Save PCI cache line size and subsystem vendor ID
*/
/*
* Turn on Master Enable (DMA) and IO Enable bits.
* Enable PCI Memory Space accesses
*/
RGE_DEBUG(("rge_chip_cfg_init: vendor 0x%x device 0x%x revision 0x%x",
RGE_DEBUG(("rge_chip_cfg_init: subven 0x%x subdev 0x%x",
RGE_DEBUG(("rge_chip_cfg_init: clsize %d latency %d command 0x%x",
}
#pragma no_inline(rge_chip_reset)
int
{
int i;
/*
* Chip should be in STOP state
*/
/*
* Disable interrupt
*/
/*
* Clear pended interrupt
*/
/*
* Reset chip
*/
/*
* Wait for reset success
*/
for (i = 0; i < CHIP_RESET_LOOP; i++) {
drv_usecwait(10);
if (!(val8 & RT_COMMAND_RESET)) {
return (0);
}
}
return (-1);
}
#pragma no_inline(rge_chip_init)
void
{
/*
* Increase the threshold voltage of RX sensitivity
*/
}
val32 |= 0x7000;
val32 &= 0xffff5fff;
}
/*
* Config MII register
*/
/*
* Enable Rx checksum offload.
* Then for vlan support, we must enable receive vlan de-tagging.
* Otherwise, there'll be checksum error.
*/
}
}
/*
*/
/*
* Set dump tally counter register
*/
/*
* Change to config register write enable mode
*/
/*
*/
} else {
}
/*
* Set receive configuration register
*/
/*
* Set transmit configuration register
*/
/*
*/
/*
* Suggested setting from Realtek
*/
else
/*
* Set multicast register
*/
} else {
}
/*
* Msic register setting:
* -- Missed packet counter: clear it
* -- TimerInt Register
* -- Timer count register
*/
/*
* disable the Unicast Wakeup Frame capability
*/
/*
*/
drv_usecwait(20);
}
/*
* including enabling interrupts
*/
#pragma no_inline(rge_chip_start)
void
{
/*
* Clear statistics
*/
/*
*/
/*
* Enable interrupt
*/
}
rgep->rx_fifo_ovf = 0;
/*
* All done!
*/
}
/*
* rge_chip_stop() -- stop board receiving
*
* Since this function is also invoked by rge_quiesce(), it
* must not block; also, no tracing or logging takes place
* when invoked by rge_quiesce().
*/
#pragma no_inline(rge_chip_stop)
void
{
/*
* Disable interrupt
*/
/*
* Clear pended interrupt
*/
}
/*
*/
if (fault)
else
}
/*
* rge_get_mac_addr() -- get the MAC address on NIC
*/
#pragma inline(rge_get_mac_addr)
static void
{
/*
* Read first 4-byte of mac address
*/
/*
* Read last 2-byte of mac address
*/
}
#pragma inline(rge_set_mac_addr)
static void
{
/*
* Change to config register write enable mode
*/
/*
* Get first 4 bytes of mac address
*/
val32 = p[3];
val32 |= p[2];
val32 |= p[1];
val32 |= p[0];
/*
* Set first 4 bytes of mac address
*/
/*
* Get last 2 bytes of mac address
*/
val32 = p[5];
val32 |= p[4];
/*
* Set last 2 bytes of mac address
*/
/*
*/
}
#pragma inline(rge_set_multi_addr)
static void
{
/*
* Change to config register write enable mode
*/
}
} else {
}
/*
*/
}
}
#pragma inline(rge_set_promisc)
static void
{
else
}
/*
* rge_chip_sync() -- program the chip with the unicast MAC address,
* the multicast hash table, the required level of promiscuity, and
* the current loopback mode ...
*/
#pragma no_inline(rge_chip_sync)
void
{
switch (todo) {
case RGE_GET_MAC:
break;
case RGE_SET_MAC:
/* Reprogram the unicast MAC address(es) ... */
break;
case RGE_SET_MUL:
/* Reprogram the hashed multicast address table ... */
break;
case RGE_SET_PROMISC:
/* Set or clear the PROMISCUOUS mode bit */
break;
default:
break;
}
}
#pragma no_inline(rge_chip_blank)
/* ARGSUSED */
void
{
}
#pragma no_inline(rge_tx_trigger)
void
{
}
#pragma no_inline(rge_tx_trigger)
void
{
int i = 0;
return;
while (regval & DUMP_START) {
drv_usecwait(100);
if (++i > STATS_DUMP_LOOP) {
RGE_DEBUG(("rge h/w statistics dump fail!"));
return;
}
}
/*
* Start H/W statistics dump for RTL8169 chip
*/
}
/*
* ========== Hardware interrupt handler ==========
*/
#pragma inline(rge_wake_factotum)
static void
{
if (rgep->factotum_flag == 0) {
}
}
/*
* rge_intr() -- handle chip interrupts
*/
{
return (DDI_INTR_UNCLAIMED);
}
/*
* Was this interrupt caused by our device...
*/
return (DDI_INTR_UNCLAIMED);
/* indicate it wasn't our interrupt */
}
/*
* Clear interrupt
* For PCIE chipset, we need disable interrupt first.
*/
}
/*
* Calculate optimal polling interval
*/
now = ddi_get_lbolt();
/* number of rx and tx packets in the last tick */
/* restore interrupt mask */
}
/* optimal number of packets in a tick */
} else {
}
/*
* calculate polling interval based on rx and tx packets
* in the last tick
*/
poll_rate = 0;
}
}
/* ensure poll_rate reasonable */
}
if (poll_rate) {
/* move to polling mode */
} else {
}
} else {
/* move to normal mode */
itimer = 0;
}
RGE_DEBUG(("%s: poll: itimer:%d int_mask:0x%x",
/* update timestamp for statistics */
/* reset timer */
}
if (int_status & TIME_OUT_INT) {
}
/* flush post writes */
/*
* Cable link change interrupt
*/
if (int_status & LINK_CHANGE_INT) {
}
if (int_status & RX_FIFO_OVERFLOW_INT) {
/* start rx watchdog timeout detection */
}
} else if (int_status & RGE_RX_INT) {
/* stop rx watchdog timeout detection */
rgep->rx_fifo_ovf = 0;
}
}
/*
* Receive interrupt
*/
if (int_status & RGE_RX_INT)
/*
* Transmit interrupt
*/
if (int_status & TX_ERR_INT) {
}
/*
* System error interrupt
*/
if (int_status & SYS_ERR_INT) {
}
/*
* Re-enable interrupt for PCIE chipset or install new int_mask
*/
if (update_int_mask)
return (DDI_INTR_CLAIMED); /* indicate it was our interrupt */
}
/*
* ========== Factotum, implemented as a softint handler ==========
*/
#pragma no_inline(rge_factotum_link_check)
static boolean_t
{
/*
* Link change.
*/
if (link == LINK_STATE_UP) {
if (media_status & PHY_STATUS_1000MF) {
} else {
(media_status & PHY_STATUS_100M) ?
}
}
return (B_TRUE);
}
return (B_FALSE);
}
/*
* Factotum routine to check for Tx stall, using the 'watchdog' counter
*/
#pragma no_inline(rge_factotum_stall_check)
static boolean_t
{
/*
* Specific check for RX stall ...
*/
return (B_TRUE);
}
/*
* Specific check for Tx stall ...
*
* The 'watchdog' counter is incremented whenever a packet
* is queued, reset to 1 when some (but not all) buffers
* are reclaimed, reset to 0 (disabled) when all buffers
* are reclaimed, and shifted left here. If it exceeds the
* threshold value, the chip is assumed to have stalled and
* is put into the ERROR state. The factotum will then reset
* it on the next pass.
*
* All of which should ensure that we don't get into a state
* where packets are left pending indefinitely!
*/
if (rgep->resched_needed)
if (dogval < rge_watchdog_count)
return (B_FALSE);
return (B_TRUE);
}
/*
* The factotum is woken up when there's something to do that we'd rather
* not do from inside a hardware interrupt handler or high-level cyclic.
* Its two main tasks are:
* reset & restart the chip after an error
* check the link status whenever necessary
*/
#pragma no_inline(rge_chip_factotum)
{
if (rgep->factotum_flag == 0)
return (DDI_INTR_UNCLAIMED);
rgep->factotum_flag = 0;
switch (rgep->rge_chip_state) {
default:
break;
case RGE_CHIP_RUNNING:
break;
case RGE_CHIP_ERROR:
break;
case RGE_CHIP_FAULT:
/*
* Fault detected, time to reset ...
*/
if (rge_autorecover) {
}
break;
}
/*
* If an error is detected, stop the chip now, marking it as
* faulty, so that it will be reset next time through ...
*/
if (error)
/*
* If the link state changed, tell the world about it.
* Note: can't do this while still holding the mutex.
*/
if (linkchg)
return (result);
}
/*
* High-level cyclic handler
*
* This routine schedules a (low-level) softint callback to the
* factotum, and prods the chip to update the status block (which
* will cause a hardware interrupt when complete).
*/
void rge_chip_cyclic(void *arg);
#pragma no_inline(rge_chip_cyclic)
void
rge_chip_cyclic(void *arg)
{
switch (rgep->rge_chip_state) {
default:
return;
case RGE_CHIP_RUNNING:
break;
case RGE_CHIP_FAULT:
case RGE_CHIP_ERROR:
break;
}
}
/*
* ========== Ioctl subfunctions ==========
*/
#if RGE_DEBUGGING || RGE_DO_PPIO
#pragma no_inline(rge_chip_peek_cfg)
static void
{
RGE_TRACE(("rge_chip_peek_cfg($%p, $%p)",
switch (ppd->pp_acc_size) {
case 1:
break;
case 2:
break;
case 4:
break;
case 8:
break;
}
}
#pragma no_inline(rge_chip_poke_cfg)
static void
{
RGE_TRACE(("rge_chip_poke_cfg($%p, $%p)",
switch (ppd->pp_acc_size) {
case 1:
break;
case 2:
break;
case 4:
break;
case 8:
break;
}
}
#pragma no_inline(rge_chip_peek_reg)
static void
{
void *regaddr;
RGE_TRACE(("rge_chip_peek_reg($%p, $%p)",
switch (ppd->pp_acc_size) {
case 1:
break;
case 2:
break;
case 4:
break;
case 8:
break;
}
}
#pragma no_inline(rge_chip_peek_reg)
static void
{
void *regaddr;
RGE_TRACE(("rge_chip_poke_reg($%p, $%p)",
switch (ppd->pp_acc_size) {
case 1:
break;
case 2:
break;
case 4:
break;
case 8:
break;
}
}
#pragma no_inline(rge_chip_peek_mii)
static void
{
RGE_TRACE(("rge_chip_peek_mii($%p, $%p)",
}
#pragma no_inline(rge_chip_poke_mii)
static void
{
RGE_TRACE(("rge_chip_poke_mii($%p, $%p)",
}
#pragma no_inline(rge_chip_peek_mem)
static void
{
void *vaddr;
RGE_TRACE(("rge_chip_peek_rge($%p, $%p)",
switch (ppd->pp_acc_size) {
case 1:
break;
case 2:
break;
case 4:
break;
case 8:
break;
}
RGE_DEBUG(("rge_chip_peek_mem($%p, $%p) peeked 0x%llx from $%p",
}
#pragma no_inline(rge_chip_poke_mem)
static void
{
void *vaddr;
RGE_TRACE(("rge_chip_poke_mem($%p, $%p)",
RGE_DEBUG(("rge_chip_poke_mem($%p, $%p) poking 0x%llx at $%p",
switch (ppd->pp_acc_size) {
case 1:
break;
case 2:
break;
case 4:
break;
case 8:
break;
}
}
#pragma no_inline(rge_pp_ioctl)
static enum ioc_reply
{
switch (cmd) {
default:
/* NOTREACHED */
return (IOC_INVAL);
case RGE_PEEK:
break;
case RGE_POKE:
break;
}
/*
* Validate format of ioctl
*/
return (IOC_INVAL);
return (IOC_INVAL);
/*
* Validate request parameters
*/
switch (ppd->pp_acc_space) {
default:
return (IOC_INVAL);
case RGE_PP_SPACE_CFG:
/*
* Config space
*/
mem_va = 0;
break;
case RGE_PP_SPACE_REG:
/*
* Memory-mapped I/O space
*/
mem_va = 0;
break;
case RGE_PP_SPACE_MII:
/*
* PHY's MII registers
* NB: all PHY registers are two bytes, but the
* addresses increment in ones (word addressing).
* So we scale the address here, then undo the
*/
sizemask = 2;
mem_va = 0;
break;
case RGE_PP_SPACE_RGE:
/*
* RGE data structure!
*/
break;
case RGE_PP_SPACE_STATISTICS:
case RGE_PP_SPACE_TXDESC:
case RGE_PP_SPACE_TXBUFF:
case RGE_PP_SPACE_RXDESC:
case RGE_PP_SPACE_RXBUFF:
/*
* Various DMA_AREAs
*/
switch (ppd->pp_acc_space) {
case RGE_PP_SPACE_TXDESC:
break;
case RGE_PP_SPACE_RXDESC:
break;
case RGE_PP_SPACE_STATISTICS:
break;
}
break;
}
switch (ppd->pp_acc_size) {
default:
return (IOC_INVAL);
case 8:
case 4:
case 2:
case 1:
return (IOC_INVAL);
break;
}
return (IOC_INVAL);
return (IOC_INVAL);
return (IOC_INVAL);
/*
* All OK - go do it!
*/
}
#pragma no_inline(rge_diag_ioctl)
static enum ioc_reply
{
switch (cmd) {
default:
/* NOTREACHED */
return (IOC_INVAL);
case RGE_DIAG:
/*
* Currently a no-op
*/
return (IOC_ACK);
case RGE_PEEK:
case RGE_POKE:
case RGE_PHY_RESET:
return (IOC_RESTART_ACK);
case RGE_SOFT_RESET:
case RGE_HARD_RESET:
/*
* Reset and reinitialise the 570x hardware
*/
return (IOC_ACK);
}
/* NOTREACHED */
}
#endif /* RGE_DEBUGGING || RGE_DO_PPIO */
#pragma no_inline(rge_mii_ioctl)
static enum ioc_reply
{
struct rge_mii_rw *miirwp;
/*
* Validate format of ioctl
*/
return (IOC_INVAL);
return (IOC_INVAL);
/*
* Validate request parameters ...
*/
return (IOC_INVAL);
switch (cmd) {
default:
/* NOTREACHED */
return (IOC_INVAL);
case RGE_MII_READ:
return (IOC_REPLY);
case RGE_MII_WRITE:
return (IOC_ACK);
}
/* NOTREACHED */
}
#pragma no_inline(rge_chip_ioctl)
enum ioc_reply
{
int cmd;
RGE_TRACE(("rge_chip_ioctl($%p, $%p, $%p, $%p)",
switch (cmd) {
default:
/* NOTREACHED */
return (IOC_INVAL);
case RGE_DIAG:
case RGE_PEEK:
case RGE_POKE:
case RGE_PHY_RESET:
case RGE_SOFT_RESET:
case RGE_HARD_RESET:
#if RGE_DEBUGGING || RGE_DO_PPIO
#else
return (IOC_INVAL);
#endif /* RGE_DEBUGGING || RGE_DO_PPIO */
case RGE_MII_READ:
case RGE_MII_WRITE:
}
/* NOTREACHED */
}