1456 lines
35 KiB
C
1456 lines
35 KiB
C
/*
|
|
* Copyright 1998, Joerg Wunsch
|
|
* 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_rdp.c,v 1.1.1.1 1998/12/21 12:43:35 j Exp $
|
|
*/
|
|
|
|
/*
|
|
* Device driver for RealTek RTL 8002 (`REDP') based pocket-ethernet
|
|
* adapters, hooked up to a printer port. `rdp' is a shorthand for
|
|
* REDP since some tools like netstat work best if the interface name
|
|
* has no more than three letters.
|
|
*
|
|
* Driver configuration flags so far:
|
|
* flags 0x1 -- assume 74S288 EEPROM (default 94C46)
|
|
* flags 0x2 -- use `slow' mode (mode 3 of the packet driver, default 0)
|
|
*
|
|
* Maybe this driver will some day also work with the successor, RTL
|
|
* 8012 (`AREDP'), which is unfortunately not fully register-
|
|
* compatible with the 8002. The 8012 offers support for faster
|
|
* transfer modi like bidirectional SPP and EPP, 64 K x 4 buffer
|
|
* memory as opposed to 16 K x 4 for the 8002, a multicast filter, and
|
|
* a builtin multiplexer that allows chaining a printer behind the
|
|
* ethernet adapter.
|
|
*
|
|
* About the only documentation i've been able to find about the RTL
|
|
* 8002 was the packet driver source code at ftp.realtek.com.tw, so
|
|
* this driver is somewhat based on the way the packet driver handles
|
|
* the chip. The exact author of the packet driver is unknown, the
|
|
* only name that i could find in the source was someone called Chiu,
|
|
* supposedly an employee of RealTek. So credits to them for that
|
|
* piece of code which has proven valuable to me.
|
|
*
|
|
* Later on, Leo kuo <leo@realtek.com.tw> has been very helpful to me
|
|
* by sending me a readable (PDF) file documenting the RTL 8012, which
|
|
* helped me to also understand the 8002, as well as by providing me
|
|
* with the source code of the 8012 packet driver that i haven't been
|
|
* able to find on the FTP site. A big Thanks! goes here to RealTek
|
|
* for this kind of service.
|
|
*/
|
|
|
|
#include "rdp.h"
|
|
#include "bpfilter.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/syslog.h>
|
|
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_mib.h>
|
|
|
|
#ifdef INET
|
|
#include <netinet/in.h>
|
|
#include <netinet/if_ether.h>
|
|
#endif
|
|
|
|
#ifdef NS
|
|
#include <netns/ns.h>
|
|
#include <netns/ns_if.h>
|
|
#endif
|
|
|
|
#if NBPFILTER > 0
|
|
#include <net/bpf.h>
|
|
#endif
|
|
|
|
#include <machine/clock.h>
|
|
#include <machine/md_var.h>
|
|
|
|
#include <i386/isa/isa_device.h>
|
|
#include <i386/isa/icu.h>
|
|
#include <i386/isa/if_rdpreg.h>
|
|
#include <i386/isa/intr_machdep.h>
|
|
|
|
#define IOCTL_CMD_T u_long
|
|
|
|
/*
|
|
* Debug levels (ORed together):
|
|
* != 0 - general (bad packets etc.)
|
|
* 2 - debug EEPROM IO
|
|
* 4 - debug interrupt status
|
|
*/
|
|
#define DEBUG 0
|
|
|
|
/*
|
|
* rdp_softc: per interface info and status
|
|
*/
|
|
struct rdp_softc {
|
|
struct arpcom arpcom; /*
|
|
* Ethernet common, always goes first so
|
|
* a rdp_softc * can be cast into an
|
|
* arpcom * or into an ifnet *.
|
|
*/
|
|
|
|
/*
|
|
* local stuff, somewhat sorted by memory alignment class
|
|
*/
|
|
u_short baseaddr; /* IO port address */
|
|
u_short txsize; /* tx size for next (buffered) packet,
|
|
* there's only one additional packet
|
|
* we can buffer, thus a single variable
|
|
* ought to be enough */
|
|
int txbusy; /* tx is transmitting */
|
|
int txbuffered; /* # of packets in tx buffer */
|
|
int slow; /* use lpt_control to send data */
|
|
u_char irqenbit; /* mirror of current Ctrl_IRQEN */
|
|
/*
|
|
* type of parameter EEPROM; device flags 0x1 selects 74S288
|
|
*/
|
|
enum {
|
|
EEPROM_93C46, EEPROM_74S288 /* or 82S123 */
|
|
} eeprom;
|
|
};
|
|
|
|
static struct rdp_softc rdp_softc[NRDP];
|
|
|
|
/*
|
|
* Since there's no fixed location in the EEPROM about where to find
|
|
* the ethernet hardware address, we drop a table of valid OUIs here,
|
|
* and search through the EEPROM until we find a possible valid
|
|
* Ethernet address. Only the first 16 bits of all possible OUIs are
|
|
* recorded in the table (as obtained from
|
|
* http://standards.ieee.org/regauth/oui/oui.txt).
|
|
*/
|
|
|
|
static u_short allowed_ouis[] = {
|
|
0x0000, 0x0001, 0x0002, 0x0004, 0x0005, 0x0006, 0x0007,
|
|
0x0008, 0x0010, 0x001C, 0x0020, 0x0040, 0x0050, 0x0060,
|
|
0x0070, 0x0080, 0x0090, 0x009D, 0x00A0, 0x00AA, 0x00BB,
|
|
0x00C0, 0x00CF, 0x00DD, 0x00E0, 0x00E6, 0x0207, 0x021C,
|
|
0x0260, 0x0270, 0x029D, 0x02AA, 0x02BB, 0x02C0, 0x02CF,
|
|
0x02E6, 0x040A, 0x04E0, 0x0800, 0x08BB, 0x1000, 0x1100,
|
|
0x8000, 0xAA00
|
|
};
|
|
|
|
/*
|
|
* ISA bus support.
|
|
*/
|
|
static int rdp_probe __P((struct isa_device *));
|
|
static int rdp_attach __P((struct isa_device *));
|
|
|
|
/*
|
|
* Required entry points.
|
|
*/
|
|
static void rdp_init(void *);
|
|
static int rdp_ioctl(struct ifnet *, IOCTL_CMD_T, caddr_t);
|
|
static void rdp_start(struct ifnet *);
|
|
static void rdp_reset(struct ifnet *);
|
|
static void rdp_watchdog(struct ifnet *);
|
|
static void rdpintr(int);
|
|
|
|
/*
|
|
* REDP private functions.
|
|
*/
|
|
|
|
static void rdp_stop(struct rdp_softc *);
|
|
static void rdp_rint(struct rdp_softc *);
|
|
static void rdp_get_packet(struct rdp_softc *, unsigned);
|
|
static u_short rdp_write_mbufs(struct rdp_softc *, struct mbuf *);
|
|
static int rdp_gethwaddr_93c46(struct rdp_softc *, u_char *);
|
|
static void rdp_gethwaddr_74s288(struct rdp_softc *, u_char *);
|
|
static void rdp_93c46_cmd(struct rdp_softc *, u_short, unsigned);
|
|
static u_short rdp_93c46_read(struct rdp_softc *);
|
|
|
|
struct isa_driver rdpdriver = {
|
|
rdp_probe,
|
|
rdp_attach,
|
|
"rdp",
|
|
1 /* we wanna get a chance before lptN */
|
|
};
|
|
|
|
/*
|
|
* REDP-specific functions.
|
|
*
|
|
* They are inlined, thus go first in this file. Together with gcc's
|
|
* usual optimization, these functions probably come close to the
|
|
* packet driver's hand-optimized code. ;-)
|
|
*
|
|
* Comments are partially obtained from the packet driver as well.
|
|
* Some of the function names contain register names which don't make
|
|
* much sense for us, but i've kept them for easier reference in
|
|
* comparision to the packet driver.
|
|
*
|
|
* Some of the functions are currently not used by the driver; it's
|
|
* not quite clear whether we ever need them at all. They are
|
|
* supposedly even slower than what is currently implemented as `slow'
|
|
* mode. Right now, `fast' (default) mode is what the packet driver
|
|
* calls mode 0, slow mode is mode 3 (writing through lpt_control,
|
|
* reading twice).
|
|
*
|
|
* We should autoprobe the modi, as opposed to making them dependent
|
|
* on a kernel configuration flag.
|
|
*/
|
|
|
|
/*
|
|
* read a nibble from rreg; end-of-data cmd is not issued;
|
|
* used for general register read.
|
|
*
|
|
* Unlike the packet driver's version, i'm shifting the result
|
|
* by 3 here (as opposed to within the caller's code) for clarity.
|
|
* -- Joerg
|
|
*/
|
|
static __inline u_char
|
|
RdNib(struct rdp_softc *sc, u_char rreg)
|
|
{
|
|
|
|
outb(sc->baseaddr + lpt_data, EOC + rreg);
|
|
outb(sc->baseaddr + lpt_data, RdAddr + rreg); /* write addr */
|
|
(void)inb(sc->baseaddr + lpt_status);
|
|
return (inb(sc->baseaddr + lpt_status) >> 3) & 0x0f;
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* read a byte from MAR register through lpt_data; the low nibble is
|
|
* read prior to the high one; end-of-read command is not issued; used
|
|
* for remote DMA in mode 4 + 5
|
|
*/
|
|
static __inline u_char
|
|
RdByte(struct rdp_softc *sc)
|
|
{
|
|
u_char hinib, lonib;
|
|
|
|
outb(sc->baseaddr + lpt_data, RdAddr + MAR); /* cmd for low nibble */
|
|
lonib = (inb(sc->baseaddr + lpt_status) >> 3) & 0x0f;
|
|
outb(sc->baseaddr + lpt_data, RdAddr + MAR + HNib);
|
|
hinib = (inb(sc->baseaddr + lpt_status) << 1) & 0xf0;
|
|
return hinib + lonib;
|
|
}
|
|
|
|
|
|
/*
|
|
* read a byte from MAR register through lpt_data; the low nibble is
|
|
* read prior to the high one; end-of-read command is not issued; used
|
|
* for remote DMA in mode 6 + 7
|
|
*/
|
|
static __inline u_char
|
|
RdByte1(struct rdp_softc *sc)
|
|
{
|
|
u_char hinib, lonib;
|
|
|
|
outb(sc->baseaddr + lpt_data, RdAddr + MAR); /* cmd for low nibble */
|
|
(void)inb(sc->baseaddr + lpt_status);
|
|
lonib = (inb(sc->baseaddr + lpt_status) >> 3) & 0x0f;
|
|
outb(sc->baseaddr + lpt_data, RdAddr + MAR + HNib);
|
|
(void)inb(sc->baseaddr + lpt_status);
|
|
hinib = (inb(sc->baseaddr + lpt_status) << 1) & 0xf0;
|
|
return hinib + lonib;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* read a byte from MAR register through lpt_control; the low nibble is
|
|
* read prior to the high one; end-of-read command is not issued; used
|
|
* for remote DMA in mode 0 + 1
|
|
*/
|
|
static __inline u_char
|
|
RdByteA1(struct rdp_softc *sc)
|
|
{
|
|
u_char hinib, lonib;
|
|
|
|
outb(sc->baseaddr + lpt_control, Ctrl_LNibRead);
|
|
lonib = (inb(sc->baseaddr + lpt_status) >> 3) & 0x0f;
|
|
outb(sc->baseaddr + lpt_control, Ctrl_HNibRead);
|
|
hinib = (inb(sc->baseaddr + lpt_status) << 1) & 0xf0;
|
|
return hinib + lonib;
|
|
}
|
|
|
|
|
|
/*
|
|
* read a byte from MAR register through lpt_control; the low nibble is
|
|
* read prior to the high one; end-of-read command is not issued; used
|
|
* for remote DMA in mode 2 + 3
|
|
*/
|
|
static __inline u_char
|
|
RdByteA2(struct rdp_softc *sc)
|
|
{
|
|
u_char hinib, lonib;
|
|
|
|
outb(sc->baseaddr + lpt_control, Ctrl_LNibRead);
|
|
(void)inb(sc->baseaddr + lpt_status);
|
|
lonib = (inb(sc->baseaddr + lpt_status) >> 3) & 0x0f;
|
|
outb(sc->baseaddr + lpt_control, Ctrl_HNibRead);
|
|
(void)inb(sc->baseaddr + lpt_status);
|
|
hinib = (inb(sc->baseaddr + lpt_status) << 1) & 0xf0;
|
|
return hinib + lonib;
|
|
}
|
|
|
|
/*
|
|
* End-of-read cmd
|
|
*/
|
|
static __inline void
|
|
RdEnd(struct rdp_softc *sc, u_char rreg)
|
|
{
|
|
|
|
outb(sc->baseaddr + lpt_data, EOC + rreg);
|
|
}
|
|
|
|
/*
|
|
* Write a nibble to a register; end-of-write is issued.
|
|
* Used for general register write.
|
|
*/
|
|
static __inline void
|
|
WrNib(struct rdp_softc *sc, u_char wreg, u_char wdata)
|
|
{
|
|
|
|
/* prepare and write address */
|
|
outb(sc->baseaddr + lpt_data, EOC + wreg);
|
|
outb(sc->baseaddr + lpt_data, WrAddr + wreg);
|
|
outb(sc->baseaddr + lpt_data, WrAddr + wreg);
|
|
/* prepare and write data */
|
|
outb(sc->baseaddr + lpt_data, WrAddr + wdata);
|
|
outb(sc->baseaddr + lpt_data, wdata);
|
|
outb(sc->baseaddr + lpt_data, wdata);
|
|
/* end-of-write */
|
|
outb(sc->baseaddr + lpt_data, EOC + wdata);
|
|
}
|
|
|
|
/*
|
|
* Write a byte to a register; end-of-write is issued.
|
|
* Used for general register write.
|
|
*/
|
|
static __inline void
|
|
WrByte(struct rdp_softc *sc, u_char wreg, u_char wdata)
|
|
{
|
|
|
|
/* prepare and write address */
|
|
outb(sc->baseaddr + lpt_data, EOC + wreg);
|
|
outb(sc->baseaddr + lpt_data, WrAddr + wreg);
|
|
outb(sc->baseaddr + lpt_data, WrAddr + wreg);
|
|
/* prepare and write low nibble */
|
|
outb(sc->baseaddr + lpt_data, WrAddr + (wdata & 0x0F));
|
|
outb(sc->baseaddr + lpt_data, (wdata & 0x0F));
|
|
outb(sc->baseaddr + lpt_data, (wdata & 0x0F));
|
|
/* prepare and write high nibble */
|
|
wdata >>= 4;
|
|
outb(sc->baseaddr + lpt_data, wdata);
|
|
outb(sc->baseaddr + lpt_data, wdata + HNib);
|
|
outb(sc->baseaddr + lpt_data, wdata + HNib);
|
|
/* end-of-write */
|
|
outb(sc->baseaddr + lpt_data, EOC + wdata + HNib);
|
|
}
|
|
|
|
/*
|
|
* Write the byte to DRAM via lpt_data;
|
|
* used for remote DMA write in mode 0 / 2 / 4
|
|
*/
|
|
static __inline void
|
|
WrByteALToDRAM(struct rdp_softc *sc, u_char val)
|
|
{
|
|
|
|
outb(sc->baseaddr + lpt_data, val & 0x0F);
|
|
outb(sc->baseaddr + lpt_data, MkHi(val));
|
|
}
|
|
|
|
/*
|
|
* Write the byte to DRAM via lpt_control;
|
|
* used for remote DMA write in mode 1 / 3 / 5
|
|
*/
|
|
static __inline void
|
|
WrByteALToDRAMA(struct rdp_softc *sc, u_char val)
|
|
{
|
|
|
|
outb(sc->baseaddr + lpt_data, val & 0x0F);
|
|
outb(sc->baseaddr + lpt_control, Ctrl_LNibRead | sc->irqenbit);
|
|
outb(sc->baseaddr + lpt_data, val >> 4);
|
|
outb(sc->baseaddr + lpt_control, Ctrl_HNibRead | sc->irqenbit);
|
|
}
|
|
|
|
#if 0 /* they could be used for the RAM test */
|
|
/*
|
|
* Write the u_short to DRAM via lpt_data;
|
|
* used for remote DMA write in mode 0 / 2 / 4
|
|
*/
|
|
static __inline void
|
|
WrWordbxToDRAM(struct rdp_softc *sc, u_short val)
|
|
{
|
|
|
|
outb(sc->baseaddr + lpt_data, val & 0x0F);
|
|
val >>= 4;
|
|
outb(sc->baseaddr + lpt_data, (val & 0x0F) + HNib);
|
|
val >>= 4;
|
|
outb(sc->baseaddr + lpt_data, val & 0x0F);
|
|
val >>= 4;
|
|
outb(sc->baseaddr + lpt_data, val + HNib);
|
|
}
|
|
|
|
|
|
/*
|
|
* Write the u_short to DRAM via lpt_control;
|
|
* used for remote DMA write in mode 1 / 3 / 5
|
|
*/
|
|
static __inline void
|
|
WrWordbxToDRAMA(struct rdp_softc *sc, u_short val)
|
|
{
|
|
|
|
outb(sc->baseaddr + lpt_data, val & 0x0F);
|
|
outb(sc->baseaddr + lpt_control, Ctrl_LNibRead | sc->irqenbit);
|
|
val >>= 4;
|
|
outb(sc->baseaddr + lpt_data, (val & 0x0F) + HNib);
|
|
outb(sc->baseaddr + lpt_control, Ctrl_HNibRead | sc->irqenbit);
|
|
val >>= 4;
|
|
outb(sc->baseaddr + lpt_data, val & 0x0F);
|
|
outb(sc->baseaddr + lpt_control, Ctrl_LNibRead | sc->irqenbit);
|
|
val >>= 4;
|
|
outb(sc->baseaddr + lpt_data, val + HNib);
|
|
outb(sc->baseaddr + lpt_control, Ctrl_HNibRead | sc->irqenbit);
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Determine if the device is present
|
|
*
|
|
* on entry:
|
|
* a pointer to an isa_device struct
|
|
* on exit:
|
|
* 0 if device not found
|
|
* or # of i/o addresses used (if found)
|
|
*/
|
|
static int
|
|
rdp_probe(struct isa_device *isa_dev)
|
|
{
|
|
int unit = isa_dev->id_unit;
|
|
struct rdp_softc *sc = &rdp_softc[unit];
|
|
u_char b1, b2;
|
|
intrmask_t irqmap[3];
|
|
u_char sval[3];
|
|
|
|
if (unit < 0 || unit >= NRDP)
|
|
return 0;
|
|
|
|
sc->baseaddr = isa_dev->id_iobase;
|
|
if (isa_dev->id_flags & 1)
|
|
sc->eeprom = EEPROM_74S288;
|
|
/* else defaults to 93C46 */
|
|
if (isa_dev->id_flags & 2)
|
|
sc->slow = 1;
|
|
|
|
/* let R/WB = A/DB = CSB = high to be ready for next r/w cycle */
|
|
outb(sc->baseaddr + lpt_data, 0xFF);
|
|
/* DIR = 0 for write mode, IRQEN=0, SLCT=INIT=AUTOFEED=STB=high */
|
|
outb(sc->baseaddr + lpt_control, Ctrl_SelData);
|
|
/* software reset */
|
|
WrNib(sc, CMR1 + HNib, MkHi(CMR1_RST));
|
|
DELAY(2000);
|
|
/* is EPLC alive? */
|
|
b1 = RdNib(sc, CMR1);
|
|
RdEnd(sc, CMR1);
|
|
b2 = RdNib(sc, CMR2) & 0x0f;
|
|
b2 |= RdNib(sc, CMR2 + HNib) << 4;
|
|
RdEnd(sc, CMR2 + HNib);
|
|
/*
|
|
* After the reset, we expect CMR1 & 7 to be 1 (rx buffer empty),
|
|
* and CMR2 & 0xf7 to be 0x20 (receive mode set to physical and
|
|
* broadcasts).
|
|
*/
|
|
if (bootverbose)
|
|
printf("rdp%d: CMR1 = %#x, CMR2 = %#x\n", unit, b1, b2);
|
|
|
|
if ((b1 & (CMR1_BUFE | CMR1_IRQ | CMR1_TRA)) != CMR1_BUFE
|
|
|| (b2 & ~CMR2_IRQINV) != CMR2_AM_PB)
|
|
return 0;
|
|
|
|
/*
|
|
* We have found something that could be a RTL 80[01]2, now
|
|
* see whether we can generate an interrupt.
|
|
*/
|
|
disable_intr();
|
|
|
|
/*
|
|
* Test whether our configured IRQ is working.
|
|
*
|
|
* Set to no acception mode + IRQout, then enable RxE + TxE,
|
|
* then cause RBER (by advancing the read pointer although
|
|
* the read buffer is empty) to generate an interrupt.
|
|
*/
|
|
WrByte(sc, CMR2, CMR2_IRQOUT);
|
|
WrNib(sc, CMR1 + HNib, MkHi(CMR1_TE | CMR1_RE));
|
|
WrNib(sc, CMR1, CMR1_RDPAC);
|
|
DELAY(1000);
|
|
|
|
irqmap[0] = isa_irq_pending();
|
|
sval[0] = inb(sc->baseaddr + lpt_status);
|
|
|
|
/* allow IRQs to pass the parallel interface */
|
|
outb(sc->baseaddr + lpt_control, Ctrl_IRQEN + Ctrl_SelData);
|
|
DELAY(1000);
|
|
/* generate interrupt */
|
|
WrNib(sc, IMR + HNib, MkHi(ISR_RBER));
|
|
DELAY(1000);
|
|
|
|
irqmap[1] = isa_irq_pending();
|
|
sval[1] = inb(sc->baseaddr + lpt_status);
|
|
|
|
/* de-assert and disable IRQ */
|
|
WrNib(sc, IMR + HNib, MkHi(0));
|
|
(void)inb(sc->baseaddr + lpt_status); /* might be necessary to
|
|
clear IRQ */
|
|
DELAY(1000);
|
|
irqmap[2] = isa_irq_pending();
|
|
sval[2] = inb(sc->baseaddr + lpt_status);
|
|
|
|
WrNib(sc, CMR1 + HNib, MkHi(0));
|
|
outb(sc->baseaddr + lpt_control, Ctrl_SelData);
|
|
WrNib(sc, CMR2, CMR2_IRQINV);
|
|
|
|
enable_intr();
|
|
|
|
if (bootverbose)
|
|
printf("rdp%d: irq maps / lpt status "
|
|
"%#x/%#x - %#x/%#x - %#x/%#x (id_irq %#x)\n",
|
|
unit, irqmap[0], sval[0], irqmap[1], sval[1],
|
|
irqmap[2], sval[2], isa_dev->id_irq);
|
|
|
|
if ((irqmap[1] & isa_dev->id_irq) == 0) {
|
|
printf("rdp%d: configured IRQ (%d) cannot be asserted "
|
|
"by device",
|
|
unit, ffs(isa_dev->id_irq) - 1);
|
|
if (irqmap[1])
|
|
printf(" (probable IRQ: %d)", ffs(irqmap[1]) - 1);
|
|
printf("\n");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* XXX should do RAMtest here
|
|
*/
|
|
|
|
switch (sc->eeprom) {
|
|
case EEPROM_93C46:
|
|
if (rdp_gethwaddr_93c46(sc, sc->arpcom.ac_enaddr) == 0) {
|
|
printf("rdp%d: failed to find a valid hardware "
|
|
"address in EEPROM\n",
|
|
unit);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case EEPROM_74S288:
|
|
rdp_gethwaddr_74s288(sc, sc->arpcom.ac_enaddr);
|
|
break;
|
|
}
|
|
|
|
return lpt_control + 1;
|
|
}
|
|
|
|
/*
|
|
* Install interface into kernel networking data structures
|
|
*/
|
|
static int
|
|
rdp_attach(struct isa_device *isa_dev)
|
|
{
|
|
int unit = isa_dev->id_unit;
|
|
struct rdp_softc *sc = &rdp_softc[unit];
|
|
struct ifnet *ifp = &sc->arpcom.ac_if;
|
|
|
|
isa_dev->id_ointr = rdpintr;
|
|
|
|
/*
|
|
* Reset interface
|
|
*/
|
|
rdp_stop(sc);
|
|
|
|
if (!ifp->if_name) {
|
|
/*
|
|
* Initialize ifnet structure
|
|
*/
|
|
ifp->if_softc = sc;
|
|
ifp->if_unit = unit;
|
|
ifp->if_name = "rdp";
|
|
ifp->if_output = ether_output;
|
|
ifp->if_start = rdp_start;
|
|
ifp->if_ioctl = rdp_ioctl;
|
|
ifp->if_watchdog = rdp_watchdog;
|
|
ifp->if_init = rdp_init;
|
|
ifp->if_snd.ifq_maxlen = IFQ_MAXLEN;
|
|
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX;
|
|
|
|
/*
|
|
* Attach the interface
|
|
*/
|
|
if_attach(ifp);
|
|
ether_ifattach(ifp);
|
|
}
|
|
|
|
/*
|
|
* Print additional info when attached
|
|
*/
|
|
printf("%s%d: RealTek RTL%s pocket ethernet, EEPROM %s, %s mode\n",
|
|
ifp->if_name, ifp->if_unit,
|
|
"8002", /* hook for 8012 */
|
|
sc->eeprom == EEPROM_93C46? "93C46": "74S288",
|
|
sc->slow? "slow": "fast");
|
|
printf("%s%d: address %6D\n", ifp->if_name, ifp->if_unit,
|
|
sc->arpcom.ac_enaddr, ":");
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* Reset interface.
|
|
*/
|
|
static void
|
|
rdp_reset(struct ifnet *ifp)
|
|
{
|
|
struct rdp_softc *sc = ifp->if_softc;
|
|
int s;
|
|
|
|
s = splimp();
|
|
|
|
/*
|
|
* Stop interface and re-initialize.
|
|
*/
|
|
rdp_stop(sc);
|
|
rdp_init(sc);
|
|
|
|
(void) splx(s);
|
|
}
|
|
|
|
/*
|
|
* Take interface offline.
|
|
*/
|
|
static void
|
|
rdp_stop(struct rdp_softc *sc)
|
|
{
|
|
|
|
sc->txbusy = sc->txbusy = 0;
|
|
|
|
/* disable printer interface interrupts */
|
|
sc->irqenbit = 0;
|
|
outb(sc->baseaddr + lpt_control, Ctrl_SelData);
|
|
outb(sc->baseaddr + lpt_data, 0xff);
|
|
|
|
/* reset the RTL 8002 */
|
|
WrNib(sc, CMR1 + HNib, MkHi(CMR1_RST));
|
|
DELAY(100);
|
|
}
|
|
|
|
/*
|
|
* Device timeout/watchdog routine. Entered if the device neglects to
|
|
* generate an interrupt after a transmit has been started on it.
|
|
*/
|
|
static void
|
|
rdp_watchdog(struct ifnet *ifp)
|
|
{
|
|
|
|
log(LOG_ERR, "rdp%d: device timeout\n", ifp->if_unit);
|
|
ifp->if_oerrors++;
|
|
|
|
rdp_reset(ifp);
|
|
}
|
|
|
|
/*
|
|
* Initialize device.
|
|
*/
|
|
static void
|
|
rdp_init(void *xsc)
|
|
{
|
|
struct rdp_softc *sc = xsc;
|
|
struct ifnet *ifp = &sc->arpcom.ac_if;
|
|
int i, s;
|
|
u_char reg;
|
|
|
|
/* address not known */
|
|
if (TAILQ_EMPTY(&ifp->if_addrhead))
|
|
return;
|
|
|
|
s = splimp();
|
|
|
|
ifp->if_timer = 0;
|
|
|
|
/* program ethernet ID into the chip */
|
|
for (i = 0, reg = IDR0; i < 6; i++, reg++)
|
|
WrByte(sc, reg, sc->arpcom.ac_enaddr[i]);
|
|
|
|
/* set accept mode */
|
|
WrNib(sc, CMR2 + HNib,
|
|
MkHi((ifp->if_flags & IFF_PROMISC)? CMR2_AM_ALL: CMR2_AM_PB));
|
|
|
|
/* enable tx and rx */
|
|
WrNib(sc, CMR1 + HNib, MkHi(CMR1_TE | CMR1_RE));
|
|
|
|
/* allow interrupts to happen */
|
|
WrNib(sc, CMR2, CMR2_IRQOUT | CMR2_IRQINV);
|
|
WrNib(sc, IMR, ISR_TOK | ISR_TER | ISR_ROK | ISR_RER);
|
|
WrNib(sc, IMR + HNib, MkHi(ISR_RBER));
|
|
|
|
/* allow IRQs to pass the parallel interface */
|
|
sc->irqenbit = Ctrl_IRQEN;
|
|
outb(sc->baseaddr + lpt_control, sc->irqenbit + Ctrl_SelData);
|
|
|
|
/* clear all flags */
|
|
sc->txbusy = sc->txbuffered = 0;
|
|
|
|
/*
|
|
* Set 'running' flag, and clear output active flag.
|
|
*/
|
|
ifp->if_flags |= IFF_RUNNING;
|
|
ifp->if_flags &= ~IFF_OACTIVE;
|
|
|
|
/*
|
|
* ...and attempt to start output
|
|
*/
|
|
rdp_start(ifp);
|
|
|
|
(void) splx(s);
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
rdp_start(struct ifnet *ifp)
|
|
{
|
|
struct rdp_softc *sc = ifp->if_softc;
|
|
struct mbuf *m;
|
|
int len;
|
|
|
|
outloop:
|
|
|
|
/*
|
|
* See if there is room to put another packet in the buffer.
|
|
*/
|
|
if (sc->txbuffered) {
|
|
/*
|
|
* 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
|
|
*/
|
|
|
|
len = rdp_write_mbufs(sc, m);
|
|
if (len == 0)
|
|
goto outloop;
|
|
|
|
/* ensure minimal valid ethernet length */
|
|
len = max(len, (ETHER_MIN_LEN-ETHER_CRC_LEN));
|
|
|
|
/*
|
|
* Actually start the transceiver. Set a timeout in case the
|
|
* Tx interrupt never arrives.
|
|
*/
|
|
if (!sc->txbusy) {
|
|
WrNib(sc, TBCR1, len >> 8);
|
|
WrByte(sc, TBCR0, len & 0xff);
|
|
WrNib(sc, CMR1, CMR1_TRA);
|
|
sc->txbusy = 1;
|
|
ifp->if_timer = 2;
|
|
} else {
|
|
sc->txbuffered = 1;
|
|
sc->txsize = len;
|
|
}
|
|
|
|
/*
|
|
* Tap off here if there is a bpf listener.
|
|
*/
|
|
#if NBPFILTER > 0
|
|
if (ifp->if_bpf) {
|
|
bpf_mtap(ifp, m);
|
|
}
|
|
#endif
|
|
|
|
m_freem(m);
|
|
|
|
/*
|
|
* Loop back to the top to possibly buffer more packets
|
|
*/
|
|
goto outloop;
|
|
}
|
|
|
|
/*
|
|
* Process an ioctl request.
|
|
*/
|
|
static int
|
|
rdp_ioctl(struct ifnet *ifp, IOCTL_CMD_T command, caddr_t data)
|
|
{
|
|
struct rdp_softc *sc = ifp->if_softc;
|
|
int s, error = 0;
|
|
|
|
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)
|
|
rdp_init(sc);
|
|
} else {
|
|
if (ifp->if_flags & IFF_RUNNING) {
|
|
rdp_stop(sc);
|
|
ifp->if_flags &= ~IFF_RUNNING;
|
|
}
|
|
}
|
|
|
|
#if NBPFILTER > 0
|
|
/*
|
|
* Promiscuous flag may have changed, propagage this
|
|
* to the NIC.
|
|
*/
|
|
if (ifp->if_flags & IFF_UP)
|
|
WrNib(sc, CMR2 + HNib,
|
|
MkHi((ifp->if_flags & IFF_PROMISC)?
|
|
CMR2_AM_ALL: CMR2_AM_PB));
|
|
|
|
#endif
|
|
break;
|
|
|
|
case SIOCADDMULTI:
|
|
case SIOCDELMULTI:
|
|
/*
|
|
* Multicast list has changed; we don't support it.
|
|
*/
|
|
error = ENOTTY;
|
|
break;
|
|
|
|
default:
|
|
error = EINVAL;
|
|
}
|
|
(void) splx(s);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* External interrupt service routine.
|
|
*/
|
|
void
|
|
rdpintr(int unit)
|
|
{
|
|
struct rdp_softc *sc = rdp_softc + unit;
|
|
struct ifnet *ifp = (struct ifnet *)sc;
|
|
u_char isr, tsr, rsr, colls;
|
|
|
|
/* disable interrupts, so SD3 can be routed to the pin */
|
|
sc->irqenbit = 0;
|
|
outb(sc->baseaddr + lpt_control, Ctrl_SelData);
|
|
WrNib(sc, CMR2, CMR2_IRQINV);
|
|
/*
|
|
* loop until there are no more new interrupts
|
|
*/
|
|
for (;;) {
|
|
isr = RdNib(sc, ISR);
|
|
isr |= RdNib(sc, ISR + HNib) << 4;
|
|
RdEnd(sc, ISR + HNib);
|
|
|
|
if (isr == 0)
|
|
break;
|
|
#if DEBUG & 4
|
|
printf("rdp%d: ISR = %#x\n", unit, isr);
|
|
#endif
|
|
|
|
/*
|
|
* Clear the pending interrupt bits.
|
|
*/
|
|
WrNib(sc, ISR, isr & 0x0f);
|
|
if (isr & 0xf0)
|
|
WrNib(sc, ISR + HNib, MkHi(isr));
|
|
|
|
/*
|
|
* Handle transmitter interrupts.
|
|
*/
|
|
if (isr & (ISR_TOK | ISR_TER)) {
|
|
tsr = RdNib(sc, TSR);
|
|
RdEnd(sc, TSR);
|
|
#if DEBUG & 4
|
|
if (isr & ISR_TER)
|
|
printf("rdp%d: tsr %#x\n", unit, tsr);
|
|
#endif
|
|
if (tsr & TSR_TABT)
|
|
ifp->if_oerrors++;
|
|
else
|
|
/*
|
|
* Update total number of successfully
|
|
* transmitted packets.
|
|
*/
|
|
ifp->if_opackets++;
|
|
|
|
if (tsr & TSR_COL) {
|
|
colls = RdNib(sc, COLR);
|
|
RdEnd(sc, COLR);
|
|
ifp->if_collisions += colls;
|
|
}
|
|
|
|
/*
|
|
* reset tx busy and output active flags
|
|
*/
|
|
sc->txbusy = 0;
|
|
ifp->if_flags &= ~IFF_OACTIVE;
|
|
|
|
/*
|
|
* If we had already queued up another packet,
|
|
* start sending it now.
|
|
*/
|
|
if (sc->txbuffered) {
|
|
WrNib(sc, TBCR1, sc->txsize >> 8);
|
|
WrByte(sc, TBCR0, sc->txsize & 0xff);
|
|
WrNib(sc, CMR1, CMR1_TRA);
|
|
sc->txbusy = 1;
|
|
sc->txbuffered = 0;
|
|
ifp->if_timer = 2;
|
|
} else {
|
|
/*
|
|
* clear watchdog timer
|
|
*/
|
|
ifp->if_timer = 0;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Handle receiver interrupts
|
|
*/
|
|
if (isr & (ISR_ROK | ISR_RER | ISR_RBER)) {
|
|
rsr = RdNib(sc, RSR);
|
|
rsr |= RdNib(sc, RSR + HNib) << 4;
|
|
RdEnd(sc, RSR + HNib);
|
|
#if DEBUG & 4
|
|
if (isr & (ISR_RER | ISR_RBER))
|
|
printf("rdp%d: rsr %#x\n", unit, rsr);
|
|
#endif
|
|
|
|
if (rsr & (RSR_PUN | RSR_POV)) {
|
|
printf("rdp%d: rsr %#x, resetting\n",
|
|
unit, rsr);
|
|
rdp_reset(ifp);
|
|
break;
|
|
}
|
|
|
|
if (rsr & RSR_BUFO)
|
|
/*
|
|
* CRC and FA errors are recorded in
|
|
* rdp_rint() on a per-packet basis
|
|
*/
|
|
ifp->if_ierrors++;
|
|
if (isr & (ISR_ROK | ISR_RER))
|
|
rdp_rint(sc);
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
rdp_start(ifp);
|
|
|
|
}
|
|
/* re-enable interrupts */
|
|
WrNib(sc, CMR2, CMR2_IRQOUT | CMR2_IRQINV);
|
|
sc->irqenbit = Ctrl_IRQEN;
|
|
outb(sc->baseaddr + lpt_control, Ctrl_SelData + sc->irqenbit);
|
|
}
|
|
|
|
/*
|
|
* Ethernet interface receiver interrupt.
|
|
*/
|
|
static void
|
|
rdp_rint(struct rdp_softc *sc)
|
|
{
|
|
struct ifnet *ifp = &sc->arpcom.ac_if;
|
|
struct rdphdr rh;
|
|
u_short len;
|
|
size_t i;
|
|
u_char *packet_ptr, b, status;
|
|
int excessive_bad_pkts = 0;
|
|
|
|
/*
|
|
* Fetch the packets from the NIC's buffer.
|
|
*/
|
|
for (;;) {
|
|
b = RdNib(sc, CMR1);
|
|
RdEnd(sc, CMR1);
|
|
|
|
if (b & CMR1_BUFE)
|
|
/* no more packets */
|
|
break;
|
|
|
|
/* first, obtain the buffer header */
|
|
|
|
outb(sc->baseaddr + lpt_data, MAR + EOC); /* prepare addr */
|
|
outb(sc->baseaddr + lpt_control, Ctrl_LNibRead);
|
|
outb(sc->baseaddr + lpt_data, MAR + RdAddr + HNib);
|
|
|
|
packet_ptr = (u_char *)&rh;
|
|
if (sc->slow)
|
|
for (i = 0; i < sizeof rh; i++, packet_ptr++)
|
|
*packet_ptr = RdByteA2(sc);
|
|
else
|
|
for (i = 0; i < sizeof rh; i++, packet_ptr++)
|
|
*packet_ptr = RdByteA1(sc);
|
|
|
|
RdEnd(sc, MAR + HNib);
|
|
outb(sc->baseaddr + lpt_control, Ctrl_SelData);
|
|
|
|
len = rh.pktlen - ETHER_CRC_LEN;
|
|
status = rh.status;
|
|
|
|
if ((status & (RSR_ROK | RSR_CRC | RSR_FA)) != RSR_ROK ||
|
|
len > (ETHER_MAX_LEN - ETHER_CRC_LEN) ||
|
|
len < (ETHER_MIN_LEN - ETHER_CRC_LEN) ||
|
|
len > MCLBYTES) {
|
|
#if DEBUG
|
|
printf("rdp%d: bad packet in buffer, "
|
|
"len %d, status %#x\n",
|
|
ifp->if_unit, (int)len, (int)status);
|
|
#endif
|
|
ifp->if_ierrors++;
|
|
/* rx jump packet */
|
|
WrNib(sc, CMR1, CMR1_RDPAC);
|
|
if (++excessive_bad_pkts > 5) {
|
|
/*
|
|
* the chip seems to be stuck, we are
|
|
* probably seeing the same bad packet
|
|
* over and over again
|
|
*/
|
|
#if DEBUG
|
|
printf("rdp%d: resetting due to an "
|
|
"excessive number of bad packets\n",
|
|
ifp->if_unit);
|
|
#endif
|
|
rdp_reset(ifp);
|
|
return;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Go get packet.
|
|
*/
|
|
excessive_bad_pkts = 0;
|
|
rdp_get_packet(sc, len);
|
|
ifp->if_ipackets++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Retreive packet from NIC 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
|
|
rdp_get_packet(struct rdp_softc *sc, unsigned len)
|
|
{
|
|
struct ether_header *eh;
|
|
struct mbuf *m;
|
|
u_char *packet_ptr;
|
|
size_t s;
|
|
|
|
/* 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.
|
|
*/
|
|
outb(sc->baseaddr + lpt_control, Ctrl_LNibRead);
|
|
outb(sc->baseaddr + lpt_data, RdAddr + MAR);
|
|
|
|
packet_ptr = (u_char *)eh;
|
|
if (sc->slow)
|
|
for (s = 0; s < len; s++, packet_ptr++)
|
|
*packet_ptr = RdByteA2(sc);
|
|
else
|
|
for (s = 0; s < len; s++, packet_ptr++)
|
|
*packet_ptr = RdByteA1(sc);
|
|
|
|
RdEnd(sc, MAR + HNib);
|
|
outb(sc->baseaddr + lpt_control, Ctrl_SelData);
|
|
WrNib(sc, CMR1, CMR1_RDPAC);
|
|
|
|
#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) {
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Write an mbuf chain to the NIC's tx buffer.
|
|
*/
|
|
static u_short
|
|
rdp_write_mbufs(struct rdp_softc *sc, struct mbuf *m)
|
|
{
|
|
u_short total_len;
|
|
struct mbuf *mp;
|
|
u_char *dp, b;
|
|
int i;
|
|
|
|
/* 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;
|
|
|
|
if (total_len == 0)
|
|
return 0;
|
|
|
|
outb(sc->baseaddr + lpt_data, MAR | EOC);
|
|
|
|
/*
|
|
* Transfer the mbuf chain to the NIC memory.
|
|
*/
|
|
if (sc->slow) {
|
|
/* writing the first byte is complicated */
|
|
outb(sc->baseaddr + lpt_control,
|
|
Ctrl_LNibRead | sc->irqenbit);
|
|
outb(sc->baseaddr + lpt_data, MAR | WrAddr);
|
|
b = *(u_char *)m->m_data;
|
|
outb(sc->baseaddr + lpt_data, (b & 0x0f) | 0x40);
|
|
outb(sc->baseaddr + lpt_data, b & 0x0f);
|
|
outb(sc->baseaddr + lpt_data, b >> 4);
|
|
outb(sc->baseaddr + lpt_control,
|
|
Ctrl_HNibRead | sc->irqenbit);
|
|
/* advance the mbuf pointer */
|
|
mp = m;
|
|
m->m_len--;
|
|
m->m_data++;
|
|
/* write the remaining bytes */
|
|
while (m) {
|
|
for (i = 0, dp = (u_char *)m->m_data;
|
|
i < m->m_len;
|
|
i++, dp++)
|
|
WrByteALToDRAMA(sc, *dp);
|
|
m = m->m_next;
|
|
}
|
|
/*
|
|
* restore old mbuf in case we have to hand it off to
|
|
* BPF again
|
|
*/
|
|
m = mp;
|
|
m->m_len++;
|
|
m->m_data--;
|
|
|
|
/* the RTL 8002 requires an even byte-count remote DMA */
|
|
if (total_len & 1)
|
|
WrByteALToDRAMA(sc, 0);
|
|
} else {
|
|
outb(sc->baseaddr + lpt_data, MAR | WrAddr);
|
|
while (m) {
|
|
for (i = 0, dp = (u_char *)m->m_data;
|
|
i < m->m_len;
|
|
i++, dp++)
|
|
WrByteALToDRAM(sc, *dp);
|
|
m = m->m_next;
|
|
}
|
|
|
|
/* the RTL 8002 requires an even byte-count remote DMA */
|
|
if (total_len & 1)
|
|
WrByteALToDRAM(sc, 0);
|
|
}
|
|
|
|
outb(sc->baseaddr + lpt_data, 0xff);
|
|
outb(sc->baseaddr + lpt_control,
|
|
Ctrl_HNibRead | Ctrl_SelData | sc->irqenbit);
|
|
|
|
return total_len;
|
|
}
|
|
|
|
/*
|
|
* Read the designated ethernet hardware address out of a 93C46
|
|
* (serial) EEPROM.
|
|
* Note that the 93C46 uses 16-bit words in big-endian notation.
|
|
*/
|
|
static int
|
|
rdp_gethwaddr_93c46(struct rdp_softc *sc, u_char *etheraddr)
|
|
{
|
|
int i, magic;
|
|
size_t j = 0;
|
|
u_short w;
|
|
|
|
WrNib(sc, CMR2, CMR2_PAGE | CMR2_IRQINV); /* select page 1 */
|
|
|
|
/*
|
|
* The original RealTek packet driver had the ethernet address
|
|
* starting at EEPROM address 0. Other vendors seem to have
|
|
* gone `creative' here -- while they didn't do anything else
|
|
* than changing a few strings in the entire driver, compared
|
|
* to the RealTek version, they also moved out the ethernet
|
|
* address to a different location in the EEPROM, so the
|
|
* original RealTek driver won't work correctly with them, and
|
|
* vice versa. Sounds pretty cool, eh? $@%&!
|
|
*
|
|
* Anyway, we walk through the EEPROM, until we find some
|
|
* allowable value based upon our table of IEEE OUI assignments.
|
|
*/
|
|
for (i = magic = 0; magic < 3 && i < 32; i++) {
|
|
/* read cmd (+ 6 bit address) */
|
|
rdp_93c46_cmd(sc, 0x180 + i, 10);
|
|
w = rdp_93c46_read(sc);
|
|
switch (magic) {
|
|
case 0:
|
|
for (j = 0;
|
|
j < sizeof allowed_ouis / sizeof(u_short);
|
|
j++)
|
|
if (w == allowed_ouis[j]) {
|
|
etheraddr[0] = (w >> 8) & 0xff;
|
|
etheraddr[1] = w & 0xff;
|
|
magic++;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
/*
|
|
* If the first two bytes have been 00:00, we
|
|
* discard the match iff the next two bytes
|
|
* are also 00:00, so we won't get fooled by
|
|
* an EEPROM that has been filled with zeros.
|
|
* This in theory would disallow 64 K of legal
|
|
* addresses assigned to Xerox, but it's
|
|
* almost certain that those addresses haven't
|
|
* been used for RTL80[01]2 chips anyway.
|
|
*/
|
|
if ((etheraddr[0] | etheraddr[1]) == 0 && w == 0) {
|
|
magic--;
|
|
break;
|
|
}
|
|
|
|
etheraddr[2] = (w >> 8) & 0xff;
|
|
etheraddr[3] = w & 0xff;
|
|
magic++;
|
|
break;
|
|
|
|
case 2:
|
|
etheraddr[4] = (w >> 8) & 0xff;
|
|
etheraddr[5] = w & 0xff;
|
|
magic++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
WrNib(sc, CMR2, CMR2_IRQINV); /* back to page 0 */
|
|
|
|
return magic == 3;
|
|
}
|
|
|
|
/*
|
|
* Read the designated ethernet hardware address out of a 74S288
|
|
* EEPROM.
|
|
*
|
|
* This is untested, since i haven't seen any adapter actually using
|
|
* a 74S288. In the RTL 8012, only the serial EEPROM (94C46) is
|
|
* supported anymore.
|
|
*/
|
|
static void
|
|
rdp_gethwaddr_74s288(struct rdp_softc *sc, u_char *etheraddr)
|
|
{
|
|
int i;
|
|
u_char b;
|
|
|
|
WrNib(sc, CMR2, CMR2_PAGE | CMR2_IRQINV); /* select page 1 */
|
|
|
|
for (i = 0; i < 6; i++) {
|
|
WrNib(sc, PCMR, i & 0x0f); /* lower 4 bit of addr */
|
|
WrNib(sc, PCMR + HNib, HNib + 4); /* upper 2 bit addr + /CS */
|
|
WrNib(sc, PCMR + HNib, HNib); /* latch data now */
|
|
b = RdNib(sc, PDR) & 0x0f;
|
|
b |= (RdNib(sc, PDR + HNib) & 0x0f) << 4;
|
|
etheraddr[i] = b;
|
|
}
|
|
|
|
RdEnd(sc, PDR + HNib);
|
|
WrNib(sc, CMR2, CMR2_IRQINV); /* reselect page 0 */
|
|
}
|
|
|
|
/*
|
|
* Send nbits of data (starting with MSB) out to the 93c46 as a
|
|
* command. Assumes register page 1 has already been selected.
|
|
*/
|
|
static void
|
|
rdp_93c46_cmd(struct rdp_softc *sc, u_short data, unsigned nbits)
|
|
{
|
|
u_short mask = 1 << (nbits - 1);
|
|
unsigned i;
|
|
u_char b;
|
|
|
|
#if DEBUG & 2
|
|
printf("rdp_93c46_cmd(): ");
|
|
#endif
|
|
for (i = 0; i < nbits; i++, mask >>= 1) {
|
|
b = HNib + PCMR_SK + PCMR_CS;
|
|
if (data & mask)
|
|
b += PCMR_DO;
|
|
#if DEBUG & 2
|
|
printf("%d", b & 1);
|
|
#endif
|
|
WrNib(sc, PCMR + HNib, b);
|
|
DELAY(1);
|
|
WrNib(sc, PCMR + HNib, b & ~PCMR_SK);
|
|
DELAY(1);
|
|
}
|
|
#if DEBUG & 2
|
|
printf("\n");
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Read one word of data from the 93c46. Actually, we have to read
|
|
* 17 bits, and discard the very first bit. Assumes register page 1
|
|
* to be selected as well.
|
|
*/
|
|
static u_short
|
|
rdp_93c46_read(struct rdp_softc *sc)
|
|
{
|
|
u_short data = 0;
|
|
u_char b;
|
|
int i;
|
|
|
|
#if DEBUG & 2
|
|
printf("rdp_93c46_read(): ");
|
|
#endif
|
|
for (i = 0; i < 17; i++) {
|
|
WrNib(sc, PCMR + HNib, PCMR_SK + PCMR_CS + HNib);
|
|
DELAY(1);
|
|
WrNib(sc, PCMR + HNib, PCMR_CS + HNib);
|
|
DELAY(1);
|
|
b = RdNib(sc, PDR);
|
|
data <<= 1;
|
|
if (b & 1)
|
|
data |= 1;
|
|
#if DEBUG & 2
|
|
printf("%d", b & 1);
|
|
#endif
|
|
RdEnd(sc, PDR);
|
|
DELAY(1);
|
|
}
|
|
|
|
#if DEBUG & 2
|
|
printf("\n");
|
|
#endif
|
|
/* end of cycle */
|
|
WrNib(sc, PCMR + HNib, PCMR_SK + HNib);
|
|
DELAY(1);
|
|
|
|
return data;
|
|
}
|