📄 3c509.c
字号:
/* Transmitter timeout, serious problems. */
if (dev->tbusy) {
int tickssofar = jiffies - dev->trans_start;
if (tickssofar < 10)
return 1;
printk("%s: transmit timed out, tx_status %2.2x status %4.4x.\n",
dev->name, inb(ioaddr + TX_STATUS), inw(ioaddr + EL3_STATUS));
dev->trans_start = jiffies;
/* Issue TX_RESET and TX_START commands. */
outw(0x5800, ioaddr + EL3_CMD); /* TX_RESET */
outw(0x4800, ioaddr + EL3_CMD); /* TX_START */
dev->tbusy = 0;
}
if (skb == NULL) {
dev_tint(dev);
return 0;
}
/* Fill in the ethernet header. */
if (!skb->arp && dev->rebuild_header(skb->data, dev)) {
skb->dev = dev;
arp_queue (skb);
return 0;
}
skb->arp=1;
if (skb->len <= 0)
return 0;
if (el3_debug > 4) {
printk("%s: el3_start_xmit(lenght = %ld) called, status %4.4x.\n",
dev->name, skb->len, inw(ioaddr + EL3_STATUS));
}
#ifndef final_version
{ /* Error-checking code, delete for 1.00. */
ushort status = inw(ioaddr + EL3_STATUS);
if (status & 0x0001 /* IRQ line active, missed one. */
&& inw(ioaddr + EL3_STATUS) & 1) { /* Make sure. */
printk("%s: Missed interrupt, status then %04x now %04x"
" Tx %2.2x Rx %4.4x.\n", dev->name, status,
inw(ioaddr + EL3_STATUS), inb(ioaddr + TX_STATUS),
inw(ioaddr + RX_STATUS));
outw(0x7800, ioaddr + EL3_CMD); /* Fake interrupt trigger. */
outw(0x6899, ioaddr + EL3_CMD); /* Ack IRQ */
outw(0x78ff, ioaddr + EL3_CMD); /* Set all status bits visible. */
}
}
#endif
/* Avoid timer-based retransmission conflicts. */
if (set_bit(0, (void*)&dev->tbusy) != 0)
printk("%s: Transmitter access conflict.\n", dev->name);
else {
/* Put out the doubleword header... */
outw(skb->len, ioaddr + TX_FIFO);
outw(0x00, ioaddr + TX_FIFO);
/* ... and the packet rounded to a doubleword. */
outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2);
dev->trans_start = jiffies;
if (inw(ioaddr + TX_FREE) > 1536) {
dev->tbusy=0;
} else
/* Interrupt us when the FIFO has room for max-sized packet. */
outw(0x9000 + 1536, ioaddr + EL3_CMD);
}
if (skb->free)
kfree_skb (skb, FREE_WRITE);
/* Clear the Tx status stack. */
{
short tx_status;
int i = 4;
while (--i > 0 && (tx_status = inb(ioaddr + TX_STATUS)) > 0) {
if (el3_debug > 5)
printk(" Tx status %4.4x.\n", tx_status);
if (tx_status & 0x38) lp->stats.tx_aborted_errors++;
if (tx_status & 0x30) outw(0x5800, ioaddr + EL3_CMD);
if (tx_status & 0x3C) outw(0x4800, ioaddr + EL3_CMD);
outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */
}
}
return 0;
}
/* The EL3 interrupt handler. */
static void
el3_interrupt(int reg_ptr)
{
int irq = -(((struct pt_regs *)reg_ptr)->orig_eax+2);
struct device *dev = (struct device *)(irq2dev_map[irq]);
int ioaddr, status;
int i = 0;
if (dev == NULL) {
printk ("el3_interrupt(): irq %d for unknown device.\n", irq);
return;
}
if (dev->interrupt)
printk("%s: Re-entering the interrupt handler.\n", dev->name);
dev->interrupt = 1;
ioaddr = dev->base_addr;
status = inw(ioaddr + EL3_STATUS);
if (el3_debug > 4)
printk("%s: interrupt, status %4.4x.\n", dev->name, status);
while ((status = inw(ioaddr + EL3_STATUS)) & 0x01) {
if (status & 0x10)
el3_rx(dev);
if (status & 0x08) {
if (el3_debug > 5)
printk(" TX room bit was handled.\n");
/* There's room in the FIFO for a full-sized packet. */
outw(0x6808, ioaddr + EL3_CMD); /* Ack IRQ */
dev->tbusy = 0;
mark_bh(INET_BH);
}
if (status & 0x80) /* Statistics full. */
update_stats(ioaddr, dev);
if (++i > 10) {
printk("%s: Infinite loop in interrupt, status %4.4x.\n",
dev->name, status);
/* Clear all interrupts we have handled. */
outw(0x68FF, ioaddr + EL3_CMD);
break;
}
/* Acknowledge the IRQ. */
outw(0x6891, ioaddr + EL3_CMD); /* Ack IRQ */
}
if (el3_debug > 4) {
printk("%s: exiting interrupt, status %4.4x.\n", dev->name,
inw(ioaddr + EL3_STATUS));
}
dev->interrupt = 0;
return;
}
static struct enet_statistics *
el3_get_stats(struct device *dev)
{
struct el3_private *lp = (struct el3_private *)dev->priv;
sti();
update_stats(dev->base_addr, dev);
cli();
return &lp->stats;
}
/* Update statistics. We change to register window 6, so this should be run
single-threaded if the device is active. This is expected to be a rare
operation, and it's simpler for the rest of the driver to assume that
window 1 is always valid rather than use a special window-state variable.
*/
static void update_stats(int ioaddr, struct device *dev)
{
struct el3_private *lp = (struct el3_private *)dev->priv;
if (el3_debug > 5)
printk(" Updating the statistics.\n");
/* Turn off statistics updates while reading. */
outw(0xB000, ioaddr + EL3_CMD);
/* Switch to the stats window, and read everything. */
EL3WINDOW(6);
lp->stats.tx_carrier_errors += inb(ioaddr + 0);
lp->stats.tx_heartbeat_errors += inb(ioaddr + 1);
/* Multiple collisions. */ inb(ioaddr + 2);
lp->stats.collisions += inb(ioaddr + 3);
lp->stats.tx_window_errors += inb(ioaddr + 4);
lp->stats.rx_fifo_errors += inb(ioaddr + 5);
lp->stats.tx_packets += inb(ioaddr + 6);
lp->stats.rx_packets += inb(ioaddr + 7);
/* Tx deferrals */ inb(ioaddr + 8);
inw(ioaddr + 10); /* Total Rx and Tx octets. */
inw(ioaddr + 12);
/* Back to window 1, and turn statistics back on. */
EL3WINDOW(1);
outw(0xA800, ioaddr + EL3_CMD);
return;
}
static int
el3_rx(struct device *dev)
{
struct el3_private *lp = (struct el3_private *)dev->priv;
int ioaddr = dev->base_addr;
short rx_status;
if (el3_debug > 5)
printk(" In rx_packet(), status %4.4x, rx_status %4.4x.\n",
inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS));
while ((rx_status = inw(ioaddr + RX_STATUS)) > 0) {
if (rx_status & 0x4000) { /* Error, update stats. */
short error = rx_status & 0x3800;
lp->stats.rx_errors++;
switch (error) {
case 0x0000: lp->stats.rx_over_errors++; break;
case 0x0800: lp->stats.rx_length_errors++; break;
case 0x1000: lp->stats.rx_frame_errors++; break;
case 0x1800: lp->stats.rx_length_errors++; break;
case 0x2000: lp->stats.rx_frame_errors++; break;
case 0x2800: lp->stats.rx_crc_errors++; break;
}
}
if ( (! (rx_status & 0x4000))
|| ! (rx_status & 0x1000)) { /* Dribble bits are OK. */
short pkt_len = rx_status & 0x7ff;
int sksize = sizeof(struct sk_buff) + pkt_len + 3;
struct sk_buff *skb;
skb = alloc_skb(sksize, GFP_ATOMIC);
if (el3_debug > 4)
printk(" Receiving packet size %d status %4.4x.\n",
pkt_len, rx_status);
if (skb != NULL) {
skb->mem_len = sksize;
skb->mem_addr = skb;
skb->len = pkt_len;
skb->dev = dev;
/* 'skb->data' points to the start of sk_buff data area. */
insl(ioaddr+RX_FIFO, skb->data,
(pkt_len + 3) >> 2);
#ifdef HAVE_NETIF_RX
netif_rx(skb);
outw(0x4000, ioaddr + EL3_CMD); /* Rx discard */
continue;
#else
skb->lock = 0;
if (dev_rint((unsigned char *)skb, pkt_len,
IN_SKBUFF,dev)== 0){
if (el3_debug > 6)
printk(" dev_rint() happy, status %4.4x.\n",
inb(ioaddr + EL3_STATUS));
outw(0x4000, ioaddr + EL3_CMD); /* Rx discard */
while (inw(ioaddr + EL3_STATUS) & 0x1000)
printk(" Waiting for 3c509 to discard packet, status %x.\n",
inw(ioaddr + EL3_STATUS) );
if (el3_debug > 6)
printk(" discarded packet, status %4.4x.\n",
inb(ioaddr + EL3_STATUS));
continue;
} else {
printk("%s: receive buffers full.\n", dev->name);
kfree_s(skb, sksize);
}
#endif
} else if (el3_debug)
printk("%s: Couldn't allocate a sk_buff of size %d.\n",
dev->name, sksize);
}
lp->stats.rx_dropped++;
outw(0x4000, ioaddr + EL3_CMD); /* Rx discard */
while (inw(ioaddr + EL3_STATUS) & 0x1000)
printk(" Waiting for 3c509 to discard packet, status %x.\n",
inw(ioaddr + EL3_STATUS) );
}
if (el3_debug > 5)
printk(" Exiting rx_packet(), status %4.4x, rx_status %4.4x.\n",
inw(ioaddr+EL3_STATUS), inw(ioaddr+8));
return 0;
}
#ifdef HAVE_MULTICAST
/* Set or clear the multicast filter for this adaptor.
num_addrs == -1 Promiscuous mode, receive all packets
num_addrs == 0 Normal mode, clear multicast list
num_addrs > 0 Multicast mode, receive normal and MC packets, and do
best-effort filtering.
*/
static void
set_multicast_list(struct device *dev, int num_addrs, void *addrs)
{
short ioaddr = dev->base_addr;
if (num_addrs > 0) {
outw(0x8007, ioaddr + EL3_CMD);
} else if (num_addrs < 0) {
outw(0x8008, ioaddr + EL3_CMD);
} else
outw(0x8005, ioaddr + EL3_CMD);
}
#endif
static int
el3_close(struct device *dev)
{
int ioaddr = dev->base_addr;
if (el3_debug > 2)
printk("%s: Shutting down ethercard.\n", dev->name);
dev->tbusy = 1;
dev->start = 0;
/* Turn off statistics. We update lp->stats below. */
outw(0xB000, ioaddr + EL3_CMD);
/* Disable the receiver and transmitter. */
outw(0x1800, ioaddr + EL3_CMD);
outw(0x5000, ioaddr + EL3_CMD);
if (dev->if_port == 3)
/* Turn off thinnet power. */
outw(0xb800, ioaddr + EL3_CMD);
else if (dev->if_port == 0) {
/* Disable link beat and jabber, if_port may change ere next open(). */
EL3WINDOW(4);
outw(inw(ioaddr + WN4_MEDIA) & ~MEDIA_TP, ioaddr + WN4_MEDIA);
}
free_irq(dev->irq);
/* Switching back to window 0 disables the IRQ. */
EL3WINDOW(0);
/* But we explicitly zero the IRQ line select anyway. */
outw(0x0f00, ioaddr + 8);
irq2dev_map[dev->irq] = 0;
update_stats(ioaddr, dev);
return 0;
}
/*
* Local variables:
* compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c 3c509.c"
* version-control: t
* kept-new-versions: 5
* tab-width: 4
* End:
*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -