mirror of https://github.com/ipxe/ipxe.git
1164 lines
32 KiB
C
1164 lines
32 KiB
C
/*
|
|
* eepro100.c -- This is a driver for Intel Fast Ethernet Controllers
|
|
* (ifec).
|
|
*
|
|
* Originally written for Etherboot by:
|
|
*
|
|
* Copyright (C) AW Computer Systems.
|
|
* written by R.E.Wolff -- R.E.Wolff@BitWizard.nl
|
|
*
|
|
* AW Computer Systems is contributing to the free software community
|
|
* by paying for this driver and then putting the result under GPL.
|
|
*
|
|
* If you need a Linux device driver, please contact BitWizard for a
|
|
* quote.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*
|
|
* date version by what
|
|
* Written: May 29 1997 V0.10 REW Initial revision.
|
|
* changes: May 31 1997 V0.90 REW Works!
|
|
* Jun 1 1997 V0.91 REW Cleanup
|
|
* Jun 2 1997 V0.92 REW Add some code documentation
|
|
* Jul 25 1997 V1.00 REW Tested by AW to work in a PROM
|
|
* Cleanup for publication
|
|
* Dez 11 2004 V1.10 Kiszka Add RX ring buffer support
|
|
* Jun 2008 v2.0 mdeck Updated to iPXE. Changed much.
|
|
*
|
|
* Cleanups and fixes by Thomas Miletich<thomas.miletich@gmail.com>
|
|
*
|
|
* This is the etherboot intel etherexpress Pro/100B driver.
|
|
*
|
|
* It was written from scratch, with Donald Beckers eepro100.c kernel
|
|
* driver as a guideline. Mostly the 82557 related definitions and the
|
|
* lower level routines have been cut-and-pasted into this source.
|
|
*
|
|
* The driver was finished before Intel got the NDA out of the closet.
|
|
*
|
|
* Datasheet is now published and available from
|
|
* ftp://download.intel.com/design/network/manuals/8255X_OpenSDM.pdf
|
|
* - Michael Brown
|
|
* */
|
|
|
|
FILE_LICENCE ( GPL2_OR_LATER );
|
|
|
|
/*
|
|
* General Theory of Operation
|
|
*
|
|
* Initialization
|
|
*
|
|
* ifec_pci_probe() is called by iPXE during initialization. Typical NIC
|
|
* initialization is performed. EEPROM data is read.
|
|
*
|
|
* Network Boot
|
|
*
|
|
* ifec_net_open() is called by iPXE before attempting to network boot from the
|
|
* card. Here, the Command Unit & Receive Unit are initialized. The tx & rx
|
|
* rings are setup. The MAC address is programmed and the card is configured.
|
|
*
|
|
* Transmit
|
|
*
|
|
* ifec_net_transmit() enqueues a packet in the tx ring - active::tcbs[] The tx
|
|
* ring is composed of TCBs linked to each other into a ring. A tx request
|
|
* fills out the next available TCB with a pointer to the packet data.
|
|
* The last enqueued tx is always at active::tcb_head. Thus, a tx request fills
|
|
* out the TCB following tcb_head.
|
|
* active::tcb_tail points to the TCB we're awaiting completion of.
|
|
* ifec_tx_process() checks tcb_tail, and once complete,
|
|
* blindly increments tcb_tail to the next ring TCB.
|
|
*
|
|
* Receive
|
|
*
|
|
* priv::rfds[] is an array of Receive Frame Descriptors. The RFDs are linked
|
|
* together to form a ring.
|
|
* ifec_net_poll() calls ifec_rx_process(), which checks the next RFD for
|
|
* data. If we received a packet, we allocate a new io_buffer and copy the
|
|
* packet data into it. If alloc_iob() fails, we don't touch the RFD and try
|
|
* again on the next poll.
|
|
*/
|
|
|
|
/*
|
|
* Debugging levels:
|
|
* - DBG() is for any errors, i.e. failed alloc_iob(), malloc_dma(),
|
|
* TX overflow, corrupted packets, ...
|
|
* - DBG2() is for successful events, like packet received,
|
|
* packet transmitted, and other general notifications.
|
|
* - DBGP() prints the name of each called function on entry
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <byteswap.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <ipxe/ethernet.h>
|
|
#include <ipxe/if_ether.h>
|
|
#include <ipxe/iobuf.h>
|
|
#include <ipxe/malloc.h>
|
|
#include <ipxe/pci.h>
|
|
#include <ipxe/spi_bit.h>
|
|
#include <ipxe/timer.h>
|
|
#include <ipxe/nvs.h>
|
|
#include <ipxe/threewire.h>
|
|
#include <ipxe/netdevice.h>
|
|
#include "eepro100.h"
|
|
|
|
/****************************** Global data **********************************/
|
|
|
|
/*
|
|
* This is the default configuration command data. The values were copied from
|
|
* the Linux kernel initialization for the eepro100.
|
|
*/
|
|
static struct ifec_cfg ifec_cfg = {
|
|
.status = 0,
|
|
.command = CmdConfigure | CmdSuspend,
|
|
.link = 0, /* Filled in later */
|
|
.byte = { 22, /* How many bytes in this array */
|
|
( TX_FIFO << 4 ) | RX_FIFO, /* Rx & Tx FIFO limits */
|
|
0, 0, /* Adaptive Interframe Spacing */
|
|
RX_DMA_COUNT, /* Rx DMA max byte count */
|
|
TX_DMA_COUNT + 0x80, /* Tx DMA max byte count */
|
|
0x32, /* Many bits. */
|
|
0x03, /* Discard short receive & Underrun retries */
|
|
1, /* 1=Use MII 0=Use AUI */
|
|
0,
|
|
0x2E, /* NSAI, Preamble length, & Loopback*/
|
|
0, /* Linear priority */
|
|
0x60, /* L PRI MODE & Interframe spacing */
|
|
0, 0xf2,
|
|
0x48, /* Promiscuous, Broadcast disable, CRS & CDT */
|
|
0, 0x40,
|
|
0xf2, /* Stripping, Padding, Receive CRC Transfer */
|
|
0x80, /* 0x40=Force full-duplex, 0x80=Allowfull-duplex*/
|
|
0x3f, /* Multiple IA */
|
|
0x0D } /* Multicast all */
|
|
};
|
|
|
|
static struct net_device_operations ifec_operations = {
|
|
.open = ifec_net_open,
|
|
.close = ifec_net_close,
|
|
.transmit = ifec_net_transmit,
|
|
.poll = ifec_net_poll,
|
|
.irq = ifec_net_irq
|
|
};
|
|
|
|
/******************* iPXE PCI Device Driver API functions ********************/
|
|
|
|
/*
|
|
* Initialize the PCI device.
|
|
*
|
|
* @v pci The device's associated pci_device structure.
|
|
* @v id The PCI device + vendor id.
|
|
* @ret rc Returns zero if successfully initialized.
|
|
*
|
|
* This function is called very early on, while iPXE is initializing.
|
|
* This is a iPXE PCI Device Driver API function.
|
|
*/
|
|
static int ifec_pci_probe ( struct pci_device *pci )
|
|
{
|
|
struct net_device *netdev;
|
|
struct ifec_private *priv;
|
|
int rc;
|
|
|
|
DBGP ( "ifec_pci_probe: " );
|
|
|
|
if ( pci->ioaddr == 0 )
|
|
return -EINVAL;
|
|
|
|
netdev = alloc_etherdev ( sizeof(*priv) );
|
|
if ( !netdev )
|
|
return -ENOMEM;
|
|
|
|
netdev_init ( netdev, &ifec_operations );
|
|
priv = netdev->priv;
|
|
|
|
pci_set_drvdata ( pci, netdev );
|
|
netdev->dev = &pci->dev;
|
|
|
|
/* enable bus master, etc */
|
|
adjust_pci_device( pci );
|
|
|
|
DBGP ( "pci " );
|
|
|
|
memset ( priv, 0, sizeof(*priv) );
|
|
priv->ioaddr = pci->ioaddr;
|
|
|
|
ifec_reset ( netdev );
|
|
DBGP ( "reset " );
|
|
|
|
ifec_init_eeprom ( netdev );
|
|
|
|
/* read MAC address */
|
|
nvs_read ( &priv->eeprom.nvs, EEPROM_ADDR_MAC_0, netdev->hw_addr,
|
|
ETH_ALEN );
|
|
/* read mdio_register */
|
|
nvs_read ( &priv->eeprom.nvs, EEPROM_ADDR_MDIO_REGISTER,
|
|
&priv->mdio_register, 2 );
|
|
|
|
if ( ( rc = register_netdev ( netdev ) ) != 0 )
|
|
goto error;
|
|
|
|
netdev_link_up ( netdev );
|
|
|
|
DBGP ( "ints\n" );
|
|
|
|
return 0;
|
|
|
|
error:
|
|
ifec_reset ( netdev );
|
|
netdev_nullify ( netdev );
|
|
netdev_put ( netdev );
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Remove a device from the PCI device list.
|
|
*
|
|
* @v pci PCI device to remove.
|
|
*
|
|
* This is a PCI Device Driver API function.
|
|
*/
|
|
static void ifec_pci_remove ( struct pci_device *pci )
|
|
{
|
|
struct net_device *netdev = pci_get_drvdata ( pci );
|
|
|
|
DBGP ( "ifec_pci_remove\n" );
|
|
|
|
unregister_netdev ( netdev );
|
|
ifec_reset ( netdev );
|
|
netdev_nullify ( netdev );
|
|
netdev_put ( netdev );
|
|
}
|
|
|
|
/****************** iPXE Network Device Driver API functions *****************/
|
|
|
|
/*
|
|
* Close a network device.
|
|
*
|
|
* @v netdev Device to close.
|
|
*
|
|
* This is a iPXE Network Device Driver API function.
|
|
*/
|
|
static void ifec_net_close ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
unsigned long ioaddr = priv->ioaddr;
|
|
unsigned short intr_status;
|
|
|
|
DBGP ( "ifec_net_close\n" );
|
|
|
|
/* disable interrupts */
|
|
ifec_net_irq ( netdev, 0 );
|
|
|
|
/* Ack & clear ints */
|
|
intr_status = inw ( ioaddr + SCBStatus );
|
|
outw ( intr_status, ioaddr + SCBStatus );
|
|
inw ( ioaddr + SCBStatus );
|
|
|
|
ifec_reset ( netdev );
|
|
|
|
/* Free any resources */
|
|
ifec_free ( netdev );
|
|
}
|
|
|
|
/* Interrupts to be masked */
|
|
#define INTERRUPT_MASK ( SCBMaskEarlyRx | SCBMaskFlowCtl )
|
|
|
|
/*
|
|
* Enable or disable IRQ masking.
|
|
*
|
|
* @v netdev Device to control.
|
|
* @v enable Zero to mask off IRQ, non-zero to enable IRQ.
|
|
*
|
|
* This is a iPXE Network Driver API function.
|
|
*/
|
|
static void ifec_net_irq ( struct net_device *netdev, int enable )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
unsigned long ioaddr = priv->ioaddr;
|
|
|
|
DBGP ( "ifec_net_irq\n" );
|
|
|
|
outw ( enable ? INTERRUPT_MASK : SCBMaskAll, ioaddr + SCBCmd );
|
|
}
|
|
|
|
/*
|
|
* Opens a network device.
|
|
*
|
|
* @v netdev Device to be opened.
|
|
* @ret rc Non-zero if failed to open.
|
|
*
|
|
* This enables tx and rx on the device.
|
|
* This is a iPXE Network Device Driver API function.
|
|
*/
|
|
static int ifec_net_open ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
struct ifec_ias *ias = NULL;
|
|
struct ifec_cfg *cfg = NULL;
|
|
int i, options;
|
|
int rc = -ENOMEM;
|
|
|
|
DBGP ( "ifec_net_open: " );
|
|
|
|
/* Ensure interrupts are disabled. */
|
|
ifec_net_irq ( netdev, 0 );
|
|
|
|
/* Initialize Command Unit and Receive Unit base addresses. */
|
|
ifec_scb_cmd ( netdev, 0, RUAddrLoad );
|
|
ifec_scb_cmd ( netdev, virt_to_bus ( &priv->stats ), CUStatsAddr );
|
|
ifec_scb_cmd ( netdev, 0, CUCmdBase );
|
|
|
|
/* Initialize both rings */
|
|
if ( ( rc = ifec_rx_setup ( netdev ) ) != 0 )
|
|
goto error;
|
|
if ( ( rc = ifec_tx_setup ( netdev ) ) != 0 )
|
|
goto error;
|
|
|
|
/* Initialize MDIO */
|
|
options = 0x00; /* 0x40 = 10mbps half duplex, 0x00 = Autosense */
|
|
ifec_mdio_setup ( netdev, options );
|
|
|
|
/* Prepare MAC address w/ Individual Address Setup (ias) command.*/
|
|
ias = malloc_dma ( sizeof ( *ias ), CB_ALIGN );
|
|
if ( !ias ) {
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
ias->command = CmdIASetup;
|
|
ias->status = 0;
|
|
memcpy ( ias->ia, netdev->ll_addr, ETH_ALEN );
|
|
|
|
/* Prepare operating parameters w/ a configure command. */
|
|
cfg = malloc_dma ( sizeof ( *cfg ), CB_ALIGN );
|
|
if ( !cfg ) {
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
memcpy ( cfg, &ifec_cfg, sizeof ( *cfg ) );
|
|
cfg->link = virt_to_bus ( priv->tcbs );
|
|
cfg->byte[19] = ( options & 0x10 ) ? 0xC0 : 0x80;
|
|
ias->link = virt_to_bus ( cfg );
|
|
|
|
/* Issue the ias and configure commands. */
|
|
ifec_scb_cmd ( netdev, virt_to_bus ( ias ), CUStart );
|
|
ifec_scb_cmd_wait ( netdev );
|
|
priv->configured = 1;
|
|
|
|
/* Wait up to 10 ms for configuration to initiate */
|
|
for ( i = 10; i && !cfg->status; i-- )
|
|
mdelay ( 1 );
|
|
if ( ! cfg->status ) {
|
|
DBG ( "Failed to initiate!\n" );
|
|
goto error;
|
|
}
|
|
free_dma ( ias, sizeof ( *ias ) );
|
|
free_dma ( cfg, sizeof ( *cfg ) );
|
|
DBG2 ( "cfg " );
|
|
|
|
/* Enable rx by sending ring address to card */
|
|
if ( priv->rfds[0] != NULL ) {
|
|
ifec_scb_cmd ( netdev, virt_to_bus( priv->rfds[0] ), RUStart );
|
|
ifec_scb_cmd_wait ( netdev );
|
|
}
|
|
DBG2 ( "rx_start\n" );
|
|
|
|
return 0;
|
|
|
|
error:
|
|
free_dma ( cfg, sizeof ( *cfg ) );
|
|
free_dma ( ias, sizeof ( *ias ) );
|
|
ifec_free ( netdev );
|
|
ifec_reset ( netdev );
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* This function allows a driver to process events during operation.
|
|
*
|
|
* @v netdev Device being polled.
|
|
*
|
|
* This is called periodically by iPXE to let the driver check the status of
|
|
* transmitted packets and to allow the driver to check for received packets.
|
|
* This is a iPXE Network Device Driver API function.
|
|
*/
|
|
static void ifec_net_poll ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
unsigned short intr_status;
|
|
|
|
DBGP ( "ifec_net_poll\n" );
|
|
|
|
/* acknowledge interrupts ASAP */
|
|
intr_status = inw ( priv->ioaddr + SCBStatus );
|
|
outw ( intr_status, priv->ioaddr + SCBStatus );
|
|
inw ( priv->ioaddr + SCBStatus );
|
|
|
|
DBG2 ( "poll - status: 0x%04X\n", intr_status );
|
|
|
|
/* anything to do here? */
|
|
if ( ( intr_status & ( ~INTERRUPT_MASK ) ) == 0 )
|
|
return;
|
|
|
|
/* process received and transmitted packets */
|
|
ifec_tx_process ( netdev );
|
|
ifec_rx_process ( netdev );
|
|
|
|
ifec_check_ru_status ( netdev, intr_status );
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This transmits a packet.
|
|
*
|
|
* @v netdev Device to transmit from.
|
|
* @v iobuf Data to transmit.
|
|
* @ret rc Non-zero if failed to transmit.
|
|
*
|
|
* This is a iPXE Network Driver API function.
|
|
*/
|
|
static int ifec_net_transmit ( struct net_device *netdev,
|
|
struct io_buffer *iobuf )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
struct ifec_tcb *tcb = priv->tcb_head->next;
|
|
unsigned long ioaddr = priv->ioaddr;
|
|
|
|
DBGP ( "ifec_net_transmit\n" );
|
|
|
|
/* Wait for TCB to become available. */
|
|
if ( tcb->status || tcb->iob ) {
|
|
DBG ( "TX overflow\n" );
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
DBG2 ( "transmitting packet (%zd bytes). status = %hX, cmd=%hX\n",
|
|
iob_len ( iobuf ), tcb->status, inw ( ioaddr + SCBCmd ) );
|
|
|
|
tcb->command = CmdSuspend | CmdTx | CmdTxFlex;
|
|
tcb->count = 0x01208000;
|
|
tcb->tbd_addr0 = virt_to_bus ( iobuf->data );
|
|
tcb->tbd_size0 = 0x3FFF & iob_len ( iobuf );
|
|
tcb->iob = iobuf;
|
|
|
|
ifec_tx_wake ( netdev );
|
|
|
|
/* Append to end of ring. */
|
|
priv->tcb_head = tcb;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*************************** Local support functions *************************/
|
|
|
|
/* Define what each GPIO Pin does */
|
|
static const uint16_t ifec_ee_bits[] = {
|
|
[SPI_BIT_SCLK] = EE_SHIFT_CLK,
|
|
[SPI_BIT_MOSI] = EE_DATA_WRITE,
|
|
[SPI_BIT_MISO] = EE_DATA_READ,
|
|
[SPI_BIT_SS(0)] = EE_ENB,
|
|
};
|
|
|
|
/*
|
|
* Read a single bit from the GPIO pins used for SPI.
|
|
* should be called by SPI bitbash functions only
|
|
*
|
|
* @v basher Bitbash device
|
|
* @v bit_id Line to be read
|
|
*/
|
|
static int ifec_spi_read_bit ( struct bit_basher *basher,
|
|
unsigned int bit_id )
|
|
{
|
|
struct ifec_private *priv =
|
|
container_of ( basher, struct ifec_private, spi.basher );
|
|
unsigned long ee_addr = priv->ioaddr + CSREeprom;
|
|
unsigned int ret = 0;
|
|
uint16_t mask;
|
|
|
|
DBGP ( "ifec_spi_read_bit\n" );
|
|
|
|
mask = ifec_ee_bits[bit_id];
|
|
ret = inw (ee_addr);
|
|
|
|
return ( ret & mask ) ? 1 : 0;
|
|
}
|
|
|
|
/*
|
|
* Write a single bit to the GPIO pins used for SPI.
|
|
* should be called by SPI bitbash functions only
|
|
*
|
|
* @v basher Bitbash device
|
|
* @v bit_id Line to write to
|
|
* @v data Value to write
|
|
*/
|
|
static void ifec_spi_write_bit ( struct bit_basher *basher,
|
|
unsigned int bit_id,
|
|
unsigned long data )
|
|
{
|
|
struct ifec_private *priv =
|
|
container_of ( basher, struct ifec_private, spi.basher );
|
|
unsigned long ee_addr = priv->ioaddr + CSREeprom;
|
|
short val;
|
|
uint16_t mask = ifec_ee_bits[bit_id];
|
|
|
|
DBGP ( "ifec_spi_write_bit\n" );
|
|
|
|
val = inw ( ee_addr );
|
|
val &= ~mask;
|
|
val |= data & mask;
|
|
|
|
outw ( val, ee_addr );
|
|
}
|
|
|
|
/* set function pointer to SPI read- and write-bit functions */
|
|
static struct bit_basher_operations ifec_basher_ops = {
|
|
.read = ifec_spi_read_bit,
|
|
.write = ifec_spi_write_bit,
|
|
};
|
|
|
|
/*
|
|
* Initialize the eeprom stuff
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ifec_init_eeprom ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
|
|
DBGP ( "ifec_init_eeprom\n" );
|
|
|
|
priv->spi.basher.op = &ifec_basher_ops;
|
|
priv->spi.bus.mode = SPI_MODE_THREEWIRE;
|
|
init_spi_bit_basher ( &priv->spi );
|
|
|
|
priv->eeprom.bus = &priv->spi.bus;
|
|
|
|
/* init as 93c46(93c14 compatible) first, to set the command len,
|
|
* block size and word len. Needs to be set for address len detection.
|
|
*/
|
|
init_at93c46 ( &priv->eeprom, 16 );
|
|
|
|
/* detect address length, */
|
|
threewire_detect_address_len ( &priv->eeprom );
|
|
|
|
/* address len == 8 means 93c66 instead of 93c46 */
|
|
if ( priv->eeprom.address_len == 8 )
|
|
init_at93c66 ( &priv->eeprom, 16 );
|
|
}
|
|
|
|
/*
|
|
* Support function: ifec_mdio_read
|
|
*
|
|
* This probably reads a register in the "physical media interface chip".
|
|
* -- REW
|
|
*/
|
|
static int ifec_mdio_read ( struct net_device *netdev, int phy_id,
|
|
int location )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
unsigned long ioaddr = priv->ioaddr;
|
|
int val;
|
|
int boguscnt = 64*4; /* <64 usec. to complete, typ 27 ticks */
|
|
|
|
DBGP ( "ifec_mdio_read\n" );
|
|
|
|
outl ( 0x08000000 | ( location << 16 ) | ( phy_id << 21 ),
|
|
ioaddr + CSRCtrlMDI );
|
|
do {
|
|
udelay ( 16 );
|
|
|
|
val = inl ( ioaddr + CSRCtrlMDI );
|
|
|
|
if ( --boguscnt < 0 ) {
|
|
DBG ( " ifec_mdio_read() time out with val = %X.\n",
|
|
val );
|
|
break;
|
|
}
|
|
} while (! ( val & 0x10000000 ) );
|
|
return val & 0xffff;
|
|
}
|
|
|
|
/*
|
|
* Initializes MDIO.
|
|
*
|
|
* @v netdev Network device
|
|
* @v options MDIO options
|
|
*/
|
|
static void ifec_mdio_setup ( struct net_device *netdev, int options )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
unsigned short mdio_register = priv->mdio_register;
|
|
|
|
DBGP ( "ifec_mdio_setup\n" );
|
|
|
|
if ( ( (mdio_register>>8) & 0x3f ) == DP83840
|
|
|| ( (mdio_register>>8) & 0x3f ) == DP83840A ) {
|
|
int mdi_reg23 = ifec_mdio_read ( netdev, mdio_register
|
|
& 0x1f, 23 ) | 0x0422;
|
|
if (CONGENB)
|
|
mdi_reg23 |= 0x0100;
|
|
DBG2 ( "DP83840 specific setup, setting register 23 to "
|
|
"%hX.\n", mdi_reg23 );
|
|
ifec_mdio_write ( netdev, mdio_register & 0x1f, 23, mdi_reg23 );
|
|
}
|
|
DBG2 ( "dp83840 " );
|
|
if ( options != 0 ) {
|
|
ifec_mdio_write ( netdev, mdio_register & 0x1f, 0,
|
|
( (options & 0x20) ? 0x2000 : 0 ) |
|
|
( (options & 0x10) ? 0x0100 : 0 ) );
|
|
DBG2 ( "set mdio_register. " );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Support function: ifec_mdio_write
|
|
*
|
|
* This probably writes to the "physical media interface chip".
|
|
* -- REW
|
|
*/
|
|
static int ifec_mdio_write ( struct net_device *netdev,
|
|
int phy_id, int location, int value )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
unsigned long ioaddr = priv->ioaddr;
|
|
int val;
|
|
int boguscnt = 64*4; /* <64 usec. to complete, typ 27 ticks */
|
|
|
|
DBGP ( "ifec_mdio_write\n" );
|
|
|
|
outl ( 0x04000000 | ( location << 16 ) | ( phy_id << 21 ) | value,
|
|
ioaddr + CSRCtrlMDI );
|
|
do {
|
|
udelay ( 16 );
|
|
|
|
val = inl ( ioaddr + CSRCtrlMDI );
|
|
if ( --boguscnt < 0 ) {
|
|
DBG ( " ifec_mdio_write() time out with val = %X.\n",
|
|
val );
|
|
break;
|
|
}
|
|
} while (! ( val & 0x10000000 ) );
|
|
return val & 0xffff;
|
|
}
|
|
|
|
/*
|
|
* Resets the hardware.
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ifec_reset ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
unsigned long ioaddr = priv->ioaddr;
|
|
|
|
DBGP ( "ifec_reset\n" );
|
|
|
|
/* do partial reset first */
|
|
outl ( PortPartialReset, ioaddr + CSRPort );
|
|
inw ( ioaddr + SCBStatus );
|
|
udelay ( 20 );
|
|
|
|
/* full reset */
|
|
outl ( PortReset, ioaddr + CSRPort );
|
|
inw ( ioaddr + SCBStatus );
|
|
udelay ( 20 );
|
|
|
|
/* disable interrupts again */
|
|
ifec_net_irq ( netdev, 0 );
|
|
}
|
|
|
|
/*
|
|
* free()s the tx/rx rings.
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ifec_free ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev_priv ( netdev );
|
|
int i;
|
|
|
|
DBGP ( "ifec_free\n" );
|
|
|
|
/* free all allocated receive io_buffers */
|
|
for ( i = 0; i < RFD_COUNT; i++ ) {
|
|
free_iob ( priv->rx_iobs[i] );
|
|
priv->rx_iobs[i] = NULL;
|
|
priv->rfds[i] = NULL;
|
|
}
|
|
|
|
/* free TX ring buffer */
|
|
free_dma ( priv->tcbs, TX_RING_BYTES );
|
|
|
|
priv->tcbs = NULL;
|
|
}
|
|
|
|
/*
|
|
* Initializes an RFD.
|
|
*
|
|
* @v rfd RFD struct to initialize
|
|
* @v command Command word
|
|
* @v link Link value
|
|
*/
|
|
static void ifec_rfd_init ( struct ifec_rfd *rfd, s16 command, u32 link )
|
|
{
|
|
DBGP ( "ifec_rfd_init\n" );
|
|
|
|
rfd->status = 0;
|
|
rfd->command = command;
|
|
rfd->rx_buf_addr = 0xFFFFFFFF;
|
|
rfd->count = 0;
|
|
rfd->size = RFD_PACKET_LEN;
|
|
rfd->link = link;
|
|
}
|
|
|
|
/*
|
|
* Send address of new RFD to card
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ifec_reprime_ru ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
int cur_rx = priv->cur_rx;
|
|
|
|
DBGP ( "ifec_reprime_ru\n" );
|
|
|
|
if ( priv->rfds[cur_rx] != NULL ) {
|
|
ifec_scb_cmd ( netdev, virt_to_bus ( priv->rfds[cur_rx] ),
|
|
RUStart );
|
|
ifec_scb_cmd_wait ( netdev );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check if reprime of RU needed
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ifec_check_ru_status ( struct net_device *netdev,
|
|
unsigned short intr_status )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
|
|
DBGP ( "ifec_check_ru_status\n" );
|
|
|
|
/*
|
|
* The chip may have suspended reception for various reasons.
|
|
* Check for that, and re-prime it should this be the case.
|
|
*/
|
|
switch ( ( intr_status >> 2 ) & 0xf ) {
|
|
case 0: /* Idle */
|
|
case 4: /* Ready */
|
|
break;
|
|
case 1: /* Suspended */
|
|
case 2: /* No resources (RFDs) */
|
|
case 9: /* Suspended with no more RBDs */
|
|
case 10: /* No resources due to no RBDs */
|
|
case 12: /* Ready with no RBDs */
|
|
DBG ( "ifec_net_poll: RU reprimed.\n" );
|
|
ifec_reprime_ru ( netdev );
|
|
break;
|
|
default:
|
|
/* reserved values */
|
|
DBG ( "ifec_net_poll: RU state anomaly: %i\n",
|
|
( inw ( priv->ioaddr + SCBStatus ) >> 2 ) & 0xf );
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define RFD_STATUS ( RFD_OK | RFDRxCol | RFDRxErr | RFDShort | \
|
|
RFDDMAOverrun | RFDNoBufs | RFDCRCError )
|
|
/*
|
|
* Looks for received packets in the rx ring, reports success or error to
|
|
* the core accordingly. Starts reallocation of rx ring.
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ifec_rx_process ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
int cur_rx = priv->cur_rx;
|
|
struct io_buffer *iob = priv->rx_iobs[cur_rx];
|
|
struct ifec_rfd *rfd = priv->rfds[cur_rx];
|
|
unsigned int rx_len;
|
|
s16 status;
|
|
|
|
DBGP ( "ifec_rx_process\n" );
|
|
|
|
/* Process any received packets */
|
|
while ( iob && rfd && ( status = rfd->status ) ) {
|
|
rx_len = rfd->count & RFDMaskCount;
|
|
|
|
DBG2 ( "Got a packet: Len = %d, cur_rx = %d.\n", rx_len,
|
|
cur_rx );
|
|
DBGIO_HD ( (void*)rfd->packet, 0x30 );
|
|
|
|
if ( ( status & ( RFD_STATUS & ~RFDShort ) ) != RFD_OK ) {
|
|
DBG ( "Corrupted packet received. "
|
|
"Status = %#08hx\n", status );
|
|
netdev_rx_err ( netdev, iob, -EINVAL );
|
|
} else {
|
|
/* Hand off the packet to the network subsystem */
|
|
iob_put ( iob, rx_len );
|
|
DBG2 ( "Received packet: %p, len: %d\n", iob, rx_len );
|
|
netdev_rx ( netdev, iob );
|
|
}
|
|
|
|
/* make sure we don't reuse this RFD */
|
|
priv->rx_iobs[cur_rx] = NULL;
|
|
priv->rfds[cur_rx] = NULL;
|
|
|
|
/* Next RFD */
|
|
priv->cur_rx = ( cur_rx + 1 ) % RFD_COUNT;
|
|
cur_rx = priv->cur_rx;
|
|
iob = priv->rx_iobs[cur_rx];
|
|
rfd = priv->rfds[cur_rx];
|
|
}
|
|
|
|
ifec_refill_rx_ring ( netdev );
|
|
}
|
|
|
|
/*
|
|
* Allocates io_buffer, set pointers in ifec_private structure accordingly,
|
|
* reserves space for RFD header in io_buffer.
|
|
*
|
|
* @v netdev Network device
|
|
* @v cur Descriptor number to work on
|
|
* @v cmd Value to set cmd field in RFD to
|
|
* @v link Pointer to ned RFD
|
|
* @ret rc 0 on success, negative on failure
|
|
*/
|
|
static int ifec_get_rx_desc ( struct net_device *netdev, int cur, int cmd,
|
|
int link )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
struct ifec_rfd *rfd = priv->rfds[cur];
|
|
|
|
DBGP ( "ifec_get_rx_desc\n" );
|
|
|
|
priv->rx_iobs[cur] = alloc_iob ( sizeof ( *rfd ) );
|
|
if ( ! priv->rx_iobs[cur] ) {
|
|
DBG ( "alloc_iob failed. desc. nr: %d\n", cur );
|
|
priv->rfds[cur] = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Initialize new tail. */
|
|
priv->rfds[cur] = priv->rx_iobs[cur]->data;
|
|
ifec_rfd_init ( priv->rfds[cur], cmd, link );
|
|
iob_reserve ( priv->rx_iobs[cur], RFD_HEADER_LEN );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Allocate new descriptor entries and initialize them if needed
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ifec_refill_rx_ring ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
int i, cur_rx;
|
|
unsigned short intr_status;
|
|
|
|
DBGP ( "ifec_refill_rx_ring\n" );
|
|
|
|
for ( i = 0; i < RFD_COUNT; i++ ) {
|
|
cur_rx = ( priv->cur_rx + i ) % RFD_COUNT;
|
|
/* only refill if empty */
|
|
if ( priv->rfds[cur_rx] != NULL ||
|
|
priv->rx_iobs[cur_rx] != NULL )
|
|
continue;
|
|
|
|
DBG2 ( "refilling RFD %d\n", cur_rx );
|
|
|
|
if ( ifec_get_rx_desc ( netdev, cur_rx,
|
|
CmdSuspend | CmdEndOfList, 0 ) == 0 ) {
|
|
if ( i > 0 ) {
|
|
int prev_rx = ( ( ( cur_rx + RFD_COUNT ) - 1 )
|
|
% RFD_COUNT );
|
|
struct ifec_rfd *rfd = priv->rfds[prev_rx];
|
|
|
|
rfd->command = 0;
|
|
rfd->link = virt_to_bus ( priv->rfds[cur_rx] );
|
|
}
|
|
}
|
|
}
|
|
|
|
intr_status = inw ( priv->ioaddr + SCBStatus );
|
|
ifec_check_ru_status ( netdev, intr_status );
|
|
}
|
|
|
|
/*
|
|
* Initial allocation & initialization of the rx ring.
|
|
*
|
|
* @v netdev Device of rx ring.
|
|
* @ret rc Non-zero if error occured
|
|
*/
|
|
static int ifec_rx_setup ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
int i;
|
|
|
|
DBGP ( "ifec_rx_setup\n" );
|
|
|
|
priv->cur_rx = 0;
|
|
|
|
/* init values for ifec_refill_rx_ring() */
|
|
for ( i = 0; i < RFD_COUNT; i++ ) {
|
|
priv->rfds[i] = NULL;
|
|
priv->rx_iobs[i] = NULL;
|
|
}
|
|
ifec_refill_rx_ring ( netdev );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initiates a SCB command.
|
|
*
|
|
* @v netdev Network device
|
|
* @v ptr General pointer value for command.
|
|
* @v cmd Command to issue.
|
|
* @ret rc Non-zero if command not issued.
|
|
*/
|
|
static int ifec_scb_cmd ( struct net_device *netdev, u32 ptr, u8 cmd )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
unsigned long ioaddr = priv->ioaddr;
|
|
int rc;
|
|
|
|
DBGP ( "ifec_scb_cmd\n" );
|
|
|
|
rc = ifec_scb_cmd_wait ( netdev ); /* Wait until ready */
|
|
if ( !rc ) {
|
|
outl ( ptr, ioaddr + SCBPointer );
|
|
outb ( cmd, ioaddr + SCBCmd ); /* Issue command */
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Wait for command unit to accept a command.
|
|
*
|
|
* @v cmd_ioaddr I/O address of command register.
|
|
* @ret rc Non-zero if command timed out.
|
|
*/
|
|
static int ifec_scb_cmd_wait ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
unsigned long cmd_ioaddr = priv->ioaddr + SCBCmd;
|
|
int rc, wait = CU_CMD_TIMEOUT;
|
|
|
|
DBGP ( "ifec_scb_cmd_wait\n" );
|
|
|
|
for ( ; wait && ( rc = inb ( cmd_ioaddr ) ); wait-- )
|
|
udelay ( 1 );
|
|
|
|
if ( !wait )
|
|
DBG ( "ifec_scb_cmd_wait timeout!\n" );
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Check status of transmitted packets & perform tx completions.
|
|
*
|
|
* @v netdev Network device.
|
|
*/
|
|
static void ifec_tx_process ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
struct ifec_tcb *tcb = priv->tcb_tail;
|
|
s16 status;
|
|
|
|
DBGP ( "ifec_tx_process\n" );
|
|
|
|
/* Check status of transmitted packets */
|
|
while ( ( status = tcb->status ) && tcb->iob ) {
|
|
if ( status & TCB_U ) {
|
|
/* report error to iPXE */
|
|
DBG ( "ifec_tx_process : tx error!\n " );
|
|
netdev_tx_complete_err ( netdev, tcb->iob, -EINVAL );
|
|
} else {
|
|
/* report successful transmit */
|
|
netdev_tx_complete ( netdev, tcb->iob );
|
|
}
|
|
DBG2 ( "tx completion\n" );
|
|
|
|
tcb->iob = NULL;
|
|
tcb->status = 0;
|
|
|
|
priv->tcb_tail = tcb->next; /* Next TCB */
|
|
tcb = tcb->next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Allocates & initialize tx resources.
|
|
*
|
|
* @v netdev Network device.
|
|
* @ret rc Non-zero if error occurred.
|
|
*/
|
|
static int ifec_tx_setup ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
struct ifec_tcb *tcb;
|
|
int i;
|
|
|
|
DBGP ( "ifec_tx_setup\n" );
|
|
|
|
/* allocate tx ring */
|
|
priv->tcbs = malloc_dma ( TX_RING_BYTES, CB_ALIGN );
|
|
if ( !priv->tcbs ) {
|
|
DBG ( "TX-ring allocation failed\n" );
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tcb = priv->tcb_tail = priv->tcbs;
|
|
priv->tx_curr = priv->tx_tail = 0;
|
|
priv->tx_cnt = 0;
|
|
|
|
for ( i = 0; i < TCB_COUNT; i++, tcb++ ) {
|
|
tcb->status = 0;
|
|
tcb->count = 0x01208000;
|
|
tcb->iob = NULL;
|
|
tcb->tbda_addr = virt_to_bus ( &tcb->tbd_addr0 );
|
|
tcb->link = virt_to_bus ( tcb + 1 );
|
|
tcb->next = tcb + 1;
|
|
}
|
|
/* We point tcb_head at the last TCB, so the first ifec_net_transmit()
|
|
* will use the first (head->next) TCB to transmit. */
|
|
priv->tcb_head = --tcb;
|
|
tcb->link = virt_to_bus ( priv->tcbs );
|
|
tcb->next = priv->tcbs;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Wake up the Command Unit and issue a Resume/Start.
|
|
*
|
|
* @v netdev Network device containing Command Unit
|
|
*
|
|
* The time between clearing the S bit and issuing Resume must be as short as
|
|
* possible to prevent a race condition. As noted in linux eepro100.c :
|
|
* Note: Watch out for the potential race condition here: imagine
|
|
* erasing the previous suspend
|
|
* the chip processes the previous command
|
|
* the chip processes the final command, and suspends
|
|
* doing the CU_RESUME
|
|
* the chip processes the next-yet-valid post-final-command.
|
|
* So blindly sending a CU_RESUME is only safe if we do it immediately after
|
|
* erasing the previous CmdSuspend, without the possibility of an intervening
|
|
* delay.
|
|
*/
|
|
void ifec_tx_wake ( struct net_device *netdev )
|
|
{
|
|
struct ifec_private *priv = netdev->priv;
|
|
unsigned long ioaddr = priv->ioaddr;
|
|
struct ifec_tcb *tcb = priv->tcb_head->next;
|
|
|
|
DBGP ( "ifec_tx_wake\n" );
|
|
|
|
/* For the special case of the first transmit, we issue a START. The
|
|
* card won't RESUME after the configure command. */
|
|
if ( priv->configured ) {
|
|
priv->configured = 0;
|
|
ifec_scb_cmd ( netdev, virt_to_bus ( tcb ), CUStart );
|
|
ifec_scb_cmd_wait ( netdev );
|
|
return;
|
|
}
|
|
|
|
/* Resume if suspended. */
|
|
switch ( ( inw ( ioaddr + SCBStatus ) >> 6 ) & 0x3 ) {
|
|
case 0: /* Idle - We should not reach this state. */
|
|
DBG2 ( "ifec_tx_wake: tx idle!\n" );
|
|
ifec_scb_cmd ( netdev, virt_to_bus ( tcb ), CUStart );
|
|
ifec_scb_cmd_wait ( netdev );
|
|
return;
|
|
case 1: /* Suspended */
|
|
DBG2 ( "s" );
|
|
break;
|
|
default: /* Active */
|
|
DBG2 ( "a" );
|
|
}
|
|
ifec_scb_cmd_wait ( netdev );
|
|
outl ( 0, ioaddr + SCBPointer );
|
|
priv->tcb_head->command &= ~CmdSuspend;
|
|
/* Immediately issue Resume command */
|
|
outb ( CUResume, ioaddr + SCBCmd );
|
|
ifec_scb_cmd_wait ( netdev );
|
|
}
|
|
|
|
/*********************************************************************/
|
|
|
|
static struct pci_device_id ifec_nics[] = {
|
|
PCI_ROM(0x8086, 0x1029, "id1029", "Intel EtherExpressPro100 ID1029", 0),
|
|
PCI_ROM(0x8086, 0x1030, "id1030", "Intel EtherExpressPro100 ID1030", 0),
|
|
PCI_ROM(0x8086, 0x1031, "82801cam", "Intel 82801CAM (ICH3) Chipset Ethernet Controller", 0),
|
|
PCI_ROM(0x8086, 0x1032, "eepro100-1032", "Intel PRO/100 VE Network Connection", 0),
|
|
PCI_ROM(0x8086, 0x1033, "eepro100-1033", "Intel PRO/100 VM Network Connection", 0),
|
|
PCI_ROM(0x8086, 0x1034, "eepro100-1034", "Intel PRO/100 VM Network Connection", 0),
|
|
PCI_ROM(0x8086, 0x1035, "eepro100-1035", "Intel 82801CAM (ICH3) Chipset Ethernet Controller", 0),
|
|
PCI_ROM(0x8086, 0x1036, "eepro100-1036", "Intel 82801CAM (ICH3) Chipset Ethernet Controller", 0),
|
|
PCI_ROM(0x8086, 0x1037, "eepro100-1037", "Intel 82801CAM (ICH3) Chipset Ethernet Controller", 0),
|
|
PCI_ROM(0x8086, 0x1038, "id1038", "Intel PRO/100 VM Network Connection", 0),
|
|
PCI_ROM(0x8086, 0x1039, "82562et", "Intel PRO100 VE 82562ET", 0),
|
|
PCI_ROM(0x8086, 0x103a, "id103a", "Intel Corporation 82559 InBusiness 10/100", 0),
|
|
PCI_ROM(0x8086, 0x103b, "82562etb", "Intel PRO100 VE 82562ETB", 0),
|
|
PCI_ROM(0x8086, 0x103c, "eepro100-103c", "Intel PRO/100 VM Network Connection", 0),
|
|
PCI_ROM(0x8086, 0x103d, "eepro100-103d", "Intel PRO/100 VE Network Connection", 0),
|
|
PCI_ROM(0x8086, 0x103e, "eepro100-103e", "Intel PRO/100 VM Network Connection", 0),
|
|
PCI_ROM(0x8086, 0x1051, "prove", "Intel PRO/100 VE Network Connection", 0),
|
|
PCI_ROM(0x8086, 0x1059, "82551qm", "Intel PRO/100 M Mobile Connection", 0),
|
|
PCI_ROM(0x8086, 0x1209, "82559er", "Intel EtherExpressPro100 82559ER", 0),
|
|
PCI_ROM(0x8086, 0x1227, "82865", "Intel 82865 EtherExpress PRO/100A", 0),
|
|
PCI_ROM(0x8086, 0x1228, "82556", "Intel 82556 EtherExpress PRO/100 Smart", 0),
|
|
PCI_ROM(0x8086, 0x1229, "eepro100", "Intel EtherExpressPro100", 0),
|
|
PCI_ROM(0x8086, 0x2449, "82562em", "Intel EtherExpressPro100 82562EM", 0),
|
|
PCI_ROM(0x8086, 0x2459, "82562-1", "Intel 82562 based Fast Ethernet Connection", 0),
|
|
PCI_ROM(0x8086, 0x245d, "82562-2", "Intel 82562 based Fast Ethernet Connection", 0),
|
|
PCI_ROM(0x8086, 0x1050, "82562ez", "Intel 82562EZ Network Connection", 0),
|
|
PCI_ROM(0x8086, 0x1051, "eepro100-1051", "Intel 82801EB/ER (ICH5/ICH5R) Chipset Ethernet Controller", 0),
|
|
PCI_ROM(0x8086, 0x1065, "82562-3", "Intel 82562 based Fast Ethernet Connection", 0),
|
|
PCI_ROM(0x8086, 0x5200, "eepro100-5200", "Intel EtherExpress PRO/100 Intelligent Server", 0),
|
|
PCI_ROM(0x8086, 0x5201, "eepro100-5201", "Intel EtherExpress PRO/100 Intelligent Server", 0),
|
|
PCI_ROM(0x8086, 0x1092, "82562-3", "Intel Pro/100 VE Network", 0),
|
|
PCI_ROM(0x8086, 0x27dc, "eepro100-27dc", "Intel 82801G (ICH7) Chipset Ethernet Controller", 0),
|
|
};
|
|
|
|
/* Cards with device ids 0x1030 to 0x103F, 0x2449, 0x2459 or 0x245D might need
|
|
* a workaround for hardware bug on 10 mbit half duplex (see linux driver eepro100.c)
|
|
* 2003/03/17 gbaum */
|
|
|
|
struct pci_driver ifec_driver __pci_driver = {
|
|
.ids = ifec_nics,
|
|
.id_count = ( sizeof (ifec_nics) / sizeof (ifec_nics[0]) ),
|
|
.probe = ifec_pci_probe,
|
|
.remove = ifec_pci_remove
|
|
};
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-basic-offset: 8
|
|
* c-indent-level: 8
|
|
* tab-width: 8
|
|
* End:
|
|
*/
|