Commit b52c85a7 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'tty-4.17-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty

Pull tty/serial fixes from Greg KH:
 "Here are some tty and serial driver fixes for reported issues for
  4.17-rc3.

  Nothing major, but a number of small things:

   - device tree fixes/updates for serial ports

   - earlycon fixes

   - n_gsm fixes

   - tty core change reverted to help resolve syszkaller reports

   - other serial driver small fixes

  All of these have been in linux-next with no reported issues"

* tag 'tty-4.17-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty:
  tty: Use __GFP_NOFAIL for tty_ldisc_get()
  tty: serial: xuartps: Setup early console when uartclk is also passed
  tty: Don't call panic() at tty_ldisc_init()
  tty: Avoid possible error pointer dereference at tty_ldisc_restore().
  dt-bindings: mvebu-uart: DT fix s/interrupts-names/interrupt-names/
  tty: serial: qcom_geni_serial: Use signed variable to get IRQ
  earlycon: Use a pointer table to fix __earlycon_table stride
  serial: sh-sci: Document r8a77470 bindings
  dt-bindings: meson-uart: DT fix s/clocks-names/clock-names/
  serial: imx: fix cached UCR2 read on software reset
  serial: imx: warn user when using unsupported configuration
  serial: mvebu-uart: Fix local flags handling on termios update
  tty: n_gsm: Fix DLCI handling for ADM mode if debug & 2 is not set
  tty: n_gsm: Fix long delays with control frame timeouts in ADM mode
