diff options
author | Thomas Bushnell <thomas@gnu.org> | 1997-02-25 21:28:37 +0000 |
---|---|---|
committer | Thomas Bushnell <thomas@gnu.org> | 1997-02-25 21:28:37 +0000 |
commit | f07a4c844da9f0ecae5bbee1ab94be56505f26f7 (patch) | |
tree | 12b07c7e578fc1a5f53dbfde2632408491ff2a70 /i386/i386at/gpl/if_hpp.c | |
download | gnumach-f07a4c844da9f0ecae5bbee1ab94be56505f26f7.tar.gz gnumach-f07a4c844da9f0ecae5bbee1ab94be56505f26f7.tar.bz2 gnumach-f07a4c844da9f0ecae5bbee1ab94be56505f26f7.zip |
Initial source
Diffstat (limited to 'i386/i386at/gpl/if_hpp.c')
-rw-r--r-- | i386/i386at/gpl/if_hpp.c | 690 |
1 files changed, 690 insertions, 0 deletions
diff --git a/i386/i386at/gpl/if_hpp.c b/i386/i386at/gpl/if_hpp.c new file mode 100644 index 00000000..c1770301 --- /dev/null +++ b/i386/i386at/gpl/if_hpp.c @@ -0,0 +1,690 @@ +/* + Written 1994 by Donald Becker. + + This driver is for the Hewlett Packard PC LAN (27***) plus ethercards. + These cards are sold under several model numbers, usually 2724*. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + As is often the case, a great deal of credit is owed to Russ Nelson. + The Crynwr packet driver was my primary source of HP-specific + programming information. +*/ + +/* + * Ported to mach by Stephen Clawson, sclawson@cs.utah.edu + * University of Utah CSL. + * + * Derived from the Linux driver by Donald Becker. + * + * Also uses code Shantanu Goel adapted from Donald Becker + * for ns8930 support. + * + */ + +#include <hpp.h> +#if NHPP > 0 + +#include <sys/types.h> +#include "vm_param.h" +#include <kern/time_out.h> +#include <device/device_types.h> +#include <device/errno.h> +#include <device/io_req.h> +#include <device/if_hdr.h> +#include <device/if_ether.h> +#include <device/net_status.h> +#include <device/net_io.h> +#include <chips/busses.h> +#include <i386/ipl.h> +#include <i386/pio.h> +#include <i386at/gpl/if_nsreg.h> + + +/* + * XXX - This is some gross glue garbage. The io instructions really + * should be integrated into pio.h... + */ +#define IO_DELAY __asm__ __volatile__("outb %al,$0x80") +#define outb_p(p, v) { outb(p, v); IO_DELAY; } +#define inb_p(p) ({ unsigned char _v; _v = inb(p); IO_DELAY; _v; }) + + +static __inline void +insw(u_short port, void *addr, int cnt) +{ + __asm __volatile("cld\n\trepne\n\tinsw" : + : "d" (port), "D" (addr), "c" (cnt) : "%edi", "%ecx"); +} + +static __inline void +outsw(u_short port, void *addr, int cnt) +{ + __asm __volatile("cld\n\trepne\n\toutsw" : + : "d" (port), "S" (addr), "c" (cnt) : "%esi", "%ecx"); +} + + +/* + The HP EtherTwist chip implementation is a fairly routine DP8390 + implementation. It allows both shared memory and programmed-I/O buffer + access, using a custom interface for both. The programmed-I/O mode is + entirely implemented in the HP EtherTwist chip, bypassing the problem + ridden built-in 8390 facilities used on NE2000 designs. The shared + memory mode is likewise special, with an offset register used to make + packets appear at the shared memory base. Both modes use a base and bounds + page register to hide the Rx ring buffer wrap -- a packet that spans the + end of physical buffer memory appears continuous to the driver. (c.f. the + 3c503 and Cabletron E2100) + + A special note: the internal buffer of the board is only 8 bits wide. + This lays several nasty traps for the unaware: + - the 8390 must be programmed for byte-wide operations + - all I/O and memory operations must work on whole words (the access + latches are serially preloaded and have no byte-swapping ability). + + This board is laid out in I/O space much like the earlier HP boards: + the first 16 locations are for the board registers, and the second 16 are + for the 8390. The board is easy to identify, with both a dedicated 16 bit + ID register and a constant 0x530* value in the upper bits of the paging + register. +*/ + +#define HP_ID 0x00 /* ID register, always 0x4850. */ +#define HP_PAGING 0x02 /* Registers visible @ 8-f, see PageName. */ +#define HPP_OPTION 0x04 /* Bitmapped options, see HP_Option.*/ +#define HPP_OUT_ADDR 0x08 /* I/O output location in Perf_Page.*/ +#define HPP_IN_ADDR 0x0A /* I/O input location in Perf_Page.*/ +#define HP_DATAPORT 0x0c /* I/O data transfer in Perf_Page.*/ +#define HPP_NIC_OFFSET 0x10 /* Offset to the 8390 registers.*/ +#define HP_IO_EXTENT 32 + +#define HP_START_PG 0x00 /* First page of TX buffer */ +#define HP_STOP_PG 0x80 /* Last page +1 of RX ring */ +/*#define HP_STOP_PG 0x1f + +/* The register set selected in HP_PAGING. */ +enum PageName { + Perf_Page = 0, /* Normal operation. */ + MAC_Page = 1, /* The ethernet address (+checksum). */ + HW_Page = 2, /* EEPROM-loaded hw parameters. */ + LAN_Page = 4, /* Transciever type, testing, etc. */ + ID_Page = 6 }; + +/* The bit definitions for the HPP_OPTION register. */ +enum HP_Option { + NICReset = 1, /* Active low, really UNreset. */ + ChipReset = 2, + EnableIRQ = 4, + FakeIntr = 8, + BootROMEnb = 0x10, + IOEnb = 0x20, + MemEnable = 0x40, + ZeroWait = 0x80, + MemDisable = 0x1000, }; + + +void hpp_reset_8390(struct nssoftc *ns); + +void hpp_mem_block_input(struct nssoftc *ns, int, char *, int); +int hpp_mem_block_output(struct nssoftc *ns, int, char *, int); +void hpp_io_block_input(struct nssoftc *ns, int, char *, int); +int hpp_io_block_output(struct nssoftc *ns, int,char *, int); + + +/* + * Watchdog timer. + */ +int hppwstart = 0; +void hppwatch(void); + + +/* + * Autoconfig structures. + */ +int hpp_std[] = { 0x200, 0x240, 0x280, 0x2C0, 0x300, 0x320, 0x340, 0 }; +struct bus_device *hpp_info[NHPP]; +int hpp_probe(); +void hpp_attach(); +struct bus_driver hppdriver = { + hpp_probe, 0, hpp_attach, 0, hpp_std, "hpp", hpp_info, 0, 0, 0 +}; + + +/* + * ns8390 state. + */ +struct nssoftc hppnssoftc[NHPP]; + + +/* + * hpp state. + */ +struct hppsoftc { + unsigned long rmem_start; /* shmem "recv" start */ + unsigned long rmem_end; /* shmem "recv" end */ + unsigned long mem_start; /* shared mem start */ + unsigned long mem_end; /* shared mem end */ +} hppsoftc[NHPP]; + + +/* + * Probe a list of addresses for the card. + * + */ +int hpp_probe(port, dev) + int port; + struct bus_device *dev; +{ + int unit = dev->unit; + char *str = "hp-plus ethernet board %d out of range.\n"; + caddr_t base = (caddr_t) (dev ? dev->address : 0); + int i; + + if ((unit < 0) || (unit >= NHPP)) { + printf(str, unit); + return(0); + } + + /* Check a single specified location. */ + if (base > (caddr_t) 0x1ff) + return hpp_probe1(dev, base); + else if (base != 0) /* Don't probe at all. */ + return 0; + + for (i = 0; hpp_std[i]; i++) { + int ioaddr = hpp_std[i]; + + if ( ioaddr > 0 && hpp_probe1(dev, ioaddr) ) { + dev->address = ioaddr; + hpp_std[i] = -1; /* Mark address used */ + return(1); + } + } + + return 0; +} + + + +/* + * Do the interesting part of the probe at a single address. + * + */ +int hpp_probe1(dev, ioaddr) + struct bus_device *dev; + int ioaddr; +{ + int i; + u_char checksum = 0; + int mem_start; + + struct hppsoftc *hpp = &hppsoftc[dev->unit]; + struct nssoftc *ns = &hppnssoftc[dev->unit]; + struct ifnet *ifp = &ns->sc_if; + + /* Check for the HP+ signature, 50 48 0x 53. */ + if (inw(ioaddr + HP_ID) != 0x4850 + || (inw(ioaddr + HP_PAGING) & 0xfff0) != 0x5300) + return 0; + + + printf("%s%d: HP PClan plus at %#3x,", dev->name, dev->unit, ioaddr); + /* Retrieve and checksum the station address. */ + outw(ioaddr + HP_PAGING, MAC_Page); + + printf("MAC_Page = %d, ioaddr = %x\n", MAC_Page, ioaddr); + + for(i = 0; i < ETHER_ADDR_LEN; i++) { + u_char inval = inb(ioaddr + 8 + i); + ns->sc_addr[i] = inval; + checksum += inval; + printf(" %2.2x", inval); + } + checksum += inb(ioaddr + 14); + + if (checksum != 0xff) { + printf(" bad checksum %2.2x.\n", checksum); + return 0; + } else { + /* Point at the Software Configuration Flags. */ + outw(ioaddr + HP_PAGING, ID_Page); + printf(" ID %4.4x", inw(ioaddr + 12)); + } + + + /* Read the IRQ line. */ + outw(ioaddr + HP_PAGING, HW_Page); + { + int irq = inb(ioaddr + 13) & 0x0f; + int option = inw(ioaddr + HPP_OPTION); + + dev->sysdep1 = irq; + take_dev_irq(dev); + + if (option & MemEnable) { + mem_start = inw(ioaddr + 9) << 8; + printf(", IRQ %d, memory address %#x.\n", irq, mem_start); + } else { + mem_start = 0; + printf(", IRQ %d, programmed-I/O mode.\n", irq); + } + } + + /* Set the wrap registers for string I/O reads. */ + outw( ioaddr + 14, (HP_START_PG + TX_2X_PAGES) | ((HP_STOP_PG - 1) << 8)); + + /* Set the base address to point to the NIC, not the "real" base! */ + ns->sc_port = ioaddr + HPP_NIC_OFFSET; + + ns->sc_name = dev->name; + ns->sc_unit = dev->unit; + ns->sc_pingpong = 0; /* turn off pingpong mode */ + ns->sc_word16 = 0; /* Agggghhhhh! Debug time: 2 days! */ + ns->sc_txstrtpg = HP_START_PG; + ns->sc_rxstrtpg = HP_START_PG + TX_2X_PAGES; + ns->sc_stoppg = HP_STOP_PG; + + + ns->sc_reset = hpp_reset_8390; + ns->sc_input = hpp_io_block_input; + ns->sc_output = hpp_io_block_output; + + /* Check if the memory_enable flag is set in the option register. */ + if (mem_start) { + ns->sc_input = hpp_mem_block_input; + ns->sc_output = hpp_mem_block_output; + hpp->mem_start = mem_start; + hpp->rmem_start = hpp->mem_start + TX_2X_PAGES * 256; + hpp->mem_end = hpp->rmem_end + = hpp->mem_start + (HP_STOP_PG - HP_START_PG) * 256; + } + + outw(ioaddr + HP_PAGING, Perf_Page); + + /* Leave the 8390 and HP chip reset. */ + outw( ioaddr + HPP_OPTION, inw(ioaddr + HPP_OPTION) & ~EnableIRQ ); + + /* + * Initialize interface header. + */ + ifp->if_unit = dev->unit; + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST; + ifp->if_header_size = sizeof(struct ether_header); + ifp->if_header_format = HDR_ETHERNET; + ifp->if_address_size = ETHER_ADDR_LEN; + ifp->if_address = ns->sc_addr; + if_init_queues(ifp); + + return (1); +} + +/* + * XXX + * + * this routine really should do the invasive part of the setup. + */ +void +hpp_attach(dev) + struct bus_device *dev; +{ + /* NULL */ +} + + + +int +hppopen(dev, flag) + dev_t dev; + int flag; +{ + int s, unit = minor(dev); + struct bus_device *bd; + struct hppsoftc *hpp; + struct nssoftc *ns = &hppnssoftc[unit]; + + int ioaddr = ns->sc_port - HPP_NIC_OFFSET; + int option_reg; + + if (unit < 0 || unit >= NHPP || + (bd = hpp_info[unit]) == 0 || !(bd->alive)) + return ENXIO; + + /* + * Start watchdog. + */ + if (!hppwstart) { + hppwstart++; + timeout(hppwatch, 0, hz); + } + hpp = &hppsoftc[unit]; + ns->sc_if.if_flags |= IFF_UP; + + s = splimp(); + + /* Reset the 8390 and HP chip. */ + option_reg = inw(ioaddr + HPP_OPTION); + outw( ioaddr + HPP_OPTION, option_reg & ~(NICReset + ChipReset) ); + IO_DELAY; IO_DELAY; + + /* Unreset the board and enable interrupts. */ + outw( ioaddr + HPP_OPTION, option_reg | (EnableIRQ + NICReset + ChipReset)); + + /* Set the wrap registers for programmed-I/O operation. */ + outw( ioaddr + HP_PAGING, HW_Page ); + outw( ioaddr + 14, (HP_START_PG + TX_2X_PAGES) | ((HP_STOP_PG - 1) << 8) ); + + /* Select the operational page. */ + outw( ioaddr + HP_PAGING, Perf_Page ); + nsinit(ns); + + splx(s); + + return (0); +} + +/* + * needs to be called at splimp()? + * + */ +void +hpp_reset_8390(ns) + struct nssoftc *ns; +{ + int ioaddr = ns->sc_port - HPP_NIC_OFFSET; + int option_reg = inw(ioaddr + HPP_OPTION); + + outw( ioaddr + HPP_OPTION, option_reg & ~(NICReset + ChipReset) ); + /* Pause a few cycles for the hardware reset to take place. */ + IO_DELAY; + IO_DELAY; + ns->sc_txing = 0; + outw( ioaddr + HPP_OPTION, option_reg | (EnableIRQ + NICReset + ChipReset) ); + + /* + * XXX - I'm not sure there needs to be this many IO_DELAY's... + */ + IO_DELAY; IO_DELAY; + IO_DELAY; IO_DELAY; + + if ((inb_p(ioaddr + HPP_NIC_OFFSET + EN0_ISR) & ENISR_RESET) == 0) + printf("%s: hp_reset_8390() did not complete.\n", ns->sc_name); + + return; +} + + +/* + * Block input and output, similar to the Crynwr packet driver. + * Note that transfer with the EtherTwist+ must be on word boundaries. + */ +void +hpp_io_block_input(ns, count, buf, ring_offset) + struct nssoftc *ns; + int count; + char *buf; + int ring_offset; +{ + int ioaddr = ns->sc_port - HPP_NIC_OFFSET; + + outw(ioaddr + HPP_IN_ADDR, ring_offset); + + insw(ioaddr + HP_DATAPORT, buf, count >> 1 ); + + if (count & 0x01) + buf[count-1] = (char) inw(ioaddr + HP_DATAPORT); + +} + +void +hpp_mem_block_input(ns, count, buf, ring_offset) + struct nssoftc *ns; + int count; + char *buf; + int ring_offset; +{ + int ioaddr = ns->sc_port - HPP_NIC_OFFSET; + int option_reg = inw(ioaddr + HPP_OPTION); + char *mem_start = (char *)phystokv(hppsoftc[ns->sc_unit].mem_start); + + outw(ioaddr + HPP_IN_ADDR, ring_offset); + outw(ioaddr + HPP_OPTION, option_reg & ~(MemDisable + BootROMEnb)); + + /* copy as much as we can straight through */ + bcopy16(mem_start, buf, count & ~1); + + /* Now we copy that last byte. */ + if (count & 0x01) { + u_short savebyte[2]; + + bcopy16(mem_start + (count & ~1), savebyte, 2); + buf[count-1] = savebyte[0]; + } + + outw(ioaddr + HPP_OPTION, option_reg); +} + + +/* + * output data into NIC buffers. + * + * NOTE: All transfers must be on word boundaries. + */ +int +hpp_io_block_output(ns, count, buf, start_page) + struct nssoftc *ns; + int count; + char *buf; + int start_page; +{ + int ioaddr = ns->sc_port - HPP_NIC_OFFSET; + + outw(ioaddr + HPP_OUT_ADDR, start_page << 8) ; + + if (count > 1) { + outsw(ioaddr + HP_DATAPORT, buf, count >> 1); + } + + if ( (count & 1) == 1 ) { + u_char savebyte[2]; + + savebyte[1] = 0; + savebyte[0] = buf[count - 1]; + outw(ioaddr + HP_DATAPORT, *(u_short *)savebyte); + } + + if (count < (ETHERMIN + sizeof( struct ether_header ))) + count = ETHERMIN + sizeof( struct ether_header ); + + + return (count) ; +} + + +/* XXX + * + * I take great pains to not try and bcopy past the end of the buffer, + * does this matter? Are the io request buffers the exact byte size? + */ +int +hpp_mem_block_output(ns, count, buf, start_page ) + struct nssoftc *ns; + int count; + char *buf; + int start_page; +{ + int ioaddr = ns->sc_port - HPP_NIC_OFFSET; + int option_reg = inw(ioaddr + HPP_OPTION); + struct hppsoftc *hpp = &hppsoftc[ns->sc_unit]; + char *shmem; + + outw(ioaddr + HPP_OUT_ADDR, start_page << 8); + outw(ioaddr + HPP_OPTION, option_reg & ~(MemDisable + BootROMEnb)); + + shmem = (char *)phystokv(hpp->mem_start); + bcopy16(buf, shmem, count & ~1); + + if ( (count & 1) == 1 ) { + u_char savebyte[2]; + + savebyte[1] = 0; + savebyte[0] = buf[count - 1]; + bcopy16(savebyte, shmem + (count & ~1), 2); + } + + while (count < ETHERMIN + sizeof(struct ether_header)) { + *(shmem + count) = 0; + count++; + } + + outw(ioaddr + HPP_OPTION, option_reg); + + return count; +} + + +int +hppintr(unit) + int unit; +{ + nsintr(&hppnssoftc[unit]); + + return(0); +} + +void +hppstart(unit) + int unit; +{ + nsstart(&hppnssoftc[unit]); +} + +int hppoutput(); + +int +hppoutput(dev, ior) + dev_t dev; + io_req_t ior; +{ + int unit = minor(dev); + struct bus_device *ui; + + if (unit >= NHPP || (ui = hpp_info[unit]) == 0 || ui->alive == 0) + return (ENXIO); + + return (net_write(&hppnssoftc[unit].sc_if, hppstart, ior)); +} + + +int +hppsetinput(dev, receive_port, priority, filter, filter_count) + dev_t dev; + mach_port_t receive_port; + int priority; + filter_t *filter; + unsigned filter_count; +{ + int unit = minor(dev); + struct bus_device *ui; + + if (unit >= NHPP || (ui = hpp_info[unit]) == 0 || ui->alive == 0) + return (ENXIO); + + return (net_set_filter(&hppnssoftc[unit].sc_if, receive_port, + priority, filter, filter_count)); +} + + +int +hppgetstat(dev, flavor, status, count) + dev_t dev; + int flavor; + dev_status_t status; + unsigned *count; +{ + int unit = minor(dev); + struct bus_device *ui; + + if (unit >= NHPP || (ui = hpp_info[unit]) == 0 || ui->alive == 0) + return (ENXIO); + + return (net_getstat(&hppnssoftc[unit].sc_if, flavor, status, count)); +} + + +int +hppsetstat(dev, flavor, status, count) + dev_t dev; + int flavor; + dev_status_t status; + unsigned count; +{ + int unit = minor(dev), oflags, s; + struct bus_device *ui; + struct ifnet *ifp; + struct net_status *ns; + + if (unit >= NHPP || (ui = hpp_info[unit]) == 0 || ui->alive == 0) + return (ENXIO); + + ifp = &hppnssoftc[unit].sc_if; + + switch (flavor) { + + case NET_STATUS: + if (count < NET_STATUS_COUNT) + return (D_INVALID_SIZE); + ns = (struct net_status *)status; + oflags = ifp->if_flags & (IFF_ALLMULTI|IFF_PROMISC); + ifp->if_flags &= ~(IFF_ALLMULTI|IFF_PROMISC); + ifp->if_flags |= ns->flags & (IFF_ALLMULTI|IFF_PROMISC); + if ((ifp->if_flags & (IFF_ALLMULTI|IFF_PROMISC)) != oflags) { + s = splimp(); + nsinit(&hppnssoftc[unit]); + splx(s); + } + break; + + default: + return (D_INVALID_OPERATION); + } + return (D_SUCCESS); +} + +/* + * Watchdog. + * Check for hung transmissions. + */ +void +hppwatch() +{ + int unit, s; + struct nssoftc *ns; + + timeout(hppwatch, 0, hz); + + s = splimp(); + for (unit = 0; unit < NHPP; unit++) { + if (hpp_info[unit] == 0 || hpp_info[unit]->alive == 0) + continue; + ns = &hppnssoftc[unit]; + if (ns->sc_timer && --ns->sc_timer == 0) { + printf("hpp%d: transmission timeout\n", unit); + (*ns->sc_reset)(ns); + nsinit(ns); + } + } + splx(s); +} + + +#endif /* NHPP > 0 */ + + |