/*******************************************************************
* 8139too.c: A RealTek RTL-8139 Fast Ethernet driver for Linux
* Thanks: ¢一天℃( QQ:185687231)
* Fix history: wenxy([email protected]), 20070927, p.m.
*
********************************************************************/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/rtnetlink.h>
#include <linux/delay.h>
#include <linux/ethtool.h>
#include <linux/version.h>
#include <linux/proc_fs.h>
#include <linux/wireless.h>
#include <asm/uaccess.h> //for copy_from_user
#include <asm/io.h>
MODULE_LICENSE("GPL");
#define MODNAME "8139too"
/* Maximum events (Rx packets, etc.) to handle at each interrupt. */
static int multicast_filter_limit = 32;
/* Size of the in-memory receive ring. */
#define RX_BUF_LEN_IDX 2 /* 0==8K, 1==16K, 2==32K, 3==64K */
#define RX_BUF_LEN (8192 << RX_BUF_LEN_IDX)
#define RX_BUF_PAD 16
#define RX_BUF_WRAP_PAD 2048 /* spare padding to handle lack of packet wrap */
#define RX_BUF_TOT_LEN (RX_BUF_LEN + RX_BUF_PAD + RX_BUF_WRAP_PAD)
/* Number of Tx descriptor registers. */
#define NUM_TX_DESC 4
#define TX_BUF_SIZE 1536
#define TX_BUF_TOT_LEN (TX_BUF_SIZE * NUM_TX_DESC)
#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, 7==end of packet. */
#define RX_FIFO_THRESH 0 /* Rx buffer level before first PCI xfer. */
#define RX_DMA_BURST 7
#define TX_DMA_BURST 6
static struct pci_device_id rtl8139_pci_tbl[] __devinitdata = {
{0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1},
{PCI_ANY_ID, 0x8139, 0x10ec, 0x8139, 0, 0,0 },
{0,}
};
MODULE_DEVICE_TABLE (pci, rtl8139_pci_tbl);
/* Symbolic offsets to registers. */
enum RTL8139_registers {
MAC0 = 0, /* Ethernet hardware address. */
MAR0 = 8, /* 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,
RxMissed = 0x4C, /* 24 bits valid, write clears. */
MultiIntr = 0x5C,
};
enum ClearBitMasks {
MultiIntrClear = 0xF000,
ChipCmdClear = 0xE2,
};
enum ChipCmdBits {
CmdReset = 0x10,
CmdRxEnb = 0x08,
CmdTxEnb = 0x04,
RxBufEmpty = 0x01,
};
enum IntrStatusBits {
PCIErr = 0x8000,
PCSTimeout = 0x4000,
RxFIFOOver = 0x40,
RxUnderrun = 0x20,
RxOverflow = 0x10,
TxErr = 0x08,
TxOK = 0x04,
RxErr = 0x02,
RxOK = 0x01,
};
enum TxStatusBits {
TxHostOwns = 0x2000,
TxUnderrun = 0x4000,
TxStatOK = 0x8000,
TxOutOfWindow = 0x20000000,
TxAborted = 0x40000000,
};
enum RxStatusBits {
RxMulticast = 0x8000,
RxPhysical = 0x4000,
RxBroadcast = 0x2000,
RxBadSymbol = 0x0020,
RxRunt = 0x0010,
RxTooLong = 0x0008,
RxCRCErr = 0x0004,
RxBadAlign = 0x0002,
RxStatusOK = 0x0001,
};
/* Bits in RxConfig. */
enum rx_mode_bits {
AcceptErr = 0x20,
AcceptRunt = 0x10,
AcceptBroadcast = 0x08,
AcceptMulticast = 0x04,
AcceptMyPhys = 0x02,
AcceptAllPhys = 0x01,
};
/* Bits in TxConfig. */
enum tx_config_bits {
TxIFG1 = (1 << 25), /* Interframe Gap Time */
TxIFG0 = (1 << 24), /* Enabling these bits violates IEEE 802.3 */
TxLoopBack = (1 << 18) | (1 << 17), /* enable loopback test mode */
TxCRC = (1 << 16), /* DISABLE appending CRC to end of Tx packets */
TxClearAbt = (1 << 0), /* Clear abort (WO) */
TxDMAShift = 8, /* DMA burst value (0-7) is shift this many bits */
TxVersionMask = 0x7C800000, /* mask out version bits 30-26, 23 */
};
enum RxConfigBits {
/* Early Rx threshold, none or X/16 */
RxCfgEarlyRxNone = 0,
RxCfgEarlyRxShift = 24,
/* rx fifo threshold */
RxCfgFIFOShift = 13,
RxCfgFIFONone = (7 << RxCfgFIFOShift),
/* Max DMA burst */
RxCfgDMAShift = 8,
RxCfgDMAUnlimited = (7 << RxCfgDMAShift),
/* rx ring buffer length */
RxCfgRcv8K = 0,
RxCfgRcv16K = (1 << 11),
RxCfgRcv32K = (1 << 12),
RxCfgRcv64K = (1 << 11) | (1 << 12),
/* Disable packet wrap at end of Rx buffer */
RxNoWrap = (1 << 7),
};
//enum MediaStatusBits {
// DuplexMode = 0x0100, //in BasicModeControlRegister
// Speed_10 = 0x08, //in Media Status Register
//};
struct rtl8139_private {
void *mmio_addr;
int drv_flags;
struct pci_dev *pci_dev;
struct net_device_stats stats;
unsigned char *rx_ring;
unsigned int cur_rx; /* Index into the Rx buffer of next Rx pkt. */
unsigned int tx_flag;
unsigned long cur_tx;
unsigned long dirty_tx;
struct sk_buff* skb[NUM_TX_DESC];
unsigned char *tx_buf[NUM_TX_DESC]; /* Tx bounce buffers */
unsigned char *tx_bufs; /* Tx bounce buffer region. */
dma_addr_t rx_ring_dma;
dma_addr_t tx_bufs_dma;
spinlock_t lock;
};
static int rtl8139_open (struct net_device *dev);
static int rtl8139_start_xmit (struct sk_buff *skb, struct net_device *dev);
static void rtl8139_interrupt (int irq, void *dev_instance, struct pt_regs *regs);
static void rtl8139_hw_start (struct net_device *dev);
static int rtl8139_close (struct net_device *dev);
static struct net_device_stats *rtl8139_get_stats (struct net_device *dev);
static void rtl8139_set_rx_mode (struct net_device *dev);
static const u16 rtl8139_intr_mask =
PCIErr | PCSTimeout | RxUnderrun | RxOverflow | RxFIFOOver |
TxErr | TxOK | RxErr | RxOK;
static const unsigned int rtl8139_rx_config =
RxCfgEarlyRxNone | RxCfgRcv32K | RxNoWrap |
(RX_FIFO_THRESH << RxCfgFIFOShift) |
(RX_DMA_BURST << RxCfgDMAShift);
/*-----------------------------------------------------------*/
static int __devinit rtl8139_init_one (struct pci_dev *pdev,
const struct pci_device_id *ent)
{
int i;
void *ioaddr = NULL;
struct net_device *dev;
struct rtl8139_private *tp;
int rc ;
unsigned long mmio_start, mmio_end, mmio_flags, mmio_len;
dev = init_etherdev (NULL, sizeof (*tp));
if (dev == NULL) {
printk ("unable to alloc new ethernet/n");
return -ENOMEM;
}
tp = dev->priv;
rc = pci_enable_device (pdev);
if (rc)
goto err_out;
mmio_start = pci_resource_start (pdev, 1);
mmio_end = pci_resource_end (pdev, 1);
mmio_flags = pci_resource_flags (pdev, 1);
mmio_len = pci_resource_len (pdev, 1);
/* make sure PCI base addr 1 is MMIO */
if (!(mmio_flags & IORESOURCE_MEM)) {
printk ("region #1 not an MMIO resource, aborting/n");
rc = -ENODEV;
goto err_out;
}
/* check for weird/broken PCI region reporting */
if (mmio_len < 256) {
printk ("Invalid PCI region size(s), aborting/n");
rc = -ENODEV;
goto err_out;
}
rc = pci_request_regions (pdev, dev->name);
if (rc)
goto err_out;
pci_set_master (pdev);
/* ioremap MMIO region */
ioaddr = ioremap (mmio_start, mmio_len);
if (ioaddr == NULL) {
printk ("cannot remap MMIO, aborting/n");
rc = -EIO;
goto err_out_free_res;
}
/* Soft reset the chip. */
writeb ((readb(ioaddr+ChipCmd) & ChipCmdClear) | CmdReset,ioaddr+ChipCmd);
/* Check that the chip has finished the reset. */
for (i = 1000; i > 0; i--)
if ((readb(ioaddr+ChipCmd) & CmdReset) == 0)
break;
else
udelay (10);
for(i = 0; i < 6; i++) { /* Hardware Address */
dev->dev_addr[i] = readb(ioaddr+i);
dev->broadcast[i] = 0xff;
}
dev->open = rtl8139_open;
dev->hard_start_xmit = rtl8139_start_xmit;
dev->stop = rtl8139_close;
dev->get_stats = rtl8139_get_stats;
dev->irq = pdev->irq;
dev->base_addr = (unsigned long) ioaddr;
tp->pci_dev = pdev;
tp->mmio_addr = ioaddr;
pdev->driver_data = dev;
return 0;
err_out_free_res:
pci_release_regions (pdev);
err_out:
unregister_netdev (dev);
kfree (dev);
return rc;
}
static int rtl8139_close (struct net_device *dev)
{
struct rtl8139_private *tp = dev->priv;
void *ioaddr = tp->mmio_addr;
netif_stop_queue (dev); spin_lock_irq (&tp->lock);
writeb((readb(ioaddr+ChipCmd) & ChipCmdClear),ioaddr+ChipCmd);
writew(0x0000,ioaddr+IntrMask);
writel(0,ioaddr+RxMissed);
spin_unlock_irq (&tp->lock);
synchronize_irq ();
free_irq (dev->irq, dev);
tp->cur_tx=0;
tp->dirty_tx=0;int i;
for (i = 0; i < NUM_TX_DESC; i++) {
if (tp->skb[i]) {
dev_kfree_skb (tp->skb[i]);
tp->skb[i] = NULL;
}
}
pci_free_consistent(tp->pci_dev, RX_BUF_TOT_LEN,tp->rx_ring, tp->rx_ring_dma);
pci_free_consistent(tp->pci_dev, TX_BUF_TOT_LEN,tp->tx_bufs, tp->tx_bufs_dma);
tp->rx_ring = NULL;
tp->tx_bufs = NULL;
return 0;
}
static void __devexit rtl8139_remove_one (struct pci_dev *pdev)
{
struct net_device *dev = pdev->driver_data;
struct rtl8139_private *np;
np = (struct rtl8139_private *) (dev->priv);
iounmap (np->mmio_addr);
pci_release_regions (pdev);
unregister_netdev (dev);
pdev->driver_data = NULL;
pci_disable_device (pdev);
}
static int rtl8139_open (struct net_device *dev)
{
struct rtl8139_private *tp = dev->priv;
int retval,i;
retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev);
if (retval) {
return retval;
}
tp->tx_bufs = pci_alloc_consistent(tp->pci_dev, TX_BUF_TOT_LEN,
&tp->tx_bufs_dma);
tp->rx_ring = pci_alloc_consistent(tp->pci_dev, RX_BUF_TOT_LEN,
&tp->rx_ring_dma);
if (tp->tx_bufs == NULL || tp->rx_ring == NULL) {
free_irq(dev->irq, dev);
if (tp->tx_bufs)
pci_free_consistent(tp->pci_dev, TX_BUF_TOT_LEN,
tp->tx_bufs, tp->tx_bufs_dma);
if (tp->rx_ring)
pci_free_consistent(tp->pci_dev, RX_BUF_TOT_LEN,
tp->rx_ring, tp->rx_ring_dma);
return -ENOMEM;
}
tp->tx_flag = (TX_FIFO_THRESH << 11) & 0x003f0000;
((struct rtl8139_private*)dev->priv)->cur_rx = 0;
((struct rtl8139_private*)dev->priv)->cur_tx = 0;
((struct rtl8139_private*)dev->priv)->dirty_tx = 0;
for (i = 0; i < NUM_TX_DESC; i++)
((struct rtl8139_private*)dev->priv)->tx_buf[i] =
&((struct rtl8139_private*)dev->priv)->tx_bufs[i * TX_BUF_SIZE];
rtl8139_hw_start (dev);
return 0;
}
/* Start the hardware at open or resume. */
static void rtl8139_hw_start (struct net_device *dev)
{
struct rtl8139_private *tp = dev->priv;
void *ioaddr = tp->mmio_addr;
u32 i;
writeb((readb(ioaddr+ChipCmd) &ChipCmdClear) | CmdReset,ioaddr+ChipCmd);
udelay (100);
for (i = 1000; i > 0; i--)
if ((readb(ioaddr+ChipCmd) & CmdReset) == 0)
break;
writeb((readb(ioaddr+ChipCmd) & ChipCmdClear) |
CmdRxEnb | CmdTxEnb,ioaddr+ChipCmd);
writel ((TX_DMA_BURST << TxDMAShift),ioaddr+TxConfig);
tp->cur_rx = 0;
writel(tp->rx_ring_dma,ioaddr+RxBuf);
for (i = 0; i < NUM_TX_DESC; i++)
{
writel(tp->tx_bufs_dma + (tp->tx_buf[i] - tp->tx_bufs),ioaddr+TxAddr0+(i*4));
readl(ioaddr+TxAddr0+(i * 4));
}
writel(0,ioaddr+RxMissed);
rtl8139_set_rx_mode (dev);
writew(readw(ioaddr+MultiIntr) & MultiIntrClear,ioaddr+MultiIntr);
writeb((readb(ioaddr+ChipCmd) & ChipCmdClear) |
CmdRxEnb | CmdTxEnb,ioaddr+ChipCmd);
readb(ioaddr+ChipCmd);
writew(rtl8139_intr_mask,ioaddr+IntrMask);
readw(ioaddr+IntrMask);
netif_start_queue (dev);
}
static int rtl8139_start_xmit (struct sk_buff *skb, struct net_device *dev)
{
struct rtl8139_private *tp = dev->priv;
void *ioaddr = tp->mmio_addr;
int entry;
/* Calculate the next Tx descriptor entry. */
entry = tp->cur_tx % NUM_TX_DESC;
// ----------------------------------------------
// For skb->len < 60, padding payload with 0x20.
// ----------------------------------------------
if( skb->len < ETH_ZLEN ){//if data_len < 60
if( (skb->data + ETH_ZLEN) <= skb->end ){
memset( skb->data + skb->len, 0x20, (ETH_ZLEN - skb->len) );
skb->len = (skb->len >= ETH_ZLEN) ? skb->len : ETH_ZLEN; }
else{
printk("%s:(skb->data+ETH_ZLEN) > skb->end/n",__FUNCTION__);
}
}
tp->skb[entry] = skb;
memcpy (tp->tx_buf[entry], skb->data, skb->len);
writel(tp->tx_bufs_dma + (tp->tx_buf[entry] - tp->tx_bufs),ioaddr+TxAddr0+(entry*4));
/* Note: the chip doesn't have auto-pad! */
writel(tp->tx_flag | (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN),ioaddr+TxStatus0+(entry * 4));
dev->trans_start = jiffies;
spin_lock_irq (&tp->lock);
tp->cur_tx++;
if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx)
netif_stop_queue (dev);
spin_unlock_irq (&tp->lock);
return 0;
}
static void rtl8139_tx_interrupt (struct net_device *dev,
struct rtl8139_private *tp,
void *ioaddr)
{
unsigned long dirty_tx, tx_left;
dirty_tx = tp->dirty_tx;
tx_left = tp->cur_tx - dirty_tx;
while (tx_left > 0) {
int entry = dirty_tx % NUM_TX_DESC;
int txstatus;
txstatus = readl(ioaddr+TxStatus0 + (entry * sizeof (u32)));
if (!(txstatus & (TxStatOK | TxUnderrun | TxAborted)))
break;
if (txstatus & (TxOutOfWindow | TxAborted)) {
if (txstatus & TxAborted) {
writel(TxClearAbt | (TX_DMA_BURST << TxDMAShift),ioaddr+TxConfig);
}
} else {
if (txstatus & TxUnderrun) {
if (tp->tx_flag < 0x00300000)
tp->tx_flag += 0x00020000;
}
}
/* Free the original skb. */
dev_kfree_skb_irq (tp->skb[entry]);
tp->skb[entry] = NULL;
dirty_tx++;
tx_left--;
}
/* only wake the queue if we did work, and the queue is stopped */
if (tp->dirty_tx != dirty_tx) {
tp->dirty_tx = dirty_tx;
if (netif_queue_stopped (dev))
netif_wake_queue (dev);
}
}
/* The data sheet doesn't describe the Rx ring at all, so I'm guessing at the
field alignments and semantics. */
static void rtl8139_rx_interrupt (struct net_device *dev,
struct rtl8139_private *tp, void *ioaddr)
{
unsigned char *rx_ring;
u16 cur_rx;
rx_ring = tp->rx_ring;
cur_rx = tp->cur_rx;
while ((readb(ioaddr+ChipCmd) & RxBufEmpty) == 0) {
int ring_offset = cur_rx % RX_BUF_LEN;
u32 rx_status;
unsigned int rx_size;
unsigned int pkt_size;
struct sk_buff *skb;
/* read size+status of next frame from DMA ring buffer */
rx_status = le32_to_cpu (*(u32 *) (rx_ring + ring_offset));
rx_size = rx_status >> 16;
pkt_size = rx_size - 4;
if (rx_size == 0xfff0)
break;
skb = dev_alloc_skb (pkt_size + 2);
if (skb) {
skb->dev = dev;
skb_reserve (skb, 2); /* 16 byte align the IP fields. */
eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0);
skb_put (skb, pkt_size);
skb->protocol = eth_type_trans (skb, dev);
netif_rx (skb);
dev->last_rx = jiffies;
} else {
printk (KERN_WARNING
"%s: Memory squeeze, dropping packet./n",
dev->name);
}
cur_rx = (cur_rx + rx_size + 4 + 3) & ~3;
writew(cur_rx - 16,ioaddr+RxBufPtr);
readw(ioaddr+RxBufPtr);
}
tp->cur_rx = cur_rx;
}
static void rtl8139_interrupt (int irq, void *dev_instance,
struct pt_regs *regs)
{
struct net_device *dev = (struct net_device *) dev_instance;
struct rtl8139_private *tp = dev->priv;
void *ioaddr = tp->mmio_addr;
int status = 0;
status = readw(ioaddr+IntrStatus);
/* h/w no longer present (hotplug?) or major error, bail */
if (status == 0xFFFF)
goto out;
writew((status & RxFIFOOver) ? (status | RxOverflow) : status,ioaddr+IntrStatus);
if ((status &
(PCIErr | PCSTimeout | RxUnderrun | RxOverflow |
RxFIFOOver | TxErr | TxOK | RxErr | RxOK)) == 0)
goto out;
if (status & (RxOK | RxUnderrun | RxOverflow | RxFIFOOver))/* Rx interrupt */
rtl8139_rx_interrupt (dev, tp, ioaddr);
if (status & (TxOK | TxErr)) {
spin_lock (&tp->lock);
rtl8139_tx_interrupt (dev, tp, ioaddr);
spin_unlock (&tp->lock);
}
out:
writew(0xffff,ioaddr+IntrStatus);
readw(ioaddr+IntrStatus);
}
static struct net_device_stats *rtl8139_get_stats (struct net_device *dev)
{
struct rtl8139_private *tp = dev->priv;
return &tp->stats;
}
/* Set or clear the multicast filter for this adaptor.
This routine is not state sensitive and need not be SMP locked. */
static unsigned const ethernet_polynomial = 0x04c11db7U;
static inline u32 ether_crc (int length, unsigned char *data)
{
int crc = -1;
while (--length >= 0) {
unsigned char current_octet = *data++;
int bit;
for (bit = 0; bit < 8; bit++, current_octet >>= 1)
crc = (crc << 1) ^ ((crc < 0) ^ (current_octet & 1) ?
ethernet_polynomial : 0);
}
return crc;
}
static void rtl8139_set_rx_mode (struct net_device *dev)
{
struct rtl8139_private *tp = dev->priv;
void *ioaddr = tp->mmio_addr;
unsigned long flags;
u32 mc_filter[2]; /* Multicast hash filter */
int i, rx_mode;
u32 tmp;
/* Note: do not reorder, GCC is clever about common statements. */
if (dev->flags & IFF_PROMISC) {
/* Unconditionally log net taps. */
printk (KERN_NOTICE "%s: Promiscuous mode enabled./n",
dev->name);
rx_mode =
AcceptBroadcast | AcceptMulticast | AcceptMyPhys |
AcceptAllPhys;
mc_filter[1] = mc_filter[0] = 0xffffffff;
} else if ((dev->mc_count > multicast_filter_limit)
|| (dev->flags & IFF_ALLMULTI)) {
/* Too many to filter perfectly -- accept all multicasts. */
rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys;
mc_filter[1] = mc_filter[0] = 0xffffffff;
} else {
struct dev_mc_list *mclist;
rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys;
mc_filter[1] = mc_filter[0] = 0;
for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count;
i++, mclist = mclist->next)
set_bit (ether_crc (ETH_ALEN, mclist->dmi_addr) >> 26,
mc_filter);
}
spin_lock_irqsave (&tp->lock, flags);
/* We can safely update without stopping the chip. */
tmp = rtl8139_rx_config | rx_mode |
(readl(ioaddr+RxConfig) );
writel(tmp,ioaddr+RxConfig);
readl(ioaddr+RxConfig);
writel(mc_filter[0],ioaddr+MAR0+0);
readl(ioaddr+MAR0+0);
writel(mc_filter[1],ioaddr+MAR0+4);
readl(ioaddr+MAR0+4);
spin_unlock_irqrestore (&tp->lock, flags);
}
static struct pci_driver rtl8139_pci_driver = {
name: MODNAME,
id_table: rtl8139_pci_tbl,
probe: rtl8139_init_one,
remove: rtl8139_remove_one,
};
static int __init rtl8139_init_module (void)
{
return pci_module_init (&rtl8139_pci_driver);
}
static void __exit rtl8139_cleanup_module (void)
{
pci_unregister_driver (&rtl8139_pci_driver);
}
module_init(rtl8139_init_module);
module_exit(rtl8139_cleanup_module);
# Makefile for a RTL8139too NIC driver
# wenxy 20070919, a.m. [email protected]
dir = /usr/src/linux-2.4/include
modules = 8139too.o
CC = gcc
MODCFLAGS = -Wall -DMODULE -D__KERNEL__ -DLINUX -I$(dir)
all : $(modules)
8139too.o : 8139too.c
$(CC) $(MODCFLAGS) -c 8139too.c
chmod 711 $@
.PHONEY : clean run exit
run:
insmod -f $(modules)
exit:
rmmod 8139too.o
clean :
-rm $(modules)
# end makefile