Goto sanos source index
//
// rtl8139.c
//
// RealTek RTL8129/RTL8139 PCI NIC network driver
//
// Written 1997-2002 by Donald Becker.
// Ported to sanos 2002 by Michael Ringgaard.
//
// This software may be used and distributed according to the terms of
// the GNU General Public License (GPL), incorporated herein by reference.
// Drivers based on or derived from this code fall under the GPL and must
// retain the authorship, copyright and license notice. This file is not
// a complete program and may only be used when the entire operating
// system is licensed under the GPL.
//
// This driver is for boards based on the RTL8129 and RTL8139 PCI ethernet
// chips.
//
// The author may be reached as becker@scyld.com, or C/O
// Scyld Computing Corporation
// 410 Severn Ave., Suite 210
// Annapolis MD 21403
//
#include <os/krnl.h>
#include <bitops.h>
#define ETH_ZLEN 60 // Min. octets in frame sans FCS
// The user-configurable values
// Maximum events (Rx packets, etc.) to handle at each interrupt
static int max_interrupt_work = 20;
// Maximum number of multicast addresses to filter (vs. Rx-all-multicast).
// The RTL chips use a 64 element hash table based on the Ethernet CRC. It
// is efficient to update the hardware filter, but recalculating the table
// for a long filter list is painful
static int multicast_filter_limit = 32;
// Operational parameters that are set at compile time
// Maximum size of the in-memory receive ring (smaller if no memory)
#define RX_BUF_LEN_IDX 2 // 0=8K, 1=16K, 2=32K, 3=64K
// Size of the Tx bounce buffers -- must be at least (mtu+14+4)
#define TX_BUF_SIZE 1536
// PCI Tuning Parameters
// Threshold is bytes transferred to chip before transmission starts
#define TX_FIFO_THRESH 256 // In bytes, rounded down to 32 byte units
// The following settings are log_2(bytes)-4: 0 = 16 bytes .. 6 = 1024
#define RX_FIFO_THRESH 4 // Rx buffer level before first PCI xfer
#define RX_DMA_BURST 4 // Maximum PCI burst, '4' is 256 bytes
#define TX_DMA_BURST 4 // Calculate as 16 << val
// Operational parameters that usually are not changed
// Time in ticks before concluding the transmitter is hung
#define TX_TIMEOUT (6*HZ)
// Allocation size of Rx buffers with full-sized Ethernet frames.
// This is a cross-driver value that is not a limit,
// but a way to keep a consistent allocation size among drivers.
#define PKT_BUF_SZ 1536
enum board_capability_flags {
HAS_MII_XCVR = 0x01,
HAS_CHIP_XCVR = 0x02,
HAS_LNK_CHNG = 0x04,
HAS_DESC = 0x08
};
#define RTL8129_CAPS HAS_MII_XCVR
#define RTL8139_CAPS HAS_CHIP_XCVR | HAS_LNK_CHNG
#define RTL8139D_CAPS HAS_CHIP_XCVR | HAS_LNK_CHNG | HAS_DESC
static struct board board_tbl[] = {
{"RealTek", "RealTek RTL8139+", BUSTYPE_PCI, PCI_UNITCODE(0x10ec, 0x8139), 0xffffffff, 0, 0, 0x20, 0xff, RTL8139D_CAPS},
{"RealTek", "RealTek RTL8139C Fast Ethernet", BUSTYPE_PCI, PCI_UNITCODE(0x10ec, 0x8139), 0xffffffff, 0, 0, 0x10, 0xff, RTL8139_CAPS},
{"RealTek", "RealTek RTL8129 Fast Ethernet", BUSTYPE_PCI, PCI_UNITCODE(0x10ec, 0x8129), 0xffffffff, 0, 0, 0, 0, RTL8129_CAPS},
{"RealTek", "RealTek RTL8139 Fast Ethernet", BUSTYPE_PCI, PCI_UNITCODE(0x10ec, 0x8139), 0xffffffff, 0, 0, 0, 0, RTL8139_CAPS},
{"RealTek", "RealTek RTL8139B PCI", BUSTYPE_PCI, PCI_UNITCODE(0x10ec, 0x8138), 0xffffffff, 0, 0, 0, 0, RTL8139_CAPS},
{"Accton", "Accton EN-1207D Fast Ethernet Adapter", BUSTYPE_PCI, PCI_UNITCODE(0x1113, 0x1211), 0xffffffff, PCI_UNITCODE(0x1113, 0x9211), 0xffffffff, 0, 0, RTL8139_CAPS},
{"SMC", "SMC1211TX EZCard 10/100 (RealTek RTL8139)", BUSTYPE_PCI, PCI_UNITCODE(0x1113, 0x1211), 0xffffffff, 0, 0, 0, 0, RTL8139_CAPS},
{"D-Link", "D-Link DFE-538TX (RTL8139)", BUSTYPE_PCI, PCI_UNITCODE(0x1186, 0x1300), 0xffffffff, 0, 0, 0, 0, RTL8139_CAPS},
{"LevelOne", "LevelOne FPC-0106Tx (RTL8139)", BUSTYPE_PCI, PCI_UNITCODE(0x018a, 0x0106), 0xffffffff, 0, 0, 0, 0, RTL8139_CAPS},
{"Compaq", "Compaq HNE-300 (RTL8139c)", BUSTYPE_PCI, PCI_UNITCODE(0x021b, 0x8139), 0xffffffff, 0, 0, 0, 0, RTL8139_CAPS},
{"Generic", "Generic RTL8139", BUSTYPE_PCI, 0, 0, 0, 0, 0, 0, RTL8139_CAPS},
{NULL,},
};
// Number of Tx descriptor registers
#define NUM_TX_DESC 4
// Symbolic offsets to registers
enum RTL8139_registers {
MAC0 = 0x00, // Ethernet hardware address
MAR0 = 0x08, // Multicast filter
TxStatus0 = 0x10, // Transmit status (Four 32bit registers)
TxAddr0 = 0x20, // Tx descriptors (also four 32bit)
RxBuf = 0x30,
RxEarlyCnt = 0x34,
RxEarlyStatus = 0x36,
ChipCmd = 0x37,
RxBufPtr = 0x38,
RxBufAddr = 0x3A,
IntrMask = 0x3C,
IntrStatus = 0x3E,
TxConfig = 0x40,
RxConfig = 0x44,
Timer = 0x48, // A general-purpose counter
RxMissed = 0x4C, // 24 bits valid, write clears
Cfg9346 = 0x50,
Config0 = 0x51,
Config1 = 0x52,
FlashReg = 0x54,
GPPinData = 0x58,
GPPinDir = 0x59,
MII_SMI = 0x5A,
HltClk = 0x5B,
MultiIntr = 0x5C,
TxSummary = 0x60,
MII_BMCR = 0x62,
MII_BMSR = 0x64,
NWayAdvert = 0x66,
NWayLPAR = 0x68,
NWayExpansion = 0x6A,
// Undocumented registers, but required for proper operation
FIFOTMS = 0x70, // FIFO Control and test
CSCR = 0x74, // Chip Status and Configuration Register
PARA78 = 0x78,
PARA7c = 0x7c, // Magic transceiver parameter register
};
enum ChipCmdBits {
RxBufEmpty = 0x01,
CmdTxEnb = 0x04,
CmdRxEnb = 0x08,
CmdReset = 0x10,
};
// Interrupt register bits
enum IntrStatusBits {
RxOK = 0x0001,
RxErr = 0x0002,
TxOK = 0x0004,
TxErr = 0x0008,
RxOverflow = 0x0010,
RxUnderrun = 0x0020,
RxFIFOOver = 0x0040,
PCSTimeout = 0x4000,
PCIErr = 0x8000,
};
enum TxStatusBits {
TxHostOwns = 0x00002000,
TxUnderrun = 0x00004000,
TxStatOK = 0x00008000,
TxOutOfWindow = 0x20000000,
TxAborted = 0x40000000,
TxCarrierLost = 0x80000000,
};
enum RxStatusBits {
RxStatusOK = 0x0001,
RxBadAlign = 0x0002,
RxCRCErr = 0x0004,
RxTooLong = 0x0008,
RxRunt = 0x0010,
RxBadSymbol = 0x0020,
RxBroadcast = 0x2000,
RxPhysical = 0x4000,
RxMulticast = 0x8000,
};
// Bits in RxConfig
enum RxConfigBits {
AcceptAllPhys = 0x01,
AcceptMyPhys = 0x02,
AcceptMulticast = 0x04,
AcceptRunt = 0x10,
AcceptErr = 0x20,
AcceptBroadcast = 0x08,
};
enum CSCRBits {
CSCR_LinkOKBit = 0x00400,
CSCR_LinkDownOffCmd = 0x003c0,
CSCR_LinkChangeBit = 0x00800,
CSCR_LinkStatusBits = 0x0f000,
CSCR_LinkDownCmd = 0x0f3c0,
};
// Twister tuning parameters from RealTek.
// Completely undocumented, but required to tune bad links.
#define PARA78_default 0x78fa8388
#define PARA7c_default 0xcb38de43
#define PARA7c_xxx 0xcb38de43
unsigned long param[4][4] = {
{0xcb39de43, 0xcb39ce43, 0xfb38de03, 0xcb38de43},
{0xcb39de43, 0xcb39ce43, 0xcb39ce83, 0xcb39ce83},
{0xcb39de43, 0xcb39ce43, 0xcb39ce83, 0xcb39ce83},
{0xbb39de43, 0xbb39ce43, 0xbb39ce83, 0xbb39ce83}
};
struct nic {
dev_t devno; // Device number
struct dev *dev; // Device block
unsigned short iobase; // Configured I/O base
unsigned short irq; // Configured IRQ
struct interrupt intr; // Interrupt object for driver
struct dpc dpc; // DPC for driver
struct timer timer; // Media selection timer
struct eth_addr hwaddr; // MAC address for NIC
struct board *board;
int flags;
struct stats_nic stats;
int msg_level;
int max_interrupt_work;
// Receive state
unsigned char *rx_ring;
unsigned int cur_rx; // Index into the Rx buffer of next Rx pkt.
unsigned int rx_buf_len; // Size (8K 16K 32K or 64KB) of the Rx ring
// Transmit state
struct sem tx_sem; // Semaphore for Tx ring not full
unsigned int cur_tx;
unsigned int dirty_tx;
unsigned int tx_flag;
struct pbuf *tx_pbuf[NUM_TX_DESC]; // The saved address of a sent-in-place packet
unsigned char *tx_buf[NUM_TX_DESC]; // Tx bounce buffers
unsigned char *tx_bufs; // Tx bounce buffer region
unsigned int trans_start;
// Receive filter state
unsigned int rx_config;
unsigned long mc_filter[2]; // Multicast hash filter
int cur_rx_mode;
int multicast_filter_limit;
// Transceiver state
char phys[4]; // MII device addresses
unsigned short advertising; // NWay media advertisement
char twistie, twist_row, twist_col; // Twister tune state
unsigned char config1;
unsigned char full_duplex; // Full-duplex operation requested
unsigned char duplex_lock;
unsigned char link_speed;
unsigned char media2; // Secondary monitored media port
unsigned char medialock; // Don't sense media type
unsigned char mediasense; // Media sensing in progress
};
static int read_eeprom(long ioaddr, int location, int addr_len);
static int mdio_read(struct dev *dev, int phy_id, int location);
static void mdio_write(struct dev *dev, int phy_id, int location, int val);
static int rtl8139_open(struct dev *dev);
static int rtl8139_close(struct dev *dev);
static void rtl8139_init_ring(struct dev *dev);
static void rtl_hw_start(struct dev *dev);
static void rtl8139_timer(void *arg);
static struct stats_nic *rtl8139_get_stats(struct dev *dev);
static int rtl8139_transmit(struct dev *dev, struct pbuf *p);
static int rtl8139_set_rx_mode(struct dev *dev);
static void rtl8139_timer(void *arg);
static void rtl8139_tx_timeout(struct dev *dev);
static int rtl8139_rx(struct dev *dev);
static void rtl8139_interrupt(int irq, void *dev_instance, struct pt_regs *regs);
static void rtl_error(struct dev *dev, int status, int link_status);
//
// Serial EEPROM section
//
// EEPROM_Ctrl bits
#define EE_SHIFT_CLK 0x04 // EEPROM shift clock
#define EE_CS 0x08 // EEPROM chip select
#define EE_DATA_WRITE 0x02 // EEPROM chip data in
#define EE_WRITE_0 0x00
#define EE_WRITE_1 0x02
#define EE_DATA_READ 0x01 // EEPROM chip data out
#define EE_ENB (0x80 | EE_CS)
// Delay between EEPROM clock transitions.
// No extra delay is needed with 33Mhz PCI, but 66Mhz may change this.
#define eeprom_delay() inpd(ee_addr)
// The EEPROM commands include the alway-set leading bit
#define EE_WRITE_CMD (5)
#define EE_READ_CMD (6)
#define EE_ERASE_CMD (7)
static int read_eeprom(long ioaddr, int location, int addr_len) {
int i;
unsigned retval = 0;
long ee_addr = ioaddr + Cfg9346;
int read_cmd = location | (EE_READ_CMD << addr_len);
outp(ee_addr, EE_ENB & ~EE_CS);
outp(ee_addr, EE_ENB);
// Shift the read command bits out
for (i = 4 + addr_len; i >= 0; i--) {
int dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0;
outp(ee_addr, EE_ENB | dataval);
eeprom_delay();
outp(ee_addr, EE_ENB | dataval | EE_SHIFT_CLK);
eeprom_delay();
}
outp(ee_addr, EE_ENB);
eeprom_delay();
for (i = 16; i > 0; i--) {
outp(ee_addr, EE_ENB | EE_SHIFT_CLK);
eeprom_delay();
retval = (retval << 1) | ((inp(ee_addr) & EE_DATA_READ) ? 1 : 0);
outp(ee_addr, EE_ENB);
eeprom_delay();
}
// Terminate the EEPROM access
outp(ee_addr, ~EE_CS);
return retval;
}
// MII serial management
// Read and write the MII management registers using software-generated
// serial MDIO protocol.
// The maximum data clock rate is 2.5 Mhz. The minimum timing is usually
// met by back-to-back PCI I/O cycles, but we insert a delay to avoid
// "overclocking" issues
#define MDIO_DIR 0x80
#define MDIO_DATA_OUT 0x04
#define MDIO_DATA_IN 0x02
#define MDIO_CLK 0x01
#define MDIO_WRITE0 (MDIO_DIR)
#define MDIO_WRITE1 (MDIO_DIR | MDIO_DATA_OUT)
#define mdio_delay(mdio_addr) inpd(mdio_addr)
static char mii_2_8139_map[8] = {
MII_BMCR, MII_BMSR, 0, 0, NWayAdvert, NWayLPAR, NWayExpansion, 0
};
// Syncronize the MII management interface by shifting 32 one bits out
static void mdio_sync(long mdio_addr) {
int i;
for (i = 32; i >= 0; i--) {
outp(mdio_addr, MDIO_WRITE1);
mdio_delay(mdio_addr);
outp(mdio_addr, MDIO_WRITE1 | MDIO_CLK);
mdio_delay(mdio_addr);
}
}
static int mdio_read(struct dev *dev, int phy_id, int location) {
struct nic *np = (struct nic *) dev->privdata;
long mdio_addr = np->iobase + MII_SMI;
int mii_cmd = (0xf6 << 10) | (phy_id << 5) | location;
int retval = 0;
int i;
if (phy_id > 31) {
// Really a 8139. Use internal registers
return location < 8 && mii_2_8139_map[location] ? inpw(np->iobase + mii_2_8139_map[location]) : 0;
}
mdio_sync(mdio_addr);
// Shift the read command bits out
for (i = 15; i >= 0; i--) {
int dataval = (mii_cmd & (1 << i)) ? MDIO_DATA_OUT : 0;
outp(mdio_addr, MDIO_DIR | dataval);
mdio_delay(mdio_addr);
outp(mdio_addr, MDIO_DIR | dataval | MDIO_CLK);
mdio_delay(mdio_addr);
}
// Read the two transition, 16 data, and wire-idle bits
for (i = 19; i > 0; i--) {
outp(mdio_addr, 0);
mdio_delay(mdio_addr);
retval = (retval << 1) | ((inp(mdio_addr) & MDIO_DATA_IN) ? 1 : 0);
outp(mdio_addr, MDIO_CLK);
mdio_delay(mdio_addr);
}
return (retval >> 1) & 0xffff;
}
static void mdio_write(struct dev *dev, int phy_id, int location, int value) {
struct nic *np = (struct nic *) dev->privdata;
long mdio_addr = np->iobase + MII_SMI;
int mii_cmd = (0x5002 << 16) | (phy_id << 23) | (location << 18) | value;
int i;
if (phy_id > 31) {
// Really a 8139. Use internal registers.
long ioaddr = np->iobase;
if (location == 0) {
outp(ioaddr + Cfg9346, 0xC0);
outpw(ioaddr + MII_BMCR, value);
outp(ioaddr + Cfg9346, 0x00);
} else if (location < 8 && mii_2_8139_map[location]) {
outpw(ioaddr + mii_2_8139_map[location], value);
}
} else {
mdio_sync(mdio_addr);
// Shift the command bits out
for (i = 31; i >= 0; i--) {
int dataval = (mii_cmd & (1 << i)) ? MDIO_WRITE1 : MDIO_WRITE0;
outp(mdio_addr, dataval);
mdio_delay(mdio_addr);
outp(mdio_addr, dataval | MDIO_CLK);
mdio_delay(mdio_addr);
}
// Clear out extra bits
for (i = 2; i > 0; i--) {
outp(mdio_addr, 0);
mdio_delay(mdio_addr);
outp(mdio_addr, MDIO_CLK);
mdio_delay(mdio_addr);
}
}
}
static struct stats_nic *rtl8139_get_stats(struct dev *dev) {
struct nic *tp = (struct nic *) dev->privdata;
long ioaddr = tp->iobase;
tp->stats.rx_missed_errors += inpd(ioaddr + RxMissed);
outpd(ioaddr + RxMissed, 0);
return &tp->stats;
}
// Set or clear the multicast filter
static int rtl8139_set_rx_mode(struct dev *dev) {
struct nic *tp = (struct nic *) dev->privdata;
long ioaddr = tp->iobase;
unsigned long mc_filter[2]; // Multicast hash filter
int rx_mode;
if (!dev->netif) {
// Network interface not attached yet -- accept all multicasts
rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys;
mc_filter[1] = mc_filter[0] = 0xffffffff;
} else if (dev->netif->flags & NETIF_PROMISC) {
// Unconditionally log net taps
kprintf("%s: Promiscuous mode enabled\n", dev->name);
rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys | AcceptAllPhys;
mc_filter[1] = mc_filter[0] = 0xffffffff;
} else if ((dev->netif->mccount > tp->multicast_filter_limit) || (dev->netif->flags & NETIF_ALLMULTI)) {
// Too many to filter perfectly -- accept all multicasts
rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys;
mc_filter[1] = mc_filter[0] = 0xffffffff;
} else {
struct mclist *mclist;
int i;
rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys;
mc_filter[1] = mc_filter[0] = 0;
for (i = 0, mclist = dev->netif->mclist; mclist && i < dev->netif->mccount; i++, mclist = mclist->next) {
set_bit(mc_filter, ether_crc(6, (unsigned char *) &mclist->hwaddr) >> 26);
}
}
// We can safely update without stopping the chip
outpd(ioaddr + RxConfig, tp->rx_config | rx_mode);
tp->mc_filter[0] = mc_filter[0];
tp->mc_filter[1] = mc_filter[1];
outpd(ioaddr + MAR0 + 0, mc_filter[0]);
outpd(ioaddr + MAR0 + 4, mc_filter[1]);
return 0;
}
// Initialize the Rx and Tx rings
static void rtl8139_init_ring(struct dev *dev) {
struct nic *tp = (struct nic *) dev->privdata;
int i;
tp->dirty_tx = tp->cur_tx = 0;
for (i = 0; i < NUM_TX_DESC; i++) {
tp->tx_pbuf[i] = NULL;
tp->tx_buf[i] = &tp->tx_bufs[i * TX_BUF_SIZE];
}
}
// Start the hardware at open or resume
static void rtl_hw_start(struct dev *dev) {
struct nic *tp = (struct nic *) dev->privdata;
long ioaddr = tp->iobase;
int i;
// Soft reset the chip
outp(ioaddr + ChipCmd, CmdReset);
// Check that the chip has finished the reset
for (i = 1000; i > 0; i--) {
if ((inp(ioaddr + ChipCmd) & CmdReset) == 0) break;
}
// Restore our idea of the MAC address
outp(ioaddr + Cfg9346, 0xC0);
outpd(ioaddr + MAC0 + 0, *(unsigned long *)(tp->hwaddr.addr + 0));
outpd(ioaddr + MAC0 + 4, *(unsigned long *)(tp->hwaddr.addr + 4));
// Hmmm, do these belong here?
tp->cur_rx = 0;
// Must enable Tx/Rx before setting transfer thresholds!
outp(ioaddr + ChipCmd, CmdRxEnb | CmdTxEnb);
outpd(ioaddr + RxConfig, tp->rx_config);
// Check this value: the documentation contradicts ifself. Is the
// IFG correct with bit 28:27 zero, or with |0x03000000 ?
outpd(ioaddr + TxConfig, (TX_DMA_BURST << 8));
// This is check_duplex()
if (tp->phys[0] >= 0 || (tp->flags & HAS_MII_XCVR)) {
unsigned short mii_reg5 = mdio_read(dev, tp->phys[0], 5);
if (mii_reg5 != 0xffff) {
if ((mii_reg5 & 0x0100) == 0x0100 || (mii_reg5 & 0x00C0) == 0x0040) {
tp->full_duplex = 1;
}
}
kprintf(KERN_INFO "%s: Setting %s%s-duplex based on auto-negotiated partner ability %4.4x\n",
dev->name,
mii_reg5 == 0 ? "" : (mii_reg5 & 0x0180) ? "100mbps " : "10mbps ",
tp->full_duplex ? "full" : "half", mii_reg5);
}
if (tp->flags & HAS_MII_XCVR) {
// RTL8129 chip
outp(ioaddr + Config1, tp->full_duplex ? 0x60 : 0x20);
}
outp(ioaddr + Cfg9346, 0x00);
outpd(ioaddr + RxBuf, virt2phys(tp->rx_ring));
// Start the chip's Tx and Rx process
outpd(ioaddr + RxMissed, 0);
rtl8139_set_rx_mode(dev);
outp(ioaddr + ChipCmd, CmdRxEnb | CmdTxEnb);
// Enable all known interrupts by setting the interrupt mask
outpw(ioaddr + IntrMask, PCIErr | PCSTimeout | RxUnderrun | RxOverflow | RxFIFOOver | TxErr | TxOK | RxErr | RxOK);
}
static int rtl8139_open(struct dev *dev) {
struct nic *tp = (struct nic *) dev->privdata;
long ioaddr = tp->iobase;
int rx_buf_len_idx;
enable_irq(tp->irq);
init_sem(&tp->tx_sem, NUM_TX_DESC);
// The Rx ring allocation size is 2^N + delta, which is worst-case for
// the kernel binary-buddy allocation. We allocate the Tx bounce buffers
// at the same time to use some of the otherwise wasted space.
// The delta of +16 is required for dribble-over because the receiver does
// not wrap when the packet terminates just beyond the end of the ring
rx_buf_len_idx = RX_BUF_LEN_IDX;
do {
tp->rx_buf_len = 8192 << rx_buf_len_idx;
tp->rx_ring = alloc_pages_linear(PAGES(tp->rx_buf_len + 16 + (TX_BUF_SIZE * NUM_TX_DESC)), 'NIC');
} while (tp->rx_ring == NULL && --rx_buf_len_idx >= 0);
if (tp->rx_ring == NULL) return -ENOMEM;
tp->tx_bufs = tp->rx_ring + tp->rx_buf_len + 16;
rtl8139_init_ring(dev);
tp->full_duplex = tp->duplex_lock;
tp->tx_flag = (TX_FIFO_THRESH << 11) & 0x003f0000;
tp->rx_config = (RX_FIFO_THRESH << 13) | (rx_buf_len_idx << 11) | (RX_DMA_BURST << 8);
rtl_hw_start(dev);
//netif_start_tx_queue(dev);
//kprintf("%s: rtl8139_open() ioaddr %#lx IRQ %d GP Pins %2.2x %s-duplex\n",
// dev->name, ioaddr, tp->irq, inp(ioaddr + GPPinData),
// tp->full_duplex ? "full" : "half");
// Set the timer to switch to check for link beat and perhaps switch
// to an alternate media type
init_timer(&tp->timer, rtl8139_timer, dev);
mod_timer(&tp->timer, get_ticks() + 3*HZ);
return 0;
}
static int rtl8139_close(struct dev *dev) {
struct nic *tp = (struct nic *) dev->privdata;
long ioaddr = tp->iobase;
int i;
//netif_stop_tx_queue(dev);
kprintf("%s: Shutting down ethercard, status was 0x%4.4x\n", dev->name, inpw(ioaddr + IntrStatus));
// Disable interrupts by clearing the interrupt mask
outpw(ioaddr + IntrMask, 0x0000);
// Stop the chip's Tx and Rx DMA processes
outp(ioaddr + ChipCmd, 0x00);
// Update the error counts
tp->stats.rx_missed_errors += inpd(ioaddr + RxMissed);
outpd(ioaddr + RxMissed, 0);
del_timer(&tp->timer);
disable_irq(tp->irq);
for (i = 0; i < NUM_TX_DESC; i++) {
if (tp->tx_pbuf[i]) pbuf_free(tp->tx_pbuf[i]);
tp->tx_pbuf[i] = NULL;
}
kfree(tp->rx_ring);
tp->rx_ring = NULL;
// Green! Put the chip in low-power mode
outp(ioaddr + Cfg9346, 0xC0);
outp(ioaddr + Config1, tp->config1 | 0x03);
outp(ioaddr + HltClk, 'H'); // 'R' would leave the clock running
return 0;
}
static int rtl8139_transmit(struct dev *dev, struct pbuf *p) {
struct nic *tp = (struct nic *) dev->privdata;
long ioaddr = tp->iobase;
int entry;
// Wait for free entry in transmit ring
if (wait_for_object(&tp->tx_sem, TX_TIMEOUT) < 0) {
kprintf(KERN_WARNING "%s: transmit timeout, drop packet\n", dev->name);
tp->stats.tx_dropped++;
return -ETIMEOUT;
}
// Calculate the next Tx descriptor entry
entry = tp->cur_tx % NUM_TX_DESC;
tp->tx_pbuf[entry] = p;
if (p->next || ((unsigned long) (p->payload) & 3)) {
struct pbuf *q;
unsigned char *ptr;
// Must use alignment buffer
q = p;
ptr = tp->tx_buf[entry];
while (q) {
memcpy(ptr, q->payload, q->len);
ptr += q->len;
q = q->next;
}
outpd(ioaddr + TxAddr0 + entry * 4, virt2phys(tp->tx_buf[entry]));
} else {
outpd(ioaddr + TxAddr0 + entry * 4, virt2phys(p->payload));
}
// Note: the chip doesn't have auto-pad!
outpd(ioaddr + TxStatus0 + entry * 4, tp->tx_flag | (p->tot_len >= ETH_ZLEN ? p->tot_len : ETH_ZLEN));
tp->trans_start = get_ticks();
tp->cur_tx++;
//kprintf("%s: Queued Tx packet at %p size %d to slot %d\n", dev->name, p->payload, p->tot_len, entry);
return 0;
}
// Receive packet from nic
static int rtl8139_rx(struct dev *dev) {
struct nic *tp = (struct nic *) dev->privdata;
long ioaddr = tp->iobase;
unsigned char *rx_ring = tp->rx_ring;
unsigned short cur_rx = tp->cur_rx;
//kprintf("%s: In rtl8139_rx(), current %4.4x BufAddr %4.4x, free to %4.4x, Cmd %2.2x\n",
// dev->name, cur_rx, inpw(ioaddr + RxBufAddr),
// inpw(ioaddr + RxBufPtr), inp(ioaddr + ChipCmd));
while ((inp(ioaddr + ChipCmd) & RxBufEmpty) == 0) {
unsigned int ring_offset = cur_rx % tp->rx_buf_len;
unsigned long rx_status = *(unsigned long *)(rx_ring + ring_offset);
unsigned int rx_size = rx_status >> 16; // Includes the CRC
//{
// int i;
// kprintf("%s: rtl8139_rx() status %4.4x, size %4.4x, cur %4.4x\n", dev->name, rx_status, rx_size, cur_rx);
// kprintf("%s: Frame contents ", dev->name);
// for (i = 0; i < 70; i++) kprintf(" %2.2x", rx_ring[ring_offset + i]);
// kprintf("\n");
//}
if (rx_status & (RxBadSymbol | RxRunt | RxTooLong | RxCRCErr | RxBadAlign)) {
kprintf("%s: Ethernet frame had errors, status %8.8x\n", dev->name, rx_status);
if (rx_status == 0xffffffff) {
kprintf("%s: Invalid receive status at ring offset %4.4x\n", dev->name, ring_offset);
rx_status = 0;
}
if (rx_status & RxTooLong) {
kprintf("%s: Oversized Ethernet frame, status %4.4x!\n", dev->name, rx_status);
// The chip hangs here.
// This should never occur, which means that we are screwed when it does
}
tp->stats.rx_errors++;
if (rx_status & (RxBadSymbol | RxBadAlign)) tp->stats.rx_frame_errors++;
if (rx_status & (RxRunt | RxTooLong)) tp->stats.rx_length_errors++;
if (rx_status & RxCRCErr) tp->stats.rx_crc_errors++;
// Reset the receiver, based on RealTek recommendation. (Bug?)
tp->cur_rx = 0;
outp(ioaddr + ChipCmd, CmdTxEnb);
// Reset the multicast list
rtl8139_set_rx_mode(dev);
outp(ioaddr + ChipCmd, CmdRxEnb | CmdTxEnb);
} else {
// Allocate new pbuf
// Omit the four octet CRC from the length
struct pbuf *p;
int pkt_size = rx_size - 4;
p = pbuf_alloc(PBUF_RAW, pkt_size, PBUF_RW);
if (p == NULL) {
kprintf("%s: Memory squeeze, deferring packet.\n", dev->name);
// We should check that some rx space is free.
// If not, free one and mark stats->rx_dropped
tp->stats.rx_dropped++;
break;
}
if (ring_offset + rx_size > tp->rx_buf_len) {
int semi_count = tp->rx_buf_len - ring_offset - 4;
memcpy(p->payload, &rx_ring[ring_offset + 4], semi_count);
memcpy((char *) p->payload + semi_count, rx_ring, pkt_size - semi_count);
} else {
memcpy(p->payload, &rx_ring[ring_offset + 4], pkt_size);
}
// Send packet to upper layer
if (dev_receive(tp->devno, p) < 0) pbuf_free(p);
tp->stats.rx_bytes += pkt_size;
tp->stats.rx_packets++;
}
cur_rx = (cur_rx + rx_size + 4 + 3) & ~3;
outpw(ioaddr + RxBufPtr, cur_rx - 16);
}
//kprintf("%s: Done rtl8139_rx(), current %4.4x BufAddr %4.4x, free to %4.4x, Cmd %2.2x\n",
// dev->name, cur_rx, inpw(ioaddr + RxBufAddr),
// inpw(ioaddr + RxBufPtr), inp(ioaddr + ChipCmd));
tp->cur_rx = cur_rx;
return 0;
}
static void rtl8139_tx_timeout(struct dev *dev) {
struct nic *tp = (struct nic *) dev->privdata;
long ioaddr = tp->iobase;
int status = inpw(ioaddr + IntrStatus);
int mii_reg;
unsigned int i;
kprintf("%s: Transmit timeout, status %2.2x %4.4x media %2.2x\n",
dev->name, inp(ioaddr + ChipCmd), status, inp(ioaddr + GPPinData));
if (status & (TxOK | RxOK)) {
kprintf("%s: RTL8139 Interrupt line blocked, status %x\n", dev->name, status);
}
// Disable interrupts by clearing the interrupt mask
outpw(ioaddr + IntrMask, 0x0000);
// Emit info to figure out what went wrong
kprintf("%s: Tx queue start entry %d dirty entry %d\n", dev->name, tp->cur_tx, tp->dirty_tx);
for (i = 0; i < NUM_TX_DESC; i++) {
kprintf("%s: Tx descriptor %d is %8.8x.%s\n",
dev->name, i, inpd(ioaddr + TxStatus0 + i*4),
i == tp->dirty_tx % NUM_TX_DESC ? " (queue head)" : "");
}
kprintf("%s: MII #%d registers are:", dev->name, tp->phys[0]);
for (mii_reg = 0; mii_reg < 8; mii_reg++) kprintf(" %4.4x", mdio_read(dev, tp->phys[0], mii_reg));
kprintf("\n");
// Dump the unsent Tx packets
for (i = 0; i < NUM_TX_DESC; i++) {
if (tp->tx_pbuf[i]) {
pbuf_free(tp->tx_pbuf[i]);
tp->tx_pbuf[i] = NULL;
tp->stats.tx_dropped++;
}
}
// Reset chip
rtl_hw_start(dev);
// Clear tx ring
tp->dirty_tx = tp->cur_tx = 0;
set_sem(&tp->tx_sem, NUM_TX_DESC);
}
// Error and abnormal or uncommon events handlers
static void rtl_error(struct dev *dev, int status, int link_changed) {
struct nic *tp = (struct nic *) dev->privdata;
long ioaddr = tp->iobase;
kprintf("%s: Abnormal interrupt, status %8.8x\n", dev->name, status);
// Update the error count
tp->stats.rx_missed_errors += inpd(ioaddr + RxMissed);
outpd(ioaddr + RxMissed, 0);
if (status & RxUnderrun) {
// This might actually be a link change event
if ((tp->flags & HAS_LNK_CHNG) && link_changed) {
// Really link-change on new chips
int lpar = inpw(ioaddr + NWayLPAR);
int duplex = (lpar & 0x0100) || (lpar & 0x01C0) == 0x0040 || tp->duplex_lock;
// Do not use MII_BMSR as that clears sticky bit
//if (inpw(ioaddr + GPPinData) & 0x0004)
// netif_link_down(dev);
//else
// netif_link_up(dev);
kprintf("%s: Link changed, link partner %4.4x new duplex %d\n", dev->name, lpar, duplex);
tp->full_duplex = duplex;
// Only count as errors with no link change
status &= ~RxUnderrun;
} else {
// If this does not work, we will do rtl_hw_start
outp(ioaddr + ChipCmd, CmdTxEnb);
rtl8139_set_rx_mode(dev); // Reset the multicast list
outp(ioaddr + ChipCmd, CmdRxEnb | CmdTxEnb);
tp->stats.rx_errors++;
tp->stats.rx_fifo_errors++;
}
}
if (status & (RxOverflow | RxErr | RxFIFOOver)) tp->stats.rx_errors++;
if (status & (PCSTimeout)) tp->stats.rx_length_errors++;
if (status & RxFIFOOver) tp->stats.rx_fifo_errors++;
if (status & RxOverflow) {
tp->stats.rx_over_errors++;
tp->cur_rx = inpw(ioaddr + RxBufAddr) % tp->rx_buf_len;
outpw(ioaddr + RxBufPtr, tp->cur_rx - 16);
}
if (status & PCIErr) {
unsigned long pci_cmd_status;
pci_cmd_status = pci_read_config_dword(dev->unit, PCI_CONFIG_CMD_STAT);
kprintf(KERN_ERR "%s: PCI Bus error %4.4x.\n", dev->name, pci_cmd_status);
}
}
// The interrupt handler does all of the Rx thread work and cleans up after the Tx thread
static void rtl8139_dpc(void *arg) {
struct dev *dev = (struct dev *) arg;
struct nic *np = (struct nic *) dev->privdata;
struct nic *tp = np;
int boguscnt = np->max_interrupt_work;
long ioaddr = tp->iobase;
int link_changed = 0;
while (1) {
int status = inpw(ioaddr + IntrStatus);
// Acknowledge all of the current interrupt sources ASAP, but
// first get an additional status bit from CSCR
if (status & RxUnderrun) link_changed = inpw(ioaddr + CSCR) & CSCR_LinkChangeBit;
outpw(ioaddr + IntrStatus, status);
//kprintf("%s: dpc status=%#4.4x new intstat=%#4.4x\n", dev->name, status, inpw(ioaddr + IntrStatus));
if ((status & (PCIErr | PCSTimeout | RxUnderrun | RxOverflow | RxFIFOOver | TxErr | TxOK | RxErr | RxOK)) == 0) break;
if (status & (RxOK | RxUnderrun | RxOverflow | RxFIFOOver)) {
// Rx interrupt
rtl8139_rx(dev);
}
if (status & (TxOK | TxErr)) {
unsigned int dirty_tx = tp->dirty_tx;
int entries_freed = 0;
while (tp->cur_tx - dirty_tx > 0) {
int entry = dirty_tx % NUM_TX_DESC;
int txstatus = inpd(ioaddr + TxStatus0 + entry * 4);
if (!(txstatus & (TxStatOK | TxUnderrun | TxAborted))) break; // It still hasn't been Txed
// Note: TxCarrierLost is always asserted at 100mbps
if (txstatus & (TxOutOfWindow | TxAborted)) {
// There was an major error, log it
kprintf("%s: Transmit error, Tx status %8.8x\n", dev->name, txstatus);
tp->stats.tx_errors++;
if (txstatus & TxAborted) {
tp->stats.tx_aborted_errors++;
outpd(ioaddr + TxConfig, TX_DMA_BURST << 8);
}
if (txstatus & TxCarrierLost) tp->stats.tx_carrier_errors++;
if (txstatus & TxOutOfWindow) tp->stats.tx_window_errors++;
} else {
//kprintf("%s: Transmit done, Tx status %8.8x\n", dev->name, txstatus);
if (txstatus & TxUnderrun) {
// Add 64 to the Tx FIFO threshold
if (tp->tx_flag < 0x00300000) tp->tx_flag += 0x00020000;
tp->stats.tx_fifo_errors++;
}
tp->stats.collisions += (txstatus >> 24) & 15;
tp->stats.tx_bytes += txstatus & 0x7ff;
tp->stats.tx_packets++;
}
// Free the original pbuf
pbuf_free(tp->tx_pbuf[entry]);
tp->tx_pbuf[entry] = NULL;
entries_freed++;
dirty_tx++;
}
tp->dirty_tx = dirty_tx;
release_sem(&tp->tx_sem, entries_freed);
}
// Check uncommon events with one test
if (status & (PCIErr | PCSTimeout | RxUnderrun | RxOverflow | RxFIFOOver | TxErr | RxErr)) {
if (status == 0xffff) break; // Missing chip!
rtl_error(dev, status, link_changed);
}
if (--boguscnt < 0) {
kprintf("%s: Too much work at interrupt, IntrStatus=0x%4.4x\n", dev->name, status);
// Clear all interrupt sources
outpw(ioaddr + IntrStatus, 0xffff);
break;
}
}
//kprintf("%s: exiting dpc, intr_status=%#4.4x\n", dev->name, inpw(ioaddr + IntrStatus));
eoi(np->irq);
}
static int rtl8139_handler(struct context *ctxt, void *arg) {
struct dev *dev = (struct dev *) arg;
struct nic *np = (struct nic *) dev->privdata;
// Queue DPC to service interrupt
//kprintf("%s: interrupt\n", dev->name);
queue_irq_dpc(&np->dpc, rtl8139_dpc, dev);
return 0;
}
static void rtl8139_timer(void *arg) {
struct dev *dev = (struct dev *) arg;
struct nic *np = (struct nic *) dev->privdata;
long ioaddr = np->iobase;
int next_tick = 60 * HZ;
int mii_reg5 = mdio_read(dev, np->phys[0], 5);
if (!np->duplex_lock && mii_reg5 != 0xffff) {
int duplex = (mii_reg5 & 0x0100) || (mii_reg5 & 0x01C0) == 0x0040;
if (np->full_duplex != duplex) {
np->full_duplex = duplex;
kprintf(KERN_INFO "%s: Using %s-duplex based on MII #%d link partner ability of %4.4x\n",
dev->name, np->full_duplex ? "full" : "half", np->phys[0], mii_reg5);
if (np->flags & HAS_MII_XCVR) {
outp(ioaddr + Cfg9346, 0xC0);
outp(ioaddr + Config1, np->full_duplex ? 0x60 : 0x20);
outp(ioaddr + Cfg9346, 0x00);
}
}
}
if (np->cur_tx - np->dirty_tx > 1 && (get_ticks() - np->trans_start) > TX_TIMEOUT) {
rtl8139_tx_timeout(dev);
}
#if defined(RTL_TUNE_TWISTER)
// This is a complicated state machine to configure the "twister" for
// impedance/echos based on the cable length.
// All of this is magic and undocumented.
if (np->twistie) {
switch(np->twistie) {
case 1:
if (inpw(ioaddr + CSCR) & CSCR_LinkOKBit) {
// We have link beat, let us tune the twister
outpw(ioaddr + CSCR, CSCR_LinkDownOffCmd);
np->twistie = 2;
next_tick = HZ / 10;
} else {
// Just put in some reasonable defaults for when beat returns
outpw(ioaddr + CSCR, CSCR_LinkDownCmd);
outpd(ioaddr + FIFOTMS, 0x20); // Turn on cable test mode
outpd(ioaddr + PARA78, PARA78_default);
outpd(ioaddr + PARA7c, PARA7c_default);
np->twistie = 0; // Bail from future actions
}
break;
case 2:
{
// Read how long it took to hear the echo
int linkcase = inpw(ioaddr + CSCR) & CSCR_LinkStatusBits;
if (linkcase == 0x7000) {
np->twist_row = 3;
} else if (linkcase == 0x3000) {
np->twist_row = 2;
} else if (linkcase == 0x1000) {
np->twist_row = 1;
} else {
np->twist_row = 0;
}
np->twist_col = 0;
np->twistie = 3;
next_tick = HZ / 10;
break;
}
case 3:
// Put out four tuning parameters, one per 100msec
if (np->twist_col == 0) outpw(ioaddr + FIFOTMS, 0);
outpd(ioaddr + PARA7c, param[(int) np->twist_row][(int) np->twist_col]);
next_tick = HZ / 10;
if (++np->twist_col >= 4) {
// For short cables we are done. For long cables (row == 3) check for mistune
np->twistie = (np->twist_row == 3) ? 4 : 0;
}
break;
case 4:
// Special case for long cables: check for mistune
if ((inpw(ioaddr + CSCR) & CSCR_LinkStatusBits) == 0x7000) {
np->twistie = 0;
} else {
outpd(0xfb38de03, ioaddr + PARA7c);
np->twistie = 5;
next_tick = HZ / 10;
}
break;
case 5:
// Retune for shorter cable (column 2)
outpd(ioaddr + FIFOTMS, 0x20);
outpd(ioaddr + PARA78, PARA78_default);
outpd(ioaddr + PARA7c, PARA7c_default);
outpd(ioaddr + FIFOTMS, 0x00);
np->twist_row = 2;
np->twist_col = 0;
np->twistie = 3;
next_tick = HZ / 10;
break;
}
}
#endif
//if (np->flags & HAS_MII_XCVR)
// kprintf("%s: Media selection tick, GP pins %2.2x\n", dev->name, inp(ioaddr + GPPinData));
//else
// kprintf("%s: Media selection tick, Link partner %4.4x\n", dev->name, inpw(ioaddr + NWayLPAR));
//kprintf("%s: Other registers are IntMask %4.4x IntStatus %4.4x RxStatus %4.4x\n",
// dev->name, inpw(ioaddr + IntrMask), inpw(ioaddr + IntrStatus), (int) inpd(ioaddr + RxEarlyStatus));
//kprintf("%s: Chip config %2.2x %2.2x\n", dev->name, inp(ioaddr + Config0), inp(ioaddr + Config1));
mod_timer(&np->timer, get_ticks() + next_tick);
}
static int rtl8139_ioctl(struct dev *dev, int cmd, void *args, size_t size) {
return -ENOSYS;
}
static int rtl8139_attach(struct dev *dev, struct eth_addr *hwaddr) {
struct nic *np = dev->privdata;
*hwaddr = np->hwaddr;
return 0;
}
static int rtl8139_detach(struct dev *dev) {
return 0;
}
struct driver rtl8139_driver = {
"rtl8139",
DEV_TYPE_PACKET,
rtl8139_ioctl,
NULL,
NULL,
rtl8139_attach,
rtl8139_detach,
rtl8139_transmit,
rtl8139_set_rx_mode
};
int __declspec(dllexport) install(struct unit *unit, char *opts) {
struct board *board;
unsigned short ioaddr;
unsigned short irq;
struct dev *dev;
struct nic *np;
int i;
int config1;
// Check license
if (license() != LICENSE_GPL) kprintf(KERN_WARNING "notice: rtl8139 driver is under GPL license\n");
// Determine NIC type
board = lookup_board(board_tbl, unit);
if (!board) return -EIO;
unit->vendorname = board->vendorname;
unit->productname = board->productname;
// Get NIC PCI configuration
ioaddr = (unsigned short) get_unit_iobase(unit);
irq = irq = (unsigned short) get_unit_irq(unit);
// Allocate private memory
np = kmalloc(sizeof(struct nic));
if (np == NULL) return -ENOMEM;
memset(np, 0, sizeof(struct nic));
// Enable bus mastering
pci_enable_busmastering(unit);
// Create new device
np->devno = dev_make("eth#", &rtl8139_driver, unit, np);
if (np->devno == NODEV) return -ENODEV;
dev = device(np->devno);
init_dpc(&np->dpc);
register_interrupt(&np->intr, IRQ2INTR(irq), rtl8139_handler, dev);
// Bring the chip out of low-power mode
config1 = inp(ioaddr + Config1);
if (board->flags & HAS_MII_XCVR) {
// RTL8129 chip
outp(ioaddr + Config1, config1 & ~0x03);
}
{
int addr_len = read_eeprom(ioaddr, 0, 8) == 0x8129 ? 8 : 6;
for (i = 0; i < 3; i++) {
((unsigned short *)(np->hwaddr.addr))[i] = read_eeprom(ioaddr, i + 7, addr_len);
}
}
kprintf(KERN_INFO "%s: %s iobase 0x%x irq %d mac %la\n", dev->name, unit->productname, ioaddr, irq, &np->hwaddr);
np->dev = dev;
np->iobase = ioaddr;
np->irq = irq;
np->board = board;
np->flags = board->flags;
np->max_interrupt_work = max_interrupt_work;
np->multicast_filter_limit = multicast_filter_limit;
np->config1 = 0;
// Find the connected MII xcvrs.
if (np->flags & HAS_MII_XCVR) {
int phy, phy_idx = 0;
for (phy = 0; phy < 32 && phy_idx < sizeof(np->phys); phy++) {
int mii_status = mdio_read(dev, phy, 1);
if (mii_status != 0xffff && mii_status != 0x0000) {
np->phys[phy_idx++] = phy;
np->advertising = mdio_read(dev, phy, 4);
kprintf("%s: MII transceiver %d status 0x%4.4x advertising %4.4x\n", dev->name, phy, mii_status, np->advertising);
}
}
if (phy_idx == 0) {
kprintf("%s: No MII transceivers found! Assuming SYM transceiver\n", dev->name);
np->phys[0] = 32;
}
} else {
np->phys[0] = 32;
}
// Put the chip into low-power mode
outp(ioaddr + Cfg9346, 0xC0);
if (np->flags & HAS_MII_XCVR) {
// RTL8129 chip
outp(ioaddr + Config1, 0x03);
}
outp(ioaddr + HltClk, 'H'); // 'R' would leave the clock running
// Set options
if (opts) {
np->full_duplex = get_num_option(opts, "fullduplex", 0);
np->link_speed = get_num_option(opts, "linkspeed", 0);
}
if (np->full_duplex) {
// Changing the MII-advertised media might prevent re-connection
np->duplex_lock = 1;
kprintf("%s: Media type forced to Full Duplex\n", dev->name);
}
if (np->link_speed) {
np->medialock = 1;
kprintf("%s: Forcing %dmbps %s-duplex operation\n", dev->name, np->link_speed, np->full_duplex ? "full" : "half");
mdio_write(dev, np->phys[0], 0,
((np->link_speed == 100) ? 0x2000 : 0) | // 100mbps?
(np->full_duplex ? 0x0100 : 0)); // Full duplex?
}
return rtl8139_open(dev);
}
int __stdcall start(hmodule_t hmod, int reason, void *reserved2) {
return 1;
}