linux_dsm_epyc7002/drivers/char/esp.c

2590 lines
68 KiB
C
Raw Normal View History

/*
* esp.c - driver for Hayes ESP serial cards
*
* --- Notices from serial.c, upon which this driver is based ---
*
* Copyright (C) 1991, 1992 Linus Torvalds
*
* Extensively rewritten by Theodore Ts'o, 8/16/92 -- 9/14/92. Now
* much more extensible to support other serial cards based on the
* 16450/16550A UART's. Added support for the AST FourPort and the
* Accent Async board.
*
* set_serial_info fixed to set the flags, custom divisor, and uart
* type fields. Fix suggested by Michael K. Johnson 12/12/92.
*
* 11/95: TIOCMIWAIT, TIOCGICOUNT by Angelo Haritsis <ah@doc.ic.ac.uk>
*
* 03/96: Modularised by Angelo Haritsis <ah@doc.ic.ac.uk>
*
* rs_set_termios fixed to look also for changes of the input
* flags INPCK, BRKINT, PARMRK, IGNPAR and IGNBRK.
* Bernd Anh<EFBFBD>pl 05/17/96.
*
* --- End of notices from serial.c ---
*
* Support for the ESP serial card by Andrew J. Robinson
* <arobinso@nyx.net> (Card detection routine taken from a patch
* by Dennis J. Boylan). Patches to allow use with 2.1.x contributed
* by Chris Faylor.
*
* Most recent changes: (Andrew J. Robinson)
* Support for PIO mode. This allows the driver to work properly with
* multiport cards.
*
* Arnaldo Carvalho de Melo <acme@conectiva.com.br> -
* several cleanups, use module_init/module_exit, etc
*
* This module exports the following rs232 io functions:
*
* int espserial_init(void);
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serialP.h>
#include <linux/serial_reg.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/fcntl.h>
#include <linux/ptrace.h>
#include <linux/ioport.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/bitops.h>
#include <asm/dma.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/hayesesp.h>
#define NR_PORTS 64 /* maximum number of ports */
#define NR_PRIMARY 8 /* maximum number of primary ports */
#define REGION_SIZE 8 /* size of io region to request */
/* The following variables can be set by giving module options */
static int irq[NR_PRIMARY]; /* IRQ for each base port */
static unsigned int divisor[NR_PRIMARY]; /* custom divisor for each port */
static unsigned int dma = ESP_DMA_CHANNEL; /* DMA channel */
static unsigned int rx_trigger = ESP_RX_TRIGGER;
static unsigned int tx_trigger = ESP_TX_TRIGGER;
static unsigned int flow_off = ESP_FLOW_OFF;
static unsigned int flow_on = ESP_FLOW_ON;
static unsigned int rx_timeout = ESP_RX_TMOUT;
static unsigned int pio_threshold = ESP_PIO_THRESHOLD;
MODULE_LICENSE("GPL");
module_param_array(irq, int, NULL, 0);
module_param_array(divisor, uint, NULL, 0);
module_param(dma, uint, 0);
module_param(rx_trigger, uint, 0);
module_param(tx_trigger, uint, 0);
module_param(flow_off, uint, 0);
module_param(flow_on, uint, 0);
module_param(rx_timeout, uint, 0);
module_param(pio_threshold, uint, 0);
/* END */
static char *dma_buffer;
static int dma_bytes;
static struct esp_pio_buffer *free_pio_buf;
#define DMA_BUFFER_SZ 1024
#define WAKEUP_CHARS 1024
static char serial_name[] __initdata = "ESP serial driver";
static char serial_version[] __initdata = "2.2";
static struct tty_driver *esp_driver;
/* serial subtype definitions */
#define SERIAL_TYPE_NORMAL 1
/*
* Serial driver configuration section. Here are the various options:
*
* SERIAL_PARANOIA_CHECK
* Check the magic number for the esp_structure where
* ever possible.
*/
#undef SERIAL_PARANOIA_CHECK
#define SERIAL_DO_RESTART
#undef SERIAL_DEBUG_INTR
#undef SERIAL_DEBUG_OPEN
#undef SERIAL_DEBUG_FLOW
#if defined(MODULE) && defined(SERIAL_DEBUG_MCOUNT)
#define DBG_CNT(s) printk("(%s): [%x] refc=%d, serc=%d, ttyc=%d -> %s\n", \
tty->name, (info->flags), serial_driver.refcount,info->count,tty->count,s)
#else
#define DBG_CNT(s)
#endif
static struct esp_struct *ports;
static void change_speed(struct esp_struct *info);
static void rs_wait_until_sent(struct tty_struct *, int);
/*
* The ESP card has a clock rate of 14.7456 MHz (that is, 2**ESPC_SCALE
* times the normal 1.8432 Mhz clock of most serial boards).
*/
#define BASE_BAUD ((1843200 / 16) * (1 << ESPC_SCALE))
/* Standard COM flags (except for COM4, because of the 8514 problem) */
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST)
static inline int serial_paranoia_check(struct esp_struct *info,
char *name, const char *routine)
{
#ifdef SERIAL_PARANOIA_CHECK
static const char badmagic[] = KERN_WARNING
"Warning: bad magic number for serial struct (%s) in %s\n";
static const char badinfo[] = KERN_WARNING
"Warning: null esp_struct for (%s) in %s\n";
if (!info) {
printk(badinfo, name, routine);
return 1;
}
if (info->magic != ESP_MAGIC) {
printk(badmagic, name, routine);
return 1;
}
#endif
return 0;
}
static inline unsigned int serial_in(struct esp_struct *info, int offset)
{
return inb(info->port + offset);
}
static inline void serial_out(struct esp_struct *info, int offset,
unsigned char value)
{
outb(value, info->port+offset);
}
/*
* ------------------------------------------------------------
* rs_stop() and rs_start()
*
* This routines are called before setting or resetting tty->stopped.
* They enable or disable transmitter interrupts, as necessary.
* ------------------------------------------------------------
*/
static void rs_stop(struct tty_struct *tty)
{
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, "rs_stop"))
return;
spin_lock_irqsave(&info->lock, flags);
if (info->IER & UART_IER_THRI) {
info->IER &= ~UART_IER_THRI;
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, info->IER);
}
spin_unlock_irqrestore(&info->lock, flags);
}
static void rs_start(struct tty_struct *tty)
{
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, "rs_start"))
return;
spin_lock_irqsave(&info->lock, flags);
if (info->xmit_cnt && info->xmit_buf && !(info->IER & UART_IER_THRI)) {
info->IER |= UART_IER_THRI;
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, info->IER);
}
spin_unlock_irqrestore(&info->lock, flags);
}
/*
* ----------------------------------------------------------------------
*
* Here starts the interrupt handling routines. All of the following
* subroutines are declared as inline and are folded into
* rs_interrupt(). They were separated out for readability's sake.
*
* Note: rs_interrupt() is a "fast" interrupt, which means that it
* runs with interrupts turned off. People who may want to modify
* rs_interrupt() should try to keep the interrupt handler as fast as
* possible. After you are done making modifications, it is not a bad
* idea to do:
*
* gcc -S -DKERNEL -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer serial.c
*
* and look at the resulting assemble code in serial.s.
*
* - Ted Ts'o (tytso@mit.edu), 7-Mar-93
* -----------------------------------------------------------------------
*/
/*
* This routine is used by the interrupt handler to schedule
* processing in the software interrupt portion of the driver.
*/
static inline void rs_sched_event(struct esp_struct *info,
int event)
{
info->event |= 1 << event;
schedule_work(&info->tqueue);
}
static DEFINE_SPINLOCK(pio_lock);
static inline struct esp_pio_buffer *get_pio_buffer(void)
{
struct esp_pio_buffer *buf;
unsigned long flags;
spin_lock_irqsave(&pio_lock, flags);
if (free_pio_buf) {
buf = free_pio_buf;
free_pio_buf = buf->next;
} else {
buf = kmalloc(sizeof(struct esp_pio_buffer), GFP_ATOMIC);
}
spin_unlock_irqrestore(&pio_lock, flags);
return buf;
}
static inline void release_pio_buffer(struct esp_pio_buffer *buf)
{
unsigned long flags;
spin_lock_irqsave(&pio_lock, flags);
buf->next = free_pio_buf;
free_pio_buf = buf;
spin_unlock_irqrestore(&pio_lock, flags);
}
static inline void receive_chars_pio(struct esp_struct *info, int num_bytes)
{
struct tty_struct *tty = info->tty;
int i;
struct esp_pio_buffer *pio_buf;
struct esp_pio_buffer *err_buf;
unsigned char status_mask;
pio_buf = get_pio_buffer();
if (!pio_buf)
return;
err_buf = get_pio_buffer();
if (!err_buf) {
release_pio_buffer(pio_buf);
return;
}
status_mask = (info->read_status_mask >> 2) & 0x07;
for (i = 0; i < num_bytes - 1; i += 2) {
*((unsigned short *)(pio_buf->data + i)) =
inw(info->port + UART_ESI_RX);
err_buf->data[i] = serial_in(info, UART_ESI_RWS);
err_buf->data[i + 1] = (err_buf->data[i] >> 3) & status_mask;
err_buf->data[i] &= status_mask;
}
if (num_bytes & 0x0001) {
pio_buf->data[num_bytes - 1] = serial_in(info, UART_ESI_RX);
err_buf->data[num_bytes - 1] =
(serial_in(info, UART_ESI_RWS) >> 3) & status_mask;
}
/* make sure everything is still ok since interrupts were enabled */
tty = info->tty;
if (!tty) {
release_pio_buffer(pio_buf);
release_pio_buffer(err_buf);
info->stat_flags &= ~ESP_STAT_RX_TIMEOUT;
return;
}
status_mask = (info->ignore_status_mask >> 2) & 0x07;
for (i = 0; i < num_bytes; i++) {
if (!(err_buf->data[i] & status_mask)) {
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 11:54:13 +07:00
int flag = 0;
if (err_buf->data[i] & 0x04) {
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 11:54:13 +07:00
flag = TTY_BREAK;
if (info->flags & ASYNC_SAK)
do_SAK(tty);
}
else if (err_buf->data[i] & 0x02)
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 11:54:13 +07:00
flag = TTY_FRAME;
else if (err_buf->data[i] & 0x01)
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 11:54:13 +07:00
flag = TTY_PARITY;
tty_insert_flip_char(tty, pio_buf->data[i], flag);
}
}
tty_schedule_flip(tty);
info->stat_flags &= ~ESP_STAT_RX_TIMEOUT;
release_pio_buffer(pio_buf);
release_pio_buffer(err_buf);
}
static inline void receive_chars_dma(struct esp_struct *info, int num_bytes)
{
unsigned long flags;
info->stat_flags &= ~ESP_STAT_RX_TIMEOUT;
dma_bytes = num_bytes;
info->stat_flags |= ESP_STAT_DMA_RX;
flags=claim_dma_lock();
disable_dma(dma);
clear_dma_ff(dma);
set_dma_mode(dma, DMA_MODE_READ);
set_dma_addr(dma, isa_virt_to_bus(dma_buffer));
set_dma_count(dma, dma_bytes);
enable_dma(dma);
release_dma_lock(flags);
serial_out(info, UART_ESI_CMD1, ESI_START_DMA_RX);
}
static inline void receive_chars_dma_done(struct esp_struct *info,
int status)
{
struct tty_struct *tty = info->tty;
int num_bytes;
unsigned long flags;
flags=claim_dma_lock();
disable_dma(dma);
clear_dma_ff(dma);
info->stat_flags &= ~ESP_STAT_DMA_RX;
num_bytes = dma_bytes - get_dma_residue(dma);
release_dma_lock(flags);
info->icount.rx += num_bytes;
if (num_bytes > 0) {
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 11:54:13 +07:00
tty_insert_flip_string(tty, dma_buffer, num_bytes - 1);
status &= (0x1c & info->read_status_mask);
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 11:54:13 +07:00
/* Is the status significant or do we throw the last byte ? */
if (!(status & info->ignore_status_mask)) {
int statflag = 0;
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 11:54:13 +07:00
if (status & 0x10) {
statflag = TTY_BREAK;
(info->icount.brk)++;
if (info->flags & ASYNC_SAK)
do_SAK(tty);
} else if (status & 0x08) {
statflag = TTY_FRAME;
(info->icount.frame)++;
}
else if (status & 0x04) {
statflag = TTY_PARITY;
(info->icount.parity)++;
}
tty_insert_flip_char(tty, dma_buffer[num_bytes - 1], statflag);
}
tty_schedule_flip(tty);
}
if (dma_bytes != num_bytes) {
num_bytes = dma_bytes - num_bytes;
dma_bytes = 0;
receive_chars_dma(info, num_bytes);
} else
dma_bytes = 0;
}
/* Caller must hold info->lock */
static inline void transmit_chars_pio(struct esp_struct *info,
int space_avail)
{
int i;
struct esp_pio_buffer *pio_buf;
pio_buf = get_pio_buffer();
if (!pio_buf)
return;
while (space_avail && info->xmit_cnt) {
if (info->xmit_tail + space_avail <= ESP_XMIT_SIZE) {
memcpy(pio_buf->data,
&(info->xmit_buf[info->xmit_tail]),
space_avail);
} else {
i = ESP_XMIT_SIZE - info->xmit_tail;
memcpy(pio_buf->data,
&(info->xmit_buf[info->xmit_tail]), i);
memcpy(&(pio_buf->data[i]), info->xmit_buf,
space_avail - i);
}
info->xmit_cnt -= space_avail;
info->xmit_tail = (info->xmit_tail + space_avail) &
(ESP_XMIT_SIZE - 1);
for (i = 0; i < space_avail - 1; i += 2) {
outw(*((unsigned short *)(pio_buf->data + i)),
info->port + UART_ESI_TX);
}
if (space_avail & 0x0001)
serial_out(info, UART_ESI_TX,
pio_buf->data[space_avail - 1]);
if (info->xmit_cnt) {
serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
space_avail = serial_in(info, UART_ESI_STAT1) << 8;
space_avail |= serial_in(info, UART_ESI_STAT2);
if (space_avail > info->xmit_cnt)
space_avail = info->xmit_cnt;
}
}
if (info->xmit_cnt < WAKEUP_CHARS) {
rs_sched_event(info, ESP_EVENT_WRITE_WAKEUP);
#ifdef SERIAL_DEBUG_INTR
printk("THRE...");
#endif
if (info->xmit_cnt <= 0) {
info->IER &= ~UART_IER_THRI;
serial_out(info, UART_ESI_CMD1,
ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, info->IER);
}
}
release_pio_buffer(pio_buf);
}
/* Caller must hold info->lock */
static inline void transmit_chars_dma(struct esp_struct *info, int num_bytes)
{
unsigned long flags;
dma_bytes = num_bytes;
if (info->xmit_tail + dma_bytes <= ESP_XMIT_SIZE) {
memcpy(dma_buffer, &(info->xmit_buf[info->xmit_tail]),
dma_bytes);
} else {
int i = ESP_XMIT_SIZE - info->xmit_tail;
memcpy(dma_buffer, &(info->xmit_buf[info->xmit_tail]),
i);
memcpy(&(dma_buffer[i]), info->xmit_buf, dma_bytes - i);
}
info->xmit_cnt -= dma_bytes;
info->xmit_tail = (info->xmit_tail + dma_bytes) & (ESP_XMIT_SIZE - 1);
if (info->xmit_cnt < WAKEUP_CHARS) {
rs_sched_event(info, ESP_EVENT_WRITE_WAKEUP);
#ifdef SERIAL_DEBUG_INTR
printk("THRE...");
#endif
if (info->xmit_cnt <= 0) {
info->IER &= ~UART_IER_THRI;
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, info->IER);
}
}
info->stat_flags |= ESP_STAT_DMA_TX;
flags=claim_dma_lock();
disable_dma(dma);
clear_dma_ff(dma);
set_dma_mode(dma, DMA_MODE_WRITE);
set_dma_addr(dma, isa_virt_to_bus(dma_buffer));
set_dma_count(dma, dma_bytes);
enable_dma(dma);
release_dma_lock(flags);
serial_out(info, UART_ESI_CMD1, ESI_START_DMA_TX);
}
static inline void transmit_chars_dma_done(struct esp_struct *info)
{
int num_bytes;
unsigned long flags;
flags=claim_dma_lock();
disable_dma(dma);
clear_dma_ff(dma);
num_bytes = dma_bytes - get_dma_residue(dma);
info->icount.tx += dma_bytes;
release_dma_lock(flags);
if (dma_bytes != num_bytes) {
dma_bytes -= num_bytes;
memmove(dma_buffer, dma_buffer + num_bytes, dma_bytes);
flags=claim_dma_lock();
disable_dma(dma);
clear_dma_ff(dma);
set_dma_mode(dma, DMA_MODE_WRITE);
set_dma_addr(dma, isa_virt_to_bus(dma_buffer));
set_dma_count(dma, dma_bytes);
enable_dma(dma);
release_dma_lock(flags);
serial_out(info, UART_ESI_CMD1, ESI_START_DMA_TX);
} else {
dma_bytes = 0;
info->stat_flags &= ~ESP_STAT_DMA_TX;
}
}
static inline void check_modem_status(struct esp_struct *info)
{
int status;
serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
status = serial_in(info, UART_ESI_STAT2);
if (status & UART_MSR_ANY_DELTA) {
/* 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 (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR))
printk("ttys%d CD now %s...", info->line,
(status & UART_MSR_DCD) ? "on" : "off");
#endif
if (status & UART_MSR_DCD)
wake_up_interruptible(&info->open_wait);
else {
#ifdef SERIAL_DEBUG_OPEN
printk("scheduling hangup...");
#endif
schedule_work(&info->tqueue_hangup);
}
}
}
/*
* This is the serial driver's interrupt routine
*/
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 20:55:46 +07:00
static irqreturn_t rs_interrupt_single(int irq, void *dev_id)
{
struct esp_struct * info;
unsigned err_status;
unsigned int scratch;
#ifdef SERIAL_DEBUG_INTR
printk("rs_interrupt_single(%d)...", irq);
#endif
info = (struct esp_struct *)dev_id;
err_status = 0;
scratch = serial_in(info, UART_ESI_SID);
spin_lock(&info->lock);
if (!info->tty) {
spin_unlock(&info->lock);
return IRQ_NONE;
}
if (scratch & 0x04) { /* error */
serial_out(info, UART_ESI_CMD1, ESI_GET_ERR_STAT);
err_status = serial_in(info, UART_ESI_STAT1);
serial_in(info, UART_ESI_STAT2);
if (err_status & 0x01)
info->stat_flags |= ESP_STAT_RX_TIMEOUT;
if (err_status & 0x20) /* UART status */
check_modem_status(info);
if (err_status & 0x80) /* Start break */
wake_up_interruptible(&info->break_wait);
}
if ((scratch & 0x88) || /* DMA completed or timed out */
(err_status & 0x1c) /* receive error */) {
if (info->stat_flags & ESP_STAT_DMA_RX)
receive_chars_dma_done(info, err_status);
else if (info->stat_flags & ESP_STAT_DMA_TX)
transmit_chars_dma_done(info);
}
if (!(info->stat_flags & (ESP_STAT_DMA_RX | ESP_STAT_DMA_TX)) &&
((scratch & 0x01) || (info->stat_flags & ESP_STAT_RX_TIMEOUT)) &&
(info->IER & UART_IER_RDI)) {
int num_bytes;
serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
serial_out(info, UART_ESI_CMD1, ESI_GET_RX_AVAIL);
num_bytes = serial_in(info, UART_ESI_STAT1) << 8;
num_bytes |= serial_in(info, UART_ESI_STAT2);
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 11:54:13 +07:00
num_bytes = tty_buffer_request_room(info->tty, num_bytes);
if (num_bytes) {
if (dma_bytes ||
(info->stat_flags & ESP_STAT_USE_PIO) ||
(num_bytes <= info->config.pio_threshold))
receive_chars_pio(info, num_bytes);
else
receive_chars_dma(info, num_bytes);
}
}
if (!(info->stat_flags & (ESP_STAT_DMA_RX | ESP_STAT_DMA_TX)) &&
(scratch & 0x02) && (info->IER & UART_IER_THRI)) {
if ((info->xmit_cnt <= 0) || info->tty->stopped) {
info->IER &= ~UART_IER_THRI;
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, info->IER);
} else {
int num_bytes;
serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
num_bytes = serial_in(info, UART_ESI_STAT1) << 8;
num_bytes |= serial_in(info, UART_ESI_STAT2);
if (num_bytes > info->xmit_cnt)
num_bytes = info->xmit_cnt;
if (num_bytes) {
if (dma_bytes ||
(info->stat_flags & ESP_STAT_USE_PIO) ||
(num_bytes <= info->config.pio_threshold))
transmit_chars_pio(info, num_bytes);
else
transmit_chars_dma(info, num_bytes);
}
}
}
info->last_active = jiffies;
#ifdef SERIAL_DEBUG_INTR
printk("end.\n");
#endif
spin_unlock(&info->lock);
return IRQ_HANDLED;
}
/*
* -------------------------------------------------------------------
* Here ends the serial interrupt routines.
* -------------------------------------------------------------------
*/
static void do_softint(struct work_struct *work)
{
struct esp_struct *info =
container_of(work, struct esp_struct, tqueue);
struct tty_struct *tty;
tty = info->tty;
if (!tty)
return;
if (test_and_clear_bit(ESP_EVENT_WRITE_WAKEUP, &info->event)) {
tty_wakeup(tty);
}
}
/*
* This routine is called from the scheduler tqueue when the interrupt
* routine has signalled that a hangup has occurred. The path of
* hangup processing is:
*
* serial interrupt routine -> (scheduler tqueue) ->
* do_serial_hangup() -> tty->hangup() -> esp_hangup()
*
*/
static void do_serial_hangup(struct work_struct *work)
{
struct esp_struct *info =
container_of(work, struct esp_struct, tqueue_hangup);
struct tty_struct *tty;
tty = info->tty;
if (tty)
tty_hangup(tty);
}
/*
* ---------------------------------------------------------------
* Low level utility subroutines for the serial driver: routines to
* figure out the appropriate timeout for an interrupt chain, routines
* to initialize and startup a serial port, and routines to shutdown a
* serial port. Useful stuff like that.
*
* Caller should hold lock
* ---------------------------------------------------------------
*/
static inline void esp_basic_init(struct esp_struct * info)
{
/* put ESPC in enhanced mode */
serial_out(info, UART_ESI_CMD1, ESI_SET_MODE);
if (info->stat_flags & ESP_STAT_NEVER_DMA)
serial_out(info, UART_ESI_CMD2, 0x01);
else
serial_out(info, UART_ESI_CMD2, 0x31);
/* disable interrupts for now */
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, 0x00);
/* set interrupt and DMA channel */
serial_out(info, UART_ESI_CMD1, ESI_SET_IRQ);
if (info->stat_flags & ESP_STAT_NEVER_DMA)
serial_out(info, UART_ESI_CMD2, 0x01);
else
serial_out(info, UART_ESI_CMD2, (dma << 4) | 0x01);
serial_out(info, UART_ESI_CMD1, ESI_SET_ENH_IRQ);
if (info->line % 8) /* secondary port */
serial_out(info, UART_ESI_CMD2, 0x0d); /* shared */
else if (info->irq == 9)
serial_out(info, UART_ESI_CMD2, 0x02);
else
serial_out(info, UART_ESI_CMD2, info->irq);
/* set error status mask (check this) */
serial_out(info, UART_ESI_CMD1, ESI_SET_ERR_MASK);
if (info->stat_flags & ESP_STAT_NEVER_DMA)
serial_out(info, UART_ESI_CMD2, 0xa1);
else
serial_out(info, UART_ESI_CMD2, 0xbd);
serial_out(info, UART_ESI_CMD2, 0x00);
/* set DMA timeout */
serial_out(info, UART_ESI_CMD1, ESI_SET_DMA_TMOUT);
serial_out(info, UART_ESI_CMD2, 0xff);
/* set FIFO trigger levels */
serial_out(info, UART_ESI_CMD1, ESI_SET_TRIGGER);
serial_out(info, UART_ESI_CMD2, info->config.rx_trigger >> 8);
serial_out(info, UART_ESI_CMD2, info->config.rx_trigger);
serial_out(info, UART_ESI_CMD2, info->config.tx_trigger >> 8);
serial_out(info, UART_ESI_CMD2, info->config.tx_trigger);
/* Set clock scaling and wait states */
serial_out(info, UART_ESI_CMD1, ESI_SET_PRESCALAR);
serial_out(info, UART_ESI_CMD2, 0x04 | ESPC_SCALE);
/* set reinterrupt pacing */
serial_out(info, UART_ESI_CMD1, ESI_SET_REINTR);
serial_out(info, UART_ESI_CMD2, 0xff);
}
static int startup(struct esp_struct * info)
{
unsigned long flags;
int retval=0;
unsigned int num_chars;
spin_lock_irqsave(&info->lock, flags);
if (info->flags & ASYNC_INITIALIZED)
goto out;
if (!info->xmit_buf) {
info->xmit_buf = (unsigned char *)get_zeroed_page(GFP_ATOMIC);
retval = -ENOMEM;
if (!info->xmit_buf)
goto out;
}
#ifdef SERIAL_DEBUG_OPEN
printk("starting up ttys%d (irq %d)...", info->line, info->irq);
#endif
/* Flush the RX buffer. Using the ESI flush command may cause */
/* wild interrupts, so read all the data instead. */
serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
serial_out(info, UART_ESI_CMD1, ESI_GET_RX_AVAIL);
num_chars = serial_in(info, UART_ESI_STAT1) << 8;
num_chars |= serial_in(info, UART_ESI_STAT2);
while (num_chars > 1) {
inw(info->port + UART_ESI_RX);
num_chars -= 2;
}
if (num_chars)
serial_in(info, UART_ESI_RX);
/* set receive character timeout */
serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
serial_out(info, UART_ESI_CMD2, info->config.rx_timeout);
/* clear all flags except the "never DMA" flag */
info->stat_flags &= ESP_STAT_NEVER_DMA;
if (info->stat_flags & ESP_STAT_NEVER_DMA)
info->stat_flags |= ESP_STAT_USE_PIO;
spin_unlock_irqrestore(&info->lock, flags);
/*
* Allocate the IRQ
*/
retval = request_irq(info->irq, rs_interrupt_single, IRQF_SHARED,
"esp serial", info);
if (retval) {
if (capable(CAP_SYS_ADMIN)) {
if (info->tty)
set_bit(TTY_IO_ERROR,
&info->tty->flags);
retval = 0;
}
goto out_unlocked;
}
if (!(info->stat_flags & ESP_STAT_USE_PIO) && !dma_buffer) {
dma_buffer = (char *)__get_dma_pages(
GFP_KERNEL, get_order(DMA_BUFFER_SZ));
/* use PIO mode if DMA buf/chan cannot be allocated */
if (!dma_buffer)
info->stat_flags |= ESP_STAT_USE_PIO;
else if (request_dma(dma, "esp serial")) {
free_pages((unsigned long)dma_buffer,
get_order(DMA_BUFFER_SZ));
dma_buffer = NULL;
info->stat_flags |= ESP_STAT_USE_PIO;
}
}
info->MCR = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2;
spin_lock_irqsave(&info->lock, flags);
serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
serial_out(info, UART_ESI_CMD2, UART_MCR);
serial_out(info, UART_ESI_CMD2, info->MCR);
/*
* Finally, enable interrupts
*/
/* info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI; */
info->IER = UART_IER_RLSI | UART_IER_RDI | UART_IER_DMA_TMOUT |
UART_IER_DMA_TC;
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, info->IER);
if (info->tty)
clear_bit(TTY_IO_ERROR, &info->tty->flags);
info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
spin_unlock_irqrestore(&info->lock, flags);
/*
* Set up the tty->alt_speed kludge
*/
if (info->tty) {
if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
info->tty->alt_speed = 57600;
if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
info->tty->alt_speed = 115200;
if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
info->tty->alt_speed = 230400;
if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
info->tty->alt_speed = 460800;
}
/*
* set the speed of the serial port
*/
change_speed(info);
info->flags |= ASYNC_INITIALIZED;
return 0;
out:
spin_unlock_irqrestore(&info->lock, flags);
out_unlocked:
return retval;
}
/*
* This routine will shutdown a serial port; interrupts are disabled, and
* DTR is dropped if the hangup on close termio flag is on.
*/
static void shutdown(struct esp_struct * info)
{
unsigned long flags, f;
if (!(info->flags & ASYNC_INITIALIZED))
return;
#ifdef SERIAL_DEBUG_OPEN
printk("Shutting down serial port %d (irq %d)....", info->line,
info->irq);
#endif
spin_lock_irqsave(&info->lock, flags);
/*
* 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);
wake_up_interruptible(&info->break_wait);
/* stop a DMA transfer on the port being closed */
/* DMA lock is higher priority always */
if (info->stat_flags & (ESP_STAT_DMA_RX | ESP_STAT_DMA_TX)) {
f=claim_dma_lock();
disable_dma(dma);
clear_dma_ff(dma);
release_dma_lock(f);
dma_bytes = 0;
}
/*
* Free the IRQ
*/
free_irq(info->irq, info);
if (dma_buffer) {
struct esp_struct *current_port = ports;
while (current_port) {
if ((current_port != info) &&
(current_port->flags & ASYNC_INITIALIZED))
break;
current_port = current_port->next_port;
}
if (!current_port) {
free_dma(dma);
free_pages((unsigned long)dma_buffer,
get_order(DMA_BUFFER_SZ));
dma_buffer = NULL;
}
}
if (info->xmit_buf) {
free_page((unsigned long) info->xmit_buf);
info->xmit_buf = NULL;
}
info->IER = 0;
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, 0x00);
if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS);
info->MCR &= ~UART_MCR_OUT2;
serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
serial_out(info, UART_ESI_CMD2, UART_MCR);
serial_out(info, UART_ESI_CMD2, info->MCR);
if (info->tty)
set_bit(TTY_IO_ERROR, &info->tty->flags);
info->flags &= ~ASYNC_INITIALIZED;
spin_unlock_irqrestore(&info->lock, flags);
}
/*
* This routine is called to set the UART divisor registers to match
* the specified baud rate for a serial port.
*/
static void change_speed(struct esp_struct *info)
{
unsigned short port;
int quot = 0;
unsigned cflag,cval;
int baud, bits;
unsigned char flow1 = 0, flow2 = 0;
unsigned long flags;
if (!info->tty || !info->tty->termios)
return;
cflag = info->tty->termios->c_cflag;
port = info->port;
/* byte size and parity */
switch (cflag & CSIZE) {
case CS5: cval = 0x00; bits = 7; break;
case CS6: cval = 0x01; bits = 8; break;
case CS7: cval = 0x02; bits = 9; break;
case CS8: cval = 0x03; bits = 10; break;
default: cval = 0x00; bits = 7; break;
}
if (cflag & CSTOPB) {
cval |= 0x04;
bits++;
}
if (cflag & PARENB) {
cval |= UART_LCR_PARITY;
bits++;
}
if (!(cflag & PARODD))
cval |= UART_LCR_EPAR;
#ifdef CMSPAR
if (cflag & CMSPAR)
cval |= UART_LCR_SPAR;
#endif
baud = tty_get_baud_rate(info->tty);
if (baud == 38400 &&
((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST))
quot = info->custom_divisor;
else {
if (baud == 134)
/* Special case since 134 is really 134.5 */
quot = (2*BASE_BAUD / 269);
else if (baud)
quot = BASE_BAUD / baud;
}
/* If the quotient is ever zero, default to 9600 bps */
if (!quot)
quot = BASE_BAUD / 9600;
info->timeout = ((1024 * HZ * bits * quot) / BASE_BAUD) + (HZ / 50);
/* CTS flow control flag and modem status interrupts */
/* info->IER &= ~UART_IER_MSI; */
if (cflag & CRTSCTS) {
info->flags |= ASYNC_CTS_FLOW;
/* info->IER |= UART_IER_MSI; */
flow1 = 0x04;
flow2 = 0x10;
} else
info->flags &= ~ASYNC_CTS_FLOW;
if (cflag & CLOCAL)
info->flags &= ~ASYNC_CHECK_CD;
else {
info->flags |= ASYNC_CHECK_CD;
/* info->IER |= UART_IER_MSI; */
}
/*
* Set up parity check flag
*/
info->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
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
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;
}
}
if (I_IXOFF(info->tty))
flow1 |= 0x81;
spin_lock_irqsave(&info->lock, flags);
/* set baud */
serial_out(info, UART_ESI_CMD1, ESI_SET_BAUD);
serial_out(info, UART_ESI_CMD2, quot >> 8);
serial_out(info, UART_ESI_CMD2, quot & 0xff);
/* set data bits, parity, etc. */
serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
serial_out(info, UART_ESI_CMD2, UART_LCR);
serial_out(info, UART_ESI_CMD2, cval);
/* Enable flow control */
serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_CNTL);
serial_out(info, UART_ESI_CMD2, flow1);
serial_out(info, UART_ESI_CMD2, flow2);
/* set flow control characters (XON/XOFF only) */
if (I_IXOFF(info->tty)) {
serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_CHARS);
serial_out(info, UART_ESI_CMD2, START_CHAR(info->tty));
serial_out(info, UART_ESI_CMD2, STOP_CHAR(info->tty));
serial_out(info, UART_ESI_CMD2, 0x10);
serial_out(info, UART_ESI_CMD2, 0x21);
switch (cflag & CSIZE) {
case CS5:
serial_out(info, UART_ESI_CMD2, 0x1f);
break;
case CS6:
serial_out(info, UART_ESI_CMD2, 0x3f);
break;
case CS7:
case CS8:
serial_out(info, UART_ESI_CMD2, 0x7f);
break;
default:
serial_out(info, UART_ESI_CMD2, 0xff);
break;
}
}
/* Set high/low water */
serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_LVL);
serial_out(info, UART_ESI_CMD2, info->config.flow_off >> 8);
serial_out(info, UART_ESI_CMD2, info->config.flow_off);
serial_out(info, UART_ESI_CMD2, info->config.flow_on >> 8);
serial_out(info, UART_ESI_CMD2, info->config.flow_on);
spin_unlock_irqrestore(&info->lock, flags);
}
static void rs_put_char(struct tty_struct *tty, unsigned char ch)
{
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, "rs_put_char"))
return;
if (!info->xmit_buf)
return;
spin_lock_irqsave(&info->lock, flags);
if (info->xmit_cnt < ESP_XMIT_SIZE - 1) {
info->xmit_buf[info->xmit_head++] = ch;
info->xmit_head &= ESP_XMIT_SIZE-1;
info->xmit_cnt++;
}
spin_unlock_irqrestore(&info->lock, flags);
}
static void rs_flush_chars(struct tty_struct *tty)
{
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, "rs_flush_chars"))
return;
spin_lock_irqsave(&info->lock, flags);
if (info->xmit_cnt <= 0 || tty->stopped || !info->xmit_buf)
goto out;
if (!(info->IER & UART_IER_THRI)) {
info->IER |= UART_IER_THRI;
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, info->IER);
}
out:
spin_unlock_irqrestore(&info->lock, flags);
}
static int rs_write(struct tty_struct * tty,
const unsigned char *buf, int count)
{
int c, t, ret = 0;
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, "rs_write"))
return 0;
if (!info->xmit_buf)
return 0;
while (1) {
/* Thanks to R. Wolff for suggesting how to do this with */
/* interrupts enabled */
c = count;
t = ESP_XMIT_SIZE - info->xmit_cnt - 1;
if (t < c)
c = t;
t = ESP_XMIT_SIZE - info->xmit_head;
if (t < c)
c = t;
if (c <= 0)
break;
memcpy(info->xmit_buf + info->xmit_head, buf, c);
info->xmit_head = (info->xmit_head + c) & (ESP_XMIT_SIZE-1);
info->xmit_cnt += c;
buf += c;
count -= c;
ret += c;
}
spin_lock_irqsave(&info->lock, flags);
if (info->xmit_cnt && !tty->stopped && !(info->IER & UART_IER_THRI)) {
info->IER |= UART_IER_THRI;
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, info->IER);
}
spin_unlock_irqrestore(&info->lock, flags);
return ret;
}
static int rs_write_room(struct tty_struct *tty)
{
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
int ret;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, "rs_write_room"))
return 0;
spin_lock_irqsave(&info->lock, flags);
ret = ESP_XMIT_SIZE - info->xmit_cnt - 1;
if (ret < 0)
ret = 0;
spin_unlock_irqrestore(&info->lock, flags);
return ret;
}
static int rs_chars_in_buffer(struct tty_struct *tty)
{
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
if (serial_paranoia_check(info, tty->name, "rs_chars_in_buffer"))
return 0;
return info->xmit_cnt;
}
static void rs_flush_buffer(struct tty_struct *tty)
{
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, "rs_flush_buffer"))
return;
spin_lock_irqsave(&info->lock, flags);
info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
spin_unlock_irqrestore(&info->lock, flags);
tty_wakeup(tty);
}
/*
* ------------------------------------------------------------
* rs_throttle()
*
* This routine is called by the upper-layer tty layer to signal that
* incoming characters should be throttled.
* ------------------------------------------------------------
*/
static void rs_throttle(struct tty_struct * tty)
{
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
#ifdef SERIAL_DEBUG_THROTTLE
char buf[64];
printk("throttle %s: %d....\n", tty_name(tty, buf),
tty->ldisc.chars_in_buffer(tty));
#endif
if (serial_paranoia_check(info, tty->name, "rs_throttle"))
return;
spin_lock_irqsave(&info->lock, flags);
info->IER &= ~UART_IER_RDI;
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, info->IER);
serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
serial_out(info, UART_ESI_CMD2, 0x00);
spin_unlock_irqrestore(&info->lock, flags);
}
static void rs_unthrottle(struct tty_struct * tty)
{
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
#ifdef SERIAL_DEBUG_THROTTLE
char buf[64];
printk("unthrottle %s: %d....\n", tty_name(tty, buf),
tty->ldisc.chars_in_buffer(tty));
#endif
if (serial_paranoia_check(info, tty->name, "rs_unthrottle"))
return;
spin_lock_irqsave(&info->lock, flags);
info->IER |= UART_IER_RDI;
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, info->IER);
serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
serial_out(info, UART_ESI_CMD2, info->config.rx_timeout);
spin_unlock_irqrestore(&info->lock, flags);
}
/*
* ------------------------------------------------------------
* rs_ioctl() and friends
* ------------------------------------------------------------
*/
static int get_serial_info(struct esp_struct * info,
struct serial_struct __user *retinfo)
{
struct serial_struct tmp;
memset(&tmp, 0, sizeof(tmp));
tmp.type = PORT_16550A;
tmp.line = info->line;
tmp.port = info->port;
tmp.irq = info->irq;
tmp.flags = info->flags;
tmp.xmit_fifo_size = 1024;
tmp.baud_base = BASE_BAUD;
tmp.close_delay = info->close_delay;
tmp.closing_wait = info->closing_wait;
tmp.custom_divisor = info->custom_divisor;
tmp.hub6 = 0;
if (copy_to_user(retinfo,&tmp,sizeof(*retinfo)))
return -EFAULT;
return 0;
}
static int get_esp_config(struct esp_struct * info,
struct hayes_esp_config __user *retinfo)
{
struct hayes_esp_config tmp;
if (!retinfo)
return -EFAULT;
memset(&tmp, 0, sizeof(tmp));
tmp.rx_timeout = info->config.rx_timeout;
tmp.rx_trigger = info->config.rx_trigger;
tmp.tx_trigger = info->config.tx_trigger;
tmp.flow_off = info->config.flow_off;
tmp.flow_on = info->config.flow_on;
tmp.pio_threshold = info->config.pio_threshold;
tmp.dma_channel = (info->stat_flags & ESP_STAT_NEVER_DMA ? 0 : dma);
return copy_to_user(retinfo, &tmp, sizeof(*retinfo)) ? -EFAULT : 0;
}
static int set_serial_info(struct esp_struct * info,
struct serial_struct __user *new_info)
{
struct serial_struct new_serial;
struct esp_struct old_info;
unsigned int change_irq;
int retval = 0;
struct esp_struct *current_async;
if (copy_from_user(&new_serial,new_info,sizeof(new_serial)))
return -EFAULT;
old_info = *info;
if ((new_serial.type != PORT_16550A) ||
(new_serial.hub6) ||
(info->port != new_serial.port) ||
(new_serial.baud_base != BASE_BAUD) ||
(new_serial.irq > 15) ||
(new_serial.irq < 2) ||
(new_serial.irq == 6) ||
(new_serial.irq == 8) ||
(new_serial.irq == 13))
return -EINVAL;
change_irq = new_serial.irq != info->irq;
if (change_irq && (info->line % 8))
return -EINVAL;
if (!capable(CAP_SYS_ADMIN)) {
if (change_irq ||
(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));
info->custom_divisor = new_serial.custom_divisor;
} else {
if (new_serial.irq == 2)
new_serial.irq = 9;
if (change_irq) {
current_async = ports;
while (current_async) {
if ((current_async->line >= info->line) &&
(current_async->line < (info->line + 8))) {
if (current_async == info) {
if (current_async->count > 1)
return -EBUSY;
} else if (current_async->count)
return -EBUSY;
}
current_async = current_async->next_port;
}
}
/*
* 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->custom_divisor = new_serial.custom_divisor;
info->close_delay = new_serial.close_delay * HZ/100;
info->closing_wait = new_serial.closing_wait * HZ/100;
if (change_irq) {
/*
* We need to shutdown the serial port at the old
* port/irq combination.
*/
shutdown(info);
current_async = ports;
while (current_async) {
if ((current_async->line >= info->line) &&
(current_async->line < (info->line + 8)))
current_async->irq = new_serial.irq;
current_async = current_async->next_port;
}
serial_out(info, UART_ESI_CMD1, ESI_SET_ENH_IRQ);
if (info->irq == 9)
serial_out(info, UART_ESI_CMD2, 0x02);
else
serial_out(info, UART_ESI_CMD2, info->irq);
}
}
if (info->flags & ASYNC_INITIALIZED) {
if (((old_info.flags & ASYNC_SPD_MASK) !=
(info->flags & ASYNC_SPD_MASK)) ||
(old_info.custom_divisor != info->custom_divisor)) {
if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
info->tty->alt_speed = 57600;
if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
info->tty->alt_speed = 115200;
if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
info->tty->alt_speed = 230400;
if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
info->tty->alt_speed = 460800;
change_speed(info);
}
} else
retval = startup(info);
return retval;
}
static int set_esp_config(struct esp_struct * info,
struct hayes_esp_config __user * new_info)
{
struct hayes_esp_config new_config;
unsigned int change_dma;
int retval = 0;
struct esp_struct *current_async;
unsigned long flags;
/* Perhaps a non-sysadmin user should be able to do some of these */
/* operations. I haven't decided yet. */
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (copy_from_user(&new_config, new_info, sizeof(new_config)))
return -EFAULT;
if ((new_config.flow_on >= new_config.flow_off) ||
(new_config.rx_trigger < 1) ||
(new_config.tx_trigger < 1) ||
(new_config.flow_off < 1) ||
(new_config.flow_on < 1) ||
(new_config.rx_trigger > 1023) ||
(new_config.tx_trigger > 1023) ||
(new_config.flow_off > 1023) ||
(new_config.flow_on > 1023) ||
(new_config.pio_threshold < 0) ||
(new_config.pio_threshold > 1024))
return -EINVAL;
if ((new_config.dma_channel != 1) && (new_config.dma_channel != 3))
new_config.dma_channel = 0;
if (info->stat_flags & ESP_STAT_NEVER_DMA)
change_dma = new_config.dma_channel;
else
change_dma = (new_config.dma_channel != dma);
if (change_dma) {
if (new_config.dma_channel) {
/* PIO mode to DMA mode transition OR */
/* change current DMA channel */
current_async = ports;
while (current_async) {
if (current_async == info) {
if (current_async->count > 1)
return -EBUSY;
} else if (current_async->count)
return -EBUSY;
current_async =
current_async->next_port;
}
shutdown(info);
dma = new_config.dma_channel;
info->stat_flags &= ~ESP_STAT_NEVER_DMA;
/* all ports must use the same DMA channel */
spin_lock_irqsave(&info->lock, flags);
current_async = ports;
while (current_async) {
esp_basic_init(current_async);
current_async = current_async->next_port;
}
spin_unlock_irqrestore(&info->lock, flags);
} else {
/* DMA mode to PIO mode only */
if (info->count > 1)
return -EBUSY;
shutdown(info);
spin_lock_irqsave(&info->lock, flags);
info->stat_flags |= ESP_STAT_NEVER_DMA;
esp_basic_init(info);
spin_unlock_irqrestore(&info->lock, flags);
}
}
info->config.pio_threshold = new_config.pio_threshold;
if ((new_config.flow_off != info->config.flow_off) ||
(new_config.flow_on != info->config.flow_on)) {
unsigned long flags;
info->config.flow_off = new_config.flow_off;
info->config.flow_on = new_config.flow_on;
spin_lock_irqsave(&info->lock, flags);
serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_LVL);
serial_out(info, UART_ESI_CMD2, new_config.flow_off >> 8);
serial_out(info, UART_ESI_CMD2, new_config.flow_off);
serial_out(info, UART_ESI_CMD2, new_config.flow_on >> 8);
serial_out(info, UART_ESI_CMD2, new_config.flow_on);
spin_unlock_irqrestore(&info->lock, flags);
}
if ((new_config.rx_trigger != info->config.rx_trigger) ||
(new_config.tx_trigger != info->config.tx_trigger)) {
unsigned long flags;
info->config.rx_trigger = new_config.rx_trigger;
info->config.tx_trigger = new_config.tx_trigger;
spin_lock_irqsave(&info->lock, flags);
serial_out(info, UART_ESI_CMD1, ESI_SET_TRIGGER);
serial_out(info, UART_ESI_CMD2,
new_config.rx_trigger >> 8);
serial_out(info, UART_ESI_CMD2, new_config.rx_trigger);
serial_out(info, UART_ESI_CMD2,
new_config.tx_trigger >> 8);
serial_out(info, UART_ESI_CMD2, new_config.tx_trigger);
spin_unlock_irqrestore(&info->lock, flags);
}
if (new_config.rx_timeout != info->config.rx_timeout) {
unsigned long flags;
info->config.rx_timeout = new_config.rx_timeout;
spin_lock_irqsave(&info->lock, flags);
if (info->IER & UART_IER_RDI) {
serial_out(info, UART_ESI_CMD1,
ESI_SET_RX_TIMEOUT);
serial_out(info, UART_ESI_CMD2,
new_config.rx_timeout);
}
spin_unlock_irqrestore(&info->lock, flags);
}
if (!(info->flags & ASYNC_INITIALIZED))
retval = startup(info);
return retval;
}
/*
* 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 get_lsr_info(struct esp_struct * info, unsigned int __user *value)
{
unsigned char status;
unsigned int result;
unsigned long flags;
spin_lock_irqsave(&info->lock, flags);
serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
status = serial_in(info, UART_ESI_STAT1);
spin_unlock_irqrestore(&info->lock, flags);
result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0);
return put_user(result,value);
}
static int esp_tiocmget(struct tty_struct *tty, struct file *file)
{
struct esp_struct * info = (struct esp_struct *)tty->driver_data;
unsigned char control, status;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, __FUNCTION__))
return -ENODEV;
if (tty->flags & (1 << TTY_IO_ERROR))
return -EIO;
control = info->MCR;
spin_lock_irqsave(&info->lock, flags);
serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
status = serial_in(info, UART_ESI_STAT2);
spin_unlock_irqrestore(&info->lock, flags);
return ((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);
}
static int esp_tiocmset(struct tty_struct *tty, struct file *file,
unsigned int set, unsigned int clear)
{
struct esp_struct * info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, __FUNCTION__))
return -ENODEV;
if (tty->flags & (1 << TTY_IO_ERROR))
return -EIO;
spin_lock_irqsave(&info->lock, flags);
if (set & TIOCM_RTS)
info->MCR |= UART_MCR_RTS;
if (set & TIOCM_DTR)
info->MCR |= UART_MCR_DTR;
if (clear & TIOCM_RTS)
info->MCR &= ~UART_MCR_RTS;
if (clear & TIOCM_DTR)
info->MCR &= ~UART_MCR_DTR;
serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
serial_out(info, UART_ESI_CMD2, UART_MCR);
serial_out(info, UART_ESI_CMD2, info->MCR);
spin_unlock_irqrestore(&info->lock, flags);
return 0;
}
/*
* rs_break() --- routine which turns the break handling on or off
*/
static void esp_break(struct tty_struct *tty, int break_state)
{
struct esp_struct * info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, "esp_break"))
return;
if (break_state == -1) {
spin_lock_irqsave(&info->lock, flags);
serial_out(info, UART_ESI_CMD1, ESI_ISSUE_BREAK);
serial_out(info, UART_ESI_CMD2, 0x01);
spin_unlock_irqrestore(&info->lock, flags);
/* FIXME - new style wait needed here */
interruptible_sleep_on(&info->break_wait);
} else {
spin_lock_irqsave(&info->lock, flags);
serial_out(info, UART_ESI_CMD1, ESI_ISSUE_BREAK);
serial_out(info, UART_ESI_CMD2, 0x00);
spin_unlock_irqrestore(&info->lock, flags);
}
}
static int rs_ioctl(struct tty_struct *tty, struct file * file,
unsigned int cmd, unsigned long arg)
{
struct esp_struct * info = (struct esp_struct *)tty->driver_data;
struct async_icount cprev, cnow; /* kernel counter temps */
struct serial_icounter_struct __user *p_cuser; /* user space */
void __user *argp = (void __user *)arg;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, "rs_ioctl"))
return -ENODEV;
if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
(cmd != TIOCSERCONFIG) && (cmd != TIOCSERGWILD) &&
(cmd != TIOCSERSWILD) && (cmd != TIOCSERGSTRUCT) &&
(cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT) &&
(cmd != TIOCGHAYESESP) && (cmd != TIOCSHAYESESP)) {
if (tty->flags & (1 << TTY_IO_ERROR))
return -EIO;
}
switch (cmd) {
case TIOCGSERIAL:
return get_serial_info(info, argp);
case TIOCSSERIAL:
return set_serial_info(info, argp);
case TIOCSERCONFIG:
/* do not reconfigure after initial configuration */
return 0;
case TIOCSERGWILD:
return put_user(0L, (unsigned long __user *)argp);
case TIOCSERGETLSR: /* Get line status register */
return get_lsr_info(info, argp);
case TIOCSERSWILD:
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
return 0;
/*
* 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:
spin_lock_irqsave(&info->lock, flags);
cprev = info->icount; /* note the counters on entry */
spin_unlock_irqrestore(&info->lock, flags);
while (1) {
/* FIXME: convert to new style wakeup */
interruptible_sleep_on(&info->delta_msr_wait);
/* see if a signal did it */
if (signal_pending(current))
return -ERESTARTSYS;
spin_lock_irqsave(&info->lock, flags);
cnow = info->icount; /* atomic copy */
spin_unlock_irqrestore(&info->lock, 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:
spin_lock_irqsave(&info->lock, flags);
cnow = info->icount;
spin_unlock_irqrestore(&info->lock, flags);
p_cuser = argp;
if (put_user(cnow.cts, &p_cuser->cts) ||
put_user(cnow.dsr, &p_cuser->dsr) ||
put_user(cnow.rng, &p_cuser->rng) ||
put_user(cnow.dcd, &p_cuser->dcd))
return -EFAULT;
return 0;
case TIOCGHAYESESP:
return get_esp_config(info, argp);
case TIOCSHAYESESP:
return set_esp_config(info, argp);
default:
return -ENOIOCTLCMD;
}
return 0;
}
static void rs_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
{
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
change_speed(info);
spin_lock_irqsave(&info->lock, flags);
/* Handle transition to B0 status */
if ((old_termios->c_cflag & CBAUD) &&
!(tty->termios->c_cflag & CBAUD)) {
info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS);
serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
serial_out(info, UART_ESI_CMD2, UART_MCR);
serial_out(info, UART_ESI_CMD2, info->MCR);
}
/* Handle transition away from B0 status */
if (!(old_termios->c_cflag & CBAUD) &&
(tty->termios->c_cflag & CBAUD)) {
info->MCR |= (UART_MCR_DTR | UART_MCR_RTS);
serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
serial_out(info, UART_ESI_CMD2, UART_MCR);
serial_out(info, UART_ESI_CMD2, info->MCR);
}
spin_unlock_irqrestore(&info->lock, flags);
/* Handle turning of CRTSCTS */
if ((old_termios->c_cflag & CRTSCTS) &&
!(tty->termios->c_cflag & CRTSCTS)) {
rs_start(tty);
}
}
/*
* ------------------------------------------------------------
* rs_close()
*
* 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 rs_close(struct tty_struct *tty, struct file * filp)
{
struct esp_struct * info = (struct esp_struct *)tty->driver_data;
unsigned long flags;
if (!info || serial_paranoia_check(info, tty->name, "rs_close"))
return;
spin_lock_irqsave(&info->lock, flags);
if (tty_hung_up_p(filp)) {
DBG_CNT("before DEC-hung");
goto out;
}
#ifdef SERIAL_DEBUG_OPEN
printk("rs_close ttys%d, count = %d\n", info->line, info->count);
#endif
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("rs_close: bad serial port count; tty->count is 1, "
"info->count is %d\n", info->count);
info->count = 1;
}
if (--info->count < 0) {
printk("rs_close: bad serial port count for ttys%d: %d\n",
info->line, info->count);
info->count = 0;
}
if (info->count) {
DBG_CNT("before DEC-2");
goto out;
}
info->flags |= ASYNC_CLOSING;
spin_unlock_irqrestore(&info->lock, flags);
/*
* 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)
tty_wait_until_sent(tty, info->closing_wait);
/*
* 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.
*/
/* info->IER &= ~UART_IER_RLSI; */
info->IER &= ~UART_IER_RDI;
info->read_status_mask &= ~UART_LSR_DR;
if (info->flags & ASYNC_INITIALIZED) {
spin_lock_irqsave(&info->lock, flags);
serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
serial_out(info, UART_ESI_CMD2, info->IER);
/* disable receive timeout */
serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
serial_out(info, UART_ESI_CMD2, 0x00);
spin_unlock_irqrestore(&info->lock, flags);
/*
* Before we drop DTR, make sure the UART transmitter
* has completely drained; this is especially
* important if there is a transmit FIFO!
*/
rs_wait_until_sent(tty, info->timeout);
}
shutdown(info);
if (tty->driver->flush_buffer)
tty->driver->flush_buffer(tty);
tty_ldisc_flush(tty);
tty->closing = 0;
info->event = 0;
info->tty = NULL;
if (info->blocked_open) {
if (info->close_delay) {
msleep_interruptible(jiffies_to_msecs(info->close_delay));
}
wake_up_interruptible(&info->open_wait);
}
info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
wake_up_interruptible(&info->close_wait);
return;
out:
spin_unlock_irqrestore(&info->lock, flags);
}
static void rs_wait_until_sent(struct tty_struct *tty, int timeout)
{
struct esp_struct *info = (struct esp_struct *)tty->driver_data;
unsigned long orig_jiffies, char_time;
unsigned long flags;
if (serial_paranoia_check(info, tty->name, "rs_wait_until_sent"))
return;
orig_jiffies = jiffies;
char_time = ((info->timeout - HZ / 50) / 1024) / 5;
if (!char_time)
char_time = 1;
spin_lock_irqsave(&info->lock, flags);
serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
while ((serial_in(info, UART_ESI_STAT1) != 0x03) ||
(serial_in(info, UART_ESI_STAT2) != 0xff)) {
spin_unlock_irqrestore(&info->lock, flags);
msleep_interruptible(jiffies_to_msecs(char_time));
if (signal_pending(current))
break;
if (timeout && time_after(jiffies, orig_jiffies + timeout))
break;
spin_lock_irqsave(&info->lock, flags);
serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
}
spin_unlock_irqrestore(&info->lock, flags);
set_current_state(TASK_RUNNING);
}
/*
* esp_hangup() --- called by tty_hangup() when a hangup is signaled.
*/
static void esp_hangup(struct tty_struct *tty)
{
struct esp_struct * info = (struct esp_struct *)tty->driver_data;
if (serial_paranoia_check(info, tty->name, "esp_hangup"))
return;
rs_flush_buffer(tty);
shutdown(info);
info->event = 0;
info->count = 0;
info->flags &= ~ASYNC_NORMAL_ACTIVE;
info->tty = NULL;
wake_up_interruptible(&info->open_wait);
}
/*
* ------------------------------------------------------------
* esp_open() and friends
* ------------------------------------------------------------
*/
static int block_til_ready(struct tty_struct *tty, struct file * filp,
struct esp_struct *info)
{
DECLARE_WAITQUEUE(wait, current);
int retval;
int do_clocal = 0;
unsigned long flags;
/*
* 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 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))) {
info->flags |= ASYNC_NORMAL_ACTIVE;
return 0;
}
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
* rs_close() knows when to free things. We restore it upon
* exit, either normal or abnormal.
*/
retval = 0;
add_wait_queue(&info->open_wait, &wait);
#ifdef SERIAL_DEBUG_OPEN
printk("block_til_ready before block: ttys%d, count = %d\n",
info->line, info->count);
#endif
spin_lock_irqsave(&info->lock, flags);
if (!tty_hung_up_p(filp))
info->count--;
info->blocked_open++;
while (1) {
if ((tty->termios->c_cflag & CBAUD)) {
unsigned int scratch;
serial_out(info, UART_ESI_CMD1, ESI_READ_UART);
serial_out(info, UART_ESI_CMD2, UART_MCR);
scratch = serial_in(info, UART_ESI_STAT1);
serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
serial_out(info, UART_ESI_CMD2, UART_MCR);
serial_out(info, UART_ESI_CMD2,
scratch | UART_MCR_DTR | UART_MCR_RTS);
}
set_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;
}
serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
if (serial_in(info, UART_ESI_STAT2) & UART_MSR_DCD)
do_clocal = 1;
if (!(info->flags & ASYNC_CLOSING) &&
(do_clocal))
break;
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
#ifdef SERIAL_DEBUG_OPEN
printk("block_til_ready blocking: ttys%d, count = %d\n",
info->line, info->count);
#endif
spin_unlock_irqrestore(&info->lock, flags);
schedule();
spin_lock_irqsave(&info->lock, flags);
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&info->open_wait, &wait);
if (!tty_hung_up_p(filp))
info->count++;
info->blocked_open--;
spin_unlock_irqrestore(&info->lock, flags);
#ifdef SERIAL_DEBUG_OPEN
printk("block_til_ready after blocking: ttys%d, count = %d\n",
info->line, info->count);
#endif
if (retval)
return retval;
info->flags |= ASYNC_NORMAL_ACTIVE;
return 0;
}
/*
* 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.
*/
static int esp_open(struct tty_struct *tty, struct file * filp)
{
struct esp_struct *info;
int retval, line;
unsigned long flags;
line = tty->index;
if ((line < 0) || (line >= NR_PORTS))
return -ENODEV;
/* find the port in the chain */
info = ports;
while (info && (info->line != line))
info = info->next_port;
if (!info) {
serial_paranoia_check(info, tty->name, "esp_open");
return -ENODEV;
}
#ifdef SERIAL_DEBUG_OPEN
printk("esp_open %s, count = %d\n", tty->name, info->count);
#endif
spin_lock_irqsave(&info->lock, flags);
info->count++;
tty->driver_data = info;
info->tty = tty;
spin_unlock_irqrestore(&info->lock, flags);
/*
* Start up serial port
*/
retval = startup(info);
if (retval)
return retval;
retval = block_til_ready(tty, filp, info);
if (retval) {
#ifdef SERIAL_DEBUG_OPEN
printk("esp_open returning after block_til_ready with %d\n",
retval);
#endif
return retval;
}
#ifdef SERIAL_DEBUG_OPEN
printk("esp_open %s successful...", tty->name);
#endif
return 0;
}
/*
* ---------------------------------------------------------------------
* espserial_init() and friends
*
* espserial_init() is called at boot-time to initialize the serial driver.
* ---------------------------------------------------------------------
*/
/*
* This routine prints out the appropriate serial driver version
* number, and identifies which options were configured into this
* driver.
*/
static inline void show_serial_version(void)
{
printk(KERN_INFO "%s version %s (DMA %u)\n",
serial_name, serial_version, dma);
}
/*
* This routine is called by espserial_init() to initialize a specific serial
* port.
*/
static inline int autoconfig(struct esp_struct * info)
{
int port_detected = 0;
unsigned long flags;
if (!request_region(info->port, REGION_SIZE, "esp serial"))
return -EIO;
spin_lock_irqsave(&info->lock, flags);
/*
* Check for ESP card
*/
if (serial_in(info, UART_ESI_BASE) == 0xf3) {
serial_out(info, UART_ESI_CMD1, 0x00);
serial_out(info, UART_ESI_CMD1, 0x01);
if ((serial_in(info, UART_ESI_STAT2) & 0x70) == 0x20) {
port_detected = 1;
if (!(info->irq)) {
serial_out(info, UART_ESI_CMD1, 0x02);
if (serial_in(info, UART_ESI_STAT1) & 0x01)
info->irq = 3;
else
info->irq = 4;
}
/* put card in enhanced mode */
/* this prevents access through */
/* the "old" IO ports */
esp_basic_init(info);
/* clear out MCR */
serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
serial_out(info, UART_ESI_CMD2, UART_MCR);
serial_out(info, UART_ESI_CMD2, 0x00);
}
}
if (!port_detected)
release_region(info->port, REGION_SIZE);
spin_unlock_irqrestore(&info->lock, flags);
return (port_detected);
}
static const struct tty_operations esp_ops = {
.open = esp_open,
.close = rs_close,
.write = rs_write,
.put_char = rs_put_char,
.flush_chars = rs_flush_chars,
.write_room = rs_write_room,
.chars_in_buffer = rs_chars_in_buffer,
.flush_buffer = rs_flush_buffer,
.ioctl = rs_ioctl,
.throttle = rs_throttle,
.unthrottle = rs_unthrottle,
.set_termios = rs_set_termios,
.stop = rs_stop,
.start = rs_start,
.hangup = esp_hangup,
.break_ctl = esp_break,
.wait_until_sent = rs_wait_until_sent,
.tiocmget = esp_tiocmget,
.tiocmset = esp_tiocmset,
};
/*
* The serial driver boot-time initialization code!
*/
static int __init espserial_init(void)
{
int i, offset;
struct esp_struct * info;
struct esp_struct *last_primary = NULL;
int esp[] = {0x100,0x140,0x180,0x200,0x240,0x280,0x300,0x380};
esp_driver = alloc_tty_driver(NR_PORTS);
if (!esp_driver)
return -ENOMEM;
for (i = 0; i < NR_PRIMARY; i++) {
if (irq[i] != 0) {
if ((irq[i] < 2) || (irq[i] > 15) || (irq[i] == 6) ||
(irq[i] == 8) || (irq[i] == 13))
irq[i] = 0;
else if (irq[i] == 2)
irq[i] = 9;
}
}
if ((dma != 1) && (dma != 3))
dma = 0;
if ((rx_trigger < 1) || (rx_trigger > 1023))
rx_trigger = 768;
if ((tx_trigger < 1) || (tx_trigger > 1023))
tx_trigger = 768;
if ((flow_off < 1) || (flow_off > 1023))
flow_off = 1016;
if ((flow_on < 1) || (flow_on > 1023))
flow_on = 944;
if ((rx_timeout < 0) || (rx_timeout > 255))
rx_timeout = 128;
if (flow_on >= flow_off)
flow_on = flow_off - 1;
show_serial_version();
/* Initialize the tty_driver structure */
esp_driver->owner = THIS_MODULE;
esp_driver->name = "ttyP";
esp_driver->major = ESP_IN_MAJOR;
esp_driver->minor_start = 0;
esp_driver->type = TTY_DRIVER_TYPE_SERIAL;
esp_driver->subtype = SERIAL_TYPE_NORMAL;
esp_driver->init_termios = tty_std_termios;
esp_driver->init_termios.c_cflag =
B9600 | CS8 | CREAD | HUPCL | CLOCAL;
esp_driver->flags = TTY_DRIVER_REAL_RAW;
tty_set_operations(esp_driver, &esp_ops);
if (tty_register_driver(esp_driver))
{
printk(KERN_ERR "Couldn't register esp serial driver");
put_tty_driver(esp_driver);
return 1;
}
2007-07-19 15:49:03 +07:00
info = kzalloc(sizeof(struct esp_struct), GFP_KERNEL);
if (!info)
{
printk(KERN_ERR "Couldn't allocate memory for esp serial device information\n");
tty_unregister_driver(esp_driver);
put_tty_driver(esp_driver);
return 1;
}
spin_lock_init(&info->lock);
/* rx_trigger, tx_trigger are needed by autoconfig */
info->config.rx_trigger = rx_trigger;
info->config.tx_trigger = tx_trigger;
i = 0;
offset = 0;
do {
info->port = esp[i] + offset;
info->irq = irq[i];
info->line = (i * 8) + (offset / 8);
if (!autoconfig(info)) {
i++;
offset = 0;
continue;
}
info->custom_divisor = (divisor[i] >> (offset / 2)) & 0xf;
info->flags = STD_COM_FLAGS;
if (info->custom_divisor)
info->flags |= ASYNC_SPD_CUST;
info->magic = ESP_MAGIC;
info->close_delay = 5*HZ/10;
info->closing_wait = 30*HZ;
INIT_WORK(&info->tqueue, do_softint);
INIT_WORK(&info->tqueue_hangup, do_serial_hangup);
info->config.rx_timeout = rx_timeout;
info->config.flow_on = flow_on;
info->config.flow_off = flow_off;
info->config.pio_threshold = pio_threshold;
info->next_port = ports;
init_waitqueue_head(&info->open_wait);
init_waitqueue_head(&info->close_wait);
init_waitqueue_head(&info->delta_msr_wait);
init_waitqueue_head(&info->break_wait);
ports = info;
printk(KERN_INFO "ttyP%d at 0x%04x (irq = %d) is an ESP ",
info->line, info->port, info->irq);
if (info->line % 8) {
printk("secondary port\n");
/* 8 port cards can't do DMA */
info->stat_flags |= ESP_STAT_NEVER_DMA;
if (last_primary)
last_primary->stat_flags |= ESP_STAT_NEVER_DMA;
} else {
printk("primary port\n");
last_primary = info;
irq[i] = info->irq;
}
if (!dma)
info->stat_flags |= ESP_STAT_NEVER_DMA;
2007-07-19 15:49:03 +07:00
info = kzalloc(sizeof(struct esp_struct), GFP_KERNEL);
if (!info)
{
printk(KERN_ERR "Couldn't allocate memory for esp serial device information\n");
/* allow use of the already detected ports */
return 0;
}
/* rx_trigger, tx_trigger are needed by autoconfig */
info->config.rx_trigger = rx_trigger;
info->config.tx_trigger = tx_trigger;
if (offset == 56) {
i++;
offset = 0;
} else {
offset += 8;
}
} while (i < NR_PRIMARY);
/* free the last port memory allocation */
kfree(info);
return 0;
}
static void __exit espserial_exit(void)
{
int e1;
struct esp_struct *temp_async;
struct esp_pio_buffer *pio_buf;
/* printk("Unloading %s: version %s\n", serial_name, serial_version); */
if ((e1 = tty_unregister_driver(esp_driver)))
printk("SERIAL: failed to unregister serial driver (%d)\n",
e1);
put_tty_driver(esp_driver);
while (ports) {
if (ports->port) {
release_region(ports->port, REGION_SIZE);
}
temp_async = ports->next_port;
kfree(ports);
ports = temp_async;
}
if (dma_buffer)
free_pages((unsigned long)dma_buffer,
get_order(DMA_BUFFER_SZ));
while (free_pio_buf) {
pio_buf = free_pio_buf->next;
kfree(free_pio_buf);
free_pio_buf = pio_buf;
}
}
module_init(espserial_init);
module_exit(espserial_exit);