/*
 *	3Com-Sonix PC/Arpeggio loadable module.
 *
 *	Revision
 *		0.05	First working(ish) attempt.
 *		0.06	Crash on remote hangup was old firmware bug. Upgraded.
 *		0.07	Now works with new firmware (endian bug).
 *		0.08	Added network support. Transmit may be broken.
 *		0.09	Network mode crashes the card. Not good.
 *		0.10	Avoid closing SAPs (Info from Sonix). Doesn't help.
 *		0.11	Don't open unused old B1D/B2D saps. Doesn't help.
 *		0.12	As well as the sonix suggested cures fix a POLL bug
 *			of my own. Now network receive is fine. Also now
 *			reports 3Com-Sonix as they have been bought out.
 *			Still no transmit in sonix mode ?
 *		0.13	Fixed some offsets. ARP now works, small frames work.
 *		0.14	Works in sonix mode.
 *
 *	TO DO:
 *		How to handle disconnect from the board side.
 *		Add PPP ioctl's and kernel mode support
 *
 *	FINALLY:
 *		Write a Sonix router protocol handler and a user mode
 *			INP processor and remote manager.
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/ioctl.h>
#include <linux/malloc.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/fcntl.h>
#include <linux/skbuff.h>
#include <linux/termios.h>
#include <linux/in.h>
#include <linux/version.h>
#include <linux/timer.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include "arp.h"
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/byteorder.h>
#include <asm/system.h>

#include <linux/version.h>


#include "sonix.h"

#define NUM_SONIX	1
#define NUM_UNIT	16

static struct wait_queue *sonix_event=NULL;
static int sonix_mode;
#define SONIX_IOMODE	1
#define SONIX_NETMODE	2
volatile unsigned char sonix_window[NUM_UNIT];
volatile unsigned char sonix_tspace[NUM_UNIT];
static struct sk_buff_head sonix_txqueue[NUM_UNIT];
static struct sk_buff_head sonix_sigqueue;
static unsigned char sonix_sigpending;
static unsigned char sonix_pollpending[NUM_UNIT];
static unsigned short sonix_active[NUM_UNIT];
static void sonix_interrupt(int irq, struct pt_regs *regs);
static struct device sonix_device;
static unsigned short sonix_busyports=0;
static unsigned short sonix_okports=(1<<SAP_MODEM)|(1<<SAP_MDATA)|(1<<SAP_NETCFG);
int bd_reset=0;
int debug=0;


/*
 *	The register window needs loading with a 512 byte window setting
 *	and an autoincrementing window offset.
 */
 
static void sll_set_base(unsigned short frame, unsigned short offset)
{
	if(offset>511)
		printk("BAD OFFSET in sll_set_base.\n");
	frame=htons(frame);	/* motorolarise */
#if 0
	outw(frame,SONIX_IOBASE+1);
#endif
	outb(frame&0xFF, SONIX_IOBASE+1);
	outb(frame>>8, SONIX_IOBASE+2);	
	offset>>=1;
	outb(offset,SONIX_IOBASE+3);
}

/*
 *	Copy a block of data from the card
 */
 	
static void sll_blk_input(unsigned short frame, unsigned short offset, unsigned char *data, int len)
{
	sll_set_base(frame,offset);
	if(debug&1)
		printk("LLR%d:%d,", frame, offset);
	if(offset&1)
	{
		while(len>1)
		{
			*data++=inb(SONIX_IOBASE+7);
			*data++=inb(SONIX_IOBASE+6);
			len-=2;
		}
		if(len)
			*data=inb(SONIX_IOBASE+7);
	}
	else
	{
		while(len>1)
		{
			*data++=inb(SONIX_IOBASE+6);
			*data++=inb(SONIX_IOBASE+7);
			len-=2;
		}
		if(len)
			*data=inb(SONIX_IOBASE+6);
	}
}

/*
 *	Copy a block of data to the card
 */
 
static void sll_blk_output(unsigned short frame, unsigned short offset, unsigned char *data, int len)
{
	sll_set_base(frame,offset);
	if(debug&2)
		printk("LLW%d:%d,", frame, offset);
	if(offset&1)
	{
		while(len>1)
		{
			outb(*data, SONIX_IOBASE+7);
			data++;
			outb(*data, SONIX_IOBASE+6);
			data++;
			len-=2;
		}
		if(len)
			outb(*data,SONIX_IOBASE+7);
	}
	else
	{
		while(len>1)
		{
			outb(*data,SONIX_IOBASE+6);
			data++;
			outb(*data,SONIX_IOBASE+7);
			data++;
			len-=2;
		}
		if(len)
			outb(*data,SONIX_IOBASE+6);
	}
}
		
