From e676253b19b2d269cccf67fdb1592120a0cd0676 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Tue, 5 Aug 2014 11:45:59 +0200 Subject: serial/8250: Add support for RS485 IOCTLs This patch allow the users of the 8250 infrastructure to define a handler for RS485 configration. If no handler is defined the 8250 driver will work as usual. Signed-off-by: Ricardo Ribalda Delgado Acked-by: Alan Cox -- v2:Change suggested by Alan "One Thousand Gnomes": - Move rs485 structure further down on the uart_8250_port structure drivers/tty/serial/8250/8250_core.c | 39 +++++++++++++++++++++++++++++++++++++ include/linux/serial_8250.h | 3 +++ 2 files changed, 42 insertions(+) Signed-off-by: Greg Kroah-Hartman --- include/linux/serial_8250.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'include') diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index f93649e22c43..6dd671765312 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h @@ -96,10 +96,13 @@ struct uart_8250_port { unsigned char msr_saved_flags; struct uart_8250_dma *dma; + struct serial_rs485 rs485; /* 8250 specific callbacks */ int (*dl_read)(struct uart_8250_port *); void (*dl_write)(struct uart_8250_port *, int); + int (*rs485_config)(struct uart_8250_port *, + struct serial_rs485 *rs485); }; static inline struct uart_8250_port *up_to_u8250p(struct uart_port *up) -- cgit v1.2.3 From ff7693d079e58fb62d735b7b8085b53fcfb74528 Mon Sep 17 00:00:00 2001 From: Carlo Caione Date: Sun, 17 Aug 2014 12:49:49 +0200 Subject: ARM: meson: serial: add MesonX SoC on-chip uart driver The SoC has four fully functional UARTs which use the same programming model. They are named UART_A, UART_B, UART_C and UART_AO (Always-On) which cannot be powered off. Signed-off-by: Carlo Caione Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 18 ++ drivers/tty/serial/Makefile | 1 + drivers/tty/serial/meson_uart.c | 634 +++++++++++++++++++++++++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 4 files changed, 656 insertions(+) create mode 100644 drivers/tty/serial/meson_uart.c (limited to 'include') diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 75cc59f5d253..636949d8144d 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -200,6 +200,24 @@ config SERIAL_KS8695_CONSOLE receives all kernel messages and warnings and which allows logins in single user mode). +config SERIAL_MESON + tristate "Meson serial port support" + depends on ARCH_MESON + select SERIAL_CORE + help + This enables the driver for the on-chip UARTs of the Amlogic + MesonX processors. + +config SERIAL_MESON_CONSOLE + bool "Support for console on meson" + depends on SERIAL_MESON=y + select SERIAL_CORE_CONSOLE + help + Say Y here if you wish to use a Amlogic MesonX UART as the + system console (the system console is the device which + receives all kernel messages and warnings and which allows + logins in single user mode) as /dev/ttyAMLx. + config SERIAL_CLPS711X tristate "CLPS711X serial port support" depends on ARCH_CLPS711X || COMPILE_TEST diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 0080cc362e09..9a548acf5fdc 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_SERIAL_MPC52xx) += mpc52xx_uart.o obj-$(CONFIG_SERIAL_ICOM) += icom.o obj-$(CONFIG_SERIAL_M32R_SIO) += m32r_sio.o obj-$(CONFIG_SERIAL_MPSC) += mpsc.o +obj-$(CONFIG_SERIAL_MESON) += meson_uart.o obj-$(CONFIG_SERIAL_SB1250_DUART) += sb1250-duart.o obj-$(CONFIG_ETRAX_SERIAL) += crisv10.o obj-$(CONFIG_SERIAL_SCCNXP) += sccnxp.o diff --git a/drivers/tty/serial/meson_uart.c b/drivers/tty/serial/meson_uart.c new file mode 100644 index 000000000000..15c749753317 --- /dev/null +++ b/drivers/tty/serial/meson_uart.c @@ -0,0 +1,634 @@ +/* + * Based on meson_uart.c, by AMLOGIC, INC. + * + * Copyright (C) 2014 Carlo Caione + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register offsets */ +#define AML_UART_WFIFO 0x00 +#define AML_UART_RFIFO 0x04 +#define AML_UART_CONTROL 0x08 +#define AML_UART_STATUS 0x0c +#define AML_UART_MISC 0x10 +#define AML_UART_REG5 0x14 + +/* AML_UART_CONTROL bits */ +#define AML_UART_TX_EN BIT(12) +#define AML_UART_RX_EN BIT(13) +#define AML_UART_TX_RST BIT(22) +#define AML_UART_RX_RST BIT(23) +#define AML_UART_CLR_ERR BIT(24) +#define AML_UART_RX_INT_EN BIT(27) +#define AML_UART_TX_INT_EN BIT(28) +#define AML_UART_DATA_LEN_MASK (0x03 << 20) +#define AML_UART_DATA_LEN_8BIT (0x00 << 20) +#define AML_UART_DATA_LEN_7BIT (0x01 << 20) +#define AML_UART_DATA_LEN_6BIT (0x02 << 20) +#define AML_UART_DATA_LEN_5BIT (0x03 << 20) + +/* AML_UART_STATUS bits */ +#define AML_UART_PARITY_ERR BIT(16) +#define AML_UART_FRAME_ERR BIT(17) +#define AML_UART_TX_FIFO_WERR BIT(18) +#define AML_UART_RX_EMPTY BIT(20) +#define AML_UART_TX_FULL BIT(21) +#define AML_UART_TX_EMPTY BIT(22) +#define AML_UART_ERR (AML_UART_PARITY_ERR | \ + AML_UART_FRAME_ERR | \ + AML_UART_TX_FIFO_WERR) + +/* AML_UART_CONTROL bits */ +#define AML_UART_TWO_WIRE_EN BIT(15) +#define AML_UART_PARITY_TYPE BIT(18) +#define AML_UART_PARITY_EN BIT(19) +#define AML_UART_CLEAR_ERR BIT(24) +#define AML_UART_STOP_BIN_LEN_MASK (0x03 << 16) +#define AML_UART_STOP_BIN_1SB (0x00 << 16) +#define AML_UART_STOP_BIN_2SB (0x01 << 16) + +/* AML_UART_MISC bits */ +#define AML_UART_XMIT_IRQ(c) (((c) & 0xff) << 8) +#define AML_UART_RECV_IRQ(c) ((c) & 0xff) + +/* AML_UART_REG5 bits */ +#define AML_UART_BAUD_MASK 0x7fffff +#define AML_UART_BAUD_USE BIT(23) + +#define AML_UART_PORT_NUM 6 +#define AML_UART_DEV_NAME "ttyAML" + + +static struct uart_driver meson_uart_driver; + +static struct uart_port *meson_ports[AML_UART_PORT_NUM]; + +static void meson_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +} + +static unsigned int meson_uart_get_mctrl(struct uart_port *port) +{ + return TIOCM_CTS; +} + +static unsigned int meson_uart_tx_empty(struct uart_port *port) +{ + u32 val; + + val = readl(port->membase + AML_UART_STATUS); + return (val & AML_UART_TX_EMPTY) ? TIOCSER_TEMT : 0; +} + +static void meson_uart_stop_tx(struct uart_port *port) +{ + u32 val; + + val = readl(port->membase + AML_UART_CONTROL); + val &= ~AML_UART_TX_EN; + writel(val, port->membase + AML_UART_CONTROL); +} + +static void meson_uart_stop_rx(struct uart_port *port) +{ + u32 val; + + val = readl(port->membase + AML_UART_CONTROL); + val &= ~AML_UART_RX_EN; + writel(val, port->membase + AML_UART_CONTROL); +} + +static void meson_uart_shutdown(struct uart_port *port) +{ + unsigned long flags; + u32 val; + + free_irq(port->irq, port); + + spin_lock_irqsave(&port->lock, flags); + + val = readl(port->membase + AML_UART_CONTROL); + val &= ~(AML_UART_RX_EN | AML_UART_TX_EN); + val &= ~(AML_UART_RX_INT_EN | AML_UART_TX_INT_EN); + writel(val, port->membase + AML_UART_CONTROL); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static void meson_uart_start_tx(struct uart_port *port) +{ + struct circ_buf *xmit = &port->state->xmit; + unsigned int ch; + + if (uart_tx_stopped(port)) { + meson_uart_stop_tx(port); + return; + } + + while (!(readl(port->membase + AML_UART_STATUS) & AML_UART_TX_FULL)) { + if (port->x_char) { + writel(port->x_char, port->membase + AML_UART_WFIFO); + port->icount.tx++; + port->x_char = 0; + continue; + } + + if (uart_circ_empty(xmit)) + break; + + ch = xmit->buf[xmit->tail]; + writel(ch, port->membase + AML_UART_WFIFO); + xmit->tail = (xmit->tail+1) & (SERIAL_XMIT_SIZE - 1); + port->icount.tx++; + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); +} + +static void meson_receive_chars(struct uart_port *port) +{ + struct tty_port *tport = &port->state->port; + char flag; + u32 status, ch, mode; + + do { + flag = TTY_NORMAL; + port->icount.rx++; + status = readl(port->membase + AML_UART_STATUS); + + if (status & AML_UART_ERR) { + if (status & AML_UART_TX_FIFO_WERR) + port->icount.overrun++; + else if (status & AML_UART_FRAME_ERR) + port->icount.frame++; + else if (status & AML_UART_PARITY_ERR) + port->icount.frame++; + + mode = readl(port->membase + AML_UART_CONTROL); + mode |= AML_UART_CLEAR_ERR; + writel(mode, port->membase + AML_UART_CONTROL); + + /* It doesn't clear to 0 automatically */ + mode &= ~AML_UART_CLEAR_ERR; + writel(mode, port->membase + AML_UART_CONTROL); + + status &= port->read_status_mask; + if (status & AML_UART_FRAME_ERR) + flag = TTY_FRAME; + else if (status & AML_UART_PARITY_ERR) + flag = TTY_PARITY; + } + + ch = readl(port->membase + AML_UART_RFIFO); + ch &= 0xff; + + if ((status & port->ignore_status_mask) == 0) + tty_insert_flip_char(tport, ch, flag); + + if (status & AML_UART_TX_FIFO_WERR) + tty_insert_flip_char(tport, 0, TTY_OVERRUN); + + } while (!(readl(port->membase + AML_UART_STATUS) & AML_UART_RX_EMPTY)); + + spin_unlock(&port->lock); + tty_flip_buffer_push(tport); + spin_lock(&port->lock); +} + +static irqreturn_t meson_uart_interrupt(int irq, void *dev_id) +{ + struct uart_port *port = (struct uart_port *)dev_id; + + spin_lock(&port->lock); + + if (!(readl(port->membase + AML_UART_STATUS) & AML_UART_RX_EMPTY)) + meson_receive_chars(port); + + if (!(readl(port->membase + AML_UART_STATUS) & AML_UART_TX_FULL)) + meson_uart_start_tx(port); + + spin_unlock(&port->lock); + + return IRQ_HANDLED; +} + +static const char *meson_uart_type(struct uart_port *port) +{ + return (port->type == PORT_MESON) ? "meson_uart" : NULL; +} + +static int meson_uart_startup(struct uart_port *port) +{ + u32 val; + int ret = 0; + + val = readl(port->membase + AML_UART_CONTROL); + val |= (AML_UART_RX_RST | AML_UART_TX_RST | AML_UART_CLR_ERR); + writel(val, port->membase + AML_UART_CONTROL); + + val &= ~(AML_UART_RX_RST | AML_UART_TX_RST | AML_UART_CLR_ERR); + writel(val, port->membase + AML_UART_CONTROL); + + val |= (AML_UART_RX_EN | AML_UART_TX_EN); + writel(val, port->membase + AML_UART_CONTROL); + + val |= (AML_UART_RX_INT_EN | AML_UART_TX_INT_EN); + writel(val, port->membase + AML_UART_CONTROL); + + val = (AML_UART_RECV_IRQ(1) | AML_UART_XMIT_IRQ(port->fifosize / 2)); + writel(val, port->membase + AML_UART_MISC); + + ret = request_irq(port->irq, meson_uart_interrupt, 0, + meson_uart_type(port), port); + + return ret; +} + +static void meson_uart_change_speed(struct uart_port *port, unsigned long baud) +{ + u32 val; + + while (!(readl(port->membase + AML_UART_STATUS) & AML_UART_TX_EMPTY)) + cpu_relax(); + + val = readl(port->membase + AML_UART_REG5); + val &= ~AML_UART_BAUD_MASK; + val = ((port->uartclk * 10 / (baud * 4) + 5) / 10) - 1; + val |= AML_UART_BAUD_USE; + writel(val, port->membase + AML_UART_REG5); +} + +static void meson_uart_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + unsigned int cflags, iflags, baud; + unsigned long flags; + u32 val; + + spin_lock_irqsave(&port->lock, flags); + + cflags = termios->c_cflag; + iflags = termios->c_iflag; + + val = readl(port->membase + AML_UART_CONTROL); + + val &= ~AML_UART_DATA_LEN_MASK; + switch (cflags & CSIZE) { + case CS8: + val |= AML_UART_DATA_LEN_8BIT; + break; + case CS7: + val |= AML_UART_DATA_LEN_7BIT; + break; + case CS6: + val |= AML_UART_DATA_LEN_6BIT; + break; + case CS5: + val |= AML_UART_DATA_LEN_5BIT; + break; + } + + if (cflags & PARENB) + val |= AML_UART_PARITY_EN; + else + val &= ~AML_UART_PARITY_EN; + + if (cflags & PARODD) + val |= AML_UART_PARITY_TYPE; + else + val &= ~AML_UART_PARITY_TYPE; + + val &= ~AML_UART_STOP_BIN_LEN_MASK; + if (cflags & CSTOPB) + val |= AML_UART_STOP_BIN_2SB; + else + val &= ~AML_UART_STOP_BIN_1SB; + + if (cflags & CRTSCTS) + val &= ~AML_UART_TWO_WIRE_EN; + else + val |= AML_UART_TWO_WIRE_EN; + + writel(val, port->membase + AML_UART_CONTROL); + + baud = uart_get_baud_rate(port, termios, old, 9600, 115200); + meson_uart_change_speed(port, baud); + + port->read_status_mask = AML_UART_TX_FIFO_WERR; + if (iflags & INPCK) + port->read_status_mask |= AML_UART_PARITY_ERR | + AML_UART_FRAME_ERR; + + port->ignore_status_mask = 0; + if (iflags & IGNPAR) + port->ignore_status_mask |= AML_UART_PARITY_ERR | + AML_UART_FRAME_ERR; + + uart_update_timeout(port, termios->c_cflag, baud); + spin_unlock_irqrestore(&port->lock, flags); +} + +static int meson_uart_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + int ret = 0; + + if (port->type != PORT_MESON) + ret = -EINVAL; + if (port->irq != ser->irq) + ret = -EINVAL; + if (ser->baud_base < 9600) + ret = -EINVAL; + return ret; +} + +static void meson_uart_release_port(struct uart_port *port) +{ + if (port->flags & UPF_IOREMAP) { + iounmap(port->membase); + port->membase = NULL; + } +} + +static int meson_uart_request_port(struct uart_port *port) +{ + struct platform_device *pdev = to_platform_device(port->dev); + struct resource *res; + int size; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "cannot obtain I/O memory region"); + return -ENODEV; + } + size = resource_size(res); + + if (!devm_request_mem_region(port->dev, port->mapbase, size, + dev_name(port->dev))) { + dev_err(port->dev, "Memory region busy\n"); + return -EBUSY; + } + + if (port->flags & UPF_IOREMAP) { + port->membase = devm_ioremap_nocache(port->dev, + port->mapbase, + size); + if (port->membase == NULL) + return -ENOMEM; + } + + return 0; +} + +static void meson_uart_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) { + port->type = PORT_MESON; + meson_uart_request_port(port); + } +} + +static struct uart_ops meson_uart_ops = { + .set_mctrl = meson_uart_set_mctrl, + .get_mctrl = meson_uart_get_mctrl, + .tx_empty = meson_uart_tx_empty, + .start_tx = meson_uart_start_tx, + .stop_tx = meson_uart_stop_tx, + .stop_rx = meson_uart_stop_rx, + .startup = meson_uart_startup, + .shutdown = meson_uart_shutdown, + .set_termios = meson_uart_set_termios, + .type = meson_uart_type, + .config_port = meson_uart_config_port, + .request_port = meson_uart_request_port, + .release_port = meson_uart_release_port, + .verify_port = meson_uart_verify_port, +}; + +#ifdef CONFIG_SERIAL_MESON_CONSOLE + +static void meson_console_putchar(struct uart_port *port, int ch) +{ + if (!port->membase) + return; + + while (readl(port->membase + AML_UART_STATUS) & AML_UART_TX_FULL) + cpu_relax(); + writel(ch, port->membase + AML_UART_WFIFO); +} + +static void meson_serial_console_write(struct console *co, const char *s, + u_int count) +{ + struct uart_port *port; + unsigned long flags; + int locked; + + port = meson_ports[co->index]; + if (!port) + return; + + local_irq_save(flags); + if (port->sysrq) { + locked = 0; + } else if (oops_in_progress) { + locked = spin_trylock(&port->lock); + } else { + spin_lock(&port->lock); + locked = 1; + } + + uart_console_write(port, s, count, meson_console_putchar); + + if (locked) + spin_unlock(&port->lock); + local_irq_restore(flags); +} + +static int meson_serial_console_setup(struct console *co, char *options) +{ + struct uart_port *port; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + + if (co->index < 0 || co->index >= AML_UART_PORT_NUM) + return -EINVAL; + + port = meson_ports[co->index]; + if (!port || !port->membase) + return -ENODEV; + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + + return uart_set_options(port, co, baud, parity, bits, flow); +} + +static struct console meson_serial_console = { + .name = AML_UART_DEV_NAME, + .write = meson_serial_console_write, + .device = uart_console_device, + .setup = meson_serial_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &meson_uart_driver, +}; + +static int __init meson_serial_console_init(void) +{ + register_console(&meson_serial_console); + return 0; +} +console_initcall(meson_serial_console_init); + +#define MESON_SERIAL_CONSOLE (&meson_serial_console) +#else +#define MESON_SERIAL_CONSOLE NULL +#endif + +static struct uart_driver meson_uart_driver = { + .owner = THIS_MODULE, + .driver_name = "meson_uart", + .dev_name = AML_UART_DEV_NAME, + .nr = AML_UART_PORT_NUM, + .cons = MESON_SERIAL_CONSOLE, +}; + +static int meson_uart_probe(struct platform_device *pdev) +{ + struct resource *res_mem, *res_irq; + struct uart_port *port; + struct clk *clk; + int ret = 0; + + if (pdev->dev.of_node) + pdev->id = of_alias_get_id(pdev->dev.of_node, "serial"); + + if (pdev->id < 0 || pdev->id >= AML_UART_PORT_NUM) + return -EINVAL; + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res_mem) + return -ENODEV; + + res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res_irq) + return -ENODEV; + + if (meson_ports[pdev->id]) { + dev_err(&pdev->dev, "port %d already allocated\n", pdev->id); + return -EBUSY; + } + + port = devm_kzalloc(&pdev->dev, sizeof(struct uart_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + port->uartclk = clk_get_rate(clk); + port->iotype = UPIO_MEM; + port->mapbase = res_mem->start; + port->irq = res_irq->start; + port->flags = UPF_BOOT_AUTOCONF | UPF_IOREMAP | UPF_LOW_LATENCY; + port->dev = &pdev->dev; + port->line = pdev->id; + port->type = PORT_MESON; + port->x_char = 0; + port->ops = &meson_uart_ops; + port->fifosize = 64; + + meson_ports[pdev->id] = port; + platform_set_drvdata(pdev, port); + + ret = uart_add_one_port(&meson_uart_driver, port); + if (ret) + meson_ports[pdev->id] = NULL; + + return ret; +} + +static int meson_uart_remove(struct platform_device *pdev) +{ + struct uart_port *port; + + port = platform_get_drvdata(pdev); + uart_remove_one_port(&meson_uart_driver, port); + meson_ports[pdev->id] = NULL; + + return 0; +} + + +static const struct of_device_id meson_uart_dt_match[] = { + { .compatible = "amlogic,meson-uart" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, meson_uart_dt_match); + +static struct platform_driver meson_uart_platform_driver = { + .probe = meson_uart_probe, + .remove = meson_uart_remove, + .driver = { + .owner = THIS_MODULE, + .name = "meson_uart", + .of_match_table = meson_uart_dt_match, + }, +}; + +static int __init meson_uart_init(void) +{ + int ret; + + ret = uart_register_driver(&meson_uart_driver); + if (ret) + return ret; + + ret = platform_driver_register(&meson_uart_platform_driver); + if (ret) + uart_unregister_driver(&meson_uart_driver); + + return ret; +} + +static void __exit meson_uart_exit(void) +{ + platform_driver_unregister(&meson_uart_platform_driver); + uart_unregister_driver(&meson_uart_driver); +} + +module_init(meson_uart_init); +module_exit(meson_uart_exit); + +MODULE_AUTHOR("Carlo Caione "); +MODULE_DESCRIPTION("Amlogic Meson serial port driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 5820269aa132..16ad8521af6a 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -244,4 +244,7 @@ /* SC16IS74xx */ #define PORT_SC16IS7XX 108 +/* MESON */ +#define PORT_MESON 109 + #endif /* _UAPILINUX_SERIAL_CORE_H */ -- cgit v1.2.3 From 8a949b07e4062cbd07e04e6a47249e69ca65b944 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Tue, 2 Sep 2014 17:39:19 -0400 Subject: serial: core: Document lock requirement for UPF_* flags updates The flags field of struct uart_port can only be safely modified if the port mutex is held; no other lock prevents concurrent changes from corrupting the field. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- include/linux/serial_core.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index cf3a1e789bf5..8cb267b1fcd5 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -152,6 +152,7 @@ struct uart_port { unsigned long sysrq; /* sysrq timeout */ #endif + /* flags must be updated while holding port mutex */ upf_t flags; #define UPF_FOURPORT ((__force upf_t) (1 << 1)) -- cgit v1.2.3 From b99b121b2aa42e60e5b73fdd3a49863337839c7b Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 5 Sep 2014 21:02:37 +0200 Subject: tty: serial: 8250_core: allow to overwrite & export serial8250_startup() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OMAP version of the 8250 can actually use 1:1 serial8250_startup(). However it needs to be extended by a wake up irq which should to be requested & enabled at ->startup() time and disabled at ->shutdown() time. v2…v3: properly copy callbacks v1…v2: add shutdown callback Acked-by: Alan Cox Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 25 +++++++++++++++++++++++-- include/linux/serial_8250.h | 2 ++ include/linux/serial_core.h | 2 ++ 3 files changed, 27 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 90e0d5e2b7ee..872c020607a8 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -1930,7 +1930,7 @@ static void serial8250_put_poll_char(struct uart_port *port, #endif /* CONFIG_CONSOLE_POLL */ -static int serial8250_startup(struct uart_port *port) +int serial8250_do_startup(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); unsigned long flags; @@ -2181,8 +2181,16 @@ dont_test_tx_en: return 0; } +EXPORT_SYMBOL_GPL(serial8250_do_startup); -static void serial8250_shutdown(struct uart_port *port) +static int serial8250_startup(struct uart_port *port) +{ + if (port->startup) + return port->startup(port); + return serial8250_do_startup(port); +} + +void serial8250_do_shutdown(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); unsigned long flags; @@ -2232,6 +2240,15 @@ static void serial8250_shutdown(struct uart_port *port) if (port->irq) serial_unlink_irq_chain(up); } +EXPORT_SYMBOL_GPL(serial8250_do_shutdown); + +static void serial8250_shutdown(struct uart_port *port) +{ + if (port->shutdown) + port->shutdown(port); + else + serial8250_do_shutdown(port); +} static unsigned int serial8250_get_divisor(struct uart_port *port, unsigned int baud) { @@ -3466,6 +3483,10 @@ int serial8250_register_8250_port(struct uart_8250_port *up) /* Possibly override set_termios call */ if (up->port.set_termios) uart->port.set_termios = up->port.set_termios; + if (up->port.startup) + uart->port.startup = up->port.startup; + if (up->port.shutdown) + uart->port.shutdown = up->port.shutdown; if (up->port.pm) uart->port.pm = up->port.pm; if (up->port.handle_break) diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index 6dd671765312..6fc9d7bee05e 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h @@ -124,6 +124,8 @@ extern void serial8250_early_out(struct uart_port *port, int offset, int value); extern int setup_early_serial8250_console(char *cmdline); extern void serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old); +extern int serial8250_do_startup(struct uart_port *port); +extern void serial8250_do_shutdown(struct uart_port *port); extern void serial8250_do_pm(struct uart_port *port, unsigned int state, unsigned int oldstate); extern int fsl8250_handle_irq(struct uart_port *port); diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 8cb267b1fcd5..3bd7d55eebce 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -122,6 +122,8 @@ struct uart_port { void (*set_termios)(struct uart_port *, struct ktermios *new, struct ktermios *old); + int (*startup)(struct uart_port *port); + void (*shutdown)(struct uart_port *port); int (*handle_irq)(struct uart_port *); void (*pm)(struct uart_port *, unsigned int state, unsigned int old); -- cgit v1.2.3 From 9a37110d20c95d1ebf6c04881177fe8f62831db2 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 10 Sep 2014 14:31:39 -0400 Subject: locking: Add WARN_ON_ONCE lock assertion An interface may need to assert a lock invariant and not flood the system logs; add a lockdep helper macro equivalent to lockdep_assert_held() which only WARNs once. Signed-off-by: Peter Hurley Acked-by: Ingo Molnar Signed-off-by: Greg Kroah-Hartman --- include/linux/lockdep.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'include') diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h index 008388f920d7..64c7425afbce 100644 --- a/include/linux/lockdep.h +++ b/include/linux/lockdep.h @@ -362,6 +362,10 @@ extern void lockdep_trace_alloc(gfp_t mask); WARN_ON(debug_locks && !lockdep_is_held(l)); \ } while (0) +#define lockdep_assert_held_once(l) do { \ + WARN_ON_ONCE(debug_locks && !lockdep_is_held(l)); \ + } while (0) + #define lockdep_recursing(tsk) ((tsk)->lockdep_recursion) #else /* !CONFIG_LOCKDEP */ @@ -412,6 +416,7 @@ struct lock_class_key { }; #define lockdep_depth(tsk) (0) #define lockdep_assert_held(l) do { (void)(l); } while (0) +#define lockdep_assert_held_once(l) do { (void)(l); } while (0) #define lockdep_recursing(tsk) (0) -- cgit v1.2.3 From 299245a145b2ad4cfb4c5432eb1264299f55e7e0 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 10 Sep 2014 15:06:24 -0400 Subject: serial: core: Privatize modem status enable flags The serial core uses the tty port flags, ASYNC_CTS_FLOW and ASYNC_CD_CHECK, to track whether CTS and DCD changes should be ignored or handled. However, the tty port flags are not safe for atomic bit operations and no lock provides serialized updates. Introduce the struct uart_port status field to track CTS and DCD enable states, and serialize access with uart port lock. Substitute uart_cts_enabled() helper for tty_port_cts_enabled(). Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mxs-auart.c | 2 +- drivers/tty/serial/serial_core.c | 29 +++++++++++++++++------------ include/linux/serial_core.h | 12 ++++++++++++ 3 files changed, 30 insertions(+), 13 deletions(-) (limited to 'include') diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c index b5c329248c81..10c29334fe2f 100644 --- a/drivers/tty/serial/mxs-auart.c +++ b/drivers/tty/serial/mxs-auart.c @@ -408,7 +408,7 @@ static void mxs_auart_set_mctrl(struct uart_port *u, unsigned mctrl) ctrl &= ~(AUART_CTRL2_RTSEN | AUART_CTRL2_RTS); if (mctrl & TIOCM_RTS) { - if (tty_port_cts_enabled(&u->state->port)) + if (uart_cts_enabled(u)) ctrl |= AUART_CTRL2_RTSEN; else ctrl |= AUART_CTRL2_RTS; diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index f764de32b658..dd21ed900635 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -59,6 +59,11 @@ static void uart_change_pm(struct uart_state *state, static void uart_port_shutdown(struct tty_port *port); +static int uart_dcd_enabled(struct uart_port *uport) +{ + return uport->status & UPSTAT_DCD_ENABLE; +} + /* * This routine is used by the interrupt handler to schedule processing in * the software interrupt portion of the driver. @@ -130,7 +135,6 @@ static int uart_port_startup(struct tty_struct *tty, struct uart_state *state, int init_hw) { struct uart_port *uport = state->uart_port; - struct tty_port *port = &state->port; unsigned long page; int retval = 0; @@ -176,12 +180,12 @@ static int uart_port_startup(struct tty_struct *tty, struct uart_state *state, uart_set_mctrl(uport, TIOCM_RTS | TIOCM_DTR); } - if (tty_port_cts_enabled(port)) { - spin_lock_irq(&uport->lock); + spin_lock_irq(&uport->lock); + if (uart_cts_enabled(uport)) { if (!(uport->ops->get_mctrl(uport) & TIOCM_CTS)) tty->hw_stopped = 1; - spin_unlock_irq(&uport->lock); } + spin_unlock_irq(&uport->lock); } /* @@ -435,7 +439,6 @@ EXPORT_SYMBOL(uart_get_divisor); static void uart_change_speed(struct tty_struct *tty, struct uart_state *state, struct ktermios *old_termios) { - struct tty_port *port = &state->port; struct uart_port *uport = state->uart_port; struct ktermios *termios; @@ -450,17 +453,19 @@ static void uart_change_speed(struct tty_struct *tty, struct uart_state *state, uport->ops->set_termios(uport, termios, old_termios); /* - * Set flags based on termios cflag + * Set modem status enables based on termios cflag */ + spin_lock_irq(&uport->lock); if (termios->c_cflag & CRTSCTS) - set_bit(ASYNCB_CTS_FLOW, &port->flags); + uport->status |= UPSTAT_CTS_ENABLE; else - clear_bit(ASYNCB_CTS_FLOW, &port->flags); + uport->status &= ~UPSTAT_CTS_ENABLE; if (termios->c_cflag & CLOCAL) - clear_bit(ASYNCB_CHECK_CD, &port->flags); + uport->status &= ~UPSTAT_DCD_ENABLE; else - set_bit(ASYNCB_CHECK_CD, &port->flags); + uport->status |= UPSTAT_DCD_ENABLE; + spin_unlock_irq(&uport->lock); } static inline int __uart_put_char(struct uart_port *port, @@ -2765,7 +2770,7 @@ void uart_handle_dcd_change(struct uart_port *uport, unsigned int status) uport->icount.dcd++; - if (port->flags & ASYNC_CHECK_CD) { + if (uart_dcd_enabled(uport)) { if (status) wake_up_interruptible(&port->open_wait); else if (tty) @@ -2790,7 +2795,7 @@ void uart_handle_cts_change(struct uart_port *uport, unsigned int status) uport->icount.cts++; - if (tty_port_cts_enabled(port)) { + if (uart_cts_enabled(uport)) { if (tty->hw_stopped) { if (status) { tty->hw_stopped = 0; diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 3bd7d55eebce..452c9cc9d717 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -112,6 +112,7 @@ struct uart_icount { }; typedef unsigned int __bitwise__ upf_t; +typedef unsigned int __bitwise__ upstat_t; struct uart_port { spinlock_t lock; /* port lock */ @@ -190,6 +191,12 @@ struct uart_port { #define UPF_CHANGE_MASK ((__force upf_t) (0x17fff)) #define UPF_USR_MASK ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY)) + /* status must be updated while holding port lock */ + upstat_t status; + +#define UPSTAT_CTS_ENABLE ((__force upstat_t) (1 << 0)) +#define UPSTAT_DCD_ENABLE ((__force upstat_t) (1 << 1)) + unsigned int mctrl; /* current modem ctrl settings */ unsigned int timeout; /* character-based timeout */ unsigned int type; /* port type */ @@ -355,6 +362,11 @@ static inline int uart_tx_stopped(struct uart_port *port) return 0; } +static inline bool uart_cts_enabled(struct uart_port *uport) +{ + return uport->status & UPSTAT_CTS_ENABLE; +} + /* * The following are helper functions for the low level drivers. */ -- cgit v1.2.3 From d01f4d181c92877ecc678adce248a30cb7077ff1 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 10 Sep 2014 15:06:26 -0400 Subject: serial: core: Privatize tty->hw_stopped tty->hw_stopped is not used by the tty core and is thread-unsafe; hw_stopped is a member of a bitfield whose fields are updated non-atomically and no lock is suitable for serializing updates. Replace serial core usage of tty->hw_stopped with uport->hw_stopped. Use int storage which works around Alpha EV4/5 non-atomic byte storage, since uart_port uses different locks to protect certain fields within the structure. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/bfin_uart.c | 14 +++++++------- drivers/tty/serial/serial_core.c | 28 +++++++++++++--------------- include/linux/serial_core.h | 3 ++- 3 files changed, 22 insertions(+), 23 deletions(-) (limited to 'include') diff --git a/drivers/tty/serial/bfin_uart.c b/drivers/tty/serial/bfin_uart.c index dec0fd725d80..fc9fbf3f24a7 100644 --- a/drivers/tty/serial/bfin_uart.c +++ b/drivers/tty/serial/bfin_uart.c @@ -108,22 +108,22 @@ static void bfin_serial_set_mctrl(struct uart_port *port, unsigned int mctrl) static irqreturn_t bfin_serial_mctrl_cts_int(int irq, void *dev_id) { struct bfin_serial_port *uart = dev_id; - unsigned int status = bfin_serial_get_mctrl(&uart->port); + struct uart_port *uport = &uart->port; + unsigned int status = bfin_serial_get_mctrl(uport); #ifdef CONFIG_SERIAL_BFIN_HARD_CTSRTS - struct tty_struct *tty = uart->port.state->port.tty; UART_CLEAR_SCTS(uart); - if (tty->hw_stopped) { + if (uport->hw_stopped) { if (status) { - tty->hw_stopped = 0; - uart_write_wakeup(&uart->port); + uport->hw_stopped = 0; + uart_write_wakeup(uport); } } else { if (!status) - tty->hw_stopped = 1; + uport->hw_stopped = 1; } #endif - uart_handle_cts_change(&uart->port, status & TIOCM_CTS); + uart_handle_cts_change(uport, status & TIOCM_CTS); return IRQ_HANDLED; } diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index dd21ed900635..7d51e26627fb 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -95,7 +95,7 @@ static void __uart_start(struct tty_struct *tty) struct uart_state *state = tty->driver_data; struct uart_port *port = state->uart_port; - if (!tty->stopped && !tty->hw_stopped) + if (!uart_tx_stopped(port)) port->ops->start_tx(port); } @@ -181,10 +181,11 @@ static int uart_port_startup(struct tty_struct *tty, struct uart_state *state, } spin_lock_irq(&uport->lock); - if (uart_cts_enabled(uport)) { - if (!(uport->ops->get_mctrl(uport) & TIOCM_CTS)) - tty->hw_stopped = 1; - } + if (uart_cts_enabled(uport) && + !(uport->ops->get_mctrl(uport) & TIOCM_CTS)) + uport->hw_stopped = 1; + else + uport->hw_stopped = 0; spin_unlock_irq(&uport->lock); } @@ -949,7 +950,7 @@ static int uart_get_lsr_info(struct tty_struct *tty, */ if (uport->x_char || ((uart_circ_chars_pending(&state->xmit) > 0) && - !tty->stopped && !tty->hw_stopped)) + !uart_tx_stopped(uport))) result &= ~TIOCSER_TEMT; return put_user(result, value); @@ -1295,7 +1296,7 @@ static void uart_set_termios(struct tty_struct *tty, /* * If the port is doing h/w assisted flow control, do nothing. - * We assume that tty->hw_stopped has never been set. + * We assume that port->hw_stopped has never been set. */ if (uport->flags & UPF_HARD_FLOW) return; @@ -1303,7 +1304,7 @@ static void uart_set_termios(struct tty_struct *tty, /* Handle turning off CRTSCTS */ if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) { spin_lock_irqsave(&uport->lock, flags); - tty->hw_stopped = 0; + uport->hw_stopped = 0; __uart_start(tty); spin_unlock_irqrestore(&uport->lock, flags); } @@ -1311,7 +1312,7 @@ static void uart_set_termios(struct tty_struct *tty, else if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) { spin_lock_irqsave(&uport->lock, flags); if (!(uport->ops->get_mctrl(uport) & TIOCM_CTS)) { - tty->hw_stopped = 1; + uport->hw_stopped = 1; uport->ops->stop_tx(uport); } spin_unlock_irqrestore(&uport->lock, flags); @@ -2788,23 +2789,20 @@ EXPORT_SYMBOL_GPL(uart_handle_dcd_change); */ void uart_handle_cts_change(struct uart_port *uport, unsigned int status) { - struct tty_port *port = &uport->state->port; - struct tty_struct *tty = port->tty; - lockdep_assert_held_once(&uport->lock); uport->icount.cts++; if (uart_cts_enabled(uport)) { - if (tty->hw_stopped) { + if (uport->hw_stopped) { if (status) { - tty->hw_stopped = 0; + uport->hw_stopped = 0; uport->ops->start_tx(uport); uart_write_wakeup(uport); } } else { if (!status) { - tty->hw_stopped = 1; + uport->hw_stopped = 1; uport->ops->stop_tx(uport); } } diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 452c9cc9d717..204c452a7567 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -197,6 +197,7 @@ struct uart_port { #define UPSTAT_CTS_ENABLE ((__force upstat_t) (1 << 0)) #define UPSTAT_DCD_ENABLE ((__force upstat_t) (1 << 1)) + int hw_stopped; /* sw-assisted CTS flow state */ unsigned int mctrl; /* current modem ctrl settings */ unsigned int timeout; /* character-based timeout */ unsigned int type; /* port type */ @@ -357,7 +358,7 @@ int uart_resume_port(struct uart_driver *reg, struct uart_port *port); static inline int uart_tx_stopped(struct uart_port *port) { struct tty_struct *tty = port->state->port.tty; - if(tty->stopped || tty->hw_stopped) + if (tty->stopped || port->hw_stopped) return 1; return 0; } -- cgit v1.2.3 From d7a855bd6ab25d10d5e3b6aeb53d9c57fa17b808 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 10 Sep 2014 15:06:30 -0400 Subject: tty: Convert tty_struct bitfield to ints The stopped, hw_stopped, flow_stopped and packet bits are smp-unsafe and interrupt-unsafe. For example, CPU 0 | CPU 1 | tty->flow_stopped = 1 | tty->hw_stopped = 0 One of these updates will be corrupted, as the bitwise operation on the bitfield is non-atomic. Ensure each flag has a separate memory location, so concurrent updates do not corrupt orthogonal states. Because DEC Alpha EV4 and EV5 cpus (from 1995) perform RMW on smaller-than-machine-word storage, "separate memory location" must be int instead of byte. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- include/linux/tty.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/tty.h b/include/linux/tty.h index 84132942902a..4cfd4a82fc31 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -261,7 +261,10 @@ struct tty_struct { unsigned long flags; int count; struct winsize winsize; /* winsize_mutex */ - unsigned char stopped:1, hw_stopped:1, flow_stopped:1, packet:1; + int stopped; + int flow_stopped; + int hw_stopped; + int packet; unsigned char ctrl_status; /* ctrl_lock */ unsigned int receive_room; /* Bytes free for queue */ int flow_change; -- cgit v1.2.3 From f9e053dcfc02b0ad29daec8524fb1afe09774976 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 10 Sep 2014 15:06:31 -0400 Subject: tty: Serialize tty flow control changes with flow_lock Without serialization, the flow control state can become inverted wrt. the actual hardware state. For example, CPU 0 | CPU 1 stop_tty() | lock ctrl_lock | tty->stopped = 1 | unlock ctrl_lock | | start_tty() | lock ctrl_lock | tty->stopped = 0 | unlock ctrl_lock | driver->start() driver->stop() | In this case, the flow control state now indicates the tty has been started, but the actual hardware state has actually been stopped. Introduce tty->flow_lock spinlock to serialize tty flow control changes. Split out unlocked __start_tty()/__stop_tty() flavors for use by ioctl(TCXONC) in follow-on patch. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_io.c | 39 ++++++++++++++++++++++++++++----------- include/linux/tty.h | 5 ++++- include/linux/tty_driver.h | 4 ++++ 3 files changed, 36 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index d4eb2a8b7047..b8ddfef6b5d8 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -919,18 +919,18 @@ void no_tty(void) * but not always. * * Locking: - * Uses the tty control lock internally + * ctrl_lock + * flow_lock */ -void stop_tty(struct tty_struct *tty) +void __stop_tty(struct tty_struct *tty) { unsigned long flags; - spin_lock_irqsave(&tty->ctrl_lock, flags); - if (tty->stopped) { - spin_unlock_irqrestore(&tty->ctrl_lock, flags); + + if (tty->stopped) return; - } tty->stopped = 1; + spin_lock_irqsave(&tty->ctrl_lock, flags); if (tty->link && tty->link->packet) { tty->ctrl_status &= ~TIOCPKT_START; tty->ctrl_status |= TIOCPKT_STOP; @@ -941,6 +941,14 @@ void stop_tty(struct tty_struct *tty) (tty->ops->stop)(tty); } +void stop_tty(struct tty_struct *tty) +{ + unsigned long flags; + + spin_lock_irqsave(&tty->flow_lock, flags); + __stop_tty(tty); + spin_unlock_irqrestore(&tty->flow_lock, flags); +} EXPORT_SYMBOL(stop_tty); /** @@ -954,17 +962,17 @@ EXPORT_SYMBOL(stop_tty); * * Locking: * ctrl_lock + * flow_lock */ -void start_tty(struct tty_struct *tty) +void __start_tty(struct tty_struct *tty) { unsigned long flags; - spin_lock_irqsave(&tty->ctrl_lock, flags); - if (!tty->stopped || tty->flow_stopped) { - spin_unlock_irqrestore(&tty->ctrl_lock, flags); + + if (!tty->stopped || tty->flow_stopped) return; - } tty->stopped = 0; + spin_lock_irqsave(&tty->ctrl_lock, flags); if (tty->link && tty->link->packet) { tty->ctrl_status &= ~TIOCPKT_STOP; tty->ctrl_status |= TIOCPKT_START; @@ -977,6 +985,14 @@ void start_tty(struct tty_struct *tty) tty_wakeup(tty); } +void start_tty(struct tty_struct *tty) +{ + unsigned long flags; + + spin_lock_irqsave(&tty->flow_lock, flags); + __start_tty(tty); + spin_unlock_irqrestore(&tty->flow_lock, flags); +} EXPORT_SYMBOL(start_tty); /* We limit tty time update visibility to every 8 seconds or so. */ @@ -3019,6 +3035,7 @@ struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx) INIT_WORK(&tty->hangup_work, do_tty_hangup); mutex_init(&tty->atomic_write_lock); spin_lock_init(&tty->ctrl_lock); + spin_lock_init(&tty->flow_lock); INIT_LIST_HEAD(&tty->tty_files); INIT_WORK(&tty->SAK_work, do_SAK_work); diff --git a/include/linux/tty.h b/include/linux/tty.h index 4cfd4a82fc31..fd4148d3a261 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -252,6 +252,7 @@ struct tty_struct { struct rw_semaphore termios_rwsem; struct mutex winsize_mutex; spinlock_t ctrl_lock; + spinlock_t flow_lock; /* Termios values are protected by the termios rwsem */ struct ktermios termios, termios_locked; struct termiox *termiox; /* May be NULL for unsupported */ @@ -261,7 +262,7 @@ struct tty_struct { unsigned long flags; int count; struct winsize winsize; /* winsize_mutex */ - int stopped; + int stopped; /* flow_lock */ int flow_stopped; int hw_stopped; int packet; @@ -400,7 +401,9 @@ extern int tty_paranoia_check(struct tty_struct *tty, struct inode *inode, extern char *tty_name(struct tty_struct *tty, char *buf); extern void tty_wait_until_sent(struct tty_struct *tty, long timeout); extern int tty_check_change(struct tty_struct *tty); +extern void __stop_tty(struct tty_struct *tty); extern void stop_tty(struct tty_struct *tty); +extern void __start_tty(struct tty_struct *tty); extern void start_tty(struct tty_struct *tty); extern int tty_register_driver(struct tty_driver *driver); extern int tty_unregister_driver(struct tty_driver *driver); diff --git a/include/linux/tty_driver.h b/include/linux/tty_driver.h index e48c608a8fa8..92e337c18839 100644 --- a/include/linux/tty_driver.h +++ b/include/linux/tty_driver.h @@ -152,6 +152,8 @@ * This routine notifies the tty driver that it should stop * outputting characters to the tty device. * + * Called with ->flow_lock held. Serialized with start() method. + * * Optional: * * Note: Call stop_tty not this method. @@ -161,6 +163,8 @@ * This routine notifies the tty driver that it resume sending * characters to the tty device. * + * Called with ->flow_lock held. Serialized with stop() method. + * * Optional: * * Note: Call start_tty not this method. -- cgit v1.2.3 From c545b66c6922b002b5fe224a6eaec58c913650b5 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 10 Sep 2014 15:06:33 -0400 Subject: tty: Serialize tcflow() with other tty flow control changes Use newly-introduced tty->flow_lock to serialize updates to tty->flow_stopped (via tcflow()) and with concurrent tty flow control changes from other sources. Merge the storage for ->stopped and ->flow_stopped, now that both flags are serialized by ->flow_lock. The padding bits are necessary to force the compiler to allocate the type specified; otherwise, gcc will ignore the type specifier and allocate the minimum number of bytes necessary to store the bitfield. In turn, this would allow Alpha EV4 and EV5 cpus to corrupt adjacent byte storage because those cpus use RMW to store byte and short data. gcc versions < 4.7.2 will also corrupt storage adjacent to bitfields smaller than unsigned long on ia64, ppc64, hppa64 and sparc64, thus requiring more than unsigned int storage (which would otherwise be sufficient to workaround the Alpha non-atomic byte/short storage problem). Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_ioctl.c | 8 ++++++-- include/linux/tty.h | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c index cd1285c3bfe9..dcf5c0af7de9 100644 --- a/drivers/tty/tty_ioctl.c +++ b/drivers/tty/tty_ioctl.c @@ -1177,16 +1177,20 @@ int n_tty_ioctl_helper(struct tty_struct *tty, struct file *file, return retval; switch (arg) { case TCOOFF: + spin_lock_irq(&tty->flow_lock); if (!tty->flow_stopped) { tty->flow_stopped = 1; - stop_tty(tty); + __stop_tty(tty); } + spin_unlock_irq(&tty->flow_lock); break; case TCOON: + spin_lock_irq(&tty->flow_lock); if (tty->flow_stopped) { tty->flow_stopped = 0; - start_tty(tty); + __start_tty(tty); } + spin_unlock_irq(&tty->flow_lock); break; case TCIOFF: if (STOP_CHAR(tty) != __DISABLED_CHAR) diff --git a/include/linux/tty.h b/include/linux/tty.h index fd4148d3a261..d80b53642f2c 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -262,8 +262,9 @@ struct tty_struct { unsigned long flags; int count; struct winsize winsize; /* winsize_mutex */ - int stopped; /* flow_lock */ - int flow_stopped; + unsigned long stopped:1, /* flow_lock */ + flow_stopped:1, + unused:62; int hw_stopped; int packet; unsigned char ctrl_status; /* ctrl_lock */ -- cgit v1.2.3 From 136d5258b2bc4ffae99cb69874a76624c26fbfad Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 10 Sep 2014 15:06:34 -0400 Subject: tty: Move and rename send_prio_char() as tty_send_xchar() Relocate the file-scope function, send_prio_char(), as a global helper tty_send_xchar(). Remove the global declarations for tty_write_lock()/tty_write_unlock(), as these are file-scope only now. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_io.c | 33 +++++++++++++++++++++++++++++++-- drivers/tty/tty_ioctl.c | 33 ++------------------------------- include/linux/tty.h | 3 +-- 3 files changed, 34 insertions(+), 35 deletions(-) (limited to 'include') diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index c249ff1d51ce..2f6f9b5e4891 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -1023,14 +1023,14 @@ static ssize_t tty_read(struct file *file, char __user *buf, size_t count, return i; } -void tty_write_unlock(struct tty_struct *tty) +static void tty_write_unlock(struct tty_struct *tty) __releases(&tty->atomic_write_lock) { mutex_unlock(&tty->atomic_write_lock); wake_up_interruptible_poll(&tty->write_wait, POLLOUT); } -int tty_write_lock(struct tty_struct *tty, int ndelay) +static int tty_write_lock(struct tty_struct *tty, int ndelay) __acquires(&tty->atomic_write_lock) { if (!mutex_trylock(&tty->atomic_write_lock)) { @@ -1217,6 +1217,35 @@ ssize_t redirected_tty_write(struct file *file, const char __user *buf, return tty_write(file, buf, count, ppos); } +/** + * tty_send_xchar - send priority character + * + * Send a high priority character to the tty even if stopped + * + * Locking: none for xchar method, write ordering for write method. + */ + +int tty_send_xchar(struct tty_struct *tty, char ch) +{ + int was_stopped = tty->stopped; + + if (tty->ops->send_xchar) { + tty->ops->send_xchar(tty, ch); + return 0; + } + + if (tty_write_lock(tty, 0) < 0) + return -ERESTARTSYS; + + if (was_stopped) + start_tty(tty); + tty->ops->write(tty, &ch, 1); + if (was_stopped) + stop_tty(tty); + tty_write_unlock(tty); + return 0; +} + static char ptychar[] = "pqrstuvwxyzabcde"; /** diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c index dcf5c0af7de9..ad9120d1c0f1 100644 --- a/drivers/tty/tty_ioctl.c +++ b/drivers/tty/tty_ioctl.c @@ -911,35 +911,6 @@ static int set_ltchars(struct tty_struct *tty, struct ltchars __user *ltchars) } #endif -/** - * send_prio_char - send priority character - * - * Send a high priority character to the tty even if stopped - * - * Locking: none for xchar method, write ordering for write method. - */ - -static int send_prio_char(struct tty_struct *tty, char ch) -{ - int was_stopped = tty->stopped; - - if (tty->ops->send_xchar) { - tty->ops->send_xchar(tty, ch); - return 0; - } - - if (tty_write_lock(tty, 0) < 0) - return -ERESTARTSYS; - - if (was_stopped) - start_tty(tty); - tty->ops->write(tty, &ch, 1); - if (was_stopped) - stop_tty(tty); - tty_write_unlock(tty); - return 0; -} - /** * tty_change_softcar - carrier change ioctl helper * @tty: tty to update @@ -1194,11 +1165,11 @@ int n_tty_ioctl_helper(struct tty_struct *tty, struct file *file, break; case TCIOFF: if (STOP_CHAR(tty) != __DISABLED_CHAR) - return send_prio_char(tty, STOP_CHAR(tty)); + return tty_send_xchar(tty, STOP_CHAR(tty)); break; case TCION: if (START_CHAR(tty) != __DISABLED_CHAR) - return send_prio_char(tty, START_CHAR(tty)); + return tty_send_xchar(tty, START_CHAR(tty)); break; default: return -EINVAL; diff --git a/include/linux/tty.h b/include/linux/tty.h index d80b53642f2c..4c6b9fd3c750 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -418,6 +418,7 @@ extern void tty_unregister_device(struct tty_driver *driver, unsigned index); extern int tty_read_raw_data(struct tty_struct *tty, unsigned char *bufp, int buflen); extern void tty_write_message(struct tty_struct *tty, char *msg); +extern int tty_send_xchar(struct tty_struct *tty, char ch); extern int tty_put_char(struct tty_struct *tty, unsigned char c); extern int tty_chars_in_buffer(struct tty_struct *tty); extern int tty_write_room(struct tty_struct *tty); @@ -502,8 +503,6 @@ extern struct tty_struct *tty_pair_get_pty(struct tty_struct *tty); extern struct mutex tty_mutex; extern spinlock_t tty_files_lock; -extern void tty_write_unlock(struct tty_struct *tty); -extern int tty_write_lock(struct tty_struct *tty, int ndelay); #define tty_is_writelocked(tty) (mutex_is_locked(&tty->atomic_write_lock)) extern void tty_port_init(struct tty_port *port); -- cgit v1.2.3 From 99416322dd16b810ba74098cc50ef2a844091d35 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 10 Sep 2014 15:06:36 -0400 Subject: tty: Workaround Alpha non-atomic byte storage in tty_struct The Alpha EV4/EV5 cpus can corrupt adjacent byte and short data because those cpus use RMW to store byte and short data. Thus, concurrent adjacent byte stores could become corrupted, if serialized by a different lock. tty_struct uses different locks to protect certain fields within the structure, and thus is vulnerable to byte stores which are not atomic. Merge the ->ctrl_status byte and packet mode bit, both protected by the ->ctrl_lock, into an unsigned long. The padding bits are necessary to force the compiler to allocate the type specified; otherwise, gcc will ignore the type specifier and allocate the minimum number of bytes required to store the bitfield. In turn, this would allow Alpha EV4/EV5 cpus to corrupt adjacent byte or short storage (because those cpus use RMW to store byte and short data). gcc versions < 4.7.2 will also corrupt storage adjacent to bitfields smaller than unsigned long on ia64, ppc64, hppa64, and sparc64, thus requiring more than unsigned int storage (which would otherwise be sufficient to fix the Alpha non-atomic storage problem). Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- include/linux/tty.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/tty.h b/include/linux/tty.h index 4c6b9fd3c750..546e94557895 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -266,8 +266,9 @@ struct tty_struct { flow_stopped:1, unused:62; int hw_stopped; - int packet; - unsigned char ctrl_status; /* ctrl_lock */ + unsigned long ctrl_status:8, /* ctrl_lock */ + packet:1, + unused_ctrl:55; unsigned int receive_room; /* Bytes free for queue */ int flow_change; -- cgit v1.2.3 From cc952e7017fa2e8871ee6a94f2c606ff5911f61e Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 24 Sep 2014 06:26:21 -0400 Subject: tty: Fix width of unsigned long bitfield padding Commit c545b66c6922b002b5fe224a6eaec58c913650b5, 'tty: Serialize tcflow() with other tty flow control changes' and commit 99416322dd16b810ba74098cc50ef2a844091d35, 'tty: Workaround Alpha non-atomic byte storage in tty_struct' work around compiler bugs and non-atomic storage on multiple arches by padding bitfields out to the declared type which is unsigned long. However, the width varies by arch. Pad bitfields to actual width of unsigned long (which is BITS_PER_LONG). Reported-by: Fengguang Wu Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- include/linux/tty.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/tty.h b/include/linux/tty.h index 546e94557895..5171ef8f7b85 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -264,11 +264,11 @@ struct tty_struct { struct winsize winsize; /* winsize_mutex */ unsigned long stopped:1, /* flow_lock */ flow_stopped:1, - unused:62; + unused:BITS_PER_LONG - 2; int hw_stopped; unsigned long ctrl_status:8, /* ctrl_lock */ packet:1, - unused_ctrl:55; + unused_ctrl:BITS_PER_LONG - 9; unsigned int receive_room; /* Bytes free for queue */ int flow_change; -- cgit v1.2.3 From 234abab143aef82c0ef1f2de409c0db96b666f3c Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 10 Sep 2014 21:29:56 +0200 Subject: tty: serial: 8250_core: allow to set ->throttle / ->unthrottle callbacks The OMAP UART provides support for HW assisted flow control. What is missing is the support to throttle / unthrottle callbacks which are used by the omap-serial driver at the moment. This patch adds the callbacks. It should be safe to add them since they are only invoked from the serial_core (uart_throttle()) if the feature flags are set. Reviewed-by: Tony Lindgren Tested-by: Tony Lindgren Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 14 ++++++++++++++ include/linux/serial_core.h | 2 ++ 2 files changed, 16 insertions(+) (limited to 'include') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index a0c1d64f34c5..68c44d97091b 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -1319,6 +1319,16 @@ static void serial8250_start_tx(struct uart_port *port) } } +static void serial8250_throttle(struct uart_port *port) +{ + port->throttle(port); +} + +static void serial8250_unthrottle(struct uart_port *port) +{ + port->unthrottle(port); +} + static void serial8250_stop_rx(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); @@ -2912,6 +2922,8 @@ static struct uart_ops serial8250_pops = { .get_mctrl = serial8250_get_mctrl, .stop_tx = serial8250_stop_tx, .start_tx = serial8250_start_tx, + .throttle = serial8250_throttle, + .unthrottle = serial8250_unthrottle, .stop_rx = serial8250_stop_rx, .enable_ms = serial8250_enable_ms, .break_ctl = serial8250_break_ctl, @@ -3462,6 +3474,8 @@ int serial8250_register_8250_port(struct uart_8250_port *up) uart->capabilities = up->capabilities; uart->rs485_config = up->rs485_config; uart->rs485 = up->rs485; + uart->port.throttle = up->port.throttle; + uart->port.unthrottle = up->port.unthrottle; /* Take tx_loadsz from fifosize if it wasn't set separately */ if (uart->port.fifosize && !uart->tx_loadsz) diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 204c452a7567..21c2e05c1bc3 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -125,6 +125,8 @@ struct uart_port { struct ktermios *old); int (*startup)(struct uart_port *port); void (*shutdown)(struct uart_port *port); + void (*throttle)(struct uart_port *port); + void (*unthrottle)(struct uart_port *port); int (*handle_irq)(struct uart_port *); void (*pm)(struct uart_port *, unsigned int state, unsigned int old); -- cgit v1.2.3 From d74d5d1b7288ff9d4439c8c7e0e314cde9743467 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 10 Sep 2014 21:29:57 +0200 Subject: tty: serial: 8250_core: add run time pm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While comparing the OMAP-serial and the 8250 part of this I noticed that the latter does not use run time-pm. Here are the pieces. It is basically a get before first register access and a last_busy + put after last access. This has to be enabled from userland _and_ UART_CAP_RPM is required for this. The runtime PM can usually work transparently in the background however there is one exception to this: After serial8250_tx_chars() completes there still may be unsent bytes in the FIFO (depending on CPU speed vs baud rate + flow control). Even if the TTY-buffer is empty we do not want RPM to disable the device because it won't send the remaining bytes. Instead we leave serial8250_tx_chars() with RPM enabled and wait for the FIFO empty interrupt. Once we enter serial8250_tx_chars() with an empty buffer we know that the FIFO is empty and since we are not going to send anything, we can disable the device. That xchg() is to ensure that serial8250_tx_chars() can be called multiple times and only the first invocation will actually invoke the runtime PM function. So that the last invocation of __stop_tx() will disable runtime pm. NOTE: do not enable RPM on the device unless you know what you do! If the device goes idle, it won't be woken up by incomming RX data _unless_ there is a wakeup irq configured which is usually the RX pin configure for wakeup via the reset module. The RX activity will then wake up the device from idle. However the first character is garbage and lost. The following bytes will be received once the device is up in time. On the beagle board xm (omap3) it takes approx 13ms from the first wakeup byte until the first byte that is received properly if the device was in core-off. v5…v8: - drop RPM from serial8250_set_mctrl() it will be used in restore path which already has RPM active and holds dev->power.lock v4…v5: - add a wrapper around rpm function and introduce UART_CAP_RPM to ensure RPM put is invoked after the TX FIFO is empty. v3…v4: - added runtime to the console code - removed device_may_wakeup() from serial8250_set_sleep() Cc: mika.westerberg@linux.intel.com Reviewed-by: Tony Lindgren Tested-by: Tony Lindgren Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250.h | 1 + drivers/tty/serial/8250/8250_core.c | 133 ++++++++++++++++++++++++++++++++---- include/linux/serial_8250.h | 1 + 3 files changed, 122 insertions(+), 13 deletions(-) (limited to 'include') diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index 85bfec58d77c..1bcb4b2141a6 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -72,6 +72,7 @@ struct serial8250_config { #define UART_CAP_UUE (1 << 12) /* UART needs IER bit 6 set (Xscale) */ #define UART_CAP_RTOIE (1 << 13) /* UART needs IER bit 4 set (Xscale, Tegra) */ #define UART_CAP_HFIFO (1 << 14) /* UART has a "hidden" FIFO */ +#define UART_CAP_RPM (1 << 15) /* Runtime PM is active while idle */ #define UART_BUG_QUOT (1 << 0) /* UART has buggy quot LSB */ #define UART_BUG_TXEN (1 << 1) /* UART has buggy TX IIR status */ diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 68c44d97091b..3cf5c98013e4 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -38,6 +38,7 @@ #include #include #include +#include #ifdef CONFIG_SPARC #include #endif @@ -540,6 +541,53 @@ void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p) } EXPORT_SYMBOL_GPL(serial8250_clear_and_reinit_fifos); +static void serial8250_rpm_get(struct uart_8250_port *p) +{ + if (!(p->capabilities & UART_CAP_RPM)) + return; + pm_runtime_get_sync(p->port.dev); +} + +static void serial8250_rpm_put(struct uart_8250_port *p) +{ + if (!(p->capabilities & UART_CAP_RPM)) + return; + pm_runtime_mark_last_busy(p->port.dev); + pm_runtime_put_autosuspend(p->port.dev); +} + +/* + * This two wrapper ensure, that enable_runtime_pm_tx() can be called more than + * once and disable_runtime_pm_tx() will still disable RPM because the fifo is + * empty and the HW can idle again. + */ +static void serial8250_rpm_get_tx(struct uart_8250_port *p) +{ + unsigned char rpm_active; + + if (!(p->capabilities & UART_CAP_RPM)) + return; + + rpm_active = xchg(&p->rpm_tx_active, 1); + if (rpm_active) + return; + pm_runtime_get_sync(p->port.dev); +} + +static void serial8250_rpm_put_tx(struct uart_8250_port *p) +{ + unsigned char rpm_active; + + if (!(p->capabilities & UART_CAP_RPM)) + return; + + rpm_active = xchg(&p->rpm_tx_active, 0); + if (!rpm_active) + return; + pm_runtime_mark_last_busy(p->port.dev); + pm_runtime_put_autosuspend(p->port.dev); +} + /* * IER sleep support. UARTs which have EFRs need the "extended * capability" bit enabled. Note that on XR16C850s, we need to @@ -554,10 +602,11 @@ static void serial8250_set_sleep(struct uart_8250_port *p, int sleep) * offset but the UART channel may only write to the corresponding * bit. */ + serial8250_rpm_get(p); if ((p->port.type == PORT_XR17V35X) || (p->port.type == PORT_XR17D15X)) { serial_out(p, UART_EXAR_SLEEP, sleep ? 0xff : 0); - return; + goto out; } if (p->capabilities & UART_CAP_SLEEP) { @@ -573,6 +622,8 @@ static void serial8250_set_sleep(struct uart_8250_port *p, int sleep) serial_out(p, UART_LCR, 0); } } +out: + serial8250_rpm_put(p); } #ifdef CONFIG_SERIAL_8250_RSA @@ -1273,6 +1324,7 @@ static inline void __stop_tx(struct uart_8250_port *p) if (p->ier & UART_IER_THRI) { p->ier &= ~UART_IER_THRI; serial_out(p, UART_IER, p->ier); + serial8250_rpm_put_tx(p); } } @@ -1280,6 +1332,7 @@ static void serial8250_stop_tx(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); + serial8250_rpm_get(up); __stop_tx(up); /* @@ -1289,12 +1342,14 @@ static void serial8250_stop_tx(struct uart_port *port) up->acr |= UART_ACR_TXDIS; serial_icr_write(up, UART_ACR, up->acr); } + serial8250_rpm_put(up); } static void serial8250_start_tx(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); + serial8250_rpm_get_tx(up); if (up->dma && !serial8250_tx_dma(up)) { return; } else if (!(up->ier & UART_IER_THRI)) { @@ -1333,9 +1388,13 @@ static void serial8250_stop_rx(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); + serial8250_rpm_get(up); + up->ier &= ~UART_IER_RLSI; up->port.read_status_mask &= ~UART_LSR_DR; serial_port_out(port, UART_IER, up->ier); + + serial8250_rpm_put(up); } static void serial8250_enable_ms(struct uart_port *port) @@ -1347,7 +1406,10 @@ static void serial8250_enable_ms(struct uart_port *port) return; up->ier |= UART_IER_MSI; + + serial8250_rpm_get(up); serial_port_out(port, UART_IER, up->ier); + serial8250_rpm_put(up); } /* @@ -1469,7 +1531,12 @@ void serial8250_tx_chars(struct uart_8250_port *up) DEBUG_INTR("THRE..."); - if (uart_circ_empty(xmit)) + /* + * With RPM enabled, we have to wait once the FIFO is empty before the + * HW can go idle. So we get here once again with empty FIFO and disable + * the interrupt and RPM in __stop_tx() + */ + if (uart_circ_empty(xmit) && !(up->capabilities & UART_CAP_RPM)) __stop_tx(up); } EXPORT_SYMBOL_GPL(serial8250_tx_chars); @@ -1537,9 +1604,17 @@ EXPORT_SYMBOL_GPL(serial8250_handle_irq); static int serial8250_default_handle_irq(struct uart_port *port) { - unsigned int iir = serial_port_in(port, UART_IIR); + struct uart_8250_port *up = up_to_u8250p(port); + unsigned int iir; + int ret; + + serial8250_rpm_get(up); - return serial8250_handle_irq(port, iir); + iir = serial_port_in(port, UART_IIR); + ret = serial8250_handle_irq(port, iir); + + serial8250_rpm_put(up); + return ret; } /* @@ -1796,11 +1871,15 @@ static unsigned int serial8250_tx_empty(struct uart_port *port) unsigned long flags; unsigned int lsr; + serial8250_rpm_get(up); + spin_lock_irqsave(&port->lock, flags); lsr = serial_port_in(port, UART_LSR); up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS; spin_unlock_irqrestore(&port->lock, flags); + serial8250_rpm_put(up); + return (lsr & BOTH_EMPTY) == BOTH_EMPTY ? TIOCSER_TEMT : 0; } @@ -1810,7 +1889,9 @@ static unsigned int serial8250_get_mctrl(struct uart_port *port) unsigned int status; unsigned int ret; + serial8250_rpm_get(up); status = serial8250_modem_status(up); + serial8250_rpm_put(up); ret = 0; if (status & UART_MSR_DCD) @@ -1850,6 +1931,7 @@ static void serial8250_break_ctl(struct uart_port *port, int break_state) struct uart_8250_port *up = up_to_u8250p(port); unsigned long flags; + serial8250_rpm_get(up); spin_lock_irqsave(&port->lock, flags); if (break_state == -1) up->lcr |= UART_LCR_SBC; @@ -1857,6 +1939,7 @@ static void serial8250_break_ctl(struct uart_port *port, int break_state) up->lcr &= ~UART_LCR_SBC; serial_port_out(port, UART_LCR, up->lcr); spin_unlock_irqrestore(&port->lock, flags); + serial8250_rpm_put(up); } /* @@ -1901,12 +1984,23 @@ static void wait_for_xmitr(struct uart_8250_port *up, int bits) static int serial8250_get_poll_char(struct uart_port *port) { - unsigned char lsr = serial_port_in(port, UART_LSR); + struct uart_8250_port *up = up_to_u8250p(port); + unsigned char lsr; + int status; + + serial8250_rpm_get(up); - if (!(lsr & UART_LSR_DR)) - return NO_POLL_CHAR; + lsr = serial_port_in(port, UART_LSR); - return serial_port_in(port, UART_RX); + if (!(lsr & UART_LSR_DR)) { + status = NO_POLL_CHAR; + goto out; + } + + status = serial_port_in(port, UART_RX); +out: + serial8250_rpm_put(up); + return status; } @@ -1916,6 +2010,7 @@ static void serial8250_put_poll_char(struct uart_port *port, unsigned int ier; struct uart_8250_port *up = up_to_u8250p(port); + serial8250_rpm_get(up); /* * First save the IER then disable the interrupts */ @@ -1937,6 +2032,7 @@ static void serial8250_put_poll_char(struct uart_port *port, */ wait_for_xmitr(up, BOTH_EMPTY); serial_port_out(port, UART_IER, ier); + serial8250_rpm_put(up); } #endif /* CONFIG_CONSOLE_POLL */ @@ -1962,6 +2058,7 @@ int serial8250_do_startup(struct uart_port *port) if (port->iotype != up->cur_iotype) set_io_from_upio(port); + serial8250_rpm_get(up); if (port->type == PORT_16C950) { /* Wake up and initialize UART */ up->acr = 0; @@ -1982,7 +2079,6 @@ int serial8250_do_startup(struct uart_port *port) */ enable_rsa(up); #endif - /* * Clear the FIFO buffers and disable them. * (they will be reenabled in set_termios()) @@ -2006,7 +2102,8 @@ int serial8250_do_startup(struct uart_port *port) (serial_port_in(port, UART_LSR) == 0xff)) { printk_ratelimited(KERN_INFO "ttyS%d: LSR safety check engaged!\n", serial_index(port)); - return -ENODEV; + retval = -ENODEV; + goto out; } /* @@ -2091,7 +2188,7 @@ int serial8250_do_startup(struct uart_port *port) } else { retval = serial_link_irq_chain(up); if (retval) - return retval; + goto out; } /* @@ -2189,8 +2286,10 @@ dont_test_tx_en: outb_p(0x80, icp); inb_p(icp); } - - return 0; + retval = 0; +out: + serial8250_rpm_put(up); + return retval; } EXPORT_SYMBOL_GPL(serial8250_do_startup); @@ -2206,6 +2305,7 @@ void serial8250_do_shutdown(struct uart_port *port) struct uart_8250_port *up = up_to_u8250p(port); unsigned long flags; + serial8250_rpm_get(up); /* * Disable interrupts from this port */ @@ -2245,6 +2345,7 @@ void serial8250_do_shutdown(struct uart_port *port) * the IRQ chain. */ serial_port_in(port, UART_RX); + serial8250_rpm_put(up); del_timer_sync(&up->timer); up->timer.function = serial8250_timeout; @@ -2360,6 +2461,7 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, * Ok, we're now changing the port state. Do it with * interrupts disabled. */ + serial8250_rpm_get(up); spin_lock_irqsave(&port->lock, flags); /* @@ -2481,6 +2583,8 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, } serial8250_set_mctrl(port, port->mctrl); spin_unlock_irqrestore(&port->lock, flags); + serial8250_rpm_put(up); + /* Don't rewrite B0 */ if (tty_termios_baud_rate(termios)) tty_termios_encode_baud_rate(termios, baud, baud); @@ -3091,6 +3195,8 @@ serial8250_console_write(struct console *co, const char *s, unsigned int count) touch_nmi_watchdog(); + serial8250_rpm_get(up); + if (port->sysrq || oops_in_progress) locked = spin_trylock_irqsave(&port->lock, flags); else @@ -3127,6 +3233,7 @@ serial8250_console_write(struct console *co, const char *s, unsigned int count) if (locked) spin_unlock_irqrestore(&port->lock, flags); + serial8250_rpm_put(up); } static int __init serial8250_console_setup(struct console *co, char *options) diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index 6fc9d7bee05e..c267412a3ef4 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h @@ -84,6 +84,7 @@ struct uart_8250_port { unsigned char mcr_mask; /* mask of user bits */ unsigned char mcr_force; /* mask of forced bits */ unsigned char cur_iotype; /* Running I/O type */ + unsigned char rpm_tx_active; /* * Some bits in registers are cleared on a read, so they must -- cgit v1.2.3 From baeb7ef34952f523a129e5d1369aa42ecbe7f8c9 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Mon, 29 Sep 2014 10:21:03 +0200 Subject: tty: serial: 8250: use 32bit variable for rpm_tx_active The kbuild test robot wrote me: | make.cross ARCH=powerpc |>> ERROR: ".__xchg_called_with_bad_pointer" [drivers/tty/serial/8250/8250.ko] undefined! The generic implementation of xchg() on arm and x86 works for variables of size one bye (char). According to the report powerpc does not support xchg() for one byte sized variables and looking further it seems also to be the same case for sparc and tile (or for 10 out of 26 architectures which provide a custom implementation). For that reason I increase the size of the variable from one to four bytes to get it work on powerpc (and the others). Reported-By: kbuild test robot Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Greg Kroah-Hartman --- include/linux/serial_8250.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index c267412a3ef4..3df10d5f154b 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h @@ -84,7 +84,7 @@ struct uart_8250_port { unsigned char mcr_mask; /* mask of user bits */ unsigned char mcr_force; /* mask of forced bits */ unsigned char cur_iotype; /* Running I/O type */ - unsigned char rpm_tx_active; + unsigned int rpm_tx_active; /* * Some bits in registers are cleared on a read, so they must -- cgit v1.2.3