patch-1.3.49 linux/drivers/char/scc.c

Next file: linux/drivers/char/scc_config.h
Previous file: linux/drivers/char/README.scc
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.48/linux/drivers/char/scc.c linux/drivers/char/scc.c
@@ -1,6 +1,6 @@
-#define RCS_ID "$Id: scc.c,v 1.26 1995/09/07 14:46:19 jreuter Exp jreuter $"
+#define RCS_ID "$Id: scc.c,v 1.41 1995/12/17 22:36:40 jreuter Exp jreuter $"
 
-#define BANNER "Z8530 SCC driver v1.9.dl1bke (beta) by dl1bke\n"
+#define BANNER "Z8530 SCC driver version 2.01.dl1bke (alpha) by DL1BKE\n"
 
 /*
 
@@ -11,14 +11,13 @@
 
    ********************************************************************
 
-	(c) 1993 - 1995 by Joerg Reuter DL1BKE
+	Copyright (c) 1993, 1995 Joerg Reuter DL1BKE
 
-	portions (c) 1994 Hans Alblas PE1AYX 
-	and      (c) 1993 Guido ten Dolle PE1NNZ
+	portions (c) 1993 Guido ten Dolle PE1NNZ
 
    ********************************************************************
    
-   The driver and the programs in this archive are UNDER CONSTRUCTION.
+   The driver and the programs in the archive are UNDER CONSTRUCTION.
    The code is likely to fail, and so your kernel could --- even 
    a whole network. 
 
@@ -60,8 +59,8 @@
 		Internet: jreuter@lykos.tng.oche.de
 		
 		
-   History of z8530drv:
-   --------------------
+   Incomplete history of z8530drv:
+   -------------------------------
 
    940913	- started to write the driver, rescued most of my own
 		  code (and Hans Alblas' memory buffer pool concept) from 
@@ -74,20 +73,47 @@
 
    950131	- changed copyright notice to GPL without limitations.
    
-   950228	- (hopefully) fixed the reason for kernel panics in
-                  chk_rcv_queue() [stupid error]
-                  
-   950304       - fixed underrun/zcount handling
+     .
+     .
+     .
+      
+   950922	- using kernel timer chain
    
-   950305	- the driver registers port addresses now
-   
-   950314	- fixed underrun interrupt handling again
+   951002	- splitting timer routine, adding support for throttle/
+   		  unthrottle calls, removed typo in kiss decoder,
+   		  corrected termios settings.
+   		  
+   951011	- at last found one (the?) problem which caused the 
+   		  driver to mess up buffers on heavy load pe1ayx was
+   		  complaining about. Quite simple to reproduce:
+   		  set a 9k6 port into audio loopback, add a route
+   		  to 44.99.99.99 on that route and do a 
+   		  ping -f 44.99.99.99
+   		  
+   951013	- it still does not survive a flood ping...
    
-   950512	- (hope to have) fixed hidden re-entrance problem
-   		  in scc_timer()
+   951107	- axattach w/o "-s" option (or setting an invalid 
+   		  baudrate) does not produce "division by zero" faults 
+   		  anymore.
+   		  
+   951114	- rewrote memory management, took first steps to allow
+		  compilation as a module. Well, it looks like a whole
+		  new driver now. BTW: It  d o e s  survive the flood
+		  ping at last...
+		  
+   951116	- scc_config.h is gone -- the driver will be initialized
+   		  through ioctl-commands. Use sccinit.c for this purpose.
+   		  
+   951117	- Well --- what should I say: You can compile it as a
+   		  module now. And I solved the problem with slip.c...
+   		  
+   951120	- most ioctl() routines may be called w/o suser()
+   		  permissions, check if you've set the permissions of
+   		  /dev/scc* right! NOT 0666, you know it's evil ;-)
    		  
-   950824	- received frames will be sent to the application
-   		  faster, clean-up of z8530_init()
+   951217	- found silly bug in init_channel(), some bugfixes
+   		  for the "standalone" module
+
 
    Thanks to:
    ----------
@@ -95,13 +121,13 @@
    PE1CHL Rob	- for a lot of good ideas from his SCC driver for DOS
    PE1NNZ Guido - for his port of the original driver to Linux
    KA9Q   Phil  - from whom we stole the mbuf-structure
-   PA3AYX Hans  - who rewrote parts of the memory management and some 
-   		  minor, but nevertheless useful changes
+   PA3AYX Hans  - for some useful changes
    DL8MBT Flori - for support
    DG0FT  Rene  - for the BayCom USCC support
    PA3AOU Harry - for ESCC testing, information supply and support
    
-   PE1KOX Rob, DG1RTF Thomas, ON5QK Roland, 
+   PE1KOX Rob, DG1RTF Thomas, ON5QK Roland, G4XYW Andy, Linus,
+   EI9GL Paul,
    
    and all who sent me bug reports and ideas... 
    
@@ -109,7 +135,7 @@
    NB -- if you find errors, change something, please let me know
       	 first before you distribute it... And please don't touch
    	 the version number. Just replace my callsign in
-   	 "v1.9.dl1bke" with your own. Just to avoid confusion...
+   	 "v2.01.dl1bke" with your own. Just to avoid confusion...
    	 
    If you want to add your modification to the linux distribution
    please (!) contact me first.
@@ -118,6 +144,25 @@
   
 */
 
+/* ----------------------------------------------------------------------- */
+
+#define DEBUG_BUFFERS	/* keep defined unless it is really stable... */
+
+#undef  SCC_DELAY 	/* perhaps a 486DX2 is a *bit* too fast */
+#undef  SCC_LDELAY 5	/* slow it even a bit more down */
+#undef  DONT_CHECK	/* don't look if the SCCs you specified are available */
+
+#define MAXSCC          4       /* number of max. supported chips */
+#define RXBUFFERS       8       /* default number of RX buffers */
+#define TXBUFFERS       8       /* default number of TX buffers */
+#define BUFSIZE         384     /* must not exceed 4096-sizeof(mbuf) */
+#define TPS             25      /* scc_tx_timer():  Ticks Per Second */
+
+#define DEFAULT_CLOCK	4915200 /* default pclock if nothing is specified */
+
+/* ----------------------------------------------------------------------- */
+
+#include <linux/module.h>
 #include <linux/errno.h>
 #include <linux/signal.h>
 #include <linux/sched.h>
@@ -135,9 +180,8 @@
 #include <linux/fcntl.h>
 #include <linux/ptrace.h>
 #include <linux/malloc.h>
-#include <linux/scc.h>
 #include <linux/delay.h>
-#include "scc_config.h"
+#include <linux/scc.h>
 
 #include <asm/system.h>
 #include <asm/io.h>
@@ -150,12 +194,23 @@
 #include <time.h>
 #include <linux/kernel.h>
 
+#ifdef MODULE
+int init_module(void);
+void cleanup_module(void);
+#endif
+
 #ifndef Z8530_MAJOR
 #define Z8530_MAJOR 34
 #endif
 
 int scc_init(void);
 
+static struct mbuf * scc_enqueue_buffer(struct mbuf **queue, struct mbuf * buffer);
+static struct mbuf * scc_dequeue_buffer(struct mbuf **queue);
+static void alloc_buffer_pool(struct scc_channel *scc);
+static void free_buffer_pool(struct scc_channel *scc);
+static struct mbuf * scc_get_buffer(struct scc_channel *scc, char type);
+
 int scc_open(struct tty_struct *tty, struct file *filp);
 static void scc_close(struct tty_struct *tty, struct file *filp);
 int scc_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count);
@@ -166,6 +221,7 @@
 static void scc_flush_buffer(struct tty_struct *tty);
 static int scc_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg);
 static void scc_set_termios(struct tty_struct *tty, struct termios *old_termios);
+static void scc_set_ldisc(struct tty_struct *tty);
 static void scc_throttle(struct tty_struct *tty);
 static void scc_unthrottle(struct tty_struct *tty);
 static void scc_start(struct tty_struct *tty);
@@ -183,9 +239,9 @@
 static void scc_rxint(register struct scc_channel *scc);
 static void scc_spint(register struct scc_channel *scc);
 static void scc_isr(int irq, struct pt_regs *regs);
-static void scc_timer(void);
+static void scc_tx_timer(unsigned long);
+static void scc_rx_timer(unsigned long);
 static void scc_init_timer(struct scc_channel *scc);
-static void scc_rx_timer(void);
 
 /* from serial.c */
 
@@ -199,15 +255,21 @@
 static struct tty_struct *scc_table[2*MAXSCC];
 static struct termios scc_termios[2 * MAXSCC];
 static struct termios scc_termios_locked[2 * MAXSCC];
+
+struct irqflags { unsigned char used : 1; } Ivec[16];
 	
 struct scc_channel SCC_Info[2 * MAXSCC];         /* information per channel */
+io_port SCC_ctrl[2 * MAXSCC];			 /* Control ports */
 
 unsigned char Random = 0;		/* random number for p-persist */
 unsigned char Driver_Initialized = 0;
-static struct sccbuf *sccfreelist[MAX_IBUFS] = {0};
-static int allocated_ibufs = 0;
+int Nchips = 0;
+io_port Vector_Latch = 0;
+
+struct semaphore scc_sem = MUTEX;
+unsigned char scc_wbuf[BUFSIZE];
 
-static struct rx_timer_CB rx_timer_cb;
+static struct termios scc_std_termios;
 
 /* ******************************************************************** */
 /* *			Port Access Functions			      * */
@@ -219,9 +281,9 @@
 #ifdef SCC_LDELAY
 	register unsigned char r;
 	Outb(port, reg);
-	udelay(5);
+	udelay(SCC_LDELAY);
 	r=Inb(port);
-	udelay(5);
+	udelay(SCC_LDELAY);
 	return r;
 #else
 	Outb(port, reg);
