To: linus, alan Cc: lkml Subject: [PATCH] Amiga GVP I/O Extender PLIP From: Linux/m68k legacy Add a PLIP driver for the Amiga GVP I/O Extender's parallel port --- drivers/char/16c552.h | 165 +++++++ drivers/char/ioext.h | 107 ++++ drivers/char/plip_ioext.c | 1057 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1329 insertions(+) --- /dev/null +++ b/drivers/char/16c552.h @@ -0,0 +1,165 @@ +/* + * Definitions for the 16c552 DACE + * (dual-asynchronous-communications-element) used on the GVP + * IO-Extender. + * + * Basically this is two 16c550 uarts's and a parallel port, which is + * why the serial definitions should be valid for the 16c550 uart + * aswell. + * + * Data was taken from National Semiconductors duart 16c552 + * data-sheets and the Texas Instruments DACE 16c552 data-sheets (the + * NS version of the chip is _non_ standard and their data-sheets did + * cost me several wasted hours of work). + * + * This file is (C) 1995 Jes Sorensen (jds@kom.auc.dk) + * + * Moved from drivers/char/ to include/linux/, because it's useful + * on more than just the one card. I'm using it on the hp300 DCA + * serial driver, for example. + * -- Peter Maydell 05/1998 + */ + +#ifndef _16C552_H_ +#define _16C552_H_ + +/* Serial stuff */ + +struct uart_16c550 { + volatile u_char skip0; + volatile u_char RBR; + volatile u_char skip1; + volatile u_char IER; + volatile u_char skip2; + volatile u_char IIR; + volatile u_char skip3; + volatile u_char LCR; + volatile u_char skip4; + volatile u_char MCR; + volatile u_char skip5; + volatile u_char LSR; + volatile u_char skip6; + volatile u_char MSR; + volatile u_char skip7; + volatile u_char SCR; +}; + +#define THR RBR +#define FCR IIR +#define DLL RBR +#define DLM IER +#define AFR IIR + +/* + * Bit-defines for the various registers. + */ + + +/* IER */ + +#define ERDAI (1<<0) +#define ETHREI (1<<1) +#define ELSI (1<<2) +#define EMSI (1<<3) + +/* IIR - Interrupt Ident. Register */ + +#define IRQ_PEND (1<<0) /* NOTE: IRQ_PEND=0 implies irq pending */ +#define IRQ_ID1 (1<<1) +#define IRQ_ID2 (1<<2) +#define IRQ_ID3 (1<<3) +#define FIFO_ENA0 (1<<6) /* Both these are set when FCR(1<<0)=1 */ +#define FIFO_ENA1 (1<<7) + +#define IRQ_RLS (IRQ_ID1 | IRQ_ID2) +#define IRQ_RDA (IRQ_ID2) +#define IRQ_CTI (IRQ_ID2 | IRQ_ID3) +#define IRQ_THRE (IRQ_ID1) +#define IRQ_MS 0 + +/* FCR - FIFO Control Register */ + +#define FIFO_ENA (1<<0) +#define RCVR_FIFO_RES (1<<1) +#define XMIT_FIFO_RES (1<<2) +#define DMA_MODE_SEL (1<<3) +#define RCVR_TRIG_LSB (1<<6) +#define RCVR_TRIG_MSB (1<<7) + +#define FIFO_TRIG_1 0x00 +#define FIFO_TRIG_4 RCVR_TRIG_LSB +#define FIFO_TRIG_8 RCVR_TRIG_MSB +#define FIFO_TRIG_14 RCVR_TRIG_LSB|RCVR_TRIG_MSB + +/* LCR - Line Control Register */ + +#define WLS0 (1<<0) +#define WLS1 (1<<1) +#define STB (1<<2) +#define PEN (1<<3) +#define EPS (1<<4) +#define STICK_PARITY (1<<5) +#define SET_BREAK (1<<6) +#define DLAB (1<<7) + +#define data_5bit 0x00 +#define data_6bit 0x01 +#define data_7bit 0x02 +#define data_8bit 0x03 + + +/* MCR - Modem Control Register */ + +#define DTR (1<<0) +#define RTS (1<<1) +#define OUT1 (1<<2) +#define OUT2 (1<<3) +#define LOOP (1<<4) + +/* LSR - Line Status Register */ + +#define DR (1<<0) +#define OE (1<<1) +#define PE (1<<2) +#define FE (1<<3) +#define BI (1<<4) +#define THRE (1<<5) +#define TEMT (1<<6) +#define RCVR_FIFO_ERR (1<<7) + +/* MSR - Modem Status Register */ + +#define DCTS (1<<0) +#define DDSR (1<<1) +#define TERI (1<<2) +#define DDCD (1<<3) +#define CTS (1<<4) +#define DSR (1<<5) +#define RING_I (1<<6) +#define DCD (1<<7) + +/* AFR - Alternate Function Register */ + +#define CONCUR_WRITE (1<<0) +#define BAUDOUT (1<<1) +#define RXRDY (1<<2) + +/* Parallel stuff */ + +/* + * Unfortunately National Semiconductors did not supply the + * specifications for the parallel port in the chip :-( + * TI succed though, so here they are :-) + * + * Defines for the bits can be found by including + */ +struct IOEXT_par { + volatile u_char skip0; + volatile u_char DATA; + volatile u_char skip1; + volatile u_char STATUS; + volatile u_char skip2; + volatile u_char CTRL; +}; + +#endif --- /dev/null +++ b/drivers/char/ioext.h @@ -0,0 +1,107 @@ +/* + * Shared data structure for GVP IO-Extender support. + * + * Merge of ioext.h and ser_ioext.h + */ +#ifndef _IOEXT_H_ +#define _IOEXT_H_ + +#include + +#include "16c552.h" + +#define MAX_IOEXT 5 /* + * The maximum number of io-extenders is 5, as you + * can't have more than 5 ZII boards in any Amiga. + */ + +#define UART_CLK 7372800 + +#define IOEXT_BAUD_BASE (UART_CLK / 16) + +#define IOEXT_MAX_LINES 2 + +#define IOEXT_PAR_PLIP 0x0001 +#define IOEXT_PAR_LP 0x0002 + + +/* + * Macros for the serial driver. + */ +#define curruart(info) ((struct uart_16c550 *)(info->port)) + +#define ser_DTRon(info) curruart(info)->MCR |= DTR +#define ser_RTSon(info) curruart(info)->MCR |= RTS +#define ser_DTRoff(info) curruart(info)->MCR &= ~DTR +#define ser_RTSoff(info) curruart(info)->MCR &= ~RTS + + +/* + * CNTR defines (copied from the GVP SCSI-driver file gvp11.h + */ +#define GVP_BUSY (1<<0) +#define GVP_IRQ_PEND (1<<1) +#define GVP_IRQ_ENA (1<<3) +#define GVP_DIR_WRITE (1<<4) + + +/* + * CTRL defines + */ +#define PORT0_MIDI (1<<0) /* CLR = DRIVERS SET = MIDI */ +#define PORT1_MIDI (1<<1) /* CLR = DRIVERS SET = MIDI */ +#define PORT0_DRIVER (1<<2) /* CLR = RS232, SET = MIDI */ +#define PORT1_DRIVER (1<<3) /* CLR = RS232, SET = MIDI */ +#define IRQ_SEL (1<<4) /* CLR = INT2, SET = INT6 */ +#define ROM_BANK_SEL (1<<5) /* CLR = LOW 32K, SET = HIGH 32K */ +#define PORT0_CTRL (1<<6) /* CLR = RTSx or RXRDYx, SET = RTSx ONLY */ +#define PORT1_CTRL (1<<7) /* CLR = RTSx or RXRDYx, SET = RTSx ONLY */ + + +/* + * This is the struct describing the registers on the IO-Extender. + * NOTE: The board uses a dual uart (16c552), which should be equal to + * two 16c550 uarts. + */ +typedef struct { + char gap0[0x41]; + volatile unsigned char CNTR; /* GVP DMAC CNTR (status register) */ + char gap1[0x11e]; + struct uart_16c550 uart0; /* The first uart */ + char gap2[0xf0]; + struct uart_16c550 uart1; /* The second uart */ + char gap3[0xf0]; + struct IOEXT_par par; /* The parallel port */ + char gap4[0xfb]; + volatile unsigned char CTRL; /* The control-register on the board */ +} IOEXT_struct; + + +typedef struct { + int num_uarts; + int line[IOEXT_MAX_LINES]; + volatile struct uart_16c550 *uart[IOEXT_MAX_LINES]; + IOEXT_struct *board; + int spurious_count; + unsigned char par_use; /* IOEXT_PAR_xxx */ +#if defined(CONFIG_GVPIOEXT_PLIP) || defined(CONFIG_GVPIOEXT_PLIP_MODULE) + struct nt_device *dev; +#endif +#if defined(CONFIG_GVPIOEXT_LP) || defined(CONFIG_GVPIOEXT_LP_MODULE) + struct lp_struct *lp_table; + int lp_dev; + int lp_interrupt; +#endif +} IOExtInfoType; + +/* Number of detected boards. */ +extern int ioext_num; +extern IOExtInfoType ioext_info[MAX_IOEXT]; + +void ioext_plip_interrupt(struct net_device *dev, int *spurious_count); +void ioext_lp_interrupt(int dev, int *spurious_count); + +extern struct net_device ioext_dev_plip[3]; +extern struct lp_struct ioext_lp_table[1]; + +#endif --- /dev/null +++ b/drivers/char/plip_ioext.c @@ -0,0 +1,1057 @@ +/* + * plip_ioext: A parallel port "network" driver for GVP IO-Extender. + * + * Authors: See drivers/net/plip.c + * IO-Extender version by Steve Bennett, + * + * This driver is for use with a 5-bit cable (LapLink (R) cable). + */ + +static const char *version = "NET3 PLIP version 2.2/m68k"; + +#define __NO_VERSION__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +/*#include */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ioext.h" + +#define DEBUG 0 + +/* Map 'struct device *' to our control structure */ +#define PLIP_DEV(DEV) (&ioext_info[(DEV)->irq]) + +/************************************************************************ +** +** PLIP definitions +** +************************************************************************* +*/ + +/* Use 0 for production, 1 for verification, >2 for debug */ +#ifndef NET_DEBUG +#define NET_DEBUG 2 +#endif +static unsigned int net_debug = NET_DEBUG; + +/* In micro second */ +#define PLIP_DELAY_UNIT 1 + +/* Connection time out = PLIP_TRIGGER_WAIT * PLIP_DELAY_UNIT usec */ +#define PLIP_TRIGGER_WAIT 500 + +/* Nibble time out = PLIP_NIBBLE_WAIT * PLIP_DELAY_UNIT usec */ +#define PLIP_NIBBLE_WAIT 3000 + +#define PAR_DATA(dev) ((dev)->base_addr+0) +#define PAR_STATUS(dev) ((dev)->base_addr+2) +#define PAR_CONTROL(dev) ((dev)->base_addr+4) + +static void enable_par_irq(struct device *dev, int on); +static int plip_init(struct device *dev); + +/* Bottom halfs */ +static void plip_kick_bh(struct device *dev); +static void plip_bh(struct device *dev); + +/* Functions for DEV methods */ +static int plip_rebuild_header(struct sk_buff *skb); +static int plip_tx_packet(struct sk_buff *skb, struct device *dev); +static int plip_open(struct device *dev); +static int plip_close(struct device *dev); +static struct enet_statistics *plip_get_stats(struct device *dev); +static int plip_config(struct device *dev, struct ifmap *map); +static int plip_ioctl(struct device *dev, struct ifreq *ifr, int cmd); + +enum plip_connection_state { + PLIP_CN_NONE=0, + PLIP_CN_RECEIVE, + PLIP_CN_SEND, + PLIP_CN_CLOSING, + PLIP_CN_ERROR +}; + +enum plip_packet_state { + PLIP_PK_DONE=0, + PLIP_PK_TRIGGER, + PLIP_PK_LENGTH_LSB, + PLIP_PK_LENGTH_MSB, + PLIP_PK_DATA, + PLIP_PK_CHECKSUM +}; + +enum plip_nibble_state { + PLIP_NB_BEGIN, + PLIP_NB_1, + PLIP_NB_2, +}; + +struct plip_local { + enum plip_packet_state state; + enum plip_nibble_state nibble; + union { + struct { +#if defined(__LITTLE_ENDIAN) + unsigned char lsb; + unsigned char msb; +#elif defined(__BIG_ENDIAN) + unsigned char msb; + unsigned char lsb; +#else +#error "Please fix the endianness defines in " +#endif + } b; + unsigned short h; + } length; + unsigned short byte; + unsigned char checksum; + unsigned char data; + struct sk_buff *skb; +}; + +struct net_local { + struct enet_statistics enet_stats; + struct tq_struct immediate; + struct tq_struct deferred; + struct plip_local snd_data; + struct plip_local rcv_data; + unsigned long trigger; + unsigned long nibble; + enum plip_connection_state connection; + unsigned short timeout_count; + char is_deferred; + int (*orig_rebuild_header)(struct sk_buff *skb); +}; + +struct device ioext_dev_plip[] = { + { + "plip0", + 0, 0, 0, 0, /* memory */ + 0, 0, /* base, irq */ + 0, 0, 0, NULL, plip_init + }, + { + "plip1", + 0, 0, 0, 0, /* memory */ + 0, 0, /* base, irq */ + 0, 0, 0, NULL, plip_init + }, + { + "plip2", + 0, 0, 0, 0, /* memory */ + 0, 0, /* base, irq */ + 0, 0, 0, NULL, plip_init + } +}; + +/* + * Check for and handle an interrupt for this PLIP device. + * + */ +void ioext_plip_interrupt(struct device *dev, int *spurious_count) +{ + struct net_local *nl; + struct plip_local *rcv; + unsigned char c0; + unsigned long flags; + + nl = (struct net_local *)dev->priv; + rcv = &nl->rcv_data; + + c0 = z_readb(PAR_STATUS(dev)); + + if (dev->interrupt) { + return; + } + + if ((c0 & 0xf8) != 0xc0) { + /* Not for us */ + ++*spurious_count; + return; + } + + *spurious_count = 0; + dev->interrupt = 1; + + local_irq_save(flags); + + switch (nl->connection) { + case PLIP_CN_CLOSING: + dev->tbusy = 0; + case PLIP_CN_NONE: + case PLIP_CN_SEND: + dev->last_rx = jiffies; + rcv->state = PLIP_PK_TRIGGER; + nl->connection = PLIP_CN_RECEIVE; + nl->timeout_count = 0; + queue_task(&nl->immediate, &tq_immediate); + mark_bh(IMMEDIATE_BH); + local_irq_restore(flags); +#if 0 + printk("%s: receive irq in SEND/NONE/CLOSING (%d) ok\n", + dev->name, nl->connection); +#endif + break; + + case PLIP_CN_RECEIVE: + local_irq_restore(flags); + printk("%s: receive interrupt when receiving packet\n", + dev->name); + break; + + case PLIP_CN_ERROR: + local_irq_restore(flags); + printk("%s: receive interrupt in error state\n", dev->name); + break; + } +} + + +/* Bottom half handler for the delayed request. + This routine is kicked by do_timer(). + Request `plip_bh' to be invoked. */ +static void +plip_kick_bh(struct device *dev) +{ + struct net_local *nl = (struct net_local *)dev->priv; + + if (nl->is_deferred) { + queue_task(&nl->immediate, &tq_immediate); + mark_bh(IMMEDIATE_BH); + } +} + +/* Forward declarations of internal routines */ +static int plip_none(struct device *, struct net_local *, + struct plip_local *, struct plip_local *); +static int plip_receive_packet(struct device *, struct net_local *, + struct plip_local *, struct plip_local *); +static int plip_send_packet(struct device *, struct net_local *, + struct plip_local *, struct plip_local *); +static int plip_connection_close(struct device *, struct net_local *, + struct plip_local *, struct plip_local *); +static int plip_error(struct device *, struct net_local *, + struct plip_local *, struct plip_local *); +static int plip_bh_timeout_error(struct device *dev, struct net_local *nl, + struct plip_local *snd, + struct plip_local *rcv, + int error); + +#define OK 0 +#define TIMEOUT 1 +#define ERROR 2 + +typedef int (*plip_func)(struct device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv); + +static plip_func connection_state_table[] = +{ + plip_none, + plip_receive_packet, + plip_send_packet, + plip_connection_close, + plip_error +}; + +/* +** enable_par_irq() +** +** Enable or disable parallel irq for 'dev' according to 'on'. +** +** It is NOT possible to disable only the parallel irq. +** So we disable the board interrupt instead. This means that +** during reception of a PLIP packet, no serial interrupts can +** happen. Sorry. +*/ +static void enable_par_irq(struct device *dev, int on) +{ + if (on) { + PLIP_DEV(dev)->board->CNTR |= GVP_IRQ_ENA; + } + else { + PLIP_DEV(dev)->board->CNTR &= ~GVP_IRQ_ENA; + } +} + +/* Bottom half handler of PLIP. */ +static void +plip_bh(struct device *dev) +{ + struct net_local *nl = (struct net_local *)dev->priv; + struct plip_local *snd = &nl->snd_data; + struct plip_local *rcv = &nl->rcv_data; + plip_func f; + int r; + + nl->is_deferred = 0; + f = connection_state_table[nl->connection]; + if ((r = (*f)(dev, nl, snd, rcv)) != OK + && (r = plip_bh_timeout_error(dev, nl, snd, rcv, r)) != OK) { + nl->is_deferred = 1; + queue_task(&nl->deferred, &tq_timer); + } +} + +static int +plip_bh_timeout_error(struct device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv, + int error) +{ + unsigned char c0; + unsigned long flags; + + local_irq_save(flags); + if (nl->connection == PLIP_CN_SEND) { + + if (error != ERROR) { /* Timeout */ + nl->timeout_count++; + if ((snd->state == PLIP_PK_TRIGGER + && nl->timeout_count <= 10) + || nl->timeout_count <= 3) { + local_irq_restore(flags); + /* Try again later */ + return TIMEOUT; + } + c0 = z_readb(PAR_STATUS(dev)); + printk(KERN_INFO "%s: transmit timeout(%d,%02x)\n", + dev->name, snd->state, c0); + } + nl->enet_stats.tx_errors++; + nl->enet_stats.tx_aborted_errors++; + } else if (nl->connection == PLIP_CN_RECEIVE) { + if (rcv->state == PLIP_PK_TRIGGER) { + /* Transmission was interrupted. */ + local_irq_restore(flags); + return OK; + } + if (error != ERROR) { /* Timeout */ + if (++nl->timeout_count <= 3) { + local_irq_restore(flags); + /* Try again later */ + return TIMEOUT; + } + c0 = z_readb(PAR_STATUS(dev)); + printk(KERN_INFO "%s: receive timeout(%d,%02x)\n", + dev->name, rcv->state, c0); + } + nl->enet_stats.rx_dropped++; + } + rcv->state = PLIP_PK_DONE; + if (rcv->skb) { + kfree_skb(rcv->skb); + rcv->skb = NULL; + } + snd->state = PLIP_PK_DONE; + if (snd->skb) { + dev_kfree_skb(snd->skb); + snd->skb = NULL; + } + enable_par_irq(dev, 0); + dev->tbusy = 1; + nl->connection = PLIP_CN_ERROR; + z_writeb(0x00, PAR_DATA(dev)); + local_irq_restore(flags); + + return TIMEOUT; +} + +static int +plip_none(struct device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv) +{ + return OK; +} + +/* PLIP_RECEIVE --- receive a byte(two nibbles) + Returns OK on success, TIMEOUT on timeout */ +inline static int +plip_receive(struct device *dev, unsigned short nibble_timeout, + enum plip_nibble_state *ns_p, unsigned char *data_p) +{ + unsigned char c0, c1; + unsigned int cx; + + switch (*ns_p) { + case PLIP_NB_BEGIN: + cx = nibble_timeout; + while (1) { + c0 = z_readb(PAR_STATUS(dev)); + udelay(PLIP_DELAY_UNIT); + if ((c0 & 0x80) == 0) { + c1 = z_readb(PAR_STATUS(dev)); + if (c0 == c1) + break; + } + if (--cx == 0) + return TIMEOUT; + } +#if 0 + printk("received first nybble: %02X -> %02X\n", + c0, (c0 >> 3) & 0x0F); +#endif + *data_p = (c0 >> 3) & 0x0f; + z_writeb(0x10, PAR_DATA(dev)); /* send ACK */ + *ns_p = PLIP_NB_1; + + case PLIP_NB_1: + cx = nibble_timeout; + while (1) { + c0 = z_readb(PAR_STATUS(dev)); + udelay(PLIP_DELAY_UNIT); + if (c0 & 0x80) { + c1 = z_readb(PAR_STATUS(dev)); + if (c0 == c1) + break; + } + if (--cx == 0) + return TIMEOUT; + } +#if 0 + printk("received second nybble: %02X -> %02X\n", + c0, (c0 << 1) & 0xF0); +#endif + *data_p |= (c0 << 1) & 0xf0; + z_writeb(0x00, PAR_DATA(dev)); /* send ACK */ + *ns_p = PLIP_NB_BEGIN; + case PLIP_NB_2: + break; + } + return OK; +} + +/* PLIP_RECEIVE_PACKET --- receive a packet */ +static int +plip_receive_packet(struct device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv) +{ + unsigned short nibble_timeout = nl->nibble; + unsigned char *lbuf; + unsigned long flags; + + switch (rcv->state) { + case PLIP_PK_TRIGGER: + enable_par_irq(dev, 0); + dev->interrupt = 0; + z_writeb(0x01, PAR_DATA(dev)); /* send ACK */ + if (net_debug > 2) + printk(KERN_DEBUG "%s: receive start\n", dev->name); + rcv->state = PLIP_PK_LENGTH_LSB; + rcv->nibble = PLIP_NB_BEGIN; + + case PLIP_PK_LENGTH_LSB: + if (snd->state != PLIP_PK_DONE) { + if (plip_receive(dev, nl->trigger, + &rcv->nibble, &rcv->length.b.lsb)) { + /* collision, here dev->tbusy == 1 */ + rcv->state = PLIP_PK_DONE; + nl->is_deferred = 1; + nl->connection = PLIP_CN_SEND; + queue_task(&nl->deferred, &tq_timer); + enable_par_irq(dev, 1); + return OK; + } + } else { + if (plip_receive(dev, nibble_timeout, + &rcv->nibble, &rcv->length.b.lsb)) + return TIMEOUT; + } + rcv->state = PLIP_PK_LENGTH_MSB; + + case PLIP_PK_LENGTH_MSB: + if (plip_receive(dev, nibble_timeout, + &rcv->nibble, &rcv->length.b.msb)) + return TIMEOUT; + if (rcv->length.h > dev->mtu + dev->hard_header_len + || rcv->length.h < 8) { + printk(KERN_INFO "%s: bogus packet size %d.\n", + dev->name, rcv->length.h); + return ERROR; + } + /* Malloc up new buffer. */ + rcv->skb = dev_alloc_skb(rcv->length.h); + if (rcv->skb == NULL) { + printk(KERN_INFO "%s: Memory squeeze.\n", dev->name); + return ERROR; + } + skb_put(rcv->skb,rcv->length.h); + rcv->skb->dev = dev; + rcv->state = PLIP_PK_DATA; + rcv->byte = 0; + rcv->checksum = 0; + + case PLIP_PK_DATA: + lbuf = rcv->skb->data; + do + if (plip_receive(dev, nibble_timeout, + &rcv->nibble, &lbuf[rcv->byte])) + return TIMEOUT; + while (++rcv->byte < rcv->length.h); + do + rcv->checksum += lbuf[--rcv->byte]; + while (rcv->byte); + rcv->state = PLIP_PK_CHECKSUM; + + case PLIP_PK_CHECKSUM: + if (plip_receive(dev, nibble_timeout, + &rcv->nibble, &rcv->data)) + return TIMEOUT; + if (rcv->data != rcv->checksum) { + nl->enet_stats.rx_crc_errors++; + if (net_debug) + printk(KERN_INFO "%s: checksum error\n", + dev->name); + return ERROR; + } + rcv->state = PLIP_PK_DONE; + + case PLIP_PK_DONE: + /* Inform the upper layer for the arrival of a packet. */ + rcv->skb->protocol=eth_type_trans(rcv->skb, dev); + netif_rx(rcv->skb); + nl->enet_stats.rx_packets++; + rcv->skb = NULL; + if (net_debug > 2) + printk(KERN_DEBUG "%s: receive end\n", dev->name); + + /* Close the connection. */ + z_writeb (0x00, PAR_DATA(dev)); + + local_irq_save(flags); + if (snd->state != PLIP_PK_DONE) { + nl->connection = PLIP_CN_SEND; + local_irq_restore(flags); + queue_task(&nl->immediate, &tq_immediate); + mark_bh(IMMEDIATE_BH); + enable_par_irq(dev, 1); + return OK; + } else { + nl->connection = PLIP_CN_NONE; + local_irq_restore(flags); + enable_par_irq(dev, 1); + return OK; + } + } + return OK; +} + +/* PLIP_SEND --- send a byte (two nibbles) + Returns OK on success, TIMEOUT when timeout */ +inline static int +plip_send(struct device *dev, unsigned short nibble_timeout, + enum plip_nibble_state *ns_p, unsigned char data) +{ + unsigned char c0; + unsigned int cx; + + switch (*ns_p) { + case PLIP_NB_BEGIN: + z_writeb((data & 0x0f), PAR_DATA(dev)); + *ns_p = PLIP_NB_1; + + case PLIP_NB_1: + z_writeb(0x10 | (data & 0x0f), PAR_DATA(dev)); + cx = nibble_timeout; + while (1) { + c0 = z_readb(PAR_STATUS(dev)); + if ((c0 & 0x80) == 0) + break; + if (--cx == 0) + return TIMEOUT; + udelay(PLIP_DELAY_UNIT); + } + z_writeb(0x10 | (data >> 4), PAR_DATA(dev)); + *ns_p = PLIP_NB_2; + + case PLIP_NB_2: + z_writeb((data >> 4), PAR_DATA(dev)); + cx = nibble_timeout; + while (1) { + c0 = z_readb(PAR_STATUS(dev)); + if (c0 & 0x80) + break; + if (--cx == 0) + return TIMEOUT; + udelay(PLIP_DELAY_UNIT); + } + *ns_p = PLIP_NB_BEGIN; + return OK; + } + return OK; +} + +/* PLIP_SEND_PACKET --- send a packet */ +static int +plip_send_packet(struct device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv) +{ + unsigned short nibble_timeout = nl->nibble; + unsigned char *lbuf; + unsigned char c0; + unsigned int cx; + unsigned long flags; + + if (snd->skb == NULL || (lbuf = snd->skb->data) == NULL) { + printk(KERN_INFO "%s: send skb lost\n", dev->name); + snd->state = PLIP_PK_DONE; + snd->skb = NULL; + return ERROR; + } + + if (snd->length.h == 0) { + return OK; + } + + switch (snd->state) { + case PLIP_PK_TRIGGER: + if ((z_readb(PAR_STATUS(dev)) & 0xf8) != 0x80) + return TIMEOUT; + + /* Trigger remote rx interrupt. */ + z_writeb(0x08, PAR_DATA(dev)); + cx = nl->trigger; + while (1) { + udelay(PLIP_DELAY_UNIT); + local_irq_save(flags); + if (nl->connection == PLIP_CN_RECEIVE) { + local_irq_restore(flags); + /* interrupted */ + nl->enet_stats.collisions++; + if (net_debug > 1) + printk(KERN_INFO "%s: collision.\n", + dev->name); + return OK; + } + c0 = z_readb(PAR_STATUS(dev)); + if (c0 & 0x08) { + enable_par_irq(dev, 0); + if (net_debug > 2) + printk(KERN_DEBUG "%s: send start\n", + dev->name); + snd->state = PLIP_PK_LENGTH_LSB; + snd->nibble = PLIP_NB_BEGIN; + nl->timeout_count = 0; + local_irq_restore(flags); + break; + } + local_irq_restore(flags); + if (--cx == 0) { + z_writeb(0x00, PAR_DATA(dev)); + return TIMEOUT; + } + } + + case PLIP_PK_LENGTH_LSB: + if (plip_send(dev, nibble_timeout, + &snd->nibble, snd->length.b.lsb)) + return TIMEOUT; + snd->state = PLIP_PK_LENGTH_MSB; + + case PLIP_PK_LENGTH_MSB: + if (plip_send(dev, nibble_timeout, + &snd->nibble, snd->length.b.msb)) + return TIMEOUT; + snd->state = PLIP_PK_DATA; + snd->byte = 0; + snd->checksum = 0; + + case PLIP_PK_DATA: + do + if (plip_send(dev, nibble_timeout, + &snd->nibble, lbuf[snd->byte])) + return TIMEOUT; + while (++snd->byte < snd->length.h); + do + snd->checksum += lbuf[--snd->byte]; + while (snd->byte); + snd->state = PLIP_PK_CHECKSUM; + + case PLIP_PK_CHECKSUM: + if (plip_send(dev, nibble_timeout, + &snd->nibble, snd->checksum)) + return TIMEOUT; + + dev_kfree_skb(snd->skb); + nl->enet_stats.tx_packets++; + snd->state = PLIP_PK_DONE; + + case PLIP_PK_DONE: + /* Close the connection */ + z_writeb (0x00, PAR_DATA(dev)); + snd->skb = NULL; + if (net_debug > 2) + printk(KERN_DEBUG "%s: send end\n", dev->name); + nl->connection = PLIP_CN_CLOSING; + nl->is_deferred = 1; + queue_task(&nl->deferred, &tq_timer); + enable_par_irq(dev, 1); + return OK; + } + return OK; +} + +static int +plip_connection_close(struct device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv) +{ + unsigned long flags; + + local_irq_save(flags); + if (nl->connection == PLIP_CN_CLOSING) { + nl->connection = PLIP_CN_NONE; + dev->tbusy = 0; + mark_bh(NET_BH); + } + local_irq_restore(flags); + return OK; +} + +/* PLIP_ERROR --- wait till other end settled */ +static int +plip_error(struct device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv) +{ + unsigned char status; + + status = z_readb(PAR_STATUS(dev)); + if ((status & 0xf8) == 0x80) { + if (net_debug > 2) + printk(KERN_DEBUG "%s: reset interface.\n", dev->name); + nl->connection = PLIP_CN_NONE; + dev->tbusy = 0; + dev->interrupt = 0; + enable_par_irq(dev, 1); + mark_bh(NET_BH); + } else { + nl->is_deferred = 1; + queue_task(&nl->deferred, &tq_timer); + } + + return OK; +} + +/* We don't need to send arp, for plip is point-to-point. */ +static int +plip_rebuild_header(struct sk_buff *skb) +{ + struct device *dev = skb->dev; + struct net_local *nl = (struct net_local *)dev->priv; + struct ethhdr *eth = (struct ethhdr *)skb->data; + int i; + + if ((dev->flags & IFF_NOARP)==0) + return nl->orig_rebuild_header(skb); + + if (eth->h_proto != __constant_htons(ETH_P_IP) +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + && eth->h_proto != __constant_htons(ETH_P_IPV6) +#endif + ) { + printk(KERN_ERR "plip_rebuild_header: Don't know how to resolve type %d addresses?\n", (int)eth->h_proto); + memcpy(eth->h_source, dev->dev_addr, dev->addr_len); + return 0; + } + + for (i=0; i < ETH_ALEN - sizeof(u32); i++) + eth->h_dest[i] = 0xfc; +#if 0 + *(u32 *)(eth->h_dest+i) = dst; +#else + /* Do not want to include net/route.h here. + * In any case, it is TOP of silliness to emulate + * hardware addresses on PtP link. --ANK + */ + *(u32 *)(eth->h_dest+i) = 0; +#endif + return 0; +} + +static int +plip_tx_packet(struct sk_buff *skb, struct device *dev) +{ + struct net_local *nl = (struct net_local *)dev->priv; + struct plip_local *snd = &nl->snd_data; + unsigned long flags; + + if (dev->tbusy) + return 1; + + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + printk(KERN_ERR "%s: Transmitter access conflict.\n", + dev->name); + return 1; + } + + if (skb->len > dev->mtu + dev->hard_header_len) { + printk(KERN_ERR "%s: packet too big, %d.\n", + dev->name, (int)skb->len); + dev->tbusy = 0; + return 0; + } + + if (net_debug > 2) + printk(KERN_DEBUG "%s: send request\n", dev->name); + + local_irq_save(flags); + dev->trans_start = jiffies; + snd->skb = skb; + snd->length.h = skb->len; + snd->state = PLIP_PK_TRIGGER; + if (nl->connection == PLIP_CN_NONE) { + nl->connection = PLIP_CN_SEND; + nl->timeout_count = 0; + } + queue_task(&nl->immediate, &tq_immediate); + mark_bh(IMMEDIATE_BH); + local_irq_restore(flags); + + return 0; +} + +/* Open/initialize the board. This is called (in the current kernel) + sometime after booting when the 'ifconfig' program is run. + + */ +static int +plip_open(struct device *dev) +{ + struct net_local *nl = (struct net_local *)dev->priv; + struct in_device *in_dev; + +#if defined(CONFIG_GVPIOEXT_LP) || defined(CONFIG_GVPIOEXT_LP_MODULE) + /* Yes, there is a race condition here. Fix it later */ + if (PLIP_DEV(dev)->par_use & IOEXT_PAR_LP) { + /* Can't open if lp is in use */ +#if DEBUG + printk("par is in use by lp\n"); +#endif + return(-EBUSY); + } +#endif + PLIP_DEV(dev)->par_use |= IOEXT_PAR_PLIP; + +#if DEBUG + printk("plip_open(): sending 00 to data port\n"); +#endif + + /* Clear the data port. */ + z_writeb (0x00, PAR_DATA(dev)); + +#if DEBUG + printk("plip_open(): sent\n"); +#endif + + /* Initialize the state machine. */ + nl->rcv_data.state = nl->snd_data.state = PLIP_PK_DONE; + nl->rcv_data.skb = nl->snd_data.skb = NULL; + nl->connection = PLIP_CN_NONE; + nl->is_deferred = 0; + + /* Fill in the MAC-level header. + (ab)Use "dev->broadcast" to store point-to-point MAC address. + + PLIP doesn't have a real mac address, but we need to create one + to be DOS compatible. */ + memset(dev->dev_addr, 0xfc, ETH_ALEN); + memset(dev->broadcast, 0xfc, ETH_ALEN); + + if ((in_dev=dev->ip_ptr) != NULL) { + /* + * Any address will do - we take the first + */ + struct in_ifaddr *ifa=in_dev->ifa_list; + if (ifa != NULL) { + memcpy(dev->dev_addr+2, &ifa->ifa_local, 4); + memcpy(dev->broadcast+2, &ifa->ifa_address, 4); + } + } + + dev->interrupt = 0; + dev->start = 1; + dev->tbusy = 0; + + MOD_INC_USE_COUNT; + + /* Enable rx interrupt. */ + enable_par_irq(dev, 1); + + return 0; +} + +/* The inverse routine to plip_open (). */ +static int +plip_close(struct device *dev) +{ + struct net_local *nl = (struct net_local *)dev->priv; + struct plip_local *snd = &nl->snd_data; + struct plip_local *rcv = &nl->rcv_data; + unsigned long flags; + + dev->tbusy = 1; + dev->start = 0; + local_irq_save(flags); + nl->is_deferred = 0; + nl->connection = PLIP_CN_NONE; + local_irq_restore(flags); + z_writeb(0x00, PAR_DATA(dev)); + + snd->state = PLIP_PK_DONE; + if (snd->skb) { + dev_kfree_skb(snd->skb); + snd->skb = NULL; + } + rcv->state = PLIP_PK_DONE; + if (rcv->skb) { + kfree_skb(rcv->skb); + rcv->skb = NULL; + } + + PLIP_DEV(dev)->par_use &= ~IOEXT_PAR_PLIP; + + MOD_DEC_USE_COUNT; + return 0; +} + +static struct enet_statistics * +plip_get_stats(struct device *dev) +{ + struct net_local *nl = (struct net_local *)dev->priv; + struct enet_statistics *r = &nl->enet_stats; + + return r; +} + +static int +plip_config(struct device *dev, struct ifmap *map) +{ + if (dev->flags & IFF_UP) + return -EBUSY; + + printk(KERN_INFO "%s: This interface is autodetected (ignored).\n", + dev->name); + + return 0; +} + +static int +plip_ioctl(struct device *dev, struct ifreq *rq, int cmd) +{ + struct net_local *nl = (struct net_local *) dev->priv; + struct plipconf *pc = (struct plipconf *) &rq->ifr_data; + + switch(pc->pcmd) { + case PLIP_GET_TIMEOUT: + pc->trigger = nl->trigger; + pc->nibble = nl->nibble; + break; + case PLIP_SET_TIMEOUT: + nl->trigger = pc->trigger; + nl->nibble = pc->nibble; + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +/* + * Detect and initialize all IO-Extenders in this system. + * + * Both PLIP and serial devices are configured. + */ +int plip_init(struct device *dev) +{ + IOEXT_struct *board; + struct net_local *nl; + + if (ioext_num == 0) { + printk(KERN_INFO "%s\n", version); + } + + board = PLIP_DEV(dev)->board; + dev->base_addr = (unsigned long)&board->par.DATA; + + /* Cheat and use irq to index into our table */ + dev->irq = ioext_num; + + printk(KERN_INFO "%s: IO-Extender parallel port at 0x%08lX\n", dev->name, dev->base_addr); + + /* Fill in the generic fields of the device structure. */ + ether_setup(dev); + + /* Then, override parts of it */ + dev->hard_start_xmit = plip_tx_packet; + dev->open = plip_open; + dev->stop = plip_close; + dev->get_stats = plip_get_stats; + dev->set_config = plip_config; + dev->do_ioctl = plip_ioctl; + dev->tx_queue_len = 10; + dev->flags = IFF_POINTOPOINT|IFF_NOARP; + + /* Set the private structure */ + dev->priv = kmalloc(sizeof (struct net_local), GFP_KERNEL); + if (dev->priv == NULL) { + printk(KERN_ERR "%s: out of memory\n", dev->name); + return -ENOMEM; + } + memset(dev->priv, 0, sizeof(struct net_local)); + nl = (struct net_local *) dev->priv; + + nl->orig_rebuild_header = dev->rebuild_header; + dev->rebuild_header = plip_rebuild_header; + + /* Initialize constants */ + nl->trigger = PLIP_TRIGGER_WAIT; + nl->nibble = PLIP_NIBBLE_WAIT; + + /* Initialize task queue structures */ + nl->immediate.next = NULL; + nl->immediate.sync = 0; + nl->immediate.routine = (void *)(void *)plip_bh; + nl->immediate.data = dev; + + nl->deferred.next = NULL; + nl->deferred.sync = 0; + nl->deferred.routine = (void *)(void *)plip_kick_bh; + nl->deferred.data = dev; + + /* Don't enable interrupts yet */ + + return 0; +}