/* 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 #if NHPP > 0 #include #include "vm_param.h" #include #include #include #include #include #include #include #include #include #include #include #include /* * 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 */