freebsd-dev/sys/dev/ex/if_ex.c
2000-03-13 12:27:21 +00:00

1206 lines
29 KiB
C

/*
* Copyright (c) 1996, Javier Martín Rueda (jmrueda@diatel.upm.es)
* 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.
*
* $FreeBSD$
*
* MAINTAINER: Matthew N. Dodd <winter@jurai.net>
* <mdodd@FreeBSD.org>
*/
/*
* Intel EtherExpress Pro/10, Pro/10+ Ethernet driver
*
* Revision history:
*
* 30-Oct-1996: first beta version. Inet and BPF supported, but no multicast.
*/
#include "ex.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_media.h>
#include <net/ethernet.h>
#include <net/bpf.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <machine/clock.h>
#include <isa/isavar.h>
#include <isa/pnpvar.h>
#include <dev/ex/if_exreg.h>
#ifdef EXDEBUG
# define Start_End 1
# define Rcvd_Pkts 2
# define Sent_Pkts 4
# define Status 8
static int debug_mask = 0;
static int exintr_count = 0;
# define DODEBUG(level, action) if (level & debug_mask) action
#else
# define DODEBUG(level, action)
#endif
#define CARD_TYPE_EX_10 1
#define CARD_TYPE_EX_10_PLUS 2
struct ex_softc {
struct arpcom arpcom; /* Ethernet common data */
struct ifmedia ifmedia;
device_t dev;
struct resource *ioport;
struct resource *irq;
u_int iobase; /* I/O base address. */
u_short irq_no; /* IRQ number. */
char * irq2ee; /* irq <-> internal */
u_char * ee2irq; /* representation conversion */
u_int mem_size; /* Total memory size, in bytes. */
u_int rx_mem_size; /* Rx memory size (by default, */
/* first 3/4 of total memory). */
u_int rx_lower_limit; /* Lower and upper limits of */
u_int rx_upper_limit; /* receive buffer. */
u_int rx_head; /* Head of receive ring buffer. */
u_int tx_mem_size; /* Tx memory size (by default, */
/* last quarter of total memory).*/
u_int tx_lower_limit; /* Lower and upper limits of */
u_int tx_upper_limit; /* transmit buffer. */
u_int tx_head; /* Head and tail of */
u_int tx_tail; /* transmit ring buffer. */
u_int tx_last; /* Pointer to beginning of last */
/* frame in the chain. */
};
static char irq2eemap[] =
{ -1, -1, 0, 1, -1, 2, -1, -1, -1, 0, 3, 4, -1, -1, -1, -1 };
static u_char ee2irqmap[] =
{ 9, 3, 5, 10, 11, 0, 0, 0 };
static char plus_irq2eemap[] =
{ -1, -1, -1, 0, 1, 2, -1, 3, -1, 4, 5, 6, 7, -1, -1, -1 };
static u_char plus_ee2irqmap[] =
{ 3, 4, 5, 7, 9, 10, 11, 12 };
/* Bus Front End Functions */
static void ex_isa_identify __P((driver_t *, device_t));
static int ex_isa_probe __P((device_t));
static int ex_isa_attach __P((device_t));
/* Network Interface Functions */
static void ex_init __P((void *));
static void ex_start __P((struct ifnet *));
static int ex_ioctl __P((struct ifnet *, u_long, caddr_t));
static void ex_watchdog __P((struct ifnet *));
/* ifmedia Functions */
static int ex_ifmedia_upd __P((struct ifnet *));
static void ex_ifmedia_sts __P((struct ifnet *, struct ifmediareq *));
static void ex_stop __P((struct ex_softc *));
static void ex_reset __P((struct ex_softc *));
static driver_intr_t ex_intr;
static void ex_tx_intr __P((struct ex_softc *));
static void ex_rx_intr __P((struct ex_softc *));
static u_short eeprom_read __P((int, int));
static device_method_t ex_methods[] = {
/* Device interface */
DEVMETHOD(device_identify, ex_isa_identify),
DEVMETHOD(device_probe, ex_isa_probe),
DEVMETHOD(device_attach, ex_isa_attach),
{ 0, 0 }
};
static driver_t ex_driver = {
"ex",
ex_methods,
sizeof(struct ex_softc),
};
static devclass_t ex_devclass;
DRIVER_MODULE(ex, isa, ex_driver, ex_devclass, 0, 0);
static struct isa_pnp_id ex_ids[] = {
{ 0x3110d425, NULL }, /* INT1031 */
{ 0x3010d425, NULL }, /* INT1030 */
{ 0, NULL },
};
static int
look_for_card (u_int iobase)
{
int count1, count2;
/*
* Check for the i82595 signature, and check that the round robin
* counter actually advances.
*/
if (((count1 = inb(iobase + ID_REG)) & Id_Mask) != Id_Sig)
return(0);
count2 = inb(iobase + ID_REG);
count2 = inb(iobase + ID_REG);
count2 = inb(iobase + ID_REG);
return((count2 & Counter_bits) == ((count1 + 0xc0) & Counter_bits));
}
static int
ex_get_media (u_int32_t iobase)
{
int tmp;
outb(iobase + CMD_REG, Bank2_Sel);
tmp = inb(iobase + REG3);
outb(iobase + CMD_REG, Bank0_Sel);
if (tmp & TPE_bit)
return(IFM_ETHER|IFM_10_T);
if (tmp & BNC_bit)
return(IFM_ETHER|IFM_10_2);
return (IFM_ETHER|IFM_10_5);
}
static void
ex_get_address (u_int32_t iobase, u_char *enaddr)
{
u_int16_t eaddr_tmp;
eaddr_tmp = eeprom_read(iobase, EE_Eth_Addr_Lo);
enaddr[5] = eaddr_tmp & 0xff;
enaddr[4] = eaddr_tmp >> 8;
eaddr_tmp = eeprom_read(iobase, EE_Eth_Addr_Mid);
enaddr[3] = eaddr_tmp & 0xff;
enaddr[2] = eaddr_tmp >> 8;
eaddr_tmp = eeprom_read(iobase, EE_Eth_Addr_Hi);
enaddr[1] = eaddr_tmp & 0xff;
enaddr[0] = eaddr_tmp >> 8;
return;
}
static int
ex_card_type (u_char *enaddr)
{
if ((enaddr[0] == 0x00) && (enaddr[1] == 0xA0) && (enaddr[2] == 0xC9))
return (CARD_TYPE_EX_10_PLUS);
return (CARD_TYPE_EX_10);
}
/*
* Non-destructive identify.
*/
static void
ex_isa_identify (driver_t *driver, device_t parent)
{
device_t child;
u_int32_t ioport;
u_char enaddr[6];
u_int irq;
int tmp;
const char * desc;
if (bootverbose)
printf("ex_isa_identify()\n");
for (ioport = 0x200; ioport < 0x3a0; ioport += 0x10) {
/* No board found at address */
if (!look_for_card(ioport)) {
continue;
}
if (bootverbose)
printf("ex: Found card at 0x%03x!\n", ioport);
/* Board in PnP mode */
if (eeprom_read(ioport, EE_W0) & EE_W0_PNP) {
/* Reset the card. */
outb(ioport + CMD_REG, Reset_CMD);
DELAY(500);
if (bootverbose)
printf("ex: card at 0x%03x in PnP mode!\n", ioport);
continue;
}
bzero(enaddr, sizeof(enaddr));
/* Reset the card. */
outb(ioport + CMD_REG, Reset_CMD);
DELAY(400);
ex_get_address(ioport, enaddr);
tmp = eeprom_read(ioport, EE_W1) & EE_W1_INT_SEL;
/* work out which set of irq <-> internal tables to use */
if (ex_card_type(enaddr) == CARD_TYPE_EX_10_PLUS) {
irq = plus_ee2irqmap[tmp];
desc = "Intel Pro/10+";
} else {
irq = ee2irqmap[tmp];
desc = "Intel Pro/10";
}
child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "ex", -1);
device_set_desc_copy(child, desc);
device_set_driver(child, driver);
bus_set_resource(child, SYS_RES_IRQ, 0, irq, 1);
bus_set_resource(child, SYS_RES_IOPORT, 0, ioport, EX_IOSIZE);
if (bootverbose)
printf("ex: Adding board at 0x%03x, irq %d\n", ioport, irq);
}
return;
}
static int
ex_isa_probe(device_t dev)
{
u_int iobase;
u_int irq;
char * irq2ee;
u_char * ee2irq;
u_char enaddr[6];
int tmp;
int error;
DODEBUG(Start_End, printf("ex_probe: start\n"););
/* Check isapnp ids */
error = ISA_PNP_PROBE(device_get_parent(dev), dev, ex_ids);
/* If the card had a PnP ID that didn't match any we know about */
if (error == ENXIO) {
return(error);
}
/* If we had some other problem. */
if (!(error == 0 || error == ENOENT)) {
return(error);
}
iobase = bus_get_resource_start(dev, SYS_RES_IOPORT, 0);
if (!iobase) {
printf("ex: no iobase?\n");
return(ENXIO);
}
if (!look_for_card(iobase)) {
printf("ex: no card found at 0x%03x\n", iobase);
return(ENXIO);
}
if (bootverbose)
printf("ex: ex_isa_probe() found card at 0x%03x\n", iobase);
/*
* Reset the card.
*/
outb(iobase + CMD_REG, Reset_CMD);
DELAY(800);
ex_get_address(iobase, enaddr);
/* work out which set of irq <-> internal tables to use */
if (ex_card_type(enaddr) == CARD_TYPE_EX_10_PLUS) {
irq2ee = plus_irq2eemap;
ee2irq = plus_ee2irqmap;
} else {
irq2ee = irq2eemap;
ee2irq = ee2irqmap;
}
tmp = eeprom_read(iobase, EE_W1) & EE_W1_INT_SEL;
irq = bus_get_resource_start(dev, SYS_RES_IRQ, 0);
if (irq > 0) {
/* This will happen if board is in PnP mode. */
if (ee2irq[tmp] != irq) {
printf("ex: WARNING: board's EEPROM is configured"
" for IRQ %d, using %d\n",
ee2irq[tmp], irq);
}
} else {
irq = ee2irq[tmp];
bus_set_resource(dev, SYS_RES_IRQ, 0, irq, 1);
}
if (irq == 0) {
printf("ex: invalid IRQ.\n");
return(ENXIO);
}
DODEBUG(Start_End, printf("ex_probe: finish\n"););
return(0);
}
static int
ex_isa_attach(device_t dev)
{
struct ex_softc * sc = device_get_softc(dev);
struct ifnet * ifp = &sc->arpcom.ac_if;
struct ifmedia * ifm;
int unit = device_get_unit(dev);
int error;
int rid;
void * ih;
u_int16_t temp;
DODEBUG(Start_End, device_printf(dev, "start\n"););
rid = 0;
sc->ioport = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
0, ~0, 1, RF_ACTIVE);
if (!sc->ioport) {
device_printf(dev, "No I/O space?!\n");
goto bad;
}
rid = 0;
sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid,
0, ~0, 1, RF_ACTIVE);
if (!sc->irq) {
device_printf(dev, "No IRQ?!\n");
goto bad;
}
error = bus_setup_intr(dev, sc->irq, INTR_TYPE_NET,
ex_intr, (void *)sc, &ih);
if (error) {
device_printf(dev, "bus_setup_intr() failed!\n");
goto bad;
}
/*
* Fill in several fields of the softc structure:
* - I/O base address.
* - Hardware Ethernet address.
* - IRQ number (if not supplied in config file, read it from EEPROM).
* - Connector type.
*/
sc->dev = dev;
sc->iobase = rman_get_start(sc->ioport);
sc->irq_no = rman_get_start(sc->irq);
ex_get_address(sc->iobase, sc->arpcom.ac_enaddr);
/* work out which set of irq <-> internal tables to use */
if (ex_card_type(sc->arpcom.ac_enaddr) == CARD_TYPE_EX_10_PLUS) {
sc->irq2ee = plus_irq2eemap;
sc->ee2irq = plus_ee2irqmap;
} else {
sc->irq2ee = irq2eemap;
sc->ee2irq = ee2irqmap;
}
sc->mem_size = CARD_RAM_SIZE; /* XXX This should be read from the card itself. */
/*
* Initialize the ifnet structure.
*/
ifp->if_softc = sc;
ifp->if_unit = unit;
ifp->if_name = "ex";
ifp->if_mtu = ETHERMTU;
ifp->if_flags = IFF_SIMPLEX | IFF_BROADCAST /* XXX not done yet. | IFF_MULTICAST */;
ifp->if_output = ether_output;
ifp->if_start = ex_start;
ifp->if_ioctl = ex_ioctl;
ifp->if_watchdog = ex_watchdog;
ifp->if_init = ex_init;
ifp->if_snd.ifq_maxlen = IFQ_MAXLEN;
ifmedia_init(&sc->ifmedia, 0, ex_ifmedia_upd, ex_ifmedia_sts);
temp = eeprom_read(sc->iobase, EE_W5);
if (temp & EE_W5_PORT_TPE)
ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_10_T, 0, NULL);
if (temp & EE_W5_PORT_BNC)
ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_10_2, 0, NULL);
if (temp & EE_W5_PORT_AUI)
ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_10_5, 0, NULL);
ifmedia_set(&sc->ifmedia, ex_get_media(sc->iobase));
ifm = &sc->ifmedia;
ifm->ifm_media = ifm->ifm_cur->ifm_media;
ex_ifmedia_upd(ifp);
/*
* Attach the interface.
*/
if_attach(ifp);
ether_ifattach(ifp);
temp = eeprom_read(sc->iobase, EE_W0);
device_printf(sc->dev, "%s config, %s bus, ",
(temp & EE_W0_PNP) ? "PnP" : "Manual",
(temp & EE_W0_BUS16) ? "16-bit" : "8-bit");
temp = eeprom_read(sc->iobase, EE_W6);
printf("board id 0x%03x, stepping 0x%01x\n",
(temp & EE_W6_BOARD_MASK) >> EE_W6_BOARD_SHIFT,
temp & EE_W6_STEP_MASK);
device_printf(sc->dev, "Ethernet address %6D\n",
sc->arpcom.ac_enaddr, ":");
/*
* If BPF is in the kernel, call the attach for it
*/
bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header));
DODEBUG(Start_End, printf("ex_isa_attach%d: finish\n", unit););
return(0);
bad:
if (sc->ioport)
bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->ioport);
if (sc->irq)
bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq);
return (-1);
}
static void
ex_init(void *xsc)
{
struct ex_softc * sc = (struct ex_softc *) xsc;
struct ifnet * ifp = &sc->arpcom.ac_if;
int s;
int i;
register int iobase = sc->iobase;
unsigned short temp_reg;
DODEBUG(Start_End, printf("ex_init%d: start\n", ifp->if_unit););
if (ifp->if_addrhead.tqh_first == NULL) {
return;
}
s = splimp();
sc->arpcom.ac_if.if_timer = 0;
/*
* Load the ethernet address into the card.
*/
outb(iobase + CMD_REG, Bank2_Sel);
temp_reg = inb(iobase + EEPROM_REG);
if (temp_reg & Trnoff_Enable) {
outb(iobase + EEPROM_REG, temp_reg & ~Trnoff_Enable);
}
for (i = 0; i < ETHER_ADDR_LEN; i++) {
outb(iobase + I_ADDR_REG0 + i, sc->arpcom.ac_enaddr[i]);
}
/*
* - Setup transmit chaining and discard bad received frames.
* - Match broadcast.
* - Clear test mode.
* - Set receiving mode.
* - Set IRQ number.
*/
outb(iobase + REG1, inb(iobase + REG1) | Tx_Chn_Int_Md | Tx_Chn_ErStp | Disc_Bad_Fr);
outb(iobase + REG2, inb(iobase + REG2) | No_SA_Ins | RX_CRC_InMem);
outb(iobase + REG3, inb(iobase + REG3) & 0x3f /* XXX constants. */ );
outb(iobase + CMD_REG, Bank1_Sel);
outb(iobase + INT_NO_REG, (inb(iobase + INT_NO_REG) & 0xf8) | sc->irq2ee[sc->irq_no]);
/*
* Divide the available memory in the card into rcv and xmt buffers.
* By default, I use the first 3/4 of the memory for the rcv buffer,
* and the remaining 1/4 of the memory for the xmt buffer.
*/
sc->rx_mem_size = sc->mem_size * 3 / 4;
sc->tx_mem_size = sc->mem_size - sc->rx_mem_size;
sc->rx_lower_limit = 0x0000;
sc->rx_upper_limit = sc->rx_mem_size - 2;
sc->tx_lower_limit = sc->rx_mem_size;
sc->tx_upper_limit = sc->mem_size - 2;
outb(iobase + RCV_LOWER_LIMIT_REG, sc->rx_lower_limit >> 8);
outb(iobase + RCV_UPPER_LIMIT_REG, sc->rx_upper_limit >> 8);
outb(iobase + XMT_LOWER_LIMIT_REG, sc->tx_lower_limit >> 8);
outb(iobase + XMT_UPPER_LIMIT_REG, sc->tx_upper_limit >> 8);
/*
* Enable receive and transmit interrupts, and clear any pending int.
*/
outb(iobase + REG1, inb(iobase + REG1) | TriST_INT);
outb(iobase + CMD_REG, Bank0_Sel);
outb(iobase + MASK_REG, All_Int & ~(Rx_Int | Tx_Int));
outb(iobase + STATUS_REG, All_Int);
/*
* Initialize receive and transmit ring buffers.
*/
outw(iobase + RCV_BAR, sc->rx_lower_limit);
sc->rx_head = sc->rx_lower_limit;
outw(iobase + RCV_STOP_REG, sc->rx_upper_limit | 0xfe);
outw(iobase + XMT_BAR, sc->tx_lower_limit);
sc->tx_head = sc->tx_tail = sc->tx_lower_limit;
ifp->if_flags |= IFF_RUNNING;
ifp->if_flags &= ~IFF_OACTIVE;
DODEBUG(Status, printf("OIDLE init\n"););
/*
* Final reset of the board, and enable operation.
*/
outb(iobase + CMD_REG, Sel_Reset_CMD);
DELAY(2);
outb(iobase + CMD_REG, Rcv_Enable_CMD);
ex_start(ifp);
splx(s);
DODEBUG(Start_End, printf("ex_init%d: finish\n", ifp->if_unit););
}
static void
ex_start(struct ifnet *ifp)
{
struct ex_softc * sc = ifp->if_softc;
int iobase = sc->iobase;
int i, s, len, data_len, avail, dest, next;
unsigned char tmp16[2];
struct mbuf * opkt;
struct mbuf * m;
DODEBUG(Start_End, printf("ex_start%d: start\n", unit););
s = splimp();
/*
* Main loop: send outgoing packets to network card until there are no
* more packets left, or the card cannot accept any more yet.
*/
while (((opkt = ifp->if_snd.ifq_head) != NULL) &&
!(ifp->if_flags & IFF_OACTIVE)) {
/*
* Ensure there is enough free transmit buffer space for
* this packet, including its header. Note: the header
* cannot wrap around the end of the transmit buffer and
* must be kept together, so we allow space for twice the
* length of the header, just in case.
*/
for (len = 0, m = opkt; m != NULL; m = m->m_next) {
len += m->m_len;
}
data_len = len;
DODEBUG(Sent_Pkts, printf("1. Sending packet with %d data bytes. ", data_len););
if (len & 1) {
len += XMT_HEADER_LEN + 1;
} else {
len += XMT_HEADER_LEN;
}
if ((i = sc->tx_tail - sc->tx_head) >= 0) {
avail = sc->tx_mem_size - i;
} else {
avail = -i;
}
DODEBUG(Sent_Pkts, printf("i=%d, avail=%d\n", i, avail););
if (avail >= len + XMT_HEADER_LEN) {
IF_DEQUEUE(&ifp->if_snd, opkt);
#ifdef EX_PSA_INTR
/*
* Disable rx and tx interrupts, to avoid corruption
* of the host address register by interrupt service
* routines.
* XXX Is this necessary with splimp() enabled?
*/
outb(iobase + MASK_REG, All_Int);
#endif
/*
* Compute the start and end addresses of this
* frame in the tx buffer.
*/
dest = sc->tx_tail;
next = dest + len;
if (next > sc->tx_upper_limit) {
if ((sc->tx_upper_limit + 2 - sc->tx_tail) <=
XMT_HEADER_LEN) {
dest = sc->tx_lower_limit;
next = dest + len;
} else {
next = sc->tx_lower_limit +
next - sc->tx_upper_limit - 2;
}
}
/*
* Build the packet frame in the card's ring buffer.
*/
DODEBUG(Sent_Pkts, printf("2. dest=%d, next=%d. ", dest, next););
outw(iobase + HOST_ADDR_REG, dest);
outw(iobase + IO_PORT_REG, Transmit_CMD);
outw(iobase + IO_PORT_REG, 0);
outw(iobase + IO_PORT_REG, next);
outw(iobase + IO_PORT_REG, data_len);
/*
* Output the packet data to the card. Ensure all
* transfers are 16-bit wide, even if individual
* mbufs have odd length.
*/
for (m = opkt, i = 0; m != NULL; m = m->m_next) {
DODEBUG(Sent_Pkts, printf("[%d]", m->m_len););
if (i) {
tmp16[1] = *(mtod(m, caddr_t));
outsw(iobase + IO_PORT_REG, tmp16, 1);
}
outsw(iobase + IO_PORT_REG,
mtod(m, caddr_t) + i, (m->m_len - i) / 2);
if ((i = (m->m_len - i) & 1) != 0) {
tmp16[0] = *(mtod(m, caddr_t) +
m->m_len - 1);
}
}
if (i) {
outsw(iobase + IO_PORT_REG, tmp16, 1);
}
/*
* If there were other frames chained, update the
* chain in the last one.
*/
if (sc->tx_head != sc->tx_tail) {
if (sc->tx_tail != dest) {
outw(iobase + HOST_ADDR_REG,
sc->tx_last + XMT_Chain_Point);
outw(iobase + IO_PORT_REG, dest);
}
outw(iobase + HOST_ADDR_REG,
sc->tx_last + XMT_Byte_Count);
i = inw(iobase + IO_PORT_REG);
outw(iobase + HOST_ADDR_REG,
sc->tx_last + XMT_Byte_Count);
outw(iobase + IO_PORT_REG, i | Ch_bit);
}
/*
* Resume normal operation of the card:
* - Make a dummy read to flush the DRAM write
* pipeline.
* - Enable receive and transmit interrupts.
* - Send Transmit or Resume_XMT command, as
* appropriate.
*/
inw(iobase + IO_PORT_REG);
#ifdef EX_PSA_INTR
outb(iobase + MASK_REG, All_Int & ~(Rx_Int | Tx_Int));
#endif
if (sc->tx_head == sc->tx_tail) {
outw(iobase + XMT_BAR, dest);
outb(iobase + CMD_REG, Transmit_CMD);
sc->tx_head = dest;
DODEBUG(Sent_Pkts, printf("Transmit\n"););
} else {
outb(iobase + CMD_REG, Resume_XMT_List_CMD);
DODEBUG(Sent_Pkts, printf("Resume\n"););
}
sc->tx_last = dest;
sc->tx_tail = next;
if (ifp->if_bpf != NULL) {
bpf_mtap(ifp, opkt);
}
ifp->if_timer = 2;
ifp->if_opackets++;
m_freem(opkt);
} else {
ifp->if_flags |= IFF_OACTIVE;
DODEBUG(Status, printf("OACTIVE start\n"););
}
}
splx(s);
DODEBUG(Start_End, printf("ex_start%d: finish\n", unit););
}
static void
ex_stop(struct ex_softc *sc)
{
int iobase = sc->iobase;
DODEBUG(Start_End, printf("ex_stop%d: start\n", unit););
/*
* Disable card operation:
* - Disable the interrupt line.
* - Flush transmission and disable reception.
* - Mask and clear all interrupts.
* - Reset the 82595.
*/
outb(iobase + CMD_REG, Bank1_Sel);
outb(iobase + REG1, inb(iobase + REG1) & ~TriST_INT);
outb(iobase + CMD_REG, Bank0_Sel);
outb(iobase + CMD_REG, Rcv_Stop);
sc->tx_head = sc->tx_tail = sc->tx_lower_limit;
sc->tx_last = 0; /* XXX I think these two lines are not necessary, because ex_init will always be called again to reinit the interface. */
outb(iobase + MASK_REG, All_Int);
outb(iobase + STATUS_REG, All_Int);
outb(iobase + CMD_REG, Reset_CMD);
DELAY(200);
DODEBUG(Start_End, printf("ex_stop%d: finish\n", unit););
return;
}
static void
ex_intr(void *arg)
{
struct ex_softc * sc = (struct ex_softc *)arg;
struct ifnet * ifp = &sc->arpcom.ac_if;
int iobase = sc->iobase;
int int_status, send_pkts;
DODEBUG(Start_End, printf("ex_intr%d: start\n", unit););
#ifdef EXDEBUG
if (++exintr_count != 1)
printf("WARNING: nested interrupt (%d). Mail the author.\n", exintr_count);
#endif
send_pkts = 0;
while ((int_status = inb(iobase + STATUS_REG)) & (Tx_Int | Rx_Int)) {
if (int_status & Rx_Int) {
outb(iobase + STATUS_REG, Rx_Int);
ex_rx_intr(sc);
} else if (int_status & Tx_Int) {
outb(iobase + STATUS_REG, Tx_Int);
ex_tx_intr(sc);
send_pkts = 1;
}
}
/*
* If any packet has been transmitted, and there are queued packets to
* be sent, attempt to send more packets to the network card.
*/
if (send_pkts && (ifp->if_snd.ifq_head != NULL)) {
ex_start(ifp);
}
#ifdef EXDEBUG
exintr_count--;
#endif
DODEBUG(Start_End, printf("ex_intr%d: finish\n", unit););
return;
}
static void
ex_tx_intr(struct ex_softc *sc)
{
struct ifnet * ifp = &sc->arpcom.ac_if;
int iobase = sc->iobase;
int tx_status;
DODEBUG(Start_End, printf("ex_tx_intr%d: start\n", unit););
/*
* - Cancel the watchdog.
* For all packets transmitted since last transmit interrupt:
* - Advance chain pointer to next queued packet.
* - Update statistics.
*/
ifp->if_timer = 0;
while (sc->tx_head != sc->tx_tail) {
outw(iobase + HOST_ADDR_REG, sc->tx_head);
if (! inw(iobase + IO_PORT_REG) & Done_bit)
break;
tx_status = inw(iobase + IO_PORT_REG);
sc->tx_head = inw(iobase + IO_PORT_REG);
if (tx_status & TX_OK_bit) {
ifp->if_opackets++;
} else {
ifp->if_oerrors++;
}
ifp->if_collisions += tx_status & No_Collisions_bits;
}
/*
* The card should be ready to accept more packets now.
*/
ifp->if_flags &= ~IFF_OACTIVE;
DODEBUG(Status, printf("OIDLE tx_intr\n"););
DODEBUG(Start_End, printf("ex_tx_intr%d: finish\n", unit););
return;
}
static void
ex_rx_intr(struct ex_softc *sc)
{
struct ifnet * ifp = &sc->arpcom.ac_if;
int iobase = sc->iobase;
int rx_status;
int pkt_len;
int QQQ;
struct mbuf * m;
struct mbuf * ipkt;
struct ether_header * eh;
DODEBUG(Start_End, printf("ex_rx_intr%d: start\n", unit););
/*
* For all packets received since last receive interrupt:
* - If packet ok, read it into a new mbuf and queue it to interface,
* updating statistics.
* - If packet bad, just discard it, and update statistics.
* Finally, advance receive stop limit in card's memory to new location.
*/
outw(iobase + HOST_ADDR_REG, sc->rx_head);
while (inw(iobase + IO_PORT_REG) == RCV_Done) {
rx_status = inw(iobase + IO_PORT_REG);
sc->rx_head = inw(iobase + IO_PORT_REG);
QQQ = pkt_len = inw(iobase + IO_PORT_REG);
if (rx_status & RCV_OK_bit) {
MGETHDR(m, M_DONTWAIT, MT_DATA);
ipkt = m;
if (ipkt == NULL) {
ifp->if_iqdrops++;
} else {
ipkt->m_pkthdr.rcvif = ifp;
ipkt->m_pkthdr.len = pkt_len;
ipkt->m_len = MHLEN;
while (pkt_len > 0) {
if (pkt_len > MINCLSIZE) {
MCLGET(m, M_DONTWAIT);
if (m->m_flags & M_EXT) {
m->m_len = MCLBYTES;
} else {
m_freem(ipkt);
ifp->if_iqdrops++;
goto rx_another;
}
}
m->m_len = min(m->m_len, pkt_len);
/*
* NOTE: I'm assuming that all mbufs allocated are of even length,
* except for the last one in an odd-length packet.
*/
insw(iobase + IO_PORT_REG,
mtod(m, caddr_t), m->m_len / 2);
if (m->m_len & 1) {
*(mtod(m, caddr_t) + m->m_len - 1) = inb(iobase + IO_PORT_REG);
}
pkt_len -= m->m_len;
if (pkt_len > 0) {
MGET(m->m_next, M_DONTWAIT, MT_DATA);
if (m->m_next == NULL) {
m_freem(ipkt);
ifp->if_iqdrops++;
goto rx_another;
}
m = m->m_next;
m->m_len = MLEN;
}
}
eh = mtod(ipkt, struct ether_header *);
#ifdef EXDEBUG
if (debug_mask & Rcvd_Pkts) {
if ((eh->ether_dhost[5] != 0xff) || (eh->ether_dhost[0] != 0xff)) {
printf("Receive packet with %d data bytes: %6D -> ", QQQ, eh->ether_shost, ":");
printf("%6D\n", eh->ether_dhost, ":");
} /* QQQ */
}
#endif
if (ifp->if_bpf != NULL) {
bpf_mtap(ifp, ipkt);
/*
* 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 ((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) {
m_freem(ipkt);
goto rx_another;
}
}
m_adj(ipkt, sizeof(struct ether_header));
ether_input(ifp, eh, ipkt);
ifp->if_ipackets++;
}
} else {
ifp->if_ierrors++;
}
outw(iobase + HOST_ADDR_REG, sc->rx_head);
rx_another: ;
}
if (sc->rx_head < sc->rx_lower_limit + 2)
outw(iobase + RCV_STOP_REG, sc->rx_upper_limit);
else
outw(iobase + RCV_STOP_REG, sc->rx_head - 2);
DODEBUG(Start_End, printf("ex_rx_intr%d: finish\n", unit););
return;
}
static int
ex_ioctl(register struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct ex_softc * sc = ifp->if_softc;
struct ifreq * ifr = (struct ifreq *)data;
int s;
int error = 0;
DODEBUG(Start_End, printf("ex_ioctl%d: start ", ifp->if_unit););
s = splimp();
switch(cmd) {
case SIOCSIFADDR:
case SIOCGIFADDR:
case SIOCSIFMTU:
error = ether_ioctl(ifp, cmd, data);
break;
case SIOCSIFFLAGS:
DODEBUG(Start_End, printf("SIOCSIFFLAGS"););
if ((ifp->if_flags & IFF_UP) == 0 &&
(ifp->if_flags & IFF_RUNNING)) {
ifp->if_flags &= ~IFF_RUNNING;
ex_stop(sc);
} else {
ex_init(sc);
}
break;
#ifdef NODEF
case SIOCGHWADDR:
DODEBUG(Start_End, printf("SIOCGHWADDR"););
bcopy((caddr_t)sc->sc_addr, (caddr_t)&ifr->ifr_data,
sizeof(sc->sc_addr));
break;
#endif
case SIOCADDMULTI:
DODEBUG(Start_End, printf("SIOCADDMULTI"););
case SIOCDELMULTI:
DODEBUG(Start_End, printf("SIOCDELMULTI"););
/* XXX Support not done yet. */
error = EINVAL;
break;
case SIOCSIFMEDIA:
case SIOCGIFMEDIA:
error = ifmedia_ioctl(ifp, ifr, &sc->ifmedia, cmd);
break;
default:
DODEBUG(Start_End, printf("unknown"););
error = EINVAL;
}
splx(s);
DODEBUG(Start_End, printf("\nex_ioctl%d: finish\n", ifp->if_unit););
return(error);
}
static void
ex_reset(struct ex_softc *sc)
{
int s;
DODEBUG(Start_End, printf("ex_reset%d: start\n", unit););
s = splimp();
ex_stop(sc);
ex_init(sc);
splx(s);
DODEBUG(Start_End, printf("ex_reset%d: finish\n", unit););
return;
}
static void
ex_watchdog(struct ifnet *ifp)
{
struct ex_softc * sc = ifp->if_softc;
DODEBUG(Start_End, printf("ex_watchdog%d: start\n", ifp->if_unit););
ifp->if_flags &= ~IFF_OACTIVE;
DODEBUG(Status, printf("OIDLE watchdog\n"););
ifp->if_oerrors++;
ex_reset(sc);
ex_start(ifp);
DODEBUG(Start_End, printf("ex_watchdog%d: finish\n", ifp->if_unit););
return;
}
static int
ex_ifmedia_upd (ifp)
struct ifnet * ifp;
{
struct ex_softc * sc = ifp->if_softc;
return (0);
}
static void
ex_ifmedia_sts(ifp, ifmr)
struct ifnet * ifp;
struct ifmediareq * ifmr;
{
struct ex_softc * sc = ifp->if_softc;
ifmr->ifm_active = ex_get_media(sc->iobase);
return;
}
static u_short
eeprom_read(int iobase, int location)
{
int i;
u_short data = 0;
int ee_addr;
int read_cmd = location | EE_READ_CMD;
short ctrl_val = EECS;
ee_addr = iobase + EEPROM_REG;
outb(iobase + CMD_REG, Bank2_Sel);
outb(ee_addr, EECS);
for (i = 8; i >= 0; i--) {
short outval = (read_cmd & (1 << i)) ? ctrl_val | EEDI : ctrl_val;
outb(ee_addr, outval);
outb(ee_addr, outval | EESK);
DELAY(3);
outb(ee_addr, outval);
DELAY(2);
}
outb(ee_addr, ctrl_val);
for (i = 16; i > 0; i--) {
outb(ee_addr, ctrl_val | EESK);
DELAY(3);
data = (data << 1) | ((inb(ee_addr) & EEDO) ? 1 : 0);
outb(ee_addr, ctrl_val);
DELAY(2);
}
ctrl_val &= ~EECS;
outb(ee_addr, ctrl_val | EESK);
DELAY(3);
outb(ee_addr, ctrl_val);
DELAY(2);
outb(iobase + CMD_REG, Bank0_Sel);
return(data);
}