| /************************************************************************** |
| Etherboot - BOOTP/TFTP Bootstrap Program |
| LANCE NIC driver for Etherboot |
| Large portions borrowed from the Linux LANCE driver by Donald Becker |
| Ken Yap, July 1997 |
| ***************************************************************************/ |
| |
| /* |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2, or (at |
| * your option) any later version. |
| */ |
| |
| /* to get some global routines like printf */ |
| #include "etherboot.h" |
| /* to get the interface to the body of the program */ |
| #include "nic.h" |
| |
| /* Offsets from base I/O address */ |
| #if defined(INCLUDE_NE2100) || defined(INCLUDE_LANCEPCI) |
| #define LANCE_ETH_ADDR 0x0 |
| #define LANCE_DATA 0x10 |
| #define LANCE_ADDR 0x12 |
| #define LANCE_RESET 0x14 |
| #define LANCE_BUS_IF 0x16 |
| #define LANCE_TOTAL_SIZE 0x18 |
| #endif |
| #ifdef INCLUDE_NI6510 |
| #define LANCE_ETH_ADDR 0x8 |
| #define LANCE_DATA 0x0 |
| #define LANCE_ADDR 0x2 |
| #define LANCE_RESET 0x4 |
| #define LANCE_BUS_IF 0x6 |
| #define LANCE_TOTAL_SIZE 0x10 |
| #endif |
| |
| struct lance_init_block |
| { |
| unsigned short mode; |
| unsigned char phys_addr[6]; |
| unsigned long filter[2]; |
| Address rx_ring; |
| Address tx_ring; |
| }; |
| |
| struct lance_rx_head |
| { |
| union { |
| Address base; |
| char addr[4]; |
| } u; |
| short buf_length; /* 2s complement */ |
| short msg_length; |
| }; |
| |
| struct lance_tx_head |
| { |
| union { |
| Address base; |
| char addr[4]; |
| } u; |
| short buf_length; /* 2s complement */ |
| short misc; |
| }; |
| |
| struct lance_interface |
| { |
| struct lance_init_block init_block; |
| struct lance_rx_head rx_ring; |
| struct lance_tx_head tx_ring; |
| unsigned char rbuf[ETH_MAX_PACKET]; |
| unsigned char tbuf[ETH_MAX_PACKET]; |
| }; |
| |
| #define LANCE_MUST_PAD 0x00000001 |
| #define LANCE_ENABLE_AUTOSELECT 0x00000002 |
| #define LANCE_MUST_REINIT_RING 0x00000004 |
| #define LANCE_MUST_UNRESET 0x00000008 |
| #define LANCE_HAS_MISSED_FRAME 0x00000010 |
| |
| /* A mapping from the chip ID number to the part number and features. |
| These are from the datasheets -- in real life the '970 version |
| reportedly has the same ID as the '965. */ |
| static struct lance_chip_type |
| { |
| int id_number; |
| char *name; |
| int flags; |
| } chip_table[] = { |
| {0x0000, "LANCE 7990", /* Ancient lance chip. */ |
| LANCE_MUST_PAD + LANCE_MUST_UNRESET}, |
| {0x0003, "PCnet/ISA 79C960", /* 79C960 PCnet/ISA. */ |
| LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + |
| LANCE_HAS_MISSED_FRAME}, |
| {0x2260, "PCnet/ISA+ 79C961", /* 79C961 PCnet/ISA+, Plug-n-Play. */ |
| LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + |
| LANCE_HAS_MISSED_FRAME}, |
| {0x2420, "PCnet/PCI 79C970", /* 79C970 or 79C974 PCnet-SCSI, PCI. */ |
| LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + |
| LANCE_HAS_MISSED_FRAME}, |
| /* Bug: the PCnet/PCI actually uses the PCnet/VLB ID number, so just call |
| it the PCnet32. */ |
| {0x2430, "PCnet32", /* 79C965 PCnet for VL bus. */ |
| LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + |
| LANCE_HAS_MISSED_FRAME}, |
| {0x2621, "PCnet/PCI-II 79C970A", /* 79C970A PCInetPCI II. */ |
| LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + |
| LANCE_HAS_MISSED_FRAME}, |
| {0x0, "PCnet (unknown)", |
| LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + |
| LANCE_HAS_MISSED_FRAME}, |
| }; |
| |
| /* Define a macro for converting program addresses to real addresses */ |
| #ifdef ETHERBOOT32 |
| #undef virt_to_bus |
| #define virt_to_bus(x) ((unsigned long)x) |
| #endif |
| #ifdef ETHERBOOT16 |
| #define virt_to_bus(x) (((Address)x)+RELOC) |
| #endif /* ETHERBOOT16 */ |
| |
| static int chip_version; |
| static unsigned short ioaddr; |
| static int dma; |
| static struct lance_interface *lp; |
| /* additional 8 bytes for 8-byte alignment space */ |
| static char lance[sizeof(struct lance_interface)+8]; |
| |
| /* DMA defines and helper routines */ |
| |
| /* DMA controller registers */ |
| #define DMA1_CMD_REG 0x08 /* command register (w) */ |
| #define DMA1_STAT_REG 0x08 /* status register (r) */ |
| #define DMA1_REQ_REG 0x09 /* request register (w) */ |
| #define DMA1_MASK_REG 0x0A /* single-channel mask (w) */ |
| #define DMA1_MODE_REG 0x0B /* mode register (w) */ |
| #define DMA1_CLEAR_FF_REG 0x0C /* clear pointer flip-flop (w) */ |
| #define DMA1_TEMP_REG 0x0D /* Temporary Register (r) */ |
| #define DMA1_RESET_REG 0x0D /* Master Clear (w) */ |
| #define DMA1_CLR_MASK_REG 0x0E /* Clear Mask */ |
| #define DMA1_MASK_ALL_REG 0x0F /* all-channels mask (w) */ |
| |
| #define DMA2_CMD_REG 0xD0 /* command register (w) */ |
| #define DMA2_STAT_REG 0xD0 /* status register (r) */ |
| #define DMA2_REQ_REG 0xD2 /* request register (w) */ |
| #define DMA2_MASK_REG 0xD4 /* single-channel mask (w) */ |
| #define DMA2_MODE_REG 0xD6 /* mode register (w) */ |
| #define DMA2_CLEAR_FF_REG 0xD8 /* clear pointer flip-flop (w) */ |
| #define DMA2_TEMP_REG 0xDA /* Temporary Register (r) */ |
| #define DMA2_RESET_REG 0xDA /* Master Clear (w) */ |
| #define DMA2_CLR_MASK_REG 0xDC /* Clear Mask */ |
| #define DMA2_MASK_ALL_REG 0xDE /* all-channels mask (w) */ |
| |
| |
| #define DMA_MODE_READ 0x44 /* I/O to memory, no autoinit, increment, single mode */ |
| #define DMA_MODE_WRITE 0x48 /* memory to I/O, no autoinit, increment, single mode */ |
| #define DMA_MODE_CASCADE 0xC0 /* pass thru DREQ->HRQ, DACK<-HLDA only */ |
| |
| /* enable/disable a specific DMA channel */ |
| static void enable_dma(unsigned int dmanr) |
| { |
| if (dmanr <= 3) |
| outb_p(dmanr, DMA1_MASK_REG); |
| else |
| outb_p(dmanr & 3, DMA2_MASK_REG); |
| } |
| |
| static void disable_dma(unsigned int dmanr) |
| { |
| if (dmanr <= 3) |
| outb_p(dmanr | 4, DMA1_MASK_REG); |
| else |
| outb_p((dmanr & 3) | 4, DMA2_MASK_REG); |
| } |
| |
| /* set mode (above) for a specific DMA channel */ |
| static void set_dma_mode(unsigned int dmanr, char mode) |
| { |
| if (dmanr <= 3) |
| outb_p(mode | dmanr, DMA1_MODE_REG); |
| else |
| outb_p(mode | (dmanr&3), DMA2_MODE_REG); |
| } |
| |
| /************************************************************************** |
| RESET - Reset adapter |
| ***************************************************************************/ |
| static void lance_reset(struct nic *nic) |
| { |
| int i; |
| Address l; |
| |
| /* Reset the LANCE */ |
| (void)inw(ioaddr+LANCE_RESET); |
| /* Un-Reset the LANCE, needed only for the NE2100 */ |
| if (chip_table[chip_version].flags & LANCE_MUST_UNRESET) |
| outw(0, ioaddr+LANCE_RESET); |
| if (chip_table[chip_version].flags & LANCE_ENABLE_AUTOSELECT) |
| { |
| /* This is 79C960 specific; Turn on auto-select of media |
| (AUI, BNC). */ |
| outw(0x2, ioaddr+LANCE_ADDR); |
| outw(0x2, ioaddr+LANCE_BUS_IF); |
| } |
| /* Re-initialise the LANCE, and start it when done. */ |
| /* Set station address */ |
| for (i = 0; i < ETHER_ADDR_SIZE; ++i) |
| lp->init_block.phys_addr[i] = nic->node_addr[i]; |
| /* Preset the receive ring header */ |
| lp->rx_ring.buf_length = -ETH_MAX_PACKET; |
| /* OWN */ |
| lp->rx_ring.u.base = virt_to_bus(lp->rbuf) & 0xffffff; |
| /* we set the top byte as the very last thing */ |
| lp->rx_ring.u.addr[3] = 0x80; |
| lp->init_block.mode = 0x0; /* enable Rx and Tx */ |
| l = (Address)virt_to_bus(&lp->init_block); |
| outw(0x1, ioaddr+LANCE_ADDR); |
| (void)inw(ioaddr+LANCE_ADDR); |
| outw((short)l, ioaddr+LANCE_DATA); |
| outw(0x2, ioaddr+LANCE_ADDR); |
| (void)inw(ioaddr+LANCE_ADDR); |
| outw((short)(l >> 16), ioaddr+LANCE_DATA); |
| outw(0x4, ioaddr+LANCE_ADDR); |
| (void)inw(ioaddr+LANCE_ADDR); |
| outw(0x915, ioaddr+LANCE_DATA); |
| outw(0x0, ioaddr+LANCE_ADDR); |
| (void)inw(ioaddr+LANCE_ADDR); |
| outw(0x4, ioaddr+LANCE_DATA); /* stop */ |
| outw(0x1, ioaddr+LANCE_DATA); /* init */ |
| for (i = 100; i > 0; --i) |
| if (inw(ioaddr+LANCE_DATA) & 0x100) |
| break; |
| /* Apparently clearing the InitDone bit here triggers a bug |
| in the '974. (Mark Stockton) */ |
| outw(0x2, ioaddr+LANCE_DATA); /* start */ |
| } |
| |
| /************************************************************************** |
| POLL - Wait for a frame |
| ***************************************************************************/ |
| static int lance_poll(struct nic *nic) |
| { |
| int status; |
| |
| status = lp->rx_ring.u.base >> 24; |
| if (status & 0x80) |
| return (0); |
| #ifdef DEBUG |
| printf("LANCE packet received rx_ring.u.base %X mcnt %x csr0 %x\n", |
| lp->rx_ring.u.base, lp->rx_ring.msg_length, |
| inw(ioaddr+LANCE_DATA)); |
| #endif |
| if (status == 0x3) |
| memcpy(nic->packet, lp->rbuf, nic->packetlen = lp->rx_ring.msg_length); |
| /* Andrew Boyd of QNX reports that some revs of the 79C765 |
| clear the buffer length */ |
| lp->rx_ring.buf_length = -ETH_MAX_PACKET; |
| lp->rx_ring.u.addr[3] |= 0x80; /* prime for next receive */ |
| outw(0x0, ioaddr+LANCE_ADDR); |
| (void)inw(ioaddr+LANCE_ADDR); |
| outw(0x500, ioaddr+LANCE_DATA); /* clear receive + InitDone */ |
| return (status == 0x3); |
| } |
| |
| /************************************************************************** |
| TRANSMIT - Transmit a frame |
| ***************************************************************************/ |
| static void lance_transmit( |
| struct nic *nic, |
| char *d, /* Destination */ |
| unsigned int t, /* Type */ |
| unsigned int s, /* size */ |
| char *p) /* Packet */ |
| { |
| unsigned long time; |
| |
| /* copy the packet to ring buffer */ |
| memcpy(lp->tbuf, d, ETHER_ADDR_SIZE); /* dst */ |
| memcpy(&lp->tbuf[ETHER_ADDR_SIZE], nic->node_addr, ETHER_ADDR_SIZE); /* src */ |
| lp->tbuf[ETHER_ADDR_SIZE+ETHER_ADDR_SIZE] = t >> 8; /* type */ |
| lp->tbuf[ETHER_ADDR_SIZE+ETHER_ADDR_SIZE+1] = t; /* type */ |
| memcpy(&lp->tbuf[ETHER_HDR_SIZE], p, s); |
| s += ETHER_HDR_SIZE; |
| if (chip_table[chip_version].flags & LANCE_MUST_PAD) |
| while (s < ETH_MIN_PACKET) /* pad to min length */ |
| lp->tbuf[s++] = 0; |
| lp->tx_ring.buf_length = -s; |
| lp->tx_ring.misc = 0x0; |
| /* OWN, STP, ENP */ |
| lp->tx_ring.u.base = virt_to_bus(lp->tbuf) & 0xffffff; |
| /* we set the top byte as the very last thing */ |
| lp->tx_ring.u.addr[3] = 0x83; |
| /* Trigger an immediate send poll */ |
| outw(0x0, ioaddr+LANCE_ADDR); |
| outw(0x48, ioaddr+LANCE_DATA); |
| /* wait for transmit complete */ |
| time = currticks() + TICKS_PER_SEC; /* wait one second */ |
| while (currticks() < time && (lp->tx_ring.u.base & 0x80000000) != 0) |
| ; |
| if ((lp->tx_ring.u.base & 0x80000000) != 0) |
| printf("LANCE timed out on transmit\n"); |
| (void)inw(ioaddr+LANCE_ADDR); |
| outw(0x200, ioaddr+LANCE_DATA); /* clear transmit + InitDone */ |
| #ifdef DEBUG |
| printf("tx_ring.u.base %X tx_ring.buf_length %x tx_ring.misc %x csr0 %x\n", |
| lp->tx_ring.u.base, lp->tx_ring.buf_length, lp->tx_ring.misc, |
| inw(ioaddr+LANCE_DATA)); |
| #endif |
| } |
| |
| static void lance_disable(struct nic *nic) |
| { |
| disable_dma(dma); |
| } |
| |
| static int lance_probe1(struct nic *nic) |
| { |
| int reset_val, lance_version, i; |
| Address l; |
| short dma_channels; |
| static char dmas[] = { 5, 6, 7, 3 }; |
| |
| reset_val = inw(ioaddr+LANCE_RESET); |
| #ifdef INCLUDE_NE2100 |
| outw(0x0, ioaddr+LANCE_ADDR); /* Switch to window 0 */ |
| if (inw(ioaddr+LANCE_DATA) != 0x4) |
| return (-1); |
| #endif |
| outw(88, ioaddr+LANCE_ADDR); /* Get the version of the chip */ |
| if (inw(ioaddr+LANCE_ADDR) != 88) |
| lance_version = 0; |
| else |
| { |
| chip_version = inw(ioaddr+LANCE_DATA); |
| outw(89, ioaddr+LANCE_ADDR); |
| chip_version |= inw(ioaddr+LANCE_DATA) << 16; |
| if ((chip_version & 0xfff) != 0x3) |
| return (-1); |
| chip_version = (chip_version >> 12) & 0xffff; |
| for (lance_version = 1; chip_table[lance_version].id_number != 0; ++lance_version) |
| if (chip_table[lance_version].id_number == chip_version) |
| break; |
| } |
| /* make sure data structure is 8-byte aligned */ |
| l = ((Address)lance + 7) & ~7; |
| lp = (struct lance_interface *)l; |
| lp->init_block.mode = 0x3; /* disable Rx and Tx */ |
| lp->init_block.filter[0] = lp->init_block.filter[1] = 0x0; |
| /* top bits are zero, we have only one buffer each for Rx and Tx */ |
| lp->init_block.rx_ring = virt_to_bus(&lp->rx_ring) & 0xffffff; |
| lp->init_block.tx_ring = virt_to_bus(&lp->tx_ring) & 0xffffff; |
| l = virt_to_bus(&lp->init_block); |
| outw(0x1, ioaddr+LANCE_ADDR); |
| (void)inw(ioaddr+LANCE_ADDR); |
| outw((unsigned short)l, ioaddr+LANCE_DATA); |
| outw(0x2, ioaddr+LANCE_ADDR); |
| (void)inw(ioaddr+LANCE_ADDR); |
| outw((unsigned short)(l >> 16), ioaddr+LANCE_DATA); |
| outw(0x4, ioaddr+LANCE_ADDR); |
| (void)inw(ioaddr+LANCE_ADDR); |
| outw(0x915, ioaddr+LANCE_DATA); |
| outw(0x0, ioaddr+LANCE_ADDR); |
| (void)inw(ioaddr+LANCE_ADDR); |
| /* now probe for DMA channel */ |
| dma_channels = ((inb(DMA1_STAT_REG) >> 4) & 0xf) | |
| (inb(DMA2_STAT_REG) & 0xf0); |
| /* need to fix when PCI provides DMA info */ |
| for (i = 0; i < (sizeof(dmas)/sizeof(dmas[0])); ++i) |
| { |
| int j; |
| |
| dma = dmas[i]; |
| /* Don't enable a permanently busy DMA channel, |
| or the machine will hang */ |
| if (dma_channels & (1 << dma)) |
| continue; |
| outw(0x7f04, ioaddr+LANCE_DATA); /* clear memory error bits */ |
| set_dma_mode(dma, DMA_MODE_CASCADE); |
| enable_dma(dma); |
| outw(0x1, ioaddr+LANCE_DATA); /* init */ |
| for (j = 100; j > 0; --j) |
| if (inw(ioaddr+LANCE_DATA) & 0x900) |
| break; |
| if (inw(ioaddr+LANCE_DATA) & 0x100) |
| break; |
| else |
| disable_dma(dma); |
| } |
| if (i >= (sizeof(dmas)/sizeof(dmas[0]))) |
| dma = 0; |
| printf("\n%s base 0x%x, DMA %d, addr ", |
| chip_table[lance_version].name, ioaddr, dma); |
| /* Get station address */ |
| for (i = 0; i < ETHER_ADDR_SIZE; ++i) |
| { |
| printf("%b", nic->node_addr[i] = inb(ioaddr+LANCE_ETH_ADDR+i)); |
| if (i < ETHER_ADDR_SIZE -1) |
| printf(":"); |
| } |
| putchar('\n'); |
| return (lance_version); |
| } |
| |
| /************************************************************************** |
| PROBE - Look for an adapter, this routine's visible to the outside |
| ***************************************************************************/ |
| |
| #ifdef INCLUDE_LANCEPCI |
| struct nic *lancepci_probe(struct nic *nic, unsigned short *probe_addrs) |
| #endif |
| #ifdef INCLUDE_NE2100 |
| struct nic *ne2100_probe(struct nic *nic, unsigned short *probe_addrs) |
| #endif |
| #ifdef INCLUDE_NI6510 |
| struct nic *ni6510_probe(struct nic *nic, unsigned short *probe_addrs) |
| #endif |
| { |
| unsigned short *p; |
| static unsigned short io_addrs[] = { 0x300, 0x320, 0x340, 0x360, 0 }; |
| |
| /* if probe_addrs is 0, then routine can use a hardwired default */ |
| if (probe_addrs == 0) |
| probe_addrs = io_addrs; |
| for (p = probe_addrs; (ioaddr = *p) != 0; ++p) |
| { |
| char offset15, offset14 = inb(ioaddr + 14); |
| |
| #ifdef INCLUDE_NE2100 |
| if ((offset14 == 0x52 || offset14 == 0x57) && |
| ((offset15 = inb(ioaddr + 15)) == 0x57 || offset15 == 0x44)) |
| #endif |
| #ifdef INCLUDE_NI6510 |
| if ((offset14 == 0x00 || offset14 == 0x52) && |
| ((offset15 = inb(ioaddr + 15)) == 0x55 || offset15 == 0x44)) |
| #endif |
| if (lance_probe1(nic) >= 0) |
| break; |
| } |
| /* if board found */ |
| if (ioaddr != 0) |
| { |
| /* point to NIC specific routines */ |
| lance_reset(nic); |
| nic->reset = lance_reset; |
| nic->poll = lance_poll; |
| nic->transmit = lance_transmit; |
| nic->disable = lance_disable; |
| return nic; |
| } |
| /* else */ |
| { |
| return 0; |
| } |
| } |