/*
 *	Copy data to the board taking account of all the windows
 *	and autoincrement limits
 */
 
static int sonix_ll_write(unsigned long bd_addr, void *data, int len)
{
	unsigned long flags;
#ifdef BD_CHECK_WRITE	
	char tmp[128];
	char old[128];
#endif	
	int bcount;
	unsigned short wof,wfr;
	int ct=len;

	save_flags(flags);
#ifdef BD_CHECK_WRITE
	if(len<128)
		sonix_ll_read(bd_addr,old,len);
#endif
	if(bd_addr+len> SONIX_ADDR_TOP)
	{
		printk("sonix_ll_write: out of range.\n");
		return -1;
	}
	
	cli();
	
	wof=WIN_OFFSET(bd_addr);
	wfr=WIN_FRAME(bd_addr);
	bcount=WIN_SIZE-wof;
	while(ct>bcount)
	{
		sll_blk_output(wfr,wof,data,bcount);
		ct-=bcount;
		data+=bcount;
		wof=0;
		bcount=WIN_SIZE;
		wfr++;
	}
	if(ct)
	{
		sll_blk_output(wfr,wof,data,ct);
	}
#ifdef BD_CHECK_WRITE	
	if(len<128)
	{
		sonix_ll_read(bd_addr,tmp,len);
		if(memcmp(tmp,data,len))
		{
			printk("ll_write: failed.[|%lX,%d]\n",bd_addr,len);
			if(memcmp(old,tmp,len)==0)
				printk("ll_write: disabled I/O ??\n");
		}
	}
#endif	
	restore_flags(flags);	
	return 0;
}

/*
 *	Copy data from the board taking account of all the windows
 *	and autoincrement limits
 */

static int sonix_ll_read(unsigned long bd_addr, void *data, int len)
{
	int bcount;
	unsigned short wof,wfr;
	int ct=len;
	unsigned long flags;
	save_flags(flags);
	
	if(bd_addr+len> SONIX_ADDR_TOP)
	{
		printk("sonix_ll_read: out of range.\n");
		return -1;
	}
	
	cli();
	
	wof=WIN_OFFSET(bd_addr);
	wfr=WIN_FRAME(bd_addr);
	bcount=WIN_SIZE-wof;
	while(ct>bcount)
	{
		sll_blk_input(wfr,wof,data,bcount);
		ct-=bcount;
		data+=bcount;
		wof=0;
		bcount=WIN_SIZE;
		wfr++;
	}
	if(ct)
	{
		sll_blk_input(wfr,wof,data,ct);
	}
	restore_flags(flags);
	return 0;
}

/*
 *	Read an index pointer from the board
 */
 
static unsigned long sonix_board_addr(unsigned long addr)
{
	unsigned long v;
	sonix_ll_read(addr, &v, sizeof(v));
	return ntohl(v);
}

/*
 *	Retrieve the base pointer
 */
 
static unsigned long sonix_get_base(void)
{
	unsigned long l=sonix_board_addr(0x410000+(4*41));
	return l;
}

static void sonix_ll_interrupt(void)
{
	unsigned char c;
	volatile unsigned long sanity=10000;
	for(;sanity>0;sanity--)
	{
		sonix_ll_read(SONIX_ICRADD,&c,1);
		if((c&0x10)==0)
			break;
	}
	if(!sanity)
		printk("sonix0: timeout abort on setting irq (%X).\n",
			(int)c);
	c=0xFF;
	sonix_ll_write(SONIX_OINTADD, &c, 1);
}

static void sonix_ll_clear_irq(void)
{
	unsigned char c=0;
	sonix_ll_write(SONIX_IINTADD, &c, 1);
}

/*
 *	Load a frame into the board
 */
 
