| /* |
| * 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., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301, 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_phys(), |
| * 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_phys ( 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_phys ( 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_phys ( ias, sizeof ( *ias ) ); |
| free_phys ( 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_phys ( cfg, sizeof ( *cfg ) ); |
| free_phys ( 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; |
| 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_phys ( 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 occurred |
| */ |
| 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_phys ( 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, 0x1050, "82562ez", "Intel 82562EZ 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, 0x1065, "82562-3", "Intel 82562 based Fast Ethernet Connection", 0), |
| PCI_ROM(0x8086, 0x1092, "82562-3", "Intel Pro/100 VE Network", 0), |
| PCI_ROM(0x8086, 0x10fe, "82552", "Intel 82552 10/100 Network 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, 0x27dc, "eepro100-27dc", "Intel 82801G (ICH7) Chipset Ethernet Controller", 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), |
| }; |
| |
| /* 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: |
| */ |