* Copyright (c) 1990 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 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
* @(#)if_we.c 7.3 (Berkeley) 5/21/91
* 8/28/89 - Initial version(if_wd.c), Tim L Tucker
* Western Digital 8003 ethernet/starlan adapter
* Supports the following interface cards:
* WD8003E, WD8003EBT, WD8003S, WD8003SBT, WD8013EBT
* The Western Digital card is one of many AT/MCA ethernet interfaces
* based on the National DS8390 Network Interface chip set.
#include "netinet/in_systm.h"
#include "netinet/in_var.h"
#include "netinet/if_ether.h"
#include "i386/isa/isa.h"
#include "i386/isa/if_wereg.h"
#include "i386/isa/isa_device.h"
#include "i386/isa/icu.h"
* This constant should really be 60 because the we adds 4 bytes of crc.
* However when set to 60 our packets are ignored by deunas , 3coms are
* okay ??????????????????????????????????????????
#define ETHER_HDR_SIZE 14
* Ethernet software status per interface.
* Each interface is referenced by a network interface structure,
* qe_if, which the routing code uses to locate the interface.
* This structure contains the output queue for the interface, its address, ...
struct arpcom we_ac
; /* Ethernet common part */
#define we_if we_ac.ac_if /* network-visible interface */
#define we_addr we_ac.ac_enaddr /* hardware Ethernet address */
u_char we_flags
; /* software state */
u_char we_type
; /* interface type code */
u_short we_vector
; /* interrupt vector */
short we_io_ctl_addr
; /* i/o bus address, control */
short we_io_nic_addr
; /* i/o bus address, DS8390 */
caddr_t we_vmem_addr
; /* card RAM virtual memory base */
u_long we_vmem_size
; /* card RAM bytes */
caddr_t we_vmem_ring
; /* receive ring RAM vaddress */
caddr_t we_vmem_end
; /* receive ring RAM end */
int weprobe(), weattach(), weintr(), westart();
int weinit(), ether_output(), weioctl(), wereset(), wewatchdog();
struct isa_driver wedriver
= {
static unsigned short wemask
[] =
{ IRQ9
, IRQ3
, IRQ5
, IRQ7
, IRQ10
, IRQ11
, IRQ15
, IRQ4
};
* Probe the WD8003 to see if its there
register struct we_softc
*sc
= &we_softc
[is
->id_unit
];
/* reset card to force it into a known state. */
outb(is
->id_iobase
, 0x80);
outb(is
->id_iobase
, 0x00);
/* wait in the case this card is reading it's EEROM */
* Here we check the card ROM, if the checksum passes, and the
* type code and ethernet address check out, then we know we have
* Autoconfiguration: No warning message is printed on error.
for (sum
= 0, i
= 0; i
< 8; ++i
)
sum
+= inb(is
->id_iobase
+ WD_ROM_OFFSET
+ i
);
sc
->we_type
= inb(is
->id_iobase
+ WD_ROM_OFFSET
+ 6);
if ((sc
->we_type
& WD_REVMASK
) != 2 /* WD8003E or WD8003S */
&& (sc
->we_type
& WD_REVMASK
) != 4 /* WD8003EBT */
&& (sc
->we_type
& WD_REVMASK
) != 6) /* WD8003ELB? */
/*printf("type %x ", sc->we_type);*/
if (sc
->we_type
& WD_SOFTCONFIG
) {
int iv
= inb(is
->id_iobase
+ 1) & 4 |
((inb(is
->id_iobase
+4) & 0x60) >> 5);
/*printf("iv %d ", iv);*/
if (wemask
[iv
] != is
->id_irq
)
outb(is
->id_iobase
+4, inb(is
->id_iobase
+4) | 0x80);
* Setup card RAM area and i/o addresses
* Kernel Virtual to segment C0000-DFFFF?????
sc
->we_io_ctl_addr
= is
->id_iobase
;
sc
->we_io_nic_addr
= sc
->we_io_ctl_addr
+ WD_NIC_OFFSET
;
sc
->we_vector
= is
->id_irq
;
sc
->we_vmem_addr
= (caddr_t
)is
->id_maddr
;
sc
->we_vmem_size
= is
->id_msize
;
sc
->we_vmem_ring
= sc
->we_vmem_addr
+ (WD_PAGE_SIZE
* WD_TXBUF_SIZE
);
sc
->we_vmem_end
= sc
->we_vmem_addr
+ is
->id_msize
;
* Save board ROM station address
for (i
= 0; i
< ETHER_ADDR_LEN
; ++i
)
sc
->we_addr
[i
] = inb(sc
->we_io_ctl_addr
+ WD_ROM_OFFSET
+ i
);
* Mapin interface memory, setup memory select register
wem
.ms_addr
= kvtop(sc
->we_vmem_addr
) >> 13;
outb(sc
->we_io_ctl_addr
, wem
.ms_byte
);
* clear interface memory, then sum to make sure its valid
for (i
= 0; i
< sc
->we_vmem_size
; ++i
)
sc
->we_vmem_addr
[i
] = 0x0;
for (sum
= 0, i
= 0; i
< sc
->we_vmem_size
; ++i
)
sum
+= sc
->we_vmem_addr
[i
];
printf("we%d: wd8003 dual port RAM address error\n", is
->id_unit
);
* Interface exists: make available by filling in network interface
* record. System will initialize the interface when it is ready
register struct we_softc
*sc
= &we_softc
[is
->id_unit
];
register struct ifnet
*ifp
= &sc
->we_if
;
wecmd
.cs_byte
= inb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
);
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
* Initialize ifnet structure
ifp
->if_unit
= is
->id_unit
;
ifp
->if_flags
= IFF_BROADCAST
| IFF_SIMPLEX
| IFF_NOTRAILERS
;
ifp
->if_output
= ether_output
;
ifp
->if_watchdog
= wewatchdog
;
(sc
->we_type
& WD_ETHERNET
) ? "ethernet" : "starlan",
ether_sprintf(sc
->we_addr
));
printf("we%d: reset\n", unit
);
/* we_softc[unit].we_flags &= ~WDF_RUNNING; */
* Take interface offline.
register struct we_softc
*sc
= &we_softc
[unit
];
wecmd
.cs_byte
= inb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
);
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
log(LOG_WARNING
,"we%d: soft reset\n", unit
);
* Initialization of interface (really just DS8390).
register struct we_softc
*sc
= &we_softc
[unit
];
register struct ifnet
*ifp
= &sc
->we_if
;
if (ifp
->if_addrlist
== (struct ifaddr
*)0)
/*if (ifp->if_flags & IFF_RUNNING) return; */
* Initialize DS8390 in order given in NSC NIC manual.
* this is stock code...please see the National manual for details.
wecmd
.cs_byte
= inb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
);
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
outb(sc
->we_io_nic_addr
+ WD_P0_DCR
, WD_D_CONFIG
);
outb(sc
->we_io_nic_addr
+ WD_P0_RBCR0
, 0);
outb(sc
->we_io_nic_addr
+ WD_P0_RBCR1
, 0);
outb(sc
->we_io_nic_addr
+ WD_P0_RCR
, WD_R_MON
);
outb(sc
->we_io_nic_addr
+ WD_P0_TCR
, WD_T_CONFIG
);
outb(sc
->we_io_nic_addr
+ WD_P0_TPSR
, 0);
outb(sc
->we_io_nic_addr
+ WD_P0_PSTART
, WD_TXBUF_SIZE
);
outb(sc
->we_io_nic_addr
+ WD_P0_PSTOP
,
sc
->we_vmem_size
/ WD_PAGE_SIZE
);
outb(sc
->we_io_nic_addr
+ WD_P0_BNRY
, WD_TXBUF_SIZE
);
outb(sc
->we_io_nic_addr
+ WD_P0_ISR
, 0xff);
outb(sc
->we_io_nic_addr
+ WD_P0_IMR
, WD_I_CONFIG
);
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
for (i
= 0; i
< ETHER_ADDR_LEN
; ++i
)
outb(sc
->we_io_nic_addr
+ WD_P1_PAR0
+ i
, sc
->we_addr
[i
]);
for (i
= 0; i
< ETHER_ADDR_LEN
; ++i
) /* == broadcast addr */
outb(sc
->we_io_nic_addr
+ WD_P1_MAR0
+ i
, 0xff);
outb(sc
->we_io_nic_addr
+ WD_P1_CURR
, WD_TXBUF_SIZE
);
outb(sc
->we_io_nic_addr
+ WD_P1_COMMAND
, wecmd
.cs_byte
);
outb(sc
->we_io_nic_addr
+ WD_P0_RCR
, WD_R_CONFIG
);
* Take the interface out of reset, program the vector,
* enable interrupts, and tell the world we are up.
ifp
->if_flags
|= IFF_RUNNING
;
sc
->we_flags
&= ~WDF_TXBUSY
;
* Start output on interface.
register struct we_softc
*sc
= &we_softc
[ifp
->if_unit
];
* The DS8390 has only one transmit buffer, if it is busy we
* must wait until the transmit interrupt completes.
if (sc
->we_flags
& WDF_TXBUSY
) {
IF_DEQUEUE(&sc
->we_if
.if_snd
, m
);
sc
->we_flags
|= WDF_TXBUSY
;
* Copy the mbuf chain into the transmit buffer
buffer
= sc
->we_vmem_addr
;
for (m0
= m
; m
!= 0; m
= m
->m_next
) {
bcopy(mtod(m
, caddr_t
), buffer
, m
->m_len
);
* Init transmit length registers, and set transmit start flag.
len
= MAX(len
, ETHER_MIN_LEN
);
wecmd
.cs_byte
= inb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
);
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
outb(sc
->we_io_nic_addr
+ WD_P0_TBCR0
, len
& 0xff);
outb(sc
->we_io_nic_addr
+ WD_P0_TBCR1
, len
>> 8);
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
* Ethernet interface interrupt processor
register struct we_softc
*sc
= &we_softc
[unit
];
union we_interrupt weisr
;
/* disable onboard interrupts, then get interrupt status */
wecmd
.cs_byte
= inb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
);
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
weisr
.is_byte
= inb(sc
->we_io_nic_addr
+ WD_P0_ISR
);
outb(sc
->we_io_nic_addr
+ WD_P0_ISR
, weisr
.is_byte
);
/* need to read these registers to clear status */
sc
->we_if
.if_collisions
+=
inb(sc
->we_io_nic_addr
+ WD_P0_TBCR0
);
/* need to read these registers to clear status */
(void) inb(sc
->we_io_nic_addr
+ 0xD);
(void) inb(sc
->we_io_nic_addr
+ 0xE);
(void) inb(sc
->we_io_nic_addr
+ 0xF);
/* normal transmit complete */
if (weisr
.is_ptx
|| weisr
.is_txe
)
/* normal receive notification */
if (weisr
.is_prx
|| weisr
.is_rxe
)
/* try to start transmit */
/* re-enable onboard interrupts */
wecmd
.cs_byte
= inb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
);
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
outb(sc
->we_io_nic_addr
+ WD_P0_IMR
, 0xff/*WD_I_CONFIG*/);
weisr
.is_byte
= inb(sc
->we_io_nic_addr
+ WD_P0_ISR
);
if (weisr
.is_byte
) goto loop
;
* Ethernet interface transmit interrupt.
register struct we_softc
*sc
= &we_softc
[unit
];
* Do some statistics (assume page zero of NIC mapped in)
sc
->we_flags
&= ~WDF_TXBUSY
;
sc
->we_if
.if_collisions
+= inb(sc
->we_io_nic_addr
+ WD_P0_TBCR0
);
* Ethernet interface receiver interrupt.
register struct we_softc
*sc
= &we_softc
[unit
];
* Traverse the receive ring looking for packets to pass back.
* The search is complete when we find a descriptor not in use.
wecmd
.cs_byte
= inb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
);
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
bnry
= inb(sc
->we_io_nic_addr
+ WD_P0_BNRY
);
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
curr
= inb(sc
->we_io_nic_addr
+ WD_P1_CURR
);
/* get pointer to this buffer header structure */
wer
= (struct we_ring
*)(sc
->we_vmem_addr
+ (bnry
<< 8));
if (len
> 30 && len
<= ETHERMTU
+100
/*&& (*(char *)wer == 1 || *(char *) wer == 0x21)*/)
weread(sc
, (caddr_t
)(wer
+ 1), len
);
else printf("reject %d", len
);
wecmd
.cs_byte
= inb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
);
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
/* advance on chip Boundry register */
if((caddr_t
) wer
+ WD_PAGE_SIZE
- 1 > sc
->we_vmem_end
) {
outb(sc
->we_io_nic_addr
+ WD_P0_BNRY
,
sc
->we_vmem_size
/ WD_PAGE_SIZE
-1);
if (len
> 30 && len
<= ETHERMTU
+100)
bnry
= wer
->we_next_packet
;
/* watch out for NIC overflow, reset Boundry if invalid */
if ((bnry
- 1) < WD_TXBUF_SIZE
) {
outb(sc
->we_io_nic_addr
+ WD_P0_BNRY
,
(sc
->we_vmem_size
/ WD_PAGE_SIZE
) - 1);
outb(sc
->we_io_nic_addr
+ WD_P0_BNRY
, bnry
-1);
/* refresh our copy of CURR */
outb(sc
->we_io_nic_addr
+ WD_P0_COMMAND
, wecmd
.cs_byte
);
curr
= inb(sc
->we_io_nic_addr
+ WD_P1_CURR
);
* Process an ioctl request.
register struct ifnet
*ifp
;
register struct ifaddr
*ifa
= (struct ifaddr
*)data
;
struct we_softc
*sc
= &we_softc
[ifp
->if_unit
];
struct ifreq
*ifr
= (struct ifreq
*)data
;
int s
= splimp(), error
= 0;
switch (ifa
->ifa_addr
->sa_family
) {
weinit(ifp
->if_unit
); /* before arpwhohas */
((struct arpcom
*)ifp
)->ac_ipaddr
=
arpwhohas((struct arpcom
*)ifp
, &IA_SIN(ifa
)->sin_addr
);
register struct ns_addr
*ina
= &(IA_SNS(ifa
)->sns_addr
);
ina
->x_host
= *(union ns_host
*)(sc
->ns_addr
);
* The manual says we cant change the address
* while the receiver is armed,
ifp
->if_flags
&= ~IFF_RUNNING
;
bcopy((caddr_t
)ina
->x_host
.c_host
,
(caddr_t
)sc
->ns_addr
, sizeof(sc
->ns_addr
));
weinit(ifp
->if_unit
); /* does ne_setaddr() */
if ((ifp
->if_flags
& IFF_UP
) == 0 &&
ifp
->if_flags
& IFF_RUNNING
) {
ifp
->if_flags
&= ~IFF_RUNNING
;
} else if (ifp
->if_flags
& IFF_UP
&&
(ifp
->if_flags
& IFF_RUNNING
) == 0)
bcopy((caddr_t
)sc
->sc_addr
, (caddr_t
) &ifr
->ifr_data
,
* set ethernet address for unit
wesetaddr(physaddr
, unit
)
register struct we_softc
*sc
= &we_softc
[unit
];
* Rewrite ethernet address, and then force restart of NIC
for (i
= 0; i
< ETHER_ADDR_LEN
; i
++)
sc
->we_addr
[i
] = physaddr
[i
];
sc
->we_flags
&= ~WDF_RUNNING
;
#define wedataaddr(sc, eh, off, type) \
((type) ((caddr_t)((eh)+1)+(off) >= (sc)->we_vmem_end) ? \
(((caddr_t)((eh)+1)+(off))) - (sc)->we_vmem_end \
((caddr_t)((eh)+1)+(off)))
* Pass a packet to the higher levels.
* We deal with the trailer protocol here.
register struct we_softc
*sc
;
register struct ether_header
*eh
;
struct mbuf
*m
, *weget();
* Deal with trailer protocol: if type is trailer type
* get true type from first 16-bit word past data.
* Remember that type was trailer by setting off.
eh
= (struct ether_header
*)buf
;
eh
->ether_type
= ntohs((u_short
)eh
->ether_type
);
if (eh
->ether_type
>= ETHERTYPE_TRAIL
&&
eh
->ether_type
< ETHERTYPE_TRAIL
+ETHERTYPE_NTRAILER
) {
off
= (eh
->ether_type
- ETHERTYPE_TRAIL
) * 512;
if (off
>= ETHERMTU
) return; /* sanity */
eh
->ether_type
= ntohs(*wedataaddr(sc
, eh
, off
, u_short
*));
resid
= ntohs(*(wedataaddr(sc
, eh
, off
+2, u_short
*)));
if (off
+ resid
> len
) return; /* sanity */
len
-= sizeof(struct ether_header
);
* Pull packet off interface. Off is nonzero if packet
* has trailing header; neget will then force this header
* information to be at the front, but we still have to drop
* the type and length which are at the front of any trailer data.
m
= weget(buf
, len
, off
, &sc
->we_if
, sc
);
ether_input(&sc
->we_if
, eh
, m
);
* Pull read data off a interface.
* Len is length of data, with local net header stripped.
* Off is non-zero if a trailer protocol was used, and
* gives the offset of the trailer information.
* We copy the trailer information and then all the normal
* data into mbufs. When full cluster sized units are present
weget(buf
, totlen
, off0
, ifp
, sc
)
struct mbuf
*top
, **mp
, *m
, *p
;
register caddr_t cp
= buf
;
buf
+= sizeof(struct ether_header
);
cp
+= off
+ 2 * sizeof(u_short
);
totlen
-= 2 * sizeof(u_short
);
MGETHDR(m
, M_DONTWAIT
, MT_DATA
);
m
->m_pkthdr
.len
= totlen
;
MGET(m
, M_DONTWAIT
, MT_DATA
);
len
= min(totlen
, epkt
- cp
);
m
->m_len
= len
= min(len
, MCLBYTES
);
* Place initial small packet/header at end of mbuf.
if (top
== 0 && len
+ max_linkhdr
<= m
->m_len
)
m
->m_data
+= max_linkhdr
;
/* only do up to end of buffer */
if (cp
+len
> sc
->we_vmem_end
) {
unsigned toend
= sc
->we_vmem_end
- cp
;
bcopy(cp
, mtod(m
, caddr_t
), toend
);
bcopy(cp
, mtod(m
, caddr_t
)+toend
, len
- toend
);
bcopy(cp
, mtod(m
, caddr_t
), (unsigned)len
);