static int sonix_stuff_packet(unsigned char channel, unsigned char *data, unsigned short len, unsigned long *frame_ptr, int lead)
{
	static char blank[4];
	unsigned long base = sonix_get_base()+SAP_OFFSET;
	unsigned long dbase;
	unsigned short window;
	base=sonix_board_addr(base);
	if(debug&512)
		printk("SAP base: %08lx\n",base);
	base+=channel*SAP_STRUCT_SIZE;
	dbase=sonix_board_addr(base+BUF_OFFSET);
	if(debug&512)
		printk("This SAP at: %08lx\n",base);
	if(debug&512)
		printk("Data written to: %08lx\n",dbase);
	if(debug&512)
	{
		char c[2];
		short s;
		sonix_ll_read(base, &c, 2);
		printk("SAP in use = %d\n",c[0]);
		printk("Window = %d\n",c[1]);
		sonix_ll_read(base+48, &c, 2);
		printk("Buffer Number = %d\n", c[0]);
		printk("Local SAP address = %d\n", c[1]);
	}
	sonix_ll_read(base+SIZE_OFFSET, &window, 2);
	window=ntohs(window);
	if(len+lead>window)
	{
		if(debug&256)
			printk("Len reduced to window(%d)\n", window);
		len=window-lead;
	}
	sonix_ll_write(dbase, blank, lead);
	sonix_ll_write(dbase+lead,data,len);
	*frame_ptr = dbase;
	return len+lead;
}

/*
 *	Take a frame off the board
 */
 
static int sonix_snarf_packet(unsigned char channel, unsigned char *data, unsigned short len)
{
	unsigned long base = sonix_get_base()+SAP_OFFSET;
	unsigned long dbase;
	unsigned short window;
	base=sonix_board_addr(base);
	base+=channel*SAP_STRUCT_SIZE;
	dbase=sonix_board_addr(base+BUF_OFFSET);
	window=sonix_ll_read(base+SIZE_OFFSET, &window, 2);
	window=ntohs(window);
	if(len>window)
		len=window;
	sonix_ll_read(dbase, data, len);
	return len;
}

/*
 *	Post a signal message to the board
 */

static void sonix_stuff_signal(union sonix_signal *s)
{
	unsigned long base=sonix_get_base() + RXSIG_OFFSET;
	if(s->ctrl.type!=S_NOP)
		if(debug&4)
			printk("Stuffed signal %d\n", s->ctrl.type);
	sonix_ll_write(base,s,sizeof(*s));
	sonix_ll_interrupt();
}

/*
 *	Check if we are clear to send another signal
 */
 
static int sonix_signal_pending(void)
{
	union sonix_signal s;
	volatile static int reenter=0;
	unsigned long base=sonix_get_base() + RXSIG_OFFSET;
	sonix_ll_read(base,&s,sizeof(s));
	if(s.ctrl.type!=S_NOSIGNAL && s.ctrl.type!=S_NOP)
	{
		if(debug&4)
			printk("PEND%d,",s.ctrl.type);
		return 1;
	}
	return 0;
}

/*
 *	Read the board signal
 */
 
static void sonix_snarf_signal(union sonix_signal *s)
{
	unsigned long base = sonix_get_base() + TXSIG_OFFSET;
	sonix_ll_read(base, s,sizeof(*s));
}

/*
 *	Tell the board it may send us another signal
 */
 
static void sonix_clear_signal()
{
	unsigned long base = sonix_get_base() + TXSIG_OFFSET;
	union sonix_signal s;
	s.ctrl.type=S_NOSIGNAL;
	sonix_ll_write(base, &s,sizeof(s));
}


/*
 *	Signal queue
 */
 
static int sonix_post_signal(union sonix_signal *s)
{
	unsigned long flags;
	struct sk_buff *skb;
	save_flags(flags);
	skb=alloc_skb(sizeof(*s),GFP_ATOMIC);
	if(!skb)
		return 0;
	cli();
	skb->free=1;
	memcpy(skb->data,s,sizeof(*s));
	skb_queue_tail(&sonix_sigqueue,skb);
	if(!sonix_signal_pending())
	{
		skb=skb_dequeue(&sonix_sigqueue);
		if(skb!=NULL)
		{
			if(debug&4)
				printk("Signal sent to sonix board.\n");
			sonix_stuff_signal((union sonix_signal *)skb->data);
			kfree_skb(skb, FREE_READ);
		}
	}
	sonix_sigpending=1;
	restore_flags(flags);
	return 1;
}

/*
 *	Make a connection request
 */
 
static int sonix_ll_connect(int channel)
{
	union sonix_signal s;
	memset(&s,0,sizeof(s));
	s.ctrl.type=S_CONNECT;
	s.ctrl.src=channel;	/* Us */
	s.ctrl.dst=channel|128; /* We mirror src/local settings for ease */
	if(debug&128)
		printk("S_CONNECT src=%d dst=%d\n",s.ctrl.src,s.ctrl.dst);
	return sonix_post_signal(&s);  /* Post request */
}

/*
 *	Make a disconnect request
 */
 
