linux-stable/drivers/tty/mxser.c

2595 lines
66 KiB
C
Raw Normal View History

tty: add SPDX identifiers to all remaining files in drivers/tty/ It's good to have SPDX identifiers in all files to make it easier to audit the kernel tree for correct licenses. Update the drivers/tty files files with the correct SPDX license identifier based on the license text in the file itself. The SPDX identifier is a legally binding shorthand, which can be used instead of the full boiler plate text. This work is based on a script and data from Thomas Gleixner, Philippe Ombredanne, and Kate Stewart. Cc: Jiri Slaby <jslaby@suse.com> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Michael Ellerman <mpe@ellerman.id.au> Cc: Chris Metcalf <cmetcalf@mellanox.com> Cc: Jiri Kosina <jikos@kernel.org> Cc: David Sterba <dsterba@suse.com> Cc: James Hogan <jhogan@kernel.org> Cc: Rob Herring <robh@kernel.org> Cc: Eric Anholt <eric@anholt.net> Cc: Stefan Wahren <stefan.wahren@i2se.com> Cc: Florian Fainelli <f.fainelli@gmail.com> Cc: Ray Jui <rjui@broadcom.com> Cc: Scott Branden <sbranden@broadcom.com> Cc: bcm-kernel-feedback-list@broadcom.com Cc: "James E.J. Bottomley" <jejb@parisc-linux.org> Cc: Helge Deller <deller@gmx.de> Cc: Joachim Eastwood <manabian@gmail.com> Cc: Matthias Brugger <matthias.bgg@gmail.com> Cc: Masahiro Yamada <yamada.masahiro@socionext.com> Cc: Tobias Klauser <tklauser@distanz.ch> Cc: Russell King <linux@armlinux.org.uk> Cc: Vineet Gupta <vgupta@synopsys.com> Cc: Richard Genoud <richard.genoud@gmail.com> Cc: Alexander Shiyan <shc_work@mail.ru> Cc: Baruch Siach <baruch@tkos.co.il> Cc: "Maciej W. Rozycki" <macro@linux-mips.org> Cc: "Uwe Kleine-König" <kernel@pengutronix.de> Cc: Pat Gefre <pfg@sgi.com> Cc: "Guilherme G. Piccoli" <gpiccoli@linux.vnet.ibm.com> Cc: Jason Wessel <jason.wessel@windriver.com> Cc: Vladimir Zapolskiy <vz@mleia.com> Cc: Sylvain Lemieux <slemieux.tyco@gmail.com> Cc: Carlo Caione <carlo@caione.org> Cc: Kevin Hilman <khilman@baylibre.com> Cc: Liviu Dudau <liviu.dudau@arm.com> Cc: Sudeep Holla <sudeep.holla@arm.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andy Gross <andy.gross@linaro.org> Cc: David Brown <david.brown@linaro.org> Cc: "Andreas Färber" <afaerber@suse.de> Cc: Kevin Cernekee <cernekee@gmail.com> Cc: Laxman Dewangan <ldewangan@nvidia.com> Cc: Thierry Reding <thierry.reding@gmail.com> Cc: Jonathan Hunter <jonathanh@nvidia.com> Cc: Barry Song <baohua@kernel.org> Cc: Patrice Chotard <patrice.chotard@st.com> Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com> Cc: Alexandre Torgue <alexandre.torgue@st.com> Cc: "David S. Miller" <davem@davemloft.net> Cc: Peter Korsgaard <jacmet@sunsite.dk> Cc: Timur Tabi <timur@tabi.org> Cc: Tony Prisk <linux@prisktech.co.nz> Cc: Michal Simek <michal.simek@xilinx.com> Cc: "Sören Brinkmann" <soren.brinkmann@xilinx.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Kate Stewart <kstewart@linuxfoundation.org> Cc: Philippe Ombredanne <pombredanne@nexb.com> Cc: Jiri Slaby <jslaby@suse.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-11-06 17:11:51 +00:00
// SPDX-License-Identifier: GPL-2.0+
/*
* mxser.c -- MOXA Smartio/Industio family multiport serial driver.
*
* Copyright (C) 1999-2006 Moxa Technologies (support@moxa.com).
* Copyright (C) 2006-2008 Jiri Slaby <jirislaby@gmail.com>
*
* This code is loosely based on the 1.8 moxa driver which is based on
* Linux serial driver, written by Linus Torvalds, Theodore T'so and
* others.
*
* Fed through a cleanup, indent and remove of non 2.6 code by Alan Cox
* <alan@lxorguk.ukuu.org.uk>. The original 1.8 code is available on
* www.moxa.com.
* - Fixed x86_64 cleanness
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.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/delay.h>
#include <linux/pci.h>
#include <linux/bitops.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>
#include <linux/ratelimit.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <linux/uaccess.h>
/*
* Semi-public control interfaces
*/
/*
* MOXA ioctls
*/
#define MOXA 0x400
#define MOXA_GETDATACOUNT (MOXA + 23)
#define MOXA_DIAGNOSE (MOXA + 50)
#define MOXA_CHKPORTENABLE (MOXA + 60)
#define MOXA_HighSpeedOn (MOXA + 61)
#define MOXA_GET_MAJOR (MOXA + 63)
#define MOXA_GETMSTATUS (MOXA + 65)
#define MOXA_SET_OP_MODE (MOXA + 66)
#define MOXA_GET_OP_MODE (MOXA + 67)
#define RS232_MODE 0
#define RS485_2WIRE_MODE 1
#define RS422_MODE 2
#define RS485_4WIRE_MODE 3
#define OP_MODE_MASK 3
#define MOXA_SDS_RSTICOUNTER (MOXA + 69)
#define MOXA_ASPP_OQUEUE (MOXA + 70)
#define MOXA_ASPP_MON (MOXA + 73)
#define MOXA_ASPP_LSTATUS (MOXA + 74)
#define MOXA_ASPP_MON_EXT (MOXA + 75)
#define MOXA_SET_BAUD_METHOD (MOXA + 76)
/* --------------------------------------------------- */
#define NPPI_NOTIFY_PARITY 0x01
#define NPPI_NOTIFY_FRAMING 0x02
#define NPPI_NOTIFY_HW_OVERRUN 0x04
#define NPPI_NOTIFY_SW_OVERRUN 0x08
#define NPPI_NOTIFY_BREAK 0x10
#define NPPI_NOTIFY_CTSHOLD 0x01 /* Tx hold by CTS low */
#define NPPI_NOTIFY_DSRHOLD 0x02 /* Tx hold by DSR low */
#define NPPI_NOTIFY_XOFFHOLD 0x08 /* Tx hold by Xoff received */
#define NPPI_NOTIFY_XOFFXENT 0x10 /* Xoff Sent */
/*
* Follow just what Moxa Must chip defines.
*
* When LCR register (offset 0x03) writes the following value, the Must chip
* will enter enchance mode. And write value on EFR (offset 0x02) bit 6,7 to
* change bank.
*/
#define MOXA_MUST_ENTER_ENCHANCE 0xBF
/* when enhance mode enabled, access on general bank register */
#define MOXA_MUST_GDL_REGISTER 0x07
#define MOXA_MUST_GDL_MASK 0x7F
#define MOXA_MUST_GDL_HAS_BAD_DATA 0x80
#define MOXA_MUST_LSR_RERR 0x80 /* error in receive FIFO */
/* enchance register bank select and enchance mode setting register */
/* when LCR register equals to 0xBF */
#define MOXA_MUST_EFR_REGISTER 0x02
#define MOXA_MUST_EFR_EFRB_ENABLE 0x10 /* enchance mode enable */
/* enchance register bank set 0, 1, 2 */
#define MOXA_MUST_EFR_BANK0 0x00
#define MOXA_MUST_EFR_BANK1 0x40
#define MOXA_MUST_EFR_BANK2 0x80
#define MOXA_MUST_EFR_BANK3 0xC0
#define MOXA_MUST_EFR_BANK_MASK 0xC0
/* set XON1 value register, when LCR=0xBF and change to bank0 */
#define MOXA_MUST_XON1_REGISTER 0x04
/* set XON2 value register, when LCR=0xBF and change to bank0 */
#define MOXA_MUST_XON2_REGISTER 0x05
/* set XOFF1 value register, when LCR=0xBF and change to bank0 */
#define MOXA_MUST_XOFF1_REGISTER 0x06
/* set XOFF2 value register, when LCR=0xBF and change to bank0 */
#define MOXA_MUST_XOFF2_REGISTER 0x07
#define MOXA_MUST_RBRTL_REGISTER 0x04
#define MOXA_MUST_RBRTH_REGISTER 0x05
#define MOXA_MUST_RBRTI_REGISTER 0x06
#define MOXA_MUST_THRTL_REGISTER 0x07
#define MOXA_MUST_ENUM_REGISTER 0x04
#define MOXA_MUST_HWID_REGISTER 0x05
#define MOXA_MUST_ECR_REGISTER 0x06
#define MOXA_MUST_CSR_REGISTER 0x07
#define MOXA_MUST_FCR_GDA_MODE_ENABLE 0x20 /* good data mode enable */
#define MOXA_MUST_FCR_GDA_ONLY_ENABLE 0x10 /* only good data put into RxFIFO */
#define MOXA_MUST_IER_ECTSI 0x80 /* enable CTS interrupt */
#define MOXA_MUST_IER_ERTSI 0x40 /* enable RTS interrupt */
#define MOXA_MUST_IER_XINT 0x20 /* enable Xon/Xoff interrupt */
#define MOXA_MUST_IER_EGDAI 0x10 /* enable GDA interrupt */
#define MOXA_MUST_RECV_ISR (UART_IER_RDI | MOXA_MUST_IER_EGDAI)
/* GDA interrupt pending */
#define MOXA_MUST_IIR_GDA 0x1C
#define MOXA_MUST_IIR_RDA 0x04
#define MOXA_MUST_IIR_RTO 0x0C
#define MOXA_MUST_IIR_LSR 0x06
/* received Xon/Xoff or specical interrupt pending */
#define MOXA_MUST_IIR_XSC 0x10
/* RTS/CTS change state interrupt pending */
#define MOXA_MUST_IIR_RTSCTS 0x20
#define MOXA_MUST_IIR_MASK 0x3E
#define MOXA_MUST_MCR_XON_FLAG 0x40
#define MOXA_MUST_MCR_XON_ANY 0x80
#define MOXA_MUST_MCR_TX_XON 0x08
#define MOXA_MUST_EFR_SF_MASK 0x0F /* software flow control on chip mask value */
#define MOXA_MUST_EFR_SF_TX1 0x08 /* send Xon1/Xoff1 */
#define MOXA_MUST_EFR_SF_TX2 0x04 /* send Xon2/Xoff2 */
#define MOXA_MUST_EFR_SF_TX12 0x0C /* send Xon1,Xon2/Xoff1,Xoff2 */
#define MOXA_MUST_EFR_SF_TX_NO 0x00 /* don't send Xon/Xoff */
#define MOXA_MUST_EFR_SF_TX_MASK 0x0C /* Tx software flow control mask */
#define MOXA_MUST_EFR_SF_RX_NO 0x00 /* don't receive Xon/Xoff */
#define MOXA_MUST_EFR_SF_RX1 0x02 /* receive Xon1/Xoff1 */
#define MOXA_MUST_EFR_SF_RX2 0x01 /* receive Xon2/Xoff2 */
#define MOXA_MUST_EFR_SF_RX12 0x03 /* receive Xon1,Xon2/Xoff1,Xoff2 */
#define MOXA_MUST_EFR_SF_RX_MASK 0x03 /* Rx software flow control mask */
#define MXSERMAJOR 174
#define MXSER_BOARDS 4 /* Max. boards */
#define MXSER_PORTS_PER_BOARD 8 /* Max. ports per board */
#define MXSER_PORTS (MXSER_BOARDS * MXSER_PORTS_PER_BOARD)
#define MXSER_ISR_PASS_LIMIT 100
#define WAKEUP_CHARS 256
#define UART_MCR_AFE 0x20
#define UART_LSR_SPECIAL 0x1E
#define PCI_DEVICE_ID_POS104UL 0x1044
#define PCI_DEVICE_ID_CB108 0x1080
#define PCI_DEVICE_ID_CP102UF 0x1023
#define PCI_DEVICE_ID_CP112UL 0x1120
#define PCI_DEVICE_ID_CB114 0x1142
#define PCI_DEVICE_ID_CP114UL 0x1143
#define PCI_DEVICE_ID_CB134I 0x1341
#define PCI_DEVICE_ID_CP138U 0x1380
#define MXSER_HIGHBAUD 1
enum mxser_must_hwid {
MOXA_OTHER_UART = 0x00,
MOXA_MUST_MU150_HWID = 0x01,
MOXA_MUST_MU860_HWID = 0x02,
};
static const struct {
u8 type;
u8 fifo_size;
u8 rx_high_water;
u8 rx_low_water;
speed_t max_baud;
} Gpci_uart_info[] = {
{ MOXA_OTHER_UART, 16, 14, 1, 921600 },
{ MOXA_MUST_MU150_HWID, 64, 48, 16, 230400 },
{ MOXA_MUST_MU860_HWID, 128, 96, 32, 921600 }
};
#define UART_INFO_NUM ARRAY_SIZE(Gpci_uart_info)
struct mxser_cardinfo {
char *name;
unsigned int nports;
unsigned int flags;
};
static const struct mxser_cardinfo mxser_cards[] = {
/* 0*/ { "C168H/PCI series", 8, },
{ "C104H/PCI series", 4, },
{ "CP-132 series", 2, },
{ "CP-114 series", 4, },
{ "CT-114 series", 4, },
/* 5*/ { "CP-102 series", 2, MXSER_HIGHBAUD },
{ "CP-104U series", 4, },
{ "CP-168U series", 8, },
{ "CP-132U series", 2, },
{ "CP-134U series", 4, },
/*10*/ { "CP-104JU series", 4, },
{ "Moxa UC7000 Serial", 8, }, /* RC7000 */
{ "CP-118U series", 8, },
{ "CP-102UL series", 2, },
{ "CP-102U series", 2, },
/*15*/ { "CP-118EL series", 8, },
{ "CP-168EL series", 8, },
{ "CP-104EL series", 4, },
{ "CB-108 series", 8, },
{ "CB-114 series", 4, },
/*20*/ { "CB-134I series", 4, },
{ "CP-138U series", 8, },
{ "POS-104UL series", 4, },
{ "CP-114UL series", 4, },
{ "CP-102UF series", 2, },
/*25*/ { "CP-112UL series", 2, },
};
/* driver_data correspond to the lines in the structure above
see also ISA probe function before you change something */
static const struct pci_device_id mxser_pcibrds[] = {
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_C168), .driver_data = 0 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_C104), .driver_data = 1 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP132), .driver_data = 2 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP114), .driver_data = 3 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CT114), .driver_data = 4 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP102), .driver_data = 5 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP104U), .driver_data = 6 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP168U), .driver_data = 7 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP132U), .driver_data = 8 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP134U), .driver_data = 9 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP104JU),.driver_data = 10 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_RC7000), .driver_data = 11 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP118U), .driver_data = 12 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP102UL),.driver_data = 13 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP102U), .driver_data = 14 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP118EL),.driver_data = 15 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP168EL),.driver_data = 16 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP104EL),.driver_data = 17 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_CB108), .driver_data = 18 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_CB114), .driver_data = 19 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_CB134I), .driver_data = 20 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_CP138U), .driver_data = 21 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_POS104UL), .driver_data = 22 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_CP114UL), .driver_data = 23 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_CP102UF), .driver_data = 24 },
{ PCI_VDEVICE(MOXA, PCI_DEVICE_ID_CP112UL), .driver_data = 25 },
{ }
};
MODULE_DEVICE_TABLE(pci, mxser_pcibrds);
static int ttymajor = MXSERMAJOR;
/* Variables for insmod */
MODULE_AUTHOR("Casper Yang");
MODULE_DESCRIPTION("MOXA Smartio/Industio Family Multiport Board Device Driver");
module_param(ttymajor, int, 0);
MODULE_LICENSE("GPL");
struct mxser_log {
int tick;
unsigned long rxcnt[MXSER_PORTS];
unsigned long txcnt[MXSER_PORTS];
};
struct mxser_mon {
unsigned long rxcnt;
unsigned long txcnt;
unsigned long up_rxcnt;
unsigned long up_txcnt;
int modem_status;
unsigned char hold_reason;
};
struct mxser_mon_ext {
unsigned long rx_cnt[32];
unsigned long tx_cnt[32];
unsigned long up_rxcnt[32];
unsigned long up_txcnt[32];
int modem_status[32];
long baudrate[32];
int databits[32];
int stopbits[32];
int parity[32];
int flowctrl[32];
int fifo[32];
int iftype[32];
};
struct mxser_board;
struct mxser_port {
struct tty_port port;
struct mxser_board *board;
unsigned long ioaddr;
unsigned long opmode_ioaddr;
u8 rx_high_water;
u8 rx_low_water;
int baud_base; /* max. speed */
int type; /* UART type */
int x_char; /* xon/xoff character */
int IER; /* Interrupt Enable Register */
int MCR; /* Modem control register */
unsigned char stop_rx;
unsigned char ldisc_stop_rx;
int custom_divisor;
unsigned char err_shadow;
struct async_icount icount; /* kernel counters for 4 input interrupts */
unsigned int timeout;
int read_status_mask;
int ignore_status_mask;
u8 xmit_fifo_size;
int xmit_head;
int xmit_tail;
int xmit_cnt;
int closing;
struct ktermios normal_termios;
struct mxser_mon mon_data;
spinlock_t slock;
};
struct mxser_board {
unsigned int idx;
int irq;
const struct mxser_cardinfo *info;
unsigned long vector;
enum mxser_must_hwid must_hwid;
speed_t max_baud;
struct mxser_port ports[MXSER_PORTS_PER_BOARD];
};
struct mxser_mstatus {
tcflag_t cflag;
int cts;
int dsr;
int ri;
int dcd;
};
static struct mxser_board mxser_boards[MXSER_BOARDS];
static struct tty_driver *mxvar_sdriver;
static struct mxser_log mxvar_log;
static int mxser_set_baud_method[MXSER_PORTS + 1];
static void mxser_enable_must_enchance_mode(unsigned long baseio)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(baseio + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
efr = inb(baseio + MOXA_MUST_EFR_REGISTER);
efr |= MOXA_MUST_EFR_EFRB_ENABLE;
outb(efr, baseio + MOXA_MUST_EFR_REGISTER);
outb(oldlcr, baseio + UART_LCR);
}
static void mxser_disable_must_enchance_mode(unsigned long baseio)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(baseio + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
efr = inb(baseio + MOXA_MUST_EFR_REGISTER);
efr &= ~MOXA_MUST_EFR_EFRB_ENABLE;
outb(efr, baseio + MOXA_MUST_EFR_REGISTER);
outb(oldlcr, baseio + UART_LCR);
}
static void mxser_set_must_xon1_value(unsigned long baseio, u8 value)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(baseio + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
efr = inb(baseio + MOXA_MUST_EFR_REGISTER);
efr &= ~MOXA_MUST_EFR_BANK_MASK;
efr |= MOXA_MUST_EFR_BANK0;
outb(efr, baseio + MOXA_MUST_EFR_REGISTER);
outb(value, baseio + MOXA_MUST_XON1_REGISTER);
outb(oldlcr, baseio + UART_LCR);
}
static void mxser_set_must_xoff1_value(unsigned long baseio, u8 value)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(baseio + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
efr = inb(baseio + MOXA_MUST_EFR_REGISTER);
efr &= ~MOXA_MUST_EFR_BANK_MASK;
efr |= MOXA_MUST_EFR_BANK0;
outb(efr, baseio + MOXA_MUST_EFR_REGISTER);
outb(value, baseio + MOXA_MUST_XOFF1_REGISTER);
outb(oldlcr, baseio + UART_LCR);
}
static void mxser_set_must_fifo_value(struct mxser_port *info)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(info->ioaddr + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, info->ioaddr + UART_LCR);
efr = inb(info->ioaddr + MOXA_MUST_EFR_REGISTER);
efr &= ~MOXA_MUST_EFR_BANK_MASK;
efr |= MOXA_MUST_EFR_BANK1;
outb(efr, info->ioaddr + MOXA_MUST_EFR_REGISTER);
outb(info->rx_high_water, info->ioaddr + MOXA_MUST_RBRTH_REGISTER);
outb(info->rx_high_water, info->ioaddr + MOXA_MUST_RBRTI_REGISTER);
outb(info->rx_low_water, info->ioaddr + MOXA_MUST_RBRTL_REGISTER);
outb(oldlcr, info->ioaddr + UART_LCR);
}
static void mxser_set_must_enum_value(unsigned long baseio, u8 value)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(baseio + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
efr = inb(baseio + MOXA_MUST_EFR_REGISTER);
efr &= ~MOXA_MUST_EFR_BANK_MASK;
efr |= MOXA_MUST_EFR_BANK2;
outb(efr, baseio + MOXA_MUST_EFR_REGISTER);
outb(value, baseio + MOXA_MUST_ENUM_REGISTER);
outb(oldlcr, baseio + UART_LCR);
}
static void mxser_get_must_hardware_id(unsigned long baseio, u8 *pId)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(baseio + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
efr = inb(baseio + MOXA_MUST_EFR_REGISTER);
efr &= ~MOXA_MUST_EFR_BANK_MASK;
efr |= MOXA_MUST_EFR_BANK2;
outb(efr, baseio + MOXA_MUST_EFR_REGISTER);
*pId = inb(baseio + MOXA_MUST_HWID_REGISTER);
outb(oldlcr, baseio + UART_LCR);
}
static void SET_MOXA_MUST_NO_SOFTWARE_FLOW_CONTROL(unsigned long baseio)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(baseio + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
efr = inb(baseio + MOXA_MUST_EFR_REGISTER);
efr &= ~MOXA_MUST_EFR_SF_MASK;
outb(efr, baseio + MOXA_MUST_EFR_REGISTER);
outb(oldlcr, baseio + UART_LCR);
}
static void mxser_enable_must_tx_software_flow_control(unsigned long baseio)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(baseio + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
efr = inb(baseio + MOXA_MUST_EFR_REGISTER);
efr &= ~MOXA_MUST_EFR_SF_TX_MASK;
efr |= MOXA_MUST_EFR_SF_TX1;
outb(efr, baseio + MOXA_MUST_EFR_REGISTER);
outb(oldlcr, baseio + UART_LCR);
}
static void mxser_disable_must_tx_software_flow_control(unsigned long baseio)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(baseio + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
efr = inb(baseio + MOXA_MUST_EFR_REGISTER);
efr &= ~MOXA_MUST_EFR_SF_TX_MASK;
outb(efr, baseio + MOXA_MUST_EFR_REGISTER);
outb(oldlcr, baseio + UART_LCR);
}
static void mxser_enable_must_rx_software_flow_control(unsigned long baseio)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(baseio + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
efr = inb(baseio + MOXA_MUST_EFR_REGISTER);
efr &= ~MOXA_MUST_EFR_SF_RX_MASK;
efr |= MOXA_MUST_EFR_SF_RX1;
outb(efr, baseio + MOXA_MUST_EFR_REGISTER);
outb(oldlcr, baseio + UART_LCR);
}
static void mxser_disable_must_rx_software_flow_control(unsigned long baseio)
{
u8 oldlcr;
u8 efr;
oldlcr = inb(baseio + UART_LCR);
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
efr = inb(baseio + MOXA_MUST_EFR_REGISTER);
efr &= ~MOXA_MUST_EFR_SF_RX_MASK;
outb(efr, baseio + MOXA_MUST_EFR_REGISTER);
outb(oldlcr, baseio + UART_LCR);
}
static enum mxser_must_hwid mxser_must_get_hwid(unsigned long io)
{
u8 oldmcr, hwid;
int i;
outb(0, io + UART_LCR);
mxser_disable_must_enchance_mode(io);
oldmcr = inb(io + UART_MCR);
outb(0, io + UART_MCR);
mxser_set_must_xon1_value(io, 0x11);
if ((hwid = inb(io + UART_MCR)) != 0) {
outb(oldmcr, io + UART_MCR);
return MOXA_OTHER_UART;
}
mxser_get_must_hardware_id(io, &hwid);
for (i = 1; i < UART_INFO_NUM; i++) /* 0 = OTHER_UART */
if (hwid == Gpci_uart_info[i].type)
return hwid;
return MOXA_OTHER_UART;
}
static void process_txrx_fifo(struct mxser_port *info)
{
int i;
if ((info->type == PORT_16450) || (info->type == PORT_8250)) {
info->rx_high_water = 1;
info->rx_low_water = 1;
info->xmit_fifo_size = 1;
} else
for (i = 0; i < UART_INFO_NUM; i++)
if (info->board->must_hwid == Gpci_uart_info[i].type) {
info->rx_low_water = Gpci_uart_info[i].rx_low_water;
info->rx_high_water = Gpci_uart_info[i].rx_high_water;
info->xmit_fifo_size = Gpci_uart_info[i].fifo_size;
break;
}
}
static unsigned char mxser_get_msr(int baseaddr, int mode, int port)
{
static unsigned char mxser_msr[MXSER_PORTS + 1];
unsigned char status = 0;
status = inb(baseaddr + UART_MSR);
mxser_msr[port] &= 0x0F;
mxser_msr[port] |= status;
status = mxser_msr[port];
if (mode)
mxser_msr[port] = 0;
return status;
}
static int mxser_carrier_raised(struct tty_port *port)
{
struct mxser_port *mp = container_of(port, struct mxser_port, port);
return (inb(mp->ioaddr + UART_MSR) & UART_MSR_DCD)?1:0;
}
static void mxser_dtr_rts(struct tty_port *port, int on)
{
struct mxser_port *mp = container_of(port, struct mxser_port, port);
unsigned long flags;
spin_lock_irqsave(&mp->slock, flags);
if (on)
outb(inb(mp->ioaddr + UART_MCR) |
UART_MCR_DTR | UART_MCR_RTS, mp->ioaddr + UART_MCR);
else
outb(inb(mp->ioaddr + UART_MCR)&~(UART_MCR_DTR | UART_MCR_RTS),
mp->ioaddr + UART_MCR);
spin_unlock_irqrestore(&mp->slock, flags);
}
static int mxser_set_baud(struct tty_struct *tty, speed_t newspd)
{
struct mxser_port *info = tty->driver_data;
unsigned int quot = 0, baud;
unsigned char cval;
u64 timeout;
if (!info->ioaddr)
return -1;
if (newspd > info->board->max_baud)
return -1;
if (newspd == 134) {
quot = 2 * info->baud_base / 269;
tty_encode_baud_rate(tty, 134, 134);
} else if (newspd) {
quot = info->baud_base / newspd;
if (quot == 0)
quot = 1;
baud = info->baud_base/quot;
tty_encode_baud_rate(tty, baud, baud);
} else {
quot = 0;
}
/*
* worst case (128 * 1000 * 10 * 18432) needs 35 bits, so divide in the
* u64 domain
*/
timeout = (u64)info->xmit_fifo_size * HZ * 10 * quot;
do_div(timeout, info->baud_base);
info->timeout = timeout + HZ / 50; /* Add .02 seconds of slop */
if (quot) {
info->MCR |= UART_MCR_DTR;
outb(info->MCR, info->ioaddr + UART_MCR);
} else {
info->MCR &= ~UART_MCR_DTR;
outb(info->MCR, info->ioaddr + UART_MCR);
return 0;
}
cval = inb(info->ioaddr + UART_LCR);
outb(cval | UART_LCR_DLAB, info->ioaddr + UART_LCR); /* set DLAB */
outb(quot & 0xff, info->ioaddr + UART_DLL); /* LS of divisor */
outb(quot >> 8, info->ioaddr + UART_DLM); /* MS of divisor */
outb(cval, info->ioaddr + UART_LCR); /* reset DLAB */
#ifdef BOTHER
if (C_BAUD(tty) == BOTHER) {
quot = info->baud_base % newspd;
quot *= 8;
if (quot % newspd > newspd / 2) {
quot /= newspd;
quot++;
} else
quot /= newspd;
mxser_set_must_enum_value(info->ioaddr, quot);
} else
#endif
mxser_set_must_enum_value(info->ioaddr, 0);
return 0;
}
/*
* This routine is called to set the UART divisor registers to match
* the specified baud rate for a serial port.
*/
static void mxser_change_speed(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
unsigned cflag, cval, fcr;
unsigned char status;
cflag = tty->termios.c_cflag;
if (!info->ioaddr)
return;
if (mxser_set_baud_method[tty->index] == 0)
mxser_set_baud(tty, tty_get_baud_rate(tty));
/* byte size and parity */
switch (cflag & CSIZE) {
case CS5:
cval = 0x00;
break;
case CS6:
cval = 0x01;
break;
case CS7:
cval = 0x02;
break;
case CS8:
cval = 0x03;
break;
default:
cval = 0x00;
break; /* too keep GCC shut... */
}
if (cflag & CSTOPB)
cval |= 0x04;
if (cflag & PARENB)
cval |= UART_LCR_PARITY;
if (!(cflag & PARODD))
cval |= UART_LCR_EPAR;
if (cflag & CMSPAR)
cval |= UART_LCR_SPAR;
if ((info->type == PORT_8250) || (info->type == PORT_16450)) {
if (info->board->must_hwid) {
fcr = UART_FCR_ENABLE_FIFO;
fcr |= MOXA_MUST_FCR_GDA_MODE_ENABLE;
mxser_set_must_fifo_value(info);
} else
fcr = 0;
} else {
fcr = UART_FCR_ENABLE_FIFO;
if (info->board->must_hwid) {
fcr |= MOXA_MUST_FCR_GDA_MODE_ENABLE;
mxser_set_must_fifo_value(info);
} else {
switch (info->rx_high_water) {
case 1:
fcr |= UART_FCR_TRIGGER_1;
break;
case 4:
fcr |= UART_FCR_TRIGGER_4;
break;
case 8:
fcr |= UART_FCR_TRIGGER_8;
break;
default:
fcr |= UART_FCR_TRIGGER_14;
break;
}
}
}
/* CTS flow control flag and modem status interrupts */
info->IER &= ~UART_IER_MSI;
info->MCR &= ~UART_MCR_AFE;
tty_port_set_cts_flow(&info->port, cflag & CRTSCTS);
if (cflag & CRTSCTS) {
info->IER |= UART_IER_MSI;
if ((info->type == PORT_16550A) || (info->board->must_hwid)) {
info->MCR |= UART_MCR_AFE;
} else {
status = inb(info->ioaddr + UART_MSR);
if (tty->hw_stopped) {
if (status & UART_MSR_CTS) {
tty->hw_stopped = 0;
if (info->type != PORT_16550A &&
!info->board->must_hwid) {
outb(info->IER & ~UART_IER_THRI,
info->ioaddr +
UART_IER);
info->IER |= UART_IER_THRI;
outb(info->IER, info->ioaddr +
UART_IER);
}
tty_wakeup(tty);
}
} else {
if (!(status & UART_MSR_CTS)) {
tty->hw_stopped = 1;
if ((info->type != PORT_16550A) &&
(!info->board->must_hwid)) {
info->IER &= ~UART_IER_THRI;
outb(info->IER, info->ioaddr +
UART_IER);
}
}
}
}
}
outb(info->MCR, info->ioaddr + UART_MCR);
tty_port_set_check_carrier(&info->port, ~cflag & CLOCAL);
if (~cflag & CLOCAL)
info->IER |= UART_IER_MSI;
outb(info->IER, info->ioaddr + UART_IER);
/*
* Set up parity check flag
*/
info->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
if (I_INPCK(tty))
info->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
if (I_BRKINT(tty) || I_PARMRK(tty))
info->read_status_mask |= UART_LSR_BI;
info->ignore_status_mask = 0;
if (I_IGNBRK(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(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 (info->board->must_hwid) {
mxser_set_must_xon1_value(info->ioaddr, START_CHAR(tty));
mxser_set_must_xoff1_value(info->ioaddr, STOP_CHAR(tty));
if (I_IXON(tty)) {
mxser_enable_must_rx_software_flow_control(
info->ioaddr);
} else {
mxser_disable_must_rx_software_flow_control(
info->ioaddr);
}
if (I_IXOFF(tty)) {
mxser_enable_must_tx_software_flow_control(
info->ioaddr);
} else {
mxser_disable_must_tx_software_flow_control(
info->ioaddr);
}
}
outb(fcr, info->ioaddr + UART_FCR); /* set fcr */
outb(cval, info->ioaddr + UART_LCR);
}
static void mxser_check_modem_status(struct tty_struct *tty,
struct mxser_port *port, int status)
{
/* update input line counters */
if (status & UART_MSR_TERI)
port->icount.rng++;
if (status & UART_MSR_DDSR)
port->icount.dsr++;
if (status & UART_MSR_DDCD)
port->icount.dcd++;
if (status & UART_MSR_DCTS)
port->icount.cts++;
port->mon_data.modem_status = status;
wake_up_interruptible(&port->port.delta_msr_wait);
if (tty_port_check_carrier(&port->port) && (status & UART_MSR_DDCD)) {
if (status & UART_MSR_DCD)
wake_up_interruptible(&port->port.open_wait);
}
if (tty_port_cts_enabled(&port->port)) {
if (tty->hw_stopped) {
if (status & UART_MSR_CTS) {
tty->hw_stopped = 0;
if ((port->type != PORT_16550A) &&
(!port->board->must_hwid)) {
outb(port->IER & ~UART_IER_THRI,
port->ioaddr + UART_IER);
port->IER |= UART_IER_THRI;
outb(port->IER, port->ioaddr +
UART_IER);
}
tty_wakeup(tty);
}
} else {
if (!(status & UART_MSR_CTS)) {
tty->hw_stopped = 1;
if (port->type != PORT_16550A &&
!port->board->must_hwid) {
port->IER &= ~UART_IER_THRI;
outb(port->IER, port->ioaddr +
UART_IER);
}
}
}
}
}
static int mxser_activate(struct tty_port *port, struct tty_struct *tty)
{
struct mxser_port *info = container_of(port, struct mxser_port, port);
unsigned long page;
unsigned long flags;
page = __get_free_page(GFP_KERNEL);
if (!page)
return -ENOMEM;
spin_lock_irqsave(&info->slock, flags);
if (!info->ioaddr || !info->type) {
set_bit(TTY_IO_ERROR, &tty->flags);
free_page(page);
spin_unlock_irqrestore(&info->slock, flags);
return 0;
}
info->port.xmit_buf = (unsigned char *) page;
/*
* Clear the FIFO buffers and disable them
* (they will be reenabled in mxser_change_speed())
*/
if (info->board->must_hwid)
outb((UART_FCR_CLEAR_RCVR |
UART_FCR_CLEAR_XMIT |
MOXA_MUST_FCR_GDA_MODE_ENABLE), info->ioaddr + UART_FCR);
else
outb((UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT),
info->ioaddr + UART_FCR);
/*
* At this point there's no way the LSR could still be 0xFF;
* if it is, then bail out, because there's likely no UART
* here.
*/
if (inb(info->ioaddr + UART_LSR) == 0xff) {
spin_unlock_irqrestore(&info->slock, flags);
if (capable(CAP_SYS_ADMIN)) {
set_bit(TTY_IO_ERROR, &tty->flags);
return 0;
} else
return -ENODEV;
}
/*
* Clear the interrupt registers.
*/
(void) inb(info->ioaddr + UART_LSR);
(void) inb(info->ioaddr + UART_RX);
(void) inb(info->ioaddr + UART_IIR);
(void) inb(info->ioaddr + UART_MSR);
/*
* Now, initialize the UART
*/
outb(UART_LCR_WLEN8, info->ioaddr + UART_LCR); /* reset DLAB */
info->MCR = UART_MCR_DTR | UART_MCR_RTS;
outb(info->MCR, info->ioaddr + UART_MCR);
/*
* Finally, enable interrupts
*/
info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI;
if (info->board->must_hwid)
info->IER |= MOXA_MUST_IER_EGDAI;
outb(info->IER, info->ioaddr + UART_IER); /* enable interrupts */
/*
* And clear the interrupt registers again for luck.
*/
(void) inb(info->ioaddr + UART_LSR);
(void) inb(info->ioaddr + UART_RX);
(void) inb(info->ioaddr + UART_IIR);
(void) inb(info->ioaddr + UART_MSR);
clear_bit(TTY_IO_ERROR, &tty->flags);
info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
/*
* and set the speed of the serial port
*/
mxser_change_speed(tty);
spin_unlock_irqrestore(&info->slock, flags);
return 0;
}
/*
* This routine will shutdown a serial port
*/
static void mxser_shutdown_port(struct tty_port *port)
{
struct mxser_port *info = container_of(port, struct mxser_port, port);
unsigned long flags;
spin_lock_irqsave(&info->slock, 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->port.delta_msr_wait);
/*
* Free the xmit buffer, if necessary
*/
if (info->port.xmit_buf) {
free_page((unsigned long) info->port.xmit_buf);
info->port.xmit_buf = NULL;
}
info->IER = 0;
outb(0x00, info->ioaddr + UART_IER);
/* clear Rx/Tx FIFO's */
if (info->board->must_hwid)
outb(UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT |
MOXA_MUST_FCR_GDA_MODE_ENABLE,
info->ioaddr + UART_FCR);
else
outb(UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT,
info->ioaddr + UART_FCR);
/* read data port to reset things */
(void) inb(info->ioaddr + UART_RX);
if (info->board->must_hwid)
SET_MOXA_MUST_NO_SOFTWARE_FLOW_CONTROL(info->ioaddr);
spin_unlock_irqrestore(&info->slock, flags);
}
/*
* 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 mxser_open(struct tty_struct *tty, struct file *filp)
{
struct mxser_port *info;
int line;
line = tty->index;
if (line == MXSER_PORTS)
return 0;
info = &mxser_boards[line / MXSER_PORTS_PER_BOARD].ports[line % MXSER_PORTS_PER_BOARD];
if (!info->ioaddr)
return -ENODEV;
tty->driver_data = info;
return tty_port_open(&info->port, tty, filp);
}
static void mxser_flush_buffer(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
char fcr;
unsigned long flags;
spin_lock_irqsave(&info->slock, flags);
info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
fcr = inb(info->ioaddr + UART_FCR);
outb((fcr | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT),
info->ioaddr + UART_FCR);
outb(fcr, info->ioaddr + UART_FCR);
spin_unlock_irqrestore(&info->slock, flags);
tty_wakeup(tty);
}
static void mxser_close_port(struct tty_port *port)
{
struct mxser_port *info = container_of(port, struct mxser_port, port);
unsigned long timeout;
/*
* 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;
if (info->board->must_hwid)
info->IER &= ~MOXA_MUST_RECV_ISR;
outb(info->IER, info->ioaddr + UART_IER);
/*
* Before we drop DTR, make sure the UART transmitter
* has completely drained; this is especially
* important if there is a transmit FIFO!
*/
timeout = jiffies + HZ;
while (!(inb(info->ioaddr + UART_LSR) & UART_LSR_TEMT)) {
schedule_timeout_interruptible(5);
if (time_after(jiffies, timeout))
break;
}
}
/*
* 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 mxser_close(struct tty_struct *tty, struct file *filp)
{
struct mxser_port *info = tty->driver_data;
struct tty_port *port = &info->port;
if (tty->index == MXSER_PORTS || info == NULL)
return;
if (tty_port_close_start(port, tty, filp) == 0)
return;
info->closing = 1;
mutex_lock(&port->mutex);
mxser_close_port(port);
mxser_flush_buffer(tty);
if (tty_port_initialized(port) && C_HUPCL(tty))
tty_port_lower_dtr_rts(port);
mxser_shutdown_port(port);
tty_port_set_initialized(port, 0);
mutex_unlock(&port->mutex);
info->closing = 0;
/* Right now the tty_port set is done outside of the close_end helper
as we don't yet have everyone using refcounts */
tty_port_close_end(port, tty);
tty_port_tty_set(port, NULL);
}
static int mxser_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
int c, total = 0;
struct mxser_port *info = tty->driver_data;
unsigned long flags;
if (!info->port.xmit_buf)
return 0;
while (1) {
c = min_t(int, count, min(SERIAL_XMIT_SIZE - info->xmit_cnt - 1,
SERIAL_XMIT_SIZE - info->xmit_head));
if (c <= 0)
break;
memcpy(info->port.xmit_buf + info->xmit_head, buf, c);
spin_lock_irqsave(&info->slock, flags);
info->xmit_head = (info->xmit_head + c) &
(SERIAL_XMIT_SIZE - 1);
info->xmit_cnt += c;
spin_unlock_irqrestore(&info->slock, flags);
buf += c;
count -= c;
total += c;
}
tty: cumulate and document tty_struct::flow* members Group the flow flags under a single struct called flow. The new struct contains 'stopped' and 'tco_stopped' bools which used to be bits in a bitfield. The struct also contains the lock protecting them to potentially share the same cache line. Note that commit c545b66c6922b (tty: Serialize tcflow() with other tty flow control changes) added a padding to the original bitfield. It was for the bitfield to occupy a whole 64b word to avoid interferring stores on Alpha (cannot we evaporate this arch with weird implications to C code yet?). But it doesn't work as expected as the padding (tty_struct::unused) is aligned to a 8B boundary too and occupies some bytes from the next word. So make it reliable by: 1) setting __aligned of the struct -- that aligns the start, and 2) making 'unsigned long unused[0]' as the last member of the struct -- pads the end. This is also the perfect time to start the documentation of tty_struct where all this lives. So we start by documenting what these bools actually serve for. And why we do all the alignment dances. Only the few up-to-date information from the Theodore's comment made it into this new Kerneldoc comment. Signed-off-by: Jiri Slaby <jslaby@suse.cz> Cc: "David S. Miller" <davem@davemloft.net> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Ulf Hansson <ulf.hansson@linaro.org> Cc: Heiko Carstens <hca@linux.ibm.com> Cc: Vasily Gorbik <gor@linux.ibm.com> Cc: Christian Borntraeger <borntraeger@de.ibm.com> Cc: Shawn Guo <shawnguo@kernel.org> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Vineet Gupta <vgupta@synopsys.com> Cc: "Maciej W. Rozycki" <macro@orcam.me.uk> Link: https://lore.kernel.org/r/20210505091928.22010-13-jslaby@suse.cz Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2021-05-05 09:19:05 +00:00
if (info->xmit_cnt && !tty->flow.stopped) {
if (!tty->hw_stopped ||
(info->type == PORT_16550A) ||
(info->board->must_hwid)) {
spin_lock_irqsave(&info->slock, flags);
outb(info->IER & ~UART_IER_THRI, info->ioaddr +
UART_IER);
info->IER |= UART_IER_THRI;
outb(info->IER, info->ioaddr + UART_IER);
spin_unlock_irqrestore(&info->slock, flags);
}
}
return total;
}
static int mxser_put_char(struct tty_struct *tty, unsigned char ch)
{
struct mxser_port *info = tty->driver_data;
unsigned long flags;
if (!info->port.xmit_buf)
return 0;
if (info->xmit_cnt >= SERIAL_XMIT_SIZE - 1)
return 0;
spin_lock_irqsave(&info->slock, flags);
info->port.xmit_buf[info->xmit_head++] = ch;
info->xmit_head &= SERIAL_XMIT_SIZE - 1;
info->xmit_cnt++;
spin_unlock_irqrestore(&info->slock, flags);
tty: cumulate and document tty_struct::flow* members Group the flow flags under a single struct called flow. The new struct contains 'stopped' and 'tco_stopped' bools which used to be bits in a bitfield. The struct also contains the lock protecting them to potentially share the same cache line. Note that commit c545b66c6922b (tty: Serialize tcflow() with other tty flow control changes) added a padding to the original bitfield. It was for the bitfield to occupy a whole 64b word to avoid interferring stores on Alpha (cannot we evaporate this arch with weird implications to C code yet?). But it doesn't work as expected as the padding (tty_struct::unused) is aligned to a 8B boundary too and occupies some bytes from the next word. So make it reliable by: 1) setting __aligned of the struct -- that aligns the start, and 2) making 'unsigned long unused[0]' as the last member of the struct -- pads the end. This is also the perfect time to start the documentation of tty_struct where all this lives. So we start by documenting what these bools actually serve for. And why we do all the alignment dances. Only the few up-to-date information from the Theodore's comment made it into this new Kerneldoc comment. Signed-off-by: Jiri Slaby <jslaby@suse.cz> Cc: "David S. Miller" <davem@davemloft.net> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Ulf Hansson <ulf.hansson@linaro.org> Cc: Heiko Carstens <hca@linux.ibm.com> Cc: Vasily Gorbik <gor@linux.ibm.com> Cc: Christian Borntraeger <borntraeger@de.ibm.com> Cc: Shawn Guo <shawnguo@kernel.org> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Vineet Gupta <vgupta@synopsys.com> Cc: "Maciej W. Rozycki" <macro@orcam.me.uk> Link: https://lore.kernel.org/r/20210505091928.22010-13-jslaby@suse.cz Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2021-05-05 09:19:05 +00:00
if (!tty->flow.stopped) {
if (!tty->hw_stopped ||
(info->type == PORT_16550A) ||
info->board->must_hwid) {
spin_lock_irqsave(&info->slock, flags);
outb(info->IER & ~UART_IER_THRI, info->ioaddr + UART_IER);
info->IER |= UART_IER_THRI;
outb(info->IER, info->ioaddr + UART_IER);
spin_unlock_irqrestore(&info->slock, flags);
}
}
return 1;
}
static void mxser_flush_chars(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
unsigned long flags;
tty: cumulate and document tty_struct::flow* members Group the flow flags under a single struct called flow. The new struct contains 'stopped' and 'tco_stopped' bools which used to be bits in a bitfield. The struct also contains the lock protecting them to potentially share the same cache line. Note that commit c545b66c6922b (tty: Serialize tcflow() with other tty flow control changes) added a padding to the original bitfield. It was for the bitfield to occupy a whole 64b word to avoid interferring stores on Alpha (cannot we evaporate this arch with weird implications to C code yet?). But it doesn't work as expected as the padding (tty_struct::unused) is aligned to a 8B boundary too and occupies some bytes from the next word. So make it reliable by: 1) setting __aligned of the struct -- that aligns the start, and 2) making 'unsigned long unused[0]' as the last member of the struct -- pads the end. This is also the perfect time to start the documentation of tty_struct where all this lives. So we start by documenting what these bools actually serve for. And why we do all the alignment dances. Only the few up-to-date information from the Theodore's comment made it into this new Kerneldoc comment. Signed-off-by: Jiri Slaby <jslaby@suse.cz> Cc: "David S. Miller" <davem@davemloft.net> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Ulf Hansson <ulf.hansson@linaro.org> Cc: Heiko Carstens <hca@linux.ibm.com> Cc: Vasily Gorbik <gor@linux.ibm.com> Cc: Christian Borntraeger <borntraeger@de.ibm.com> Cc: Shawn Guo <shawnguo@kernel.org> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Vineet Gupta <vgupta@synopsys.com> Cc: "Maciej W. Rozycki" <macro@orcam.me.uk> Link: https://lore.kernel.org/r/20210505091928.22010-13-jslaby@suse.cz Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2021-05-05 09:19:05 +00:00
if (info->xmit_cnt <= 0 || tty->flow.stopped || !info->port.xmit_buf ||
(tty->hw_stopped && info->type != PORT_16550A &&
!info->board->must_hwid))
return;
spin_lock_irqsave(&info->slock, flags);
outb(info->IER & ~UART_IER_THRI, info->ioaddr + UART_IER);
info->IER |= UART_IER_THRI;
outb(info->IER, info->ioaddr + UART_IER);
spin_unlock_irqrestore(&info->slock, flags);
}
tty: make tty_operations::write_room return uint Line disciplines expect a positive value or zero returned from tty->ops->write_room (invoked by tty_write_room). So make this assumption explicit by using unsigned int as a return value. Both of tty->ops->write_room and tty_write_room. Signed-off-by: Jiri Slaby <jslaby@suse.cz> Acked-by: Laurentiu Tudor <laurentiu.tudor@nxp.com> Acked-by: Alex Elder <elder@linaro.org> Acked-by: Max Filippov <jcmvbkbc@gmail.com> # xtensa Acked-by: David Sterba <dsterba@suse.com> Acked-By: Anton Ivanov <anton.ivanov@cambridgegreys.com> Acked-by: Geert Uytterhoeven <geert@linux-m68k.org> Cc: Richard Henderson <rth@twiddle.net> Cc: Ivan Kokshaysky <ink@jurassic.park.msu.ru> Cc: Matt Turner <mattst88@gmail.com> Cc: "James E.J. Bottomley" <James.Bottomley@HansenPartnership.com> Cc: Helge Deller <deller@gmx.de> Cc: Jeff Dike <jdike@addtoit.com> Cc: Richard Weinberger <richard@nod.at> Cc: Chris Zankel <chris@zankel.net> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Samuel Iglesias Gonsalvez <siglesias@igalia.com> Cc: Jens Taprogge <jens.taprogge@taprogge.org> Cc: Karsten Keil <isdn@linux-pingi.de> Cc: Scott Branden <scott.branden@broadcom.com> Cc: Ulf Hansson <ulf.hansson@linaro.org> Cc: "David S. Miller" <davem@davemloft.net> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Heiko Carstens <hca@linux.ibm.com> Cc: Vasily Gorbik <gor@linux.ibm.com> Cc: Christian Borntraeger <borntraeger@de.ibm.com> Cc: David Lin <dtwlin@gmail.com> Cc: Johan Hovold <johan@kernel.org> Cc: Jiri Kosina <jikos@kernel.org> Cc: Shawn Guo <shawnguo@kernel.org> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Oliver Neukum <oneukum@suse.com> Cc: Felipe Balbi <balbi@kernel.org> Cc: Mathias Nyman <mathias.nyman@intel.com> Cc: Marcel Holtmann <marcel@holtmann.org> Cc: Johan Hedberg <johan.hedberg@gmail.com> Cc: Luiz Augusto von Dentz <luiz.dentz@gmail.com> Link: https://lore.kernel.org/r/20210505091928.22010-23-jslaby@suse.cz Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2021-05-05 09:19:15 +00:00
static unsigned int mxser_write_room(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
int ret;
ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1;
return ret < 0 ? 0 : ret;
}
static unsigned int mxser_chars_in_buffer(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
return info->xmit_cnt;
}
/*
* ------------------------------------------------------------
* friends of mxser_ioctl()
* ------------------------------------------------------------
*/
static int mxser_get_serial_info(struct tty_struct *tty,
struct serial_struct *ss)
{
struct mxser_port *info = tty->driver_data;
struct tty_port *port = &info->port;
unsigned int closing_wait, close_delay;
if (tty->index == MXSER_PORTS)
return -ENOTTY;
mutex_lock(&port->mutex);
close_delay = jiffies_to_msecs(info->port.close_delay) / 10;
closing_wait = info->port.closing_wait;
if (closing_wait != ASYNC_CLOSING_WAIT_NONE)
closing_wait = jiffies_to_msecs(closing_wait) / 10;
ss->type = info->type,
ss->line = tty->index,
ss->port = info->ioaddr,
ss->irq = info->board->irq,
ss->flags = info->port.flags,
ss->baud_base = info->baud_base,
ss->close_delay = close_delay;
ss->closing_wait = closing_wait;
ss->custom_divisor = info->custom_divisor,
mutex_unlock(&port->mutex);
return 0;
}
static int mxser_set_serial_info(struct tty_struct *tty,
struct serial_struct *ss)
{
struct mxser_port *info = tty->driver_data;
struct tty_port *port = &info->port;
speed_t baud;
unsigned long sl_flags;
unsigned int flags, close_delay, closing_wait;
int retval = 0;
if (tty->index == MXSER_PORTS)
return -ENOTTY;
if (tty_io_error(tty))
return -EIO;
mutex_lock(&port->mutex);
if (!info->ioaddr) {
mutex_unlock(&port->mutex);
return -ENODEV;
}
if (ss->irq != info->board->irq ||
ss->port != info->ioaddr) {
mutex_unlock(&port->mutex);
return -EINVAL;
}
flags = port->flags & ASYNC_SPD_MASK;
close_delay = msecs_to_jiffies(ss->close_delay * 10);
closing_wait = ss->closing_wait;
if (closing_wait != ASYNC_CLOSING_WAIT_NONE)
closing_wait = msecs_to_jiffies(closing_wait * 10);
if (!capable(CAP_SYS_ADMIN)) {
if ((ss->baud_base != info->baud_base) ||
(close_delay != info->port.close_delay) ||
(closing_wait != info->port.closing_wait) ||
((ss->flags & ~ASYNC_USR_MASK) != (info->port.flags & ~ASYNC_USR_MASK))) {
mutex_unlock(&port->mutex);
return -EPERM;
}
info->port.flags = ((info->port.flags & ~ASYNC_USR_MASK) |
(ss->flags & ASYNC_USR_MASK));
} else {
/*
* OK, past this point, all the error checking has been done.
* At this point, we start making changes.....
*/
port->flags = ((port->flags & ~ASYNC_FLAGS) |
(ss->flags & ASYNC_FLAGS));
port->close_delay = close_delay;
port->closing_wait = closing_wait;
if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST &&
(ss->baud_base != info->baud_base ||
ss->custom_divisor !=
info->custom_divisor)) {
if (ss->custom_divisor == 0) {
mutex_unlock(&port->mutex);
return -EINVAL;
}
baud = ss->baud_base / ss->custom_divisor;
tty_encode_baud_rate(tty, baud, baud);
}
info->type = ss->type;
process_txrx_fifo(info);
}
if (tty_port_initialized(port)) {
if (flags != (port->flags & ASYNC_SPD_MASK)) {
spin_lock_irqsave(&info->slock, sl_flags);
mxser_change_speed(tty);
spin_unlock_irqrestore(&info->slock, sl_flags);
}
} else {
retval = mxser_activate(port, tty);
if (retval == 0)
tty_port_set_initialized(port, 1);
}
mutex_unlock(&port->mutex);
return retval;
}
/*
* mxser_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 mxser_get_lsr_info(struct mxser_port *info,
unsigned int __user *value)
{
unsigned char status;
unsigned int result;
unsigned long flags;
spin_lock_irqsave(&info->slock, flags);
status = inb(info->ioaddr + UART_LSR);
spin_unlock_irqrestore(&info->slock, flags);
result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0);
return put_user(result, value);
}
static int mxser_tiocmget(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
unsigned char control, status;
unsigned long flags;
if (tty->index == MXSER_PORTS)
return -ENOIOCTLCMD;
if (tty_io_error(tty))
return -EIO;
control = info->MCR;
spin_lock_irqsave(&info->slock, flags);
status = inb(info->ioaddr + UART_MSR);
if (status & UART_MSR_ANY_DELTA)
mxser_check_modem_status(tty, info, status);
spin_unlock_irqrestore(&info->slock, 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 mxser_tiocmset(struct tty_struct *tty,
unsigned int set, unsigned int clear)
{
struct mxser_port *info = tty->driver_data;
unsigned long flags;
if (tty->index == MXSER_PORTS)
return -ENOIOCTLCMD;
if (tty_io_error(tty))
return -EIO;
spin_lock_irqsave(&info->slock, 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;
outb(info->MCR, info->ioaddr + UART_MCR);
spin_unlock_irqrestore(&info->slock, flags);
return 0;
}
static int mxser_ioctl_special(unsigned int cmd, void __user *argp)
{
struct mxser_port *ip;
struct tty_port *port;
struct tty_struct *tty;
int result, status;
unsigned int i, j;
int ret = 0;
switch (cmd) {
case MOXA_GET_MAJOR:
printk_ratelimited(KERN_WARNING "mxser: '%s' uses deprecated ioctl "
"%x (GET_MAJOR), fix your userspace\n",
current->comm, cmd);
return put_user(ttymajor, (int __user *)argp);
case MOXA_CHKPORTENABLE:
result = 0;
for (i = 0; i < MXSER_BOARDS; i++)
for (j = 0; j < MXSER_PORTS_PER_BOARD; j++)
if (mxser_boards[i].ports[j].ioaddr)
result |= (1 << i);
return put_user(result, (unsigned long __user *)argp);
case MOXA_GETDATACOUNT:
/* The receive side is locked by port->slock but it isn't
clear that an exact snapshot is worth copying here */
if (copy_to_user(argp, &mxvar_log, sizeof(mxvar_log)))
ret = -EFAULT;
return ret;
case MOXA_GETMSTATUS: {
struct mxser_mstatus ms, __user *msu = argp;
for (i = 0; i < MXSER_BOARDS; i++)
for (j = 0; j < MXSER_PORTS_PER_BOARD; j++) {
ip = &mxser_boards[i].ports[j];
port = &ip->port;
memset(&ms, 0, sizeof(ms));
mutex_lock(&port->mutex);
if (!ip->ioaddr)
goto copy;
tty = tty_port_tty_get(port);
if (!tty)
ms.cflag = ip->normal_termios.c_cflag;
else
ms.cflag = tty->termios.c_cflag;
tty_kref_put(tty);
spin_lock_irq(&ip->slock);
status = inb(ip->ioaddr + UART_MSR);
spin_unlock_irq(&ip->slock);
if (status & UART_MSR_DCD)
ms.dcd = 1;
if (status & UART_MSR_DSR)
ms.dsr = 1;
if (status & UART_MSR_CTS)
ms.cts = 1;
copy:
mutex_unlock(&port->mutex);
if (copy_to_user(msu, &ms, sizeof(ms)))
return -EFAULT;
msu++;
}
return 0;
}
case MOXA_ASPP_MON_EXT: {
struct mxser_mon_ext *me; /* it's 2k, stack unfriendly */
unsigned int cflag, iflag, p;
u8 opmode;
me = kzalloc(sizeof(*me), GFP_KERNEL);
if (!me)
return -ENOMEM;
for (i = 0, p = 0; i < MXSER_BOARDS; i++) {
for (j = 0; j < MXSER_PORTS_PER_BOARD; j++, p++) {
if (p >= ARRAY_SIZE(me->rx_cnt)) {
i = MXSER_BOARDS;
break;
}
ip = &mxser_boards[i].ports[j];
port = &ip->port;
mutex_lock(&port->mutex);
if (!ip->ioaddr) {
mutex_unlock(&port->mutex);
continue;
}
spin_lock_irq(&ip->slock);
status = mxser_get_msr(ip->ioaddr, 0, p);
if (status & UART_MSR_TERI)
ip->icount.rng++;
if (status & UART_MSR_DDSR)
ip->icount.dsr++;
if (status & UART_MSR_DDCD)
ip->icount.dcd++;
if (status & UART_MSR_DCTS)
ip->icount.cts++;
ip->mon_data.modem_status = status;
me->rx_cnt[p] = ip->mon_data.rxcnt;
me->tx_cnt[p] = ip->mon_data.txcnt;
me->up_rxcnt[p] = ip->mon_data.up_rxcnt;
me->up_txcnt[p] = ip->mon_data.up_txcnt;
me->modem_status[p] =
ip->mon_data.modem_status;
spin_unlock_irq(&ip->slock);
tty = tty_port_tty_get(&ip->port);
if (!tty) {
cflag = ip->normal_termios.c_cflag;
iflag = ip->normal_termios.c_iflag;
me->baudrate[p] = tty_termios_baud_rate(&ip->normal_termios);
} else {
cflag = tty->termios.c_cflag;
iflag = tty->termios.c_iflag;
me->baudrate[p] = tty_get_baud_rate(tty);
}
tty_kref_put(tty);
me->databits[p] = cflag & CSIZE;
me->stopbits[p] = cflag & CSTOPB;
me->parity[p] = cflag & (PARENB | PARODD |
CMSPAR);
if (cflag & CRTSCTS)
me->flowctrl[p] |= 0x03;
if (iflag & (IXON | IXOFF))
me->flowctrl[p] |= 0x0C;
if (ip->type == PORT_16550A)
me->fifo[p] = 1;
if (ip->board->must_hwid == MOXA_MUST_MU860_HWID) {
opmode = inb(ip->opmode_ioaddr)>>((p % 4) * 2);
opmode &= OP_MODE_MASK;
} else {
opmode = RS232_MODE;
}
me->iftype[p] = opmode;
mutex_unlock(&port->mutex);
}
}
if (copy_to_user(argp, me, sizeof(*me)))
ret = -EFAULT;
kfree(me);
return ret;
}
default:
return -ENOIOCTLCMD;
}
return 0;
}
static int mxser_cflags_changed(struct mxser_port *info, unsigned long arg,
struct async_icount *cprev)
{
struct async_icount cnow;
unsigned long flags;
int ret;
spin_lock_irqsave(&info->slock, flags);
cnow = info->icount; /* atomic copy */
spin_unlock_irqrestore(&info->slock, flags);
ret = ((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));
*cprev = cnow;
return ret;
}
static int mxser_ioctl(struct tty_struct *tty,
unsigned int cmd, unsigned long arg)
{
struct mxser_port *info = tty->driver_data;
struct async_icount cnow;
unsigned long flags;
void __user *argp = (void __user *)arg;
if (tty->index == MXSER_PORTS)
return mxser_ioctl_special(cmd, argp);
if (cmd == MOXA_SET_OP_MODE || cmd == MOXA_GET_OP_MODE) {
int p;
unsigned long opmode;
static unsigned char ModeMask[] = { 0xfc, 0xf3, 0xcf, 0x3f };
int shiftbit;
unsigned char val, mask;
if (info->board->must_hwid != MOXA_MUST_MU860_HWID)
return -EFAULT;
p = tty->index % 4;
if (cmd == MOXA_SET_OP_MODE) {
if (get_user(opmode, (int __user *) argp))
return -EFAULT;
if (opmode != RS232_MODE &&
opmode != RS485_2WIRE_MODE &&
opmode != RS422_MODE &&
opmode != RS485_4WIRE_MODE)
return -EFAULT;
mask = ModeMask[p];
shiftbit = p * 2;
spin_lock_irq(&info->slock);
val = inb(info->opmode_ioaddr);
val &= mask;
val |= (opmode << shiftbit);
outb(val, info->opmode_ioaddr);
spin_unlock_irq(&info->slock);
} else {
shiftbit = p * 2;
spin_lock_irq(&info->slock);
opmode = inb(info->opmode_ioaddr) >> shiftbit;
spin_unlock_irq(&info->slock);
opmode &= OP_MODE_MASK;
if (put_user(opmode, (int __user *)argp))
return -EFAULT;
}
return 0;
}
if (cmd != TIOCMIWAIT && tty_io_error(tty))
return -EIO;
switch (cmd) {
case TIOCSERGETLSR: /* Get line status register */
return mxser_get_lsr_info(info, argp);
/*
* 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->slock, flags);
cnow = info->icount; /* note the counters on entry */
spin_unlock_irqrestore(&info->slock, flags);
return wait_event_interruptible(info->port.delta_msr_wait,
mxser_cflags_changed(info, arg, &cnow));
case MOXA_HighSpeedOn:
return put_user(info->baud_base != 115200 ? 1 : 0, (int __user *)argp);
case MOXA_SDS_RSTICOUNTER:
spin_lock_irq(&info->slock);
info->mon_data.rxcnt = 0;
info->mon_data.txcnt = 0;
spin_unlock_irq(&info->slock);
return 0;
case MOXA_ASPP_OQUEUE:{
int len, lsr;
len = mxser_chars_in_buffer(tty);
spin_lock_irq(&info->slock);
lsr = inb(info->ioaddr + UART_LSR) & UART_LSR_THRE;
spin_unlock_irq(&info->slock);
len += (lsr ? 0 : 1);
return put_user(len, (int __user *)argp);
}
case MOXA_ASPP_MON: {
int mcr, status;
spin_lock_irq(&info->slock);
status = mxser_get_msr(info->ioaddr, 1, tty->index);
mxser_check_modem_status(tty, info, status);
mcr = inb(info->ioaddr + UART_MCR);
spin_unlock_irq(&info->slock);
if (mcr & MOXA_MUST_MCR_XON_FLAG)
info->mon_data.hold_reason &= ~NPPI_NOTIFY_XOFFHOLD;
else
info->mon_data.hold_reason |= NPPI_NOTIFY_XOFFHOLD;
if (mcr & MOXA_MUST_MCR_TX_XON)
info->mon_data.hold_reason &= ~NPPI_NOTIFY_XOFFXENT;
else
info->mon_data.hold_reason |= NPPI_NOTIFY_XOFFXENT;
if (tty->hw_stopped)
info->mon_data.hold_reason |= NPPI_NOTIFY_CTSHOLD;
else
info->mon_data.hold_reason &= ~NPPI_NOTIFY_CTSHOLD;
if (copy_to_user(argp, &info->mon_data,
sizeof(struct mxser_mon)))
return -EFAULT;
return 0;
}
case MOXA_ASPP_LSTATUS: {
if (put_user(info->err_shadow, (unsigned char __user *)argp))
return -EFAULT;
info->err_shadow = 0;
return 0;
}
case MOXA_SET_BAUD_METHOD: {
int method;
if (get_user(method, (int __user *)argp))
return -EFAULT;
mxser_set_baud_method[tty->index] = method;
return put_user(method, (int __user *)argp);
}
default:
return -ENOIOCTLCMD;
}
return 0;
}
/*
* 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.
*/
static int mxser_get_icount(struct tty_struct *tty,
struct serial_icounter_struct *icount)
{
struct mxser_port *info = tty->driver_data;
struct async_icount cnow;
unsigned long flags;
spin_lock_irqsave(&info->slock, flags);
cnow = info->icount;
spin_unlock_irqrestore(&info->slock, flags);
icount->frame = cnow.frame;
icount->brk = cnow.brk;
icount->overrun = cnow.overrun;
icount->buf_overrun = cnow.buf_overrun;
icount->parity = cnow.parity;
icount->rx = cnow.rx;
icount->tx = cnow.tx;
icount->cts = cnow.cts;
icount->dsr = cnow.dsr;
icount->rng = cnow.rng;
icount->dcd = cnow.dcd;
return 0;
}
static void mxser_stoprx(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
info->ldisc_stop_rx = 1;
if (I_IXOFF(tty)) {
if (info->board->must_hwid) {
info->IER &= ~MOXA_MUST_RECV_ISR;
outb(info->IER, info->ioaddr + UART_IER);
} else {
info->x_char = STOP_CHAR(tty);
outb(0, info->ioaddr + UART_IER);
info->IER |= UART_IER_THRI;
outb(info->IER, info->ioaddr + UART_IER);
}
}
if (C_CRTSCTS(tty)) {
info->MCR &= ~UART_MCR_RTS;
outb(info->MCR, info->ioaddr + UART_MCR);
}
}
/*
* This routine is called by the upper-layer tty layer to signal that
* incoming characters should be throttled.
*/
static void mxser_throttle(struct tty_struct *tty)
{
mxser_stoprx(tty);
}
static void mxser_unthrottle(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
/* startrx */
info->ldisc_stop_rx = 0;
if (I_IXOFF(tty)) {
if (info->x_char)
info->x_char = 0;
else {
if (info->board->must_hwid) {
info->IER |= MOXA_MUST_RECV_ISR;
outb(info->IER, info->ioaddr + UART_IER);
} else {
info->x_char = START_CHAR(tty);
outb(0, info->ioaddr + UART_IER);
info->IER |= UART_IER_THRI;
outb(info->IER, info->ioaddr + UART_IER);
}
}
}
if (C_CRTSCTS(tty)) {
info->MCR |= UART_MCR_RTS;
outb(info->MCR, info->ioaddr + UART_MCR);
}
}
/*
* mxser_stop() and mxser_start()
*
tty: cumulate and document tty_struct::flow* members Group the flow flags under a single struct called flow. The new struct contains 'stopped' and 'tco_stopped' bools which used to be bits in a bitfield. The struct also contains the lock protecting them to potentially share the same cache line. Note that commit c545b66c6922b (tty: Serialize tcflow() with other tty flow control changes) added a padding to the original bitfield. It was for the bitfield to occupy a whole 64b word to avoid interferring stores on Alpha (cannot we evaporate this arch with weird implications to C code yet?). But it doesn't work as expected as the padding (tty_struct::unused) is aligned to a 8B boundary too and occupies some bytes from the next word. So make it reliable by: 1) setting __aligned of the struct -- that aligns the start, and 2) making 'unsigned long unused[0]' as the last member of the struct -- pads the end. This is also the perfect time to start the documentation of tty_struct where all this lives. So we start by documenting what these bools actually serve for. And why we do all the alignment dances. Only the few up-to-date information from the Theodore's comment made it into this new Kerneldoc comment. Signed-off-by: Jiri Slaby <jslaby@suse.cz> Cc: "David S. Miller" <davem@davemloft.net> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Ulf Hansson <ulf.hansson@linaro.org> Cc: Heiko Carstens <hca@linux.ibm.com> Cc: Vasily Gorbik <gor@linux.ibm.com> Cc: Christian Borntraeger <borntraeger@de.ibm.com> Cc: Shawn Guo <shawnguo@kernel.org> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Vineet Gupta <vgupta@synopsys.com> Cc: "Maciej W. Rozycki" <macro@orcam.me.uk> Link: https://lore.kernel.org/r/20210505091928.22010-13-jslaby@suse.cz Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2021-05-05 09:19:05 +00:00
* This routines are called before setting or resetting tty->flow.stopped.
* They enable or disable transmitter interrupts, as necessary.
*/
static void mxser_stop(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
unsigned long flags;
spin_lock_irqsave(&info->slock, flags);
if (info->IER & UART_IER_THRI) {
info->IER &= ~UART_IER_THRI;
outb(info->IER, info->ioaddr + UART_IER);
}
spin_unlock_irqrestore(&info->slock, flags);
}
static void mxser_start(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
unsigned long flags;
spin_lock_irqsave(&info->slock, flags);
if (info->xmit_cnt && info->port.xmit_buf) {
outb(info->IER & ~UART_IER_THRI, info->ioaddr + UART_IER);
info->IER |= UART_IER_THRI;
outb(info->IER, info->ioaddr + UART_IER);
}
spin_unlock_irqrestore(&info->slock, flags);
}
static void mxser_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
{
struct mxser_port *info = tty->driver_data;
unsigned long flags;
spin_lock_irqsave(&info->slock, flags);
mxser_change_speed(tty);
spin_unlock_irqrestore(&info->slock, flags);
if ((old_termios->c_cflag & CRTSCTS) && !C_CRTSCTS(tty)) {
tty->hw_stopped = 0;
mxser_start(tty);
}
/* Handle sw stopped */
if ((old_termios->c_iflag & IXON) && !I_IXON(tty)) {
tty: cumulate and document tty_struct::flow* members Group the flow flags under a single struct called flow. The new struct contains 'stopped' and 'tco_stopped' bools which used to be bits in a bitfield. The struct also contains the lock protecting them to potentially share the same cache line. Note that commit c545b66c6922b (tty: Serialize tcflow() with other tty flow control changes) added a padding to the original bitfield. It was for the bitfield to occupy a whole 64b word to avoid interferring stores on Alpha (cannot we evaporate this arch with weird implications to C code yet?). But it doesn't work as expected as the padding (tty_struct::unused) is aligned to a 8B boundary too and occupies some bytes from the next word. So make it reliable by: 1) setting __aligned of the struct -- that aligns the start, and 2) making 'unsigned long unused[0]' as the last member of the struct -- pads the end. This is also the perfect time to start the documentation of tty_struct where all this lives. So we start by documenting what these bools actually serve for. And why we do all the alignment dances. Only the few up-to-date information from the Theodore's comment made it into this new Kerneldoc comment. Signed-off-by: Jiri Slaby <jslaby@suse.cz> Cc: "David S. Miller" <davem@davemloft.net> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Ulf Hansson <ulf.hansson@linaro.org> Cc: Heiko Carstens <hca@linux.ibm.com> Cc: Vasily Gorbik <gor@linux.ibm.com> Cc: Christian Borntraeger <borntraeger@de.ibm.com> Cc: Shawn Guo <shawnguo@kernel.org> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Vineet Gupta <vgupta@synopsys.com> Cc: "Maciej W. Rozycki" <macro@orcam.me.uk> Link: https://lore.kernel.org/r/20210505091928.22010-13-jslaby@suse.cz Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2021-05-05 09:19:05 +00:00
tty->flow.stopped = 0;
if (info->board->must_hwid) {
spin_lock_irqsave(&info->slock, flags);
mxser_disable_must_rx_software_flow_control(
info->ioaddr);
spin_unlock_irqrestore(&info->slock, flags);
}
mxser_start(tty);
}
}
/*
* mxser_wait_until_sent() --- wait until the transmitter is empty
*/
static void mxser_wait_until_sent(struct tty_struct *tty, int timeout)
{
struct mxser_port *info = tty->driver_data;
unsigned long orig_jiffies, char_time;
unsigned long flags;
int lsr;
if (info->type == PORT_UNKNOWN)
return;
if (info->xmit_fifo_size == 0)
return; /* Just in case.... */
orig_jiffies = jiffies;
/*
* Set the check interval to be 1/5 of the estimated time to
* send a single character, and make it at least 1. The check
* interval should also be less than the timeout.
*
* Note: we have to use pretty tight timings here to satisfy
* the NIST-PCTS.
*/
char_time = (info->timeout - HZ / 50) / info->xmit_fifo_size;
char_time = char_time / 5;
if (char_time == 0)
char_time = 1;
if (timeout && timeout < char_time)
char_time = timeout;
/*
* If the transmitter hasn't cleared in twice the approximate
* amount of time to send the entire FIFO, it probably won't
* ever clear. This assumes the UART isn't doing flow
* control, which is currently the case. Hence, if it ever
* takes longer than info->timeout, this is probably due to a
* UART bug of some kind. So, we clamp the timeout parameter at
* 2*info->timeout.
*/
if (!timeout || timeout > 2 * info->timeout)
timeout = 2 * info->timeout;
spin_lock_irqsave(&info->slock, flags);
while (!((lsr = inb(info->ioaddr + UART_LSR)) & UART_LSR_TEMT)) {
spin_unlock_irqrestore(&info->slock, flags);
schedule_timeout_interruptible(char_time);
spin_lock_irqsave(&info->slock, flags);
if (signal_pending(current))
break;
if (timeout && time_after(jiffies, orig_jiffies + timeout))
break;
}
spin_unlock_irqrestore(&info->slock, flags);
set_current_state(TASK_RUNNING);
}
/*
* This routine is called by tty_hangup() when a hangup is signaled.
*/
static void mxser_hangup(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
mxser_flush_buffer(tty);
tty_port_hangup(&info->port);
}
/*
* mxser_rs_break() --- routine which turns the break handling on or off
*/
static int mxser_rs_break(struct tty_struct *tty, int break_state)
{
struct mxser_port *info = tty->driver_data;
unsigned long flags;
spin_lock_irqsave(&info->slock, flags);
if (break_state == -1)
outb(inb(info->ioaddr + UART_LCR) | UART_LCR_SBC,
info->ioaddr + UART_LCR);
else
outb(inb(info->ioaddr + UART_LCR) & ~UART_LCR_SBC,
info->ioaddr + UART_LCR);
spin_unlock_irqrestore(&info->slock, flags);
return 0;
}
static bool mxser_receive_chars_new(struct tty_struct *tty,
struct mxser_port *port, u8 status, int *cnt)
{
enum mxser_must_hwid hwid = port->board->must_hwid;
u8 gdl;
if (hwid == MOXA_OTHER_UART)
return false;
if (status & UART_LSR_SPECIAL)
return false;
if (hwid == MOXA_MUST_MU860_HWID && (status & MOXA_MUST_LSR_RERR))
return false;
if (status & MOXA_MUST_LSR_RERR)
return false;
gdl = inb(port->ioaddr + MOXA_MUST_GDL_REGISTER);
if (hwid == MOXA_MUST_MU150_HWID)
gdl &= MOXA_MUST_GDL_MASK;
if (gdl >= tty->receive_room && !port->ldisc_stop_rx)
mxser_stoprx(tty);
while (gdl--) {
u8 ch = inb(port->ioaddr + UART_RX);
tty_insert_flip_char(&port->port, ch, 0);
(*cnt)++;
}
return true;
}
static u8 mxser_receive_chars_old(struct tty_struct *tty,
struct mxser_port *port, u8 status, int *cnt)
{
enum mxser_must_hwid hwid = port->board->must_hwid;
int recv_room = tty->receive_room;
int ignored = 0;
int max = 256;
u8 ch;
do {
if (max-- < 0)
break;
ch = inb(port->ioaddr + UART_RX);
if (hwid && (status & UART_LSR_OE))
outb(0x23, port->ioaddr + UART_FCR);
status &= port->read_status_mask;
if (status & port->ignore_status_mask) {
if (++ignored > 100)
break;
} else {
char flag = 0;
if (status & UART_LSR_SPECIAL) {
if (status & UART_LSR_BI) {
flag = TTY_BREAK;
port->icount.brk++;
if (port->port.flags & ASYNC_SAK)
do_SAK(tty);
} else if (status & UART_LSR_PE) {
flag = TTY_PARITY;
port->icount.parity++;
} else if (status & UART_LSR_FE) {
flag = TTY_FRAME;
port->icount.frame++;
} else if (status & UART_LSR_OE) {
flag = TTY_OVERRUN;
port->icount.overrun++;
}
}
tty_insert_flip_char(&port->port, ch, flag);
(*cnt)++;
if (*cnt >= recv_room) {
if (!port->ldisc_stop_rx)
mxser_stoprx(tty);
break;
}
}
if (hwid)
break;
status = inb(port->ioaddr + UART_LSR);
} while (status & UART_LSR_DR);
return status;
}
static u8 mxser_receive_chars(struct tty_struct *tty,
struct mxser_port *port, u8 status)
{
int cnt = 0;
if (tty->receive_room == 0 && !port->ldisc_stop_rx)
mxser_stoprx(tty);
if (!mxser_receive_chars_new(tty, port, status, &cnt))
status = mxser_receive_chars_old(tty, port, status, &cnt);
mxvar_log.rxcnt[tty->index] += cnt;
port->mon_data.rxcnt += cnt;
port->mon_data.up_rxcnt += cnt;
tty_flip_buffer_push(&port->port);
return status;
}
static void mxser_transmit_chars(struct tty_struct *tty, struct mxser_port *port)
{
int count, cnt;
if (port->x_char) {
outb(port->x_char, port->ioaddr + UART_TX);
port->x_char = 0;
mxvar_log.txcnt[tty->index]++;
port->mon_data.txcnt++;
port->mon_data.up_txcnt++;
port->icount.tx++;
return;
}
if (port->port.xmit_buf == NULL)
return;
tty: cumulate and document tty_struct::flow* members Group the flow flags under a single struct called flow. The new struct contains 'stopped' and 'tco_stopped' bools which used to be bits in a bitfield. The struct also contains the lock protecting them to potentially share the same cache line. Note that commit c545b66c6922b (tty: Serialize tcflow() with other tty flow control changes) added a padding to the original bitfield. It was for the bitfield to occupy a whole 64b word to avoid interferring stores on Alpha (cannot we evaporate this arch with weird implications to C code yet?). But it doesn't work as expected as the padding (tty_struct::unused) is aligned to a 8B boundary too and occupies some bytes from the next word. So make it reliable by: 1) setting __aligned of the struct -- that aligns the start, and 2) making 'unsigned long unused[0]' as the last member of the struct -- pads the end. This is also the perfect time to start the documentation of tty_struct where all this lives. So we start by documenting what these bools actually serve for. And why we do all the alignment dances. Only the few up-to-date information from the Theodore's comment made it into this new Kerneldoc comment. Signed-off-by: Jiri Slaby <jslaby@suse.cz> Cc: "David S. Miller" <davem@davemloft.net> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Ulf Hansson <ulf.hansson@linaro.org> Cc: Heiko Carstens <hca@linux.ibm.com> Cc: Vasily Gorbik <gor@linux.ibm.com> Cc: Christian Borntraeger <borntraeger@de.ibm.com> Cc: Shawn Guo <shawnguo@kernel.org> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Vineet Gupta <vgupta@synopsys.com> Cc: "Maciej W. Rozycki" <macro@orcam.me.uk> Link: https://lore.kernel.org/r/20210505091928.22010-13-jslaby@suse.cz Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2021-05-05 09:19:05 +00:00
if (port->xmit_cnt <= 0 || tty->flow.stopped ||
(tty->hw_stopped &&
(port->type != PORT_16550A) &&
(!port->board->must_hwid))) {
port->IER &= ~UART_IER_THRI;
outb(port->IER, port->ioaddr + UART_IER);
return;
}
cnt = port->xmit_cnt;
count = port->xmit_fifo_size;
do {
outb(port->port.xmit_buf[port->xmit_tail++],
port->ioaddr + UART_TX);
port->xmit_tail = port->xmit_tail & (SERIAL_XMIT_SIZE - 1);
if (--port->xmit_cnt <= 0)
break;
} while (--count > 0);
mxvar_log.txcnt[tty->index] += (cnt - port->xmit_cnt);
port->mon_data.txcnt += (cnt - port->xmit_cnt);
port->mon_data.up_txcnt += (cnt - port->xmit_cnt);
port->icount.tx += (cnt - port->xmit_cnt);
if (port->xmit_cnt < WAKEUP_CHARS)
tty_wakeup(tty);
if (port->xmit_cnt <= 0) {
port->IER &= ~UART_IER_THRI;
outb(port->IER, port->ioaddr + UART_IER);
}
}
static bool mxser_port_isr(struct mxser_port *port)
{
struct tty_struct *tty;
u8 iir, msr, status;
bool error = false;
iir = inb(port->ioaddr + UART_IIR);
if (iir & UART_IIR_NO_INT)
return true;
iir &= MOXA_MUST_IIR_MASK;
tty = tty_port_tty_get(&port->port);
if (!tty || port->closing || !tty_port_initialized(&port->port)) {
status = inb(port->ioaddr + UART_LSR);
outb(0x27, port->ioaddr + UART_FCR);
inb(port->ioaddr + UART_MSR);
error = true;
goto put_tty;
}
status = inb(port->ioaddr + UART_LSR);
if (status & UART_LSR_PE)
port->err_shadow |= NPPI_NOTIFY_PARITY;
if (status & UART_LSR_FE)
port->err_shadow |= NPPI_NOTIFY_FRAMING;
if (status & UART_LSR_OE)
port->err_shadow |= NPPI_NOTIFY_HW_OVERRUN;
if (status & UART_LSR_BI)
port->err_shadow |= NPPI_NOTIFY_BREAK;
if (port->board->must_hwid) {
if (iir == MOXA_MUST_IIR_GDA ||
iir == MOXA_MUST_IIR_RDA ||
iir == MOXA_MUST_IIR_RTO ||
iir == MOXA_MUST_IIR_LSR)
status = mxser_receive_chars(tty, port, status);
} else {
status &= port->read_status_mask;
if (status & UART_LSR_DR)
status = mxser_receive_chars(tty, port, status);
}
msr = inb(port->ioaddr + UART_MSR);
if (msr & UART_MSR_ANY_DELTA)
mxser_check_modem_status(tty, port, msr);
if (port->board->must_hwid) {
if (iir == 0x02 && (status & UART_LSR_THRE))
mxser_transmit_chars(tty, port);
} else {
if (status & UART_LSR_THRE)
mxser_transmit_chars(tty, port);
}
put_tty:
tty_kref_put(tty);
return error;
}
/*
* This is the serial driver's generic interrupt routine
*/
static irqreturn_t mxser_interrupt(int irq, void *dev_id)
{
struct mxser_board *brd = dev_id;
struct mxser_port *port;
unsigned int int_cnt, pass_counter = 0;
unsigned int i, max = brd->info->nports;
int handled = IRQ_NONE;
u8 irqbits, bits, mask = BIT(max) - 1;
while (pass_counter++ < MXSER_ISR_PASS_LIMIT) {
irqbits = inb(brd->vector) & mask;
if (irqbits == mask)
break;
handled = IRQ_HANDLED;
for (i = 0, bits = 1; i < max; i++, irqbits |= bits, bits <<= 1) {
if (irqbits == mask)
break;
if (bits & irqbits)
continue;
port = &brd->ports[i];
int_cnt = 0;
spin_lock(&port->slock);
do {
if (mxser_port_isr(port))
break;
} while (int_cnt++ < MXSER_ISR_PASS_LIMIT);
spin_unlock(&port->slock);
}
}
return handled;
}
static const struct tty_operations mxser_ops = {
.open = mxser_open,
.close = mxser_close,
.write = mxser_write,
.put_char = mxser_put_char,
.flush_chars = mxser_flush_chars,
.write_room = mxser_write_room,
.chars_in_buffer = mxser_chars_in_buffer,
.flush_buffer = mxser_flush_buffer,
.ioctl = mxser_ioctl,
.throttle = mxser_throttle,
.unthrottle = mxser_unthrottle,
.set_termios = mxser_set_termios,
.stop = mxser_stop,
.start = mxser_start,
.hangup = mxser_hangup,
.break_ctl = mxser_rs_break,
.wait_until_sent = mxser_wait_until_sent,
.tiocmget = mxser_tiocmget,
.tiocmset = mxser_tiocmset,
.set_serial = mxser_set_serial_info,
.get_serial = mxser_get_serial_info,
.get_icount = mxser_get_icount,
};
static const struct tty_port_operations mxser_port_ops = {
.carrier_raised = mxser_carrier_raised,
.dtr_rts = mxser_dtr_rts,
.activate = mxser_activate,
.shutdown = mxser_shutdown_port,
};
/*
* The MOXA Smartio/Industio serial driver boot-time initialization code!
*/
static int mxser_initbrd(struct mxser_board *brd)
{
struct mxser_port *info;
unsigned int i;
int retval;
for (i = 0; i < brd->info->nports; i++) {
info = &brd->ports[i];
tty_port_init(&info->port);
info->port.ops = &mxser_port_ops;
info->board = brd;
info->stop_rx = 0;
info->ldisc_stop_rx = 0;
/* Enhance mode enabled here */
if (brd->must_hwid != MOXA_OTHER_UART)
mxser_enable_must_enchance_mode(info->ioaddr);
info->type = PORT_16550A;
process_txrx_fifo(info);
info->custom_divisor = info->baud_base * 16;
info->port.close_delay = 5 * HZ / 10;
info->port.closing_wait = 30 * HZ;
info->normal_termios = mxvar_sdriver->init_termios;
memset(&info->mon_data, 0, sizeof(struct mxser_mon));
info->err_shadow = 0;
spin_lock_init(&info->slock);
/* before set INT ISR, disable all int */
outb(inb(info->ioaddr + UART_IER) & 0xf0,
info->ioaddr + UART_IER);
}
retval = request_irq(brd->irq, mxser_interrupt, IRQF_SHARED, "mxser",
brd);
if (retval) {
for (i = 0; i < brd->info->nports; i++)
tty_port_destroy(&brd->ports[i].port);
printk(KERN_ERR "Board %s: Request irq failed, IRQ (%d) may "
"conflict with another device.\n",
brd->info->name, brd->irq);
}
return retval;
}
static void mxser_board_remove(struct mxser_board *brd)
{
unsigned int i;
for (i = 0; i < brd->info->nports; i++) {
tty_unregister_device(mxvar_sdriver, brd->idx + i);
tty_port_destroy(&brd->ports[i].port);
}
free_irq(brd->irq, brd);
}
static int mxser_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
struct mxser_board *brd;
unsigned int i, j;
unsigned long ioaddress;
struct device *tty_dev;
int retval = -EINVAL;
for (i = 0; i < MXSER_BOARDS; i++)
if (mxser_boards[i].info == NULL)
break;
if (i >= MXSER_BOARDS) {
dev_err(&pdev->dev, "too many boards found (maximum %d), board "
"not configured\n", MXSER_BOARDS);
goto err;
}
brd = &mxser_boards[i];
brd->idx = i * MXSER_PORTS_PER_BOARD;
dev_info(&pdev->dev, "found MOXA %s board (BusNo=%d, DevNo=%d)\n",
mxser_cards[ent->driver_data].name,
pdev->bus->number, PCI_SLOT(pdev->devfn));
retval = pci_enable_device(pdev);
if (retval) {
dev_err(&pdev->dev, "PCI enable failed\n");
goto err;
}
/* io address */
ioaddress = pci_resource_start(pdev, 2);
retval = pci_request_region(pdev, 2, "mxser(IO)");
if (retval)
goto err_dis;
brd->info = &mxser_cards[ent->driver_data];
for (i = 0; i < brd->info->nports; i++)
brd->ports[i].ioaddr = ioaddress + 8 * i;
/* vector */
ioaddress = pci_resource_start(pdev, 3);
retval = pci_request_region(pdev, 3, "mxser(vector)");
if (retval)
goto err_zero;
brd->vector = ioaddress;
/* irq */
brd->irq = pdev->irq;
brd->must_hwid = mxser_must_get_hwid(brd->ports[0].ioaddr);
for (j = 0; j < UART_INFO_NUM; j++) {
if (Gpci_uart_info[j].type == brd->must_hwid) {
brd->max_baud = Gpci_uart_info[j].max_baud;
/* exception....CP-102 */
if (brd->info->flags & MXSER_HIGHBAUD)
brd->max_baud = 921600;
break;
}
}
if (brd->must_hwid == MOXA_MUST_MU860_HWID) {
for (i = 0; i < brd->info->nports; i++) {
if (i < 4)
brd->ports[i].opmode_ioaddr = ioaddress + 4;
else
brd->ports[i].opmode_ioaddr = ioaddress + 0x0c;
}
outb(0, ioaddress + 4); /* default set to RS232 mode */
outb(0, ioaddress + 0x0c); /* default set to RS232 mode */
}
for (i = 0; i < brd->info->nports; i++) {
brd->ports[i].baud_base = 921600;
}
/* mxser_initbrd will hook ISR. */
retval = mxser_initbrd(brd);
if (retval)
goto err_rel3;
for (i = 0; i < brd->info->nports; i++) {
tty_dev = tty_port_register_device(&brd->ports[i].port,
mxvar_sdriver, brd->idx + i, &pdev->dev);
if (IS_ERR(tty_dev)) {
retval = PTR_ERR(tty_dev);
for (; i > 0; i--)
tty_unregister_device(mxvar_sdriver,
brd->idx + i - 1);
goto err_relbrd;
}
}
pci_set_drvdata(pdev, brd);
return 0;
err_relbrd:
for (i = 0; i < brd->info->nports; i++)
tty_port_destroy(&brd->ports[i].port);
free_irq(brd->irq, brd);
err_rel3:
pci_release_region(pdev, 3);
err_zero:
brd->info = NULL;
pci_release_region(pdev, 2);
err_dis:
pci_disable_device(pdev);
err:
return retval;
}
static void mxser_remove(struct pci_dev *pdev)
{
struct mxser_board *brd = pci_get_drvdata(pdev);
mxser_board_remove(brd);
pci_release_region(pdev, 2);
pci_release_region(pdev, 3);
pci_disable_device(pdev);
brd->info = NULL;
}
static struct pci_driver mxser_driver = {
.name = "mxser",
.id_table = mxser_pcibrds,
.probe = mxser_probe,
.remove = mxser_remove
};
static int __init mxser_module_init(void)
{
int retval;
mxvar_sdriver = alloc_tty_driver(MXSER_PORTS + 1);
if (!mxvar_sdriver)
return -ENOMEM;
/* Initialize the tty_driver structure */
mxvar_sdriver->name = "ttyMI";
mxvar_sdriver->major = ttymajor;
mxvar_sdriver->minor_start = 0;
mxvar_sdriver->type = TTY_DRIVER_TYPE_SERIAL;
mxvar_sdriver->subtype = SERIAL_TYPE_NORMAL;
mxvar_sdriver->init_termios = tty_std_termios;
mxvar_sdriver->init_termios.c_cflag = B9600|CS8|CREAD|HUPCL|CLOCAL;
mxvar_sdriver->flags = TTY_DRIVER_REAL_RAW|TTY_DRIVER_DYNAMIC_DEV;
tty_set_operations(mxvar_sdriver, &mxser_ops);
retval = tty_register_driver(mxvar_sdriver);
if (retval) {
printk(KERN_ERR "Couldn't install MOXA Smartio/Industio family "
"tty driver !\n");
goto err_put;
}
retval = pci_register_driver(&mxser_driver);
if (retval) {
printk(KERN_ERR "mxser: can't register pci driver\n");
goto err_unr;
}
return 0;
err_unr:
tty_unregister_driver(mxvar_sdriver);
err_put:
put_tty_driver(mxvar_sdriver);
return retval;
}
static void __exit mxser_module_exit(void)
{
pci_unregister_driver(&mxser_driver);
tty_unregister_driver(mxvar_sdriver);
put_tty_driver(mxvar_sdriver);
}
module_init(mxser_module_init);
module_exit(mxser_module_exit);