@@ -233,8 +295,8 @@
 OutReg(register io_port port, register unsigned char reg, register unsigned char val)
 {
 #ifdef SCC_LDELAY
-	Outb(port, reg); udelay(5);
-	Outb(port, val); udelay(5);
+	Outb(port, reg); udelay(SCC_LDELAY);
+	Outb(port, val); udelay(SCC_LDELAY);
 #else
 	Outb(port, reg);
 	Outb(port, val);
@@ -263,243 +325,379 @@
 /* * 			Memory Buffer Management			*/
 /* ******************************************************************** */
 
-/* mbuf concept lent from KA9Q. Tnx PE1AYX for the buffer pool concept	*/
-/* (sorry, do you have any better ideas?) */
+/*
+ * The new buffer scheme uses a ring chain of buffers. This has the
+ * advantage to access both, first and last element of the list, very
+ * fast. It has the disadvantage to mess things up double if something
+ * is wrong.
+ *
+ * Every scc channel has its own buffer pool now with an adjustable
+ * number of buffers for transmit and receive buffers. The buffer
+ * size remains the same for each AX.25 frame, but is (as a semi-
+ * undocumented feature) adjustable within a range of 512 and 4096
+ * to benefit experiments with higher frame lengths. If you need
+ * larger frames... Well, you'd better choose a whole new concept
+ * for that, like a DMA capable I/O card and a block device driver...
+ *
+ */
 
+/* ------ Management of buffer queues ------ */
 
-/* allocate memory for the interrupt buffer pool */
 
-void scc_alloc_buffer_pool(void)
-{
-	int i;
-	struct sccbuf *sccb;
-	struct mbuf   *bp;
-	
-   	for (i = 0 ; i < MAX_IBUFS ; i++)
-   	{
-   		sccb = (struct sccbuf *)kmalloc(sizeof(struct sccbuf), GFP_ATOMIC);
-   		bp = (struct mbuf *)kmalloc(sizeof(struct mbuf), GFP_ATOMIC);
-   		
-		if ( !(sccb && bp) )
-		{
-			allocated_ibufs = --i;
-			
-			if (allocated_ibufs < 0)
-				panic("scc_alloc_buffer_pool() - can't get buffer space");
-			else
-				printk("Warning: scc_alloc_buffer_pool() - allocated only %i buffers\n",i);
-				
-			return;
-		}
-		
-		sccfreelist[i] = sccb;
-		sccfreelist[i]->bp = bp;
-		memset(sccfreelist[i]->bp ,0,sizeof(struct mbuf));
-		sccfreelist[i]->inuse = 0;
-		sccfreelist[i]->bp->type = 0;
-		sccfreelist[i]->bp->refcnt = 0;
-		sccfreelist[i]->bp->size = BUFSIZE;
-	}
-	allocated_ibufs = MAX_IBUFS;
-}
+/* Insert a buffer at the "end" of the chain */
 
-unsigned int scc_count_used_buffers(unsigned int * rx, unsigned int * tx)
+static struct mbuf * 
+scc_enqueue_buffer(struct mbuf **queue, struct mbuf * buffer)
 {
-	unsigned int i, used = 0;
+	unsigned long flags;
+	struct mbuf * anchor;
 	
-	if (rx) *rx = 0;
-	if (tx) *tx = 0;
+	save_flags(flags); cli();		/* do not disturb! */
+
+#ifdef DEBUG_BUFFERS			
+	if (queue == NULLBUFP)			/* called with illegal parameters, notify the user */
+
+	{
+		printk("z8530drv: Pointer to queue anchor is NULL pointer [enq]\n");
+		restore_flags(flags);
+		return NULLBUF;
+	}
 	
-	for (i = 0 ; i < allocated_ibufs ; i++)
+	if (buffer == NULLBUF)
 	{
-		if (sccfreelist[i]->inuse)
-		{
-			switch (sccfreelist[i]->bp->type)
-			{
-				case BT_RECEIVE:
-					if (rx) (*rx)++; break;
-				case BT_TRANSMIT:
-					if (tx) (*tx)++; break;
-			}
-			
-			used++;
-		}
+		printk("z8530drv: can't enqueue a NULL pointer\n");
+		restore_flags(flags);
+		return NULLBUF;
+	}
+#endif
+
+	anchor = *queue;			/* the anchor is the "start" of the chain */
+
+	if (anchor == NULLBUF)			/* found an empty list */
+	{
+		*queue = buffer;		/* new anchor */
+		buffer->next = buffer->prev = NULLBUF;
+	} else	
+	if (anchor->prev == NULLBUF)		/* list has one member only */
+	{
+#ifdef DEBUG_BUFFERS
+		if (anchor->prev != NULLBUF)	/* oops?! */
+			printk("weird --- anchor->prev is NULL but not anchor->next [enq]\n");
+#endif			
+		anchor->next = anchor->prev = buffer;
+		buffer->next = buffer->prev = anchor;
+	} else
+#ifdef DEBUG_BUFFERS	
+	if (anchor->next == NULLBUF)		/* this has to be an error. Okay, make the best out of it */
+	{
+		printk("z8530drv: weird --- anchor->next is NULL but not anchor->prev [enq]\n");
+		anchor->next = anchor->prev = buffer;
+		buffer->next = buffer->prev = anchor;
+	} else
+#endif
+	{					/* every other case */
+		buffer->prev = anchor->prev;	/* self explaining, isn't it? */
+		buffer->next = anchor;
+		anchor->prev->next = buffer;
+		anchor->prev = buffer;
 	}
 	
-	return used;
+	restore_flags(flags);
+	return *queue;				/* return "start" of chain */
 }
 
 
-/* Allocate mbuf */
-struct mbuf *
-scc_get_buffer(char type)
+
+/* Remove a buffer from the "start" of the chain an return it */
+
+static struct mbuf * 
+scc_dequeue_buffer(struct mbuf **queue)
 {
-	int i;
 	unsigned long flags;
+	struct mbuf *buffer;
+	
+	save_flags(flags); cli();
+
+#ifdef DEBUG_BUFFERS			
+	if (queue == NULLBUFP)			/* called with illegal parameter */
 
-	save_flags(flags); cli();	/* just to be sure */
+	{
+		printk("z8530drv: Pointer to queue anchor is NULL pointer [deq]\n");
+		restore_flags(flags);
+		return NULLBUF;
+	}
+#endif
 
-	for (i = 0 ; i < allocated_ibufs ; i++)
+	buffer = *queue;			/* head of the chain */
+	
+	if (buffer != NULLBUF)			/* not an empty list? */
 	{
-		if(sccfreelist[i]->inuse == 0)
+		if (buffer->prev != NULLBUF)	/* not last buffer? */
 		{
-			sccfreelist[i]->inuse = 1;
-			sccfreelist[i]->bp->type = type;
-			sccfreelist[i]->bp->next = NULLBUF;
-			sccfreelist[i]->bp->anext = NULLBUF;
-			sccfreelist[i]->bp->dup = NULLBUF;
-			sccfreelist[i]->bp->size = BUFSIZE;
-			sccfreelist[i]->bp->refcnt = 1;
-			sccfreelist[i]->bp->cnt = 0;
-			sccfreelist[i]->bp->in_use = 0;
+#ifdef DEBUG_BUFFERS
+			if (buffer->next == NULLBUF)
+			{			/* what?! */
+				printk("z8530drv: weird --- buffer->next is NULL but not buffer->prev [deq]\n");
+			} else
+#endif
+			if (buffer->prev->next == buffer->prev->prev)
+			{			/* only one buffer remaining... */
+				buffer->next->prev = NULLBUF;
+				buffer->next->next = NULLBUF;
+			} else {		/* the one remaining situation... */
+				buffer->next->prev = buffer->prev;
+				buffer->prev->next = buffer->next;
+			}
+		} 
+#ifdef DEBUG_BUFFERS
+		else if (buffer->next != NULLBUF)
+			printk("z8530drv: weird --- buffer->prev is NULL but not buffer->next [deq]\n");
+#endif
+		*queue = buffer->next;		/* new head of chain */
 		
-			restore_flags(flags);
-			return sccfreelist[i]->bp;
-		}
-	}
+		buffer->next = NULLBUF;		/* for securety only... */
+		buffer->prev = NULLBUF;
+		buffer->rw_ptr = buffer->data;
+	} 
 	
-	printk("\nSCC scc_get_buffer(): buffer pool empty\n");	/* should never happen */
 	restore_flags(flags);
-	return NULLBUF;
+	return buffer;				/* give it away... */
 }
 
+/* ------ buffer pool management ------ */
 
-/* Decrement the reference pointer in an mbuf. If it goes to zero,
- * free all resources associated with mbuf.
- * Return pointer to next mbuf in packet chain
- */
-struct mbuf *
-scc_return_buffer(register struct mbuf *bp, char type)
+
+/* allocate buffer pool	for a channel */
+
+static void 
+alloc_buffer_pool(struct scc_channel *scc)
 {
-	struct mbuf *bpnext;
-	int i;
-	unsigned long flags;
+	int k;
+	struct mbuf * bptr;
+	int  buflen;
 	
-	if(!bp)
-		return NULLBUF;
-		
-	save_flags(flags); cli();
-	bpnext = bp->next;
+	buflen = sizeof(struct mbuf) + scc->stat.bufsize;
 	
-	if (bp->dup)
+	if (scc->stat.bufsize <  336) scc->stat.bufsize = 336;
+	if (buflen > 4096)
 	{
-		for(i = 0 ; i < allocated_ibufs ; i++)
+		scc->stat.bufsize = 4096-sizeof(struct mbuf);
+		buflen = 4096;
+	}
+	
+	if (scc->stat.rxbuffers < 4)  scc->stat.rxbuffers = 4;
+	if (scc->stat.txbuffers < 4)  scc->stat.txbuffers = 4;
+	
+
+	
+	/* allocate receive buffers for this channel */
+	
+	for (k = 0; k < scc->stat.rxbuffers ; k++)
+	{
+		/* allocate memory for the struct and the buffer */
+		
+		bptr = (struct mbuf *) kmalloc(buflen, GFP_ATOMIC);
+		
+		/* should not happen, but who knows? */
+		
+		if (bptr == NULLBUF)
 		{
-			if(sccfreelist[i]->bp == bp->dup)
-			{
-			      if (sccfreelist[i]->bp->type != type)
-			      {
-			      	   printk("scc_return_buffer(bp->dup, %i): wrong buffer type %i",
-			      	   	  type,sccfreelist[i]->bp->type);
-			      }
-			      
-			      sccfreelist[i]->bp->cnt = 0;
-			      sccfreelist[i]->bp->refcnt = 0;
-			      sccfreelist[i]->bp->in_use = 0;
-			      sccfreelist[i]->inuse = 0;
-			      bp->dup = NULLBUF;
-			}
+			printk("z8530drv: %s: can't allocate memory for rx buffer pool", kdevname(scc->tty->device));
+			break;
 		}
+		
+		/* clear memory */
+		
+		memset(bptr, 0, buflen);
+		
+		/* initialize structure */
+		
+		bptr->rw_ptr = bptr->data;
+		
+		/* and append the buffer to the pool */
+		
+		scc_enqueue_buffer(&scc->rx_buffer_pool, bptr);
 	}
 	
-	/* Decrement reference count. If it has gone to zero, free it. */
-	if(--bp->refcnt <= 0)
+	/* now do the same for the transmit buffers */
+	
+	for (k = 0; k < scc->stat.txbuffers ; k++)
 	{
-		for(i = 0 ; i < allocated_ibufs ; i++)
+		bptr = (struct mbuf *) kmalloc(buflen, GFP_ATOMIC);
+		
+		if (bptr == NULLBUF)
 		{
-			if(sccfreelist[i]->bp == bp)
-			{
-				if (sccfreelist[i]->bp->type != type)
-				{
-					printk("scc_return_buffer(bp, %i): wrong buffer type %i",
-						type,sccfreelist[i]->bp->type);
-				}				
-			     
-				sccfreelist[i]->bp->cnt = 0;
-				sccfreelist[i]->bp->refcnt = 0;
-				sccfreelist[i]->inuse = 0;
-				restore_flags(flags);
-				return bpnext;
-			}
+			printk("z8530drv: %s: can't allocate memory for tx buffer pool", kdevname(scc->tty->device));
+			break;
 		}
-	}
 		
-	printk("\nscc_return_buffer(): bogus pointer %p\n",bp);
-	restore_flags(flags);
-	return bpnext;
-} 
-
-
-/* Free packet (a chain of mbufs). Return pointer to next packet on queue,
- * if any
- */
-struct mbuf *
-scc_free_chain(register struct mbuf *bp, char type)
-{
-	register struct mbuf *abp;
-	unsigned long flags;
-
-	if(!bp) 
-		return NULLBUF;
+		memset(bptr, 0, buflen);
 		
-	save_flags(flags); cli();	
-	
-	abp = bp->anext;
-	while (bp) bp = scc_return_buffer(bp, type);
+		bptr->rw_ptr = bptr->data;
 		
-	restore_flags(flags);
-	return abp;
+		scc_enqueue_buffer(&scc->tx_buffer_pool, bptr);
+	}
 }
 
 