static int sonix_ll_disconnect(int channel)
{
	union sonix_signal s;
	memset(&s,0,sizeof(s));
	s.ctrl.type=S_DISCONNECT;
	s.ctrl.src=channel;
	s.ctrl.dst=channel|128;
	if(debug&128)
		printk("S_DISCONNECT src=%d dst=%d\n",s.ctrl.src,s.ctrl.dst);
	return sonix_post_signal(&s);
}

#define min(a,b)	(((a)<(b))?(a):(b))


/*
 *	The sonix board has caused an interrupt.
 *
 */
 
static void sonix_interrupt(int irq, struct pt_regs *regs)
{
	struct sk_buff *skb;
	union sonix_signal s;
	int len;
	int dt_off=0;
	unsigned char c;
	if(debug&16)
		printk("Interrupt.\n");
	sonix_snarf_signal(&s);
	if(debug&16)
		printk("IRQ: Sonix board says %d %d %d %d\n",
		(int)s.ctrl.type,(int)s.ctrl.control,
		(int)s.ctrl.src,(int)s.ctrl.dst);
		
	switch(s.ctrl.type)
	{
		case S_DATA:		/* Data */
		case S_DATAAKRQ:	/* Data please ack */
		case S_DATAPOLL:	/* Data and polled receive */
			if((s.ctrl.src&0x7F)>=NUM_UNIT)
				break;
				
			/*
			 *	Network packet ?
			 */
			 
			if((s.ctrl.src&0x7F)==SAP_MDATA && sonix_mode==SONIX_NETMODE)
				dt_off=1;
			if(sonix_active[s.ctrl.src&0x7F])
			{
				len=htons(s.data.size);
				skb=alloc_skb(len, GFP_ATOMIC);
				if(skb==NULL)
				{
					printk("sonix0: frame dropped due to low memory.\n");
					break;
				}
				skb->free=1;
				skb->len=len;
				if(debug&16)
					printk("Buffer at %08lX size %d\n",ntohl(s.data.buffer), len);
				sonix_ll_read(ntohl(s.data.buffer),skb->data,len);
				if(debug&16)
				{
					printk("RX queue buffer received: \n");
					{
						int ct;
						for(ct=0;ct<skb->len;ct++)
							printk("%02X:",skb->data[ct]);
						printk("\n");
					}
				}
				/*
				 *	Queue to reader
				 */
				if(!dt_off)
				{
					sonix_tspace[s.ctrl.src&0x7F]--;
					skb_queue_tail(&sonix_txqueue[s.ctrl.src],skb);
				}
				else
				{
				/*
				 *	Queue to network layer.
				 */
				 	skb->len+=4;
					skb->free=1;
					skb->dev=&sonix_device;
					netif_rx(skb);
				}
			}
			if(s.ctrl.type==S_DATAAKRQ)
			{
				s.ctrl.type=S_ACKNOWLEDGE;
				sonix_post_signal(&s);
			}
			if(s.ctrl.type==S_DATAPOLL)
			{
				s.ctrl.type=S_WINDOWSIZE;
				s.ctrl.control=sonix_tspace[s.ctrl.src&0x7F];
				if(debug&128)
					printk("S_WINDOWSIZE src=%d dst=%d window=%d\n",
						s.ctrl.src,s.ctrl.dst,s.ctrl.control);
				sonix_post_signal(&s);		/* Send it back */
			}
			break;
		case S_CONNECT: 	/* A connect */
		case S_DISCONNECT: 	/* A disconnect */
			break;
		case S_POLL:		/* A poll request */
			s.ctrl.control=sonix_tspace[s.ctrl.src&0x7F];
			if(!sonix_post_signal(&s))
				goto endit;		/* Send it back if we can */
			break;
		case S_WINDOWSIZE:	/* Window size report */
			sonix_window[s.ctrl.src&0x7F]=s.ctrl.control;
			if(s.ctrl.control)
			{
				if((debug&16384) && sonix_device.tbusy)
					printk("S_WINDOWSIZE clears tbusy.\n");
				sonix_device.tbusy=0;
			}
			sonix_pollpending[s.ctrl.src&0x7F]=0;
			break;
		case S_ACKNOWLEDGE:	/* Ack */
			break;
		case S_NOSIGNAL:	/* No signal */
			sonix_sigpending=0;
			break;
		case S_NOP:
			break;
		case 255: /* This isnt a real code its a happens to be the
		             case code */
		        printk("sonix0: ISDN board crashed and restarted\n");
			/* Reboot the board */
			c=0;
			sonix_ll_write(SONIX_REBOOT, &c, 1);
			/* Really need to restart all the saps */	        
		default:
			printk("Spurious signal '%d' from %s.\n",
				(int)s.ctrl.type, SONIX_NAME);
	}

	/* Clear the IRQ/Signal */
	
	if(debug&16)
		printk("Clear signal/IRQ\n");
	sonix_clear_signal();
	sonix_ll_clear_irq();

	/*
	 *	Send the next signal.
	 */
	 
	if(debug&16)
		printk("Begin signal send.\n");
	if(!sonix_signal_pending())
	{
		skb=skb_dequeue(&sonix_sigqueue);
		if(sonix_sigpending && !skb)
		{
			memset(&s,0,sizeof(s));
			s.ctrl.type=S_NOP;
			sonix_sigpending=0;
			sonix_stuff_signal(&s);
		}
		else if(skb!=NULL)
		{
			sonix_stuff_signal((union sonix_signal *)skb->data);
			kfree_skb(skb, FREE_READ);
			sonix_sigpending=1;
		}
	}
	/*
	 *	Wake user processes so they can try to move on.
	 */
endit:	 
	wake_up_interruptible(&sonix_event);	
	if(sonix_mode==SONIX_NETMODE)
		mark_bh(NET_BH);
	if(debug&16)
		printk("Done\n");
	
}

