📄 smc91111.c
字号:
/*--------------------------------------------------------
. Called by the kernel to send a packet out into the void
. of the net. This routine is largely based on
. skeleton.c, from Becker.
.--------------------------------------------------------
*/
static void smc_timeout (struct net_device *dev)
{
PRINTK3("%s:smc_send_packet\n", dev->name);
/* If we get here, some higher level has decided we are broken.
There should really be a "kick me" function call instead. */
printk(KERN_WARNING "%s: transmit timed out, %s?\n",dev->name, tx_done(dev) ? "IRQ conflict" :"network cable problem");
/* "kick" the adaptor */
smc_reset( dev );
smc_enable( dev );
/* Reconfigure the PHY */
smc_phy_configure(dev);
netif_wake_queue(dev);
dev->trans_start = jiffies;
/* clear anything saved */
((struct smc_local *)dev->priv)->saved_skb = NULL;
}
/*--------------------------------------------------------------------
.
. This is the main routine of the driver, to handle the net_device when
. it needs some attention.
.
. So:
. first, save state of the chipset
. branch off into routines to handle each case, and acknowledge
. each to the interrupt register
. and finally restore state.
.
---------------------------------------------------------------------*/
static void smc_interrupt(int irq, void * dev_id, struct pt_regs * regs)
{
struct net_device *dev = dev_id;
int ioaddr = dev->base_addr;
struct smc_local *lp = (struct smc_local *)dev->priv;
byte status;
word card_stats;
byte mask;
int timeout;
/* state registers */
word saved_bank;
word saved_pointer;
PRINTK3("%s: SMC interrupt started \n", dev->name);
if (dev == NULL) {
printk(KERN_WARNING "%s: irq %d for unknown device.\n",
dev->name, irq);
return;
}
/* will Linux let this happen ?? If not, this costs some speed
if ( dev->interrupt ) {
printk(KERN_WARNING "%s: interrupt inside interrupt.\n",
dev->name);
return;
}
dev->interrupt = 1; */
saved_bank = inw( ioaddr + BANK_SELECT );
SMC_SELECT_BANK(2);
saved_pointer = inw( ioaddr + PTR_REG );
/* read the interrupt status register */
mask = inb( ioaddr + IM_REG );
/* disable all interrupts */
outb( 0, ioaddr + IM_REG );
/* set a timeout value, so I don't stay here forever */
timeout = 4;
PRINTK2(KERN_WARNING "%s: MASK IS %x \n", dev->name, mask);
do {
/* read the status flag, and mask it */
status = inb( ioaddr + INT_REG ) & mask;
if (!status )
break;
PRINTK3(KERN_WARNING "%s: Handling interrupt status %x \n",
dev->name, status);
if (status & IM_RCV_INT) {
/* Got a packet(s). */
PRINTK2(KERN_WARNING
"%s: Receive Interrupt\n", dev->name);
smc_rcv(dev);
} else if (status & IM_TX_INT ) {
PRINTK2(KERN_WARNING "%s: TX ERROR handled\n",
dev->name);
smc_tx(dev);
// Acknowledge the interrupt
outb(IM_TX_INT, ioaddr + INT_REG );
} else if (status & IM_TX_EMPTY_INT ) {
/* update stats */
SMC_SELECT_BANK( 0 );
card_stats = inw( ioaddr + COUNTER_REG );
/* single collisions */
lp->stats.collisions += card_stats & 0xF;
card_stats >>= 4;
/* multiple collisions */
lp->stats.collisions += card_stats & 0xF;
/* these are for when linux supports these statistics */
#if 0
card_stats >>= 4;
/* deferred */
card_stats >>= 4;
/* excess deferred */
#endif
SMC_SELECT_BANK( 2 );
PRINTK2(KERN_WARNING "%s: TX_BUFFER_EMPTY handled\n",
dev->name);
// Acknowledge the interrupt
outb( IM_TX_EMPTY_INT, ioaddr + INT_REG );
mask &= ~IM_TX_EMPTY_INT;
lp->stats.tx_packets += lp->packets_waiting;
lp->packets_waiting = 0;
} else if (status & IM_ALLOC_INT ) {
PRINTK2(KERN_DEBUG "%s: Allocation interrupt \n",
dev->name);
/* clear this interrupt so it doesn't happen again */
mask &= ~IM_ALLOC_INT;
smc_hardware_send_packet( dev );
/* enable xmit interrupts based on this */
mask |= ( IM_TX_EMPTY_INT | IM_TX_INT );
/* and let the card send more packets to me */
netif_wake_queue(dev);
PRINTK2("%s: Handoff done successfully.\n",
dev->name);
} else if (status & IM_RX_OVRN_INT ) {
lp->stats.rx_errors++;
lp->stats.rx_fifo_errors++;
// Acknowledge the interrupt
outb( IM_RX_OVRN_INT, ioaddr + INT_REG );
} else if (status & IM_EPH_INT ) {
PRINTK("%s: UNSUPPORTED: EPH INTERRUPT \n",
dev->name);
} else if (status & IM_MDINT ) {
smc_phy_interrupt(dev);
// Acknowledge the interrupt
outb(IM_MDINT, ioaddr + INT_REG );
} else if (status & IM_ERCV_INT ) {
PRINTK("%s: UNSUPPORTED: ERCV INTERRUPT \n",
dev->name);
// Acknowledge the interrupt
outb( IM_ERCV_INT, ioaddr + INT_REG );
}
} while ( timeout -- );
/* restore register states */
SMC_SELECT_BANK( 2 );
outb( mask, ioaddr + IM_REG );
PRINTK3( KERN_WARNING "%s: MASK is now %x \n", dev->name, mask);
outw( saved_pointer, ioaddr + PTR_REG );
SMC_SELECT_BANK( saved_bank );
//dev->interrupt = 0;
PRINTK3("%s: Interrupt done\n", dev->name);
return;
}
/*-------------------------------------------------------------
.
. smc_rcv - receive a packet from the card
.
. There is ( at least ) a packet waiting to be read from
. chip-memory.
.
. o Read the status
. o If an error, record it
. o otherwise, read in the packet
--------------------------------------------------------------
*/
static void smc_rcv(struct net_device *dev)
{
struct smc_local *lp = (struct smc_local *)dev->priv;
int ioaddr = dev->base_addr;
int packet_number;
word status;
word packet_length;
PRINTK3("%s:smc_rcv\n", dev->name);
/* assume bank 2 */
packet_number = inw( ioaddr + RXFIFO_REG );
if ( packet_number & RXFIFO_REMPTY ) {
/* we got called , but nothing was on the FIFO */
PRINTK("%s: WARNING: smc_rcv with nothing on FIFO. \n",
dev->name);
/* don't need to restore anything */
return;
}
/* start reading from the start of the packet */
outw( PTR_READ | PTR_RCV | PTR_AUTOINC, ioaddr + PTR_REG );
/* First two words are status and packet_length */
status = inw( ioaddr + DATA_REG );
packet_length = inw( ioaddr + DATA_REG );
packet_length &= 0x07ff; /* mask off top bits */
PRINTK2("RCV: STATUS %4x LENGTH %4x\n", status, packet_length );
if ( !(status & RS_ERRORS ) ){
/* do stuff to make a new packet */
struct sk_buff * skb;
byte * data;
/* set multicast stats */
if ( status & RS_MULTICAST )
lp->stats.multicast++;
// Allocate enough memory for entire receive frame, to be safe
skb = dev_alloc_skb( packet_length );
/* Adjust for having already read the first two words */
packet_length -= 4;
if ( skb == NULL ) {
printk(KERN_NOTICE "%s: Low memory, packet dropped.\n",
dev->name);
lp->stats.rx_dropped++;
goto done;
}
/*
! This should work without alignment, but it could be
! in the worse case
*/
/* TODO: Should I use 32bit alignment here ? */
skb_reserve( skb, 2 ); /* 16 bit alignment */
skb->dev = dev;
/* =>
ODD-BYTE ISSUE : The odd byte problem has been fixed in the LAN91C111 Rev B.
So we check if the Chip Revision, stored in smsc_local->ChipRev, is = 1.
If so then we increment the packet length only if RS_ODDFRAME is set.
If the Chip's revision is equal to 0, then we blindly increment the packet length
by 1, thus always assuming that the packet is odd length, leaving the higher layer
to decide the actual length.
-- Pramod
<= */
if ((9 == lp->ChipID) && (1 == lp->ChipRev))
{
if (status & RS_ODDFRAME)
data = skb_put( skb, packet_length + 1 );
else
data = skb_put( skb, packet_length);
}
else
{
// set odd length for bug in LAN91C111, REV A
// which never sets RS_ODDFRAME
data = skb_put( skb, packet_length + 1 );
}
#ifdef USE_32_BIT
PRINTK3(" Reading %d dwords (and %d bytes) \n",
packet_length >> 2, packet_length & 3 );
/* QUESTION: Like in the TX routine, do I want
to send the DWORDs or the bytes first, or some
mixture. A mixture might improve already slow PIO
performance */
insl(ioaddr + DATA_REG , data, packet_length >> 2 );
/* read the left over bytes */
insb( ioaddr + DATA_REG, data + (packet_length & 0xFFFFFC),
packet_length & 0x3 );
#else
PRINTK3(" Reading %d words and %d byte(s) \n",
(packet_length >> 1 ), packet_length & 1 );
insw(ioaddr + DATA_REG , data, packet_length >> 1);
#endif // USE_32_BIT
#if SMC_DEBUG > 2
printk("Receiving Packet\n");
print_packet( data, packet_length );
#endif
skb->protocol = eth_type_trans(skb, dev );
netif_rx(skb);
lp->stats.rx_packets++;
} else {
/* error ... */
lp->stats.rx_errors++;
if ( status & RS_ALGNERR ) lp->stats.rx_frame_errors++;
if ( status & (RS_TOOSHORT | RS_TOOLONG ) )
lp->stats.rx_length_errors++;
if ( status & RS_BADCRC) lp->stats.rx_crc_errors++;
}
while ( inw( ioaddr + MMU_CMD_REG ) & MC_BUSY )
udelay(1); // Wait until not busy
done:
/* error or good, tell the card to get rid of this packet */
outw( MC_RELEASE, ioaddr + MMU_CMD_REG );
return;
}
/*************************************************************************
. smc_tx
.
. Purpose: Handle a transmit error message. This will only be called
. when an error, because of the AUTO_RELEASE mode.
.
. Algorithm:
. Save pointer and packet no
. Get the packet no from the top of the queue
. check if it's valid ( if not, is this an error??? )
. read the status word
. record the error
. ( resend? Not really, since we don't want old packets around )
. Restore saved values
************************************************************************/
static void smc_tx( struct net_device * dev )
{
int ioaddr = dev->base_addr;
struct smc_local *lp = (struct smc_local *)dev->priv;
byte saved_packet;
byte packet_no;
word tx_status;
PRINTK3("%s:smc_tx\n", dev->name);
/* assume bank 2 */
saved_packet = inb( ioaddr + PN_REG );
packet_no = inw( ioaddr + RXFIFO_REG );
packet_no &= 0x7F;
/* If the TX FIFO is empty then nothing to do */
if ( packet_no & TXFIFO_TEMPTY )
return;
/* select this as the packet to read from */
outb( packet_no, ioaddr + PN_REG );
/* read the first word (status word) from this packet */
outw( PTR_AUTOINC | PTR_READ, ioaddr + PTR_REG );
tx_status = inw( ioaddr + DATA_REG );
PRINTK3("%s: TX DONE STATUS: %4x \n", dev->name, tx_status);
lp->stats.tx_errors++;
if ( tx_status & TS_LOSTCAR ) lp->stats.tx_carrier_errors++;
if ( tx_status & TS_LATCOL ) {
printk(KERN_DEBUG
"%s: Late collision occurred on last xmit.\n",
dev->name);
lp->stats.tx_window_errors++;
lp->ctl_forcol = 0; // Reset forced collsion
}
#if 0
if ( tx_status & TS_16COL ) { ... }
#endif
if ( tx_status & TS_SUCCESS ) {
printk("%s: Successful packet caused interrupt \n", dev->name);
}
/* re-enable transmit */
SMC_SELECT_BANK( 0 );
outw( inw( ioaddr + TCR_REG ) | TCR_ENABLE, ioaddr + TCR_REG );
/* kill the packet */
SMC_SELECT_BANK( 2 );
outw( MC_FREEPKT, ioaddr + MMU_CMD_REG );
/* one less packet waiting for me */
lp->packets_waiting--;
/* Don't change Packet Number Reg until busy bit is cleared */
/* Per LAN91C111 Spec, Page 50 */
while ( inw( ioaddr + MMU_CMD_REG ) & MC_BUSY );
outb( saved_packet, ioaddr + PN_REG );
return;
}
/*----------------------------------------------------
. smc_close
.
. this makes the board clean up everything that it can
. and not talk to the outside world. Caused by
. an 'ifconfig ethX down'
.
-----------------------------------------------------*/
static int smc_close(struct net_device *dev)
{
netif_stop_queue(dev);
//dev->start = 0;
PRINTK2("%s:smc_close\n", dev->name);
#ifdef CONFIG_SYSCTL
smc_sysctl_unregister(dev);
#endif /* CONFIG_SYSCTL */
/* clear everything */
smc_shutdown( dev->base_addr );
/* Update the statistics here. */
#ifdef MODULE
MOD_DEC_USE_COUNT;
#endif
return 0;
}
/*------------------------------------------------------------
. Get the current statistics.
. This may be called with the card open or closed.
.-------------------------------------------------------------*/
static struct net_device_stats* smc_query_statistics(struct net_device *dev) {
struct smc_local *lp = (struct smc_local *)dev->priv;
PRINTK2("%s:smc_query_statistics\n", dev->name);
return &lp->stats;
}
/*-----------------------------------------------------------
. smc_set_multicast_list
.
. This routine will, depending on the values passed to it,
. either make it accept multicast packets, go into
. promiscuous mode ( for TCPDUMP and cousins ) or accept
. a select set of multicast packets
*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -