forked from Minki/linux
606d099cdd
This is the grungy swap all the occurrences in the right places patch that goes with the updates. At this point we have the same functionality as before (except that sgttyb() returns speeds not zero) and are ready to begin turning new stuff on providing nobody reports lots of bugs If you are a tty driver author converting an out of tree driver the only impact should be termios->ktermios name changes for the speed/property setting functions from your upper layers. If you are implementing your own TCGETS function before then your driver was broken already and its about to get a whole lot more painful for you so please fix it 8) Also fill in c_ispeed/ospeed on init for most devices, although the current code will do this for you anyway but I'd like eventually to lose that extra paranoia [akpm@osdl.org: bluetooth fix] [mp3@de.ibm.com: sclp fix] [mp3@de.ibm.com: warning fix for tty3270] [hugh@veritas.com: fix tty_ioctl powerpc build] [jdike@addtoit.com: uml: fix ->set_termios declaration] Signed-off-by: Alan Cox <alan@redhat.com> Signed-off-by: Martin Peschke <mp3@de.ibm.com> Acked-by: Peter Oberparleiter <oberpar@de.ibm.com> Cc: Cornelia Huck <cornelia.huck@de.ibm.com> Signed-off-by: Hugh Dickins <hugh@veritas.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Cc: Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
936 lines
23 KiB
C
936 lines
23 KiB
C
/************************************************************************
|
|
* Copyright 2003 Digi International (www.digi.com)
|
|
*
|
|
* Copyright (C) 2004 IBM Corporation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the
|
|
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 * Temple Place - Suite 330, Boston,
|
|
* MA 02111-1307, USA.
|
|
*
|
|
* Contact Information:
|
|
* Scott H Kilau <Scott_Kilau@digi.com>
|
|
* Ananda Venkatarman <mansarov@us.ibm.com>
|
|
* Modifications:
|
|
* 01/19/06: changed jsm_input routine to use the dynamically allocated
|
|
* tty_buffer changes. Contributors: Scott Kilau and Ananda V.
|
|
***********************************************************************/
|
|
#include <linux/tty.h>
|
|
#include <linux/tty_flip.h>
|
|
#include <linux/serial_reg.h>
|
|
#include <linux/delay.h> /* For udelay */
|
|
#include <linux/pci.h>
|
|
|
|
#include "jsm.h"
|
|
|
|
static void jsm_carrier(struct jsm_channel *ch);
|
|
|
|
static inline int jsm_get_mstat(struct jsm_channel *ch)
|
|
{
|
|
unsigned char mstat;
|
|
unsigned result;
|
|
|
|
jsm_printk(IOCTL, INFO, &ch->ch_bd->pci_dev, "start\n");
|
|
|
|
mstat = (ch->ch_mostat | ch->ch_mistat);
|
|
|
|
result = 0;
|
|
|
|
if (mstat & UART_MCR_DTR)
|
|
result |= TIOCM_DTR;
|
|
if (mstat & UART_MCR_RTS)
|
|
result |= TIOCM_RTS;
|
|
if (mstat & UART_MSR_CTS)
|
|
result |= TIOCM_CTS;
|
|
if (mstat & UART_MSR_DSR)
|
|
result |= TIOCM_DSR;
|
|
if (mstat & UART_MSR_RI)
|
|
result |= TIOCM_RI;
|
|
if (mstat & UART_MSR_DCD)
|
|
result |= TIOCM_CD;
|
|
|
|
jsm_printk(IOCTL, INFO, &ch->ch_bd->pci_dev, "finish\n");
|
|
return result;
|
|
}
|
|
|
|
static unsigned int jsm_tty_tx_empty(struct uart_port *port)
|
|
{
|
|
return TIOCSER_TEMT;
|
|
}
|
|
|
|
/*
|
|
* Return modem signals to ld.
|
|
*/
|
|
static unsigned int jsm_tty_get_mctrl(struct uart_port *port)
|
|
{
|
|
int result;
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
|
|
jsm_printk(IOCTL, INFO, &channel->ch_bd->pci_dev, "start\n");
|
|
|
|
result = jsm_get_mstat(channel);
|
|
|
|
if (result < 0)
|
|
return -ENXIO;
|
|
|
|
jsm_printk(IOCTL, INFO, &channel->ch_bd->pci_dev, "finish\n");
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* jsm_set_modem_info()
|
|
*
|
|
* Set modem signals, called by ld.
|
|
*/
|
|
static void jsm_tty_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
|
{
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
|
|
jsm_printk(IOCTL, INFO, &channel->ch_bd->pci_dev, "start\n");
|
|
|
|
if (mctrl & TIOCM_RTS)
|
|
channel->ch_mostat |= UART_MCR_RTS;
|
|
else
|
|
channel->ch_mostat &= ~UART_MCR_RTS;
|
|
|
|
if (mctrl & TIOCM_DTR)
|
|
channel->ch_mostat |= UART_MCR_DTR;
|
|
else
|
|
channel->ch_mostat &= ~UART_MCR_DTR;
|
|
|
|
channel->ch_bd->bd_ops->assert_modem_signals(channel);
|
|
|
|
jsm_printk(IOCTL, INFO, &channel->ch_bd->pci_dev, "finish\n");
|
|
udelay(10);
|
|
}
|
|
|
|
static void jsm_tty_start_tx(struct uart_port *port)
|
|
{
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
|
|
jsm_printk(IOCTL, INFO, &channel->ch_bd->pci_dev, "start\n");
|
|
|
|
channel->ch_flags &= ~(CH_STOP);
|
|
jsm_tty_write(port);
|
|
|
|
jsm_printk(IOCTL, INFO, &channel->ch_bd->pci_dev, "finish\n");
|
|
}
|
|
|
|
static void jsm_tty_stop_tx(struct uart_port *port)
|
|
{
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
|
|
jsm_printk(IOCTL, INFO, &channel->ch_bd->pci_dev, "start\n");
|
|
|
|
channel->ch_flags |= (CH_STOP);
|
|
|
|
jsm_printk(IOCTL, INFO, &channel->ch_bd->pci_dev, "finish\n");
|
|
}
|
|
|
|
static void jsm_tty_send_xchar(struct uart_port *port, char ch)
|
|
{
|
|
unsigned long lock_flags;
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
struct ktermios *termios;
|
|
|
|
spin_lock_irqsave(&port->lock, lock_flags);
|
|
termios = port->info->tty->termios;
|
|
if (ch == termios->c_cc[VSTART])
|
|
channel->ch_bd->bd_ops->send_start_character(channel);
|
|
|
|
if (ch == termios->c_cc[VSTOP])
|
|
channel->ch_bd->bd_ops->send_stop_character(channel);
|
|
spin_unlock_irqrestore(&port->lock, lock_flags);
|
|
}
|
|
|
|
static void jsm_tty_stop_rx(struct uart_port *port)
|
|
{
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
|
|
channel->ch_bd->bd_ops->disable_receiver(channel);
|
|
}
|
|
|
|
static void jsm_tty_break(struct uart_port *port, int break_state)
|
|
{
|
|
unsigned long lock_flags;
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
|
|
spin_lock_irqsave(&port->lock, lock_flags);
|
|
if (break_state == -1)
|
|
channel->ch_bd->bd_ops->send_break(channel);
|
|
else
|
|
channel->ch_bd->bd_ops->clear_break(channel, 0);
|
|
|
|
spin_unlock_irqrestore(&port->lock, lock_flags);
|
|
}
|
|
|
|
static int jsm_tty_open(struct uart_port *port)
|
|
{
|
|
struct jsm_board *brd;
|
|
int rc = 0;
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
struct ktermios *termios;
|
|
|
|
/* Get board pointer from our array of majors we have allocated */
|
|
brd = channel->ch_bd;
|
|
|
|
/*
|
|
* Allocate channel buffers for read/write/error.
|
|
* Set flag, so we don't get trounced on.
|
|
*/
|
|
channel->ch_flags |= (CH_OPENING);
|
|
|
|
/* Drop locks, as malloc with GFP_KERNEL can sleep */
|
|
|
|
if (!channel->ch_rqueue) {
|
|
channel->ch_rqueue = (u8 *) kmalloc(RQUEUESIZE, GFP_KERNEL);
|
|
if (!channel->ch_rqueue) {
|
|
jsm_printk(INIT, ERR, &channel->ch_bd->pci_dev,
|
|
"unable to allocate read queue buf");
|
|
return -ENOMEM;
|
|
}
|
|
memset(channel->ch_rqueue, 0, RQUEUESIZE);
|
|
}
|
|
if (!channel->ch_equeue) {
|
|
channel->ch_equeue = (u8 *) kmalloc(EQUEUESIZE, GFP_KERNEL);
|
|
if (!channel->ch_equeue) {
|
|
jsm_printk(INIT, ERR, &channel->ch_bd->pci_dev,
|
|
"unable to allocate error queue buf");
|
|
return -ENOMEM;
|
|
}
|
|
memset(channel->ch_equeue, 0, EQUEUESIZE);
|
|
}
|
|
if (!channel->ch_wqueue) {
|
|
channel->ch_wqueue = (u8 *) kmalloc(WQUEUESIZE, GFP_KERNEL);
|
|
if (!channel->ch_wqueue) {
|
|
jsm_printk(INIT, ERR, &channel->ch_bd->pci_dev,
|
|
"unable to allocate write queue buf");
|
|
return -ENOMEM;
|
|
}
|
|
memset(channel->ch_wqueue, 0, WQUEUESIZE);
|
|
}
|
|
|
|
channel->ch_flags &= ~(CH_OPENING);
|
|
/*
|
|
* Initialize if neither terminal is open.
|
|
*/
|
|
jsm_printk(OPEN, INFO, &channel->ch_bd->pci_dev,
|
|
"jsm_open: initializing channel in open...\n");
|
|
|
|
/*
|
|
* Flush input queues.
|
|
*/
|
|
channel->ch_r_head = channel->ch_r_tail = 0;
|
|
channel->ch_e_head = channel->ch_e_tail = 0;
|
|
channel->ch_w_head = channel->ch_w_tail = 0;
|
|
|
|
brd->bd_ops->flush_uart_write(channel);
|
|
brd->bd_ops->flush_uart_read(channel);
|
|
|
|
channel->ch_flags = 0;
|
|
channel->ch_cached_lsr = 0;
|
|
channel->ch_stops_sent = 0;
|
|
|
|
termios = port->info->tty->termios;
|
|
channel->ch_c_cflag = termios->c_cflag;
|
|
channel->ch_c_iflag = termios->c_iflag;
|
|
channel->ch_c_oflag = termios->c_oflag;
|
|
channel->ch_c_lflag = termios->c_lflag;
|
|
channel->ch_startc = termios->c_cc[VSTART];
|
|
channel->ch_stopc = termios->c_cc[VSTOP];
|
|
|
|
/* Tell UART to init itself */
|
|
brd->bd_ops->uart_init(channel);
|
|
|
|
/*
|
|
* Run param in case we changed anything
|
|
*/
|
|
brd->bd_ops->param(channel);
|
|
|
|
jsm_carrier(channel);
|
|
|
|
channel->ch_open_count++;
|
|
|
|
jsm_printk(OPEN, INFO, &channel->ch_bd->pci_dev, "finish\n");
|
|
return rc;
|
|
}
|
|
|
|
static void jsm_tty_close(struct uart_port *port)
|
|
{
|
|
struct jsm_board *bd;
|
|
struct ktermios *ts;
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
|
|
jsm_printk(CLOSE, INFO, &channel->ch_bd->pci_dev, "start\n");
|
|
|
|
bd = channel->ch_bd;
|
|
ts = channel->uart_port.info->tty->termios;
|
|
|
|
channel->ch_flags &= ~(CH_STOPI);
|
|
|
|
channel->ch_open_count--;
|
|
|
|
/*
|
|
* If we have HUPCL set, lower DTR and RTS
|
|
*/
|
|
if (channel->ch_c_cflag & HUPCL) {
|
|
jsm_printk(CLOSE, INFO, &channel->ch_bd->pci_dev,
|
|
"Close. HUPCL set, dropping DTR/RTS\n");
|
|
|
|
/* Drop RTS/DTR */
|
|
channel->ch_mostat &= ~(UART_MCR_DTR | UART_MCR_RTS);
|
|
bd->bd_ops->assert_modem_signals(channel);
|
|
}
|
|
|
|
channel->ch_old_baud = 0;
|
|
|
|
/* Turn off UART interrupts for this port */
|
|
channel->ch_bd->bd_ops->uart_off(channel);
|
|
|
|
jsm_printk(CLOSE, INFO, &channel->ch_bd->pci_dev, "finish\n");
|
|
}
|
|
|
|
static void jsm_tty_set_termios(struct uart_port *port,
|
|
struct ktermios *termios,
|
|
struct ktermios *old_termios)
|
|
{
|
|
unsigned long lock_flags;
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
|
|
spin_lock_irqsave(&port->lock, lock_flags);
|
|
channel->ch_c_cflag = termios->c_cflag;
|
|
channel->ch_c_iflag = termios->c_iflag;
|
|
channel->ch_c_oflag = termios->c_oflag;
|
|
channel->ch_c_lflag = termios->c_lflag;
|
|
channel->ch_startc = termios->c_cc[VSTART];
|
|
channel->ch_stopc = termios->c_cc[VSTOP];
|
|
|
|
channel->ch_bd->bd_ops->param(channel);
|
|
jsm_carrier(channel);
|
|
spin_unlock_irqrestore(&port->lock, lock_flags);
|
|
}
|
|
|
|
static const char *jsm_tty_type(struct uart_port *port)
|
|
{
|
|
return "jsm";
|
|
}
|
|
|
|
static void jsm_tty_release_port(struct uart_port *port)
|
|
{
|
|
}
|
|
|
|
static int jsm_tty_request_port(struct uart_port *port)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void jsm_config_port(struct uart_port *port, int flags)
|
|
{
|
|
port->type = PORT_JSM;
|
|
}
|
|
|
|
static struct uart_ops jsm_ops = {
|
|
.tx_empty = jsm_tty_tx_empty,
|
|
.set_mctrl = jsm_tty_set_mctrl,
|
|
.get_mctrl = jsm_tty_get_mctrl,
|
|
.stop_tx = jsm_tty_stop_tx,
|
|
.start_tx = jsm_tty_start_tx,
|
|
.send_xchar = jsm_tty_send_xchar,
|
|
.stop_rx = jsm_tty_stop_rx,
|
|
.break_ctl = jsm_tty_break,
|
|
.startup = jsm_tty_open,
|
|
.shutdown = jsm_tty_close,
|
|
.set_termios = jsm_tty_set_termios,
|
|
.type = jsm_tty_type,
|
|
.release_port = jsm_tty_release_port,
|
|
.request_port = jsm_tty_request_port,
|
|
.config_port = jsm_config_port,
|
|
};
|
|
|
|
/*
|
|
* jsm_tty_init()
|
|
*
|
|
* Init the tty subsystem. Called once per board after board has been
|
|
* downloaded and init'ed.
|
|
*/
|
|
int jsm_tty_init(struct jsm_board *brd)
|
|
{
|
|
int i;
|
|
void __iomem *vaddr;
|
|
struct jsm_channel *ch;
|
|
|
|
if (!brd)
|
|
return -ENXIO;
|
|
|
|
jsm_printk(INIT, INFO, &brd->pci_dev, "start\n");
|
|
|
|
/*
|
|
* Initialize board structure elements.
|
|
*/
|
|
|
|
brd->nasync = brd->maxports;
|
|
|
|
/*
|
|
* Allocate channel memory that might not have been allocated
|
|
* when the driver was first loaded.
|
|
*/
|
|
for (i = 0; i < brd->nasync; i++) {
|
|
if (!brd->channels[i]) {
|
|
|
|
/*
|
|
* Okay to malloc with GFP_KERNEL, we are not at
|
|
* interrupt context, and there are no locks held.
|
|
*/
|
|
brd->channels[i] = kmalloc(sizeof(struct jsm_channel), GFP_KERNEL);
|
|
if (!brd->channels[i]) {
|
|
jsm_printk(CORE, ERR, &brd->pci_dev,
|
|
"%s:%d Unable to allocate memory for channel struct\n",
|
|
__FILE__, __LINE__);
|
|
}
|
|
memset(brd->channels[i], 0, sizeof(struct jsm_channel));
|
|
}
|
|
}
|
|
|
|
ch = brd->channels[0];
|
|
vaddr = brd->re_map_membase;
|
|
|
|
/* Set up channel variables */
|
|
for (i = 0; i < brd->nasync; i++, ch = brd->channels[i]) {
|
|
|
|
if (!brd->channels[i])
|
|
continue;
|
|
|
|
spin_lock_init(&ch->ch_lock);
|
|
|
|
if (brd->bd_uart_offset == 0x200)
|
|
ch->ch_neo_uart = vaddr + (brd->bd_uart_offset * i);
|
|
|
|
ch->ch_bd = brd;
|
|
ch->ch_portnum = i;
|
|
|
|
/* .25 second delay */
|
|
ch->ch_close_delay = 250;
|
|
|
|
init_waitqueue_head(&ch->ch_flags_wait);
|
|
}
|
|
|
|
jsm_printk(INIT, INFO, &brd->pci_dev, "finish\n");
|
|
return 0;
|
|
}
|
|
|
|
int jsm_uart_port_init(struct jsm_board *brd)
|
|
{
|
|
int i;
|
|
struct jsm_channel *ch;
|
|
|
|
if (!brd)
|
|
return -ENXIO;
|
|
|
|
jsm_printk(INIT, INFO, &brd->pci_dev, "start\n");
|
|
|
|
/*
|
|
* Initialize board structure elements.
|
|
*/
|
|
|
|
brd->nasync = brd->maxports;
|
|
|
|
/* Set up channel variables */
|
|
for (i = 0; i < brd->nasync; i++, ch = brd->channels[i]) {
|
|
|
|
if (!brd->channels[i])
|
|
continue;
|
|
|
|
brd->channels[i]->uart_port.irq = brd->irq;
|
|
brd->channels[i]->uart_port.type = PORT_JSM;
|
|
brd->channels[i]->uart_port.iotype = UPIO_MEM;
|
|
brd->channels[i]->uart_port.membase = brd->re_map_membase;
|
|
brd->channels[i]->uart_port.fifosize = 16;
|
|
brd->channels[i]->uart_port.ops = &jsm_ops;
|
|
brd->channels[i]->uart_port.line = brd->channels[i]->ch_portnum + brd->boardnum * 2;
|
|
if (uart_add_one_port (&jsm_uart_driver, &brd->channels[i]->uart_port))
|
|
printk(KERN_INFO "Added device failed\n");
|
|
else
|
|
printk(KERN_INFO "Added device \n");
|
|
}
|
|
|
|
jsm_printk(INIT, INFO, &brd->pci_dev, "finish\n");
|
|
return 0;
|
|
}
|
|
|
|
int jsm_remove_uart_port(struct jsm_board *brd)
|
|
{
|
|
int i;
|
|
struct jsm_channel *ch;
|
|
|
|
if (!brd)
|
|
return -ENXIO;
|
|
|
|
jsm_printk(INIT, INFO, &brd->pci_dev, "start\n");
|
|
|
|
/*
|
|
* Initialize board structure elements.
|
|
*/
|
|
|
|
brd->nasync = brd->maxports;
|
|
|
|
/* Set up channel variables */
|
|
for (i = 0; i < brd->nasync; i++) {
|
|
|
|
if (!brd->channels[i])
|
|
continue;
|
|
|
|
ch = brd->channels[i];
|
|
|
|
uart_remove_one_port(&jsm_uart_driver, &brd->channels[i]->uart_port);
|
|
}
|
|
|
|
jsm_printk(INIT, INFO, &brd->pci_dev, "finish\n");
|
|
return 0;
|
|
}
|
|
|
|
void jsm_input(struct jsm_channel *ch)
|
|
{
|
|
struct jsm_board *bd;
|
|
struct tty_struct *tp;
|
|
struct tty_ldisc *ld;
|
|
u32 rmask;
|
|
u16 head;
|
|
u16 tail;
|
|
int data_len;
|
|
unsigned long lock_flags;
|
|
int flip_len = 0;
|
|
int len = 0;
|
|
int n = 0;
|
|
int s = 0;
|
|
int i = 0;
|
|
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev, "start\n");
|
|
|
|
if (!ch)
|
|
return;
|
|
|
|
tp = ch->uart_port.info->tty;
|
|
|
|
bd = ch->ch_bd;
|
|
if(!bd)
|
|
return;
|
|
|
|
spin_lock_irqsave(&ch->ch_lock, lock_flags);
|
|
|
|
/*
|
|
*Figure the number of characters in the buffer.
|
|
*Exit immediately if none.
|
|
*/
|
|
|
|
rmask = RQUEUEMASK;
|
|
|
|
head = ch->ch_r_head & rmask;
|
|
tail = ch->ch_r_tail & rmask;
|
|
|
|
data_len = (head - tail) & rmask;
|
|
if (data_len == 0) {
|
|
spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
|
|
return;
|
|
}
|
|
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev, "start\n");
|
|
|
|
/*
|
|
*If the device is not open, or CREAD is off, flush
|
|
*input data and return immediately.
|
|
*/
|
|
if (!tp ||
|
|
!(tp->termios->c_cflag & CREAD) ) {
|
|
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev,
|
|
"input. dropping %d bytes on port %d...\n", data_len, ch->ch_portnum);
|
|
ch->ch_r_head = tail;
|
|
|
|
/* Force queue flow control to be released, if needed */
|
|
jsm_check_queue_flow_control(ch);
|
|
|
|
spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If we are throttled, simply don't read any data.
|
|
*/
|
|
if (ch->ch_flags & CH_STOPI) {
|
|
spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev,
|
|
"Port %d throttled, not reading any data. head: %x tail: %x\n",
|
|
ch->ch_portnum, head, tail);
|
|
return;
|
|
}
|
|
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev, "start 2\n");
|
|
|
|
/*
|
|
* If the rxbuf is empty and we are not throttled, put as much
|
|
* as we can directly into the linux TTY buffer.
|
|
*
|
|
*/
|
|
flip_len = TTY_FLIPBUF_SIZE;
|
|
|
|
len = min(data_len, flip_len);
|
|
len = min(len, (N_TTY_BUF_SIZE - 1) - tp->read_cnt);
|
|
ld = tty_ldisc_ref(tp);
|
|
|
|
/*
|
|
* If we were unable to get a reference to the ld,
|
|
* don't flush our buffer, and act like the ld doesn't
|
|
* have any space to put the data right now.
|
|
*/
|
|
if (!ld) {
|
|
len = 0;
|
|
} else {
|
|
/*
|
|
* If ld doesn't have a pointer to a receive_buf function,
|
|
* flush the data, then act like the ld doesn't have any
|
|
* space to put the data right now.
|
|
*/
|
|
if (!ld->receive_buf) {
|
|
ch->ch_r_head = ch->ch_r_tail;
|
|
len = 0;
|
|
}
|
|
}
|
|
|
|
if (len <= 0) {
|
|
spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev, "jsm_input 1\n");
|
|
if (ld)
|
|
tty_ldisc_deref(ld);
|
|
return;
|
|
}
|
|
|
|
len = tty_buffer_request_room(tp, len);
|
|
n = len;
|
|
|
|
/*
|
|
* n now contains the most amount of data we can copy,
|
|
* bounded either by the flip buffer size or the amount
|
|
* of data the card actually has pending...
|
|
*/
|
|
while (n) {
|
|
s = ((head >= tail) ? head : RQUEUESIZE) - tail;
|
|
s = min(s, n);
|
|
|
|
if (s <= 0)
|
|
break;
|
|
|
|
/*
|
|
* If conditions are such that ld needs to see all
|
|
* UART errors, we will have to walk each character
|
|
* and error byte and send them to the buffer one at
|
|
* a time.
|
|
*/
|
|
|
|
if (I_PARMRK(tp) || I_BRKINT(tp) || I_INPCK(tp)) {
|
|
for (i = 0; i < s; i++) {
|
|
/*
|
|
* Give the Linux ld the flags in the
|
|
* format it likes.
|
|
*/
|
|
if (*(ch->ch_equeue +tail +i) & UART_LSR_BI)
|
|
tty_insert_flip_char(tp, *(ch->ch_rqueue +tail +i), TTY_BREAK);
|
|
else if (*(ch->ch_equeue +tail +i) & UART_LSR_PE)
|
|
tty_insert_flip_char(tp, *(ch->ch_rqueue +tail +i), TTY_PARITY);
|
|
else if (*(ch->ch_equeue +tail +i) & UART_LSR_FE)
|
|
tty_insert_flip_char(tp, *(ch->ch_rqueue +tail +i), TTY_FRAME);
|
|
else
|
|
tty_insert_flip_char(tp, *(ch->ch_rqueue +tail +i), TTY_NORMAL);
|
|
}
|
|
} else {
|
|
tty_insert_flip_string(tp, ch->ch_rqueue + tail, s) ;
|
|
}
|
|
tail += s;
|
|
n -= s;
|
|
/* Flip queue if needed */
|
|
tail &= rmask;
|
|
}
|
|
|
|
ch->ch_r_tail = tail & rmask;
|
|
ch->ch_e_tail = tail & rmask;
|
|
jsm_check_queue_flow_control(ch);
|
|
spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
|
|
|
|
/* Tell the tty layer its okay to "eat" the data now */
|
|
tty_flip_buffer_push(tp);
|
|
|
|
if (ld)
|
|
tty_ldisc_deref(ld);
|
|
|
|
jsm_printk(IOCTL, INFO, &ch->ch_bd->pci_dev, "finish\n");
|
|
}
|
|
|
|
static void jsm_carrier(struct jsm_channel *ch)
|
|
{
|
|
struct jsm_board *bd;
|
|
|
|
int virt_carrier = 0;
|
|
int phys_carrier = 0;
|
|
|
|
jsm_printk(CARR, INFO, &ch->ch_bd->pci_dev, "start\n");
|
|
if (!ch)
|
|
return;
|
|
|
|
bd = ch->ch_bd;
|
|
|
|
if (!bd)
|
|
return;
|
|
|
|
if (ch->ch_mistat & UART_MSR_DCD) {
|
|
jsm_printk(CARR, INFO, &ch->ch_bd->pci_dev,
|
|
"mistat: %x D_CD: %x\n", ch->ch_mistat, ch->ch_mistat & UART_MSR_DCD);
|
|
phys_carrier = 1;
|
|
}
|
|
|
|
if (ch->ch_c_cflag & CLOCAL)
|
|
virt_carrier = 1;
|
|
|
|
jsm_printk(CARR, INFO, &ch->ch_bd->pci_dev,
|
|
"DCD: physical: %d virt: %d\n", phys_carrier, virt_carrier);
|
|
|
|
/*
|
|
* Test for a VIRTUAL carrier transition to HIGH.
|
|
*/
|
|
if (((ch->ch_flags & CH_FCAR) == 0) && (virt_carrier == 1)) {
|
|
|
|
/*
|
|
* When carrier rises, wake any threads waiting
|
|
* for carrier in the open routine.
|
|
*/
|
|
|
|
jsm_printk(CARR, INFO, &ch->ch_bd->pci_dev,
|
|
"carrier: virt DCD rose\n");
|
|
|
|
if (waitqueue_active(&(ch->ch_flags_wait)))
|
|
wake_up_interruptible(&ch->ch_flags_wait);
|
|
}
|
|
|
|
/*
|
|
* Test for a PHYSICAL carrier transition to HIGH.
|
|
*/
|
|
if (((ch->ch_flags & CH_CD) == 0) && (phys_carrier == 1)) {
|
|
|
|
/*
|
|
* When carrier rises, wake any threads waiting
|
|
* for carrier in the open routine.
|
|
*/
|
|
|
|
jsm_printk(CARR, INFO, &ch->ch_bd->pci_dev,
|
|
"carrier: physical DCD rose\n");
|
|
|
|
if (waitqueue_active(&(ch->ch_flags_wait)))
|
|
wake_up_interruptible(&ch->ch_flags_wait);
|
|
}
|
|
|
|
/*
|
|
* Test for a PHYSICAL transition to low, so long as we aren't
|
|
* currently ignoring physical transitions (which is what "virtual
|
|
* carrier" indicates).
|
|
*
|
|
* The transition of the virtual carrier to low really doesn't
|
|
* matter... it really only means "ignore carrier state", not
|
|
* "make pretend that carrier is there".
|
|
*/
|
|
if ((virt_carrier == 0) && ((ch->ch_flags & CH_CD) != 0)
|
|
&& (phys_carrier == 0)) {
|
|
/*
|
|
* When carrier drops:
|
|
*
|
|
* Drop carrier on all open units.
|
|
*
|
|
* Flush queues, waking up any task waiting in the
|
|
* line discipline.
|
|
*
|
|
* Send a hangup to the control terminal.
|
|
*
|
|
* Enable all select calls.
|
|
*/
|
|
if (waitqueue_active(&(ch->ch_flags_wait)))
|
|
wake_up_interruptible(&ch->ch_flags_wait);
|
|
}
|
|
|
|
/*
|
|
* Make sure that our cached values reflect the current reality.
|
|
*/
|
|
if (virt_carrier == 1)
|
|
ch->ch_flags |= CH_FCAR;
|
|
else
|
|
ch->ch_flags &= ~CH_FCAR;
|
|
|
|
if (phys_carrier == 1)
|
|
ch->ch_flags |= CH_CD;
|
|
else
|
|
ch->ch_flags &= ~CH_CD;
|
|
}
|
|
|
|
|
|
void jsm_check_queue_flow_control(struct jsm_channel *ch)
|
|
{
|
|
struct board_ops *bd_ops = ch->ch_bd->bd_ops;
|
|
int qleft = 0;
|
|
|
|
/* Store how much space we have left in the queue */
|
|
if ((qleft = ch->ch_r_tail - ch->ch_r_head - 1) < 0)
|
|
qleft += RQUEUEMASK + 1;
|
|
|
|
/*
|
|
* Check to see if we should enforce flow control on our queue because
|
|
* the ld (or user) isn't reading data out of our queue fast enuf.
|
|
*
|
|
* NOTE: This is done based on what the current flow control of the
|
|
* port is set for.
|
|
*
|
|
* 1) HWFLOW (RTS) - Turn off the UART's Receive interrupt.
|
|
* This will cause the UART's FIFO to back up, and force
|
|
* the RTS signal to be dropped.
|
|
* 2) SWFLOW (IXOFF) - Keep trying to send a stop character to
|
|
* the other side, in hopes it will stop sending data to us.
|
|
* 3) NONE - Nothing we can do. We will simply drop any extra data
|
|
* that gets sent into us when the queue fills up.
|
|
*/
|
|
if (qleft < 256) {
|
|
/* HWFLOW */
|
|
if (ch->ch_c_cflag & CRTSCTS) {
|
|
if(!(ch->ch_flags & CH_RECEIVER_OFF)) {
|
|
bd_ops->disable_receiver(ch);
|
|
ch->ch_flags |= (CH_RECEIVER_OFF);
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev,
|
|
"Internal queue hit hilevel mark (%d)! Turning off interrupts.\n",
|
|
qleft);
|
|
}
|
|
}
|
|
/* SWFLOW */
|
|
else if (ch->ch_c_iflag & IXOFF) {
|
|
if (ch->ch_stops_sent <= MAX_STOPS_SENT) {
|
|
bd_ops->send_stop_character(ch);
|
|
ch->ch_stops_sent++;
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev,
|
|
"Sending stop char! Times sent: %x\n", ch->ch_stops_sent);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check to see if we should unenforce flow control because
|
|
* ld (or user) finally read enuf data out of our queue.
|
|
*
|
|
* NOTE: This is done based on what the current flow control of the
|
|
* port is set for.
|
|
*
|
|
* 1) HWFLOW (RTS) - Turn back on the UART's Receive interrupt.
|
|
* This will cause the UART's FIFO to raise RTS back up,
|
|
* which will allow the other side to start sending data again.
|
|
* 2) SWFLOW (IXOFF) - Send a start character to
|
|
* the other side, so it will start sending data to us again.
|
|
* 3) NONE - Do nothing. Since we didn't do anything to turn off the
|
|
* other side, we don't need to do anything now.
|
|
*/
|
|
if (qleft > (RQUEUESIZE / 2)) {
|
|
/* HWFLOW */
|
|
if (ch->ch_c_cflag & CRTSCTS) {
|
|
if (ch->ch_flags & CH_RECEIVER_OFF) {
|
|
bd_ops->enable_receiver(ch);
|
|
ch->ch_flags &= ~(CH_RECEIVER_OFF);
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev,
|
|
"Internal queue hit lowlevel mark (%d)! Turning on interrupts.\n",
|
|
qleft);
|
|
}
|
|
}
|
|
/* SWFLOW */
|
|
else if (ch->ch_c_iflag & IXOFF && ch->ch_stops_sent) {
|
|
ch->ch_stops_sent = 0;
|
|
bd_ops->send_start_character(ch);
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev, "Sending start char!\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* jsm_tty_write()
|
|
*
|
|
* Take data from the user or kernel and send it out to the FEP.
|
|
* In here exists all the Transparent Print magic as well.
|
|
*/
|
|
int jsm_tty_write(struct uart_port *port)
|
|
{
|
|
int bufcount = 0, n = 0;
|
|
int data_count = 0,data_count1 =0;
|
|
u16 head;
|
|
u16 tail;
|
|
u16 tmask;
|
|
u32 remain;
|
|
int temp_tail = port->info->xmit.tail;
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
|
|
tmask = WQUEUEMASK;
|
|
head = (channel->ch_w_head) & tmask;
|
|
tail = (channel->ch_w_tail) & tmask;
|
|
|
|
if ((bufcount = tail - head - 1) < 0)
|
|
bufcount += WQUEUESIZE;
|
|
|
|
n = bufcount;
|
|
|
|
n = min(n, 56);
|
|
remain = WQUEUESIZE - head;
|
|
|
|
data_count = 0;
|
|
if (n >= remain) {
|
|
n -= remain;
|
|
while ((port->info->xmit.head != temp_tail) &&
|
|
(data_count < remain)) {
|
|
channel->ch_wqueue[head++] =
|
|
port->info->xmit.buf[temp_tail];
|
|
|
|
temp_tail++;
|
|
temp_tail &= (UART_XMIT_SIZE - 1);
|
|
data_count++;
|
|
}
|
|
if (data_count == remain) head = 0;
|
|
}
|
|
|
|
data_count1 = 0;
|
|
if (n > 0) {
|
|
remain = n;
|
|
while ((port->info->xmit.head != temp_tail) &&
|
|
(data_count1 < remain)) {
|
|
channel->ch_wqueue[head++] =
|
|
port->info->xmit.buf[temp_tail];
|
|
|
|
temp_tail++;
|
|
temp_tail &= (UART_XMIT_SIZE - 1);
|
|
data_count1++;
|
|
|
|
}
|
|
}
|
|
|
|
port->info->xmit.tail = temp_tail;
|
|
|
|
data_count += data_count1;
|
|
if (data_count) {
|
|
head &= tmask;
|
|
channel->ch_w_head = head;
|
|
}
|
|
|
|
if (data_count) {
|
|
channel->ch_bd->bd_ops->copy_data_from_queue_to_uart(channel);
|
|
}
|
|
|
|
return data_count;
|
|
}
|