-/* Append mbuf to end of mbuf chain */
-void
-scc_append_to_chain(struct mbuf **bph,struct mbuf *bp)
+/* remove buffer pool */
+
+static void 
+free_buffer_pool(struct scc_channel *scc)
 {
-	register struct mbuf *p;
+	struct mbuf * bptr;
 	unsigned long flags;
-
-	if(bph == NULLBUFP || bp == NULLBUF)
-		return;
+	int cnt;
 	
+	/* this one is a bit tricky and probably dangerous. */
+		
 	save_flags(flags); cli();
 	
-	if(*bph == NULLBUF)
+	/* esp. to free the buffers currently in use by ISR */
+	
+	bptr = scc->rx_bp;
+	if (bptr != NULLBUF)
 	{
-		/* First one on chain */
-		*bph = bp;
-	} else {
-		for(p = *bph ; p->next != NULLBUF ; p = p->next)
-			;
-		p->next = bp;
+		scc->rx_bp = NULLBUF;
+		scc_enqueue_buffer(&scc->rx_buffer_pool, bptr);
+	}
+
+	bptr = scc->tx_bp;	
+	if (bptr != NULLBUF)
+	{
+		scc->tx_bp = NULLBUF;
+		scc_enqueue_buffer(&scc->tx_buffer_pool, bptr);
+	}
+	
+	bptr = scc->kiss_decode_bp;
+	if (bptr != NULLBUF)
+	{
+		scc->kiss_decode_bp = NULLBUF;
+		scc_enqueue_buffer(&scc->tx_buffer_pool, bptr);
+	}
+	
+	bptr = scc->kiss_encode_bp;
+	if (bptr != NULLBUF)
+	{
+		scc->kiss_encode_bp = NULLBUF;
+		scc_enqueue_buffer(&scc->rx_buffer_pool, bptr);
 	}
 	
 	restore_flags(flags);
+	
+	
+	while (scc->rx_queue != NULLBUF)
+	{
+		bptr = scc_dequeue_buffer(&scc->rx_queue);
+		scc_enqueue_buffer(&scc->rx_buffer_pool, bptr);
+	}
+	
+	while (scc->tx_queue != NULLBUF)
+	{
+		bptr = scc_dequeue_buffer(&scc->tx_queue);
+		scc_enqueue_buffer(&scc->tx_buffer_pool, bptr);
+	}
+	
+	/* you want to know why we move every buffer back to the
+	   buffer pool? Well, good question... ;-) 
+	 */
+	
+	cnt = 0;
+	
+	/* Probably because we just want a central position in the
+	   code were we actually call free()?
+	 */
+	
+	while (scc->rx_buffer_pool != NULLBUF)
+	{
+		bptr = scc_dequeue_buffer(&scc->rx_buffer_pool);
+		
+		if (bptr != NULLBUF)
+		{
+			cnt++;
+			kfree(bptr);
+		}
+	}
+
+	if (cnt < scc->stat.rxbuffers)		/* hmm... hmm... :-( */
+		printk("z8530drv: oops, deallocated only %d of %d rx buffers\n", cnt, scc->stat.rxbuffers);
+	if (cnt > scc->stat.rxbuffers)		/* WHAT?!! */
+		printk("z8530drv: oops, deallocated %d instead of %d rx buffers. Very strange.\n", cnt, scc->stat.rxbuffers);
+		
+	cnt = 0;
+	
+	while (scc->tx_buffer_pool != NULLBUF)
+	{
+		bptr = scc_dequeue_buffer(&scc->tx_buffer_pool);
+		
+		if (bptr != NULLBUF)
+		{
+			cnt++;
+			kfree(bptr);
+		}
+	}
+
+	if (cnt < scc->stat.txbuffers)
+		printk("z8530drv: oops, deallocated only %d of %d tx buffers\n", cnt, scc->stat.txbuffers);
+	if (cnt > scc->stat.txbuffers)
+		printk("z8530drv: oops, deallocated %d instead of %d tx buffers. Very strange.\n", cnt, scc->stat.txbuffers);
 }
+		
+		
+/* ------ rx/tx buffer management ------ */
 
+/* 
+   get a fresh buffer from the pool if possible; if not: get one from
+   the queue. We will remove the oldest frame from the queue and hope
+   it was a good idea... ;-)
+   
+ */
 
-/* Append packet (chain of mbufs) to end of packet queue */
-void
-scc_enqueue(struct mbuf **queue,struct mbuf *bp)
+static struct mbuf * 
+scc_get_buffer(struct scc_channel *scc, char type)
 {
-	register struct mbuf *p;
-	unsigned long flags;
-
-	if(queue == NULLBUFP || bp == NULLBUF)
-		return;
-		
-	save_flags(flags); cli();
+	struct mbuf * bptr;
 	
-	if(*queue == NULLBUF)
+	if (type == BT_TRANSMIT)
 	{
-		/* List is empty, stick at front */
-		*queue = bp;
+		bptr = scc_dequeue_buffer(&scc->tx_buffer_pool);
+		
+		/* no free buffers in the pool anymore? */
+		
+		if (bptr == NULLBUF)
+		{
+			printk("z8530drv: scc_get_buffer(%s): tx buffer pool empty\n", kdevname(scc->tty->device));
+			
+			/* use the oldest from the queue instead */
+			
+			bptr = scc_dequeue_buffer(&scc->tx_queue);
+			
+			/* this should never, ever happen... */
+			
+			if (bptr == NULLBUF)
+				printk("z8530drv: scc_get_buffer(): panic - even no buffer found in tx queue\n");
+		}
 	} else {
-		for(p = *queue ; p->anext != NULLBUF ; p = p->anext)
-			;
-		p->anext = bp;
+		bptr = scc_dequeue_buffer(&scc->rx_buffer_pool);
+		
+		if (bptr == NULLBUF)
+		{
+			printk("z8530drv: scc_get_buffer(%s): rx buffer pool empty\n", kdevname(scc->tty->device));
+			
+			bptr = scc_dequeue_buffer(&scc->rx_queue);
+			
+			if (bptr == NULLBUF)
+				printk("z8530drv: scc_get_buffer(): panic - even no buffer found in rx queue\n");
+		}	
 	}
-	restore_flags(flags);
+	
+	if (bptr != NULLBUF)
+	{
+		bptr->rw_ptr = bptr->data;
+		bptr->cnt = 0;
+	}
+
+	return bptr;
 }
 
 
@@ -625,20 +823,6 @@
 /*       DCD/CTS and Rx/Tx errors					*/
 
 
-static inline void prepare_next_txframe(register struct scc_channel *scc)
-{
-	if ((scc->tbp = scc->sndq))
-	{
-		scc->sndq = scc->sndq->anext;
-		scc->stat.tx_state = TXS_NEWFRAME;
-
-	} else {
-		scc->stat.tx_state = TXS_BUSY;
-		scc->t_tail = scc->kiss.tailtime;
-	}
-}
-
-
 /* Transmitter interrupt handler */
 static void
 scc_txint(register struct scc_channel *scc)
@@ -647,56 +831,60 @@
 
 	scc->stat.txints++;
 
-	bp = scc->tbp;
-			
-	while (bp && !bp->cnt)      /* find next buffer */
-		bp = scc_return_buffer(bp, BT_TRANSMIT);
-				
-	if (bp == NULLBUF)			/* no more buffers in this frame */
+	bp = scc->tx_bp;
+	
+	if (bp == NULLBUF)
 	{
-		if (--scc->stat.tx_queued < 0)
-			scc->stat.tx_queued = 0;
+		do 
+		{
+			if (bp != NULLBUF)
+				scc_enqueue_buffer(&scc->tx_buffer_pool, bp);
+
+			bp = scc_dequeue_buffer(&scc->tx_queue);
 			
-		Outb(scc->ctrl,RES_Tx_P);       /* reset pending int */					
-		cl(scc,R10,ABUNDER);            /* frame complete, allow CRC transmit */ 
-		prepare_next_txframe(scc);
-		
-	} else {				/* okay, send byte */
-	
-		if (scc->stat.tx_state == TXS_NEWFRAME)
-		{				/* first byte ? */
-			Outb(scc->ctrl, RES_Tx_CRC);	/* reset CRC generator */
-			or(scc,R10,ABUNDER);		/* re-install underrun protection */
-			Outb(scc->data,bp->data[bp->in_use++]);
-							/* send byte */
-			if (!scc->enhanced)		/* reset EOM latch */
-				Outb(scc->ctrl, RES_EOM_L);
+			if (bp == NULLBUF)
+			{
+				scc->stat.tx_state = TXS_BUSY;
+				scc->t_tail = scc->kiss.tailtime;
 				
-			scc->stat.tx_state = TXS_ACTIVE;/* next byte... */
-		} else {
-			Outb(scc->data,bp->data[bp->in_use++]);
-		}
+				Outb(scc->ctrl, RES_Tx_P);	/* clear int */
+				return;
+			}
+			
+			if ( scc->kiss.not_slip && (bp->cnt > 0) )
+			{
+				bp->rw_ptr++;
+				bp->cnt--;
+			}
+			
+		} while (bp->cnt < 1);
 		
-		bp->cnt--;                      /* decrease byte count */
-		scc->tbp=bp;			/* store buffer address */
-	}
-}
-
-/* Throw away received mbuf(s) when an error occurred */
-
-static inline void
-scc_toss_buffer(register struct scc_channel *scc)
-{
-	register struct mbuf *bp;
-	
-	if((bp = scc->rbp) != NULLBUF)
+		
+		Outb(scc->ctrl, RES_Tx_CRC);	/* reset CRC generator */
+		or(scc,R10,ABUNDER);		/* re-install underrun protection */
+		Outb(scc->data,*bp->rw_ptr);	/* send byte */
+		if (!scc->enhanced)		/* reset EOM latch */
+			Outb(scc->ctrl, RES_EOM_L);
+		
+		scc->tx_bp = bp;
+		scc->stat.tx_state = TXS_ACTIVE; /* next byte... */
+	} else
+	if (bp->cnt <= 0)
 	{
-		scc_free_chain(bp->next, BT_RECEIVE);
-		bp->next = NULLBUF;
-		scc->rbp1 = bp;         /* Don't throw this one away */
-		bp->cnt = 0;            /* Simply rewind it */
-		bp->in_use = 0;
+		if (--scc->stat.tx_queued < 0) scc->stat.tx_queued = 0;
+		
+		Outb(scc->ctrl, RES_Tx_P);	/* reset pending int */
+		cl(scc, R10, ABUNDER);		/* send CRC */
+		scc_enqueue_buffer(&scc->tx_buffer_pool, bp);
+		scc->tx_bp = NULLBUF;
+		scc->stat.tx_state = TXS_NEWFRAME; /* next frame... */
+		return;
+	} else {
+		Outb(scc->data,*bp->rw_ptr);		
 	}
+	
+	bp->rw_ptr++;			/* increment pointer */
+	bp->cnt--;                      /* decrease byte count */
 }
 
 static inline void
@@ -707,17 +895,16 @@
 	for (k=0; k<3; k++)
 		Inb(scc->data);
 		
-	if(scc->rbp != NULLBUF)	/* did we receive something? */
+	if(scc->rx_bp != NULLBUF)	/* did we receive something? */
 	{
-		if(scc->rbp->next != NULLBUF || scc->rbp->cnt > 0)
-			scc->stat.rxerrs++;  /* then count it as an error */
-			
-		scc_toss_buffer(scc);         /* throw away buffer */
+		scc->stat.rxerrs++;  /* then count it as an error */
+		scc_enqueue_buffer(&scc->rx_buffer_pool, scc->rx_bp);
+		
+		scc->rx_bp = NULLBUF;
 	}
 }
 
 
-
 /* External/Status interrupt handler */
 static void
 scc_exint(register struct scc_channel *scc)
@@ -753,7 +940,7 @@
 		}
 	}
 
-
+#ifdef notdef
 	/* CTS: use external TxDelay (what's that good for?!) */
 	
 	if (chg_and_stat & CTS)			/* CTS is now ON */
@@ -762,14 +949,20 @@
 			scc->t_txdel = 0;	/* kick it! */		
 		
 	}
+#endif
 	
