Goto sanos source index
//
// sis900.c
//
// SiS 900/7016 PCI Fast Ethernet driver
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
// Copyright (C) 1999 Silicon Integrated System Corporation.
//
// Modified from the driver which is originally written by Donald Becker.
//
// 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 this skeleton fall under the GPL and must retain
// the authorship (implicit copyright) notice.
//
#include <os/krnl.h>
#include <bitops.h>
#include "sis900.h"
static int max_interrupt_work = 40;
static int multicast_filter_limit = 128;
// Time in ticks before concluding the transmitter is hung
#define TX_TIMEOUT (4*HZ)
// SiS 900 is capable of 32 bits BM DMA
#define SIS900_DMA_MASK 0xffffffff
enum {
SIS_900 = 0,
SIS_7016
};
static struct board board_tbl[] = {
{"SiS", "SiS 900 PCI Fast Ethernet", BUSTYPE_PCI, PCI_UNITCODE(0x1039, 0x0900), 0xffffffff, 0},
{"SiS", "SiS 7016 PCI Fast Ethernet", BUSTYPE_PCI, PCI_UNITCODE(0x1039, 0x7016), 0xffffffff, 0},
{0,}
};
#define HOME 0x0001
#define LAN 0x0002
#define MIX 0x0003
struct mii_chip_info {
const char *name;
unsigned short phy_id0;
unsigned short phy_id1;
unsigned char phy_types;
};
static struct mii_chip_info mii_chip_table[] = {
{"SiS 900 Internal MII PHY", 0x001d, 0x8000, LAN},
{"SiS 7014 Physical Layer Solution", 0x0016, 0xf830, LAN},
{"AMD 79C901 10BASE-T PHY", 0x0000, 0x6B70, LAN},
{"AMD 79C901 HomePNA PHY", 0x0000, 0x6B90, HOME},
{"ICS LAN PHY", 0x0015, 0xF440, LAN},
{"NS 83851 PHY", 0x2000, 0x5C20, MIX},
{"Realtek RTL8201 PHY", 0x0000, 0x8200, LAN},
{0,},
};
struct mii_phy {
struct mii_phy *next;
int phy_addr;
unsigned short phy_id0;
unsigned short phy_id1;
unsigned short status;
unsigned char phy_types;
};
typedef struct bufferdesc {
unsigned long link;
unsigned long cmdsts;
unsigned long bufptr;
};
struct sis900_private {
dev_t devno; // Device number
struct dev *dev; // Device block
unsigned long iobase; // Configured I/O base
unsigned long irq; // Configured IRQ
struct interrupt intr; // Interrupt object for driver
struct dpc dpc; // DPC for driver
struct timer timer; // Link status detection timer
struct eth_addr hwaddr; // MAC address for NIC
struct stats_nic stats;
struct mii_phy *mii;
struct mii_phy *first_mii; // Record the first mii structure
unsigned int cur_phy;
unsigned char autong_complete; // Auto-negotiate complete
unsigned char carrier_ok;
struct event link_up;
struct sem tx_sem; // Semaphore for Tx ring not full
unsigned int cur_rx, dirty_rx; // Producer/comsumer pointers for Tx/Rx ring
unsigned int cur_tx, dirty_tx;
// The saved address of a sent/receive-in-place packet buffer
struct pbuf *tx_pbuf[NUM_TX_DESC];
struct pbuf *rx_pbuf[NUM_RX_DESC];
struct bufferdesc *tx_ring;
struct bufferdesc *rx_ring;
unsigned long tx_ring_dma;
unsigned long rx_ring_dma;
unsigned int tx_full; // The Tx queue is full
};
static unsigned short read_eeprom(long ioaddr, int location);
static unsigned short 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 unsigned short sis900_default_phy(struct dev *dev);
static unsigned short sis900_reset_phy(struct dev *dev, int phy_addr);
static void sis900_check_mode(struct dev *dev, struct mii_phy *mii_phy);
static void sis900_set_mode(long ioaddr, int speed, int duplex);
static void sis900_auto_negotiate(struct dev *dev, int phy_addr);
static void sis900_init_rxfilter (struct dev *dev);
static void sis900_timer(void *arg);
static void sis900_init_tx_ring(struct dev *dev);
static void sis900_init_rx_ring(struct dev *dev);
static void sis630_set_eq(struct dev *dev);
static void sis900_reset(struct dev *dev);
static int sis900_set_rx_mode(struct dev *dev);
static int sis900_rx(struct dev *dev);
static void sis900_finish_xmit(struct dev *dev);
static void sis900_read_mode(struct dev *dev, int *speed, int *duplex);
//
// sis900_get_mac_addr - Get MAC address for stand alone SiS900 model
//
// Older SiS900 and friends, use EEPROM to store MAC address.
// MAC address is read from read_eeprom() into sp->hwaddr.
//
static sis900_get_mac_addr(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
unsigned short signature;
int i;
// Check to see if we have sane EEPROM
signature = (unsigned short) read_eeprom(ioaddr, EEPROMSignature);
if (signature == 0xffff || signature == 0x0000) {
kprintf(KERN_ERR "%s: Error EEPROM read %x\n", dev->name, signature);
return 0;
}
// Get MAC address from EEPROM
for (i = 0; i < 3; i++) {
((unsigned short *)(&sp->hwaddr))[i] = read_eeprom(ioaddr, i + EEPROMMACAddr);
}
return 1;
}
//
// sis630e_get_mac_addr - Get MAC address for SiS630E model
//
// SiS630E model, use APC CMOS RAM to store MAC address.
// APC CMOS RAM is accessed through ISA bridge.
// MAC address is read into sp->hwaddr.
//
static int sis630e_get_mac_addr(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
struct unit *isa_bridge = NULL;
unsigned char reg;
int i;
isa_bridge = lookup_unit(NULL, PCI_UNITCODE(0x1039, 0x0008), PCI_ID_ANY);
if (isa_bridge == NULL) {
kprintf("%s: Can not find ISA bridge\n", dev->name);
return 0;
}
pci_read_buffer(isa_bridge, 0x48, ®, 1);
reg |= 0x40;
pci_write_buffer(isa_bridge, 0x48, ®, 1);
for (i = 0; i < 6; i++) {
outp(0x70, 0x09 + i);
((unsigned char *)(&sp->hwaddr))[i] = inp(0x71);
}
reg &= ~0x40;
pci_write_buffer(isa_bridge, 0x48, ®, 1);
return 1;
}
//
// sis635_get_mac_addr - Get MAC address for SIS635 model
//
// SiS635 model, set MAC Reload Bit to load Mac address from APC
// to rfdr. rfdr is accessed through rfcr. MAC address is read into
// sp->hwaddr.
//
static int sis635_get_mac_addr(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
unsigned long rfcr_save;
unsigned long i;
rfcr_save = inpd(rfcr + ioaddr);
outpd(ioaddr + cr, rfcr_save | RELOAD);
outpd(ioaddr + cr, 0);
// Disable packet filtering before setting filter
outpd(rfcr + ioaddr, rfcr_save & ~RFEN);
// Load MAC addr to filter data register
for (i = 0 ; i < 3 ; i++) {
outpd(ioaddr + rfcr, (i << RFADDR_shift));
*(((unsigned short *) &sp->hwaddr) + i) = inpw(ioaddr + rfdr);
}
// Enable packet filitering
outpd(rfcr + ioaddr, rfcr_save | RFEN);
return 1;
}
//
// sis96x_get_mac_addr: - Get MAC address for SiS962 or SiS963 model
//
// SiS962 or SiS963 model, use EEPROM to store MAC address. And EEPROM
// is shared by LAN and 1394. When access EEPROM, send EEREQ signal to
// hardware first and wait for EEGNT. If EEGNT is ON, EEPROM is permitted
// to be access by LAN, otherwise is not. After MAC address is read from
// EEPROM, send EEDONE signal to refuse EEPROM access by LAN.
// The EEPROM map of SiS962 or SiS963 is different to SiS900.
// The signature field in SiS962 or SiS963 spec is meaningless.
// MAC address is read into sp->hwaddr.
//
static int sis96x_get_mac_addr(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
long ee_addr = ioaddr + mear;
unsigned long waittime = 0;
int i;
outpd(ee_addr, EEREQ);
while (waittime < 2000) {
if (inpd(ee_addr) & EEGNT) {
// Fet MAC address from EEPROM
for (i = 0; i < 3; i++) {
((unsigned short *)(&sp->hwaddr))[i] = read_eeprom(ioaddr, i + EEPROMMACAddr);
}
outpd(ee_addr, EEDONE);
return 1;
} else {
udelay(1);
waittime++;
}
}
outpd(ee_addr, EEDONE);
return 0;
}
//
// sis900_mii_probe - Probe MII PHY for sis900
//
// Search for total of 32 possible mii phy addresses.
// Identify and set current phy if found one,
// return error if it failed to found.
//
static int sis900_mii_probe(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
unsigned short poll_bit = MII_STAT_LINK, status = 0;
unsigned int timeout = get_ticks() + 5 * HZ;
int phy_addr;
sp->mii = NULL;
// Search for total of 32 possible mii phy addresses
for (phy_addr = 0; phy_addr < 32; phy_addr++) {
struct mii_phy *mii_phy = NULL;
unsigned short mii_status;
int i;
mii_phy = NULL;
for (i = 0; i < 2; i++) mii_status = mdio_read(dev, phy_addr, MII_STATUS);
if (mii_status == 0xffff || mii_status == 0x7fff || mii_status == 0x0000) continue;
mii_phy = kmalloc(sizeof(struct mii_phy));
if (!mii_phy) return -ENOMEM;
mii_phy->phy_id0 = mdio_read(dev, phy_addr, MII_PHY_ID0);
mii_phy->phy_id1 = mdio_read(dev, phy_addr, MII_PHY_ID1);
mii_phy->phy_addr = phy_addr;
mii_phy->status = mii_status;
mii_phy->next = sp->mii;
sp->mii = mii_phy;
sp->first_mii = mii_phy;
for (i = 0; mii_chip_table[i].phy_id1; i++) {
if ((mii_phy->phy_id0 == mii_chip_table[i].phy_id0 ) && ((mii_phy->phy_id1 & 0xFFF0) == mii_chip_table[i].phy_id1)) {
mii_phy->phy_types = mii_chip_table[i].phy_types;
if (mii_chip_table[i].phy_types == MIX) {
mii_phy->phy_types = (mii_status & (MII_STAT_CAN_TX_FDX | MII_STAT_CAN_TX)) ? LAN : HOME;
}
kprintf("%s: %s transceiver found at address %d.\n", dev->name, mii_chip_table[i].name, phy_addr);
break;
}
}
if (!mii_chip_table[i].phy_id1) {
kprintf("%s: Unknown PHY transceiver found at address %d.\n", dev->name, phy_addr);
}
}
if (sp->mii == NULL) {
kprintf("%s: No MII transceivers found!\n", dev->name);
return 0;
}
// Select default PHY for mac
sp->mii = NULL;
sis900_default_phy(dev);
// Reset phy if default phy is internal sis900
if ((sp->mii->phy_id0 == 0x001D) && ((sp->mii->phy_id1 & 0xFFF0) == 0x8000)) {
status = sis900_reset_phy(dev, sp->cur_phy);
}
// Workaround for ICS1893 PHY
if ((sp->mii->phy_id0 == 0x0015) && ((sp->mii->phy_id1 & 0xFFF0) == 0xF440)) {
mdio_write(dev, sp->cur_phy, 0x0018, 0xD200);
}
if (status & MII_STAT_LINK) {
while (poll_bit) {
poll_bit ^= (mdio_read(dev, sp->cur_phy, MII_STATUS) & poll_bit);
if (get_ticks() >= timeout) {
kprintf(KERN_WARNING "%s: reset phy and link down now\n", dev->name);
return -ETIMEOUT;
}
}
}
if (dev->unit->revision == SIS630E_900_REV) {
// SiS 630E has some bugs on default value of PHY registers
mdio_write(dev, sp->cur_phy, MII_ANADV, 0x05e1);
mdio_write(dev, sp->cur_phy, MII_CONFIG1, 0x22);
mdio_write(dev, sp->cur_phy, MII_CONFIG2, 0xff00);
mdio_write(dev, sp->cur_phy, MII_MASK, 0xffc0);
}
if (sp->mii->status & MII_STAT_LINK) {
sp->carrier_ok = 1;
} else {
sp->carrier_ok = 0;
}
return 1;
}
//
// sis900_default_phy - Select default PHY for sis900 mac.
//
// Select first detected PHY with link as default.
// If no one is link on, select PHY whose types is HOME as default.
// If HOME doesn't exist, select LAN.
//
static unsigned short sis900_default_phy(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
struct mii_phy *phy = NULL, *phy_home = NULL, *default_phy = NULL;
unsigned short status;
for (phy = sp->first_mii; phy; phy = phy->next) {
status = mdio_read(dev, phy->phy_addr, MII_STATUS);
status = mdio_read(dev, phy->phy_addr, MII_STATUS);
// Link ON & Not select default PHY
if ((status & MII_STAT_LINK) && !(default_phy)) {
default_phy = phy;
} else {
status = mdio_read(dev, phy->phy_addr, MII_CONTROL);
mdio_write(dev, phy->phy_addr, MII_CONTROL, status | MII_CNTL_AUTO | MII_CNTL_ISOLATE);
if (phy->phy_types == HOME) phy_home = phy;
}
}
if ((!default_phy) && phy_home) {
default_phy = phy_home;
} else if (!default_phy) {
default_phy = sp->first_mii;
}
if (sp->mii != default_phy) {
sp->mii = default_phy;
sp->cur_phy = default_phy->phy_addr;
kprintf("%s: Using transceiver found at address %d as default\n", dev->name, sp->cur_phy);
}
status = mdio_read(dev, sp->cur_phy, MII_CONTROL);
status &= (~MII_CNTL_ISOLATE);
mdio_write(dev, sp->cur_phy, MII_CONTROL, status);
status = mdio_read(dev, sp->cur_phy, MII_STATUS);
status = mdio_read(dev, sp->cur_phy, MII_STATUS);
return status;
}
//
// sis900_set_capability - set the media capability of network adapter.
//
// Set the media capability of network adapter according to
// mii status register. It's necessary before auto-negotiate.
//
static void sis900_set_capability(struct dev *dev , struct mii_phy *phy) {
unsigned short cap;
unsigned short status;
status = mdio_read(dev, phy->phy_addr, MII_STATUS);
status = mdio_read(dev, phy->phy_addr, MII_STATUS);
cap = MII_NWAY_CSMA_CD |
((phy->status & MII_STAT_CAN_TX_FDX)? MII_NWAY_TX_FDX : 0) |
((phy->status & MII_STAT_CAN_TX) ? MII_NWAY_TX : 0) |
((phy->status & MII_STAT_CAN_T_FDX) ? MII_NWAY_T_FDX : 0)|
((phy->status & MII_STAT_CAN_T) ? MII_NWAY_T : 0);
mdio_write(dev, phy->phy_addr, MII_ANADV, cap);
}
// Delay between EEPROM clock transitions.
#define eeprom_delay() inpd(ee_addr)
//
// read_eeprom - Read Serial EEPROM
//
// Read Serial EEPROM through EEPROM Access Register.
// Note that location is in word (16 bits) unit
//
static unsigned short read_eeprom(long ioaddr, int location) {
int i;
unsigned short retval = 0;
long ee_addr = ioaddr + mear;
unsigned long read_cmd = location | EEread;
outpd(ee_addr, 0);
eeprom_delay();
outpd(ee_addr, EECS);
eeprom_delay();
// Shift the read command (9) bits out.
for (i = 8; i >= 0; i--) {
unsigned long dataval = (read_cmd & (1 << i)) ? EEDI | EECS : EECS;
outpd(ee_addr, dataval);
eeprom_delay();
outpd(ee_addr, dataval | EECLK);
eeprom_delay();
}
outpd(ee_addr, EECS);
eeprom_delay();
// Read the 16-bits data in
for (i = 16; i > 0; i--) {
outpd(ee_addr, EECS);
eeprom_delay();
outpd(ee_addr, EECS | EECLK);
eeprom_delay();
retval = (retval << 1) | ((inpd(ee_addr) & EEDO) ? 1 : 0);
eeprom_delay();
}
// Terminate the EEPROM access.
outpd(ee_addr, 0);
eeprom_delay();
return retval;
}
// Read and write the MII management registers using software-generated
// serial MDIO protocol. Note that the command bits and data bits are
// send out seperately
#define mdio_delay() inpd(mdio_addr)
static void mdio_idle(long mdio_addr) {
outpd(mdio_addr, MDIO | MDDIR);
mdio_delay();
outpd(mdio_addr, MDIO | MDDIR | MDC);
}
// Syncronize the MII management interface by shifting 32 one bits out.
static void mdio_reset(long mdio_addr) {
int i;
for (i = 31; i >= 0; i--) {
outpd(mdio_addr, MDDIR | MDIO);
mdio_delay();
outpd(mdio_addr, MDDIR | MDIO | MDC);
mdio_delay();
}
}
//
// mdio_read - read MII PHY register
//
// Read MII registers through MDIO and MDC
// using MDIO management frame structure and protocol(defined by ISO/IEC).
// Please see SiS7014 or ICS spec
//
static unsigned short mdio_read(struct dev *dev, int phy_id, int location) {
struct sis900_private *sp = dev->privdata;
long mdio_addr = sp->iobase + mear;
int mii_cmd = MIIread | (phy_id << MIIpmdShift) | (location << MIIregShift);
unsigned short retval = 0;
int i;
mdio_reset(mdio_addr);
mdio_idle(mdio_addr);
for (i = 15; i >= 0; i--) {
int dataval = (mii_cmd & (1 << i)) ? MDDIR | MDIO : MDDIR;
outpd(mdio_addr, dataval);
mdio_delay();
outpd(mdio_addr, dataval | MDC);
mdio_delay();
}
// Read the 16 data bits.
for (i = 16; i > 0; i--) {
outpd(mdio_addr, 0);
mdio_delay();
retval = (retval << 1) | ((inpd(mdio_addr) & MDIO) ? 1 : 0);
outpd(mdio_addr, MDC);
mdio_delay();
}
outpd(mdio_addr, 0x00);
return retval;
}
//
// mdio_write - write MII PHY register
//
// Write MII registers with @value through MDIO and MDC
// using MDIO management frame structure and protocol(defined by ISO/IEC)
// please see SiS7014 or ICS spec
//
static void mdio_write(struct dev *dev, int phy_id, int location, int value) {
struct sis900_private *sp = dev->privdata;
long mdio_addr = sp->iobase + mear;
int mii_cmd = MIIwrite | (phy_id << MIIpmdShift) | (location << MIIregShift);
int i;
mdio_reset(mdio_addr);
mdio_idle(mdio_addr);
// Shift the command bits out.
for (i = 15; i >= 0; i--) {
int dataval = (mii_cmd & (1 << i)) ? MDDIR | MDIO : MDDIR;
outp(mdio_addr, dataval);
mdio_delay();
outp(mdio_addr, dataval | MDC);
mdio_delay();
}
mdio_delay();
// Shift the value bits out.
for (i = 15; i >= 0; i--) {
int dataval = (value & (1 << i)) ? MDDIR | MDIO : MDDIR;
outpd(mdio_addr, dataval);
mdio_delay();
outpd(mdio_addr, dataval | MDC);
mdio_delay();
}
mdio_delay();
// Clear out extra bits.
for (i = 2; i > 0; i--) {
outp(mdio_addr, 0);
mdio_delay();
outp(mdio_addr, MDC);
mdio_delay();
}
outpd(mdio_addr, 0x00);
}
//
// sis900_reset_phy - reset sis900 mii phy.
//
// Some specific phy can't work properly without reset.
// This function will be called during initialization and
// link status change from ON to DOWN.
//
static unsigned short sis900_reset_phy(struct dev *dev, int phy_addr) {
int i = 0;
unsigned short status;
while (i++ < 2) status = mdio_read(dev, phy_addr, MII_STATUS);
mdio_write(dev, phy_addr, MII_CONTROL, MII_CNTL_RESET);
return status;
}
//
// sis900_open - open sis900 device
//
// Do some initialization and start net interface.
// enable interrupts and set sis900 timer.
//
static int sis900_open(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
// Soft reset the chip.
sis900_reset(dev);
// Equalizer workaround Rule
sis630_set_eq(dev);
sis900_init_rxfilter(dev);
sis900_init_tx_ring(dev);
sis900_init_rx_ring(dev);
sis900_set_rx_mode(dev);
// Workaround for EDB
sis900_set_mode(ioaddr, HW_SPEED_10_MBPS, FDX_CAPABLE_HALF_SELECTED);
// Enable all known interrupts by setting the interrupt mask.
outpd(ioaddr + imr, (RxSOVR | RxORN | RxERR | RxOK | TxURN | TxERR | TxIDLE));
outpd(ioaddr + cr, RxENA | inpd(ioaddr + cr));
outpd(ioaddr + ier, IE);
sis900_check_mode(dev, sp->mii);
// Set the timer to switch to check for link beat and perhaps switch
// to an alternate media type.
init_timer(&sp->timer, sis900_timer, dev);
mod_timer(&sp->timer, get_ticks() + HZ);
return 0;
}
//
// sis900_init_rxfilter - Initialize the Rx filter
//
// Set receive filter address to our MAC address
// and enable packet filtering.
//
static void sis900_init_rxfilter(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
unsigned long rfcr_save;
unsigned long i;
rfcr_save = inpd(rfcr + ioaddr);
// Disable packet filtering before setting filter
outpd(rfcr + ioaddr, rfcr_save & ~RFEN);
// Load MAC addr to filter data register
for (i = 0 ; i < 3 ; i++) {
unsigned long w;
w = (unsigned long) *((unsigned short *)(&sp->hwaddr) + i);
outpd(ioaddr + rfcr, (i << RFADDR_shift));
outpd(ioaddr + rfdr, w);
//kprintf("%s: Receive Filter Address[%d]=%x\n", dev->name, i, inpd(ioaddr + rfdr));
}
// Enable packet filitering
outpd(rfcr + ioaddr, rfcr_save | RFEN);
}
//
// sis900_init_tx_ring - Initialize the Tx descriptor ring
//
// Initialize the Tx descriptor ring,
//
static void sis900_init_tx_ring(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
int i;
sp->tx_full = 0;
sp->dirty_tx = sp->cur_tx = 0;
for (i = 0; i < NUM_TX_DESC; i++) {
sp->tx_pbuf[i] = NULL;
sp->tx_ring[i].link = sp->tx_ring_dma + ((i + 1) % NUM_TX_DESC) * sizeof(struct bufferdesc);
sp->tx_ring[i].cmdsts = 0;
sp->tx_ring[i].bufptr = 0;
}
// Load Transmit Descriptor Register
outpd(ioaddr + txdp, sp->tx_ring_dma);
//kprintf("%s: TX descriptor register loaded with: %8.8x\n", dev->name, inpd(ioaddr + txdp));
}
//
// sis900_init_rx_ring - Initialize the Rx descriptor ring
//
// Initialize the Rx descriptor ring,
// and pre-allocate recevie buffers (socket buffer)
//
static void sis900_init_rx_ring(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
int i;
sp->cur_rx = 0;
sp->dirty_rx = 0;
// Init RX descriptor
for (i = 0; i < NUM_RX_DESC; i++) {
sp->rx_pbuf[i] = NULL;
sp->rx_ring[i].link = sp->rx_ring_dma + ((i + 1) % NUM_RX_DESC) * sizeof(struct bufferdesc);
sp->rx_ring[i].cmdsts = 0;
sp->rx_ring[i].bufptr = 0;
}
// Allocate packet buffers
for (i = 0; i < NUM_RX_DESC; i++) {
struct pbuf *p;
p = pbuf_alloc(PBUF_RAW, RX_BUF_SIZE, PBUF_RW);
if (!p) {
// Not enough memory for pbuf, this makes a "hole"
// on the buffer ring, it is not clear how the
// hardware will react to this kind of degenerated
// buffer
break;
}
sp->rx_pbuf[i] = p;
sp->rx_ring[i].cmdsts = RX_BUF_SIZE;
sp->rx_ring[i].bufptr = virt2phys(p->payload);
}
sp->dirty_rx = (unsigned int) (i - NUM_RX_DESC);
// Load Receive Descriptor Register
outpd(ioaddr + rxdp, sp->rx_ring_dma);
//kprintf("%s: RX descriptor register loaded with: %8.8x\n", dev->name, inpd(ioaddr + rxdp));
}
//
// sis630_set_eq - set phy equalizer value for 630 LAN
//
// 630E equalizer workaround rule (Cyrus Huang 08/15)
// PHY register 14h(Test)
// Bit 14: 0 -- Automatically dectect (default)
// 1 -- Manually set Equalizer filter
// Bit 13: 0 -- (Default)
// 1 -- Speed up convergence of equalizer setting
// Bit 9 : 0 -- (Default)
// 1 -- Disable Baseline Wander
// Bit 3~7 -- Equalizer filter setting
// Link ON: Set Bit 9, 13 to 1, Bit 14 to 0
// Then calculate equalizer value
// Then set equalizer value, and set Bit 14 to 1, Bit 9 to 0
// Link Off:Set Bit 13 to 1, Bit 14 to 0
// Calculate Equalizer value:
// When Link is ON and Bit 14 is 0, SIS900PHY will auto-dectect proper equalizer value.
// When the equalizer is stable, this value is not a fixed value. It will be within
// a small range(eg. 7~9). Then we get a minimum and a maximum value(eg. min=7, max=9)
// 0 <= max <= 4 --> set equalizer to max
// 5 <= max <= 14 --> set equalizer to max + 1 or set equalizer to max + 2 if max == min
// max >= 15 --> set equalizer to max + 5 or set equalizer to max + 6 if max == min
static void sis630_set_eq(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
unsigned short reg14h, eq_value = 0, max_value = 0, min_value = 0;
int host_bridge_rev = 0;
int revision;
int i, maxcount = 10;
struct unit *host_bridge;
revision = dev->unit->revision;
if (!(revision == SIS630E_900_REV || revision == SIS630EA1_900_REV ||
revision == SIS630A_900_REV || revision == SIS630ET_900_REV))
return;
host_bridge = lookup_unit(NULL, PCI_UNITCODE(0x1039, 0x0630), PCI_ID_ANY);
if (host_bridge) host_bridge_rev = host_bridge->revision;
if (sp->carrier_ok) {
reg14h = mdio_read(dev, sp->cur_phy, MII_RESV);
mdio_write(dev, sp->cur_phy, MII_RESV, (0x2200 | reg14h) & 0xBFFF);
for (i = 0; i < maxcount; i++) {
eq_value = (0x00F8 & mdio_read(dev, sp->cur_phy, MII_RESV)) >> 3;
if (i == 0) max_value = min_value = eq_value;
max_value = (eq_value > max_value) ? eq_value : max_value;
min_value = (eq_value < min_value) ? eq_value : min_value;
}
// 630E rule to determine the equalizer value
if (revision == SIS630E_900_REV || revision == SIS630EA1_900_REV || revision == SIS630ET_900_REV) {
if (max_value < 5) {
eq_value = max_value;
} else if (max_value >= 5 && max_value < 15) {
eq_value = (max_value == min_value) ? max_value + 2 : max_value + 1;
} else if (max_value >= 15) {
eq_value = (max_value == min_value) ? max_value + 6 : max_value + 5;
}
}
// 630B0&B1 rule to determine the equalizer value
if (revision == SIS630A_900_REV && (host_bridge_rev == SIS630B0 || host_bridge_rev == SIS630B1)) {
if (max_value == 0) {
eq_value = 3;
} else {
eq_value = (max_value + min_value + 1) / 2;
}
}
// Write equalizer value and setting
reg14h = mdio_read(dev, sp->cur_phy, MII_RESV);
reg14h = (reg14h & 0xFF07) | ((eq_value << 3) & 0x00F8);
reg14h = (reg14h | 0x6000) & 0xFDFF;
mdio_write(dev, sp->cur_phy, MII_RESV, reg14h);
} else {
reg14h = mdio_read(dev, sp->cur_phy, MII_RESV);
if (revision == SIS630A_900_REV && (host_bridge_rev == SIS630B0 || host_bridge_rev == SIS630B1)) {
mdio_write(dev, sp->cur_phy, MII_RESV, (reg14h | 0x2200) & 0xBFFF);
} else {
mdio_write(dev, sp->cur_phy, MII_RESV, (reg14h | 0x2000) & 0xBFFF);
}
}
}
//
// sis900_timer - sis900 timer routine
//
// On each timer ticks we check two things,
// link status (ON/OFF) and link mode (10/100/Full/Half)
//
static void sis900_timer(void *arg) {
struct dev *dev = arg;
struct sis900_private *sp = dev->privdata;
struct mii_phy *mii_phy = sp->mii;
unsigned short status;
if (!sp->autong_complete) {
int speed, duplex = 0;
sis900_read_mode(dev, &speed, &duplex);
if (duplex) {
sis900_set_mode(sp->iobase, speed, duplex);
sis630_set_eq(dev);
//sp->carrier_ok = 1; // MRI
}
mod_timer(&sp->timer, get_ticks() + HZ);
return;
}
status = mdio_read(dev, sp->cur_phy, MII_STATUS);
status = mdio_read(dev, sp->cur_phy, MII_STATUS);
if (!sp->carrier_ok) {
// Link OFF -> ON
look_for_link:
// Search for new PHY
status = sis900_default_phy(dev);
mii_phy = sp->mii;
if (status & MII_STAT_LINK) {
sis900_check_mode(dev, mii_phy);
sp->carrier_ok = 1;
}
} else {
// Link ON -> OFF
if (!(status & MII_STAT_LINK)) {
sp->carrier_ok = 0;
kprintf(KERN_WARNING "%s: Media Link Off\n", dev->name);
// Change mode issue
if ((mii_phy->phy_id0 == 0x001D) && ((mii_phy->phy_id1 & 0xFFF0) == 0x8000)) {
sis900_reset_phy(dev, sp->cur_phy);
}
sis630_set_eq(dev);
goto look_for_link;
}
}
mod_timer(&sp->timer, get_ticks() + 5*HZ);
}
//
// sis900_check_mode - check the media mode for sis900
//
// Older driver gets the media mode from mii status output
// register. Now we set our media capability and auto-negotiate
// to get the upper bound of speed and duplex between two ends.
// If the types of mii phy is HOME, it doesn't need to auto-negotiate
// and autong_complete should be set to 1.
//
static void sis900_check_mode(struct dev *dev, struct mii_phy *mii_phy) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
int speed, duplex;
if (mii_phy->phy_types == LAN) {
outpd(ioaddr + cfg, ~EXD & inpd(ioaddr + cfg));
sis900_set_capability(dev , mii_phy);
sis900_auto_negotiate(dev, sp->cur_phy);
} else {
outpd(ioaddr + cfg, EXD | inpd(ioaddr + cfg));
speed = HW_SPEED_HOME;
duplex = FDX_CAPABLE_HALF_SELECTED;
sis900_set_mode(ioaddr, speed, duplex);
sp->autong_complete = 1;
}
}
//
// sis900_set_mode - Set the media mode of mac register.
//
// Set the media mode of mac register txcfg/rxcfg according to
// speed and duplex of phy. Bit EDB_MASTER_EN indicates the EDB
// bus is used instead of PCI bus. When this bit is set 1, the
// Max DMA Burst Size for TX/RX DMA should be no larger than 16
// double words.
//
static void sis900_set_mode(long ioaddr, int speed, int duplex) {
unsigned long tx_flags = 0, rx_flags = 0;
if (inpd(ioaddr + cfg) & EDB_MASTER_EN) {
tx_flags = TxATP | (DMA_BURST_64 << TxMXDMA_shift) | (TX_FILL_THRESH << TxFILLT_shift);
rx_flags = DMA_BURST_64 << RxMXDMA_shift;
} else {
tx_flags = TxATP | (DMA_BURST_512 << TxMXDMA_shift) | (TX_FILL_THRESH << TxFILLT_shift);
rx_flags = DMA_BURST_512 << RxMXDMA_shift;
}
if (speed == HW_SPEED_HOME || speed == HW_SPEED_10_MBPS)
{
rx_flags |= (RxDRNT_10 << RxDRNT_shift);
tx_flags |= (TxDRNT_10 << TxDRNT_shift);
} else {
rx_flags |= (RxDRNT_100 << RxDRNT_shift);
tx_flags |= (TxDRNT_100 << TxDRNT_shift);
}
if (duplex == FDX_CAPABLE_FULL_SELECTED) {
tx_flags |= (TxCSI | TxHBI);
rx_flags |= RxATX;
}
outpd(ioaddr + txcfg, tx_flags);
outpd(ioaddr + rxcfg, rx_flags);
}
//
// sis900_auto_negotiate: Set the Auto-Negotiation Enable/Reset bit.
//
// If the adapter is link-on, set the auto-negotiate enable/reset bit.
// autong_complete should be set to 0 when starting auto-negotiation.
// autong_complete should be set to 1 if we didn't start auto-negotiation.
// sis900_timer will wait for link on again if autong_complete = 0.
//
static void sis900_auto_negotiate(struct dev *dev, int phy_addr) {
struct sis900_private *sp = dev->privdata;
int i = 0;
unsigned long status;
while (i++ < 2) status = mdio_read(dev, phy_addr, MII_STATUS);
if (!(status & MII_STAT_LINK)) {
kprintf(KERN_WARNING "%s: Media Link Off\n", dev->name);
sp->autong_complete = 1;
sp->carrier_ok = 0;
return;
}
// (Re)start AutoNegotiate
mdio_write(dev, phy_addr, MII_CONTROL, MII_CNTL_AUTO | MII_CNTL_RST_AUTO);
sp->autong_complete = 0;
}
//
// sis900_read_mode - read media mode for sis900 internal phy
//
// The capability of remote end will be put in mii register autorec
// after auto-negotiation. Use AND operation to get the upper bound
// of speed and duplex between two ends.
//
static void sis900_read_mode(struct dev *dev, int *speed, int *duplex) {
struct sis900_private *sp = dev->privdata;
struct mii_phy *phy = sp->mii;
int phy_addr = sp->cur_phy;
unsigned long status;
unsigned short autoadv, autorec;
int i = 0;
while (i++ < 2) status = mdio_read(dev, phy_addr, MII_STATUS);
if (!(status & MII_STAT_LINK)) return;
// AutoNegotiate completed
autoadv = mdio_read(dev, phy_addr, MII_ANADV);
autorec = mdio_read(dev, phy_addr, MII_ANLPAR);
status = autoadv & autorec;
*speed = HW_SPEED_10_MBPS;
*duplex = FDX_CAPABLE_HALF_SELECTED;
if (status & (MII_NWAY_TX | MII_NWAY_TX_FDX)) *speed = HW_SPEED_100_MBPS;
if (status & (MII_NWAY_TX_FDX | MII_NWAY_T_FDX)) *duplex = FDX_CAPABLE_FULL_SELECTED;
sp->autong_complete = 1;
// Workaround for Realtek RTL8201 PHY issue
if ((phy->phy_id0 == 0x0000) && ((phy->phy_id1 & 0xFFF0) == 0x8200)) {
if (mdio_read(dev, phy_addr, MII_CONTROL) & MII_CNTL_FDX) *duplex = FDX_CAPABLE_FULL_SELECTED;
if (mdio_read(dev, phy_addr, 0x0019) & 0x01) *speed = HW_SPEED_100_MBPS;
}
kprintf(KERN_INFO "%s: Media Link On %s %s-duplex \n", dev->name, *speed == HW_SPEED_100_MBPS ? "100mbps" : "10mbps", *duplex == FDX_CAPABLE_FULL_SELECTED ? "full" : "half");
// Signal autonegotiate complete and link up
set_event(&sp->link_up);
}
//
// sis900_tx_timeout - sis900 transmit timeout routine
//
// Print transmit timeout status
// Disable interrupts and do some tasks
//
static void sis900_tx_timeout(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
int i;
kprintf(KERN_WARNING "%s: Transmit timeout, status %8.8x %8.8x \n", dev->name, inpd(ioaddr + cr), inpd(ioaddr + isr));
// Disable interrupts by clearing the interrupt mask.
outpd(ioaddr + imr, 0x0000);
// Discard unsent packets
sp->dirty_tx = sp->cur_tx = 0;
for (i = 0; i < NUM_TX_DESC; i++) {
struct pbuf *p = sp->tx_pbuf[i];
if (p) {
pbuf_free(p);
sp->tx_pbuf[i] = 0;
sp->tx_ring[i].cmdsts = 0;
sp->tx_ring[i].bufptr = 0;
sp->stats.tx_dropped++;
}
}
sp->tx_full = 0;
// Load Transmit Descriptor Register
outpd(ioaddr + txdp, sp->tx_ring_dma);
// Enable all known interrupts by setting the interrupt mask.
outpd(ioaddr + imr, (RxSOVR | RxORN | RxERR | RxOK | TxURN | TxERR | TxIDLE));
}
//
// sis900_transmit - sis900 start transmit routine
//
// Set the transmit buffer descriptor,
// and write TxENA to enable transimt state machine.
// Tell upper layer if the buffer is full
//
static int sis900_transmit(struct dev *dev, struct pbuf *p) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
unsigned int entry;
//unsigned int index_cur_tx, index_dirty_tx;
//unsigned int count_dirty_tx;
// Don't transmit data before the complete of auto-negotiation
if (!sp->autong_complete) return -EBUSY;
// Wait for free entry in transmit ring
if (wait_for_object(&sp->tx_sem, TX_TIMEOUT) < 0) {
kprintf(KERN_WARNING "%s: transmit timeout, drop packet\n", dev->name);
sp->stats.tx_dropped++;
return -ETIMEOUT;
}
// Make sure the packet buffer is not fragmented
p = pbuf_linearize(PBUF_RAW, p);
// Calculate the next Tx descriptor entry.
entry = sp->cur_tx % NUM_TX_DESC;
sp->tx_pbuf[entry] = p;
// Set the transmit buffer descriptor and enable Transmit State Machine
sp->tx_ring[entry].bufptr = virt2phys(p->payload);
sp->tx_ring[entry].cmdsts = (OWN | p->tot_len);
outpd(ioaddr + cr, TxENA | inpd(ioaddr + cr));
sp->cur_tx++;
//index_cur_tx = sp->cur_tx;
//index_dirty_tx = sp->dirty_tx;
//for (count_dirty_tx = 0; index_cur_tx != index_dirty_tx; index_dirty_tx++) count_dirty_tx++;
//if (index_cur_tx == index_dirty_tx)
//{
// // Dirty_tx is met in the cycle of cur_tx, buffer full
// sp->tx_full = 1;
// //netif_stop_queue(net_dev);
//}
//else if (count_dirty_tx < NUM_TX_DESC)
//{
// // Typical path, tell upper layer that more transmission is possible
// //netif_start_queue(net_dev);
//} else
//{
// // Buffer full, tell upper layer no more transmission
// sp->tx_full = 1;
// //netif_stop_queue(net_dev);
//}
//kprintf("%s: Queued Tx packet at %p size %d to slot %d.\n", dev->name, p->payload, p->tot_len, entry);
return 0;
}
//
// sis900_dpc - sis900 interrupt handler
//
// The dpc handler does all of the Rx thread work,
// and cleans up after the Tx thread
//
static void sis900_dpc(void *arg) {
struct dev *dev = arg;
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
int boguscnt = max_interrupt_work;
unsigned long status;
while (1) {
status = inpd(ioaddr + isr);
if ((status & (HIBERR | TxURN | TxERR | TxIDLE | RxORN | RxERR | RxOK)) == 0) break;
// Why dow't we break after Tx/Rx case ?? keyword: full-duplex
if (status & (RxORN | RxERR | RxOK)) sis900_rx(dev);
if (status & (TxURN | TxERR | TxIDLE)) sis900_finish_xmit(dev);
// Something strange happened !!!
if (status & HIBERR) {
kprintf("%s: Abnormal interrupt, status 0x%08x.\n", dev->name, status);
break;
}
if (--boguscnt < 0) {
kprintf("%s: Too much work at interrupt, interrupt status = 0x%08x.\n", dev->name, status);
break;
}
}
//kprintf("%s: exiting interrupt, interrupt status = 0x%08x.\n", dev->name, inpd(ioaddr + isr));
eoi(sp->irq);
}
//
// sis900_rx - sis900 receive routine
//
// Process receive interrupt events,
// put buffer to higher layer and refill buffer pool
// Note: This fucntion is called by interrupt handler,
// don't do "too much" work here
//
static int sis900_rx(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
unsigned int entry = sp->cur_rx % NUM_RX_DESC;
unsigned long rx_status = sp->rx_ring[entry].cmdsts;
//kprintf("sis900_rx: cur_rx:%4.4d, dirty_rx:%4.4d status:0x%08x\n", sp->cur_rx, sp->dirty_rx, rx_status);
while (rx_status & OWN) {
unsigned int rx_size;
rx_size = (rx_status & DSIZE) - CRC_SIZE;
if (rx_status & (ABORT | OVERRUN | TOOLONG | RUNT | RXISERR | CRCERR | FAERR)) {
// Corrupted packet received
kprintf("%s: Corrupted packet received, buffer status = 0x%08x.\n", dev->name, rx_status);
sp->stats.rx_errors++;
if (rx_status & OVERRUN) sp->stats.rx_over_errors++;
if (rx_status & (TOOLONG | RUNT)) sp->stats.rx_length_errors++;
if (rx_status & (RXISERR | FAERR)) sp->stats.rx_frame_errors++;
if (rx_status & CRCERR) sp->stats.rx_crc_errors++;
// Reset buffer descriptor state
sp->rx_ring[entry].cmdsts = RX_BUF_SIZE;
} else {
struct pbuf *p;
// This situation should never happen, but due to
// some unknow bugs, it is possible that
// we are working on NULL pbuf :-(
if (sp->rx_pbuf[entry] == NULL) {
kprintf("%s: NULL pointer encountered in Rx ring, skipping\n", dev->name);
break;
}
// Give the packet buffer to upper layers
p = sp->rx_pbuf[entry];
pbuf_realloc(p, rx_size);
if (dev_receive(sp->devno, p) < 0) pbuf_free(p);
// Some network statistics
if ((rx_status & BCAST) == MCAST) sp->stats.multicast++;
sp->stats.rx_bytes += rx_size;
sp->stats.rx_packets++;
// Refill the Rx buffer, what if there is not enought memory for
// new socket buffer ??
p = pbuf_alloc(PBUF_RAW, RX_BUF_SIZE, PBUF_RW);
if (!p) {
// Not enough memory for packet buffer , this makes a "hole"
// on the buffer ring, it is not clear how the
// hardware will react to this kind of degenerated
// buffer
kprintf("%s: Memory squeeze, deferring packet.\n", dev->name);
sp->rx_pbuf[entry] = NULL;
// Reset buffer descriptor state
sp->rx_ring[entry].cmdsts = 0;
sp->rx_ring[entry].bufptr = 0;
sp->stats.rx_dropped++;
break;
}
sp->rx_pbuf[entry] = p;
sp->rx_ring[entry].cmdsts = RX_BUF_SIZE;
sp->rx_ring[entry].bufptr = virt2phys(p->payload);
sp->dirty_rx++;
}
sp->cur_rx++;
entry = sp->cur_rx % NUM_RX_DESC;
rx_status = sp->rx_ring[entry].cmdsts;
}
// Refill the Rx buffer, what if the rate of refilling is slower than
// consuming
for (;sp->cur_rx - sp->dirty_rx > 0; sp->dirty_rx++) {
struct pbuf *p;
entry = sp->dirty_rx % NUM_RX_DESC;
if (sp->rx_pbuf[entry] == NULL) {
p = pbuf_alloc(PBUF_RAW, RX_BUF_SIZE, PBUF_RW);
if (!p) {
// Not enough memory for packet buffer, this makes a "hole"
// on the buffer ring, it is not clear how the
// hardware will react to this kind of degenerated
// buffer
kprintf("%s: Memory squeeze, deferring packet.\n", dev->name);
sp->stats.rx_dropped++;
break;
}
sp->rx_pbuf[entry] = p;
sp->rx_ring[entry].cmdsts = RX_BUF_SIZE;
sp->rx_ring[entry].bufptr = virt2phys(p->payload);
}
}
// Re-enable the potentially idle receive state matchine
outpd(ioaddr + cr, RxENA | inpd(ioaddr + cr));
return 0;
}
//
// sis900_finish_xmit - finish up transmission of packets
//
// Check for error condition and free socket buffer etc
// schedule for more transmission as needed
// Note: This fucntion is called by interrupt handler,
// don't do "too much" work here
//
static void sis900_finish_xmit(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
int freed = 0;
for (; sp->dirty_tx != sp->cur_tx; sp->dirty_tx++) {
struct pbuf *p;
unsigned int entry;
unsigned long tx_status;
entry = sp->dirty_tx % NUM_TX_DESC;
tx_status = sp->tx_ring[entry].cmdsts;
if (tx_status & OWN) {
// The packet is not transmitted yet (owned by hardware) !
// Note: the interrupt is generated only when Tx Machine
// is idle, so this is an almost impossible case
break;
}
if (tx_status & (ABORT | UNDERRUN | OWCOLL)) {
// Packet unsuccessfully transmitted
kprintf("%s: Transmit error, Tx status %8.8x.\n", dev->name, tx_status);
sp->stats.tx_errors++;
if (tx_status & UNDERRUN) sp->stats.tx_fifo_errors++;
if (tx_status & ABORT) sp->stats.tx_aborted_errors++;
if (tx_status & NOCARRIER) sp->stats.tx_carrier_errors++;
if (tx_status & OWCOLL) sp->stats.tx_window_errors++;
} else {
// packet successfully transmitted
sp->stats.collisions += (tx_status & COLCNT) >> 16;
sp->stats.tx_bytes += tx_status & DSIZE;
sp->stats.tx_packets++;
}
// Free the original packet buffer
p = sp->tx_pbuf[entry];
pbuf_free(p);
sp->tx_pbuf[entry] = NULL;
sp->tx_ring[entry].bufptr = 0;
sp->tx_ring[entry].cmdsts = 0;
freed++;
}
//if (sp->tx_full && netif_queue_stopped(net_dev) && sp->cur_tx - sp->dirty_tx < NUM_TX_DESC - 4)
//{
// // The ring is no longer full, clear tx_full and schedule more transmission
// // by netif_wake_queue(net_dev)
// sp->tx_full = 0;
// netif_wake_queue (net_dev);
//}
//kprintf("%s: sis900_finish_xmit released %d packets from tx ring\n", dev->name, freed);
release_sem(&sp->tx_sem, freed);
}
//
// sis900_close - close sis900 device
//
// Disable interrupts, stop the Tx and Rx Status Machine
// free Tx and RX socket buffer
//
static int sis900_close(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
struct pbuf *p;
int i;
// Disable interrupts by clearing the interrupt mask.
outpd(ioaddr + imr, 0x0000);
outpd(ioaddr + ier, 0x0000);
// Stop the chip's Tx and Rx Status Machine
outpd(ioaddr + cr, RxDIS | TxDIS | inpd(ioaddr + cr));
del_timer(&sp->timer);
// Free Tx and RX pbuf
for (i = 0; i < NUM_RX_DESC; i++) {
p = sp->rx_pbuf[i];
if (p) {
pbuf_free(p);
sp->rx_pbuf[i] = 0;
}
}
for (i = 0; i < NUM_TX_DESC; i++) {
p = sp->tx_pbuf[i];
if (p) {
pbuf_free(p);
sp->tx_pbuf[i] = 0;
}
}
return 0;
}
//
// sis900_compute_hashtable_index - compute hashtable index
//
// SiS 900 uses the most sigificant 7 bits to index a 128 bits multicast
// hash table, which makes this function a little bit different from other drivers
// SiS 900 B0 & 635 M/B uses the most significat 8 bits to index 256 bits
// multicast hash table.
//
static unsigned short sis900_compute_hashtable_index(unsigned char *addr, unsigned long revision) {
unsigned long crc = ether_crc(6, addr);
// leave 8 or 7 most siginifant bits
if (revision >= SIS635A_900_REV || revision == SIS900B_900_REV) {
return (unsigned short) (crc >> 24);
} else {
return (unsigned short) (crc >> 25);
}
}
//
// sis900_set_rx_mode - Set SiS900 receive mode
//
// Set SiS900 receive mode for promiscuous, multicast, or broadcast mode.
// And set the appropriate multicast filter.
// Multicast hash table changes from 128 to 256 bits for 635M/B & 900B0.
//
static int sis900_set_rx_mode(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
unsigned short mc_filter[16] = {0}; // 256/128 bits multicast hash table
int i, table_entries;
unsigned long rx_mode;
// 635 Hash Table entires = 256(2^16)
if (dev->unit->revision >= SIS635A_900_REV || dev->unit->revision == SIS900B_900_REV) {
table_entries = 16;
} else {
table_entries = 8;
}
if (dev->netif == NULL) {
// Net interface not yet ready, accept all multicast packet
rx_mode = RFAAB | RFAAM;
for (i = 0; i < table_entries; i++) mc_filter[i] = 0xffff;
} else if (dev->netif->flags & NETIF_PROMISC) {
// Accept any kinds of packets
rx_mode = RFPromiscuous;
for (i = 0; i < table_entries; i++) mc_filter[i] = 0xffff;
} else if ((dev->netif->mccount > multicast_filter_limit) || (dev->netif->flags & NETIF_ALLMULTI)) {
// Too many multicast addresses or accept all multicast packet
rx_mode = RFAAB | RFAAM;
for (i = 0; i < table_entries; i++) mc_filter[i] = 0xffff;
} else {
// Accept Broadcast packet, destination address matchs our MAC address,
// use Receive Filter to reject unwanted MCAST packet
struct mclist *mclist;
rx_mode = RFAAB;
for (i = 0, mclist = dev->netif->mclist; mclist && i < dev->netif->mccount; i++, mclist = mclist->next) {
set_bit(mc_filter, sis900_compute_hashtable_index((unsigned char *) &mclist->hwaddr, dev->unit->revision));
}
}
// Update Multicast Hash Table in Receive Filter
for (i = 0; i < table_entries; i++) {
// Why plus 0x04 ??, That makes the correct value for hash table.
outpd(ioaddr + rfcr, (unsigned long) (0x00000004 + i) << RFADDR_shift);
outpd(ioaddr + rfdr, mc_filter[i]);
}
outpd(ioaddr + rfcr, RFEN | rx_mode);
// sis900 is capatable of looping back packet at MAC level for debugging purpose
if (dev->netif && dev->netif->flags & NETIF_LOOPBACK) {
unsigned long cr_saved;
// We must disable Tx/Rx before setting loopback mode
cr_saved = inpd(ioaddr + cr);
outpd(ioaddr + cr, cr_saved | TxDIS | RxDIS);
// Enable loopback
outpd(ioaddr + txcfg, inpd(ioaddr + txcfg) | TxMLB);
outpd(ioaddr + rxcfg, inpd(ioaddr + rxcfg) | RxATX);
// Restore cr
outpd(ioaddr + cr, cr_saved);
}
return 0;
}
//
// sis900_reset - Reset sis900 MAC
//
// Reset sis900 MAC and wait until finished
// reset through command register
// Change backoff algorithm for 900B0 & 635 M/B
//
static void sis900_reset(struct dev *dev) {
struct sis900_private *sp = dev->privdata;
long ioaddr = sp->iobase;
int i = 0;
unsigned long status = TxRCMP | RxRCMP;
outpd(ioaddr + ier, 0);
outpd(ioaddr + imr, 0);
outpd(ioaddr + rfcr, 0);
outpd(ioaddr + cr, RxRESET | TxRESET | RESET | inpd(ioaddr + cr));
// Check that the chip has finished the reset.
while (status && (i++ < 1000)) status ^= (inpd(isr + ioaddr) & status);
if (dev->unit->revision >= SIS635A_900_REV || dev->unit->revision == SIS900B_900_REV) {
outpd(ioaddr + cfg, PESEL | RND_CNT);
} else {
outpd(ioaddr + cfg, PESEL);
}
}
static int sis900_handler(struct context *ctxt, void *arg) {
struct dev *dev = arg;
struct sis900_private *sp = dev->privdata;
// Queue DPC to service interrupt
//kprintf("%s: interrupt\n", dev->name);
queue_irq_dpc(&sp->dpc, sis900_dpc, dev);
return 0;
}
static int sis900_ioctl(struct dev *dev, int cmd, void *args, size_t size) {
return -ENOSYS;
}
static int sis900_attach(struct dev *dev, struct eth_addr *hwaddr) {
struct sis900_private *sp = dev->privdata;
*hwaddr = sp->hwaddr;
sis900_set_rx_mode(dev);
return 0;
}
static int sis900_detach(struct dev *dev) {
return 0;
}
struct driver sis900_driver = {
"sis900",
DEV_TYPE_PACKET,
sis900_ioctl,
NULL,
NULL,
sis900_attach,
sis900_detach,
sis900_transmit,
sis900_set_rx_mode,
};
int __declspec(dllexport) install(struct unit *unit, char *opts) {
struct sis900_private *sp;
struct dev *dev;
struct board *board;
void *ring_space;
long ioaddr;
long irq;
int rc;
// 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 = get_unit_iobase(unit);
irq = get_unit_irq(unit);
// Enable device and bus mastering
pci_enable_busmastering(unit);
// Allocate private memory
sp = kmalloc(sizeof(struct sis900_private));
if (sp == NULL) return -ENOMEM;
memset(sp, 0, sizeof(struct sis900_private));
// Create new device
sp->devno = dev_make("eth#", &sis900_driver, unit, sp);
if (sp->devno == NODEV) return -ENODEV;
dev = device(sp->devno);
sp->dev = dev;
sp->iobase = ioaddr;
sp->irq = irq;
ring_space = kmalloc(TX_TOTAL_SIZE);
if (!ring_space) return -ENOMEM;
sp->tx_ring = (struct bufferdesc *) ring_space;
sp->tx_ring_dma = virt2phys(ring_space);
ring_space = kmalloc(RX_TOTAL_SIZE);
if (!ring_space) return -ENOMEM;
sp->rx_ring = (struct bufferdesc *) ring_space;
sp->rx_ring_dma = virt2phys(ring_space);
// Get Mac address according to the chip revision
if (unit->revision == SIS630E_900_REV) {
rc = sis630e_get_mac_addr(dev);
} else if (unit->revision > 0x81 && unit->revision <= 0x90) {
rc = sis635_get_mac_addr(dev);
} else if (unit->revision == SIS96x_900_REV) {
rc = sis96x_get_mac_addr(dev);
} else {
rc = sis900_get_mac_addr(dev);
}
if (rc == 0) return -ENODEV;
// Initialize interrupt handler
init_dpc(&sp->dpc);
register_interrupt(&sp->intr, IRQ2INTR(irq), sis900_handler, dev);
enable_irq(irq);
init_sem(&sp->tx_sem, NUM_TX_DESC);
init_event(&sp->link_up, 1, 0);
// 630ET: set the mii access mode as software-mode
if (unit->revision == SIS630ET_900_REV) outpd(ioaddr + cr, ACCESSMODE | inpd(ioaddr + cr));
// Probe for mii transceiver
if (sis900_mii_probe(dev) == 0) return -ENODEV;
// Print some information about our NIC
kprintf(KERN_INFO "%s: %s iobase %#lx irq %d mac %la\n", dev->name, board->productname, ioaddr, irq, &sp->hwaddr);
// Initialize NIC
rc = sis900_open(dev);
if (rc < 0) return rc;
// Wait for link up and autonegotiation complete
wait_for_object(&sp->link_up, 5000);
return 0;
}
int __stdcall start(hmodule_t hmod, int reason, void *reserved2) {
return 1;
}