/*
 *	Transmit functionality. This could be more efficient if we checked
 *	for the case where there is buffer space ready and went user -> board
 *	in one copy. For the future...
 */
 
static int sonix_write(struct inode * inode, struct file * file, char * buf, int count)
{
	unsigned char *tmp;
	struct sk_buff *skb;
	int minor=MINOR(inode->i_rdev);
	int offset=0;
	int sap=minor&0xF;
	union sonix_signal s;
	unsigned long dp;
	
	tmp=kmalloc(count, GFP_KERNEL);
	memcpy_fromfs(tmp,buf,count);
	
	if(debug&32)
		printk("Begin wait for window.\n");

	if(sonix_window[sap]==0 && !sonix_pollpending[sap])
	{
		union sonix_signal s;
		memset(&s,0,sizeof(s));
		cli();
		s.ctrl.src=minor;
		s.ctrl.dst=minor^128;
		s.ctrl.type=S_POLL;
		if(debug&128)
			printk("S_POLL src=%d dst=%d\n",s.ctrl.src,s.ctrl.dst);
		sonix_post_signal(&s);
		sonix_pollpending[sap]=1;
		if(debug&32)
			printk("POLL\n");
	}
	
	while(sonix_window[sap]==0)
	{
		sti();		/* In case we turned it off above */
		
		
		if(file->f_flags&O_NDELAY)
			return -EWOULDBLOCK;
		/* IRQ handler will reset sonix_window */
		interruptible_sleep_on(&sonix_event);
		if(current->signal & ~current->blocked)
		{
			if(debug&32)
				printk("Interrupted - exiting.\n");
			kfree(tmp);
			if(debug&32)
				printk("Freed\n");
			return -EINTR;
		}
	}
	if(debug&32)
		printk("Copy data\n");
	/* We have room - write the bytes */
	/* Fix this to write direct via user space not copy */
	sonix_window[sap]--;
	count=sonix_stuff_packet(sap,tmp,count, &dp, 0);
	kfree(tmp);
	memset(&s,0,sizeof(s));
	if(sonix_window[sap]<2 && !sonix_pollpending[sap])
	{
		sonix_pollpending[sap]=1;
		s.data.type=S_DATAPOLL;
	}
	else
		s.data.type = S_DATA;
	s.data.src = sap;
	s.data.dst = sap|128;
	s.data.buffer = 0/*dp*/;
	s.data.size = count;/*htons(count);*/
	if(debug&128)
		printk("S_DATA src=%d dst=%d len=%d\n",s.data.src,s.data.dst,
			ntohs(s.data.size));
	
	if(debug&32)
		printk("Send DATA signal\n");
	sonix_post_signal(&s);
	return count;
}

/*
 *	Read from the Sonix board. All the work is done in the interrupt
 *	handler. We merely empty down the queue
 */
 
