summaryrefslogtreecommitdiff
path: root/i386/i386at/gpl/if_hpp.c
diff options
context:
space:
mode:
Diffstat (limited to 'i386/i386at/gpl/if_hpp.c')
-rw-r--r--i386/i386at/gpl/if_hpp.c690
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 0000000..c177030
--- /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 */
+
+