summaryrefslogblamecommitdiffstats
path: root/drivers/staging/dgnc/dgnc_tty.c
blob: 4135cb0ed9f5b4e852b7c166cd138b95388d1b0d (plain) (tree)


















                                                                       
                                                                 





                                                                   

                                                                  


                                                                         
  

                                                            
  

                                                                         


                         







                                                                               
                                                











                                                                     
                                          
                                 
                                                                



                     
                                                         








































                                                                                                 
                                                                                       

                                                                                     
                                                       
                                                                      
                                                            








                                                                                                         

                                                                                           






























                                                                                       
  
                                       
  
                                                                          
 




















                                                                         
                               

         
                 







                                         
                                             




                                           

                                                                 

                                                   
 






                                                                                

                                                        



                                                                                                             
          
                                                      
                                      
           
                                                                                                      
                                    
                               
 
                                           
                                                                                                            
                                       
                               
 










                                                                      
                                  





                                                                           
                                                                             









                                                                              
                                                       






                                                                                                            
                                                              

                                                     
                                                                                                    
                                   
                               
                                          
                                                                                                          
                                      
                               
 







                                                              
                                                            

                                                                                    
                                  









                                                                              
                  








                                                                      
                                         

              
                            


                             
                              





















                                                                          
                                                                                          






























                                                                                                 
                                                                            
                    
                                                                            





























                                                                           
                 




















                                                                    
             
   
                                            































                                                                                 
         























































                                                                                     
                                                      











                                                                                        
                                   
                                       
 





























































                                                                                 
 









                                                                  
                            























                                                                         
  
                                                     
  


                                                                           
                              









                              








                                                   
                                                 



                                           

                                                              















                                                      
                                                   
           
                                                                                    
                                                                                   

























                                                                                                 
                                    


                                             
                                             



























                                                                          
                 













                                                                       
          







                                                                          

                                                     
 







                                                                     
 

                              

                  
                                                                  


                                                                    
                   










                                                                                                                
                        
                                                                                  

                 
                                                                                   
 



                                          
         
 



                                             
 

                                                               







                                            
                                                                         
                                                              
          


                                                                          
                              
 

                             
 














                                                                                                  
                                                   
                                 
 
                                    
                                 












































                                                                               
                                





















                                                                    
                                                   









                                                                         
            














                                                                     
                         


                      



                                        









                                                                               
                                                 
 
                          





                                                                            
                                                                              








                                                                          






                                                                                        
 
                                                 
                                                        
                            
                                                       
                 
         
 
                                      







                                                            

                                                  



















                                                                                   
                                                                







































































                                                                                                            

                                             
                                   






                                                     






                                                                                         


































                                                                                               






                                                                                         





















                                                                                               
  
                                        
  







                                                                          
                                     











                                       
                        
                              


                                                                          
                 
                              







                                                                  
               
                          
























                                                                         
                                     

                                                             
                



































































                                                                                                   
                                                                
                           
                                                                
                           
                                                                



























                                                                           





                                                          




















                                                                
          






                                                 
               

                                                                               








                                                                        
                  


 
  




                                                                                                
 






                                                                                                 
                              


                              
                                                
                              





























                                                                                 
                                                                    










                                                                                  
                                                       
                                      














                                                                                         
                        





































                                                                                    
                                      

                                                                                            
                    

                                                                            

















                                                                                       
                              



                                                                                    
                 






                                                                      
   




























                                                                                         
                              



















                                                   
                           


















                                                                                   
         












                                                                               

                       










                                                                        
                                       







                                                                                   
                                                                              













                                                                   
                       
                                                                            


















                                                                            
                                                        
















                                                                                        
                


                                                                   
                                                                              














                                                               
                                                   





















                                                                    
                         


                                                
                         


                                                   
                         

















                                                           
                                                                                   

                                                      
                     


 
  





                                                              
   





                                                                        
                                       


                                                
                                       


                                                   
                                       





                                                          
                                       
 
                                                                          






                                                                                  
                                                                                  
                                                             
                                                          

                                                                                                    
                        
                                                   
                                      




                                                                  
                               






                                      
   










                                                      
                         


                                                
                         


                                                   
                         






                                           

                              





                                         
                                                    





                                                      
                







                                                       
 

                                                                                     
                   






                                  
  

































                                                                     
                         


                                                
                         


                                                   
                         

                   
                         

















                                                                      

                                   





















                                                                              
                         

























                                                                                    
           

                                                     
                         













                                                                              
                                                          
                                      








                                                                                             
                                       




























































                                                                                         
                                                                         


















                                                                                   
                     





                              
 
                                                    



















































                                                   
 
                                                    
                                                     
 
                              
























                                                   
                            
                                              
 
                            
                                              
 
                              
                                                 
 
                              
                                                 






                                                    
                 









                                                                
                              








































                                                                    
                 










                                                                         
                              
























                                                                    
 








                                                               
                              




























                                                    
 















                                                      
                              























                                                
                      














                                                                                 
                              



                                    
                              



                                                    
                  









                                                                                                        
                              


























                                                     
                           


                          
                                    
                                                      
 
                                    
                                                      



                      
                                    
                                                         
 
                                    
                                                         


                      
                      
 
                                    
                                                      
                    
                                                         
 
                                    
                                                      
                    
                                                         



                      
                               









                                                    
                 



  
                      













                                                                                   
                               

                                            
                               


                                                
                               


                                                   
                               







                                                          
                               
 
                 



  
                      







                                                                                    
                              







                                            
                               


                                                
                               


                                                   
                               


                                                 
                               
 
                                                                    
                                                                 
                               



















                                                                                                   
                                                          
 
                                        

                                            
                                            






















                                                                    
                 







                                                                                      
                              




















                                                   





                                                  



















                                                     
 


                                                   
























                                                       
 


                                                   














                                                    
                              









                                                
 


                                                   


















                                                 
                              









                                                
 


                                                   
















                                                 
  













                                                                         
                              









                                                
 


                                                   



















                                                     
  













                                                         
 


                                                   

































                                                                                 
 




                                  
                                                                   

                                  
                              






                                                
                               


                                                
                               


                                                   
                               


                                                 
                               
 
                                                                              






                                                                 
                            







                                                                      
                                                                    







                                                                          
                       
                                  




                                                                            
                                      



                                                   
                                                                      




                                                               
                                                                                       

                                                                         
                         


                     
                                                  





                                                                          
                       
                                  



                                                                            
                                      







                                                       
                                                                                       

                                                                         
                         



                                                     
                       
                                  



                                                                            
                                      







                                                       
                                                                                       

                                                                         
                         










                                                                                   
                          





                                                                 
                                  

                                                   
                                                                                               


                                                     
                         
 

                                                     
                                                     




                                                     
                                                           



                                                                            

                    







                                                                    
                   


                                                             
                                  


























                                                                                         
                                                            
                                                     
                                    
























                                                                                
                                      

                 
                                                                                       


                                                                         
                                    






                                                                            
                                      


                                                      
                                    



                                                     
                                    



                                                     
                                                    










                                                                                    
                                              

                                                           
                        





                                                     
                                                    
 






                                                                                    
                                          

                                                           
                                                                            






                                                               
                                 
                 



                                                                                
                          


                                
                             

                                                                            
                                                            
                       
                                  



                                                     
                         
         













                                                                    
                                  


                                                              
                         





















                                                                               
                                                          
                                       
 
                         






                                                         
           






                                                              
                                                                                
                                                    
 





                                                                                    
                          






                                                              
           










                                                     
                                                            
                                       



























                                                                               
                                      
                                          



                                                                             
                          
                                       
                    
                                       


                                                     
                                                          
                                       
 
                         




                                                             
                                                                         

                                                         
                                    



                                             
                                                                 
                                                 
 
                 
 
/*
 * Copyright 2003 Digi International (www.digi.com)
 *	Scott H Kilau <Scott_Kilau at digi dot com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 *	NOTE TO LINUX KERNEL HACKERS:  DO NOT REFORMAT THIS CODE!
 *
 *	This is shared code between Digi's CVS archive and the
 *	Linux Kernel sources.
 *	Changing the source just for reformatting needlessly breaks
 *	our CVS diff history.
 *
 *	Send any bug fixes/changes to:  Eng.Linux at digi dot com.
 *	Thank you.
 */

/************************************************************************
 *
 * This file implements the tty driver functionality for the
 * Neo and ClassicBoard PCI based product lines.
 *
 ************************************************************************
 *
 */

#include <linux/kernel.h>
#include <linux/sched.h>	/* For jiffies, task states */
#include <linux/interrupt.h>	/* For tasklet and interrupt structs/defines */
#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_reg.h>
#include <linux/slab.h>
#include <linux/delay.h>	/* For udelay */
#include <asm/uaccess.h>	/* For copy_from_user/copy_to_user */
#include <linux/pci.h>

#include "dgnc_driver.h"
#include "dgnc_tty.h"
#include "dgnc_types.h"
#include "dgnc_trace.h"
#include "dgnc_neo.h"
#include "dgnc_cls.h"
#include "dpacompat.h"
#include "dgnc_sysfs.h"

#define init_MUTEX(sem)	 sema_init(sem, 1)
#define DECLARE_MUTEX(name)     \
	struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

/*
 * internal variables
 */
static struct dgnc_board	*dgnc_BoardsByMajor[256];
static uchar		*dgnc_TmpWriteBuf = NULL;
static DECLARE_MUTEX(dgnc_TmpWriteSem);

/*
 * Default transparent print information.
 */
static struct digi_t dgnc_digi_init = {
	.digi_flags =	DIGI_COOK,	/* Flags			*/
	.digi_maxcps =	100,		/* Max CPS			*/
	.digi_maxchar =	50,		/* Max chars in print queue	*/
	.digi_bufsize =	100,		/* Printer buffer size		*/
	.digi_onlen =	4,		/* size of printer on string	*/
	.digi_offlen =	4,		/* size of printer off string	*/
	.digi_onstr =	"\033[5i",	/* ANSI printer on string ]	*/
	.digi_offstr =	"\033[4i",	/* ANSI printer off string ]	*/
	.digi_term =	"ansi"		/* default terminal type	*/
};


/*
 * Define a local default termios struct. All ports will be created
 * with this termios initially.
 *
 * This defines a raw port at 9600 baud, 8 data bits, no parity,
 * 1 stop bit.
 */
static struct ktermios DgncDefaultTermios =
{
	.c_iflag =	(DEFAULT_IFLAGS),	/* iflags */
	.c_oflag =	(DEFAULT_OFLAGS),	/* oflags */
	.c_cflag =	(DEFAULT_CFLAGS),	/* cflags */
	.c_lflag =	(DEFAULT_LFLAGS),	/* lflags */
	.c_cc =		INIT_C_CC,
	.c_line =	0,
};


/* Our function prototypes */
static int dgnc_tty_open(struct tty_struct *tty, struct file *file);
static void dgnc_tty_close(struct tty_struct *tty, struct file *file);
static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch);
static int dgnc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg);
static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo);
static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info);
static int dgnc_tty_write_room(struct tty_struct *tty);
static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c);
static int dgnc_tty_chars_in_buffer(struct tty_struct *tty);
static void dgnc_tty_start(struct tty_struct *tty);
static void dgnc_tty_stop(struct tty_struct *tty);
static void dgnc_tty_throttle(struct tty_struct *tty);
static void dgnc_tty_unthrottle(struct tty_struct *tty);
static void dgnc_tty_flush_chars(struct tty_struct *tty);
static void dgnc_tty_flush_buffer(struct tty_struct *tty);
static void dgnc_tty_hangup(struct tty_struct *tty);
static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value);
static int dgnc_get_modem_info(struct channel_t *ch, unsigned int __user *value);
static int dgnc_tty_tiocmget(struct tty_struct *tty);
static int dgnc_tty_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear);
static int dgnc_tty_send_break(struct tty_struct *tty, int msec);
static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout);
static int dgnc_tty_write(struct tty_struct *tty, const unsigned char *buf, int count);
static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios);
static void dgnc_tty_send_xchar(struct tty_struct *tty, char ch);


static const struct tty_operations dgnc_tty_ops = {
	.open = dgnc_tty_open,
	.close = dgnc_tty_close,
	.write = dgnc_tty_write,
	.write_room = dgnc_tty_write_room,
	.flush_buffer = dgnc_tty_flush_buffer,
	.chars_in_buffer = dgnc_tty_chars_in_buffer,
	.flush_chars = dgnc_tty_flush_chars,
	.ioctl = dgnc_tty_ioctl,
	.set_termios = dgnc_tty_set_termios,
	.stop = dgnc_tty_stop,
	.start = dgnc_tty_start,
	.throttle = dgnc_tty_throttle,
	.unthrottle = dgnc_tty_unthrottle,
	.hangup = dgnc_tty_hangup,
	.put_char = dgnc_tty_put_char,
	.tiocmget = dgnc_tty_tiocmget,
	.tiocmset = dgnc_tty_tiocmset,
	.break_ctl = dgnc_tty_send_break,
	.wait_until_sent = dgnc_tty_wait_until_sent,
	.send_xchar = dgnc_tty_send_xchar
};

/************************************************************************
 *
 * TTY Initialization/Cleanup Functions
 *
 ************************************************************************/

/*
 * dgnc_tty_preinit()
 *
 * Initialize any global tty related data before we download any boards.
 */
int dgnc_tty_preinit(void)
{
	/*
	 * Allocate a buffer for doing the copy from user space to
	 * kernel space in dgnc_write().  We only use one buffer and
	 * control access to it with a semaphore.  If we are paging, we
	 * are already in trouble so one buffer won't hurt much anyway.
	 *
	 * We are okay to sleep in the malloc, as this routine
	 * is only called during module load, (not in interrupt context),
	 * and with no locks held.
	 */
	dgnc_TmpWriteBuf = kmalloc(WRITEBUFLEN, GFP_KERNEL);

	if (!dgnc_TmpWriteBuf) {
		DPR_INIT(("unable to allocate tmp write buf"));
		return -ENOMEM;
	}

	return 0;
}


/*
 * dgnc_tty_register()
 *
 * Init the tty subsystem for this board.
 */
int dgnc_tty_register(struct dgnc_board *brd)
{
	int rc = 0;

	DPR_INIT(("tty_register start\n"));

	memset(&brd->SerialDriver, 0, sizeof(brd->SerialDriver));
	memset(&brd->PrintDriver, 0, sizeof(brd->PrintDriver));

	brd->SerialDriver.magic = TTY_DRIVER_MAGIC;

	snprintf(brd->SerialName, MAXTTYNAMELEN, "tty_dgnc_%d_", brd->boardnum);

	brd->SerialDriver.name = brd->SerialName;
	brd->SerialDriver.name_base = 0;
	brd->SerialDriver.major = 0;
	brd->SerialDriver.minor_start = 0;
	brd->SerialDriver.num = brd->maxports;
	brd->SerialDriver.type = TTY_DRIVER_TYPE_SERIAL;
	brd->SerialDriver.subtype = SERIAL_TYPE_NORMAL;
	brd->SerialDriver.init_termios = DgncDefaultTermios;
	brd->SerialDriver.driver_name = DRVSTR;
	brd->SerialDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK);

	/*
	 * The kernel wants space to store pointers to
	 * tty_struct's and termios's.
	 */
	brd->SerialDriver.ttys = kzalloc(brd->maxports * sizeof(*brd->SerialDriver.ttys), GFP_KERNEL);
	if (!brd->SerialDriver.ttys)
		return -ENOMEM;

	kref_init(&brd->SerialDriver.kref);
	brd->SerialDriver.termios = kzalloc(brd->maxports * sizeof(*brd->SerialDriver.termios), GFP_KERNEL);
	if (!brd->SerialDriver.termios)
		return -ENOMEM;

	/*
	 * Entry points for driver.  Called by the kernel from
	 * tty_io.c and n_tty.c.
	 */
	tty_set_operations(&brd->SerialDriver, &dgnc_tty_ops);

	if (!brd->dgnc_Major_Serial_Registered) {
		/* Register tty devices */
		rc = tty_register_driver(&brd->SerialDriver);
		if (rc < 0) {
			APR(("Can't register tty device (%d)\n", rc));
			return rc;
		}
		brd->dgnc_Major_Serial_Registered = TRUE;
	}

	/*
	 * If we're doing transparent print, we have to do all of the above
	 * again, separately so we don't get the LD confused about what major
	 * we are when we get into the dgnc_tty_open() routine.
	 */
	brd->PrintDriver.magic = TTY_DRIVER_MAGIC;
	snprintf(brd->PrintName, MAXTTYNAMELEN, "pr_dgnc_%d_", brd->boardnum);

	brd->PrintDriver.name = brd->PrintName;
	brd->PrintDriver.name_base = 0;
	brd->PrintDriver.major = brd->SerialDriver.major;
	brd->PrintDriver.minor_start = 0x80;
	brd->PrintDriver.num = brd->maxports;
	brd->PrintDriver.type = TTY_DRIVER_TYPE_SERIAL;
	brd->PrintDriver.subtype = SERIAL_TYPE_NORMAL;
	brd->PrintDriver.init_termios = DgncDefaultTermios;
	brd->PrintDriver.driver_name = DRVSTR;
	brd->PrintDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK);

	/*
	 * The kernel wants space to store pointers to
	 * tty_struct's and termios's.  Must be separated from
	 * the Serial Driver so we don't get confused
	 */
	brd->PrintDriver.ttys = kzalloc(brd->maxports * sizeof(*brd->PrintDriver.ttys), GFP_KERNEL);
	if (!brd->PrintDriver.ttys)
		return -ENOMEM;
	kref_init(&brd->PrintDriver.kref);
	brd->PrintDriver.termios = kzalloc(brd->maxports * sizeof(*brd->PrintDriver.termios), GFP_KERNEL);
	if (!brd->PrintDriver.termios)
		return -ENOMEM;

	/*
	 * Entry points for driver.  Called by the kernel from
	 * tty_io.c and n_tty.c.
	 */
	tty_set_operations(&brd->PrintDriver, &dgnc_tty_ops);

	if (!brd->dgnc_Major_TransparentPrint_Registered) {
		/* Register Transparent Print devices */
		rc = tty_register_driver(&brd->PrintDriver);
		if (rc < 0) {
			APR(("Can't register Transparent Print device (%d)\n", rc));
			return rc;
		}
		brd->dgnc_Major_TransparentPrint_Registered = TRUE;
	}

	dgnc_BoardsByMajor[brd->SerialDriver.major] = brd;
	brd->dgnc_Serial_Major = brd->SerialDriver.major;
	brd->dgnc_TransparentPrint_Major = brd->PrintDriver.major;

	DPR_INIT(("DGNC REGISTER TTY: MAJOR: %d\n", brd->SerialDriver.major));

	return rc;
}


/*
 * dgnc_tty_init()
 *
 * Init the tty subsystem.  Called once per board after board has been
 * downloaded and init'ed.
 */
int dgnc_tty_init(struct dgnc_board *brd)
{
	int i;
	void __iomem *vaddr;
	struct channel_t *ch;

	if (!brd)
		return -ENXIO;

	DPR_INIT(("dgnc_tty_init start\n"));

	/*
	 * Initialize board structure elements.
	 */

	vaddr = brd->re_map_membase;

	brd->nasync = brd->maxports;

	/*
	 * Allocate channel memory that might not have been allocated
	 * when the driver was first loaded.
	 */
	for (i = 0; i < brd->nasync; i++) {
		if (!brd->channels[i]) {

			/*
			 * Okay to malloc with GFP_KERNEL, we are not at
			 * interrupt context, and there are no locks held.
			 */
			brd->channels[i] = kzalloc(sizeof(*brd->channels[i]), GFP_KERNEL);
			if (!brd->channels[i]) {
				DPR_CORE(("%s:%d Unable to allocate memory for channel struct\n",
				    __FILE__, __LINE__));
			}
		}
	}

	ch = brd->channels[0];
	vaddr = brd->re_map_membase;

	/* Set up channel variables */
	for (i = 0; i < brd->nasync; i++, ch = brd->channels[i]) {

		if (!brd->channels[i])
			continue;

		DGNC_SPINLOCK_INIT(ch->ch_lock);

		/* Store all our magic numbers */
		ch->magic = DGNC_CHANNEL_MAGIC;
		ch->ch_tun.magic = DGNC_UNIT_MAGIC;
		ch->ch_tun.un_ch = ch;
		ch->ch_tun.un_type = DGNC_SERIAL;
		ch->ch_tun.un_dev = i;

		ch->ch_pun.magic = DGNC_UNIT_MAGIC;
		ch->ch_pun.un_ch = ch;
		ch->ch_pun.un_type = DGNC_PRINT;
		ch->ch_pun.un_dev = i + 128;

		if (brd->bd_uart_offset == 0x200)
			ch->ch_neo_uart = vaddr + (brd->bd_uart_offset * i);
		else
			ch->ch_cls_uart = vaddr + (brd->bd_uart_offset * i);

		ch->ch_bd = brd;
		ch->ch_portnum = i;
		ch->ch_digi = dgnc_digi_init;

		/* .25 second delay */
		ch->ch_close_delay = 250;

		init_waitqueue_head(&ch->ch_flags_wait);
		init_waitqueue_head(&ch->ch_tun.un_flags_wait);
		init_waitqueue_head(&ch->ch_pun.un_flags_wait);
		init_waitqueue_head(&ch->ch_sniff_wait);

		{
			struct device *classp;
			classp = tty_register_device(&brd->SerialDriver, i,
				&(ch->ch_bd->pdev->dev));
			ch->ch_tun.un_sysfs = classp;
			dgnc_create_tty_sysfs(&ch->ch_tun, classp);

			classp = tty_register_device(&brd->PrintDriver, i,
				&(ch->ch_bd->pdev->dev));
			ch->ch_pun.un_sysfs = classp;
			dgnc_create_tty_sysfs(&ch->ch_pun, classp);
		}

	}

	DPR_INIT(("dgnc_tty_init finish\n"));

	return 0;
}


/*
 * dgnc_tty_post_uninit()
 *
 * UnInitialize any global tty related data.
 */
void dgnc_tty_post_uninit(void)
{
	if (dgnc_TmpWriteBuf) {
		kfree(dgnc_TmpWriteBuf);
		dgnc_TmpWriteBuf = NULL;
	}
}


/*
 * dgnc_tty_uninit()
 *
 * Uninitialize the TTY portion of this driver.  Free all memory and
 * resources.
 */
void dgnc_tty_uninit(struct dgnc_board *brd)
{
	int i = 0;

	if (brd->dgnc_Major_Serial_Registered) {
		dgnc_BoardsByMajor[brd->SerialDriver.major] = NULL;
		brd->dgnc_Serial_Major = 0;
		for (i = 0; i < brd->nasync; i++) {
			dgnc_remove_tty_sysfs(brd->channels[i]->ch_tun.un_sysfs);
			tty_unregister_device(&brd->SerialDriver, i);
		}
		tty_unregister_driver(&brd->SerialDriver);
		brd->dgnc_Major_Serial_Registered = FALSE;
	}

	if (brd->dgnc_Major_TransparentPrint_Registered) {
		dgnc_BoardsByMajor[brd->PrintDriver.major] = NULL;
		brd->dgnc_TransparentPrint_Major = 0;
		for (i = 0; i < brd->nasync; i++) {
			dgnc_remove_tty_sysfs(brd->channels[i]->ch_pun.un_sysfs);
			tty_unregister_device(&brd->PrintDriver, i);
		}
		tty_unregister_driver(&brd->PrintDriver);
		brd->dgnc_Major_TransparentPrint_Registered = FALSE;
	}

	if (brd->SerialDriver.ttys) {
		kfree(brd->SerialDriver.ttys);
		brd->SerialDriver.ttys = NULL;
	}
	if (brd->PrintDriver.ttys) {
		kfree(brd->PrintDriver.ttys);
		brd->PrintDriver.ttys = NULL;
	}
}


#define TMPBUFLEN (1024)

/*
 * dgnc_sniff - Dump data out to the "sniff" buffer if the
 * proc sniff file is opened...
 */
void dgnc_sniff_nowait_nolock(struct channel_t *ch, uchar *text, uchar *buf, int len)
{
	struct timeval tv;
	int n;
	int r;
	int nbuf;
	int i;
	int tmpbuflen;
	char tmpbuf[TMPBUFLEN];
	char *p = tmpbuf;
	int too_much_data;

	/* Leave if sniff not open */
	if (!(ch->ch_sniff_flags & SNIFF_OPEN))
		return;

	do_gettimeofday(&tv);

	/* Create our header for data dump */
	p += sprintf(p, "<%ld %ld><%s><", tv.tv_sec, tv.tv_usec, text);
	tmpbuflen = p - tmpbuf;

	do {
		too_much_data = 0;

		for (i = 0; i < len && tmpbuflen < (TMPBUFLEN - 4); i++) {
			p += sprintf(p, "%02x ", *buf);
			buf++;
			tmpbuflen = p - tmpbuf;
		}

		if (tmpbuflen < (TMPBUFLEN - 4)) {
			if (i > 0)
				p += sprintf(p - 1, "%s\n", ">");
			else
				p += sprintf(p, "%s\n", ">");
		} else {
			too_much_data = 1;
			len -= i;
		}

		nbuf = strlen(tmpbuf);
		p = tmpbuf;

		/*
		 *  Loop while data remains.
		 */
		while (nbuf > 0 && ch->ch_sniff_buf) {
			/*
			 *  Determine the amount of available space left in the
			 *  buffer.  If there's none, wait until some appears.
			 */
			n = (ch->ch_sniff_out - ch->ch_sniff_in - 1) & SNIFF_MASK;

			/*
			 * If there is no space left to write to in our sniff buffer,
			 * we have no choice but to drop the data.
			 * We *cannot* sleep here waiting for space, because this
			 * function was probably called by the interrupt/timer routines!
			 */
			if (n == 0)
				return;

			/*
			 * Copy as much data as will fit.
			 */

			if (n > nbuf)
				n = nbuf;

			r = SNIFF_MAX - ch->ch_sniff_in;

			if (r <= n) {
				memcpy(ch->ch_sniff_buf + ch->ch_sniff_in, p, r);

				n -= r;
				ch->ch_sniff_in = 0;
				p += r;
				nbuf -= r;
			}

			memcpy(ch->ch_sniff_buf + ch->ch_sniff_in, p, n);

			ch->ch_sniff_in += n;
			p += n;
			nbuf -= n;

			/*
			 *  Wakeup any thread waiting for data
			 */
			if (ch->ch_sniff_flags & SNIFF_WAIT_DATA) {
				ch->ch_sniff_flags &= ~SNIFF_WAIT_DATA;
				wake_up_interruptible(&ch->ch_sniff_wait);
			}
		}

		/*
		 * If the user sent us too much data to push into our tmpbuf,
		 * we need to keep looping around on all the data.
		 */
		if (too_much_data) {
			p = tmpbuf;
			tmpbuflen = 0;
		}

	} while (too_much_data);
}


/*=======================================================================
 *
 *	dgnc_wmove - Write data to transmit queue.
 *
 *		ch	- Pointer to channel structure.
 *		buf	- Poiter to characters to be moved.
 *		n	- Number of characters to move.
 *
 *=======================================================================*/
static void dgnc_wmove(struct channel_t *ch, char *buf, uint n)
{
	int	remain;
	uint	head;

	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	head = ch->ch_w_head & WQUEUEMASK;

	/*
	 * If the write wraps over the top of the circular buffer,
	 * move the portion up to the wrap point, and reset the
	 * pointers to the bottom.
	 */
	remain = WQUEUESIZE - head;

	if (n >= remain) {
		n -= remain;
		memcpy(ch->ch_wqueue + head, buf, remain);
		head = 0;
		buf += remain;
	}

	if (n > 0) {
		/*
		 * Move rest of data.
		 */
		remain = n;
		memcpy(ch->ch_wqueue + head, buf, remain);
		head += remain;
	}

	head &= WQUEUEMASK;
	ch->ch_w_head = head;
}




/*=======================================================================
 *
 *      dgnc_input - Process received data.
 *
 *	      ch      - Pointer to channel structure.
 *
 *=======================================================================*/
void dgnc_input(struct channel_t *ch)
{
	struct dgnc_board *bd;
	struct tty_struct *tp;
	struct tty_ldisc *ld;
	uint	rmask;
	ushort	head;
	ushort	tail;
	int	data_len;
	ulong	lock_flags;
	int flip_len;
	int len = 0;
	int n = 0;
	int s = 0;
	int i = 0;

	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	tp = ch->ch_tun.un_tty;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return;

	DGNC_LOCK(ch->ch_lock, lock_flags);

	/*
	 *      Figure the number of characters in the buffer.
	 *      Exit immediately if none.
	 */
	rmask = RQUEUEMASK;
	head = ch->ch_r_head & rmask;
	tail = ch->ch_r_tail & rmask;
	data_len = (head - tail) & rmask;

	if (data_len == 0) {
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return;
	}

	DPR_READ(("dgnc_input start\n"));

	/*
	 * If the device is not open, or CREAD is off,
	 * flush input data and return immediately.
	 */
	if (!tp || (tp->magic != TTY_MAGIC) || !(ch->ch_tun.un_flags & UN_ISOPEN) ||
	    !(tp->termios.c_cflag & CREAD) || (ch->ch_tun.un_flags & UN_CLOSING)) {

		DPR_READ(("input. dropping %d bytes on port %d...\n", data_len, ch->ch_portnum));
		DPR_READ(("input. tp: %p tp->magic: %x MAGIC:%x ch flags: %x\n",
			tp, tp ? tp->magic : 0, TTY_MAGIC, ch->ch_tun.un_flags));

		ch->ch_r_head = tail;

		/* Force queue flow control to be released, if needed */
		dgnc_check_queue_flow_control(ch);

		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return;
	}

	/*
	 * If we are throttled, simply don't read any data.
	 */
	if (ch->ch_flags & CH_FORCED_STOPI) {
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		DPR_READ(("Port %d throttled, not reading any data. head: %x tail: %x\n",
			ch->ch_portnum, head, tail));
		return;
	}

	DPR_READ(("dgnc_input start 2\n"));

	flip_len = TTY_FLIPBUF_SIZE;

	/* Chop down the length, if needed */
	len = min(data_len, flip_len);
	len = min(len, (N_TTY_BUF_SIZE - 1));

	ld = tty_ldisc_ref(tp);

#ifdef TTY_DONT_FLIP
	/*
	 * If the DONT_FLIP flag is on, don't flush our buffer, and act
	 * like the ld doesn't have any space to put the data right now.
	 */
	if (test_bit(TTY_DONT_FLIP, &tp->flags))
		len = 0;
#endif

	/*
	 * If we were unable to get a reference to the ld,
	 * don't flush our buffer, and act like the ld doesn't
	 * have any space to put the data right now.
	 */
	if (!ld) {
		len = 0;
	} else {
		/*
		 * If ld doesn't have a pointer to a receive_buf function,
		 * flush the data, then act like the ld doesn't have any
		 * space to put the data right now.
		 */
		if (!ld->ops->receive_buf) {
			ch->ch_r_head = ch->ch_r_tail;
			len = 0;
		}
	}

	if (len <= 0) {
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		if (ld)
			tty_ldisc_deref(ld);
		return;
	}

	/*
	 * The tty layer in the kernel has changed in 2.6.16+.
	 *
	 * The flip buffers in the tty structure are no longer exposed,
	 * and probably will be going away eventually.
	 *
	 * If we are completely raw, we don't need to go through a lot
	 * of the tty layers that exist.
	 * In this case, we take the shortest and fastest route we
	 * can to relay the data to the user.
	 *
	 * On the other hand, if we are not raw, we need to go through
	 * the new 2.6.16+ tty layer, which has its API more well defined.
	 */
	len = tty_buffer_request_room(tp->port, len);
	n = len;

	/*
	 * n now contains the most amount of data we can copy,
	 * bounded either by how much the Linux tty layer can handle,
	 * or the amount of data the card actually has pending...
	 */
	while (n) {
		s = ((head >= tail) ? head : RQUEUESIZE) - tail;
		s = min(s, n);

		if (s <= 0)
			break;

		/*
		 * If conditions are such that ld needs to see all
		 * UART errors, we will have to walk each character
		 * and error byte and send them to the buffer one at
		 * a time.
		 */
		if (I_PARMRK(tp) || I_BRKINT(tp) || I_INPCK(tp)) {
			for (i = 0; i < s; i++) {
				if (*(ch->ch_equeue + tail + i) & UART_LSR_BI)
					tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_BREAK);
				else if (*(ch->ch_equeue + tail + i) & UART_LSR_PE)
					tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_PARITY);
				else if (*(ch->ch_equeue + tail + i) & UART_LSR_FE)
					tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_FRAME);
				else
					tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_NORMAL);
			}
		} else {
			tty_insert_flip_string(tp->port, ch->ch_rqueue + tail, s);
		}

		dgnc_sniff_nowait_nolock(ch, "USER READ", ch->ch_rqueue + tail, s);

		tail += s;
		n -= s;
		/* Flip queue if needed */
		tail &= rmask;
	}

	ch->ch_r_tail = tail & rmask;
	ch->ch_e_tail = tail & rmask;
	dgnc_check_queue_flow_control(ch);
	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	/* Tell the tty layer its okay to "eat" the data now */
	tty_flip_buffer_push(tp->port);

	if (ld)
		tty_ldisc_deref(ld);

	DPR_READ(("dgnc_input - finish\n"));
}


/************************************************************************
 * Determines when CARRIER changes state and takes appropriate
 * action.
 ************************************************************************/
void dgnc_carrier(struct channel_t *ch)
{
	struct dgnc_board *bd;

	int virt_carrier = 0;
	int phys_carrier = 0;

	DPR_CARR(("dgnc_carrier called...\n"));

	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	bd = ch->ch_bd;

	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return;

	if (ch->ch_mistat & UART_MSR_DCD) {
		DPR_CARR(("mistat: %x  D_CD: %x\n", ch->ch_mistat, ch->ch_mistat & UART_MSR_DCD));
		phys_carrier = 1;
	}

	if (ch->ch_digi.digi_flags & DIGI_FORCEDCD)
		virt_carrier = 1;

	if (ch->ch_c_cflag & CLOCAL)
		virt_carrier = 1;

	DPR_CARR(("DCD: physical: %d virt: %d\n", phys_carrier, virt_carrier));

	/*
	 * Test for a VIRTUAL carrier transition to HIGH.
	 */
	if (((ch->ch_flags & CH_FCAR) == 0) && (virt_carrier == 1)) {

		/*
		 * When carrier rises, wake any threads waiting
		 * for carrier in the open routine.
		 */

		DPR_CARR(("carrier: virt DCD rose\n"));

		if (waitqueue_active(&(ch->ch_flags_wait)))
			wake_up_interruptible(&ch->ch_flags_wait);
	}

	/*
	 * Test for a PHYSICAL carrier transition to HIGH.
	 */
	if (((ch->ch_flags & CH_CD) == 0) && (phys_carrier == 1)) {

		/*
		 * When carrier rises, wake any threads waiting
		 * for carrier in the open routine.
		 */

		DPR_CARR(("carrier: physical DCD rose\n"));

		if (waitqueue_active(&(ch->ch_flags_wait)))
			wake_up_interruptible(&ch->ch_flags_wait);
	}

	/*
	 *  Test for a PHYSICAL transition to low, so long as we aren't
	 *  currently ignoring physical transitions (which is what "virtual
	 *  carrier" indicates).
	 *
	 *  The transition of the virtual carrier to low really doesn't
	 *  matter... it really only means "ignore carrier state", not
	 *  "make pretend that carrier is there".
	 */
	if ((virt_carrier == 0) && ((ch->ch_flags & CH_CD) != 0) &&
	    (phys_carrier == 0))
	{

		/*
		 *   When carrier drops:
		 *
		 *   Drop carrier on all open units.
		 *
		 *   Flush queues, waking up any task waiting in the
		 *   line discipline.
		 *
		 *   Send a hangup to the control terminal.
		 *
		 *   Enable all select calls.
		 */
		if (waitqueue_active(&(ch->ch_flags_wait)))
			wake_up_interruptible(&ch->ch_flags_wait);

		if (ch->ch_tun.un_open_count > 0) {
			DPR_CARR(("Sending tty hangup\n"));
			tty_hangup(ch->ch_tun.un_tty);
		}

		if (ch->ch_pun.un_open_count > 0) {
			DPR_CARR(("Sending pr hangup\n"));
			tty_hangup(ch->ch_pun.un_tty);
		}
	}

	/*
	 *  Make sure that our cached values reflect the current reality.
	 */
	if (virt_carrier == 1)
		ch->ch_flags |= CH_FCAR;
	else
		ch->ch_flags &= ~CH_FCAR;

	if (phys_carrier == 1)
		ch->ch_flags |= CH_CD;
	else
		ch->ch_flags &= ~CH_CD;
}

/*
 *  Assign the custom baud rate to the channel structure
 */
static void dgnc_set_custom_speed(struct channel_t *ch, uint newrate)
{
	int testdiv;
	int testrate_high;
	int testrate_low;
	int deltahigh;
	int deltalow;

	if (newrate <= 0) {
		ch->ch_custom_speed = 0;
		return;
	}

	/*
	 *  Since the divisor is stored in a 16-bit integer, we make sure
	 *  we don't allow any rates smaller than a 16-bit integer would allow.
	 *  And of course, rates above the dividend won't fly.
	 */
	if (newrate && newrate < ((ch->ch_bd->bd_dividend / 0xFFFF) + 1))
		newrate = ((ch->ch_bd->bd_dividend / 0xFFFF) + 1);

	if (newrate && newrate > ch->ch_bd->bd_dividend)
		newrate = ch->ch_bd->bd_dividend;

	if (newrate > 0) {
		testdiv = ch->ch_bd->bd_dividend / newrate;

		/*
		 *  If we try to figure out what rate the board would use
		 *  with the test divisor, it will be either equal or higher
		 *  than the requested baud rate.  If we then determine the
		 *  rate with a divisor one higher, we will get the next lower
		 *  supported rate below the requested.
		 */
		testrate_high = ch->ch_bd->bd_dividend / testdiv;
		testrate_low  = ch->ch_bd->bd_dividend / (testdiv + 1);

		/*
		 *  If the rate for the requested divisor is correct, just
		 *  use it and be done.
		 */
		if (testrate_high != newrate) {
			/*
			 *  Otherwise, pick the rate that is closer (i.e. whichever rate
			 *  has a smaller delta).
			 */
			deltahigh = testrate_high - newrate;
			deltalow = newrate - testrate_low;

			if (deltahigh < deltalow)
				newrate = testrate_high;
			else
				newrate = testrate_low;
		}
	}

	ch->ch_custom_speed = newrate;
}


void dgnc_check_queue_flow_control(struct channel_t *ch)
{
	int qleft = 0;

	/* Store how much space we have left in the queue */
	qleft = ch->ch_r_tail - ch->ch_r_head - 1;
	if (qleft < 0)
		qleft += RQUEUEMASK + 1;

	/*
	 * Check to see if we should enforce flow control on our queue because
	 * the ld (or user) isn't reading data out of our queue fast enuf.
	 *
	 * NOTE: This is done based on what the current flow control of the
	 * port is set for.
	 *
	 * 1) HWFLOW (RTS) - Turn off the UART's Receive interrupt.
	 *	This will cause the UART's FIFO to back up, and force
	 *	the RTS signal to be dropped.
	 * 2) SWFLOW (IXOFF) - Keep trying to send a stop character to
	 *	the other side, in hopes it will stop sending data to us.
	 * 3) NONE - Nothing we can do.  We will simply drop any extra data
	 *	that gets sent into us when the queue fills up.
	 */
	if (qleft < 256) {
		/* HWFLOW */
		if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) {
			if (!(ch->ch_flags & CH_RECEIVER_OFF)) {
				ch->ch_bd->bd_ops->disable_receiver(ch);
				ch->ch_flags |= (CH_RECEIVER_OFF);
				DPR_READ(("Internal queue hit hilevel mark (%d)! Turning off interrupts.\n",
					qleft));
			}
		}
		/* SWFLOW */
		else if (ch->ch_c_iflag & IXOFF) {
			if (ch->ch_stops_sent <= MAX_STOPS_SENT) {
				ch->ch_bd->bd_ops->send_stop_character(ch);
				ch->ch_stops_sent++;
				DPR_READ(("Sending stop char!  Times sent: %x\n", ch->ch_stops_sent));
			}
		}
		/* No FLOW */
		else {
			/* Empty... Can't do anything about the impending overflow... */
		}
	}

	/*
	 * Check to see if we should unenforce flow control because
	 * ld (or user) finally read enuf data out of our queue.
	 *
	 * NOTE: This is done based on what the current flow control of the
	 * port is set for.
	 *
	 * 1) HWFLOW (RTS) - Turn back on the UART's Receive interrupt.
	 *	This will cause the UART's FIFO to raise RTS back up,
	 *	which will allow the other side to start sending data again.
	 * 2) SWFLOW (IXOFF) - Send a start character to
	 *	the other side, so it will start sending data to us again.
	 * 3) NONE - Do nothing. Since we didn't do anything to turn off the
	 *	other side, we don't need to do anything now.
	 */
	if (qleft > (RQUEUESIZE / 2)) {
		/* HWFLOW */
		if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) {
			if (ch->ch_flags & CH_RECEIVER_OFF) {
				ch->ch_bd->bd_ops->enable_receiver(ch);
				ch->ch_flags &= ~(CH_RECEIVER_OFF);
				DPR_READ(("Internal queue hit lowlevel mark (%d)! Turning on interrupts.\n",
					qleft));
			}
		}
		/* SWFLOW */
		else if (ch->ch_c_iflag & IXOFF && ch->ch_stops_sent) {
			ch->ch_stops_sent = 0;
			ch->ch_bd->bd_ops->send_start_character(ch);
			DPR_READ(("Sending start char!\n"));
		}
		/* No FLOW */
		else {
			/* Nothing needed. */
		}
	}
}


void dgnc_wakeup_writes(struct channel_t *ch)
{
	int qlen = 0;
	ulong lock_flags;

	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	DGNC_LOCK(ch->ch_lock, lock_flags);

	/*
	 * If channel now has space, wake up anyone waiting on the condition.
	 */
	qlen = ch->ch_w_head - ch->ch_w_tail;
	if (qlen < 0)
		qlen += WQUEUESIZE;

	if (qlen >= (WQUEUESIZE - 256)) {
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return;
	}

	if (ch->ch_tun.un_flags & UN_ISOPEN) {
		if ((ch->ch_tun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
			ch->ch_tun.un_tty->ldisc->ops->write_wakeup)
		{
			DGNC_UNLOCK(ch->ch_lock, lock_flags);
			(ch->ch_tun.un_tty->ldisc->ops->write_wakeup)(ch->ch_tun.un_tty);
			DGNC_LOCK(ch->ch_lock, lock_flags);
		}

		wake_up_interruptible(&ch->ch_tun.un_tty->write_wait);

		/*
		 * If unit is set to wait until empty, check to make sure
		 * the queue AND FIFO are both empty.
		 */
		if (ch->ch_tun.un_flags & UN_EMPTY) {
			if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0)) {
				ch->ch_tun.un_flags &= ~(UN_EMPTY);

				/*
				 * If RTS Toggle mode is on, whenever
				 * the queue and UART is empty, keep RTS low.
				 */
				if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) {
					ch->ch_mostat &= ~(UART_MCR_RTS);
					ch->ch_bd->bd_ops->assert_modem_signals(ch);
				}

				/*
				 * If DTR Toggle mode is on, whenever
				 * the queue and UART is empty, keep DTR low.
				 */
				if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) {
					ch->ch_mostat &= ~(UART_MCR_DTR);
					ch->ch_bd->bd_ops->assert_modem_signals(ch);
				}
			}
		}

		wake_up_interruptible(&ch->ch_tun.un_flags_wait);
	}

	if (ch->ch_pun.un_flags & UN_ISOPEN) {
		if ((ch->ch_pun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
			ch->ch_pun.un_tty->ldisc->ops->write_wakeup)
		{
			DGNC_UNLOCK(ch->ch_lock, lock_flags);
			(ch->ch_pun.un_tty->ldisc->ops->write_wakeup)(ch->ch_pun.un_tty);
			DGNC_LOCK(ch->ch_lock, lock_flags);
		}

		wake_up_interruptible(&ch->ch_pun.un_tty->write_wait);

		/*
		 * If unit is set to wait until empty, check to make sure
		 * the queue AND FIFO are both empty.
		 */
		if (ch->ch_pun.un_flags & UN_EMPTY) {
			if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0)) {
				ch->ch_pun.un_flags &= ~(UN_EMPTY);
			}
		}

		wake_up_interruptible(&ch->ch_pun.un_flags_wait);
	}

	DGNC_UNLOCK(ch->ch_lock, lock_flags);
}



/************************************************************************
 *
 * TTY Entry points and helper functions
 *
 ************************************************************************/

/*
 * dgnc_tty_open()
 *
 */
static int dgnc_tty_open(struct tty_struct *tty, struct file *file)
{
	struct dgnc_board	*brd;
	struct channel_t *ch;
	struct un_t	*un;
	uint		major = 0;
	uint		minor = 0;
	int		rc = 0;
	ulong		lock_flags;

	rc = 0;

	major = MAJOR(tty_devnum(tty));
	minor = MINOR(tty_devnum(tty));

	if (major > 255)
		return -ENXIO;

	/* Get board pointer from our array of majors we have allocated */
	brd = dgnc_BoardsByMajor[major];
	if (!brd)
		return -ENXIO;

	/*
	 * If board is not yet up to a state of READY, go to
	 * sleep waiting for it to happen or they cancel the open.
	 */
	rc = wait_event_interruptible(brd->state_wait,
		(brd->state & BOARD_READY));

	if (rc)
		return rc;

	DGNC_LOCK(brd->bd_lock, lock_flags);

	/* If opened device is greater than our number of ports, bail. */
	if (PORT_NUM(minor) > brd->nasync) {
		DGNC_UNLOCK(brd->bd_lock, lock_flags);
		return -ENXIO;
	}

	ch = brd->channels[PORT_NUM(minor)];
	if (!ch) {
		DGNC_UNLOCK(brd->bd_lock, lock_flags);
		return -ENXIO;
	}

	/* Drop board lock */
	DGNC_UNLOCK(brd->bd_lock, lock_flags);

	/* Grab channel lock */
	DGNC_LOCK(ch->ch_lock, lock_flags);

	/* Figure out our type */
	if (!IS_PRINT(minor)) {
		un = &brd->channels[PORT_NUM(minor)]->ch_tun;
		un->un_type = DGNC_SERIAL;
	} else if (IS_PRINT(minor)) {
		un = &brd->channels[PORT_NUM(minor)]->ch_pun;
		un->un_type = DGNC_PRINT;
	} else {
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		DPR_OPEN(("%d Unknown TYPE!\n", __LINE__));
		return -ENXIO;
	}

	/*
	 * If the port is still in a previous open, and in a state
	 * where we simply cannot safely keep going, wait until the
	 * state clears.
	 */
	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	rc = wait_event_interruptible(ch->ch_flags_wait, ((ch->ch_flags & CH_OPENING) == 0));

	/* If ret is non-zero, user ctrl-c'ed us */
	if (rc) {
		DPR_OPEN(("%d User ctrl c'ed\n", __LINE__));
		return -EINTR;
	}

	/*
	 * If either unit is in the middle of the fragile part of close,
	 * we just cannot touch the channel safely.
	 * Go to sleep, knowing that when the channel can be
	 * touched safely, the close routine will signal the
	 * ch_flags_wait to wake us back up.
	 */
	rc = wait_event_interruptible(ch->ch_flags_wait,
		(((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING) == 0));

	/* If ret is non-zero, user ctrl-c'ed us */
	if (rc) {
		DPR_OPEN(("%d User ctrl c'ed\n", __LINE__));
		return -EINTR;
	}

	DGNC_LOCK(ch->ch_lock, lock_flags);


	/* Store our unit into driver_data, so we always have it available. */
	tty->driver_data = un;

	DPR_OPEN(("Open called. MAJOR: %d MINOR:%d PORT_NUM: %x unit: %p NAME: %s\n",
		MAJOR(tty_devnum(tty)), MINOR(tty_devnum(tty)), PORT_NUM(minor), un, brd->name));

	DPR_OPEN(("%d: tflag=%x  pflag=%x\n", __LINE__, ch->ch_tun.un_flags, ch->ch_pun.un_flags));

	/*
	 * Initialize tty's
	 */
	if (!(un->un_flags & UN_ISOPEN)) {
		/* Store important variables. */
		un->un_tty     = tty;

		/* Maybe do something here to the TTY struct as well? */
	}


	/*
	 * Allocate channel buffers for read/write/error.
	 * Set flag, so we don't get trounced on.
	 */
	ch->ch_flags |= (CH_OPENING);

	/* Drop locks, as malloc with GFP_KERNEL can sleep */
	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	if (!ch->ch_rqueue)
		ch->ch_rqueue = kzalloc(RQUEUESIZE, GFP_KERNEL);
	if (!ch->ch_equeue)
		ch->ch_equeue = kzalloc(EQUEUESIZE, GFP_KERNEL);
	if (!ch->ch_wqueue)
		ch->ch_wqueue = kzalloc(WQUEUESIZE, GFP_KERNEL);

	DGNC_LOCK(ch->ch_lock, lock_flags);

	ch->ch_flags &= ~(CH_OPENING);
	wake_up_interruptible(&ch->ch_flags_wait);

	/*
	 * Initialize if neither terminal or printer is open.
	 */
	if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_ISOPEN)) {

		DPR_OPEN(("dgnc_open: initializing channel in open...\n"));

		/*
		 * Flush input queues.
		 */
		ch->ch_r_head = ch->ch_r_tail = 0;
		ch->ch_e_head = ch->ch_e_tail = 0;
		ch->ch_w_head = ch->ch_w_tail = 0;

		brd->bd_ops->flush_uart_write(ch);
		brd->bd_ops->flush_uart_read(ch);

		ch->ch_flags = 0;
		ch->ch_cached_lsr = 0;
		ch->ch_stop_sending_break = 0;
		ch->ch_stops_sent = 0;

		ch->ch_c_cflag   = tty->termios.c_cflag;
		ch->ch_c_iflag   = tty->termios.c_iflag;
		ch->ch_c_oflag   = tty->termios.c_oflag;
		ch->ch_c_lflag   = tty->termios.c_lflag;
		ch->ch_startc = tty->termios.c_cc[VSTART];
		ch->ch_stopc  = tty->termios.c_cc[VSTOP];

		/*
		 * Bring up RTS and DTR...
		 * Also handle RTS or DTR toggle if set.
		 */
		if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
			ch->ch_mostat |= (UART_MCR_RTS);
		if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
			ch->ch_mostat |= (UART_MCR_DTR);

		/* Tell UART to init itself */
		brd->bd_ops->uart_init(ch);
	}

	/*
	 * Run param in case we changed anything
	 */
	brd->bd_ops->param(tty);

	dgnc_carrier(ch);

	/*
	 * follow protocol for opening port
	 */

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	rc = dgnc_block_til_ready(tty, file, ch);

	if (rc)
		DPR_OPEN(("dgnc_tty_open returning after dgnc_block_til_ready "
			"with %d\n", rc));

	/* No going back now, increment our unit and channel counters */
	DGNC_LOCK(ch->ch_lock, lock_flags);
	ch->ch_open_count++;
	un->un_open_count++;
	un->un_flags |= (UN_ISOPEN);
	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_OPEN(("dgnc_tty_open finished\n"));
	return rc;
}


/*
 * dgnc_block_til_ready()
 *
 * Wait for DCD, if needed.
 */
static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch)
{
	int retval = 0;
	struct un_t *un = NULL;
	ulong   lock_flags;
	uint	old_flags = 0;
	int	sleep_on_un_flags = 0;

	if (!tty || tty->magic != TTY_MAGIC || !file || !ch || ch->magic != DGNC_CHANNEL_MAGIC) {
		return -ENXIO;
	}

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return -ENXIO;

	DPR_OPEN(("dgnc_block_til_ready - before block.\n"));

	DGNC_LOCK(ch->ch_lock, lock_flags);

	ch->ch_wopen++;

	/* Loop forever */
	while (1) {

		sleep_on_un_flags = 0;

		/*
		 * If board has failed somehow during our sleep, bail with error.
		 */
		if (ch->ch_bd->state == BOARD_FAILED) {
			retval = -ENXIO;
			break;
		}

		/* If tty was hung up, break out of loop and set error. */
		if (tty_hung_up_p(file)) {
			retval = -EAGAIN;
			break;
		}

		/*
		 * If either unit is in the middle of the fragile part of close,
		 * we just cannot touch the channel safely.
		 * Go back to sleep, knowing that when the channel can be
		 * touched safely, the close routine will signal the
		 * ch_wait_flags to wake us back up.
		 */
		if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING)) {

			/*
			 * Our conditions to leave cleanly and happily:
			 * 1) NONBLOCKING on the tty is set.
			 * 2) CLOCAL is set.
			 * 3) DCD (fake or real) is active.
			 */

			if (file->f_flags & O_NONBLOCK)
				break;

			if (tty->flags & (1 << TTY_IO_ERROR)) {
				retval = -EIO;
				break;
			}

			if (ch->ch_flags & CH_CD) {
				DPR_OPEN(("%d: ch_flags: %x\n", __LINE__, ch->ch_flags));
				break;
			}

			if (ch->ch_flags & CH_FCAR) {
				DPR_OPEN(("%d: ch_flags: %x\n", __LINE__, ch->ch_flags));
				break;
			}
		} else {
			sleep_on_un_flags = 1;
		}

		/*
		 * If there is a signal pending, the user probably
		 * interrupted (ctrl-c) us.
		 * Leave loop with error set.
		 */
		if (signal_pending(current)) {
			DPR_OPEN(("%d: signal pending...\n", __LINE__));
			retval = -ERESTARTSYS;
			break;
		}

		DPR_OPEN(("dgnc_block_til_ready - blocking.\n"));

		/*
		 * Store the flags before we let go of channel lock
		 */
		if (sleep_on_un_flags)
			old_flags = ch->ch_tun.un_flags | ch->ch_pun.un_flags;
		else
			old_flags = ch->ch_flags;

		/*
		 * Let go of channel lock before calling schedule.
		 * Our poller will get any FEP events and wake us up when DCD
		 * eventually goes active.
		 */

		DGNC_UNLOCK(ch->ch_lock, lock_flags);

		DPR_OPEN(("Going to sleep on %s flags...\n",
			(sleep_on_un_flags ? "un" : "ch")));

		/*
		 * Wait for something in the flags to change from the current value.
		 */
		if (sleep_on_un_flags)
			retval = wait_event_interruptible(un->un_flags_wait,
				(old_flags != (ch->ch_tun.un_flags | ch->ch_pun.un_flags)));
		else
			retval = wait_event_interruptible(ch->ch_flags_wait,
				(old_flags != ch->ch_flags));

		DPR_OPEN(("After sleep... retval: %x\n", retval));

		/*
		 * We got woken up for some reason.
		 * Before looping around, grab our channel lock.
		 */
		DGNC_LOCK(ch->ch_lock, lock_flags);
	}

	ch->ch_wopen--;

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_OPEN(("dgnc_block_til_ready - after blocking.\n"));

	if (retval) {
		DPR_OPEN(("dgnc_block_til_ready - done. error. retval: %x\n", retval));
		return retval;
	}

	DPR_OPEN(("dgnc_block_til_ready - done no error. jiffies: %lu\n", jiffies));

	return 0;
}


/*
 * dgnc_tty_hangup()
 *
 * Hangup the port.  Like a close, but don't wait for output to drain.
 */
static void dgnc_tty_hangup(struct tty_struct *tty)
{
	struct un_t	*un;

	if (!tty || tty->magic != TTY_MAGIC)
		return;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return;

	DPR_CLOSE(("dgnc_hangup called. ch->ch_open_count: %d un->un_open_count: %d\n",
		un->un_ch->ch_open_count, un->un_open_count));

	/* flush the transmit queues */
	dgnc_tty_flush_buffer(tty);

	DPR_CLOSE(("dgnc_hangup finished. ch->ch_open_count: %d un->un_open_count: %d\n",
		un->un_ch->ch_open_count, un->un_open_count));
}


/*
 * dgnc_tty_close()
 *
 */
static void dgnc_tty_close(struct tty_struct *tty, struct file *file)
{
	struct ktermios *ts;
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	ulong lock_flags;
	int rc = 0;

	if (!tty || tty->magic != TTY_MAGIC)
		return;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return;

	ts = &tty->termios;

	DPR_CLOSE(("Close called\n"));

	DGNC_LOCK(ch->ch_lock, lock_flags);

	/*
	 * Determine if this is the last close or not - and if we agree about
	 * which type of close it is with the Line Discipline
	 */
	if ((tty->count == 1) && (un->un_open_count != 1)) {
		/*
		 * Uh, oh.  tty->count is 1, which means that the tty
		 * structure will be freed.  un_open_count should always
		 * be one in these conditions.  If it's greater than
		 * one, we've got real problems, since it means the
		 * serial port won't be shutdown.
		 */
		APR(("tty->count is 1, un open count is %d\n", un->un_open_count));
		un->un_open_count = 1;
	}

	if (--un->un_open_count < 0) {
		APR(("bad serial port open count of %d\n", un->un_open_count));
		un->un_open_count = 0;
	}

	ch->ch_open_count--;

	if (ch->ch_open_count && un->un_open_count) {
		DPR_CLOSE(("dgnc_tty_close: not last close ch: %d un:%d\n",
			ch->ch_open_count, un->un_open_count));

		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return;
	}

	/* OK, its the last close on the unit */
	DPR_CLOSE(("dgnc_tty_close - last close on unit procedures\n"));

	un->un_flags |= UN_CLOSING;

	tty->closing = 1;


	/*
	 * Only officially close channel if count is 0 and
	 * DIGI_PRINTER bit is not set.
	 */
	if ((ch->ch_open_count == 0) && !(ch->ch_digi.digi_flags & DIGI_PRINTER)) {

		ch->ch_flags &= ~(CH_STOPI | CH_FORCED_STOPI);

		/*
		 * turn off print device when closing print device.
		 */
		if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
			dgnc_wmove(ch, ch->ch_digi.digi_offstr,
				(int) ch->ch_digi.digi_offlen);
			ch->ch_flags &= ~CH_PRON;
		}

		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		/* wait for output to drain */
		/* This will also return if we take an interrupt */

		DPR_CLOSE(("Calling wait_for_drain\n"));
		rc = bd->bd_ops->drain(tty, 0);

		DPR_CLOSE(("After calling wait_for_drain\n"));

		if (rc)
			DPR_BASIC(("dgnc_tty_close - bad return: %d ", rc));

		dgnc_tty_flush_buffer(tty);
		tty_ldisc_flush(tty);

		DGNC_LOCK(ch->ch_lock, lock_flags);

		tty->closing = 0;

		/*
		 * If we have HUPCL set, lower DTR and RTS
		 */
		if (ch->ch_c_cflag & HUPCL) {
			DPR_CLOSE(("Close. HUPCL set, dropping DTR/RTS\n"));

			/* Drop RTS/DTR */
			ch->ch_mostat &= ~(UART_MCR_DTR | UART_MCR_RTS);
			bd->bd_ops->assert_modem_signals(ch);

			/*
			 * Go to sleep to ensure RTS/DTR
			 * have been dropped for modems to see it.
			 */
			if (ch->ch_close_delay) {
				DPR_CLOSE(("Close. Sleeping for RTS/DTR drop\n"));

				DGNC_UNLOCK(ch->ch_lock, lock_flags);
				dgnc_ms_sleep(ch->ch_close_delay);
				DGNC_LOCK(ch->ch_lock, lock_flags);

				DPR_CLOSE(("Close. After sleeping for RTS/DTR drop\n"));
			}
		}

		ch->ch_old_baud = 0;

		/* Turn off UART interrupts for this port */
		ch->ch_bd->bd_ops->uart_off(ch);
	} else {
		/*
		 * turn off print device when closing print device.
		 */
		if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
			dgnc_wmove(ch, ch->ch_digi.digi_offstr,
				(int) ch->ch_digi.digi_offlen);
			ch->ch_flags &= ~CH_PRON;
		}
	}

	un->un_tty = NULL;
	un->un_flags &= ~(UN_ISOPEN | UN_CLOSING);

	DPR_CLOSE(("Close. Doing wakeups\n"));
	wake_up_interruptible(&ch->ch_flags_wait);
	wake_up_interruptible(&un->un_flags_wait);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_BASIC(("dgnc_tty_close - complete\n"));
}


/*
 * dgnc_tty_chars_in_buffer()
 *
 * Return number of characters that have not been transmitted yet.
 *
 * This routine is used by the line discipline to determine if there
 * is data waiting to be transmitted/drained/flushed or not.
 */
static int dgnc_tty_chars_in_buffer(struct tty_struct *tty)
{
	struct channel_t *ch = NULL;
	struct un_t *un = NULL;
	ushort thead;
	ushort ttail;
	uint tmask;
	uint chars = 0;
	ulong   lock_flags = 0;

	if (tty == NULL)
		return 0;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return 0;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return 0;

	DGNC_LOCK(ch->ch_lock, lock_flags);

	tmask = WQUEUEMASK;
	thead = ch->ch_w_head & tmask;
	ttail = ch->ch_w_tail & tmask;

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	if (ttail == thead) {
		chars = 0;
	} else {
		if (thead >= ttail)
			chars = thead - ttail;
		else
			chars = thead - ttail + WQUEUESIZE;
	}

	DPR_WRITE(("dgnc_tty_chars_in_buffer. Port: %x - %d (head: %d tail: %d)\n",
		ch->ch_portnum, chars, thead, ttail));

	return chars;
}


/*
 * dgnc_maxcps_room
 *
 * Reduces bytes_available to the max number of characters
 * that can be sent currently given the maxcps value, and
 * returns the new bytes_available.  This only affects printer
 * output.
 */
static int dgnc_maxcps_room(struct tty_struct *tty, int bytes_available)
{
	struct channel_t *ch = NULL;
	struct un_t *un = NULL;

	if (!tty)
		return bytes_available;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return bytes_available;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return bytes_available;

	/*
	 * If its not the Transparent print device, return
	 * the full data amount.
	 */
	if (un->un_type != DGNC_PRINT)
		return bytes_available;

	if (ch->ch_digi.digi_maxcps > 0 && ch->ch_digi.digi_bufsize > 0) {
		int cps_limit = 0;
		unsigned long current_time = jiffies;
		unsigned long buffer_time = current_time +
			(HZ * ch->ch_digi.digi_bufsize) / ch->ch_digi.digi_maxcps;

		if (ch->ch_cpstime < current_time) {
			/* buffer is empty */
			ch->ch_cpstime = current_time;	    /* reset ch_cpstime */
			cps_limit = ch->ch_digi.digi_bufsize;
		} else if (ch->ch_cpstime < buffer_time) {
			/* still room in the buffer */
			cps_limit = ((buffer_time - ch->ch_cpstime) * ch->ch_digi.digi_maxcps) / HZ;
		} else {
			/* no room in the buffer */
			cps_limit = 0;
		}

		bytes_available = min(cps_limit, bytes_available);
	}

	return bytes_available;
}


/*
 * dgnc_tty_write_room()
 *
 * Return space available in Tx buffer
 */
static int dgnc_tty_write_room(struct tty_struct *tty)
{
	struct channel_t *ch = NULL;
	struct un_t *un = NULL;
	ushort head;
	ushort tail;
	ushort tmask;
	int ret = 0;
	ulong   lock_flags = 0;

	if (tty == NULL || dgnc_TmpWriteBuf == NULL)
		return 0;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return 0;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return 0;

	DGNC_LOCK(ch->ch_lock, lock_flags);

	tmask = WQUEUEMASK;
	head = (ch->ch_w_head) & tmask;
	tail = (ch->ch_w_tail) & tmask;

	ret = tail - head - 1;
	if (ret < 0)
		ret += WQUEUESIZE;

	/* Limit printer to maxcps */
	ret = dgnc_maxcps_room(tty, ret);

	/*
	 * If we are printer device, leave space for
	 * possibly both the on and off strings.
	 */
	if (un->un_type == DGNC_PRINT) {
		if (!(ch->ch_flags & CH_PRON))
			ret -= ch->ch_digi.digi_onlen;
		ret -= ch->ch_digi.digi_offlen;
	} else {
		if (ch->ch_flags & CH_PRON)
			ret -= ch->ch_digi.digi_offlen;
	}

	if (ret < 0)
		ret = 0;

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_WRITE(("dgnc_tty_write_room - %d tail: %d head: %d\n", ret, tail, head));

	return ret;
}


/*
 * dgnc_tty_put_char()
 *
 * Put a character into ch->ch_buf
 *
 *      - used by the line discipline for OPOST processing
 */
static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c)
{
	/*
	 * Simply call tty_write.
	 */
	DPR_WRITE(("dgnc_tty_put_char called\n"));
	dgnc_tty_write(tty, &c, 1);
	return 1;
}


/*
 * dgnc_tty_write()
 *
 * Take data from the user or kernel and send it out to the FEP.
 * In here exists all the Transparent Print magic as well.
 */
static int dgnc_tty_write(struct tty_struct *tty,
		const unsigned char *buf, int count)
{
	struct channel_t *ch = NULL;
	struct un_t *un = NULL;
	int bufcount = 0, n = 0;
	int orig_count = 0;
	ulong lock_flags;
	ushort head;
	ushort tail;
	ushort tmask;
	uint remain;
	int from_user = 0;

	if (tty == NULL || dgnc_TmpWriteBuf == NULL)
		return 0;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return 0;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return 0;

	if (!count)
		return 0;

	DPR_WRITE(("dgnc_tty_write: Port: %x tty=%p user=%d len=%d\n",
		ch->ch_portnum, tty, from_user, count));

	/*
	 * Store original amount of characters passed in.
	 * This helps to figure out if we should ask the FEP
	 * to send us an event when it has more space available.
	 */
	orig_count = count;

	DGNC_LOCK(ch->ch_lock, lock_flags);

	/* Get our space available for the channel from the board */
	tmask = WQUEUEMASK;
	head = (ch->ch_w_head) & tmask;
	tail = (ch->ch_w_tail) & tmask;

	bufcount = tail - head - 1;
	if (bufcount < 0)
		bufcount += WQUEUESIZE;

	DPR_WRITE(("%d: bufcount: %x count: %x tail: %x head: %x tmask: %x\n",
		__LINE__, bufcount, count, tail, head, tmask));

	/*
	 * Limit printer output to maxcps overall, with bursts allowed
	 * up to bufsize characters.
	 */
	bufcount = dgnc_maxcps_room(tty, bufcount);

	/*
	 * Take minimum of what the user wants to send, and the
	 * space available in the FEP buffer.
	 */
	count = min(count, bufcount);

	/*
	 * Bail if no space left.
	 */
	if (count <= 0) {
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return 0;
	}

	/*
	 * Output the printer ON string, if we are in terminal mode, but
	 * need to be in printer mode.
	 */
	if ((un->un_type == DGNC_PRINT) && !(ch->ch_flags & CH_PRON)) {
		dgnc_wmove(ch, ch->ch_digi.digi_onstr,
		    (int) ch->ch_digi.digi_onlen);
		head = (ch->ch_w_head) & tmask;
		ch->ch_flags |= CH_PRON;
	}

	/*
	 * On the other hand, output the printer OFF string, if we are
	 * currently in printer mode, but need to output to the terminal.
	 */
	if ((un->un_type != DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
		dgnc_wmove(ch, ch->ch_digi.digi_offstr,
			(int) ch->ch_digi.digi_offlen);
		head = (ch->ch_w_head) & tmask;
		ch->ch_flags &= ~CH_PRON;
	}

	/*
	 * If there is nothing left to copy, or I can't handle any more data, leave.
	 */
	if (count <= 0) {
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return 0;
	}

	if (from_user) {

		count = min(count, WRITEBUFLEN);

		DGNC_UNLOCK(ch->ch_lock, lock_flags);

		/*
		 * If data is coming from user space, copy it into a temporary
		 * buffer so we don't get swapped out while doing the copy to
		 * the board.
		 */
		/* we're allowed to block if it's from_user */
		if (down_interruptible(&dgnc_TmpWriteSem))
			return -EINTR;

		/*
		 * copy_from_user() returns the number
		 * of bytes that could *NOT* be copied.
		 */
		count -= copy_from_user(dgnc_TmpWriteBuf, (const uchar __user *) buf, count);

		if (!count) {
			up(&dgnc_TmpWriteSem);
			return -EFAULT;
		}

		DGNC_LOCK(ch->ch_lock, lock_flags);

		buf = dgnc_TmpWriteBuf;

	}

	n = count;

	/*
	 * If the write wraps over the top of the circular buffer,
	 * move the portion up to the wrap point, and reset the
	 * pointers to the bottom.
	 */
	remain = WQUEUESIZE - head;

	if (n >= remain) {
		n -= remain;
		memcpy(ch->ch_wqueue + head, buf, remain);
		dgnc_sniff_nowait_nolock(ch, "USER WRITE", ch->ch_wqueue + head, remain);
		head = 0;
		buf += remain;
	}

	if (n > 0) {
		/*
		 * Move rest of data.
		 */
		remain = n;
		memcpy(ch->ch_wqueue + head, buf, remain);
		dgnc_sniff_nowait_nolock(ch, "USER WRITE", ch->ch_wqueue + head, remain);
		head += remain;
	}

	if (count) {
		head &= tmask;
		ch->ch_w_head = head;
	}

#if 0
	/*
	 * If this is the print device, and the
	 * printer is still on, we need to turn it
	 * off before going idle.
	 */
	if (count == orig_count) {
		if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
			head &= tmask;
			ch->ch_w_head = head;
			dgnc_wmove(ch, ch->ch_digi.digi_offstr,
				(int) ch->ch_digi.digi_offlen);
			head = (ch->ch_w_head) & tmask;
			ch->ch_flags &= ~CH_PRON;
		}
	}
#endif

	/* Update printer buffer empty time. */
	if ((un->un_type == DGNC_PRINT) && (ch->ch_digi.digi_maxcps > 0)
	    && (ch->ch_digi.digi_bufsize > 0)) {
		ch->ch_cpstime += (HZ * count) / ch->ch_digi.digi_maxcps;
	}

	if (from_user) {
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		up(&dgnc_TmpWriteSem);
	} else {
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
	}

	DPR_WRITE(("Write finished - Write %d bytes of %d.\n", count, orig_count));

	if (count) {
		/*
		 * Channel lock is grabbed and then released
		 * inside this routine.
		 */
		ch->ch_bd->bd_ops->copy_data_from_queue_to_uart(ch);
	}

	return count;
}


/*
 * Return modem signals to ld.
 */

static int dgnc_tty_tiocmget(struct tty_struct *tty)
{
	struct channel_t *ch;
	struct un_t *un;
	int result = -EIO;
	uchar mstat = 0;
	ulong   lock_flags;

	if (!tty || tty->magic != TTY_MAGIC)
		return result;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return result;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return result;

	DPR_IOCTL(("dgnc_tty_tiocmget start\n"));

	DGNC_LOCK(ch->ch_lock, lock_flags);

	mstat = (ch->ch_mostat | ch->ch_mistat);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	result = 0;

	if (mstat & UART_MCR_DTR)
		result |= TIOCM_DTR;
	if (mstat & UART_MCR_RTS)
		result |= TIOCM_RTS;
	if (mstat & UART_MSR_CTS)
		result |= TIOCM_CTS;
	if (mstat & UART_MSR_DSR)
		result |= TIOCM_DSR;
	if (mstat & UART_MSR_RI)
		result |= TIOCM_RI;
	if (mstat & UART_MSR_DCD)
		result |= TIOCM_CD;

	DPR_IOCTL(("dgnc_tty_tiocmget finish\n"));

	return result;
}


/*
 * dgnc_tty_tiocmset()
 *
 * Set modem signals, called by ld.
 */

static int dgnc_tty_tiocmset(struct tty_struct *tty,
		unsigned int set, unsigned int clear)
{
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	int ret = -EIO;
	ulong   lock_flags;

	if (!tty || tty->magic != TTY_MAGIC)
		return ret;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return ret;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return ret;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return ret;

	DPR_IOCTL(("dgnc_tty_tiocmset start\n"));


	DGNC_LOCK(ch->ch_lock, lock_flags);

	if (set & TIOCM_RTS)
		ch->ch_mostat |= UART_MCR_RTS;

	if (set & TIOCM_DTR)
		ch->ch_mostat |= UART_MCR_DTR;

	if (clear & TIOCM_RTS)
		ch->ch_mostat &= ~(UART_MCR_RTS);

	if (clear & TIOCM_DTR)
		ch->ch_mostat &= ~(UART_MCR_DTR);

	ch->ch_bd->bd_ops->assert_modem_signals(ch);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("dgnc_tty_tiocmset finish\n"));

	return 0;
}


/*
 * dgnc_tty_send_break()
 *
 * Send a Break, called by ld.
 */
static int dgnc_tty_send_break(struct tty_struct *tty, int msec)
{
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	int ret = -EIO;
	ulong   lock_flags;

	if (!tty || tty->magic != TTY_MAGIC)
		return ret;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return ret;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return ret;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return ret;

	switch (msec) {
	case -1:
		msec = 0xFFFF;
		break;
	case 0:
		msec = 0;
		break;
	default:
		break;
	}

	DPR_IOCTL(("dgnc_tty_send_break start 1.  %lx\n", jiffies));

	DGNC_LOCK(ch->ch_lock, lock_flags);

	ch->ch_bd->bd_ops->send_break(ch, msec);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("dgnc_tty_send_break finish\n"));

	return 0;

}


/*
 * dgnc_tty_wait_until_sent()
 *
 * wait until data has been transmitted, called by ld.
 */
static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout)
{
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	int rc;

	if (!tty || tty->magic != TTY_MAGIC)
		return;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return;

	rc = bd->bd_ops->drain(tty, 0);
	if (rc) {
		DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
		return;
	}
	return;
}


/*
 * dgnc_send_xchar()
 *
 * send a high priority character, called by ld.
 */