-	if ((scc->stat.tx_state == TXS_ACTIVE) && (status & TxEOM))
+	if ( (status & TxEOM) && (scc->stat.tx_state == TXS_ACTIVE) )
 	{
 		scc->stat.tx_under++;	  /* oops, an underrun! count 'em */
 		Outb(scc->ctrl, RES_Tx_P);
 		Outb(scc->ctrl, RES_EXT_INT);	/* reset ext/status interrupts */
 		scc->t_maxk = 1;
-		scc->tbp = scc_free_chain(scc->tbp, BT_TRANSMIT);
+		
+		if (scc->tx_bp != NULLBUF)
+		{
+			scc_enqueue_buffer(&scc->tx_buffer_pool, scc->tx_bp);
+			scc->tx_bp = NULLBUF;
+		}
 		
 		if (--scc->stat.tx_queued < 0) scc->stat.tx_queued = 0;
 		or(scc,R10,ABUNDER);
@@ -780,7 +973,13 @@
 		scc->stat.tx_under = 9999;  /* errr... yes. */
 		Outb(scc->ctrl, RES_Tx_P); /* just to be sure */
 		scc->t_maxk = 1;
-		scc->tbp = scc_free_chain(scc->tbp, BT_TRANSMIT);
+		
+		if (scc->tx_bp != NULLBUF)
+		{
+			scc_enqueue_buffer(&scc->tx_buffer_pool, scc->tx_bp);
+			scc->tx_bp = NULLBUF;
+		}
+
 		if (--scc->stat.tx_queued < 0) scc->stat.tx_queued = 0;
 		scc->kiss.tx_inhibit = 1;	/* don't try it again! */
 	}
@@ -806,54 +1005,60 @@
 		return;
 	}
 
-	if ((bp = scc->rbp1) == NULLBUF || bp->cnt >= bp->size)	
-	{ 				/* no buffer available or buffer full */
-		if (scc->rbp == NULLBUF)
-		{
-			if ((bp = scc_get_buffer(BT_RECEIVE)) != NULLBUF)
-				scc->rbp = scc->rbp1 = bp;
-				
-		}
-		else if ((bp = scc_get_buffer(BT_RECEIVE)))
-		{
-			scc_append_to_chain(&scc->rbp, bp);
-			scc->rbp1 = bp;
-		}
-		
-		if (bp == NULLBUF)		/* no buffer available? */
+	bp = scc->rx_bp;
+	
+	if (bp == NULLBUF)
+	{
+		bp = scc_get_buffer(scc, BT_RECEIVE);
+		if (bp == NULLBUF)
 		{
-			Inb(scc->data);		/* discard character */
-			or(scc,R3,ENT_HM);      /* enter hunt mode */
-			scc_toss_buffer(scc);	/* throw away buffers */
-			scc->stat.nospace++;    /* and count this error */
+			printk("scc_rxint(): panic --- cannot get a buffer\n");
+			Inb(scc->data);
+			or(scc, R3, ENT_HM);
+			scc->stat.nospace++;
 			return;
 		}
+		
+		scc->rx_bp = bp;
 	}
-
+	
+	if (bp->cnt > scc->stat.bufsize)
+	{
+#ifdef notdef
+		printk("scc_rxint(): oops, received huge frame...\n");
+#endif
+		scc_enqueue_buffer(&scc->rx_buffer_pool, bp);
+		scc->rx_bp = NULLBUF;
+		Inb(scc->data);
+		or(scc, R3, ENT_HM);
+		return;
+	}
+		
 	/* now, we have a buffer. read character and store it */
-	bp->data[bp->cnt++] = Inb(scc->data);
+	*bp->rw_ptr = Inb(scc->data);
+	bp->rw_ptr++;
+	bp->cnt++;
 }
 
 /* kick rx_timer (try to send received frame or part of it ASAP) */
-/* !experimental! */
+
+/* of course we could define a "bottom half" routine to do the job,
+   but since its structures are saved in an array instead of a linked
+   list we would get in trouble if it clashes with another driver or
+   when we try to modularize the driver. IMHO we are fast enough
+   with a timer routine called on the next timer-INT... Your opinions?
+ */
 
 static inline void
 kick_rx_timer(register struct scc_channel *scc)
 {
-	register unsigned long expires;
-
-	if (!rx_timer_cb.lock)
-	{
-		expires = timer_table[SCC_TIMER].expires - jiffies;
-
-		rx_timer_cb.expires = (expires > 1)? expires:1;
-		rx_timer_cb.scc = scc;
-		rx_timer_cb.lock = 1;
-	
-		timer_table[SCC_TIMER].fn = scc_rx_timer;
-		timer_table[SCC_TIMER].expires = jiffies + 1;
-		timer_active |= 1 << SCC_TIMER;
-	}
+	if (scc->rx_t.next)
+		del_timer(&(scc->rx_t));
+		
+	scc->rx_t.expires = jiffies + 1;
+	scc->rx_t.function = scc_rx_timer;
+	scc->rx_t.data = (unsigned long) scc;
+	add_timer(&scc->rx_t);
 }
 
 /* Receive Special Condition interrupt handler */
@@ -868,32 +1073,34 @@
 	status = InReg(scc->ctrl,R1);		/* read receiver status */
 	
 	Inb(scc->data);				/* throw away Rx byte */
+	bp = scc->rx_bp;
 
 	if(status & Rx_OVR)			/* receiver overrun */
 	{
-		scc->stat.rx_over++;                /* count them */
-		or(scc,R3,ENT_HM);              /* enter hunt mode for next flag */
-		scc_toss_buffer(scc);                 /* rewind the buffer and toss */
+		scc->stat.rx_over++;             /* count them */
+		or(scc,R3,ENT_HM);               /* enter hunt mode for next flag */
+		
+		if (bp) scc_enqueue_buffer(&scc->rx_buffer_pool, bp);
+		scc->rx_bp = NULLBUF;
 	}
 	
-	if(status & END_FR && scc->rbp != NULLBUF)	/* end of frame */
+	if(status & END_FR && bp != NULLBUF)	/* end of frame */
 	{
 		/* CRC okay, frame ends on 8 bit boundary and received something ? */
 		
-		if (!(status & CRC_ERR) && (status & 0xe) == RES8 && scc->rbp->cnt)
+		if (!(status & CRC_ERR) && (status & 0xe) == RES8 && bp->cnt)
 		{
 			/* ignore last received byte (first of the CRC bytes) */
+			bp->cnt--;
 			
-			for (bp = scc->rbp; bp->next != NULLBUF; bp = bp->next) ;
-				bp->cnt--;              /* last byte is first CRC byte */
-				
-			scc_enqueue(&scc->rcvq,scc->rbp);
-			scc->rbp = scc->rbp1 = NULLBUF;
+			scc_enqueue_buffer(&scc->rx_queue, bp);
+			scc->rx_bp = NULLBUF;
 			scc->stat.rxframes++;
 			scc->stat.rx_queued++;
 			kick_rx_timer(scc);
 		} else {				/* a bad frame */
-			scc_toss_buffer(scc);		/* throw away frame */
+			scc_enqueue_buffer(&scc->rx_buffer_pool, bp);
+			scc->rx_bp = NULLBUF;
 			scc->stat.rxerrs++;
 		}
 	}
@@ -925,7 +1132,8 @@
 
 static inline void set_speed(register struct scc_channel *scc)
 {
-	set_brg(scc, (unsigned) (Clock / (scc->modem.speed * 64)) - 2);
+	if (scc->modem.speed > 0)	/* paranoia... */
+		set_brg(scc, (unsigned) (scc->clock / (scc->modem.speed * 64)) - 2);
 }
 
 
@@ -938,19 +1146,68 @@
 	OutReg(scc->ctrl, R14, SNRZI|scc->wreg[R14]);	/* DPLL NRZI mode */
 }
 
+/*
+ * Initalization according to the Z8530 manual (SGS-Thomson's version):
+ *
+ * 1. Modes and constants
+ *
+ * WR9	11000000	chip reset
+ * WR4	XXXXXXXX	Tx/Rx control, async or sync mode
+ * WR1	0XX00X00	select W/REQ (optional)
+ * WR2	XXXXXXXX	program interrupt vector
+ * WR3	XXXXXXX0	select Rx control
+ * WR5	XXXX0XXX	select Tx control
+ * WR6	XXXXXXXX	sync character
+ * WR7	XXXXXXXX	sync character
+ * WR9	000X0XXX	select interrupt control
+ * WR10	XXXXXXXX	miscellaneous control (optional)
+ * WR11	XXXXXXXX	clock control
+ * WR12	XXXXXXXX	time constant lower byte (optional)
+ * WR13	XXXXXXXX	time constant upper byte (optional)
+ * WR14	XXXXXXX0	miscellaneous control
+ * WR14	XXXSSSSS	commands (optional)
+ *
+ * 2. Enables
+ *
+ * WR14	000SSSS1	baud rate enable
+ * WR3	SSSSSSS1	Rx enable
+ * WR5	SSSS1SSS	Tx enable
+ * WR0	10000000	reset Tx CRG (optional)
+ * WR1	XSS00S00	DMA enable (optional)
+ *
+ * 3. Interrupt status
+ *
+ * WR15	XXXXXXXX	enable external/status
+ * WR0	00010000	reset external status
+ * WR0	00010000	reset external status twice
+ * WR1	SSSXXSXX	enable Rx, Tx and Ext/status
+ * WR9	000SXSSS	enable master interrupt enable
+ *
+ * 1 = set to one, 0 = reset to zero
+ * X = user defined, S = same as previous init
+ *
+ *
+ * Note that the implementation differs in some points from above scheme.
+ *
+ */
+ 
 static void
 init_channel(register struct scc_channel *scc)
 {
 	unsigned long flags;
+	
+	if (scc->rx_t.next) del_timer(&(scc->rx_t));
+	if (scc->tx_t.next) del_timer(&(scc->tx_t));
 
 	save_flags(flags); cli();
 
+	wr(scc,R4,X1CLK|SDLC);		/* *1 clock, SDLC mode */
 	wr(scc,R1,0);			/* no W/REQ operation */
 	wr(scc,R3,Rx8|RxCRC_ENAB);	/* RX 8 bits/char, CRC, disabled */	
-	wr(scc,R4,X1CLK|SDLC);		/* *1 clock, SDLC mode */
 	wr(scc,R5,Tx8|DTR|TxCRC_ENAB);	/* TX 8 bits/char, disabled, DTR */
 	wr(scc,R6,0);			/* SDLC address zero (not used) */
 	wr(scc,R7,FLAG);		/* SDLC flag value */
+	wr(scc,R9,VIS);			/* vector includes status */
 	wr(scc,R10,(scc->modem.nrz? NRZ : NRZI)|CRCPS|ABUNDER); /* abort on underrun, preset CRC generator, NRZ(I) */
 	wr(scc,R14, 0);
 
@@ -989,20 +1246,19 @@
 			break;
 
 		case CLK_DIVIDER:
-			wr(scc, R11, ((Board & BAYCOM)? TRxCDP : TRxCBR) | RCDPLL|TCRTxCP|TRxCOI);
+			wr(scc, R11, ((scc->brand & BAYCOM)? TRxCDP : TRxCBR) | RCDPLL|TCRTxCP|TRxCOI);
 			init_brg(scc);
 			break;
 
 		case CLK_EXTERNAL:
-			wr(scc, R11, (Board & BAYCOM)? RCTRxCP|TCRTxCP : RCRTxCP|TCTRxCP);
+			wr(scc, R11, (scc->brand & BAYCOM)? RCTRxCP|TCRTxCP : RCRTxCP|TCTRxCP);
 			OutReg(scc->ctrl, R14, DISDPLL);
 			break;
 
 	}
 	
-	/* enable CTS (not for Baycom), ABORT & DCD interrupts */
-	wr(scc,R15,((Board & BAYCOM) ? 0 : CTSIE)|BRKIE|DCDIE|TxUIE);
-
+	set_speed(scc);			/* set baudrate */
+	
 	if(scc->enhanced)
 	{
 		or(scc,R15,SHDLCE|FIFOE);	/* enable FIFO, SDLC/HDLC Enhancements (From now R7 is R7') */
@@ -1017,17 +1273,21 @@
 		or(scc,R3,ENT_HM|RxENABLE);	/* enable the receiver, hunt mode */
 	}
 	
