* 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
* A driver for the 3Com 3C503 (Etherlink II) ethernet adaptor.
* Written by Herb Peyerl (hpeyerl@novatel.cuc.ab.ca) on 04/25/92.
* (This is my first ever device driver and I couldn't have done
* it without the consumption of many "Brock Gummy Bears" so a
* big thanx to the "Brock Candy Company" of Chattanooga TN)
* This driver uses the Western Digital 8003 driver for a template
* since the two cards use the DP8390 chip by National. Everything
* is fairly similar except that the nic on the wd8003 appears to
* to see shared memory at 0x0000 and on the 3com, the nic sees it
* at 0x2000. Also, the 3c503 has it's own ASIC for controlling
* things like the IRQ level in software and whether to use the
* onboard xceiver or not. Since the Clarkson drivers do a very
* good rendition of a 3c503, I also scavenged a lot of ideas from
* Since I couldn't really think of any non-creative way (other than
* using a #define) of configuring the board to use the onboard xceiver,
* I kludged the isa_device->unit to contain this information. Simply
* put, if bit-7 of isa_device->unit is set (>127) then the driver
* configures that unit to onboard-xceiver (BNC) and if <128 it assumes
* AUI. ec_attach informs the user of this on bootup. Also, ec_probe
* repairs this bit after obtaining it's information since I didn't know
* what else within the depths of the kernel would freak out if I left it.
#include "netinet/in_systm.h"
#include "netinet/in_var.h"
#include "netinet/if_ether.h"
#include "i386/isa/isa_device.h"
#include "i386/isa/if_ec.h"
* 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 arpcom
; /* Ethernet common part */
#define ec_if arpcom.ac_if /* network-visible interface */
#define ec_addr arpcom.ac_enaddr /* hardware Ethernet address */
u_char ec_flags
; /* software state */
u_char ec_type
; /* interface type code */
u_short ec_vector
; /* interrupt vector */
short ec_io_ctl_addr
; /* i/o bus address, control */
short ec_io_nic_addr
; /* i/o bus address, DS8390 */
short thick_or_thin
; /* thick=0;thin=2 */
caddr_t ec_vmem_addr
; /* card RAM virtual memory base */
u_long ec_vmem_size
; /* card RAM bytes */
caddr_t ec_vmem_ring
; /* receive ring RAM vaddress */
caddr_t ec_vmem_end
; /* receive ring RAM end */
#define PAGE0 outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_PAGE0);
#define PAGE1 outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_PAGE1);
struct isa_driver ecdriver
= {
register struct ec_softc
*sc
= &ec_softc
[is
->id_unit
&127];
* Set up the softc structure with card specific info.
* The 3Com asic is at base+0x400
sc
->ec_io_ctl_addr
= is
->id_iobase
+ 0x400;
sc
->ec_io_nic_addr
= is
->id_iobase
;
sc
->ec_vector
= is
->id_irq
;
sc
->ec_vmem_addr
= (caddr_t
)is
->id_maddr
;
sc
->ec_vmem_size
= is
->id_msize
;
sc
->ec_vmem_ring
= sc
->ec_vmem_addr
+ (EC_PAGE_SIZE
* EC_TXBUF_SIZE
);
sc
->ec_vmem_end
= sc
->ec_vmem_addr
+ is
->id_msize
;
* Now we get the MAC address. Assume thin ethernet unless told otherwise later.
outb(sc
->ec_io_ctl_addr
+ E33G_CNTRL
, ECNTRL_RESET
|2); /* Toggle reset bit on*/
outb(sc
->ec_io_ctl_addr
+ E33G_CNTRL
, 2); /* Toggle reset bit off */
outb(sc
->ec_io_ctl_addr
+ E33G_CNTRL
, ECNTRL_SAPROM
|2); /* Map SA_PROM */
for (i
=0;i
<ETHER_ADDR_LEN
; ++i
)
sc
->ec_addr
[i
] = inb(sc
->ec_io_nic_addr
+ i
);
outb(sc
->ec_io_ctl_addr
+ E33G_CNTRL
, 2); /* Disable SA_PROM */
outb(sc
->ec_io_ctl_addr
+ E33G_GACFR
, EGACFR_IRQOFF
); /* tcm, rsel, mbs0, nim */
* Stop the chip just in case.
outb(sc
->ec_io_nic_addr
+ EN_CCMD
, ENC_NODMA
|ENC_STOP
);
/* Check cmd reg and fail if not right */
if ((i
=inb(sc
->ec_io_nic_addr
+ EN_CCMD
)) != (ENC_NODMA
|ENC_STOP
))
* Test the shared memory.
for(i
= 0 ; i
< sc
->ec_vmem_size
; ++i
)
sc
->ec_vmem_addr
[i
] = 0x0;
for(sum
=0, i
=0; i
<sc
->ec_vmem_size
; ++i
)
sum
+= sc
->ec_vmem_addr
[i
];
printf("ec%d: shared memory error (device conflict?)\n",
register struct ec_softc
*sc
= &ec_softc
[is
->id_unit
];
register struct ifnet
*ifp
= &sc
->ec_if
;
** Initialize the ASIC in same order as Clarkson driver.
* Point vector pointer registers off into boonies.
outb(sc
->ec_io_ctl_addr
+ E33G_VP2
, 0xff);
outb(sc
->ec_io_ctl_addr
+ E33G_VP1
, 0xff);
outb(sc
->ec_io_ctl_addr
+ E33G_VP0
, 0x0);
* Set up control of shared memory, buffer ring, etc.
outb(sc
->ec_io_ctl_addr
+ E33G_STARTPG
, EC_RXBUF_OFFSET
);
outb(sc
->ec_io_ctl_addr
+ E33G_STOPPG
, EC_RXBUF_END
);
* Set up the IRQ and NBURST on the board.
* ( Not sure why we set up NBURST since we don't use DMA.)
* Normally we would is->id_irq<<2 but IRQ2 is defined as 0x200
* in icu.h so it's a special case.
outb(sc
->ec_io_ctl_addr
+ E33G_IDCFR
, 0x10);
outb(sc
->ec_io_ctl_addr
+ E33G_IDCFR
, is
->id_irq
<< 2);
outb(sc
->ec_io_ctl_addr
+ E33G_NBURST
, 0x08); /* Set Burst to 8 */
outb(sc
->ec_io_ctl_addr
+ E33G_DMAAH
, 0x20);
outb(sc
->ec_io_ctl_addr
+ E33G_DMAAL
, 0x0);
* Fill in the ifnet structure.
ifp
->if_unit
= is
->id_unit
;
ifp
->if_flags
= IFF_BROADCAST
| IFF_SIMPLEX
| IFF_NOTRAILERS
;
ifp
->if_output
= ether_output
;
ifp
->if_start
= ec_start_output
;
ifp
->if_ioctl
= ec_ioctl
;
ifp
->if_reset
= ec_reset
;
ifp
->if_watchdog
= ec_watchdog
;
* Attach the interface to something. Have to figure this out later.
* Weeee.. We get to tell people we exist...
printf("ec%d: address %s\n", is
->id_unit
, ether_sprintf(sc
->ec_addr
));
register struct ec_softc
*sc
= &ec_softc
[unit
];
register struct ifnet
*ifp
= &sc
->ec_if
;
if(ifp
->if_addrlist
== (struct ifaddr
*) 0)
* select thick (e.g. AUI connector) if LLC0 bit is set
if (ifp
->if_flags
& IFF_LLC0
)
outb(sc
->ec_io_ctl_addr
+ E33G_CNTRL
, 0);
outb(sc
->ec_io_ctl_addr
+ E33G_CNTRL
, 2);
* (Use sequence recommended by 3Com. )
outb(sc
->ec_io_nic_addr
+ EN_CCMD
, ENC_NODMA
|ENC_PAGE0
|ENC_STOP
);
outb(sc
->ec_io_nic_addr
+ EN0_DCFG
, ENDCFG_BM8
);
outb(sc
->ec_io_nic_addr
+ EN0_RCNTLO
, 0x0);
outb(sc
->ec_io_nic_addr
+ EN0_RCNTHI
, 0x0);
outb(sc
->ec_io_nic_addr
+ EN0_RXCR
, ENRXCR_MON
);
outb(sc
->ec_io_nic_addr
+ EN0_TXCR
, 0x02);
outb(sc
->ec_io_nic_addr
+ EN0_BOUNDARY
, EC_RXBUF_OFFSET
);
outb(sc
->ec_io_nic_addr
+ EN0_TPSR
, 0x20);
outb(sc
->ec_io_nic_addr
+ EN0_STARTPG
, EC_RXBUF_OFFSET
);
outb(sc
->ec_io_nic_addr
+ EN0_STOPPG
, EC_RXBUF_END
);
outb(sc
->ec_io_nic_addr
+ EN0_ISR
, 0xff);
outb(sc
->ec_io_nic_addr
+ EN0_IMR
, 0x3f);
* Copy Ethernet address from SA_PROM into 8390 chip registers.
outb(sc
->ec_io_nic_addr
+ EN1_PHYS
+i
, sc
->ec_addr
[i
]);
* Set multicast filter mask bits in case promiscuous rcv wanted (???)
* (set to 0xff as in if_we.c)
outb(sc
->ec_io_nic_addr
+ EN1_MULT
+i
, 0xff);
* Set current shared page for RX to work on.
outb(sc
->ec_io_nic_addr
+ EN1_CURPAG
, EC_RXBUF_OFFSET
);
* Start the 8390. Clear Interrupt Status reg, and accept Broadcast
outb(sc
->ec_io_nic_addr
+ EN_CCMD
, ENC_START
|ENC_PAGE0
|ENC_NODMA
);
outb(sc
->ec_io_nic_addr
+ EN0_ISR
, 0xff);
outb(sc
->ec_io_nic_addr
+ EN0_TXCR
, 0x0);
outb(sc
->ec_io_nic_addr
+ EN0_RXCR
, ENRXCR_BCST
);
* Take interface out of reset, program the vector,
* enable interrupts, and tell the world we are up.
ifp
->if_flags
|= IFF_RUNNING
;
outb(sc
->ec_io_ctl_addr
+ E33G_GACFR
, EGACFR_NORM
); /* tcm, rsel, mbs0 */
sc
->ec_flags
&= ~EC_TXBUSY
;
register struct ec_softc
*sc
= &ec_softc
[ifp
->if_unit
];
* The DS8390 only has one transmit buffer, if it is busy we
* must wait until the transmit interrupt completes.
if(sc
->ec_flags
& EC_TXBUSY
)
IF_DEQUEUE(&sc
->ec_if
.if_snd
, m
);
sc
->ec_flags
|= EC_TXBUSY
;
* Copy the mbuf chain into the transmit buffer
buffer
= sc
->ec_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
);
outb(sc
->ec_io_nic_addr
+ EN0_TCNTLO
, len
& 0xff);
outb(sc
->ec_io_nic_addr
+ EN0_TCNTHI
, len
>> 8);
ec_cmd_reg
= inb(sc
->ec_io_nic_addr
+ EN_CCMD
);
outb(sc
->ec_io_nic_addr
+ EN_CCMD
, ec_cmd_reg
|ENC_TRANS
);
int ec_cmd_reg
, ec_sts_reg
;
register struct ec_softc
*sc
= &ec_softc
[unit
];
* Get current command register and interrupt status.
* Turn off interrupts while we take care of things.
ec_cmd_reg
= inb(sc
->ec_io_nic_addr
+ EN_CCMD
);
ec_sts_reg
= inb(sc
->ec_io_nic_addr
+ EN0_ISR
);
outb(sc
->ec_io_nic_addr
+ EN0_IMR
, 0x0);
outb(sc
->ec_io_nic_addr
+ EN0_ISR
, ec_sts_reg
);
* have we lost ourselves somewhere?
if(ec_sts_reg
& ENISR_TX_ERR
)
sc
->ec_if
.if_collisions
+= inb(sc
->ec_io_nic_addr
+ EN0_TCNTLO
);
if(ec_sts_reg
& ENISR_RX_ERR
)
(void) inb(sc
->ec_io_nic_addr
+ 0xD);
(void) inb(sc
->ec_io_nic_addr
+ 0xE);
(void) inb(sc
->ec_io_nic_addr
+ 0xF);
* Normal transmit complete.
if(ec_sts_reg
&ENISR_TX
|| ec_sts_reg
&ENISR_TX_ERR
)
* Normal receive notification
if(ec_sts_reg
&(ENISR_RX
|ENISR_RX_ERR
))
ec_start_output(&sc
->ec_if
);
* Reenable onboard interrupts.
outb(sc
->ec_io_nic_addr
+ EN_CCMD
, ec_cmd_reg
);
outb(sc
->ec_io_nic_addr
+ EN0_IMR
, 0x3f);
if(ec_sts_reg
=inb(sc
->ec_io_nic_addr
+ EN0_ISR
))
register struct ec_softc
*sc
= &ec_softc
[unit
];
sc
->ec_flags
&= ~EC_TXBUSY
;
sc
->ec_if
.if_collisions
+= inb(sc
->ec_io_nic_addr
+ EN0_TCNTLO
);
* (This gave me the most trouble so excuse the mess.)
register struct ec_softc
*sc
= &ec_softc
[unit
];
* Traverse the receive ring looking for packets to pass back.
* The search is complete when we find a descriptor not in use.
bnry
= inb(sc
->ec_io_nic_addr
+ EN0_BOUNDARY
);
curr
= inb(sc
->ec_io_nic_addr
+ EN1_CURPAG
);
/* get pointer to this buffer header structure */
ecr
= (struct ec_ring
*)(sc
->ec_vmem_addr
+ ((bnry
-EC_VMEM_OFFSET
) << 8));
/*if (len > 30 && len <= ETHERMTU+100) */
ecread(sc
, (caddr_t
)(ecr
+ 1), len
);
/*else printf("reject:%x bnry:%x curr:%x", len, bnry, curr);*/
/* advance on chip Boundry register */
if((caddr_t
) ecr
+ EC_PAGE_SIZE
- 1 > sc
->ec_vmem_end
) {
outb(sc
->ec_io_nic_addr
+ EN0_BOUNDARY
,
(sc
->ec_vmem_size
/ EC_PAGE_SIZE
) - 1 - EC_VMEM_OFFSET
);
if (len
> 30 && len
<= ETHERMTU
+100)
bnry
= ecr
->ec_next_packet
;
/* watch out for NIC overflow, reset Boundry if invalid */
if ((bnry
- 1) < EC_RXBUF_OFFSET
) {
outb(sc
->ec_io_nic_addr
+ EN0_BOUNDARY
,
(sc
->ec_vmem_size
/ EC_PAGE_SIZE
) - 1 - EC_VMEM_OFFSET
);
outb(sc
->ec_io_nic_addr
+ EN0_BOUNDARY
, bnry
-1);
/* refresh our copy of CURR */
curr
= inb(sc
->ec_io_nic_addr
+ EN1_CURPAG
);
#define ecdataaddr(sc, eh, off, type) \
((type) ((caddr_t)((eh)+1)+(off) >= (sc)->ec_vmem_end) ? \
(((caddr_t)((eh)+1)+(off))) - (sc)->ec_vmem_end \
((caddr_t)((eh)+1)+(off)))
register struct ec_softc
*sc
;
register struct ether_header
*eh
;
struct mbuf
*m
, *ecget();
* 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(*ecdataaddr(sc
, eh
, off
, u_short
*));
resid
= ntohs(*(ecdataaddr(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
= ecget(buf
, len
, off
, &sc
->ec_if
, sc
);
ether_input(&sc
->ec_if
, eh
, m
);
register struct ifnet
*ifp
;
register struct ifaddr
*ifa
= (struct ifaddr
*)data
;
struct ec_softc
*sc
= &ec_softc
[ifp
->if_unit
];
struct ifreq
*ifr
= (struct ifreq
*)data
;
switch (ifa
->ifa_addr
->sa_family
) {
ec_init(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
);
*(union ns_host
*)(sc
->arpcom
.ac_enaddr
);
* The manual says we can't change the address
* while the receiver is armed,
ifp
->if_flags
&= ~IFF_RUNNING
;
bcopy((caddr_t
)ina
->x_host
.c_host
,
(caddr_t
)sc
->arpcom
.ac_enaddr
,
sizeof(sc
->arpcom
.ac_enaddr
));
ec_init(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
,
printf("ec%d: reset\n", unit
);
log(LOG_WARNING
, "ec%d: soft reset\n", unit
);
register struct ec_softc
*sc
= &ec_softc
[unit
];
outb(sc
->ec_io_nic_addr
+ EN_CCMD
, ENC_NODMA
|ENC_STOP
);
outb(sc
->ec_io_nic_addr
+ EN0_IMR
, 0x0);
sc
->ec_flags
&= ~EC_RUNNING
;
* 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
ecget(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
->ec_vmem_end
) {
unsigned toend
= sc
->ec_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
);