/* ** File: 3c509.c Jun. 01, 2000 ** ** Author: Giovanni Falzoni ** ** This file contains specific implementation of the ethernet ** device driver for 3Com Etherlink III (3c509) boards. ** NOTE: The board has to be setup to disable PnP and to assign ** I/O base and IRQ. The driver is for ISA bus only ** ** $Id: 3c509.c,v 1.3 2005/08/05 19:08:43 beng Exp $ */ #include "drivers.h" #include #include #include #include #include "dp.h" #if (ENABLE_3C509 == 1) #include "3c509.h" static const char *const IfNamesMsg[] = { "10BaseT", "AUI", "unknown", "BNC", }; /* ** Name: void el3_update_stats(dpeth_t *dep) ** Function: Reads statistic counters from board ** and updates local counters. */ static void el3_update_stats(dpeth_t * dep) { /* Disables statistics while reading and switches to the correct window */ outw_el3(dep, REG_CmdStatus, CMD_StatsDisable); SetWindow(WNO_Statistics); /* Reads everything, adding values to the local counters */ dep->de_stat.ets_sendErr += inb_el3(dep, REG_TxCarrierLost); /* Reg. 00 */ dep->de_stat.ets_sendErr += inb_el3(dep, REG_TxNoCD); /* Reg. 01 */ dep->de_stat.ets_collision += inb_el3(dep, REG_TxMultColl); /* Reg. 02 */ dep->de_stat.ets_collision += inb_el3(dep, REG_TxSingleColl); /* Reg. 03 */ dep->de_stat.ets_collision += inb_el3(dep, REG_TxLate); /* Reg. 04 */ dep->de_stat.ets_recvErr += inb_el3(dep, REG_RxDiscarded); /* Reg. 05 */ dep->de_stat.ets_packetT += inb_el3(dep, REG_TxFrames); /* Reg. 06 */ dep->de_stat.ets_packetR += inb_el3(dep, REG_RxFrames); /* Reg. 07 */ dep->de_stat.ets_transDef += inb_el3(dep, REG_TxDefer); /* Reg. 08 */ dep->bytes_Rx += (unsigned) inw_el3(dep, REG_RxBytes); /* Reg. 10 */ dep->bytes_Tx += (unsigned) inw_el3(dep, REG_TxBytes); /* Reg. 12 */ /* Goes back to operating window and enables statistics */ SetWindow(WNO_Operating); outw_el3(dep, REG_CmdStatus, CMD_StatsEnable); return; } /* ** Name: void el3_getstats(dpeth_t *dep) ** Function: Reads statistics counters from board. */ static void el3_getstats(dpeth_t * dep) { lock(); el3_update_stats(dep); unlock(); return; } /* ** Name: void el3_dodump(dpeth_t *dep) ** Function: Dumps counter on screen (support for console display). */ static void el3_dodump(dpeth_t * dep) { el3_getstats(dep); return; } /* ** Name: void el3_rx_mode(dpeth_t *dep) ** Function: Initializes receiver mode */ static void el3_rx_mode(dpeth_t * dep) { dep->de_recv_mode = FilterIndividual; if (dep->de_flags & DEF_BROAD) dep->de_recv_mode |= FilterBroadcast; if (dep->de_flags & DEF_MULTI) dep->de_recv_mode |= FilterMulticast; if (dep->de_flags & DEF_PROMISC) dep->de_recv_mode |= FilterPromiscuous; outw_el3(dep, REG_CmdStatus, CMD_RxReset); outw_el3(dep, REG_CmdStatus, CMD_SetRxFilter | dep->de_recv_mode); outw_el3(dep, REG_CmdStatus, CMD_RxEnable); return; } /* ** Name: void el3_reset(dpeth_t *dep) ** Function: Reset function specific for Etherlink hardware. */ static void el3_reset(dpeth_t * dep) { return; /* Done */ } /* ** Name: void el3_write_fifo(dpeth_t * dep, int pktsize); ** Function: Writes a packet from user area to board. ** Remark: Writing a word/dword at a time may result faster ** but is a lot more complicated. Let's go simpler way. */ static void el3_write_fifo(dpeth_t * dep, int pktsize) { phys_bytes phys_user; int bytes, ix = 0; iovec_dat_t *iovp = &dep->de_write_iovec; int padding = pktsize; do { /* Writes chuncks of packet from user buffers */ bytes = iovp->iod_iovec[ix].iov_size; /* Size of buffer */ if (bytes > pktsize) bytes = pktsize; /* Writes from user buffer to Tx FIFO */ outsb(dep->de_data_port, iovp->iod_proc_nr, (void*)(iovp->iod_iovec[ix].iov_addr), bytes); if (++ix >= IOVEC_NR) { /* Next buffer of IO vector */ dp_next_iovec(iovp); ix = 0; } /* Till packet done */ } while ((pktsize -= bytes) > 0); while ((padding++ % sizeof(long)) != 0) outb(dep->de_data_port, 0x00); return; } /* ** Name: void el3_recv(dpeth_t *dep, int fromint, int size) ** Function: Receive function. Called from interrupt handler or ** from main to unload recv. buffer (packet to client) */ static void el3_recv(dpeth_t *dep, int fromint, int size) { buff_t *rxptr; while ((dep->de_flags & DEF_READING) && (rxptr = dep->de_recvq_head)) { lock(); /* Remove buffer from queue */ if (dep->de_recvq_tail == dep->de_recvq_head) dep->de_recvq_head = dep->de_recvq_tail = NULL; else dep->de_recvq_head = rxptr->next; unlock(); /* Copy buffer to user area and free it */ mem2user(dep, rxptr); dep->de_read_s = rxptr->size; dep->de_flags |= DEF_ACK_RECV; dep->de_flags &= NOT(DEF_READING); /* Return buffer to the idle pool */ free_buff(dep, rxptr); } return; } /* ** Name: void el3_rx_complete(dpeth_t * dep); ** Function: Upon receiving a packet, provides status checks ** and if packet is OK copies it to local buffer. */ static void el3_rx_complete(dpeth_t * dep) { short int RxStatus; int pktsize; buff_t *rxptr; RxStatus = inw_el3(dep, REG_RxStatus); pktsize = RxStatus & RXS_Length; /* Mask off packet length */ if (RxStatus & RXS_Error) { /* First checks for receiving errors */ RxStatus &= RXS_ErrType; switch (RxStatus) { /* Bad packet (see error type) */ case RXS_Dribble: case RXS_Oversize: case RXS_Runt: dep->de_stat.ets_recvErr += 1; break; case RXS_Overrun: dep->de_stat.ets_OVW += 1; break; case RXS_Framing: dep->de_stat.ets_frameAll += 1; break; case RXS_CRC: dep->de_stat.ets_CRCerr += 1; break; } } else if ((rxptr = alloc_buff(dep, pktsize + sizeof(buff_t))) == NULL) { /* Memory not available. Drop packet */ dep->de_stat.ets_fifoOver += 1; } else { /* Good packet. Read it from FIFO */ insb(dep->de_data_port, SELF, rxptr->buffer, pktsize); rxptr->next = NULL; rxptr->size = pktsize; lock(); /* Queue packet to receive queue */ if (dep->de_recvq_head == NULL) dep->de_recvq_head = rxptr; else dep->de_recvq_tail->next = rxptr; dep->de_recvq_tail = rxptr; unlock(); /* Reply to pending Receive requests, if any */ el3_recv(dep, TRUE, pktsize); } /* Discard top packet from queue */ outw_el3(dep, REG_CmdStatus, CMD_RxDiscard); return; } /* ** Name: void el3_send(dpeth_t *dep, int count) ** Function: Send function. Called from main to transit a packet or ** from interrupt handler when Tx FIFO gets available. */ static void el3_send(dpeth_t * dep, int from_int, int count) { clock_t now; int ix; short int TxStatus; getuptime(&now); if ((dep->de_flags & DEF_XMIT_BUSY) && (now - dep->de_xmit_start) > 4) { DEBUG(printf("3c509: Transmitter timed out. Resetting ....\n");) dep->de_stat.ets_sendErr += 1; /* Resets and restars the transmitter */ outw_el3(dep, REG_CmdStatus, CMD_TxReset); outw_el3(dep, REG_CmdStatus, CMD_TxEnable); dep->de_flags &= NOT(DEF_XMIT_BUSY); } if (!(dep->de_flags & DEF_XMIT_BUSY)) { /* Writes Transmitter preamble 1st Word (packet len, no ints) */ outw_el3(dep, REG_TxFIFO, count); /* Writes Transmitter preamble 2nd Word (all zero) */ outw_el3(dep, REG_TxFIFO, 0); /* Writes packet */ el3_write_fifo(dep, count); getuptime(&dep->de_xmit_start); dep->de_flags |= (DEF_XMIT_BUSY | DEF_ACK_SEND); if (inw_el3(dep, REG_TxFree) > ETH_MAX_PACK_SIZE) { /* Tx has enough room for a packet of maximum size */ dep->de_flags &= NOT(DEF_XMIT_BUSY | DEF_SENDING); } else { /* Interrupt driver when enough room is available */ outw_el3(dep, REG_CmdStatus, CMD_SetTxAvailable | ETH_MAX_PACK_SIZE); dep->de_flags &= NOT(DEF_SENDING); } /* Pops Tx status stack */ for (ix = 4; --ix && (TxStatus = inb_el3(dep, REG_TxStatus)) > 0;) { if (TxStatus & 0x38) dep->de_stat.ets_sendErr += 1; if (TxStatus & 0x30) outw_el3(dep, REG_CmdStatus, CMD_TxReset); if (TxStatus & 0x3C) outw_el3(dep, REG_CmdStatus, CMD_TxEnable); outb_el3(dep, REG_TxStatus, 0); } } return; } /* ** Name: void el3_close(dpeth_t *dep) ** Function: Stops board and makes it ready to shut down. */ static void el3_close(dpeth_t * dep) { /* Disables statistics, Receiver and Transmitter */ outw_el3(dep, REG_CmdStatus, CMD_StatsDisable); outw_el3(dep, REG_CmdStatus, CMD_RxDisable); outw_el3(dep, REG_CmdStatus, CMD_TxDisable); if (dep->de_if_port == BNC_XCVR) { outw_el3(dep, REG_CmdStatus, CMD_StopIntXcvr); /* milli_delay(5); */ } else if (dep->de_if_port == TP_XCVR) { SetWindow(WNO_Diagnostics); outw_el3(dep, REG_MediaStatus, inw_el3(dep, REG_MediaStatus) & NOT((MediaLBeatEnable | MediaJabberEnable))); /* milli_delay(5); */ } DEBUG(printf("%s: stopping Etherlink ... \n", dep->de_name)); /* Issues a global reset outw_el3(dep, REG_CmdStatus, CMD_GlobalReset); */ sys_irqdisable(&dep->de_hook); /* Disable interrupt */ return; } /* ** Name: void el3_interrupt(dpeth_t *dep) ** Function: Interrupt handler. Acknwledges transmit interrupts ** or unloads receive buffer to memory queue. */ static void el3_interrupt(dpeth_t * dep) { int loop; unsigned short isr; for (loop = 5; loop > 0 && ((isr = inw_el3(dep, REG_CmdStatus)) & (INT_Latch | INT_RxComplete | INT_UpdateStats)); loop -= 1) { if (isr & INT_RxComplete) /* Got a new packet */ el3_rx_complete(dep); if (isr & INT_TxAvailable) { /* Tx has room for big packets */ DEBUG(printf("3c509: got Tx interrupt, Status=0x%04x\n", isr);) dep->de_flags &= NOT(DEF_XMIT_BUSY); outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | INT_TxAvailable); if (dep->de_flags & DEF_SENDING) /* Send pending */ el3_send(dep, TRUE, dep->de_send_s); } if (isr & (INT_AdapterFail | INT_RxEarly | INT_UpdateStats)) { if (isr & INT_UpdateStats) /* Empties statistics */ el3_getstats(dep); if (isr & INT_RxEarly) /* Not really used. Do nothing */ outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | (INT_RxEarly)); if (isr & INT_AdapterFail) { /* Adapter error. Reset and re-enable receiver */ DEBUG(printf("3c509: got Rx fail interrupt, Status=0x%04x\n", isr);) el3_rx_mode(dep); outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | INT_AdapterFail); } } /* Acknowledge interrupt */ outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | (INT_Latch | INT_Requested)); } return; } /* ** Name: unsigned el3_read_eeprom(port_t port, unsigned address); ** Function: Reads the EEPROM at specified address */ static unsigned el3_read_eeprom(port_t port, unsigned address) { unsigned int result; int bit; address |= EL3_READ_EEPROM; outb(port, address); milli_delay(5); /* Allows EEPROM reads */ for (result = 0, bit = 16; bit > 0; bit -= 1) { result = (result << 1) | (inb(port) & 0x0001); } return result; } /* ** Name: void el3_read_StationAddress(dpeth_t *dep) ** Function: Reads station address from board */ static void el3_read_StationAddress(dpeth_t * dep) { unsigned int ix, rc; for (ix = EE_3COM_NODE_ADDR; ix < SA_ADDR_LEN;) { /* Accesses with word No. */ rc = el3_read_eeprom(dep->de_id_port, ix / 2); /* Swaps bytes of word */ dep->de_address.ea_addr[ix++] = (rc >> 8) & 0xFF; dep->de_address.ea_addr[ix++] = rc & 0xFF; } return; } /* ** Name: void el3_open(dpeth_t *dep) ** Function: Initalizes board hardware and driver data structures. */ static void el3_open(dpeth_t * dep) { unsigned int AddrCfgReg, ResCfgReg; unsigned int ix; el3_read_StationAddress(dep); /* Get ethernet address */ /* Get address and resource configurations */ AddrCfgReg = el3_read_eeprom(dep->de_id_port, EE_ADDR_CFG); ResCfgReg = el3_read_eeprom(dep->de_id_port, EE_RESOURCE_CFG); outb(dep->de_id_port, EL3_ACTIVATE); /* Activate the board */ /* Gets xcvr configuration */ dep->de_if_port = AddrCfgReg & EL3_CONFIG_XCVR_MASK; AddrCfgReg = ((AddrCfgReg & EL3_CONFIG_IOBASE_MASK) << 4) + EL3_IO_BASE_ADDR; if (AddrCfgReg != dep->de_base_port) panic(dep->de_name, "Bad I/O port for Etherlink board", NO_NUM); ResCfgReg >>= 12; dep->de_irq &= NOT(DEI_DEFAULT); /* Strips the default flag */ if (ResCfgReg != dep->de_irq) panic(dep->de_name, "Bad IRQ for Etherlink board", NO_NUM); SetWindow(WNO_Setup); /* Reset transmitter and receiver */ outw_el3(dep, REG_CmdStatus, CMD_TxReset); outw_el3(dep, REG_CmdStatus, CMD_RxReset); /* Enable the adapter */ outb_el3(dep, REG_CfgControl, EL3_EnableAdapter); /* Disable Status bits */ outw_el3(dep, REG_CmdStatus, CMD_SetStatusEnab + 0x00); /* Set "my own" address */ SetWindow(WNO_StationAddress); for (ix = 0; ix < 6; ix += 1) outb_el3(dep, REG_SA0_1 + ix, dep->de_address.ea_addr[ix]); /* Start Transceivers as required */ if (dep->de_if_port == BNC_XCVR) { /* Start internal transceiver for Coaxial cable */ outw_el3(dep, REG_CmdStatus, CMD_StartIntXcvr); milli_delay(5); } else if (dep->de_if_port == TP_XCVR) { /* Start internal transceiver for Twisted pair cable */ SetWindow(WNO_Diagnostics); outw_el3(dep, REG_MediaStatus, inw_el3(dep, REG_MediaStatus) | (MediaLBeatEnable | MediaJabberEnable)); } /* Switch to the statistic window, and clear counts (by reading) */ SetWindow(WNO_Statistics); for (ix = REG_TxCarrierLost; ix <= REG_TxDefer; ix += 1) inb_el3(dep, ix); inw_el3(dep, REG_RxBytes); inw_el3(dep, REG_TxBytes); /* Switch to operating window for normal use */ SetWindow(WNO_Operating); /* Receive individual address & broadcast. (Mofified later by rx_mode) */ outw_el3(dep, REG_CmdStatus, CMD_SetRxFilter | (FilterIndividual | FilterBroadcast)); /* Turn on statistics */ outw_el3(dep, REG_CmdStatus, CMD_StatsEnable); /* Enable transmitter and receiver */ outw_el3(dep, REG_CmdStatus, CMD_TxEnable); outw_el3(dep, REG_CmdStatus, CMD_RxEnable); /* Enable all the status bits */ outw_el3(dep, REG_CmdStatus, CMD_SetStatusEnab | 0xFF); /* Acknowledge all interrupts to clear adapter. Enable interrupts */ outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | 0xFF); outw_el3(dep, REG_CmdStatus, CMD_SetIntMask | (INT_Latch | INT_TxAvailable | INT_RxComplete | INT_UpdateStats)); /* Ready to operate, sets the environment for eth_task */ dep->de_data_port = dep->de_base_port; /* Allocates Rx/Tx buffers */ init_buff(dep, NULL); /* Device specific functions */ dep->de_recvf = el3_recv; dep->de_sendf = el3_send; dep->de_flagsf = el3_rx_mode; dep->de_resetf = el3_reset; dep->de_getstatsf = el3_getstats; dep->de_dumpstatsf = el3_dodump; dep->de_interruptf = el3_interrupt; printf("%s: Etherlink III (%s) at %X:%d, %s port - ", dep->de_name, "3c509", dep->de_base_port, dep->de_irq, IfNamesMsg[dep->de_if_port >> 14]); for (ix = 0; ix < SA_ADDR_LEN; ix += 1) printf("%02X%c", dep->de_address.ea_addr[ix], ix < SA_ADDR_LEN - 1 ? ':' : '\n'); return; /* Done */ } /* ** Name: unsigned int el3_checksum(port_t port); ** Function: Reads EEPROM and computes checksum. */ static unsigned short el3_checksum(port_t port) { unsigned short rc, checksum, address; unsigned char lo, hi; for (checksum = address = 0; address < 15; address += 1) { rc = el3_read_eeprom(port, address); lo = rc & 0xFF; hi = (rc >> 8) & 0xFF; if ((address == EE_PROD_ID && (rc & EE_PROD_ID_MASK) != EL3_PRODUCT_ID) || (address == EE_3COM_CODE && rc != EL3_3COM_CODE)) return address; if (address == EE_ADDR_CFG || address == EE_RESOURCE_CFG || address == EE_SW_CONFIG_INFO) { lo ^= hi; hi = 0; } else { hi ^= lo; lo = 0; } rc = ((unsigned) hi << 8) + lo; checksum ^= rc; } rc = el3_read_eeprom(port, address); return(checksum ^= rc); /* If OK checksum is 0 */ } /* ** Name: void el3_write_id(port_t port); ** Function: Writes the ID sequence to the board. */ static void el3_write_id(port_t port) { int ix, pattern; outb(port, 0); /* Selects the ID port */ outb(port, 0); /* Resets hardware pattern generator */ for (pattern = ix = 0x00FF; ix > 0; ix -= 1) { outb(port, pattern); pattern <<= 1; pattern = (pattern & 0x0100) ? pattern ^ 0xCF : pattern; } return; } /* ** Name: int el3_probe(dpeth_t *dep) ** Function: Checks for presence of the board. */ PUBLIC int el3_probe(dpeth_t * dep) { port_t id_port; /* Don't ask me what is this for !! */ outb(0x0279, 0x02); /* Select PnP config control register. */ outb(0x0A79, 0x02); /* Return to WaitForKey state. */ /* Tests I/O ports in the 0x1xF range for a valid ID port */ for (id_port = 0x110; id_port < 0x200; id_port += 0x10) { outb(id_port, 0x00); outb(id_port, 0xFF); if (inb(id_port) & 0x01) break; } if (id_port == 0x200) return 0; /* No board responding */ el3_write_id(id_port); outb(id_port, EL3_ID_GLOBAL_RESET); /* Reset the board */ milli_delay(5); /* Technical reference says 162 micro sec. */ el3_write_id(id_port); outb(id_port, EL3_SET_TAG_REGISTER); milli_delay(5); dep->de_id_port = id_port; /* Stores ID port No. */ dep->de_ramsize = /* RAM size is meaningless */ dep->de_offset_page = 0; dep->de_linmem = 0L; /* Access is via I/O port */ /* Device specific functions */ dep->de_initf = el3_open; dep->de_stopf = el3_close; return(el3_checksum(id_port) == 0); /* Etherlink board found/not found */ } #endif /* ENABLE_3C509 */ /** 3c509.c **/