+	/* enable CTS (not for Baycom), ABORT & DCD interrupts */
+	wr(scc,R15,((scc->brand & BAYCOM) ? 0 : CTSIE)|BRKIE|DCDIE|TxUIE);
+	
 	Outb(scc->ctrl,RES_EXT_INT);	/* reset ext/status interrupts */
 	Outb(scc->ctrl,RES_EXT_INT);	/* must be done twice */
-	
-	scc->status = InReg(scc->ctrl,R0);	/* read initial status */
 
 	or(scc,R1,INT_ALL_Rx|TxINT_ENAB|EXT_INT_ENAB); /* enable interrupts */
+	
+	scc->status = InReg(scc->ctrl,R0);	/* read initial status */
+	
 	or(scc,R9,MIE);			/* master interrupt enable */
+	
+	scc_init_timer(scc);
 			
 	restore_flags(flags);
-	
-	set_speed(scc); 
 }
 
 
@@ -1049,10 +1309,10 @@
 	if (scc->modem.speed < baud_table[1]) 
 		scc->modem.speed = 1200;
 		
-	if (Board & PRIMUS)
-		Outb(scc->ctrl + 4, Option | (tx? 0x80 : 0));
+	if (scc->brand & PRIMUS)
+		Outb(scc->ctrl + 4, scc->option | (tx? 0x80 : 0));
 	
-	time_const = (unsigned) (Clock / (scc->modem.speed * (tx? 2:64))) - 2;
+	time_const = (unsigned) (scc->clock / (scc->modem.speed * (tx? 2:64))) - 2;
 	
 	if (scc->modem.clocksrc == CLK_DPLL)
 	{				/* simplex operation */
@@ -1155,10 +1415,9 @@
 	scc->t_txdel = TIMER_STOPPED;
 	
 	scc->t_maxk = TPS * scc->kiss.maxkeyup;
-	prepare_next_txframe(scc);
 	
-	if (scc->stat.tx_state != TXS_BUSY)
-		scc_txint(scc);		
+	if (scc->tx_bp == NULLBUF)
+		scc_txint(scc);	
 }
 	
 
@@ -1177,7 +1436,7 @@
 	 
 	 if (scc->kiss.fulldup < 2)
 	 {
-	 	if (scc->sndq)		/* we had a timeout? */
+	 	if (scc->tx_bp)		/* we had a timeout? */
 	 	{
 	 		scc->stat.tx_state = TXS_BUSY;
 	 		scc->t_dwait = TPS * scc->kiss.mintime; /* try again */
@@ -1189,7 +1448,7 @@
 	 	return;
 	 }
 	 
-	 if (scc->sndq)			/* maxkeyup expired */ /* ?! */
+	 if (scc->tx_bp)			/* maxkeyup expired */ /* ?! */
 	 {
 	 	scc->stat.tx_state = TXS_BUSY;
 	 	scc->t_txdel = TPS * scc->kiss.waittime;
@@ -1205,11 +1464,10 @@
 #ifdef	THROW_AWAY_AFTER_BUSY_TIMEOUT
 	register struct mbuf *bp;		/* not tested */
 
-	bp = scc->sndq;
-	
-	while (bp) bp = scc_free_chain(bp, BT_TRANSMIT);
-	
-	scc->sndq = NULLBUF;
+	while (bp = scc_dequeue_buffer(&scc->tx_queue))
+		scc_enqueue_buffer(&scc->tx_buffer_pool, bp);
+		
+	scc->tx_queue = NULLBUF;
 	scc->stat.tx_state = TXS_IDLE;
 	
 #else
@@ -1229,81 +1487,66 @@
 	scc->t_tail = scc->kiss.tailtime;
 }
 
-static inline void check_rcv_queue(register struct scc_channel *scc)
+static void
+scc_tx_timer(unsigned long channel)
 {
-	register struct mbuf *bp;
-	
-	if (scc->stat.rx_queued > QUEUE_THRES)
-	{
-		if (scc->rcvq == NULLBUF)
-		{
-			printk("z8530drv: Warning - scc->stat.rx_queued shows overflow"
-			       " (%d) but queue is empty\n", scc->stat.rx_queued);
-			       
-			scc->stat.rx_queued = 0;	/* correct it */
-			scc->stat.nospace = 12345;	/* draw attention to it */
-			return;
-		}
-			
-		bp = scc->rcvq->anext;	/* don't use the one we currently use */
+	register struct scc_channel *scc;
+	unsigned long flags;
 		
-		while (bp && (scc->stat.rx_queued > QUEUE_HYST))
-		{
-			bp = scc_free_chain(bp, BT_RECEIVE);
-			scc->stat.rx_queued--;
-			scc->stat.nospace++;
-		}
+
+	scc = (struct scc_channel *) channel;
+		
+	if (scc->tty && scc->init)
+	{		
+		save_flags(flags); cli();
+		
+		/* KISS-TNC emulation */
 		
-		scc->rcvq->anext = bp;
+		if (Expired(t_dwait)) dw_slot_timeout(scc)	; else
+		if (Expired(t_slot))  dw_slot_timeout(scc)	; else
+		if (Expired(t_txdel)) txdel_timeout(scc)   	; else
+		if (Expired(t_tail))  tail_timeout(scc)	   	;
+			
+		/* watchdogs */
+		
+		if (Expired(t_mbusy)) busy_timeout(scc);
+		if (Expired(t_maxk))  maxk_idle_timeout(scc);
+		if (Expired(t_idle))  maxk_idle_timeout(scc);
+			
+		restore_flags(flags);
 	}
+	
+	scc->tx_t.expires = jiffies + HZ/TPS;
+	add_timer(&scc->tx_t);
 }
+			
 
 static void
-scc_timer(void)
+scc_rx_timer(unsigned long channel)
 {
 	register struct scc_channel *scc;
-	register int chan;
-	unsigned long flags;
-		
-
-	for (chan = 0; chan < (Nchips * 2); chan++)
+	
+	scc = (struct scc_channel *) channel;
+	
+	if (scc->rx_queue && scc->throttled)
 	{
-		scc = &SCC_Info[chan];
-		
-		if (scc->tty && scc->init)
-		{
-			kiss_encode(scc);
-			
-			save_flags(flags); cli();
-			
-			check_rcv_queue(scc);
-			
-			/* KISS-TNC emulation */
-			
-			if (Expired(t_dwait)) dw_slot_timeout(scc)	; else
-			if (Expired(t_slot))  dw_slot_timeout(scc)	; else
-			if (Expired(t_txdel)) txdel_timeout(scc)   	; else
-			if (Expired(t_tail))  tail_timeout(scc)	   	;
-			
-			/* watchdogs */
-			
-			if (Expired(t_mbusy)) busy_timeout(scc);
-			if (Expired(t_maxk))  maxk_idle_timeout(scc);
-			if (Expired(t_idle))  maxk_idle_timeout(scc);
-			
-			restore_flags(flags);
-		}
+		scc->rx_t.expires = jiffies + HZ/TPS;
+		add_timer(&scc->rx_t);
+		return;
 	}
 	
-	save_flags(flags); cli();
-	
-	timer_table[SCC_TIMER].fn = scc_timer;
-	timer_table[SCC_TIMER].expires = jiffies + HZ/TPS;
-	timer_active |= 1 << SCC_TIMER; 
+	kiss_encode(scc);
 	
-	restore_flags(flags);
+	if (scc->rx_queue && !scc->throttled)
+	{
+
+		printk("z8530drv: warning: %s should be throttled\n", 
+		       kdevname(scc->tty->device));
+			       
+		scc->rx_t.expires = jiffies + HZ/TPS;
+		add_timer(&scc->rx_t);
+	}
 }
-			
 
 static void
 scc_init_timer(struct scc_channel *scc)
@@ -1319,26 +1562,19 @@
 	Stop_Timer(t_mbusy);
 	Stop_Timer(t_maxk);
 	Stop_Timer(t_idle);
-	scc->stat.tx_state = TXS_IDLE;
-	
-	restore_flags(flags);
-}
-
-
-static void
-scc_rx_timer(void)
-{
-	unsigned long flags;
 	
-	kiss_encode(rx_timer_cb.scc);
+	scc->stat.tx_state = TXS_IDLE;
 	
-	save_flags(flags); cli();
+	if (scc->tx_t.next) 
+		del_timer(&scc->tx_t);
 	
-	timer_table[SCC_TIMER].fn = scc_timer;
-	timer_table[SCC_TIMER].expires = jiffies + rx_timer_cb.expires;
-	timer_active |= 1 << SCC_TIMER;
+	scc->tx_t.data = (unsigned long) scc;
+	scc->tx_t.function = scc_tx_timer;
+	scc->tx_t.expires = jiffies + HZ/TPS;
+	add_timer(&scc->tx_t);
 	
-	rx_timer_cb.lock = 0;
+	scc->rx_t.data = (unsigned long) scc;
+	scc->rx_t.function = scc_rx_timer;
 	
 	restore_flags(flags);
 }
@@ -1401,15 +1637,23 @@
 {
 	unsigned char kisscmd;
 	unsigned long flags;
+	struct mbuf *bp;
 
-	if (scc->sndq1->cnt < 2)
+	bp = scc->kiss_decode_bp;
+	bp->rw_ptr = bp->data;
+	
+#ifdef DEBUG_BUFFERS
+	if (bp == NULLBUF)
 	{
-		if (scc->sndq1) 
-			scc_free_chain(scc->sndq1, BT_TRANSMIT);
-		else
-			scc->sndq1 = NULLBUF;
-			
-		scc->sndq2 = NULLBUF;
+		printk("kiss_interpret_frame(): weird --- nothing to do.\n");
+		return;
+	}
+#endif
+	
+	if (bp->cnt < 2)
+	{
+		scc_enqueue_buffer(&scc->tx_buffer_pool, bp);
+		scc->kiss_decode_bp = NULLBUF;
 		return;
 	}
 	
@@ -1417,20 +1661,20 @@
 	
 	if (scc->kiss.not_slip)
 	{
-		kisscmd = scc->sndq1->data[scc->sndq1->in_use++];
-		scc->sndq1->cnt--;
+		kisscmd = *bp->rw_ptr;
+		bp->rw_ptr++;
 	} else {
 		kisscmd = 0;
 	}
 
 	if (kisscmd & 0xa0)
 	{
-		if (scc->sndq1->cnt > 2)
-			scc->sndq1->cnt -= 2;
+		if (bp->cnt > 3) 
+			bp->cnt -= 2;
 		else
 		{
-			scc_free_chain(scc->sndq1, BT_TRANSMIT);
-			scc->sndq2 = NULLBUF;
+			scc_enqueue_buffer(&scc->tx_buffer_pool, bp);
+			scc->kiss_decode_bp = NULLBUF;
 			return;
 		}
 	}
@@ -1441,25 +1685,22 @@
 		
 	if (kisscmd)
 	{
-		kiss_set_param(scc, kisscmd, scc->sndq1->data[scc->sndq1->in_use]);
-		scc->sndq1->cnt=0;
-		scc->sndq1->in_use=0;
-					
-		scc_free_chain(scc->sndq1, BT_TRANSMIT);
-		scc->sndq2 = NULLBUF;
+		kiss_set_param(scc, kisscmd, *bp->rw_ptr);
+		scc_enqueue_buffer(&scc->tx_buffer_pool, bp);
+		scc->kiss_decode_bp = NULLBUF;
 		return;
 	}
+
+	scc_enqueue_buffer(&scc->tx_queue, bp);	/* enqueue frame */
 	
-	scc_enqueue(&scc->sndq,scc->sndq1); /* scc_enqueue packet */
 	scc->stat.txframes++;
 	scc->stat.tx_queued++;
-	scc->sndq2 = NULLBUF;		/* acquire a new buffer next time */
+	scc->kiss_decode_bp = NULLBUF;
 
 	save_flags(flags); cli();
 
 	if(scc->stat.tx_state == TXS_IDLE)
 	{      				/* when transmitter is idle */
-		scc_init_timer(scc);
 		scc->stat.tx_state = TXS_BUSY;
 		scc->t_dwait = (scc->kiss.fulldup? 0:scc->kiss.waittime);
 	}
@@ -1467,21 +1708,21 @@
 	restore_flags(flags);
 }
 
-static void kiss_store_byte(struct scc_channel *scc, unsigned char ch)
+static inline void kiss_store_byte(struct scc_channel *scc, unsigned char ch)
 {
-	if (scc->sndq2 == NULLBUF) return;
+	register struct mbuf *bp = scc->kiss_decode_bp;
 	
-	if(scc->sndq2->cnt == scc->sndq2->size)		/* buffer full? */
+	if (bp != NULLBUF)
 	{
-		if((scc->sndq2 = scc_get_buffer(BT_TRANSMIT)) == NULLBUF)
+		if (bp->cnt > scc->stat.bufsize)
+			printk("kiss_decode(): frame too long\n");
+		else
 		{
-			printk("\nsccdrv: running out of memory\n");
-			return;
+			*bp->rw_ptr = ch;
+			bp->rw_ptr++;
+			bp->cnt++;
 		}
-		scc_append_to_chain(&scc->sndq1,scc->sndq2);         /* add buffer */
 	}
-	
-	scc->sndq2->data[scc->sndq2->cnt++] = ch;
 }
 
 static inline int kiss_decode(struct scc_channel *scc, unsigned char ch)
@@ -1491,9 +1732,10 @@
 		case KISS_IDLE:
 			if (ch == FEND)
 			{
-				if (!(scc->sndq2 = scc->sndq1 = scc_get_buffer(BT_TRANSMIT)))
-					return 0;
-					
+				scc->kiss_decode_bp = scc_get_buffer(scc, BT_TRANSMIT);
+				if (scc->kiss_decode_bp == NULLBUF)
+					return 1;
+
 				scc->stat.tx_kiss_state = KISS_DATA;
 			} else scc->stat.txerrs++;
 			break;
@@ -1503,7 +1745,7 @@
 				scc->stat.tx_kiss_state = KISS_ESCAPE;
 			else if (ch == FEND)
 			{
-				kiss_interpret_frame(scc);	
+				kiss_interpret_frame(scc);
 				scc->stat.tx_kiss_state = KISS_IDLE;
 			}
 			else kiss_store_byte(scc, ch);
@@ -1522,8 +1764,8 @@
 			}
 			else
 			{
-				scc_free_chain(scc->sndq1, BT_TRANSMIT);
-				scc->sndq2 = NULLBUF;
+				scc_enqueue_buffer(&scc->tx_buffer_pool, scc->kiss_decode_bp);
+				scc->kiss_decode_bp = NULLBUF;
 				scc->stat.txerrs++;
 				scc->stat.tx_kiss_state = KISS_IDLE;
 			}
