836 lines
22 KiB
C
836 lines
22 KiB
C
|
/* Plip.c: A parallel port "network" driver for linux. */
|
|||
|
/*
|
|||
|
Written 1993 by Donald Becker and TANABE Hiroyasu.
|
|||
|
This code is distributed under the GPL.
|
|||
|
|
|||
|
The current author is reached as hiro@sanpo.t.u-tokyo.ac.jp .
|
|||
|
For more information do 'whois -h whois.nic.ad.jp HT043JP'
|
|||
|
|
|||
|
The original author may be reached as becker@super.org or
|
|||
|
C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715
|
|||
|
|
|||
|
This is parallel port packet pusher. It's actually more general
|
|||
|
than the "IP" in its name suggests -- but 'plip' is just such a
|
|||
|
great name!
|
|||
|
|
|||
|
This driver was first developed by D. Becker, when he was inspired by
|
|||
|
Russ Nelson's parallel port packet driver. He also did the update
|
|||
|
to 0.99.10.
|
|||
|
|
|||
|
It was further developed by Tommy Thorn (tthorn@daimi.aau.dk).
|
|||
|
|
|||
|
Recent versions were debugged and maintained by TANABE Hiroyasu.
|
|||
|
|
|||
|
Updated for 0.99pl12 by Donald Becker.
|
|||
|
|
|||
|
Changes even more Alan Cox <iiitac@pyr.swan.ac.uk>
|
|||
|
Fixed: sets skb->arp=1, always claims success like ethernet, doesn't
|
|||
|
free skb and then claim fail. Incorrect brackets causing reset problem
|
|||
|
Attempting to make it work (works for me - email me if it does work)
|
|||
|
|
|||
|
Bugs:
|
|||
|
Should be timer oriented state machine.
|
|||
|
Should never use jiffies for timeouts.
|
|||
|
Protocol is buggy when broadcasts occur (Must ask Russ Nelson)
|
|||
|
Can hang forever on collisions (tough - you fix it!).
|
|||
|
I get 15K/second NFS throughput (about 20-25K second IP).
|
|||
|
Change the protocol back.
|
|||
|
|
|||
|
*/
|
|||
|
|
|||
|
static char *version =
|
|||
|
"Net2Debugged PLIP 1.01 (from plip.c:v0.15 for 0.99pl12+, 8/11/93)\n";
|
|||
|
|
|||
|
#include <linux/config.h>
|
|||
|
|
|||
|
/*
|
|||
|
Sources:
|
|||
|
Ideas and protocols came from Russ Nelson's (nelson@crynwr.com)
|
|||
|
"parallel.asm" parallel port packet driver.
|
|||
|
TANABE Hiroyasu changes the protocol.
|
|||
|
The "Crynwr" parallel port standard specifies the following protocol:
|
|||
|
send header nibble '8'
|
|||
|
type octet '0xfd' or '0xfc'
|
|||
|
count-low octet
|
|||
|
count-high octet
|
|||
|
... data octets
|
|||
|
checksum octet
|
|||
|
Each octet is sent as <wait for rx. '0x1?'> <send 0x10+(octet&0x0F)>
|
|||
|
<wait for rx. '0x0?'> <send 0x00+((octet>>4)&0x0F)>
|
|||
|
|
|||
|
The cable used is a de facto standard parallel null cable -- sold as
|
|||
|
a "LapLink" cable by various places. You'll need a 10-conductor cable to
|
|||
|
make one yourself. The wiring is:
|
|||
|
INIT 16 - 16 SLCTIN 17 - 17
|
|||
|
GROUND 25 - 25
|
|||
|
D0->ERROR 2 - 15 15 - 2
|
|||
|
D1->SLCT 3 - 13 13 - 3
|
|||
|
D2->PAPOUT 4 - 12 12 - 4
|
|||
|
D3->ACK 5 - 10 10 - 5
|
|||
|
D4->BUSY 6 - 11 11 - 6
|
|||
|
Do not connect the other pins. They are
|
|||
|
D5,D6,D7 are 7,8,9
|
|||
|
STROBE is 1, FEED is 14
|
|||
|
extra grounds are 18,19,20,21,22,23,24
|
|||
|
*/
|
|||
|
|
|||
|
#include <linux/kernel.h>
|
|||
|
#include <linux/sched.h>
|
|||
|
#include <linux/types.h>
|
|||
|
#include <linux/fcntl.h>
|
|||
|
#include <linux/interrupt.h>
|
|||
|
#include <linux/string.h>
|
|||
|
#include <linux/ptrace.h>
|
|||
|
#include <linux/if_ether.h>
|
|||
|
#include <asm/system.h>
|
|||
|
#include <asm/io.h>
|
|||
|
#include <netinet/in.h>
|
|||
|
#include <errno.h>
|
|||
|
|
|||
|
#include "dev.h"
|
|||
|
#include "eth.h"
|
|||
|
#include "ip.h"
|
|||
|
#include "protocol.h"
|
|||
|
#include "tcp.h"
|
|||
|
#include "skbuff.h"
|
|||
|
#include "sock.h"
|
|||
|
#include "arp.h"
|
|||
|
|
|||
|
#ifdef PRINTK
|
|||
|
#undef PRINTK
|
|||
|
#endif
|
|||
|
#ifdef PRINTK2
|
|||
|
#undef PRINTK2
|
|||
|
#endif
|
|||
|
|
|||
|
#define PLIP_DEBUG /* debugging */
|
|||
|
#undef PLIP_DEBUG2 /* debugging with more varbose report */
|
|||
|
|
|||
|
#ifdef PLIP_DEBUG
|
|||
|
#define PRINTK(x) printk x
|
|||
|
#else
|
|||
|
#define PRINTK(x) /**/
|
|||
|
#endif
|
|||
|
#ifdef PLIP_DEBUG2
|
|||
|
#define PRINTK2(x) printk x
|
|||
|
#else
|
|||
|
#define PRINTK2(x) /**/
|
|||
|
#endif
|
|||
|
|
|||
|
/* The map from IRQ number (as passed to the interrupt handler) to
|
|||
|
'struct device'. */
|
|||
|
extern struct device *irq2dev_map[16];
|
|||
|
|
|||
|
/* Network statistics, with the same names as 'struct enet_statistics'. */
|
|||
|
#define netstats enet_statistics
|
|||
|
|
|||
|
/* constants */
|
|||
|
#define PAR_DATA 0
|
|||
|
#define PAR_STATUS 1
|
|||
|
#define PAR_CONTROL 2
|
|||
|
#define PLIP_MTU 1600
|
|||
|
#define PLIP_HEADER_TYPE1 0xfd
|
|||
|
#define PLIP_HEADER_TYPE2 0xfc
|
|||
|
|
|||
|
/* Index to functions, as function prototypes. */
|
|||
|
extern int plip_probe(int ioaddr, struct device *dev);
|
|||
|
static int plip_open(struct device *dev);
|
|||
|
static int plip_close(struct device *dev);
|
|||
|
static int plip_tx_packet(struct sk_buff *skb, struct device *dev);
|
|||
|
static int plip_header (unsigned char *buff, struct device *dev,
|
|||
|
unsigned short type, unsigned long h_dest,
|
|||
|
unsigned long h_source, unsigned len);
|
|||
|
|
|||
|
/* variables used internally. */
|
|||
|
#define INITIALTIMEOUTFACTOR 4
|
|||
|
#define MAXTIMEOUTFACTOR 20
|
|||
|
static int timeoutfactor = INITIALTIMEOUTFACTOR;
|
|||
|
|
|||
|
/* Routines used internally. */
|
|||
|
static void plip_device_clear(struct device *dev);
|
|||
|
static void plip_receiver_error(struct device *dev);
|
|||
|
static void plip_set_physicaladdr(struct device *dev, unsigned long ipaddr);
|
|||
|
static int plip_addrcmp(struct ethhdr *eth);
|
|||
|
static int plip_send_enethdr(struct device *dev, struct ethhdr *eth);
|
|||
|
static int plip_rebuild_enethdr(struct device *dev, struct ethhdr *eth,
|
|||
|
unsigned char h_dest, unsigned char h_source,
|
|||
|
unsigned short type);
|
|||
|
static void cold_sleep(int tics);
|
|||
|
static void plip_interrupt(int reg_ptr); /* Dispatch from interrupts. */
|
|||
|
static int plip_receive_packet(struct device *dev);
|
|||
|
static int plip_send_packet(struct device *dev, unsigned char *buf, int length);
|
|||
|
static int plip_send_start(struct device *dev, struct ethhdr *eth);
|
|||
|
static void double_timeoutfactor(void);
|
|||
|
static struct enet_statistics *plip_get_stats(struct device *dev);
|
|||
|
|
|||
|
int
|
|||
|
plip_init(struct device *dev)
|
|||
|
{
|
|||
|
int port_base = dev->base_addr;
|
|||
|
int i;
|
|||
|
|
|||
|
/* Check that there is something at base_addr. */
|
|||
|
outb(0x00, port_base + PAR_CONTROL);
|
|||
|
outb(0x55, port_base + PAR_DATA);
|
|||
|
if (inb(port_base + PAR_DATA) != 0x55)
|
|||
|
return -ENODEV;
|
|||
|
|
|||
|
/* Alpha testers must have the version number to report bugs. */
|
|||
|
#ifdef PLIP_DEBUG
|
|||
|
{
|
|||
|
static int version_shown = 0;
|
|||
|
if (! version_shown)
|
|||
|
printk(version), version_shown++;
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
/* Initialize the device structure. */
|
|||
|
dev->priv = kmalloc(sizeof(struct netstats), GFP_KERNEL);
|
|||
|
memset(dev->priv, 0, sizeof(struct netstats));
|
|||
|
|
|||
|
for (i = 0; i < DEV_NUMBUFFS; i++)
|
|||
|
dev->buffs[i] = NULL;
|
|||
|
dev->hard_header = &plip_header;
|
|||
|
dev->add_arp = eth_add_arp;
|
|||
|
dev->queue_xmit = dev_queue_xmit;
|
|||
|
dev->rebuild_header = eth_rebuild_header;
|
|||
|
dev->type_trans = eth_type_trans;
|
|||
|
|
|||
|
dev->open = &plip_open;
|
|||
|
dev->stop = &plip_close;
|
|||
|
dev->hard_start_xmit = &plip_tx_packet;
|
|||
|
dev->get_stats = &plip_get_stats;
|
|||
|
|
|||
|
/* These are ethernet specific. */
|
|||
|
dev->type = ARPHRD_ETHER;
|
|||
|
dev->hard_header_len = ETH_HLEN;
|
|||
|
dev->mtu = PLIP_MTU; /* PLIP may later negotiate max pkt size */
|
|||
|
dev->addr_len = ETH_ALEN;
|
|||
|
for (i = 0; i < dev->addr_len; i++) {
|
|||
|
dev->broadcast[i]=0xff;
|
|||
|
dev->dev_addr[i] = 0;
|
|||
|
}
|
|||
|
printk("%s: configured for parallel port at %#3x, IRQ %d.\n",
|
|||
|
dev->name, dev->base_addr, dev->irq);
|
|||
|
|
|||
|
/* initialize internal value */
|
|||
|
timeoutfactor = INITIALTIMEOUTFACTOR;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Open/initialize the board. This is called (in the current kernel)
|
|||
|
sometime after booting when the 'config <dev->name>' program is
|
|||
|
run.
|
|||
|
|
|||
|
This routine gets exclusive access to the parallel port by allocating
|
|||
|
its IRQ line.
|
|||
|
*/
|
|||
|
static int
|
|||
|
plip_open(struct device *dev)
|
|||
|
{
|
|||
|
if (dev->irq == 0)
|
|||
|
dev->irq = 7;
|
|||
|
cli();
|
|||
|
if (request_irq(dev->irq , &plip_interrupt) != 0) {
|
|||
|
sti();
|
|||
|
PRINTK(("%s: couldn't get IRQ %d.\n", dev->name, dev->irq));
|
|||
|
return -EAGAIN;
|
|||
|
}
|
|||
|
|
|||
|
irq2dev_map[dev->irq] = dev;
|
|||
|
sti();
|
|||
|
plip_device_clear(dev);
|
|||
|
dev->tbusy = 0;
|
|||
|
dev->interrupt = 0;
|
|||
|
dev->start = 1;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* The inverse routine to plip_open(). */
|
|||
|
static int
|
|||
|
plip_close(struct device *dev)
|
|||
|
{
|
|||
|
dev->tbusy = 1;
|
|||
|
dev->start = 0;
|
|||
|
cli();
|
|||
|
free_irq(dev->irq);
|
|||
|
irq2dev_map[dev->irq] = NULL;
|
|||
|
sti();
|
|||
|
outb(0x00, dev->base_addr); /* Release the interrupt. */
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
plip_tx_packet(struct sk_buff *skb, struct device *dev)
|
|||
|
{
|
|||
|
int ret_val;
|
|||
|
|
|||
|
if (dev->tbusy || dev->interrupt) { /* Do timeouts, to avoid hangs. */
|
|||
|
int tickssofar = jiffies - dev->trans_start;
|
|||
|
if (tickssofar < 50)
|
|||
|
return 1;
|
|||
|
printk("%s: transmit timed out\n", dev->name);
|
|||
|
/* Try to restart the adaptor. */
|
|||
|
plip_device_clear(dev);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* If some higher layer thinks we've missed an tx-done interrupt
|
|||
|
we are passed NULL. Caution: dev_tint() handles the cli()/sti()
|
|||
|
itself. */
|
|||
|
if (skb == NULL) {
|
|||
|
dev_tint(dev);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Pretend we are an ethernet and fill in the header. This could use
|
|||
|
a simplified routine someday. */
|
|||
|
if (!skb->arp && dev->rebuild_header(skb->data, dev)) {
|
|||
|
skb->dev = dev;
|
|||
|
arp_queue (skb);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
skb->arp=1;
|
|||
|
|
|||
|
dev->trans_start = jiffies;
|
|||
|
ret_val = plip_send_packet(dev, skb->data, skb->len);
|
|||
|
if (skb->free)
|
|||
|
kfree_skb (skb, FREE_WRITE);
|
|||
|
dev->tbusy = 0;
|
|||
|
mark_bh (INET_BH);
|
|||
|
return 0/*ret_val*/;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
plip_header (unsigned char *buff, struct device *dev,
|
|||
|
unsigned short type, unsigned long h_dest,
|
|||
|
unsigned long h_source, unsigned len)
|
|||
|
{
|
|||
|
if (dev->dev_addr[0] == 0) {
|
|||
|
/* set physical address */
|
|||
|
plip_set_physicaladdr(dev, h_source);
|
|||
|
}
|
|||
|
return eth_header(buff, dev, type, h_dest, h_source, len);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
plip_device_clear(struct device *dev)
|
|||
|
{
|
|||
|
dev->interrupt = 0;
|
|||
|
dev->tbusy = 0;
|
|||
|
outb(0x00, dev->base_addr + PAR_DATA);
|
|||
|
outb(0x10, dev->base_addr + PAR_CONTROL); /* Enable the rx interrupt. */
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
plip_receiver_error(struct device *dev)
|
|||
|
{
|
|||
|
dev->interrupt = 0;
|
|||
|
dev->tbusy = 0;
|
|||
|
outb(0x02, dev->base_addr + PAR_DATA);
|
|||
|
outb(0x10, dev->base_addr + PAR_CONTROL); /* Enable the rx interrupt. */
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
get_byte(struct device *dev)
|
|||
|
{
|
|||
|
unsigned char val, oldval;
|
|||
|
unsigned char low_nibble;
|
|||
|
int timeout;
|
|||
|
int error = 0;
|
|||
|
val = inb(dev->base_addr + PAR_STATUS);
|
|||
|
timeout = jiffies + timeoutfactor * 2;
|
|||
|
do {
|
|||
|
oldval = val;
|
|||
|
val = inb(dev->base_addr + PAR_STATUS);
|
|||
|
if ( oldval != val ) continue; /* it's unstable */
|
|||
|
if ( timeout < jiffies ) {
|
|||
|
error++;
|
|||
|
break;
|
|||
|
}
|
|||
|
} while ( (val & 0x80) );
|
|||
|
val = inb(dev->base_addr + PAR_STATUS);
|
|||
|
low_nibble = (val >> 3) & 0x0f;
|
|||
|
outb(0x11, dev->base_addr + PAR_DATA);
|
|||
|
timeout = jiffies + timeoutfactor * 2;
|
|||
|
do {
|
|||
|
oldval = val;
|
|||
|
val = inb(dev->base_addr + PAR_STATUS);
|
|||
|
if (oldval != val) continue; /* it's unstable */
|
|||
|
if ( timeout < jiffies ) {
|
|||
|
error++;
|
|||
|
break;
|
|||
|
}
|
|||
|
} while ( !(val & 0x80) );
|
|||
|
val = inb(dev->base_addr + PAR_STATUS);
|
|||
|
PRINTK2(("%02x %s ", low_nibble | ((val << 1) & 0xf0),
|
|||
|
error ? "t":""));
|
|||
|
outb(0x01, dev->base_addr + PAR_DATA);
|
|||
|
if (error) {
|
|||
|
/* timeout error */
|
|||
|
double_timeoutfactor();
|
|||
|
return -1;
|
|||
|
}
|
|||
|
return low_nibble | ((val << 1) & 0xf0);
|
|||
|
}
|
|||
|
|
|||
|
/* The typical workload of the driver:
|
|||
|
Handle the parallel port interrupts. */
|
|||
|
static void
|
|||
|
plip_interrupt(int reg_ptr)
|
|||
|
{
|
|||
|
int irq = -(((struct pt_regs *)reg_ptr)->orig_eax+2);
|
|||
|
struct device *dev = irq2dev_map[irq];
|
|||
|
struct netstats *localstats;
|
|||
|
|
|||
|
if (dev == NULL) {
|
|||
|
PRINTK(("plip_interrupt(): irq %d for unknown device.\n", irq));
|
|||
|
return;
|
|||
|
}
|
|||
|
localstats = (struct netstats*) dev->priv;
|
|||
|
if (dev->tbusy || dev->interrupt) return;
|
|||
|
dev->interrupt = 1;
|
|||
|
outb(0x00, dev->base_addr + PAR_CONTROL); /* Disable the rx interrupt. */
|
|||
|
sti(); /* Allow other interrupts. */
|
|||
|
PRINTK2(("%s: interrupt. ", dev->name));
|
|||
|
|
|||
|
{
|
|||
|
/* check whether the interrupt is valid or not.*/
|
|||
|
int timeout = jiffies + timeoutfactor;
|
|||
|
while ((inb(dev->base_addr + PAR_STATUS) & 0xf8) != 0xc0) {
|
|||
|
if ( timeout < jiffies ) {
|
|||
|
PRINTK2(("%s: No interrupt (status=%#02x)!\n",
|
|||
|
dev->name, inb(dev->base_addr + PAR_STATUS)));
|
|||
|
plip_device_clear(dev);
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (plip_receive_packet(dev)) {
|
|||
|
/* get some error while receiving data */
|
|||
|
localstats->rx_errors++;
|
|||
|
plip_receiver_error(dev);
|
|||
|
} else {
|
|||
|
plip_device_clear(dev);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
plip_receive_packet(struct device *dev)
|
|||
|
{
|
|||
|
int plip_type;
|
|||
|
unsigned length;
|
|||
|
int checksum = 0;
|
|||
|
struct sk_buff *skb;
|
|||
|
struct netstats *localstats;
|
|||
|
struct ethhdr eth;
|
|||
|
|
|||
|
localstats = (struct netstats*) dev->priv;
|
|||
|
|
|||
|
outb(1, dev->base_addr + PAR_DATA); /* Ack: 'Ready' */
|
|||
|
|
|||
|
{
|
|||
|
/* get header octet and length of packet */
|
|||
|
plip_type = get_byte(dev);
|
|||
|
if (plip_type < 0) return 1; /* probably wrong interrupt */
|
|||
|
length = get_byte(dev) << 8;
|
|||
|
length |= get_byte(dev);
|
|||
|
switch ( plip_type ) {
|
|||
|
case PLIP_HEADER_TYPE1:
|
|||
|
{
|
|||
|
int i;
|
|||
|
unsigned char *eth_p = (unsigned char*)ð
|
|||
|
for ( i = 0; i < sizeof(eth); i++, eth_p++) {
|
|||
|
*eth_p = get_byte(dev);
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
case PLIP_HEADER_TYPE2:
|
|||
|
{
|
|||
|
unsigned char h_dest, h_source;
|
|||
|
unsigned short type;
|
|||
|
h_dest = get_byte(dev);
|
|||
|
h_source = get_byte(dev);
|
|||
|
type = get_byte(dev) << 8;
|
|||
|
type |= get_byte(dev);
|
|||
|
plip_rebuild_enethdr(dev, ð, h_dest, h_source, type);
|
|||
|
}
|
|||
|
break;
|
|||
|
default:
|
|||
|
PRINTK(("%s: wrong header octet\n", dev->name));
|
|||
|
}
|
|||
|
PRINTK2(("length = %d\n", length));
|
|||
|
if (length > dev->mtu || length < 8) {
|
|||
|
PRINTK2(("%s: bogus packet size %d.\n", dev->name, length));
|
|||
|
return 1;
|
|||
|
}
|
|||
|
}
|
|||
|
{
|
|||
|
/* get skb area from kernel and
|
|||
|
* set appropriate values to skb
|
|||
|
*/
|
|||
|
int sksize;
|
|||
|
sksize = sizeof(struct sk_buff) + length;
|
|||
|
skb = alloc_skb(sksize, GFP_ATOMIC);
|
|||
|
if (skb == NULL) {
|
|||
|
PRINTK(("%s: Couldn't allocate a sk_buff of size %d.\n",
|
|||
|
dev->name, sksize));
|
|||
|
return 1;
|
|||
|
}
|
|||
|
skb->lock = 0;
|
|||
|
skb->mem_len = sksize;
|
|||
|
skb->mem_addr = skb;
|
|||
|
}
|
|||
|
{
|
|||
|
/* phase of receiving the data */
|
|||
|
/* 'skb->data' points to the start of sk_buff data area. */
|
|||
|
unsigned char *buf = skb->data;
|
|||
|
unsigned char *eth_p = (unsigned char *)ð
|
|||
|
int i;
|
|||
|
for ( i = 0; i < sizeof(eth); i++) {
|
|||
|
checksum += *eth_p;
|
|||
|
*buf++ = *eth_p++;
|
|||
|
}
|
|||
|
for ( i = 0; i < length - sizeof(eth); i++) {
|
|||
|
unsigned char new_byte = get_byte(dev);
|
|||
|
checksum += new_byte;
|
|||
|
*buf++ = new_byte;
|
|||
|
}
|
|||
|
checksum &= 0xff;
|
|||
|
if (checksum != get_byte(dev)) {
|
|||
|
localstats->rx_crc_errors++;
|
|||
|
PRINTK(("checksum error\n"));
|
|||
|
return 1;
|
|||
|
} else if(dev_rint((unsigned char *)skb, length, IN_SKBUFF, dev)) {
|
|||
|
printk("%s: rcv buff full.\n", dev->name);
|
|||
|
localstats->rx_dropped++;
|
|||
|
return 1;
|
|||
|
}
|
|||
|
}
|
|||
|
{
|
|||
|
/* phase of terminating this connection */
|
|||
|
int timeout;
|
|||
|
|
|||
|
timeout = jiffies + length * timeoutfactor / 16;
|
|||
|
outb(0x00, dev->base_addr + PAR_DATA);
|
|||
|
/* Wait for the remote end to reset. */
|
|||
|
while ( (inb(dev->base_addr + PAR_STATUS) & 0xf8) != 0x80 ) {
|
|||
|
if (timeout < jiffies ) {
|
|||
|
double_timeoutfactor();
|
|||
|
PRINTK(("Remote has not reset.\n"));
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
localstats->rx_packets++;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static int send_byte(struct device *dev, unsigned char val)
|
|||
|
{
|
|||
|
int timeout;
|
|||
|
int error = 0;
|
|||
|
if (!(inb(dev->base_addr+PAR_STATUS) & 0x08)) {
|
|||
|
PRINTK(("remote end become unready while sending\n"));
|
|||
|
return -1;
|
|||
|
}
|
|||
|
PRINTK2((" S%02x", val));
|
|||
|
outb(val, dev->base_addr); /* this makes data bits more stable */
|
|||
|
outb(0x10 | val, dev->base_addr);
|
|||
|
timeout = jiffies + timeoutfactor;
|
|||
|
while( inb(dev->base_addr+PAR_STATUS) & 0x80 )
|
|||
|
if ( timeout < jiffies ) {
|
|||
|
error++;
|
|||
|
break;
|
|||
|
}
|
|||
|
outb(0x10 | (val >> 4), dev->base_addr);
|
|||
|
outb(val >> 4, dev->base_addr);
|
|||
|
timeout = jiffies + timeoutfactor;
|
|||
|
while( (inb(dev->base_addr+PAR_STATUS) & 0x80) == 0 )
|
|||
|
if ( timeout < jiffies ) {
|
|||
|
error++;
|
|||
|
break;
|
|||
|
}
|
|||
|
if (error) {
|
|||
|
/* timeout error */
|
|||
|
double_timeoutfactor();
|
|||
|
PRINTK2(("t"));
|
|||
|
return -1;
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
/*
|
|||
|
* plip_send_start
|
|||
|
* trigger remoto rx interrupt and establish a connection.
|
|||
|
*
|
|||
|
* return value
|
|||
|
* 0 : establish the connection
|
|||
|
* -1 : connection failed.
|
|||
|
*/
|
|||
|
static int
|
|||
|
plip_send_start(struct device *dev, struct ethhdr *eth)
|
|||
|
{
|
|||
|
int timeout;
|
|||
|
int status;
|
|||
|
int lasttrigger;
|
|||
|
struct netstats *localstats = (struct netstats*) dev->priv;
|
|||
|
|
|||
|
/* This starts the packet protocol by triggering a remote IRQ. */
|
|||
|
timeout = jiffies + timeoutfactor * 16;
|
|||
|
lasttrigger = jiffies;
|
|||
|
while ( ((status = inb(dev->base_addr+PAR_STATUS)) & 0x08) == 0 ) {
|
|||
|
dev->tbusy = 1;
|
|||
|
outb(0x00, dev->base_addr + PAR_CONTROL); /* Disable my rx intr. */
|
|||
|
outb(0x08, dev->base_addr + PAR_DATA); /* Trigger remote rx intr. */
|
|||
|
if (status & 0x40) {
|
|||
|
/* The remote end is also trying to send a packet.
|
|||
|
* Only one end may go to the receiving phase,
|
|||
|
* so we use the "ethernet" address (set from the IP address)
|
|||
|
* to determine which end dominates.
|
|||
|
*/
|
|||
|
if ( plip_addrcmp(eth) > 0 ) {
|
|||
|
localstats->collisions++;
|
|||
|
PRINTK2(("both ends are trying to send a packet.\n"));
|
|||
|
if (plip_receive_packet(dev)) {
|
|||
|
/* get some error while receiving data */
|
|||
|
localstats->rx_errors++;
|
|||
|
outb(0x02, dev->base_addr + PAR_DATA);
|
|||
|
} else {
|
|||
|
outb(0x00, dev->base_addr + PAR_DATA);
|
|||
|
}
|
|||
|
cold_sleep(2); /* make sure that remote end is ready */
|
|||
|
}
|
|||
|
continue; /* restart send sequence */
|
|||
|
}
|
|||
|
if (lasttrigger != jiffies) {
|
|||
|
/* trigger again */
|
|||
|
outb(0x00, dev->base_addr + PAR_DATA);
|
|||
|
cold_sleep(1);
|
|||
|
lasttrigger = jiffies;
|
|||
|
}
|
|||
|
if (timeout < jiffies) {
|
|||
|
double_timeoutfactor();
|
|||
|
plip_device_clear(dev);
|
|||
|
localstats->tx_errors++;
|
|||
|
PRINTK(("%s: Connect failed in send_packet().\n",
|
|||
|
dev->name));
|
|||
|
/* We failed to send the packet. To emulate the ethernet we
|
|||
|
should pretent the send worked fine */
|
|||
|
return -1;
|
|||
|
}
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
static int
|
|||
|
plip_send_packet(struct device *dev, unsigned char *buf, int length)
|
|||
|
{
|
|||
|
int error = 0;
|
|||
|
int plip_type;
|
|||
|
struct netstats *localstats;
|
|||
|
|
|||
|
PRINTK2(("%s: plip_send_packet(%d) %02x %02x %02x %02x %02x...",
|
|||
|
dev->name, length, buf[0], buf[1], buf[2], buf[3], buf[4]));
|
|||
|
if (length > dev->mtu) {
|
|||
|
printk("%s: packet too big, %d.\n", dev->name, length);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
localstats = (struct netstats*) dev->priv;
|
|||
|
|
|||
|
{
|
|||
|
/* phase of checking remote status */
|
|||
|
int i;
|
|||
|
int timeout = jiffies + timeoutfactor * 8;
|
|||
|
while ( (i = (inb(dev->base_addr+PAR_STATUS) & 0xe8)) != 0x80 ) {
|
|||
|
if (i == 0x78) {
|
|||
|
/* probably cable is not connected */
|
|||
|
/* Implementation Note:
|
|||
|
* This status should result in 'Network unreachable'.
|
|||
|
* but I don't know the way.
|
|||
|
*/
|
|||
|
return 0;
|
|||
|
}
|
|||
|
if (timeout < jiffies) {
|
|||
|
/* remote end is not ready */
|
|||
|
double_timeoutfactor();
|
|||
|
localstats->tx_errors++;
|
|||
|
PRINTK(("remote end is not ready.\n"));
|
|||
|
return 1; /* Failed to send the packet */
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
/* phase of making a connection */
|
|||
|
if (plip_send_start(dev, (struct ethhdr *)buf) < 0)
|
|||
|
return 1;
|
|||
|
|
|||
|
/* select plip type */
|
|||
|
{
|
|||
|
/* Use stripped ethernet header if each first 5 octet of eth
|
|||
|
* address is same.
|
|||
|
*/
|
|||
|
int i;
|
|||
|
struct ethhdr *eth = (struct ethhdr *)buf;
|
|||
|
|
|||
|
plip_type = PLIP_HEADER_TYPE2;
|
|||
|
for ( i = 0; i < ETH_ALEN - 1; i++)
|
|||
|
if (eth->h_dest[i] != eth->h_source[i])
|
|||
|
plip_type = PLIP_HEADER_TYPE1;
|
|||
|
}
|
|||
|
|
|||
|
send_byte(dev, plip_type); /* send header octet */
|
|||
|
|
|||
|
{
|
|||
|
/* send packet's length */
|
|||
|
/*
|
|||
|
* in original plip (before v0.1), it was sent with little endian.
|
|||
|
* but in internet, network byteorder is big endian,
|
|||
|
* so changed to use big endian.
|
|||
|
* maybe using 'ntos()' is better.
|
|||
|
*/
|
|||
|
send_byte(dev, length >> 8); send_byte(dev, length);
|
|||
|
}
|
|||
|
{
|
|||
|
/* phase of sending data */
|
|||
|
int i;
|
|||
|
int checksum = 0;
|
|||
|
|
|||
|
if (plip_type == PLIP_HEADER_TYPE2) {
|
|||
|
plip_send_enethdr(dev, (struct ethhdr*)buf);
|
|||
|
}
|
|||
|
for ( i = 0; i < sizeof(struct ethhdr); i++ ) {
|
|||
|
if (plip_type == PLIP_HEADER_TYPE1) {
|
|||
|
send_byte(dev, *buf);
|
|||
|
}
|
|||
|
checksum += *buf++;
|
|||
|
}
|
|||
|
|
|||
|
for (i = 0; i < length - sizeof(struct ethhdr); i++) {
|
|||
|
checksum += buf[i];
|
|||
|
if (send_byte(dev, buf[i]) < 0) {
|
|||
|
error++;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
send_byte(dev, checksum & 0xff);
|
|||
|
}
|
|||
|
{
|
|||
|
/* phase of terminating this connection */
|
|||
|
int timeout;
|
|||
|
|
|||
|
outb(0x00, dev->base_addr + PAR_DATA);
|
|||
|
/* Wait for the remote end to reset. */
|
|||
|
timeout = jiffies + ((length * timeoutfactor) >> 4);
|
|||
|
while ((inb(dev->base_addr + PAR_STATUS) & 0xe8) != 0x80) {
|
|||
|
if (timeout < jiffies ) {
|
|||
|
double_timeoutfactor();
|
|||
|
PRINTK(("Remote end has not reset.\n"));
|
|||
|
error++;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (inb(dev->base_addr + PAR_STATUS) & 0x10) {
|
|||
|
/* receiver reports error */
|
|||
|
error++;
|
|||
|
}
|
|||
|
}
|
|||
|
plip_device_clear(dev);
|
|||
|
localstats->tx_packets++;
|
|||
|
PRINTK2(("plip_send_packet(%d) done.\n", length));
|
|||
|
return error?1:0;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* some trivial functions
|
|||
|
*/
|
|||
|
static void
|
|||
|
plip_set_physicaladdr(struct device *dev, unsigned long ipaddr)
|
|||
|
{
|
|||
|
/*
|
|||
|
* set physical address to
|
|||
|
* 0xfd.0xfd.ipaddr
|
|||
|
*/
|
|||
|
|
|||
|
unsigned char *addr = dev->dev_addr;
|
|||
|
int i;
|
|||
|
|
|||
|
if ((ipaddr >> 24) == 0 || (ipaddr >> 24) == 0xff) return;
|
|||
|
PRINTK2(("%s: set physical address to %08x\n", dev->name, ipaddr));
|
|||
|
for (i=0; i < ETH_ALEN - sizeof(unsigned long); i++) {
|
|||
|
addr[i] = 0xfd;
|
|||
|
}
|
|||
|
memcpy(&(addr[i]), &ipaddr, sizeof(unsigned long));
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
plip_addrcmp(struct ethhdr *eth)
|
|||
|
{
|
|||
|
int i;
|
|||
|
for ( i = ETH_ALEN - 1; i >= 0; i-- ) {
|
|||
|
if (eth->h_dest[i] > eth->h_source[i]) return -1;
|
|||
|
if (eth->h_dest[i] < eth->h_source[i]) return 1;
|
|||
|
}
|
|||
|
PRINTK2(("h_dest = %08x%04x h_source = %08x%04x\n",
|
|||
|
*(long*)ð->h_dest[2],*(short*)ð->h_dest[0],
|
|||
|
*(long*)ð->h_source[2],*(short*)ð->h_source[0]));
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
plip_send_enethdr(struct device *dev, struct ethhdr *eth)
|
|||
|
{
|
|||
|
send_byte(dev, eth->h_dest[ETH_ALEN-1]);
|
|||
|
send_byte(dev, eth->h_source[ETH_ALEN-1]);
|
|||
|
send_byte(dev, eth->h_proto >> 8);
|
|||
|
send_byte(dev, eth->h_proto);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
plip_rebuild_enethdr(struct device *dev, struct ethhdr *eth,
|
|||
|
unsigned char dest, unsigned char source,
|
|||
|
unsigned short type)
|
|||
|
{
|
|||
|
eth->h_proto = type;
|
|||
|
memcpy(eth->h_dest, dev->dev_addr, ETH_ALEN-1);
|
|||
|
eth->h_dest[ETH_ALEN-1] = dest;
|
|||
|
memcpy(eth->h_source, dev->dev_addr, ETH_ALEN-1);
|
|||
|
eth->h_source[ETH_ALEN-1] = source;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* This function is evil, evil, evil. This should be a
|
|||
|
_kernel_, rescheduling sleep!. */
|
|||
|
static void
|
|||
|
cold_sleep(int tics)
|
|||
|
{
|
|||
|
int start = jiffies;
|
|||
|
while(jiffies < start + tics)
|
|||
|
; /* do nothing */
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
double_timeoutfactor()
|
|||
|
{
|
|||
|
timeoutfactor *= 2;
|
|||
|
if (timeoutfactor >= MAXTIMEOUTFACTOR) {
|
|||
|
timeoutfactor = MAXTIMEOUTFACTOR;
|
|||
|
}
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
static struct enet_statistics *
|
|||
|
plip_get_stats(struct device *dev)
|
|||
|
{
|
|||
|
struct netstats *localstats = (struct netstats*) dev->priv;
|
|||
|
return localstats;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Local variables:
|
|||
|
* compile-command: "gcc -D__KERNEL__ -Wall -O6 -fomit-frame-pointer -x c++ -c plip.c"
|
|||
|
* version-control: t
|
|||
|
* kept-new-versions: 5
|
|||
|
* End:
|
|||
|
*/
|