2005-04-16 22:20:36 +00:00
|
|
|
/************************************************************************
|
|
|
|
* 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>
|
2006-02-03 11:04:30 +00:00
|
|
|
* 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.
|
2005-04-16 22:20:36 +00:00
|
|
|
***********************************************************************/
|
|
|
|
#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 cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 08:04:11 +00:00
|
|
|
#include <linux/slab.h>
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
#include "jsm.h"
|
|
|
|
|
2009-06-11 11:56:00 +00:00
|
|
|
static DECLARE_BITMAP(linemap, MAXLINES);
|
|
|
|
|
2005-05-01 15:59:29 +00:00
|
|
|
static void jsm_carrier(struct jsm_channel *ch);
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2011-08-24 16:14:22 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
static void jsm_tty_write(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct jsm_channel *channel;
|
|
|
|
channel = container_of(port, struct jsm_channel, uart_port);
|
|
|
|
channel->ch_bd->bd_ops->copy_data_from_queue_to_uart(channel);
|
|
|
|
}
|
|
|
|
|
2005-08-31 09:12:14 +00:00
|
|
|
static void jsm_tty_start_tx(struct uart_port *port)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2005-08-31 09:12:14 +00:00
|
|
|
static void jsm_tty_stop_tx(struct uart_port *port)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
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;
|
2006-12-08 10:38:45 +00:00
|
|
|
struct ktermios *termios;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
spin_lock_irqsave(&port->lock, lock_flags);
|
2012-07-14 14:31:47 +00:00
|
|
|
termios = &port->state->port.tty->termios;
|
2006-03-31 10:32:12 +00:00
|
|
|
if (ch == termios->c_cc[VSTART])
|
2005-04-16 22:20:36 +00:00
|
|
|
channel->ch_bd->bd_ops->send_start_character(channel);
|
|
|
|
|
2006-03-31 10:32:12 +00:00
|
|
|
if (ch == termios->c_cc[VSTOP])
|
2005-04-16 22:20:36 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2009-01-30 16:21:49 +00:00
|
|
|
static void jsm_tty_enable_ms(struct uart_port *port)
|
|
|
|
{
|
|
|
|
/* Nothing needed */
|
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
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;
|
|
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
2006-12-08 10:38:45 +00:00
|
|
|
struct ktermios *termios;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* 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) {
|
2007-02-14 08:33:07 +00:00
|
|
|
channel->ch_rqueue = kzalloc(RQUEUESIZE, GFP_KERNEL);
|
2005-04-16 22:20:36 +00:00
|
|
|
if (!channel->ch_rqueue) {
|
|
|
|
jsm_printk(INIT, ERR, &channel->ch_bd->pci_dev,
|
|
|
|
"unable to allocate read queue buf");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!channel->ch_equeue) {
|
2007-02-14 08:33:07 +00:00
|
|
|
channel->ch_equeue = kzalloc(EQUEUESIZE, GFP_KERNEL);
|
2005-04-16 22:20:36 +00:00
|
|
|
if (!channel->ch_equeue) {
|
|
|
|
jsm_printk(INIT, ERR, &channel->ch_bd->pci_dev,
|
|
|
|
"unable to allocate error queue buf");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2012-07-14 14:31:47 +00:00
|
|
|
termios = &port->state->port.tty->termios;
|
2006-03-31 10:32:12 +00:00
|
|
|
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];
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* 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");
|
2009-04-07 15:53:48 +00:00
|
|
|
return 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void jsm_tty_close(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct jsm_board *bd;
|
2006-12-08 10:38:45 +00:00
|
|
|
struct ktermios *ts;
|
2005-04-16 22:20:36 +00:00
|
|
|
struct jsm_channel *channel = (struct jsm_channel *)port;
|
|
|
|
|
|
|
|
jsm_printk(CLOSE, INFO, &channel->ch_bd->pci_dev, "start\n");
|
|
|
|
|
|
|
|
bd = channel->ch_bd;
|
2012-07-14 14:31:47 +00:00
|
|
|
ts = &port->state->port.tty->termios;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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,
|
2006-12-08 10:38:45 +00:00
|
|
|
struct ktermios *termios,
|
|
|
|
struct ktermios *old_termios)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
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,
|
2009-01-30 16:21:49 +00:00
|
|
|
.enable_ms = jsm_tty_enable_ms,
|
2005-04-16 22:20:36 +00:00
|
|
|
.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.
|
|
|
|
*/
|
2009-04-06 16:34:17 +00:00
|
|
|
int __devinit jsm_tty_init(struct jsm_board *brd)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
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.
|
|
|
|
*/
|
2007-02-14 08:33:07 +00:00
|
|
|
brd->channels[i] = kzalloc(sizeof(struct jsm_channel), GFP_KERNEL);
|
2005-04-16 22:20:36 +00:00
|
|
|
if (!brd->channels[i]) {
|
|
|
|
jsm_printk(CORE, ERR, &brd->pci_dev,
|
|
|
|
"%s:%d Unable to allocate memory for channel struct\n",
|
|
|
|
__FILE__, __LINE__);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ch = brd->channels[0];
|
|
|
|
vaddr = brd->re_map_membase;
|
|
|
|
|
|
|
|
/* Set up channel variables */
|
|
|
|
for (i = 0; i < brd->nasync; i++, ch = brd->channels[i]) {
|
|
|
|
|
|
|
|
if (!brd->channels[i])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-10-14 17:57:51 +00:00
|
|
|
int jsm_uart_port_init(struct jsm_board *brd)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2010-02-25 18:31:50 +00:00
|
|
|
int i, rc;
|
2009-06-11 11:56:00 +00:00
|
|
|
unsigned int line;
|
2005-04-16 22:20:36 +00:00
|
|
|
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;
|
2007-05-08 07:26:30 +00:00
|
|
|
brd->channels[i]->uart_port.uartclk = 14745600;
|
2005-04-16 22:20:36 +00:00
|
|
|
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;
|
2009-06-11 11:56:00 +00:00
|
|
|
line = find_first_zero_bit(linemap, MAXLINES);
|
|
|
|
if (line >= MAXLINES) {
|
|
|
|
printk(KERN_INFO "jsm: linemap is full, added device failed\n");
|
|
|
|
continue;
|
|
|
|
} else
|
2009-06-24 17:34:43 +00:00
|
|
|
set_bit(line, linemap);
|
2009-06-11 11:56:00 +00:00
|
|
|
brd->channels[i]->uart_port.line = line;
|
2010-02-25 18:31:50 +00:00
|
|
|
rc = uart_add_one_port (&jsm_uart_driver, &brd->channels[i]->uart_port);
|
|
|
|
if (rc){
|
|
|
|
printk(KERN_INFO "jsm: Port %d failed. Aborting...\n", i);
|
|
|
|
return rc;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
else
|
2009-09-24 19:58:23 +00:00
|
|
|
printk(KERN_INFO "jsm: Port %d added\n", i);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
2009-06-24 17:34:43 +00:00
|
|
|
clear_bit(ch->uart_port.line, linemap);
|
2005-04-16 22:20:36 +00:00
|
|
|
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;
|
|
|
|
u32 rmask;
|
|
|
|
u16 head;
|
|
|
|
u16 tail;
|
|
|
|
int data_len;
|
|
|
|
unsigned long lock_flags;
|
|
|
|
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;
|
|
|
|
|
2009-09-19 20:13:28 +00:00
|
|
|
tp = ch->uart_port.state->port.tty;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
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 ||
|
2012-07-14 14:31:47 +00:00
|
|
|
!(tp->termios.c_cflag & CREAD) ) {
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
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");
|
|
|
|
|
2007-10-17 06:27:05 +00:00
|
|
|
if (data_len <= 0) {
|
2006-02-03 11:04:30 +00:00
|
|
|
spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
|
|
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev, "jsm_input 1\n");
|
|
|
|
return;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2007-10-17 06:27:05 +00:00
|
|
|
len = tty_buffer_request_room(tp, data_len);
|
2005-04-16 22:20:36 +00:00
|
|
|
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;
|
|
|
|
|
2006-02-03 11:04:30 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
if (I_PARMRK(tp) || I_BRKINT(tp) || I_INPCK(tp)) {
|
2006-02-03 11:04:30 +00:00
|
|
|
for (i = 0; i < s; i++) {
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
|
|
|
* Give the Linux ld the flags in the
|
|
|
|
* format it likes.
|
|
|
|
*/
|
2006-02-03 11:04:30 +00:00
|
|
|
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);
|
2005-04-16 22:20:36 +00:00
|
|
|
else
|
2007-10-17 06:27:05 +00:00
|
|
|
tty_insert_flip_char(tp, *(ch->ch_rqueue +tail +i), TTY_NORMAL);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
} else {
|
2006-02-03 11:04:30 +00:00
|
|
|
tty_insert_flip_string(tp, ch->ch_rqueue + tail, s) ;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2006-02-03 11:04:30 +00:00
|
|
|
tail += s;
|
|
|
|
n -= s;
|
|
|
|
/* Flip queue if needed */
|
|
|
|
tail &= rmask;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2006-02-03 11:04:30 +00:00
|
|
|
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);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-02-03 11:04:30 +00:00
|
|
|
/* Tell the tty layer its okay to "eat" the data now */
|
|
|
|
tty_flip_buffer_push(tp);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
jsm_printk(IOCTL, INFO, &ch->ch_bd->pci_dev, "finish\n");
|
|
|
|
}
|
|
|
|
|
2005-05-01 15:59:29 +00:00
|
|
|
static void jsm_carrier(struct jsm_channel *ch)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
2006-03-31 10:32:12 +00:00
|
|
|
struct board_ops *bd_ops = ch->ch_bd->bd_ops;
|
2009-04-07 15:53:48 +00:00
|
|
|
int qleft;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* 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)) {
|
2006-03-31 10:32:12 +00:00
|
|
|
bd_ops->disable_receiver(ch);
|
2005-04-16 22:20:36 +00:00
|
|
|
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) {
|
2006-03-31 10:32:12 +00:00
|
|
|
bd_ops->send_stop_character(ch);
|
2005-04-16 22:20:36 +00:00
|
|
|
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) {
|
2006-03-31 10:32:12 +00:00
|
|
|
bd_ops->enable_receiver(ch);
|
2005-04-16 22:20:36 +00:00
|
|
|
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;
|
2006-03-31 10:32:12 +00:00
|
|
|
bd_ops->send_start_character(ch);
|
2005-04-16 22:20:36 +00:00
|
|
|
jsm_printk(READ, INFO, &ch->ch_bd->pci_dev, "Sending start char!\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|