@@ -1536,91 +1778,78 @@
 
 /* ----> Encode received data and write it to the flip-buffer  <---- */
 
-/* receive raw frame from SCC. used for AX.25 */
 static void
 kiss_encode(register struct scc_channel *scc)
 {
-	struct mbuf *bp,*bp2;
+	struct mbuf *bp;
 	struct tty_struct * tty = scc->tty;
-	unsigned long flags; 
 	unsigned char ch;
 
-	if(!scc->rcvq)
-	{
-		scc->stat.rx_kiss_state = KISS_IDLE;
-		return;
-	}
+	bp = scc->kiss_encode_bp;
 	
 	/* worst case: FEND 0 FESC TFEND -> 4 bytes */
 	
-	while(tty->flip.count < TTY_FLIPBUF_SIZE-3)
-	{ 
-		if (scc->rcvq->cnt)
+	while(tty->flip.count < TTY_FLIPBUF_SIZE-4)
+	{
+		if (bp == NULLBUF)
 		{
-			bp = scc->rcvq;
+			bp = scc_dequeue_buffer(&scc->rx_queue);
+			scc->kiss_encode_bp = bp;
 			
-			if (scc->stat.rx_kiss_state == KISS_IDLE)
+			if (bp == NULLBUF)
 			{
-				tty_insert_flip_char(tty, FEND, 0);
-				
-				if (scc->kiss.not_slip)
-					tty_insert_flip_char(tty, 0, 0);
-					
-				scc->stat.rx_kiss_state = KISS_RXFRAME;
-			}
-				
-			switch(ch = bp->data[bp->in_use++])
-			{
-				case FEND:
-					tty_insert_flip_char(tty, FESC, 0);
-					tty_insert_flip_char(tty, TFEND, 0);
-					break;
-				case FESC:
-					tty_insert_flip_char(tty, FESC, 0);
-					tty_insert_flip_char(tty, TFESC, 0);
-					break;
-				default:
-					tty_insert_flip_char(tty, ch, 0);
+				scc->stat.rx_kiss_state = KISS_IDLE;
+				break;
 			}
-			
-			bp->cnt--;
-			
- 		} else {
-			save_flags(flags); cli();
-			
-			while (!scc->rcvq->cnt)
-	 		{				 /* buffer empty? */
-				bp  = scc->rcvq->next;  /* next buffer */
-				bp2 = scc->rcvq->anext; /* next packet */
-				
-				
-				scc_return_buffer(scc->rcvq, BT_RECEIVE);
-				
-				if (!bp)	/* end of frame ? */
-				{
-					scc->rcvq = bp2;
-					
-					if (--scc->stat.rx_queued < 0)
-						scc->stat.rx_queued = 0;
-					
-					if (scc->stat.rx_kiss_state == KISS_RXFRAME)	/* new packet? */
-					{
-						tty_insert_flip_char(tty, FEND, 0); /* send FEND for old frame */
-						scc->stat.rx_kiss_state = KISS_IDLE; /* generate FEND for new frame */
-					}
-					
-					restore_flags(flags);
-					queue_task(&tty->flip.tqueue, &tq_timer);
-					return;
+		}
+		
+
+		if (bp->cnt <= 0)
+		{
+			if (--scc->stat.rx_queued < 0)
+				scc->stat.rx_queued = 0;
 					
-				} else scc->rcvq = bp; /* next buffer */
+			if (scc->stat.rx_kiss_state == KISS_RXFRAME)	/* new packet? */
+			{
+				tty_insert_flip_char(tty, FEND, 0);  /* send FEND for old frame */
+				scc->stat.rx_kiss_state = KISS_IDLE; /* generate FEND for new frame */
 			}
 			
-			restore_flags(flags);
-		}						
+			scc_enqueue_buffer(&scc->rx_buffer_pool, bp);
+			
+			bp = scc->kiss_encode_bp = NULLBUF;
+			continue;
+		}
 		
+
+		if (scc->stat.rx_kiss_state == KISS_IDLE)
+		{
+			tty_insert_flip_char(tty, FEND, 0);
+			
+			if (scc->kiss.not_slip)
+				tty_insert_flip_char(tty, 0, 0);
+					
+			scc->stat.rx_kiss_state = KISS_RXFRAME;
+		}
+				
+		switch(ch = *bp->rw_ptr)
+		{
+			case FEND:
+				tty_insert_flip_char(tty, FESC, 0);
+				tty_insert_flip_char(tty, TFEND, 0);
+				break;
+			case FESC:
+				tty_insert_flip_char(tty, FESC, 0);
+				tty_insert_flip_char(tty, TFESC, 0);
+				break;
+			default:
+				tty_insert_flip_char(tty, ch, 0);
+		}
+			
+		bp->rw_ptr++;
+		bp->cnt--;
 	}
-	
+		
  	queue_task(&tty->flip.tqueue, &tq_timer); /* kick it... */
 }
 
@@ -1634,27 +1863,42 @@
 z8530_init(void)
 {
 	struct scc_channel *scc;
-	int chip;
+	int chip, k;
 	unsigned long flags;
+	char *flag;
+
+
+	printk("Init Z8530 driver: %u channels, IRQ", Nchips*2);
+	
+	flag=" ";
+	for (k = 0; k < 16; k++)
+		if (Ivec[k].used) 
+		{
+			printk("%s%d", flag, k);
+			flag=",";
+		}
+	printk("\n");
+	
 
 	/* reset and pre-init all chips in the system */
 	for (chip = 0; chip < Nchips; chip++)
 	{
+		scc=&SCC_Info[2*chip];
+		if (!scc->ctrl) continue;
+			
+		save_flags(flags); cli();	/* because of 2-step accesses */
+		
 		/* Special SCC cards */
 
-		if(Board & EAGLE)			/* this is an EAGLE card */
-			Outb(Special_Port,0x08);	/* enable interrupt on the board */
+		if(scc->brand & EAGLE)			/* this is an EAGLE card */
+			Outb(scc->special,0x08);	/* enable interrupt on the board */
 			
-		if(Board & (PC100 | PRIMUS))		/* this is a PC100/EAGLE card */
-			Outb(Special_Port,Option);	/* set the MODEM mode (0x22) */
+		if(scc->brand & (PC100 | PRIMUS))	/* this is a PC100/EAGLE card */
+			Outb(scc->special,scc->option);	/* set the MODEM mode (0x22) */
+
 			
 		/* Init SCC */
-		
-		scc=&SCC_Info[2*chip];
-		if (!scc->ctrl) continue;
-		
-		save_flags(flags); cli();
-		
+
 		/* some general init we can do now */
 		
 		Outb(scc->ctrl, 0);
@@ -1666,8 +1910,6 @@
         	restore_flags(flags);
         }
 
-	if (Ivec == 2) Ivec = 9;			/* this f... IBM AT-design! */
-	request_irq(Ivec, scc_isr,   SA_INTERRUPT, "AX.25 SCC");
  
 	Driver_Initialized = 1;
 }
@@ -1685,20 +1927,23 @@
 				     const char *routine)
 {
 #ifdef SCC_PARANOIA_CHECK
-	static const char *badmagic =
-		"Warning: bad magic number for Z8530 SCC struct (%s) in %s\n";
-        static const char *badinfo =
-                "Warning: Z8530 not found for (%s) in %s\n";
+
+static const char *badmagic = 
+	"Warning: bad magic number for Z8530 SCC struct (%s) in %s\n"; 
+static const char *badinfo =  
+	"Warning: Z8530 not found for (%s) in %s\n";
        
 	if (!scc->init) 
 	{
-        	printk(badinfo, kdevname(device), routine);
-                return 1;
-        }
-        if (scc->magic != SCC_MAGIC) {
-        	printk(badmagic, kdevname(device), routine);
-                return 1;
-        }
+       		printk(badinfo, kdevname(device), routine);
+		return 1;
+	}
+	
+	if (scc->magic != SCC_MAGIC)
+	{
+		printk(badmagic, kdevname(device), routine);
+		return 1;
+	}
 #endif
 
 	return 0;
@@ -1712,46 +1957,49 @@
 	struct scc_channel *scc;
 	int chan;
 	
-        chan = MINOR(tty->device) - tty->driver.minor_start;
-        if ((chan < 0) || (chan >= (Nchips * 2)))
-                return -ENODEV;
+	chan = MINOR(tty->device) - tty->driver.minor_start;
+	
+	if (Driver_Initialized)
+	{
+		if ( (chan < 0) || (chan >= (Nchips * 2)) )
+                	return -ENODEV;
+        } else {
+        	tty->driver_data = &SCC_Info[0];
+        	MOD_INC_USE_COUNT;
+        	return 0;
+        }
  
  	scc = &SCC_Info[chan];
  	
 	tty->driver_data = scc;
