* Copyright (c) 1990 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* %sccs.include.noredist.c%
* @(#)if_we.c 5.1 (Berkeley) %G%
* 8/28/89 - Initial version, Tim L Tucker
* Western Digital 8003 ethernet/starlan adapter
* Supports the following interface cards:
* WD8003E, WD8003EBT, WD8003S, WD8003SBT
* The Western Digital card is one of many AT/MCA ethernet interfaces
* based on the National N8390/NS32490 Network Interface chip set.
#include "../net/netisr.h"
#include "../netinet/in.h"
#include "../netinet/in_systm.h"
#include "../netinet/in_var.h"
#include "../netinet/ip.h"
#include "../netinet/if_ether.h"
#include "../netns/ns_if.h"
#include "../isa/isavar.h"
* This constant should really be 60 because the wd adds 4 bytes of crc.
* However when set to 60 our packets are ignored by deuna's , 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 wd_ac
; /* Ethernet common part */
#define wd_if wd_ac.ac_if /* network-visible interface */
#define wd_addr wd_ac.ac_enaddr /* hardware Ethernet address */
u_char wd_flags
; /* software state */
u_char wd_type
; /* interface type code */
u_short wd_vector
; /* interrupt vector */
caddr_t wd_io_ctl_addr
; /* i/o bus address, control */
caddr_t wd_io_nic_addr
; /* i/o bus address, NS32490 */
caddr_t wd_vmem_addr
; /* card RAM virtual memory base */
u_long wd_vmem_size
; /* card RAM bytes */
caddr_t wd_vmem_ring
; /* receive ring RAM vaddress */
int wdprobe(), wdattach(), wdintr();
int wdinit(), wdoutput(), wdioctl(), wdreset();
* Probe the WD8003 to see if it's there
register struct wd_softc
*sc
= &wd_softc
[is
->is_unit
];
* 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(reg
+ WD_ROM_OFFSET
+ i
);
sc
->wd_type
= INB(reg
+ WD_ROM_OFFSET
+ 6);
if ((sc
->wd_type
!= WD_ETHER
) && (sc
->wd_type
!= WD_STARLAN
))
* Setup card RAM area and i/o addresses
* Kernel Virtual to segment C0000-DFFFF?????
sc
->wd_io_ctl_addr
= reg
;
sc
->wd_io_nic_addr
= sc
->wd_io_ctl_addr
+ WD_NIC_OFFSET
;
sc
->wd_vector
= is
->is_vector
;
sc
->wd_vmem_addr
= (caddr_t
)is
->is_mem
;
sc
->wd_vmem_size
= is
->is_memsize
;
sc
->wd_vmem_ring
= sc
->wd_vmem_addr
+ (WD_PAGE_SIZE
* WD_TXBUF_SIZE
);
* Save board ROM station address
for (i
= 0; i
< ETHER_ADDR_LEN
; ++i
)
sc
->wd_addr
[i
] = INB(sc
->wd_io_ctl_addr
+ WD_ROM_OFFSET
+ i
);
* Mapin interface memory, setup memory select register
wdm
.ms_addr
= (u_long
)sc
->wd_vmem_addr
>> 13;
OUTB(sc
->wd_io_ctl_addr
, wdm
.ms_byte
);
* clear interface memory, then sum to make sure its valid
for (i
= 0; i
< sc
->wd_vmem_size
; ++i
)
sc
->wd_vmem_addr
[i
] = 0x0;
for (sum
= 0, i
= 0; i
< sc
->wd_vmem_size
; ++i
)
sum
+= sc
->wd_vmem_addr
[i
];
printf("wd%d: wd8003 dual port RAM address error\n", is
->is_unit
);
* Interface exists: make available by filling in network interface
* record. System will initialize the interface when it is ready
register struct wd_softc
*sc
= &wd_softc
[is
->is_unit
];
register struct ifnet
*ifp
= &sc
->wd_if
;
* Initialize ifnet structure
ifp
->if_unit
= is
->is_unit
;
ifp
->if_flags
= IFF_BROADCAST
|IFF_NOTRAILERS
;
ifp
->if_output
= wdoutput
;
printf("wd%d: %s, hardware address %s\n", is
->is_unit
,
((sc
->wd_type
== WD_ETHER
) ? "ethernet" : "starlan"),
ether_sprintf(sc
->wd_addr
));
printf("wd%d: reset\n", unit
);
wd_softc
[unit
].wd_flags
&= ~WDF_RUNNING
;
* Take interface offline.
register struct wd_softc
*sc
= &wd_softc
[unit
];
wdcmd
.cs_byte
= INB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
, wdcmd
.cs_byte
);
* Initialization of interface (really just NS32490).
register struct wd_softc
*sc
= &wd_softc
[unit
];
register struct ifnet
*ifp
= &sc
->wd_if
;
if (ifp
->if_addrlist
== (struct ifaddr
*)0)
if (sc
->wd_flags
& WDF_RUNNING
)
* Initialize NS32490 in order given in NSC NIC manual.
* this is stock code...please see the National manual for details.
wdcmd
.cs_byte
= INB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
, wdcmd
.cs_byte
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_DCR
, WD_D_CONFIG
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_RBCR0
, 0);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_RBCR1
, 0);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_RCR
, WD_R_MON
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_TCR
, WD_T_CONFIG
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_TPSR
, 0);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_PSTART
, WD_TXBUF_SIZE
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_PSTOP
,
sc
->wd_vmem_size
/ WD_PAGE_SIZE
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_BNRY
, WD_TXBUF_SIZE
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_ISR
, 0xff);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_IMR
, WD_I_CONFIG
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
, wdcmd
.cs_byte
);
for (i
= 0; i
< ETHER_ADDR_LEN
; ++i
)
OUTB(sc
->wd_io_nic_addr
+ WD_P1_PAR0
+ i
, sc
->wd_addr
[i
]);
for (i
= 0; i
< ETHER_ADDR_LEN
; ++i
) /* == broadcast addr */
OUTB(sc
->wd_io_nic_addr
+ WD_P1_MAR0
+ i
, 0xff);
OUTB(sc
->wd_io_nic_addr
+ WD_P1_CURR
, WD_TXBUF_SIZE
);
OUTB(sc
->wd_io_nic_addr
+ WD_P1_COMMAND
, wdcmd
.cs_byte
);
OUTB(sc
->wd_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_UP
| IFF_RUNNING
;
sc
->wd_flags
|= WDF_RUNNING
;
sc
->wd_flags
&= ~WDF_TXBUSY
;
* Start output on interface.
register struct wd_softc
*sc
= &wd_softc
[unit
];
* The NS32490 has only one transmit buffer, if it is busy we
* must wait until the transmit interrupt completes.
if (sc
->wd_flags
& WDF_TXBUSY
) {
IF_DEQUEUE(&sc
->wd_if
.if_snd
, m
);
sc
->wd_flags
|= WDF_TXBUSY
;
* Copy the mbuf chain into the transmit buffer
buffer
= sc
->wd_vmem_addr
;
for (m0
= m
; m
!= 0; m
= m
->m_next
) {
bcopy(mtod(m
, caddr_t
), buffer
, m
->m_len
);
* If this was a broadcast packet loop it
* back because the hardware can't hear its own
if (bcmp((caddr_t
)(mtod(m0
, struct ether_header
*)->ether_dhost
),
(caddr_t
)etherbroadcastaddr
,
sizeof(etherbroadcastaddr
)) == 0) {
* Init transmit length registers, and set transmit start flag.
len
= MAX(len
, ETHER_MIN_LEN
);
wdcmd
.cs_byte
= INB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
, wdcmd
.cs_byte
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_TBCR0
, len
& 0xff);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_TBCR1
, len
>> 8);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
, wdcmd
.cs_byte
);
* Ethernet interface interrupt processor
register struct wd_softc
*sc
= &wd_softc
[unit
];
union wd_interrupt wdisr
;
/* disable onboard interrupts, then get interrupt status */
wdcmd
.cs_byte
= INB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
, wdcmd
.cs_byte
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_IMR
, 0);
wdisr
.is_byte
= INB(sc
->wd_io_nic_addr
+ WD_P0_ISR
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_ISR
, 0xff);
/* need to read these registers to clear status */
sc
->wd_if
.if_collisions
+=
INB(sc
->wd_io_nic_addr
+ WD_P0_TBCR0
);
/* need to read these registers to clear status */
(void) INB(sc
->wd_io_nic_addr
+ 0xD);
(void) INB(sc
->wd_io_nic_addr
+ 0xE);
(void) INB(sc
->wd_io_nic_addr
+ 0xF);
/* normal transmit complete */
/* normal receive notification */
/* try to start transmit */
/* re-enable onboard interrupts */
wdcmd
.cs_byte
= INB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
, wdcmd
.cs_byte
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_IMR
, WD_I_CONFIG
);
* Ethernet interface transmit interrupt.
register struct wd_softc
*sc
= &wd_softc
[unit
];
* Do some statistics (assume page zero of NIC mapped in)
sc
->wd_flags
&= ~WDF_TXBUSY
;
sc
->wd_if
.if_collisions
+= INB(sc
->wd_io_nic_addr
+ WD_P0_TBCR0
);
* Ethernet interface receiver interrupt.
register struct wd_softc
*sc
= &wd_softc
[unit
];
register struct mbuf
**m
;
* Traverse the receive ring looking for packets to pass back.
* The search is complete when we find a descriptor not in use.
wdcmd
.cs_byte
= INB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
, wdcmd
.cs_byte
);
bnry
= INB(sc
->wd_io_nic_addr
+ WD_P0_BNRY
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
, wdcmd
.cs_byte
);
curr
= INB(sc
->wd_io_nic_addr
+ WD_P1_CURR
);
/* get pointer to this buffer header structure */
wdr
= (struct wd_ring
*)(sc
->wd_vmem_addr
+ (bnry
<< 8));
len
= wdr
->wd_count
- 4; /* count includes CRC */
pkt
= (caddr_t
)(wdr
+ 1) - 2; /* 2 - word align pkt data */
count
= len
+ 2; /* copy two extra bytes */
endp
= (caddr_t
)(sc
->wd_vmem_addr
+ sc
->wd_vmem_size
);
/* pull packet out of dual ported RAM */
/* drop chain if can't get another buffer */
MGET(*m
, M_DONTWAIT
, MT_DATA
);
/* fill mbuf and attach to packet list */
mlen
= MIN(mlen
, endp
- pkt
);
bcopy(pkt
, mtod(*m
, caddr_t
), mlen
);
/* wrap memory pointer around circ buffer */
pkt
= (caddr_t
)sc
->wd_vmem_ring
;
/* skip aligment bytes, send packet up to higher levels */
/* advance on chip Boundry register */
bnry
= wdr
->wd_next_packet
;
wdcmd
.cs_byte
= INB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
, wdcmd
.cs_byte
);
/* watch out for NIC overflow, reset Boundry if invalid */
if ((bnry
- 1) < WD_TXBUF_SIZE
) {
OUTB(sc
->wd_io_nic_addr
+ WD_P0_BNRY
,
(sc
->wd_vmem_size
/ WD_PAGE_SIZE
) - 1);
OUTB(sc
->wd_io_nic_addr
+ WD_P0_BNRY
, bnry
- 1);
/* refresh our copy of CURR */
OUTB(sc
->wd_io_nic_addr
+ WD_P0_COMMAND
, wdcmd
.cs_byte
);
curr
= INB(sc
->wd_io_nic_addr
+ WD_P1_CURR
);
* Ethernet output routine.
* Encapsulate a packet of type family for the local net.
register struct wd_softc
*sc
= &wd_softc
[ifp
->if_unit
];
register struct mbuf
*m
= m0
;
register struct ether_header
*eh
;
if ((ifp
->if_flags
& (IFF_UP
|IFF_RUNNING
)) != (IFF_UP
|IFF_RUNNING
)) {
switch (dst
->sa_family
) {
/* Note: we ignore usetrailers */
idst
= ((struct sockaddr_in
*)dst
)->sin_addr
;
if (!arpresolve(&sc
->wd_ac
, m
, &idst
, edst
, &usetrailers
))
return (0); /* if not yet resolved */
bcopy((caddr_t
)&(((struct sockaddr_ns
*)dst
)->sns_addr
.x_host
),
(caddr_t
)edst
, sizeof (edst
));
eh
= (struct ether_header
*)dst
->sa_data
;
bcopy((caddr_t
)eh
->ether_dhost
, (caddr_t
)edst
, sizeof (edst
));
printf("wd%d: can't handle af%d\n", ifp
->if_unit
,
* Add local net header. If no space in first mbuf,
if (m
->m_off
> MMAXOFF
|| MMINOFF
+ ETHER_HDR_SIZE
> m
->m_off
) {
m
= m_get(M_DONTWAIT
, MT_HEADER
);
m
->m_len
= ETHER_HDR_SIZE
;
m
->m_off
-= ETHER_HDR_SIZE
;
m
->m_len
+= ETHER_HDR_SIZE
;
eh
= mtod(m
, struct ether_header
*);
eh
->ether_type
= htons((u_short
)type
);
bcopy((caddr_t
)edst
, (caddr_t
)eh
->ether_dhost
, sizeof (edst
));
bcopy((caddr_t
)sc
->wd_addr
, (caddr_t
)eh
->ether_shost
,
* Queue message on interface, and start output if interface
if (IF_QFULL(&ifp
->if_snd
)) {
IF_ENQUEUE(&ifp
->if_snd
, m
);
* Process an ioctl request.
register struct ifnet
*ifp
;
struct wd_softc
*sc
= &wd_softc
[ifp
->if_unit
];
struct ifaddr
*ifa
= (struct ifaddr
*)data
;
int s
= splimp(), error
= 0;
switch(ifa
->ifa_addr
.sa_family
) {
((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
->wd_addr
);
wdsetaddr(ina
->x_host
.c_host
, ifp
->if_unit
);
if (((ifp
->if_flags
& IFF_UP
) == 0) &&
(sc
->wd_flags
& WDF_RUNNING
)) {
} else if (((ifp
->if_flags
& IFF_UP
) == IFF_UP
) &&
((sc
->wd_flags
& WDF_RUNNING
) == 0))
* set ethernet address for unit
wdsetaddr(physaddr
, unit
)
register struct wd_softc
*sc
= &wd_softc
[unit
];
* Rewrite ethernet address, and then force restart of NIC
for (i
= 0; i
< ETHER_ADDR_LEN
; i
++)
sc
->wd_addr
[i
] = physaddr
[i
];
sc
->wd_flags
&= ~WDF_RUNNING
;
* Pass a packet to the higher levels.
register struct wd_softc
*sc
;
* Get ethernet protocol type out of ether header
eh
= mtod(m
, struct ether_header
*);
type
= ntohs((u_short
)eh
->ether_type
);
m
->m_off
+= ETHER_HDR_SIZE
;
m
->m_len
-= ETHER_HDR_SIZE
;
* Insert ifp pointer at start of packet
m
->m_off
-= sizeof (struct ifnet
*);
m
->m_len
+= sizeof (struct ifnet
*);
*(mtod(m
, struct ifnet
**)) = &sc
->wd_if
;