static int sonix_read(struct inode * inode, struct file * file, char * buf, int count)
{
	struct sk_buff *skb;
	int minor=MINOR(inode->i_rdev);
	int sap=minor&0x0F;

	cli();
	while((skb=skb_dequeue(	&sonix_txqueue[sap] ))==NULL)
	{
		if(file->f_flags & O_NONBLOCK)
		{
			sti();
			return -EWOULDBLOCK;
		}
		interruptible_sleep_on(&sonix_event);
		sti();
		if(current->signal & ~current->blocked)
			return -EINTR;
	}
	sti();
	if(count>skb->len)
		count=skb->len;
	memcpy_tofs(buf,skb->data,count);
	kfree_skb(skb, FREE_READ);
	sonix_tspace[sap]++;
	return count;	
}

/*
 *	Seek is not available on this device
 */
 
static int sonix_lseek(struct inode * inode, struct file * file,
		    off_t offset, int origin)
{
	return -ESPIPE;
}

/* 
 *	An open on the sonix device.
 */
 
static int sonix_open(struct inode * inode, struct file * file)
{
	unsigned int minor = MINOR(inode->i_rdev);
	int ret;

	/*
	 *	Single board only for now
	 */
	
	if(minor>15)
		return -ENXIO;

	if(sonix_busyports&(1<<minor))
		return -EBUSY;
		
	if(!(sonix_okports&(1<<minor)))
		return -EINVAL;
	
	if(debug&32)
		printk("Connect %d\n",minor&0x0F);
#ifdef FIRMWARE_IS_FIXED		
	sonix_ll_connect(minor&0x0F);
#endif	
	sonix_tspace[minor&0x0F]=4;
	sonix_active[minor&0x0F]++;
	
	sonix_busyports|=(1<<minor);	
	/*
	 *	Update use count. We can't be unloaded while open.
	 */
	 
	MOD_INC_USE_COUNT;
	return 0;
}

static void sonix_release(struct inode * inode, struct file * file)
{
	struct sk_buff *skb;
	unsigned int minor=MINOR(inode->i_rdev);
#ifdef FIRMWARE_IS_FIXED	
	sonix_ll_disconnect(minor&0x0F);
#endif	
	sonix_active[minor&0x0F]--;
	while(skb=skb_dequeue(&sonix_txqueue[minor&0x0F]))
		kfree_skb(skb, FREE_READ);
	sonix_busyports&=~(1<<minor);
	MOD_DEC_USE_COUNT;
}

/*
 *	Select on this descriptor.
 */
 
static int sonix_select(struct inode *inode, struct file *file, int sel_type, select_table *wait)
{
	unsigned int minor = MINOR(inode->i_rdev);
	int sap=minor&0xF;
	select_wait(&sonix_event,wait);
	switch(sel_type)
	{
		case SEL_IN:
			if(skb_peek(&sonix_txqueue[sap])!=NULL)
				return 1;
			return 0;
		case SEL_OUT:
			if(SONIX_MEM_LIMIT-sonix_window[sap]>SONIX_SELECT_WRITE)
				return 1;
			return 0;
		case SEL_EX:
			return 0;	
	}
}


/*	
 *	IOCTL handler. We support the SIOCSIFENCAP ioctl for setting
 *	modes and TIOCINQ, TIOCOUTQ for checking free space.
 */
 
static int sonix_ioctl(struct inode *inode, struct file *file,
		    unsigned int cmd, unsigned long arg)
{
	unsigned int minor = MINOR(inode->i_rdev);
	int retval = 0;
	int err;
	struct sk_buff *skb;
	switch(cmd)
	{
		default:
			retval= -EINVAL;
	}
	return retval;
}


static struct file_operations sonix_fops = {
	sonix_lseek,
	sonix_read,	/* sonix_read */
	sonix_write,
	NULL,		/* sonix_readdir */
	sonix_select,	/* sonix_select */
	sonix_ioctl,
	NULL,		/* sonix_mmap */
	sonix_open,
	sonix_release
};

/*
 *	Send a packet to the network.
 */
 
