/***************************************************************************** * * exser.c XR17C158 Family Multiport Serial Driver with mods * to work with LCI hardware boards. * * Copyright (C) 2002 Lantronix (www.lantronix.com) by Anthony Petillo * Copyright (C) 2001 Exar Corp. (www.exar.com) * Copyright (C) 1991, 1992, Linus Torvalds * Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, * 1998, 1999, Theodore Ts'o * * Modified by Anthony Petillo to make use of the auto-flow control * capabilities of the Exar17C1788, and added buffer device, listen * device, and /proc entries to read the serial data buffers. * * This code is loosely based on the Linux serial driver, written by * Linus Torvalds, Theodore T'so and others. * * 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 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; 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. * * XR17C158 Family Multiport Serial Driver * for : LINUX (Tested with RedHat 7.0 and 7.1) * date : June, 2001 * version : 1.01 * *----------------------------------------------------------------------- * * Anthony Petillo: April-29-2002 * * The exser driver is a "tty" driver. It works with tty driver interface. * The behavior is slightly different from a driver that is directly * registered with the kernel. One of the main differences is that if * the open fails then close is still called. This is by design so * that you can perform any clean-up if the open failed and the * clean-up code is better placed in the "close" function. * * There are three device drivers in this module. They are connected * via the exser's interrupt routine. The ISR basically writes the * incomming bytes to up to three buffers. One: the history buffer * (a circular queue), two: the listen buffer (to support listen * clients) and three: the buffer of an application who opened the * device. The lcibuf device's function is to provide a means of * reading the history buffer for a serial port. The listen device * allows an third party to see the data that is comming into the * serial port. * * In addition to the three devices, there are also proc entries * defined. The provide an alternative way to access the history * buffer. This alternative method provides the characteristics * to allow applications to see the block of data as a "regular file". * This allows programs such as ftp to be used to transfer the file * off the box. * * This version of the driver has been tested with 2.2.12 and * 2.2.19 versions of the linux kernel. * * Contents of the make file (to build module): CC=gcc -c OPTS=-DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -m486 all: .depend exser.o clean: rm -f exser.o depend .depend dep: $(CC) $(OPTS) -M *.c > $@ exser.o: exser.c $(CC) $(OPTS) exser.c * meaning of parameters: * * -c compile only (produce an object file only) * -DMODULE define for compilation the macro MODULE used to include module related functions * -D__KERNEL__ define for compilation the macro __KERNEL__ header files look for this * -Wall enable all warnings * -Wstrict-prototypes force all prototypes to exist * -O2 optimize level 2 * -fomit-frame-pointer avoid functions to set, save, and restore frame pointers * -m486 target is 486 or higher * -M output dependencies suitable for make * */ /*--------------------------------------------------------------------*/ /* added by Anthony Petillo */ #define LW_DEBUG // when defined allows debug messages // to be compiled in #define LW_DEBUG_TIMESTAMP "" //" : "__DATE__" " __TIME__ // add date & time to the // version for debug builds, // change to "" to remove timestamp #define LCI // mods which exclude PCI or ISA calls #define LCI_DEVICE_BUFFERS // create buffers and IOCTLS to setup properties #define LCI_BUFFER_SIZE (256L*1024L) // data received by the uart #define LCI_LISTEN_BUFFER_SIZE (2*1024) // another copy of received data, // but smaller size #define LW_PROC_BUFFER_ROOT_NAME "lwbuf" // ==> /proc/lwbuf //----------------------------------------------------------------- // bits in enabledebug can be used to filter messages at run time // using DEBUG_PRINT macro #define DEBUG_CLOSE 0x00000001 // debug messages in exser_close #define DEBUG_OPEN 0x00000002 // debug messages in exser_open #define DEBUG_PROC 0x00000004 // debug messages in proc related functions #define DEBUG_ID 0x00000008 // debug messages in exser_get_LCI_conf #define DEBUG_LCIBUF_R 0x00000010 // debug messages in lcibuf_read #define DEBUG_LCIBUF_OPEN 0x00000020 // debug messages in lcibuf_open #define DEBUG_LCIBUF_CLOSE 0x00000040 // debug messages in lcibuf_close #define DEBUG_ISR 0x00000080 // debug messages in exser_interrupt (isr) #define DEBUG_SPEED 0x00000100 // debug messages in exser_change_speed #define DEBUG_TRACE 0x00000200 // debug messages for tracing the driver #define DEBUG_TERMIOS 0x00000400 // debug messages for exser_set_termios #define DEBUG_COMM_ERR 0x00000800 // debug messages for exser_receive_chars #define DEBUG_ANY 0xffffffff // enable any debug messages // define prefixes for the printk calls #define ISR_PREFIX "" // no level for the printk #define TRACE_PREFIX KERN_DEBUG"exser: " #define DEFAULT_PREFIX KERN_DEBUG"exser: " // most calls use this static int debugenable=0; #ifdef LW_DEBUG #define DEBUG_PRINT(d, fmt, args...) \ do { \ if (debugenable & d) {\ printk( KERN_DEBUG fmt, ## args);\ }\ } while (0) #else #define DEBUG_PRINT(d, fmt, args...) do { ; } while (0) #endif // for messages we want to appear all the time // i.e. during initialization, etc. #define PRINT(fmt, args...) printk("LW: " fmt, ## args) //----------------------------------------------------------------- #ifdef CONFIG_PCI // we don't want PCI functions involved #undef CONFIG_PCI #endif #define TRUE 1 #define FALSE 0 /*--------------------------------------------------------------------*/ #ifdef MODVERSIONS #ifndef MODULE #define MODULE #endif #endif #ifdef MODULE #include #ifdef MODVERSIONS #include #endif #include #else #define MOD_INC_USE_COUNT #define MOD_DEC_USE_COUNT #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // printk() #include // everything... #include // size_t #include // proc file system api #include #include #include #include #include #include #define VERSION_CODE(ver,rel,seq) ((ver << 16) | (rel << 8) | seq) #ifdef CONFIG_PCI #include #endif #include "lcibuf.h" // lci buffer device #include // lci product information data structure //------------------------------------------------------------------------ #define __EXSER_VERSION "2.8" #ifdef LW_DEBUG_TIMESTAMP #define EXSER_VERSION __EXSER_VERSION LW_DEBUG_TIMESTAMP #else #define EXSER_VERSION __EXSER_VERSION #endif //------------------------------------------------------------------------ #define EXSERMAJOR 30 #define EXSERCUMAJOR 35 #ifdef CONFIG_PCI #if (LINUX_VERSION_CODE < VERSION_CODE(2,1,0)) #include #endif #include #endif /* ENABLE_PCI */ #if (LINUX_VERSION_CODE < VERSION_CODE(2,1,0)) #define copy_from_user memcpy_fromfs #define copy_to_user memcpy_tofs #define put_to_user(arg1, arg2) put_fs_long(arg1, (unsigned long *)arg2) #define get_from_user(arg1, arg2) arg1 = get_fs_long((unsigned long *)arg2) #define schedule_timeout(x) {current->timeout = jiffies + (x); schedule();} #define signal_pending(x) ((x)->signal & ~(x)->blocked) #else #include #define put_to_user(arg1, arg2) put_user(arg1, (unsigned long *)arg2) #define get_from_user(arg1, arg2) get_user(arg1, (unsigned int *)arg2) #endif #define EXSER_EVENT_TXLOW 1 #define EXSER_EVENT_HANGUP 2 #define SERIAL_DO_RESTART // maximums #define EXSER_PORTS 160 /* Max. ports */ #define EXSER_PORTS_PER_BOARD 8 /* Max. ports per board */ #define EXSER_ISR_PASS_LIMIT 256 #define EXSER_CHIPS 20 /* Max. boards (chips) */ #define EXSER_ERR_IOADDR -1 #define EXSER_ERR_IRQ -2 #define EXSER_ERR_IRQ_CONFLIT -3 #define EXSER_ERR_VECTOR -4 #define SERIAL_TYPE_NORMAL 1 #define SERIAL_TYPE_CALLOUT 2 #define WAKEUP_CHARS 256 #define UART_XMIT_FIFO_SIZE 64 #define UART_MCR_AFE 0x20 #define UART_LSR_SPECIAL 0x1E #define PORTNO(x) (MINOR((x)->device) - (x)->driver.minor_start) #define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|IXOFF|IXON)) #define IRQ_T(info) ((info->flags & ASYNC_SHARE_IRQ) ? SA_SHIRQ : SA_INTERRUPT) #define _INLINE_ inline #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif static type_lci_device lci_devices[EXSER_PORTS]; static type_lci_device lci_listen_devices[EXSER_PORTS]; /* * Define the Exar PCI vendor and device IDs. */ #ifndef PCI_VENDOR_ID_EXAR #define PCI_VENDOR_ID_EXAR 0x13a8 #endif #ifndef PCI_DEVICE_ID_C158 #define PCI_DEVICE_ID_C158 0x158 #endif #ifndef PCI_DEVICE_ID_C154 #define PCI_DEVICE_ID_C154 0x154 /* future */ #endif #ifndef PCI_DEVICE_ID_C152 #define PCI_DEVICE_ID_C152 0x152 /* future */ #endif #ifndef PCI_DEVICE_ID_C104 #define PCI_DEVICE_ID_C104 0x1040 #endif #define C158_ASIC_ID 1 #define C104_ASIC_ID 2 #define CI104J_ASIC_ID 5 enum { EXSER_BOARD_C158_ISA = 1, EXSER_BOARD_C104_ISA, EXSER_BOARD_CI104J, EXSER_BOARD_C158_PCI, EXSER_BOARD_C154_PCI, EXSER_BOARD_C152_PCI, EXSER_BOARD_C104_PCI, }; static char *exser_brdname[] = { "C158 series", "C104 series", "CI-104J series", "C158/PCI series", "C154/PCI series", "C152/PCI series", "C104/PCI series", }; static int exser_numports[] = { 8, 4, 4, 8, 4, 2, 4, }; // hgc - undef this one first to prevent warning #ifdef UART_FCTR #undef UART_FCTR #endif // extended registers used in the Exar 17c1788 #define UART_FCTR 8 #define UART_EFR2 9 #define UART_XOFF1 12 #define UART_XON1 14 #define UART_XOFF2 13 #define UART_XON2 15 #define UART_RXTRIG 11 // write only #define UART_RFCNT 11 // read only #define UART_TXTRIG 10 #define UART_TFCNT 10 #define UART_ISR 2 /* * EXAR ioctls */ #define EXAR 0x400 #define EXAR_GETDATACOUNT (EXAR + 23) #define EXAR_GET_CONF (EXAR + 35) #define EXAR_DIAGNOSE (EXAR + 50) #define EXAR_CHKPORTENABLE (EXAR + 60) #define EXAR_HighSpeedOn (EXAR + 61) #define EXAR_GET_MAJOR (EXAR + 63) #define EXAR_GET_CUMAJOR (EXAR + 64) #define EXAR_GETMSTATUS (EXAR + 65) #define EXAR_READ_REG (EXAR + 66) #define EXAR_WRITE_REG (EXAR + 67) #define READ_IO_PORT (EXAR + 68) #define RESET_MODULE_CNT (EXAR + 69) #define BUFFER_ENABLE (EXAR + 70) #define PARITY_MARK_SPACE (EXAR + 71) #define QUERY_BUFFER_ENABLE (EXAR + 72) #define QUERY_PARITY (EXAR + 73) #define RESET_RDWR_COUNT (EXAR + 74) #define QUERY_RDWR_COUNT (EXAR + 75) #define SET_PORT_TYPE (EXAR + 76) #define QUERY_PORT_TYPE (EXAR + 77) #define SET_HARDWARE_PORT_TYPE (EXAR + 78) #define QUERY_HARDWARE_PORT_TYPE (EXAR + 79) #ifdef CONFIG_PCI typedef struct { unsigned short vendor_id; unsigned short device_id; unsigned short board_type; } exser_pciinfo; static exser_pciinfo exser_pcibrds[] = { {PCI_VENDOR_ID_EXAR,PCI_DEVICE_ID_C158 ,EXSER_BOARD_C158_PCI}, {PCI_VENDOR_ID_EXAR,PCI_DEVICE_ID_C104 ,EXSER_BOARD_C104_PCI}, }; typedef struct _exar_pci_info { unsigned short busNum; unsigned short devNum; } exar_pci_info; #endif // NOTE: use care when changing these i/o addresses // there are other places in the code that rely on theses // exact addresses, in the future this dependence can be // improved so that it can be more dynamic //static int ioaddr[EXSER_CHIPS]={0x1300,0x1100,0x1200,0}; static int ttymajor = EXSERMAJOR; static int calloutmajor = EXSERCUMAJOR; static int verbose = 1; static int disable_lcibuf= 0; // if non-zero then disable the // creation of the lcibufXX devices #ifdef MODULE /* Variables for insmod */ # if (LINUX_VERSION_CODE > VERSION_CODE(2,1,11)) MODULE_AUTHOR("EXAR, modified by Anthony Petillo"); MODULE_DESCRIPTION("EXAR-XR16L788 Device Driver-LCI Buffers by Anthony Petillo"); MODULE_PARM(ttymajor, "i"); MODULE_PARM(calloutmajor, "i"); MODULE_PARM(verbose, "i"); MODULE_PARM(debugenable, "i"); MODULE_PARM(disable_lcibuf, "i"); # endif #endif MODULE struct exser_hwconf { int board_type; // ? int ports; // number of ports on a board? int irq; // interrupt for this chip int vector; // interrupt vector address int vector_mask; // interrupt vector mask register int uart_type; // int ioaddr[EXSER_PORTS_PER_BOARD]; // ioaddresses for each uart int baud_base[EXSER_PORTS_PER_BOARD]; // baud base value for each uart int buffer_flags; // tells which channels are to // start with history buffer enabled int brdid; // ? exar_pci_info pciInfo; }; typedef enum { PORT_TYPE_OFF=0, PORT_TYPE_GETTY=1, PORT_TYPE_NON_GETTY=2, PORT_TYPE_DEVICE=3, PORT_TYPE_LIST_LAST // just a place holder for enum list } type_port; struct exser_struct { int port; /* port 0-31 */ type_port port_type; /* see type_port */ int base; /* port base address */ int irq; /* port using irq no. */ int vector; /* port irq vector */ int vectormask; /* port vector mask */ int rx_trigger; /* Rx fifo trigger level */ int baud_base; /* max. speed */ int flags; /* defined in tty.h */ int type; /* UART type */ int port_hardware_type; /* off=0 dte=1 dce=2 */ int buffer_flag; /* lci device history buffer */ struct tty_struct * tty; int read_status_mask; int ignore_status_mask; int xmit_fifo_size; int custom_divisor; int x_char; /* xon/xoff character */ int close_delay; unsigned short closing_wait; int IER; /* Interrupt Enable Register */ int MCR; /* Modem control register */ unsigned long event; int count; /* # of fd on device */ int blocked_open; /* # of blocked opens */ long session; /* Session of opening process */ long pgrp; /* pgrp of opening process */ unsigned char * xmit_buf; int xmit_head; int xmit_tail; int xmit_cnt; struct tq_struct tqueue; struct termios normal_termios; struct termios callout_termios; struct wait_queue *open_wait; struct wait_queue *close_wait; struct wait_queue *delta_msr_wait; struct async_icount icount; /* kernel counters for the 4 input interrupts */ }; struct exser_log { unsigned int tick; unsigned int rxcnt[EXSER_PORTS]; unsigned int txcnt[EXSER_PORTS]; unsigned int framing_err_cnt[EXSER_PORTS]; unsigned int parity_err_cnt[EXSER_PORTS]; unsigned int overrun_err_cnt[EXSER_PORTS]; unsigned int break_rcv_cnt[EXSER_PORTS]; unsigned int break_xmit_cnt[EXSER_PORTS]; unsigned int rts_cnt[EXSER_PORTS]; // used in throttle/unthrottle unsigned int cts_cnt[EXSER_PORTS]; // need to enable ier bit 6 unsigned int dsr_cnt[EXSER_PORTS]; // need to enable ier bit 7 unsigned int dtr_cnt[EXSER_PORTS]; // we currently do not support DTR/DSR flow control }; struct exser_mstatus{ tcflag_t cflag; int cts; int dsr; int ri; int dcd; }; static struct exser_mstatus GMStatus[EXSER_PORTS]; // NOTE: use care when changing these i/o addresses // there are other places in the code that rely on theses // exact addresses, in the future this dependence can be // improved so that it can be more dynamic // (see get_uart_table for more details) static int exserBoardCAP[EXSER_CHIPS] = { 0x1300,0x1100,0x1200,0 }; struct exser_read_reg { int reg; int regvalue; }; static struct tty_driver exvar_sdriver; // serial - ttyMx static struct tty_driver exvar_cdriver; // callout - cuax static int exvar_refcount; static struct exser_struct exvar_table[EXSER_PORTS]; static struct tty_struct * exvar_tty[EXSER_PORTS+1]; static struct termios * exvar_termios[EXSER_PORTS+1]; static struct termios * exvar_termios_locked[EXSER_PORTS+1]; static struct exser_log exvar_log; static int exvar_diagflag; /* * exvar_tmp_buf is used as a temporary buffer by serial_write. We need * to lock it in case the memcpy_fromfs blocks while swapping in a page, * and some other program tries to do a serial write at the same time. * Since the lock will only come under contention when the system is * swapping and available memory is low, it makes sense to share one * buffer across all the serial ports, since it significantly saves * memory if large numbers of serial ports are open. */ static unsigned char * exvar_tmp_buf = 0; static struct semaphore exvar_tmp_buf_sem = MUTEX; /* * This is used to figure out the divisor speeds and the timeouts */ static int exvar_baud_table[] = { 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600, 0 }; struct exser_hwconf exsercfg[EXSER_CHIPS]; // hyteresis for use with autoflow control #define HYSTERESIS_NONE 0 #define HYSTERESIS_PLUS_MINUS_4 1 #define HYSTERESIS_PLUS_MINUS_6 2 #define HYSTERESIS_PLUS_MINUS_8 3 #define HYSTERESIS_PLUS_MINUS_12 12 #define HYSTERESIS_PLUS_MINUS_16 4 #define HYSTERESIS_PLUS_MINUS_20 13 #define HYSTERESIS_PLUS_MINUS_24 6 #define HYSTERESIS_PLUS_MINUS_28 14 #define HYSTERESIS_PLUS_MINUS_32 7 #define HYSTERESIS_PLUS_MINUS_36 15 #define HYSTERESIS_PLUS_MINUS_40 8 #define HYSTERESIS_PLUS_MINUS_44 9 #define HYSTERESIS_PLUS_MINUS_48 10 #define HYSTERESIS_PLUS_MINUS_52 11 //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= typedef struct tag_uart_hardware_io { unsigned int number_of_uarts; // number of channels in a single chip unsigned int base_io_address; // base address for the chip unsigned int irq_number; // irq for the chip unsigned int buffer_flags; // which of the channels have buffering // enabled by default } type_uart_hardware_io; //================================================ // If the product id table does not exist, then // the following table will be used in its place, // it assumes a 1620 platform. // // 1600 has: // // x86 port irq uart port buffered // --------- ------ ---------- -------- // 1300 5 0-7 all // 1100 10 8-15 all // 1200 7 16-modem pwr only // 17-terminal // 18-pwr mngmt // 19-23- // static unsigned int lci_uart_count = 3; // will contain the number of octal uarts in system // default is three (1620 has only three chips) static unsigned int lci_serial_channels = 0; static type_uart_hardware_io lci_uart_table[20] = { { 8, 0x1300, 5, 0x000000FF }, { 8, 0x1100, 10, 0x000000FF }, { 8, 0x1200, 7, 0x00000004 }, { -1, -1, -1, 0xffffffff } }; /* * static functions: */ #ifdef MODULE int init_module(void); void cleanup_module(void); static int exser_init(void); // If lcibuf and lci_listen devices were to be moved to their own modules // then these statements would need to be renamed init_module and cleanup_module for // each device or the "module_init(init_module);" and "module_exit(cleanup_module);" // can be used. // static int lcibuf_init_module(void); // static int lci_listen_init_module(void); static void lcibuf_cleanup_module(void); static void lci_listen_cleanup_module(void); #else /* function to be called by kernel init() */ int exser_init(void); #endif static int lci_listen_init(void); static int lcibuf_init(void); static int get_uart_table(void); #ifdef LCI static int exser_get_LCI_conf(type_uart_hardware_io *lci_uart_item,struct exser_hwconf *hwconf); #else static int exser_get_ISA_conf(int, struct exser_hwconf *); #endif static void exser_getcfg(int board,struct exser_hwconf *hwconf); static int exser_get_PCI_conf(int ,int ,int ,struct exser_hwconf *, struct pci_dev *); static void exser_do_softint(void *); static int exser_open(struct tty_struct *, struct file *); static void exser_close(struct tty_struct *, struct file *); static int exser_write(struct tty_struct *, int, const unsigned char *, int); static int exser_write_room(struct tty_struct *); static void exser_flush_buffer(struct tty_struct *); static int exser_chars_in_buffer(struct tty_struct *); static void exser_flush_chars(struct tty_struct *); static void exser_put_char(struct tty_struct *, unsigned char); static int exser_ioctl(struct tty_struct *, struct file *, uint, ulong); static int exser_ioctl_special(unsigned int, unsigned long); static void exser_throttle(struct tty_struct *); static void exser_unthrottle(struct tty_struct *); static void exser_set_termios(struct tty_struct *, struct termios *); static void exser_stop(struct tty_struct *); static void exser_start(struct tty_struct *); static void exser_hangup(struct tty_struct *); static void exser_interrupt(int, void *, struct pt_regs *); static _INLINE_ void exser_receive_chars(struct exser_struct *, int *, unsigned char, int); static _INLINE_ void exser_transmit_chars(struct exser_struct *); static _INLINE_ void exser_check_modem_status(struct exser_struct *, int); static int exser_block_til_ready(struct tty_struct *, struct file *, struct exser_struct *); static int exser_startup(struct exser_struct *); static void exser_shutdown(struct exser_struct *); static int exser_change_speed(struct exser_struct *, struct termios *old_termios, unsigned char lci_special); static int exser_get_serial_info(struct exser_struct *, struct serial_struct *); static int exser_set_serial_info(struct exser_struct *, struct serial_struct *); static int exser_get_lsr_info(struct exser_struct *, unsigned int *); static void exser_send_break(struct exser_struct *, int); static int exser_get_modem_info(struct exser_struct *, unsigned int *); static int exser_set_modem_info(struct exser_struct *, unsigned int, unsigned int *); static int exser_read_reg(struct exser_struct * info, struct exser_read_reg *arg); static int exser_write_reg(struct exser_struct * info, struct exser_read_reg *arg); static int startup_device_ports(void); static int exser_lci_startup(struct exser_struct * info); // for the buffer proc entries void lcibuf_proc_remove(void); void lcibuf_proc_create(void); /* * The EXAR C158/C104 serial driver boot-time initialization code! */ #ifdef MODULE int init_module(void) { int ret; if (verbose) printk("Loading exser, lcibuf, listen & proc:...\n"); // initialize the exser, lcibuf, listen, and proc drivers ret = exser_init(); if (verbose) printk("exser, lcibuf, listen, & proc: Done\n"); return (ret); } void cleanup_module(void) { int i,err = 0; if (verbose) printk("XR-16L788 start unloading module ...\n"); if ((err |= tty_unregister_driver(&exvar_cdriver))) printk("Couldn't unregister EXAR callout driver\n"); if ((err |= tty_unregister_driver(&exvar_sdriver))) printk("Couldn't unregister EXAR serial driver\n"); printk("EXAR: uninstalling proc buffer entries...\n"); lcibuf_proc_remove(); for(i=0; iports; i++, n++, info++ ) { info->port = n; info->base = hwconf->ioaddr[i]; info->irq = hwconf->irq; info->vector = hwconf->vector; info->vectormask = hwconf->vector_mask; info->rx_trigger = 14; info->baud_base = hwconf->baud_base[i]; info->buffer_flag = (hwconf->buffer_flags >> i) & 0x1; info->flags = ASYNC_SHARE_IRQ; info->type = hwconf->uart_type; if ( (info->type == PORT_16450) || (info->type == PORT_8250) ) info->xmit_fifo_size = 1; else info->xmit_fifo_size = UART_XMIT_FIFO_SIZE; info->custom_divisor = hwconf->baud_base[i] * 16; info->close_delay = 5*HZ/10; info->closing_wait = 4*HZ; // 1.14 driver used: 30*HZ; afp-2002-03-11 info->tqueue.routine = exser_do_softint; info->tqueue.data = info; info->callout_termios = exvar_cdriver.init_termios; info->normal_termios = exvar_sdriver.init_termios; } /* * Allocate the IRQ if necessary */ save_flags(flags); n = board*EXSER_PORTS_PER_BOARD; info = &exvar_table[n]; cli(); retval = request_irq(hwconf->irq, exser_interrupt, IRQ_T(info), "exser", info); if ( retval ) { restore_flags(flags); printk("Board %d: %s", board, exser_brdname[hwconf->board_type-1]); printk(" Request irq fail,IRQ (%d) may be conflit with another device.\n",info->irq); return(retval); } restore_flags(flags); /* check and reserve io ports for the driver */ retval = check_region(exserBoardCAP[board], 0x100); if (retval<0) { printk("<1>*** reserve io ports failed for region 0x%x to 0x%x.\n", exserBoardCAP[board], exserBoardCAP[board]+0xff); return(retval); } request_region(exserBoardCAP[board], 0x100, "exser"); printk("reserved io space 0x%x to 0x%x\n", exserBoardCAP[board], exserBoardCAP[board]+0xff); return 0; } static void exser_getcfg(int board,struct exser_hwconf *hwconf) { exsercfg[board] = *hwconf; } // not used for the lw products static int exser_get_PCI_conf(int busnum,int devnum,int board_type,struct exser_hwconf *hwconf, struct pci_dev *pcidev) { int i; unsigned int val,ioaddress; hwconf->board_type = board_type; hwconf->ports = exser_numports[board_type-1]; /* pcibios_read_config_dword(busnum,devnum,PCI_BASE_ADDRESS_2,&val); if (val == 0xffffffff) return (EXSER_ERR_IOADDR); else ioaddress = val & 0xffffffc; */ ioaddress = (unsigned int)ioremap( (pcidev->base_address[0] & PCI_BASE_ADDRESS_MEM_MASK), 0x1000); for (i = 0; i < hwconf->ports; i++) hwconf->ioaddr[i] = ioaddress + 0x200 * i; /* pcibios_read_config_dword(busnum,devnum,PCI_BASE_ADDRESS_3,&val); if (val == 0xffffffff) return (EXSER_ERR_VECTOR); else ioaddress = val & 0xffffffc; */ hwconf->vector = ioaddress + 0x80; pcibios_read_config_dword(busnum,devnum,PCI_INTERRUPT_LINE,&val); if (val == 0xffffffff) return (EXSER_ERR_IRQ); else hwconf->irq = val & 0xff; hwconf->uart_type = PORT_16550A; hwconf->vector_mask = 0; for (i = 0; i < hwconf->ports; i++) { hwconf->vector_mask |= (1<baud_base[i] = 921600; } return(0); } #ifdef MODULE static #endif int exser_init(void) { int i, m, retval, b; int n, index; int ret1, ret2; unsigned char busnum, devnum; struct exser_hwconf hwconf; struct pci_dev *pcidev = NULL; printk("EXAR-XR16L788 Device Driver V%s\n",EXSER_VERSION); DEBUG_PRINT(DEBUG_ANY, DEFAULT_PREFIX" debug version: %s\n", __DATE__ " / " __TIME__); #ifdef LCI get_uart_table(); // locate lci product specific information // describing the characteristics of the product // includes information about the uart chips // (uses default data if information is not present) #endif /* Initialize the tty_driver structure */ memset(&exvar_sdriver, 0, sizeof(struct tty_driver)); exvar_sdriver.magic = TTY_DRIVER_MAGIC; exvar_sdriver.name = "ttyM"; exvar_sdriver.major = ttymajor; exvar_sdriver.minor_start = 0; exvar_sdriver.num = EXSER_PORTS + 1; exvar_sdriver.type = TTY_DRIVER_TYPE_SERIAL; exvar_sdriver.subtype = SERIAL_TYPE_NORMAL; exvar_sdriver.init_termios = tty_std_termios; exvar_sdriver.init_termios.c_iflag = IXON|IXOFF; // ICRNL | IXON; exvar_sdriver.init_termios.c_cflag = B9600|CS8|CLOCAL|HUPCL|CREAD; exvar_sdriver.init_termios.c_lflag = IEXTEN; // ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE; exvar_sdriver.flags = TTY_DRIVER_REAL_RAW; exvar_sdriver.refcount = &exvar_refcount; exvar_sdriver.table = exvar_tty; exvar_sdriver.termios = exvar_termios; exvar_sdriver.termios_locked = exvar_termios_locked; exvar_sdriver.open = exser_open; exvar_sdriver.close = exser_close; exvar_sdriver.write = exser_write; exvar_sdriver.put_char = exser_put_char; exvar_sdriver.flush_chars = exser_flush_chars; exvar_sdriver.write_room = exser_write_room; exvar_sdriver.chars_in_buffer = exser_chars_in_buffer; exvar_sdriver.flush_buffer = exser_flush_buffer; exvar_sdriver.ioctl = exser_ioctl; exvar_sdriver.throttle = exser_throttle; exvar_sdriver.unthrottle = exser_unthrottle; exvar_sdriver.set_termios = exser_set_termios; exvar_sdriver.stop = exser_stop; exvar_sdriver.start = exser_start; exvar_sdriver.hangup = exser_hangup; /* * The callout device is just like normal device except for * major number and the subtype code. */ exvar_cdriver = exvar_sdriver; exvar_cdriver.name = "cum"; exvar_cdriver.major = calloutmajor; exvar_cdriver.subtype = SERIAL_TYPE_CALLOUT; exvar_diagflag = 0; memset(exvar_table, 0, EXSER_PORTS * sizeof(struct exser_struct)); memset(&exvar_log, 0, sizeof(struct exser_log)); m = 0; /* Start to finding ISA boards here */ for ( b=0; b> 3); index++; if ( m >= EXSER_CHIPS) { printk("Too many EXAR multiport serial boards find (maximum %d),board not configured\n",EXSER_CHIPS); } else { pcidev = pci_find_device( exser_pcibrds[b].vendor_id, exser_pcibrds[b].device_id, pcidev); hwconf.brdid = index; retval = exser_get_PCI_conf(busnum,devnum, exser_pcibrds[b].board_type,&hwconf, pcidev); if (retval < 0) { if (retval == EXSER_ERR_IRQ) printk("Invalid interrupt number,board not configured\n"); else if (retval == EXSER_ERR_IRQ_CONFLIT) printk("Invalid interrupt number,board not configured\n"); else if (retval == EXSER_ERR_VECTOR) printk("Invalid interrupt vector,board not configured\n"); else if (retval == EXSER_ERR_IOADDR) printk("Invalid I/O address,board not configured\n"); continue; } if(exser_initbrd(m,&hwconf)<0) continue; exser_getcfg(m,&hwconf); m++; } } } #endif for(i=m; iport_type = PORT_TYPE_DEVICE; } // modem channel - mgetty info = exvar_table + 16; info->port_type = PORT_TYPE_GETTY; // terminal channel - getty info = exvar_table + 17; info->port_type = PORT_TYPE_GETTY; // startup the power management port as well // it behaves like a device port, it's history // can also be viewed info = exvar_table + 18; info->port_type = PORT_TYPE_DEVICE; */ //--------------------------------------- // build device and listen buffers to // the PORT_TYPE_DEVICE ports retval = lcibuf_init(); if (retval) return retval; retval = lci_listen_init(); if (retval) return retval; //--------------------------------------- ret1 = 0; ret2 = 0; if ( !(ret1=tty_register_driver(&exvar_sdriver)) ) { if ( !(ret2=tty_register_driver(&exvar_cdriver)) ) { retval = startup_device_ports(); } else { tty_unregister_driver(&exvar_sdriver); printk("Couldn't install EXAR family callout driver !\n"); } } else printk("Couldn't install EXAR family driver !\n"); if(ret1 || ret2) { for(i=0; iport_type) { retval = exser_startup(info); if ( retval ) { return(retval); } exser_change_speed(info, 0, TRUE); info->IER &= ~UART_IER_MSI; info->MCR &= ~UART_MCR_AFE; outb(info->MCR, info->base + UART_MCR); outb(info->IER, info->base + UART_IER); } else { // disable all others info->IER = 0; outb(info->IER, info->base + UART_IER); } } return 0; } /* */ static void exser_do_softint(void *private_) { struct exser_struct * info = (struct exser_struct *)private_; struct tty_struct * tty; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" softint\n"); tty = info->tty; if ( !tty ) return; #ifdef __SMP__ lock_kernel(); #endif #if (LINUX_VERSION_CODE < VERSION_CODE(2,1,0)) if ( clear_bit(EXSER_EVENT_TXLOW, &info->event) ) { if ( (tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup ) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait); } if ( clear_bit(EXSER_EVENT_HANGUP, &info->event) ) { tty_hangup(tty); } #else if ( test_and_clear_bit(EXSER_EVENT_TXLOW, &info->event) ) { if ( (tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup ) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait); } if ( test_and_clear_bit(EXSER_EVENT_HANGUP, &info->event) ) { tty_hangup(tty); } #endif #ifdef __SMP__ unlock_kernel(); #endif } /* * This routine is called whenever a serial port is opened. It * enables interrupts for a serial port, linking in its async structure into * the IRQ chain. It also performs the serial-specific * initialization for the tty structure. * * Note: For character devices directly registered with the kernel, * the open returns a negative value to signify failure. Returning * a negative value below does not guarantee that the close * function will not be called. The tty layer above makes the * determination based on the driver_data tty_struct item. * */ static int exser_open(struct tty_struct * tty, struct file * filp) { struct exser_struct * info; int retval, line; unsigned long page; // if driver returns a negative number, then // make sure the the MOD_DEC_USE_COUNT is // called before returning the negative value MOD_INC_USE_COUNT; tty->driver_data = NULL; DEBUG_PRINT(DEBUG_OPEN, DEFAULT_PREFIX" open\n"); line = PORTNO(tty); if ( line == EXSER_PORTS ) return(0); // this is okay, this is a special minor number // which allows for special control over the driver // invalid minor number - shouldn't happen if ( (line < 0) || (line > EXSER_PORTS) ) { MOD_DEC_USE_COUNT; return(-ENODEV); } // make sure that the base io address is not 0 info = exvar_table + line; if ( !info->base ) { MOD_DEC_USE_COUNT; return(-ENODEV); } info->count++; tty->driver_data = info; info->tty = tty; // special check to make sure // only one application opens the buffer // for read/write DEBUG_PRINT(DEBUG_OPEN, DEFAULT_PREFIX" open - f_flags:0x%x\n", filp->f_flags & O_ACCMODE); if ((filp->f_flags & O_ACCMODE) == O_WRONLY || (filp->f_flags & O_ACCMODE) == O_RDWR) { if (PORT_TYPE_DEVICE == info->port_type) { // only allow one user in as RDWR or WRONLY DEBUG_PRINT(DEBUG_OPEN, DEFAULT_PREFIX" open - rdwr_count = %d\n", lci_devices[line].rdwr_count); lci_devices[line].rdwr_count++; if (lci_devices[line].rdwr_count > 1) { /*lci_devices[line].rdwr_count--; */ /* MOD_DEC_USE_COUNT; */ return -ENODEV; } } } // allocate a buffer if ( !exvar_tmp_buf ) { page = get_free_page(GFP_KERNEL); if ( !page ) { /* MOD_DEC_USE_COUNT; */ return(-ENOMEM); } if ( exvar_tmp_buf ) free_page(page); else exvar_tmp_buf = (unsigned char *)page; } /* * Start up serial port */ if (PORT_TYPE_DEVICE != info->port_type) retval = exser_startup(info); else retval = exser_lci_startup(info); if ( retval ) { /* MOD_DEC_USE_COUNT;*/ return(retval); } retval = exser_block_til_ready(tty, filp, info); if ( retval ) { /* MOD_DEC_USE_COUNT;*/ return(retval); } // we want special behavior for our buffered ports if ( (info->count == 1) && (info->flags & ASYNC_SPLIT_TERMIOS) ) { if ( tty->driver.subtype == SERIAL_TYPE_NORMAL ) *tty->termios = info->normal_termios; else *tty->termios = info->callout_termios; exser_change_speed(info, 0, FALSE); } info->session = current->session; info->pgrp = current->pgrp; return(0); } static char *tty_driver_name(struct tty_struct *tty) { static char buf[64]="Invalid tty"; if (tty) { sprintf(buf, "%s%d", tty->driver.name, PORTNO(tty)); } else { strncpy(buf, "NULL tty", sizeof(buf)-1); buf[sizeof(buf)-1] = 0; } return buf; } //============================================================== // Reset the auto flow control of the chip. // Function was created to correct problem // during the closing of a port. The problem // was that the close operation would hang // if software flow control was active // and the port had received an XOFF // by accident (say because of cat'ing // a binary file to the terminal). // // static void exser_reset_auto_flowctl(struct exser_struct * info) { unsigned long flags; unsigned char ier; unsigned char efr2; unsigned char mcr; save_flags(flags); cli(); ier = inb(info->base + UART_IER); outb(0x00, info->base + UART_IER); // disable interrupts restore_flags(flags); // to be restored later efr2 = inb(info->base + UART_EFR2); mcr = inb(info->base + UART_MCR); // found note in manual stating that this register must be set to 0 // before trying to change the auto flow control outb(0x00, info->base + UART_EFR2); // before we can change flow control must zero these bits outb(0x00, info->base + UART_MCR); outb(efr2, info->base + UART_EFR2 ); // restore flow control settings outb(mcr, info->base + UART_MCR); save_flags(flags); cli(); outb(ier, info->base + UART_IER); restore_flags(flags); } /* * This routine is called when the serial port gets closed. First, we * wait for the last remaining data to be sent. Then, we unlink its * async structure from the interrupt chain if necessary, and we free * that IRQ if nothing is left in the chain. */ static void exser_close(struct tty_struct * tty, struct file * filp) { struct exser_struct * info; unsigned long flags; unsigned long timeout; unsigned int line; int count; DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - start\n"); info = (struct exser_struct *)tty->driver_data; if ( (line=PORTNO(tty)) == EXSER_PORTS ) { DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - special node\n"); MOD_DEC_USE_COUNT; return; } // clean-up if ( !info ) { DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - null private structure\n"); return; } DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - xon indicator: %d\n", inb(info->base + 12)); DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - (1 - rcv xoff, 2 - rcv xon)\n"); //--------------------------------------------------------- // // if this port is a "DEVICE" port and software flow ctl // is enabled and there // //--------------------------------------------------------- if (PORT_TYPE_DEVICE == info->port_type && I_IXOFF(tty) && (count=tty->driver.chars_in_buffer(tty)) > 0) { tty_wait_until_sent(tty, HZ); if(tty->driver.chars_in_buffer(tty) > 0) { if ( tty->driver.flush_buffer ) tty->driver.flush_buffer(tty); if ( tty->ldisc.flush_buffer ) tty->ldisc.flush_buffer(tty); exser_reset_auto_flowctl(info); } } DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - xon indicator: %d\n", inb(info->base + 12)); //--------------------------------------------------------- // for device channels and software flow control // make sure we don't ever leave a device in an xoff state //--------------------------------------------------------- if (PORT_TYPE_DEVICE == info->port_type && I_IXOFF(tty)) { DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - software flow ctl enabled - send XON\n"); info->x_char = START_CHAR(tty); save_flags(flags); cli(); info->IER |= UART_IER_THRI; // trigger Tx interrupt outb(info->IER, info->base + UART_IER); restore_flags(flags); } save_flags(flags); cli(); if ( tty_hung_up_p(filp) ) { // decrement read/write count if necessary DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - f_flags:0x%x\n", filp->f_flags & O_ACCMODE); //if ((filp->f_flags & O_ACCMODE) == O_RDWR) { if ((filp->f_flags & O_ACCMODE) == O_WRONLY || (filp->f_flags & O_ACCMODE) == O_RDWR) { DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close(1): rdwr_count = %d\n", lci_devices[line].rdwr_count); if (PORT_TYPE_DEVICE == info->port_type) if (lci_devices[line].rdwr_count) lci_devices[line].rdwr_count--; } DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - tty hung up p\n"); MOD_DEC_USE_COUNT; restore_flags(flags); return; } if ( (tty->count == 1) && (info->count != 1) ) { /* * Uh, oh. tty->count is 1, which means that the tty * structure will be freed. Info->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. */ printk("exser_close: bad serial port count; tty->count is 1, " "info->count is %d\n", info->count); info->count = 1; } if ( --info->count < 0 ) { printk("exser_close: bad serial port count for ttys%d: %d\n", info->port, info->count); info->count = 0; } // decrement read/write count if necessary DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - f_flags:0x%x\n", filp->f_flags & O_ACCMODE); //if ((filp->f_flags & O_ACCMODE) == O_RDWR) { if ((filp->f_flags & O_ACCMODE) == O_WRONLY || (filp->f_flags & O_ACCMODE) == O_RDWR) { DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close(1): rdwr_count = %d\n", lci_devices[line].rdwr_count); if (PORT_TYPE_DEVICE == info->port_type) if (lci_devices[line].rdwr_count) lci_devices[line].rdwr_count--; } if ( info->count ) { MOD_DEC_USE_COUNT; restore_flags(flags); return; } info->flags |= ASYNC_CLOSING; /* * Save the termios structure, since this port may have * separate termios for callout and dialin. */ if ( info->flags & ASYNC_NORMAL_ACTIVE ) info->normal_termios = *tty->termios; if ( info->flags & ASYNC_CALLOUT_ACTIVE ) info->callout_termios = *tty->termios; /* * Now we wait for the transmit buffer to clear; and we notify * the line discipline to only process XON/XOFF characters. */ tty->closing = 1; if ( info->closing_wait != ASYNC_CLOSING_WAIT_NONE ) { DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - tty_wait_until_sent...\n"); DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" waiting %s...(%d)\n", tty_driver_name(tty), tty->driver.chars_in_buffer(tty)); tty_wait_until_sent(tty, info->closing_wait); DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - tty_wait_until_sent - done\n"); } /* * At this point we stop accepting input. To do this, we * disable the receive line status interrupts, and tell the * interrupt driver to stop checking the data ready bit in the * line status register. */ if(PORT_TYPE_DEVICE != info->port_type) info->IER &= ~UART_IER_RLSI; /* by William info->read_status_mask &= ~UART_LSR_DR; */ if ( info->flags & ASYNC_INITIALIZED ) { if(PORT_TYPE_DEVICE != info->port_type) outb(info->IER, info->base + UART_IER); /* * Before we drop DTR, make sure the UART transmitter * has completely drained; this is especially * important if there is a transmit FIFO! */ timeout = jiffies + HZ; while ( !(inb(info->base + UART_LSR) & UART_LSR_TEMT) ) { current->state = TASK_INTERRUPTIBLE; schedule_timeout(5); if ( jiffies > timeout ) break; } } DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - exser_shutdown...\n"); exser_shutdown(info); DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - exser_shutdown - done\n"); if (PORT_TYPE_DEVICE != info->port_type) { if ( tty->driver.flush_buffer ) tty->driver.flush_buffer(tty); if ( tty->ldisc.flush_buffer ) tty->ldisc.flush_buffer(tty); } tty->closing = 0; info->event = 0; info->tty = 0; if ( info->blocked_open ) { if ( info->close_delay ) { current->state = TASK_INTERRUPTIBLE; schedule_timeout(info->close_delay); } wake_up_interruptible(&info->open_wait); } info->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CALLOUT_ACTIVE | ASYNC_CLOSING); DEBUG_PRINT(DEBUG_CLOSE, DEFAULT_PREFIX" close - wake_up_interruptible...\n"); wake_up_interruptible(&info->close_wait); MOD_DEC_USE_COUNT; restore_flags(flags); } static int exser_write(struct tty_struct * tty, int from_user, const unsigned char * buf, int count) { int c, total = 0; struct exser_struct *info = (struct exser_struct *)tty->driver_data; unsigned long flags; if ( !tty || !info->xmit_buf || !exvar_tmp_buf ) return(0); if ( from_user ) down(&exvar_tmp_buf_sem); save_flags(flags); while ( 1 ) { cli(); c = MIN(count, MIN(SERIAL_XMIT_SIZE - info->xmit_cnt - 1, SERIAL_XMIT_SIZE - info->xmit_head)); if ( c <= 0 ) break; if ( from_user ) { copy_from_user(exvar_tmp_buf, buf, c); /* used for debugging { int i; for(i=0; ixmit_cnt - 1, SERIAL_XMIT_SIZE - info->xmit_head)); memcpy(info->xmit_buf + info->xmit_head, exvar_tmp_buf, c); } else memcpy(info->xmit_buf + info->xmit_head, buf, c); info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE - 1); info->xmit_cnt += c; restore_flags(flags); buf += c; count -= c; total += c; } if ( from_user ) up(&exvar_tmp_buf_sem); if ( info->xmit_cnt && !tty->stopped && !tty->hw_stopped && !(info->IER & UART_IER_THRI) ) { info->IER |= UART_IER_THRI; outb(info->IER, info->base + UART_IER); } restore_flags(flags); return(total); } static void exser_put_char(struct tty_struct * tty, unsigned char ch) { struct exser_struct *info = (struct exser_struct *)tty->driver_data; unsigned long flags; if ( !tty || !info->xmit_buf ) return; // for debugging //if (PORTNO(tty) == 17) // DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" pchar = 0x%x\n", ch); save_flags(flags); cli(); if ( info->xmit_cnt >= SERIAL_XMIT_SIZE - 1 ) { restore_flags(flags); return; } info->xmit_buf[info->xmit_head++] = ch; info->xmit_head &= SERIAL_XMIT_SIZE - 1; info->xmit_cnt++; /********************************************** why ??? *********** if ( !tty->stopped && !tty->hw_stopped && !(info->IER & UART_IER_THRI) ) { info->IER |= UART_IER_THRI; outb(info->IER, info->base + UART_IER); } *****************************************************************/ restore_flags(flags); } static void exser_flush_chars(struct tty_struct * tty) { struct exser_struct *info = (struct exser_struct *)tty->driver_data; unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" flush_chars-enable xmitter\n"); if ( info->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped || !info->xmit_buf ) return; save_flags(flags); cli(); info->IER |= UART_IER_THRI; outb(info->IER, info->base + UART_IER); restore_flags(flags); } static int exser_write_room(struct tty_struct * tty) { struct exser_struct *info = (struct exser_struct *)tty->driver_data; int ret; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" write_room\n"); ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1; if ( ret < 0 ) ret = 0; return(ret); } static int exser_chars_in_buffer(struct tty_struct * tty) { struct exser_struct *info = (struct exser_struct *)tty->driver_data; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" chars_in_buffer=%d\n", info->xmit_cnt); return(info->xmit_cnt); } static void exser_flush_buffer(struct tty_struct * tty) { struct exser_struct *info = (struct exser_struct *)tty->driver_data; unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" flush buffer\n"); save_flags(flags); cli(); info->xmit_cnt = info->xmit_head = info->xmit_tail = 0; restore_flags(flags); wake_up_interruptible(&tty->write_wait); if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup ) (tty->ldisc.write_wakeup)(tty); } static int exser_ioctl(struct tty_struct * tty, struct file * file, unsigned int cmd, unsigned long arg) { int error; struct exser_struct * info = (struct exser_struct *)tty->driver_data; int retval; struct async_icount cprev, cnow; /* kernel counter temps */ struct serial_icounter_struct *p_cuser; /* user space */ unsigned long templ; unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: %x\n", cmd); if ( PORTNO(tty) == EXSER_PORTS ) { return(exser_ioctl_special(cmd, arg)); } if ( (cmd != TIOCGSERIAL) && (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT) ) { if ( tty->flags & (1 << TTY_IO_ERROR) ) { return(-EIO); } } switch ( cmd ) { case TCSBRK: /* SVID version: non-zero arg --> no break */ DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TCSBRK \n"); retval = tty_check_change(tty); if ( retval ) return(retval); tty_wait_until_sent(tty, 0); if ( !arg ) exser_send_break(info, HZ/4); /* 1/4 second */ return(0); case TCSBRKP: /* support for POSIX tcsendbreak() */ DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TCSBRKP \n"); retval = tty_check_change(tty); if ( retval ) return(retval); tty_wait_until_sent(tty, 0); exser_send_break(info, arg ? arg*(HZ/10) : HZ/4); return(0); case TIOCGSOFTCAR: DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TIOCGSOFTCAR \n"); error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(long)); if ( error ) return(error); put_to_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long *)arg); return(0); case TIOCSSOFTCAR: DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TIOCSSOFTCAR \n"); error = verify_area(VERIFY_READ, (void *)arg, sizeof(long)); if ( error ) return(error); get_from_user(templ,(unsigned long *)arg); arg = templ; tty->termios->c_cflag = ((tty->termios->c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0)); return(0); case TIOCMGET: DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TIOCMGET \n"); error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(unsigned int)); if ( error ) return(error); return(exser_get_modem_info(info, (unsigned int *)arg)); case TIOCMBIS: DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TIOCMBIS \n"); case TIOCMBIC: DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TIOCMBIC \n"); case TIOCMSET: DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TIOCMSET \n"); return(exser_set_modem_info(info, cmd, (unsigned int *)arg)); case TIOCGSERIAL: DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TIOCGSERIAL \n"); error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(struct serial_struct)); if ( error ) return(error); return(exser_get_serial_info(info, (struct serial_struct *)arg)); case TIOCSSERIAL: DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TIOCSSERIAL \n"); error = verify_area(VERIFY_READ, (void *)arg, sizeof(struct serial_struct)); if ( error ) return(error); return(exser_set_serial_info(info, (struct serial_struct *)arg)); case TIOCSERGETLSR: /* Get line status register */ DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TIOCSERGETLSR \n"); error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(unsigned int)); if ( error ) return(error); else return(exser_get_lsr_info(info, (unsigned int *)arg)); /* * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change * - mask passed in arg for lines of interest * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking) * Caller should use TIOCGICOUNT to see which one it was */ case TIOCMIWAIT: DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TIOCMIWAIT \n"); save_flags(flags); cli(); cprev = info->icount; /* note the counters on entry */ restore_flags(flags); while ( 1 ) { interruptible_sleep_on(&info->delta_msr_wait); /* see if a signal did it */ if ( signal_pending(current) ) return(-ERESTARTSYS); save_flags(flags); cli(); cnow = info->icount; /* atomic copy */ restore_flags(flags); if ( cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && cnow.dcd == cprev.dcd && cnow.cts == cprev.cts ) return(-EIO); /* no change => error */ if ( ((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) { return(0); } cprev = cnow; } /* NOTREACHED */ /* * Get counter of input serial line interrupts (DCD,RI,DSR,CTS) * Return: write counters to the user passed counter struct * NB: both 1->0 and 0->1 transitions are counted except for * RI where only 0->1 is counted. */ case TIOCGICOUNT: DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: TIOCGICOUNT \n"); error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(struct serial_icounter_struct)); if ( error ) return(error); save_flags(flags); cli(); cnow = info->icount; restore_flags(flags); p_cuser = (struct serial_icounter_struct *)arg; put_to_user(cnow.cts, &p_cuser->cts); put_to_user(cnow.dsr, &p_cuser->dsr); put_to_user(cnow.rng, &p_cuser->rng); put_to_user(cnow.dcd, &p_cuser->dcd); return(0); case EXAR_HighSpeedOn: DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl: EXAR_HighSpeedOn \n"); error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(int)); if ( error ) return(error); put_to_user(info->baud_base != 115200 ? 1 : 0, (int *)arg); return(0); case EXAR_READ_REG: error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(struct exser_read_reg)); if ( error ) return(error); else return(exser_read_reg(info, (struct exser_read_reg *)arg)); break; case EXAR_WRITE_REG: error = verify_area(VERIFY_READ, (void *)arg, sizeof(struct exser_read_reg)); if ( error ) return(error); else return(exser_write_reg(info, (struct exser_read_reg *)arg)); break; case READ_IO_PORT: error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(struct exser_read_reg)); if ( error ) return(error); else { unsigned char result; struct exser_read_reg *rr = (struct exser_read_reg *)arg; result = inb(rr->reg); put_to_user(result, &(rr->regvalue)); return (0); } break; case BUFFER_ENABLE: { int port = PORTNO(tty); if (PORT_TYPE_DEVICE == info->port_type) { lci_devices[port].stop_buffering_in_direct = !(int)arg; return 0; } return -EIO; } break; case QUERY_BUFFER_ENABLE: { int port = PORTNO(tty); if (PORT_TYPE_DEVICE == info->port_type) { put_user(!(lci_devices[port].stop_buffering_in_direct), (int *)arg); return 0; } return -EIO; } break; case PARITY_MARK_SPACE: { unsigned char scratch; // LCR bits 5 4 3 // x x 0 no parity // 0 0 1 odd // 0 1 1 even // 1 0 1 mark (always '1') // 1 1 1 space (always '0') // 0 - do nothing; 1 - mark; 2 - space switch ((int)arg) { case 0: default: break; case 1: //mark scratch = (inb(info->base + UART_LCR) & ~(0x7<<3)) | (0x5<<3); outb(scratch, info->base + UART_LCR); break; case 2: //space scratch = inb(info->base + UART_LCR) | (0x7<<3); outb(scratch, info->base + UART_LCR); break; } } return 0; break; case QUERY_PARITY: { // LCR bits 5 4 3 // x x 0 no parity // 0 0 1 odd // 0 1 1 even // 1 0 1 mark (always '1') // 1 1 1 space (always '0') // // return values: // 0 - no parity // 1 - odd parity // 2 - even parity // 3 - mark // 4 - space // int scratch = inb(info->base + UART_LCR)>>3 & 0x7; if (scratch & 0x1) { scratch >>= 1; ++scratch; put_user(scratch, (int *)arg); } else { scratch = 0; put_user(scratch, (int *)arg); } return 0; } break; case RESET_RDWR_COUNT: { int port = PORTNO(tty); if (PORT_TYPE_DEVICE == info->port_type) { lci_devices[port].rdwr_count = 0; return 0; } return -EIO; } break; case QUERY_RDWR_COUNT: { int port = PORTNO(tty); if (PORT_TYPE_DEVICE == info->port_type) { put_user(lci_devices[port].rdwr_count, (int *)arg); return 0; } return -EIO; } break; case SET_PORT_TYPE: { unsigned long flags; int port = PORTNO(tty); if (PORT_TYPE_DEVICE != info->port_type && PORT_TYPE_DEVICE == (type_port)arg) { save_flags(flags); cli(); lci_devices[port].rdwr_count = 0; info->port_type = PORT_TYPE_DEVICE; restore_flags(flags); // enable reception of data retval = exser_startup(info); if ( retval ) { return(retval); } exser_change_speed(info, 0, TRUE); return 0; } else if (PORT_TYPE_DEVICE == info->port_type && PORT_TYPE_DEVICE != (type_port)arg) { save_flags(flags); cli(); lci_devices[port].rdwr_count = 0; info->port_type = (type_port)arg; restore_flags(flags); // disable reception of data exser_shutdown(info); return 0; } else if ((type_port)arg == PORT_TYPE_OFF || (type_port)arg == PORT_TYPE_GETTY || (type_port)arg == PORT_TYPE_NON_GETTY) return 0; return -EIO; } break; case QUERY_PORT_TYPE: { if (info->port_type < PORT_TYPE_LIST_LAST) { put_user(info->port_type, (int *)arg); return 0; } return -EIO; } break; #ifdef MODULE case RESET_MODULE_CNT: while (MOD_IN_USE) MOD_DEC_USE_COUNT; MOD_INC_USE_COUNT; break; #endif /* tty driver handles these, by calling this drivers set_termios */ case TCSETS: case TCSETSW: case TCGETS: case TCFLSH: case TCSETSF: return(-ENOIOCTLCMD); default: /* printk("exser: IOCTL default cmd=%x\n", cmd); */ return(-ENOIOCTLCMD); } return(0); } static int exser_read_reg(struct exser_struct * info, struct exser_read_reg *arg) { unsigned char result; unsigned int reg; unsigned long flags; int error; error = verify_area(VERIFY_READ, (void *)arg->reg, sizeof(struct exser_read_reg)); if ( error ) return(error); get_from_user(reg,&(arg->reg)); save_flags(flags); cli(); result = inb(info->base + reg); restore_flags(flags); printk("exser: Read base=0x%08x, Reg=0x%08x, value=0x%08x\n", info->base, reg, result); put_to_user(result, &(arg->regvalue)); return(0); } static int exser_write_reg(struct exser_struct * info, struct exser_read_reg *arg) { unsigned int reg, value; get_from_user(reg,&(arg->reg)); get_from_user(value,&(arg->regvalue)); outb(value, info->base + reg); printk("exser: write base=0x%08x, Reg=0x%08x, value=0x%08x\n", info->base, reg, value); return(0); } static int exser_ioctl_special(unsigned int cmd, unsigned long arg) { int error, i, result, status; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" IOCTL_SPECIAL start\n"); switch ( cmd ) { case EXAR_GET_CONF: error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(struct exser_hwconf)*4); if ( error ) return(error); copy_to_user((struct exser_hwconf *)arg, exsercfg, sizeof(struct exser_hwconf)*4); return 0; case EXAR_GET_MAJOR: error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(int)); if ( error ) return(error); copy_to_user((int*)arg, &ttymajor, sizeof(int)); return 0; case EXAR_GET_CUMAJOR: error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(int)); if ( error ) return(error); copy_to_user((int*)arg, &calloutmajor, sizeof(int)); return 0; case EXAR_CHKPORTENABLE: error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(long)); if ( error ) return(error); result = 0; for ( i=0; itermios ) GMStatus[i].cflag=exvar_table[i].normal_termios.c_cflag; else GMStatus[i].cflag = exvar_table[i].tty->termios->c_cflag; status = inb(exvar_table[i].base + UART_MSR); if(status & 0x80/*UART_MSR_DCD*/) GMStatus[i].dcd = 1; else GMStatus[i].dcd = 0; if(status & 0x20/*UART_MSR_DSR*/) GMStatus[i].dsr = 1; else GMStatus[i].dsr = 0; if(status & 0x10/*UART_MSR_CTS*/) GMStatus[i].cts = 1; else GMStatus[i].cts = 0; } copy_to_user((struct exser_mstatus *)arg, GMStatus, sizeof(struct exser_mstatus) * EXSER_PORTS); return 0; case QUERY_HARDWARE_PORT_TYPE: { int reg; struct exser_read_reg rr; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl_special: " "QUERY_HARDWARE_PORT_TYPE\n"); error = verify_area(VERIFY_READ, (void *)arg, sizeof(struct exser_read_reg)); if ( error ) return(error); error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(struct exser_read_reg)); if ( error ) return(error); reg = ((struct exser_read_reg *)arg)->reg; if (reg < 0 || reg >= EXSER_PORTS) { return -ENOIOCTLCMD; } rr.reg = reg; rr.regvalue = exvar_table[reg].port_hardware_type; copy_to_user((struct exser_read_reg *)arg, &rr, sizeof(rr)); return 0; } case SET_HARDWARE_PORT_TYPE: { int reg; int regValue; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" ioctl_special: " "SET_HARDWARE_PORT_TYPE\n"); error = verify_area(VERIFY_READ, (void *)arg, sizeof(struct exser_read_reg)); if ( error ) return(error); reg = ((struct exser_read_reg *)arg)->reg; regValue = ((struct exser_read_reg *)arg)->regvalue; if (reg < 0 || reg >= EXSER_PORTS) { return -ENOIOCTLCMD; } exvar_table[reg].port_hardware_type = regValue; return 0; } default: return(-ENOIOCTLCMD); } return(0); } /* * This routine is called by the upper-layer tty layer to signal that * incoming characters should be throttled. */ static void exser_throttle(struct tty_struct * tty) { struct exser_struct *info = (struct exser_struct *)tty->driver_data; unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" throttle\n"); if ( I_IXOFF(tty) ) { info->x_char = STOP_CHAR(tty); save_flags(flags); cli(); // outb(info->IER, 0); don't know what this is supposed to to??? info->IER |= UART_IER_THRI; outb(info->IER, info->base + UART_IER); /* force Tx interrupt */ restore_flags(flags); } if ( info->tty->termios->c_cflag & CRTSCTS ) { info->MCR &= ~UART_MCR_RTS; save_flags(flags); cli(); outb(info->MCR, info->base + UART_MCR); restore_flags(flags); exvar_log.rts_cnt[info->port]++; } } /* */ static void exser_unthrottle(struct tty_struct * tty) { struct exser_struct *info = (struct exser_struct *)tty->driver_data; unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" unthrottle\n"); if ( I_IXOFF(tty) ) { if ( info->x_char ) info->x_char = 0; else { info->x_char = START_CHAR(tty); save_flags(flags); cli(); info->IER |= UART_IER_THRI; /* force Tx interrupt */ outb(info->IER, info->base + UART_IER); restore_flags(flags); } } if ( info->tty->termios->c_cflag & CRTSCTS ) { info->MCR |= UART_MCR_RTS; save_flags(flags); cli(); outb(info->MCR, info->base + UART_MCR); restore_flags(flags); exvar_log.rts_cnt[info->port]++; } } /* */ static void exser_set_termios(struct tty_struct * tty, struct termios * old_termios) { struct exser_struct *info = (struct exser_struct *)tty->driver_data; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" set termios\n"); /* 8-2-99 by William if ( (tty->termios->c_cflag == old_termios->c_cflag) && (RELEVANT_IFLAG(tty->termios->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag)) ) return; exser_change_speed(info, old_termios); if ( (old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS) ) { tty->hw_stopped = 0; exser_start(tty); } */ DEBUG_PRINT(DEBUG_TERMIOS, DEFAULT_PREFIX" set termios %d \n", __LINE__); if ( (tty->termios->c_cflag != old_termios->c_cflag) || (RELEVANT_IFLAG(tty->termios->c_iflag) != RELEVANT_IFLAG(old_termios->c_iflag)) ) { DEBUG_PRINT(DEBUG_TERMIOS, DEFAULT_PREFIX" set_termios calling change_speed\n"); // similar to change_speed, but resets the channel // and sets the auto config parameters // also waits for the channel's fifo to clear exser_change_speed(info, old_termios, FALSE); if ( (old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS) ) { tty->hw_stopped = 0; exser_start(tty); } } /* Handle sw stopped */ if ( (old_termios->c_iflag & IXON) && !(tty->termios->c_iflag & IXON) ) { tty->stopped = 0; DEBUG_PRINT(DEBUG_TERMIOS, DEFAULT_PREFIX" set termios %d (before start 2)\n", __LINE__); exser_start(tty); } } /* * exser_stop() and exser_start() * * This routines are called before setting or resetting tty->stopped. * They enable or disable transmitter interrupts, as necessary. */ static void exser_stop(struct tty_struct * tty) { struct exser_struct *info = (struct exser_struct *)tty->driver_data; unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" stop (%d)\n", PORTNO(tty)); save_flags(flags); cli(); if ( info->IER & UART_IER_THRI ) { info->IER &= ~UART_IER_THRI; outb(info->IER, info->base + UART_IER); } restore_flags(flags); } /* */ static void exser_start(struct tty_struct * tty) { struct exser_struct *info = (struct exser_struct *)tty->driver_data; unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" start (%d)\n", PORTNO(tty)); save_flags(flags); cli(); if ( info->xmit_cnt && info->xmit_buf && !(info->IER & UART_IER_THRI) ) { info->IER |= UART_IER_THRI; outb(info->IER, info->base + UART_IER); } restore_flags(flags); } /* * This routine is called by tty_hangup() when a hangup is signaled. */ static void exser_hangup(struct tty_struct * tty) { struct exser_struct * info = (struct exser_struct *)tty->driver_data; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" hangup (%d)\n", PORTNO(tty)); exser_flush_buffer(tty); exser_shutdown(info); info->event = 0; info->count = 0; info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE); info->tty = 0; wake_up_interruptible(&info->open_wait); } /* * This is the serial driver's generic interrupt routine */ static void exser_interrupt(int irq, void *dev_id, struct pt_regs * regs) { int status, i; struct exser_struct * info; struct exser_struct * port; int max, bits, msr, isr_bits; volatile int irqbits; int pass_counter = 0; unsigned char device_channel = ~0; unsigned char chip_number=0; unsigned char more_interrupts=2; //return; // for debugging auto flow control port = 0; for(i=0; i EXSER_PORTS) printk(KERN_ALERT "exser isr: bad chip number found\n"); if(i==EXSER_CHIPS) { printk(KERN_WARNING "exser isr: could not find port\n"); return; } if(port==0) { printk(KERN_WARNING "exser isr: port var not init\n"); return; } max = 8; //exser_numports[exsercfg[i].board_type-1]; while ( more_interrupts ) { irqbits = inb(port->vector) & port->vectormask; for ( i=0, bits=1; itty) { status = inb(info->base + UART_LSR) & info->read_status_mask; if ( status & UART_LSR_DR ) exser_receive_chars(info, &status, FALSE, device_channel); // we can clear the TXRDY interrupt // also clears any XON/XOFF special character interrupts isr_bits = inb(info->base + UART_ISR); // clear any msr interrupts // also clears RTS/DTR or CTS/DSR status change interrupts msr = inb(info->base + UART_MSR); continue; } if ( (inb(info->base + UART_IIR) & UART_IIR_NO_INT) ) // removed "!info->tty ||" afp 04-25-02 continue; status = inb(info->base + UART_LSR) & info->read_status_mask; if ( status & UART_LSR_DR ) exser_receive_chars(info, &status, TRUE, device_channel); msr = inb(info->base + UART_MSR); if ( msr & UART_MSR_ANY_DELTA ) { exser_check_modem_status(info, msr); } if ( status & UART_LSR_THRE ) { /* 8-2-99 by William if ( info->x_char || (info->xmit_cnt > 0) ) */ isr_bits = inb(info->base + UART_ISR); exser_transmit_chars(info); } } --more_interrupts; if ( pass_counter++ > EXSER_ISR_PASS_LIMIT ) { printk("EXAR multiport serial driver interrupt loop break\n"); break; /* Prevent infinite loops */ } } } static _INLINE_ void exser_receive_chars(struct exser_struct *info, int *status, unsigned char normal_receive, int device_channel) { int ignored = 0; int cnt = 0; int not_in_use_cnt = 0; struct tty_struct * tty = info->tty; type_lci_device * p = NULL; unsigned char ch; unsigned char lci_status = (unsigned char)*status; unsigned char rcv_cnt; //----------------------------------------------------------------------- do { rcv_cnt = inb(info->base + UART_RFCNT); ch = inb(info->base + UART_RX); DEBUG_PRINT(DEBUG_ISR, ISR_PREFIX">%x", ch); #if defined(LCI) && defined(LCI_DEVICE_BUFFERS) /* add to history buffer */ if(PORT_TYPE_DEVICE == info->port_type) { if (rcv_cnt) { p = &lci_devices[device_channel]; // keep a count of the types of communications errors if any occur // only do this if it will not be performed below for if (!normal_receive && (lci_status & UART_LSR_SPECIAL) ) { lci_status &= UART_LSR_SPECIAL; if ( lci_status & 0x10) //UART_LSR_BI ) { // break detected DEBUG_PRINT(DEBUG_COMM_ERR, DEFAULT_PREFIX"rcv isr: break received (%d)\n", device_channel); exvar_log.break_rcv_cnt[info->port]++; } if ( lci_status & 0x04) //UART_LSR_PE ) { // parity DEBUG_PRINT(DEBUG_COMM_ERR, DEFAULT_PREFIX"rcv isr: parity error (%d)\n", device_channel); exvar_log.parity_err_cnt[info->port]++; } if ( lci_status & 0x08) //UART_LSR_FE ) { // frame DEBUG_PRINT(DEBUG_COMM_ERR, DEFAULT_PREFIX"rcv isr: framing error (%d)\n", device_channel); exvar_log.framing_err_cnt[info->port]++; } if ( lci_status & 0x02) //UART_LSR_OE ) { // overrun DEBUG_PRINT(DEBUG_COMM_ERR, DEFAULT_PREFIX"rcv isr: overrun error (%d)\n", device_channel); exvar_log.overrun_err_cnt[info->port]++; } else // something weird happened DEBUG_PRINT(DEBUG_COMM_ERR, DEFAULT_PREFIX"rcv isr: undefined %x (%d)\n", lci_status & UART_LSR_SPECIAL, device_channel); } // add to lcibuf device's memory buffer if (0 == p->rdwr_count || !p->stop_buffering_in_direct) { *(p->head) = ch; p->head++; if(p->head > p->pbuf_end) p->head = p->pbuf; if(p->head == p->tail) { p->tail++; if(p->tail > p->pbuf_end) p->tail = p->pbuf; } // calculate the new size of the file // for proc entry file size if (p->head < p->tail) { p->ent->size = p->size - 1; } else { p->ent->size = p->head - p->tail + 1; if (p->ent->size > p->size) p->ent->size = p->size; p->ent->size--; } } not_in_use_cnt++; wake_up_interruptible(&p->in_queue); // add to listen buffer too p = &lci_listen_devices[device_channel]; *(p->head) = ch; p->head++; if(p->head > p->pbuf_end) p->head = p->pbuf; if(p->head == p->tail) { p->tail++; if(p->tail > p->pbuf_end) p->tail = p->pbuf; } wake_up_interruptible(&p->in_queue); } } #endif if(normal_receive) { if ( *status & info->ignore_status_mask ) { if ( ++ignored > 100 ) break; } else { if ( tty->flip.count >= TTY_FLIPBUF_SIZE ) { break; } tty->flip.count++; if ( *status & UART_LSR_SPECIAL ) { if ( *status & UART_LSR_BI ) { *tty->flip.flag_buf_ptr++ = TTY_BREAK; if ( info->flags & ASYNC_SAK ) do_SAK(tty); exvar_log.break_rcv_cnt[info->port]++; } else if ( *status & UART_LSR_PE ) { *tty->flip.flag_buf_ptr++ = TTY_PARITY; exvar_log.parity_err_cnt[info->port]++; } else if ( *status & UART_LSR_FE ) { *tty->flip.flag_buf_ptr++ = TTY_FRAME; exvar_log.framing_err_cnt[info->port]++; } else if ( *status & UART_LSR_OE ) { *tty->flip.flag_buf_ptr++ = TTY_OVERRUN; exvar_log.overrun_err_cnt[info->port]++; } else *tty->flip.flag_buf_ptr++ = 0; } else *tty->flip.flag_buf_ptr++ = 0; *tty->flip.char_buf_ptr++ = ch; cnt++; } } #ifdef LCI_DEBUG_ISR printk(":b=%x", info->base); printk(":rsm=%x", info->read_status_mask); printk(":ier=%x", inb(info->base + UART_IER)); printk(":isr=%x", inb(info->base + 2)); // UART_IIR #endif lci_status = *status = inb(info->base + UART_LSR); #ifdef LCI_DEBUG_ISR printk(":s=%x", *status); #endif if (normal_receive) // afp - must always check for DataReady *status = *status & (UART_LSR_DR | info->read_status_mask); } while ( *status & UART_LSR_DR ); // tally the number of bytes received if(normal_receive) { exvar_log.rxcnt[info->port] += cnt; } else { exvar_log.rxcnt[info->port] += not_in_use_cnt; // correct bug } if(normal_receive) { #if (LINUX_VERSION_CODE >= VERSION_CODE(2,1,0)) queue_task(&tty->flip.tqueue, &tq_timer); #else queue_task_irq_off(&tty->flip.tqueue, &tq_timer); #endif } } /* */ static _INLINE_ void exser_transmit_chars(struct exser_struct *info) { int count, cnt; if ( info->x_char ) { outb(info->x_char, info->base + UART_TX); info->x_char = 0; exvar_log.txcnt[info->port]++; return; } /* disable the tx interrupt and exit if any of the following */ /* become true: */ if ( (info->xmit_cnt <= 0) || info->tty->stopped || info->tty->hw_stopped ) { info->IER &= ~UART_IER_THRI; outb(info->IER, info->base + UART_IER); return; } cnt = info->xmit_cnt; count = info->xmit_fifo_size; do { /* used for debugging int cnt; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX"i0x%x ", info->xmit_buf[info->xmit_tail]); */ outb(info->xmit_buf[info->xmit_tail], info->base + UART_TX); /* used for debugging if (info->xmit_buf[info->xmit_tail] == 0x0a) { volatile int cnt; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" isr waiting for cr\n"); while ( (cnt=inb(info->base + UART_TFCNT)) != 0) ; while ( (inb(info->base + UART_LSR) & 0x60) == 0) ; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" isr thr empty\n"); for(cnt=0; cnt<65000; ++cnt) ; } */ info->xmit_tail++; info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE - 1); if ( --info->xmit_cnt <= 0 ) break; } while ( --count > 0 ); exvar_log.txcnt[info->port] += (cnt - info->xmit_cnt); if ( info->xmit_cnt < WAKEUP_CHARS ) { set_bit(EXSER_EVENT_TXLOW,&info->event); queue_task(&info->tqueue,&tq_scheduler); } if ( info->xmit_cnt <= 0 ) { info->IER &= ~UART_IER_THRI; outb(info->IER, info->base + UART_IER); } } static _INLINE_ void exser_check_modem_status(struct exser_struct *info, int status) { DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" check modem status \n"); /* update input line counters */ if ( status & UART_MSR_TERI ) info->icount.rng++; if ( status & UART_MSR_DDSR ) info->icount.dsr++; if ( status & UART_MSR_DDCD ) info->icount.dcd++; if ( status & UART_MSR_DCTS ) info->icount.cts++; wake_up_interruptible(&info->delta_msr_wait); if ( (info->flags & ASYNC_CHECK_CD) && (status & UART_MSR_DDCD) ) { if ( status & UART_MSR_DCD ) wake_up_interruptible(&info->open_wait); else if ( !((info->flags & ASYNC_CALLOUT_ACTIVE) && (info->flags & ASYNC_CALLOUT_NOHUP)) ) { set_bit(EXSER_EVENT_HANGUP, &info->event); if (info->tty) tty_hangup(info->tty); } #if (LINUX_VERSION_CODE >= VERSION_CODE(2,1,0)) queue_task(&info->tqueue, &tq_scheduler); #else queue_task_irq_off(&info->tqueue, &tq_scheduler); #endif } if ( info->flags & ASYNC_CTS_FLOW ) { if ( info->tty->hw_stopped ) { if ( status & UART_MSR_CTS ) { info->tty->hw_stopped = 0; info->IER |= UART_IER_THRI; outb(info->IER, info->base + UART_IER); set_bit(EXSER_EVENT_TXLOW,&info->event); #if (LINUX_VERSION_CODE >= VERSION_CODE(2,1,0)) queue_task(&info->tqueue, &tq_scheduler); #else queue_task_irq_off(&info->tqueue, &tq_scheduler); #endif } } else { if ( !(status & UART_MSR_CTS) ) { info->tty->hw_stopped = 1; info->IER &= ~UART_IER_THRI; outb(info->IER, info->base + UART_IER); } } } } static int exser_block_til_ready(struct tty_struct *tty, struct file * filp, struct exser_struct *info) { struct wait_queue wait = { current, NULL }; int retval; int do_clocal = 0; unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" block til ready\n"); /* * If the device is in the middle of being closed, then block * until it's done, and then try again. */ if ( tty_hung_up_p(filp) || (info->flags & ASYNC_CLOSING) ) { if ( info->flags & ASYNC_CLOSING ) interruptible_sleep_on(&info->close_wait); #ifdef SERIAL_DO_RESTART if ( info->flags & ASYNC_HUP_NOTIFY ) return(-EAGAIN); else return(-ERESTARTSYS); #else return(-EAGAIN); #endif } /* * If this is a callout device, then just make sure the normal * device isn't being used. */ if ( tty->driver.subtype == SERIAL_TYPE_CALLOUT ) { if ( info->flags & ASYNC_NORMAL_ACTIVE ) return(-EBUSY); if ( (info->flags & ASYNC_CALLOUT_ACTIVE) && (info->flags & ASYNC_SESSION_LOCKOUT) && (info->session != current->session) ) return(-EBUSY); if ( (info->flags & ASYNC_CALLOUT_ACTIVE) && (info->flags & ASYNC_PGRP_LOCKOUT) && (info->pgrp != current->pgrp) ) return(-EBUSY); info->flags |= ASYNC_CALLOUT_ACTIVE; return(0); } /* * If non-blocking mode is set, or the port is not enabled, * then make the check up front and then exit. */ if ( (filp->f_flags & O_NONBLOCK) || (tty->flags & (1 << TTY_IO_ERROR)) ) { if ( info->flags & ASYNC_CALLOUT_ACTIVE ) return(-EBUSY); info->flags |= ASYNC_NORMAL_ACTIVE; return(0); } if ( info->flags & ASYNC_CALLOUT_ACTIVE ) { if ( info->normal_termios.c_cflag & CLOCAL ) do_clocal = 1; } else { if ( tty->termios->c_cflag & CLOCAL ) do_clocal = 1; } /* * Block waiting for the carrier detect and the line to become * free (i.e., not in use by the callout). While we are in * this loop, info->count is dropped by one, so that * exser_close() knows when to free things. We restore it upon * exit, either normal or abnormal. */ retval = 0; add_wait_queue(&info->open_wait, &wait); save_flags(flags); cli(); if ( !tty_hung_up_p(filp) ) info->count--; restore_flags(flags); info->blocked_open++; while ( 1 ) { save_flags(flags); cli(); if ( !(info->flags & ASYNC_CALLOUT_ACTIVE) ) outb(inb(info->base + UART_MCR) | UART_MCR_DTR | UART_MCR_RTS, info->base + UART_MCR); restore_flags(flags); current->state = TASK_INTERRUPTIBLE; if ( tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED) ) { #ifdef SERIAL_DO_RESTART if ( info->flags & ASYNC_HUP_NOTIFY ) retval = -EAGAIN; else retval = -ERESTARTSYS; #else retval = -EAGAIN; #endif break; } if ( !(info->flags & ASYNC_CALLOUT_ACTIVE) && !(info->flags & ASYNC_CLOSING) && (do_clocal || (inb(info->base + UART_MSR) & UART_MSR_DCD)) ) break; if ( signal_pending(current) ) { retval = -ERESTARTSYS; break; } schedule(); } current->state = TASK_RUNNING; remove_wait_queue(&info->open_wait, &wait); if ( !tty_hung_up_p(filp) ) info->count++; info->blocked_open--; if ( retval ) return(retval); info->flags |= ASYNC_NORMAL_ACTIVE; return(0); } static int exser_startup(struct exser_struct * info) { unsigned long flags; unsigned long page; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" startup\n"); page = get_free_page(GFP_KERNEL); if ( !page ) return(-ENOMEM); save_flags(flags); cli(); if ( info->flags & ASYNC_INITIALIZED ) { free_page(page); restore_flags(flags); return(0); } if ( !info->base || !info->type ) { if ( info->tty ) set_bit(TTY_IO_ERROR, &info->tty->flags); free_page(page); restore_flags(flags); return(0); } if ( info->xmit_buf ) free_page(page); else info->xmit_buf = (unsigned char *)page; /* * Clear the FIFO buffers and disable them * (they will be reenabled in exser_change_speed()) */ if ( info->xmit_fifo_size > 1 ) outb((UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT), info->base + UART_FCR); /* * At this point there's no way the LSR could still be 0xFF; * if it is, then bail out, because there's likely no UART * here. */ if ( inb(info->base + UART_LSR) == 0xff ) { restore_flags(flags); if ( suser() ) { if ( info->tty ) set_bit(TTY_IO_ERROR, &info->tty->flags); return(0); } else return(-ENODEV); } /* * Clear the interrupt registers. */ (void)inb(info->base + UART_LSR); (void)inb(info->base + UART_RX); (void)inb(info->base + UART_IIR); (void)inb(info->base + UART_MSR); /* * Now, initialize the UART */ outb(UART_LCR_WLEN8, info->base + UART_LCR); /* reset DLAB */ info->MCR = UART_MCR_DTR | UART_MCR_RTS; outb(info->MCR, info->base + UART_MCR); /* * Finally, enable interrupts */ info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI; outb(info->IER, info->base + UART_IER); /* enable interrupts */ /* * And clear the interrupt registers again for luck. */ (void)inb(info->base + UART_LSR); (void)inb(info->base + UART_RX); (void)inb(info->base + UART_IIR); (void)inb(info->base + UART_MSR); #if (LINUX_VERSION_CODE < VERSION_CODE(2,1,0)) if ( info->tty ) clear_bit(TTY_IO_ERROR, &info->tty->flags); #else if ( info->tty ) test_and_clear_bit(TTY_IO_ERROR, &info->tty->flags); #endif info->xmit_cnt = info->xmit_head = info->xmit_tail = 0; /* * and set the speed of the serial port */ exser_change_speed(info, 0, FALSE); info->flags |= ASYNC_INITIALIZED; restore_flags(flags); return(0); } //********************************************************** // // special startup for device ports // //********************************************************** static int exser_lci_startup(struct exser_struct * info) { unsigned long flags; unsigned long page; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" lci startup\n"); page = get_free_page(GFP_KERNEL); if ( !page ) return(-ENOMEM); save_flags(flags); cli(); if ( info->flags & ASYNC_INITIALIZED ) { free_page(page); restore_flags(flags); return(0); } if ( !info->base || !info->type ) { if ( info->tty ) set_bit(TTY_IO_ERROR, &info->tty->flags); free_page(page); restore_flags(flags); return(0); } if ( info->xmit_buf ) free_page(page); else info->xmit_buf = (unsigned char *)page; /* * Clear the FIFO buffers and disable them * (they will be reenabled in exser_change_speed()) */ // because the uart is already "on", we don't want to clear the fifo's here // if ( info->xmit_fifo_size == 16 ) // outb((UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT), info->base + UART_FCR); /* * At this point there's no way the LSR could still be 0xFF; * if it is, then bail out, because there's likely no UART * here. * if ( inb(info->base + UART_LSR) == 0xff ) { restore_flags(flags); if ( suser() ) { if ( info->tty ) set_bit(TTY_IO_ERROR, &info->tty->flags); return(0); } else return(-ENODEV); } */ /* * Clear the interrupt registers. */ // we want to leave the interrupt register alone // (void)inb(info->base + UART_LSR); // (void)inb(info->base + UART_RX); // (void)inb(info->base + UART_IIR); // (void)inb(info->base + UART_MSR); /* * Now, initialize the UART */ // afp don't reset the LCR because the last settings will be wiped out // outb(UART_LCR_WLEN8, info->base + UART_LCR); /* reset DLAB */ //??? this should not be a problem, but... info->MCR = UART_MCR_DTR | UART_MCR_RTS; // outb(info->MCR, info->base + UART_MCR); /* * Finally, enable interrupts */ info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI; // outb(info->IER, info->base + UART_IER); /* enable interrupts */ /* * And clear the interrupt registers again for luck. */ // (void)inb(info->base + UART_LSR); // (void)inb(info->base + UART_RX); // (void)inb(info->base + UART_IIR); // (void)inb(info->base + UART_MSR); #if (LINUX_VERSION_CODE < VERSION_CODE(2,1,0)) if ( info->tty ) clear_bit(TTY_IO_ERROR, &info->tty->flags); #else if ( info->tty ) test_and_clear_bit(TTY_IO_ERROR, &info->tty->flags); #endif info->xmit_cnt = info->xmit_head = info->xmit_tail = 0; /* * and set the speed of the serial port */ exser_change_speed(info, 0, FALSE); info->flags |= ASYNC_INITIALIZED; restore_flags(flags); return(0); } /* * This routine will shutdown a serial port; interrupts maybe disabled, and * DTR is dropped if the hangup on close termio flag is on. */ static void exser_shutdown(struct exser_struct * info) { unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" shutdown\n"); if ( !(info->flags & ASYNC_INITIALIZED) ) return; save_flags(flags); cli(); /* Disable interrupts */ /* * clear delta_msr_wait queue to avoid mem leaks: we may free the irq * here so the queue might never be waken up */ wake_up_interruptible(&info->delta_msr_wait); /* * Free the IRQ, if necessary */ if ( info->xmit_buf ) { free_page((unsigned long)info->xmit_buf); info->xmit_buf = 0; } if(PORT_TYPE_DEVICE != info->port_type) { info->IER = 0; outb(0x00, info->base + UART_IER); /* disable all intrs */ // ??? debug the following code, the hang-up should be skipped // ??? maybe info->MCR is not set properly // ??? *** this was dropping the line when exiting minicom // ??? *** even though we told minicom not to //if ( !info->tty || (info->tty->termios->c_cflag & HUPCL) ) // info->MCR &= ~(UART_MCR_DTR | UART_MCR_RTS); //outb(info->MCR, info->base + UART_MCR); /* clear Rx/Tx FIFO's */ outb((UART_FCR_CLEAR_RCVR|UART_FCR_CLEAR_XMIT), info->base + UART_FCR); /* read data port to reset things */ (void)inb(info->base + UART_RX); } if ( info->tty ) set_bit(TTY_IO_ERROR, &info->tty->flags); info->flags &= ~ASYNC_INITIALIZED; restore_flags(flags); } static int exser_get_quotient(struct exser_struct *info, int baud_code) { int i; int quot; switch( baud_code ) { case B921600 : i = 20; break; case B460800 : i = 19; break; case B230400 : i = 18; break; case B115200 : i = 17; break; case B57600 : i = 16; break; case B38400 : i = 15; break; case B19200 : i = 14; break; case B9600 : i = 13; break; case B4800 : i = 12; break; case B2400 : i = 11; break; case B1800 : i = 10; break; case B1200 : i = 9; break; case B600 : i = 8; break; case B300 : i = 7; break; case B200 : i = 6; break; case B150 : i = 5; break; case B134 : i = 4; break; case B110 : i = 3; break; case B75 : i = 2; break; case B50 : i = 1; break; default : i = 0; break; } DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" change speed to (i = %d)\n", i); if (i==15) { if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) i = 16; /* 57600 bps */ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) i = 17; /* 115200 bps */ #ifdef ASYNC_SPD_SHI if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) i = 18; #endif #ifdef ASYNC_SPD_WARP if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) i = 19; #endif } if ( exvar_baud_table[i] == 134 ) { quot = (2 * info->baud_base / 269); } else if ( exvar_baud_table[i] ) { quot = info->baud_base / exvar_baud_table[i]; if(0==quot) quot = 1; } else { quot = 0; } if (0 == quot) quot = 1; return quot; } /* exser_get_quotient */ static void exser_set_auto_flowctl(unsigned int chip_baseaddr, unsigned int channel_baseaddr, unsigned char ier, unsigned int t_trg, unsigned int r_trg, unsigned int hysteresis, unsigned char flowcontrol) { unsigned long flags; save_flags(flags); cli(); // outb((0x01 << ((channel_baseaddr&0x00F0) >> 4)), chip_baseaddr|0x8a); // reset channel 0 // outb(0x01, baseaddr|0x8e); // enable simultaneous write to all channels outb(0x00, channel_baseaddr + UART_IER); // disable interrupts restore_flags(flags); // found note in manual stating that this register must be set to 0 // before trying to change the auto flow control outb(0x00, channel_baseaddr + UART_EFR2); // before we can change flow control must zero these bits outb(0x00, channel_baseaddr + UART_MCR); // outb(0x07, channel_baseaddr + UART_FCR); // enable & flush fifo's // outb(0x80, channel_baseaddr + UART_LCR); // set DLAB // outb(quot & 0xff, channel_baseaddr + UART_DLL); // LS of divisor // outb(quot >> 8, channel_baseaddr + UART_DLM); // MS of divisor // outb(0x03, channel_baseaddr + UART_LCR); // reset DLAB outb(0xc0 | hysteresis, channel_baseaddr + UART_FCTR); // trigger table D selected with hysteresis outb(r_trg, channel_baseaddr + UART_RXTRIG); // RX trigger level - usually 64 outb(t_trg, channel_baseaddr + UART_TXTRIG); // TX trigger level - usually 16 // auto flow control //outb('s', channel_baseaddr + UART_XOFF1); outb(0x13, channel_baseaddr + UART_XOFF1); //outb('g', channel_baseaddr + UART_XON1); outb(0x11, channel_baseaddr + UART_XON1); outb(0x12, channel_baseaddr + UART_XOFF2); outb(0x14, channel_baseaddr + UART_XON2); outb(0x10|flowcontrol, channel_baseaddr + UART_EFR2 ); //enable shaded bits + flow control outb(0x03, channel_baseaddr + UART_MCR); save_flags(flags); cli(); outb(ier, channel_baseaddr + UART_IER); restore_flags(flags); /* - from sample code config_write ( 0, RESET, 0xff ); // reset all channels config_write ( 8, RESET, 0xff ); // reset all channels config_write ( 16, RESET, 0xff ); // reset all channels config_write ( ch, REG2, 0x01 ); // enable simultaneous write to all channels pwrite ( ch, IER, 0x00 ); // disable interrupts pwrite ( ch, FCR, 0x07 ); // enable & flush FIFOs pwrite ( ch, LCR, 0x80 ); // setup baud rate registers pwrite ( ch, DLL, dll ); pwrite ( ch, DLM, dlm ); pwrite ( ch, LCR, 0x03 ); //setting 8N1 pwrite ( ch, FCTR, 0xc0 | hyst ); //trigger table D selected with hysteresis pwrite ( ch, RXTRIG, r_trg ); //RX trigger level - usually 64 pwrite ( ch, TXTRIG, t_trg ); //TX trigger level - usually 16 pwrite ( ch, EFR, 0x10 | flowcontrol ); //enable shaded bits + flow control pwrite ( ch, XOFF1, 0x11 ); pwrite ( ch, XON1, 0x13 ); pwrite ( ch, XOFF2, 0x12 ); pwrite ( ch, XON2, 0x14 ); pwrite ( ch, MCR, 0x03 ); // enable RTS and DTR lines all the time // disable simultaneous write to all channels config_write ( ch, REG2, 0x00 ); pwrite ( ch, IER, 0x03 ); // enable rx/tx interrupts */ } /* * This routine is called to set the UART divisor registers to match * the specified baud rate for a serial port. * */ static int exser_change_speed(struct exser_struct *info, struct termios *old_termios, unsigned char lci_special) { int quot = 0; unsigned cflag, cval, fcr, iflag; int i; int ret = 0; unsigned long flags; unsigned char flowcontrol; unsigned int t_trg, r_trg; unsigned int hysteresis; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" change speed\n"); if(!lci_special) { if ( !info->tty || !info->tty->termios ) return ret; cflag = info->tty->termios->c_cflag; iflag = info->tty->termios->c_iflag; } else { DEBUG_PRINT(DEBUG_SPEED, " change speed %d (force 9600)\n", __LINE__); cflag = B9600|CS8|CLOCAL|HUPCL|CREAD; iflag = 0; // default NO FLOWCONTROL } DEBUG_PRINT(DEBUG_SPEED, "1"); if ( !(info->base) ) return ret; DEBUG_PRINT(DEBUG_SPEED, "2"); #ifndef B921600 #define B921600 (B460800 +1) #endif // using the info value to set baud rate switch( cflag & (CBAUD | CBAUDEX) ) { case B921600 : i = 20; break; case B460800 : i = 19; break; case B230400 : i = 18; break; case B115200 : i = 17; break; case B57600 : i = 16; break; case B38400 : i = 15; break; case B19200 : i = 14; break; case B9600 : i = 13; break; case B4800 : i = 12; break; case B2400 : i = 11; break; case B1800 : i = 10; break; case B1200 : i = 9; break; case B600 : i = 8; break; case B300 : i = 7; break; case B200 : i = 6; break; case B150 : i = 5; break; case B134 : i = 4; break; case B110 : i = 3; break; case B75 : i = 2; break; case B50 : i = 1; break; default: i = 0; break; } DEBUG_PRINT(DEBUG_SPEED, "exser: speed is (i = %d)\n", i); DEBUG_PRINT(DEBUG_SPEED, "3"); if ( i == 15 ) { if ( (info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI ) i = 16; // 57600 bps if ( (info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI ) i = 17; // 115200 bps #ifdef ASYNC_SPD_SHI if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) i = 18; #endif #ifdef ASYNC_SPD_WARP if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) i = 19; #endif } if ( exvar_baud_table[i] == 134 ) { quot = (2 * info->baud_base / 269); } else if ( exvar_baud_table[i] ) { quot = info->baud_base / exvar_baud_table[i]; DEBUG_PRINT(DEBUG_SPEED, "exser: quot = %d (i = %d)\n", quot, i); DEBUG_PRINT(DEBUG_SPEED, "exser: info->baud_base = %d \n", info->baud_base); DEBUG_PRINT(DEBUG_SPEED, "4"); if (!quot && old_termios) { DEBUG_PRINT(DEBUG_SPEED, "5"); // re-calculate info->tty->termios->c_cflag &= ~CBAUD; info->tty->termios->c_cflag |= (old_termios->c_cflag & CBAUD); DEBUG_PRINT(DEBUG_SPEED, "exser: change speed to (c_cflag = %x)\n", info->tty->termios->c_cflag); quot = exser_get_quotient(info, old_termios->c_cflag & (CBAUD | CBAUDEX)); } else if(quot==0) quot = 1; } else { quot = 0; } DEBUG_PRINT(DEBUG_SPEED, "6"); if ( quot ) { DEBUG_PRINT(DEBUG_SPEED, "exser: change speed %d (i = %d)\n", __LINE__, i); info->MCR |= UART_MCR_DTR; save_flags(flags); cli(); outb(info->MCR, info->base + UART_MCR); restore_flags(flags); } else { info->MCR &= ~UART_MCR_DTR; save_flags(flags); cli(); outb(info->MCR, info->base + UART_MCR); restore_flags(flags); return ret; } DEBUG_PRINT(DEBUG_SPEED, "7"); // byte size and parity switch ( cflag & CSIZE ) { case CS5: cval = 0x00; break; case CS6: cval = 0x01; break; case CS7: cval = 0x02; break; case CS8: cval = 0x03; break; default: cval = 0x00; break; // too keep GCC shut... } if ( cflag & CSTOPB ) cval |= 0x04; if ( cflag & PARENB ) cval |= UART_LCR_PARITY; if ( !(cflag & PARODD) ) cval |= UART_LCR_EPAR; if ( (info->type == PORT_8250) || (info->type == PORT_16450) ) { fcr = 0; } else { DEBUG_PRINT(DEBUG_SPEED, "8"); fcr = UART_FCR_ENABLE_FIFO; switch ( info->rx_trigger ) { case 1: fcr |= UART_FCR_TRIGGER_1; break; case 4: fcr |= UART_FCR_TRIGGER_4; break; case 8: fcr |= UART_FCR_TRIGGER_8; break; default: fcr |= UART_FCR_TRIGGER_14; } } // CTS flow control flag and modem status interrupts info->IER &= ~UART_IER_MSI; info->MCR &= ~UART_MCR_AFE; if ( cflag & CRTSCTS ) { info->flags |= ASYNC_CTS_FLOW; info->IER |= UART_IER_MSI; if ( info->type == PORT_16550A ) info->MCR |= UART_MCR_AFE; } else { info->flags &= ~ASYNC_CTS_FLOW; } outb(info->MCR, info->base + UART_MCR); if ( cflag & CLOCAL ) info->flags &= ~ASYNC_CHECK_CD; else { info->flags |= ASYNC_CHECK_CD; info->IER |= UART_IER_MSI; } outb(info->IER, info->base + UART_IER); // // Set up parity check flag // DEBUG_PRINT(DEBUG_SPEED, "9"); info->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR; if (!lci_special) { if ( I_INPCK(info->tty) ) info->read_status_mask |= UART_LSR_FE | UART_LSR_PE; if ( I_BRKINT(info->tty) || I_PARMRK(info->tty) ) info->read_status_mask |= UART_LSR_BI; } info->ignore_status_mask = 0; #if 0 // This should be safe, but for some broken bits of hardware... if ( I_IGNPAR(info->tty) ) { info->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; info->read_status_mask |= UART_LSR_PE | UART_LSR_FE; } #endif DEBUG_PRINT(DEBUG_SPEED, "A"); if (!lci_special) { if ( I_IGNBRK(info->tty) ) { info->ignore_status_mask |= UART_LSR_BI; info->read_status_mask |= UART_LSR_BI; // // If we're ignore parity and break indicators, ignore // overruns too. (For real raw support). // if ( I_IGNPAR(info->tty) ) { info->ignore_status_mask |= UART_LSR_OE|UART_LSR_PE|UART_LSR_FE; info->read_status_mask |= UART_LSR_OE|UART_LSR_PE|UART_LSR_FE; } } } DEBUG_PRINT(DEBUG_SPEED, "B"); save_flags(flags); cli(); outb(cval | UART_LCR_DLAB, info->base + UART_LCR); // set DLAB outb(quot & 0xff, info->base + UART_DLL); // LS of divisor outb(quot >> 8, info->base + UART_DLM); // MS of divisor outb(cval, info->base + UART_LCR); // reset DLAB outb(fcr, info->base + UART_FCR); // set fcr (enable fifo & set trigger level) restore_flags(flags); DEBUG_PRINT(DEBUG_SPEED, "exser: change speed (fcr=0x%x)\n", fcr); DEBUG_PRINT(DEBUG_SPEED, "C"); DEBUG_PRINT(DEBUG_SPEED, "exser: update flow control too!\n"); t_trg = 16; r_trg = 32; flowcontrol = 0; // assume none hysteresis = 0; // assume none if ( cflag & CRTSCTS ) { flowcontrol |= 0xc0; hysteresis = HYSTERESIS_PLUS_MINUS_8; DEBUG_PRINT(DEBUG_SPEED, "exser: CRTSCTS\n"); } if ( iflag & IXOFF ) { flowcontrol |= 0x08; hysteresis = HYSTERESIS_PLUS_MINUS_8; DEBUG_PRINT(DEBUG_SPEED, "exser: IXOFF\n"); } if ( iflag & IXON ) { flowcontrol |= 0x02; hysteresis = HYSTERESIS_PLUS_MINUS_8; DEBUG_PRINT(DEBUG_SPEED, "exser: IXON\n"); } if (!flowcontrol) DEBUG_PRINT(DEBUG_SPEED, "exser: no flow control\n"); exser_set_auto_flowctl(info->base & 0xFF00, info->base, info->IER, t_trg, r_trg, hysteresis, flowcontrol); return ret; } /* * ------------------------------------------------------------ * friends of exser_ioctl() * ------------------------------------------------------------ */ static int exser_get_serial_info(struct exser_struct * info, struct serial_struct * retinfo) { struct serial_struct tmp; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" get serial info\n"); if ( !retinfo ) return(-EFAULT); memset(&tmp, 0, sizeof(tmp)); tmp.type = info->type; tmp.line = info->port; tmp.port = info->base; tmp.irq = info->irq; tmp.flags = info->flags; tmp.baud_base = info->baud_base; tmp.close_delay = info->close_delay; tmp.closing_wait = info->closing_wait; tmp.custom_divisor = info->custom_divisor; tmp.hub6 = 0; copy_to_user(retinfo, &tmp, sizeof(*retinfo)); return(0); } static int exser_set_serial_info(struct exser_struct * info, struct serial_struct * new_info) { struct serial_struct new_serial; unsigned int flags; int retval = 0; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" set serial info\n"); if ( !new_info || !info->base ) return(-EFAULT); copy_from_user(&new_serial, new_info, sizeof(new_serial)); if ((new_serial.irq != info->irq) || (new_serial.port != info->base) || (new_serial.type != info->type) || (new_serial.custom_divisor != info->custom_divisor) || (new_serial.baud_base != info->baud_base) ) return(-EPERM); flags = info->flags & ASYNC_SPD_MASK; if ( !suser() ) { if ((new_serial.baud_base != info->baud_base) || (new_serial.close_delay != info->close_delay) || ( (new_serial.flags & ~ASYNC_USR_MASK) != (info->flags & ~ASYNC_USR_MASK))) return(-EPERM); info->flags = ((info->flags & ~ASYNC_USR_MASK) | (new_serial.flags & ASYNC_USR_MASK)); } else { /* * OK, past this point, all the error checking has been done. * At this point, we start making changes..... */ info->flags = ((info->flags & ~ASYNC_FLAGS) | (new_serial.flags & ASYNC_FLAGS)); info->close_delay = new_serial.close_delay * HZ/100; info->closing_wait = new_serial.closing_wait * HZ/100; } if ( info->flags & ASYNC_INITIALIZED ) { if ( flags != (info->flags & ASYNC_SPD_MASK) ) { exser_change_speed(info, 0, FALSE); } } else retval = exser_startup(info); return(retval); } /* * exser_get_lsr_info - get line status register info * * Purpose: Let user call ioctl() to get info when the UART physically * is emptied. On bus types like RS485, the transmitter must * release the bus after transmitting. This must be done when * the transmit shift register is empty, not be done when the * transmit holding register is empty. This functionality * allows an RS485 driver to be written in user space. */ static int exser_get_lsr_info(struct exser_struct * info, unsigned int *value) { unsigned char status; unsigned int result; unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" get lsr info\n"); save_flags(flags); cli(); status = inb(info->base + UART_LSR); restore_flags(flags); result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0); put_to_user(result, value); return(0); } /* * This routine sends a break character out the serial port. */ static void exser_send_break(struct exser_struct * info, int duration) { unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" send break\n"); if ( !info->base ) return; current->state = TASK_INTERRUPTIBLE; exvar_log.break_xmit_cnt[info->port]++; save_flags(flags); cli(); outb(inb(info->base + UART_LCR) | UART_LCR_SBC, info->base + UART_LCR); schedule_timeout(duration); outb(inb(info->base + UART_LCR) & ~UART_LCR_SBC, info->base + UART_LCR); restore_flags(flags); } static int exser_get_modem_info(struct exser_struct * info, unsigned int *value) { unsigned char control, status; unsigned int result; unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" get modem info\n"); control = info->MCR; save_flags(flags); cli(); status = inb(info->base + UART_MSR); if ( status & UART_MSR_ANY_DELTA ) exser_check_modem_status(info, status); restore_flags(flags); result = ((control & UART_MCR_RTS) ? TIOCM_RTS : 0) | ((control & UART_MCR_DTR) ? TIOCM_DTR : 0) | ((status & UART_MSR_DCD) ? TIOCM_CAR : 0) | ((status & UART_MSR_RI) ? TIOCM_RNG : 0) | ((status & UART_MSR_DSR) ? TIOCM_DSR : 0) | ((status & UART_MSR_CTS) ? TIOCM_CTS : 0); put_to_user(result, value); return(0); } static int exser_set_modem_info(struct exser_struct * info, unsigned int cmd, unsigned int *value) { int error; unsigned int arg; unsigned long flags; DEBUG_PRINT(DEBUG_TRACE, TRACE_PREFIX" set modem info\n"); error = verify_area(VERIFY_READ, value, sizeof(int)); if ( error ) return(error); get_from_user(arg,value); switch ( cmd ) { case TIOCMBIS: if ( arg & TIOCM_RTS ) info->MCR |= UART_MCR_RTS; if ( arg & TIOCM_DTR ) info->MCR |= UART_MCR_DTR; break; case TIOCMBIC: if ( arg & TIOCM_RTS ) info->MCR &= ~UART_MCR_RTS; if ( arg & TIOCM_DTR ) info->MCR &= ~UART_MCR_DTR; break; case TIOCMSET: info->MCR = ((info->MCR & ~(UART_MCR_RTS | UART_MCR_DTR)) | ((arg & TIOCM_RTS) ? UART_MCR_RTS : 0) | ((arg & TIOCM_DTR) ? UART_MCR_DTR : 0)); break; default: return(-EINVAL); } save_flags(flags); cli(); outb(info->MCR, info->base + UART_MCR); restore_flags(flags); return(0); } #ifndef LCI static int exser_read_register(int, unsigned short *); static int exser_program_mode(int); static void exser_normal_mode(int); static int exser_get_ISA_conf(int cap,struct exser_hwconf *hwconf) { int id, i, bits; unsigned short regs[16], irq; unsigned char scratch, scratch2; id = exser_read_register(cap, regs); if (id == C158_ASIC_ID) hwconf->board_type = EXSER_BOARD_C158_ISA; else if (id == C104_ASIC_ID) hwconf->board_type = EXSER_BOARD_C104_ISA; else if (id == CI104J_ASIC_ID) hwconf->board_type = EXSER_BOARD_CI104J; else return(0); irq = regs[9] & 0x0F; irq = irq | (irq << 4); irq = irq | (irq << 8); if ( (irq != regs[9]) || ((id == 1) && (irq != regs[10])) ) { return(EXSER_ERR_IRQ_CONFLIT); } if ( !irq ) { return(EXSER_ERR_IRQ); } for ( i=0; i<8; i++ ) hwconf->ioaddr[i] = (int)regs[i + 1] & 0xFFF8; hwconf->irq = (int)(irq & 0x0F); if ( (regs[12] & 0x80) == 0 ) { return(EXSER_ERR_VECTOR); } hwconf->vector = (int)regs[11]; /* interrupt vector */ if ( id == 1 ) hwconf->vector_mask = 0x00FF; else hwconf->vector_mask = 0x000F; for ( i=7, bits=0x0100; i>=0; i--, bits <<= 1 ) { if ( regs[12] & bits ) hwconf->baud_base[i] = 921600; else hwconf->baud_base[i] = 115200; } scratch2 = inb(cap + UART_LCR) & (~UART_LCR_DLAB); outb(scratch2 | UART_LCR_DLAB, cap + UART_LCR); outb(0, cap + UART_EFR); /* EFR is the same as FCR */ outb(scratch2, cap + UART_LCR); outb(UART_FCR_ENABLE_FIFO, cap + UART_FCR); scratch = inb(cap + UART_IIR); if ( scratch & 0xC0 ) hwconf->uart_type = PORT_16550A; else hwconf->uart_type = PORT_16450; if ( id == 1 ) hwconf->ports = 8; else hwconf->ports = 4; return(hwconf->ports); } #endif #ifdef LCI static int exser_get_LCI_conf(type_uart_hardware_io *lci_uart_item, struct exser_hwconf *hwconf) { int i; unsigned char scratch, scratch2; unsigned short base_ioaddrs[8]={0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70}; hwconf->board_type = EXSER_BOARD_C158_ISA; // ??? we probably could define our own if (lci_uart_item->number_of_uarts < 0) return(-1); DEBUG_PRINT(DEBUG_ID, DEFAULT_PREFIX" number of uarts in system: %d\n", lci_uart_item->number_of_uarts); DEBUG_PRINT(DEBUG_ID, DEFAULT_PREFIX" base i/o address 0x%x\n", lci_uart_item->base_io_address); // base io address for each port for ( i=0; inumber_of_uarts; i++ ) hwconf->ioaddr[i] = lci_uart_item->base_io_address + base_ioaddrs[i]; hwconf->irq = (int)(lci_uart_item->irq_number & 0x0F); hwconf->vector = (int)(lci_uart_item->base_io_address + 0x80); /* bits 0-7 tell which channel interrupted */ hwconf->vector_mask = 0x00FF; hwconf->buffer_flags = lci_uart_item->buffer_flags; for ( i=0; i<8; ++i ) hwconf->baud_base[i] = 921600; lci_serial_channels += lci_uart_item->number_of_uarts; // lets make sure the data registers are selected DEBUG_PRINT(DEBUG_ID, DEFAULT_PREFIX" read from LCR (0x%x)\n", lci_uart_item->base_io_address + UART_LCR); scratch2 = inb(lci_uart_item->base_io_address + UART_LCR) & (~UART_LCR_DLAB); DEBUG_PRINT(DEBUG_ID, DEFAULT_PREFIX" value = 0x%x\n", scratch2); DEBUG_PRINT(DEBUG_ID, DEFAULT_PREFIX" write value back to LCR 0x%x\n", scratch2|UART_LCR_DLAB); outb(scratch2|UART_LCR_DLAB, lci_uart_item->base_io_address + UART_LCR); outb(0, lci_uart_item->base_io_address + UART_EFR); /* EFR is the same as FCR */ outb(scratch2, lci_uart_item->base_io_address + UART_LCR); outb(UART_FCR_ENABLE_FIFO, lci_uart_item->base_io_address + UART_FCR); scratch = inb(lci_uart_item->base_io_address + UART_IIR); DEBUG_PRINT(DEBUG_ID, DEFAULT_PREFIX" value of IIR = 0x%x\n", scratch); hwconf->uart_type = PORT_16550A; hwconf->ports = lci_uart_item->number_of_uarts; return(hwconf->ports); } #endif ////////////////////////////////////////////////////////////////////////////// ///////////// ///////////////////// ///////////// history buffer device driver ///////////////////// ///////////// ///////////////////// ////////////////////////////////////////////////////////////////////////////// #define LCI_STATIC static LCI_STATIC int lci_clear_buffer(type_lci_device *dev); LCI_STATIC int lcibuf_open(struct inode *inode, struct file *filp); LCI_STATIC int lcibuf_release(struct inode *inode, struct file *filp); LCI_STATIC ssize_t lcibuf_read(struct file *filp, char *buf, size_t count, loff_t *f_pos); LCI_STATIC ssize_t lcibuf_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos); LCI_STATIC int lcibuf_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); LCI_STATIC loff_t lcibuf_llseek(struct file *filp, loff_t off, int whence); /* major number */ int lcibuf_major = 31; LCI_STATIC int lci_clear_buffer(type_lci_device *dev) { unsigned long flags; save_flags(flags); cli(); dev->head = dev->tail = dev->pbuf; restore_flags(flags); return 0; } /* * Open and close */ /* In lcibuf_open, the fop_array is used according to TYPE(dev) */ LCI_STATIC int lcibuf_open(struct inode *inode, struct file *filp) { unsigned int device_channel; type_lci_device * dev; unsigned char success; struct exser_struct * info; //----------------------------------------------------------------------- MOD_INC_USE_COUNT; /* Before we maybe sleep */ device_channel = MINOR(inode->i_rdev); success = ~0; if (device_channel < 0 || device_channel >= ELEMENTSOF(lci_devices)) { MOD_DEC_USE_COUNT; return -ENODEV; } dev = &lci_devices[device_channel]; ++(dev->usage_count); info = &exvar_table[device_channel]; if (PORT_TYPE_DEVICE != info->port_type) { --(dev->usage_count); MOD_DEC_USE_COUNT; return -ENODEV; } /* clear buffer if open was write-only */ /* if other users are viewing buffer then fail the clear */ if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { // here we can clear the buffer if (down_interruptible(&dev->sem)) { --(dev->usage_count); MOD_DEC_USE_COUNT; return -ERESTARTSYS; } if (dev->usage_count > 1) { --(dev->usage_count); success = 0; // already in use } if(success) lci_clear_buffer(dev); /* ignore errors */ up(&dev->sem); if(!success) { --(dev->usage_count); MOD_DEC_USE_COUNT; DEBUG_PRINT(DEBUG_LCIBUF_OPEN, DEFAULT_PREFIX" usage_count = %d\n", dev->usage_count); DEBUG_PRINT(DEBUG_LCIBUF_OPEN, DEFAULT_PREFIX" dev %d\n", device_channel); return -EBUSY; } } /* used by read/write */ filp->private_data = dev; return 0; /* success */ } LCI_STATIC int lcibuf_release(struct inode *inode, struct file *filp) { type_lci_device * dev = filp->private_data; if(dev!=NULL && dev->usage_count > 0) --(dev->usage_count); DEBUG_PRINT(DEBUG_LCIBUF_CLOSE, DEFAULT_PREFIX" usage_count = %d\n", dev->usage_count); MOD_DEC_USE_COUNT; return 0; } /* * Data management: read and write */ LCI_STATIC ssize_t lcibuf_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) { type_lci_device * dev = filp->private_data; ssize_t ret = 0; unsigned long seg_size, seg_size2; loff_t t_pos; unsigned long flags; char * volatile tail, * volatile head; if (NULL == dev) { return -EFAULT; /* this should never happen... */ /* ??? find a better error */ } if (down_interruptible(&dev->sem)) return -ERESTARTSYS; save_flags(flags); cli(); head = dev->head; tail = dev->tail; restore_flags(flags); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" enter -\n"); /* we are trying to read beyond the buffer's size */ if (*f_pos >= dev->size) goto out; DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 1\n"); /* buffer is empty */ if (head == tail) goto out; DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 2\n"); /* head greater than tail - buffer may not be full yet */ if(head > tail) { seg_size = head - tail; DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 3\n"); /* this should never happen... */ if (seg_size > LCI_BUFFER_SIZE) { DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" seg_size = %ld\n", seg_size); ret = -9; //ret = -EFAULT; goto out; } /* *f_pos is an offset 0 - (LCI_BUFFER_SIZE-1) */ /* nothing to read beyond the buffer's head */ if (tail+(*f_pos) >= head) goto out; DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 4"); if(tail+(*f_pos)+count > head) { DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" head-tail = %x\n", (int)seg_size); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" count = %x\n", (int)count); ret = head - tail - (*f_pos); /* actual count */ } else { /* otherwise count is smaller or equal to what's left to be read */ ret = count; } DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 5\n"); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" buf = %x\n", (int)buf); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" tail = %x\n", (int)tail); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" *f_pos = %Lx\n", *f_pos); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" ret = %x\n", ret); if (copy_to_user(buf, tail+(*f_pos), ret)) { ret = -EFAULT; goto out; } DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6\n"); *f_pos += ret; DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" read cnt: %d\n", ret); } else { /* (tail > head) must be true, since we tested for == above */ seg_size = dev->pbuf_end - tail + 1; /* how much before we hit the end of the buffer */ seg_size2 = head - dev->pbuf; t_pos = *f_pos; /* nothing to read from first segment of buffer */ if (tail+t_pos > dev->pbuf_end) { //t_pos = (tail+t_pos)-dev->pbuf_end-1; t_pos -= seg_size; // simplified //if (dev->pbuf+t_pos > head) { /* *f_pos puts us out of range */ if (t_pos > seg_size2) { /* *f_pos puts us out of range */ goto out; } //if (t_pos+count <= seg_size2) // ret = count; //else // ret = seg_size2-t_pos; //if (seg_size2-t_pos > count) 09-17-01 if (seg_size2-t_pos > count) ret = count; else { ret = seg_size2-t_pos; if (ret <= 0) goto out; } /* easy */ DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 1 dev->pbuf+t_pos 0x%x\n", (dev->pbuf+t_pos)[ret-4]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 1 dev->pbuf+t_pos 0x%x\n", (dev->pbuf+t_pos)[ret-3]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 1 dev->pbuf+t_pos 0x%x\n", (dev->pbuf+t_pos)[ret-2]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 1 dev->pbuf+t_pos 0x%x\n", (dev->pbuf+t_pos)[ret-1]); if (copy_to_user(buf, dev->pbuf+t_pos, ret)) { ret = -EFAULT; goto out; } *f_pos += ret; goto out; } else { /* we may need to copy from both segments of memory */ seg_size = dev->pbuf_end - (tail+t_pos) + 1; if (count <= seg_size) { /* easy */ DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 2 tail+*f_pos 0x%x\n", (tail+*f_pos)[count-4]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 2 tail+*f_pos 0x%x\n", (tail+*f_pos)[count-3]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 2 tail+*f_pos 0x%x\n", (tail+*f_pos)[count-2]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 2 tail+*f_pos 0x%x\n", (tail+*f_pos)[count-1]); if (copy_to_user(buf, tail+(*f_pos), count)) { ret = -EFAULT; goto out; } ret = count; *f_pos += count; goto out; } else if (count <= (seg_size+seg_size2) ) { /* copy to temp buffer first then to user space */ DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 3 tail+*f_pos 0x%x\n", (tail+*f_pos)[seg_size-4]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 3 tail+*f_pos 0x%x\n", (tail+*f_pos)[seg_size-3]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 3 tail+*f_pos 0x%x\n", (tail+*f_pos)[seg_size-2]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 3 tail+*f_pos 0x%x\n", (tail+*f_pos)[seg_size-1]); if (copy_to_user(buf, tail+(*f_pos), seg_size)) { ret = -EFAULT; goto out; } DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 4 dev->pbuf 0x%x\n", (dev->pbuf)[count-seg_size-4]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 4 dev->pbuf 0x%x\n", (dev->pbuf)[count-seg_size-3]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 4 dev->pbuf 0x%x\n", (dev->pbuf)[count-seg_size-2]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 4 dev->pbuf 0x%x\n", (dev->pbuf)[count-seg_size-1]); if (copy_to_user(buf+seg_size, dev->pbuf, count-seg_size)) { ret = -EFAULT; goto out; } ret = count; *f_pos += count; goto out; } else { /* copy to temp buffer first then to user space */ DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 5 tail+*f_pos 0x%x\n", (tail+*f_pos)[seg_size-4]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 5 tail+*f_pos 0x%x\n", (tail+*f_pos)[seg_size-3]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 5 tail+*f_pos 0x%x\n", (tail+*f_pos)[seg_size-2]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 5 tail+*f_pos 0x%x\n", (tail+*f_pos)[seg_size-1]); if (copy_to_user(buf, tail+*f_pos, seg_size)) { ret = -EFAULT; goto out; } DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 count 0x%lx\n", (unsigned long)count); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 seg_size2 0x%lx\n", seg_size2); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 seg_size 0x%lx\n", seg_size); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 *f_pos 0x%lx\n", (unsigned long)(*f_pos)); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 *head 0x%x\n", (unsigned char)(head)[-10]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 *head 0x%x\n", (unsigned char)(head)[-9]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 *head 0x%x\n", (unsigned char)(head)[-8]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 *head 0x%x\n", (unsigned char)(head)[-7]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 *head 0x%x\n", (unsigned char)(head)[-6]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 *head 0x%x\n", (unsigned char)(head)[-5]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 *head 0x%x\n", (unsigned char)(head)[-4]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 *head 0x%x\n", (unsigned char)(head)[-3]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 *head 0x%x\n", (unsigned char)(head)[-2]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 *head 0x%x\n", (unsigned char)(head)[-1]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 dev->pbuf 0x%x\n", (unsigned char)(dev->pbuf)[seg_size2-10]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 dev->pbuf 0x%x\n", (unsigned char)(dev->pbuf)[seg_size2-9]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 dev->pbuf 0x%x\n", (unsigned char)(dev->pbuf)[seg_size2-8]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 dev->pbuf 0x%x\n", (unsigned char)(dev->pbuf)[seg_size2-7]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 dev->pbuf 0x%x\n", (unsigned char)(dev->pbuf)[seg_size2-6]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 dev->pbuf 0x%x\n", (unsigned char)(dev->pbuf)[seg_size2-5]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 dev->pbuf 0x%x\n", (unsigned char)(dev->pbuf)[seg_size2-4]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 dev->pbuf 0x%x\n", (unsigned char)(dev->pbuf)[seg_size2-3]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 dev->pbuf 0x%x\n", (unsigned char)(dev->pbuf)[seg_size2-2]); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" 6 dev->pbuf 0x%x\n", (unsigned char)(dev->pbuf)[seg_size2-1]); if (copy_to_user(buf+seg_size, dev->pbuf, seg_size2)) { ret = -EFAULT; goto out; } ret = seg_size+seg_size2; *f_pos += ret; goto out; } } } out: DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" - end\n"); up(&dev->sem); return ret; } LCI_STATIC ssize_t lcibuf_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) { ssize_t ret = count; /* make the write happy */ return ret; } /* * The ioctl() implementation * * - future: some of these maybe useful * * clear * * modify size of buffer * * set/reset the don't buffer while in direct mode flag * * enable/disable debug flag * * enable/disable proc data * * return buffer size * * return number of bytes in the buffer * * return block of buffer * * */ LCI_STATIC int lcibuf_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { // int err = 0, tmp; int err = 0; int ret = 0; /* * extract the type and number bitfields, and don't decode * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() */ if (_IOC_TYPE(cmd) != LCIBUF_IOC_MAGIC) return -ENOTTY; if (_IOC_NR(cmd) > LCIBUF_IOC_MAXNR) return -ENOTTY; /* * the direction is a bitmask, and VERIFY_WRITE catches R/W * transfers. `Type' is user-oriented, while * access_ok is kernel-oriented, so the concept of "read" and * "write" is reversed */ if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; switch(cmd) { #ifdef LEAVE_OUT /* remove all scull stuff for now */ #ifdef SCULL_DEBUG case SCULL_IOCHARDRESET: /* * reset the counter to 1, to allow unloading in case * of problems. Use 1, not 0, because the invoking * process has the device open. */ while (MOD_IN_USE) MOD_DEC_USE_COUNT; MOD_INC_USE_COUNT; /* don't break: fall through and reset things */ #endif /* SCULL_DEBUG */ case SCULL_IOCRESET: scull_quantum = SCULL_QUANTUM; scull_qset = SCULL_QSET; break; case SCULL_IOCSQUANTUM: /* Set: arg points to the value */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; ret = __get_user(scull_quantum, (int *)arg); break; case SCULL_IOCTQUANTUM: /* Tell: arg is the value */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; scull_quantum = arg; break; case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */ ret = __put_user(scull_quantum, (int *)arg); break; case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */ return scull_quantum; case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; ret = __get_user(scull_quantum, (int *)arg); if (ret == 0) ret = __put_user(tmp, (int *)arg); break; case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; scull_quantum = arg; return tmp; case SCULL_IOCSQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; ret = __get_user(scull_qset, (int *)arg); break; case SCULL_IOCTQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; scull_qset = arg; break; case SCULL_IOCGQSET: ret = __put_user(scull_qset, (int *)arg); break; case SCULL_IOCQQSET: return scull_qset; case SCULL_IOCXQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_qset; ret = __get_user(scull_qset, (int *)arg); if (ret == 0) ret = put_user(tmp, (int *)arg); break; case SCULL_IOCHQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_qset; scull_qset = arg; return tmp; /* * The following two change the buffer size for scullpipe. * The scullpipe device uses this same ioctl method, just to * write less code. Actually, it's the same driver, isn't it? */ case SCULL_P_IOCTSIZE: scull_p_buffer = arg; break; case SCULL_P_IOCQSIZE: return scull_p_buffer; #endif default: /* redundant, as cmd was checked against MAXNR */ return -ENOTTY; } return ret; } unsigned int lcibuf_poll(struct file *filp, poll_table *wait) { type_lci_device *dev = filp->private_data; static char * old_head = NULL; unsigned int mask = 0; poll_wait(filp, &dev->in_queue, wait); // if there is something in the buffer // when we first enter poll, then let the world know if (NULL == old_head && dev->head != dev->tail) { mask |= POLLIN | POLLRDNORM; old_head = dev->head; } else { if (dev->head == dev->tail) old_head = dev->head; if (old_head != dev->head) { old_head = dev->head; mask |= POLLIN | POLLRDNORM; /* we have data in the queue */ } } return mask; } /* * The "extended" operations -- only seek */ LCI_STATIC loff_t lcibuf_llseek(struct file *filp, loff_t off, int whence) { type_lci_device * dev = filp->private_data; loff_t newpos; switch(whence) { case 0: /* SEEK_SET */ newpos = off; break; case 1: /* SEEK_CUR */ newpos = filp->f_pos + off; break; case 2: /* SEEK_END */ if(dev->head == dev->tail) /* buffer empty */ newpos = off; else { if(dev->head > dev->tail) newpos = dev->head - dev->tail + off; else newpos = dev->size + off; } break; default: /* can't happen */ return -EINVAL; } if (newpos<0) return -EINVAL; filp->f_pos = newpos; return newpos; } struct file_operations lcibuf_fops = { llseek: lcibuf_llseek, read: lcibuf_read, write: lcibuf_write, ioctl: lcibuf_ioctl, open: lcibuf_open, release: lcibuf_release, poll: lcibuf_poll, }; static int lcibuf_init(void) { int result, i, m; /* allocate all device buffers */ memset(lci_devices, 0, sizeof(lci_devices)); for(i=0; i*** lcibuf: failed to allocate device buffers.\n"); return -ENOMEM; } sema_init(&lci_devices[i].sem, 1); } #ifdef CONFIG_DEVFS_FS /* If we have devfs, create /dev/lcibuf to put files in there */ lcibuf_devfs_dir = devfs_mk_dir(NULL, "lcibuf", NULL); if (!lcibuf_devfs_dir) return -EBUSY; /* problem */ #else /* no devfs, do it the "classic" way */ /* * Register your major, and accept a dynamic number. This is the * first thing to do, in order to avoid releasing other module's * fops in lcibuf_cleanup_module() */ if (!disable_lcibuf) { result = register_chrdev(lcibuf_major, "lcibuf", &lcibuf_fops); if (result < 0) { printk(KERN_WARNING "lcibuf: can't get major %d\n",lcibuf_major); return result; } if (0 == lcibuf_major) lcibuf_major = result; /* dynamic */ } #endif /* CONFIG_DEVFS_FS */ #ifdef CONFIG_DEVFS_FS sprintf(devname, "%i", i); devfs_register(lcibuf_devfs_dir, devname, DEVFS_FL_AUTO_DEVNUM, 0, 0, S_IFCHR | S_IRUGO | S_IWUGO, &lcibuf_fops, lcibuf_devices+i); #endif #ifdef MODULE #ifndef LCIBUF_DEBUG EXPORT_NO_SYMBOLS; /* otherwise, leave global symbols visible */ #endif #endif #ifdef LCIBUF_DEBUG /* only when debugging */ lcibuf_create_proc(); #endif return 0; } /* * entry points to be called by module * or by kernel initialization for linking with the kernel */ #ifdef MODULE /* used only if we break this driver out into a separate module static int lcibuf_init_module(void) { return lcibuf_init(); } */ /* * The cleanup function is used to handle initialization failures as well. * Thefore, it must be careful to work correctly even if some of the items * have not been initialized */ static void lcibuf_cleanup_module(void) { int i; /* release any memory allocations */ for(i=0; ihead = dev->tail = dev->pbuf; restore_flags(flags); return 0; } /* * Open and close */ /* In lcibuf_open, the fop_array is used according to TYPE(dev) */ LCI_STATIC int lci_listen_open(struct inode *inode, struct file *filp) { unsigned int device_channel; type_lci_device * dev; struct exser_struct * info; MOD_INC_USE_COUNT; device_channel = MINOR(inode->i_rdev); dev = NULL; info = &exvar_table[device_channel]; if (device_channel < 0 || device_channel >= ELEMENTSOF(lci_devices)) { MOD_DEC_USE_COUNT; return -ENODEV; } if (PORT_TYPE_DEVICE != info->port_type) { MOD_DEC_USE_COUNT; return -ENODEV; } dev = &lci_listen_devices[device_channel]; ++(dev->usage_count); if (down_interruptible(&dev->sem)) { --(dev->usage_count); MOD_DEC_USE_COUNT; return -ERESTARTSYS; } lci_listen_clear_buffer(dev); /* ignore errors */ up(&dev->sem); /* used by read/write */ filp->private_data = dev; return 0; /* success */ } LCI_STATIC int lci_listen_release(struct inode *inode, struct file *filp) { type_lci_device * dev = filp->private_data; if(dev!=NULL && dev->usage_count > 0) --(dev->usage_count); DEBUG_PRINT(DEBUG_LCIBUF_R, DEFAULT_PREFIX" usage_count = %d\n", dev->usage_count); MOD_DEC_USE_COUNT; return 0; } /* * Data management: read and write */ LCI_STATIC ssize_t lci_listen_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) { type_lci_device * dev = filp->private_data; ssize_t ret = 0; unsigned long flags; if (NULL == dev) { return -EFAULT; /* this should never happen... */ /* ??? find a better error */ } if (down_interruptible(&dev->sem)) return -ERESTARTSYS; save_flags(flags); cli(); /* we are trying to read beyond the buffer's size */ if (*f_pos >= dev->size || dev->head == dev->tail) { goto out; } /* ok, data is there, return something */ if (dev->head > dev->tail) ret = count = MIN(count, dev->head - dev->tail); else /* the write pointer has wrapped, return data up to dev->end */ ret = count = MIN(count, dev->pbuf_end+1 - dev->tail); if (copy_to_user(buf, dev->tail, count)) { ret = -EFAULT; goto out; } dev->tail += count; if (dev->tail > dev->pbuf_end) dev->tail = dev->pbuf; /* wrapped */ out: restore_flags(flags); up(&dev->sem); return ret; } LCI_STATIC ssize_t lci_listen_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) { ssize_t ret = count; /* make the write happy */ return ret; } /* * The ioctl() implementation * * - future: some of these maybe useful * * clear * * modify size of buffer * * set/reset the don't buffer while in direct mode flag * * enable/disable debug flag * * enable/disable proc data * * return buffer size * * return number of bytes in the buffer * * return block of buffer * * */ LCI_STATIC int lci_listen_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { int err = 0; int ret = 0; /* * extract the type and number bitfields, and don't decode * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() */ if (_IOC_TYPE(cmd) != LCIBUF_IOC_MAGIC) return -ENOTTY; if (_IOC_NR(cmd) > LCIBUF_IOC_MAXNR) return -ENOTTY; /* * the direction is a bitmask, and VERIFY_WRITE catches R/W * transfers. `Type' is user-oriented, while * access_ok is kernel-oriented, so the concept of "read" and * "write" is reversed */ if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; switch(cmd) { #ifdef LEAVE_OUT /* remove all scull stuff for now */ #ifdef SCULL_DEBUG case SCULL_IOCHARDRESET: /* * reset the counter to 1, to allow unloading in case * of problems. Use 1, not 0, because the invoking * process has the device open. */ while (MOD_IN_USE) MOD_DEC_USE_COUNT; MOD_INC_USE_COUNT; /* don't break: fall through and reset things */ #endif /* SCULL_DEBUG */ case SCULL_IOCRESET: scull_quantum = SCULL_QUANTUM; scull_qset = SCULL_QSET; break; case SCULL_IOCSQUANTUM: /* Set: arg points to the value */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; ret = __get_user(scull_quantum, (int *)arg); break; case SCULL_IOCTQUANTUM: /* Tell: arg is the value */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; scull_quantum = arg; break; case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */ ret = __put_user(scull_quantum, (int *)arg); break; case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */ return scull_quantum; case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; ret = __get_user(scull_quantum, (int *)arg); if (ret == 0) ret = __put_user(tmp, (int *)arg); break; case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; scull_quantum = arg; return tmp; case SCULL_IOCSQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; ret = __get_user(scull_qset, (int *)arg); break; case SCULL_IOCTQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; scull_qset = arg; break; case SCULL_IOCGQSET: ret = __put_user(scull_qset, (int *)arg); break; case SCULL_IOCQQSET: return scull_qset; case SCULL_IOCXQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_qset; ret = __get_user(scull_qset, (int *)arg); if (ret == 0) ret = put_user(tmp, (int *)arg); break; case SCULL_IOCHQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_qset; scull_qset = arg; return tmp; /* * The following two change the buffer size for scullpipe. * The scullpipe device uses this same ioctl method, just to * write less code. Actually, it's the same driver, isn't it? */ case SCULL_P_IOCTSIZE: scull_p_buffer = arg; break; case SCULL_P_IOCQSIZE: return scull_p_buffer; #endif default: /* redundant, as cmd was checked against MAXNR */ return -ENOTTY; } return ret; } unsigned int lci_listen_poll(struct file *filp, poll_table *wait) { type_lci_device *dev = filp->private_data; unsigned int mask = 0; poll_wait(filp, &dev->in_queue, wait); if (dev->head != dev->tail) mask |= POLLIN | POLLRDNORM; /* we have data in the queue */ return mask; } /* * The "extended" operations -- only seek */ LCI_STATIC loff_t lci_listen_llseek(struct file *filp, loff_t off, int whence) { type_lci_device * dev = filp->private_data; loff_t newpos; switch(whence) { case 0: /* SEEK_SET */ newpos = off; break; case 1: /* SEEK_CUR */ newpos = filp->f_pos + off; break; case 2: /* SEEK_END */ if(dev->head == dev->tail) /* buffer empty */ newpos = off; else { if(dev->head > dev->tail) newpos = dev->head - dev->tail + off; else newpos = dev->size + off; } break; default: /* can't happen */ return -EINVAL; } if (newpos<0) return -EINVAL; filp->f_pos = newpos; return newpos; } struct file_operations lci_listen_fops = { llseek: lci_listen_llseek, read: lci_listen_read, write: lci_listen_write, ioctl: lci_listen_ioctl, open: lci_listen_open, release: lci_listen_release, poll: lci_listen_poll, }; static int lci_listen_init(void) { int result, i, m; /* allocate all device buffers */ memset(lci_listen_devices, 0, sizeof(lci_listen_devices)); for(i=0; i*** lcibuf: failed to allocate device buffers.\n"); return -ENOMEM; } sema_init(&lci_listen_devices[i].sem, 1); } #ifdef CONFIG_DEVFS_FS /* If we have devfs, create /dev/lcibuf to put files in there */ lci_listen_devfs_dir = devfs_mk_dir(NULL, "listen", NULL); if (!lci_listen_devfs_dir) return -EBUSY; /* problem */ #else /* no devfs, do it the "classic" way */ /* * Register your major, and accept a dynamic number. This is the * first thing to do, in order to avoid releasing other module's * fops in lcibuf_cleanup_module() */ result = register_chrdev(lci_listen_major, "listen", &lci_listen_fops); if (result < 0) { printk(KERN_WARNING "listen: can't get major %d\n",lci_listen_major); return result; } if (0 == lci_listen_major) lci_listen_major = result; /* dynamic */ #endif /* CONFIG_DEVFS_FS */ #ifdef CONFIG_DEVFS_FS sprintf(devname, "%i", i); devfs_register(lci_listen_devfs_dir, devname, DEVFS_FL_AUTO_DEVNUM, 0, 0, S_IFCHR | S_IRUGO | S_IWUGO, &lci_listen_fops, lci_listen_devices+i); #endif #ifdef MODULE #ifndef LCIBUF_DEBUG EXPORT_NO_SYMBOLS; /* otherwise, leave global symbols visible */ #endif #endif #ifdef LCIBUF_DEBUG /* only when debugging */ lci_listen_create_proc(); #endif return 0; } /* * entry points to be called by module * or by kernel initialization for linking with the kernel */ #ifdef MODULE /* * The cleanup function is used to handle initialization failures as well. * Thefore, it must be careful to work correctly even if some of the items * have not been initialized */ static void lci_listen_cleanup_module(void) { int i; /* release any memory allocations */ for(i=0; i ELEMENTSOF(tens)) j = ELEMENTSOF(tens); // convert to binary from base 10 for (i=0; i=0; ++i, --j) { if (s[i] < '0' || s[i] > '9') break; result += (s[i] - 0x30) * tens[j]; } return result; } ////////////////////////////////////////////////////////////////////////////// ///////////// ///////////////////// ///////////// interface to /proc fs driver ///////////////////// ///////////// ///////////////////// ////////////////////////////////////////////////////////////////////////////// //=========================================================================== //=========================================================================== // // access buffers using proc entries // // /proc/lcibuf/1, /proc/lcibuf/2, ... // //=========================================================================== //=========================================================================== static int proc_open_lcibuf(struct inode * inode, struct file * filep) { char name[10]; // names are the minor numbers of the ports + 1 int length = filep->f_dentry->d_name.len; int dev_number; type_lci_device * dev; int success = 1; strncpy(name, filep->f_dentry->d_name.name, sizeof(name)-1 < length ? sizeof(name) : length); name[sizeof(name)-1] = 0; DEBUG_PRINT(DEBUG_PROC, DEFAULT_PREFIX" open: inode: %lu\n", inode->i_ino); DEBUG_PRINT(DEBUG_PROC, DEFAULT_PREFIX" open: name: \"%s\"\n", name); DEBUG_PRINT(DEBUG_PROC, DEFAULT_PREFIX" open: size: %ld\n", inode->i_size); dev_number = atoi_base10(name); DEBUG_PRINT(DEBUG_PROC, DEFAULT_PREFIX" dev number: %d\n", dev_number); if (dev_number >= 0 && dev_number < ELEMENTSOF(lci_devices)) dev = filep->private_data = &lci_devices[dev_number]; else dev = filep->private_data = NULL; /* clear buffer if open was write-only */ if (dev != NULL) { ++(dev->usage_count); if ( (filep->f_flags & O_ACCMODE) == O_WRONLY) { // here we can clear the buffer if (down_interruptible(&dev->sem)) { --(dev->usage_count); return -ERESTARTSYS; } if (dev->usage_count > 1) { --(dev->usage_count); success = 0; // already in use } if(success) lci_clear_buffer(dev); /* ignore errors */ up(&dev->sem); if(!success) { --(dev->usage_count); DEBUG_PRINT(DEBUG_PROC, DEFAULT_PREFIX" usage_count = %d\n", dev->usage_count); DEBUG_PRINT(DEBUG_PROC, DEFAULT_PREFIX" dev %d\n", dev_number); return -EBUSY; } } } return 0; //return capable(CAP_SYS_RAWIO) ? 0 : -EPERM; } static int proc_close_lcibuf(struct inode * inode, struct file * filep) { type_lci_device * dev = filep->private_data; if(dev!=NULL && dev->usage_count > 0) --(dev->usage_count); DEBUG_PRINT(DEBUG_PROC, DEFAULT_PREFIX" usage_count = %d\n", dev->usage_count); return 0; } static loff_t proc_llseek_lcibuf(struct file *filep, loff_t off, int whence) { return lcibuf_llseek(filep, off, whence); } static ssize_t proc_read_lcibuf(struct file * filep, char * buf, size_t count, loff_t *ppos) { ssize_t size; DEBUG_PRINT(DEBUG_PROC, DEFAULT_PREFIX" read\n"); size = lcibuf_read(filep, buf, count, ppos); DEBUG_PRINT(DEBUG_PROC, DEFAULT_PREFIX" read cnt: %d\n", size); return size; } /* static int proc_readdir_lcibuf (struct file *filep, void *dirent, filldir_t filldir) { DEBUG_PRINT(DEBUG_PROC, DEFAULT_PREFIX" readdir\n"); return 0; } static struct file_operations proc_lcibuf_root_operations = { NULL, // lseek NULL, // read NULL, // write proc_readdir_lcibuf, // readdir NULL, // poll NULL, // ioctl NULL, // mmap NULL // open }; */ static struct file_operations proc_lcibuf_operations = { llseek: proc_llseek_lcibuf, /* llseek */ proc_read_lcibuf, /* read */ NULL, /* write */ NULL, /* readdir */ NULL, /* poll */ NULL, /* ioctl */ NULL, /* mmap */ proc_open_lcibuf, /* open */ release: proc_close_lcibuf, }; //struct inode_operations proc_lcibuf_root_inode_operations = { // &proc_lcibuf_root_operations, //}; struct inode_operations proc_lcibuf_inode_operations = { &proc_lcibuf_operations, }; #ifdef CONFIG_PROC_FS //=========================================================================== // // common exit funtions for the read functions // //=========================================================================== #define PROC_READ_RETURN(page,start,off,count,eof,len) \ { \ len -= off; \ if (len < count) {\ *eof = 1; \ if (len <= 0) \ return 0; \ } else \ len = count; \ *start = page + off;\ return len; \ } #else #define PROC_READ_RETURN(page,start,off,count,eof,len) return 0; #endif //=========================================================================== // // callback function for the history buffer items // //=========================================================================== /*static int lcibuf_proc_return_buffer ( char * page, char ** start, off_t off, int count, int * eof, void * data ) //------------------------------------------------ { char * out = page; int len; struct proc_dir_entry *ent = data; //-------------------------------------------- out += sprintf(out, "my name: %s\n", ent->name); len = out - page; PROC_READ_RETURN(page,start,off,count,eof,len); } // static int return_info(...) */ //=========================================================================== // // module install function // //=========================================================================== void lcibuf_proc_create(void) { int i; struct proc_dir_entry *root, *ent; char bufname[10]; struct exser_struct * port_info_p; //----------------------------------------------------- // // create the root for the lci entries // root = create_proc_entry(LW_PROC_BUFFER_ROOT_NAME, S_IFDIR, 0); if (!root) { PRINT("Failed to create lw proc entry\n"); return; } //----------------------------------------------------- // // add entries for each buffer // for(i=0; iport_type) { sprintf(bufname, "%d", i); ent = create_proc_entry(bufname, S_IFREG|S_IRUSR|S_IRGRP|S_IROTH| S_IWUSR|S_IWGRP|S_IWOTH, root); if (!ent) { printk("LW: failed to create lcibuf/%s proc entry\n", bufname); } else { // register callback function to be called // when the directory item is opened for reading ent->ops = &proc_lcibuf_inode_operations; ent->size = 0; ent->data = ent; //ent->read_proc = &lcibuf_proc_return_buffer; // another way lci_devices[i].ent = ent; DEBUG_PRINT(DEBUG_PROC, DEFAULT_PREFIX" inode: %d\n", ent->low_ino); } } } } // void lcibuf_proc_create(void) //=========================================================================== // // module deinstall function // //=========================================================================== void lcibuf_proc_remove(void) { int i; char bufname[40]; memset(bufname, 0, sizeof(bufname)); for (i=1; i <= lci_serial_channels; i++) { sprintf(bufname, LW_PROC_BUFFER_ROOT_NAME"/%d", i); remove_proc_entry(bufname, 0); } remove_proc_entry(LW_PROC_BUFFER_ROOT_NAME, 0); } // void lcibuf_proc_remove(void) //=========================================================================== // // perform a 16 bit checksum on data passed in, each byte of the // data is added together // //=========================================================================== static U16 do_checksum(void *p, long length) { U16 checkSum = 0,i; for (i=0; i