/* * Copyright (c) 1994 Shantanu Goel * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * THE AUTHOR ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. THE AUTHOR DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. */ /* * Written 1993 by Donald Becker. * * Copyright 1993 United States Government as represented by the * Director, National Security Agency. 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@super.org or * C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715 */ #include #include #if NUL > 0 /* * Driver for SMC Ultra ethernet adaptor. * Derived from the Linux driver by Donald Becker. * * Shantanu Goel (goel@cs.columbia.edu) */ #include #include "vm_param.h" #include #include #include #include #include #include #include #include #include #include #include #include #define START_PG 0x00 /* first page of TX buffer */ #define ULTRA_CMDREG 0 /* offset of ASIC command register */ #define ULTRA_RESET 0x80 /* board reset in ULTRA_CMDREG */ #define ULTRA_MEMEN 0x40 /* enable shared memory */ #define ULTRA_NIC_OFF 16 /* NIC register offset */ #define ulunit(dev) minor(dev) /* * Autoconfiguration stuff. */ int ulprobe(); void ulattach(); int ulstd[] = { 0x200, 0x220, 0x240, 0x280, 0x300, 0x340, 0x380, 0 }; struct bus_device *ulinfo[NUL]; struct bus_driver uldriver = { ulprobe, 0, ulattach, 0, ulstd, "ul", ulinfo, 0, 0, 0 }; /* * NS8390 state. */ struct nssoftc ulnssoftc[NUL]; /* * Ultra state. */ struct ulsoftc { int sc_mstart; /* start of board's RAM */ int sc_mend; /* end of board's RAM */ int sc_rmstart; /* start of receive RAM */ int sc_rmend; /* end of receive RAM */ } ulsoftc[NUL]; void ulstart(int); void ul_reset(struct nssoftc *sc); void ul_input(struct nssoftc *sc, int, char *, int); int ul_output(struct nssoftc *sc, int, char *, int); /* * Watchdog. */ int ulwstart = 0; void ulwatch(void); #define ULDEBUG #ifdef ULDEBUG int uldebug = 0; #define DEBUGF(stmt) { if (uldebug) stmt; } #else #define DEBUGF(stmt) #endif /* * Probe for the Ultra. * This looks like an 8013 with the station address PROM * at I/O ports +8 to +13, with a checksum following. */ int ulprobe(xxx, ui) int xxx; struct bus_device *ui; { int *port; if (ui->unit >= NUL) { printf("ul%d: not configured\n", ui->unit); return (0); } for (port = ulstd; *port; port++) { if (*port < 0) continue; /* * Check chip ID nibble. */ if ((inb(*port + 7) & 0xf0) != 0x20) continue; if (ulprobe1(*port, ui)) { ui->address = *port; *port = -1; #if NWD > 0 /* * XXX: The Western Digital/SMC driver can sometimes * probe the Ultra incorrectly. Remove the Ultra's * port from it's list to avoid the problem. */ { int i; extern int wdstd[]; for (i = 0; wdstd[i]; i++) { if (wdstd[i] == ui->address) { wdstd[i] = -1; break; } } } #endif return (1); } } return (0); } int ulprobe1(port, ui) int port; struct bus_device *ui; { u_char num_pages, irqreg, addr, reg4; u_char irqmap[] = { 0, 9, 3, 5, 7, 10, 11, 15 }; short num_pages_tbl[4] = { 0x20, 0x40, 0x80, 0xff }; int i, irq, checksum = 0; int addr_tbl[4] = { 0x0c0000, 0x0e0000, 0xfc0000, 0xfe0000 }; struct ulsoftc *ul = &ulsoftc[ui->unit]; struct nssoftc *ns = &ulnssoftc[ui->unit]; struct ifnet *ifp = &ns->sc_if; /* * Select the station address register set. */ reg4 = inb(port + 4) & 0x7f; outb(port + 4, reg4); for (i = 0; i < 8; i++) checksum += inb(port + 8 + i); if ((checksum & 0xff) != 0xff) return (0); /* * Use 2 transmit buffers. */ ns->sc_pingpong = 1; printf("ul%d: SMC Ultra at 0x%03x, ", ui->unit, port); for (i = 0; i < ETHER_ADDR_LEN; i++) { if (i == 0) printf("%02x", ns->sc_addr[i] = inb(port + 8 + i)); else printf(":%02x", ns->sc_addr[i] = inb(port + 8 + i)); } /* * Switch from station address to alternate register set * and read useful registers there. */ outb(port + 4, 0x80 | reg4); /* * Enable FINE16 mode to avoid BIOS ROM width mismatches * during reboot. */ outb(port + 0x0c, 0x80 | inb(port + 0x0c)); irqreg = inb(port + 0x0d); addr = inb(port + 0x0b); /* * Switch back to station address register set so the MSDOG * driver can find the card after a warm boot. */ outb(port + 4, reg4); /* * Determine IRQ. The IRQ bits are split. */ irq = irqmap[((irqreg & 0x40) >> 4) + ((irqreg & 0x0c) >> 2)]; if (irq == 0) { printf(", failed to detect IRQ line.\n"); return (0); } ui->sysdep1 = irq; take_dev_irq(ui); printf(", irq %d", irq); /* * Determine board's RAM location. */ ul->sc_mstart = ((addr & 0x0f) << 13) + addr_tbl[(addr >> 6) & 3]; num_pages = num_pages_tbl[(addr >> 4) & 3]; ul->sc_rmstart = ul->sc_mstart + TX_PAGES(ns) * 256; ul->sc_mend = ul->sc_rmend = ul->sc_mstart + (num_pages - START_PG) * 256; printf(", memory 0x%05x-0x%05x\n", ul->sc_mstart, ul->sc_mend); /* * Initialize 8390 state. */ ns->sc_name = ui->name; ns->sc_unit = ui->unit; ns->sc_port = port + ULTRA_NIC_OFF; ns->sc_word16 = 1; ns->sc_txstrtpg = START_PG; ns->sc_rxstrtpg = START_PG + TX_PAGES(ns); ns->sc_stoppg = num_pages; ns->sc_reset = ul_reset; ns->sc_input = ul_input; ns->sc_output = ul_output; DEBUGF(printf("ul%d: txstrtpg %d rxstrtpg %d num_pages %d\n", ui->unit, ns->sc_txstrtpg, ns->sc_rxstrtpg, num_pages)); /* * Initialize interface header. */ ifp->if_unit = ui->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); } void ulattach(ui) struct bus_device *ui; { /* * void */ } int ulopen(dev, flag) dev_t dev; int flag; { int unit = ulunit(dev), s; struct bus_device *ui; if (unit >= NUL || (ui = ulinfo[unit]) == 0 || ui->alive == 0) return (ENXIO); /* * Start watchdog. */ if (!ulwstart) { ulwstart++; timeout(ulwatch, 0, hz); } ulnssoftc[unit].sc_if.if_flags |= IFF_UP; s = splimp(); outb(ui->address, ULTRA_MEMEN); /* enable memory, 16 bit mode */ outb(ui->address + 5, 0x80); outb(ui->address + 6, 0x01); /* enable interrupts and memory */ nsinit(&ulnssoftc[unit]); splx(s); return (0); } int uloutput(dev, ior) dev_t dev; io_req_t ior; { int unit = ulunit(dev); struct bus_device *ui; if (unit >= NUL || (ui = ulinfo[unit]) == 0 || ui->alive == 0) return (ENXIO); return (net_write(&ulnssoftc[unit].sc_if, ulstart, ior)); } int ulsetinput(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 = ulunit(dev); struct bus_device *ui; if (unit >= NUL || (ui = ulinfo[unit]) == 0 || ui->alive == 0) return (ENXIO); return (net_set_filter(&ulnssoftc[unit].sc_if, receive_port, priority, filter, filter_count)); } int ulgetstat(dev, flavor, status, count) dev_t dev; int flavor; dev_status_t status; unsigned *count; { int unit = ulunit(dev); struct bus_device *ui; if (unit >= NUL || (ui = ulinfo[unit]) == 0 || ui->alive == 0) return (ENXIO); return (net_getstat(&ulnssoftc[unit].sc_if, flavor, status, count)); } int ulsetstat(dev, flavor, status, count) dev_t dev; int flavor; dev_status_t status; unsigned count; { int unit = ulunit(dev), oflags, s; struct bus_device *ui; struct ifnet *ifp; struct net_status *ns; if (unit >= NUL || (ui = ulinfo[unit]) == 0 || ui->alive == 0) return (ENXIO); ifp = &ulnssoftc[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(&ulnssoftc[unit]); splx(s); } break; default: return (D_INVALID_OPERATION); } return (D_SUCCESS); } void ulintr(unit) int unit; { nsintr(&ulnssoftc[unit]); } void ulstart(unit) int unit; { nsstart(&ulnssoftc[unit]); } void ul_reset(ns) struct nssoftc *ns; { int port = ns->sc_port - ULTRA_NIC_OFF; /* ASIC base address */ outb(port, ULTRA_RESET); outb(0x80, 0); /* I/O delay */ outb(port, ULTRA_MEMEN); } void ul_input(ns, count, buf, ring_offset) struct nssoftc *ns; int count; char *buf; int ring_offset; { int xfer_start; struct ulsoftc *ul = &ulsoftc[ns->sc_unit]; DEBUGF(printf("ul%d: ring_offset = %d\n", ns->sc_unit, ring_offset)); xfer_start = ul->sc_mstart + ring_offset - (START_PG << 8); if (xfer_start + count > ul->sc_rmend) { int semi_count = ul->sc_rmend - xfer_start; /* * Input move must be wrapped. */ bcopy((char *)phystokv(xfer_start), buf, semi_count); count -= semi_count; bcopy((char *)phystokv(ul->sc_rmstart), buf+semi_count, count); } else bcopy((char *)phystokv(xfer_start), buf, count); } int ul_output(ns, count, buf, start_page) struct nssoftc *ns; int count; char *buf; int start_page; { char *shmem; int i; struct ulsoftc *ul = &ulsoftc[ns->sc_unit]; DEBUGF(printf("ul%d: start_page = %d\n", ns->sc_unit, start_page)); shmem = (char *)phystokv(ul->sc_mstart + ((start_page-START_PG) << 8)); bcopy(buf, shmem, count); while (count < ETHERMIN + sizeof(struct ether_header)) { *(shmem + count) = 0; count++; } return (count); } /* * Watchdog. * Check for hung transmissions. */ void ulwatch() { int unit, s; struct nssoftc *ns; timeout(ulwatch, 0, hz); s = splimp(); for (unit = 0; unit < NUL; unit++) { if (ulinfo[unit] == 0 || ulinfo[unit]->alive == 0) continue; ns = &ulnssoftc[unit]; if (ns->sc_timer && --ns->sc_timer == 0) { printf("ul%d: transmission timeout\n", unit); nsinit(ns); } } splx(s); } #endif /* NUL > 0 */