parents 79a17dd9 bcdd0ca8
...@@ -21,7 +21,7 @@ Required properties: ...@@ -21,7 +21,7 @@ Required properties:
- interrupts : identifier to the device interrupt - interrupts : identifier to the device interrupt
- clocks : a list of phandle + clock-specifier pairs, one for each - clocks : a list of phandle + clock-specifier pairs, one for each
entry in clock names. entry in clock names.
- clocks-names : - clock-names :
* "xtal" for external xtal clock identifier * "xtal" for external xtal clock identifier
* "pclk" for the bus core clock, either the clk81 clock or the gate clock * "pclk" for the bus core clock, either the clk81 clock or the gate clock
* "baud" for the source of the baudrate generator, can be either the xtal * "baud" for the source of the baudrate generator, can be either the xtal
......
...@@ -24,7 +24,7 @@ Required properties: ...@@ -24,7 +24,7 @@ Required properties:
- Must contain two elements for the extended variant of the IP - Must contain two elements for the extended variant of the IP
(marvell,armada-3700-uart-ext): "uart-tx" and "uart-rx", (marvell,armada-3700-uart-ext): "uart-tx" and "uart-rx",
respectively the UART TX interrupt and the UART RX interrupt. A respectively the UART TX interrupt and the UART RX interrupt. A
corresponding interrupts-names property must be defined. corresponding interrupt-names property must be defined.
- For backward compatibility reasons, a single element interrupts - For backward compatibility reasons, a single element interrupts
property is also supported for the standard variant of the IP, property is also supported for the standard variant of the IP,
containing only the UART sum interrupt. This form is deprecated containing only the UART sum interrupt. This form is deprecated
......
...@@ -17,6 +17,8 @@ Required properties: ...@@ -17,6 +17,8 @@ Required properties:
- "renesas,scifa-r8a7745" for R8A7745 (RZ/G1E) SCIFA compatible UART. - "renesas,scifa-r8a7745" for R8A7745 (RZ/G1E) SCIFA compatible UART.
- "renesas,scifb-r8a7745" for R8A7745 (RZ/G1E) SCIFB compatible UART. - "renesas,scifb-r8a7745" for R8A7745 (RZ/G1E) SCIFB compatible UART.
- "renesas,hscif-r8a7745" for R8A7745 (RZ/G1E) HSCIF compatible UART. - "renesas,hscif-r8a7745" for R8A7745 (RZ/G1E) HSCIF compatible UART.
- "renesas,scif-r8a77470" for R8A77470 (RZ/G1C) SCIF compatible UART.
- "renesas,hscif-r8a77470" for R8A77470 (RZ/G1C) HSCIF compatible UART.
- "renesas,scif-r8a7778" for R8A7778 (R-Car M1) SCIF compatible UART. - "renesas,scif-r8a7778" for R8A7778 (R-Car M1) SCIF compatible UART.
- "renesas,scif-r8a7779" for R8A7779 (R-Car H1) SCIF compatible UART. - "renesas,scif-r8a7779" for R8A7779 (R-Car H1) SCIF compatible UART.
- "renesas,scif-r8a7790" for R8A7790 (R-Car H2) SCIF compatible UART. - "renesas,scif-r8a7790" for R8A7790 (R-Car H2) SCIF compatible UART.
......
...@@ -942,7 +942,7 @@ int __init early_init_dt_scan_chosen_stdout(void) ...@@ -942,7 +942,7 @@ int __init early_init_dt_scan_chosen_stdout(void)
int offset; int offset;
const char *p, *q, *options = NULL; const char *p, *q, *options = NULL;
int l; int l;
const struct earlycon_id *match; const struct earlycon_id **p_match;
const void *fdt = initial_boot_params; const void *fdt = initial_boot_params;
offset = fdt_path_offset(fdt, "/chosen"); offset = fdt_path_offset(fdt, "/chosen");
...@@ -969,7 +969,10 @@ int __init early_init_dt_scan_chosen_stdout(void) ...@@ -969,7 +969,10 @@ int __init early_init_dt_scan_chosen_stdout(void)
return 0; return 0;
} }
for (match = __earlycon_table; match < __earlycon_table_end; match++) { for (p_match = __earlycon_table; p_match < __earlycon_table_end;
p_match++) {
const struct earlycon_id *match = *p_match;
if (!match->compatible[0]) if (!match->compatible[0])
continue; continue;
......
...@@ -121,6 +121,9 @@ struct gsm_dlci { ...@@ -121,6 +121,9 @@ struct gsm_dlci {
struct mutex mutex; struct mutex mutex;
/* Link layer */ /* Link layer */
int mode;
#define DLCI_MODE_ABM 0 /* Normal Asynchronous Balanced Mode */
#define DLCI_MODE_ADM 1 /* Asynchronous Disconnected Mode */
spinlock_t lock; /* Protects the internal state */ spinlock_t lock; /* Protects the internal state */
struct timer_list t1; /* Retransmit timer for SABM and UA */ struct timer_list t1; /* Retransmit timer for SABM and UA */
int retries; int retries;
...@@ -1364,7 +1367,13 @@ static struct gsm_control *gsm_control_send(struct gsm_mux *gsm, ...@@ -1364,7 +1367,13 @@ static struct gsm_control *gsm_control_send(struct gsm_mux *gsm,
ctrl->data = data; ctrl->data = data;
ctrl->len = clen; ctrl->len = clen;
gsm->pending_cmd = ctrl; gsm->pending_cmd = ctrl;
gsm->cretries = gsm->n2;
/* If DLCI0 is in ADM mode skip retries, it won't respond */
if (gsm->dlci[0]->mode == DLCI_MODE_ADM)
gsm->cretries = 1;
else
gsm->cretries = gsm->n2;
mod_timer(&gsm->t2_timer, jiffies + gsm->t2 * HZ / 100); mod_timer(&gsm->t2_timer, jiffies + gsm->t2 * HZ / 100);
gsm_control_transmit(gsm, ctrl); gsm_control_transmit(gsm, ctrl);
spin_unlock_irqrestore(&gsm->control_lock, flags); spin_unlock_irqrestore(&gsm->control_lock, flags);
...@@ -1472,6 +1481,7 @@ static void gsm_dlci_t1(struct timer_list *t) ...@@ -1472,6 +1481,7 @@ static void gsm_dlci_t1(struct timer_list *t)
if (debug & 8) if (debug & 8)
pr_info("DLCI %d opening in ADM mode.\n", pr_info("DLCI %d opening in ADM mode.\n",
dlci->addr); dlci->addr);
dlci->mode = DLCI_MODE_ADM;
gsm_dlci_open(dlci); gsm_dlci_open(dlci);
} else { } else {
gsm_dlci_close(dlci); gsm_dlci_close(dlci);
...@@ -2861,11 +2871,22 @@ static int gsmtty_modem_update(struct gsm_dlci *dlci, u8 brk) ...@@ -2861,11 +2871,22 @@ static int gsmtty_modem_update(struct gsm_dlci *dlci, u8 brk)
static int gsm_carrier_raised(struct tty_port *port) static int gsm_carrier_raised(struct tty_port *port)
{ {
struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port); struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port);
struct gsm_mux *gsm = dlci->gsm;
/* Not yet open so no carrier info */ /* Not yet open so no carrier info */
if (dlci->state != DLCI_OPEN) if (dlci->state != DLCI_OPEN)
return 0; return 0;
if (debug & 2) if (debug & 2)
return 1; return 1;
/*
* Basic mode with control channel in ADM mode may not respond
* to CMD_MSC at all and modem_rx is empty.
*/
if (gsm->encoding == 0 && gsm->dlci[0]->mode == DLCI_MODE_ADM &&
!dlci->modem_rx)
return 1;
return dlci->modem_rx & TIOCM_CD; return dlci->modem_rx & TIOCM_CD;
} }
......
...@@ -169,7 +169,7 @@ static int __init register_earlycon(char *buf, const struct earlycon_id *match) ...@@ -169,7 +169,7 @@ static int __init register_earlycon(char *buf, const struct earlycon_id *match)
*/ */
int __init setup_earlycon(char *buf) int __init setup_earlycon(char *buf)
{ {
const struct earlycon_id *match; const struct earlycon_id **p_match;
if (!buf || !buf[0]) if (!buf || !buf[0])
return -EINVAL; return -EINVAL;
...@@ -177,7 +177,9 @@ int __init setup_earlycon(char *buf) ...@@ -177,7 +177,9 @@ int __init setup_earlycon(char *buf)
if (early_con.flags & CON_ENABLED) if (early_con.flags & CON_ENABLED)
return -EALREADY; return -EALREADY;
for (match = __earlycon_table; match < __earlycon_table_end; match++) { for (p_match = __earlycon_table; p_match < __earlycon_table_end;
p_match++) {
const struct earlycon_id *match = *p_match;
size_t len = strlen(match->name); size_t len = strlen(match->name);
if (strncmp(buf, match->name, len)) if (strncmp(buf, match->name, len))
......
...@@ -316,7 +316,7 @@ static u32 imx_uart_readl(struct imx_port *sport, u32 offset) ...@@ -316,7 +316,7 @@ static u32 imx_uart_readl(struct imx_port *sport, u32 offset)
* differ from the value that was last written. As it only * differ from the value that was last written. As it only
* clears after being set, reread conditionally. * clears after being set, reread conditionally.
*/ */
if (sport->ucr2 & UCR2_SRST) if (!(sport->ucr2 & UCR2_SRST))
sport->ucr2 = readl(sport->port.membase + offset); sport->ucr2 = readl(sport->port.membase + offset);
return sport->ucr2; return sport->ucr2;
break; break;
...@@ -1833,6 +1833,11 @@ static int imx_uart_rs485_config(struct uart_port *port, ...@@ -1833,6 +1833,11 @@ static int imx_uart_rs485_config(struct uart_port *port,
rs485conf->flags &= ~SER_RS485_ENABLED; rs485conf->flags &= ~SER_RS485_ENABLED;
if (rs485conf->flags & SER_RS485_ENABLED) { if (rs485conf->flags & SER_RS485_ENABLED) {
/* Enable receiver if low-active RTS signal is requested */
if (sport->have_rtscts && !sport->have_rtsgpio &&
!(rs485conf->flags & SER_RS485_RTS_ON_SEND))
rs485conf->flags |= SER_RS485_RX_DURING_TX;
/* disable transmitter */ /* disable transmitter */
ucr2 = imx_uart_readl(sport, UCR2); ucr2 = imx_uart_readl(sport, UCR2);
if (rs485conf->flags & SER_RS485_RTS_AFTER_SEND) if (rs485conf->flags & SER_RS485_RTS_AFTER_SEND)
...@@ -2265,6 +2270,18 @@ static int imx_uart_probe(struct platform_device *pdev) ...@@ -2265,6 +2270,18 @@ static int imx_uart_probe(struct platform_device *pdev)
(!sport->have_rtscts && !sport->have_rtsgpio)) (!sport->have_rtscts && !sport->have_rtsgpio))
dev_err(&pdev->dev, "no RTS control, disabling rs485\n"); dev_err(&pdev->dev, "no RTS control, disabling rs485\n");
/*
* If using the i.MX UART RTS/CTS control then the RTS (CTS_B)
* signal cannot be set low during transmission in case the
* receiver is off (limitation of the i.MX UART IP).
*/
if (sport->port.rs485.flags & SER_RS485_ENABLED &&
sport->have_rtscts && !sport->have_rtsgpio &&
(!(sport->port.rs485.flags & SER_RS485_RTS_ON_SEND) &&
!(sport->port.rs485.flags & SER_RS485_RX_DURING_TX)))
dev_err(&pdev->dev,
"low-active RTS not possible when receiver is off, enabling receiver\n");
imx_uart_rs485_config(&sport->port, &sport->port.rs485); imx_uart_rs485_config(&sport->port, &sport->port.rs485);
/* Disable interrupts before requesting them */ /* Disable interrupts before requesting them */
......
...@@ -495,7 +495,6 @@ static void mvebu_uart_set_termios(struct uart_port *port, ...@@ -495,7 +495,6 @@ static void mvebu_uart_set_termios(struct uart_port *port,
termios->c_iflag |= old->c_iflag & ~(INPCK | IGNPAR); termios->c_iflag |= old->c_iflag & ~(INPCK | IGNPAR);
termios->c_cflag &= CREAD | CBAUD; termios->c_cflag &= CREAD | CBAUD;
termios->c_cflag |= old->c_cflag & ~(CREAD | CBAUD); termios->c_cflag |= old->c_cflag & ~(CREAD | CBAUD);
termios->c_lflag = old->c_lflag;
} }
spin_unlock_irqrestore(&port->lock, flags); spin_unlock_irqrestore(&port->lock, flags);
......
...@@ -1022,6 +1022,7 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) ...@@ -1022,6 +1022,7 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
struct qcom_geni_serial_port *port; struct qcom_geni_serial_port *port;
struct uart_port *uport; struct uart_port *uport;
struct resource *res; struct resource *res;
int irq;
if (pdev->dev.of_node) if (pdev->dev.of_node)
line = of_alias_get_id(pdev->dev.of_node, "serial"); line = of_alias_get_id(pdev->dev.of_node, "serial");
...@@ -1061,11 +1062,12 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) ...@@ -1061,11 +1062,12 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
port->rx_fifo_depth = DEF_FIFO_DEPTH_WORDS; port->rx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
port->tx_fifo_width = DEF_FIFO_WIDTH_BITS; port->tx_fifo_width = DEF_FIFO_WIDTH_BITS;
uport->irq = platform_get_irq(pdev, 0); irq = platform_get_irq(pdev, 0);
if (uport->irq < 0) { if (irq < 0) {
dev_err(&pdev->dev, "Failed to get IRQ %d\n", uport->irq); dev_err(&pdev->dev, "Failed to get IRQ %d\n", irq);
return uport->irq; return irq;
} }
uport->irq = irq;
uport->private_data = &qcom_geni_console_driver; uport->private_data = &qcom_geni_console_driver;
platform_set_drvdata(pdev, port); platform_set_drvdata(pdev, port);
......
...@@ -1181,7 +1181,7 @@ static int __init cdns_early_console_setup(struct earlycon_device *device, ...@@ -1181,7 +1181,7 @@ static int __init cdns_early_console_setup(struct earlycon_device *device,
/* only set baud if specified on command line - otherwise /* only set baud if specified on command line - otherwise
* assume it has been initialized by a boot loader. * assume it has been initialized by a boot loader.
*/ */
if (device->baud) { if (port->uartclk && device->baud) {
u32 cd = 0, bdiv = 0; u32 cd = 0, bdiv = 0;
u32 mr; u32 mr;
int div8; int div8;
......
...@@ -2816,7 +2816,10 @@ struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx) ...@@ -2816,7 +2816,10 @@ struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx)
kref_init(&tty->kref); kref_init(&tty->kref);
tty->magic = TTY_MAGIC; tty->magic = TTY_MAGIC;
tty_ldisc_init(tty); if (tty_ldisc_init(tty)) {
kfree(tty);
return NULL;
}
tty->session = NULL; tty->session = NULL;
tty->pgrp = NULL; tty->pgrp = NULL;
mutex_init(&tty->legacy_mutex); mutex_init(&tty->legacy_mutex);
......
...@@ -176,12 +176,11 @@ static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc) ...@@ -176,12 +176,11 @@ static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc)
return ERR_CAST(ldops); return ERR_CAST(ldops);
} }
ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL); /*
if (ld == NULL) { * There is no way to handle allocation failure of only 16 bytes.
put_ldops(ldops); * Let's simplify error handling and save more memory.
return ERR_PTR(-ENOMEM); */
} ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL | __GFP_NOFAIL);
ld->ops = ldops; ld->ops = ldops;
ld->tty = tty; ld->tty = tty;
...@@ -527,19 +526,16 @@ static int tty_ldisc_failto(struct tty_struct *tty, int ld) ...@@ -527,19 +526,16 @@ static int tty_ldisc_failto(struct tty_struct *tty, int ld)
static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
{ {
/* There is an outstanding reference here so this is safe */ /* There is an outstanding reference here so this is safe */
old = tty_ldisc_get(tty, old->ops->num); if (tty_ldisc_failto(tty, old->ops->num) < 0) {
WARN_ON(IS_ERR(old)); const char *name = tty_name(tty);
tty->ldisc = old;
tty_set_termios_ldisc(tty, old->ops->num); pr_warn("Falling back ldisc for %s.\n", name);
if (tty_ldisc_open(tty, old) < 0) {
tty_ldisc_put(old);
/* The traditional behaviour is to fall back to N_TTY, we /* The traditional behaviour is to fall back to N_TTY, we
want to avoid falling back to N_NULL unless we have no want to avoid falling back to N_NULL unless we have no
choice to avoid the risk of breaking anything */ choice to avoid the risk of breaking anything */
if (tty_ldisc_failto(tty, N_TTY) < 0 && if (tty_ldisc_failto(tty, N_TTY) < 0 &&
tty_ldisc_failto(tty, N_NULL) < 0) tty_ldisc_failto(tty, N_NULL) < 0)
panic("Couldn't open N_NULL ldisc for %s.", panic("Couldn't open N_NULL ldisc for %s.", name);
tty_name(tty));
} }
} }
...@@ -824,12 +820,13 @@ EXPORT_SYMBOL_GPL(tty_ldisc_release); ...@@ -824,12 +820,13 @@ EXPORT_SYMBOL_GPL(tty_ldisc_release);
* the tty structure is not completely set up when this call is made. * the tty structure is not completely set up when this call is made.
*/ */
void tty_ldisc_init(struct tty_struct *tty) int tty_ldisc_init(struct tty_struct *tty)
{ {
struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY); struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY);
if (IS_ERR(ld)) if (IS_ERR(ld))
panic("n_tty: init_tty"); return PTR_ERR(ld);
tty->ldisc = ld; tty->ldisc = ld;
return 0;
} }
/** /**
......
...@@ -188,7 +188,7 @@ ...@@ -188,7 +188,7 @@
#endif #endif
#ifdef CONFIG_SERIAL_EARLYCON #ifdef CONFIG_SERIAL_EARLYCON
#define EARLYCON_TABLE() STRUCT_ALIGN(); \ #define EARLYCON_TABLE() . = ALIGN(8); \
VMLINUX_SYMBOL(__earlycon_table) = .; \ VMLINUX_SYMBOL(__earlycon_table) = .; \
KEEP(*(__earlycon_table)) \ KEEP(*(__earlycon_table)) \
VMLINUX_SYMBOL(__earlycon_table_end) = .; VMLINUX_SYMBOL(__earlycon_table_end) = .;
......
...@@ -351,10 +351,10 @@ struct earlycon_id { ...@@ -351,10 +351,10 @@ struct earlycon_id {
char name[16]; char name[16];
char compatible[128]; char compatible[128];
int (*setup)(struct earlycon_device *, const char *options); int (*setup)(struct earlycon_device *, const char *options);
} __aligned(32); };
extern const struct earlycon_id __earlycon_table[]; extern const struct earlycon_id *__earlycon_table[];
extern const struct earlycon_id __earlycon_table_end[]; extern const struct earlycon_id *__earlycon_table_end[];
#if defined(CONFIG_SERIAL_EARLYCON) && !defined(MODULE) #if defined(CONFIG_SERIAL_EARLYCON) && !defined(MODULE)
#define EARLYCON_USED_OR_UNUSED __used #define EARLYCON_USED_OR_UNUSED __used
...@@ -362,12 +362,19 @@ extern const struct earlycon_id __earlycon_table_end[]; ...@@ -362,12 +362,19 @@ extern const struct earlycon_id __earlycon_table_end[];
#define EARLYCON_USED_OR_UNUSED __maybe_unused #define EARLYCON_USED_OR_UNUSED __maybe_unused
#endif #endif
#define OF_EARLYCON_DECLARE(_name, compat, fn) \ #define _OF_EARLYCON_DECLARE(_name, compat, fn, unique_id) \
static const struct earlycon_id __UNIQUE_ID(__earlycon_##_name) \ static const struct earlycon_id unique_id \
EARLYCON_USED_OR_UNUSED __section(__earlycon_table) \ EARLYCON_USED_OR_UNUSED __initconst \
= { .name = __stringify(_name), \ = { .name = __stringify(_name), \
.compatible = compat, \ .compatible = compat, \
.setup = fn } .setup = fn }; \
static const struct earlycon_id EARLYCON_USED_OR_UNUSED \
__section(__earlycon_table) \
* const __PASTE(__p, unique_id) = &unique_id
#define OF_EARLYCON_DECLARE(_name, compat, fn) \
_OF_EARLYCON_DECLARE(_name, compat, fn, \
__UNIQUE_ID(__earlycon_##_name))
#define EARLYCON_DECLARE(_name, fn) OF_EARLYCON_DECLARE(_name, "", fn) #define EARLYCON_DECLARE(_name, fn) OF_EARLYCON_DECLARE(_name, "", fn)
......
...@@ -701,7 +701,7 @@ extern int tty_unregister_ldisc(int disc); ...@@ -701,7 +701,7 @@ extern int tty_unregister_ldisc(int disc);
extern int tty_set_ldisc(struct tty_struct *tty, int disc); extern int tty_set_ldisc(struct tty_struct *tty, int disc);
extern int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty); extern int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty);
extern void tty_ldisc_release(struct tty_struct *tty); extern void tty_ldisc_release(struct tty_struct *tty);
extern void tty_ldisc_init(struct tty_struct *tty); extern int __must_check tty_ldisc_init(struct tty_struct *tty);
extern void tty_ldisc_deinit(struct tty_struct *tty); extern void tty_ldisc_deinit(struct tty_struct *tty);
extern int tty_ldisc_receive_buf(struct tty_ldisc *ld, const unsigned char *p, extern int tty_ldisc_receive_buf(struct tty_ldisc *ld, const unsigned char *p,
char *f, int count); char *f, int count);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment