From e3224111b3a527eb8f9b9b6deed83b727522941e Mon Sep 17 00:00:00 2001 From: Bill Pemberton Date: Thu, 25 Oct 2012 12:46:21 -0400 Subject: staging: dgrp: remove use of real_raw and read_cnt in dgrp_input dgrp_input used real_raw and read_cnt from struct tty_struct. Those members have gone away. Rework the code to not use them. Reported-by: Fengguang Wu Signed-off-by: Bill Pemberton Signed-off-by: Greg Kroah-Hartman --- drivers/staging/dgrp/dgrp_net_ops.c | 67 +++++-------------------------------- 1 file changed, 8 insertions(+), 59 deletions(-) (limited to 'drivers/staging') diff --git a/drivers/staging/dgrp/dgrp_net_ops.c b/drivers/staging/dgrp/dgrp_net_ops.c index ab839ea3b44c..0788357fd3ca 100644 --- a/drivers/staging/dgrp/dgrp_net_ops.c +++ b/drivers/staging/dgrp/dgrp_net_ops.c @@ -151,20 +151,15 @@ static void dgrp_read_data_block(struct ch_struct *ch, u8 *flipbuf, * Copys the rbuf to the flipbuf and sends to line discipline. * Sends input buffer data to the line discipline. * - * There are several modes to consider here: - * rawreadok, tty->real_raw, and IF_PARMRK */ static void dgrp_input(struct ch_struct *ch) { struct nd_struct *nd; struct tty_struct *tty; - int remain; int data_len; int len; - int flip_len; int tty_count; ulong lock_flags; - struct tty_ldisc *ld; u8 *myflipbuf; u8 *myflipflagbuf; @@ -212,37 +207,11 @@ static void dgrp_input(struct ch_struct *ch) spin_unlock_irqrestore(&nd->nd_lock, lock_flags); - /* Decide how much data we can send into the tty layer */ - if (dgrp_rawreadok && tty->real_raw) - flip_len = MYFLIPLEN; - else - flip_len = TTY_FLIPBUF_SIZE; - /* data_len should be the number of chars that we read in */ data_len = (ch->ch_rin - ch->ch_rout) & RBUF_MASK; - remain = data_len; /* len is the amount of data we are going to transfer here */ - len = min(data_len, flip_len); - - /* take into consideration length of ldisc */ - len = min(len, (N_TTY_BUF_SIZE - 1) - tty->read_cnt); - - ld = tty_ldisc_ref(tty); - - /* - * If we were unable to get a reference to the ld, - * don't flush our buffer, and act like the ld doesn't - * have any space to put the data right now. - */ - if (!ld) { - len = 0; - } else if (!ld->ops->receive_buf) { - spin_lock_irqsave(&nd->nd_lock, lock_flags); - ch->ch_rout = ch->ch_rin; - spin_unlock_irqrestore(&nd->nd_lock, lock_flags); - len = 0; - } + len = tty_buffer_request_room(tty, data_len); /* Check DPA flow control */ if ((nd->nd_dpa_debug) && @@ -254,42 +223,22 @@ static void dgrp_input(struct ch_struct *ch) dgrp_read_data_block(ch, myflipbuf, len); - /* - * In high performance mode, we don't have to update - * flag_buf or any of the counts or pointers into flip buf. - */ - if (!dgrp_rawreadok || !tty->real_raw) { - if (I_PARMRK(tty) || I_BRKINT(tty) || I_INPCK(tty)) - parity_scan(ch, myflipbuf, myflipflagbuf, &len); - else - memset(myflipflagbuf, TTY_NORMAL, len); - } + if (I_PARMRK(tty) || I_BRKINT(tty) || I_INPCK(tty)) + parity_scan(ch, myflipbuf, myflipflagbuf, &len); + else + memset(myflipflagbuf, TTY_NORMAL, len); if ((nd->nd_dpa_debug) && (nd->nd_dpa_port == PORT_NUM(MINOR(tty_devnum(tty))))) dgrp_dpa_data(nd, 1, myflipbuf, len); - /* - * If we're doing raw reads, jam it right into the - * line disc bypassing the flip buffers. - */ - if (dgrp_rawreadok && tty->real_raw) - ld->ops->receive_buf(tty, myflipbuf, NULL, len); - else { - len = tty_buffer_request_room(tty, len); - tty_insert_flip_string_flags(tty, myflipbuf, - myflipflagbuf, len); - - /* Tell the tty layer its okay to "eat" the data now */ - tty_flip_buffer_push(tty); - } + tty_insert_flip_string_flags(tty, myflipbuf, + myflipflagbuf, len); + tty_flip_buffer_push(tty); ch->ch_rxcount += len; } - if (ld) - tty_ldisc_deref(ld); - /* * Wake up any sleepers (maybe dgrp close) that might be waiting * for a channel flag state change. -- cgit v1.2.3 From 57cf50acbf5b153f331a966ecc08836324c1cd8d Mon Sep 17 00:00:00 2001 From: Bill Pemberton Date: Thu, 25 Oct 2012 12:46:22 -0400 Subject: staging: dgrp: remove rawreadok module option The functionality behind this option has been removed in the driver so remove the config option to set/unset it. Signed-off-by: Bill Pemberton Signed-off-by: Greg Kroah-Hartman --- drivers/staging/dgrp/dgrp_common.h | 1 - drivers/staging/dgrp/dgrp_driver.c | 4 ---- drivers/staging/dgrp/dgrp_specproc.c | 2 -- drivers/staging/dgrp/dgrp_sysfs.c | 18 ------------------ 4 files changed, 25 deletions(-) (limited to 'drivers/staging') diff --git a/drivers/staging/dgrp/dgrp_common.h b/drivers/staging/dgrp/dgrp_common.h index 05ff338471ac..0583fe9c7b03 100644 --- a/drivers/staging/dgrp/dgrp_common.h +++ b/drivers/staging/dgrp/dgrp_common.h @@ -31,7 +31,6 @@ * All global storage allocation. ************************************************************************/ -extern int dgrp_rawreadok; /* Allow raw writing of input */ extern int dgrp_register_cudevices; /* enable legacy cu devices */ extern int dgrp_register_prdevices; /* enable transparent print devices */ extern int dgrp_poll_tick; /* Poll interval - in ms */ diff --git a/drivers/staging/dgrp/dgrp_driver.c b/drivers/staging/dgrp/dgrp_driver.c index 6e4a0ebc0749..aa262588e9b9 100644 --- a/drivers/staging/dgrp/dgrp_driver.c +++ b/drivers/staging/dgrp/dgrp_driver.c @@ -39,14 +39,10 @@ MODULE_VERSION(DIGI_VERSION); struct list_head nd_struct_list; struct dgrp_poll_data dgrp_poll_data; -int dgrp_rawreadok = 1; /* Bypass flipbuf on input */ int dgrp_register_cudevices = 1;/* Turn on/off registering legacy cu devices */ int dgrp_register_prdevices = 1;/* Turn on/off registering transparent print */ int dgrp_poll_tick = 20; /* Poll interval - in ms */ -module_param_named(rawreadok, dgrp_rawreadok, int, 0644); -MODULE_PARM_DESC(rawreadok, "Bypass flip buffers on input"); - module_param_named(register_cudevices, dgrp_register_cudevices, int, 0644); MODULE_PARM_DESC(register_cudevices, "Turn on/off registering legacy cu devices"); diff --git a/drivers/staging/dgrp/dgrp_specproc.c b/drivers/staging/dgrp/dgrp_specproc.c index 24327c3bad83..db91f676508a 100644 --- a/drivers/staging/dgrp/dgrp_specproc.c +++ b/drivers/staging/dgrp/dgrp_specproc.c @@ -629,8 +629,6 @@ static int info_proc_show(struct seq_file *m, void *v) { seq_printf(m, "version: %s\n", DIGI_VERSION); seq_puts(m, "register_with_sysfs: 1\n"); - seq_printf(m, "rawreadok: 0x%08x\t(%d)\n", - dgrp_rawreadok, dgrp_rawreadok); seq_printf(m, "pollrate: 0x%08x\t(%d)\n", dgrp_poll_tick, dgrp_poll_tick); diff --git a/drivers/staging/dgrp/dgrp_sysfs.c b/drivers/staging/dgrp/dgrp_sysfs.c index e5a3c88d016e..8b513e9111c1 100644 --- a/drivers/staging/dgrp/dgrp_sysfs.c +++ b/drivers/staging/dgrp/dgrp_sysfs.c @@ -55,23 +55,6 @@ static DEVICE_ATTR(register_with_sysfs, 0400, dgrp_class_register_with_sysfs_show, NULL); -static ssize_t dgrp_class_rawreadok_show(struct device *c, - struct device_attribute *attr, - char *buf) -{ - return snprintf(buf, PAGE_SIZE, "%d\n", dgrp_rawreadok); -} -static ssize_t dgrp_class_rawreadok_store(struct device *c, - struct device_attribute *attr, - const char *buf, size_t count) -{ - sscanf(buf, "0x%x\n", &dgrp_rawreadok); - return count; -} -static DEVICE_ATTR(rawreadok, 0600, dgrp_class_rawreadok_show, - dgrp_class_rawreadok_store); - - static ssize_t dgrp_class_pollrate_show(struct device *c, struct device_attribute *attr, char *buf) @@ -91,7 +74,6 @@ static DEVICE_ATTR(pollrate, 0600, dgrp_class_pollrate_show, static struct attribute *dgrp_sysfs_global_settings_entries[] = { &dev_attr_pollrate.attr, - &dev_attr_rawreadok.attr, &dev_attr_register_with_sysfs.attr, NULL }; -- cgit v1.2.3 From 191c5f10275cfbb36802edadbdb10c73537327b4 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 15 Nov 2012 09:49:56 +0100 Subject: TTY: call tty_port_destroy in the rest of drivers After commit "TTY: move tty buffers to tty_port", the tty buffers are not freed in some drivers. This is because tty_port_destructor is not called whenever a tty_port is freed. This was an assumption I counted with but was unfortunately untrue. So fix the drivers to fulfil this assumption. To be sure, the TTY buffers (and later some stuff) are gone along with the tty_port, we have to call tty_port_destroy at tear-down places. This is mostly where the structure containing a tty_port is freed. This patch does exactly that -- put tty_port_destroy at those places. Signed-off-by: Jiri Slaby Signed-off-by: Greg Kroah-Hartman --- drivers/staging/ccg/u_serial.c | 5 ++++- drivers/staging/dgrp/dgrp_specproc.c | 2 ++ drivers/staging/dgrp/dgrp_tty.c | 4 +++- drivers/staging/ipack/devices/ipoctal.c | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) (limited to 'drivers/staging') diff --git a/drivers/staging/ccg/u_serial.c b/drivers/staging/ccg/u_serial.c index 5b3f5fffea92..373c40656b52 100644 --- a/drivers/staging/ccg/u_serial.c +++ b/drivers/staging/ccg/u_serial.c @@ -1140,8 +1140,10 @@ int gserial_setup(struct usb_gadget *g, unsigned count) return status; fail: - while (count--) + while (count--) { + tty_port_destroy(&ports[count].port->port); kfree(ports[count].port); + } put_tty_driver(gs_tty_driver); gs_tty_driver = NULL; return status; @@ -1195,6 +1197,7 @@ void gserial_cleanup(void) WARN_ON(port->port_usb != NULL); + tty_port_destroy(&port->port); kfree(port); } n_ports = 0; diff --git a/drivers/staging/dgrp/dgrp_specproc.c b/drivers/staging/dgrp/dgrp_specproc.c index db91f676508a..c214078a89e9 100644 --- a/drivers/staging/dgrp/dgrp_specproc.c +++ b/drivers/staging/dgrp/dgrp_specproc.c @@ -752,6 +752,8 @@ static int dgrp_add_id(long id) return 0; + /* FIXME this guy should free the tty driver stored in nd and destroy + * all channel ports */ error_out: kfree(nd); return ret; diff --git a/drivers/staging/dgrp/dgrp_tty.c b/drivers/staging/dgrp/dgrp_tty.c index e125b03598d7..0db4c0514f63 100644 --- a/drivers/staging/dgrp/dgrp_tty.c +++ b/drivers/staging/dgrp/dgrp_tty.c @@ -3119,6 +3119,7 @@ static void dgrp_tty_hangup(struct tty_struct *tty) void dgrp_tty_uninit(struct nd_struct *nd) { + unsigned int i; char id[3]; ID_TO_CHAR(nd->nd_ID, id); @@ -3152,6 +3153,8 @@ dgrp_tty_uninit(struct nd_struct *nd) put_tty_driver(nd->nd_xprint_ttdriver); nd->nd_ttdriver_flags &= ~XPRINT_TTDRV_REG; } + for (i = 0; i < CHAN_MAX; i++) + tty_port_destroy(&nd->nd_chan[i].port); } @@ -3335,7 +3338,6 @@ dgrp_tty_init(struct nd_struct *nd) init_waitqueue_head(&(ch->ch_pun.un_open_wait)); init_waitqueue_head(&(ch->ch_pun.un_close_wait)); tty_port_init(&ch->port); - tty_port_init(&ch->port); } return 0; } diff --git a/drivers/staging/ipack/devices/ipoctal.c b/drivers/staging/ipack/devices/ipoctal.c index d751edfda839..729cb642840a 100644 --- a/drivers/staging/ipack/devices/ipoctal.c +++ b/drivers/staging/ipack/devices/ipoctal.c @@ -446,6 +446,7 @@ static int ipoctal_inst_slot(struct ipoctal *ipoctal, unsigned int bus_nr, tty_dev = tty_port_register_device(&channel->tty_port, tty, i, NULL); if (IS_ERR(tty_dev)) { dev_err(&ipoctal->dev->dev, "Failed to register tty device.\n"); + tty_port_destroy(&channel->tty_port); continue; } dev_set_drvdata(tty_dev, channel); @@ -741,6 +742,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal) struct ipoctal_channel *channel = &ipoctal->channel[i]; tty_unregister_device(ipoctal->tty_drv, i); tty_port_free_xmit_buf(&channel->tty_port); + tty_port_destroy(&channel->tty_port); } tty_unregister_driver(ipoctal->tty_drv); -- cgit v1.2.3 From 68a81291ff6650f3ff409ebfc58ef97dfe85a2e4 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Fri, 16 Nov 2012 09:28:49 -0500 Subject: staging: Add SystemBase Multi-2/PCI driver I ported the driver supplied by SystemBase to mainline. As the driver had MODULE_LICENSE("GPL") it is declared as a GPL module and thus I have the right to distribute it upstream. Note, I did the bare minimum to get it working. It still needs a lot of loving. Cc: hjchoi Signed-off-by: Steven Rostedt Signed-off-by: Greg Kroah-Hartman --- drivers/staging/Kconfig | 2 + drivers/staging/Makefile | 1 + drivers/staging/sb105x/Kconfig | 9 + drivers/staging/sb105x/Makefile | 3 + drivers/staging/sb105x/sb_mp_register.h | 295 +++ drivers/staging/sb105x/sb_pci_mp.c | 3195 +++++++++++++++++++++++++++++++ drivers/staging/sb105x/sb_pci_mp.h | 293 +++ drivers/staging/sb105x/sb_ser_core.h | 368 ++++ 8 files changed, 4166 insertions(+) create mode 100644 drivers/staging/sb105x/Kconfig create mode 100644 drivers/staging/sb105x/Makefile create mode 100644 drivers/staging/sb105x/sb_mp_register.h create mode 100644 drivers/staging/sb105x/sb_pci_mp.c create mode 100644 drivers/staging/sb105x/sb_pci_mp.h create mode 100644 drivers/staging/sb105x/sb_ser_core.h (limited to 'drivers/staging') diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index d805eef11915..f245fd3153d0 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -144,4 +144,6 @@ source "drivers/staging/imx-drm/Kconfig" source "drivers/staging/dgrp/Kconfig" +source "drivers/staging/sb105x/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 76e2ebd596ff..94cc3fa5ef81 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -64,3 +64,4 @@ obj-$(CONFIG_NET_VENDOR_SILICOM) += silicom/ obj-$(CONFIG_CED1401) += ced1401/ obj-$(CONFIG_DRM_IMX) += imx-drm/ obj-$(CONFIG_DGRP) += dgrp/ +obj-$(CONFIG_SB105X) += sb105x/ diff --git a/drivers/staging/sb105x/Kconfig b/drivers/staging/sb105x/Kconfig new file mode 100644 index 000000000000..ac87c5e38dee --- /dev/null +++ b/drivers/staging/sb105x/Kconfig @@ -0,0 +1,9 @@ +config SB105X + tristate "SystemBase PCI Multiport UART" + select SERIAL_CORE + depends on PCI + help + A driver for the SystemBase Multi-2/PCI serial card + + To compile this driver a module, choose M here: the module + will be called "sb105x". diff --git a/drivers/staging/sb105x/Makefile b/drivers/staging/sb105x/Makefile new file mode 100644 index 000000000000..b1bf3779acae --- /dev/null +++ b/drivers/staging/sb105x/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SB105X) += sb105x.o + +sb105x-y := sb_pci_mp.o diff --git a/drivers/staging/sb105x/sb_mp_register.h b/drivers/staging/sb105x/sb_mp_register.h new file mode 100644 index 000000000000..5480ae11368f --- /dev/null +++ b/drivers/staging/sb105x/sb_mp_register.h @@ -0,0 +1,295 @@ + +/* + * SB105X_UART.h + * + * Copyright (C) 2008 systembase + * + * UART registers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef UART_SB105X_H +#define UART_SB105X_H + +/* + * option register + */ + +/* Device Infomation Register */ +#define MP_OPTR_DIR0 0x04 /* port0 ~ port8 */ +#define MP_OPTR_DIR1 0x05 /* port8 ~ port15 */ +#define MP_OPTR_DIR2 0x06 /* port16 ~ port23 */ +#define MP_OPTR_DIR3 0x07 /* port24 ~ port31 */ + +#define DIR_UART_16C550 0 +#define DIR_UART_16C1050 1 +#define DIR_UART_16C1050A 2 + +#define DIR_CLK_1843200 0x0 /* input clock 1843200 Hz */ +#define DIR_CLK_3686400 0x1 /* input clock 3686400 Hz */ +#define DIR_CLK_7372800 0x2 /* input clock 7372800 Hz */ +#define DIR_CLK_14745600 0x3 /* input clock 14745600 Hz */ +#define DIR_CLK_29491200 0x4 /* input clock 29491200 Hz */ +#define DIR_CLK_58985400 0x5 /* input clock 58985400 Hz */ + +/* Interface Information Register */ +#define MP_OPTR_IIR0 0x08 /* port0 ~ port8 */ +#define MP_OPTR_IIR1 0x09 /* port8 ~ port15 */ +#define MP_OPTR_IIR2 0x0A /* port16 ~ port23 */ +#define MP_OPTR_IIR3 0x0B /* port24 ~ port31 */ + +#define IIR_RS232 0x00 /* RS232 type */ +#define IIR_RS422 0x10 /* RS422 type */ +#define IIR_RS485 0x20 /* RS485 type */ +#define IIR_UNKNOWN 0x30 /* unknown type */ + +/* Interrrupt Mask Register */ +#define MP_OPTR_IMR0 0x0C /* port0 ~ port8 */ +#define MP_OPTR_IMR1 0x0D /* port8 ~ port15 */ +#define MP_OPTR_IMR2 0x0E /* port16 ~ port23 */ +#define MP_OPTR_IMR3 0x0F /* port24 ~ port31 */ + +/* Interrupt Poll Register */ +#define MP_OPTR_IPR0 0x10 /* port0 ~ port8 */ +#define MP_OPTR_IPR1 0x11 /* port8 ~ port15 */ +#define MP_OPTR_IPR2 0x12 /* port16 ~ port23 */ +#define MP_OPTR_IPR3 0x13 /* port24 ~ port31 */ + +/* General Purpose Output Control Register */ +#define MP_OPTR_GPOCR 0x20 + +/* General Purpose Output Data Register */ +#define MP_OPTR_GPODR 0x21 + +/* Parallel Additional Function Register */ +#define MP_OPTR_PAFR 0x23 + +/* + * systembase 16c105x UART register + */ + +#define PAGE_0 0 +#define PAGE_1 1 +#define PAGE_2 2 +#define PAGE_3 3 +#define PAGE_4 4 + +/* + * ****************************************************************** + * * DLAB=0 =============== Page 0 Registers * + * ****************************************************************** + */ + +#define SB105X_RX 0 /* In: Receive buffer */ +#define SB105X_TX 0 /* Out: Transmit buffer */ + +#define SB105X_IER 1 /* Out: Interrupt Enable Register */ + +#define SB105X_IER_CTSI 0x80 /* CTS# Interrupt Enable (Requires EFR[4] = 1) */ +#define SB105X_IER_RTSI 0x40 /* RTS# Interrupt Enable (Requires EFR[4] = 1) */ +#define SB105X_IER_XOI 0x20 /* Xoff Interrupt Enable (Requires EFR[4] = 1) */ +#define SB105X_IER_SME 0x10 /* Sleep Mode Enable (Requires EFR[4] = 1) */ +#define SB105X_IER_MSI 0x08 /* Enable Modem status interrupt */ +#define SB105X_IER_RLSI 0x04 /* Enable receiver line status interrupt */ +#define SB105X_IER_THRI 0x02 /* Enable Transmitter holding register int. */ +#define SB105X_IER_RDI 0x01 /* Enable receiver data interrupt */ + +#define SB105X_ISR 2 /* In: Interrupt ID Register */ + +#define SB105X_ISR_NOINT 0x01 /* No interrupts pending */ +#define SB105X_ISR_RLSI 0x06 /* Receiver line status interrupt (Priority = 1)*/ +#define SB105X_ISR_RDAI 0x0c /* Receive Data Available interrupt */ +#define SB105X_ISR_CTII 0x04 /* Character Timeout Indication interrupt */ +#define SB105X_ISR_THRI 0x02 /* Transmitter holding register empty */ +#define SB105X_ISR_MSI 0x00 /* Modem status interrupt */ +#define SB105X_ISR_RXCI 0x10 /* Receive Xoff or Special Character interrupt */ +#define SB105X_ISR_RCSI 0x20 /* RTS#, CTS# status interrupt during Auto RTS/CTS flow control */ + +#define SB105X_FCR 2 /* Out: FIFO Control Register */ + +#define SB105X_FCR_FEN 0x01 /* FIFO Enable */ +#define SB105X_FCR_RXFR 0x02 /* RX FIFO Reset */ +#define SB105X_FCR_TXFR 0x04 /* TX FIFO Reset */ +#define SB105X_FCR_DMS 0x08 /* DMA Mode Select */ + +#define SB105X_FCR_RTR08 0x00 /* Receice Trigger Level set at 8 */ +#define SB105X_FCR_RTR16 0x40 /* Receice Trigger Level set at 16 */ +#define SB105X_FCR_RTR56 0x80 /* Receice Trigger Level set at 56 */ +#define SB105X_FCR_RTR60 0xc0 /* Receice Trigger Level set at 60 */ +#define SB105X_FCR_TTR08 0x00 /* Transmit Trigger Level set at 8 */ +#define SB105X_FCR_TTR16 0x10 /* Transmit Trigger Level set at 16 */ +#define SB105X_FCR_TTR32 0x20 /* Transmit Trigger Level set at 32 */ +#define SB105X_FCR_TTR56 0x30 /* Transmit Trigger Level set at 56 */ + +#define SB105X_LCR 3 /* Out: Line Control Register */ +/* + * * Note: if the word length is 5 bits (SB105X_LCR_WLEN5), then setting + * * SB105X_LCR_STOP will select 1.5 stop bits, not 2 stop bits. + */ +#define SB105X_LCR_DLAB 0x80 /* Divisor Latch Enable */ +#define SB105X_LCR_SBC 0x40 /* Break Enable*/ +#define SB105X_LCR_SPAR 0x20 /* Set Stick parity */ +#define SB105X_LCR_EPAR 0x10 /* Even parity select */ +#define SB105X_LCR_PAREN 0x08 /* Parity Enable */ +#define SB105X_LCR_STOP 0x04 /* Stop bits: 0->1 bit, 1->2 bits, 1 and SB105X_LCR_WLEN5 -> 1.5 bit */ +#define SB105X_LCR_WLEN5 0x00 /* Wordlength: 5 bits */ +#define SB105X_LCR_WLEN6 0x01 /* Wordlength: 6 bits */ +#define SB105X_LCR_WLEN7 0x02 /* Wordlength: 7 bits */ +#define SB105X_LCR_WLEN8 0x03 /* Wordlength: 8 bits */ + +#define SB105X_LCR_BF 0xBF + +#define SB105X_MCR 4 /* Out: Modem Control Register */ +#define SB105X_MCR_CPS 0x80 /* Clock Prescaler Select */ +#define SB105X_MCR_P2S 0x40 /* Page 2 Select /Xoff Re-Transmit Access Enable */ +#define SB105X_MCR_XOA 0x20 /* Xon Any Enable */ +#define SB105X_MCR_ILB 0x10 /* Internal Loopback Enable */ +#define SB105X_MCR_OUT2 0x08 /* Out2/Interrupt Output Enable*/ +#define SB105X_MCR_OUT1 0x04 /* Out1/Interrupt Output Enable */ +#define SB105X_MCR_RTS 0x02 /* RTS# Output */ +#define SB105X_MCR_DTR 0x01 /* DTR# Output */ + +#define SB105X_LSR 5 /* In: Line Status Register */ +#define SB105X_LSR_RFEI 0x80 /* Receive FIFO data error Indicator */ +#define SB105X_LSR_TEMI 0x40 /* THR and TSR Empty Indicator */ +#define SB105X_LSR_THRE 0x20 /* THR Empty Indicator */ +#define SB105X_LSR_BII 0x10 /* Break interrupt indicator */ +#define SB105X_LSR_FEI 0x08 /* Frame error indicator */ +#define SB105X_LSR_PEI 0x04 /* Parity error indicator */ +#define SB105X_LSR_OEI 0x02 /* Overrun error indicator */ +#define SB105X_LSR_RDRI 0x01 /* Receive data ready Indicator*/ + +#define SB105X_MSR 6 /* In: Modem Status Register */ +#define SB105X_MSR_DCD 0x80 /* Data Carrier Detect */ +#define SB105X_MSR_RI 0x40 /* Ring Indicator */ +#define SB105X_MSR_DSR 0x20 /* Data Set Ready */ +#define SB105X_MSR_CTS 0x10 /* Clear to Send */ +#define SB105X_MSR_DDCD 0x08 /* Delta DCD */ +#define SB105X_MSR_DRI 0x04 /* Delta ring indicator */ +#define SB105X_MSR_DDSR 0x02 /* Delta DSR */ +#define SB105X_MSR_DCTS 0x01 /* Delta CTS */ + +#define SB105XA_MDR 6 /* Out: Multi Drop mode Register */ +#define SB105XA_MDR_NPS 0x08 /* 9th Bit Polarity Select */ +#define SB105XA_MDR_AME 0x02 /* Auto Multi-drop Enable */ +#define SB105XA_MDR_MDE 0x01 /* Multi Drop Enable */ + +#define SB105X_SPR 7 /* I/O: Scratch Register */ + +/* + * DLAB=1 + */ +#define SB105X_DLL 0 /* Out: Divisor Latch Low */ +#define SB105X_DLM 1 /* Out: Divisor Latch High */ + +/* + * ****************************************************************** + * * DLAB(LCR[7]) = 0 , MCR[6] = 1 ============= Page 2 Registers * + * ****************************************************************** + */ +#define SB105X_GICR 1 /* Global Interrupt Control Register */ +#define SB105X_GICR_GIM 0x01 /* Global Interrupt Mask */ + +#define SB105X_GISR 2 /* Global Interrupt Status Register */ +#define SB105X_GISR_MGICR0 0x80 /* Mirror the content of GICR[0] */ +#define SB105X_GISR_CS3IS 0x08 /* SB105X of CS3# Interrupt Status */ +#define SB105X_GISR_CS2IS 0x04 /* SB105X of CS2# Interrupt Status */ +#define SB105X_GISR_CS1IS 0x02 /* SB105X of CS1# Interrupt Status */ +#define SB105X_GISR_CS0IS 0x01 /* SB105X of CS0# Interrupt Status */ + +#define SB105X_TFCR 5 /* Transmit FIFO Count Register */ + +#define SB105X_RFCR 6 /* Receive FIFO Count Register */ + +#define SB105X_FSR 7 /* Flow Control Status Register */ +#define SB105X_FSR_THFS 0x20 /* Transmit Hardware Flow Control Status */ +#define SB105X_FSR_TSFS 0x10 /* Transmit Software Flow Control Status */ +#define SB105X_FSR_RHFS 0x02 /* Receive Hardware Flow Control Status */ +#define SB105X_FSR_RSFS 0x01 /* Receive Software Flow Control Status */ + +/* + * ****************************************************************** + * * LCR = 0xBF, PSR[0] = 0 ============= Page 3 Registers * + * ****************************************************************** + */ + +#define SB105X_PSR 0 /* Page Select Register */ +#define SB105X_PSR_P3KEY 0xA4 /* Page 3 Select Key */ +#define SB105X_PSR_P4KEY 0xA5 /* Page 5 Select Key */ + +#define SB105X_ATR 1 /* Auto Toggle Control Register */ +#define SB105X_ATR_RPS 0x80 /* RXEN Polarity Select */ +#define SB105X_ATR_RCMS 0x40 /* RXEN Control Mode Select */ +#define SB105X_ATR_TPS 0x20 /* TXEN Polarity Select */ +#define SB105X_ATR_TCMS 0x10 /* TXEN Control Mode Select */ +#define SB105X_ATR_ATDIS 0x00 /* Auto Toggle is disabled */ +#define SB105X_ATR_ART 0x01 /* RTS#/TXEN pin operates as TXEN */ +#define SB105X_ATR_ADT 0x02 /* DTR#/TXEN pin operates as TXEN */ +#define SB105X_ATR_A80 0x03 /* only in 80 pin use */ + +#define SB105X_EFR 2 /* (Auto) Enhanced Feature Register */ +#define SB105X_EFR_ACTS 0x80 /* Auto-CTS Flow Control Enable */ +#define SB105X_EFR_ARTS 0x40 /* Auto-RTS Flow Control Enable */ +#define SB105X_EFR_SCD 0x20 /* Special Character Detect */ +#define SB105X_EFR_EFBEN 0x10 /* Enhanced Function Bits Enable */ + +#define SB105X_XON1 4 /* Xon1 Character Register */ +#define SB105X_XON2 5 /* Xon2 Character Register */ +#define SB105X_XOFF1 6 /* Xoff1 Character Register */ +#define SB105X_XOFF2 7 /* Xoff2 Character Register */ + +/* + * ****************************************************************** + * * LCR = 0xBF, PSR[0] = 1 ============ Page 4 Registers * + * ****************************************************************** + */ + +#define SB105X_AFR 1 /* Additional Feature Register */ +#define SB105X_AFR_GIPS 0x20 /* Global Interrupt Polarity Select */ +#define SB105X_AFR_GIEN 0x10 /* Global Interrupt Enable */ +#define SB105X_AFR_AFEN 0x01 /* 256-byte FIFO Enable */ + +#define SB105X_XRCR 2 /* Xoff Re-transmit Count Register */ +#define SB105X_XRCR_NRC1 0x00 /* Transmits Xoff Character whenever the number of received data is 1 during XOFF status */ +#define SB105X_XRCR_NRC4 0x01 /* Transmits Xoff Character whenever the number of received data is 4 during XOFF status */ +#define SB105X_XRCR_NRC8 0x02 /* Transmits Xoff Character whenever the number of received data is 8 during XOFF status */ +#define SB105X_XRCR_NRC16 0x03 /* Transmits Xoff Character whenever the number of received data is 16 during XOFF status */ + +#define SB105X_TTR 4 /* Transmit FIFO Trigger Level Register */ +#define SB105X_RTR 5 /* Receive FIFO Trigger Level Register */ +#define SB105X_FUR 6 /* Flow Control Upper Threshold Register */ +#define SB105X_FLR 7 /* Flow Control Lower Threshold Register */ + + +/* page 0 */ + +#define SB105X_GET_CHAR(port) inb((port)->iobase + SB105X_RX) +#define SB105X_GET_IER(port) inb((port)->iobase + SB105X_IER) +#define SB105X_GET_ISR(port) inb((port)->iobase + SB105X_ISR) +#define SB105X_GET_LCR(port) inb((port)->iobase + SB105X_LCR) +#define SB105X_GET_MCR(port) inb((port)->iobase + SB105X_MCR) +#define SB105X_GET_LSR(port) inb((port)->iobase + SB105X_LSR) +#define SB105X_GET_MSR(port) inb((port)->iobase + SB105X_MSR) +#define SB105X_GET_SPR(port) inb((port)->iobase + SB105X_SPR) + +#define SB105X_PUT_CHAR(port,v) outb((v),(port)->iobase + SB105X_TX ) +#define SB105X_PUT_IER(port,v) outb((v),(port)->iobase + SB105X_IER ) +#define SB105X_PUT_FCR(port,v) outb((v),(port)->iobase + SB105X_FCR ) +#define SB105X_PUT_LCR(port,v) outb((v),(port)->iobase + SB105X_LCR ) +#define SB105X_PUT_MCR(port,v) outb((v),(port)->iobase + SB105X_MCR ) +#define SB105X_PUT_SPR(port,v) outb((v),(port)->iobase + SB105X_SPR ) + + +/* page 1 */ +#define SB105X_GET_REG(port,reg) inb((port)->iobase + (reg)) +#define SB105X_PUT_REG(port,reg,v) outb((v),(port)->iobase + (reg)) + +/* page 2 */ + +#define SB105X_PUT_PSR(port,v) outb((v),(port)->iobase + SB105X_PSR ) + +#endif diff --git a/drivers/staging/sb105x/sb_pci_mp.c b/drivers/staging/sb105x/sb_pci_mp.c new file mode 100644 index 000000000000..fbebf88226d3 --- /dev/null +++ b/drivers/staging/sb105x/sb_pci_mp.c @@ -0,0 +1,3195 @@ +#include "sb_pci_mp.h" +#include +#include + +extern struct parport *parport_pc_probe_port(unsigned long base_lo, + unsigned long base_hi, + int irq, int dma, + struct device *dev, + int irqflags); + +static struct mp_device_t mp_devs[MAX_MP_DEV]; +static int mp_nrpcibrds = sizeof(mp_pciboards)/sizeof(mppcibrd_t); +static int NR_BOARD=0; +static int NR_PORTS=0; +static struct mp_port multi_ports[MAX_MP_PORT]; +static struct irq_info irq_lists[NR_IRQS]; + +static _INLINE_ unsigned int serial_in(struct mp_port *mtpt, int offset); +static _INLINE_ void serial_out(struct mp_port *mtpt, int offset, int value); +static _INLINE_ unsigned int read_option_register(struct mp_port *mtpt, int offset); +static int sb1054_get_register(struct sb_uart_port * port, int page, int reg); +static int sb1054_set_register(struct sb_uart_port * port, int page, int reg, int value); +static void SendATCommand(struct mp_port * mtpt); +static int set_deep_fifo(struct sb_uart_port * port, int status); +static int get_deep_fifo(struct sb_uart_port * port); +static int get_device_type(int arg); +static int set_auto_rts(struct sb_uart_port *port, int status); +static void mp_stop(struct tty_struct *tty); +static void __mp_start(struct tty_struct *tty); +static void mp_start(struct tty_struct *tty); +static void mp_tasklet_action(unsigned long data); +static inline void mp_update_mctrl(struct sb_uart_port *port, unsigned int set, unsigned int clear); +static int mp_startup(struct sb_uart_state *state, int init_hw); +static void mp_shutdown(struct sb_uart_state *state); +static void mp_change_speed(struct sb_uart_state *state, struct MP_TERMIOS *old_termios); + +static inline int __mp_put_char(struct sb_uart_port *port, struct circ_buf *circ, unsigned char c); +static int mp_put_char(struct tty_struct *tty, unsigned char ch); + +static void mp_put_chars(struct tty_struct *tty); +static int mp_write(struct tty_struct *tty, const unsigned char * buf, int count); +static int mp_write_room(struct tty_struct *tty); +static int mp_chars_in_buffer(struct tty_struct *tty); +static void mp_flush_buffer(struct tty_struct *tty); +static void mp_send_xchar(struct tty_struct *tty, char ch); +static void mp_throttle(struct tty_struct *tty); +static void mp_unthrottle(struct tty_struct *tty); +static int mp_get_info(struct sb_uart_state *state, struct serial_struct *retinfo); +static int mp_set_info(struct sb_uart_state *state, struct serial_struct *newinfo); +static int mp_get_lsr_info(struct sb_uart_state *state, unsigned int *value); + +static int mp_tiocmget(struct tty_struct *tty); +static int mp_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear); +static int mp_break_ctl(struct tty_struct *tty, int break_state); +static int mp_do_autoconfig(struct sb_uart_state *state); +static int mp_wait_modem_status(struct sb_uart_state *state, unsigned long arg); +static int mp_get_count(struct sb_uart_state *state, struct serial_icounter_struct *icnt); +static int mp_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg); +static void mp_set_termios(struct tty_struct *tty, struct MP_TERMIOS *old_termios); +static void mp_close(struct tty_struct *tty, struct file *filp); +static void mp_wait_until_sent(struct tty_struct *tty, int timeout); +static void mp_hangup(struct tty_struct *tty); +static void mp_update_termios(struct sb_uart_state *state); +static int mp_block_til_ready(struct file *filp, struct sb_uart_state *state); +static struct sb_uart_state *uart_get(struct uart_driver *drv, int line); +static int mp_open(struct tty_struct *tty, struct file *filp); +static const char *mp_type(struct sb_uart_port *port); +static void mp_change_pm(struct sb_uart_state *state, int pm_state); +static inline void mp_report_port(struct uart_driver *drv, struct sb_uart_port *port); +static void mp_configure_port(struct uart_driver *drv, struct sb_uart_state *state, struct sb_uart_port *port); +static void mp_unconfigure_port(struct uart_driver *drv, struct sb_uart_state *state); +static int mp_register_driver(struct uart_driver *drv); +static void mp_unregister_driver(struct uart_driver *drv); +static int mp_add_one_port(struct uart_driver *drv, struct sb_uart_port *port); +static int mp_remove_one_port(struct uart_driver *drv, struct sb_uart_port *port); +static void autoconfig(struct mp_port *mtpt, unsigned int probeflags); +static void autoconfig_irq(struct mp_port *mtpt); +static void multi_stop_tx(struct sb_uart_port *port); +static void multi_start_tx(struct sb_uart_port *port); +static void multi_stop_rx(struct sb_uart_port *port); +static void multi_enable_ms(struct sb_uart_port *port); +static _INLINE_ void receive_chars(struct mp_port *mtpt, int *status ); +static _INLINE_ void transmit_chars(struct mp_port *mtpt); +static _INLINE_ void check_modem_status(struct mp_port *mtpt); +static inline void multi_handle_port(struct mp_port *mtpt); +static irqreturn_t multi_interrupt(int irq, void *dev_id); +static void serial_do_unlink(struct irq_info *i, struct mp_port *mtpt); +static int serial_link_irq_chain(struct mp_port *mtpt); +static void serial_unlink_irq_chain(struct mp_port *mtpt); +static void multi_timeout(unsigned long data); +static unsigned int multi_tx_empty(struct sb_uart_port *port); +static unsigned int multi_get_mctrl(struct sb_uart_port *port); +static void multi_set_mctrl(struct sb_uart_port *port, unsigned int mctrl); +static void multi_break_ctl(struct sb_uart_port *port, int break_state); +static int multi_startup(struct sb_uart_port *port); +static void multi_shutdown(struct sb_uart_port *port); +static unsigned int multi_get_divisor(struct sb_uart_port *port, unsigned int baud); +static void multi_set_termios(struct sb_uart_port *port, struct MP_TERMIOS *termios, struct MP_TERMIOS *old); +static void multi_pm(struct sb_uart_port *port, unsigned int state, unsigned int oldstate); +static void multi_release_std_resource(struct mp_port *mtpt); +static void multi_release_port(struct sb_uart_port *port); +static int multi_request_port(struct sb_uart_port *port); +static void multi_config_port(struct sb_uart_port *port, int flags); +static int multi_verify_port(struct sb_uart_port *port, struct serial_struct *ser); +static const char * multi_type(struct sb_uart_port *port); +static void __init multi_init_ports(void); +static void __init multi_register_ports(struct uart_driver *drv); +static int init_mp_dev(struct pci_dev *pcidev, mppcibrd_t brd); + +static int deep[256]; +static int deep_count; +static int fcr_arr[256]; +static int fcr_count; +static int ttr[256]; +static int ttr_count; +static int rtr[256]; +static int rtr_count; + +module_param_array(deep,int,&deep_count,0); +module_param_array(fcr_arr,int,&fcr_count,0); +module_param_array(ttr,int,&ttr_count,0); +module_param_array(rtr,int,&rtr_count,0); + +static _INLINE_ unsigned int serial_in(struct mp_port *mtpt, int offset) +{ + return inb(mtpt->port.iobase + offset); +} + +static _INLINE_ void serial_out(struct mp_port *mtpt, int offset, int value) +{ + outb(value, mtpt->port.iobase + offset); +} + +static _INLINE_ unsigned int read_option_register(struct mp_port *mtpt, int offset) +{ + return inb(mtpt->option_base_addr + offset); +} + +static int sb1053a_get_interface(struct mp_port *mtpt, int port_num) +{ + unsigned long option_base_addr = mtpt->option_base_addr; + unsigned int interface = 0; + + switch (port_num) + { + case 0: + case 1: + /* set GPO[1:0] = 00 */ + outb(0x00, option_base_addr + MP_OPTR_GPODR); + break; + case 2: + case 3: + /* set GPO[1:0] = 01 */ + outb(0x01, option_base_addr + MP_OPTR_GPODR); + break; + case 4: + case 5: + /* set GPO[1:0] = 10 */ + outb(0x02, option_base_addr + MP_OPTR_GPODR); + break; + default: + break; + } + + port_num &= 0x1; + + /* get interface */ + interface = inb(option_base_addr + MP_OPTR_IIR0 + port_num); + + /* set GPO[1:0] = 11 */ + outb(0x03, option_base_addr + MP_OPTR_GPODR); + + return (interface); +} + +static int sb1054_get_register(struct sb_uart_port * port, int page, int reg) +{ + int ret = 0; + unsigned int lcr = 0; + unsigned int mcr = 0; + unsigned int tmp = 0; + + if( page <= 0) + { + printk(" page 0 can not use this fuction\n"); + return -1; + } + + switch(page) + { + case 1: + lcr = SB105X_GET_LCR(port); + tmp = lcr | SB105X_LCR_DLAB; + SB105X_PUT_LCR(port, tmp); + + tmp = SB105X_GET_LCR(port); + + ret = SB105X_GET_REG(port,reg); + SB105X_PUT_LCR(port,lcr); + break; + case 2: + mcr = SB105X_GET_MCR(port); + tmp = mcr | SB105X_MCR_P2S; + SB105X_PUT_MCR(port,tmp); + + ret = SB105X_GET_REG(port,reg); + + SB105X_PUT_MCR(port,mcr); + break; + case 3: + lcr = SB105X_GET_LCR(port); + tmp = lcr | SB105X_LCR_BF; + SB105X_PUT_LCR(port,tmp); + SB105X_PUT_REG(port,SB105X_PSR,SB105X_PSR_P3KEY); + + ret = SB105X_GET_REG(port,reg); + + SB105X_PUT_LCR(port,lcr); + break; + case 4: + lcr = SB105X_GET_LCR(port); + tmp = lcr | SB105X_LCR_BF; + SB105X_PUT_LCR(port,tmp); + SB105X_PUT_REG(port,SB105X_PSR,SB105X_PSR_P4KEY); + + ret = SB105X_GET_REG(port,reg); + + SB105X_PUT_LCR(port,lcr); + break; + default: + printk(" error invalid page number \n"); + return -1; + } + + return ret; +} + +static int sb1054_set_register(struct sb_uart_port * port, int page, int reg, int value) +{ + int lcr = 0; + int mcr = 0; + int ret = 0; + + if( page <= 0) + { + printk(" page 0 can not use this fuction\n"); + return -1; + } + switch(page) + { + case 1: + lcr = SB105X_GET_LCR(port); + SB105X_PUT_LCR(port, lcr | SB105X_LCR_DLAB); + + SB105X_PUT_REG(port,reg,value); + + SB105X_PUT_LCR(port, lcr); + ret = 1; + break; + case 2: + mcr = SB105X_GET_MCR(port); + SB105X_PUT_MCR(port, mcr | SB105X_MCR_P2S); + + SB105X_PUT_REG(port,reg,value); + + SB105X_PUT_MCR(port, mcr); + ret = 1; + break; + case 3: + lcr = SB105X_GET_LCR(port); + SB105X_PUT_LCR(port, lcr | SB105X_LCR_BF); + SB105X_PUT_PSR(port, SB105X_PSR_P3KEY); + + SB105X_PUT_REG(port,reg,value); + + SB105X_PUT_LCR(port, lcr); + ret = 1; + break; + case 4: + lcr = SB105X_GET_LCR(port); + SB105X_PUT_LCR(port, lcr | SB105X_LCR_BF); + SB105X_PUT_PSR(port, SB105X_PSR_P4KEY); + + SB105X_PUT_REG(port,reg,value); + + SB105X_PUT_LCR(port, lcr); + ret = 1; + break; + default: + printk(" error invalid page number \n"); + return -1; + } + + return ret; +} + +static int set_multidrop_mode(struct sb_uart_port *port, unsigned int mode) +{ + int mdr = SB105XA_MDR_NPS; + + if (mode & MDMODE_ENABLE) + { + mdr |= SB105XA_MDR_MDE; + } + + if (1) //(mode & MDMODE_AUTO) + { + int efr = 0; + mdr |= SB105XA_MDR_AME; + efr = sb1054_get_register(port, PAGE_3, SB105X_EFR); + efr |= SB105X_EFR_SCD; + sb1054_set_register(port, PAGE_3, SB105X_EFR, efr); + } + + sb1054_set_register(port, PAGE_1, SB105XA_MDR, mdr); + port->mdmode &= ~0x6; + port->mdmode |= mode; + printk("[%d] multidrop init: %x\n", port->line, port->mdmode); + + return 0; +} + +static int get_multidrop_addr(struct sb_uart_port *port) +{ + return sb1054_get_register(port, PAGE_3, SB105X_XOFF2); +} + +static int set_multidrop_addr(struct sb_uart_port *port, unsigned int addr) +{ + sb1054_set_register(port, PAGE_3, SB105X_XOFF2, addr); + + return 0; +} + +static void SendATCommand(struct mp_port * mtpt) +{ + // a t cr lf + unsigned char ch[] = {0x61,0x74,0x0d,0x0a,0x0}; + unsigned char lineControl; + unsigned char i=0; + unsigned char Divisor = 0xc; + + lineControl = serial_inp(mtpt,UART_LCR); + serial_outp(mtpt,UART_LCR,(lineControl | UART_LCR_DLAB)); + serial_outp(mtpt,UART_DLL,(Divisor & 0xff)); + serial_outp(mtpt,UART_DLM,(Divisor & 0xff00)>>8); //baudrate is 4800 + + + serial_outp(mtpt,UART_LCR,lineControl); + serial_outp(mtpt,UART_LCR,0x03); // N-8-1 + serial_outp(mtpt,UART_FCR,7); + serial_outp(mtpt,UART_MCR,0x3); + while(ch[i]){ + while((serial_inp(mtpt,UART_LSR) & 0x60) !=0x60){ + ; + } + serial_outp(mtpt,0,ch[i++]); + } + + +}// end of SendATCommand() + +static int set_deep_fifo(struct sb_uart_port * port, int status) +{ + int afr_status = 0; + afr_status = sb1054_get_register(port, PAGE_4, SB105X_AFR); + + if(status == ENABLE) + { + afr_status |= SB105X_AFR_AFEN; + } + else + { + afr_status &= ~SB105X_AFR_AFEN; + } + + sb1054_set_register(port,PAGE_4,SB105X_AFR,afr_status); + sb1054_set_register(port,PAGE_4,SB105X_TTR,ttr[port->line]); + sb1054_set_register(port,PAGE_4,SB105X_RTR,rtr[port->line]); + afr_status = sb1054_get_register(port, PAGE_4, SB105X_AFR); + + return afr_status; +} + +static int get_device_type(int arg) +{ + int ret; + ret = inb(mp_devs[arg].option_reg_addr+MP_OPTR_DIR0); + ret = (ret & 0xf0) >> 4; + switch (ret) + { + case DIR_UART_16C550: + return PORT_16C55X; + case DIR_UART_16C1050: + return PORT_16C105X; + case DIR_UART_16C1050A: + /* + if (mtpt->port.line < 2) + { + return PORT_16C105XA; + } + else + { + if (mtpt->device->device_id & 0x50) + { + return PORT_16C55X; + } + else + { + return PORT_16C105X; + } + }*/ + return PORT_16C105XA; + default: + return PORT_UNKNOWN; + } + +} +static int get_deep_fifo(struct sb_uart_port * port) +{ + int afr_status = 0; + afr_status = sb1054_get_register(port, PAGE_4, SB105X_AFR); + return afr_status; +} + +static int set_auto_rts(struct sb_uart_port *port, int status) +{ + int atr_status = 0; + +#if 0 + int efr_status = 0; + + efr_status = sb1054_get_register(port, PAGE_3, SB105X_EFR); + if(status == ENABLE) + efr_status |= SB105X_EFR_ARTS; + else + efr_status &= ~SB105X_EFR_ARTS; + sb1054_set_register(port,PAGE_3,SB105X_EFR,efr_status); + efr_status = sb1054_get_register(port, PAGE_3, SB105X_EFR); +#endif + +//ATR + atr_status = sb1054_get_register(port, PAGE_3, SB105X_ATR); + switch(status) + { + case RS422PTP: + atr_status = (SB105X_ATR_TPS) | (SB105X_ATR_A80); + break; + case RS422MD: + atr_status = (SB105X_ATR_TPS) | (SB105X_ATR_TCMS) | (SB105X_ATR_A80); + break; + case RS485NE: + atr_status = (SB105X_ATR_RCMS) | (SB105X_ATR_TPS) | (SB105X_ATR_TCMS) | (SB105X_ATR_A80); + break; + case RS485ECHO: + atr_status = (SB105X_ATR_TPS) | (SB105X_ATR_TCMS) | (SB105X_ATR_A80); + break; + } + + sb1054_set_register(port,PAGE_3,SB105X_ATR,atr_status); + atr_status = sb1054_get_register(port, PAGE_3, SB105X_ATR); + + return atr_status; +} + +static void mp_stop(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + port->ops->stop_tx(port); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void __mp_start(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + + if (!uart_circ_empty(&state->info->xmit) && state->info->xmit.buf && + !tty->stopped && !tty->hw_stopped) + port->ops->start_tx(port); +} + +static void mp_start(struct tty_struct *tty) +{ + __mp_start(tty); +} + +static void mp_tasklet_action(unsigned long data) +{ + struct sb_uart_state *state = (struct sb_uart_state *)data; + struct tty_struct *tty; + + printk("tasklet is called!\n"); + tty = state->info->tty; + tty_wakeup(tty); +} + +static inline void mp_update_mctrl(struct sb_uart_port *port, unsigned int set, unsigned int clear) +{ + unsigned int old; + + old = port->mctrl; + port->mctrl = (old & ~clear) | set; + if (old != port->mctrl) + port->ops->set_mctrl(port, port->mctrl); +} + +#define uart_set_mctrl(port,set) mp_update_mctrl(port,set,0) +#define uart_clear_mctrl(port,clear) mp_update_mctrl(port,0,clear) + +static int mp_startup(struct sb_uart_state *state, int init_hw) +{ + struct sb_uart_info *info = state->info; + struct sb_uart_port *port = state->port; + unsigned long page; + int retval = 0; + + if (info->flags & UIF_INITIALIZED) + return 0; + + if (info->tty) + set_bit(TTY_IO_ERROR, &info->tty->flags); + + if (port->type == PORT_UNKNOWN) + return 0; + + if (!info->xmit.buf) { + page = get_zeroed_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + + info->xmit.buf = (unsigned char *) page; + + uart_circ_clear(&info->xmit); + } + + retval = port->ops->startup(port); + if (retval == 0) { + if (init_hw) { + mp_change_speed(state, NULL); + + if (info->tty->termios.c_cflag & CBAUD) + uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR); + } + + info->flags |= UIF_INITIALIZED; + + + clear_bit(TTY_IO_ERROR, &info->tty->flags); + } + + if (retval && capable(CAP_SYS_ADMIN)) + retval = 0; + + return retval; +} + +static void mp_shutdown(struct sb_uart_state *state) +{ + struct sb_uart_info *info = state->info; + struct sb_uart_port *port = state->port; + + if (info->tty) + set_bit(TTY_IO_ERROR, &info->tty->flags); + + if (info->flags & UIF_INITIALIZED) { + info->flags &= ~UIF_INITIALIZED; + + if (!info->tty || (info->tty->termios.c_cflag & HUPCL)) + uart_clear_mctrl(port, TIOCM_DTR | TIOCM_RTS); + + wake_up_interruptible(&info->delta_msr_wait); + + port->ops->shutdown(port); + + synchronize_irq(port->irq); + } + tasklet_kill(&info->tlet); + + if (info->xmit.buf) { + free_page((unsigned long)info->xmit.buf); + info->xmit.buf = NULL; + } +} + +static void mp_change_speed(struct sb_uart_state *state, struct MP_TERMIOS *old_termios) +{ + struct tty_struct *tty = state->info->tty; + struct sb_uart_port *port = state->port; + + if (!tty || port->type == PORT_UNKNOWN) + return; + + if (tty->termios.c_cflag & CRTSCTS) + state->info->flags |= UIF_CTS_FLOW; + else + state->info->flags &= ~UIF_CTS_FLOW; + + if (tty->termios.c_cflag & CLOCAL) + state->info->flags &= ~UIF_CHECK_CD; + else + state->info->flags |= UIF_CHECK_CD; + + port->ops->set_termios(port, &tty->termios, old_termios); +} + +static inline int __mp_put_char(struct sb_uart_port *port, struct circ_buf *circ, unsigned char c) +{ + unsigned long flags; + int ret = 0; + + if (!circ->buf) + return 0; + + spin_lock_irqsave(&port->lock, flags); + if (uart_circ_chars_free(circ) != 0) { + circ->buf[circ->head] = c; + circ->head = (circ->head + 1) & (UART_XMIT_SIZE - 1); + ret = 1; + } + spin_unlock_irqrestore(&port->lock, flags); + return ret; +} + +static int mp_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct sb_uart_state *state = tty->driver_data; + + return __mp_put_char(state->port, &state->info->xmit, ch); +} + +static void mp_put_chars(struct tty_struct *tty) +{ + mp_start(tty); +} + +static int mp_write(struct tty_struct *tty, const unsigned char * buf, int count) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port; + struct circ_buf *circ; + int c, ret = 0; + + if (!state || !state->info) { + return -EL3HLT; + } + + port = state->port; + circ = &state->info->xmit; + + if (!circ->buf) + return 0; + + while (1) { + c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE); + if (count < c) + c = count; + if (c <= 0) + break; + memcpy(circ->buf + circ->head, buf, c); + + circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1); + buf += c; + count -= c; + ret += c; + } + mp_start(tty); + return ret; +} + +static int mp_write_room(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + + return uart_circ_chars_free(&state->info->xmit); +} + +static int mp_chars_in_buffer(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + + return uart_circ_chars_pending(&state->info->xmit); +} + +static void mp_flush_buffer(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + unsigned long flags; + + if (!state || !state->info) { + return; + } + + spin_lock_irqsave(&port->lock, flags); + uart_circ_clear(&state->info->xmit); + spin_unlock_irqrestore(&port->lock, flags); + wake_up_interruptible(&tty->write_wait); + tty_wakeup(tty); +} + +static void mp_send_xchar(struct tty_struct *tty, char ch) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + unsigned long flags; + + if (port->ops->send_xchar) + port->ops->send_xchar(port, ch); + else { + port->x_char = ch; + if (ch) { + spin_lock_irqsave(&port->lock, flags); + port->ops->start_tx(port); + spin_unlock_irqrestore(&port->lock, flags); + } + } +} + +static void mp_throttle(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + + if (I_IXOFF(tty)) + mp_send_xchar(tty, STOP_CHAR(tty)); + + if (tty->termios.c_cflag & CRTSCTS) + uart_clear_mctrl(state->port, TIOCM_RTS); +} + +static void mp_unthrottle(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + + if (I_IXOFF(tty)) { + if (port->x_char) + port->x_char = 0; + else + mp_send_xchar(tty, START_CHAR(tty)); + } + + if (tty->termios.c_cflag & CRTSCTS) + uart_set_mctrl(port, TIOCM_RTS); +} + +static int mp_get_info(struct sb_uart_state *state, struct serial_struct *retinfo) +{ + struct sb_uart_port *port = state->port; + struct serial_struct tmp; + + memset(&tmp, 0, sizeof(tmp)); + tmp.type = port->type; + tmp.line = port->line; + tmp.port = port->iobase; + if (HIGH_BITS_OFFSET) + tmp.port_high = (long) port->iobase >> HIGH_BITS_OFFSET; + tmp.irq = port->irq; + tmp.flags = port->flags; + tmp.xmit_fifo_size = port->fifosize; + tmp.baud_base = port->uartclk / 16; + tmp.close_delay = state->close_delay; + tmp.closing_wait = state->closing_wait == USF_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + state->closing_wait; + tmp.custom_divisor = port->custom_divisor; + tmp.hub6 = port->hub6; + tmp.io_type = port->iotype; + tmp.iomem_reg_shift = port->regshift; + tmp.iomem_base = (void *)port->mapbase; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int mp_set_info(struct sb_uart_state *state, struct serial_struct *newinfo) +{ + struct serial_struct new_serial; + struct sb_uart_port *port = state->port; + unsigned long new_port; + unsigned int change_irq, change_port, closing_wait; + unsigned int old_custom_divisor; + unsigned int old_flags, new_flags; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + new_port = new_serial.port; + if (HIGH_BITS_OFFSET) + new_port += (unsigned long) new_serial.port_high << HIGH_BITS_OFFSET; + + new_serial.irq = irq_canonicalize(new_serial.irq); + + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + USF_CLOSING_WAIT_NONE : new_serial.closing_wait; + MP_STATE_LOCK(state); + + change_irq = new_serial.irq != port->irq; + change_port = new_port != port->iobase || + (unsigned long)new_serial.iomem_base != port->mapbase || + new_serial.hub6 != port->hub6 || + new_serial.io_type != port->iotype || + new_serial.iomem_reg_shift != port->regshift || + new_serial.type != port->type; + old_flags = port->flags; + new_flags = new_serial.flags; + old_custom_divisor = port->custom_divisor; + + if (!capable(CAP_SYS_ADMIN)) { + retval = -EPERM; + if (change_irq || change_port || + (new_serial.baud_base != port->uartclk / 16) || + (new_serial.close_delay != state->close_delay) || + (closing_wait != state->closing_wait) || + (new_serial.xmit_fifo_size != port->fifosize) || + (((new_flags ^ old_flags) & ~UPF_USR_MASK) != 0)) + goto exit; + port->flags = ((port->flags & ~UPF_USR_MASK) | + (new_flags & UPF_USR_MASK)); + port->custom_divisor = new_serial.custom_divisor; + goto check_and_exit; + } + + if (port->ops->verify_port) + retval = port->ops->verify_port(port, &new_serial); + + if ((new_serial.irq >= NR_IRQS) || (new_serial.irq < 0) || + (new_serial.baud_base < 9600)) + retval = -EINVAL; + + if (retval) + goto exit; + + if (change_port || change_irq) { + retval = -EBUSY; + + if (uart_users(state) > 1) + goto exit; + + mp_shutdown(state); + } + + if (change_port) { + unsigned long old_iobase, old_mapbase; + unsigned int old_type, old_iotype, old_hub6, old_shift; + + old_iobase = port->iobase; + old_mapbase = port->mapbase; + old_type = port->type; + old_hub6 = port->hub6; + old_iotype = port->iotype; + old_shift = port->regshift; + + if (old_type != PORT_UNKNOWN) + port->ops->release_port(port); + + port->iobase = new_port; + port->type = new_serial.type; + port->hub6 = new_serial.hub6; + port->iotype = new_serial.io_type; + port->regshift = new_serial.iomem_reg_shift; + port->mapbase = (unsigned long)new_serial.iomem_base; + + if (port->type != PORT_UNKNOWN) { + retval = port->ops->request_port(port); + } else { + retval = 0; + } + + if (retval && old_type != PORT_UNKNOWN) { + port->iobase = old_iobase; + port->type = old_type; + port->hub6 = old_hub6; + port->iotype = old_iotype; + port->regshift = old_shift; + port->mapbase = old_mapbase; + retval = port->ops->request_port(port); + if (retval) + port->type = PORT_UNKNOWN; + + retval = -EBUSY; + } + } + + port->irq = new_serial.irq; + port->uartclk = new_serial.baud_base * 16; + port->flags = (port->flags & ~UPF_CHANGE_MASK) | + (new_flags & UPF_CHANGE_MASK); + port->custom_divisor = new_serial.custom_divisor; + state->close_delay = new_serial.close_delay; + state->closing_wait = closing_wait; + port->fifosize = new_serial.xmit_fifo_size; + if (state->info->tty) + state->info->tty->low_latency = + (port->flags & UPF_LOW_LATENCY) ? 1 : 0; + +check_and_exit: + retval = 0; + if (port->type == PORT_UNKNOWN) + goto exit; + if (state->info->flags & UIF_INITIALIZED) { + if (((old_flags ^ port->flags) & UPF_SPD_MASK) || + old_custom_divisor != port->custom_divisor) { + if (port->flags & UPF_SPD_MASK) { + printk(KERN_NOTICE + "%s sets custom speed on ttyMP%d. This " + "is deprecated.\n", current->comm, + port->line); + } + mp_change_speed(state, NULL); + } + } else + retval = mp_startup(state, 1); +exit: + MP_STATE_UNLOCK(state); + return retval; +} + + +static int mp_get_lsr_info(struct sb_uart_state *state, unsigned int *value) +{ + struct sb_uart_port *port = state->port; + unsigned int result; + + result = port->ops->tx_empty(port); + + if (port->x_char || + ((uart_circ_chars_pending(&state->info->xmit) > 0) && + !state->info->tty->stopped && !state->info->tty->hw_stopped)) + result &= ~TIOCSER_TEMT; + + return put_user(result, value); +} + +static int mp_tiocmget(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + int result = -EIO; + + MP_STATE_LOCK(state); + if (!(tty->flags & (1 << TTY_IO_ERROR))) { + result = port->mctrl; + spin_lock_irq(&port->lock); + result |= port->ops->get_mctrl(port); + spin_unlock_irq(&port->lock); + } + MP_STATE_UNLOCK(state); + return result; +} + +static int mp_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + int ret = -EIO; + + + MP_STATE_LOCK(state); + if (!(tty->flags & (1 << TTY_IO_ERROR))) { + mp_update_mctrl(port, set, clear); + ret = 0; + } + MP_STATE_UNLOCK(state); + + return ret; +} + +static int mp_break_ctl(struct tty_struct *tty, int break_state) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + + MP_STATE_LOCK(state); + + if (port->type != PORT_UNKNOWN) + port->ops->break_ctl(port, break_state); + + MP_STATE_UNLOCK(state); + return 0; +} + +static int mp_do_autoconfig(struct sb_uart_state *state) +{ + struct sb_uart_port *port = state->port; + int flags, ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (mutex_lock_interruptible(&state->mutex)) + return -ERESTARTSYS; + ret = -EBUSY; + if (uart_users(state) == 1) { + mp_shutdown(state); + + if (port->type != PORT_UNKNOWN) + port->ops->release_port(port); + + flags = UART_CONFIG_TYPE; + if (port->flags & UPF_AUTO_IRQ) + flags |= UART_CONFIG_IRQ; + + port->ops->config_port(port, flags); + + ret = mp_startup(state, 1); + } + MP_STATE_UNLOCK(state); + return ret; +} + +static int mp_wait_modem_status(struct sb_uart_state *state, unsigned long arg) +{ + struct sb_uart_port *port = state->port; + DECLARE_WAITQUEUE(wait, current); + struct sb_uart_icount cprev, cnow; + int ret; + + spin_lock_irq(&port->lock); + memcpy(&cprev, &port->icount, sizeof(struct sb_uart_icount)); + + port->ops->enable_ms(port); + spin_unlock_irq(&port->lock); + + add_wait_queue(&state->info->delta_msr_wait, &wait); + for (;;) { + spin_lock_irq(&port->lock); + memcpy(&cnow, &port->icount, sizeof(struct sb_uart_icount)); + spin_unlock_irq(&port->lock); + + set_current_state(TASK_INTERRUPTIBLE); + + if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || + ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || + ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || + ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts))) { + ret = 0; + break; + } + + schedule(); + + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + + cprev = cnow; + } + + current->state = TASK_RUNNING; + remove_wait_queue(&state->info->delta_msr_wait, &wait); + + return ret; +} + +static int mp_get_count(struct sb_uart_state *state, struct serial_icounter_struct *icnt) +{ + struct serial_icounter_struct icount; + struct sb_uart_icount cnow; + struct sb_uart_port *port = state->port; + + spin_lock_irq(&port->lock); + memcpy(&cnow, &port->icount, sizeof(struct sb_uart_icount)); + spin_unlock_irq(&port->lock); + + icount.cts = cnow.cts; + icount.dsr = cnow.dsr; + icount.rng = cnow.rng; + icount.dcd = cnow.dcd; + icount.rx = cnow.rx; + icount.tx = cnow.tx; + icount.frame = cnow.frame; + icount.overrun = cnow.overrun; + icount.parity = cnow.parity; + icount.brk = cnow.brk; + icount.buf_overrun = cnow.buf_overrun; + + return copy_to_user(icnt, &icount, sizeof(icount)) ? -EFAULT : 0; +} + +static int mp_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) +{ + struct sb_uart_state *state = tty->driver_data; + struct mp_port *info = (struct mp_port *)state->port; + int ret = -ENOIOCTLCMD; + + + switch (cmd) { + case TIOCSMULTIDROP: + /* set multi-drop mode enable or disable, and default operation mode is H/W mode */ + if (info->port.type == PORT_16C105XA) + { + //arg &= ~0x6; + //state->port->mdmode = 0; + return set_multidrop_mode((struct sb_uart_port *)info, (unsigned int)arg); + } + ret = -ENOTSUPP; + break; + case GETDEEPFIFO: + ret = get_deep_fifo(state->port); + return ret; + case SETDEEPFIFO: + ret = set_deep_fifo(state->port,arg); + deep[state->port->line] = arg; + return ret; + case SETTTR: + if (info->port.type == PORT_16C105X || info->port.type == PORT_16C105XA){ + ret = sb1054_set_register(state->port,PAGE_4,SB105X_TTR,arg); + ttr[state->port->line] = arg; + } + return ret; + case SETRTR: + if (info->port.type == PORT_16C105X || info->port.type == PORT_16C105XA){ + ret = sb1054_set_register(state->port,PAGE_4,SB105X_RTR,arg); + rtr[state->port->line] = arg; + } + return ret; + case GETTTR: + if (info->port.type == PORT_16C105X || info->port.type == PORT_16C105XA){ + ret = sb1054_get_register(state->port,PAGE_4,SB105X_TTR); + } + return ret; + case GETRTR: + if (info->port.type == PORT_16C105X || info->port.type == PORT_16C105XA){ + ret = sb1054_get_register(state->port,PAGE_4,SB105X_RTR); + } + return ret; + + case SETFCR: + if (info->port.type == PORT_16C105X || info->port.type == PORT_16C105XA){ + ret = sb1054_set_register(state->port,PAGE_1,SB105X_FCR,arg); + } + else{ + serial_out(info,2,arg); + } + + return ret; + case TIOCSMDADDR: + /* set multi-drop address */ + if (info->port.type == PORT_16C105XA) + { + state->port->mdmode |= MDMODE_ADDR; + return set_multidrop_addr((struct sb_uart_port *)info, (unsigned int)arg); + } + ret = -ENOTSUPP; + break; + + case TIOCGMDADDR: + /* set multi-drop address */ + if ((info->port.type == PORT_16C105XA) && (state->port->mdmode & MDMODE_ADDR)) + { + return get_multidrop_addr((struct sb_uart_port *)info); + } + ret = -ENOTSUPP; + break; + + case TIOCSENDADDR: + /* send address in multi-drop mode */ + if ((info->port.type == PORT_16C105XA) + && (state->port->mdmode & (MDMODE_ENABLE))) + { + if (mp_chars_in_buffer(tty) > 0) + { + tty_wait_until_sent(tty, 0); + } + //while ((serial_in(info, UART_LSR) & 0x60) != 0x60); + //while (sb1054_get_register(state->port, PAGE_2, SB105X_TFCR) != 0); + while ((serial_in(info, UART_LSR) & 0x60) != 0x60); + serial_out(info, UART_SCR, (int)arg); + } + break; + + case TIOCGSERIAL: + ret = mp_get_info(state, (struct serial_struct *)arg); + break; + + case TIOCSSERIAL: + ret = mp_set_info(state, (struct serial_struct *)arg); + break; + + case TIOCSERCONFIG: + ret = mp_do_autoconfig(state); + break; + + case TIOCSERGWILD: /* obsolete */ + case TIOCSERSWILD: /* obsolete */ + ret = 0; + break; + /* for Multiport */ + case TIOCGNUMOFPORT: /* Get number of ports */ + return NR_PORTS; + case TIOCGGETDEVID: + return mp_devs[arg].device_id; + case TIOCGGETREV: + return mp_devs[arg].revision; + case TIOCGGETNRPORTS: + return mp_devs[arg].nr_ports; + case TIOCGGETBDNO: + return NR_BOARD; + case TIOCGGETINTERFACE: + if (mp_devs[arg].revision == 0xc0) + { + /* for SB16C1053APCI */ + return (sb1053a_get_interface(info, info->port.line)); + } + else + { + return (inb(mp_devs[arg].option_reg_addr+MP_OPTR_IIR0+(state->port->line/8))); + } + case TIOCGGETPORTTYPE: + ret = get_device_type(arg);; + return ret; + case TIOCSMULTIECHO: /* set to multi-drop mode(RS422) or echo mode(RS485)*/ + outb( ( inb(info->interface_config_addr) & ~0x03 ) | 0x01 , + info->interface_config_addr); + return 0; + case TIOCSPTPNOECHO: /* set to multi-drop mode(RS422) or echo mode(RS485) */ + outb( ( inb(info->interface_config_addr) & ~0x03 ) , + info->interface_config_addr); + return 0; + } + + if (ret != -ENOIOCTLCMD) + goto out; + + if (tty->flags & (1 << TTY_IO_ERROR)) { + ret = -EIO; + goto out; + } + + switch (cmd) { + case TIOCMIWAIT: + ret = mp_wait_modem_status(state, arg); + break; + + case TIOCGICOUNT: + ret = mp_get_count(state, (struct serial_icounter_struct *)arg); + break; + } + + if (ret != -ENOIOCTLCMD) + goto out; + + MP_STATE_LOCK(state); + switch (cmd) { + case TIOCSERGETLSR: /* Get line status register */ + ret = mp_get_lsr_info(state, (unsigned int *)arg); + break; + + default: { + struct sb_uart_port *port = state->port; + if (port->ops->ioctl) + ret = port->ops->ioctl(port, cmd, arg); + break; + } + } + + MP_STATE_UNLOCK(state); +out: + return ret; +} + +static void mp_set_termios(struct tty_struct *tty, struct MP_TERMIOS *old_termios) +{ + struct sb_uart_state *state = tty->driver_data; + unsigned long flags; + unsigned int cflag = tty->termios.c_cflag; + +#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + + if ((cflag ^ old_termios->c_cflag) == 0 && + RELEVANT_IFLAG(tty->termios.c_iflag ^ old_termios->c_iflag) == 0) + return; + + mp_change_speed(state, old_termios); + + if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) + uart_clear_mctrl(state->port, TIOCM_RTS | TIOCM_DTR); + + if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) { + unsigned int mask = TIOCM_DTR; + if (!(cflag & CRTSCTS) || + !test_bit(TTY_THROTTLED, &tty->flags)) + mask |= TIOCM_RTS; + uart_set_mctrl(state->port, mask); + } + + if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) { + spin_lock_irqsave(&state->port->lock, flags); + tty->hw_stopped = 0; + __mp_start(tty); + spin_unlock_irqrestore(&state->port->lock, flags); + } + + if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) { + spin_lock_irqsave(&state->port->lock, flags); + if (!(state->port->ops->get_mctrl(state->port) & TIOCM_CTS)) { + tty->hw_stopped = 1; + state->port->ops->stop_tx(state->port); + } + spin_unlock_irqrestore(&state->port->lock, flags); + } +} + +static void mp_close(struct tty_struct *tty, struct file *filp) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port; + + printk("mp_close!\n"); + if (!state || !state->port) + return; + + port = state->port; + + printk("close1 %d\n", __LINE__); + MP_STATE_LOCK(state); + + printk("close2 %d\n", __LINE__); + if (tty_hung_up_p(filp)) + goto done; + + printk("close3 %d\n", __LINE__); + if ((tty->count == 1) && (state->count != 1)) { + printk("mp_close: bad serial port count; tty->count is 1, " + "state->count is %d\n", state->count); + state->count = 1; + } + printk("close4 %d\n", __LINE__); + if (--state->count < 0) { + printk("rs_close: bad serial port count for ttyMP%d: %d\n", + port->line, state->count); + state->count = 0; + } + if (state->count) + goto done; + + tty->closing = 1; + + printk("close5 %d\n", __LINE__); + if (state->closing_wait != USF_CLOSING_WAIT_NONE) + tty_wait_until_sent(tty, state->closing_wait); + + printk("close6 %d\n", __LINE__); + if (state->info->flags & UIF_INITIALIZED) { + unsigned long flags; + spin_lock_irqsave(&port->lock, flags); + port->ops->stop_rx(port); + spin_unlock_irqrestore(&port->lock, flags); + mp_wait_until_sent(tty, port->timeout); + } + printk("close7 %d\n", __LINE__); + + mp_shutdown(state); + printk("close8 %d\n", __LINE__); + mp_flush_buffer(tty); + tty_ldisc_flush(tty); + tty->closing = 0; + state->info->tty = NULL; + if (state->info->blocked_open) + { + if (state->close_delay) + { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(state->close_delay); + } + } + else + { + mp_change_pm(state, 3); + } + printk("close8 %d\n", __LINE__); + + state->info->flags &= ~UIF_NORMAL_ACTIVE; + wake_up_interruptible(&state->info->open_wait); + +done: + printk("close done\n"); + MP_STATE_UNLOCK(state); + module_put(THIS_MODULE); +} + +static void mp_wait_until_sent(struct tty_struct *tty, int timeout) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + unsigned long char_time, expire; + + if (port->type == PORT_UNKNOWN || port->fifosize == 0) + return; + + char_time = (port->timeout - HZ/50) / port->fifosize; + char_time = char_time / 5; + if (char_time == 0) + char_time = 1; + if (timeout && timeout < char_time) + char_time = timeout; + + if (timeout == 0 || timeout > 2 * port->timeout) + timeout = 2 * port->timeout; + + expire = jiffies + timeout; + + while (!port->ops->tx_empty(port)) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(char_time); + if (signal_pending(current)) + break; + if (time_after(jiffies, expire)) + break; + } + set_current_state(TASK_RUNNING); /* might not be needed */ +} + +static void mp_hangup(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + + MP_STATE_LOCK(state); + if (state->info && state->info->flags & UIF_NORMAL_ACTIVE) { + mp_flush_buffer(tty); + mp_shutdown(state); + state->count = 0; + state->info->flags &= ~UIF_NORMAL_ACTIVE; + state->info->tty = NULL; + wake_up_interruptible(&state->info->open_wait); + wake_up_interruptible(&state->info->delta_msr_wait); + } + MP_STATE_UNLOCK(state); +} + +static void mp_update_termios(struct sb_uart_state *state) +{ + struct tty_struct *tty = state->info->tty; + struct sb_uart_port *port = state->port; + + if (!(tty->flags & (1 << TTY_IO_ERROR))) { + mp_change_speed(state, NULL); + + if (tty->termios.c_cflag & CBAUD) + uart_set_mctrl(port, TIOCM_DTR | TIOCM_RTS); + } +} + +static int mp_block_til_ready(struct file *filp, struct sb_uart_state *state) +{ + DECLARE_WAITQUEUE(wait, current); + struct sb_uart_info *info = state->info; + struct sb_uart_port *port = state->port; + unsigned int mctrl; + + info->blocked_open++; + state->count--; + + add_wait_queue(&info->open_wait, &wait); + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + + if (tty_hung_up_p(filp) || info->tty == NULL) + break; + + if (!(info->flags & UIF_INITIALIZED)) + break; + + if ((filp->f_flags & O_NONBLOCK) || + (info->tty->termios.c_cflag & CLOCAL) || + (info->tty->flags & (1 << TTY_IO_ERROR))) { + break; + } + + if (info->tty->termios.c_cflag & CBAUD) + uart_set_mctrl(port, TIOCM_DTR); + + spin_lock_irq(&port->lock); + port->ops->enable_ms(port); + mctrl = port->ops->get_mctrl(port); + spin_unlock_irq(&port->lock); + if (mctrl & TIOCM_CAR) + break; + + MP_STATE_UNLOCK(state); + schedule(); + MP_STATE_LOCK(state); + + if (signal_pending(current)) + break; + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&info->open_wait, &wait); + + state->count++; + info->blocked_open--; + + if (signal_pending(current)) + return -ERESTARTSYS; + + if (!info->tty || tty_hung_up_p(filp)) + return -EAGAIN; + + return 0; +} + +static struct sb_uart_state *uart_get(struct uart_driver *drv, int line) +{ + struct sb_uart_state *state; + + MP_MUTEX_LOCK(mp_mutex); + state = drv->state + line; + if (mutex_lock_interruptible(&state->mutex)) { + state = ERR_PTR(-ERESTARTSYS); + goto out; + } + state->count++; + if (!state->port) { + state->count--; + MP_STATE_UNLOCK(state); + state = ERR_PTR(-ENXIO); + goto out; + } + + if (!state->info) { + state->info = kmalloc(sizeof(struct sb_uart_info), GFP_KERNEL); + if (state->info) { + memset(state->info, 0, sizeof(struct sb_uart_info)); + init_waitqueue_head(&state->info->open_wait); + init_waitqueue_head(&state->info->delta_msr_wait); + + state->port->info = state->info; + + tasklet_init(&state->info->tlet, mp_tasklet_action, + (unsigned long)state); + } else { + state->count--; + MP_STATE_UNLOCK(state); + state = ERR_PTR(-ENOMEM); + } + } + +out: + MP_MUTEX_UNLOCK(mp_mutex); + return state; +} + +static int mp_open(struct tty_struct *tty, struct file *filp) +{ + struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state; + struct sb_uart_state *state; + int retval; + int line = tty->index; + struct mp_port *mtpt; + + retval = -ENODEV; + if (line >= tty->driver->num) + goto fail; + + state = uart_get(drv, line); + + mtpt = (struct mp_port *)state->port; + + if (IS_ERR(state)) { + retval = PTR_ERR(state); + goto fail; + } + + tty->driver_data = state; + tty->low_latency = (state->port->flags & UPF_LOW_LATENCY) ? 1 : 0; + tty->alt_speed = 0; + state->info->tty = tty; + + if (tty_hung_up_p(filp)) { + retval = -EAGAIN; + state->count--; + MP_STATE_UNLOCK(state); + goto fail; + } + + if (state->count == 1) + mp_change_pm(state, 0); + + retval = mp_startup(state, 0); + + if (retval == 0) + retval = mp_block_til_ready(filp, state); + MP_STATE_UNLOCK(state); + + if (retval == 0 && !(state->info->flags & UIF_NORMAL_ACTIVE)) { + state->info->flags |= UIF_NORMAL_ACTIVE; + + mp_update_termios(state); + } + + uart_clear_mctrl(state->port, TIOCM_RTS); + try_module_get(THIS_MODULE); +fail: + return retval; +} + + +static const char *mp_type(struct sb_uart_port *port) +{ + const char *str = NULL; + + if (port->ops->type) + str = port->ops->type(port); + + if (!str) + str = "unknown"; + + return str; +} + +static void mp_change_pm(struct sb_uart_state *state, int pm_state) +{ + struct sb_uart_port *port = state->port; + if (port->ops->pm) + port->ops->pm(port, pm_state, state->pm_state); + state->pm_state = pm_state; +} + +static inline void mp_report_port(struct uart_driver *drv, struct sb_uart_port *port) +{ + char address[64]; + + switch (port->iotype) { + case UPIO_PORT: + snprintf(address, sizeof(address),"I/O 0x%x", port->iobase); + break; + case UPIO_HUB6: + snprintf(address, sizeof(address),"I/O 0x%x offset 0x%x", port->iobase, port->hub6); + break; + case UPIO_MEM: + snprintf(address, sizeof(address),"MMIO 0x%lx", port->mapbase); + break; + default: + snprintf(address, sizeof(address),"*unknown*" ); + strlcpy(address, "*unknown*", sizeof(address)); + break; + } + + printk( "%s%d at %s (irq = %d) is a %s\n", + drv->dev_name, port->line, address, port->irq, mp_type(port)); + +} + +static void mp_configure_port(struct uart_driver *drv, struct sb_uart_state *state, struct sb_uart_port *port) +{ + unsigned int flags; + + + if (!port->iobase && !port->mapbase && !port->membase) + { + DPRINTK("%s error \n",__FUNCTION__); + return; + } + flags = UART_CONFIG_TYPE; + if (port->flags & UPF_AUTO_IRQ) + flags |= UART_CONFIG_IRQ; + if (port->flags & UPF_BOOT_AUTOCONF) { + port->type = PORT_UNKNOWN; + port->ops->config_port(port, flags); + } + + if (port->type != PORT_UNKNOWN) { + unsigned long flags; + + mp_report_port(drv, port); + + spin_lock_irqsave(&port->lock, flags); + port->ops->set_mctrl(port, 0); + spin_unlock_irqrestore(&port->lock, flags); + + mp_change_pm(state, 3); + } +} + +static void mp_unconfigure_port(struct uart_driver *drv, struct sb_uart_state *state) +{ + struct sb_uart_port *port = state->port; + struct sb_uart_info *info = state->info; + + if (info && info->tty) + tty_hangup(info->tty); + + MP_STATE_LOCK(state); + + state->info = NULL; + + if (port->type != PORT_UNKNOWN) + port->ops->release_port(port); + + port->type = PORT_UNKNOWN; + + if (info) { + tasklet_kill(&info->tlet); + kfree(info); + } + + MP_STATE_UNLOCK(state); +} +static struct tty_operations mp_ops = { + .open = mp_open, + .close = mp_close, + .write = mp_write, + .put_char = mp_put_char, + .flush_chars = mp_put_chars, + .write_room = mp_write_room, + .chars_in_buffer= mp_chars_in_buffer, + .flush_buffer = mp_flush_buffer, + .ioctl = mp_ioctl, + .throttle = mp_throttle, + .unthrottle = mp_unthrottle, + .send_xchar = mp_send_xchar, + .set_termios = mp_set_termios, + .stop = mp_stop, + .start = mp_start, + .hangup = mp_hangup, + .break_ctl = mp_break_ctl, + .wait_until_sent= mp_wait_until_sent, +#ifdef CONFIG_PROC_FS + .proc_fops = NULL, +#endif + .tiocmget = mp_tiocmget, + .tiocmset = mp_tiocmset, +}; + +static int mp_register_driver(struct uart_driver *drv) +{ + struct tty_driver *normal = NULL; + int i, retval; + + drv->state = kmalloc(sizeof(struct sb_uart_state) * drv->nr, GFP_KERNEL); + retval = -ENOMEM; + if (!drv->state) + { + printk("SB PCI Error: Kernel memory allocation error!\n"); + goto out; + } + memset(drv->state, 0, sizeof(struct sb_uart_state) * drv->nr); + + normal = alloc_tty_driver(drv->nr); + if (!normal) + { + printk("SB PCI Error: tty allocation error!\n"); + goto out; + } + + drv->tty_driver = normal; + + normal->owner = drv->owner; + normal->magic = TTY_DRIVER_MAGIC; + normal->driver_name = drv->driver_name; + normal->name = drv->dev_name; + normal->major = drv->major; + normal->minor_start = drv->minor; + + normal->num = MAX_MP_PORT ; + + normal->type = TTY_DRIVER_TYPE_SERIAL; + normal->subtype = SERIAL_TYPE_NORMAL; + normal->init_termios = tty_std_termios; + normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + normal->driver_state = drv; + + tty_set_operations(normal, &mp_ops); + +for (i = 0; i < drv->nr; i++) { + struct sb_uart_state *state = drv->state + i; + + state->close_delay = 500; + state->closing_wait = 30000; + + mutex_init(&state->mutex); + } + + retval = tty_register_driver(normal); +out: + if (retval < 0) { + printk("Register tty driver Fail!\n"); + put_tty_driver(normal); + kfree(drv->state); + } + + return retval; +} + +void mp_unregister_driver(struct uart_driver *drv) +{ + struct tty_driver *normal = NULL; + + normal = drv->tty_driver; + + if (!normal) + { + return; + } + + tty_unregister_driver(normal); + put_tty_driver(normal); + drv->tty_driver = NULL; + + + if (drv->state) + { + kfree(drv->state); + } + +} + +static int mp_add_one_port(struct uart_driver *drv, struct sb_uart_port *port) +{ + struct sb_uart_state *state; + int ret = 0; + + + if (port->line >= drv->nr) + return -EINVAL; + + state = drv->state + port->line; + + MP_MUTEX_LOCK(mp_mutex); + if (state->port) { + ret = -EINVAL; + goto out; + } + + state->port = port; + + spin_lock_init(&port->lock); + port->cons = drv->cons; + port->info = state->info; + + mp_configure_port(drv, state, port); + + tty_register_device(drv->tty_driver, port->line, port->dev); + +out: + MP_MUTEX_UNLOCK(mp_mutex); + + + return ret; +} + +static int mp_remove_one_port(struct uart_driver *drv, struct sb_uart_port *port) +{ + struct sb_uart_state *state = drv->state + port->line; + + if (state->port != port) + printk(KERN_ALERT "Removing wrong port: %p != %p\n", + state->port, port); + + MP_MUTEX_LOCK(mp_mutex); + + tty_unregister_device(drv->tty_driver, port->line); + + mp_unconfigure_port(drv, state); + state->port = NULL; + MP_MUTEX_UNLOCK(mp_mutex); + + return 0; +} + +static void autoconfig(struct mp_port *mtpt, unsigned int probeflags) +{ + unsigned char status1, scratch, scratch2, scratch3; + unsigned char save_lcr, save_mcr; + unsigned long flags; + + unsigned char u_type; + unsigned char b_ret = 0; + + if (!mtpt->port.iobase && !mtpt->port.mapbase && !mtpt->port.membase) + return; + + DEBUG_AUTOCONF("ttyMP%d: autoconf (0x%04x, 0x%p): ", + mtpt->port.line, mtpt->port.iobase, mtpt->port.membase); + + spin_lock_irqsave(&mtpt->port.lock, flags); + + if (!(mtpt->port.flags & UPF_BUGGY_UART)) { + scratch = serial_inp(mtpt, UART_IER); + serial_outp(mtpt, UART_IER, 0); +#ifdef __i386__ + outb(0xff, 0x080); +#endif + scratch2 = serial_inp(mtpt, UART_IER) & 0x0f; + serial_outp(mtpt, UART_IER, 0x0F); +#ifdef __i386__ + outb(0, 0x080); +#endif + scratch3 = serial_inp(mtpt, UART_IER) & 0x0F; + serial_outp(mtpt, UART_IER, scratch); + if (scratch2 != 0 || scratch3 != 0x0F) { + DEBUG_AUTOCONF("IER test failed (%02x, %02x) ", + scratch2, scratch3); + goto out; + } + } + + save_mcr = serial_in(mtpt, UART_MCR); + save_lcr = serial_in(mtpt, UART_LCR); + + if (!(mtpt->port.flags & UPF_SKIP_TEST)) { + serial_outp(mtpt, UART_MCR, UART_MCR_LOOP | 0x0A); + status1 = serial_inp(mtpt, UART_MSR) & 0xF0; + serial_outp(mtpt, UART_MCR, save_mcr); + if (status1 != 0x90) { + DEBUG_AUTOCONF("LOOP test failed (%02x) ", + status1); + goto out; + } + } + + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, 0); + serial_outp(mtpt, UART_LCR, 0); + + serial_outp(mtpt, UART_FCR, UART_FCR_ENABLE_FIFO); + scratch = serial_in(mtpt, UART_IIR) >> 6; + + DEBUG_AUTOCONF("iir=%d ", scratch); + if(mtpt->device->nr_ports >= 8) + b_ret = read_option_register(mtpt,(MP_OPTR_DIR0 + ((mtpt->port.line)/8))); + else + b_ret = read_option_register(mtpt,MP_OPTR_DIR0); + u_type = (b_ret & 0xf0) >> 4; + if(mtpt->port.type == PORT_UNKNOWN ) + { + switch (u_type) + { + case DIR_UART_16C550: + mtpt->port.type = PORT_16C55X; + break; + case DIR_UART_16C1050: + mtpt->port.type = PORT_16C105X; + break; + case DIR_UART_16C1050A: + if (mtpt->port.line < 2) + { + mtpt->port.type = PORT_16C105XA; + } + else + { + if (mtpt->device->device_id & 0x50) + { + mtpt->port.type = PORT_16C55X; + } + else + { + mtpt->port.type = PORT_16C105X; + } + } + break; + default: + mtpt->port.type = PORT_UNKNOWN; + break; + } + } + + if(mtpt->port.type == PORT_UNKNOWN ) + { +printk("unknow2\n"); + switch (scratch) { + case 0: + case 1: + mtpt->port.type = PORT_UNKNOWN; + break; + case 2: + case 3: + mtpt->port.type = PORT_16C55X; + break; + } + } + + serial_outp(mtpt, UART_LCR, save_lcr); + + mtpt->port.fifosize = uart_config[mtpt->port.type].dfl_xmit_fifo_size; + mtpt->capabilities = uart_config[mtpt->port.type].flags; + + if (mtpt->port.type == PORT_UNKNOWN) + goto out; + serial_outp(mtpt, UART_MCR, save_mcr); + serial_outp(mtpt, UART_FCR, (UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | + UART_FCR_CLEAR_XMIT)); + serial_outp(mtpt, UART_FCR, 0); + (void)serial_in(mtpt, UART_RX); + serial_outp(mtpt, UART_IER, 0); + +out: + spin_unlock_irqrestore(&mtpt->port.lock, flags); + DEBUG_AUTOCONF("type=%s\n", uart_config[mtpt->port.type].name); +} + +static void autoconfig_irq(struct mp_port *mtpt) +{ + unsigned char save_mcr, save_ier; + unsigned long irqs; + int irq; + + /* forget possible initially masked and pending IRQ */ + probe_irq_off(probe_irq_on()); + save_mcr = serial_inp(mtpt, UART_MCR); + save_ier = serial_inp(mtpt, UART_IER); + serial_outp(mtpt, UART_MCR, UART_MCR_OUT1 | UART_MCR_OUT2); + + irqs = probe_irq_on(); + serial_outp(mtpt, UART_MCR, 0); + serial_outp(mtpt, UART_MCR, + UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2); + + serial_outp(mtpt, UART_IER, 0x0f); /* enable all intrs */ + (void)serial_inp(mtpt, UART_LSR); + (void)serial_inp(mtpt, UART_RX); + (void)serial_inp(mtpt, UART_IIR); + (void)serial_inp(mtpt, UART_MSR); + serial_outp(mtpt, UART_TX, 0xFF); + irq = probe_irq_off(irqs); + + serial_outp(mtpt, UART_MCR, save_mcr); + serial_outp(mtpt, UART_IER, save_ier); + + mtpt->port.irq = (irq > 0) ? irq : 0; +} + +static void multi_stop_tx(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + + if (mtpt->ier & UART_IER_THRI) { + mtpt->ier &= ~UART_IER_THRI; + serial_out(mtpt, UART_IER, mtpt->ier); + } + + tasklet_schedule(&port->info->tlet); +} + +static void multi_start_tx(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + + if (!(mtpt->ier & UART_IER_THRI)) { + mtpt->ier |= UART_IER_THRI; + serial_out(mtpt, UART_IER, mtpt->ier); + } +} + +static void multi_stop_rx(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + + mtpt->ier &= ~UART_IER_RLSI; + mtpt->port.read_status_mask &= ~UART_LSR_DR; + serial_out(mtpt, UART_IER, mtpt->ier); +} + +static void multi_enable_ms(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + + mtpt->ier |= UART_IER_MSI; + serial_out(mtpt, UART_IER, mtpt->ier); +} + + +static _INLINE_ void receive_chars(struct mp_port *mtpt, int *status ) +{ + struct tty_struct *tty = mtpt->port.info->tty; + unsigned char lsr = *status; + int max_count = 256; + unsigned char ch; + char flag; + + //lsr &= mtpt->port.read_status_mask; + + do { + if ((lsr & UART_LSR_PE) && (mtpt->port.mdmode & MDMODE_ENABLE)) + { + ch = serial_inp(mtpt, UART_RX); + } + else if (lsr & UART_LSR_SPECIAL) + { + flag = 0; + ch = serial_inp(mtpt, UART_RX); + + if (lsr & UART_LSR_BI) + { + + mtpt->port.icount.brk++; + flag = TTY_BREAK; + + if (sb_uart_handle_break(&mtpt->port)) + goto ignore_char; + } + if (lsr & UART_LSR_PE) + { + mtpt->port.icount.parity++; + flag = TTY_PARITY; + } + if (lsr & UART_LSR_FE) + { + mtpt->port.icount.frame++; + flag = TTY_FRAME; + } + if (lsr & UART_LSR_OE) + { + mtpt->port.icount.overrun++; + flag = TTY_OVERRUN; + } + tty_insert_flip_char(tty, ch, flag); + } + else + { + ch = serial_inp(mtpt, UART_RX); + tty_insert_flip_char(tty, ch, 0); + } +ignore_char: + lsr = serial_inp(mtpt, UART_LSR); + } while ((lsr & UART_LSR_DR) && (max_count-- > 0)); + + tty_flip_buffer_push(tty); +} + + + + +static _INLINE_ void transmit_chars(struct mp_port *mtpt) +{ + struct circ_buf *xmit = &mtpt->port.info->xmit; + int count; + + if (mtpt->port.x_char) { + serial_outp(mtpt, UART_TX, mtpt->port.x_char); + mtpt->port.icount.tx++; + mtpt->port.x_char = 0; + return; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(&mtpt->port)) { + multi_stop_tx(&mtpt->port); + return; + } + + count = uart_circ_chars_pending(xmit); + + if(count > mtpt->port.fifosize) + { + count = mtpt->port.fifosize; + } + + printk("[%d] mdmode: %x\n", mtpt->port.line, mtpt->port.mdmode); + do { +#if 0 + /* check multi-drop mode */ + if ((mtpt->port.mdmode & (MDMODE_ENABLE | MDMODE_ADDR)) == (MDMODE_ENABLE | MDMODE_ADDR)) + { + printk("send address\n"); + /* send multi-drop address */ + serial_out(mtpt, UART_SCR, xmit->buf[xmit->tail]); + } + else +#endif + { + serial_out(mtpt, UART_TX, xmit->buf[xmit->tail]); + } + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + mtpt->port.icount.tx++; + } while (--count > 0); +} + + + +static _INLINE_ void check_modem_status(struct mp_port *mtpt) +{ + int status; + + status = serial_in(mtpt, UART_MSR); + + if ((status & UART_MSR_ANY_DELTA) == 0) + return; + + if (status & UART_MSR_TERI) + mtpt->port.icount.rng++; + if (status & UART_MSR_DDSR) + mtpt->port.icount.dsr++; + if (status & UART_MSR_DDCD) + sb_uart_handle_dcd_change(&mtpt->port, status & UART_MSR_DCD); + if (status & UART_MSR_DCTS) + sb_uart_handle_cts_change(&mtpt->port, status & UART_MSR_CTS); + + wake_up_interruptible(&mtpt->port.info->delta_msr_wait); +} + +static inline void multi_handle_port(struct mp_port *mtpt) +{ + unsigned int status = serial_inp(mtpt, UART_LSR); + + //printk("lsr: %x\n", status); + + if ((status & UART_LSR_DR) || (status & UART_LSR_SPECIAL)) + receive_chars(mtpt, &status); + check_modem_status(mtpt); + if (status & UART_LSR_THRE) + { + if ((mtpt->port.type == PORT_16C105X) + || (mtpt->port.type == PORT_16C105XA)) + transmit_chars(mtpt); + else + { + if (mtpt->interface >= RS485NE) + uart_set_mctrl(&mtpt->port, TIOCM_RTS); + + transmit_chars(mtpt); + + + if (mtpt->interface >= RS485NE) + { + while((status=serial_in(mtpt,UART_LSR) &0x60)!=0x60); + uart_clear_mctrl(&mtpt->port, TIOCM_RTS); + } + } + } +} + + + +static irqreturn_t multi_interrupt(int irq, void *dev_id) +{ + struct irq_info *iinfo = dev_id; + struct list_head *lhead, *end = NULL; + int pass_counter = 0; + + + spin_lock(&iinfo->lock); + + lhead = iinfo->head; + do { + struct mp_port *mtpt; + unsigned int iir; + + mtpt = list_entry(lhead, struct mp_port, list); + + iir = serial_in(mtpt, UART_IIR); + printk("intrrupt! port %d, iir 0x%x\n", mtpt->port.line, iir); //wlee + if (!(iir & UART_IIR_NO_INT)) + { + printk("interrupt handle\n"); + spin_lock(&mtpt->port.lock); + multi_handle_port(mtpt); + spin_unlock(&mtpt->port.lock); + + end = NULL; + } else if (end == NULL) + end = lhead; + + lhead = lhead->next; + if (lhead == iinfo->head && pass_counter++ > PASS_LIMIT) + { + printk(KERN_ERR "multi: too much work for " + "irq%d\n", irq); + printk( "multi: too much work for " + "irq%d\n", irq); + break; + } + } while (lhead != end); + + spin_unlock(&iinfo->lock); + + + return IRQ_HANDLED; +} + +static void serial_do_unlink(struct irq_info *i, struct mp_port *mtpt) +{ + spin_lock_irq(&i->lock); + + if (!list_empty(i->head)) { + if (i->head == &mtpt->list) + i->head = i->head->next; + list_del(&mtpt->list); + } else { + i->head = NULL; + } + + spin_unlock_irq(&i->lock); +} + +static int serial_link_irq_chain(struct mp_port *mtpt) +{ + struct irq_info *i = irq_lists + mtpt->port.irq; + int ret, irq_flags = mtpt->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0; + spin_lock_irq(&i->lock); + + if (i->head) { + list_add(&mtpt->list, i->head); + spin_unlock_irq(&i->lock); + + ret = 0; + } else { + INIT_LIST_HEAD(&mtpt->list); + i->head = &mtpt->list; + spin_unlock_irq(&i->lock); + + ret = request_irq(mtpt->port.irq, multi_interrupt, + irq_flags, "serial", i); + if (ret < 0) + serial_do_unlink(i, mtpt); + } + + return ret; +} + + + + +static void serial_unlink_irq_chain(struct mp_port *mtpt) +{ + struct irq_info *i = irq_lists + mtpt->port.irq; + + if (list_empty(i->head)) + { + free_irq(mtpt->port.irq, i); + } + serial_do_unlink(i, mtpt); +} + +static void multi_timeout(unsigned long data) +{ + struct mp_port *mtpt = (struct mp_port *)data; + + + spin_lock(&mtpt->port.lock); + multi_handle_port(mtpt); + spin_unlock(&mtpt->port.lock); + + mod_timer(&mtpt->timer, jiffies+1 ); +} + +static unsigned int multi_tx_empty(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned long flags; + unsigned int ret; + + spin_lock_irqsave(&mtpt->port.lock, flags); + ret = serial_in(mtpt, UART_LSR) & UART_LSR_TEMT ? TIOCSER_TEMT : 0; + spin_unlock_irqrestore(&mtpt->port.lock, flags); + + return ret; +} + + +static unsigned int multi_get_mctrl(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned char status; + unsigned int ret; + + status = serial_in(mtpt, UART_MSR); + + ret = 0; + if (status & UART_MSR_DCD) + ret |= TIOCM_CAR; + if (status & UART_MSR_RI) + ret |= TIOCM_RNG; + if (status & UART_MSR_DSR) + ret |= TIOCM_DSR; + if (status & UART_MSR_CTS) + ret |= TIOCM_CTS; + return ret; +} + +static void multi_set_mctrl(struct sb_uart_port *port, unsigned int mctrl) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned char mcr = 0; + + mctrl &= 0xff; + + if (mctrl & TIOCM_RTS) + mcr |= UART_MCR_RTS; + if (mctrl & TIOCM_DTR) + mcr |= UART_MCR_DTR; + if (mctrl & TIOCM_OUT1) + mcr |= UART_MCR_OUT1; + if (mctrl & TIOCM_OUT2) + mcr |= UART_MCR_OUT2; + if (mctrl & TIOCM_LOOP) + mcr |= UART_MCR_LOOP; + + + serial_out(mtpt, UART_MCR, mcr); +} + + +static void multi_break_ctl(struct sb_uart_port *port, int break_state) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned long flags; + + spin_lock_irqsave(&mtpt->port.lock, flags); + if (break_state == -1) + mtpt->lcr |= UART_LCR_SBC; + else + mtpt->lcr &= ~UART_LCR_SBC; + serial_out(mtpt, UART_LCR, mtpt->lcr); + spin_unlock_irqrestore(&mtpt->port.lock, flags); +} + + + +static int multi_startup(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned long flags; + int retval; + + mtpt->capabilities = uart_config[mtpt->port.type].flags; + mtpt->mcr = 0; + + if (mtpt->capabilities & UART_CLEAR_FIFO) { + serial_outp(mtpt, UART_FCR, UART_FCR_ENABLE_FIFO); + serial_outp(mtpt, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + serial_outp(mtpt, UART_FCR, 0); + } + + (void) serial_inp(mtpt, UART_LSR); + (void) serial_inp(mtpt, UART_RX); + (void) serial_inp(mtpt, UART_IIR); + (void) serial_inp(mtpt, UART_MSR); + //test-wlee 9-bit disable + serial_outp(mtpt, UART_MSR, 0); + + + if (!(mtpt->port.flags & UPF_BUGGY_UART) && + (serial_inp(mtpt, UART_LSR) == 0xff)) { + printk("ttyS%d: LSR safety check engaged!\n", mtpt->port.line); + //return -ENODEV; + } + + if ((!is_real_interrupt(mtpt->port.irq)) || (mtpt->poll_type==TYPE_POLL)) { + unsigned int timeout = mtpt->port.timeout; + + timeout = timeout > 6 ? (timeout / 2 - 2) : 1; + + mtpt->timer.data = (unsigned long)mtpt; + mod_timer(&mtpt->timer, jiffies + timeout); + } + else + { + retval = serial_link_irq_chain(mtpt); + if (retval) + return retval; + } + + serial_outp(mtpt, UART_LCR, UART_LCR_WLEN8); + + spin_lock_irqsave(&mtpt->port.lock, flags); + if ((is_real_interrupt(mtpt->port.irq))||(mtpt->poll_type==TYPE_INTERRUPT)) + mtpt->port.mctrl |= TIOCM_OUT2; + + multi_set_mctrl(&mtpt->port, mtpt->port.mctrl); + spin_unlock_irqrestore(&mtpt->port.lock, flags); + + + mtpt->ier = UART_IER_RLSI | UART_IER_RDI; + serial_outp(mtpt, UART_IER, mtpt->ier); + + (void) serial_inp(mtpt, UART_LSR); + (void) serial_inp(mtpt, UART_RX); + (void) serial_inp(mtpt, UART_IIR); + (void) serial_inp(mtpt, UART_MSR); + + return 0; +} + + + +static void multi_shutdown(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned long flags; + + + mtpt->ier = 0; + serial_outp(mtpt, UART_IER, 0); + + spin_lock_irqsave(&mtpt->port.lock, flags); + mtpt->port.mctrl &= ~TIOCM_OUT2; + + multi_set_mctrl(&mtpt->port, mtpt->port.mctrl); + spin_unlock_irqrestore(&mtpt->port.lock, flags); + + serial_out(mtpt, UART_LCR, serial_inp(mtpt, UART_LCR) & ~UART_LCR_SBC); + serial_outp(mtpt, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | + UART_FCR_CLEAR_XMIT); + serial_outp(mtpt, UART_FCR, 0); + + + (void) serial_in(mtpt, UART_RX); + + if ((!is_real_interrupt(mtpt->port.irq))||(mtpt->poll_type==TYPE_POLL)) + { + del_timer_sync(&mtpt->timer); + } + else + { + serial_unlink_irq_chain(mtpt); + } +} + + + +static unsigned int multi_get_divisor(struct sb_uart_port *port, unsigned int baud) +{ + unsigned int quot; + + if ((port->flags & UPF_MAGIC_MULTIPLIER) && + baud == (port->uartclk/4)) + quot = 0x8001; + else if ((port->flags & UPF_MAGIC_MULTIPLIER) && + baud == (port->uartclk/8)) + quot = 0x8002; + else + quot = sb_uart_get_divisor(port, baud); + + return quot; +} + + + + +static void multi_set_termios(struct sb_uart_port *port, struct MP_TERMIOS *termios, struct MP_TERMIOS *old) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned char cval, fcr = 0; + unsigned long flags; + unsigned int baud, quot; + + switch (termios->c_cflag & CSIZE) { + case CS5: + cval = 0x00; + break; + case CS6: + cval = 0x01; + break; + case CS7: + cval = 0x02; + break; + default: + case CS8: + cval = 0x03; + break; + } + + if (termios->c_cflag & CSTOPB) + cval |= 0x04; + if (termios->c_cflag & PARENB) + cval |= UART_LCR_PARITY; + if (!(termios->c_cflag & PARODD)) + cval |= UART_LCR_EPAR; + +#ifdef CMSPAR + if (termios->c_cflag & CMSPAR) + cval |= UART_LCR_SPAR; +#endif + + baud = sb_uart_get_baud_rate(port, termios, old, 0, port->uartclk/16); + quot = multi_get_divisor(port, baud); + + if (mtpt->capabilities & UART_USE_FIFO) { + //if (baud < 2400) + // fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1; + //else + // fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_8; + + // fcr = UART_FCR_ENABLE_FIFO | 0x90; + fcr = fcr_arr[mtpt->port.line]; + } + + spin_lock_irqsave(&mtpt->port.lock, flags); + + sb_uart_update_timeout(port, termios->c_cflag, baud); + + mtpt->port.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR; + if (termios->c_iflag & INPCK) + mtpt->port.read_status_mask |= UART_LSR_FE | UART_LSR_PE; + if (termios->c_iflag & (BRKINT | PARMRK)) + mtpt->port.read_status_mask |= UART_LSR_BI; + + mtpt->port.ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + mtpt->port.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; + if (termios->c_iflag & IGNBRK) { + mtpt->port.ignore_status_mask |= UART_LSR_BI; + if (termios->c_iflag & IGNPAR) + mtpt->port.ignore_status_mask |= UART_LSR_OE; + } + + if ((termios->c_cflag & CREAD) == 0) + mtpt->port.ignore_status_mask |= UART_LSR_DR; + + mtpt->ier &= ~UART_IER_MSI; + if (UART_ENABLE_MS(&mtpt->port, termios->c_cflag)) + mtpt->ier |= UART_IER_MSI; + + serial_out(mtpt, UART_IER, mtpt->ier); + + if (mtpt->capabilities & UART_STARTECH) { + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, + termios->c_cflag & CRTSCTS ? UART_EFR_CTS :0); + } + + serial_outp(mtpt, UART_LCR, cval | UART_LCR_DLAB);/* set DLAB */ + + serial_outp(mtpt, UART_DLL, quot & 0xff); /* LS of divisor */ + serial_outp(mtpt, UART_DLM, quot >> 8); /* MS of divisor */ + + serial_outp(mtpt, UART_LCR, cval); /* reset DLAB */ + mtpt->lcr = cval; /* Save LCR */ + + if (fcr & UART_FCR_ENABLE_FIFO) { + /* emulated UARTs (Lucent Venus 167x) need two steps */ + serial_outp(mtpt, UART_FCR, UART_FCR_ENABLE_FIFO); + } + + serial_outp(mtpt, UART_FCR, fcr); /* set fcr */ + + + if ((mtpt->port.type == PORT_16C105X) + || (mtpt->port.type == PORT_16C105XA)) + { + if(deep[mtpt->port.line]!=0) + set_deep_fifo(port, ENABLE); + + if (mtpt->interface != RS232) + set_auto_rts(port,mtpt->interface); + + } + else + { + if (mtpt->interface >= RS485NE) + { + uart_clear_mctrl(&mtpt->port, TIOCM_RTS); + } + } + + if(mtpt->device->device_id == PCI_DEVICE_ID_MP4M) + { + SendATCommand(mtpt); + printk("SendATCommand\n"); + } + multi_set_mctrl(&mtpt->port, mtpt->port.mctrl); + spin_unlock_irqrestore(&mtpt->port.lock, flags); +} + +static void multi_pm(struct sb_uart_port *port, unsigned int state, unsigned int oldstate) +{ + struct mp_port *mtpt = (struct mp_port *)port; + if (state) { + if (mtpt->capabilities & UART_STARTECH) { + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, UART_EFR_ECB); + serial_outp(mtpt, UART_LCR, 0); + serial_outp(mtpt, UART_IER, UART_IERX_SLEEP); + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, 0); + serial_outp(mtpt, UART_LCR, 0); + } + + if (mtpt->pm) + mtpt->pm(port, state, oldstate); + } + else + { + if (mtpt->capabilities & UART_STARTECH) { + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, UART_EFR_ECB); + serial_outp(mtpt, UART_LCR, 0); + serial_outp(mtpt, UART_IER, 0); + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, 0); + serial_outp(mtpt, UART_LCR, 0); + } + + if (mtpt->pm) + mtpt->pm(port, state, oldstate); + } +} + +static void multi_release_std_resource(struct mp_port *mtpt) +{ + unsigned int size = 8 << mtpt->port.regshift; + + switch (mtpt->port.iotype) { + case UPIO_MEM: + if (!mtpt->port.mapbase) + break; + + if (mtpt->port.flags & UPF_IOREMAP) { + iounmap(mtpt->port.membase); + mtpt->port.membase = NULL; + } + + release_mem_region(mtpt->port.mapbase, size); + break; + + case UPIO_HUB6: + case UPIO_PORT: + release_region(mtpt->port.iobase,size); + break; + } +} + +static void multi_release_port(struct sb_uart_port *port) +{ +} + +static int multi_request_port(struct sb_uart_port *port) +{ + return 0; +} + +static void multi_config_port(struct sb_uart_port *port, int flags) +{ + struct mp_port *mtpt = (struct mp_port *)port; + int probeflags = PROBE_ANY; + + if (flags & UART_CONFIG_TYPE) + autoconfig(mtpt, probeflags); + if (mtpt->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ) + autoconfig_irq(mtpt); + + if (mtpt->port.type == PORT_UNKNOWN) + multi_release_std_resource(mtpt); +} + +static int multi_verify_port(struct sb_uart_port *port, struct serial_struct *ser) +{ + if (ser->irq >= NR_IRQS || ser->irq < 0 || + ser->baud_base < 9600 || ser->type < PORT_UNKNOWN || + ser->type == PORT_STARTECH) + return -EINVAL; + return 0; +} + +static const char * multi_type(struct sb_uart_port *port) +{ + int type = port->type; + + if (type >= ARRAY_SIZE(uart_config)) + type = 0; + return uart_config[type].name; +} + +static struct sb_uart_ops multi_pops = { + .tx_empty = multi_tx_empty, + .set_mctrl = multi_set_mctrl, + .get_mctrl = multi_get_mctrl, + .stop_tx = multi_stop_tx, + .start_tx = multi_start_tx, + .stop_rx = multi_stop_rx, + .enable_ms = multi_enable_ms, + .break_ctl = multi_break_ctl, + .startup = multi_startup, + .shutdown = multi_shutdown, + .set_termios = multi_set_termios, + .pm = multi_pm, + .type = multi_type, + .release_port = multi_release_port, + .request_port = multi_request_port, + .config_port = multi_config_port, + .verify_port = multi_verify_port, +}; + +static struct uart_driver multi_reg = { + .owner = THIS_MODULE, + .driver_name = "goldel_tulip", + .dev_name = "ttyMP", + .major = SB_TTY_MP_MAJOR, + .minor = 0, + .nr = MAX_MP_PORT, + .cons = NULL, +}; + +static void __init multi_init_ports(void) +{ + struct mp_port *mtpt; + static int first = 1; + int i,j,k; + unsigned char osc; + unsigned char b_ret = 0; + static struct mp_device_t * sbdev; + + if (!first) + return; + first = 0; + + mtpt = multi_ports; + + for (k=0;knr_ports; i++, mtpt++) + { + mtpt->device = sbdev; + mtpt->port.iobase = sbdev->uart_access_addr + 8*i; + mtpt->port.irq = sbdev->irq; + if ( ((sbdev->device_id == PCI_DEVICE_ID_MP4)&&(sbdev->revision==0x91))) + mtpt->interface_config_addr = sbdev->option_reg_addr + 0x08 + i; + else if (sbdev->revision == 0xc0) + mtpt->interface_config_addr = sbdev->option_reg_addr + 0x08 + (i & 0x1); + else + mtpt->interface_config_addr = sbdev->option_reg_addr + 0x08 + i/8; + + mtpt->option_base_addr = sbdev->option_reg_addr; + + mtpt->poll_type = sbdev->poll_type; + + mtpt->port.uartclk = BASE_BAUD * 16; + + /* get input clock infomation */ + osc = inb(sbdev->option_reg_addr + MP_OPTR_DIR0 + i/8) & 0x0F; + if (osc==0x0f) + osc = 0; + for(j=0;jport.uartclk *= 2; + mtpt->port.flags |= STD_COM_FLAGS | UPF_SHARE_IRQ ; + mtpt->port.iotype = UPIO_PORT; + mtpt->port.ops = &multi_pops; + + if (sbdev->revision == 0xc0) + { + /* for SB16C1053APCI */ + b_ret = sb1053a_get_interface(mtpt, i); + } + else + { + b_ret = read_option_register(mtpt,(MP_OPTR_IIR0 + i/8)); + printk("IIR_RET = %x\n",b_ret); + } + + if(IIR_RS232 == (b_ret & IIR_RS232)) + { + mtpt->interface = RS232; + } + if(IIR_RS422 == (b_ret & IIR_RS422)) + { + mtpt->interface = RS422PTP; + } + if(IIR_RS485 == (b_ret & IIR_RS485)) + { + mtpt->interface = RS485NE; + } + } + } +} + +static void __init multi_register_ports(struct uart_driver *drv) +{ + int i; + + multi_init_ports(); + + for (i = 0; i < NR_PORTS; i++) { + struct mp_port *mtpt = &multi_ports[i]; + + mtpt->port.line = i; + mtpt->port.ops = &multi_pops; + init_timer(&mtpt->timer); + mtpt->timer.function = multi_timeout; + mp_add_one_port(drv, &mtpt->port); + } +} + +/** + * pci_remap_base - remap BAR value of pci device + * + * PARAMETERS + * pcidev - pci_dev structure address + * offset - BAR offset PCI_BASE_ADDRESS_0 ~ PCI_BASE_ADDRESS_4 + * address - address to be changed BAR value + * size - size of address space + * + * RETURNS + * If this function performs successful, it returns 0. Otherwise, It returns -1. + */ +static int pci_remap_base(struct pci_dev *pcidev, unsigned int offset, + unsigned int address, unsigned int size) +{ +#if 0 + struct resource *root; + unsigned index = (offset - 0x10) >> 2; +#endif + + pci_write_config_dword(pcidev, offset, address); +#if 0 + root = pcidev->resource[index].parent; + release_resource(&pcidev->resource[index]); + address &= ~0x1; + pcidev->resource[index].start = address; + pcidev->resource[index].end = address + size - 1; + + if (request_resource(root, &pcidev->resource[index]) != NULL) + { + printk(KERN_ERR "pci remap conflict!! 0x%x\n", address); + return (-1); + } +#endif + + return (0); +} + +static int init_mp_dev(struct pci_dev *pcidev, mppcibrd_t brd) +{ + static struct mp_device_t * sbdev = mp_devs; + unsigned long addr = 0; + int j; + struct resource * ret = NULL; + + sbdev->device_id = brd.device_id; + pci_read_config_byte(pcidev, PCI_CLASS_REVISION, &(sbdev->revision)); + sbdev->name = brd.name; + sbdev->uart_access_addr = pcidev->resource[0].start & PCI_BASE_ADDRESS_IO_MASK; + + /* check revision. The SB16C1053APCI's option i/o address is BAR4 */ + if (sbdev->revision == 0xc0) + { + /* SB16C1053APCI */ + sbdev->option_reg_addr = pcidev->resource[4].start & PCI_BASE_ADDRESS_IO_MASK; + } + else + { + sbdev->option_reg_addr = pcidev->resource[1].start & PCI_BASE_ADDRESS_IO_MASK; + } +#if 1 + if (sbdev->revision == 0xc0) + { + outb(0x00, sbdev->option_reg_addr + MP_OPTR_GPOCR); + inb(sbdev->option_reg_addr + MP_OPTR_GPOCR); + outb(0x83, sbdev->option_reg_addr + MP_OPTR_GPOCR); + } +#endif + + sbdev->irq = pcidev->irq; + + if ((brd.device_id & 0x0800) || !(brd.device_id &0xff00)) + { + sbdev->poll_type = TYPE_INTERRUPT; + } + else + { + sbdev->poll_type = TYPE_POLL; + } + + /* codes which is specific to each board*/ + switch(brd.device_id){ + case PCI_DEVICE_ID_MP1 : + case PCIE_DEVICE_ID_MP1 : + case PCIE_DEVICE_ID_MP1E : + case PCIE_DEVICE_ID_GT_MP1 : + sbdev->nr_ports = 1; + break; + case PCI_DEVICE_ID_MP2 : + case PCIE_DEVICE_ID_MP2 : + case PCIE_DEVICE_ID_GT_MP2 : + case PCIE_DEVICE_ID_MP2B : + case PCIE_DEVICE_ID_MP2E : + sbdev->nr_ports = 2; + + /* serial base address remap */ + if (sbdev->revision == 0xc0) + { + int prev_port_addr = 0; + + pci_read_config_dword(pcidev, PCI_BASE_ADDRESS_0, &prev_port_addr); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_1, prev_port_addr + 8, 8); + } + break; + case PCI_DEVICE_ID_MP4 : + case PCI_DEVICE_ID_MP4A : + case PCIE_DEVICE_ID_MP4 : + case PCI_DEVICE_ID_GT_MP4 : + case PCI_DEVICE_ID_GT_MP4A : + case PCIE_DEVICE_ID_GT_MP4 : + case PCI_DEVICE_ID_MP4M : + case PCIE_DEVICE_ID_MP4B : + sbdev->nr_ports = 4; + + if(sbdev->revision == 0x91){ + sbdev->reserved_addr[0] = pcidev->resource[0].start & PCI_BASE_ADDRESS_IO_MASK; + outb(0x03 , sbdev->reserved_addr[0] + 0x01); + outb(0x03 , sbdev->reserved_addr[0] + 0x02); + outb(0x01 , sbdev->reserved_addr[0] + 0x20); + outb(0x00 , sbdev->reserved_addr[0] + 0x21); + request_region(sbdev->reserved_addr[0], 32, sbdev->name); + sbdev->uart_access_addr = pcidev->resource[1].start & PCI_BASE_ADDRESS_IO_MASK; + sbdev->option_reg_addr = pcidev->resource[2].start & PCI_BASE_ADDRESS_IO_MASK; + } + + /* SB16C1053APCI */ + if (sbdev->revision == 0xc0) + { + int prev_port_addr = 0; + + pci_read_config_dword(pcidev, PCI_BASE_ADDRESS_0, &prev_port_addr); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_1, prev_port_addr + 8, 8); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_2, prev_port_addr + 16, 8); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_3, prev_port_addr + 24, 8); + } + break; + case PCI_DEVICE_ID_MP6 : + case PCI_DEVICE_ID_MP6A : + case PCI_DEVICE_ID_GT_MP6 : + case PCI_DEVICE_ID_GT_MP6A : + sbdev->nr_ports = 6; + + /* SB16C1053APCI */ + if (sbdev->revision == 0xc0) + { + int prev_port_addr = 0; + + pci_read_config_dword(pcidev, PCI_BASE_ADDRESS_0, &prev_port_addr); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_1, prev_port_addr + 8, 8); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_2, prev_port_addr + 16, 16); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_3, prev_port_addr + 32, 16); + } + break; + case PCI_DEVICE_ID_MP8 : + case PCIE_DEVICE_ID_MP8 : + case PCI_DEVICE_ID_GT_MP8 : + case PCIE_DEVICE_ID_GT_MP8 : + case PCIE_DEVICE_ID_MP8B : + sbdev->nr_ports = 8; + break; + case PCI_DEVICE_ID_MP32 : + case PCIE_DEVICE_ID_MP32 : + case PCI_DEVICE_ID_GT_MP32 : + case PCIE_DEVICE_ID_GT_MP32 : + { + int portnum_hex=0; + portnum_hex = inb(sbdev->option_reg_addr); + sbdev->nr_ports = ((portnum_hex/16)*10) + (portnum_hex % 16); + } + break; + case PCI_DEVICE_ID_MP2S1P : + sbdev->nr_ports = 2; + + /* SB16C1053APCI */ + if (sbdev->revision == 0xc0) + { + int prev_port_addr = 0; + + pci_read_config_dword(pcidev, PCI_BASE_ADDRESS_0, &prev_port_addr); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_1, prev_port_addr + 8, 8); + } + + /* add PC compatible parallel port */ + parport_pc_probe_port(pcidev->resource[2].start, pcidev->resource[3].start, PARPORT_IRQ_NONE, PARPORT_DMA_NONE, &pcidev->dev, 0); + break; + case PCI_DEVICE_ID_MP1P : + /* add PC compatible parallel port */ + parport_pc_probe_port(pcidev->resource[2].start, pcidev->resource[3].start, PARPORT_IRQ_NONE, PARPORT_DMA_NONE, &pcidev->dev, 0); + break; + } + + ret = request_region(sbdev->uart_access_addr, (8*sbdev->nr_ports), sbdev->name); + + if (sbdev->revision == 0xc0) + { + ret = request_region(sbdev->option_reg_addr, 0x40, sbdev->name); + } + else + { + ret = request_region(sbdev->option_reg_addr, 0x20, sbdev->name); + } + + + NR_BOARD++; + NR_PORTS += sbdev->nr_ports; + + /* Enable PCI interrupt */ + addr = sbdev->option_reg_addr + MP_OPTR_IMR0; + for(j=0; j < (sbdev->nr_ports/8)+1; j++) + { + if (sbdev->poll_type == TYPE_INTERRUPT) + { + outb(0xff,addr +j); + } + } + sbdev++; + + return 0; +} + +static int __init multi_init(void) +{ + int ret, i; + struct pci_dev *dev = NULL; + + if(fcr_count==0) + { + for(i=0;i<256;i++) + { + fcr_arr[i] = 0x01; + + } + } + if(deep_count==0) + { + for(i=0;i<256;i++) + { + deep[i] = 1; + + } + } + if(rtr_count==0) + { + for(i=0;i<256;i++) + { + rtr[i] = 0x10; + } + } + if(ttr_count==0) + { + for(i=0;i<256;i++) + { + ttr[i] = 0x38; + } + } + + +printk("MULTI INIT\n"); + for( i=0; i< mp_nrpcibrds; i++) + { + + while( (dev = pci_get_device(mp_pciboards[i].vendor_id, mp_pciboards[i].device_id, dev) ) ) + + { +printk("FOUND~~~\n"); +// Cent OS bug fix +// if (mp_pciboards[i].device_id & 0x0800) + { + int status; + pci_disable_device(dev); + status = pci_enable_device(dev); + + if (status != 0) + { + printk("Multiport Board Enable Fail !\n\n"); + status = -ENXIO; + return status; + } + } + + init_mp_dev(dev, mp_pciboards[i]); + } + } + + for (i = 0; i < NR_IRQS; i++) + spin_lock_init(&irq_lists[i].lock); + + ret = mp_register_driver(&multi_reg); + + if (ret >= 0) + multi_register_ports(&multi_reg); + + return ret; +} + +static void __exit multi_exit(void) +{ + int i; + + for (i = 0; i < NR_PORTS; i++) + mp_remove_one_port(&multi_reg, &multi_ports[i].port); + + mp_unregister_driver(&multi_reg); +} + +module_init(multi_init); +module_exit(multi_exit); + +MODULE_DESCRIPTION("SystemBase Multiport PCI/PCIe CORE"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/sb105x/sb_pci_mp.h b/drivers/staging/sb105x/sb_pci_mp.h new file mode 100644 index 000000000000..f33efde07b97 --- /dev/null +++ b/drivers/staging/sb105x/sb_pci_mp.h @@ -0,0 +1,293 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include + + +#define MP_TERMIOS ktermios + +#include "sb_mp_register.h" +#include "sb_ser_core.h" + +#define DRIVER_VERSION "1.1" +#define DRIVER_DATE "2012/01/05" +#define DRIVER_AUTHOR "SYSTEMBASE" +#define DRIVER_DESC "SystemBase PCI/PCIe Multiport Core" + +#define SB_TTY_MP_MAJOR 54 +#define PCI_VENDOR_ID_MULTIPORT 0x14A1 + +#define PCI_DEVICE_ID_MP1 0x4d01 +#define PCI_DEVICE_ID_MP2 0x4d02 +#define PCI_DEVICE_ID_MP4 0x4d04 +#define PCI_DEVICE_ID_MP4A 0x4d54 +#define PCI_DEVICE_ID_MP6 0x4d06 +#define PCI_DEVICE_ID_MP6A 0x4d56 +#define PCI_DEVICE_ID_MP8 0x4d08 +#define PCI_DEVICE_ID_MP32 0x4d32 +/* Parallel port */ +#define PCI_DEVICE_ID_MP1P 0x4301 +#define PCI_DEVICE_ID_MP2S1P 0x4303 + +#define PCIE_DEVICE_ID_MP1 0x4501 +#define PCIE_DEVICE_ID_MP2 0x4502 +#define PCIE_DEVICE_ID_MP4 0x4504 +#define PCIE_DEVICE_ID_MP8 0x4508 +#define PCIE_DEVICE_ID_MP32 0x4532 + +#define PCIE_DEVICE_ID_MP1E 0x4e01 +#define PCIE_DEVICE_ID_MP2E 0x4e02 +#define PCIE_DEVICE_ID_MP2B 0x4b02 +#define PCIE_DEVICE_ID_MP4B 0x4b04 +#define PCIE_DEVICE_ID_MP8B 0x4b08 + +#define PCI_DEVICE_ID_GT_MP4 0x0004 +#define PCI_DEVICE_ID_GT_MP4A 0x0054 +#define PCI_DEVICE_ID_GT_MP6 0x0006 +#define PCI_DEVICE_ID_GT_MP6A 0x0056 +#define PCI_DEVICE_ID_GT_MP8 0x0008 +#define PCI_DEVICE_ID_GT_MP32 0x0032 + +#define PCIE_DEVICE_ID_GT_MP1 0x1501 +#define PCIE_DEVICE_ID_GT_MP2 0x1502 +#define PCIE_DEVICE_ID_GT_MP4 0x1504 +#define PCIE_DEVICE_ID_GT_MP8 0x1508 +#define PCIE_DEVICE_ID_GT_MP32 0x1532 + +#define PCI_DEVICE_ID_MP4M 0x4604 //modem + +#define MAX_MP_DEV 8 +#define BD_MAX_PORT 32 /* Max serial port in one board */ +#define MAX_MP_PORT 256 /* Max serial port in one PC */ + +#define PORT_16C105XA 3 +#define PORT_16C105X 2 +#define PORT_16C55X 1 + +#define ENABLE 1 +#define DISABLE 0 + +/* ioctls */ +#define TIOCGNUMOFPORT 0x545F +#define TIOCSMULTIECHO 0x5440 +#define TIOCSPTPNOECHO 0x5441 + +#define TIOCGOPTIONREG 0x5461 +#define TIOCGDISABLEIRQ 0x5462 +#define TIOCGENABLEIRQ 0x5463 +#define TIOCGSOFTRESET 0x5464 +#define TIOCGSOFTRESETR 0x5465 +#define TIOCGREGINFO 0x5466 +#define TIOCGGETLSR 0x5467 +#define TIOCGGETDEVID 0x5468 +#define TIOCGGETBDNO 0x5469 +#define TIOCGGETINTERFACE 0x546A +#define TIOCGGETREV 0x546B +#define TIOCGGETNRPORTS 0x546C +#define TIOCGGETPORTTYPE 0x546D +#define GETDEEPFIFO 0x54AA +#define SETDEEPFIFO 0x54AB +#define SETFCR 0x54BA +#define SETTTR 0x54B1 +#define SETRTR 0x54B2 +#define GETTTR 0x54B3 +#define GETRTR 0x54B4 + +/* multi-drop mode related ioctl commands */ +#define TIOCSMULTIDROP 0x5470 +#define TIOCSMDADDR 0x5471 +#define TIOCGMDADDR 0x5472 +#define TIOCSENDADDR 0x5473 + + +/* serial interface */ +#define RS232 1 +#define RS422PTP 2 +#define RS422MD 3 +#define RS485NE 4 +#define RS485ECHO 5 + +#define serial_inp(up, offset) serial_in(up, offset) +#define serial_outp(up, offset, value) serial_out(up, offset, value) + +#define PASS_LIMIT 256 +#define is_real_interrupt(irq) ((irq) != 0) + +#define PROBE_ANY (~0) + +static DEFINE_MUTEX(mp_mutex); +#define MP_MUTEX_LOCK(x) mutex_lock(&(x)) +#define MP_MUTEX_UNLOCK(x) mutex_unlock(&(x)) +#define MP_STATE_LOCK(x) mutex_lock(&((x)->mutex)) +#define MP_STATE_UNLOCK(x) mutex_unlock(&((x)->mutex)) + + +#define UART_LSR_SPECIAL 0x1E + +#define HIGH_BITS_OFFSET ((sizeof(long)-sizeof(int))*8) +#define uart_users(state) ((state)->count + ((state)->info ? (state)->info->blocked_open : 0)) + + +//#define MP_DEBUG 1 +#undef MP_DEBUG + +#ifdef MP_DEBUG +#define DPRINTK(x...) printk(x) +#else +#define DPRINTK(x...) do { } while (0) +#endif + +#ifdef MP_DEBUG +#define DEBUG_AUTOCONF(fmt...) printk(fmt) +#else +#define DEBUG_AUTOCONF(fmt...) do { } while (0) +#endif + +#ifdef MP_DEBUG +#define DEBUG_INTR(fmt...) printk(fmt) +#else +#define DEBUG_INTR(fmt...) do { } while (0) +#endif + +#if defined(__i386__) && (defined(CONFIG_M386) || defined(CONFIG_M486)) +#define SERIAL_INLINE +#endif +#ifdef SERIAL_INLINE +#define _INLINE_ inline +#else +#define _INLINE_ +#endif + +#define TYPE_POLL 1 +#define TYPE_INTERRUPT 2 + + +struct mp_device_t { + unsigned short device_id; + unsigned char revision; + char *name; + unsigned long uart_access_addr; + unsigned long option_reg_addr; + unsigned long reserved_addr[4]; + int irq; + int nr_ports; + int poll_type; +}; + +typedef struct mppcibrd { + char *name; + unsigned short vendor_id; + unsigned short device_id; +} mppcibrd_t; + +static mppcibrd_t mp_pciboards[] = { + + { "Multi-1 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP1} , + { "Multi-2 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP2} , + { "Multi-4 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP4} , + { "Multi-4 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP4A} , + { "Multi-6 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP6} , + { "Multi-6 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP6A} , + { "Multi-8 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP8} , + { "Multi-32 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP32} , + + { "Multi-1P PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP1P} , + { "Multi-2S1P PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP2S1P} , + + { "Multi-4(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP4} , + { "Multi-4(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP4A} , + { "Multi-6(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP6} , + { "Multi-6(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP6A} , + { "Multi-8(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP8} , + { "Multi-32(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP32} , + + { "Multi-1 PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP1} , + { "Multi-2 PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP2} , + { "Multi-4 PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP4} , + { "Multi-8 PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP8} , + { "Multi-32 PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP32} , + + { "Multi-1 PCIe E", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP1E} , + { "Multi-2 PCIe E", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP2E} , + { "Multi-2 PCIe B", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP2B} , + { "Multi-4 PCIe B", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP4B} , + { "Multi-8 PCIe B", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP8B} , + + { "Multi-1(GT) PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_GT_MP1} , + { "Multi-2(GT) PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_GT_MP2} , + { "Multi-4(GT) PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_GT_MP4} , + { "Multi-8(GT) PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_GT_MP8} , + { "Multi-32(GT) PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_GT_MP32} , + + { "Multi-4M PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP4M} , +}; + +struct mp_port { + struct sb_uart_port port; + + struct timer_list timer; /* "no irq" timer */ + struct list_head list; /* ports on this IRQ */ + unsigned int capabilities; /* port capabilities */ + unsigned short rev; + unsigned char acr; + unsigned char ier; + unsigned char lcr; + unsigned char mcr; + unsigned char mcr_mask; /* mask of user bits */ + unsigned char mcr_force; /* mask of forced bits */ + unsigned char lsr_break_flag; + + void (*pm)(struct sb_uart_port *port, + unsigned int state, unsigned int old); + struct mp_device_t *device; + unsigned long interface_config_addr; + unsigned long option_base_addr; + unsigned char interface; + unsigned char poll_type; +}; + +struct irq_info { + spinlock_t lock; + struct list_head *head; +}; + +struct sb105x_uart_config { + char *name; + int dfl_xmit_fifo_size; + int flags; +}; + +static const struct sb105x_uart_config uart_config[] = { + { "unknown", 1, 0 }, + { "16550A", 16, UART_CLEAR_FIFO | UART_USE_FIFO }, + { "SB16C1050", 128, UART_CLEAR_FIFO | UART_USE_FIFO | UART_STARTECH }, + { "SB16C1050A", 128, UART_CLEAR_FIFO | UART_USE_FIFO | UART_STARTECH }, +}; + + + diff --git a/drivers/staging/sb105x/sb_ser_core.h b/drivers/staging/sb105x/sb_ser_core.h new file mode 100644 index 000000000000..c8fb99173299 --- /dev/null +++ b/drivers/staging/sb105x/sb_ser_core.h @@ -0,0 +1,368 @@ +#include + +#define UART_CONFIG_TYPE (1 << 0) +#define UART_CONFIG_IRQ (1 << 1) +#define UPIO_PORT (0) +#define UPIO_HUB6 (1) +#define UPIO_MEM (2) +#define UPIO_MEM32 (3) +#define UPIO_AU (4) /* Au1x00 type IO */ +#define UPIO_TSI (5) /* Tsi108/109 type IO */ +#define UPF_FOURPORT (1 << 1) +#define UPF_SAK (1 << 2) +#define UPF_SPD_MASK (0x1030) +#define UPF_SPD_HI (0x0010) +#define UPF_SPD_VHI (0x0020) +#define UPF_SPD_CUST (0x0030) +#define UPF_SPD_SHI (0x1000) +#define UPF_SPD_WARP (0x1010) +#define UPF_SKIP_TEST (1 << 6) +#define UPF_AUTO_IRQ (1 << 7) +#define UPF_HARDPPS_CD (1 << 11) +#define UPF_LOW_LATENCY (1 << 13) +#define UPF_BUGGY_UART (1 << 14) +#define UPF_MAGIC_MULTIPLIER (1 << 16) +#define UPF_CONS_FLOW (1 << 23) +#define UPF_SHARE_IRQ (1 << 24) +#define UPF_BOOT_AUTOCONF (1 << 28) +#define UPF_DEAD (1 << 30) +#define UPF_IOREMAP (1 << 31) +#define UPF_CHANGE_MASK (0x17fff) +#define UPF_USR_MASK (UPF_SPD_MASK|UPF_LOW_LATENCY) +#define USF_CLOSING_WAIT_INF (0) +#define USF_CLOSING_WAIT_NONE (~0U) + +#define UART_XMIT_SIZE PAGE_SIZE + +#define UIF_CHECK_CD (1 << 25) +#define UIF_CTS_FLOW (1 << 26) +#define UIF_NORMAL_ACTIVE (1 << 29) +#define UIF_INITIALIZED (1 << 31) +#define UIF_SUSPENDED (1 << 30) + +#define WAKEUP_CHARS 256 + +#define uart_circ_empty(circ) ((circ)->head == (circ)->tail) +#define uart_circ_clear(circ) ((circ)->head = (circ)->tail = 0) + +#define uart_circ_chars_pending(circ) \ + (CIRC_CNT((circ)->head, (circ)->tail, UART_XMIT_SIZE)) + +#define uart_circ_chars_free(circ) \ + (CIRC_SPACE((circ)->head, (circ)->tail, UART_XMIT_SIZE)) + +#define uart_tx_stopped(port) \ + ((port)->info->tty->stopped || (port)->info->tty->hw_stopped) + +#define UART_ENABLE_MS(port,cflag) ((port)->flags & UPF_HARDPPS_CD || \ + (cflag) & CRTSCTS || \ + !((cflag) & CLOCAL)) + + +struct sb_uart_port; +struct sb_uart_info; +struct serial_struct; +struct device; + +struct sb_uart_ops { + unsigned int (*tx_empty)(struct sb_uart_port *); + void (*set_mctrl)(struct sb_uart_port *, unsigned int mctrl); + unsigned int (*get_mctrl)(struct sb_uart_port *); + void (*stop_tx)(struct sb_uart_port *); + void (*start_tx)(struct sb_uart_port *); + void (*send_xchar)(struct sb_uart_port *, char ch); + void (*stop_rx)(struct sb_uart_port *); + void (*enable_ms)(struct sb_uart_port *); + void (*break_ctl)(struct sb_uart_port *, int ctl); + int (*startup)(struct sb_uart_port *); + void (*shutdown)(struct sb_uart_port *); + void (*set_termios)(struct sb_uart_port *, struct MP_TERMIOS *new, + struct MP_TERMIOS *old); + void (*pm)(struct sb_uart_port *, unsigned int state, + unsigned int oldstate); + int (*set_wake)(struct sb_uart_port *, unsigned int state); + + const char *(*type)(struct sb_uart_port *); + + void (*release_port)(struct sb_uart_port *); + + int (*request_port)(struct sb_uart_port *); + void (*config_port)(struct sb_uart_port *, int); + int (*verify_port)(struct sb_uart_port *, struct serial_struct *); + int (*ioctl)(struct sb_uart_port *, unsigned int, unsigned long); +}; + + +struct sb_uart_icount { + __u32 cts; + __u32 dsr; + __u32 rng; + __u32 dcd; + __u32 rx; + __u32 tx; + __u32 frame; + __u32 overrun; + __u32 parity; + __u32 brk; + __u32 buf_overrun; +}; +typedef unsigned int upf_t; + +struct sb_uart_port { + spinlock_t lock; /* port lock */ + unsigned int iobase; /* in/out[bwl] */ + unsigned char __iomem *membase; /* read/write[bwl] */ + unsigned int irq; /* irq number */ + unsigned int uartclk; /* base uart clock */ + unsigned int fifosize; /* tx fifo size */ + unsigned char x_char; /* xon/xoff char */ + unsigned char regshift; /* reg offset shift */ + unsigned char iotype; /* io access style */ + unsigned char unused1; + + + unsigned int read_status_mask; /* driver specific */ + unsigned int ignore_status_mask; /* driver specific */ + struct sb_uart_info *info; /* pointer to parent info */ + struct sb_uart_icount icount; /* statistics */ + + struct console *cons; /* struct console, if any */ +#ifdef CONFIG_SERIAL_CORE_CONSOLE + unsigned long sysrq; /* sysrq timeout */ +#endif + + upf_t flags; + + unsigned int mctrl; /* current modem ctrl settings */ + unsigned int timeout; /* character-based timeout */ + unsigned int type; /* port type */ + const struct sb_uart_ops *ops; + unsigned int custom_divisor; + unsigned int line; /* port index */ + unsigned long mapbase; /* for ioremap */ + struct device *dev; /* parent device */ + unsigned char hub6; /* this should be in the 8250 driver */ + unsigned char unused[3]; +}; + +#define mdmode unused[2] +#define MDMODE_ADDR 0x1 +#define MDMODE_ENABLE 0x2 +#define MDMODE_AUTO 0x4 +#define MDMODE_ADDRSEND 0x8 + +struct sb_uart_state { + unsigned int close_delay; /* msec */ + unsigned int closing_wait; /* msec */ + + + int count; + int pm_state; + struct sb_uart_info *info; + struct sb_uart_port *port; + + struct mutex mutex; +}; + +typedef unsigned int uif_t; + +struct sb_uart_info { + struct tty_struct *tty; + struct circ_buf xmit; + uif_t flags; + + int blocked_open; + + struct tasklet_struct tlet; + + wait_queue_head_t open_wait; + wait_queue_head_t delta_msr_wait; +}; + + +struct module; +struct tty_driver; + +struct uart_driver { + struct module *owner; + const char *driver_name; + const char *dev_name; + int major; + int minor; + int nr; + struct console *cons; + + struct sb_uart_state *state; + struct tty_driver *tty_driver; +}; + +void sb_uart_write_wakeup(struct sb_uart_port *port) +{ + struct sb_uart_info *info = port->info; + tasklet_schedule(&info->tlet); +} + +void sb_uart_update_timeout(struct sb_uart_port *port, unsigned int cflag, + unsigned int baud) +{ + unsigned int bits; + + switch (cflag & CSIZE) + { + case CS5: + bits = 7; + break; + + case CS6: + bits = 8; + break; + + case CS7: + bits = 9; + break; + + default: + bits = 10; + break; + } + + if (cflag & CSTOPB) + { + bits++; + } + + if (cflag & PARENB) + { + bits++; + } + + bits = bits * port->fifosize; + + port->timeout = (HZ * bits) / baud + HZ/50; +} +unsigned int sb_uart_get_baud_rate(struct sb_uart_port *port, struct MP_TERMIOS *termios, + struct MP_TERMIOS *old, unsigned int min, + unsigned int max) +{ + unsigned int try, baud, altbaud = 38400; + upf_t flags = port->flags & UPF_SPD_MASK; + + if (flags == UPF_SPD_HI) + altbaud = 57600; + if (flags == UPF_SPD_VHI) + altbaud = 115200; + if (flags == UPF_SPD_SHI) + altbaud = 230400; + if (flags == UPF_SPD_WARP) + altbaud = 460800; + + for (try = 0; try < 2; try++) { + + switch (termios->c_cflag & (CBAUD | CBAUDEX)) + { + case B921600 : baud = 921600; break; + case B460800 : baud = 460800; break; + case B230400 : baud = 230400; break; + case B115200 : baud = 115200; break; + case B57600 : baud = 57600; break; + case B38400 : baud = 38400; break; + case B19200 : baud = 19200; break; + case B9600 : baud = 9600; break; + case B4800 : baud = 4800; break; + case B2400 : baud = 2400; break; + case B1800 : baud = 1800; break; + case B1200 : baud = 1200; break; + case B600 : baud = 600; break; + case B300 : baud = 300; break; + case B200 : baud = 200; break; + case B150 : baud = 150; break; + case B134 : baud = 134; break; + case B110 : baud = 110; break; + case B75 : baud = 75; break; + case B50 : baud = 50; break; + default : baud = 9600; break; + } + + if (baud == 38400) + baud = altbaud; + + if (baud == 0) + baud = 9600; + + if (baud >= min && baud <= max) + return baud; + + termios->c_cflag &= ~CBAUD; + if (old) { + termios->c_cflag |= old->c_cflag & CBAUD; + old = NULL; + continue; + } + + termios->c_cflag |= B9600; + } + + return 0; +} +unsigned int sb_uart_get_divisor(struct sb_uart_port *port, unsigned int baud) +{ + unsigned int quot; + + if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST) + quot = port->custom_divisor; + else + quot = (port->uartclk + (8 * baud)) / (16 * baud); + + return quot; +} + + + +static inline int sb_uart_handle_break(struct sb_uart_port *port) +{ + struct sb_uart_info *info = port->info; + + if (port->flags & UPF_SAK) + do_SAK(info->tty); + return 0; +} + +static inline void sb_uart_handle_dcd_change(struct sb_uart_port *port, unsigned int status) +{ + struct sb_uart_info *info = port->info; + + port->icount.dcd++; + + if (info->flags & UIF_CHECK_CD) { + if (status) + wake_up_interruptible(&info->open_wait); + else if (info->tty) + tty_hangup(info->tty); + } +} + +static inline void sb_uart_handle_cts_change(struct sb_uart_port *port, unsigned int status) +{ + struct sb_uart_info *info = port->info; + struct tty_struct *tty = info->tty; + + port->icount.cts++; + + if (info->flags & UIF_CTS_FLOW) { + if (tty->hw_stopped) { + if (status) { + tty->hw_stopped = 0; + port->ops->start_tx(port); + sb_uart_write_wakeup(port); + } + } else { + if (!status) { + tty->hw_stopped = 1; + port->ops->stop_tx(port); + } + } + } +} + + + -- cgit v1.2.3 From a547e5e0d8b85ccc640756eb40b3dd6b33796cf8 Mon Sep 17 00:00:00 2001 From: Masanari Iida Date: Sat, 17 Nov 2012 16:43:25 +0900 Subject: staging: dgrp: Fix typo in dgrp driver Correct spelling typo in staging/dgrp driver Signed-off-by: Masanari Iida Signed-off-by: Greg Kroah-Hartman --- drivers/staging/dgrp/dgrp_net_ops.c | 6 +++--- drivers/staging/dgrp/dgrp_tty.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/staging') diff --git a/drivers/staging/dgrp/dgrp_net_ops.c b/drivers/staging/dgrp/dgrp_net_ops.c index 0788357fd3ca..8185a57d31c5 100644 --- a/drivers/staging/dgrp/dgrp_net_ops.c +++ b/drivers/staging/dgrp/dgrp_net_ops.c @@ -2495,7 +2495,7 @@ data: /* * Fabricate and insert a data packet header to - * preceed the remaining data when it comes in. + * preced the remaining data when it comes in. */ if (remain < plen) { @@ -2664,7 +2664,7 @@ data: } /* - * Handle delayed response arrival preceeding + * Handle delayed response arrival preceding * the open response we are waiting for. */ @@ -3502,7 +3502,7 @@ void dgrp_poll_handler(unsigned long arg) /* * Decrement statistics. These are only for use with * KME, so don't worry that the operations are done - * unlocked, and so the results are occassionally wrong. + * unlocked, and so the results are occasionally wrong. */ nd->nd_read_count -= (nd->nd_read_count + diff --git a/drivers/staging/dgrp/dgrp_tty.c b/drivers/staging/dgrp/dgrp_tty.c index 0db4c0514f63..e3fa6eac53ce 100644 --- a/drivers/staging/dgrp/dgrp_tty.c +++ b/drivers/staging/dgrp/dgrp_tty.c @@ -432,7 +432,7 @@ static void drp_param(struct ch_struct *ch) /* * From the POSIX.1 spec (7.1.2.6): "If {_POSIX_VDISABLE} * is defined for the terminal device file, and the value - * of one of the changable special control characters (see + * of one of the changeable special control characters (see * 7.1.1.9) is {_POSIX_VDISABLE}, that function shall be * disabled, that is, no input data shall be recognized as * the disabled special character." @@ -2699,7 +2699,7 @@ static int dgrp_tty_ioctl(struct tty_struct *tty, unsigned int cmd, - looking at the tty_ioctl code, these command all call our tty_set_termios at the driver's end, when a TCSETA* is sent, it is expecting the tty to have a termio structure, - NOT a termios stucture. These two structures differ in size + NOT a termios structure. These two structures differ in size and the tty_ioctl code does a conversion before processing them both. - we should treat the TCSETAW TCSETAF ioctls the same, and let the tty_ioctl code do the conversion stuff. @@ -2996,7 +2996,7 @@ static void dgrp_tty_start(struct tty_struct *tty) } /* - * Stop the reciever + * Stop the receiver */ static void dgrp_tty_input_stop(struct tty_struct *tty) { -- cgit v1.2.3 From 3ba89e96610b85b5a2f3a146f65342e38af27a79 Mon Sep 17 00:00:00 2001 From: Kumar Amit Mehta Date: Fri, 23 Nov 2012 01:16:34 +0530 Subject: staging: dgrp: dgrp_tty.c: Remove the TIOCSSOFTCAR ioctl handler from dgrp driver Remove the TIOCSSOFTCAR ioctl handler from dgrp driver and let the core tty layer to take care of this ioctl instead. Signed-off-by: Kumar Amit Mehta Signed-off-by: Greg Kroah-Hartman --- drivers/staging/dgrp/dgrp_tty.c | 7 ------- 1 file changed, 7 deletions(-) (limited to 'drivers/staging') diff --git a/drivers/staging/dgrp/dgrp_tty.c b/drivers/staging/dgrp/dgrp_tty.c index e3fa6eac53ce..5796de8ddbe4 100644 --- a/drivers/staging/dgrp/dgrp_tty.c +++ b/drivers/staging/dgrp/dgrp_tty.c @@ -2623,13 +2623,6 @@ static int dgrp_tty_ioctl(struct tty_struct *tty, unsigned int cmd, put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) arg); return 0; - case TIOCSSOFTCAR: - get_user(arg, (unsigned long __user *) arg); - tty->termios.c_cflag = - ((tty->termios.c_cflag & ~CLOCAL) | - (arg ? CLOCAL : 0)); - return 0; - case TIOCMGET: rc = access_ok(VERIFY_WRITE, (void __user *) arg, sizeof(unsigned int)); -- cgit v1.2.3 From d53c57dca2ccb540cceb0b1a228f11e1325cecde Mon Sep 17 00:00:00 2001 From: Kumar Amit Mehta Date: Fri, 23 Nov 2012 01:16:35 +0530 Subject: staging: dgrp: dgrp_tty.c: Audit the return values of get/put_user() fix for missing audits for return values of get_user() and put_user(). Inspecting the return values of get/put_user() would make the access_ok() redundant, hence removing calls to access_ok() in such scenarios. Signed-off-by: Kumar Amit Mehta Signed-off-by: Greg Kroah-Hartman --- drivers/staging/dgrp/dgrp_tty.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) (limited to 'drivers/staging') diff --git a/drivers/staging/dgrp/dgrp_tty.c b/drivers/staging/dgrp/dgrp_tty.c index 5796de8ddbe4..5647068b9385 100644 --- a/drivers/staging/dgrp/dgrp_tty.c +++ b/drivers/staging/dgrp/dgrp_tty.c @@ -2265,9 +2265,7 @@ static int get_modem_info(struct ch_struct *ch, unsigned int *value) | ((mlast & DM_RI) ? TIOCM_RNG : 0) | ((mlast & DM_DSR) ? TIOCM_DSR : 0) | ((mlast & DM_CTS) ? TIOCM_CTS : 0); - put_user(mlast, (unsigned int __user *) value); - - return 0; + return put_user(mlast, (unsigned int __user *) value); } /* @@ -2285,7 +2283,8 @@ static int set_modem_info(struct ch_struct *ch, unsigned int command, if (error == 0) return -EFAULT; - get_user(arg, (unsigned int __user *) value); + if (get_user(arg, (unsigned int __user *) value)) + return -EFAULT; mval |= ((arg & TIOCM_RTS) ? DM_RTS : 0) | ((arg & TIOCM_DTR) ? DM_DTR : 0); @@ -2616,12 +2615,8 @@ static int dgrp_tty_ioctl(struct tty_struct *tty, unsigned int cmd, return 0; case TIOCGSOFTCAR: - rc = access_ok(VERIFY_WRITE, (void __user *) arg, - sizeof(long)); - if (rc == 0) - return -EFAULT; - put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) arg); - return 0; + return put_user(C_CLOCAL(tty) ? 1 : 0, + (unsigned long __user *) arg); case TIOCMGET: rc = access_ok(VERIFY_WRITE, (void __user *) arg, @@ -2844,17 +2839,16 @@ static int dgrp_tty_ioctl(struct tty_struct *tty, unsigned int cmd, break; case DIGI_GETCUSTOMBAUD: - rc = access_ok(VERIFY_WRITE, (void __user *) arg, sizeof(int)); - if (rc == 0) + if (put_user(ch->ch_custom_speed, (unsigned int __user *) arg)) return -EFAULT; - put_user(ch->ch_custom_speed, (unsigned int __user *) arg); break; case DIGI_SETCUSTOMBAUD: { int new_rate; - get_user(new_rate, (unsigned int __user *) arg); + if (get_user(new_rate, (unsigned int __user *) arg)) + return -EFAULT; dgrp_set_custom_speed(ch, new_rate); break; -- cgit v1.2.3 From 7355ba3445f2a741bfb9f03c7c8d5fc3e10bf63f Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Fri, 2 Nov 2012 08:16:33 -0400 Subject: staging: fwserial: Add TTY-over-Firewire serial driver This patch provides the kernel driver for high-speed TTY communication over the IEEE 1394 bus. Signed-off-by: Peter Hurley Acked-by: Stefan Richter Signed-off-by: Greg Kroah-Hartman --- drivers/staging/Kconfig | 2 + drivers/staging/Makefile | 1 + drivers/staging/fwserial/Kconfig | 9 + drivers/staging/fwserial/Makefile | 2 + drivers/staging/fwserial/TODO | 37 + drivers/staging/fwserial/dma_fifo.c | 310 ++++ drivers/staging/fwserial/dma_fifo.h | 130 ++ drivers/staging/fwserial/fwserial.c | 2946 +++++++++++++++++++++++++++++++++++ drivers/staging/fwserial/fwserial.h | 387 +++++ 9 files changed, 3824 insertions(+) create mode 100644 drivers/staging/fwserial/Kconfig create mode 100644 drivers/staging/fwserial/Makefile create mode 100644 drivers/staging/fwserial/TODO create mode 100644 drivers/staging/fwserial/dma_fifo.c create mode 100644 drivers/staging/fwserial/dma_fifo.h create mode 100644 drivers/staging/fwserial/fwserial.c create mode 100644 drivers/staging/fwserial/fwserial.h (limited to 'drivers/staging') diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index f245fd3153d0..fc95eaba223b 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -146,4 +146,6 @@ source "drivers/staging/dgrp/Kconfig" source "drivers/staging/sb105x/Kconfig" +source "drivers/staging/fwserial/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 94cc3fa5ef81..76db1e242747 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -65,3 +65,4 @@ obj-$(CONFIG_CED1401) += ced1401/ obj-$(CONFIG_DRM_IMX) += imx-drm/ obj-$(CONFIG_DGRP) += dgrp/ obj-$(CONFIG_SB105X) += sb105x/ +obj-$(CONFIG_FIREWIRE_SERIAL) += fwserial/ diff --git a/drivers/staging/fwserial/Kconfig b/drivers/staging/fwserial/Kconfig new file mode 100644 index 000000000000..580406cb1808 --- /dev/null +++ b/drivers/staging/fwserial/Kconfig @@ -0,0 +1,9 @@ +config FIREWIRE_SERIAL + tristate "TTY over Firewire" + depends on FIREWIRE + help + This enables TTY over IEEE 1394, providing high-speed serial + connectivity to cabled peers. + + To compile this driver as a module, say M here: the module will + be called firewire-serial. diff --git a/drivers/staging/fwserial/Makefile b/drivers/staging/fwserial/Makefile new file mode 100644 index 000000000000..2170869a19b1 --- /dev/null +++ b/drivers/staging/fwserial/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_FIREWIRE_SERIAL) += firewire-serial.o +firewire-serial-objs := fwserial.o dma_fifo.o diff --git a/drivers/staging/fwserial/TODO b/drivers/staging/fwserial/TODO new file mode 100644 index 000000000000..726900548eae --- /dev/null +++ b/drivers/staging/fwserial/TODO @@ -0,0 +1,37 @@ +TODOs +----- +1. Implement retries for RCODE_BUSY, RCODE_NO_ACK and RCODE_SEND_ERROR + - I/O is handled asynchronously which presents some issues when error + conditions occur. +2. Implement _robust_ console on top of this. The existing prototype console + driver is not ready for the big leagues yet. +3. Expose means of controlling attach/detach of peers via sysfs. Include + GUID-to-port matching/whitelist/blacklist. + +-- Issues with firewire stack -- +1. This driver uses the same unregistered vendor id that the firewire core does + (0xd00d1e). Perhaps this could be exposed as a define in + firewire-constants.h? +2. MAX_ASYNC_PAYLOAD needs to be publicly exposed by core/ohci + - otherwise how will this driver know the max size of address window to + open for one packet write? +3. Maybe device_max_receive() and link_speed_to_max_payload() should be + taken up by the firewire core? +4. To avoid dropping rx data while still limiting the maximum buffering, + the size of the AR context must be known. How to expose this to drivers? +5. Explore if bigger AR context will reduce RCODE_BUSY responses + (or auto-grow to certain max size -- but this would require major surgery + as the current AR is contiguously mapped) + +-- Issues with TTY core -- + 1. Hack for alternate device name scheme + - because udev no longer allows device renaming, devices should have + their proper names on creation. This is an issue for creating the + fwloop device with the fwtty devices because although duplicating + roughly the same operations as tty_port_register_device() isn't difficult, + access to the tty_class & tty_fops is restricted in scope. + + This is currently being worked around in create_loop_device() by + extracting the tty_class ptr and tty_fops ptr from the previously created + tty devices. Perhaps an add'l api can be added -- eg., + tty_{port_}register_named_device(). diff --git a/drivers/staging/fwserial/dma_fifo.c b/drivers/staging/fwserial/dma_fifo.c new file mode 100644 index 000000000000..72aa0533f018 --- /dev/null +++ b/drivers/staging/fwserial/dma_fifo.c @@ -0,0 +1,310 @@ +/* + * DMA-able FIFO implementation + * + * Copyright (C) 2012 Peter Hurley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include "dma_fifo.h" + +#ifdef DEBUG_TRACING +#define df_trace(s, args...) pr_debug(s, ##args) +#else +#define df_trace(s, args...) +#endif + +#define FAIL(fifo, condition, format...) ({ \ + fifo->corrupt = !!(condition); \ + if (unlikely(fifo->corrupt)) { \ + __WARN_printf(format); \ + } \ + unlikely(fifo->corrupt); \ +}) + +/* + * private helper fn to determine if check is in open interval (lo,hi) + */ +static bool addr_check(unsigned check, unsigned lo, unsigned hi) +{ + return check - (lo + 1) < (hi - 1) - lo; +} + +/** + * dma_fifo_init: initialize the fifo to a valid but inoperative state + * @fifo: address of in-place "struct dma_fifo" object + */ +void dma_fifo_init(struct dma_fifo *fifo) +{ + memset(fifo, 0, sizeof(*fifo)); + INIT_LIST_HEAD(&fifo->pending); +} + +/** + * dma_fifo_alloc - initialize and allocate dma_fifo + * @fifo: address of in-place "struct dma_fifo" object + * @size: 'apparent' size, in bytes, of fifo + * @align: dma alignment to maintain (should be at least cpu cache alignment), + * must be power of 2 + * @tx_limit: maximum # of bytes transmissable per dma (rounded down to + * multiple of alignment, but at least align size) + * @open_limit: maximum # of outstanding dma transactions allowed + * @gfp_mask: get_free_pages mask, passed to kmalloc() + * + * The 'apparent' size will be rounded up to next greater aligned size. + * Returns 0 if no error, otherwise an error code + */ +int dma_fifo_alloc(struct dma_fifo *fifo, int size, unsigned align, + int tx_limit, int open_limit, gfp_t gfp_mask) +{ + int capacity; + + if (!is_power_of_2(align) || size < 0) + return -EINVAL; + + size = round_up(size, align); + capacity = size + align * open_limit + align * DMA_FIFO_GUARD; + fifo->data = kmalloc(capacity, gfp_mask); + if (!fifo->data) + return -ENOMEM; + + fifo->in = 0; + fifo->out = 0; + fifo->done = 0; + fifo->size = size; + fifo->avail = size; + fifo->align = align; + fifo->tx_limit = max_t(int, round_down(tx_limit, align), align); + fifo->open = 0; + fifo->open_limit = open_limit; + fifo->guard = size + align * open_limit; + fifo->capacity = capacity; + fifo->corrupt = 0; + + return 0; +} + +/** + * dma_fifo_free - frees the fifo + * @fifo: address of in-place "struct dma_fifo" to free + * + * Also reinits the fifo to a valid but inoperative state. This + * allows the fifo to be reused with a different target requiring + * different fifo parameters. + */ +void dma_fifo_free(struct dma_fifo *fifo) +{ + struct dma_pending *pending, *next; + + if (fifo->data == NULL) + return; + + list_for_each_entry_safe(pending, next, &fifo->pending, link) + list_del_init(&pending->link); + kfree(fifo->data); + fifo->data = NULL; +} + +/** + * dma_fifo_reset - dumps the fifo contents and reinits for reuse + * @fifo: address of in-place "struct dma_fifo" to reset + */ +void dma_fifo_reset(struct dma_fifo *fifo) +{ + struct dma_pending *pending, *next; + + if (fifo->data == NULL) + return; + + list_for_each_entry_safe(pending, next, &fifo->pending, link) + list_del_init(&pending->link); + fifo->in = 0; + fifo->out = 0; + fifo->done = 0; + fifo->avail = fifo->size; + fifo->open = 0; + fifo->corrupt = 0; +} + +/** + * dma_fifo_in - copies data into the fifo + * @fifo: address of in-place "struct dma_fifo" to write to + * @src: buffer to copy from + * @n: # of bytes to copy + * + * Returns the # of bytes actually copied, which can be less than requested if + * the fifo becomes full. If < 0, return is error code. + */ +int dma_fifo_in(struct dma_fifo *fifo, const void *src, int n) +{ + int ofs, l; + + if (fifo->data == NULL) + return -ENOENT; + if (fifo->corrupt) + return -ENXIO; + + if (n > fifo->avail) + n = fifo->avail; + if (n <= 0) + return 0; + + ofs = fifo->in % fifo->capacity; + l = min(n, fifo->capacity - ofs); + memcpy(fifo->data + ofs, src, l); + memcpy(fifo->data, src + l, n - l); + + if (FAIL(fifo, addr_check(fifo->done, fifo->in, fifo->in + n) || + fifo->avail < n, + "fifo corrupt: in:%u out:%u done:%u n:%d avail:%d", + fifo->in, fifo->out, fifo->done, n, fifo->avail)) + return -ENXIO; + + fifo->in += n; + fifo->avail -= n; + + df_trace("in:%u out:%u done:%u n:%d avail:%d", fifo->in, fifo->out, + fifo->done, n, fifo->avail); + + return n; +} + +/** + * dma_fifo_out_pend - gets address/len of next avail read and marks as pended + * @fifo: address of in-place "struct dma_fifo" to read from + * @pended: address of structure to fill with read address/len + * The data/len fields will be NULL/0 if no dma is pended. + * + * Returns the # of used bytes remaining in fifo (ie, if > 0, more data + * remains in the fifo that was not pended). If < 0, return is error code. + */ +int dma_fifo_out_pend(struct dma_fifo *fifo, struct dma_pending *pended) +{ + unsigned len, n, ofs, l, limit; + + if (fifo->data == NULL) + return -ENOENT; + if (fifo->corrupt) + return -ENXIO; + + pended->len = 0; + pended->data = NULL; + pended->out = fifo->out; + + len = fifo->in - fifo->out; + if (!len) + return -ENODATA; + if (fifo->open == fifo->open_limit) + return -EAGAIN; + + n = len; + ofs = fifo->out % fifo->capacity; + l = fifo->capacity - ofs; + limit = min_t(unsigned, l, fifo->tx_limit); + if (n > limit) { + n = limit; + fifo->out += limit; + } else if (ofs + n > fifo->guard) { + fifo->out += l; + fifo->in = fifo->out; + } else { + fifo->out += round_up(n, fifo->align); + fifo->in = fifo->out; + } + + df_trace("in: %u out: %u done: %u n: %d len: %u avail: %d", fifo->in, + fifo->out, fifo->done, n, len, fifo->avail); + + pended->len = n; + pended->data = fifo->data + ofs; + pended->next = fifo->out; + list_add_tail(&pended->link, &fifo->pending); + ++fifo->open; + + if (FAIL(fifo, fifo->open > fifo->open_limit, + "past open limit:%d (limit:%d)", + fifo->open, fifo->open_limit)) + return -ENXIO; + if (FAIL(fifo, fifo->out & (fifo->align - 1), + "fifo out unaligned:%u (align:%u)", + fifo->out, fifo->align)) + return -ENXIO; + + return len - n; +} + +/** + * dma_fifo_out_complete - marks pended dma as completed + * @fifo: address of in-place "struct dma_fifo" which was read from + * @complete: address of structure for previously pended dma to mark completed + */ +int dma_fifo_out_complete(struct dma_fifo *fifo, struct dma_pending *complete) +{ + struct dma_pending *pending, *next, *tmp; + + if (fifo->data == NULL) + return -ENOENT; + if (fifo->corrupt) + return -ENXIO; + if (list_empty(&fifo->pending) && fifo->open == 0) + return -EINVAL; + + if (FAIL(fifo, list_empty(&fifo->pending) != (fifo->open == 0), + "pending list disagrees with open count:%d", + fifo->open)) + return -ENXIO; + + tmp = complete->data; + *tmp = *complete; + list_replace(&complete->link, &tmp->link); + dp_mark_completed(tmp); + + /* Only update the fifo in the original pended order */ + list_for_each_entry_safe(pending, next, &fifo->pending, link) { + if (!dp_is_completed(pending)) { + df_trace("still pending: saved out: %u len: %d", + pending->out, pending->len); + break; + } + + if (FAIL(fifo, pending->out != fifo->done || + addr_check(fifo->in, fifo->done, pending->next), + "in:%u out:%u done:%u saved:%u next:%u", + fifo->in, fifo->out, fifo->done, pending->out, + pending->next)) + return -ENXIO; + + list_del_init(&pending->link); + fifo->done = pending->next; + fifo->avail += pending->len; + --fifo->open; + + df_trace("in: %u out: %u done: %u len: %u avail: %d", fifo->in, + fifo->out, fifo->done, pending->len, fifo->avail); + } + + if (FAIL(fifo, fifo->open < 0, "open dma:%d < 0", fifo->open)) + return -ENXIO; + if (FAIL(fifo, fifo->avail > fifo->size, "fifo avail:%d > size:%d", + fifo->avail, fifo->size)) + return -ENXIO; + + return 0; +} diff --git a/drivers/staging/fwserial/dma_fifo.h b/drivers/staging/fwserial/dma_fifo.h new file mode 100644 index 000000000000..a113fe1e6f19 --- /dev/null +++ b/drivers/staging/fwserial/dma_fifo.h @@ -0,0 +1,130 @@ +/* + * DMA-able FIFO interface + * + * Copyright (C) 2012 Peter Hurley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DMA_FIFO_H_ +#define _DMA_FIFO_H_ + +/** + * The design basis for the DMA FIFO is to provide an output side that + * complies with the streaming DMA API design that can be DMA'd from directly + * (without additional copying), coupled with an input side that maintains a + * logically consistent 'apparent' size (ie, bytes in + bytes avail is static + * for the lifetime of the FIFO). + * + * DMA output transactions originate on a cache line boundary and can be + * variably-sized. DMA output transactions can be retired out-of-order but + * the FIFO will only advance the output in the original input sequence. + * This means the FIFO will eventually stall if a transaction is never retired. + * + * Chunking the output side into cache line multiples means that some FIFO + * memory is unused. For example, if all the avail input has been pended out, + * then the in and out markers are re-aligned to the next cache line. + * The maximum possible waste is + * (cache line alignment - 1) * (max outstanding dma transactions) + * This potential waste requires additional hidden capacity within the FIFO + * to be able to accept input while the 'apparent' size has not been reached. + * + * Additional cache lines (ie, guard area) are used to minimize DMA + * fragmentation when wrapping at the end of the FIFO. Input is allowed into the + * guard area, but the in and out FIFO markers are wrapped when DMA is pended. + */ + +#define DMA_FIFO_GUARD 3 /* # of cache lines to reserve for the guard area */ + +struct dma_fifo { + unsigned in; + unsigned out; /* updated when dma is pended */ + unsigned done; /* updated upon dma completion */ + struct { + unsigned corrupt:1; + }; + int size; /* 'apparent' size of fifo */ + int guard; /* ofs of guard area */ + int capacity; /* size + reserved */ + int avail; /* # of unused bytes in fifo */ + unsigned align; /* must be power of 2 */ + int tx_limit; /* max # of bytes per dma transaction */ + int open_limit; /* max # of outstanding allowed */ + int open; /* # of outstanding dma transactions */ + struct list_head pending; /* fifo markers for outstanding dma */ + void *data; +}; + +struct dma_pending { + struct list_head link; + void *data; + unsigned len; + unsigned next; + unsigned out; +}; + +static inline void dp_mark_completed(struct dma_pending *dp) +{ + dp->data += 1; +} + +static inline bool dp_is_completed(struct dma_pending *dp) +{ + return (unsigned long)dp->data & 1UL; +} + +extern void dma_fifo_init(struct dma_fifo *fifo); +extern int dma_fifo_alloc(struct dma_fifo *fifo, int size, unsigned align, + int tx_limit, int open_limit, gfp_t gfp_mask); +extern void dma_fifo_free(struct dma_fifo *fifo); +extern void dma_fifo_reset(struct dma_fifo *fifo); +extern int dma_fifo_in(struct dma_fifo *fifo, const void *src, int n); +extern int dma_fifo_out_pend(struct dma_fifo *fifo, struct dma_pending *pended); +extern int dma_fifo_out_complete(struct dma_fifo *fifo, + struct dma_pending *complete); + +/* returns the # of used bytes in the fifo */ +static inline int dma_fifo_level(struct dma_fifo *fifo) +{ + return fifo->size - fifo->avail; +} + +/* returns the # of bytes ready for output in the fifo */ +static inline int dma_fifo_out_level(struct dma_fifo *fifo) +{ + return fifo->in - fifo->out; +} + +/* returns the # of unused bytes in the fifo */ +static inline int dma_fifo_avail(struct dma_fifo *fifo) +{ + return fifo->avail; +} + +/* returns true if fifo has max # of outstanding dmas */ +static inline bool dma_fifo_busy(struct dma_fifo *fifo) +{ + return fifo->open == fifo->open_limit; +} + +/* changes the max size of dma returned from dma_fifo_out_pend() */ +static inline int dma_fifo_change_tx_limit(struct dma_fifo *fifo, int tx_limit) +{ + tx_limit = round_down(tx_limit, fifo->align); + fifo->tx_limit = max_t(int, tx_limit, fifo->align); + return 0; +} + +#endif /* _DMA_FIFO_H_ */ diff --git a/drivers/staging/fwserial/fwserial.c b/drivers/staging/fwserial/fwserial.c new file mode 100644 index 000000000000..5d4d64a3ea81 --- /dev/null +++ b/drivers/staging/fwserial/fwserial.c @@ -0,0 +1,2946 @@ +/* + * FireWire Serial driver + * + * Copyright (C) 2012 Peter Hurley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fwserial.h" + +#define be32_to_u64(hi, lo) ((u64)be32_to_cpu(hi) << 32 | be32_to_cpu(lo)) + +#define LINUX_VENDOR_ID 0xd00d1eU /* same id used in card root directory */ +#define FWSERIAL_VERSION 0x00e81cU /* must be unique within LINUX_VENDOR_ID */ + +/* configurable options */ +static int num_ttys = 4; /* # of std ttys to create per fw_card */ + /* - doubles as loopback port index */ +static bool auto_connect = true; /* try to VIRT_CABLE to every peer */ +static bool create_loop_dev = true; /* create a loopback device for each card */ +bool limit_bw; /* limit async bandwidth to 20% of max */ + +module_param_named(ttys, num_ttys, int, S_IRUGO | S_IWUSR); +module_param_named(auto, auto_connect, bool, S_IRUGO | S_IWUSR); +module_param_named(loop, create_loop_dev, bool, S_IRUGO | S_IWUSR); +module_param(limit_bw, bool, S_IRUGO | S_IWUSR); + +/* + * Threshold below which the tty is woken for writing + * - should be equal to WAKEUP_CHARS in drivers/tty/n_tty.c because + * even if the writer is woken, n_tty_poll() won't set POLLOUT until + * our fifo is below this level + */ +#define WAKEUP_CHARS 256 + +/** + * fwserial_list: list of every fw_serial created for each fw_card + * See discussion in fwserial_probe. + */ +static LIST_HEAD(fwserial_list); +static DEFINE_MUTEX(fwserial_list_mutex); + +/** + * port_table: array of tty ports allocated to each fw_card + * + * tty ports are allocated during probe when an fw_serial is first + * created for a given fw_card. Ports are allocated in a contiguous block, + * each block consisting of 'num_ports' ports. + */ +static struct fwtty_port *port_table[MAX_TOTAL_PORTS]; +static DEFINE_MUTEX(port_table_lock); +static bool port_table_corrupt; +#define FWTTY_INVALID_INDEX MAX_TOTAL_PORTS + +/* total # of tty ports created per fw_card */ +static int num_ports; + +/* slab used as pool for struct fwtty_transactions */ +static struct kmem_cache *fwtty_txn_cache; + +struct fwtty_transaction; +typedef void (*fwtty_transaction_cb)(struct fw_card *card, int rcode, + void *data, size_t length, + struct fwtty_transaction *txn); + +struct fwtty_transaction { + struct fw_transaction fw_txn; + fwtty_transaction_cb callback; + struct fwtty_port *port; + union { + struct dma_pending dma_pended; + }; +}; + +#define to_device(a, b) (a->b) +#define fwtty_err(p, s, v...) dev_err(to_device(p, device), s, ##v) +#define fwtty_info(p, s, v...) dev_info(to_device(p, device), s, ##v) +#define fwtty_notice(p, s, v...) dev_notice(to_device(p, device), s, ##v) +#define fwtty_dbg(p, s, v...) \ + dev_dbg(to_device(p, device), "%s: " s, __func__, ##v) +#define fwtty_err_ratelimited(p, s, v...) \ + dev_err_ratelimited(to_device(p, device), s, ##v) + +#ifdef DEBUG +static inline void debug_short_write(struct fwtty_port *port, int c, int n) +{ + int avail; + + if (n < c) { + spin_lock_bh(&port->lock); + avail = dma_fifo_avail(&port->tx_fifo); + spin_unlock_bh(&port->lock); + fwtty_dbg(port, "short write: avail:%d req:%d wrote:%d", + avail, c, n); + } +} +#else +#define debug_short_write(port, c, n) +#endif + +static struct fwtty_peer *__fwserial_peer_by_node_id(struct fw_card *card, + int generation, int id); + +#ifdef FWTTY_PROFILING + +static void profile_fifo_avail(struct fwtty_port *port, unsigned *stat) +{ + spin_lock_bh(&port->lock); + profile_size_distrib(stat, dma_fifo_avail(&port->tx_fifo)); + spin_unlock_bh(&port->lock); +} + +static void dump_profile(struct seq_file *m, struct stats *stats) +{ + /* for each stat, print sum of 0 to 2^k, then individually */ + int k = 4; + unsigned sum; + int j; + char t[10]; + + snprintf(t, 10, "< %d", 1 << k); + seq_printf(m, "\n%14s %6s", " ", t); + for (j = k + 1; j < DISTRIBUTION_MAX_INDEX; ++j) + seq_printf(m, "%6d", 1 << j); + + ++k; + for (j = 0, sum = 0; j <= k; ++j) + sum += stats->reads[j]; + seq_printf(m, "\n%14s: %6d", "reads", sum); + for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j) + seq_printf(m, "%6d", stats->reads[j]); + + for (j = 0, sum = 0; j <= k; ++j) + sum += stats->writes[j]; + seq_printf(m, "\n%14s: %6d", "writes", sum); + for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j) + seq_printf(m, "%6d", stats->writes[j]); + + for (j = 0, sum = 0; j <= k; ++j) + sum += stats->txns[j]; + seq_printf(m, "\n%14s: %6d", "txns", sum); + for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j) + seq_printf(m, "%6d", stats->txns[j]); + + for (j = 0, sum = 0; j <= k; ++j) + sum += stats->unthrottle[j]; + seq_printf(m, "\n%14s: %6d", "avail @ unthr", sum); + for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j) + seq_printf(m, "%6d", stats->unthrottle[j]); +} + +#else +#define profile_fifo_avail(port, stat) +#define dump_profile(m, stats) +#endif + +/* Returns the max receive packet size for the given card */ +static inline int device_max_receive(struct fw_device *fw_device) +{ + return 1 << (clamp_t(int, fw_device->max_rec, 8U, 13U) + 1); +} + +static void fwtty_log_tx_error(struct fwtty_port *port, int rcode) +{ + switch (rcode) { + case RCODE_SEND_ERROR: + fwtty_err_ratelimited(port, "card busy"); + break; + case RCODE_ADDRESS_ERROR: + fwtty_err_ratelimited(port, "bad unit addr or write length"); + break; + case RCODE_DATA_ERROR: + fwtty_err_ratelimited(port, "failed rx"); + break; + case RCODE_NO_ACK: + fwtty_err_ratelimited(port, "missing ack"); + break; + case RCODE_BUSY: + fwtty_err_ratelimited(port, "remote busy"); + break; + default: + fwtty_err_ratelimited(port, "failed tx: %d", rcode); + } +} + +static void fwtty_txn_constructor(void *this) +{ + struct fwtty_transaction *txn = this; + + init_timer(&txn->fw_txn.split_timeout_timer); +} + +static void fwtty_common_callback(struct fw_card *card, int rcode, + void *payload, size_t len, void *cb_data) +{ + struct fwtty_transaction *txn = cb_data; + struct fwtty_port *port = txn->port; + + if (port && rcode != RCODE_COMPLETE) + fwtty_log_tx_error(port, rcode); + if (txn->callback) + txn->callback(card, rcode, payload, len, txn); + kmem_cache_free(fwtty_txn_cache, txn); +} + +static int fwtty_send_data_async(struct fwtty_peer *peer, int tcode, + unsigned long long addr, void *payload, + size_t len, fwtty_transaction_cb callback, + struct fwtty_port *port) +{ + struct fwtty_transaction *txn; + int generation; + + txn = kmem_cache_alloc(fwtty_txn_cache, GFP_ATOMIC); + if (!txn) + return -ENOMEM; + + txn->callback = callback; + txn->port = port; + + generation = peer->generation; + smp_rmb(); + fw_send_request(peer->serial->card, &txn->fw_txn, tcode, + peer->node_id, generation, peer->speed, addr, payload, + len, fwtty_common_callback, txn); + return 0; +} + +static void fwtty_send_txn_async(struct fwtty_peer *peer, + struct fwtty_transaction *txn, int tcode, + unsigned long long addr, void *payload, + size_t len, fwtty_transaction_cb callback, + struct fwtty_port *port) +{ + int generation; + + txn->callback = callback; + txn->port = port; + + generation = peer->generation; + smp_rmb(); + fw_send_request(peer->serial->card, &txn->fw_txn, tcode, + peer->node_id, generation, peer->speed, addr, payload, + len, fwtty_common_callback, txn); +} + + +static void __fwtty_restart_tx(struct fwtty_port *port) +{ + int len, avail; + + len = dma_fifo_out_level(&port->tx_fifo); + if (len) + schedule_delayed_work(&port->drain, 0); + avail = dma_fifo_avail(&port->tx_fifo); + + fwtty_dbg(port, "fifo len: %d avail: %d", len, avail); +} + +static void fwtty_restart_tx(struct fwtty_port *port) +{ + spin_lock_bh(&port->lock); + __fwtty_restart_tx(port); + spin_unlock_bh(&port->lock); +} + +/** + * fwtty_update_port_status - decodes & dispatches line status changes + * + * Note: in loopback, the port->lock is being held. Only use functions that + * don't attempt to reclaim the port->lock. + */ +static void fwtty_update_port_status(struct fwtty_port *port, unsigned status) +{ + unsigned delta; + struct tty_struct *tty; + + /* simulated LSR/MSR status from remote */ + status &= ~MCTRL_MASK; + delta = (port->mstatus ^ status) & ~MCTRL_MASK; + delta &= ~(status & TIOCM_RNG); + port->mstatus = status; + + if (delta & TIOCM_RNG) + ++port->icount.rng; + if (delta & TIOCM_DSR) + ++port->icount.dsr; + if (delta & TIOCM_CAR) + ++port->icount.dcd; + if (delta & TIOCM_CTS) + ++port->icount.cts; + + fwtty_dbg(port, "status: %x delta: %x", status, delta); + + if (delta & TIOCM_CAR) { + tty = tty_port_tty_get(&port->port); + if (tty && !C_CLOCAL(tty)) { + if (status & TIOCM_CAR) + wake_up_interruptible(&port->port.open_wait); + else + schedule_work(&port->hangup); + } + tty_kref_put(tty); + } + + if (delta & TIOCM_CTS) { + tty = tty_port_tty_get(&port->port); + if (tty && C_CRTSCTS(tty)) { + if (tty->hw_stopped) { + if (status & TIOCM_CTS) { + tty->hw_stopped = 0; + if (port->loopback) + __fwtty_restart_tx(port); + else + fwtty_restart_tx(port); + } + } else { + if (~status & TIOCM_CTS) + tty->hw_stopped = 1; + } + } + tty_kref_put(tty); + + } else if (delta & OOB_TX_THROTTLE) { + tty = tty_port_tty_get(&port->port); + if (tty) { + if (tty->hw_stopped) { + if (~status & OOB_TX_THROTTLE) { + tty->hw_stopped = 0; + if (port->loopback) + __fwtty_restart_tx(port); + else + fwtty_restart_tx(port); + } + } else { + if (status & OOB_TX_THROTTLE) + tty->hw_stopped = 1; + } + } + tty_kref_put(tty); + } + + if (delta & (UART_LSR_BI << 24)) { + if (status & (UART_LSR_BI << 24)) { + port->break_last = jiffies; + schedule_delayed_work(&port->emit_breaks, 0); + } else { + /* run emit_breaks one last time (if pending) */ + mod_delayed_work(system_wq, &port->emit_breaks, 0); + } + } + + if (delta & (TIOCM_DSR | TIOCM_CAR | TIOCM_CTS | TIOCM_RNG)) + wake_up_interruptible(&port->port.delta_msr_wait); +} + +/** + * __fwtty_port_line_status - generate 'line status' for indicated port + * + * This function returns a remote 'MSR' state based on the local 'MCR' state, + * as if a null modem cable was attached. The actual status is a mangling + * of TIOCM_* bits suitable for sending to a peer's status_addr. + * + * Note: caller must be holding port lock + */ +static unsigned __fwtty_port_line_status(struct fwtty_port *port) +{ + unsigned status = 0; + + /* TODO: add module param to tie RNG to DTR as well */ + + if (port->mctrl & TIOCM_DTR) + status |= TIOCM_DSR | TIOCM_CAR; + if (port->mctrl & TIOCM_RTS) + status |= TIOCM_CTS; + if (port->mctrl & OOB_RX_THROTTLE) + status |= OOB_TX_THROTTLE; + /* emulate BRK as add'l line status */ + if (port->break_ctl) + status |= UART_LSR_BI << 24; + + return status; +} + +/** + * __fwtty_write_port_status - send the port line status to peer + * + * Note: caller must be holding the port lock. + */ +static int __fwtty_write_port_status(struct fwtty_port *port) +{ + struct fwtty_peer *peer; + int err = -ENOENT; + unsigned status = __fwtty_port_line_status(port); + + rcu_read_lock(); + peer = rcu_dereference(port->peer); + if (peer) { + err = fwtty_send_data_async(peer, TCODE_WRITE_QUADLET_REQUEST, + peer->status_addr, &status, + sizeof(status), NULL, port); + } + rcu_read_unlock(); + + return err; +} + +/** + * fwtty_write_port_status - same as above but locked by port lock + */ +static int fwtty_write_port_status(struct fwtty_port *port) +{ + int err; + + spin_lock_bh(&port->lock); + err = __fwtty_write_port_status(port); + spin_unlock_bh(&port->lock); + return err; +} + +static void __fwtty_throttle(struct fwtty_port *port, struct tty_struct *tty) +{ + unsigned old; + + old = port->mctrl; + port->mctrl |= OOB_RX_THROTTLE; + if (C_CRTSCTS(tty)) + port->mctrl &= ~TIOCM_RTS; + if (~old & OOB_RX_THROTTLE) + __fwtty_write_port_status(port); +} + +/** + * fwtty_do_hangup - wait for ldisc to deliver all pending rx; only then hangup + * + * When the remote has finished tx, and all in-flight rx has been received and + * and pushed to the flip buffer, the remote may close its device. This will + * drop DTR on the remote which will drop carrier here. Typically, the tty is + * hung up when carrier is dropped or lost. + * + * However, there is a race between the hang up and the line discipline + * delivering its data to the reader. A hangup will cause the ldisc to flush + * (ie., clear) the read buffer and flip buffer. Because of firewire's + * relatively high throughput, the ldisc frequently lags well behind the driver, + * resulting in lost data (which has already been received and written to + * the flip buffer) when the remote closes its end. + * + * Unfortunately, since the flip buffer offers no direct method for determining + * if it holds data, ensuring the ldisc has delivered all data is problematic. + */ + +/* FIXME: drop this workaround when __tty_hangup waits for ldisc completion */ +static void fwtty_do_hangup(struct work_struct *work) +{ + struct fwtty_port *port = to_port(work, hangup); + struct tty_struct *tty; + + schedule_timeout_uninterruptible(msecs_to_jiffies(50)); + + tty = tty_port_tty_get(&port->port); + if (tty) + tty_vhangup(tty); + tty_kref_put(tty); +} + + +static void fwtty_emit_breaks(struct work_struct *work) +{ + struct fwtty_port *port = to_port(to_delayed_work(work), emit_breaks); + struct tty_struct *tty; + static const char buf[16]; + unsigned long now = jiffies; + unsigned long elapsed = now - port->break_last; + int n, t, c, brk = 0; + + tty = tty_port_tty_get(&port->port); + if (!tty) + return; + + /* generate breaks at the line rate (but at least 1) */ + n = (elapsed * port->cps) / HZ + 1; + port->break_last = now; + + fwtty_dbg(port, "sending %d brks", n); + + while (n) { + t = min(n, 16); + c = tty_insert_flip_string_fixed_flag(tty, buf, TTY_BREAK, t); + n -= c; + brk += c; + if (c < t) + break; + } + tty_flip_buffer_push(tty); + + tty_kref_put(tty); + + if (port->mstatus & (UART_LSR_BI << 24)) + schedule_delayed_work(&port->emit_breaks, FREQ_BREAKS); + port->icount.brk += brk; +} + +static void fwtty_pushrx(struct work_struct *work) +{ + struct fwtty_port *port = to_port(work, push); + struct tty_struct *tty; + struct buffered_rx *buf, *next; + int n, c = 0; + + tty = tty_port_tty_get(&port->port); + if (!tty) + return; + + spin_lock_bh(&port->lock); + list_for_each_entry_safe(buf, next, &port->buf_list, list) { + n = tty_insert_flip_string_fixed_flag(tty, buf->data, + TTY_NORMAL, buf->n); + c += n; + port->buffered -= n; + if (n < buf->n) { + if (n > 0) { + memmove(buf->data, buf->data + n, buf->n - n); + buf->n -= n; + } + __fwtty_throttle(port, tty); + break; + } else { + list_del(&buf->list); + kfree(buf); + } + } + if (c > 0) + tty_flip_buffer_push(tty); + + if (list_empty(&port->buf_list)) + clear_bit(BUFFERING_RX, &port->flags); + spin_unlock_bh(&port->lock); + + tty_kref_put(tty); +} + +static int fwtty_buffer_rx(struct fwtty_port *port, unsigned char *d, size_t n) +{ + struct buffered_rx *buf; + size_t size = (n + sizeof(struct buffered_rx) + 0xFF) & ~0xFF; + + if (port->buffered + n > HIGH_WATERMARK) + return 0; + buf = kmalloc(size, GFP_ATOMIC); + if (!buf) + return 0; + INIT_LIST_HEAD(&buf->list); + buf->n = n; + memcpy(buf->data, d, n); + + spin_lock_bh(&port->lock); + list_add_tail(&buf->list, &port->buf_list); + port->buffered += n; + if (port->buffered > port->stats.watermark) + port->stats.watermark = port->buffered; + set_bit(BUFFERING_RX, &port->flags); + spin_unlock_bh(&port->lock); + + return n; +} + +static int fwtty_rx(struct fwtty_port *port, unsigned char *data, size_t len) +{ + struct tty_struct *tty; + int c, n = len; + unsigned lsr; + int err = 0; + + tty = tty_port_tty_get(&port->port); + if (!tty) + return -ENOENT; + + fwtty_dbg(port, "%d", n); + profile_size_distrib(port->stats.reads, n); + + if (port->write_only) { + n = 0; + goto out; + } + + /* disregard break status; breaks are generated by emit_breaks work */ + lsr = (port->mstatus >> 24) & ~UART_LSR_BI; + + if (port->overrun) + lsr |= UART_LSR_OE; + + if (lsr & UART_LSR_OE) + ++port->icount.overrun; + + lsr &= port->status_mask; + if (lsr & ~port->ignore_mask & UART_LSR_OE) { + if (!tty_insert_flip_char(tty, 0, TTY_OVERRUN)) { + err = -EIO; + goto out; + } + } + port->overrun = false; + + if (lsr & port->ignore_mask & ~UART_LSR_OE) { + /* TODO: don't drop SAK and Magic SysRq here */ + n = 0; + goto out; + } + + if (!test_bit(BUFFERING_RX, &port->flags)) { + c = tty_insert_flip_string_fixed_flag(tty, data, TTY_NORMAL, n); + if (c > 0) + tty_flip_buffer_push(tty); + n -= c; + + if (n) { + /* start buffering and throttling */ + n -= fwtty_buffer_rx(port, &data[c], n); + + spin_lock_bh(&port->lock); + __fwtty_throttle(port, tty); + spin_unlock_bh(&port->lock); + } + } else + n -= fwtty_buffer_rx(port, data, n); + + if (n) { + port->overrun = true; + err = -EIO; + } + +out: + tty_kref_put(tty); + + port->icount.rx += len; + port->stats.lost += n; + return err; +} + +/** + * fwtty_port_handler - bus address handler for port reads/writes + * @parameters: fw_address_callback_t as specified by firewire core interface + * + * This handler is responsible for handling inbound read/write dma from remotes. + */ +static void fwtty_port_handler(struct fw_card *card, + struct fw_request *request, + int tcode, int destination, int source, + int generation, + unsigned long long addr, + void *data, size_t len, + void *callback_data) +{ + struct fwtty_port *port = callback_data; + struct fwtty_peer *peer; + int err; + int rcode; + + /* Only accept rx from the peer virtual-cabled to this port */ + rcu_read_lock(); + peer = __fwserial_peer_by_node_id(card, generation, source); + rcu_read_unlock(); + if (!peer || peer != rcu_access_pointer(port->peer)) { + rcode = RCODE_ADDRESS_ERROR; + fwtty_err_ratelimited(port, "ignoring unauthenticated data"); + goto respond; + } + + switch (tcode) { + case TCODE_WRITE_QUADLET_REQUEST: + if (addr != port->rx_handler.offset || len != 4) + rcode = RCODE_ADDRESS_ERROR; + else { + fwtty_update_port_status(port, *(unsigned *)data); + rcode = RCODE_COMPLETE; + } + break; + + case TCODE_WRITE_BLOCK_REQUEST: + if (addr != port->rx_handler.offset + 4 || + len > port->rx_handler.length - 4) { + rcode = RCODE_ADDRESS_ERROR; + } else { + err = fwtty_rx(port, data, len); + switch (err) { + case 0: + rcode = RCODE_COMPLETE; + break; + case -EIO: + rcode = RCODE_DATA_ERROR; + break; + default: + rcode = RCODE_CONFLICT_ERROR; + break; + } + } + break; + + default: + rcode = RCODE_TYPE_ERROR; + } + +respond: + fw_send_response(card, request, rcode); +} + +/** + * fwtty_tx_complete - callback for tx dma + * @data: ignored, has no meaning for write txns + * @length: ignored, has no meaning for write txns + * + * The writer must be woken here if the fifo has been emptied because it + * may have slept if chars_in_buffer was != 0 + */ +static void fwtty_tx_complete(struct fw_card *card, int rcode, + void *data, size_t length, + struct fwtty_transaction *txn) +{ + struct fwtty_port *port = txn->port; + struct tty_struct *tty; + int len; + + fwtty_dbg(port, "rcode: %d", rcode); + + switch (rcode) { + case RCODE_COMPLETE: + spin_lock_bh(&port->lock); + dma_fifo_out_complete(&port->tx_fifo, &txn->dma_pended); + len = dma_fifo_level(&port->tx_fifo); + spin_unlock_bh(&port->lock); + + port->icount.tx += txn->dma_pended.len; + break; + + default: + /* TODO: implement retries */ + spin_lock_bh(&port->lock); + dma_fifo_out_complete(&port->tx_fifo, &txn->dma_pended); + len = dma_fifo_level(&port->tx_fifo); + spin_unlock_bh(&port->lock); + + port->stats.dropped += txn->dma_pended.len; + } + + if (len < WAKEUP_CHARS) { + tty = tty_port_tty_get(&port->port); + if (tty) { + tty_wakeup(tty); + tty_kref_put(tty); + } + } +} + +static int fwtty_tx(struct fwtty_port *port, bool drain) +{ + struct fwtty_peer *peer; + struct fwtty_transaction *txn; + struct tty_struct *tty; + int n, len; + + tty = tty_port_tty_get(&port->port); + if (!tty) + return -ENOENT; + + rcu_read_lock(); + peer = rcu_dereference(port->peer); + if (!peer) { + n = -EIO; + goto out; + } + + if (test_and_set_bit(IN_TX, &port->flags)) { + n = -EALREADY; + goto out; + } + + /* try to write as many dma transactions out as possible */ + n = -EAGAIN; + while (!tty->stopped && !tty->hw_stopped && + !test_bit(STOP_TX, &port->flags)) { + txn = kmem_cache_alloc(fwtty_txn_cache, GFP_ATOMIC); + if (!txn) { + n = -ENOMEM; + break; + } + + spin_lock_bh(&port->lock); + n = dma_fifo_out_pend(&port->tx_fifo, &txn->dma_pended); + spin_unlock_bh(&port->lock); + + fwtty_dbg(port, "out: %u rem: %d", txn->dma_pended.len, n); + + if (n < 0) { + kmem_cache_free(fwtty_txn_cache, txn); + if (n == -EAGAIN) + ++port->stats.tx_stall; + else if (n == -ENODATA) + profile_size_distrib(port->stats.txns, 0); + else { + ++port->stats.fifo_errs; + fwtty_err_ratelimited(port, "fifo err: %d", n); + } + break; + } + + profile_size_distrib(port->stats.txns, txn->dma_pended.len); + + fwtty_send_txn_async(peer, txn, TCODE_WRITE_BLOCK_REQUEST, + peer->fifo_addr, txn->dma_pended.data, + txn->dma_pended.len, fwtty_tx_complete, + port); + ++port->stats.sent; + + /* + * Stop tx if the 'last view' of the fifo is empty or if + * this is the writer and there's not enough data to bother + */ + if (n == 0 || (!drain && n < WRITER_MINIMUM)) + break; + } + + if (n >= 0 || n == -EAGAIN || n == -ENOMEM || n == -ENODATA) { + spin_lock_bh(&port->lock); + len = dma_fifo_out_level(&port->tx_fifo); + if (len) { + unsigned long delay = (n == -ENOMEM) ? HZ : 1; + schedule_delayed_work(&port->drain, delay); + } + len = dma_fifo_level(&port->tx_fifo); + spin_unlock_bh(&port->lock); + + /* wakeup the writer */ + if (drain && len < WAKEUP_CHARS) + tty_wakeup(tty); + } + + clear_bit(IN_TX, &port->flags); + wake_up_interruptible(&port->wait_tx); + +out: + rcu_read_unlock(); + tty_kref_put(tty); + return n; +} + +static void fwtty_drain_tx(struct work_struct *work) +{ + struct fwtty_port *port = to_port(to_delayed_work(work), drain); + + fwtty_tx(port, true); +} + +static void fwtty_write_xchar(struct fwtty_port *port, char ch) +{ + struct fwtty_peer *peer; + + ++port->stats.xchars; + + fwtty_dbg(port, "%02x", ch); + + rcu_read_lock(); + peer = rcu_dereference(port->peer); + if (peer) { + fwtty_send_data_async(peer, TCODE_WRITE_BLOCK_REQUEST, + peer->fifo_addr, &ch, sizeof(ch), + NULL, port); + } + rcu_read_unlock(); +} + +struct fwtty_port *fwtty_port_get(unsigned index) +{ + struct fwtty_port *port; + + if (index >= MAX_TOTAL_PORTS) + return NULL; + + mutex_lock(&port_table_lock); + port = port_table[index]; + if (port) + kref_get(&port->serial->kref); + mutex_unlock(&port_table_lock); + return port; +} +EXPORT_SYMBOL(fwtty_port_get); + +static int fwtty_ports_add(struct fw_serial *serial) +{ + int err = -EBUSY; + int i, j; + + if (port_table_corrupt) + return err; + + mutex_lock(&port_table_lock); + for (i = 0; i + num_ports <= MAX_TOTAL_PORTS; i += num_ports) { + if (!port_table[i]) { + for (j = 0; j < num_ports; ++i, ++j) { + serial->ports[j]->index = i; + port_table[i] = serial->ports[j]; + } + err = 0; + break; + } + } + mutex_unlock(&port_table_lock); + return err; +} + +static void fwserial_destroy(struct kref *kref) +{ + struct fw_serial *serial = to_serial(kref, kref); + struct fwtty_port **ports = serial->ports; + int j, i = ports[0]->index; + + synchronize_rcu(); + + mutex_lock(&port_table_lock); + for (j = 0; j < num_ports; ++i, ++j) { + static bool once; + int corrupt = port_table[i] != ports[j]; + if (corrupt && !once) { + WARN(corrupt, "port_table[%d]: %p != ports[%d]: %p", + i, port_table[i], j, ports[j]); + once = true; + port_table_corrupt = true; + } + + port_table[i] = NULL; + } + mutex_unlock(&port_table_lock); + + for (j = 0; j < num_ports; ++j) { + fw_core_remove_address_handler(&ports[j]->rx_handler); + dma_fifo_free(&ports[j]->tx_fifo); + kfree(ports[j]); + } + kfree(serial); +} + +void fwtty_port_put(struct fwtty_port *port) +{ + kref_put(&port->serial->kref, fwserial_destroy); +} +EXPORT_SYMBOL(fwtty_port_put); + +static void fwtty_port_dtr_rts(struct tty_port *tty_port, int on) +{ + struct fwtty_port *port = to_port(tty_port, port); + + fwtty_dbg(port, "on/off: %d", on); + + spin_lock_bh(&port->lock); + /* Don't change carrier state if this is a console */ + if (!port->port.console) { + if (on) + port->mctrl |= TIOCM_DTR | TIOCM_RTS; + else + port->mctrl &= ~(TIOCM_DTR | TIOCM_RTS); + } + + __fwtty_write_port_status(port); + spin_unlock_bh(&port->lock); +} + +/** + * fwtty_port_carrier_raised: required tty_port operation + * + * This port operation is polled after a tty has been opened and is waiting for + * carrier detect -- see drivers/tty/tty_port:tty_port_block_til_ready(). + */ +static int fwtty_port_carrier_raised(struct tty_port *tty_port) +{ + struct fwtty_port *port = to_port(tty_port, port); + int rc; + + rc = (port->mstatus & TIOCM_CAR); + + fwtty_dbg(port, "%d", rc); + + return rc; +} + +static unsigned set_termios(struct fwtty_port *port, struct tty_struct *tty) +{ + unsigned baud, frame; + + baud = tty_termios_baud_rate(&tty->termios); + tty_termios_encode_baud_rate(&tty->termios, baud, baud); + + /* compute bit count of 2 frames */ + frame = 12 + ((C_CSTOPB(tty)) ? 4 : 2) + ((C_PARENB(tty)) ? 2 : 0); + + switch (C_CSIZE(tty)) { + case CS5: + frame -= (C_CSTOPB(tty)) ? 1 : 0; + break; + case CS6: + frame += 2; + break; + case CS7: + frame += 4; + break; + case CS8: + frame += 6; + break; + } + + port->cps = (baud << 1) / frame; + + port->status_mask = UART_LSR_OE; + if (_I_FLAG(tty, BRKINT | PARMRK)) + port->status_mask |= UART_LSR_BI; + + port->ignore_mask = 0; + if (I_IGNBRK(tty)) { + port->ignore_mask |= UART_LSR_BI; + if (I_IGNPAR(tty)) + port->ignore_mask |= UART_LSR_OE; + } + + port->write_only = !C_CREAD(tty); + + /* turn off echo and newline xlat if loopback */ + if (port->loopback) { + tty->termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHOKE | + ECHONL | ECHOPRT | ECHOCTL); + tty->termios.c_oflag &= ~ONLCR; + } + + return baud; +} + +static int fwtty_port_activate(struct tty_port *tty_port, + struct tty_struct *tty) +{ + struct fwtty_port *port = to_port(tty_port, port); + unsigned baud; + int err; + + set_bit(TTY_IO_ERROR, &tty->flags); + + err = dma_fifo_alloc(&port->tx_fifo, FWTTY_PORT_TXFIFO_LEN, + cache_line_size(), + port->max_payload, + FWTTY_PORT_MAX_PEND_DMA, + GFP_KERNEL); + if (err) + return err; + + spin_lock_bh(&port->lock); + + baud = set_termios(port, tty); + + /* if console, don't change carrier state */ + if (!port->port.console) { + port->mctrl = 0; + if (baud != 0) + port->mctrl = TIOCM_DTR | TIOCM_RTS; + } + + if (C_CRTSCTS(tty) && ~port->mstatus & TIOCM_CTS) + tty->hw_stopped = 1; + + __fwtty_write_port_status(port); + spin_unlock_bh(&port->lock); + + clear_bit(TTY_IO_ERROR, &tty->flags); + + return 0; +} + +/** + * fwtty_port_shutdown + * + * Note: the tty port core ensures this is not the console and + * manages TTY_IO_ERROR properly + */ +static void fwtty_port_shutdown(struct tty_port *tty_port) +{ + struct fwtty_port *port = to_port(tty_port, port); + struct buffered_rx *buf, *next; + + /* TODO: cancel outstanding transactions */ + + cancel_delayed_work_sync(&port->emit_breaks); + cancel_delayed_work_sync(&port->drain); + cancel_work_sync(&port->push); + + spin_lock_bh(&port->lock); + list_for_each_entry_safe(buf, next, &port->buf_list, list) { + list_del(&buf->list); + kfree(buf); + } + port->buffered = 0; + port->flags = 0; + port->break_ctl = 0; + port->overrun = 0; + __fwtty_write_port_status(port); + dma_fifo_free(&port->tx_fifo); + spin_unlock_bh(&port->lock); +} + +static int fwtty_open(struct tty_struct *tty, struct file *fp) +{ + struct fwtty_port *port = tty->driver_data; + + return tty_port_open(&port->port, tty, fp); +} + +static void fwtty_close(struct tty_struct *tty, struct file *fp) +{ + struct fwtty_port *port = tty->driver_data; + + tty_port_close(&port->port, tty, fp); +} + +static void fwtty_hangup(struct tty_struct *tty) +{ + struct fwtty_port *port = tty->driver_data; + + tty_port_hangup(&port->port); +} + +static void fwtty_cleanup(struct tty_struct *tty) +{ + struct fwtty_port *port = tty->driver_data; + + tty->driver_data = NULL; + fwtty_port_put(port); +} + +static int fwtty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct fwtty_port *port = fwtty_port_get(tty->index); + int err; + + err = tty_standard_install(driver, tty); + if (!err) + tty->driver_data = port; + else + fwtty_port_put(port); + return err; +} + +static int fwtty_write(struct tty_struct *tty, const unsigned char *buf, int c) +{ + struct fwtty_port *port = tty->driver_data; + int n, len; + + fwtty_dbg(port, "%d", c); + profile_size_distrib(port->stats.writes, c); + + spin_lock_bh(&port->lock); + n = dma_fifo_in(&port->tx_fifo, buf, c); + len = dma_fifo_out_level(&port->tx_fifo); + if (len < DRAIN_THRESHOLD) + schedule_delayed_work(&port->drain, 1); + spin_unlock_bh(&port->lock); + + if (len >= DRAIN_THRESHOLD) + fwtty_tx(port, false); + + debug_short_write(port, c, n); + + return (n < 0) ? 0 : n; +} + +static int fwtty_write_room(struct tty_struct *tty) +{ + struct fwtty_port *port = tty->driver_data; + int n; + + spin_lock_bh(&port->lock); + n = dma_fifo_avail(&port->tx_fifo); + spin_unlock_bh(&port->lock); + + fwtty_dbg(port, "%d", n); + + return n; +} + +static int fwtty_chars_in_buffer(struct tty_struct *tty) +{ + struct fwtty_port *port = tty->driver_data; + int n; + + spin_lock_bh(&port->lock); + n = dma_fifo_level(&port->tx_fifo); + spin_unlock_bh(&port->lock); + + fwtty_dbg(port, "%d", n); + + return n; +} + +static void fwtty_send_xchar(struct tty_struct *tty, char ch) +{ + struct fwtty_port *port = tty->driver_data; + + fwtty_dbg(port, "%02x", ch); + + fwtty_write_xchar(port, ch); +} + +static void fwtty_throttle(struct tty_struct *tty) +{ + struct fwtty_port *port = tty->driver_data; + + /* + * Ignore throttling (but not unthrottling). + * It only makes sense to throttle when data will no longer be + * accepted by the tty flip buffer. For example, it is + * possible for received data to overflow the tty buffer long + * before the line discipline ever has a chance to throttle the driver. + * Additionally, the driver may have already completed the I/O + * but the tty buffer is still emptying, so the line discipline is + * throttling and unthrottling nothing. + */ + + ++port->stats.throttled; +} + +static void fwtty_unthrottle(struct tty_struct *tty) +{ + struct fwtty_port *port = tty->driver_data; + + fwtty_dbg(port, "CRTSCTS: %d", (C_CRTSCTS(tty) != 0)); + + profile_fifo_avail(port, port->stats.unthrottle); + + schedule_work(&port->push); + + spin_lock_bh(&port->lock); + port->mctrl &= ~OOB_RX_THROTTLE; + if (C_CRTSCTS(tty)) + port->mctrl |= TIOCM_RTS; + __fwtty_write_port_status(port); + spin_unlock_bh(&port->lock); +} + +static int check_msr_delta(struct fwtty_port *port, unsigned long mask, + struct async_icount *prev) +{ + struct async_icount now; + int delta; + + now = port->icount; + + delta = ((mask & TIOCM_RNG && prev->rng != now.rng) || + (mask & TIOCM_DSR && prev->dsr != now.dsr) || + (mask & TIOCM_CAR && prev->dcd != now.dcd) || + (mask & TIOCM_CTS && prev->cts != now.cts)); + + *prev = now; + + return delta; +} + +static int wait_msr_change(struct fwtty_port *port, unsigned long mask) +{ + struct async_icount prev; + + prev = port->icount; + + return wait_event_interruptible(port->port.delta_msr_wait, + check_msr_delta(port, mask, &prev)); +} + +static int get_serial_info(struct fwtty_port *port, + struct serial_struct __user *info) +{ + struct serial_struct tmp; + + memset(&tmp, 0, sizeof(tmp)); + + tmp.type = PORT_UNKNOWN; + tmp.line = port->port.tty->index; + tmp.flags = port->port.flags; + tmp.xmit_fifo_size = FWTTY_PORT_TXFIFO_LEN; + tmp.baud_base = 400000000; + tmp.close_delay = port->port.close_delay; + + return (copy_to_user(info, &tmp, sizeof(*info))) ? -EFAULT : 0; +} + +static int set_serial_info(struct fwtty_port *port, + struct serial_struct __user *info) +{ + struct serial_struct tmp; + + if (copy_from_user(&tmp, info, sizeof(tmp))) + return -EFAULT; + + if (tmp.irq != 0 || tmp.port != 0 || tmp.custom_divisor != 0 || + tmp.baud_base != 400000000) + return -EPERM; + + if (!capable(CAP_SYS_ADMIN)) { + if (((tmp.flags & ~ASYNC_USR_MASK) != + (port->port.flags & ~ASYNC_USR_MASK))) + return -EPERM; + } else + port->port.close_delay = tmp.close_delay * HZ / 100; + + return 0; +} + +static int fwtty_ioctl(struct tty_struct *tty, unsigned cmd, + unsigned long arg) +{ + struct fwtty_port *port = tty->driver_data; + int err; + + switch (cmd) { + case TIOCGSERIAL: + mutex_lock(&port->port.mutex); + err = get_serial_info(port, (void __user *)arg); + mutex_unlock(&port->port.mutex); + break; + + case TIOCSSERIAL: + mutex_lock(&port->port.mutex); + err = set_serial_info(port, (void __user *)arg); + mutex_unlock(&port->port.mutex); + break; + + case TIOCMIWAIT: + err = wait_msr_change(port, arg); + break; + + default: + err = -ENOIOCTLCMD; + } + + return err; +} + +static void fwtty_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + struct fwtty_port *port = tty->driver_data; + unsigned baud; + + spin_lock_bh(&port->lock); + baud = set_termios(port, tty); + + if ((baud == 0) && (old->c_cflag & CBAUD)) + port->mctrl &= ~(TIOCM_DTR | TIOCM_RTS); + else if ((baud != 0) && !(old->c_cflag & CBAUD)) { + if (C_CRTSCTS(tty) || !test_bit(TTY_THROTTLED, &tty->flags)) + port->mctrl |= TIOCM_DTR | TIOCM_RTS; + else + port->mctrl |= TIOCM_DTR; + } + __fwtty_write_port_status(port); + spin_unlock_bh(&port->lock); + + if (old->c_cflag & CRTSCTS) { + if (!C_CRTSCTS(tty)) { + tty->hw_stopped = 0; + fwtty_restart_tx(port); + } + } else if (C_CRTSCTS(tty) && ~port->mstatus & TIOCM_CTS) { + tty->hw_stopped = 1; + } +} + +/** + * fwtty_break_ctl - start/stop sending breaks + * + * Signals the remote to start or stop generating simulated breaks. + * First, stop dequeueing from the fifo and wait for writer/drain to leave tx + * before signalling the break line status. This guarantees any pending rx will + * be queued to the line discipline before break is simulated on the remote. + * Conversely, turning off break_ctl requires signalling the line status change, + * then enabling tx. + */ +static int fwtty_break_ctl(struct tty_struct *tty, int state) +{ + struct fwtty_port *port = tty->driver_data; + long ret; + + fwtty_dbg(port, "%d", state); + + if (state == -1) { + set_bit(STOP_TX, &port->flags); + ret = wait_event_interruptible_timeout(port->wait_tx, + !test_bit(IN_TX, &port->flags), + 10); + if (ret == 0 || ret == -ERESTARTSYS) { + clear_bit(STOP_TX, &port->flags); + fwtty_restart_tx(port); + return -EINTR; + } + } + + spin_lock_bh(&port->lock); + port->break_ctl = (state == -1); + __fwtty_write_port_status(port); + spin_unlock_bh(&port->lock); + + if (state == 0) { + spin_lock_bh(&port->lock); + dma_fifo_reset(&port->tx_fifo); + clear_bit(STOP_TX, &port->flags); + spin_unlock_bh(&port->lock); + } + return 0; +} + +static int fwtty_tiocmget(struct tty_struct *tty) +{ + struct fwtty_port *port = tty->driver_data; + unsigned tiocm; + + spin_lock_bh(&port->lock); + tiocm = (port->mctrl & MCTRL_MASK) | (port->mstatus & ~MCTRL_MASK); + spin_unlock_bh(&port->lock); + + fwtty_dbg(port, "%x", tiocm); + + return tiocm; +} + +static int fwtty_tiocmset(struct tty_struct *tty, unsigned set, unsigned clear) +{ + struct fwtty_port *port = tty->driver_data; + + fwtty_dbg(port, "set: %x clear: %x", set, clear); + + /* TODO: simulate loopback if TIOCM_LOOP set */ + + spin_lock_bh(&port->lock); + port->mctrl &= ~(clear & MCTRL_MASK & 0xffff); + port->mctrl |= set & MCTRL_MASK & 0xffff; + __fwtty_write_port_status(port); + spin_unlock_bh(&port->lock); + return 0; +} + +static int fwtty_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct fwtty_port *port = tty->driver_data; + struct stats stats; + + memcpy(&stats, &port->stats, sizeof(stats)); + if (port->port.console) + (*port->fwcon_ops->stats)(&stats, port->con_data); + + icount->cts = port->icount.cts; + icount->dsr = port->icount.dsr; + icount->rng = port->icount.rng; + icount->dcd = port->icount.dcd; + icount->rx = port->icount.rx; + icount->tx = port->icount.tx + stats.xchars; + icount->frame = port->icount.frame; + icount->overrun = port->icount.overrun; + icount->parity = port->icount.parity; + icount->brk = port->icount.brk; + icount->buf_overrun = port->icount.overrun; + return 0; +} + +static void fwtty_proc_show_port(struct seq_file *m, struct fwtty_port *port) +{ + struct stats stats; + + memcpy(&stats, &port->stats, sizeof(stats)); + if (port->port.console) + (*port->fwcon_ops->stats)(&stats, port->con_data); + + seq_printf(m, " tx:%d rx:%d", port->icount.tx + stats.xchars, + port->icount.rx); + seq_printf(m, " cts:%d dsr:%d rng:%d dcd:%d", port->icount.cts, + port->icount.dsr, port->icount.rng, port->icount.dcd); + seq_printf(m, " fe:%d oe:%d pe:%d brk:%d", port->icount.frame, + port->icount.overrun, port->icount.parity, port->icount.brk); + seq_printf(m, " dr:%d st:%d err:%d lost:%d", stats.dropped, + stats.tx_stall, stats.fifo_errs, stats.lost); + seq_printf(m, " pkts:%d thr:%d wtrmk:%d", stats.sent, stats.throttled, + stats.watermark); + seq_printf(m, " addr:%012llx", port->rx_handler.offset); + + if (port->port.console) { + seq_printf(m, "\n "); + (*port->fwcon_ops->proc_show)(m, port->con_data); + } + + dump_profile(m, &port->stats); +} + +static void fwtty_proc_show_peer(struct seq_file *m, struct fwtty_peer *peer) +{ + int generation = peer->generation; + + smp_rmb(); + seq_printf(m, " %s:", dev_name(&peer->unit->device)); + seq_printf(m, " node:%04x gen:%d", peer->node_id, generation); + seq_printf(m, " sp:%d max:%d guid:%016llx", peer->speed, + peer->max_payload, (unsigned long long) peer->guid); + + if (capable(CAP_SYS_ADMIN)) { + seq_printf(m, " mgmt:%012llx", + (unsigned long long) peer->mgmt_addr); + seq_printf(m, " addr:%012llx", + (unsigned long long) peer->status_addr); + } + seq_putc(m, '\n'); +} + +static int fwtty_proc_show(struct seq_file *m, void *v) +{ + struct fwtty_port *port; + struct fw_serial *serial; + struct fwtty_peer *peer; + int i; + + seq_puts(m, "fwserinfo: 1.0 driver: 1.0\n"); + for (i = 0; i < MAX_TOTAL_PORTS && (port = fwtty_port_get(i)); ++i) { + seq_printf(m, "%2d:", i); + if (capable(CAP_SYS_ADMIN)) + fwtty_proc_show_port(m, port); + fwtty_port_put(port); + seq_printf(m, "\n"); + } + seq_putc(m, '\n'); + + rcu_read_lock(); + list_for_each_entry_rcu(serial, &fwserial_list, list) { + seq_printf(m, "card: %s guid: %016llx\n", + dev_name(serial->card->device), + (unsigned long long) serial->card->guid); + list_for_each_entry_rcu(peer, &serial->peer_list, list) + fwtty_proc_show_peer(m, peer); + } + rcu_read_unlock(); + return 0; +} + +static int fwtty_proc_open(struct inode *inode, struct file *fp) +{ + return single_open(fp, fwtty_proc_show, NULL); +} + +static const struct file_operations fwtty_proc_fops = { + .owner = THIS_MODULE, + .open = fwtty_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct tty_port_operations fwtty_port_ops = { + .dtr_rts = fwtty_port_dtr_rts, + .carrier_raised = fwtty_port_carrier_raised, + .shutdown = fwtty_port_shutdown, + .activate = fwtty_port_activate, +}; + +static const struct tty_operations fwtty_ops = { + .open = fwtty_open, + .close = fwtty_close, + .hangup = fwtty_hangup, + .cleanup = fwtty_cleanup, + .install = fwtty_install, + .write = fwtty_write, + .write_room = fwtty_write_room, + .chars_in_buffer = fwtty_chars_in_buffer, + .send_xchar = fwtty_send_xchar, + .throttle = fwtty_throttle, + .unthrottle = fwtty_unthrottle, + .ioctl = fwtty_ioctl, + .set_termios = fwtty_set_termios, + .break_ctl = fwtty_break_ctl, + .tiocmget = fwtty_tiocmget, + .tiocmset = fwtty_tiocmset, + .get_icount = fwtty_get_icount, + .proc_fops = &fwtty_proc_fops, +}; + +static inline int mgmt_pkt_expected_len(__be16 code) +{ + static const struct fwserial_mgmt_pkt pkt; + + switch (be16_to_cpu(code)) { + case FWSC_VIRT_CABLE_PLUG: + return sizeof(pkt.hdr) + sizeof(pkt.plug_req); + + case FWSC_VIRT_CABLE_PLUG_RSP: /* | FWSC_RSP_OK */ + return sizeof(pkt.hdr) + sizeof(pkt.plug_rsp); + + + case FWSC_VIRT_CABLE_UNPLUG: + case FWSC_VIRT_CABLE_UNPLUG_RSP: + case FWSC_VIRT_CABLE_PLUG_RSP | FWSC_RSP_NACK: + case FWSC_VIRT_CABLE_UNPLUG_RSP | FWSC_RSP_NACK: + return sizeof(pkt.hdr); + + default: + return -1; + } +} + +static inline void fill_plug_params(struct virt_plug_params *params, + struct fwtty_port *port) +{ + u64 status_addr = port->rx_handler.offset; + u64 fifo_addr = port->rx_handler.offset + 4; + size_t fifo_len = port->rx_handler.length - 4; + + params->status_hi = cpu_to_be32(status_addr >> 32); + params->status_lo = cpu_to_be32(status_addr); + params->fifo_hi = cpu_to_be32(fifo_addr >> 32); + params->fifo_lo = cpu_to_be32(fifo_addr); + params->fifo_len = cpu_to_be32(fifo_len); +} + +static inline void fill_plug_req(struct fwserial_mgmt_pkt *pkt, + struct fwtty_port *port) +{ + pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG); + pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code)); + fill_plug_params(&pkt->plug_req, port); +} + +static inline void fill_plug_rsp_ok(struct fwserial_mgmt_pkt *pkt, + struct fwtty_port *port) +{ + pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG_RSP); + pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code)); + fill_plug_params(&pkt->plug_rsp, port); +} + +static inline void fill_plug_rsp_nack(struct fwserial_mgmt_pkt *pkt) +{ + pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG_RSP | FWSC_RSP_NACK); + pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code)); +} + +static inline void fill_unplug_req(struct fwserial_mgmt_pkt *pkt) +{ + pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG); + pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code)); +} + +static inline void fill_unplug_rsp_nack(struct fwserial_mgmt_pkt *pkt) +{ + pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG_RSP | FWSC_RSP_NACK); + pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code)); +} + +static inline void fill_unplug_rsp_ok(struct fwserial_mgmt_pkt *pkt) +{ + pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG_RSP); + pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code)); +} + +static void fwserial_virt_plug_complete(struct fwtty_peer *peer, + struct virt_plug_params *params) +{ + struct fwtty_port *port = peer->port; + + peer->status_addr = be32_to_u64(params->status_hi, params->status_lo); + peer->fifo_addr = be32_to_u64(params->fifo_hi, params->fifo_lo); + peer->fifo_len = be32_to_cpu(params->fifo_len); + peer_set_state(peer, FWPS_ATTACHED); + + /* reconfigure tx_fifo optimally for this peer */ + spin_lock_bh(&port->lock); + port->max_payload = min3(peer->max_payload, peer->fifo_len, + MAX_ASYNC_PAYLOAD); + dma_fifo_change_tx_limit(&port->tx_fifo, port->max_payload); + spin_unlock_bh(&peer->port->lock); + + if (port->port.console && port->fwcon_ops->notify != NULL) + (*port->fwcon_ops->notify)(FWCON_NOTIFY_ATTACH, port->con_data); + + fwtty_info(&peer->unit, "peer (guid:%016llx) connected on %s", + (unsigned long long)peer->guid, dev_name(port->device)); +} + +static inline int fwserial_send_mgmt_sync(struct fwtty_peer *peer, + struct fwserial_mgmt_pkt *pkt) +{ + int generation; + int rcode, tries = 5; + + do { + generation = peer->generation; + smp_rmb(); + + rcode = fw_run_transaction(peer->serial->card, + TCODE_WRITE_BLOCK_REQUEST, + peer->node_id, + generation, peer->speed, + peer->mgmt_addr, + pkt, be16_to_cpu(pkt->hdr.len)); + if (rcode == RCODE_BUSY || rcode == RCODE_SEND_ERROR || + rcode == RCODE_GENERATION) { + fwtty_dbg(&peer->unit, "mgmt write error: %d", rcode); + continue; + } else + break; + } while (--tries > 0); + return rcode; +} + +/** + * fwserial_claim_port - attempt to claim port @ index for peer + * + * Returns ptr to claimed port or error code (as ERR_PTR()) + * Can sleep - must be called from process context + */ +static struct fwtty_port *fwserial_claim_port(struct fwtty_peer *peer, + int index) +{ + struct fwtty_port *port; + + if (index < 0 || index >= num_ports) + return ERR_PTR(-EINVAL); + + /* must guarantee that previous port releases have completed */ + synchronize_rcu(); + + port = peer->serial->ports[index]; + spin_lock_bh(&port->lock); + if (!rcu_access_pointer(port->peer)) + rcu_assign_pointer(port->peer, peer); + else + port = ERR_PTR(-EBUSY); + spin_unlock_bh(&port->lock); + + return port; +} + +/** + * fwserial_find_port - find avail port and claim for peer + * + * Returns ptr to claimed port or NULL if none avail + * Can sleep - must be called from process context + */ +static struct fwtty_port *fwserial_find_port(struct fwtty_peer *peer) +{ + struct fwtty_port **ports = peer->serial->ports; + int i; + + /* must guarantee that previous port releases have completed */ + synchronize_rcu(); + + /* TODO: implement optional GUID-to-specific port # matching */ + + /* find an unattached port (but not the loopback port, if present) */ + for (i = 0; i < num_ttys; ++i) { + spin_lock_bh(&ports[i]->lock); + if (!ports[i]->peer) { + /* claim port */ + rcu_assign_pointer(ports[i]->peer, peer); + spin_unlock_bh(&ports[i]->lock); + return ports[i]; + } + spin_unlock_bh(&ports[i]->lock); + } + return NULL; +} + +static void fwserial_release_port(struct fwtty_port *port) +{ + /* drop carrier (and all other line status) */ + fwtty_update_port_status(port, 0); + + spin_lock_bh(&port->lock); + + /* reset dma fifo max transmission size back to S100 */ + port->max_payload = link_speed_to_max_payload(SCODE_100); + dma_fifo_change_tx_limit(&port->tx_fifo, port->max_payload); + + rcu_assign_pointer(port->peer, NULL); + spin_unlock_bh(&port->lock); + + if (port->port.console && port->fwcon_ops->notify != NULL) + (*port->fwcon_ops->notify)(FWCON_NOTIFY_DETACH, port->con_data); +} + +static void fwserial_plug_timeout(unsigned long data) +{ + struct fwtty_peer *peer = (struct fwtty_peer *) data; + struct fwtty_port *port; + + spin_lock_bh(&peer->lock); + if (peer->state != FWPS_PLUG_PENDING) { + spin_unlock_bh(&peer->lock); + return; + } + + port = peer_revert_state(peer); + spin_unlock_bh(&peer->lock); + + if (port) + fwserial_release_port(port); +} + +/** + * fwserial_connect_peer - initiate virtual cable with peer + * + * Returns 0 if VIRT_CABLE_PLUG request was successfully sent, + * otherwise error code. Must be called from process context. + */ +static int fwserial_connect_peer(struct fwtty_peer *peer) +{ + struct fwtty_port *port; + struct fwserial_mgmt_pkt *pkt; + int err, rcode; + + pkt = kmalloc(sizeof(*pkt), GFP_KERNEL); + if (!pkt) + return -ENOMEM; + + port = fwserial_find_port(peer); + if (!port) { + fwtty_err(&peer->unit, "avail ports in use"); + err = -EBUSY; + goto free_pkt; + } + + spin_lock_bh(&peer->lock); + + /* only initiate VIRT_CABLE_PLUG if peer is currently not attached */ + if (peer->state != FWPS_NOT_ATTACHED) { + err = -EBUSY; + goto release_port; + } + + peer->port = port; + peer_set_state(peer, FWPS_PLUG_PENDING); + + fill_plug_req(pkt, peer->port); + + setup_timer(&peer->timer, fwserial_plug_timeout, (unsigned long)peer); + mod_timer(&peer->timer, jiffies + VIRT_CABLE_PLUG_TIMEOUT); + spin_unlock_bh(&peer->lock); + + rcode = fwserial_send_mgmt_sync(peer, pkt); + + spin_lock_bh(&peer->lock); + if (peer->state == FWPS_PLUG_PENDING && rcode != RCODE_COMPLETE) { + if (rcode == RCODE_CONFLICT_ERROR) + err = -EAGAIN; + else + err = -EIO; + goto cancel_timer; + } + spin_unlock_bh(&peer->lock); + + kfree(pkt); + return 0; + +cancel_timer: + del_timer(&peer->timer); + peer_revert_state(peer); +release_port: + spin_unlock_bh(&peer->lock); + fwserial_release_port(port); +free_pkt: + kfree(pkt); + return err; +} + +/** + * fwserial_close_port - + * HUP the tty (if the tty exists) and unregister the tty device. + * Only used by the unit driver upon unit removal to disconnect and + * cleanup all attached ports + * + * The port reference is put by fwtty_cleanup (if a reference was + * ever taken). + */ +static void fwserial_close_port(struct fwtty_port *port) +{ + struct tty_struct *tty; + + mutex_lock(&port->port.mutex); + tty = tty_port_tty_get(&port->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + mutex_unlock(&port->port.mutex); + + tty_unregister_device(fwtty_driver, port->index); +} + +/** + * fwserial_lookup - finds first fw_serial associated with card + * @card: fw_card to match + * + * NB: caller must be holding fwserial_list_mutex + */ +static struct fw_serial *fwserial_lookup(struct fw_card *card) +{ + struct fw_serial *serial; + + list_for_each_entry(serial, &fwserial_list, list) { + if (card == serial->card) + return serial; + } + + return NULL; +} + +/** + * __fwserial_lookup_rcu - finds first fw_serial associated with card + * @card: fw_card to match + * + * NB: caller must be inside rcu_read_lock() section + */ +static struct fw_serial *__fwserial_lookup_rcu(struct fw_card *card) +{ + struct fw_serial *serial; + + list_for_each_entry_rcu(serial, &fwserial_list, list) { + if (card == serial->card) + return serial; + } + + return NULL; +} + +/** + * __fwserial_peer_by_node_id - finds a peer matching the given generation + id + * + * If a matching peer could not be found for the specified generation/node id, + * this could be because: + * a) the generation has changed and one of the nodes hasn't updated yet + * b) the remote node has created its remote unit device before this + * local node has created its corresponding remote unit device + * In either case, the remote node should retry + * + * Note: caller must be in rcu_read_lock() section + */ +static struct fwtty_peer *__fwserial_peer_by_node_id(struct fw_card *card, + int generation, int id) +{ + struct fw_serial *serial; + struct fwtty_peer *peer; + + serial = __fwserial_lookup_rcu(card); + if (!serial) { + /* + * Something is very wrong - there should be a matching + * fw_serial structure for every fw_card. Maybe the remote node + * has created its remote unit device before this driver has + * been probed for any unit devices... + */ + fwtty_err(card, "unknown card (guid %016llx)", + (unsigned long long) card->guid); + return NULL; + } + + list_for_each_entry_rcu(peer, &serial->peer_list, list) { + int g = peer->generation; + smp_rmb(); + if (generation == g && id == peer->node_id) + return peer; + } + + return NULL; +} + +#ifdef DEBUG +static void __dump_peer_list(struct fw_card *card) +{ + struct fw_serial *serial; + struct fwtty_peer *peer; + + serial = __fwserial_lookup_rcu(card); + if (!serial) + return; + + list_for_each_entry_rcu(peer, &serial->peer_list, list) { + int g = peer->generation; + smp_rmb(); + fwtty_dbg(card, "peer(%d:%x) guid: %016llx\n", g, + peer->node_id, (unsigned long long) peer->guid); + } +} +#else +#define __dump_peer_list(s) +#endif + +static void fwserial_auto_connect(struct work_struct *work) +{ + struct fwtty_peer *peer = to_peer(to_delayed_work(work), connect); + int err; + + err = fwserial_connect_peer(peer); + if (err == -EAGAIN && ++peer->connect_retries < MAX_CONNECT_RETRIES) + schedule_delayed_work(&peer->connect, CONNECT_RETRY_DELAY); +} + +/** + * fwserial_add_peer - add a newly probed 'serial' unit device as a 'peer' + * @serial: aggregate representing the specific fw_card to add the peer to + * @unit: 'peer' to create and add to peer_list of serial + * + * Adds a 'peer' (ie, a local or remote 'serial' unit device) to the list of + * peers for a specific fw_card. Optionally, auto-attach this peer to an + * available tty port. This function is called either directly or indirectly + * as a result of a 'serial' unit device being created & probed. + * + * Note: this function is serialized with fwserial_remove_peer() by the + * fwserial_list_mutex held in fwserial_probe(). + * + * A 1:1 correspondence between an fw_unit and an fwtty_peer is maintained + * via the dev_set_drvdata() for the device of the fw_unit. + */ +static int fwserial_add_peer(struct fw_serial *serial, struct fw_unit *unit) +{ + struct device *dev = &unit->device; + struct fw_device *parent = fw_parent_device(unit); + struct fwtty_peer *peer; + struct fw_csr_iterator ci; + int key, val; + int generation; + + peer = kzalloc(sizeof(*peer), GFP_KERNEL); + if (!peer) + return -ENOMEM; + + peer_set_state(peer, FWPS_NOT_ATTACHED); + + dev_set_drvdata(dev, peer); + peer->unit = unit; + peer->guid = (u64)parent->config_rom[3] << 32 | parent->config_rom[4]; + peer->speed = parent->max_speed; + peer->max_payload = min(device_max_receive(parent), + link_speed_to_max_payload(peer->speed)); + + generation = parent->generation; + smp_rmb(); + peer->node_id = parent->node_id; + smp_wmb(); + peer->generation = generation; + + /* retrieve the mgmt bus addr from the unit directory */ + fw_csr_iterator_init(&ci, unit->directory); + while (fw_csr_iterator_next(&ci, &key, &val)) { + if (key == (CSR_OFFSET | CSR_DEPENDENT_INFO)) { + peer->mgmt_addr = CSR_REGISTER_BASE + 4 * val; + break; + } + } + if (peer->mgmt_addr == 0ULL) { + /* + * No mgmt address effectively disables VIRT_CABLE_PLUG - + * this peer will not be able to attach to a remote + */ + peer_set_state(peer, FWPS_NO_MGMT_ADDR); + } + + spin_lock_init(&peer->lock); + peer->port = NULL; + + init_timer(&peer->timer); + INIT_WORK(&peer->work, NULL); + INIT_DELAYED_WORK(&peer->connect, fwserial_auto_connect); + + /* associate peer with specific fw_card */ + peer->serial = serial; + list_add_rcu(&peer->list, &serial->peer_list); + + fwtty_info(&peer->unit, "peer added (guid:%016llx)", + (unsigned long long)peer->guid); + + /* identify the local unit & virt cable to loopback port */ + if (parent->is_local) { + serial->self = peer; + if (create_loop_dev) { + struct fwtty_port *port; + port = fwserial_claim_port(peer, num_ttys); + if (!IS_ERR(port)) { + struct virt_plug_params params; + + spin_lock_bh(&peer->lock); + peer->port = port; + fill_plug_params(¶ms, port); + fwserial_virt_plug_complete(peer, ¶ms); + spin_unlock_bh(&peer->lock); + + fwtty_write_port_status(port); + } + } + + } else if (auto_connect) { + /* auto-attach to remote units only (if policy allows) */ + schedule_delayed_work(&peer->connect, 1); + } + + return 0; +} + +/** + * fwserial_remove_peer - remove a 'serial' unit device as a 'peer' + * + * Remove a 'peer' from its list of peers. This function is only + * called by fwserial_remove() on bus removal of the unit device. + * + * Note: this function is serialized with fwserial_add_peer() by the + * fwserial_list_mutex held in fwserial_remove(). + */ +static void fwserial_remove_peer(struct fwtty_peer *peer) +{ + struct fwtty_port *port; + + spin_lock_bh(&peer->lock); + peer_set_state(peer, FWPS_GONE); + spin_unlock_bh(&peer->lock); + + cancel_delayed_work_sync(&peer->connect); + cancel_work_sync(&peer->work); + + spin_lock_bh(&peer->lock); + /* if this unit is the local unit, clear link */ + if (peer == peer->serial->self) + peer->serial->self = NULL; + + /* cancel the request timeout timer (if running) */ + del_timer(&peer->timer); + + port = peer->port; + peer->port = NULL; + + list_del_rcu(&peer->list); + + fwtty_info(&peer->unit, "peer removed (guid:%016llx)", + (unsigned long long)peer->guid); + + spin_unlock_bh(&peer->lock); + + if (port) + fwserial_release_port(port); + + synchronize_rcu(); + kfree(peer); +} + +/** + * create_loop_device - create a loopback tty device + * @tty_driver: tty_driver to own loopback device + * @prototype: ptr to already-assigned 'prototype' tty port + * @index: index to associate this device with the tty port + * @parent: device to child to + * + * HACK - this is basically tty_port_register_device() with an + * alternate naming scheme. Suggest tty_port_register_named_device() + * helper api. + * + * Creates a loopback tty device named 'fwloop' which is attached to + * the local unit in fwserial_add_peer(). Note that in the device + * name advances in increments of port allocation blocks, ie., for port + * indices 0..3, the device name will be 'fwloop0'; for 4..7, 'fwloop1', + * and so on. + * + * Only one loopback device should be created per fw_card. + */ +static void release_loop_device(struct device *dev) +{ + kfree(dev); +} + +static struct device *create_loop_device(struct tty_driver *driver, + struct fwtty_port *prototype, + struct fwtty_port *port, + struct device *parent) +{ + char name[64]; + int index = port->index; + dev_t devt = MKDEV(driver->major, driver->minor_start) + index; + struct device *dev = NULL; + int err; + + if (index >= fwtty_driver->num) + return ERR_PTR(-EINVAL); + + snprintf(name, 64, "%s%d", loop_dev_name, index / num_ports); + + tty_port_link_device(&port->port, driver, index); + + cdev_init(&driver->cdevs[index], driver->cdevs[prototype->index].ops); + driver->cdevs[index].owner = driver->owner; + err = cdev_add(&driver->cdevs[index], devt, 1); + if (err) + return ERR_PTR(err); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + cdev_del(&driver->cdevs[index]); + return ERR_PTR(-ENOMEM); + } + + dev->devt = devt; + dev->class = prototype->device->class; + dev->parent = parent; + dev->release = release_loop_device; + dev_set_name(dev, "%s", name); + dev->groups = NULL; + dev_set_drvdata(dev, NULL); + + err = device_register(dev); + if (err) { + put_device(dev); + cdev_del(&driver->cdevs[index]); + return ERR_PTR(err); + } + + return dev; +} + +/** + * fwserial_create - init everything to create TTYs for a specific fw_card + * @unit: fw_unit for first 'serial' unit device probed for this fw_card + * + * This function inits the aggregate structure (an fw_serial instance) + * used to manage the TTY ports registered by a specific fw_card. Also, the + * unit device is added as the first 'peer'. + * + * This unit device may represent a local unit device (as specified by the + * config ROM unit directory) or it may represent a remote unit device + * (as specified by the reading of the remote node's config ROM). + * + * Returns 0 to indicate "ownership" of the unit device, or a negative errno + * value to indicate which error. + */ +static int fwserial_create(struct fw_unit *unit) +{ + struct fw_device *parent = fw_parent_device(unit); + struct fw_card *card = parent->card; + struct fw_serial *serial; + struct fwtty_port *port; + struct device *tty_dev; + int i, j; + int err; + + serial = kzalloc(sizeof(*serial), GFP_KERNEL); + if (!serial) + return -ENOMEM; + + kref_init(&serial->kref); + serial->card = card; + INIT_LIST_HEAD(&serial->peer_list); + + for (i = 0; i < num_ports; ++i) { + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) { + err = -ENOMEM; + goto free_ports; + } + tty_port_init(&port->port); + port->index = FWTTY_INVALID_INDEX; + port->port.ops = &fwtty_port_ops; + port->serial = serial; + + spin_lock_init(&port->lock); + INIT_DELAYED_WORK(&port->drain, fwtty_drain_tx); + INIT_DELAYED_WORK(&port->emit_breaks, fwtty_emit_breaks); + INIT_WORK(&port->hangup, fwtty_do_hangup); + INIT_WORK(&port->push, fwtty_pushrx); + INIT_LIST_HEAD(&port->buf_list); + init_waitqueue_head(&port->wait_tx); + port->max_payload = link_speed_to_max_payload(SCODE_100); + dma_fifo_init(&port->tx_fifo); + + rcu_assign_pointer(port->peer, NULL); + serial->ports[i] = port; + + /* get unique bus addr region for port's status & recv fifo */ + port->rx_handler.length = FWTTY_PORT_RXFIFO_LEN + 4; + port->rx_handler.address_callback = fwtty_port_handler; + port->rx_handler.callback_data = port; + /* + * XXX: use custom memory region above cpu physical memory addrs + * this will ease porting to 64-bit firewire adapters + */ + err = fw_core_add_address_handler(&port->rx_handler, + &fw_high_memory_region); + if (err) { + kfree(port); + goto free_ports; + } + } + /* preserve i for error cleanup */ + + err = fwtty_ports_add(serial); + if (err) { + fwtty_err(&unit, "no space in port table"); + goto free_ports; + } + + for (j = 0; j < num_ttys; ++j) { + tty_dev = tty_port_register_device(&serial->ports[j]->port, + fwtty_driver, + serial->ports[j]->index, + card->device); + if (IS_ERR(tty_dev)) { + err = PTR_ERR(tty_dev); + fwtty_err(&unit, "register tty device error (%d)", err); + goto unregister_ttys; + } + + serial->ports[j]->device = tty_dev; + } + /* preserve j for error cleanup */ + + if (create_loop_dev) { + struct device *loop_dev; + + loop_dev = create_loop_device(fwtty_driver, + serial->ports[0], + serial->ports[num_ttys], + card->device); + if (IS_ERR(loop_dev)) { + err = PTR_ERR(loop_dev); + fwtty_err(&unit, "create loop device failed (%d)", err); + goto unregister_ttys; + } + serial->ports[num_ttys]->device = loop_dev; + serial->ports[num_ttys]->loopback = true; + } + + list_add_rcu(&serial->list, &fwserial_list); + + fwtty_notice(&unit, "TTY over FireWire on device %s (guid %016llx)", + dev_name(card->device), (unsigned long long) card->guid); + + err = fwserial_add_peer(serial, unit); + if (!err) + return 0; + + fwtty_err(&unit, "unable to add peer unit device (%d)", err); + + /* fall-through to error processing */ + list_del_rcu(&serial->list); +unregister_ttys: + for (--j; j >= 0; --j) + tty_unregister_device(fwtty_driver, serial->ports[j]->index); + kref_put(&serial->kref, fwserial_destroy); + return err; + +free_ports: + for (--i; i >= 0; --i) + kfree(serial->ports[i]); + kfree(serial); + return err; +} + +/** + * fwserial_probe: bus probe function for firewire 'serial' unit devices + * + * A 'serial' unit device is created and probed as a result of: + * - declaring a ieee1394 bus id table for 'devices' matching a fabricated + * 'serial' unit specifier id + * - adding a unit directory to the config ROM(s) for a 'serial' unit + * + * The firewire core registers unit devices by enumerating unit directories + * of a node's config ROM after reading the config ROM when a new node is + * added to the bus topology after a bus reset. + * + * The practical implications of this are: + * - this probe is called for both local and remote nodes that have a 'serial' + * unit directory in their config ROM (that matches the specifiers in + * fwserial_id_table). + * - no specific order is enforced for local vs. remote unit devices + * + * This unit driver copes with the lack of specific order in the same way the + * firewire net driver does -- each probe, for either a local or remote unit + * device, is treated as a 'peer' (has a struct fwtty_peer instance) and the + * first peer created for a given fw_card (tracked by the global fwserial_list) + * creates the underlying TTYs (aggregated in a fw_serial instance). + * + * NB: an early attempt to differentiate local & remote unit devices by creating + * peers only for remote units and fw_serial instances (with their + * associated TTY devices) only for local units was discarded. Managing + * the peer lifetimes on device removal proved too complicated. + * + * fwserial_probe/fwserial_remove are effectively serialized by the + * fwserial_list_mutex. This is necessary because the addition of the first peer + * for a given fw_card will trigger the creation of the fw_serial for that + * fw_card, which must not simultaneously contend with the removal of the + * last peer for a given fw_card triggering the destruction of the same + * fw_serial for the same fw_card. + */ +static int fwserial_probe(struct device *dev) +{ + struct fw_unit *unit = fw_unit(dev); + struct fw_serial *serial; + int err; + + mutex_lock(&fwserial_list_mutex); + serial = fwserial_lookup(fw_parent_device(unit)->card); + if (!serial) + err = fwserial_create(unit); + else + err = fwserial_add_peer(serial, unit); + mutex_unlock(&fwserial_list_mutex); + return err; +} + +/** + * fwserial_remove: bus removal function for firewire 'serial' unit devices + * + * The corresponding 'peer' for this unit device is removed from the list of + * peers for the associated fw_serial (which has a 1:1 correspondence with a + * specific fw_card). If this is the last peer being removed, then trigger + * the destruction of the underlying TTYs. + */ +static int fwserial_remove(struct device *dev) +{ + struct fwtty_peer *peer = dev_get_drvdata(dev); + struct fw_serial *serial = peer->serial; + int i; + + mutex_lock(&fwserial_list_mutex); + fwserial_remove_peer(peer); + + if (list_empty(&serial->peer_list)) { + /* unlink from the fwserial_list here */ + list_del_rcu(&serial->list); + + for (i = 0; i < num_ports; ++i) + fwserial_close_port(serial->ports[i]); + kref_put(&serial->kref, fwserial_destroy); + } + mutex_unlock(&fwserial_list_mutex); + + return 0; +} + +/** + * fwserial_update: bus update function for 'firewire' serial unit devices + * + * Updates the new node_id and bus generation for this peer. Note that locking + * is unnecessary; but careful memory barrier usage is important to enforce the + * load and store order of generation & node_id. + * + * The fw-core orders the write of node_id before generation in the parent + * fw_device to ensure that a stale node_id cannot be used with a current + * bus generation. So the generation value must be read before the node_id. + * + * In turn, this orders the write of node_id before generation in the peer to + * also ensure a stale node_id cannot be used with a current bus generation. + */ +static void fwserial_update(struct fw_unit *unit) +{ + struct fw_device *parent = fw_parent_device(unit); + struct fwtty_peer *peer = dev_get_drvdata(&unit->device); + int generation; + + generation = parent->generation; + smp_rmb(); + peer->node_id = parent->node_id; + smp_wmb(); + peer->generation = generation; +} + +static const struct ieee1394_device_id fwserial_id_table[] = { + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .specifier_id = LINUX_VENDOR_ID, + .version = FWSERIAL_VERSION, + }, + { } +}; + +static struct fw_driver fwserial_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + .probe = fwserial_probe, + .remove = fwserial_remove, + }, + .update = fwserial_update, + .id_table = fwserial_id_table, +}; + +#define FW_UNIT_SPECIFIER(id) ((CSR_SPECIFIER_ID << 24) | (id)) +#define FW_UNIT_VERSION(ver) ((CSR_VERSION << 24) | (ver)) +#define FW_UNIT_ADDRESS(ofs) (((CSR_OFFSET | CSR_DEPENDENT_INFO) << 24) \ + | (((ofs) - CSR_REGISTER_BASE) >> 2)) +/* XXX: config ROM definitons could be improved with semi-automated offset + * and length calculation + */ +#define FW_ROM_DESCRIPTOR(ofs) (((CSR_LEAF | CSR_DESCRIPTOR) << 24) | (ofs)) + +struct fwserial_unit_directory_data { + u16 crc; + u16 len; + u32 unit_specifier; + u32 unit_sw_version; + u32 unit_addr_offset; + u32 desc1_ofs; + u16 desc1_crc; + u16 desc1_len; + u32 desc1_data[5]; +} __packed; + +static struct fwserial_unit_directory_data fwserial_unit_directory_data = { + .len = 4, + .unit_specifier = FW_UNIT_SPECIFIER(LINUX_VENDOR_ID), + .unit_sw_version = FW_UNIT_VERSION(FWSERIAL_VERSION), + .desc1_ofs = FW_ROM_DESCRIPTOR(1), + .desc1_len = 5, + .desc1_data = { + 0x00000000, /* type = text */ + 0x00000000, /* enc = ASCII, lang EN */ + 0x4c696e75, /* 'Linux TTY' */ + 0x78205454, + 0x59000000, + }, +}; + +static struct fw_descriptor fwserial_unit_directory = { + .length = sizeof(fwserial_unit_directory_data) / sizeof(u32), + .key = (CSR_DIRECTORY | CSR_UNIT) << 24, + .data = (u32 *)&fwserial_unit_directory_data, +}; + +/* + * The management address is in the unit space region but above other known + * address users (to keep wild writes from causing havoc) + */ +const struct fw_address_region fwserial_mgmt_addr_region = { + .start = CSR_REGISTER_BASE + 0x1e0000ULL, + .end = 0x1000000000000ULL, +}; + +static struct fw_address_handler fwserial_mgmt_addr_handler; + +/** + * fwserial_handle_plug_req - handle VIRT_CABLE_PLUG request work + * @work: ptr to peer->work + * + * Attempts to complete the VIRT_CABLE_PLUG handshake sequence for this peer. + * + * This checks for a collided request-- ie, that a VIRT_CABLE_PLUG request was + * already sent to this peer. If so, the collision is resolved by comparing + * guid values; the loser sends the plug response. + * + * Note: if an error prevents a response, don't do anything -- the + * remote will timeout its request. + */ +static void fwserial_handle_plug_req(struct work_struct *work) +{ + struct fwtty_peer *peer = to_peer(work, work); + struct virt_plug_params *plug_req = &peer->work_params.plug_req; + struct fwtty_port *port; + struct fwserial_mgmt_pkt *pkt; + int rcode; + + pkt = kmalloc(sizeof(*pkt), GFP_KERNEL); + if (!pkt) + return; + + port = fwserial_find_port(peer); + + spin_lock_bh(&peer->lock); + + switch (peer->state) { + case FWPS_NOT_ATTACHED: + if (!port) { + fwtty_err(&peer->unit, "no more ports avail"); + fill_plug_rsp_nack(pkt); + } else { + peer->port = port; + fill_plug_rsp_ok(pkt, peer->port); + peer_set_state(peer, FWPS_PLUG_RESPONDING); + /* don't release claimed port */ + port = NULL; + } + break; + + case FWPS_PLUG_PENDING: + if (peer->serial->card->guid > peer->guid) + goto cleanup; + + /* We lost - hijack the already-claimed port and send ok */ + del_timer(&peer->timer); + fill_plug_rsp_ok(pkt, peer->port); + peer_set_state(peer, FWPS_PLUG_RESPONDING); + break; + + default: + fill_plug_rsp_nack(pkt); + } + + spin_unlock_bh(&peer->lock); + if (port) + fwserial_release_port(port); + + rcode = fwserial_send_mgmt_sync(peer, pkt); + + spin_lock_bh(&peer->lock); + if (peer->state == FWPS_PLUG_RESPONDING) { + if (rcode == RCODE_COMPLETE) { + struct fwtty_port *tmp = peer->port; + + fwserial_virt_plug_complete(peer, plug_req); + spin_unlock_bh(&peer->lock); + + fwtty_write_port_status(tmp); + spin_lock_bh(&peer->lock); + } else { + fwtty_err(&peer->unit, "PLUG_RSP error (%d)", rcode); + port = peer_revert_state(peer); + } + } +cleanup: + spin_unlock_bh(&peer->lock); + if (port) + fwserial_release_port(port); + kfree(pkt); + return; +} + +static void fwserial_handle_unplug_req(struct work_struct *work) +{ + struct fwtty_peer *peer = to_peer(work, work); + struct fwtty_port *port = NULL; + struct fwserial_mgmt_pkt *pkt; + int rcode; + + pkt = kmalloc(sizeof(*pkt), GFP_KERNEL); + if (!pkt) + return; + + spin_lock_bh(&peer->lock); + + switch (peer->state) { + case FWPS_ATTACHED: + fill_unplug_rsp_ok(pkt); + peer_set_state(peer, FWPS_UNPLUG_RESPONDING); + break; + + case FWPS_UNPLUG_PENDING: + if (peer->serial->card->guid > peer->guid) + goto cleanup; + + /* We lost - send unplug rsp */ + del_timer(&peer->timer); + fill_unplug_rsp_ok(pkt); + peer_set_state(peer, FWPS_UNPLUG_RESPONDING); + break; + + default: + fill_unplug_rsp_nack(pkt); + } + + spin_unlock_bh(&peer->lock); + + rcode = fwserial_send_mgmt_sync(peer, pkt); + + spin_lock_bh(&peer->lock); + if (peer->state == FWPS_UNPLUG_RESPONDING) { + if (rcode == RCODE_COMPLETE) + port = peer_revert_state(peer); + else + fwtty_err(&peer->unit, "UNPLUG_RSP error (%d)", rcode); + } +cleanup: + spin_unlock_bh(&peer->lock); + if (port) + fwserial_release_port(port); + kfree(pkt); + return; +} + +static int fwserial_parse_mgmt_write(struct fwtty_peer *peer, + struct fwserial_mgmt_pkt *pkt, + unsigned long long addr, + size_t len) +{ + struct fwtty_port *port = NULL; + int rcode; + + if (addr != fwserial_mgmt_addr_handler.offset || len < sizeof(pkt->hdr)) + return RCODE_ADDRESS_ERROR; + + if (len != be16_to_cpu(pkt->hdr.len) || + len != mgmt_pkt_expected_len(pkt->hdr.code)) + return RCODE_DATA_ERROR; + + spin_lock_bh(&peer->lock); + if (peer->state == FWPS_GONE) { + /* + * This should never happen - it would mean that the + * remote unit that just wrote this transaction was + * already removed from the bus -- and the removal was + * processed before we rec'd this transaction + */ + fwtty_err(&peer->unit, "peer already removed"); + spin_unlock_bh(&peer->lock); + return RCODE_ADDRESS_ERROR; + } + + rcode = RCODE_COMPLETE; + + fwtty_dbg(&peer->unit, "mgmt: hdr.code: %04hx", pkt->hdr.code); + + switch (be16_to_cpu(pkt->hdr.code) & FWSC_CODE_MASK) { + case FWSC_VIRT_CABLE_PLUG: + if (work_pending(&peer->work)) { + fwtty_err(&peer->unit, "plug req: busy"); + rcode = RCODE_CONFLICT_ERROR; + + } else { + peer->work_params.plug_req = pkt->plug_req; + PREPARE_WORK(&peer->work, fwserial_handle_plug_req); + queue_work(system_unbound_wq, &peer->work); + } + break; + + case FWSC_VIRT_CABLE_PLUG_RSP: + if (peer->state != FWPS_PLUG_PENDING) { + rcode = RCODE_CONFLICT_ERROR; + + } else if (be16_to_cpu(pkt->hdr.code) & FWSC_RSP_NACK) { + fwtty_notice(&peer->unit, "NACK plug rsp"); + port = peer_revert_state(peer); + + } else { + struct fwtty_port *tmp = peer->port; + + fwserial_virt_plug_complete(peer, &pkt->plug_rsp); + spin_unlock_bh(&peer->lock); + + fwtty_write_port_status(tmp); + spin_lock_bh(&peer->lock); + } + break; + + case FWSC_VIRT_CABLE_UNPLUG: + if (work_pending(&peer->work)) { + fwtty_err(&peer->unit, "unplug req: busy"); + rcode = RCODE_CONFLICT_ERROR; + } else { + PREPARE_WORK(&peer->work, fwserial_handle_unplug_req); + queue_work(system_unbound_wq, &peer->work); + } + break; + + case FWSC_VIRT_CABLE_UNPLUG_RSP: + if (peer->state != FWPS_UNPLUG_PENDING) + rcode = RCODE_CONFLICT_ERROR; + else { + if (be16_to_cpu(pkt->hdr.code) & FWSC_RSP_NACK) + fwtty_notice(&peer->unit, "NACK unplug?"); + port = peer_revert_state(peer); + } + break; + + default: + fwtty_err(&peer->unit, "unknown mgmt code %d", + be16_to_cpu(pkt->hdr.code)); + rcode = RCODE_DATA_ERROR; + } + spin_unlock_bh(&peer->lock); + + if (port) + fwserial_release_port(port); + + return rcode; +} + +/** + * fwserial_mgmt_handler: bus address handler for mgmt requests + * @parameters: fw_address_callback_t as specified by firewire core interface + * + * This handler is responsible for handling virtual cable requests from remotes + * for all cards. + */ +static void fwserial_mgmt_handler(struct fw_card *card, + struct fw_request *request, + int tcode, int destination, int source, + int generation, + unsigned long long addr, + void *data, size_t len, + void *callback_data) +{ + struct fwserial_mgmt_pkt *pkt = data; + struct fwtty_peer *peer; + int rcode; + + rcu_read_lock(); + peer = __fwserial_peer_by_node_id(card, generation, source); + if (!peer) { + fwtty_dbg(card, "peer(%d:%x) not found", generation, source); + __dump_peer_list(card); + rcode = RCODE_CONFLICT_ERROR; + + } else { + switch (tcode) { + case TCODE_WRITE_BLOCK_REQUEST: + rcode = fwserial_parse_mgmt_write(peer, pkt, addr, len); + break; + + default: + rcode = RCODE_TYPE_ERROR; + } + } + + rcu_read_unlock(); + fw_send_response(card, request, rcode); +} + +static int __init fwserial_init(void) +{ + int err, num_loops = !!(create_loop_dev); + + /* num_ttys/num_ports must not be set above the static alloc avail */ + if (num_ttys + num_loops > MAX_CARD_PORTS) + num_ttys = MAX_CARD_PORTS - num_loops; + num_ports = num_ttys + num_loops; + + fwtty_driver = alloc_tty_driver(MAX_TOTAL_PORTS); + if (!fwtty_driver) { + err = -ENOMEM; + return err; + } + + fwtty_driver->driver_name = KBUILD_MODNAME; + fwtty_driver->name = tty_dev_name; + fwtty_driver->major = 0; + fwtty_driver->minor_start = 0; + fwtty_driver->type = TTY_DRIVER_TYPE_SERIAL; + fwtty_driver->subtype = SERIAL_TYPE_NORMAL; + fwtty_driver->flags = TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV; + + fwtty_driver->init_termios = tty_std_termios; + fwtty_driver->init_termios.c_cflag |= CLOCAL; + tty_set_operations(fwtty_driver, &fwtty_ops); + + err = tty_register_driver(fwtty_driver); + if (err) { + driver_err("register tty driver failed (%d)", err); + goto put_tty; + } + + fwtty_txn_cache = kmem_cache_create("fwtty_txn_cache", + sizeof(struct fwtty_transaction), + 0, 0, fwtty_txn_constructor); + if (!fwtty_txn_cache) { + err = -ENOMEM; + goto unregister_driver; + } + + /* + * Ideally, this address handler would be registered per local node + * (rather than the same handler for all local nodes). However, + * since the firewire core requires the config rom descriptor *before* + * the local unit device(s) are created, a single management handler + * must suffice for all local serial units. + */ + fwserial_mgmt_addr_handler.length = sizeof(struct fwserial_mgmt_pkt); + fwserial_mgmt_addr_handler.address_callback = fwserial_mgmt_handler; + + err = fw_core_add_address_handler(&fwserial_mgmt_addr_handler, + &fwserial_mgmt_addr_region); + if (err) { + driver_err("add management handler failed (%d)", err); + goto destroy_cache; + } + + fwserial_unit_directory_data.unit_addr_offset = + FW_UNIT_ADDRESS(fwserial_mgmt_addr_handler.offset); + err = fw_core_add_descriptor(&fwserial_unit_directory); + if (err) { + driver_err("add unit descriptor failed (%d)", err); + goto remove_handler; + } + + err = driver_register(&fwserial_driver.driver); + if (err) { + driver_err("register fwserial driver failed (%d)", err); + goto remove_descriptor; + } + + return 0; + +remove_descriptor: + fw_core_remove_descriptor(&fwserial_unit_directory); +remove_handler: + fw_core_remove_address_handler(&fwserial_mgmt_addr_handler); +destroy_cache: + kmem_cache_destroy(fwtty_txn_cache); +unregister_driver: + tty_unregister_driver(fwtty_driver); +put_tty: + put_tty_driver(fwtty_driver); + return err; +} + +static void __exit fwserial_exit(void) +{ + driver_unregister(&fwserial_driver.driver); + fw_core_remove_descriptor(&fwserial_unit_directory); + fw_core_remove_address_handler(&fwserial_mgmt_addr_handler); + kmem_cache_destroy(fwtty_txn_cache); + tty_unregister_driver(fwtty_driver); + put_tty_driver(fwtty_driver); +} + +module_init(fwserial_init); +module_exit(fwserial_exit); + +MODULE_AUTHOR("Peter Hurley (peter@hurleysoftware.com)"); +MODULE_DESCRIPTION("FireWire Serial TTY Driver"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(ieee1394, fwserial_id_table); +MODULE_PARM_DESC(ttys, "Number of ttys to create for each local firewire node"); +MODULE_PARM_DESC(auto, "Auto-connect a tty to each firewire node discovered"); +MODULE_PARM_DESC(loop, "Create a loopback device, fwloop, with ttys"); +MODULE_PARM_DESC(limit_bw, "Limit bandwidth utilization to 20%."); diff --git a/drivers/staging/fwserial/fwserial.h b/drivers/staging/fwserial/fwserial.h new file mode 100644 index 000000000000..8b572edf9563 --- /dev/null +++ b/drivers/staging/fwserial/fwserial.h @@ -0,0 +1,387 @@ +#ifndef _FIREWIRE_FWSERIAL_H +#define _FIREWIRE_FWSERIAL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dma_fifo.h" + +#ifdef FWTTY_PROFILING +#define DISTRIBUTION_MAX_SIZE 8192 +#define DISTRIBUTION_MAX_INDEX (ilog2(DISTRIBUTION_MAX_SIZE) + 1) +static inline void profile_size_distrib(unsigned stat[], unsigned val) +{ + int n = (val) ? min(ilog2(val) + 1, DISTRIBUTION_MAX_INDEX) : 0; + ++stat[n]; +} +#else +#define DISTRIBUTION_MAX_INDEX 0 +#define profile_size_distrib(st, n) +#endif + +/* Parameters for both VIRT_CABLE_PLUG & VIRT_CABLE_PLUG_RSP mgmt codes */ +struct virt_plug_params { + __be32 status_hi; + __be32 status_lo; + __be32 fifo_hi; + __be32 fifo_lo; + __be32 fifo_len; +}; + +struct peer_work_params { + union { + struct virt_plug_params plug_req; + }; +}; + +/** + * fwtty_peer: structure representing local & remote unit devices + * @unit: unit child device of fw_device node + * @serial: back pointer to associated fw_serial aggregate + * @guid: unique 64-bit guid for this unit device + * @generation: most recent bus generation + * @node_id: most recent node_id + * @speed: link speed of peer (0 = S100, 2 = S400, ... 5 = S3200) + * @mgmt_addr: bus addr region to write mgmt packets to + * @status_addr: bus addr register to write line status to + * @fifo_addr: bus addr region to write serial output to + * @fifo_len: max length for single write to fifo_addr + * @list: link for insertion into fw_serial's peer_list + * @rcu: for deferring peer reclamation + * @lock: spinlock to synchonize changes to state & port fields + * @work: only one work item can be queued at any one time + * Note: pending work is canceled prior to removal, so this + * peer is valid for at least the lifetime of the work function + * @work_params: parameter block for work functions + * @timer: timer for resetting peer state if remote request times out + * @state: current state + * @connect: work item for auto-connecting + * @connect_retries: # of connections already attempted + * @port: associated tty_port (usable if state == FWSC_ATTACHED) + */ +struct fwtty_peer { + struct fw_unit *unit; + struct fw_serial *serial; + u64 guid; + int generation; + int node_id; + unsigned speed; + int max_payload; + u64 mgmt_addr; + + /* these are usable only if state == FWSC_ATTACHED */ + u64 status_addr; + u64 fifo_addr; + int fifo_len; + + struct list_head list; + struct rcu_head rcu; + + spinlock_t lock; + struct work_struct work; + struct peer_work_params work_params; + struct timer_list timer; + int state; + struct delayed_work connect; + int connect_retries; + + struct fwtty_port *port; +}; + +#define to_peer(ptr, field) (container_of(ptr, struct fwtty_peer, field)) + +/* state values for fwtty_peer.state field */ +enum fwtty_peer_state { + FWPS_GONE, + FWPS_NOT_ATTACHED, + FWPS_ATTACHED, + FWPS_PLUG_PENDING, + FWPS_PLUG_RESPONDING, + FWPS_UNPLUG_PENDING, + FWPS_UNPLUG_RESPONDING, + + FWPS_NO_MGMT_ADDR = -1, +}; + +#define CONNECT_RETRY_DELAY HZ +#define MAX_CONNECT_RETRIES 10 + +/* must be holding peer lock for these state funclets */ +static inline void peer_set_state(struct fwtty_peer *peer, int new) +{ + peer->state = new; +} + +static inline struct fwtty_port *peer_revert_state(struct fwtty_peer *peer) +{ + struct fwtty_port *port = peer->port; + + peer->port = NULL; + peer_set_state(peer, FWPS_NOT_ATTACHED); + return port; +} + +struct fwserial_mgmt_pkt { + struct { + __be16 len; + __be16 code; + } hdr; + union { + struct virt_plug_params plug_req; + struct virt_plug_params plug_rsp; + }; +} __packed; + +/* fwserial_mgmt_packet codes */ +#define FWSC_RSP_OK 0x0000 +#define FWSC_RSP_NACK 0x8000 +#define FWSC_CODE_MASK 0x0fff + +#define FWSC_VIRT_CABLE_PLUG 1 +#define FWSC_VIRT_CABLE_UNPLUG 2 +#define FWSC_VIRT_CABLE_PLUG_RSP 3 +#define FWSC_VIRT_CABLE_UNPLUG_RSP 4 + +/* 1 min. plug timeout -- suitable for userland authorization */ +#define VIRT_CABLE_PLUG_TIMEOUT (60 * HZ) + +struct stats { + unsigned xchars; + unsigned dropped; + unsigned tx_stall; + unsigned fifo_errs; + unsigned sent; + unsigned lost; + unsigned throttled; + unsigned watermark; + unsigned reads[DISTRIBUTION_MAX_INDEX + 1]; + unsigned writes[DISTRIBUTION_MAX_INDEX + 1]; + unsigned txns[DISTRIBUTION_MAX_INDEX + 1]; + unsigned unthrottle[DISTRIBUTION_MAX_INDEX + 1]; +}; + +struct fwconsole_ops { + void (*notify)(int code, void *data); + void (*stats)(struct stats *stats, void *data); + void (*proc_show)(struct seq_file *m, void *data); +}; + +/* codes for console ops notify */ +#define FWCON_NOTIFY_ATTACH 1 +#define FWCON_NOTIFY_DETACH 2 + +struct buffered_rx { + struct list_head list; + size_t n; + unsigned char data[0]; +}; + +/** + * fwtty_port: structure used to track/represent underlying tty_port + * @port: underlying tty_port + * @device: tty device + * @index: index into port_table for this particular port + * note: minor = index + FWSERIAL_TTY_START_MINOR + * @serial: back pointer to the containing fw_serial + * @rx_handler: bus address handler for unique addr region used by remotes + * to communicate with this port. Every port uses + * fwtty_port_handler() for per port transactions. + * @fwcon_ops: ops for attached fw_console (if any) + * @con_data: private data for fw_console + * @wait_tx: waitqueue for sleeping until writer/drain completes tx + * @emit_breaks: delayed work responsible for generating breaks when the + * break line status is active + * @cps : characters per second computed from the termios settings + * @break_last: timestamp in jiffies from last emit_breaks + * @hangup: work responsible for HUPing when carrier is dropped/lost + * @mstatus: loose virtualization of LSR/MSR + * bits 15..0 correspond to TIOCM_* bits + * bits 19..16 reserved for mctrl + * bit 20 OOB_TX_THROTTLE + * bits 23..21 reserved + * bits 31..24 correspond to UART_LSR_* bits + * @lock: spinlock for protecting concurrent access to fields below it + * @mctrl: loose virtualization of MCR + * bits 15..0 correspond to TIOCM_* bits + * bit 16 OOB_RX_THROTTLE + * bits 19..17 reserved + * bits 31..20 reserved for mstatus + * @drain: delayed work scheduled to ensure that writes are flushed. + * The work can race with the writer but concurrent sending is + * prevented with the IN_TX flag. Scheduled under lock to + * limit scheduling when fifo has just been drained. + * @push: work responsible for pushing buffered rx to the ldisc. + * rx can become buffered if the tty buffer is filled before the + * ldisc throttles the sender. + * @buf_list: list of buffered rx yet to be sent to ldisc + * @buffered: byte count of buffered rx + * @tx_fifo: fifo used to store & block-up writes for dma to remote + * @max_payload: max bytes transmissable per dma (based on peer's max_payload) + * @status_mask: UART_LSR_* bitmask significant to rx (based on termios) + * @ignore_mask: UART_LSR_* bitmask of states to ignore (also based on termios) + * @break_ctl: if set, port is 'sending break' to remote + * @write_only: self-explanatory + * @overrun: previous rx was lost (partially or completely) + * @loopback: if set, port is in loopback mode + * @flags: atomic bit flags + * bit 0: IN_TX - gate to allow only one cpu to send from the dma fifo + * at a time. + * bit 1: STOP_TX - force tx to exit while sending + * @peer: rcu-pointer to associated fwtty_peer (if attached) + * NULL if no peer attached + * @icount: predefined statistics reported by the TIOCGICOUNT ioctl + * @stats: additional statistics reported in /proc/tty/driver/firewire_serial + */ +struct fwtty_port { + struct tty_port port; + struct device *device; + unsigned index; + struct fw_serial *serial; + struct fw_address_handler rx_handler; + + struct fwconsole_ops *fwcon_ops; + void *con_data; + + wait_queue_head_t wait_tx; + struct delayed_work emit_breaks; + unsigned cps; + unsigned long break_last; + + struct work_struct hangup; + + unsigned mstatus; + + spinlock_t lock; + unsigned mctrl; + struct delayed_work drain; + struct work_struct push; + struct list_head buf_list; + int buffered; + struct dma_fifo tx_fifo; + int max_payload; + unsigned status_mask; + unsigned ignore_mask; + unsigned break_ctl:1, + write_only:1, + overrun:1, + loopback:1; + unsigned long flags; + + struct fwtty_peer *peer; + + struct async_icount icount; + struct stats stats; +}; + +#define to_port(ptr, field) (container_of(ptr, struct fwtty_port, field)) + +/* bit #s for flags field */ +#define IN_TX 0 +#define STOP_TX 1 +#define BUFFERING_RX 2 + +/* bitmasks for special mctrl/mstatus bits */ +#define OOB_RX_THROTTLE 0x00010000 +#define MCTRL_RSRVD 0x000e0000 +#define OOB_TX_THROTTLE 0x00100000 +#define MSTATUS_RSRVD 0x00e00000 + +#define MCTRL_MASK (TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 | TIOCM_OUT2 | \ + TIOCM_LOOP | OOB_RX_THROTTLE | MCTRL_RSRVD) + +/* XXX even every 1/50th secs. may be unnecessarily accurate */ +/* delay in jiffies between brk emits */ +#define FREQ_BREAKS (HZ / 50) + +/* Ports are allocated in blocks of num_ports for each fw_card */ +#define MAX_CARD_PORTS 32 /* max # of ports per card */ +#define MAX_TOTAL_PORTS 64 /* max # of ports total */ + +/* tuning parameters */ +#define FWTTY_PORT_TXFIFO_LEN 4096 +#define FWTTY_PORT_MAX_PEND_DMA 8 /* costs a cache line per pend */ +#define DRAIN_THRESHOLD 1024 +#define MAX_ASYNC_PAYLOAD 4096 /* ohci-defined limit */ +#define WRITER_MINIMUM 128 +/* TODO: how to set watermark to AR context size? see fwtty_rx() */ +#define HIGH_WATERMARK 32768 /* AR context is 32K */ + +/* + * Size of bus addr region above 4GB used per port as the recv addr + * - must be at least as big as the MAX_ASYNC_PAYLOAD + */ +#define FWTTY_PORT_RXFIFO_LEN MAX_ASYNC_PAYLOAD + +/** + * fw_serial: aggregate used to associate tty ports with specific fw_card + * @card: fw_card associated with this fw_serial device (1:1 association) + * @kref: reference-counted multi-port management allows delayed destroy + * @self: local unit device as 'peer'. Not valid until local unit device + * is enumerated. + * @list: link for insertion into fwserial_list + * @peer_list: list of local & remote unit devices attached to this card + * @ports: fixed array of tty_ports provided by this serial device + */ +struct fw_serial { + struct fw_card *card; + struct kref kref; + + struct fwtty_peer *self; + + struct list_head list; + struct list_head peer_list; + + struct fwtty_port *ports[MAX_CARD_PORTS]; +}; + +#define to_serial(ptr, field) (container_of(ptr, struct fw_serial, field)) + +#define TTY_DEV_NAME "fwtty" /* ttyFW was taken */ +static const char tty_dev_name[] = TTY_DEV_NAME; +static const char loop_dev_name[] = "fwloop"; +extern bool limit_bw; + +struct tty_driver *fwtty_driver; + +#define driver_err(s, v...) pr_err(KBUILD_MODNAME ": " s, ##v) + +struct fwtty_port *fwtty_port_get(unsigned index); +void fwtty_port_put(struct fwtty_port *port); + +static inline void fwtty_bind_console(struct fwtty_port *port, + struct fwconsole_ops *fwcon_ops, + void *data) +{ + port->con_data = data; + port->fwcon_ops = fwcon_ops; +} + +/* + * Returns the max send async payload size in bytes based on the unit device + * link speed - if set to limit bandwidth to max 20%, use lookup table + */ +static inline int link_speed_to_max_payload(unsigned speed) +{ + static const int max_async[] = { 307, 614, 1229, 2458, 4916, 9832, }; + BUILD_BUG_ON(ARRAY_SIZE(max_async) - 1 != SCODE_3200); + + speed = clamp(speed, (unsigned) SCODE_100, (unsigned) SCODE_3200); + if (limit_bw) + return max_async[speed]; + else + return 1 << (speed + 9); +} + +#endif /* _FIREWIRE_FWSERIAL_H */ -- cgit v1.2.3 From eef6e7b286e6dcf39a9b8c1d31477ee489451a8a Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Tue, 27 Nov 2012 09:30:45 -0500 Subject: staging/fwserial: Fix build breakage when !CONFIG_BUG Use WARN() as intended. Reported-by: Randy Dunlap Acked-by: Randy Dunlap Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/staging/fwserial/dma_fifo.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers/staging') diff --git a/drivers/staging/fwserial/dma_fifo.c b/drivers/staging/fwserial/dma_fifo.c index 72aa0533f018..5e8463445504 100644 --- a/drivers/staging/fwserial/dma_fifo.c +++ b/drivers/staging/fwserial/dma_fifo.c @@ -33,10 +33,7 @@ #define FAIL(fifo, condition, format...) ({ \ fifo->corrupt = !!(condition); \ - if (unlikely(fifo->corrupt)) { \ - __WARN_printf(format); \ - } \ - unlikely(fifo->corrupt); \ + WARN(fifo->corrupt, format); \ }) /* -- cgit v1.2.3 From a321846492f9ce3bab27f826e6579217fbc35732 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Tue, 27 Nov 2012 21:37:11 -0500 Subject: staging/fwserial: Destruct embedded tty_port on teardown For TTY drivers that manage the port lifetime, the tty_port should to be specifically destructed when the port lifetime ends. Now that a method has been added to do this, use it. Signed-off-by: Peter Hurley Cc: Jiri Slaby Cc: Alan Cox --- drivers/staging/fwserial/fwserial.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/staging') diff --git a/drivers/staging/fwserial/fwserial.c b/drivers/staging/fwserial/fwserial.c index 5d4d64a3ea81..99a2d2dbd322 100644 --- a/drivers/staging/fwserial/fwserial.c +++ b/drivers/staging/fwserial/fwserial.c @@ -955,6 +955,7 @@ static void fwserial_destroy(struct kref *kref) for (j = 0; j < num_ports; ++j) { fw_core_remove_address_handler(&ports[j]->rx_handler); dma_fifo_free(&ports[j]->tx_fifo); + tty_port_destroy(&ports[j]->port); kfree(ports[j]); } kfree(serial); @@ -2369,8 +2370,10 @@ unregister_ttys: return err; free_ports: - for (--i; i >= 0; --i) + for (--i; i >= 0; --i) { + tty_port_destroy(&serial->ports[i]->port); kfree(serial->ports[i]); + } kfree(serial); return err; } -- cgit v1.2.3 From 49b2746e1426ffa188421996a0323bdd47657108 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Tue, 27 Nov 2012 21:37:12 -0500 Subject: staging/fwserial: Use WARN_ONCE when port table is corrupted Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/staging/fwserial/fwserial.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'drivers/staging') diff --git a/drivers/staging/fwserial/fwserial.c b/drivers/staging/fwserial/fwserial.c index 99a2d2dbd322..0681967337f9 100644 --- a/drivers/staging/fwserial/fwserial.c +++ b/drivers/staging/fwserial/fwserial.c @@ -939,14 +939,9 @@ static void fwserial_destroy(struct kref *kref) mutex_lock(&port_table_lock); for (j = 0; j < num_ports; ++i, ++j) { - static bool once; - int corrupt = port_table[i] != ports[j]; - if (corrupt && !once) { - WARN(corrupt, "port_table[%d]: %p != ports[%d]: %p", - i, port_table[i], j, ports[j]); - once = true; - port_table_corrupt = true; - } + port_table_corrupt |= port_table[i] != ports[j]; + WARN_ONCE(port_table_corrupt, "port_table[%d]: %p != ports[%d]: %p", + i, port_table[i], j, ports[j]); port_table[i] = NULL; } -- cgit v1.2.3 From 218cbbd82132d48c6f4e042e31beb6019f92db01 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Tue, 27 Nov 2012 21:37:13 -0500 Subject: staging/fwserial: Remove superfluous free Now that the dma fifo is allocated on activate and freed on shutdown, this extra free is harmless but unnecessary. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/staging/fwserial/fwserial.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/staging') diff --git a/drivers/staging/fwserial/fwserial.c b/drivers/staging/fwserial/fwserial.c index 0681967337f9..61ee29083b26 100644 --- a/drivers/staging/fwserial/fwserial.c +++ b/drivers/staging/fwserial/fwserial.c @@ -949,7 +949,6 @@ static void fwserial_destroy(struct kref *kref) for (j = 0; j < num_ports; ++j) { fw_core_remove_address_handler(&ports[j]->rx_handler); - dma_fifo_free(&ports[j]->tx_fifo); tty_port_destroy(&ports[j]->port); kfree(ports[j]); } -- cgit v1.2.3 From b0ab02361167faa82198b783a8d555eb6f58901c Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 28 Nov 2012 21:30:01 -0500 Subject: staging: sb105x: fix potential NULL pointer dereference in mp_chars_in_buffer() The dereference to 'state' should be moved below the NULL test. Cc: Steven Rostedt Signed-off-by: Wei Yongjun Signed-off-by: Greg Kroah-Hartman --- drivers/staging/sb105x/sb_pci_mp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/staging') diff --git a/drivers/staging/sb105x/sb_pci_mp.c b/drivers/staging/sb105x/sb_pci_mp.c index fbebf88226d3..edb2a85b9d52 100644 --- a/drivers/staging/sb105x/sb_pci_mp.c +++ b/drivers/staging/sb105x/sb_pci_mp.c @@ -689,13 +689,14 @@ static int mp_chars_in_buffer(struct tty_struct *tty) static void mp_flush_buffer(struct tty_struct *tty) { struct sb_uart_state *state = tty->driver_data; - struct sb_uart_port *port = state->port; + struct sb_uart_port *port; unsigned long flags; if (!state || !state->info) { return; } + port = state->port; spin_lock_irqsave(&port->lock, flags); uart_circ_clear(&state->info->xmit); spin_unlock_irqrestore(&port->lock, flags); -- cgit v1.2.3