Coherent4.2.10/conf/streams/src/ether.c
/* STREAMS style driver for the DP8390 Ethernet interface */
/*
* An interesting problem is how to manage the interface address binding,
* especially for upper protocols (specifically, the Internet ARP protocol)
* that need to advertise the physical host address, especially in the
* presence of multiple hardware adapters (or network types).
*
* For now, I am happy with having a specific version of ARP for each network
* type, and having ARP catch any M_PROTO SP_BIND messages going past.
*/
#include <sys/kernel.h>
#include <sys/dma.h>
#include <sys/stream.h>
#include <sys/strmlib.h>
#include <sys/strproto.h>
#include <sys/dp8390.h>
#include <sys/ether.h>
#include <sys/ints.h>
#include <sys/netstuff.h>
#include <string.h>
/*
* Allow up to 8 minor devices connected to us - each one receives a copy of
* all incoming traffic, and each can place datagrams.
*/
#define MAXMINOR 8
static queue_t * minortab[MAXMINOR];
#ifdef M68K
/*
* The 68070 uses memory mapped I/O exclusively, so treat the 8390 as a
* structure at a fixed location in memory. The header file also defines
* the padding of the structure on the 16-bit 68000 bus.
*
* We usually assign this to a local to put the base into an address
* register, which gets faster code than using the folded immediate address.
*/
#define DP8390 volatile dp8390 *
#define DP8390_BASE ((DP8390)(0x1FFC00 + PHYSICAL_SEG))
#define DEVICE _device
#define DEVICE_REG DP8390 DEVICE = DP8390_BASE;
#define input(dev, dp_reg) (dev)->dp_pg0rd.dp_reg
#define input1(dev, dp_reg) (dev)->dp_pg1rdwr.dp_reg
#define output(dev, dp_reg, val) (dev)->dp_pg0wr.dp_reg = val
#define output1(dev, dp_reg, val) (dev)->dp_pg1rdwr.dp_reg = val
#endif
#ifdef IBMPC
/*
* The 8086 has special instructions for I/O, but if we treat the base
* address as a NEAR mode pointer (16-bit, current data segment) then cast
* it to a short for the I/O instructions, we'll have the right kind of
* effect.
*
* We DON'T assign the base to a local, since better code is produced by
* folding the constants and loading a new one into DX every I/O.
*
* Note that we emit a JMP $+2 just before the I/O instruction to waste a
* little time, as a NIC CS should not occur more often than 400ns.
*/
#define DP8390 dp8390 near *
#define DP8390_BASE ((DP8390)0x300)
#define DEVICE DP8390_BASE
#define DEVICE_REG
#define _input(addr) (_DX=(UWORD)addr, __emit__ (0xEB, 0, 0xEB, 0, 0xEB, 0, 0xEB, 0, 0xEB, 0, 0xEC), _AL)
#define _output(addr,val) (_DX=(UWORD)addr, _AL = val, __emit__ (0xEB, 0, 0xEB, 0, 0xEB, 0, 0xEB, 0, 0xEB, 0, 0xEE))
#define input(dev, dp_reg) _input (&(dev)->dp_pg0rd.dp_reg)
#define input1(dev, dp_reg) _input (&(dev)->dp_pg1rdwr.dp_reg)
#define output(dev, dp_reg, val) _output (&(dev)->dp_pg0wr.dp_reg,(val))
#define output1(dev, dp_reg, val) _output (&(dev)->dp_pg1rdwr.dp_reg,(val))
#define ETHER_IRQ 2
#endif
/*
* For scheduling remote DMA operations on the NIC, since there is only one
* channel on the NIC which is shared between remote read and remote write
* operations. Complicated since the TX buffer area is held until the NIC has
* actually completed the transmission, and because RX DMA may be halted in the
* middle of a packet due to STREAMS buffer depletion.
*
* Made a structure in the anticipation of one day having multiple interfaces.
*/
static struct ethcb { /* control block for a single interface */
char tx_state; /* TX DMA in progress or TX DMA blocked */
/* cleared when packet send completed */
char rx_state; /* RX DMA in progress or waiting for buffers */
char rx_pending; /* flag that RX packets for DMA are waiting */
short rx_length; /* remaining length of RX packet */
UWORD rx_base; /* NIC buffer address of RX DMA */
mblk_t * rx_start; /* first mblk in RX packet */
mblk_t * rx_current; /* current RX DMA buffer */
short tx_length; /* length of packet being transmitted */
short tx_count; /* count packets sent */
short rx_count; /* RX activity */
char tx_error; /* packet never ended transmission */
short tx_lastcount; /* count at last timer tick */
short rx_lastcount; /* count at last timre tick */
/*
* About write packet queueing - since this device effectively functions as a
* kind of multiplexor, flow control needs explicit attention. I use the
* general methods developed for the other multiplexing drivers (like TCP),
* namely the generic scheduler in my STREAMS implementation.
*/
/* schedule of driver queues that have output packets queued */
struct schedule tx_sched;
struct dp8390info dp8390info;
/* useful addresses that we may wish to keep copies of */
Eth_addr myaddr; /* our own address */
/* This flag gets set when the NIC tests OK */
int netok;
/* error counter extensions */
int errcnt0, errcnt1, errcnt2;
} eth;
#define ETH LONG_TO_PTR (eth, struct ethcb *)
#define QETH ((struct ethcb *) q->q_ptr)
/*
* Possible states for TX
*/
#define TX_IDLE 0
#define TX_DMA 1
#define TX_SEND 2
/*
* Possible events for TX
*/
#define TXE_CRANK 0
#define TXE_DMADONE 1
#define TXE_SENDDONE 2
/*
* Possible states for RX.
*
* The two states RX_HALT and RX_BLOCKED distinguish whether or not to
* continue RX dma after a TX DMA or not.
*/
#define RX_IDLE 0
#define RX_DMA 1
#define RX_HALT 2
#define RX_BLOCKED 3
/*
* possible events for RX
*/
#define RXE_CRANK 0
#define RXE_GOTMEM 1
#define RXE_NOMEM 2
#define RXE_DMADONE 3
#define RXE_ALLDONE 4
/*
* To aid in detecting when the cable has been unplugged (which causes a
* constant collision state, so the backoff count doesn't get incremented).
*/
static P_TIMER eth_timer = NIL_TIMER;
/*
* program the address into the address registers.
* Note : assumes that register set 1 has already been chosen!
*/
static void set_addr (int selpage)
{
DEVICE_REG
if (selpage)
output (DEVICE, dp_cr, CR_PS_P1 | CR_DM_ABORT | CR_STA);
output1 (DEVICE, dp_par0, eth.myaddr.e[0]);
output1 (DEVICE, dp_par1, eth.myaddr.e[1]);
output1 (DEVICE, dp_par2, eth.myaddr.e[2]);
output1 (DEVICE, dp_par3, eth.myaddr.e[3]);
output1 (DEVICE, dp_par4, eth.myaddr.e[4]);
output1 (DEVICE, dp_par5, eth.myaddr.e[5]);
if (input1 (DEVICE, dp_par0) != eth.myaddr.e[0] ||
input1 (DEVICE, dp_par1) != eth.myaddr.e[1] ||
input1 (DEVICE, dp_par2) != eth.myaddr.e[2] ||
input1 (DEVICE, dp_par3) != eth.myaddr.e[3] ||
input1 (DEVICE, dp_par4) != eth.myaddr.e[4] ||
input1 (DEVICE, dp_par5) != eth.myaddr.e[5] )
STREAMS_DEBUG ("Device not accepting physical address!\n");
if (selpage)
output (DEVICE, dp_cr, CR_PS_P0 | CR_DM_ABORT | CR_STA);
}
/*
* Since the DMA channel to the DP8390 is word-wide, I have taken special care
* to deal with odd-length subunits of packets, or packets that are not word
* aligned with pullupmsg(). It's not fast, but such ugly messages should not
* occur often.
*/
extern void streams_levels (void);
static void eth_timer_func (ULONG eth)
{
char buf [4];
TIMER_SET (eth_timer, 1, eth_timer_func, eth);
if (ETH->tx_lastcount != ETH->tx_count)
ETH->tx_lastcount = ETH->tx_count;
else if (ETH->tx_state != TX_IDLE &&
ETH->tx_error == 0) {/* packet send taking too long */
mblk_t * temp;
ETH->tx_error = 1;
/* dispose of all the packets queued here */
while (ETH->tx_sched.s_head != NULL) {
temp = getq (ETH->tx_sched.s_head);
freemsg (temp);
muxrobin (& ETH->tx_sched);
}
#if 1
STREAMS_DEBUG ("Stuck in TX ");
#endif
}
if (ETH->rx_lastcount != ETH->rx_count)
ETH->rx_lastcount = ETH->rx_count;
else if (ETH->rx_state != RX_IDLE &&
ETH->tx_error == 0) {
ETH->tx_error = 1;
STREAMS_DEBUG ("Stuck in RX ");
}
}
static UWORD dma_base; /* DMA base address in NIC */
static UWORD dma_len; /* remote DMA length */
static void *dma_buf; /* local buffer address */
static int dma_dir; /* 1 => remote write */
/* start a piece of remote DMA, rounding the transfer size up */
void dma_start (void)
{
DEVICE_REG
int xfer_len;
if (dma_dir) {
output(DEVICE, dp_rbcr0, 15); /* dummy byte count */
output(DEVICE, dp_rbcr1, 0);
output(DEVICE, dp_cr, CR_PS_P0 | CR_DM_RR | CR_STA);
// for (xfer_len = 0; xfer_len < 5; xfer_len ++)
// ;
}
output(DEVICE, dp_rsar0, dma_base & 0xff); /* remote DMA base */
output(DEVICE, dp_rsar1, dma_base >> 8 );
if ((xfer_len = dma_len) & 1)
xfer_len++;
output(DEVICE, dp_rbcr0, xfer_len & 0xff); /* remote DMA count */
output(DEVICE, dp_rbcr1, xfer_len >> 8 );
output(DEVICE, dp_cr, dma_dir ?
CR_PS_P0 | CR_DM_RW | CR_STA :
CR_PS_P0 | CR_DM_RR | CR_STA);
dma_setup (DMA_0, (char *)dma_buf, xfer_len >> 1,
dma_dir ? DMAF_TODEV|DMAF_WORDWIDE|DMAF_BURST :
DMAF_TOMEM|DMAF_WORDWIDE|DMAF_BURST);
}
/*
* During test, we want to poll for DMA completion. Also used when reading
* NIC packet header, since it's so short.
*/
void wait_for_dma (void)
{
DEVICE_REG
for (;;) {
while ((input (DEVICE, dp_cr) & CR_DM_ABORT) == 0)
;
if (dma_done (DMA_0) == -1)
break;
dma_reset (DMA_0);
dma_start (); /* restart */
}
}
/*
* retrieve NIC memory from "base" to "buffer"
*/
static INLINE void get_mem (int base, int len, void * buffer)
{
dma_base = base;
dma_len = len;
dma_buf = buffer;
dma_dir = 0;
dma_start ();
}
static INLINE void set_mem (int base, int len, unsigned char * buf)
{
dma_base = base;
dma_len = len;
dma_buf = buf;
dma_dir = 1;
dma_start ();
}
/* send streams message block "mp" to NIC */
static void send_block (queue_t * q, mblk_t * mp)
{
/*
* identify the losers, and fix them with pullupmsg(), which both
* concatenates (fixing length) and aligns at the same time.
*/
if ((mp->b_cont != NULL && ((mp->b_wptr - mp->b_rptr) & 1) != 0) ||
((ULONG)mp->b_rptr & 1) != 0)
pullupmsg (mp, -1);
dma_base = (QETH->dp8390info.dpi_tbuf << 8) + QETH->tx_length;
dma_len = mp->b_wptr - mp->b_rptr;
dma_buf = mp->b_rptr;
dma_dir = 1;
dma_start ();
}
/* initiate transmission of the buffered packet, with length "len". */
static void send_pkt (struct ethcb * eth)
{
DEVICE_REG
if (eth->tx_length < 64) /* Ethernet magic */
eth->tx_length = 64;
output (DEVICE, dp_tbcr0, eth->tx_length & 0xff);
output (DEVICE, dp_tbcr1, eth->tx_length >> 8);
output (DEVICE, dp_tpsr, eth->dp8390info.dpi_tbuf);
output (DEVICE, dp_cr, CR_PS_P0|CR_DM_ABORT|CR_TXP|CR_STA);
}
static void rx_next (struct ethcb * eth, int event);
static void tx_next (struct ethcb * eth, int event)
{
again:
SCREEN (26) = 'T';
SCREEN (28) = eth->tx_state + '0';
SCREEN (30) = event + '0';
switch (eth->tx_state) {
case TX_IDLE:
if (event == TXE_CRANK) {
if (eth->tx_sched.s_head != NULL &&
eth->rx_state != RX_DMA) {
eth->tx_count ++;
eth->tx_state = TX_DMA;
eth->tx_length = 0;
send_block (eth->tx_sched.s_head,
eth->tx_sched.s_head->q_first);
}
return;
}
break;
case TX_DMA:
if (event == TXE_DMADONE) {
mblk_t * temp = getq (eth->tx_sched.s_head),
* msg = temp->b_cont;
eth->tx_length += dma_len;
freeb (temp);
if (msg != NULL) {
putbq (eth->tx_sched.s_head, msg);
send_block (eth->tx_sched.s_head, msg);
} else {
send_pkt (eth); /* spit it out */
muxrobin (& eth->tx_sched);
eth->tx_state = TX_SEND;
rx_next (eth, RXE_CRANK);
}
return;
}
if (event == TXE_CRANK)
return;
break;
case TX_SEND:
if (event == TXE_SENDDONE) {
eth->tx_error = 0;
eth->tx_state = TX_IDLE;
rx_next (eth, RXE_CRANK);
event = TXE_CRANK;
goto again;
}
if (event == TXE_CRANK)
return;
break;
}
STREAMS_DEBUG ("Bad TX state/event ");
}
static void rx_got_mem (long eth)
{
short s = SPL7 ();
rx_next (LONG_TO_PTR (eth, struct ethcb *), RXE_GOTMEM);
SPLX (s);
}
static void rx_next (struct ethcb * eth, int event)
{
mblk_t * mp;
int len;
again:
SCREEN (26) = 'R';
SCREEN (28) = eth->rx_state + '0';
SCREEN (30) = event + '0';
switch (eth->rx_state) {
case RX_IDLE:
if (event == RXE_CRANK) {
if (eth->rx_pending && eth->tx_state != TX_DMA) {
/* busy wait on the header part */
struct rcvdheader nic;
get_mem (eth->dp8390info.dpi_next << 8, 4, & nic);
wait_for_dma ();
eth->dp8390info.dpi_next = nic.rp_next;
/*
* count in packet header includes FCS, which we
* are not really interested in.
*/
eth->rx_length = (nic.rp_rbch << 8) + nic.rp_rbcl - 4;
eth->rx_base = dma_base + 4;
if ((input (DEVICE, dp_isr) & ISR_RDC) == 0)
STREAMS_DEBUG ("IMPOSSIBLE 1");
output (DEVICE, dp_isr, ISR_RDC);
eth->rx_count ++;
eth->rx_state = RX_DMA;
event = RXE_DMADONE;
if (input (DEVICE, dp_isr) & ISR_RDC)
STREAMS_DEBUG ("IMPOSSIBLE 2");
goto again;
}
return;
}
break;
case RX_DMA:
len = eth->rx_length;
if (len > 768) {
if (len > 1024)
len = 1024;
} else if (len > 192) {
if (len > 256)
len = 256;
} else if (len > 64)
len = 64;
if (event == RXE_DMADONE) {
if (len == 0) {
event = RXE_ALLDONE;
goto again;
}
if ((mp = allocb (len, BPRI_MED)) == NULL) {
while (len < 1024) {
if ((mp = allocb (len, BPRI_LO)) != NULL)
break;
len *= 2;
}
if (len >= 1024) {
event = RXE_NOMEM;
goto again;
}
if (len > eth->rx_length)
len = eth->rx_length;
}
eth->rx_count ++;
mp->b_wptr += len;
if (eth->rx_start == NULL)
eth->rx_start = mp; /* first buffer */
else
eth->rx_current->b_cont = mp; /* link buffers */
eth->rx_current = mp;
get_mem (eth->rx_base, len, mp->b_rptr);
/* take care to wrap the rx_base! */
if (((eth->rx_base += len) >> 8) >= eth->dp8390info.dpi_pstop)
eth->rx_base -= (eth->dp8390info.dpi_pstop - eth->dp8390info.dpi_pstart) << 8;
eth->rx_length -= len;
return;
}
if (event == RXE_NOMEM) {
eth->rx_state = RX_HALT;
bufcall (len, BPRI_MED, rx_got_mem, (long) eth);
tx_next (eth, TXE_CRANK);
event = RXE_CRANK;
goto again;
}
if (event == RXE_ALLDONE) {
/*
* finished a piece of receive DMA - advance buffer
* queue endpoint over just received packet. In
* addition, we should check to see if there are any
* more buffered receive packets.
*/
output (DEVICE, dp_bnry, eth->dp8390info.dpi_next == eth->dp8390info.dpi_pstart ?
eth->dp8390info.dpi_pstop - 1 :
eth->dp8390info.dpi_next - 1);
output (DEVICE, dp_cr, CR_PS_P1 | CR_DM_ABORT | CR_STA);
if (input1 (DEVICE, dp_curr) == eth->dp8390info.dpi_next)
eth->rx_pending = 0;
output (DEVICE, dp_cr, CR_PS_P0 | CR_DM_ABORT | CR_STA);
if (eth->rx_start != NULL) {
/* send a copy of every message upwards */
for (len = 0; len < MAXMINOR ; len ++)
if (minortab [len] &&
(mp = dupmsg (eth->rx_start)) != NULL)
putq (minortab [len], mp);
SCREEN (22) ++;
freemsg (eth->rx_start);
eth->rx_start = eth->rx_current = NULL;
}
eth->rx_state = RX_IDLE;
tx_next (eth, TXE_CRANK);
event = RXE_CRANK;
goto again;
}
return;
case RX_HALT:
if (event == RXE_GOTMEM) {
if (eth->tx_state == TX_DMA) {
eth->rx_state = RX_BLOCKED;
return;
}
eth->rx_state = RX_DMA;
event = RXE_DMADONE;
goto again;
}
if (event == RXE_CRANK)
return;
break;
case RX_BLOCKED:
if (event == RXE_CRANK && eth->tx_state != TX_DMA) {
eth->rx_state = RX_DMA;
event = RXE_DMADONE;
goto again;
}
break;
default:
STREAMS_DEBUG ("Impossible state");
return;
}
STREAMS_DEBUG ("Bad RX state/event combo ");
}
/* Referenced in "system.asm", here is the interrupt service routine */
HW_INT_FUNC (void) etherint (void)
{
DEVICE_REG
UBYTE status = input (DEVICE, dp_isr);
static UBYTE reentry;
if (reentry ++ != 0) {
STREAMS_DEBUG ("Reentry ");
reentry --;
return;
}
/*
* GCC - specific kernel hack, save all used registers on entry to
* this function. We don't use INTERRUPT for the PC version since
* hardware IRQ handlers need assembly-language wrappers to give
* us enough stack to use STREAMS library routines.
*/
GNU_INTERRUPT;
rescan:
/* reset the flag bits that were indicated for us */
output (DEVICE, dp_isr, status);
if (eth.netok == 0) { /* device not active! */
reentry --;
return;
}
if ((status & ISR_PRX) != 0) {
eth.rx_pending = 1;
rx_next (& eth, RXE_CRANK);
}
SCREEN (16) ++;
if ((status & ISR_PTX) != 0) {
if ((input (DEVICE, dp_tsr) & ~(TSR_COL | TSR_CRS | TSR_PTX | TSR_DFR))
!= 0)
STREAMS_DEBUG ("Packet TX error\n");
SCREEN (18) ++;
tx_next (& eth, TXE_SENDDONE);
}
/*
* The ISR_RDC bit is gated with CR_DM_ABORT since the fix for the
* NIC remote write problem causes spurious RDCs, which are not
* normally detected unless the TX DMA is delayed by arrive DMA to
* the NIC local memory.
*/
if ((status & ISR_RDC) != 0 &&
(input (DEVICE, dp_cr) & CR_DM_ABORT) != 0)
if (dma_done (DMA_0) != -1) {
dma_reset (DMA_0);
dma_start ();
} else if (dma_dir)
tx_next (& eth, TXE_DMADONE);
else
rx_next (& eth, RXE_DMADONE);
if ((status & (ISR_RXE | ISR_TXE | ISR_OVW | ISR_CNT)) != 0) {
if (status & ISR_RXE)
STREAMS_DEBUG ("Receive error\n");
if (status & ISR_TXE) {
/*
* This can happen due to excessive collisions if
* there is an open cable end or due to a FIFO
* underrun (which indicates a board problem).
*/
if ((input (DEVICE, dp_tsr) & (TSR_FU)) != 0)
STREAMS_DEBUG ("TX FIFO underrun");
tx_next (& eth, TXE_SENDDONE);
}
if (status & ISR_OVW)
STREAMS_DEBUG ("Buffer overflow\n");
if (status & ISR_CNT) {
/* reset the error counters by reading them */
eth.errcnt0 += input (DEVICE, dp_cntr0);
eth.errcnt1 += input (DEVICE, dp_cntr1);
eth.errcnt2 += input (DEVICE, dp_cntr2);
}
}
if ((status = input (DEVICE, dp_isr)) != 0)
goto rescan;
reentry --;
}
/*
* Routines for reading from/writing to the NIC memory.
*/
/* queue a packet for transmission */
static void queue_packet (queue_t *q, mblk_t *mp)
{
if (QETH->tx_error != 0)
return;
short s = SPL7 ();
/*
* We queue the message "normally" for STREAMS so that flow control
* operates as one would expect. Then we place the streams queue onto
* our own "scheduling" list for processing by the NIC interrupt.
*/
putq (q, mp);
qschedule (q, & QETH->tx_sched);
/*
* If there is no I/O presently being executed, we must start
* the ball rolling.
*/
tx_next (QETH, TXE_CRANK);
SPLX (s);
}
/*
* Set up the DP8390. This initialisation procedure comes fairly directly
* from the NatSemi manual - much dark magic.
*/
static void chipinit (Eth_addr * addr)
{
unsigned char * testbuf, * test2;
mblk_t * testmem;
int i, start = -1, len;
DEVICE_REG
if (input (DEVICE, dp_cr) == 0xFF) {
eth.netok = -1;
return;
}
/* reset dp8390 */
output(DEVICE, dp_cr, CR_STP|CR_PS_P0|CR_DM_ABORT);
#ifdef M68K
output(DEVICE, dp_dcr,
DCR_LOOP | DCR_WORDWIDE | DCR_BIGENDIAN | DCR_8BYTES);
#endif
#ifdef IBMPC
set_hw_int (ETHER_IRQ, etherint);
output(DEVICE, dp_dcr, DCR_LOOP | DCR_WORDWIDE | DCR_8BYTES);
#endif
output(DEVICE, dp_rbcr0, 0);
output(DEVICE, dp_rbcr1, 0);
output(DEVICE, dp_rcr, RCR_AB);
output(DEVICE, dp_tcr, TCR_INTERNAL);
output(DEVICE, dp_isr, 0xff);
output(DEVICE, dp_imr, 0);
/*
* We start the device here (and shut it down later) since the
* Remote DMA doesn't work until we do, and we can't find out how
* much memory we have until we can DMA. (Sigh)
*/
output (DEVICE, dp_cr, CR_PS_P0 | CR_DM_ABORT | CR_STA);
/*
* Since the NIC is in internal loopback mode (TCR_INTERNAL), and
* interrupts are disabled, we can now test the NIC's memory to
* determine how much there is.
*/
testmem = getbuf (1024); /* get temporary space */
testbuf = testmem->b_rptr;
test2 = testbuf + 256;
for (i = 0 ; i < 256 ; i ++)
testbuf [i] = i;
for (i = 0 ; i < 256 ; i ++) {
testbuf [0] = i;
testbuf [1] = ~i;
set_mem (i << 8, 256, testbuf);
wait_for_dma ();
}
/* find start of memory */
for (i = 0 ; i < 256 ; i++) {
testbuf [0] = i;
testbuf [1] = ~i;
get_mem (i << 8, 256, test2);
wait_for_dma ();
if (memcmp (test2, testbuf, 256) == 0)
/* got one - either start or extend a span */
if (start < 0) {
start = i;
len = 1;
} else
len++;
else
if (start >= 0)
break;
}
if (len < 16) { /* needs at least 4k to work */
eth.netok = -1;
goto alldone;
}
eth.dp8390info.dpi_pstart = start + 8;
eth.dp8390info.dpi_pstop = start + len;
eth.dp8390info.dpi_tbuf = start;
/*
* Since we're at it... if the board has an address PROM, it should
* be at address 0 (where the NIC has trouble locating RAM), so
* unless we're given an address, try finding our location.
*/
if (addr != NULL)
eth.myaddr = * addr;
else if (start > 0) { /* may have PROM, there's no RAM */
get_mem (0, 12, testbuf);
wait_for_dma ();
/* Fetch the PROM data from every second word */
for (i = 0;i < 6;i++)
eth.myaddr.e [i] = testbuf [i + i];
}
STREAMS_DEBUG_LONG (len << 8, 16);
STREAMS_DEBUG ("h bytes of Ethernet memory, Address = $");
for (i = 0 ; i < 6 ; i ++)
STREAMS_DEBUG_LONG (eth.myaddr.e [i], 16);
STREAMS_DEBUG (", IRQ ");
STREAMS_DEBUG_LONG (ETHER_IRQ, 10);
STREAMS_DEBUG ("\r\n");
output (DEVICE, dp_isr, 0xFF);
output (DEVICE, dp_tpsr, eth.dp8390info.dpi_tbuf);
output (DEVICE, dp_pstart, eth.dp8390info.dpi_pstart);
output (DEVICE, dp_bnry, eth.dp8390info.dpi_pstart);
output (DEVICE, dp_pstop, eth.dp8390info.dpi_pstop);
output (DEVICE, dp_cr, CR_PS_P1|CR_DM_ABORT|CR_STP);
output1 (DEVICE, dp_curr, eth.dp8390info.dpi_pstart + 1);
eth.dp8390info.dpi_next = eth.dp8390info.dpi_pstart + 1;
set_addr (0);
output (DEVICE, dp_cr, CR_PS_P0|CR_DM_ABORT|CR_STA);
output (DEVICE, dp_tcr, 0);
output (DEVICE, dp_imr, 0x7F); /* all interrupts */
if ((eth_timer = TIMER_ALLOC ()) != NIL_TIMER)
TIMER_SET (eth_timer, 1, eth_timer_func, (long) & eth);
eth.netok ++;
alldone:
freemsg (testmem);
}
/*
* Shut down the E'net chip. Whenever I get around to it, this will send
* the ARP-style packet that indicates this station is dead'n'gone. This
* will use the poll-type DMA access shown in the open.
*/
static void chipoff (void)
{
DEVICE_REG
if (eth.netok < 0)
return;
#ifdef IBMPC
set_hw_int (ETHER_IRQ, NULL);
#endif /* IBMPC */
output (DEVICE, dp_imr, 0);
output (DEVICE, dp_cr, CR_PS_P0 | CR_DM_ABORT | CR_STP);
}
/*
* stream open routine for the driver
*/
static int ethopen (queue_t * q, dev_t dev, int /* flag */, int sflag)
{
/* refuse to do anything unless everything checked out OK at boot */
W (q)->q_ptr = q->q_ptr = & eth;
if (QETH->netok < 0)
return OPENFAIL;
if (QETH->netok == 0) {
chipinit (NULL);
if (QETH->netok < 0)
return OPENFAIL;
} else
QETH->netok ++;
/* allow regular or clone device open */
if (sflag == CLONEOPEN) {
for (dev = 0;dev < MAXMINOR;dev++)
if (minortab[dev] == NULL)
break;
} else
dev = minor(dev);
if (dev < 0 || dev >= MAXMINOR)
return OPENFAIL;
minortab[dev] = q;
return dev;
}
/* ethernet write put procedure */
static void ethwput (queue_t * q, mblk_t * mp)
{
Framehdr * frame;
struct sp_bind * spbind;
Eth_addr * addr;
switch (mp->b_datap->db_type) {
case M_PROTO:
spbind = (struct sp_bind *)mp->b_rptr;
addr = (Eth_addr *)(spbind + 1);
if (spbind->protoid == SP_BIND) {
if (spbind->family != AF_ENET) {
freemsg (mp);
return;
}
if (mp->b_wptr > (unsigned char *) addr) {
QETH->myaddr = * addr ++;
set_addr (1);
}
freemsg (mp);
mp = allocb (sizeof (struct sp_bind) + sizeof (Eth_addr),
BPRI_MED);
if (mp != NULL) {
spbind = (struct sp_bind *)mp->b_rptr;
addr = (Eth_addr *)(spbind + 1);
spbind->protoid = SP_BIND;
spbind->family = AF_ENET;
spbind->connect = TPS_BOUND;
* addr ++ = QETH->myaddr;
mp->b_wptr = (unsigned char *) addr;
mp->b_datap->db_type = M_PROTO;
qreply (q, mp);
}
return;
}
default:
nogood:
freemsg (mp);
break;
case M_DATA:
frame = (Framehdr *)mp->b_rptr;
if ((unsigned char *)(frame + 1) > mp->b_wptr)
goto nogood;
SCREEN (20) ++;
frame->f_srcaddr = QETH->myaddr;
queue_packet (q, mp);
break;
case M_FLUSH:
/* canonical flush processing */
if ((* mp->b_rptr & FLUSHW) != 0)
flushq (q, FLUSHDATA);
if ((* mp->b_rptr & FLUSHR) != 0) {
flushq (RD(q), FLUSHDATA);
* mp->b_rptr &= ~FLUSHW;
qreply (q, mp);
} else
freemsg (mp);
break;
case M_IOCTL:
mp->b_datap->db_type = M_IOCNAK;
qreply (q, mp);
break;
}
}
/* read service routine */
static void ethrsrv (queue_t * q)
{
mblk_t * mp;
while ((mp = getq (q)) != NULL)
if (canput (q->q_next))
putnext (q, mp);
else {
putbq (q, mp);
break;
}
SCREEN (24) ++;
}
/* close procedure - called on last close of stream */
static int ethclose(queue_t *q)
{
int i;
if (QETH->netok == 1)
chipoff ();
QETH->netok --;
for (i = 0;i < MAXMINOR;i++)
if (minortab[i] == q)
break;
minortab[i] = NULL;
return(0);
}
static struct module_info ethrinfo = {
0, "ether", 0, 1024, 1024, 0
};
static struct qinit ethread = {
NULLFUNC, ethrsrv, ethopen, ethclose, NULLFUNC,
ðrinfo, NULLSTAT
}, ethwrite = {
ethwput, NULLFUNC, NULLFUNC, NULLFUNC, NULLFUNC,
ðrinfo, NULLSTAT
};
struct streamtab ethinfo = {
ðread, ðwrite, NULLINIT, NULLINIT
};