![Michael Brown](/assets/img/avatar_default.png)
It is unclear from the datasheets whether or not the TX ring can be completely filled (i.e. whether writing the tail value as equal to the current head value will cause the ring to be treated as completely full or completely empty). It is very plausible that this edge case could differ in behaviour between real hardware and the many implementations of an emulated Intel NIC found in various virtual machines. Err on the side of caution and always leave at least one ring entry empty. Signed-off-by: Michael Brown <mcb30@ipxe.org>
973 lines
29 KiB
C
973 lines
29 KiB
C
/*
|
|
* Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
|
|
*
|
|
* 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 of the
|
|
* License, 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., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*/
|
|
|
|
FILE_LICENCE ( GPL2_OR_LATER );
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <byteswap.h>
|
|
#include <ipxe/netdevice.h>
|
|
#include <ipxe/ethernet.h>
|
|
#include <ipxe/if_ether.h>
|
|
#include <ipxe/iobuf.h>
|
|
#include <ipxe/malloc.h>
|
|
#include <ipxe/pci.h>
|
|
#include "intel.h"
|
|
|
|
/** @file
|
|
*
|
|
* Intel 10/100/1000 network card driver
|
|
*
|
|
*/
|
|
|
|
/******************************************************************************
|
|
*
|
|
* EEPROM interface
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Read data from EEPROM
|
|
*
|
|
* @v nvs NVS device
|
|
* @v address Address from which to read
|
|
* @v data Data buffer
|
|
* @v len Length of data buffer
|
|
* @ret rc Return status code
|
|
*/
|
|
static int intel_read_eeprom ( struct nvs_device *nvs, unsigned int address,
|
|
void *data, size_t len ) {
|
|
struct intel_nic *intel =
|
|
container_of ( nvs, struct intel_nic, eeprom );
|
|
unsigned int i;
|
|
uint32_t value;
|
|
uint16_t *data_word = data;
|
|
|
|
/* Sanity check. We advertise a blocksize of one word, so
|
|
* should only ever receive single-word requests.
|
|
*/
|
|
assert ( len == sizeof ( *data_word ) );
|
|
|
|
/* Initiate read */
|
|
writel ( ( INTEL_EERD_START | ( address << intel->eerd_addr_shift ) ),
|
|
intel->regs + INTEL_EERD );
|
|
|
|
/* Wait for read to complete */
|
|
for ( i = 0 ; i < INTEL_EEPROM_MAX_WAIT_MS ; i++ ) {
|
|
|
|
/* If read is not complete, delay 1ms and retry */
|
|
value = readl ( intel->regs + INTEL_EERD );
|
|
if ( ! ( value & intel->eerd_done ) ) {
|
|
mdelay ( 1 );
|
|
continue;
|
|
}
|
|
|
|
/* Extract data */
|
|
*data_word = cpu_to_le16 ( INTEL_EERD_DATA ( value ) );
|
|
return 0;
|
|
}
|
|
|
|
DBGC ( intel, "INTEL %p timed out waiting for EEPROM read\n", intel );
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/**
|
|
* Write data to EEPROM
|
|
*
|
|
* @v nvs NVS device
|
|
* @v address Address to which to write
|
|
* @v data Data buffer
|
|
* @v len Length of data buffer
|
|
* @ret rc Return status code
|
|
*/
|
|
static int intel_write_eeprom ( struct nvs_device *nvs,
|
|
unsigned int address __unused,
|
|
const void *data __unused,
|
|
size_t len __unused ) {
|
|
struct intel_nic *intel =
|
|
container_of ( nvs, struct intel_nic, eeprom );
|
|
|
|
DBGC ( intel, "INTEL %p EEPROM write not supported\n", intel );
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/**
|
|
* Initialise EEPROM
|
|
*
|
|
* @v intel Intel device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int intel_init_eeprom ( struct intel_nic *intel ) {
|
|
unsigned int i;
|
|
uint32_t value;
|
|
|
|
/* The NIC automatically detects the type of attached EEPROM.
|
|
* The EERD register provides access to only a single word at
|
|
* a time, so we pretend to have a single-word block size.
|
|
*
|
|
* The EEPROM size may be larger than the minimum size, but
|
|
* this doesn't matter to us since we access only the first
|
|
* few words.
|
|
*/
|
|
intel->eeprom.word_len_log2 = INTEL_EEPROM_WORD_LEN_LOG2;
|
|
intel->eeprom.size = INTEL_EEPROM_MIN_SIZE_WORDS;
|
|
intel->eeprom.block_size = 1;
|
|
intel->eeprom.read = intel_read_eeprom;
|
|
intel->eeprom.write = intel_write_eeprom;
|
|
|
|
/* The layout of the EERD register was changed at some point
|
|
* to accommodate larger EEPROMs. Read from address zero (for
|
|
* which the request layouts are compatible) to determine
|
|
* which type of register we have.
|
|
*/
|
|
writel ( INTEL_EERD_START, intel->regs + INTEL_EERD );
|
|
for ( i = 0 ; i < INTEL_EEPROM_MAX_WAIT_MS ; i++ ) {
|
|
value = readl ( intel->regs + INTEL_EERD );
|
|
if ( value & INTEL_EERD_DONE_LARGE ) {
|
|
DBGC ( intel, "INTEL %p has large-format EERD\n",
|
|
intel );
|
|
intel->eerd_done = INTEL_EERD_DONE_LARGE;
|
|
intel->eerd_addr_shift = INTEL_EERD_ADDR_SHIFT_LARGE;
|
|
return 0;
|
|
}
|
|
if ( value & INTEL_EERD_DONE_SMALL ) {
|
|
DBGC ( intel, "INTEL %p has small-format EERD\n",
|
|
intel );
|
|
intel->eerd_done = INTEL_EERD_DONE_SMALL;
|
|
intel->eerd_addr_shift = INTEL_EERD_ADDR_SHIFT_SMALL;
|
|
return 0;
|
|
}
|
|
mdelay ( 1 );
|
|
}
|
|
|
|
DBGC ( intel, "INTEL %p timed out waiting for initial EEPROM read "
|
|
"(value %08x)\n", intel, value );
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* MAC address
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Fetch initial MAC address from EEPROM
|
|
*
|
|
* @v intel Intel device
|
|
* @v hw_addr Hardware address to fill in
|
|
* @ret rc Return status code
|
|
*/
|
|
static int intel_fetch_mac_eeprom ( struct intel_nic *intel,
|
|
uint8_t *hw_addr ) {
|
|
int rc;
|
|
|
|
/* Initialise EEPROM */
|
|
if ( ( rc = intel_init_eeprom ( intel ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Read base MAC address from EEPROM */
|
|
if ( ( rc = nvs_read ( &intel->eeprom, INTEL_EEPROM_MAC,
|
|
hw_addr, ETH_ALEN ) ) != 0 ) {
|
|
DBGC ( intel, "INTEL %p could not read EEPROM base MAC "
|
|
"address: %s\n", intel, strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
/* Adjust MAC address for multi-port devices */
|
|
hw_addr[ETH_ALEN-1] ^= intel->port;
|
|
|
|
DBGC ( intel, "INTEL %p has EEPROM MAC address %s (port %d)\n",
|
|
intel, eth_ntoa ( hw_addr ), intel->port );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Fetch initial MAC address
|
|
*
|
|
* @v intel Intel device
|
|
* @v hw_addr Hardware address to fill in
|
|
* @ret rc Return status code
|
|
*/
|
|
static int intel_fetch_mac ( struct intel_nic *intel, uint8_t *hw_addr ) {
|
|
union intel_receive_address mac;
|
|
int rc;
|
|
|
|
/* Read current address from RAL0/RAH0 */
|
|
mac.reg.low = cpu_to_le32 ( readl ( intel->regs + INTEL_RAL0 ) );
|
|
mac.reg.high = cpu_to_le32 ( readl ( intel->regs + INTEL_RAH0 ) );
|
|
DBGC ( intel, "INTEL %p has autoloaded MAC address %s\n",
|
|
intel, eth_ntoa ( mac.raw ) );
|
|
|
|
/* Try to read address from EEPROM */
|
|
if ( ( rc = intel_fetch_mac_eeprom ( intel, hw_addr ) ) == 0 )
|
|
return 0;
|
|
|
|
/* Use current address if valid */
|
|
if ( is_valid_ether_addr ( mac.raw ) ) {
|
|
memcpy ( hw_addr, mac.raw, ETH_ALEN );
|
|
return 0;
|
|
}
|
|
|
|
DBGC ( intel, "INTEL %p has no MAC address to use\n", intel );
|
|
return -ENOENT;
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Diagnostics
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Dump diagnostic information
|
|
*
|
|
* @v intel Intel device
|
|
*/
|
|
static void __attribute__ (( unused )) intel_diag ( struct intel_nic *intel ) {
|
|
|
|
DBGC ( intel, "INTEL %p TX %04x(%02x)/%04x(%02x) "
|
|
"RX %04x(%02x)/%04x(%02x)\n", intel,
|
|
( intel->tx.cons & 0xffff ),
|
|
readl ( intel->regs + intel->tx.reg + INTEL_xDH ),
|
|
( intel->tx.prod & 0xffff ),
|
|
readl ( intel->regs + intel->tx.reg + INTEL_xDT ),
|
|
( intel->rx.cons & 0xffff ),
|
|
readl ( intel->regs + intel->rx.reg + INTEL_xDH ),
|
|
( intel->rx.prod & 0xffff ),
|
|
readl ( intel->regs + intel->rx.reg + INTEL_xDT ) );
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Device reset
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Reset hardware
|
|
*
|
|
* @v intel Intel device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int intel_reset ( struct intel_nic *intel ) {
|
|
uint32_t pbs;
|
|
uint32_t ctrl;
|
|
uint32_t status;
|
|
|
|
/* Force RX and TX packet buffer allocation, to work around an
|
|
* errata in ICH devices.
|
|
*/
|
|
pbs = readl ( intel->regs + INTEL_PBS );
|
|
if ( ( pbs == 0x14 ) || ( pbs == 0x18 ) ) {
|
|
DBGC ( intel, "INTEL %p WARNING: applying ICH PBS/PBA errata\n",
|
|
intel );
|
|
writel ( 0x08, intel->regs + INTEL_PBA );
|
|
writel ( 0x10, intel->regs + INTEL_PBS );
|
|
}
|
|
|
|
/* Always reset MAC. Required to reset the TX and RX rings. */
|
|
ctrl = readl ( intel->regs + INTEL_CTRL );
|
|
writel ( ( ctrl | INTEL_CTRL_RST ), intel->regs + INTEL_CTRL );
|
|
mdelay ( INTEL_RESET_DELAY_MS );
|
|
|
|
/* Set a sensible default configuration */
|
|
ctrl |= ( INTEL_CTRL_SLU | INTEL_CTRL_ASDE );
|
|
ctrl &= ~( INTEL_CTRL_LRST | INTEL_CTRL_FRCSPD | INTEL_CTRL_FRCDPLX );
|
|
writel ( ctrl, intel->regs + INTEL_CTRL );
|
|
mdelay ( INTEL_RESET_DELAY_MS );
|
|
|
|
/* If link is already up, do not attempt to reset the PHY. On
|
|
* some models (notably ICH), performing a PHY reset seems to
|
|
* drop the link speed to 10Mbps.
|
|
*/
|
|
status = readl ( intel->regs + INTEL_STATUS );
|
|
if ( status & INTEL_STATUS_LU ) {
|
|
DBGC ( intel, "INTEL %p MAC reset (ctrl %08x)\n",
|
|
intel, ctrl );
|
|
return 0;
|
|
}
|
|
|
|
/* Reset PHY and MAC simultaneously */
|
|
writel ( ( ctrl | INTEL_CTRL_RST | INTEL_CTRL_PHY_RST ),
|
|
intel->regs + INTEL_CTRL );
|
|
mdelay ( INTEL_RESET_DELAY_MS );
|
|
|
|
/* PHY reset is not self-clearing on all models */
|
|
writel ( ctrl, intel->regs + INTEL_CTRL );
|
|
mdelay ( INTEL_RESET_DELAY_MS );
|
|
|
|
DBGC ( intel, "INTEL %p MAC+PHY reset (ctrl %08x)\n", intel, ctrl );
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Link state
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Check link state
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void intel_check_link ( struct net_device *netdev ) {
|
|
struct intel_nic *intel = netdev->priv;
|
|
uint32_t status;
|
|
|
|
/* Read link status */
|
|
status = readl ( intel->regs + INTEL_STATUS );
|
|
DBGC ( intel, "INTEL %p link status is %08x\n", intel, status );
|
|
|
|
/* Update network device */
|
|
if ( status & INTEL_STATUS_LU ) {
|
|
netdev_link_up ( netdev );
|
|
} else {
|
|
netdev_link_down ( netdev );
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Network device interface
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Create descriptor ring
|
|
*
|
|
* @v intel Intel device
|
|
* @v ring Descriptor ring
|
|
* @ret rc Return status code
|
|
*/
|
|
int intel_create_ring ( struct intel_nic *intel, struct intel_ring *ring ) {
|
|
physaddr_t address;
|
|
uint32_t dctl;
|
|
|
|
/* Allocate descriptor ring. Align ring on its own size to
|
|
* prevent any possible page-crossing errors due to hardware
|
|
* errata.
|
|
*/
|
|
ring->desc = malloc_dma ( ring->len, ring->len );
|
|
if ( ! ring->desc )
|
|
return -ENOMEM;
|
|
|
|
/* Initialise descriptor ring */
|
|
memset ( ring->desc, 0, ring->len );
|
|
|
|
/* Program ring address */
|
|
address = virt_to_bus ( ring->desc );
|
|
writel ( ( address & 0xffffffffUL ),
|
|
( intel->regs + ring->reg + INTEL_xDBAL ) );
|
|
if ( sizeof ( physaddr_t ) > sizeof ( uint32_t ) ) {
|
|
writel ( ( ( ( uint64_t ) address ) >> 32 ),
|
|
( intel->regs + ring->reg + INTEL_xDBAH ) );
|
|
} else {
|
|
writel ( 0, intel->regs + ring->reg + INTEL_xDBAH );
|
|
}
|
|
|
|
/* Program ring length */
|
|
writel ( ring->len, ( intel->regs + ring->reg + INTEL_xDLEN ) );
|
|
|
|
/* Reset head and tail pointers */
|
|
writel ( 0, ( intel->regs + ring->reg + INTEL_xDH ) );
|
|
writel ( 0, ( intel->regs + ring->reg + INTEL_xDT ) );
|
|
|
|
/* Enable ring */
|
|
dctl = readl ( intel->regs + ring->reg + INTEL_xDCTL );
|
|
dctl |= INTEL_xDCTL_ENABLE;
|
|
writel ( dctl, intel->regs + ring->reg + INTEL_xDCTL );
|
|
|
|
DBGC ( intel, "INTEL %p ring %05x is at [%08llx,%08llx)\n",
|
|
intel, ring->reg, ( ( unsigned long long ) address ),
|
|
( ( unsigned long long ) address + ring->len ) );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Destroy descriptor ring
|
|
*
|
|
* @v intel Intel device
|
|
* @v ring Descriptor ring
|
|
*/
|
|
void intel_destroy_ring ( struct intel_nic *intel, struct intel_ring *ring ) {
|
|
|
|
/* Clear ring length */
|
|
writel ( 0, ( intel->regs + ring->reg + INTEL_xDLEN ) );
|
|
|
|
/* Clear ring address */
|
|
writel ( 0, ( intel->regs + ring->reg + INTEL_xDBAL ) );
|
|
writel ( 0, ( intel->regs + ring->reg + INTEL_xDBAH ) );
|
|
|
|
/* Free descriptor ring */
|
|
free_dma ( ring->desc, ring->len );
|
|
ring->desc = NULL;
|
|
ring->prod = 0;
|
|
ring->cons = 0;
|
|
}
|
|
|
|
/**
|
|
* Refill receive descriptor ring
|
|
*
|
|
* @v intel Intel device
|
|
*/
|
|
void intel_refill_rx ( struct intel_nic *intel ) {
|
|
struct intel_descriptor *rx;
|
|
struct io_buffer *iobuf;
|
|
unsigned int rx_idx;
|
|
unsigned int rx_tail;
|
|
physaddr_t address;
|
|
|
|
while ( ( intel->rx.prod - intel->rx.cons ) < INTEL_RX_FILL ) {
|
|
|
|
/* Allocate I/O buffer */
|
|
iobuf = alloc_iob ( INTEL_RX_MAX_LEN );
|
|
if ( ! iobuf ) {
|
|
/* Wait for next refill */
|
|
return;
|
|
}
|
|
|
|
/* Get next receive descriptor */
|
|
rx_idx = ( intel->rx.prod++ % INTEL_NUM_RX_DESC );
|
|
rx_tail = ( intel->rx.prod % INTEL_NUM_RX_DESC );
|
|
rx = &intel->rx.desc[rx_idx];
|
|
|
|
/* Populate receive descriptor */
|
|
address = virt_to_bus ( iobuf->data );
|
|
rx->address = cpu_to_le64 ( address );
|
|
rx->length = 0;
|
|
rx->status = 0;
|
|
rx->errors = 0;
|
|
wmb();
|
|
|
|
/* Record I/O buffer */
|
|
assert ( intel->rx_iobuf[rx_idx] == NULL );
|
|
intel->rx_iobuf[rx_idx] = iobuf;
|
|
|
|
/* Push descriptor to card */
|
|
writel ( rx_tail, intel->regs + intel->rx.reg + INTEL_xDT );
|
|
|
|
DBGC2 ( intel, "INTEL %p RX %d is [%llx,%llx)\n", intel, rx_idx,
|
|
( ( unsigned long long ) address ),
|
|
( ( unsigned long long ) address + INTEL_RX_MAX_LEN ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Discard unused receive I/O buffers
|
|
*
|
|
* @v intel Intel device
|
|
*/
|
|
void intel_empty_rx ( struct intel_nic *intel ) {
|
|
unsigned int i;
|
|
|
|
for ( i = 0 ; i < INTEL_NUM_RX_DESC ; i++ ) {
|
|
if ( intel->rx_iobuf[i] )
|
|
free_iob ( intel->rx_iobuf[i] );
|
|
intel->rx_iobuf[i] = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open network device
|
|
*
|
|
* @v netdev Network device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int intel_open ( struct net_device *netdev ) {
|
|
struct intel_nic *intel = netdev->priv;
|
|
union intel_receive_address mac;
|
|
uint32_t tctl;
|
|
uint32_t rctl;
|
|
int rc;
|
|
|
|
/* Create transmit descriptor ring */
|
|
if ( ( rc = intel_create_ring ( intel, &intel->tx ) ) != 0 )
|
|
goto err_create_tx;
|
|
|
|
/* Create receive descriptor ring */
|
|
if ( ( rc = intel_create_ring ( intel, &intel->rx ) ) != 0 )
|
|
goto err_create_rx;
|
|
|
|
/* Program MAC address */
|
|
memset ( &mac, 0, sizeof ( mac ) );
|
|
memcpy ( mac.raw, netdev->ll_addr, sizeof ( mac.raw ) );
|
|
writel ( le32_to_cpu ( mac.reg.low ), intel->regs + INTEL_RAL0 );
|
|
writel ( ( le32_to_cpu ( mac.reg.high ) | INTEL_RAH0_AV ),
|
|
intel->regs + INTEL_RAH0 );
|
|
|
|
/* Enable transmitter */
|
|
tctl = readl ( intel->regs + INTEL_TCTL );
|
|
tctl &= ~( INTEL_TCTL_CT_MASK | INTEL_TCTL_COLD_MASK );
|
|
tctl |= ( INTEL_TCTL_EN | INTEL_TCTL_PSP | INTEL_TCTL_CT_DEFAULT |
|
|
INTEL_TCTL_COLD_DEFAULT );
|
|
writel ( tctl, intel->regs + INTEL_TCTL );
|
|
|
|
/* Enable receiver */
|
|
rctl = readl ( intel->regs + INTEL_RCTL );
|
|
rctl &= ~( INTEL_RCTL_BSIZE_BSEX_MASK );
|
|
rctl |= ( INTEL_RCTL_EN | INTEL_RCTL_UPE | INTEL_RCTL_MPE |
|
|
INTEL_RCTL_BAM | INTEL_RCTL_BSIZE_2048 | INTEL_RCTL_SECRC );
|
|
writel ( rctl, intel->regs + INTEL_RCTL );
|
|
|
|
/* Fill receive ring */
|
|
intel_refill_rx ( intel );
|
|
|
|
/* Update link state */
|
|
intel_check_link ( netdev );
|
|
|
|
return 0;
|
|
|
|
intel_destroy_ring ( intel, &intel->rx );
|
|
err_create_rx:
|
|
intel_destroy_ring ( intel, &intel->tx );
|
|
err_create_tx:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Close network device
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void intel_close ( struct net_device *netdev ) {
|
|
struct intel_nic *intel = netdev->priv;
|
|
|
|
/* Disable receiver */
|
|
writel ( 0, intel->regs + INTEL_RCTL );
|
|
|
|
/* Disable transmitter */
|
|
writel ( 0, intel->regs + INTEL_TCTL );
|
|
|
|
/* Destroy receive descriptor ring */
|
|
intel_destroy_ring ( intel, &intel->rx );
|
|
|
|
/* Discard any unused receive buffers */
|
|
intel_empty_rx ( intel );
|
|
|
|
/* Destroy transmit descriptor ring */
|
|
intel_destroy_ring ( intel, &intel->tx );
|
|
|
|
/* Reset the NIC, to flush the transmit and receive FIFOs */
|
|
intel_reset ( intel );
|
|
}
|
|
|
|
/**
|
|
* Transmit packet
|
|
*
|
|
* @v netdev Network device
|
|
* @v iobuf I/O buffer
|
|
* @ret rc Return status code
|
|
*/
|
|
int intel_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) {
|
|
struct intel_nic *intel = netdev->priv;
|
|
struct intel_descriptor *tx;
|
|
unsigned int tx_idx;
|
|
unsigned int tx_tail;
|
|
physaddr_t address;
|
|
|
|
/* Get next transmit descriptor */
|
|
if ( ( intel->tx.prod - intel->tx.cons ) >= INTEL_TX_FILL ) {
|
|
DBGC ( intel, "INTEL %p out of transmit descriptors\n", intel );
|
|
return -ENOBUFS;
|
|
}
|
|
tx_idx = ( intel->tx.prod++ % INTEL_NUM_TX_DESC );
|
|
tx_tail = ( intel->tx.prod % INTEL_NUM_TX_DESC );
|
|
tx = &intel->tx.desc[tx_idx];
|
|
|
|
/* Populate transmit descriptor */
|
|
address = virt_to_bus ( iobuf->data );
|
|
tx->address = cpu_to_le64 ( address );
|
|
tx->length = cpu_to_le16 ( iob_len ( iobuf ) );
|
|
tx->command = ( INTEL_DESC_CMD_RS | INTEL_DESC_CMD_IFCS |
|
|
INTEL_DESC_CMD_EOP );
|
|
tx->status = 0;
|
|
wmb();
|
|
|
|
/* Notify card that there are packets ready to transmit */
|
|
writel ( tx_tail, intel->regs + intel->tx.reg + INTEL_xDT );
|
|
|
|
DBGC2 ( intel, "INTEL %p TX %d is [%llx,%llx)\n", intel, tx_idx,
|
|
( ( unsigned long long ) address ),
|
|
( ( unsigned long long ) address + iob_len ( iobuf ) ) );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Poll for completed packets
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
void intel_poll_tx ( struct net_device *netdev ) {
|
|
struct intel_nic *intel = netdev->priv;
|
|
struct intel_descriptor *tx;
|
|
unsigned int tx_idx;
|
|
|
|
/* Check for completed packets */
|
|
while ( intel->tx.cons != intel->tx.prod ) {
|
|
|
|
/* Get next transmit descriptor */
|
|
tx_idx = ( intel->tx.cons % INTEL_NUM_TX_DESC );
|
|
tx = &intel->tx.desc[tx_idx];
|
|
|
|
/* Stop if descriptor is still in use */
|
|
if ( ! ( tx->status & INTEL_DESC_STATUS_DD ) )
|
|
return;
|
|
|
|
DBGC2 ( intel, "INTEL %p TX %d complete\n", intel, tx_idx );
|
|
|
|
/* Complete TX descriptor */
|
|
netdev_tx_complete_next ( netdev );
|
|
intel->tx.cons++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Poll for received packets
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
void intel_poll_rx ( struct net_device *netdev ) {
|
|
struct intel_nic *intel = netdev->priv;
|
|
struct intel_descriptor *rx;
|
|
struct io_buffer *iobuf;
|
|
unsigned int rx_idx;
|
|
size_t len;
|
|
|
|
/* Check for received packets */
|
|
while ( intel->rx.cons != intel->rx.prod ) {
|
|
|
|
/* Get next receive descriptor */
|
|
rx_idx = ( intel->rx.cons % INTEL_NUM_RX_DESC );
|
|
rx = &intel->rx.desc[rx_idx];
|
|
|
|
/* Stop if descriptor is still in use */
|
|
if ( ! ( rx->status & INTEL_DESC_STATUS_DD ) )
|
|
return;
|
|
|
|
/* Populate I/O buffer */
|
|
iobuf = intel->rx_iobuf[rx_idx];
|
|
intel->rx_iobuf[rx_idx] = NULL;
|
|
len = le16_to_cpu ( rx->length );
|
|
iob_put ( iobuf, len );
|
|
|
|
/* Hand off to network stack */
|
|
if ( rx->errors ) {
|
|
DBGC ( intel, "INTEL %p RX %d error (length %zd, "
|
|
"errors %02x)\n",
|
|
intel, rx_idx, len, rx->errors );
|
|
netdev_rx_err ( netdev, iobuf, -EIO );
|
|
} else {
|
|
DBGC2 ( intel, "INTEL %p RX %d complete (length %zd)\n",
|
|
intel, rx_idx, len );
|
|
netdev_rx ( netdev, iobuf );
|
|
}
|
|
intel->rx.cons++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Poll for completed and received packets
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void intel_poll ( struct net_device *netdev ) {
|
|
struct intel_nic *intel = netdev->priv;
|
|
uint32_t icr;
|
|
|
|
/* Check for and acknowledge interrupts */
|
|
icr = readl ( intel->regs + INTEL_ICR );
|
|
if ( ! icr )
|
|
return;
|
|
|
|
/* Poll for TX completions, if applicable */
|
|
if ( icr & INTEL_IRQ_TXDW )
|
|
intel_poll_tx ( netdev );
|
|
|
|
/* Poll for RX completions, if applicable */
|
|
if ( icr & ( INTEL_IRQ_RXT0 | INTEL_IRQ_RXO ) )
|
|
intel_poll_rx ( netdev );
|
|
|
|
/* Report receive overruns */
|
|
if ( icr & INTEL_IRQ_RXO )
|
|
netdev_rx_err ( netdev, NULL, -ENOBUFS );
|
|
|
|
/* Check link state, if applicable */
|
|
if ( icr & INTEL_IRQ_LSC )
|
|
intel_check_link ( netdev );
|
|
|
|
/* Refill RX ring */
|
|
intel_refill_rx ( intel );
|
|
}
|
|
|
|
/**
|
|
* Enable or disable interrupts
|
|
*
|
|
* @v netdev Network device
|
|
* @v enable Interrupts should be enabled
|
|
*/
|
|
static void intel_irq ( struct net_device *netdev, int enable ) {
|
|
struct intel_nic *intel = netdev->priv;
|
|
uint32_t mask;
|
|
|
|
mask = ( INTEL_IRQ_TXDW | INTEL_IRQ_LSC | INTEL_IRQ_RXT0 );
|
|
if ( enable ) {
|
|
writel ( mask, intel->regs + INTEL_IMS );
|
|
} else {
|
|
writel ( mask, intel->regs + INTEL_IMC );
|
|
}
|
|
}
|
|
|
|
/** Intel network device operations */
|
|
static struct net_device_operations intel_operations = {
|
|
.open = intel_open,
|
|
.close = intel_close,
|
|
.transmit = intel_transmit,
|
|
.poll = intel_poll,
|
|
.irq = intel_irq,
|
|
};
|
|
|
|
/******************************************************************************
|
|
*
|
|
* PCI interface
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Probe PCI device
|
|
*
|
|
* @v pci PCI device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int intel_probe ( struct pci_device *pci ) {
|
|
struct net_device *netdev;
|
|
struct intel_nic *intel;
|
|
int rc;
|
|
|
|
/* Allocate and initialise net device */
|
|
netdev = alloc_etherdev ( sizeof ( *intel ) );
|
|
if ( ! netdev ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
netdev_init ( netdev, &intel_operations );
|
|
intel = netdev->priv;
|
|
pci_set_drvdata ( pci, netdev );
|
|
netdev->dev = &pci->dev;
|
|
memset ( intel, 0, sizeof ( *intel ) );
|
|
intel->port = PCI_FUNC ( pci->busdevfn );
|
|
intel_init_ring ( &intel->tx, INTEL_NUM_TX_DESC, INTEL_TD );
|
|
intel_init_ring ( &intel->rx, INTEL_NUM_RX_DESC, INTEL_RD );
|
|
|
|
/* Fix up PCI device */
|
|
adjust_pci_device ( pci );
|
|
|
|
/* Map registers */
|
|
intel->regs = ioremap ( pci->membase, INTEL_BAR_SIZE );
|
|
|
|
/* Reset the NIC */
|
|
if ( ( rc = intel_reset ( intel ) ) != 0 )
|
|
goto err_reset;
|
|
|
|
/* Fetch MAC address */
|
|
if ( ( rc = intel_fetch_mac ( intel, netdev->hw_addr ) ) != 0 )
|
|
goto err_fetch_mac;
|
|
|
|
/* Register network device */
|
|
if ( ( rc = register_netdev ( netdev ) ) != 0 )
|
|
goto err_register_netdev;
|
|
|
|
/* Set initial link state */
|
|
intel_check_link ( netdev );
|
|
|
|
return 0;
|
|
|
|
unregister_netdev ( netdev );
|
|
err_register_netdev:
|
|
err_fetch_mac:
|
|
intel_reset ( intel );
|
|
err_reset:
|
|
iounmap ( intel->regs );
|
|
netdev_nullify ( netdev );
|
|
netdev_put ( netdev );
|
|
err_alloc:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Remove PCI device
|
|
*
|
|
* @v pci PCI device
|
|
*/
|
|
static void intel_remove ( struct pci_device *pci ) {
|
|
struct net_device *netdev = pci_get_drvdata ( pci );
|
|
struct intel_nic *intel = netdev->priv;
|
|
|
|
/* Unregister network device */
|
|
unregister_netdev ( netdev );
|
|
|
|
/* Reset the NIC */
|
|
intel_reset ( intel );
|
|
|
|
/* Free network device */
|
|
iounmap ( intel->regs );
|
|
netdev_nullify ( netdev );
|
|
netdev_put ( netdev );
|
|
}
|
|
|
|
/** Intel PCI device IDs */
|
|
static struct pci_device_id intel_nics[] = {
|
|
PCI_ROM ( 0x8086, 0x0438, "dh8900cc", "DH8900CC", 0 ),
|
|
PCI_ROM ( 0x8086, 0x043a, "dh8900cc-f", "DH8900CC Fiber", 0 ),
|
|
PCI_ROM ( 0x8086, 0x043c, "dh8900cc-b", "DH8900CC Backplane", 0 ),
|
|
PCI_ROM ( 0x8086, 0x0440, "dh8900cc-s", "DH8900CC SFP", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1000, "82542-f", "82542 (Fiber)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1001, "82543gc-f", "82543GC (Fiber)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1004, "82543gc", "82543GC (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1008, "82544ei", "82544EI (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1009, "82544ei-f", "82544EI (Fiber)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x100c, "82544gc", "82544GC (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x100d, "82544gc-l", "82544GC (LOM)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x100e, "82540em", "82540EM", 0 ),
|
|
PCI_ROM ( 0x8086, 0x100f, "82545em", "82545EM (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1010, "82546eb", "82546EB (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1011, "82545em-f", "82545EM (Fiber)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1012, "82546eb-f", "82546EB (Fiber)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1013, "82541ei", "82541EI", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1014, "82541er", "82541ER", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1015, "82540em-l", "82540EM (LOM)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1016, "82540ep-m", "82540EP (Mobile)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1017, "82540ep", "82540EP", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1018, "82541ei", "82541EI", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1019, "82547ei", "82547EI", 0 ),
|
|
PCI_ROM ( 0x8086, 0x101a, "82547ei-m", "82547EI (Mobile)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x101d, "82546eb", "82546EB", 0 ),
|
|
PCI_ROM ( 0x8086, 0x101e, "82540ep-m", "82540EP (Mobile)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1026, "82545gm", "82545GM", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1027, "82545gm-1", "82545GM", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1028, "82545gm-2", "82545GM", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1049, "82566mm", "82566MM", 0 ),
|
|
PCI_ROM ( 0x8086, 0x104a, "82566dm", "82566DM", 0 ),
|
|
PCI_ROM ( 0x8086, 0x104b, "82566dc", "82566DC", 0 ),
|
|
PCI_ROM ( 0x8086, 0x104c, "82562v", "82562V 10/100", 0 ),
|
|
PCI_ROM ( 0x8086, 0x104d, "82566mc", "82566MC", 0 ),
|
|
PCI_ROM ( 0x8086, 0x105e, "82571eb", "82571EB", 0 ),
|
|
PCI_ROM ( 0x8086, 0x105f, "82571eb-1", "82571EB", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1060, "82571eb-2", "82571EB", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1075, "82547gi", "82547GI", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1076, "82541gi", "82541GI", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1077, "82541gi-1", "82541GI", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1078, "82541er", "82541ER", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1079, "82546gb", "82546GB", 0 ),
|
|
PCI_ROM ( 0x8086, 0x107a, "82546gb-1", "82546GB", 0 ),
|
|
PCI_ROM ( 0x8086, 0x107b, "82546gb-2", "82546GB", 0 ),
|
|
PCI_ROM ( 0x8086, 0x107c, "82541pi", "82541PI", 0 ),
|
|
PCI_ROM ( 0x8086, 0x107d, "82572ei", "82572EI (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x107e, "82572ei-f", "82572EI (Fiber)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x107f, "82572ei", "82572EI", 0 ),
|
|
PCI_ROM ( 0x8086, 0x108a, "82546gb-3", "82546GB", 0 ),
|
|
PCI_ROM ( 0x8086, 0x108b, "82573v", "82573V (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x108c, "82573e", "82573E (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1096, "80003es2lan", "80003ES2LAN (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1098, "80003es2lan-s", "80003ES2LAN (Serdes)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1099, "82546gb-4", "82546GB (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x109a, "82573l", "82573L", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10a4, "82571eb", "82571EB", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10a5, "82571eb", "82571EB (Fiber)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10a7, "82575eb", "82575EB", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10a9, "82575eb", "82575EB Backplane", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10b5, "82546gb", "82546GB (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10b9, "82572ei", "82572EI (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10ba, "80003es2lan", "80003ES2LAN (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10bb, "80003es2lan", "80003ES2LAN (Serdes)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10bc, "82571eb", "82571EB (Copper)", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10bd, "82566dm-2", "82566DM-2", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10bf, "82567lf", "82567LF", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10c0, "82562v-2", "82562V-2 10/100", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10c2, "82562g-2", "82562G-2 10/100", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10c3, "82562gt-2", "82562GT-2 10/100", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10c4, "82562gt", "82562GT 10/100", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10c5, "82562g", "82562G 10/100", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10c9, "82576", "82576", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10cb, "82567v", "82567V", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10cc, "82567lm-2", "82567LM-2", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10cd, "82567lf-2", "82567LF-2", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10ce, "82567v-2", "82567V-2", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10d3, "82574l", "82574L", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10d5, "82571pt", "82571PT PT Quad", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10d6, "82575gb", "82575GB", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10d9, "82571eb-d", "82571EB Dual Mezzanine", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10da, "82571eb-q", "82571EB Quad Mezzanine", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10de, "82567lm-3", "82567LM-3", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10df, "82567lf-3", "82567LF-3", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10e5, "82567lm-4", "82567LM-4", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10e6, "82576", "82576", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10e7, "82576-2", "82576", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10e8, "82576-3", "82576", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10ea, "82577lm", "82577LM", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10eb, "82577lc", "82577LC", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10ef, "82578dm", "82578DM", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10f0, "82578dc", "82578DC", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10f5, "82567lm", "82567LM", 0 ),
|
|
PCI_ROM ( 0x8086, 0x10f6, "82574l", "82574L", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1501, "82567v-3", "82567V-3", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1502, "82579lm", "82579LM", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1503, "82579v", "82579V", 0 ),
|
|
PCI_ROM ( 0x8086, 0x150a, "82576ns", "82576NS", 0 ),
|
|
PCI_ROM ( 0x8086, 0x150c, "82583v", "82583V", 0 ),
|
|
PCI_ROM ( 0x8086, 0x150d, "82576-4", "82576 Backplane", 0 ),
|
|
PCI_ROM ( 0x8086, 0x150e, "82580", "82580", 0 ),
|
|
PCI_ROM ( 0x8086, 0x150f, "82580-f", "82580 Fiber", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1510, "82580-b", "82580 Backplane", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1511, "82580-s", "82580 SFP", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1516, "82580-2", "82580", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1518, "82576ns", "82576NS SerDes", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1521, "i350", "I350", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1522, "i350-f", "I350 Fiber", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1523, "i350-b", "I350 Backplane", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1524, "i350-2", "I350", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1525, "82567v-4", "82567V-4", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1526, "82576-5", "82576", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1527, "82580-f2", "82580 Fiber", 0 ),
|
|
PCI_ROM ( 0x8086, 0x1533, "i210", "I210", 0 ),
|
|
PCI_ROM ( 0x8086, 0x153b, "i217", "I217", 0 ),
|
|
PCI_ROM ( 0x8086, 0x294c, "82566dc-2", "82566DC-2", 0 ),
|
|
PCI_ROM ( 0x8086, 0x2e6e, "cemedia", "CE Media Processor", 0 ),
|
|
};
|
|
|
|
/** Intel PCI driver */
|
|
struct pci_driver intel_driver __pci_driver = {
|
|
.ids = intel_nics,
|
|
.id_count = ( sizeof ( intel_nics ) / sizeof ( intel_nics[0] ) ),
|
|
.probe = intel_probe,
|
|
.remove = intel_remove,
|
|
};
|