- 	tty->termios->c_cflag &= ~CBAUD; 
+ 	tty->termios->c_cflag &= ~CBAUD;
  	
-	if (!Driver_Initialized)
-		return 0;
-	
 	if (scc->magic != SCC_MAGIC)
 	{
-		printk("ERROR: scc_open(): bad magic number for device ("
-		       "%s)",
+		printk("ERROR: scc_open(): bad magic number for device (%s)",
 		       kdevname(tty->device));
 		return -ENODEV;
 	}		
 	
+	MOD_INC_USE_COUNT;
+	
 	if(scc->tty != NULL)
 	{
 		scc->tty_opened++;
 		return 0;
 	}
 
- 	if(!scc->init) return 0;
- 	 	 	 
   	scc->tty = tty;
-	init_channel(scc);
+	alloc_buffer_pool(scc);
+	
+ 	if(!scc->init) return 0;
+ 	
+	scc->throttled = 0;
 
 	scc->stat.tx_kiss_state = KISS_IDLE;	/* don't change this... */
 	scc->stat.rx_kiss_state = KISS_IDLE;	/* ...or this */
-	
-	scc_init_timer(scc);
-	
-	timer_table[SCC_TIMER].fn = scc_timer;
-	timer_table[SCC_TIMER].expires = 0;	/* now! */
-	timer_active |= 1 << SCC_TIMER;
-	
+ 
+	init_channel(scc);
 	return 0;
 }
 
@@ -1766,6 +2014,8 @@
 
         if (!scc || (scc->magic != SCC_MAGIC))
                 return;
+                
+        MOD_DEC_USE_COUNT;
 	
 	if(scc->tty_opened)
 	{
@@ -1775,6 +2025,9 @@
 	
 	tty->driver_data = NULLBUF;
 	
+	if (!Driver_Initialized)
+		return;
+	
 	save_flags(flags); cli();
 	
 	Outb(scc->ctrl,0);		/* Make sure pointer is written */
@@ -1783,13 +2036,18 @@
 	
 	scc->tty = NULL;
 	
+	del_timer(&scc->tx_t);
+	del_timer(&scc->rx_t);
+
+	free_buffer_pool(scc);
+	
 	restore_flags(flags);
-	tty->stopped = 0;		
+	
+	scc->throttled = 0;
+	tty->stopped = 0;
 }
 
 
-
-
 /*
  * change scc_speed
  */
@@ -1797,10 +2055,15 @@
 static void
 scc_change_speed(struct scc_channel * scc)
 {
+	long speed;
+	
 	if (scc->tty == NULL)
 		return;
 		
-	scc->modem.speed = baud_table[scc->tty->termios->c_cflag & CBAUD];
+
+	speed = baud_table[scc->tty->termios->c_cflag & CBAUD];
+	
+	if (speed > 0) scc->modem.speed = speed;
 	
 	if (scc->stat.tx_state == 0)	/* only switch baudrate on rx... ;-) */
 		set_speed(scc);
@@ -1833,11 +2096,13 @@
 	unsigned int result;
 	unsigned int value;
 	struct ioctl_command kiss_cmd;
-	int error;
+	struct scc_mem_config memcfg;
+	struct scc_hw_config hwcfg;
+	int error, chan;
 
         if (scc->magic != SCC_MAGIC) 
         {
-		printk("ERROR: scc_ioctl(): bad magic number for device %s", 
+		printk("ERROR: scc_ioctl(): bad magic number for device %s",
 			kdevname(tty->device));
 			
                 return -ENODEV;
@@ -1847,13 +2112,106 @@
 	
 	if (!Driver_Initialized)
 	{
+		if (cmd == TIOCSCCCFG)
+		{
+			int found = 1;
+			
+			if (!suser()) return -EPERM;
+			if (!arg) return -EFAULT;
+			
+			if (Nchips >= MAXSCC) 
+				return -EINVAL;
+			
+			memcpy_fromfs(&hwcfg, (void *) arg, sizeof(hwcfg));
+			
+			if (hwcfg.irq == 2) hwcfg.irq = 9;
+			
+			if (!Ivec[hwcfg.irq].used && hwcfg.irq)
+			{
+				if (request_irq(hwcfg.irq, scc_isr, SA_INTERRUPT, "AX.25 SCC"))
+					printk("z8530drv: Warning --- could not get IRQ %d\n", hwcfg.irq);
+				else
+					Ivec[hwcfg.irq].used = 1;
+			}
+			
+			if (hwcfg.vector_latch) 
+				Vector_Latch = hwcfg.vector_latch;
+				
+			if (hwcfg.clock == 0)
+				hwcfg.clock = DEFAULT_CLOCK;
+
+#ifndef DONT_CHECK
+			save_flags(flags); cli();
+			
+			check_region(scc->ctrl, 1);
+			Outb(hwcfg.ctrl_a, 0);
+			udelay(5);
+			OutReg(hwcfg.ctrl_a,R13,0x55);		/* is this chip really there? */
+			udelay(5);
+			
+			if (InReg(hwcfg.ctrl_a,R13) != 0x55 )
+				found = 0;
+				
+			restore_flags(flags);
+#endif
+
+			if (found)
+			{
+				SCC_Info[2*Nchips  ].ctrl = hwcfg.ctrl_a;
+				SCC_Info[2*Nchips  ].data = hwcfg.data_a;
+				SCC_Info[2*Nchips+1].ctrl = hwcfg.ctrl_b;
+				SCC_Info[2*Nchips+1].data = hwcfg.data_b;
+			
+				SCC_ctrl[2*Nchips  ] = hwcfg.ctrl_a;
+				SCC_ctrl[2*Nchips+1] = hwcfg.ctrl_b;
+			}
+		
+			for (chan = 0; chan < 2; chan++)
+			{
+				SCC_Info[2*Nchips+chan].special = hwcfg.special;
+				SCC_Info[2*Nchips+chan].clock = hwcfg.clock;
+				SCC_Info[2*Nchips+chan].brand = hwcfg.brand;
+				SCC_Info[2*Nchips+chan].option = hwcfg.option;
+				SCC_Info[2*Nchips+chan].enhanced = hwcfg.escc;
+			
+#ifdef DONT_CHECK
+				printk("%s%i: data port = 0x%3.3x  control port = 0x%3.3x\n",
+					scc_driver.name, 2*Nchips+chan, 
+					SCC_Info[2*Nchips+chan].data, 
+					SCC_Info[2*Nchips+chan].ctrl);
+
+#else
+				printk("%s%i: data port = 0x%3.3x  control port = 0x%3.3x -- %s\n",
+					scc_driver.name, 2*Nchips+chan, 
+					chan? hwcfg.data_b : hwcfg.data_a, 
+					chan? hwcfg.ctrl_b : hwcfg.ctrl_a,
+					found? "found" : "missing");
+#endif
+				
+				if (found)
+				{
+					request_region(SCC_Info[2*Nchips+chan].ctrl, 1, "scc ctrl");
+					request_region(SCC_Info[2*Nchips+chan].data, 1, "scc data");
+				}
+			}
+			
+			if (found) Nchips++;
+			
+			return 0;
+		}
+		
 		if (cmd == TIOCSCCINI)
 		{
 			if (!suser())
 				return -EPERM;
+				
+			if (Nchips == 0)
+				return -EINVAL;
 			
-			scc_alloc_buffer_pool();
 			z8530_init();
+			
+			scc->tty=tty;
+			alloc_buffer_pool(scc);
 			return 0;
 		}
 		
@@ -1862,7 +2220,6 @@
 	
 	if (!scc->init)
 	{
-
 		if (cmd == TIOCCHANINI)
 		{
 			if (!arg)
@@ -1979,8 +2336,6 @@
 	case TCSETS:
 	case TCSETSF:		/* should flush first, but... */
 	case TCSETSW:		/* should wait 'till flush, but... */
-		if (!suser())
-			return -EPERM;
 		if (!arg)
 			return -EFAULT;
 		
@@ -1988,6 +2343,23 @@
 		scc_change_speed(scc);
 		return 0;
 		
+	case TIOCCHANMEM:
+		if (!arg)
+			return -EFAULT;
+			
+		memcpy_fromfs(&memcfg, (void *) arg, sizeof(struct scc_mem_config));
+		
+		save_flags(flags); cli();
+		
+		free_buffer_pool(scc);
+		scc->stat.rxbuffers = memcfg.rxbuffers;
+		scc->stat.txbuffers = memcfg.txbuffers;
+		scc->stat.bufsize   = memcfg.bufsize;
+		alloc_buffer_pool(scc);
+		
+		restore_flags(flags);
+		return 0;
+		
 		
 	case TIOCSCCSTAT:
 		error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(struct scc_stat));
@@ -1997,9 +2369,6 @@
 		if (!arg)
 			return -EFAULT;
 			
-		scc->stat.used_buf = scc_count_used_buffers(&scc->stat.rx_alloc, 
-							    &scc->stat.tx_alloc);
-			
 		memcpy_tofs((void *) arg, &scc->stat, sizeof(struct scc_stat));
 		return 0;
 		
@@ -2051,9 +2420,6 @@
 		if (!arg)
 			return -EFAULT;
 
-		if (!suser())
-			return -EPERM;
-			
 		memcpy_fromfs(&kiss_cmd, (void *) arg, sizeof(struct ioctl_command));
 		
 		switch (kiss_cmd.command)
@@ -2092,55 +2458,14 @@
 }
 
 
-/* ----- TERMIOS function ----- */
-
-static void
-scc_set_termios(struct tty_struct * tty, struct termios * old_termios)
-{
-	if (tty->termios->c_cflag == old_termios->c_cflag) 
-		return;
-	scc_change_speed(tty->driver_data);
-}
-
-
-static inline void check_tx_queue(register struct scc_channel *scc)
-{
-	register struct mbuf *bp;
-	
-	if (scc->stat.tx_queued > QUEUE_THRES)
-	{
-		if (scc->sndq1 == NULLBUF)
-		{
-			printk("z8530drv: Warning - scc->stat.tx_queued shows overflow"
-			       " (%d) but queue is empty\n", scc->stat.tx_queued);
-			       
-			scc->stat.tx_queued = 0;	/* correct it */
-			scc->stat.nospace = 54321;	/* draw attention to it */
-			return;
-		}
-			
-		bp = scc->sndq1->anext;	/* don't use the one we currently use */
-		
-		while (bp && (scc->stat.tx_queued > QUEUE_HYST))
-		{
-			bp = scc_free_chain(bp, BT_TRANSMIT);
-			scc->stat.tx_queued--;
-			scc->stat.nospace++;
-		}
-		
-		scc->sndq1->anext = bp;
-	}
-}
-
-
-
 /* ----> tx routine: decode KISS data and scc_enqueue it <---- */
 
 /* send raw frame to SCC. used for AX.25 */
 int scc_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count)
 {
 	struct scc_channel * scc = tty->driver_data;
-	unsigned char tbuf[BUFSIZE], *p;
+	unsigned char *p;
+	unsigned long flags;
 	int cnt, cnt2;
 	
 	if (!tty) return count;
@@ -2150,8 +2475,8 @@
 
 	if (scc->kiss.tx_inhibit) return count;
 	
-	check_tx_queue(scc);
-
+	save_flags(flags); cli();
+	
 	cnt2 = count;
 	
 	while (cnt2)
@@ -2160,23 +2485,40 @@
 		cnt2 -= cnt;
 		
 		if (from_user)
-			memcpy_fromfs(tbuf, buf, cnt);
+		{
+			down(&scc_sem);
+			memcpy_fromfs(scc_wbuf, buf, cnt);
+			up(&scc_sem);
+		}
 		else
-			memcpy(tbuf, buf, cnt);
+			memcpy(scc_wbuf, buf, cnt);
 		
-		buf += cnt;
+		/* Strange thing. The timeout of the slip driver is */
+		/* very small, thus we'll wake him up now. 	    */
+
+		if (cnt2 == 0)
+		{
+			wake_up_interruptible(&tty->write_wait);
+		
+			if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+			    tty->ldisc.write_wakeup)
+				(tty->ldisc.write_wakeup)(tty);
+		} else 
+			buf += cnt;
 			
-		p=tbuf;
+		p=scc_wbuf;
 		
 		while(cnt--) 
 		  if (kiss_decode(scc, *p++))
 		  {
 		  	scc->stat.nospace++;
+		  	restore_flags(flags);
 		  	return 0;
-		  }
-		  	
+		  }		  	
 	} /* while cnt2 */
-	
+
+	restore_flags(flags);
+		
 	return count;
 }
 				
