From 90c01ef1e48ef95a24f90940becfa476eefc5493 Mon Sep 17 00:00:00 2001 From: Thomas Miletich Date: Wed, 4 Mar 2009 16:02:13 -0500 Subject: [PATCH] [3c90x] 3c90x driver rewrite using gPXE API This is a major rewrite of the legacy etherboot 3c90x driver using the gPXE API for much improved performance over the legacy driver it replaces. This driver has been tested on 3c905, 3c905B, and 3c905C cards. Reviewed-by: Stefan Hajnoczi Reviewed-by: Marty Connor Tested-by: Marty Connor Tested-by: Daniel Verkamp Signed-off-by: Marty Connor --- src/drivers/net/3c90x.c | 1923 +++++++++++++++++++-------------------- src/drivers/net/3c90x.h | 300 ++++++ 2 files changed, 1244 insertions(+), 979 deletions(-) create mode 100644 src/drivers/net/3c90x.h diff --git a/src/drivers/net/3c90x.c b/src/drivers/net/3c90x.c index a98e6628..3a7495a0 100644 --- a/src/drivers/net/3c90x.c +++ b/src/drivers/net/3c90x.c @@ -1,10 +1,19 @@ /* - * 3c90x.c -- This file implements the 3c90x driver for etherboot. Written - * by Greg Beeley, Greg.Beeley@LightSys.org. Modified by Steve Smith, - * Steve.Smith@Juno.Com. Alignment bug fix Neil Newell (nn@icenoir.net). + * 3c90x.c -- This file implements a gPXE API 3c90x driver * - * This program Copyright (C) 1999 LightSys Technology Services, Inc. - * Portions Copyright (C) 1999 Steve Smith + * Originally written for etherboot by: + * Greg Beeley, Greg.Beeley@LightSys.org + * Modified by Steve Smith, + * Steve.Smith@Juno.Com. Alignment bug fix Neil Newell (nn@icenoir.net). + * Almost totally Rewritten to use gPXE API, implementation of tx/rx ring support + * by Thomas Miletich, thomas.miletich@gmail.com + * Thanks to Marty Connor and Stefan Hajnoczi for their help and feedback, + * and to Daniel Verkamp for his help with testing. + * + * Copyright (c) 2009 Thomas Miletich + * + * Copyright (c) 1999 LightSys Technology Services, Inc. + * Portions Copyright (c) 1999 Steve Smith * * This program may be re-distributed in source or binary form, modified, * sold, or copied for any purpose, provided that the above copyright message @@ -15,1008 +24,964 @@ * PURPOSE or MERCHANTABILITY. Please read the associated documentation * "3c90x.txt" before compiling and using this driver. * - * -------- + * [ --mdc 20090313 The 3c90x.txt file is now at: + * http://etherboot.org/wiki/appnotes/3c90x_issues ] * - * Program written with the assistance of the 3com documentation for + * This program was written with the assistance of the 3com documentation for * the 3c905B-TX card, as well as with some assistance from the 3c59x * driver Donald Becker wrote for the Linux kernel, and with some assistance * from the remainder of the Etherboot distribution. * - * REVISION HISTORY: - * - * v0.10 1-26-1998 GRB Initial implementation. - * v0.90 1-27-1998 GRB System works. - * v1.00pre1 2-11-1998 GRB Got prom boot issue fixed. - * v2.0 9-24-1999 SCS Modified for 3c905 (from 3c905b code) - * Re-wrote poll and transmit for - * better error recovery and heavy - * network traffic operation - * v2.01 5-26-2003 NN Fixed driver alignment issue which - * caused system lockups if driver structures - * not 8-byte aligned. - * v2.02 11-28-2007 GSt Got polling working again by replacing - * "for(i=0;i<40000;i++);" with "mdelay(1);" - * + * Indented with unix 'indent' command: + * $ indent -kr -i8 3c90x.c */ -#include "etherboot.h" -#include "nic.h" -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include -static struct nic_operations a3c90x_operations; +#include "3c90x.h" -#define XCVR_MAGIC (0x5A00) -/** any single transmission fails after 16 collisions or other errors - ** this is the number of times to retry the transmission -- this should - ** be plenty - **/ -#define XMIT_RETRIES 250 +/** + * a3c90x_internal_IssueCommand: sends a command to the 3c90x card + * and waits for it's completion + * + * @v ioaddr IOAddress of the NIC + * @v cmd Command to be issued + * @v param Command parameter + */ +static void a3c90x_internal_IssueCommand(int ioaddr, int cmd, int param) +{ + unsigned int val = (cmd << 11) | param; + int cnt = 0; -/*** Register definitions for the 3c905 ***/ -enum Registers - { - regPowerMgmtCtrl_w = 0x7c, /** 905B Revision Only **/ - regUpMaxBurst_w = 0x7a, /** 905B Revision Only **/ - regDnMaxBurst_w = 0x78, /** 905B Revision Only **/ - regDebugControl_w = 0x74, /** 905B Revision Only **/ - regDebugData_l = 0x70, /** 905B Revision Only **/ - regRealTimeCnt_l = 0x40, /** Universal **/ - regUpBurstThresh_b = 0x3e, /** 905B Revision Only **/ - regUpPoll_b = 0x3d, /** 905B Revision Only **/ - regUpPriorityThresh_b = 0x3c, /** 905B Revision Only **/ - regUpListPtr_l = 0x38, /** Universal **/ - regCountdown_w = 0x36, /** Universal **/ - regFreeTimer_w = 0x34, /** Universal **/ - regUpPktStatus_l = 0x30, /** Universal with Exception, pg 130 **/ - regTxFreeThresh_b = 0x2f, /** 90X Revision Only **/ - regDnPoll_b = 0x2d, /** 905B Revision Only **/ - regDnPriorityThresh_b = 0x2c, /** 905B Revision Only **/ - regDnBurstThresh_b = 0x2a, /** 905B Revision Only **/ - regDnListPtr_l = 0x24, /** Universal with Exception, pg 107 **/ - regDmaCtrl_l = 0x20, /** Universal with Exception, pg 106 **/ - /** **/ - regIntStatusAuto_w = 0x1e, /** 905B Revision Only **/ - regTxStatus_b = 0x1b, /** Universal with Exception, pg 113 **/ - regTimer_b = 0x1a, /** Universal **/ - regTxPktId_b = 0x18, /** 905B Revision Only **/ - regCommandIntStatus_w = 0x0e, /** Universal (Command Variations) **/ - }; + DBGP("a3c90x_internal_IssueCommand\n"); -/** following are windowed registers **/ -enum Registers7 - { - regPowerMgmtEvent_7_w = 0x0c, /** 905B Revision Only **/ - regVlanEtherType_7_w = 0x04, /** 905B Revision Only **/ - regVlanMask_7_w = 0x00, /** 905B Revision Only **/ - }; - -enum Registers6 - { - regBytesXmittedOk_6_w = 0x0c, /** Universal **/ - regBytesRcvdOk_6_w = 0x0a, /** Universal **/ - regUpperFramesOk_6_b = 0x09, /** Universal **/ - regFramesDeferred_6_b = 0x08, /** Universal **/ - regFramesRecdOk_6_b = 0x07, /** Universal with Exceptions, pg 142 **/ - regFramesXmittedOk_6_b = 0x06, /** Universal **/ - regRxOverruns_6_b = 0x05, /** Universal **/ - regLateCollisions_6_b = 0x04, /** Universal **/ - regSingleCollisions_6_b = 0x03, /** Universal **/ - regMultipleCollisions_6_b = 0x02, /** Universal **/ - regSqeErrors_6_b = 0x01, /** Universal **/ - regCarrierLost_6_b = 0x00, /** Universal **/ - }; - -enum Registers5 - { - regIndicationEnable_5_w = 0x0c, /** Universal **/ - regInterruptEnable_5_w = 0x0a, /** Universal **/ - regTxReclaimThresh_5_b = 0x09, /** 905B Revision Only **/ - regRxFilter_5_b = 0x08, /** Universal **/ - regRxEarlyThresh_5_w = 0x06, /** Universal **/ - regTxStartThresh_5_w = 0x00, /** Universal **/ - }; - -enum Registers4 - { - regUpperBytesOk_4_b = 0x0d, /** Universal **/ - regBadSSD_4_b = 0x0c, /** Universal **/ - regMediaStatus_4_w = 0x0a, /** Universal with Exceptions, pg 201 **/ - regPhysicalMgmt_4_w = 0x08, /** Universal **/ - regNetworkDiagnostic_4_w = 0x06, /** Universal with Exceptions, pg 203 **/ - regFifoDiagnostic_4_w = 0x04, /** Universal with Exceptions, pg 196 **/ - regVcoDiagnostic_4_w = 0x02, /** Undocumented? **/ - }; - -enum Registers3 - { - regTxFree_3_w = 0x0c, /** Universal **/ - regRxFree_3_w = 0x0a, /** Universal with Exceptions, pg 125 **/ - regResetMediaOptions_3_w = 0x08, /** Media Options on B Revision, **/ - /** Reset Options on Non-B Revision **/ - regMacControl_3_w = 0x06, /** Universal with Exceptions, pg 199 **/ - regMaxPktSize_3_w = 0x04, /** 905B Revision Only **/ - regInternalConfig_3_l = 0x00, /** Universal, different bit **/ - /** definitions, pg 59 **/ - }; - -enum Registers2 - { - regResetOptions_2_w = 0x0c, /** 905B Revision Only **/ - regStationMask_2_3w = 0x06, /** Universal with Exceptions, pg 127 **/ - regStationAddress_2_3w = 0x00, /** Universal with Exceptions, pg 127 **/ - }; - -enum Registers1 - { - regRxStatus_1_w = 0x0a, /** 90X Revision Only, Pg 126 **/ - }; - -enum Registers0 - { - regEepromData_0_w = 0x0c, /** Universal **/ - regEepromCommand_0_w = 0x0a, /** Universal **/ - regBiosRomData_0_b = 0x08, /** 905B Revision Only **/ - regBiosRomAddr_0_l = 0x04, /** 905B Revision Only **/ - }; - - -/*** The names for the eight register windows ***/ -enum Windows - { - winPowerVlan7 = 0x07, - winStatistics6 = 0x06, - winTxRxControl5 = 0x05, - winDiagnostics4 = 0x04, - winTxRxOptions3 = 0x03, - winAddressing2 = 0x02, - winUnused1 = 0x01, - winEepromBios0 = 0x00, - }; - - -/*** Command definitions for the 3c90X ***/ -enum Commands - { - cmdGlobalReset = 0x00, /** Universal with Exceptions, pg 151 **/ - cmdSelectRegisterWindow = 0x01, /** Universal **/ - cmdEnableDcConverter = 0x02, /** **/ - cmdRxDisable = 0x03, /** **/ - cmdRxEnable = 0x04, /** Universal **/ - cmdRxReset = 0x05, /** Universal **/ - cmdStallCtl = 0x06, /** Universal **/ - cmdTxEnable = 0x09, /** Universal **/ - cmdTxDisable = 0x0A, /** **/ - cmdTxReset = 0x0B, /** Universal **/ - cmdRequestInterrupt = 0x0C, /** **/ - cmdAcknowledgeInterrupt = 0x0D, /** Universal **/ - cmdSetInterruptEnable = 0x0E, /** Universal **/ - cmdSetIndicationEnable = 0x0F, /** Universal **/ - cmdSetRxFilter = 0x10, /** Universal **/ - cmdSetRxEarlyThresh = 0x11, /** **/ - cmdSetTxStartThresh = 0x13, /** **/ - cmdStatisticsEnable = 0x15, /** **/ - cmdStatisticsDisable = 0x16, /** **/ - cmdDisableDcConverter = 0x17, /** **/ - cmdSetTxReclaimThresh = 0x18, /** **/ - cmdSetHashFilterBit = 0x19, /** **/ - }; - - -/*** Values for int status register bitmask **/ -#define INT_INTERRUPTLATCH (1<<0) -#define INT_HOSTERROR (1<<1) -#define INT_TXCOMPLETE (1<<2) -#define INT_RXCOMPLETE (1<<4) -#define INT_RXEARLY (1<<5) -#define INT_INTREQUESTED (1<<6) -#define INT_UPDATESTATS (1<<7) -#define INT_LINKEVENT (1<<8) -#define INT_DNCOMPLETE (1<<9) -#define INT_UPCOMPLETE (1<<10) -#define INT_CMDINPROGRESS (1<<12) -#define INT_WINDOWNUMBER (7<<13) - - -/*** TX descriptor ***/ -typedef struct - { - unsigned int DnNextPtr; - unsigned int FrameStartHeader; - unsigned int HdrAddr; - unsigned int HdrLength; - unsigned int DataAddr; - unsigned int DataLength; - } - TXD __attribute__ ((aligned(8))); /* 64-bit aligned for bus mastering */ - -/*** RX descriptor ***/ -typedef struct - { - unsigned int UpNextPtr; - unsigned int UpPktStatus; - unsigned int DataAddr; - unsigned int DataLength; - } - RXD __attribute__ ((aligned(8))); /* 64-bit aligned for bus mastering */ - -/*** Global variables ***/ -static struct - { - unsigned int is3c556; - unsigned char isBrev; - unsigned char CurrentWindow; - unsigned int IOAddr; - unsigned char HWAddr[ETH_ALEN]; - TXD TransmitDPD; - RXD ReceiveUPD; - } - INF_3C90X; - - -/*** a3c90x_internal_IssueCommand: sends a command to the 3c90x card - ***/ -static int -a3c90x_internal_IssueCommand(int ioaddr, int cmd, int param) - { - unsigned int val; - - /** Build the cmd. **/ - val = cmd; - val <<= 11; - val |= param; - - /** Send the cmd to the cmd register **/ + /* Send the cmd to the cmd register */ outw(val, ioaddr + regCommandIntStatus_w); - /** Wait for the cmd to complete, if necessary **/ - while (inw(ioaddr + regCommandIntStatus_w) & INT_CMDINPROGRESS); - - return 0; - } - - -/*** a3c90x_internal_SetWindow: selects a register window set. - ***/ -static int -a3c90x_internal_SetWindow(int ioaddr, int window) - { - - /** Window already as set? **/ - if (INF_3C90X.CurrentWindow == window) return 0; - - /** Issue the window command. **/ - a3c90x_internal_IssueCommand(ioaddr, cmdSelectRegisterWindow, window); - INF_3C90X.CurrentWindow = window; - - return 0; - } - - -/*** a3c90x_internal_ReadEeprom - read data from the serial eeprom. - ***/ -static unsigned short -a3c90x_internal_ReadEeprom(int ioaddr, int address) - { - unsigned short val; - - /** Select correct window **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winEepromBios0); - - /** Make sure the eeprom isn't busy **/ - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - /** Read the value. **/ - if (INF_3C90X.is3c556) - { - outw(address + (0x230), ioaddr + regEepromCommand_0_w); - } - else - { - outw(address + ((0x02)<<6), ioaddr + regEepromCommand_0_w); - } - - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - val = inw(ioaddr + regEepromData_0_w); - - return val; - } - - -#if 0 -/*** a3c90x_internal_WriteEepromWord - write a physical word of - *** data to the onboard serial eeprom (not the BIOS prom, but the - *** nvram in the card that stores, among other things, the MAC - *** address). - ***/ -static int -a3c90x_internal_WriteEepromWord(int ioaddr, int address, unsigned short value) - { - /** Select register window **/ - a3c90x_internal_SetWindow(ioaddr, winEepromBios0); - - /** Verify Eeprom not busy **/ - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - /** Issue WriteEnable, and wait for completion. **/ - outw(0x30, ioaddr + regEepromCommand_0_w); - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - /** Issue EraseRegister, and wait for completion. **/ - outw(address + ((0x03)<<6), ioaddr + regEepromCommand_0_w); - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - /** Send the new data to the eeprom, and wait for completion. **/ - outw(value, ioaddr + regEepromData_0_w); - outw(0x30, ioaddr + regEepromCommand_0_w); - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - /** Burn the new data into the eeprom, and wait for completion. **/ - outw(address + ((0x01)<<6), ioaddr + regEepromCommand_0_w); - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - return 0; - } -#endif - -#if 0 -/*** a3c90x_internal_WriteEeprom - write data to the serial eeprom, - *** and re-compute the eeprom checksum. - ***/ -static int -a3c90x_internal_WriteEeprom(int ioaddr, int address, unsigned short value) - { - int cksum = 0,v; - int i; - int maxAddress, cksumAddress; - - if (INF_3C90X.isBrev) - { - maxAddress=0x1f; - cksumAddress=0x20; - } - else - { - maxAddress=0x16; - cksumAddress=0x17; - } - - /** Write the value. **/ - if (a3c90x_internal_WriteEepromWord(ioaddr, address, value) == -1) - return -1; - - /** Recompute the checksum. **/ - for(i=0;i<=maxAddress;i++) - { - v = a3c90x_internal_ReadEeprom(ioaddr, i); - cksum ^= (v & 0xFF); - cksum ^= ((v>>8) & 0xFF); - } - /** Write the checksum to the location in the eeprom **/ - if (a3c90x_internal_WriteEepromWord(ioaddr, cksumAddress, cksum) == -1) - return -1; - - return 0; - } -#endif - -/*** a3c90x_reset: exported function that resets the card to its default - *** state. This is so the Linux driver can re-set the card up the way - *** it wants to. If CFG_3C90X_PRESERVE_XCVR is defined, then the reset will - *** not alter the selected transceiver that we used to download the boot - *** image. - ***/ -static void a3c90x_reset(void) - { -#ifdef CFG_3C90X_PRESERVE_XCVR - int cfg; - /** Read the current InternalConfig value. **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winTxRxOptions3); - cfg = inl(INF_3C90X.IOAddr + regInternalConfig_3_l); -#endif - - /** Send the reset command to the card **/ - printf("Issuing RESET:\n"); - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdGlobalReset, 0); - - /** wait for reset command to complete **/ - while (inw(INF_3C90X.IOAddr + regCommandIntStatus_w) & INT_CMDINPROGRESS); - - /** global reset command resets station mask, non-B revision cards - ** require explicit reset of values - **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winAddressing2); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+0); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+2); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+4); - -#ifdef CFG_3C90X_PRESERVE_XCVR - /** Re-set the original InternalConfig value from before reset **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winTxRxOptions3); - outl(cfg, INF_3C90X.IOAddr + regInternalConfig_3_l); - - /** enable DC converter for 10-Base-T **/ - if ((cfg&0x0300) == 0x0300) - { - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdEnableDcConverter, 0); - } -#endif - - /** Issue transmit reset, wait for command completion **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxReset, 0); - while (inw(INF_3C90X.IOAddr + regCommandIntStatus_w) & INT_CMDINPROGRESS) - ; - if (! INF_3C90X.isBrev) - outb(0x01, INF_3C90X.IOAddr + regTxFreeThresh_b); - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxEnable, 0); - - /** - ** reset of the receiver on B-revision cards re-negotiates the link - ** takes several seconds (a computer eternity) - **/ - if (INF_3C90X.isBrev) - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxReset, 0x04); - else - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxReset, 0x00); - while (inw(INF_3C90X.IOAddr + regCommandIntStatus_w) & INT_CMDINPROGRESS); - ; - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxEnable, 0); - - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, - cmdSetInterruptEnable, 0); - /** enable rxComplete and txComplete **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, - cmdSetIndicationEnable, 0x0014); - /** acknowledge any pending status flags **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, - cmdAcknowledgeInterrupt, 0x661); - - return; - } - - - -/*** a3c90x_transmit: exported function that transmits a packet. Does not - *** return any particular status. Parameters are: - *** d[6] - destination address, ethernet; - *** t - protocol type (ARP, IP, etc); - *** s - size of the non-header part of the packet that needs transmitted; - *** p - the pointer to the packet data itself. - ***/ -static void -a3c90x_transmit(struct nic *nic __unused, const char *d, unsigned int t, - unsigned int s, const char *p) - { - - struct eth_hdr - { - unsigned char dst_addr[ETH_ALEN]; - unsigned char src_addr[ETH_ALEN]; - unsigned short type; - } hdr; - - unsigned char status; - unsigned i, retries; - unsigned long ct; - - for (retries=0; retries < XMIT_RETRIES ; retries++) - { - /** Stall the download engine **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdStallCtl, 2); - - /** Make sure the card is not waiting on us **/ - inw(INF_3C90X.IOAddr + regCommandIntStatus_w); - inw(INF_3C90X.IOAddr + regCommandIntStatus_w); - - while (inw(INF_3C90X.IOAddr+regCommandIntStatus_w) & - INT_CMDINPROGRESS) - ; - - /** Set the ethernet packet type **/ - hdr.type = htons(t); - - /** Copy the destination address **/ - memcpy(hdr.dst_addr, d, ETH_ALEN); - - /** Copy our MAC address **/ - memcpy(hdr.src_addr, INF_3C90X.HWAddr, ETH_ALEN); - - /** Setup the DPD (download descriptor) **/ - INF_3C90X.TransmitDPD.DnNextPtr = 0; - /** set notification for transmission completion (bit 15) **/ - INF_3C90X.TransmitDPD.FrameStartHeader = (s + sizeof(hdr)) | 0x8000; - INF_3C90X.TransmitDPD.HdrAddr = virt_to_bus(&hdr); - INF_3C90X.TransmitDPD.HdrLength = sizeof(hdr); - INF_3C90X.TransmitDPD.DataAddr = virt_to_bus(p); - INF_3C90X.TransmitDPD.DataLength = s + (1<<31); - - /** Send the packet **/ - outl(virt_to_bus(&(INF_3C90X.TransmitDPD)), - INF_3C90X.IOAddr + regDnListPtr_l); - - /** End Stall and Wait for upload to complete. **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdStallCtl, 3); - while(inl(INF_3C90X.IOAddr + regDnListPtr_l) != 0) - ; - - /** Wait for NIC Transmit to Complete **/ - ct = currticks(); - - while (!(inw(INF_3C90X.IOAddr + regCommandIntStatus_w)&0x0004) && - ct + 10*1000 < currticks()); - ; - - if (!(inw(INF_3C90X.IOAddr + regCommandIntStatus_w)&0x0004)) - { - printf("3C90X: Tx Timeout\n"); - continue; - } - - status = inb(INF_3C90X.IOAddr + regTxStatus_b); - - /** acknowledge transmit interrupt by writing status **/ - outb(0x00, INF_3C90X.IOAddr + regTxStatus_b); - - /** successful completion (sans "interrupt Requested" bit) **/ - if ((status & 0xbf) == 0x80) - return; - - printf("3C90X: Status (%hhX)\n", status); - /** check error codes **/ - if (status & 0x02) - { - printf("3C90X: Tx Reclaim Error (%hhX)\n", status); - a3c90x_reset(); - } - else if (status & 0x04) - { - printf("3C90X: Tx Status Overflow (%hhX)\n", status); - for (i=0; i<32; i++) - outb(0x00, INF_3C90X.IOAddr + regTxStatus_b); - /** must re-enable after max collisions before re-issuing tx **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxEnable, 0); - } - else if (status & 0x08) - { - printf("3C90X: Tx Max Collisions (%hhX)\n", status); - /** must re-enable after max collisions before re-issuing tx **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxEnable, 0); - } - else if (status & 0x10) - { - printf("3C90X: Tx Underrun (%hhX)\n", status); - a3c90x_reset(); - } - else if (status & 0x20) - { - printf("3C90X: Tx Jabber (%hhX)\n", status); - a3c90x_reset(); - } - else if ((status & 0x80) != 0x80) - { - printf("3C90X: Internal Error - Incomplete Transmission (%hhX)\n", - status); - a3c90x_reset(); - } - } - - /** failed after RETRY attempts **/ - printf("Failed to send after %d retries\n", retries); - return; - - } - - - -/*** a3c90x_poll: exported routine that waits for a certain length of time - *** for a packet, and if it sees none, returns 0. This routine should - *** copy the packet to nic->packet if it gets a packet and set the size - *** in nic->packetlen. Return 1 if a packet was found. - ***/ -static int -a3c90x_poll(struct nic *nic, int retrieve) - { - int errcode; - - if (!(inw(INF_3C90X.IOAddr + regCommandIntStatus_w)&0x0010)) - { - return 0; - } - - if ( ! retrieve ) return 1; - - /** we don't need to acknowledge rxComplete -- the upload engine - ** does it for us. - **/ - - /** Build the up-load descriptor **/ - INF_3C90X.ReceiveUPD.UpNextPtr = 0; - INF_3C90X.ReceiveUPD.UpPktStatus = 0; - INF_3C90X.ReceiveUPD.DataAddr = virt_to_bus(nic->packet); - INF_3C90X.ReceiveUPD.DataLength = 1536 + (1<<31); - - /** Submit the upload descriptor to the NIC **/ - outl(virt_to_bus(&(INF_3C90X.ReceiveUPD)), - INF_3C90X.IOAddr + regUpListPtr_l); - - /** Wait for upload completion (upComplete(15) or upError (14)) **/ - mdelay(1); - while((INF_3C90X.ReceiveUPD.UpPktStatus & ((1<<14) | (1<<15))) == 0) - mdelay(1); - - /** Check for Error (else we have good packet) **/ - if (INF_3C90X.ReceiveUPD.UpPktStatus & (1<<14)) - { - errcode = INF_3C90X.ReceiveUPD.UpPktStatus; - if (errcode & (1<<16)) - printf("3C90X: Rx Overrun (%hX)\n",errcode>>16); - else if (errcode & (1<<17)) - printf("3C90X: Runt Frame (%hX)\n",errcode>>16); - else if (errcode & (1<<18)) - printf("3C90X: Alignment Error (%hX)\n",errcode>>16); - else if (errcode & (1<<19)) - printf("3C90X: CRC Error (%hX)\n",errcode>>16); - else if (errcode & (1<<20)) - printf("3C90X: Oversized Frame (%hX)\n",errcode>>16); - else - printf("3C90X: Packet error (%hX)\n",errcode>>16); - return 0; - } - - /** Ok, got packet. Set length in nic->packetlen. **/ - nic->packetlen = (INF_3C90X.ReceiveUPD.UpPktStatus & 0x1FFF); - - return 1; - } - - - -/*** a3c90x_disable: exported routine to disable the card. What's this for? - *** the eepro100.c driver didn't have one, so I just left this one empty too. - *** Ideas anyone? - *** Must turn off receiver at least so stray packets will not corrupt memory - *** [Ken] - ***/ -static void -a3c90x_disable ( struct nic *nic __unused ) { - a3c90x_reset(); - /* Disable the receiver and transmitter. */ - outw(cmdRxDisable, INF_3C90X.IOAddr + regCommandIntStatus_w); - outw(cmdTxDisable, INF_3C90X.IOAddr + regCommandIntStatus_w); -} - -static void a3c90x_irq(struct nic *nic __unused, irq_action_t action __unused) -{ - switch ( action ) { - case DISABLE : - break; - case ENABLE : - break; - case FORCE : - break; - } -} - -/*** a3c90x_probe: exported routine to probe for the 3c905 card and perform - *** initialization. If this routine is called, the pci functions did find the - *** card. We just have to init it here. - ***/ -static int a3c90x_probe ( struct nic *nic, struct pci_device *pci ) { - - int i, c; - unsigned short eeprom[0x21]; - unsigned int cfg; - unsigned int mopt; - unsigned int mstat; - unsigned short linktype; -#define HWADDR_OFFSET 10 - - if (pci->ioaddr == 0) - return 0; - - adjust_pci_device(pci); - - nic->ioaddr = pci->ioaddr; - nic->irqno = 0; - - INF_3C90X.is3c556 = (pci->device == 0x6055); - INF_3C90X.IOAddr = pci->ioaddr & ~3; - INF_3C90X.CurrentWindow = 255; - switch (a3c90x_internal_ReadEeprom(INF_3C90X.IOAddr, 0x03)) - { - case 0x9000: /** 10 Base TPO **/ - case 0x9001: /** 10/100 T4 **/ - case 0x9050: /** 10/100 TPO **/ - case 0x9051: /** 10 Base Combo **/ - INF_3C90X.isBrev = 0; - break; - - case 0x9004: /** 10 Base TPO **/ - case 0x9005: /** 10 Base Combo **/ - case 0x9006: /** 10 Base TPO and Base2 **/ - case 0x900A: /** 10 Base FL **/ - case 0x9055: /** 10/100 TPO **/ - case 0x9056: /** 10/100 T4 **/ - case 0x905A: /** 10 Base FX **/ - default: - INF_3C90X.isBrev = 1; - break; - } - - /** Load the EEPROM contents **/ - if (INF_3C90X.isBrev) - { - for(i=0;i<=0x20;i++) - { - eeprom[i] = a3c90x_internal_ReadEeprom(INF_3C90X.IOAddr, i); - } - -#ifdef CFG_3C90X_BOOTROM_FIX - /** Set xcvrSelect in InternalConfig in eeprom. **/ - /* only necessary for 3c905b revision cards with boot PROM bug!!! */ - a3c90x_internal_WriteEeprom(INF_3C90X.IOAddr, 0x13, 0x0160); -#endif - -#ifdef CFG_3C90X_XCVR - if (CFG_3C90X_XCVR == 255) - { - /** Clear the LanWorks register **/ - a3c90x_internal_WriteEeprom(INF_3C90X.IOAddr, 0x16, 0); - } - else - { - /** Set the selected permanent-xcvrSelect in the - ** LanWorks register - **/ - a3c90x_internal_WriteEeprom(INF_3C90X.IOAddr, 0x16, - XCVR_MAGIC + ((CFG_3C90X_XCVR) & 0x000F)); - } -#endif - } - else - { - for(i=0;i<=0x17;i++) - { - eeprom[i] = a3c90x_internal_ReadEeprom(INF_3C90X.IOAddr, i); - } - } - - /** Print identification message **/ - printf("\n\n3C90X Driver 2.02 " - "Copyright 1999 LightSys Technology Services, Inc.\n" - "Portions Copyright 1999 Steve Smith\n"); - printf("Provided with ABSOLUTELY NO WARRANTY.\n"); -#ifdef CFG_3C90X_BOOTROM_FIX - if (INF_3C90X.isBrev) - { - printf("NOTE: 3c905b bootrom fix enabled; has side " - "effects. See 3c90x.txt for info.\n"); - } -#endif - printf("-------------------------------------------------------" - "------------------------\n"); - - /** Retrieve the Hardware address and print it on the screen. **/ - INF_3C90X.HWAddr[0] = eeprom[HWADDR_OFFSET + 0]>>8; - INF_3C90X.HWAddr[1] = eeprom[HWADDR_OFFSET + 0]&0xFF; - INF_3C90X.HWAddr[2] = eeprom[HWADDR_OFFSET + 1]>>8; - INF_3C90X.HWAddr[3] = eeprom[HWADDR_OFFSET + 1]&0xFF; - INF_3C90X.HWAddr[4] = eeprom[HWADDR_OFFSET + 2]>>8; - INF_3C90X.HWAddr[5] = eeprom[HWADDR_OFFSET + 2]&0xFF; - - DBG ( "MAC Address = %s\n", eth_ntoa ( INF_3C90X.HWAddr ) ); - - /** 3C556: Invert MII power **/ - if (INF_3C90X.is3c556) { - unsigned int tmp; - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winAddressing2); - tmp = inw(INF_3C90X.IOAddr + regResetOptions_2_w); - tmp |= 0x4000; - outw(tmp, INF_3C90X.IOAddr + regResetOptions_2_w); - } - - /* Test if the link is good, if not continue */ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winDiagnostics4); - mstat = inw(INF_3C90X.IOAddr + regMediaStatus_4_w); - if((mstat & (1<<11)) == 0) { - printf("Valid link not established\n"); - return 0; - } - - /** Program the MAC address into the station address registers **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winAddressing2); - outw(htons(eeprom[HWADDR_OFFSET + 0]), INF_3C90X.IOAddr + regStationAddress_2_3w); - outw(htons(eeprom[HWADDR_OFFSET + 1]), INF_3C90X.IOAddr + regStationAddress_2_3w+2); - outw(htons(eeprom[HWADDR_OFFSET + 2]), INF_3C90X.IOAddr + regStationAddress_2_3w+4); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+0); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+2); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+4); - - /** Fill in our entry in the etherboot arp table **/ - for(i=0;inode_addr[i] = (eeprom[HWADDR_OFFSET + i/2] >> (8*((i&1)^1))) & 0xff; - - /** Read the media options register, print a message and set default - ** xcvr. - ** - ** Uses Media Option command on B revision, Reset Option on non-B - ** revision cards -- same register address - **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winTxRxOptions3); - mopt = inw(INF_3C90X.IOAddr + regResetMediaOptions_3_w); - - /** mask out VCO bit that is defined as 10baseFL bit on B-rev cards **/ - if (! INF_3C90X.isBrev) - { - mopt &= 0x7F; - } - - printf("Connectors present: "); - c = 0; - linktype = 0x0008; - if (mopt & 0x01) - { - printf("%s100Base-T4",(c++)?", ":""); - linktype = 0x0006; - } - if (mopt & 0x04) - { - printf("%s100Base-FX",(c++)?", ":""); - linktype = 0x0005; - } - if (mopt & 0x10) - { - printf("%s10Base-2",(c++)?", ":""); - linktype = 0x0003; - } - if (mopt & 0x20) - { - printf("%sAUI",(c++)?", ":""); - linktype = 0x0001; - } - if (mopt & 0x40) - { - printf("%sMII",(c++)?", ":""); - linktype = 0x0006; - } - if ((mopt & 0xA) == 0xA) - { - printf("%s10Base-T / 100Base-TX",(c++)?", ":""); - linktype = 0x0008; - } - else if ((mopt & 0xA) == 0x2) - { - printf("%s100Base-TX",(c++)?", ":""); - linktype = 0x0008; - } - else if ((mopt & 0xA) == 0x8) - { - printf("%s10Base-T",(c++)?", ":""); - linktype = 0x0008; - } - printf(".\n"); - - /** Determine transceiver type to use, depending on value stored in - ** eeprom 0x16 - **/ - if (INF_3C90X.isBrev) - { - if ((eeprom[0x16] & 0xFF00) == XCVR_MAGIC) - { - /** User-defined **/ - linktype = eeprom[0x16] & 0x000F; - } - } - else - { -#ifdef CFG_3C90X_XCVR - if (CFG_3C90X_XCVR != 255) - linktype = CFG_3C90X_XCVR; -#endif /* CFG_3C90X_XCVR */ - - /** I don't know what MII MAC only mode is!!! **/ - if (linktype == 0x0009) - { - if (INF_3C90X.isBrev) - printf("WARNING: MII External MAC Mode only supported on B-revision " - "cards!!!!\nFalling Back to MII Mode\n"); - linktype = 0x0006; + /* Wait for the cmd to complete */ + for (cnt = 0; cnt < 100000; cnt++) { + if (inw(ioaddr + regCommandIntStatus_w) & INT_CMDINPROGRESS) { + continue; + } else { + DBG2("Command 0x%04X finished in time. cnt = %d.\n", cmd, cnt); + return; } } - /** enable DC converter for 10-Base-T **/ - if (linktype == 0x0003) - { - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdEnableDcConverter, 0); - } - - /** Set the link to the type we just determined. **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winTxRxOptions3); - cfg = inl(INF_3C90X.IOAddr + regInternalConfig_3_l); - cfg &= ~(0xF<<20); - cfg |= (linktype<<20); - outl(cfg, INF_3C90X.IOAddr + regInternalConfig_3_l); - - /** Now that we set the xcvr type, reset the Tx and Rx, re-enable. **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxReset, 0x00); - while (inw(INF_3C90X.IOAddr + regCommandIntStatus_w) & INT_CMDINPROGRESS) - ; - - if (!INF_3C90X.isBrev) - outb(0x01, INF_3C90X.IOAddr + regTxFreeThresh_b); - - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxEnable, 0); - - /** - ** reset of the receiver on B-revision cards re-negotiates the link - ** takes several seconds (a computer eternity) - **/ - if (INF_3C90X.isBrev) - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxReset, 0x04); - else - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxReset, 0x00); - while (inw(INF_3C90X.IOAddr + regCommandIntStatus_w) & INT_CMDINPROGRESS) - ; - - /** Set the RX filter = receive only individual pkts & multicast & bcast. **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdSetRxFilter, 0x01 + 0x02 + 0x04); - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxEnable, 0); - - - /** - ** set Indication and Interrupt flags , acknowledge any IRQ's - **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdSetInterruptEnable, 0); - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, - cmdSetIndicationEnable, 0x0014); - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, - cmdAcknowledgeInterrupt, 0x661); - - /** Set our exported functions **/ - nic->nic_op = &a3c90x_operations; - return 1; + DBG("Command 0x%04X DID NOT finish in time. cnt = %d.\n", cmd, cnt); } -static struct nic_operations a3c90x_operations = { - .connect = dummy_connect, - .poll = a3c90x_poll, - .transmit = a3c90x_transmit, - .irq = a3c90x_irq, +/** + * a3c90x_internal_SetWindow: selects a register window set. + * + * @v inf_3c90x private NIC data + * @v window window to be selected + */ +static void a3c90x_internal_SetWindow(struct INF_3C90X *inf_3c90x, int window) +{ + DBGP("a3c90x_internal_SetWindow\n"); + /* Window already as set? */ + if (inf_3c90x->CurrentWindow == window) + return; + /* Issue the window command. */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdSelectRegisterWindow, window); + inf_3c90x->CurrentWindow = window; + + return; +} + +static void a3c90x_internal_WaitForEeprom(struct INF_3C90X *inf_3c90x) +{ + int cnt = 0; + + DBGP("a3c90x_internal_WaitForEeprom\n"); + + while (eepromBusy & inw(inf_3c90x->IOAddr + regEepromCommand_0_w)) { + if (cnt == EEPROM_TIMEOUT) { + DBG("Read from eeprom failed: timeout\n"); + return; + } + udelay(1); + cnt++; + } +} + +/** + * a3c90x_internal_ReadEeprom - nvs routine to read eeprom data + * We only support reading one word(2 byte). The nvs subsystem will make sure + * that the routine will never be called with len != 2. + * + * @v nvs nvs data. + * @v address eeprom address to read data from. + * @v data data is put here. + * @v len number of bytes to read. + */ +static int +a3c90x_internal_ReadEeprom(struct nvs_device *nvs, unsigned int address, void *data, size_t len) +{ + unsigned short *dest = (unsigned short *) data; + struct INF_3C90X *inf_3c90x = + container_of(nvs, struct INF_3C90X, nvs); + + DBGP("a3c90x_internal_ReadEeprom\n"); + + /* we support reading 2 bytes only */ + assert(len == 2); + + /* Select correct window */ + a3c90x_internal_SetWindow(inf_3c90x, winEepromBios0); + + /* set eepromRead bits in command sent to NIC */ + address += (inf_3c90x->is3c556 ? eepromRead_556 : eepromRead); + + a3c90x_internal_WaitForEeprom(inf_3c90x); + /* send address to NIC */ + outw(address, inf_3c90x->IOAddr + regEepromCommand_0_w); + a3c90x_internal_WaitForEeprom(inf_3c90x); + + /* read value */ + *dest = inw(inf_3c90x->IOAddr + regEepromData_0_w); + + return 0; +} + +/** + * a3c90x_internal_WriteEeprom - nvs routine to write eeprom data + * currently not implemented + * + * @v nvs nvs data. + * @v address eeprom address to read data from. + * @v data data is put here. + * @v len number of bytes to read. + */ +static int +a3c90x_internal_WriteEeprom(struct nvs_device *nvs __unused, + unsigned int address __unused, + const void *data __unused, size_t len __unused) +{ + return -ENOTSUP; +} + +static void a3c90x_internal_ReadEepromContents(struct INF_3C90X *inf_3c90x) +{ + int eeprom_size = (inf_3c90x->isBrev ? 0x20 : 0x17) * 2; + + DBGP("a3c90x_internal_ReadEepromContents\n"); + + nvs_read(&inf_3c90x->nvs, 0, inf_3c90x->eeprom, eeprom_size); +} + +/** + * a3c90x_reset: exported function that resets the card to its default + * state. This is so the Linux driver can re-set the card up the way + * it wants to. If CFG_3C90X_PRESERVE_XCVR is defined, then the reset will + * not alter the selected transceiver that we used to download the boot + * image. + * + * @v inf_3c90x Private NIC data + */ +static void a3c90x_reset(struct INF_3C90X *inf_3c90x) +{ + DBGP("a3c90x_reset\n"); + /* Send the reset command to the card */ + DBG("3c90x: Issuing RESET\n"); + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdGlobalReset, 0); + + /* global reset command resets station mask, non-B revision cards + * require explicit reset of values + */ + a3c90x_internal_SetWindow(inf_3c90x, winAddressing2); + outw(0, inf_3c90x->IOAddr + regStationMask_2_3w + 0); + outw(0, inf_3c90x->IOAddr + regStationMask_2_3w + 2); + outw(0, inf_3c90x->IOAddr + regStationMask_2_3w + 4); + + /* Issue transmit reset, wait for command completion */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdTxReset, 0); + + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdTxEnable, 0); + + /* + * reset of the receiver on B-revision cards re-negotiates the link + * takes several seconds (a computer eternity) + */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdRxReset, + inf_3c90x->isBrev ? 0x04 : 0x00); + + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdRxEnable, 0); + + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdSetInterruptEnable, 0); + /* enable rxComplete and txComplete */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdSetIndicationEnable, + INT_TXCOMPLETE | INT_UPCOMPLETE); + /* acknowledge any pending status flags */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdAcknowledgeInterrupt, 0x661); + + return; +} + +/** + * a3c90x_setup_tx_ring - Allocates TX ring, initialize tx_desc values + * + * @v p Private NIC data + * + * @ret Returns 0 on success, negative on failure + */ +static int a3c90x_setup_tx_ring(struct INF_3C90X *p) +{ + DBGP("a3c90x_setup_tx_ring\n"); + p->tx_ring = + malloc_dma(TX_RING_SIZE * sizeof(struct TXD), TX_RING_ALIGN); + + if (!p->tx_ring) { + DBG("Could not allocate TX-ring\n"); + return -ENOMEM; + } + + memset(p->tx_ring, 0, TX_RING_SIZE * sizeof(struct TXD)); + p->tx_cur = 0; + p->tx_cnt = 0; + p->tx_tail = 0; + + return 0; +} + +/** + * a3c90x_process_tx_packets - Checks for successfully sent packets, + * reports them to gPXE with netdev_tx_complete(); + * + * @v netdev Network device info + */ +static void a3c90x_process_tx_packets(struct net_device *netdev) +{ + struct INF_3C90X *p = netdev_priv(netdev); + unsigned int downlist_ptr; + + DBGP("a3c90x_process_tx_packets\n"); + + DBG(" tx_cnt: %d\n", p->tx_cnt); + + while (p->tx_tail != p->tx_cur) { + + downlist_ptr = inl(p->IOAddr + regDnListPtr_l); + + DBG(" downlist_ptr: %#08x\n", downlist_ptr); + DBG(" tx_tail: %d tx_cur: %d\n", p->tx_tail, p->tx_cur); + + /* NIC is currently working on this tx desc */ + if(downlist_ptr == virt_to_bus(p->tx_ring + p->tx_tail)) + return; + + netdev_tx_complete(netdev, p->tx_iobuf[p->tx_tail]); + + DBG("transmitted packet\n"); + DBG(" size: %d\n", iob_len(p->tx_iobuf[p->tx_tail])); + + p->tx_tail = (p->tx_tail + 1) % TX_RING_SIZE; + p->tx_cnt--; + } +} + +static void a3c90x_free_tx_ring(struct INF_3C90X *p) +{ + DBGP("a3c90x_free_tx_ring\n"); + + free_dma(p->tx_ring, TX_RING_SIZE * sizeof(struct TXD)); + p->tx_ring = NULL; + /* io_buffers are free()ed by netdev_tx_complete[,_err]() */ +} + +/** + * a3c90x_transmit - Transmits a packet. + * + * @v netdev Network device info + * @v iob io_buffer containing the data to be send + * + * @ret Returns 0 on success, negative on failure + */ +static int a3c90x_transmit(struct net_device *netdev, + struct io_buffer *iob) +{ + struct INF_3C90X *inf_3c90x = netdev_priv(netdev); + struct TXD *tx_cur_desc; + struct TXD *tx_prev_desc; + + unsigned int len; + unsigned int downlist_ptr; + + DBGP("a3c90x_transmit\n"); + + if (inf_3c90x->tx_cnt == TX_RING_SIZE) { + DBG("TX-Ring overflow\n"); + return -ENOBUFS; + } + + inf_3c90x->tx_iobuf[inf_3c90x->tx_cur] = iob; + tx_cur_desc = inf_3c90x->tx_ring + inf_3c90x->tx_cur; + + tx_prev_desc = inf_3c90x->tx_ring + + (((inf_3c90x->tx_cur + TX_RING_SIZE) - 1) % TX_RING_SIZE); + + len = iob_len(iob); + + /* Setup the DPD (download descriptor) */ + tx_cur_desc->DnNextPtr = 0; + + /* FrameStartHeader differs in 90x and >= 90xB + * It contains length in 90x and a round up boundary and packet ID for + * 90xB and 90xC. We can leave this to 0 for 90xB and 90xC. + */ + tx_cur_desc->FrameStartHeader = + fshTxIndicate | (inf_3c90x->isBrev ? 0x00 : len); + + tx_cur_desc->DataAddr = virt_to_bus(iob->data); + tx_cur_desc->DataLength = len | downLastFrag; + + /* We have to stall the download engine, so the NIC won't access the + * tx descriptor while we modify it. There is a way around this + * from revision B and upwards. To stay compatible with older revisions + * we don't use it here. + */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdStallCtl, + dnStall); + + tx_prev_desc->DnNextPtr = virt_to_bus(tx_cur_desc); + + downlist_ptr = inl(inf_3c90x->IOAddr + regDnListPtr_l); + if (downlist_ptr == 0) { + /* currently no DownList, sending a new one */ + outl(virt_to_bus(tx_cur_desc), + inf_3c90x->IOAddr + regDnListPtr_l); + } + + /* End Stall */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdStallCtl, + dnUnStall); + + inf_3c90x->tx_cur = (inf_3c90x->tx_cur + 1) % TX_RING_SIZE; + inf_3c90x->tx_cnt++; + + return 0; +} + +/** + * a3c90x_prepare_rx_desc - fills the rx desc with initial data + * + * @v p NIC private data + * @v index Index for rx_iobuf and rx_ring array + */ + +static void a3c90x_prepare_rx_desc(struct INF_3C90X *p, unsigned int index) +{ + DBGP("a3c90x_prepare_rx_desc\n"); + DBG("Populating rx_desc %d\n", index); + + /* We have to stall the upload engine, so the NIC won't access the + * rx descriptor while we modify it. There is a way around this + * from revision B and upwards. To stay compatible with older revisions + * we don't use it here. + */ + a3c90x_internal_IssueCommand(p->IOAddr, cmdStallCtl, upStall); + + p->rx_ring[index].DataAddr = virt_to_bus(p->rx_iobuf[index]->data); + p->rx_ring[index].DataLength = RX_BUF_SIZE | upLastFrag; + p->rx_ring[index].UpPktStatus = 0; + + /* unstall upload engine */ + a3c90x_internal_IssueCommand(p->IOAddr, cmdStallCtl, upUnStall); +} + +/** + * a3c90x_refill_rx_ring -checks every entry in the rx ring and reallocates + * them as necessary. Then it calls a3c90x_prepare_rx_desc to fill the rx desc + * with initial data. + * + * @v p NIC private data + */ +static void a3c90x_refill_rx_ring(struct INF_3C90X *p) +{ + int i; + unsigned int status; + struct RXD *rx_cur_desc; + + DBGP("a3c90x_refill_rx_ring\n"); + + for (i = 0; i < RX_RING_SIZE; i++) { + rx_cur_desc = p->rx_ring + i; + status = rx_cur_desc->UpPktStatus; + + /* only refill used descriptor */ + if (!(status & upComplete)) + continue; + + /* we still need to process this descriptor */ + if (p->rx_iobuf[i] != NULL) + continue; + + p->rx_iobuf[i] = alloc_iob(RX_BUF_SIZE); + if (p->rx_iobuf[i] == NULL) { + DBG("alloc_iob() failed\n"); + break; + } + + a3c90x_prepare_rx_desc(p, i); + } +} + +/** + * a3c90x_setup_rx_ring - Allocates RX ring, initialize rx_desc values + * + * @v p Private NIC data + * + * @ret Returns 0 on success, negative on failure + */ +static int a3c90x_setup_rx_ring(struct INF_3C90X *p) +{ + int i; + + DBGP("a3c90x_setup_rx_ring\n"); + + p->rx_ring = + malloc_dma(RX_RING_SIZE * sizeof(struct RXD), RX_RING_ALIGN); + + if (!p->rx_ring) { + DBG("Could not allocate RX-ring\n"); + return -ENOMEM; + } + + p->rx_cur = 0; + + for (i = 0; i < RX_RING_SIZE; i++) { + p->rx_ring[i].UpNextPtr = + virt_to_bus(p->rx_ring + (i + 1)); + + /* these are needed so refill_rx_ring initializes the ring */ + p->rx_ring[i].UpPktStatus = upComplete; + p->rx_iobuf[i] = NULL; + } + + /* Loop the ring */ + p->rx_ring[i - 1].UpNextPtr = virt_to_bus(p->rx_ring); + + a3c90x_refill_rx_ring(p); + + return 0; +} + +static void a3c90x_free_rx_ring(struct INF_3C90X *p) +{ + DBGP("a3c90x_free_rx_ring\n"); + + free_dma(p->rx_ring, RX_RING_SIZE * sizeof(struct RXD)); + p->rx_ring = NULL; +} + +static void a3c90x_free_rx_iobuf(struct INF_3C90X *p) +{ + int i; + + DBGP("a3c90x_free_rx_iobuf\n"); + + for (i = 0; i < RX_RING_SIZE; i++) { + free_iob(p->rx_iobuf[i]); + p->rx_iobuf[i] = NULL; + } +} + +/** + * a3c90x_process_rx_packets - Checks for received packets, + * reports them to gPXE with netdev_rx() or netdev_rx_err() if there was an + * error while receiving the packet + * + * @v netdev Network device info + */ +static void a3c90x_process_rx_packets(struct net_device *netdev) +{ + int i; + unsigned int rx_status; + struct INF_3C90X *p = netdev_priv(netdev); + struct RXD *rx_cur_desc; + + DBGP("a3c90x_process_rx_packets\n"); + + for (i = 0; i < RX_RING_SIZE; i++) { + rx_cur_desc = p->rx_ring + p->rx_cur; + rx_status = rx_cur_desc->UpPktStatus; + + if (!(rx_status & upComplete) && !(rx_status & upError)) + break; + + if (p->rx_iobuf[p->rx_cur] == NULL) + break; + + if (rx_status & upError) { + DBG("Corrupted packet received\n"); + netdev_rx_err(netdev, p->rx_iobuf[p->rx_cur], + -EINVAL); + } else { + /* if we're here, we've got good packet */ + int packet_len; + + packet_len = rx_status & 0x1FFF; + iob_put(p->rx_iobuf[p->rx_cur], packet_len); + + DBG("received packet\n"); + DBG(" size: %d\n", packet_len); + + netdev_rx(netdev, p->rx_iobuf[p->rx_cur]); + } + + p->rx_iobuf[p->rx_cur] = NULL; /* invalidate rx desc */ + p->rx_cur = (p->rx_cur + 1) % RX_RING_SIZE; + } + a3c90x_refill_rx_ring(p); + +} + +/** + * a3c90x_poll - Routine that gets called periodically. + * Here we hanle transmitted and received packets. + * We could also check the link status from time to time, which we + * currently don't do. + * + * @v netdev Network device info + */ +static void a3c90x_poll(struct net_device *netdev) +{ + struct INF_3C90X *p = netdev_priv(netdev); + uint16_t raw_status, int_status; + + DBGP("a3c90x_poll\n"); + + raw_status = inw(p->IOAddr + regCommandIntStatus_w); + int_status = (raw_status & 0x0FFF); + + if ( int_status == 0 ) + return; + + a3c90x_internal_IssueCommand(p->IOAddr, cmdAcknowledgeInterrupt, + int_status); + + if (int_status & INT_TXCOMPLETE) + outb(0x00, p->IOAddr + regTxStatus_b); + + DBG("poll: status = %#04x\n", raw_status); + + a3c90x_process_tx_packets(netdev); + + a3c90x_process_rx_packets(netdev); +} + + + +static void a3c90x_free_resources(struct INF_3C90X *p) +{ + DBGP("a3c90x_free_resources\n"); + + a3c90x_free_tx_ring(p); + a3c90x_free_rx_ring(p); + a3c90x_free_rx_iobuf(p); +} + +/** + * a3c90x_remove - Routine to remove the card. Unregisters + * the NIC from gPXE, disables RX/TX and resets the card. + * + * @v pci PCI device info + */ +static void a3c90x_remove(struct pci_device *pci) +{ + struct net_device *netdev = pci_get_drvdata(pci); + struct INF_3C90X *inf_3c90x = netdev_priv(netdev); + + DBGP("a3c90x_remove\n"); + + unregister_netdev(netdev); + + /* Disable the receiver and transmitter. */ + outw(cmdRxDisable, inf_3c90x->IOAddr + regCommandIntStatus_w); + outw(cmdTxDisable, inf_3c90x->IOAddr + regCommandIntStatus_w); + + a3c90x_reset(inf_3c90x); + netdev_nullify(netdev); + netdev_put(netdev); +} + +static void a3c90x_irq(struct net_device *netdev, int enable) +{ + struct INF_3C90X *p = netdev_priv(netdev); + + DBGP("a3c90x_irq\n"); + + if (enable == 0) { + /* disable interrupts */ + a3c90x_internal_IssueCommand(p->IOAddr, + cmdSetInterruptEnable, 0); + } else { + a3c90x_internal_IssueCommand(p->IOAddr, + cmdSetInterruptEnable, + INT_TXCOMPLETE | + INT_UPCOMPLETE); + a3c90x_internal_IssueCommand(p->IOAddr, + cmdAcknowledgeInterrupt, + 0x661); + } +} + +/** + * a3c90x_hw_start - Initialize hardware, copy MAC address + * to NIC registers, set default receiver + */ +static void a3c90x_hw_start(struct net_device *netdev) +{ + int i, c; + unsigned int cfg; + unsigned int mopt; + unsigned short linktype; + struct INF_3C90X *inf_3c90x = netdev_priv(netdev); + + DBGP("a3c90x_hw_start\n"); + + /* 3C556: Invert MII power */ + if (inf_3c90x->is3c556) { + unsigned int tmp; + a3c90x_internal_SetWindow(inf_3c90x, winAddressing2); + tmp = inw(inf_3c90x->IOAddr + regResetOptions_2_w); + tmp |= 0x4000; + outw(tmp, inf_3c90x->IOAddr + regResetOptions_2_w); + } + + /* Copy MAC address into the NIC registers */ + a3c90x_internal_SetWindow(inf_3c90x, winAddressing2); + for (i = 0; i < ETH_ALEN; i++) + outb(netdev->ll_addr[i], + inf_3c90x->IOAddr + regStationAddress_2_3w + i); + for (i = 0; i < ETH_ALEN; i++) + outb(0, inf_3c90x->IOAddr + regStationMask_2_3w + i); + + /* Read the media options register, print a message and set default + * xcvr. + * + * Uses Media Option command on B revision, Reset Option on non-B + * revision cards -- same register address + */ + a3c90x_internal_SetWindow(inf_3c90x, winTxRxOptions3); + mopt = inw(inf_3c90x->IOAddr + regResetMediaOptions_3_w); + + /* mask out VCO bit that is defined as 10baseFL bit on B-rev cards */ + if (!inf_3c90x->isBrev) { + mopt &= 0x7F; + } + + DBG("Connectors present: "); + c = 0; + linktype = 0x0008; + if (mopt & 0x01) { + DBG("%s100Base-T4", (c++) ? ", " : ""); + linktype = linkMII; + } + if (mopt & 0x04) { + DBG("%s100Base-FX", (c++) ? ", " : ""); + linktype = link100BaseFX; + } + if (mopt & 0x10) { + DBG("%s10Base-2", (c++) ? ", " : ""); + linktype = link10Base2; + } + if (mopt & 0x20) { + DBG("%sAUI", (c++) ? ", " : ""); + linktype = linkAUI; + } + if (mopt & 0x40) { + DBG("%sMII", (c++) ? ", " : ""); + linktype = linkMII; + } + if ((mopt & 0xA) == 0xA) { + DBG("%s10Base-T / 100Base-TX", (c++) ? ", " : ""); + linktype = linkAutoneg; + } else if ((mopt & 0xA) == 0x2) { + DBG("%s100Base-TX", (c++) ? ", " : ""); + linktype = linkAutoneg; + } else if ((mopt & 0xA) == 0x8) { + DBG("%s10Base-T", (c++) ? ", " : ""); + linktype = linkAutoneg; + } + DBG(".\n"); + + /* Determine transceiver type to use, depending on value stored in + * eeprom 0x16 + */ + if (inf_3c90x->isBrev) { + if ((inf_3c90x->eeprom[0x16] & 0xFF00) == XCVR_MAGIC) { + /* User-defined */ + linktype = inf_3c90x->eeprom[0x16] & 0x000F; + } + } else { + /* I don't know what MII MAC only mode is!!! */ + if (linktype == linkExternalMII) { + if (inf_3c90x->isBrev) + DBG("WARNING: MII External MAC Mode only supported on B-revision " "cards!!!!\nFalling Back to MII Mode\n"); + linktype = linkMII; + } + } + + /* enable DC converter for 10-Base-T */ + if (linktype == link10Base2) { + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdEnableDcConverter, 0); + } + + /* Set the link to the type we just determined. */ + a3c90x_internal_SetWindow(inf_3c90x, winTxRxOptions3); + cfg = inl(inf_3c90x->IOAddr + regInternalConfig_3_l); + cfg &= ~(0xF << 20); + cfg |= (linktype << 20); + + DBG("Setting internal cfg register: 0x%08X (linktype: 0x%02X)\n", + cfg, linktype); + + outl(cfg, inf_3c90x->IOAddr + regInternalConfig_3_l); + + /* Now that we set the xcvr type, reset the Tx and Rx */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdTxReset, 0x00); + + if (!inf_3c90x->isBrev) + outb(0x01, inf_3c90x->IOAddr + regTxFreeThresh_b); + + /* Set the RX filter = receive only individual pkts & multicast & bcast. */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdSetRxFilter, + 0x01 + 0x02 + 0x04); + + + /* + * set Indication and Interrupt flags , acknowledge any IRQ's + */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdSetInterruptEnable, + INT_TXCOMPLETE | INT_UPCOMPLETE); + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdSetIndicationEnable, + INT_TXCOMPLETE | INT_UPCOMPLETE); + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdAcknowledgeInterrupt, 0x661); +} + +/** + * a3c90x_open - Routine to initialize the card. Initialize hardware, + * allocate TX and RX ring, send RX ring address to the NIC. + * + * @v netdev Network device info + * + * @ret Returns 0 on success, negative on failure + */ +static int a3c90x_open(struct net_device *netdev) +{ + int rc; + struct INF_3C90X *inf_3c90x = netdev_priv(netdev); + + DBGP("a3c90x_open\n"); + + a3c90x_hw_start(netdev); + + rc = a3c90x_setup_tx_ring(inf_3c90x); + if (rc != 0) { + DBG("Error setting up TX Ring\n"); + goto error; + } + + rc = a3c90x_setup_rx_ring(inf_3c90x); + if (rc != 0) { + DBG("Error setting up RX Ring\n"); + goto error; + } + + /* send rx_ring address to NIC */ + outl(virt_to_bus(inf_3c90x->rx_ring), + inf_3c90x->IOAddr + regUpListPtr_l); + + /* enable packet transmission and reception */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdTxEnable, 0); + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdRxEnable, 0); + + return 0; + + error: + a3c90x_free_resources(inf_3c90x); + a3c90x_reset(inf_3c90x); + return rc; +} + +/** + * a3c90x_close - free()s TX and RX ring, disablex RX/TX, resets NIC + * + * @v netdev Network device info + */ +static void a3c90x_close(struct net_device *netdev) +{ + struct INF_3C90X *inf_3c90x = netdev_priv(netdev); + + DBGP("a3c90x_close\n"); + + outw(cmdRxDisable, inf_3c90x->IOAddr + regCommandIntStatus_w); + outw(cmdTxDisable, inf_3c90x->IOAddr + regCommandIntStatus_w); + a3c90x_reset(inf_3c90x); + a3c90x_free_resources(inf_3c90x); +} + +static struct net_device_operations a3c90x_operations = { + .open = a3c90x_open, + .close = a3c90x_close, + .poll = a3c90x_poll, + .transmit = a3c90x_transmit, + .irq = a3c90x_irq, }; +/** + * a3c90x_probe: exported routine to probe for the 3c905 card. + * If this routine is called, the pci functions did find the + * card. We read the eeprom here and get the MAC address. + * Initialization is done in a3c90x_open(). + * + * @v pci PCI device info + * @ pci_id PCI device IDs + * + * @ret rc Returns 0 on success, negative on failure + */ +static int a3c90x_probe(struct pci_device *pci, + const struct pci_device_id *pci_id __unused) +{ + + struct net_device *netdev; + struct INF_3C90X *inf_3c90x; + unsigned char *HWAddr; + int rc; + + DBGP("a3c90x_probe\n"); + + if (pci->ioaddr == 0) + return -EINVAL; + + netdev = alloc_etherdev(sizeof(*inf_3c90x)); + if (!netdev) + return -ENOMEM; + + netdev_init(netdev, &a3c90x_operations); + pci_set_drvdata(pci, netdev); + netdev->dev = &pci->dev; + + inf_3c90x = netdev_priv(netdev); + memset(inf_3c90x, 0, sizeof(*inf_3c90x)); + + adjust_pci_device(pci); + + inf_3c90x->is3c556 = (pci->device == 0x6055); + inf_3c90x->IOAddr = pci->ioaddr; + inf_3c90x->CurrentWindow = winNone; + + inf_3c90x->isBrev = 1; + switch (pci->device) { + case 0x9000: /* 10 Base TPO */ + case 0x9001: /* 10/100 T4 */ + case 0x9050: /* 10/100 TPO */ + case 0x9051: /* 10 Base Combo */ + inf_3c90x->isBrev = 0; + break; + } + + DBG("[3c90x]: found NIC(0x%04X, 0x%04X), isBrev=%d, is3c556=%d\n", + pci->vendor, pci->device, inf_3c90x->isBrev, + inf_3c90x->is3c556); + + /* initialize nvs device */ + inf_3c90x->nvs.word_len_log2 = 1; /* word */ + inf_3c90x->nvs.size = (inf_3c90x->isBrev ? 0x20 : 0x17); + inf_3c90x->nvs.block_size = 1; + inf_3c90x->nvs.read = a3c90x_internal_ReadEeprom; + inf_3c90x->nvs.write = a3c90x_internal_WriteEeprom; + + /* reset NIC before accessing any data from it */ + a3c90x_reset(inf_3c90x); + + /* load eeprom contents to inf_3c90x->eeprom */ + a3c90x_internal_ReadEepromContents(inf_3c90x); + + HWAddr = netdev->ll_addr; + + /* Retrieve the Hardware address */ + HWAddr[0] = inf_3c90x->eeprom[eepromHwAddrOffset + 0] >> 8; + HWAddr[1] = inf_3c90x->eeprom[eepromHwAddrOffset + 0] & 0xFF; + HWAddr[2] = inf_3c90x->eeprom[eepromHwAddrOffset + 1] >> 8; + HWAddr[3] = inf_3c90x->eeprom[eepromHwAddrOffset + 1] & 0xFF; + HWAddr[4] = inf_3c90x->eeprom[eepromHwAddrOffset + 2] >> 8; + HWAddr[5] = inf_3c90x->eeprom[eepromHwAddrOffset + 2] & 0xFF; + + /* we don't handle linkstates yet, so we're always up */ + netdev_link_up(netdev); + + if ((rc = register_netdev(netdev)) != 0) { + DBG("3c90x: register_netdev() failed\n"); + netdev_put(netdev); + return rc; + } + + return 0; +} + static struct pci_device_id a3c90x_nics[] = { /* Original 90x revisions: */ -PCI_ROM(0x10b7, 0x6055, "3c556", "3C556"), /* Huricane */ -PCI_ROM(0x10b7, 0x9000, "3c905-tpo", "3Com900-TPO"), /* 10 Base TPO */ -PCI_ROM(0x10b7, 0x9001, "3c905-t4", "3Com900-Combo"), /* 10/100 T4 */ -PCI_ROM(0x10b7, 0x9050, "3c905-tpo100", "3Com905-TX"), /* 100 Base TX / 10/100 TPO */ -PCI_ROM(0x10b7, 0x9051, "3c905-combo", "3Com905-T4"), /* 100 Base T4 / 10 Base Combo */ + PCI_ROM(0x10b7, 0x6055, "3c556", "3C556"), /* Huricane */ + PCI_ROM(0x10b7, 0x9000, "3c905-tpo", "3Com900-TPO"), /* 10 Base TPO */ + PCI_ROM(0x10b7, 0x9001, "3c905-t4", "3Com900-Combo"), /* 10/100 T4 */ + PCI_ROM(0x10b7, 0x9050, "3c905-tpo100", "3Com905-TX"), /* 100 Base TX / 10/100 TPO */ + PCI_ROM(0x10b7, 0x9051, "3c905-combo", "3Com905-T4"), /* 100 Base T4 / 10 Base Combo */ /* Newer 90xB revisions: */ -PCI_ROM(0x10b7, 0x9004, "3c905b-tpo", "3Com900B-TPO"), /* 10 Base TPO */ -PCI_ROM(0x10b7, 0x9005, "3c905b-combo", "3Com900B-Combo"), /* 10 Base Combo */ -PCI_ROM(0x10b7, 0x9006, "3c905b-tpb2", "3Com900B-2/T"), /* 10 Base TP and Base2 */ -PCI_ROM(0x10b7, 0x900a, "3c905b-fl", "3Com900B-FL"), /* 10 Base FL */ -PCI_ROM(0x10b7, 0x9055, "3c905b-tpo100", "3Com905B-TX"), /* 10/100 TPO */ -PCI_ROM(0x10b7, 0x9056, "3c905b-t4", "3Com905B-T4"), /* 10/100 T4 */ -PCI_ROM(0x10b7, 0x9058, "3c905b-9058", "3Com905B-9058"), /* Cyclone 10/100/BNC */ -PCI_ROM(0x10b7, 0x905a, "3c905b-fx", "3Com905B-FL"), /* 100 Base FX / 10 Base FX */ + PCI_ROM(0x10b7, 0x9004, "3c905b-tpo", "3Com900B-TPO"), /* 10 Base TPO */ + PCI_ROM(0x10b7, 0x9005, "3c905b-combo", "3Com900B-Combo"), /* 10 Base Combo */ + PCI_ROM(0x10b7, 0x9006, "3c905b-tpb2", "3Com900B-2/T"), /* 10 Base TP and Base2 */ + PCI_ROM(0x10b7, 0x900a, "3c905b-fl", "3Com900B-FL"), /* 10 Base FL */ + PCI_ROM(0x10b7, 0x9055, "3c905b-tpo100", "3Com905B-TX"), /* 10/100 TPO */ + PCI_ROM(0x10b7, 0x9056, "3c905b-t4", "3Com905B-T4"), /* 10/100 T4 */ + PCI_ROM(0x10b7, 0x9058, "3c905b-9058", "3Com905B-9058"), /* Cyclone 10/100/BNC */ + PCI_ROM(0x10b7, 0x905a, "3c905b-fx", "3Com905B-FL"), /* 100 Base FX / 10 Base FX */ /* Newer 90xC revision: */ -PCI_ROM(0x10b7, 0x9200, "3c905c-tpo", "3Com905C-TXM"), /* 10/100 TPO (3C905C-TXM) */ -PCI_ROM(0x10b7, 0x9202, "3c920b-emb-ati", "3c920B-EMB-WNM (ATI Radeon 9100 IGP)"), /* 3c920B-EMB-WNM (ATI Radeon 9100 IGP) */ -PCI_ROM(0x10b7, 0x9210, "3c920b-emb-wnm","3Com20B-EMB WNM"), -PCI_ROM(0x10b7, 0x9800, "3c980", "3Com980-Cyclone"), /* Cyclone */ -PCI_ROM(0x10b7, 0x9805, "3c9805", "3Com9805"), /* Dual Port Server Cyclone */ -PCI_ROM(0x10b7, 0x7646, "3csoho100-tx", "3CSOHO100-TX"), /* Hurricane */ -PCI_ROM(0x10b7, 0x4500, "3c450", "3Com450 HomePNA Tornado"), -PCI_ROM(0x10b7, 0x1201, "3c982a", "3Com982A"), -PCI_ROM(0x10b7, 0x1202, "3c982b", "3Com982B"), + PCI_ROM(0x10b7, 0x9200, "3c905c-tpo", "3Com905C-TXM"), /* 10/100 TPO (3C905C-TXM) */ + PCI_ROM(0x10b7, 0x9202, "3c920b-emb-ati", "3c920B-EMB-WNM (ATI Radeon 9100 IGP)"), /* 3c920B-EMB-WNM (ATI Radeon 9100 IGP) */ + PCI_ROM(0x10b7, 0x9210, "3c920b-emb-wnm", "3Com20B-EMB WNM"), + PCI_ROM(0x10b7, 0x9800, "3c980", "3Com980-Cyclone"), /* Cyclone */ + PCI_ROM(0x10b7, 0x9805, "3c9805", "3Com9805"), /* Dual Port Server Cyclone */ + PCI_ROM(0x10b7, 0x7646, "3csoho100-tx", "3CSOHO100-TX"), /* Hurricane */ + PCI_ROM(0x10b7, 0x4500, "3c450", "3Com450 HomePNA Tornado"), + PCI_ROM(0x10b7, 0x1201, "3c982a", "3Com982A"), + PCI_ROM(0x10b7, 0x1202, "3c982b", "3Com982B"), }; -PCI_DRIVER ( a3c90x_driver, a3c90x_nics, PCI_NO_CLASS ); - -DRIVER ( "3C90X", nic_driver, pci_driver, a3c90x_driver, - a3c90x_probe, a3c90x_disable ); +struct pci_driver a3c90x_driver __pci_driver = { + .ids = a3c90x_nics, + .id_count = (sizeof(a3c90x_nics) / sizeof(a3c90x_nics[0])), + .probe = a3c90x_probe, + .remove = a3c90x_remove, +}; /* * Local variables: diff --git a/src/drivers/net/3c90x.h b/src/drivers/net/3c90x.h new file mode 100644 index 00000000..c62ac1a6 --- /dev/null +++ b/src/drivers/net/3c90x.h @@ -0,0 +1,300 @@ +/* + * 3c90x.c -- This file implements the 3c90x driver for etherboot. Written + * by Greg Beeley, Greg.Beeley@LightSys.org. Modified by Steve Smith, + * Steve.Smith@Juno.Com. Alignment bug fix Neil Newell (nn@icenoir.net). + * + * Port from etherboot to gPXE API, implementation of tx/rx ring support + * by Thomas Miletich, thomas.miletich@gmail.com + * Thanks to Marty Connor and Stefan Hajnoczi for their help and feedback. + * + * This program Copyright (C) 1999 LightSys Technology Services, Inc. + * Portions Copyright (C) 1999 Steve Smith + * + * This program may be re-distributed in source or binary form, modified, + * sold, or copied for any purpose, provided that the above copyright message + * and this text are included with all source copies or derivative works, and + * provided that the above copyright message and this text are included in the + * documentation of any binary-only distributions. This program is distributed + * WITHOUT ANY WARRANTY, without even the warranty of FITNESS FOR A PARTICULAR + * PURPOSE or MERCHANTABILITY. Please read the associated documentation + * "3c90x.txt" before compiling and using this driver. + * + * -------- + * + * Program written with the assistance of the 3com documentation for + * the 3c905B-TX card, as well as with some assistance from the 3c59x + * driver Donald Becker wrote for the Linux kernel, and with some assistance + * from the remainder of the Etherboot distribution. + * + * REVISION HISTORY: + * + * v0.10 1-26-1998 GRB Initial implementation. + * v0.90 1-27-1998 GRB System works. + * v1.00pre1 2-11-1998 GRB Got prom boot issue fixed. + * v2.0 9-24-1999 SCS Modified for 3c905 (from 3c905b code) + * Re-wrote poll and transmit for + * better error recovery and heavy + * network traffic operation + * v2.01 5-26-2003 NN Fixed driver alignment issue which + * caused system lockups if driver structures + * not 8-byte aligned. + * v2.02 11-28-2007 GSt Got polling working again by replacing + * "for(i=0;i<40000;i++);" with "mdelay(1);" + * + * + * indent options: indent -kr -i8 3c90x.c + */ + +#ifndef __3C90X_H_ +#define __3C90X_H_ + +static struct net_device_operations a3c90x_operations; + +#define XCVR_MAGIC (0x5A00) + +/* Register definitions for the 3c905 */ +enum Registers { + regPowerMgmtCtrl_w = 0x7c, /* 905B Revision Only */ + regUpMaxBurst_w = 0x7a, /* 905B Revision Only */ + regDnMaxBurst_w = 0x78, /* 905B Revision Only */ + regDebugControl_w = 0x74, /* 905B Revision Only */ + regDebugData_l = 0x70, /* 905B Revision Only */ + regRealTimeCnt_l = 0x40, /* Universal */ + regUpBurstThresh_b = 0x3e, /* 905B Revision Only */ + regUpPoll_b = 0x3d, /* 905B Revision Only */ + regUpPriorityThresh_b = 0x3c, /* 905B Revision Only */ + regUpListPtr_l = 0x38, /* Universal */ + regCountdown_w = 0x36, /* Universal */ + regFreeTimer_w = 0x34, /* Universal */ + regUpPktStatus_l = 0x30, /* Universal with Exception, pg 130 */ + regTxFreeThresh_b = 0x2f, /* 90X Revision Only */ + regDnPoll_b = 0x2d, /* 905B Revision Only */ + regDnPriorityThresh_b = 0x2c, /* 905B Revision Only */ + regDnBurstThresh_b = 0x2a, /* 905B Revision Only */ + regDnListPtr_l = 0x24, /* Universal with Exception, pg 107 */ + regDmaCtrl_l = 0x20, /* Universal with Exception, pg 106 */ + /* */ + regIntStatusAuto_w = 0x1e, /* 905B Revision Only */ + regTxStatus_b = 0x1b, /* Universal with Exception, pg 113 */ + regTimer_b = 0x1a, /* Universal */ + regTxPktId_b = 0x18, /* 905B Revision Only */ + regCommandIntStatus_w = 0x0e, /* Universal (Command Variations) */ +}; + +/* following are windowed registers */ +enum Registers7 { + regPowerMgmtEvent_7_w = 0x0c, /* 905B Revision Only */ + regVlanEtherType_7_w = 0x04, /* 905B Revision Only */ + regVlanMask_7_w = 0x00, /* 905B Revision Only */ +}; + +enum Registers6 { + regBytesXmittedOk_6_w = 0x0c, /* Universal */ + regBytesRcvdOk_6_w = 0x0a, /* Universal */ + regUpperFramesOk_6_b = 0x09, /* Universal */ + regFramesDeferred_6_b = 0x08, /* Universal */ + regFramesRecdOk_6_b = 0x07, /* Universal with Exceptions, pg 142 */ + regFramesXmittedOk_6_b = 0x06, /* Universal */ + regRxOverruns_6_b = 0x05, /* Universal */ + regLateCollisions_6_b = 0x04, /* Universal */ + regSingleCollisions_6_b = 0x03, /* Universal */ + regMultipleCollisions_6_b = 0x02, /* Universal */ + regSqeErrors_6_b = 0x01, /* Universal */ + regCarrierLost_6_b = 0x00, /* Universal */ +}; + +enum Registers5 { + regIndicationEnable_5_w = 0x0c, /* Universal */ + regInterruptEnable_5_w = 0x0a, /* Universal */ + regTxReclaimThresh_5_b = 0x09, /* 905B Revision Only */ + regRxFilter_5_b = 0x08, /* Universal */ + regRxEarlyThresh_5_w = 0x06, /* Universal */ + regTxStartThresh_5_w = 0x00, /* Universal */ +}; + +enum Registers4 { + regUpperBytesOk_4_b = 0x0d, /* Universal */ + regBadSSD_4_b = 0x0c, /* Universal */ + regMediaStatus_4_w = 0x0a, /* Universal with Exceptions, pg 201 */ + regPhysicalMgmt_4_w = 0x08, /* Universal */ + regNetworkDiagnostic_4_w = 0x06, /* Universal with Exceptions, pg 203 */ + regFifoDiagnostic_4_w = 0x04, /* Universal with Exceptions, pg 196 */ + regVcoDiagnostic_4_w = 0x02, /* Undocumented? */ +}; + +enum Registers3 { + regTxFree_3_w = 0x0c, /* Universal */ + regRxFree_3_w = 0x0a, /* Universal with Exceptions, pg 125 */ + regResetMediaOptions_3_w = 0x08, /* Media Options on B Revision, */ + /* Reset Options on Non-B Revision */ + regMacControl_3_w = 0x06, /* Universal with Exceptions, pg 199 */ + regMaxPktSize_3_w = 0x04, /* 905B Revision Only */ + regInternalConfig_3_l = 0x00, /* Universal, different bit */ + /* definitions, pg 59 */ +}; + +enum Registers2 { + regResetOptions_2_w = 0x0c, /* 905B Revision Only */ + regStationMask_2_3w = 0x06, /* Universal with Exceptions, pg 127 */ + regStationAddress_2_3w = 0x00, /* Universal with Exceptions, pg 127 */ +}; + +enum Registers1 { + regRxStatus_1_w = 0x0a, /* 90X Revision Only, Pg 126 */ +}; + +enum Registers0 { + regEepromData_0_w = 0x0c, /* Universal */ + regEepromCommand_0_w = 0x0a, /* Universal */ + regBiosRomData_0_b = 0x08, /* 905B Revision Only */ + regBiosRomAddr_0_l = 0x04, /* 905B Revision Only */ +}; + + +/* The names for the eight register windows */ +enum Windows { + winNone = 0xff, + winPowerVlan7 = 0x07, + winStatistics6 = 0x06, + winTxRxControl5 = 0x05, + winDiagnostics4 = 0x04, + winTxRxOptions3 = 0x03, + winAddressing2 = 0x02, + winUnused1 = 0x01, + winEepromBios0 = 0x00, +}; + + +/* Command definitions for the 3c90X */ +enum Commands { + cmdGlobalReset = 0x00, /* Universal with Exceptions, pg 151 */ + cmdSelectRegisterWindow = 0x01, /* Universal */ + cmdEnableDcConverter = 0x02, /* */ + cmdRxDisable = 0x03, /* */ + cmdRxEnable = 0x04, /* Universal */ + cmdRxReset = 0x05, /* Universal */ + cmdStallCtl = 0x06, /* Universal */ + cmdTxEnable = 0x09, /* Universal */ + cmdTxDisable = 0x0A, /* */ + cmdTxReset = 0x0B, /* Universal */ + cmdRequestInterrupt = 0x0C, /* */ + cmdAcknowledgeInterrupt = 0x0D, /* Universal */ + cmdSetInterruptEnable = 0x0E, /* Universal */ + cmdSetIndicationEnable = 0x0F, /* Universal */ + cmdSetRxFilter = 0x10, /* Universal */ + cmdSetRxEarlyThresh = 0x11, /* */ + cmdSetTxStartThresh = 0x13, /* */ + cmdStatisticsEnable = 0x15, /* */ + cmdStatisticsDisable = 0x16, /* */ + cmdDisableDcConverter = 0x17, /* */ + cmdSetTxReclaimThresh = 0x18, /* */ + cmdSetHashFilterBit = 0x19, /* */ +}; + +enum FrameStartHeader { + fshTxIndicate = 0x8000, + fshDnComplete = 0x10000, +}; + +enum UpDownDesc { + upLastFrag = (1 << 31), + downLastFrag = (1 << 31), +}; + +enum UpPktStatus { + upComplete = (1 << 15), + upError = (1 << 14), +}; + +enum Stalls { + upStall = 0x00, + upUnStall = 0x01, + + dnStall = 0x02, + dnUnStall = 0x03, +}; + +enum Resources { + resRxRing = 0x00, + resTxRing = 0x02, + resRxIOBuf = 0x04 +}; + +enum eeprom { + eepromBusy = (1 << 15), + eepromRead = ((0x02) << 6), + eepromRead_556 = 0x230, + eepromHwAddrOffset = 0x0a, +}; + +/* Bit 4 is only used in revison B and upwards */ +enum linktype { + link10BaseT = 0x00, + linkAUI = 0x01, + link10Base2 = 0x03, + link100BaseFX = 0x05, + linkMII = 0x06, + linkAutoneg = 0x08, + linkExternalMII = 0x09, +}; + +/* Values for int status register bitmask */ +#define INT_INTERRUPTLATCH (1<<0) +#define INT_HOSTERROR (1<<1) +#define INT_TXCOMPLETE (1<<2) +#define INT_RXCOMPLETE (1<<4) +#define INT_RXEARLY (1<<5) +#define INT_INTREQUESTED (1<<6) +#define INT_UPDATESTATS (1<<7) +#define INT_LINKEVENT (1<<8) +#define INT_DNCOMPLETE (1<<9) +#define INT_UPCOMPLETE (1<<10) +#define INT_CMDINPROGRESS (1<<12) +#define INT_WINDOWNUMBER (7<<13) + +/* Buffer sizes */ +#define TX_RING_SIZE 8 +#define RX_RING_SIZE 8 +#define TX_RING_ALIGN 16 +#define RX_RING_ALIGN 16 +#define RX_BUF_SIZE 1536 + +/* Timeouts for eeprom and command completion */ +/* Timeout 1 second, to be save */ +#define EEPROM_TIMEOUT 1 * 1000 * 1000 + +/* TX descriptor */ +struct TXD { + volatile unsigned int DnNextPtr; + volatile unsigned int FrameStartHeader; + volatile unsigned int DataAddr; + volatile unsigned int DataLength; +} __attribute__ ((aligned(8))); /* 64-bit aligned for bus mastering */ + +/* RX descriptor */ +struct RXD { + volatile unsigned int UpNextPtr; + volatile unsigned int UpPktStatus; + volatile unsigned int DataAddr; + volatile unsigned int DataLength; +} __attribute__ ((aligned(8))); /* 64-bit aligned for bus mastering */ + +/* Private NIC dats */ +struct INF_3C90X { + unsigned int is3c556; + unsigned char isBrev; + unsigned char CurrentWindow; + unsigned int IOAddr; + unsigned short eeprom[0x21]; + unsigned int tx_cur; /* current entry in tx_ring */ + unsigned int tx_cnt; /* current number of used tx descriptors */ + unsigned int tx_tail; /* entry of last finished packet */ + unsigned int rx_cur; + struct TXD *tx_ring; + struct RXD *rx_ring; + struct io_buffer *tx_iobuf[TX_RING_SIZE]; + struct io_buffer *rx_iobuf[RX_RING_SIZE]; + struct nvs_device nvs; +}; + +#endif