static int sonix_netxmit(struct sk_buff *skb, struct device *dev)
{
	union sonix_signal s;
	unsigned long flags;
	unsigned long dp;
	if(skb==NULL)
	{
		dev_tint(dev);
		return 0;
	}
	if(sonix_window[SAP_MDATA]==0 && ! sonix_pollpending[SAP_MDATA])
	{
		union sonix_signal s;
		memset(&s,0,sizeof(s));
		save_flags(flags);
		cli();
		s.ctrl.src=SAP_MDATA;
		s.ctrl.dst=SAP_MDATA^128;
		s.ctrl.type=S_POLL;
		sonix_post_signal(&s);
		sonix_pollpending[SAP_MDATA]=1;
		restore_flags(flags);
		dev->tbusy=1;
		if(debug&16384)
			printk("sonix0: net xmit - POLL defer.\n");
		return 1;
	}
	sonix_window[SAP_MDATA]--;
	sonix_stuff_packet(SAP_MDATA, skb->data, skb->len, &dp, 4);
	memset(&s,0,sizeof(s));
	if(sonix_window[SAP_MDATA]<2 && !sonix_pollpending[SAP_MDATA])
	{
		sonix_pollpending[SAP_MDATA]=1;
		s.data.type=S_DATAPOLL;
	}
	else
		s.data.type = S_DATA;
	s.data.src = SAP_MDATA;
	s.data.dst = SAP_MDATA|128;
	s.data.buffer = dp;
	s.data.size = skb->len+8;
	sonix_post_signal(&s);
	if(debug&16384)
		printk("sonix0: net xmit - OK\n");
	dev_kfree_skb(skb, FREE_READ);
	if(sonix_window[SAP_MDATA])
		mark_bh(NET_BH);
	return 0;
}

static int sonix_netopen(struct device *dev)
{
	if(sonix_busyports&(1<<SAP_MDATA))
		return -EBUSY;
	sonix_busyports|=(1<<SAP_MDATA);
#ifdef FIRMWARE_IS_FIXED	
	sonix_ll_connect(SAP_MDATA);
#endif	
	sonix_active[SAP_MDATA]++;
	dev->tbusy=0;
	sonix_mode=SONIX_NETMODE;
	MOD_INC_USE_COUNT;
	return 0;
}

static int sonix_netclose(struct device *dev)
{
	sonix_ll_disconnect(SAP_MDATA);
	sonix_busyports&=~(1<<SAP_MDATA);
	dev->tbusy=1;
	MOD_DEC_USE_COUNT;
	sonix_mode=SONIX_IOMODE;
	sonix_active[SAP_MDATA]--;
	return 0;
}

static int sonix_set_mac(struct device *dev, void *addr)
{
	if(memcmp(addr,"\x00\x20\x8A",3))
		return -EINVAL;
	memcpy(dev->dev_addr,addr,6);
}

unsigned short sonix_type_trans(struct sk_buff *skb, struct device *dev)
{
	struct ethhdr *eth = (struct ethhdr *) skb->data;
	unsigned char *rawp;
	
	if(*eth->h_dest&1)
	{
		if(memcmp(eth->h_dest,dev->broadcast, ETH_ALEN)==0)
			skb->pkt_type=PACKET_BROADCAST;
		else
			skb->pkt_type=PACKET_MULTICAST;
	}
	else if(memcmp(eth->h_dest,dev->dev_addr, ETH_ALEN))
		skb->pkt_type=PACKET_OTHERHOST;
	
	if (ntohs(eth->h_proto) >= 1536)
	{
		printk("Its %d\n",ntohs(eth->h_proto));
		return eth->h_proto;
	}
		
	rawp = (unsigned char *)(eth + 1);
	
	if (*(unsigned short *)rawp == 0xFFFF)
		return htons(ETH_P_802_3);
		
	return htons(ETH_P_802_2);
}

static int sonix_netprobe(struct device *dev)
{
	int i;
	dev->open=sonix_netopen;
	dev->stop=sonix_netclose;
	dev->get_stats=NULL;	/* FIXME */
	dev->set_multicast_list=NULL;
	dev->set_mac_address=sonix_set_mac;
	dev->hard_start_xmit=sonix_netxmit;
	ether_setup(dev);
	dev->type_trans=sonix_type_trans;
	dev->flags |= IFF_PROMISC;
	return 0;
}
	