@@ -2204,7 +2546,6 @@
 	return;	/* no flush needed */
 }
 
-/* the kernel does NOT use this routine yet... */
 
 static int scc_write_room(struct tty_struct *tty)
 {
@@ -2213,12 +2554,6 @@
 	if (scc_paranoia_check(scc, tty->device, "scc_write_room"))
 		return 0;
 	
-	if (scc->stat.tx_alloc >= QUEUE_THRES)
-	{
-		printk("scc_write_room(): buffer full (ignore)\n");
-		return 0;
-	}
-		
 	return BUFSIZE;
 }
 
@@ -2226,8 +2561,8 @@
 {
 	struct scc_channel *scc = tty->driver_data;
 	
-	if (scc && scc->sndq2)
-		return scc->sndq2->cnt;
+	if (scc && scc->kiss_decode_bp)
+		return scc->kiss_decode_bp->cnt;
 	else
 		return 0;
 }
@@ -2235,12 +2570,9 @@
 static void scc_flush_buffer(struct tty_struct *tty)
 {
 	struct scc_channel *scc = tty->driver_data;
-	
+
 	if (scc_paranoia_check(scc, tty->device, "scc_flush_buffer"))
 		return;
-		
-	scc->stat.tx_kiss_state = KISS_IDLE;
-	
 	wake_up_interruptible(&tty->write_wait);
 	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
 	    tty->ldisc.write_wakeup)
@@ -2253,9 +2585,15 @@
 	
 	if (scc_paranoia_check(scc, tty->device, "scc_throttle"))
 		return;
-		
-		
-	/* dummy */
+
+#ifdef DEBUG		
+	printk("scc: scc_throttle() called for device %d\n", MINOR(tty->device));
+#endif
+	scc->throttled = 1;
+	
+	del_timer(&(scc->rx_t));
+	scc->rx_t.expires = jiffies + HZ/TPS;
+	add_timer(&scc->rx_t);
 }
 
 static void scc_unthrottle(struct tty_struct *tty)
@@ -2265,9 +2603,16 @@
 	if (scc_paranoia_check(scc, tty->device, "scc_unthrottle"))
 		return;
 		
-	/* dummy */
+#ifdef DEBUG
+	printk("scc: scc_unthrottle() called for device %d\n", MINOR(tty->device));
+#endif		
+	scc->throttled = 0;
+	del_timer(&(scc->rx_t));
+	scc_tx_timer(scc->rx_t.data);
 }
 
+/* experimental, the easiest way to stop output is a fake scc_throttle */
+
 static void scc_start(struct tty_struct *tty)
 {
 	struct scc_channel *scc = tty->driver_data;
@@ -2275,9 +2620,8 @@
 	if (scc_paranoia_check(scc, tty->device, "scc_start"))
 		return;
 		
-	/* dummy */
+	scc_unthrottle(tty);
 }
-	                                            	
 
 static void scc_stop(struct tty_struct *tty)
 {
@@ -2286,7 +2630,32 @@
 	if (scc_paranoia_check(scc, tty->device, "scc_stop"))
 		return;
 		
-	/* dummy */
+	scc_throttle(tty);
+}
+
+static void
+scc_set_termios(struct tty_struct * tty, struct termios * old_termios)
+{
+	struct scc_channel *scc = tty->driver_data;
+	
+	if (scc_paranoia_check(scc, tty->device, "scc_set_termios"))
+		return;
+		
+	if (old_termios && (tty->termios->c_cflag == old_termios->c_cflag)) 
+		return;
+		
+	scc_change_speed(scc);
+}
+
+static void
+scc_set_ldisc(struct tty_struct * tty)
+{
+	struct scc_channel *scc = tty->driver_data;
+
+	if (scc_paranoia_check(scc, tty->device, "scc_set_ldisc"))
+		return;
+		
+	scc_change_speed(scc);
 }
 
 
@@ -2294,25 +2663,24 @@
 /* * 			Init SCC driver 			      * */
 /* ******************************************************************** */
 
-int scc_init (void)
+int  scc_init (void)
 {
-	int chip, chan;
-	register io_port ctrl;
-	long flags;
-	
+	int chip, chan, k;
 	
+	memset(&scc_std_termios, 0, sizeof(struct termios));
         memset(&scc_driver, 0, sizeof(struct tty_driver));
         scc_driver.magic = TTY_DRIVER_MAGIC;
-        scc_driver.name = "sc";
-        scc_driver.major = Z8530_MAJOR;		
+        scc_driver.name = "scc";
+        scc_driver.major = Z8530_MAJOR;
         scc_driver.minor_start = 0;
-        scc_driver.num = Nchips*2;
+        scc_driver.num = MAXSCC*2;
         scc_driver.type = TTY_DRIVER_TYPE_SERIAL;
-        scc_driver.subtype = 0;			/* not needed */
-        scc_driver.init_termios = tty_std_termios;
-        scc_driver.init_termios.c_cflag = B9600	| CS8 | CREAD | HUPCL | CLOCAL;
+        scc_driver.subtype = 1;			/* not needed */
+        scc_driver.init_termios = scc_std_termios;
+        scc_driver.init_termios.c_cflag = B9600  | CREAD | CS8 | HUPCL | CLOCAL;
+        scc_driver.init_termios.c_iflag = IGNBRK | IGNPAR;
         scc_driver.flags = TTY_DRIVER_REAL_RAW;
-        scc_driver.refcount = &scc_refcount;	/* not needed yet */
+        scc_driver.refcount = &scc_refcount;
         scc_driver.table = scc_table;
         scc_driver.termios = (struct termios **) scc_termios;
         scc_driver.termios_locked = (struct termios **) scc_termios_locked;
@@ -2333,86 +2701,91 @@
         
         scc_driver.ioctl = scc_ioctl;
         scc_driver.set_termios = scc_set_termios;
+        scc_driver.set_ldisc = scc_set_ldisc;
+
+	printk(BANNER);
         
         if (tty_register_driver(&scc_driver))
-           panic("Couldn't register Z8530 SCC driver\n");
-                                
-	printk (BANNER);
+        {
+		printk("Failed to register Z8530 SCC driver\n");
+		return -EIO;
+	}
 	
-	if (Nchips > MAXSCC) Nchips = MAXSCC;	/* to avoid the "DAU" (duemmster anzunehmender User) */
-
-	/* reset and pre-init all chips in the system */
+	/* pre-init channel information */
 	
-	for (chip = 0; chip < Nchips; chip++)
+	for (chip = 0; chip < MAXSCC; chip++)
 	{
 		memset((char *) &SCC_Info[2*chip  ], 0, sizeof(struct scc_channel));
 		memset((char *) &SCC_Info[2*chip+1], 0, sizeof(struct scc_channel));
 		
-		ctrl = SCC_ctrl[chip * 2];
-		if (!ctrl) continue;
+		for (chan = 0; chan < 2; chan++)
+		{
+			SCC_Info[2*chip+chan].magic    = SCC_MAGIC;
+			SCC_Info[2*chip+chan].stat.rxbuffers = RXBUFFERS;
+			SCC_Info[2*chip+chan].stat.txbuffers = TXBUFFERS;
+			SCC_Info[2*chip+chan].stat.bufsize   = BUFSIZE;
+		}
+	}
+	
+	for (k = 0; k < 16; k++) Ivec[k].used = 0;
 
-		save_flags(flags); cli();	/* because of 2-step accesses */
-		
+	return 0;
+}
 
-/* Hmm... this may fail on fast systems with cards who don't delay the INTACK */
-/* If you are sure you specified the right port addresses and the driver doesn't */
-/* recognize the chips, define DONT_CHECK in scc_config.h */
+/* ******************************************************************** */
+/* *			    Module support 			      * */
+/* ******************************************************************** */
 
-#ifndef DONT_CHECK
-		check_region(ctrl, 1);
 
-		Outb(ctrl, 0);
-		OutReg(ctrl,R13,0x55);		/* is this chip realy there? */
+#ifdef MODULE
+int init_module(void)
+{
+	int result = 0;
+	
+	result = scc_init();
+	
+	if (result == 0)
+		printk("Copyright 1993,1995 Joerg Reuter DL1BKE (jreuter@lykos.tng.oche.de)\n");
 		
-		if (InReg(ctrl,R13) != 0x55 )
+	return result;
+}
+
+void cleanup_module(void)
+{
+	long flags;
+	io_port ctrl;
+	int k, errno;
+	struct scc_channel *scc;
+	
+	save_flags(flags); cli();
+	if ( (errno = tty_unregister_driver(&scc_driver)) )
+	{
+		printk("Failed to unregister Z8530 SCC driver (%d)", -errno);
+		restore_flags(flags);
+		return;
+	}
+	
+	for (k = 0; k < Nchips; k++)
+		if ( (ctrl = SCC_ctrl[k*2]) )
 		{
-			restore_flags(flags);
-			continue;
+			Outb(ctrl, 0);
+			OutReg(ctrl,R9,FHWRES);	/* force hardware reset */
+			udelay(50);
 		}
-#endif
 		
-		SCC_Info[2*chip  ].magic    = SCC_MAGIC;
-		SCC_Info[2*chip  ].ctrl     = SCC_ctrl[2*chip];
-		SCC_Info[2*chip  ].data     = SCC_data[2*chip];
-		SCC_Info[2*chip  ].enhanced = SCC_Enhanced[chip];
-			
-		SCC_Info[2*chip+1].magic    = SCC_MAGIC;
-		SCC_Info[2*chip+1].ctrl     = SCC_ctrl[2*chip+1];
-		SCC_Info[2*chip+1].data     = SCC_data[2*chip+1];
-		SCC_Info[2*chip+1].enhanced = SCC_Enhanced[chip];
-
- 
-        	restore_flags(flags);
-        }
-
-#ifdef DO_FAST_RX
-        rx_timer_cb.lock = 0;
-#else
-	rx_timer_cb.lock = 1;
-#endif
-        
-#ifdef VERBOSE_BOOTMSG
-	printk("Init Z8530 driver: %u channels, using irq %u\n",Nchips*2,Ivec);
-	
-
-	for (chan = 0; chan < Nchips * 2 ; chan++)
+	for (k = 0; k < Nchips*2; k++)
 	{
-		printk("/dev/%s%i: data port = 0x%3.3x  control port = 0x%3.3x -- %s\n",
-			scc_driver.name, chan, SCC_data[chan], SCC_ctrl[chan],
-			SCC_Info[chan].ctrl? "found"   : "missing");
-			
-		if (SCC_Info[chan].ctrl == 0) 
+		scc = &SCC_Info[k];
+		if (scc)
 		{
-			SCC_ctrl[chan] = 0;
-		} else {
-			request_region(SCC_ctrl[chan], 1, "scc ctrl");
-			request_region(SCC_data[chan], 1, "scc data");
+			release_region(scc->ctrl, 1);
+			release_region(scc->data, 1);
 		}
 	}
-#else
-	printk("Init Z8530 driver: %u channels\n",Nchips*2);
-#endif
-
 	
-	return 0;
+	for (k=0; k < 16 ; k++)
+		if (Ivec[k].used) free_irq(k);
+		
+	restore_flags(flags);
 }
+#endif

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this