static void dgnc_tty_send_xchar(struct tty_struct *tty, char c)
{
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	ulong   lock_flags;

	if (!tty || tty->magic != TTY_MAGIC)
		return;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return;

	DPR_IOCTL(("dgnc_tty_send_xchar start\n"));
	printk("dgnc_tty_send_xchar start\n");

	DGNC_LOCK(ch->ch_lock, lock_flags);
	bd->bd_ops->send_immediate_char(ch, c);
	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("dgnc_tty_send_xchar finish\n"));
	printk("dgnc_tty_send_xchar finish\n");
	return;
}




/*
 * Return modem signals to ld.
 */
static inline int dgnc_get_mstat(struct channel_t *ch)
{
	unsigned char mstat;
	int result = -EIO;
	ulong   lock_flags;

	DPR_IOCTL(("dgnc_getmstat start\n"));

	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return -ENXIO;

	DGNC_LOCK(ch->ch_lock, lock_flags);

	mstat = (ch->ch_mostat | ch->ch_mistat);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	result = 0;

	if (mstat & UART_MCR_DTR)
		result |= TIOCM_DTR;
	if (mstat & UART_MCR_RTS)
		result |= TIOCM_RTS;
	if (mstat & UART_MSR_CTS)
		result |= TIOCM_CTS;
	if (mstat & UART_MSR_DSR)
		result |= TIOCM_DSR;
	if (mstat & UART_MSR_RI)
		result |= TIOCM_RI;
	if (mstat & UART_MSR_DCD)
		result |= TIOCM_CD;

	DPR_IOCTL(("dgnc_getmstat finish\n"));

	return result;
}



/*
 * Return modem signals to ld.
 */
static int dgnc_get_modem_info(struct channel_t *ch, unsigned int  __user *value)
{
	int result;
	int rc;

	DPR_IOCTL(("dgnc_get_modem_info start\n"));

	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return -ENXIO;

	result = dgnc_get_mstat(ch);

	if (result < 0)
		return -ENXIO;

	rc = put_user(result, value);

	DPR_IOCTL(("dgnc_get_modem_info finish\n"));
	return rc;
}


/*
 * dgnc_set_modem_info()
 *
 * Set modem signals, called by ld.
 */
static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value)
{
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	int ret = -ENXIO;
	unsigned int arg = 0;
	ulong   lock_flags;

	if (!tty || tty->magic != TTY_MAGIC)
		return ret;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return ret;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return ret;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return ret;

	ret = 0;

	DPR_IOCTL(("dgnc_set_modem_info() start\n"));

	ret = get_user(arg, value);
	if (ret)
		return ret;

	switch (command) {
	case TIOCMBIS:
		if (arg & TIOCM_RTS)
			ch->ch_mostat |= UART_MCR_RTS;

		if (arg & TIOCM_DTR)
			ch->ch_mostat |= UART_MCR_DTR;

		break;

	case TIOCMBIC:
		if (arg & TIOCM_RTS)
			ch->ch_mostat &= ~(UART_MCR_RTS);

		if (arg & TIOCM_DTR)
			ch->ch_mostat &= ~(UART_MCR_DTR);

		break;

	case TIOCMSET:

		if (arg & TIOCM_RTS)
			ch->ch_mostat |= UART_MCR_RTS;
		else
			ch->ch_mostat &= ~(UART_MCR_RTS);

		if (arg & TIOCM_DTR)
			ch->ch_mostat |= UART_MCR_DTR;
		else
			ch->ch_mostat &= ~(UART_MCR_DTR);

		break;

	default:
		return -EINVAL;
	}

	DGNC_LOCK(ch->ch_lock, lock_flags);

	ch->ch_bd->bd_ops->assert_modem_signals(ch);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("dgnc_set_modem_info finish\n"));

	return 0;
}


/*
 * dgnc_tty_digigeta()
 *
 * Ioctl to get the information for ditty.
 *
 *
 *
 */
static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo)
{
	struct channel_t *ch;
	struct un_t *un;
	struct digi_t tmp;
	ulong   lock_flags;

	if (!retinfo)
		return -EFAULT;

	if (!tty || tty->magic != TTY_MAGIC)
		return -EFAULT;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return -EFAULT;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return -EFAULT;

	memset(&tmp, 0, sizeof(tmp));

	DGNC_LOCK(ch->ch_lock, lock_flags);
	memcpy(&tmp, &ch->ch_digi, sizeof(tmp));
	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
		return -EFAULT;

	return 0;
}


/*
 * dgnc_tty_digiseta()
 *
 * Ioctl to set the information for ditty.
 *
 *
 *
 */
static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info)
{
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	struct digi_t new_digi;
	ulong lock_flags;

	DPR_IOCTL(("DIGI_SETA start\n"));

	if (!tty || tty->magic != TTY_MAGIC)
		return -EFAULT;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return -EFAULT;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return -EFAULT;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return -EFAULT;

	if (copy_from_user(&new_digi, new_info, sizeof(new_digi))) {
		DPR_IOCTL(("DIGI_SETA failed copy_from_user\n"));
		return -EFAULT;
	}

	DGNC_LOCK(ch->ch_lock, lock_flags);

	/*
	 * Handle transistions to and from RTS Toggle.
	 */
	if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && (new_digi.digi_flags & DIGI_RTS_TOGGLE))
		ch->ch_mostat &= ~(UART_MCR_RTS);
	if ((ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && !(new_digi.digi_flags & DIGI_RTS_TOGGLE))
		ch->ch_mostat |= (UART_MCR_RTS);

	/*
	 * Handle transistions to and from DTR Toggle.
	 */
	if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && (new_digi.digi_flags & DIGI_DTR_TOGGLE))
		ch->ch_mostat &= ~(UART_MCR_DTR);
	if ((ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && !(new_digi.digi_flags & DIGI_DTR_TOGGLE))
		ch->ch_mostat |= (UART_MCR_DTR);

	memcpy(&ch->ch_digi, &new_digi, sizeof(new_digi));

	if (ch->ch_digi.digi_maxcps < 1)
		ch->ch_digi.digi_maxcps = 1;

	if (ch->ch_digi.digi_maxcps > 10000)
		ch->ch_digi.digi_maxcps = 10000;

	if (ch->ch_digi.digi_bufsize < 10)
		ch->ch_digi.digi_bufsize = 10;

	if (ch->ch_digi.digi_maxchar < 1)
		ch->ch_digi.digi_maxchar = 1;

	if (ch->ch_digi.digi_maxchar > ch->ch_digi.digi_bufsize)
		ch->ch_digi.digi_maxchar = ch->ch_digi.digi_bufsize;

	if (ch->ch_digi.digi_onlen > DIGI_PLEN)
		ch->ch_digi.digi_onlen = DIGI_PLEN;

	if (ch->ch_digi.digi_offlen > DIGI_PLEN)
		ch->ch_digi.digi_offlen = DIGI_PLEN;

	ch->ch_bd->bd_ops->param(tty);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("DIGI_SETA finish\n"));

	return 0;
}


/*
 * dgnc_set_termios()
 */
static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
{
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	unsigned long lock_flags;

	if (!tty || tty->magic != TTY_MAGIC)
		return;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return;

	DGNC_LOCK(ch->ch_lock, lock_flags);

	ch->ch_c_cflag   = tty->termios.c_cflag;
	ch->ch_c_iflag   = tty->termios.c_iflag;
	ch->ch_c_oflag   = tty->termios.c_oflag;
	ch->ch_c_lflag   = tty->termios.c_lflag;
	ch->ch_startc = tty->termios.c_cc[VSTART];
	ch->ch_stopc  = tty->termios.c_cc[VSTOP];

	ch->ch_bd->bd_ops->param(tty);
	dgnc_carrier(ch);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);
}


static void dgnc_tty_throttle(struct tty_struct *tty)
{
	struct channel_t *ch;
	struct un_t *un;
	ulong   lock_flags = 0;

	if (!tty || tty->magic != TTY_MAGIC)
		return;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	DPR_IOCTL(("dgnc_tty_throttle start\n"));

	DGNC_LOCK(ch->ch_lock, lock_flags);

	ch->ch_flags |= (CH_FORCED_STOPI);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("dgnc_tty_throttle finish\n"));
}


static void dgnc_tty_unthrottle(struct tty_struct *tty)
{
	struct channel_t *ch;
	struct un_t *un;
	ulong   lock_flags;

	if (!tty || tty->magic != TTY_MAGIC)
		return;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	DPR_IOCTL(("dgnc_tty_unthrottle start\n"));

	DGNC_LOCK(ch->ch_lock, lock_flags);

	ch->ch_flags &= ~(CH_FORCED_STOPI);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("dgnc_tty_unthrottle finish\n"));
}


static void dgnc_tty_start(struct tty_struct *tty)
{
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	ulong lock_flags;

	if (!tty || tty->magic != TTY_MAGIC)
		return;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return;

	DPR_IOCTL(("dgcn_tty_start start\n"));

	DGNC_LOCK(ch->ch_lock, lock_flags);

	ch->ch_flags &= ~(CH_FORCED_STOP);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("dgnc_tty_start finish\n"));
}


static void dgnc_tty_stop(struct tty_struct *tty)
{
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	ulong lock_flags;

	if (!tty || tty->magic != TTY_MAGIC)
		return;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return;

	DPR_IOCTL(("dgnc_tty_stop start\n"));

	DGNC_LOCK(ch->ch_lock, lock_flags);

	ch->ch_flags |= (CH_FORCED_STOP);

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("dgnc_tty_stop finish\n"));
}


/*
 * dgnc_tty_flush_chars()
 *
 * Flush the cook buffer
 *
 * Note to self, and any other poor souls who venture here:
 *
 * flush in this case DOES NOT mean dispose of the data.
 * instead, it means "stop buffering and send it if you
 * haven't already."  Just guess how I figured that out...   SRW 2-Jun-98
 *
 * It is also always called in interrupt context - JAR 8-Sept-99
 */
static void dgnc_tty_flush_chars(struct tty_struct *tty)
{
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	ulong lock_flags;

	if (!tty || tty->magic != TTY_MAGIC)
		return;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return;

	DPR_IOCTL(("dgnc_tty_flush_chars start\n"));

	DGNC_LOCK(ch->ch_lock, lock_flags);

	/* Do something maybe here */

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("dgnc_tty_flush_chars finish\n"));
}



/*
 * dgnc_tty_flush_buffer()
 *
 * Flush Tx buffer (make in == out)
 */
static void dgnc_tty_flush_buffer(struct tty_struct *tty)
{
	struct channel_t *ch;
	struct un_t *un;
	ulong lock_flags;

	if (!tty || tty->magic != TTY_MAGIC)
		return;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return;

	DPR_IOCTL(("dgnc_tty_flush_buffer on port: %d start\n", ch->ch_portnum));

	DGNC_LOCK(ch->ch_lock, lock_flags);

	ch->ch_flags &= ~CH_STOP;

	/* Flush our write queue */
	ch->ch_w_head = ch->ch_w_tail;

	/* Flush UARTs transmit FIFO */
	ch->ch_bd->bd_ops->flush_uart_write(ch);

	if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) {
		ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY);
		wake_up_interruptible(&ch->ch_tun.un_flags_wait);
	}
	if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) {
		ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY);
		wake_up_interruptible(&ch->ch_pun.un_flags_wait);
	}

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("dgnc_tty_flush_buffer finish\n"));
}



/*****************************************************************************
 *
 * The IOCTL function and all of its helpers
 *
 *****************************************************************************/

/*
 * dgnc_tty_ioctl()
 *
 * The usual assortment of ioctl's
 */
