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_ul.c |
Initial source
Diffstat (limited to 'i386/i386at/gpl/if_ul.c')
-rw-r--r-- | i386/i386at/gpl/if_ul.c | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/i386/i386at/gpl/if_ul.c b/i386/i386at/gpl/if_ul.c new file mode 100644 index 0000000..59e74f5 --- /dev/null +++ b/i386/i386at/gpl/if_ul.c @@ -0,0 +1,489 @@ +/* + * 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 <wd.h> +#include <ul.h> +#if NUL > 0 +/* + * Driver for SMC Ultra ethernet adaptor. + * Derived from the Linux driver by Donald Becker. + * + * Shantanu Goel (goel@cs.columbia.edu) + */ +#include <mach/sa/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/machspl.h> +#include <i386/pio.h> +#include <i386at/gpl/if_nsreg.h> + +#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 <base>+8 to <base>+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 */ |