URL
https://opencores.org/ocsvn/openrisc/openrisc/trunk
Subversion Repositories openrisc
[/] [openrisc/] [trunk/] [rtos/] [ecos-3.0/] [packages/] [devs/] [eth/] [amd/] [lancepci/] [current/] [src/] [if_lancepci.c] - Rev 786
Compare with Previous | Blame | View Log
//========================================================================== // // dev/if_lancepci.c // // Ethernet device driver for AMD PCI Lance (for instance vmWare VLANCE) // compatible controllers // //========================================================================== // ####ECOSGPLCOPYRIGHTBEGIN#### // ------------------------------------------- // This file is part of eCos, the Embedded Configurable Operating System. // Copyright (C) 1998, 1999, 2000, 2001, 2002, 2009 Free Software Foundation, Inc. // // eCos 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. // // eCos 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 eCos; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // As a special exception, if other files instantiate templates or use // macros or inline functions from this file, or you compile this file // and link it with other works to produce a work based on this file, // this file does not by itself cause the resulting work to be covered by // the GNU General Public License. However the source code for this file // must still be made available in accordance with section (3) of the GNU // General Public License v2. // // This exception does not invalidate any other reasons why a work based // on this file might be covered by the GNU General Public License. // ------------------------------------------- // ####ECOSGPLCOPYRIGHTEND#### //========================================================================== //#####DESCRIPTIONBEGIN#### // // Author(s): jskov, based on lan91cxx driver by hmt & jskov, iz // Contributors: gthomas, jskov, hmt, iz // Date: 2002-07-17, 2003-01-26 // Purpose: // Description: hardware driver for AMD Lance PCI (and possibly PCnet) // and wmWare VLANCE ethernet // Notes: The controller is used in its 16bit mode. That means that // all addresses are 24bit only - and that all controller // accessed memory must be within the same 16MB region // (starting at 0 on older controllers). // // The KEEP_STATISTICS code is not implemented yet. Look // for FIXME macro. // //####DESCRIPTIONEND#### // //========================================================================== //#####VMWAREDESCRIPTIONBEGIN#### // // Notes: The vmWare VLACNCE virtual controller does not seem to do // anything about SUSPEND and seems it must be reinitialized after // every STOP. In addition it lacks some registers. // // Sometimes, the driver must wait to let Vmware get a tick, to // process the chip initialization and control functions!!! // // That's the reason for not patching the PCnet driver // but cloning a special one from it. // // //####VMWAREDESCRIPTIONEND#### //========================================================================== #include <pkgconf/system.h> #include <pkgconf/devs_eth_amd_lancepci.h> #include <pkgconf/io_eth_drivers.h> #include <cyg/infra/cyg_type.h> #include <cyg/hal/hal_arch.h> #include <cyg/hal/hal_intr.h> #include <cyg/infra/cyg_ass.h> #include <cyg/infra/diag.h> #include <cyg/hal/drv_api.h> #include <cyg/hal/hal_if.h> // delays #include <string.h> #include <cyg/io/eth/netdev.h> #include <cyg/io/eth/eth_drv.h> #ifdef CYGPKG_NET #include <pkgconf/net.h> #include <cyg/kernel/kapi.h> #include <net/if.h> // Needed for struct ifnet #include <pkgconf/io_eth_drivers.h> #endif #include CYGHWR_MEMORY_LAYOUT_H #ifdef CYGPKG_IO_PCI #include <cyg/io/pci.h> #else #error "Need PCI package here" #endif #define FIXME 0 #define _BUF_SIZE 1544 #ifdef CYGPKG_INFRA_DEBUG // Then we log, OOI, the number of times we get a bad packet number // from the tx done fifo. int lancepci_txfifo_good = 0; int lancepci_txfifo_bad = 0; #endif #include "amd_lance.h" #define __WANT_DEVS #include CYGDAT_DEVS_ETH_AMD_LANCEPCI_INL #undef __WANT_DEVS //#define DEBUG 0xff #if defined(CYGPKG_REDBOOT) static void db_printf( char *fmt, ... ) { extern int start_console(void); extern void end_console(int); va_list a; int old_console; va_start( a, fmt ); old_console = start_console(); diag_vprintf( fmt, a ); end_console(old_console); va_end( a ); } #else #define db_printf diag_printf #endif static struct eth_drv_sc *oursc; //a dummy sc pointer static void lancepci_poll(struct eth_drv_sc *sc); // This ISR is called when the ethernet interrupt occurs static cyg_uint32 lancepci_isr(cyg_vector_t vector, cyg_addrword_t data) { struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)data; DEBUG_FUNCTION(); INCR_STAT( interrupts ); cpd->event = get_reg(oursc, LANCE_CSR_CSCR); if (cpd->event & LANCE_CSR_CSCR_TINT) cpd->txbusyh=0; // take care of HW txbusy flag cyg_drv_interrupt_mask(cpd->interrupt); cyg_drv_interrupt_acknowledge(cpd->interrupt); return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR); // Run the DSR } static void lancepci_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data) { // This conditioning out is necessary because of explicit calls to this // DSR - which would not ever be called in the case of a polled mode // usage ie. in RedBoot. #ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED struct lancepci_priv_data* cpd = (struct lancepci_priv_data *)data; struct cyg_netdevtab_entry *ndp = (struct cyg_netdevtab_entry *)(cpd->ndp); struct eth_drv_sc *sc = (struct eth_drv_sc *)(ndp->device_instance); // but here, it must be a *sc: eth_drv_dsr( vector, count, (cyg_addrword_t)sc ); #else # ifndef CYGPKG_REDBOOT # error Empty lancepci ethernet DSR is compiled. Is this what you want? # endif #endif } // The deliver function (ex-DSR) handles the ethernet [logical] processing static void lancepci_deliver(struct eth_drv_sc *sc) { struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; DEBUG_FUNCTION(); // Service the interrupt: lancepci_poll(sc); // Allow interrupts to happen again cyg_drv_interrupt_unmask(cpd->interrupt); } static int lancepci_int_vector(struct eth_drv_sc *sc) { struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; return (cpd->interrupt); } // ------------------------------------------------------------------------ // Memory management // // Simply carve off from the front of the PCI mapped window into real memory static cyg_uint32 lancepci_heap_size; static cyg_uint8 *lancepci_heap_base; static cyg_uint8 *lancepci_heap_free; static void* pciwindow_mem_alloc(int size) { void *p_memory; int _size = size; CYG_ASSERT( (CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_BASE <= (int)lancepci_heap_free) && ((CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_BASE + CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_SIZE) > (int)lancepci_heap_free) && (0 < lancepci_heap_size) && (CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_SIZE >= lancepci_heap_size) && (CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_BASE == (int)lancepci_heap_base), "Heap variables corrupted" ); p_memory = (void *)0; size = (size + 3) & ~3; if ( (lancepci_heap_free+size) < (lancepci_heap_base+lancepci_heap_size) ) { cyg_uint32 *p; p_memory = (void *)lancepci_heap_free; lancepci_heap_free += size; for ( p = (cyg_uint32 *)p_memory; _size > 0; _size -= 4 ) *p++ = 0; } #if DEBUG & 9 db_printf("Allocated %d bytes at 0x%08x\n", size, p_memory); #endif return p_memory; } static cyg_pci_match_func find_lancepci_match_func; static cyg_bool find_lancepci_match_func( cyg_uint16 v, cyg_uint16 d, cyg_uint32 c, void *p ) { #if DEBUG & 9 db_printf("PCI match vendor 0x%04x device 0x%04x\n", v, d); #endif return (0x1022 == v) && (0x2000 == d); } static int pci_init_find_lancepci( void ) { cyg_pci_device_id devid; cyg_pci_device dev_info; cyg_uint16 cmd; int device_index; int found_devices = 0; DEBUG_FUNCTION(); #ifdef CYGARC_UNCACHED_ADDRESS CYG_ASSERT( CYGARC_UNCACHED_ADDRESS((CYG_ADDRWORD)CYGMEM_SECTION_pci_window) == CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_BASE, "PCI window configured does not match PCI memory section base" ); #else CYG_ASSERT( (CYG_ADDRWORD)CYGMEM_SECTION_pci_window == CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_BASE, "PCI window configured does not match PCI memory section base" ); #endif CYG_ASSERT( CYGMEM_SECTION_pci_window_SIZE == CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_SIZE, "PCI window configured does not match PCI memory section size" ); if ( #ifdef CYGARC_UNCACHED_ADDRESS CYGARC_UNCACHED_ADDRESS((CYG_ADDRWORD)CYGMEM_SECTION_pci_window) != #else (CYG_ADDRWORD)CYGMEM_SECTION_pci_window != #endif (CYG_ADDRWORD)CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_BASE || CYGMEM_SECTION_pci_window_SIZE != CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_SIZE ) { #if DEBUG & 8 db_printf("pci_init_find_lancepci(): PCI window misconfigured\n"); #endif return 0; } // First initialize the heap in PCI window'd memory lancepci_heap_size = CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_SIZE; lancepci_heap_base = (cyg_uint8 *)CYGHWR_AMD_LANCEPCI_PCI_MEM_MAP_BASE; lancepci_heap_free = lancepci_heap_base; #if DEBUG & 9 db_printf("pcimem : 0x%08x size: 0x%08x\n", lancepci_heap_base, lancepci_heap_size); #endif cyg_pci_init(); #if DEBUG & 8 db_printf("Finished cyg_pci_init();\n"); #endif devid = CYG_PCI_NULL_DEVID; for (device_index = 0; device_index < CYGNUM_DEVS_ETH_AMD_LANCEPCI_DEV_COUNT; device_index++) { struct lancepci_priv_data* cpd = lancepci_priv_array[device_index]; cpd->index = device_index; // See above for find_lancepci_match_func - it selects any of several // variants. This is necessary in case we have multiple mixed-type // devices on one board in arbitrary orders. if (cyg_pci_find_matching( &find_lancepci_match_func, NULL, &devid )) { #if DEBUG & 8 db_printf("eth%d = lancepci\n", device_index); #endif cyg_pci_get_device_info(devid, &dev_info); cpd->interrupt_handle = 0; // Flag not attached. if (cyg_pci_translate_interrupt(&dev_info, &cpd->interrupt)) { #if DEBUG & 8 db_printf(" Wired to HAL vector %d\n", cpd->interrupt); #endif cyg_drv_interrupt_create( cpd->interrupt, 1, // Priority - unused (cyg_addrword_t)cpd,// Data item passed to ISR & DSR lancepci_isr, // ISR lancepci_dsr, // DSR &cpd->interrupt_handle, // handle to intr obj &cpd->interrupt_object ); // space for int obj cyg_drv_interrupt_attach(cpd->interrupt_handle); // Don't unmask the interrupt yet, that could get us into a // race. } else { cpd->interrupt = 0; #if DEBUG & 8 db_printf(" Does not generate interrupts.\n"); #endif } if (cyg_pci_configure_device(&dev_info)) { #if DEBUG & 8 int i; db_printf("Found device on bus %d, devfn 0x%02x:\n", CYG_PCI_DEV_GET_BUS(devid), CYG_PCI_DEV_GET_DEVFN(devid)); if (dev_info.command & CYG_PCI_CFG_COMMAND_ACTIVE) { db_printf(" Note that board is active. Probed" " sizes and CPU addresses invalid!\n"); } db_printf(" Vendor 0x%04x", dev_info.vendor); db_printf("\n Device 0x%04x", dev_info.device); db_printf("\n Command 0x%04x, Status 0x%04x\n", dev_info.command, dev_info.status); db_printf(" Class/Rev 0x%08x", dev_info.class_rev); db_printf("\n Header 0x%02x\n", dev_info.header_type); db_printf(" SubVendor 0x%04x, Sub ID 0x%04x\n", dev_info.header.normal.sub_vendor, dev_info.header.normal.sub_id); for(i = 0; i < CYG_PCI_MAX_BAR; i++) { db_printf(" BAR[%d] 0x%08x /", i, dev_info.base_address[i]); db_printf(" probed size 0x%08x / CPU addr 0x%08x\n", dev_info.base_size[i], dev_info.base_map[i]); } db_printf(" eth%d configured\n", device_index); #endif found_devices++; cpd->found = 1; cpd->active = 0; cpd->devid = devid; cpd->base = (unsigned char*) dev_info.base_map[0]; #if DEBUG & 8 db_printf(" I/O address = 0x%08x\n", cpd->base); #endif // Don't use cyg_pci_set_device_info since it clears // some of the fields we want to print out below. cyg_pci_read_config_uint16(dev_info.devid, CYG_PCI_CFG_COMMAND, &cmd); cmd |= (CYG_PCI_CFG_COMMAND_IO // enable I/O space | CYG_PCI_CFG_COMMAND_MEMORY // enable memory space | CYG_PCI_CFG_COMMAND_MASTER); // enable bus master cyg_pci_write_config_uint16(dev_info.devid, CYG_PCI_CFG_COMMAND, cmd); // This is the indicator for "uses an interrupt" if (cpd->interrupt_handle != 0) { cyg_drv_interrupt_acknowledge(cpd->interrupt); cyg_drv_interrupt_unmask(cpd->interrupt); #if DEBUG & 8 db_printf(" Enabled interrupt %d\n", cpd->interrupt); #endif } #if DEBUG & 8 db_printf(" **** Device enabled for I/O and Memory " "and Bus Master\n"); #endif } else { cpd->found = 0; cpd->active = 0; #if DEBUG & 8 db_printf("Failed to configure device %d\n", device_index); #endif } } else { cpd->found = 0; cpd->active = 0; #if DEBUG & 8 db_printf("eth%d not found\n", device_index); #endif } } if (0 == found_devices) return 0; return 1; } static bool amd_lancepci_init(struct cyg_netdevtab_entry *tab) { static int initialized = 0; // only probe PCI et al *once* struct eth_drv_sc *sc = (struct eth_drv_sc *)tab->device_instance; struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; cyg_uint16 val; cyg_uint32 b; cyg_uint8* p; cyg_uint8* d; int i; DEBUG_FUNCTION(); if ( 0 == initialized++ ) { // then this is the first time ever: if ( ! pci_init_find_lancepci() ) { #if DEBUG & 8 db_printf( "pci_init_find_lancepci failed" ); #endif return false; } } // If this device is not present, exit if (0 == cpd->found) return 0; #if DEBUG & 8 db_printf("lancepci at base 0x%08x, EEPROM key 0x%04x\n", cpd->base, _SU16(cpd->base, LANCE_IO_ID)); #endif #if 0 // FIXME: Doesn't work with non-conforming EEPROMS if (LANCE_IO_ID_KEY != _SU16(cpd->base, LANCE_IO_ID) ) { db_printf("Lance EPROM key not found\n"); return false; } #endif #if DEBUG & 9 db_printf("pcimem : %08x size: %08x\n", lancepci_heap_base, lancepci_heap_size); #endif // Prepare ESA if (!cpd->hardwired_esa) { // Don't use the address from the EEPROM for VMware // Use the address that VMware prepares in CSR_PAR registers // if You want to be able to use NAT networking. (iz@elsis.si Feb 27 04) // // p = cpd->base + LANCE_IO_EEPROM; // for (i = 0; i < 6; i++) // cpd->esa[i] = *p++; put_reg(sc, LANCE_CSR_CSCR, LANCE_CSR_CSCR_STOP); for (i = 0; i < sizeof(cpd->esa); i += 2) { cyg_uint16 z = get_reg(sc, LANCE_CSR_PAR0+i/2 ); cpd->esa[i] = (cyg_uint8)(0xff & z); cpd->esa[i+1] = (cyg_uint8)(0xff & (z >> 8)); } } #if DEBUG & 9 db_printf("Lance - %s ESA: %02x:%02x:%02x:%02x:%02x:%02x\n", (cpd->hardwired_esa) ? "static" : "eeprom", cpd->esa[0], cpd->esa[1], cpd->esa[2], cpd->esa[3], cpd->esa[4], cpd->esa[5] ); #endif // Prepare RX and TX rings p = cpd->rx_ring = (cyg_uint8*) CYGARC_UNCACHED_ADDRESS((cyg_uint32)pciwindow_mem_alloc((1<<cpd->rx_ring_log_cnt)*LANCE_RD_SIZE)); memset(cpd->rx_ring,0,(1<<cpd->rx_ring_log_cnt)*LANCE_RD_SIZE); d = cpd->rx_buffers = (cyg_uint8*) CYGARC_UNCACHED_ADDRESS((cyg_uint32)pciwindow_mem_alloc(_BUF_SIZE*cpd->rx_ring_cnt)); memset(cpd->rx_buffers,0,_BUF_SIZE*cpd->rx_ring_cnt); for (i = 0; i < cpd->rx_ring_cnt; i++) { HAL_PCI_CPU_TO_BUS((cyg_uint32)d, b); _SU32(p, LANCE_RD_PTR) = (b & LANCE_RD_PTR_MASK) | LANCE_RD_PTR_OWN; _SU16(p, LANCE_RD_BLEN) = (-_BUF_SIZE); p += LANCE_RD_SIZE; d += _BUF_SIZE; } cpd->rx_ring_next = 0; p = cpd->tx_ring = (cyg_uint8*) CYGARC_UNCACHED_ADDRESS((cyg_uint32)pciwindow_mem_alloc((1<<cpd->tx_ring_log_cnt)*LANCE_TD_SIZE)); memset(cpd->tx_ring,0,(1<<cpd->tx_ring_log_cnt)*LANCE_TD_SIZE); d = cpd->tx_buffers = (cyg_uint8*) CYGARC_UNCACHED_ADDRESS((cyg_uint32)pciwindow_mem_alloc(_BUF_SIZE*cpd->tx_ring_cnt)); for (i = 0; i < cpd->tx_ring_cnt; i++) { HAL_PCI_CPU_TO_BUS((cyg_uint32)d, b); _SU32(p, LANCE_RD_PTR) = b & LANCE_TD_PTR_MASK; p += LANCE_TD_SIZE; d += _BUF_SIZE; } cpd->tx_ring_free = cpd->tx_ring_alloc = cpd->tx_ring_owned = 0; // Initialization table cpd->init_table = (cyg_uint8*)CYGARC_UNCACHED_ADDRESS((cyg_uint32)pciwindow_mem_alloc(LANCE_IB_SIZE)); _SU16(cpd->init_table, LANCE_IB_MODE) = 0x0000; for (i = 0; i < 6; i++) _SU8(cpd->init_table, LANCE_IB_PADR0+i) = cpd->esa[i]; for (i = 0; i < 8; i++) _SU8(cpd->init_table, LANCE_IB_LADRF0+i) = 0; HAL_PCI_CPU_TO_BUS((cyg_uint32)cpd->rx_ring, b); _SU32(cpd->init_table, LANCE_IB_RDRA) = ((b & LANCE_IB_RDRA_PTR_mask) | (cpd->rx_ring_log_cnt << LANCE_IB_RDRA_CNT_shift)); HAL_PCI_CPU_TO_BUS((cyg_uint32)cpd->tx_ring, b); _SU32(cpd->init_table, LANCE_IB_TDRA) = ((b & LANCE_IB_TDRA_PTR_mask) | (cpd->tx_ring_log_cnt << LANCE_IB_TDRA_CNT_shift)); #if DEBUG & 9 db_printf("Loading up lance controller from table at 0x%08x\n", cpd->init_table); db_printf(" Mode 0x%04x\n", _SU16(cpd->init_table, LANCE_IB_MODE)); db_printf(" PADR %02x:%02x:%02x:%02x:%02x:%02x ", _SU8(cpd->init_table, LANCE_IB_PADR0+0), _SU8(cpd->init_table, LANCE_IB_PADR0+1), _SU8(cpd->init_table, LANCE_IB_PADR0+2), _SU8(cpd->init_table, LANCE_IB_PADR0+3), _SU8(cpd->init_table, LANCE_IB_PADR0+4), _SU8(cpd->init_table, LANCE_IB_PADR0+5)); db_printf("LADR %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", _SU8(cpd->init_table, LANCE_IB_LADRF0+0), _SU8(cpd->init_table, LANCE_IB_LADRF0+1), _SU8(cpd->init_table, LANCE_IB_LADRF0+2), _SU8(cpd->init_table, LANCE_IB_LADRF0+3), _SU8(cpd->init_table, LANCE_IB_LADRF0+4), _SU8(cpd->init_table, LANCE_IB_LADRF0+5), _SU8(cpd->init_table, LANCE_IB_LADRF0+5), _SU8(cpd->init_table, LANCE_IB_LADRF0+7)); db_printf(" RX 0x%08x (len %d) TX 0x%08x (len %d)\n", _SU32(cpd->init_table, LANCE_IB_RDRA) & 0x1fffffff, (_SU32(cpd->init_table, LANCE_IB_RDRA) >> LANCE_IB_RDRA_CNT_shift) & 7, _SU32(cpd->init_table, LANCE_IB_TDRA) & 0x1fffffff, (_SU32(cpd->init_table, LANCE_IB_TDRA) >> LANCE_IB_TDRA_CNT_shift) & 7); #endif // Reset chip HAL_PCI_IO_READ_UINT16(cpd->base+LANCE_IO_RESET, val); // Load up chip with buffers. // Note: There is a 16M limit on the addresses used by the driver // since the top 8 bits of the init_table address is appended to // all other addresses used by the controller. HAL_PCI_CPU_TO_BUS((cyg_uint32)cpd->init_table, b); put_reg(sc, LANCE_CSR_IBA0, (b >> 0) & 0xffff); put_reg(sc, LANCE_CSR_IBA1, (b >> 16) & 0xffff); // Disable automatic TX polling (_send will force a poll), pad // XT frames to legal length, mask status interrupts. put_reg(sc, LANCE_CSR_TFC, (LANCE_CSR_TFC_TXDPOLL | LANCE_CSR_TFC_APAD_XMT | LANCE_CSR_TFC_MFCOM | LANCE_CSR_TFC_RCVCCOM | LANCE_CSR_TFC_TXSTRTM)); // Recover after TX FIFO underflow put_reg(sc, LANCE_CSR_IM, LANCE_CSR_IM_DXSUFLO); // Initialize controller - load up init_table put_reg(sc, LANCE_CSR_CSCR, LANCE_CSR_CSCR_INIT); while (0 == (get_reg(sc, LANCE_CSR_CSCR) & LANCE_CSR_CSCR_IDON)); // Stop controller put_reg(sc, LANCE_CSR_CSCR, LANCE_CSR_CSCR_STOP); #if DEBUG & 9 db_printf("lancepci controller state is now:\n"); db_printf(" Mode 0x%04x TFC 0x%04x\n", _SU16(cpd->init_table, LANCE_IB_MODE), get_reg(sc, LANCE_CSR_TFC)); db_printf(" PADR %04x:%04x:%04x ", get_reg(sc, LANCE_CSR_PAR0), get_reg(sc, LANCE_CSR_PAR1), get_reg(sc, LANCE_CSR_PAR2)); db_printf("LADR %04x:%04x:%04x:%04x\n", get_reg(sc, LANCE_CSR_LAR0), get_reg(sc, LANCE_CSR_LAR1), get_reg(sc, LANCE_CSR_LAR2), get_reg(sc, LANCE_CSR_LAR3)); db_printf(" RX 0x%04x%04x (len 0x%04x) TX 0x%04x%04x (len 0x%04x)\n", get_reg(sc, LANCE_CSR_BARRU), get_reg(sc, LANCE_CSR_BARRL), get_reg(sc, LANCE_CSR_RRLEN), get_reg(sc, LANCE_CSR_BATRU), get_reg(sc, LANCE_CSR_BATRL), get_reg(sc, LANCE_CSR_TRLEN)); val = get_reg(sc, LANCE_CSR_ID_LO); db_printf("lancepci ID 0x%04x (%s) ", val, (0x5003 == val) ? "Am79C973" : (0x7003 == val) ? "Am79C975" : (0x1003 == val) ? "Am79C900 or wmWare VLANCE" : "Unknown"); val = get_reg(sc, LANCE_CSR_ID_HI); db_printf("Part IDU 0x%03x Silicon rev %d\n", val & 0x0fff, (val >> 12) & 0xf); #endif // and record the net dev pointer cpd->ndp = (void *)tab; cpd->active = 0; oursc=sc; // Initialize upper level driver (sc->funs->eth_drv->init)(sc, cpd->esa); cpd->txbusyh=cpd->txbusy=0; cpd->event=0; db_printf("Lancepci driver loaded and Init Done\n"); return true; } static void lancepci_stop(struct eth_drv_sc *sc) { cyg_uint32 b; struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; DEBUG_FUNCTION(); if (!cpd->active) return; if (cpd->txbusyh) { #if DEBUG & 9 db_printf("Lancepci-stop:waiting for tx empty\n"); #endif b=100; while (cpd->txbusyh && b) { CYGACC_CALL_IF_DELAY_US(200); b--; } } put_reg(sc, LANCE_CSR_CSCR, LANCE_CSR_CSCR_STOP); cpd->active = 0; #if DEBUG & 9 db_printf("Lancepci-stop:done\n"); #endif } // // This function is called to "start up" the interface. It may be called // multiple times, even when the hardware is already running. It will be // called whenever something "hardware oriented" changes and should leave // the hardware ready to send/receive packets. // static void lancepci_start(struct eth_drv_sc *sc, unsigned char *enaddr, int flags) { cyg_uint16 reg; cyg_uint32 b; struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; #ifdef CYGPKG_NET struct ifnet *ifp = &sc->sc_arpcom.ac_if; #endif DEBUG_FUNCTION(); // If device is already active, stop it #if DEBUG & 9 db_printf("Lancepci-start:entered\n"); #endif if (cpd->active) { if (cpd->txbusyh) { #if DEBUG & 9 db_printf("Lancepci-start:waiting for tx empty\n"); #endif b=100; while (cpd->txbusyh && b) { CYGACC_CALL_IF_DELAY_US(200); b--; } } put_reg(sc, LANCE_CSR_CSCR, LANCE_CSR_CSCR_STOP); cpd->active = 0; #if DEBUG & 9 db_printf("Lancepci-start:stopped\n"); #endif } CYGACC_CALL_IF_DELAY_US(200); #ifdef CYGPKG_NET if (( 0 #ifdef ETH_DRV_FLAGS_PROMISC_MODE != (flags & ETH_DRV_FLAGS_PROMISC_MODE) #endif ) || (ifp->if_flags & IFF_PROMISC) ) { // Then we select promiscuous mode. _SU16(cpd->init_table, LANCE_IB_MODE) = 0x0000|LANCE_CSR_MODE_PROM; #if DEBUG & 9 db_printf("Promisc MODE!"); #endif } else _SU16(cpd->init_table, LANCE_IB_MODE) = 0x0000; #endif cpd->rx_ring_next = 0; cpd->tx_ring_free = cpd->tx_ring_alloc = cpd->tx_ring_owned = 0; // Init the chip again HAL_PCI_CPU_TO_BUS((cyg_uint32)cpd->init_table, b); put_reg(sc, LANCE_CSR_IBA0, (b >> 0) & 0xffff); put_reg(sc, LANCE_CSR_IBA1, (b >> 16) & 0xffff); // Disable automatic TX polling (_send will force a poll), pad // XT frames to legal length, mask status interrupts. put_reg(sc, LANCE_CSR_TFC, (LANCE_CSR_TFC_TXDPOLL | LANCE_CSR_TFC_APAD_XMT | LANCE_CSR_TFC_MFCOM | LANCE_CSR_TFC_RCVCCOM | LANCE_CSR_TFC_TXSTRTM)); // Recover after TX FIFO underflow put_reg(sc, LANCE_CSR_IM, LANCE_CSR_IM_DXSUFLO); // Initialize controller - load up init_table put_reg(sc, LANCE_CSR_CSCR, LANCE_CSR_CSCR_INIT); while (0 == (get_reg(sc, LANCE_CSR_CSCR) & LANCE_CSR_CSCR_IDON)); reg=get_reg(sc,LANCE_CSR_CSCR); put_reg(sc, LANCE_CSR_CSCR, (reg|(LANCE_CSR_CSCR_IENA | LANCE_CSR_CSCR_STRT))&~LANCE_CSR_CSCR_INIT); #if DEBUG & 9 reg=get_reg(sc,LANCE_CSR_CSCR); db_printf("CSR after start = %4x\n",reg); #endif cpd->active = 1; cpd->txbusy=0; cpd->txbusyh=0; // delay is necessary for Vmware to get a tick !!! CYGACC_CALL_IF_DELAY_US(50000); } // // This routine is called to perform special "control" opertions // static int lancepci_control(struct eth_drv_sc *sc, unsigned long key, void *data, int data_length) { cyg_uint8 *esa = (cyg_uint8 *)data; int i, res; cyg_uint16 reg; struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; DEBUG_FUNCTION(); #if DEBUG & 9 db_printf("Lancepci-control:entered\n"); #endif res = 0; // expect success switch (key) { case ETH_DRV_SET_MAC_ADDRESS: #if 9 & DEBUG db_printf("PCNET - set ESA: %02x:%02x:%02x:%02x:%02x:%02x\n", esa[0], esa[1], esa[2], esa[3], esa[4], esa[5] ); #endif // DEBUG for ( i = 0; i < sizeof(cpd->esa); i++ ) cpd->esa[i] = esa[i]; for (i = 0; i < sizeof(cpd->esa); i += 2) { reg = cpd->esa[i] | (cpd->esa[i+1] << 8); put_reg(sc, LANCE_CSR_PAR0+i/2, reg ); } for (i = 0; i < 6; i++) // in case of later restart _SU8(cpd->init_table, LANCE_IB_PADR0+i) = cpd->esa[i]; break; #ifdef ETH_DRV_GET_MAC_ADDRESS case ETH_DRV_GET_MAC_ADDRESS: // Extract the MAC address that is in the chip, and tell the // system about it. for (i = 0; i < sizeof(cpd->esa); i += 2) { cyg_uint16 z = get_reg(sc, LANCE_CSR_PAR0+i/2 ); esa[i] = (cyg_uint8)(0xff & z); esa[i+1] = (cyg_uint8)(0xff & (z >> 8)); } break; #endif #ifdef ETH_DRV_GET_IF_STATS_UD case ETH_DRV_GET_IF_STATS_UD: // UD == UPDATE #endif // drop through #ifdef ETH_DRV_GET_IF_STATS case ETH_DRV_GET_IF_STATS: #endif #if defined(ETH_DRV_GET_IF_STATS) || defined (ETH_DRV_GET_IF_STATS_UD) { struct ether_drv_stats *p = (struct ether_drv_stats *)data; // Chipset entry is no longer supported; RFC1573. for ( i = 0; i < SNMP_CHIPSET_LEN; i++ ) p->snmp_chipset[i] = 0; // This perhaps should be a config opt, so you can make up your own // description, or supply it from the instantiation. strcpy( p->description, "AMD LancePCI" ); // CYG_ASSERT( 48 > strlen(p->description), "Description too long" ); p->operational = 3; // LINK UP p->duplex = 2; // 2 = SIMPLEX p->speed = 10 * 1000000; #if FIXME #ifdef KEEP_STATISTICS { struct amd_lancepci_stats *ps = &(cpd->stats); // Admit to it... p->supports_dot3 = true; p->tx_good = ps->tx_good ; p->tx_max_collisions = ps->tx_max_collisions ; p->tx_late_collisions = ps->tx_late_collisions ; p->tx_underrun = ps->tx_underrun ; p->tx_carrier_loss = ps->tx_carrier_loss ; p->tx_deferred = ps->tx_deferred ; p->tx_sqetesterrors = ps->tx_sqetesterrors ; p->tx_single_collisions = ps->tx_single_collisions; p->tx_mult_collisions = ps->tx_mult_collisions ; p->tx_total_collisions = ps->tx_total_collisions ; p->rx_good = ps->rx_good ; p->rx_crc_errors = ps->rx_crc_errors ; p->rx_align_errors = ps->rx_align_errors ; p->rx_resource_errors = ps->rx_resource_errors ; p->rx_overrun_errors = ps->rx_overrun_errors ; p->rx_collisions = ps->rx_collisions ; p->rx_short_frames = ps->rx_short_frames ; p->rx_too_long_frames = ps->rx_too_long_frames ; p->rx_symbol_errors = ps->rx_symbol_errors ; p->interrupts = ps->interrupts ; p->rx_count = ps->rx_count ; p->rx_deliver = ps->rx_deliver ; p->rx_resource = ps->rx_resource ; p->rx_restart = ps->rx_restart ; p->tx_count = ps->tx_count ; p->tx_complete = ps->tx_complete ; p->tx_dropped = ps->tx_dropped ; } #endif // KEEP_STATISTICS #endif // FIXME p->tx_queue_len = 1; break; } #endif default: res = 1; break; } #if DEBUG & 9 db_printf("Lancepci-control:done\n"); #endif CYGACC_CALL_IF_DELAY_US(50000); // let VMware get a tick return res; } // // This routine is called to see if it is possible to send another packet. // It will return non-zero if a transmit is possible, zero otherwise. // static int lancepci_can_send(struct eth_drv_sc *sc) { struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; DEBUG_FUNCTION(); return (0 == cpd->txbusy); } // // This routine is called to send data to the hardware. static void lancepci_send(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len, int total_len, unsigned long key) { struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; int i, len, plen, ring_entry; cyg_uint8* sdata = NULL; cyg_uint8 *d, *buf, *txd; cyg_uint16 ints; cyg_uint32 b; DEBUG_FUNCTION(); INCR_STAT( tx_count ); cpd->txbusy = 1; cpd->txbusyh=1; cpd->txkey = key; // Find packet length plen = 0; for (i = 0; i < sg_len; i++) plen += sg_list[i].len; CYG_ASSERT( plen == total_len, "sg data length mismatch" ); // Get next TX descriptor ring_entry = cpd->tx_ring_free; do { if (cpd->tx_ring_owned == cpd->tx_ring_cnt) { // Is this a dead end? Probably is. #if DEBUG & 1 db_printf("%s: Allocation failed! Retrying...\n", __FUNCTION__ ); #endif continue; } cpd->tx_ring_free++; cpd->tx_ring_owned++; if (cpd->tx_ring_free == cpd->tx_ring_cnt) cpd->tx_ring_free = 0; } while (0); txd = cpd->tx_ring + ring_entry*LANCE_TD_SIZE; buf = cpd->tx_buffers + ring_entry*_BUF_SIZE; CYG_ASSERT(0 == (_SU32(txd, LANCE_TD_PTR) & LANCE_TD_PTR_OWN), "TX descriptor not free"); #if DEBUG & 4 db_printf("#####Tx descriptor 0x%08x buffer 0x%08x\n", txd, buf); #endif // Put data into buffer d = buf; for (i = 0; i < sg_len; i++) { sdata = (cyg_uint8 *)sg_list[i].buf; len = sg_list[i].len; CYG_ASSERT( sdata, "No sg data pointer here" ); while(len--) *d++ = *sdata++; } CYG_ASSERT( sdata, "No sg data pointer outside" ); #if DEBUG & 1 db_printf("CSCR %04x\n", get_reg(sc, LANCE_CSR_CSCR)); #endif _SU16(txd, LANCE_TD_LEN) = (-plen); _SU16(txd, LANCE_TD_MISC) = 0; HAL_PCI_CPU_TO_BUS((cyg_uint32)buf, b); _SU32(txd, LANCE_TD_PTR) = ((b & LANCE_TD_PTR_MASK) | LANCE_TD_PTR_OWN | LANCE_TD_PTR_STP | LANCE_TD_PTR_ENP); #if DEBUG & 1 db_printf("Last TX: LEN %04x MISC %04x PTR %08x\n", _SU16(txd, LANCE_TD_LEN), _SU16(txd, LANCE_TD_MISC), _SU32(txd, LANCE_TD_PTR)); #endif // This delay seems to be necessary on some platforms // (Malta 5kc for example). // Why it is needed is not clear, but removing it or // reducing it cause transmission failures in RedBoot (at least). CYGACC_CALL_IF_DELAY_US(100); // Set transmit demand ints = get_reg(sc, LANCE_CSR_CSCR); ints &= LANCE_CSR_CSCR_EV_MASK; ints |= LANCE_CSR_CSCR_TDMD; put_reg(sc, LANCE_CSR_CSCR, ints); #if DEBUG & 1 ints = get_reg(sc, LANCE_CSR_CSCR); db_printf("%s:END: ints at TX: 0x%04x\n", __FUNCTION__, ints); #endif // This is another mystery delay like the one above. This one is // even stranger, since waiting here at the _end_ of the function // should have no effect. CYGACC_CALL_IF_DELAY_US(200); } static void lancepci_TxEvent(struct eth_drv_sc *sc, int stat) { struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; int success = 1; cyg_uint8 *txd; cyg_uint16 ints; cyg_uint32 pkt_stat; DEBUG_FUNCTION(); if (0 == cpd->tx_ring_owned) { #if DEBUG & 1 db_printf("%s: got TX completion when no outstanding packets\n", __FUNCTION__); #endif return; } INCR_STAT( tx_complete ); txd = cpd->tx_ring + cpd->tx_ring_alloc*LANCE_TD_SIZE; pkt_stat = _SU32(txd, LANCE_TD_PTR); if (pkt_stat & LANCE_TD_PTR_OWN) { #if DEBUG & 1 db_printf("%s: got TX completion when buffer is still owned\n", __FUNCTION__); #endif // first dirty ring entry not freed - wtf? } if (pkt_stat & LANCE_TD_PTR_ERR) { // We had an error. Tell the stack. success = 0; #if DEBUG & 1 db_printf("%s: TX failure, retrying...\n", __FUNCTION__); #endif } cpd->tx_ring_alloc++; if (cpd->tx_ring_alloc == cpd->tx_ring_cnt) cpd->tx_ring_alloc = 0; cpd->tx_ring_owned--; #if FIXME #ifdef KEEP_STATISTICS { cyg_uint16 reg; reg = get_reg( sc, LANCE_CSR_CSCR ); // Covering each bit in turn... if ( reg & LANCE_STATUS_TX_UNRN ) INCR_STAT( tx_underrun ); //if ( reg & LANCE_STATUS_LINK_OK ) INCR_STAT( ); //if ( reg & LANCE_STATUS_CTR_ROL ) INCR_STAT( ); //if ( reg & LANCE_STATUS_EXC_DEF ) INCR_STAT( ); if ( reg & LANCE_STATUS_LOST_CARR ) INCR_STAT( tx_carrier_loss ); if ( reg & LANCE_STATUS_LATCOL ) INCR_STAT( tx_late_collisions ); //if ( reg & LANCE_STATUS_WAKEUP ) INCR_STAT( ); if ( reg & LANCE_STATUS_TX_DEFR ) INCR_STAT( tx_deferred ); //if ( reg & LANCE_STATUS_LTX_BRD ) INCR_STAT( ); if ( reg & LANCE_STATUS_SQET ) INCR_STAT( tx_sqetesterrors ); if ( reg & LANCE_STATUS_16COL ) INCR_STAT( tx_max_collisions ); //if ( reg & LANCE_STATUS_LTX_MULT) INCR_STAT( ); if ( reg & LANCE_STATUS_MUL_COL ) INCR_STAT( tx_mult_collisions ); if ( reg & LANCE_STATUS_SNGL_COL ) INCR_STAT( tx_single_collisions ); if ( reg & LANCE_STATUS_TX_SUC ) INCR_STAT( tx_good ); cpd->stats.tx_total_collisions = cpd->stats.tx_late_collisions + cpd->stats.tx_max_collisions + cpd->stats.tx_mult_collisions + cpd->stats.tx_single_collisions; // We do not need to look in the Counter Register (LANCE_COUNTER) // because it just mimics the info we already have above. } #endif // KEEP_STATISTICS #endif // FIXME // Ack the TX int which clears the packet from the TX completion // queue. ints = get_reg(sc, LANCE_CSR_CSCR); ints |= LANCE_CSR_CSCR_TINT; put_reg(sc, LANCE_CSR_CSCR, ints); #if DEBUG & 4 db_printf("#####Tx packet freed 0x%08x\n", txd ); #endif if ( cpd->txbusy ) { cpd->txbusy = 0; (sc->funs->eth_drv->tx_done)(sc, cpd->txkey, success); } } // // This function is called when a packet has been received. Its job is // to prepare to unload the packet from the hardware. Once the length of // the packet is known, the upper layer of the driver can be told. When // the upper layer is ready to unload the packet, the internal function // 'lancepci_recv' will be called to actually fetch it from the hardware. // static void lancepci_RxEvent(struct eth_drv_sc *sc) { struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; cyg_uint8 *rxd; cyg_uint32 rstat; cyg_uint16 ints, len; DEBUG_FUNCTION(); ints = get_reg(sc, LANCE_CSR_CSCR); #if DEBUG & 1 db_printf("RxEvent - CSR: 0x%04x\n", ints); #endif while (1) { // Get state of next (supposedly) full ring entry cpd->rxpacket = cpd->rx_ring_next; rxd = cpd->rx_ring + cpd->rxpacket*LANCE_RD_SIZE; rstat = _SU32(rxd, LANCE_RD_PTR); // Keep going until we hit an entry that is owned by the // controller. if (rstat & LANCE_RD_PTR_OWN) { #if DEBUG & 1 int i; for (i = 0; i < cpd->rx_ring_cnt; i++) { rxd = cpd->rx_ring + i*LANCE_RD_SIZE; rstat = _SU32(rxd, LANCE_RD_PTR); if (!(rstat & LANCE_RD_PTR_OWN)) { int i; cyg_uint32 rstat; cyg_uint16 mlen, blen; cyg_uint8* rxd; db_printf("%s: Inconsistent RX state\n", __FUNCTION__); for (i = 0; i < cpd->rx_ring_cnt; i++) { rxd = cpd->rx_ring + i*LANCE_RD_SIZE; rstat = _SU32(rxd, LANCE_RD_PTR); blen = _SU16(rxd, LANCE_RD_BLEN); mlen = _SU16(rxd, LANCE_RD_MLEN); db_printf(" %02d: 0x%08x:0x%04x:0x%04x\n", i, rstat, blen, mlen); } } } #endif break; } #if DEBUG & 4 db_printf("#####Rx packet at index %d\n", cpd->rxpacket); #endif // Increment counts INCR_STAT( rx_count ); cpd->rx_ring_next++; if (cpd->rx_ring_next == cpd->rx_ring_cnt) cpd->rx_ring_next = 0; len = _SU16(rxd, LANCE_RD_MLEN); #ifdef KEEP_STATISTICS //if ( rstat & LANCE_RD_PTR_FRAM ) INCR_STAT( rx_frame_errors ); //if ( rstat & LANCE_RD_PTR_OFLO ) INCR_STAT( ); if ( rstat & LANCE_RD_PTR_CRC ) INCR_STAT( rx_crc_errors ); //if ( rstat & LANCE_RD_PTR_BUFF ) INCR_STAT( ); #endif // KEEP_STATISTICS if (0 == (rstat & LANCE_RD_PTR_ERR)) { // It's OK INCR_STAT( rx_good ); #if DEBUG & 1 db_printf("RxEvent good rx - stat: 0x%08x, len: 0x%04x\n", rstat, len); #endif // Check for bogusly short packets; can happen in promisc // mode: Asserted against and checked by upper layer // driver. #ifdef CYGPKG_NET if ( len > sizeof( struct ether_header ) ) // then it is acceptable; offer the data to the network stack #endif (sc->funs->eth_drv->recv)(sc, len); } else { // Not OK for one reason or another... #if DEBUG & 1 db_printf("RxEvent - No RX bit: stat: 0x%08x, len: 0x%04x\n", rstat, len); #endif } // Free packet (clear all status flags, and set OWN) _SU32(rxd, LANCE_RD_PTR) &= LANCE_RD_PTR_MASK; _SU32(rxd, LANCE_RD_PTR) |= LANCE_RD_PTR_OWN; } // Ack RX interrupt set ints = get_reg(sc, LANCE_CSR_CSCR); ints &= LANCE_CSR_CSCR_EV_MASK; ints |= LANCE_CSR_CSCR_RINT; put_reg(sc, LANCE_CSR_CSCR, ints); } // // This function is called as a result of the "eth_drv_recv()" call above. // Its job is to actually fetch data for a packet from the hardware once // memory buffers have been allocated for the packet. Note that the buffers // may come in pieces, using a scatter-gather list. This allows for more // efficient processing in the upper layers of the stack. // static void lancepci_recv(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len) { struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; int i, mlen=0, plen; cyg_uint8 *data, *rxd, *buf; DEBUG_FUNCTION(); rxd = cpd->rx_ring + cpd->rxpacket*LANCE_RD_SIZE; buf = cpd->rx_buffers + cpd->rxpacket*_BUF_SIZE; INCR_STAT( rx_deliver ); plen = _SU16(rxd, LANCE_RD_MLEN); for (i = 0; i < sg_len; i++) { data = (cyg_uint8*)sg_list[i].buf; mlen = sg_list[i].len; #if DEBUG & 1 db_printf("%s : mlen %x, plen %x\n", __FUNCTION__, mlen, plen); #endif if (data) { while (mlen > 0) { *data++ = *buf++; mlen--; plen--; } } } } static void lancepci_poll(struct eth_drv_sc *sc) { cyg_uint16 event; struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; // DEBUG_FUNCTION(); while (1) { // Get the (unmasked) requests if (cpd->event) { event=cpd->event; cpd->event=0; } else event = get_reg(sc, LANCE_CSR_CSCR); if (!((LANCE_CSR_CSCR_ERR|LANCE_CSR_CSCR_INTR) & event)) break; if (event & LANCE_CSR_CSCR_RINT) { lancepci_RxEvent(sc); } else if (event & LANCE_CSR_CSCR_TINT) { cpd->txbusyh=0; // again , for polled mode lancepci_TxEvent(sc, event); } else if (event & LANCE_CSR_CSCR_MISS) { #if DEBUG & 1 int i; cyg_uint32 rstat; cyg_uint16 mlen, blen; cyg_uint8* rxd; struct lancepci_priv_data *cpd = (struct lancepci_priv_data *)sc->driver_private; db_printf("%s: Ran out of RX buffers (%04x)\n", __FUNCTION__, event); for (i = 0; i < cpd->rx_ring_cnt; i++) { rxd = cpd->rx_ring + i*LANCE_TD_SIZE; rstat = _SU32(rxd, LANCE_RD_PTR); blen = _SU16(rxd, LANCE_RD_BLEN); mlen = _SU16(rxd, LANCE_RD_MLEN); db_printf(" %02d: 0x%08x:0x%04x:0x%04x\n", i, rstat, blen, mlen); } #endif event &= LANCE_CSR_CSCR_EV_MASK; event |= LANCE_CSR_CSCR_MISS; put_reg(sc, LANCE_CSR_CSCR, event); } else { #if DEBUG & 1 db_printf("%s: Unknown interrupt: 0x%04x\n", __FUNCTION__, event); #endif put_reg(sc, LANCE_CSR_CSCR, event); } } } // EOF if_lancepci.c