static int dgnc_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
		unsigned long arg)
{
	struct dgnc_board *bd;
	struct channel_t *ch;
	struct un_t *un;
	int rc;
	ulong lock_flags;
	void __user *uarg = (void __user *) arg;

	if (!tty || tty->magic != TTY_MAGIC)
		return -ENODEV;

	un = tty->driver_data;
	if (!un || un->magic != DGNC_UNIT_MAGIC)
		return -ENODEV;

	ch = un->un_ch;
	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
		return -ENODEV;

	bd = ch->ch_bd;
	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
		return -ENODEV;

	DPR_IOCTL(("dgnc_tty_ioctl start on port %d - cmd %s (%x), arg %lx\n",
		ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));

	DGNC_LOCK(ch->ch_lock, lock_flags);

	if (un->un_open_count <= 0) {
		DPR_BASIC(("dgnc_tty_ioctl - unit not open.\n"));
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return -EIO;
	}

	switch (cmd) {

	/* Here are all the standard ioctl's that we MUST implement */

	case TCSBRK:
		/*
		 * TCSBRK is SVID version: non-zero arg --> no break
		 * this behaviour is exploited by tcdrain().
		 *
		 * According to POSIX.1 spec (7.2.2.1.2) breaks should be
		 * between 0.25 and 0.5 seconds so we'll ask for something
		 * in the middle: 0.375 seconds.
		 */
		rc = tty_check_change(tty);
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		if (rc)
			return rc;

		rc = ch->ch_bd->bd_ops->drain(tty, 0);

		if (rc) {
			DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
			return -EINTR;
		}

		DGNC_LOCK(ch->ch_lock, lock_flags);

		if (((cmd == TCSBRK) && (!arg)) || (cmd == TCSBRKP)) {
			ch->ch_bd->bd_ops->send_break(ch, 250);
		}

		DGNC_UNLOCK(ch->ch_lock, lock_flags);

		DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n",
			ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));

		return 0;


	case TCSBRKP:
		/* support for POSIX tcsendbreak()
		 * According to POSIX.1 spec (7.2.2.1.2) breaks should be
		 * between 0.25 and 0.5 seconds so we'll ask for something
		 * in the middle: 0.375 seconds.
		 */
		rc = tty_check_change(tty);
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		if (rc)
			return rc;

		rc = ch->ch_bd->bd_ops->drain(tty, 0);
		if (rc) {
			DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
			return -EINTR;
		}

		DGNC_LOCK(ch->ch_lock, lock_flags);

		ch->ch_bd->bd_ops->send_break(ch, 250);

		DGNC_UNLOCK(ch->ch_lock, lock_flags);

		DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n",
			ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));

		return 0;

	case TIOCSBRK:
		rc = tty_check_change(tty);
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		if (rc)
			return rc;

		rc = ch->ch_bd->bd_ops->drain(tty, 0);
		if (rc) {
			DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
			return -EINTR;
		}

		DGNC_LOCK(ch->ch_lock, lock_flags);

		ch->ch_bd->bd_ops->send_break(ch, 250);

		DGNC_UNLOCK(ch->ch_lock, lock_flags);

		DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n",
			ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));

		return 0;

	case TIOCCBRK:
		/* Do Nothing */
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return 0;

	case TIOCGSOFTCAR:

		DGNC_UNLOCK(ch->ch_lock, lock_flags);

		rc = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) arg);
		return rc;

	case TIOCSSOFTCAR:

		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		rc = get_user(arg, (unsigned long __user *) arg);
		if (rc)
			return rc;

		DGNC_LOCK(ch->ch_lock, lock_flags);
		tty->termios.c_cflag = ((tty->termios.c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
		ch->ch_bd->bd_ops->param(tty);
		DGNC_UNLOCK(ch->ch_lock, lock_flags);

		return 0;

	case TIOCMGET:
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return dgnc_get_modem_info(ch, uarg);

	case TIOCMBIS:
	case TIOCMBIC:
	case TIOCMSET:
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return dgnc_set_modem_info(tty, cmd, uarg);

		/*
		 * Here are any additional ioctl's that we want to implement
		 */

	case TCFLSH:
		/*
		 * The linux tty driver doesn't have a flush
		 * input routine for the driver, assuming all backed
		 * up data is in the line disc. buffers.  However,
		 * we all know that's not the case.  Here, we
		 * act on the ioctl, but then lie and say we didn't
		 * so the line discipline will process the flush
		 * also.
		 */
		rc = tty_check_change(tty);
		if (rc) {
			DGNC_UNLOCK(ch->ch_lock, lock_flags);
			return rc;
		}

		if ((arg == TCIFLUSH) || (arg == TCIOFLUSH)) {
			ch->ch_r_head = ch->ch_r_tail;
			ch->ch_bd->bd_ops->flush_uart_read(ch);
			/* Force queue flow control to be released, if needed */
			dgnc_check_queue_flow_control(ch);
		}

		if ((arg == TCOFLUSH) || (arg == TCIOFLUSH)) {
			if (!(un->un_type == DGNC_PRINT)) {
				ch->ch_w_head = ch->ch_w_tail;
				ch->ch_bd->bd_ops->flush_uart_write(ch);

				if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) {
					ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY);
					wake_up_interruptible(&ch->ch_tun.un_flags_wait);
				}

				if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) {
					ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY);
					wake_up_interruptible(&ch->ch_pun.un_flags_wait);
				}

			}
		}

		/* pretend we didn't recognize this IOCTL */
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return -ENOIOCTLCMD;
	case TCSETSF:
	case TCSETSW:
		/*
		 * The linux tty driver doesn't have a flush
		 * input routine for the driver, assuming all backed
		 * up data is in the line disc. buffers.  However,
		 * we all know that's not the case.  Here, we
		 * act on the ioctl, but then lie and say we didn't
		 * so the line discipline will process the flush
		 * also.
		 */
		if (cmd == TCSETSF) {
			/* flush rx */
			ch->ch_flags &= ~CH_STOP;
			ch->ch_r_head = ch->ch_r_tail;
			ch->ch_bd->bd_ops->flush_uart_read(ch);
			/* Force queue flow control to be released, if needed */
			dgnc_check_queue_flow_control(ch);
		}

		/* now wait for all the output to drain */
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		rc = ch->ch_bd->bd_ops->drain(tty, 0);
		if (rc) {
			DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d\n", rc));
			return -EINTR;
		}

		DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n",
			ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));

		/* pretend we didn't recognize this */
		return -ENOIOCTLCMD;

	case TCSETAW:

		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		rc = ch->ch_bd->bd_ops->drain(tty, 0);
		if (rc) {
			DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
			return -EINTR;
		}

		/* pretend we didn't recognize this */
		return -ENOIOCTLCMD;

	case TCXONC:
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		/* Make the ld do it */
		return -ENOIOCTLCMD;

	case DIGI_GETA:
		/* get information for ditty */
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return dgnc_tty_digigeta(tty, uarg);

	case DIGI_SETAW:
	case DIGI_SETAF:

		/* set information for ditty */
		if (cmd == (DIGI_SETAW)) {

			DGNC_UNLOCK(ch->ch_lock, lock_flags);
			rc = ch->ch_bd->bd_ops->drain(tty, 0);
			if (rc) {
				DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
				return -EINTR;
			}
			DGNC_LOCK(ch->ch_lock, lock_flags);
		} else {
			tty_ldisc_flush(tty);
		}
		/* fall thru */

	case DIGI_SETA:
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return dgnc_tty_digiseta(tty, uarg);

	case DIGI_LOOPBACK:
		{
			uint loopback = 0;
			/* Let go of locks when accessing user space, could sleep */
			DGNC_UNLOCK(ch->ch_lock, lock_flags);
			rc = get_user(loopback, (unsigned int __user *) arg);
			if (rc)
				return rc;
			DGNC_LOCK(ch->ch_lock, lock_flags);

			/* Enable/disable internal loopback for this port */
			if (loopback)
				ch->ch_flags |= CH_LOOPBACK;
			else
				ch->ch_flags &= ~(CH_LOOPBACK);

			ch->ch_bd->bd_ops->param(tty);
			DGNC_UNLOCK(ch->ch_lock, lock_flags);
			return 0;
		}

	case DIGI_GETCUSTOMBAUD:
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		rc = put_user(ch->ch_custom_speed, (unsigned int __user *) arg);
		return rc;

	case DIGI_SETCUSTOMBAUD:
	{
		int new_rate;
		/* Let go of locks when accessing user space, could sleep */
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		rc = get_user(new_rate, (int __user *) arg);
		if (rc)
			return rc;
		DGNC_LOCK(ch->ch_lock, lock_flags);
		dgnc_set_custom_speed(ch, new_rate);
		ch->ch_bd->bd_ops->param(tty);
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return 0;
	}

	/*
	 * This ioctl allows insertion of a character into the front
	 * of any pending data to be transmitted.
	 *
	 * This ioctl is to satify the "Send Character Immediate"
	 * call that the RealPort protocol spec requires.
	 */
	case DIGI_REALPORT_SENDIMMEDIATE:
	{
		unsigned char c;
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		rc = get_user(c, (unsigned char __user *) arg);
		if (rc)
			return rc;
		DGNC_LOCK(ch->ch_lock, lock_flags);
		ch->ch_bd->bd_ops->send_immediate_char(ch, c);
		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		return 0;
	}

	/*
	 * This ioctl returns all the current counts for the port.
	 *
	 * This ioctl is to satify the "Line Error Counters"
	 * call that the RealPort protocol spec requires.
	 */
	case DIGI_REALPORT_GETCOUNTERS:
	{
		struct digi_getcounter buf;

		buf.norun = ch->ch_err_overrun;
		buf.noflow = 0;  	/* The driver doesn't keep this stat */
		buf.nframe = ch->ch_err_frame;
		buf.nparity = ch->ch_err_parity;
		buf.nbreak = ch->ch_err_break;
		buf.rbytes = ch->ch_rxcount;
		buf.tbytes = ch->ch_txcount;

		DGNC_UNLOCK(ch->ch_lock, lock_flags);

		if (copy_to_user(uarg, &buf, sizeof(buf)))
			return -EFAULT;

		return 0;
	}

	/*
	 * This ioctl returns all current events.
	 *
	 * This ioctl is to satify the "Event Reporting"
	 * call that the RealPort protocol spec requires.
	 */
	case DIGI_REALPORT_GETEVENTS:
	{
		unsigned int events = 0;

		/* NOTE: MORE EVENTS NEEDS TO BE ADDED HERE */
		if (ch->ch_flags & CH_BREAK_SENDING)
			events |= EV_TXB;
		if ((ch->ch_flags & CH_STOP) || (ch->ch_flags & CH_FORCED_STOP))
			events |= (EV_OPU | EV_OPS);

		if ((ch->ch_flags & CH_STOPI) || (ch->ch_flags & CH_FORCED_STOPI)) {
			events |= (EV_IPU | EV_IPS);
		}

		DGNC_UNLOCK(ch->ch_lock, lock_flags);
		rc = put_user(events, (unsigned int __user *) arg);
		return rc;
	}

	/*
	 * This ioctl returns TOUT and TIN counters based
	 * upon the values passed in by the RealPort Server.
	 * It also passes back whether the UART Transmitter is
	 * empty as well.
	 */
	case DIGI_REALPORT_GETBUFFERS:
	{
		struct digi_getbuffer buf;
		int tdist;
		int count;

		DGNC_UNLOCK(ch->ch_lock, lock_flags);

		/*
		 * Get data from user first.
		 */
		if (copy_from_user(&buf, uarg, sizeof(buf)))
			return -EFAULT;

		DGNC_LOCK(ch->ch_lock, lock_flags);

		/*
		 * Figure out how much data is in our RX and TX queues.
		 */
		buf.rxbuf = (ch->ch_r_head - ch->ch_r_tail) & RQUEUEMASK;
		buf.txbuf = (ch->ch_w_head - ch->ch_w_tail) & WQUEUEMASK;

		/*
		 * Is the UART empty? Add that value to whats in our TX queue.
		 */
		count = buf.txbuf + ch->ch_bd->bd_ops->get_uart_bytes_left(ch);

		/*
		 * Figure out how much data the RealPort Server believes should
		 * be in our TX queue.
		 */
		tdist = (buf.tIn - buf.tOut) & 0xffff;

		/*
		 * If we have more data than the RealPort Server believes we
		 * should have, reduce our count to its amount.
		 *
		 * This count difference CAN happen because the Linux LD can
		 * insert more characters into our queue for OPOST processing
		 * that the RealPort Server doesn't know about.
		 */
		if (buf.txbuf > tdist)
			buf.txbuf = tdist;

		/*
		 * Report whether our queue and UART TX are completely empty.
		 */
		if (count)
			buf.txdone = 0;
		else
			buf.txdone = 1;

		DGNC_UNLOCK(ch->ch_lock, lock_flags);

		if (copy_to_user(uarg, &buf, sizeof(buf)))
			return -EFAULT;

		return 0;
	}
	default:
		DGNC_UNLOCK(ch->ch_lock, lock_flags);

		DPR_IOCTL(("dgnc_tty_ioctl - in default\n"));
		DPR_IOCTL(("dgnc_tty_ioctl end - cmd %s (%x), arg %lx\n",
			dgnc_ioctl_name(cmd), cmd, arg));

		return -ENOIOCTLCMD;
	}

	DGNC_UNLOCK(ch->ch_lock, lock_flags);

	DPR_IOCTL(("dgnc_tty_ioctl end - cmd %s (%x), arg %lx\n",
		dgnc_ioctl_name(cmd), cmd, arg));

	return 0;
}