diff --git a/sys/dev/ed/if_ed.c b/sys/dev/ed/if_ed.c index 6bed04eb51ae..95a48599def5 100644 --- a/sys/dev/ed/if_ed.c +++ b/sys/dev/ed/if_ed.c @@ -1,3441 +1,3441 @@ /* * Copyright (c) 1995, David Greenman * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: if_ed.c,v 1.109 1996/12/03 16:08:00 phk Exp $ + * $Id: if_ed.c,v 1.110 1996/12/10 07:29:39 davidg Exp $ */ /* * Device driver for National Semiconductor DS8390/WD83C690 based ethernet * adapters. By David Greenman, 29-April-1993 * * Currently supports the Western Digital/SMC 8003 and 8013 series, * the SMC Elite Ultra (8216), the 3Com 3c503, the NE1000 and NE2000, * and a variety of similar clones. * */ #include "ed.h" #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #ifdef IPX #include #include #endif #ifdef NS #include #include #endif #if NBPFILTER > 0 #include #include #endif #include #include #include #include #include /* * ed_softc: per line info and status */ struct ed_softc { struct arpcom arpcom; /* ethernet common */ char *type_str; /* pointer to type string */ u_char vendor; /* interface vendor */ u_char type; /* interface type code */ u_char gone; /* HW missing, presumed having a good time */ u_short asic_addr; /* ASIC I/O bus address */ u_short nic_addr; /* NIC (DS8390) I/O bus address */ /* * The following 'proto' variable is part of a work-around for 8013EBT asics * being write-only. It's sort of a prototype/shadow of the real thing. */ u_char wd_laar_proto; u_char cr_proto; u_char isa16bit; /* width of access to card 0=8 or 1=16 */ int is790; /* set by the probe code if the card is 790 * based */ /* * HP PC LAN PLUS card support. */ u_short hpp_options; /* flags controlling behaviour of the HP card */ u_short hpp_id; /* software revision and other fields */ caddr_t hpp_mem_start; /* Memory-mapped IO register address */ caddr_t mem_start; /* NIC memory start address */ caddr_t mem_end; /* NIC memory end address */ u_long mem_size; /* total NIC memory size */ caddr_t mem_ring; /* start of RX ring-buffer (in NIC mem) */ u_char mem_shared; /* NIC memory is shared with host */ u_char xmit_busy; /* transmitter is busy */ u_char txb_cnt; /* number of transmit buffers */ u_char txb_inuse; /* number of TX buffers currently in-use */ u_char txb_new; /* pointer to where new buffer will be added */ u_char txb_next_tx; /* pointer to next buffer ready to xmit */ u_short txb_len[8]; /* buffered xmit buffer lengths */ u_char tx_page_start; /* first page of TX buffer area */ u_char rec_page_start; /* first page of RX ring-buffer */ u_char rec_page_stop; /* last page of RX ring-buffer */ u_char next_packet; /* pointer to next unread RX packet */ struct ifmib_iso_8802_3 mibdata; /* stuff for network mgmt */ }; static struct ed_softc ed_softc[NED]; static int ed_attach __P((struct ed_softc *, int, int)); static int ed_attach_isa __P((struct isa_device *)); static void ed_init __P((void *)); static int ed_ioctl __P((struct ifnet *, int, caddr_t)); static int ed_probe __P((struct isa_device *)); static void ed_start __P((struct ifnet *)); static void ed_reset __P((struct ifnet *)); static void ed_watchdog __P((struct ifnet *)); static void ed_stop __P((struct ed_softc *)); static int ed_probe_generic8390 __P((struct ed_softc *)); static int ed_probe_WD80x3 __P((struct isa_device *)); static int ed_probe_3Com __P((struct isa_device *)); static int ed_probe_Novell __P((struct isa_device *)); static int ed_probe_Novell_generic __P((struct ed_softc *, int, int, int)); static int ed_probe_HP_pclanp __P((struct isa_device *)); #include "pci.h" #if NPCI > 0 void *ed_attach_NE2000_pci __P((int, int)); #endif #include "crd.h" #if NCRD > 0 static int ed_probe_pccard __P((struct isa_device *, u_char *)); #endif static void ds_getmcaf __P((struct ed_softc *, u_long *)); static void ed_get_packet(struct ed_softc *, char *, /* u_short */ int, int); static void ed_rint __P((struct ed_softc *)); static void ed_xmit __P((struct ed_softc *)); static char * ed_ring_copy __P((struct ed_softc *, char *, char *, /* u_short */ int)); static void ed_hpp_set_physical_link __P((struct ed_softc *)); static void ed_hpp_readmem __P((struct ed_softc *, int, unsigned char *, /* u_short */ int)); static u_short ed_hpp_write_mbufs __P((struct ed_softc *, struct mbuf *, int)); static void ed_pio_readmem __P((struct ed_softc *, int, unsigned char *, /* u_short */ int)); static void ed_pio_writemem __P((struct ed_softc *, char *, /* u_short */ int, /* u_short */ int)); static u_short ed_pio_write_mbufs __P((struct ed_softc *, struct mbuf *, int)); void edintr_sc __P((struct ed_softc *)); static void ed_setrcr(struct ed_softc *); static u_long ds_crc(u_char *ep); #if NCRD > 0 #include #include #include #include /* * PC-Card (PCMCIA) specific code. */ static int card_intr(struct pccard_dev *); /* Interrupt handler */ static void edunload(struct pccard_dev *); /* Disable driver */ static void edsuspend(struct pccard_dev *); /* Suspend driver */ static int edinit(struct pccard_dev *, int); /* init device */ static struct pccard_drv ed_info = { "ed", card_intr, edunload, edsuspend, edinit, 0, /* Attributes - presently unused */ &net_imask /* Interrupt mask for device */ /* XXX - Should this also include net_imask? */ }; /* * Called when a power down is requested. Shuts down the * device and configures the device as unavailable (but * still loaded...). A resume is done by calling * edinit with first=0. This is called when the user suspends * the system, or the APM code suspends the system. */ static void edsuspend(struct pccard_dev *dp) { struct ed_softc *sc = &ed_softc[dp->isahd.id_unit]; /* * Some 'ed' cards will generate a interrupt as they go away, * and by the time the interrupt handler gets to the card, * the interrupt can't be cleared. * By setting gone here, we tell the handler to ignore the * interrupt when it happens. */ sc->gone = 1; /* avoid spinning endlessly in interrupt handler */ printf("ed%d: suspending\n", dp->isahd.id_unit); } /* * Initialize the device - called from Slot manager. * If first is set, then check for the device's existence * before initializing it. Once initialized, the device table may * be set up. */ static int edinit(struct pccard_dev *dp, int first) { struct ed_softc *sc = &ed_softc[dp->isahd.id_unit]; /* validate unit number. */ if (first) { if (dp->isahd.id_unit >= NED) return(ENODEV); /* * Probe the device. If a value is returned, the * device was found at the location. */ sc->gone = 0; if (ed_probe_pccard(&dp->isahd,dp->misc)==0) return(ENXIO); if (ed_attach_isa(&dp->isahd)==0) return(ENXIO); } else { sc->gone = 0; /* reenable after a suspend */ } /* * XXX TODO: * If it was initialized before, the device structure * should also be initialized. We should * reset (and possibly restart) the hardware, but * I am not sure of the best way to do this... */ return(0); } /* * edunload - unload the driver and clear the table. * XXX TODO: * This is usually called when the card is ejected, but * can be caused by a modunload of a controller driver. * The idea is to reset the driver's view of the device * and ensure that any driver entry points such as * read and write do not hang. */ static void edunload(struct pccard_dev *dp) { struct ed_softc *sc = &ed_softc[dp->isahd.id_unit]; struct ifnet *ifp = &sc->arpcom.ac_if; if (sc->gone) { printf("ed%d: already unloaded\n", dp->isahd.id_unit); return; } ifp->if_flags &= ~IFF_RUNNING; if_down(ifp); sc->gone = 1; printf("ed%d: unload\n", dp->isahd.id_unit); } /* * card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int card_intr(struct pccard_dev *dp) { edintr_sc(&ed_softc[dp->isahd.id_unit]); return(1); } #endif /* NCRD > 0 */ struct isa_driver eddriver = { ed_probe, ed_attach_isa, "ed", 1 /* We are ultra sensitive */ }; /* * Interrupt conversion table for WD/SMC ASIC/83C584 * (IRQ* are defined in icu.h) */ static unsigned short ed_intr_mask[] = { IRQ9, IRQ3, IRQ5, IRQ7, IRQ10, IRQ11, IRQ15, IRQ4 }; /* * Interrupt conversion table for 83C790 */ static unsigned short ed_790_intr_mask[] = { 0, IRQ9, IRQ3, IRQ5, IRQ7, IRQ10, IRQ11, IRQ15 }; /* * Interrupt conversion table for the HP PC LAN+ */ static unsigned short ed_hpp_intr_mask[] = { 0, /* 0 */ 0, /* 1 */ 0, /* 2 */ IRQ3, /* 3 */ IRQ4, /* 4 */ IRQ5, /* 5 */ IRQ6, /* 6 */ IRQ7, /* 7 */ 0, /* 8 */ IRQ9, /* 9 */ IRQ10, /* 10 */ IRQ11, /* 11 */ IRQ12, /* 12 */ 0, /* 13 */ 0, /* 14 */ IRQ15 /* 15 */ }; /* * Determine if the device is present * * on entry: * a pointer to an isa_device struct * on exit: * NULL if device not found * or # of i/o addresses used (if found) */ static int ed_probe(isa_dev) struct isa_device *isa_dev; { int nports; #if NCRD > 0 /* * If PC-Card probe required, then register driver with * slot manager. */ pccard_add_driver(&ed_info); #endif nports = ed_probe_WD80x3(isa_dev); if (nports) return (nports); nports = ed_probe_3Com(isa_dev); if (nports) return (nports); nports = ed_probe_Novell(isa_dev); if (nports) return (nports); nports = ed_probe_HP_pclanp(isa_dev); if (nports) return (nports); return (0); } /* * Generic probe routine for testing for the existance of a DS8390. * Must be called after the NIC has just been reset. This routine * works by looking at certain register values that are guaranteed * to be initialized a certain way after power-up or reset. Seems * not to currently work on the 83C690. * * Specifically: * * Register reset bits set bits * Command Register (CR) TXP, STA RD2, STP * Interrupt Status (ISR) RST * Interrupt Mask (IMR) All bits * Data Control (DCR) LAS * Transmit Config. (TCR) LB1, LB0 * * We only look at the CR and ISR registers, however, because looking at * the others would require changing register pages (which would be * intrusive if this isn't an 8390). * * Return 1 if 8390 was found, 0 if not. */ static int ed_probe_generic8390(sc) struct ed_softc *sc; { if ((inb(sc->nic_addr + ED_P0_CR) & (ED_CR_RD2 | ED_CR_TXP | ED_CR_STA | ED_CR_STP)) != (ED_CR_RD2 | ED_CR_STP)) return (0); if ((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST) != ED_ISR_RST) return (0); return (1); } /* * Probe and vendor-specific initialization routine for SMC/WD80x3 boards */ static int ed_probe_WD80x3(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; int i; u_int memsize; u_char iptr, isa16bit, sum; sc->asic_addr = isa_dev->id_iobase; sc->nic_addr = sc->asic_addr + ED_WD_NIC_OFFSET; sc->is790 = 0; #ifdef TOSH_ETHER outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_POW); DELAY(10000); #endif /* * Attempt to do a checksum over the station address PROM. If it * fails, it's probably not a SMC/WD board. There is a problem with * this, though: some clone WD boards don't pass the checksum test. * Danpex boards for one. */ for (sum = 0, i = 0; i < 8; ++i) sum += inb(sc->asic_addr + ED_WD_PROM + i); if (sum != ED_WD_ROM_CHECKSUM_TOTAL) { /* * Checksum is invalid. This often happens with cheap WD8003E * clones. In this case, the checksum byte (the eighth byte) * seems to always be zero. */ if (inb(sc->asic_addr + ED_WD_CARD_ID) != ED_TYPE_WD8003E || inb(sc->asic_addr + ED_WD_PROM + 7) != 0) return (0); } /* reset card to force it into a known state. */ #ifdef TOSH_ETHER outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_RST | ED_WD_MSR_POW); #else outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_RST); #endif DELAY(100); outb(sc->asic_addr + ED_WD_MSR, inb(sc->asic_addr + ED_WD_MSR) & ~ED_WD_MSR_RST); /* wait in the case this card is reading it's EEROM */ DELAY(5000); sc->vendor = ED_VENDOR_WD_SMC; sc->type = inb(sc->asic_addr + ED_WD_CARD_ID); /* * Set initial values for width/size. */ memsize = 8192; isa16bit = 0; switch (sc->type) { case ED_TYPE_WD8003S: sc->type_str = "WD8003S"; break; case ED_TYPE_WD8003E: sc->type_str = "WD8003E"; break; case ED_TYPE_WD8003EB: sc->type_str = "WD8003EB"; break; case ED_TYPE_WD8003W: sc->type_str = "WD8003W"; break; case ED_TYPE_WD8013EBT: sc->type_str = "WD8013EBT"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013W: sc->type_str = "WD8013W"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013EP: /* also WD8003EP */ if (inb(sc->asic_addr + ED_WD_ICR) & ED_WD_ICR_16BIT) { isa16bit = 1; memsize = 16384; sc->type_str = "WD8013EP"; } else { sc->type_str = "WD8003EP"; } break; case ED_TYPE_WD8013WC: sc->type_str = "WD8013WC"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013EBP: sc->type_str = "WD8013EBP"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013EPC: sc->type_str = "WD8013EPC"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_SMC8216C: /* 8216 has 16K shared mem -- 8416 has 8K */ case ED_TYPE_SMC8216T: if (sc->type == ED_TYPE_SMC8216C) { sc->type_str = "SMC8216/SMC8216C"; } else { sc->type_str = "SMC8216T"; } outb(sc->asic_addr + ED_WD790_HWR, inb(sc->asic_addr + ED_WD790_HWR) | ED_WD790_HWR_SWH); switch (inb(sc->asic_addr + ED_WD790_RAR) & ED_WD790_RAR_SZ64) { case ED_WD790_RAR_SZ64: memsize = 65536; break; case ED_WD790_RAR_SZ32: memsize = 32768; break; case ED_WD790_RAR_SZ16: memsize = 16384; break; case ED_WD790_RAR_SZ8: /* 8216 has 16K shared mem -- 8416 has 8K */ if (sc->type == ED_TYPE_SMC8216C) { sc->type_str = "SMC8416C/SMC8416BT"; } else { sc->type_str = "SMC8416T"; } memsize = 8192; break; } outb(sc->asic_addr + ED_WD790_HWR, inb(sc->asic_addr + ED_WD790_HWR) & ~ED_WD790_HWR_SWH); isa16bit = 1; sc->is790 = 1; break; #ifdef TOSH_ETHER case ED_TYPE_TOSHIBA1: sc->type_str = "Toshiba1"; memsize = 32768; isa16bit = 1; break; case ED_TYPE_TOSHIBA4: sc->type_str = "Toshiba4"; memsize = 32768; isa16bit = 1; break; #endif default: sc->type_str = ""; break; } /* * Make some adjustments to initial values depending on what is found * in the ICR. */ if (isa16bit && (sc->type != ED_TYPE_WD8013EBT) #ifdef TOSH_ETHER && (sc->type != ED_TYPE_TOSHIBA1) && (sc->type != ED_TYPE_TOSHIBA4) #endif && ((inb(sc->asic_addr + ED_WD_ICR) & ED_WD_ICR_16BIT) == 0)) { isa16bit = 0; memsize = 8192; } #if ED_DEBUG printf("type = %x type_str=%s isa16bit=%d memsize=%d id_msize=%d\n", sc->type, sc->type_str, isa16bit, memsize, isa_dev->id_msize); for (i = 0; i < 8; i++) printf("%x -> %x\n", i, inb(sc->asic_addr + i)); #endif /* * Allow the user to override the autoconfiguration */ if (isa_dev->id_msize) memsize = isa_dev->id_msize; /* * (note that if the user specifies both of the following flags that * '8bit' mode intentionally has precedence) */ if (isa_dev->id_flags & ED_FLAGS_FORCE_16BIT_MODE) isa16bit = 1; if (isa_dev->id_flags & ED_FLAGS_FORCE_8BIT_MODE) isa16bit = 0; /* * If possible, get the assigned interrupt number from the card and * use it. */ if ((sc->type & ED_WD_SOFTCONFIG) && (!sc->is790)) { /* * Assemble together the encoded interrupt number. */ iptr = (inb(isa_dev->id_iobase + ED_WD_ICR) & ED_WD_ICR_IR2) | ((inb(isa_dev->id_iobase + ED_WD_IRR) & (ED_WD_IRR_IR0 | ED_WD_IRR_IR1)) >> 5); /* * If no interrupt specified (or "?"), use what the board tells us. */ if (isa_dev->id_irq <= 0) isa_dev->id_irq = ed_intr_mask[iptr]; /* * Enable the interrupt. */ outb(isa_dev->id_iobase + ED_WD_IRR, inb(isa_dev->id_iobase + ED_WD_IRR) | ED_WD_IRR_IEN); } if (sc->is790) { outb(isa_dev->id_iobase + ED_WD790_HWR, inb(isa_dev->id_iobase + ED_WD790_HWR) | ED_WD790_HWR_SWH); iptr = (((inb(isa_dev->id_iobase + ED_WD790_GCR) & ED_WD790_GCR_IR2) >> 4) | (inb(isa_dev->id_iobase + ED_WD790_GCR) & (ED_WD790_GCR_IR1 | ED_WD790_GCR_IR0)) >> 2); outb(isa_dev->id_iobase + ED_WD790_HWR, inb(isa_dev->id_iobase + ED_WD790_HWR) & ~ED_WD790_HWR_SWH); /* * If no interrupt specified (or "?"), use what the board tells us. */ if (isa_dev->id_irq <= 0) isa_dev->id_irq = ed_790_intr_mask[iptr]; /* * Enable interrupts. */ outb(isa_dev->id_iobase + ED_WD790_ICR, inb(isa_dev->id_iobase + ED_WD790_ICR) | ED_WD790_ICR_EIL); } if (isa_dev->id_irq <= 0) { printf("ed%d: %s cards don't support auto-detected/assigned interrupts.\n", isa_dev->id_unit, sc->type_str); return (0); } sc->isa16bit = isa16bit; sc->mem_shared = 1; isa_dev->id_msize = memsize; sc->mem_start = (caddr_t) isa_dev->id_maddr; /* * allocate one xmit buffer if < 16k, two buffers otherwise */ if ((memsize < 16384) || (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING)) { sc->txb_cnt = 1; } else { sc->txb_cnt = 2; } sc->tx_page_start = ED_WD_PAGE_OFFSET; sc->rec_page_start = ED_WD_PAGE_OFFSET + ED_TXBUF_SIZE * sc->txb_cnt; sc->rec_page_stop = ED_WD_PAGE_OFFSET + memsize / ED_PAGE_SIZE; sc->mem_ring = sc->mem_start + (ED_PAGE_SIZE * sc->rec_page_start); sc->mem_size = memsize; sc->mem_end = sc->mem_start + memsize; /* * Get station address from on-board ROM */ for (i = 0; i < ETHER_ADDR_LEN; ++i) sc->arpcom.ac_enaddr[i] = inb(sc->asic_addr + ED_WD_PROM + i); /* * Set upper address bits and 8/16 bit access to shared memory */ if (isa16bit) { if (sc->is790) { sc->wd_laar_proto = inb(sc->asic_addr + ED_WD_LAAR); outb(sc->asic_addr + ED_WD_LAAR, ED_WD_LAAR_M16EN); } else { outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto = ED_WD_LAAR_L16EN | ED_WD_LAAR_M16EN | ((kvtop(sc->mem_start) >> 19) & ED_WD_LAAR_ADDRHI))); } } else { if (((sc->type & ED_WD_SOFTCONFIG) || #ifdef TOSH_ETHER (sc->type == ED_TYPE_TOSHIBA1) || (sc->type == ED_TYPE_TOSHIBA4) || #endif (sc->type == ED_TYPE_WD8013EBT)) && (!sc->is790)) { outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto = ((kvtop(sc->mem_start) >> 19) & ED_WD_LAAR_ADDRHI))); } } /* * Set address and enable interface shared memory. */ if (!sc->is790) { #ifdef TOSH_ETHER outb(sc->asic_addr + ED_WD_MSR + 1, ((kvtop(sc->mem_start) >> 8) & 0xe0) | 4); outb(sc->asic_addr + ED_WD_MSR + 2, ((kvtop(sc->mem_start) >> 16) & 0x0f)); outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB | ED_WD_MSR_POW); #else outb(sc->asic_addr + ED_WD_MSR, ((kvtop(sc->mem_start) >> 13) & ED_WD_MSR_ADDR) | ED_WD_MSR_MENB); #endif sc->cr_proto = ED_CR_RD2; } else { outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB); outb(sc->asic_addr + ED_WD790_HWR, (inb(sc->asic_addr + ED_WD790_HWR) | ED_WD790_HWR_SWH)); outb(sc->asic_addr + ED_WD790_RAR, ((kvtop(sc->mem_start) >> 13) & 0x0f) | ((kvtop(sc->mem_start) >> 11) & 0x40) | (inb(sc->asic_addr + ED_WD790_RAR) & 0xb0)); outb(sc->asic_addr + ED_WD790_HWR, (inb(sc->asic_addr + ED_WD790_HWR) & ~ED_WD790_HWR_SWH)); sc->cr_proto = 0; } #if 0 printf("starting memory performance test at 0x%x, size %d...\n", sc->mem_start, memsize*16384); for (i = 0; i < 16384; i++) bzero(sc->mem_start, memsize); printf("***DONE***\n"); #endif /* * Now zero memory and verify that it is clear */ bzero(sc->mem_start, memsize); for (i = 0; i < memsize; ++i) { if (sc->mem_start[i]) { printf("ed%d: failed to clear shared memory at %lx - check configuration\n", isa_dev->id_unit, kvtop(sc->mem_start + i)); /* * Disable 16 bit access to shared memory */ if (isa16bit) { if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto &= ~ED_WD_LAAR_M16EN)); } return (0); } } /* * Disable 16bit access to shared memory - we leave it * disabled so that 1) machines reboot properly when the board * is set 16 bit mode and there are conflicting 8bit * devices/ROMS in the same 128k address space as this boards * shared memory. and 2) so that other 8 bit devices with * shared memory can be used in this 128k region, too. */ if (isa16bit) { if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto &= ~ED_WD_LAAR_M16EN)); } return (ED_WD_IO_PORTS); } /* * Probe and vendor-specific initialization routine for 3Com 3c503 boards */ static int ed_probe_3Com(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; int i; u_int memsize; u_char isa16bit; sc->asic_addr = isa_dev->id_iobase + ED_3COM_ASIC_OFFSET; sc->nic_addr = isa_dev->id_iobase + ED_3COM_NIC_OFFSET; /* * Verify that the kernel configured I/O address matches the board * configured address */ switch (inb(sc->asic_addr + ED_3COM_BCFR)) { case ED_3COM_BCFR_300: if (isa_dev->id_iobase != 0x300) return (0); break; case ED_3COM_BCFR_310: if (isa_dev->id_iobase != 0x310) return (0); break; case ED_3COM_BCFR_330: if (isa_dev->id_iobase != 0x330) return (0); break; case ED_3COM_BCFR_350: if (isa_dev->id_iobase != 0x350) return (0); break; case ED_3COM_BCFR_250: if (isa_dev->id_iobase != 0x250) return (0); break; case ED_3COM_BCFR_280: if (isa_dev->id_iobase != 0x280) return (0); break; case ED_3COM_BCFR_2A0: if (isa_dev->id_iobase != 0x2a0) return (0); break; case ED_3COM_BCFR_2E0: if (isa_dev->id_iobase != 0x2e0) return (0); break; default: return (0); } /* * Verify that the kernel shared memory address matches the board * configured address. */ switch (inb(sc->asic_addr + ED_3COM_PCFR)) { case ED_3COM_PCFR_DC000: if (kvtop(isa_dev->id_maddr) != 0xdc000) return (0); break; case ED_3COM_PCFR_D8000: if (kvtop(isa_dev->id_maddr) != 0xd8000) return (0); break; case ED_3COM_PCFR_CC000: if (kvtop(isa_dev->id_maddr) != 0xcc000) return (0); break; case ED_3COM_PCFR_C8000: if (kvtop(isa_dev->id_maddr) != 0xc8000) return (0); break; default: return (0); } /* * Reset NIC and ASIC. Enable on-board transceiver throughout reset * sequence because it'll lock up if the cable isn't connected if we * don't. */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_RST | ED_3COM_CR_XSEL); /* * Wait for a while, then un-reset it */ DELAY(50); /* * The 3Com ASIC defaults to rather strange settings for the CR after * a reset - it's important to set it again after the following outb * (this is done when we map the PROM below). */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); /* * Wait a bit for the NIC to recover from the reset */ DELAY(5000); sc->vendor = ED_VENDOR_3COM; sc->type_str = "3c503"; sc->mem_shared = 1; sc->cr_proto = ED_CR_RD2; /* * Hmmm...a 16bit 3Com board has 16k of memory, but only an 8k window * to it. */ memsize = 8192; /* * Get station address from on-board ROM */ /* * First, map ethernet address PROM over the top of where the NIC * registers normally appear. */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_EALO | ED_3COM_CR_XSEL); for (i = 0; i < ETHER_ADDR_LEN; ++i) sc->arpcom.ac_enaddr[i] = inb(sc->nic_addr + i); /* * Unmap PROM - select NIC registers. The proper setting of the * tranceiver is set in ed_init so that the attach code is given a * chance to set the default based on a compile-time config option */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); /* * Determine if this is an 8bit or 16bit board */ /* * select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); /* * Attempt to clear WTS bit. If it doesn't clear, then this is a 16bit * board. */ outb(sc->nic_addr + ED_P0_DCR, 0); /* * select page 2 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_2 | ED_CR_RD2 | ED_CR_STP); /* * The 3c503 forces the WTS bit to a one if this is a 16bit board */ if (inb(sc->nic_addr + ED_P2_DCR) & ED_DCR_WTS) isa16bit = 1; else isa16bit = 0; /* * select page 0 registers */ outb(sc->nic_addr + ED_P2_CR, ED_CR_RD2 | ED_CR_STP); sc->mem_start = (caddr_t) isa_dev->id_maddr; sc->mem_size = memsize; sc->mem_end = sc->mem_start + memsize; /* * We have an entire 8k window to put the transmit buffers on the * 16bit boards. But since the 16bit 3c503's shared memory is only * fast enough to overlap the loading of one full-size packet, trying * to load more than 2 buffers can actually leave the transmitter idle * during the load. So 2 seems the best value. (Although a mix of * variable-sized packets might change this assumption. Nonetheless, * we optimize for linear transfers of same-size packets.) */ if (isa16bit) { if (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING) sc->txb_cnt = 1; else sc->txb_cnt = 2; sc->tx_page_start = ED_3COM_TX_PAGE_OFFSET_16BIT; sc->rec_page_start = ED_3COM_RX_PAGE_OFFSET_16BIT; sc->rec_page_stop = memsize / ED_PAGE_SIZE + ED_3COM_RX_PAGE_OFFSET_16BIT; sc->mem_ring = sc->mem_start; } else { sc->txb_cnt = 1; sc->tx_page_start = ED_3COM_TX_PAGE_OFFSET_8BIT; sc->rec_page_start = ED_TXBUF_SIZE + ED_3COM_TX_PAGE_OFFSET_8BIT; sc->rec_page_stop = memsize / ED_PAGE_SIZE + ED_3COM_TX_PAGE_OFFSET_8BIT; sc->mem_ring = sc->mem_start + (ED_PAGE_SIZE * ED_TXBUF_SIZE); } sc->isa16bit = isa16bit; /* * Initialize GA page start/stop registers. Probably only needed if * doing DMA, but what the hell. */ outb(sc->asic_addr + ED_3COM_PSTR, sc->rec_page_start); outb(sc->asic_addr + ED_3COM_PSPR, sc->rec_page_stop); /* * Set IRQ. 3c503 only allows a choice of irq 2-5. */ switch (isa_dev->id_irq) { case IRQ2: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ2); break; case IRQ3: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ3); break; case IRQ4: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ4); break; case IRQ5: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ5); break; default: printf("ed%d: Invalid irq configuration (%d) must be 3-5,9 for 3c503\n", isa_dev->id_unit, ffs(isa_dev->id_irq) - 1); return (0); } /* * Initialize GA configuration register. Set bank and enable shared * mem. */ outb(sc->asic_addr + ED_3COM_GACFR, ED_3COM_GACFR_RSEL | ED_3COM_GACFR_MBS0); /* * Initialize "Vector Pointer" registers. These gawd-awful things are * compared to 20 bits of the address on ISA, and if they match, the * shared memory is disabled. We set them to 0xffff0...allegedly the * reset vector. */ outb(sc->asic_addr + ED_3COM_VPTR2, 0xff); outb(sc->asic_addr + ED_3COM_VPTR1, 0xff); outb(sc->asic_addr + ED_3COM_VPTR0, 0x00); /* * Zero memory and verify that it is clear */ bzero(sc->mem_start, memsize); for (i = 0; i < memsize; ++i) if (sc->mem_start[i]) { printf("ed%d: failed to clear shared memory at %lx - check configuration\n", isa_dev->id_unit, kvtop(sc->mem_start + i)); return (0); } isa_dev->id_msize = memsize; return (ED_3COM_IO_PORTS); } /* * Probe and vendor-specific initialization routine for NE1000/2000 boards */ static int ed_probe_Novell_generic(sc, port, unit, flags) struct ed_softc *sc; int port; int unit; int flags; { u_int memsize, n; u_char romdata[16], tmp; static char test_pattern[32] = "THIS is A memory TEST pattern"; char test_buffer[32]; sc->asic_addr = port + ED_NOVELL_ASIC_OFFSET; sc->nic_addr = port + ED_NOVELL_NIC_OFFSET; /* XXX - do Novell-specific probe here */ /* Reset the board */ #ifdef GWETHER outb(sc->asic_addr + ED_NOVELL_RESET, 0); DELAY(200); #endif /* GWETHER */ tmp = inb(sc->asic_addr + ED_NOVELL_RESET); /* * I don't know if this is necessary; probably cruft leftover from * Clarkson packet driver code. Doesn't do a thing on the boards I've * tested. -DG [note that a outb(0x84, 0) seems to work here, and is * non-invasive...but some boards don't seem to reset and I don't have * complete documentation on what the 'right' thing to do is...so we * do the invasive thing for now. Yuck.] */ outb(sc->asic_addr + ED_NOVELL_RESET, tmp); DELAY(5000); /* * This is needed because some NE clones apparently don't reset the * NIC properly (or the NIC chip doesn't reset fully on power-up) XXX * - this makes the probe invasive! ...Done against my better * judgement. -DLG */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); DELAY(5000); /* Make sure that we really have an 8390 based board */ if (!ed_probe_generic8390(sc)) return (0); sc->vendor = ED_VENDOR_NOVELL; sc->mem_shared = 0; sc->cr_proto = ED_CR_RD2; /* * Test the ability to read and write to the NIC memory. This has the * side affect of determining if this is an NE1000 or an NE2000. */ /* * This prevents packets from being stored in the NIC memory when the * readmem routine turns on the start bit in the CR. */ outb(sc->nic_addr + ED_P0_RCR, ED_RCR_MON); /* Temporarily initialize DCR for byte operations */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_LS); outb(sc->nic_addr + ED_P0_PSTART, 8192 / ED_PAGE_SIZE); outb(sc->nic_addr + ED_P0_PSTOP, 16384 / ED_PAGE_SIZE); sc->isa16bit = 0; /* * Write a test pattern in byte mode. If this fails, then there * probably isn't any memory at 8k - which likely means that the board * is an NE2000. */ ed_pio_writemem(sc, test_pattern, 8192, sizeof(test_pattern)); ed_pio_readmem(sc, 8192, test_buffer, sizeof(test_pattern)); if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) { /* not an NE1000 - try NE2000 */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_WTS | ED_DCR_FT1 | ED_DCR_LS); outb(sc->nic_addr + ED_P0_PSTART, 16384 / ED_PAGE_SIZE); outb(sc->nic_addr + ED_P0_PSTOP, 32768 / ED_PAGE_SIZE); sc->isa16bit = 1; /* * Write a test pattern in word mode. If this also fails, then * we don't know what this board is. */ ed_pio_writemem(sc, test_pattern, 16384, sizeof(test_pattern)); ed_pio_readmem(sc, 16384, test_buffer, sizeof(test_pattern)); if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) return (0); /* not an NE2000 either */ sc->type = ED_TYPE_NE2000; sc->type_str = "NE2000"; } else { sc->type = ED_TYPE_NE1000; sc->type_str = "NE1000"; } /* 8k of memory plus an additional 8k if 16bit */ memsize = 8192 + sc->isa16bit * 8192; #if 0 /* probably not useful - NE boards only come two ways */ /* allow kernel config file overrides */ if (isa_dev->id_msize) memsize = isa_dev->id_msize; #endif sc->mem_size = memsize; /* NIC memory doesn't start at zero on an NE board */ /* The start address is tied to the bus width */ sc->mem_start = (char *) 8192 + sc->isa16bit * 8192; sc->mem_end = sc->mem_start + memsize; sc->tx_page_start = memsize / ED_PAGE_SIZE; #ifdef GWETHER { int x, i, mstart = 0, msize = 0; char pbuf0[ED_PAGE_SIZE], pbuf[ED_PAGE_SIZE], tbuf[ED_PAGE_SIZE]; for (i = 0; i < ED_PAGE_SIZE; i++) pbuf0[i] = 0; /* Clear all the memory. */ for (x = 1; x < 256; x++) ed_pio_writemem(sc, pbuf0, x * 256, ED_PAGE_SIZE); /* Search for the start of RAM. */ for (x = 1; x < 256; x++) { ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf0, tbuf, ED_PAGE_SIZE) == 0) { for (i = 0; i < ED_PAGE_SIZE; i++) pbuf[i] = 255 - x; ed_pio_writemem(sc, pbuf, x * 256, ED_PAGE_SIZE); ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf, tbuf, ED_PAGE_SIZE) == 0) { mstart = x * ED_PAGE_SIZE; msize = ED_PAGE_SIZE; break; } } } if (mstart == 0) { printf("ed%d: Cannot find start of RAM.\n", unit); return 0; } /* Search for the start of RAM. */ for (x = (mstart / ED_PAGE_SIZE) + 1; x < 256; x++) { ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf0, tbuf, ED_PAGE_SIZE) == 0) { for (i = 0; i < ED_PAGE_SIZE; i++) pbuf[i] = 255 - x; ed_pio_writemem(sc, pbuf, x * 256, ED_PAGE_SIZE); ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf, tbuf, ED_PAGE_SIZE) == 0) msize += ED_PAGE_SIZE; else { break; } } else { break; } } if (msize == 0) { printf("ed%d: Cannot find any RAM, start : %d, x = %d.\n", unit, mstart, x); return 0; } printf("ed%d: RAM start at %d, size : %d.\n", unit, mstart, msize); sc->mem_size = msize; sc->mem_start = (char *) mstart; sc->mem_end = (char *) (msize + mstart); sc->tx_page_start = mstart / ED_PAGE_SIZE; } #endif /* GWETHER */ /* * Use one xmit buffer if < 16k, two buffers otherwise (if not told * otherwise). */ if ((memsize < 16384) || (flags & ED_FLAGS_NO_MULTI_BUFFERING)) sc->txb_cnt = 1; else sc->txb_cnt = 2; sc->rec_page_start = sc->tx_page_start + sc->txb_cnt * ED_TXBUF_SIZE; sc->rec_page_stop = sc->tx_page_start + memsize / ED_PAGE_SIZE; sc->mem_ring = sc->mem_start + sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE; ed_pio_readmem(sc, 0, romdata, 16); for (n = 0; n < ETHER_ADDR_LEN; n++) sc->arpcom.ac_enaddr[n] = romdata[n * (sc->isa16bit + 1)]; #ifdef GWETHER if (sc->arpcom.ac_enaddr[2] == 0x86) { sc->type_str = "Gateway AT"; } #endif /* GWETHER */ /* clear any pending interrupts that might have occurred above */ outb(sc->nic_addr + ED_P0_ISR, 0xff); return (ED_NOVELL_IO_PORTS); } static int ed_probe_Novell(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; isa_dev->id_maddr = 0; return ed_probe_Novell_generic(sc, isa_dev->id_iobase, isa_dev->id_unit, isa_dev->id_flags); } #if NCRD > 0 /* * Probe framework for pccards. Replicates the standard framework, * minus the pccard driver registration and ignores the ether address * supplied (from the CIS), relying on the probe to find it instead. */ static int ed_probe_pccard(isa_dev, ether) struct isa_device *isa_dev; u_char *ether; { int nports; nports = ed_probe_WD80x3(isa_dev); if (nports) return (nports); nports = ed_probe_Novell(isa_dev); if (nports) return (nports); return (0); } #endif /* NCRD > 0 */ #define ED_HPP_TEST_SIZE 16 /* * Probe and vendor specific initialization for the HP PC Lan+ Cards. * (HP Part nos: 27247B and 27252A). * * The card has an asic wrapper around a DS8390 core. The asic handles * host accesses and offers both standard register IO and memory mapped * IO. Memory mapped I/O allows better performance at the expense of greater * chance of an incompatibility with existing ISA cards. * * The card has a few caveats: it isn't tolerant of byte wide accesses, only * short (16 bit) or word (32 bit) accesses are allowed. Some card revisions * don't allow 32 bit accesses; these are indicated by a bit in the software * ID register (see if_edreg.h). * * Other caveats are: we should read the MAC address only when the card * is inactive. * * For more information; please consult the CRYNWR packet driver. * * The AUI port is turned on using the "link2" option on the ifconfig * command line. */ static int ed_probe_HP_pclanp(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; int n; /* temp var */ int memsize; /* mem on board */ u_char checksum; /* checksum of board address */ u_char irq; /* board configured IRQ */ char test_pattern[ED_HPP_TEST_SIZE]; /* read/write areas for */ char test_buffer[ED_HPP_TEST_SIZE]; /* probing card */ /* Fill in basic information */ sc->asic_addr = isa_dev->id_iobase + ED_HPP_ASIC_OFFSET; sc->nic_addr = isa_dev->id_iobase + ED_HPP_NIC_OFFSET; sc->is790 = 0; sc->isa16bit = 0; /* the 8390 core needs to be in byte mode */ /* * Look for the HP PCLAN+ signature: "0x50,0x48,0x00,0x53" */ if ((inb(sc->asic_addr + ED_HPP_ID) != 0x50) || (inb(sc->asic_addr + ED_HPP_ID + 1) != 0x48) || ((inb(sc->asic_addr + ED_HPP_ID + 2) & 0xF0) != 0) || (inb(sc->asic_addr + ED_HPP_ID + 3) != 0x53)) return 0; /* * Read the MAC address and verify checksum on the address. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_MAC); for (n = 0, checksum = 0; n < ETHER_ADDR_LEN; n++) checksum += (sc->arpcom.ac_enaddr[n] = inb(sc->asic_addr + ED_HPP_MAC_ADDR + n)); checksum += inb(sc->asic_addr + ED_HPP_MAC_ADDR + ETHER_ADDR_LEN); if (checksum != 0xFF) return 0; /* * Verify that the software model number is 0. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_ID); if (((sc->hpp_id = inw(sc->asic_addr + ED_HPP_PAGE_4)) & ED_HPP_ID_SOFT_MODEL_MASK) != 0x0000) return 0; /* * Read in and save the current options configured on card. */ sc->hpp_options = inw(sc->asic_addr + ED_HPP_OPTION); sc->hpp_options |= (ED_HPP_OPTION_NIC_RESET | ED_HPP_OPTION_CHIP_RESET | ED_HPP_OPTION_ENABLE_IRQ); /* * Reset the chip. This requires writing to the option register * so take care to preserve the other bits. */ outw(sc->asic_addr + ED_HPP_OPTION, (sc->hpp_options & ~(ED_HPP_OPTION_NIC_RESET | ED_HPP_OPTION_CHIP_RESET))); DELAY(5000); /* wait for chip reset to complete */ outw(sc->asic_addr + ED_HPP_OPTION, (sc->hpp_options | (ED_HPP_OPTION_NIC_RESET | ED_HPP_OPTION_CHIP_RESET | ED_HPP_OPTION_ENABLE_IRQ))); DELAY(5000); if (!(inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST)) return 0; /* reset did not complete */ /* * Read out configuration information. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_HW); irq = inb(sc->asic_addr + ED_HPP_HW_IRQ); /* * Check for impossible IRQ. */ if (irq >= (sizeof(ed_hpp_intr_mask) / sizeof(ed_hpp_intr_mask[0]))) return 0; /* * If the kernel IRQ was specified with a '?' use the cards idea * of the IRQ. If the kernel IRQ was explicitly specified, it * should match that of the hardware. */ if (isa_dev->id_irq <= 0) isa_dev->id_irq = ed_hpp_intr_mask[irq]; else if (isa_dev->id_irq != ed_hpp_intr_mask[irq]) return 0; /* * Fill in softconfig info. */ sc->vendor = ED_VENDOR_HP; sc->type = ED_TYPE_HP_PCLANPLUS; sc->type_str = "HP-PCLAN+"; sc->mem_shared = 0; /* we DON'T have dual ported RAM */ sc->mem_start = 0; /* we use offsets inside the card RAM */ sc->hpp_mem_start = NULL;/* no memory mapped I/O by default */ /* * Check if memory mapping of the I/O registers possible. */ if (sc->hpp_options & ED_HPP_OPTION_MEM_ENABLE) { u_long mem_addr; /* * determine the memory address from the board. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_HW); mem_addr = (inw(sc->asic_addr + ED_HPP_HW_MEM_MAP) << 8); /* * Check that the kernel specified start of memory and * hardware's idea of it match. */ if (mem_addr != kvtop(isa_dev->id_maddr)) return 0; sc->hpp_mem_start = isa_dev->id_maddr; } /* * The board has 32KB of memory. Is there a way to determine * this programmatically? */ memsize = 32768; /* * Fill in the rest of the soft config structure. */ /* * The transmit page index. */ sc->tx_page_start = ED_HPP_TX_PAGE_OFFSET; if (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING) sc->txb_cnt = 1; else sc->txb_cnt = 2; /* * Memory description */ sc->mem_size = memsize; sc->mem_ring = sc->mem_start + (sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE); sc->mem_end = sc->mem_start + sc->mem_size; /* * Receive area starts after the transmit area and * continues till the end of memory. */ sc->rec_page_start = sc->tx_page_start + (sc->txb_cnt * ED_TXBUF_SIZE); sc->rec_page_stop = (sc->mem_size / ED_PAGE_SIZE); sc->cr_proto = 0; /* value works */ /* * Set the wrap registers for string I/O reads. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_HW); outw(sc->asic_addr + ED_HPP_HW_WRAP, ((sc->rec_page_start / ED_PAGE_SIZE) | (((sc->rec_page_stop / ED_PAGE_SIZE) - 1) << 8))); /* * Reset the register page to normal operation. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_PERF); /* * Verify that we can read/write from adapter memory. * Create test pattern. */ for (n = 0; n < ED_HPP_TEST_SIZE; n++) { test_pattern[n] = (n*n) ^ ~n; } #undef ED_HPP_TEST_SIZE /* * Check that the memory is accessible thru the I/O ports. * Write out the contents of "test_pattern", read back * into "test_buffer" and compare the two for any * mismatch. */ for (n = 0; n < (32768 / ED_PAGE_SIZE); n ++) { ed_pio_writemem(sc, test_pattern, (n * ED_PAGE_SIZE), sizeof(test_pattern)); ed_pio_readmem(sc, (n * ED_PAGE_SIZE), test_buffer, sizeof(test_pattern)); if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) return 0; } return (ED_HPP_IO_PORTS); } /* * HP PC Lan+ : Set the physical link to use AUI or TP/TL. */ void ed_hpp_set_physical_link(struct ed_softc *sc) { struct ifnet *ifp = &sc->arpcom.ac_if; int lan_page; outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_LAN); lan_page = inw(sc->asic_addr + ED_HPP_PAGE_0); if (ifp->if_flags & IFF_ALTPHYS) { /* * Use the AUI port. */ lan_page |= ED_HPP_LAN_AUI; outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_LAN); outw(sc->asic_addr + ED_HPP_PAGE_0, lan_page); } else { /* * Use the ThinLan interface */ lan_page &= ~ED_HPP_LAN_AUI; outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_LAN); outw(sc->asic_addr + ED_HPP_PAGE_0, lan_page); } /* * Wait for the lan card to re-initialize itself */ DELAY(150000); /* wait 150 ms */ /* * Restore normal pages. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_PERF); } /* * Install interface into kernel networking data structures */ static int ed_attach(sc, unit, flags) struct ed_softc *sc; int unit; int flags; { struct ifnet *ifp = &sc->arpcom.ac_if; /* * Set interface to stopped condition (reset) */ ed_stop(sc); if (!ifp->if_name) { /* * Initialize ifnet structure */ ifp->if_softc = sc; ifp->if_unit = unit; ifp->if_name = "ed"; ifp->if_output = ether_output; ifp->if_start = ed_start; ifp->if_ioctl = ed_ioctl; ifp->if_watchdog = ed_watchdog; ifp->if_init = ed_init; ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; ifp->if_linkmib = &sc->mibdata; ifp->if_linkmiblen = sizeof sc->mibdata; /* * XXX - should do a better job. */ if (sc->is790) sc->mibdata.dot3StatsEtherChipSet = DOT3CHIPSET(dot3VendorWesternDigital, dot3ChipSetWesternDigital83C790); else sc->mibdata.dot3StatsEtherChipSet = DOT3CHIPSET(dot3VendorNational, dot3ChipSetNational8390); sc->mibdata.dot3Compliance = DOT3COMPLIANCE_COLLS; /* * Set default state for ALTPHYS flag (used to disable the * tranceiver for AUI operation), based on compile-time * config option. */ if (flags & ED_FLAGS_DISABLE_TRANCEIVER) ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | IFF_ALTPHYS); else ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); /* * Attach the interface */ if_attach(ifp); ether_ifattach(ifp); } /* device attach does transition from UNCONFIGURED to IDLE state */ /* * Print additional info when attached */ printf("%s%d: address %6D, ", ifp->if_name, ifp->if_unit, sc->arpcom.ac_enaddr, ":"); if (sc->type_str && (*sc->type_str != 0)) printf("type %s ", sc->type_str); else printf("type unknown (0x%x) ", sc->type); if (sc->vendor == ED_VENDOR_HP) printf("(%s %s IO)", (sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS) ? "16-bit" : "32-bit", sc->hpp_mem_start ? "memory mapped" : "regular"); else printf("%s ", sc->isa16bit ? "(16 bit)" : "(8 bit)"); printf("%s\n", (((sc->vendor == ED_VENDOR_3COM) || (sc->vendor == ED_VENDOR_HP)) && (ifp->if_flags & IFF_ALTPHYS)) ? " tranceiver disabled" : ""); /* * If BPF is in the kernel, call the attach for it */ #if NBPFILTER > 0 bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); #endif return 1; } static int ed_attach_isa(isa_dev) struct isa_device *isa_dev; { int unit = isa_dev->id_unit; struct ed_softc *sc = &ed_softc[unit]; int flags = isa_dev->id_flags; return ed_attach(sc, unit, flags); } #if NPCI > 0 void * ed_attach_NE2000_pci(unit, port) int unit; int port; { struct ed_softc *sc = malloc(sizeof *sc, M_DEVBUF, M_NOWAIT); int isa_flags = 0; if (!sc) return sc; bzero(sc, sizeof *sc); if (ed_probe_Novell_generic(sc, port, unit, isa_flags) == 0 || ed_attach(sc, unit, isa_flags) == 0) { free(sc, M_DEVBUF); return NULL; } return sc; } #endif /* * Reset interface. */ static void ed_reset(ifp) struct ifnet *ifp; { struct ed_softc *sc = ifp->if_softc; int s; if (sc->gone) return; s = splimp(); /* * Stop interface and re-initialize. */ ed_stop(sc); ed_init(sc); (void) splx(s); } /* * Take interface offline. */ static void ed_stop(sc) struct ed_softc *sc; { int n = 5000; if (sc->gone) return; /* * Stop everything on the interface, and select page 0 registers. */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); /* * Wait for interface to enter stopped state, but limit # of checks to * 'n' (about 5ms). It shouldn't even take 5us on modern DS8390's, but * just in case it's an old one. */ while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST) == 0) && --n); } /* * Device timeout/watchdog routine. Entered if the device neglects to * generate an interrupt after a transmit has been started on it. */ static void ed_watchdog(ifp) struct ifnet *ifp; { struct ed_softc *sc = ifp->if_softc; if (sc->gone) return; log(LOG_ERR, "ed%d: device timeout\n", ifp->if_unit); ifp->if_oerrors++; ed_reset(ifp); } /* * Initialize device. */ static void ed_init(xsc) void *xsc; { struct ed_softc *sc = xsc; struct ifnet *ifp = &sc->arpcom.ac_if; int i, s; if (sc->gone) return; /* address not known */ - if (ifp->if_addrlist == (struct ifaddr *) 0) + if (TAILQ_EMPTY(&ifp->if_addrhead)) /* unlikely? XXX */ return; /* * Initialize the NIC in the exact order outlined in the NS manual. * This init procedure is "mandatory"...don't change what or when * things happen. */ s = splimp(); /* reset transmitter flags */ sc->xmit_busy = 0; ifp->if_timer = 0; sc->txb_inuse = 0; sc->txb_new = 0; sc->txb_next_tx = 0; /* This variable is used below - don't move this assignment */ sc->next_packet = sc->rec_page_start + 1; /* * Set interface for page 0, Remote DMA complete, Stopped */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); if (sc->isa16bit) { /* * Set FIFO threshold to 8, No auto-init Remote DMA, byte * order=80x86, word-wide DMA xfers, */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_WTS | ED_DCR_LS); } else { /* * Same as above, but byte-wide DMA xfers */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_LS); } /* * Clear Remote Byte Count Registers */ outb(sc->nic_addr + ED_P0_RBCR0, 0); outb(sc->nic_addr + ED_P0_RBCR1, 0); /* * For the moment, don't store incoming packets in memory. */ outb(sc->nic_addr + ED_P0_RCR, ED_RCR_MON); /* * Place NIC in internal loopback mode */ outb(sc->nic_addr + ED_P0_TCR, ED_TCR_LB0); /* * Initialize transmit/receive (ring-buffer) Page Start */ outb(sc->nic_addr + ED_P0_TPSR, sc->tx_page_start); outb(sc->nic_addr + ED_P0_PSTART, sc->rec_page_start); /* Set lower bits of byte addressable framing to 0 */ if (sc->is790) outb(sc->nic_addr + 0x09, 0); /* * Initialize Receiver (ring-buffer) Page Stop and Boundry */ outb(sc->nic_addr + ED_P0_PSTOP, sc->rec_page_stop); outb(sc->nic_addr + ED_P0_BNRY, sc->rec_page_start); /* * Clear all interrupts. A '1' in each bit position clears the * corresponding flag. */ outb(sc->nic_addr + ED_P0_ISR, 0xff); /* * Enable the following interrupts: receive/transmit complete, * receive/transmit error, and Receiver OverWrite. * * Counter overflow and Remote DMA complete are *not* enabled. */ outb(sc->nic_addr + ED_P0_IMR, ED_IMR_PRXE | ED_IMR_PTXE | ED_IMR_RXEE | ED_IMR_TXEE | ED_IMR_OVWE); /* * Program Command Register for page 1 */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STP); /* * Copy out our station address */ for (i = 0; i < ETHER_ADDR_LEN; ++i) outb(sc->nic_addr + ED_P1_PAR0 + i, sc->arpcom.ac_enaddr[i]); /* * Set Current Page pointer to next_packet (initialized above) */ outb(sc->nic_addr + ED_P1_CURR, sc->next_packet); /* * Program Receiver Configuration Register and multicast filter. CR is * set to page 0 on return. */ ed_setrcr(sc); /* * Take interface out of loopback */ outb(sc->nic_addr + ED_P0_TCR, 0); /* * If this is a 3Com board, the tranceiver must be software enabled * (there is no settable hardware default). */ if (sc->vendor == ED_VENDOR_3COM) { if (ifp->if_flags & IFF_ALTPHYS) { outb(sc->asic_addr + ED_3COM_CR, 0); } else { outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); } } /* * Set 'running' flag, and clear output active flag. */ ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; /* * ...and attempt to start output */ ed_start(ifp); (void) splx(s); } /* * This routine actually starts the transmission on the interface */ static inline void ed_xmit(sc) struct ed_softc *sc; { struct ifnet *ifp = (struct ifnet *)sc; unsigned short len; if (sc->gone) return; len = sc->txb_len[sc->txb_next_tx]; /* * Set NIC for page 0 register access */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* * Set TX buffer start page */ outb(sc->nic_addr + ED_P0_TPSR, sc->tx_page_start + sc->txb_next_tx * ED_TXBUF_SIZE); /* * Set TX length */ outb(sc->nic_addr + ED_P0_TBCR0, len); outb(sc->nic_addr + ED_P0_TBCR1, len >> 8); /* * Set page 0, Remote DMA complete, Transmit Packet, and *Start* */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_TXP | ED_CR_STA); sc->xmit_busy = 1; /* * Point to next transmit buffer slot and wrap if necessary. */ sc->txb_next_tx++; if (sc->txb_next_tx == sc->txb_cnt) sc->txb_next_tx = 0; /* * Set a timer just in case we never hear from the board again */ ifp->if_timer = 2; } /* * Start output on interface. * We make two assumptions here: * 1) that the current priority is set to splimp _before_ this code * is called *and* is returned to the appropriate priority after * return * 2) that the IFF_OACTIVE flag is checked before this code is called * (i.e. that the output part of the interface is idle) */ static void ed_start(ifp) struct ifnet *ifp; { struct ed_softc *sc = ifp->if_softc; struct mbuf *m0, *m; caddr_t buffer; int len; if (sc->gone) { printf("ed_start(%p) GONE\n",ifp); return; } outloop: /* * First, see if there are buffered packets and an idle transmitter - * should never happen at this point. */ if (sc->txb_inuse && (sc->xmit_busy == 0)) { printf("ed: packets buffered, but transmitter idle\n"); ed_xmit(sc); } /* * See if there is room to put another packet in the buffer. */ if (sc->txb_inuse == sc->txb_cnt) { /* * No room. Indicate this to the outside world and exit. */ ifp->if_flags |= IFF_OACTIVE; return; } IF_DEQUEUE(&ifp->if_snd, m); if (m == 0) { /* * We are using the !OACTIVE flag to indicate to the outside * world that we can accept an additional packet rather than * that the transmitter is _actually_ active. Indeed, the * transmitter may be active, but if we haven't filled all the * buffers with data then we still want to accept more. */ ifp->if_flags &= ~IFF_OACTIVE; return; } /* * Copy the mbuf chain into the transmit buffer */ m0 = m; /* txb_new points to next open buffer slot */ buffer = sc->mem_start + (sc->txb_new * ED_TXBUF_SIZE * ED_PAGE_SIZE); if (sc->mem_shared) { /* * Special case setup for 16 bit boards... */ if (sc->isa16bit) { switch (sc->vendor) { /* * For 16bit 3Com boards (which have 16k of * memory), we have the xmit buffers in a * different page of memory ('page 0') - so * change pages. */ case ED_VENDOR_3COM: outb(sc->asic_addr + ED_3COM_GACFR, ED_3COM_GACFR_RSEL); break; /* * Enable 16bit access to shared memory on * WD/SMC boards. */ case ED_VENDOR_WD_SMC:{ outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto | ED_WD_LAAR_M16EN)); if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB); } break; } } } for (len = 0; m != 0; m = m->m_next) { bcopy(mtod(m, caddr_t), buffer, m->m_len); buffer += m->m_len; len += m->m_len; } /* * Restore previous shared memory access */ if (sc->isa16bit) { switch (sc->vendor) { case ED_VENDOR_3COM: outb(sc->asic_addr + ED_3COM_GACFR, ED_3COM_GACFR_RSEL | ED_3COM_GACFR_MBS0); break; case ED_VENDOR_WD_SMC:{ if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, sc->wd_laar_proto); break; } } } } else { len = ed_pio_write_mbufs(sc, m, (int)buffer); if (len == 0) goto outloop; } sc->txb_len[sc->txb_new] = max(len, (ETHER_MIN_LEN-ETHER_CRC_LEN)); sc->txb_inuse++; /* * Point to next buffer slot and wrap if necessary. */ sc->txb_new++; if (sc->txb_new == sc->txb_cnt) sc->txb_new = 0; if (sc->xmit_busy == 0) ed_xmit(sc); /* * Tap off here if there is a bpf listener. */ #if NBPFILTER > 0 if (ifp->if_bpf) { bpf_mtap(ifp, m0); } #endif m_freem(m0); /* * Loop back to the top to possibly buffer more packets */ goto outloop; } /* * Ethernet interface receiver interrupt. */ static inline void ed_rint(sc) struct ed_softc *sc; { struct ifnet *ifp = &sc->arpcom.ac_if; u_char boundry; u_short len; struct ed_ring packet_hdr; char *packet_ptr; if (sc->gone) return; /* * Set NIC to page 1 registers to get 'current' pointer */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STA); /* * 'sc->next_packet' is the logical beginning of the ring-buffer - * i.e. it points to where new data has been buffered. The 'CURR' * (current) register points to the logical end of the ring-buffer - * i.e. it points to where additional new data will be added. We loop * here until the logical beginning equals the logical end (or in * other words, until the ring-buffer is empty). */ while (sc->next_packet != inb(sc->nic_addr + ED_P1_CURR)) { /* get pointer to this buffer's header structure */ packet_ptr = sc->mem_ring + (sc->next_packet - sc->rec_page_start) * ED_PAGE_SIZE; /* * The byte count includes a 4 byte header that was added by * the NIC. */ if (sc->mem_shared) packet_hdr = *(struct ed_ring *) packet_ptr; else ed_pio_readmem(sc, (int)packet_ptr, (char *) &packet_hdr, sizeof(packet_hdr)); len = packet_hdr.count; if (len > (ETHER_MAX_LEN - ETHER_CRC_LEN + sizeof(struct ed_ring)) || len < (ETHER_MIN_LEN - ETHER_CRC_LEN + sizeof(struct ed_ring))) { /* * Length is a wild value. There's a good chance that * this was caused by the NIC being old and buggy. * The bug is that the length low byte is duplicated in * the high byte. Try to recalculate the length based on * the pointer to the next packet. */ /* * NOTE: sc->next_packet is pointing at the current packet. */ len &= ED_PAGE_SIZE - 1; /* preserve offset into page */ if (packet_hdr.next_packet >= sc->next_packet) { len += (packet_hdr.next_packet - sc->next_packet) * ED_PAGE_SIZE; } else { len += ((packet_hdr.next_packet - sc->rec_page_start) + (sc->rec_page_stop - sc->next_packet)) * ED_PAGE_SIZE; } if (len > (ETHER_MAX_LEN - ETHER_CRC_LEN + sizeof(struct ed_ring))) sc->mibdata.dot3StatsFrameTooLongs++; } /* * Be fairly liberal about what we allow as a "reasonable" length * so that a [crufty] packet will make it to BPF (and can thus * be analyzed). Note that all that is really important is that * we have a length that will fit into one mbuf cluster or less; * the upper layer protocols can then figure out the length from * their own length field(s). */ if ((len > sizeof(struct ed_ring)) && (len <= MCLBYTES) && (packet_hdr.next_packet >= sc->rec_page_start) && (packet_hdr.next_packet < sc->rec_page_stop)) { /* * Go get packet. */ ed_get_packet(sc, packet_ptr + sizeof(struct ed_ring), len - sizeof(struct ed_ring), packet_hdr.rsr & ED_RSR_PHY); ifp->if_ipackets++; } else { /* * Really BAD. The ring pointers are corrupted. */ log(LOG_ERR, "ed%d: NIC memory corrupt - invalid packet length %d\n", ifp->if_unit, len); ifp->if_ierrors++; ed_reset(ifp); return; } /* * Update next packet pointer */ sc->next_packet = packet_hdr.next_packet; /* * Update NIC boundry pointer - being careful to keep it one * buffer behind. (as recommended by NS databook) */ boundry = sc->next_packet - 1; if (boundry < sc->rec_page_start) boundry = sc->rec_page_stop - 1; /* * Set NIC to page 0 registers to update boundry register */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); outb(sc->nic_addr + ED_P0_BNRY, boundry); /* * Set NIC to page 1 registers before looping to top (prepare * to get 'CURR' current pointer) */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STA); } } /* * Ethernet interface interrupt processor */ void edintr_sc(sc) struct ed_softc *sc; { struct ifnet *ifp = (struct ifnet *)sc; u_char isr; if (sc->gone) return; /* * Set NIC to page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* * loop until there are no more new interrupts */ while ((isr = inb(sc->nic_addr + ED_P0_ISR)) != 0) { /* * reset all the bits that we are 'acknowledging' by writing a * '1' to each bit position that was set (writing a '1' * *clears* the bit) */ outb(sc->nic_addr + ED_P0_ISR, isr); /* * Handle transmitter interrupts. Handle these first because * the receiver will reset the board under some conditions. */ if (isr & (ED_ISR_PTX | ED_ISR_TXE)) { u_char collisions = inb(sc->nic_addr + ED_P0_NCR) & 0x0f; /* * Check for transmit error. If a TX completed with an * error, we end up throwing the packet away. Really * the only error that is possible is excessive * collisions, and in this case it is best to allow * the automatic mechanisms of TCP to backoff the * flow. Of course, with UDP we're screwed, but this * is expected when a network is heavily loaded. */ (void) inb(sc->nic_addr + ED_P0_TSR); if (isr & ED_ISR_TXE) { u_char tsr; /* * Excessive collisions (16) */ tsr = inb(sc->nic_addr + ED_P0_TSR); if ((tsr & ED_TSR_ABT) && (collisions == 0)) { /* * When collisions total 16, the * P0_NCR will indicate 0, and the * TSR_ABT is set. */ collisions = 16; sc->mibdata.dot3StatsExcessiveCollisions++; sc->mibdata.dot3StatsCollFrequencies[15]++; } if (tsr & ED_TSR_OWC) sc->mibdata.dot3StatsLateCollisions++; if (tsr & ED_TSR_CDH) sc->mibdata.dot3StatsSQETestErrors++; if (tsr & ED_TSR_CRS) sc->mibdata.dot3StatsCarrierSenseErrors++; if (tsr & ED_TSR_FU) sc->mibdata.dot3StatsInternalMacTransmitErrors++; /* * update output errors counter */ ifp->if_oerrors++; } else { /* * Update total number of successfully * transmitted packets. */ ifp->if_opackets++; } /* * reset tx busy and output active flags */ sc->xmit_busy = 0; ifp->if_flags &= ~IFF_OACTIVE; /* * clear watchdog timer */ ifp->if_timer = 0; /* * Add in total number of collisions on last * transmission. */ ifp->if_collisions += collisions; switch(collisions) { case 0: case 16: break; case 1: sc->mibdata.dot3StatsSingleCollisionFrames++; sc->mibdata.dot3StatsCollFrequencies[0]++; break; default: sc->mibdata.dot3StatsMultipleCollisionFrames++; sc->mibdata. dot3StatsCollFrequencies[collisions-1] ++; break; } /* * Decrement buffer in-use count if not zero (can only * be zero if a transmitter interrupt occured while * not actually transmitting). If data is ready to * transmit, start it transmitting, otherwise defer * until after handling receiver */ if (sc->txb_inuse && --sc->txb_inuse) ed_xmit(sc); } /* * Handle receiver interrupts */ if (isr & (ED_ISR_PRX | ED_ISR_RXE | ED_ISR_OVW)) { /* * Overwrite warning. In order to make sure that a * lockup of the local DMA hasn't occurred, we reset * and re-init the NIC. The NSC manual suggests only a * partial reset/re-init is necessary - but some chips * seem to want more. The DMA lockup has been seen * only with early rev chips - Methinks this bug was * fixed in later revs. -DG */ if (isr & ED_ISR_OVW) { ifp->if_ierrors++; #ifdef DIAGNOSTIC log(LOG_WARNING, "ed%d: warning - receiver ring buffer overrun\n", ifp->if_unit); #endif /* * Stop/reset/re-init NIC */ ed_reset(ifp); } else { /* * Receiver Error. One or more of: CRC error, * frame alignment error FIFO overrun, or * missed packet. */ if (isr & ED_ISR_RXE) { u_char rsr; rsr = inb(sc->nic_addr + ED_P0_RSR); if (rsr & ED_RSR_CRC) sc->mibdata.dot3StatsFCSErrors++; if (rsr & ED_RSR_FAE) sc->mibdata.dot3StatsAlignmentErrors++; if (rsr & ED_RSR_FO) sc->mibdata.dot3StatsInternalMacReceiveErrors++; ifp->if_ierrors++; #ifdef ED_DEBUG printf("ed%d: receive error %x\n", ifp->if_unit, inb(sc->nic_addr + ED_P0_RSR)); #endif } /* * Go get the packet(s) XXX - Doing this on an * error is dubious because there shouldn't be * any data to get (we've configured the * interface to not accept packets with * errors). */ /* * Enable 16bit access to shared memory first * on WD/SMC boards. */ if (sc->isa16bit && (sc->vendor == ED_VENDOR_WD_SMC)) { outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto |= ED_WD_LAAR_M16EN)); if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB); } } ed_rint(sc); /* disable 16bit access */ if (sc->isa16bit && (sc->vendor == ED_VENDOR_WD_SMC)) { if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto &= ~ED_WD_LAAR_M16EN)); } } } /* * If it looks like the transmitter can take more data, * attempt to start output on the interface. This is done * after handling the receiver to give the receiver priority. */ if ((ifp->if_flags & IFF_OACTIVE) == 0) ed_start(ifp); /* * return NIC CR to standard state: page 0, remote DMA * complete, start (toggling the TXP bit off, even if was just * set in the transmit routine, is *okay* - it is 'edge' * triggered from low to high) */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* * If the Network Talley Counters overflow, read them to reset * them. It appears that old 8390's won't clear the ISR flag * otherwise - resulting in an infinite loop. */ if (isr & ED_ISR_CNT) { (void) inb(sc->nic_addr + ED_P0_CNTR0); (void) inb(sc->nic_addr + ED_P0_CNTR1); (void) inb(sc->nic_addr + ED_P0_CNTR2); } } } void edintr(unit) int unit; { edintr_sc (&ed_softc[unit]); } /* * Process an ioctl request. This code needs some work - it looks * pretty ugly. */ static int ed_ioctl(ifp, command, data) register struct ifnet *ifp; int command; caddr_t data; { struct ed_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; int s, error = 0; if (sc->gone) { ifp->if_flags &= ~IFF_RUNNING; return ENXIO; } s = splimp(); switch (command) { case SIOCSIFADDR: case SIOCGIFADDR: case SIOCSIFMTU: error = ether_ioctl(ifp, command, data); break; case SIOCSIFFLAGS: /* * If the interface is marked up and stopped, then start it. * If it is marked down and running, then stop it. */ if (ifp->if_flags & IFF_UP) { if ((ifp->if_flags & IFF_RUNNING) == 0) ed_init(ifp->if_softc); } else { if (ifp->if_flags & IFF_RUNNING) { ed_stop(sc); ifp->if_flags &= ~IFF_RUNNING; } } #if NBPFILTER > 0 /* * Promiscuous flag may have changed, so reprogram the RCR. */ ed_setrcr(sc); #endif /* * An unfortunate hack to provide the (required) software * control of the tranceiver for 3Com boards. The ALTPHYS flag * disables the tranceiver if set. */ if (sc->vendor == ED_VENDOR_3COM) { if (ifp->if_flags & IFF_ALTPHYS) { outb(sc->asic_addr + ED_3COM_CR, 0); } else { outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); } } else if (sc->vendor == ED_VENDOR_HP) ed_hpp_set_physical_link(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: /* * Update out multicast list. */ error = (command == SIOCADDMULTI) ? ether_addmulti(ifr, &sc->arpcom) : ether_delmulti(ifr, &sc->arpcom); if (error == ENETRESET) { /* * Multicast list has changed; set the hardware filter * accordingly. */ ed_setrcr(sc); error = 0; } break; default: error = EINVAL; } (void) splx(s); return (error); } /* * Given a source and destination address, copy 'amount' of a packet from * the ring buffer into a linear destination buffer. Takes into account * ring-wrap. */ static inline char * ed_ring_copy(sc, src, dst, amount) struct ed_softc *sc; char *src; char *dst; u_short amount; { u_short tmp_amount; /* does copy wrap to lower addr in ring buffer? */ if (src + amount > sc->mem_end) { tmp_amount = sc->mem_end - src; /* copy amount up to end of NIC memory */ if (sc->mem_shared) bcopy(src, dst, tmp_amount); else ed_pio_readmem(sc, (int)src, dst, tmp_amount); amount -= tmp_amount; src = sc->mem_ring; dst += tmp_amount; } if (sc->mem_shared) bcopy(src, dst, amount); else ed_pio_readmem(sc, (int)src, dst, amount); return (src + amount); } /* * Retreive packet from shared memory and send to the next level up via * ether_input(). If there is a BPF listener, give a copy to BPF, too. */ static void ed_get_packet(sc, buf, len, multicast) struct ed_softc *sc; char *buf; u_short len; int multicast; { struct ether_header *eh; struct mbuf *m; /* Allocate a header mbuf */ MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return; m->m_pkthdr.rcvif = &sc->arpcom.ac_if; m->m_pkthdr.len = m->m_len = len; /* * We always put the received packet in a single buffer - * either with just an mbuf header or in a cluster attached * to the header. The +2 is to compensate for the alignment * fixup below. */ if ((len + 2) > MHLEN) { /* Attach an mbuf cluster */ MCLGET(m, M_DONTWAIT); /* Insist on getting a cluster */ if ((m->m_flags & M_EXT) == 0) { m_freem(m); return; } } /* * The +2 is to longword align the start of the real packet. * This is important for NFS. */ m->m_data += 2; eh = mtod(m, struct ether_header *); /* * Get packet, including link layer address, from interface. */ ed_ring_copy(sc, buf, (char *)eh, len); #if NBPFILTER > 0 /* * Check if there's a BPF listener on this interface. If so, hand off * the raw packet to bpf. */ if (sc->arpcom.ac_if.if_bpf) { bpf_mtap(&sc->arpcom.ac_if, m); /* * Note that the interface cannot be in promiscuous mode if * there are no BPF listeners. And if we are in promiscuous * mode, we have to check if this packet is really ours. */ if ((sc->arpcom.ac_if.if_flags & IFF_PROMISC) && bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, sizeof(eh->ether_dhost)) != 0 && multicast == 0) { m_freem(m); return; } } #endif /* * Remove link layer address. */ m->m_pkthdr.len = m->m_len = len - sizeof(struct ether_header); m->m_data += sizeof(struct ether_header); ether_input(&sc->arpcom.ac_if, eh, m); return; } /* * Supporting routines */ /* * Given a NIC memory source address and a host memory destination * address, copy 'amount' from NIC to host using Programmed I/O. * The 'amount' is rounded up to a word - okay as long as mbufs * are word sized. * This routine is currently Novell-specific. */ static void ed_pio_readmem(sc, src, dst, amount) struct ed_softc *sc; int src; unsigned char *dst; unsigned short amount; { /* HP cards need special handling */ if (sc->vendor == ED_VENDOR_HP && sc->type == ED_TYPE_HP_PCLANPLUS) { ed_hpp_readmem(sc, src, dst, amount); return; } /* Regular Novell cards */ /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); /* round up to a word */ if (amount & 1) ++amount; /* set up DMA byte count */ outb(sc->nic_addr + ED_P0_RBCR0, amount); outb(sc->nic_addr + ED_P0_RBCR1, amount >> 8); /* set up source address in NIC mem */ outb(sc->nic_addr + ED_P0_RSAR0, src); outb(sc->nic_addr + ED_P0_RSAR1, src >> 8); outb(sc->nic_addr + ED_P0_CR, ED_CR_RD0 | ED_CR_STA); if (sc->isa16bit) { insw(sc->asic_addr + ED_NOVELL_DATA, dst, amount / 2); } else insb(sc->asic_addr + ED_NOVELL_DATA, dst, amount); } /* * Stripped down routine for writing a linear buffer to NIC memory. * Only used in the probe routine to test the memory. 'len' must * be even. */ static void ed_pio_writemem(sc, src, dst, len) struct ed_softc *sc; char *src; unsigned short dst; unsigned short len; { int maxwait = 200; /* about 240us */ if (sc->vendor == ED_VENDOR_NOVELL) { /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* set up DMA byte count */ outb(sc->nic_addr + ED_P0_RBCR0, len); outb(sc->nic_addr + ED_P0_RBCR1, len >> 8); /* set up destination address in NIC mem */ outb(sc->nic_addr + ED_P0_RSAR0, dst); outb(sc->nic_addr + ED_P0_RSAR1, dst >> 8); /* set remote DMA write */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD1 | ED_CR_STA); if (sc->isa16bit) outsw(sc->asic_addr + ED_NOVELL_DATA, src, len / 2); else outsb(sc->asic_addr + ED_NOVELL_DATA, src, len); /* * Wait for remote DMA complete. This is necessary because on the * transmit side, data is handled internally by the NIC in bursts and * we can't start another remote DMA until this one completes. Not * waiting causes really bad things to happen - like the NIC * irrecoverably jamming the ISA bus. */ while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RDC) != ED_ISR_RDC) && --maxwait); } else if ((sc->vendor == ED_VENDOR_HP) && (sc->type == ED_TYPE_HP_PCLANPLUS)) { /* HP PCLAN+ */ /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* program the write address in RAM */ outw(sc->asic_addr + ED_HPP_PAGE_0, dst); if (sc->hpp_mem_start) { u_short *s = (u_short *) src; volatile u_short *d = (u_short *) sc->hpp_mem_start; u_short *const fence = s + (len >> 1); /* * Enable memory mapped access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options & ~(ED_HPP_OPTION_MEM_DISABLE | ED_HPP_OPTION_BOOT_ROM_ENB)); /* * Copy to NIC memory. */ while (s < fence) *d = *s++; /* * Restore Boot ROM access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options); } else { /* write data using I/O writes */ outsw(sc->asic_addr + ED_HPP_PAGE_4, src, len / 2); } } } /* * Write an mbuf chain to the destination NIC memory address using * programmed I/O. */ static u_short ed_pio_write_mbufs(sc, m, dst) struct ed_softc *sc; struct mbuf *m; int dst; { struct ifnet *ifp = (struct ifnet *)sc; unsigned short total_len, dma_len; struct mbuf *mp; int maxwait = 200; /* about 240us */ /* HP PC Lan+ cards need special handling */ if ((sc->vendor == ED_VENDOR_HP) && (sc->type == ED_TYPE_HP_PCLANPLUS)) { return ed_hpp_write_mbufs(sc, m, dst); } /* First, count up the total number of bytes to copy */ for (total_len = 0, mp = m; mp; mp = mp->m_next) total_len += mp->m_len; dma_len = total_len; if (sc->isa16bit && (dma_len & 1)) dma_len++; /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* set up DMA byte count */ outb(sc->nic_addr + ED_P0_RBCR0, dma_len); outb(sc->nic_addr + ED_P0_RBCR1, dma_len >> 8); /* set up destination address in NIC mem */ outb(sc->nic_addr + ED_P0_RSAR0, dst); outb(sc->nic_addr + ED_P0_RSAR1, dst >> 8); /* set remote DMA write */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD1 | ED_CR_STA); /* * Transfer the mbuf chain to the NIC memory. * 16-bit cards require that data be transferred as words, and only words. * So that case requires some extra code to patch over odd-length mbufs. */ if (!sc->isa16bit) { /* NE1000s are easy */ while (m) { if (m->m_len) { outsb(sc->asic_addr + ED_NOVELL_DATA, m->m_data, m->m_len); } m = m->m_next; } } else { /* NE2000s are a pain */ unsigned char *data; int len, wantbyte; unsigned char savebyte[2]; wantbyte = 0; while (m) { len = m->m_len; if (len) { data = mtod(m, caddr_t); /* finish the last word */ if (wantbyte) { savebyte[1] = *data; outw(sc->asic_addr + ED_NOVELL_DATA, *(u_short *)savebyte); data++; len--; wantbyte = 0; } /* output contiguous words */ if (len > 1) { outsw(sc->asic_addr + ED_NOVELL_DATA, data, len >> 1); data += len & ~1; len &= 1; } /* save last byte, if necessary */ if (len == 1) { savebyte[0] = *data; wantbyte = 1; } } m = m->m_next; } /* spit last byte */ if (wantbyte) { outw(sc->asic_addr + ED_NOVELL_DATA, *(u_short *)savebyte); } } /* * Wait for remote DMA complete. This is necessary because on the * transmit side, data is handled internally by the NIC in bursts and * we can't start another remote DMA until this one completes. Not * waiting causes really bad things to happen - like the NIC * irrecoverably jamming the ISA bus. */ while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RDC) != ED_ISR_RDC) && --maxwait); if (!maxwait) { log(LOG_WARNING, "ed%d: remote transmit DMA failed to complete\n", ifp->if_unit); ed_reset(ifp); return(0); } return (total_len); } /* * Support routines to handle the HP PC Lan+ card. */ /* * HP PC Lan+: Read from NIC memory, using either PIO or memory mapped * IO. */ static void ed_hpp_readmem(sc, src, dst, amount) struct ed_softc *sc; unsigned short src; unsigned char *dst; unsigned short amount; { int use_32bit_access = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS); /* Program the source address in RAM */ outw(sc->asic_addr + ED_HPP_PAGE_2, src); /* * The HP PC Lan+ card supports word reads as well as * a memory mapped i/o port that is aliased to every * even address on the board. */ if (sc->hpp_mem_start) { /* Enable memory mapped access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options & ~(ED_HPP_OPTION_MEM_DISABLE | ED_HPP_OPTION_BOOT_ROM_ENB)); if (use_32bit_access && (amount > 3)) { u_long *dl = (u_long *) dst; volatile u_long *const sl = (u_long *) sc->hpp_mem_start; u_long *const fence = dl + (amount >> 2); /* Copy out NIC data. We could probably write this as a `movsl'. The currently generated code is lousy. */ while (dl < fence) *dl++ = *sl; dst += (amount & ~3); amount &= 3; } /* Finish off any words left, as a series of short reads */ if (amount > 1) { u_short *d = (u_short *) dst; volatile u_short *const s = (u_short *) sc->hpp_mem_start; u_short *const fence = d + (amount >> 1); /* Copy out NIC data. */ while (d < fence) *d++ = *s; dst += (amount & ~1); amount &= 1; } /* * read in a byte; however we need to always read 16 bits * at a time or the hardware gets into a funny state */ if (amount == 1) { /* need to read in a short and copy LSB */ volatile u_short *const s = (volatile u_short *) sc->hpp_mem_start; *dst = (*s) & 0xFF; } /* Restore Boot ROM access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options); } else { /* Read in data using the I/O port */ if (use_32bit_access && (amount > 3)) { insl(sc->asic_addr + ED_HPP_PAGE_4, dst, amount >> 2); dst += (amount & ~3); amount &= 3; } if (amount > 1) { insw(sc->asic_addr + ED_HPP_PAGE_4, dst, amount >> 1); dst += (amount & ~1); amount &= 1; } if (amount == 1) { /* read in a short and keep the LSB */ *dst = inw(sc->asic_addr + ED_HPP_PAGE_4) & 0xFF; } } } /* * Write to HP PC Lan+ NIC memory. Access to the NIC can be by using * outsw() or via the memory mapped interface to the same register. * Writes have to be in word units; byte accesses won't work and may cause * the NIC to behave wierdly. Long word accesses are permitted if the ASIC * allows it. */ static u_short ed_hpp_write_mbufs(struct ed_softc *sc, struct mbuf *m, int dst) { int len, wantbyte; unsigned short total_len; unsigned char savebyte[2]; volatile u_short * const d = (volatile u_short *) sc->hpp_mem_start; int use_32bit_accesses = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS); /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* program the write address in RAM */ outw(sc->asic_addr + ED_HPP_PAGE_0, dst); if (sc->hpp_mem_start) /* enable memory mapped I/O */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options & ~(ED_HPP_OPTION_MEM_DISABLE | ED_HPP_OPTION_BOOT_ROM_ENB)); wantbyte = 0; total_len = 0; if (sc->hpp_mem_start) { /* Memory mapped I/O port */ while (m) { total_len += (len = m->m_len); if (len) { caddr_t data = mtod(m, caddr_t); /* finish the last word of the previous mbuf */ if (wantbyte) { savebyte[1] = *data; *d = *((ushort *) savebyte); data++; len--; wantbyte = 0; } /* output contiguous words */ if ((len > 3) && (use_32bit_accesses)) { volatile u_long *const dl = (volatile u_long *) d; u_long *sl = (u_long *) data; u_long *fence = sl + (len >> 2); while (sl < fence) *dl = *sl++; data += (len & ~3); len &= 3; } /* finish off remain 16 bit writes */ if (len > 1) { u_short *s = (u_short *) data; u_short *fence = s + (len >> 1); while (s < fence) *d = *s++; data += (len & ~1); len &= 1; } /* save last byte if needed */ if (wantbyte = (len == 1)) savebyte[0] = *data; } m = m->m_next; /* to next mbuf */ } if (wantbyte) /* write last byte */ *d = *((u_short *) savebyte); } else { /* use programmed I/O */ while (m) { total_len += (len = m->m_len); if (len) { caddr_t data = mtod(m, caddr_t); /* finish the last word of the previous mbuf */ if (wantbyte) { savebyte[1] = *data; outw(sc->asic_addr + ED_HPP_PAGE_4, *((u_short *)savebyte)); data++; len--; wantbyte = 0; } /* output contiguous words */ if ((len > 3) && use_32bit_accesses) { outsl(sc->asic_addr + ED_HPP_PAGE_4, data, len >> 2); data += (len & ~3); len &= 3; } /* finish off remaining 16 bit accesses */ if (len > 1) { outsw(sc->asic_addr + ED_HPP_PAGE_4, data, len >> 1); data += (len & ~1); len &= 1; } if (wantbyte = (len == 1)) savebyte[0] = *data; } /* if len != 0 */ m = m->m_next; } if (wantbyte) /* spit last byte */ outw(sc->asic_addr + ED_HPP_PAGE_4, *(u_short *)savebyte); } if (sc->hpp_mem_start) /* turn off memory mapped i/o */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options); return (total_len); } static void ed_setrcr(sc) struct ed_softc *sc; { struct ifnet *ifp = (struct ifnet *)sc; int i; /* set page 1 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STP); if (ifp->if_flags & IFF_PROMISC) { /* * Reconfigure the multicast filter. */ for (i = 0; i < 8; i++) outb(sc->nic_addr + ED_P1_MAR0 + i, 0xff); /* * And turn on promiscuous mode. Also enable reception of * runts and packets with CRC & alignment errors. */ /* Set page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); outb(sc->nic_addr + ED_P0_RCR, ED_RCR_PRO | ED_RCR_AM | ED_RCR_AB | ED_RCR_AR | ED_RCR_SEP); } else { /* set up multicast addresses and filter modes */ if (ifp->if_flags & IFF_MULTICAST) { u_long mcaf[2]; if (ifp->if_flags & IFF_ALLMULTI) { mcaf[0] = 0xffffffff; mcaf[1] = 0xffffffff; } else ds_getmcaf(sc, mcaf); /* * Set multicast filter on chip. */ for (i = 0; i < 8; i++) outb(sc->nic_addr + ED_P1_MAR0 + i, ((u_char *) mcaf)[i]); /* Set page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); outb(sc->nic_addr + ED_P0_RCR, ED_RCR_AM | ED_RCR_AB); } else { /* * Initialize multicast address hashing registers to * not accept multicasts. */ for (i = 0; i < 8; ++i) outb(sc->nic_addr + ED_P1_MAR0 + i, 0x00); /* Set page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); outb(sc->nic_addr + ED_P0_RCR, ED_RCR_AB); } } /* * Start interface. */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); } /* * Compute crc for ethernet address */ static u_long ds_crc(ep) u_char *ep; { #define POLYNOMIAL 0x04c11db6 register u_long crc = 0xffffffffL; register int carry, i, j; register u_char b; for (i = 6; --i >= 0;) { b = *ep++; for (j = 8; --j >= 0;) { carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); crc <<= 1; b >>= 1; if (carry) crc = ((crc ^ POLYNOMIAL) | carry); } } return crc; #undef POLYNOMIAL } /* * Compute the multicast address filter from the * list of multicast addresses we need to listen to. */ static void ds_getmcaf(sc, mcaf) struct ed_softc *sc; u_long *mcaf; { register u_int index; register u_char *af = (u_char *) mcaf; register struct ether_multi *enm; register struct ether_multistep step; mcaf[0] = 0; mcaf[1] = 0; ETHER_FIRST_MULTI(step, &sc->arpcom, enm); while (enm != NULL) { if (bcmp(enm->enm_addrlo, enm->enm_addrhi, 6) != 0) { mcaf[0] = 0xffffffff; mcaf[1] = 0xffffffff; return; } index = ds_crc(enm->enm_addrlo) >> 26; af[index >> 3] |= 1 << (index & 7); ETHER_NEXT_MULTI(step, enm); } } diff --git a/sys/dev/ep/if_ep.c b/sys/dev/ep/if_ep.c index 7840e8b1a809..6efbe85214a5 100644 --- a/sys/dev/ep/if_ep.c +++ b/sys/dev/ep/if_ep.c @@ -1,1626 +1,1607 @@ /* * Copyright (c) 1994 Herb Peyerl * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Herb Peyerl. * 4. The name of Herb Peyerl may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * if_ep.c,v 1.19 1995/01/24 20:53:45 davidg Exp */ /* * Modified from the FreeBSD 1.1.5.1 version by: * Andres Vega Garcia * INRIA - Sophia Antipolis, France * avega@sophia.inria.fr */ /* - * $Id: if_ep.c,v 1.52 1996/07/27 12:40:31 amurai Exp $ + * $Id: if_ep.c,v 1.53 1996/09/06 23:07:33 phk Exp $ * * Promiscuous mode added and interrupt logic slightly changed * to reduce the number of adapter failures. Transceiver select * logic changed to use value from EEPROM. Autoconfiguration * features added. * Done by: * Serge Babkin * Chelindbank (Chelyabinsk, Russia) * babkin@hq.icb.chel.su */ /* * Pccard support for 3C589 by: * HAMADA Naoki * nao@tom-yam.or.jp */ #include "ep.h" #if NEP > 0 #include "bpfilter.h" #include #if defined(__FreeBSD__) #include #include #include #endif #include #include #include #include #include #if defined(__NetBSD__) #include #endif #include #include #include #ifdef INET #include #include #include #include #include #endif #ifdef IPX #include #include #endif #ifdef NS #include #include #endif #if NBPFILTER > 0 #include #include #endif #if defined(__FreeBSD__) #include #endif #include #include #include /* Exported variables */ u_long ep_unit; int ep_boards; struct ep_board ep_board[EP_MAX_BOARDS + 1]; static int eeprom_rdy __P((struct ep_softc *sc)); static int ep_isa_probe __P((struct isa_device *)); static struct ep_board * ep_look_for_board_at __P((struct isa_device *is)); static int ep_isa_attach __P((struct isa_device *)); static int epioctl __P((struct ifnet * ifp, int, caddr_t)); static void epmbuffill __P((caddr_t, int)); static void epmbufempty __P((struct ep_softc *)); static void epinit __P((struct ep_softc *)); static void epread __P((struct ep_softc *)); void epreset __P((int)); static void epstart __P((struct ifnet *)); static void epstop __P((struct ep_softc *)); static void epwatchdog __P((struct ifnet *)); #if 0 static int send_ID_sequence __P((int)); #endif static int get_eeprom_data __P((int, int)); static struct ep_softc* ep_softc[NEP]; static int ep_current_tag = EP_LAST_TAG + 1; static char *ep_conn_type[] = {"UTP", "AUI", "???", "BNC"}; #define ep_ftst(f) (sc->stat&(f)) #define ep_fset(f) (sc->stat|=(f)) #define ep_frst(f) (sc->stat&=~(f)) struct isa_driver epdriver = { ep_isa_probe, ep_isa_attach, "ep", 0 }; #include "crd.h" #if NCRD > 0 #include "apm.h" #include #include #include #include /* * PC-Card (PCMCIA) specific code. */ static int card_intr __P((struct pccard_dev *)); static void ep_unload __P((struct pccard_dev *)); static void ep_suspend __P((struct pccard_dev *)); static int ep_pccard_init __P((struct pccard_dev *, int)); static int ep_pccard_attach __P((struct pccard_dev *)); static struct pccard_drv ep_info = { "ep", card_intr, ep_unload, ep_suspend, ep_pccard_init, 0, /* Attributes - presently unused */ &net_imask }; /* Resume is done by executing ep_pccard_init(dp, 0). */ static void ep_suspend(dp) struct pccard_dev *dp; { struct ep_softc *sc = ep_softc[dp->isahd.id_unit]; printf("ep%d: suspending\n", dp->isahd.id_unit); sc->gone = 1; } /* * */ static int ep_pccard_init(dp, first) struct pccard_dev *dp; int first; { struct isa_device *is = &dp->isahd; struct ep_softc *sc = ep_softc[is->id_unit]; struct ep_board *epb; int i; epb = &ep_board[is->id_unit]; if (sc == 0) { if ((sc = ep_alloc(is->id_unit, epb)) == 0) { return (ENXIO); } ep_unit++; } /* get_e() requires these. */ sc->ep_io_addr = is->id_iobase; sc->unit = is->id_unit; epb->epb_addr = is->id_iobase; epb->epb_used = 1; epb->prod_id = get_e(sc, EEPROM_PROD_ID); if (epb->prod_id != 0x9058) { /* 3C589's product id */ if (first) { printf("ep%d: failed to come ready.\n", is->id_unit); } else { printf("ep%d: failed to resume.\n", is->id_unit); } return (ENXIO); } epb->res_cfg = get_e(sc, EEPROM_RESOURCE_CFG); for (i = 0; i < 3; i++) { sc->epb->eth_addr[i] = get_e(sc, EEPROM_NODE_ADDR_0 + i); } if (first) { if (ep_pccard_attach(dp) == 0) { return (ENXIO); } sc->arpcom.ac_if.if_snd.ifq_maxlen = ifqmaxlen; } if (!first) { sc->gone = 0; printf("ep%d: resumed.\n", is->id_unit); epinit(sc); } return (0); } static int ep_pccard_attach(dp) struct pccard_dev *dp; { struct isa_device *is = &dp->isahd; struct ep_softc *sc = ep_softc[is->id_unit]; u_short config; sc->ep_connectors = 0; config = inw(IS_BASE + EP_W0_CONFIG_CTRL); if (config & IS_BNC) { sc->ep_connectors |= BNC; } if (config & IS_UTP) { sc->ep_connectors |= UTP; } if (!(sc->ep_connectors & 7)) printf("no connectors!"); sc->ep_connector = inw(BASE + EP_W0_ADDRESS_CFG) >> ACF_CONNECTOR_BITS; /* ROM size = 0, ROM base = 0 */ /* For now, ignore AUTO SELECT feature of 3C589B and later. */ outw(BASE + EP_W0_ADDRESS_CFG, get_e(sc, EEPROM_ADDR_CFG) & 0xc000); /* Fake IRQ must be 3 */ outw(BASE + EP_W0_RESOURCE_CFG, (sc->epb->res_cfg & 0x0fff) | 0x3000); outw(BASE + EP_W0_PRODUCT_ID, sc->epb->prod_id); ep_attach(sc); return 1; } static void ep_unload(dp) struct pccard_dev *dp; { struct ep_softc *sc = ep_softc[dp->isahd.id_unit]; if (sc->gone) { printf("ep%d: already unloaded\n", dp->isahd.id_unit); return; } sc->arpcom.ac_if.if_flags &= ~IFF_RUNNING; sc->gone = 1; printf("ep%d: unload\n", dp->isahd.id_unit); } /* * card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int card_intr(dp) struct pccard_dev *dp; { epintr(dp->isahd.id_unit); return(1); } #endif /* NCRD > 0 */ static int eeprom_rdy(sc) struct ep_softc *sc; { int i; for (i = 0; is_eeprom_busy(BASE) && i < MAX_EEPROMBUSY; i++); if (i >= MAX_EEPROMBUSY) { printf("ep%d: eeprom failed to come ready.\n", sc->unit); return (0); } return (1); } static struct ep_board * ep_look_for_board_at(is) struct isa_device *is; { int data, i, j, id_port = ELINK_ID_PORT; int count = 0; if (ep_current_tag == (EP_LAST_TAG + 1)) { /* Come here just one time */ ep_current_tag--; /* Look for the ISA boards. Init and leave them actived */ outb(id_port, 0); outb(id_port, 0); elink_idseq(0xCF); elink_reset(); DELAY(10000); for (i = 0; i < EP_MAX_BOARDS; i++) { outb(id_port, 0); outb(id_port, 0); elink_idseq(0xCF); data = get_eeprom_data(id_port, EEPROM_MFG_ID); if (data != MFG_ID) break; /* resolve contention using the Ethernet address */ for (j = 0; j < 3; j++) get_eeprom_data(id_port, j); /* and save this address for later use */ for (j = 0; j < 3; j++) ep_board[ep_boards].eth_addr[j] = get_eeprom_data(id_port, j); ep_board[ep_boards].res_cfg = get_eeprom_data(id_port, EEPROM_RESOURCE_CFG); ep_board[ep_boards].prod_id = get_eeprom_data(id_port, EEPROM_PROD_ID); ep_board[ep_boards].epb_used = 0; ep_board[ep_boards].epb_addr = (get_eeprom_data(id_port, EEPROM_ADDR_CFG) & 0x1f) * 0x10 + 0x200; if(ep_board[ep_boards].epb_addr > 0x3E0) /* Board in EISA configuration mode */ continue; outb(id_port, ep_current_tag); /* tags board */ outb(id_port, ACTIVATE_ADAPTER_TO_CONFIG); ep_boards++; count++; ep_current_tag--; } ep_board[ep_boards].epb_addr = 0; if (count) { printf("%d 3C5x9 board(s) on ISA found at", count); for (j = 0; ep_board[j].epb_addr; j++) if (ep_board[j].epb_addr <= 0x3E0) printf(" 0x%x", ep_board[j].epb_addr); printf("\n"); } } /* we have two cases: * * 1. Device was configured with 'port ?' * In this case we search for the first unused card in list * * 2. Device was configured with 'port xxx' * In this case we search for the unused card with that address * */ if(IS_BASE==-1) { /* port? */ for (i = 0; ep_board[i].epb_addr && ep_board[i].epb_used; i++); if(ep_board[i].epb_addr==0) return 0; IS_BASE=ep_board[i].epb_addr; ep_board[i].epb_used=1; return &ep_board[i]; } else { for (i=0; ep_board[i].epb_addr && ep_board[i].epb_addr != IS_BASE; i++); if( ep_board[i].epb_used || ep_board[i].epb_addr != IS_BASE) return 0; if (inw(IS_BASE + EP_W0_EEPROM_COMMAND) & EEPROM_TST_MODE) printf("ep%d: 3c5x9 at 0x%x in PnP mode. Disable PnP mode!\n", is->id_unit, IS_BASE); ep_board[i].epb_used=1; return &ep_board[i]; } } /* * get_e: gets a 16 bits word from the EEPROM. we must have set the window * before */ u_int16_t get_e(sc, offset) struct ep_softc *sc; int offset; { if (!eeprom_rdy(sc)) return (0xffff); outw(BASE + EP_W0_EEPROM_COMMAND, EEPROM_CMD_RD | offset); if (!eeprom_rdy(sc)) return (0xffff); return (inw(BASE + EP_W0_EEPROM_DATA)); } struct ep_softc * ep_alloc(unit, epb) int unit; struct ep_board *epb; { struct ep_softc *sc; if (unit >= NEP) { printf("ep: unit number (%d) too high\n", unit); return NULL; } /* * Allocate a storage area for us */ if (ep_softc[unit]) { printf("ep%d: unit number already allocated to another " "adaptor\n", unit); return NULL; } sc = malloc(sizeof(struct ep_softc), M_DEVBUF, M_NOWAIT); if(!sc) { printf("ep%d: cannot malloc!\n", unit); return NULL; } bzero(sc, sizeof(struct ep_softc)); ep_softc[unit] = sc; sc->unit = unit; sc->ep_io_addr = epb->epb_addr; sc->epb = epb; return(sc); } void ep_free(sc) struct ep_softc *sc; { ep_softc[sc->unit] = NULL; free(sc, M_DEVBUF); return; } int ep_isa_probe(is) struct isa_device *is; { struct ep_softc *sc; struct ep_board *epb; u_short k; #if NCRD > 0 pccard_add_driver(&ep_info); #endif /* NCRD > 0 */ if(( epb=ep_look_for_board_at(is) )==0) return (0); /* * Allocate a storage area for us */ sc = ep_alloc(ep_unit, epb); if( !sc ) return (0); is->id_unit = ep_unit++; /* * The iobase was found and MFG_ID was 0x6d50. PROD_ID should be * 0x9[0-f]50 */ GO_WINDOW(0); k = sc->epb->prod_id; if ((k & 0xf0ff) != (PROD_ID & 0xf0ff)) { printf("ep_isa_probe: ignoring model %04x\n", k); ep_free(sc); return (0); } k = sc->epb->res_cfg; k >>= 12; /* Now we have two cases again: * * 1. Device was configured with 'irq?' * In this case we use irq read from the board * * 2. Device was configured with 'irq xxx' * In this case we set up the board to use specified interrupt * */ if(is->id_irq==0) { /* irq? */ is->id_irq= 1 << ( (k==2) ? 9 : k ); } sc->stat = 0; /* 16 bit access */ /* By now, the adapter is already activated */ return (EP_IOSIZE); /* 16 bytes of I/O space used. */ } static int ep_isa_attach(is) struct isa_device *is; { struct ep_softc *sc = ep_softc[is->id_unit]; u_short config; int irq; sc->ep_connectors = 0; config = inw(IS_BASE + EP_W0_CONFIG_CTRL); if (config & IS_AUI) { sc->ep_connectors |= AUI; } if (config & IS_BNC) { sc->ep_connectors |= BNC; } if (config & IS_UTP) { sc->ep_connectors |= UTP; } if (!(sc->ep_connectors & 7)) printf("no connectors!"); sc->ep_connector = inw(BASE + EP_W0_ADDRESS_CFG) >> ACF_CONNECTOR_BITS; /* * Write IRQ value to board */ irq = ffs(is->id_irq) - 1; if(irq == -1) { printf(" invalid irq... cannot attach\n"); return 0; } GO_WINDOW(0); if(irq == 9) irq = 2; SET_IRQ(BASE, irq); ep_attach(sc); return 1; } int ep_attach(sc) struct ep_softc *sc; { struct ifaddr *ifa; struct ifnet *ifp = &sc->arpcom.ac_if; struct sockaddr_dl *sdl; u_short *p; int i; int attached; sc->gone = 0; attached = (ifp->if_softc != 0); printf("ep%d: ", sc->unit); /* * Current media type */ if(sc->ep_connectors & AUI) { printf("aui"); if(sc->ep_connectors & ~AUI) printf("/"); } if(sc->ep_connectors & UTP) { printf("utp"); if(sc->ep_connectors & BNC) printf("/"); } if(sc->ep_connectors & BNC) { printf("bnc"); } printf("[*%s*]", ep_conn_type[sc->ep_connector]); /* * Setup the station address */ p = (u_short *) & sc->arpcom.ac_enaddr; GO_WINDOW(2); for (i = 0; i < 3; i++) { p[i] = htons(sc->epb->eth_addr[i]); outw(BASE + EP_W2_ADDR_0 + (i * 2), ntohs(p[i])); } printf(" address %6D\n", sc->arpcom.ac_enaddr, ":"); ifp->if_softc = sc; ifp->if_unit = sc->unit; ifp->if_name = "ep"; ifp->if_mtu = ETHERMTU; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_output = ether_output; ifp->if_start = epstart; ifp->if_ioctl = epioctl; ifp->if_watchdog = epwatchdog; if (!attached) { if_attach(ifp); ether_ifattach(ifp); } - /* device attach does transition from UNCONFIGURED to IDLE state */ - - /* - * Fill the hardware address into ifa_addr if we find an AF_LINK entry. - * We need to do this so bpf's can get the hardware addr of this card. - * netstat likes this too! - */ - ifa = ifp->if_addrlist; - while ((ifa != 0) && (ifa->ifa_addr != 0) && - (ifa->ifa_addr->sa_family != AF_LINK)) - ifa = ifa->ifa_next; - - if ((ifa != 0) && (ifa->ifa_addr != 0)) { - sdl = (struct sockaddr_dl *) ifa->ifa_addr; - sdl->sdl_type = IFT_ETHER; - sdl->sdl_alen = ETHER_ADDR_LEN; - sdl->sdl_slen = 0; - bcopy(sc->arpcom.ac_enaddr, LLADDR(sdl), ETHER_ADDR_LEN); - } /* we give some initial parameters */ sc->rx_avg_pkt = 128; /* * NOTE: In all this I multiply everything by 64. * W_s = the speed the CPU is able to write to the TX FIFO. * T_s = the speed the board sends the info to the Ether. * W_s/T_s = 16 (represents 16/64) => W_s = 25 % of T_s. * This will give us for a packet of 1500 bytes * tx_start_thresh=1125 and for a pkt of 64 bytes tx_start_threshold=48. * We prefer to start thinking the CPU is much slower than the Ethernet * transmission. */ sc->tx_rate = TX_INIT_RATE; sc->tx_counter = 0; sc->rx_latency = RX_INIT_LATENCY; sc->rx_early_thresh = RX_INIT_EARLY_THRESH; #ifdef EP_LOCAL_STATS sc->rx_no_first = sc->rx_no_mbuf = sc->rx_bpf_disc = sc->rx_overrunf = sc->rx_overrunl = sc->tx_underrun = 0; #endif ep_fset(F_RX_FIRST); sc->top = sc->mcur = 0; #if NBPFILTER > 0 if (!attached) { bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); } #endif return 0; } /* * The order in here seems important. Otherwise we may not receive * interrupts. ?! */ static void epinit(sc) struct ep_softc *sc; { register struct ifnet *ifp = &sc->arpcom.ac_if; int s, i, j; if (sc->gone) return; /* if (ifp->if_addrlist == (struct ifaddr *) 0) return; */ s = splimp(); while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); GO_WINDOW(0); outw(BASE + EP_COMMAND, STOP_TRANSCEIVER); GO_WINDOW(4); outw(BASE + EP_W4_MEDIA_TYPE, DISABLE_UTP); GO_WINDOW(0); /* Disable the card */ outw(BASE + EP_W0_CONFIG_CTRL, 0); /* Enable the card */ outw(BASE + EP_W0_CONFIG_CTRL, ENABLE_DRQ_IRQ); GO_WINDOW(2); /* Reload the ether_addr. */ for (i = 0; i < 6; i++) outb(BASE + EP_W2_ADDR_0 + i, sc->arpcom.ac_enaddr[i]); outw(BASE + EP_COMMAND, RX_RESET); outw(BASE + EP_COMMAND, TX_RESET); /* Window 1 is operating window */ GO_WINDOW(1); for (i = 0; i < 31; i++) inb(BASE + EP_W1_TX_STATUS); /* get rid of stray intr's */ outw(BASE + EP_COMMAND, ACK_INTR | 0xff); outw(BASE + EP_COMMAND, SET_RD_0_MASK | S_5_INTS); outw(BASE + EP_COMMAND, SET_INTR_MASK | S_5_INTS); if(ifp->if_flags & IFF_PROMISC) outw(BASE + EP_COMMAND, SET_RX_FILTER | FIL_INDIVIDUAL | FIL_GROUP | FIL_BRDCST | FIL_ALL); else outw(BASE + EP_COMMAND, SET_RX_FILTER | FIL_INDIVIDUAL | FIL_GROUP | FIL_BRDCST); /* * S.B. * * Now behavior was slightly changed: * * if any of flags link[0-2] is used and its connector is * physically present the following connectors are used: * * link0 - AUI * highest precedence * link1 - BNC * link2 - UTP * lowest precedence * * If none of them is specified then * connector specified in the EEPROM is used * (if present on card or AUI if not). * */ /* Set the xcvr. */ if(ifp->if_flags & IFF_LINK0 && sc->ep_connectors & AUI) { i = ACF_CONNECTOR_AUI; } else if(ifp->if_flags & IFF_LINK1 && sc->ep_connectors & BNC) { i = ACF_CONNECTOR_BNC; } else if(ifp->if_flags & IFF_LINK2 && sc->ep_connectors & UTP) { i = ACF_CONNECTOR_UTP; } else { i = sc->ep_connector; } GO_WINDOW(0); j = inw(BASE + EP_W0_ADDRESS_CFG) & 0x3fff; outw(BASE + EP_W0_ADDRESS_CFG, j | (i << ACF_CONNECTOR_BITS)); switch(i) { case ACF_CONNECTOR_UTP: if(sc->ep_connectors & UTP) { GO_WINDOW(4); outw(BASE + EP_W4_MEDIA_TYPE, ENABLE_UTP); } break; case ACF_CONNECTOR_BNC: if(sc->ep_connectors & BNC) { outw(BASE + EP_COMMAND, START_TRANSCEIVER); DELAY(1000); } break; case ACF_CONNECTOR_AUI: /* nothing to do */ break; default: printf("ep%d: strange connector type in EEPROM: assuming AUI\n", sc->unit); break; } outw(BASE + EP_COMMAND, RX_ENABLE); outw(BASE + EP_COMMAND, TX_ENABLE); ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; /* just in case */ sc->tx_rate = TX_INIT_RATE; sc->tx_counter = 0; sc->rx_latency = RX_INIT_LATENCY; sc->rx_early_thresh = RX_INIT_EARLY_THRESH; #ifdef EP_LOCAL_STATS sc->rx_no_first = sc->rx_no_mbuf = sc->rx_bpf_disc = sc->rx_overrunf = sc->rx_overrunl = sc->tx_underrun = 0; #endif ep_fset(F_RX_FIRST); ep_frst(F_RX_TRAILER); if (sc->top) { m_freem(sc->top); sc->top = sc->mcur = 0; } outw(BASE + EP_COMMAND, SET_RX_EARLY_THRESH | sc->rx_early_thresh); /* * These clever computations look very interesting * but the fixed threshold gives near no output errors * and if it as low as 16 bytes it gives the max. throughput. * We think that processor is anyway quicker than Ethernet * (and this should be true for any 386 and higher) */ outw(BASE + EP_COMMAND, SET_TX_START_THRESH | 16); /* * Store up a bunch of mbuf's for use later. (MAX_MBS). First we free up * any that we had in case we're being called from intr or somewhere * else. */ sc->last_mb = 0; sc->next_mb = 0; epmbuffill((caddr_t) sc, 0); GO_WINDOW(1); epstart(ifp); splx(s); } static const char padmap[] = {0, 3, 2, 1}; static void epstart(ifp) struct ifnet *ifp; { register struct ep_softc *sc = ifp->if_softc; register u_int len; register struct mbuf *m; struct mbuf *top; int s, pad; if (sc->gone) { return; } s = splimp(); if (ifp->if_flags & IFF_OACTIVE) { splx(s); return; } startagain: /* Sneak a peek at the next packet */ m = ifp->if_snd.ifq_head; if (m == 0) { splx(s); return; } for (len = 0, top = m; m; m = m->m_next) len += m->m_len; pad = padmap[len & 3]; /* * The 3c509 automatically pads short packets to minimum ethernet length, * but we drop packets that are too large. Perhaps we should truncate * them instead? */ if (len + pad > ETHER_MAX_LEN) { /* packet is obviously too large: toss it */ ++ifp->if_oerrors; IF_DEQUEUE(&ifp->if_snd, m); m_freem(m); goto readcheck; } if (inw(BASE + EP_W1_FREE_TX) < len + pad + 4) { /* no room in FIFO */ outw(BASE + EP_COMMAND, SET_TX_AVAIL_THRESH | (len + pad + 4)); ifp->if_flags |= IFF_OACTIVE; splx(s); return; } IF_DEQUEUE(&ifp->if_snd, m); outw(BASE + EP_W1_TX_PIO_WR_1, len); outw(BASE + EP_W1_TX_PIO_WR_1, 0x0); /* Second dword meaningless */ /* compute the Tx start threshold for this packet */ sc->tx_start_thresh = len = (((len * (64 - sc->tx_rate)) >> 6) & ~3) + 16; #if 0 /* * The following string does something strange with the card and * we get a lot of output errors due to it so it's commented out * and we use fixed threshold (see above) */ outw(BASE + EP_COMMAND, SET_TX_START_THRESH | len); #endif for (top = m; m != 0; m = m->m_next) if(ep_ftst(F_ACCESS_32_BITS)) { outsl(BASE + EP_W1_TX_PIO_WR_1, mtod(m, caddr_t), m->m_len / 4); if (m->m_len & 3) outsb(BASE + EP_W1_TX_PIO_WR_1, mtod(m, caddr_t) + (m->m_len & (~3)), m->m_len & 3); } else { outsw(BASE + EP_W1_TX_PIO_WR_1, mtod(m, caddr_t), m->m_len / 2); if (m->m_len & 1) outb(BASE + EP_W1_TX_PIO_WR_1, *(mtod(m, caddr_t) + m->m_len - 1)); } while (pad--) outb(BASE + EP_W1_TX_PIO_WR_1, 0); /* Padding */ #if NBPFILTER > 0 if (ifp->if_bpf) { bpf_mtap(ifp, top); } #endif ifp->if_timer=2; ifp->if_opackets++; m_freem(top); /* * Every 1024*4 packets we increment the tx_rate if we haven't had * errors, that in the case it has abnormaly goten too low */ if (!(++sc->tx_counter & (1024 * 4 - 1)) && sc->tx_rate < TX_INIT_MAX_RATE) sc->tx_rate++; /* * Is another packet coming in? We don't want to overflow the tiny RX * fifo. */ readcheck: if (inw(BASE + EP_W1_RX_STATUS) & RX_BYTES_MASK) { /* * we check if we have packets left, in that case we prepare to come * back later */ if (ifp->if_snd.ifq_head) { outw(BASE + EP_COMMAND, SET_TX_AVAIL_THRESH | sc->tx_start_thresh); } splx(s); return; } goto startagain; } void epintr(unit) int unit; { register struct ep_softc *sc = ep_softc[unit]; if (sc->gone) { return; } ep_intr(sc); } void ep_intr(arg) void *arg; { struct ep_softc *sc; register int status; struct ifnet *ifp; int x; x=splbio(); sc = (struct ep_softc *)arg; ifp = &sc->arpcom.ac_if; outw(BASE + EP_COMMAND, SET_INTR_MASK); /* disable all Ints */ rescan: while ((status = inw(BASE + EP_STATUS)) & S_5_INTS) { /* first acknowledge all interrupt sources */ outw(BASE + EP_COMMAND, ACK_INTR | (status & S_MASK)); if (status & (S_RX_COMPLETE | S_RX_EARLY)) { epread(sc); continue; } if (status & S_TX_AVAIL) { /* we need ACK */ ifp->if_timer=0; ifp->if_flags &= ~IFF_OACTIVE; GO_WINDOW(1); inw(BASE + EP_W1_FREE_TX); epstart(ifp); } if (status & S_CARD_FAILURE) { ifp->if_timer=0; #ifdef EP_LOCAL_STATS printf("\nep%d:\n\tStatus: %x\n", sc->unit, status); GO_WINDOW(4); printf("\tFIFO Diagnostic: %x\n", inw(BASE + EP_W4_FIFO_DIAG)); printf("\tStat: %x\n", sc->stat); printf("\tIpackets=%d, Opackets=%d\n", ifp->if_ipackets, ifp->if_opackets); printf("\tNOF=%d, NOMB=%d, BPFD=%d, RXOF=%d, RXOL=%d, TXU=%d\n", sc->rx_no_first, sc->rx_no_mbuf, sc->rx_bpf_disc, sc->rx_overrunf, sc->rx_overrunl, sc->tx_underrun); #else #ifdef DIAGNOSTIC printf("ep%d: Status: %x (input buffer overflow)\n", sc->unit, status); #else ++ifp->if_ierrors; #endif #endif epinit(sc); splx(x); return; } if (status & S_TX_COMPLETE) { ifp->if_timer=0; /* we need ACK. we do it at the end */ /* * We need to read TX_STATUS until we get a 0 status in order to * turn off the interrupt flag. */ while ((status = inb(BASE + EP_W1_TX_STATUS)) & TXS_COMPLETE) { if (status & TXS_SUCCES_INTR_REQ); else if (status & (TXS_UNDERRUN | TXS_JABBER | TXS_MAX_COLLISION)) { outw(BASE + EP_COMMAND, TX_RESET); if (status & TXS_UNDERRUN) { if (sc->tx_rate > 1) { sc->tx_rate--; /* Actually in steps of 1/64 */ sc->tx_counter = 0; /* We reset it */ } #ifdef EP_LOCAL_STATS sc->tx_underrun++; #endif } else { if (status & TXS_JABBER); else /* TXS_MAX_COLLISION - we shouldn't get here */ ++ifp->if_collisions; } ++ifp->if_oerrors; outw(BASE + EP_COMMAND, TX_ENABLE); /* * To have a tx_avail_int but giving the chance to the * Reception */ if (ifp->if_snd.ifq_head) { outw(BASE + EP_COMMAND, SET_TX_AVAIL_THRESH | 8); } } outb(BASE + EP_W1_TX_STATUS, 0x0); /* pops up the next * status */ } /* while */ ifp->if_flags &= ~IFF_OACTIVE; GO_WINDOW(1); inw(BASE + EP_W1_FREE_TX); epstart(ifp); } /* end TX_COMPLETE */ } outw(BASE + EP_COMMAND, C_INTR_LATCH); /* ACK int Latch */ if ((status = inw(BASE + EP_STATUS)) & S_5_INTS) goto rescan; /* re-enable Ints */ outw(BASE + EP_COMMAND, SET_INTR_MASK | S_5_INTS); splx(x); } static void epread(sc) register struct ep_softc *sc; { struct ether_header *eh; struct mbuf *top, *mcur, *m; struct ifnet *ifp; int lenthisone; short rx_fifo2, status; register short delta; register short rx_fifo; ifp = &sc->arpcom.ac_if; status = inw(BASE + EP_W1_RX_STATUS); read_again: if (status & ERR_RX) { ++ifp->if_ierrors; if (status & ERR_RX_OVERRUN) { /* * we can think the rx latency is actually greather than we * expect */ #ifdef EP_LOCAL_STATS if (ep_ftst(F_RX_FIRST)) sc->rx_overrunf++; else sc->rx_overrunl++; #endif if (sc->rx_latency < ETHERMTU) sc->rx_latency += 16; } goto out; } rx_fifo = rx_fifo2 = status & RX_BYTES_MASK; if (ep_ftst(F_RX_FIRST)) { if (m = sc->mb[sc->next_mb]) { sc->mb[sc->next_mb] = 0; sc->next_mb = (sc->next_mb + 1) % MAX_MBS; m->m_data = m->m_pktdat; m->m_flags = M_PKTHDR; } else { MGETHDR(m, M_DONTWAIT, MT_DATA); if (!m) goto out; } sc->top = sc->mcur = top = m; #define EROUND ((sizeof(struct ether_header) + 3) & ~3) #define EOFF (EROUND - sizeof(struct ether_header)) top->m_data += EOFF; /* Read what should be the header. */ insw(BASE + EP_W1_RX_PIO_RD_1, mtod(top, caddr_t), sizeof(struct ether_header) / 2); top->m_len = sizeof(struct ether_header); rx_fifo -= sizeof(struct ether_header); sc->cur_len = rx_fifo2; } else { /* come here if we didn't have a complete packet last time */ top = sc->top; m = sc->mcur; sc->cur_len += rx_fifo2; if (ep_ftst(F_RX_TRAILER)) /* We don't read the trailer */ rx_fifo -= sizeof(struct ether_header); } /* Reads what is left in the RX FIFO */ while (rx_fifo > 0) { lenthisone = min(rx_fifo, M_TRAILINGSPACE(m)); if (lenthisone == 0) { /* no room in this one */ mcur = m; if (m = sc->mb[sc->next_mb]) { sc->mb[sc->next_mb] = 0; sc->next_mb = (sc->next_mb + 1) % MAX_MBS; } else { MGET(m, M_DONTWAIT, MT_DATA); if (!m) goto out; } if (rx_fifo >= MINCLSIZE) MCLGET(m, M_DONTWAIT); m->m_len = 0; mcur->m_next = m; lenthisone = min(rx_fifo, M_TRAILINGSPACE(m)); } if (ep_ftst(F_ACCESS_32_BITS)) { /* default for EISA configured cards*/ insl(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t) + m->m_len, lenthisone / 4); m->m_len += (lenthisone & ~3); if (lenthisone & 3) insb(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t) + m->m_len, lenthisone & 3); m->m_len += (lenthisone & 3); } else { insw(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t) + m->m_len, lenthisone / 2); m->m_len += lenthisone; if (lenthisone & 1) *(mtod(m, caddr_t) + m->m_len - 1) = inb(BASE + EP_W1_RX_PIO_RD_1); } rx_fifo -= lenthisone; } if (ep_ftst(F_RX_TRAILER)) {/* reads the trailer */ if (m = sc->mb[sc->next_mb]) { sc->mb[sc->next_mb] = 0; sc->next_mb = (sc->next_mb + 1) % MAX_MBS; m->m_data = m->m_pktdat; m->m_flags = M_PKTHDR; } else { MGETHDR(m, M_DONTWAIT, MT_DATA); if (!m) goto out; } insw(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t), sizeof(struct ether_header)); m->m_len = sizeof(struct ether_header); m->m_next = top; sc->top = top = m; /* XXX Accomodate for type and len from beginning of trailer */ sc->cur_len -= (2 * sizeof(u_short)); ep_frst(F_RX_TRAILER); goto all_pkt; } if (status & ERR_RX_INCOMPLETE) { /* we haven't received the complete * packet */ sc->mcur = m; #ifdef EP_LOCAL_STATS sc->rx_no_first++; /* to know how often we come here */ #endif /* * Re-compute rx_latency, the factor used is 1/4 to go up and 1/32 to * go down */ delta = rx_fifo2 - sc->rx_early_thresh; /* last latency seen LLS */ delta -= sc->rx_latency;/* LLS - estimated_latency */ if (delta >= 0) sc->rx_latency += (delta / 4); else sc->rx_latency += (delta / 32); ep_frst(F_RX_FIRST); if (!((status = inw(BASE + EP_W1_RX_STATUS)) & ERR_RX_INCOMPLETE)) { /* we see if by now, the packet has completly arrived */ goto read_again; } /* compute rx_early_threshold */ delta = (sc->rx_avg_pkt - sc->cur_len - sc->rx_latency - 16) & ~3; if (delta < MIN_RX_EARLY_THRESHL) delta = MIN_RX_EARLY_THRESHL; outw(BASE + EP_COMMAND, SET_RX_EARLY_THRESH | (sc->rx_early_thresh = delta)); return; } all_pkt: outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); /* * recompute average packet's length, the factor used is 1/8 to go down * and 1/32 to go up */ delta = sc->cur_len - sc->rx_avg_pkt; if (delta > 0) sc->rx_avg_pkt += (delta / 32); else sc->rx_avg_pkt += (delta / 8); delta = (sc->rx_avg_pkt - sc->rx_latency - 16) & ~3; if (delta < MIN_RX_EARLY_THRESHF) delta = MIN_RX_EARLY_THRESHF; sc->rx_early_thresh = delta; ++ifp->if_ipackets; ep_fset(F_RX_FIRST); ep_frst(F_RX_TRAILER); top->m_pkthdr.rcvif = &sc->arpcom.ac_if; top->m_pkthdr.len = sc->cur_len; #if NBPFILTER > 0 if (ifp->if_bpf) { bpf_mtap(ifp, top); /* * Note that the interface cannot be in promiscuous mode if there are * no BPF listeners. And if we are in promiscuous mode, we have to * check if this packet is really ours. */ eh = mtod(top, struct ether_header *); if ((ifp->if_flags & IFF_PROMISC) && (eh->ether_dhost[0] & 1) == 0 && bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, sizeof(eh->ether_dhost)) != 0 && bcmp(eh->ether_dhost, etherbroadcastaddr, sizeof(eh->ether_dhost)) != 0) { if (sc->top) { m_freem(sc->top); sc->top = 0; } ep_fset(F_RX_FIRST); ep_frst(F_RX_TRAILER); #ifdef EP_LOCAL_STATS sc->rx_bpf_disc++; #endif while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); outw(BASE + EP_COMMAND, SET_RX_EARLY_THRESH | delta); return; } } #endif eh = mtod(top, struct ether_header *); m_adj(top, sizeof(struct ether_header)); ether_input(ifp, eh, top); if (!sc->mb[sc->next_mb]) epmbuffill((caddr_t) sc, 0); sc->top = 0; while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); outw(BASE + EP_COMMAND, SET_RX_EARLY_THRESH | delta); return; out: outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); if (sc->top) { m_freem(sc->top); sc->top = 0; #ifdef EP_LOCAL_STATS sc->rx_no_mbuf++; #endif } delta = (sc->rx_avg_pkt - sc->rx_latency - 16) & ~3; if (delta < MIN_RX_EARLY_THRESHF) delta = MIN_RX_EARLY_THRESHF; ep_fset(F_RX_FIRST); ep_frst(F_RX_TRAILER); while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); outw(BASE + EP_COMMAND, SET_RX_EARLY_THRESH | (sc->rx_early_thresh = delta)); } /* * Look familiar? */ static int epioctl(ifp, cmd, data) register struct ifnet *ifp; int cmd; caddr_t data; { register struct ifaddr *ifa = (struct ifaddr *) data; struct ep_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; int s, error = 0; s = splimp(); switch (cmd) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; /* netifs are BUSY when UP */ switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: epinit(sc); /* before arpwhohas */ arp_ifinit((struct arpcom *)ifp, ifa); break; #endif #ifdef IPX case AF_IPX: { register struct ipx_addr *ina = &(IA_SIPX(ifa)->sipx_addr); if (ipx_nullhost(*ina)) ina->x_host = *(union ipx_host *) (sc->arpcom.ac_enaddr); else { ifp->if_flags &= ~IFF_RUNNING; bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->arpcom.ac_enaddr, sizeof(sc->arpcom.ac_enaddr)); } epinit(sc); break; } #endif #ifdef NS case AF_NS: { register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); if (ns_nullhost(*ina)) ina->x_host = *(union ns_host *) (sc->arpcom.ac_enaddr); else { ifp->if_flags &= ~IFF_RUNNING; bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->arpcom.ac_enaddr, sizeof(sc->arpcom.ac_enaddr)); } epinit(sc); break; } #endif default: epinit(sc); break; } break; case SIOCGIFADDR: { struct sockaddr *sa; sa = (struct sockaddr *) & ifr->ifr_data; bcopy((caddr_t) sc->arpcom.ac_enaddr, (caddr_t) sa->sa_data, ETHER_ADDR_LEN); } break; case SIOCSIFFLAGS: if ((ifp->if_flags & IFF_UP) == 0 && ifp->if_flags & IFF_RUNNING) { ifp->if_flags &= ~IFF_RUNNING; epstop(sc); epmbufempty(sc); break; } else { /* reinitialize card on any parameter change */ epinit(sc); break; } /* NOTREACHED */ break; #ifdef notdef case SIOCGHWADDR: bcopy((caddr_t) sc->sc_addr, (caddr_t) & ifr->ifr_data, sizeof(sc->sc_addr)); break; #endif case SIOCSIFMTU: /* * Set the interface MTU. */ if (ifr->ifr_mtu > ETHERMTU) { error = EINVAL; } else { ifp->if_mtu = ifr->ifr_mtu; } break; case SIOCADDMULTI: case SIOCDELMULTI: /* Now this driver has no support for programmable * multicast filters. If some day it will gain this * support this part of code must be extended. */ error=0; break; default: error = EINVAL; } splx(s); return (error); } static void epwatchdog(ifp) struct ifnet *ifp; { struct ep_softc *sc = ifp->if_softc; /* printf("ep: watchdog\n"); log(LOG_ERR, "ep%d: watchdog\n", ifp->if_unit); ifp->if_oerrors++; */ if (sc->gone) { return; } ifp->if_flags &= ~IFF_OACTIVE; epstart(ifp); ep_intr(ifp->if_softc); } static void epstop(sc) struct ep_softc *sc; { if (sc->gone) { return; } outw(BASE + EP_COMMAND, RX_DISABLE); outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); outw(BASE + EP_COMMAND, TX_DISABLE); outw(BASE + EP_COMMAND, STOP_TRANSCEIVER); outw(BASE + EP_COMMAND, RX_RESET); outw(BASE + EP_COMMAND, TX_RESET); outw(BASE + EP_COMMAND, C_INTR_LATCH); outw(BASE + EP_COMMAND, SET_RD_0_MASK); outw(BASE + EP_COMMAND, SET_INTR_MASK); outw(BASE + EP_COMMAND, SET_RX_FILTER); } #if 0 static int send_ID_sequence(port) int port; { int cx, al; for (al = 0xff, cx = 0; cx < 255; cx++) { outb(port, al); al <<= 1; if (al & 0x100) al ^= 0xcf; } return (1); } #endif /* * We get eeprom data from the id_port given an offset into the eeprom. * Basically; after the ID_sequence is sent to all of the cards; they enter * the ID_CMD state where they will accept command requests. 0x80-0xbf loads * the eeprom data. We then read the port 16 times and with every read; the * cards check for contention (ie: if one card writes a 0 bit and another * writes a 1 bit then the host sees a 0. At the end of the cycle; each card * compares the data on the bus; if there is a difference then that card goes * into ID_WAIT state again). In the meantime; one bit of data is returned in * the AX register which is conveniently returned to us by inb(). Hence; we * read 16 times getting one bit of data with each read. */ static int get_eeprom_data(id_port, offset) int id_port; int offset; { int i, data = 0; outb(id_port, 0x80 + offset); DELAY(1000); for (i = 0; i < 16; i++) data = (data << 1) | (inw(id_port) & 1); return (data); } /* * We suppose this is always called inside a splimp(){...}splx() region */ static void epmbuffill(sp, dummy_arg) caddr_t sp; int dummy_arg; { struct ep_softc *sc = (struct ep_softc *) sp; int i; i = sc->last_mb; do { if (sc->mb[i] == NULL) MGET(sc->mb[i], M_DONTWAIT, MT_DATA); if (sc->mb[i] == NULL) break; i = (i + 1) % MAX_MBS; } while (i != sc->next_mb); sc->last_mb = i; } static void epmbufempty(sc) struct ep_softc *sc; { int s, i; s = splimp(); for (i = 0; i < MAX_MBS; i++) { if (sc->mb[i]) { m_freem(sc->mb[i]); sc->mb[i] = NULL; } } sc->last_mb = sc->next_mb = 0; splx(s); } #endif /* NEP > 0 */ diff --git a/sys/dev/fe/if_fe.c b/sys/dev/fe/if_fe.c index a8fdc3cd213a..9c2cf8e33c9f 100644 --- a/sys/dev/fe/if_fe.c +++ b/sys/dev/fe/if_fe.c @@ -1,3148 +1,3148 @@ /* * All Rights Reserved, Copyright (C) Fujitsu Limited 1995 * * This software may be used, modified, copied, distributed, and sold, in * both source and binary form provided that the above copyright, these * terms and the following disclaimer are retained. The name of the author * and/or the contributor may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND THE CONTRIBUTOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR THE CONTRIBUTOR BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* - * $Id: if_fe.c,v 1.20 1996/10/07 17:50:00 wollman Exp $ + * $Id: if_fe.c,v 1.21 1996/11/15 16:15:56 wollman Exp $ * * Device driver for Fujitsu MB86960A/MB86965A based Ethernet cards. * To be used with FreeBSD 2.x * Contributed by M. Sekiguchi. * * This version is intended to be a generic template for various * MB86960A/MB86965A based Ethernet cards. It currently supports * Fujitsu FMV-180 series for ISA and Allied-Telesis AT1700/RE2000 * series for ISA, as well as Fujitsu MBH10302 PC card. * There are some currently- * unused hooks embedded, which are primarily intended to support * other types of Ethernet cards, but the author is not sure whether * they are useful. * * This version also includes some alignments for * RE1000/RE1000+/ME1500 support. It is incomplete, however, since the * cards are not for AT-compatibles. (They are for PC98 bus -- a * proprietary bus architecture available only in Japan.) Further * work for PC98 version will be available as a part of FreeBSD(98) * project. * * This software is a derivative work of if_ed.c version 1.56 by David * Greenman available as a part of FreeBSD 2.0 RELEASE source distribution. * * The following lines are retained from the original if_ed.c: * * Copyright (C) 1993, David Greenman. This software may be used, modified, * copied, distributed, and sold, in both source and binary form provided * that the above copyright and these terms are retained. Under no * circumstances is the author responsible for the proper functioning * of this software, nor does the author assume any responsibility * for damages incurred with its use. */ /* * TODO: * o To support MBH10304 PC card. It is another MB8696x based * PCMCIA Ethernet card by Fujitsu, which is not compatible with * MBH10302. * o To merge FreeBSD(98) efforts into a single source file. * o To support ISA PnP auto configuration for FMV-183/184. * o To reconsider mbuf usage. * o To reconsider transmission buffer usage, including * transmission buffer size (currently 4KB x 2) and pros-and- * cons of multiple frame transmission. * o To test IPX codes. */ #include "isa.h" #include "fe.h" #include "crd.h" #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif /* IPX code is not tested. FIXME. */ #ifdef IPX #include #include #endif /* To be used with IPv6 package of INRIA. */ #ifdef INET6 /* IPv6 added by shin 96.2.6 */ #include #endif /* XNS code is not tested. FIXME. */ #ifdef NS #include #include #endif #if NBPFILTER > 0 #include #include #endif #include #include #include #include /* PCCARD suport */ #if NCRD > 0 #include #include #include #include #endif #include #include /* * This version of fe is an ISA device driver. * Override the following macro to adapt it to another bus. * (E.g., PC98.) */ #define DEVICE struct isa_device /* * Default settings for fe driver specific options. * They can be set in config file by "options" statements. */ /* * Debug control. * 0: No debug at all. All debug specific codes are stripped off. * 1: Silent. No debug messages are logged except emergent ones. * 2: Brief. Lair events and/or important information are logged. * 3: Detailed. Logs all information which *may* be useful for debugging. * 4: Trace. All actions in the driver is logged. Super verbose. */ #ifndef FE_DEBUG #define FE_DEBUG 1 #endif /* * Transmit just one packet per a "send" command to 86960. * This option is intended for performance test. An EXPERIMENTAL option. */ #ifndef FE_SINGLE_TRANSMISSION #define FE_SINGLE_TRANSMISSION 0 #endif /* * Device configuration flags. */ /* DLCR6 settings. */ #define FE_FLAGS_DLCR6_VALUE 0x007F /* Force DLCR6 override. */ #define FE_FLAGS_OVERRIDE_DLCR6 0x0080 /* Shouldn't these be defined somewhere else such as isa_device.h? */ #define NO_IOADDR (-1) #define NO_IRQ 0 /* * Data type for a multicast address filter on 8696x. */ struct fe_filter { u_char data [ FE_FILTER_LEN ]; }; /* * Special filter values. */ static struct fe_filter const fe_filter_nothing = { FE_FILTER_NOTHING }; static struct fe_filter const fe_filter_all = { FE_FILTER_ALL }; /* How many registers does an fe-supported adapter have at maximum? */ #define MAXREGISTERS 32 /* * fe_softc: per line info and status */ static struct fe_softc { /* Used by "common" codes. */ struct arpcom arpcom; /* Ethernet common */ /* Used by config codes. */ /* Set by probe() and not modified in later phases. */ char * typestr; /* printable name of the interface. */ u_short iobase; /* base I/O address of the adapter. */ u_short ioaddr [ MAXREGISTERS ]; /* I/O addresses of register. */ u_short txb_size; /* size of TX buffer, in bytes */ u_char proto_dlcr4; /* DLCR4 prototype. */ u_char proto_dlcr5; /* DLCR5 prototype. */ u_char proto_dlcr6; /* DLCR6 prototype. */ u_char proto_dlcr7; /* DLCR7 prototype. */ u_char proto_bmpr13; /* BMPR13 prototype. */ /* Vendor specific hooks. */ void ( * init )( struct fe_softc * ); /* Just before fe_init(). */ void ( * stop )( struct fe_softc * ); /* Just after fe_stop(). */ /* Transmission buffer management. */ u_short txb_free; /* free bytes in TX buffer */ u_char txb_count; /* number of packets in TX buffer */ u_char txb_sched; /* number of scheduled packets */ /* Excessive collision counter (see fe_tint() for details. */ u_char tx_excolls; /* # of excessive collisions. */ /* Multicast address filter management. */ u_char filter_change; /* MARs must be changed ASAP. */ struct fe_filter filter;/* new filter value. */ } fe_softc[NFE]; #define sc_if arpcom.ac_if #define sc_unit arpcom.ac_if.if_unit #define sc_enaddr arpcom.ac_enaddr /* Standard driver entry points. These can be static. */ static int fe_probe ( struct isa_device * ); static int fe_attach ( struct isa_device * ); static void fe_init ( int ); static int fe_ioctl ( struct ifnet *, int, caddr_t ); static void fe_start ( struct ifnet * ); static void fe_reset ( int ); static void fe_watchdog ( struct ifnet * ); /* Local functions. Order of declaration is confused. FIXME. */ static int fe_probe_fmv ( DEVICE *, struct fe_softc * ); static int fe_probe_ati ( DEVICE *, struct fe_softc * ); static void fe_init_ati ( struct fe_softc * ); static int fe_probe_gwy ( DEVICE *, struct fe_softc * ); #if NCRD > 0 static int fe_probe_mbh ( DEVICE *, struct fe_softc * ); static void fe_init_mbh ( struct fe_softc * ); static int fe_probe_tdk ( DEVICE *, struct fe_softc * ); #endif static int fe_get_packet ( struct fe_softc *, u_short ); static void fe_stop ( int ); static void fe_tint ( struct fe_softc *, u_char ); static void fe_rint ( struct fe_softc *, u_char ); static void fe_xmit ( struct fe_softc * ); static void fe_emptybuffer ( struct fe_softc * ); static void fe_write_mbufs ( struct fe_softc *, struct mbuf * ); static struct fe_filter fe_mcaf ( struct fe_softc * ); static int fe_hash ( u_char * ); static void fe_setmode ( struct fe_softc * ); static void fe_loadmar ( struct fe_softc * ); #if FE_DEBUG >= 1 static void fe_dump ( int, struct fe_softc *, char * ); #endif /* Driver struct used in the config code. This must be public (external.) */ struct isa_driver fedriver = { fe_probe, fe_attach, "fe", 1 /* It's safe to mark as "sensitive" */ }; /* * Fe driver specific constants which relate to 86960/86965. */ /* Interrupt masks */ #define FE_TMASK ( FE_D2_COLL16 | FE_D2_TXDONE ) #define FE_RMASK ( FE_D3_OVRFLO | FE_D3_CRCERR \ | FE_D3_ALGERR | FE_D3_SRTPKT | FE_D3_PKTRDY ) /* Maximum number of iterations for a receive interrupt. */ #define FE_MAX_RECV_COUNT ( ( 65536 - 2048 * 2 ) / 64 ) /* * Maximum size of SRAM is 65536, * minimum size of transmission buffer in fe is 2x2KB, * and minimum amount of received packet including headers * added by the chip is 64 bytes. * Hence FE_MAX_RECV_COUNT is the upper limit for number * of packets in the receive buffer. */ /* * Routines to access contiguous I/O ports. */ static void inblk ( struct fe_softc * sc, int offs, u_char * mem, int len ) { while ( --len >= 0 ) { *mem++ = inb( sc->ioaddr[ offs++ ] ); } } static void outblk ( struct fe_softc * sc, int offs, u_char const * mem, int len ) { while ( --len >= 0 ) { outb( sc->ioaddr[ offs++ ], *mem++ ); } } /* PCCARD Support */ #if NCRD > 0 /* * PC-Card (PCMCIA) specific code. */ static int fe_card_intr(struct pccard_dev *); /* Interrupt handler */ static void feunload(struct pccard_dev *); /* Disable driver */ static void fesuspend(struct pccard_dev *); /* Suspend driver */ static int feinit(struct pccard_dev *, int); /* init device */ static struct pccard_drv fe_info = { "fe", fe_card_intr, feunload, fesuspend, feinit, 0, /* Attributes - presently unused */ &net_imask /* Interrupt mask for device */ /* XXX - Should this also include net_imask? */ }; /* * Called when a power down is requested. Shuts down the * device and configures the device as unavailable (but * still loaded...). A resume is done by calling * feinit with first=0. This is called when the user suspends * the system, or the APM code suspends the system. */ static void fesuspend(struct pccard_dev *dp) { printf("fe%d: suspending\n", dp->isahd.id_unit); } /* * Initialize the device - called from Slot manager. * if first is set, then initially check for * the device's existence before initializing it. * Once initialized, the device table may be set up. */ static int feinit(struct pccard_dev *dp, int first) { /* validate unit number. */ struct fe_softc *sc; if (first) { if (dp->isahd.id_unit >= NFE) return (ENODEV); /* * Probe the device. If a value is returned, * the device was found at the location. */ #if FE_DEBUG >= 2 printf("Start Probe\n"); #endif sc = &fe_softc[dp->isahd.id_unit]; memcpy( sc->sc_enaddr, dp->misc, ETHER_ADDR_LEN ); if (fe_probe(&dp->isahd) == 0) return (ENXIO); #if FE_DEBUG >= 2 printf("Start attach\n"); #endif if (fe_attach(&dp->isahd) == 0) return (ENXIO); } /* * XXX TODO: * If it was initialized before, the device structure * should also be initialized. We should * reset (and possibly restart) the hardware, but * I am not sure of the best way to do this... */ return (0); } /* * feunload - unload the driver and clear the table. * XXX TODO: * This is usually called when the card is ejected, but * can be caused by a modunload of a controller driver. * The idea is to reset the driver's view of the device * and ensure that any driver entry points such as * read and write do not hang. */ static void feunload(struct pccard_dev *dp) { struct fe_softc *sc = &fe_softc[dp->isahd.id_unit]; printf("fe%d: unload\n", dp->isahd.id_unit); fe_stop(dp->isahd.id_unit); } /* * fe_card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int fe_card_intr(struct pccard_dev *dp) { feintr(dp->isahd.id_unit); return (1); } #endif /* NCRD > 0 */ /* * Hardware probe routines. */ /* How and where to probe; to support automatic I/O address detection. */ struct fe_probe_list { int ( * probe ) ( DEVICE *, struct fe_softc * ); u_short const * addresses; }; /* Lists of possible addresses. */ static u_short const fe_fmv_addr [] = { 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x300, 0x340, 0 }; static u_short const fe_ati_addr [] = { 0x240, 0x260, 0x280, 0x2A0, 0x300, 0x320, 0x340, 0x380, 0 }; static struct fe_probe_list const fe_probe_list [] = { { fe_probe_fmv, fe_fmv_addr }, { fe_probe_ati, fe_ati_addr }, #if NCRD > 0 { fe_probe_mbh, NULL }, /* PCMCIAs cannot be auto-detected. */ { fe_probe_tdk, NULL }, #endif { NULL, NULL } }; /* * Determine if the device is present * * on entry: * a pointer to an isa_device struct * on exit: * zero if device not found * or number of i/o addresses used (if found) */ static int fe_probe ( DEVICE * dev ) { #if NCRD > 0 static int fe_already_init; #endif struct fe_softc * sc; int u; int nports; struct fe_probe_list const * list; u_short const * addr; u_short single [ 2 ]; /* Initialize "minimum" parts of our softc. */ sc = &fe_softc[ dev->id_unit ]; sc->sc_unit = dev->id_unit; #if NCRD > 0 /* * If PC-Card probe required, then register driver with * slot manager. */ if (fe_already_init != 1) { pccard_add_driver(&fe_info); fe_already_init = 1; } #endif /* NCRD > 0 */ /* Probe each possibility, one at a time. */ for ( list = fe_probe_list; list->probe != NULL; list++ ) { if ( dev->id_iobase != NO_IOADDR ) { /* Probe one specific address. */ single[ 0 ] = dev->id_iobase; single[ 1 ] = 0; addr = single; } else if ( list->addresses != NULL ) { /* Auto detect. */ addr = list->addresses; } else { /* We need a list of addresses to do auto detect. */ continue; } /* Probe all possible addresses for the board. */ while ( *addr != 0 ) { /* See if the address is already in use. */ for ( u = 0; u < NFE; u++ ) { if ( fe_softc[u].iobase == *addr ) break; } #if FE_DEBUG >= 3 if ( u == NFE ) { log( LOG_INFO, "fe%d: probing %d at 0x%x\n", sc->sc_unit, list - fe_probe_list, *addr ); } else if ( u == sc->sc_unit ) { log( LOG_INFO, "fe%d: re-probing %d at 0x%x?\n", sc->sc_unit, list - fe_probe_list, *addr ); } else { log( LOG_INFO, "fe%d: skipping %d at 0x%x\n", sc->sc_unit, list - fe_probe_list, *addr ); } #endif /* Probe the address if it is free. */ if ( u == NFE || u == sc->sc_unit ) { /* Probe an address. */ sc->iobase = *addr; nports = list->probe( dev, sc ); if ( nports > 0 ) { /* Found. */ dev->id_iobase = *addr; return ( nports ); } sc->iobase = 0; } /* Try next. */ addr++; } } /* Probe failed. */ return ( 0 ); } /* * Check for specific bits in specific registers have specific values. */ struct fe_simple_probe_struct { u_char port; /* Offset from the base I/O address. */ u_char mask; /* Bits to be checked. */ u_char bits; /* Values to be compared against. */ }; static int fe_simple_probe ( struct fe_softc const * sc, struct fe_simple_probe_struct const * sp ) { struct fe_simple_probe_struct const * p; for ( p = sp; p->mask != 0; p++ ) { #if FE_DEBUG >=2 printf("Probe Port:%x,Value:%x,Mask:%x.Bits:%x\n", p->port,inb(sc->ioaddr[ p->port]),p->mask,p->bits); #endif if ( ( inb( sc->ioaddr[ p->port ] ) & p->mask ) != p->bits ) { return ( 0 ); } } return ( 1 ); } /* * Routines to read all bytes from the config EEPROM through MB86965A. * I'm not sure what exactly I'm doing here... I was told just to follow * the steps, and it worked. Could someone tell me why the following * code works? (Or, why all similar codes I tried previously doesn't * work.) FIXME. */ static void fe_strobe_eeprom ( u_short bmpr16 ) { /* * We must guarantee 800ns (or more) interval to access slow * EEPROMs. The following redundant code provides enough * delay with ISA timing. (Even if the bus clock is "tuned.") * Some modification will be needed on faster busses. */ outb( bmpr16, FE_B16_SELECT ); outb( bmpr16, FE_B16_SELECT ); outb( bmpr16, FE_B16_SELECT | FE_B16_CLOCK ); outb( bmpr16, FE_B16_SELECT | FE_B16_CLOCK ); outb( bmpr16, FE_B16_SELECT ); outb( bmpr16, FE_B16_SELECT ); } static void fe_read_eeprom ( struct fe_softc * sc, u_char * data ) { u_short bmpr16 = sc->ioaddr[ FE_BMPR16 ]; u_short bmpr17 = sc->ioaddr[ FE_BMPR17 ]; u_char n, val, bit; /* Read bytes from EEPROM; two bytes per an iteration. */ for ( n = 0; n < FE_EEPROM_SIZE / 2; n++ ) { /* Reset the EEPROM interface. */ outb( bmpr16, 0x00 ); outb( bmpr17, 0x00 ); /* Start EEPROM access. */ outb( bmpr16, FE_B16_SELECT ); outb( bmpr17, FE_B17_DATA ); fe_strobe_eeprom( bmpr16 ); /* Pass the iteration count to the chip. */ val = 0x80 | n; for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { outb( bmpr17, ( val & bit ) ? FE_B17_DATA : 0 ); fe_strobe_eeprom( bmpr16 ); } outb( bmpr17, 0x00 ); /* Read a byte. */ val = 0; for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { fe_strobe_eeprom( bmpr16 ); if ( inb( bmpr17 ) & FE_B17_DATA ) { val |= bit; } } *data++ = val; /* Read one more byte. */ val = 0; for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { fe_strobe_eeprom( bmpr16 ); if ( inb( bmpr17 ) & FE_B17_DATA ) { val |= bit; } } *data++ = val; } /* Reset the EEPROM interface, again. */ outb( bmpr16, 0x00 ); outb( bmpr17, 0x00 ); #if FE_DEBUG >= 3 /* Report what we got. */ data -= FE_EEPROM_SIZE; log( LOG_INFO, "fe%d: EEPROM:" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x\n", sc->sc_unit, data[ 0], data[ 1], data[ 2], data[ 3], data[ 4], data[ 5], data[ 6], data[ 7], data[ 8], data[ 9], data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23], data[24], data[25], data[26], data[27], data[28], data[29], data[30], data[31] ); #endif } /* * Hardware (vendor) specific probe routines. */ /* * Probe and initialization for Fujitsu FMV-180 series boards */ static int fe_probe_fmv ( DEVICE * dev, struct fe_softc * sc ) { int i, n; static u_short const baseaddr [ 8 ] = { 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x300, 0x340 }; static u_short const irqmap [ 4 ] = { IRQ3, IRQ7, IRQ10, IRQ15 }; static struct fe_simple_probe_struct const probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, /* { FE_DLCR5, 0x80, 0x00 }, Doesn't work. */ { FE_FMV0, 0x78, 0x50 }, /* ERRDY+PRRDY */ { FE_FMV1, 0xB0, 0x00 }, /* FMV-183/184 has 0x48 bits. */ { FE_FMV3, 0x7F, 0x00 }, #if 1 /* * Test *vendor* part of the station address for Fujitsu. * The test will gain reliability of probe process, but * it rejects FMV-180 clone boards manufactured by other vendors. * We have to turn the test off when such cards are made available. */ { FE_FMV4, 0xFF, 0x00 }, { FE_FMV5, 0xFF, 0x00 }, { FE_FMV6, 0xFF, 0x0E }, #else /* * We can always verify the *first* 2 bits (in Ethernet * bit order) are "no multicast" and "no local" even for * unknown vendors. */ { FE_FMV4, 0x03, 0x00 }, #endif { 0 } }; /* "Hardware revision ID" */ int revision; /* * See if the specified address is possible for FMV-180 series. */ for ( i = 0; i < 8; i++ ) { if ( baseaddr[ i ] == sc->iobase ) break; } if ( i == 8 ) return 0; /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* Simple probe. */ if ( !fe_simple_probe( sc, probe_table ) ) return 0; /* Check if our I/O address matches config info. on EEPROM. */ n = ( inb( sc->ioaddr[ FE_FMV2 ] ) & FE_FMV2_IOS ) >> FE_FMV2_IOS_SHIFT; if ( baseaddr[ n ] != sc->iobase ) { #if 0 /* May not work on some revisions of the cards... FIXME. */ return 0; #else /* Just log the fact and see what happens... FIXME. */ log( LOG_WARNING, "fe%d: strange I/O config?n", sc->sc_unit ); #endif } /* Find the "hardware revision." */ revision = inb( sc->ioaddr[ FE_FMV1 ] ) & FE_FMV1_REV; /* Determine the card type. */ sc->typestr = NULL; switch ( inb( sc->ioaddr[ FE_FMV0 ] ) & FE_FMV0_MEDIA ) { case 0: /* No interface? This doesn't seem to be an FMV-180... */ return 0; case FE_FMV0_MEDIUM_T: switch ( revision ) { case 8: sc->typestr = "FMV-183"; break; case 12: sc->typestr = "FMV-183 (on-board)"; break; } break; case FE_FMV0_MEDIUM_T | FE_FMV0_MEDIUM_5: switch ( revision ) { case 0: sc->typestr = "FMV-181"; break; case 1: sc->typestr = "FMV-181A"; break; } break; case FE_FMV0_MEDIUM_2: switch ( revision ) { case 8: sc->typestr = "FMV-184 (CSR = 2)"; break; } break; case FE_FMV0_MEDIUM_5: switch ( revision ) { case 8: sc->typestr = "FMV-184 (CSR = 1)"; break; } break; case FE_FMV0_MEDIUM_2 | FE_FMV0_MEDIUM_5: switch ( revision ) { case 0: sc->typestr = "FMV-182"; break; case 1: sc->typestr = "FMV-182A"; break; case 8: sc->typestr = "FMV-184 (CSR = 3)"; break; } break; } if ( sc->typestr == NULL ) { /* Unknown card type... Hope the driver works. */ sc->typestr = "unknown FMV-180 version"; log( LOG_WARNING, "fe%d: %s: %x-%x-%x-%x\n", sc->sc_unit, sc->typestr, inb( sc->ioaddr[ FE_FMV0 ] ), inb( sc->ioaddr[ FE_FMV1 ] ), inb( sc->ioaddr[ FE_FMV2 ] ), inb( sc->ioaddr[ FE_FMV3 ] ) ); } /* * An FMV-180 has been proved. * Determine which IRQ to be used. * * In this version, we give a priority to the kernel config file. * If the EEPROM and config don't match, say it to the user for * an attention. */ n = ( inb( sc->ioaddr[ FE_FMV2 ] ) & FE_FMV2_IRS ) >> FE_FMV2_IRS_SHIFT; if ( dev->id_irq == NO_IRQ ) { /* Just use the probed value. */ dev->id_irq = irqmap[ n ]; } else if ( dev->id_irq != irqmap[ n ] ) { /* Don't match. */ log( LOG_WARNING, "fe%d: check IRQ in config; it may be incorrect", sc->sc_unit ); } /* * Initialize constants in the per-line structure. */ /* Get our station address from EEPROM. */ inblk( sc, FE_FMV4, sc->sc_enaddr, ETHER_ADDR_LEN ); /* Make sure we got a valid station address. */ if ( ( sc->sc_enaddr[ 0 ] & 0x03 ) != 0x00 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) return 0; /* * Register values which (may) depend on board design. * * Program the 86960 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_EC; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; /* * Minimum initialization of the hardware. * We write into registers; hope I/O ports have no * overlap with other boards. */ /* Initialize ASIC. */ outb( sc->ioaddr[ FE_FMV3 ], 0 ); outb( sc->ioaddr[ FE_FMV10 ], 0 ); /* Initialize 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* "Refresh" hardware configuration. FIXME. */ outb( sc->ioaddr[ FE_FMV2 ], inb( sc->ioaddr[ FE_FMV2 ] ) ); /* Turn the "master interrupt control" flag of ASIC on. */ outb( sc->ioaddr[ FE_FMV3 ], FE_FMV3_IRQENB ); /* * That's all. FMV-180 occupies 32 I/O addresses, by the way. */ return 32; } /* * Probe and initialization for Allied-Telesis AT1700/RE2000 series. */ static int fe_probe_ati ( DEVICE * dev, struct fe_softc * sc ) { int i, n; u_char eeprom [ FE_EEPROM_SIZE ]; u_char save16, save17; static u_short const baseaddr [ 8 ] = { 0x260, 0x280, 0x2A0, 0x240, 0x340, 0x320, 0x380, 0x300 }; static u_short const irqmaps [ 4 ][ 4 ] = { { IRQ3, IRQ4, IRQ5, IRQ9 }, { IRQ10, IRQ11, IRQ12, IRQ15 }, { IRQ3, IRQ11, IRQ5, IRQ15 }, { IRQ10, IRQ11, IRQ14, IRQ15 }, }; static struct fe_simple_probe_struct const probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, { FE_DLCR5, 0x80, 0x00 }, #if 0 { FE_BMPR16, 0x1B, 0x00 }, { FE_BMPR17, 0x7F, 0x00 }, #endif { 0 } }; /* Assume we have 86965 and no need to restore these. */ save16 = 0; save17 = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: probe (0x%x) for ATI\n", sc->sc_unit, sc->iobase ); fe_dump( LOG_INFO, sc, NULL ); #endif /* * See if the specified address is possible for MB86965A JLI mode. */ for ( i = 0; i < 8; i++ ) { if ( baseaddr[ i ] == sc->iobase ) break; } if ( i == 8 ) goto NOTFOUND; /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* * We should test if MB86965A is on the base address now. * Unfortunately, it is very hard to probe it reliably, since * we have no way to reset the chip under software control. * On cold boot, we could check the "signature" bit patterns * described in the Fujitsu document. On warm boot, however, * we can predict almost nothing about register values. */ if ( !fe_simple_probe( sc, probe_table ) ) goto NOTFOUND; /* Check if our I/O address matches config info on 86965. */ n = ( inb( sc->ioaddr[ FE_BMPR19 ] ) & FE_B19_ADDR ) >> FE_B19_ADDR_SHIFT; if ( baseaddr[ n ] != sc->iobase ) goto NOTFOUND; /* * We are now almost sure we have an AT1700 at the given * address. So, read EEPROM through 86965. We have to write * into LSI registers to read from EEPROM. I want to avoid it * at this stage, but I cannot test the presence of the chip * any further without reading EEPROM. FIXME. */ save16 = inb( sc->ioaddr[ FE_BMPR16 ] ); save17 = inb( sc->ioaddr[ FE_BMPR17 ] ); fe_read_eeprom( sc, eeprom ); /* Make sure the EEPROM is turned off. */ outb( sc->ioaddr[ FE_BMPR16 ], 0 ); outb( sc->ioaddr[ FE_BMPR17 ], 0 ); /* Make sure that config info in EEPROM and 86965 agree. */ if ( eeprom[ FE_EEPROM_CONF ] != inb( sc->ioaddr[ FE_BMPR19 ] ) ) { goto NOTFOUND; } /* * The following model identification codes are stolen from * from the NetBSD port of the fe driver. My reviewers * suggested minor revision. */ /* Determine the card type. */ switch (eeprom[FE_ATI_EEP_MODEL]) { case FE_ATI_MODEL_AT1700T: sc->typestr = "AT-1700T/RE2001"; break; case FE_ATI_MODEL_AT1700BT: sc->typestr = "AT-1700BT/RE2003"; break; case FE_ATI_MODEL_AT1700FT: sc->typestr = "AT-1700FT/RE2009"; break; case FE_ATI_MODEL_AT1700AT: sc->typestr = "AT-1700AT/RE2005"; break; default: sc->typestr = "unknown AT-1700/RE2000 ?"; break; } /* * Try to determine IRQ settings. * Different models use different ranges of IRQs. */ if ( dev->id_irq == NO_IRQ ) { n = ( inb( sc->ioaddr[ FE_BMPR19 ] ) & FE_B19_IRQ ) >> FE_B19_IRQ_SHIFT; switch ( eeprom[ FE_ATI_EEP_REVISION ] & 0xf0 ) { case 0x30: dev->id_irq = irqmaps[ 3 ][ n ]; break; case 0x10: case 0x50: dev->id_irq = irqmaps[ 2 ][ n ]; break; case 0x40: case 0x60: if ( eeprom[ FE_ATI_EEP_MAGIC ] & 0x04 ) { dev->id_irq = irqmaps[ 1 ][ n ]; } else { dev->id_irq = irqmaps[ 0 ][ n ]; } break; default: dev->id_irq = irqmaps[ 0 ][ n ]; break; } } /* * Initialize constants in the per-line structure. */ /* Get our station address from EEPROM. */ bcopy( eeprom + FE_ATI_EEP_ADDR, sc->sc_enaddr, ETHER_ADDR_LEN ); #if 1 /* * This test doesn't work well for AT1700 look-alike by * other vendors. */ /* Make sure the vendor part is for Allied-Telesis. */ if ( sc->sc_enaddr[ 0 ] != 0x00 || sc->sc_enaddr[ 1 ] != 0x00 || sc->sc_enaddr[ 2 ] != 0xF4 ) return 0; #else /* Make sure we got a valid station address. */ if ( ( sc->sc_enaddr[ 0 ] & 0x03 ) != 0x00 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) return 0; #endif /* * Program the 86960 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; /* FIXME */ sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_EC; #if 0 /* XXXX Should we use this? FIXME. */ sc->proto_bmpr13 = eeprom[ FE_ATI_EEP_MEDIA ]; #else sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; #endif #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "ATI found" ); #endif /* Setup hooks. This may solves a nasty bug. FIXME. */ sc->init = fe_init_ati; /* Initialize 86965. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "end of fe_probe_ati()" ); #endif /* * That's all. AT1700 occupies 32 I/O addresses, by the way. */ return 32; NOTFOUND: /* * We have no AT1700 at a given address. * Restore BMPR16 and BMPR17 if we have destroyed them, * hoping that the hardware on the address didn't get * bad side effect. */ if ( save16 != 0 | save17 != 0 ) { outb( sc->ioaddr[ FE_BMPR16 ], save16 ); outb( sc->ioaddr[ FE_BMPR17 ], save17 ); } return ( 0 ); } /* ATI specific initialization routine. */ static void fe_init_ati ( struct fe_softc * sc ) { /* * I've told that the following operation "Resets" the chip. * Hope this solve a bug which hangs up the driver under * heavy load... FIXME. */ /* Minimal initialization of 86965. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* "Reset" by wrting into an undocument register location. */ outb( sc->ioaddr[ 0x1F ], 0 ); /* How long do we have to wait after the reset? FIXME. */ DELAY( 300 ); } /* * Probe and initialization for Gateway Communications' old cards. */ static int fe_probe_gwy ( DEVICE * dev, struct fe_softc * sc ) { int i,type; static struct fe_simple_probe_struct probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, { FE_DLCR7, 0xC0, 0x00 }, /* * Test *vendor* part of the address for Gateway. * This test is essential to identify Gateway's cards. * We shuld define some symbolic names for the * following offsets. FIXME. */ { 0x18, 0xFF, 0x00 }, { 0x19, 0xFF, 0x00 }, { 0x1A, 0xFF, 0x61 }, { 0 } }; /* * We need explicit IRQ and supported address. * I'm not sure which address and IRQ is possible for Gateway * Ethernet family. The following accepts everything. FIXME. */ if ( dev->id_irq == NO_IRQ || ( sc->iobase & ~0x3E0 ) != 0 ) { return ( 0 ); } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "top of probe" ); #endif /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* See if the card is on its address. */ if ( !fe_simple_probe( sc, probe_table ) ) { return 0; } /* Determine the card type. */ sc->typestr = "Gateway Ethernet w/ Fujitsu chipset"; /* Get our station address from EEPROM. */ inblk( sc, 0x18, sc->sc_enaddr, ETHER_ADDR_LEN ); /* * Program the 86960 as follows: * SRAM: 16KB, 100ns, byte-wide access. * Transmission buffer: 2KB x 2. * System bus interface: 16 bits. * Make sure to clear out ID bits in DLCR7 * (They actually are Encoder/Decoder control in NICE.) */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_16KB | FE_D6_TXBSIZ_2x2KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH; sc->proto_bmpr13 = 0; /* Minimal initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* That's all. The card occupies 32 I/O addresses, as always. */ return 32; } #if NCRD > 0 /* * Probe and initialization for Fujitsu MBH10302 PCMCIA Ethernet interface. * Note that this is for 10302 only; MBH10304 is handled by fe_probe_tdk(). */ static int fe_probe_mbh ( DEVICE * dev, struct fe_softc * sc ) { int i,type; static struct fe_simple_probe_struct probe_table [] = { { FE_DLCR0, 0x09, 0x00 }, { FE_DLCR2, 0x79, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, { FE_DLCR6, 0xFF, 0xB6 }, /* * The following location has the first byte of the card's * Ethernet (MAC) address. * We can always verify the *first* 2 bits (in Ethernet * bit order) are "global" and "unicast" for any vendors'. */ { FE_MBH10, 0x03, 0x00 }, /* Just a gap? Seems reliable, anyway. */ { 0x12, 0xFF, 0x00 }, { 0x13, 0xFF, 0x00 }, { 0x14, 0xFF, 0x00 }, { 0x15, 0xFF, 0x00 }, { 0x16, 0xFF, 0x00 }, { 0x17, 0xFF, 0x00 }, #if 0 { 0x18, 0xFF, 0xFF }, { 0x19, 0xFF, 0xFF }, #endif { 0 } }; /* * We need explicit IRQ and supported address. */ if ( dev->id_irq == NO_IRQ || ( sc->iobase & ~0x3E0 ) != 0 ) { return ( 0 ); } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "top of probe" ); #endif /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* * See if MBH10302 is on its address. * I'm not sure the following probe code works. FIXME. */ if ( !fe_simple_probe( sc, probe_table ) ) return 0; /* Determine the card type. */ sc->typestr = "MBH10302 (PCMCIA)"; /* * Initialize constants in the per-line structure. */ /* Get our station address from EEPROM. */ inblk( sc, FE_MBH10, sc->sc_enaddr, ETHER_ADDR_LEN ); /* Make sure we got a valid station address. */ if ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) return 0; /* * Program the 86960 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_NICE; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; /* Setup hooks. We need a special initialization procedure. */ sc->init = fe_init_mbh; /* * Minimum initialization. */ /* Minimal initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); #if 1 /* FIXME. */ /* Initialize system bus interface and encoder/decoder operation. */ outb( sc->ioaddr[ FE_MBH0 ], FE_MBH0_MAGIC | FE_MBH0_INTR_DISABLE ); #endif /* * That's all. MBH10302 occupies 32 I/O addresses, by the way. */ return 32; } /* MBH specific initialization routine. */ static void fe_init_mbh ( struct fe_softc * sc ) { /* Minimal initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* Enable master interrupt flag. */ outb( sc->ioaddr[ FE_MBH0 ], FE_MBH0_MAGIC | FE_MBH0_INTR_ENABLE ); } #endif /* NCRD > 0 */ #if NCRD > 0 /* * Probe and initialization for TDK/CONTEC PCMCIA Ethernet interface. * by MASUI Kenji * * (Contec uses TDK Ethenet chip -- hosokawa) * * This version of fe_probe_tdk has been rewrote to handle * *generic* PC card implementation of Fujitsu MB8696x family. The * name _tdk is just for a historical reason. :-) */ static int fe_probe_tdk ( DEVICE * dev, struct fe_softc * sc ) { int i; static struct fe_simple_probe_struct probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, /* { FE_DLCR5, 0x80, 0x00 }, Does not work well. */ { 0 } }; if ( dev->id_irq == NO_IRQ ) { return ( 0 ); } /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* * See if C-NET(PC)C is on its address. */ if ( !fe_simple_probe( sc, probe_table ) ) return 0; /* Determine the card type. */ sc->typestr = "Generic MB8696x Ethernet (PCMCIA)"; /* * Initialize constants in the per-line structure. */ /* The station address *must*be* already in sc_enaddr; Make sure we got a valid station address. */ if ( ( sc->sc_enaddr[ 0 ] & 0x03 ) != 0x00 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) return 0; /* * Program the 86965 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. * XXX: Should we remove IDENT_NICE from DLCR7? Or, * even add IDENT_EC instead? FIXME. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_NICE; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; /* Minimul initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* * That's all. C-NET(PC)C occupies 16 I/O addresses. * XXX: Are there any card with 32 I/O addresses? FIXME. */ return 16; } #endif /* * Install interface into kernel networking data structures */ static int fe_attach ( DEVICE * dev ) { #if NCRD > 0 static int already_ifattach[NFE]; #endif struct fe_softc *sc = &fe_softc[dev->id_unit]; /* * Initialize ifnet structure */ sc->sc_if.if_softc = sc; sc->sc_if.if_unit = sc->sc_unit; sc->sc_if.if_name = "fe"; sc->sc_if.if_output = ether_output; sc->sc_if.if_start = fe_start; sc->sc_if.if_ioctl = fe_ioctl; sc->sc_if.if_watchdog = fe_watchdog; /* * Set default interface flags. */ sc->sc_if.if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; /* * Set maximum size of output queue, if it has not been set. * It is done here as this driver may be started after the * system initialization (i.e., the interface is PCMCIA.) * * I'm not sure this is really necessary, but, even if it is, * it should be done somewhere else, e.g., in if_attach(), * since it must be a common workaround for all network drivers. * FIXME. */ if ( sc->sc_if.if_snd.ifq_maxlen == 0 ) { sc->sc_if.if_snd.ifq_maxlen = ifqmaxlen; } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "attach()" ); #endif #if FE_SINGLE_TRANSMISSION /* Override txb config to allocate minimum. */ sc->proto_dlcr6 &= ~FE_D6_TXBSIZ sc->proto_dlcr6 |= FE_D6_TXBSIZ_2x2KB; #endif /* Modify hardware config if it is requested. */ if ( dev->id_flags & FE_FLAGS_OVERRIDE_DLCR6 ) { sc->proto_dlcr6 = dev->id_flags & FE_FLAGS_DLCR6_VALUE; } /* Find TX buffer size, based on the hardware dependent proto. */ switch ( sc->proto_dlcr6 & FE_D6_TXBSIZ ) { case FE_D6_TXBSIZ_2x2KB: sc->txb_size = 2048; break; case FE_D6_TXBSIZ_2x4KB: sc->txb_size = 4096; break; case FE_D6_TXBSIZ_2x8KB: sc->txb_size = 8192; break; default: /* Oops, we can't work with single buffer configuration. */ #if FE_DEBUG >= 2 log( LOG_WARNING, "fe%d: strange TXBSIZ config; fixing\n", sc->sc_unit ); #endif sc->proto_dlcr6 &= ~FE_D6_TXBSIZ; sc->proto_dlcr6 |= FE_D6_TXBSIZ_2x2KB; sc->txb_size = 2048; break; } /* Attach and stop the interface. */ #if NCRD > 0 if (already_ifattach[dev->id_unit] != 1) { if_attach(&sc->sc_if); already_ifattach[dev->id_unit] = 1; } #else if_attach(&sc->sc_if); #endif /* NCRD > 0 */ fe_stop(sc->sc_unit); /* This changes the state to IDLE. */ ether_ifattach(&sc->sc_if); /* Print additional info when attached. */ printf( "fe%d: address %6D, type %s\n", sc->sc_unit, sc->sc_enaddr, ":" , sc->typestr ); #if FE_DEBUG >= 3 { int buf, txb, bbw, sbw, ram; buf = txb = bbw = sbw = ram = -1; switch ( sc->proto_dlcr6 & FE_D6_BUFSIZ ) { case FE_D6_BUFSIZ_8KB: buf = 8; break; case FE_D6_BUFSIZ_16KB: buf = 16; break; case FE_D6_BUFSIZ_32KB: buf = 32; break; case FE_D6_BUFSIZ_64KB: buf = 64; break; } switch ( sc->proto_dlcr6 & FE_D6_TXBSIZ ) { case FE_D6_TXBSIZ_2x2KB: txb = 2; break; case FE_D6_TXBSIZ_2x4KB: txb = 4; break; case FE_D6_TXBSIZ_2x8KB: txb = 8; break; } switch ( sc->proto_dlcr6 & FE_D6_BBW ) { case FE_D6_BBW_BYTE: bbw = 8; break; case FE_D6_BBW_WORD: bbw = 16; break; } switch ( sc->proto_dlcr6 & FE_D6_SBW ) { case FE_D6_SBW_BYTE: sbw = 8; break; case FE_D6_SBW_WORD: sbw = 16; break; } switch ( sc->proto_dlcr6 & FE_D6_SRAM ) { case FE_D6_SRAM_100ns: ram = 100; break; case FE_D6_SRAM_150ns: ram = 150; break; } printf( "fe%d: SRAM %dKB %dbit %dns, TXB %dKBx2, %dbit I/O\n", sc->sc_unit, buf, bbw, ram, txb, sbw ); } #endif #if NBPFILTER > 0 /* If BPF is in the kernel, call the attach for it. */ bpfattach( &sc->sc_if, DLT_EN10MB, sizeof(struct ether_header)); #endif return 1; } /* * Reset interface. */ static void fe_reset ( int unit ) { /* * Stop interface and re-initialize. */ fe_stop(unit); fe_init(unit); } /* * Stop everything on the interface. * * All buffered packets, both transmitting and receiving, * if any, will be lost by stopping the interface. */ static void fe_stop ( int unit ) { struct fe_softc *sc = &fe_softc[unit]; int s; s = splimp(); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "stop()" ); #endif /* Disable interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0x00 ); outb( sc->ioaddr[ FE_DLCR3 ], 0x00 ); /* Stop interface hardware. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Clear all interrupt status. */ outb( sc->ioaddr[ FE_DLCR0 ], 0xFF ); outb( sc->ioaddr[ FE_DLCR1 ], 0xFF ); /* Put the chip in stand-by mode. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_POWER_DOWN ); DELAY( 200 ); /* Reset transmitter variables and interface flags. */ sc->sc_if.if_flags &= ~( IFF_OACTIVE | IFF_RUNNING ); sc->sc_if.if_timer = 0; sc->txb_free = sc->txb_size; sc->txb_count = 0; sc->txb_sched = 0; /* MAR loading can be delayed. */ sc->filter_change = 0; /* Update config status also. */ /* Call a hook. */ if ( sc->stop ) sc->stop( sc ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "end of stop()" ); #endif (void) splx(s); } /* * Device timeout/watchdog routine. Entered if the device neglects to * generate an interrupt after a transmit has been started on it. */ static void fe_watchdog ( struct ifnet *ifp ) { struct fe_softc *sc = (struct fe_softc *)ifp; #if FE_DEBUG >= 1 /* A "debug" message. */ log( LOG_ERR, "fe%d: transmission timeout (%d+%d)%s\n", ifp->if_unit, sc->txb_sched, sc->txb_count, ( ifp->if_flags & IFF_UP ) ? "" : " when down" ); if ( sc->sc_if.if_opackets == 0 && sc->sc_if.if_ipackets == 0 ) { log( LOG_WARNING, "fe%d: wrong IRQ setting in config?\n", ifp->if_unit ); } #endif #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, NULL ); #endif /* Record how many packets are lost by this accident. */ ifp->if_oerrors += sc->txb_sched + sc->txb_count; /* Put the interface into known initial state. */ if ( ifp->if_flags & IFF_UP ) { fe_reset( ifp->if_unit ); } else { fe_stop( ifp->if_unit ); } } /* * Initialize device. */ static void fe_init ( int unit ) { struct fe_softc *sc = &fe_softc[unit]; int i, s; #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "init()" ); #endif /* We need an address. */ - if (sc->sc_if.if_addrlist == 0) { + if (TAILQ_EMPTY(&sc->sc_if.if_addrhead)) { /* XXX unlikely */ #if FE_DEBUG >= 1 log( LOG_ERR, "fe%d: init() without any address\n", sc->sc_unit ); #endif return; } #if FE_DEBUG >= 1 /* * Make sure we have a valid station address. * The following test is applicable for any Ethernet interfaces. * It can be done in somewhere common to all of them. FIXME. */ if ( ( sc->sc_enaddr[ 0 ] & 0x01 ) != 0 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) { log( LOG_ERR, "fe%d: invalid station address (%6D)\n", sc->sc_unit, sc->sc_enaddr, ":" ); return; } #endif /* Start initializing 86960. */ s = splimp(); /* Call a hook. */ if ( sc->init ) sc->init( sc ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after init hook" ); #endif /* * Make sure to disable the chip, also. * This may also help re-programming the chip after * hot insertion of PCMCIAs. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Power up the chip and select register bank for DLCRs. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_DLCR | FE_D7_POWER_UP ); DELAY( 200 ); /* Feed the station address. */ outblk( sc, FE_DLCR8, sc->sc_enaddr, ETHER_ADDR_LEN ); /* Clear multicast address filter to receive nothing. */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_MAR | FE_D7_POWER_UP ); outblk( sc, FE_MAR8, fe_filter_nothing.data, FE_FILTER_LEN ); /* Select the BMPR bank for runtime register access. */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_BMPR | FE_D7_POWER_UP ); /* Initialize registers. */ outb( sc->ioaddr[ FE_DLCR0 ], 0xFF ); /* Clear all bits. */ outb( sc->ioaddr[ FE_DLCR1 ], 0xFF ); /* ditto. */ outb( sc->ioaddr[ FE_DLCR2 ], 0x00 ); outb( sc->ioaddr[ FE_DLCR3 ], 0x00 ); outb( sc->ioaddr[ FE_DLCR4 ], sc->proto_dlcr4 ); outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 ); outb( sc->ioaddr[ FE_BMPR10 ], 0x00 ); outb( sc->ioaddr[ FE_BMPR11 ], FE_B11_CTRL_SKIP | FE_B11_MODE1 ); outb( sc->ioaddr[ FE_BMPR12 ], 0x00 ); outb( sc->ioaddr[ FE_BMPR13 ], sc->proto_bmpr13 ); outb( sc->ioaddr[ FE_BMPR14 ], 0x00 ); outb( sc->ioaddr[ FE_BMPR15 ], 0x00 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "just before enabling DLC" ); #endif /* Enable interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], FE_TMASK ); outb( sc->ioaddr[ FE_DLCR3 ], FE_RMASK ); /* Enable transmitter and receiver. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_ENABLE ); DELAY( 200 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "just after enabling DLC" ); #endif /* * Make sure to empty the receive buffer. * * This may be redundant, but *if* the receive buffer were full * at this point, then the driver would hang. I have experienced * some strange hang-up just after UP. I hope the following * code solve the problem. * * I have changed the order of hardware initialization. * I think the receive buffer cannot have any packets at this * point in this version. The following code *must* be * redundant now. FIXME. * * I've heard a rumore that on some PC card implementation of * 8696x, the receive buffer can have some data at this point. * The following message helps discovering the fact. FIXME. */ if ( !( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) ) { log( LOG_WARNING, "fe%d: receive buffer has some data after reset\n", sc->sc_unit ); fe_emptybuffer( sc ); } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after ERB loop" ); #endif /* Do we need this here? Actually, no. I must be paranoia. */ outb( sc->ioaddr[ FE_DLCR0 ], 0xFF ); /* Clear all bits. */ outb( sc->ioaddr[ FE_DLCR1 ], 0xFF ); /* ditto. */ #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after FIXME" ); #endif /* Set 'running' flag, because we are now running. */ sc->sc_if.if_flags |= IFF_RUNNING; /* * At this point, the interface is running properly, * except that it receives *no* packets. we then call * fe_setmode() to tell the chip what packets to be * received, based on the if_flags and multicast group * list. It completes the initialization process. */ fe_setmode( sc ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after setmode" ); #endif /* ...and attempt to start output queued packets. */ fe_start( &sc->sc_if ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "init() done" ); #endif (void) splx(s); } /* * This routine actually starts the transmission on the interface */ static void fe_xmit ( struct fe_softc * sc ) { /* * Set a timer just in case we never hear from the board again. * We use longer timeout for multiple packet transmission. * I'm not sure this timer value is appropriate. FIXME. */ sc->sc_if.if_timer = 1 + sc->txb_count; /* Update txb variables. */ sc->txb_sched = sc->txb_count; sc->txb_count = 0; sc->txb_free = sc->txb_size; sc->tx_excolls = 0; /* Start transmitter, passing packets in TX buffer. */ outb( sc->ioaddr[ FE_BMPR10 ], sc->txb_sched | FE_B10_START ); } /* * Start output on interface. * We make two assumptions here: * 1) that the current priority is set to splimp _before_ this code * is called *and* is returned to the appropriate priority after * return * 2) that the IFF_OACTIVE flag is checked before this code is called * (i.e. that the output part of the interface is idle) */ void fe_start ( struct ifnet *ifp ) { struct fe_softc *sc = ifp->if_softc; struct mbuf *m; #if FE_DEBUG >= 1 /* Just a sanity check. */ if ( ( sc->txb_count == 0 ) != ( sc->txb_free == sc->txb_size ) ) { /* * Txb_count and txb_free co-works to manage the * transmission buffer. Txb_count keeps track of the * used potion of the buffer, while txb_free does unused * potion. So, as long as the driver runs properly, * txb_count is zero if and only if txb_free is same * as txb_size (which represents whole buffer.) */ log( LOG_ERR, "fe%d: inconsistent txb variables (%d, %d)\n", sc->sc_unit, sc->txb_count, sc->txb_free ); /* * So, what should I do, then? * * We now know txb_count and txb_free contradicts. We * cannot, however, tell which is wrong. More * over, we cannot peek 86960 transmission buffer or * reset the transmission buffer. (In fact, we can * reset the entire interface. I don't want to do it.) * * If txb_count is incorrect, leaving it as-is will cause * sending of garbage after next interrupt. We have to * avoid it. Hence, we reset the txb_count here. If * txb_free was incorrect, resetting txb_count just loose * some packets. We can live with it. */ sc->txb_count = 0; } #endif #if FE_DEBUG >= 1 /* * First, see if there are buffered packets and an idle * transmitter - should never happen at this point. */ if ( ( sc->txb_count > 0 ) && ( sc->txb_sched == 0 ) ) { log( LOG_ERR, "fe%d: transmitter idle with %d buffered packets\n", sc->sc_unit, sc->txb_count ); fe_xmit( sc ); } #endif /* * Stop accepting more transmission packets temporarily, when * a filter change request is delayed. Updating the MARs on * 86960 flushes the transmission buffer, so it is delayed * until all buffered transmission packets have been sent * out. */ if ( sc->filter_change ) { /* * Filter change request is delayed only when the DLC is * working. DLC soon raise an interrupt after finishing * the work. */ goto indicate_active; } for (;;) { /* * See if there is room to put another packet in the buffer. * We *could* do better job by peeking the send queue to * know the length of the next packet. Current version just * tests against the worst case (i.e., longest packet). FIXME. * * When adding the packet-peek feature, don't forget adding a * test on txb_count against QUEUEING_MAX. * There is a little chance the packet count exceeds * the limit. Assume transmission buffer is 8KB (2x8KB * configuration) and an application sends a bunch of small * (i.e., minimum packet sized) packets rapidly. An 8KB * buffer can hold 130 blocks of 62 bytes long... */ if ( sc->txb_free < ETHER_MAX_LEN - ETHER_CRC_LEN + FE_DATA_LEN_LEN ) { /* No room. */ goto indicate_active; } #if FE_SINGLE_TRANSMISSION if ( sc->txb_count > 0 ) { /* Just one packet per a transmission buffer. */ goto indicate_active; } #endif /* * Get the next mbuf chain for a packet to send. */ IF_DEQUEUE( &sc->sc_if.if_snd, m ); if ( m == NULL ) { /* No more packets to send. */ goto indicate_inactive; } /* * Copy the mbuf chain into the transmission buffer. * txb_* variables are updated as necessary. */ fe_write_mbufs( sc, m ); /* Start transmitter if it's idle. */ if ( ( sc->txb_count > 0 ) && ( sc->txb_sched == 0 ) ) { fe_xmit( sc ); } /* * Tap off here if there is a bpf listener, * and the device is *not* in promiscuous mode. * (86960 receives self-generated packets if * and only if it is in "receive everything" * mode.) */ #if NBPFILTER > 0 if ( sc->sc_if.if_bpf && !( sc->sc_if.if_flags & IFF_PROMISC ) ) { bpf_mtap( &sc->sc_if, m ); } #endif m_freem( m ); } indicate_inactive: /* * We are using the !OACTIVE flag to indicate to * the outside world that we can accept an * additional packet rather than that the * transmitter is _actually_ active. Indeed, the * transmitter may be active, but if we haven't * filled all the buffers with data then we still * want to accept more. */ sc->sc_if.if_flags &= ~IFF_OACTIVE; return; indicate_active: /* * The transmitter is active, and there are no room for * more outgoing packets in the transmission buffer. */ sc->sc_if.if_flags |= IFF_OACTIVE; return; } /* * Drop (skip) a packet from receive buffer in 86960 memory. */ static void fe_droppacket ( struct fe_softc * sc, int len ) { int i; /* * 86960 manual says that we have to read 8 bytes from the buffer * before skip the packets and that there must be more than 8 bytes * remaining in the buffer when issue a skip command. * Remember, we have already read 4 bytes before come here. */ if ( len > 12 ) { /* Read 4 more bytes, and skip the rest of the packet. */ ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); outb( sc->ioaddr[ FE_BMPR14 ], FE_B14_SKIP ); } else { /* We should not come here unless receiving RUNTs. */ for ( i = 0; i < len; i += 2 ) { ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); } } } /* * Empty receiving buffer. */ static void fe_emptybuffer ( struct fe_softc * sc ) { int i; u_char saved_dlcr5; #if FE_DEBUG >= 1 log( LOG_WARNING, "fe%d: emptying receive buffer", sc->sc_unit ); #endif /* * Stop receiving packets, temporarily. */ saved_dlcr5 = inb( sc->ioaddr[ FE_DLCR5 ] ); outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 ); /* * When we come here, the receive buffer management should * have been broken. So, we cannot use skip operation. */ for ( i = 0; i < sc->txb_size; i += 2 ) { if ( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) break; ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); } /* * Double check. */ if ( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) { log( LOG_ERR, "fe%d: could not empty receive buffer\n", sc->sc_unit ); /* Hmm. What should I do if this happens? FIXME. */ } /* * Restart receiving packets. */ outb( sc->ioaddr[ FE_DLCR5 ], saved_dlcr5 ); } /* * Transmission interrupt handler * The control flow of this function looks silly. FIXME. */ static void fe_tint ( struct fe_softc * sc, u_char tstat ) { int left; int col; /* * Handle "excessive collision" interrupt. */ if ( tstat & FE_D0_COLL16 ) { /* * Find how many packets (including this collided one) * are left unsent in transmission buffer. */ left = inb( sc->ioaddr[ FE_BMPR10 ] ); #if FE_DEBUG >= 2 log( LOG_WARNING, "fe%d: excessive collision (%d/%d)\n", sc->sc_unit, left, sc->txb_sched ); #endif #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, NULL ); #endif /* * Clear the collision flag (in 86960) here * to avoid confusing statistics. */ outb( sc->ioaddr[ FE_DLCR0 ], FE_D0_COLLID ); /* * Restart transmitter, skipping the * collided packet. * * We *must* skip the packet to keep network running * properly. Excessive collision error is an * indication of the network overload. If we * tried sending the same packet after excessive * collision, the network would be filled with * out-of-time packets. Packets belonging * to reliable transport (such as TCP) are resent * by some upper layer. */ outb( sc->ioaddr[ FE_BMPR11 ], FE_B11_CTRL_SKIP | FE_B11_MODE1 ); /* Update statistics. */ sc->tx_excolls++; } /* * Handle "transmission complete" interrupt. */ if ( tstat & FE_D0_TXDONE ) { /* * Add in total number of collisions on last * transmission. We also clear "collision occurred" flag * here. * * 86960 has a design flaw on collision count on multiple * packet transmission. When we send two or more packets * with one start command (that's what we do when the * transmission queue is crowded), 86960 informs us number * of collisions occurred on the last packet on the * transmission only. Number of collisions on previous * packets are lost. I have told that the fact is clearly * stated in the Fujitsu document. * * I considered not to mind it seriously. Collision * count is not so important, anyway. Any comments? FIXME. */ if ( inb( sc->ioaddr[ FE_DLCR0 ] ) & FE_D0_COLLID ) { /* Clear collision flag. */ outb( sc->ioaddr[ FE_DLCR0 ], FE_D0_COLLID ); /* Extract collision count from 86960. */ col = inb( sc->ioaddr[ FE_DLCR4 ] ); col = ( col & FE_D4_COL ) >> FE_D4_COL_SHIFT; if ( col == 0 ) { /* * Status register indicates collisions, * while the collision count is zero. * This can happen after multiple packet * transmission, indicating that one or more * previous packet(s) had been collided. * * Since the accurate number of collisions * has been lost, we just guess it as 1; * Am I too optimistic? FIXME. */ col = 1; } sc->sc_if.if_collisions += col; #if FE_DEBUG >= 3 log( LOG_WARNING, "fe%d: %d collision(s) (%d)\n", sc->sc_unit, col, sc->txb_sched ); #endif } /* * Update transmission statistics. * Be sure to reflect number of excessive collisions. */ sc->sc_if.if_opackets += sc->txb_sched - sc->tx_excolls; sc->sc_if.if_oerrors += sc->tx_excolls; sc->sc_if.if_collisions += sc->tx_excolls * 16; sc->txb_sched = 0; /* * The transmitter is no more active. * Reset output active flag and watchdog timer. */ sc->sc_if.if_flags &= ~IFF_OACTIVE; sc->sc_if.if_timer = 0; /* * If more data is ready to transmit in the buffer, start * transmitting them. Otherwise keep transmitter idle, * even if more data is queued. This gives receive * process a slight priority. */ if ( sc->txb_count > 0 ) fe_xmit( sc ); } } /* * Ethernet interface receiver interrupt. */ static void fe_rint ( struct fe_softc * sc, u_char rstat ) { u_short len; u_char status; int i; /* * Update statistics if this interrupt is caused by an error. */ if ( rstat & ( FE_D1_OVRFLO | FE_D1_CRCERR | FE_D1_ALGERR | FE_D1_SRTPKT ) ) { #if FE_DEBUG >= 3 log( LOG_WARNING, "fe%d: receive error: %s%s%s%s(%02x)\n", sc->sc_unit, rstat & FE_D1_OVRFLO ? "OVR " : "", rstat & FE_D1_CRCERR ? "CRC " : "", rstat & FE_D1_ALGERR ? "ALG " : "", rstat & FE_D1_SRTPKT ? "LEN " : "", rstat ); #endif sc->sc_if.if_ierrors++; } /* * MB86960 has a flag indicating "receive queue empty." * We just loop, checking the flag, to pull out all received * packets. * * We limit the number of iterations to avoid infinite-loop. * It can be caused by a very slow CPU (some broken * peripheral may insert incredible number of wait cycles) * or, worse, by a broken MB86960 chip. */ for ( i = 0; i < FE_MAX_RECV_COUNT; i++ ) { /* Stop the iteration if 86960 indicates no packets. */ if ( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) break; /* * Extract A receive status byte. * As our 86960 is in 16 bit bus access mode, we have to * use inw() to get the status byte. The significant * value is returned in lower 8 bits. */ status = ( u_char )inw( sc->ioaddr[ FE_BMPR8 ] ); #if FE_DEBUG >= 4 log( LOG_INFO, "fe%d: receive status = %04x\n", sc->sc_unit, status ); #endif /* * Extract the packet length. * It is a sum of a header (14 bytes) and a payload. * CRC has been stripped off by the 86960. */ len = inw( sc->ioaddr[ FE_BMPR8 ] ); /* * If there was an error, update statistics and drop * the packet, unless the interface is in promiscuous * mode. */ if ( ( status & 0xF0 ) != 0x20 ) { if ( !( sc->sc_if.if_flags & IFF_PROMISC ) ) { sc->sc_if.if_ierrors++; fe_droppacket( sc, len ); continue; } } /* * MB86960 checks the packet length and drop big packet * before passing it to us. There are no chance we can * get big packets through it, even if they are actually * sent over a line. Hence, if the length exceeds * the specified limit, it means some serious failure, * such as out-of-sync on receive buffer management. * * Same for short packets, since we have programmed * 86960 to drop short packets. */ if ( len > ETHER_MAX_LEN - ETHER_CRC_LEN || len < ETHER_MIN_LEN - ETHER_CRC_LEN ) { #if FE_DEBUG >= 1 log( LOG_WARNING, "fe%d: received a %s packet? (%u bytes)\n", sc->sc_unit, len < ETHER_MIN_LEN - ETHER_CRC_LEN ? "partial" : "big", len ); #endif sc->sc_if.if_ierrors++; fe_emptybuffer( sc ); continue; } /* * Go get a packet. */ if ( fe_get_packet( sc, len ) < 0 ) { /* Skip a packet, updating statistics. */ #if FE_DEBUG >= 2 log( LOG_WARNING, "%s%d: out of mbuf;" " dropping a packet (%u bytes)\n", sc->sc_unit, len ); #endif sc->sc_if.if_ierrors++; fe_droppacket( sc, len ); /* * We stop receiving packets, even if there are * more in the buffer. We hope we can get more * mbuf next time. */ return; } /* Successfully received a packet. Update stat. */ sc->sc_if.if_ipackets++; } } /* * Ethernet interface interrupt processor */ void feintr ( int unit ) { struct fe_softc *sc = &fe_softc[unit]; u_char tstat, rstat; /* * Loop until there are no more new interrupt conditions. */ for (;;) { #if FE_DEBUG >= 4 fe_dump( LOG_INFO, sc, "intr()" ); #endif /* * Get interrupt conditions, masking unneeded flags. */ tstat = inb( sc->ioaddr[ FE_DLCR0 ] ) & FE_TMASK; rstat = inb( sc->ioaddr[ FE_DLCR1 ] ) & FE_RMASK; if ( tstat == 0 && rstat == 0 ) break; /* * Reset the conditions we are acknowledging. */ outb( sc->ioaddr[ FE_DLCR0 ], tstat ); outb( sc->ioaddr[ FE_DLCR1 ], rstat ); /* * Handle transmitter interrupts. Handle these first because * the receiver will reset the board under some conditions. */ if ( tstat ) { fe_tint( sc, tstat ); } /* * Handle receiver interrupts */ if ( rstat ) { fe_rint( sc, rstat ); } /* * Update the multicast address filter if it is * needed and possible. We do it now, because * we can make sure the transmission buffer is empty, * and there is a good chance that the receive queue * is empty. It will minimize the possibility of * packet loss. */ if ( sc->filter_change && sc->txb_count == 0 && sc->txb_sched == 0 ) { fe_loadmar(sc); sc->sc_if.if_flags &= ~IFF_OACTIVE; } /* * If it looks like the transmitter can take more data, * attempt to start output on the interface. This is done * after handling the receiver interrupt to give the * receive operation priority. * * BTW, I'm not sure in what case the OACTIVE is on at * this point. Is the following test redundant? * * No. This routine polls for both transmitter and * receiver interrupts. 86960 can raise a receiver * interrupt when the transmission buffer is full. */ if ( ( sc->sc_if.if_flags & IFF_OACTIVE ) == 0 ) { fe_start( &sc->sc_if ); } } } /* * Process an ioctl request. This code needs some work - it looks * pretty ugly. */ static int fe_ioctl ( struct ifnet * ifp, int command, caddr_t data ) { struct fe_softc *sc = ifp->if_softc; int s, error = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: ioctl(%x)\n", sc->sc_unit, command ); #endif s = splimp(); switch (command) { case SIOCSIFADDR: { struct ifaddr * ifa = ( struct ifaddr * )data; sc->sc_if.if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: fe_init( sc->sc_unit ); /* before arp_ifinit */ arp_ifinit( &sc->arpcom, ifa ); break; #endif #ifdef IPX /* * XXX - This code is probably wrong */ case AF_IPX: { register struct ipx_addr *ina = &(IA_SIPX(ifa)->sipx_addr); if (ipx_nullhost(*ina)) ina->x_host = *(union ipx_host *) (sc->sc_enaddr); else { bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->sc_enaddr, sizeof(sc->sc_enaddr)); } /* * Set new address */ fe_init(sc->sc_unit); break; } #endif #ifdef INET6 case AF_INET6: /* IPV6 added by shin 96.2.6 */ fe_init(sc->sc_unit); ndp6_ifinit(&sc->arpcom, ifa); break; #endif #ifdef NS /* * XXX - This code is probably wrong */ case AF_NS: { register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); if (ns_nullhost(*ina)) ina->x_host = *(union ns_host *) (sc->sc_enaddr); else { bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->sc_enaddr, sizeof(sc->sc_enaddr)); } /* * Set new address */ fe_init(sc->sc_unit); break; } #endif default: fe_init( sc->sc_unit ); break; } break; } #ifdef SIOCGIFADDR case SIOCGIFADDR: { struct ifreq * ifr = ( struct ifreq * )data; struct sockaddr * sa = ( struct sockaddr * )&ifr->ifr_data; bcopy((caddr_t)sc->sc_enaddr, (caddr_t)sa->sa_data, ETHER_ADDR_LEN); break; } #endif #ifdef SIOCGIFPHYSADDR case SIOCGIFPHYSADDR: { struct ifreq * ifr = ( struct ifreq * )data; bcopy((caddr_t)sc->sc_enaddr, (caddr_t)&ifr->ifr_data, ETHER_ADDR_LEN); break; } #endif #ifdef notdef #ifdef SIOCSIFPHYSADDR case SIOCSIFPHYSADDR: { /* * Set the physical (Ethernet) address of the interface. * When and by whom is this command used? FIXME. */ struct ifreq * ifr = ( struct ifreq * )data; bcopy((caddr_t)&ifr->ifr_data, (caddr_t)sc->sc_enaddr, ETHER_ADDR_LEN); fe_setlinkaddr( sc ); break; } #endif #endif /* notdef */ #ifdef SIOCSIFFLAGS case SIOCSIFFLAGS: { /* * Switch interface state between "running" and * "stopped", reflecting the UP flag. */ if ( sc->sc_if.if_flags & IFF_UP ) { if ( ( sc->sc_if.if_flags & IFF_RUNNING ) == 0 ) { fe_init( sc->sc_unit ); } } else { if ( ( sc->sc_if.if_flags & IFF_RUNNING ) != 0 ) { fe_stop( sc->sc_unit ); } } /* * Promiscuous and/or multicast flags may have changed, * so reprogram the multicast filter and/or receive mode. */ fe_setmode( sc ); #if FE_DEBUG >= 1 /* "ifconfig fe0 debug" to print register dump. */ if ( sc->sc_if.if_flags & IFF_DEBUG ) { fe_dump( LOG_DEBUG, sc, "SIOCSIFFLAGS(DEBUG)" ); } #endif break; } #endif #ifdef SIOCADDMULTI case SIOCADDMULTI: case SIOCDELMULTI: { /* * Update out multicast list. */ struct ifreq * ifr = ( struct ifreq * )data; error = ( command == SIOCADDMULTI ) ? ether_addmulti( ifr, &sc->arpcom ) : ether_delmulti( ifr, &sc->arpcom ); if ( error == ENETRESET ) { /* * Multicast list has changed; set the hardware filter * accordingly. */ fe_setmode( sc ); error = 0; } break; } #endif #ifdef SIOCSIFMTU case SIOCSIFMTU: { /* * Set the interface MTU. */ struct ifreq * ifr = ( struct ifreq * )data; if ( ifr->ifr_mtu > ETHERMTU ) { error = EINVAL; } else { sc->sc_if.if_mtu = ifr->ifr_mtu; } break; } #endif default: error = EINVAL; } (void) splx(s); return (error); } /* * Retrieve packet from receive buffer and send to the next level up via * ether_input(). If there is a BPF listener, give a copy to BPF, too. * Returns 0 if success, -1 if error (i.e., mbuf allocation failure). */ static int fe_get_packet ( struct fe_softc * sc, u_short len ) { struct ether_header *eh; struct mbuf *m; /* * NFS wants the data be aligned to the word (4 byte) * boundary. Ethernet header has 14 bytes. There is a * 2-byte gap. */ #define NFS_MAGIC_OFFSET 2 /* * This function assumes that an Ethernet packet fits in an * mbuf (with a cluster attached when necessary.) On FreeBSD * 2.0 for x86, which is the primary target of this driver, an * mbuf cluster has 4096 bytes, and we are happy. On ancient * BSDs, such as vanilla 4.3 for 386, a cluster size was 1024, * however. If the following #error message were printed upon * compile, you need to rewrite this function. */ #if ( MCLBYTES < ETHER_MAX_LEN - ETHER_CRC_LEN + NFS_MAGIC_OFFSET ) #error "Too small MCLBYTES to use fe driver." #endif /* * Our strategy has one more problem. There is a policy on * mbuf cluster allocation. It says that we must have at * least MINCLSIZE (208 bytes on FreeBSD 2.0 for x86) to * allocate a cluster. For a packet of a size between * (MHLEN - 2) to (MINCLSIZE - 2), our code violates the rule... * On the other hand, the current code is short, simple, * and fast, however. It does no harmful thing, just waists * some memory. Any comments? FIXME. */ /* Allocate an mbuf with packet header info. */ MGETHDR(m, M_DONTWAIT, MT_DATA); if ( m == NULL ) return -1; /* Attach a cluster if this packet doesn't fit in a normal mbuf. */ if ( len > MHLEN - NFS_MAGIC_OFFSET ) { MCLGET( m, M_DONTWAIT ); if ( !( m->m_flags & M_EXT ) ) { m_freem( m ); return -1; } } /* Initialize packet header info. */ m->m_pkthdr.rcvif = &sc->sc_if; m->m_pkthdr.len = len; /* Set the length of this packet. */ m->m_len = len; /* The following silliness is to make NFS happy */ m->m_data += NFS_MAGIC_OFFSET; /* Get a packet. */ insw( sc->ioaddr[ FE_BMPR8 ], m->m_data, ( len + 1 ) >> 1 ); /* Get (actually just point to) the header part. */ eh = mtod( m, struct ether_header *); #define ETHER_ADDR_IS_MULTICAST(A) (*(char *)(A) & 1) #if NBPFILTER > 0 /* * Check if there's a BPF listener on this interface. * If it is, hand off the raw packet to bpf. */ if ( sc->sc_if.if_bpf ) { bpf_mtap( &sc->sc_if, m ); } #endif /* * Make sure this packet is (or may be) directed to us. * That is, the packet is either unicasted to our address, * or broad/multi-casted. If any other packets are * received, it is an indication of an error -- probably * 86960 is in a wrong operation mode. * Promiscuous mode is an exception. Under the mode, all * packets on the media must be received. (We must have * programmed the 86960 so.) */ if ( ( sc->sc_if.if_flags & IFF_PROMISC ) && !ETHER_ADDR_IS_MULTICAST( eh->ether_dhost ) && bcmp( eh->ether_dhost, sc->sc_enaddr, ETHER_ADDR_LEN ) != 0 ) { /* * The packet was not for us. This is normal since * we are now in promiscuous mode. Just drop the packet. */ m_freem( m ); return 0; } #if FE_DEBUG >= 3 if ( !ETHER_ADDR_IS_MULTICAST( eh->ether_dhost ) && bcmp( eh->ether_dhost, sc->sc_enaddr, ETHER_ADDR_LEN ) != 0 ) { /* * This packet was not for us. We can't be in promiscuous * mode since the case was handled by above test. * We found an error (of this driver.) */ log( LOG_WARNING, "fe%d: got an unwanted packet, dst = %6D\n", sc->sc_unit, eh->ether_dhost , ":" ); m_freem( m ); return 0; } #endif /* Strip off the Ethernet header. */ m->m_pkthdr.len -= sizeof ( struct ether_header ); m->m_len -= sizeof ( struct ether_header ); m->m_data += sizeof ( struct ether_header ); /* Feed the packet to upper layer. */ ether_input( &sc->sc_if, eh, m ); return 0; } /* * Write an mbuf chain to the transmission buffer memory using 16 bit PIO. * Returns number of bytes actually written, including length word. * * If an mbuf chain is too long for an Ethernet frame, it is not sent. * Packets shorter than Ethernet minimum are legal, and we pad them * before sending out. An exception is "partial" packets which are * shorter than mandatory Ethernet header. */ static void fe_write_mbufs ( struct fe_softc *sc, struct mbuf *m ) { u_short addr_bmpr8 = sc->ioaddr[ FE_BMPR8 ]; u_short length, len; struct mbuf *mp; u_char *data; u_short savebyte; /* WARNING: Architecture dependent! */ #define NO_PENDING_BYTE 0xFFFF static u_char padding [ ETHER_MIN_LEN - ETHER_CRC_LEN - ETHER_HDR_LEN ]; #if FE_DEBUG >= 2 /* First, count up the total number of bytes to copy */ length = 0; for ( mp = m; mp != NULL; mp = mp->m_next ) { length += mp->m_len; } /* Check if this matches the one in the packet header. */ if ( length != m->m_pkthdr.len ) { log( LOG_WARNING, "fe%d: packet length mismatch? (%d/%d)\n", sc->sc_unit, length, m->m_pkthdr.len ); } #else /* Just use the length value in the packet header. */ length = m->m_pkthdr.len; #endif #if FE_DEBUG >= 1 /* * Should never send big packets. If such a packet is passed, * it should be a bug of upper layer. We just ignore it. * ... Partial (too short) packets, neither. */ if ( length < ETHER_HDR_LEN || length > ETHER_MAX_LEN - ETHER_CRC_LEN ) { log( LOG_ERR, "fe%d: got an out-of-spec packet (%u bytes) to send\n", sc->sc_unit, length ); sc->sc_if.if_oerrors++; return; } #endif /* * Put the length word for this frame. * Does 86960 accept odd length? -- Yes. * Do we need to pad the length to minimum size by ourselves? * -- Generally yes. But for (or will be) the last * packet in the transmission buffer, we can skip the * padding process. It may gain performance slightly. FIXME. */ outw( addr_bmpr8, max( length, ETHER_MIN_LEN - ETHER_CRC_LEN ) ); /* * Update buffer status now. * Truncate the length up to an even number, since we use outw(). */ length = ( length + 1 ) & ~1; sc->txb_free -= FE_DATA_LEN_LEN + max( length, ETHER_MIN_LEN - ETHER_CRC_LEN); sc->txb_count++; /* * Transfer the data from mbuf chain to the transmission buffer. * MB86960 seems to require that data be transferred as words, and * only words. So that we require some extra code to patch * over odd-length mbufs. */ savebyte = NO_PENDING_BYTE; for ( mp = m; mp != 0; mp = mp->m_next ) { /* Ignore empty mbuf. */ len = mp->m_len; if ( len == 0 ) continue; /* Find the actual data to send. */ data = mtod(mp, caddr_t); /* Finish the last byte. */ if ( savebyte != NO_PENDING_BYTE ) { outw( addr_bmpr8, savebyte | ( *data << 8 ) ); data++; len--; savebyte = NO_PENDING_BYTE; } /* output contiguous words */ if (len > 1) { outsw( addr_bmpr8, data, len >> 1); data += len & ~1; len &= 1; } /* Save a remaining byte, if there is one. */ if ( len > 0 ) { savebyte = *data; } } /* Spit the last byte, if the length is odd. */ if ( savebyte != NO_PENDING_BYTE ) { outw( addr_bmpr8, savebyte ); } /* Pad to the Ethernet minimum length, if the packet is too short. */ if ( length < ETHER_MIN_LEN - ETHER_CRC_LEN ) { outsw( addr_bmpr8, padding, ( ETHER_MIN_LEN - ETHER_CRC_LEN - length ) >> 1); } } /* * Compute hash value for an Ethernet address */ static int fe_hash ( u_char * ep ) { #define FE_HASH_MAGIC_NUMBER 0xEDB88320L u_long hash = 0xFFFFFFFFL; int i, j; u_char b; u_long m; for ( i = ETHER_ADDR_LEN; --i >= 0; ) { b = *ep++; for ( j = 8; --j >= 0; ) { m = hash; hash >>= 1; if ( ( m ^ b ) & 1 ) hash ^= FE_HASH_MAGIC_NUMBER; b >>= 1; } } return ( ( int )( hash >> 26 ) ); } /* * Compute the multicast address filter from the * list of multicast addresses we need to listen to. */ static struct fe_filter fe_mcaf ( struct fe_softc *sc ) { int index; struct fe_filter filter; struct ether_multi *enm; struct ether_multistep step; filter = fe_filter_nothing; ETHER_FIRST_MULTI(step, &sc->arpcom, enm); while ( enm != NULL) { if ( bcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN) ) { return ( fe_filter_all ); } index = fe_hash( enm->enm_addrlo ); #if FE_DEBUG >= 4 log( LOG_INFO, "fe%d: hash(%6D) == %d\n", sc->sc_unit, enm->enm_addrlo , ":", index ); #endif filter.data[index >> 3] |= 1 << (index & 7); ETHER_NEXT_MULTI(step, enm); } return ( filter ); } /* * Calculate a new "multicast packet filter" and put the 86960 * receiver in appropriate mode. */ static void fe_setmode ( struct fe_softc *sc ) { int flags = sc->sc_if.if_flags; /* * If the interface is not running, we postpone the update * process for receive modes and multicast address filter * until the interface is restarted. It reduces some * complicated job on maintaining chip states. (Earlier versions * of this driver had a bug on that point...) * * To complete the trick, fe_init() calls fe_setmode() after * restarting the interface. */ if ( !( flags & IFF_RUNNING ) ) return; /* * Promiscuous mode is handled separately. */ if ( flags & IFF_PROMISC ) { /* * Program 86960 to receive all packets on the segment * including those directed to other stations. * Multicast filter stored in MARs are ignored * under this setting, so we don't need to update it. * * Promiscuous mode in FreeBSD 2 is used solely by * BPF, and BPF only listens to valid (no error) packets. * So, we ignore erroneous ones even in this mode. * (Older versions of fe driver mistook the point.) */ outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 | FE_D5_AFM0 | FE_D5_AFM1 ); sc->filter_change = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: promiscuous mode\n", sc->sc_unit ); #endif return; } /* * Turn the chip to the normal (non-promiscuous) mode. */ outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 | FE_D5_AFM1 ); /* * Find the new multicast filter value. * I'm not sure we have to handle modes other than MULTICAST. * Who sets ALLMULTI? Who turns MULTICAST off? FIXME. */ if ( flags & IFF_ALLMULTI ) { sc->filter = fe_filter_all; } else if ( flags & IFF_MULTICAST ) { sc->filter = fe_mcaf( sc ); } else { sc->filter = fe_filter_nothing; } sc->filter_change = 1; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: address filter: [%8D]\n", sc->sc_unit, sc->filter.data, " " ); #endif /* * We have to update the multicast filter in the 86960, A.S.A.P. * * Note that the DLC (Data Link Control unit, i.e. transmitter * and receiver) must be stopped when feeding the filter, and * DLC trashes all packets in both transmission and receive * buffers when stopped. * * ... Are the above sentences correct? I have to check the * manual of the MB86960A. FIXME. * * To reduce the packet loss, we delay the filter update * process until buffers are empty. */ if ( sc->txb_sched == 0 && sc->txb_count == 0 && !( inb( sc->ioaddr[ FE_DLCR1 ] ) & FE_D1_PKTRDY ) ) { /* * Buffers are (apparently) empty. Load * the new filter value into MARs now. */ fe_loadmar(sc); } else { /* * Buffers are not empty. Mark that we have to update * the MARs. The new filter will be loaded by feintr() * later. */ #if FE_DEBUG >= 4 log( LOG_INFO, "fe%d: filter change delayed\n", sc->sc_unit ); #endif } } /* * Load a new multicast address filter into MARs. * * The caller must have splimp'ed before fe_loadmar. * This function starts the DLC upon return. So it can be called only * when the chip is working, i.e., from the driver's point of view, when * a device is RUNNING. (I mistook the point in previous versions.) */ static void fe_loadmar ( struct fe_softc * sc ) { /* Stop the DLC (transmitter and receiver). */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Select register bank 1 for MARs. */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_MAR | FE_D7_POWER_UP ); /* Copy filter value into the registers. */ outblk( sc, FE_MAR8, sc->filter.data, FE_FILTER_LEN ); /* Restore the bank selection for BMPRs (i.e., runtime registers). */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_BMPR | FE_D7_POWER_UP ); /* Restart the DLC. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_ENABLE ); DELAY( 200 ); /* We have just updated the filter. */ sc->filter_change = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: address filter changed\n", sc->sc_unit ); #endif } #if FE_DEBUG >= 1 static void fe_dump ( int level, struct fe_softc * sc, char * message ) { log( level, "fe%d: %s," " DLCR = %02x %02x %02x %02x %02x %02x %02x %02x," " BMPR = xx xx %02x %02x %02x %02x %02x %02x," " asic = %02x %02x %02x %02x %02x %02x %02x %02x" " + %02x %02x %02x %02x %02x %02x %02x %02x\n", sc->sc_unit, message ? message : "registers", inb( sc->ioaddr[ FE_DLCR0 ] ), inb( sc->ioaddr[ FE_DLCR1 ] ), inb( sc->ioaddr[ FE_DLCR2 ] ), inb( sc->ioaddr[ FE_DLCR3 ] ), inb( sc->ioaddr[ FE_DLCR4 ] ), inb( sc->ioaddr[ FE_DLCR5 ] ), inb( sc->ioaddr[ FE_DLCR6 ] ), inb( sc->ioaddr[ FE_DLCR7 ] ), inb( sc->ioaddr[ FE_BMPR10 ] ), inb( sc->ioaddr[ FE_BMPR11 ] ), inb( sc->ioaddr[ FE_BMPR12 ] ), inb( sc->ioaddr[ FE_BMPR13 ] ), inb( sc->ioaddr[ FE_BMPR14 ] ), inb( sc->ioaddr[ FE_BMPR15 ] ), inb( sc->ioaddr[ 0x10 ] ), inb( sc->ioaddr[ 0x11 ] ), inb( sc->ioaddr[ 0x12 ] ), inb( sc->ioaddr[ 0x13 ] ), inb( sc->ioaddr[ 0x14 ] ), inb( sc->ioaddr[ 0x15 ] ), inb( sc->ioaddr[ 0x16 ] ), inb( sc->ioaddr[ 0x17 ] ), inb( sc->ioaddr[ 0x18 ] ), inb( sc->ioaddr[ 0x19 ] ), inb( sc->ioaddr[ 0x1A ] ), inb( sc->ioaddr[ 0x1B ] ), inb( sc->ioaddr[ 0x1C ] ), inb( sc->ioaddr[ 0x1D ] ), inb( sc->ioaddr[ 0x1E ] ), inb( sc->ioaddr[ 0x1F ] ) ); } #endif diff --git a/sys/dev/lnc/if_lnc.c b/sys/dev/lnc/if_lnc.c index 20201e46a93d..0df6bbe715f2 100644 --- a/sys/dev/lnc/if_lnc.c +++ b/sys/dev/lnc/if_lnc.c @@ -1,1860 +1,1860 @@ /*- * Copyright (c) 1995, 1996 * Paul Richards. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * verbatim and that no modifications are made prior to this * point in the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Paul Richards. * 4. The name Paul Richards may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY PAUL RICHARDS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL PAUL RICHARDS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* #define LNC_MULTICAST #define DIAGNOSTIC #define DEBUG * * TODO ---- * * This driver will need bounce buffer support when dma'ing to mbufs above the * 16Mb mark. * * Check all the XXX comments -- some of them are just things I've left * unfinished rather than "difficult" problems that were hacked around. * * Check log settings. * * Check how all the arpcom flags get set and used. * * Re-inline and re-static all routines after debugging. * * Remember to assign iobase in SHMEM probe routines. * * Replace all occurences of LANCE-controller-card etc in prints by the name * strings of the appropriate type -- nifty window dressing * * Add DEPCA support -- mostly done. * */ #include "pci.h" #include "lnc.h" #if NLNC > 0 #include "bpfilter.h" /* Some defines that should really be in generic locations */ #define FCS_LEN 4 #define MULTICAST_FILTER_LEN 8 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #if NBPFILTER > 0 #include #include #endif #include #include #include struct lnc_softc { struct arpcom arpcom; /* see ../../netinet/if_ether.h */ struct nic_info nic; /* NIC specific info */ int nrdre; struct host_ring_entry *recv_ring; /* start of alloc'd mem */ int recv_next; int ntdre; struct host_ring_entry *trans_ring; int trans_next; struct init_block *init_block; /* Initialisation block */ int pending_transmits; /* No. of transmit descriptors in use */ int next_to_send; struct mbuf *mbufs; int mbuf_count; int initialised; int rap; int rdp; #ifdef DEBUG int lnc_debug; #endif LNCSTATS_STRUCT }; static struct lnc_softc lnc_softc[NLNC]; static char const * const nic_ident[] = { "Unknown", "BICC", "NE2100", "DEPCA", }; static char const * const ic_ident[] = { "Unknown", "LANCE", "C-LANCE", "PCnet-ISA", "PCnet-ISA+", "PCnet-32 VL-Bus", "PCnet-PCI", /* "can't happen" */ }; #ifdef LNC_MULTICAST static void lnc_setladrf __P((struct lnc_softc *sc)); #endif static void lnc_stop __P((struct lnc_softc *sc)); static void lnc_reset __P((struct lnc_softc *sc)); static void lnc_free_mbufs __P((struct lnc_softc *sc)); static int alloc_mbuf_cluster __P((struct lnc_softc *sc, struct host_ring_entry *desc)); static struct mbuf *chain_mbufs __P((struct lnc_softc *sc, int start_of_packet, int pkt_len)); static struct mbuf *mbuf_packet __P((struct lnc_softc *sc, int start_of_packet, int pkt_len)); static void lnc_rint __P((struct lnc_softc *sc)); static void lnc_tint __P((struct lnc_softc *sc)); static int lnc_probe __P((struct isa_device *isa_dev)); static int ne2100_probe __P((struct lnc_softc *sc, unsigned iobase)); static int bicc_probe __P((struct lnc_softc *sc, unsigned iobase)); static int dec_macaddr_extract __P((u_char ring[], struct lnc_softc *sc)); static int depca_probe __P((struct lnc_softc *sc, unsigned iobase)); static int lance_probe __P((struct lnc_softc *sc)); static int pcnet_probe __P((struct lnc_softc *sc)); static int lnc_attach_sc __P((struct lnc_softc *sc, int unit)); static int lnc_attach __P((struct isa_device *isa_dev)); static void lnc_init __P((struct lnc_softc *sc)); static int mbuf_to_buffer __P((struct mbuf *m, char *buffer)); static struct mbuf *chain_to_cluster __P((struct mbuf *m)); static void lnc_start __P((struct ifnet *ifp)); static int lnc_ioctl __P((struct ifnet *ifp, int command, caddr_t data)); static void lnc_watchdog __P((struct ifnet *ifp)); #ifdef DEBUG static void lnc_dump_state __P((struct lnc_softc *sc)); static void mbuf_dump_chain __P((struct mbuf *m)); #endif #if NPCI > 0 void *lnc_attach_ne2100_pci __P((int unit, unsigned iobase)); #endif void lncintr_sc __P((struct lnc_softc *sc)); struct isa_driver lncdriver = {lnc_probe, lnc_attach, "lnc"}; static inline void write_csr(struct lnc_softc *sc, u_short port, u_short val) { outw(sc->rap, port); outw(sc->rdp, val); } static inline u_short read_csr(struct lnc_softc *sc, u_short port) { outw(sc->rap, port); return (inw(sc->rdp)); } #ifdef LNC_MULTICAST static inline u_long ether_crc(u_char *ether_addr) { #define POLYNOMIAL 0x04c11db6 u_long crc = 0xffffffffL; int i, j, carry; u_char b; for (i = ETHER_ADDR_LEN; --i >= 0;) { b = *ether_addr++; for (j = 8; --j >= 0;) { carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); crc <<= 1; b >>= 1; if (carry) crc = ((crc ^ POLYNOMIAL) | carry); } } return crc; #undef POLYNOMIAL } /* * Set up the logical address filter for multicast packets */ static void lnc_setladrf(struct lnc_softc *sc) { struct ifnet *ifp = &sc->arpcom.ac_if; struct ether_multistep step; struct ether_multi *enm; u_long index; int i; /* If promiscuous mode is set then all packets are accepted anyway */ if (ifp->if_flags & IFF_PROMISC) { ifp->if_flags |= IFF_ALLMULTI; for (i = 0; i < MULTICAST_FILTER_LEN; i++) sc->init_block->ladrf[i] = 0xff; return; } /* * For each multicast address, calculate a crc for that address and * then use the high order 6 bits of the crc as a hash code where * bits 3-5 select the byte of the address filter and bits 0-2 select * the bit within that byte. */ bzero(sc->init_block->ladrf, MULTICAST_FILTER_LEN); ETHER_FIRST_MULTI(step, &sc->arpcom, enm); while (enm != NULL) { if (bcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN) != 0) { /* * A range of multicast addresses should be accepted but * but for now just accept all multicasts. Only currently * used by multicast routing where the range would require * all bits to be set anyway. */ ifp->if_flags |= IFF_ALLMULTI; for (i = 0; i < MULTICAST_FILTER_LEN; i++) sc->init_block->ladrf[i] = 0xff; return; } index = ether_crc(enm->enm_addrlo) >> 26; sc->init_block->ladrf[index >> 3] |= 1 << (index & 7); ETHER_NEXT_MULTI(step, enm); } } #endif /* LNC_MULTICAST */ static void lnc_stop(struct lnc_softc *sc) { write_csr(sc, CSR0, STOP); } static void lnc_reset(struct lnc_softc *sc) { lnc_init(sc); } static void lnc_free_mbufs(struct lnc_softc *sc) { int i; /* * We rely on other routines to keep the buff.mbuf field valid. If * it's not NULL then we assume it points to an allocated mbuf. */ for (i = 0; i < NDESC(sc->nrdre); i++) if ((sc->recv_ring + i)->buff.mbuf) m_free((sc->recv_ring + i)->buff.mbuf); for (i = 0; i < NDESC(sc->ntdre); i++) if ((sc->trans_ring + i)->buff.mbuf) m_free((sc->trans_ring + i)->buff.mbuf); if (sc->mbuf_count) m_freem(sc->mbufs); } static inline int alloc_mbuf_cluster(struct lnc_softc *sc, struct host_ring_entry *desc) { register struct mds *md = desc->md; struct mbuf *m=0; int addr; /* Try and get cluster off local cache */ if (sc->mbuf_count) { sc->mbuf_count--; m = sc->mbufs; sc->mbufs = m->m_next; /* XXX m->m_data = m->m_ext.ext_buf;*/ } else { MGET(m, M_DONTWAIT, MT_DATA); if (!m) return(1); MCLGET(m, M_DONTWAIT); if (!m->m_ext.ext_buf) { m_free(m); return(1); } } desc->buff.mbuf = m; addr = kvtop(m->m_data); md->md0 = addr; md->md1= ((addr >> 16) & 0xff) | OWN; md->md2 = -(short)(MCLBYTES - sizeof(struct pkthdr)); md->md3 = 0; return(0); } static inline struct mbuf * chain_mbufs(struct lnc_softc *sc, int start_of_packet, int pkt_len) { struct mbuf *head, *m; struct host_ring_entry *desc; /* * Turn head into a pkthdr mbuf -- * assumes a pkthdr type mbuf was * allocated to the descriptor * originally. */ desc = sc->recv_ring + start_of_packet; head = desc->buff.mbuf; head->m_flags |= M_PKTHDR; m = head; do { m = desc->buff.mbuf; m->m_len = min((MCLBYTES - sizeof(struct pkthdr)), pkt_len); pkt_len -= m->m_len; if (alloc_mbuf_cluster(sc, desc)) return((struct mbuf *)NULL); INC_MD_PTR(start_of_packet, sc->nrdre) desc = sc->recv_ring + start_of_packet; m->m_next = desc->buff.mbuf; } while (start_of_packet != sc->recv_next); m->m_next = 0; return(head); } static inline struct mbuf * mbuf_packet(struct lnc_softc *sc, int start_of_packet, int pkt_len) { struct host_ring_entry *start; struct mbuf *head,*m,*m_prev; char *data,*mbuf_data; short blen; int amount; /* Get a pkthdr mbuf for the start of packet */ MGETHDR(head, M_DONTWAIT, MT_DATA); if (!head) { LNCSTATS(drop_packet) return(0); } m = head; m->m_len = 0; start = sc->recv_ring + start_of_packet; /*blen = -(start->md->md2);*/ blen = RECVBUFSIZE; /* XXX More PCnet-32 crap */ data = start->buff.data; mbuf_data = m->m_data; while (start_of_packet != sc->recv_next) { /* * If the data left fits in a single buffer then set * blen to the size of the data left. */ if (pkt_len < blen) blen = pkt_len; /* * amount is least of data in current ring buffer and * amount of space left in current mbuf. */ amount = min(blen, M_TRAILINGSPACE(m)); if (amount == 0) { /* mbuf must be empty */ m_prev = m; MGET(m, M_DONTWAIT, MT_DATA); if (!m) { m_freem(head); return(0); } if (pkt_len >= MINCLSIZE) MCLGET(m, M_DONTWAIT); m->m_len = 0; m_prev->m_next = m; amount = min(blen, M_TRAILINGSPACE(m)); mbuf_data = m->m_data; } bcopy(data, mbuf_data, amount); blen -= amount; pkt_len -= amount; m->m_len += amount; data += amount; mbuf_data += amount; if (blen == 0) { start->md->md1 &= HADR; start->md->md1 |= OWN; start->md->md2 = -RECVBUFSIZE; /* XXX - shouldn't be necessary */ INC_MD_PTR(start_of_packet, sc->nrdre) start = sc->recv_ring + start_of_packet; data = start->buff.data; /*blen = -(start->md->md2);*/ blen = RECVBUFSIZE; /* XXX More PCnet-32 crap */ } } return(head); } static inline void lnc_rint(struct lnc_softc *sc) { struct host_ring_entry *next, *start; int start_of_packet; struct mbuf *head; struct ether_header *eh; int lookahead; int flags; int pkt_len; /* * The LANCE will issue a RINT interrupt when the ownership of the * last buffer of a receive packet has been relinquished by the LANCE. * Therefore, it can be assumed that a complete packet can be found * before hitting buffers that are still owned by the LANCE, if not * then there is a bug in the driver that is causing the descriptors * to get out of sync. */ #ifdef DIAGNOSTIC if ((sc->recv_ring + sc->recv_next)->md->md1 & OWN) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Receive interrupt with buffer still owned by controller -- Resetting\n", unit); lnc_reset(sc); return; } if (!((sc->recv_ring + sc->recv_next)->md->md1 & STP)) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Receive interrupt but not start of packet -- Resetting\n", unit); lnc_reset(sc); return; } #endif lookahead = 0; next = sc->recv_ring + sc->recv_next; while ((flags = next->md->md1) & STP) { /* Make a note of the start of the packet */ start_of_packet = sc->recv_next; /* * Find the end of the packet. Even if not data chaining, * jabber packets can overrun into a second descriptor. * If there is no error, then the ENP flag is set in the last * descriptor of the packet. If there is an error then the ERR * flag will be set in the descriptor where the error occured. * Therefore, to find the last buffer of a packet we search for * either ERR or ENP. */ if (!(flags & (ENP | MDERR))) { do { INC_MD_PTR(sc->recv_next, sc->nrdre) next = sc->recv_ring + sc->recv_next; flags = next->md->md1; } while (!(flags & (STP | OWN | ENP | MDERR))); if (flags & STP) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Start of packet found before end of previous in receive ring -- Resetting\n", unit); lnc_reset(sc); return; } if (flags & OWN) { if (lookahead) { /* * Looked ahead into a packet still * being received */ sc->recv_next = start_of_packet; break; } else { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: End of received packet not found-- Resetting\n", unit); lnc_reset(sc); return; } } } pkt_len = (next->md->md3 & MCNT) - FCS_LEN; /* Move pointer onto start of next packet */ INC_MD_PTR(sc->recv_next, sc->nrdre) next = sc->recv_ring + sc->recv_next; if (flags & MDERR) { int unit = sc->arpcom.ac_if.if_unit; if (flags & RBUFF) { LNCSTATS(rbuff) log(LOG_ERR, "lnc%d: Receive buffer error\n", unit); } if (flags & OFLO) { /* OFLO only valid if ENP is not set */ if (!(flags & ENP)) { LNCSTATS(oflo) log(LOG_ERR, "lnc%d: Receive overflow error \n", unit); } } else if (flags & ENP) { /* * FRAM and CRC are valid only if ENP * is set and OFLO is not. */ if (flags & FRAM) { LNCSTATS(fram) log(LOG_ERR, "lnc%d: Framming error\n", unit); /* * FRAM is only set if there's a CRC * error so avoid multiple messages */ } else if (flags & CRC) { LNCSTATS(crc) log(LOG_ERR, "lnc%d: Receive CRC error\n", unit); } } /* Drop packet */ LNCSTATS(rerr) sc->arpcom.ac_if.if_ierrors++; while (start_of_packet != sc->recv_next) { start = sc->recv_ring + start_of_packet; start->md->md2 = -RECVBUFSIZE; /* XXX - shouldn't be necessary */ start->md->md1 &= HADR; start->md->md1 |= OWN; INC_MD_PTR(start_of_packet, sc->nrdre) } } else { /* Valid packet */ sc->arpcom.ac_if.if_ipackets++; if (sc->nic.mem_mode == DMA_MBUF) head = chain_mbufs(sc, start_of_packet, pkt_len); else head = mbuf_packet(sc, start_of_packet, pkt_len); if (head) { /* * First mbuf in packet holds the * ethernet and packet headers */ head->m_pkthdr.rcvif = &sc->arpcom.ac_if; head->m_pkthdr.len = pkt_len - sizeof *eh; /* * BPF expects the ether header to be in the first * mbuf of the chain so point eh at the right place * but don't increment the mbuf pointers before * the bpf tap. */ eh = (struct ether_header *) head->m_data; #if NBPFILTER > 0 if (sc->arpcom.ac_if.if_bpf) bpf_mtap(&sc->arpcom.ac_if, head); /* Check this packet is really for us */ if ((sc->arpcom.ac_if.if_flags & IFF_PROMISC) && !(eh->ether_dhost[0] & 1) && /* Broadcast and multicast */ (bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, sizeof(eh->ether_dhost)))) m_freem(head); else #endif { /* Skip over the ether header */ head->m_data += sizeof *eh; head->m_len -= sizeof *eh; ether_input(&sc->arpcom.ac_if, eh, head); } } else { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR,"lnc%d: Packet dropped, no mbufs\n",unit); LNCSTATS(drop_packet) } } lookahead++; } /* * At this point all completely received packets have been processed * so clear RINT since any packets that have arrived while we were in * here have been dealt with. */ outw(sc->rdp, RINT | INEA); } static inline void lnc_tint(struct lnc_softc *sc) { struct host_ring_entry *next, *start; int start_of_packet; int lookahead; /* * If the driver is reset in this routine then we return immediately to * the interrupt driver routine. Any interrupts that have occured * since the reset will be dealt with there. sc->trans_next * should point to the start of the first packet that was awaiting * transmission after the last transmit interrupt was dealt with. The * LANCE should have relinquished ownership of that descriptor before * the interrupt. Therefore, sc->trans_next should point to a * descriptor with STP set and OWN cleared. If not then the driver's * pointers are out of sync with the LANCE, which signifies a bug in * the driver. Therefore, the following two checks are really * diagnostic, since if the driver is working correctly they should * never happen. */ #ifdef DIAGNOSTIC if ((sc->trans_ring + sc->trans_next)->md->md1 & OWN) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Transmit interrupt with buffer still owned by controller -- Resetting\n", unit); lnc_reset(sc); return; } #endif /* * The LANCE will write the status information for the packet it just * tried to transmit in one of two places. If the packet was * transmitted successfully then the status will be written into the * last descriptor of the packet. If the transmit failed then the * status will be written into the descriptor that was being accessed * when the error occured and all subsequent descriptors in that * packet will have been relinquished by the LANCE. * * At this point we know that sc->trans_next points to the start * of a packet that the LANCE has just finished trying to transmit. * We now search for a buffer with either ENP or ERR set. */ lookahead = 0; do { start_of_packet = sc->trans_next; next = sc->trans_ring + sc->trans_next; #ifdef DIAGNOSTIC if (!(next->md->md1 & STP)) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Transmit interrupt but not start of packet -- Resetting\n", unit); lnc_reset(sc); return; } #endif /* * Find end of packet. */ if (!(next->md->md1 & (ENP | MDERR))) { do { INC_MD_PTR(sc->trans_next, sc->ntdre) next = sc->trans_ring + sc->trans_next; } while (!(next->md->md1 & (STP | OWN | ENP | MDERR))); if (next->md->md1 & STP) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Start of packet found before end of previous in transmit ring -- Resetting\n", unit); lnc_reset(sc); return; } if (next->md->md1 & OWN) { if (lookahead) { /* * Looked ahead into a packet still * being transmitted */ sc->trans_next = start_of_packet; break; } else { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: End of transmitted packet not found -- Resetting\n", unit); lnc_reset(sc); return; } } } /* * Check for ERR first since other flags are irrelevant if an * error occurred. */ if (next->md->md1 & MDERR) { int unit = sc->arpcom.ac_if.if_unit; LNCSTATS(terr) sc->arpcom.ac_if.if_oerrors++; if (next->md->md3 & LCOL) { LNCSTATS(lcol) log(LOG_ERR, "lnc%d: Transmit late collision -- Net error?\n", unit); sc->arpcom.ac_if.if_collisions++; /* * Clear TBUFF since it's not valid when LCOL * set */ next->md->md3 &= ~TBUFF; } if (next->md->md3 & LCAR) { LNCSTATS(lcar) log(LOG_ERR, "lnc%d: Loss of carrier during transmit -- Net error?\n", unit); } if (next->md->md3 & RTRY) { LNCSTATS(rtry) log(LOG_ERR, "lnc%d: Transmit of packet failed after 16 attempts -- TDR = %d\n", unit, ((sc->trans_ring + sc->trans_next)->md->md3 & TDR)); sc->arpcom.ac_if.if_collisions += 16; /* * Clear TBUFF since it's not valid when RTRY * set */ next->md->md3 &= ~TBUFF; } /* * TBUFF is only valid if neither LCOL nor RTRY are set. * We need to check UFLO after LCOL and RTRY so that we * know whether or not TBUFF is valid. If either are * set then TBUFF will have been cleared above. A * UFLO error will turn off the transmitter so we * have to reset. * */ if (next->md->md3 & UFLO) { LNCSTATS(uflo) /* * If an UFLO has occured it's possibly due * to a TBUFF error */ if (next->md->md3 & TBUFF) { LNCSTATS(tbuff) log(LOG_ERR, "lnc%d: Transmit buffer error -- Resetting\n", unit); } else log(LOG_ERR, "lnc%d: Transmit underflow error -- Resetting\n", unit); lnc_reset(sc); return; } do { INC_MD_PTR(sc->trans_next, sc->ntdre) next = sc->trans_ring + sc->trans_next; } while (!(next->md->md1 & STP) && (sc->trans_next != sc->next_to_send)); } else { /* * Since we check for ERR first then if we get here * the packet was transmitted correctly. There may * still have been non-fatal errors though. * Don't bother checking for DEF, waste of time. */ sc->arpcom.ac_if.if_opackets++; if (next->md->md1 & MORE) { LNCSTATS(more) sc->arpcom.ac_if.if_collisions += 2; } /* * ONE is invalid if LCOL is set. If LCOL was set then * ERR would have also been set and we would have * returned from lnc_tint above. Therefore we can * assume if we arrive here that ONE is valid. * */ if (next->md->md1 & ONE) { LNCSTATS(one) sc->arpcom.ac_if.if_collisions++; } INC_MD_PTR(sc->trans_next, sc->ntdre) next = sc->trans_ring + sc->trans_next; } /* * Clear descriptors and free any mbufs. */ do { start = sc->trans_ring + start_of_packet; start->md->md1 &= HADR; if (sc->nic.mem_mode == DMA_MBUF) { /* Cache clusters on a local queue */ if ((start->buff.mbuf->m_flags & M_EXT) && (sc->mbuf_count < MBUF_CACHE_LIMIT)) { if (sc->mbuf_count) { start->buff.mbuf->m_next = sc->mbufs; sc->mbufs = start->buff.mbuf; } else sc->mbufs = start->buff.mbuf; sc->mbuf_count++; start->buff.mbuf = 0; } else { struct mbuf *junk; MFREE(start->buff.mbuf, junk); start->buff.mbuf = 0; } } sc->pending_transmits--; INC_MD_PTR(start_of_packet, sc->ntdre) }while (start_of_packet != sc->trans_next); /* * There's now at least one free descriptor * in the ring so indicate that we can accept * more packets again. */ sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; lookahead++; } while (sc->pending_transmits && !(next->md->md1 & OWN)); /* * Clear TINT since we've dealt with all * the completed transmissions. */ outw(sc->rdp, TINT | INEA); /* XXX only while doing if_is comparisons */ if (!(sc->arpcom.ac_if.if_flags & IFF_OACTIVE)) lnc_start(&sc->arpcom.ac_if); } static int lnc_probe(struct isa_device * isa_dev) { int nports; int unit = isa_dev->id_unit; struct lnc_softc *sc = &lnc_softc[unit]; unsigned iobase = isa_dev->id_iobase; #ifdef DIAGNOSTIC int vsw; vsw = inw(isa_dev->id_iobase + PCNET_VSW); printf("Vendor Specific Word = %x\n", vsw); #endif nports = bicc_probe(sc, iobase); if (nports == 0) nports = ne2100_probe(sc, iobase); if (nports == 0) nports = depca_probe(sc, iobase); return (nports); } static int ne2100_probe(struct lnc_softc *sc, unsigned iobase) { int i; sc->rap = iobase + PCNET_RAP; sc->rdp = iobase + PCNET_RDP; if ((sc->nic.ic = pcnet_probe(sc))) { sc->nic.ident = NE2100; sc->nic.mem_mode = DMA_FIXED; /* XXX - For now just use the defines */ sc->nrdre = NRDRE; sc->ntdre = NTDRE; /* Extract MAC address from PROM */ for (i = 0; i < ETHER_ADDR_LEN; i++) sc->arpcom.ac_enaddr[i] = inb(iobase + i); return (NE2100_IOSIZE); } else { return (0); } } static int bicc_probe(struct lnc_softc *sc, unsigned iobase) { int i; /* * There isn't any way to determine if a NIC is a BICC. Basically, if * the lance probe succeeds using the i/o addresses of the BICC then * we assume it's a BICC. * */ sc->rap = iobase + BICC_RAP; sc->rdp = iobase + BICC_RDP; /* I think all these cards us the Am7990 */ if ((sc->nic.ic = lance_probe(sc))) { sc->nic.ident = BICC; sc->nic.mem_mode = DMA_FIXED; /* XXX - For now just use the defines */ sc->nrdre = NRDRE; sc->ntdre = NTDRE; /* Extract MAC address from PROM */ for (i = 0; i < ETHER_ADDR_LEN; i++) sc->arpcom.ac_enaddr[i] = inb(iobase + (i * 2)); return (BICC_IOSIZE); } else { return (0); } } /* * I don't have data sheets for the dec cards but it looks like the mac * address is contained in a 32 byte ring. Each time you read from the port * you get the next byte in the ring. The mac address is stored after a * signature so keep searching for the signature first. */ static int dec_macaddr_extract(u_char ring[], struct lnc_softc * sc) { const unsigned char signature[] = {0xff, 0x00, 0x55, 0xaa, 0xff, 0x00, 0x55, 0xaa}; int i, j, rindex; for (i = 0; i < sizeof ring; i++) { for (j = 0, rindex = i; j < sizeof signature; j++) { if (ring[rindex] != signature[j]) break; if (++rindex > sizeof ring) rindex = 0; } if (j == sizeof signature) { for (j = 0, rindex = i; j < ETHER_ADDR_LEN; j++) { sc->arpcom.ac_enaddr[j] = ring[rindex]; if (++rindex > sizeof ring) rindex = 0; } return (1); } } return (0); } static int depca_probe(struct lnc_softc *sc, unsigned iobase) { int i; unsigned char maddr_ring[DEPCA_ADDR_ROM_SIZE]; sc->rap = iobase + DEPCA_RAP; sc->rdp = iobase + DEPCA_RDP; if ((sc->nic.ic = lance_probe(sc))) { sc->nic.ident = DEPCA; sc->nic.mem_mode = SHMEM; /* Extract MAC address from PROM */ for (i = 0; i < DEPCA_ADDR_ROM_SIZE; i++) maddr_ring[i] = inb(iobase + DEPCA_ADP); if (dec_macaddr_extract(maddr_ring, sc)) { return (DEPCA_IOSIZE); } } return (0); } static int lance_probe(struct lnc_softc *sc) { write_csr(sc, CSR0, STOP); if ((inw(sc->rdp) & STOP) && !(read_csr(sc, CSR3))) { /* * Check to see if it's a C-LANCE. For the LANCE the INEA bit * cannot be set while the STOP bit is. This restriction is * removed for the C-LANCE. */ write_csr(sc, CSR0, INEA); if (read_csr(sc, CSR0) & INEA) return (C_LANCE); else return (LANCE); } else return (UNKNOWN); } static int pcnet_probe(struct lnc_softc *sc) { u_long chip_id; int type; /* * The PCnet family don't reset the RAP register on reset so we'll * have to write during the probe :-) It does have an ID register * though so the probe is just a matter of reading it. */ if ((type = lance_probe(sc))) { chip_id = read_csr(sc, CSR89); chip_id <<= 16; chip_id |= read_csr(sc, CSR88); if (chip_id & AMD_MASK) { chip_id >>= 12; switch (chip_id & PART_MASK) { case Am79C960: return (PCnet_ISA); case Am79C961: return (PCnet_ISAplus); case Am79C965: return (PCnet_32); case Am79C970: /* * do NOT try to ISA attach the PCI version */ return (0); default: break; } } } return (type); } static int lnc_attach_sc(struct lnc_softc *sc, int unit) { int lnc_mem_size; /* * Allocate memory for use by the controller. * * XXX -- the Am7990 and Am79C960 only have 24 address lines and so can * only access the lower 16Mb of physical memory. For the moment we * assume that malloc will allocate memory within the lower 16Mb * range. This is not a very valid assumption but there's nothing * that can be done about it yet. For shared memory NICs this isn't * relevant. * */ lnc_mem_size = ((NDESC(sc->nrdre) + NDESC(sc->ntdre)) * sizeof(struct host_ring_entry)); if (sc->nic.mem_mode != SHMEM) lnc_mem_size += sizeof(struct init_block) + (sizeof(struct mds) * (NDESC(sc->nrdre) + NDESC(sc->ntdre))) + MEM_SLEW; /* If using DMA to fixed host buffers then allocate memory for them */ if (sc->nic.mem_mode == DMA_FIXED) lnc_mem_size += (NDESC(sc->nrdre) * RECVBUFSIZE) + (NDESC(sc->ntdre) * TRANSBUFSIZE); sc->recv_ring = malloc(lnc_mem_size, M_DEVBUF, M_NOWAIT); if (!sc->recv_ring) { log(LOG_ERR, "lnc%d: Couldn't allocate memory for NIC\n", unit); return (0); /* XXX -- attach failed -- not tested in * calling routines */ } /* * XXX - Shouldn't this be skipped for the EISA and PCI versions ??? * Print the message but do not return for the PCnet_PCI ! */ if ((sc->nic.mem_mode != SHMEM) && (kvtop(sc->recv_ring) > 0x1000000)) { log(LOG_ERR, "lnc%d: Memory allocated above 16Mb limit\n", unit); if (sc->nic.ic != PCnet_PCI) return (0); } /* Set default mode */ sc->nic.mode = NORMAL; /* Fill in arpcom structure entries */ sc->arpcom.ac_if.if_softc = sc; sc->arpcom.ac_if.if_name = lncdriver.name; sc->arpcom.ac_if.if_unit = unit; sc->arpcom.ac_if.if_mtu = ETHERMTU; sc->arpcom.ac_if.if_flags = IFF_BROADCAST | IFF_SIMPLEX; sc->arpcom.ac_if.if_timer = 0; sc->arpcom.ac_if.if_output = ether_output; sc->arpcom.ac_if.if_start = lnc_start; sc->arpcom.ac_if.if_ioctl = lnc_ioctl; sc->arpcom.ac_if.if_watchdog = lnc_watchdog; sc->arpcom.ac_if.if_type = IFT_ETHER; sc->arpcom.ac_if.if_addrlen = ETHER_ADDR_LEN; sc->arpcom.ac_if.if_hdrlen = ETHER_HDR_LEN; /* * XXX -- should check return status of if_attach */ if_attach(&sc->arpcom.ac_if); ether_ifattach(&sc->arpcom.ac_if); printf("lnc%d: ", unit); if (sc->nic.ic == LANCE || sc->nic.ic == C_LANCE) printf("%s (%s)", nic_ident[sc->nic.ident], ic_ident[sc->nic.ic]); else printf("%s", ic_ident[sc->nic.ic]); printf(" address %6D\n", sc->arpcom.ac_enaddr, ":"); #if NBPFILTER > 0 bpfattach(&sc->arpcom.ac_if, DLT_EN10MB, sizeof(struct ether_header)); #endif return (1); } static int lnc_attach(struct isa_device * isa_dev) { int unit = isa_dev->id_unit; struct lnc_softc *sc = &lnc_softc[unit]; int result = lnc_attach_sc (sc, unit); if (result == 0) return (0); /* * XXX - is it safe to call isa_dmacascade() after if_attach() * and ether_ifattach() have been called in lnc_attach() ??? */ if ((sc->nic.mem_mode != SHMEM) && (sc->nic.ic != PCnet_32) && (sc->nic.ic != PCnet_PCI)) isa_dmacascade(isa_dev->id_drq); return result; } #if NPCI > 0 void * lnc_attach_ne2100_pci(int unit, unsigned iobase) { struct lnc_softc *sc = malloc(sizeof *sc, M_DEVBUF, M_NOWAIT); if (sc) { bzero (sc, sizeof *sc); if ((ne2100_probe(sc, iobase) == 0) || (lnc_attach_sc(sc, unit) == 0)) { free(sc, M_DEVBUF); sc = NULL; } } return sc; } #endif static void lnc_init(struct lnc_softc *sc) { int s, i; char *lnc_mem; /* Check that interface has valid address */ - if (!sc->arpcom.ac_if.if_addrlist) + if (TAILQ_EMPTY(&sc->arpcom.ac_if.if_addrhead)) /* XXX unlikely */ return; /* Shut down interface */ s = splimp(); lnc_stop(sc); sc->arpcom.ac_if.if_flags |= IFF_BROADCAST | IFF_SIMPLEX; /* XXX??? */ /* * This sets up the memory area for the controller. Memory is set up for * the initialisation block (12 words of contiguous memory starting * on a word boundary),the transmit and receive ring structures (each * entry is 4 words long and must start on a quadword boundary) and * the data buffers. * * The alignment tests are particularly paranoid. */ sc->recv_next = 0; sc->trans_ring = sc->recv_ring + NDESC(sc->nrdre); sc->trans_next = 0; if (sc->nic.mem_mode == SHMEM) lnc_mem = (char *) sc->nic.iobase; else lnc_mem = (char *) (sc->trans_ring + NDESC(sc->ntdre)); lnc_mem = (char *)(((int)lnc_mem + 1) & ~1); sc->init_block = (struct init_block *) ((int) lnc_mem & ~1); lnc_mem = (char *) (sc->init_block + 1); lnc_mem = (char *)(((int)lnc_mem + 7) & ~7); /* Initialise pointers to descriptor entries */ for (i = 0; i < NDESC(sc->nrdre); i++) { (sc->recv_ring + i)->md = (struct mds *) lnc_mem; lnc_mem += sizeof(struct mds); } for (i = 0; i < NDESC(sc->ntdre); i++) { (sc->trans_ring + i)->md = (struct mds *) lnc_mem; lnc_mem += sizeof(struct mds); } /* Initialise the remaining ring entries */ if (sc->nic.mem_mode == DMA_MBUF) { sc->mbufs = 0; sc->mbuf_count = 0; /* Free previously allocated mbufs */ if (sc->initialised) lnc_free_mbufs(sc); for (i = 0; i < NDESC(sc->nrdre); i++) { if (alloc_mbuf_cluster(sc, sc->recv_ring+i)) { log(LOG_ERR, "Initialisation failed -- no mbufs\n"); splx(s); return; } } for (i = 0; i < NDESC(sc->ntdre); i++) { (sc->trans_ring + i)->buff.mbuf = 0; (sc->trans_ring + i)->md->md0 = 0; (sc->trans_ring + i)->md->md1 = 0; (sc->trans_ring + i)->md->md2 = 0; (sc->trans_ring + i)->md->md3 = 0; } } else { for (i = 0; i < NDESC(sc->nrdre); i++) { (sc->recv_ring + i)->md->md0 = kvtop(lnc_mem); (sc->recv_ring + i)->md->md1 = ((kvtop(lnc_mem) >> 16) & 0xff) | OWN; (sc->recv_ring + i)->md->md2 = -RECVBUFSIZE; (sc->recv_ring + i)->md->md3 = 0; (sc->recv_ring + i)->buff.data = lnc_mem; lnc_mem += RECVBUFSIZE; } for (i = 0; i < NDESC(sc->ntdre); i++) { (sc->trans_ring + i)->md->md0 = kvtop(lnc_mem); (sc->trans_ring + i)->md->md1 = ((kvtop(lnc_mem) >> 16) & 0xff); (sc->trans_ring + i)->md->md2 = 0; (sc->trans_ring + i)->md->md3 = 0; (sc->trans_ring + i)->buff.data = lnc_mem; lnc_mem += TRANSBUFSIZE; } } sc->next_to_send = 0; /* Set up initialisation block */ sc->init_block->mode = sc->nic.mode; for (i = 0; i < ETHER_ADDR_LEN; i++) sc->init_block->padr[i] = sc->arpcom.ac_enaddr[i]; #ifdef LNC_MULTICAST lnc_setladrf(sc); #else for (i = 0; i < MULTICAST_FILTER_LEN; i++) sc->init_block->ladrf[i] = MULTI_INIT_ADDR; #endif sc->init_block->rdra = kvtop(sc->recv_ring->md); sc->init_block->rlen = ((kvtop(sc->recv_ring->md) >> 16) & 0xff) | (sc->nrdre << 13); sc->init_block->tdra = kvtop(sc->trans_ring->md); sc->init_block->tlen = ((kvtop(sc->trans_ring->md) >> 16) & 0xff) | (sc->ntdre << 13); /* Set initialised to show that the memory area is valid */ sc->initialised = 1; sc->pending_transmits = 0; /* Give the LANCE the physical address of the initialisation block */ write_csr(sc, CSR1, kvtop(sc->init_block)); write_csr(sc, CSR2, (kvtop(sc->init_block) >> 16) & 0xff); /* * Depending on which controller this is, CSR3 has different meanings. * For the Am7990 it controls DMA operations, for the Am79C960 it * controls interrupt masks and transmitter algorithms. In either * case, none of the flags are set. * */ write_csr(sc, CSR3, 0); /* Let's see if it starts */ write_csr(sc, CSR0, INIT); for (i = 0; i < 1000; i++) if (read_csr(sc, CSR0) & IDON) break; /* * Now that the initialisation is complete there's no reason to * access anything except CSR0, so we leave RAP pointing there * so we can just access RDP from now on, saving an outw each * time. */ if (read_csr(sc, CSR0) & IDON) { /* * Enable interrupts, start the LANCE, mark the interface as * running and transmit any pending packets. */ write_csr(sc, CSR0, STRT | INEA); sc->arpcom.ac_if.if_flags |= IFF_RUNNING; sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; lnc_start(&sc->arpcom.ac_if); } else log(LOG_ERR, "lnc%d: Initialisation failed\n", sc->arpcom.ac_if.if_unit); splx(s); } /* * The interrupt flag (INTR) will be set and provided that the interrupt enable * flag (INEA) is also set, the interrupt pin will be driven low when any of * the following occur: * * 1) Completion of the initialisation routine (IDON). 2) The reception of a * packet (RINT). 3) The transmission of a packet (TINT). 4) A transmitter * timeout error (BABL). 5) A missed packet (MISS). 6) A memory error (MERR). * * The interrupt flag is cleared when all of the above conditions are cleared. * * If the driver is reset from this routine then it first checks to see if any * interrupts have ocurred since the reset and handles them before returning. * This is because the NIC may signify a pending interrupt in CSR0 using the * INTR flag even if a hardware interrupt is currently inhibited (at least I * think it does from reading the data sheets). We may as well deal with * these pending interrupts now rather than get the overhead of another * hardware interrupt immediately upon returning from the interrupt handler. * */ void lncintr_sc(struct lnc_softc *sc) { int unit = sc->arpcom.ac_if.if_unit; u_short csr0; /* * INEA is the only bit that can be cleared by writing a 0 to it so * we have to include it in any writes that clear other flags. */ while ((csr0 = inw(sc->rdp)) & INTR) { /* * Clear interrupt flags early to avoid race conditions. The * controller can still set these flags even while we're in * this interrupt routine. If the flag is still set from the * event that caused this interrupt any new events will * be missed. */ outw(sc->rdp, IDON | CERR | BABL | MISS | MERR | RINT | TINT | INEA); /* We don't do anything with the IDON flag */ if (csr0 & ERR) { if (csr0 & CERR) { log(LOG_ERR, "lnc%d: Heartbeat error -- SQE test failed\n", unit); LNCSTATS(cerr) } if (csr0 & BABL) { log(LOG_ERR, "lnc%d: Babble error - more than 1519 bytes transmitted\n", unit); LNCSTATS(babl) sc->arpcom.ac_if.if_oerrors++; } if (csr0 & MISS) { log(LOG_ERR, "lnc%d: Missed packet -- no receive buffer\n", unit); LNCSTATS(miss) sc->arpcom.ac_if.if_ierrors++; } if (csr0 & MERR) { log(LOG_ERR, "lnc%d: Memory error -- Resetting\n", unit); LNCSTATS(merr) lnc_reset(sc); continue; } } if (csr0 & RINT) { LNCSTATS(rint) lnc_rint(sc); } if (csr0 & TINT) { LNCSTATS(tint) sc->arpcom.ac_if.if_timer = 0; lnc_tint(sc); } /* * If there's room in the transmit descriptor ring then queue * some more transmit packets. */ if (!(sc->arpcom.ac_if.if_flags & IFF_OACTIVE)) lnc_start(&sc->arpcom.ac_if); } } void lncintr(int unit) { struct lnc_softc *sc = &lnc_softc[unit]; lncintr_sc (sc); } static inline int mbuf_to_buffer(struct mbuf *m, char *buffer) { int len=0; for( ; m; m = m->m_next) { bcopy(mtod(m, caddr_t), buffer, m->m_len); buffer += m->m_len; len += m->m_len; } return(len); } static inline struct mbuf * chain_to_cluster(struct mbuf *m) { struct mbuf *new; MGET(new, M_DONTWAIT, MT_DATA); if (new) { MCLGET(new, M_DONTWAIT); if (new->m_ext.ext_buf) { new->m_len = mbuf_to_buffer(m, new->m_data); m_freem(m); return(new); } else m_free(new); } return(0); } /* * IFF_OACTIVE and IFF_RUNNING are checked in ether_output so it's redundant * to check them again since we wouldn't have got here if they were not * appropriately set. This is also called from lnc_init and lncintr but the * flags should be ok at those points too. */ static void lnc_start(struct ifnet *ifp) { struct lnc_softc *sc = ifp->if_softc; struct host_ring_entry *desc; int tmp; int end_of_packet; struct mbuf *head, *m; int len, chunk; int addr; int no_entries_needed; do { IF_DEQUEUE(&sc->arpcom.ac_if.if_snd, head); if (!head) return; if (sc->nic.mem_mode == DMA_MBUF) { no_entries_needed = 0; for (m=head; m; m = m->m_next) no_entries_needed++; /* * We try and avoid bcopy as much as possible * but there are two cases when we use it. * * 1) If there are not enough free entries in the ring * to hold each mbuf in the chain then compact the * chain into a single cluster. * * 2) The Am7990 and Am79C90 must not have less than * 100 bytes in the first descriptor of a chained * packet so it's necessary to shuffle the mbuf * contents to ensure this. */ if (no_entries_needed > (NDESC(sc->ntdre) - sc->pending_transmits)) if (!(head = chain_to_cluster(head))) { log(LOG_ERR, "lnc%d: Couldn't get mbuf for transmit packet -- Resetting \n ",ifp->if_unit); lnc_reset(sc); return; } else if ((sc->nic.ic == LANCE) || (sc->nic.ic == C_LANCE)) { if ((head->m_len < 100) && (head->m_next)) { len = 100 - head->m_len; if (M_TRAILINGSPACE(head) < len) { /* * Move data to start of data * area. We assume the first * mbuf has a packet header * and is not a cluster. */ bcopy((caddr_t)head->m_data, (caddr_t)head->m_pktdat, head->m_len); head->m_data = head->m_pktdat; } m = head->m_next; while (m && (len > 0)) { chunk = min(len, m->m_len); bcopy(mtod(m, caddr_t), mtod(head, caddr_t) + head->m_len, chunk); len -= chunk; head->m_len += chunk; m->m_len -= chunk; m->m_data += chunk; if (m->m_len <= 0) { MFREE(m, head->m_next); m = head->m_next; } } } } tmp = sc->next_to_send; /* * On entering this loop we know that tmp points to a * descriptor with a clear OWN bit. */ desc = sc->trans_ring + tmp; len = ETHER_MIN_LEN; for (m = head; m; m = m->m_next) { desc->buff.mbuf = m; addr = kvtop(m->m_data); desc->md->md0 = addr; desc->md->md1 = ((addr >> 16) & 0xff); desc->md->md3 = 0; desc->md->md2 = -m->m_len; sc->pending_transmits++; len -= m->m_len; INC_MD_PTR(tmp, sc->ntdre) desc = sc->trans_ring + tmp; } end_of_packet = tmp; DEC_MD_PTR(tmp, sc->ntdre) desc = sc->trans_ring + tmp; desc->md->md1 |= ENP; if (len > 0) desc->md->md2 -= len; /* * Set OWN bits in reverse order, otherwise the Lance * could start sending the packet before all the * buffers have been relinquished by the host. */ while (tmp != sc->next_to_send) { desc->md->md1 |= OWN; DEC_MD_PTR(tmp, sc->ntdre) desc = sc->trans_ring + tmp; } sc->next_to_send = end_of_packet; desc->md->md1 |= STP | OWN; } else { sc->pending_transmits++; desc = sc->trans_ring + sc->next_to_send; len = mbuf_to_buffer(head, desc->buff.data); desc->md->md3 = 0; desc->md->md2 = -max(len, ETHER_MIN_LEN); desc->md->md1 |= OWN | STP | ENP; INC_MD_PTR(sc->next_to_send, sc->ntdre) } /* Force an immediate poll of the transmit ring */ outw(sc->rdp, TDMD | INEA); /* * Set a timer so if the buggy Am7990.h shuts * down we can wake it up. */ ifp->if_timer = 2; #if NBPFILTER > 0 if (sc->arpcom.ac_if.if_bpf) bpf_mtap(&sc->arpcom.ac_if, head); #endif if (sc->nic.mem_mode != DMA_MBUF) m_freem(head); } while (sc->pending_transmits < NDESC(sc->ntdre)); /* * Transmit ring is full so set IFF_OACTIVE * since we can't buffer any more packets. */ sc->arpcom.ac_if.if_flags |= IFF_OACTIVE; LNCSTATS(trans_ring_full) } static int lnc_ioctl(struct ifnet * ifp, int command, caddr_t data) { struct lnc_softc *sc = ifp->if_softc; struct ifaddr *ifa = (struct ifaddr *) data; struct ifreq *ifr = (struct ifreq *) data; int s, error = 0; s = splimp(); switch (command) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: lnc_init(sc); arp_ifinit((struct arpcom *)ifp, ifa); break; #endif default: lnc_init(sc); break; } break; case SIOCSIFFLAGS: #ifdef DEBUG if (ifp->if_flags & IFF_DEBUG) sc->lnc_debug = 1; else sc->lnc_debug = 0; #endif if (ifp->if_flags & IFF_PROMISC) { if (!(sc->nic.mode & PROM)) { sc->nic.mode |= PROM; lnc_init(sc); } } else if (sc->nic.mode & PROM) { sc->nic.mode &= ~PROM; lnc_init(sc); } if ((ifp->if_flags & IFF_UP) == 0 && (ifp->if_flags & IFF_RUNNING) != 0) { /* * If interface is marked down and it is running, * then stop it. */ lnc_stop(sc); ifp->if_flags &= ~IFF_RUNNING; } else if ((ifp->if_flags & IFF_UP) != 0 && (ifp->if_flags & IFF_RUNNING) == 0) { /* * If interface is marked up and it is stopped, then * start it. */ lnc_init(sc); } break; #ifdef LNC_MULTICAST case SIOCADDMULTI: case SIOCDELMULTI: error = (command == SIOCADDMULTI) ? ether_addmulti(ifr, &sc->arpcom) : ether_delmulti(ifr, &sc->arpcom); if (error == ENETRESET) { lnc_setladrf(sc); error = 0; } break; #endif case SIOCSIFMTU: /* * Set the interface MTU. */ if (ifr->ifr_mtu > ETHERMTU) { error = EINVAL; } else ifp->if_mtu = ifr->ifr_mtu; break; default: error = EINVAL; } (void) splx(s); return error; } static void lnc_watchdog(struct ifnet *ifp) { log(LOG_ERR, "lnc%d: Device timeout -- Resetting\n", ifp->if_unit); ifp->if_oerrors++; lnc_reset(ifp->if_softc); } #ifdef DEBUG static void lnc_dump_state(struct lnc_softc *sc) { int i; printf("\nDriver/NIC [%d] state dump\n", sc->arpcom.ac_if.if_unit); printf("Memory access mode: %b\n", sc->nic.mem_mode, MEM_MODES); printf("Host memory\n"); printf("-----------\n"); printf("Receive ring: base = %x, next = %x\n", sc->recv_ring, (sc->recv_ring + sc->recv_next)); for (i = 0; i < NDESC(sc->nrdre); i++) printf("\t%d:%x md = %x buff = %x\n", i, sc->recv_ring + i, (sc->recv_ring + i)->md, (sc->recv_ring + i)->buff); printf("Transmit ring: base = %x, next = %x\n", sc->trans_ring, (sc->trans_ring + sc->trans_next)); for (i = 0; i < NDESC(sc->ntdre); i++) printf("\t%d:%x md = %x buff = %x\n", i, sc->trans_ring + i, (sc->trans_ring + i)->md, (sc->trans_ring + i)->buff); printf("Lance memory (may be on host(DMA) or card(SHMEM))\n"); printf("Init block = %x\n", sc->init_block); printf("\tmode = %b rlen:rdra = %x:%x tlen:tdra = %x:%x\n", sc->init_block->mode, INIT_MODE, sc->init_block->rlen, sc->init_block->rdra, sc->init_block->tlen, sc->init_block->tdra); printf("Receive descriptor ring\n"); for (i = 0; i < NDESC(sc->nrdre); i++) printf("\t%d buffer = 0x%x%x, BCNT = %d,\tMCNT = %u,\tflags = %b\n", i, ((sc->recv_ring + i)->md->md1 & HADR), (sc->recv_ring + i)->md->md0, -(short) (sc->recv_ring + i)->md->md2, (sc->recv_ring + i)->md->md3, (((sc->recv_ring + i)->md->md1 & ~HADR) >> 8), RECV_MD1); printf("Transmit descriptor ring\n"); for (i = 0; i < NDESC(sc->ntdre); i++) printf("\t%d buffer = 0x%x%x, BCNT = %d,\tflags = %b %b\n", i, ((sc->trans_ring + i)->md->md1 & HADR), (sc->trans_ring + i)->md->md0, -(short) (sc->trans_ring + i)->md->md2, ((sc->trans_ring + i)->md->md1 >> 8), TRANS_MD1, ((sc->trans_ring + i)->md->md3 >> 10), TRANS_MD3); printf("\nnext_to_send = %x\n", sc->next_to_send); printf("\n CSR0 = %b CSR1 = %x CSR2 = %x CSR3 = %x\n\n", read_csr(sc, CSR0), CSR0_FLAGS, read_csr(sc, CSR1), read_csr(sc, CSR2), read_csr(sc, CSR3)); /* Set RAP back to CSR0 */ outw(sc->rap, CSR0); } static void mbuf_dump_chain(struct mbuf * m) { #define MBUF_FLAGS \ "\20\1M_EXT\2M_PKTHDR\3M_EOR\4UNKNOWN\5M_BCAST\6M_MCAST" if (!m) log(LOG_DEBUG, "m == NULL\n"); do { log(LOG_DEBUG, "m = %x\n", m); log(LOG_DEBUG, "m_hdr.mh_next = %x\n", m->m_hdr.mh_next); log(LOG_DEBUG, "m_hdr.mh_nextpkt = %x\n", m->m_hdr.mh_nextpkt); log(LOG_DEBUG, "m_hdr.mh_len = %d\n", m->m_hdr.mh_len); log(LOG_DEBUG, "m_hdr.mh_data = %x\n", m->m_hdr.mh_data); log(LOG_DEBUG, "m_hdr.mh_type = %d\n", m->m_hdr.mh_type); log(LOG_DEBUG, "m_hdr.mh_flags = %b\n", m->m_hdr.mh_flags, MBUF_FLAGS); if (!(m->m_hdr.mh_flags & (M_PKTHDR | M_EXT))) log(LOG_DEBUG, "M_dat.M_databuf = %x\n", m->M_dat.M_databuf); else { if (m->m_hdr.mh_flags & M_PKTHDR) { log(LOG_DEBUG, "M_dat.MH.MH_pkthdr.len = %d\n", m->M_dat.MH.MH_pkthdr.len); log(LOG_DEBUG, "M_dat.MH.MH_pkthdr.rcvif = %x\n", m->M_dat.MH.MH_pkthdr.rcvif); if (!(m->m_hdr.mh_flags & M_EXT)) log(LOG_DEBUG, "M_dat.MH.MH_dat.MH_databuf = %x\n", m->M_dat.MH.MH_dat.MH_databuf); } if (m->m_hdr.mh_flags & M_EXT) { log(LOG_DEBUG, "M_dat.MH.MH_dat.MH_ext.ext_buff %x\n", m->M_dat.MH.MH_dat.MH_ext.ext_buf); log(LOG_DEBUG, "M_dat.MH.MH_dat.MH_ext.ext_free %x\n", m->M_dat.MH.MH_dat.MH_ext.ext_free); log(LOG_DEBUG, "M_dat.MH.MH_dat.MH_ext.ext_size %d\n", m->M_dat.MH.MH_dat.MH_ext.ext_size); } } } while (m = m->m_next); } #endif #endif diff --git a/sys/i386/isa/if_ed.c b/sys/i386/isa/if_ed.c index 6bed04eb51ae..95a48599def5 100644 --- a/sys/i386/isa/if_ed.c +++ b/sys/i386/isa/if_ed.c @@ -1,3441 +1,3441 @@ /* * Copyright (c) 1995, David Greenman * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: if_ed.c,v 1.109 1996/12/03 16:08:00 phk Exp $ + * $Id: if_ed.c,v 1.110 1996/12/10 07:29:39 davidg Exp $ */ /* * Device driver for National Semiconductor DS8390/WD83C690 based ethernet * adapters. By David Greenman, 29-April-1993 * * Currently supports the Western Digital/SMC 8003 and 8013 series, * the SMC Elite Ultra (8216), the 3Com 3c503, the NE1000 and NE2000, * and a variety of similar clones. * */ #include "ed.h" #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #ifdef IPX #include #include #endif #ifdef NS #include #include #endif #if NBPFILTER > 0 #include #include #endif #include #include #include #include #include /* * ed_softc: per line info and status */ struct ed_softc { struct arpcom arpcom; /* ethernet common */ char *type_str; /* pointer to type string */ u_char vendor; /* interface vendor */ u_char type; /* interface type code */ u_char gone; /* HW missing, presumed having a good time */ u_short asic_addr; /* ASIC I/O bus address */ u_short nic_addr; /* NIC (DS8390) I/O bus address */ /* * The following 'proto' variable is part of a work-around for 8013EBT asics * being write-only. It's sort of a prototype/shadow of the real thing. */ u_char wd_laar_proto; u_char cr_proto; u_char isa16bit; /* width of access to card 0=8 or 1=16 */ int is790; /* set by the probe code if the card is 790 * based */ /* * HP PC LAN PLUS card support. */ u_short hpp_options; /* flags controlling behaviour of the HP card */ u_short hpp_id; /* software revision and other fields */ caddr_t hpp_mem_start; /* Memory-mapped IO register address */ caddr_t mem_start; /* NIC memory start address */ caddr_t mem_end; /* NIC memory end address */ u_long mem_size; /* total NIC memory size */ caddr_t mem_ring; /* start of RX ring-buffer (in NIC mem) */ u_char mem_shared; /* NIC memory is shared with host */ u_char xmit_busy; /* transmitter is busy */ u_char txb_cnt; /* number of transmit buffers */ u_char txb_inuse; /* number of TX buffers currently in-use */ u_char txb_new; /* pointer to where new buffer will be added */ u_char txb_next_tx; /* pointer to next buffer ready to xmit */ u_short txb_len[8]; /* buffered xmit buffer lengths */ u_char tx_page_start; /* first page of TX buffer area */ u_char rec_page_start; /* first page of RX ring-buffer */ u_char rec_page_stop; /* last page of RX ring-buffer */ u_char next_packet; /* pointer to next unread RX packet */ struct ifmib_iso_8802_3 mibdata; /* stuff for network mgmt */ }; static struct ed_softc ed_softc[NED]; static int ed_attach __P((struct ed_softc *, int, int)); static int ed_attach_isa __P((struct isa_device *)); static void ed_init __P((void *)); static int ed_ioctl __P((struct ifnet *, int, caddr_t)); static int ed_probe __P((struct isa_device *)); static void ed_start __P((struct ifnet *)); static void ed_reset __P((struct ifnet *)); static void ed_watchdog __P((struct ifnet *)); static void ed_stop __P((struct ed_softc *)); static int ed_probe_generic8390 __P((struct ed_softc *)); static int ed_probe_WD80x3 __P((struct isa_device *)); static int ed_probe_3Com __P((struct isa_device *)); static int ed_probe_Novell __P((struct isa_device *)); static int ed_probe_Novell_generic __P((struct ed_softc *, int, int, int)); static int ed_probe_HP_pclanp __P((struct isa_device *)); #include "pci.h" #if NPCI > 0 void *ed_attach_NE2000_pci __P((int, int)); #endif #include "crd.h" #if NCRD > 0 static int ed_probe_pccard __P((struct isa_device *, u_char *)); #endif static void ds_getmcaf __P((struct ed_softc *, u_long *)); static void ed_get_packet(struct ed_softc *, char *, /* u_short */ int, int); static void ed_rint __P((struct ed_softc *)); static void ed_xmit __P((struct ed_softc *)); static char * ed_ring_copy __P((struct ed_softc *, char *, char *, /* u_short */ int)); static void ed_hpp_set_physical_link __P((struct ed_softc *)); static void ed_hpp_readmem __P((struct ed_softc *, int, unsigned char *, /* u_short */ int)); static u_short ed_hpp_write_mbufs __P((struct ed_softc *, struct mbuf *, int)); static void ed_pio_readmem __P((struct ed_softc *, int, unsigned char *, /* u_short */ int)); static void ed_pio_writemem __P((struct ed_softc *, char *, /* u_short */ int, /* u_short */ int)); static u_short ed_pio_write_mbufs __P((struct ed_softc *, struct mbuf *, int)); void edintr_sc __P((struct ed_softc *)); static void ed_setrcr(struct ed_softc *); static u_long ds_crc(u_char *ep); #if NCRD > 0 #include #include #include #include /* * PC-Card (PCMCIA) specific code. */ static int card_intr(struct pccard_dev *); /* Interrupt handler */ static void edunload(struct pccard_dev *); /* Disable driver */ static void edsuspend(struct pccard_dev *); /* Suspend driver */ static int edinit(struct pccard_dev *, int); /* init device */ static struct pccard_drv ed_info = { "ed", card_intr, edunload, edsuspend, edinit, 0, /* Attributes - presently unused */ &net_imask /* Interrupt mask for device */ /* XXX - Should this also include net_imask? */ }; /* * Called when a power down is requested. Shuts down the * device and configures the device as unavailable (but * still loaded...). A resume is done by calling * edinit with first=0. This is called when the user suspends * the system, or the APM code suspends the system. */ static void edsuspend(struct pccard_dev *dp) { struct ed_softc *sc = &ed_softc[dp->isahd.id_unit]; /* * Some 'ed' cards will generate a interrupt as they go away, * and by the time the interrupt handler gets to the card, * the interrupt can't be cleared. * By setting gone here, we tell the handler to ignore the * interrupt when it happens. */ sc->gone = 1; /* avoid spinning endlessly in interrupt handler */ printf("ed%d: suspending\n", dp->isahd.id_unit); } /* * Initialize the device - called from Slot manager. * If first is set, then check for the device's existence * before initializing it. Once initialized, the device table may * be set up. */ static int edinit(struct pccard_dev *dp, int first) { struct ed_softc *sc = &ed_softc[dp->isahd.id_unit]; /* validate unit number. */ if (first) { if (dp->isahd.id_unit >= NED) return(ENODEV); /* * Probe the device. If a value is returned, the * device was found at the location. */ sc->gone = 0; if (ed_probe_pccard(&dp->isahd,dp->misc)==0) return(ENXIO); if (ed_attach_isa(&dp->isahd)==0) return(ENXIO); } else { sc->gone = 0; /* reenable after a suspend */ } /* * XXX TODO: * If it was initialized before, the device structure * should also be initialized. We should * reset (and possibly restart) the hardware, but * I am not sure of the best way to do this... */ return(0); } /* * edunload - unload the driver and clear the table. * XXX TODO: * This is usually called when the card is ejected, but * can be caused by a modunload of a controller driver. * The idea is to reset the driver's view of the device * and ensure that any driver entry points such as * read and write do not hang. */ static void edunload(struct pccard_dev *dp) { struct ed_softc *sc = &ed_softc[dp->isahd.id_unit]; struct ifnet *ifp = &sc->arpcom.ac_if; if (sc->gone) { printf("ed%d: already unloaded\n", dp->isahd.id_unit); return; } ifp->if_flags &= ~IFF_RUNNING; if_down(ifp); sc->gone = 1; printf("ed%d: unload\n", dp->isahd.id_unit); } /* * card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int card_intr(struct pccard_dev *dp) { edintr_sc(&ed_softc[dp->isahd.id_unit]); return(1); } #endif /* NCRD > 0 */ struct isa_driver eddriver = { ed_probe, ed_attach_isa, "ed", 1 /* We are ultra sensitive */ }; /* * Interrupt conversion table for WD/SMC ASIC/83C584 * (IRQ* are defined in icu.h) */ static unsigned short ed_intr_mask[] = { IRQ9, IRQ3, IRQ5, IRQ7, IRQ10, IRQ11, IRQ15, IRQ4 }; /* * Interrupt conversion table for 83C790 */ static unsigned short ed_790_intr_mask[] = { 0, IRQ9, IRQ3, IRQ5, IRQ7, IRQ10, IRQ11, IRQ15 }; /* * Interrupt conversion table for the HP PC LAN+ */ static unsigned short ed_hpp_intr_mask[] = { 0, /* 0 */ 0, /* 1 */ 0, /* 2 */ IRQ3, /* 3 */ IRQ4, /* 4 */ IRQ5, /* 5 */ IRQ6, /* 6 */ IRQ7, /* 7 */ 0, /* 8 */ IRQ9, /* 9 */ IRQ10, /* 10 */ IRQ11, /* 11 */ IRQ12, /* 12 */ 0, /* 13 */ 0, /* 14 */ IRQ15 /* 15 */ }; /* * Determine if the device is present * * on entry: * a pointer to an isa_device struct * on exit: * NULL if device not found * or # of i/o addresses used (if found) */ static int ed_probe(isa_dev) struct isa_device *isa_dev; { int nports; #if NCRD > 0 /* * If PC-Card probe required, then register driver with * slot manager. */ pccard_add_driver(&ed_info); #endif nports = ed_probe_WD80x3(isa_dev); if (nports) return (nports); nports = ed_probe_3Com(isa_dev); if (nports) return (nports); nports = ed_probe_Novell(isa_dev); if (nports) return (nports); nports = ed_probe_HP_pclanp(isa_dev); if (nports) return (nports); return (0); } /* * Generic probe routine for testing for the existance of a DS8390. * Must be called after the NIC has just been reset. This routine * works by looking at certain register values that are guaranteed * to be initialized a certain way after power-up or reset. Seems * not to currently work on the 83C690. * * Specifically: * * Register reset bits set bits * Command Register (CR) TXP, STA RD2, STP * Interrupt Status (ISR) RST * Interrupt Mask (IMR) All bits * Data Control (DCR) LAS * Transmit Config. (TCR) LB1, LB0 * * We only look at the CR and ISR registers, however, because looking at * the others would require changing register pages (which would be * intrusive if this isn't an 8390). * * Return 1 if 8390 was found, 0 if not. */ static int ed_probe_generic8390(sc) struct ed_softc *sc; { if ((inb(sc->nic_addr + ED_P0_CR) & (ED_CR_RD2 | ED_CR_TXP | ED_CR_STA | ED_CR_STP)) != (ED_CR_RD2 | ED_CR_STP)) return (0); if ((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST) != ED_ISR_RST) return (0); return (1); } /* * Probe and vendor-specific initialization routine for SMC/WD80x3 boards */ static int ed_probe_WD80x3(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; int i; u_int memsize; u_char iptr, isa16bit, sum; sc->asic_addr = isa_dev->id_iobase; sc->nic_addr = sc->asic_addr + ED_WD_NIC_OFFSET; sc->is790 = 0; #ifdef TOSH_ETHER outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_POW); DELAY(10000); #endif /* * Attempt to do a checksum over the station address PROM. If it * fails, it's probably not a SMC/WD board. There is a problem with * this, though: some clone WD boards don't pass the checksum test. * Danpex boards for one. */ for (sum = 0, i = 0; i < 8; ++i) sum += inb(sc->asic_addr + ED_WD_PROM + i); if (sum != ED_WD_ROM_CHECKSUM_TOTAL) { /* * Checksum is invalid. This often happens with cheap WD8003E * clones. In this case, the checksum byte (the eighth byte) * seems to always be zero. */ if (inb(sc->asic_addr + ED_WD_CARD_ID) != ED_TYPE_WD8003E || inb(sc->asic_addr + ED_WD_PROM + 7) != 0) return (0); } /* reset card to force it into a known state. */ #ifdef TOSH_ETHER outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_RST | ED_WD_MSR_POW); #else outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_RST); #endif DELAY(100); outb(sc->asic_addr + ED_WD_MSR, inb(sc->asic_addr + ED_WD_MSR) & ~ED_WD_MSR_RST); /* wait in the case this card is reading it's EEROM */ DELAY(5000); sc->vendor = ED_VENDOR_WD_SMC; sc->type = inb(sc->asic_addr + ED_WD_CARD_ID); /* * Set initial values for width/size. */ memsize = 8192; isa16bit = 0; switch (sc->type) { case ED_TYPE_WD8003S: sc->type_str = "WD8003S"; break; case ED_TYPE_WD8003E: sc->type_str = "WD8003E"; break; case ED_TYPE_WD8003EB: sc->type_str = "WD8003EB"; break; case ED_TYPE_WD8003W: sc->type_str = "WD8003W"; break; case ED_TYPE_WD8013EBT: sc->type_str = "WD8013EBT"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013W: sc->type_str = "WD8013W"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013EP: /* also WD8003EP */ if (inb(sc->asic_addr + ED_WD_ICR) & ED_WD_ICR_16BIT) { isa16bit = 1; memsize = 16384; sc->type_str = "WD8013EP"; } else { sc->type_str = "WD8003EP"; } break; case ED_TYPE_WD8013WC: sc->type_str = "WD8013WC"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013EBP: sc->type_str = "WD8013EBP"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013EPC: sc->type_str = "WD8013EPC"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_SMC8216C: /* 8216 has 16K shared mem -- 8416 has 8K */ case ED_TYPE_SMC8216T: if (sc->type == ED_TYPE_SMC8216C) { sc->type_str = "SMC8216/SMC8216C"; } else { sc->type_str = "SMC8216T"; } outb(sc->asic_addr + ED_WD790_HWR, inb(sc->asic_addr + ED_WD790_HWR) | ED_WD790_HWR_SWH); switch (inb(sc->asic_addr + ED_WD790_RAR) & ED_WD790_RAR_SZ64) { case ED_WD790_RAR_SZ64: memsize = 65536; break; case ED_WD790_RAR_SZ32: memsize = 32768; break; case ED_WD790_RAR_SZ16: memsize = 16384; break; case ED_WD790_RAR_SZ8: /* 8216 has 16K shared mem -- 8416 has 8K */ if (sc->type == ED_TYPE_SMC8216C) { sc->type_str = "SMC8416C/SMC8416BT"; } else { sc->type_str = "SMC8416T"; } memsize = 8192; break; } outb(sc->asic_addr + ED_WD790_HWR, inb(sc->asic_addr + ED_WD790_HWR) & ~ED_WD790_HWR_SWH); isa16bit = 1; sc->is790 = 1; break; #ifdef TOSH_ETHER case ED_TYPE_TOSHIBA1: sc->type_str = "Toshiba1"; memsize = 32768; isa16bit = 1; break; case ED_TYPE_TOSHIBA4: sc->type_str = "Toshiba4"; memsize = 32768; isa16bit = 1; break; #endif default: sc->type_str = ""; break; } /* * Make some adjustments to initial values depending on what is found * in the ICR. */ if (isa16bit && (sc->type != ED_TYPE_WD8013EBT) #ifdef TOSH_ETHER && (sc->type != ED_TYPE_TOSHIBA1) && (sc->type != ED_TYPE_TOSHIBA4) #endif && ((inb(sc->asic_addr + ED_WD_ICR) & ED_WD_ICR_16BIT) == 0)) { isa16bit = 0; memsize = 8192; } #if ED_DEBUG printf("type = %x type_str=%s isa16bit=%d memsize=%d id_msize=%d\n", sc->type, sc->type_str, isa16bit, memsize, isa_dev->id_msize); for (i = 0; i < 8; i++) printf("%x -> %x\n", i, inb(sc->asic_addr + i)); #endif /* * Allow the user to override the autoconfiguration */ if (isa_dev->id_msize) memsize = isa_dev->id_msize; /* * (note that if the user specifies both of the following flags that * '8bit' mode intentionally has precedence) */ if (isa_dev->id_flags & ED_FLAGS_FORCE_16BIT_MODE) isa16bit = 1; if (isa_dev->id_flags & ED_FLAGS_FORCE_8BIT_MODE) isa16bit = 0; /* * If possible, get the assigned interrupt number from the card and * use it. */ if ((sc->type & ED_WD_SOFTCONFIG) && (!sc->is790)) { /* * Assemble together the encoded interrupt number. */ iptr = (inb(isa_dev->id_iobase + ED_WD_ICR) & ED_WD_ICR_IR2) | ((inb(isa_dev->id_iobase + ED_WD_IRR) & (ED_WD_IRR_IR0 | ED_WD_IRR_IR1)) >> 5); /* * If no interrupt specified (or "?"), use what the board tells us. */ if (isa_dev->id_irq <= 0) isa_dev->id_irq = ed_intr_mask[iptr]; /* * Enable the interrupt. */ outb(isa_dev->id_iobase + ED_WD_IRR, inb(isa_dev->id_iobase + ED_WD_IRR) | ED_WD_IRR_IEN); } if (sc->is790) { outb(isa_dev->id_iobase + ED_WD790_HWR, inb(isa_dev->id_iobase + ED_WD790_HWR) | ED_WD790_HWR_SWH); iptr = (((inb(isa_dev->id_iobase + ED_WD790_GCR) & ED_WD790_GCR_IR2) >> 4) | (inb(isa_dev->id_iobase + ED_WD790_GCR) & (ED_WD790_GCR_IR1 | ED_WD790_GCR_IR0)) >> 2); outb(isa_dev->id_iobase + ED_WD790_HWR, inb(isa_dev->id_iobase + ED_WD790_HWR) & ~ED_WD790_HWR_SWH); /* * If no interrupt specified (or "?"), use what the board tells us. */ if (isa_dev->id_irq <= 0) isa_dev->id_irq = ed_790_intr_mask[iptr]; /* * Enable interrupts. */ outb(isa_dev->id_iobase + ED_WD790_ICR, inb(isa_dev->id_iobase + ED_WD790_ICR) | ED_WD790_ICR_EIL); } if (isa_dev->id_irq <= 0) { printf("ed%d: %s cards don't support auto-detected/assigned interrupts.\n", isa_dev->id_unit, sc->type_str); return (0); } sc->isa16bit = isa16bit; sc->mem_shared = 1; isa_dev->id_msize = memsize; sc->mem_start = (caddr_t) isa_dev->id_maddr; /* * allocate one xmit buffer if < 16k, two buffers otherwise */ if ((memsize < 16384) || (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING)) { sc->txb_cnt = 1; } else { sc->txb_cnt = 2; } sc->tx_page_start = ED_WD_PAGE_OFFSET; sc->rec_page_start = ED_WD_PAGE_OFFSET + ED_TXBUF_SIZE * sc->txb_cnt; sc->rec_page_stop = ED_WD_PAGE_OFFSET + memsize / ED_PAGE_SIZE; sc->mem_ring = sc->mem_start + (ED_PAGE_SIZE * sc->rec_page_start); sc->mem_size = memsize; sc->mem_end = sc->mem_start + memsize; /* * Get station address from on-board ROM */ for (i = 0; i < ETHER_ADDR_LEN; ++i) sc->arpcom.ac_enaddr[i] = inb(sc->asic_addr + ED_WD_PROM + i); /* * Set upper address bits and 8/16 bit access to shared memory */ if (isa16bit) { if (sc->is790) { sc->wd_laar_proto = inb(sc->asic_addr + ED_WD_LAAR); outb(sc->asic_addr + ED_WD_LAAR, ED_WD_LAAR_M16EN); } else { outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto = ED_WD_LAAR_L16EN | ED_WD_LAAR_M16EN | ((kvtop(sc->mem_start) >> 19) & ED_WD_LAAR_ADDRHI))); } } else { if (((sc->type & ED_WD_SOFTCONFIG) || #ifdef TOSH_ETHER (sc->type == ED_TYPE_TOSHIBA1) || (sc->type == ED_TYPE_TOSHIBA4) || #endif (sc->type == ED_TYPE_WD8013EBT)) && (!sc->is790)) { outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto = ((kvtop(sc->mem_start) >> 19) & ED_WD_LAAR_ADDRHI))); } } /* * Set address and enable interface shared memory. */ if (!sc->is790) { #ifdef TOSH_ETHER outb(sc->asic_addr + ED_WD_MSR + 1, ((kvtop(sc->mem_start) >> 8) & 0xe0) | 4); outb(sc->asic_addr + ED_WD_MSR + 2, ((kvtop(sc->mem_start) >> 16) & 0x0f)); outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB | ED_WD_MSR_POW); #else outb(sc->asic_addr + ED_WD_MSR, ((kvtop(sc->mem_start) >> 13) & ED_WD_MSR_ADDR) | ED_WD_MSR_MENB); #endif sc->cr_proto = ED_CR_RD2; } else { outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB); outb(sc->asic_addr + ED_WD790_HWR, (inb(sc->asic_addr + ED_WD790_HWR) | ED_WD790_HWR_SWH)); outb(sc->asic_addr + ED_WD790_RAR, ((kvtop(sc->mem_start) >> 13) & 0x0f) | ((kvtop(sc->mem_start) >> 11) & 0x40) | (inb(sc->asic_addr + ED_WD790_RAR) & 0xb0)); outb(sc->asic_addr + ED_WD790_HWR, (inb(sc->asic_addr + ED_WD790_HWR) & ~ED_WD790_HWR_SWH)); sc->cr_proto = 0; } #if 0 printf("starting memory performance test at 0x%x, size %d...\n", sc->mem_start, memsize*16384); for (i = 0; i < 16384; i++) bzero(sc->mem_start, memsize); printf("***DONE***\n"); #endif /* * Now zero memory and verify that it is clear */ bzero(sc->mem_start, memsize); for (i = 0; i < memsize; ++i) { if (sc->mem_start[i]) { printf("ed%d: failed to clear shared memory at %lx - check configuration\n", isa_dev->id_unit, kvtop(sc->mem_start + i)); /* * Disable 16 bit access to shared memory */ if (isa16bit) { if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto &= ~ED_WD_LAAR_M16EN)); } return (0); } } /* * Disable 16bit access to shared memory - we leave it * disabled so that 1) machines reboot properly when the board * is set 16 bit mode and there are conflicting 8bit * devices/ROMS in the same 128k address space as this boards * shared memory. and 2) so that other 8 bit devices with * shared memory can be used in this 128k region, too. */ if (isa16bit) { if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto &= ~ED_WD_LAAR_M16EN)); } return (ED_WD_IO_PORTS); } /* * Probe and vendor-specific initialization routine for 3Com 3c503 boards */ static int ed_probe_3Com(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; int i; u_int memsize; u_char isa16bit; sc->asic_addr = isa_dev->id_iobase + ED_3COM_ASIC_OFFSET; sc->nic_addr = isa_dev->id_iobase + ED_3COM_NIC_OFFSET; /* * Verify that the kernel configured I/O address matches the board * configured address */ switch (inb(sc->asic_addr + ED_3COM_BCFR)) { case ED_3COM_BCFR_300: if (isa_dev->id_iobase != 0x300) return (0); break; case ED_3COM_BCFR_310: if (isa_dev->id_iobase != 0x310) return (0); break; case ED_3COM_BCFR_330: if (isa_dev->id_iobase != 0x330) return (0); break; case ED_3COM_BCFR_350: if (isa_dev->id_iobase != 0x350) return (0); break; case ED_3COM_BCFR_250: if (isa_dev->id_iobase != 0x250) return (0); break; case ED_3COM_BCFR_280: if (isa_dev->id_iobase != 0x280) return (0); break; case ED_3COM_BCFR_2A0: if (isa_dev->id_iobase != 0x2a0) return (0); break; case ED_3COM_BCFR_2E0: if (isa_dev->id_iobase != 0x2e0) return (0); break; default: return (0); } /* * Verify that the kernel shared memory address matches the board * configured address. */ switch (inb(sc->asic_addr + ED_3COM_PCFR)) { case ED_3COM_PCFR_DC000: if (kvtop(isa_dev->id_maddr) != 0xdc000) return (0); break; case ED_3COM_PCFR_D8000: if (kvtop(isa_dev->id_maddr) != 0xd8000) return (0); break; case ED_3COM_PCFR_CC000: if (kvtop(isa_dev->id_maddr) != 0xcc000) return (0); break; case ED_3COM_PCFR_C8000: if (kvtop(isa_dev->id_maddr) != 0xc8000) return (0); break; default: return (0); } /* * Reset NIC and ASIC. Enable on-board transceiver throughout reset * sequence because it'll lock up if the cable isn't connected if we * don't. */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_RST | ED_3COM_CR_XSEL); /* * Wait for a while, then un-reset it */ DELAY(50); /* * The 3Com ASIC defaults to rather strange settings for the CR after * a reset - it's important to set it again after the following outb * (this is done when we map the PROM below). */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); /* * Wait a bit for the NIC to recover from the reset */ DELAY(5000); sc->vendor = ED_VENDOR_3COM; sc->type_str = "3c503"; sc->mem_shared = 1; sc->cr_proto = ED_CR_RD2; /* * Hmmm...a 16bit 3Com board has 16k of memory, but only an 8k window * to it. */ memsize = 8192; /* * Get station address from on-board ROM */ /* * First, map ethernet address PROM over the top of where the NIC * registers normally appear. */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_EALO | ED_3COM_CR_XSEL); for (i = 0; i < ETHER_ADDR_LEN; ++i) sc->arpcom.ac_enaddr[i] = inb(sc->nic_addr + i); /* * Unmap PROM - select NIC registers. The proper setting of the * tranceiver is set in ed_init so that the attach code is given a * chance to set the default based on a compile-time config option */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); /* * Determine if this is an 8bit or 16bit board */ /* * select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); /* * Attempt to clear WTS bit. If it doesn't clear, then this is a 16bit * board. */ outb(sc->nic_addr + ED_P0_DCR, 0); /* * select page 2 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_2 | ED_CR_RD2 | ED_CR_STP); /* * The 3c503 forces the WTS bit to a one if this is a 16bit board */ if (inb(sc->nic_addr + ED_P2_DCR) & ED_DCR_WTS) isa16bit = 1; else isa16bit = 0; /* * select page 0 registers */ outb(sc->nic_addr + ED_P2_CR, ED_CR_RD2 | ED_CR_STP); sc->mem_start = (caddr_t) isa_dev->id_maddr; sc->mem_size = memsize; sc->mem_end = sc->mem_start + memsize; /* * We have an entire 8k window to put the transmit buffers on the * 16bit boards. But since the 16bit 3c503's shared memory is only * fast enough to overlap the loading of one full-size packet, trying * to load more than 2 buffers can actually leave the transmitter idle * during the load. So 2 seems the best value. (Although a mix of * variable-sized packets might change this assumption. Nonetheless, * we optimize for linear transfers of same-size packets.) */ if (isa16bit) { if (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING) sc->txb_cnt = 1; else sc->txb_cnt = 2; sc->tx_page_start = ED_3COM_TX_PAGE_OFFSET_16BIT; sc->rec_page_start = ED_3COM_RX_PAGE_OFFSET_16BIT; sc->rec_page_stop = memsize / ED_PAGE_SIZE + ED_3COM_RX_PAGE_OFFSET_16BIT; sc->mem_ring = sc->mem_start; } else { sc->txb_cnt = 1; sc->tx_page_start = ED_3COM_TX_PAGE_OFFSET_8BIT; sc->rec_page_start = ED_TXBUF_SIZE + ED_3COM_TX_PAGE_OFFSET_8BIT; sc->rec_page_stop = memsize / ED_PAGE_SIZE + ED_3COM_TX_PAGE_OFFSET_8BIT; sc->mem_ring = sc->mem_start + (ED_PAGE_SIZE * ED_TXBUF_SIZE); } sc->isa16bit = isa16bit; /* * Initialize GA page start/stop registers. Probably only needed if * doing DMA, but what the hell. */ outb(sc->asic_addr + ED_3COM_PSTR, sc->rec_page_start); outb(sc->asic_addr + ED_3COM_PSPR, sc->rec_page_stop); /* * Set IRQ. 3c503 only allows a choice of irq 2-5. */ switch (isa_dev->id_irq) { case IRQ2: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ2); break; case IRQ3: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ3); break; case IRQ4: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ4); break; case IRQ5: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ5); break; default: printf("ed%d: Invalid irq configuration (%d) must be 3-5,9 for 3c503\n", isa_dev->id_unit, ffs(isa_dev->id_irq) - 1); return (0); } /* * Initialize GA configuration register. Set bank and enable shared * mem. */ outb(sc->asic_addr + ED_3COM_GACFR, ED_3COM_GACFR_RSEL | ED_3COM_GACFR_MBS0); /* * Initialize "Vector Pointer" registers. These gawd-awful things are * compared to 20 bits of the address on ISA, and if they match, the * shared memory is disabled. We set them to 0xffff0...allegedly the * reset vector. */ outb(sc->asic_addr + ED_3COM_VPTR2, 0xff); outb(sc->asic_addr + ED_3COM_VPTR1, 0xff); outb(sc->asic_addr + ED_3COM_VPTR0, 0x00); /* * Zero memory and verify that it is clear */ bzero(sc->mem_start, memsize); for (i = 0; i < memsize; ++i) if (sc->mem_start[i]) { printf("ed%d: failed to clear shared memory at %lx - check configuration\n", isa_dev->id_unit, kvtop(sc->mem_start + i)); return (0); } isa_dev->id_msize = memsize; return (ED_3COM_IO_PORTS); } /* * Probe and vendor-specific initialization routine for NE1000/2000 boards */ static int ed_probe_Novell_generic(sc, port, unit, flags) struct ed_softc *sc; int port; int unit; int flags; { u_int memsize, n; u_char romdata[16], tmp; static char test_pattern[32] = "THIS is A memory TEST pattern"; char test_buffer[32]; sc->asic_addr = port + ED_NOVELL_ASIC_OFFSET; sc->nic_addr = port + ED_NOVELL_NIC_OFFSET; /* XXX - do Novell-specific probe here */ /* Reset the board */ #ifdef GWETHER outb(sc->asic_addr + ED_NOVELL_RESET, 0); DELAY(200); #endif /* GWETHER */ tmp = inb(sc->asic_addr + ED_NOVELL_RESET); /* * I don't know if this is necessary; probably cruft leftover from * Clarkson packet driver code. Doesn't do a thing on the boards I've * tested. -DG [note that a outb(0x84, 0) seems to work here, and is * non-invasive...but some boards don't seem to reset and I don't have * complete documentation on what the 'right' thing to do is...so we * do the invasive thing for now. Yuck.] */ outb(sc->asic_addr + ED_NOVELL_RESET, tmp); DELAY(5000); /* * This is needed because some NE clones apparently don't reset the * NIC properly (or the NIC chip doesn't reset fully on power-up) XXX * - this makes the probe invasive! ...Done against my better * judgement. -DLG */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); DELAY(5000); /* Make sure that we really have an 8390 based board */ if (!ed_probe_generic8390(sc)) return (0); sc->vendor = ED_VENDOR_NOVELL; sc->mem_shared = 0; sc->cr_proto = ED_CR_RD2; /* * Test the ability to read and write to the NIC memory. This has the * side affect of determining if this is an NE1000 or an NE2000. */ /* * This prevents packets from being stored in the NIC memory when the * readmem routine turns on the start bit in the CR. */ outb(sc->nic_addr + ED_P0_RCR, ED_RCR_MON); /* Temporarily initialize DCR for byte operations */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_LS); outb(sc->nic_addr + ED_P0_PSTART, 8192 / ED_PAGE_SIZE); outb(sc->nic_addr + ED_P0_PSTOP, 16384 / ED_PAGE_SIZE); sc->isa16bit = 0; /* * Write a test pattern in byte mode. If this fails, then there * probably isn't any memory at 8k - which likely means that the board * is an NE2000. */ ed_pio_writemem(sc, test_pattern, 8192, sizeof(test_pattern)); ed_pio_readmem(sc, 8192, test_buffer, sizeof(test_pattern)); if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) { /* not an NE1000 - try NE2000 */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_WTS | ED_DCR_FT1 | ED_DCR_LS); outb(sc->nic_addr + ED_P0_PSTART, 16384 / ED_PAGE_SIZE); outb(sc->nic_addr + ED_P0_PSTOP, 32768 / ED_PAGE_SIZE); sc->isa16bit = 1; /* * Write a test pattern in word mode. If this also fails, then * we don't know what this board is. */ ed_pio_writemem(sc, test_pattern, 16384, sizeof(test_pattern)); ed_pio_readmem(sc, 16384, test_buffer, sizeof(test_pattern)); if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) return (0); /* not an NE2000 either */ sc->type = ED_TYPE_NE2000; sc->type_str = "NE2000"; } else { sc->type = ED_TYPE_NE1000; sc->type_str = "NE1000"; } /* 8k of memory plus an additional 8k if 16bit */ memsize = 8192 + sc->isa16bit * 8192; #if 0 /* probably not useful - NE boards only come two ways */ /* allow kernel config file overrides */ if (isa_dev->id_msize) memsize = isa_dev->id_msize; #endif sc->mem_size = memsize; /* NIC memory doesn't start at zero on an NE board */ /* The start address is tied to the bus width */ sc->mem_start = (char *) 8192 + sc->isa16bit * 8192; sc->mem_end = sc->mem_start + memsize; sc->tx_page_start = memsize / ED_PAGE_SIZE; #ifdef GWETHER { int x, i, mstart = 0, msize = 0; char pbuf0[ED_PAGE_SIZE], pbuf[ED_PAGE_SIZE], tbuf[ED_PAGE_SIZE]; for (i = 0; i < ED_PAGE_SIZE; i++) pbuf0[i] = 0; /* Clear all the memory. */ for (x = 1; x < 256; x++) ed_pio_writemem(sc, pbuf0, x * 256, ED_PAGE_SIZE); /* Search for the start of RAM. */ for (x = 1; x < 256; x++) { ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf0, tbuf, ED_PAGE_SIZE) == 0) { for (i = 0; i < ED_PAGE_SIZE; i++) pbuf[i] = 255 - x; ed_pio_writemem(sc, pbuf, x * 256, ED_PAGE_SIZE); ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf, tbuf, ED_PAGE_SIZE) == 0) { mstart = x * ED_PAGE_SIZE; msize = ED_PAGE_SIZE; break; } } } if (mstart == 0) { printf("ed%d: Cannot find start of RAM.\n", unit); return 0; } /* Search for the start of RAM. */ for (x = (mstart / ED_PAGE_SIZE) + 1; x < 256; x++) { ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf0, tbuf, ED_PAGE_SIZE) == 0) { for (i = 0; i < ED_PAGE_SIZE; i++) pbuf[i] = 255 - x; ed_pio_writemem(sc, pbuf, x * 256, ED_PAGE_SIZE); ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf, tbuf, ED_PAGE_SIZE) == 0) msize += ED_PAGE_SIZE; else { break; } } else { break; } } if (msize == 0) { printf("ed%d: Cannot find any RAM, start : %d, x = %d.\n", unit, mstart, x); return 0; } printf("ed%d: RAM start at %d, size : %d.\n", unit, mstart, msize); sc->mem_size = msize; sc->mem_start = (char *) mstart; sc->mem_end = (char *) (msize + mstart); sc->tx_page_start = mstart / ED_PAGE_SIZE; } #endif /* GWETHER */ /* * Use one xmit buffer if < 16k, two buffers otherwise (if not told * otherwise). */ if ((memsize < 16384) || (flags & ED_FLAGS_NO_MULTI_BUFFERING)) sc->txb_cnt = 1; else sc->txb_cnt = 2; sc->rec_page_start = sc->tx_page_start + sc->txb_cnt * ED_TXBUF_SIZE; sc->rec_page_stop = sc->tx_page_start + memsize / ED_PAGE_SIZE; sc->mem_ring = sc->mem_start + sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE; ed_pio_readmem(sc, 0, romdata, 16); for (n = 0; n < ETHER_ADDR_LEN; n++) sc->arpcom.ac_enaddr[n] = romdata[n * (sc->isa16bit + 1)]; #ifdef GWETHER if (sc->arpcom.ac_enaddr[2] == 0x86) { sc->type_str = "Gateway AT"; } #endif /* GWETHER */ /* clear any pending interrupts that might have occurred above */ outb(sc->nic_addr + ED_P0_ISR, 0xff); return (ED_NOVELL_IO_PORTS); } static int ed_probe_Novell(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; isa_dev->id_maddr = 0; return ed_probe_Novell_generic(sc, isa_dev->id_iobase, isa_dev->id_unit, isa_dev->id_flags); } #if NCRD > 0 /* * Probe framework for pccards. Replicates the standard framework, * minus the pccard driver registration and ignores the ether address * supplied (from the CIS), relying on the probe to find it instead. */ static int ed_probe_pccard(isa_dev, ether) struct isa_device *isa_dev; u_char *ether; { int nports; nports = ed_probe_WD80x3(isa_dev); if (nports) return (nports); nports = ed_probe_Novell(isa_dev); if (nports) return (nports); return (0); } #endif /* NCRD > 0 */ #define ED_HPP_TEST_SIZE 16 /* * Probe and vendor specific initialization for the HP PC Lan+ Cards. * (HP Part nos: 27247B and 27252A). * * The card has an asic wrapper around a DS8390 core. The asic handles * host accesses and offers both standard register IO and memory mapped * IO. Memory mapped I/O allows better performance at the expense of greater * chance of an incompatibility with existing ISA cards. * * The card has a few caveats: it isn't tolerant of byte wide accesses, only * short (16 bit) or word (32 bit) accesses are allowed. Some card revisions * don't allow 32 bit accesses; these are indicated by a bit in the software * ID register (see if_edreg.h). * * Other caveats are: we should read the MAC address only when the card * is inactive. * * For more information; please consult the CRYNWR packet driver. * * The AUI port is turned on using the "link2" option on the ifconfig * command line. */ static int ed_probe_HP_pclanp(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; int n; /* temp var */ int memsize; /* mem on board */ u_char checksum; /* checksum of board address */ u_char irq; /* board configured IRQ */ char test_pattern[ED_HPP_TEST_SIZE]; /* read/write areas for */ char test_buffer[ED_HPP_TEST_SIZE]; /* probing card */ /* Fill in basic information */ sc->asic_addr = isa_dev->id_iobase + ED_HPP_ASIC_OFFSET; sc->nic_addr = isa_dev->id_iobase + ED_HPP_NIC_OFFSET; sc->is790 = 0; sc->isa16bit = 0; /* the 8390 core needs to be in byte mode */ /* * Look for the HP PCLAN+ signature: "0x50,0x48,0x00,0x53" */ if ((inb(sc->asic_addr + ED_HPP_ID) != 0x50) || (inb(sc->asic_addr + ED_HPP_ID + 1) != 0x48) || ((inb(sc->asic_addr + ED_HPP_ID + 2) & 0xF0) != 0) || (inb(sc->asic_addr + ED_HPP_ID + 3) != 0x53)) return 0; /* * Read the MAC address and verify checksum on the address. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_MAC); for (n = 0, checksum = 0; n < ETHER_ADDR_LEN; n++) checksum += (sc->arpcom.ac_enaddr[n] = inb(sc->asic_addr + ED_HPP_MAC_ADDR + n)); checksum += inb(sc->asic_addr + ED_HPP_MAC_ADDR + ETHER_ADDR_LEN); if (checksum != 0xFF) return 0; /* * Verify that the software model number is 0. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_ID); if (((sc->hpp_id = inw(sc->asic_addr + ED_HPP_PAGE_4)) & ED_HPP_ID_SOFT_MODEL_MASK) != 0x0000) return 0; /* * Read in and save the current options configured on card. */ sc->hpp_options = inw(sc->asic_addr + ED_HPP_OPTION); sc->hpp_options |= (ED_HPP_OPTION_NIC_RESET | ED_HPP_OPTION_CHIP_RESET | ED_HPP_OPTION_ENABLE_IRQ); /* * Reset the chip. This requires writing to the option register * so take care to preserve the other bits. */ outw(sc->asic_addr + ED_HPP_OPTION, (sc->hpp_options & ~(ED_HPP_OPTION_NIC_RESET | ED_HPP_OPTION_CHIP_RESET))); DELAY(5000); /* wait for chip reset to complete */ outw(sc->asic_addr + ED_HPP_OPTION, (sc->hpp_options | (ED_HPP_OPTION_NIC_RESET | ED_HPP_OPTION_CHIP_RESET | ED_HPP_OPTION_ENABLE_IRQ))); DELAY(5000); if (!(inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST)) return 0; /* reset did not complete */ /* * Read out configuration information. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_HW); irq = inb(sc->asic_addr + ED_HPP_HW_IRQ); /* * Check for impossible IRQ. */ if (irq >= (sizeof(ed_hpp_intr_mask) / sizeof(ed_hpp_intr_mask[0]))) return 0; /* * If the kernel IRQ was specified with a '?' use the cards idea * of the IRQ. If the kernel IRQ was explicitly specified, it * should match that of the hardware. */ if (isa_dev->id_irq <= 0) isa_dev->id_irq = ed_hpp_intr_mask[irq]; else if (isa_dev->id_irq != ed_hpp_intr_mask[irq]) return 0; /* * Fill in softconfig info. */ sc->vendor = ED_VENDOR_HP; sc->type = ED_TYPE_HP_PCLANPLUS; sc->type_str = "HP-PCLAN+"; sc->mem_shared = 0; /* we DON'T have dual ported RAM */ sc->mem_start = 0; /* we use offsets inside the card RAM */ sc->hpp_mem_start = NULL;/* no memory mapped I/O by default */ /* * Check if memory mapping of the I/O registers possible. */ if (sc->hpp_options & ED_HPP_OPTION_MEM_ENABLE) { u_long mem_addr; /* * determine the memory address from the board. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_HW); mem_addr = (inw(sc->asic_addr + ED_HPP_HW_MEM_MAP) << 8); /* * Check that the kernel specified start of memory and * hardware's idea of it match. */ if (mem_addr != kvtop(isa_dev->id_maddr)) return 0; sc->hpp_mem_start = isa_dev->id_maddr; } /* * The board has 32KB of memory. Is there a way to determine * this programmatically? */ memsize = 32768; /* * Fill in the rest of the soft config structure. */ /* * The transmit page index. */ sc->tx_page_start = ED_HPP_TX_PAGE_OFFSET; if (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING) sc->txb_cnt = 1; else sc->txb_cnt = 2; /* * Memory description */ sc->mem_size = memsize; sc->mem_ring = sc->mem_start + (sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE); sc->mem_end = sc->mem_start + sc->mem_size; /* * Receive area starts after the transmit area and * continues till the end of memory. */ sc->rec_page_start = sc->tx_page_start + (sc->txb_cnt * ED_TXBUF_SIZE); sc->rec_page_stop = (sc->mem_size / ED_PAGE_SIZE); sc->cr_proto = 0; /* value works */ /* * Set the wrap registers for string I/O reads. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_HW); outw(sc->asic_addr + ED_HPP_HW_WRAP, ((sc->rec_page_start / ED_PAGE_SIZE) | (((sc->rec_page_stop / ED_PAGE_SIZE) - 1) << 8))); /* * Reset the register page to normal operation. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_PERF); /* * Verify that we can read/write from adapter memory. * Create test pattern. */ for (n = 0; n < ED_HPP_TEST_SIZE; n++) { test_pattern[n] = (n*n) ^ ~n; } #undef ED_HPP_TEST_SIZE /* * Check that the memory is accessible thru the I/O ports. * Write out the contents of "test_pattern", read back * into "test_buffer" and compare the two for any * mismatch. */ for (n = 0; n < (32768 / ED_PAGE_SIZE); n ++) { ed_pio_writemem(sc, test_pattern, (n * ED_PAGE_SIZE), sizeof(test_pattern)); ed_pio_readmem(sc, (n * ED_PAGE_SIZE), test_buffer, sizeof(test_pattern)); if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) return 0; } return (ED_HPP_IO_PORTS); } /* * HP PC Lan+ : Set the physical link to use AUI or TP/TL. */ void ed_hpp_set_physical_link(struct ed_softc *sc) { struct ifnet *ifp = &sc->arpcom.ac_if; int lan_page; outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_LAN); lan_page = inw(sc->asic_addr + ED_HPP_PAGE_0); if (ifp->if_flags & IFF_ALTPHYS) { /* * Use the AUI port. */ lan_page |= ED_HPP_LAN_AUI; outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_LAN); outw(sc->asic_addr + ED_HPP_PAGE_0, lan_page); } else { /* * Use the ThinLan interface */ lan_page &= ~ED_HPP_LAN_AUI; outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_LAN); outw(sc->asic_addr + ED_HPP_PAGE_0, lan_page); } /* * Wait for the lan card to re-initialize itself */ DELAY(150000); /* wait 150 ms */ /* * Restore normal pages. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_PERF); } /* * Install interface into kernel networking data structures */ static int ed_attach(sc, unit, flags) struct ed_softc *sc; int unit; int flags; { struct ifnet *ifp = &sc->arpcom.ac_if; /* * Set interface to stopped condition (reset) */ ed_stop(sc); if (!ifp->if_name) { /* * Initialize ifnet structure */ ifp->if_softc = sc; ifp->if_unit = unit; ifp->if_name = "ed"; ifp->if_output = ether_output; ifp->if_start = ed_start; ifp->if_ioctl = ed_ioctl; ifp->if_watchdog = ed_watchdog; ifp->if_init = ed_init; ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; ifp->if_linkmib = &sc->mibdata; ifp->if_linkmiblen = sizeof sc->mibdata; /* * XXX - should do a better job. */ if (sc->is790) sc->mibdata.dot3StatsEtherChipSet = DOT3CHIPSET(dot3VendorWesternDigital, dot3ChipSetWesternDigital83C790); else sc->mibdata.dot3StatsEtherChipSet = DOT3CHIPSET(dot3VendorNational, dot3ChipSetNational8390); sc->mibdata.dot3Compliance = DOT3COMPLIANCE_COLLS; /* * Set default state for ALTPHYS flag (used to disable the * tranceiver for AUI operation), based on compile-time * config option. */ if (flags & ED_FLAGS_DISABLE_TRANCEIVER) ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | IFF_ALTPHYS); else ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); /* * Attach the interface */ if_attach(ifp); ether_ifattach(ifp); } /* device attach does transition from UNCONFIGURED to IDLE state */ /* * Print additional info when attached */ printf("%s%d: address %6D, ", ifp->if_name, ifp->if_unit, sc->arpcom.ac_enaddr, ":"); if (sc->type_str && (*sc->type_str != 0)) printf("type %s ", sc->type_str); else printf("type unknown (0x%x) ", sc->type); if (sc->vendor == ED_VENDOR_HP) printf("(%s %s IO)", (sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS) ? "16-bit" : "32-bit", sc->hpp_mem_start ? "memory mapped" : "regular"); else printf("%s ", sc->isa16bit ? "(16 bit)" : "(8 bit)"); printf("%s\n", (((sc->vendor == ED_VENDOR_3COM) || (sc->vendor == ED_VENDOR_HP)) && (ifp->if_flags & IFF_ALTPHYS)) ? " tranceiver disabled" : ""); /* * If BPF is in the kernel, call the attach for it */ #if NBPFILTER > 0 bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); #endif return 1; } static int ed_attach_isa(isa_dev) struct isa_device *isa_dev; { int unit = isa_dev->id_unit; struct ed_softc *sc = &ed_softc[unit]; int flags = isa_dev->id_flags; return ed_attach(sc, unit, flags); } #if NPCI > 0 void * ed_attach_NE2000_pci(unit, port) int unit; int port; { struct ed_softc *sc = malloc(sizeof *sc, M_DEVBUF, M_NOWAIT); int isa_flags = 0; if (!sc) return sc; bzero(sc, sizeof *sc); if (ed_probe_Novell_generic(sc, port, unit, isa_flags) == 0 || ed_attach(sc, unit, isa_flags) == 0) { free(sc, M_DEVBUF); return NULL; } return sc; } #endif /* * Reset interface. */ static void ed_reset(ifp) struct ifnet *ifp; { struct ed_softc *sc = ifp->if_softc; int s; if (sc->gone) return; s = splimp(); /* * Stop interface and re-initialize. */ ed_stop(sc); ed_init(sc); (void) splx(s); } /* * Take interface offline. */ static void ed_stop(sc) struct ed_softc *sc; { int n = 5000; if (sc->gone) return; /* * Stop everything on the interface, and select page 0 registers. */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); /* * Wait for interface to enter stopped state, but limit # of checks to * 'n' (about 5ms). It shouldn't even take 5us on modern DS8390's, but * just in case it's an old one. */ while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST) == 0) && --n); } /* * Device timeout/watchdog routine. Entered if the device neglects to * generate an interrupt after a transmit has been started on it. */ static void ed_watchdog(ifp) struct ifnet *ifp; { struct ed_softc *sc = ifp->if_softc; if (sc->gone) return; log(LOG_ERR, "ed%d: device timeout\n", ifp->if_unit); ifp->if_oerrors++; ed_reset(ifp); } /* * Initialize device. */ static void ed_init(xsc) void *xsc; { struct ed_softc *sc = xsc; struct ifnet *ifp = &sc->arpcom.ac_if; int i, s; if (sc->gone) return; /* address not known */ - if (ifp->if_addrlist == (struct ifaddr *) 0) + if (TAILQ_EMPTY(&ifp->if_addrhead)) /* unlikely? XXX */ return; /* * Initialize the NIC in the exact order outlined in the NS manual. * This init procedure is "mandatory"...don't change what or when * things happen. */ s = splimp(); /* reset transmitter flags */ sc->xmit_busy = 0; ifp->if_timer = 0; sc->txb_inuse = 0; sc->txb_new = 0; sc->txb_next_tx = 0; /* This variable is used below - don't move this assignment */ sc->next_packet = sc->rec_page_start + 1; /* * Set interface for page 0, Remote DMA complete, Stopped */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); if (sc->isa16bit) { /* * Set FIFO threshold to 8, No auto-init Remote DMA, byte * order=80x86, word-wide DMA xfers, */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_WTS | ED_DCR_LS); } else { /* * Same as above, but byte-wide DMA xfers */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_LS); } /* * Clear Remote Byte Count Registers */ outb(sc->nic_addr + ED_P0_RBCR0, 0); outb(sc->nic_addr + ED_P0_RBCR1, 0); /* * For the moment, don't store incoming packets in memory. */ outb(sc->nic_addr + ED_P0_RCR, ED_RCR_MON); /* * Place NIC in internal loopback mode */ outb(sc->nic_addr + ED_P0_TCR, ED_TCR_LB0); /* * Initialize transmit/receive (ring-buffer) Page Start */ outb(sc->nic_addr + ED_P0_TPSR, sc->tx_page_start); outb(sc->nic_addr + ED_P0_PSTART, sc->rec_page_start); /* Set lower bits of byte addressable framing to 0 */ if (sc->is790) outb(sc->nic_addr + 0x09, 0); /* * Initialize Receiver (ring-buffer) Page Stop and Boundry */ outb(sc->nic_addr + ED_P0_PSTOP, sc->rec_page_stop); outb(sc->nic_addr + ED_P0_BNRY, sc->rec_page_start); /* * Clear all interrupts. A '1' in each bit position clears the * corresponding flag. */ outb(sc->nic_addr + ED_P0_ISR, 0xff); /* * Enable the following interrupts: receive/transmit complete, * receive/transmit error, and Receiver OverWrite. * * Counter overflow and Remote DMA complete are *not* enabled. */ outb(sc->nic_addr + ED_P0_IMR, ED_IMR_PRXE | ED_IMR_PTXE | ED_IMR_RXEE | ED_IMR_TXEE | ED_IMR_OVWE); /* * Program Command Register for page 1 */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STP); /* * Copy out our station address */ for (i = 0; i < ETHER_ADDR_LEN; ++i) outb(sc->nic_addr + ED_P1_PAR0 + i, sc->arpcom.ac_enaddr[i]); /* * Set Current Page pointer to next_packet (initialized above) */ outb(sc->nic_addr + ED_P1_CURR, sc->next_packet); /* * Program Receiver Configuration Register and multicast filter. CR is * set to page 0 on return. */ ed_setrcr(sc); /* * Take interface out of loopback */ outb(sc->nic_addr + ED_P0_TCR, 0); /* * If this is a 3Com board, the tranceiver must be software enabled * (there is no settable hardware default). */ if (sc->vendor == ED_VENDOR_3COM) { if (ifp->if_flags & IFF_ALTPHYS) { outb(sc->asic_addr + ED_3COM_CR, 0); } else { outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); } } /* * Set 'running' flag, and clear output active flag. */ ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; /* * ...and attempt to start output */ ed_start(ifp); (void) splx(s); } /* * This routine actually starts the transmission on the interface */ static inline void ed_xmit(sc) struct ed_softc *sc; { struct ifnet *ifp = (struct ifnet *)sc; unsigned short len; if (sc->gone) return; len = sc->txb_len[sc->txb_next_tx]; /* * Set NIC for page 0 register access */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* * Set TX buffer start page */ outb(sc->nic_addr + ED_P0_TPSR, sc->tx_page_start + sc->txb_next_tx * ED_TXBUF_SIZE); /* * Set TX length */ outb(sc->nic_addr + ED_P0_TBCR0, len); outb(sc->nic_addr + ED_P0_TBCR1, len >> 8); /* * Set page 0, Remote DMA complete, Transmit Packet, and *Start* */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_TXP | ED_CR_STA); sc->xmit_busy = 1; /* * Point to next transmit buffer slot and wrap if necessary. */ sc->txb_next_tx++; if (sc->txb_next_tx == sc->txb_cnt) sc->txb_next_tx = 0; /* * Set a timer just in case we never hear from the board again */ ifp->if_timer = 2; } /* * Start output on interface. * We make two assumptions here: * 1) that the current priority is set to splimp _before_ this code * is called *and* is returned to the appropriate priority after * return * 2) that the IFF_OACTIVE flag is checked before this code is called * (i.e. that the output part of the interface is idle) */ static void ed_start(ifp) struct ifnet *ifp; { struct ed_softc *sc = ifp->if_softc; struct mbuf *m0, *m; caddr_t buffer; int len; if (sc->gone) { printf("ed_start(%p) GONE\n",ifp); return; } outloop: /* * First, see if there are buffered packets and an idle transmitter - * should never happen at this point. */ if (sc->txb_inuse && (sc->xmit_busy == 0)) { printf("ed: packets buffered, but transmitter idle\n"); ed_xmit(sc); } /* * See if there is room to put another packet in the buffer. */ if (sc->txb_inuse == sc->txb_cnt) { /* * No room. Indicate this to the outside world and exit. */ ifp->if_flags |= IFF_OACTIVE; return; } IF_DEQUEUE(&ifp->if_snd, m); if (m == 0) { /* * We are using the !OACTIVE flag to indicate to the outside * world that we can accept an additional packet rather than * that the transmitter is _actually_ active. Indeed, the * transmitter may be active, but if we haven't filled all the * buffers with data then we still want to accept more. */ ifp->if_flags &= ~IFF_OACTIVE; return; } /* * Copy the mbuf chain into the transmit buffer */ m0 = m; /* txb_new points to next open buffer slot */ buffer = sc->mem_start + (sc->txb_new * ED_TXBUF_SIZE * ED_PAGE_SIZE); if (sc->mem_shared) { /* * Special case setup for 16 bit boards... */ if (sc->isa16bit) { switch (sc->vendor) { /* * For 16bit 3Com boards (which have 16k of * memory), we have the xmit buffers in a * different page of memory ('page 0') - so * change pages. */ case ED_VENDOR_3COM: outb(sc->asic_addr + ED_3COM_GACFR, ED_3COM_GACFR_RSEL); break; /* * Enable 16bit access to shared memory on * WD/SMC boards. */ case ED_VENDOR_WD_SMC:{ outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto | ED_WD_LAAR_M16EN)); if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB); } break; } } } for (len = 0; m != 0; m = m->m_next) { bcopy(mtod(m, caddr_t), buffer, m->m_len); buffer += m->m_len; len += m->m_len; } /* * Restore previous shared memory access */ if (sc->isa16bit) { switch (sc->vendor) { case ED_VENDOR_3COM: outb(sc->asic_addr + ED_3COM_GACFR, ED_3COM_GACFR_RSEL | ED_3COM_GACFR_MBS0); break; case ED_VENDOR_WD_SMC:{ if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, sc->wd_laar_proto); break; } } } } else { len = ed_pio_write_mbufs(sc, m, (int)buffer); if (len == 0) goto outloop; } sc->txb_len[sc->txb_new] = max(len, (ETHER_MIN_LEN-ETHER_CRC_LEN)); sc->txb_inuse++; /* * Point to next buffer slot and wrap if necessary. */ sc->txb_new++; if (sc->txb_new == sc->txb_cnt) sc->txb_new = 0; if (sc->xmit_busy == 0) ed_xmit(sc); /* * Tap off here if there is a bpf listener. */ #if NBPFILTER > 0 if (ifp->if_bpf) { bpf_mtap(ifp, m0); } #endif m_freem(m0); /* * Loop back to the top to possibly buffer more packets */ goto outloop; } /* * Ethernet interface receiver interrupt. */ static inline void ed_rint(sc) struct ed_softc *sc; { struct ifnet *ifp = &sc->arpcom.ac_if; u_char boundry; u_short len; struct ed_ring packet_hdr; char *packet_ptr; if (sc->gone) return; /* * Set NIC to page 1 registers to get 'current' pointer */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STA); /* * 'sc->next_packet' is the logical beginning of the ring-buffer - * i.e. it points to where new data has been buffered. The 'CURR' * (current) register points to the logical end of the ring-buffer - * i.e. it points to where additional new data will be added. We loop * here until the logical beginning equals the logical end (or in * other words, until the ring-buffer is empty). */ while (sc->next_packet != inb(sc->nic_addr + ED_P1_CURR)) { /* get pointer to this buffer's header structure */ packet_ptr = sc->mem_ring + (sc->next_packet - sc->rec_page_start) * ED_PAGE_SIZE; /* * The byte count includes a 4 byte header that was added by * the NIC. */ if (sc->mem_shared) packet_hdr = *(struct ed_ring *) packet_ptr; else ed_pio_readmem(sc, (int)packet_ptr, (char *) &packet_hdr, sizeof(packet_hdr)); len = packet_hdr.count; if (len > (ETHER_MAX_LEN - ETHER_CRC_LEN + sizeof(struct ed_ring)) || len < (ETHER_MIN_LEN - ETHER_CRC_LEN + sizeof(struct ed_ring))) { /* * Length is a wild value. There's a good chance that * this was caused by the NIC being old and buggy. * The bug is that the length low byte is duplicated in * the high byte. Try to recalculate the length based on * the pointer to the next packet. */ /* * NOTE: sc->next_packet is pointing at the current packet. */ len &= ED_PAGE_SIZE - 1; /* preserve offset into page */ if (packet_hdr.next_packet >= sc->next_packet) { len += (packet_hdr.next_packet - sc->next_packet) * ED_PAGE_SIZE; } else { len += ((packet_hdr.next_packet - sc->rec_page_start) + (sc->rec_page_stop - sc->next_packet)) * ED_PAGE_SIZE; } if (len > (ETHER_MAX_LEN - ETHER_CRC_LEN + sizeof(struct ed_ring))) sc->mibdata.dot3StatsFrameTooLongs++; } /* * Be fairly liberal about what we allow as a "reasonable" length * so that a [crufty] packet will make it to BPF (and can thus * be analyzed). Note that all that is really important is that * we have a length that will fit into one mbuf cluster or less; * the upper layer protocols can then figure out the length from * their own length field(s). */ if ((len > sizeof(struct ed_ring)) && (len <= MCLBYTES) && (packet_hdr.next_packet >= sc->rec_page_start) && (packet_hdr.next_packet < sc->rec_page_stop)) { /* * Go get packet. */ ed_get_packet(sc, packet_ptr + sizeof(struct ed_ring), len - sizeof(struct ed_ring), packet_hdr.rsr & ED_RSR_PHY); ifp->if_ipackets++; } else { /* * Really BAD. The ring pointers are corrupted. */ log(LOG_ERR, "ed%d: NIC memory corrupt - invalid packet length %d\n", ifp->if_unit, len); ifp->if_ierrors++; ed_reset(ifp); return; } /* * Update next packet pointer */ sc->next_packet = packet_hdr.next_packet; /* * Update NIC boundry pointer - being careful to keep it one * buffer behind. (as recommended by NS databook) */ boundry = sc->next_packet - 1; if (boundry < sc->rec_page_start) boundry = sc->rec_page_stop - 1; /* * Set NIC to page 0 registers to update boundry register */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); outb(sc->nic_addr + ED_P0_BNRY, boundry); /* * Set NIC to page 1 registers before looping to top (prepare * to get 'CURR' current pointer) */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STA); } } /* * Ethernet interface interrupt processor */ void edintr_sc(sc) struct ed_softc *sc; { struct ifnet *ifp = (struct ifnet *)sc; u_char isr; if (sc->gone) return; /* * Set NIC to page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* * loop until there are no more new interrupts */ while ((isr = inb(sc->nic_addr + ED_P0_ISR)) != 0) { /* * reset all the bits that we are 'acknowledging' by writing a * '1' to each bit position that was set (writing a '1' * *clears* the bit) */ outb(sc->nic_addr + ED_P0_ISR, isr); /* * Handle transmitter interrupts. Handle these first because * the receiver will reset the board under some conditions. */ if (isr & (ED_ISR_PTX | ED_ISR_TXE)) { u_char collisions = inb(sc->nic_addr + ED_P0_NCR) & 0x0f; /* * Check for transmit error. If a TX completed with an * error, we end up throwing the packet away. Really * the only error that is possible is excessive * collisions, and in this case it is best to allow * the automatic mechanisms of TCP to backoff the * flow. Of course, with UDP we're screwed, but this * is expected when a network is heavily loaded. */ (void) inb(sc->nic_addr + ED_P0_TSR); if (isr & ED_ISR_TXE) { u_char tsr; /* * Excessive collisions (16) */ tsr = inb(sc->nic_addr + ED_P0_TSR); if ((tsr & ED_TSR_ABT) && (collisions == 0)) { /* * When collisions total 16, the * P0_NCR will indicate 0, and the * TSR_ABT is set. */ collisions = 16; sc->mibdata.dot3StatsExcessiveCollisions++; sc->mibdata.dot3StatsCollFrequencies[15]++; } if (tsr & ED_TSR_OWC) sc->mibdata.dot3StatsLateCollisions++; if (tsr & ED_TSR_CDH) sc->mibdata.dot3StatsSQETestErrors++; if (tsr & ED_TSR_CRS) sc->mibdata.dot3StatsCarrierSenseErrors++; if (tsr & ED_TSR_FU) sc->mibdata.dot3StatsInternalMacTransmitErrors++; /* * update output errors counter */ ifp->if_oerrors++; } else { /* * Update total number of successfully * transmitted packets. */ ifp->if_opackets++; } /* * reset tx busy and output active flags */ sc->xmit_busy = 0; ifp->if_flags &= ~IFF_OACTIVE; /* * clear watchdog timer */ ifp->if_timer = 0; /* * Add in total number of collisions on last * transmission. */ ifp->if_collisions += collisions; switch(collisions) { case 0: case 16: break; case 1: sc->mibdata.dot3StatsSingleCollisionFrames++; sc->mibdata.dot3StatsCollFrequencies[0]++; break; default: sc->mibdata.dot3StatsMultipleCollisionFrames++; sc->mibdata. dot3StatsCollFrequencies[collisions-1] ++; break; } /* * Decrement buffer in-use count if not zero (can only * be zero if a transmitter interrupt occured while * not actually transmitting). If data is ready to * transmit, start it transmitting, otherwise defer * until after handling receiver */ if (sc->txb_inuse && --sc->txb_inuse) ed_xmit(sc); } /* * Handle receiver interrupts */ if (isr & (ED_ISR_PRX | ED_ISR_RXE | ED_ISR_OVW)) { /* * Overwrite warning. In order to make sure that a * lockup of the local DMA hasn't occurred, we reset * and re-init the NIC. The NSC manual suggests only a * partial reset/re-init is necessary - but some chips * seem to want more. The DMA lockup has been seen * only with early rev chips - Methinks this bug was * fixed in later revs. -DG */ if (isr & ED_ISR_OVW) { ifp->if_ierrors++; #ifdef DIAGNOSTIC log(LOG_WARNING, "ed%d: warning - receiver ring buffer overrun\n", ifp->if_unit); #endif /* * Stop/reset/re-init NIC */ ed_reset(ifp); } else { /* * Receiver Error. One or more of: CRC error, * frame alignment error FIFO overrun, or * missed packet. */ if (isr & ED_ISR_RXE) { u_char rsr; rsr = inb(sc->nic_addr + ED_P0_RSR); if (rsr & ED_RSR_CRC) sc->mibdata.dot3StatsFCSErrors++; if (rsr & ED_RSR_FAE) sc->mibdata.dot3StatsAlignmentErrors++; if (rsr & ED_RSR_FO) sc->mibdata.dot3StatsInternalMacReceiveErrors++; ifp->if_ierrors++; #ifdef ED_DEBUG printf("ed%d: receive error %x\n", ifp->if_unit, inb(sc->nic_addr + ED_P0_RSR)); #endif } /* * Go get the packet(s) XXX - Doing this on an * error is dubious because there shouldn't be * any data to get (we've configured the * interface to not accept packets with * errors). */ /* * Enable 16bit access to shared memory first * on WD/SMC boards. */ if (sc->isa16bit && (sc->vendor == ED_VENDOR_WD_SMC)) { outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto |= ED_WD_LAAR_M16EN)); if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB); } } ed_rint(sc); /* disable 16bit access */ if (sc->isa16bit && (sc->vendor == ED_VENDOR_WD_SMC)) { if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto &= ~ED_WD_LAAR_M16EN)); } } } /* * If it looks like the transmitter can take more data, * attempt to start output on the interface. This is done * after handling the receiver to give the receiver priority. */ if ((ifp->if_flags & IFF_OACTIVE) == 0) ed_start(ifp); /* * return NIC CR to standard state: page 0, remote DMA * complete, start (toggling the TXP bit off, even if was just * set in the transmit routine, is *okay* - it is 'edge' * triggered from low to high) */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* * If the Network Talley Counters overflow, read them to reset * them. It appears that old 8390's won't clear the ISR flag * otherwise - resulting in an infinite loop. */ if (isr & ED_ISR_CNT) { (void) inb(sc->nic_addr + ED_P0_CNTR0); (void) inb(sc->nic_addr + ED_P0_CNTR1); (void) inb(sc->nic_addr + ED_P0_CNTR2); } } } void edintr(unit) int unit; { edintr_sc (&ed_softc[unit]); } /* * Process an ioctl request. This code needs some work - it looks * pretty ugly. */ static int ed_ioctl(ifp, command, data) register struct ifnet *ifp; int command; caddr_t data; { struct ed_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; int s, error = 0; if (sc->gone) { ifp->if_flags &= ~IFF_RUNNING; return ENXIO; } s = splimp(); switch (command) { case SIOCSIFADDR: case SIOCGIFADDR: case SIOCSIFMTU: error = ether_ioctl(ifp, command, data); break; case SIOCSIFFLAGS: /* * If the interface is marked up and stopped, then start it. * If it is marked down and running, then stop it. */ if (ifp->if_flags & IFF_UP) { if ((ifp->if_flags & IFF_RUNNING) == 0) ed_init(ifp->if_softc); } else { if (ifp->if_flags & IFF_RUNNING) { ed_stop(sc); ifp->if_flags &= ~IFF_RUNNING; } } #if NBPFILTER > 0 /* * Promiscuous flag may have changed, so reprogram the RCR. */ ed_setrcr(sc); #endif /* * An unfortunate hack to provide the (required) software * control of the tranceiver for 3Com boards. The ALTPHYS flag * disables the tranceiver if set. */ if (sc->vendor == ED_VENDOR_3COM) { if (ifp->if_flags & IFF_ALTPHYS) { outb(sc->asic_addr + ED_3COM_CR, 0); } else { outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); } } else if (sc->vendor == ED_VENDOR_HP) ed_hpp_set_physical_link(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: /* * Update out multicast list. */ error = (command == SIOCADDMULTI) ? ether_addmulti(ifr, &sc->arpcom) : ether_delmulti(ifr, &sc->arpcom); if (error == ENETRESET) { /* * Multicast list has changed; set the hardware filter * accordingly. */ ed_setrcr(sc); error = 0; } break; default: error = EINVAL; } (void) splx(s); return (error); } /* * Given a source and destination address, copy 'amount' of a packet from * the ring buffer into a linear destination buffer. Takes into account * ring-wrap. */ static inline char * ed_ring_copy(sc, src, dst, amount) struct ed_softc *sc; char *src; char *dst; u_short amount; { u_short tmp_amount; /* does copy wrap to lower addr in ring buffer? */ if (src + amount > sc->mem_end) { tmp_amount = sc->mem_end - src; /* copy amount up to end of NIC memory */ if (sc->mem_shared) bcopy(src, dst, tmp_amount); else ed_pio_readmem(sc, (int)src, dst, tmp_amount); amount -= tmp_amount; src = sc->mem_ring; dst += tmp_amount; } if (sc->mem_shared) bcopy(src, dst, amount); else ed_pio_readmem(sc, (int)src, dst, amount); return (src + amount); } /* * Retreive packet from shared memory and send to the next level up via * ether_input(). If there is a BPF listener, give a copy to BPF, too. */ static void ed_get_packet(sc, buf, len, multicast) struct ed_softc *sc; char *buf; u_short len; int multicast; { struct ether_header *eh; struct mbuf *m; /* Allocate a header mbuf */ MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return; m->m_pkthdr.rcvif = &sc->arpcom.ac_if; m->m_pkthdr.len = m->m_len = len; /* * We always put the received packet in a single buffer - * either with just an mbuf header or in a cluster attached * to the header. The +2 is to compensate for the alignment * fixup below. */ if ((len + 2) > MHLEN) { /* Attach an mbuf cluster */ MCLGET(m, M_DONTWAIT); /* Insist on getting a cluster */ if ((m->m_flags & M_EXT) == 0) { m_freem(m); return; } } /* * The +2 is to longword align the start of the real packet. * This is important for NFS. */ m->m_data += 2; eh = mtod(m, struct ether_header *); /* * Get packet, including link layer address, from interface. */ ed_ring_copy(sc, buf, (char *)eh, len); #if NBPFILTER > 0 /* * Check if there's a BPF listener on this interface. If so, hand off * the raw packet to bpf. */ if (sc->arpcom.ac_if.if_bpf) { bpf_mtap(&sc->arpcom.ac_if, m); /* * Note that the interface cannot be in promiscuous mode if * there are no BPF listeners. And if we are in promiscuous * mode, we have to check if this packet is really ours. */ if ((sc->arpcom.ac_if.if_flags & IFF_PROMISC) && bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, sizeof(eh->ether_dhost)) != 0 && multicast == 0) { m_freem(m); return; } } #endif /* * Remove link layer address. */ m->m_pkthdr.len = m->m_len = len - sizeof(struct ether_header); m->m_data += sizeof(struct ether_header); ether_input(&sc->arpcom.ac_if, eh, m); return; } /* * Supporting routines */ /* * Given a NIC memory source address and a host memory destination * address, copy 'amount' from NIC to host using Programmed I/O. * The 'amount' is rounded up to a word - okay as long as mbufs * are word sized. * This routine is currently Novell-specific. */ static void ed_pio_readmem(sc, src, dst, amount) struct ed_softc *sc; int src; unsigned char *dst; unsigned short amount; { /* HP cards need special handling */ if (sc->vendor == ED_VENDOR_HP && sc->type == ED_TYPE_HP_PCLANPLUS) { ed_hpp_readmem(sc, src, dst, amount); return; } /* Regular Novell cards */ /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); /* round up to a word */ if (amount & 1) ++amount; /* set up DMA byte count */ outb(sc->nic_addr + ED_P0_RBCR0, amount); outb(sc->nic_addr + ED_P0_RBCR1, amount >> 8); /* set up source address in NIC mem */ outb(sc->nic_addr + ED_P0_RSAR0, src); outb(sc->nic_addr + ED_P0_RSAR1, src >> 8); outb(sc->nic_addr + ED_P0_CR, ED_CR_RD0 | ED_CR_STA); if (sc->isa16bit) { insw(sc->asic_addr + ED_NOVELL_DATA, dst, amount / 2); } else insb(sc->asic_addr + ED_NOVELL_DATA, dst, amount); } /* * Stripped down routine for writing a linear buffer to NIC memory. * Only used in the probe routine to test the memory. 'len' must * be even. */ static void ed_pio_writemem(sc, src, dst, len) struct ed_softc *sc; char *src; unsigned short dst; unsigned short len; { int maxwait = 200; /* about 240us */ if (sc->vendor == ED_VENDOR_NOVELL) { /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* set up DMA byte count */ outb(sc->nic_addr + ED_P0_RBCR0, len); outb(sc->nic_addr + ED_P0_RBCR1, len >> 8); /* set up destination address in NIC mem */ outb(sc->nic_addr + ED_P0_RSAR0, dst); outb(sc->nic_addr + ED_P0_RSAR1, dst >> 8); /* set remote DMA write */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD1 | ED_CR_STA); if (sc->isa16bit) outsw(sc->asic_addr + ED_NOVELL_DATA, src, len / 2); else outsb(sc->asic_addr + ED_NOVELL_DATA, src, len); /* * Wait for remote DMA complete. This is necessary because on the * transmit side, data is handled internally by the NIC in bursts and * we can't start another remote DMA until this one completes. Not * waiting causes really bad things to happen - like the NIC * irrecoverably jamming the ISA bus. */ while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RDC) != ED_ISR_RDC) && --maxwait); } else if ((sc->vendor == ED_VENDOR_HP) && (sc->type == ED_TYPE_HP_PCLANPLUS)) { /* HP PCLAN+ */ /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* program the write address in RAM */ outw(sc->asic_addr + ED_HPP_PAGE_0, dst); if (sc->hpp_mem_start) { u_short *s = (u_short *) src; volatile u_short *d = (u_short *) sc->hpp_mem_start; u_short *const fence = s + (len >> 1); /* * Enable memory mapped access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options & ~(ED_HPP_OPTION_MEM_DISABLE | ED_HPP_OPTION_BOOT_ROM_ENB)); /* * Copy to NIC memory. */ while (s < fence) *d = *s++; /* * Restore Boot ROM access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options); } else { /* write data using I/O writes */ outsw(sc->asic_addr + ED_HPP_PAGE_4, src, len / 2); } } } /* * Write an mbuf chain to the destination NIC memory address using * programmed I/O. */ static u_short ed_pio_write_mbufs(sc, m, dst) struct ed_softc *sc; struct mbuf *m; int dst; { struct ifnet *ifp = (struct ifnet *)sc; unsigned short total_len, dma_len; struct mbuf *mp; int maxwait = 200; /* about 240us */ /* HP PC Lan+ cards need special handling */ if ((sc->vendor == ED_VENDOR_HP) && (sc->type == ED_TYPE_HP_PCLANPLUS)) { return ed_hpp_write_mbufs(sc, m, dst); } /* First, count up the total number of bytes to copy */ for (total_len = 0, mp = m; mp; mp = mp->m_next) total_len += mp->m_len; dma_len = total_len; if (sc->isa16bit && (dma_len & 1)) dma_len++; /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* set up DMA byte count */ outb(sc->nic_addr + ED_P0_RBCR0, dma_len); outb(sc->nic_addr + ED_P0_RBCR1, dma_len >> 8); /* set up destination address in NIC mem */ outb(sc->nic_addr + ED_P0_RSAR0, dst); outb(sc->nic_addr + ED_P0_RSAR1, dst >> 8); /* set remote DMA write */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD1 | ED_CR_STA); /* * Transfer the mbuf chain to the NIC memory. * 16-bit cards require that data be transferred as words, and only words. * So that case requires some extra code to patch over odd-length mbufs. */ if (!sc->isa16bit) { /* NE1000s are easy */ while (m) { if (m->m_len) { outsb(sc->asic_addr + ED_NOVELL_DATA, m->m_data, m->m_len); } m = m->m_next; } } else { /* NE2000s are a pain */ unsigned char *data; int len, wantbyte; unsigned char savebyte[2]; wantbyte = 0; while (m) { len = m->m_len; if (len) { data = mtod(m, caddr_t); /* finish the last word */ if (wantbyte) { savebyte[1] = *data; outw(sc->asic_addr + ED_NOVELL_DATA, *(u_short *)savebyte); data++; len--; wantbyte = 0; } /* output contiguous words */ if (len > 1) { outsw(sc->asic_addr + ED_NOVELL_DATA, data, len >> 1); data += len & ~1; len &= 1; } /* save last byte, if necessary */ if (len == 1) { savebyte[0] = *data; wantbyte = 1; } } m = m->m_next; } /* spit last byte */ if (wantbyte) { outw(sc->asic_addr + ED_NOVELL_DATA, *(u_short *)savebyte); } } /* * Wait for remote DMA complete. This is necessary because on the * transmit side, data is handled internally by the NIC in bursts and * we can't start another remote DMA until this one completes. Not * waiting causes really bad things to happen - like the NIC * irrecoverably jamming the ISA bus. */ while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RDC) != ED_ISR_RDC) && --maxwait); if (!maxwait) { log(LOG_WARNING, "ed%d: remote transmit DMA failed to complete\n", ifp->if_unit); ed_reset(ifp); return(0); } return (total_len); } /* * Support routines to handle the HP PC Lan+ card. */ /* * HP PC Lan+: Read from NIC memory, using either PIO or memory mapped * IO. */ static void ed_hpp_readmem(sc, src, dst, amount) struct ed_softc *sc; unsigned short src; unsigned char *dst; unsigned short amount; { int use_32bit_access = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS); /* Program the source address in RAM */ outw(sc->asic_addr + ED_HPP_PAGE_2, src); /* * The HP PC Lan+ card supports word reads as well as * a memory mapped i/o port that is aliased to every * even address on the board. */ if (sc->hpp_mem_start) { /* Enable memory mapped access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options & ~(ED_HPP_OPTION_MEM_DISABLE | ED_HPP_OPTION_BOOT_ROM_ENB)); if (use_32bit_access && (amount > 3)) { u_long *dl = (u_long *) dst; volatile u_long *const sl = (u_long *) sc->hpp_mem_start; u_long *const fence = dl + (amount >> 2); /* Copy out NIC data. We could probably write this as a `movsl'. The currently generated code is lousy. */ while (dl < fence) *dl++ = *sl; dst += (amount & ~3); amount &= 3; } /* Finish off any words left, as a series of short reads */ if (amount > 1) { u_short *d = (u_short *) dst; volatile u_short *const s = (u_short *) sc->hpp_mem_start; u_short *const fence = d + (amount >> 1); /* Copy out NIC data. */ while (d < fence) *d++ = *s; dst += (amount & ~1); amount &= 1; } /* * read in a byte; however we need to always read 16 bits * at a time or the hardware gets into a funny state */ if (amount == 1) { /* need to read in a short and copy LSB */ volatile u_short *const s = (volatile u_short *) sc->hpp_mem_start; *dst = (*s) & 0xFF; } /* Restore Boot ROM access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options); } else { /* Read in data using the I/O port */ if (use_32bit_access && (amount > 3)) { insl(sc->asic_addr + ED_HPP_PAGE_4, dst, amount >> 2); dst += (amount & ~3); amount &= 3; } if (amount > 1) { insw(sc->asic_addr + ED_HPP_PAGE_4, dst, amount >> 1); dst += (amount & ~1); amount &= 1; } if (amount == 1) { /* read in a short and keep the LSB */ *dst = inw(sc->asic_addr + ED_HPP_PAGE_4) & 0xFF; } } } /* * Write to HP PC Lan+ NIC memory. Access to the NIC can be by using * outsw() or via the memory mapped interface to the same register. * Writes have to be in word units; byte accesses won't work and may cause * the NIC to behave wierdly. Long word accesses are permitted if the ASIC * allows it. */ static u_short ed_hpp_write_mbufs(struct ed_softc *sc, struct mbuf *m, int dst) { int len, wantbyte; unsigned short total_len; unsigned char savebyte[2]; volatile u_short * const d = (volatile u_short *) sc->hpp_mem_start; int use_32bit_accesses = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS); /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* program the write address in RAM */ outw(sc->asic_addr + ED_HPP_PAGE_0, dst); if (sc->hpp_mem_start) /* enable memory mapped I/O */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options & ~(ED_HPP_OPTION_MEM_DISABLE | ED_HPP_OPTION_BOOT_ROM_ENB)); wantbyte = 0; total_len = 0; if (sc->hpp_mem_start) { /* Memory mapped I/O port */ while (m) { total_len += (len = m->m_len); if (len) { caddr_t data = mtod(m, caddr_t); /* finish the last word of the previous mbuf */ if (wantbyte) { savebyte[1] = *data; *d = *((ushort *) savebyte); data++; len--; wantbyte = 0; } /* output contiguous words */ if ((len > 3) && (use_32bit_accesses)) { volatile u_long *const dl = (volatile u_long *) d; u_long *sl = (u_long *) data; u_long *fence = sl + (len >> 2); while (sl < fence) *dl = *sl++; data += (len & ~3); len &= 3; } /* finish off remain 16 bit writes */ if (len > 1) { u_short *s = (u_short *) data; u_short *fence = s + (len >> 1); while (s < fence) *d = *s++; data += (len & ~1); len &= 1; } /* save last byte if needed */ if (wantbyte = (len == 1)) savebyte[0] = *data; } m = m->m_next; /* to next mbuf */ } if (wantbyte) /* write last byte */ *d = *((u_short *) savebyte); } else { /* use programmed I/O */ while (m) { total_len += (len = m->m_len); if (len) { caddr_t data = mtod(m, caddr_t); /* finish the last word of the previous mbuf */ if (wantbyte) { savebyte[1] = *data; outw(sc->asic_addr + ED_HPP_PAGE_4, *((u_short *)savebyte)); data++; len--; wantbyte = 0; } /* output contiguous words */ if ((len > 3) && use_32bit_accesses) { outsl(sc->asic_addr + ED_HPP_PAGE_4, data, len >> 2); data += (len & ~3); len &= 3; } /* finish off remaining 16 bit accesses */ if (len > 1) { outsw(sc->asic_addr + ED_HPP_PAGE_4, data, len >> 1); data += (len & ~1); len &= 1; } if (wantbyte = (len == 1)) savebyte[0] = *data; } /* if len != 0 */ m = m->m_next; } if (wantbyte) /* spit last byte */ outw(sc->asic_addr + ED_HPP_PAGE_4, *(u_short *)savebyte); } if (sc->hpp_mem_start) /* turn off memory mapped i/o */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options); return (total_len); } static void ed_setrcr(sc) struct ed_softc *sc; { struct ifnet *ifp = (struct ifnet *)sc; int i; /* set page 1 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STP); if (ifp->if_flags & IFF_PROMISC) { /* * Reconfigure the multicast filter. */ for (i = 0; i < 8; i++) outb(sc->nic_addr + ED_P1_MAR0 + i, 0xff); /* * And turn on promiscuous mode. Also enable reception of * runts and packets with CRC & alignment errors. */ /* Set page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); outb(sc->nic_addr + ED_P0_RCR, ED_RCR_PRO | ED_RCR_AM | ED_RCR_AB | ED_RCR_AR | ED_RCR_SEP); } else { /* set up multicast addresses and filter modes */ if (ifp->if_flags & IFF_MULTICAST) { u_long mcaf[2]; if (ifp->if_flags & IFF_ALLMULTI) { mcaf[0] = 0xffffffff; mcaf[1] = 0xffffffff; } else ds_getmcaf(sc, mcaf); /* * Set multicast filter on chip. */ for (i = 0; i < 8; i++) outb(sc->nic_addr + ED_P1_MAR0 + i, ((u_char *) mcaf)[i]); /* Set page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); outb(sc->nic_addr + ED_P0_RCR, ED_RCR_AM | ED_RCR_AB); } else { /* * Initialize multicast address hashing registers to * not accept multicasts. */ for (i = 0; i < 8; ++i) outb(sc->nic_addr + ED_P1_MAR0 + i, 0x00); /* Set page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); outb(sc->nic_addr + ED_P0_RCR, ED_RCR_AB); } } /* * Start interface. */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); } /* * Compute crc for ethernet address */ static u_long ds_crc(ep) u_char *ep; { #define POLYNOMIAL 0x04c11db6 register u_long crc = 0xffffffffL; register int carry, i, j; register u_char b; for (i = 6; --i >= 0;) { b = *ep++; for (j = 8; --j >= 0;) { carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); crc <<= 1; b >>= 1; if (carry) crc = ((crc ^ POLYNOMIAL) | carry); } } return crc; #undef POLYNOMIAL } /* * Compute the multicast address filter from the * list of multicast addresses we need to listen to. */ static void ds_getmcaf(sc, mcaf) struct ed_softc *sc; u_long *mcaf; { register u_int index; register u_char *af = (u_char *) mcaf; register struct ether_multi *enm; register struct ether_multistep step; mcaf[0] = 0; mcaf[1] = 0; ETHER_FIRST_MULTI(step, &sc->arpcom, enm); while (enm != NULL) { if (bcmp(enm->enm_addrlo, enm->enm_addrhi, 6) != 0) { mcaf[0] = 0xffffffff; mcaf[1] = 0xffffffff; return; } index = ds_crc(enm->enm_addrlo) >> 26; af[index >> 3] |= 1 << (index & 7); ETHER_NEXT_MULTI(step, enm); } } diff --git a/sys/i386/isa/if_el.c b/sys/i386/isa/if_el.c index afa08ae4bf2b..3338aee5857c 100644 --- a/sys/i386/isa/if_el.c +++ b/sys/i386/isa/if_el.c @@ -1,801 +1,786 @@ /* Copyright (c) 1994, Matthew E. Kimmel. Permission is hereby granted * to use, copy, modify and distribute this software 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. * * Questions, comments, bug reports and fixes to kimmel@cs.umass.edu. * - * $Id: if_el.c,v 1.25 1996/08/06 21:14:04 phk Exp $ + * $Id: if_el.c,v 1.26 1996/09/06 23:07:32 phk Exp $ */ /* Except of course for the portions of code lifted from other FreeBSD * drivers (mainly elread, elget and el_ioctl) */ /* 3COM Etherlink 3C501 device driver for FreeBSD */ /* Yeah, I know these cards suck, but you can also get them for free * really easily... */ /* Bugs/possible improvements: * - Does not currently support DMA * - Does not currently support multicasts */ #include "el.h" #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #ifdef IPX #include #include #endif #ifdef NS #include #include #endif #if NBPFILTER > 0 #include #include #endif #include #include #include /* For debugging convenience */ #ifdef EL_DEBUG #define dprintf(x) printf x #else #define dprintf(x) #endif /* el_softc: per line info and status */ static struct el_softc { struct arpcom arpcom; /* Ethernet common */ u_short el_base; /* Base I/O addr */ char el_pktbuf[EL_BUFSIZ]; /* Frame buffer */ } el_softc[NEL]; /* Prototypes */ static int el_attach(struct isa_device *); static void el_init(int); static int el_ioctl(struct ifnet *,int,caddr_t); static int el_probe(struct isa_device *); static void el_start(struct ifnet *); static void el_reset(int); static void el_watchdog(struct ifnet *); static void el_stop(int); static int el_xmit(struct el_softc *,int); static inline void elread(struct el_softc *,caddr_t,int); static struct mbuf *elget(caddr_t,int,int,struct ifnet *); static inline void el_hardreset(int); /* isa_driver structure for autoconf */ struct isa_driver eldriver = { el_probe, el_attach, "el" }; /* Probe routine. See if the card is there and at the right place. */ static int el_probe(struct isa_device *idev) { struct el_softc *sc; u_short base; /* Just for convenience */ u_char station_addr[ETHER_ADDR_LEN]; int i; /* Grab some info for our structure */ sc = &el_softc[idev->id_unit]; sc->el_base = idev->id_iobase; base = sc->el_base; /* First check the base */ if((base < 0x280) || (base > 0x3f0)) { printf("el%d: ioaddr must be between 0x280 and 0x3f0\n", idev->id_unit); return(0); } /* Now attempt to grab the station address from the PROM * and see if it contains the 3com vendor code. */ dprintf(("Probing 3c501 at 0x%x...\n",base)); /* Reset the board */ dprintf(("Resetting board...\n")); outb(base+EL_AC,EL_AC_RESET); DELAY(5); outb(base+EL_AC,0); dprintf(("Reading station address...\n")); /* Now read the address */ for(i=0;iarpcom.ac_enaddr,ETHER_ADDR_LEN); return(1); } } /* Attach the interface to the kernel data structures. By the time * this is called, we know that the card exists at the given I/O address. * We still assume that the IRQ given is correct. */ static int el_attach(struct isa_device *idev) { struct el_softc *sc; struct ifnet *ifp; struct ifaddr *ifa; struct sockaddr_dl *sdl; u_short base; dprintf(("Attaching el%d...\n",idev->id_unit)); /* Get things pointing to the right places. */ sc = &el_softc[idev->id_unit]; ifp = &sc->arpcom.ac_if; base = sc->el_base; /* Now reset the board */ dprintf(("Resetting board...\n")); el_hardreset(idev->id_unit); /* Initialize ifnet structure */ ifp->if_softc = sc; ifp->if_unit = idev->id_unit; ifp->if_name = "el"; ifp->if_mtu = ETHERMTU; ifp->if_output = ether_output; ifp->if_start = el_start; ifp->if_ioctl = el_ioctl; ifp->if_watchdog = el_watchdog; ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX); /* Now we can attach the interface */ dprintf(("Attaching interface...\n")); if_attach(ifp); ether_ifattach(ifp); - /* Put the station address in the ifa address list's AF_LINK - * entry, if any. - */ - ifa = ifp->if_addrlist; - while ((ifa != NULL) && (ifa->ifa_addr != NULL) && - (ifa->ifa_addr->sa_family != AF_LINK)) - ifa = ifa->ifa_next; - if((ifa != NULL) && (ifa->ifa_addr != NULL)) { - sdl = (struct sockaddr_dl *)ifa->ifa_addr; - sdl->sdl_type = IFT_ETHER; - sdl->sdl_alen = ETHER_ADDR_LEN; - sdl->sdl_slen = 0; - bcopy(sc->arpcom.ac_enaddr,LLADDR(sdl),ETHER_ADDR_LEN); - } - /* Print out some information for the user */ printf("el%d: 3c501 address %6D\n",idev->id_unit, sc->arpcom.ac_enaddr, ":"); /* Finally, attach to bpf filter if it is present. */ #if NBPFILTER > 0 dprintf(("Attaching to BPF...\n")); bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); #endif dprintf(("el_attach() finished.\n")); return(1); } /* This routine resets the interface. */ static void el_reset(int unit) { int s; dprintf(("elreset()\n")); s = splimp(); el_stop(unit); el_init(unit); splx(s); } static void el_stop(int unit) { struct el_softc *sc; sc = &el_softc[unit]; outb(sc->el_base+EL_AC,0); } /* Do a hardware reset of the 3c501. Do not call until after el_probe()! */ static inline void el_hardreset(int unit) { register struct el_softc *sc; register int base; register int j; sc = &el_softc[unit]; base = sc->el_base; /* First reset the board */ outb(base+EL_AC,EL_AC_RESET); DELAY(5); outb(base+EL_AC,0); /* Then give it back its ethernet address. Thanks to the mach * source code for this undocumented goodie... */ for(j=0;jarpcom.ac_enaddr[j]); } /* Initialize interface. */ static void el_init(int unit) { struct el_softc *sc; struct ifnet *ifp; int s; u_short base; /* Set up pointers */ sc = &el_softc[unit]; ifp = &sc->arpcom.ac_if; base = sc->el_base; /* If address not known, do nothing. */ - if(ifp->if_addrlist == (struct ifaddr *)0) + if(TAILQ_EMPTY(&ifp->if_addrhead)) /* XXX unlikely */ return; s = splimp(); /* First, reset the board. */ dprintf(("Resetting board...\n")); el_hardreset(unit); /* Configure rx */ dprintf(("Configuring rx...\n")); if(ifp->if_flags & IFF_PROMISC) outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); else outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); outb(base+EL_RBC,0); /* Configure TX */ dprintf(("Configuring tx...\n")); outb(base+EL_TXC,0); /* Start reception */ dprintf(("Starting reception...\n")); outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); /* Set flags appropriately */ ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; /* And start output. */ el_start(ifp); splx(s); } /* Start output on interface. Get datagrams from the queue and output * them, giving the receiver a chance between datagrams. Call only * from splimp or interrupt level! */ static void el_start(struct ifnet *ifp) { struct el_softc *sc; u_short base; struct mbuf *m, *m0; int s, i, len, retries, done; /* Get things pointing in the right directions */ sc = ifp->if_softc; base = sc->el_base; dprintf(("el_start()...\n")); s = splimp(); /* Don't do anything if output is active */ if(sc->arpcom.ac_if.if_flags & IFF_OACTIVE) return; sc->arpcom.ac_if.if_flags |= IFF_OACTIVE; /* The main loop. They warned me against endless loops, but * would I listen? NOOO.... */ while(1) { /* Dequeue the next datagram */ IF_DEQUEUE(&sc->arpcom.ac_if.if_snd,m0); /* If there's nothing to send, return. */ if(m0 == NULL) { sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; splx(s); return; } /* Disable the receiver */ outb(base+EL_AC,EL_AC_HOST); outb(base+EL_RBC,0); /* Copy the datagram to the buffer. */ len = 0; for(m = m0; m != NULL; m = m->m_next) { if(m->m_len == 0) continue; bcopy(mtod(m,caddr_t),sc->el_pktbuf+len,m->m_len); len += m->m_len; } m_freem(m0); len = max(len,ETHER_MIN_LEN); /* Give the packet to the bpf, if any */ #if NBPFILTER > 0 if(sc->arpcom.ac_if.if_bpf) bpf_tap(&sc->arpcom.ac_if, sc->el_pktbuf, len); #endif /* Transfer datagram to board */ dprintf(("el: xfr pkt length=%d...\n",len)); i = EL_BUFSIZ - len; outb(base+EL_GPBL,(i & 0xff)); outb(base+EL_GPBH,((i>>8)&0xff)); outsb(base+EL_BUF,sc->el_pktbuf,len); /* Now transmit the datagram */ retries=0; done=0; while(!done) { if(el_xmit(sc,len)) { /* Something went wrong */ done = -1; break; } /* Check out status */ i = inb(base+EL_TXS); dprintf(("tx status=0x%x\n",i)); if(!(i & EL_TXS_READY)) { dprintf(("el: err txs=%x\n",i)); sc->arpcom.ac_if.if_oerrors++; if(i & (EL_TXS_COLL|EL_TXS_COLL16)) { if((!(i & EL_TXC_DCOLL16)) && retries < 15) { retries++; outb(base+EL_AC,EL_AC_HOST); } } else done = 1; } else { sc->arpcom.ac_if.if_opackets++; done = 1; } } if(done == -1) /* Packet not transmitted */ continue; /* Now give the card a chance to receive. * Gotta love 3c501s... */ (void)inb(base+EL_AS); outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); splx(s); /* Interrupt here */ s = splimp(); } } /* This function actually attempts to transmit a datagram downloaded * to the board. Call at splimp or interrupt, after downloading data! * Returns 0 on success, non-0 on failure */ static int el_xmit(struct el_softc *sc,int len) { int gpl; int i; gpl = EL_BUFSIZ - len; dprintf(("el: xmit...")); outb((sc->el_base)+EL_GPBL,(gpl & 0xff)); outb((sc->el_base)+EL_GPBH,((gpl>>8)&0xff)); outb((sc->el_base)+EL_AC,EL_AC_TXFRX); i = 20000; while((inb((sc->el_base)+EL_AS) & EL_AS_TXBUSY) && (i>0)) i--; if(i == 0) { dprintf(("tx not ready\n")); sc->arpcom.ac_if.if_oerrors++; return(-1); } dprintf(("%d cycles.\n",(20000-i))); return(0); } /* controller interrupt */ void elintr(int unit) { register struct el_softc *sc; register base; int stat, rxstat, len, done; /* Get things pointing properly */ sc = &el_softc[unit]; base = sc->el_base; dprintf(("elintr: ")); /* Check board status */ stat = inb(base+EL_AS); if(stat & EL_AS_RXBUSY) { (void)inb(base+EL_RXC); outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); return; } done = 0; while(!done) { rxstat = inb(base+EL_RXS); if(rxstat & EL_RXS_STALE) { (void)inb(base+EL_RXC); outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); return; } /* If there's an overflow, reinit the board. */ if(!(rxstat & EL_RXS_NOFLOW)) { dprintf(("overflow.\n")); el_hardreset(unit); /* Put board back into receive mode */ if(sc->arpcom.ac_if.if_flags & IFF_PROMISC) outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); else outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); (void)inb(base+EL_AS); outb(base+EL_RBC,0); (void)inb(base+EL_RXC); outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); return; } /* Incoming packet */ len = inb(base+EL_RBL); len |= inb(base+EL_RBH) << 8; dprintf(("receive len=%d rxstat=%x ",len,rxstat)); outb(base+EL_AC,EL_AC_HOST); /* If packet too short or too long, restore rx mode and return */ if((len <= sizeof(struct ether_header)) || (len > ETHER_MAX_LEN)) { if(sc->arpcom.ac_if.if_flags & IFF_PROMISC) outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); else outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); (void)inb(base+EL_AS); outb(base+EL_RBC,0); (void)inb(base+EL_RXC); outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); return; } sc->arpcom.ac_if.if_ipackets++; /* Copy the data into our buffer */ outb(base+EL_GPBL,0); outb(base+EL_GPBH,0); insb(base+EL_BUF,sc->el_pktbuf,len); outb(base+EL_RBC,0); outb(base+EL_AC,EL_AC_RX); dprintf(("%6D-->",sc->el_pktbuf+6,":")); dprintf(("%6D\n",sc->el_pktbuf,":")); /* Pass data up to upper levels */ len -= sizeof(struct ether_header); elread(sc,(caddr_t)(sc->el_pktbuf),len); /* Is there another packet? */ stat = inb(base+EL_AS); /* If so, do it all again (i.e. don't set done to 1) */ if(!(stat & EL_AS_RXBUSY)) dprintf((" ")); else done = 1; } (void)inb(base+EL_RXC); outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); return; } /* Pass a packet up to the higher levels. */ static inline void elread(struct el_softc *sc,caddr_t buf,int len) { register struct ether_header *eh; struct mbuf *m; eh = (struct ether_header *)buf; #if NBPFILTER > 0 /* * Check if there's a bpf filter listening on this interface. * If so, hand off the raw packet to bpf. */ if(sc->arpcom.ac_if.if_bpf) { bpf_tap(&sc->arpcom.ac_if, buf, len + sizeof(struct ether_header)); /* * Note that the interface cannot be in promiscuous mode if * there are no bpf listeners. And if el are in promiscuous * mode, el have to check if this packet is really ours. * * This test does not support multicasts. */ if((sc->arpcom.ac_if.if_flags & IFF_PROMISC) && bcmp(eh->ether_dhost,sc->arpcom.ac_enaddr, sizeof(eh->ether_dhost)) != 0 && bcmp(eh->ether_dhost,etherbroadcastaddr, sizeof(eh->ether_dhost)) != 0) return; } #endif /* * Pull packet off interface. */ m = elget(buf,len,0,&sc->arpcom.ac_if); if(m == 0) return; ether_input(&sc->arpcom.ac_if,eh,m); } /* * Pull read data off a interface. * Len is length of data, with local net header stripped. */ struct mbuf * elget(buf, totlen, off0, ifp) caddr_t buf; int totlen, off0; struct ifnet *ifp; { struct mbuf *top, **mp, *m; int off = off0, len; register caddr_t cp = buf; char *epkt; buf += sizeof(struct ether_header); cp = buf; epkt = cp + totlen; if (off) { cp += off + 2 * sizeof(u_short); totlen -= 2 * sizeof(u_short); } MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == 0) return (0); m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = totlen; m->m_len = MHLEN; top = 0; mp = ⊤ while (totlen > 0) { if (top) { MGET(m, M_DONTWAIT, MT_DATA); if (m == 0) { m_freem(top); return (0); } m->m_len = MLEN; } len = min(totlen, epkt - cp); if (len >= MINCLSIZE) { MCLGET(m, M_DONTWAIT); if (m->m_flags & M_EXT) m->m_len = len = min(len, MCLBYTES); else len = m->m_len; } else { /* * Place initial small packet/header at end of mbuf. */ if (len < m->m_len) { if (top == 0 && len + max_linkhdr <= m->m_len) m->m_data += max_linkhdr; m->m_len = len; } else len = m->m_len; } bcopy(cp, mtod(m, caddr_t), (unsigned)len); cp += len; *mp = m; mp = &m->m_next; totlen -= len; if (cp == epkt) cp = buf; } return (top); } /* * Process an ioctl request. This code needs some work - it looks * pretty ugly. */ static int el_ioctl(ifp, command, data) register struct ifnet *ifp; int command; caddr_t data; { register struct ifaddr *ifa = (struct ifaddr *)data; struct el_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *)data; int s, error = 0; s = splimp(); switch (command) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: el_init(ifp->if_unit); /* before arpwhohas */ arp_ifinit((struct arpcom *)ifp, ifa); break; #endif #ifdef IPX /* * XXX - This code is probably wrong */ case AF_IPX: { register struct ipx_addr *ina = &(IA_SIPX(ifa)->sipx_addr); if (ipx_nullhost(*ina)) ina->x_host = *(union ipx_host *)(sc->arpcom.ac_enaddr); else { /* * */ bcopy((caddr_t)ina->x_host.c_host, (caddr_t)sc->arpcom.ac_enaddr, sizeof(sc->arpcom.ac_enaddr)); } /* * Set new address */ el_init(ifp->if_unit); break; } #endif #ifdef NS /* * XXX - This code is probably wrong */ case AF_NS: { register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); if (ns_nullhost(*ina)) ina->x_host = *(union ns_host *)(sc->arpcom.ac_enaddr); else { /* * */ bcopy((caddr_t)ina->x_host.c_host, (caddr_t)sc->arpcom.ac_enaddr, sizeof(sc->arpcom.ac_enaddr)); } /* * Set new address */ el_init(ifp->if_unit); break; } #endif default: el_init(ifp->if_unit); break; } break; case SIOCGIFADDR: { struct sockaddr *sa; sa = (struct sockaddr *)&ifr->ifr_data; bcopy((caddr_t)sc->arpcom.ac_enaddr, (caddr_t) sa->sa_data, ETHER_ADDR_LEN); } break; case SIOCSIFFLAGS: /* * If interface is marked down and it is running, then stop it */ if (((ifp->if_flags & IFF_UP) == 0) && (ifp->if_flags & IFF_RUNNING)) { el_stop(ifp->if_unit); ifp->if_flags &= ~IFF_RUNNING; } else { /* * If interface is marked up and it is stopped, then start it */ if ((ifp->if_flags & IFF_UP) && ((ifp->if_flags & IFF_RUNNING) == 0)) el_init(ifp->if_unit); } break; case SIOCSIFMTU: /* * Set the interface MTU. */ if (ifr->ifr_mtu > ETHERMTU) { error = EINVAL; } else { ifp->if_mtu = ifr->ifr_mtu; } break; default: error = EINVAL; } (void) splx(s); return (error); } /* Device timeout routine */ static void el_watchdog(struct ifnet *ifp) { log(LOG_ERR,"el%d: device timeout\n", ifp->if_unit); ifp->if_oerrors++; el_reset(ifp->if_unit); } diff --git a/sys/i386/isa/if_ep.c b/sys/i386/isa/if_ep.c index 7840e8b1a809..6efbe85214a5 100644 --- a/sys/i386/isa/if_ep.c +++ b/sys/i386/isa/if_ep.c @@ -1,1626 +1,1607 @@ /* * Copyright (c) 1994 Herb Peyerl * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Herb Peyerl. * 4. The name of Herb Peyerl may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * if_ep.c,v 1.19 1995/01/24 20:53:45 davidg Exp */ /* * Modified from the FreeBSD 1.1.5.1 version by: * Andres Vega Garcia * INRIA - Sophia Antipolis, France * avega@sophia.inria.fr */ /* - * $Id: if_ep.c,v 1.52 1996/07/27 12:40:31 amurai Exp $ + * $Id: if_ep.c,v 1.53 1996/09/06 23:07:33 phk Exp $ * * Promiscuous mode added and interrupt logic slightly changed * to reduce the number of adapter failures. Transceiver select * logic changed to use value from EEPROM. Autoconfiguration * features added. * Done by: * Serge Babkin * Chelindbank (Chelyabinsk, Russia) * babkin@hq.icb.chel.su */ /* * Pccard support for 3C589 by: * HAMADA Naoki * nao@tom-yam.or.jp */ #include "ep.h" #if NEP > 0 #include "bpfilter.h" #include #if defined(__FreeBSD__) #include #include #include #endif #include #include #include #include #include #if defined(__NetBSD__) #include #endif #include #include #include #ifdef INET #include #include #include #include #include #endif #ifdef IPX #include #include #endif #ifdef NS #include #include #endif #if NBPFILTER > 0 #include #include #endif #if defined(__FreeBSD__) #include #endif #include #include #include /* Exported variables */ u_long ep_unit; int ep_boards; struct ep_board ep_board[EP_MAX_BOARDS + 1]; static int eeprom_rdy __P((struct ep_softc *sc)); static int ep_isa_probe __P((struct isa_device *)); static struct ep_board * ep_look_for_board_at __P((struct isa_device *is)); static int ep_isa_attach __P((struct isa_device *)); static int epioctl __P((struct ifnet * ifp, int, caddr_t)); static void epmbuffill __P((caddr_t, int)); static void epmbufempty __P((struct ep_softc *)); static void epinit __P((struct ep_softc *)); static void epread __P((struct ep_softc *)); void epreset __P((int)); static void epstart __P((struct ifnet *)); static void epstop __P((struct ep_softc *)); static void epwatchdog __P((struct ifnet *)); #if 0 static int send_ID_sequence __P((int)); #endif static int get_eeprom_data __P((int, int)); static struct ep_softc* ep_softc[NEP]; static int ep_current_tag = EP_LAST_TAG + 1; static char *ep_conn_type[] = {"UTP", "AUI", "???", "BNC"}; #define ep_ftst(f) (sc->stat&(f)) #define ep_fset(f) (sc->stat|=(f)) #define ep_frst(f) (sc->stat&=~(f)) struct isa_driver epdriver = { ep_isa_probe, ep_isa_attach, "ep", 0 }; #include "crd.h" #if NCRD > 0 #include "apm.h" #include #include #include #include /* * PC-Card (PCMCIA) specific code. */ static int card_intr __P((struct pccard_dev *)); static void ep_unload __P((struct pccard_dev *)); static void ep_suspend __P((struct pccard_dev *)); static int ep_pccard_init __P((struct pccard_dev *, int)); static int ep_pccard_attach __P((struct pccard_dev *)); static struct pccard_drv ep_info = { "ep", card_intr, ep_unload, ep_suspend, ep_pccard_init, 0, /* Attributes - presently unused */ &net_imask }; /* Resume is done by executing ep_pccard_init(dp, 0). */ static void ep_suspend(dp) struct pccard_dev *dp; { struct ep_softc *sc = ep_softc[dp->isahd.id_unit]; printf("ep%d: suspending\n", dp->isahd.id_unit); sc->gone = 1; } /* * */ static int ep_pccard_init(dp, first) struct pccard_dev *dp; int first; { struct isa_device *is = &dp->isahd; struct ep_softc *sc = ep_softc[is->id_unit]; struct ep_board *epb; int i; epb = &ep_board[is->id_unit]; if (sc == 0) { if ((sc = ep_alloc(is->id_unit, epb)) == 0) { return (ENXIO); } ep_unit++; } /* get_e() requires these. */ sc->ep_io_addr = is->id_iobase; sc->unit = is->id_unit; epb->epb_addr = is->id_iobase; epb->epb_used = 1; epb->prod_id = get_e(sc, EEPROM_PROD_ID); if (epb->prod_id != 0x9058) { /* 3C589's product id */ if (first) { printf("ep%d: failed to come ready.\n", is->id_unit); } else { printf("ep%d: failed to resume.\n", is->id_unit); } return (ENXIO); } epb->res_cfg = get_e(sc, EEPROM_RESOURCE_CFG); for (i = 0; i < 3; i++) { sc->epb->eth_addr[i] = get_e(sc, EEPROM_NODE_ADDR_0 + i); } if (first) { if (ep_pccard_attach(dp) == 0) { return (ENXIO); } sc->arpcom.ac_if.if_snd.ifq_maxlen = ifqmaxlen; } if (!first) { sc->gone = 0; printf("ep%d: resumed.\n", is->id_unit); epinit(sc); } return (0); } static int ep_pccard_attach(dp) struct pccard_dev *dp; { struct isa_device *is = &dp->isahd; struct ep_softc *sc = ep_softc[is->id_unit]; u_short config; sc->ep_connectors = 0; config = inw(IS_BASE + EP_W0_CONFIG_CTRL); if (config & IS_BNC) { sc->ep_connectors |= BNC; } if (config & IS_UTP) { sc->ep_connectors |= UTP; } if (!(sc->ep_connectors & 7)) printf("no connectors!"); sc->ep_connector = inw(BASE + EP_W0_ADDRESS_CFG) >> ACF_CONNECTOR_BITS; /* ROM size = 0, ROM base = 0 */ /* For now, ignore AUTO SELECT feature of 3C589B and later. */ outw(BASE + EP_W0_ADDRESS_CFG, get_e(sc, EEPROM_ADDR_CFG) & 0xc000); /* Fake IRQ must be 3 */ outw(BASE + EP_W0_RESOURCE_CFG, (sc->epb->res_cfg & 0x0fff) | 0x3000); outw(BASE + EP_W0_PRODUCT_ID, sc->epb->prod_id); ep_attach(sc); return 1; } static void ep_unload(dp) struct pccard_dev *dp; { struct ep_softc *sc = ep_softc[dp->isahd.id_unit]; if (sc->gone) { printf("ep%d: already unloaded\n", dp->isahd.id_unit); return; } sc->arpcom.ac_if.if_flags &= ~IFF_RUNNING; sc->gone = 1; printf("ep%d: unload\n", dp->isahd.id_unit); } /* * card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int card_intr(dp) struct pccard_dev *dp; { epintr(dp->isahd.id_unit); return(1); } #endif /* NCRD > 0 */ static int eeprom_rdy(sc) struct ep_softc *sc; { int i; for (i = 0; is_eeprom_busy(BASE) && i < MAX_EEPROMBUSY; i++); if (i >= MAX_EEPROMBUSY) { printf("ep%d: eeprom failed to come ready.\n", sc->unit); return (0); } return (1); } static struct ep_board * ep_look_for_board_at(is) struct isa_device *is; { int data, i, j, id_port = ELINK_ID_PORT; int count = 0; if (ep_current_tag == (EP_LAST_TAG + 1)) { /* Come here just one time */ ep_current_tag--; /* Look for the ISA boards. Init and leave them actived */ outb(id_port, 0); outb(id_port, 0); elink_idseq(0xCF); elink_reset(); DELAY(10000); for (i = 0; i < EP_MAX_BOARDS; i++) { outb(id_port, 0); outb(id_port, 0); elink_idseq(0xCF); data = get_eeprom_data(id_port, EEPROM_MFG_ID); if (data != MFG_ID) break; /* resolve contention using the Ethernet address */ for (j = 0; j < 3; j++) get_eeprom_data(id_port, j); /* and save this address for later use */ for (j = 0; j < 3; j++) ep_board[ep_boards].eth_addr[j] = get_eeprom_data(id_port, j); ep_board[ep_boards].res_cfg = get_eeprom_data(id_port, EEPROM_RESOURCE_CFG); ep_board[ep_boards].prod_id = get_eeprom_data(id_port, EEPROM_PROD_ID); ep_board[ep_boards].epb_used = 0; ep_board[ep_boards].epb_addr = (get_eeprom_data(id_port, EEPROM_ADDR_CFG) & 0x1f) * 0x10 + 0x200; if(ep_board[ep_boards].epb_addr > 0x3E0) /* Board in EISA configuration mode */ continue; outb(id_port, ep_current_tag); /* tags board */ outb(id_port, ACTIVATE_ADAPTER_TO_CONFIG); ep_boards++; count++; ep_current_tag--; } ep_board[ep_boards].epb_addr = 0; if (count) { printf("%d 3C5x9 board(s) on ISA found at", count); for (j = 0; ep_board[j].epb_addr; j++) if (ep_board[j].epb_addr <= 0x3E0) printf(" 0x%x", ep_board[j].epb_addr); printf("\n"); } } /* we have two cases: * * 1. Device was configured with 'port ?' * In this case we search for the first unused card in list * * 2. Device was configured with 'port xxx' * In this case we search for the unused card with that address * */ if(IS_BASE==-1) { /* port? */ for (i = 0; ep_board[i].epb_addr && ep_board[i].epb_used; i++); if(ep_board[i].epb_addr==0) return 0; IS_BASE=ep_board[i].epb_addr; ep_board[i].epb_used=1; return &ep_board[i]; } else { for (i=0; ep_board[i].epb_addr && ep_board[i].epb_addr != IS_BASE; i++); if( ep_board[i].epb_used || ep_board[i].epb_addr != IS_BASE) return 0; if (inw(IS_BASE + EP_W0_EEPROM_COMMAND) & EEPROM_TST_MODE) printf("ep%d: 3c5x9 at 0x%x in PnP mode. Disable PnP mode!\n", is->id_unit, IS_BASE); ep_board[i].epb_used=1; return &ep_board[i]; } } /* * get_e: gets a 16 bits word from the EEPROM. we must have set the window * before */ u_int16_t get_e(sc, offset) struct ep_softc *sc; int offset; { if (!eeprom_rdy(sc)) return (0xffff); outw(BASE + EP_W0_EEPROM_COMMAND, EEPROM_CMD_RD | offset); if (!eeprom_rdy(sc)) return (0xffff); return (inw(BASE + EP_W0_EEPROM_DATA)); } struct ep_softc * ep_alloc(unit, epb) int unit; struct ep_board *epb; { struct ep_softc *sc; if (unit >= NEP) { printf("ep: unit number (%d) too high\n", unit); return NULL; } /* * Allocate a storage area for us */ if (ep_softc[unit]) { printf("ep%d: unit number already allocated to another " "adaptor\n", unit); return NULL; } sc = malloc(sizeof(struct ep_softc), M_DEVBUF, M_NOWAIT); if(!sc) { printf("ep%d: cannot malloc!\n", unit); return NULL; } bzero(sc, sizeof(struct ep_softc)); ep_softc[unit] = sc; sc->unit = unit; sc->ep_io_addr = epb->epb_addr; sc->epb = epb; return(sc); } void ep_free(sc) struct ep_softc *sc; { ep_softc[sc->unit] = NULL; free(sc, M_DEVBUF); return; } int ep_isa_probe(is) struct isa_device *is; { struct ep_softc *sc; struct ep_board *epb; u_short k; #if NCRD > 0 pccard_add_driver(&ep_info); #endif /* NCRD > 0 */ if(( epb=ep_look_for_board_at(is) )==0) return (0); /* * Allocate a storage area for us */ sc = ep_alloc(ep_unit, epb); if( !sc ) return (0); is->id_unit = ep_unit++; /* * The iobase was found and MFG_ID was 0x6d50. PROD_ID should be * 0x9[0-f]50 */ GO_WINDOW(0); k = sc->epb->prod_id; if ((k & 0xf0ff) != (PROD_ID & 0xf0ff)) { printf("ep_isa_probe: ignoring model %04x\n", k); ep_free(sc); return (0); } k = sc->epb->res_cfg; k >>= 12; /* Now we have two cases again: * * 1. Device was configured with 'irq?' * In this case we use irq read from the board * * 2. Device was configured with 'irq xxx' * In this case we set up the board to use specified interrupt * */ if(is->id_irq==0) { /* irq? */ is->id_irq= 1 << ( (k==2) ? 9 : k ); } sc->stat = 0; /* 16 bit access */ /* By now, the adapter is already activated */ return (EP_IOSIZE); /* 16 bytes of I/O space used. */ } static int ep_isa_attach(is) struct isa_device *is; { struct ep_softc *sc = ep_softc[is->id_unit]; u_short config; int irq; sc->ep_connectors = 0; config = inw(IS_BASE + EP_W0_CONFIG_CTRL); if (config & IS_AUI) { sc->ep_connectors |= AUI; } if (config & IS_BNC) { sc->ep_connectors |= BNC; } if (config & IS_UTP) { sc->ep_connectors |= UTP; } if (!(sc->ep_connectors & 7)) printf("no connectors!"); sc->ep_connector = inw(BASE + EP_W0_ADDRESS_CFG) >> ACF_CONNECTOR_BITS; /* * Write IRQ value to board */ irq = ffs(is->id_irq) - 1; if(irq == -1) { printf(" invalid irq... cannot attach\n"); return 0; } GO_WINDOW(0); if(irq == 9) irq = 2; SET_IRQ(BASE, irq); ep_attach(sc); return 1; } int ep_attach(sc) struct ep_softc *sc; { struct ifaddr *ifa; struct ifnet *ifp = &sc->arpcom.ac_if; struct sockaddr_dl *sdl; u_short *p; int i; int attached; sc->gone = 0; attached = (ifp->if_softc != 0); printf("ep%d: ", sc->unit); /* * Current media type */ if(sc->ep_connectors & AUI) { printf("aui"); if(sc->ep_connectors & ~AUI) printf("/"); } if(sc->ep_connectors & UTP) { printf("utp"); if(sc->ep_connectors & BNC) printf("/"); } if(sc->ep_connectors & BNC) { printf("bnc"); } printf("[*%s*]", ep_conn_type[sc->ep_connector]); /* * Setup the station address */ p = (u_short *) & sc->arpcom.ac_enaddr; GO_WINDOW(2); for (i = 0; i < 3; i++) { p[i] = htons(sc->epb->eth_addr[i]); outw(BASE + EP_W2_ADDR_0 + (i * 2), ntohs(p[i])); } printf(" address %6D\n", sc->arpcom.ac_enaddr, ":"); ifp->if_softc = sc; ifp->if_unit = sc->unit; ifp->if_name = "ep"; ifp->if_mtu = ETHERMTU; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_output = ether_output; ifp->if_start = epstart; ifp->if_ioctl = epioctl; ifp->if_watchdog = epwatchdog; if (!attached) { if_attach(ifp); ether_ifattach(ifp); } - /* device attach does transition from UNCONFIGURED to IDLE state */ - - /* - * Fill the hardware address into ifa_addr if we find an AF_LINK entry. - * We need to do this so bpf's can get the hardware addr of this card. - * netstat likes this too! - */ - ifa = ifp->if_addrlist; - while ((ifa != 0) && (ifa->ifa_addr != 0) && - (ifa->ifa_addr->sa_family != AF_LINK)) - ifa = ifa->ifa_next; - - if ((ifa != 0) && (ifa->ifa_addr != 0)) { - sdl = (struct sockaddr_dl *) ifa->ifa_addr; - sdl->sdl_type = IFT_ETHER; - sdl->sdl_alen = ETHER_ADDR_LEN; - sdl->sdl_slen = 0; - bcopy(sc->arpcom.ac_enaddr, LLADDR(sdl), ETHER_ADDR_LEN); - } /* we give some initial parameters */ sc->rx_avg_pkt = 128; /* * NOTE: In all this I multiply everything by 64. * W_s = the speed the CPU is able to write to the TX FIFO. * T_s = the speed the board sends the info to the Ether. * W_s/T_s = 16 (represents 16/64) => W_s = 25 % of T_s. * This will give us for a packet of 1500 bytes * tx_start_thresh=1125 and for a pkt of 64 bytes tx_start_threshold=48. * We prefer to start thinking the CPU is much slower than the Ethernet * transmission. */ sc->tx_rate = TX_INIT_RATE; sc->tx_counter = 0; sc->rx_latency = RX_INIT_LATENCY; sc->rx_early_thresh = RX_INIT_EARLY_THRESH; #ifdef EP_LOCAL_STATS sc->rx_no_first = sc->rx_no_mbuf = sc->rx_bpf_disc = sc->rx_overrunf = sc->rx_overrunl = sc->tx_underrun = 0; #endif ep_fset(F_RX_FIRST); sc->top = sc->mcur = 0; #if NBPFILTER > 0 if (!attached) { bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); } #endif return 0; } /* * The order in here seems important. Otherwise we may not receive * interrupts. ?! */ static void epinit(sc) struct ep_softc *sc; { register struct ifnet *ifp = &sc->arpcom.ac_if; int s, i, j; if (sc->gone) return; /* if (ifp->if_addrlist == (struct ifaddr *) 0) return; */ s = splimp(); while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); GO_WINDOW(0); outw(BASE + EP_COMMAND, STOP_TRANSCEIVER); GO_WINDOW(4); outw(BASE + EP_W4_MEDIA_TYPE, DISABLE_UTP); GO_WINDOW(0); /* Disable the card */ outw(BASE + EP_W0_CONFIG_CTRL, 0); /* Enable the card */ outw(BASE + EP_W0_CONFIG_CTRL, ENABLE_DRQ_IRQ); GO_WINDOW(2); /* Reload the ether_addr. */ for (i = 0; i < 6; i++) outb(BASE + EP_W2_ADDR_0 + i, sc->arpcom.ac_enaddr[i]); outw(BASE + EP_COMMAND, RX_RESET); outw(BASE + EP_COMMAND, TX_RESET); /* Window 1 is operating window */ GO_WINDOW(1); for (i = 0; i < 31; i++) inb(BASE + EP_W1_TX_STATUS); /* get rid of stray intr's */ outw(BASE + EP_COMMAND, ACK_INTR | 0xff); outw(BASE + EP_COMMAND, SET_RD_0_MASK | S_5_INTS); outw(BASE + EP_COMMAND, SET_INTR_MASK | S_5_INTS); if(ifp->if_flags & IFF_PROMISC) outw(BASE + EP_COMMAND, SET_RX_FILTER | FIL_INDIVIDUAL | FIL_GROUP | FIL_BRDCST | FIL_ALL); else outw(BASE + EP_COMMAND, SET_RX_FILTER | FIL_INDIVIDUAL | FIL_GROUP | FIL_BRDCST); /* * S.B. * * Now behavior was slightly changed: * * if any of flags link[0-2] is used and its connector is * physically present the following connectors are used: * * link0 - AUI * highest precedence * link1 - BNC * link2 - UTP * lowest precedence * * If none of them is specified then * connector specified in the EEPROM is used * (if present on card or AUI if not). * */ /* Set the xcvr. */ if(ifp->if_flags & IFF_LINK0 && sc->ep_connectors & AUI) { i = ACF_CONNECTOR_AUI; } else if(ifp->if_flags & IFF_LINK1 && sc->ep_connectors & BNC) { i = ACF_CONNECTOR_BNC; } else if(ifp->if_flags & IFF_LINK2 && sc->ep_connectors & UTP) { i = ACF_CONNECTOR_UTP; } else { i = sc->ep_connector; } GO_WINDOW(0); j = inw(BASE + EP_W0_ADDRESS_CFG) & 0x3fff; outw(BASE + EP_W0_ADDRESS_CFG, j | (i << ACF_CONNECTOR_BITS)); switch(i) { case ACF_CONNECTOR_UTP: if(sc->ep_connectors & UTP) { GO_WINDOW(4); outw(BASE + EP_W4_MEDIA_TYPE, ENABLE_UTP); } break; case ACF_CONNECTOR_BNC: if(sc->ep_connectors & BNC) { outw(BASE + EP_COMMAND, START_TRANSCEIVER); DELAY(1000); } break; case ACF_CONNECTOR_AUI: /* nothing to do */ break; default: printf("ep%d: strange connector type in EEPROM: assuming AUI\n", sc->unit); break; } outw(BASE + EP_COMMAND, RX_ENABLE); outw(BASE + EP_COMMAND, TX_ENABLE); ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; /* just in case */ sc->tx_rate = TX_INIT_RATE; sc->tx_counter = 0; sc->rx_latency = RX_INIT_LATENCY; sc->rx_early_thresh = RX_INIT_EARLY_THRESH; #ifdef EP_LOCAL_STATS sc->rx_no_first = sc->rx_no_mbuf = sc->rx_bpf_disc = sc->rx_overrunf = sc->rx_overrunl = sc->tx_underrun = 0; #endif ep_fset(F_RX_FIRST); ep_frst(F_RX_TRAILER); if (sc->top) { m_freem(sc->top); sc->top = sc->mcur = 0; } outw(BASE + EP_COMMAND, SET_RX_EARLY_THRESH | sc->rx_early_thresh); /* * These clever computations look very interesting * but the fixed threshold gives near no output errors * and if it as low as 16 bytes it gives the max. throughput. * We think that processor is anyway quicker than Ethernet * (and this should be true for any 386 and higher) */ outw(BASE + EP_COMMAND, SET_TX_START_THRESH | 16); /* * Store up a bunch of mbuf's for use later. (MAX_MBS). First we free up * any that we had in case we're being called from intr or somewhere * else. */ sc->last_mb = 0; sc->next_mb = 0; epmbuffill((caddr_t) sc, 0); GO_WINDOW(1); epstart(ifp); splx(s); } static const char padmap[] = {0, 3, 2, 1}; static void epstart(ifp) struct ifnet *ifp; { register struct ep_softc *sc = ifp->if_softc; register u_int len; register struct mbuf *m; struct mbuf *top; int s, pad; if (sc->gone) { return; } s = splimp(); if (ifp->if_flags & IFF_OACTIVE) { splx(s); return; } startagain: /* Sneak a peek at the next packet */ m = ifp->if_snd.ifq_head; if (m == 0) { splx(s); return; } for (len = 0, top = m; m; m = m->m_next) len += m->m_len; pad = padmap[len & 3]; /* * The 3c509 automatically pads short packets to minimum ethernet length, * but we drop packets that are too large. Perhaps we should truncate * them instead? */ if (len + pad > ETHER_MAX_LEN) { /* packet is obviously too large: toss it */ ++ifp->if_oerrors; IF_DEQUEUE(&ifp->if_snd, m); m_freem(m); goto readcheck; } if (inw(BASE + EP_W1_FREE_TX) < len + pad + 4) { /* no room in FIFO */ outw(BASE + EP_COMMAND, SET_TX_AVAIL_THRESH | (len + pad + 4)); ifp->if_flags |= IFF_OACTIVE; splx(s); return; } IF_DEQUEUE(&ifp->if_snd, m); outw(BASE + EP_W1_TX_PIO_WR_1, len); outw(BASE + EP_W1_TX_PIO_WR_1, 0x0); /* Second dword meaningless */ /* compute the Tx start threshold for this packet */ sc->tx_start_thresh = len = (((len * (64 - sc->tx_rate)) >> 6) & ~3) + 16; #if 0 /* * The following string does something strange with the card and * we get a lot of output errors due to it so it's commented out * and we use fixed threshold (see above) */ outw(BASE + EP_COMMAND, SET_TX_START_THRESH | len); #endif for (top = m; m != 0; m = m->m_next) if(ep_ftst(F_ACCESS_32_BITS)) { outsl(BASE + EP_W1_TX_PIO_WR_1, mtod(m, caddr_t), m->m_len / 4); if (m->m_len & 3) outsb(BASE + EP_W1_TX_PIO_WR_1, mtod(m, caddr_t) + (m->m_len & (~3)), m->m_len & 3); } else { outsw(BASE + EP_W1_TX_PIO_WR_1, mtod(m, caddr_t), m->m_len / 2); if (m->m_len & 1) outb(BASE + EP_W1_TX_PIO_WR_1, *(mtod(m, caddr_t) + m->m_len - 1)); } while (pad--) outb(BASE + EP_W1_TX_PIO_WR_1, 0); /* Padding */ #if NBPFILTER > 0 if (ifp->if_bpf) { bpf_mtap(ifp, top); } #endif ifp->if_timer=2; ifp->if_opackets++; m_freem(top); /* * Every 1024*4 packets we increment the tx_rate if we haven't had * errors, that in the case it has abnormaly goten too low */ if (!(++sc->tx_counter & (1024 * 4 - 1)) && sc->tx_rate < TX_INIT_MAX_RATE) sc->tx_rate++; /* * Is another packet coming in? We don't want to overflow the tiny RX * fifo. */ readcheck: if (inw(BASE + EP_W1_RX_STATUS) & RX_BYTES_MASK) { /* * we check if we have packets left, in that case we prepare to come * back later */ if (ifp->if_snd.ifq_head) { outw(BASE + EP_COMMAND, SET_TX_AVAIL_THRESH | sc->tx_start_thresh); } splx(s); return; } goto startagain; } void epintr(unit) int unit; { register struct ep_softc *sc = ep_softc[unit]; if (sc->gone) { return; } ep_intr(sc); } void ep_intr(arg) void *arg; { struct ep_softc *sc; register int status; struct ifnet *ifp; int x; x=splbio(); sc = (struct ep_softc *)arg; ifp = &sc->arpcom.ac_if; outw(BASE + EP_COMMAND, SET_INTR_MASK); /* disable all Ints */ rescan: while ((status = inw(BASE + EP_STATUS)) & S_5_INTS) { /* first acknowledge all interrupt sources */ outw(BASE + EP_COMMAND, ACK_INTR | (status & S_MASK)); if (status & (S_RX_COMPLETE | S_RX_EARLY)) { epread(sc); continue; } if (status & S_TX_AVAIL) { /* we need ACK */ ifp->if_timer=0; ifp->if_flags &= ~IFF_OACTIVE; GO_WINDOW(1); inw(BASE + EP_W1_FREE_TX); epstart(ifp); } if (status & S_CARD_FAILURE) { ifp->if_timer=0; #ifdef EP_LOCAL_STATS printf("\nep%d:\n\tStatus: %x\n", sc->unit, status); GO_WINDOW(4); printf("\tFIFO Diagnostic: %x\n", inw(BASE + EP_W4_FIFO_DIAG)); printf("\tStat: %x\n", sc->stat); printf("\tIpackets=%d, Opackets=%d\n", ifp->if_ipackets, ifp->if_opackets); printf("\tNOF=%d, NOMB=%d, BPFD=%d, RXOF=%d, RXOL=%d, TXU=%d\n", sc->rx_no_first, sc->rx_no_mbuf, sc->rx_bpf_disc, sc->rx_overrunf, sc->rx_overrunl, sc->tx_underrun); #else #ifdef DIAGNOSTIC printf("ep%d: Status: %x (input buffer overflow)\n", sc->unit, status); #else ++ifp->if_ierrors; #endif #endif epinit(sc); splx(x); return; } if (status & S_TX_COMPLETE) { ifp->if_timer=0; /* we need ACK. we do it at the end */ /* * We need to read TX_STATUS until we get a 0 status in order to * turn off the interrupt flag. */ while ((status = inb(BASE + EP_W1_TX_STATUS)) & TXS_COMPLETE) { if (status & TXS_SUCCES_INTR_REQ); else if (status & (TXS_UNDERRUN | TXS_JABBER | TXS_MAX_COLLISION)) { outw(BASE + EP_COMMAND, TX_RESET); if (status & TXS_UNDERRUN) { if (sc->tx_rate > 1) { sc->tx_rate--; /* Actually in steps of 1/64 */ sc->tx_counter = 0; /* We reset it */ } #ifdef EP_LOCAL_STATS sc->tx_underrun++; #endif } else { if (status & TXS_JABBER); else /* TXS_MAX_COLLISION - we shouldn't get here */ ++ifp->if_collisions; } ++ifp->if_oerrors; outw(BASE + EP_COMMAND, TX_ENABLE); /* * To have a tx_avail_int but giving the chance to the * Reception */ if (ifp->if_snd.ifq_head) { outw(BASE + EP_COMMAND, SET_TX_AVAIL_THRESH | 8); } } outb(BASE + EP_W1_TX_STATUS, 0x0); /* pops up the next * status */ } /* while */ ifp->if_flags &= ~IFF_OACTIVE; GO_WINDOW(1); inw(BASE + EP_W1_FREE_TX); epstart(ifp); } /* end TX_COMPLETE */ } outw(BASE + EP_COMMAND, C_INTR_LATCH); /* ACK int Latch */ if ((status = inw(BASE + EP_STATUS)) & S_5_INTS) goto rescan; /* re-enable Ints */ outw(BASE + EP_COMMAND, SET_INTR_MASK | S_5_INTS); splx(x); } static void epread(sc) register struct ep_softc *sc; { struct ether_header *eh; struct mbuf *top, *mcur, *m; struct ifnet *ifp; int lenthisone; short rx_fifo2, status; register short delta; register short rx_fifo; ifp = &sc->arpcom.ac_if; status = inw(BASE + EP_W1_RX_STATUS); read_again: if (status & ERR_RX) { ++ifp->if_ierrors; if (status & ERR_RX_OVERRUN) { /* * we can think the rx latency is actually greather than we * expect */ #ifdef EP_LOCAL_STATS if (ep_ftst(F_RX_FIRST)) sc->rx_overrunf++; else sc->rx_overrunl++; #endif if (sc->rx_latency < ETHERMTU) sc->rx_latency += 16; } goto out; } rx_fifo = rx_fifo2 = status & RX_BYTES_MASK; if (ep_ftst(F_RX_FIRST)) { if (m = sc->mb[sc->next_mb]) { sc->mb[sc->next_mb] = 0; sc->next_mb = (sc->next_mb + 1) % MAX_MBS; m->m_data = m->m_pktdat; m->m_flags = M_PKTHDR; } else { MGETHDR(m, M_DONTWAIT, MT_DATA); if (!m) goto out; } sc->top = sc->mcur = top = m; #define EROUND ((sizeof(struct ether_header) + 3) & ~3) #define EOFF (EROUND - sizeof(struct ether_header)) top->m_data += EOFF; /* Read what should be the header. */ insw(BASE + EP_W1_RX_PIO_RD_1, mtod(top, caddr_t), sizeof(struct ether_header) / 2); top->m_len = sizeof(struct ether_header); rx_fifo -= sizeof(struct ether_header); sc->cur_len = rx_fifo2; } else { /* come here if we didn't have a complete packet last time */ top = sc->top; m = sc->mcur; sc->cur_len += rx_fifo2; if (ep_ftst(F_RX_TRAILER)) /* We don't read the trailer */ rx_fifo -= sizeof(struct ether_header); } /* Reads what is left in the RX FIFO */ while (rx_fifo > 0) { lenthisone = min(rx_fifo, M_TRAILINGSPACE(m)); if (lenthisone == 0) { /* no room in this one */ mcur = m; if (m = sc->mb[sc->next_mb]) { sc->mb[sc->next_mb] = 0; sc->next_mb = (sc->next_mb + 1) % MAX_MBS; } else { MGET(m, M_DONTWAIT, MT_DATA); if (!m) goto out; } if (rx_fifo >= MINCLSIZE) MCLGET(m, M_DONTWAIT); m->m_len = 0; mcur->m_next = m; lenthisone = min(rx_fifo, M_TRAILINGSPACE(m)); } if (ep_ftst(F_ACCESS_32_BITS)) { /* default for EISA configured cards*/ insl(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t) + m->m_len, lenthisone / 4); m->m_len += (lenthisone & ~3); if (lenthisone & 3) insb(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t) + m->m_len, lenthisone & 3); m->m_len += (lenthisone & 3); } else { insw(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t) + m->m_len, lenthisone / 2); m->m_len += lenthisone; if (lenthisone & 1) *(mtod(m, caddr_t) + m->m_len - 1) = inb(BASE + EP_W1_RX_PIO_RD_1); } rx_fifo -= lenthisone; } if (ep_ftst(F_RX_TRAILER)) {/* reads the trailer */ if (m = sc->mb[sc->next_mb]) { sc->mb[sc->next_mb] = 0; sc->next_mb = (sc->next_mb + 1) % MAX_MBS; m->m_data = m->m_pktdat; m->m_flags = M_PKTHDR; } else { MGETHDR(m, M_DONTWAIT, MT_DATA); if (!m) goto out; } insw(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t), sizeof(struct ether_header)); m->m_len = sizeof(struct ether_header); m->m_next = top; sc->top = top = m; /* XXX Accomodate for type and len from beginning of trailer */ sc->cur_len -= (2 * sizeof(u_short)); ep_frst(F_RX_TRAILER); goto all_pkt; } if (status & ERR_RX_INCOMPLETE) { /* we haven't received the complete * packet */ sc->mcur = m; #ifdef EP_LOCAL_STATS sc->rx_no_first++; /* to know how often we come here */ #endif /* * Re-compute rx_latency, the factor used is 1/4 to go up and 1/32 to * go down */ delta = rx_fifo2 - sc->rx_early_thresh; /* last latency seen LLS */ delta -= sc->rx_latency;/* LLS - estimated_latency */ if (delta >= 0) sc->rx_latency += (delta / 4); else sc->rx_latency += (delta / 32); ep_frst(F_RX_FIRST); if (!((status = inw(BASE + EP_W1_RX_STATUS)) & ERR_RX_INCOMPLETE)) { /* we see if by now, the packet has completly arrived */ goto read_again; } /* compute rx_early_threshold */ delta = (sc->rx_avg_pkt - sc->cur_len - sc->rx_latency - 16) & ~3; if (delta < MIN_RX_EARLY_THRESHL) delta = MIN_RX_EARLY_THRESHL; outw(BASE + EP_COMMAND, SET_RX_EARLY_THRESH | (sc->rx_early_thresh = delta)); return; } all_pkt: outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); /* * recompute average packet's length, the factor used is 1/8 to go down * and 1/32 to go up */ delta = sc->cur_len - sc->rx_avg_pkt; if (delta > 0) sc->rx_avg_pkt += (delta / 32); else sc->rx_avg_pkt += (delta / 8); delta = (sc->rx_avg_pkt - sc->rx_latency - 16) & ~3; if (delta < MIN_RX_EARLY_THRESHF) delta = MIN_RX_EARLY_THRESHF; sc->rx_early_thresh = delta; ++ifp->if_ipackets; ep_fset(F_RX_FIRST); ep_frst(F_RX_TRAILER); top->m_pkthdr.rcvif = &sc->arpcom.ac_if; top->m_pkthdr.len = sc->cur_len; #if NBPFILTER > 0 if (ifp->if_bpf) { bpf_mtap(ifp, top); /* * Note that the interface cannot be in promiscuous mode if there are * no BPF listeners. And if we are in promiscuous mode, we have to * check if this packet is really ours. */ eh = mtod(top, struct ether_header *); if ((ifp->if_flags & IFF_PROMISC) && (eh->ether_dhost[0] & 1) == 0 && bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, sizeof(eh->ether_dhost)) != 0 && bcmp(eh->ether_dhost, etherbroadcastaddr, sizeof(eh->ether_dhost)) != 0) { if (sc->top) { m_freem(sc->top); sc->top = 0; } ep_fset(F_RX_FIRST); ep_frst(F_RX_TRAILER); #ifdef EP_LOCAL_STATS sc->rx_bpf_disc++; #endif while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); outw(BASE + EP_COMMAND, SET_RX_EARLY_THRESH | delta); return; } } #endif eh = mtod(top, struct ether_header *); m_adj(top, sizeof(struct ether_header)); ether_input(ifp, eh, top); if (!sc->mb[sc->next_mb]) epmbuffill((caddr_t) sc, 0); sc->top = 0; while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); outw(BASE + EP_COMMAND, SET_RX_EARLY_THRESH | delta); return; out: outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); if (sc->top) { m_freem(sc->top); sc->top = 0; #ifdef EP_LOCAL_STATS sc->rx_no_mbuf++; #endif } delta = (sc->rx_avg_pkt - sc->rx_latency - 16) & ~3; if (delta < MIN_RX_EARLY_THRESHF) delta = MIN_RX_EARLY_THRESHF; ep_fset(F_RX_FIRST); ep_frst(F_RX_TRAILER); while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); outw(BASE + EP_COMMAND, SET_RX_EARLY_THRESH | (sc->rx_early_thresh = delta)); } /* * Look familiar? */ static int epioctl(ifp, cmd, data) register struct ifnet *ifp; int cmd; caddr_t data; { register struct ifaddr *ifa = (struct ifaddr *) data; struct ep_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; int s, error = 0; s = splimp(); switch (cmd) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; /* netifs are BUSY when UP */ switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: epinit(sc); /* before arpwhohas */ arp_ifinit((struct arpcom *)ifp, ifa); break; #endif #ifdef IPX case AF_IPX: { register struct ipx_addr *ina = &(IA_SIPX(ifa)->sipx_addr); if (ipx_nullhost(*ina)) ina->x_host = *(union ipx_host *) (sc->arpcom.ac_enaddr); else { ifp->if_flags &= ~IFF_RUNNING; bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->arpcom.ac_enaddr, sizeof(sc->arpcom.ac_enaddr)); } epinit(sc); break; } #endif #ifdef NS case AF_NS: { register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); if (ns_nullhost(*ina)) ina->x_host = *(union ns_host *) (sc->arpcom.ac_enaddr); else { ifp->if_flags &= ~IFF_RUNNING; bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->arpcom.ac_enaddr, sizeof(sc->arpcom.ac_enaddr)); } epinit(sc); break; } #endif default: epinit(sc); break; } break; case SIOCGIFADDR: { struct sockaddr *sa; sa = (struct sockaddr *) & ifr->ifr_data; bcopy((caddr_t) sc->arpcom.ac_enaddr, (caddr_t) sa->sa_data, ETHER_ADDR_LEN); } break; case SIOCSIFFLAGS: if ((ifp->if_flags & IFF_UP) == 0 && ifp->if_flags & IFF_RUNNING) { ifp->if_flags &= ~IFF_RUNNING; epstop(sc); epmbufempty(sc); break; } else { /* reinitialize card on any parameter change */ epinit(sc); break; } /* NOTREACHED */ break; #ifdef notdef case SIOCGHWADDR: bcopy((caddr_t) sc->sc_addr, (caddr_t) & ifr->ifr_data, sizeof(sc->sc_addr)); break; #endif case SIOCSIFMTU: /* * Set the interface MTU. */ if (ifr->ifr_mtu > ETHERMTU) { error = EINVAL; } else { ifp->if_mtu = ifr->ifr_mtu; } break; case SIOCADDMULTI: case SIOCDELMULTI: /* Now this driver has no support for programmable * multicast filters. If some day it will gain this * support this part of code must be extended. */ error=0; break; default: error = EINVAL; } splx(s); return (error); } static void epwatchdog(ifp) struct ifnet *ifp; { struct ep_softc *sc = ifp->if_softc; /* printf("ep: watchdog\n"); log(LOG_ERR, "ep%d: watchdog\n", ifp->if_unit); ifp->if_oerrors++; */ if (sc->gone) { return; } ifp->if_flags &= ~IFF_OACTIVE; epstart(ifp); ep_intr(ifp->if_softc); } static void epstop(sc) struct ep_softc *sc; { if (sc->gone) { return; } outw(BASE + EP_COMMAND, RX_DISABLE); outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); outw(BASE + EP_COMMAND, TX_DISABLE); outw(BASE + EP_COMMAND, STOP_TRANSCEIVER); outw(BASE + EP_COMMAND, RX_RESET); outw(BASE + EP_COMMAND, TX_RESET); outw(BASE + EP_COMMAND, C_INTR_LATCH); outw(BASE + EP_COMMAND, SET_RD_0_MASK); outw(BASE + EP_COMMAND, SET_INTR_MASK); outw(BASE + EP_COMMAND, SET_RX_FILTER); } #if 0 static int send_ID_sequence(port) int port; { int cx, al; for (al = 0xff, cx = 0; cx < 255; cx++) { outb(port, al); al <<= 1; if (al & 0x100) al ^= 0xcf; } return (1); } #endif /* * We get eeprom data from the id_port given an offset into the eeprom. * Basically; after the ID_sequence is sent to all of the cards; they enter * the ID_CMD state where they will accept command requests. 0x80-0xbf loads * the eeprom data. We then read the port 16 times and with every read; the * cards check for contention (ie: if one card writes a 0 bit and another * writes a 1 bit then the host sees a 0. At the end of the cycle; each card * compares the data on the bus; if there is a difference then that card goes * into ID_WAIT state again). In the meantime; one bit of data is returned in * the AX register which is conveniently returned to us by inb(). Hence; we * read 16 times getting one bit of data with each read. */ static int get_eeprom_data(id_port, offset) int id_port; int offset; { int i, data = 0; outb(id_port, 0x80 + offset); DELAY(1000); for (i = 0; i < 16; i++) data = (data << 1) | (inw(id_port) & 1); return (data); } /* * We suppose this is always called inside a splimp(){...}splx() region */ static void epmbuffill(sp, dummy_arg) caddr_t sp; int dummy_arg; { struct ep_softc *sc = (struct ep_softc *) sp; int i; i = sc->last_mb; do { if (sc->mb[i] == NULL) MGET(sc->mb[i], M_DONTWAIT, MT_DATA); if (sc->mb[i] == NULL) break; i = (i + 1) % MAX_MBS; } while (i != sc->next_mb); sc->last_mb = i; } static void epmbufempty(sc) struct ep_softc *sc; { int s, i; s = splimp(); for (i = 0; i < MAX_MBS; i++) { if (sc->mb[i]) { m_freem(sc->mb[i]); sc->mb[i] = NULL; } } sc->last_mb = sc->next_mb = 0; splx(s); } #endif /* NEP > 0 */ diff --git a/sys/i386/isa/if_fe.c b/sys/i386/isa/if_fe.c index a8fdc3cd213a..9c2cf8e33c9f 100644 --- a/sys/i386/isa/if_fe.c +++ b/sys/i386/isa/if_fe.c @@ -1,3148 +1,3148 @@ /* * All Rights Reserved, Copyright (C) Fujitsu Limited 1995 * * This software may be used, modified, copied, distributed, and sold, in * both source and binary form provided that the above copyright, these * terms and the following disclaimer are retained. The name of the author * and/or the contributor may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND THE CONTRIBUTOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR THE CONTRIBUTOR BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* - * $Id: if_fe.c,v 1.20 1996/10/07 17:50:00 wollman Exp $ + * $Id: if_fe.c,v 1.21 1996/11/15 16:15:56 wollman Exp $ * * Device driver for Fujitsu MB86960A/MB86965A based Ethernet cards. * To be used with FreeBSD 2.x * Contributed by M. Sekiguchi. * * This version is intended to be a generic template for various * MB86960A/MB86965A based Ethernet cards. It currently supports * Fujitsu FMV-180 series for ISA and Allied-Telesis AT1700/RE2000 * series for ISA, as well as Fujitsu MBH10302 PC card. * There are some currently- * unused hooks embedded, which are primarily intended to support * other types of Ethernet cards, but the author is not sure whether * they are useful. * * This version also includes some alignments for * RE1000/RE1000+/ME1500 support. It is incomplete, however, since the * cards are not for AT-compatibles. (They are for PC98 bus -- a * proprietary bus architecture available only in Japan.) Further * work for PC98 version will be available as a part of FreeBSD(98) * project. * * This software is a derivative work of if_ed.c version 1.56 by David * Greenman available as a part of FreeBSD 2.0 RELEASE source distribution. * * The following lines are retained from the original if_ed.c: * * Copyright (C) 1993, David Greenman. This software may be used, modified, * copied, distributed, and sold, in both source and binary form provided * that the above copyright and these terms are retained. Under no * circumstances is the author responsible for the proper functioning * of this software, nor does the author assume any responsibility * for damages incurred with its use. */ /* * TODO: * o To support MBH10304 PC card. It is another MB8696x based * PCMCIA Ethernet card by Fujitsu, which is not compatible with * MBH10302. * o To merge FreeBSD(98) efforts into a single source file. * o To support ISA PnP auto configuration for FMV-183/184. * o To reconsider mbuf usage. * o To reconsider transmission buffer usage, including * transmission buffer size (currently 4KB x 2) and pros-and- * cons of multiple frame transmission. * o To test IPX codes. */ #include "isa.h" #include "fe.h" #include "crd.h" #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif /* IPX code is not tested. FIXME. */ #ifdef IPX #include #include #endif /* To be used with IPv6 package of INRIA. */ #ifdef INET6 /* IPv6 added by shin 96.2.6 */ #include #endif /* XNS code is not tested. FIXME. */ #ifdef NS #include #include #endif #if NBPFILTER > 0 #include #include #endif #include #include #include #include /* PCCARD suport */ #if NCRD > 0 #include #include #include #include #endif #include #include /* * This version of fe is an ISA device driver. * Override the following macro to adapt it to another bus. * (E.g., PC98.) */ #define DEVICE struct isa_device /* * Default settings for fe driver specific options. * They can be set in config file by "options" statements. */ /* * Debug control. * 0: No debug at all. All debug specific codes are stripped off. * 1: Silent. No debug messages are logged except emergent ones. * 2: Brief. Lair events and/or important information are logged. * 3: Detailed. Logs all information which *may* be useful for debugging. * 4: Trace. All actions in the driver is logged. Super verbose. */ #ifndef FE_DEBUG #define FE_DEBUG 1 #endif /* * Transmit just one packet per a "send" command to 86960. * This option is intended for performance test. An EXPERIMENTAL option. */ #ifndef FE_SINGLE_TRANSMISSION #define FE_SINGLE_TRANSMISSION 0 #endif /* * Device configuration flags. */ /* DLCR6 settings. */ #define FE_FLAGS_DLCR6_VALUE 0x007F /* Force DLCR6 override. */ #define FE_FLAGS_OVERRIDE_DLCR6 0x0080 /* Shouldn't these be defined somewhere else such as isa_device.h? */ #define NO_IOADDR (-1) #define NO_IRQ 0 /* * Data type for a multicast address filter on 8696x. */ struct fe_filter { u_char data [ FE_FILTER_LEN ]; }; /* * Special filter values. */ static struct fe_filter const fe_filter_nothing = { FE_FILTER_NOTHING }; static struct fe_filter const fe_filter_all = { FE_FILTER_ALL }; /* How many registers does an fe-supported adapter have at maximum? */ #define MAXREGISTERS 32 /* * fe_softc: per line info and status */ static struct fe_softc { /* Used by "common" codes. */ struct arpcom arpcom; /* Ethernet common */ /* Used by config codes. */ /* Set by probe() and not modified in later phases. */ char * typestr; /* printable name of the interface. */ u_short iobase; /* base I/O address of the adapter. */ u_short ioaddr [ MAXREGISTERS ]; /* I/O addresses of register. */ u_short txb_size; /* size of TX buffer, in bytes */ u_char proto_dlcr4; /* DLCR4 prototype. */ u_char proto_dlcr5; /* DLCR5 prototype. */ u_char proto_dlcr6; /* DLCR6 prototype. */ u_char proto_dlcr7; /* DLCR7 prototype. */ u_char proto_bmpr13; /* BMPR13 prototype. */ /* Vendor specific hooks. */ void ( * init )( struct fe_softc * ); /* Just before fe_init(). */ void ( * stop )( struct fe_softc * ); /* Just after fe_stop(). */ /* Transmission buffer management. */ u_short txb_free; /* free bytes in TX buffer */ u_char txb_count; /* number of packets in TX buffer */ u_char txb_sched; /* number of scheduled packets */ /* Excessive collision counter (see fe_tint() for details. */ u_char tx_excolls; /* # of excessive collisions. */ /* Multicast address filter management. */ u_char filter_change; /* MARs must be changed ASAP. */ struct fe_filter filter;/* new filter value. */ } fe_softc[NFE]; #define sc_if arpcom.ac_if #define sc_unit arpcom.ac_if.if_unit #define sc_enaddr arpcom.ac_enaddr /* Standard driver entry points. These can be static. */ static int fe_probe ( struct isa_device * ); static int fe_attach ( struct isa_device * ); static void fe_init ( int ); static int fe_ioctl ( struct ifnet *, int, caddr_t ); static void fe_start ( struct ifnet * ); static void fe_reset ( int ); static void fe_watchdog ( struct ifnet * ); /* Local functions. Order of declaration is confused. FIXME. */ static int fe_probe_fmv ( DEVICE *, struct fe_softc * ); static int fe_probe_ati ( DEVICE *, struct fe_softc * ); static void fe_init_ati ( struct fe_softc * ); static int fe_probe_gwy ( DEVICE *, struct fe_softc * ); #if NCRD > 0 static int fe_probe_mbh ( DEVICE *, struct fe_softc * ); static void fe_init_mbh ( struct fe_softc * ); static int fe_probe_tdk ( DEVICE *, struct fe_softc * ); #endif static int fe_get_packet ( struct fe_softc *, u_short ); static void fe_stop ( int ); static void fe_tint ( struct fe_softc *, u_char ); static void fe_rint ( struct fe_softc *, u_char ); static void fe_xmit ( struct fe_softc * ); static void fe_emptybuffer ( struct fe_softc * ); static void fe_write_mbufs ( struct fe_softc *, struct mbuf * ); static struct fe_filter fe_mcaf ( struct fe_softc * ); static int fe_hash ( u_char * ); static void fe_setmode ( struct fe_softc * ); static void fe_loadmar ( struct fe_softc * ); #if FE_DEBUG >= 1 static void fe_dump ( int, struct fe_softc *, char * ); #endif /* Driver struct used in the config code. This must be public (external.) */ struct isa_driver fedriver = { fe_probe, fe_attach, "fe", 1 /* It's safe to mark as "sensitive" */ }; /* * Fe driver specific constants which relate to 86960/86965. */ /* Interrupt masks */ #define FE_TMASK ( FE_D2_COLL16 | FE_D2_TXDONE ) #define FE_RMASK ( FE_D3_OVRFLO | FE_D3_CRCERR \ | FE_D3_ALGERR | FE_D3_SRTPKT | FE_D3_PKTRDY ) /* Maximum number of iterations for a receive interrupt. */ #define FE_MAX_RECV_COUNT ( ( 65536 - 2048 * 2 ) / 64 ) /* * Maximum size of SRAM is 65536, * minimum size of transmission buffer in fe is 2x2KB, * and minimum amount of received packet including headers * added by the chip is 64 bytes. * Hence FE_MAX_RECV_COUNT is the upper limit for number * of packets in the receive buffer. */ /* * Routines to access contiguous I/O ports. */ static void inblk ( struct fe_softc * sc, int offs, u_char * mem, int len ) { while ( --len >= 0 ) { *mem++ = inb( sc->ioaddr[ offs++ ] ); } } static void outblk ( struct fe_softc * sc, int offs, u_char const * mem, int len ) { while ( --len >= 0 ) { outb( sc->ioaddr[ offs++ ], *mem++ ); } } /* PCCARD Support */ #if NCRD > 0 /* * PC-Card (PCMCIA) specific code. */ static int fe_card_intr(struct pccard_dev *); /* Interrupt handler */ static void feunload(struct pccard_dev *); /* Disable driver */ static void fesuspend(struct pccard_dev *); /* Suspend driver */ static int feinit(struct pccard_dev *, int); /* init device */ static struct pccard_drv fe_info = { "fe", fe_card_intr, feunload, fesuspend, feinit, 0, /* Attributes - presently unused */ &net_imask /* Interrupt mask for device */ /* XXX - Should this also include net_imask? */ }; /* * Called when a power down is requested. Shuts down the * device and configures the device as unavailable (but * still loaded...). A resume is done by calling * feinit with first=0. This is called when the user suspends * the system, or the APM code suspends the system. */ static void fesuspend(struct pccard_dev *dp) { printf("fe%d: suspending\n", dp->isahd.id_unit); } /* * Initialize the device - called from Slot manager. * if first is set, then initially check for * the device's existence before initializing it. * Once initialized, the device table may be set up. */ static int feinit(struct pccard_dev *dp, int first) { /* validate unit number. */ struct fe_softc *sc; if (first) { if (dp->isahd.id_unit >= NFE) return (ENODEV); /* * Probe the device. If a value is returned, * the device was found at the location. */ #if FE_DEBUG >= 2 printf("Start Probe\n"); #endif sc = &fe_softc[dp->isahd.id_unit]; memcpy( sc->sc_enaddr, dp->misc, ETHER_ADDR_LEN ); if (fe_probe(&dp->isahd) == 0) return (ENXIO); #if FE_DEBUG >= 2 printf("Start attach\n"); #endif if (fe_attach(&dp->isahd) == 0) return (ENXIO); } /* * XXX TODO: * If it was initialized before, the device structure * should also be initialized. We should * reset (and possibly restart) the hardware, but * I am not sure of the best way to do this... */ return (0); } /* * feunload - unload the driver and clear the table. * XXX TODO: * This is usually called when the card is ejected, but * can be caused by a modunload of a controller driver. * The idea is to reset the driver's view of the device * and ensure that any driver entry points such as * read and write do not hang. */ static void feunload(struct pccard_dev *dp) { struct fe_softc *sc = &fe_softc[dp->isahd.id_unit]; printf("fe%d: unload\n", dp->isahd.id_unit); fe_stop(dp->isahd.id_unit); } /* * fe_card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int fe_card_intr(struct pccard_dev *dp) { feintr(dp->isahd.id_unit); return (1); } #endif /* NCRD > 0 */ /* * Hardware probe routines. */ /* How and where to probe; to support automatic I/O address detection. */ struct fe_probe_list { int ( * probe ) ( DEVICE *, struct fe_softc * ); u_short const * addresses; }; /* Lists of possible addresses. */ static u_short const fe_fmv_addr [] = { 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x300, 0x340, 0 }; static u_short const fe_ati_addr [] = { 0x240, 0x260, 0x280, 0x2A0, 0x300, 0x320, 0x340, 0x380, 0 }; static struct fe_probe_list const fe_probe_list [] = { { fe_probe_fmv, fe_fmv_addr }, { fe_probe_ati, fe_ati_addr }, #if NCRD > 0 { fe_probe_mbh, NULL }, /* PCMCIAs cannot be auto-detected. */ { fe_probe_tdk, NULL }, #endif { NULL, NULL } }; /* * Determine if the device is present * * on entry: * a pointer to an isa_device struct * on exit: * zero if device not found * or number of i/o addresses used (if found) */ static int fe_probe ( DEVICE * dev ) { #if NCRD > 0 static int fe_already_init; #endif struct fe_softc * sc; int u; int nports; struct fe_probe_list const * list; u_short const * addr; u_short single [ 2 ]; /* Initialize "minimum" parts of our softc. */ sc = &fe_softc[ dev->id_unit ]; sc->sc_unit = dev->id_unit; #if NCRD > 0 /* * If PC-Card probe required, then register driver with * slot manager. */ if (fe_already_init != 1) { pccard_add_driver(&fe_info); fe_already_init = 1; } #endif /* NCRD > 0 */ /* Probe each possibility, one at a time. */ for ( list = fe_probe_list; list->probe != NULL; list++ ) { if ( dev->id_iobase != NO_IOADDR ) { /* Probe one specific address. */ single[ 0 ] = dev->id_iobase; single[ 1 ] = 0; addr = single; } else if ( list->addresses != NULL ) { /* Auto detect. */ addr = list->addresses; } else { /* We need a list of addresses to do auto detect. */ continue; } /* Probe all possible addresses for the board. */ while ( *addr != 0 ) { /* See if the address is already in use. */ for ( u = 0; u < NFE; u++ ) { if ( fe_softc[u].iobase == *addr ) break; } #if FE_DEBUG >= 3 if ( u == NFE ) { log( LOG_INFO, "fe%d: probing %d at 0x%x\n", sc->sc_unit, list - fe_probe_list, *addr ); } else if ( u == sc->sc_unit ) { log( LOG_INFO, "fe%d: re-probing %d at 0x%x?\n", sc->sc_unit, list - fe_probe_list, *addr ); } else { log( LOG_INFO, "fe%d: skipping %d at 0x%x\n", sc->sc_unit, list - fe_probe_list, *addr ); } #endif /* Probe the address if it is free. */ if ( u == NFE || u == sc->sc_unit ) { /* Probe an address. */ sc->iobase = *addr; nports = list->probe( dev, sc ); if ( nports > 0 ) { /* Found. */ dev->id_iobase = *addr; return ( nports ); } sc->iobase = 0; } /* Try next. */ addr++; } } /* Probe failed. */ return ( 0 ); } /* * Check for specific bits in specific registers have specific values. */ struct fe_simple_probe_struct { u_char port; /* Offset from the base I/O address. */ u_char mask; /* Bits to be checked. */ u_char bits; /* Values to be compared against. */ }; static int fe_simple_probe ( struct fe_softc const * sc, struct fe_simple_probe_struct const * sp ) { struct fe_simple_probe_struct const * p; for ( p = sp; p->mask != 0; p++ ) { #if FE_DEBUG >=2 printf("Probe Port:%x,Value:%x,Mask:%x.Bits:%x\n", p->port,inb(sc->ioaddr[ p->port]),p->mask,p->bits); #endif if ( ( inb( sc->ioaddr[ p->port ] ) & p->mask ) != p->bits ) { return ( 0 ); } } return ( 1 ); } /* * Routines to read all bytes from the config EEPROM through MB86965A. * I'm not sure what exactly I'm doing here... I was told just to follow * the steps, and it worked. Could someone tell me why the following * code works? (Or, why all similar codes I tried previously doesn't * work.) FIXME. */ static void fe_strobe_eeprom ( u_short bmpr16 ) { /* * We must guarantee 800ns (or more) interval to access slow * EEPROMs. The following redundant code provides enough * delay with ISA timing. (Even if the bus clock is "tuned.") * Some modification will be needed on faster busses. */ outb( bmpr16, FE_B16_SELECT ); outb( bmpr16, FE_B16_SELECT ); outb( bmpr16, FE_B16_SELECT | FE_B16_CLOCK ); outb( bmpr16, FE_B16_SELECT | FE_B16_CLOCK ); outb( bmpr16, FE_B16_SELECT ); outb( bmpr16, FE_B16_SELECT ); } static void fe_read_eeprom ( struct fe_softc * sc, u_char * data ) { u_short bmpr16 = sc->ioaddr[ FE_BMPR16 ]; u_short bmpr17 = sc->ioaddr[ FE_BMPR17 ]; u_char n, val, bit; /* Read bytes from EEPROM; two bytes per an iteration. */ for ( n = 0; n < FE_EEPROM_SIZE / 2; n++ ) { /* Reset the EEPROM interface. */ outb( bmpr16, 0x00 ); outb( bmpr17, 0x00 ); /* Start EEPROM access. */ outb( bmpr16, FE_B16_SELECT ); outb( bmpr17, FE_B17_DATA ); fe_strobe_eeprom( bmpr16 ); /* Pass the iteration count to the chip. */ val = 0x80 | n; for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { outb( bmpr17, ( val & bit ) ? FE_B17_DATA : 0 ); fe_strobe_eeprom( bmpr16 ); } outb( bmpr17, 0x00 ); /* Read a byte. */ val = 0; for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { fe_strobe_eeprom( bmpr16 ); if ( inb( bmpr17 ) & FE_B17_DATA ) { val |= bit; } } *data++ = val; /* Read one more byte. */ val = 0; for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { fe_strobe_eeprom( bmpr16 ); if ( inb( bmpr17 ) & FE_B17_DATA ) { val |= bit; } } *data++ = val; } /* Reset the EEPROM interface, again. */ outb( bmpr16, 0x00 ); outb( bmpr17, 0x00 ); #if FE_DEBUG >= 3 /* Report what we got. */ data -= FE_EEPROM_SIZE; log( LOG_INFO, "fe%d: EEPROM:" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x\n", sc->sc_unit, data[ 0], data[ 1], data[ 2], data[ 3], data[ 4], data[ 5], data[ 6], data[ 7], data[ 8], data[ 9], data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23], data[24], data[25], data[26], data[27], data[28], data[29], data[30], data[31] ); #endif } /* * Hardware (vendor) specific probe routines. */ /* * Probe and initialization for Fujitsu FMV-180 series boards */ static int fe_probe_fmv ( DEVICE * dev, struct fe_softc * sc ) { int i, n; static u_short const baseaddr [ 8 ] = { 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x300, 0x340 }; static u_short const irqmap [ 4 ] = { IRQ3, IRQ7, IRQ10, IRQ15 }; static struct fe_simple_probe_struct const probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, /* { FE_DLCR5, 0x80, 0x00 }, Doesn't work. */ { FE_FMV0, 0x78, 0x50 }, /* ERRDY+PRRDY */ { FE_FMV1, 0xB0, 0x00 }, /* FMV-183/184 has 0x48 bits. */ { FE_FMV3, 0x7F, 0x00 }, #if 1 /* * Test *vendor* part of the station address for Fujitsu. * The test will gain reliability of probe process, but * it rejects FMV-180 clone boards manufactured by other vendors. * We have to turn the test off when such cards are made available. */ { FE_FMV4, 0xFF, 0x00 }, { FE_FMV5, 0xFF, 0x00 }, { FE_FMV6, 0xFF, 0x0E }, #else /* * We can always verify the *first* 2 bits (in Ethernet * bit order) are "no multicast" and "no local" even for * unknown vendors. */ { FE_FMV4, 0x03, 0x00 }, #endif { 0 } }; /* "Hardware revision ID" */ int revision; /* * See if the specified address is possible for FMV-180 series. */ for ( i = 0; i < 8; i++ ) { if ( baseaddr[ i ] == sc->iobase ) break; } if ( i == 8 ) return 0; /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* Simple probe. */ if ( !fe_simple_probe( sc, probe_table ) ) return 0; /* Check if our I/O address matches config info. on EEPROM. */ n = ( inb( sc->ioaddr[ FE_FMV2 ] ) & FE_FMV2_IOS ) >> FE_FMV2_IOS_SHIFT; if ( baseaddr[ n ] != sc->iobase ) { #if 0 /* May not work on some revisions of the cards... FIXME. */ return 0; #else /* Just log the fact and see what happens... FIXME. */ log( LOG_WARNING, "fe%d: strange I/O config?n", sc->sc_unit ); #endif } /* Find the "hardware revision." */ revision = inb( sc->ioaddr[ FE_FMV1 ] ) & FE_FMV1_REV; /* Determine the card type. */ sc->typestr = NULL; switch ( inb( sc->ioaddr[ FE_FMV0 ] ) & FE_FMV0_MEDIA ) { case 0: /* No interface? This doesn't seem to be an FMV-180... */ return 0; case FE_FMV0_MEDIUM_T: switch ( revision ) { case 8: sc->typestr = "FMV-183"; break; case 12: sc->typestr = "FMV-183 (on-board)"; break; } break; case FE_FMV0_MEDIUM_T | FE_FMV0_MEDIUM_5: switch ( revision ) { case 0: sc->typestr = "FMV-181"; break; case 1: sc->typestr = "FMV-181A"; break; } break; case FE_FMV0_MEDIUM_2: switch ( revision ) { case 8: sc->typestr = "FMV-184 (CSR = 2)"; break; } break; case FE_FMV0_MEDIUM_5: switch ( revision ) { case 8: sc->typestr = "FMV-184 (CSR = 1)"; break; } break; case FE_FMV0_MEDIUM_2 | FE_FMV0_MEDIUM_5: switch ( revision ) { case 0: sc->typestr = "FMV-182"; break; case 1: sc->typestr = "FMV-182A"; break; case 8: sc->typestr = "FMV-184 (CSR = 3)"; break; } break; } if ( sc->typestr == NULL ) { /* Unknown card type... Hope the driver works. */ sc->typestr = "unknown FMV-180 version"; log( LOG_WARNING, "fe%d: %s: %x-%x-%x-%x\n", sc->sc_unit, sc->typestr, inb( sc->ioaddr[ FE_FMV0 ] ), inb( sc->ioaddr[ FE_FMV1 ] ), inb( sc->ioaddr[ FE_FMV2 ] ), inb( sc->ioaddr[ FE_FMV3 ] ) ); } /* * An FMV-180 has been proved. * Determine which IRQ to be used. * * In this version, we give a priority to the kernel config file. * If the EEPROM and config don't match, say it to the user for * an attention. */ n = ( inb( sc->ioaddr[ FE_FMV2 ] ) & FE_FMV2_IRS ) >> FE_FMV2_IRS_SHIFT; if ( dev->id_irq == NO_IRQ ) { /* Just use the probed value. */ dev->id_irq = irqmap[ n ]; } else if ( dev->id_irq != irqmap[ n ] ) { /* Don't match. */ log( LOG_WARNING, "fe%d: check IRQ in config; it may be incorrect", sc->sc_unit ); } /* * Initialize constants in the per-line structure. */ /* Get our station address from EEPROM. */ inblk( sc, FE_FMV4, sc->sc_enaddr, ETHER_ADDR_LEN ); /* Make sure we got a valid station address. */ if ( ( sc->sc_enaddr[ 0 ] & 0x03 ) != 0x00 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) return 0; /* * Register values which (may) depend on board design. * * Program the 86960 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_EC; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; /* * Minimum initialization of the hardware. * We write into registers; hope I/O ports have no * overlap with other boards. */ /* Initialize ASIC. */ outb( sc->ioaddr[ FE_FMV3 ], 0 ); outb( sc->ioaddr[ FE_FMV10 ], 0 ); /* Initialize 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* "Refresh" hardware configuration. FIXME. */ outb( sc->ioaddr[ FE_FMV2 ], inb( sc->ioaddr[ FE_FMV2 ] ) ); /* Turn the "master interrupt control" flag of ASIC on. */ outb( sc->ioaddr[ FE_FMV3 ], FE_FMV3_IRQENB ); /* * That's all. FMV-180 occupies 32 I/O addresses, by the way. */ return 32; } /* * Probe and initialization for Allied-Telesis AT1700/RE2000 series. */ static int fe_probe_ati ( DEVICE * dev, struct fe_softc * sc ) { int i, n; u_char eeprom [ FE_EEPROM_SIZE ]; u_char save16, save17; static u_short const baseaddr [ 8 ] = { 0x260, 0x280, 0x2A0, 0x240, 0x340, 0x320, 0x380, 0x300 }; static u_short const irqmaps [ 4 ][ 4 ] = { { IRQ3, IRQ4, IRQ5, IRQ9 }, { IRQ10, IRQ11, IRQ12, IRQ15 }, { IRQ3, IRQ11, IRQ5, IRQ15 }, { IRQ10, IRQ11, IRQ14, IRQ15 }, }; static struct fe_simple_probe_struct const probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, { FE_DLCR5, 0x80, 0x00 }, #if 0 { FE_BMPR16, 0x1B, 0x00 }, { FE_BMPR17, 0x7F, 0x00 }, #endif { 0 } }; /* Assume we have 86965 and no need to restore these. */ save16 = 0; save17 = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: probe (0x%x) for ATI\n", sc->sc_unit, sc->iobase ); fe_dump( LOG_INFO, sc, NULL ); #endif /* * See if the specified address is possible for MB86965A JLI mode. */ for ( i = 0; i < 8; i++ ) { if ( baseaddr[ i ] == sc->iobase ) break; } if ( i == 8 ) goto NOTFOUND; /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* * We should test if MB86965A is on the base address now. * Unfortunately, it is very hard to probe it reliably, since * we have no way to reset the chip under software control. * On cold boot, we could check the "signature" bit patterns * described in the Fujitsu document. On warm boot, however, * we can predict almost nothing about register values. */ if ( !fe_simple_probe( sc, probe_table ) ) goto NOTFOUND; /* Check if our I/O address matches config info on 86965. */ n = ( inb( sc->ioaddr[ FE_BMPR19 ] ) & FE_B19_ADDR ) >> FE_B19_ADDR_SHIFT; if ( baseaddr[ n ] != sc->iobase ) goto NOTFOUND; /* * We are now almost sure we have an AT1700 at the given * address. So, read EEPROM through 86965. We have to write * into LSI registers to read from EEPROM. I want to avoid it * at this stage, but I cannot test the presence of the chip * any further without reading EEPROM. FIXME. */ save16 = inb( sc->ioaddr[ FE_BMPR16 ] ); save17 = inb( sc->ioaddr[ FE_BMPR17 ] ); fe_read_eeprom( sc, eeprom ); /* Make sure the EEPROM is turned off. */ outb( sc->ioaddr[ FE_BMPR16 ], 0 ); outb( sc->ioaddr[ FE_BMPR17 ], 0 ); /* Make sure that config info in EEPROM and 86965 agree. */ if ( eeprom[ FE_EEPROM_CONF ] != inb( sc->ioaddr[ FE_BMPR19 ] ) ) { goto NOTFOUND; } /* * The following model identification codes are stolen from * from the NetBSD port of the fe driver. My reviewers * suggested minor revision. */ /* Determine the card type. */ switch (eeprom[FE_ATI_EEP_MODEL]) { case FE_ATI_MODEL_AT1700T: sc->typestr = "AT-1700T/RE2001"; break; case FE_ATI_MODEL_AT1700BT: sc->typestr = "AT-1700BT/RE2003"; break; case FE_ATI_MODEL_AT1700FT: sc->typestr = "AT-1700FT/RE2009"; break; case FE_ATI_MODEL_AT1700AT: sc->typestr = "AT-1700AT/RE2005"; break; default: sc->typestr = "unknown AT-1700/RE2000 ?"; break; } /* * Try to determine IRQ settings. * Different models use different ranges of IRQs. */ if ( dev->id_irq == NO_IRQ ) { n = ( inb( sc->ioaddr[ FE_BMPR19 ] ) & FE_B19_IRQ ) >> FE_B19_IRQ_SHIFT; switch ( eeprom[ FE_ATI_EEP_REVISION ] & 0xf0 ) { case 0x30: dev->id_irq = irqmaps[ 3 ][ n ]; break; case 0x10: case 0x50: dev->id_irq = irqmaps[ 2 ][ n ]; break; case 0x40: case 0x60: if ( eeprom[ FE_ATI_EEP_MAGIC ] & 0x04 ) { dev->id_irq = irqmaps[ 1 ][ n ]; } else { dev->id_irq = irqmaps[ 0 ][ n ]; } break; default: dev->id_irq = irqmaps[ 0 ][ n ]; break; } } /* * Initialize constants in the per-line structure. */ /* Get our station address from EEPROM. */ bcopy( eeprom + FE_ATI_EEP_ADDR, sc->sc_enaddr, ETHER_ADDR_LEN ); #if 1 /* * This test doesn't work well for AT1700 look-alike by * other vendors. */ /* Make sure the vendor part is for Allied-Telesis. */ if ( sc->sc_enaddr[ 0 ] != 0x00 || sc->sc_enaddr[ 1 ] != 0x00 || sc->sc_enaddr[ 2 ] != 0xF4 ) return 0; #else /* Make sure we got a valid station address. */ if ( ( sc->sc_enaddr[ 0 ] & 0x03 ) != 0x00 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) return 0; #endif /* * Program the 86960 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; /* FIXME */ sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_EC; #if 0 /* XXXX Should we use this? FIXME. */ sc->proto_bmpr13 = eeprom[ FE_ATI_EEP_MEDIA ]; #else sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; #endif #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "ATI found" ); #endif /* Setup hooks. This may solves a nasty bug. FIXME. */ sc->init = fe_init_ati; /* Initialize 86965. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "end of fe_probe_ati()" ); #endif /* * That's all. AT1700 occupies 32 I/O addresses, by the way. */ return 32; NOTFOUND: /* * We have no AT1700 at a given address. * Restore BMPR16 and BMPR17 if we have destroyed them, * hoping that the hardware on the address didn't get * bad side effect. */ if ( save16 != 0 | save17 != 0 ) { outb( sc->ioaddr[ FE_BMPR16 ], save16 ); outb( sc->ioaddr[ FE_BMPR17 ], save17 ); } return ( 0 ); } /* ATI specific initialization routine. */ static void fe_init_ati ( struct fe_softc * sc ) { /* * I've told that the following operation "Resets" the chip. * Hope this solve a bug which hangs up the driver under * heavy load... FIXME. */ /* Minimal initialization of 86965. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* "Reset" by wrting into an undocument register location. */ outb( sc->ioaddr[ 0x1F ], 0 ); /* How long do we have to wait after the reset? FIXME. */ DELAY( 300 ); } /* * Probe and initialization for Gateway Communications' old cards. */ static int fe_probe_gwy ( DEVICE * dev, struct fe_softc * sc ) { int i,type; static struct fe_simple_probe_struct probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, { FE_DLCR7, 0xC0, 0x00 }, /* * Test *vendor* part of the address for Gateway. * This test is essential to identify Gateway's cards. * We shuld define some symbolic names for the * following offsets. FIXME. */ { 0x18, 0xFF, 0x00 }, { 0x19, 0xFF, 0x00 }, { 0x1A, 0xFF, 0x61 }, { 0 } }; /* * We need explicit IRQ and supported address. * I'm not sure which address and IRQ is possible for Gateway * Ethernet family. The following accepts everything. FIXME. */ if ( dev->id_irq == NO_IRQ || ( sc->iobase & ~0x3E0 ) != 0 ) { return ( 0 ); } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "top of probe" ); #endif /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* See if the card is on its address. */ if ( !fe_simple_probe( sc, probe_table ) ) { return 0; } /* Determine the card type. */ sc->typestr = "Gateway Ethernet w/ Fujitsu chipset"; /* Get our station address from EEPROM. */ inblk( sc, 0x18, sc->sc_enaddr, ETHER_ADDR_LEN ); /* * Program the 86960 as follows: * SRAM: 16KB, 100ns, byte-wide access. * Transmission buffer: 2KB x 2. * System bus interface: 16 bits. * Make sure to clear out ID bits in DLCR7 * (They actually are Encoder/Decoder control in NICE.) */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_16KB | FE_D6_TXBSIZ_2x2KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH; sc->proto_bmpr13 = 0; /* Minimal initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* That's all. The card occupies 32 I/O addresses, as always. */ return 32; } #if NCRD > 0 /* * Probe and initialization for Fujitsu MBH10302 PCMCIA Ethernet interface. * Note that this is for 10302 only; MBH10304 is handled by fe_probe_tdk(). */ static int fe_probe_mbh ( DEVICE * dev, struct fe_softc * sc ) { int i,type; static struct fe_simple_probe_struct probe_table [] = { { FE_DLCR0, 0x09, 0x00 }, { FE_DLCR2, 0x79, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, { FE_DLCR6, 0xFF, 0xB6 }, /* * The following location has the first byte of the card's * Ethernet (MAC) address. * We can always verify the *first* 2 bits (in Ethernet * bit order) are "global" and "unicast" for any vendors'. */ { FE_MBH10, 0x03, 0x00 }, /* Just a gap? Seems reliable, anyway. */ { 0x12, 0xFF, 0x00 }, { 0x13, 0xFF, 0x00 }, { 0x14, 0xFF, 0x00 }, { 0x15, 0xFF, 0x00 }, { 0x16, 0xFF, 0x00 }, { 0x17, 0xFF, 0x00 }, #if 0 { 0x18, 0xFF, 0xFF }, { 0x19, 0xFF, 0xFF }, #endif { 0 } }; /* * We need explicit IRQ and supported address. */ if ( dev->id_irq == NO_IRQ || ( sc->iobase & ~0x3E0 ) != 0 ) { return ( 0 ); } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "top of probe" ); #endif /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* * See if MBH10302 is on its address. * I'm not sure the following probe code works. FIXME. */ if ( !fe_simple_probe( sc, probe_table ) ) return 0; /* Determine the card type. */ sc->typestr = "MBH10302 (PCMCIA)"; /* * Initialize constants in the per-line structure. */ /* Get our station address from EEPROM. */ inblk( sc, FE_MBH10, sc->sc_enaddr, ETHER_ADDR_LEN ); /* Make sure we got a valid station address. */ if ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) return 0; /* * Program the 86960 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_NICE; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; /* Setup hooks. We need a special initialization procedure. */ sc->init = fe_init_mbh; /* * Minimum initialization. */ /* Minimal initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); #if 1 /* FIXME. */ /* Initialize system bus interface and encoder/decoder operation. */ outb( sc->ioaddr[ FE_MBH0 ], FE_MBH0_MAGIC | FE_MBH0_INTR_DISABLE ); #endif /* * That's all. MBH10302 occupies 32 I/O addresses, by the way. */ return 32; } /* MBH specific initialization routine. */ static void fe_init_mbh ( struct fe_softc * sc ) { /* Minimal initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* Enable master interrupt flag. */ outb( sc->ioaddr[ FE_MBH0 ], FE_MBH0_MAGIC | FE_MBH0_INTR_ENABLE ); } #endif /* NCRD > 0 */ #if NCRD > 0 /* * Probe and initialization for TDK/CONTEC PCMCIA Ethernet interface. * by MASUI Kenji * * (Contec uses TDK Ethenet chip -- hosokawa) * * This version of fe_probe_tdk has been rewrote to handle * *generic* PC card implementation of Fujitsu MB8696x family. The * name _tdk is just for a historical reason. :-) */ static int fe_probe_tdk ( DEVICE * dev, struct fe_softc * sc ) { int i; static struct fe_simple_probe_struct probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, /* { FE_DLCR5, 0x80, 0x00 }, Does not work well. */ { 0 } }; if ( dev->id_irq == NO_IRQ ) { return ( 0 ); } /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* * See if C-NET(PC)C is on its address. */ if ( !fe_simple_probe( sc, probe_table ) ) return 0; /* Determine the card type. */ sc->typestr = "Generic MB8696x Ethernet (PCMCIA)"; /* * Initialize constants in the per-line structure. */ /* The station address *must*be* already in sc_enaddr; Make sure we got a valid station address. */ if ( ( sc->sc_enaddr[ 0 ] & 0x03 ) != 0x00 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) return 0; /* * Program the 86965 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. * XXX: Should we remove IDENT_NICE from DLCR7? Or, * even add IDENT_EC instead? FIXME. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_NICE; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; /* Minimul initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* * That's all. C-NET(PC)C occupies 16 I/O addresses. * XXX: Are there any card with 32 I/O addresses? FIXME. */ return 16; } #endif /* * Install interface into kernel networking data structures */ static int fe_attach ( DEVICE * dev ) { #if NCRD > 0 static int already_ifattach[NFE]; #endif struct fe_softc *sc = &fe_softc[dev->id_unit]; /* * Initialize ifnet structure */ sc->sc_if.if_softc = sc; sc->sc_if.if_unit = sc->sc_unit; sc->sc_if.if_name = "fe"; sc->sc_if.if_output = ether_output; sc->sc_if.if_start = fe_start; sc->sc_if.if_ioctl = fe_ioctl; sc->sc_if.if_watchdog = fe_watchdog; /* * Set default interface flags. */ sc->sc_if.if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; /* * Set maximum size of output queue, if it has not been set. * It is done here as this driver may be started after the * system initialization (i.e., the interface is PCMCIA.) * * I'm not sure this is really necessary, but, even if it is, * it should be done somewhere else, e.g., in if_attach(), * since it must be a common workaround for all network drivers. * FIXME. */ if ( sc->sc_if.if_snd.ifq_maxlen == 0 ) { sc->sc_if.if_snd.ifq_maxlen = ifqmaxlen; } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "attach()" ); #endif #if FE_SINGLE_TRANSMISSION /* Override txb config to allocate minimum. */ sc->proto_dlcr6 &= ~FE_D6_TXBSIZ sc->proto_dlcr6 |= FE_D6_TXBSIZ_2x2KB; #endif /* Modify hardware config if it is requested. */ if ( dev->id_flags & FE_FLAGS_OVERRIDE_DLCR6 ) { sc->proto_dlcr6 = dev->id_flags & FE_FLAGS_DLCR6_VALUE; } /* Find TX buffer size, based on the hardware dependent proto. */ switch ( sc->proto_dlcr6 & FE_D6_TXBSIZ ) { case FE_D6_TXBSIZ_2x2KB: sc->txb_size = 2048; break; case FE_D6_TXBSIZ_2x4KB: sc->txb_size = 4096; break; case FE_D6_TXBSIZ_2x8KB: sc->txb_size = 8192; break; default: /* Oops, we can't work with single buffer configuration. */ #if FE_DEBUG >= 2 log( LOG_WARNING, "fe%d: strange TXBSIZ config; fixing\n", sc->sc_unit ); #endif sc->proto_dlcr6 &= ~FE_D6_TXBSIZ; sc->proto_dlcr6 |= FE_D6_TXBSIZ_2x2KB; sc->txb_size = 2048; break; } /* Attach and stop the interface. */ #if NCRD > 0 if (already_ifattach[dev->id_unit] != 1) { if_attach(&sc->sc_if); already_ifattach[dev->id_unit] = 1; } #else if_attach(&sc->sc_if); #endif /* NCRD > 0 */ fe_stop(sc->sc_unit); /* This changes the state to IDLE. */ ether_ifattach(&sc->sc_if); /* Print additional info when attached. */ printf( "fe%d: address %6D, type %s\n", sc->sc_unit, sc->sc_enaddr, ":" , sc->typestr ); #if FE_DEBUG >= 3 { int buf, txb, bbw, sbw, ram; buf = txb = bbw = sbw = ram = -1; switch ( sc->proto_dlcr6 & FE_D6_BUFSIZ ) { case FE_D6_BUFSIZ_8KB: buf = 8; break; case FE_D6_BUFSIZ_16KB: buf = 16; break; case FE_D6_BUFSIZ_32KB: buf = 32; break; case FE_D6_BUFSIZ_64KB: buf = 64; break; } switch ( sc->proto_dlcr6 & FE_D6_TXBSIZ ) { case FE_D6_TXBSIZ_2x2KB: txb = 2; break; case FE_D6_TXBSIZ_2x4KB: txb = 4; break; case FE_D6_TXBSIZ_2x8KB: txb = 8; break; } switch ( sc->proto_dlcr6 & FE_D6_BBW ) { case FE_D6_BBW_BYTE: bbw = 8; break; case FE_D6_BBW_WORD: bbw = 16; break; } switch ( sc->proto_dlcr6 & FE_D6_SBW ) { case FE_D6_SBW_BYTE: sbw = 8; break; case FE_D6_SBW_WORD: sbw = 16; break; } switch ( sc->proto_dlcr6 & FE_D6_SRAM ) { case FE_D6_SRAM_100ns: ram = 100; break; case FE_D6_SRAM_150ns: ram = 150; break; } printf( "fe%d: SRAM %dKB %dbit %dns, TXB %dKBx2, %dbit I/O\n", sc->sc_unit, buf, bbw, ram, txb, sbw ); } #endif #if NBPFILTER > 0 /* If BPF is in the kernel, call the attach for it. */ bpfattach( &sc->sc_if, DLT_EN10MB, sizeof(struct ether_header)); #endif return 1; } /* * Reset interface. */ static void fe_reset ( int unit ) { /* * Stop interface and re-initialize. */ fe_stop(unit); fe_init(unit); } /* * Stop everything on the interface. * * All buffered packets, both transmitting and receiving, * if any, will be lost by stopping the interface. */ static void fe_stop ( int unit ) { struct fe_softc *sc = &fe_softc[unit]; int s; s = splimp(); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "stop()" ); #endif /* Disable interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0x00 ); outb( sc->ioaddr[ FE_DLCR3 ], 0x00 ); /* Stop interface hardware. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Clear all interrupt status. */ outb( sc->ioaddr[ FE_DLCR0 ], 0xFF ); outb( sc->ioaddr[ FE_DLCR1 ], 0xFF ); /* Put the chip in stand-by mode. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_POWER_DOWN ); DELAY( 200 ); /* Reset transmitter variables and interface flags. */ sc->sc_if.if_flags &= ~( IFF_OACTIVE | IFF_RUNNING ); sc->sc_if.if_timer = 0; sc->txb_free = sc->txb_size; sc->txb_count = 0; sc->txb_sched = 0; /* MAR loading can be delayed. */ sc->filter_change = 0; /* Update config status also. */ /* Call a hook. */ if ( sc->stop ) sc->stop( sc ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "end of stop()" ); #endif (void) splx(s); } /* * Device timeout/watchdog routine. Entered if the device neglects to * generate an interrupt after a transmit has been started on it. */ static void fe_watchdog ( struct ifnet *ifp ) { struct fe_softc *sc = (struct fe_softc *)ifp; #if FE_DEBUG >= 1 /* A "debug" message. */ log( LOG_ERR, "fe%d: transmission timeout (%d+%d)%s\n", ifp->if_unit, sc->txb_sched, sc->txb_count, ( ifp->if_flags & IFF_UP ) ? "" : " when down" ); if ( sc->sc_if.if_opackets == 0 && sc->sc_if.if_ipackets == 0 ) { log( LOG_WARNING, "fe%d: wrong IRQ setting in config?\n", ifp->if_unit ); } #endif #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, NULL ); #endif /* Record how many packets are lost by this accident. */ ifp->if_oerrors += sc->txb_sched + sc->txb_count; /* Put the interface into known initial state. */ if ( ifp->if_flags & IFF_UP ) { fe_reset( ifp->if_unit ); } else { fe_stop( ifp->if_unit ); } } /* * Initialize device. */ static void fe_init ( int unit ) { struct fe_softc *sc = &fe_softc[unit]; int i, s; #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "init()" ); #endif /* We need an address. */ - if (sc->sc_if.if_addrlist == 0) { + if (TAILQ_EMPTY(&sc->sc_if.if_addrhead)) { /* XXX unlikely */ #if FE_DEBUG >= 1 log( LOG_ERR, "fe%d: init() without any address\n", sc->sc_unit ); #endif return; } #if FE_DEBUG >= 1 /* * Make sure we have a valid station address. * The following test is applicable for any Ethernet interfaces. * It can be done in somewhere common to all of them. FIXME. */ if ( ( sc->sc_enaddr[ 0 ] & 0x01 ) != 0 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) { log( LOG_ERR, "fe%d: invalid station address (%6D)\n", sc->sc_unit, sc->sc_enaddr, ":" ); return; } #endif /* Start initializing 86960. */ s = splimp(); /* Call a hook. */ if ( sc->init ) sc->init( sc ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after init hook" ); #endif /* * Make sure to disable the chip, also. * This may also help re-programming the chip after * hot insertion of PCMCIAs. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Power up the chip and select register bank for DLCRs. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_DLCR | FE_D7_POWER_UP ); DELAY( 200 ); /* Feed the station address. */ outblk( sc, FE_DLCR8, sc->sc_enaddr, ETHER_ADDR_LEN ); /* Clear multicast address filter to receive nothing. */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_MAR | FE_D7_POWER_UP ); outblk( sc, FE_MAR8, fe_filter_nothing.data, FE_FILTER_LEN ); /* Select the BMPR bank for runtime register access. */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_BMPR | FE_D7_POWER_UP ); /* Initialize registers. */ outb( sc->ioaddr[ FE_DLCR0 ], 0xFF ); /* Clear all bits. */ outb( sc->ioaddr[ FE_DLCR1 ], 0xFF ); /* ditto. */ outb( sc->ioaddr[ FE_DLCR2 ], 0x00 ); outb( sc->ioaddr[ FE_DLCR3 ], 0x00 ); outb( sc->ioaddr[ FE_DLCR4 ], sc->proto_dlcr4 ); outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 ); outb( sc->ioaddr[ FE_BMPR10 ], 0x00 ); outb( sc->ioaddr[ FE_BMPR11 ], FE_B11_CTRL_SKIP | FE_B11_MODE1 ); outb( sc->ioaddr[ FE_BMPR12 ], 0x00 ); outb( sc->ioaddr[ FE_BMPR13 ], sc->proto_bmpr13 ); outb( sc->ioaddr[ FE_BMPR14 ], 0x00 ); outb( sc->ioaddr[ FE_BMPR15 ], 0x00 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "just before enabling DLC" ); #endif /* Enable interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], FE_TMASK ); outb( sc->ioaddr[ FE_DLCR3 ], FE_RMASK ); /* Enable transmitter and receiver. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_ENABLE ); DELAY( 200 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "just after enabling DLC" ); #endif /* * Make sure to empty the receive buffer. * * This may be redundant, but *if* the receive buffer were full * at this point, then the driver would hang. I have experienced * some strange hang-up just after UP. I hope the following * code solve the problem. * * I have changed the order of hardware initialization. * I think the receive buffer cannot have any packets at this * point in this version. The following code *must* be * redundant now. FIXME. * * I've heard a rumore that on some PC card implementation of * 8696x, the receive buffer can have some data at this point. * The following message helps discovering the fact. FIXME. */ if ( !( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) ) { log( LOG_WARNING, "fe%d: receive buffer has some data after reset\n", sc->sc_unit ); fe_emptybuffer( sc ); } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after ERB loop" ); #endif /* Do we need this here? Actually, no. I must be paranoia. */ outb( sc->ioaddr[ FE_DLCR0 ], 0xFF ); /* Clear all bits. */ outb( sc->ioaddr[ FE_DLCR1 ], 0xFF ); /* ditto. */ #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after FIXME" ); #endif /* Set 'running' flag, because we are now running. */ sc->sc_if.if_flags |= IFF_RUNNING; /* * At this point, the interface is running properly, * except that it receives *no* packets. we then call * fe_setmode() to tell the chip what packets to be * received, based on the if_flags and multicast group * list. It completes the initialization process. */ fe_setmode( sc ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after setmode" ); #endif /* ...and attempt to start output queued packets. */ fe_start( &sc->sc_if ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "init() done" ); #endif (void) splx(s); } /* * This routine actually starts the transmission on the interface */ static void fe_xmit ( struct fe_softc * sc ) { /* * Set a timer just in case we never hear from the board again. * We use longer timeout for multiple packet transmission. * I'm not sure this timer value is appropriate. FIXME. */ sc->sc_if.if_timer = 1 + sc->txb_count; /* Update txb variables. */ sc->txb_sched = sc->txb_count; sc->txb_count = 0; sc->txb_free = sc->txb_size; sc->tx_excolls = 0; /* Start transmitter, passing packets in TX buffer. */ outb( sc->ioaddr[ FE_BMPR10 ], sc->txb_sched | FE_B10_START ); } /* * Start output on interface. * We make two assumptions here: * 1) that the current priority is set to splimp _before_ this code * is called *and* is returned to the appropriate priority after * return * 2) that the IFF_OACTIVE flag is checked before this code is called * (i.e. that the output part of the interface is idle) */ void fe_start ( struct ifnet *ifp ) { struct fe_softc *sc = ifp->if_softc; struct mbuf *m; #if FE_DEBUG >= 1 /* Just a sanity check. */ if ( ( sc->txb_count == 0 ) != ( sc->txb_free == sc->txb_size ) ) { /* * Txb_count and txb_free co-works to manage the * transmission buffer. Txb_count keeps track of the * used potion of the buffer, while txb_free does unused * potion. So, as long as the driver runs properly, * txb_count is zero if and only if txb_free is same * as txb_size (which represents whole buffer.) */ log( LOG_ERR, "fe%d: inconsistent txb variables (%d, %d)\n", sc->sc_unit, sc->txb_count, sc->txb_free ); /* * So, what should I do, then? * * We now know txb_count and txb_free contradicts. We * cannot, however, tell which is wrong. More * over, we cannot peek 86960 transmission buffer or * reset the transmission buffer. (In fact, we can * reset the entire interface. I don't want to do it.) * * If txb_count is incorrect, leaving it as-is will cause * sending of garbage after next interrupt. We have to * avoid it. Hence, we reset the txb_count here. If * txb_free was incorrect, resetting txb_count just loose * some packets. We can live with it. */ sc->txb_count = 0; } #endif #if FE_DEBUG >= 1 /* * First, see if there are buffered packets and an idle * transmitter - should never happen at this point. */ if ( ( sc->txb_count > 0 ) && ( sc->txb_sched == 0 ) ) { log( LOG_ERR, "fe%d: transmitter idle with %d buffered packets\n", sc->sc_unit, sc->txb_count ); fe_xmit( sc ); } #endif /* * Stop accepting more transmission packets temporarily, when * a filter change request is delayed. Updating the MARs on * 86960 flushes the transmission buffer, so it is delayed * until all buffered transmission packets have been sent * out. */ if ( sc->filter_change ) { /* * Filter change request is delayed only when the DLC is * working. DLC soon raise an interrupt after finishing * the work. */ goto indicate_active; } for (;;) { /* * See if there is room to put another packet in the buffer. * We *could* do better job by peeking the send queue to * know the length of the next packet. Current version just * tests against the worst case (i.e., longest packet). FIXME. * * When adding the packet-peek feature, don't forget adding a * test on txb_count against QUEUEING_MAX. * There is a little chance the packet count exceeds * the limit. Assume transmission buffer is 8KB (2x8KB * configuration) and an application sends a bunch of small * (i.e., minimum packet sized) packets rapidly. An 8KB * buffer can hold 130 blocks of 62 bytes long... */ if ( sc->txb_free < ETHER_MAX_LEN - ETHER_CRC_LEN + FE_DATA_LEN_LEN ) { /* No room. */ goto indicate_active; } #if FE_SINGLE_TRANSMISSION if ( sc->txb_count > 0 ) { /* Just one packet per a transmission buffer. */ goto indicate_active; } #endif /* * Get the next mbuf chain for a packet to send. */ IF_DEQUEUE( &sc->sc_if.if_snd, m ); if ( m == NULL ) { /* No more packets to send. */ goto indicate_inactive; } /* * Copy the mbuf chain into the transmission buffer. * txb_* variables are updated as necessary. */ fe_write_mbufs( sc, m ); /* Start transmitter if it's idle. */ if ( ( sc->txb_count > 0 ) && ( sc->txb_sched == 0 ) ) { fe_xmit( sc ); } /* * Tap off here if there is a bpf listener, * and the device is *not* in promiscuous mode. * (86960 receives self-generated packets if * and only if it is in "receive everything" * mode.) */ #if NBPFILTER > 0 if ( sc->sc_if.if_bpf && !( sc->sc_if.if_flags & IFF_PROMISC ) ) { bpf_mtap( &sc->sc_if, m ); } #endif m_freem( m ); } indicate_inactive: /* * We are using the !OACTIVE flag to indicate to * the outside world that we can accept an * additional packet rather than that the * transmitter is _actually_ active. Indeed, the * transmitter may be active, but if we haven't * filled all the buffers with data then we still * want to accept more. */ sc->sc_if.if_flags &= ~IFF_OACTIVE; return; indicate_active: /* * The transmitter is active, and there are no room for * more outgoing packets in the transmission buffer. */ sc->sc_if.if_flags |= IFF_OACTIVE; return; } /* * Drop (skip) a packet from receive buffer in 86960 memory. */ static void fe_droppacket ( struct fe_softc * sc, int len ) { int i; /* * 86960 manual says that we have to read 8 bytes from the buffer * before skip the packets and that there must be more than 8 bytes * remaining in the buffer when issue a skip command. * Remember, we have already read 4 bytes before come here. */ if ( len > 12 ) { /* Read 4 more bytes, and skip the rest of the packet. */ ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); outb( sc->ioaddr[ FE_BMPR14 ], FE_B14_SKIP ); } else { /* We should not come here unless receiving RUNTs. */ for ( i = 0; i < len; i += 2 ) { ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); } } } /* * Empty receiving buffer. */ static void fe_emptybuffer ( struct fe_softc * sc ) { int i; u_char saved_dlcr5; #if FE_DEBUG >= 1 log( LOG_WARNING, "fe%d: emptying receive buffer", sc->sc_unit ); #endif /* * Stop receiving packets, temporarily. */ saved_dlcr5 = inb( sc->ioaddr[ FE_DLCR5 ] ); outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 ); /* * When we come here, the receive buffer management should * have been broken. So, we cannot use skip operation. */ for ( i = 0; i < sc->txb_size; i += 2 ) { if ( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) break; ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); } /* * Double check. */ if ( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) { log( LOG_ERR, "fe%d: could not empty receive buffer\n", sc->sc_unit ); /* Hmm. What should I do if this happens? FIXME. */ } /* * Restart receiving packets. */ outb( sc->ioaddr[ FE_DLCR5 ], saved_dlcr5 ); } /* * Transmission interrupt handler * The control flow of this function looks silly. FIXME. */ static void fe_tint ( struct fe_softc * sc, u_char tstat ) { int left; int col; /* * Handle "excessive collision" interrupt. */ if ( tstat & FE_D0_COLL16 ) { /* * Find how many packets (including this collided one) * are left unsent in transmission buffer. */ left = inb( sc->ioaddr[ FE_BMPR10 ] ); #if FE_DEBUG >= 2 log( LOG_WARNING, "fe%d: excessive collision (%d/%d)\n", sc->sc_unit, left, sc->txb_sched ); #endif #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, NULL ); #endif /* * Clear the collision flag (in 86960) here * to avoid confusing statistics. */ outb( sc->ioaddr[ FE_DLCR0 ], FE_D0_COLLID ); /* * Restart transmitter, skipping the * collided packet. * * We *must* skip the packet to keep network running * properly. Excessive collision error is an * indication of the network overload. If we * tried sending the same packet after excessive * collision, the network would be filled with * out-of-time packets. Packets belonging * to reliable transport (such as TCP) are resent * by some upper layer. */ outb( sc->ioaddr[ FE_BMPR11 ], FE_B11_CTRL_SKIP | FE_B11_MODE1 ); /* Update statistics. */ sc->tx_excolls++; } /* * Handle "transmission complete" interrupt. */ if ( tstat & FE_D0_TXDONE ) { /* * Add in total number of collisions on last * transmission. We also clear "collision occurred" flag * here. * * 86960 has a design flaw on collision count on multiple * packet transmission. When we send two or more packets * with one start command (that's what we do when the * transmission queue is crowded), 86960 informs us number * of collisions occurred on the last packet on the * transmission only. Number of collisions on previous * packets are lost. I have told that the fact is clearly * stated in the Fujitsu document. * * I considered not to mind it seriously. Collision * count is not so important, anyway. Any comments? FIXME. */ if ( inb( sc->ioaddr[ FE_DLCR0 ] ) & FE_D0_COLLID ) { /* Clear collision flag. */ outb( sc->ioaddr[ FE_DLCR0 ], FE_D0_COLLID ); /* Extract collision count from 86960. */ col = inb( sc->ioaddr[ FE_DLCR4 ] ); col = ( col & FE_D4_COL ) >> FE_D4_COL_SHIFT; if ( col == 0 ) { /* * Status register indicates collisions, * while the collision count is zero. * This can happen after multiple packet * transmission, indicating that one or more * previous packet(s) had been collided. * * Since the accurate number of collisions * has been lost, we just guess it as 1; * Am I too optimistic? FIXME. */ col = 1; } sc->sc_if.if_collisions += col; #if FE_DEBUG >= 3 log( LOG_WARNING, "fe%d: %d collision(s) (%d)\n", sc->sc_unit, col, sc->txb_sched ); #endif } /* * Update transmission statistics. * Be sure to reflect number of excessive collisions. */ sc->sc_if.if_opackets += sc->txb_sched - sc->tx_excolls; sc->sc_if.if_oerrors += sc->tx_excolls; sc->sc_if.if_collisions += sc->tx_excolls * 16; sc->txb_sched = 0; /* * The transmitter is no more active. * Reset output active flag and watchdog timer. */ sc->sc_if.if_flags &= ~IFF_OACTIVE; sc->sc_if.if_timer = 0; /* * If more data is ready to transmit in the buffer, start * transmitting them. Otherwise keep transmitter idle, * even if more data is queued. This gives receive * process a slight priority. */ if ( sc->txb_count > 0 ) fe_xmit( sc ); } } /* * Ethernet interface receiver interrupt. */ static void fe_rint ( struct fe_softc * sc, u_char rstat ) { u_short len; u_char status; int i; /* * Update statistics if this interrupt is caused by an error. */ if ( rstat & ( FE_D1_OVRFLO | FE_D1_CRCERR | FE_D1_ALGERR | FE_D1_SRTPKT ) ) { #if FE_DEBUG >= 3 log( LOG_WARNING, "fe%d: receive error: %s%s%s%s(%02x)\n", sc->sc_unit, rstat & FE_D1_OVRFLO ? "OVR " : "", rstat & FE_D1_CRCERR ? "CRC " : "", rstat & FE_D1_ALGERR ? "ALG " : "", rstat & FE_D1_SRTPKT ? "LEN " : "", rstat ); #endif sc->sc_if.if_ierrors++; } /* * MB86960 has a flag indicating "receive queue empty." * We just loop, checking the flag, to pull out all received * packets. * * We limit the number of iterations to avoid infinite-loop. * It can be caused by a very slow CPU (some broken * peripheral may insert incredible number of wait cycles) * or, worse, by a broken MB86960 chip. */ for ( i = 0; i < FE_MAX_RECV_COUNT; i++ ) { /* Stop the iteration if 86960 indicates no packets. */ if ( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) break; /* * Extract A receive status byte. * As our 86960 is in 16 bit bus access mode, we have to * use inw() to get the status byte. The significant * value is returned in lower 8 bits. */ status = ( u_char )inw( sc->ioaddr[ FE_BMPR8 ] ); #if FE_DEBUG >= 4 log( LOG_INFO, "fe%d: receive status = %04x\n", sc->sc_unit, status ); #endif /* * Extract the packet length. * It is a sum of a header (14 bytes) and a payload. * CRC has been stripped off by the 86960. */ len = inw( sc->ioaddr[ FE_BMPR8 ] ); /* * If there was an error, update statistics and drop * the packet, unless the interface is in promiscuous * mode. */ if ( ( status & 0xF0 ) != 0x20 ) { if ( !( sc->sc_if.if_flags & IFF_PROMISC ) ) { sc->sc_if.if_ierrors++; fe_droppacket( sc, len ); continue; } } /* * MB86960 checks the packet length and drop big packet * before passing it to us. There are no chance we can * get big packets through it, even if they are actually * sent over a line. Hence, if the length exceeds * the specified limit, it means some serious failure, * such as out-of-sync on receive buffer management. * * Same for short packets, since we have programmed * 86960 to drop short packets. */ if ( len > ETHER_MAX_LEN - ETHER_CRC_LEN || len < ETHER_MIN_LEN - ETHER_CRC_LEN ) { #if FE_DEBUG >= 1 log( LOG_WARNING, "fe%d: received a %s packet? (%u bytes)\n", sc->sc_unit, len < ETHER_MIN_LEN - ETHER_CRC_LEN ? "partial" : "big", len ); #endif sc->sc_if.if_ierrors++; fe_emptybuffer( sc ); continue; } /* * Go get a packet. */ if ( fe_get_packet( sc, len ) < 0 ) { /* Skip a packet, updating statistics. */ #if FE_DEBUG >= 2 log( LOG_WARNING, "%s%d: out of mbuf;" " dropping a packet (%u bytes)\n", sc->sc_unit, len ); #endif sc->sc_if.if_ierrors++; fe_droppacket( sc, len ); /* * We stop receiving packets, even if there are * more in the buffer. We hope we can get more * mbuf next time. */ return; } /* Successfully received a packet. Update stat. */ sc->sc_if.if_ipackets++; } } /* * Ethernet interface interrupt processor */ void feintr ( int unit ) { struct fe_softc *sc = &fe_softc[unit]; u_char tstat, rstat; /* * Loop until there are no more new interrupt conditions. */ for (;;) { #if FE_DEBUG >= 4 fe_dump( LOG_INFO, sc, "intr()" ); #endif /* * Get interrupt conditions, masking unneeded flags. */ tstat = inb( sc->ioaddr[ FE_DLCR0 ] ) & FE_TMASK; rstat = inb( sc->ioaddr[ FE_DLCR1 ] ) & FE_RMASK; if ( tstat == 0 && rstat == 0 ) break; /* * Reset the conditions we are acknowledging. */ outb( sc->ioaddr[ FE_DLCR0 ], tstat ); outb( sc->ioaddr[ FE_DLCR1 ], rstat ); /* * Handle transmitter interrupts. Handle these first because * the receiver will reset the board under some conditions. */ if ( tstat ) { fe_tint( sc, tstat ); } /* * Handle receiver interrupts */ if ( rstat ) { fe_rint( sc, rstat ); } /* * Update the multicast address filter if it is * needed and possible. We do it now, because * we can make sure the transmission buffer is empty, * and there is a good chance that the receive queue * is empty. It will minimize the possibility of * packet loss. */ if ( sc->filter_change && sc->txb_count == 0 && sc->txb_sched == 0 ) { fe_loadmar(sc); sc->sc_if.if_flags &= ~IFF_OACTIVE; } /* * If it looks like the transmitter can take more data, * attempt to start output on the interface. This is done * after handling the receiver interrupt to give the * receive operation priority. * * BTW, I'm not sure in what case the OACTIVE is on at * this point. Is the following test redundant? * * No. This routine polls for both transmitter and * receiver interrupts. 86960 can raise a receiver * interrupt when the transmission buffer is full. */ if ( ( sc->sc_if.if_flags & IFF_OACTIVE ) == 0 ) { fe_start( &sc->sc_if ); } } } /* * Process an ioctl request. This code needs some work - it looks * pretty ugly. */ static int fe_ioctl ( struct ifnet * ifp, int command, caddr_t data ) { struct fe_softc *sc = ifp->if_softc; int s, error = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: ioctl(%x)\n", sc->sc_unit, command ); #endif s = splimp(); switch (command) { case SIOCSIFADDR: { struct ifaddr * ifa = ( struct ifaddr * )data; sc->sc_if.if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: fe_init( sc->sc_unit ); /* before arp_ifinit */ arp_ifinit( &sc->arpcom, ifa ); break; #endif #ifdef IPX /* * XXX - This code is probably wrong */ case AF_IPX: { register struct ipx_addr *ina = &(IA_SIPX(ifa)->sipx_addr); if (ipx_nullhost(*ina)) ina->x_host = *(union ipx_host *) (sc->sc_enaddr); else { bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->sc_enaddr, sizeof(sc->sc_enaddr)); } /* * Set new address */ fe_init(sc->sc_unit); break; } #endif #ifdef INET6 case AF_INET6: /* IPV6 added by shin 96.2.6 */ fe_init(sc->sc_unit); ndp6_ifinit(&sc->arpcom, ifa); break; #endif #ifdef NS /* * XXX - This code is probably wrong */ case AF_NS: { register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); if (ns_nullhost(*ina)) ina->x_host = *(union ns_host *) (sc->sc_enaddr); else { bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->sc_enaddr, sizeof(sc->sc_enaddr)); } /* * Set new address */ fe_init(sc->sc_unit); break; } #endif default: fe_init( sc->sc_unit ); break; } break; } #ifdef SIOCGIFADDR case SIOCGIFADDR: { struct ifreq * ifr = ( struct ifreq * )data; struct sockaddr * sa = ( struct sockaddr * )&ifr->ifr_data; bcopy((caddr_t)sc->sc_enaddr, (caddr_t)sa->sa_data, ETHER_ADDR_LEN); break; } #endif #ifdef SIOCGIFPHYSADDR case SIOCGIFPHYSADDR: { struct ifreq * ifr = ( struct ifreq * )data; bcopy((caddr_t)sc->sc_enaddr, (caddr_t)&ifr->ifr_data, ETHER_ADDR_LEN); break; } #endif #ifdef notdef #ifdef SIOCSIFPHYSADDR case SIOCSIFPHYSADDR: { /* * Set the physical (Ethernet) address of the interface. * When and by whom is this command used? FIXME. */ struct ifreq * ifr = ( struct ifreq * )data; bcopy((caddr_t)&ifr->ifr_data, (caddr_t)sc->sc_enaddr, ETHER_ADDR_LEN); fe_setlinkaddr( sc ); break; } #endif #endif /* notdef */ #ifdef SIOCSIFFLAGS case SIOCSIFFLAGS: { /* * Switch interface state between "running" and * "stopped", reflecting the UP flag. */ if ( sc->sc_if.if_flags & IFF_UP ) { if ( ( sc->sc_if.if_flags & IFF_RUNNING ) == 0 ) { fe_init( sc->sc_unit ); } } else { if ( ( sc->sc_if.if_flags & IFF_RUNNING ) != 0 ) { fe_stop( sc->sc_unit ); } } /* * Promiscuous and/or multicast flags may have changed, * so reprogram the multicast filter and/or receive mode. */ fe_setmode( sc ); #if FE_DEBUG >= 1 /* "ifconfig fe0 debug" to print register dump. */ if ( sc->sc_if.if_flags & IFF_DEBUG ) { fe_dump( LOG_DEBUG, sc, "SIOCSIFFLAGS(DEBUG)" ); } #endif break; } #endif #ifdef SIOCADDMULTI case SIOCADDMULTI: case SIOCDELMULTI: { /* * Update out multicast list. */ struct ifreq * ifr = ( struct ifreq * )data; error = ( command == SIOCADDMULTI ) ? ether_addmulti( ifr, &sc->arpcom ) : ether_delmulti( ifr, &sc->arpcom ); if ( error == ENETRESET ) { /* * Multicast list has changed; set the hardware filter * accordingly. */ fe_setmode( sc ); error = 0; } break; } #endif #ifdef SIOCSIFMTU case SIOCSIFMTU: { /* * Set the interface MTU. */ struct ifreq * ifr = ( struct ifreq * )data; if ( ifr->ifr_mtu > ETHERMTU ) { error = EINVAL; } else { sc->sc_if.if_mtu = ifr->ifr_mtu; } break; } #endif default: error = EINVAL; } (void) splx(s); return (error); } /* * Retrieve packet from receive buffer and send to the next level up via * ether_input(). If there is a BPF listener, give a copy to BPF, too. * Returns 0 if success, -1 if error (i.e., mbuf allocation failure). */ static int fe_get_packet ( struct fe_softc * sc, u_short len ) { struct ether_header *eh; struct mbuf *m; /* * NFS wants the data be aligned to the word (4 byte) * boundary. Ethernet header has 14 bytes. There is a * 2-byte gap. */ #define NFS_MAGIC_OFFSET 2 /* * This function assumes that an Ethernet packet fits in an * mbuf (with a cluster attached when necessary.) On FreeBSD * 2.0 for x86, which is the primary target of this driver, an * mbuf cluster has 4096 bytes, and we are happy. On ancient * BSDs, such as vanilla 4.3 for 386, a cluster size was 1024, * however. If the following #error message were printed upon * compile, you need to rewrite this function. */ #if ( MCLBYTES < ETHER_MAX_LEN - ETHER_CRC_LEN + NFS_MAGIC_OFFSET ) #error "Too small MCLBYTES to use fe driver." #endif /* * Our strategy has one more problem. There is a policy on * mbuf cluster allocation. It says that we must have at * least MINCLSIZE (208 bytes on FreeBSD 2.0 for x86) to * allocate a cluster. For a packet of a size between * (MHLEN - 2) to (MINCLSIZE - 2), our code violates the rule... * On the other hand, the current code is short, simple, * and fast, however. It does no harmful thing, just waists * some memory. Any comments? FIXME. */ /* Allocate an mbuf with packet header info. */ MGETHDR(m, M_DONTWAIT, MT_DATA); if ( m == NULL ) return -1; /* Attach a cluster if this packet doesn't fit in a normal mbuf. */ if ( len > MHLEN - NFS_MAGIC_OFFSET ) { MCLGET( m, M_DONTWAIT ); if ( !( m->m_flags & M_EXT ) ) { m_freem( m ); return -1; } } /* Initialize packet header info. */ m->m_pkthdr.rcvif = &sc->sc_if; m->m_pkthdr.len = len; /* Set the length of this packet. */ m->m_len = len; /* The following silliness is to make NFS happy */ m->m_data += NFS_MAGIC_OFFSET; /* Get a packet. */ insw( sc->ioaddr[ FE_BMPR8 ], m->m_data, ( len + 1 ) >> 1 ); /* Get (actually just point to) the header part. */ eh = mtod( m, struct ether_header *); #define ETHER_ADDR_IS_MULTICAST(A) (*(char *)(A) & 1) #if NBPFILTER > 0 /* * Check if there's a BPF listener on this interface. * If it is, hand off the raw packet to bpf. */ if ( sc->sc_if.if_bpf ) { bpf_mtap( &sc->sc_if, m ); } #endif /* * Make sure this packet is (or may be) directed to us. * That is, the packet is either unicasted to our address, * or broad/multi-casted. If any other packets are * received, it is an indication of an error -- probably * 86960 is in a wrong operation mode. * Promiscuous mode is an exception. Under the mode, all * packets on the media must be received. (We must have * programmed the 86960 so.) */ if ( ( sc->sc_if.if_flags & IFF_PROMISC ) && !ETHER_ADDR_IS_MULTICAST( eh->ether_dhost ) && bcmp( eh->ether_dhost, sc->sc_enaddr, ETHER_ADDR_LEN ) != 0 ) { /* * The packet was not for us. This is normal since * we are now in promiscuous mode. Just drop the packet. */ m_freem( m ); return 0; } #if FE_DEBUG >= 3 if ( !ETHER_ADDR_IS_MULTICAST( eh->ether_dhost ) && bcmp( eh->ether_dhost, sc->sc_enaddr, ETHER_ADDR_LEN ) != 0 ) { /* * This packet was not for us. We can't be in promiscuous * mode since the case was handled by above test. * We found an error (of this driver.) */ log( LOG_WARNING, "fe%d: got an unwanted packet, dst = %6D\n", sc->sc_unit, eh->ether_dhost , ":" ); m_freem( m ); return 0; } #endif /* Strip off the Ethernet header. */ m->m_pkthdr.len -= sizeof ( struct ether_header ); m->m_len -= sizeof ( struct ether_header ); m->m_data += sizeof ( struct ether_header ); /* Feed the packet to upper layer. */ ether_input( &sc->sc_if, eh, m ); return 0; } /* * Write an mbuf chain to the transmission buffer memory using 16 bit PIO. * Returns number of bytes actually written, including length word. * * If an mbuf chain is too long for an Ethernet frame, it is not sent. * Packets shorter than Ethernet minimum are legal, and we pad them * before sending out. An exception is "partial" packets which are * shorter than mandatory Ethernet header. */ static void fe_write_mbufs ( struct fe_softc *sc, struct mbuf *m ) { u_short addr_bmpr8 = sc->ioaddr[ FE_BMPR8 ]; u_short length, len; struct mbuf *mp; u_char *data; u_short savebyte; /* WARNING: Architecture dependent! */ #define NO_PENDING_BYTE 0xFFFF static u_char padding [ ETHER_MIN_LEN - ETHER_CRC_LEN - ETHER_HDR_LEN ]; #if FE_DEBUG >= 2 /* First, count up the total number of bytes to copy */ length = 0; for ( mp = m; mp != NULL; mp = mp->m_next ) { length += mp->m_len; } /* Check if this matches the one in the packet header. */ if ( length != m->m_pkthdr.len ) { log( LOG_WARNING, "fe%d: packet length mismatch? (%d/%d)\n", sc->sc_unit, length, m->m_pkthdr.len ); } #else /* Just use the length value in the packet header. */ length = m->m_pkthdr.len; #endif #if FE_DEBUG >= 1 /* * Should never send big packets. If such a packet is passed, * it should be a bug of upper layer. We just ignore it. * ... Partial (too short) packets, neither. */ if ( length < ETHER_HDR_LEN || length > ETHER_MAX_LEN - ETHER_CRC_LEN ) { log( LOG_ERR, "fe%d: got an out-of-spec packet (%u bytes) to send\n", sc->sc_unit, length ); sc->sc_if.if_oerrors++; return; } #endif /* * Put the length word for this frame. * Does 86960 accept odd length? -- Yes. * Do we need to pad the length to minimum size by ourselves? * -- Generally yes. But for (or will be) the last * packet in the transmission buffer, we can skip the * padding process. It may gain performance slightly. FIXME. */ outw( addr_bmpr8, max( length, ETHER_MIN_LEN - ETHER_CRC_LEN ) ); /* * Update buffer status now. * Truncate the length up to an even number, since we use outw(). */ length = ( length + 1 ) & ~1; sc->txb_free -= FE_DATA_LEN_LEN + max( length, ETHER_MIN_LEN - ETHER_CRC_LEN); sc->txb_count++; /* * Transfer the data from mbuf chain to the transmission buffer. * MB86960 seems to require that data be transferred as words, and * only words. So that we require some extra code to patch * over odd-length mbufs. */ savebyte = NO_PENDING_BYTE; for ( mp = m; mp != 0; mp = mp->m_next ) { /* Ignore empty mbuf. */ len = mp->m_len; if ( len == 0 ) continue; /* Find the actual data to send. */ data = mtod(mp, caddr_t); /* Finish the last byte. */ if ( savebyte != NO_PENDING_BYTE ) { outw( addr_bmpr8, savebyte | ( *data << 8 ) ); data++; len--; savebyte = NO_PENDING_BYTE; } /* output contiguous words */ if (len > 1) { outsw( addr_bmpr8, data, len >> 1); data += len & ~1; len &= 1; } /* Save a remaining byte, if there is one. */ if ( len > 0 ) { savebyte = *data; } } /* Spit the last byte, if the length is odd. */ if ( savebyte != NO_PENDING_BYTE ) { outw( addr_bmpr8, savebyte ); } /* Pad to the Ethernet minimum length, if the packet is too short. */ if ( length < ETHER_MIN_LEN - ETHER_CRC_LEN ) { outsw( addr_bmpr8, padding, ( ETHER_MIN_LEN - ETHER_CRC_LEN - length ) >> 1); } } /* * Compute hash value for an Ethernet address */ static int fe_hash ( u_char * ep ) { #define FE_HASH_MAGIC_NUMBER 0xEDB88320L u_long hash = 0xFFFFFFFFL; int i, j; u_char b; u_long m; for ( i = ETHER_ADDR_LEN; --i >= 0; ) { b = *ep++; for ( j = 8; --j >= 0; ) { m = hash; hash >>= 1; if ( ( m ^ b ) & 1 ) hash ^= FE_HASH_MAGIC_NUMBER; b >>= 1; } } return ( ( int )( hash >> 26 ) ); } /* * Compute the multicast address filter from the * list of multicast addresses we need to listen to. */ static struct fe_filter fe_mcaf ( struct fe_softc *sc ) { int index; struct fe_filter filter; struct ether_multi *enm; struct ether_multistep step; filter = fe_filter_nothing; ETHER_FIRST_MULTI(step, &sc->arpcom, enm); while ( enm != NULL) { if ( bcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN) ) { return ( fe_filter_all ); } index = fe_hash( enm->enm_addrlo ); #if FE_DEBUG >= 4 log( LOG_INFO, "fe%d: hash(%6D) == %d\n", sc->sc_unit, enm->enm_addrlo , ":", index ); #endif filter.data[index >> 3] |= 1 << (index & 7); ETHER_NEXT_MULTI(step, enm); } return ( filter ); } /* * Calculate a new "multicast packet filter" and put the 86960 * receiver in appropriate mode. */ static void fe_setmode ( struct fe_softc *sc ) { int flags = sc->sc_if.if_flags; /* * If the interface is not running, we postpone the update * process for receive modes and multicast address filter * until the interface is restarted. It reduces some * complicated job on maintaining chip states. (Earlier versions * of this driver had a bug on that point...) * * To complete the trick, fe_init() calls fe_setmode() after * restarting the interface. */ if ( !( flags & IFF_RUNNING ) ) return; /* * Promiscuous mode is handled separately. */ if ( flags & IFF_PROMISC ) { /* * Program 86960 to receive all packets on the segment * including those directed to other stations. * Multicast filter stored in MARs are ignored * under this setting, so we don't need to update it. * * Promiscuous mode in FreeBSD 2 is used solely by * BPF, and BPF only listens to valid (no error) packets. * So, we ignore erroneous ones even in this mode. * (Older versions of fe driver mistook the point.) */ outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 | FE_D5_AFM0 | FE_D5_AFM1 ); sc->filter_change = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: promiscuous mode\n", sc->sc_unit ); #endif return; } /* * Turn the chip to the normal (non-promiscuous) mode. */ outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 | FE_D5_AFM1 ); /* * Find the new multicast filter value. * I'm not sure we have to handle modes other than MULTICAST. * Who sets ALLMULTI? Who turns MULTICAST off? FIXME. */ if ( flags & IFF_ALLMULTI ) { sc->filter = fe_filter_all; } else if ( flags & IFF_MULTICAST ) { sc->filter = fe_mcaf( sc ); } else { sc->filter = fe_filter_nothing; } sc->filter_change = 1; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: address filter: [%8D]\n", sc->sc_unit, sc->filter.data, " " ); #endif /* * We have to update the multicast filter in the 86960, A.S.A.P. * * Note that the DLC (Data Link Control unit, i.e. transmitter * and receiver) must be stopped when feeding the filter, and * DLC trashes all packets in both transmission and receive * buffers when stopped. * * ... Are the above sentences correct? I have to check the * manual of the MB86960A. FIXME. * * To reduce the packet loss, we delay the filter update * process until buffers are empty. */ if ( sc->txb_sched == 0 && sc->txb_count == 0 && !( inb( sc->ioaddr[ FE_DLCR1 ] ) & FE_D1_PKTRDY ) ) { /* * Buffers are (apparently) empty. Load * the new filter value into MARs now. */ fe_loadmar(sc); } else { /* * Buffers are not empty. Mark that we have to update * the MARs. The new filter will be loaded by feintr() * later. */ #if FE_DEBUG >= 4 log( LOG_INFO, "fe%d: filter change delayed\n", sc->sc_unit ); #endif } } /* * Load a new multicast address filter into MARs. * * The caller must have splimp'ed before fe_loadmar. * This function starts the DLC upon return. So it can be called only * when the chip is working, i.e., from the driver's point of view, when * a device is RUNNING. (I mistook the point in previous versions.) */ static void fe_loadmar ( struct fe_softc * sc ) { /* Stop the DLC (transmitter and receiver). */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Select register bank 1 for MARs. */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_MAR | FE_D7_POWER_UP ); /* Copy filter value into the registers. */ outblk( sc, FE_MAR8, sc->filter.data, FE_FILTER_LEN ); /* Restore the bank selection for BMPRs (i.e., runtime registers). */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_BMPR | FE_D7_POWER_UP ); /* Restart the DLC. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_ENABLE ); DELAY( 200 ); /* We have just updated the filter. */ sc->filter_change = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: address filter changed\n", sc->sc_unit ); #endif } #if FE_DEBUG >= 1 static void fe_dump ( int level, struct fe_softc * sc, char * message ) { log( level, "fe%d: %s," " DLCR = %02x %02x %02x %02x %02x %02x %02x %02x," " BMPR = xx xx %02x %02x %02x %02x %02x %02x," " asic = %02x %02x %02x %02x %02x %02x %02x %02x" " + %02x %02x %02x %02x %02x %02x %02x %02x\n", sc->sc_unit, message ? message : "registers", inb( sc->ioaddr[ FE_DLCR0 ] ), inb( sc->ioaddr[ FE_DLCR1 ] ), inb( sc->ioaddr[ FE_DLCR2 ] ), inb( sc->ioaddr[ FE_DLCR3 ] ), inb( sc->ioaddr[ FE_DLCR4 ] ), inb( sc->ioaddr[ FE_DLCR5 ] ), inb( sc->ioaddr[ FE_DLCR6 ] ), inb( sc->ioaddr[ FE_DLCR7 ] ), inb( sc->ioaddr[ FE_BMPR10 ] ), inb( sc->ioaddr[ FE_BMPR11 ] ), inb( sc->ioaddr[ FE_BMPR12 ] ), inb( sc->ioaddr[ FE_BMPR13 ] ), inb( sc->ioaddr[ FE_BMPR14 ] ), inb( sc->ioaddr[ FE_BMPR15 ] ), inb( sc->ioaddr[ 0x10 ] ), inb( sc->ioaddr[ 0x11 ] ), inb( sc->ioaddr[ 0x12 ] ), inb( sc->ioaddr[ 0x13 ] ), inb( sc->ioaddr[ 0x14 ] ), inb( sc->ioaddr[ 0x15 ] ), inb( sc->ioaddr[ 0x16 ] ), inb( sc->ioaddr[ 0x17 ] ), inb( sc->ioaddr[ 0x18 ] ), inb( sc->ioaddr[ 0x19 ] ), inb( sc->ioaddr[ 0x1A ] ), inb( sc->ioaddr[ 0x1B ] ), inb( sc->ioaddr[ 0x1C ] ), inb( sc->ioaddr[ 0x1D ] ), inb( sc->ioaddr[ 0x1E ] ), inb( sc->ioaddr[ 0x1F ] ) ); } #endif diff --git a/sys/i386/isa/if_lnc.c b/sys/i386/isa/if_lnc.c index 20201e46a93d..0df6bbe715f2 100644 --- a/sys/i386/isa/if_lnc.c +++ b/sys/i386/isa/if_lnc.c @@ -1,1860 +1,1860 @@ /*- * Copyright (c) 1995, 1996 * Paul Richards. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * verbatim and that no modifications are made prior to this * point in the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Paul Richards. * 4. The name Paul Richards may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY PAUL RICHARDS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL PAUL RICHARDS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* #define LNC_MULTICAST #define DIAGNOSTIC #define DEBUG * * TODO ---- * * This driver will need bounce buffer support when dma'ing to mbufs above the * 16Mb mark. * * Check all the XXX comments -- some of them are just things I've left * unfinished rather than "difficult" problems that were hacked around. * * Check log settings. * * Check how all the arpcom flags get set and used. * * Re-inline and re-static all routines after debugging. * * Remember to assign iobase in SHMEM probe routines. * * Replace all occurences of LANCE-controller-card etc in prints by the name * strings of the appropriate type -- nifty window dressing * * Add DEPCA support -- mostly done. * */ #include "pci.h" #include "lnc.h" #if NLNC > 0 #include "bpfilter.h" /* Some defines that should really be in generic locations */ #define FCS_LEN 4 #define MULTICAST_FILTER_LEN 8 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #if NBPFILTER > 0 #include #include #endif #include #include #include struct lnc_softc { struct arpcom arpcom; /* see ../../netinet/if_ether.h */ struct nic_info nic; /* NIC specific info */ int nrdre; struct host_ring_entry *recv_ring; /* start of alloc'd mem */ int recv_next; int ntdre; struct host_ring_entry *trans_ring; int trans_next; struct init_block *init_block; /* Initialisation block */ int pending_transmits; /* No. of transmit descriptors in use */ int next_to_send; struct mbuf *mbufs; int mbuf_count; int initialised; int rap; int rdp; #ifdef DEBUG int lnc_debug; #endif LNCSTATS_STRUCT }; static struct lnc_softc lnc_softc[NLNC]; static char const * const nic_ident[] = { "Unknown", "BICC", "NE2100", "DEPCA", }; static char const * const ic_ident[] = { "Unknown", "LANCE", "C-LANCE", "PCnet-ISA", "PCnet-ISA+", "PCnet-32 VL-Bus", "PCnet-PCI", /* "can't happen" */ }; #ifdef LNC_MULTICAST static void lnc_setladrf __P((struct lnc_softc *sc)); #endif static void lnc_stop __P((struct lnc_softc *sc)); static void lnc_reset __P((struct lnc_softc *sc)); static void lnc_free_mbufs __P((struct lnc_softc *sc)); static int alloc_mbuf_cluster __P((struct lnc_softc *sc, struct host_ring_entry *desc)); static struct mbuf *chain_mbufs __P((struct lnc_softc *sc, int start_of_packet, int pkt_len)); static struct mbuf *mbuf_packet __P((struct lnc_softc *sc, int start_of_packet, int pkt_len)); static void lnc_rint __P((struct lnc_softc *sc)); static void lnc_tint __P((struct lnc_softc *sc)); static int lnc_probe __P((struct isa_device *isa_dev)); static int ne2100_probe __P((struct lnc_softc *sc, unsigned iobase)); static int bicc_probe __P((struct lnc_softc *sc, unsigned iobase)); static int dec_macaddr_extract __P((u_char ring[], struct lnc_softc *sc)); static int depca_probe __P((struct lnc_softc *sc, unsigned iobase)); static int lance_probe __P((struct lnc_softc *sc)); static int pcnet_probe __P((struct lnc_softc *sc)); static int lnc_attach_sc __P((struct lnc_softc *sc, int unit)); static int lnc_attach __P((struct isa_device *isa_dev)); static void lnc_init __P((struct lnc_softc *sc)); static int mbuf_to_buffer __P((struct mbuf *m, char *buffer)); static struct mbuf *chain_to_cluster __P((struct mbuf *m)); static void lnc_start __P((struct ifnet *ifp)); static int lnc_ioctl __P((struct ifnet *ifp, int command, caddr_t data)); static void lnc_watchdog __P((struct ifnet *ifp)); #ifdef DEBUG static void lnc_dump_state __P((struct lnc_softc *sc)); static void mbuf_dump_chain __P((struct mbuf *m)); #endif #if NPCI > 0 void *lnc_attach_ne2100_pci __P((int unit, unsigned iobase)); #endif void lncintr_sc __P((struct lnc_softc *sc)); struct isa_driver lncdriver = {lnc_probe, lnc_attach, "lnc"}; static inline void write_csr(struct lnc_softc *sc, u_short port, u_short val) { outw(sc->rap, port); outw(sc->rdp, val); } static inline u_short read_csr(struct lnc_softc *sc, u_short port) { outw(sc->rap, port); return (inw(sc->rdp)); } #ifdef LNC_MULTICAST static inline u_long ether_crc(u_char *ether_addr) { #define POLYNOMIAL 0x04c11db6 u_long crc = 0xffffffffL; int i, j, carry; u_char b; for (i = ETHER_ADDR_LEN; --i >= 0;) { b = *ether_addr++; for (j = 8; --j >= 0;) { carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); crc <<= 1; b >>= 1; if (carry) crc = ((crc ^ POLYNOMIAL) | carry); } } return crc; #undef POLYNOMIAL } /* * Set up the logical address filter for multicast packets */ static void lnc_setladrf(struct lnc_softc *sc) { struct ifnet *ifp = &sc->arpcom.ac_if; struct ether_multistep step; struct ether_multi *enm; u_long index; int i; /* If promiscuous mode is set then all packets are accepted anyway */ if (ifp->if_flags & IFF_PROMISC) { ifp->if_flags |= IFF_ALLMULTI; for (i = 0; i < MULTICAST_FILTER_LEN; i++) sc->init_block->ladrf[i] = 0xff; return; } /* * For each multicast address, calculate a crc for that address and * then use the high order 6 bits of the crc as a hash code where * bits 3-5 select the byte of the address filter and bits 0-2 select * the bit within that byte. */ bzero(sc->init_block->ladrf, MULTICAST_FILTER_LEN); ETHER_FIRST_MULTI(step, &sc->arpcom, enm); while (enm != NULL) { if (bcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN) != 0) { /* * A range of multicast addresses should be accepted but * but for now just accept all multicasts. Only currently * used by multicast routing where the range would require * all bits to be set anyway. */ ifp->if_flags |= IFF_ALLMULTI; for (i = 0; i < MULTICAST_FILTER_LEN; i++) sc->init_block->ladrf[i] = 0xff; return; } index = ether_crc(enm->enm_addrlo) >> 26; sc->init_block->ladrf[index >> 3] |= 1 << (index & 7); ETHER_NEXT_MULTI(step, enm); } } #endif /* LNC_MULTICAST */ static void lnc_stop(struct lnc_softc *sc) { write_csr(sc, CSR0, STOP); } static void lnc_reset(struct lnc_softc *sc) { lnc_init(sc); } static void lnc_free_mbufs(struct lnc_softc *sc) { int i; /* * We rely on other routines to keep the buff.mbuf field valid. If * it's not NULL then we assume it points to an allocated mbuf. */ for (i = 0; i < NDESC(sc->nrdre); i++) if ((sc->recv_ring + i)->buff.mbuf) m_free((sc->recv_ring + i)->buff.mbuf); for (i = 0; i < NDESC(sc->ntdre); i++) if ((sc->trans_ring + i)->buff.mbuf) m_free((sc->trans_ring + i)->buff.mbuf); if (sc->mbuf_count) m_freem(sc->mbufs); } static inline int alloc_mbuf_cluster(struct lnc_softc *sc, struct host_ring_entry *desc) { register struct mds *md = desc->md; struct mbuf *m=0; int addr; /* Try and get cluster off local cache */ if (sc->mbuf_count) { sc->mbuf_count--; m = sc->mbufs; sc->mbufs = m->m_next; /* XXX m->m_data = m->m_ext.ext_buf;*/ } else { MGET(m, M_DONTWAIT, MT_DATA); if (!m) return(1); MCLGET(m, M_DONTWAIT); if (!m->m_ext.ext_buf) { m_free(m); return(1); } } desc->buff.mbuf = m; addr = kvtop(m->m_data); md->md0 = addr; md->md1= ((addr >> 16) & 0xff) | OWN; md->md2 = -(short)(MCLBYTES - sizeof(struct pkthdr)); md->md3 = 0; return(0); } static inline struct mbuf * chain_mbufs(struct lnc_softc *sc, int start_of_packet, int pkt_len) { struct mbuf *head, *m; struct host_ring_entry *desc; /* * Turn head into a pkthdr mbuf -- * assumes a pkthdr type mbuf was * allocated to the descriptor * originally. */ desc = sc->recv_ring + start_of_packet; head = desc->buff.mbuf; head->m_flags |= M_PKTHDR; m = head; do { m = desc->buff.mbuf; m->m_len = min((MCLBYTES - sizeof(struct pkthdr)), pkt_len); pkt_len -= m->m_len; if (alloc_mbuf_cluster(sc, desc)) return((struct mbuf *)NULL); INC_MD_PTR(start_of_packet, sc->nrdre) desc = sc->recv_ring + start_of_packet; m->m_next = desc->buff.mbuf; } while (start_of_packet != sc->recv_next); m->m_next = 0; return(head); } static inline struct mbuf * mbuf_packet(struct lnc_softc *sc, int start_of_packet, int pkt_len) { struct host_ring_entry *start; struct mbuf *head,*m,*m_prev; char *data,*mbuf_data; short blen; int amount; /* Get a pkthdr mbuf for the start of packet */ MGETHDR(head, M_DONTWAIT, MT_DATA); if (!head) { LNCSTATS(drop_packet) return(0); } m = head; m->m_len = 0; start = sc->recv_ring + start_of_packet; /*blen = -(start->md->md2);*/ blen = RECVBUFSIZE; /* XXX More PCnet-32 crap */ data = start->buff.data; mbuf_data = m->m_data; while (start_of_packet != sc->recv_next) { /* * If the data left fits in a single buffer then set * blen to the size of the data left. */ if (pkt_len < blen) blen = pkt_len; /* * amount is least of data in current ring buffer and * amount of space left in current mbuf. */ amount = min(blen, M_TRAILINGSPACE(m)); if (amount == 0) { /* mbuf must be empty */ m_prev = m; MGET(m, M_DONTWAIT, MT_DATA); if (!m) { m_freem(head); return(0); } if (pkt_len >= MINCLSIZE) MCLGET(m, M_DONTWAIT); m->m_len = 0; m_prev->m_next = m; amount = min(blen, M_TRAILINGSPACE(m)); mbuf_data = m->m_data; } bcopy(data, mbuf_data, amount); blen -= amount; pkt_len -= amount; m->m_len += amount; data += amount; mbuf_data += amount; if (blen == 0) { start->md->md1 &= HADR; start->md->md1 |= OWN; start->md->md2 = -RECVBUFSIZE; /* XXX - shouldn't be necessary */ INC_MD_PTR(start_of_packet, sc->nrdre) start = sc->recv_ring + start_of_packet; data = start->buff.data; /*blen = -(start->md->md2);*/ blen = RECVBUFSIZE; /* XXX More PCnet-32 crap */ } } return(head); } static inline void lnc_rint(struct lnc_softc *sc) { struct host_ring_entry *next, *start; int start_of_packet; struct mbuf *head; struct ether_header *eh; int lookahead; int flags; int pkt_len; /* * The LANCE will issue a RINT interrupt when the ownership of the * last buffer of a receive packet has been relinquished by the LANCE. * Therefore, it can be assumed that a complete packet can be found * before hitting buffers that are still owned by the LANCE, if not * then there is a bug in the driver that is causing the descriptors * to get out of sync. */ #ifdef DIAGNOSTIC if ((sc->recv_ring + sc->recv_next)->md->md1 & OWN) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Receive interrupt with buffer still owned by controller -- Resetting\n", unit); lnc_reset(sc); return; } if (!((sc->recv_ring + sc->recv_next)->md->md1 & STP)) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Receive interrupt but not start of packet -- Resetting\n", unit); lnc_reset(sc); return; } #endif lookahead = 0; next = sc->recv_ring + sc->recv_next; while ((flags = next->md->md1) & STP) { /* Make a note of the start of the packet */ start_of_packet = sc->recv_next; /* * Find the end of the packet. Even if not data chaining, * jabber packets can overrun into a second descriptor. * If there is no error, then the ENP flag is set in the last * descriptor of the packet. If there is an error then the ERR * flag will be set in the descriptor where the error occured. * Therefore, to find the last buffer of a packet we search for * either ERR or ENP. */ if (!(flags & (ENP | MDERR))) { do { INC_MD_PTR(sc->recv_next, sc->nrdre) next = sc->recv_ring + sc->recv_next; flags = next->md->md1; } while (!(flags & (STP | OWN | ENP | MDERR))); if (flags & STP) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Start of packet found before end of previous in receive ring -- Resetting\n", unit); lnc_reset(sc); return; } if (flags & OWN) { if (lookahead) { /* * Looked ahead into a packet still * being received */ sc->recv_next = start_of_packet; break; } else { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: End of received packet not found-- Resetting\n", unit); lnc_reset(sc); return; } } } pkt_len = (next->md->md3 & MCNT) - FCS_LEN; /* Move pointer onto start of next packet */ INC_MD_PTR(sc->recv_next, sc->nrdre) next = sc->recv_ring + sc->recv_next; if (flags & MDERR) { int unit = sc->arpcom.ac_if.if_unit; if (flags & RBUFF) { LNCSTATS(rbuff) log(LOG_ERR, "lnc%d: Receive buffer error\n", unit); } if (flags & OFLO) { /* OFLO only valid if ENP is not set */ if (!(flags & ENP)) { LNCSTATS(oflo) log(LOG_ERR, "lnc%d: Receive overflow error \n", unit); } } else if (flags & ENP) { /* * FRAM and CRC are valid only if ENP * is set and OFLO is not. */ if (flags & FRAM) { LNCSTATS(fram) log(LOG_ERR, "lnc%d: Framming error\n", unit); /* * FRAM is only set if there's a CRC * error so avoid multiple messages */ } else if (flags & CRC) { LNCSTATS(crc) log(LOG_ERR, "lnc%d: Receive CRC error\n", unit); } } /* Drop packet */ LNCSTATS(rerr) sc->arpcom.ac_if.if_ierrors++; while (start_of_packet != sc->recv_next) { start = sc->recv_ring + start_of_packet; start->md->md2 = -RECVBUFSIZE; /* XXX - shouldn't be necessary */ start->md->md1 &= HADR; start->md->md1 |= OWN; INC_MD_PTR(start_of_packet, sc->nrdre) } } else { /* Valid packet */ sc->arpcom.ac_if.if_ipackets++; if (sc->nic.mem_mode == DMA_MBUF) head = chain_mbufs(sc, start_of_packet, pkt_len); else head = mbuf_packet(sc, start_of_packet, pkt_len); if (head) { /* * First mbuf in packet holds the * ethernet and packet headers */ head->m_pkthdr.rcvif = &sc->arpcom.ac_if; head->m_pkthdr.len = pkt_len - sizeof *eh; /* * BPF expects the ether header to be in the first * mbuf of the chain so point eh at the right place * but don't increment the mbuf pointers before * the bpf tap. */ eh = (struct ether_header *) head->m_data; #if NBPFILTER > 0 if (sc->arpcom.ac_if.if_bpf) bpf_mtap(&sc->arpcom.ac_if, head); /* Check this packet is really for us */ if ((sc->arpcom.ac_if.if_flags & IFF_PROMISC) && !(eh->ether_dhost[0] & 1) && /* Broadcast and multicast */ (bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, sizeof(eh->ether_dhost)))) m_freem(head); else #endif { /* Skip over the ether header */ head->m_data += sizeof *eh; head->m_len -= sizeof *eh; ether_input(&sc->arpcom.ac_if, eh, head); } } else { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR,"lnc%d: Packet dropped, no mbufs\n",unit); LNCSTATS(drop_packet) } } lookahead++; } /* * At this point all completely received packets have been processed * so clear RINT since any packets that have arrived while we were in * here have been dealt with. */ outw(sc->rdp, RINT | INEA); } static inline void lnc_tint(struct lnc_softc *sc) { struct host_ring_entry *next, *start; int start_of_packet; int lookahead; /* * If the driver is reset in this routine then we return immediately to * the interrupt driver routine. Any interrupts that have occured * since the reset will be dealt with there. sc->trans_next * should point to the start of the first packet that was awaiting * transmission after the last transmit interrupt was dealt with. The * LANCE should have relinquished ownership of that descriptor before * the interrupt. Therefore, sc->trans_next should point to a * descriptor with STP set and OWN cleared. If not then the driver's * pointers are out of sync with the LANCE, which signifies a bug in * the driver. Therefore, the following two checks are really * diagnostic, since if the driver is working correctly they should * never happen. */ #ifdef DIAGNOSTIC if ((sc->trans_ring + sc->trans_next)->md->md1 & OWN) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Transmit interrupt with buffer still owned by controller -- Resetting\n", unit); lnc_reset(sc); return; } #endif /* * The LANCE will write the status information for the packet it just * tried to transmit in one of two places. If the packet was * transmitted successfully then the status will be written into the * last descriptor of the packet. If the transmit failed then the * status will be written into the descriptor that was being accessed * when the error occured and all subsequent descriptors in that * packet will have been relinquished by the LANCE. * * At this point we know that sc->trans_next points to the start * of a packet that the LANCE has just finished trying to transmit. * We now search for a buffer with either ENP or ERR set. */ lookahead = 0; do { start_of_packet = sc->trans_next; next = sc->trans_ring + sc->trans_next; #ifdef DIAGNOSTIC if (!(next->md->md1 & STP)) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Transmit interrupt but not start of packet -- Resetting\n", unit); lnc_reset(sc); return; } #endif /* * Find end of packet. */ if (!(next->md->md1 & (ENP | MDERR))) { do { INC_MD_PTR(sc->trans_next, sc->ntdre) next = sc->trans_ring + sc->trans_next; } while (!(next->md->md1 & (STP | OWN | ENP | MDERR))); if (next->md->md1 & STP) { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: Start of packet found before end of previous in transmit ring -- Resetting\n", unit); lnc_reset(sc); return; } if (next->md->md1 & OWN) { if (lookahead) { /* * Looked ahead into a packet still * being transmitted */ sc->trans_next = start_of_packet; break; } else { int unit = sc->arpcom.ac_if.if_unit; log(LOG_ERR, "lnc%d: End of transmitted packet not found -- Resetting\n", unit); lnc_reset(sc); return; } } } /* * Check for ERR first since other flags are irrelevant if an * error occurred. */ if (next->md->md1 & MDERR) { int unit = sc->arpcom.ac_if.if_unit; LNCSTATS(terr) sc->arpcom.ac_if.if_oerrors++; if (next->md->md3 & LCOL) { LNCSTATS(lcol) log(LOG_ERR, "lnc%d: Transmit late collision -- Net error?\n", unit); sc->arpcom.ac_if.if_collisions++; /* * Clear TBUFF since it's not valid when LCOL * set */ next->md->md3 &= ~TBUFF; } if (next->md->md3 & LCAR) { LNCSTATS(lcar) log(LOG_ERR, "lnc%d: Loss of carrier during transmit -- Net error?\n", unit); } if (next->md->md3 & RTRY) { LNCSTATS(rtry) log(LOG_ERR, "lnc%d: Transmit of packet failed after 16 attempts -- TDR = %d\n", unit, ((sc->trans_ring + sc->trans_next)->md->md3 & TDR)); sc->arpcom.ac_if.if_collisions += 16; /* * Clear TBUFF since it's not valid when RTRY * set */ next->md->md3 &= ~TBUFF; } /* * TBUFF is only valid if neither LCOL nor RTRY are set. * We need to check UFLO after LCOL and RTRY so that we * know whether or not TBUFF is valid. If either are * set then TBUFF will have been cleared above. A * UFLO error will turn off the transmitter so we * have to reset. * */ if (next->md->md3 & UFLO) { LNCSTATS(uflo) /* * If an UFLO has occured it's possibly due * to a TBUFF error */ if (next->md->md3 & TBUFF) { LNCSTATS(tbuff) log(LOG_ERR, "lnc%d: Transmit buffer error -- Resetting\n", unit); } else log(LOG_ERR, "lnc%d: Transmit underflow error -- Resetting\n", unit); lnc_reset(sc); return; } do { INC_MD_PTR(sc->trans_next, sc->ntdre) next = sc->trans_ring + sc->trans_next; } while (!(next->md->md1 & STP) && (sc->trans_next != sc->next_to_send)); } else { /* * Since we check for ERR first then if we get here * the packet was transmitted correctly. There may * still have been non-fatal errors though. * Don't bother checking for DEF, waste of time. */ sc->arpcom.ac_if.if_opackets++; if (next->md->md1 & MORE) { LNCSTATS(more) sc->arpcom.ac_if.if_collisions += 2; } /* * ONE is invalid if LCOL is set. If LCOL was set then * ERR would have also been set and we would have * returned from lnc_tint above. Therefore we can * assume if we arrive here that ONE is valid. * */ if (next->md->md1 & ONE) { LNCSTATS(one) sc->arpcom.ac_if.if_collisions++; } INC_MD_PTR(sc->trans_next, sc->ntdre) next = sc->trans_ring + sc->trans_next; } /* * Clear descriptors and free any mbufs. */ do { start = sc->trans_ring + start_of_packet; start->md->md1 &= HADR; if (sc->nic.mem_mode == DMA_MBUF) { /* Cache clusters on a local queue */ if ((start->buff.mbuf->m_flags & M_EXT) && (sc->mbuf_count < MBUF_CACHE_LIMIT)) { if (sc->mbuf_count) { start->buff.mbuf->m_next = sc->mbufs; sc->mbufs = start->buff.mbuf; } else sc->mbufs = start->buff.mbuf; sc->mbuf_count++; start->buff.mbuf = 0; } else { struct mbuf *junk; MFREE(start->buff.mbuf, junk); start->buff.mbuf = 0; } } sc->pending_transmits--; INC_MD_PTR(start_of_packet, sc->ntdre) }while (start_of_packet != sc->trans_next); /* * There's now at least one free descriptor * in the ring so indicate that we can accept * more packets again. */ sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; lookahead++; } while (sc->pending_transmits && !(next->md->md1 & OWN)); /* * Clear TINT since we've dealt with all * the completed transmissions. */ outw(sc->rdp, TINT | INEA); /* XXX only while doing if_is comparisons */ if (!(sc->arpcom.ac_if.if_flags & IFF_OACTIVE)) lnc_start(&sc->arpcom.ac_if); } static int lnc_probe(struct isa_device * isa_dev) { int nports; int unit = isa_dev->id_unit; struct lnc_softc *sc = &lnc_softc[unit]; unsigned iobase = isa_dev->id_iobase; #ifdef DIAGNOSTIC int vsw; vsw = inw(isa_dev->id_iobase + PCNET_VSW); printf("Vendor Specific Word = %x\n", vsw); #endif nports = bicc_probe(sc, iobase); if (nports == 0) nports = ne2100_probe(sc, iobase); if (nports == 0) nports = depca_probe(sc, iobase); return (nports); } static int ne2100_probe(struct lnc_softc *sc, unsigned iobase) { int i; sc->rap = iobase + PCNET_RAP; sc->rdp = iobase + PCNET_RDP; if ((sc->nic.ic = pcnet_probe(sc))) { sc->nic.ident = NE2100; sc->nic.mem_mode = DMA_FIXED; /* XXX - For now just use the defines */ sc->nrdre = NRDRE; sc->ntdre = NTDRE; /* Extract MAC address from PROM */ for (i = 0; i < ETHER_ADDR_LEN; i++) sc->arpcom.ac_enaddr[i] = inb(iobase + i); return (NE2100_IOSIZE); } else { return (0); } } static int bicc_probe(struct lnc_softc *sc, unsigned iobase) { int i; /* * There isn't any way to determine if a NIC is a BICC. Basically, if * the lance probe succeeds using the i/o addresses of the BICC then * we assume it's a BICC. * */ sc->rap = iobase + BICC_RAP; sc->rdp = iobase + BICC_RDP; /* I think all these cards us the Am7990 */ if ((sc->nic.ic = lance_probe(sc))) { sc->nic.ident = BICC; sc->nic.mem_mode = DMA_FIXED; /* XXX - For now just use the defines */ sc->nrdre = NRDRE; sc->ntdre = NTDRE; /* Extract MAC address from PROM */ for (i = 0; i < ETHER_ADDR_LEN; i++) sc->arpcom.ac_enaddr[i] = inb(iobase + (i * 2)); return (BICC_IOSIZE); } else { return (0); } } /* * I don't have data sheets for the dec cards but it looks like the mac * address is contained in a 32 byte ring. Each time you read from the port * you get the next byte in the ring. The mac address is stored after a * signature so keep searching for the signature first. */ static int dec_macaddr_extract(u_char ring[], struct lnc_softc * sc) { const unsigned char signature[] = {0xff, 0x00, 0x55, 0xaa, 0xff, 0x00, 0x55, 0xaa}; int i, j, rindex; for (i = 0; i < sizeof ring; i++) { for (j = 0, rindex = i; j < sizeof signature; j++) { if (ring[rindex] != signature[j]) break; if (++rindex > sizeof ring) rindex = 0; } if (j == sizeof signature) { for (j = 0, rindex = i; j < ETHER_ADDR_LEN; j++) { sc->arpcom.ac_enaddr[j] = ring[rindex]; if (++rindex > sizeof ring) rindex = 0; } return (1); } } return (0); } static int depca_probe(struct lnc_softc *sc, unsigned iobase) { int i; unsigned char maddr_ring[DEPCA_ADDR_ROM_SIZE]; sc->rap = iobase + DEPCA_RAP; sc->rdp = iobase + DEPCA_RDP; if ((sc->nic.ic = lance_probe(sc))) { sc->nic.ident = DEPCA; sc->nic.mem_mode = SHMEM; /* Extract MAC address from PROM */ for (i = 0; i < DEPCA_ADDR_ROM_SIZE; i++) maddr_ring[i] = inb(iobase + DEPCA_ADP); if (dec_macaddr_extract(maddr_ring, sc)) { return (DEPCA_IOSIZE); } } return (0); } static int lance_probe(struct lnc_softc *sc) { write_csr(sc, CSR0, STOP); if ((inw(sc->rdp) & STOP) && !(read_csr(sc, CSR3))) { /* * Check to see if it's a C-LANCE. For the LANCE the INEA bit * cannot be set while the STOP bit is. This restriction is * removed for the C-LANCE. */ write_csr(sc, CSR0, INEA); if (read_csr(sc, CSR0) & INEA) return (C_LANCE); else return (LANCE); } else return (UNKNOWN); } static int pcnet_probe(struct lnc_softc *sc) { u_long chip_id; int type; /* * The PCnet family don't reset the RAP register on reset so we'll * have to write during the probe :-) It does have an ID register * though so the probe is just a matter of reading it. */ if ((type = lance_probe(sc))) { chip_id = read_csr(sc, CSR89); chip_id <<= 16; chip_id |= read_csr(sc, CSR88); if (chip_id & AMD_MASK) { chip_id >>= 12; switch (chip_id & PART_MASK) { case Am79C960: return (PCnet_ISA); case Am79C961: return (PCnet_ISAplus); case Am79C965: return (PCnet_32); case Am79C970: /* * do NOT try to ISA attach the PCI version */ return (0); default: break; } } } return (type); } static int lnc_attach_sc(struct lnc_softc *sc, int unit) { int lnc_mem_size; /* * Allocate memory for use by the controller. * * XXX -- the Am7990 and Am79C960 only have 24 address lines and so can * only access the lower 16Mb of physical memory. For the moment we * assume that malloc will allocate memory within the lower 16Mb * range. This is not a very valid assumption but there's nothing * that can be done about it yet. For shared memory NICs this isn't * relevant. * */ lnc_mem_size = ((NDESC(sc->nrdre) + NDESC(sc->ntdre)) * sizeof(struct host_ring_entry)); if (sc->nic.mem_mode != SHMEM) lnc_mem_size += sizeof(struct init_block) + (sizeof(struct mds) * (NDESC(sc->nrdre) + NDESC(sc->ntdre))) + MEM_SLEW; /* If using DMA to fixed host buffers then allocate memory for them */ if (sc->nic.mem_mode == DMA_FIXED) lnc_mem_size += (NDESC(sc->nrdre) * RECVBUFSIZE) + (NDESC(sc->ntdre) * TRANSBUFSIZE); sc->recv_ring = malloc(lnc_mem_size, M_DEVBUF, M_NOWAIT); if (!sc->recv_ring) { log(LOG_ERR, "lnc%d: Couldn't allocate memory for NIC\n", unit); return (0); /* XXX -- attach failed -- not tested in * calling routines */ } /* * XXX - Shouldn't this be skipped for the EISA and PCI versions ??? * Print the message but do not return for the PCnet_PCI ! */ if ((sc->nic.mem_mode != SHMEM) && (kvtop(sc->recv_ring) > 0x1000000)) { log(LOG_ERR, "lnc%d: Memory allocated above 16Mb limit\n", unit); if (sc->nic.ic != PCnet_PCI) return (0); } /* Set default mode */ sc->nic.mode = NORMAL; /* Fill in arpcom structure entries */ sc->arpcom.ac_if.if_softc = sc; sc->arpcom.ac_if.if_name = lncdriver.name; sc->arpcom.ac_if.if_unit = unit; sc->arpcom.ac_if.if_mtu = ETHERMTU; sc->arpcom.ac_if.if_flags = IFF_BROADCAST | IFF_SIMPLEX; sc->arpcom.ac_if.if_timer = 0; sc->arpcom.ac_if.if_output = ether_output; sc->arpcom.ac_if.if_start = lnc_start; sc->arpcom.ac_if.if_ioctl = lnc_ioctl; sc->arpcom.ac_if.if_watchdog = lnc_watchdog; sc->arpcom.ac_if.if_type = IFT_ETHER; sc->arpcom.ac_if.if_addrlen = ETHER_ADDR_LEN; sc->arpcom.ac_if.if_hdrlen = ETHER_HDR_LEN; /* * XXX -- should check return status of if_attach */ if_attach(&sc->arpcom.ac_if); ether_ifattach(&sc->arpcom.ac_if); printf("lnc%d: ", unit); if (sc->nic.ic == LANCE || sc->nic.ic == C_LANCE) printf("%s (%s)", nic_ident[sc->nic.ident], ic_ident[sc->nic.ic]); else printf("%s", ic_ident[sc->nic.ic]); printf(" address %6D\n", sc->arpcom.ac_enaddr, ":"); #if NBPFILTER > 0 bpfattach(&sc->arpcom.ac_if, DLT_EN10MB, sizeof(struct ether_header)); #endif return (1); } static int lnc_attach(struct isa_device * isa_dev) { int unit = isa_dev->id_unit; struct lnc_softc *sc = &lnc_softc[unit]; int result = lnc_attach_sc (sc, unit); if (result == 0) return (0); /* * XXX - is it safe to call isa_dmacascade() after if_attach() * and ether_ifattach() have been called in lnc_attach() ??? */ if ((sc->nic.mem_mode != SHMEM) && (sc->nic.ic != PCnet_32) && (sc->nic.ic != PCnet_PCI)) isa_dmacascade(isa_dev->id_drq); return result; } #if NPCI > 0 void * lnc_attach_ne2100_pci(int unit, unsigned iobase) { struct lnc_softc *sc = malloc(sizeof *sc, M_DEVBUF, M_NOWAIT); if (sc) { bzero (sc, sizeof *sc); if ((ne2100_probe(sc, iobase) == 0) || (lnc_attach_sc(sc, unit) == 0)) { free(sc, M_DEVBUF); sc = NULL; } } return sc; } #endif static void lnc_init(struct lnc_softc *sc) { int s, i; char *lnc_mem; /* Check that interface has valid address */ - if (!sc->arpcom.ac_if.if_addrlist) + if (TAILQ_EMPTY(&sc->arpcom.ac_if.if_addrhead)) /* XXX unlikely */ return; /* Shut down interface */ s = splimp(); lnc_stop(sc); sc->arpcom.ac_if.if_flags |= IFF_BROADCAST | IFF_SIMPLEX; /* XXX??? */ /* * This sets up the memory area for the controller. Memory is set up for * the initialisation block (12 words of contiguous memory starting * on a word boundary),the transmit and receive ring structures (each * entry is 4 words long and must start on a quadword boundary) and * the data buffers. * * The alignment tests are particularly paranoid. */ sc->recv_next = 0; sc->trans_ring = sc->recv_ring + NDESC(sc->nrdre); sc->trans_next = 0; if (sc->nic.mem_mode == SHMEM) lnc_mem = (char *) sc->nic.iobase; else lnc_mem = (char *) (sc->trans_ring + NDESC(sc->ntdre)); lnc_mem = (char *)(((int)lnc_mem + 1) & ~1); sc->init_block = (struct init_block *) ((int) lnc_mem & ~1); lnc_mem = (char *) (sc->init_block + 1); lnc_mem = (char *)(((int)lnc_mem + 7) & ~7); /* Initialise pointers to descriptor entries */ for (i = 0; i < NDESC(sc->nrdre); i++) { (sc->recv_ring + i)->md = (struct mds *) lnc_mem; lnc_mem += sizeof(struct mds); } for (i = 0; i < NDESC(sc->ntdre); i++) { (sc->trans_ring + i)->md = (struct mds *) lnc_mem; lnc_mem += sizeof(struct mds); } /* Initialise the remaining ring entries */ if (sc->nic.mem_mode == DMA_MBUF) { sc->mbufs = 0; sc->mbuf_count = 0; /* Free previously allocated mbufs */ if (sc->initialised) lnc_free_mbufs(sc); for (i = 0; i < NDESC(sc->nrdre); i++) { if (alloc_mbuf_cluster(sc, sc->recv_ring+i)) { log(LOG_ERR, "Initialisation failed -- no mbufs\n"); splx(s); return; } } for (i = 0; i < NDESC(sc->ntdre); i++) { (sc->trans_ring + i)->buff.mbuf = 0; (sc->trans_ring + i)->md->md0 = 0; (sc->trans_ring + i)->md->md1 = 0; (sc->trans_ring + i)->md->md2 = 0; (sc->trans_ring + i)->md->md3 = 0; } } else { for (i = 0; i < NDESC(sc->nrdre); i++) { (sc->recv_ring + i)->md->md0 = kvtop(lnc_mem); (sc->recv_ring + i)->md->md1 = ((kvtop(lnc_mem) >> 16) & 0xff) | OWN; (sc->recv_ring + i)->md->md2 = -RECVBUFSIZE; (sc->recv_ring + i)->md->md3 = 0; (sc->recv_ring + i)->buff.data = lnc_mem; lnc_mem += RECVBUFSIZE; } for (i = 0; i < NDESC(sc->ntdre); i++) { (sc->trans_ring + i)->md->md0 = kvtop(lnc_mem); (sc->trans_ring + i)->md->md1 = ((kvtop(lnc_mem) >> 16) & 0xff); (sc->trans_ring + i)->md->md2 = 0; (sc->trans_ring + i)->md->md3 = 0; (sc->trans_ring + i)->buff.data = lnc_mem; lnc_mem += TRANSBUFSIZE; } } sc->next_to_send = 0; /* Set up initialisation block */ sc->init_block->mode = sc->nic.mode; for (i = 0; i < ETHER_ADDR_LEN; i++) sc->init_block->padr[i] = sc->arpcom.ac_enaddr[i]; #ifdef LNC_MULTICAST lnc_setladrf(sc); #else for (i = 0; i < MULTICAST_FILTER_LEN; i++) sc->init_block->ladrf[i] = MULTI_INIT_ADDR; #endif sc->init_block->rdra = kvtop(sc->recv_ring->md); sc->init_block->rlen = ((kvtop(sc->recv_ring->md) >> 16) & 0xff) | (sc->nrdre << 13); sc->init_block->tdra = kvtop(sc->trans_ring->md); sc->init_block->tlen = ((kvtop(sc->trans_ring->md) >> 16) & 0xff) | (sc->ntdre << 13); /* Set initialised to show that the memory area is valid */ sc->initialised = 1; sc->pending_transmits = 0; /* Give the LANCE the physical address of the initialisation block */ write_csr(sc, CSR1, kvtop(sc->init_block)); write_csr(sc, CSR2, (kvtop(sc->init_block) >> 16) & 0xff); /* * Depending on which controller this is, CSR3 has different meanings. * For the Am7990 it controls DMA operations, for the Am79C960 it * controls interrupt masks and transmitter algorithms. In either * case, none of the flags are set. * */ write_csr(sc, CSR3, 0); /* Let's see if it starts */ write_csr(sc, CSR0, INIT); for (i = 0; i < 1000; i++) if (read_csr(sc, CSR0) & IDON) break; /* * Now that the initialisation is complete there's no reason to * access anything except CSR0, so we leave RAP pointing there * so we can just access RDP from now on, saving an outw each * time. */ if (read_csr(sc, CSR0) & IDON) { /* * Enable interrupts, start the LANCE, mark the interface as * running and transmit any pending packets. */ write_csr(sc, CSR0, STRT | INEA); sc->arpcom.ac_if.if_flags |= IFF_RUNNING; sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; lnc_start(&sc->arpcom.ac_if); } else log(LOG_ERR, "lnc%d: Initialisation failed\n", sc->arpcom.ac_if.if_unit); splx(s); } /* * The interrupt flag (INTR) will be set and provided that the interrupt enable * flag (INEA) is also set, the interrupt pin will be driven low when any of * the following occur: * * 1) Completion of the initialisation routine (IDON). 2) The reception of a * packet (RINT). 3) The transmission of a packet (TINT). 4) A transmitter * timeout error (BABL). 5) A missed packet (MISS). 6) A memory error (MERR). * * The interrupt flag is cleared when all of the above conditions are cleared. * * If the driver is reset from this routine then it first checks to see if any * interrupts have ocurred since the reset and handles them before returning. * This is because the NIC may signify a pending interrupt in CSR0 using the * INTR flag even if a hardware interrupt is currently inhibited (at least I * think it does from reading the data sheets). We may as well deal with * these pending interrupts now rather than get the overhead of another * hardware interrupt immediately upon returning from the interrupt handler. * */ void lncintr_sc(struct lnc_softc *sc) { int unit = sc->arpcom.ac_if.if_unit; u_short csr0; /* * INEA is the only bit that can be cleared by writing a 0 to it so * we have to include it in any writes that clear other flags. */ while ((csr0 = inw(sc->rdp)) & INTR) { /* * Clear interrupt flags early to avoid race conditions. The * controller can still set these flags even while we're in * this interrupt routine. If the flag is still set from the * event that caused this interrupt any new events will * be missed. */ outw(sc->rdp, IDON | CERR | BABL | MISS | MERR | RINT | TINT | INEA); /* We don't do anything with the IDON flag */ if (csr0 & ERR) { if (csr0 & CERR) { log(LOG_ERR, "lnc%d: Heartbeat error -- SQE test failed\n", unit); LNCSTATS(cerr) } if (csr0 & BABL) { log(LOG_ERR, "lnc%d: Babble error - more than 1519 bytes transmitted\n", unit); LNCSTATS(babl) sc->arpcom.ac_if.if_oerrors++; } if (csr0 & MISS) { log(LOG_ERR, "lnc%d: Missed packet -- no receive buffer\n", unit); LNCSTATS(miss) sc->arpcom.ac_if.if_ierrors++; } if (csr0 & MERR) { log(LOG_ERR, "lnc%d: Memory error -- Resetting\n", unit); LNCSTATS(merr) lnc_reset(sc); continue; } } if (csr0 & RINT) { LNCSTATS(rint) lnc_rint(sc); } if (csr0 & TINT) { LNCSTATS(tint) sc->arpcom.ac_if.if_timer = 0; lnc_tint(sc); } /* * If there's room in the transmit descriptor ring then queue * some more transmit packets. */ if (!(sc->arpcom.ac_if.if_flags & IFF_OACTIVE)) lnc_start(&sc->arpcom.ac_if); } } void lncintr(int unit) { struct lnc_softc *sc = &lnc_softc[unit]; lncintr_sc (sc); } static inline int mbuf_to_buffer(struct mbuf *m, char *buffer) { int len=0; for( ; m; m = m->m_next) { bcopy(mtod(m, caddr_t), buffer, m->m_len); buffer += m->m_len; len += m->m_len; } return(len); } static inline struct mbuf * chain_to_cluster(struct mbuf *m) { struct mbuf *new; MGET(new, M_DONTWAIT, MT_DATA); if (new) { MCLGET(new, M_DONTWAIT); if (new->m_ext.ext_buf) { new->m_len = mbuf_to_buffer(m, new->m_data); m_freem(m); return(new); } else m_free(new); } return(0); } /* * IFF_OACTIVE and IFF_RUNNING are checked in ether_output so it's redundant * to check them again since we wouldn't have got here if they were not * appropriately set. This is also called from lnc_init and lncintr but the * flags should be ok at those points too. */ static void lnc_start(struct ifnet *ifp) { struct lnc_softc *sc = ifp->if_softc; struct host_ring_entry *desc; int tmp; int end_of_packet; struct mbuf *head, *m; int len, chunk; int addr; int no_entries_needed; do { IF_DEQUEUE(&sc->arpcom.ac_if.if_snd, head); if (!head) return; if (sc->nic.mem_mode == DMA_MBUF) { no_entries_needed = 0; for (m=head; m; m = m->m_next) no_entries_needed++; /* * We try and avoid bcopy as much as possible * but there are two cases when we use it. * * 1) If there are not enough free entries in the ring * to hold each mbuf in the chain then compact the * chain into a single cluster. * * 2) The Am7990 and Am79C90 must not have less than * 100 bytes in the first descriptor of a chained * packet so it's necessary to shuffle the mbuf * contents to ensure this. */ if (no_entries_needed > (NDESC(sc->ntdre) - sc->pending_transmits)) if (!(head = chain_to_cluster(head))) { log(LOG_ERR, "lnc%d: Couldn't get mbuf for transmit packet -- Resetting \n ",ifp->if_unit); lnc_reset(sc); return; } else if ((sc->nic.ic == LANCE) || (sc->nic.ic == C_LANCE)) { if ((head->m_len < 100) && (head->m_next)) { len = 100 - head->m_len; if (M_TRAILINGSPACE(head) < len) { /* * Move data to start of data * area. We assume the first * mbuf has a packet header * and is not a cluster. */ bcopy((caddr_t)head->m_data, (caddr_t)head->m_pktdat, head->m_len); head->m_data = head->m_pktdat; } m = head->m_next; while (m && (len > 0)) { chunk = min(len, m->m_len); bcopy(mtod(m, caddr_t), mtod(head, caddr_t) + head->m_len, chunk); len -= chunk; head->m_len += chunk; m->m_len -= chunk; m->m_data += chunk; if (m->m_len <= 0) { MFREE(m, head->m_next); m = head->m_next; } } } } tmp = sc->next_to_send; /* * On entering this loop we know that tmp points to a * descriptor with a clear OWN bit. */ desc = sc->trans_ring + tmp; len = ETHER_MIN_LEN; for (m = head; m; m = m->m_next) { desc->buff.mbuf = m; addr = kvtop(m->m_data); desc->md->md0 = addr; desc->md->md1 = ((addr >> 16) & 0xff); desc->md->md3 = 0; desc->md->md2 = -m->m_len; sc->pending_transmits++; len -= m->m_len; INC_MD_PTR(tmp, sc->ntdre) desc = sc->trans_ring + tmp; } end_of_packet = tmp; DEC_MD_PTR(tmp, sc->ntdre) desc = sc->trans_ring + tmp; desc->md->md1 |= ENP; if (len > 0) desc->md->md2 -= len; /* * Set OWN bits in reverse order, otherwise the Lance * could start sending the packet before all the * buffers have been relinquished by the host. */ while (tmp != sc->next_to_send) { desc->md->md1 |= OWN; DEC_MD_PTR(tmp, sc->ntdre) desc = sc->trans_ring + tmp; } sc->next_to_send = end_of_packet; desc->md->md1 |= STP | OWN; } else { sc->pending_transmits++; desc = sc->trans_ring + sc->next_to_send; len = mbuf_to_buffer(head, desc->buff.data); desc->md->md3 = 0; desc->md->md2 = -max(len, ETHER_MIN_LEN); desc->md->md1 |= OWN | STP | ENP; INC_MD_PTR(sc->next_to_send, sc->ntdre) } /* Force an immediate poll of the transmit ring */ outw(sc->rdp, TDMD | INEA); /* * Set a timer so if the buggy Am7990.h shuts * down we can wake it up. */ ifp->if_timer = 2; #if NBPFILTER > 0 if (sc->arpcom.ac_if.if_bpf) bpf_mtap(&sc->arpcom.ac_if, head); #endif if (sc->nic.mem_mode != DMA_MBUF) m_freem(head); } while (sc->pending_transmits < NDESC(sc->ntdre)); /* * Transmit ring is full so set IFF_OACTIVE * since we can't buffer any more packets. */ sc->arpcom.ac_if.if_flags |= IFF_OACTIVE; LNCSTATS(trans_ring_full) } static int lnc_ioctl(struct ifnet * ifp, int command, caddr_t data) { struct lnc_softc *sc = ifp->if_softc; struct ifaddr *ifa = (struct ifaddr *) data; struct ifreq *ifr = (struct ifreq *) data; int s, error = 0; s = splimp(); switch (command) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: lnc_init(sc); arp_ifinit((struct arpcom *)ifp, ifa); break; #endif default: lnc_init(sc); break; } break; case SIOCSIFFLAGS: #ifdef DEBUG if (ifp->if_flags & IFF_DEBUG) sc->lnc_debug = 1; else sc->lnc_debug = 0; #endif if (ifp->if_flags & IFF_PROMISC) { if (!(sc->nic.mode & PROM)) { sc->nic.mode |= PROM; lnc_init(sc); } } else if (sc->nic.mode & PROM) { sc->nic.mode &= ~PROM; lnc_init(sc); } if ((ifp->if_flags & IFF_UP) == 0 && (ifp->if_flags & IFF_RUNNING) != 0) { /* * If interface is marked down and it is running, * then stop it. */ lnc_stop(sc); ifp->if_flags &= ~IFF_RUNNING; } else if ((ifp->if_flags & IFF_UP) != 0 && (ifp->if_flags & IFF_RUNNING) == 0) { /* * If interface is marked up and it is stopped, then * start it. */ lnc_init(sc); } break; #ifdef LNC_MULTICAST case SIOCADDMULTI: case SIOCDELMULTI: error = (command == SIOCADDMULTI) ? ether_addmulti(ifr, &sc->arpcom) : ether_delmulti(ifr, &sc->arpcom); if (error == ENETRESET) { lnc_setladrf(sc); error = 0; } break; #endif case SIOCSIFMTU: /* * Set the interface MTU. */ if (ifr->ifr_mtu > ETHERMTU) { error = EINVAL; } else ifp->if_mtu = ifr->ifr_mtu; break; default: error = EINVAL; } (void) splx(s); return error; } static void lnc_watchdog(struct ifnet *ifp) { log(LOG_ERR, "lnc%d: Device timeout -- Resetting\n", ifp->if_unit); ifp->if_oerrors++; lnc_reset(ifp->if_softc); } #ifdef DEBUG static void lnc_dump_state(struct lnc_softc *sc) { int i; printf("\nDriver/NIC [%d] state dump\n", sc->arpcom.ac_if.if_unit); printf("Memory access mode: %b\n", sc->nic.mem_mode, MEM_MODES); printf("Host memory\n"); printf("-----------\n"); printf("Receive ring: base = %x, next = %x\n", sc->recv_ring, (sc->recv_ring + sc->recv_next)); for (i = 0; i < NDESC(sc->nrdre); i++) printf("\t%d:%x md = %x buff = %x\n", i, sc->recv_ring + i, (sc->recv_ring + i)->md, (sc->recv_ring + i)->buff); printf("Transmit ring: base = %x, next = %x\n", sc->trans_ring, (sc->trans_ring + sc->trans_next)); for (i = 0; i < NDESC(sc->ntdre); i++) printf("\t%d:%x md = %x buff = %x\n", i, sc->trans_ring + i, (sc->trans_ring + i)->md, (sc->trans_ring + i)->buff); printf("Lance memory (may be on host(DMA) or card(SHMEM))\n"); printf("Init block = %x\n", sc->init_block); printf("\tmode = %b rlen:rdra = %x:%x tlen:tdra = %x:%x\n", sc->init_block->mode, INIT_MODE, sc->init_block->rlen, sc->init_block->rdra, sc->init_block->tlen, sc->init_block->tdra); printf("Receive descriptor ring\n"); for (i = 0; i < NDESC(sc->nrdre); i++) printf("\t%d buffer = 0x%x%x, BCNT = %d,\tMCNT = %u,\tflags = %b\n", i, ((sc->recv_ring + i)->md->md1 & HADR), (sc->recv_ring + i)->md->md0, -(short) (sc->recv_ring + i)->md->md2, (sc->recv_ring + i)->md->md3, (((sc->recv_ring + i)->md->md1 & ~HADR) >> 8), RECV_MD1); printf("Transmit descriptor ring\n"); for (i = 0; i < NDESC(sc->ntdre); i++) printf("\t%d buffer = 0x%x%x, BCNT = %d,\tflags = %b %b\n", i, ((sc->trans_ring + i)->md->md1 & HADR), (sc->trans_ring + i)->md->md0, -(short) (sc->trans_ring + i)->md->md2, ((sc->trans_ring + i)->md->md1 >> 8), TRANS_MD1, ((sc->trans_ring + i)->md->md3 >> 10), TRANS_MD3); printf("\nnext_to_send = %x\n", sc->next_to_send); printf("\n CSR0 = %b CSR1 = %x CSR2 = %x CSR3 = %x\n\n", read_csr(sc, CSR0), CSR0_FLAGS, read_csr(sc, CSR1), read_csr(sc, CSR2), read_csr(sc, CSR3)); /* Set RAP back to CSR0 */ outw(sc->rap, CSR0); } static void mbuf_dump_chain(struct mbuf * m) { #define MBUF_FLAGS \ "\20\1M_EXT\2M_PKTHDR\3M_EOR\4UNKNOWN\5M_BCAST\6M_MCAST" if (!m) log(LOG_DEBUG, "m == NULL\n"); do { log(LOG_DEBUG, "m = %x\n", m); log(LOG_DEBUG, "m_hdr.mh_next = %x\n", m->m_hdr.mh_next); log(LOG_DEBUG, "m_hdr.mh_nextpkt = %x\n", m->m_hdr.mh_nextpkt); log(LOG_DEBUG, "m_hdr.mh_len = %d\n", m->m_hdr.mh_len); log(LOG_DEBUG, "m_hdr.mh_data = %x\n", m->m_hdr.mh_data); log(LOG_DEBUG, "m_hdr.mh_type = %d\n", m->m_hdr.mh_type); log(LOG_DEBUG, "m_hdr.mh_flags = %b\n", m->m_hdr.mh_flags, MBUF_FLAGS); if (!(m->m_hdr.mh_flags & (M_PKTHDR | M_EXT))) log(LOG_DEBUG, "M_dat.M_databuf = %x\n", m->M_dat.M_databuf); else { if (m->m_hdr.mh_flags & M_PKTHDR) { log(LOG_DEBUG, "M_dat.MH.MH_pkthdr.len = %d\n", m->M_dat.MH.MH_pkthdr.len); log(LOG_DEBUG, "M_dat.MH.MH_pkthdr.rcvif = %x\n", m->M_dat.MH.MH_pkthdr.rcvif); if (!(m->m_hdr.mh_flags & M_EXT)) log(LOG_DEBUG, "M_dat.MH.MH_dat.MH_databuf = %x\n", m->M_dat.MH.MH_dat.MH_databuf); } if (m->m_hdr.mh_flags & M_EXT) { log(LOG_DEBUG, "M_dat.MH.MH_dat.MH_ext.ext_buff %x\n", m->M_dat.MH.MH_dat.MH_ext.ext_buf); log(LOG_DEBUG, "M_dat.MH.MH_dat.MH_ext.ext_free %x\n", m->M_dat.MH.MH_dat.MH_ext.ext_free); log(LOG_DEBUG, "M_dat.MH.MH_dat.MH_ext.ext_size %d\n", m->M_dat.MH.MH_dat.MH_ext.ext_size); } } } while (m = m->m_next); } #endif #endif diff --git a/sys/i386/isa/if_ze.c b/sys/i386/isa/if_ze.c index 7eb6abe81996..1b7e1aba6ad3 100644 --- a/sys/i386/isa/if_ze.c +++ b/sys/i386/isa/if_ze.c @@ -1,1631 +1,1631 @@ /*- * TODO: * [1] integrate into current if_ed.c * [2] parse tuples to find out where to map the shared memory buffer, * and what to write into the configuration register * [3] move pcic-specific code into a separate module. * * Device driver for IBM PCMCIA Credit Card Adapter for Ethernet, * if_ze.c * * Based on the Device driver for National Semiconductor DS8390 ethernet * adapters by David Greenman. Modifications for PCMCIA by Keith Moore. * Adapted for FreeBSD 1.1.5 by Jordan Hubbard. * * Currently supports only the IBM Credit Card Adapter for Ethernet, but * could probably work with other PCMCIA cards also, if it were modified * to get the locations of the PCMCIA configuration option register (COR) * by parsing the configuration tuples, rather than by hard-coding in * the value expected by IBM's card. * * Sources for data on the PCMCIA/IBM CCAE specific portions of the driver: * * [1] _Local Area Network Credit Card Adapters Technical Reference_, * IBM Corp., SC30-3585-00, part # 33G9243. * [2] "pre-alpha" PCMCIA support code for Linux by Barry Jaspan. * [3] Intel 82536SL PC Card Interface Controller Data Sheet, Intel * Order Number 290423-002 * [4] National Semiconductor DP83902A ST-NIC (tm) Serial Network * Interface Controller for Twisted Pair data sheet. * * * Copyright (C) 1993, David Greenman. This software may be used, modified, * copied, distributed, and sold, in both source and binary form provided * that the above copyright and these terms are retained. Under no * circumstances is the author responsible for the proper functioning * of this software, nor does the author assume any responsibility * for damages incurred with its use. */ /* * I doubled delay loops in this file because it is not enough for some * laptop machines' PCIC (especially, on my Chaplet ILFA 350 ^^;). * HOSOKAWA, Tatsumi */ /* * Very small patch for IBM Ethernet PCMCIA Card II and IBM ThinkPad230Cs. * ETO, Toshihisa */ /* - * $Id: if_ze.c,v 1.32 1996/07/12 04:11:23 bde Exp $ + * $Id: if_ze.c,v 1.33 1996/08/06 21:14:11 phk Exp $ */ #include "ze.h" #if NZE > 0 #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #ifdef IPX #include #include #endif #ifdef NS #include #include #endif #if NBPFILTER > 0 #include #include #endif #include #include #include #include #include #include #include #include "apm.h" #if NAPM > 0 #include #endif /* NAPM > 0 */ /***************************************************************************** * Driver for Ethernet Adapter * *****************************************************************************/ /* * ze_softc: per line info and status */ static struct ze_softc { struct arpcom arpcom; /* ethernet common */ caddr_t maddr; u_long iobase, irq; char *type_str; /* pointer to type string */ char *mau; /* type of media access unit */ u_short nic_addr; /* NIC (DS8390) I/O bus address */ caddr_t smem_start; /* shared memory start address */ caddr_t smem_end; /* shared memory end address */ u_long smem_size; /* total shared memory size */ caddr_t smem_ring; /* start of RX ring-buffer (in smem) */ u_char memwidth; /* width of access to card mem 8 or 16 */ u_char xmit_busy; /* transmitter is busy */ u_char txb_cnt; /* Number of transmit buffers */ u_char txb_next; /* Pointer to next buffer ready to xmit */ u_short txb_next_len; /* next xmit buffer length */ u_char data_buffered; /* data has been buffered in interface memory */ u_char tx_page_start; /* first page of TX buffer area */ u_char rec_page_start; /* first page of RX ring-buffer */ u_char rec_page_stop; /* last page of RX ring-buffer */ u_char next_packet; /* pointer to next unread RX packet */ int slot; /* information for reconfiguration */ u_char last_alive; /* information for reconfiguration */ u_char last_up; /* information for reconfiguration */ #if NAPM > 0 struct apmhook s_hook; /* reconfiguration support */ struct apmhook r_hook; /* reconfiguration support */ #endif /* NAPM > 0 */ } ze_softc[NZE]; static int ze_check_cis __P((unsigned char *scratch)); static int ze_find_adapter __P((unsigned char *scratch, int reconfig)); static int ze_probe __P((struct isa_device *isa_dev)); static void ze_setup __P((struct ze_softc *sc)); static int ze_suspend __P((void *visa_dev)); static int ze_resume __P((void *visa_dev)); static int ze_attach __P((struct isa_device *isa_dev)); static void ze_reset __P((int unit)); static void ze_stop __P((int unit)); static void ze_watchdog __P((struct ifnet *ifp)); static void ze_init __P((int unit)); static inline void ze_xmit __P((struct ifnet *ifp)); static void ze_start __P((struct ifnet *ifp)); static inline void ze_rint __P((int unit)); static int ze_ioctl __P((struct ifnet *ifp, int command, caddr_t data)); static void ze_get_packet __P((struct ze_softc *sc, char *buf, int len)); static inline char *ze_ring_copy __P((struct ze_softc *sc, char *src, char *dst, int amount)); static struct mbuf *ze_ring_to_mbuf __P((struct ze_softc *sc, char *src, struct mbuf *dst, int total_len)); struct isa_driver zedriver = { ze_probe, ze_attach, "ze" }; static unsigned char enet_addr[6]; static unsigned char card_info[256]; #define CARD_INFO "IBM Corp.~Ethernet~0933495" /* * IBM Ethernet PCMCIA Card II returns following info. */ #define CARD2_INFO "IBM Corp.~Ethernet~0934214" /* */ #define CARD3_INFO "National Semiconductor~InfoMover NE4" /* * scan the card information structure looking for the version/product info * tuple. when we find it, compare it to the string we are looking for. * return 1 if we find it, 0 otherwise. */ static int ze_check_cis (unsigned char *scratch) { int i,j,k; card_info[0] = '\0'; i = 0; while (scratch[i] != 0xff && i < 1024) { unsigned char link = scratch[i+2]; #if 0 printf ("[%02x] %02x ", i, link); for (j = 4; j < 2 * link + 4 && j < 32; j += 2) printf ("%02x ", scratch[j + i]); printf ("\n"); #endif if (scratch[i] == 0x15) { /* * level 1 version/product info * copy to card_info, translating '\0' to '~' */ k = 0; for (j = i+8; scratch[j] != 0xff; j += 2) card_info[k++] = scratch[j] == '\0' ? '~' : scratch[j]; card_info[k++] = '\0'; #if 0 return (bcmp (card_info, CARD_INFO, sizeof(CARD_INFO)-1) == 0); #else if ((bcmp (card_info, CARD_INFO, sizeof(CARD_INFO)-1) == 0) || (bcmp (card_info, CARD2_INFO, sizeof(CARD2_INFO)-1) == 0) || (bcmp (card_info, CARD3_INFO, sizeof(CARD3_INFO)-1) == 0)) { return 1; } return 0; #endif } i += 4 + 2 * link; } return 0; } /* * Probe each slot looking for an IBM Credit Card Adapter for Ethernet * For each card that we find, map its card information structure * into system memory at 'scratch' and see whether it's one of ours. * Return the slot number if we find a card, or -1 otherwise. * * Side effects: * + On success, leaves CIS mapped into memory at 'scratch'; * caller must free it. * + On success, leaves ethernet address in enet_addr. * + Leaves product/vendor id of last card probed in 'card_info' */ static int prev_slot = 0; static int ze_find_adapter (unsigned char *scratch, int reconfig) { int slot; for (slot = prev_slot; slot < MAXSLOT; ++slot) { /* * see if there's a PCMCIA controller here * Intel PCMCIA controllers use 0x82 and 0x83 * IBM clone chips use 0x88 and 0x89, apparently */ /* * IBM ThinkPad230Cs use 0x84. */ unsigned char idbyte = pcic_getb (slot, PCIC_ID_REV); if (idbyte != 0x82 && idbyte != 0x83 && idbyte != 0x84 && /* for IBM ThinkPad 230Cs */ idbyte != 0x88 && idbyte != 0x89) { #if 0 printf ("ibmccae: pcic slot %d: wierd id/rev code 0x%02x\n", slot, idbyte); #endif continue; } if ((pcic_getb (slot, PCIC_STATUS) & PCIC_CD) != PCIC_CD) { if (!reconfig) { printf ("ze: slot %d: no card in slot\n", slot); } else { log (LOG_NOTICE, "ze: slot %d: no card in slot\n", slot); } /* no card in slot */ continue; } pcic_power_on (slot); pcic_reset (slot); /* * map the card's attribute memory and examine its * card information structure tuples for something * we recognize. */ pcic_map_memory (slot, 0, kvtop (scratch), 0L, 0xFFFL, ATTRIBUTE, 1); if ((ze_check_cis (scratch)) > 0) { /* found it */ if (!reconfig) { printf ("ze: found card in slot %d\n", slot); } else { log (LOG_NOTICE, "ze: found card in slot %d\n", slot); } prev_slot = (prev_slot == MAXSLOT - 1) ? 0 : prev_slot+1; return slot; } else { if (!reconfig) { printf ("ze: pcmcia slot %d: %s\n", slot, card_info); } else { log (LOG_NOTICE, "ze: pcmcia slot %d: %s\n", slot, card_info); } } pcic_unmap_memory (slot, 0); } prev_slot = 0; return -1; } /* * macros to handle casting unsigned long to (char *) so we can * read/write into physical memory space. */ #define PEEK(addr) (*((unsigned char *)(addr))) #define POKE(addr,val) do { PEEK(addr) = (val); } while (0) /* * Determine if the device is present * * on entry: * a pointer to an isa_device struct * on exit: * NULL if device not found * or # of i/o addresses used (if found) pcic( */ static int ze_probe(isa_dev) struct isa_device *isa_dev; { struct ze_softc *sc = &ze_softc[isa_dev->id_unit]; int i; u_int memsize; u_char tmp; int slot; if ((slot = ze_find_adapter (isa_dev->id_maddr, isa_dev->id_reconfig)) < 0) return 0; /* * okay, we found a card, so set it up */ /* * Inhibit 16 bit memory delay. * POINTETH.SYS apparently does this, for what reason I don't know. */ pcic_putb (slot, PCIC_CDGC, pcic_getb (slot, PCIC_CDGC) | PCIC_16_DL_INH); /* * things to map * (1) card's EEPROM is already mapped by the find_adapter routine * but we still need to get the card's ethernet address. * after that we unmap that part of attribute memory. * (2) card configuration registers need to be mapped in so we * can set the configuration and socket # registers. * (3) shared memory packet buffer * (4) i/o ports * (5) IRQ */ /* * Sigh. Location of the ethernet address isn't documented in [1]. * It was derived by doing a hex dump of all of attribute memory * and looking for the IBM vendor prefix. */ enet_addr[0] = PEEK(isa_dev->id_maddr+0xff0); enet_addr[1] = PEEK(isa_dev->id_maddr+0xff2); enet_addr[2] = PEEK(isa_dev->id_maddr+0xff4); enet_addr[3] = PEEK(isa_dev->id_maddr+0xff6); enet_addr[4] = PEEK(isa_dev->id_maddr+0xff8); enet_addr[5] = PEEK(isa_dev->id_maddr+0xffa); pcic_unmap_memory (slot, 0); sc->maddr = isa_dev->id_maddr; sc->irq = isa_dev->id_irq; sc->iobase = isa_dev->id_iobase; sc->slot = slot; /* * Setup i/o addresses */ sc->nic_addr = sc->iobase; sc->smem_start = (caddr_t)sc->maddr; ze_setup(sc); tmp = inb (sc->iobase + ZE_RESET); sc->mau = tmp & 0x09 ? "10base2" : "10baseT"; /* set width/size */ sc->type_str = "IBM PCMCIA"; memsize = 16*1024; sc->memwidth = 16; /* allocate 1 xmit buffer */ sc->smem_ring = sc->smem_start + (ED_PAGE_SIZE * ED_TXBUF_SIZE); sc->txb_cnt = 1; sc->rec_page_start = ED_TXBUF_SIZE + ZE_PAGE_OFFSET; sc->smem_size = memsize; sc->smem_end = sc->smem_start + memsize; sc->rec_page_stop = memsize / ED_PAGE_SIZE + ZE_PAGE_OFFSET; sc->tx_page_start = ZE_PAGE_OFFSET; /* get station address */ for (i = 0; i < ETHER_ADDR_LEN; ++i) sc->arpcom.ac_enaddr[i] = enet_addr[i]; isa_dev->id_msize = memsize; /* information for reconfiguration */ sc->last_alive = 0; sc->last_up = 0; return 32; } static void ze_setup(struct ze_softc *sc) { int re_init_flag = 0,tmp,slot = sc->slot; re_init: /* * (2) map card configuration registers. these are offset * in card memory space by 0x20000. normally we could get * this offset from the card information structure, but I'm * too lazy and am not quite sure if I understand the CIS anyway. * * XXX IF YOU'RE TRYING TO PORT THIS DRIVER FOR A DIFFERENT * PCMCIA CARD, the most likely thing to change is the constant * 0x20000 in the next statement. Oh yes, also change the * card id string that we probe for. */ pcic_map_memory (slot, 0, kvtop (sc->maddr), 0x20000, 8L, ATTRIBUTE, 1); POKE(sc->maddr, 0x80); /* reset the card (how long?) */ DELAY (40000); /* * Set the configuration index. According to [1], the adapter won't * respond to any i/o signals until we do this; it uses the * Memory Only interface (whatever that is; it's not documented). * Also turn on "level" (not pulse) interrupts. * * XXX probably should init the socket and copy register also, * so that we can deal with multiple instances of the same card. */ POKE(sc->maddr, 0x41); pcic_unmap_memory (slot, 0); /* * (3) now map in the shared memory buffer. This has to be mapped * as words, not bytes, and on a 16k boundary. The offset value * was derived by installing IBM's POINTETH.SYS under DOS and * looking at the PCIC registers; it's not documented in IBM's * tech ref manual ([1]). */ pcic_map_memory (slot, 0, kvtop (sc->maddr), 0x4000L, 0x4000L, COMMON, 2); /* * (4) map i/o ports. * * XXX is it possible that the config file leaves this unspecified, * in which case we have to pick one? * * At least one PCMCIA device driver I'v seen maps a block * of 32 consecutive i/o ports as two windows of 16 ports each. * Maybe some other pcic chips are restricted to 16-port windows; * the 82365SL doesn't seem to have that problem. But since * we have an extra window anyway... */ #ifdef SHARED_MEMORY pcic_map_io (slot, 0, sc->iobase, 32, 1); #else pcic_map_io (slot, 0, sc->iobase, 16, 1); pcic_map_io (slot, 1, sc->iobase+16, 16, 2); #endif /* SHARED_MEMORY */ /* * (5) configure the card for the desired interrupt * * XXX is it possible that the config file leaves this unspecified? */ pcic_map_irq (slot, ffs (sc->irq) - 1); /* tell the PCIC that this is an I/O card (not memory) */ pcic_putb (slot, PCIC_INT_GEN, pcic_getb (slot, PCIC_INT_GEN) | PCIC_CARDTYPE); #if 0 /* tell the PCIC to use level-mode interrupts */ /* XXX this register may not be present on all controllers */ pcic_putb (slot, PCIC_GLO_CTRL, pcic_getb (slot, PCIC_GLO_CTRL) | PCIC_LVL_MODE); #endif #if 0 pcic_print_regs (slot); #endif /* reset card to force it into a known state */ tmp = inb (sc->iobase + ZE_RESET); DELAY(20000); outb (sc->iobase + ZE_RESET, tmp); DELAY(20000); #if 0 tmp = inb(sc->iobase); printf("CR = 0x%x\n", tmp); #endif /* * query MAM bit in misc register for 10base2 */ tmp = inb (sc->iobase + ZE_MISC); /* * Some Intel-compatible PCICs of Cirrus Logic fails in * initializing them. This is a quick hack to fix this * problem. * HOSOKAWA, Tatsumi */ if (!tmp && !re_init_flag) { re_init_flag++; goto re_init; } } #if NAPM > 0 static int ze_suspend(visa_dev) void *visa_dev; { struct isa_device *isa_dev = visa_dev; struct ze_softc *sc = &ze_softc[isa_dev->id_unit]; pcic_power_off(sc->slot); return 0; } static int ze_resume(visa_dev) void *visa_dev; { struct isa_device *isa_dev = visa_dev; #if 0 printf("Resume ze:\n"); #endif prev_slot = 0; reconfig_isadev(isa_dev, &net_imask); return 0; } #endif /* NAPM > 0 */ /* * Install interface into kernel networking data structures */ static int ze_attach(isa_dev) struct isa_device *isa_dev; { struct ze_softc *sc = &ze_softc[isa_dev->id_unit]; struct ifnet *ifp = &sc->arpcom.ac_if; int pl; /* PCMCIA card can be offlined. Reconfiguration is required */ if (isa_dev->id_reconfig) { ze_reset(isa_dev->id_unit); if (!isa_dev->id_alive && sc->last_alive) { pl = splimp(); sc->last_up = (ifp->if_flags & IFF_UP); if_down(ifp); splx(pl); sc->last_alive = 0; } if (isa_dev->id_alive && !sc->last_alive) { if (sc->last_up) { pl = splimp(); if_up(ifp); splx(pl); } sc->last_alive = 1; } return 1; } else { sc->last_alive = 1; } /* * Set interface to stopped condition (reset) */ ze_stop(isa_dev->id_unit); /* * Initialize ifnet structure */ ifp->if_softc = sc; ifp->if_unit = isa_dev->id_unit; ifp->if_name = "ze" ; ifp->if_mtu = ETHERMTU; ifp->if_output = ether_output; ifp->if_start = ze_start; ifp->if_ioctl = ze_ioctl; ifp->if_watchdog = ze_watchdog; ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX); /* * Attach the interface */ if_attach(ifp); ether_ifattach(ifp); /* * Print additional info when attached */ printf("ze%d: address %6D, type %s (%dbit), MAU %s\n", isa_dev->id_unit, sc->arpcom.ac_enaddr, ":", sc->type_str, sc->memwidth, sc->mau); /* * If BPF is in the kernel, call the attach for it */ #if NBPFILTER > 0 bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); #endif #if NAPM > 0 sc->s_hook.ah_fun = ze_suspend; sc->s_hook.ah_arg = (void *)isa_dev; sc->s_hook.ah_name = "IBM PCMCIA Ethernet I/II"; sc->s_hook.ah_order = APM_MID_ORDER; apm_hook_establish(APM_HOOK_SUSPEND , &sc->s_hook); sc->r_hook.ah_fun = ze_resume; sc->r_hook.ah_arg = (void *)isa_dev; sc->r_hook.ah_name = "IBM PCMCIA Ethernet I/II"; sc->r_hook.ah_order = APM_MID_ORDER; apm_hook_establish(APM_HOOK_RESUME , &sc->r_hook); #endif /* NAPM > 0 */ return 1; } /* * Reset interface. */ static void ze_reset(unit) int unit; { int s; s = splnet(); /* * Stop interface and re-initialize. */ ze_stop(unit); ze_init(unit); (void) splx(s); } /* * Take interface offline. */ static void ze_stop(unit) int unit; { struct ze_softc *sc = &ze_softc[unit]; int n = 5000; /* * Stop everything on the interface, and select page 0 registers. */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2|ED_CR_STP); /* * Wait for interface to enter stopped state, but limit # of checks * to 'n' (about 5ms). It shouldn't even take 5us on modern * DS8390's, but just in case it's an old one. */ while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST) == 0) && --n); pcic_power_off(sc->slot); } /* * Device timeout/watchdog routine. Entered if the device neglects to * generate an interrupt after a transmit has been started on it. */ static void ze_watchdog(ifp) struct ifnet *ifp; { #if 1 struct ze_softc *sc = (struct ze_softc *)ifp; u_char isr, imr; u_short imask; if(!(ifp->if_flags & IFF_UP)) return; /* select page zero */ outb (sc->nic_addr + ED_P0_CR, (inb (sc->nic_addr + ED_P0_CR) & 0x3f) | ED_CR_PAGE_0); /* read interrupt status register */ isr = inb (sc->nic_addr + ED_P0_ISR) & 0xff; /* select page two */ outb (sc->nic_addr + ED_P0_CR, (inb (sc->nic_addr + ED_P0_CR) & 0x3f) | ED_CR_PAGE_2); /* read interrupt mask register */ imr = inb (sc->nic_addr + ED_P2_IMR) & 0xff; imask = inb(IO_ICU2) << 8 | inb(IO_ICU1); log (LOG_ERR, "ze%d: device timeout, isr=%02x, imr=%02x, imask=%04x\n", ifp->if_unit, isr, imr, imask); #else log(LOG_ERR, "ze%d: device timeout\n", ifp->if_unit); #endif ze_reset(ifp->if_unit); } /* * Initialize device. */ static void ze_init(unit) int unit; { struct ze_softc *sc = &ze_softc[unit]; struct ifnet *ifp = &sc->arpcom.ac_if; int i, s; pcic_power_on(sc->slot); pcic_reset(sc->slot); if(!(sc->arpcom.ac_if.if_flags & IFF_UP)) Debugger("here!!"); ze_setup(sc); /* address not known */ - if (ifp->if_addrlist == (struct ifaddr *)0) return; + if (TAILQ_EMPTY(&ifp->if_addrhead)) return; /* XXX unlikely! */ /* * Initialize the NIC in the exact order outlined in the NS manual. * This init procedure is "mandatory"...don't change what or when * things happen. */ s = splnet(); /* reset transmitter flags */ sc->data_buffered = 0; sc->xmit_busy = 0; sc->arpcom.ac_if.if_timer = 0; sc->txb_next = 0; /* This variable is used below - don't move this assignment */ sc->next_packet = sc->rec_page_start + 1; /* * Set interface for page 0, Remote DMA complete, Stopped */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2|ED_CR_STP); if (sc->memwidth == 16) { /* * Set FIFO threshold to 8, No auto-init Remote DMA, * byte order=80x86, word-wide DMA xfers */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1|ED_DCR_WTS); } else { /* * Same as above, but byte-wide DMA xfers */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1); } /* * Clear Remote Byte Count Registers */ outb(sc->nic_addr + ED_P0_RBCR0, 0); outb(sc->nic_addr + ED_P0_RBCR1, 0); /* * Enable reception of broadcast packets */ outb(sc->nic_addr + ED_P0_RCR, ED_RCR_AB); /* * Place NIC in internal loopback mode */ outb(sc->nic_addr + ED_P0_TCR, ED_TCR_LB0); /* * Initialize transmit/receive (ring-buffer) Page Start */ outb(sc->nic_addr + ED_P0_TPSR, sc->tx_page_start); outb(sc->nic_addr + ED_P0_PSTART, sc->rec_page_start); /* * Initialize Receiver (ring-buffer) Page Stop and Boundry */ outb(sc->nic_addr + ED_P0_PSTOP, sc->rec_page_stop); outb(sc->nic_addr + ED_P0_BNRY, sc->rec_page_start); /* * Clear all interrupts. A '1' in each bit position clears the * corresponding flag. */ outb(sc->nic_addr + ED_P0_ISR, 0xff); /* * Enable the following interrupts: receive/transmit complete, * receive/transmit error, and Receiver OverWrite. * * Counter overflow and Remote DMA complete are *not* enabled. */ outb(sc->nic_addr + ED_P0_IMR, ED_IMR_PRXE|ED_IMR_PTXE|ED_IMR_RXEE|ED_IMR_TXEE|ED_IMR_OVWE); /* * Program Command Register for page 1 */ outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_1|ED_CR_RD2|ED_CR_STP); /* * Copy out our station address */ for (i = 0; i < ETHER_ADDR_LEN; ++i) outb(sc->nic_addr + ED_P1_PAR0 + i, sc->arpcom.ac_enaddr[i]); #if NBPFILTER > 0 /* * Initialize multicast address hashing registers to accept * all multicasts (only used when in promiscuous mode) */ for (i = 0; i < 8; ++i) outb(sc->nic_addr + ED_P1_MAR0 + i, 0xff); #endif /* * Set Current Page pointer to next_packet (initialized above) */ outb(sc->nic_addr + ED_P1_CURR, sc->next_packet); /* * Set Command Register for page 0, Remote DMA complete, * and interface Start. */ outb(sc->nic_addr + ED_P1_CR, ED_CR_RD2|ED_CR_STA); /* * Take interface out of loopback */ outb(sc->nic_addr + ED_P0_TCR, 0); /* * Set 'running' flag, and clear output active flag. */ ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; /* * ...and attempt to start output */ ze_start(ifp); (void) splx(s); } /* * This routine actually starts the transmission on the interface */ static inline void ze_xmit(ifp) struct ifnet *ifp; { struct ze_softc *sc = ifp->if_softc; u_short len = sc->txb_next_len; /* * Set NIC for page 0 register access */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2|ED_CR_STA); /* * Set TX buffer start page */ outb(sc->nic_addr + ED_P0_TPSR, sc->tx_page_start + sc->txb_next * ED_TXBUF_SIZE); /* * Set TX length */ outb(sc->nic_addr + ED_P0_TBCR0, len & 0xff); outb(sc->nic_addr + ED_P0_TBCR1, len >> 8); /* * Set page 0, Remote DMA complete, Transmit Packet, and *Start* */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2|ED_CR_TXP|ED_CR_STA); sc->xmit_busy = 1; sc->data_buffered = 0; /* * Switch buffers if we are doing double-buffered transmits */ if ((sc->txb_next == 0) && (sc->txb_cnt > 1)) sc->txb_next = 1; else sc->txb_next = 0; /* * Set a timer just in case we never hear from the board again */ ifp->if_timer = 2; } /* * Start output on interface. * We make two assumptions here: * 1) that the current priority is set to splnet _before_ this code * is called *and* is returned to the appropriate priority after * return * 2) that the IFF_OACTIVE flag is checked before this code is called * (i.e. that the output part of the interface is idle) */ static void ze_start(ifp) struct ifnet *ifp; { struct ze_softc *sc = ifp->if_softc; struct mbuf *m0, *m; caddr_t buffer; int len; outloop: /* * See if there is room to send more data (i.e. one or both of the * buffers is empty). */ if (sc->data_buffered) if (sc->xmit_busy) { /* * No room. Indicate this to the outside world * and exit. */ ifp->if_flags |= IFF_OACTIVE; return; } else { /* * Data is buffered, but we're not transmitting, so * start the xmit on the buffered data. * Note that ze_xmit() resets the data_buffered flag * before returning. */ ze_xmit(ifp); } IF_DEQUEUE(&sc->arpcom.ac_if.if_snd, m); if (m == NULL) { /* * The following isn't pretty; we are using the !OACTIVE flag to * indicate to the outside world that we can accept an additional * packet rather than that the transmitter is _actually_ * active. Indeed, the transmitter may be active, but if we haven't * filled the secondary buffer with data then we still want to * accept more. * Note that it isn't necessary to test the data_buffered flag - * we wouldn't have tried to de-queue the packet in the first place * if it was set. */ ifp->if_flags &= ~IFF_OACTIVE; return; } /* * Copy the mbuf chain into the transmit buffer */ buffer = sc->smem_start + (sc->txb_next * ED_TXBUF_SIZE * ED_PAGE_SIZE); len = 0; for (m0 = m; m != 0; m = m->m_next) { bcopy(mtod(m, caddr_t), buffer, m->m_len); buffer += m->m_len; len += m->m_len; } sc->txb_next_len = max(len, ETHER_MIN_LEN); if (sc->txb_cnt > 1) /* * only set 'buffered' flag if doing multiple buffers */ sc->data_buffered = 1; if (sc->xmit_busy == 0) ze_xmit(ifp); /* * If there is BPF support in the configuration, tap off here. */ #if NBPFILTER > 0 if (ifp->if_bpf) { bpf_mtap(ifp, m0); } #endif m_freem(m0); /* * If we are doing double-buffering, a buffer might be free to * fill with another packet, so loop back to the top. */ if (sc->txb_cnt > 1) goto outloop; else { ifp->if_flags |= IFF_OACTIVE; return; } } /* * Ethernet interface receiver interrupt. */ static inline void /* only called from one place, so may as well integrate */ ze_rint(unit) int unit; { register struct ze_softc *sc = &ze_softc[unit]; u_char boundry; u_short len; struct ed_ring *packet_ptr; /* * Set NIC to page 1 registers to get 'current' pointer */ outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_1|ED_CR_RD2|ED_CR_STA); /* * 'sc->next_packet' is the logical beginning of the ring-buffer - i.e. * it points to where new data has been buffered. The 'CURR' * (current) register points to the logical end of the ring-buffer * - i.e. it points to where additional new data will be added. * We loop here until the logical beginning equals the logical * end (or in other words, until the ring-buffer is empty). */ while (sc->next_packet != inb(sc->nic_addr + ED_P1_CURR)) { /* get pointer to this buffer header structure */ packet_ptr = (struct ed_ring *)(sc->smem_ring + (sc->next_packet - sc->rec_page_start) * ED_PAGE_SIZE); /* * The byte count includes the FCS - Frame Check Sequence (a * 32 bit CRC). */ len = packet_ptr->count; if ((len >= ETHER_MIN_LEN) && (len <= ETHER_MAX_LEN)) { /* * Go get packet. len - 4 removes CRC from length. * (packet_ptr + 1) points to data just after the packet ring * header (+4 bytes) */ ze_get_packet(sc, (caddr_t)(packet_ptr + 1), len - 4); ++sc->arpcom.ac_if.if_ipackets; } else { /* * Really BAD...probably indicates that the ring pointers * are corrupted. Also seen on early rev chips under * high load - the byte order of the length gets switched. */ log(LOG_ERR, "ze%d: shared memory corrupt - invalid packet length %d\n", unit, len); ze_reset(unit); return; } /* * Update next packet pointer */ sc->next_packet = packet_ptr->next_packet; /* * Update NIC boundry pointer - being careful to keep it * one buffer behind. (as recommended by NS databook) */ boundry = sc->next_packet - 1; if (boundry < sc->rec_page_start) boundry = sc->rec_page_stop - 1; /* * Set NIC to page 0 registers to update boundry register */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2|ED_CR_STA); outb(sc->nic_addr + ED_P0_BNRY, boundry); /* * Set NIC to page 1 registers before looping to top (prepare to * get 'CURR' current pointer) */ outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_1|ED_CR_RD2|ED_CR_STA); } } /* * Ethernet interface interrupt processor */ void zeintr(unit) int unit; { struct ze_softc *sc = &ze_softc[unit]; u_char isr; if(!(sc->arpcom.ac_if.if_flags & IFF_UP)) return; /* * Set NIC to page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2|ED_CR_STA); /* * loop until there are no more new interrupts */ while (isr = inb(sc->nic_addr + ED_P0_ISR)) { /* * reset all the bits that we are 'acknowleging' * by writing a '1' to each bit position that was set * (writing a '1' *clears* the bit) */ outb(sc->nic_addr + ED_P0_ISR, isr); /* * Transmit error. If a TX completed with an error, we end up * throwing the packet away. Really the only error that is * possible is excessive collisions, and in this case it is * best to allow the automatic mechanisms of TCP to backoff * the flow. Of course, with UDP we're screwed, but this is * expected when a network is heavily loaded. */ if (isr & ED_ISR_TXE) { u_char tsr = inb(sc->nic_addr + ED_P0_TSR); u_char ncr = inb(sc->nic_addr + ED_P0_NCR); /* * Excessive collisions (16) */ if ((tsr & ED_TSR_ABT) && (ncr == 0)) { /* * When collisions total 16, the P0_NCR will * indicate 0, and the TSR_ABT is set. */ sc->arpcom.ac_if.if_collisions += 16; } else sc->arpcom.ac_if.if_collisions += ncr; /* * update output errors counter */ ++sc->arpcom.ac_if.if_oerrors; /* * reset tx busy and output active flags */ sc->xmit_busy = 0; sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; /* * clear watchdog timer */ sc->arpcom.ac_if.if_timer = 0; } /* * Receiver Error. One or more of: CRC error, frame alignment error * FIFO overrun, or missed packet. */ if (isr & ED_ISR_RXE) { ++sc->arpcom.ac_if.if_ierrors; #ifdef ZE_DEBUG printf("ze%d: receive error %b\n", unit, inb(sc->nic_addr + ED_P0_RSR), "\20\8DEF\7REC DISAB\6PHY/MC\5MISSED\4OVR\3ALIGN\2FCS\1RCVD"); #endif } /* * Overwrite warning. In order to make sure that a lockup * of the local DMA hasn't occurred, we reset and * re-init the NIC. The NSC manual suggests only a * partial reset/re-init is necessary - but some * chips seem to want more. The DMA lockup has been * seen only with early rev chips - Methinks this * bug was fixed in later revs. -DG */ if (isr & ED_ISR_OVW) { ++sc->arpcom.ac_if.if_ierrors; /* * Stop/reset/re-init NIC */ ze_reset(unit); } /* * Transmission completed normally. */ if (isr & ED_ISR_PTX) { /* * reset tx busy and output active flags */ sc->xmit_busy = 0; sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; /* * clear watchdog timer */ sc->arpcom.ac_if.if_timer = 0; /* * Update total number of successfully transmitted * packets. */ ++sc->arpcom.ac_if.if_opackets; /* * Add in total number of collisions on last * transmission. */ sc->arpcom.ac_if.if_collisions += inb(sc->nic_addr + ED_P0_TBCR0); } /* * Receive Completion. Go and get the packet. * XXX - Doing this on an error is dubious because there * shouldn't be any data to get (we've configured the * interface to not accept packets with errors). */ if (isr & (ED_ISR_PRX|ED_ISR_RXE)) { ze_rint (unit); } /* * If it looks like the transmitter can take more data, * attempt to start output on the interface. If data is * already buffered and ready to go, send it first. */ if ((sc->arpcom.ac_if.if_flags & IFF_OACTIVE) == 0) { if (sc->data_buffered) ze_xmit(&sc->arpcom.ac_if); ze_start(&sc->arpcom.ac_if); } /* * return NIC CR to standard state: page 0, remote DMA complete, * start (toggling the TXP bit off, even if was just set * in the transmit routine, is *okay* - it is 'edge' * triggered from low to high) */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2|ED_CR_STA); /* * If the Network Talley Counters overflow, read them to * reset them. It appears that old 8390's won't * clear the ISR flag otherwise - resulting in an * infinite loop. */ if (isr & ED_ISR_CNT) { (void) inb(sc->nic_addr + ED_P0_CNTR0); (void) inb(sc->nic_addr + ED_P0_CNTR1); (void) inb(sc->nic_addr + ED_P0_CNTR2); } } } /* * Process an ioctl request. This code needs some work - it looks * pretty ugly. */ static int ze_ioctl(ifp, command, data) register struct ifnet *ifp; int command; caddr_t data; { register struct ifaddr *ifa = (struct ifaddr *)data; struct ze_softc *sc = ifp->if_softc; int s, error = 0; s = splnet(); switch (command) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: ze_init(ifp->if_unit); /* before arpwhohas */ arp_ifinit((struct arpcom*) ifp, ifa); break; #endif #ifdef IPX /* * XXX - This code is probably wrong */ case AF_IPX: { register struct ipx_addr *ina = &(IA_SIPX(ifa)->sipx_addr); if (ipx_nullhost(*ina)) ina->x_host = *(union ipx_host *)(sc->arpcom.ac_enaddr); else { /* * */ bcopy((caddr_t)ina->x_host.c_host, (caddr_t)sc->arpcom.ac_enaddr, sizeof(sc->arpcom.ac_enaddr)); } /* * Set new address */ ze_init(ifp->if_unit); break; } #endif #ifdef NS /* * XXX - This code is probably wrong */ case AF_NS: { register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); if (ns_nullhost(*ina)) ina->x_host = *(union ns_host *)(sc->arpcom.ac_enaddr); else { /* * */ bcopy((caddr_t)ina->x_host.c_host, (caddr_t)sc->arpcom.ac_enaddr, sizeof(sc->arpcom.ac_enaddr)); } /* * Set new address */ ze_init(ifp->if_unit); break; } #endif default: ze_init(ifp->if_unit); break; } break; case SIOCSIFFLAGS: /* * When the card is offlined, `up' operation can't be permitted */ if (!sc->last_alive) { int tmp; tmp = (ifp->if_flags & IFF_UP); if (!sc->last_up && (ifp->if_flags & IFF_UP)) { ifp->if_flags &= ~(IFF_UP); } sc->last_up = tmp; } /* * If interface is marked down and it is running, then stop it */ if (((ifp->if_flags & IFF_UP) == 0) && (ifp->if_flags & IFF_RUNNING)) { ze_stop(ifp->if_unit); ifp->if_flags &= ~IFF_RUNNING; } else { /* * If interface is marked up and it is stopped, then start it */ if ((ifp->if_flags & IFF_UP) && ((ifp->if_flags & IFF_RUNNING) == 0)) ze_init(ifp->if_unit); } #if NBPFILTER > 0 if (ifp->if_flags & IFF_PROMISC) { /* * Set promiscuous mode on interface. * XXX - for multicasts to work, we would need to * write 1's in all bits of multicast * hashing array. For now we assume that * this was done in ze_init(). */ outb(sc->nic_addr + ED_P0_RCR, ED_RCR_PRO|ED_RCR_AM|ED_RCR_AB); } else { /* * XXX - for multicasts to work, we would need to * rewrite the multicast hashing array with the * proper hash (would have been destroyed above). */ outb(sc->nic_addr + ED_P0_RCR, ED_RCR_AB); } #endif break; default: error = EINVAL; } (void) splx(s); return (error); } /* * Macro to calculate a new address within shared memory when given an offset * from an address, taking into account ring-wrap. */ #define ringoffset(sc, start, off, type) \ ((type)( ((caddr_t)(start)+(off) >= (sc)->smem_end) ? \ (((caddr_t)(start)+(off))) - (sc)->smem_end \ + (sc)->smem_ring: \ ((caddr_t)(start)+(off)) )) /* * Retreive packet from shared memory and send to the next level up via * ether_input(). If there is a BPF listener, give a copy to BPF, too. */ static void ze_get_packet(sc, buf, len) struct ze_softc *sc; char *buf; u_short len; { struct ether_header *eh; struct mbuf *m, *head = NULL; /* Allocate a header mbuf */ MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) goto bad; m->m_pkthdr.rcvif = &sc->arpcom.ac_if; m->m_pkthdr.len = len; m->m_len = 0; head = m; eh = (struct ether_header *)buf; /* The following sillines is to make NFS happy */ #define EROUND ((sizeof(struct ether_header) + 3) & ~3) #define EOFF (EROUND - sizeof(struct ether_header)) /* * The following assumes there is room for * the ether header in the header mbuf */ head->m_data += EOFF; bcopy(buf, mtod(head, caddr_t), sizeof(struct ether_header)); buf += sizeof(struct ether_header); head->m_len += sizeof(struct ether_header); len -= sizeof(struct ether_header); /* * Pull packet off interface. Or if this was a trailer packet, * the data portion is appended. */ m = ze_ring_to_mbuf(sc, buf, m, len); if (m == NULL) goto bad; #if NBPFILTER > 0 /* * Check if there's a BPF listener on this interface. * If so, hand off the raw packet to bpf. */ if (sc->arpcom.ac_if.if_bpf) { bpf_mtap(&sc->arpcom.ac_if, head); /* * Note that the interface cannot be in promiscuous mode if * there are no BPF listeners. And if we are in promiscuous * mode, we have to check if this packet is really ours. * * XXX This test does not support multicasts. */ if ((sc->arpcom.ac_if.if_flags & IFF_PROMISC) && bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, sizeof(eh->ether_dhost)) != 0 && bcmp(eh->ether_dhost, etherbroadcastaddr, sizeof(eh->ether_dhost)) != 0) { m_freem(head); return; } } #endif /* * Fix up data start offset in mbuf to point past ether header */ m_adj(head, sizeof(struct ether_header)); ether_input(&sc->arpcom.ac_if, eh, head); return; bad: if (head) m_freem(head); return; } /* * Supporting routines */ /* * Given a source and destination address, copy 'amount' of a packet from * the ring buffer into a linear destination buffer. Takes into account * ring-wrap. */ static inline char * ze_ring_copy(sc,src,dst,amount) struct ze_softc *sc; char *src; char *dst; u_short amount; { u_short tmp_amount; /* does copy wrap to lower addr in ring buffer? */ if (src + amount > sc->smem_end) { tmp_amount = sc->smem_end - src; bcopy(src,dst,tmp_amount); /* copy amount up to end of smem */ amount -= tmp_amount; src = sc->smem_ring; dst += tmp_amount; } bcopy(src, dst, amount); return(src + amount); } /* * Copy data from receive buffer to end of mbuf chain * allocate additional mbufs as needed. return pointer * to last mbuf in chain. * sc = ze info (softc) * src = pointer in ze ring buffer * dst = pointer to last mbuf in mbuf chain to copy to * amount = amount of data to copy */ static struct mbuf * ze_ring_to_mbuf(sc,src,dst,total_len) struct ze_softc *sc; char *src; struct mbuf *dst; u_short total_len; { register struct mbuf *m = dst; while (total_len) { register u_short amount = min(total_len, M_TRAILINGSPACE(m)); if (amount == 0) { /* no more data in this mbuf, alloc another */ /* * If there is enough data for an mbuf cluster, attempt * to allocate one of those, otherwise, a regular * mbuf will do. * Note that a regular mbuf is always required, even if * we get a cluster - getting a cluster does not * allocate any mbufs, and one is needed to assign * the cluster to. The mbuf that has a cluster * extension can not be used to contain data - only * the cluster can contain data. */ dst = m; MGET(m, M_DONTWAIT, MT_DATA); if (m == NULL) return (0); if (total_len >= MINCLSIZE) MCLGET(m, M_DONTWAIT); m->m_len = 0; dst->m_next = m; amount = min(total_len, M_TRAILINGSPACE(m)); } src = ze_ring_copy(sc, src, mtod(m, caddr_t) + m->m_len, amount); m->m_len += amount; total_len -= amount; } return (m); } #endif diff --git a/sys/i386/isa/if_zp.c b/sys/i386/isa/if_zp.c index f4b1a7bd3c0e..d76bd88facb0 100644 --- a/sys/i386/isa/if_zp.c +++ b/sys/i386/isa/if_zp.c @@ -1,1158 +1,1158 @@ /* * This code is based on * (1) FreeBSD implementation on ISA/EISA Ethelink III by Herb Peyerl * (2) Linux implementation on PCMCIA Etherlink III by Devid Hinds * (3) FreeBSD implementation on PCMCIA IBM Ethernet Card I/II * by David Greenman * (4) RT-Mach implementation on PCMCIA/ISA/EISA Etherlink III * by Seiji Murata * * Copyright (c) by HOSOKAWA, Tatsumi * Copyright (c) by Seiji Murata */ /* * Copyright (c) 1993 Herb Peyerl * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * From: if_ep.c,v 1.9 1994/01/25 10:46:29 deraadt Exp $ - * $Id: if_zp.c,v 1.26 1996/09/11 16:11:21 nate Exp $ + * $Id: if_zp.c,v 1.27 1996/11/11 17:11:08 bde Exp $ */ /*- * TODO: * [1] integrate into current if_ed.c * [2] parse tuples to find out where to map the shared memory buffer, * and what to write into the configuration register * [3] move pcic-specific code into a separate module. * * Device driver for IBM PCMCIA Credit Card Adapter for Ethernet, * if_ze.c * * Based on the Device driver for National Semiconductor DS8390 ethernet * adapters by David Greenman. Modifications for PCMCIA by Keith Moore. * Adapted for FreeBSD 1.1.5 by Jordan Hubbard. * * Currently supports only the IBM Credit Card Adapter for Ethernet, but * could probably work with other PCMCIA cards also, if it were modified * to get the locations of the PCMCIA configuration option register (COR) * by parsing the configuration tuples, rather than by hard-coding in * the value expected by IBM's card. * * Sources for data on the PCMCIA/IBM CCAE specific portions of the driver: * * [1] _Local Area Network Credit Card Adapters Technical Reference_, * IBM Corp., SC30-3585-00, part # 33G9243. * [2] "pre-alpha" PCMCIA support code for Linux by Barry Jaspan. * [3] Intel 82536SL PC Card Interface Controller Data Sheet, Intel * Order Number 290423-002 * [4] National Semiconductor DP83902A ST-NIC (tm) Serial Network * Interface Controller for Twisted Pair data sheet. * * * Copyright (C) 1993, David Greenman. This software may be used, modified, * copied, distributed, and sold, in both source and binary form provided * that the above copyright and these terms are retained. Under no * circumstances is the author responsible for the proper functioning * of this software, nor does the author assume any responsibility * for damages incurred with its use. */ /*====================================================================== A PCMCIA ethernet driver for the 3com 3c589 card. Written by David Hinds, dhinds@allegro.stanford.edu The network driver code is based on Donald Becker's 3c589 code: Written 1994 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. Donald Becker may be reached at becker@cesdis1.gsfc.nasa.gov ======================================================================*/ /* * I doubled delay loops in this file because it is not enough for some * laptop machines' PCIC (especially, on my Chaplet ILFA 350 ^^;). * HOSOKAWA, Tatsumi */ /* * Very small patch for IBM Ethernet PCMCIA Card II and IBM ThinkPad230Cs. * ETO, Toshihisa */ #include "zp.h" #include "bpfilter.h" #include #if defined(__FreeBSD__) #include #include #include #endif #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #ifdef IPX #include #include #endif #ifdef NS #include #include #endif #if NBPFILTER > 0 #include #include #endif #include #include #include #include #include #include "apm.h" #if NAPM > 0 #include #endif /* NAPM > 0 */ /***************************************************************************** * Driver for Ethernet Adapter * *****************************************************************************/ /* * zp_softc: per line info and status */ static struct zp_softc { struct arpcom arpcom; /* Ethernet common part */ #define MAX_MBS 8 /* # of mbufs we keep around */ struct mbuf *mb[MAX_MBS]; /* spare mbuf storage. */ int next_mb; /* Which mbuf to use next. */ int last_mb; /* Last mbuf. */ int ep_io_addr; /* i/o bus address */ char ep_connectors; /* Connectors on this card. */ int tx_start_thresh;/* Current TX_start_thresh. */ char bus32bit; /* 32bit access possible */ u_short if_port; u_char last_alive; /* information for reconfiguration */ u_char last_up; /* information for reconfiguration */ int slot; /* PCMCIA slot */ #if NAPM > 0 struct apmhook s_hook; /* reconfiguration support */ struct apmhook r_hook; /* reconfiguration support */ #endif /* NAPM > 0 */ } zp_softc[NZP]; static int zpprobe __P((struct isa_device *)); static int zpattach __P((struct isa_device *)); static int zp_suspend __P((void *visa_dev)); static int zp_resume __P((void *visa_dev)); static int zpioctl __P((struct ifnet * ifp, int, caddr_t)); static u_short read_eeprom_data __P((int, int)); static void zpinit __P((int)); static void zpmbuffill __P((void *)); static void zpmbufempty __P((struct zp_softc *)); static void zpread __P((struct zp_softc *)); static void zpreset __P((int)); static void zpstart __P((struct ifnet *)); static void zpstop __P((int)); static void zpwatchdog __P((struct ifnet *)); struct isa_driver zpdriver = { zpprobe, zpattach, "zp" }; #define CARD_INFO "3Com Corporation~3C589" static unsigned char card_info[256]; /* * scan the card information structure looking for the version/product info * tuple. when we find it, compare it to the string we are looking for. * return 1 if we find it, 0 otherwise. */ static int zp_check_cis(unsigned char *scratch) { int i, j, k; card_info[0] = '\0'; i = 0; while (scratch[i] != 0xff && i < 1024) { unsigned char link = scratch[i + 2]; if (scratch[i] == 0x15) { /* level 1 version/product info copy to card_info, * translating '\0' to '~' */ k = 0; for (j = i + 8; scratch[j] != 0xff; j += 2) card_info[k++] = scratch[j] == '\0' ? '~' : scratch[j]; card_info[k++] = '\0'; return (bcmp(card_info, CARD_INFO, sizeof(CARD_INFO) - 1) == 0); } i += 4 + 2 * link; } return 0; } /* * Probe each slot looking for an IBM Credit Card Adapter for Ethernet * For each card that we find, map its card information structure * into system memory at 'scratch' and see whether it's one of ours. * Return the slot number if we find a card, or -1 otherwise. * * Side effects: * + On success, leaves CIS mapped into memory at 'scratch'; * caller must free it. * + On success, leaves ethernet address in enet_addr. * + Leaves product/vendor id of last card probed in 'card_info' */ static int prev_slot = 0; static int zp_find_adapter(unsigned char *scratch, int reconfig) { int slot; for (slot = prev_slot; slot < MAXSLOT; ++slot) { /* see if there's a PCMCIA controller here Intel PCMCIA * controllers use 0x82 and 0x83 IBM clone chips use 0x88 and * 0x89, apparently */ /* IBM ThinkPad230Cs use 0x84. */ unsigned char idbyte = pcic_getb(slot, PCIC_ID_REV); if (idbyte != 0x82 && idbyte != 0x83 && idbyte != 0x84 && /* for IBM ThinkPad 230Cs */ idbyte != 0x88 && idbyte != 0x89) { continue; } if ((pcic_getb(slot, PCIC_STATUS) & PCIC_CD) != PCIC_CD) { if (!reconfig) { printf("zp: slot %d: no card in slot\n", slot); } else { log(LOG_NOTICE, "zp: slot %d: no card in slot\n", slot); } /* no card in slot */ continue; } pcic_power_on(slot); pcic_reset(slot); /* map the card's attribute memory and examine its card * information structure tuples for something we recognize. */ pcic_map_memory(slot, 0, kvtop(scratch), 0L, 0xFFFL, ATTRIBUTE, 1); if ((zp_check_cis(scratch)) > 0) { /* found it */ if (!reconfig) { printf("zp: found card in slot %d\n", slot); } else { log(LOG_NOTICE, "zp: found card in slot %d\n", slot); } prev_slot = (prev_slot == MAXSLOT - 1) ? 0 : prev_slot + 1; return slot; } else { if (!reconfig) { printf("zp: pcmcia slot %d: %s\n", slot, card_info); } else { log(LOG_NOTICE, "zp: pcmcia slot %d: %s\n", slot, card_info); } } pcic_unmap_memory(slot, 0); } prev_slot = 0; return -1; } /* * macros to handle casting unsigned long to (char *) so we can * read/write into physical memory space. */ #define PEEK(addr) (*((unsigned char *)(addr))) #define POKE(addr,val) do { PEEK(addr) = (val); } while (0) /* * Determine if the device is present * * on entry: * a pointer to an isa_device struct * on exit: * NULL if device not found * or # of i/o addresses used (if found) */ static int zpprobe(struct isa_device * isa_dev) { struct zp_softc *sc = &zp_softc[isa_dev->id_unit]; int slot; u_short k; int re_init_flag; if ((slot = zp_find_adapter(isa_dev->id_maddr, isa_dev->id_reconfig)) < 0) return 0; /* okay, we found a card, so set it up */ /* Inhibit 16 bit memory delay. POINTETH.SYS apparently does this, for * what reason I don't know. */ pcic_putb(slot, PCIC_CDGC, pcic_getb(slot, PCIC_CDGC) | PCIC_16_DL_INH); /* things to map (1) card's EEPROM is already mapped by the * find_adapter routine but we still need to get the card's ethernet * address. after that we unmap that part of attribute memory. (2) * card configuration registers need to be mapped in so we can set the * configuration and socket # registers. (3) shared memory packet * buffer (4) i/o ports (5) IRQ */ #ifdef notdef /* Sigh. Location of the ethernet address isn't documented in [1]. It * was derived by doing a hex dump of all of attribute memory and * looking for the IBM vendor prefix. */ enet_addr[0] = PEEK(isa_dev->id_maddr + 0xff0); enet_addr[1] = PEEK(isa_dev->id_maddr + 0xff2); enet_addr[2] = PEEK(isa_dev->id_maddr + 0xff4); enet_addr[3] = PEEK(isa_dev->id_maddr + 0xff6); enet_addr[4] = PEEK(isa_dev->id_maddr + 0xff8); enet_addr[5] = PEEK(isa_dev->id_maddr + 0xffa); #endif re_init_flag = 0; re_init: /* (2) map card configuration registers. these are offset in card * memory space by 0x20000. normally we could get this offset from * the card information structure, but I'm too lazy and am not quite * sure if I understand the CIS anyway. * * XXX IF YOU'RE TRYING TO PORT THIS DRIVER FOR A DIFFERENT PCMCIA CARD, * the most likely thing to change is the constant 0x20000 in the next * statement. Oh yes, also change the card id string that we probe * for. */ pcic_map_memory(slot, 0, kvtop(isa_dev->id_maddr), 0x10000, 8L, ATTRIBUTE, 1); #if OLD_3C589B_CARDS POKE(isa_dev->id_maddr, 0x80); /* reset the card (how long?) */ DELAY(40000); #endif /* Set the configuration index. According to [1], the adapter won't * respond to any i/o signals until we do this; it uses the Memory * Only interface (whatever that is; it's not documented). Also turn * on "level" (not pulse) interrupts. * * XXX probably should init the socket and copy register also, so that we * can deal with multiple instances of the same card. */ POKE(isa_dev->id_maddr, 0x41); pcic_unmap_memory(slot, 0); /* (4) map i/o ports. * * XXX is it possible that the config file leaves this unspecified, in * which case we have to pick one? * * At least one PCMCIA device driver I'v seen maps a block of 32 * consecutive i/o ports as two windows of 16 ports each. Maybe some * other pcic chips are restricted to 16-port windows; the 82365SL * doesn't seem to have that problem. But since we have an extra * window anyway... */ pcic_map_io(slot, 0, isa_dev->id_iobase, 16, 2); /* (5) configure the card for the desired interrupt * * XXX is it possible that the config file leaves this unspecified? */ pcic_map_irq(slot, ffs(isa_dev->id_irq) - 1); /* tell the PCIC that this is an I/O card (not memory) */ pcic_putb(slot, PCIC_INT_GEN, pcic_getb(slot, PCIC_INT_GEN) | PCIC_CARDTYPE); sc->ep_io_addr = isa_dev->id_iobase; GO_WINDOW(0); k = read_eeprom_data(BASE, EEPROM_ADDR_CFG); /* get addr cfg */ sc->if_port = k >> 14; k = (k & 0x1f) * 0x10 + 0x200; /* decode base addr. */ if (k != (u_short) isa_dev->id_iobase) { if (!re_init_flag) { re_init_flag++; goto re_init; } return (0); } k = read_eeprom_data(BASE, EEPROM_RESOURCE_CFG); k >>= 12; if (isa_dev->id_irq != (1 << ((k == 2) ? 9 : k))) return (0); outb(BASE, ACTIVATE_ADAPTER_TO_CONFIG); /* information for reconfiguration */ sc->last_alive = 0; sc->last_up = 0; sc->slot = slot; return (0x10); /* 16 bytes of I/O space used. */ } #if NAPM > 0 static int zp_suspend(visa_dev) void *visa_dev; { #if 0 struct isa_device *isa_dev = visa_dev; struct zp_softc *sc = &zp_softc[isa_dev->id_unit]; pcic_power_off(sc->slot); #endif return 0; } static int zp_resume(visa_dev) void *visa_dev; { struct isa_device *isa_dev = visa_dev; prev_slot = 0; reconfig_isadev(isa_dev, &net_imask); return 0; } #endif /* NAPM > 0 */ /* * Install interface into kernel networking data structures */ static int zpattach(isa_dev) struct isa_device *isa_dev; { struct zp_softc *sc = &zp_softc[isa_dev->id_unit]; struct ifnet *ifp = &sc->arpcom.ac_if; u_short i; int pl; /* PCMCIA card can be offlined. Reconfiguration is required */ if (isa_dev->id_reconfig) { if (!isa_dev->id_alive && sc->last_alive) { pl = splimp(); sc->last_up = (ifp->if_flags & IFF_UP); if_down(ifp); splx(pl); sc->last_alive = 0; } if (isa_dev->id_alive && !sc->last_alive) { zpreset(isa_dev->id_unit); if (sc->last_up) { pl = splimp(); if_up(ifp); splx(pl); } sc->last_alive = 1; } return 1; } else { sc->last_alive = 1; } sc->ep_io_addr = isa_dev->id_iobase; printf("zp%d: ", isa_dev->id_unit); sc->ep_connectors = 0; i = inw(isa_dev->id_iobase + EP_W0_CONFIG_CTRL); if (i & IS_AUI) { printf("aui"); sc->ep_connectors |= AUI; } if (i & IS_BNC) { if (sc->ep_connectors) printf("/"); printf("bnc"); sc->ep_connectors |= BNC; } if (i & IS_UTP) { if (sc->ep_connectors) printf("/"); printf("utp"); sc->ep_connectors |= UTP; } if (!sc->ep_connectors) printf("no connectors!"); GO_WINDOW(0); { short tmp_addr[3]; int i; for (i = 0; i < 3; i++) { tmp_addr[i] = htons(read_eeprom_data(BASE, i)); } bcopy(tmp_addr, sc->arpcom.ac_enaddr, 6); } printf(" address %6D\n", sc->arpcom.ac_enaddr, ":"); ifp->if_softc = sc; ifp->if_mtu = ETHERMTU; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX; ifp->if_unit = isa_dev->id_unit; ifp->if_name = "zp"; ifp->if_output = ether_output; ifp->if_start = zpstart; ifp->if_ioctl = zpioctl; ifp->if_watchdog = zpwatchdog; /* Select connector according to board setting. */ ifp->if_flags |= IFF_LINK0; if_attach(ifp); ether_ifattach(ifp); #if NBPFILTER > 0 bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); #endif #if NAPM > 0 sc->s_hook.ah_fun = zp_suspend; sc->s_hook.ah_arg = (void *) isa_dev; sc->s_hook.ah_name = "3Com PCMCIA Etherlink III 3C589"; sc->s_hook.ah_order = APM_MID_ORDER; apm_hook_establish(APM_HOOK_SUSPEND, &sc->s_hook); sc->r_hook.ah_fun = zp_resume; sc->r_hook.ah_arg = (void *) isa_dev; sc->r_hook.ah_name = "3Com PCMCIA Etherlink III 3C589"; sc->r_hook.ah_order = APM_MID_ORDER; apm_hook_establish(APM_HOOK_RESUME, &sc->r_hook); #endif /* NAPM > 0 */ return 1; } /* * The order in here seems important. Otherwise we may not receive * interrupts. ?! */ static void zpinit(unit) int unit; { register struct zp_softc *sc = &zp_softc[unit]; register struct ifnet *ifp = &sc->arpcom.ac_if; int s, i; - if (ifp->if_addrlist == (struct ifaddr *) 0) + if (TAILQ_EMPTY(&ifp->if_addrhead)) /* XXX unlikely */ return; s = splimp(); while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); GO_WINDOW(0); /* Disable the card */ outw(BASE + EP_W0_CONFIG_CTRL, 0); /* Enable the card */ outw(BASE + EP_W0_CONFIG_CTRL, ENABLE_DRQ_IRQ); GO_WINDOW(2); /* Reload the ether_addr. */ for (i = 0; i < 6; i++) outb(BASE + EP_W2_ADDR_0 + i, sc->arpcom.ac_enaddr[i]); outw(BASE + EP_COMMAND, RX_RESET); outw(BASE + EP_COMMAND, TX_RESET); /* Window 1 is operating window */ GO_WINDOW(1); for (i = 0; i < 31; i++) inb(BASE + EP_W1_TX_STATUS); /* get rid of stray intr's */ outw(BASE + EP_COMMAND, ACK_INTR | 0xff); outw(BASE + EP_COMMAND, SET_RD_0_MASK | S_CARD_FAILURE | S_RX_COMPLETE | S_TX_COMPLETE | S_TX_AVAIL); outw(BASE + EP_COMMAND, SET_INTR_MASK | S_CARD_FAILURE | S_RX_COMPLETE | S_TX_COMPLETE | S_TX_AVAIL); #ifndef IFF_MULTICAST #define IFF_MULTICAST 0x10000 #endif outw(BASE + EP_COMMAND, SET_RX_FILTER | FIL_INDIVIDUAL | ((sc->arpcom.ac_if.if_flags & IFF_MULTICAST) ? FIL_GROUP : 0) | FIL_BRDCST | ((sc->arpcom.ac_if.if_flags & IFF_PROMISC) ? FIL_ALL : 0)); /* you can `ifconfig (link0|-link0) ep0' to get the following * behaviour: -link0 disable AUI/UTP. enable BNC. link0 disable * BNC. enable AUI. if the card has a UTP connector, that is enabled * too. not sure, but it seems you have to be careful to not plug * things into both AUI & UTP. */ if (!(ifp->if_flags & IFF_LINK0) && (sc->ep_connectors & BNC)) { GO_WINDOW(0); /* set the xcvr */ outw(BASE + EP_W0_ADDRESS_CFG, 3 << 14); GO_WINDOW(2); outw(BASE + EP_COMMAND, START_TRANSCEIVER); GO_WINDOW(1); } #if defined(__NetBSD__) || defined(__FreeBSD__) if ((ifp->if_flags & IFF_LINK0) && (sc->ep_connectors & UTP)) { #else if ((ifp->if_flags & IFF_ALTPHYS) && (sc->ep_connectors & UTP)) { #endif GO_WINDOW(4); outw(BASE + EP_W4_MEDIA_TYPE, ENABLE_UTP); GO_WINDOW(1); } outw(BASE + EP_COMMAND, RX_ENABLE); outw(BASE + EP_COMMAND, TX_ENABLE); ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; /* just in case */ sc->tx_start_thresh = 20; /* probably a good starting point. */ /* Store up a bunch of mbuf's for use later. (MAX_MBS). First we free * up any that we had in case we're being called from intr or * somewhere else. */ sc->last_mb = 0; sc->next_mb = 0; zpmbuffill(sc); zpstart(ifp); splx(s); } static const char padmap[] = {0, 3, 2, 1}; static void zpstart(ifp) struct ifnet *ifp; { register struct zp_softc *sc = ifp->if_softc; struct mbuf *m, *top; int s, len, pad; s = splimp(); if (sc->arpcom.ac_if.if_flags & IFF_OACTIVE) { splx(s); return; } startagain: /* Sneak a peek at the next packet */ m = sc->arpcom.ac_if.if_snd.ifq_head; if (m == 0) { splx(s); return; } for (len = 0, top = m; m; m = m->m_next) len += m->m_len; pad = padmap[len & 3]; /* The 3c509 automatically pads short packets to minimum ethernet * length, but we drop packets that are too large. Perhaps we should * truncate them instead? */ if (len + pad > ETHER_MAX_LEN) { /* packet is obviously too large: toss it */ ++sc->arpcom.ac_if.if_oerrors; IF_DEQUEUE(&sc->arpcom.ac_if.if_snd, m); m_freem(m); goto readcheck; } if (inw(BASE + EP_W1_FREE_TX) < len + pad + 4) { /* no room in FIFO */ outw(BASE + EP_COMMAND, SET_TX_AVAIL_THRESH | (len + pad + 4)); sc->arpcom.ac_if.if_flags |= IFF_OACTIVE; splx(s); return; } IF_DEQUEUE(&sc->arpcom.ac_if.if_snd, m); if (m == 0) { /* not really needed */ splx(s); return; } outw(BASE + EP_COMMAND, SET_TX_START_THRESH | (len / 4 + sc->tx_start_thresh)); outw(BASE + EP_W1_TX_PIO_WR_1, len); outw(BASE + EP_W1_TX_PIO_WR_1, 0xffff); /* Second dword meaningless */ for (top = m; m != 0; m = m->m_next) { if (sc->bus32bit) { outsl(BASE + EP_W1_TX_PIO_WR_1, mtod(m, caddr_t), m->m_len / 4); if (m->m_len & 3) outsb(BASE + EP_W1_TX_PIO_WR_1, mtod(m, caddr_t) + (m->m_len & (~3)), m->m_len & 3); } else { outsw(BASE + EP_W1_TX_PIO_WR_1, mtod(m, caddr_t), m->m_len / 2); if (m->m_len & 1) outb(BASE + EP_W1_TX_PIO_WR_1, *(mtod(m, caddr_t) + m->m_len - 1)); } } while (pad--) outb(BASE + EP_W1_TX_PIO_WR_1, 0); /* Padding */ #if NBPFILTER > 0 if (sc->arpcom.ac_if.if_bpf) { bpf_mtap(&sc->arpcom.ac_if, top); } #endif m_freem(top); ++sc->arpcom.ac_if.if_opackets; /* Is another packet coming in? We don't want to overflow the tiny RX * fifo. */ readcheck: if (inw(BASE + EP_W1_RX_STATUS) & RX_BYTES_MASK) { splx(s); return; } goto startagain; } void zpintr(unit) int unit; { int status, i; register struct zp_softc *sc = &zp_softc[unit]; struct ifnet *ifp = &sc->arpcom.ac_if; status = 0; checkintr: status = inw(BASE + EP_STATUS) & (S_TX_COMPLETE | S_TX_AVAIL | S_RX_COMPLETE | S_CARD_FAILURE); checkintr2: if (status == 0) { /* No interrupts. */ outw(BASE + EP_COMMAND, C_INTR_LATCH); status = inw(BASE + EP_STATUS) & (S_TX_COMPLETE | S_TX_AVAIL | S_RX_COMPLETE | S_CARD_FAILURE); if (status) goto checkintr2; return; } /* important that we do this first. */ outw(BASE + EP_COMMAND, ACK_INTR | status); if (status & S_TX_AVAIL) { status &= ~S_TX_AVAIL; inw(BASE + EP_W1_FREE_TX); sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; zpstart(&sc->arpcom.ac_if); } if (status & S_RX_COMPLETE) { status &= ~S_RX_COMPLETE; zpread(sc); } if (status & S_CARD_FAILURE) { printf("zp%d: reset (status: %x)\n", unit, status); outw(BASE + EP_COMMAND, C_INTR_LATCH); zpinit(unit); return; } if (status & S_TX_COMPLETE) { status &= ~S_TX_COMPLETE; /* We need to read TX_STATUS until we get a 0 status in order * to turn off the interrupt flag. */ while ((i = inb(BASE + EP_W1_TX_STATUS)) & TXS_COMPLETE) { outw(BASE + EP_W1_TX_STATUS, 0x0); if (i & (TXS_MAX_COLLISION | TXS_JABBER | TXS_UNDERRUN)) { if (i & TXS_MAX_COLLISION) ++sc->arpcom.ac_if.if_collisions; if (i & (TXS_JABBER | TXS_UNDERRUN)) { outw(BASE + EP_COMMAND, TX_RESET); if (i & TXS_UNDERRUN) { if (sc->tx_start_thresh < ETHER_MAX_LEN) { sc->tx_start_thresh += 20; outw(BASE + EP_COMMAND, SET_TX_START_THRESH | sc->tx_start_thresh); } } } outw(BASE + EP_COMMAND, TX_ENABLE); ++sc->arpcom.ac_if.if_oerrors; } } zpstart(ifp); } goto checkintr; } static void zpread(sc) register struct zp_softc *sc; { struct ether_header *eh; struct mbuf *mcur, *m, *m0, *top; int totlen, lenthisone; int save_totlen; int off; totlen = inw(BASE + EP_W1_RX_STATUS); off = 0; top = 0; if (totlen & ERR_RX) { ++sc->arpcom.ac_if.if_ierrors; goto out; } save_totlen = totlen &= RX_BYTES_MASK; /* Lower 11 bits = RX bytes. */ m = sc->mb[sc->next_mb]; sc->mb[sc->next_mb] = 0; if (m == 0) { MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == 0) goto out; } else { /* Convert one of our saved mbuf's */ sc->next_mb = (sc->next_mb + 1) % MAX_MBS; m->m_data = m->m_pktdat; m->m_flags = M_PKTHDR; } top = m0 = m; /* We assign top so we can "goto out" */ #define EROUND ((sizeof(struct ether_header) + 3) & ~3) #define EOFF (EROUND - sizeof(struct ether_header)) m0->m_data += EOFF; /* Read what should be the header. */ insw(BASE + EP_W1_RX_PIO_RD_1, mtod(m0, caddr_t), sizeof(struct ether_header) / 2); m->m_len = sizeof(struct ether_header); totlen -= sizeof(struct ether_header); /* mostly deal with trailer here. (untested) We do this in a couple * of parts. First we check for a trailer, if we have one we convert * the mbuf back to a regular mbuf and set the offset and subtract * sizeof(struct ether_header) from the pktlen. After we've read the * packet off the interface (all except for the trailer header, we * then get a header mbuf, read the trailer into it, and fix up the * mbuf pointer chain. */ eh = mtod(m, struct ether_header *); while (totlen > 0) { lenthisone = min(totlen, M_TRAILINGSPACE(m)); if (lenthisone == 0) { /* no room in this one */ mcur = m; m = sc->mb[sc->next_mb]; sc->mb[sc->next_mb] = 0; if (!m) { MGET(m, M_DONTWAIT, MT_DATA); if (m == 0) goto out; } else { timeout(zpmbuffill, sc, 0); sc->next_mb = (sc->next_mb + 1) % MAX_MBS; } if (totlen >= MINCLSIZE) MCLGET(m, M_DONTWAIT); m->m_len = 0; mcur->m_next = m; lenthisone = min(totlen, M_TRAILINGSPACE(m)); } if (sc->bus32bit) { insl(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t) + m->m_len, lenthisone / 4); m->m_len += (lenthisone & ~3); if (lenthisone & 3) insb(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t) + m->m_len, lenthisone & 3); m->m_len += (lenthisone & 3); } else { insw(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t) + m->m_len, lenthisone / 2); m->m_len += lenthisone; if (lenthisone & 1) *(mtod(m, caddr_t) + m->m_len - 1) = inb(BASE + EP_W1_RX_PIO_RD_1); } totlen -= lenthisone; } if (off) { top = sc->mb[sc->next_mb]; sc->mb[sc->next_mb] = 0; if (top == 0) { MGETHDR(m, M_DONTWAIT, MT_DATA); if (top == 0) { top = m0; goto out; } } else { /* Convert one of our saved mbuf's */ sc->next_mb = (sc->next_mb + 1) % MAX_MBS; top->m_data = top->m_pktdat; top->m_flags = M_PKTHDR; } insw(BASE + EP_W1_RX_PIO_RD_1, mtod(top, caddr_t), sizeof(struct ether_header)); top->m_next = m0; top->m_len = sizeof(struct ether_header); /* XXX Accomodate for type and len from beginning of trailer */ top->m_pkthdr.len = save_totlen - (2 * sizeof(u_short)); } else { top = m0; top->m_pkthdr.len = save_totlen; } top->m_pkthdr.rcvif = &sc->arpcom.ac_if; outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); ++sc->arpcom.ac_if.if_ipackets; #if NBPFILTER > 0 if (sc->arpcom.ac_if.if_bpf) { bpf_mtap(&sc->arpcom.ac_if, top); /* Note that the interface cannot be in promiscuous mode if * there are no BPF listeners. And if we are in promiscuous * mode, we have to check if this packet is really ours. */ if ((sc->arpcom.ac_if.if_flags & IFF_PROMISC) && (eh->ether_dhost[0] & 1) == 0 && bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, sizeof(eh->ether_dhost)) != 0 && bcmp(eh->ether_dhost, etherbroadcastaddr, sizeof(eh->ether_dhost)) != 0) { m_freem(top); return; } } #endif m_adj(top, sizeof(struct ether_header)); ether_input(&sc->arpcom.ac_if, eh, top); return; out: outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); if (top) m_freem(top); } /* * Look familiar? */ static int zpioctl(ifp, cmd, data) register struct ifnet *ifp; int cmd; caddr_t data; { register struct ifaddr *ifa = (struct ifaddr *) data; struct zp_softc *sc = ifp->if_softc; int error = 0; switch (cmd) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: zpinit(ifp->if_unit); /* before arpwhohas */ arp_ifinit((struct arpcom *) ifp, ifa); break; #endif #ifdef IPX case AF_IPX: { register struct ipx_addr *ina = &(IA_SIPX(ifa)->sipx_addr); if (ipx_nullhost(*ina)) ina->x_host = *(union ipx_host *) (sc->arpcom.ac_enaddr); else { ifp->if_flags &= ~IFF_RUNNING; bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->arpcom.ac_enaddr, sizeof(sc->arpcom.ac_enaddr)); } zpinit(ifp->if_unit); break; } #endif #ifdef NS case AF_NS: { register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); if (ns_nullhost(*ina)) ina->x_host = *(union ns_host *) (sc->arpcom.ac_enaddr); else { ifp->if_flags &= ~IFF_RUNNING; bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->arpcom.ac_enaddr, sizeof(sc->arpcom.ac_enaddr)); } zpinit(ifp->if_unit); break; } #endif default: zpinit(ifp->if_unit); break; } break; case SIOCSIFFLAGS: if ((ifp->if_flags & IFF_UP) == 0 && ifp->if_flags & IFF_RUNNING) { ifp->if_flags &= ~IFF_RUNNING; zpstop(ifp->if_unit); zpmbufempty(sc); break; } zpinit(ifp->if_unit); break; default: error = EINVAL; } return (error); } static void zpreset(unit) int unit; { int s = splimp(); zpstop(unit); zpinit(unit); splx(s); } static void zpwatchdog(ifp) struct ifnet *ifp; { log(LOG_ERR, "zp%d: watchdog\n", ifp->if_unit); ifp->if_oerrors++; zpreset(ifp->if_unit); } static void zpstop(unit) int unit; { struct zp_softc *sc = &zp_softc[unit]; outw(BASE + EP_COMMAND, RX_DISABLE); outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS); outw(BASE + EP_COMMAND, TX_DISABLE); outw(BASE + EP_COMMAND, STOP_TRANSCEIVER); outw(BASE + EP_COMMAND, RX_RESET); outw(BASE + EP_COMMAND, TX_RESET); outw(BASE + EP_COMMAND, C_INTR_LATCH); outw(BASE + EP_COMMAND, SET_RD_0_MASK); outw(BASE + EP_COMMAND, SET_INTR_MASK); outw(BASE + EP_COMMAND, SET_RX_FILTER); } static u_short read_eeprom_data(id_port, offset) int id_port; int offset; { outb(id_port + 10, 0x80 + offset); DELAY(1000); return inw(id_port + 12); } static void zpmbuffill(sp) void *sp; { struct zp_softc *sc = (struct zp_softc *) sp; int s, i; s = splimp(); i = sc->last_mb; do { if (sc->mb[i] == NULL) MGET(sc->mb[i], M_DONTWAIT, MT_DATA); if (sc->mb[i] == NULL) break; i = (i + 1) % MAX_MBS; } while (i != sc->next_mb); sc->last_mb = i; splx(s); } static void zpmbufempty(sc) struct zp_softc *sc; { int s, i; s = splimp(); for (i = 0; i < MAX_MBS; i++) { if (sc->mb[i]) { m_freem(sc->mb[i]); sc->mb[i] = NULL; } } sc->last_mb = sc->next_mb = 0; untimeout(zpmbuffill, sc); splx(s); } diff --git a/sys/net/if.c b/sys/net/if.c index 78a98d8a162f..65ec137dd661 100644 --- a/sys/net/if.c +++ b/sys/net/if.c @@ -1,750 +1,762 @@ /* * Copyright (c) 1980, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)if.c 8.3 (Berkeley) 1/4/94 - * $Id: if.c,v 1.36 1996/08/07 04:09:05 julian Exp $ + * $Id: if.c,v 1.37 1996/12/11 20:38:14 wollman Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * System initialization */ static int ifconf __P((int, caddr_t)); static void ifinit __P((void *)); static void if_qflush __P((struct ifqueue *)); static void if_slowtimo __P((void *)); static void link_rtrequest __P((int, struct rtentry *, struct sockaddr *)); SYSINIT(interfaces, SI_SUB_PROTO_IF, SI_ORDER_FIRST, ifinit, NULL) int ifqmaxlen = IFQ_MAXLEN; struct ifnethead ifnet; /* depend on static init XXX */ /* * Network interface utility routines. * * Routines with ifa_ifwith* names take sockaddr *'s as * parameters. * * This routine assumes that it will be called at splimp() or higher. */ /* ARGSUSED*/ void ifinit(dummy) void *dummy; { register struct ifnet *ifp; for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_link.tqe_next) if (ifp->if_snd.ifq_maxlen == 0) ifp->if_snd.ifq_maxlen = ifqmaxlen; if_slowtimo(0); } int if_index = 0; struct ifaddr **ifnet_addrs; /* * Attach an interface to the * list of "active" interfaces. */ void if_attach(ifp) struct ifnet *ifp; { unsigned socksize, ifasize; int namelen, masklen; char workbuf[64]; register struct sockaddr_dl *sdl; register struct ifaddr *ifa; static int if_indexlim = 8; static int inited; if (!inited) { TAILQ_INIT(&ifnet); inited = 1; } TAILQ_INSERT_TAIL(&ifnet, ifp, if_link); ifp->if_index = ++if_index; + /* + * XXX - + * The old code would work if the interface passed a pre-existing + * chain of ifaddrs to this code. We don't trust our callers to + * properly initialize the tailq, however, so we no longer allow + * this unlikely case. + */ + TAILQ_INIT(&ifp->if_addrhead); microtime(&ifp->if_lastchange); if (ifnet_addrs == 0 || if_index >= if_indexlim) { unsigned n = (if_indexlim <<= 1) * sizeof(ifa); struct ifaddr **q = (struct ifaddr **) malloc(n, M_IFADDR, M_WAITOK); bzero((caddr_t)q, n); if (ifnet_addrs) { bcopy((caddr_t)ifnet_addrs, (caddr_t)q, n/2); free((caddr_t)ifnet_addrs, M_IFADDR); } ifnet_addrs = q; } /* * create a Link Level name for this device */ namelen = sprintf(workbuf, "%s%d", ifp->if_name, ifp->if_unit); #define _offsetof(t, m) ((int)((caddr_t)&((t *)0)->m)) masklen = _offsetof(struct sockaddr_dl, sdl_data[0]) + namelen; socksize = masklen + ifp->if_addrlen; #define ROUNDUP(a) (1 + (((a) - 1) | (sizeof(long) - 1))) socksize = ROUNDUP(socksize); if (socksize < sizeof(*sdl)) socksize = sizeof(*sdl); ifasize = sizeof(*ifa) + 2 * socksize; ifa = (struct ifaddr *)malloc(ifasize, M_IFADDR, M_WAITOK); if (ifa) { bzero((caddr_t)ifa, ifasize); sdl = (struct sockaddr_dl *)(ifa + 1); sdl->sdl_len = socksize; sdl->sdl_family = AF_LINK; bcopy(workbuf, sdl->sdl_data, namelen); sdl->sdl_nlen = namelen; sdl->sdl_index = ifp->if_index; sdl->sdl_type = ifp->if_type; ifnet_addrs[if_index - 1] = ifa; ifa->ifa_ifp = ifp; - ifa->ifa_next = ifp->if_addrlist; ifa->ifa_rtrequest = link_rtrequest; - ifp->if_addrlist = ifa; ifa->ifa_addr = (struct sockaddr *)sdl; - sdl = (struct sockaddr_dl *)(socksize + (caddr_t)sdl); ifa->ifa_netmask = (struct sockaddr *)sdl; sdl->sdl_len = masklen; while (namelen != 0) sdl->sdl_data[--namelen] = 0xff; + TAILQ_INSERT_HEAD(&ifp->if_addrhead, ifa, ifa_link); } } /* * Locate an interface based on a complete address. */ /*ARGSUSED*/ struct ifaddr * ifa_ifwithaddr(addr) register struct sockaddr *addr; { register struct ifnet *ifp; register struct ifaddr *ifa; #define equal(a1, a2) \ (bcmp((caddr_t)(a1), (caddr_t)(a2), ((struct sockaddr *)(a1))->sa_len) == 0) for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_link.tqe_next) - for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) { + for (ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) { if (ifa->ifa_addr->sa_family != addr->sa_family) continue; if (equal(addr, ifa->ifa_addr)) return (ifa); if ((ifp->if_flags & IFF_BROADCAST) && ifa->ifa_broadaddr && equal(ifa->ifa_broadaddr, addr)) return (ifa); } return ((struct ifaddr *)0); } /* * Locate the point to point interface with a given destination address. */ /*ARGSUSED*/ struct ifaddr * ifa_ifwithdstaddr(addr) register struct sockaddr *addr; { register struct ifnet *ifp; register struct ifaddr *ifa; for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_link.tqe_next) if (ifp->if_flags & IFF_POINTOPOINT) - for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) { + for (ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) { if (ifa->ifa_addr->sa_family != addr->sa_family) continue; if (ifa->ifa_dstaddr && equal(addr, ifa->ifa_dstaddr)) return (ifa); } return ((struct ifaddr *)0); } /* * Find an interface on a specific network. If many, choice * is most specific found. */ struct ifaddr * ifa_ifwithnet(addr) struct sockaddr *addr; { register struct ifnet *ifp; register struct ifaddr *ifa; struct ifaddr *ifa_maybe = (struct ifaddr *) 0; u_int af = addr->sa_family; char *addr_data = addr->sa_data, *cplim; if (af == AF_LINK) { register struct sockaddr_dl *sdl = (struct sockaddr_dl *)addr; if (sdl->sdl_index && sdl->sdl_index <= if_index) return (ifnet_addrs[sdl->sdl_index - 1]); } for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_link.tqe_next) { - for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) { + for (ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) { register char *cp, *cp2, *cp3; if (ifa->ifa_addr->sa_family != af) next: continue; if (ifp->if_flags & IFF_POINTOPOINT) { if (ifa->ifa_dstaddr != 0 && equal(addr, ifa->ifa_dstaddr)) return (ifa); } else { if (ifa->ifa_netmask == 0) continue; cp = addr_data; cp2 = ifa->ifa_addr->sa_data; cp3 = ifa->ifa_netmask->sa_data; cplim = ifa->ifa_netmask->sa_len + (char *)ifa->ifa_netmask; while (cp3 < cplim) if ((*cp++ ^ *cp2++) & *cp3++) goto next; if (ifa_maybe == 0 || rn_refines((caddr_t)ifa->ifa_netmask, (caddr_t)ifa_maybe->ifa_netmask)) ifa_maybe = ifa; } } } return (ifa_maybe); } /* * Find an interface address specific to an interface best matching * a given address. */ struct ifaddr * ifaof_ifpforaddr(addr, ifp) struct sockaddr *addr; register struct ifnet *ifp; { register struct ifaddr *ifa; register char *cp, *cp2, *cp3; register char *cplim; struct ifaddr *ifa_maybe = 0; u_int af = addr->sa_family; if (af >= AF_MAX) return (0); - for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) { + for (ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) { if (ifa->ifa_addr->sa_family != af) continue; if (ifa_maybe == 0) ifa_maybe = ifa; if (ifa->ifa_netmask == 0) { if (equal(addr, ifa->ifa_addr) || (ifa->ifa_dstaddr && equal(addr, ifa->ifa_dstaddr))) return (ifa); continue; } if (ifp->if_flags & IFF_POINTOPOINT) { if (equal(addr, ifa->ifa_dstaddr)) return (ifa); } else { cp = addr->sa_data; cp2 = ifa->ifa_addr->sa_data; cp3 = ifa->ifa_netmask->sa_data; cplim = ifa->ifa_netmask->sa_len + (char *)ifa->ifa_netmask; for (; cp3 < cplim; cp3++) if ((*cp++ ^ *cp2++) & *cp3) break; if (cp3 == cplim) return (ifa); } } return (ifa_maybe); } #include /* * Default action when installing a route with a Link Level gateway. * Lookup an appropriate real ifa to point to. * This should be moved to /sys/net/link.c eventually. */ static void link_rtrequest(cmd, rt, sa) int cmd; register struct rtentry *rt; struct sockaddr *sa; { register struct ifaddr *ifa; struct sockaddr *dst; struct ifnet *ifp; if (cmd != RTM_ADD || ((ifa = rt->rt_ifa) == 0) || ((ifp = ifa->ifa_ifp) == 0) || ((dst = rt_key(rt)) == 0)) return; ifa = ifaof_ifpforaddr(dst, ifp); if (ifa) { IFAFREE(rt->rt_ifa); rt->rt_ifa = ifa; ifa->ifa_refcnt++; if (ifa->ifa_rtrequest && ifa->ifa_rtrequest != link_rtrequest) ifa->ifa_rtrequest(cmd, rt, sa); } } /* * Mark an interface down and notify protocols of * the transition. * NOTE: must be called at splnet or eqivalent. */ void if_down(ifp) register struct ifnet *ifp; { register struct ifaddr *ifa; ifp->if_flags &= ~IFF_UP; microtime(&ifp->if_lastchange); - for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) + for (ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) pfctlinput(PRC_IFDOWN, ifa->ifa_addr); if_qflush(&ifp->if_snd); rt_ifmsg(ifp); } /* * Mark an interface up and notify protocols of * the transition. * NOTE: must be called at splnet or eqivalent. */ void if_up(ifp) register struct ifnet *ifp; { ifp->if_flags |= IFF_UP; microtime(&ifp->if_lastchange); #ifdef notyet register struct ifaddr *ifa; /* this has no effect on IP, and will kill all iso connections XXX */ for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) pfctlinput(PRC_IFUP, ifa->ifa_addr); #endif rt_ifmsg(ifp); } /* * Flush an interface queue. */ static void if_qflush(ifq) register struct ifqueue *ifq; { register struct mbuf *m, *n; n = ifq->ifq_head; while ((m = n) != 0) { n = m->m_act; m_freem(m); } ifq->ifq_head = 0; ifq->ifq_tail = 0; ifq->ifq_len = 0; } /* * Handle interface watchdog timer routines. Called * from softclock, we decrement timers (if set) and * call the appropriate interface routine on expiration. */ static void if_slowtimo(arg) void *arg; { register struct ifnet *ifp; int s = splimp(); for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_link.tqe_next) { if (ifp->if_timer == 0 || --ifp->if_timer) continue; if (ifp->if_watchdog) (*ifp->if_watchdog)(ifp); } splx(s); timeout(if_slowtimo, (void *)0, hz / IFNET_SLOWHZ); } /* * Map interface name to * interface structure pointer. */ struct ifnet * ifunit(name) register char *name; { register char *cp; register struct ifnet *ifp; int unit; unsigned len; char *ep, c; for (cp = name; cp < name + IFNAMSIZ && *cp; cp++) if (*cp >= '0' && *cp <= '9') break; if (*cp == '\0' || cp == name + IFNAMSIZ) return ((struct ifnet *)0); /* * Save first char of unit, and pointer to it, * so we can put a null there to avoid matching * initial substrings of interface names. */ len = cp - name + 1; c = *cp; ep = cp; for (unit = 0; *cp >= '0' && *cp <= '9'; ) unit = unit * 10 + *cp++ - '0'; if (*cp != '\0') return 0; /* no trailing garbage allowed */ *ep = 0; for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_link.tqe_next) { if (bcmp(ifp->if_name, name, len)) continue; if (unit == ifp->if_unit) break; } *ep = c; return (ifp); } /* * Interface ioctls. */ int ifioctl(so, cmd, data, p) struct socket *so; int cmd; caddr_t data; struct proc *p; { register struct ifnet *ifp; register struct ifreq *ifr; int error; switch (cmd) { case SIOCGIFCONF: case OSIOCGIFCONF: return (ifconf(cmd, data)); } ifr = (struct ifreq *)data; ifp = ifunit(ifr->ifr_name); if (ifp == 0) return (ENXIO); switch (cmd) { case SIOCGIFFLAGS: ifr->ifr_flags = ifp->if_flags; break; case SIOCGIFMETRIC: ifr->ifr_metric = ifp->if_metric; break; case SIOCGIFMTU: ifr->ifr_mtu = ifp->if_mtu; break; case SIOCGIFPHYS: ifr->ifr_phys = ifp->if_physical; break; case SIOCSIFFLAGS: error = suser(p->p_ucred, &p->p_acflag); if (error) return (error); if (ifp->if_flags & IFF_UP && (ifr->ifr_flags & IFF_UP) == 0) { int s = splimp(); if_down(ifp); splx(s); } if (ifr->ifr_flags & IFF_UP && (ifp->if_flags & IFF_UP) == 0) { int s = splimp(); if_up(ifp); splx(s); } ifp->if_flags = (ifp->if_flags & IFF_CANTCHANGE) | (ifr->ifr_flags &~ IFF_CANTCHANGE); if (ifp->if_ioctl) (void) (*ifp->if_ioctl)(ifp, cmd, data); microtime(&ifp->if_lastchange); break; case SIOCSIFMETRIC: error = suser(p->p_ucred, &p->p_acflag); if (error) return (error); ifp->if_metric = ifr->ifr_metric; microtime(&ifp->if_lastchange); break; case SIOCSIFPHYS: error = suser(p->p_ucred, &p->p_acflag); if (error) return error; if (!ifp->if_ioctl) return EOPNOTSUPP; error = (*ifp->if_ioctl)(ifp, cmd, data); if (error == 0) microtime(&ifp->if_lastchange); return(error); case SIOCSIFMTU: error = suser(p->p_ucred, &p->p_acflag); if (error) return (error); if (ifp->if_ioctl == NULL) return (EOPNOTSUPP); /* * 72 was chosen below because it is the size of a TCP/IP * header (40) + the minimum mss (32). */ if (ifr->ifr_mtu < 72 || ifr->ifr_mtu > 65535) return (EINVAL); error = (*ifp->if_ioctl)(ifp, cmd, data); if (error == 0) microtime(&ifp->if_lastchange); return(error); case SIOCADDMULTI: case SIOCDELMULTI: error = suser(p->p_ucred, &p->p_acflag); if (error) return (error); if (ifp->if_ioctl == NULL) return (EOPNOTSUPP); error = (*ifp->if_ioctl)(ifp, cmd, data); if (error == 0 ) microtime(&ifp->if_lastchange); return(error); default: if (so->so_proto == 0) return (EOPNOTSUPP); #ifndef COMPAT_43 return ((*so->so_proto->pr_usrreqs->pru_control)(so, cmd, data, ifp)); #else { int ocmd = cmd; switch (cmd) { case SIOCSIFDSTADDR: case SIOCSIFADDR: case SIOCSIFBRDADDR: case SIOCSIFNETMASK: #if BYTE_ORDER != BIG_ENDIAN if (ifr->ifr_addr.sa_family == 0 && ifr->ifr_addr.sa_len < 16) { ifr->ifr_addr.sa_family = ifr->ifr_addr.sa_len; ifr->ifr_addr.sa_len = 16; } #else if (ifr->ifr_addr.sa_len == 0) ifr->ifr_addr.sa_len = 16; #endif break; case OSIOCGIFADDR: cmd = SIOCGIFADDR; break; case OSIOCGIFDSTADDR: cmd = SIOCGIFDSTADDR; break; case OSIOCGIFBRDADDR: cmd = SIOCGIFBRDADDR; break; case OSIOCGIFNETMASK: cmd = SIOCGIFNETMASK; } error = ((*so->so_proto->pr_usrreqs->pru_control)(so, cmd, data, ifp)); switch (ocmd) { case OSIOCGIFADDR: case OSIOCGIFDSTADDR: case OSIOCGIFBRDADDR: case OSIOCGIFNETMASK: *(u_short *)&ifr->ifr_addr = ifr->ifr_addr.sa_family; } return (error); } #endif } return (0); } /* * Set/clear promiscuous mode on interface ifp based on the truth value * of pswitch. The calls are reference counted so that only the first * "on" request actually has an effect, as does the final "off" request. * Results are undefined if the "off" and "on" requests are not matched. */ int ifpromisc(ifp, pswitch) struct ifnet *ifp; int pswitch; { struct ifreq ifr; if (pswitch) { /* * If the device is not configured up, we cannot put it in * promiscuous mode. */ if ((ifp->if_flags & IFF_UP) == 0) return (ENETDOWN); if (ifp->if_pcount++ != 0) return (0); ifp->if_flags |= IFF_PROMISC; log(LOG_INFO, "%s%d: promiscuous mode enabled\n", ifp->if_name, ifp->if_unit); } else { if (--ifp->if_pcount > 0) return (0); ifp->if_flags &= ~IFF_PROMISC; } ifr.ifr_flags = ifp->if_flags; return ((*ifp->if_ioctl)(ifp, SIOCSIFFLAGS, (caddr_t)&ifr)); } /* * Return interface configuration * of system. List may be used * in later ioctl's (above) to get * other information. */ /*ARGSUSED*/ static int ifconf(cmd, data) int cmd; caddr_t data; { register struct ifconf *ifc = (struct ifconf *)data; register struct ifnet *ifp = ifnet.tqh_first; register struct ifaddr *ifa; struct ifreq ifr, *ifrp; int space = ifc->ifc_len, error = 0; ifrp = ifc->ifc_req; for (; space > sizeof (ifr) && ifp; ifp = ifp->if_link.tqe_next) { char workbuf[64]; int ifnlen; ifnlen = sprintf(workbuf, "%s%d", ifp->if_name, ifp->if_unit); if(ifnlen + 1 > sizeof ifr.ifr_name) { error = ENAMETOOLONG; } else { strcpy(ifr.ifr_name, workbuf); } - if ((ifa = ifp->if_addrlist) == 0) { + if ((ifa = ifp->if_addrhead.tqh_first) == 0) { bzero((caddr_t)&ifr.ifr_addr, sizeof(ifr.ifr_addr)); error = copyout((caddr_t)&ifr, (caddr_t)ifrp, sizeof (ifr)); if (error) break; space -= sizeof (ifr), ifrp++; } else - for ( ; space > sizeof (ifr) && ifa; ifa = ifa->ifa_next) { + for ( ; space > sizeof (ifr) && ifa; + ifa = ifa->ifa_link.tqe_next) { register struct sockaddr *sa = ifa->ifa_addr; #ifdef COMPAT_43 if (cmd == OSIOCGIFCONF) { struct osockaddr *osa = (struct osockaddr *)&ifr.ifr_addr; ifr.ifr_addr = *sa; osa->sa_family = sa->sa_family; error = copyout((caddr_t)&ifr, (caddr_t)ifrp, sizeof (ifr)); ifrp++; } else #endif if (sa->sa_len <= sizeof(*sa)) { ifr.ifr_addr = *sa; error = copyout((caddr_t)&ifr, (caddr_t)ifrp, sizeof (ifr)); ifrp++; } else { space -= sa->sa_len - sizeof(*sa); if (space < sizeof (ifr)) break; error = copyout((caddr_t)&ifr, (caddr_t)ifrp, sizeof (ifr.ifr_name)); if (error == 0) error = copyout((caddr_t)sa, (caddr_t)&ifrp->ifr_addr, sa->sa_len); ifrp = (struct ifreq *) (sa->sa_len + (caddr_t)&ifrp->ifr_addr); } if (error) break; space -= sizeof (ifr); } } ifc->ifc_len -= space; return (error); } SYSCTL_NODE(_net, PF_LINK, link, CTLFLAG_RW, 0, "Link layers"); SYSCTL_NODE(_net_link, 0, generic, CTLFLAG_RW, 0, "Generic link-management"); diff --git a/sys/net/if.h b/sys/net/if.h index 267c5b01415b..09b6bc9f9aa7 100644 --- a/sys/net/if.h +++ b/sys/net/if.h @@ -1,458 +1,452 @@ /* * Copyright (c) 1982, 1986, 1989, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)if.h 8.1 (Berkeley) 6/10/93 - * $Id: if.h,v 1.38 1996/12/10 18:03:51 wollman Exp $ + * $Id: if.h,v 1.40 1996/12/11 20:38:14 wollman Exp $ */ #ifndef _NET_IF_H_ #define _NET_IF_H_ /* * Structures defining a network interface, providing a packet * transport mechanism (ala level 0 of the PUP protocols). * * Each interface accepts output datagrams of a specified maximum * length, and provides higher level routines with input datagrams * received from its medium. * * Output occurs when the routine if_output is called, with three parameters: * (*ifp->if_output)(ifp, m, dst, rt) * Here m is the mbuf chain to be sent and dst is the destination address. * The output routine encapsulates the supplied datagram if necessary, * and then transmits it on its medium. * * On input, each interface unwraps the data received by it, and either * places it on the input queue of a internetwork datagram routine * and posts the associated software interrupt, or passes the datagram to a raw * packet input routine. * * Routines exist for locating interfaces by their addresses * or for locating a interface on a certain network, as well as more general * routing and gateway routines maintaining information used to locate * interfaces. These routines live in the files if.c and route.c */ #ifdef __STDC__ /* * Forward structure declarations for function prototypes [sic]. */ struct mbuf; struct proc; struct rtentry; struct socket; struct ether_header; #endif -#include /* get LIST macros */ +#include /* get TAILQ macros */ + +TAILQ_HEAD(ifnethead, ifnet); /* we use TAILQs so that the order of */ +TAILQ_HEAD(ifaddrhead, ifaddr); /* instantiation is preserved in the list */ /* * Structure describing information about an interface * which may be of interest to management entities. */ struct if_data { /* generic interface information */ u_char ifi_type; /* ethernet, tokenring, etc */ u_char ifi_physical; /* e.g., AUI, Thinnet, 10base-T, etc */ u_char ifi_addrlen; /* media address length */ u_char ifi_hdrlen; /* media header length */ u_char ifi_recvquota; /* polling quota for receive intrs */ u_char ifi_xmitquota; /* polling quota for xmit intrs */ u_long ifi_mtu; /* maximum transmission unit */ u_long ifi_metric; /* routing metric (external only) */ u_long ifi_baudrate; /* linespeed */ /* volatile statistics */ u_long ifi_ipackets; /* packets received on interface */ u_long ifi_ierrors; /* input errors on interface */ u_long ifi_opackets; /* packets sent on interface */ u_long ifi_oerrors; /* output errors on interface */ u_long ifi_collisions; /* collisions on csma interfaces */ u_long ifi_ibytes; /* total number of octets received */ u_long ifi_obytes; /* total number of octets sent */ u_long ifi_imcasts; /* packets received via multicast */ u_long ifi_omcasts; /* packets sent via multicast */ u_long ifi_iqdrops; /* dropped on input, this interface */ u_long ifi_noproto; /* destined for unsupported protocol */ u_long ifi_recvtiming; /* usec spent receiving when timing */ u_long ifi_xmittiming; /* usec spent xmitting when timing */ struct timeval ifi_lastchange; /* time of last administrative change */ }; /* * Structure defining a queue for a network interface. */ struct ifqueue { struct mbuf *ifq_head; struct mbuf *ifq_tail; int ifq_len; int ifq_maxlen; int ifq_drops; }; /* * Structure defining a network interface. * * (Would like to call this struct ``if'', but C isn't PL/1.) */ struct ifnet { void *if_softc; /* pointer to driver state */ char *if_name; /* name, e.g. ``en'' or ``lo'' */ TAILQ_ENTRY(ifnet) if_link; /* all struct ifnets are chained */ -#if 0 - LIST_HEAD(, ifaddr) if_addrlist; -#else - struct ifaddr *if_addrlist; /* linked list of addresses per if */ -#endif + struct ifaddrhead if_addrhead; /* linked list of addresses per if */ int if_pcount; /* number of promiscuous listeners */ struct bpf_if *if_bpf; /* packet filter structure */ u_short if_index; /* numeric abbreviation for this if */ short if_unit; /* sub-unit for lower level driver */ short if_timer; /* time 'til if_watchdog called */ short if_flags; /* up/down, broadcast, etc. */ int if_ipending; /* interrupts pending */ void *if_linkmib; /* link-type-specific MIB data */ size_t if_linkmiblen; /* length of above data */ struct if_data if_data; /* procedure handles */ int (*if_output) /* output routine (enqueue) */ __P((struct ifnet *, struct mbuf *, struct sockaddr *, struct rtentry *)); void (*if_start) /* initiate output routine */ __P((struct ifnet *)); int (*if_done) /* output complete routine */ __P((struct ifnet *)); /* (XXX not used; fake prototype) */ int (*if_ioctl) /* ioctl routine */ __P((struct ifnet *, int, caddr_t)); void (*if_watchdog) /* timer routine */ __P((struct ifnet *)); int (*if_poll_recv) /* polled receive routine */ __P((struct ifnet *, int *)); int (*if_poll_xmit) /* polled transmit routine */ __P((struct ifnet *, int *)); void (*if_poll_intren) /* polled interrupt reenable routine */ __P((struct ifnet *)); void (*if_poll_slowinput) /* input routine for slow devices */ __P((struct ifnet *, struct mbuf *)); void (*if_init) /* Init routine */ __P((void *)); struct ifqueue if_snd; /* output queue */ struct ifqueue *if_poll_slowq; /* input queue for slow devices */ }; typedef void if_init_f_t __P((void *)); -TAILQ_HEAD(ifnethead, ifnet); #define if_mtu if_data.ifi_mtu #define if_type if_data.ifi_type #define if_physical if_data.ifi_physical #define if_addrlen if_data.ifi_addrlen #define if_hdrlen if_data.ifi_hdrlen #define if_metric if_data.ifi_metric #define if_baudrate if_data.ifi_baudrate #define if_ipackets if_data.ifi_ipackets #define if_ierrors if_data.ifi_ierrors #define if_opackets if_data.ifi_opackets #define if_oerrors if_data.ifi_oerrors #define if_collisions if_data.ifi_collisions #define if_ibytes if_data.ifi_ibytes #define if_obytes if_data.ifi_obytes #define if_imcasts if_data.ifi_imcasts #define if_omcasts if_data.ifi_omcasts #define if_iqdrops if_data.ifi_iqdrops #define if_noproto if_data.ifi_noproto #define if_lastchange if_data.ifi_lastchange #define if_recvquota if_data.ifi_recvquota #define if_xmitquota if_data.ifi_xmitquota #define if_rawoutput(if, m, sa) if_output(if, m, sa, (struct rtentry *)0) #define IFF_UP 0x1 /* interface is up */ #define IFF_BROADCAST 0x2 /* broadcast address valid */ #define IFF_DEBUG 0x4 /* turn on debugging */ #define IFF_LOOPBACK 0x8 /* is a loopback net */ #define IFF_POINTOPOINT 0x10 /* interface is point-to-point link */ /*#define IFF_NOTRAILERS 0x20 * obsolete: avoid use of trailers */ #define IFF_RUNNING 0x40 /* resources allocated */ #define IFF_NOARP 0x80 /* no address resolution protocol */ #define IFF_PROMISC 0x100 /* receive all packets */ #define IFF_ALLMULTI 0x200 /* receive all multicast packets */ #define IFF_OACTIVE 0x400 /* transmission in progress */ #define IFF_SIMPLEX 0x800 /* can't hear own transmissions */ #define IFF_LINK0 0x1000 /* per link layer defined bit */ #define IFF_LINK1 0x2000 /* per link layer defined bit */ #define IFF_LINK2 0x4000 /* per link layer defined bit */ #define IFF_ALTPHYS IFF_LINK2 /* use alternate physical connection */ #define IFF_MULTICAST 0x8000 /* supports multicast */ /* flags set internally only: */ #define IFF_CANTCHANGE \ (IFF_BROADCAST|IFF_POINTOPOINT|IFF_RUNNING|IFF_OACTIVE|\ IFF_SIMPLEX|IFF_MULTICAST|IFF_ALLMULTI) /* * These really don't belong here, but there's no other obviously appropriate * location. */ #define IFP_AUI 0 #define IFP_10BASE2 1 #define IFP_10BASET 2 /* etc. */ /* * Bit values in if_ipending */ #define IFI_RECV 1 /* I want to receive */ #define IFI_XMIT 2 /* I want to transmit */ /* * Output queues (ifp->if_snd) and slow device input queues (*ifp->if_slowq) * are queues of messages stored on ifqueue structures * (defined above). Entries are added to and deleted from these structures * by these macros, which should be called with ipl raised to splimp(). */ #define IF_QFULL(ifq) ((ifq)->ifq_len >= (ifq)->ifq_maxlen) #define IF_DROP(ifq) ((ifq)->ifq_drops++) #define IF_ENQUEUE(ifq, m) { \ (m)->m_nextpkt = 0; \ if ((ifq)->ifq_tail == 0) \ (ifq)->ifq_head = m; \ else \ (ifq)->ifq_tail->m_nextpkt = m; \ (ifq)->ifq_tail = m; \ (ifq)->ifq_len++; \ } #define IF_PREPEND(ifq, m) { \ (m)->m_nextpkt = (ifq)->ifq_head; \ if ((ifq)->ifq_tail == 0) \ (ifq)->ifq_tail = (m); \ (ifq)->ifq_head = (m); \ (ifq)->ifq_len++; \ } #define IF_DEQUEUE(ifq, m) { \ (m) = (ifq)->ifq_head; \ if (m) { \ if (((ifq)->ifq_head = (m)->m_nextpkt) == 0) \ (ifq)->ifq_tail = 0; \ (m)->m_nextpkt = 0; \ (ifq)->ifq_len--; \ } \ } #ifdef KERNEL #define IF_ENQ_DROP(ifq, m) if_enq_drop(ifq, m) #if defined(__GNUC__) && defined(MT_HEADER) static inline int if_queue_drop(struct ifqueue *ifq, struct mbuf *m) { IF_DROP(ifq); return 0; } static inline int if_enq_drop(struct ifqueue *ifq, struct mbuf *m) { if (IF_QFULL(ifq) && !if_queue_drop(ifq, m)) return 0; IF_ENQUEUE(ifq, m); return 1; } #else #ifdef MT_HEADER int if_enq_drop __P((struct ifqueue *, struct mbuf *)); #endif #endif #endif /* KERNEL */ #define IFQ_MAXLEN 50 #define IFNET_SLOWHZ 1 /* granularity is 1 second */ /* * The ifaddr structure contains information about one address * of an interface. They are maintained by the different address families, * are allocated and attached when an address is set, and are linked * together so all addresses for an interface can be located. */ struct ifaddr { struct sockaddr *ifa_addr; /* address of interface */ struct sockaddr *ifa_dstaddr; /* other end of p-to-p link */ #define ifa_broadaddr ifa_dstaddr /* broadcast address interface */ struct sockaddr *ifa_netmask; /* used to determine subnet */ struct ifnet *ifa_ifp; /* back-pointer to interface */ -#if 0 - LIST_ENTRY(ifaddr) ifa_link; -#else - struct ifaddr *ifa_next; /* next address for interface */ -#endif + TAILQ_ENTRY(ifaddr) ifa_link; /* queue macro glue */ void (*ifa_rtrequest) /* check or clean routes (+ or -)'d */ __P((int, struct rtentry *, struct sockaddr *)); u_short ifa_flags; /* mostly rt_flags for cloning */ short ifa_refcnt; /* references to this structure */ int ifa_metric; /* cost of going out this interface */ #ifdef notdef struct rtentry *ifa_rt; /* XXXX for ROUTETOIF ????? */ #endif }; #define IFA_ROUTE RTF_UP /* route installed */ /* * Message format for use in obtaining information about interfaces * from getkerninfo and the routing socket */ struct if_msghdr { u_short ifm_msglen; /* to skip over non-understood messages */ u_char ifm_version; /* future binary compatability */ u_char ifm_type; /* message type */ int ifm_addrs; /* like rtm_addrs */ int ifm_flags; /* value of if_flags */ u_short ifm_index; /* index for associated ifp */ struct if_data ifm_data;/* statistics and other data about if */ }; /* * Message format for use in obtaining information about interface addresses * from getkerninfo and the routing socket */ struct ifa_msghdr { u_short ifam_msglen; /* to skip over non-understood messages */ u_char ifam_version; /* future binary compatability */ u_char ifam_type; /* message type */ int ifam_addrs; /* like rtm_addrs */ int ifam_flags; /* value of ifa_flags */ u_short ifam_index; /* index for associated ifp */ int ifam_metric; /* value of ifa_metric */ }; /* * Interface request structure used for socket * ioctl's. All interface ioctl's must have parameter * definitions which begin with ifr_name. The * remainder may be interface specific. */ struct ifreq { #define IFNAMSIZ 16 char ifr_name[IFNAMSIZ]; /* if name, e.g. "en0" */ union { struct sockaddr ifru_addr; struct sockaddr ifru_dstaddr; struct sockaddr ifru_broadaddr; short ifru_flags; int ifru_metric; int ifru_mtu; int ifru_phys; caddr_t ifru_data; } ifr_ifru; #define ifr_addr ifr_ifru.ifru_addr /* address */ #define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-to-p link */ #define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ #define ifr_flags ifr_ifru.ifru_flags /* flags */ #define ifr_metric ifr_ifru.ifru_metric /* metric */ #define ifr_mtu ifr_ifru.ifru_mtu /* mtu */ #define ifr_phys ifr_ifru.ifru_phys /* physical wire */ #define ifr_data ifr_ifru.ifru_data /* for use by interface */ }; struct ifaliasreq { char ifra_name[IFNAMSIZ]; /* if name, e.g. "en0" */ struct sockaddr ifra_addr; struct sockaddr ifra_broadaddr; struct sockaddr ifra_mask; }; /* * Structure used in SIOCGIFCONF request. * Used to retrieve interface configuration * for machine (useful for programs which * must know all networks accessible). */ struct ifconf { int ifc_len; /* size of associated buffer */ union { caddr_t ifcu_buf; struct ifreq *ifcu_req; } ifc_ifcu; #define ifc_buf ifc_ifcu.ifcu_buf /* buffer address */ #define ifc_req ifc_ifcu.ifcu_req /* array of structures returned */ }; #ifdef KERNEL #define IFAFREE(ifa) \ if ((ifa)->ifa_refcnt <= 0) \ ifafree(ifa); \ else \ (ifa)->ifa_refcnt--; extern struct ifnethead ifnet; extern int ifqmaxlen; extern struct ifnet loif[]; extern int if_index; extern struct ifaddr **ifnet_addrs; void ether_ifattach __P((struct ifnet *)); void ether_input __P((struct ifnet *, struct ether_header *, struct mbuf *)); int ether_output __P((struct ifnet *, struct mbuf *, struct sockaddr *, struct rtentry *)); int ether_ioctl __P((struct ifnet *, int, caddr_t)); void if_attach __P((struct ifnet *)); void if_down __P((struct ifnet *)); void if_up __P((struct ifnet *)); #ifdef vax void ifubareset __P((int)); #endif /*void ifinit __P((void));*/ /* declared in systm.h for main() */ int ifioctl __P((struct socket *, int, caddr_t, struct proc *)); int ifpromisc __P((struct ifnet *, int)); struct ifnet *ifunit __P((char *)); int if_poll_recv_slow __P((struct ifnet *ifp, int *quotap)); void if_poll_xmit_slow __P((struct ifnet *ifp, int *quotap)); void if_poll_throttle __P((void)); void if_poll_unthrottle __P((void *)); void if_poll_init __P((void)); void if_poll __P((void)); struct ifaddr *ifa_ifwithaddr __P((struct sockaddr *)); struct ifaddr *ifa_ifwithdstaddr __P((struct sockaddr *)); struct ifaddr *ifa_ifwithnet __P((struct sockaddr *)); struct ifaddr *ifa_ifwithroute __P((int, struct sockaddr *, struct sockaddr *)); struct ifaddr *ifaof_ifpforaddr __P((struct sockaddr *, struct ifnet *)); void ifafree __P((struct ifaddr *)); int looutput __P((struct ifnet *, struct mbuf *, struct sockaddr *, struct rtentry *)); #endif /* KERNEL */ #endif /* !_NET_IF_H_ */ diff --git a/sys/net/if_ethersubr.c b/sys/net/if_ethersubr.c index bdcdde7239a3..3aefabe55f6f 100644 --- a/sys/net/if_ethersubr.c +++ b/sys/net/if_ethersubr.c @@ -1,948 +1,948 @@ /* * Copyright (c) 1982, 1989, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)if_ethersubr.c 8.1 (Berkeley) 6/10/93 - * $Id: if_ethersubr.c,v 1.27 1996/11/18 04:55:44 davidg Exp $ + * $Id: if_ethersubr.c,v 1.28 1996/12/10 07:29:48 davidg Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #endif #include #ifdef IPX #include #include #endif #ifdef NS #include #include ushort ns_nettype; int ether_outputdebug = 0; int ether_inputdebug = 0; #endif #ifdef ISO #include #include #include #include #endif /*#ifdef LLC #include #include #endif*/ #if defined(LLC) && defined(CCITT) extern struct ifqueue pkintrq; #endif #ifdef NETATALK #include #include #include #define llc_snap_org_code llc_un.type_snap.org_code #define llc_snap_ether_type llc_un.type_snap.ether_type extern u_char at_org_code[ 3 ]; extern u_char aarp_org_code[ 3 ]; #endif NETATALK u_char etherbroadcastaddr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; #define senderr(e) { error = (e); goto bad;} /* * Ethernet output routine. * Encapsulate a packet of type family for the local net. * Use trailer local net encapsulation if enough data in first * packet leaves a multiple of 512 bytes of data in remainder. * Assumes that ifp is actually pointer to arpcom structure. */ int ether_output(ifp, m0, dst, rt0) register struct ifnet *ifp; struct mbuf *m0; struct sockaddr *dst; struct rtentry *rt0; { short type; int s, error = 0; u_char *cp, edst[6]; register struct mbuf *m2, *m = m0; register struct rtentry *rt; struct mbuf *mcopy = (struct mbuf *)0; register struct ether_header *eh; int off, len = m->m_pkthdr.len; struct arpcom *ac = (struct arpcom *)ifp; register struct ifqueue *inq; #ifdef NETATALK struct at_ifaddr *aa; #endif NETATALK if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) senderr(ENETDOWN); rt = rt0; if (rt) { if ((rt->rt_flags & RTF_UP) == 0) { rt0 = rt = rtalloc1(dst, 1, 0UL); if (rt0) rt->rt_refcnt--; else senderr(EHOSTUNREACH); } if (rt->rt_flags & RTF_GATEWAY) { if (rt->rt_gwroute == 0) goto lookup; if (((rt = rt->rt_gwroute)->rt_flags & RTF_UP) == 0) { rtfree(rt); rt = rt0; lookup: rt->rt_gwroute = rtalloc1(rt->rt_gateway, 1, 0UL); if ((rt = rt->rt_gwroute) == 0) senderr(EHOSTUNREACH); } } if (rt->rt_flags & RTF_REJECT) if (rt->rt_rmx.rmx_expire == 0 || time.tv_sec < rt->rt_rmx.rmx_expire) senderr(rt == rt0 ? EHOSTDOWN : EHOSTUNREACH); } switch (dst->sa_family) { #ifdef INET case AF_INET: if (!arpresolve(ac, rt, m, dst, edst, rt0)) return (0); /* if not yet resolved */ /* If broadcasting on a simplex interface, loopback a copy */ if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX)) mcopy = m_copy(m, 0, (int)M_COPYALL); off = m->m_pkthdr.len - m->m_len; type = htons(ETHERTYPE_IP); break; #endif #ifdef IPX case AF_IPX: type = htons(ETHERTYPE_IPX); bcopy((caddr_t)&(((struct sockaddr_ipx *)dst)->sipx_addr.x_host), (caddr_t)edst, sizeof (edst)); if (!bcmp((caddr_t)edst, (caddr_t)&ipx_thishost, sizeof(edst))) return (looutput(ifp, m, dst, rt)); /* If broadcasting on a simplex interface, loopback a copy */ if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX)) mcopy = m_copy(m, 0, (int)M_COPYALL); break; #endif #ifdef NETATALK case AF_APPLETALK: if (!aarpresolve(ac, m, (struct sockaddr_at *)dst, edst)) { #ifdef NETATALKDEBUG extern char *prsockaddr(struct sockaddr *); printf("aarpresolv: failed for %s\n", prsockaddr(dst)); #endif NETATALKDEBUG return (0); } /* * ifaddr is the first thing in at_ifaddr */ if ((aa = (struct at_ifaddr *)at_ifawithnet( - (struct sockaddr_at *)dst, ifp->if_addrlist)) + (struct sockaddr_at *)dst, &ifp->if_addrhead)) == 0) goto bad; /* * In the phase 2 case, we need to prepend an mbuf for the llc header. * Since we must preserve the value of m, which is passed to us by * value, we m_copy() the first mbuf, and use it for our llc header. */ if ( aa->aa_flags & AFA_PHASE2 ) { struct llc llc; M_PREPEND(m, sizeof(struct llc), M_WAIT); len += sizeof(struct llc); llc.llc_dsap = llc.llc_ssap = LLC_SNAP_LSAP; llc.llc_control = LLC_UI; bcopy(at_org_code, llc.llc_snap_org_code, sizeof(at_org_code)); llc.llc_snap_ether_type = htons( ETHERTYPE_AT ); bcopy(&llc, mtod(m, caddr_t), sizeof(struct llc)); type = htons(m->m_pkthdr.len); } else { type = htons(ETHERTYPE_AT); } break; #endif NETATALK #ifdef NS case AF_NS: switch(ns_nettype){ default: case 0x8137: /* Novell Ethernet_II Ethernet TYPE II */ type = 0x8137; break; case 0x0: /* Novell 802.3 */ type = htons( m->m_pkthdr.len); break; case 0xe0e0: /* Novell 802.2 and Token-Ring */ M_PREPEND(m, 3, M_WAIT); type = htons( m->m_pkthdr.len); cp = mtod(m, u_char *); *cp++ = 0xE0; *cp++ = 0xE0; *cp++ = 0x03; break; } bcopy((caddr_t)&(((struct sockaddr_ns *)dst)->sns_addr.x_host), (caddr_t)edst, sizeof (edst)); if (!bcmp((caddr_t)edst, (caddr_t)&ns_thishost, sizeof(edst))){ m->m_pkthdr.rcvif = ifp; schednetisr(NETISR_NS); inq = &nsintrq; s = splimp(); if (IF_QFULL(inq)) { IF_DROP(inq); m_freem(m); } else IF_ENQUEUE(inq, m); splx(s); return (error); } if (!bcmp((caddr_t)edst, (caddr_t)&ns_broadhost, sizeof(edst))){ m2 = m_copy(m, 0, (int)M_COPYALL); m2->m_pkthdr.rcvif = ifp; schednetisr(NETISR_NS); inq = &nsintrq; s = splimp(); if (IF_QFULL(inq)) { IF_DROP(inq); m_freem(m2); } else IF_ENQUEUE(inq, m2); splx(s); } /* If broadcasting on a simplex interface, loopback a copy */ if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX)){ mcopy = m_copy(m, 0, (int)M_COPYALL); } break; #endif /* NS */ #ifdef ISO case AF_ISO: { int snpalen; struct llc *l; register struct sockaddr_dl *sdl; if (rt && (sdl = (struct sockaddr_dl *)rt->rt_gateway) && sdl->sdl_family == AF_LINK && sdl->sdl_alen > 0) { bcopy(LLADDR(sdl), (caddr_t)edst, sizeof(edst)); } else if (error = iso_snparesolve(ifp, (struct sockaddr_iso *)dst, (char *)edst, &snpalen)) goto bad; /* Not Resolved */ /* If broadcasting on a simplex interface, loopback a copy */ if (*edst & 1) m->m_flags |= (M_BCAST|M_MCAST); if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX) && (mcopy = m_copy(m, 0, (int)M_COPYALL))) { M_PREPEND(mcopy, sizeof (*eh), M_DONTWAIT); if (mcopy) { eh = mtod(mcopy, struct ether_header *); bcopy((caddr_t)edst, (caddr_t)eh->ether_dhost, sizeof (edst)); bcopy((caddr_t)ac->ac_enaddr, (caddr_t)eh->ether_shost, sizeof (edst)); } } M_PREPEND(m, 3, M_DONTWAIT); if (m == NULL) return (0); type = htons(m->m_pkthdr.len); l = mtod(m, struct llc *); l->llc_dsap = l->llc_ssap = LLC_ISO_LSAP; l->llc_control = LLC_UI; len += 3; IFDEBUG(D_ETHER) int i; printf("unoutput: sending pkt to: "); for (i=0; i<6; i++) printf("%x ", edst[i] & 0xff); printf("\n"); ENDDEBUG } break; #endif /* ISO */ #ifdef LLC /* case AF_NSAP: */ case AF_CCITT: { register struct sockaddr_dl *sdl = (struct sockaddr_dl *) rt -> rt_gateway; if (sdl && sdl->sdl_family == AF_LINK && sdl->sdl_alen > 0) { bcopy(LLADDR(sdl), (char *)edst, sizeof(edst)); } else goto bad; /* Not a link interface ? Funny ... */ if ((ifp->if_flags & IFF_SIMPLEX) && (*edst & 1) && (mcopy = m_copy(m, 0, (int)M_COPYALL))) { M_PREPEND(mcopy, sizeof (*eh), M_DONTWAIT); if (mcopy) { eh = mtod(mcopy, struct ether_header *); bcopy((caddr_t)edst, (caddr_t)eh->ether_dhost, sizeof (edst)); bcopy((caddr_t)ac->ac_enaddr, (caddr_t)eh->ether_shost, sizeof (edst)); } } type = htons(m->m_pkthdr.len); #ifdef LLC_DEBUG { int i; register struct llc *l = mtod(m, struct llc *); printf("ether_output: sending LLC2 pkt to: "); for (i=0; i<6; i++) printf("%x ", edst[i] & 0xff); printf(" len 0x%x dsap 0x%x ssap 0x%x control 0x%x\n", type & 0xff, l->llc_dsap & 0xff, l->llc_ssap &0xff, l->llc_control & 0xff); } #endif /* LLC_DEBUG */ } break; #endif /* LLC */ case AF_UNSPEC: eh = (struct ether_header *)dst->sa_data; (void)memcpy(edst, eh->ether_dhost, sizeof (edst)); type = eh->ether_type; break; default: printf("%s%d: can't handle af%d\n", ifp->if_name, ifp->if_unit, dst->sa_family); senderr(EAFNOSUPPORT); } if (mcopy) (void) looutput(ifp, mcopy, dst, rt); /* * Add local net header. If no space in first mbuf, * allocate another. */ M_PREPEND(m, sizeof (struct ether_header), M_DONTWAIT); if (m == 0) senderr(ENOBUFS); eh = mtod(m, struct ether_header *); (void)memcpy(&eh->ether_type, &type, sizeof(eh->ether_type)); (void)memcpy(eh->ether_dhost, edst, sizeof (edst)); (void)memcpy(eh->ether_shost, ac->ac_enaddr, sizeof(eh->ether_shost)); s = splimp(); /* * Queue message on interface, and start output if interface * not yet active. */ if (IF_QFULL(&ifp->if_snd)) { IF_DROP(&ifp->if_snd); splx(s); senderr(ENOBUFS); } IF_ENQUEUE(&ifp->if_snd, m); if ((ifp->if_flags & IFF_OACTIVE) == 0) (*ifp->if_start)(ifp); splx(s); ifp->if_obytes += len + sizeof (struct ether_header); if (m->m_flags & M_MCAST) ifp->if_omcasts++; return (error); bad: if (m) m_freem(m); return (error); } /* * Process a received Ethernet packet; * the packet is in the mbuf chain m without * the ether header, which is provided separately. */ void ether_input(ifp, eh, m) struct ifnet *ifp; register struct ether_header *eh; struct mbuf *m; { register struct ifqueue *inq; u_short ether_type, *checksum; int s; #if defined (ISO) || defined (LLC) || defined(NETATALK) register struct llc *l; #endif if ((ifp->if_flags & IFF_UP) == 0) { m_freem(m); return; } ifp->if_ibytes += m->m_pkthdr.len + sizeof (*eh); if (bcmp((caddr_t)etherbroadcastaddr, (caddr_t)eh->ether_dhost, sizeof(etherbroadcastaddr)) == 0) m->m_flags |= M_BCAST; else if (eh->ether_dhost[0] & 1) m->m_flags |= M_MCAST; if (m->m_flags & (M_BCAST|M_MCAST)) ifp->if_imcasts++; ether_type = ntohs(eh->ether_type); switch (ether_type) { #ifdef INET case ETHERTYPE_IP: schednetisr(NETISR_IP); inq = &ipintrq; break; case ETHERTYPE_ARP: schednetisr(NETISR_ARP); inq = &arpintrq; break; #endif #ifdef IPX case ETHERTYPE_IPX: schednetisr(NETISR_IPX); inq = &ipxintrq; break; #endif #ifdef NS case 0x8137: /* Novell Ethernet_II Ethernet TYPE II */ schednetisr(NETISR_NS); inq = &nsintrq; break; #endif /* NS */ #ifdef NETATALK case ETHERTYPE_AT: schednetisr(NETISR_ATALK); inq = &atintrq1; break; case ETHERTYPE_AARP: /* probably this should be done with a NETISR as well */ aarpinput((struct arpcom *)ifp, m); /* XXX */ return; #endif NETATALK default: #ifdef NS checksum = mtod(m, ushort *); /* Novell 802.3 */ if ((ether_type <= ETHERMTU) && ((*checksum == 0xffff) || (*checksum == 0xE0E0))){ if(*checksum == 0xE0E0) { m->m_pkthdr.len -= 3; m->m_len -= 3; m->m_data += 3; } schednetisr(NETISR_NS); inq = &nsintrq; break; } #endif /* NS */ #if defined (ISO) || defined (LLC) || defined(NETATALK) if (ether_type > ETHERMTU) goto dropanyway; l = mtod(m, struct llc *); switch (l->llc_dsap) { #ifdef NETATALK case LLC_SNAP_LSAP: switch (l->llc_control) { case LLC_UI: if (l->llc_ssap != LLC_SNAP_LSAP) goto dropanyway; if (Bcmp(&(l->llc_snap_org_code)[0], at_org_code, sizeof(at_org_code)) == 0 && ntohs(l->llc_snap_ether_type) == ETHERTYPE_AT) { inq = &atintrq2; m_adj( m, sizeof( struct llc )); schednetisr(NETISR_ATALK); break; } if (Bcmp(&(l->llc_snap_org_code)[0], aarp_org_code, sizeof(aarp_org_code)) == 0 && ntohs(l->llc_snap_ether_type) == ETHERTYPE_AARP) { m_adj( m, sizeof( struct llc )); aarpinput((struct arpcom *)ifp, m); /* XXX */ return; } default: goto dropanyway; } break; #endif NETATALK #ifdef ISO case LLC_ISO_LSAP: switch (l->llc_control) { case LLC_UI: /* LLC_UI_P forbidden in class 1 service */ if ((l->llc_dsap == LLC_ISO_LSAP) && (l->llc_ssap == LLC_ISO_LSAP)) { /* LSAP for ISO */ if (m->m_pkthdr.len > ether_type) m_adj(m, ether_type - m->m_pkthdr.len); m->m_data += 3; /* XXX */ m->m_len -= 3; /* XXX */ m->m_pkthdr.len -= 3; /* XXX */ M_PREPEND(m, sizeof *eh, M_DONTWAIT); if (m == 0) return; *mtod(m, struct ether_header *) = *eh; IFDEBUG(D_ETHER) printf("clnp packet"); ENDDEBUG schednetisr(NETISR_ISO); inq = &clnlintrq; break; } goto dropanyway; case LLC_XID: case LLC_XID_P: if(m->m_len < 6) goto dropanyway; l->llc_window = 0; l->llc_fid = 9; l->llc_class = 1; l->llc_dsap = l->llc_ssap = 0; /* Fall through to */ case LLC_TEST: case LLC_TEST_P: { struct sockaddr sa; register struct ether_header *eh2; int i; u_char c = l->llc_dsap; l->llc_dsap = l->llc_ssap; l->llc_ssap = c; if (m->m_flags & (M_BCAST | M_MCAST)) bcopy((caddr_t)ac->ac_enaddr, (caddr_t)eh->ether_dhost, 6); sa.sa_family = AF_UNSPEC; sa.sa_len = sizeof(sa); eh2 = (struct ether_header *)sa.sa_data; for (i = 0; i < 6; i++) { eh2->ether_shost[i] = c = eh->ether_dhost[i]; eh2->ether_dhost[i] = eh->ether_dhost[i] = eh->ether_shost[i]; eh->ether_shost[i] = c; } ifp->if_output(ifp, m, &sa, NULL); return; } default: m_freem(m); return; } break; #endif /* ISO */ #ifdef LLC case LLC_X25_LSAP: { if (m->m_pkthdr.len > ether_type) m_adj(m, ether_type - m->m_pkthdr.len); M_PREPEND(m, sizeof(struct sdl_hdr) , M_DONTWAIT); if (m == 0) return; if ( !sdl_sethdrif(ifp, eh->ether_shost, LLC_X25_LSAP, eh->ether_dhost, LLC_X25_LSAP, 6, mtod(m, struct sdl_hdr *))) panic("ETHER cons addr failure"); mtod(m, struct sdl_hdr *)->sdlhdr_len = ether_type; #ifdef LLC_DEBUG printf("llc packet\n"); #endif /* LLC_DEBUG */ schednetisr(NETISR_CCITT); inq = &llcintrq; break; } #endif /* LLC */ dropanyway: default: m_freem(m); return; } #else /* ISO || LLC || NETATALK */ m_freem(m); return; #endif /* ISO || LLC || NETATALK */ } s = splimp(); if (IF_QFULL(inq)) { IF_DROP(inq); m_freem(m); } else IF_ENQUEUE(inq, m); splx(s); } /* * Perform common duties while attaching to interface list */ void ether_ifattach(ifp) register struct ifnet *ifp; { register struct ifaddr *ifa; register struct sockaddr_dl *sdl; ifp->if_type = IFT_ETHER; ifp->if_addrlen = 6; ifp->if_hdrlen = 14; ifp->if_mtu = ETHERMTU; if (ifp->if_baudrate == 0) ifp->if_baudrate = 10000000; - for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) - if ((sdl = (struct sockaddr_dl *)ifa->ifa_addr) && - sdl->sdl_family == AF_LINK) { - sdl->sdl_type = IFT_ETHER; - sdl->sdl_alen = ifp->if_addrlen; - bcopy((caddr_t)((struct arpcom *)ifp)->ac_enaddr, - LLADDR(sdl), ifp->if_addrlen); - break; - } + ifa = ifnet_addrs[ifp->if_index - 1]; + if (ifa == 0) { + printf("ether_ifattach: no lladdr!\n"); + return; + } + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + sdl->sdl_type = IFT_ETHER; + sdl->sdl_alen = ifp->if_addrlen; + bcopy(((struct arpcom *)ifp)->ac_enaddr, LLADDR(sdl), ifp->if_addrlen); } static u_char ether_ipmulticast_min[6] = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 }; static u_char ether_ipmulticast_max[6] = { 0x01, 0x00, 0x5e, 0x7f, 0xff, 0xff }; /* * Add an Ethernet multicast address or range of addresses to the list for a * given interface. */ int ether_addmulti(ifr, ac) struct ifreq *ifr; register struct arpcom *ac; { register struct ether_multi *enm; struct sockaddr_in *sin; u_char addrlo[6]; u_char addrhi[6]; int set_allmulti = 0; int s = splimp(); switch (ifr->ifr_addr.sa_family) { case AF_UNSPEC: bcopy(ifr->ifr_addr.sa_data, addrlo, 6); bcopy(addrlo, addrhi, 6); break; #ifdef INET case AF_INET: sin = (struct sockaddr_in *)&(ifr->ifr_addr); if (sin->sin_addr.s_addr == INADDR_ANY) { /* * An IP address of INADDR_ANY means listen to all * of the Ethernet multicast addresses used for IP. * (This is for the sake of IP multicast routers.) */ bcopy(ether_ipmulticast_min, addrlo, 6); bcopy(ether_ipmulticast_max, addrhi, 6); set_allmulti = 1; } else { ETHER_MAP_IP_MULTICAST(&sin->sin_addr, addrlo); bcopy(addrlo, addrhi, 6); } break; #endif default: splx(s); return (EAFNOSUPPORT); } /* * Verify that we have valid Ethernet multicast addresses. */ if ((addrlo[0] & 0x01) != 1 || (addrhi[0] & 0x01) != 1) { splx(s); return (EINVAL); } /* * See if the address range is already in the list. */ ETHER_LOOKUP_MULTI(addrlo, addrhi, ac, enm); if (enm != NULL) { /* * Found it; just increment the reference count. */ ++enm->enm_refcount; splx(s); return (0); } /* * New address or range; malloc a new multicast record * and link it into the interface's multicast list. */ enm = (struct ether_multi *)malloc(sizeof(*enm), M_IFMADDR, M_NOWAIT); if (enm == NULL) { splx(s); return (ENOBUFS); } bcopy(addrlo, enm->enm_addrlo, 6); bcopy(addrhi, enm->enm_addrhi, 6); enm->enm_ac = ac; enm->enm_refcount = 1; enm->enm_next = ac->ac_multiaddrs; ac->ac_multiaddrs = enm; ac->ac_multicnt++; splx(s); if (set_allmulti) ac->ac_if.if_flags |= IFF_ALLMULTI; /* * Return ENETRESET to inform the driver that the list has changed * and its reception filter should be adjusted accordingly. */ return (ENETRESET); } /* * Delete a multicast address record. */ int ether_delmulti(ifr, ac) struct ifreq *ifr; register struct arpcom *ac; { register struct ether_multi *enm; register struct ether_multi **p; struct sockaddr_in *sin; u_char addrlo[6]; u_char addrhi[6]; int unset_allmulti = 0; int s = splimp(); switch (ifr->ifr_addr.sa_family) { case AF_UNSPEC: bcopy(ifr->ifr_addr.sa_data, addrlo, 6); bcopy(addrlo, addrhi, 6); break; #ifdef INET case AF_INET: sin = (struct sockaddr_in *)&(ifr->ifr_addr); if (sin->sin_addr.s_addr == INADDR_ANY) { /* * An IP address of INADDR_ANY means stop listening * to the range of Ethernet multicast addresses used * for IP. */ bcopy(ether_ipmulticast_min, addrlo, 6); bcopy(ether_ipmulticast_max, addrhi, 6); unset_allmulti = 1; } else { ETHER_MAP_IP_MULTICAST(&sin->sin_addr, addrlo); bcopy(addrlo, addrhi, 6); } break; #endif default: splx(s); return (EAFNOSUPPORT); } /* * Look up the address in our list. */ ETHER_LOOKUP_MULTI(addrlo, addrhi, ac, enm); if (enm == NULL) { splx(s); return (ENXIO); } if (--enm->enm_refcount != 0) { /* * Still some claims to this record. */ splx(s); return (0); } /* * No remaining claims to this record; unlink and free it. */ for (p = &enm->enm_ac->ac_multiaddrs; *p != enm; p = &(*p)->enm_next) continue; *p = (*p)->enm_next; free(enm, M_IFMADDR); ac->ac_multicnt--; splx(s); if (unset_allmulti) ac->ac_if.if_flags &= ~IFF_ALLMULTI; /* * Return ENETRESET to inform the driver that the list has changed * and its reception filter should be adjusted accordingly. */ return (ENETRESET); } SYSCTL_NODE(_net_link, IFT_ETHER, ether, CTLFLAG_RW, 0, "Ethernet"); int ether_ioctl(struct ifnet *ifp, int command, caddr_t data) { struct ifaddr *ifa = (struct ifaddr *) data; struct ifreq *ifr = (struct ifreq *) data; int error = 0; switch (command) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: ifp->if_init(ifp->if_softc); /* before arpwhohas */ arp_ifinit((struct arpcom *)ifp, ifa); break; #endif #ifdef IPX /* * XXX - This code is probably wrong */ case AF_IPX: { register struct ipx_addr *ina = &(IA_SIPX(ifa)->sipx_addr); struct arpcom *ac = (struct arpcom *) (ifp->if_softc); if (ipx_nullhost(*ina)) ina->x_host = *(union ipx_host *) ac->ac_enaddr; else { bcopy((caddr_t) ina->x_host.c_host, (caddr_t) ac->ac_enaddr, sizeof(ac->ac_enaddr)); } /* * Set new address */ ifp->if_init(ifp->if_softc); break; } #endif #ifdef NS /* * XXX - This code is probably wrong */ case AF_NS: { register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); struct arpcom *ac = (struct arpcom *) (ifp->if_softc); if (ns_nullhost(*ina)) ina->x_host = *(union ns_host *) (ac->ac_enaddr); else { bcopy((caddr_t) ina->x_host.c_host, (caddr_t) ac->ac_enaddr, sizeof(ac->ac_enaddr)); } /* * Set new address */ ifp->if_init(ifp->if_softc); break; } #endif default: ifp->if_init(ifp->if_softc); break; } break; case SIOCGIFADDR: { struct sockaddr *sa; sa = (struct sockaddr *) & ifr->ifr_data; bcopy(((struct arpcom *)ifp->if_softc)->ac_enaddr, (caddr_t) sa->sa_data, ETHER_ADDR_LEN); } break; case SIOCSIFMTU: /* * Set the interface MTU. */ if (ifr->ifr_mtu > ETHERMTU) { error = EINVAL; } else { ifp->if_mtu = ifr->ifr_mtu; } break; } return (error); } diff --git a/sys/net/if_fddisubr.c b/sys/net/if_fddisubr.c index 1d13de2c9244..f910e567f090 100644 --- a/sys/net/if_fddisubr.c +++ b/sys/net/if_fddisubr.c @@ -1,579 +1,575 @@ /* * Copyright (c) 1982, 1989, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: if_ethersubr.c,v 1.5 1994/12/13 22:31:45 wollman Exp - * $Id: if_fddisubr.c,v 1.9 1996/06/10 23:07:31 gpalmer Exp $ + * $Id: if_fddisubr.c,v 1.10 1996/11/12 08:43:32 davidg Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #endif #include #include #ifdef IPX #include #include #endif #ifdef NS #include #include #endif #ifdef DECNET #include #endif #ifdef ISO #include #include #include #include #endif #include "bpfilter.h" #ifdef LLC #include #include #endif #if defined(LLC) && defined(CCITT) extern struct ifqueue pkintrq; #endif #define senderr(e) { error = (e); goto bad;} /* * This really should be defined in if_llc.h but in case it isn't. */ #ifndef llc_snap #define llc_snap llc_un.type_snap #endif #ifdef __bsdi__ #define RTALLOC1(a, b) rtalloc1(a, b) #define ARPRESOLVE(a, b, c, d, e, f) arpresolve(a, b, c, d, e) #else #define RTALLOC1(a, b) rtalloc1(a, b, 0UL) #define ARPRESOLVE(a, b, c, d, e, f) arpresolve(a, b, c, d, e, f) #endif /* * FDDI output routine. * Encapsulate a packet of type family for the local net. * Use trailer local net encapsulation if enough data in first * packet leaves a multiple of 512 bytes of data in remainder. * Assumes that ifp is actually pointer to arpcom structure. */ int fddi_output(ifp, m0, dst, rt0) register struct ifnet *ifp; struct mbuf *m0; struct sockaddr *dst; struct rtentry *rt0; { short type; int s, error = 0; u_char edst[6]; register struct mbuf *m = m0; register struct rtentry *rt; struct mbuf *mcopy = (struct mbuf *)0; register struct fddi_header *fh; struct arpcom *ac = (struct arpcom *)ifp; if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) senderr(ENETDOWN); if (rt = rt0) { if ((rt->rt_flags & RTF_UP) == 0) { if (rt0 = rt = RTALLOC1(dst, 1)) rt->rt_refcnt--; else senderr(EHOSTUNREACH); } if (rt->rt_flags & RTF_GATEWAY) { if (rt->rt_gwroute == 0) goto lookup; if (((rt = rt->rt_gwroute)->rt_flags & RTF_UP) == 0) { rtfree(rt); rt = rt0; lookup: rt->rt_gwroute = RTALLOC1(rt->rt_gateway, 1); if ((rt = rt->rt_gwroute) == 0) senderr(EHOSTUNREACH); } } if (rt->rt_flags & RTF_REJECT) if (rt->rt_rmx.rmx_expire == 0 || time.tv_sec < rt->rt_rmx.rmx_expire) senderr(rt == rt0 ? EHOSTDOWN : EHOSTUNREACH); } switch (dst->sa_family) { #ifdef INET case AF_INET: if (!ARPRESOLVE(ac, rt, m, dst, edst, rt0)) return (0); /* if not yet resolved */ /* If broadcasting on a simplex interface, loopback a copy */ if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX)) mcopy = m_copy(m, 0, (int)M_COPYALL); type = ETHERTYPE_IP; break; #endif #ifdef IPX case AF_IPX: type = ETHERTYPE_IPX; bcopy((caddr_t)&(((struct sockaddr_ipx *)dst)->sipx_addr.x_host), (caddr_t)edst, sizeof (edst)); if (!bcmp((caddr_t)edst, (caddr_t)&ipx_thishost, sizeof(edst))) return (looutput(ifp, m, dst, rt)); /* If broadcasting on a simplex interface, loopback a copy */ if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX)) mcopy = m_copy(m, 0, (int)M_COPYALL); break; #endif #ifdef NS case AF_NS: type = ETHERTYPE_NS; bcopy((caddr_t)&(((struct sockaddr_ns *)dst)->sns_addr.x_host), (caddr_t)edst, sizeof (edst)); if (!bcmp((caddr_t)edst, (caddr_t)&ns_thishost, sizeof(edst))) return (looutput(ifp, m, dst, rt)); /* If broadcasting on a simplex interface, loopback a copy */ if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX)) mcopy = m_copy(m, 0, (int)M_COPYALL); break; #endif #ifdef ISO case AF_ISO: { int snpalen; struct llc *l; register struct sockaddr_dl *sdl; if (rt && (sdl = (struct sockaddr_dl *)rt->rt_gateway) && sdl->sdl_family == AF_LINK && sdl->sdl_alen > 0) { bcopy(LLADDR(sdl), (caddr_t)edst, sizeof(edst)); } else if (error = iso_snparesolve(ifp, (struct sockaddr_iso *)dst, (char *)edst, &snpalen)) goto bad; /* Not Resolved */ /* If broadcasting on a simplex interface, loopback a copy */ if (*edst & 1) m->m_flags |= (M_BCAST|M_MCAST); if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX) && (mcopy = m_copy(m, 0, (int)M_COPYALL))) { M_PREPEND(mcopy, sizeof (*fh), M_DONTWAIT); if (mcopy) { fh = mtod(mcopy, struct fddi_header *); bcopy((caddr_t)edst, (caddr_t)fh->fddi_dhost, sizeof (edst)); bcopy((caddr_t)ac->ac_enaddr, (caddr_t)fh->fddi_shost, sizeof (edst)); } } M_PREPEND(m, 3, M_DONTWAIT); if (m == NULL) return (0); type = 0; l = mtod(m, struct llc *); l->llc_dsap = l->llc_ssap = LLC_ISO_LSAP; l->llc_control = LLC_UI; IFDEBUG(D_ETHER) int i; printf("unoutput: sending pkt to: "); for (i=0; i<6; i++) printf("%x ", edst[i] & 0xff); printf("\n"); ENDDEBUG } break; #endif /* ISO */ #ifdef LLC /* case AF_NSAP: */ case AF_CCITT: { register struct sockaddr_dl *sdl = (struct sockaddr_dl *) rt -> rt_gateway; if (sdl && sdl->sdl_family == AF_LINK && sdl->sdl_alen > 0) { bcopy(LLADDR(sdl), (char *)edst, sizeof(edst)); } else goto bad; /* Not a link interface ? Funny ... */ if ((ifp->if_flags & IFF_SIMPLEX) && (*edst & 1) && (mcopy = m_copy(m, 0, (int)M_COPYALL))) { M_PREPEND(mcopy, sizeof (*fh), M_DONTWAIT); if (mcopy) { fh = mtod(mcopy, struct fddi_header *); bcopy((caddr_t)edst, (caddr_t)fh->fddi_dhost, sizeof (edst)); bcopy((caddr_t)ac->ac_enaddr, (caddr_t)fh->fddi_shost, sizeof (edst)); fh->fddi_fc = FDDIFC_LLC_ASYNC|FDDIFC_LLC_PRIO4; } } type = 0; #ifdef LLC_DEBUG { int i; register struct llc *l = mtod(m, struct llc *); printf("fddi_output: sending LLC2 pkt to: "); for (i=0; i<6; i++) printf("%x ", edst[i] & 0xff); printf(" len 0x%x dsap 0x%x ssap 0x%x control 0x%x\n", type & 0xff, l->llc_dsap & 0xff, l->llc_ssap &0xff, l->llc_control & 0xff); } #endif /* LLC_DEBUG */ } break; #endif /* LLC */ case AF_UNSPEC: { struct ether_header *eh; eh = (struct ether_header *)dst->sa_data; (void)memcpy((caddr_t)edst, (caddr_t)eh->ether_dhost, sizeof (edst)); if (*edst & 1) m->m_flags |= (M_BCAST|M_MCAST); type = eh->ether_type; break; } #if NBPFILTER > 0 case AF_IMPLINK: { fh = mtod(m, struct fddi_header *); error = EPROTONOSUPPORT; switch (fh->fddi_fc & (FDDIFC_C|FDDIFC_L|FDDIFC_F)) { case FDDIFC_LLC_ASYNC: { /* legal priorities are 0 through 7 */ if ((fh->fddi_fc & FDDIFC_Z) > 7) goto bad; break; } case FDDIFC_LLC_SYNC: { /* FDDIFC_Z bits reserved, must be zero */ if (fh->fddi_fc & FDDIFC_Z) goto bad; break; } case FDDIFC_SMT: { /* FDDIFC_Z bits must be non zero */ if ((fh->fddi_fc & FDDIFC_Z) == 0) goto bad; break; } default: { /* anything else is too dangerous */ goto bad; } } error = 0; if (fh->fddi_dhost[0] & 1) m->m_flags |= (M_BCAST|M_MCAST); goto queue_it; } #endif default: printf("%s%d: can't handle af%d\n", ifp->if_name, ifp->if_unit, dst->sa_family); senderr(EAFNOSUPPORT); } if (mcopy) (void) looutput(ifp, mcopy, dst, rt); if (type != 0) { register struct llc *l; M_PREPEND(m, sizeof (struct llc), M_DONTWAIT); if (m == 0) senderr(ENOBUFS); l = mtod(m, struct llc *); l->llc_control = LLC_UI; l->llc_dsap = l->llc_ssap = LLC_SNAP_LSAP; l->llc_snap.org_code[0] = l->llc_snap.org_code[1] = l->llc_snap.org_code[2] = 0; type = ntohs(type); (void)memcpy((caddr_t) &l->llc_snap.ether_type, (caddr_t) &type, sizeof(u_short)); } /* * Add local net header. If no space in first mbuf, * allocate another. */ M_PREPEND(m, sizeof (struct fddi_header), M_DONTWAIT); if (m == 0) senderr(ENOBUFS); fh = mtod(m, struct fddi_header *); fh->fddi_fc = FDDIFC_LLC_ASYNC|FDDIFC_LLC_PRIO4; (void)memcpy((caddr_t)fh->fddi_dhost, (caddr_t)edst, sizeof (edst)); queue_it: (void)memcpy((caddr_t)fh->fddi_shost, (caddr_t)ac->ac_enaddr, sizeof(fh->fddi_shost)); s = splimp(); /* * Queue message on interface, and start output if interface * not yet active. */ if (IF_QFULL(&ifp->if_snd)) { IF_DROP(&ifp->if_snd); splx(s); senderr(ENOBUFS); } ifp->if_obytes += m->m_pkthdr.len; IF_ENQUEUE(&ifp->if_snd, m); if ((ifp->if_flags & IFF_OACTIVE) == 0) (*ifp->if_start)(ifp); splx(s); if (m->m_flags & M_MCAST) ifp->if_omcasts++; return (error); bad: if (m) m_freem(m); return (error); } /* * Process a received FDDI packet; * the packet is in the mbuf chain m without * the fddi header, which is provided separately. */ void fddi_input(ifp, fh, m) struct ifnet *ifp; register struct fddi_header *fh; struct mbuf *m; { register struct ifqueue *inq; register struct llc *l; int s; if ((ifp->if_flags & IFF_UP) == 0) { m_freem(m); return; } ifp->if_ibytes += m->m_pkthdr.len + sizeof (*fh); if (bcmp((caddr_t)fddibroadcastaddr, (caddr_t)fh->fddi_dhost, sizeof(fddibroadcastaddr)) == 0) m->m_flags |= M_BCAST; else if (fh->fddi_dhost[0] & 1) m->m_flags |= M_MCAST; if (m->m_flags & (M_BCAST|M_MCAST)) ifp->if_imcasts++; l = mtod(m, struct llc *); switch (l->llc_dsap) { #if defined(INET) || defined(NS) || defined(DECNET) case LLC_SNAP_LSAP: { unsigned fddi_type; if (l->llc_control != LLC_UI || l->llc_ssap != LLC_SNAP_LSAP) goto dropanyway; if (l->llc_snap.org_code[0] != 0 || l->llc_snap.org_code[1] != 0|| l->llc_snap.org_code[2] != 0) goto dropanyway; fddi_type = ntohs(l->llc_snap.ether_type); m_adj(m, 8); switch (fddi_type) { #ifdef INET case ETHERTYPE_IP: schednetisr(NETISR_IP); inq = &ipintrq; break; case ETHERTYPE_ARP: schednetisr(NETISR_ARP); inq = &arpintrq; break; #endif #ifdef NS case ETHERTYPE_NS: schednetisr(NETISR_NS); inq = &nsintrq; break; #endif #ifdef DECNET case ETHERTYPE_DECENT: schednetisr(NETISR_DECNET); inq = &decnetintrq; break; #endif default: ifp->if_noproto++; goto dropanyway; } break; } #endif /* INET || NS */ #ifdef ISO case LLC_ISO_LSAP: switch (l->llc_control) { case LLC_UI: /* LLC_UI_P forbidden in class 1 service */ if ((l->llc_dsap == LLC_ISO_LSAP) && (l->llc_ssap == LLC_ISO_LSAP)) { /* LSAP for ISO */ m->m_data += 3; /* XXX */ m->m_len -= 3; /* XXX */ m->m_pkthdr.len -= 3; /* XXX */ M_PREPEND(m, sizeof *fh, M_DONTWAIT); if (m == 0) return; *mtod(m, struct fddi_header *) = *fh; IFDEBUG(D_ETHER) printf("clnp packet"); ENDDEBUG schednetisr(NETISR_ISO); inq = &clnlintrq; break; } goto dropanyway; case LLC_XID: case LLC_XID_P: if(m->m_len < 6) goto dropanyway; l->llc_window = 0; l->llc_fid = 9; l->llc_class = 1; l->llc_dsap = l->llc_ssap = 0; /* Fall through to */ case LLC_TEST: case LLC_TEST_P: { struct sockaddr sa; register struct ether_header *eh2; int i; u_char c = l->llc_dsap; l->llc_dsap = l->llc_ssap; l->llc_ssap = c; if (m->m_flags & (M_BCAST | M_MCAST)) bcopy((caddr_t)ac->ac_enaddr, (caddr_t)eh->ether_dhost, 6); sa.sa_family = AF_UNSPEC; sa.sa_len = sizeof(sa); eh2 = (struct ether_header *)sa.sa_data; for (i = 0; i < 6; i++) { eh2->ether_shost[i] = c = eh->fddi_dhost[i]; eh2->ether_dhost[i] = eh->ether_dhost[i] = eh->fddi_shost[i]; eh2->ether_shost[i] = c; } eh2->ether_type = 0; ifp->if_output(ifp, m, &sa, NULL); return; } default: m_freem(m); return; } break; #endif /* ISO */ #ifdef LLC case LLC_X25_LSAP: { M_PREPEND(m, sizeof(struct sdl_hdr) , M_DONTWAIT); if (m == 0) return; if ( !sdl_sethdrif(ifp, fh->fddi_shost, LLC_X25_LSAP, fh->fddi_dhost, LLC_X25_LSAP, 6, mtod(m, struct sdl_hdr *))) panic("ETHER cons addr failure"); mtod(m, struct sdl_hdr *)->sdlhdr_len = m->m_pkthdr.len - sizeof(struct sdl_hdr); #ifdef LLC_DEBUG printf("llc packet\n"); #endif /* LLC_DEBUG */ schednetisr(NETISR_CCITT); inq = &llcintrq; break; } #endif /* LLC */ default: printf("fddi_input: unknown dsap 0x%x\n", l->llc_dsap); ifp->if_noproto++; dropanyway: m_freem(m); return; } s = splimp(); if (IF_QFULL(inq)) { IF_DROP(inq); m_freem(m); } else IF_ENQUEUE(inq, m); splx(s); } /* * Perform common duties while attaching to interface list */ void fddi_ifattach(ifp) register struct ifnet *ifp; { register struct ifaddr *ifa; register struct sockaddr_dl *sdl; ifp->if_type = IFT_FDDI; ifp->if_addrlen = 6; ifp->if_hdrlen = 21; ifp->if_mtu = FDDIMTU; ifp->if_baudrate = 100000000; - for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) - if ((sdl = (struct sockaddr_dl *)ifa->ifa_addr) && - sdl->sdl_family == AF_LINK) { - sdl->sdl_type = IFT_FDDI; - sdl->sdl_alen = ifp->if_addrlen; - bcopy((caddr_t)((struct arpcom *)ifp)->ac_enaddr, - LLADDR(sdl), ifp->if_addrlen); - break; - } + ifa = ifnet_addrs[ifp->if_index - 1]; + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + sdl->sdl_type = IFT_FDDI; + sdl->sdl_alen = ifp->if_addrlen; + bcopy(((struct arpcom *)ifp)->ac_enaddr, LLADDR(sdl), ifp->if_addrlen); } diff --git a/sys/net/if_spppsubr.c b/sys/net/if_spppsubr.c index e295cbd875da..882080090f88 100644 --- a/sys/net/if_spppsubr.c +++ b/sys/net/if_spppsubr.c @@ -1,1372 +1,1373 @@ /* * Synchronous PPP/Cisco link level subroutines. * Keepalive protocol implemented in both Cisco and PPP modes. * * Copyright (C) 1994 Cronyx Ltd. * Author: Serge Vakulenko, * * This software is distributed with NO WARRANTIES, not even the implied * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * Authors grant any other persons or organisations permission to use * or modify this software as long as this message is kept with the software, * all derivative works or modified versions. * * Version 1.9, Wed Oct 4 18:58:15 MSK 1995 * - * $Id: if_spppsubr.c,v 1.12 1996/06/10 23:17:45 gpalmer Exp $ + * $Id: if_spppsubr.c,v 1.13 1996/08/30 16:44:36 jhay Exp $ */ #undef DEBUG #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #include #endif #ifdef IPX #include #include #endif #ifdef NS #include #include #endif #ifdef ISO #include #include #include #include #endif #include #ifdef DEBUG #define print(s) printf s #else #define print(s) {/*void*/} #endif #define MAXALIVECNT 3 /* max. alive packets */ #define PPP_ALLSTATIONS 0xff /* All-Stations broadcast address */ #define PPP_UI 0x03 /* Unnumbered Information */ #define PPP_IP 0x0021 /* Internet Protocol */ #define PPP_ISO 0x0023 /* ISO OSI Protocol */ #define PPP_XNS 0x0025 /* Xerox NS Protocol */ #define PPP_IPX 0x002b /* Novell IPX Protocol */ #define PPP_LCP 0xc021 /* Link Control Protocol */ #define PPP_IPCP 0x8021 /* Internet Protocol Control Protocol */ #define LCP_CONF_REQ 1 /* PPP LCP configure request */ #define LCP_CONF_ACK 2 /* PPP LCP configure acknowledge */ #define LCP_CONF_NAK 3 /* PPP LCP configure negative ack */ #define LCP_CONF_REJ 4 /* PPP LCP configure reject */ #define LCP_TERM_REQ 5 /* PPP LCP terminate request */ #define LCP_TERM_ACK 6 /* PPP LCP terminate acknowledge */ #define LCP_CODE_REJ 7 /* PPP LCP code reject */ #define LCP_PROTO_REJ 8 /* PPP LCP protocol reject */ #define LCP_ECHO_REQ 9 /* PPP LCP echo request */ #define LCP_ECHO_REPLY 10 /* PPP LCP echo reply */ #define LCP_DISC_REQ 11 /* PPP LCP discard request */ #define LCP_OPT_MRU 1 /* maximum receive unit */ #define LCP_OPT_ASYNC_MAP 2 /* async control character map */ #define LCP_OPT_AUTH_PROTO 3 /* authentication protocol */ #define LCP_OPT_QUAL_PROTO 4 /* quality protocol */ #define LCP_OPT_MAGIC 5 /* magic number */ #define LCP_OPT_RESERVED 6 /* reserved */ #define LCP_OPT_PROTO_COMP 7 /* protocol field compression */ #define LCP_OPT_ADDR_COMP 8 /* address/control field compression */ #define IPCP_CONF_REQ LCP_CONF_REQ /* PPP IPCP configure request */ #define IPCP_CONF_ACK LCP_CONF_ACK /* PPP IPCP configure acknowledge */ #define IPCP_CONF_NAK LCP_CONF_NAK /* PPP IPCP configure negative ack */ #define IPCP_CONF_REJ LCP_CONF_REJ /* PPP IPCP configure reject */ #define IPCP_TERM_REQ LCP_TERM_REQ /* PPP IPCP terminate request */ #define IPCP_TERM_ACK LCP_TERM_ACK /* PPP IPCP terminate acknowledge */ #define IPCP_CODE_REJ LCP_CODE_REJ /* PPP IPCP code reject */ #define CISCO_MULTICAST 0x8f /* Cisco multicast address */ #define CISCO_UNICAST 0x0f /* Cisco unicast address */ #define CISCO_KEEPALIVE 0x8035 /* Cisco keepalive protocol */ #define CISCO_ADDR_REQ 0 /* Cisco address request */ #define CISCO_ADDR_REPLY 1 /* Cisco address reply */ #define CISCO_KEEPALIVE_REQ 2 /* Cisco keepalive request */ struct ppp_header { u_char address; u_char control; u_short protocol; }; #define PPP_HEADER_LEN sizeof (struct ppp_header) struct lcp_header { u_char type; u_char ident; u_short len; }; #define LCP_HEADER_LEN sizeof (struct lcp_header) struct cisco_packet { u_long type; u_long par1; u_long par2; u_short rel; u_short time0; u_short time1; }; #define CISCO_PACKET_LEN 18 static struct sppp *spppq; /* * The following disgusting hack gets around the problem that IP TOS * can't be set yet. We want to put "interactive" traffic on a high * priority queue. To decide if traffic is interactive, we check that * a) it is TCP and b) one of its ports is telnet, rlogin or ftp control. */ static u_short interactive_ports[8] = { 0, 513, 0, 0, 0, 21, 0, 23, }; #define INTERACTIVE(p) (interactive_ports[(p) & 7] == (p)) /* * Timeout routine activation macros. */ #define TIMO(p,s) if (! ((p)->pp_flags & PP_TIMO)) { \ timeout (sppp_cp_timeout, (void*) (p), (s)*hz); \ (p)->pp_flags |= PP_TIMO; } #define UNTIMO(p) if ((p)->pp_flags & PP_TIMO) { \ untimeout (sppp_cp_timeout, (void*) (p)); \ (p)->pp_flags &= ~PP_TIMO; } static void sppp_keepalive (void *dummy); static void sppp_cp_send (struct sppp *sp, u_short proto, u_char type, u_char ident, u_short len, void *data); static void sppp_cisco_send (struct sppp *sp, int type, long par1, long par2); static void sppp_lcp_input (struct sppp *sp, struct mbuf *m); static void sppp_cisco_input (struct sppp *sp, struct mbuf *m); static void sppp_ipcp_input (struct sppp *sp, struct mbuf *m); static void sppp_lcp_open (struct sppp *sp); static void sppp_ipcp_open (struct sppp *sp); static int sppp_lcp_conf_parse_options (struct sppp *sp, struct lcp_header *h, int len, u_long *magic); static void sppp_cp_timeout (void *arg); static char *sppp_lcp_type_name (u_char type); static char *sppp_ipcp_type_name (u_char type); static void sppp_print_bytes (u_char *p, u_short len); static int sppp_output (struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, struct rtentry *rt); /* * Flush interface queue. */ static void qflush (struct ifqueue *ifq) { struct mbuf *m, *n; n = ifq->ifq_head; while ((m = n)) { n = m->m_act; m_freem (m); } ifq->ifq_head = 0; ifq->ifq_tail = 0; ifq->ifq_len = 0; } /* * Process the received packet. */ void sppp_input (struct ifnet *ifp, struct mbuf *m) { struct ppp_header *h; struct sppp *sp = (struct sppp*) ifp; struct ifqueue *inq = 0; int s; if (ifp->if_flags & IFF_UP) /* Count received bytes, add FCS and one flag */ ifp->if_ibytes += m->m_pkthdr.len + 3; if (m->m_pkthdr.len <= PPP_HEADER_LEN) { /* Too small packet, drop it. */ if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: input packet is too small, %d bytes\n", ifp->if_name, ifp->if_unit, m->m_pkthdr.len); drop: ++ifp->if_iqdrops; m_freem (m); return; } /* Get PPP header. */ h = mtod (m, struct ppp_header*); m_adj (m, PPP_HEADER_LEN); switch (h->address) { default: /* Invalid PPP packet. */ invalid: if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: invalid input packet <0x%x 0x%x 0x%x>\n", ifp->if_name, ifp->if_unit, h->address, h->control, ntohs (h->protocol)); goto drop; case PPP_ALLSTATIONS: if (h->control != PPP_UI) goto invalid; if (sp->pp_flags & PP_CISCO) { if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: PPP packet in Cisco mode <0x%x 0x%x 0x%x>\n", ifp->if_name, ifp->if_unit, h->address, h->control, ntohs (h->protocol)); goto drop; } switch (ntohs (h->protocol)) { default: if (sp->lcp.state == LCP_STATE_OPENED) sppp_cp_send (sp, PPP_LCP, LCP_PROTO_REJ, ++sp->pp_seq, m->m_pkthdr.len + 2, &h->protocol); if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: invalid input protocol <0x%x 0x%x 0x%x>\n", ifp->if_name, ifp->if_unit, h->address, h->control, ntohs (h->protocol)); ++ifp->if_noproto; goto drop; case PPP_LCP: sppp_lcp_input ((struct sppp*) ifp, m); m_freem (m); return; #ifdef INET case PPP_IPCP: if (sp->lcp.state == LCP_STATE_OPENED) sppp_ipcp_input ((struct sppp*) ifp, m); m_freem (m); return; case PPP_IP: if (sp->ipcp.state == IPCP_STATE_OPENED) { schednetisr (NETISR_IP); inq = &ipintrq; } break; #endif #ifdef IPX case PPP_IPX: /* IPX IPXCP not implemented yet */ if (sp->lcp.state == LCP_STATE_OPENED) { schednetisr (NETISR_IPX); inq = &ipxintrq; } break; #endif #ifdef NS case PPP_XNS: /* XNS IDPCP not implemented yet */ if (sp->lcp.state == LCP_STATE_OPENED) { schednetisr (NETISR_NS); inq = &nsintrq; } break; #endif #ifdef ISO case PPP_ISO: /* OSI NLCP not implemented yet */ if (sp->lcp.state == LCP_STATE_OPENED) { schednetisr (NETISR_ISO); inq = &clnlintrq; } break; #endif } break; case CISCO_MULTICAST: case CISCO_UNICAST: /* Don't check the control field here (RFC 1547). */ if (! (sp->pp_flags & PP_CISCO)) { if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: Cisco packet in PPP mode <0x%x 0x%x 0x%x>\n", ifp->if_name, ifp->if_unit, h->address, h->control, ntohs (h->protocol)); goto drop; } switch (ntohs (h->protocol)) { default: ++ifp->if_noproto; goto invalid; case CISCO_KEEPALIVE: sppp_cisco_input ((struct sppp*) ifp, m); m_freem (m); return; #ifdef INET case ETHERTYPE_IP: schednetisr (NETISR_IP); inq = &ipintrq; break; #endif #ifdef IPX case ETHERTYPE_IPX: schednetisr (NETISR_IPX); inq = &ipxintrq; break; #endif #ifdef NS case ETHERTYPE_NS: schednetisr (NETISR_NS); inq = &nsintrq; break; #endif } break; } if (! (ifp->if_flags & IFF_UP) || ! inq) goto drop; /* Check queue. */ s = splimp (); if (IF_QFULL (inq)) { /* Queue overflow. */ IF_DROP (inq); splx (s); if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: protocol queue overflow\n", ifp->if_name, ifp->if_unit); goto drop; } IF_ENQUEUE (inq, m); splx (s); } /* * Enqueue transmit packet. */ static int sppp_output (struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, struct rtentry *rt) { struct sppp *sp = (struct sppp*) ifp; struct ppp_header *h; struct ifqueue *ifq; int s = splimp (); if (! (ifp->if_flags & IFF_UP) || ! (ifp->if_flags & IFF_RUNNING)) { m_freem (m); splx (s); return (ENETDOWN); } ifq = &ifp->if_snd; #ifdef INET /* * Put low delay, telnet, rlogin and ftp control packets * in front of the queue. */ if (dst->sa_family == AF_INET) { struct ip *ip = mtod (m, struct ip*); struct tcphdr *tcp = (struct tcphdr*) ((long*)ip + ip->ip_hl); if (! IF_QFULL (&sp->pp_fastq) && ((ip->ip_tos & IPTOS_LOWDELAY) || ip->ip_p == IPPROTO_TCP && m->m_len >= sizeof (struct ip) + sizeof (struct tcphdr) && (INTERACTIVE (ntohs (tcp->th_sport)) || INTERACTIVE (ntohs (tcp->th_dport))))) ifq = &sp->pp_fastq; } #endif /* * Prepend general data packet PPP header. For now, IP only. */ M_PREPEND (m, PPP_HEADER_LEN, M_DONTWAIT); if (! m) { if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: no memory for transmit header\n", ifp->if_name, ifp->if_unit); splx (s); return (ENOBUFS); } h = mtod (m, struct ppp_header*); if (sp->pp_flags & PP_CISCO) { h->address = CISCO_MULTICAST; /* broadcast address */ h->control = 0; } else { h->address = PPP_ALLSTATIONS; /* broadcast address */ h->control = PPP_UI; /* Unnumbered Info */ } switch (dst->sa_family) { #ifdef INET case AF_INET: /* Internet Protocol */ if (sp->pp_flags & PP_CISCO) h->protocol = htons (ETHERTYPE_IP); else if (sp->ipcp.state == IPCP_STATE_OPENED) h->protocol = htons (PPP_IP); else { m_freem (m); splx (s); return (ENETDOWN); } break; #endif #ifdef NS case AF_NS: /* Xerox NS Protocol */ h->protocol = htons ((sp->pp_flags & PP_CISCO) ? ETHERTYPE_NS : PPP_XNS); break; #endif #ifdef IPX case AF_IPX: /* Novell IPX Protocol */ h->protocol = htons ((sp->pp_flags & PP_CISCO) ? ETHERTYPE_IPX : PPP_IPX); break; #endif #ifdef ISO case AF_ISO: /* ISO OSI Protocol */ if (sp->pp_flags & PP_CISCO) goto nosupport; h->protocol = htons (PPP_ISO); break; nosupport: #endif default: m_freem (m); splx (s); return (EAFNOSUPPORT); } /* * Queue message on interface, and start output if interface * not yet active. */ if (IF_QFULL (ifq)) { IF_DROP (&ifp->if_snd); m_freem (m); splx (s); return (ENOBUFS); } IF_ENQUEUE (ifq, m); if (! (ifp->if_flags & IFF_OACTIVE)) (*ifp->if_start) (ifp); /* * Count output packets and bytes. * The packet length includes header, FCS and 1 flag, * according to RFC 1333. */ ifp->if_obytes += m->m_pkthdr.len + 3; splx (s); return (0); } void sppp_attach (struct ifnet *ifp) { struct sppp *sp = (struct sppp*) ifp; /* Initialize keepalive handler. */ if (! spppq) timeout (sppp_keepalive, 0, hz * 10); /* Insert new entry into the keepalive list. */ sp->pp_next = spppq; spppq = sp; sp->pp_if.if_type = IFT_PPP; sp->pp_if.if_output = sppp_output; sp->pp_fastq.ifq_maxlen = 32; sp->pp_loopcnt = 0; sp->pp_alivecnt = 0; sp->pp_seq = 0; sp->pp_rseq = 0; sp->lcp.magic = 0; sp->lcp.state = LCP_STATE_CLOSED; sp->ipcp.state = IPCP_STATE_CLOSED; } void sppp_detach (struct ifnet *ifp) { struct sppp **q, *p, *sp = (struct sppp*) ifp; /* Remove the entry from the keepalive list. */ for (q = &spppq; (p = *q); q = &p->pp_next) if (p == sp) { *q = p->pp_next; break; } /* Stop keepalive handler. */ if (! spppq) untimeout (sppp_keepalive, 0); UNTIMO (sp); } /* * Flush the interface output queue. */ void sppp_flush (struct ifnet *ifp) { struct sppp *sp = (struct sppp*) ifp; qflush (&sp->pp_if.if_snd); qflush (&sp->pp_fastq); } /* * Check if the output queue is empty. */ int sppp_isempty (struct ifnet *ifp) { struct sppp *sp = (struct sppp*) ifp; int empty, s = splimp (); empty = !sp->pp_fastq.ifq_head && !sp->pp_if.if_snd.ifq_head; splx (s); return (empty); } /* * Get next packet to send. */ struct mbuf *sppp_dequeue (struct ifnet *ifp) { struct sppp *sp = (struct sppp*) ifp; struct mbuf *m; int s = splimp (); IF_DEQUEUE (&sp->pp_fastq, m); if (! m) IF_DEQUEUE (&sp->pp_if.if_snd, m); splx (s); return (m); } /* * Send keepalive packets, every 10 seconds. */ void sppp_keepalive (void *dummy) { struct sppp *sp; int s = splimp (); for (sp=spppq; sp; sp=sp->pp_next) { struct ifnet *ifp = &sp->pp_if; /* Keepalive mode disabled or channel down? */ if (! (sp->pp_flags & PP_KEEPALIVE) || ! (ifp->if_flags & IFF_RUNNING)) continue; /* No keepalive in PPP mode if LCP not opened yet. */ if (! (sp->pp_flags & PP_CISCO) && sp->lcp.state != LCP_STATE_OPENED) continue; if (sp->pp_alivecnt == MAXALIVECNT) { /* No keepalive packets got. Stop the interface. */ printf ("%s%d: down\n", ifp->if_name, ifp->if_unit); if_down (ifp); qflush (&sp->pp_fastq); if (! (sp->pp_flags & PP_CISCO)) { /* Shut down the PPP link. */ sp->lcp.state = LCP_STATE_CLOSED; sp->ipcp.state = IPCP_STATE_CLOSED; UNTIMO (sp); /* Initiate negotiation. */ sppp_lcp_open (sp); } } if (sp->pp_alivecnt <= MAXALIVECNT) ++sp->pp_alivecnt; if (sp->pp_flags & PP_CISCO) sppp_cisco_send (sp, CISCO_KEEPALIVE_REQ, ++sp->pp_seq, sp->pp_rseq); else if (sp->lcp.state == LCP_STATE_OPENED) { long nmagic = htonl (sp->lcp.magic); sp->lcp.echoid = ++sp->pp_seq; sppp_cp_send (sp, PPP_LCP, LCP_ECHO_REQ, sp->lcp.echoid, 4, &nmagic); } } splx (s); timeout (sppp_keepalive, 0, hz * 10); } /* * Handle incoming PPP Link Control Protocol packets. */ void sppp_lcp_input (struct sppp *sp, struct mbuf *m) { struct lcp_header *h; struct ifnet *ifp = &sp->pp_if; int len = m->m_pkthdr.len; u_char *p, opt[6]; u_long rmagic; if (len < 4) { if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: invalid lcp packet length: %d bytes\n", ifp->if_name, ifp->if_unit, len); return; } h = mtod (m, struct lcp_header*); if (ifp->if_flags & IFF_DEBUG) { char state = '?'; switch (sp->lcp.state) { case LCP_STATE_CLOSED: state = 'C'; break; case LCP_STATE_ACK_RCVD: state = 'R'; break; case LCP_STATE_ACK_SENT: state = 'S'; break; case LCP_STATE_OPENED: state = 'O'; break; } printf ("%s%d: lcp input(%c): %d bytes <%s id=%xh len=%xh", ifp->if_name, ifp->if_unit, state, len, sppp_lcp_type_name (h->type), h->ident, ntohs (h->len)); if (len > 4) sppp_print_bytes ((u_char*) (h+1), len-4); printf (">\n"); } if (len > ntohs (h->len)) len = ntohs (h->len); switch (h->type) { default: /* Unknown packet type -- send Code-Reject packet. */ sppp_cp_send (sp, PPP_LCP, LCP_CODE_REJ, ++sp->pp_seq, m->m_pkthdr.len, h); break; case LCP_CONF_REQ: if (len < 4) { if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: invalid lcp configure request packet length: %d bytes\n", ifp->if_name, ifp->if_unit, len); break; } if (len>4 && !sppp_lcp_conf_parse_options (sp, h, len, &rmagic)) goto badreq; if (rmagic == sp->lcp.magic) { /* Local and remote magics equal -- loopback? */ if (sp->pp_loopcnt >= MAXALIVECNT*5) { printf ("%s%d: loopback\n", ifp->if_name, ifp->if_unit); sp->pp_loopcnt = 0; if (ifp->if_flags & IFF_UP) { if_down (ifp); qflush (&sp->pp_fastq); } } else if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: conf req: magic glitch\n", ifp->if_name, ifp->if_unit); ++sp->pp_loopcnt; /* MUST send Conf-Nack packet. */ rmagic = ~sp->lcp.magic; opt[0] = LCP_OPT_MAGIC; opt[1] = sizeof (opt); opt[2] = rmagic >> 24; opt[3] = rmagic >> 16; opt[4] = rmagic >> 8; opt[5] = rmagic; sppp_cp_send (sp, PPP_LCP, LCP_CONF_NAK, h->ident, sizeof (opt), &opt); badreq: switch (sp->lcp.state) { case LCP_STATE_OPENED: /* Initiate renegotiation. */ sppp_lcp_open (sp); /* fall through... */ case LCP_STATE_ACK_SENT: /* Go to closed state. */ sp->lcp.state = LCP_STATE_CLOSED; sp->ipcp.state = IPCP_STATE_CLOSED; } break; } /* Send Configure-Ack packet. */ sp->pp_loopcnt = 0; sppp_cp_send (sp, PPP_LCP, LCP_CONF_ACK, h->ident, len-4, h+1); /* Change the state. */ switch (sp->lcp.state) { case LCP_STATE_CLOSED: sp->lcp.state = LCP_STATE_ACK_SENT; break; case LCP_STATE_ACK_RCVD: sp->lcp.state = LCP_STATE_OPENED; sppp_ipcp_open (sp); break; case LCP_STATE_OPENED: /* Remote magic changed -- close session. */ sp->lcp.state = LCP_STATE_CLOSED; sp->ipcp.state = IPCP_STATE_CLOSED; /* Initiate renegotiation. */ sppp_lcp_open (sp); /* An ACK has already been sent. */ sp->lcp.state = LCP_STATE_ACK_SENT; break; } break; case LCP_CONF_ACK: if (h->ident != sp->lcp.confid) break; UNTIMO (sp); if (! (ifp->if_flags & IFF_UP) && (ifp->if_flags & IFF_RUNNING)) { /* Coming out of loopback mode. */ ifp->if_flags |= IFF_UP; printf ("%s%d: up\n", ifp->if_name, ifp->if_unit); } switch (sp->lcp.state) { case LCP_STATE_CLOSED: sp->lcp.state = LCP_STATE_ACK_RCVD; TIMO (sp, 5); break; case LCP_STATE_ACK_SENT: sp->lcp.state = LCP_STATE_OPENED; sppp_ipcp_open (sp); break; } break; case LCP_CONF_NAK: if (h->ident != sp->lcp.confid) break; p = (u_char*) (h+1); if (len>=10 && p[0] == LCP_OPT_MAGIC && p[1] >= 4) { rmagic = (u_long)p[2] << 24 | (u_long)p[3] << 16 | p[4] << 8 | p[5]; if (rmagic == ~sp->lcp.magic) { if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: conf nak: magic glitch\n", ifp->if_name, ifp->if_unit); sp->lcp.magic += time.tv_sec + time.tv_usec; } else sp->lcp.magic = rmagic; } if (sp->lcp.state != LCP_STATE_ACK_SENT) { /* Go to closed state. */ sp->lcp.state = LCP_STATE_CLOSED; sp->ipcp.state = IPCP_STATE_CLOSED; } /* The link will be renegotiated after timeout, * to avoid endless req-nack loop. */ UNTIMO (sp); TIMO (sp, 2); break; case LCP_CONF_REJ: if (h->ident != sp->lcp.confid) break; UNTIMO (sp); /* Initiate renegotiation. */ sppp_lcp_open (sp); if (sp->lcp.state != LCP_STATE_ACK_SENT) { /* Go to closed state. */ sp->lcp.state = LCP_STATE_CLOSED; sp->ipcp.state = IPCP_STATE_CLOSED; } break; case LCP_TERM_REQ: UNTIMO (sp); /* Send Terminate-Ack packet. */ sppp_cp_send (sp, PPP_LCP, LCP_TERM_ACK, h->ident, 0, 0); /* Go to closed state. */ sp->lcp.state = LCP_STATE_CLOSED; sp->ipcp.state = IPCP_STATE_CLOSED; /* Initiate renegotiation. */ sppp_lcp_open (sp); break; case LCP_TERM_ACK: case LCP_CODE_REJ: case LCP_PROTO_REJ: /* Ignore for now. */ break; case LCP_DISC_REQ: /* Discard the packet. */ break; case LCP_ECHO_REQ: if (sp->lcp.state != LCP_STATE_OPENED) break; if (len < 8) { if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: invalid lcp echo request packet length: %d bytes\n", ifp->if_name, ifp->if_unit, len); break; } if (ntohl (*(long*)(h+1)) == sp->lcp.magic) { /* Line loopback mode detected. */ printf ("%s%d: loopback\n", ifp->if_name, ifp->if_unit); if_down (ifp); qflush (&sp->pp_fastq); /* Shut down the PPP link. */ sp->lcp.state = LCP_STATE_CLOSED; sp->ipcp.state = IPCP_STATE_CLOSED; UNTIMO (sp); /* Initiate negotiation. */ sppp_lcp_open (sp); break; } *(long*)(h+1) = htonl (sp->lcp.magic); sppp_cp_send (sp, PPP_LCP, LCP_ECHO_REPLY, h->ident, len-4, h+1); break; case LCP_ECHO_REPLY: if (h->ident != sp->lcp.echoid) break; if (len < 8) { if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: invalid lcp echo reply packet length: %d bytes\n", ifp->if_name, ifp->if_unit, len); break; } if (ntohl (*(long*)(h+1)) != sp->lcp.magic) sp->pp_alivecnt = 0; break; } } /* * Handle incoming Cisco keepalive protocol packets. */ static void sppp_cisco_input (struct sppp *sp, struct mbuf *m) { struct cisco_packet *h; struct ifaddr *ifa; struct ifnet *ifp = &sp->pp_if; if (m->m_pkthdr.len != CISCO_PACKET_LEN) { if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: invalid cisco packet length: %d bytes\n", ifp->if_name, ifp->if_unit, m->m_pkthdr.len); return; } h = mtod (m, struct cisco_packet*); if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: cisco input: %d bytes <%lxh %lxh %lxh %xh %xh-%xh>\n", ifp->if_name, ifp->if_unit, m->m_pkthdr.len, ntohl (h->type), h->par1, h->par2, h->rel, h->time0, h->time1); switch (ntohl (h->type)) { default: if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: unknown cisco packet type: 0x%lx\n", ifp->if_name, ifp->if_unit, ntohl (h->type)); break; case CISCO_ADDR_REPLY: /* Reply on address request, ignore */ break; case CISCO_KEEPALIVE_REQ: sp->pp_alivecnt = 0; sp->pp_rseq = ntohl (h->par1); if (sp->pp_seq == sp->pp_rseq) { /* Local and remote sequence numbers are equal. * Probably, the line is in loopback mode. */ if (sp->pp_loopcnt >= MAXALIVECNT) { printf ("%s%d: loopback\n", ifp->if_name, ifp->if_unit); sp->pp_loopcnt = 0; if (ifp->if_flags & IFF_UP) { if_down (ifp); qflush (&sp->pp_fastq); } } ++sp->pp_loopcnt; /* Generate new local sequence number */ sp->pp_seq ^= time.tv_sec ^ time.tv_usec; break; } sp->pp_loopcnt = 0; if (! (ifp->if_flags & IFF_UP) && (ifp->if_flags & IFF_RUNNING)) { ifp->if_flags |= IFF_UP; printf ("%s%d: up\n", ifp->if_name, ifp->if_unit); } break; case CISCO_ADDR_REQ: - for (ifa=ifp->if_addrlist; ifa; ifa=ifa->ifa_next) + for (ifa=ifp->if_addrhead.tqh_first; ifa; + ifa=ifa->ifa_link.tqe_next) if (ifa->ifa_addr->sa_family == AF_INET) break; if (! ifa) { if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: unknown address for cisco request\n", ifp->if_name, ifp->if_unit); return; } sppp_cisco_send (sp, CISCO_ADDR_REPLY, ntohl (((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr), ntohl (((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr.s_addr)); break; } } /* * Send PPP LCP packet. */ static void sppp_cp_send (struct sppp *sp, u_short proto, u_char type, u_char ident, u_short len, void *data) { struct ppp_header *h; struct lcp_header *lh; struct mbuf *m; struct ifnet *ifp = &sp->pp_if; if (len > MHLEN - PPP_HEADER_LEN - LCP_HEADER_LEN) len = MHLEN - PPP_HEADER_LEN - LCP_HEADER_LEN; MGETHDR (m, M_DONTWAIT, MT_DATA); if (! m) return; m->m_pkthdr.len = m->m_len = PPP_HEADER_LEN + LCP_HEADER_LEN + len; m->m_pkthdr.rcvif = 0; h = mtod (m, struct ppp_header*); h->address = PPP_ALLSTATIONS; /* broadcast address */ h->control = PPP_UI; /* Unnumbered Info */ h->protocol = htons (proto); /* Link Control Protocol */ lh = (struct lcp_header*) (h + 1); lh->type = type; lh->ident = ident; lh->len = htons (LCP_HEADER_LEN + len); if (len) bcopy (data, lh+1, len); if (ifp->if_flags & IFF_DEBUG) { printf ("%s%d: %s output <%s id=%xh len=%xh", ifp->if_name, ifp->if_unit, proto==PPP_LCP ? "lcp" : "ipcp", proto==PPP_LCP ? sppp_lcp_type_name (lh->type) : sppp_ipcp_type_name (lh->type), lh->ident, ntohs (lh->len)); if (len) sppp_print_bytes ((u_char*) (lh+1), len); printf (">\n"); } if (IF_QFULL (&sp->pp_fastq)) { IF_DROP (&ifp->if_snd); m_freem (m); } else IF_ENQUEUE (&sp->pp_fastq, m); if (! (ifp->if_flags & IFF_OACTIVE)) (*ifp->if_start) (ifp); ifp->if_obytes += m->m_pkthdr.len + 3; } /* * Send Cisco keepalive packet. */ static void sppp_cisco_send (struct sppp *sp, int type, long par1, long par2) { struct ppp_header *h; struct cisco_packet *ch; struct mbuf *m; struct ifnet *ifp = &sp->pp_if; u_long t = (time.tv_sec - boottime.tv_sec) * 1000; MGETHDR (m, M_DONTWAIT, MT_DATA); if (! m) return; m->m_pkthdr.len = m->m_len = PPP_HEADER_LEN + CISCO_PACKET_LEN; m->m_pkthdr.rcvif = 0; h = mtod (m, struct ppp_header*); h->address = CISCO_MULTICAST; h->control = 0; h->protocol = htons (CISCO_KEEPALIVE); ch = (struct cisco_packet*) (h + 1); ch->type = htonl (type); ch->par1 = htonl (par1); ch->par2 = htonl (par2); ch->rel = -1; ch->time0 = htons ((u_short) (t >> 16)); ch->time1 = htons ((u_short) t); if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: cisco output: <%lxh %lxh %lxh %xh %xh-%xh>\n", ifp->if_name, ifp->if_unit, ntohl (ch->type), ch->par1, ch->par2, ch->rel, ch->time0, ch->time1); if (IF_QFULL (&sp->pp_fastq)) { IF_DROP (&ifp->if_snd); m_freem (m); } else IF_ENQUEUE (&sp->pp_fastq, m); if (! (ifp->if_flags & IFF_OACTIVE)) (*ifp->if_start) (ifp); ifp->if_obytes += m->m_pkthdr.len + 3; } /* * Process an ioctl request. Called on low priority level. */ int sppp_ioctl (struct ifnet *ifp, int cmd, void *data) { struct ifreq *ifr = (struct ifreq*) data; struct sppp *sp = (struct sppp*) ifp; int s, going_up, going_down; switch (cmd) { default: return (EINVAL); case SIOCAIFADDR: case SIOCSIFDSTADDR: break; case SIOCSIFADDR: ifp->if_flags |= IFF_UP; /* fall through... */ case SIOCSIFFLAGS: s = splimp (); going_up = (ifp->if_flags & IFF_UP) && ! (ifp->if_flags & IFF_RUNNING); going_down = ! (ifp->if_flags & IFF_UP) && (ifp->if_flags & IFF_RUNNING); if (going_up || going_down) { /* Shut down the PPP link. */ ifp->if_flags &= ~IFF_RUNNING; sp->lcp.state = LCP_STATE_CLOSED; sp->ipcp.state = IPCP_STATE_CLOSED; UNTIMO (sp); } if (going_up) { /* Interface is starting -- initiate negotiation. */ ifp->if_flags |= IFF_RUNNING; if (!(sp->pp_flags & PP_CISCO)) sppp_lcp_open (sp); } splx (s); break; #ifdef SIOCSIFMTU #ifndef ifr_mtu #define ifr_mtu ifr_metric #endif case SIOCSIFMTU: if (ifr->ifr_mtu < 128 || ifr->ifr_mtu > PP_MTU) return (EINVAL); ifp->if_mtu = ifr->ifr_mtu; break; #endif #ifdef SLIOCSETMTU case SLIOCSETMTU: if (*(short*)data < 128 || *(short*)data > PP_MTU) return (EINVAL); ifp->if_mtu = *(short*)data; break; #endif #ifdef SIOCGIFMTU case SIOCGIFMTU: ifr->ifr_mtu = ifp->if_mtu; break; #endif #ifdef SLIOCGETMTU case SLIOCGETMTU: *(short*)data = ifp->if_mtu; break; #endif case SIOCADDMULTI: case SIOCDELMULTI: break; } return (0); } /* * Analyze the LCP Configure-Request options list * for the presence of unknown options. * If the request contains unknown options, build and * send Configure-reject packet, containing only unknown options. */ static int sppp_lcp_conf_parse_options (struct sppp *sp, struct lcp_header *h, int len, u_long *magic) { u_char *buf, *r, *p; int rlen; len -= 4; buf = r = malloc (len, M_TEMP, M_NOWAIT); if (! buf) return (0); p = (void*) (h+1); for (rlen=0; len>1 && p[1]; len-=p[1], p+=p[1]) { switch (*p) { case LCP_OPT_MAGIC: /* Magic number -- extract. */ if (len >= 6 && p[1] == 6) { *magic = (u_long)p[2] << 24 | (u_long)p[3] << 16 | p[4] << 8 | p[5]; continue; } break; case LCP_OPT_ASYNC_MAP: /* Async control character map -- check to be zero. */ if (len >= 6 && p[1] == 6 && ! p[2] && ! p[3] && ! p[4] && ! p[5]) continue; break; case LCP_OPT_MRU: /* Maximum receive unit -- always OK. */ continue; default: /* Others not supported. */ break; } /* Add the option to rejected list. */ bcopy (p, r, p[1]); r += p[1]; rlen += p[1]; } if (rlen) sppp_cp_send (sp, PPP_LCP, LCP_CONF_REJ, h->ident, rlen, buf); free (buf, M_TEMP); return (rlen == 0); } static void sppp_ipcp_input (struct sppp *sp, struct mbuf *m) { struct lcp_header *h; struct ifnet *ifp = &sp->pp_if; int len = m->m_pkthdr.len; if (len < 4) { /* if (ifp->if_flags & IFF_DEBUG) */ printf ("%s%d: invalid ipcp packet length: %d bytes\n", ifp->if_name, ifp->if_unit, len); return; } h = mtod (m, struct lcp_header*); if (ifp->if_flags & IFF_DEBUG) { printf ("%s%d: ipcp input: %d bytes <%s id=%xh len=%xh", ifp->if_name, ifp->if_unit, len, sppp_ipcp_type_name (h->type), h->ident, ntohs (h->len)); if (len > 4) sppp_print_bytes ((u_char*) (h+1), len-4); printf (">\n"); } if (len > ntohs (h->len)) len = ntohs (h->len); switch (h->type) { default: /* Unknown packet type -- send Code-Reject packet. */ sppp_cp_send (sp, PPP_IPCP, IPCP_CODE_REJ, ++sp->pp_seq, len, h); break; case IPCP_CONF_REQ: if (len < 4) { if (ifp->if_flags & IFF_DEBUG) printf ("%s%d: invalid ipcp configure request packet length: %d bytes\n", ifp->if_name, ifp->if_unit, len); return; } if (len > 4) { sppp_cp_send (sp, PPP_IPCP, LCP_CONF_REJ, h->ident, len-4, h+1); switch (sp->ipcp.state) { case IPCP_STATE_OPENED: /* Initiate renegotiation. */ sppp_ipcp_open (sp); /* fall through... */ case IPCP_STATE_ACK_SENT: /* Go to closed state. */ sp->ipcp.state = IPCP_STATE_CLOSED; } } else { /* Send Configure-Ack packet. */ sppp_cp_send (sp, PPP_IPCP, IPCP_CONF_ACK, h->ident, 0, 0); /* Change the state. */ if (sp->ipcp.state == IPCP_STATE_ACK_RCVD) sp->ipcp.state = IPCP_STATE_OPENED; else sp->ipcp.state = IPCP_STATE_ACK_SENT; } break; case IPCP_CONF_ACK: if (h->ident != sp->ipcp.confid) break; UNTIMO (sp); switch (sp->ipcp.state) { case IPCP_STATE_CLOSED: sp->ipcp.state = IPCP_STATE_ACK_RCVD; TIMO (sp, 5); break; case IPCP_STATE_ACK_SENT: sp->ipcp.state = IPCP_STATE_OPENED; break; } break; case IPCP_CONF_NAK: case IPCP_CONF_REJ: if (h->ident != sp->ipcp.confid) break; UNTIMO (sp); /* Initiate renegotiation. */ sppp_ipcp_open (sp); if (sp->ipcp.state != IPCP_STATE_ACK_SENT) /* Go to closed state. */ sp->ipcp.state = IPCP_STATE_CLOSED; break; case IPCP_TERM_REQ: /* Send Terminate-Ack packet. */ sppp_cp_send (sp, PPP_IPCP, IPCP_TERM_ACK, h->ident, 0, 0); /* Go to closed state. */ sp->ipcp.state = IPCP_STATE_CLOSED; /* Initiate renegotiation. */ sppp_ipcp_open (sp); break; case IPCP_TERM_ACK: /* Ignore for now. */ case IPCP_CODE_REJ: /* Ignore for now. */ break; } } static void sppp_lcp_open (struct sppp *sp) { char opt[6]; if (! sp->lcp.magic) sp->lcp.magic = time.tv_sec + time.tv_usec; opt[0] = LCP_OPT_MAGIC; opt[1] = sizeof (opt); opt[2] = sp->lcp.magic >> 24; opt[3] = sp->lcp.magic >> 16; opt[4] = sp->lcp.magic >> 8; opt[5] = sp->lcp.magic; sp->lcp.confid = ++sp->pp_seq; sppp_cp_send (sp, PPP_LCP, LCP_CONF_REQ, sp->lcp.confid, sizeof (opt), &opt); TIMO (sp, 2); } static void sppp_ipcp_open (struct sppp *sp) { sp->ipcp.confid = ++sp->pp_seq; sppp_cp_send (sp, PPP_IPCP, IPCP_CONF_REQ, sp->ipcp.confid, 0, 0); TIMO (sp, 2); } /* * Process PPP control protocol timeouts. */ static void sppp_cp_timeout (void *arg) { struct sppp *sp = (struct sppp*) arg; int s = splimp (); sp->pp_flags &= ~PP_TIMO; if (! (sp->pp_if.if_flags & IFF_RUNNING) || (sp->pp_flags & PP_CISCO)) { splx (s); return; } switch (sp->lcp.state) { case LCP_STATE_CLOSED: /* No ACK for Configure-Request, retry. */ sppp_lcp_open (sp); break; case LCP_STATE_ACK_RCVD: /* ACK got, but no Configure-Request for peer, retry. */ sppp_lcp_open (sp); sp->lcp.state = LCP_STATE_CLOSED; break; case LCP_STATE_ACK_SENT: /* ACK sent but no ACK for Configure-Request, retry. */ sppp_lcp_open (sp); break; case LCP_STATE_OPENED: /* LCP is already OK, try IPCP. */ switch (sp->ipcp.state) { case IPCP_STATE_CLOSED: /* No ACK for Configure-Request, retry. */ sppp_ipcp_open (sp); break; case IPCP_STATE_ACK_RCVD: /* ACK got, but no Configure-Request for peer, retry. */ sppp_ipcp_open (sp); sp->ipcp.state = IPCP_STATE_CLOSED; break; case IPCP_STATE_ACK_SENT: /* ACK sent but no ACK for Configure-Request, retry. */ sppp_ipcp_open (sp); break; case IPCP_STATE_OPENED: /* IPCP is OK. */ break; } break; } splx (s); } static char *sppp_lcp_type_name (u_char type) { static char buf [8]; switch (type) { case LCP_CONF_REQ: return ("conf-req"); case LCP_CONF_ACK: return ("conf-ack"); case LCP_CONF_NAK: return ("conf-nack"); case LCP_CONF_REJ: return ("conf-rej"); case LCP_TERM_REQ: return ("term-req"); case LCP_TERM_ACK: return ("term-ack"); case LCP_CODE_REJ: return ("code-rej"); case LCP_PROTO_REJ: return ("proto-rej"); case LCP_ECHO_REQ: return ("echo-req"); case LCP_ECHO_REPLY: return ("echo-reply"); case LCP_DISC_REQ: return ("discard-req"); } sprintf (buf, "%xh", type); return (buf); } static char *sppp_ipcp_type_name (u_char type) { static char buf [8]; switch (type) { case IPCP_CONF_REQ: return ("conf-req"); case IPCP_CONF_ACK: return ("conf-ack"); case IPCP_CONF_NAK: return ("conf-nack"); case IPCP_CONF_REJ: return ("conf-rej"); case IPCP_TERM_REQ: return ("term-req"); case IPCP_TERM_ACK: return ("term-ack"); case IPCP_CODE_REJ: return ("code-rej"); } sprintf (buf, "%xh", type); return (buf); } static void sppp_print_bytes (u_char *p, u_short len) { printf (" %x", *p++); while (--len > 0) printf ("-%x", *p++); } diff --git a/sys/net/if_tun.c b/sys/net/if_tun.c index 9518c31f36fb..0b740e5727d8 100644 --- a/sys/net/if_tun.c +++ b/sys/net/if_tun.c @@ -1,629 +1,631 @@ /* $NetBSD: if_tun.c,v 1.14 1994/06/29 06:36:25 cgd Exp $ */ /* * Copyright (c) 1988, Julian Onions * Nottingham University 1987. * * This source may be freely distributed, however I would be interested * in any changes that are made. * * This driver takes packets off the IP i/f and hands them up to a * user process to have it's wicked way with. This driver has it's * roots in a similar driver written by Phil Cockcroft (formerly) at * UCL. This driver is based much more on read/write/select mode of * operation though. */ #include "tun.h" #if NTUN > 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEVFS #include #endif /*DEVFS*/ #include #include #include #include #ifdef INET #include #include #include #include #include #endif #ifdef NS #include #include #endif #include "bpfilter.h" #if NBPFILTER > 0 #include #include #endif #include static void tunattach __P((void *)); PSEUDO_SET(tunattach, if_tun); #define TUNDEBUG if (tundebug) printf static int tundebug = 0; SYSCTL_INT(_debug, OID_AUTO, if_tun_debug, CTLFLAG_RW, &tundebug, 0, ""); static struct tun_softc tunctl[NTUN]; static int tunoutput __P((struct ifnet *, struct mbuf *, struct sockaddr *, struct rtentry *rt)); static int tunifioctl __P((struct ifnet *, int, caddr_t)); static int tuninit __P((int)); static d_open_t tunopen; static d_close_t tunclose; static d_read_t tunread; static d_write_t tunwrite; static d_ioctl_t tunioctl; static d_select_t tunselect; #define CDEV_MAJOR 52 static struct cdevsw tun_cdevsw = { tunopen, tunclose, tunread, tunwrite, tunioctl, nullstop, noreset, nodevtotty, tunselect, nommap, nostrategy, "tun", NULL, -1 }; static tun_devsw_installed = 0; #ifdef DEVFS static void *tun_devfs_token[NTUN]; #endif static void tunattach(dummy) void *dummy; { register int i; struct ifnet *ifp; dev_t dev; if ( tun_devsw_installed ) return; dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev, &tun_cdevsw, NULL); tun_devsw_installed = 1; for ( i = 0; i < NTUN; i++ ) { #ifdef DEVFS tun_devfs_token[i] = devfs_add_devswf(&tun_cdevsw, i, DV_CHR, UID_UUCP, GID_DIALER, 0600, "tun%d", i); #endif tunctl[i].tun_flags = TUN_INITED; ifp = &tunctl[i].tun_if; ifp->if_unit = i; ifp->if_name = "tun"; ifp->if_mtu = TUNMTU; ifp->if_ioctl = tunifioctl; ifp->if_output = tunoutput; ifp->if_flags = IFF_POINTOPOINT | IFF_MULTICAST; ifp->if_snd.ifq_maxlen = ifqmaxlen; ifp->if_collisions = 0; ifp->if_ierrors = 0; ifp->if_oerrors = 0; ifp->if_ipackets = 0; ifp->if_opackets = 0; if_attach(ifp); #if NBPFILTER > 0 bpfattach(ifp, DLT_NULL, sizeof(u_int)); #endif } } /* * tunnel open - must be superuser & the device must be * configured in */ static int tunopen(dev, flag, mode, p) dev_t dev; int flag, mode; struct proc *p; { struct ifnet *ifp; struct tun_softc *tp; register int unit, error; error = suser(p->p_ucred, &p->p_acflag); if (error) return (error); if ((unit = minor(dev)) >= NTUN) return (ENXIO); tp = &tunctl[unit]; if (tp->tun_flags & TUN_OPEN) return EBUSY; ifp = &tp->tun_if; tp->tun_flags |= TUN_OPEN; TUNDEBUG("%s%d: open\n", ifp->if_name, ifp->if_unit); return (0); } /* * tunclose - close the device - mark i/f down & delete * routing info */ static int tunclose(dev_t dev, int foo, int bar, struct proc *p) { register int unit = minor(dev), s; struct tun_softc *tp = &tunctl[unit]; struct ifnet *ifp = &tp->tun_if; struct mbuf *m; tp->tun_flags &= ~TUN_OPEN; /* * junk all pending output */ do { s = splimp(); IF_DEQUEUE(&ifp->if_snd, m); splx(s); if (m) m_freem(m); } while (m); if (ifp->if_flags & IFF_UP) { s = splimp(); if_down(ifp); if (ifp->if_flags & IFF_RUNNING) { /* find internet addresses and delete routes */ register struct ifaddr *ifa; - for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) { + for (ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) { if (ifa->ifa_addr->sa_family == AF_INET) { rtinit(ifa, (int)RTM_DELETE, tp->tun_flags & TUN_DSTADDR ? RTF_HOST : 0); } } } splx(s); } tp->tun_pgrp = 0; selwakeup(&tp->tun_rsel); TUNDEBUG ("%s%d: closed\n", ifp->if_name, ifp->if_unit); return (0); } static int tuninit(unit) int unit; { struct tun_softc *tp = &tunctl[unit]; struct ifnet *ifp = &tp->tun_if; register struct ifaddr *ifa; TUNDEBUG("%s%d: tuninit\n", ifp->if_name, ifp->if_unit); ifp->if_flags |= IFF_UP | IFF_RUNNING; microtime(&ifp->if_lastchange); - for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) + for (ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in *si; si = (struct sockaddr_in *)ifa->ifa_addr; if (si && si->sin_addr.s_addr) tp->tun_flags |= TUN_IASET; si = (struct sockaddr_in *)ifa->ifa_dstaddr; if (si && si->sin_addr.s_addr) tp->tun_flags |= TUN_DSTADDR; } return 0; } /* * Process an ioctl request. */ int tunifioctl(ifp, cmd, data) struct ifnet *ifp; int cmd; caddr_t data; { register struct ifreq *ifr = (struct ifreq *)data; int error = 0, s; s = splimp(); switch(cmd) { case SIOCSIFADDR: tuninit(ifp->if_unit); TUNDEBUG("%s%d: address set\n", ifp->if_name, ifp->if_unit); break; case SIOCSIFDSTADDR: tuninit(ifp->if_unit); TUNDEBUG("%s%d: destination address set\n", ifp->if_name, ifp->if_unit); break; case SIOCADDMULTI: case SIOCDELMULTI: if (ifr == 0) { error = EAFNOSUPPORT; /* XXX */ break; } switch (ifr->ifr_addr.sa_family) { #ifdef INET case AF_INET: break; #endif default: error = EAFNOSUPPORT; break; } break; default: error = EINVAL; } splx(s); return (error); } /* * tunoutput - queue packets from higher level ready to put out. */ int tunoutput(ifp, m0, dst, rt) struct ifnet *ifp; struct mbuf *m0; struct sockaddr *dst; struct rtentry *rt; { struct tun_softc *tp = &tunctl[ifp->if_unit]; struct proc *p; int s; TUNDEBUG ("%s%d: tunoutput\n", ifp->if_name, ifp->if_unit); if ((tp->tun_flags & TUN_READY) != TUN_READY) { TUNDEBUG ("%s%d: not ready 0%o\n", ifp->if_name, ifp->if_unit, tp->tun_flags); m_freem (m0); return EHOSTDOWN; } #if NBPFILTER > 0 /* BPF write needs to be handled specially */ if (dst->sa_family == AF_UNSPEC) { dst->sa_family = *(mtod(m0, int *)); m0->m_len -= sizeof(int); m0->m_pkthdr.len -= sizeof(int); m0->m_data += sizeof(int); } if (ifp->if_bpf) { /* * We need to prepend the address family as * a four byte field. Cons up a dummy header * to pacify bpf. This is safe because bpf * will only read from the mbuf (i.e., it won't * try to free it or keep a pointer to it). */ struct mbuf m; u_int af = dst->sa_family; m.m_next = m0; m.m_len = 4; m.m_data = (char *)⁡ bpf_mtap(ifp, &m); } #endif switch(dst->sa_family) { #ifdef INET case AF_INET: s = splimp(); if (IF_QFULL(&ifp->if_snd)) { IF_DROP(&ifp->if_snd); m_freem(m0); splx(s); ifp->if_collisions++; return (ENOBUFS); } ifp->if_obytes += m0->m_pkthdr.len; IF_ENQUEUE(&ifp->if_snd, m0); splx(s); ifp->if_opackets++; break; #endif default: m_freem(m0); return EAFNOSUPPORT; } if (tp->tun_flags & TUN_RWAIT) { tp->tun_flags &= ~TUN_RWAIT; wakeup((caddr_t)tp); } if (tp->tun_flags & TUN_ASYNC && tp->tun_pgrp) { if (tp->tun_pgrp > 0) gsignal(tp->tun_pgrp, SIGIO); else if ((p = pfind(-tp->tun_pgrp)) != 0) psignal(p, SIGIO); } selwakeup(&tp->tun_rsel); return 0; } /* * the cdevsw interface is now pretty minimal. */ static int tunioctl(dev, cmd, data, flag, p) dev_t dev; int cmd; caddr_t data; int flag; struct proc *p; { int unit = minor(dev), s; struct tun_softc *tp = &tunctl[unit]; struct tuninfo *tunp; switch (cmd) { case TUNSIFINFO: tunp = (struct tuninfo *)data; tp->tun_if.if_mtu = tunp->mtu; tp->tun_if.if_type = tunp->type; tp->tun_if.if_baudrate = tunp->baudrate; break; case TUNGIFINFO: tunp = (struct tuninfo *)data; tunp->mtu = tp->tun_if.if_mtu; tunp->type = tp->tun_if.if_type; tunp->baudrate = tp->tun_if.if_baudrate; break; case TUNSDEBUG: tundebug = *(int *)data; break; case TUNGDEBUG: *(int *)data = tundebug; break; case FIONBIO: if (*(int *)data) tp->tun_flags |= TUN_NBIO; else tp->tun_flags &= ~TUN_NBIO; break; case FIOASYNC: if (*(int *)data) tp->tun_flags |= TUN_ASYNC; else tp->tun_flags &= ~TUN_ASYNC; break; case FIONREAD: s = splimp(); if (tp->tun_if.if_snd.ifq_head) { struct mbuf *mb = tp->tun_if.if_snd.ifq_head; for( *(int *)data = 0; mb != 0; mb = mb->m_next) *(int *)data += mb->m_len; } else *(int *)data = 0; splx(s); break; case TIOCSPGRP: tp->tun_pgrp = *(int *)data; break; case TIOCGPGRP: *(int *)data = tp->tun_pgrp; break; default: return (ENOTTY); } return (0); } /* * The cdevsw read interface - reads a packet at a time, or at * least as much of a packet as can be read. */ static int tunread(dev_t dev, struct uio *uio, int flag) { int unit = minor(dev); struct tun_softc *tp = &tunctl[unit]; struct ifnet *ifp = &tp->tun_if; struct mbuf *m, *m0; int error=0, len, s; TUNDEBUG ("%s%d: read\n", ifp->if_name, ifp->if_unit); if ((tp->tun_flags & TUN_READY) != TUN_READY) { TUNDEBUG ("%s%d: not ready 0%o\n", ifp->if_name, ifp->if_unit, tp->tun_flags); return EHOSTDOWN; } tp->tun_flags &= ~TUN_RWAIT; s = splimp(); do { IF_DEQUEUE(&ifp->if_snd, m0); if (m0 == 0) { if (tp->tun_flags & TUN_NBIO) { splx(s); return EWOULDBLOCK; } tp->tun_flags |= TUN_RWAIT; if( error = tsleep((caddr_t)tp, PCATCH | (PZERO + 1), "tunread", 0)) { splx(s); return error; } } } while (m0 == 0); splx(s); while (m0 && uio->uio_resid > 0 && error == 0) { len = min(uio->uio_resid, m0->m_len); if (len == 0) break; error = uiomove(mtod(m0, caddr_t), len, uio); MFREE(m0, m); m0 = m; } if (m0) { TUNDEBUG("Dropping mbuf\n"); m_freem(m0); } return error; } /* * the cdevsw write interface - an atomic write is a packet - or else! */ static int tunwrite(dev_t dev, struct uio *uio, int flag) { int unit = minor (dev); struct ifnet *ifp = &tunctl[unit].tun_if; struct mbuf *top, **mp, *m; int error=0, s, tlen, mlen; TUNDEBUG("%s%d: tunwrite\n", ifp->if_name, ifp->if_unit); if (uio->uio_resid < 0 || uio->uio_resid > TUNMTU) { TUNDEBUG("%s%d: len=%d!\n", ifp->if_name, ifp->if_unit, uio->uio_resid); return EIO; } tlen = uio->uio_resid; /* get a header mbuf */ MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return ENOBUFS; mlen = MHLEN; top = 0; mp = ⊤ while (error == 0 && uio->uio_resid > 0) { m->m_len = min(mlen, uio->uio_resid); error = uiomove(mtod (m, caddr_t), m->m_len, uio); *mp = m; mp = &m->m_next; if (uio->uio_resid > 0) { MGET (m, M_DONTWAIT, MT_DATA); if (m == 0) { error = ENOBUFS; break; } mlen = MLEN; } } if (error) { if (top) m_freem (top); return error; } top->m_pkthdr.len = tlen; top->m_pkthdr.rcvif = ifp; #if NBPFILTER > 0 if (ifp->if_bpf) { /* * We need to prepend the address family as * a four byte field. Cons up a dummy header * to pacify bpf. This is safe because bpf * will only read from the mbuf (i.e., it won't * try to free it or keep a pointer to it). */ struct mbuf m; u_int af = AF_INET; m.m_next = top; m.m_len = 4; m.m_data = (char *)⁡ bpf_mtap(ifp, &m); } #endif s = splimp(); if (IF_QFULL (&ipintrq)) { IF_DROP(&ipintrq); splx(s); ifp->if_collisions++; m_freem(top); return ENOBUFS; } IF_ENQUEUE(&ipintrq, top); splx(s); ifp->if_ibytes += tlen; ifp->if_ipackets++; schednetisr(NETISR_IP); return error; } /* * tunselect - the select interface, this is only useful on reads * really. The write detect always returns true, write never blocks * anyway, it either accepts the packet or drops it. */ static int tunselect(dev_t dev, int rw, struct proc *p) { int unit = minor(dev), s; struct tun_softc *tp = &tunctl[unit]; struct ifnet *ifp = &tp->tun_if; s = splimp(); TUNDEBUG("%s%d: tunselect\n", ifp->if_name, ifp->if_unit); switch (rw) { case FREAD: if (ifp->if_snd.ifq_len > 0) { splx(s); TUNDEBUG("%s%d: tunselect q=%d\n", ifp->if_name, ifp->if_unit, ifp->if_snd.ifq_len); return 1; } selrecord(p, &tp->tun_rsel); break; case FWRITE: splx(s); return 1; } splx(s); TUNDEBUG("%s%d: tunselect waiting\n", ifp->if_name, ifp->if_unit); return 0; } #endif /* NTUN */ diff --git a/sys/net/rtsock.c b/sys/net/rtsock.c index 582ff7cbe4c3..eb786fa82699 100644 --- a/sys/net/rtsock.c +++ b/sys/net/rtsock.c @@ -1,795 +1,795 @@ /* * Copyright (c) 1988, 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)rtsock.c 8.5 (Berkeley) 11/2/94 - * $Id: rtsock.c,v 1.20 1996/07/10 01:34:36 fenner Exp $ + * $Id: rtsock.c,v 1.21 1996/12/11 20:38:16 wollman Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct sockaddr route_dst = { 2, PF_ROUTE, }; static struct sockaddr route_src = { 2, PF_ROUTE, }; static struct sockproto route_proto = { PF_ROUTE, }; struct walkarg { int w_tmemsize; int w_op, w_arg; caddr_t w_tmem; struct sysctl_req *w_req; }; static struct mbuf * rt_msg1 __P((int, struct rt_addrinfo *)); static int rt_msg2 __P((int, struct rt_addrinfo *, caddr_t, struct walkarg *)); static void rt_xaddrs __P((caddr_t, caddr_t, struct rt_addrinfo *)); static int sysctl_dumpentry __P((struct radix_node *rn, void *vw)); static int sysctl_iflist __P((int af, struct walkarg *w)); static int route_output __P((struct mbuf *, struct socket *)); static int route_usrreq __P((struct socket *, int, struct mbuf *, struct mbuf *, struct mbuf *)); static void rt_setmetrics __P((u_long, struct rt_metrics *, struct rt_metrics *)); /* Sleazy use of local variables throughout file, warning!!!! */ #define dst info.rti_info[RTAX_DST] #define gate info.rti_info[RTAX_GATEWAY] #define netmask info.rti_info[RTAX_NETMASK] #define genmask info.rti_info[RTAX_GENMASK] #define ifpaddr info.rti_info[RTAX_IFP] #define ifaaddr info.rti_info[RTAX_IFA] #define brdaddr info.rti_info[RTAX_BRD] /*ARGSUSED*/ static int route_usrreq(so, req, m, nam, control) register struct socket *so; int req; struct mbuf *m, *nam, *control; { register int error = 0; register struct rawcb *rp = sotorawcb(so); int s; if (req == PRU_ATTACH) { MALLOC(rp, struct rawcb *, sizeof(*rp), M_PCB, M_WAITOK); so->so_pcb = (caddr_t)rp; if (so->so_pcb) bzero(so->so_pcb, sizeof(*rp)); } if (req == PRU_DETACH && rp) { int af = rp->rcb_proto.sp_protocol; if (af == AF_INET) route_cb.ip_count--; else if (af == AF_IPX) route_cb.ipx_count--; else if (af == AF_NS) route_cb.ns_count--; else if (af == AF_ISO) route_cb.iso_count--; route_cb.any_count--; } s = splnet(); error = raw_usrreq(so, req, m, nam, control); rp = sotorawcb(so); if (req == PRU_ATTACH && rp) { int af = rp->rcb_proto.sp_protocol; if (error) { free((caddr_t)rp, M_PCB); splx(s); return (error); } if (af == AF_INET) route_cb.ip_count++; else if (af == AF_IPX) route_cb.ipx_count++; else if (af == AF_NS) route_cb.ns_count++; else if (af == AF_ISO) route_cb.iso_count++; rp->rcb_faddr = &route_src; route_cb.any_count++; soisconnected(so); so->so_options |= SO_USELOOPBACK; } splx(s); return (error); } /*ARGSUSED*/ static int route_output(m, so) register struct mbuf *m; struct socket *so; { register struct rt_msghdr *rtm = 0; register struct rtentry *rt = 0; struct rtentry *saved_nrt = 0; struct radix_node_head *rnh; struct rt_addrinfo info; int len, error = 0; struct ifnet *ifp = 0; struct ifaddr *ifa = 0; #define senderr(e) { error = e; goto flush;} if (m == 0 || ((m->m_len < sizeof(long)) && (m = m_pullup(m, sizeof(long))) == 0)) return (ENOBUFS); if ((m->m_flags & M_PKTHDR) == 0) panic("route_output"); len = m->m_pkthdr.len; if (len < sizeof(*rtm) || len != mtod(m, struct rt_msghdr *)->rtm_msglen) { dst = 0; senderr(EINVAL); } R_Malloc(rtm, struct rt_msghdr *, len); if (rtm == 0) { dst = 0; senderr(ENOBUFS); } m_copydata(m, 0, len, (caddr_t)rtm); if (rtm->rtm_version != RTM_VERSION) { dst = 0; senderr(EPROTONOSUPPORT); } rtm->rtm_pid = curproc->p_pid; info.rti_addrs = rtm->rtm_addrs; rt_xaddrs((caddr_t)(rtm + 1), len + (caddr_t)rtm, &info); if (dst == 0 || (dst->sa_family >= AF_MAX) || (gate != 0 && (gate->sa_family >= AF_MAX))) senderr(EINVAL); if (genmask) { struct radix_node *t; t = rn_addmask((caddr_t)genmask, 0, 1); if (t && Bcmp(genmask, t->rn_key, *(u_char *)genmask) == 0) genmask = (struct sockaddr *)(t->rn_key); else senderr(ENOBUFS); } switch (rtm->rtm_type) { case RTM_ADD: if (gate == 0) senderr(EINVAL); error = rtrequest(RTM_ADD, dst, gate, netmask, rtm->rtm_flags, &saved_nrt); if (error == 0 && saved_nrt) { rt_setmetrics(rtm->rtm_inits, &rtm->rtm_rmx, &saved_nrt->rt_rmx); saved_nrt->rt_refcnt--; saved_nrt->rt_genmask = genmask; } break; case RTM_DELETE: error = rtrequest(RTM_DELETE, dst, gate, netmask, rtm->rtm_flags, &saved_nrt); if (error == 0) { if ((rt = saved_nrt)) rt->rt_refcnt++; goto report; } break; case RTM_GET: case RTM_CHANGE: case RTM_LOCK: if ((rnh = rt_tables[dst->sa_family]) == 0) { senderr(EAFNOSUPPORT); } else if (rt = (struct rtentry *) rnh->rnh_lookup(dst, netmask, rnh)) rt->rt_refcnt++; else senderr(ESRCH); switch(rtm->rtm_type) { case RTM_GET: report: dst = rt_key(rt); gate = rt->rt_gateway; netmask = rt_mask(rt); genmask = rt->rt_genmask; if (rtm->rtm_addrs & (RTA_IFP | RTA_IFA)) { ifp = rt->rt_ifp; if (ifp) { - ifpaddr = ifp->if_addrlist->ifa_addr; + ifpaddr = ifp->if_addrhead.tqh_first->ifa_addr; ifaaddr = rt->rt_ifa->ifa_addr; rtm->rtm_index = ifp->if_index; } else { ifpaddr = 0; ifaaddr = 0; } } len = rt_msg2(rtm->rtm_type, &info, (caddr_t)0, (struct walkarg *)0); if (len > rtm->rtm_msglen) { struct rt_msghdr *new_rtm; R_Malloc(new_rtm, struct rt_msghdr *, len); if (new_rtm == 0) senderr(ENOBUFS); Bcopy(rtm, new_rtm, rtm->rtm_msglen); Free(rtm); rtm = new_rtm; } (void)rt_msg2(rtm->rtm_type, &info, (caddr_t)rtm, (struct walkarg *)0); rtm->rtm_flags = rt->rt_flags; rtm->rtm_rmx = rt->rt_rmx; rtm->rtm_addrs = info.rti_addrs; break; case RTM_CHANGE: if (gate && (error = rt_setgate(rt, rt_key(rt), gate))) senderr(error); /* * If they tried to change things but didn't specify * the required gateway, then just use the old one. * This can happen if the user tries to change the * flags on the default route without changing the * default gateway. Changing flags still doesn't work. */ if ((rt->rt_flags & RTF_GATEWAY) && !gate) gate = rt->rt_gateway; /* new gateway could require new ifaddr, ifp; flags may also be different; ifp may be specified by ll sockaddr when protocol address is ambiguous */ if (ifpaddr && (ifa = ifa_ifwithnet(ifpaddr)) && (ifp = ifa->ifa_ifp)) ifa = ifaof_ifpforaddr(ifaaddr ? ifaaddr : gate, ifp); else if ((ifaaddr && (ifa = ifa_ifwithaddr(ifaaddr))) || (ifa = ifa_ifwithroute(rt->rt_flags, rt_key(rt), gate))) ifp = ifa->ifa_ifp; if (ifa) { register struct ifaddr *oifa = rt->rt_ifa; if (oifa != ifa) { if (oifa && oifa->ifa_rtrequest) oifa->ifa_rtrequest(RTM_DELETE, rt, gate); IFAFREE(rt->rt_ifa); rt->rt_ifa = ifa; ifa->ifa_refcnt++; rt->rt_ifp = ifp; } } rt_setmetrics(rtm->rtm_inits, &rtm->rtm_rmx, &rt->rt_rmx); if (rt->rt_ifa && rt->rt_ifa->ifa_rtrequest) rt->rt_ifa->ifa_rtrequest(RTM_ADD, rt, gate); if (genmask) rt->rt_genmask = genmask; /* * Fall into */ case RTM_LOCK: rt->rt_rmx.rmx_locks &= ~(rtm->rtm_inits); rt->rt_rmx.rmx_locks |= (rtm->rtm_inits & rtm->rtm_rmx.rmx_locks); break; } break; default: senderr(EOPNOTSUPP); } flush: if (rtm) { if (error) rtm->rtm_errno = error; else rtm->rtm_flags |= RTF_DONE; } if (rt) rtfree(rt); { register struct rawcb *rp = 0; /* * Check to see if we don't want our own messages. */ if ((so->so_options & SO_USELOOPBACK) == 0) { if (route_cb.any_count <= 1) { if (rtm) Free(rtm); m_freem(m); return (error); } /* There is another listener, so construct message */ rp = sotorawcb(so); } if (rtm) { m_copyback(m, 0, rtm->rtm_msglen, (caddr_t)rtm); Free(rtm); } if (rp) rp->rcb_proto.sp_family = 0; /* Avoid us */ if (dst) route_proto.sp_protocol = dst->sa_family; raw_input(m, &route_proto, &route_src, &route_dst); if (rp) rp->rcb_proto.sp_family = PF_ROUTE; } return (error); } static void rt_setmetrics(which, in, out) u_long which; register struct rt_metrics *in, *out; { #define metric(f, e) if (which & (f)) out->e = in->e; metric(RTV_RPIPE, rmx_recvpipe); metric(RTV_SPIPE, rmx_sendpipe); metric(RTV_SSTHRESH, rmx_ssthresh); metric(RTV_RTT, rmx_rtt); metric(RTV_RTTVAR, rmx_rttvar); metric(RTV_HOPCOUNT, rmx_hopcount); metric(RTV_MTU, rmx_mtu); metric(RTV_EXPIRE, rmx_expire); #undef metric } #define ROUNDUP(a) \ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len)) static void rt_xaddrs(cp, cplim, rtinfo) register caddr_t cp, cplim; register struct rt_addrinfo *rtinfo; { register struct sockaddr *sa; register int i; bzero(rtinfo->rti_info, sizeof(rtinfo->rti_info)); for (i = 0; (i < RTAX_MAX) && (cp < cplim); i++) { if ((rtinfo->rti_addrs & (1 << i)) == 0) continue; rtinfo->rti_info[i] = sa = (struct sockaddr *)cp; ADVANCE(cp, sa); } } static struct mbuf * rt_msg1(type, rtinfo) int type; register struct rt_addrinfo *rtinfo; { register struct rt_msghdr *rtm; register struct mbuf *m; register int i; register struct sockaddr *sa; int len, dlen; m = m_gethdr(M_DONTWAIT, MT_DATA); if (m == 0) return (m); switch (type) { case RTM_DELADDR: case RTM_NEWADDR: len = sizeof(struct ifa_msghdr); break; case RTM_IFINFO: len = sizeof(struct if_msghdr); break; default: len = sizeof(struct rt_msghdr); } if (len > MHLEN) panic("rt_msg1"); m->m_pkthdr.len = m->m_len = len; m->m_pkthdr.rcvif = 0; rtm = mtod(m, struct rt_msghdr *); bzero((caddr_t)rtm, len); for (i = 0; i < RTAX_MAX; i++) { if ((sa = rtinfo->rti_info[i]) == NULL) continue; rtinfo->rti_addrs |= (1 << i); dlen = ROUNDUP(sa->sa_len); m_copyback(m, len, dlen, (caddr_t)sa); len += dlen; } if (m->m_pkthdr.len != len) { m_freem(m); return (NULL); } rtm->rtm_msglen = len; rtm->rtm_version = RTM_VERSION; rtm->rtm_type = type; return (m); } static int rt_msg2(type, rtinfo, cp, w) int type; register struct rt_addrinfo *rtinfo; caddr_t cp; struct walkarg *w; { register int i; int len, dlen, second_time = 0; caddr_t cp0; rtinfo->rti_addrs = 0; again: switch (type) { case RTM_DELADDR: case RTM_NEWADDR: len = sizeof(struct ifa_msghdr); break; case RTM_IFINFO: len = sizeof(struct if_msghdr); break; default: len = sizeof(struct rt_msghdr); } cp0 = cp; if (cp0) cp += len; for (i = 0; i < RTAX_MAX; i++) { register struct sockaddr *sa; if ((sa = rtinfo->rti_info[i]) == 0) continue; rtinfo->rti_addrs |= (1 << i); dlen = ROUNDUP(sa->sa_len); if (cp) { bcopy((caddr_t)sa, cp, (unsigned)dlen); cp += dlen; } len += dlen; } if (cp == 0 && w != NULL && !second_time) { register struct walkarg *rw = w; if (rw->w_req) { if (rw->w_tmemsize < len) { if (rw->w_tmem) free(rw->w_tmem, M_RTABLE); rw->w_tmem = (caddr_t) malloc(len, M_RTABLE, M_NOWAIT); if (rw->w_tmem) rw->w_tmemsize = len; } if (rw->w_tmem) { cp = rw->w_tmem; second_time = 1; goto again; } } } if (cp) { register struct rt_msghdr *rtm = (struct rt_msghdr *)cp0; rtm->rtm_version = RTM_VERSION; rtm->rtm_type = type; rtm->rtm_msglen = len; } return (len); } /* * This routine is called to generate a message from the routing * socket indicating that a redirect has occured, a routing lookup * has failed, or that a protocol has detected timeouts to a particular * destination. */ void rt_missmsg(type, rtinfo, flags, error) int type, flags, error; register struct rt_addrinfo *rtinfo; { register struct rt_msghdr *rtm; register struct mbuf *m; struct sockaddr *sa = rtinfo->rti_info[RTAX_DST]; if (route_cb.any_count == 0) return; m = rt_msg1(type, rtinfo); if (m == 0) return; rtm = mtod(m, struct rt_msghdr *); rtm->rtm_flags = RTF_DONE | flags; rtm->rtm_errno = error; rtm->rtm_addrs = rtinfo->rti_addrs; route_proto.sp_protocol = sa ? sa->sa_family : 0; raw_input(m, &route_proto, &route_src, &route_dst); } /* * This routine is called to generate a message from the routing * socket indicating that the status of a network interface has changed. */ void rt_ifmsg(ifp) register struct ifnet *ifp; { register struct if_msghdr *ifm; struct mbuf *m; struct rt_addrinfo info; if (route_cb.any_count == 0) return; bzero((caddr_t)&info, sizeof(info)); m = rt_msg1(RTM_IFINFO, &info); if (m == 0) return; ifm = mtod(m, struct if_msghdr *); ifm->ifm_index = ifp->if_index; ifm->ifm_flags = (u_short)ifp->if_flags; ifm->ifm_data = ifp->if_data; ifm->ifm_addrs = 0; route_proto.sp_protocol = 0; raw_input(m, &route_proto, &route_src, &route_dst); } /* * This is called to generate messages from the routing socket * indicating a network interface has had addresses associated with it. * if we ever reverse the logic and replace messages TO the routing * socket indicate a request to configure interfaces, then it will * be unnecessary as the routing socket will automatically generate * copies of it. */ void rt_newaddrmsg(cmd, ifa, error, rt) int cmd, error; register struct ifaddr *ifa; register struct rtentry *rt; { struct rt_addrinfo info; struct sockaddr *sa = 0; int pass; struct mbuf *m = 0; struct ifnet *ifp = ifa->ifa_ifp; if (route_cb.any_count == 0) return; for (pass = 1; pass < 3; pass++) { bzero((caddr_t)&info, sizeof(info)); if ((cmd == RTM_ADD && pass == 1) || (cmd == RTM_DELETE && pass == 2)) { register struct ifa_msghdr *ifam; int ncmd = cmd == RTM_ADD ? RTM_NEWADDR : RTM_DELADDR; ifaaddr = sa = ifa->ifa_addr; - ifpaddr = ifp->if_addrlist->ifa_addr; + ifpaddr = ifp->if_addrhead.tqh_first->ifa_addr; netmask = ifa->ifa_netmask; brdaddr = ifa->ifa_dstaddr; if ((m = rt_msg1(ncmd, &info)) == NULL) continue; ifam = mtod(m, struct ifa_msghdr *); ifam->ifam_index = ifp->if_index; ifam->ifam_metric = ifa->ifa_metric; ifam->ifam_flags = ifa->ifa_flags; ifam->ifam_addrs = info.rti_addrs; } if ((cmd == RTM_ADD && pass == 2) || (cmd == RTM_DELETE && pass == 1)) { register struct rt_msghdr *rtm; if (rt == 0) continue; netmask = rt_mask(rt); dst = sa = rt_key(rt); gate = rt->rt_gateway; if ((m = rt_msg1(cmd, &info)) == NULL) continue; rtm = mtod(m, struct rt_msghdr *); rtm->rtm_index = ifp->if_index; rtm->rtm_flags |= rt->rt_flags; rtm->rtm_errno = error; rtm->rtm_addrs = info.rti_addrs; } route_proto.sp_protocol = sa ? sa->sa_family : 0; raw_input(m, &route_proto, &route_src, &route_dst); } } /* * This is used in dumping the kernel table via sysctl(). */ int sysctl_dumpentry(rn, vw) struct radix_node *rn; void *vw; { register struct walkarg *w = vw; register struct rtentry *rt = (struct rtentry *)rn; int error = 0, size; struct rt_addrinfo info; if (w->w_op == NET_RT_FLAGS && !(rt->rt_flags & w->w_arg)) return 0; bzero((caddr_t)&info, sizeof(info)); dst = rt_key(rt); gate = rt->rt_gateway; netmask = rt_mask(rt); genmask = rt->rt_genmask; size = rt_msg2(RTM_GET, &info, 0, w); if (w->w_req && w->w_tmem) { register struct rt_msghdr *rtm = (struct rt_msghdr *)w->w_tmem; rtm->rtm_flags = rt->rt_flags; rtm->rtm_use = rt->rt_use; rtm->rtm_rmx = rt->rt_rmx; rtm->rtm_index = rt->rt_ifp->if_index; rtm->rtm_errno = rtm->rtm_pid = rtm->rtm_seq = 0; rtm->rtm_addrs = info.rti_addrs; error = SYSCTL_OUT(w->w_req, (caddr_t)rtm, size); return (error); } return (error); } int sysctl_iflist(af, w) int af; register struct walkarg *w; { register struct ifnet *ifp; register struct ifaddr *ifa; struct rt_addrinfo info; int len, error = 0; bzero((caddr_t)&info, sizeof(info)); for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_link.tqe_next) { if (w->w_arg && w->w_arg != ifp->if_index) continue; - ifa = ifp->if_addrlist; + ifa = ifp->if_addrhead.tqh_first; ifpaddr = ifa->ifa_addr; len = rt_msg2(RTM_IFINFO, &info, (caddr_t)0, w); ifpaddr = 0; if (w->w_req && w->w_tmem) { register struct if_msghdr *ifm; ifm = (struct if_msghdr *)w->w_tmem; ifm->ifm_index = ifp->if_index; ifm->ifm_flags = (u_short)ifp->if_flags; ifm->ifm_data = ifp->if_data; ifm->ifm_addrs = info.rti_addrs; error = SYSCTL_OUT(w->w_req,(caddr_t)ifm, len); if (error) return (error); } - while ((ifa = ifa->ifa_next) != 0) { + while ((ifa = ifa->ifa_link.tqe_next) != 0) { if (af && af != ifa->ifa_addr->sa_family) continue; ifaaddr = ifa->ifa_addr; netmask = ifa->ifa_netmask; brdaddr = ifa->ifa_dstaddr; len = rt_msg2(RTM_NEWADDR, &info, 0, w); if (w->w_req && w->w_tmem) { register struct ifa_msghdr *ifam; ifam = (struct ifa_msghdr *)w->w_tmem; ifam->ifam_index = ifa->ifa_ifp->if_index; ifam->ifam_flags = ifa->ifa_flags; ifam->ifam_metric = ifa->ifa_metric; ifam->ifam_addrs = info.rti_addrs; error = SYSCTL_OUT(w->w_req, w->w_tmem, len); if (error) return (error); } } ifaaddr = netmask = brdaddr = 0; } return (0); } static int sysctl_rtsock SYSCTL_HANDLER_ARGS { int *name = (int *)arg1; u_int namelen = arg2; register struct radix_node_head *rnh; int i, s, error = EINVAL; u_char af; struct walkarg w; name ++; namelen--; if (req->newptr) return (EPERM); if (namelen != 3) return (EINVAL); af = name[0]; Bzero(&w, sizeof(w)); w.w_op = name[1]; w.w_arg = name[2]; w.w_req = req; s = splnet(); switch (w.w_op) { case NET_RT_DUMP: case NET_RT_FLAGS: for (i = 1; i <= AF_MAX; i++) if ((rnh = rt_tables[i]) && (af == 0 || af == i) && (error = rnh->rnh_walktree(rnh, sysctl_dumpentry, &w))) break; break; case NET_RT_IFLIST: error = sysctl_iflist(af, &w); } splx(s); if (w.w_tmem) free(w.w_tmem, M_RTABLE); return (error); } SYSCTL_NODE(_net, PF_ROUTE, routetable, CTLFLAG_RD, sysctl_rtsock,""); /* * Definitions of protocols supported in the ROUTE domain. */ extern struct domain routedomain; /* or at least forward */ static struct protosw routesw[] = { { SOCK_RAW, &routedomain, 0, PR_ATOMIC|PR_ADDR, 0, route_output, raw_ctlinput, 0, route_usrreq, raw_init } }; static struct domain routedomain = { PF_ROUTE, "route", route_init, 0, 0, routesw, &routesw[sizeof(routesw)/sizeof(routesw[0])] }; DOMAIN_SET(route); diff --git a/sys/netatalk/aarp.c b/sys/netatalk/aarp.c index 1d7e029f8679..d81d1774bc08 100644 --- a/sys/netatalk/aarp.c +++ b/sys/netatalk/aarp.c @@ -1,624 +1,625 @@ /* * Copyright (c) 1990,1991 Regents of The University of Michigan. * All Rights Reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef s_net #include #include #include #include #include #include #include static void aarptfree( struct aarptab *aat); static void at_aarpinput( struct arpcom *ac, struct mbuf *m); #define AARPTAB_BSIZ 9 #define AARPTAB_NB 19 #define AARPTAB_SIZE (AARPTAB_BSIZ * AARPTAB_NB) struct aarptab aarptab[AARPTAB_SIZE]; int aarptab_size = AARPTAB_SIZE; #define AARPTAB_HASH(a) \ ((((a).s_net << 8 ) + (a).s_node ) % AARPTAB_NB ) #define AARPTAB_LOOK(aat,addr) { \ int n; \ aat = &aarptab[ AARPTAB_HASH(addr) * AARPTAB_BSIZ ]; \ for ( n = 0; n < AARPTAB_BSIZ; n++, aat++ ) \ if ( aat->aat_ataddr.s_net == (addr).s_net && \ aat->aat_ataddr.s_node == (addr).s_node ) \ break; \ if ( n >= AARPTAB_BSIZ ) \ aat = 0; \ } #define AARPT_AGE (60 * 1) #define AARPT_KILLC 20 #define AARPT_KILLI 3 # if !defined( __FreeBSD__ ) extern u_char etherbroadcastaddr[6]; # endif __FreeBSD__ u_char atmulticastaddr[ 6 ] = { 0x09, 0x00, 0x07, 0xff, 0xff, 0xff, }; u_char at_org_code[ 3 ] = { 0x08, 0x00, 0x07, }; u_char aarp_org_code[ 3 ] = { 0x00, 0x00, 0x00, }; static void aarptimer(void *ignored) { struct aarptab *aat; int i, s; timeout( aarptimer, (caddr_t)0, AARPT_AGE * hz ); aat = aarptab; for ( i = 0; i < AARPTAB_SIZE; i++, aat++ ) { if ( aat->aat_flags == 0 || ( aat->aat_flags & ATF_PERM )) continue; if ( ++aat->aat_timer < (( aat->aat_flags & ATF_COM ) ? AARPT_KILLC : AARPT_KILLI )) continue; s = splimp(); aarptfree( aat ); splx( s ); } } /* * search through the network addresses to find one that includes * the given network.. remember to take netranges into * consideration. */ struct ifaddr * -at_ifawithnet( sat, ifa ) +at_ifawithnet( sat, ifah ) struct sockaddr_at *sat; - struct ifaddr *ifa; + struct ifaddrhead *ifah; { struct sockaddr_at *sat2; struct netrange *nr; + struct ifaddr *ifa; - for (; ifa; ifa = ifa->ifa_next ) { + for (ifa = ifah->tqh_first; ifa; ifa = ifa->ifa_link.tqe_next ) { if ( ifa->ifa_addr->sa_family != AF_APPLETALK ) { continue; } sat2 = satosat( ifa->ifa_addr ); if ( sat2->sat_addr.s_net == sat->sat_addr.s_net ) { break; } nr = (struct netrange *)(sat2->sat_zero); if( (nr->nr_phase == 2 ) && (nr->nr_firstnet <= sat->sat_addr.s_net) && (nr->nr_lastnet >= sat->sat_addr.s_net)) { break; } } return( ifa ); } static void aarpwhohas( struct arpcom *ac, struct sockaddr_at *sat ) { struct mbuf *m; struct ether_header *eh; struct ether_aarp *ea; struct at_ifaddr *aa; struct llc *llc; struct sockaddr sa; if (( m = m_gethdr( M_DONTWAIT, MT_DATA )) == NULL ) { return; } m->m_len = sizeof( *ea ); m->m_pkthdr.len = sizeof( *ea ); MH_ALIGN( m, sizeof( *ea )); ea = mtod( m, struct ether_aarp *); bzero((caddr_t)ea, sizeof( *ea )); ea->aarp_hrd = htons( AARPHRD_ETHER ); ea->aarp_pro = htons( ETHERTYPE_AT ); ea->aarp_hln = sizeof( ea->aarp_sha ); ea->aarp_pln = sizeof( ea->aarp_spu ); ea->aarp_op = htons( AARPOP_REQUEST ); bcopy((caddr_t)ac->ac_enaddr, (caddr_t)ea->aarp_sha, sizeof( ea->aarp_sha )); /* * We need to check whether the output ethernet type should * be phase 1 or 2. We have the interface that we'll be sending * the aarp out. We need to find an AppleTalk network on that * interface with the same address as we're looking for. If the * net is phase 2, generate an 802.2 and SNAP header. */ - if (( aa = (struct at_ifaddr *)at_ifawithnet( sat, ac->ac_if.if_addrlist )) - == NULL ) { + if ((aa = (struct at_ifaddr *)at_ifawithnet(sat, &ac->ac_if.if_addrhead)) + == NULL) { m_freem( m ); return; } eh = (struct ether_header *)sa.sa_data; if ( aa->aa_flags & AFA_PHASE2 ) { bcopy((caddr_t)atmulticastaddr, (caddr_t)eh->ether_dhost, sizeof( eh->ether_dhost )); eh->ether_type = htons(sizeof(struct llc) + sizeof(struct ether_aarp)); M_PREPEND( m, sizeof( struct llc ), M_WAIT ); llc = mtod( m, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; bcopy( aarp_org_code, llc->llc_org_code, sizeof( aarp_org_code )); llc->llc_ether_type = htons( ETHERTYPE_AARP ); bcopy( &AA_SAT( aa )->sat_addr.s_net, ea->aarp_spnet, sizeof( ea->aarp_spnet )); bcopy( &sat->sat_addr.s_net, ea->aarp_tpnet, sizeof( ea->aarp_tpnet )); ea->aarp_spnode = AA_SAT( aa )->sat_addr.s_node; ea->aarp_tpnode = sat->sat_addr.s_node; } else { bcopy((caddr_t)etherbroadcastaddr, (caddr_t)eh->ether_dhost, sizeof( eh->ether_dhost )); eh->ether_type = htons( ETHERTYPE_AARP ); ea->aarp_spa = AA_SAT( aa )->sat_addr.s_node; ea->aarp_tpa = sat->sat_addr.s_node; } #ifdef NETATALKDEBUG printf("aarp: sending request for %u.%u\n", ntohs(AA_SAT( aa )->sat_addr.s_net), AA_SAT( aa )->sat_addr.s_node); #endif NETATALKDEBUG sa.sa_len = sizeof( struct sockaddr ); sa.sa_family = AF_UNSPEC; (*ac->ac_if.if_output)(&ac->ac_if, m, &sa, NULL); /* XXX NULL should be routing information */ } int aarpresolve( ac, m, destsat, desten ) struct arpcom *ac; struct mbuf *m; struct sockaddr_at *destsat; u_char *desten; { struct at_ifaddr *aa; struct aarptab *aat; int s; if ( at_broadcast( destsat )) { - if (( aa = (struct at_ifaddr *)at_ifawithnet( destsat, - ((struct ifnet *)ac)->if_addrlist )) == NULL ) { + if ((aa = (struct at_ifaddr *)at_ifawithnet(destsat, + &((struct ifnet *)ac)->if_addrhead)) == NULL) { m_freem( m ); return( 0 ); } if ( aa->aa_flags & AFA_PHASE2 ) { bcopy( (caddr_t)atmulticastaddr, (caddr_t)desten, sizeof( atmulticastaddr )); } else { bcopy( (caddr_t)etherbroadcastaddr, (caddr_t)desten, sizeof( etherbroadcastaddr )); } return( 1 ); } s = splimp(); AARPTAB_LOOK( aat, destsat->sat_addr ); if ( aat == 0 ) { /* No entry */ aat = aarptnew( &destsat->sat_addr ); if ( aat == 0 ) { panic( "aarpresolve: no free entry" ); } aat->aat_hold = m; aarpwhohas( ac, destsat ); splx( s ); return( 0 ); } /* found an entry */ aat->aat_timer = 0; if ( aat->aat_flags & ATF_COM ) { /* entry is COMplete */ bcopy( (caddr_t)aat->aat_enaddr, (caddr_t)desten, sizeof( aat->aat_enaddr )); splx( s ); return( 1 ); } /* entry has not completed */ if ( aat->aat_hold ) { m_freem( aat->aat_hold ); } aat->aat_hold = m; aarpwhohas( ac, destsat ); splx( s ); return( 0 ); } void aarpinput( ac, m ) struct arpcom *ac; struct mbuf *m; { struct arphdr *ar; if ( ac->ac_if.if_flags & IFF_NOARP ) goto out; if ( m->m_len < sizeof( struct arphdr )) { goto out; } ar = mtod( m, struct arphdr *); if ( ntohs( ar->ar_hrd ) != AARPHRD_ETHER ) { goto out; } if ( m->m_len < sizeof( struct arphdr ) + 2 * ar->ar_hln + 2 * ar->ar_pln ) { goto out; } switch( ntohs( ar->ar_pro )) { case ETHERTYPE_AT : at_aarpinput( ac, m ); return; default: break; } out: m_freem( m ); } static void at_aarpinput( struct arpcom *ac, struct mbuf *m) { struct ether_aarp *ea; struct at_ifaddr *aa; struct aarptab *aat; struct ether_header *eh; struct llc *llc; struct sockaddr_at sat; struct sockaddr sa; struct at_addr spa, tpa, ma; int op; u_short net; ea = mtod( m, struct ether_aarp *); /* Check to see if from my hardware address */ if ( !bcmp(( caddr_t )ea->aarp_sha, ( caddr_t )ac->ac_enaddr, sizeof( ac->ac_enaddr ))) { m_freem( m ); return; } op = ntohs( ea->aarp_op ); bcopy( ea->aarp_tpnet, &net, sizeof( net )); if ( net != 0 ) { /* should be ATADDR_ANYNET? */ sat.sat_len = sizeof(struct sockaddr_at); sat.sat_family = AF_APPLETALK; sat.sat_addr.s_net = net; - if (( aa = (struct at_ifaddr *)at_ifawithnet( &sat, - ac->ac_if.if_addrlist )) == NULL ) { + if ((aa = (struct at_ifaddr *)at_ifawithnet(&sat, + &ac->ac_if.if_addrhead)) == NULL) { m_freem( m ); return; } bcopy( ea->aarp_spnet, &spa.s_net, sizeof( spa.s_net )); bcopy( ea->aarp_tpnet, &tpa.s_net, sizeof( tpa.s_net )); } else { /* * Since we don't know the net, we just look for the first * phase 1 address on the interface. */ - for ( aa = (struct at_ifaddr *)ac->ac_if.if_addrlist; aa; - aa = (struct at_ifaddr *)aa->aa_ifa.ifa_next ) { + for (aa = (struct at_ifaddr *)ac->ac_if.if_addrhead.tqh_first; aa; + aa = (struct at_ifaddr *)aa->aa_ifa.ifa_link.tqe_next) { if ( AA_SAT( aa )->sat_family == AF_APPLETALK && ( aa->aa_flags & AFA_PHASE2 ) == 0 ) { break; } } if ( aa == NULL ) { m_freem( m ); return; } tpa.s_net = spa.s_net = AA_SAT( aa )->sat_addr.s_net; } spa.s_node = ea->aarp_spnode; tpa.s_node = ea->aarp_tpnode; ma.s_net = AA_SAT( aa )->sat_addr.s_net; ma.s_node = AA_SAT( aa )->sat_addr.s_node; /* * This looks like it's from us. */ if ( spa.s_net == ma.s_net && spa.s_node == ma.s_node ) { if ( aa->aa_flags & AFA_PROBING ) { /* * We're probing, someone either responded to our probe, or * probed for the same address we'd like to use. Change the * address we're probing for. */ untimeout((timeout_func_t) aarpprobe, ac ); wakeup( aa ); m_freem( m ); return; } else if ( op != AARPOP_PROBE ) { /* * This is not a probe, and we're not probing. This means * that someone's saying they have the same source address * as the one we're using. Get upset... */ log( LOG_ERR, "aarp: duplicate AT address!! %x:%x:%x:%x:%x:%x\n", ea->aarp_sha[ 0 ], ea->aarp_sha[ 1 ], ea->aarp_sha[ 2 ], ea->aarp_sha[ 3 ], ea->aarp_sha[ 4 ], ea->aarp_sha[ 5 ]); m_freem( m ); return; } } AARPTAB_LOOK( aat, spa ); if ( aat ) { if ( op == AARPOP_PROBE ) { /* * Someone's probing for spa, dealocate the one we've got, * so that if the prober keeps the address, we'll be able * to arp for him. */ aarptfree( aat ); m_freem( m ); return; } bcopy(( caddr_t )ea->aarp_sha, ( caddr_t )aat->aat_enaddr, sizeof( ea->aarp_sha )); aat->aat_flags |= ATF_COM; if ( aat->aat_hold ) { sat.sat_len = sizeof(struct sockaddr_at); sat.sat_family = AF_APPLETALK; sat.sat_addr = spa; (*ac->ac_if.if_output)( &ac->ac_if, aat->aat_hold, (struct sockaddr *)&sat, NULL); /* XXX */ aat->aat_hold = 0; } } if ( aat == 0 && tpa.s_net == ma.s_net && tpa.s_node == ma.s_node && op != AARPOP_PROBE ) { if ( aat = aarptnew( &spa )) { bcopy(( caddr_t )ea->aarp_sha, ( caddr_t )aat->aat_enaddr, sizeof( ea->aarp_sha )); aat->aat_flags |= ATF_COM; } } /* * Don't respond to responses, and never respond if we're * still probing. */ if ( tpa.s_net != ma.s_net || tpa.s_node != ma.s_node || op == AARPOP_RESPONSE || ( aa->aa_flags & AFA_PROBING )) { m_freem( m ); return; } bcopy(( caddr_t )ea->aarp_sha, ( caddr_t )ea->aarp_tha, sizeof( ea->aarp_sha )); bcopy(( caddr_t )ac->ac_enaddr, ( caddr_t )ea->aarp_sha, sizeof( ea->aarp_sha )); /* XXX */ eh = (struct ether_header *)sa.sa_data; bcopy(( caddr_t )ea->aarp_tha, ( caddr_t )eh->ether_dhost, sizeof( eh->ether_dhost )); if ( aa->aa_flags & AFA_PHASE2 ) { eh->ether_type = htons( sizeof( struct llc ) + sizeof( struct ether_aarp )); M_PREPEND( m, sizeof( struct llc ), M_DONTWAIT ); if ( m == NULL ) { return; } llc = mtod( m, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; bcopy( aarp_org_code, llc->llc_org_code, sizeof( aarp_org_code )); llc->llc_ether_type = htons( ETHERTYPE_AARP ); bcopy( ea->aarp_spnet, ea->aarp_tpnet, sizeof( ea->aarp_tpnet )); bcopy( &ma.s_net, ea->aarp_spnet, sizeof( ea->aarp_spnet )); } else { eh->ether_type = htons( ETHERTYPE_AARP ); } ea->aarp_tpnode = ea->aarp_spnode; ea->aarp_spnode = ma.s_node; ea->aarp_op = htons( AARPOP_RESPONSE ); sa.sa_len = sizeof( struct sockaddr ); sa.sa_family = AF_UNSPEC; (*ac->ac_if.if_output)( &ac->ac_if, m, &sa, NULL); /* XXX */ return; } static void aarptfree( struct aarptab *aat) { if ( aat->aat_hold ) m_freem( aat->aat_hold ); aat->aat_hold = 0; aat->aat_timer = aat->aat_flags = 0; aat->aat_ataddr.s_net = 0; aat->aat_ataddr.s_node = 0; } struct aarptab * aarptnew( addr ) struct at_addr *addr; { int n; int oldest = -1; struct aarptab *aat, *aato = NULL; static int first = 1; if ( first ) { first = 0; timeout( aarptimer, (caddr_t)0, hz ); } aat = &aarptab[ AARPTAB_HASH( *addr ) * AARPTAB_BSIZ ]; for ( n = 0; n < AARPTAB_BSIZ; n++, aat++ ) { if ( aat->aat_flags == 0 ) goto out; if ( aat->aat_flags & ATF_PERM ) continue; if ((int) aat->aat_timer > oldest ) { oldest = aat->aat_timer; aato = aat; } } if ( aato == NULL ) return( NULL ); aat = aato; aarptfree( aat ); out: aat->aat_ataddr = *addr; aat->aat_flags = ATF_INUSE; return( aat ); } void aarpprobe( struct arpcom *ac ) { struct mbuf *m; struct ether_header *eh; struct ether_aarp *ea; struct at_ifaddr *aa; struct llc *llc; struct sockaddr sa; /* * We need to check whether the output ethernet type should * be phase 1 or 2. We have the interface that we'll be sending * the aarp out. We need to find an AppleTalk network on that * interface with the same address as we're looking for. If the * net is phase 2, generate an 802.2 and SNAP header. */ - for ( aa = (struct at_ifaddr *)ac->ac_if.if_addrlist; aa; - aa = (struct at_ifaddr *)aa->aa_ifa.ifa_next ) { + for (aa = (struct at_ifaddr *)ac->ac_if.if_addrhead.tqh_first; aa; + aa = (struct at_ifaddr *)aa->aa_ifa.ifa_link.tqe_next) { if ( AA_SAT( aa )->sat_family == AF_APPLETALK && ( aa->aa_flags & AFA_PROBING )) { break; } } if ( aa == NULL ) { /* serious error XXX */ printf( "aarpprobe why did this happen?!\n" ); return; } if ( aa->aa_probcnt <= 0 ) { aa->aa_flags &= ~AFA_PROBING; wakeup( aa ); return; } else { timeout( (timeout_func_t)aarpprobe, (caddr_t)ac, hz / 5 ); } if (( m = m_gethdr( M_DONTWAIT, MT_DATA )) == NULL ) { return; } m->m_len = sizeof( *ea ); m->m_pkthdr.len = sizeof( *ea ); MH_ALIGN( m, sizeof( *ea )); ea = mtod( m, struct ether_aarp *); bzero((caddr_t)ea, sizeof( *ea )); ea->aarp_hrd = htons( AARPHRD_ETHER ); ea->aarp_pro = htons( ETHERTYPE_AT ); ea->aarp_hln = sizeof( ea->aarp_sha ); ea->aarp_pln = sizeof( ea->aarp_spu ); ea->aarp_op = htons( AARPOP_PROBE ); bcopy((caddr_t)ac->ac_enaddr, (caddr_t)ea->aarp_sha, sizeof( ea->aarp_sha )); eh = (struct ether_header *)sa.sa_data; if ( aa->aa_flags & AFA_PHASE2 ) { bcopy((caddr_t)atmulticastaddr, (caddr_t)eh->ether_dhost, sizeof( eh->ether_dhost )); eh->ether_type = htons( sizeof( struct llc ) + sizeof( struct ether_aarp )); M_PREPEND( m, sizeof( struct llc ), M_WAIT ); llc = mtod( m, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; bcopy( aarp_org_code, llc->llc_org_code, sizeof( aarp_org_code )); llc->llc_ether_type = htons( ETHERTYPE_AARP ); bcopy( &AA_SAT( aa )->sat_addr.s_net, ea->aarp_spnet, sizeof( ea->aarp_spnet )); bcopy( &AA_SAT( aa )->sat_addr.s_net, ea->aarp_tpnet, sizeof( ea->aarp_tpnet )); ea->aarp_spnode = ea->aarp_tpnode = AA_SAT( aa )->sat_addr.s_node; } else { bcopy((caddr_t)etherbroadcastaddr, (caddr_t)eh->ether_dhost, sizeof( eh->ether_dhost )); eh->ether_type = htons( ETHERTYPE_AARP ); ea->aarp_spa = ea->aarp_tpa = AA_SAT( aa )->sat_addr.s_node; } #ifdef NETATALKDEBUG printf("aarp: sending probe for %u.%u\n", ntohs(AA_SAT( aa )->sat_addr.s_net), AA_SAT( aa )->sat_addr.s_node); #endif NETATALKDEBUG sa.sa_len = sizeof( struct sockaddr ); sa.sa_family = AF_UNSPEC; (*ac->ac_if.if_output)(&ac->ac_if, m, &sa, NULL); /* XXX */ aa->aa_probcnt--; } void aarp_clean(void) { struct aarptab *aat; int i; untimeout( aarptimer, 0 ); for ( i = 0, aat = aarptab; i < AARPTAB_SIZE; i++, aat++ ) { if ( aat->aat_hold ) { m_freem( aat->aat_hold ); } } } diff --git a/sys/netatalk/at_control.c b/sys/netatalk/at_control.c index 65608b72815d..4d8f8faf3b9e 100644 --- a/sys/netatalk/at_control.c +++ b/sys/netatalk/at_control.c @@ -1,854 +1,835 @@ /* * Copyright (c) 1990,1991 Regents of The University of Michigan. * All Rights Reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #undef s_net #include #include #include #include #include #include static int aa_dorangeroute(struct ifaddr *ifa, u_int first, u_int last, int cmd); static int aa_addsingleroute(struct ifaddr *ifa, struct at_addr *addr, struct at_addr *mask); static int aa_delsingleroute(struct ifaddr *ifa, struct at_addr *addr, struct at_addr *mask); static int aa_dosingleroute(struct ifaddr *ifa, struct at_addr *addr, struct at_addr *mask, int cmd, int flags); static int at_scrub( struct ifnet *ifp, struct at_ifaddr *aa ); static int at_ifinit( struct ifnet *ifp, struct at_ifaddr *aa, struct sockaddr_at *sat ); # define sateqaddr(a,b) ((a)->sat_len == (b)->sat_len && \ (a)->sat_family == (b)->sat_family && \ (a)->sat_addr.s_net == (b)->sat_addr.s_net && \ (a)->sat_addr.s_node == (b)->sat_addr.s_node ) int at_control( int cmd, caddr_t data, struct ifnet *ifp, struct proc *p ) { struct ifreq *ifr = (struct ifreq *)data; struct sockaddr_at *sat; struct netrange *nr; struct at_aliasreq *ifra = (struct at_aliasreq *)data; struct at_ifaddr *aa0; struct at_ifaddr *aa = 0; struct ifaddr *ifa, *ifa0; /* * If we have an ifp, then find the matching at_ifaddr if it exists */ if ( ifp ) { for ( aa = at_ifaddr; aa; aa = aa->aa_next ) { if ( aa->aa_ifp == ifp ) break; } } /* * In this first switch table we are basically getting ready for * the second one, by getting the atalk-specific things set up * so that they start to look more similar to other protocols etc. */ switch ( cmd ) { case SIOCAIFADDR: case SIOCDIFADDR: /* * If we have an appletalk sockaddr, scan forward of where * we are now on the at_ifaddr list to find one with a matching * address on this interface. * This may leave aa pointing to the first address on the * NEXT interface! */ if ( ifra->ifra_addr.sat_family == AF_APPLETALK ) { for ( ; aa; aa = aa->aa_next ) { if ( aa->aa_ifp == ifp && sateqaddr( &aa->aa_addr, &ifra->ifra_addr )) { break; } } } /* * If we a retrying to delete an addres but didn't find such, * then rewurn with an error */ if ( cmd == SIOCDIFADDR && aa == 0 ) { return( EADDRNOTAVAIL ); } /*FALLTHROUGH*/ case SIOCSIFADDR: /* * If we are not superuser, then we don't get to do these ops. */ if ( suser(p->p_ucred, &p->p_acflag) ) { return( EPERM ); } sat = satosat( &ifr->ifr_addr ); nr = (struct netrange *)sat->sat_zero; if ( nr->nr_phase == 1 ) { /* * Look for a phase 1 address on this interface. * This may leave aa pointing to the first address on the * NEXT interface! */ for ( ; aa; aa = aa->aa_next ) { if ( aa->aa_ifp == ifp && ( aa->aa_flags & AFA_PHASE2 ) == 0 ) { break; } } } else { /* default to phase 2 */ /* * Look for a phase 2 address on this interface. * This may leave aa pointing to the first address on the * NEXT interface! */ for ( ; aa; aa = aa->aa_next ) { if ( aa->aa_ifp == ifp && ( aa->aa_flags & AFA_PHASE2 )) { break; } } } if ( ifp == 0 ) panic( "at_control" ); /* * If we failed to find an existing at_ifaddr entry, then we * allocate a fresh one. * XXX change this to use malloc */ if ( aa == (struct at_ifaddr *) 0 ) { aa0 = malloc(sizeof(struct at_ifaddr), M_IFADDR, M_WAITOK); bzero(aa0, sizeof(struct at_ifaddr)); if (( aa = at_ifaddr ) != NULL ) { /* * Don't let the loopback be first, since the first * address is the machine's default address for * binding. * If it is, stick ourself in front, otherwise * go to the back of the list. */ if ( at_ifaddr->aa_ifp->if_flags & IFF_LOOPBACK ) { aa = aa0; aa->aa_next = at_ifaddr; at_ifaddr = aa; } else { for ( ; aa->aa_next; aa = aa->aa_next ) ; aa->aa_next = aa0; } } else { at_ifaddr = aa0; } /* * Don't Add a reference for the aa itself! * I fell into this trap. IFAFREE tests for <=0 * not <= 1 like RTFREE */ /* aa->aa_ifa.ifa_refcnt++; DON'T DO THIS!! */ aa = aa0; /* * Find the end of the interface's addresses * and link our new one on the end */ - if (( ifa = ifp->if_addrlist ) != NULL ) { - for ( ; ifa->ifa_next; ifa = ifa->ifa_next ) - ; - ifa->ifa_next = (struct ifaddr *)aa; - } else { - ifp->if_addrlist = (struct ifaddr *)aa; - } + ifa = (struct ifaddr *)aa; + TAILQ_INSERT_TAIL(&ifp->if_addrhead, ifa, ifa_link); + /* * Add a reference for the linking into the ifp_if_addrlist. */ - aa->aa_ifa.ifa_refcnt++; + ifa->ifa_refcnt++; /* * As the at_ifaddr contains the actual sockaddrs, * and the ifaddr itself, link them al together correctly. */ - aa->aa_ifa.ifa_addr = (struct sockaddr *)&aa->aa_addr; - aa->aa_ifa.ifa_dstaddr = (struct sockaddr *)&aa->aa_addr; - aa->aa_ifa.ifa_netmask = (struct sockaddr *)&aa->aa_netmask; + ifa->ifa_addr = (struct sockaddr *)&aa->aa_addr; + ifa->ifa_dstaddr = (struct sockaddr *)&aa->aa_addr; + ifa->ifa_netmask = (struct sockaddr *)&aa->aa_netmask; /* * Set/clear the phase 2 bit. */ if ( nr->nr_phase == 1 ) { aa->aa_flags &= ~AFA_PHASE2; } else { aa->aa_flags |= AFA_PHASE2; } /* * and link it all together */ aa->aa_ifp = ifp; } else { /* * If we DID find one then we clobber any routes dependent on it.. */ at_scrub( ifp, aa ); } break; case SIOCGIFADDR : sat = satosat( &ifr->ifr_addr ); nr = (struct netrange *)sat->sat_zero; if ( nr->nr_phase == 1 ) { /* * If the request is specifying phase 1, then * only look at a phase one address */ for ( ; aa; aa = aa->aa_next ) { if ( aa->aa_ifp == ifp && ( aa->aa_flags & AFA_PHASE2 ) == 0 ) { break; } } } else { /* * default to phase 2 */ for ( ; aa; aa = aa->aa_next ) { if ( aa->aa_ifp == ifp && ( aa->aa_flags & AFA_PHASE2 )) { break; } } } if ( aa == (struct at_ifaddr *) 0 ) return( EADDRNOTAVAIL ); break; } /* * By the time this switch is run we should be able to assume that * the "aa" pointer is valid when needed. */ switch ( cmd ) { case SIOCGIFADDR: /* * copy the contents of the sockaddr blindly. */ sat = (struct sockaddr_at *)&ifr->ifr_addr; *sat = aa->aa_addr; /* * and do some cleanups */ ((struct netrange *)&sat->sat_zero)->nr_phase = (aa->aa_flags & AFA_PHASE2) ? 2 : 1; ((struct netrange *)&sat->sat_zero)->nr_firstnet = aa->aa_firstnet; ((struct netrange *)&sat->sat_zero)->nr_lastnet = aa->aa_lastnet; break; case SIOCSIFADDR: return( at_ifinit( ifp, aa, (struct sockaddr_at *)&ifr->ifr_addr )); case SIOCAIFADDR: if ( sateqaddr( &ifra->ifra_addr, &aa->aa_addr )) { return( 0 ); } return( at_ifinit( ifp, aa, (struct sockaddr_at *)&ifr->ifr_addr )); case SIOCDIFADDR: /* * scrub all routes.. didn't we just DO this? XXX yes, del it */ at_scrub( ifp, aa ); /* * remove the ifaddr from the interface */ ifa0 = (struct ifaddr *)aa; - if (( ifa = ifp->if_addrlist ) == ifa0 ) { - ifp->if_addrlist = ifa->ifa_next; - } else { - while ( ifa->ifa_next && ( ifa->ifa_next != ifa0 )) { - ifa = ifa->ifa_next; - } + TAILQ_REMOVE(&ifp->if_addrhead, ifa0, ifa_link); - /* - * if we found it, remove it, otherwise we screwed up. - * decrement the reference count by one. - */ - if ( ifa->ifa_next ) { - ifa = ifa->ifa_next = ifa0->ifa_next; - } else { - panic( "at_control" ); - } - } /* * refs goes from 1->0 if no external refs. note.. * This will not free it ... looks for -1. */ IFAFREE(ifa0); /* * Now remove the at_ifaddr from the parallel structure * as well, or we'd be in deep trouble */ aa0 = aa; if ( aa0 == ( aa = at_ifaddr )) { at_ifaddr = aa->aa_next; } else { while ( aa->aa_next && ( aa->aa_next != aa0 )) { aa = aa->aa_next; } /* * if we found it, remove it, otherwise we screwed up. */ if ( aa->aa_next ) { aa->aa_next = aa0->aa_next; } else { panic( "at_control" ); } } /* * Now dump the memory we were using. * Decrement the reference count. * This should probably be the last reference * as the count will go from 0 to -1. * (unless there is still a route referencing this) */ IFAFREE(ifa0); break; default: if ( ifp == 0 || ifp->if_ioctl == 0 ) return( EOPNOTSUPP ); return( (*ifp->if_ioctl)( ifp, cmd, data )); } return( 0 ); } /* * Given an interface and an at_ifaddr (supposedly on that interface) * remove any routes that depend on this. * Why ifp is needed I'm not sure, * as aa->at_ifaddr.ifa_ifp should be the same. */ static int at_scrub( ifp, aa ) struct ifnet *ifp; struct at_ifaddr *aa; { int error; if ( aa->aa_flags & AFA_ROUTE ) { if (ifp->if_flags & IFF_LOOPBACK) { if (error = aa_delsingleroute(&aa->aa_ifa, &aa->aa_addr.sat_addr, &aa->aa_netmask.sat_addr)) { return( error ); } } else if (ifp->if_flags & IFF_POINTOPOINT) { if ((error = rtinit( &aa->aa_ifa, RTM_DELETE, RTF_HOST)) != 0) return( error ); } else if (ifp->if_flags & IFF_BROADCAST) { error = aa_dorangeroute(&aa->aa_ifa, ntohs(aa->aa_firstnet), ntohs(aa->aa_lastnet), RTM_DELETE ); } aa->aa_ifa.ifa_flags &= ~IFA_ROUTE; aa->aa_flags &= ~AFA_ROUTE; } return( 0 ); } /* * given an at_ifaddr,a sockaddr_at and an ifp, * bang them all together at high speed and see what happens */ static int at_ifinit( ifp, aa, sat ) struct ifnet *ifp; struct at_ifaddr *aa; struct sockaddr_at *sat; { struct netrange nr, onr; struct sockaddr_at oldaddr; int s = splimp(), error = 0, i, j; int netinc, nodeinc, nnets; u_short net; /* * save the old addresses in the at_ifaddr just in case we need them. */ oldaddr = aa->aa_addr; onr.nr_firstnet = aa->aa_firstnet; onr.nr_lastnet = aa->aa_lastnet; /* * take the address supplied as an argument, and add it to the * at_ifnet (also given). Remember ing to update * those parts of the at_ifaddr that need special processing */ bzero( AA_SAT( aa ), sizeof( struct sockaddr_at )); bcopy( sat->sat_zero, &nr, sizeof( struct netrange )); bcopy( sat->sat_zero, AA_SAT( aa )->sat_zero, sizeof( struct netrange )); nnets = ntohs( nr.nr_lastnet ) - ntohs( nr.nr_firstnet ) + 1; aa->aa_firstnet = nr.nr_firstnet; aa->aa_lastnet = nr.nr_lastnet; /* XXX ALC */ #if 0 printf("at_ifinit: %s: %u.%u range %u-%u phase %d\n", ifp->if_name, ntohs(sat->sat_addr.s_net), sat->sat_addr.s_node, ntohs(aa->aa_firstnet), ntohs(aa->aa_lastnet), (aa->aa_flags & AFA_PHASE2) ? 2 : 1); #endif /* * We could eliminate the need for a second phase 1 probe (post * autoconf) if we check whether we're resetting the node. Note * that phase 1 probes use only nodes, not net.node pairs. Under * phase 2, both the net and node must be the same. */ if ( ifp->if_flags & IFF_LOOPBACK ) { AA_SAT( aa )->sat_len = sat->sat_len; AA_SAT( aa )->sat_family = AF_APPLETALK; AA_SAT( aa )->sat_addr.s_net = sat->sat_addr.s_net; AA_SAT( aa )->sat_addr.s_node = sat->sat_addr.s_node; #if 0 } else if ( fp->if_flags & IFF_POINTOPOINT) { /* unimplemented */ /* * we'd have to copy the dstaddr field over from the sat * but it's not clear that it would contain the right info.. */ #endif } else { /* * We are a normal (probably ethernet) interface. * apply the new address to the interface structures etc. * We will probe this address on the net first, before * applying it to ensure that it is free.. If it is not, then * we will try a number of other randomly generated addresses * in this net and then increment the net. etc.etc. until * we find an unused address. */ aa->aa_flags |= AFA_PROBING; /* if not loopback we Must probe? */ AA_SAT( aa )->sat_len = sizeof(struct sockaddr_at); AA_SAT( aa )->sat_family = AF_APPLETALK; if ( aa->aa_flags & AFA_PHASE2 ) { if ( sat->sat_addr.s_net == ATADDR_ANYNET ) { /* * If we are phase 2, and the net was not specified * then we select a random net within the supplied netrange. * XXX use /dev/random? */ if ( nnets != 1 ) { net = ntohs( nr.nr_firstnet ) + time.tv_sec % ( nnets - 1 ); } else { net = ntohs( nr.nr_firstnet ); } } else { /* * if a net was supplied, then check that it is within * the netrange. If it is not then replace the old values * and return an error */ if ( ntohs( sat->sat_addr.s_net ) < ntohs( nr.nr_firstnet ) || ntohs( sat->sat_addr.s_net ) > ntohs( nr.nr_lastnet )) { aa->aa_addr = oldaddr; aa->aa_firstnet = onr.nr_firstnet; aa->aa_lastnet = onr.nr_lastnet; splx(s); return( EINVAL ); } /* * otherwise just use the new net number.. */ net = ntohs( sat->sat_addr.s_net ); } } else { /* * we must be phase one, so just use whatever we were given. * I guess it really isn't going to be used... RIGHT? */ net = ntohs( sat->sat_addr.s_net ); } /* * set the node part of the address into the ifaddr. * If it's not specified, be random about it... * XXX use /dev/random? */ if ( sat->sat_addr.s_node == ATADDR_ANYNODE ) { AA_SAT( aa )->sat_addr.s_node = time.tv_sec; } else { AA_SAT( aa )->sat_addr.s_node = sat->sat_addr.s_node; } /* * step through the nets in the range * starting at the (possibly random) start point. */ for ( i = nnets, netinc = 1; i > 0; net = ntohs( nr.nr_firstnet ) + (( net - ntohs( nr.nr_firstnet ) + netinc ) % nnets ), i-- ) { AA_SAT( aa )->sat_addr.s_net = htons( net ); /* * using a rather strange stepping method, * stagger through the possible node addresses * Once again, starting at the (possibly random) * initial node address. */ for ( j = 0, nodeinc = time.tv_sec | 1; j < 256; j++, AA_SAT( aa )->sat_addr.s_node += nodeinc ) { if ( AA_SAT( aa )->sat_addr.s_node > 253 || AA_SAT( aa )->sat_addr.s_node < 1 ) { continue; } aa->aa_probcnt = 10; /* * start off the probes as an asynchronous activity. * though why wait 200mSec? */ timeout( (timeout_func_t)aarpprobe, (caddr_t)ifp, hz / 5 ); if ( tsleep( aa, PPAUSE|PCATCH, "at_ifinit", 0 )) { /* * theoretically we shouldn't time out here * so if we returned with an error.. */ printf( "at_ifinit: why did this happen?!\n" ); aa->aa_addr = oldaddr; aa->aa_firstnet = onr.nr_firstnet; aa->aa_lastnet = onr.nr_lastnet; splx( s ); return( EINTR ); } /* * The async activity should have woken us up. * We need to see if it was successful in finding * a free spot, or if we need to iterate to the next * address to try. */ if (( aa->aa_flags & AFA_PROBING ) == 0 ) { break; } } /* * of course we need to break out through two loops... */ if (( aa->aa_flags & AFA_PROBING ) == 0 ) { break; } /* reset node for next network */ AA_SAT( aa )->sat_addr.s_node = time.tv_sec; } /* * if we are still trying to probe, then we have finished all * the possible addresses, so we need to give up */ if ( aa->aa_flags & AFA_PROBING ) { aa->aa_addr = oldaddr; aa->aa_firstnet = onr.nr_firstnet; aa->aa_lastnet = onr.nr_lastnet; splx( s ); return( EADDRINUSE ); } } /* * Now that we have selected an address, we need to tell the interface * about it, just in case it needs to adjust something. */ if ( ifp->if_ioctl && ( error = (*ifp->if_ioctl)( ifp, SIOCSIFADDR, (caddr_t)aa ))) { /* * of course this could mean that it objects violently * so if it does, we back out again.. */ aa->aa_addr = oldaddr; aa->aa_firstnet = onr.nr_firstnet; aa->aa_lastnet = onr.nr_lastnet; splx( s ); return( error ); } /* * set up the netmask part of the at_ifaddr * and point the appropriate pointer in the ifaddr to it. * probably pointless, but what the heck.. XXX */ bzero(&aa->aa_netmask, sizeof(aa->aa_netmask)); aa->aa_netmask.sat_len = sizeof(struct sockaddr_at); aa->aa_netmask.sat_family = AF_APPLETALK; aa->aa_netmask.sat_addr.s_net = 0xffff; aa->aa_netmask.sat_addr.s_node = 0; aa->aa_ifa.ifa_netmask =(struct sockaddr *) &(aa->aa_netmask); /* XXX */ /* * Initialize broadcast (or remote p2p) address */ bzero(&aa->aa_broadaddr, sizeof(aa->aa_broadaddr)); aa->aa_broadaddr.sat_len = sizeof(struct sockaddr_at); aa->aa_broadaddr.sat_family = AF_APPLETALK; aa->aa_ifa.ifa_metric = ifp->if_metric; if (ifp->if_flags & IFF_BROADCAST) { aa->aa_broadaddr.sat_addr.s_net = htons(0); aa->aa_broadaddr.sat_addr.s_node = 0xff; aa->aa_ifa.ifa_broadaddr = (struct sockaddr *) &aa->aa_broadaddr; /* add the range of routes needed */ error = aa_dorangeroute(&aa->aa_ifa, ntohs(aa->aa_firstnet), ntohs(aa->aa_lastnet), RTM_ADD ); } else if (ifp->if_flags & IFF_POINTOPOINT) { struct at_addr rtaddr, rtmask; bzero(&rtaddr, sizeof(rtaddr)); bzero(&rtmask, sizeof(rtmask)); /* fill in the far end if we know it here XXX */ aa->aa_ifa.ifa_dstaddr = (struct sockaddr *) &aa->aa_dstaddr; error = aa_addsingleroute(&aa->aa_ifa, &rtaddr, &rtmask); } else if ( ifp->if_flags & IFF_LOOPBACK ) { struct at_addr rtaddr, rtmask; bzero(&rtaddr, sizeof(rtaddr)); bzero(&rtmask, sizeof(rtmask)); rtaddr.s_net = AA_SAT( aa )->sat_addr.s_net; rtaddr.s_node = AA_SAT( aa )->sat_addr.s_node; rtmask.s_net = 0xffff; rtmask.s_node = 0x0; /* XXX should not be so.. should be HOST route */ error = aa_addsingleroute(&aa->aa_ifa, &rtaddr, &rtmask); } /* * of course if we can't add these routes we back out, but it's getting * risky by now XXX */ if ( error ) { at_scrub( ifp, aa ); aa->aa_addr = oldaddr; aa->aa_firstnet = onr.nr_firstnet; aa->aa_lastnet = onr.nr_lastnet; splx( s ); return( error ); } /* * note that the address has a route associated with it.... */ aa->aa_ifa.ifa_flags |= IFA_ROUTE; aa->aa_flags |= AFA_ROUTE; splx( s ); return( 0 ); } /* * check whether a given address is a broadcast address for us.. */ int at_broadcast( sat ) struct sockaddr_at *sat; { struct at_ifaddr *aa; /* * If the node is not right, it can't be a broadcast */ if ( sat->sat_addr.s_node != ATADDR_BCAST ) { return( 0 ); } /* * If the node was right then if the net is right, it's a broadcast */ if ( sat->sat_addr.s_net == ATADDR_ANYNET ) { return( 1 ); } /* * failing that, if the net is one we have, it's a broadcast as well. */ for ( aa = at_ifaddr; aa; aa = aa->aa_next ) { if (( aa->aa_ifp->if_flags & IFF_BROADCAST ) && ( ntohs( sat->sat_addr.s_net ) >= ntohs( aa->aa_firstnet ) && ntohs( sat->sat_addr.s_net ) <= ntohs( aa->aa_lastnet ))) { return( 1 ); } } return( 0 ); } /* * aa_dorangeroute() * * Add a route for a range of networks from bot to top - 1. * Algorithm: * * Split the range into two subranges such that the middle * of the two ranges is the point where the highest bit of difference * between the two addresses, makes it's transition * Each of the upper and lower ranges might not exist, or might be * representable by 1 or more netmasks. In addition, if both * ranges can be represented by the same netmask, then teh can be merged * by using the next higher netmask.. */ static int aa_dorangeroute(struct ifaddr *ifa, u_int bot, u_int top, int cmd) { u_int mask1; struct at_addr addr; struct at_addr mask; int error; /* * slight sanity check */ if (bot > top) return (EINVAL); addr.s_node = 0; mask.s_node = 0; /* * just start out with the lowest boundary * and keep extending the mask till it's too big. */ while (bot <= top) { mask1 = 1; while ((( bot & ~mask1) >= bot) && (( bot | mask1) <= top)) { mask1 <<= 1; mask1 |= 1; } mask1 >>= 1; mask.s_net = htons(~mask1); addr.s_net = htons(bot); if(cmd == RTM_ADD) { error = aa_addsingleroute(ifa,&addr,&mask); if (error) { /* XXX clean up? */ return (error); } } else { error = aa_delsingleroute(ifa,&addr,&mask); } bot = (bot | mask1) + 1; } return 0; } static int aa_addsingleroute(struct ifaddr *ifa, struct at_addr *addr, struct at_addr *mask) { int error; #if 0 printf("aa_addsingleroute: %x.%x mask %x.%x ...\n", ntohs(addr->s_net), addr->s_node, ntohs(mask->s_net), mask->s_node); #endif error = aa_dosingleroute(ifa, addr, mask, RTM_ADD, RTF_UP); if (error) printf("aa_addsingleroute: error %d\n", error); return(error); } static int aa_delsingleroute(struct ifaddr *ifa, struct at_addr *addr, struct at_addr *mask) { int error; error = aa_dosingleroute(ifa, addr, mask, RTM_DELETE, 0); if (error) printf("aa_delsingleroute: error %d\n", error); return(error); } static int aa_dosingleroute(struct ifaddr *ifa, struct at_addr *at_addr, struct at_addr *at_mask, int cmd, int flags) { struct sockaddr_at addr, mask; bzero(&addr, sizeof(addr)); bzero(&mask, sizeof(mask)); addr.sat_family = AF_APPLETALK; addr.sat_len = sizeof(struct sockaddr_at); addr.sat_addr.s_net = at_addr->s_net; addr.sat_addr.s_node = at_addr->s_node; mask.sat_family = AF_APPLETALK; mask.sat_len = sizeof(struct sockaddr_at); mask.sat_addr.s_net = at_mask->s_net; mask.sat_addr.s_node = at_mask->s_node; if (at_mask->s_node) flags |= RTF_HOST; return(rtrequest(cmd, (struct sockaddr *) &addr, (flags & RTF_HOST)?(ifa->ifa_dstaddr):(ifa->ifa_addr), (struct sockaddr *) &mask, flags, NULL)); } #if 0 static void aa_clean(void) { struct at_ifaddr *aa; struct ifaddr *ifa; struct ifnet *ifp; while ( aa = at_ifaddr ) { ifp = aa->aa_ifp; at_scrub( ifp, aa ); at_ifaddr = aa->aa_next; if (( ifa = ifp->if_addrlist ) == (struct ifaddr *)aa ) { ifp->if_addrlist = ifa->ifa_next; } else { while ( ifa->ifa_next && ( ifa->ifa_next != (struct ifaddr *)aa )) { ifa = ifa->ifa_next; } if ( ifa->ifa_next ) { ifa->ifa_next = ((struct ifaddr *)aa)->ifa_next; } else { panic( "at_entry" ); } } } } #endif diff --git a/sys/netatalk/at_extern.h b/sys/netatalk/at_extern.h index 18badf4fa21b..bed1022c4a1d 100644 --- a/sys/netatalk/at_extern.h +++ b/sys/netatalk/at_extern.h @@ -1,41 +1,41 @@ #ifdef _NETINET_IF_ETHER_H_ extern void aarpprobe __P((struct arpcom *)); extern int aarpresolve __P((struct arpcom *, struct mbuf *, struct sockaddr_at *, u_char *)); extern void aarpinput __P(( struct arpcom *, struct mbuf *)); extern int at_broadcast __P((struct sockaddr_at *)); #endif #ifdef _NETATALK_AARP_H_ extern void aarptfree __P((struct aarptab *)); #endif extern void aarp_clean __P((void)); extern int at_control __P(( int cmd, caddr_t data, struct ifnet *ifp, struct proc *p )); extern u_short at_cksum __P(( struct mbuf *m, int skip)); extern int ddp_usrreq __P(( struct socket *so, int req, struct mbuf *m, struct mbuf *addr, struct mbuf *rights)); extern void ddp_init __P((void )); extern struct ifaddr *at_ifawithnet __P((struct sockaddr_at *, - struct ifaddr *)); + struct ifaddrhead *)); #ifdef _NETATALK_DDP_VAR_H_ extern int ddp_output __P(( struct ddpcb *ddp, struct mbuf *m)); #endif #if defined (_NETATALK_DDP_VAR_H_) && defined(_NETATALK_AT_VAR_H_) extern struct ddpcb *ddp_search __P((struct sockaddr_at *, struct sockaddr_at *, struct at_ifaddr *)); #endif #ifdef _NET_ROUTE_H_ int ddp_route( struct mbuf *m, struct route *ro); #endif diff --git a/sys/netinet/in.c b/sys/netinet/in.c index 878f3bb8614d..4e1df85c2aed 100644 --- a/sys/netinet/in.c +++ b/sys/netinet/in.c @@ -1,728 +1,700 @@ /* * Copyright (c) 1982, 1986, 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)in.c 8.4 (Berkeley) 1/9/95 - * $Id: in.c,v 1.24 1996/04/07 06:59:52 davidg Exp $ + * $Id: in.c,v 1.25 1996/09/09 20:17:24 wollman Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * This structure is used to keep track of in_multi chains which belong to * deleted interface addresses. */ static LIST_HEAD(, multi_kludge) in_mk; /* XXX BSS initialization */ struct multi_kludge { LIST_ENTRY(multi_kludge) mk_entry; struct ifnet *mk_ifp; struct in_multihead mk_head; }; static void in_socktrim __P((struct sockaddr_in *)); static int in_ifinit __P((struct ifnet *, struct in_ifaddr *, struct sockaddr_in *, int)); static void in_ifscrub __P((struct ifnet *, struct in_ifaddr *)); static int subnetsarelocal = 0; SYSCTL_INT(_net_inet_ip, OID_AUTO, subnets_are_local, CTLFLAG_RW, &subnetsarelocal, 0, ""); /* * Return 1 if an internet address is for a ``local'' host * (one to which we have a connection). If subnetsarelocal * is true, this includes other subnets of the local net. * Otherwise, it includes only the directly-connected (sub)nets. */ int in_localaddr(in) struct in_addr in; { register u_long i = ntohl(in.s_addr); register struct in_ifaddr *ia; if (subnetsarelocal) { - for (ia = in_ifaddr; ia; ia = ia->ia_next) + for (ia = in_ifaddrhead.tqh_first; ia; + ia = ia->ia_link.tqe_next) if ((i & ia->ia_netmask) == ia->ia_net) return (1); } else { - for (ia = in_ifaddr; ia; ia = ia->ia_next) + for (ia = in_ifaddrhead.tqh_first; ia; + ia = ia->ia_link.tqe_next) if ((i & ia->ia_subnetmask) == ia->ia_subnet) return (1); } return (0); } /* * Determine whether an IP address is in a reserved set of addresses * that may not be forwarded, or whether datagrams to that destination * may be forwarded. */ int in_canforward(in) struct in_addr in; { register u_long i = ntohl(in.s_addr); register u_long net; if (IN_EXPERIMENTAL(i) || IN_MULTICAST(i)) return (0); if (IN_CLASSA(i)) { net = i & IN_CLASSA_NET; if (net == 0 || net == (IN_LOOPBACKNET << IN_CLASSA_NSHIFT)) return (0); } return (1); } /* * Trim a mask in a sockaddr */ static void in_socktrim(ap) struct sockaddr_in *ap; { register char *cplim = (char *) &ap->sin_addr; register char *cp = (char *) (&ap->sin_addr + 1); ap->sin_len = 0; while (--cp >= cplim) if (*cp) { (ap)->sin_len = cp - (char *) (ap) + 1; break; } } static int in_interfaces; /* number of external internet interfaces */ /* * Generic internet control operations (ioctl's). * Ifp is 0 if not an interface-specific ioctl. */ /* ARGSUSED */ int in_control(so, cmd, data, ifp) struct socket *so; u_long cmd; caddr_t data; register struct ifnet *ifp; { register struct ifreq *ifr = (struct ifreq *)data; register struct in_ifaddr *ia = 0, *iap; register struct ifaddr *ifa; struct in_ifaddr *oia; struct in_aliasreq *ifra = (struct in_aliasreq *)data; struct sockaddr_in oldaddr; int error, hostIsNew, maskIsNew, s; u_long i; struct multi_kludge *mk; /* * Find address for this interface, if it exists. * * If an alias address was specified, find that one instead of * the first one on the interface. */ if (ifp) - for (iap = in_ifaddr; iap; iap = iap->ia_next) + for (iap = in_ifaddrhead.tqh_first; iap; + iap = iap->ia_link.tqe_next) if (iap->ia_ifp == ifp) { if (((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr.s_addr == iap->ia_addr.sin_addr.s_addr) { ia = iap; break; } else if (ia == NULL) { ia = iap; if (ifr->ifr_addr.sa_family != AF_INET) break; } } switch (cmd) { case SIOCAIFADDR: case SIOCDIFADDR: if (ifra->ifra_addr.sin_family == AF_INET) { for (oia = ia; ia; ia = ia->ia_next) { if (ia->ia_ifp == ifp && ia->ia_addr.sin_addr.s_addr == ifra->ifra_addr.sin_addr.s_addr) break; } if ((ifp->if_flags & IFF_POINTOPOINT) && (cmd == SIOCAIFADDR) && (ifra->ifra_dstaddr.sin_addr.s_addr == INADDR_ANY)) { return EDESTADDRREQ; } } if (cmd == SIOCDIFADDR && ia == 0) return (EADDRNOTAVAIL); /* FALLTHROUGH */ case SIOCSIFADDR: case SIOCSIFNETMASK: case SIOCSIFDSTADDR: if ((so->so_state & SS_PRIV) == 0) return (EPERM); if (ifp == 0) panic("in_control"); if (ia == (struct in_ifaddr *)0) { - oia = (struct in_ifaddr *) - malloc(sizeof *oia, M_IFADDR, M_WAITOK); - if (oia == (struct in_ifaddr *)NULL) + ia = (struct in_ifaddr *) + malloc(sizeof *ia, M_IFADDR, M_WAITOK); + if (ia == (struct in_ifaddr *)NULL) return (ENOBUFS); - bzero((caddr_t)oia, sizeof *oia); - ia = in_ifaddr; + bzero((caddr_t)ia, sizeof *ia); /* * Protect from ipintr() traversing address list * while we're modifying it. */ s = splnet(); - - if (ia) { - for ( ; ia->ia_next; ia = ia->ia_next) - continue; - ia->ia_next = oia; - } else - in_ifaddr = oia; - ia = oia; - ifa = ifp->if_addrlist; - if (ifa) { - for ( ; ifa->ifa_next; ifa = ifa->ifa_next) - continue; - ifa->ifa_next = (struct ifaddr *) ia; - } else - ifp->if_addrlist = (struct ifaddr *) ia; - ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; - ia->ia_ifa.ifa_dstaddr - = (struct sockaddr *)&ia->ia_dstaddr; - ia->ia_ifa.ifa_netmask - = (struct sockaddr *)&ia->ia_sockmask; + + TAILQ_INSERT_TAIL(&in_ifaddrhead, ia, ia_link); + ifa = &ia->ia_ifa; + TAILQ_INSERT_TAIL(&ifp->if_addrhead, ifa, ifa_link); + + ifa->ifa_addr = (struct sockaddr *)&ia->ia_addr; + ifa->ifa_dstaddr = (struct sockaddr *)&ia->ia_dstaddr; + ifa->ifa_netmask = (struct sockaddr *)&ia->ia_sockmask; ia->ia_sockmask.sin_len = 8; if (ifp->if_flags & IFF_BROADCAST) { ia->ia_broadaddr.sin_len = sizeof(ia->ia_addr); ia->ia_broadaddr.sin_family = AF_INET; } ia->ia_ifp = ifp; if (!(ifp->if_flags & IFF_LOOPBACK)) in_interfaces++; splx(s); } break; case SIOCSIFBRDADDR: if ((so->so_state & SS_PRIV) == 0) return (EPERM); /* FALLTHROUGH */ case SIOCGIFADDR: case SIOCGIFNETMASK: case SIOCGIFDSTADDR: case SIOCGIFBRDADDR: if (ia == (struct in_ifaddr *)0) return (EADDRNOTAVAIL); break; } switch (cmd) { case SIOCGIFADDR: *((struct sockaddr_in *)&ifr->ifr_addr) = ia->ia_addr; break; case SIOCGIFBRDADDR: if ((ifp->if_flags & IFF_BROADCAST) == 0) return (EINVAL); *((struct sockaddr_in *)&ifr->ifr_dstaddr) = ia->ia_broadaddr; break; case SIOCGIFDSTADDR: if ((ifp->if_flags & IFF_POINTOPOINT) == 0) return (EINVAL); *((struct sockaddr_in *)&ifr->ifr_dstaddr) = ia->ia_dstaddr; break; case SIOCGIFNETMASK: *((struct sockaddr_in *)&ifr->ifr_addr) = ia->ia_sockmask; break; case SIOCSIFDSTADDR: if ((ifp->if_flags & IFF_POINTOPOINT) == 0) return (EINVAL); oldaddr = ia->ia_dstaddr; ia->ia_dstaddr = *(struct sockaddr_in *)&ifr->ifr_dstaddr; if (ifp->if_ioctl && (error = (*ifp->if_ioctl) (ifp, SIOCSIFDSTADDR, (caddr_t)ia))) { ia->ia_dstaddr = oldaddr; return (error); } if (ia->ia_flags & IFA_ROUTE) { ia->ia_ifa.ifa_dstaddr = (struct sockaddr *)&oldaddr; rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST); ia->ia_ifa.ifa_dstaddr = (struct sockaddr *)&ia->ia_dstaddr; rtinit(&(ia->ia_ifa), (int)RTM_ADD, RTF_HOST|RTF_UP); } break; case SIOCSIFBRDADDR: if ((ifp->if_flags & IFF_BROADCAST) == 0) return (EINVAL); ia->ia_broadaddr = *(struct sockaddr_in *)&ifr->ifr_broadaddr; break; case SIOCSIFADDR: return (in_ifinit(ifp, ia, (struct sockaddr_in *) &ifr->ifr_addr, 1)); case SIOCSIFNETMASK: i = ifra->ifra_addr.sin_addr.s_addr; ia->ia_subnetmask = ntohl(ia->ia_sockmask.sin_addr.s_addr = i); break; case SIOCAIFADDR: maskIsNew = 0; hostIsNew = 1; error = 0; if (ia->ia_addr.sin_family == AF_INET) { if (ifra->ifra_addr.sin_len == 0) { ifra->ifra_addr = ia->ia_addr; hostIsNew = 0; } else if (ifra->ifra_addr.sin_addr.s_addr == ia->ia_addr.sin_addr.s_addr) hostIsNew = 0; } if (ifra->ifra_mask.sin_len) { in_ifscrub(ifp, ia); ia->ia_sockmask = ifra->ifra_mask; ia->ia_subnetmask = ntohl(ia->ia_sockmask.sin_addr.s_addr); maskIsNew = 1; } if ((ifp->if_flags & IFF_POINTOPOINT) && (ifra->ifra_dstaddr.sin_family == AF_INET)) { in_ifscrub(ifp, ia); ia->ia_dstaddr = ifra->ifra_dstaddr; maskIsNew = 1; /* We lie; but the effect's the same */ } if (ifra->ifra_addr.sin_family == AF_INET && (hostIsNew || maskIsNew)) error = in_ifinit(ifp, ia, &ifra->ifra_addr, 0); if ((ifp->if_flags & IFF_BROADCAST) && (ifra->ifra_broadaddr.sin_family == AF_INET)) ia->ia_broadaddr = ifra->ifra_broadaddr; return (error); case SIOCDIFADDR: mk = malloc(sizeof *mk, M_IPMADDR, M_WAITOK); if (!mk) return ENOBUFS; in_ifscrub(ifp, ia); /* * Protect from ipintr() traversing address list * while we're modifying it. */ s = splnet(); - if ((ifa = ifp->if_addrlist) == (struct ifaddr *)ia) - ifp->if_addrlist = ifa->ifa_next; - else { - while (ifa->ifa_next && - (ifa->ifa_next != (struct ifaddr *)ia)) - ifa = ifa->ifa_next; - if (ifa->ifa_next) - ifa->ifa_next = ((struct ifaddr *)ia)->ifa_next; - else - printf("Couldn't unlink inifaddr from ifp\n"); - } + ifa = &ia->ia_ifa; + TAILQ_REMOVE(&ifp->if_addrhead, ifa, ifa_link); oia = ia; - if (oia == (ia = in_ifaddr)) - in_ifaddr = ia->ia_next; - else { - while (ia->ia_next && (ia->ia_next != oia)) - ia = ia->ia_next; - if (ia->ia_next) - ia->ia_next = oia->ia_next; - else - printf("Didn't unlink inifadr from list\n"); - } - + TAILQ_REMOVE(&in_ifaddrhead, oia, ia_link); if (!oia->ia_multiaddrs.lh_first) { IFAFREE(&oia->ia_ifa); FREE(mk, M_IPMADDR); splx(s); break; } /* * Multicast address kludge: * If there were any multicast addresses attached to this * interface address, either move them to another address * on this interface, or save them until such time as this * interface is reconfigured for IP. */ IFP_TO_IA(oia->ia_ifp, ia); if (ia) { /* there is another address */ struct in_multi *inm; for(inm = oia->ia_multiaddrs.lh_first; inm; inm = inm->inm_entry.le_next) { IFAFREE(&inm->inm_ia->ia_ifa); ia->ia_ifa.ifa_refcnt++; inm->inm_ia = ia; LIST_INSERT_HEAD(&ia->ia_multiaddrs, inm, inm_entry); } FREE(mk, M_IPMADDR); } else { /* last address on this if deleted, save */ struct in_multi *inm; LIST_INIT(&mk->mk_head); mk->mk_ifp = ifp; for(inm = oia->ia_multiaddrs.lh_first; inm; inm = inm->inm_entry.le_next) { LIST_INSERT_HEAD(&mk->mk_head, inm, inm_entry); } if (mk->mk_head.lh_first) { LIST_INSERT_HEAD(&in_mk, mk, mk_entry); } else { FREE(mk, M_IPMADDR); } } IFAFREE((&oia->ia_ifa)); splx(s); break; default: if (ifp == 0 || ifp->if_ioctl == 0) return (EOPNOTSUPP); return ((*ifp->if_ioctl)(ifp, cmd, data)); } return (0); } /* * Delete any existing route for an interface. */ static void in_ifscrub(ifp, ia) register struct ifnet *ifp; register struct in_ifaddr *ia; { if ((ia->ia_flags & IFA_ROUTE) == 0) return; if (ifp->if_flags & (IFF_LOOPBACK|IFF_POINTOPOINT)) rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST); else rtinit(&(ia->ia_ifa), (int)RTM_DELETE, 0); ia->ia_flags &= ~IFA_ROUTE; } /* * Initialize an interface's internet address * and routing table entry. */ static int in_ifinit(ifp, ia, sin, scrub) register struct ifnet *ifp; register struct in_ifaddr *ia; struct sockaddr_in *sin; int scrub; { register u_long i = ntohl(sin->sin_addr.s_addr); struct sockaddr_in oldaddr; int s = splimp(), flags = RTF_UP, error; struct multi_kludge *mk; oldaddr = ia->ia_addr; ia->ia_addr = *sin; /* * Give the interface a chance to initialize * if this is its first address, * and to validate the address if necessary. */ if (ifp->if_ioctl && (error = (*ifp->if_ioctl)(ifp, SIOCSIFADDR, (caddr_t)ia))) { splx(s); ia->ia_addr = oldaddr; return (error); } splx(s); if (scrub) { ia->ia_ifa.ifa_addr = (struct sockaddr *)&oldaddr; in_ifscrub(ifp, ia); ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; } if (IN_CLASSA(i)) ia->ia_netmask = IN_CLASSA_NET; else if (IN_CLASSB(i)) ia->ia_netmask = IN_CLASSB_NET; else ia->ia_netmask = IN_CLASSC_NET; /* * The subnet mask usually includes at least the standard network part, * but may may be smaller in the case of supernetting. * If it is set, we believe it. */ if (ia->ia_subnetmask == 0) { ia->ia_subnetmask = ia->ia_netmask; ia->ia_sockmask.sin_addr.s_addr = htonl(ia->ia_subnetmask); } else ia->ia_netmask &= ia->ia_subnetmask; ia->ia_net = i & ia->ia_netmask; ia->ia_subnet = i & ia->ia_subnetmask; in_socktrim(&ia->ia_sockmask); /* * Add route for the network. */ ia->ia_ifa.ifa_metric = ifp->if_metric; if (ifp->if_flags & IFF_BROADCAST) { ia->ia_broadaddr.sin_addr.s_addr = htonl(ia->ia_subnet | ~ia->ia_subnetmask); ia->ia_netbroadcast.s_addr = htonl(ia->ia_net | ~ ia->ia_netmask); } else if (ifp->if_flags & IFF_LOOPBACK) { ia->ia_ifa.ifa_dstaddr = ia->ia_ifa.ifa_addr; flags |= RTF_HOST; } else if (ifp->if_flags & IFF_POINTOPOINT) { if (ia->ia_dstaddr.sin_family != AF_INET) return (0); flags |= RTF_HOST; } if ((error = rtinit(&(ia->ia_ifa), (int)RTM_ADD, flags)) == 0) ia->ia_flags |= IFA_ROUTE; LIST_INIT(&ia->ia_multiaddrs); /* * If the interface supports multicast, join the "all hosts" * multicast group on that interface. */ if (ifp->if_flags & IFF_MULTICAST) { struct in_addr addr; /* * Continuation of multicast address hack: * If there was a multicast group list previously saved * for this interface, then we re-attach it to the first * address configured on the i/f. */ for(mk = in_mk.lh_first; mk; mk = mk->mk_entry.le_next) { if(mk->mk_ifp == ifp) { struct in_multi *inm; for(inm = mk->mk_head.lh_first; inm; inm = inm->inm_entry.le_next) { IFAFREE(&inm->inm_ia->ia_ifa); ia->ia_ifa.ifa_refcnt++; inm->inm_ia = ia; LIST_INSERT_HEAD(&ia->ia_multiaddrs, inm, inm_entry); } LIST_REMOVE(mk, mk_entry); free(mk, M_IPMADDR); break; } } addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); in_addmulti(&addr, ifp); } return (error); } /* * Return 1 if the address might be a local broadcast address. */ int in_broadcast(in, ifp) struct in_addr in; struct ifnet *ifp; { register struct ifaddr *ifa; u_long t; if (in.s_addr == INADDR_BROADCAST || in.s_addr == INADDR_ANY) return 1; if ((ifp->if_flags & IFF_BROADCAST) == 0) return 0; t = ntohl(in.s_addr); /* * Look through the list of addresses for a match * with a broadcast address. */ #define ia ((struct in_ifaddr *)ifa) - for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) + for (ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) if (ifa->ifa_addr->sa_family == AF_INET && (in.s_addr == ia->ia_broadaddr.sin_addr.s_addr || in.s_addr == ia->ia_netbroadcast.s_addr || /* * Check for old-style (host 0) broadcast. */ t == ia->ia_subnet || t == ia->ia_net) && /* * Check for an all one subnetmask. These * only exist when an interface gets a secondary * address. */ ia->ia_subnetmask != (u_long)0xffffffff) return 1; return (0); #undef ia } /* * Add an address to the list of IP multicast addresses for a given interface. */ struct in_multi * in_addmulti(ap, ifp) register struct in_addr *ap; register struct ifnet *ifp; { register struct in_multi *inm; struct ifreq ifr; struct in_ifaddr *ia; int s = splnet(); /* * See if address already in list. */ IN_LOOKUP_MULTI(*ap, ifp, inm); if (inm != NULL) { /* * Found it; just increment the reference count. */ ++inm->inm_refcount; } else { /* * New address; allocate a new multicast record * and link it into the interface's multicast list. */ inm = (struct in_multi *)malloc(sizeof(*inm), M_IPMADDR, M_NOWAIT); if (inm == NULL) { splx(s); return (NULL); } inm->inm_addr = *ap; inm->inm_ifp = ifp; inm->inm_refcount = 1; IFP_TO_IA(ifp, ia); if (ia == NULL) { free(inm, M_IPMADDR); splx(s); return (NULL); } inm->inm_ia = ia; ia->ia_ifa.ifa_refcnt++; /* gain a reference */ LIST_INSERT_HEAD(&ia->ia_multiaddrs, inm, inm_entry); /* * Ask the network driver to update its multicast reception * filter appropriately for the new address. */ ((struct sockaddr_in *)&ifr.ifr_addr)->sin_family = AF_INET; ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr = *ap; if ((ifp->if_ioctl == NULL) || (*ifp->if_ioctl)(ifp, SIOCADDMULTI,(caddr_t)&ifr) != 0) { LIST_REMOVE(inm, inm_entry); IFAFREE(&ia->ia_ifa); /* release reference */ free(inm, M_IPMADDR); splx(s); return (NULL); } /* * Let IGMP know that we have joined a new IP multicast group. */ igmp_joingroup(inm); } splx(s); return (inm); } /* * Delete a multicast address record. */ void in_delmulti(inm) register struct in_multi *inm; { struct ifreq ifr; int s = splnet(); if (--inm->inm_refcount == 0) { /* * No remaining claims to this record; let IGMP know that * we are leaving the multicast group. */ igmp_leavegroup(inm); /* * Unlink from list. */ LIST_REMOVE(inm, inm_entry); IFAFREE(&inm->inm_ia->ia_ifa); /* release reference */ /* * Notify the network driver to update its multicast reception * filter. */ ((struct sockaddr_in *)&(ifr.ifr_addr))->sin_family = AF_INET; ((struct sockaddr_in *)&(ifr.ifr_addr))->sin_addr = inm->inm_addr; (*inm->inm_ifp->if_ioctl)(inm->inm_ifp, SIOCDELMULTI, (caddr_t)&ifr); free(inm, M_IPMADDR); } splx(s); } diff --git a/sys/netinet/in_pcb.c b/sys/netinet/in_pcb.c index e5da5fc54c29..31fceab40258 100644 --- a/sys/netinet/in_pcb.c +++ b/sys/netinet/in_pcb.c @@ -1,748 +1,749 @@ /* * Copyright (c) 1982, 1986, 1991, 1993, 1995 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)in_pcb.c 8.4 (Berkeley) 5/24/95 - * $Id: in_pcb.c,v 1.22 1996/10/07 19:06:07 davidg Exp $ + * $Id: in_pcb.c,v 1.23 1996/10/30 06:13:09 peter Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct in_addr zeroin_addr; static void in_pcbinshash __P((struct inpcb *)); static void in_rtchange __P((struct inpcb *, int)); /* * These configure the range of local port addresses assigned to * "unspecified" outgoing connections/packets/whatever. */ static int ipport_lowfirstauto = IPPORT_RESERVED - 1; /* 1023 */ static int ipport_lowlastauto = IPPORT_RESERVEDSTART; /* 600 */ static int ipport_firstauto = IPPORT_RESERVED; /* 1024 */ static int ipport_lastauto = IPPORT_USERRESERVED; /* 5000 */ static int ipport_hifirstauto = IPPORT_HIFIRSTAUTO; /* 40000 */ static int ipport_hilastauto = IPPORT_HILASTAUTO; /* 44999 */ #define RANGECHK(var, min, max) \ if ((var) < (min)) { (var) = (min); } \ else if ((var) > (max)) { (var) = (max); } static int sysctl_net_ipport_check SYSCTL_HANDLER_ARGS { int error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req); if (!error) { RANGECHK(ipport_lowfirstauto, 1, IPPORT_RESERVED - 1); RANGECHK(ipport_lowlastauto, 1, IPPORT_RESERVED - 1); RANGECHK(ipport_firstauto, IPPORT_RESERVED, USHRT_MAX); RANGECHK(ipport_lastauto, IPPORT_RESERVED, USHRT_MAX); RANGECHK(ipport_hifirstauto, IPPORT_RESERVED, USHRT_MAX); RANGECHK(ipport_hilastauto, IPPORT_RESERVED, USHRT_MAX); } return error; } #undef RANGECHK SYSCTL_NODE(_net_inet_ip, IPPROTO_IP, portrange, CTLFLAG_RW, 0, "IP Ports"); SYSCTL_PROC(_net_inet_ip_portrange, OID_AUTO, lowfirst, CTLTYPE_INT|CTLFLAG_RW, &ipport_lowfirstauto, 0, &sysctl_net_ipport_check, "I", ""); SYSCTL_PROC(_net_inet_ip_portrange, OID_AUTO, lowlast, CTLTYPE_INT|CTLFLAG_RW, &ipport_lowlastauto, 0, &sysctl_net_ipport_check, "I", ""); SYSCTL_PROC(_net_inet_ip_portrange, OID_AUTO, first, CTLTYPE_INT|CTLFLAG_RW, &ipport_firstauto, 0, &sysctl_net_ipport_check, "I", ""); SYSCTL_PROC(_net_inet_ip_portrange, OID_AUTO, last, CTLTYPE_INT|CTLFLAG_RW, &ipport_lastauto, 0, &sysctl_net_ipport_check, "I", ""); SYSCTL_PROC(_net_inet_ip_portrange, OID_AUTO, hifirst, CTLTYPE_INT|CTLFLAG_RW, &ipport_hifirstauto, 0, &sysctl_net_ipport_check, "I", ""); SYSCTL_PROC(_net_inet_ip_portrange, OID_AUTO, hilast, CTLTYPE_INT|CTLFLAG_RW, &ipport_hilastauto, 0, &sysctl_net_ipport_check, "I", ""); int in_pcballoc(so, pcbinfo) struct socket *so; struct inpcbinfo *pcbinfo; { register struct inpcb *inp; int s; MALLOC(inp, struct inpcb *, sizeof(*inp), M_PCB, M_NOWAIT); if (inp == NULL) return (ENOBUFS); bzero((caddr_t)inp, sizeof(*inp)); inp->inp_pcbinfo = pcbinfo; inp->inp_socket = so; s = splnet(); LIST_INSERT_HEAD(pcbinfo->listhead, inp, inp_list); in_pcbinshash(inp); splx(s); so->so_pcb = (caddr_t)inp; return (0); } int in_pcbbind(inp, nam) register struct inpcb *inp; struct mbuf *nam; { register struct socket *so = inp->inp_socket; unsigned short *lastport; struct sockaddr_in *sin; struct proc *p = curproc; /* XXX */ u_short lport = 0; int wild = 0, reuseport = (so->so_options & SO_REUSEPORT); int error; - if (in_ifaddr == 0) + if (TAILQ_EMPTY(&in_ifaddrhead)) /* XXX broken! */ return (EADDRNOTAVAIL); if (inp->inp_lport || inp->inp_laddr.s_addr != INADDR_ANY) return (EINVAL); if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0 && ((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0 || (so->so_options & SO_ACCEPTCONN) == 0)) wild = 1; if (nam) { sin = mtod(nam, struct sockaddr_in *); if (nam->m_len != sizeof (*sin)) return (EINVAL); #ifdef notdef /* * We should check the family, but old programs * incorrectly fail to initialize it. */ if (sin->sin_family != AF_INET) return (EAFNOSUPPORT); #endif lport = sin->sin_port; if (IN_MULTICAST(ntohl(sin->sin_addr.s_addr))) { /* * Treat SO_REUSEADDR as SO_REUSEPORT for multicast; * allow complete duplication of binding if * SO_REUSEPORT is set, or if SO_REUSEADDR is set * and a multicast address is bound on both * new and duplicated sockets. */ if (so->so_options & SO_REUSEADDR) reuseport = SO_REUSEADDR|SO_REUSEPORT; } else if (sin->sin_addr.s_addr != INADDR_ANY) { sin->sin_port = 0; /* yech... */ if (ifa_ifwithaddr((struct sockaddr *)sin) == 0) return (EADDRNOTAVAIL); } if (lport) { struct inpcb *t; /* GROSS */ if (ntohs(lport) < IPPORT_RESERVED && (error = suser(p->p_ucred, &p->p_acflag))) return (EACCES); t = in_pcblookup(inp->inp_pcbinfo, zeroin_addr, 0, sin->sin_addr, lport, wild); if (t && (reuseport & t->inp_socket->so_options) == 0) return (EADDRINUSE); } inp->inp_laddr = sin->sin_addr; } if (lport == 0) { ushort first, last; int count; inp->inp_flags |= INP_ANONPORT; if (inp->inp_flags & INP_HIGHPORT) { first = ipport_hifirstauto; /* sysctl */ last = ipport_hilastauto; lastport = &inp->inp_pcbinfo->lasthi; } else if (inp->inp_flags & INP_LOWPORT) { if (error = suser(p->p_ucred, &p->p_acflag)) return (EACCES); first = ipport_lowfirstauto; /* 1023 */ last = ipport_lowlastauto; /* 600 */ lastport = &inp->inp_pcbinfo->lastlow; } else { first = ipport_firstauto; /* sysctl */ last = ipport_lastauto; lastport = &inp->inp_pcbinfo->lastport; } /* * Simple check to ensure all ports are not used up causing * a deadlock here. * * We split the two cases (up and down) so that the direction * is not being tested on each round of the loop. */ if (first > last) { /* * counting down */ count = first - last; do { if (count-- <= 0) /* completely used? */ return (EADDRNOTAVAIL); --*lastport; if (*lastport > first || *lastport < last) *lastport = first; lport = htons(*lastport); } while (in_pcblookup(inp->inp_pcbinfo, zeroin_addr, 0, inp->inp_laddr, lport, wild)); } else { /* * counting up */ count = last - first; do { if (count-- <= 0) /* completely used? */ return (EADDRNOTAVAIL); ++*lastport; if (*lastport < first || *lastport > last) *lastport = first; lport = htons(*lastport); } while (in_pcblookup(inp->inp_pcbinfo, zeroin_addr, 0, inp->inp_laddr, lport, wild)); } } inp->inp_lport = lport; in_pcbrehash(inp); return (0); } /* * Transform old in_pcbconnect() into an inner subroutine for new * in_pcbconnect(): Do some validity-checking on the remote * address (in mbuf 'nam') and then determine local host address * (i.e., which interface) to use to access that remote host. * * This preserves definition of in_pcbconnect(), while supporting a * slightly different version for T/TCP. (This is more than * a bit of a kludge, but cleaning up the internal interfaces would * have forced minor changes in every protocol). */ int in_pcbladdr(inp, nam, plocal_sin) register struct inpcb *inp; struct mbuf *nam; struct sockaddr_in **plocal_sin; { struct in_ifaddr *ia; register struct sockaddr_in *sin = mtod(nam, struct sockaddr_in *); if (nam->m_len != sizeof (*sin)) return (EINVAL); if (sin->sin_family != AF_INET) return (EAFNOSUPPORT); if (sin->sin_port == 0) return (EADDRNOTAVAIL); - if (in_ifaddr) { + if (!TAILQ_EMPTY(&in_ifaddrhead)) { /* * If the destination address is INADDR_ANY, * use the primary local address. * If the supplied address is INADDR_BROADCAST, * and the primary interface supports broadcast, * choose the broadcast address for that interface. */ #define satosin(sa) ((struct sockaddr_in *)(sa)) #define sintosa(sin) ((struct sockaddr *)(sin)) #define ifatoia(ifa) ((struct in_ifaddr *)(ifa)) if (sin->sin_addr.s_addr == INADDR_ANY) - sin->sin_addr = IA_SIN(in_ifaddr)->sin_addr; + sin->sin_addr = IA_SIN(in_ifaddrhead.tqh_first)->sin_addr; else if (sin->sin_addr.s_addr == (u_long)INADDR_BROADCAST && - (in_ifaddr->ia_ifp->if_flags & IFF_BROADCAST)) - sin->sin_addr = satosin(&in_ifaddr->ia_broadaddr)->sin_addr; + (in_ifaddrhead.tqh_first->ia_ifp->if_flags & IFF_BROADCAST)) + sin->sin_addr = satosin(&in_ifaddrhead.tqh_first->ia_broadaddr)->sin_addr; } if (inp->inp_laddr.s_addr == INADDR_ANY) { register struct route *ro; ia = (struct in_ifaddr *)0; /* * If route is known or can be allocated now, * our src addr is taken from the i/f, else punt. */ ro = &inp->inp_route; if (ro->ro_rt && (satosin(&ro->ro_dst)->sin_addr.s_addr != sin->sin_addr.s_addr || inp->inp_socket->so_options & SO_DONTROUTE)) { RTFREE(ro->ro_rt); ro->ro_rt = (struct rtentry *)0; } if ((inp->inp_socket->so_options & SO_DONTROUTE) == 0 && /*XXX*/ (ro->ro_rt == (struct rtentry *)0 || ro->ro_rt->rt_ifp == (struct ifnet *)0)) { /* No route yet, so try to acquire one */ ro->ro_dst.sa_family = AF_INET; ro->ro_dst.sa_len = sizeof(struct sockaddr_in); ((struct sockaddr_in *) &ro->ro_dst)->sin_addr = sin->sin_addr; rtalloc(ro); } /* * If we found a route, use the address * corresponding to the outgoing interface * unless it is the loopback (in case a route * to our address on another net goes to loopback). */ if (ro->ro_rt && !(ro->ro_rt->rt_ifp->if_flags & IFF_LOOPBACK)) ia = ifatoia(ro->ro_rt->rt_ifa); if (ia == 0) { u_short fport = sin->sin_port; sin->sin_port = 0; ia = ifatoia(ifa_ifwithdstaddr(sintosa(sin))); if (ia == 0) ia = ifatoia(ifa_ifwithnet(sintosa(sin))); sin->sin_port = fport; if (ia == 0) - ia = in_ifaddr; + ia = in_ifaddrhead.tqh_first; if (ia == 0) return (EADDRNOTAVAIL); } /* * If the destination address is multicast and an outgoing * interface has been set as a multicast option, use the * address of that interface as our source address. */ if (IN_MULTICAST(ntohl(sin->sin_addr.s_addr)) && inp->inp_moptions != NULL) { struct ip_moptions *imo; struct ifnet *ifp; imo = inp->inp_moptions; if (imo->imo_multicast_ifp != NULL) { ifp = imo->imo_multicast_ifp; - for (ia = in_ifaddr; ia; ia = ia->ia_next) + for (ia = in_ifaddrhead.tqh_first; ia; + ia = ia->ia_link.tqe_next) if (ia->ia_ifp == ifp) break; if (ia == 0) return (EADDRNOTAVAIL); } } /* * Don't do pcblookup call here; return interface in plocal_sin * and exit to caller, that will do the lookup. */ *plocal_sin = &ia->ia_addr; } return(0); } /* * Outer subroutine: * Connect from a socket to a specified address. * Both address and port must be specified in argument sin. * If don't have a local address for this socket yet, * then pick one. */ int in_pcbconnect(inp, nam) register struct inpcb *inp; struct mbuf *nam; { struct sockaddr_in *ifaddr; register struct sockaddr_in *sin = mtod(nam, struct sockaddr_in *); int error; /* * Call inner routine, to assign local interface address. */ if (error = in_pcbladdr(inp, nam, &ifaddr)) return(error); if (in_pcblookuphash(inp->inp_pcbinfo, sin->sin_addr, sin->sin_port, inp->inp_laddr.s_addr ? inp->inp_laddr : ifaddr->sin_addr, inp->inp_lport, 0) != NULL) return (EADDRINUSE); if (inp->inp_laddr.s_addr == INADDR_ANY) { if (inp->inp_lport == 0) (void)in_pcbbind(inp, (struct mbuf *)0); inp->inp_laddr = ifaddr->sin_addr; } inp->inp_faddr = sin->sin_addr; inp->inp_fport = sin->sin_port; in_pcbrehash(inp); return (0); } void in_pcbdisconnect(inp) struct inpcb *inp; { inp->inp_faddr.s_addr = INADDR_ANY; inp->inp_fport = 0; in_pcbrehash(inp); if (inp->inp_socket->so_state & SS_NOFDREF) in_pcbdetach(inp); } void in_pcbdetach(inp) struct inpcb *inp; { struct socket *so = inp->inp_socket; int s; so->so_pcb = 0; sofree(so); if (inp->inp_options) (void)m_free(inp->inp_options); if (inp->inp_route.ro_rt) rtfree(inp->inp_route.ro_rt); ip_freemoptions(inp->inp_moptions); s = splnet(); LIST_REMOVE(inp, inp_hash); LIST_REMOVE(inp, inp_list); splx(s); FREE(inp, M_PCB); } void in_setsockaddr(inp, nam) register struct inpcb *inp; struct mbuf *nam; { register struct sockaddr_in *sin; nam->m_len = sizeof (*sin); sin = mtod(nam, struct sockaddr_in *); bzero((caddr_t)sin, sizeof (*sin)); sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_port = inp->inp_lport; sin->sin_addr = inp->inp_laddr; } void in_setpeeraddr(inp, nam) struct inpcb *inp; struct mbuf *nam; { register struct sockaddr_in *sin; nam->m_len = sizeof (*sin); sin = mtod(nam, struct sockaddr_in *); bzero((caddr_t)sin, sizeof (*sin)); sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_port = inp->inp_fport; sin->sin_addr = inp->inp_faddr; } /* * Pass some notification to all connections of a protocol * associated with address dst. The local address and/or port numbers * may be specified to limit the search. The "usual action" will be * taken, depending on the ctlinput cmd. The caller must filter any * cmds that are uninteresting (e.g., no error in the map). * Call the protocol specific routine (if any) to report * any errors for each matching socket. * * Must be called at splnet. */ void in_pcbnotify(head, dst, fport_arg, laddr, lport_arg, cmd, notify) struct inpcbhead *head; struct sockaddr *dst; u_int fport_arg, lport_arg; struct in_addr laddr; int cmd; void (*notify) __P((struct inpcb *, int)); { register struct inpcb *inp, *oinp; struct in_addr faddr; u_short fport = fport_arg, lport = lport_arg; int errno, s; if ((unsigned)cmd > PRC_NCMDS || dst->sa_family != AF_INET) return; faddr = ((struct sockaddr_in *)dst)->sin_addr; if (faddr.s_addr == INADDR_ANY) return; /* * Redirects go to all references to the destination, * and use in_rtchange to invalidate the route cache. * Dead host indications: notify all references to the destination. * Otherwise, if we have knowledge of the local port and address, * deliver only to that socket. */ if (PRC_IS_REDIRECT(cmd) || cmd == PRC_HOSTDEAD) { fport = 0; lport = 0; laddr.s_addr = 0; if (cmd != PRC_HOSTDEAD) notify = in_rtchange; } errno = inetctlerrmap[cmd]; s = splnet(); for (inp = head->lh_first; inp != NULL;) { if (inp->inp_faddr.s_addr != faddr.s_addr || inp->inp_socket == 0 || (lport && inp->inp_lport != lport) || (laddr.s_addr && inp->inp_laddr.s_addr != laddr.s_addr) || (fport && inp->inp_fport != fport)) { inp = inp->inp_list.le_next; continue; } oinp = inp; inp = inp->inp_list.le_next; if (notify) (*notify)(oinp, errno); } splx(s); } /* * Check for alternatives when higher level complains * about service problems. For now, invalidate cached * routing information. If the route was created dynamically * (by a redirect), time to try a default gateway again. */ void in_losing(inp) struct inpcb *inp; { register struct rtentry *rt; struct rt_addrinfo info; if ((rt = inp->inp_route.ro_rt)) { inp->inp_route.ro_rt = 0; bzero((caddr_t)&info, sizeof(info)); info.rti_info[RTAX_DST] = (struct sockaddr *)&inp->inp_route.ro_dst; info.rti_info[RTAX_GATEWAY] = rt->rt_gateway; info.rti_info[RTAX_NETMASK] = rt_mask(rt); rt_missmsg(RTM_LOSING, &info, rt->rt_flags, 0); if (rt->rt_flags & RTF_DYNAMIC) (void) rtrequest(RTM_DELETE, rt_key(rt), rt->rt_gateway, rt_mask(rt), rt->rt_flags, (struct rtentry **)0); else /* * A new route can be allocated * the next time output is attempted. */ rtfree(rt); } } /* * After a routing change, flush old routing * and allocate a (hopefully) better one. */ static void in_rtchange(inp, errno) register struct inpcb *inp; int errno; { if (inp->inp_route.ro_rt) { rtfree(inp->inp_route.ro_rt); inp->inp_route.ro_rt = 0; /* * A new route can be allocated the next time * output is attempted. */ } } struct inpcb * in_pcblookup(pcbinfo, faddr, fport_arg, laddr, lport_arg, wild_okay) struct inpcbinfo *pcbinfo; struct in_addr faddr, laddr; u_int fport_arg, lport_arg; int wild_okay; { register struct inpcb *inp, *match = NULL; int matchwild = 3, wildcard; u_short fport = fport_arg, lport = lport_arg; int s; s = splnet(); for (inp = pcbinfo->listhead->lh_first; inp != NULL; inp = inp->inp_list.le_next) { if (inp->inp_lport != lport) continue; wildcard = 0; if (inp->inp_faddr.s_addr != INADDR_ANY) { if (faddr.s_addr == INADDR_ANY) wildcard++; else if (inp->inp_faddr.s_addr != faddr.s_addr || inp->inp_fport != fport) continue; } else { if (faddr.s_addr != INADDR_ANY) wildcard++; } if (inp->inp_laddr.s_addr != INADDR_ANY) { if (laddr.s_addr == INADDR_ANY) wildcard++; else if (inp->inp_laddr.s_addr != laddr.s_addr) continue; } else { if (laddr.s_addr != INADDR_ANY) wildcard++; } if (wildcard && wild_okay == 0) continue; if (wildcard < matchwild) { match = inp; matchwild = wildcard; if (matchwild == 0) { break; } } } splx(s); return (match); } /* * Lookup PCB in hash list. */ struct inpcb * in_pcblookuphash(pcbinfo, faddr, fport_arg, laddr, lport_arg, wildcard) struct inpcbinfo *pcbinfo; struct in_addr faddr, laddr; u_int fport_arg, lport_arg; int wildcard; { struct inpcbhead *head; register struct inpcb *inp; u_short fport = fport_arg, lport = lport_arg; int s; s = splnet(); /* * First look for an exact match. */ head = &pcbinfo->hashbase[(faddr.s_addr + lport + fport) % pcbinfo->hashsize]; for (inp = head->lh_first; inp != NULL; inp = inp->inp_hash.le_next) { if (inp->inp_faddr.s_addr == faddr.s_addr && inp->inp_fport == fport && inp->inp_lport == lport && inp->inp_laddr.s_addr == laddr.s_addr) goto found; } if (wildcard) { struct inpcb *local_wild = NULL; head = &pcbinfo->hashbase[(INADDR_ANY + lport) % pcbinfo->hashsize]; for (inp = head->lh_first; inp != NULL; inp = inp->inp_hash.le_next) { if (inp->inp_faddr.s_addr == INADDR_ANY && inp->inp_fport == 0 && inp->inp_lport == lport) { if (inp->inp_laddr.s_addr == laddr.s_addr) goto found; else if (inp->inp_laddr.s_addr == INADDR_ANY) local_wild = inp; } } if (local_wild != NULL) { inp = local_wild; goto found; } } splx(s); return (NULL); found: /* * Move PCB to head of this hash chain so that it can be * found more quickly in the future. * XXX - this is a pessimization on machines with few * concurrent connections. */ if (inp != head->lh_first) { LIST_REMOVE(inp, inp_hash); LIST_INSERT_HEAD(head, inp, inp_hash); } splx(s); return (inp); } /* * Insert PCB into hash chain. Must be called at splnet. */ static void in_pcbinshash(inp) struct inpcb *inp; { struct inpcbhead *head; head = &inp->inp_pcbinfo->hashbase[(inp->inp_faddr.s_addr + inp->inp_lport + inp->inp_fport) % inp->inp_pcbinfo->hashsize]; LIST_INSERT_HEAD(head, inp, inp_hash); } void in_pcbrehash(inp) struct inpcb *inp; { struct inpcbhead *head; int s; s = splnet(); LIST_REMOVE(inp, inp_hash); head = &inp->inp_pcbinfo->hashbase[(inp->inp_faddr.s_addr + inp->inp_lport + inp->inp_fport) % inp->inp_pcbinfo->hashsize]; LIST_INSERT_HEAD(head, inp, inp_hash); splx(s); } diff --git a/sys/netinet/in_var.h b/sys/netinet/in_var.h index c71c85a16756..f349a412365f 100644 --- a/sys/netinet/in_var.h +++ b/sys/netinet/in_var.h @@ -1,235 +1,236 @@ /* * Copyright (c) 1985, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)in_var.h 8.2 (Berkeley) 1/9/95 - * $Id: in_var.h,v 1.16 1996/02/05 20:35:59 wollman Exp $ + * $Id: in_var.h,v 1.17 1996/03/14 16:59:19 fenner Exp $ */ #ifndef _NETINET_IN_VAR_H_ #define _NETINET_IN_VAR_H_ #include /* * Interface address, Internet version. One of these structures * is allocated for each interface with an Internet address. * The ifaddr structure contains the protocol-independent part * of the structure and is assumed to be first. */ struct in_ifaddr { struct ifaddr ia_ifa; /* protocol-independent info */ #define ia_ifp ia_ifa.ifa_ifp #define ia_flags ia_ifa.ifa_flags /* ia_{,sub}net{,mask} in host order */ u_long ia_net; /* network number of interface */ u_long ia_netmask; /* mask of net part */ u_long ia_subnet; /* subnet number, including net */ u_long ia_subnetmask; /* mask of subnet part */ struct in_addr ia_netbroadcast; /* to recognize net broadcasts */ + TAILQ_ENTRY(in_ifaddr) ia_link; /* tailq macro glue */ struct in_ifaddr *ia_next; /* next in list of internet addresses */ struct sockaddr_in ia_addr; /* reserve space for interface name */ struct sockaddr_in ia_dstaddr; /* reserve space for broadcast addr */ #define ia_broadaddr ia_dstaddr struct sockaddr_in ia_sockmask; /* reserve space for general netmask */ LIST_HEAD(in_multihead, in_multi) ia_multiaddrs; /* list of multicast addresses */ }; struct in_aliasreq { char ifra_name[IFNAMSIZ]; /* if name, e.g. "en0" */ struct sockaddr_in ifra_addr; struct sockaddr_in ifra_broadaddr; #define ifra_dstaddr ifra_broadaddr struct sockaddr_in ifra_mask; }; /* * Given a pointer to an in_ifaddr (ifaddr), * return a pointer to the addr as a sockaddr_in. */ #define IA_SIN(ia) (&(((struct in_ifaddr *)(ia))->ia_addr)) #define IA_DSTSIN(ia) (&(((struct in_ifaddr *)(ia))->ia_dstaddr)) #define IN_LNAOF(in, ifa) \ ((ntohl((in).s_addr) & ~((struct in_ifaddr *)(ifa)->ia_subnetmask)) #ifdef KERNEL -extern struct in_ifaddr *in_ifaddr; +extern TAILQ_HEAD(in_ifaddrhead, in_ifaddr) in_ifaddrhead; extern struct ifqueue ipintrq; /* ip packet input queue */ extern struct in_addr zeroin_addr; extern u_char inetctlerrmap[]; extern int rtq_reallyold; /* XXX */ extern int rtq_minreallyold; /* XXX */ extern int rtq_toomany; /* XXX */ /* * Macro for finding the interface (ifnet structure) corresponding to one * of our IP addresses. */ #define INADDR_TO_IFP(addr, ifp) \ /* struct in_addr addr; */ \ /* struct ifnet *ifp; */ \ { \ register struct in_ifaddr *ia; \ \ - for (ia = in_ifaddr; \ + for (ia = in_ifaddrhead.tqh_first; \ ia != NULL && ((ia->ia_ifp->if_flags & IFF_POINTOPOINT)? \ IA_DSTSIN(ia):IA_SIN(ia))->sin_addr.s_addr != (addr).s_addr; \ - ia = ia->ia_next) \ + ia = ia->ia_link.tqe_next) \ continue; \ if (ia == NULL) \ - for (ia = in_ifaddr; \ + for (ia = in_ifaddrhead.tqh_first; \ ia != NULL; \ - ia = ia->ia_next) \ + ia = ia->ia_link.tqe_next) \ if (ia->ia_ifp->if_flags & IFF_POINTOPOINT && \ IA_SIN(ia)->sin_addr.s_addr == (addr).s_addr) \ break; \ (ifp) = (ia == NULL) ? NULL : ia->ia_ifp; \ } /* * Macro for finding the internet address structure (in_ifaddr) corresponding * to a given interface (ifnet structure). */ #define IFP_TO_IA(ifp, ia) \ /* struct ifnet *ifp; */ \ /* struct in_ifaddr *ia; */ \ { \ - for ((ia) = in_ifaddr; \ + for ((ia) = in_ifaddrhead.tqh_first; \ (ia) != NULL && (ia)->ia_ifp != (ifp); \ - (ia) = (ia)->ia_next) \ + (ia) = (ia)->ia_link.tqe_next) \ continue; \ } #endif /* * This information should be part of the ifnet structure but we don't wish * to change that - as it might break a number of things */ struct router_info { struct ifnet *rti_ifp; int rti_type; /* type of router which is querier on this interface */ int rti_time; /* # of slow timeouts since last old query */ struct router_info *rti_next; }; /* * Internet multicast address structure. There is one of these for each IP * multicast group to which this host belongs on a given network interface. * They are kept in a linked list, rooted in the interface's in_ifaddr * structure. */ struct in_multi { LIST_ENTRY(in_multi) inm_entry; /* list glue */ struct in_addr inm_addr; /* IP multicast address */ struct ifnet *inm_ifp; /* back pointer to ifnet */ struct in_ifaddr *inm_ia; /* back pointer to in_ifaddr */ u_int inm_refcount; /* no. membership claims by sockets */ u_int inm_timer; /* IGMP membership report timer */ u_int inm_state; /* state of the membership */ struct router_info *inm_rti; /* router info*/ }; #ifdef KERNEL /* * Structure used by macros below to remember position when stepping through * all of the in_multi records. */ struct in_multistep { struct in_ifaddr *i_ia; struct in_multi *i_inm; }; /* * Macro for looking up the in_multi record for a given IP multicast address * on a given interface. If no matching record is found, "inm" returns NULL. */ #define IN_LOOKUP_MULTI(addr, ifp, inm) \ /* struct in_addr addr; */ \ /* struct ifnet *ifp; */ \ /* struct in_multi *inm; */ \ -{ \ +do { \ register struct in_ifaddr *ia; \ \ IFP_TO_IA((ifp), ia); \ if (ia == NULL) \ (inm) = NULL; \ else \ for ((inm) = ia->ia_multiaddrs.lh_first; \ (inm) != NULL && (inm)->inm_addr.s_addr != (addr).s_addr; \ (inm) = inm->inm_entry.le_next) \ continue; \ -} +} while(0) /* * Macro to step through all of the in_multi records, one at a time. * The current position is remembered in "step", which the caller must * provide. IN_FIRST_MULTI(), below, must be called to initialize "step" * and get the first record. Both macros return a NULL "inm" when there * are no remaining records. */ #define IN_NEXT_MULTI(step, inm) \ /* struct in_multistep step; */ \ /* struct in_multi *inm; */ \ -{ \ +do { \ if (((inm) = (step).i_inm) != NULL) \ (step).i_inm = (inm)->inm_entry.le_next; \ else \ while ((step).i_ia != NULL) { \ (inm) = (step).i_ia->ia_multiaddrs.lh_first; \ - (step).i_ia = (step).i_ia->ia_next; \ + (step).i_ia = (step).i_ia->ia_link.tqe_next; \ if ((inm) != NULL) { \ (step).i_inm = (inm)->inm_entry.le_next; \ break; \ } \ } \ -} +} while(0) #define IN_FIRST_MULTI(step, inm) \ /* struct in_multistep step; */ \ /* struct in_multi *inm; */ \ -{ \ - (step).i_ia = in_ifaddr; \ +do { \ + (step).i_ia = in_ifaddrhead.tqh_first; \ (step).i_inm = NULL; \ IN_NEXT_MULTI((step), (inm)); \ -} +} while(0) struct in_multi *in_addmulti __P((struct in_addr *, struct ifnet *)); void in_delmulti __P((struct in_multi *)); int in_control __P((struct socket *, u_long, caddr_t, struct ifnet *)); void in_rtqdrain __P((void)); void ip_input __P((struct mbuf *)); #endif /* KERNEL */ #endif /* _NETINET_IN_VAR_H_ */ diff --git a/sys/netinet/ip_divert.c b/sys/netinet/ip_divert.c index 7720f8484268..4e3afa89266a 100644 --- a/sys/netinet/ip_divert.c +++ b/sys/netinet/ip_divert.c @@ -1,365 +1,365 @@ /* * Copyright (c) 1982, 1986, 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: ip_divert.c,v 1.2 1996/06/14 00:28:38 archie Exp $ + * $Id: ip_divert.c,v 1.1 1996/07/10 19:44:22 julian Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Divert sockets */ /* * Allocate enough space to hold a full IP packet */ #define DIVSNDQ (65536 + 100) #define DIVRCVQ (65536 + 100) /* Global variables */ /* * ip_input() and ip_output() set this secret value before calling us to * let us know which divert port to divert a packet to; this is done so * we can use the existing prototype for struct protosw's pr_input(). * This is stored in host order. */ u_short ip_divert_port; /* * We set this value to a non-zero port number when we want the call to * ip_fw_chk() in ip_input() or ip_output() to ignore ``divert '' * chain entries. This is stored in host order. */ u_short ip_divert_ignore; /* Internal variables */ static struct inpcbhead divcb; static struct inpcbinfo divcbinfo; static u_long div_sendspace = DIVSNDQ; /* XXX sysctl ? */ static u_long div_recvspace = DIVRCVQ; /* XXX sysctl ? */ /* Optimization: have this preinitialized */ static struct sockaddr_in divsrc = { sizeof(divsrc), AF_INET }; /* Internal functions */ static int div_output(struct socket *so, struct mbuf *m, struct mbuf *addr, struct mbuf *control); /* * Initialize divert connection block queue. */ void div_init(void) { LIST_INIT(&divcb); divcbinfo.listhead = &divcb; /* * XXX We don't use the hash list for divert IP, but it's easier * to allocate a one entry hash list than it is to check all * over the place for hashbase == NULL. */ divcbinfo.hashbase = phashinit(1, M_PCB, &divcbinfo.hashsize); } /* * Setup generic address and protocol structures * for div_input routine, then pass them along with * mbuf chain. ip->ip_len is assumed to have had * the header length (hlen) subtracted out already. * We tell whether the packet was incoming or outgoing * by seeing if hlen == 0, which is a hack. */ void div_input(struct mbuf *m, int hlen) { register struct ip *ip = mtod(m, struct ip *); register struct inpcb *inp; register struct socket *sa; /* Sanity check */ if (ip_divert_port == 0) panic("div_input"); /* Record divert port */ divsrc.sin_port = htons(ip_divert_port); /* Restore packet header fields */ ip->ip_len += hlen; HTONS(ip->ip_len); HTONS(ip->ip_off); /* Record receive interface address, if any */ divsrc.sin_addr.s_addr = 0; if (hlen) { struct ifaddr *ifa; /* More fields affected by ip_input() */ HTONS(ip->ip_id); /* Find IP address for recieve interface */ - for (ifa = m->m_pkthdr.rcvif->if_addrlist; - ifa != NULL; ifa = ifa->ifa_next) { + for (ifa = m->m_pkthdr.rcvif->if_addrhead.tqh_first; + ifa != NULL; ifa = ifa->ifa_link.tqe_next) { if (ifa->ifa_addr == NULL) continue; if (ifa->ifa_addr->sa_family != AF_INET) continue; divsrc.sin_addr = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr; break; } } /* Put packet on socket queue, if any */ sa = NULL; for (inp = divcb.lh_first; inp != NULL; inp = inp->inp_list.le_next) { if (inp->inp_lport == htons(ip_divert_port)) sa = inp->inp_socket; } if (sa) { if (sbappendaddr(&sa->so_rcv, (struct sockaddr *)&divsrc, m, (struct mbuf *)0) == 0) m_freem(m); else sorwakeup(sa); } else { m_freem(m); ipstat.ips_noproto++; ipstat.ips_delivered--; } } /* * Deliver packet back into the IP processing machinery. * * If no address specified, or address is 0.0.0.0, send to ip_output(); * otherwise, send to ip_input() and mark as having been received on * the interface with that address. * * If no address specified, or dest port is 0, allow packet to divert * back to this socket; otherwise, don't. */ static int div_output(so, m, addr, control) struct socket *so; register struct mbuf *m; struct mbuf *addr, *control; { register struct inpcb *const inp = sotoinpcb(so); register struct ip *const ip = mtod(m, struct ip *); struct sockaddr_in *sin = NULL; int error = 0; if (control) m_freem(control); /* XXX */ if (addr) sin = mtod(addr, struct sockaddr_in *); /* Loopback avoidance option */ if (sin && sin->sin_port) ip_divert_ignore = ntohs(inp->inp_lport); /* Reinject packet into the system as incoming or outgoing */ if (!sin || sin->sin_addr.s_addr == 0) { /* Don't allow both user specified and setsockopt options, and don't allow packet length sizes that will crash */ if (((ip->ip_hl != (sizeof (*ip) >> 2)) && inp->inp_options) || ((u_short)ntohs(ip->ip_len) > m->m_pkthdr.len)) { error = EINVAL; goto cantsend; } /* Convert fields to host order for ip_output() */ NTOHS(ip->ip_len); NTOHS(ip->ip_off); /* Send packet to output processing */ ipstat.ips_rawout++; /* XXX */ error = ip_output(m, inp->inp_options, &inp->inp_route, (so->so_options & SO_DONTROUTE) | IP_ALLOWBROADCAST | IP_RAWOUTPUT, inp->inp_moptions); } else { struct ifaddr *ifa; /* Find receive interface with the given IP address */ sin->sin_port = 0; if ((ifa = ifa_ifwithaddr((struct sockaddr *) sin)) == 0) { error = EADDRNOTAVAIL; goto cantsend; } m->m_pkthdr.rcvif = ifa->ifa_ifp; /* Send packet to input processing */ ip_input(m); } /* Reset for next time (and other packets) */ ip_divert_ignore = 0; return error; cantsend: ip_divert_ignore = 0; m_freem(m); return error; } /*ARGSUSED*/ int div_usrreq(so, req, m, nam, control) register struct socket *so; int req; struct mbuf *m, *nam, *control; { register int error = 0; register struct inpcb *inp = sotoinpcb(so); int s = 0; if (inp == NULL && req != PRU_ATTACH) { error = EINVAL; goto release; } switch (req) { case PRU_ATTACH: if (inp) panic("div_attach"); if ((so->so_state & SS_PRIV) == 0) { error = EACCES; break; } if ((error = soreserve(so, div_sendspace, div_recvspace)) || (error = in_pcballoc(so, &divcbinfo))) break; inp = (struct inpcb *)so->so_pcb; inp->inp_ip.ip_p = (int)nam; /* XXX */ inp->inp_flags |= INP_HDRINCL; /* The socket is always "connected" because we always know "where" to send the packet */ so->so_state |= SS_ISCONNECTED; break; case PRU_DISCONNECT: if ((so->so_state & SS_ISCONNECTED) == 0) { error = ENOTCONN; break; } /* FALLTHROUGH */ case PRU_ABORT: soisdisconnected(so); /* FALLTHROUGH */ case PRU_DETACH: if (inp == 0) panic("div_detach"); in_pcbdetach(inp); break; case PRU_BIND: s = splnet(); error = in_pcbbind(inp, nam); splx(s); break; /* * Mark the connection as being incapable of further input. */ case PRU_SHUTDOWN: socantsendmore(so); break; case PRU_SEND: /* Packet must have a header (but that's about it) */ if (m->m_len < sizeof (struct ip) || (m = m_pullup(m, sizeof (struct ip))) == 0) { ipstat.ips_toosmall++; error = EINVAL; break; } /* Send packet */ error = div_output(so, m, nam, control); m = NULL; break; case PRU_SOCKADDR: in_setsockaddr(inp, nam); break; case PRU_SENSE: /* * stat: don't bother with a blocksize. */ return (0); /* * Not supported. */ case PRU_CONNECT: case PRU_CONNECT2: case PRU_CONTROL: case PRU_RCVOOB: case PRU_RCVD: case PRU_LISTEN: case PRU_ACCEPT: case PRU_SENDOOB: case PRU_PEERADDR: error = EOPNOTSUPP; break; default: panic("div_usrreq"); } release: if (m) m_freem(m); return (error); } diff --git a/sys/netinet/ip_fw.c b/sys/netinet/ip_fw.c index 77739b443749..d85041fb84a9 100644 --- a/sys/netinet/ip_fw.c +++ b/sys/netinet/ip_fw.c @@ -1,825 +1,826 @@ /* * Copyright (c) 1996 Alex Nash * Copyright (c) 1993 Daniel Boulet * Copyright (c) 1994 Ugen J.S.Antsilevich * * Redistribution and use in source forms, with and without modification, * are permitted provided that this entire comment appears intact. * * Redistribution in binary form may occur without any restrictions. * Obviously, it would be nice if you gave credit where credit is due * but requiring it would be too onerous. * * This software is provided ``AS IS'' without any warranties of any kind. * - * $Id: ip_fw.c,v 1.50 1996/10/12 19:38:50 alex Exp $ + * $Id: ip_fw.c,v 1.51 1996/10/12 19:49:36 bde Exp $ */ /* * Implement IP packet firewall */ #ifndef IPFIREWALL_MODULE #include "opt_ipfw.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int fw_debug = 1; #ifdef IPFIREWALL_VERBOSE static int fw_verbose = 1; #else static int fw_verbose = 0; #endif #ifdef IPFIREWALL_VERBOSE_LIMIT static int fw_verbose_limit = IPFIREWALL_VERBOSE_LIMIT; #else static int fw_verbose_limit = 0; #endif LIST_HEAD (ip_fw_head, ip_fw_chain) ip_fw_chain; #ifdef SYSCTL_NODE SYSCTL_NODE(_net_inet_ip, OID_AUTO, fw, CTLFLAG_RW, 0, "Firewall"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, debug, CTLFLAG_RW, &fw_debug, 0, ""); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose, CTLFLAG_RW, &fw_verbose, 0, ""); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose_limit, CTLFLAG_RW, &fw_verbose_limit, 0, ""); #endif #define dprintf(a) if (!fw_debug); else printf a #define print_ip(a) printf("%ld.%ld.%ld.%ld",(ntohl(a.s_addr)>>24)&0xFF,\ (ntohl(a.s_addr)>>16)&0xFF,\ (ntohl(a.s_addr)>>8)&0xFF,\ (ntohl(a.s_addr))&0xFF); #define dprint_ip(a) if (!fw_debug); else print_ip(a) static int add_entry __P((struct ip_fw_head *chainptr, struct ip_fw *frwl)); static int del_entry __P((struct ip_fw_head *chainptr, struct ip_fw *frwl)); static int zero_entry __P((struct mbuf *m)); static struct ip_fw * check_ipfw_struct __P(( struct mbuf *m)); static int ipopts_match __P((struct ip *ip, struct ip_fw *f)); static int port_match __P((u_short *portptr, int nports, u_short port, int range_flag)); static int tcpflg_match __P((struct tcphdr *tcp, struct ip_fw *f)); static int icmptype_match __P((struct icmp * icmp, struct ip_fw * f)); static void ipfw_report __P((char *txt, int rule, struct ip *ip, int counter, struct ifnet *rif)); #ifdef IPFIREWALL_MODULE static ip_fw_chk_t *old_chk_ptr; static ip_fw_ctl_t *old_ctl_ptr; #endif static int ip_fw_chk __P((struct ip **pip, int hlen, struct ifnet *rif, int dirport, struct mbuf **m)); static int ip_fw_ctl __P((int stage, struct mbuf **mm)); static char err_prefix[] = "ip_fw_ctl:"; /* * Returns 1 if the port is matched by the vector, 0 otherwise */ static inline int port_match(u_short *portptr, int nports, u_short port, int range_flag) { if (!nports) return 1; if (range_flag) { if (portptr[0] <= port && port <= portptr[1]) { return 1; } nports -= 2; portptr += 2; } while (nports-- > 0) { if (*portptr++ == port) { return 1; } } return 0; } static int tcpflg_match(struct tcphdr *tcp, struct ip_fw *f) { u_char flg_set, flg_clr; if ((f->fw_tcpf & IP_FW_TCPF_ESTAB) && (tcp->th_flags & (IP_FW_TCPF_RST | IP_FW_TCPF_ACK))) return 1; flg_set = tcp->th_flags & f->fw_tcpf; flg_clr = tcp->th_flags & f->fw_tcpnf; if (flg_set != f->fw_tcpf) return 0; if (flg_clr) return 0; return 1; } static int icmptype_match(struct icmp *icmp, struct ip_fw *f) { int type; if (!(f->fw_flg & IP_FW_F_ICMPBIT)) return(1); type = icmp->icmp_type; /* check for matching type in the bitmap */ if (f->fw_icmptypes[type / (sizeof(unsigned) * 8)] & (1U << (type % (8 * sizeof(unsigned))))) return(1); return(0); /* no match */ } static int ipopts_match(struct ip *ip, struct ip_fw *f) { register u_char *cp; int opt, optlen, cnt; u_char opts, nopts, nopts_sve; cp = (u_char *)(ip + 1); cnt = (ip->ip_hl << 2) - sizeof (struct ip); opts = f->fw_ipopt; nopts = nopts_sve = f->fw_ipnopt; for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[IPOPT_OPTVAL]; if (opt == IPOPT_EOL) break; if (opt == IPOPT_NOP) optlen = 1; else { optlen = cp[IPOPT_OLEN]; if (optlen <= 0 || optlen > cnt) { return 0; /*XXX*/ } } switch (opt) { default: break; case IPOPT_LSRR: opts &= ~IP_FW_IPOPT_LSRR; nopts &= ~IP_FW_IPOPT_LSRR; break; case IPOPT_SSRR: opts &= ~IP_FW_IPOPT_SSRR; nopts &= ~IP_FW_IPOPT_SSRR; break; case IPOPT_RR: opts &= ~IP_FW_IPOPT_RR; nopts &= ~IP_FW_IPOPT_RR; break; case IPOPT_TS: opts &= ~IP_FW_IPOPT_TS; nopts &= ~IP_FW_IPOPT_TS; break; } if (opts == nopts) break; } if (opts == 0 && nopts == nopts_sve) return 1; else return 0; } static void ipfw_report(char *txt, int rule, struct ip *ip, int counter, struct ifnet *rif) { struct tcphdr *tcp = (struct tcphdr *) ((u_long *) ip + ip->ip_hl); struct udphdr *udp = (struct udphdr *) ((u_long *) ip + ip->ip_hl); struct icmp *icmp = (struct icmp *) ((u_long *) ip + ip->ip_hl); if (!fw_verbose) return; if (fw_verbose_limit != 0 && counter > fw_verbose_limit) return; printf("ipfw: %d %s ",rule, txt); switch (ip->ip_p) { case IPPROTO_TCP: printf("TCP "); print_ip(ip->ip_src); printf(":%d ", ntohs(tcp->th_sport)); print_ip(ip->ip_dst); printf(":%d", ntohs(tcp->th_dport)); break; case IPPROTO_UDP: printf("UDP "); print_ip(ip->ip_src); printf(":%d ", ntohs(udp->uh_sport)); print_ip(ip->ip_dst); printf(":%d", ntohs(udp->uh_dport)); break; case IPPROTO_ICMP: printf("ICMP:%u.%u ", icmp->icmp_type, icmp->icmp_code); print_ip(ip->ip_src); printf(" "); print_ip(ip->ip_dst); break; default: printf("P:%d ", ip->ip_p); print_ip(ip->ip_src); printf(" "); print_ip(ip->ip_dst); break; } printf(" via %s%d", rif->if_name, rif->if_unit); if ((ip->ip_off & IP_OFFMASK)) printf(" Fragment = %d",ip->ip_off & IP_OFFMASK); printf("\n"); if (fw_verbose_limit != 0 && counter == fw_verbose_limit) printf("ipfw: limit reached on rule #%d\n", rule); } /* * We overload the "dirport" parameter: * * If dirport is negative, packet is outgoing; otherwise incoming. * The low order 16 bits of dirport, if non-zero, indicate that * we should ignore all ``divert '' rules, where is * the low order 16 bits. * * Return value: * * -1 The packet was denied/rejected and has been dropped * 0 The packet is to be accepted; route normally * Divert the packet to divert , if any socket * is bound to it; otherwise just drop it. */ static int ip_fw_chk(struct ip **pip, int hlen, struct ifnet *rif, int dirport, struct mbuf **m) { struct ip_fw_chain *chain; register struct ip_fw *f = NULL; struct ip *ip = *pip; struct tcphdr *tcp = (struct tcphdr *) ((u_long *) ip + ip->ip_hl); struct udphdr *udp = (struct udphdr *) ((u_long *) ip + ip->ip_hl); struct icmp *icmp = (struct icmp *) ((u_long *) ip + ip->ip_hl); struct ifaddr *ia = NULL, *ia_p; struct in_addr src, dst, ia_i; u_short src_port, dst_port, offset; src = ip->ip_src; dst = ip->ip_dst; /* * If we got interface from which packet came-store pointer to it's * first adress */ if (rif != NULL) - ia = rif->if_addrlist; + ia = rif->if_addrhead.tqh_first; /* * Go down the chain, looking for enlightment */ for (chain=ip_fw_chain.lh_first; chain; chain = chain->chain.le_next) { f = chain->rule; /* Check direction inbound */ if (dirport >= 0 && !(f->fw_flg & IP_FW_F_IN)) continue; /* Check direction outbound */ if (dirport < 0 && !(f->fw_flg & IP_FW_F_OUT)) continue; /* Fragments */ if ((f->fw_flg & IP_FW_F_FRAG) && !(ip->ip_off & IP_OFFMASK)) continue; /* If src-addr doesn't match, not this rule. */ if ((src.s_addr & f->fw_smsk.s_addr) != f->fw_src.s_addr) continue; /* If dest-addr doesn't match, not this rule. */ if ((dst.s_addr & f->fw_dmsk.s_addr) != f->fw_dst.s_addr) continue; /* If a i/f name was specified, and we don't know */ if ((f->fw_flg & IP_FW_F_IFNAME) && !rif) continue; /* If a i/f name was specified, check it */ if ((f->fw_flg & IP_FW_F_IFNAME) && f->fw_via_name[0]) { /* Not same unit, don't match */ if (!(f->fw_flg & IP_FW_F_IFUWILD) && rif->if_unit != f->fw_via_unit) continue; /* Not same name */ if (strncmp(rif->if_name, f->fw_via_name, FW_IFNLEN)) continue; } /* If a i/f addr was specified, check it */ if (!(f->fw_flg & IP_FW_F_IFNAME) && f->fw_via_ip.s_addr) { int match = 0; - for (ia_p = ia; ia_p != NULL; ia_p = ia_p->ifa_next) { + for (ia_p = ia; ia_p != NULL; + ia_p = ia_p->ifa_link.tqe_next) { if ((ia_p->ifa_addr == NULL)) continue; if (ia_p->ifa_addr->sa_family != AF_INET) continue; ia_i.s_addr = ((struct sockaddr_in *) (ia_p->ifa_addr))->sin_addr.s_addr; if (ia_i.s_addr != f->fw_via_ip.s_addr) continue; match = 1; break; } if (!match) continue; } /* * Check IP options */ if (f->fw_ipopt != f->fw_ipnopt) if (!ipopts_match(ip, f)) continue; /* If wildcard, match */ if (f->fw_prot == IPPROTO_IP) goto got_match; /* If different, dont match */ if (ip->ip_p != f->fw_prot) continue; switch (ip->ip_p) { case IPPROTO_TCP: offset = ip->ip_off & IP_OFFMASK; if (offset == 1) { static int frag_counter = 0; ++frag_counter; ipfw_report("Refuse", -1, ip, frag_counter, rif); m_freem(*m); return -1; } if ((offset == 0) && (f->fw_tcpf != f->fw_tcpnf) && !tcpflg_match(tcp, f)) continue; src_port = ntohs(tcp->th_sport); dst_port = ntohs(tcp->th_dport); goto check_ports; case IPPROTO_UDP: src_port = ntohs(udp->uh_sport); dst_port = ntohs(udp->uh_dport); check_ports: if (!port_match(&f->fw_pts[0], f->fw_nsp, src_port, f->fw_flg & IP_FW_F_SRNG)) continue; if (!port_match(&f->fw_pts[f->fw_nsp], f->fw_ndp, dst_port, f->fw_flg & IP_FW_F_DRNG)) continue; break; case IPPROTO_ICMP: if (!icmptype_match(icmp, f)) continue; goto got_match; default: break; } got_match: f->fw_pcnt++; f->fw_bcnt+=ip->ip_len; f->timestamp = time.tv_sec; if (f->fw_flg & IP_FW_F_PRN) { if ((f->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_ACCEPT) { ipfw_report("Allow", f->fw_number, ip, f->fw_pcnt, rif); } else if ((f->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_DIVERT) { if (f->fw_divert_port != (dirport & 0xffff)) ipfw_report("Divert", f->fw_number, ip, f->fw_pcnt, rif); } else if ((f->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_COUNT) { ipfw_report("Count", f->fw_number, ip, f->fw_pcnt, rif); } else { ipfw_report("Deny", f->fw_number, ip, f->fw_pcnt, rif); } } /* Take appropriate action */ if ((f->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_ACCEPT) { return 0; } else if ((f->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_COUNT) { continue; } else if ((f->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_DIVERT) { if (f->fw_divert_port == (dirport & 0xffff)) continue; /* ignore this rule */ return (f->fw_divert_port); } else break; /* ie, deny/reject */ } #ifdef DIAGNOSTIC if (!chain) /* rule 65535 should always be there */ panic("ip_fw: chain"); if (!f) panic("ip_fw: entry"); #endif /* * At this point, we're going to drop the packet. * Send an ICMP only if all of the following are true: * * - The packet is an incoming packet * - The packet matched a deny rule * - The packet is not an ICMP packet * - The rule has the special ICMP reply flag set */ if (dirport >= 0 && (f->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_DENY && (ip->ip_p != IPPROTO_ICMP) && (f->fw_flg & IP_FW_F_ICMPRPL)) { icmp_error(*m, ICMP_UNREACH, ICMP_UNREACH_PORT, 0L, 0); return -1; } m_freem(*m); return -1; } static int add_entry(struct ip_fw_head *chainptr, struct ip_fw *frwl) { struct ip_fw *ftmp = 0; struct ip_fw_chain *fwc = 0, *fcp, *fcpl = 0; u_short nbr = 0; int s; fwc = malloc(sizeof *fwc, M_IPFW, M_DONTWAIT); ftmp = malloc(sizeof *ftmp, M_IPFW, M_DONTWAIT); if (!fwc || !ftmp) { dprintf(("%s malloc said no\n", err_prefix)); if (fwc) free(fwc, M_IPFW); if (ftmp) free(ftmp, M_IPFW); return (ENOSPC); } bcopy(frwl, ftmp, sizeof(struct ip_fw)); ftmp->fw_pcnt = 0L; ftmp->fw_bcnt = 0L; fwc->rule = ftmp; s = splnet(); if (!chainptr->lh_first) { LIST_INSERT_HEAD(chainptr, fwc, chain); splx(s); return(0); } else if (ftmp->fw_number == (u_short)-1) { if (fwc) free(fwc, M_IPFW); if (ftmp) free(ftmp, M_IPFW); splx(s); return (EINVAL); } /* If entry number is 0, find highest numbered rule and add 100 */ if (ftmp->fw_number == 0) { for (fcp = chainptr->lh_first; fcp; fcp = fcp->chain.le_next) { if (fcp->rule->fw_number != (u_short)-1) nbr = fcp->rule->fw_number; else break; } if (nbr < (u_short)-1 - 100) nbr += 100; ftmp->fw_number = nbr; } /* Got a valid number; now insert it, keeping the list ordered */ for (fcp = chainptr->lh_first; fcp; fcp = fcp->chain.le_next) { if (fcp->rule->fw_number > ftmp->fw_number) { if (fcpl) { LIST_INSERT_AFTER(fcpl, fwc, chain); } else { LIST_INSERT_HEAD(chainptr, fwc, chain); } break; } else { fcpl = fcp; } } splx(s); return (0); } static int del_entry(struct ip_fw_head *chainptr, struct ip_fw *frwl) { struct ip_fw_chain *fcp; int s; s = splnet(); fcp = chainptr->lh_first; if (frwl->fw_number != (u_short)-1) { for (; fcp; fcp = fcp->chain.le_next) { if (fcp->rule->fw_number == frwl->fw_number) { LIST_REMOVE(fcp, chain); splx(s); free(fcp->rule, M_IPFW); free(fcp, M_IPFW); return 0; } } } splx(s); return (EINVAL); } static int zero_entry(struct mbuf *m) { struct ip_fw *frwl; struct ip_fw_chain *fcp; int s; if (m) { frwl = check_ipfw_struct(m); if (!frwl) return(EINVAL); } else frwl = NULL; /* * It's possible to insert multiple chain entries with the * same number, so we don't stop after finding the first * match if zeroing a specific entry. */ s = splnet(); for (fcp = ip_fw_chain.lh_first; fcp; fcp = fcp->chain.le_next) if (!frwl || frwl->fw_number == fcp->rule->fw_number) { fcp->rule->fw_bcnt = fcp->rule->fw_pcnt = 0; fcp->rule->timestamp = 0; } splx(s); if ( frwl ) printf("ipfw: Entry %d cleared.\n", frwl->fw_number); else printf("ipfw: Accounting cleared.\n"); return(0); } static struct ip_fw * check_ipfw_struct(struct mbuf *m) { struct ip_fw *frwl; if (m->m_len != sizeof(struct ip_fw)) { dprintf(("%s len=%d, want %d\n", err_prefix, m->m_len, sizeof(struct ip_fw))); return (NULL); } frwl = mtod(m, struct ip_fw *); if ((frwl->fw_flg & ~IP_FW_F_MASK) != 0) { dprintf(("%s undefined flag bits set (flags=%x)\n", err_prefix, frwl->fw_flg)); return (NULL); } /* If neither In nor Out, then both */ if (!(frwl->fw_flg & (IP_FW_F_IN | IP_FW_F_OUT))) frwl->fw_flg |= IP_FW_F_IN | IP_FW_F_OUT; if ((frwl->fw_flg & IP_FW_F_SRNG) && frwl->fw_nsp < 2) { dprintf(("%s src range set but n_src_p=%d\n", err_prefix, frwl->fw_nsp)); return (NULL); } if ((frwl->fw_flg & IP_FW_F_DRNG) && frwl->fw_ndp < 2) { dprintf(("%s dst range set but n_dst_p=%d\n", err_prefix, frwl->fw_ndp)); return (NULL); } if (frwl->fw_nsp + frwl->fw_ndp > IP_FW_MAX_PORTS) { dprintf(("%s too many ports (%d+%d)\n", err_prefix, frwl->fw_nsp, frwl->fw_ndp)); return (NULL); } /* * ICMP protocol doesn't use port range */ if ((frwl->fw_prot != IPPROTO_TCP) && (frwl->fw_prot != IPPROTO_UDP) && (frwl->fw_nsp || frwl->fw_ndp)) { dprintf(("%s port(s) specified for non TCP/UDP rule\n", err_prefix)); return(NULL); } /* * Rather than modify the entry to make such entries work, * we reject this rule and require user level utilities * to enforce whatever policy they deem appropriate. */ if ((frwl->fw_src.s_addr & (~frwl->fw_smsk.s_addr)) || (frwl->fw_dst.s_addr & (~frwl->fw_dmsk.s_addr))) { dprintf(("%s rule never matches\n", err_prefix)); return(NULL); } /* Diverting to port zero is illegal */ if ((frwl->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_DIVERT && frwl->fw_divert_port == 0) { dprintf(("ip_fw_ctl: can't divert to port 0\n")); return (NULL); } return frwl; } static int ip_fw_ctl(int stage, struct mbuf **mm) { int error; struct mbuf *m; if (stage == IP_FW_GET) { struct ip_fw_chain *fcp = ip_fw_chain.lh_first; *mm = m = m_get(M_WAIT, MT_SOOPTS); for (; fcp; fcp = fcp->chain.le_next) { memcpy(m->m_data, fcp->rule, sizeof *(fcp->rule)); m->m_len = sizeof *(fcp->rule); m->m_next = m_get(M_WAIT, MT_SOOPTS); m = m->m_next; m->m_len = 0; } return (0); } m = *mm; /* only allow get calls if secure mode > 2 */ if (securelevel > 2) { if (m) (void)m_free(m); return(EPERM); } if (stage == IP_FW_FLUSH) { while (ip_fw_chain.lh_first != NULL && ip_fw_chain.lh_first->rule->fw_number != (u_short)-1) { struct ip_fw_chain *fcp = ip_fw_chain.lh_first; int s = splnet(); LIST_REMOVE(ip_fw_chain.lh_first, chain); splx(s); free(fcp->rule, M_IPFW); free(fcp, M_IPFW); } if (m) (void)m_free(m); return (0); } if (stage == IP_FW_ZERO) { error = zero_entry(m); if (m) (void)m_free(m); return (error); } if (m == NULL) { printf("%s NULL mbuf ptr\n", err_prefix); return (EINVAL); } if (stage == IP_FW_ADD || stage == IP_FW_DEL) { struct ip_fw *frwl = check_ipfw_struct(m); if (!frwl) { if (m) (void)m_free(m); return (EINVAL); } if (stage == IP_FW_ADD) error = add_entry(&ip_fw_chain, frwl); else error = del_entry(&ip_fw_chain, frwl); if (m) (void)m_free(m); return error; } dprintf(("%s unknown request %d\n", err_prefix, stage)); if (m) (void)m_free(m); return (EINVAL); } void ip_fw_init(void) { struct ip_fw deny; ip_fw_chk_ptr = ip_fw_chk; ip_fw_ctl_ptr = ip_fw_ctl; LIST_INIT(&ip_fw_chain); bzero(&deny, sizeof deny); deny.fw_prot = IPPROTO_IP; deny.fw_number = (u_short)-1; add_entry(&ip_fw_chain, &deny); printf("IP packet filtering initialized, " #ifdef IPDIVERT "divert enabled, "); #else "divert disabled, "); #endif #ifndef IPFIREWALL_VERBOSE printf("logging disabled\n"); #else if (fw_verbose_limit == 0) printf("unlimited logging\n"); else printf("logging limited to %d packets/entry\n", fw_verbose_limit); #endif } #ifdef IPFIREWALL_MODULE #include #include #include MOD_MISC(ipfw); static int ipfw_load(struct lkm_table *lkmtp, int cmd) { int s=splnet(); old_chk_ptr = ip_fw_chk_ptr; old_ctl_ptr = ip_fw_ctl_ptr; ip_fw_init(); splx(s); return 0; } static int ipfw_unload(struct lkm_table *lkmtp, int cmd) { int s=splnet(); ip_fw_chk_ptr = old_chk_ptr; ip_fw_ctl_ptr = old_ctl_ptr; while (ip_fw_chain.lh_first != NULL) { struct ip_fw_chain *fcp = ip_fw_chain.lh_first; LIST_REMOVE(ip_fw_chain.lh_first, chain); free(fcp->rule, M_IPFW); free(fcp, M_IPFW); } splx(s); printf("IP firewall unloaded\n"); return 0; } int ipfw_mod(struct lkm_table *lkmtp, int cmd, int ver) { DISPATCH(lkmtp, cmd, ver, ipfw_load, ipfw_unload, lkm_nullcmd); } #endif diff --git a/sys/netinet/ip_icmp.c b/sys/netinet/ip_icmp.c index 8fc9eaa2d7ce..1dbcc432f431 100644 --- a/sys/netinet/ip_icmp.c +++ b/sys/netinet/ip_icmp.c @@ -1,694 +1,694 @@ /* * Copyright (c) 1982, 1986, 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ip_icmp.c 8.2 (Berkeley) 1/4/94 - * $Id: ip_icmp.c,v 1.21 1996/07/24 18:46:17 wollman Exp $ + * $Id: ip_icmp.c,v 1.22 1996/09/20 08:23:54 pst Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #define _IP_VHL #include #include #include #include #include #include #include /* * ICMP routines: error generation, receive packet processing, and * routines to turnaround packets back to the originator, and * host table maintenance routines. */ static struct icmpstat icmpstat; SYSCTL_STRUCT(_net_inet_icmp, ICMPCTL_STATS, stats, CTLFLAG_RD, &icmpstat, icmpstat, ""); static int icmpmaskrepl = 0; SYSCTL_INT(_net_inet_icmp, ICMPCTL_MASKREPL, maskrepl, CTLFLAG_RW, &icmpmaskrepl, 0, ""); #ifdef ICMPPRINTFS int icmpprintfs = 0; #endif static void icmp_reflect __P((struct mbuf *)); static void icmp_send __P((struct mbuf *, struct mbuf *)); static int ip_next_mtu __P((int, int)); extern struct protosw inetsw[]; /* * Generate an error packet of type error * in response to bad packet ip. */ void icmp_error(n, type, code, dest, destifp) struct mbuf *n; int type, code; n_long dest; struct ifnet *destifp; { register struct ip *oip = mtod(n, struct ip *), *nip; register unsigned oiplen = IP_VHL_HL(oip->ip_vhl) << 2; register struct icmp *icp; register struct mbuf *m; unsigned icmplen; #ifdef ICMPPRINTFS if (icmpprintfs) printf("icmp_error(%p, %x, %d)\n", oip, type, code); #endif if (type != ICMP_REDIRECT) icmpstat.icps_error++; /* * Don't send error if not the first fragment of message. * Don't error if the old packet protocol was ICMP * error message, only known informational types. */ if (oip->ip_off &~ (IP_MF|IP_DF)) goto freeit; if (oip->ip_p == IPPROTO_ICMP && type != ICMP_REDIRECT && n->m_len >= oiplen + ICMP_MINLEN && !ICMP_INFOTYPE(((struct icmp *)((caddr_t)oip + oiplen))->icmp_type)) { icmpstat.icps_oldicmp++; goto freeit; } /* Don't send error in response to a multicast or broadcast packet */ if (n->m_flags & (M_BCAST|M_MCAST)) goto freeit; /* * First, formulate icmp message */ m = m_gethdr(M_DONTWAIT, MT_HEADER); if (m == NULL) goto freeit; icmplen = oiplen + min(8, oip->ip_len); m->m_len = icmplen + ICMP_MINLEN; MH_ALIGN(m, m->m_len); icp = mtod(m, struct icmp *); if ((u_int)type > ICMP_MAXTYPE) panic("icmp_error"); icmpstat.icps_outhist[type]++; icp->icmp_type = type; if (type == ICMP_REDIRECT) icp->icmp_gwaddr.s_addr = dest; else { icp->icmp_void = 0; /* * The following assignments assume an overlay with the * zeroed icmp_void field. */ if (type == ICMP_PARAMPROB) { icp->icmp_pptr = code; code = 0; } else if (type == ICMP_UNREACH && code == ICMP_UNREACH_NEEDFRAG && destifp) { icp->icmp_nextmtu = htons(destifp->if_mtu); } } icp->icmp_code = code; bcopy((caddr_t)oip, (caddr_t)&icp->icmp_ip, icmplen); nip = &icp->icmp_ip; nip->ip_len = htons((u_short)(nip->ip_len + oiplen)); /* * Now, copy old ip header (without options) * in front of icmp message. */ if (m->m_data - sizeof(struct ip) < m->m_pktdat) panic("icmp len"); m->m_data -= sizeof(struct ip); m->m_len += sizeof(struct ip); m->m_pkthdr.len = m->m_len; m->m_pkthdr.rcvif = n->m_pkthdr.rcvif; nip = mtod(m, struct ip *); bcopy((caddr_t)oip, (caddr_t)nip, sizeof(struct ip)); nip->ip_len = m->m_len; nip->ip_vhl = IP_VHL_BORING; nip->ip_p = IPPROTO_ICMP; nip->ip_tos = 0; icmp_reflect(m); freeit: m_freem(n); } static struct sockaddr_in icmpsrc = { sizeof (struct sockaddr_in), AF_INET }; static struct sockaddr_in icmpdst = { sizeof (struct sockaddr_in), AF_INET }; static struct sockaddr_in icmpgw = { sizeof (struct sockaddr_in), AF_INET }; /* * Process a received ICMP message. */ void icmp_input(m, hlen) register struct mbuf *m; int hlen; { register struct icmp *icp; register struct ip *ip = mtod(m, struct ip *); int icmplen = ip->ip_len; register int i; struct in_ifaddr *ia; void (*ctlfunc) __P((int, struct sockaddr *, void *)); int code; /* * Locate icmp structure in mbuf, and check * that not corrupted and of at least minimum length. */ #ifdef ICMPPRINTFS if (icmpprintfs) { char buf[4 * sizeof "123"]; strcpy(buf, inet_ntoa(ip->ip_src)); printf("icmp_input from %s to %s, len %d\n", buf, inet_ntoa(ip->ip_dst), icmplen); } #endif if (icmplen < ICMP_MINLEN) { icmpstat.icps_tooshort++; goto freeit; } i = hlen + min(icmplen, ICMP_ADVLENMIN); if (m->m_len < i && (m = m_pullup(m, i)) == 0) { icmpstat.icps_tooshort++; return; } ip = mtod(m, struct ip *); m->m_len -= hlen; m->m_data += hlen; icp = mtod(m, struct icmp *); if (in_cksum(m, icmplen)) { icmpstat.icps_checksum++; goto freeit; } m->m_len += hlen; m->m_data -= hlen; #ifdef ICMPPRINTFS if (icmpprintfs) printf("icmp_input, type %d code %d\n", icp->icmp_type, icp->icmp_code); #endif /* * Message type specific processing. */ if (icp->icmp_type > ICMP_MAXTYPE) goto raw; icmpstat.icps_inhist[icp->icmp_type]++; code = icp->icmp_code; switch (icp->icmp_type) { case ICMP_UNREACH: switch (code) { case ICMP_UNREACH_NET: case ICMP_UNREACH_HOST: case ICMP_UNREACH_PROTOCOL: case ICMP_UNREACH_PORT: case ICMP_UNREACH_SRCFAIL: code += PRC_UNREACH_NET; break; case ICMP_UNREACH_NEEDFRAG: code = PRC_MSGSIZE; break; case ICMP_UNREACH_NET_UNKNOWN: case ICMP_UNREACH_NET_PROHIB: case ICMP_UNREACH_TOSNET: code = PRC_UNREACH_NET; break; case ICMP_UNREACH_HOST_UNKNOWN: case ICMP_UNREACH_ISOLATED: case ICMP_UNREACH_HOST_PROHIB: case ICMP_UNREACH_TOSHOST: code = PRC_UNREACH_HOST; break; case ICMP_UNREACH_FILTER_PROHIB: case ICMP_UNREACH_HOST_PRECEDENCE: case ICMP_UNREACH_PRECEDENCE_CUTOFF: code = PRC_UNREACH_PORT; break; default: goto badcode; } goto deliver; case ICMP_TIMXCEED: if (code > 1) goto badcode; code += PRC_TIMXCEED_INTRANS; goto deliver; case ICMP_PARAMPROB: if (code > 1) goto badcode; code = PRC_PARAMPROB; goto deliver; case ICMP_SOURCEQUENCH: if (code) goto badcode; code = PRC_QUENCH; deliver: /* * Problem with datagram; advise higher level routines. */ if (icmplen < ICMP_ADVLENMIN || icmplen < ICMP_ADVLEN(icp) || IP_VHL_HL(icp->icmp_ip.ip_vhl) < (sizeof(struct ip) >> 2)) { icmpstat.icps_badlen++; goto freeit; } NTOHS(icp->icmp_ip.ip_len); /* Discard ICMP's in response to multicast packets */ if (IN_MULTICAST(ntohl(icp->icmp_ip.ip_dst.s_addr))) goto badcode; #ifdef ICMPPRINTFS if (icmpprintfs) printf("deliver to protocol %d\n", icp->icmp_ip.ip_p); #endif icmpsrc.sin_addr = icp->icmp_ip.ip_dst; #if 1 /* * MTU discovery: * If we got a needfrag and there is a host route to the * original destination, and the MTU is not locked, then * set the MTU in the route to the suggested new value * (if given) and then notify as usual. The ULPs will * notice that the MTU has changed and adapt accordingly. * If no new MTU was suggested, then we guess a new one * less than the current value. If the new MTU is * unreasonably small (arbitrarily set at 296), then * we reset the MTU to the interface value and enable the * lock bit, indicating that we are no longer doing MTU * discovery. */ if (code == PRC_MSGSIZE) { struct rtentry *rt; int mtu; rt = rtalloc1((struct sockaddr *)&icmpsrc, 0, RTF_CLONING | RTF_PRCLONING); if (rt && (rt->rt_flags & RTF_HOST) && !(rt->rt_rmx.rmx_locks & RTV_MTU)) { mtu = ntohs(icp->icmp_nextmtu); if (!mtu) mtu = ip_next_mtu(rt->rt_rmx.rmx_mtu, 1); #ifdef DEBUG_MTUDISC printf("MTU for %s reduced to %d\n", inet_ntoa(icmpsrc.sin_addr), mtu); #endif if (mtu < 296) { /* rt->rt_rmx.rmx_mtu = rt->rt_ifp->if_mtu; */ rt->rt_rmx.rmx_locks |= RTV_MTU; } else if (rt->rt_rmx.rmx_mtu > mtu) { rt->rt_rmx.rmx_mtu = mtu; } } if (rt) RTFREE(rt); } #endif ctlfunc = inetsw[ip_protox[icp->icmp_ip.ip_p]].pr_ctlinput; if (ctlfunc) (*ctlfunc)(code, (struct sockaddr *)&icmpsrc, (void *)&icp->icmp_ip); break; badcode: icmpstat.icps_badcode++; break; case ICMP_ECHO: icp->icmp_type = ICMP_ECHOREPLY; goto reflect; case ICMP_TSTAMP: if (icmplen < ICMP_TSLEN) { icmpstat.icps_badlen++; break; } icp->icmp_type = ICMP_TSTAMPREPLY; icp->icmp_rtime = iptime(); icp->icmp_ttime = icp->icmp_rtime; /* bogus, do later! */ goto reflect; case ICMP_MASKREQ: #define satosin(sa) ((struct sockaddr_in *)(sa)) if (icmpmaskrepl == 0) break; /* * We are not able to respond with all ones broadcast * unless we receive it over a point-to-point interface. */ if (icmplen < ICMP_MASKLEN) break; switch (ip->ip_dst.s_addr) { case INADDR_BROADCAST: case INADDR_ANY: icmpdst.sin_addr = ip->ip_src; break; default: icmpdst.sin_addr = ip->ip_dst; } ia = (struct in_ifaddr *)ifaof_ifpforaddr( (struct sockaddr *)&icmpdst, m->m_pkthdr.rcvif); if (ia == 0) break; if (ia->ia_ifp == 0) break; icp->icmp_type = ICMP_MASKREPLY; icp->icmp_mask = ia->ia_sockmask.sin_addr.s_addr; if (ip->ip_src.s_addr == 0) { if (ia->ia_ifp->if_flags & IFF_BROADCAST) ip->ip_src = satosin(&ia->ia_broadaddr)->sin_addr; else if (ia->ia_ifp->if_flags & IFF_POINTOPOINT) ip->ip_src = satosin(&ia->ia_dstaddr)->sin_addr; } reflect: ip->ip_len += hlen; /* since ip_input deducts this */ icmpstat.icps_reflect++; icmpstat.icps_outhist[icp->icmp_type]++; icmp_reflect(m); return; case ICMP_REDIRECT: if (code > 3) goto badcode; if (icmplen < ICMP_ADVLENMIN || icmplen < ICMP_ADVLEN(icp) || IP_VHL_HL(icp->icmp_ip.ip_vhl) < (sizeof(struct ip) >> 2)) { icmpstat.icps_badlen++; break; } /* * Short circuit routing redirects to force * immediate change in the kernel's routing * tables. The message is also handed to anyone * listening on a raw socket (e.g. the routing * daemon for use in updating its tables). */ icmpgw.sin_addr = ip->ip_src; icmpdst.sin_addr = icp->icmp_gwaddr; #ifdef ICMPPRINTFS if (icmpprintfs) { char buf[4 * sizeof "123"]; strcpy(buf, inet_ntoa(icp->icmp_ip.ip_dst)); printf("redirect dst %s to %s\n", buf, inet_ntoa(icp->icmp_gwaddr)); } #endif icmpsrc.sin_addr = icp->icmp_ip.ip_dst; rtredirect((struct sockaddr *)&icmpsrc, (struct sockaddr *)&icmpdst, (struct sockaddr *)0, RTF_GATEWAY | RTF_HOST, (struct sockaddr *)&icmpgw, (struct rtentry **)0); pfctlinput(PRC_REDIRECT_HOST, (struct sockaddr *)&icmpsrc); break; /* * No kernel processing for the following; * just fall through to send to raw listener. */ case ICMP_ECHOREPLY: case ICMP_ROUTERADVERT: case ICMP_ROUTERSOLICIT: case ICMP_TSTAMPREPLY: case ICMP_IREQREPLY: case ICMP_MASKREPLY: default: break; } raw: rip_input(m, hlen); return; freeit: m_freem(m); } /* * Reflect the ip packet back to the source */ static void icmp_reflect(m) struct mbuf *m; { register struct ip *ip = mtod(m, struct ip *); register struct in_ifaddr *ia; struct in_addr t; struct mbuf *opts = 0; int optlen = (IP_VHL_HL(ip->ip_vhl) << 2) - sizeof(struct ip); if (!in_canforward(ip->ip_src) && ((ntohl(ip->ip_src.s_addr) & IN_CLASSA_NET) != (IN_LOOPBACKNET << IN_CLASSA_NSHIFT))) { m_freem(m); /* Bad return address */ goto done; /* Ip_output() will check for broadcast */ } t = ip->ip_dst; ip->ip_dst = ip->ip_src; /* * If the incoming packet was addressed directly to us, * use dst as the src for the reply. Otherwise (broadcast * or anonymous), use the address which corresponds * to the incoming interface. */ - for (ia = in_ifaddr; ia; ia = ia->ia_next) { + for (ia = in_ifaddrhead.tqh_first; ia; ia = ia->ia_link.tqe_next) { if (t.s_addr == IA_SIN(ia)->sin_addr.s_addr) break; if (ia->ia_ifp && (ia->ia_ifp->if_flags & IFF_BROADCAST) && t.s_addr == satosin(&ia->ia_broadaddr)->sin_addr.s_addr) break; } icmpdst.sin_addr = t; if (ia == (struct in_ifaddr *)0) ia = (struct in_ifaddr *)ifaof_ifpforaddr( (struct sockaddr *)&icmpdst, m->m_pkthdr.rcvif); /* * The following happens if the packet was not addressed to us, * and was received on an interface with no IP address. */ if (ia == (struct in_ifaddr *)0) - ia = in_ifaddr; + ia = in_ifaddrhead.tqh_first; t = IA_SIN(ia)->sin_addr; ip->ip_src = t; ip->ip_ttl = MAXTTL; if (optlen > 0) { register u_char *cp; int opt, cnt; u_int len; /* * Retrieve any source routing from the incoming packet; * add on any record-route or timestamp options. */ cp = (u_char *) (ip + 1); if ((opts = ip_srcroute()) == 0 && (opts = m_gethdr(M_DONTWAIT, MT_HEADER))) { opts->m_len = sizeof(struct in_addr); mtod(opts, struct in_addr *)->s_addr = 0; } if (opts) { #ifdef ICMPPRINTFS if (icmpprintfs) printf("icmp_reflect optlen %d rt %d => ", optlen, opts->m_len); #endif for (cnt = optlen; cnt > 0; cnt -= len, cp += len) { opt = cp[IPOPT_OPTVAL]; if (opt == IPOPT_EOL) break; if (opt == IPOPT_NOP) len = 1; else { len = cp[IPOPT_OLEN]; if (len <= 0 || len > cnt) break; } /* * Should check for overflow, but it "can't happen" */ if (opt == IPOPT_RR || opt == IPOPT_TS || opt == IPOPT_SECURITY) { bcopy((caddr_t)cp, mtod(opts, caddr_t) + opts->m_len, len); opts->m_len += len; } } /* Terminate & pad, if necessary */ cnt = opts->m_len % 4; if (cnt) { for (; cnt < 4; cnt++) { *(mtod(opts, caddr_t) + opts->m_len) = IPOPT_EOL; opts->m_len++; } } #ifdef ICMPPRINTFS if (icmpprintfs) printf("%d\n", opts->m_len); #endif } /* * Now strip out original options by copying rest of first * mbuf's data back, and adjust the IP length. */ ip->ip_len -= optlen; ip->ip_vhl = IP_VHL_BORING; m->m_len -= optlen; if (m->m_flags & M_PKTHDR) m->m_pkthdr.len -= optlen; optlen += sizeof(struct ip); bcopy((caddr_t)ip + optlen, (caddr_t)(ip + 1), (unsigned)(m->m_len - sizeof(struct ip))); } m->m_flags &= ~(M_BCAST|M_MCAST); icmp_send(m, opts); done: if (opts) (void)m_free(opts); } /* * Send an icmp packet back to the ip level, * after supplying a checksum. */ static void icmp_send(m, opts) register struct mbuf *m; struct mbuf *opts; { register struct ip *ip = mtod(m, struct ip *); register int hlen; register struct icmp *icp; struct route ro; hlen = IP_VHL_HL(ip->ip_vhl) << 2; m->m_data += hlen; m->m_len -= hlen; icp = mtod(m, struct icmp *); icp->icmp_cksum = 0; icp->icmp_cksum = in_cksum(m, ip->ip_len - hlen); m->m_data -= hlen; m->m_len += hlen; #ifdef ICMPPRINTFS if (icmpprintfs) { char buf[4 * sizeof "123"]; strcpy(buf, inet_ntoa(ip->ip_dst)); printf("icmp_send dst %s src %s\n", buf, inet_ntoa(ip->ip_src)); } #endif bzero(&ro, sizeof ro); (void) ip_output(m, opts, &ro, 0, NULL); if (ro.ro_rt) RTFREE(ro.ro_rt); } n_time iptime() { struct timeval atv; u_long t; microtime(&atv); t = (atv.tv_sec % (24*60*60)) * 1000 + atv.tv_usec / 1000; return (htonl(t)); } #if 1 /* * Return the next larger or smaller MTU plateau (table from RFC 1191) * given current value MTU. If DIR is less than zero, a larger plateau * is returned; otherwise, a smaller value is returned. */ static int ip_next_mtu(mtu, dir) int mtu; int dir; { static int mtutab[] = { 65535, 32000, 17914, 8166, 4352, 2002, 1492, 1006, 508, 296, 68, 0 }; int i; for (i = 0; i < (sizeof mtutab) / (sizeof mtutab[0]); i++) { if (mtu >= mtutab[i]) break; } if (dir < 0) { if (i == 0) { return 0; } else { return mtutab[i - 1]; } } else { if (mtutab[i] == 0) { return 0; } else if(mtu > mtutab[i]) { return mtutab[i]; } else { return mtutab[i + 1]; } } } #endif diff --git a/sys/netinet/ip_input.c b/sys/netinet/ip_input.c index 20256b87ef1d..969834c586e0 100644 --- a/sys/netinet/ip_input.c +++ b/sys/netinet/ip_input.c @@ -1,1412 +1,1415 @@ /* * Copyright (c) 1982, 1986, 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ip_input.c 8.2 (Berkeley) 1/4/94 - * $Id: ip_input.c,v 1.51 1996/11/11 04:56:15 fenner Exp $ + * $Id: ip_input.c,v 1.52 1996/12/11 03:26:36 davidg Exp $ * $ANA: ip_input.c,v 1.5 1996/09/18 14:34:59 wollman Exp $ */ #define _IP_VHL #include "opt_ipfw.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IPFIREWALL #include #endif int rsvp_on = 0; static int ip_rsvp_on; struct socket *ip_rsvpd; static int ipforwarding = 0; SYSCTL_INT(_net_inet_ip, IPCTL_FORWARDING, forwarding, CTLFLAG_RW, &ipforwarding, 0, ""); static int ipsendredirects = 1; /* XXX */ SYSCTL_INT(_net_inet_ip, IPCTL_SENDREDIRECTS, redirect, CTLFLAG_RW, &ipsendredirects, 0, ""); int ip_defttl = IPDEFTTL; SYSCTL_INT(_net_inet_ip, IPCTL_DEFTTL, ttl, CTLFLAG_RW, &ip_defttl, 0, ""); static int ip_dosourceroute = 0; SYSCTL_INT(_net_inet_ip, IPCTL_SOURCEROUTE, sourceroute, CTLFLAG_RW, &ip_dosourceroute, 0, ""); #ifdef DIAGNOSTIC static int ipprintfs = 0; #endif extern struct domain inetdomain; extern struct protosw inetsw[]; u_char ip_protox[IPPROTO_MAX]; static int ipqmaxlen = IFQ_MAXLEN; -struct in_ifaddr *in_ifaddr; /* first inet address */ +struct in_ifaddrhead in_ifaddrhead; /* first inet address */ struct ifqueue ipintrq; SYSCTL_INT(_net_inet_ip, IPCTL_INTRQMAXLEN, intr_queue_maxlen, CTLFLAG_RD, &ipintrq.ifq_maxlen, 0, ""); SYSCTL_INT(_net_inet_ip, IPCTL_INTRQDROPS, intr_queue_drops, CTLFLAG_RD, &ipintrq.ifq_drops, 0, ""); struct ipstat ipstat; static struct ipq ipq; #ifdef IPCTL_DEFMTU SYSCTL_INT(_net_inet_ip, IPCTL_DEFMTU, mtu, CTLFLAG_RW, &ip_mtu, 0, ""); #endif #if !defined(COMPAT_IPFW) || COMPAT_IPFW == 1 #undef COMPAT_IPFW #define COMPAT_IPFW 1 #else #undef COMPAT_IPFW #endif #ifdef COMPAT_IPFW /* Firewall hooks */ ip_fw_chk_t *ip_fw_chk_ptr; ip_fw_ctl_t *ip_fw_ctl_ptr; /* IP Network Address Translation (NAT) hooks */ ip_nat_t *ip_nat_ptr; ip_nat_ctl_t *ip_nat_ctl_ptr; #endif /* * We need to save the IP options in case a protocol wants to respond * to an incoming packet over the same route if the packet got here * using IP source routing. This allows connection establishment and * maintenance when the remote end is on a network that is not known * to us. */ static int ip_nhops = 0; static struct ip_srcrt { struct in_addr dst; /* final destination */ char nop; /* one NOP to align */ char srcopt[IPOPT_OFFSET + 1]; /* OPTVAL, OLEN and OFFSET */ struct in_addr route[MAX_IPOPTLEN/sizeof(struct in_addr)]; } ip_srcrt; #ifdef IPDIVERT /* * Shared variable between ip_input() and ip_reass() to communicate * about which packets, once assembled from fragments, get diverted, * and to which port. */ static u_short frag_divert_port; #endif static void save_rte __P((u_char *, struct in_addr)); static void ip_deq __P((struct ipasfrag *)); static int ip_dooptions __P((struct mbuf *)); static void ip_enq __P((struct ipasfrag *, struct ipasfrag *)); static void ip_forward __P((struct mbuf *, int)); static void ip_freef __P((struct ipq *)); static struct ip * ip_reass __P((struct ipasfrag *, struct ipq *)); static struct in_ifaddr * ip_rtaddr __P((struct in_addr)); static void ipintr __P((void)); /* * IP initialization: fill in IP protocol switch table. * All protocols not implemented in kernel go to raw IP protocol handler. */ void ip_init() { register struct protosw *pr; register int i; + TAILQ_INIT(&in_ifaddrhead); pr = pffindproto(PF_INET, IPPROTO_RAW, SOCK_RAW); if (pr == 0) panic("ip_init"); for (i = 0; i < IPPROTO_MAX; i++) ip_protox[i] = pr - inetsw; for (pr = inetdomain.dom_protosw; pr < inetdomain.dom_protoswNPROTOSW; pr++) if (pr->pr_domain->dom_family == PF_INET && pr->pr_protocol && pr->pr_protocol != IPPROTO_RAW) ip_protox[pr->pr_protocol] = pr - inetsw; ipq.next = ipq.prev = &ipq; ip_id = time.tv_sec & 0xffff; ipintrq.ifq_maxlen = ipqmaxlen; #ifdef IPFIREWALL ip_fw_init(); #endif #ifdef IPNAT ip_nat_init(); #endif } static struct sockaddr_in ipaddr = { sizeof(ipaddr), AF_INET }; static struct route ipforward_rt; /* * Ip input routine. Checksum and byte swap header. If fragmented * try to reassemble. Process options. Pass to next level. */ void ip_input(struct mbuf *m) { struct ip *ip; struct ipq *fp; struct in_ifaddr *ia; int hlen; #ifdef DIAGNOSTIC if ((m->m_flags & M_PKTHDR) == 0) panic("ip_input no HDR"); #endif /* * If no IP addresses have been set yet but the interfaces * are receiving, can't do anything with incoming packets yet. + * XXX This is broken! We should be able to receive broadcasts + * and multicasts even without any local addresses configured. */ - if (in_ifaddr == NULL) + if (TAILQ_EMPTY(&in_ifaddrhead)) goto bad; ipstat.ips_total++; if (m->m_pkthdr.len < sizeof(struct ip)) goto tooshort; #ifdef DIAGNOSTIC if (m->m_len < sizeof(struct ip)) panic("ipintr mbuf too short"); #endif if (m->m_len < sizeof (struct ip) && (m = m_pullup(m, sizeof (struct ip))) == 0) { ipstat.ips_toosmall++; return; } ip = mtod(m, struct ip *); if (IP_VHL_V(ip->ip_vhl) != IPVERSION) { ipstat.ips_badvers++; goto bad; } hlen = IP_VHL_HL(ip->ip_vhl) << 2; if (hlen < sizeof(struct ip)) { /* minimum header length */ ipstat.ips_badhlen++; goto bad; } if (hlen > m->m_len) { if ((m = m_pullup(m, hlen)) == 0) { ipstat.ips_badhlen++; return; } ip = mtod(m, struct ip *); } if (hlen == sizeof(struct ip)) { ip->ip_sum = in_cksum_hdr(ip); } else { ip->ip_sum = in_cksum(m, hlen); } if (ip->ip_sum) { ipstat.ips_badsum++; goto bad; } /* * Convert fields to host representation. */ NTOHS(ip->ip_len); if (ip->ip_len < hlen) { ipstat.ips_badlen++; goto bad; } NTOHS(ip->ip_id); NTOHS(ip->ip_off); /* * Check that the amount of data in the buffers * is as at least much as the IP header would have us expect. * Trim mbufs if longer than we expect. * Drop packet if shorter than we expect. */ if (m->m_pkthdr.len < ip->ip_len) { tooshort: ipstat.ips_tooshort++; goto bad; } if (m->m_pkthdr.len > ip->ip_len) { if (m->m_len == m->m_pkthdr.len) { m->m_len = ip->ip_len; m->m_pkthdr.len = ip->ip_len; } else m_adj(m, ip->ip_len - m->m_pkthdr.len); } /* * IpHack's section. * Right now when no processing on packet has done * and it is still fresh out of network we do our black * deals with it. * - Firewall: deny/allow/divert * - Xlate: translate packet's addr/port (NAT). * - Wrap: fake packet's addr/port * - Encapsulate: put it in another IP and send out. */ #ifdef COMPAT_IPFW if (ip_fw_chk_ptr) { int action; #ifdef IPDIVERT action = (*ip_fw_chk_ptr)(&ip, hlen, m->m_pkthdr.rcvif, ip_divert_ignore, &m); #else action = (*ip_fw_chk_ptr)(&ip, hlen, m->m_pkthdr.rcvif, 0, &m); #endif if (action == -1) return; if (action != 0) { #ifdef IPDIVERT frag_divert_port = action; goto ours; #else goto bad; /* ipfw said divert but we can't */ #endif } } if (ip_nat_ptr && !(*ip_nat_ptr)(&ip, &m, m->m_pkthdr.rcvif, IP_NAT_IN)) return; #endif /* * Process options and, if not destined for us, * ship it on. ip_dooptions returns 1 when an * error was detected (causing an icmp message * to be sent and the original packet to be freed). */ ip_nhops = 0; /* for source routed packets */ if (hlen > sizeof (struct ip) && ip_dooptions(m)) return; /* greedy RSVP, snatches any PATH packet of the RSVP protocol and no * matter if it is destined to another node, or whether it is * a multicast one, RSVP wants it! and prevents it from being forwarded * anywhere else. Also checks if the rsvp daemon is running before * grabbing the packet. */ if (rsvp_on && ip->ip_p==IPPROTO_RSVP) goto ours; /* * Check our list of addresses, to see if the packet is for us. */ - for (ia = in_ifaddr; ia; ia = ia->ia_next) { + for (ia = in_ifaddrhead.tqh_first; ia; ia = ia->ia_link.tqe_next) { #define satosin(sa) ((struct sockaddr_in *)(sa)) if (IA_SIN(ia)->sin_addr.s_addr == ip->ip_dst.s_addr) goto ours; if (ia->ia_ifp && ia->ia_ifp->if_flags & IFF_BROADCAST) { if (satosin(&ia->ia_broadaddr)->sin_addr.s_addr == ip->ip_dst.s_addr) goto ours; if (ip->ip_dst.s_addr == ia->ia_netbroadcast.s_addr) goto ours; } } if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr))) { struct in_multi *inm; if (ip_mrouter) { /* * If we are acting as a multicast router, all * incoming multicast packets are passed to the * kernel-level multicast forwarding function. * The packet is returned (relatively) intact; if * ip_mforward() returns a non-zero value, the packet * must be discarded, else it may be accepted below. * * (The IP ident field is put in the same byte order * as expected when ip_mforward() is called from * ip_output().) */ ip->ip_id = htons(ip->ip_id); if (ip_mforward(ip, m->m_pkthdr.rcvif, m, 0) != 0) { ipstat.ips_cantforward++; m_freem(m); return; } ip->ip_id = ntohs(ip->ip_id); /* * The process-level routing demon needs to receive * all multicast IGMP packets, whether or not this * host belongs to their destination groups. */ if (ip->ip_p == IPPROTO_IGMP) goto ours; ipstat.ips_forward++; } /* * See if we belong to the destination multicast group on the * arrival interface. */ IN_LOOKUP_MULTI(ip->ip_dst, m->m_pkthdr.rcvif, inm); if (inm == NULL) { ipstat.ips_cantforward++; m_freem(m); return; } goto ours; } if (ip->ip_dst.s_addr == (u_long)INADDR_BROADCAST) goto ours; if (ip->ip_dst.s_addr == INADDR_ANY) goto ours; /* * Not for us; forward if possible and desirable. */ if (ipforwarding == 0) { ipstat.ips_cantforward++; m_freem(m); } else ip_forward(m, 0); return; ours: /* * If offset or IP_MF are set, must reassemble. * Otherwise, nothing need be done. * (We could look in the reassembly queue to see * if the packet was previously fragmented, * but it's not worth the time; just let them time out.) */ if (ip->ip_off & (IP_MF | IP_OFFMASK)) { if (m->m_flags & M_EXT) { /* XXX */ if ((m = m_pullup(m, sizeof (struct ip))) == 0) { ipstat.ips_toosmall++; #ifdef IPDIVERT frag_divert_port = 0; #endif return; } ip = mtod(m, struct ip *); } /* * Look for queue of fragments * of this datagram. */ for (fp = ipq.next; fp != &ipq; fp = fp->next) if (ip->ip_id == fp->ipq_id && ip->ip_src.s_addr == fp->ipq_src.s_addr && ip->ip_dst.s_addr == fp->ipq_dst.s_addr && ip->ip_p == fp->ipq_p) goto found; fp = 0; found: /* * Adjust ip_len to not reflect header, * set ip_mff if more fragments are expected, * convert offset of this to bytes. */ ip->ip_len -= hlen; ((struct ipasfrag *)ip)->ipf_mff &= ~1; if (ip->ip_off & IP_MF) ((struct ipasfrag *)ip)->ipf_mff |= 1; ip->ip_off <<= 3; /* * If datagram marked as having more fragments * or if this is not the first fragment, * attempt reassembly; if it succeeds, proceed. */ if (((struct ipasfrag *)ip)->ipf_mff & 1 || ip->ip_off) { ipstat.ips_fragments++; ip = ip_reass((struct ipasfrag *)ip, fp); if (ip == 0) return; ipstat.ips_reassembled++; m = dtom(ip); } else if (fp) ip_freef(fp); } else ip->ip_len -= hlen; #ifdef IPDIVERT /* * Divert packets here to the divert protocol if required */ if (frag_divert_port) { ip_divert_port = frag_divert_port; frag_divert_port = 0; (*inetsw[ip_protox[IPPROTO_DIVERT]].pr_input)(m, hlen); return; } #endif /* * Switch out to protocol's input routine. */ ipstat.ips_delivered++; (*inetsw[ip_protox[ip->ip_p]].pr_input)(m, hlen); return; bad: m_freem(m); } /* * IP software interrupt routine - to go away sometime soon */ static void ipintr(void) { int s; struct mbuf *m; while(1) { s = splimp(); IF_DEQUEUE(&ipintrq, m); splx(s); if (m == 0) return; ip_input(m); } } NETISR_SET(NETISR_IP, ipintr); /* * Take incoming datagram fragment and try to * reassemble it into whole datagram. If a chain for * reassembly of this datagram already exists, then it * is given as fp; otherwise have to make a chain. */ static struct ip * ip_reass(ip, fp) register struct ipasfrag *ip; register struct ipq *fp; { register struct mbuf *m = dtom(ip); register struct ipasfrag *q; struct mbuf *t; int hlen = ip->ip_hl << 2; int i, next; /* * Presence of header sizes in mbufs * would confuse code below. */ m->m_data += hlen; m->m_len -= hlen; /* * If first fragment to arrive, create a reassembly queue. */ if (fp == 0) { if ((t = m_get(M_DONTWAIT, MT_FTABLE)) == NULL) goto dropfrag; fp = mtod(t, struct ipq *); insque(fp, &ipq); fp->ipq_ttl = IPFRAGTTL; fp->ipq_p = ip->ip_p; fp->ipq_id = ip->ip_id; fp->ipq_next = fp->ipq_prev = (struct ipasfrag *)fp; fp->ipq_src = ((struct ip *)ip)->ip_src; fp->ipq_dst = ((struct ip *)ip)->ip_dst; #ifdef IPDIVERT fp->ipq_divert = 0; #endif q = (struct ipasfrag *)fp; goto insert; } /* * Find a segment which begins after this one does. */ for (q = fp->ipq_next; q != (struct ipasfrag *)fp; q = q->ipf_next) if (q->ip_off > ip->ip_off) break; /* * If there is a preceding segment, it may provide some of * our data already. If so, drop the data from the incoming * segment. If it provides all of our data, drop us. */ if (q->ipf_prev != (struct ipasfrag *)fp) { i = q->ipf_prev->ip_off + q->ipf_prev->ip_len - ip->ip_off; if (i > 0) { if (i >= ip->ip_len) goto dropfrag; m_adj(dtom(ip), i); ip->ip_off += i; ip->ip_len -= i; } } /* * While we overlap succeeding segments trim them or, * if they are completely covered, dequeue them. */ while (q != (struct ipasfrag *)fp && ip->ip_off + ip->ip_len > q->ip_off) { struct mbuf *m0; i = (ip->ip_off + ip->ip_len) - q->ip_off; if (i < q->ip_len) { q->ip_len -= i; q->ip_off += i; m_adj(dtom(q), i); break; } m0 = dtom(q); q = q->ipf_next; ip_deq(q->ipf_prev); m_freem(m0); } insert: #ifdef IPDIVERT /* * Any fragment diverting causes the whole packet to divert */ if (frag_divert_port != 0) fp->ipq_divert = frag_divert_port; frag_divert_port = 0; #endif /* * Stick new segment in its place; * check for complete reassembly. */ ip_enq(ip, q->ipf_prev); next = 0; for (q = fp->ipq_next; q != (struct ipasfrag *)fp; q = q->ipf_next) { if (q->ip_off != next) return (0); next += q->ip_len; } if (q->ipf_prev->ipf_mff & 1) return (0); /* * Reassembly is complete. Make sure the packet is a sane size. */ if (next + (IP_VHL_HL(((struct ip *)fp->ipq_next)->ip_vhl) << 2) > IP_MAXPACKET) { ipstat.ips_toolong++; ip_freef(fp); return (0); } /* * Concatenate fragments. */ q = fp->ipq_next; m = dtom(q); t = m->m_next; m->m_next = 0; m_cat(m, t); q = q->ipf_next; while (q != (struct ipasfrag *)fp) { t = dtom(q); q = q->ipf_next; m_cat(m, t); } #ifdef IPDIVERT /* * Record divert port for packet, if any */ frag_divert_port = fp->ipq_divert; #endif /* * Create header for new ip packet by * modifying header of first packet; * dequeue and discard fragment reassembly header. * Make header visible. */ ip = fp->ipq_next; ip->ip_len = next; ip->ipf_mff &= ~1; ((struct ip *)ip)->ip_src = fp->ipq_src; ((struct ip *)ip)->ip_dst = fp->ipq_dst; remque(fp); (void) m_free(dtom(fp)); m = dtom(ip); m->m_len += (ip->ip_hl << 2); m->m_data -= (ip->ip_hl << 2); /* some debugging cruft by sklower, below, will go away soon */ if (m->m_flags & M_PKTHDR) { /* XXX this should be done elsewhere */ register int plen = 0; for (t = m; m; m = m->m_next) plen += m->m_len; t->m_pkthdr.len = plen; } return ((struct ip *)ip); dropfrag: ipstat.ips_fragdropped++; m_freem(m); return (0); } /* * Free a fragment reassembly header and all * associated datagrams. */ static void ip_freef(fp) struct ipq *fp; { register struct ipasfrag *q, *p; for (q = fp->ipq_next; q != (struct ipasfrag *)fp; q = p) { p = q->ipf_next; ip_deq(q); m_freem(dtom(q)); } remque(fp); (void) m_free(dtom(fp)); } /* * Put an ip fragment on a reassembly chain. * Like insque, but pointers in middle of structure. */ static void ip_enq(p, prev) register struct ipasfrag *p, *prev; { p->ipf_prev = prev; p->ipf_next = prev->ipf_next; prev->ipf_next->ipf_prev = p; prev->ipf_next = p; } /* * To ip_enq as remque is to insque. */ static void ip_deq(p) register struct ipasfrag *p; { p->ipf_prev->ipf_next = p->ipf_next; p->ipf_next->ipf_prev = p->ipf_prev; } /* * IP timer processing; * if a timer expires on a reassembly * queue, discard it. */ void ip_slowtimo() { register struct ipq *fp; int s = splnet(); fp = ipq.next; if (fp == 0) { splx(s); return; } while (fp != &ipq) { --fp->ipq_ttl; fp = fp->next; if (fp->prev->ipq_ttl == 0) { ipstat.ips_fragtimeout++; ip_freef(fp->prev); } } splx(s); } /* * Drain off all datagram fragments. */ void ip_drain() { while (ipq.next != &ipq) { ipstat.ips_fragdropped++; ip_freef(ipq.next); } in_rtqdrain(); } /* * Do option processing on a datagram, * possibly discarding it if bad options are encountered, * or forwarding it if source-routed. * Returns 1 if packet has been forwarded/freed, * 0 if the packet should be processed further. */ static int ip_dooptions(m) struct mbuf *m; { register struct ip *ip = mtod(m, struct ip *); register u_char *cp; register struct ip_timestamp *ipt; register struct in_ifaddr *ia; int opt, optlen, cnt, off, code, type = ICMP_PARAMPROB, forward = 0; struct in_addr *sin, dst; n_time ntime; dst = ip->ip_dst; cp = (u_char *)(ip + 1); cnt = (IP_VHL_HL(ip->ip_vhl) << 2) - sizeof (struct ip); for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[IPOPT_OPTVAL]; if (opt == IPOPT_EOL) break; if (opt == IPOPT_NOP) optlen = 1; else { optlen = cp[IPOPT_OLEN]; if (optlen <= 0 || optlen > cnt) { code = &cp[IPOPT_OLEN] - (u_char *)ip; goto bad; } } switch (opt) { default: break; /* * Source routing with record. * Find interface with current destination address. * If none on this machine then drop if strictly routed, * or do nothing if loosely routed. * Record interface address and bring up next address * component. If strictly routed make sure next * address is on directly accessible net. */ case IPOPT_LSRR: case IPOPT_SSRR: if ((off = cp[IPOPT_OFFSET]) < IPOPT_MINOFF) { code = &cp[IPOPT_OFFSET] - (u_char *)ip; goto bad; } ipaddr.sin_addr = ip->ip_dst; ia = (struct in_ifaddr *) ifa_ifwithaddr((struct sockaddr *)&ipaddr); if (ia == 0) { if (opt == IPOPT_SSRR) { type = ICMP_UNREACH; code = ICMP_UNREACH_SRCFAIL; goto bad; } /* * Loose routing, and not at next destination * yet; nothing to do except forward. */ break; } off--; /* 0 origin */ if (off > optlen - sizeof(struct in_addr)) { /* * End of source route. Should be for us. */ save_rte(cp, ip->ip_src); break; } if (!ip_dosourceroute) { char buf[4*sizeof "123"]; strcpy(buf, inet_ntoa(ip->ip_dst)); log(LOG_WARNING, "attempted source route from %s to %s\n", inet_ntoa(ip->ip_src), buf); type = ICMP_UNREACH; code = ICMP_UNREACH_SRCFAIL; goto bad; } /* * locate outgoing interface */ (void)memcpy(&ipaddr.sin_addr, cp + off, sizeof(ipaddr.sin_addr)); if (opt == IPOPT_SSRR) { #define INA struct in_ifaddr * #define SA struct sockaddr * if ((ia = (INA)ifa_ifwithdstaddr((SA)&ipaddr)) == 0) ia = (INA)ifa_ifwithnet((SA)&ipaddr); } else ia = ip_rtaddr(ipaddr.sin_addr); if (ia == 0) { type = ICMP_UNREACH; code = ICMP_UNREACH_SRCFAIL; goto bad; } ip->ip_dst = ipaddr.sin_addr; (void)memcpy(cp + off, &(IA_SIN(ia)->sin_addr), sizeof(struct in_addr)); cp[IPOPT_OFFSET] += sizeof(struct in_addr); /* * Let ip_intr's mcast routing check handle mcast pkts */ forward = !IN_MULTICAST(ntohl(ip->ip_dst.s_addr)); break; case IPOPT_RR: if ((off = cp[IPOPT_OFFSET]) < IPOPT_MINOFF) { code = &cp[IPOPT_OFFSET] - (u_char *)ip; goto bad; } /* * If no space remains, ignore. */ off--; /* 0 origin */ if (off > optlen - sizeof(struct in_addr)) break; (void)memcpy(&ipaddr.sin_addr, &ip->ip_dst, sizeof(ipaddr.sin_addr)); /* * locate outgoing interface; if we're the destination, * use the incoming interface (should be same). */ if ((ia = (INA)ifa_ifwithaddr((SA)&ipaddr)) == 0 && (ia = ip_rtaddr(ipaddr.sin_addr)) == 0) { type = ICMP_UNREACH; code = ICMP_UNREACH_HOST; goto bad; } (void)memcpy(cp + off, &(IA_SIN(ia)->sin_addr), sizeof(struct in_addr)); cp[IPOPT_OFFSET] += sizeof(struct in_addr); break; case IPOPT_TS: code = cp - (u_char *)ip; ipt = (struct ip_timestamp *)cp; if (ipt->ipt_len < 5) goto bad; if (ipt->ipt_ptr > ipt->ipt_len - sizeof (long)) { if (++ipt->ipt_oflw == 0) goto bad; break; } sin = (struct in_addr *)(cp + ipt->ipt_ptr - 1); switch (ipt->ipt_flg) { case IPOPT_TS_TSONLY: break; case IPOPT_TS_TSANDADDR: if (ipt->ipt_ptr + sizeof(n_time) + sizeof(struct in_addr) > ipt->ipt_len) goto bad; ipaddr.sin_addr = dst; ia = (INA)ifaof_ifpforaddr((SA)&ipaddr, m->m_pkthdr.rcvif); if (ia == 0) continue; (void)memcpy(sin, &IA_SIN(ia)->sin_addr, sizeof(struct in_addr)); ipt->ipt_ptr += sizeof(struct in_addr); break; case IPOPT_TS_PRESPEC: if (ipt->ipt_ptr + sizeof(n_time) + sizeof(struct in_addr) > ipt->ipt_len) goto bad; (void)memcpy(&ipaddr.sin_addr, sin, sizeof(struct in_addr)); if (ifa_ifwithaddr((SA)&ipaddr) == 0) continue; ipt->ipt_ptr += sizeof(struct in_addr); break; default: goto bad; } ntime = iptime(); (void)memcpy(cp + ipt->ipt_ptr - 1, &ntime, sizeof(n_time)); ipt->ipt_ptr += sizeof(n_time); } } if (forward) { ip_forward(m, 1); return (1); } return (0); bad: ip->ip_len -= IP_VHL_HL(ip->ip_vhl) << 2; /* XXX icmp_error adds in hdr length */ icmp_error(m, type, code, 0, 0); ipstat.ips_badoptions++; return (1); } /* * Given address of next destination (final or next hop), * return internet address info of interface to be used to get there. */ static struct in_ifaddr * ip_rtaddr(dst) struct in_addr dst; { register struct sockaddr_in *sin; sin = (struct sockaddr_in *) &ipforward_rt.ro_dst; if (ipforward_rt.ro_rt == 0 || dst.s_addr != sin->sin_addr.s_addr) { if (ipforward_rt.ro_rt) { RTFREE(ipforward_rt.ro_rt); ipforward_rt.ro_rt = 0; } sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_addr = dst; rtalloc_ign(&ipforward_rt, RTF_PRCLONING); } if (ipforward_rt.ro_rt == 0) return ((struct in_ifaddr *)0); return ((struct in_ifaddr *) ipforward_rt.ro_rt->rt_ifa); } /* * Save incoming source route for use in replies, * to be picked up later by ip_srcroute if the receiver is interested. */ void save_rte(option, dst) u_char *option; struct in_addr dst; { unsigned olen; olen = option[IPOPT_OLEN]; #ifdef DIAGNOSTIC if (ipprintfs) printf("save_rte: olen %d\n", olen); #endif if (olen > sizeof(ip_srcrt) - (1 + sizeof(dst))) return; bcopy(option, ip_srcrt.srcopt, olen); ip_nhops = (olen - IPOPT_OFFSET - 1) / sizeof(struct in_addr); ip_srcrt.dst = dst; } /* * Retrieve incoming source route for use in replies, * in the same form used by setsockopt. * The first hop is placed before the options, will be removed later. */ struct mbuf * ip_srcroute() { register struct in_addr *p, *q; register struct mbuf *m; if (ip_nhops == 0) return ((struct mbuf *)0); m = m_get(M_DONTWAIT, MT_SOOPTS); if (m == 0) return ((struct mbuf *)0); #define OPTSIZ (sizeof(ip_srcrt.nop) + sizeof(ip_srcrt.srcopt)) /* length is (nhops+1)*sizeof(addr) + sizeof(nop + srcrt header) */ m->m_len = ip_nhops * sizeof(struct in_addr) + sizeof(struct in_addr) + OPTSIZ; #ifdef DIAGNOSTIC if (ipprintfs) printf("ip_srcroute: nhops %d mlen %d", ip_nhops, m->m_len); #endif /* * First save first hop for return route */ p = &ip_srcrt.route[ip_nhops - 1]; *(mtod(m, struct in_addr *)) = *p--; #ifdef DIAGNOSTIC if (ipprintfs) printf(" hops %lx", ntohl(mtod(m, struct in_addr *)->s_addr)); #endif /* * Copy option fields and padding (nop) to mbuf. */ ip_srcrt.nop = IPOPT_NOP; ip_srcrt.srcopt[IPOPT_OFFSET] = IPOPT_MINOFF; (void)memcpy(mtod(m, caddr_t) + sizeof(struct in_addr), &ip_srcrt.nop, OPTSIZ); q = (struct in_addr *)(mtod(m, caddr_t) + sizeof(struct in_addr) + OPTSIZ); #undef OPTSIZ /* * Record return path as an IP source route, * reversing the path (pointers are now aligned). */ while (p >= ip_srcrt.route) { #ifdef DIAGNOSTIC if (ipprintfs) printf(" %lx", ntohl(q->s_addr)); #endif *q++ = *p--; } /* * Last hop goes to final destination. */ *q = ip_srcrt.dst; #ifdef DIAGNOSTIC if (ipprintfs) printf(" %lx\n", ntohl(q->s_addr)); #endif return (m); } /* * Strip out IP options, at higher * level protocol in the kernel. * Second argument is buffer to which options * will be moved, and return value is their length. * XXX should be deleted; last arg currently ignored. */ void ip_stripoptions(m, mopt) register struct mbuf *m; struct mbuf *mopt; { register int i; struct ip *ip = mtod(m, struct ip *); register caddr_t opts; int olen; olen = (IP_VHL_HL(ip->ip_vhl) << 2) - sizeof (struct ip); opts = (caddr_t)(ip + 1); i = m->m_len - (sizeof (struct ip) + olen); bcopy(opts + olen, opts, (unsigned)i); m->m_len -= olen; if (m->m_flags & M_PKTHDR) m->m_pkthdr.len -= olen; ip->ip_vhl = IP_MAKE_VHL(IPVERSION, sizeof(struct ip) >> 2); } u_char inetctlerrmap[PRC_NCMDS] = { 0, 0, 0, 0, 0, EMSGSIZE, EHOSTDOWN, EHOSTUNREACH, EHOSTUNREACH, EHOSTUNREACH, ECONNREFUSED, ECONNREFUSED, EMSGSIZE, EHOSTUNREACH, 0, 0, 0, 0, 0, 0, ENOPROTOOPT }; /* * Forward a packet. If some error occurs return the sender * an icmp packet. Note we can't always generate a meaningful * icmp message because icmp doesn't have a large enough repertoire * of codes and types. * * If not forwarding, just drop the packet. This could be confusing * if ipforwarding was zero but some routing protocol was advancing * us as a gateway to somewhere. However, we must let the routing * protocol deal with that. * * The srcrt parameter indicates whether the packet is being forwarded * via a source route. */ static void ip_forward(m, srcrt) struct mbuf *m; int srcrt; { register struct ip *ip = mtod(m, struct ip *); register struct sockaddr_in *sin; register struct rtentry *rt; int error, type = 0, code = 0; struct mbuf *mcopy; n_long dest; struct ifnet *destifp; dest = 0; #ifdef DIAGNOSTIC if (ipprintfs) printf("forward: src %lx dst %lx ttl %x\n", ip->ip_src.s_addr, ip->ip_dst.s_addr, ip->ip_ttl); #endif if (m->m_flags & M_BCAST || in_canforward(ip->ip_dst) == 0) { ipstat.ips_cantforward++; m_freem(m); return; } HTONS(ip->ip_id); if (ip->ip_ttl <= IPTTLDEC) { icmp_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, dest, 0); return; } ip->ip_ttl -= IPTTLDEC; sin = (struct sockaddr_in *)&ipforward_rt.ro_dst; if ((rt = ipforward_rt.ro_rt) == 0 || ip->ip_dst.s_addr != sin->sin_addr.s_addr) { if (ipforward_rt.ro_rt) { RTFREE(ipforward_rt.ro_rt); ipforward_rt.ro_rt = 0; } sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_addr = ip->ip_dst; rtalloc_ign(&ipforward_rt, RTF_PRCLONING); if (ipforward_rt.ro_rt == 0) { icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_HOST, dest, 0); return; } rt = ipforward_rt.ro_rt; } /* * Save at most 64 bytes of the packet in case * we need to generate an ICMP message to the src. */ mcopy = m_copy(m, 0, imin((int)ip->ip_len, 64)); /* * If forwarding packet using same interface that it came in on, * perhaps should send a redirect to sender to shortcut a hop. * Only send redirect if source is sending directly to us, * and if packet was not source routed (or has any options). * Also, don't send redirect if forwarding using a default route * or a route modified by a redirect. */ #define satosin(sa) ((struct sockaddr_in *)(sa)) if (rt->rt_ifp == m->m_pkthdr.rcvif && (rt->rt_flags & (RTF_DYNAMIC|RTF_MODIFIED)) == 0 && satosin(rt_key(rt))->sin_addr.s_addr != 0 && ipsendredirects && !srcrt) { #define RTA(rt) ((struct in_ifaddr *)(rt->rt_ifa)) u_long src = ntohl(ip->ip_src.s_addr); if (RTA(rt) && (src & RTA(rt)->ia_subnetmask) == RTA(rt)->ia_subnet) { if (rt->rt_flags & RTF_GATEWAY) dest = satosin(rt->rt_gateway)->sin_addr.s_addr; else dest = ip->ip_dst.s_addr; /* Router requirements says to only send host redirects */ type = ICMP_REDIRECT; code = ICMP_REDIRECT_HOST; #ifdef DIAGNOSTIC if (ipprintfs) printf("redirect (%d) to %lx\n", code, (u_long)dest); #endif } } error = ip_output(m, (struct mbuf *)0, &ipforward_rt, IP_FORWARDING, 0); if (error) ipstat.ips_cantforward++; else { ipstat.ips_forward++; if (type) ipstat.ips_redirectsent++; else { if (mcopy) m_freem(mcopy); return; } } if (mcopy == NULL) return; destifp = NULL; switch (error) { case 0: /* forwarded, but need redirect */ /* type, code set above */ break; case ENETUNREACH: /* shouldn't happen, checked above */ case EHOSTUNREACH: case ENETDOWN: case EHOSTDOWN: default: type = ICMP_UNREACH; code = ICMP_UNREACH_HOST; break; case EMSGSIZE: type = ICMP_UNREACH; code = ICMP_UNREACH_NEEDFRAG; if (ipforward_rt.ro_rt) destifp = ipforward_rt.ro_rt->rt_ifp; ipstat.ips_cantfrag++; break; case ENOBUFS: type = ICMP_SOURCEQUENCH; code = 0; break; } icmp_error(mcopy, type, code, dest, destifp); } void ip_savecontrol(inp, mp, ip, m) register struct inpcb *inp; register struct mbuf **mp; register struct ip *ip; register struct mbuf *m; { if (inp->inp_socket->so_options & SO_TIMESTAMP) { struct timeval tv; microtime(&tv); *mp = sbcreatecontrol((caddr_t) &tv, sizeof(tv), SCM_TIMESTAMP, SOL_SOCKET); if (*mp) mp = &(*mp)->m_next; } if (inp->inp_flags & INP_RECVDSTADDR) { *mp = sbcreatecontrol((caddr_t) &ip->ip_dst, sizeof(struct in_addr), IP_RECVDSTADDR, IPPROTO_IP); if (*mp) mp = &(*mp)->m_next; } #ifdef notyet /* XXX * Moving these out of udp_input() made them even more broken * than they already were. */ /* options were tossed already */ if (inp->inp_flags & INP_RECVOPTS) { *mp = sbcreatecontrol((caddr_t) opts_deleted_above, sizeof(struct in_addr), IP_RECVOPTS, IPPROTO_IP); if (*mp) mp = &(*mp)->m_next; } /* ip_srcroute doesn't do what we want here, need to fix */ if (inp->inp_flags & INP_RECVRETOPTS) { *mp = sbcreatecontrol((caddr_t) ip_srcroute(), sizeof(struct in_addr), IP_RECVRETOPTS, IPPROTO_IP); if (*mp) mp = &(*mp)->m_next; } #endif if (inp->inp_flags & INP_RECVIF) { struct sockaddr_dl sdl; sdl.sdl_len = offsetof(struct sockaddr_dl, sdl_data[0]); sdl.sdl_family = AF_LINK; sdl.sdl_index = m->m_pkthdr.rcvif ? m->m_pkthdr.rcvif->if_index : 0; sdl.sdl_nlen = sdl.sdl_alen = sdl.sdl_slen = 0; *mp = sbcreatecontrol((caddr_t) &sdl, sdl.sdl_len, IP_RECVIF, IPPROTO_IP); if (*mp) mp = &(*mp)->m_next; } } int ip_rsvp_init(struct socket *so) { if (so->so_type != SOCK_RAW || so->so_proto->pr_protocol != IPPROTO_RSVP) return EOPNOTSUPP; if (ip_rsvpd != NULL) return EADDRINUSE; ip_rsvpd = so; /* * This may seem silly, but we need to be sure we don't over-increment * the RSVP counter, in case something slips up. */ if (!ip_rsvp_on) { ip_rsvp_on = 1; rsvp_on++; } return 0; } int ip_rsvp_done(void) { ip_rsvpd = NULL; /* * This may seem silly, but we need to be sure we don't over-decrement * the RSVP counter, in case something slips up. */ if (ip_rsvp_on) { ip_rsvp_on = 0; rsvp_on--; } return 0; } diff --git a/sys/netinet/ip_output.c b/sys/netinet/ip_output.c index 8addbe65984c..ba5b4bd3f9c0 100644 --- a/sys/netinet/ip_output.c +++ b/sys/netinet/ip_output.c @@ -1,1308 +1,1309 @@ /* * Copyright (c) 1982, 1986, 1988, 1990, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ip_output.c 8.3 (Berkeley) 1/21/94 - * $Id: ip_output.c,v 1.44 1996/10/22 22:26:02 sos Exp $ + * $Id: ip_output.c,v 1.45 1996/11/11 04:56:19 fenner Exp $ */ #define _IP_VHL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef vax #include #endif #include #if !defined(COMPAT_IPFW) || COMPAT_IPFW == 1 #undef COMPAT_IPFW #define COMPAT_IPFW 1 #else #undef COMPAT_IPFW #endif u_short ip_id; static struct mbuf *ip_insertoptions __P((struct mbuf *, struct mbuf *, int *)); static void ip_mloopback __P((struct ifnet *, struct mbuf *, struct sockaddr_in *)); static int ip_getmoptions __P((int, struct ip_moptions *, struct mbuf **)); static int ip_optcopy __P((struct ip *, struct ip *)); static int ip_pcbopts __P((struct mbuf **, struct mbuf *)); static int ip_setmoptions __P((int, struct ip_moptions **, struct mbuf *)); extern struct protosw inetsw[]; /* * IP output. The packet in mbuf chain m contains a skeletal IP * header (with len, off, ttl, proto, tos, src, dst). * The mbuf chain containing the packet will be freed. * The mbuf opt, if present, will not be freed. */ int ip_output(m0, opt, ro, flags, imo) struct mbuf *m0; struct mbuf *opt; struct route *ro; int flags; struct ip_moptions *imo; { struct ip *ip, *mhip; struct ifnet *ifp; struct mbuf *m = m0; int hlen = sizeof (struct ip); int len, off, error = 0; struct sockaddr_in *dst; struct in_ifaddr *ia; int isbroadcast; #ifdef DIAGNOSTIC if ((m->m_flags & M_PKTHDR) == 0) panic("ip_output no HDR"); if (!ro) panic("ip_output no route, proto = %d", mtod(m, struct ip *)->ip_p); #endif if (opt) { m = ip_insertoptions(m, opt, &len); hlen = len; } ip = mtod(m, struct ip *); /* * Fill in IP header. */ if ((flags & (IP_FORWARDING|IP_RAWOUTPUT)) == 0) { ip->ip_vhl = IP_MAKE_VHL(IPVERSION, hlen >> 2); ip->ip_off &= IP_DF; ip->ip_id = htons(ip_id++); ipstat.ips_localout++; } else { hlen = IP_VHL_HL(ip->ip_vhl) << 2; } dst = (struct sockaddr_in *)&ro->ro_dst; /* * If there is a cached route, * check that it is to the same destination * and is still up. If not, free it and try again. */ if (ro->ro_rt && ((ro->ro_rt->rt_flags & RTF_UP) == 0 || dst->sin_addr.s_addr != ip->ip_dst.s_addr)) { RTFREE(ro->ro_rt); ro->ro_rt = (struct rtentry *)0; } if (ro->ro_rt == 0) { dst->sin_family = AF_INET; dst->sin_len = sizeof(*dst); dst->sin_addr = ip->ip_dst; } /* * If routing to interface only, * short circuit routing lookup. */ #define ifatoia(ifa) ((struct in_ifaddr *)(ifa)) #define sintosa(sin) ((struct sockaddr *)(sin)) if (flags & IP_ROUTETOIF) { if ((ia = ifatoia(ifa_ifwithdstaddr(sintosa(dst)))) == 0 && (ia = ifatoia(ifa_ifwithnet(sintosa(dst)))) == 0) { ipstat.ips_noroute++; error = ENETUNREACH; goto bad; } ifp = ia->ia_ifp; ip->ip_ttl = 1; isbroadcast = in_broadcast(dst->sin_addr, ifp); } else { /* * If this is the case, we probably don't want to allocate * a protocol-cloned route since we didn't get one from the * ULP. This lets TCP do its thing, while not burdening * forwarding or ICMP with the overhead of cloning a route. * Of course, we still want to do any cloning requested by * the link layer, as this is probably required in all cases * for correct operation (as it is for ARP). */ if (ro->ro_rt == 0) rtalloc_ign(ro, RTF_PRCLONING); if (ro->ro_rt == 0) { ipstat.ips_noroute++; error = EHOSTUNREACH; goto bad; } ia = ifatoia(ro->ro_rt->rt_ifa); ifp = ro->ro_rt->rt_ifp; ro->ro_rt->rt_use++; if (ro->ro_rt->rt_flags & RTF_GATEWAY) dst = (struct sockaddr_in *)ro->ro_rt->rt_gateway; if (ro->ro_rt->rt_flags & RTF_HOST) isbroadcast = (ro->ro_rt->rt_flags & RTF_BROADCAST); else isbroadcast = in_broadcast(dst->sin_addr, ifp); } if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr))) { struct in_multi *inm; m->m_flags |= M_MCAST; /* * IP destination address is multicast. Make sure "dst" * still points to the address in "ro". (It may have been * changed to point to a gateway address, above.) */ dst = (struct sockaddr_in *)&ro->ro_dst; /* * See if the caller provided any multicast options */ if (imo != NULL) { ip->ip_ttl = imo->imo_multicast_ttl; if (imo->imo_multicast_ifp != NULL) ifp = imo->imo_multicast_ifp; if (imo->imo_multicast_vif != -1) ip->ip_src.s_addr = ip_mcast_src(imo->imo_multicast_vif); } else ip->ip_ttl = IP_DEFAULT_MULTICAST_TTL; /* * Confirm that the outgoing interface supports multicast. */ if ((imo == NULL) || (imo->imo_multicast_vif == -1)) { if ((ifp->if_flags & IFF_MULTICAST) == 0) { ipstat.ips_noroute++; error = ENETUNREACH; goto bad; } } /* * If source address not specified yet, use address * of outgoing interface. */ if (ip->ip_src.s_addr == INADDR_ANY) { register struct in_ifaddr *ia; - for (ia = in_ifaddr; ia; ia = ia->ia_next) + for (ia = in_ifaddrhead.tqh_first; ia; + ia = ia->ia_link.tqe_next) if (ia->ia_ifp == ifp) { ip->ip_src = IA_SIN(ia)->sin_addr; break; } } IN_LOOKUP_MULTI(ip->ip_dst, ifp, inm); if (inm != NULL && (imo == NULL || imo->imo_multicast_loop)) { /* * If we belong to the destination multicast group * on the outgoing interface, and the caller did not * forbid loopback, loop back a copy. */ ip_mloopback(ifp, m, dst); } else { /* * If we are acting as a multicast router, perform * multicast forwarding as if the packet had just * arrived on the interface to which we are about * to send. The multicast forwarding function * recursively calls this function, using the * IP_FORWARDING flag to prevent infinite recursion. * * Multicasts that are looped back by ip_mloopback(), * above, will be forwarded by the ip_input() routine, * if necessary. */ if (ip_mrouter && (flags & IP_FORWARDING) == 0) { /* * Check if rsvp daemon is running. If not, don't * set ip_moptions. This ensures that the packet * is multicast and not just sent down one link * as prescribed by rsvpd. */ if (!rsvp_on) imo = NULL; if (ip_mforward(ip, ifp, m, imo) != 0) { m_freem(m); goto done; } } } /* * Multicasts with a time-to-live of zero may be looped- * back, above, but must not be transmitted on a network. * Also, multicasts addressed to the loopback interface * are not sent -- the above call to ip_mloopback() will * loop back a copy if this host actually belongs to the * destination group on the loopback interface. */ if (ip->ip_ttl == 0 || ifp->if_flags & IFF_LOOPBACK) { m_freem(m); goto done; } goto sendit; } #ifndef notdef /* * If source address not specified yet, use address * of outgoing interface. */ if (ip->ip_src.s_addr == INADDR_ANY) ip->ip_src = IA_SIN(ia)->sin_addr; #endif /* * Verify that we have any chance at all of being able to queue * the packet or packet fragments */ if ((ifp->if_snd.ifq_len + ip->ip_len / ifp->if_mtu + 1) >= ifp->if_snd.ifq_maxlen) { error = ENOBUFS; goto bad; } /* * Look for broadcast address and * and verify user is allowed to send * such a packet. */ if (isbroadcast) { if ((ifp->if_flags & IFF_BROADCAST) == 0) { error = EADDRNOTAVAIL; goto bad; } if ((flags & IP_ALLOWBROADCAST) == 0) { error = EACCES; goto bad; } /* don't allow broadcast messages to be fragmented */ if ((u_short)ip->ip_len > ifp->if_mtu) { error = EMSGSIZE; goto bad; } m->m_flags |= M_BCAST; } else { m->m_flags &= ~M_BCAST; } sendit: /* * IpHack's section. * - Xlate: translate packet's addr/port (NAT). * - Firewall: deny/allow * - Wrap: fake packet's addr/port * - Encapsulate: put it in another IP and send out. */ #ifdef COMPAT_IPFW if (ip_nat_ptr && !(*ip_nat_ptr)(&ip, &m, ifp, IP_NAT_OUT)) { error = EACCES; goto done; } /* * Check with the firewall... */ if (ip_fw_chk_ptr) { int action; #ifdef IPDIVERT action = (*ip_fw_chk_ptr)(&ip, hlen, ifp, (~0 << 16) | ip_divert_ignore, &m); #else action = (*ip_fw_chk_ptr)(&ip, hlen, ifp, (~0 << 16), &m); #endif if (action == -1) { error = EACCES; /* XXX is this appropriate? */ goto done; } else if (action != 0) { #ifdef IPDIVERT ip_divert_port = action; /* divert to port */ (*inetsw[ip_protox[IPPROTO_DIVERT]].pr_input)(m, 0); goto done; #else m_freem(m); /* ipfw says divert, but we can't */ goto done; #endif } } #endif /* COMPAT_IPFW */ /* * If small enough for interface, can just send directly. */ if ((u_short)ip->ip_len <= ifp->if_mtu) { ip->ip_len = htons((u_short)ip->ip_len); ip->ip_off = htons((u_short)ip->ip_off); ip->ip_sum = 0; if (ip->ip_vhl == IP_VHL_BORING) { ip->ip_sum = in_cksum_hdr(ip); } else { ip->ip_sum = in_cksum(m, hlen); } error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, ro->ro_rt); goto done; } /* * Too large for interface; fragment if possible. * Must be able to put at least 8 bytes per fragment. */ if (ip->ip_off & IP_DF) { error = EMSGSIZE; /* * This case can happen if the user changed the MTU * of an interface after enabling IP on it. Because * most netifs don't keep track of routes pointing to * them, there is no way for one to update all its * routes when the MTU is changed. */ if ((ro->ro_rt->rt_flags & (RTF_UP | RTF_HOST)) && !(ro->ro_rt->rt_rmx.rmx_locks & RTV_MTU) && (ro->ro_rt->rt_rmx.rmx_mtu > ifp->if_mtu)) { ro->ro_rt->rt_rmx.rmx_mtu = ifp->if_mtu; } ipstat.ips_cantfrag++; goto bad; } len = (ifp->if_mtu - hlen) &~ 7; if (len < 8) { error = EMSGSIZE; goto bad; } { int mhlen, firstlen = len; struct mbuf **mnext = &m->m_nextpkt; /* * Loop through length of segment after first fragment, * make new header and copy data of each part and link onto chain. */ m0 = m; mhlen = sizeof (struct ip); for (off = hlen + len; off < (u_short)ip->ip_len; off += len) { MGETHDR(m, M_DONTWAIT, MT_HEADER); if (m == 0) { error = ENOBUFS; ipstat.ips_odropped++; goto sendorfree; } m->m_data += max_linkhdr; mhip = mtod(m, struct ip *); *mhip = *ip; if (hlen > sizeof (struct ip)) { mhlen = ip_optcopy(ip, mhip) + sizeof (struct ip); mhip->ip_vhl = IP_MAKE_VHL(IPVERSION, mhlen >> 2); } m->m_len = mhlen; mhip->ip_off = ((off - hlen) >> 3) + (ip->ip_off & ~IP_MF); if (ip->ip_off & IP_MF) mhip->ip_off |= IP_MF; if (off + len >= (u_short)ip->ip_len) len = (u_short)ip->ip_len - off; else mhip->ip_off |= IP_MF; mhip->ip_len = htons((u_short)(len + mhlen)); m->m_next = m_copy(m0, off, len); if (m->m_next == 0) { (void) m_free(m); error = ENOBUFS; /* ??? */ ipstat.ips_odropped++; goto sendorfree; } m->m_pkthdr.len = mhlen + len; m->m_pkthdr.rcvif = (struct ifnet *)0; mhip->ip_off = htons((u_short)mhip->ip_off); mhip->ip_sum = 0; if (mhip->ip_vhl == IP_VHL_BORING) { mhip->ip_sum = in_cksum_hdr(mhip); } else { mhip->ip_sum = in_cksum(m, mhlen); } *mnext = m; mnext = &m->m_nextpkt; ipstat.ips_ofragments++; } /* * Update first fragment by trimming what's been copied out * and updating header, then send each fragment (in order). */ m = m0; m_adj(m, hlen + firstlen - (u_short)ip->ip_len); m->m_pkthdr.len = hlen + firstlen; ip->ip_len = htons((u_short)m->m_pkthdr.len); ip->ip_off = htons((u_short)(ip->ip_off | IP_MF)); ip->ip_sum = 0; if (ip->ip_vhl == IP_VHL_BORING) { ip->ip_sum = in_cksum_hdr(ip); } else { ip->ip_sum = in_cksum(m, hlen); } sendorfree: for (m = m0; m; m = m0) { m0 = m->m_nextpkt; m->m_nextpkt = 0; if (error == 0) error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, ro->ro_rt); else m_freem(m); } if (error == 0) ipstat.ips_fragmented++; } done: return (error); bad: m_freem(m0); goto done; } /* * Insert IP options into preformed packet. * Adjust IP destination as required for IP source routing, * as indicated by a non-zero in_addr at the start of the options. * * XXX This routine assumes that the packet has no options in place. */ static struct mbuf * ip_insertoptions(m, opt, phlen) register struct mbuf *m; struct mbuf *opt; int *phlen; { register struct ipoption *p = mtod(opt, struct ipoption *); struct mbuf *n; register struct ip *ip = mtod(m, struct ip *); unsigned optlen; optlen = opt->m_len - sizeof(p->ipopt_dst); if (optlen + (u_short)ip->ip_len > IP_MAXPACKET) return (m); /* XXX should fail */ if (p->ipopt_dst.s_addr) ip->ip_dst = p->ipopt_dst; if (m->m_flags & M_EXT || m->m_data - optlen < m->m_pktdat) { MGETHDR(n, M_DONTWAIT, MT_HEADER); if (n == 0) return (m); n->m_pkthdr.len = m->m_pkthdr.len + optlen; m->m_len -= sizeof(struct ip); m->m_data += sizeof(struct ip); n->m_next = m; m = n; m->m_len = optlen + sizeof(struct ip); m->m_data += max_linkhdr; (void)memcpy(mtod(m, void *), ip, sizeof(struct ip)); } else { m->m_data -= optlen; m->m_len += optlen; m->m_pkthdr.len += optlen; ovbcopy((caddr_t)ip, mtod(m, caddr_t), sizeof(struct ip)); } ip = mtod(m, struct ip *); bcopy(p->ipopt_list, ip + 1, optlen); *phlen = sizeof(struct ip) + optlen; ip->ip_vhl = IP_MAKE_VHL(IPVERSION, *phlen >> 2); ip->ip_len += optlen; return (m); } /* * Copy options from ip to jp, * omitting those not copied during fragmentation. */ static int ip_optcopy(ip, jp) struct ip *ip, *jp; { register u_char *cp, *dp; int opt, optlen, cnt; cp = (u_char *)(ip + 1); dp = (u_char *)(jp + 1); cnt = (IP_VHL_HL(ip->ip_vhl) << 2) - sizeof (struct ip); for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[0]; if (opt == IPOPT_EOL) break; if (opt == IPOPT_NOP) { /* Preserve for IP mcast tunnel's LSRR alignment. */ *dp++ = IPOPT_NOP; optlen = 1; continue; } else optlen = cp[IPOPT_OLEN]; /* bogus lengths should have been caught by ip_dooptions */ if (optlen > cnt) optlen = cnt; if (IPOPT_COPIED(opt)) { bcopy(cp, dp, optlen); dp += optlen; } } for (optlen = dp - (u_char *)(jp+1); optlen & 0x3; optlen++) *dp++ = IPOPT_EOL; return (optlen); } /* * IP socket option processing. */ int ip_ctloutput(op, so, level, optname, mp) int op; struct socket *so; int level, optname; struct mbuf **mp; { register struct inpcb *inp = sotoinpcb(so); register struct mbuf *m = *mp; register int optval = 0; int error = 0; if (level != IPPROTO_IP) { error = EINVAL; if (op == PRCO_SETOPT && *mp) (void) m_free(*mp); } else switch (op) { case PRCO_SETOPT: switch (optname) { case IP_OPTIONS: #ifdef notyet case IP_RETOPTS: return (ip_pcbopts(optname, &inp->inp_options, m)); #else return (ip_pcbopts(&inp->inp_options, m)); #endif case IP_TOS: case IP_TTL: case IP_RECVOPTS: case IP_RECVRETOPTS: case IP_RECVDSTADDR: case IP_RECVIF: if (m == 0 || m->m_len != sizeof(int)) error = EINVAL; else { optval = *mtod(m, int *); switch (optname) { case IP_TOS: inp->inp_ip.ip_tos = optval; break; case IP_TTL: inp->inp_ip.ip_ttl = optval; break; #define OPTSET(bit) \ if (optval) \ inp->inp_flags |= bit; \ else \ inp->inp_flags &= ~bit; case IP_RECVOPTS: OPTSET(INP_RECVOPTS); break; case IP_RECVRETOPTS: OPTSET(INP_RECVRETOPTS); break; case IP_RECVDSTADDR: OPTSET(INP_RECVDSTADDR); break; case IP_RECVIF: OPTSET(INP_RECVIF); break; } } break; #undef OPTSET case IP_MULTICAST_IF: case IP_MULTICAST_VIF: case IP_MULTICAST_TTL: case IP_MULTICAST_LOOP: case IP_ADD_MEMBERSHIP: case IP_DROP_MEMBERSHIP: error = ip_setmoptions(optname, &inp->inp_moptions, m); break; case IP_PORTRANGE: if (m == 0 || m->m_len != sizeof(int)) error = EINVAL; else { optval = *mtod(m, int *); switch (optval) { case IP_PORTRANGE_DEFAULT: inp->inp_flags &= ~(INP_LOWPORT); inp->inp_flags &= ~(INP_HIGHPORT); break; case IP_PORTRANGE_HIGH: inp->inp_flags &= ~(INP_LOWPORT); inp->inp_flags |= INP_HIGHPORT; break; case IP_PORTRANGE_LOW: inp->inp_flags &= ~(INP_HIGHPORT); inp->inp_flags |= INP_LOWPORT; break; default: error = EINVAL; break; } } break; default: error = ENOPROTOOPT; break; } if (m) (void)m_free(m); break; case PRCO_GETOPT: switch (optname) { case IP_OPTIONS: case IP_RETOPTS: *mp = m = m_get(M_WAIT, MT_SOOPTS); if (inp->inp_options) { m->m_len = inp->inp_options->m_len; bcopy(mtod(inp->inp_options, void *), mtod(m, void *), m->m_len); } else m->m_len = 0; break; case IP_TOS: case IP_TTL: case IP_RECVOPTS: case IP_RECVRETOPTS: case IP_RECVDSTADDR: case IP_RECVIF: *mp = m = m_get(M_WAIT, MT_SOOPTS); m->m_len = sizeof(int); switch (optname) { case IP_TOS: optval = inp->inp_ip.ip_tos; break; case IP_TTL: optval = inp->inp_ip.ip_ttl; break; #define OPTBIT(bit) (inp->inp_flags & bit ? 1 : 0) case IP_RECVOPTS: optval = OPTBIT(INP_RECVOPTS); break; case IP_RECVRETOPTS: optval = OPTBIT(INP_RECVRETOPTS); break; case IP_RECVDSTADDR: optval = OPTBIT(INP_RECVDSTADDR); break; case IP_RECVIF: optval = OPTBIT(INP_RECVIF); break; } *mtod(m, int *) = optval; break; case IP_MULTICAST_IF: case IP_MULTICAST_VIF: case IP_MULTICAST_TTL: case IP_MULTICAST_LOOP: case IP_ADD_MEMBERSHIP: case IP_DROP_MEMBERSHIP: error = ip_getmoptions(optname, inp->inp_moptions, mp); break; case IP_PORTRANGE: *mp = m = m_get(M_WAIT, MT_SOOPTS); m->m_len = sizeof(int); if (inp->inp_flags & INP_HIGHPORT) optval = IP_PORTRANGE_HIGH; else if (inp->inp_flags & INP_LOWPORT) optval = IP_PORTRANGE_LOW; else optval = 0; *mtod(m, int *) = optval; break; default: error = ENOPROTOOPT; break; } break; } return (error); } /* * Set up IP options in pcb for insertion in output packets. * Store in mbuf with pointer in pcbopt, adding pseudo-option * with destination address if source routed. */ static int #ifdef notyet ip_pcbopts(optname, pcbopt, m) int optname; #else ip_pcbopts(pcbopt, m) #endif struct mbuf **pcbopt; register struct mbuf *m; { register cnt, optlen; register u_char *cp; u_char opt; /* turn off any old options */ if (*pcbopt) (void)m_free(*pcbopt); *pcbopt = 0; if (m == (struct mbuf *)0 || m->m_len == 0) { /* * Only turning off any previous options. */ if (m) (void)m_free(m); return (0); } #ifndef vax if (m->m_len % sizeof(long)) goto bad; #endif /* * IP first-hop destination address will be stored before * actual options; move other options back * and clear it when none present. */ if (m->m_data + m->m_len + sizeof(struct in_addr) >= &m->m_dat[MLEN]) goto bad; cnt = m->m_len; m->m_len += sizeof(struct in_addr); cp = mtod(m, u_char *) + sizeof(struct in_addr); ovbcopy(mtod(m, caddr_t), (caddr_t)cp, (unsigned)cnt); bzero(mtod(m, caddr_t), sizeof(struct in_addr)); for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[IPOPT_OPTVAL]; if (opt == IPOPT_EOL) break; if (opt == IPOPT_NOP) optlen = 1; else { optlen = cp[IPOPT_OLEN]; if (optlen <= IPOPT_OLEN || optlen > cnt) goto bad; } switch (opt) { default: break; case IPOPT_LSRR: case IPOPT_SSRR: /* * user process specifies route as: * ->A->B->C->D * D must be our final destination (but we can't * check that since we may not have connected yet). * A is first hop destination, which doesn't appear in * actual IP option, but is stored before the options. */ if (optlen < IPOPT_MINOFF - 1 + sizeof(struct in_addr)) goto bad; m->m_len -= sizeof(struct in_addr); cnt -= sizeof(struct in_addr); optlen -= sizeof(struct in_addr); cp[IPOPT_OLEN] = optlen; /* * Move first hop before start of options. */ bcopy((caddr_t)&cp[IPOPT_OFFSET+1], mtod(m, caddr_t), sizeof(struct in_addr)); /* * Then copy rest of options back * to close up the deleted entry. */ ovbcopy((caddr_t)(&cp[IPOPT_OFFSET+1] + sizeof(struct in_addr)), (caddr_t)&cp[IPOPT_OFFSET+1], (unsigned)cnt + sizeof(struct in_addr)); break; } } if (m->m_len > MAX_IPOPTLEN + sizeof(struct in_addr)) goto bad; *pcbopt = m; return (0); bad: (void)m_free(m); return (EINVAL); } /* * Set the IP multicast options in response to user setsockopt(). */ static int ip_setmoptions(optname, imop, m) int optname; struct ip_moptions **imop; struct mbuf *m; { register int error = 0; u_char loop; register int i; struct in_addr addr; register struct ip_mreq *mreq; register struct ifnet *ifp; register struct ip_moptions *imo = *imop; struct route ro; register struct sockaddr_in *dst; int s; if (imo == NULL) { /* * No multicast option buffer attached to the pcb; * allocate one and initialize to default values. */ imo = (struct ip_moptions*)malloc(sizeof(*imo), M_IPMOPTS, M_WAITOK); if (imo == NULL) return (ENOBUFS); *imop = imo; imo->imo_multicast_ifp = NULL; imo->imo_multicast_vif = -1; imo->imo_multicast_ttl = IP_DEFAULT_MULTICAST_TTL; imo->imo_multicast_loop = IP_DEFAULT_MULTICAST_LOOP; imo->imo_num_memberships = 0; } switch (optname) { /* store an index number for the vif you wanna use in the send */ case IP_MULTICAST_VIF: if (!legal_vif_num) { error = EOPNOTSUPP; break; } if (m == NULL || m->m_len != sizeof(int)) { error = EINVAL; break; } i = *(mtod(m, int *)); if (!legal_vif_num(i) && (i != -1)) { error = EINVAL; break; } imo->imo_multicast_vif = i; break; case IP_MULTICAST_IF: /* * Select the interface for outgoing multicast packets. */ if (m == NULL || m->m_len != sizeof(struct in_addr)) { error = EINVAL; break; } addr = *(mtod(m, struct in_addr *)); /* * INADDR_ANY is used to remove a previous selection. * When no interface is selected, a default one is * chosen every time a multicast packet is sent. */ if (addr.s_addr == INADDR_ANY) { imo->imo_multicast_ifp = NULL; break; } /* * The selected interface is identified by its local * IP address. Find the interface and confirm that * it supports multicasting. */ s = splimp(); INADDR_TO_IFP(addr, ifp); if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) { splx(s); error = EADDRNOTAVAIL; break; } imo->imo_multicast_ifp = ifp; splx(s); break; case IP_MULTICAST_TTL: /* * Set the IP time-to-live for outgoing multicast packets. */ if (m == NULL || m->m_len != 1) { error = EINVAL; break; } imo->imo_multicast_ttl = *(mtod(m, u_char *)); break; case IP_MULTICAST_LOOP: /* * Set the loopback flag for outgoing multicast packets. * Must be zero or one. */ if (m == NULL || m->m_len != 1 || (loop = *(mtod(m, u_char *))) > 1) { error = EINVAL; break; } imo->imo_multicast_loop = loop; break; case IP_ADD_MEMBERSHIP: /* * Add a multicast group membership. * Group must be a valid IP multicast address. */ if (m == NULL || m->m_len != sizeof(struct ip_mreq)) { error = EINVAL; break; } mreq = mtod(m, struct ip_mreq *); if (!IN_MULTICAST(ntohl(mreq->imr_multiaddr.s_addr))) { error = EINVAL; break; } s = splimp(); /* * If no interface address was provided, use the interface of * the route to the given multicast address. */ if (mreq->imr_interface.s_addr == INADDR_ANY) { bzero((caddr_t)&ro, sizeof(ro)); dst = (struct sockaddr_in *)&ro.ro_dst; dst->sin_len = sizeof(*dst); dst->sin_family = AF_INET; dst->sin_addr = mreq->imr_multiaddr; rtalloc(&ro); if (ro.ro_rt == NULL) { error = EADDRNOTAVAIL; splx(s); break; } ifp = ro.ro_rt->rt_ifp; rtfree(ro.ro_rt); } else { INADDR_TO_IFP(mreq->imr_interface, ifp); } /* * See if we found an interface, and confirm that it * supports multicast. */ if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) { error = EADDRNOTAVAIL; splx(s); break; } /* * See if the membership already exists or if all the * membership slots are full. */ for (i = 0; i < imo->imo_num_memberships; ++i) { if (imo->imo_membership[i]->inm_ifp == ifp && imo->imo_membership[i]->inm_addr.s_addr == mreq->imr_multiaddr.s_addr) break; } if (i < imo->imo_num_memberships) { error = EADDRINUSE; splx(s); break; } if (i == IP_MAX_MEMBERSHIPS) { error = ETOOMANYREFS; splx(s); break; } /* * Everything looks good; add a new record to the multicast * address list for the given interface. */ if ((imo->imo_membership[i] = in_addmulti(&mreq->imr_multiaddr, ifp)) == NULL) { error = ENOBUFS; splx(s); break; } ++imo->imo_num_memberships; splx(s); break; case IP_DROP_MEMBERSHIP: /* * Drop a multicast group membership. * Group must be a valid IP multicast address. */ if (m == NULL || m->m_len != sizeof(struct ip_mreq)) { error = EINVAL; break; } mreq = mtod(m, struct ip_mreq *); if (!IN_MULTICAST(ntohl(mreq->imr_multiaddr.s_addr))) { error = EINVAL; break; } s = splimp(); /* * If an interface address was specified, get a pointer * to its ifnet structure. */ if (mreq->imr_interface.s_addr == INADDR_ANY) ifp = NULL; else { INADDR_TO_IFP(mreq->imr_interface, ifp); if (ifp == NULL) { error = EADDRNOTAVAIL; splx(s); break; } } /* * Find the membership in the membership array. */ for (i = 0; i < imo->imo_num_memberships; ++i) { if ((ifp == NULL || imo->imo_membership[i]->inm_ifp == ifp) && imo->imo_membership[i]->inm_addr.s_addr == mreq->imr_multiaddr.s_addr) break; } if (i == imo->imo_num_memberships) { error = EADDRNOTAVAIL; splx(s); break; } /* * Give up the multicast address record to which the * membership points. */ in_delmulti(imo->imo_membership[i]); /* * Remove the gap in the membership array. */ for (++i; i < imo->imo_num_memberships; ++i) imo->imo_membership[i-1] = imo->imo_membership[i]; --imo->imo_num_memberships; splx(s); break; default: error = EOPNOTSUPP; break; } /* * If all options have default values, no need to keep the mbuf. */ if (imo->imo_multicast_ifp == NULL && imo->imo_multicast_vif == -1 && imo->imo_multicast_ttl == IP_DEFAULT_MULTICAST_TTL && imo->imo_multicast_loop == IP_DEFAULT_MULTICAST_LOOP && imo->imo_num_memberships == 0) { free(*imop, M_IPMOPTS); *imop = NULL; } return (error); } /* * Return the IP multicast options in response to user getsockopt(). */ static int ip_getmoptions(optname, imo, mp) int optname; register struct ip_moptions *imo; register struct mbuf **mp; { u_char *ttl; u_char *loop; struct in_addr *addr; struct in_ifaddr *ia; *mp = m_get(M_WAIT, MT_SOOPTS); switch (optname) { case IP_MULTICAST_VIF: if (imo != NULL) *(mtod(*mp, int *)) = imo->imo_multicast_vif; else *(mtod(*mp, int *)) = -1; (*mp)->m_len = sizeof(int); return(0); case IP_MULTICAST_IF: addr = mtod(*mp, struct in_addr *); (*mp)->m_len = sizeof(struct in_addr); if (imo == NULL || imo->imo_multicast_ifp == NULL) addr->s_addr = INADDR_ANY; else { IFP_TO_IA(imo->imo_multicast_ifp, ia); addr->s_addr = (ia == NULL) ? INADDR_ANY : IA_SIN(ia)->sin_addr.s_addr; } return (0); case IP_MULTICAST_TTL: ttl = mtod(*mp, u_char *); (*mp)->m_len = 1; *ttl = (imo == NULL) ? IP_DEFAULT_MULTICAST_TTL : imo->imo_multicast_ttl; return (0); case IP_MULTICAST_LOOP: loop = mtod(*mp, u_char *); (*mp)->m_len = 1; *loop = (imo == NULL) ? IP_DEFAULT_MULTICAST_LOOP : imo->imo_multicast_loop; return (0); default: return (EOPNOTSUPP); } } /* * Discard the IP multicast options. */ void ip_freemoptions(imo) register struct ip_moptions *imo; { register int i; if (imo != NULL) { for (i = 0; i < imo->imo_num_memberships; ++i) in_delmulti(imo->imo_membership[i]); free(imo, M_IPMOPTS); } } /* * Routine called from ip_output() to loop back a copy of an IP multicast * packet to the input queue of a specified interface. Note that this * calls the output routine of the loopback "driver", but with an interface * pointer that might NOT be a loopback interface -- evil, but easier than * replicating that code here. */ static void ip_mloopback(ifp, m, dst) struct ifnet *ifp; register struct mbuf *m; register struct sockaddr_in *dst; { register struct ip *ip; struct mbuf *copym; copym = m_copy(m, 0, M_COPYALL); if (copym != NULL) { /* * We don't bother to fragment if the IP length is greater * than the interface's MTU. Can this possibly matter? */ ip = mtod(copym, struct ip *); ip->ip_len = htons((u_short)ip->ip_len); ip->ip_off = htons((u_short)ip->ip_off); ip->ip_sum = 0; if (ip->ip_vhl == IP_VHL_BORING) { ip->ip_sum = in_cksum_hdr(ip); } else { ip->ip_sum = in_cksum(copym, IP_VHL_HL(ip->ip_vhl) << 2); } /* * NB: * We can't simply call ip_input() directly because * the ip_mforward() depends on the `input interface' * being set to something unreasonable so that we don't * attempt to forward the looped-back copy. * It's also not clear whether there are any lingering * reentrancy problems in other areas which might be * exposed by this code. For the moment, we'll err * on the side of safety by continuing to abuse * loinput(). */ #ifdef notdef copym->m_pkthdr.rcvif = &loif[0]; ip_input(copym) #else (void) looutput(ifp, copym, (struct sockaddr *)dst, NULL); #endif } } diff --git a/sys/netipx/ipx.c b/sys/netipx/ipx.c index b33a7acef337..764e5971312e 100644 --- a/sys/netipx/ipx.c +++ b/sys/netipx/ipx.c @@ -1,375 +1,360 @@ /* * Copyright (c) 1995, Mike Mitchell * Copyright (c) 1984, 1985, 1986, 1987, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ipx.c * - * $Id: ipx.c,v 1.3 1995/11/04 09:02:34 julian Exp $ + * $Id: ipx.c,v 1.4 1996/03/11 15:13:46 davidg Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IPX struct ipx_ifaddr *ipx_ifaddr; int ipx_interfaces; /* * Generic internet control operations (ioctl's). */ /* ARGSUSED */ int ipx_control(so, cmd, data, ifp) struct socket *so; int cmd; caddr_t data; register struct ifnet *ifp; { register struct ifreq *ifr = (struct ifreq *)data; register struct ipx_aliasreq *ifra = (struct ipx_aliasreq *)data; register struct ipx_ifaddr *ia; struct ifaddr *ifa; struct ipx_ifaddr *oia; int dstIsNew, hostIsNew; int error = 0; /* * Find address for this interface, if it exists. */ if (ifp == 0) return (EADDRNOTAVAIL); for (ia = ipx_ifaddr; ia; ia = ia->ia_next) if (ia->ia_ifp == ifp) break; switch (cmd) { case SIOCGIFADDR: if (ia == (struct ipx_ifaddr *)0) return (EADDRNOTAVAIL); *(struct sockaddr_ipx *)&ifr->ifr_addr = ia->ia_addr; return (0); case SIOCGIFBRDADDR: if (ia == (struct ipx_ifaddr *)0) return (EADDRNOTAVAIL); if ((ifp->if_flags & IFF_BROADCAST) == 0) return (EINVAL); *(struct sockaddr_ipx *)&ifr->ifr_dstaddr = ia->ia_broadaddr; return (0); case SIOCGIFDSTADDR: if (ia == (struct ipx_ifaddr *)0) return (EADDRNOTAVAIL); if ((ifp->if_flags & IFF_POINTOPOINT) == 0) return (EINVAL); *(struct sockaddr_ipx *)&ifr->ifr_dstaddr = ia->ia_dstaddr; return (0); } if ((so->so_state & SS_PRIV) == 0) return (EPERM); switch (cmd) { case SIOCAIFADDR: case SIOCDIFADDR: if (ifra->ifra_addr.sipx_family == AF_IPX) for (oia = ia; ia; ia = ia->ia_next) { if (ia->ia_ifp == ifp && ipx_neteq(ia->ia_addr.sipx_addr, ifra->ifra_addr.sipx_addr)) break; } if (cmd == SIOCDIFADDR && ia == 0) return (EADDRNOTAVAIL); /* FALLTHROUGH */ case SIOCSIFADDR: case SIOCSIFDSTADDR: if (ia == (struct ipx_ifaddr *)0) { oia = (struct ipx_ifaddr *) malloc(sizeof *ia, M_IFADDR, M_WAITOK); if (oia == (struct ipx_ifaddr *)NULL) return (ENOBUFS); bzero((caddr_t)oia, sizeof(*oia)); if ((ia = ipx_ifaddr)) { for ( ; ia->ia_next; ia = ia->ia_next) ; ia->ia_next = oia; } else ipx_ifaddr = oia; ia = oia; - if ((ifa = ifp->if_addrlist)) { - for ( ; ifa->ifa_next; ifa = ifa->ifa_next) - ; - ifa->ifa_next = (struct ifaddr *) ia; - } else - ifp->if_addrlist = (struct ifaddr *) ia; + ifa = (struct ifaddr *)ia; + TAILQ_INSERT_TAIL(&ifp->if_addrhead, ifa, ifa_link); ia->ia_ifp = ifp; - ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; + ifa->ifa_addr = (struct sockaddr *)&ia->ia_addr; - ia->ia_ifa.ifa_netmask = - (struct sockaddr *)&ipx_netmask; + ifa->ifa_netmask = (struct sockaddr *)&ipx_netmask; - ia->ia_ifa.ifa_dstaddr = - (struct sockaddr *)&ia->ia_dstaddr; + ifa->ifa_dstaddr = (struct sockaddr *)&ia->ia_dstaddr; if (ifp->if_flags & IFF_BROADCAST) { ia->ia_broadaddr.sipx_family = AF_IPX; ia->ia_broadaddr.sipx_len = sizeof(ia->ia_addr); ia->ia_broadaddr.sipx_addr.x_host = ipx_broadhost; } ipx_interfaces++; } } switch (cmd) { case SIOCSIFDSTADDR: if ((ifp->if_flags & IFF_POINTOPOINT) == 0) return (EINVAL); if (ia->ia_flags & IFA_ROUTE) { rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST); ia->ia_flags &= ~IFA_ROUTE; } if (ifp->if_ioctl) { error = (*ifp->if_ioctl)(ifp, SIOCSIFDSTADDR, (void *)ia); if (error) return (error); } *(struct sockaddr *)&ia->ia_dstaddr = ifr->ifr_dstaddr; return (0); case SIOCSIFADDR: return (ipx_ifinit(ifp, ia, (struct sockaddr_ipx *)&ifr->ifr_addr, 1)); case SIOCDIFADDR: ipx_ifscrub(ifp, ia); - if ((ifa = ifp->if_addrlist) == (struct ifaddr *)ia) - ifp->if_addrlist = ifa->ifa_next; - else { - while (ifa->ifa_next && - (ifa->ifa_next != (struct ifaddr *)ia)) - ifa = ifa->ifa_next; - if (ifa->ifa_next) - ifa->ifa_next = ((struct ifaddr *)ia)->ifa_next; - else - printf("Couldn't unlink ipxifaddr from ifp\n"); - } + ifa = (struct ifaddr *)ia; + TAILQ_REMOVE(&ifp->if_addrhead, ifa, ifa_link); oia = ia; if (oia == (ia = ipx_ifaddr)) { ipx_ifaddr = ia->ia_next; } else { while (ia->ia_next && (ia->ia_next != oia)) { ia = ia->ia_next; } if (ia->ia_next) ia->ia_next = oia->ia_next; else printf("Didn't unlink ipxifadr from list\n"); } IFAFREE((&oia->ia_ifa)); if (0 == --ipx_interfaces) { /* * We reset to virginity and start all over again */ ipx_thishost = ipx_zerohost; } return (0); case SIOCAIFADDR: dstIsNew = 0; hostIsNew = 1; if (ia->ia_addr.sipx_family == AF_IPX) { if (ifra->ifra_addr.sipx_len == 0) { ifra->ifra_addr = ia->ia_addr; hostIsNew = 0; } else if (ipx_neteq(ifra->ifra_addr.sipx_addr, ia->ia_addr.sipx_addr)) hostIsNew = 0; } if ((ifp->if_flags & IFF_POINTOPOINT) && (ifra->ifra_dstaddr.sipx_family == AF_IPX)) { if (hostIsNew == 0) ipx_ifscrub(ifp, ia); ia->ia_dstaddr = ifra->ifra_dstaddr; dstIsNew = 1; } if (ifra->ifra_addr.sipx_family == AF_IPX && (hostIsNew || dstIsNew)) error = ipx_ifinit(ifp, ia, &ifra->ifra_addr, 0); return (error); default: if (ifp->if_ioctl == 0) return (EOPNOTSUPP); return ((*ifp->if_ioctl)(ifp, cmd, data)); } } /* * Delete any previous route for an old address. */ void ipx_ifscrub(ifp, ia) register struct ifnet *ifp; register struct ipx_ifaddr *ia; { if (ia->ia_flags & IFA_ROUTE) { if (ifp->if_flags & IFF_POINTOPOINT) { rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST); } else rtinit(&(ia->ia_ifa), (int)RTM_DELETE, 0); ia->ia_flags &= ~IFA_ROUTE; } } /* * Initialize an interface's internet address * and routing table entry. */ int ipx_ifinit(ifp, ia, sipx, scrub) register struct ifnet *ifp; register struct ipx_ifaddr *ia; register struct sockaddr_ipx *sipx; int scrub; { struct sockaddr_ipx oldaddr; register union ipx_host *h = &ia->ia_addr.sipx_addr.x_host; int s = splimp(), error; /* * Set up new addresses. */ oldaddr = ia->ia_addr; ia->ia_addr = *sipx; /* * The convention we shall adopt for naming is that * a supplied address of zero means that "we don't care". * if there is a single interface, use the address of that * interface as our 6 byte host address. * if there are multiple interfaces, use any address already * used. * * Give the interface a chance to initialize * if this is its first address, * and to validate the address if necessary. */ if (ipx_hosteqnh(ipx_thishost, ipx_zerohost)) { if (ifp->if_ioctl && (error = (*ifp->if_ioctl)(ifp, SIOCSIFADDR, (void *)ia))) { ia->ia_addr = oldaddr; splx(s); return (error); } ipx_thishost = *h; } else if (ipx_hosteqnh(sipx->sipx_addr.x_host, ipx_zerohost) || ipx_hosteqnh(sipx->sipx_addr.x_host, ipx_thishost)) { *h = ipx_thishost; if (ifp->if_ioctl && (error = (*ifp->if_ioctl)(ifp, SIOCSIFADDR, (void *)ia))) { ia->ia_addr = oldaddr; splx(s); return (error); } if (!ipx_hosteqnh(ipx_thishost,*h)) { ia->ia_addr = oldaddr; splx(s); return (EINVAL); } } else { ia->ia_addr = oldaddr; splx(s); return (EINVAL); } ia->ia_ifa.ifa_metric = ifp->if_metric; /* * Add route for the network. */ if (scrub) { ia->ia_ifa.ifa_addr = (struct sockaddr *)&oldaddr; ipx_ifscrub(ifp, ia); ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; } if (ifp->if_flags & IFF_POINTOPOINT) rtinit(&(ia->ia_ifa), (int)RTM_ADD, RTF_HOST|RTF_UP); else { ia->ia_broadaddr.sipx_addr.x_net = ia->ia_addr.sipx_addr.x_net; rtinit(&(ia->ia_ifa), (int)RTM_ADD, RTF_UP); } ia->ia_flags |= IFA_ROUTE; return (0); } /* * Return address info for specified internet network. */ struct ipx_ifaddr * ipx_iaonnetof(dst) register struct ipx_addr *dst; { register struct ipx_ifaddr *ia; register struct ipx_addr *compare; register struct ifnet *ifp; struct ipx_ifaddr *ia_maybe = 0; union ipx_net net = dst->x_net; for (ia = ipx_ifaddr; ia; ia = ia->ia_next) { if ((ifp = ia->ia_ifp)) { if (ifp->if_flags & IFF_POINTOPOINT) { compare = &satoipx_addr(ia->ia_dstaddr); if (ipx_hosteq(*dst, *compare)) return (ia); if (ipx_neteqnn(net, ia->ia_addr.sipx_addr.x_net)) ia_maybe = ia; } else { if (ipx_neteqnn(net, ia->ia_addr.sipx_addr.x_net)) return (ia); } } } return (ia_maybe); } #endif diff --git a/sys/netipx/ipx_input.c b/sys/netipx/ipx_input.c index b49c79d95779..e1b5b8b9190b 100644 --- a/sys/netipx/ipx_input.c +++ b/sys/netipx/ipx_input.c @@ -1,526 +1,526 @@ /* * Copyright (c) 1995, Mike Mitchell * Copyright (c) 1984, 1985, 1986, 1987, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ipx_input.c * - * $Id: ipx_input.c,v 1.8 1996/03/11 15:13:48 davidg Exp $ + * $Id: ipx_input.c,v 1.9 1996/08/18 08:38:15 jhay Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef IPXPRINTFS #define IPXPRINTFS 1 /* printing forwarding information */ #endif int ipxcksum = 0; SYSCTL_INT(_net_ipx_ipx, OID_AUTO, checksum, CTLFLAG_RW, &ipxcksum, 0, ""); int ipxprintfs = IPXPRINTFS; int ipxdonosocks = 0; SYSCTL_INT(_net_ipx_ipx, OID_AUTO, donosocks, CTLFLAG_RW, &ipxdonosocks, 0, ""); int ipxforwarding = 0; SYSCTL_INT(_net_ipx_ipx, OID_AUTO, ipxforwarding, CTLFLAG_RW, &ipxforwarding, 0, ""); union ipx_host ipx_thishost; union ipx_net ipx_zeronet; union ipx_host ipx_zerohost; union ipx_net ipx_broadnet; union ipx_host ipx_broadhost; struct ipxstat ipxstat; struct sockaddr_ipx ipx_netmask, ipx_hostmask; int ipxintr_getpck = 0; int ipxintr_swtch = 0; static u_short allones[] = {-1, -1, -1}; struct ipxpcb ipxpcb; struct ipxpcb ipxrawpcb; struct ifqueue ipxintrq; int ipxqmaxlen = IFQ_MAXLEN; long ipx_pexseq; NETISR_SET(NETISR_IPX, ipxintr); /* * IPX initialization. */ void ipx_init() { ipx_broadnet = * (union ipx_net *) allones; ipx_broadhost = * (union ipx_host *) allones; ipx_pexseq = time.tv_usec; ipxintrq.ifq_maxlen = ipxqmaxlen; ipxpcb.ipxp_next = ipxpcb.ipxp_prev = &ipxpcb; ipxrawpcb.ipxp_next = ipxrawpcb.ipxp_prev = &ipxrawpcb; ipx_netmask.sipx_len = 6; ipx_netmask.sipx_addr.x_net = ipx_broadnet; ipx_hostmask.sipx_len = 12; ipx_hostmask.sipx_addr.x_net = ipx_broadnet; ipx_hostmask.sipx_addr.x_host = ipx_broadhost; } /* * IPX input routine. Pass to next level. */ void ipxintr() { register struct ipx *ipx; register struct mbuf *m; register struct ipxpcb *ipxp; register int i; int len, s, error; char oddshortpacket = 0; next: /* * Get next datagram off input queue and get IPX header * in first mbuf. */ s = splimp(); IF_DEQUEUE(&ipxintrq, m); splx(s); ipxintr_getpck++; if (m == 0) return; if ((m->m_flags & M_EXT || m->m_len < sizeof (struct ipx)) && (m = m_pullup(m, sizeof (struct ipx))) == 0) { ipxstat.ipxs_toosmall++; goto next; } /* * Give any raw listeners a crack at the packet */ for (ipxp = ipxrawpcb.ipxp_next; ipxp != &ipxrawpcb; ipxp = ipxp->ipxp_next) { struct mbuf *m1 = m_copy(m, 0, (int)M_COPYALL); if (m1) ipx_input(m1, ipxp); } ipx = mtod(m, struct ipx *); len = ntohs(ipx->ipx_len); if ((len < m->m_pkthdr.len) && (oddshortpacket = len & 1)) { /* * If this packet is of odd length, and the length * inside the header is less than the received packet * length, preserve garbage byte for possible checksum. */ len++; } /* * Check that the amount of data in the buffers * is as at least much as the IPX header would have us expect. * Trim mbufs if longer than we expect. * Drop packet if shorter than we expect. */ if (m->m_pkthdr.len < len) { ipxstat.ipxs_tooshort++; goto bad; } if (m->m_pkthdr.len > len) { if (m->m_len == m->m_pkthdr.len) { m->m_len = len; m->m_pkthdr.len = len; } else m_adj(m, len - m->m_pkthdr.len); } if (ipxcksum && ((i = ipx->ipx_sum)!=0xffff)) { ipx->ipx_sum = 0; if (i != (ipx->ipx_sum = ipx_cksum(m, len))) { ipxstat.ipxs_badsum++; ipx->ipx_sum = i; if (ipx_hosteqnh(ipx_thishost, ipx->ipx_dna.x_host)) error = IPX_ERR_BADSUM; else error = IPX_ERR_BADSUM_T; ipx_error(m, error, 0); goto next; } } /* * Is this a directed broadcast? */ if (ipx_hosteqnh(ipx_broadhost,ipx->ipx_dna.x_host)) { if ((!ipx_neteq(ipx->ipx_dna, ipx->ipx_sna)) && (!ipx_neteqnn(ipx->ipx_dna.x_net, ipx_broadnet)) && (!ipx_neteqnn(ipx->ipx_sna.x_net, ipx_zeronet)) && (!ipx_neteqnn(ipx->ipx_dna.x_net, ipx_zeronet)) ) { /* * Look to see if I need to eat this packet. * Algorithm is to forward all young packets * and prematurely age any packets which will * by physically broadcasted. * Any very old packets eaten without forwarding * would die anyway. * * Suggestion of Bill Nesheim, Cornell U. */ if (ipx->ipx_tc < IPX_MAXHOPS) { ipx_forward(m); goto next; } } /* * Is this our packet? If not, forward. */ } else if (!ipx_hosteqnh(ipx_thishost,ipx->ipx_dna.x_host)) { ipx_forward(m); goto next; } /* * Locate pcb for datagram. */ ipxp = ipx_pcblookup(&ipx->ipx_sna, ipx->ipx_dna.x_port, IPX_WILDCARD); /* * Switch out to protocol's input routine. */ ipxintr_swtch++; if (ipxp) { if (oddshortpacket) { m_adj(m, -1); } if ((ipxp->ipxp_flags & IPXP_ALL_PACKETS)==0) switch (ipx->ipx_pt) { case IPXPROTO_SPX: spx_input(m, ipxp); goto next; case IPXPROTO_ERROR: ipx_err_input(m); goto next; } ipx_input(m, ipxp); } else { ipx_error(m, IPX_ERR_NOSOCK, 0); } goto next; bad: m_freem(m); goto next; } u_char ipxctlerrmap[PRC_NCMDS] = { ECONNABORTED, ECONNABORTED, 0, 0, 0, 0, EHOSTDOWN, EHOSTUNREACH, ENETUNREACH, EHOSTUNREACH, ECONNREFUSED, ECONNREFUSED, EMSGSIZE, 0, 0, 0, 0, 0, 0, 0 }; void ipx_ctlinput(cmd, arg_as_sa, dummy) int cmd; struct sockaddr *arg_as_sa; /* XXX should be swapped with dummy */ void *dummy; { caddr_t arg = (/* XXX */ caddr_t)arg_as_sa; struct ipx_addr *ipx; struct ipxpcb *ipxp; struct ipx_errp *errp; int type; if (cmd < 0 || cmd > PRC_NCMDS) return; if (ipxctlerrmap[cmd] == 0) return; /* XXX */ type = IPX_ERR_UNREACH_HOST; errp = (struct ipx_errp *)arg; switch (cmd) { struct sockaddr_ipx *sipx; case PRC_IFDOWN: case PRC_HOSTDEAD: case PRC_HOSTUNREACH: sipx = (struct sockaddr_ipx *)arg; if (sipx->sipx_family != AF_IPX) return; ipx = &sipx->sipx_addr; break; default: ipx = &errp->ipx_err_ipx.ipx_dna; type = errp->ipx_err_num; type = ntohs((u_short)type); break; } switch (type) { case IPX_ERR_UNREACH_HOST: ipx_pcbnotify(ipx, (int)ipxctlerrmap[cmd], ipx_abort, (long)0); break; case IPX_ERR_NOSOCK: ipxp = ipx_pcblookup(ipx, errp->ipx_err_ipx.ipx_sna.x_port, IPX_WILDCARD); if(ipxp && ipxdonosocks && ! ipx_nullhost(ipxp->ipxp_faddr)) (void) ipx_drop(ipxp, (int)ipxctlerrmap[cmd]); } } /* * Forward a packet. If some error occurs return the sender * an error packet. Note we can't always generate a meaningful * error message because the IPX errors don't have a large enough repetoire * of codes and types. */ struct route ipx_droute; struct route ipx_sroute; void ipx_forward(m) struct mbuf *m; { register struct ipx *ipx = mtod(m, struct ipx *); register int error, type, code; struct mbuf *mcopy = NULL; int agedelta = 1; int flags = IPX_FORWARDING; int ok_there = 0; int ok_back = 0; if (ipxforwarding == 0) { /* can't tell difference between net and host */ type = IPX_ERR_UNREACH_HOST, code = 0; goto senderror; } ipx->ipx_tc++; if (ipx->ipx_tc > IPX_MAXHOPS) { type = IPX_ERR_TOO_OLD, code = 0; goto senderror; } /* * Save at most 42 bytes of the packet in case * we need to generate an IPX error message to the src. */ mcopy = m_copy(m, 0, imin((int)ntohs(ipx->ipx_len), 42)); if ((ok_there = ipx_do_route(&ipx->ipx_dna,&ipx_droute))==0) { type = IPX_ERR_UNREACH_HOST, code = 0; goto senderror; } /* * Here we think about forwarding broadcast packets, * so we try to insure that it doesn't go back out * on the interface it came in on. Also, if we * are going to physically broadcast this, let us * age the packet so we can eat it safely the second time around. */ if (ipx->ipx_dna.x_host.c_host[0] & 0x1) { struct ipx_ifaddr *ia = ipx_iaonnetof(&ipx->ipx_dna); struct ifnet *ifp; if (ia) { /* I'm gonna hafta eat this packet */ agedelta += IPX_MAXHOPS - ipx->ipx_tc; ipx->ipx_tc = IPX_MAXHOPS; } if ((ok_back = ipx_do_route(&ipx->ipx_sna,&ipx_sroute))==0) { /* error = ENETUNREACH; He'll never get it! */ m_freem(m); goto cleanup; } if (ipx_droute.ro_rt && (ifp=ipx_droute.ro_rt->rt_ifp) && ipx_sroute.ro_rt && (ifp!=ipx_sroute.ro_rt->rt_ifp)) { flags |= IPX_ALLOWBROADCAST; } else { type = IPX_ERR_UNREACH_HOST, code = 0; goto senderror; } } /* need to adjust checksum */ if (ipxcksum && ipx->ipx_sum != 0xffff) { union bytes { u_char c[4]; u_short s[2]; long l; } x; register int shift; x.l = 0; x.c[0] = agedelta; shift = (((((int)ntohs(ipx->ipx_len))+1)>>1)-2) & 0xf; x.l = ipx->ipx_sum + (x.s[0] << shift); x.l = x.s[0] + x.s[1]; x.l = x.s[0] + x.s[1]; if (x.l==0xffff) ipx->ipx_sum = 0; else ipx->ipx_sum = x.l; } else ipx->ipx_sum = 0xffff; error = ipx_outputfl(m, &ipx_droute, flags); if (ipxprintfs && !error) { printf("forward: "); ipx_printhost(&ipx->ipx_sna); printf(" to "); ipx_printhost(&ipx->ipx_dna); printf(" hops %d\n", ipx->ipx_tc); } if (error && mcopy != NULL) { ipx = mtod(mcopy, struct ipx *); type = IPX_ERR_UNSPEC_T, code = 0; switch (error) { case ENETUNREACH: case EHOSTDOWN: case EHOSTUNREACH: case ENETDOWN: case EPERM: type = IPX_ERR_UNREACH_HOST; break; case EMSGSIZE: type = IPX_ERR_TOO_BIG; code = 576; /* too hard to figure out mtu here */ break; case ENOBUFS: type = IPX_ERR_UNSPEC_T; break; } mcopy = NULL; senderror: ipx_error(m, type, code); } cleanup: if (ok_there) ipx_undo_route(&ipx_droute); if (ok_back) ipx_undo_route(&ipx_sroute); if (mcopy != NULL) m_freem(mcopy); } int ipx_do_route(src, ro) struct ipx_addr *src; struct route *ro; { struct sockaddr_ipx *dst; bzero((caddr_t)ro, sizeof (*ro)); dst = (struct sockaddr_ipx *)&ro->ro_dst; dst->sipx_len = sizeof(*dst); dst->sipx_family = AF_IPX; dst->sipx_addr = *src; dst->sipx_addr.x_port = 0; rtalloc(ro); if (ro->ro_rt == 0 || ro->ro_rt->rt_ifp == 0) { return (0); } ro->ro_rt->rt_use++; return (1); } void ipx_undo_route(ro) register struct route *ro; { if (ro->ro_rt) {RTFREE(ro->ro_rt);} } void ipx_watch_output(m, ifp) struct mbuf *m; struct ifnet *ifp; { register struct ipxpcb *ipxp; register struct ifaddr *ifa; /* * Give any raw listeners a crack at the packet */ for (ipxp = ipxrawpcb.ipxp_next; ipxp != &ipxrawpcb; ipxp = ipxp->ipxp_next) { struct mbuf *m0 = m_copy(m, 0, (int)M_COPYALL); if (m0) { register struct ipx *ipx; M_PREPEND(m0, sizeof (*ipx), M_DONTWAIT); if (m0 == NULL) continue; ipx = mtod(m0, struct ipx *); ipx->ipx_sna.x_net = ipx_zeronet; ipx->ipx_sna.x_host = ipx_thishost; if (ifp && (ifp->if_flags & IFF_POINTOPOINT)) - for(ifa = ifp->if_addrlist; ifa; - ifa = ifa->ifa_next) { + for(ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) { if (ifa->ifa_addr->sa_family==AF_IPX) { ipx->ipx_sna = IA_SIPX(ifa)->sipx_addr; break; } } ipx->ipx_len = ntohl(m0->m_pkthdr.len); ipx_input(m0, ipxp); } } } diff --git a/sys/netipx/ipx_ip.c b/sys/netipx/ipx_ip.c index e3362056b669..d12a8ef6e1b7 100644 --- a/sys/netipx/ipx_ip.c +++ b/sys/netipx/ipx_ip.c @@ -1,437 +1,438 @@ /* * Copyright (c) 1995, Mike Mitchell * Copyright (c) 1984, 1985, 1986, 1987, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ipx_ip.c * - * $Id: ipx_ip.c,v 1.8 1996/05/08 19:31:45 jhay Exp $ + * $Id: ipx_ip.c,v 1.9 1996/06/12 05:10:27 gpalmer Exp $ */ /* * Software interface driver for encapsulating IPX in IP. */ #ifdef IPXIP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct ifnet ipxipif; struct ifnet_en *ipxip_list; /* list of all hosts and gateways or broadcast addrs */ struct ifnet_en * ipxipattach() { register struct ifnet_en *m; register struct ifnet *ifp; if (ipxipif.if_mtu == 0) { ifp = &ipxipif; ifp->if_name = "ipxip"; ifp->if_mtu = LOMTU; ifp->if_ioctl = ipxipioctl; ifp->if_output = ipxipoutput; ifp->if_start = ipxipstart; ifp->if_flags = IFF_POINTOPOINT; } MALLOC((m), struct ifnet_en *, sizeof(*m), M_PCB, M_NOWAIT); if (m == NULL) return (NULL); bzero(m, sizeof(*m)); m->ifen_next = ipxip_list; ipxip_list = m; ifp = &m->ifen_ifnet; ifp->if_name = "ipxip"; ifp->if_mtu = LOMTU; ifp->if_ioctl = ipxipioctl; ifp->if_output = ipxipoutput; ifp->if_start = ipxipstart; ifp->if_flags = IFF_POINTOPOINT; ifp->if_unit = ipxipif.if_unit++; if_attach(ifp); return (m); } /* * Process an ioctl request. */ /* ARGSUSED */ int ipxipioctl(ifp, cmd, data) register struct ifnet *ifp; int cmd; caddr_t data; { int error = 0; struct ifreq *ifr; switch (cmd) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; /* fall into: */ case SIOCSIFDSTADDR: /* * Everything else is done at a higher level. */ break; case SIOCSIFFLAGS: ifr = (struct ifreq *)data; if ((ifr->ifr_flags & IFF_UP) == 0) error = ipxip_free(ifp); default: error = EINVAL; } return (error); } struct mbuf *ipxip_badlen; struct mbuf *ipxip_lastin; int ipxip_hold_input; void ipxip_input(m, hlen) register struct mbuf *m; int hlen; { register struct ip *ip; register struct ipx *ipx; register struct ifqueue *ifq = &ipxintrq; int len, s; if (ipxip_hold_input) { if (ipxip_lastin) { m_freem(ipxip_lastin); } ipxip_lastin = m_copym(m, 0, (int)M_COPYALL, M_DONTWAIT); } /* * Get IP and IPX header together in first mbuf. */ ipxipif.if_ipackets++; s = sizeof (struct ip) + sizeof (struct ipx); if (((m->m_flags & M_EXT) || m->m_len < s) && (m = m_pullup(m, s)) == 0) { ipxipif.if_ierrors++; return; } ip = mtod(m, struct ip *); if (ip->ip_hl > (sizeof (struct ip) >> 2)) { ip_stripoptions(m, (struct mbuf *)0); if (m->m_len < s) { if ((m = m_pullup(m, s)) == 0) { ipxipif.if_ierrors++; return; } ip = mtod(m, struct ip *); } } /* * Make mbuf data length reflect IPX length. * If not enough data to reflect IPX length, drop. */ m->m_data += sizeof (struct ip); m->m_len -= sizeof (struct ip); m->m_pkthdr.len -= sizeof (struct ip); ipx = mtod(m, struct ipx *); len = ntohs(ipx->ipx_len); if (len & 1) len++; /* Preserve Garbage Byte */ if (ip->ip_len != len) { if (len > ip->ip_len) { ipxipif.if_ierrors++; if (ipxip_badlen) m_freem(ipxip_badlen); ipxip_badlen = m; return; } /* Any extra will be trimmed off by the IPX routines */ } /* * Deliver to IPX */ s = splimp(); if (IF_QFULL(ifq)) { IF_DROP(ifq); m_freem(m); splx(s); return; } IF_ENQUEUE(ifq, m); schednetisr(NETISR_IPX); splx(s); return; } /* ARGSUSED */ int ipxipoutput(ifp, m, dst, rt) struct ifnet *ifp; register struct mbuf *m; struct sockaddr *dst; struct rtentry *rt; { register struct ifnet_en *ifn = (struct ifnet_en *)ifp; register struct ip *ip; register struct route *ro = &(ifn->ifen_route); register int len = 0; register struct ipx *ipx = mtod(m, struct ipx *); int error; ifn->ifen_ifnet.if_opackets++; ipxipif.if_opackets++; /* * Calculate data length and make space * for IP header. */ len = ntohs(ipx->ipx_len); if (len & 1) len++; /* Preserve Garbage Byte */ /* following clause not necessary on vax */ if (3 & (int)m->m_data) { /* force longword alignment of ip hdr */ struct mbuf *m0 = m_gethdr(MT_HEADER, M_DONTWAIT); if (m0 == 0) { m_freem(m); return (ENOBUFS); } MH_ALIGN(m0, sizeof (struct ip)); m0->m_flags = m->m_flags & M_COPYFLAGS; m0->m_next = m; m0->m_len = sizeof (struct ip); m0->m_pkthdr.len = m0->m_len + m->m_len; m->m_flags &= ~M_PKTHDR; m = m0; } else { M_PREPEND(m, sizeof (struct ip), M_DONTWAIT); if (m == 0) return (ENOBUFS); } /* * Fill in IP header. */ ip = mtod(m, struct ip *); *(long *)ip = 0; ip->ip_p = IPPROTO_IDP; ip->ip_src = ifn->ifen_src; ip->ip_dst = ifn->ifen_dst; ip->ip_len = (u_short)len + sizeof (struct ip); ip->ip_ttl = MAXTTL; /* * Output final datagram. */ error = (ip_output(m, (struct mbuf *)0, ro, SO_BROADCAST, NULL)); if (error) { ifn->ifen_ifnet.if_oerrors++; ifn->ifen_ifnet.if_ierrors = error; } return (error); m_freem(m); return (ENETUNREACH); } void ipxipstart(ifp) struct ifnet *ifp; { panic("ipxip_start called\n"); } struct ifreq ifr_ipxip = {"ipxip0"}; int ipxip_route(so, m) struct socket *so; register struct mbuf *m; { register struct ipxip_req *rq = mtod(m, struct ipxip_req *); struct sockaddr_ipx *ipx_dst = (struct sockaddr_ipx *)&rq->rq_ipx; struct sockaddr_in *ip_dst = (struct sockaddr_in *)&rq->rq_ip; struct route ro; struct ifnet_en *ifn; struct sockaddr_in *src; /* * First, make sure we already have an IPX address: */ if (ipx_hosteqnh(ipx_thishost, ipx_zerohost)) return (EADDRNOTAVAIL); /* * Now, determine if we can get to the destination */ bzero((caddr_t)&ro, sizeof (ro)); ro.ro_dst = *(struct sockaddr *)ip_dst; rtalloc(&ro); if (ro.ro_rt == 0 || ro.ro_rt->rt_ifp == 0) { return (ENETUNREACH); } /* * And see how he's going to get back to us: * i.e., what return ip address do we use? */ { register struct in_ifaddr *ia; struct ifnet *ifp = ro.ro_rt->rt_ifp; - for (ia = in_ifaddr; ia; ia = ia->ia_next) + for (ia = in_ifaddrhead.tqh_first; ia; + ia = ia->ia_link.tqe_next) if (ia->ia_ifp == ifp) break; if (ia == 0) - ia = in_ifaddr; + ia = in_ifaddrhead.tqh_first; if (ia == 0) { RTFREE(ro.ro_rt); return (EADDRNOTAVAIL); } src = (struct sockaddr_in *)&ia->ia_addr; } /* * Is there a free (pseudo-)interface or space? */ for (ifn = ipxip_list; ifn; ifn = ifn->ifen_next) { if ((ifn->ifen_ifnet.if_flags & IFF_UP) == 0) break; } if (ifn == NULL) ifn = ipxipattach(); if (ifn == NULL) { RTFREE(ro.ro_rt); return (ENOBUFS); } ifn->ifen_route = ro; ifn->ifen_dst = ip_dst->sin_addr; ifn->ifen_src = src->sin_addr; /* * now configure this as a point to point link */ ifr_ipxip.ifr_name[4] = '0' + ipxipif.if_unit - 1; ifr_ipxip.ifr_dstaddr = * (struct sockaddr *) ipx_dst; (void)ipx_control(so, (int)SIOCSIFDSTADDR, (caddr_t)&ifr_ipxip, (struct ifnet *)ifn); satoipx_addr(ifr_ipxip.ifr_addr).x_host = ipx_thishost; return (ipx_control(so, (int)SIOCSIFADDR, (caddr_t)&ifr_ipxip, (struct ifnet *)ifn)); } int ipxip_free(ifp) struct ifnet *ifp; { register struct ifnet_en *ifn = (struct ifnet_en *)ifp; struct route *ro = & ifn->ifen_route; if (ro->ro_rt) { RTFREE(ro->ro_rt); ro->ro_rt = 0; } ifp->if_flags &= ~IFF_UP; return (0); } void ipxip_ctlinput(cmd, sa, dummy) int cmd; struct sockaddr *sa; void *dummy; { /*extern u_char inetctlerrmap[]; */ /*XXX*/ /*JRE*/ struct sockaddr_in *sin; /* int in_rtchange(); */ /*XXX*/ /*JRE*/ if ((unsigned)cmd >= PRC_NCMDS) return; if (sa->sa_family != AF_INET && sa->sa_family != AF_IMPLINK) return; sin = (struct sockaddr_in *)sa; if (sin->sin_addr.s_addr == INADDR_ANY) return; switch (cmd) { case PRC_ROUTEDEAD: case PRC_REDIRECT_NET: case PRC_REDIRECT_HOST: case PRC_REDIRECT_TOSNET: case PRC_REDIRECT_TOSHOST: ipxip_rtchange(&sin->sin_addr); break; } } void ipxip_rtchange(dst) register struct in_addr *dst; { register struct ifnet_en *ifn; for (ifn = ipxip_list; ifn; ifn = ifn->ifen_next) { if (ifn->ifen_dst.s_addr == dst->s_addr && ifn->ifen_route.ro_rt) { RTFREE(ifn->ifen_route.ro_rt); ifn->ifen_route.ro_rt = 0; } } } #endif diff --git a/sys/netipx/ipx_usrreq.c b/sys/netipx/ipx_usrreq.c index 9746f4764cf4..0601e93e132c 100644 --- a/sys/netipx/ipx_usrreq.c +++ b/sys/netipx/ipx_usrreq.c @@ -1,599 +1,600 @@ /* * Copyright (c) 1995, Mike Mitchell * Copyright (c) 1984, 1985, 1986, 1987, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ipx_usrreq.c * - * $Id: ipx_usrreq.c,v 1.7 1996/05/08 19:31:48 jhay Exp $ + * $Id: ipx_usrreq.c,v 1.8 1996/11/24 08:25:48 jhay Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * IPX protocol implementation. */ int noipxRoute; int ipxsendspace = IPXSNDQ; SYSCTL_INT(_net_ipx_ipx, OID_AUTO, ipxsendspace, CTLFLAG_RW, &ipxsendspace, 0, ""); int ipxrecvspace = IPXRCVQ; SYSCTL_INT(_net_ipx_ipx, OID_AUTO, ipxrecvspace, CTLFLAG_RW, &ipxrecvspace, 0, ""); /* * This may also be called for raw listeners. */ void ipx_input(m, ipxp) struct mbuf *m; register struct ipxpcb *ipxp; { register struct ipx *ipx = mtod(m, struct ipx *); struct ifnet *ifp = m->m_pkthdr.rcvif; struct sockaddr_ipx ipx_ipx; if (ipxp==0) panic("No ipxpcb"); /* * Construct sockaddr format source address. * Stuff source address and datagram in user buffer. */ ipx_ipx.sipx_len = sizeof(ipx_ipx); ipx_ipx.sipx_family = AF_IPX; ipx_ipx.sipx_addr = ipx->ipx_sna; ipx_ipx.sipx_zero[0] = '\0'; ipx_ipx.sipx_zero[1] = '\0'; if (ipx_neteqnn(ipx->ipx_sna.x_net, ipx_zeronet) && ifp) { register struct ifaddr *ifa; - for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) { + for (ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) { if (ifa->ifa_addr->sa_family == AF_IPX) { ipx_ipx.sipx_addr.x_net = IA_SIPX(ifa)->sipx_addr.x_net; break; } } } ipxp->ipxp_rpt = ipx->ipx_pt; if ( ! (ipxp->ipxp_flags & IPXP_RAWIN) ) { m->m_len -= sizeof (struct ipx); m->m_pkthdr.len -= sizeof (struct ipx); m->m_data += sizeof (struct ipx); } if (sbappendaddr(&ipxp->ipxp_socket->so_rcv, (struct sockaddr *)&ipx_ipx, m, (struct mbuf *)0) == 0) goto bad; sorwakeup(ipxp->ipxp_socket); return; bad: m_freem(m); } void ipx_abort(ipxp) struct ipxpcb *ipxp; { struct socket *so = ipxp->ipxp_socket; ipx_pcbdisconnect(ipxp); soisdisconnected(so); } /* * Drop connection, reporting * the specified error. */ /* struct ipxpcb * DELETE THIS */ void ipx_drop(ipxp, errno) register struct ipxpcb *ipxp; int errno; { struct socket *so = ipxp->ipxp_socket; /* * someday, in the xerox world * we will generate error protocol packets * announcing that the socket has gone away. */ /*if (TCPS_HAVERCVDSYN(tp->t_state)) { tp->t_state = TCPS_CLOSED; (void) tcp_output(tp); }*/ so->so_error = errno; ipx_pcbdisconnect(ipxp); soisdisconnected(so); } int ipx_output(ipxp, m0) struct ipxpcb *ipxp; struct mbuf *m0; { register struct mbuf *m; register struct ipx *ipx; register struct socket *so; register int len = 0; register struct route *ro; struct mbuf *mprev = NULL; /* * Calculate data length. */ for (m = m0; m; m = m->m_next) { mprev = m; len += m->m_len; } /* * Make sure packet is actually of even length. */ if (len & 1) { m = mprev; if ((m->m_flags & M_EXT) == 0 && (m->m_len + m->m_data < &m->m_dat[MLEN])) { m->m_len++; } else { struct mbuf *m1 = m_get(M_DONTWAIT, MT_DATA); if (m1 == 0) { m_freem(m0); return (ENOBUFS); } m1->m_len = 1; * mtod(m1, char *) = 0; m->m_next = m1; } m0->m_pkthdr.len++; } /* * Fill in mbuf with extended IPX header * and addresses and length put into network format. */ m = m0; if (ipxp->ipxp_flags & IPXP_RAWOUT) { ipx = mtod(m, struct ipx *); } else { M_PREPEND(m, sizeof (struct ipx), M_DONTWAIT); if (m == 0) return (ENOBUFS); ipx = mtod(m, struct ipx *); ipx->ipx_tc = 0; ipx->ipx_pt = ipxp->ipxp_dpt; ipx->ipx_sna = ipxp->ipxp_laddr; ipx->ipx_dna = ipxp->ipxp_faddr; len += sizeof (struct ipx); } ipx->ipx_len = htons((u_short)len); if (ipxcksum) { ipx->ipx_sum = 0; len = ((len - 1) | 1) + 1; ipx->ipx_sum = ipx_cksum(m, len); } else ipx->ipx_sum = 0xffff; /* * Output datagram. */ so = ipxp->ipxp_socket; if (so->so_options & SO_DONTROUTE) return (ipx_outputfl(m, (struct route *)0, (so->so_options & SO_BROADCAST) | IPX_ROUTETOIF)); /* * Use cached route for previous datagram if * possible. If the previous net was the same * and the interface was a broadcast medium, or * if the previous destination was identical, * then we are ok. * * NB: We don't handle broadcasts because that * would require 3 subroutine calls. */ ro = &ipxp->ipxp_route; #ifdef ancient_history /* * I think that this will all be handled in ipx_pcbconnect! */ if (ro->ro_rt) { if(ipx_neteq(ipxp->ipxp_lastdst, ipx->ipx_dna)) { /* * This assumes we have no GH type routes */ if (ro->ro_rt->rt_flags & RTF_HOST) { if (!ipx_hosteq(ipxp->ipxp_lastdst, ipx->ipx_dna)) goto re_route; } if ((ro->ro_rt->rt_flags & RTF_GATEWAY) == 0) { register struct ipx_addr *dst = &satoipx_addr(ro->ro_dst); dst->x_host = ipx->ipx_dna.x_host; } /* * Otherwise, we go through the same gateway * and dst is already set up. */ } else { re_route: RTFREE(ro->ro_rt); ro->ro_rt = (struct rtentry *)0; } } ipxp->ipxp_lastdst = ipx->ipx_dna; #endif /* ancient_history */ if (noipxRoute) ro = 0; return (ipx_outputfl(m, ro, so->so_options & SO_BROADCAST)); } /* ARGSUSED */ int ipx_ctloutput(req, so, level, name, value) int req, level; struct socket *so; int name; struct mbuf **value; { register struct mbuf *m; struct ipxpcb *ipxp = sotoipxpcb(so); int mask, error = 0; /*extern long ipx_pexseq;*/ /*XXX*//*JRE*/ if (ipxp == NULL) return (EINVAL); switch (req) { case PRCO_GETOPT: if (value==NULL) return (EINVAL); m = m_get(M_DONTWAIT, MT_DATA); if (m==NULL) return (ENOBUFS); switch (name) { case SO_ALL_PACKETS: mask = IPXP_ALL_PACKETS; goto get_flags; case SO_HEADERS_ON_INPUT: mask = IPXP_RAWIN; goto get_flags; case SO_HEADERS_ON_OUTPUT: mask = IPXP_RAWOUT; get_flags: m->m_len = sizeof(short); *mtod(m, short *) = ipxp->ipxp_flags & mask; break; case SO_DEFAULT_HEADERS: m->m_len = sizeof(struct ipx); { register struct ipx *ipx = mtod(m, struct ipx *); ipx->ipx_len = 0; ipx->ipx_sum = 0; ipx->ipx_tc = 0; ipx->ipx_pt = ipxp->ipxp_dpt; ipx->ipx_dna = ipxp->ipxp_faddr; ipx->ipx_sna = ipxp->ipxp_laddr; } break; case SO_SEQNO: m->m_len = sizeof(long); *mtod(m, long *) = ipx_pexseq++; break; default: error = EINVAL; } *value = m; break; case PRCO_SETOPT: switch (name) { int *ok; case SO_ALL_PACKETS: mask = IPXP_ALL_PACKETS; goto set_head; case SO_HEADERS_ON_INPUT: mask = IPXP_RAWIN; goto set_head; case SO_HEADERS_ON_OUTPUT: mask = IPXP_RAWOUT; set_head: if (value && *value) { ok = mtod(*value, int *); if (*ok) ipxp->ipxp_flags |= mask; else ipxp->ipxp_flags &= ~mask; } else error = EINVAL; break; case SO_DEFAULT_HEADERS: { register struct ipx *ipx = mtod(*value, struct ipx *); ipxp->ipxp_dpt = ipx->ipx_pt; } break; #ifdef IPXIP case SO_IPXIP_ROUTE: error = ipxip_route(so, *value); break; #endif /* IPXIP */ #ifdef IPXTUNNEL case SO_IPXTUNNEL_ROUTE error = ipxtun_route(so, *value); break; #endif default: error = EINVAL; } if (value && *value) m_freem(*value); break; } return (error); } /*ARGSUSED*/ int ipx_usrreq(so, req, m, nam, control) struct socket *so; int req; struct mbuf *m, *nam, *control; { struct ipxpcb *ipxp = sotoipxpcb(so); int error = 0; if (req == PRU_CONTROL) return (ipx_control(so, (int)m, (caddr_t)nam, (struct ifnet *)control)); if (control && control->m_len) { error = EINVAL; goto release; } if (ipxp == NULL && req != PRU_ATTACH) { error = EINVAL; goto release; } switch (req) { case PRU_ATTACH: if (ipxp != NULL) { error = EINVAL; break; } error = ipx_pcballoc(so, &ipxpcb); if (error) break; error = soreserve(so, ipxsendspace, ipxrecvspace); if (error) break; break; case PRU_DETACH: if (ipxp == NULL) { error = ENOTCONN; break; } ipx_pcbdetach(ipxp); break; case PRU_BIND: error = ipx_pcbbind(ipxp, nam); break; case PRU_LISTEN: error = EOPNOTSUPP; break; case PRU_CONNECT: if (!ipx_nullhost(ipxp->ipxp_faddr)) { error = EISCONN; break; } error = ipx_pcbconnect(ipxp, nam); if (error == 0) soisconnected(so); break; case PRU_CONNECT2: error = EOPNOTSUPP; break; case PRU_ACCEPT: error = EOPNOTSUPP; break; case PRU_DISCONNECT: if (ipx_nullhost(ipxp->ipxp_faddr)) { error = ENOTCONN; break; } ipx_pcbdisconnect(ipxp); soisdisconnected(so); break; case PRU_SHUTDOWN: socantsendmore(so); break; case PRU_SEND: { struct ipx_addr laddr; int s = 0; if (nam) { laddr = ipxp->ipxp_laddr; if (!ipx_nullhost(ipxp->ipxp_faddr)) { error = EISCONN; break; } /* * Must block input while temporarily connected. */ s = splnet(); error = ipx_pcbconnect(ipxp, nam); if (error) { splx(s); break; } } else { if (ipx_nullhost(ipxp->ipxp_faddr)) { error = ENOTCONN; break; } } error = ipx_output(ipxp, m); m = NULL; if (nam) { ipx_pcbdisconnect(ipxp); splx(s); ipxp->ipxp_laddr.x_host = laddr.x_host; ipxp->ipxp_laddr.x_port = laddr.x_port; } } break; case PRU_ABORT: ipx_pcbdetach(ipxp); sofree(so); soisdisconnected(so); break; case PRU_SOCKADDR: ipx_setsockaddr(ipxp, nam); break; case PRU_PEERADDR: ipx_setpeeraddr(ipxp, nam); break; case PRU_SENSE: /* * stat: don't bother with a blocksize. */ return (0); case PRU_SENDOOB: case PRU_FASTTIMO: case PRU_SLOWTIMO: case PRU_PROTORCV: case PRU_PROTOSEND: error = EOPNOTSUPP; break; case PRU_CONTROL: case PRU_RCVD: case PRU_RCVOOB: return (EOPNOTSUPP); /* do not free mbuf's */ default: panic("ipx_usrreq"); } release: if (control != NULL) m_freem(control); if (m != NULL) m_freem(m); return (error); } /*ARGSUSED*/ int ipx_raw_usrreq(so, req, m, nam, control) struct socket *so; int req; struct mbuf *m, *nam, *control; { int error = 0; struct ipxpcb *ipxp = sotoipxpcb(so); /*extern struct ipxpcb ipxrawpcb;*//*XXX*//*JRE*/ switch (req) { case PRU_ATTACH: if (!(so->so_state & SS_PRIV) || (ipxp != NULL)) { error = EINVAL; break; } error = ipx_pcballoc(so, &ipxrawpcb); if (error) break; error = soreserve(so, ipxsendspace, ipxrecvspace); if (error) break; ipxp = sotoipxpcb(so); ipxp->ipxp_faddr.x_host = ipx_broadhost; ipxp->ipxp_flags = IPXP_RAWIN | IPXP_RAWOUT; break; default: error = ipx_usrreq(so, req, m, nam, control); } return (error); } diff --git a/sys/nfs/nfs_vnops.c b/sys/nfs/nfs_vnops.c index 8a6bb0dfcf61..4405179f07c6 100644 --- a/sys/nfs/nfs_vnops.c +++ b/sys/nfs/nfs_vnops.c @@ -1,3416 +1,3416 @@ /* * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Rick Macklem at The University of Guelph. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)nfs_vnops.c 8.5 (Berkeley) 2/13/94 - * $Id: nfs_vnops.c,v 1.36 1996/10/21 10:07:52 dfr Exp $ + * $Id: nfs_vnops.c,v 1.37 1996/11/06 10:53:12 dfr Exp $ */ /* * vnode op calls for Sun NFS version 2 and 3 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Defs */ #define TRUE 1 #define FALSE 0 /* * Ifdef for FreeBSD-current merged buffer cache. It is unfortunate that these * calls are not in getblk() and brelse() so that they would not be necessary * here. */ #ifndef B_VMIO #define vfs_busy_pages(bp, f) #endif static int nfsspec_read __P((struct vop_read_args *)); static int nfsspec_write __P((struct vop_write_args *)); static int nfsfifo_read __P((struct vop_read_args *)); static int nfsfifo_write __P((struct vop_write_args *)); static int nfsspec_close __P((struct vop_close_args *)); static int nfsfifo_close __P((struct vop_close_args *)); static int nfs_ioctl __P((struct vop_ioctl_args *)); static int nfs_select __P((struct vop_select_args *)); static int nfs_flush __P((struct vnode *,struct ucred *,int,struct proc *,int)); static int nfs_setattrrpc __P((struct vnode *,struct vattr *,struct ucred *,struct proc *)); static int nfs_lookup __P((struct vop_lookup_args *)); static int nfs_create __P((struct vop_create_args *)); static int nfs_mknod __P((struct vop_mknod_args *)); static int nfs_open __P((struct vop_open_args *)); static int nfs_close __P((struct vop_close_args *)); static int nfs_access __P((struct vop_access_args *)); static int nfs_getattr __P((struct vop_getattr_args *)); static int nfs_setattr __P((struct vop_setattr_args *)); static int nfs_read __P((struct vop_read_args *)); static int nfs_mmap __P((struct vop_mmap_args *)); static int nfs_fsync __P((struct vop_fsync_args *)); static int nfs_remove __P((struct vop_remove_args *)); static int nfs_link __P((struct vop_link_args *)); static int nfs_rename __P((struct vop_rename_args *)); static int nfs_mkdir __P((struct vop_mkdir_args *)); static int nfs_rmdir __P((struct vop_rmdir_args *)); static int nfs_symlink __P((struct vop_symlink_args *)); static int nfs_readdir __P((struct vop_readdir_args *)); static int nfs_bmap __P((struct vop_bmap_args *)); static int nfs_strategy __P((struct vop_strategy_args *)); static int nfs_lookitup __P((struct vnode *,char *,int,struct ucred *,struct proc *,struct nfsnode **)); static int nfs_sillyrename __P((struct vnode *,struct vnode *,struct componentname *)); static int nfsspec_access __P((struct vop_access_args *)); static int nfs_readlink __P((struct vop_readlink_args *)); static int nfs_print __P((struct vop_print_args *)); static int nfs_pathconf __P((struct vop_pathconf_args *)); static int nfs_advlock __P((struct vop_advlock_args *)); static int nfs_blkatoff __P((struct vop_blkatoff_args *)); static int nfs_bwrite __P((struct vop_bwrite_args *)); static int nfs_valloc __P((struct vop_valloc_args *)); static int nfs_vfree __P((struct vop_vfree_args *)); static int nfs_truncate __P((struct vop_truncate_args *)); static int nfs_update __P((struct vop_update_args *)); /* * Global vfs data structures for nfs */ vop_t **nfsv2_vnodeop_p; static struct vnodeopv_entry_desc nfsv2_vnodeop_entries[] = { { &vop_default_desc, (vop_t *)vn_default_error }, { &vop_lookup_desc, (vop_t *)nfs_lookup }, /* lookup */ { &vop_create_desc, (vop_t *)nfs_create }, /* create */ { &vop_mknod_desc, (vop_t *)nfs_mknod }, /* mknod */ { &vop_open_desc, (vop_t *)nfs_open }, /* open */ { &vop_close_desc, (vop_t *)nfs_close }, /* close */ { &vop_access_desc, (vop_t *)nfs_access }, /* access */ { &vop_getattr_desc, (vop_t *)nfs_getattr }, /* getattr */ { &vop_setattr_desc, (vop_t *)nfs_setattr }, /* setattr */ { &vop_read_desc, (vop_t *)nfs_read }, /* read */ { &vop_write_desc, (vop_t *)nfs_write }, /* write */ #ifdef HAS_VOPLEASE { &vop_lease_desc, (vop_t *)nfs_lease_check }, /* lease */ #endif { &vop_ioctl_desc, (vop_t *)nfs_ioctl }, /* ioctl */ { &vop_select_desc, (vop_t *)nfs_select }, /* select */ #ifdef HAS_VOPREVOKE { &vop_revoke_desc, (vop_t *)nfs_revoke }, /* revoke */ #endif { &vop_mmap_desc, (vop_t *)nfs_mmap }, /* mmap */ { &vop_fsync_desc, (vop_t *)nfs_fsync }, /* fsync */ { &vop_seek_desc, (vop_t *)nfs_seek }, /* seek */ { &vop_remove_desc, (vop_t *)nfs_remove }, /* remove */ { &vop_link_desc, (vop_t *)nfs_link }, /* link */ { &vop_rename_desc, (vop_t *)nfs_rename }, /* rename */ { &vop_mkdir_desc, (vop_t *)nfs_mkdir }, /* mkdir */ { &vop_rmdir_desc, (vop_t *)nfs_rmdir }, /* rmdir */ { &vop_symlink_desc, (vop_t *)nfs_symlink }, /* symlink */ { &vop_readdir_desc, (vop_t *)nfs_readdir }, /* readdir */ { &vop_readlink_desc, (vop_t *)nfs_readlink }, /* readlink */ { &vop_abortop_desc, (vop_t *)nfs_abortop }, /* abortop */ { &vop_inactive_desc, (vop_t *)nfs_inactive }, /* inactive */ { &vop_reclaim_desc, (vop_t *)nfs_reclaim }, /* reclaim */ { &vop_lock_desc, (vop_t *)nfs_lock }, /* lock */ { &vop_unlock_desc, (vop_t *)nfs_unlock }, /* unlock */ { &vop_bmap_desc, (vop_t *)nfs_bmap }, /* bmap */ { &vop_strategy_desc, (vop_t *)nfs_strategy }, /* strategy */ { &vop_print_desc, (vop_t *)nfs_print }, /* print */ { &vop_islocked_desc, (vop_t *)nfs_islocked }, /* islocked */ { &vop_pathconf_desc, (vop_t *)nfs_pathconf }, /* pathconf */ { &vop_advlock_desc, (vop_t *)nfs_advlock }, /* advlock */ { &vop_blkatoff_desc, (vop_t *)nfs_blkatoff }, /* blkatoff */ { &vop_valloc_desc, (vop_t *)nfs_valloc }, /* valloc */ { &vop_reallocblks_desc, (vop_t *)nfs_reallocblks }, /* reallocblks */ { &vop_vfree_desc, (vop_t *)nfs_vfree }, /* vfree */ { &vop_truncate_desc, (vop_t *)nfs_truncate }, /* truncate */ { &vop_update_desc, (vop_t *)nfs_update }, /* update */ { &vop_bwrite_desc, (vop_t *)nfs_bwrite }, /* bwrite */ { NULL, NULL } }; static struct vnodeopv_desc nfsv2_vnodeop_opv_desc = { &nfsv2_vnodeop_p, nfsv2_vnodeop_entries }; #ifdef __FreeBSD__ VNODEOP_SET(nfsv2_vnodeop_opv_desc); #endif /* * Special device vnode ops */ vop_t **spec_nfsv2nodeop_p; static struct vnodeopv_entry_desc spec_nfsv2nodeop_entries[] = { { &vop_default_desc, (vop_t *)vn_default_error }, { &vop_lookup_desc, (vop_t *)spec_lookup }, /* lookup */ { &vop_create_desc, (vop_t *)spec_create }, /* create */ { &vop_mknod_desc, (vop_t *)spec_mknod }, /* mknod */ { &vop_open_desc, (vop_t *)spec_open }, /* open */ { &vop_close_desc, (vop_t *)nfsspec_close }, /* close */ { &vop_access_desc, (vop_t *)nfsspec_access }, /* access */ { &vop_getattr_desc, (vop_t *)nfs_getattr }, /* getattr */ { &vop_setattr_desc, (vop_t *)nfs_setattr }, /* setattr */ { &vop_read_desc, (vop_t *)nfsspec_read }, /* read */ { &vop_write_desc, (vop_t *)nfsspec_write }, /* write */ #ifdef HAS_VOPLEASE { &vop_lease_desc, (vop_t *)spec_lease_check }, /* lease */ #endif { &vop_ioctl_desc, (vop_t *)spec_ioctl }, /* ioctl */ { &vop_select_desc, (vop_t *)spec_select }, /* select */ #ifdef HAS_VOPREVOKE { &vop_revoke_desc, (vop_t *)spec_revoke }, /* revoke */ #endif { &vop_mmap_desc, (vop_t *)spec_mmap }, /* mmap */ { &vop_fsync_desc, (vop_t *)nfs_fsync }, /* fsync */ { &vop_seek_desc, (vop_t *)spec_seek }, /* seek */ { &vop_remove_desc, (vop_t *)spec_remove }, /* remove */ { &vop_link_desc, (vop_t *)spec_link }, /* link */ { &vop_rename_desc, (vop_t *)spec_rename }, /* rename */ { &vop_mkdir_desc, (vop_t *)spec_mkdir }, /* mkdir */ { &vop_rmdir_desc, (vop_t *)spec_rmdir }, /* rmdir */ { &vop_symlink_desc, (vop_t *)spec_symlink }, /* symlink */ { &vop_readdir_desc, (vop_t *)spec_readdir }, /* readdir */ { &vop_readlink_desc, (vop_t *)spec_readlink }, /* readlink */ { &vop_abortop_desc, (vop_t *)spec_abortop }, /* abortop */ { &vop_inactive_desc, (vop_t *)nfs_inactive }, /* inactive */ { &vop_reclaim_desc, (vop_t *)nfs_reclaim }, /* reclaim */ { &vop_lock_desc, (vop_t *)nfs_lock }, /* lock */ { &vop_unlock_desc, (vop_t *)nfs_unlock }, /* unlock */ { &vop_bmap_desc, (vop_t *)spec_bmap }, /* bmap */ { &vop_strategy_desc, (vop_t *)spec_strategy }, /* strategy */ { &vop_print_desc, (vop_t *)nfs_print }, /* print */ { &vop_islocked_desc, (vop_t *)nfs_islocked }, /* islocked */ { &vop_pathconf_desc, (vop_t *)spec_pathconf }, /* pathconf */ { &vop_advlock_desc, (vop_t *)spec_advlock }, /* advlock */ { &vop_blkatoff_desc, (vop_t *)spec_blkatoff }, /* blkatoff */ { &vop_valloc_desc, (vop_t *)spec_valloc }, /* valloc */ { &vop_reallocblks_desc, (vop_t *)spec_reallocblks }, /* reallocblks */ { &vop_vfree_desc, (vop_t *)spec_vfree }, /* vfree */ { &vop_truncate_desc, (vop_t *)spec_truncate }, /* truncate */ { &vop_update_desc, (vop_t *)nfs_update }, /* update */ { &vop_bwrite_desc, (vop_t *)vn_bwrite }, /* bwrite */ { NULL, NULL } }; static struct vnodeopv_desc spec_nfsv2nodeop_opv_desc = { &spec_nfsv2nodeop_p, spec_nfsv2nodeop_entries }; #ifdef __FreeBSD__ VNODEOP_SET(spec_nfsv2nodeop_opv_desc); #endif vop_t **fifo_nfsv2nodeop_p; static struct vnodeopv_entry_desc fifo_nfsv2nodeop_entries[] = { { &vop_default_desc, (vop_t *)vn_default_error }, { &vop_lookup_desc, (vop_t *)fifo_lookup }, /* lookup */ { &vop_create_desc, (vop_t *)fifo_create }, /* create */ { &vop_mknod_desc, (vop_t *)fifo_mknod }, /* mknod */ { &vop_open_desc, (vop_t *)fifo_open }, /* open */ { &vop_close_desc, (vop_t *)nfsfifo_close }, /* close */ { &vop_access_desc, (vop_t *)nfsspec_access }, /* access */ { &vop_getattr_desc, (vop_t *)nfs_getattr }, /* getattr */ { &vop_setattr_desc, (vop_t *)nfs_setattr }, /* setattr */ { &vop_read_desc, (vop_t *)nfsfifo_read }, /* read */ { &vop_write_desc, (vop_t *)nfsfifo_write }, /* write */ #ifdef HAS_VOPLEASE { &vop_lease_desc, (vop_t *)fifo_lease_check }, /* lease */ #endif { &vop_ioctl_desc, (vop_t *)fifo_ioctl }, /* ioctl */ { &vop_select_desc, (vop_t *)fifo_select }, /* select */ #ifdef HAS_VOPREVOKE { &vop_revoke_desc, (vop_t *)fifo_revoke }, /* revoke */ #endif { &vop_mmap_desc, (vop_t *)fifo_mmap }, /* mmap */ { &vop_fsync_desc, (vop_t *)nfs_fsync }, /* fsync */ { &vop_seek_desc, (vop_t *)fifo_seek }, /* seek */ { &vop_remove_desc, (vop_t *)fifo_remove }, /* remove */ { &vop_link_desc, (vop_t *)fifo_link }, /* link */ { &vop_rename_desc, (vop_t *)fifo_rename }, /* rename */ { &vop_mkdir_desc, (vop_t *)fifo_mkdir }, /* mkdir */ { &vop_rmdir_desc, (vop_t *)fifo_rmdir }, /* rmdir */ { &vop_symlink_desc, (vop_t *)fifo_symlink }, /* symlink */ { &vop_readdir_desc, (vop_t *)fifo_readdir }, /* readdir */ { &vop_readlink_desc, (vop_t *)fifo_readlink }, /* readlink */ { &vop_abortop_desc, (vop_t *)fifo_abortop }, /* abortop */ { &vop_inactive_desc, (vop_t *)nfs_inactive }, /* inactive */ { &vop_reclaim_desc, (vop_t *)nfs_reclaim }, /* reclaim */ { &vop_lock_desc, (vop_t *)nfs_lock }, /* lock */ { &vop_unlock_desc, (vop_t *)nfs_unlock }, /* unlock */ { &vop_bmap_desc, (vop_t *)fifo_bmap }, /* bmap */ { &vop_strategy_desc, (vop_t *)fifo_badop }, /* strategy */ { &vop_print_desc, (vop_t *)nfs_print }, /* print */ { &vop_islocked_desc, (vop_t *)nfs_islocked }, /* islocked */ { &vop_pathconf_desc, (vop_t *)fifo_pathconf }, /* pathconf */ { &vop_advlock_desc, (vop_t *)fifo_advlock }, /* advlock */ { &vop_blkatoff_desc, (vop_t *)fifo_blkatoff }, /* blkatoff */ { &vop_valloc_desc, (vop_t *)fifo_valloc }, /* valloc */ { &vop_reallocblks_desc, (vop_t *)fifo_reallocblks }, /* reallocblks */ { &vop_vfree_desc, (vop_t *)fifo_vfree }, /* vfree */ { &vop_truncate_desc, (vop_t *)fifo_truncate }, /* truncate */ { &vop_update_desc, (vop_t *)nfs_update }, /* update */ { &vop_bwrite_desc, (vop_t *)vn_bwrite }, /* bwrite */ { NULL, NULL } }; static struct vnodeopv_desc fifo_nfsv2nodeop_opv_desc = { &fifo_nfsv2nodeop_p, fifo_nfsv2nodeop_entries }; #ifdef __FreeBSD__ VNODEOP_SET(fifo_nfsv2nodeop_opv_desc); #endif static int nfs_commit __P((struct vnode *vp, u_quad_t offset, int cnt, struct ucred *cred, struct proc *procp)); static int nfs_mknodrpc __P((struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, struct vattr *vap)); static int nfs_removerpc __P((struct vnode *dvp, char *name, int namelen, struct ucred *cred, struct proc *proc)); static int nfs_renamerpc __P((struct vnode *fdvp, char *fnameptr, int fnamelen, struct vnode *tdvp, char *tnameptr, int tnamelen, struct ucred *cred, struct proc *proc)); static int nfs_renameit __P((struct vnode *sdvp, struct componentname *scnp, struct sillyrename *sp)); /* * Global variables */ extern u_long nfs_true, nfs_false; extern struct nfsstats nfsstats; extern nfstype nfsv3_type[9]; struct proc *nfs_iodwant[NFS_MAXASYNCDAEMON]; struct nfsmount *nfs_iodmount[NFS_MAXASYNCDAEMON]; int nfs_numasync = 0; #define DIRHDSIZ (sizeof (struct dirent) - (MAXNAMLEN + 1)) /* * nfs access vnode op. * For nfs version 2, just return ok. File accesses may fail later. * For nfs version 3, use the access rpc to check accessibility. If file modes * are changed on the server, accesses might still fail later. */ static int nfs_access(ap) struct vop_access_args /* { struct vnode *a_vp; int a_mode; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register u_long *tl; register caddr_t cp; register int t1, t2; caddr_t bpos, dpos, cp2; int error = 0, attrflag; struct mbuf *mreq, *mrep, *md, *mb, *mb2; u_long mode, rmode; int v3 = NFS_ISV3(vp); /* * Disallow write attempts on filesystems mounted read-only; * unless the file is a socket, fifo, or a block or character * device resident on the filesystem. */ if ((ap->a_mode & VWRITE) && (vp->v_mount->mnt_flag & MNT_RDONLY)) { switch (vp->v_type) { case VREG: case VDIR: case VLNK: return (EROFS); } } /* * For nfs v3, do an access rpc, otherwise you are stuck emulating * ufs_access() locally using the vattr. This may not be correct, * since the server may apply other access criteria such as * client uid-->server uid mapping that we do not know about, but * this is better than just returning anything that is lying about * in the cache. */ if (v3) { nfsstats.rpccnt[NFSPROC_ACCESS]++; nfsm_reqhead(vp, NFSPROC_ACCESS, NFSX_FH(v3) + NFSX_UNSIGNED); nfsm_fhtom(vp, v3); nfsm_build(tl, u_long *, NFSX_UNSIGNED); if (ap->a_mode & VREAD) mode = NFSV3ACCESS_READ; else mode = 0; if (vp->v_type == VDIR) { if (ap->a_mode & VWRITE) mode |= (NFSV3ACCESS_MODIFY | NFSV3ACCESS_EXTEND | NFSV3ACCESS_DELETE); if (ap->a_mode & VEXEC) mode |= NFSV3ACCESS_LOOKUP; } else { if (ap->a_mode & VWRITE) mode |= (NFSV3ACCESS_MODIFY | NFSV3ACCESS_EXTEND); if (ap->a_mode & VEXEC) mode |= NFSV3ACCESS_EXECUTE; } *tl = txdr_unsigned(mode); nfsm_request(vp, NFSPROC_ACCESS, ap->a_p, ap->a_cred); nfsm_postop_attr(vp, attrflag); if (!error) { nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); rmode = fxdr_unsigned(u_long, *tl); /* * The NFS V3 spec does not clarify whether or not * the returned access bits can be a superset of * the ones requested, so... */ if ((rmode & mode) != mode) error = EACCES; } nfsm_reqdone; return (error); } else return (nfsspec_access(ap)); } /* * nfs open vnode op * Check to see if the type is ok * and that deletion is not in progress. * For paged in text files, you will need to flush the page cache * if consistency is lost. */ /* ARGSUSED */ static int nfs_open(ap) struct vop_open_args /* { struct vnode *a_vp; int a_mode; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; struct nfsnode *np = VTONFS(vp); struct nfsmount *nmp = VFSTONFS(vp->v_mount); struct vattr vattr; int error; if (vp->v_type != VREG && vp->v_type != VDIR && vp->v_type != VLNK) { printf("open eacces vtyp=%d\n",vp->v_type); return (EACCES); } /* * Get a valid lease. If cached data is stale, flush it. */ if (nmp->nm_flag & NFSMNT_NQNFS) { if (NQNFS_CKINVALID(vp, np, ND_READ)) { do { error = nqnfs_getlease(vp, ND_READ, ap->a_cred, ap->a_p); } while (error == NQNFS_EXPIRED); if (error) return (error); if (np->n_lrev != np->n_brev || (np->n_flag & NQNFSNONCACHE)) { if ((error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1)) == EINTR) return (error); (void) vnode_pager_uncache(vp); np->n_brev = np->n_lrev; } } } else { if (np->n_flag & NMODIFIED) { if ((error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1)) == EINTR) return (error); np->n_attrstamp = 0; if (vp->v_type == VDIR) np->n_direofoffset = 0; error = VOP_GETATTR(vp, &vattr, ap->a_cred, ap->a_p); if (error) return (error); np->n_mtime = vattr.va_mtime.tv_sec; } else { error = VOP_GETATTR(vp, &vattr, ap->a_cred, ap->a_p); if (error) return (error); if (np->n_mtime != vattr.va_mtime.tv_sec) { if (vp->v_type == VDIR) np->n_direofoffset = 0; if ((error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1)) == EINTR) return (error); np->n_mtime = vattr.va_mtime.tv_sec; } } } if ((nmp->nm_flag & NFSMNT_NQNFS) == 0) np->n_attrstamp = 0; /* For Open/Close consistency */ return (0); } /* * nfs close vnode op * What an NFS client should do upon close after writing is a debatable issue. * Most NFS clients push delayed writes to the server upon close, basically for * two reasons: * 1 - So that any write errors may be reported back to the client process * doing the close system call. By far the two most likely errors are * NFSERR_NOSPC and NFSERR_DQUOT to indicate space allocation failure. * 2 - To put a worst case upper bound on cache inconsistency between * multiple clients for the file. * There is also a consistency problem for Version 2 of the protocol w.r.t. * not being able to tell if other clients are writing a file concurrently, * since there is no way of knowing if the changed modify time in the reply * is only due to the write for this client. * (NFS Version 3 provides weak cache consistency data in the reply that * should be sufficient to detect and handle this case.) * * The current code does the following: * for NFS Version 2 - play it safe and flush/invalidate all dirty buffers * for NFS Version 3 - flush dirty buffers to the server but don't invalidate * or commit them (this satisfies 1 and 2 except for the * case where the server crashes after this close but * before the commit RPC, which is felt to be "good * enough". Changing the last argument to nfs_flush() to * a 1 would force a commit operation, if it is felt a * commit is necessary now. * for NQNFS - do nothing now, since 2 is dealt with via leases and * 1 should be dealt with via an fsync() system call for * cases where write errors are important. */ /* ARGSUSED */ static int nfs_close(ap) struct vop_close_args /* { struct vnodeop_desc *a_desc; struct vnode *a_vp; int a_fflag; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); int error = 0; if (vp->v_type == VREG) { if ((VFSTONFS(vp->v_mount)->nm_flag & NFSMNT_NQNFS) == 0 && (np->n_flag & NMODIFIED)) { if (NFS_ISV3(vp)) { error = nfs_flush(vp, ap->a_cred, MNT_WAIT, ap->a_p, 0); np->n_flag &= ~NMODIFIED; } else error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1); np->n_attrstamp = 0; } if (np->n_flag & NWRITEERR) { np->n_flag &= ~NWRITEERR; error = np->n_error; } } return (error); } /* * nfs getattr call from vfs. */ static int nfs_getattr(ap) struct vop_getattr_args /* { struct vnode *a_vp; struct vattr *a_vap; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); register caddr_t cp; register u_long *tl; register int t1, t2; caddr_t bpos, dpos; int error = 0; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(vp); /* * Update local times for special files. */ if (np->n_flag & (NACC | NUPD)) np->n_flag |= NCHG; /* * First look in the cache. */ if (nfs_getattrcache(vp, ap->a_vap) == 0) return (0); nfsstats.rpccnt[NFSPROC_GETATTR]++; nfsm_reqhead(vp, NFSPROC_GETATTR, NFSX_FH(v3)); nfsm_fhtom(vp, v3); nfsm_request(vp, NFSPROC_GETATTR, ap->a_p, ap->a_cred); if (!error) nfsm_loadattr(vp, ap->a_vap); nfsm_reqdone; return (error); } /* * nfs setattr call. */ static int nfs_setattr(ap) struct vop_setattr_args /* { struct vnodeop_desc *a_desc; struct vnode *a_vp; struct vattr *a_vap; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); register struct vattr *vap = ap->a_vap; int error = 0; u_quad_t tsize; #ifndef nolint tsize = (u_quad_t)0; #endif /* * Disallow write attempts if the filesystem is mounted read-only. */ if ((vap->va_flags != VNOVAL || vap->va_uid != (uid_t)VNOVAL || vap->va_gid != (gid_t)VNOVAL || vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL || vap->va_mode != (mode_t)VNOVAL) && (vp->v_mount->mnt_flag & MNT_RDONLY)) return (EROFS); if (vap->va_size != VNOVAL) { switch (vp->v_type) { case VDIR: return (EISDIR); case VCHR: case VBLK: if (vap->va_mtime.tv_sec == VNOVAL && vap->va_atime.tv_sec == VNOVAL && vap->va_mode == (u_short)VNOVAL && vap->va_uid == (uid_t)VNOVAL && vap->va_gid == (gid_t)VNOVAL) return (0); vap->va_size = VNOVAL; break; default: /* * Disallow write attempts if the filesystem is * mounted read-only. */ if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); if (vap->va_size == 0) error = nfs_vinvalbuf(vp, 0, ap->a_cred, ap->a_p, 1); else error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1); if (error) return (error); tsize = np->n_size; np->n_size = np->n_vattr.va_size = vap->va_size; vnode_pager_setsize(vp, (u_long)np->n_size); }; } else if ((vap->va_mtime.tv_sec != VNOVAL || vap->va_atime.tv_sec != VNOVAL) && vp->v_type == VREG && (error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1)) == EINTR) return (error); error = nfs_setattrrpc(vp, vap, ap->a_cred, ap->a_p); if (error && vap->va_size != VNOVAL) { np->n_size = np->n_vattr.va_size = tsize; vnode_pager_setsize(vp, (u_long)np->n_size); } return (error); } /* * Do an nfs setattr rpc. */ static int nfs_setattrrpc(vp, vap, cred, procp) register struct vnode *vp; register struct vattr *vap; struct ucred *cred; struct proc *procp; { register struct nfsv2_sattr *sp; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; u_long *tl; int error = 0, wccflag = NFSV3_WCCRATTR; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(vp); nfsstats.rpccnt[NFSPROC_SETATTR]++; nfsm_reqhead(vp, NFSPROC_SETATTR, NFSX_FH(v3) + NFSX_SATTR(v3)); nfsm_fhtom(vp, v3); if (v3) { if (vap->va_mode != (u_short)VNOVAL) { nfsm_build(tl, u_long *, 2 * NFSX_UNSIGNED); *tl++ = nfs_true; *tl = txdr_unsigned(vap->va_mode); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = nfs_false; } if (vap->va_uid != (uid_t)VNOVAL) { nfsm_build(tl, u_long *, 2 * NFSX_UNSIGNED); *tl++ = nfs_true; *tl = txdr_unsigned(vap->va_uid); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = nfs_false; } if (vap->va_gid != (gid_t)VNOVAL) { nfsm_build(tl, u_long *, 2 * NFSX_UNSIGNED); *tl++ = nfs_true; *tl = txdr_unsigned(vap->va_gid); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = nfs_false; } if (vap->va_size != VNOVAL) { nfsm_build(tl, u_long *, 3 * NFSX_UNSIGNED); *tl++ = nfs_true; txdr_hyper(&vap->va_size, tl); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = nfs_false; } if (vap->va_atime.tv_sec != VNOVAL) { if (vap->va_atime.tv_sec != time.tv_sec) { nfsm_build(tl, u_long *, 3 * NFSX_UNSIGNED); *tl++ = txdr_unsigned(NFSV3SATTRTIME_TOCLIENT); txdr_nfsv3time(&vap->va_atime, tl); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = txdr_unsigned(NFSV3SATTRTIME_TOSERVER); } } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = txdr_unsigned(NFSV3SATTRTIME_DONTCHANGE); } if (vap->va_mtime.tv_sec != VNOVAL) { if (vap->va_mtime.tv_sec != time.tv_sec) { nfsm_build(tl, u_long *, 3 * NFSX_UNSIGNED); *tl++ = txdr_unsigned(NFSV3SATTRTIME_TOCLIENT); txdr_nfsv3time(&vap->va_mtime, tl); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = txdr_unsigned(NFSV3SATTRTIME_TOSERVER); } } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = txdr_unsigned(NFSV3SATTRTIME_DONTCHANGE); } nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = nfs_false; } else { nfsm_build(sp, struct nfsv2_sattr *, NFSX_V2SATTR); if (vap->va_mode == (u_short)VNOVAL) sp->sa_mode = VNOVAL; else sp->sa_mode = vtonfsv2_mode(vp->v_type, vap->va_mode); if (vap->va_uid == (uid_t)VNOVAL) sp->sa_uid = VNOVAL; else sp->sa_uid = txdr_unsigned(vap->va_uid); if (vap->va_gid == (gid_t)VNOVAL) sp->sa_gid = VNOVAL; else sp->sa_gid = txdr_unsigned(vap->va_gid); sp->sa_size = txdr_unsigned(vap->va_size); txdr_nfsv2time(&vap->va_atime, &sp->sa_atime); txdr_nfsv2time(&vap->va_mtime, &sp->sa_mtime); } nfsm_request(vp, NFSPROC_SETATTR, procp, cred); if (v3) { nfsm_wcc_data(vp, wccflag); } else nfsm_loadattr(vp, (struct vattr *)0); nfsm_reqdone; return (error); } /* * nfs lookup call, one step at a time... * First look in cache * If not found, unlock the directory nfsnode and do the rpc */ static int nfs_lookup(ap) struct vop_lookup_args /* { struct vnodeop_desc *a_desc; struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; } */ *ap; { register struct componentname *cnp = ap->a_cnp; register struct vnode *dvp = ap->a_dvp; register struct vnode **vpp = ap->a_vpp; register int flags = cnp->cn_flags; register struct vnode *newvp; register u_long *tl; register caddr_t cp; register long t1, t2; struct nfsmount *nmp; caddr_t bpos, dpos, cp2; struct mbuf *mreq, *mrep, *md, *mb, *mb2; long len; nfsfh_t *fhp; struct nfsnode *np; int lockparent, wantparent, error = 0, attrflag, fhsize; int v3 = NFS_ISV3(dvp); if ((flags & ISLASTCN) && (dvp->v_mount->mnt_flag & MNT_RDONLY) && (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) return (EROFS); *vpp = NULLVP; if (dvp->v_type != VDIR) return (ENOTDIR); lockparent = flags & LOCKPARENT; wantparent = flags & (LOCKPARENT|WANTPARENT); nmp = VFSTONFS(dvp->v_mount); np = VTONFS(dvp); if ((error = cache_lookup(dvp, vpp, cnp)) && error != ENOENT) { struct vattr vattr; int vpid; newvp = *vpp; vpid = newvp->v_id; /* * See the comment starting `Step through' in ufs/ufs_lookup.c * for an explanation of the locking protocol */ if (dvp == newvp) { VREF(newvp); error = 0; } else if (flags & ISDOTDOT) { VOP_UNLOCK(dvp); error = vget(newvp, 1); if (!error && lockparent && (flags & ISLASTCN)) error = VOP_LOCK(dvp); } else { error = vget(newvp, 1); if (!lockparent || error || !(flags & ISLASTCN)) VOP_UNLOCK(dvp); } if (!error) { if (vpid == newvp->v_id) { if (!VOP_GETATTR(newvp, &vattr, cnp->cn_cred, cnp->cn_proc) && vattr.va_ctime.tv_sec == VTONFS(newvp)->n_ctime) { nfsstats.lookupcache_hits++; if (cnp->cn_nameiop != LOOKUP && (flags & ISLASTCN)) cnp->cn_flags |= SAVENAME; return (0); } cache_purge(newvp); } vput(newvp); if (lockparent && dvp != newvp && (flags & ISLASTCN)) VOP_UNLOCK(dvp); } error = VOP_LOCK(dvp); if (error) return (error); *vpp = NULLVP; } error = 0; newvp = NULLVP; nfsstats.lookupcache_misses++; nfsstats.rpccnt[NFSPROC_LOOKUP]++; len = cnp->cn_namelen; nfsm_reqhead(dvp, NFSPROC_LOOKUP, NFSX_FH(v3) + NFSX_UNSIGNED + nfsm_rndup(len)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, len, NFS_MAXNAMLEN); nfsm_request(dvp, NFSPROC_LOOKUP, cnp->cn_proc, cnp->cn_cred); if (error) { nfsm_postop_attr(dvp, attrflag); m_freem(mrep); goto nfsmout; } nfsm_getfh(fhp, fhsize, v3); /* * Handle RENAME case... */ if (cnp->cn_nameiop == RENAME && wantparent && (flags & ISLASTCN)) { if (NFS_CMPFH(np, fhp, fhsize)) { m_freem(mrep); return (EISDIR); } if (error = nfs_nget(dvp->v_mount, fhp, fhsize, &np)) { m_freem(mrep); return (error); } newvp = NFSTOV(np); if (v3) { nfsm_postop_attr(newvp, attrflag); nfsm_postop_attr(dvp, attrflag); } else nfsm_loadattr(newvp, (struct vattr *)0); *vpp = newvp; m_freem(mrep); cnp->cn_flags |= SAVENAME; if (!lockparent) VOP_UNLOCK(dvp); return (0); } if (flags & ISDOTDOT) { VOP_UNLOCK(dvp); error = nfs_nget(dvp->v_mount, fhp, fhsize, &np); if (error) { VOP_LOCK(dvp); return (error); } newvp = NFSTOV(np); if (lockparent && (flags & ISLASTCN) && (error = VOP_LOCK(dvp))) { vput(newvp); return (error); } } else if (NFS_CMPFH(np, fhp, fhsize)) { VREF(dvp); newvp = dvp; } else { if (error = nfs_nget(dvp->v_mount, fhp, fhsize, &np)) { m_freem(mrep); return (error); } if (!lockparent || !(flags & ISLASTCN)) VOP_UNLOCK(dvp); newvp = NFSTOV(np); } if (v3) { nfsm_postop_attr(newvp, attrflag); nfsm_postop_attr(dvp, attrflag); } else nfsm_loadattr(newvp, (struct vattr *)0); if (cnp->cn_nameiop != LOOKUP && (flags & ISLASTCN)) cnp->cn_flags |= SAVENAME; if ((cnp->cn_flags & MAKEENTRY) && (cnp->cn_nameiop != DELETE || !(flags & ISLASTCN))) { np->n_ctime = np->n_vattr.va_ctime.tv_sec; cache_enter(dvp, newvp, cnp); } *vpp = newvp; nfsm_reqdone; if (error) { if (newvp != NULLVP) vrele(newvp); if ((cnp->cn_nameiop == CREATE || cnp->cn_nameiop == RENAME) && (flags & ISLASTCN) && error == ENOENT) { if (!lockparent) VOP_UNLOCK(dvp); if (dvp->v_mount->mnt_flag & MNT_RDONLY) error = EROFS; else error = EJUSTRETURN; } if (cnp->cn_nameiop != LOOKUP && (flags & ISLASTCN)) cnp->cn_flags |= SAVENAME; } return (error); } /* * nfs read call. * Just call nfs_bioread() to do the work. */ static int nfs_read(ap) struct vop_read_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; struct ucred *a_cred; } */ *ap; { register struct vnode *vp = ap->a_vp; if (vp->v_type != VREG) return (EPERM); return (nfs_bioread(vp, ap->a_uio, ap->a_ioflag, ap->a_cred)); } /* * nfs readlink call */ static int nfs_readlink(ap) struct vop_readlink_args /* { struct vnode *a_vp; struct uio *a_uio; struct ucred *a_cred; } */ *ap; { register struct vnode *vp = ap->a_vp; if (vp->v_type != VLNK) return (EPERM); return (nfs_bioread(vp, ap->a_uio, 0, ap->a_cred)); } /* * Do a readlink rpc. * Called by nfs_doio() from below the buffer cache. */ int nfs_readlinkrpc(vp, uiop, cred) register struct vnode *vp; struct uio *uiop; struct ucred *cred; { register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int error = 0, len, attrflag; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(vp); nfsstats.rpccnt[NFSPROC_READLINK]++; nfsm_reqhead(vp, NFSPROC_READLINK, NFSX_FH(v3)); nfsm_fhtom(vp, v3); nfsm_request(vp, NFSPROC_READLINK, uiop->uio_procp, cred); if (v3) nfsm_postop_attr(vp, attrflag); if (!error) { nfsm_strsiz(len, NFS_MAXPATHLEN); nfsm_mtouio(uiop, len); } nfsm_reqdone; return (error); } /* * nfs read rpc call * Ditto above */ int nfs_readrpc(vp, uiop, cred) register struct vnode *vp; struct uio *uiop; struct ucred *cred; { register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; struct mbuf *mreq, *mrep, *md, *mb, *mb2; struct nfsmount *nmp; int error = 0, len, retlen, tsiz, eof, attrflag; int v3 = NFS_ISV3(vp); #ifndef nolint eof = 0; #endif nmp = VFSTONFS(vp->v_mount); tsiz = uiop->uio_resid; if (uiop->uio_offset + tsiz > 0xffffffff && !v3) return (EFBIG); while (tsiz > 0) { nfsstats.rpccnt[NFSPROC_READ]++; len = (tsiz > nmp->nm_rsize) ? nmp->nm_rsize : tsiz; nfsm_reqhead(vp, NFSPROC_READ, NFSX_FH(v3) + NFSX_UNSIGNED * 3); nfsm_fhtom(vp, v3); nfsm_build(tl, u_long *, NFSX_UNSIGNED * 3); if (v3) { txdr_hyper(&uiop->uio_offset, tl); *(tl + 2) = txdr_unsigned(len); } else { *tl++ = txdr_unsigned(uiop->uio_offset); *tl++ = txdr_unsigned(len); *tl = 0; } nfsm_request(vp, NFSPROC_READ, uiop->uio_procp, cred); if (v3) { nfsm_postop_attr(vp, attrflag); if (error) { m_freem(mrep); goto nfsmout; } nfsm_dissect(tl, u_long *, 2 * NFSX_UNSIGNED); eof = fxdr_unsigned(int, *(tl + 1)); } else nfsm_loadattr(vp, (struct vattr *)0); nfsm_strsiz(retlen, nmp->nm_rsize); nfsm_mtouio(uiop, retlen); m_freem(mrep); tsiz -= retlen; if (v3) { if (eof || retlen == 0) tsiz = 0; } else if (retlen < len) tsiz = 0; } nfsmout: return (error); } /* * nfs write call */ int nfs_writerpc(vp, uiop, cred, iomode, must_commit) register struct vnode *vp; register struct uio *uiop; struct ucred *cred; int *iomode, *must_commit; { register u_long *tl; register caddr_t cp; register int t1, t2, backup; caddr_t bpos, dpos, cp2; struct mbuf *mreq, *mrep, *md, *mb, *mb2; struct nfsmount *nmp = VFSTONFS(vp->v_mount); int error = 0, len, tsiz, wccflag = NFSV3_WCCRATTR, rlen, commit; int v3 = NFS_ISV3(vp), committed = NFSV3WRITE_FILESYNC; #ifndef DIAGNOSTIC if (uiop->uio_iovcnt != 1) panic("nfs: writerpc iovcnt > 1"); #endif *must_commit = 0; tsiz = uiop->uio_resid; if (uiop->uio_offset + tsiz > 0xffffffff && !v3) return (EFBIG); while (tsiz > 0) { nfsstats.rpccnt[NFSPROC_WRITE]++; len = (tsiz > nmp->nm_wsize) ? nmp->nm_wsize : tsiz; nfsm_reqhead(vp, NFSPROC_WRITE, NFSX_FH(v3) + 5 * NFSX_UNSIGNED + nfsm_rndup(len)); nfsm_fhtom(vp, v3); if (v3) { nfsm_build(tl, u_long *, 5 * NFSX_UNSIGNED); txdr_hyper(&uiop->uio_offset, tl); tl += 2; *tl++ = txdr_unsigned(len); *tl++ = txdr_unsigned(*iomode); } else { nfsm_build(tl, u_long *, 4 * NFSX_UNSIGNED); *++tl = txdr_unsigned(uiop->uio_offset); tl += 2; } *tl = txdr_unsigned(len); nfsm_uiotom(uiop, len); nfsm_request(vp, NFSPROC_WRITE, uiop->uio_procp, cred); if (v3) { wccflag = NFSV3_WCCCHK; nfsm_wcc_data(vp, wccflag); if (!error) { nfsm_dissect(tl, u_long *, 2 * NFSX_UNSIGNED + NFSX_V3WRITEVERF); rlen = fxdr_unsigned(int, *tl++); if (rlen == 0) { error = NFSERR_IO; break; } else if (rlen < len) { backup = len - rlen; uiop->uio_iov->iov_base -= backup; uiop->uio_iov->iov_len += backup; uiop->uio_offset -= backup; uiop->uio_resid += backup; len = rlen; } commit = fxdr_unsigned(int, *tl++); /* * Return the lowest committment level * obtained by any of the RPCs. */ if (committed == NFSV3WRITE_FILESYNC) committed = commit; else if (committed == NFSV3WRITE_DATASYNC && commit == NFSV3WRITE_UNSTABLE) committed = commit; if ((nmp->nm_flag & NFSMNT_HASWRITEVERF) == 0) { bcopy((caddr_t)tl, (caddr_t)nmp->nm_verf, NFSX_V3WRITEVERF); nmp->nm_flag |= NFSMNT_HASWRITEVERF; } else if (bcmp((caddr_t)tl, (caddr_t)nmp->nm_verf, NFSX_V3WRITEVERF)) { *must_commit = 1; bcopy((caddr_t)tl, (caddr_t)nmp->nm_verf, NFSX_V3WRITEVERF); } } } else nfsm_loadattr(vp, (struct vattr *)0); if (wccflag) VTONFS(vp)->n_mtime = VTONFS(vp)->n_vattr.va_mtime.tv_sec; m_freem(mrep); tsiz -= len; } nfsmout: *iomode = committed; if (error) uiop->uio_resid = tsiz; return (error); } /* * nfs mknod rpc * For NFS v2 this is a kludge. Use a create rpc but with the IFMT bits of the * mode set to specify the file type and the size field for rdev. */ static int nfs_mknodrpc(dvp, vpp, cnp, vap) register struct vnode *dvp; register struct vnode **vpp; register struct componentname *cnp; register struct vattr *vap; { register struct nfsv2_sattr *sp; register struct nfsv3_sattr *sp3; register u_long *tl; register caddr_t cp; register long t1, t2; struct vnode *newvp = (struct vnode *)0; struct nfsnode *np = (struct nfsnode *)0; struct vattr vattr; char *cp2; caddr_t bpos, dpos; int error = 0, wccflag = NFSV3_WCCRATTR, gotvp = 0; struct mbuf *mreq, *mrep, *md, *mb, *mb2; u_long rdev; int v3 = NFS_ISV3(dvp); if (vap->va_type == VCHR || vap->va_type == VBLK) rdev = txdr_unsigned(vap->va_rdev); else if (vap->va_type == VFIFO || vap->va_type == VSOCK) rdev = 0xffffffff; else { VOP_ABORTOP(dvp, cnp); vput(dvp); return (EOPNOTSUPP); } if (error = VOP_GETATTR(dvp, &vattr, cnp->cn_cred, cnp->cn_proc)) { VOP_ABORTOP(dvp, cnp); vput(dvp); return (error); } nfsstats.rpccnt[NFSPROC_MKNOD]++; nfsm_reqhead(dvp, NFSPROC_MKNOD, NFSX_FH(v3) + 4 * NFSX_UNSIGNED + + nfsm_rndup(cnp->cn_namelen) + NFSX_SATTR(v3)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, cnp->cn_namelen, NFS_MAXNAMLEN); if (v3) { nfsm_build(tl, u_long *, NFSX_UNSIGNED + NFSX_V3SRVSATTR); *tl++ = vtonfsv3_type(vap->va_type); sp3 = (struct nfsv3_sattr *)tl; nfsm_v3sattr(sp3, vap, cnp->cn_cred->cr_uid, vattr.va_gid); if (vap->va_type == VCHR || vap->va_type == VBLK) { nfsm_build(tl, u_long *, 2 * NFSX_UNSIGNED); *tl++ = txdr_unsigned(major(vap->va_rdev)); *tl = txdr_unsigned(minor(vap->va_rdev)); } } else { nfsm_build(sp, struct nfsv2_sattr *, NFSX_V2SATTR); sp->sa_mode = vtonfsv2_mode(vap->va_type, vap->va_mode); sp->sa_uid = txdr_unsigned(cnp->cn_cred->cr_uid); sp->sa_gid = txdr_unsigned(vattr.va_gid); sp->sa_size = rdev; txdr_nfsv2time(&vap->va_atime, &sp->sa_atime); txdr_nfsv2time(&vap->va_mtime, &sp->sa_mtime); } nfsm_request(dvp, NFSPROC_MKNOD, cnp->cn_proc, cnp->cn_cred); if (!error) { nfsm_mtofh(dvp, newvp, v3, gotvp); if (!gotvp) { if (newvp) { vput(newvp); newvp = (struct vnode *)0; } error = nfs_lookitup(dvp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_cred, cnp->cn_proc, &np); if (!error) newvp = NFSTOV(np); } } if (v3) nfsm_wcc_data(dvp, wccflag); nfsm_reqdone; if (error) { if (newvp) vput(newvp); } else { if (cnp->cn_flags & MAKEENTRY) cache_enter(dvp, newvp, cnp); *vpp = newvp; } FREE(cnp->cn_pnbuf, M_NAMEI); VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; vput(dvp); return (error); } /* * nfs mknod vop * just call nfs_mknodrpc() to do the work. */ /* ARGSUSED */ static int nfs_mknod(ap) struct vop_mknod_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; struct vattr *a_vap; } */ *ap; { struct vnode *newvp; int error; error = nfs_mknodrpc(ap->a_dvp, &newvp, ap->a_cnp, ap->a_vap); if (!error) vput(newvp); return (error); } static u_long create_verf; /* * nfs file create call */ static int nfs_create(ap) struct vop_create_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; struct vattr *a_vap; } */ *ap; { register struct vnode *dvp = ap->a_dvp; register struct vattr *vap = ap->a_vap; register struct componentname *cnp = ap->a_cnp; register struct nfsv2_sattr *sp; register struct nfsv3_sattr *sp3; register u_long *tl; register caddr_t cp; register long t1, t2; struct nfsnode *np = (struct nfsnode *)0; struct vnode *newvp = (struct vnode *)0; caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR, gotvp = 0, fmode = 0; struct mbuf *mreq, *mrep, *md, *mb, *mb2; struct vattr vattr; int v3 = NFS_ISV3(dvp); /* * Oops, not for me.. */ if (vap->va_type == VSOCK) return (nfs_mknodrpc(dvp, ap->a_vpp, cnp, vap)); if (error = VOP_GETATTR(dvp, &vattr, cnp->cn_cred, cnp->cn_proc)) { VOP_ABORTOP(dvp, cnp); vput(dvp); return (error); } if (vap->va_vaflags & VA_EXCLUSIVE) fmode |= O_EXCL; again: nfsstats.rpccnt[NFSPROC_CREATE]++; nfsm_reqhead(dvp, NFSPROC_CREATE, NFSX_FH(v3) + 2 * NFSX_UNSIGNED + nfsm_rndup(cnp->cn_namelen) + NFSX_SATTR(v3)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, cnp->cn_namelen, NFS_MAXNAMLEN); if (v3) { nfsm_build(tl, u_long *, NFSX_UNSIGNED); if (fmode & O_EXCL) { *tl = txdr_unsigned(NFSV3CREATE_EXCLUSIVE); nfsm_build(tl, u_long *, NFSX_V3CREATEVERF); - if (in_ifaddr) - *tl++ = IA_SIN(in_ifaddr)->sin_addr.s_addr; + if (!TAILQ_EMPTY(&in_ifaddrhead)) + *tl++ = IA_SIN(in_ifaddrhead.tqh_first)->sin_addr.s_addr; else *tl++ = create_verf; *tl = ++create_verf; } else { *tl = txdr_unsigned(NFSV3CREATE_UNCHECKED); nfsm_build(tl, u_long *, NFSX_V3SRVSATTR); sp3 = (struct nfsv3_sattr *)tl; nfsm_v3sattr(sp3, vap, cnp->cn_cred->cr_uid, vattr.va_gid); } } else { nfsm_build(sp, struct nfsv2_sattr *, NFSX_V2SATTR); sp->sa_mode = vtonfsv2_mode(vap->va_type, vap->va_mode); sp->sa_uid = txdr_unsigned(cnp->cn_cred->cr_uid); sp->sa_gid = txdr_unsigned(vattr.va_gid); sp->sa_size = 0; txdr_nfsv2time(&vap->va_atime, &sp->sa_atime); txdr_nfsv2time(&vap->va_mtime, &sp->sa_mtime); } nfsm_request(dvp, NFSPROC_CREATE, cnp->cn_proc, cnp->cn_cred); if (!error) { nfsm_mtofh(dvp, newvp, v3, gotvp); if (!gotvp) { if (newvp) { vput(newvp); newvp = (struct vnode *)0; } error = nfs_lookitup(dvp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_cred, cnp->cn_proc, &np); if (!error) newvp = NFSTOV(np); } } if (v3) nfsm_wcc_data(dvp, wccflag); nfsm_reqdone; if (error) { if (v3 && (fmode & O_EXCL) && error == NFSERR_NOTSUPP) { fmode &= ~O_EXCL; goto again; } if (newvp) vput(newvp); } else if (v3 && (fmode & O_EXCL)) error = nfs_setattrrpc(newvp, vap, cnp->cn_cred, cnp->cn_proc); if (!error) { if (cnp->cn_flags & MAKEENTRY) cache_enter(dvp, newvp, cnp); *ap->a_vpp = newvp; } FREE(cnp->cn_pnbuf, M_NAMEI); VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; vput(dvp); return (error); } /* * nfs file remove call * To try and make nfs semantics closer to ufs semantics, a file that has * other processes using the vnode is renamed instead of removed and then * removed later on the last close. * - If v_usecount > 1 * If a rename is not already in the works * call nfs_sillyrename() to set it up * else * do the remove rpc */ static int nfs_remove(ap) struct vop_remove_args /* { struct vnodeop_desc *a_desc; struct vnode * a_dvp; struct vnode * a_vp; struct componentname * a_cnp; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct vnode *dvp = ap->a_dvp; register struct componentname *cnp = ap->a_cnp; register struct nfsnode *np = VTONFS(vp); int error = 0; struct vattr vattr; #ifndef DIAGNOSTIC if ((cnp->cn_flags & HASBUF) == 0) panic("nfs_remove: no name"); if (vp->v_usecount < 1) panic("nfs_remove: bad v_usecount"); #endif if (vp->v_usecount == 1 || (np->n_sillyrename && VOP_GETATTR(vp, &vattr, cnp->cn_cred, cnp->cn_proc) == 0 && vattr.va_nlink > 1)) { /* * Purge the name cache so that the chance of a lookup for * the name succeeding while the remove is in progress is * minimized. Without node locking it can still happen, such * that an I/O op returns ESTALE, but since you get this if * another host removes the file.. */ cache_purge(vp); /* * throw away biocache buffers, mainly to avoid * unnecessary delayed writes later. */ error = nfs_vinvalbuf(vp, 0, cnp->cn_cred, cnp->cn_proc, 1); /* Do the rpc */ if (error != EINTR) error = nfs_removerpc(dvp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_cred, cnp->cn_proc); /* * Kludge City: If the first reply to the remove rpc is lost.. * the reply to the retransmitted request will be ENOENT * since the file was in fact removed * Therefore, we cheat and return success. */ if (error == ENOENT) error = 0; } else if (!np->n_sillyrename) error = nfs_sillyrename(dvp, vp, cnp); FREE(cnp->cn_pnbuf, M_NAMEI); np->n_attrstamp = 0; vput(dvp); if (vp == dvp) vrele(vp); else vput(vp); return (error); } /* * nfs file remove rpc called from nfs_inactive */ int nfs_removeit(sp) register struct sillyrename *sp; { return (nfs_removerpc(sp->s_dvp, sp->s_name, sp->s_namlen, sp->s_cred, (struct proc *)0)); } /* * Nfs remove rpc, called from nfs_remove() and nfs_removeit(). */ static int nfs_removerpc(dvp, name, namelen, cred, proc) register struct vnode *dvp; char *name; int namelen; struct ucred *cred; struct proc *proc; { register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(dvp); nfsstats.rpccnt[NFSPROC_REMOVE]++; nfsm_reqhead(dvp, NFSPROC_REMOVE, NFSX_FH(v3) + NFSX_UNSIGNED + nfsm_rndup(namelen)); nfsm_fhtom(dvp, v3); nfsm_strtom(name, namelen, NFS_MAXNAMLEN); nfsm_request(dvp, NFSPROC_REMOVE, proc, cred); if (v3) nfsm_wcc_data(dvp, wccflag); nfsm_reqdone; VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; return (error); } /* * nfs file rename call */ static int nfs_rename(ap) struct vop_rename_args /* { struct vnode *a_fdvp; struct vnode *a_fvp; struct componentname *a_fcnp; struct vnode *a_tdvp; struct vnode *a_tvp; struct componentname *a_tcnp; } */ *ap; { register struct vnode *fvp = ap->a_fvp; register struct vnode *tvp = ap->a_tvp; register struct vnode *fdvp = ap->a_fdvp; register struct vnode *tdvp = ap->a_tdvp; register struct componentname *tcnp = ap->a_tcnp; register struct componentname *fcnp = ap->a_fcnp; int error; #ifndef DIAGNOSTIC if ((tcnp->cn_flags & HASBUF) == 0 || (fcnp->cn_flags & HASBUF) == 0) panic("nfs_rename: no name"); #endif /* Check for cross-device rename */ if ((fvp->v_mount != tdvp->v_mount) || (tvp && (fvp->v_mount != tvp->v_mount))) { error = EXDEV; goto out; } /* * If the tvp exists and is in use, sillyrename it before doing the * rename of the new file over it. */ if (tvp && tvp->v_usecount > 1 && !VTONFS(tvp)->n_sillyrename && !nfs_sillyrename(tdvp, tvp, tcnp)) { vrele(tvp); tvp = NULL; } error = nfs_renamerpc(fdvp, fcnp->cn_nameptr, fcnp->cn_namelen, tdvp, tcnp->cn_nameptr, tcnp->cn_namelen, tcnp->cn_cred, tcnp->cn_proc); if (fvp->v_type == VDIR) { if (tvp != NULL && tvp->v_type == VDIR) cache_purge(tdvp); cache_purge(fdvp); } out: if (tdvp == tvp) vrele(tdvp); else vput(tdvp); if (tvp) vput(tvp); vrele(fdvp); vrele(fvp); /* * Kludge: Map ENOENT => 0 assuming that it is a reply to a retry. */ if (error == ENOENT) error = 0; return (error); } /* * nfs file rename rpc called from nfs_remove() above */ static int nfs_renameit(sdvp, scnp, sp) struct vnode *sdvp; struct componentname *scnp; register struct sillyrename *sp; { return (nfs_renamerpc(sdvp, scnp->cn_nameptr, scnp->cn_namelen, sdvp, sp->s_name, sp->s_namlen, scnp->cn_cred, scnp->cn_proc)); } /* * Do an nfs rename rpc. Called from nfs_rename() and nfs_renameit(). */ static int nfs_renamerpc(fdvp, fnameptr, fnamelen, tdvp, tnameptr, tnamelen, cred, proc) register struct vnode *fdvp; char *fnameptr; int fnamelen; register struct vnode *tdvp; char *tnameptr; int tnamelen; struct ucred *cred; struct proc *proc; { register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int error = 0, fwccflag = NFSV3_WCCRATTR, twccflag = NFSV3_WCCRATTR; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(fdvp); nfsstats.rpccnt[NFSPROC_RENAME]++; nfsm_reqhead(fdvp, NFSPROC_RENAME, (NFSX_FH(v3) + NFSX_UNSIGNED)*2 + nfsm_rndup(fnamelen) + nfsm_rndup(tnamelen)); nfsm_fhtom(fdvp, v3); nfsm_strtom(fnameptr, fnamelen, NFS_MAXNAMLEN); nfsm_fhtom(tdvp, v3); nfsm_strtom(tnameptr, tnamelen, NFS_MAXNAMLEN); nfsm_request(fdvp, NFSPROC_RENAME, proc, cred); if (v3) { nfsm_wcc_data(fdvp, fwccflag); nfsm_wcc_data(tdvp, twccflag); } nfsm_reqdone; VTONFS(fdvp)->n_flag |= NMODIFIED; VTONFS(tdvp)->n_flag |= NMODIFIED; if (!fwccflag) VTONFS(fdvp)->n_attrstamp = 0; if (!twccflag) VTONFS(tdvp)->n_attrstamp = 0; return (error); } /* * nfs hard link create call */ static int nfs_link(ap) struct vop_link_args /* { struct vnode *a_tdvp; struct vnode *a_vp; struct componentname *a_cnp; } */ *ap; { #if defined(__NetBSD__) /* * Since the args are reversed in the VOP_LINK() calls, * switch them back. Argh! */ register struct vnode *vp = ap->a_tdvp; register struct vnode *tdvp = ap->a_vp; #else register struct vnode *vp = ap->a_vp; register struct vnode *tdvp = ap->a_tdvp; #endif register struct componentname *cnp = ap->a_cnp; register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR, attrflag = 0; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(vp); if (vp->v_mount != tdvp->v_mount) { /*VOP_ABORTOP(vp, cnp);*/ if (tdvp == vp) vrele(tdvp); else vput(tdvp); return (EXDEV); } /* * Push all writes to the server, so that the attribute cache * doesn't get "out of sync" with the server. * XXX There should be a better way! */ VOP_FSYNC(vp, cnp->cn_cred, MNT_WAIT, cnp->cn_proc); nfsstats.rpccnt[NFSPROC_LINK]++; nfsm_reqhead(vp, NFSPROC_LINK, NFSX_FH(v3)*2 + NFSX_UNSIGNED + nfsm_rndup(cnp->cn_namelen)); nfsm_fhtom(vp, v3); nfsm_fhtom(tdvp, v3); nfsm_strtom(cnp->cn_nameptr, cnp->cn_namelen, NFS_MAXNAMLEN); nfsm_request(vp, NFSPROC_LINK, cnp->cn_proc, cnp->cn_cred); if (v3) { nfsm_postop_attr(vp, attrflag); nfsm_wcc_data(tdvp, wccflag); } nfsm_reqdone; FREE(cnp->cn_pnbuf, M_NAMEI); VTONFS(tdvp)->n_flag |= NMODIFIED; if (!attrflag) VTONFS(vp)->n_attrstamp = 0; if (!wccflag) VTONFS(tdvp)->n_attrstamp = 0; vput(tdvp); /* * Kludge: Map EEXIST => 0 assuming that it is a reply to a retry. */ if (error == EEXIST) error = 0; return (error); } /* * nfs symbolic link create call */ static int nfs_symlink(ap) struct vop_symlink_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; struct vattr *a_vap; char *a_target; } */ *ap; { register struct vnode *dvp = ap->a_dvp; register struct vattr *vap = ap->a_vap; register struct componentname *cnp = ap->a_cnp; register struct nfsv2_sattr *sp; register struct nfsv3_sattr *sp3; register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int slen, error = 0, wccflag = NFSV3_WCCRATTR, gotvp; struct mbuf *mreq, *mrep, *md, *mb, *mb2; struct vnode *newvp = (struct vnode *)0; int v3 = NFS_ISV3(dvp); nfsstats.rpccnt[NFSPROC_SYMLINK]++; slen = strlen(ap->a_target); nfsm_reqhead(dvp, NFSPROC_SYMLINK, NFSX_FH(v3) + 2*NFSX_UNSIGNED + nfsm_rndup(cnp->cn_namelen) + nfsm_rndup(slen) + NFSX_SATTR(v3)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, cnp->cn_namelen, NFS_MAXNAMLEN); if (v3) { nfsm_build(sp3, struct nfsv3_sattr *, NFSX_V3SRVSATTR); nfsm_v3sattr(sp3, vap, cnp->cn_cred->cr_uid, cnp->cn_cred->cr_gid); } nfsm_strtom(ap->a_target, slen, NFS_MAXPATHLEN); if (!v3) { nfsm_build(sp, struct nfsv2_sattr *, NFSX_V2SATTR); sp->sa_mode = vtonfsv2_mode(VLNK, vap->va_mode); sp->sa_uid = txdr_unsigned(cnp->cn_cred->cr_uid); sp->sa_gid = txdr_unsigned(cnp->cn_cred->cr_gid); sp->sa_size = -1; txdr_nfsv2time(&vap->va_atime, &sp->sa_atime); txdr_nfsv2time(&vap->va_mtime, &sp->sa_mtime); } nfsm_request(dvp, NFSPROC_SYMLINK, cnp->cn_proc, cnp->cn_cred); if (v3) { if (!error) nfsm_mtofh(dvp, newvp, v3, gotvp); nfsm_wcc_data(dvp, wccflag); } nfsm_reqdone; if (newvp) vput(newvp); FREE(cnp->cn_pnbuf, M_NAMEI); VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; vput(dvp); /* * Kludge: Map EEXIST => 0 assuming that it is a reply to a retry. */ if (error == EEXIST) error = 0; return (error); } /* * nfs make dir call */ static int nfs_mkdir(ap) struct vop_mkdir_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; struct vattr *a_vap; } */ *ap; { register struct vnode *dvp = ap->a_dvp; register struct vattr *vap = ap->a_vap; register struct componentname *cnp = ap->a_cnp; register struct nfsv2_sattr *sp; register struct nfsv3_sattr *sp3; register u_long *tl; register caddr_t cp; register long t1, t2; register int len; struct nfsnode *np = (struct nfsnode *)0; struct vnode *newvp = (struct vnode *)0; caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR; int gotvp = 0; struct mbuf *mreq, *mrep, *md, *mb, *mb2; struct vattr vattr; int v3 = NFS_ISV3(dvp); if (error = VOP_GETATTR(dvp, &vattr, cnp->cn_cred, cnp->cn_proc)) { VOP_ABORTOP(dvp, cnp); vput(dvp); return (error); } len = cnp->cn_namelen; nfsstats.rpccnt[NFSPROC_MKDIR]++; nfsm_reqhead(dvp, NFSPROC_MKDIR, NFSX_FH(v3) + NFSX_UNSIGNED + nfsm_rndup(len) + NFSX_SATTR(v3)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, len, NFS_MAXNAMLEN); if (v3) { nfsm_build(sp3, struct nfsv3_sattr *, NFSX_V3SRVSATTR); nfsm_v3sattr(sp3, vap, cnp->cn_cred->cr_uid, vattr.va_gid); } else { nfsm_build(sp, struct nfsv2_sattr *, NFSX_V2SATTR); sp->sa_mode = vtonfsv2_mode(VDIR, vap->va_mode); sp->sa_uid = txdr_unsigned(cnp->cn_cred->cr_uid); sp->sa_gid = txdr_unsigned(vattr.va_gid); sp->sa_size = -1; txdr_nfsv2time(&vap->va_atime, &sp->sa_atime); txdr_nfsv2time(&vap->va_mtime, &sp->sa_mtime); } nfsm_request(dvp, NFSPROC_MKDIR, cnp->cn_proc, cnp->cn_cred); if (!error) nfsm_mtofh(dvp, newvp, v3, gotvp); if (v3) nfsm_wcc_data(dvp, wccflag); nfsm_reqdone; VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; /* * Kludge: Map EEXIST => 0 assuming that you have a reply to a retry * if we can succeed in looking up the directory. */ if (error == EEXIST || (!error && !gotvp)) { if (newvp) { vrele(newvp); newvp = (struct vnode *)0; } error = nfs_lookitup(dvp, cnp->cn_nameptr, len, cnp->cn_cred, cnp->cn_proc, &np); if (!error) { newvp = NFSTOV(np); if (newvp->v_type != VDIR) error = EEXIST; } } if (error) { if (newvp) vrele(newvp); } else *ap->a_vpp = newvp; FREE(cnp->cn_pnbuf, M_NAMEI); vput(dvp); return (error); } /* * nfs remove directory call */ static int nfs_rmdir(ap) struct vop_rmdir_args /* { struct vnode *a_dvp; struct vnode *a_vp; struct componentname *a_cnp; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct vnode *dvp = ap->a_dvp; register struct componentname *cnp = ap->a_cnp; register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(dvp); if (dvp == vp) { vput(dvp); vrele(dvp); FREE(cnp->cn_pnbuf, M_NAMEI); return (EINVAL); } nfsstats.rpccnt[NFSPROC_RMDIR]++; nfsm_reqhead(dvp, NFSPROC_RMDIR, NFSX_FH(v3) + NFSX_UNSIGNED + nfsm_rndup(cnp->cn_namelen)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, cnp->cn_namelen, NFS_MAXNAMLEN); nfsm_request(dvp, NFSPROC_RMDIR, cnp->cn_proc, cnp->cn_cred); if (v3) nfsm_wcc_data(dvp, wccflag); nfsm_reqdone; FREE(cnp->cn_pnbuf, M_NAMEI); VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; cache_purge(dvp); cache_purge(vp); vput(vp); vput(dvp); /* * Kludge: Map ENOENT => 0 assuming that you have a reply to a retry. */ if (error == ENOENT) error = 0; return (error); } /* * nfs readdir call */ static int nfs_readdir(ap) struct vop_readdir_args /* { struct vnode *a_vp; struct uio *a_uio; struct ucred *a_cred; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); register struct uio *uio = ap->a_uio; int tresid, error; struct vattr vattr; if (vp->v_type != VDIR) return (EPERM); /* * First, check for hit on the EOF offset cache */ if (np->n_direofoffset > 0 && uio->uio_offset >= np->n_direofoffset && (np->n_flag & NMODIFIED) == 0) { if (VFSTONFS(vp->v_mount)->nm_flag & NFSMNT_NQNFS) { if (NQNFS_CKCACHABLE(vp, ND_READ)) { nfsstats.direofcache_hits++; return (0); } } else if (VOP_GETATTR(vp, &vattr, ap->a_cred, uio->uio_procp) == 0 && np->n_mtime == vattr.va_mtime.tv_sec) { nfsstats.direofcache_hits++; return (0); } } /* * Call nfs_bioread() to do the real work. */ tresid = uio->uio_resid; error = nfs_bioread(vp, uio, 0, ap->a_cred); if (!error && uio->uio_resid == tresid) nfsstats.direofcache_misses++; return (error); } /* * Readdir rpc call. * Called from below the buffer cache by nfs_doio(). */ int nfs_readdirrpc(vp, uiop, cred) struct vnode *vp; register struct uio *uiop; struct ucred *cred; { register int len, left; register struct dirent *dp; register u_long *tl; register caddr_t cp; register long t1, t2; register nfsuint64 *cookiep; caddr_t bpos, dpos, cp2; struct mbuf *mreq, *mrep, *md, *mb, *mb2; nfsuint64 cookie; struct nfsmount *nmp = VFSTONFS(vp->v_mount); struct nfsnode *dnp = VTONFS(vp); u_quad_t fileno; int error = 0, tlen, more_dirs = 1, blksiz = 0, bigenough = 1; int attrflag; int v3 = NFS_ISV3(vp); #ifndef nolint dp = (struct dirent *)0; #endif #ifndef DIAGNOSTIC if (uiop->uio_iovcnt != 1 || (uiop->uio_offset & (NFS_DIRBLKSIZ - 1)) || (uiop->uio_resid & (NFS_DIRBLKSIZ - 1))) panic("nfs readdirrpc bad uio"); #endif /* * If there is no cookie, assume directory was stale. */ cookiep = nfs_getcookie(dnp, uiop->uio_offset, 0); if (cookiep) cookie = *cookiep; else return (NFSERR_BAD_COOKIE); /* * Loop around doing readdir rpc's of size nm_readdirsize * truncated to a multiple of DIRBLKSIZ. * The stopping criteria is EOF or buffer full. */ while (more_dirs && bigenough) { nfsstats.rpccnt[NFSPROC_READDIR]++; nfsm_reqhead(vp, NFSPROC_READDIR, NFSX_FH(v3) + NFSX_READDIR(v3)); nfsm_fhtom(vp, v3); if (v3) { nfsm_build(tl, u_long *, 5 * NFSX_UNSIGNED); *tl++ = cookie.nfsuquad[0]; *tl++ = cookie.nfsuquad[1]; *tl++ = dnp->n_cookieverf.nfsuquad[0]; *tl++ = dnp->n_cookieverf.nfsuquad[1]; } else { nfsm_build(tl, u_long *, 2 * NFSX_UNSIGNED); *tl++ = cookie.nfsuquad[0]; } *tl = txdr_unsigned(nmp->nm_readdirsize); nfsm_request(vp, NFSPROC_READDIR, uiop->uio_procp, cred); if (v3) { nfsm_postop_attr(vp, attrflag); if (!error) { nfsm_dissect(tl, u_long *, 2 * NFSX_UNSIGNED); dnp->n_cookieverf.nfsuquad[0] = *tl++; dnp->n_cookieverf.nfsuquad[1] = *tl; } else { m_freem(mrep); goto nfsmout; } } nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); more_dirs = fxdr_unsigned(int, *tl); /* loop thru the dir entries, doctoring them to 4bsd form */ while (more_dirs && bigenough) { if (v3) { nfsm_dissect(tl, u_long *, 3 * NFSX_UNSIGNED); fxdr_hyper(tl, &fileno); len = fxdr_unsigned(int, *(tl + 2)); } else { nfsm_dissect(tl, u_long *, 2 * NFSX_UNSIGNED); fileno = fxdr_unsigned(u_quad_t, *tl++); len = fxdr_unsigned(int, *tl); } if (len <= 0 || len > NFS_MAXNAMLEN) { error = EBADRPC; m_freem(mrep); goto nfsmout; } tlen = nfsm_rndup(len); if (tlen == len) tlen += 4; /* To ensure null termination */ left = DIRBLKSIZ - blksiz; if ((tlen + DIRHDSIZ) > left) { dp->d_reclen += left; uiop->uio_iov->iov_base += left; uiop->uio_iov->iov_len -= left; uiop->uio_offset += left; uiop->uio_resid -= left; blksiz = 0; } if ((tlen + DIRHDSIZ) > uiop->uio_resid) bigenough = 0; if (bigenough) { dp = (struct dirent *)uiop->uio_iov->iov_base; dp->d_fileno = (int)fileno; dp->d_namlen = len; dp->d_reclen = tlen + DIRHDSIZ; dp->d_type = DT_UNKNOWN; blksiz += dp->d_reclen; if (blksiz == DIRBLKSIZ) blksiz = 0; uiop->uio_offset += DIRHDSIZ; uiop->uio_resid -= DIRHDSIZ; uiop->uio_iov->iov_base += DIRHDSIZ; uiop->uio_iov->iov_len -= DIRHDSIZ; nfsm_mtouio(uiop, len); cp = uiop->uio_iov->iov_base; tlen -= len; *cp = '\0'; /* null terminate */ uiop->uio_iov->iov_base += tlen; uiop->uio_iov->iov_len -= tlen; uiop->uio_offset += tlen; uiop->uio_resid -= tlen; } else nfsm_adv(nfsm_rndup(len)); if (v3) { nfsm_dissect(tl, u_long *, 3 * NFSX_UNSIGNED); } else { nfsm_dissect(tl, u_long *, 2 * NFSX_UNSIGNED); } if (bigenough) { cookie.nfsuquad[0] = *tl++; if (v3) cookie.nfsuquad[1] = *tl++; } else if (v3) tl += 2; else tl++; more_dirs = fxdr_unsigned(int, *tl); } /* * If at end of rpc data, get the eof boolean */ if (!more_dirs) { nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); more_dirs = (fxdr_unsigned(int, *tl) == 0); } m_freem(mrep); } /* * Fill last record, iff any, out to a multiple of DIRBLKSIZ * by increasing d_reclen for the last record. */ if (blksiz > 0) { left = DIRBLKSIZ - blksiz; dp->d_reclen += left; uiop->uio_iov->iov_base += left; uiop->uio_iov->iov_len -= left; uiop->uio_offset += left; uiop->uio_resid -= left; } /* * We are now either at the end of the directory or have filled the * block. */ if (bigenough) dnp->n_direofoffset = uiop->uio_offset; else { if (uiop->uio_resid > 0) printf("EEK! readdirrpc resid > 0\n"); cookiep = nfs_getcookie(dnp, uiop->uio_offset, 1); *cookiep = cookie; } nfsmout: return (error); } /* * NFS V3 readdir plus RPC. Used in place of nfs_readdirrpc(). */ int nfs_readdirplusrpc(vp, uiop, cred) struct vnode *vp; register struct uio *uiop; struct ucred *cred; { register int len, left; register struct dirent *dp; register u_long *tl; register caddr_t cp; register long t1, t2; register struct vnode *newvp; register nfsuint64 *cookiep; caddr_t bpos, dpos, cp2, dpossav1, dpossav2; struct mbuf *mreq, *mrep, *md, *mb, *mb2, *mdsav1, *mdsav2; struct nameidata nami, *ndp = &nami; struct componentname *cnp = &ndp->ni_cnd; nfsuint64 cookie; struct nfsmount *nmp = VFSTONFS(vp->v_mount); struct nfsnode *dnp = VTONFS(vp), *np; nfsfh_t *fhp; u_quad_t fileno; int error = 0, tlen, more_dirs = 1, blksiz = 0, doit, bigenough = 1, i; int attrflag, fhsize; #ifndef nolint dp = (struct dirent *)0; #endif #ifndef DIAGNOSTIC if (uiop->uio_iovcnt != 1 || (uiop->uio_offset & (DIRBLKSIZ - 1)) || (uiop->uio_resid & (DIRBLKSIZ - 1))) panic("nfs readdirplusrpc bad uio"); #endif ndp->ni_dvp = vp; newvp = NULLVP; /* * If there is no cookie, assume directory was stale. */ cookiep = nfs_getcookie(dnp, uiop->uio_offset, 0); if (cookiep) cookie = *cookiep; else return (NFSERR_BAD_COOKIE); /* * Loop around doing readdir rpc's of size nm_readdirsize * truncated to a multiple of DIRBLKSIZ. * The stopping criteria is EOF or buffer full. */ while (more_dirs && bigenough) { nfsstats.rpccnt[NFSPROC_READDIRPLUS]++; nfsm_reqhead(vp, NFSPROC_READDIRPLUS, NFSX_FH(1) + 6 * NFSX_UNSIGNED); nfsm_fhtom(vp, 1); nfsm_build(tl, u_long *, 6 * NFSX_UNSIGNED); *tl++ = cookie.nfsuquad[0]; *tl++ = cookie.nfsuquad[1]; *tl++ = dnp->n_cookieverf.nfsuquad[0]; *tl++ = dnp->n_cookieverf.nfsuquad[1]; *tl++ = txdr_unsigned(nmp->nm_readdirsize); *tl = txdr_unsigned(nmp->nm_rsize); nfsm_request(vp, NFSPROC_READDIRPLUS, uiop->uio_procp, cred); nfsm_postop_attr(vp, attrflag); if (error) { m_freem(mrep); goto nfsmout; } nfsm_dissect(tl, u_long *, 3 * NFSX_UNSIGNED); dnp->n_cookieverf.nfsuquad[0] = *tl++; dnp->n_cookieverf.nfsuquad[1] = *tl++; more_dirs = fxdr_unsigned(int, *tl); /* loop thru the dir entries, doctoring them to 4bsd form */ while (more_dirs && bigenough) { nfsm_dissect(tl, u_long *, 3 * NFSX_UNSIGNED); fxdr_hyper(tl, &fileno); len = fxdr_unsigned(int, *(tl + 2)); if (len <= 0 || len > NFS_MAXNAMLEN) { error = EBADRPC; m_freem(mrep); goto nfsmout; } tlen = nfsm_rndup(len); if (tlen == len) tlen += 4; /* To ensure null termination*/ left = DIRBLKSIZ - blksiz; if ((tlen + DIRHDSIZ) > left) { dp->d_reclen += left; uiop->uio_iov->iov_base += left; uiop->uio_iov->iov_len -= left; uiop->uio_offset += left; uiop->uio_resid -= left; blksiz = 0; } if ((tlen + DIRHDSIZ) > uiop->uio_resid) bigenough = 0; if (bigenough) { dp = (struct dirent *)uiop->uio_iov->iov_base; dp->d_fileno = (int)fileno; dp->d_namlen = len; dp->d_reclen = tlen + DIRHDSIZ; dp->d_type = DT_UNKNOWN; blksiz += dp->d_reclen; if (blksiz == DIRBLKSIZ) blksiz = 0; uiop->uio_offset += DIRHDSIZ; uiop->uio_resid -= DIRHDSIZ; uiop->uio_iov->iov_base += DIRHDSIZ; uiop->uio_iov->iov_len -= DIRHDSIZ; cnp->cn_nameptr = uiop->uio_iov->iov_base; cnp->cn_namelen = len; nfsm_mtouio(uiop, len); cp = uiop->uio_iov->iov_base; tlen -= len; *cp = '\0'; uiop->uio_iov->iov_base += tlen; uiop->uio_iov->iov_len -= tlen; uiop->uio_offset += tlen; uiop->uio_resid -= tlen; } else nfsm_adv(nfsm_rndup(len)); nfsm_dissect(tl, u_long *, 3 * NFSX_UNSIGNED); if (bigenough) { cookie.nfsuquad[0] = *tl++; cookie.nfsuquad[1] = *tl++; } else tl += 2; /* * Since the attributes are before the file handle * (sigh), we must skip over the attributes and then * come back and get them. */ attrflag = fxdr_unsigned(int, *tl); if (attrflag) { dpossav1 = dpos; mdsav1 = md; nfsm_adv(NFSX_V3FATTR); nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); doit = fxdr_unsigned(int, *tl); if (doit) { nfsm_getfh(fhp, fhsize, 1); if (NFS_CMPFH(dnp, fhp, fhsize)) { VREF(vp); newvp = vp; np = dnp; } else { if (error = nfs_nget(vp->v_mount, fhp, fhsize, &np)) doit = 0; else newvp = NFSTOV(np); } } if (doit) { dpossav2 = dpos; dpos = dpossav1; mdsav2 = md; md = mdsav1; nfsm_loadattr(newvp, (struct vattr *)0); dpos = dpossav2; md = mdsav2; dp->d_type = IFTODT(VTTOIF(np->n_vattr.va_type)); ndp->ni_vp = newvp; cnp->cn_hash = 0; for (cp = cnp->cn_nameptr, i = 1; i <= len; i++, cp++) cnp->cn_hash += (unsigned char)*cp * i; if (cnp->cn_namelen <= NCHNAMLEN) cache_enter(ndp->ni_dvp, ndp->ni_vp, cnp); } } else { /* Just skip over the file handle */ nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); i = fxdr_unsigned(int, *tl); nfsm_adv(nfsm_rndup(i)); } if (newvp != NULLVP) { vrele(newvp); newvp = NULLVP; } nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); more_dirs = fxdr_unsigned(int, *tl); } /* * If at end of rpc data, get the eof boolean */ if (!more_dirs) { nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); more_dirs = (fxdr_unsigned(int, *tl) == 0); } m_freem(mrep); } /* * Fill last record, iff any, out to a multiple of NFS_DIRBLKSIZ * by increasing d_reclen for the last record. */ if (blksiz > 0) { left = DIRBLKSIZ - blksiz; dp->d_reclen += left; uiop->uio_iov->iov_base += left; uiop->uio_iov->iov_len -= left; uiop->uio_offset += left; uiop->uio_resid -= left; } /* * We are now either at the end of the directory or have filled the * block. */ if (bigenough) dnp->n_direofoffset = uiop->uio_offset; else { if (uiop->uio_resid > 0) printf("EEK! readdirplusrpc resid > 0\n"); cookiep = nfs_getcookie(dnp, uiop->uio_offset, 1); *cookiep = cookie; } nfsmout: if (newvp != NULLVP) { if (newvp == vp) vrele(newvp); else vput(newvp); newvp = NULLVP; } return (error); } /* * Silly rename. To make the NFS filesystem that is stateless look a little * more like the "ufs" a remove of an active vnode is translated to a rename * to a funny looking filename that is removed by nfs_inactive on the * nfsnode. There is the potential for another process on a different client * to create the same funny name between the nfs_lookitup() fails and the * nfs_rename() completes, but... */ static int nfs_sillyrename(dvp, vp, cnp) struct vnode *dvp, *vp; struct componentname *cnp; { register struct sillyrename *sp; struct nfsnode *np; int error; short pid; cache_purge(dvp); np = VTONFS(vp); #ifndef DIAGNOSTIC if (vp->v_type == VDIR) panic("nfs: sillyrename dir"); #endif MALLOC(sp, struct sillyrename *, sizeof (struct sillyrename), M_NFSREQ, M_WAITOK); sp->s_cred = crdup(cnp->cn_cred); sp->s_dvp = dvp; VREF(dvp); /* Fudge together a funny name */ pid = cnp->cn_proc->p_pid; sp->s_namlen = sprintf(sp->s_name, ".nfsA%04x4.4", pid); /* Try lookitups until we get one that isn't there */ while (nfs_lookitup(dvp, sp->s_name, sp->s_namlen, sp->s_cred, cnp->cn_proc, (struct nfsnode **)0) == 0) { sp->s_name[4]++; if (sp->s_name[4] > 'z') { error = EINVAL; goto bad; } } if (error = nfs_renameit(dvp, cnp, sp)) goto bad; error = nfs_lookitup(dvp, sp->s_name, sp->s_namlen, sp->s_cred, cnp->cn_proc, &np); np->n_sillyrename = sp; return (0); bad: vrele(sp->s_dvp); crfree(sp->s_cred); free((caddr_t)sp, M_NFSREQ); return (error); } /* * Look up a file name and optionally either update the file handle or * allocate an nfsnode, depending on the value of npp. * npp == NULL --> just do the lookup * *npp == NULL --> allocate a new nfsnode and make sure attributes are * handled too * *npp != NULL --> update the file handle in the vnode */ static int nfs_lookitup(dvp, name, len, cred, procp, npp) register struct vnode *dvp; char *name; int len; struct ucred *cred; struct proc *procp; struct nfsnode **npp; { register u_long *tl; register caddr_t cp; register long t1, t2; struct vnode *newvp = (struct vnode *)0; struct nfsnode *np, *dnp = VTONFS(dvp); caddr_t bpos, dpos, cp2; int error = 0, fhlen, attrflag; struct mbuf *mreq, *mrep, *md, *mb, *mb2; nfsfh_t *nfhp; int v3 = NFS_ISV3(dvp); nfsstats.rpccnt[NFSPROC_LOOKUP]++; nfsm_reqhead(dvp, NFSPROC_LOOKUP, NFSX_FH(v3) + NFSX_UNSIGNED + nfsm_rndup(len)); nfsm_fhtom(dvp, v3); nfsm_strtom(name, len, NFS_MAXNAMLEN); nfsm_request(dvp, NFSPROC_LOOKUP, procp, cred); if (npp && !error) { nfsm_getfh(nfhp, fhlen, v3); if (*npp) { np = *npp; if (np->n_fhsize > NFS_SMALLFH && fhlen <= NFS_SMALLFH) { free((caddr_t)np->n_fhp, M_NFSBIGFH); np->n_fhp = &np->n_fh; } else if (np->n_fhsize <= NFS_SMALLFH && fhlen>NFS_SMALLFH) np->n_fhp =(nfsfh_t *)malloc(fhlen,M_NFSBIGFH,M_WAITOK); bcopy((caddr_t)nfhp, (caddr_t)np->n_fhp, fhlen); np->n_fhsize = fhlen; newvp = NFSTOV(np); } else if (NFS_CMPFH(dnp, nfhp, fhlen)) { VREF(dvp); newvp = dvp; } else { error = nfs_nget(dvp->v_mount, nfhp, fhlen, &np); if (error) { m_freem(mrep); return (error); } newvp = NFSTOV(np); } if (v3) { nfsm_postop_attr(newvp, attrflag); if (!attrflag && *npp == NULL) { m_freem(mrep); if (newvp == dvp) vrele(newvp); else vput(newvp); return (ENOENT); } } else nfsm_loadattr(newvp, (struct vattr *)0); } nfsm_reqdone; if (npp && *npp == NULL) { if (error) { if (newvp) if (newvp == dvp) vrele(newvp); else vput(newvp); } else *npp = np; } return (error); } /* * Nfs Version 3 commit rpc */ static int nfs_commit(vp, offset, cnt, cred, procp) register struct vnode *vp; u_quad_t offset; int cnt; struct ucred *cred; struct proc *procp; { register caddr_t cp; register u_long *tl; register int t1, t2; register struct nfsmount *nmp = VFSTONFS(vp->v_mount); caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR; struct mbuf *mreq, *mrep, *md, *mb, *mb2; if ((nmp->nm_flag & NFSMNT_HASWRITEVERF) == 0) return (0); nfsstats.rpccnt[NFSPROC_COMMIT]++; nfsm_reqhead(vp, NFSPROC_COMMIT, NFSX_FH(1)); nfsm_fhtom(vp, 1); nfsm_build(tl, u_long *, 3 * NFSX_UNSIGNED); txdr_hyper(&offset, tl); tl += 2; *tl = txdr_unsigned(cnt); nfsm_request(vp, NFSPROC_COMMIT, procp, cred); nfsm_wcc_data(vp, wccflag); if (!error) { nfsm_dissect(tl, u_long *, NFSX_V3WRITEVERF); if (bcmp((caddr_t)nmp->nm_verf, (caddr_t)tl, NFSX_V3WRITEVERF)) { bcopy((caddr_t)tl, (caddr_t)nmp->nm_verf, NFSX_V3WRITEVERF); error = NFSERR_STALEWRITEVERF; } } nfsm_reqdone; return (error); } /* * Kludge City.. * - make nfs_bmap() essentially a no-op that does no translation * - do nfs_strategy() by doing I/O with nfs_readrpc/nfs_writerpc * (Maybe I could use the process's page mapping, but I was concerned that * Kernel Write might not be enabled and also figured copyout() would do * a lot more work than bcopy() and also it currently happens in the * context of the swapper process (2). */ static int nfs_bmap(ap) struct vop_bmap_args /* { struct vnode *a_vp; daddr_t a_bn; struct vnode **a_vpp; daddr_t *a_bnp; int *a_runp; int *a_runb; } */ *ap; { register struct vnode *vp = ap->a_vp; if (ap->a_vpp != NULL) *ap->a_vpp = vp; if (ap->a_bnp != NULL) *ap->a_bnp = ap->a_bn * btodb(vp->v_mount->mnt_stat.f_iosize); if (ap->a_runp != NULL) *ap->a_runp = 0; if (ap->a_runb != NULL) *ap->a_runb = 0; return (0); } /* * Strategy routine. * For async requests when nfsiod(s) are running, queue the request by * calling nfs_asyncio(), otherwise just all nfs_doio() to do the * request. */ static int nfs_strategy(ap) struct vop_strategy_args *ap; { register struct buf *bp = ap->a_bp; struct ucred *cr; struct proc *p; int error = 0; if (bp->b_flags & B_PHYS) panic("nfs physio"); if (bp->b_flags & B_ASYNC) p = (struct proc *)0; else p = curproc; /* XXX */ if (bp->b_flags & B_READ) cr = bp->b_rcred; else cr = bp->b_wcred; /* * If the op is asynchronous and an i/o daemon is waiting * queue the request, wake it up and wait for completion * otherwise just do it ourselves. */ if ((bp->b_flags & B_ASYNC) == 0 || nfs_asyncio(bp, NOCRED)) error = nfs_doio(bp, cr, p); return (error); } /* * Mmap a file * * NB Currently unsupported. */ /* ARGSUSED */ static int nfs_mmap(ap) struct vop_mmap_args /* { struct vnode *a_vp; int a_fflags; struct ucred *a_cred; struct proc *a_p; } */ *ap; { return (EINVAL); } /* * fsync vnode op. Just call nfs_flush() with commit == 1. */ /* ARGSUSED */ static int nfs_fsync(ap) struct vop_fsync_args /* { struct vnodeop_desc *a_desc; struct vnode * a_vp; struct ucred * a_cred; int a_waitfor; struct proc * a_p; } */ *ap; { return (nfs_flush(ap->a_vp, ap->a_cred, ap->a_waitfor, ap->a_p, 1)); } /* * Flush all the blocks associated with a vnode. * Walk through the buffer pool and push any dirty pages * associated with the vnode. */ static int nfs_flush(vp, cred, waitfor, p, commit) register struct vnode *vp; struct ucred *cred; int waitfor; struct proc *p; int commit; { register struct nfsnode *np = VTONFS(vp); register struct buf *bp; register int i; struct buf *nbp; struct nfsmount *nmp = VFSTONFS(vp->v_mount); int s, error = 0, slptimeo = 0, slpflag = 0, retv, bvecpos; int passone = 1; u_quad_t off = (u_quad_t)-1, endoff = 0, toff; struct ucred* wcred = NULL; #ifndef NFS_COMMITBVECSIZ #define NFS_COMMITBVECSIZ 20 #endif struct buf *bvec[NFS_COMMITBVECSIZ]; if (nmp->nm_flag & NFSMNT_INT) slpflag = PCATCH; if (!commit) passone = 0; /* * A b_flags == (B_DELWRI | B_NEEDCOMMIT) block has been written to the * server, but nas not been committed to stable storage on the server * yet. On the first pass, the byte range is worked out and the commit * rpc is done. On the second pass, nfs_writebp() is called to do the * job. */ again: bvecpos = 0; if (NFS_ISV3(vp) && commit) { s = splbio(); for (bp = vp->v_dirtyblkhd.lh_first; bp; bp = nbp) { nbp = bp->b_vnbufs.le_next; if (bvecpos >= NFS_COMMITBVECSIZ) break; if ((bp->b_flags & (B_BUSY | B_DELWRI | B_NEEDCOMMIT)) != (B_DELWRI | B_NEEDCOMMIT)) continue; bremfree(bp); /* * Work out if all buffers are using the same cred * so we can deal with them all with one commit. */ if (wcred == NULL) wcred = bp->b_wcred; else if (wcred != bp->b_wcred) wcred = NOCRED; bp->b_flags |= (B_BUSY | B_WRITEINPROG); vfs_busy_pages(bp, 1); /* * A list of these buffers is kept so that the * second loop knows which buffers have actually * been committed. This is necessary, since there * may be a race between the commit rpc and new * uncommitted writes on the file. */ bvec[bvecpos++] = bp; toff = ((u_quad_t)bp->b_blkno) * DEV_BSIZE + bp->b_dirtyoff; if (toff < off) off = toff; toff += (u_quad_t)(bp->b_dirtyend - bp->b_dirtyoff); if (toff > endoff) endoff = toff; } splx(s); } if (bvecpos > 0) { /* * Commit data on the server, as required. * If all bufs are using the same wcred, then use that with * one call for all of them, otherwise commit each one * separately. */ if (wcred != NOCRED) retv = nfs_commit(vp, off, (int)(endoff - off), wcred, p); else { retv = 0; for (i = 0; i < bvecpos; i++) { off_t off, size; bp = bvec[i]; off = ((u_quad_t)bp->b_blkno) * DEV_BSIZE + bp->b_dirtyoff; size = (u_quad_t)(bp->b_dirtyend - bp->b_dirtyoff); retv = nfs_commit(vp, off, (int)size, bp->b_wcred, p); if (retv) break; } } if (retv == NFSERR_STALEWRITEVERF) nfs_clearcommit(vp->v_mount); /* * Now, either mark the blocks I/O done or mark the * blocks dirty, depending on whether the commit * succeeded. */ for (i = 0; i < bvecpos; i++) { bp = bvec[i]; bp->b_flags &= ~(B_NEEDCOMMIT | B_WRITEINPROG); if (retv) { vfs_unbusy_pages(bp); brelse(bp); } else { vp->v_numoutput++; bp->b_flags |= B_ASYNC; bp->b_flags &= ~(B_READ|B_DONE|B_ERROR|B_DELWRI); bp->b_dirtyoff = bp->b_dirtyend = 0; reassignbuf(bp, vp); biodone(bp); } } } /* * Start/do any write(s) that are required. */ loop: s = splbio(); for (bp = vp->v_dirtyblkhd.lh_first; bp; bp = nbp) { nbp = bp->b_vnbufs.le_next; if (bp->b_flags & B_BUSY) { if (waitfor != MNT_WAIT || passone) continue; bp->b_flags |= B_WANTED; error = tsleep((caddr_t)bp, slpflag | (PRIBIO + 1), "nfsfsync", slptimeo); splx(s); if (error) { if (nfs_sigintr(nmp, (struct nfsreq *)0, p)) return (EINTR); if (slpflag == PCATCH) { slpflag = 0; slptimeo = 2 * hz; } } goto loop; } if ((bp->b_flags & B_DELWRI) == 0) panic("nfs_fsync: not dirty"); if ((passone || !commit) && (bp->b_flags & B_NEEDCOMMIT)) continue; bremfree(bp); if (passone || !commit) bp->b_flags |= (B_BUSY|B_ASYNC); else bp->b_flags |= (B_BUSY|B_ASYNC|B_WRITEINPROG|B_NEEDCOMMIT); splx(s); VOP_BWRITE(bp); goto loop; } splx(s); if (passone) { passone = 0; goto again; } if (waitfor == MNT_WAIT) { while (vp->v_numoutput) { vp->v_flag |= VBWAIT; error = tsleep((caddr_t)&vp->v_numoutput, slpflag | (PRIBIO + 1), "nfsfsync", slptimeo); if (error) { if (nfs_sigintr(nmp, (struct nfsreq *)0, p)) return (EINTR); if (slpflag == PCATCH) { slpflag = 0; slptimeo = 2 * hz; } } } if (vp->v_dirtyblkhd.lh_first && commit) { goto loop; } } if (np->n_flag & NWRITEERR) { error = np->n_error; np->n_flag &= ~NWRITEERR; } return (error); } /* * Return POSIX pathconf information applicable to nfs. * * The NFS V2 protocol doesn't support this, so just return EINVAL * for V2. */ /* ARGSUSED */ static int nfs_pathconf(ap) struct vop_pathconf_args /* { struct vnode *a_vp; int a_name; int *a_retval; } */ *ap; { return (EINVAL); } /* * NFS advisory byte-level locks. * Currently unsupported. */ static int nfs_advlock(ap) struct vop_advlock_args /* { struct vnode *a_vp; caddr_t a_id; int a_op; struct flock *a_fl; int a_flags; } */ *ap; { #ifdef __FreeBSD__ register struct nfsnode *np = VTONFS(ap->a_vp); /* * The following kludge is to allow diskless support to work * until a real NFS lockd is implemented. Basically, just pretend * that this is a local lock. */ return (lf_advlock(ap, &(np->n_lockf), np->n_size)); #else return (EOPNOTSUPP); #endif } /* * Print out the contents of an nfsnode. */ static int nfs_print(ap) struct vop_print_args /* { struct vnode *a_vp; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); printf("tag VT_NFS, fileid %ld fsid 0x%lx", np->n_vattr.va_fileid, np->n_vattr.va_fsid); if (vp->v_type == VFIFO) fifo_printinfo(vp); printf("\n"); return (0); } /* * NFS directory offset lookup. * Currently unsupported. */ static int nfs_blkatoff(ap) struct vop_blkatoff_args /* { struct vnode *a_vp; off_t a_offset; char **a_res; struct buf **a_bpp; } */ *ap; { return (EOPNOTSUPP); } /* * NFS flat namespace allocation. * Currently unsupported. */ static int nfs_valloc(ap) struct vop_valloc_args /* { struct vnode *a_pvp; int a_mode; struct ucred *a_cred; struct vnode **a_vpp; } */ *ap; { return (EOPNOTSUPP); } /* * NFS flat namespace free. * Currently unsupported. */ static int nfs_vfree(ap) struct vop_vfree_args /* { struct vnode *a_pvp; ino_t a_ino; int a_mode; } */ *ap; { return (EOPNOTSUPP); } /* * NFS file truncation. */ static int nfs_truncate(ap) struct vop_truncate_args /* { struct vnode *a_vp; off_t a_length; int a_flags; struct ucred *a_cred; struct proc *a_p; } */ *ap; { /* Use nfs_setattr */ printf("nfs_truncate: need to implement!!"); return (EOPNOTSUPP); } /* * NFS update. */ static int nfs_update(ap) struct vop_update_args /* { struct vnode *a_vp; struct timeval *a_ta; struct timeval *a_tm; int a_waitfor; } */ *ap; { #if 0 /* Use nfs_setattr */ printf("nfs_update: need to implement!!"); #endif return (EOPNOTSUPP); } /* * Just call nfs_writebp() with the force argument set to 1. */ static int nfs_bwrite(ap) struct vop_bwrite_args /* { struct vnode *a_bp; } */ *ap; { return (nfs_writebp(ap->a_bp, 1)); } /* * This is a clone of vn_bwrite(), except that B_WRITEINPROG isn't set unless * the force flag is one and it also handles the B_NEEDCOMMIT flag. */ int nfs_writebp(bp, force) register struct buf *bp; int force; { register int oldflags = bp->b_flags, retv = 1; off_t off; if(!(bp->b_flags & B_BUSY)) panic("bwrite: buffer is not busy???"); bp->b_flags &= ~(B_READ|B_DONE|B_ERROR|B_DELWRI); if ((oldflags & (B_ASYNC|B_DELWRI)) == (B_ASYNC|B_DELWRI)) { reassignbuf(bp, bp->b_vp); } bp->b_vp->v_numoutput++; curproc->p_stats->p_ru.ru_oublock++; /* * If B_NEEDCOMMIT is set, a commit rpc may do the trick. If not * an actual write will have to be scheduled via. VOP_STRATEGY(). * If B_WRITEINPROG is already set, then push it with a write anyhow. */ if (oldflags & (B_NEEDCOMMIT | B_WRITEINPROG) == B_NEEDCOMMIT) { off = ((u_quad_t)bp->b_blkno) * DEV_BSIZE + bp->b_dirtyoff; bp->b_flags |= B_WRITEINPROG; retv = nfs_commit(bp->b_vp, off, bp->b_dirtyend-bp->b_dirtyoff, bp->b_wcred, bp->b_proc); bp->b_flags &= ~B_WRITEINPROG; if (!retv) { bp->b_dirtyoff = bp->b_dirtyend = 0; bp->b_flags &= ~B_NEEDCOMMIT; biodone(bp); } else if (retv == NFSERR_STALEWRITEVERF) nfs_clearcommit(bp->b_vp->v_mount); } if (retv) { if (force) bp->b_flags |= B_WRITEINPROG; vfs_busy_pages(bp, 1); VOP_STRATEGY(bp); } if( (oldflags & B_ASYNC) == 0) { int rtval = biowait(bp); if (oldflags & B_DELWRI) { reassignbuf(bp, bp->b_vp); } brelse(bp); return (rtval); } return (0); } /* * nfs special file access vnode op. * Essentially just get vattr and then imitate iaccess() since the device is * local to the client. */ static int nfsspec_access(ap) struct vop_access_args /* { struct vnode *a_vp; int a_mode; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vattr *vap; register gid_t *gp; register struct ucred *cred = ap->a_cred; struct vnode *vp = ap->a_vp; mode_t mode = ap->a_mode; struct vattr vattr; register int i; int error; /* * Disallow write attempts on filesystems mounted read-only; * unless the file is a socket, fifo, or a block or character * device resident on the filesystem. */ if ((mode & VWRITE) && (vp->v_mount->mnt_flag & MNT_RDONLY)) { switch (vp->v_type) { case VREG: case VDIR: case VLNK: return (EROFS); } } /* * If you're the super-user, * you always get access. */ if (cred->cr_uid == 0) return (0); vap = &vattr; error = VOP_GETATTR(ap->a_vp, vap, cred, ap->a_p); if (error) return (error); /* * Access check is based on only one of owner, group, public. * If not owner, then check group. If not a member of the * group, then check public access. */ if (cred->cr_uid != vap->va_uid) { mode >>= 3; gp = cred->cr_groups; for (i = 0; i < cred->cr_ngroups; i++, gp++) if (vap->va_gid == *gp) goto found; mode >>= 3; found: ; } error = (vap->va_mode & mode) == mode ? 0 : EACCES; return (error); } /* * Read wrapper for special devices. */ static int nfsspec_read(ap) struct vop_read_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; struct ucred *a_cred; } */ *ap; { register struct nfsnode *np = VTONFS(ap->a_vp); /* * Set access flag. */ np->n_flag |= NACC; np->n_atim.tv_sec = time.tv_sec; np->n_atim.tv_nsec = time.tv_usec * 1000; return (VOCALL(spec_vnodeop_p, VOFFSET(vop_read), ap)); } /* * Write wrapper for special devices. */ static int nfsspec_write(ap) struct vop_write_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; struct ucred *a_cred; } */ *ap; { register struct nfsnode *np = VTONFS(ap->a_vp); /* * Set update flag. */ np->n_flag |= NUPD; np->n_mtim.tv_sec = time.tv_sec; np->n_mtim.tv_nsec = time.tv_usec * 1000; return (VOCALL(spec_vnodeop_p, VOFFSET(vop_write), ap)); } /* * Close wrapper for special devices. * * Update the times on the nfsnode then do device close. */ static int nfsspec_close(ap) struct vop_close_args /* { struct vnode *a_vp; int a_fflag; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); struct vattr vattr; if (np->n_flag & (NACC | NUPD)) { np->n_flag |= NCHG; if (vp->v_usecount == 1 && (vp->v_mount->mnt_flag & MNT_RDONLY) == 0) { VATTR_NULL(&vattr); if (np->n_flag & NACC) vattr.va_atime = np->n_atim; if (np->n_flag & NUPD) vattr.va_mtime = np->n_mtim; (void)VOP_SETATTR(vp, &vattr, ap->a_cred, ap->a_p); } } return (VOCALL(spec_vnodeop_p, VOFFSET(vop_close), ap)); } /* * Read wrapper for fifos. */ static int nfsfifo_read(ap) struct vop_read_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; struct ucred *a_cred; } */ *ap; { register struct nfsnode *np = VTONFS(ap->a_vp); /* * Set access flag. */ np->n_flag |= NACC; np->n_atim.tv_sec = time.tv_sec; np->n_atim.tv_nsec = time.tv_usec * 1000; return (VOCALL(fifo_vnodeop_p, VOFFSET(vop_read), ap)); } /* * Write wrapper for fifos. */ static int nfsfifo_write(ap) struct vop_write_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; struct ucred *a_cred; } */ *ap; { register struct nfsnode *np = VTONFS(ap->a_vp); /* * Set update flag. */ np->n_flag |= NUPD; np->n_mtim.tv_sec = time.tv_sec; np->n_mtim.tv_nsec = time.tv_usec * 1000; return (VOCALL(fifo_vnodeop_p, VOFFSET(vop_write), ap)); } /* * Close wrapper for fifos. * * Update the times on the nfsnode then do fifo close. */ static int nfsfifo_close(ap) struct vop_close_args /* { struct vnode *a_vp; int a_fflag; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); struct vattr vattr; if (np->n_flag & (NACC | NUPD)) { if (np->n_flag & NACC) { np->n_atim.tv_sec = time.tv_sec; np->n_atim.tv_nsec = time.tv_usec * 1000; } if (np->n_flag & NUPD) { np->n_mtim.tv_sec = time.tv_sec; np->n_mtim.tv_nsec = time.tv_usec * 1000; } np->n_flag |= NCHG; if (vp->v_usecount == 1 && (vp->v_mount->mnt_flag & MNT_RDONLY) == 0) { VATTR_NULL(&vattr); if (np->n_flag & NACC) vattr.va_atime = np->n_atim; if (np->n_flag & NUPD) vattr.va_mtime = np->n_mtim; (void)VOP_SETATTR(vp, &vattr, ap->a_cred, ap->a_p); } } return (VOCALL(fifo_vnodeop_p, VOFFSET(vop_close), ap)); } static int nfs_ioctl(ap) struct vop_ioctl_args *ap; { /* * XXX we were once bogusly enoictl() which returned this (ENOTTY). * Probably we should return ENODEV. */ return (ENOTTY); } static int nfs_select(ap) struct vop_select_args *ap; { /* * We were once bogusly seltrue() which returns 1. Is this right? */ return (1); } diff --git a/sys/nfsclient/nfs_vnops.c b/sys/nfsclient/nfs_vnops.c index 8a6bb0dfcf61..4405179f07c6 100644 --- a/sys/nfsclient/nfs_vnops.c +++ b/sys/nfsclient/nfs_vnops.c @@ -1,3416 +1,3416 @@ /* * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Rick Macklem at The University of Guelph. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)nfs_vnops.c 8.5 (Berkeley) 2/13/94 - * $Id: nfs_vnops.c,v 1.36 1996/10/21 10:07:52 dfr Exp $ + * $Id: nfs_vnops.c,v 1.37 1996/11/06 10:53:12 dfr Exp $ */ /* * vnode op calls for Sun NFS version 2 and 3 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Defs */ #define TRUE 1 #define FALSE 0 /* * Ifdef for FreeBSD-current merged buffer cache. It is unfortunate that these * calls are not in getblk() and brelse() so that they would not be necessary * here. */ #ifndef B_VMIO #define vfs_busy_pages(bp, f) #endif static int nfsspec_read __P((struct vop_read_args *)); static int nfsspec_write __P((struct vop_write_args *)); static int nfsfifo_read __P((struct vop_read_args *)); static int nfsfifo_write __P((struct vop_write_args *)); static int nfsspec_close __P((struct vop_close_args *)); static int nfsfifo_close __P((struct vop_close_args *)); static int nfs_ioctl __P((struct vop_ioctl_args *)); static int nfs_select __P((struct vop_select_args *)); static int nfs_flush __P((struct vnode *,struct ucred *,int,struct proc *,int)); static int nfs_setattrrpc __P((struct vnode *,struct vattr *,struct ucred *,struct proc *)); static int nfs_lookup __P((struct vop_lookup_args *)); static int nfs_create __P((struct vop_create_args *)); static int nfs_mknod __P((struct vop_mknod_args *)); static int nfs_open __P((struct vop_open_args *)); static int nfs_close __P((struct vop_close_args *)); static int nfs_access __P((struct vop_access_args *)); static int nfs_getattr __P((struct vop_getattr_args *)); static int nfs_setattr __P((struct vop_setattr_args *)); static int nfs_read __P((struct vop_read_args *)); static int nfs_mmap __P((struct vop_mmap_args *)); static int nfs_fsync __P((struct vop_fsync_args *)); static int nfs_remove __P((struct vop_remove_args *)); static int nfs_link __P((struct vop_link_args *)); static int nfs_rename __P((struct vop_rename_args *)); static int nfs_mkdir __P((struct vop_mkdir_args *)); static int nfs_rmdir __P((struct vop_rmdir_args *)); static int nfs_symlink __P((struct vop_symlink_args *)); static int nfs_readdir __P((struct vop_readdir_args *)); static int nfs_bmap __P((struct vop_bmap_args *)); static int nfs_strategy __P((struct vop_strategy_args *)); static int nfs_lookitup __P((struct vnode *,char *,int,struct ucred *,struct proc *,struct nfsnode **)); static int nfs_sillyrename __P((struct vnode *,struct vnode *,struct componentname *)); static int nfsspec_access __P((struct vop_access_args *)); static int nfs_readlink __P((struct vop_readlink_args *)); static int nfs_print __P((struct vop_print_args *)); static int nfs_pathconf __P((struct vop_pathconf_args *)); static int nfs_advlock __P((struct vop_advlock_args *)); static int nfs_blkatoff __P((struct vop_blkatoff_args *)); static int nfs_bwrite __P((struct vop_bwrite_args *)); static int nfs_valloc __P((struct vop_valloc_args *)); static int nfs_vfree __P((struct vop_vfree_args *)); static int nfs_truncate __P((struct vop_truncate_args *)); static int nfs_update __P((struct vop_update_args *)); /* * Global vfs data structures for nfs */ vop_t **nfsv2_vnodeop_p; static struct vnodeopv_entry_desc nfsv2_vnodeop_entries[] = { { &vop_default_desc, (vop_t *)vn_default_error }, { &vop_lookup_desc, (vop_t *)nfs_lookup }, /* lookup */ { &vop_create_desc, (vop_t *)nfs_create }, /* create */ { &vop_mknod_desc, (vop_t *)nfs_mknod }, /* mknod */ { &vop_open_desc, (vop_t *)nfs_open }, /* open */ { &vop_close_desc, (vop_t *)nfs_close }, /* close */ { &vop_access_desc, (vop_t *)nfs_access }, /* access */ { &vop_getattr_desc, (vop_t *)nfs_getattr }, /* getattr */ { &vop_setattr_desc, (vop_t *)nfs_setattr }, /* setattr */ { &vop_read_desc, (vop_t *)nfs_read }, /* read */ { &vop_write_desc, (vop_t *)nfs_write }, /* write */ #ifdef HAS_VOPLEASE { &vop_lease_desc, (vop_t *)nfs_lease_check }, /* lease */ #endif { &vop_ioctl_desc, (vop_t *)nfs_ioctl }, /* ioctl */ { &vop_select_desc, (vop_t *)nfs_select }, /* select */ #ifdef HAS_VOPREVOKE { &vop_revoke_desc, (vop_t *)nfs_revoke }, /* revoke */ #endif { &vop_mmap_desc, (vop_t *)nfs_mmap }, /* mmap */ { &vop_fsync_desc, (vop_t *)nfs_fsync }, /* fsync */ { &vop_seek_desc, (vop_t *)nfs_seek }, /* seek */ { &vop_remove_desc, (vop_t *)nfs_remove }, /* remove */ { &vop_link_desc, (vop_t *)nfs_link }, /* link */ { &vop_rename_desc, (vop_t *)nfs_rename }, /* rename */ { &vop_mkdir_desc, (vop_t *)nfs_mkdir }, /* mkdir */ { &vop_rmdir_desc, (vop_t *)nfs_rmdir }, /* rmdir */ { &vop_symlink_desc, (vop_t *)nfs_symlink }, /* symlink */ { &vop_readdir_desc, (vop_t *)nfs_readdir }, /* readdir */ { &vop_readlink_desc, (vop_t *)nfs_readlink }, /* readlink */ { &vop_abortop_desc, (vop_t *)nfs_abortop }, /* abortop */ { &vop_inactive_desc, (vop_t *)nfs_inactive }, /* inactive */ { &vop_reclaim_desc, (vop_t *)nfs_reclaim }, /* reclaim */ { &vop_lock_desc, (vop_t *)nfs_lock }, /* lock */ { &vop_unlock_desc, (vop_t *)nfs_unlock }, /* unlock */ { &vop_bmap_desc, (vop_t *)nfs_bmap }, /* bmap */ { &vop_strategy_desc, (vop_t *)nfs_strategy }, /* strategy */ { &vop_print_desc, (vop_t *)nfs_print }, /* print */ { &vop_islocked_desc, (vop_t *)nfs_islocked }, /* islocked */ { &vop_pathconf_desc, (vop_t *)nfs_pathconf }, /* pathconf */ { &vop_advlock_desc, (vop_t *)nfs_advlock }, /* advlock */ { &vop_blkatoff_desc, (vop_t *)nfs_blkatoff }, /* blkatoff */ { &vop_valloc_desc, (vop_t *)nfs_valloc }, /* valloc */ { &vop_reallocblks_desc, (vop_t *)nfs_reallocblks }, /* reallocblks */ { &vop_vfree_desc, (vop_t *)nfs_vfree }, /* vfree */ { &vop_truncate_desc, (vop_t *)nfs_truncate }, /* truncate */ { &vop_update_desc, (vop_t *)nfs_update }, /* update */ { &vop_bwrite_desc, (vop_t *)nfs_bwrite }, /* bwrite */ { NULL, NULL } }; static struct vnodeopv_desc nfsv2_vnodeop_opv_desc = { &nfsv2_vnodeop_p, nfsv2_vnodeop_entries }; #ifdef __FreeBSD__ VNODEOP_SET(nfsv2_vnodeop_opv_desc); #endif /* * Special device vnode ops */ vop_t **spec_nfsv2nodeop_p; static struct vnodeopv_entry_desc spec_nfsv2nodeop_entries[] = { { &vop_default_desc, (vop_t *)vn_default_error }, { &vop_lookup_desc, (vop_t *)spec_lookup }, /* lookup */ { &vop_create_desc, (vop_t *)spec_create }, /* create */ { &vop_mknod_desc, (vop_t *)spec_mknod }, /* mknod */ { &vop_open_desc, (vop_t *)spec_open }, /* open */ { &vop_close_desc, (vop_t *)nfsspec_close }, /* close */ { &vop_access_desc, (vop_t *)nfsspec_access }, /* access */ { &vop_getattr_desc, (vop_t *)nfs_getattr }, /* getattr */ { &vop_setattr_desc, (vop_t *)nfs_setattr }, /* setattr */ { &vop_read_desc, (vop_t *)nfsspec_read }, /* read */ { &vop_write_desc, (vop_t *)nfsspec_write }, /* write */ #ifdef HAS_VOPLEASE { &vop_lease_desc, (vop_t *)spec_lease_check }, /* lease */ #endif { &vop_ioctl_desc, (vop_t *)spec_ioctl }, /* ioctl */ { &vop_select_desc, (vop_t *)spec_select }, /* select */ #ifdef HAS_VOPREVOKE { &vop_revoke_desc, (vop_t *)spec_revoke }, /* revoke */ #endif { &vop_mmap_desc, (vop_t *)spec_mmap }, /* mmap */ { &vop_fsync_desc, (vop_t *)nfs_fsync }, /* fsync */ { &vop_seek_desc, (vop_t *)spec_seek }, /* seek */ { &vop_remove_desc, (vop_t *)spec_remove }, /* remove */ { &vop_link_desc, (vop_t *)spec_link }, /* link */ { &vop_rename_desc, (vop_t *)spec_rename }, /* rename */ { &vop_mkdir_desc, (vop_t *)spec_mkdir }, /* mkdir */ { &vop_rmdir_desc, (vop_t *)spec_rmdir }, /* rmdir */ { &vop_symlink_desc, (vop_t *)spec_symlink }, /* symlink */ { &vop_readdir_desc, (vop_t *)spec_readdir }, /* readdir */ { &vop_readlink_desc, (vop_t *)spec_readlink }, /* readlink */ { &vop_abortop_desc, (vop_t *)spec_abortop }, /* abortop */ { &vop_inactive_desc, (vop_t *)nfs_inactive }, /* inactive */ { &vop_reclaim_desc, (vop_t *)nfs_reclaim }, /* reclaim */ { &vop_lock_desc, (vop_t *)nfs_lock }, /* lock */ { &vop_unlock_desc, (vop_t *)nfs_unlock }, /* unlock */ { &vop_bmap_desc, (vop_t *)spec_bmap }, /* bmap */ { &vop_strategy_desc, (vop_t *)spec_strategy }, /* strategy */ { &vop_print_desc, (vop_t *)nfs_print }, /* print */ { &vop_islocked_desc, (vop_t *)nfs_islocked }, /* islocked */ { &vop_pathconf_desc, (vop_t *)spec_pathconf }, /* pathconf */ { &vop_advlock_desc, (vop_t *)spec_advlock }, /* advlock */ { &vop_blkatoff_desc, (vop_t *)spec_blkatoff }, /* blkatoff */ { &vop_valloc_desc, (vop_t *)spec_valloc }, /* valloc */ { &vop_reallocblks_desc, (vop_t *)spec_reallocblks }, /* reallocblks */ { &vop_vfree_desc, (vop_t *)spec_vfree }, /* vfree */ { &vop_truncate_desc, (vop_t *)spec_truncate }, /* truncate */ { &vop_update_desc, (vop_t *)nfs_update }, /* update */ { &vop_bwrite_desc, (vop_t *)vn_bwrite }, /* bwrite */ { NULL, NULL } }; static struct vnodeopv_desc spec_nfsv2nodeop_opv_desc = { &spec_nfsv2nodeop_p, spec_nfsv2nodeop_entries }; #ifdef __FreeBSD__ VNODEOP_SET(spec_nfsv2nodeop_opv_desc); #endif vop_t **fifo_nfsv2nodeop_p; static struct vnodeopv_entry_desc fifo_nfsv2nodeop_entries[] = { { &vop_default_desc, (vop_t *)vn_default_error }, { &vop_lookup_desc, (vop_t *)fifo_lookup }, /* lookup */ { &vop_create_desc, (vop_t *)fifo_create }, /* create */ { &vop_mknod_desc, (vop_t *)fifo_mknod }, /* mknod */ { &vop_open_desc, (vop_t *)fifo_open }, /* open */ { &vop_close_desc, (vop_t *)nfsfifo_close }, /* close */ { &vop_access_desc, (vop_t *)nfsspec_access }, /* access */ { &vop_getattr_desc, (vop_t *)nfs_getattr }, /* getattr */ { &vop_setattr_desc, (vop_t *)nfs_setattr }, /* setattr */ { &vop_read_desc, (vop_t *)nfsfifo_read }, /* read */ { &vop_write_desc, (vop_t *)nfsfifo_write }, /* write */ #ifdef HAS_VOPLEASE { &vop_lease_desc, (vop_t *)fifo_lease_check }, /* lease */ #endif { &vop_ioctl_desc, (vop_t *)fifo_ioctl }, /* ioctl */ { &vop_select_desc, (vop_t *)fifo_select }, /* select */ #ifdef HAS_VOPREVOKE { &vop_revoke_desc, (vop_t *)fifo_revoke }, /* revoke */ #endif { &vop_mmap_desc, (vop_t *)fifo_mmap }, /* mmap */ { &vop_fsync_desc, (vop_t *)nfs_fsync }, /* fsync */ { &vop_seek_desc, (vop_t *)fifo_seek }, /* seek */ { &vop_remove_desc, (vop_t *)fifo_remove }, /* remove */ { &vop_link_desc, (vop_t *)fifo_link }, /* link */ { &vop_rename_desc, (vop_t *)fifo_rename }, /* rename */ { &vop_mkdir_desc, (vop_t *)fifo_mkdir }, /* mkdir */ { &vop_rmdir_desc, (vop_t *)fifo_rmdir }, /* rmdir */ { &vop_symlink_desc, (vop_t *)fifo_symlink }, /* symlink */ { &vop_readdir_desc, (vop_t *)fifo_readdir }, /* readdir */ { &vop_readlink_desc, (vop_t *)fifo_readlink }, /* readlink */ { &vop_abortop_desc, (vop_t *)fifo_abortop }, /* abortop */ { &vop_inactive_desc, (vop_t *)nfs_inactive }, /* inactive */ { &vop_reclaim_desc, (vop_t *)nfs_reclaim }, /* reclaim */ { &vop_lock_desc, (vop_t *)nfs_lock }, /* lock */ { &vop_unlock_desc, (vop_t *)nfs_unlock }, /* unlock */ { &vop_bmap_desc, (vop_t *)fifo_bmap }, /* bmap */ { &vop_strategy_desc, (vop_t *)fifo_badop }, /* strategy */ { &vop_print_desc, (vop_t *)nfs_print }, /* print */ { &vop_islocked_desc, (vop_t *)nfs_islocked }, /* islocked */ { &vop_pathconf_desc, (vop_t *)fifo_pathconf }, /* pathconf */ { &vop_advlock_desc, (vop_t *)fifo_advlock }, /* advlock */ { &vop_blkatoff_desc, (vop_t *)fifo_blkatoff }, /* blkatoff */ { &vop_valloc_desc, (vop_t *)fifo_valloc }, /* valloc */ { &vop_reallocblks_desc, (vop_t *)fifo_reallocblks }, /* reallocblks */ { &vop_vfree_desc, (vop_t *)fifo_vfree }, /* vfree */ { &vop_truncate_desc, (vop_t *)fifo_truncate }, /* truncate */ { &vop_update_desc, (vop_t *)nfs_update }, /* update */ { &vop_bwrite_desc, (vop_t *)vn_bwrite }, /* bwrite */ { NULL, NULL } }; static struct vnodeopv_desc fifo_nfsv2nodeop_opv_desc = { &fifo_nfsv2nodeop_p, fifo_nfsv2nodeop_entries }; #ifdef __FreeBSD__ VNODEOP_SET(fifo_nfsv2nodeop_opv_desc); #endif static int nfs_commit __P((struct vnode *vp, u_quad_t offset, int cnt, struct ucred *cred, struct proc *procp)); static int nfs_mknodrpc __P((struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, struct vattr *vap)); static int nfs_removerpc __P((struct vnode *dvp, char *name, int namelen, struct ucred *cred, struct proc *proc)); static int nfs_renamerpc __P((struct vnode *fdvp, char *fnameptr, int fnamelen, struct vnode *tdvp, char *tnameptr, int tnamelen, struct ucred *cred, struct proc *proc)); static int nfs_renameit __P((struct vnode *sdvp, struct componentname *scnp, struct sillyrename *sp)); /* * Global variables */ extern u_long nfs_true, nfs_false; extern struct nfsstats nfsstats; extern nfstype nfsv3_type[9]; struct proc *nfs_iodwant[NFS_MAXASYNCDAEMON]; struct nfsmount *nfs_iodmount[NFS_MAXASYNCDAEMON]; int nfs_numasync = 0; #define DIRHDSIZ (sizeof (struct dirent) - (MAXNAMLEN + 1)) /* * nfs access vnode op. * For nfs version 2, just return ok. File accesses may fail later. * For nfs version 3, use the access rpc to check accessibility. If file modes * are changed on the server, accesses might still fail later. */ static int nfs_access(ap) struct vop_access_args /* { struct vnode *a_vp; int a_mode; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register u_long *tl; register caddr_t cp; register int t1, t2; caddr_t bpos, dpos, cp2; int error = 0, attrflag; struct mbuf *mreq, *mrep, *md, *mb, *mb2; u_long mode, rmode; int v3 = NFS_ISV3(vp); /* * Disallow write attempts on filesystems mounted read-only; * unless the file is a socket, fifo, or a block or character * device resident on the filesystem. */ if ((ap->a_mode & VWRITE) && (vp->v_mount->mnt_flag & MNT_RDONLY)) { switch (vp->v_type) { case VREG: case VDIR: case VLNK: return (EROFS); } } /* * For nfs v3, do an access rpc, otherwise you are stuck emulating * ufs_access() locally using the vattr. This may not be correct, * since the server may apply other access criteria such as * client uid-->server uid mapping that we do not know about, but * this is better than just returning anything that is lying about * in the cache. */ if (v3) { nfsstats.rpccnt[NFSPROC_ACCESS]++; nfsm_reqhead(vp, NFSPROC_ACCESS, NFSX_FH(v3) + NFSX_UNSIGNED); nfsm_fhtom(vp, v3); nfsm_build(tl, u_long *, NFSX_UNSIGNED); if (ap->a_mode & VREAD) mode = NFSV3ACCESS_READ; else mode = 0; if (vp->v_type == VDIR) { if (ap->a_mode & VWRITE) mode |= (NFSV3ACCESS_MODIFY | NFSV3ACCESS_EXTEND | NFSV3ACCESS_DELETE); if (ap->a_mode & VEXEC) mode |= NFSV3ACCESS_LOOKUP; } else { if (ap->a_mode & VWRITE) mode |= (NFSV3ACCESS_MODIFY | NFSV3ACCESS_EXTEND); if (ap->a_mode & VEXEC) mode |= NFSV3ACCESS_EXECUTE; } *tl = txdr_unsigned(mode); nfsm_request(vp, NFSPROC_ACCESS, ap->a_p, ap->a_cred); nfsm_postop_attr(vp, attrflag); if (!error) { nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); rmode = fxdr_unsigned(u_long, *tl); /* * The NFS V3 spec does not clarify whether or not * the returned access bits can be a superset of * the ones requested, so... */ if ((rmode & mode) != mode) error = EACCES; } nfsm_reqdone; return (error); } else return (nfsspec_access(ap)); } /* * nfs open vnode op * Check to see if the type is ok * and that deletion is not in progress. * For paged in text files, you will need to flush the page cache * if consistency is lost. */ /* ARGSUSED */ static int nfs_open(ap) struct vop_open_args /* { struct vnode *a_vp; int a_mode; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; struct nfsnode *np = VTONFS(vp); struct nfsmount *nmp = VFSTONFS(vp->v_mount); struct vattr vattr; int error; if (vp->v_type != VREG && vp->v_type != VDIR && vp->v_type != VLNK) { printf("open eacces vtyp=%d\n",vp->v_type); return (EACCES); } /* * Get a valid lease. If cached data is stale, flush it. */ if (nmp->nm_flag & NFSMNT_NQNFS) { if (NQNFS_CKINVALID(vp, np, ND_READ)) { do { error = nqnfs_getlease(vp, ND_READ, ap->a_cred, ap->a_p); } while (error == NQNFS_EXPIRED); if (error) return (error); if (np->n_lrev != np->n_brev || (np->n_flag & NQNFSNONCACHE)) { if ((error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1)) == EINTR) return (error); (void) vnode_pager_uncache(vp); np->n_brev = np->n_lrev; } } } else { if (np->n_flag & NMODIFIED) { if ((error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1)) == EINTR) return (error); np->n_attrstamp = 0; if (vp->v_type == VDIR) np->n_direofoffset = 0; error = VOP_GETATTR(vp, &vattr, ap->a_cred, ap->a_p); if (error) return (error); np->n_mtime = vattr.va_mtime.tv_sec; } else { error = VOP_GETATTR(vp, &vattr, ap->a_cred, ap->a_p); if (error) return (error); if (np->n_mtime != vattr.va_mtime.tv_sec) { if (vp->v_type == VDIR) np->n_direofoffset = 0; if ((error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1)) == EINTR) return (error); np->n_mtime = vattr.va_mtime.tv_sec; } } } if ((nmp->nm_flag & NFSMNT_NQNFS) == 0) np->n_attrstamp = 0; /* For Open/Close consistency */ return (0); } /* * nfs close vnode op * What an NFS client should do upon close after writing is a debatable issue. * Most NFS clients push delayed writes to the server upon close, basically for * two reasons: * 1 - So that any write errors may be reported back to the client process * doing the close system call. By far the two most likely errors are * NFSERR_NOSPC and NFSERR_DQUOT to indicate space allocation failure. * 2 - To put a worst case upper bound on cache inconsistency between * multiple clients for the file. * There is also a consistency problem for Version 2 of the protocol w.r.t. * not being able to tell if other clients are writing a file concurrently, * since there is no way of knowing if the changed modify time in the reply * is only due to the write for this client. * (NFS Version 3 provides weak cache consistency data in the reply that * should be sufficient to detect and handle this case.) * * The current code does the following: * for NFS Version 2 - play it safe and flush/invalidate all dirty buffers * for NFS Version 3 - flush dirty buffers to the server but don't invalidate * or commit them (this satisfies 1 and 2 except for the * case where the server crashes after this close but * before the commit RPC, which is felt to be "good * enough". Changing the last argument to nfs_flush() to * a 1 would force a commit operation, if it is felt a * commit is necessary now. * for NQNFS - do nothing now, since 2 is dealt with via leases and * 1 should be dealt with via an fsync() system call for * cases where write errors are important. */ /* ARGSUSED */ static int nfs_close(ap) struct vop_close_args /* { struct vnodeop_desc *a_desc; struct vnode *a_vp; int a_fflag; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); int error = 0; if (vp->v_type == VREG) { if ((VFSTONFS(vp->v_mount)->nm_flag & NFSMNT_NQNFS) == 0 && (np->n_flag & NMODIFIED)) { if (NFS_ISV3(vp)) { error = nfs_flush(vp, ap->a_cred, MNT_WAIT, ap->a_p, 0); np->n_flag &= ~NMODIFIED; } else error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1); np->n_attrstamp = 0; } if (np->n_flag & NWRITEERR) { np->n_flag &= ~NWRITEERR; error = np->n_error; } } return (error); } /* * nfs getattr call from vfs. */ static int nfs_getattr(ap) struct vop_getattr_args /* { struct vnode *a_vp; struct vattr *a_vap; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); register caddr_t cp; register u_long *tl; register int t1, t2; caddr_t bpos, dpos; int error = 0; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(vp); /* * Update local times for special files. */ if (np->n_flag & (NACC | NUPD)) np->n_flag |= NCHG; /* * First look in the cache. */ if (nfs_getattrcache(vp, ap->a_vap) == 0) return (0); nfsstats.rpccnt[NFSPROC_GETATTR]++; nfsm_reqhead(vp, NFSPROC_GETATTR, NFSX_FH(v3)); nfsm_fhtom(vp, v3); nfsm_request(vp, NFSPROC_GETATTR, ap->a_p, ap->a_cred); if (!error) nfsm_loadattr(vp, ap->a_vap); nfsm_reqdone; return (error); } /* * nfs setattr call. */ static int nfs_setattr(ap) struct vop_setattr_args /* { struct vnodeop_desc *a_desc; struct vnode *a_vp; struct vattr *a_vap; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); register struct vattr *vap = ap->a_vap; int error = 0; u_quad_t tsize; #ifndef nolint tsize = (u_quad_t)0; #endif /* * Disallow write attempts if the filesystem is mounted read-only. */ if ((vap->va_flags != VNOVAL || vap->va_uid != (uid_t)VNOVAL || vap->va_gid != (gid_t)VNOVAL || vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL || vap->va_mode != (mode_t)VNOVAL) && (vp->v_mount->mnt_flag & MNT_RDONLY)) return (EROFS); if (vap->va_size != VNOVAL) { switch (vp->v_type) { case VDIR: return (EISDIR); case VCHR: case VBLK: if (vap->va_mtime.tv_sec == VNOVAL && vap->va_atime.tv_sec == VNOVAL && vap->va_mode == (u_short)VNOVAL && vap->va_uid == (uid_t)VNOVAL && vap->va_gid == (gid_t)VNOVAL) return (0); vap->va_size = VNOVAL; break; default: /* * Disallow write attempts if the filesystem is * mounted read-only. */ if (vp->v_mount->mnt_flag & MNT_RDONLY) return (EROFS); if (vap->va_size == 0) error = nfs_vinvalbuf(vp, 0, ap->a_cred, ap->a_p, 1); else error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1); if (error) return (error); tsize = np->n_size; np->n_size = np->n_vattr.va_size = vap->va_size; vnode_pager_setsize(vp, (u_long)np->n_size); }; } else if ((vap->va_mtime.tv_sec != VNOVAL || vap->va_atime.tv_sec != VNOVAL) && vp->v_type == VREG && (error = nfs_vinvalbuf(vp, V_SAVE, ap->a_cred, ap->a_p, 1)) == EINTR) return (error); error = nfs_setattrrpc(vp, vap, ap->a_cred, ap->a_p); if (error && vap->va_size != VNOVAL) { np->n_size = np->n_vattr.va_size = tsize; vnode_pager_setsize(vp, (u_long)np->n_size); } return (error); } /* * Do an nfs setattr rpc. */ static int nfs_setattrrpc(vp, vap, cred, procp) register struct vnode *vp; register struct vattr *vap; struct ucred *cred; struct proc *procp; { register struct nfsv2_sattr *sp; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; u_long *tl; int error = 0, wccflag = NFSV3_WCCRATTR; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(vp); nfsstats.rpccnt[NFSPROC_SETATTR]++; nfsm_reqhead(vp, NFSPROC_SETATTR, NFSX_FH(v3) + NFSX_SATTR(v3)); nfsm_fhtom(vp, v3); if (v3) { if (vap->va_mode != (u_short)VNOVAL) { nfsm_build(tl, u_long *, 2 * NFSX_UNSIGNED); *tl++ = nfs_true; *tl = txdr_unsigned(vap->va_mode); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = nfs_false; } if (vap->va_uid != (uid_t)VNOVAL) { nfsm_build(tl, u_long *, 2 * NFSX_UNSIGNED); *tl++ = nfs_true; *tl = txdr_unsigned(vap->va_uid); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = nfs_false; } if (vap->va_gid != (gid_t)VNOVAL) { nfsm_build(tl, u_long *, 2 * NFSX_UNSIGNED); *tl++ = nfs_true; *tl = txdr_unsigned(vap->va_gid); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = nfs_false; } if (vap->va_size != VNOVAL) { nfsm_build(tl, u_long *, 3 * NFSX_UNSIGNED); *tl++ = nfs_true; txdr_hyper(&vap->va_size, tl); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = nfs_false; } if (vap->va_atime.tv_sec != VNOVAL) { if (vap->va_atime.tv_sec != time.tv_sec) { nfsm_build(tl, u_long *, 3 * NFSX_UNSIGNED); *tl++ = txdr_unsigned(NFSV3SATTRTIME_TOCLIENT); txdr_nfsv3time(&vap->va_atime, tl); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = txdr_unsigned(NFSV3SATTRTIME_TOSERVER); } } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = txdr_unsigned(NFSV3SATTRTIME_DONTCHANGE); } if (vap->va_mtime.tv_sec != VNOVAL) { if (vap->va_mtime.tv_sec != time.tv_sec) { nfsm_build(tl, u_long *, 3 * NFSX_UNSIGNED); *tl++ = txdr_unsigned(NFSV3SATTRTIME_TOCLIENT); txdr_nfsv3time(&vap->va_mtime, tl); } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = txdr_unsigned(NFSV3SATTRTIME_TOSERVER); } } else { nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = txdr_unsigned(NFSV3SATTRTIME_DONTCHANGE); } nfsm_build(tl, u_long *, NFSX_UNSIGNED); *tl = nfs_false; } else { nfsm_build(sp, struct nfsv2_sattr *, NFSX_V2SATTR); if (vap->va_mode == (u_short)VNOVAL) sp->sa_mode = VNOVAL; else sp->sa_mode = vtonfsv2_mode(vp->v_type, vap->va_mode); if (vap->va_uid == (uid_t)VNOVAL) sp->sa_uid = VNOVAL; else sp->sa_uid = txdr_unsigned(vap->va_uid); if (vap->va_gid == (gid_t)VNOVAL) sp->sa_gid = VNOVAL; else sp->sa_gid = txdr_unsigned(vap->va_gid); sp->sa_size = txdr_unsigned(vap->va_size); txdr_nfsv2time(&vap->va_atime, &sp->sa_atime); txdr_nfsv2time(&vap->va_mtime, &sp->sa_mtime); } nfsm_request(vp, NFSPROC_SETATTR, procp, cred); if (v3) { nfsm_wcc_data(vp, wccflag); } else nfsm_loadattr(vp, (struct vattr *)0); nfsm_reqdone; return (error); } /* * nfs lookup call, one step at a time... * First look in cache * If not found, unlock the directory nfsnode and do the rpc */ static int nfs_lookup(ap) struct vop_lookup_args /* { struct vnodeop_desc *a_desc; struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; } */ *ap; { register struct componentname *cnp = ap->a_cnp; register struct vnode *dvp = ap->a_dvp; register struct vnode **vpp = ap->a_vpp; register int flags = cnp->cn_flags; register struct vnode *newvp; register u_long *tl; register caddr_t cp; register long t1, t2; struct nfsmount *nmp; caddr_t bpos, dpos, cp2; struct mbuf *mreq, *mrep, *md, *mb, *mb2; long len; nfsfh_t *fhp; struct nfsnode *np; int lockparent, wantparent, error = 0, attrflag, fhsize; int v3 = NFS_ISV3(dvp); if ((flags & ISLASTCN) && (dvp->v_mount->mnt_flag & MNT_RDONLY) && (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) return (EROFS); *vpp = NULLVP; if (dvp->v_type != VDIR) return (ENOTDIR); lockparent = flags & LOCKPARENT; wantparent = flags & (LOCKPARENT|WANTPARENT); nmp = VFSTONFS(dvp->v_mount); np = VTONFS(dvp); if ((error = cache_lookup(dvp, vpp, cnp)) && error != ENOENT) { struct vattr vattr; int vpid; newvp = *vpp; vpid = newvp->v_id; /* * See the comment starting `Step through' in ufs/ufs_lookup.c * for an explanation of the locking protocol */ if (dvp == newvp) { VREF(newvp); error = 0; } else if (flags & ISDOTDOT) { VOP_UNLOCK(dvp); error = vget(newvp, 1); if (!error && lockparent && (flags & ISLASTCN)) error = VOP_LOCK(dvp); } else { error = vget(newvp, 1); if (!lockparent || error || !(flags & ISLASTCN)) VOP_UNLOCK(dvp); } if (!error) { if (vpid == newvp->v_id) { if (!VOP_GETATTR(newvp, &vattr, cnp->cn_cred, cnp->cn_proc) && vattr.va_ctime.tv_sec == VTONFS(newvp)->n_ctime) { nfsstats.lookupcache_hits++; if (cnp->cn_nameiop != LOOKUP && (flags & ISLASTCN)) cnp->cn_flags |= SAVENAME; return (0); } cache_purge(newvp); } vput(newvp); if (lockparent && dvp != newvp && (flags & ISLASTCN)) VOP_UNLOCK(dvp); } error = VOP_LOCK(dvp); if (error) return (error); *vpp = NULLVP; } error = 0; newvp = NULLVP; nfsstats.lookupcache_misses++; nfsstats.rpccnt[NFSPROC_LOOKUP]++; len = cnp->cn_namelen; nfsm_reqhead(dvp, NFSPROC_LOOKUP, NFSX_FH(v3) + NFSX_UNSIGNED + nfsm_rndup(len)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, len, NFS_MAXNAMLEN); nfsm_request(dvp, NFSPROC_LOOKUP, cnp->cn_proc, cnp->cn_cred); if (error) { nfsm_postop_attr(dvp, attrflag); m_freem(mrep); goto nfsmout; } nfsm_getfh(fhp, fhsize, v3); /* * Handle RENAME case... */ if (cnp->cn_nameiop == RENAME && wantparent && (flags & ISLASTCN)) { if (NFS_CMPFH(np, fhp, fhsize)) { m_freem(mrep); return (EISDIR); } if (error = nfs_nget(dvp->v_mount, fhp, fhsize, &np)) { m_freem(mrep); return (error); } newvp = NFSTOV(np); if (v3) { nfsm_postop_attr(newvp, attrflag); nfsm_postop_attr(dvp, attrflag); } else nfsm_loadattr(newvp, (struct vattr *)0); *vpp = newvp; m_freem(mrep); cnp->cn_flags |= SAVENAME; if (!lockparent) VOP_UNLOCK(dvp); return (0); } if (flags & ISDOTDOT) { VOP_UNLOCK(dvp); error = nfs_nget(dvp->v_mount, fhp, fhsize, &np); if (error) { VOP_LOCK(dvp); return (error); } newvp = NFSTOV(np); if (lockparent && (flags & ISLASTCN) && (error = VOP_LOCK(dvp))) { vput(newvp); return (error); } } else if (NFS_CMPFH(np, fhp, fhsize)) { VREF(dvp); newvp = dvp; } else { if (error = nfs_nget(dvp->v_mount, fhp, fhsize, &np)) { m_freem(mrep); return (error); } if (!lockparent || !(flags & ISLASTCN)) VOP_UNLOCK(dvp); newvp = NFSTOV(np); } if (v3) { nfsm_postop_attr(newvp, attrflag); nfsm_postop_attr(dvp, attrflag); } else nfsm_loadattr(newvp, (struct vattr *)0); if (cnp->cn_nameiop != LOOKUP && (flags & ISLASTCN)) cnp->cn_flags |= SAVENAME; if ((cnp->cn_flags & MAKEENTRY) && (cnp->cn_nameiop != DELETE || !(flags & ISLASTCN))) { np->n_ctime = np->n_vattr.va_ctime.tv_sec; cache_enter(dvp, newvp, cnp); } *vpp = newvp; nfsm_reqdone; if (error) { if (newvp != NULLVP) vrele(newvp); if ((cnp->cn_nameiop == CREATE || cnp->cn_nameiop == RENAME) && (flags & ISLASTCN) && error == ENOENT) { if (!lockparent) VOP_UNLOCK(dvp); if (dvp->v_mount->mnt_flag & MNT_RDONLY) error = EROFS; else error = EJUSTRETURN; } if (cnp->cn_nameiop != LOOKUP && (flags & ISLASTCN)) cnp->cn_flags |= SAVENAME; } return (error); } /* * nfs read call. * Just call nfs_bioread() to do the work. */ static int nfs_read(ap) struct vop_read_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; struct ucred *a_cred; } */ *ap; { register struct vnode *vp = ap->a_vp; if (vp->v_type != VREG) return (EPERM); return (nfs_bioread(vp, ap->a_uio, ap->a_ioflag, ap->a_cred)); } /* * nfs readlink call */ static int nfs_readlink(ap) struct vop_readlink_args /* { struct vnode *a_vp; struct uio *a_uio; struct ucred *a_cred; } */ *ap; { register struct vnode *vp = ap->a_vp; if (vp->v_type != VLNK) return (EPERM); return (nfs_bioread(vp, ap->a_uio, 0, ap->a_cred)); } /* * Do a readlink rpc. * Called by nfs_doio() from below the buffer cache. */ int nfs_readlinkrpc(vp, uiop, cred) register struct vnode *vp; struct uio *uiop; struct ucred *cred; { register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int error = 0, len, attrflag; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(vp); nfsstats.rpccnt[NFSPROC_READLINK]++; nfsm_reqhead(vp, NFSPROC_READLINK, NFSX_FH(v3)); nfsm_fhtom(vp, v3); nfsm_request(vp, NFSPROC_READLINK, uiop->uio_procp, cred); if (v3) nfsm_postop_attr(vp, attrflag); if (!error) { nfsm_strsiz(len, NFS_MAXPATHLEN); nfsm_mtouio(uiop, len); } nfsm_reqdone; return (error); } /* * nfs read rpc call * Ditto above */ int nfs_readrpc(vp, uiop, cred) register struct vnode *vp; struct uio *uiop; struct ucred *cred; { register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; struct mbuf *mreq, *mrep, *md, *mb, *mb2; struct nfsmount *nmp; int error = 0, len, retlen, tsiz, eof, attrflag; int v3 = NFS_ISV3(vp); #ifndef nolint eof = 0; #endif nmp = VFSTONFS(vp->v_mount); tsiz = uiop->uio_resid; if (uiop->uio_offset + tsiz > 0xffffffff && !v3) return (EFBIG); while (tsiz > 0) { nfsstats.rpccnt[NFSPROC_READ]++; len = (tsiz > nmp->nm_rsize) ? nmp->nm_rsize : tsiz; nfsm_reqhead(vp, NFSPROC_READ, NFSX_FH(v3) + NFSX_UNSIGNED * 3); nfsm_fhtom(vp, v3); nfsm_build(tl, u_long *, NFSX_UNSIGNED * 3); if (v3) { txdr_hyper(&uiop->uio_offset, tl); *(tl + 2) = txdr_unsigned(len); } else { *tl++ = txdr_unsigned(uiop->uio_offset); *tl++ = txdr_unsigned(len); *tl = 0; } nfsm_request(vp, NFSPROC_READ, uiop->uio_procp, cred); if (v3) { nfsm_postop_attr(vp, attrflag); if (error) { m_freem(mrep); goto nfsmout; } nfsm_dissect(tl, u_long *, 2 * NFSX_UNSIGNED); eof = fxdr_unsigned(int, *(tl + 1)); } else nfsm_loadattr(vp, (struct vattr *)0); nfsm_strsiz(retlen, nmp->nm_rsize); nfsm_mtouio(uiop, retlen); m_freem(mrep); tsiz -= retlen; if (v3) { if (eof || retlen == 0) tsiz = 0; } else if (retlen < len) tsiz = 0; } nfsmout: return (error); } /* * nfs write call */ int nfs_writerpc(vp, uiop, cred, iomode, must_commit) register struct vnode *vp; register struct uio *uiop; struct ucred *cred; int *iomode, *must_commit; { register u_long *tl; register caddr_t cp; register int t1, t2, backup; caddr_t bpos, dpos, cp2; struct mbuf *mreq, *mrep, *md, *mb, *mb2; struct nfsmount *nmp = VFSTONFS(vp->v_mount); int error = 0, len, tsiz, wccflag = NFSV3_WCCRATTR, rlen, commit; int v3 = NFS_ISV3(vp), committed = NFSV3WRITE_FILESYNC; #ifndef DIAGNOSTIC if (uiop->uio_iovcnt != 1) panic("nfs: writerpc iovcnt > 1"); #endif *must_commit = 0; tsiz = uiop->uio_resid; if (uiop->uio_offset + tsiz > 0xffffffff && !v3) return (EFBIG); while (tsiz > 0) { nfsstats.rpccnt[NFSPROC_WRITE]++; len = (tsiz > nmp->nm_wsize) ? nmp->nm_wsize : tsiz; nfsm_reqhead(vp, NFSPROC_WRITE, NFSX_FH(v3) + 5 * NFSX_UNSIGNED + nfsm_rndup(len)); nfsm_fhtom(vp, v3); if (v3) { nfsm_build(tl, u_long *, 5 * NFSX_UNSIGNED); txdr_hyper(&uiop->uio_offset, tl); tl += 2; *tl++ = txdr_unsigned(len); *tl++ = txdr_unsigned(*iomode); } else { nfsm_build(tl, u_long *, 4 * NFSX_UNSIGNED); *++tl = txdr_unsigned(uiop->uio_offset); tl += 2; } *tl = txdr_unsigned(len); nfsm_uiotom(uiop, len); nfsm_request(vp, NFSPROC_WRITE, uiop->uio_procp, cred); if (v3) { wccflag = NFSV3_WCCCHK; nfsm_wcc_data(vp, wccflag); if (!error) { nfsm_dissect(tl, u_long *, 2 * NFSX_UNSIGNED + NFSX_V3WRITEVERF); rlen = fxdr_unsigned(int, *tl++); if (rlen == 0) { error = NFSERR_IO; break; } else if (rlen < len) { backup = len - rlen; uiop->uio_iov->iov_base -= backup; uiop->uio_iov->iov_len += backup; uiop->uio_offset -= backup; uiop->uio_resid += backup; len = rlen; } commit = fxdr_unsigned(int, *tl++); /* * Return the lowest committment level * obtained by any of the RPCs. */ if (committed == NFSV3WRITE_FILESYNC) committed = commit; else if (committed == NFSV3WRITE_DATASYNC && commit == NFSV3WRITE_UNSTABLE) committed = commit; if ((nmp->nm_flag & NFSMNT_HASWRITEVERF) == 0) { bcopy((caddr_t)tl, (caddr_t)nmp->nm_verf, NFSX_V3WRITEVERF); nmp->nm_flag |= NFSMNT_HASWRITEVERF; } else if (bcmp((caddr_t)tl, (caddr_t)nmp->nm_verf, NFSX_V3WRITEVERF)) { *must_commit = 1; bcopy((caddr_t)tl, (caddr_t)nmp->nm_verf, NFSX_V3WRITEVERF); } } } else nfsm_loadattr(vp, (struct vattr *)0); if (wccflag) VTONFS(vp)->n_mtime = VTONFS(vp)->n_vattr.va_mtime.tv_sec; m_freem(mrep); tsiz -= len; } nfsmout: *iomode = committed; if (error) uiop->uio_resid = tsiz; return (error); } /* * nfs mknod rpc * For NFS v2 this is a kludge. Use a create rpc but with the IFMT bits of the * mode set to specify the file type and the size field for rdev. */ static int nfs_mknodrpc(dvp, vpp, cnp, vap) register struct vnode *dvp; register struct vnode **vpp; register struct componentname *cnp; register struct vattr *vap; { register struct nfsv2_sattr *sp; register struct nfsv3_sattr *sp3; register u_long *tl; register caddr_t cp; register long t1, t2; struct vnode *newvp = (struct vnode *)0; struct nfsnode *np = (struct nfsnode *)0; struct vattr vattr; char *cp2; caddr_t bpos, dpos; int error = 0, wccflag = NFSV3_WCCRATTR, gotvp = 0; struct mbuf *mreq, *mrep, *md, *mb, *mb2; u_long rdev; int v3 = NFS_ISV3(dvp); if (vap->va_type == VCHR || vap->va_type == VBLK) rdev = txdr_unsigned(vap->va_rdev); else if (vap->va_type == VFIFO || vap->va_type == VSOCK) rdev = 0xffffffff; else { VOP_ABORTOP(dvp, cnp); vput(dvp); return (EOPNOTSUPP); } if (error = VOP_GETATTR(dvp, &vattr, cnp->cn_cred, cnp->cn_proc)) { VOP_ABORTOP(dvp, cnp); vput(dvp); return (error); } nfsstats.rpccnt[NFSPROC_MKNOD]++; nfsm_reqhead(dvp, NFSPROC_MKNOD, NFSX_FH(v3) + 4 * NFSX_UNSIGNED + + nfsm_rndup(cnp->cn_namelen) + NFSX_SATTR(v3)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, cnp->cn_namelen, NFS_MAXNAMLEN); if (v3) { nfsm_build(tl, u_long *, NFSX_UNSIGNED + NFSX_V3SRVSATTR); *tl++ = vtonfsv3_type(vap->va_type); sp3 = (struct nfsv3_sattr *)tl; nfsm_v3sattr(sp3, vap, cnp->cn_cred->cr_uid, vattr.va_gid); if (vap->va_type == VCHR || vap->va_type == VBLK) { nfsm_build(tl, u_long *, 2 * NFSX_UNSIGNED); *tl++ = txdr_unsigned(major(vap->va_rdev)); *tl = txdr_unsigned(minor(vap->va_rdev)); } } else { nfsm_build(sp, struct nfsv2_sattr *, NFSX_V2SATTR); sp->sa_mode = vtonfsv2_mode(vap->va_type, vap->va_mode); sp->sa_uid = txdr_unsigned(cnp->cn_cred->cr_uid); sp->sa_gid = txdr_unsigned(vattr.va_gid); sp->sa_size = rdev; txdr_nfsv2time(&vap->va_atime, &sp->sa_atime); txdr_nfsv2time(&vap->va_mtime, &sp->sa_mtime); } nfsm_request(dvp, NFSPROC_MKNOD, cnp->cn_proc, cnp->cn_cred); if (!error) { nfsm_mtofh(dvp, newvp, v3, gotvp); if (!gotvp) { if (newvp) { vput(newvp); newvp = (struct vnode *)0; } error = nfs_lookitup(dvp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_cred, cnp->cn_proc, &np); if (!error) newvp = NFSTOV(np); } } if (v3) nfsm_wcc_data(dvp, wccflag); nfsm_reqdone; if (error) { if (newvp) vput(newvp); } else { if (cnp->cn_flags & MAKEENTRY) cache_enter(dvp, newvp, cnp); *vpp = newvp; } FREE(cnp->cn_pnbuf, M_NAMEI); VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; vput(dvp); return (error); } /* * nfs mknod vop * just call nfs_mknodrpc() to do the work. */ /* ARGSUSED */ static int nfs_mknod(ap) struct vop_mknod_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; struct vattr *a_vap; } */ *ap; { struct vnode *newvp; int error; error = nfs_mknodrpc(ap->a_dvp, &newvp, ap->a_cnp, ap->a_vap); if (!error) vput(newvp); return (error); } static u_long create_verf; /* * nfs file create call */ static int nfs_create(ap) struct vop_create_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; struct vattr *a_vap; } */ *ap; { register struct vnode *dvp = ap->a_dvp; register struct vattr *vap = ap->a_vap; register struct componentname *cnp = ap->a_cnp; register struct nfsv2_sattr *sp; register struct nfsv3_sattr *sp3; register u_long *tl; register caddr_t cp; register long t1, t2; struct nfsnode *np = (struct nfsnode *)0; struct vnode *newvp = (struct vnode *)0; caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR, gotvp = 0, fmode = 0; struct mbuf *mreq, *mrep, *md, *mb, *mb2; struct vattr vattr; int v3 = NFS_ISV3(dvp); /* * Oops, not for me.. */ if (vap->va_type == VSOCK) return (nfs_mknodrpc(dvp, ap->a_vpp, cnp, vap)); if (error = VOP_GETATTR(dvp, &vattr, cnp->cn_cred, cnp->cn_proc)) { VOP_ABORTOP(dvp, cnp); vput(dvp); return (error); } if (vap->va_vaflags & VA_EXCLUSIVE) fmode |= O_EXCL; again: nfsstats.rpccnt[NFSPROC_CREATE]++; nfsm_reqhead(dvp, NFSPROC_CREATE, NFSX_FH(v3) + 2 * NFSX_UNSIGNED + nfsm_rndup(cnp->cn_namelen) + NFSX_SATTR(v3)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, cnp->cn_namelen, NFS_MAXNAMLEN); if (v3) { nfsm_build(tl, u_long *, NFSX_UNSIGNED); if (fmode & O_EXCL) { *tl = txdr_unsigned(NFSV3CREATE_EXCLUSIVE); nfsm_build(tl, u_long *, NFSX_V3CREATEVERF); - if (in_ifaddr) - *tl++ = IA_SIN(in_ifaddr)->sin_addr.s_addr; + if (!TAILQ_EMPTY(&in_ifaddrhead)) + *tl++ = IA_SIN(in_ifaddrhead.tqh_first)->sin_addr.s_addr; else *tl++ = create_verf; *tl = ++create_verf; } else { *tl = txdr_unsigned(NFSV3CREATE_UNCHECKED); nfsm_build(tl, u_long *, NFSX_V3SRVSATTR); sp3 = (struct nfsv3_sattr *)tl; nfsm_v3sattr(sp3, vap, cnp->cn_cred->cr_uid, vattr.va_gid); } } else { nfsm_build(sp, struct nfsv2_sattr *, NFSX_V2SATTR); sp->sa_mode = vtonfsv2_mode(vap->va_type, vap->va_mode); sp->sa_uid = txdr_unsigned(cnp->cn_cred->cr_uid); sp->sa_gid = txdr_unsigned(vattr.va_gid); sp->sa_size = 0; txdr_nfsv2time(&vap->va_atime, &sp->sa_atime); txdr_nfsv2time(&vap->va_mtime, &sp->sa_mtime); } nfsm_request(dvp, NFSPROC_CREATE, cnp->cn_proc, cnp->cn_cred); if (!error) { nfsm_mtofh(dvp, newvp, v3, gotvp); if (!gotvp) { if (newvp) { vput(newvp); newvp = (struct vnode *)0; } error = nfs_lookitup(dvp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_cred, cnp->cn_proc, &np); if (!error) newvp = NFSTOV(np); } } if (v3) nfsm_wcc_data(dvp, wccflag); nfsm_reqdone; if (error) { if (v3 && (fmode & O_EXCL) && error == NFSERR_NOTSUPP) { fmode &= ~O_EXCL; goto again; } if (newvp) vput(newvp); } else if (v3 && (fmode & O_EXCL)) error = nfs_setattrrpc(newvp, vap, cnp->cn_cred, cnp->cn_proc); if (!error) { if (cnp->cn_flags & MAKEENTRY) cache_enter(dvp, newvp, cnp); *ap->a_vpp = newvp; } FREE(cnp->cn_pnbuf, M_NAMEI); VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; vput(dvp); return (error); } /* * nfs file remove call * To try and make nfs semantics closer to ufs semantics, a file that has * other processes using the vnode is renamed instead of removed and then * removed later on the last close. * - If v_usecount > 1 * If a rename is not already in the works * call nfs_sillyrename() to set it up * else * do the remove rpc */ static int nfs_remove(ap) struct vop_remove_args /* { struct vnodeop_desc *a_desc; struct vnode * a_dvp; struct vnode * a_vp; struct componentname * a_cnp; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct vnode *dvp = ap->a_dvp; register struct componentname *cnp = ap->a_cnp; register struct nfsnode *np = VTONFS(vp); int error = 0; struct vattr vattr; #ifndef DIAGNOSTIC if ((cnp->cn_flags & HASBUF) == 0) panic("nfs_remove: no name"); if (vp->v_usecount < 1) panic("nfs_remove: bad v_usecount"); #endif if (vp->v_usecount == 1 || (np->n_sillyrename && VOP_GETATTR(vp, &vattr, cnp->cn_cred, cnp->cn_proc) == 0 && vattr.va_nlink > 1)) { /* * Purge the name cache so that the chance of a lookup for * the name succeeding while the remove is in progress is * minimized. Without node locking it can still happen, such * that an I/O op returns ESTALE, but since you get this if * another host removes the file.. */ cache_purge(vp); /* * throw away biocache buffers, mainly to avoid * unnecessary delayed writes later. */ error = nfs_vinvalbuf(vp, 0, cnp->cn_cred, cnp->cn_proc, 1); /* Do the rpc */ if (error != EINTR) error = nfs_removerpc(dvp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_cred, cnp->cn_proc); /* * Kludge City: If the first reply to the remove rpc is lost.. * the reply to the retransmitted request will be ENOENT * since the file was in fact removed * Therefore, we cheat and return success. */ if (error == ENOENT) error = 0; } else if (!np->n_sillyrename) error = nfs_sillyrename(dvp, vp, cnp); FREE(cnp->cn_pnbuf, M_NAMEI); np->n_attrstamp = 0; vput(dvp); if (vp == dvp) vrele(vp); else vput(vp); return (error); } /* * nfs file remove rpc called from nfs_inactive */ int nfs_removeit(sp) register struct sillyrename *sp; { return (nfs_removerpc(sp->s_dvp, sp->s_name, sp->s_namlen, sp->s_cred, (struct proc *)0)); } /* * Nfs remove rpc, called from nfs_remove() and nfs_removeit(). */ static int nfs_removerpc(dvp, name, namelen, cred, proc) register struct vnode *dvp; char *name; int namelen; struct ucred *cred; struct proc *proc; { register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(dvp); nfsstats.rpccnt[NFSPROC_REMOVE]++; nfsm_reqhead(dvp, NFSPROC_REMOVE, NFSX_FH(v3) + NFSX_UNSIGNED + nfsm_rndup(namelen)); nfsm_fhtom(dvp, v3); nfsm_strtom(name, namelen, NFS_MAXNAMLEN); nfsm_request(dvp, NFSPROC_REMOVE, proc, cred); if (v3) nfsm_wcc_data(dvp, wccflag); nfsm_reqdone; VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; return (error); } /* * nfs file rename call */ static int nfs_rename(ap) struct vop_rename_args /* { struct vnode *a_fdvp; struct vnode *a_fvp; struct componentname *a_fcnp; struct vnode *a_tdvp; struct vnode *a_tvp; struct componentname *a_tcnp; } */ *ap; { register struct vnode *fvp = ap->a_fvp; register struct vnode *tvp = ap->a_tvp; register struct vnode *fdvp = ap->a_fdvp; register struct vnode *tdvp = ap->a_tdvp; register struct componentname *tcnp = ap->a_tcnp; register struct componentname *fcnp = ap->a_fcnp; int error; #ifndef DIAGNOSTIC if ((tcnp->cn_flags & HASBUF) == 0 || (fcnp->cn_flags & HASBUF) == 0) panic("nfs_rename: no name"); #endif /* Check for cross-device rename */ if ((fvp->v_mount != tdvp->v_mount) || (tvp && (fvp->v_mount != tvp->v_mount))) { error = EXDEV; goto out; } /* * If the tvp exists and is in use, sillyrename it before doing the * rename of the new file over it. */ if (tvp && tvp->v_usecount > 1 && !VTONFS(tvp)->n_sillyrename && !nfs_sillyrename(tdvp, tvp, tcnp)) { vrele(tvp); tvp = NULL; } error = nfs_renamerpc(fdvp, fcnp->cn_nameptr, fcnp->cn_namelen, tdvp, tcnp->cn_nameptr, tcnp->cn_namelen, tcnp->cn_cred, tcnp->cn_proc); if (fvp->v_type == VDIR) { if (tvp != NULL && tvp->v_type == VDIR) cache_purge(tdvp); cache_purge(fdvp); } out: if (tdvp == tvp) vrele(tdvp); else vput(tdvp); if (tvp) vput(tvp); vrele(fdvp); vrele(fvp); /* * Kludge: Map ENOENT => 0 assuming that it is a reply to a retry. */ if (error == ENOENT) error = 0; return (error); } /* * nfs file rename rpc called from nfs_remove() above */ static int nfs_renameit(sdvp, scnp, sp) struct vnode *sdvp; struct componentname *scnp; register struct sillyrename *sp; { return (nfs_renamerpc(sdvp, scnp->cn_nameptr, scnp->cn_namelen, sdvp, sp->s_name, sp->s_namlen, scnp->cn_cred, scnp->cn_proc)); } /* * Do an nfs rename rpc. Called from nfs_rename() and nfs_renameit(). */ static int nfs_renamerpc(fdvp, fnameptr, fnamelen, tdvp, tnameptr, tnamelen, cred, proc) register struct vnode *fdvp; char *fnameptr; int fnamelen; register struct vnode *tdvp; char *tnameptr; int tnamelen; struct ucred *cred; struct proc *proc; { register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int error = 0, fwccflag = NFSV3_WCCRATTR, twccflag = NFSV3_WCCRATTR; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(fdvp); nfsstats.rpccnt[NFSPROC_RENAME]++; nfsm_reqhead(fdvp, NFSPROC_RENAME, (NFSX_FH(v3) + NFSX_UNSIGNED)*2 + nfsm_rndup(fnamelen) + nfsm_rndup(tnamelen)); nfsm_fhtom(fdvp, v3); nfsm_strtom(fnameptr, fnamelen, NFS_MAXNAMLEN); nfsm_fhtom(tdvp, v3); nfsm_strtom(tnameptr, tnamelen, NFS_MAXNAMLEN); nfsm_request(fdvp, NFSPROC_RENAME, proc, cred); if (v3) { nfsm_wcc_data(fdvp, fwccflag); nfsm_wcc_data(tdvp, twccflag); } nfsm_reqdone; VTONFS(fdvp)->n_flag |= NMODIFIED; VTONFS(tdvp)->n_flag |= NMODIFIED; if (!fwccflag) VTONFS(fdvp)->n_attrstamp = 0; if (!twccflag) VTONFS(tdvp)->n_attrstamp = 0; return (error); } /* * nfs hard link create call */ static int nfs_link(ap) struct vop_link_args /* { struct vnode *a_tdvp; struct vnode *a_vp; struct componentname *a_cnp; } */ *ap; { #if defined(__NetBSD__) /* * Since the args are reversed in the VOP_LINK() calls, * switch them back. Argh! */ register struct vnode *vp = ap->a_tdvp; register struct vnode *tdvp = ap->a_vp; #else register struct vnode *vp = ap->a_vp; register struct vnode *tdvp = ap->a_tdvp; #endif register struct componentname *cnp = ap->a_cnp; register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR, attrflag = 0; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(vp); if (vp->v_mount != tdvp->v_mount) { /*VOP_ABORTOP(vp, cnp);*/ if (tdvp == vp) vrele(tdvp); else vput(tdvp); return (EXDEV); } /* * Push all writes to the server, so that the attribute cache * doesn't get "out of sync" with the server. * XXX There should be a better way! */ VOP_FSYNC(vp, cnp->cn_cred, MNT_WAIT, cnp->cn_proc); nfsstats.rpccnt[NFSPROC_LINK]++; nfsm_reqhead(vp, NFSPROC_LINK, NFSX_FH(v3)*2 + NFSX_UNSIGNED + nfsm_rndup(cnp->cn_namelen)); nfsm_fhtom(vp, v3); nfsm_fhtom(tdvp, v3); nfsm_strtom(cnp->cn_nameptr, cnp->cn_namelen, NFS_MAXNAMLEN); nfsm_request(vp, NFSPROC_LINK, cnp->cn_proc, cnp->cn_cred); if (v3) { nfsm_postop_attr(vp, attrflag); nfsm_wcc_data(tdvp, wccflag); } nfsm_reqdone; FREE(cnp->cn_pnbuf, M_NAMEI); VTONFS(tdvp)->n_flag |= NMODIFIED; if (!attrflag) VTONFS(vp)->n_attrstamp = 0; if (!wccflag) VTONFS(tdvp)->n_attrstamp = 0; vput(tdvp); /* * Kludge: Map EEXIST => 0 assuming that it is a reply to a retry. */ if (error == EEXIST) error = 0; return (error); } /* * nfs symbolic link create call */ static int nfs_symlink(ap) struct vop_symlink_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; struct vattr *a_vap; char *a_target; } */ *ap; { register struct vnode *dvp = ap->a_dvp; register struct vattr *vap = ap->a_vap; register struct componentname *cnp = ap->a_cnp; register struct nfsv2_sattr *sp; register struct nfsv3_sattr *sp3; register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int slen, error = 0, wccflag = NFSV3_WCCRATTR, gotvp; struct mbuf *mreq, *mrep, *md, *mb, *mb2; struct vnode *newvp = (struct vnode *)0; int v3 = NFS_ISV3(dvp); nfsstats.rpccnt[NFSPROC_SYMLINK]++; slen = strlen(ap->a_target); nfsm_reqhead(dvp, NFSPROC_SYMLINK, NFSX_FH(v3) + 2*NFSX_UNSIGNED + nfsm_rndup(cnp->cn_namelen) + nfsm_rndup(slen) + NFSX_SATTR(v3)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, cnp->cn_namelen, NFS_MAXNAMLEN); if (v3) { nfsm_build(sp3, struct nfsv3_sattr *, NFSX_V3SRVSATTR); nfsm_v3sattr(sp3, vap, cnp->cn_cred->cr_uid, cnp->cn_cred->cr_gid); } nfsm_strtom(ap->a_target, slen, NFS_MAXPATHLEN); if (!v3) { nfsm_build(sp, struct nfsv2_sattr *, NFSX_V2SATTR); sp->sa_mode = vtonfsv2_mode(VLNK, vap->va_mode); sp->sa_uid = txdr_unsigned(cnp->cn_cred->cr_uid); sp->sa_gid = txdr_unsigned(cnp->cn_cred->cr_gid); sp->sa_size = -1; txdr_nfsv2time(&vap->va_atime, &sp->sa_atime); txdr_nfsv2time(&vap->va_mtime, &sp->sa_mtime); } nfsm_request(dvp, NFSPROC_SYMLINK, cnp->cn_proc, cnp->cn_cred); if (v3) { if (!error) nfsm_mtofh(dvp, newvp, v3, gotvp); nfsm_wcc_data(dvp, wccflag); } nfsm_reqdone; if (newvp) vput(newvp); FREE(cnp->cn_pnbuf, M_NAMEI); VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; vput(dvp); /* * Kludge: Map EEXIST => 0 assuming that it is a reply to a retry. */ if (error == EEXIST) error = 0; return (error); } /* * nfs make dir call */ static int nfs_mkdir(ap) struct vop_mkdir_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; struct vattr *a_vap; } */ *ap; { register struct vnode *dvp = ap->a_dvp; register struct vattr *vap = ap->a_vap; register struct componentname *cnp = ap->a_cnp; register struct nfsv2_sattr *sp; register struct nfsv3_sattr *sp3; register u_long *tl; register caddr_t cp; register long t1, t2; register int len; struct nfsnode *np = (struct nfsnode *)0; struct vnode *newvp = (struct vnode *)0; caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR; int gotvp = 0; struct mbuf *mreq, *mrep, *md, *mb, *mb2; struct vattr vattr; int v3 = NFS_ISV3(dvp); if (error = VOP_GETATTR(dvp, &vattr, cnp->cn_cred, cnp->cn_proc)) { VOP_ABORTOP(dvp, cnp); vput(dvp); return (error); } len = cnp->cn_namelen; nfsstats.rpccnt[NFSPROC_MKDIR]++; nfsm_reqhead(dvp, NFSPROC_MKDIR, NFSX_FH(v3) + NFSX_UNSIGNED + nfsm_rndup(len) + NFSX_SATTR(v3)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, len, NFS_MAXNAMLEN); if (v3) { nfsm_build(sp3, struct nfsv3_sattr *, NFSX_V3SRVSATTR); nfsm_v3sattr(sp3, vap, cnp->cn_cred->cr_uid, vattr.va_gid); } else { nfsm_build(sp, struct nfsv2_sattr *, NFSX_V2SATTR); sp->sa_mode = vtonfsv2_mode(VDIR, vap->va_mode); sp->sa_uid = txdr_unsigned(cnp->cn_cred->cr_uid); sp->sa_gid = txdr_unsigned(vattr.va_gid); sp->sa_size = -1; txdr_nfsv2time(&vap->va_atime, &sp->sa_atime); txdr_nfsv2time(&vap->va_mtime, &sp->sa_mtime); } nfsm_request(dvp, NFSPROC_MKDIR, cnp->cn_proc, cnp->cn_cred); if (!error) nfsm_mtofh(dvp, newvp, v3, gotvp); if (v3) nfsm_wcc_data(dvp, wccflag); nfsm_reqdone; VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; /* * Kludge: Map EEXIST => 0 assuming that you have a reply to a retry * if we can succeed in looking up the directory. */ if (error == EEXIST || (!error && !gotvp)) { if (newvp) { vrele(newvp); newvp = (struct vnode *)0; } error = nfs_lookitup(dvp, cnp->cn_nameptr, len, cnp->cn_cred, cnp->cn_proc, &np); if (!error) { newvp = NFSTOV(np); if (newvp->v_type != VDIR) error = EEXIST; } } if (error) { if (newvp) vrele(newvp); } else *ap->a_vpp = newvp; FREE(cnp->cn_pnbuf, M_NAMEI); vput(dvp); return (error); } /* * nfs remove directory call */ static int nfs_rmdir(ap) struct vop_rmdir_args /* { struct vnode *a_dvp; struct vnode *a_vp; struct componentname *a_cnp; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct vnode *dvp = ap->a_dvp; register struct componentname *cnp = ap->a_cnp; register u_long *tl; register caddr_t cp; register long t1, t2; caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR; struct mbuf *mreq, *mrep, *md, *mb, *mb2; int v3 = NFS_ISV3(dvp); if (dvp == vp) { vput(dvp); vrele(dvp); FREE(cnp->cn_pnbuf, M_NAMEI); return (EINVAL); } nfsstats.rpccnt[NFSPROC_RMDIR]++; nfsm_reqhead(dvp, NFSPROC_RMDIR, NFSX_FH(v3) + NFSX_UNSIGNED + nfsm_rndup(cnp->cn_namelen)); nfsm_fhtom(dvp, v3); nfsm_strtom(cnp->cn_nameptr, cnp->cn_namelen, NFS_MAXNAMLEN); nfsm_request(dvp, NFSPROC_RMDIR, cnp->cn_proc, cnp->cn_cred); if (v3) nfsm_wcc_data(dvp, wccflag); nfsm_reqdone; FREE(cnp->cn_pnbuf, M_NAMEI); VTONFS(dvp)->n_flag |= NMODIFIED; if (!wccflag) VTONFS(dvp)->n_attrstamp = 0; cache_purge(dvp); cache_purge(vp); vput(vp); vput(dvp); /* * Kludge: Map ENOENT => 0 assuming that you have a reply to a retry. */ if (error == ENOENT) error = 0; return (error); } /* * nfs readdir call */ static int nfs_readdir(ap) struct vop_readdir_args /* { struct vnode *a_vp; struct uio *a_uio; struct ucred *a_cred; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); register struct uio *uio = ap->a_uio; int tresid, error; struct vattr vattr; if (vp->v_type != VDIR) return (EPERM); /* * First, check for hit on the EOF offset cache */ if (np->n_direofoffset > 0 && uio->uio_offset >= np->n_direofoffset && (np->n_flag & NMODIFIED) == 0) { if (VFSTONFS(vp->v_mount)->nm_flag & NFSMNT_NQNFS) { if (NQNFS_CKCACHABLE(vp, ND_READ)) { nfsstats.direofcache_hits++; return (0); } } else if (VOP_GETATTR(vp, &vattr, ap->a_cred, uio->uio_procp) == 0 && np->n_mtime == vattr.va_mtime.tv_sec) { nfsstats.direofcache_hits++; return (0); } } /* * Call nfs_bioread() to do the real work. */ tresid = uio->uio_resid; error = nfs_bioread(vp, uio, 0, ap->a_cred); if (!error && uio->uio_resid == tresid) nfsstats.direofcache_misses++; return (error); } /* * Readdir rpc call. * Called from below the buffer cache by nfs_doio(). */ int nfs_readdirrpc(vp, uiop, cred) struct vnode *vp; register struct uio *uiop; struct ucred *cred; { register int len, left; register struct dirent *dp; register u_long *tl; register caddr_t cp; register long t1, t2; register nfsuint64 *cookiep; caddr_t bpos, dpos, cp2; struct mbuf *mreq, *mrep, *md, *mb, *mb2; nfsuint64 cookie; struct nfsmount *nmp = VFSTONFS(vp->v_mount); struct nfsnode *dnp = VTONFS(vp); u_quad_t fileno; int error = 0, tlen, more_dirs = 1, blksiz = 0, bigenough = 1; int attrflag; int v3 = NFS_ISV3(vp); #ifndef nolint dp = (struct dirent *)0; #endif #ifndef DIAGNOSTIC if (uiop->uio_iovcnt != 1 || (uiop->uio_offset & (NFS_DIRBLKSIZ - 1)) || (uiop->uio_resid & (NFS_DIRBLKSIZ - 1))) panic("nfs readdirrpc bad uio"); #endif /* * If there is no cookie, assume directory was stale. */ cookiep = nfs_getcookie(dnp, uiop->uio_offset, 0); if (cookiep) cookie = *cookiep; else return (NFSERR_BAD_COOKIE); /* * Loop around doing readdir rpc's of size nm_readdirsize * truncated to a multiple of DIRBLKSIZ. * The stopping criteria is EOF or buffer full. */ while (more_dirs && bigenough) { nfsstats.rpccnt[NFSPROC_READDIR]++; nfsm_reqhead(vp, NFSPROC_READDIR, NFSX_FH(v3) + NFSX_READDIR(v3)); nfsm_fhtom(vp, v3); if (v3) { nfsm_build(tl, u_long *, 5 * NFSX_UNSIGNED); *tl++ = cookie.nfsuquad[0]; *tl++ = cookie.nfsuquad[1]; *tl++ = dnp->n_cookieverf.nfsuquad[0]; *tl++ = dnp->n_cookieverf.nfsuquad[1]; } else { nfsm_build(tl, u_long *, 2 * NFSX_UNSIGNED); *tl++ = cookie.nfsuquad[0]; } *tl = txdr_unsigned(nmp->nm_readdirsize); nfsm_request(vp, NFSPROC_READDIR, uiop->uio_procp, cred); if (v3) { nfsm_postop_attr(vp, attrflag); if (!error) { nfsm_dissect(tl, u_long *, 2 * NFSX_UNSIGNED); dnp->n_cookieverf.nfsuquad[0] = *tl++; dnp->n_cookieverf.nfsuquad[1] = *tl; } else { m_freem(mrep); goto nfsmout; } } nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); more_dirs = fxdr_unsigned(int, *tl); /* loop thru the dir entries, doctoring them to 4bsd form */ while (more_dirs && bigenough) { if (v3) { nfsm_dissect(tl, u_long *, 3 * NFSX_UNSIGNED); fxdr_hyper(tl, &fileno); len = fxdr_unsigned(int, *(tl + 2)); } else { nfsm_dissect(tl, u_long *, 2 * NFSX_UNSIGNED); fileno = fxdr_unsigned(u_quad_t, *tl++); len = fxdr_unsigned(int, *tl); } if (len <= 0 || len > NFS_MAXNAMLEN) { error = EBADRPC; m_freem(mrep); goto nfsmout; } tlen = nfsm_rndup(len); if (tlen == len) tlen += 4; /* To ensure null termination */ left = DIRBLKSIZ - blksiz; if ((tlen + DIRHDSIZ) > left) { dp->d_reclen += left; uiop->uio_iov->iov_base += left; uiop->uio_iov->iov_len -= left; uiop->uio_offset += left; uiop->uio_resid -= left; blksiz = 0; } if ((tlen + DIRHDSIZ) > uiop->uio_resid) bigenough = 0; if (bigenough) { dp = (struct dirent *)uiop->uio_iov->iov_base; dp->d_fileno = (int)fileno; dp->d_namlen = len; dp->d_reclen = tlen + DIRHDSIZ; dp->d_type = DT_UNKNOWN; blksiz += dp->d_reclen; if (blksiz == DIRBLKSIZ) blksiz = 0; uiop->uio_offset += DIRHDSIZ; uiop->uio_resid -= DIRHDSIZ; uiop->uio_iov->iov_base += DIRHDSIZ; uiop->uio_iov->iov_len -= DIRHDSIZ; nfsm_mtouio(uiop, len); cp = uiop->uio_iov->iov_base; tlen -= len; *cp = '\0'; /* null terminate */ uiop->uio_iov->iov_base += tlen; uiop->uio_iov->iov_len -= tlen; uiop->uio_offset += tlen; uiop->uio_resid -= tlen; } else nfsm_adv(nfsm_rndup(len)); if (v3) { nfsm_dissect(tl, u_long *, 3 * NFSX_UNSIGNED); } else { nfsm_dissect(tl, u_long *, 2 * NFSX_UNSIGNED); } if (bigenough) { cookie.nfsuquad[0] = *tl++; if (v3) cookie.nfsuquad[1] = *tl++; } else if (v3) tl += 2; else tl++; more_dirs = fxdr_unsigned(int, *tl); } /* * If at end of rpc data, get the eof boolean */ if (!more_dirs) { nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); more_dirs = (fxdr_unsigned(int, *tl) == 0); } m_freem(mrep); } /* * Fill last record, iff any, out to a multiple of DIRBLKSIZ * by increasing d_reclen for the last record. */ if (blksiz > 0) { left = DIRBLKSIZ - blksiz; dp->d_reclen += left; uiop->uio_iov->iov_base += left; uiop->uio_iov->iov_len -= left; uiop->uio_offset += left; uiop->uio_resid -= left; } /* * We are now either at the end of the directory or have filled the * block. */ if (bigenough) dnp->n_direofoffset = uiop->uio_offset; else { if (uiop->uio_resid > 0) printf("EEK! readdirrpc resid > 0\n"); cookiep = nfs_getcookie(dnp, uiop->uio_offset, 1); *cookiep = cookie; } nfsmout: return (error); } /* * NFS V3 readdir plus RPC. Used in place of nfs_readdirrpc(). */ int nfs_readdirplusrpc(vp, uiop, cred) struct vnode *vp; register struct uio *uiop; struct ucred *cred; { register int len, left; register struct dirent *dp; register u_long *tl; register caddr_t cp; register long t1, t2; register struct vnode *newvp; register nfsuint64 *cookiep; caddr_t bpos, dpos, cp2, dpossav1, dpossav2; struct mbuf *mreq, *mrep, *md, *mb, *mb2, *mdsav1, *mdsav2; struct nameidata nami, *ndp = &nami; struct componentname *cnp = &ndp->ni_cnd; nfsuint64 cookie; struct nfsmount *nmp = VFSTONFS(vp->v_mount); struct nfsnode *dnp = VTONFS(vp), *np; nfsfh_t *fhp; u_quad_t fileno; int error = 0, tlen, more_dirs = 1, blksiz = 0, doit, bigenough = 1, i; int attrflag, fhsize; #ifndef nolint dp = (struct dirent *)0; #endif #ifndef DIAGNOSTIC if (uiop->uio_iovcnt != 1 || (uiop->uio_offset & (DIRBLKSIZ - 1)) || (uiop->uio_resid & (DIRBLKSIZ - 1))) panic("nfs readdirplusrpc bad uio"); #endif ndp->ni_dvp = vp; newvp = NULLVP; /* * If there is no cookie, assume directory was stale. */ cookiep = nfs_getcookie(dnp, uiop->uio_offset, 0); if (cookiep) cookie = *cookiep; else return (NFSERR_BAD_COOKIE); /* * Loop around doing readdir rpc's of size nm_readdirsize * truncated to a multiple of DIRBLKSIZ. * The stopping criteria is EOF or buffer full. */ while (more_dirs && bigenough) { nfsstats.rpccnt[NFSPROC_READDIRPLUS]++; nfsm_reqhead(vp, NFSPROC_READDIRPLUS, NFSX_FH(1) + 6 * NFSX_UNSIGNED); nfsm_fhtom(vp, 1); nfsm_build(tl, u_long *, 6 * NFSX_UNSIGNED); *tl++ = cookie.nfsuquad[0]; *tl++ = cookie.nfsuquad[1]; *tl++ = dnp->n_cookieverf.nfsuquad[0]; *tl++ = dnp->n_cookieverf.nfsuquad[1]; *tl++ = txdr_unsigned(nmp->nm_readdirsize); *tl = txdr_unsigned(nmp->nm_rsize); nfsm_request(vp, NFSPROC_READDIRPLUS, uiop->uio_procp, cred); nfsm_postop_attr(vp, attrflag); if (error) { m_freem(mrep); goto nfsmout; } nfsm_dissect(tl, u_long *, 3 * NFSX_UNSIGNED); dnp->n_cookieverf.nfsuquad[0] = *tl++; dnp->n_cookieverf.nfsuquad[1] = *tl++; more_dirs = fxdr_unsigned(int, *tl); /* loop thru the dir entries, doctoring them to 4bsd form */ while (more_dirs && bigenough) { nfsm_dissect(tl, u_long *, 3 * NFSX_UNSIGNED); fxdr_hyper(tl, &fileno); len = fxdr_unsigned(int, *(tl + 2)); if (len <= 0 || len > NFS_MAXNAMLEN) { error = EBADRPC; m_freem(mrep); goto nfsmout; } tlen = nfsm_rndup(len); if (tlen == len) tlen += 4; /* To ensure null termination*/ left = DIRBLKSIZ - blksiz; if ((tlen + DIRHDSIZ) > left) { dp->d_reclen += left; uiop->uio_iov->iov_base += left; uiop->uio_iov->iov_len -= left; uiop->uio_offset += left; uiop->uio_resid -= left; blksiz = 0; } if ((tlen + DIRHDSIZ) > uiop->uio_resid) bigenough = 0; if (bigenough) { dp = (struct dirent *)uiop->uio_iov->iov_base; dp->d_fileno = (int)fileno; dp->d_namlen = len; dp->d_reclen = tlen + DIRHDSIZ; dp->d_type = DT_UNKNOWN; blksiz += dp->d_reclen; if (blksiz == DIRBLKSIZ) blksiz = 0; uiop->uio_offset += DIRHDSIZ; uiop->uio_resid -= DIRHDSIZ; uiop->uio_iov->iov_base += DIRHDSIZ; uiop->uio_iov->iov_len -= DIRHDSIZ; cnp->cn_nameptr = uiop->uio_iov->iov_base; cnp->cn_namelen = len; nfsm_mtouio(uiop, len); cp = uiop->uio_iov->iov_base; tlen -= len; *cp = '\0'; uiop->uio_iov->iov_base += tlen; uiop->uio_iov->iov_len -= tlen; uiop->uio_offset += tlen; uiop->uio_resid -= tlen; } else nfsm_adv(nfsm_rndup(len)); nfsm_dissect(tl, u_long *, 3 * NFSX_UNSIGNED); if (bigenough) { cookie.nfsuquad[0] = *tl++; cookie.nfsuquad[1] = *tl++; } else tl += 2; /* * Since the attributes are before the file handle * (sigh), we must skip over the attributes and then * come back and get them. */ attrflag = fxdr_unsigned(int, *tl); if (attrflag) { dpossav1 = dpos; mdsav1 = md; nfsm_adv(NFSX_V3FATTR); nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); doit = fxdr_unsigned(int, *tl); if (doit) { nfsm_getfh(fhp, fhsize, 1); if (NFS_CMPFH(dnp, fhp, fhsize)) { VREF(vp); newvp = vp; np = dnp; } else { if (error = nfs_nget(vp->v_mount, fhp, fhsize, &np)) doit = 0; else newvp = NFSTOV(np); } } if (doit) { dpossav2 = dpos; dpos = dpossav1; mdsav2 = md; md = mdsav1; nfsm_loadattr(newvp, (struct vattr *)0); dpos = dpossav2; md = mdsav2; dp->d_type = IFTODT(VTTOIF(np->n_vattr.va_type)); ndp->ni_vp = newvp; cnp->cn_hash = 0; for (cp = cnp->cn_nameptr, i = 1; i <= len; i++, cp++) cnp->cn_hash += (unsigned char)*cp * i; if (cnp->cn_namelen <= NCHNAMLEN) cache_enter(ndp->ni_dvp, ndp->ni_vp, cnp); } } else { /* Just skip over the file handle */ nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); i = fxdr_unsigned(int, *tl); nfsm_adv(nfsm_rndup(i)); } if (newvp != NULLVP) { vrele(newvp); newvp = NULLVP; } nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); more_dirs = fxdr_unsigned(int, *tl); } /* * If at end of rpc data, get the eof boolean */ if (!more_dirs) { nfsm_dissect(tl, u_long *, NFSX_UNSIGNED); more_dirs = (fxdr_unsigned(int, *tl) == 0); } m_freem(mrep); } /* * Fill last record, iff any, out to a multiple of NFS_DIRBLKSIZ * by increasing d_reclen for the last record. */ if (blksiz > 0) { left = DIRBLKSIZ - blksiz; dp->d_reclen += left; uiop->uio_iov->iov_base += left; uiop->uio_iov->iov_len -= left; uiop->uio_offset += left; uiop->uio_resid -= left; } /* * We are now either at the end of the directory or have filled the * block. */ if (bigenough) dnp->n_direofoffset = uiop->uio_offset; else { if (uiop->uio_resid > 0) printf("EEK! readdirplusrpc resid > 0\n"); cookiep = nfs_getcookie(dnp, uiop->uio_offset, 1); *cookiep = cookie; } nfsmout: if (newvp != NULLVP) { if (newvp == vp) vrele(newvp); else vput(newvp); newvp = NULLVP; } return (error); } /* * Silly rename. To make the NFS filesystem that is stateless look a little * more like the "ufs" a remove of an active vnode is translated to a rename * to a funny looking filename that is removed by nfs_inactive on the * nfsnode. There is the potential for another process on a different client * to create the same funny name between the nfs_lookitup() fails and the * nfs_rename() completes, but... */ static int nfs_sillyrename(dvp, vp, cnp) struct vnode *dvp, *vp; struct componentname *cnp; { register struct sillyrename *sp; struct nfsnode *np; int error; short pid; cache_purge(dvp); np = VTONFS(vp); #ifndef DIAGNOSTIC if (vp->v_type == VDIR) panic("nfs: sillyrename dir"); #endif MALLOC(sp, struct sillyrename *, sizeof (struct sillyrename), M_NFSREQ, M_WAITOK); sp->s_cred = crdup(cnp->cn_cred); sp->s_dvp = dvp; VREF(dvp); /* Fudge together a funny name */ pid = cnp->cn_proc->p_pid; sp->s_namlen = sprintf(sp->s_name, ".nfsA%04x4.4", pid); /* Try lookitups until we get one that isn't there */ while (nfs_lookitup(dvp, sp->s_name, sp->s_namlen, sp->s_cred, cnp->cn_proc, (struct nfsnode **)0) == 0) { sp->s_name[4]++; if (sp->s_name[4] > 'z') { error = EINVAL; goto bad; } } if (error = nfs_renameit(dvp, cnp, sp)) goto bad; error = nfs_lookitup(dvp, sp->s_name, sp->s_namlen, sp->s_cred, cnp->cn_proc, &np); np->n_sillyrename = sp; return (0); bad: vrele(sp->s_dvp); crfree(sp->s_cred); free((caddr_t)sp, M_NFSREQ); return (error); } /* * Look up a file name and optionally either update the file handle or * allocate an nfsnode, depending on the value of npp. * npp == NULL --> just do the lookup * *npp == NULL --> allocate a new nfsnode and make sure attributes are * handled too * *npp != NULL --> update the file handle in the vnode */ static int nfs_lookitup(dvp, name, len, cred, procp, npp) register struct vnode *dvp; char *name; int len; struct ucred *cred; struct proc *procp; struct nfsnode **npp; { register u_long *tl; register caddr_t cp; register long t1, t2; struct vnode *newvp = (struct vnode *)0; struct nfsnode *np, *dnp = VTONFS(dvp); caddr_t bpos, dpos, cp2; int error = 0, fhlen, attrflag; struct mbuf *mreq, *mrep, *md, *mb, *mb2; nfsfh_t *nfhp; int v3 = NFS_ISV3(dvp); nfsstats.rpccnt[NFSPROC_LOOKUP]++; nfsm_reqhead(dvp, NFSPROC_LOOKUP, NFSX_FH(v3) + NFSX_UNSIGNED + nfsm_rndup(len)); nfsm_fhtom(dvp, v3); nfsm_strtom(name, len, NFS_MAXNAMLEN); nfsm_request(dvp, NFSPROC_LOOKUP, procp, cred); if (npp && !error) { nfsm_getfh(nfhp, fhlen, v3); if (*npp) { np = *npp; if (np->n_fhsize > NFS_SMALLFH && fhlen <= NFS_SMALLFH) { free((caddr_t)np->n_fhp, M_NFSBIGFH); np->n_fhp = &np->n_fh; } else if (np->n_fhsize <= NFS_SMALLFH && fhlen>NFS_SMALLFH) np->n_fhp =(nfsfh_t *)malloc(fhlen,M_NFSBIGFH,M_WAITOK); bcopy((caddr_t)nfhp, (caddr_t)np->n_fhp, fhlen); np->n_fhsize = fhlen; newvp = NFSTOV(np); } else if (NFS_CMPFH(dnp, nfhp, fhlen)) { VREF(dvp); newvp = dvp; } else { error = nfs_nget(dvp->v_mount, nfhp, fhlen, &np); if (error) { m_freem(mrep); return (error); } newvp = NFSTOV(np); } if (v3) { nfsm_postop_attr(newvp, attrflag); if (!attrflag && *npp == NULL) { m_freem(mrep); if (newvp == dvp) vrele(newvp); else vput(newvp); return (ENOENT); } } else nfsm_loadattr(newvp, (struct vattr *)0); } nfsm_reqdone; if (npp && *npp == NULL) { if (error) { if (newvp) if (newvp == dvp) vrele(newvp); else vput(newvp); } else *npp = np; } return (error); } /* * Nfs Version 3 commit rpc */ static int nfs_commit(vp, offset, cnt, cred, procp) register struct vnode *vp; u_quad_t offset; int cnt; struct ucred *cred; struct proc *procp; { register caddr_t cp; register u_long *tl; register int t1, t2; register struct nfsmount *nmp = VFSTONFS(vp->v_mount); caddr_t bpos, dpos, cp2; int error = 0, wccflag = NFSV3_WCCRATTR; struct mbuf *mreq, *mrep, *md, *mb, *mb2; if ((nmp->nm_flag & NFSMNT_HASWRITEVERF) == 0) return (0); nfsstats.rpccnt[NFSPROC_COMMIT]++; nfsm_reqhead(vp, NFSPROC_COMMIT, NFSX_FH(1)); nfsm_fhtom(vp, 1); nfsm_build(tl, u_long *, 3 * NFSX_UNSIGNED); txdr_hyper(&offset, tl); tl += 2; *tl = txdr_unsigned(cnt); nfsm_request(vp, NFSPROC_COMMIT, procp, cred); nfsm_wcc_data(vp, wccflag); if (!error) { nfsm_dissect(tl, u_long *, NFSX_V3WRITEVERF); if (bcmp((caddr_t)nmp->nm_verf, (caddr_t)tl, NFSX_V3WRITEVERF)) { bcopy((caddr_t)tl, (caddr_t)nmp->nm_verf, NFSX_V3WRITEVERF); error = NFSERR_STALEWRITEVERF; } } nfsm_reqdone; return (error); } /* * Kludge City.. * - make nfs_bmap() essentially a no-op that does no translation * - do nfs_strategy() by doing I/O with nfs_readrpc/nfs_writerpc * (Maybe I could use the process's page mapping, but I was concerned that * Kernel Write might not be enabled and also figured copyout() would do * a lot more work than bcopy() and also it currently happens in the * context of the swapper process (2). */ static int nfs_bmap(ap) struct vop_bmap_args /* { struct vnode *a_vp; daddr_t a_bn; struct vnode **a_vpp; daddr_t *a_bnp; int *a_runp; int *a_runb; } */ *ap; { register struct vnode *vp = ap->a_vp; if (ap->a_vpp != NULL) *ap->a_vpp = vp; if (ap->a_bnp != NULL) *ap->a_bnp = ap->a_bn * btodb(vp->v_mount->mnt_stat.f_iosize); if (ap->a_runp != NULL) *ap->a_runp = 0; if (ap->a_runb != NULL) *ap->a_runb = 0; return (0); } /* * Strategy routine. * For async requests when nfsiod(s) are running, queue the request by * calling nfs_asyncio(), otherwise just all nfs_doio() to do the * request. */ static int nfs_strategy(ap) struct vop_strategy_args *ap; { register struct buf *bp = ap->a_bp; struct ucred *cr; struct proc *p; int error = 0; if (bp->b_flags & B_PHYS) panic("nfs physio"); if (bp->b_flags & B_ASYNC) p = (struct proc *)0; else p = curproc; /* XXX */ if (bp->b_flags & B_READ) cr = bp->b_rcred; else cr = bp->b_wcred; /* * If the op is asynchronous and an i/o daemon is waiting * queue the request, wake it up and wait for completion * otherwise just do it ourselves. */ if ((bp->b_flags & B_ASYNC) == 0 || nfs_asyncio(bp, NOCRED)) error = nfs_doio(bp, cr, p); return (error); } /* * Mmap a file * * NB Currently unsupported. */ /* ARGSUSED */ static int nfs_mmap(ap) struct vop_mmap_args /* { struct vnode *a_vp; int a_fflags; struct ucred *a_cred; struct proc *a_p; } */ *ap; { return (EINVAL); } /* * fsync vnode op. Just call nfs_flush() with commit == 1. */ /* ARGSUSED */ static int nfs_fsync(ap) struct vop_fsync_args /* { struct vnodeop_desc *a_desc; struct vnode * a_vp; struct ucred * a_cred; int a_waitfor; struct proc * a_p; } */ *ap; { return (nfs_flush(ap->a_vp, ap->a_cred, ap->a_waitfor, ap->a_p, 1)); } /* * Flush all the blocks associated with a vnode. * Walk through the buffer pool and push any dirty pages * associated with the vnode. */ static int nfs_flush(vp, cred, waitfor, p, commit) register struct vnode *vp; struct ucred *cred; int waitfor; struct proc *p; int commit; { register struct nfsnode *np = VTONFS(vp); register struct buf *bp; register int i; struct buf *nbp; struct nfsmount *nmp = VFSTONFS(vp->v_mount); int s, error = 0, slptimeo = 0, slpflag = 0, retv, bvecpos; int passone = 1; u_quad_t off = (u_quad_t)-1, endoff = 0, toff; struct ucred* wcred = NULL; #ifndef NFS_COMMITBVECSIZ #define NFS_COMMITBVECSIZ 20 #endif struct buf *bvec[NFS_COMMITBVECSIZ]; if (nmp->nm_flag & NFSMNT_INT) slpflag = PCATCH; if (!commit) passone = 0; /* * A b_flags == (B_DELWRI | B_NEEDCOMMIT) block has been written to the * server, but nas not been committed to stable storage on the server * yet. On the first pass, the byte range is worked out and the commit * rpc is done. On the second pass, nfs_writebp() is called to do the * job. */ again: bvecpos = 0; if (NFS_ISV3(vp) && commit) { s = splbio(); for (bp = vp->v_dirtyblkhd.lh_first; bp; bp = nbp) { nbp = bp->b_vnbufs.le_next; if (bvecpos >= NFS_COMMITBVECSIZ) break; if ((bp->b_flags & (B_BUSY | B_DELWRI | B_NEEDCOMMIT)) != (B_DELWRI | B_NEEDCOMMIT)) continue; bremfree(bp); /* * Work out if all buffers are using the same cred * so we can deal with them all with one commit. */ if (wcred == NULL) wcred = bp->b_wcred; else if (wcred != bp->b_wcred) wcred = NOCRED; bp->b_flags |= (B_BUSY | B_WRITEINPROG); vfs_busy_pages(bp, 1); /* * A list of these buffers is kept so that the * second loop knows which buffers have actually * been committed. This is necessary, since there * may be a race between the commit rpc and new * uncommitted writes on the file. */ bvec[bvecpos++] = bp; toff = ((u_quad_t)bp->b_blkno) * DEV_BSIZE + bp->b_dirtyoff; if (toff < off) off = toff; toff += (u_quad_t)(bp->b_dirtyend - bp->b_dirtyoff); if (toff > endoff) endoff = toff; } splx(s); } if (bvecpos > 0) { /* * Commit data on the server, as required. * If all bufs are using the same wcred, then use that with * one call for all of them, otherwise commit each one * separately. */ if (wcred != NOCRED) retv = nfs_commit(vp, off, (int)(endoff - off), wcred, p); else { retv = 0; for (i = 0; i < bvecpos; i++) { off_t off, size; bp = bvec[i]; off = ((u_quad_t)bp->b_blkno) * DEV_BSIZE + bp->b_dirtyoff; size = (u_quad_t)(bp->b_dirtyend - bp->b_dirtyoff); retv = nfs_commit(vp, off, (int)size, bp->b_wcred, p); if (retv) break; } } if (retv == NFSERR_STALEWRITEVERF) nfs_clearcommit(vp->v_mount); /* * Now, either mark the blocks I/O done or mark the * blocks dirty, depending on whether the commit * succeeded. */ for (i = 0; i < bvecpos; i++) { bp = bvec[i]; bp->b_flags &= ~(B_NEEDCOMMIT | B_WRITEINPROG); if (retv) { vfs_unbusy_pages(bp); brelse(bp); } else { vp->v_numoutput++; bp->b_flags |= B_ASYNC; bp->b_flags &= ~(B_READ|B_DONE|B_ERROR|B_DELWRI); bp->b_dirtyoff = bp->b_dirtyend = 0; reassignbuf(bp, vp); biodone(bp); } } } /* * Start/do any write(s) that are required. */ loop: s = splbio(); for (bp = vp->v_dirtyblkhd.lh_first; bp; bp = nbp) { nbp = bp->b_vnbufs.le_next; if (bp->b_flags & B_BUSY) { if (waitfor != MNT_WAIT || passone) continue; bp->b_flags |= B_WANTED; error = tsleep((caddr_t)bp, slpflag | (PRIBIO + 1), "nfsfsync", slptimeo); splx(s); if (error) { if (nfs_sigintr(nmp, (struct nfsreq *)0, p)) return (EINTR); if (slpflag == PCATCH) { slpflag = 0; slptimeo = 2 * hz; } } goto loop; } if ((bp->b_flags & B_DELWRI) == 0) panic("nfs_fsync: not dirty"); if ((passone || !commit) && (bp->b_flags & B_NEEDCOMMIT)) continue; bremfree(bp); if (passone || !commit) bp->b_flags |= (B_BUSY|B_ASYNC); else bp->b_flags |= (B_BUSY|B_ASYNC|B_WRITEINPROG|B_NEEDCOMMIT); splx(s); VOP_BWRITE(bp); goto loop; } splx(s); if (passone) { passone = 0; goto again; } if (waitfor == MNT_WAIT) { while (vp->v_numoutput) { vp->v_flag |= VBWAIT; error = tsleep((caddr_t)&vp->v_numoutput, slpflag | (PRIBIO + 1), "nfsfsync", slptimeo); if (error) { if (nfs_sigintr(nmp, (struct nfsreq *)0, p)) return (EINTR); if (slpflag == PCATCH) { slpflag = 0; slptimeo = 2 * hz; } } } if (vp->v_dirtyblkhd.lh_first && commit) { goto loop; } } if (np->n_flag & NWRITEERR) { error = np->n_error; np->n_flag &= ~NWRITEERR; } return (error); } /* * Return POSIX pathconf information applicable to nfs. * * The NFS V2 protocol doesn't support this, so just return EINVAL * for V2. */ /* ARGSUSED */ static int nfs_pathconf(ap) struct vop_pathconf_args /* { struct vnode *a_vp; int a_name; int *a_retval; } */ *ap; { return (EINVAL); } /* * NFS advisory byte-level locks. * Currently unsupported. */ static int nfs_advlock(ap) struct vop_advlock_args /* { struct vnode *a_vp; caddr_t a_id; int a_op; struct flock *a_fl; int a_flags; } */ *ap; { #ifdef __FreeBSD__ register struct nfsnode *np = VTONFS(ap->a_vp); /* * The following kludge is to allow diskless support to work * until a real NFS lockd is implemented. Basically, just pretend * that this is a local lock. */ return (lf_advlock(ap, &(np->n_lockf), np->n_size)); #else return (EOPNOTSUPP); #endif } /* * Print out the contents of an nfsnode. */ static int nfs_print(ap) struct vop_print_args /* { struct vnode *a_vp; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); printf("tag VT_NFS, fileid %ld fsid 0x%lx", np->n_vattr.va_fileid, np->n_vattr.va_fsid); if (vp->v_type == VFIFO) fifo_printinfo(vp); printf("\n"); return (0); } /* * NFS directory offset lookup. * Currently unsupported. */ static int nfs_blkatoff(ap) struct vop_blkatoff_args /* { struct vnode *a_vp; off_t a_offset; char **a_res; struct buf **a_bpp; } */ *ap; { return (EOPNOTSUPP); } /* * NFS flat namespace allocation. * Currently unsupported. */ static int nfs_valloc(ap) struct vop_valloc_args /* { struct vnode *a_pvp; int a_mode; struct ucred *a_cred; struct vnode **a_vpp; } */ *ap; { return (EOPNOTSUPP); } /* * NFS flat namespace free. * Currently unsupported. */ static int nfs_vfree(ap) struct vop_vfree_args /* { struct vnode *a_pvp; ino_t a_ino; int a_mode; } */ *ap; { return (EOPNOTSUPP); } /* * NFS file truncation. */ static int nfs_truncate(ap) struct vop_truncate_args /* { struct vnode *a_vp; off_t a_length; int a_flags; struct ucred *a_cred; struct proc *a_p; } */ *ap; { /* Use nfs_setattr */ printf("nfs_truncate: need to implement!!"); return (EOPNOTSUPP); } /* * NFS update. */ static int nfs_update(ap) struct vop_update_args /* { struct vnode *a_vp; struct timeval *a_ta; struct timeval *a_tm; int a_waitfor; } */ *ap; { #if 0 /* Use nfs_setattr */ printf("nfs_update: need to implement!!"); #endif return (EOPNOTSUPP); } /* * Just call nfs_writebp() with the force argument set to 1. */ static int nfs_bwrite(ap) struct vop_bwrite_args /* { struct vnode *a_bp; } */ *ap; { return (nfs_writebp(ap->a_bp, 1)); } /* * This is a clone of vn_bwrite(), except that B_WRITEINPROG isn't set unless * the force flag is one and it also handles the B_NEEDCOMMIT flag. */ int nfs_writebp(bp, force) register struct buf *bp; int force; { register int oldflags = bp->b_flags, retv = 1; off_t off; if(!(bp->b_flags & B_BUSY)) panic("bwrite: buffer is not busy???"); bp->b_flags &= ~(B_READ|B_DONE|B_ERROR|B_DELWRI); if ((oldflags & (B_ASYNC|B_DELWRI)) == (B_ASYNC|B_DELWRI)) { reassignbuf(bp, bp->b_vp); } bp->b_vp->v_numoutput++; curproc->p_stats->p_ru.ru_oublock++; /* * If B_NEEDCOMMIT is set, a commit rpc may do the trick. If not * an actual write will have to be scheduled via. VOP_STRATEGY(). * If B_WRITEINPROG is already set, then push it with a write anyhow. */ if (oldflags & (B_NEEDCOMMIT | B_WRITEINPROG) == B_NEEDCOMMIT) { off = ((u_quad_t)bp->b_blkno) * DEV_BSIZE + bp->b_dirtyoff; bp->b_flags |= B_WRITEINPROG; retv = nfs_commit(bp->b_vp, off, bp->b_dirtyend-bp->b_dirtyoff, bp->b_wcred, bp->b_proc); bp->b_flags &= ~B_WRITEINPROG; if (!retv) { bp->b_dirtyoff = bp->b_dirtyend = 0; bp->b_flags &= ~B_NEEDCOMMIT; biodone(bp); } else if (retv == NFSERR_STALEWRITEVERF) nfs_clearcommit(bp->b_vp->v_mount); } if (retv) { if (force) bp->b_flags |= B_WRITEINPROG; vfs_busy_pages(bp, 1); VOP_STRATEGY(bp); } if( (oldflags & B_ASYNC) == 0) { int rtval = biowait(bp); if (oldflags & B_DELWRI) { reassignbuf(bp, bp->b_vp); } brelse(bp); return (rtval); } return (0); } /* * nfs special file access vnode op. * Essentially just get vattr and then imitate iaccess() since the device is * local to the client. */ static int nfsspec_access(ap) struct vop_access_args /* { struct vnode *a_vp; int a_mode; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vattr *vap; register gid_t *gp; register struct ucred *cred = ap->a_cred; struct vnode *vp = ap->a_vp; mode_t mode = ap->a_mode; struct vattr vattr; register int i; int error; /* * Disallow write attempts on filesystems mounted read-only; * unless the file is a socket, fifo, or a block or character * device resident on the filesystem. */ if ((mode & VWRITE) && (vp->v_mount->mnt_flag & MNT_RDONLY)) { switch (vp->v_type) { case VREG: case VDIR: case VLNK: return (EROFS); } } /* * If you're the super-user, * you always get access. */ if (cred->cr_uid == 0) return (0); vap = &vattr; error = VOP_GETATTR(ap->a_vp, vap, cred, ap->a_p); if (error) return (error); /* * Access check is based on only one of owner, group, public. * If not owner, then check group. If not a member of the * group, then check public access. */ if (cred->cr_uid != vap->va_uid) { mode >>= 3; gp = cred->cr_groups; for (i = 0; i < cred->cr_ngroups; i++, gp++) if (vap->va_gid == *gp) goto found; mode >>= 3; found: ; } error = (vap->va_mode & mode) == mode ? 0 : EACCES; return (error); } /* * Read wrapper for special devices. */ static int nfsspec_read(ap) struct vop_read_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; struct ucred *a_cred; } */ *ap; { register struct nfsnode *np = VTONFS(ap->a_vp); /* * Set access flag. */ np->n_flag |= NACC; np->n_atim.tv_sec = time.tv_sec; np->n_atim.tv_nsec = time.tv_usec * 1000; return (VOCALL(spec_vnodeop_p, VOFFSET(vop_read), ap)); } /* * Write wrapper for special devices. */ static int nfsspec_write(ap) struct vop_write_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; struct ucred *a_cred; } */ *ap; { register struct nfsnode *np = VTONFS(ap->a_vp); /* * Set update flag. */ np->n_flag |= NUPD; np->n_mtim.tv_sec = time.tv_sec; np->n_mtim.tv_nsec = time.tv_usec * 1000; return (VOCALL(spec_vnodeop_p, VOFFSET(vop_write), ap)); } /* * Close wrapper for special devices. * * Update the times on the nfsnode then do device close. */ static int nfsspec_close(ap) struct vop_close_args /* { struct vnode *a_vp; int a_fflag; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); struct vattr vattr; if (np->n_flag & (NACC | NUPD)) { np->n_flag |= NCHG; if (vp->v_usecount == 1 && (vp->v_mount->mnt_flag & MNT_RDONLY) == 0) { VATTR_NULL(&vattr); if (np->n_flag & NACC) vattr.va_atime = np->n_atim; if (np->n_flag & NUPD) vattr.va_mtime = np->n_mtim; (void)VOP_SETATTR(vp, &vattr, ap->a_cred, ap->a_p); } } return (VOCALL(spec_vnodeop_p, VOFFSET(vop_close), ap)); } /* * Read wrapper for fifos. */ static int nfsfifo_read(ap) struct vop_read_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; struct ucred *a_cred; } */ *ap; { register struct nfsnode *np = VTONFS(ap->a_vp); /* * Set access flag. */ np->n_flag |= NACC; np->n_atim.tv_sec = time.tv_sec; np->n_atim.tv_nsec = time.tv_usec * 1000; return (VOCALL(fifo_vnodeop_p, VOFFSET(vop_read), ap)); } /* * Write wrapper for fifos. */ static int nfsfifo_write(ap) struct vop_write_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; struct ucred *a_cred; } */ *ap; { register struct nfsnode *np = VTONFS(ap->a_vp); /* * Set update flag. */ np->n_flag |= NUPD; np->n_mtim.tv_sec = time.tv_sec; np->n_mtim.tv_nsec = time.tv_usec * 1000; return (VOCALL(fifo_vnodeop_p, VOFFSET(vop_write), ap)); } /* * Close wrapper for fifos. * * Update the times on the nfsnode then do fifo close. */ static int nfsfifo_close(ap) struct vop_close_args /* { struct vnode *a_vp; int a_fflag; struct ucred *a_cred; struct proc *a_p; } */ *ap; { register struct vnode *vp = ap->a_vp; register struct nfsnode *np = VTONFS(vp); struct vattr vattr; if (np->n_flag & (NACC | NUPD)) { if (np->n_flag & NACC) { np->n_atim.tv_sec = time.tv_sec; np->n_atim.tv_nsec = time.tv_usec * 1000; } if (np->n_flag & NUPD) { np->n_mtim.tv_sec = time.tv_sec; np->n_mtim.tv_nsec = time.tv_usec * 1000; } np->n_flag |= NCHG; if (vp->v_usecount == 1 && (vp->v_mount->mnt_flag & MNT_RDONLY) == 0) { VATTR_NULL(&vattr); if (np->n_flag & NACC) vattr.va_atime = np->n_atim; if (np->n_flag & NUPD) vattr.va_mtime = np->n_mtim; (void)VOP_SETATTR(vp, &vattr, ap->a_cred, ap->a_p); } } return (VOCALL(fifo_vnodeop_p, VOFFSET(vop_close), ap)); } static int nfs_ioctl(ap) struct vop_ioctl_args *ap; { /* * XXX we were once bogusly enoictl() which returned this (ENOTTY). * Probably we should return ENODEV. */ return (ENOTTY); } static int nfs_select(ap) struct vop_select_args *ap; { /* * We were once bogusly seltrue() which returns 1. Is this right? */ return (1); }