static struct device sonix_device = {
	"sonix0",
	0,0,0,0,
	SONIX_REGION, SONIX_IRQ,
	0,0,0, NULL, sonix_netprobe
};

	
static int sonix_detect(void)
{
	char buf[512];
	unsigned char c,t;
	unsigned char test_pattern[]={222,57,47,208,247,2,72,214,205,183,53};
	unsigned char ret[sizeof(test_pattern)];
	union sonix_signal s;

	unsigned long wb;
	unsigned long wa;

	wb=(ntohs(inw(SONIX_IOBASE+1)))<<8|inb(SONIX_IOBASE+3);
	inb(SONIX_IOBASE+6);
	inb(SONIX_IOBASE+7);
	wa=(ntohs(inw(SONIX_IOBASE+1)))<<8|inb(SONIX_IOBASE+3);
	
	if(wb+1!=wa)
	{
		return -1;
	}

	if(bd_reset==1)
	{
		/* Reboot the board */
		c=0;
		sonix_ll_write(SONIX_REBOOT, &c, 1);
	}
#if 0	
	c=inb(SONIX_IOBASE);
	printk("sonix0: ");
	if(c&1)
		printk("16bit ");
	else
		printk("8bit ");
	if(c&2)
		printk("I/O-off ");
	if(c&16)
		printk("COM%d ",4-((c>>2)&3));
#endif
	/* Board select IRQ 5 */
	sonix_ll_read(SONIX_ICRADD, &c, 1);
	c&=0xF1;
	c|=0x08;
	sonix_ll_write(SONIX_ICRADD, &c, 1);
#if 0	
	if(c&1)
		printk("CIRQ-3 ");
	else
		printk("CIRQ-4 ");
	switch((c>>1)&3)
	{
		case 0:printk("NoIRQ ");break;
		case 1:printk("IRQ 5 ");break;
		case 2:printk("IRQ 11 ");break;
		case 3:printk("IRQ 10 ");break;
	}
	if(c&8)
		printk("[irqpend] ");
#endif
	sonix_ll_write(0x43F800, test_pattern, sizeof(test_pattern));
	sonix_ll_read(0x43F800, ret, sizeof(test_pattern));
	if(memcmp(test_pattern, ret, sizeof(test_pattern)))
	{
		return -1;
	}
	if(debug&4096)
	{
		printk("\nArpeggio passes.\n");
		printk("DataLink Vector  |%08lx\n", sonix_board_addr(0x410000+4*41));
		printk("Board Signal     |%08lx\n", sonix_get_base()+TXSIG_OFFSET);
		printk("PC Signal        |%08lx\n", sonix_get_base()+RXSIG_OFFSET);
	}
	sonix_clear_signal();
	memset(&s,'\0',sizeof(s));
	s.ctrl.type=S_NOP;
	sonix_stuff_signal(&s);
	return 0;
}
	

char kernel_version[]= "1.2.0";

int init_module(void)
{
	int ct;

	if(sizeof(struct sonix_sig_control)!=4)
		printk("Control signal size botch.\n");
	if(sizeof(struct sonix_sig_data)!=10)
		printk("Data signal size botch.\n");	
	if(check_region(SONIX_REGION, SONIX_REGION_SIZE)==-1)
	{
		printk("PC/Arpeggio load failed: I/O clash.\n");
		return -EIO;
	}
	
	if(request_irq(SONIX_IRQ, &sonix_interrupt, 0, SONIX_NAME))
	{
		printk("PC/Arpeggio - interrupt clash.\n");
		return -EAGAIN;
	}

	/* Next initialise the list heads */
	for(ct=0;ct<NUM_UNIT;ct++)
	{
		skb_queue_head_init(&sonix_txqueue[ct]);
		sonix_tspace[ct]=4;
	}
	skb_queue_head_init(&sonix_sigqueue);

	if(sonix_detect())
	{
		printk("PC/Arpeggio not found.\n");
		free_irq(SONIX_IRQ);
		return -EIO;
	}
	
	request_region(SONIX_REGION, SONIX_REGION_SIZE, SONIX_NAME);
	
	if(register_chrdev(SONIX_MAJOR, SONIX_NAME, &sonix_fops)==-1)
	{
		release_region(SONIX_REGION, SONIX_REGION_SIZE);
		printk("Unable to get major %d for PC/Arpeggio.\n",SONIX_MAJOR);
		free_irq(SONIX_IRQ);
		return -EIO;
	}
	
	if(register_netdev(&sonix_device)!=0)
		printk("sonix0: network driver initialise failed. Mode disabled.\n");
	printk("sonix0: 3Com-Sonix Arpeggio/PC at 0x%X irq %d\n",
		SONIX_REGION, SONIX_IRQ);
#ifndef FIRMWARE_IS_FIXED
	{
		int ct=0;
		for(ct=0;ct<16;ct++)
			if(sonix_okports&(1<<ct))
				sonix_ll_connect(ct);
	}
#endif		
	return 0;
}

void cleanup_module(void)
{
	struct sk_buff *skb;
	unregister_chrdev(SONIX_MAJOR, SONIX_NAME);
	unregister_netdev(&sonix_device);
	while(skb=skb_dequeue(&sonix_sigqueue))
		kfree_skb(skb, FREE_READ);
	release_region(SONIX_REGION, SONIX_REGION_SIZE);
	free_irq(SONIX_IRQ);
}

