/* 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 };