From e52ed2dd8287f7438c80e8e5e8c8bf14400cc1a2 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 11 Dec 2024 08:49:31 +0100 Subject: [PATCH 01/16] tty: serial_core: use more guard(mutex) Simplify 4 more functions using guard(mutex): uart_get_info(), console_store(), serial_core_add_one_port(), and serial_core_register_port(). Especially console_store() is now much less convoluted. In the others, we save some goto-s and even local variables are dropped in some. Signed-off-by: Jiri Slaby (SUSE) Link: https://lore.kernel.org/r/20241211074933.92973-2-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 83 ++++++++++++-------------------- 1 file changed, 31 insertions(+), 52 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 74fa02b23772..f595f2336fc0 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -790,7 +790,6 @@ static int uart_get_info(struct tty_port *port, struct serial_struct *retinfo) { struct uart_state *state = container_of(port, struct uart_state, port); struct uart_port *uport; - int ret = -ENODEV; /* Initialize structure in case we error out later to prevent any stack info leakage. */ *retinfo = (struct serial_struct){}; @@ -799,10 +798,10 @@ static int uart_get_info(struct tty_port *port, struct serial_struct *retinfo) * Ensure the state we copy is consistent and no hardware changes * occur as we go */ - mutex_lock(&port->mutex); + guard(mutex)(&port->mutex); uport = uart_port_check(state); if (!uport) - goto out; + return -ENODEV; retinfo->type = uport->type; retinfo->line = uport->line; @@ -823,10 +822,7 @@ static int uart_get_info(struct tty_port *port, struct serial_struct *retinfo) retinfo->iomem_reg_shift = uport->regshift; retinfo->iomem_base = (void *)(unsigned long)uport->mapbase; - ret = 0; -out: - mutex_unlock(&port->mutex); - return ret; + return 0; } static int uart_get_info_user(struct tty_struct *tty, @@ -3061,26 +3057,25 @@ static ssize_t console_store(struct device *dev, if (ret) return ret; - mutex_lock(&port->mutex); + guard(mutex)(&port->mutex); uport = uart_port_check(state); - if (uport) { - oldconsole = uart_console_registered(uport); - if (oldconsole && !newconsole) { - ret = unregister_console(uport->cons); - } else if (!oldconsole && newconsole) { - if (uart_console(uport)) { - uport->console_reinit = 1; - register_console(uport->cons); - } else { - ret = -ENOENT; - } - } - } else { - ret = -ENXIO; + if (!uport) + return -ENXIO; + + oldconsole = uart_console_registered(uport); + if (oldconsole && !newconsole) { + ret = unregister_console(uport->cons); + if (ret < 0) + return ret; + } else if (!oldconsole && newconsole) { + if (!uart_console(uport)) + return -ENOENT; + + uport->console_reinit = 1; + register_console(uport->cons); } - mutex_unlock(&port->mutex); - return ret < 0 ? ret : count; + return count; } static DEVICE_ATTR_RO(uartclk); @@ -3136,7 +3131,6 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u { struct uart_state *state; struct tty_port *port; - int ret = 0; struct device *tty_dev; int num_groups; @@ -3146,11 +3140,9 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u state = drv->state + uport->line; port = &state->port; - mutex_lock(&port->mutex); - if (state->uart_port) { - ret = -EINVAL; - goto out; - } + guard(mutex)(&port->mutex); + if (state->uart_port) + return -EINVAL; /* Link the port to the driver state table and vice versa */ atomic_set(&state->refcount, 1); @@ -3170,10 +3162,8 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u uport->minor = drv->tty_driver->minor_start + uport->line; uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name, drv->tty_driver->name_base + uport->line); - if (!uport->name) { - ret = -ENOMEM; - goto out; - } + if (!uport->name) + return -ENOMEM; if (uport->cons && uport->dev) of_console_check(uport->dev->of_node, uport->cons->name, uport->line); @@ -3189,10 +3179,9 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups), GFP_KERNEL); - if (!uport->tty_groups) { - ret = -ENOMEM; - goto out; - } + if (!uport->tty_groups) + return -ENOMEM; + uport->tty_groups[0] = &tty_dev_attr_group; if (uport->attr_group) uport->tty_groups[1] = uport->attr_group; @@ -3215,10 +3204,7 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u uport->line); } - out: - mutex_unlock(&port->mutex); - - return ret; + return 0; } /** @@ -3384,7 +3370,7 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) struct serial_ctrl_device *ctrl_dev, *new_ctrl_dev = NULL; int ret; - mutex_lock(&port_mutex); + guard(mutex)(&port_mutex); /* * Prevent serial_port_runtime_resume() from trying to use the port @@ -3396,10 +3382,8 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) ctrl_dev = serial_core_ctrl_find(drv, port->dev, port->ctrl_id); if (!ctrl_dev) { new_ctrl_dev = serial_core_ctrl_device_add(port); - if (IS_ERR(new_ctrl_dev)) { - ret = PTR_ERR(new_ctrl_dev); - goto err_unlock; - } + if (IS_ERR(new_ctrl_dev)) + return PTR_ERR(new_ctrl_dev); ctrl_dev = new_ctrl_dev; } @@ -3420,8 +3404,6 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) if (ret) goto err_unregister_port_dev; - mutex_unlock(&port_mutex); - return 0; err_unregister_port_dev: @@ -3430,9 +3412,6 @@ err_unregister_port_dev: err_unregister_ctrl_dev: serial_base_ctrl_device_remove(new_ctrl_dev); -err_unlock: - mutex_unlock(&port_mutex); - return ret; } -- 2.50.1 From 4d0e56d571b8675e5a4863f394884c7db4387bcb Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 11 Dec 2024 08:49:32 +0100 Subject: [PATCH 02/16] tty: serial: get rid of exit label from uart_set_info() The label is unneeded since 7ba2e769825f (tty: Split the serial_core helpers for setserial into two). Until then, there was a lock held in uart_set_info(). Now it is not, so we can remove the label. This involves reordering the code, so that it is clear what values are returned, where and why. Until now, it was really hard to follow. The "change_port" part of the function is extracted into a separate function in the next patch. This patch makes the transition there easier too. Signed-off-by: Jiri Slaby (SUSE) Link: https://lore.kernel.org/r/20241211074933.92973-3-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 116 ++++++++++++++----------------- 1 file changed, 51 insertions(+), 65 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index f595f2336fc0..ce3cf95fc910 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -843,7 +843,7 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port, unsigned int change_irq, change_port, closing_wait; unsigned int old_custom_divisor, close_delay; upf_t old_flags, new_flags; - int retval = 0; + int retval; if (!uport) return -EIO; @@ -882,13 +882,10 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port, if (!(uport->flags & UPF_FIXED_PORT)) { unsigned int uartclk = new_info->baud_base * 16; /* check needs to be done here before other settings made */ - if (uartclk == 0) { - retval = -EINVAL; - goto exit; - } + if (uartclk == 0) + return -EINVAL; } if (!capable(CAP_SYS_ADMIN)) { - retval = -EPERM; if (change_irq || change_port || (new_info->baud_base != uport->uartclk / 16) || (close_delay != port->close_delay) || @@ -896,7 +893,7 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port, (new_info->xmit_fifo_size && new_info->xmit_fifo_size != uport->fifosize) || (((new_flags ^ old_flags) & ~UPF_USR_MASK) != 0)) - goto exit; + return -EPERM; uport->flags = ((uport->flags & ~UPF_USR_MASK) | (new_flags & UPF_USR_MASK)); uport->custom_divisor = new_info->custom_divisor; @@ -906,30 +903,24 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port, if (change_irq || change_port) { retval = security_locked_down(LOCKDOWN_TIOCSSERIAL); if (retval) - goto exit; + return retval; } - /* - * Ask the low level driver to verify the settings. - */ - if (uport->ops->verify_port) + /* Ask the low level driver to verify the settings. */ + if (uport->ops->verify_port) { retval = uport->ops->verify_port(uport, new_info); + if (retval) + return retval; + } if ((new_info->irq >= irq_get_nr_irqs()) || (new_info->irq < 0) || (new_info->baud_base < 9600)) - retval = -EINVAL; - - if (retval) - goto exit; + return -EINVAL; if (change_port || change_irq) { - retval = -EBUSY; - - /* - * Make sure that we are the sole user of this port. - */ + /* Make sure that we are the sole user of this port. */ if (tty_port_users(port) > 1) - goto exit; + return -EBUSY; /* * We need to shutdown the serial port at the old @@ -967,40 +958,33 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port, */ if (uport->type != PORT_UNKNOWN && uport->ops->request_port) { retval = uport->ops->request_port(uport); - } else { - /* Always success - Jean II */ - retval = 0; - } - - /* - * If we fail to request resources for the - * new port, try to restore the old settings. - */ - if (retval) { - uport->iobase = old_iobase; - uport->type = old_type; - uport->hub6 = old_hub6; - uport->iotype = old_iotype; - uport->regshift = old_shift; - uport->mapbase = old_mapbase; - - if (old_type != PORT_UNKNOWN) { - retval = uport->ops->request_port(uport); - /* - * If we failed to restore the old settings, - * we fail like this. - */ - if (retval) - uport->type = PORT_UNKNOWN; - - /* - * We failed anyway. - */ - retval = -EBUSY; + /* + * If we fail to request resources for the + * new port, try to restore the old settings. + */ + if (retval) { + uport->iobase = old_iobase; + uport->type = old_type; + uport->hub6 = old_hub6; + uport->iotype = old_iotype; + uport->regshift = old_shift; + uport->mapbase = old_mapbase; + + if (old_type != PORT_UNKNOWN) { + retval = uport->ops->request_port(uport); + /* + * If we failed to restore the old + * settings, we fail like this. + */ + if (retval) + uport->type = PORT_UNKNOWN; + + /* We failed anyway. */ + return -EBUSY; + } + + return retval; } - - /* Added to return the correct error -Ram Gupta */ - goto exit; } } @@ -1017,9 +1001,9 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port, uport->fifosize = new_info->xmit_fifo_size; check_and_exit: - retval = 0; if (uport->type == PORT_UNKNOWN) - goto exit; + return 0; + if (tty_port_initialized(port)) { if (((old_flags ^ uport->flags) & UPF_SPD_MASK) || old_custom_divisor != uport->custom_divisor) { @@ -1035,15 +1019,17 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port, } uart_change_line_settings(tty, state, NULL); } - } else { - retval = uart_startup(tty, state, true); - if (retval == 0) - tty_port_set_initialized(port, true); - if (retval > 0) - retval = 0; + + return 0; } - exit: - return retval; + + retval = uart_startup(tty, state, true); + if (retval < 0) + return retval; + if (retval == 0) + tty_port_set_initialized(port, true); + + return 0; } static int uart_set_info_user(struct tty_struct *tty, struct serial_struct *ss) -- 2.50.1 From d2740f7d878932a4b4aaf69d751baf3825c1b287 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 11 Dec 2024 08:49:33 +0100 Subject: [PATCH 03/16] tty: serial: extract uart_change_port() from uart_set_info() This "change_port" part of uart_set_info() is for no good reason inlined there. It makes the function rather hard to read. Therefore, extract it to a separate function. This allows for flattening the ifs (with short path "return"s) and avoiding two levels of indentation. Both making the code really flat and comprehesible. Signed-off-by: Jiri Slaby (SUSE) Link: https://lore.kernel.org/r/20241211074933.92973-4-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 114 ++++++++++++++++--------------- 1 file changed, 58 insertions(+), 56 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index ce3cf95fc910..c472594c3a9f 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -834,6 +834,61 @@ static int uart_get_info_user(struct tty_struct *tty, return uart_get_info(port, ss) < 0 ? -EIO : 0; } +static int uart_change_port(struct uart_port *uport, + const struct serial_struct *new_info, + unsigned long new_port) +{ + unsigned long old_iobase, old_mapbase; + unsigned int old_type, old_iotype, old_hub6, old_shift; + int retval; + + old_iobase = uport->iobase; + old_mapbase = uport->mapbase; + old_type = uport->type; + old_hub6 = uport->hub6; + old_iotype = uport->iotype; + old_shift = uport->regshift; + + if (old_type != PORT_UNKNOWN && uport->ops->release_port) + uport->ops->release_port(uport); + + uport->iobase = new_port; + uport->type = new_info->type; + uport->hub6 = new_info->hub6; + uport->iotype = new_info->io_type; + uport->regshift = new_info->iomem_reg_shift; + uport->mapbase = (unsigned long)new_info->iomem_base; + + if (uport->type == PORT_UNKNOWN || !uport->ops->request_port) + return 0; + + retval = uport->ops->request_port(uport); + if (retval == 0) + return 0; /* succeeded => done */ + + /* + * If we fail to request resources for the new port, try to restore the + * old settings. + */ + uport->iobase = old_iobase; + uport->type = old_type; + uport->hub6 = old_hub6; + uport->iotype = old_iotype; + uport->regshift = old_shift; + uport->mapbase = old_mapbase; + + if (old_type == PORT_UNKNOWN) + return retval; + + retval = uport->ops->request_port(uport); + /* If we failed to restore the old settings, we fail like this. */ + if (retval) + uport->type = PORT_UNKNOWN; + + /* We failed anyway. */ + return -EBUSY; +} + static int uart_set_info(struct tty_struct *tty, struct tty_port *port, struct uart_state *state, struct serial_struct *new_info) @@ -930,62 +985,9 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port, } if (change_port) { - unsigned long old_iobase, old_mapbase; - unsigned int old_type, old_iotype, old_hub6, old_shift; - - old_iobase = uport->iobase; - old_mapbase = uport->mapbase; - old_type = uport->type; - old_hub6 = uport->hub6; - old_iotype = uport->iotype; - old_shift = uport->regshift; - - /* - * Free and release old regions - */ - if (old_type != PORT_UNKNOWN && uport->ops->release_port) - uport->ops->release_port(uport); - - uport->iobase = new_port; - uport->type = new_info->type; - uport->hub6 = new_info->hub6; - uport->iotype = new_info->io_type; - uport->regshift = new_info->iomem_reg_shift; - uport->mapbase = (unsigned long)new_info->iomem_base; - - /* - * Claim and map the new regions - */ - if (uport->type != PORT_UNKNOWN && uport->ops->request_port) { - retval = uport->ops->request_port(uport); - /* - * If we fail to request resources for the - * new port, try to restore the old settings. - */ - if (retval) { - uport->iobase = old_iobase; - uport->type = old_type; - uport->hub6 = old_hub6; - uport->iotype = old_iotype; - uport->regshift = old_shift; - uport->mapbase = old_mapbase; - - if (old_type != PORT_UNKNOWN) { - retval = uport->ops->request_port(uport); - /* - * If we failed to restore the old - * settings, we fail like this. - */ - if (retval) - uport->type = PORT_UNKNOWN; - - /* We failed anyway. */ - return -EBUSY; - } - - return retval; - } - } + retval = uart_change_port(uport, new_info, new_port); + if (retval) + return retval; } if (change_irq) -- 2.50.1 From aea2654cce40a6e34e91f3be2a31cd040fdea822 Mon Sep 17 00:00:00 2001 From: "Ricardo B. Marliere" Date: Fri, 13 Dec 2024 16:35:05 -0300 Subject: [PATCH 04/16] tty: Make sysctl table const MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Since commit 7abc9b53bd51 ("sysctl: allow registration of const struct ctl_table"), the sysctl registration API allows for struct ctl_table to be in read-only memory. Move tty_table to be declared at build time, instead of having to be dynamically allocated at boot time. Cc: Thomas Weißschuh Suggested-by: Thomas Weißschuh Signed-off-by: Ricardo B. Marliere Link: https://lore.kernel.org/r/20241213-sysctl_const-tty-v1-1-2e2bcec77f85@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index dcb1769c3625..0e84677712b4 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -3618,7 +3618,7 @@ void console_sysfs_notify(void) sysfs_notify(&consdev->kobj, NULL, "active"); } -static struct ctl_table tty_table[] = { +static const struct ctl_table tty_table[] = { { .procname = "legacy_tiocsti", .data = &tty_legacy_tiocsti, -- 2.50.1 From c83508da5620ef89232cb614fb9e02dfdfef2b8f Mon Sep 17 00:00:00 2001 From: Priya Bala Govindasamy Date: Fri, 13 Dec 2024 17:58:58 -0800 Subject: [PATCH 05/16] bpf: Avoid deadlock caused by nested kprobe and fentry bpf programs BPF program types like kprobe and fentry can cause deadlocks in certain situations. If a function takes a lock and one of these bpf programs is hooked to some point in the function's critical section, and if the bpf program tries to call the same function and take the same lock it will lead to deadlock. These situations have been reported in the following bug reports. In percpu_freelist - Link: https://lore.kernel.org/bpf/CAADnVQLAHwsa+2C6j9+UC6ScrDaN9Fjqv1WjB1pP9AzJLhKuLQ@mail.gmail.com/T/ Link: https://lore.kernel.org/bpf/CAPPBnEYm+9zduStsZaDnq93q1jPLqO-PiKX9jy0MuL8LCXmCrQ@mail.gmail.com/T/ In bpf_lru_list - Link: https://lore.kernel.org/bpf/CAPPBnEajj+DMfiR_WRWU5=6A7KKULdB5Rob_NJopFLWF+i9gCA@mail.gmail.com/T/ Link: https://lore.kernel.org/bpf/CAPPBnEZQDVN6VqnQXvVqGoB+ukOtHGZ9b9U0OLJJYvRoSsMY_g@mail.gmail.com/T/ Link: https://lore.kernel.org/bpf/CAPPBnEaCB1rFAYU7Wf8UxqcqOWKmRPU1Nuzk3_oLk6qXR7LBOA@mail.gmail.com/T/ Similar bugs have been reported by syzbot. In queue_stack_maps - Link: https://lore.kernel.org/lkml/0000000000004c3fc90615f37756@google.com/ Link: https://lore.kernel.org/all/20240418230932.2689-1-hdanton@sina.com/T/ In lpm_trie - Link: https://lore.kernel.org/linux-kernel/00000000000035168a061a47fa38@google.com/T/ In ringbuf - Link: https://lore.kernel.org/bpf/20240313121345.2292-1-hdanton@sina.com/T/ Prevent kprobe and fentry bpf programs from attaching to these critical sections by removing CC_FLAGS_FTRACE for percpu_freelist.o, bpf_lru_list.o, queue_stack_maps.o, lpm_trie.o, ringbuf.o files. The bugs reported by syzbot are due to tracepoint bpf programs being called in the critical sections. This patch does not aim to fix deadlocks caused by tracepoint programs. However, it does prevent deadlocks from occurring in similar situations due to kprobe and fentry programs. Signed-off-by: Priya Bala Govindasamy Link: https://lore.kernel.org/r/CAPPBnEZpjGnsuA26Mf9kYibSaGLm=oF6=12L21X1GEQdqjLnzQ@mail.gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile index 9762bdddf1de..410028633621 100644 --- a/kernel/bpf/Makefile +++ b/kernel/bpf/Makefile @@ -53,3 +53,9 @@ obj-$(CONFIG_BPF_SYSCALL) += relo_core.o obj-$(CONFIG_BPF_SYSCALL) += btf_iter.o obj-$(CONFIG_BPF_SYSCALL) += btf_relocate.o obj-$(CONFIG_BPF_SYSCALL) += kmem_cache_iter.o + +CFLAGS_REMOVE_percpu_freelist.o = $(CC_FLAGS_FTRACE) +CFLAGS_REMOVE_bpf_lru_list.o = $(CC_FLAGS_FTRACE) +CFLAGS_REMOVE_queue_stack_maps.o = $(CC_FLAGS_FTRACE) +CFLAGS_REMOVE_lpm_trie.o = $(CC_FLAGS_FTRACE) +CFLAGS_REMOVE_ringbuf.o = $(CC_FLAGS_FTRACE) -- 2.50.1 From 78d4f34e2115b517bcbfe7ec0d018bbbb6f9b0b8 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 15 Dec 2024 15:58:23 -0800 Subject: [PATCH 06/16] Linux 6.13-rc3 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 64c594bd7ad0..e5b8a8832c0c 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 6 PATCHLEVEL = 13 SUBLEVEL = 0 -EXTRAVERSION = -rc2 +EXTRAVERSION = -rc3 NAME = Baby Opossum Posse # *DOCUMENTATION* -- 2.50.1 From e95cb63e57381f00d9274533ea7fd0ac3bf4e5b0 Mon Sep 17 00:00:00 2001 From: Rengarajan S Date: Wed, 18 Dec 2024 15:10:17 +0530 Subject: [PATCH 07/16] 8250: microchip: pci1xxxx: Add workaround for RTS bit toggle In the B0 revision, the RTS pin remains high due to incorrect hardware mapping. To address this issue, enable auto-direction control with the RTS bit in ADCL_CFG_REG. This configuration ensures that the RTS pin goes low when the terminal is opened and high when the terminal is closed. Additionally, we reset the step counter for Rx and Tx engines by writing into FRAC_DIV_CFG_REG. Signed-off-by: Rengarajan S Link: https://lore.kernel.org/r/20241218094017.18290-1-rengarajan.s@microchip.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_pci1xxxx.c | 60 ++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_pci1xxxx.c b/drivers/tty/serial/8250/8250_pci1xxxx.c index 838f181f929b..e9c51d4e447d 100644 --- a/drivers/tty/serial/8250/8250_pci1xxxx.c +++ b/drivers/tty/serial/8250/8250_pci1xxxx.c @@ -78,6 +78,12 @@ #define UART_TX_BYTE_FIFO 0x00 #define UART_FIFO_CTL 0x02 +#define UART_MODEM_CTL_REG 0x04 +#define UART_MODEM_CTL_RTS_SET BIT(1) + +#define UART_LINE_STAT_REG 0x05 +#define UART_LINE_XMIT_CHECK_MASK GENMASK(6, 5) + #define UART_ACTV_REG 0x11 #define UART_BLOCK_SET_ACTIVE BIT(0) @@ -94,6 +100,7 @@ #define UART_BIT_SAMPLE_CNT_16 16 #define BAUD_CLOCK_DIV_INT_MSK GENMASK(31, 8) #define ADCL_CFG_RTS_DELAY_MASK GENMASK(11, 8) +#define FRAC_DIV_TX_END_POINT_MASK GENMASK(23, 20) #define UART_WAKE_REG 0x8C #define UART_WAKE_MASK_REG 0x90 @@ -134,6 +141,11 @@ #define UART_BST_STAT_LSR_FRAME_ERR 0x8000000 #define UART_BST_STAT_LSR_THRE 0x20000000 +#define GET_MODEM_CTL_RTS_STATUS(reg) ((reg) & UART_MODEM_CTL_RTS_SET) +#define GET_RTS_PIN_STATUS(val) (((val) & TIOCM_RTS) >> 1) +#define RTS_TOGGLE_STATUS_MASK(val, reg) (GET_MODEM_CTL_RTS_STATUS(reg) \ + != GET_RTS_PIN_STATUS(val)) + struct pci1xxxx_8250 { unsigned int nr; u8 dev_rev; @@ -254,6 +266,47 @@ static void pci1xxxx_set_divisor(struct uart_port *port, unsigned int baud, port->membase + UART_BAUD_CLK_DIVISOR_REG); } +static void pci1xxxx_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + u32 fract_div_cfg_reg; + u32 line_stat_reg; + u32 modem_ctl_reg; + u32 adcl_cfg_reg; + + adcl_cfg_reg = readl(port->membase + ADCL_CFG_REG); + + /* HW is responsible in ADCL_EN case */ + if ((adcl_cfg_reg & (ADCL_CFG_EN | ADCL_CFG_PIN_SEL))) + return; + + modem_ctl_reg = readl(port->membase + UART_MODEM_CTL_REG); + + serial8250_do_set_mctrl(port, mctrl); + + if (RTS_TOGGLE_STATUS_MASK(mctrl, modem_ctl_reg)) { + line_stat_reg = readl(port->membase + UART_LINE_STAT_REG); + if (line_stat_reg & UART_LINE_XMIT_CHECK_MASK) { + fract_div_cfg_reg = readl(port->membase + + FRAC_DIV_CFG_REG); + + writel((fract_div_cfg_reg & + ~(FRAC_DIV_TX_END_POINT_MASK)), + port->membase + FRAC_DIV_CFG_REG); + + /* Enable ADC and set the nRTS pin */ + writel((adcl_cfg_reg | (ADCL_CFG_EN | + ADCL_CFG_PIN_SEL)), + port->membase + ADCL_CFG_REG); + + /* Revert to the original settings */ + writel(adcl_cfg_reg, port->membase + ADCL_CFG_REG); + + writel(fract_div_cfg_reg, port->membase + + FRAC_DIV_CFG_REG); + } + } +} + static int pci1xxxx_rs485_config(struct uart_port *port, struct ktermios *termios, struct serial_rs485 *rs485) @@ -631,9 +684,14 @@ static int pci1xxxx_setup(struct pci_dev *pdev, port->port.rs485_config = pci1xxxx_rs485_config; port->port.rs485_supported = pci1xxxx_rs485_supported; - /* From C0 rev Burst operation is supported */ + /* + * C0 and later revisions support Burst operation. + * RTS workaround in mctrl is applicable only to B0. + */ if (rev >= 0xC0) port->port.handle_irq = pci1xxxx_handle_irq; + else if (rev == 0xB0) + port->port.set_mctrl = pci1xxxx_set_mctrl; ret = serial8250_pci_setup_port(pdev, port, 0, PORT_OFFSET * port_idx, 0); if (ret < 0) -- 2.50.1 From c9f49e3e45fccae1841ae61bc5187fef18419ce6 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Mon, 16 Dec 2024 18:18:41 +0106 Subject: [PATCH 08/16] serial: 8250: Use @ier bits to determine if Rx is stopped Commit f19c3f6c8109 ("serial: 8250_port: Don't service RX FIFO if throttled") uses @read_status_mask (bit UART_LSR_DR) to determine if Rx has been stopped. However, the bit UART_LSR_DR is not managed properly in @read_status_mask for all Rx stop/start situations and is therefore not suitable for this purpose. Use the UART_IER_RLSI and UART_IER_RDI bits in @ier instead, as this is already common in 8250-variants and drivers. Signed-off-by: John Ogness Link: https://lore.kernel.org/r/20241216171244.12783-2-john.ogness@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_port.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 1ea52fce9bf1..4d742bfb7e9a 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -1931,7 +1931,7 @@ int serial8250_handle_irq(struct uart_port *port, unsigned int iir) */ if (!(status & (UART_LSR_FIFOE | UART_LSR_BRK_ERROR_BITS)) && (port->status & (UPSTAT_AUTOCTS | UPSTAT_AUTORTS)) && - !(port->read_status_mask & UART_LSR_DR)) + !(up->ier & (UART_IER_RLSI | UART_IER_RDI))) skip_rx = true; if (status & (UART_LSR_DR | UART_LSR_BI) && !skip_rx) { -- 2.50.1 From 9d31746b5385c3f760746f7b2d63b702e2f80de0 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Mon, 16 Dec 2024 18:18:42 +0106 Subject: [PATCH 09/16] serial: 8250: Do not set UART_LSR_THRE in @read_status_mask Since Linux 2.1.8 @read_status_mask is no longer used as a general control of which bits are used from the LSR register. Instead it has become an additional mask applied to @ignore_status_mask. Since UART_LSR_THRE is never set for @ignore_status_mask, it serves no purpose to set it for @read_status_mask. In fact, it propagates the misconception that @read_status_mask can be used as a general mask for LSR bits. Do not set UART_LSR_THRE for @read_status_mask. Signed-off-by: John Ogness Link: https://lore.kernel.org/r/20241216171244.12783-3-john.ogness@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_omap.c | 2 +- drivers/tty/serial/8250/8250_port.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 9eb9aa766811..5290aed76a5e 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -412,7 +412,7 @@ static void omap_8250_set_termios(struct uart_port *port, */ uart_update_timeout(port, termios->c_cflag, baud); - up->port.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR; + up->port.read_status_mask = UART_LSR_OE | UART_LSR_DR; if (termios->c_iflag & INPCK) up->port.read_status_mask |= UART_LSR_FE | UART_LSR_PE; if (termios->c_iflag & (IGNBRK | PARMRK)) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 4d742bfb7e9a..a5c1b069c67b 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -2786,7 +2786,7 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, */ uart_update_timeout(port, termios->c_cflag, baud); - port->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR; + port->read_status_mask = UART_LSR_OE | UART_LSR_DR; if (termios->c_iflag & INPCK) port->read_status_mask |= UART_LSR_FE | UART_LSR_PE; if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK)) -- 2.50.1 From 9c76c0fa81812619a3de4f3fc681f82c8dc17a66 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Mon, 16 Dec 2024 18:18:43 +0106 Subject: [PATCH 10/16] serial: 8250: Never adjust UART_LSR_DR in @read_status_mask The CREAD feature of termios is implemented by setting/clearing UART_LSR_DR within @ignore_status_mask. For this feature to function correctly, it requires that UART_LSR_DR is never masked (unset) in @read_status_mask so that uart_insert_char() can properly drop the character if CREAD is disabled. Currently there are code paths that clear/set UART_LSR_DR from @read_status_mask at times. This appears to be a relic from Linux 1.1.60, where @read_status_mask could be used to mask all UART_LSR reading. However, since Linux 2.1.8 that is no longer the case. Now if UART_LSR_DR is cleared from @read_status_mask, received characters may not be dropped even though CREAD is disabled. This can be seen when: - CREAD is disabled (UART_LSR_DR is set in @ignore_status_mask) - LSR has an error bit set from UART_LSR_BRK_ERROR_BITS (and that error is not ignored via @ignore_status_mask) - UART_LSR_DR is cleared in @read_status_mask In this case characters will be inserted into the tty buffer even though they should be ignored. Remove all setting/clearing of UART_LSR_DR for @read_status_mask except for its initialization. Signed-off-by: John Ogness Link: https://lore.kernel.org/r/20241216171244.12783-4-john.ogness@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 1 - drivers/tty/serial/8250/8250_omap.c | 1 - drivers/tty/serial/8250/8250_port.c | 1 - 3 files changed, 3 deletions(-) diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 5f9f06911795..2b70e82dffeb 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -675,7 +675,6 @@ static void serial_8250_overrun_backoff_work(struct work_struct *work) uart_port_lock_irqsave(port, &flags); up->ier |= UART_IER_RLSI | UART_IER_RDI; - up->port.read_status_mask |= UART_LSR_DR; serial_out(up, UART_IER, up->ier); uart_port_unlock_irqrestore(port, flags); } diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 5290aed76a5e..10144fcc0363 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -838,7 +838,6 @@ static void omap_8250_unthrottle(struct uart_port *port) if (up->dma) up->dma->rx_dma(up); up->ier |= UART_IER_RLSI | UART_IER_RDI; - port->read_status_mask |= UART_LSR_DR; serial_out(up, UART_IER, up->ier); uart_port_unlock_irqrestore(port, flags); diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index a5c1b069c67b..3b9dc2bb06eb 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -1390,7 +1390,6 @@ static void serial8250_stop_rx(struct uart_port *port) serial8250_rpm_get(up); up->ier &= ~(UART_IER_RLSI | UART_IER_RDI); - up->port.read_status_mask &= ~UART_LSR_DR; serial_port_out(port, UART_IER, up->ier); serial8250_rpm_put(up); -- 2.50.1 From b3ee0bc1a065469cfbcef0c35dc42f138563a1fb Mon Sep 17 00:00:00 2001 From: John Ogness Date: Mon, 16 Dec 2024 18:18:44 +0106 Subject: [PATCH 11/16] serial: 8250: Explain the role of @read_status_mask The role of @read_status_mask has changed over time and seems to still cause confusion. This can be expected since there is zero documentation about this driver-specific variable. Add comments to the initialization of @read_status_mask to clarify its role. Signed-off-by: John Ogness Link: https://lore.kernel.org/r/20241216171244.12783-5-john.ogness@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_omap.c | 6 ++++++ drivers/tty/serial/8250/8250_port.c | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 10144fcc0363..42b4aa56b902 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -412,6 +412,12 @@ static void omap_8250_set_termios(struct uart_port *port, */ uart_update_timeout(port, termios->c_cflag, baud); + /* + * Specify which conditions may be considered for error + * handling and the ignoring of characters. The actual + * ignoring of characters only occurs if the bit is set + * in @ignore_status_mask as well. + */ up->port.read_status_mask = UART_LSR_OE | UART_LSR_DR; if (termios->c_iflag & INPCK) up->port.read_status_mask |= UART_LSR_FE | UART_LSR_PE; diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 3b9dc2bb06eb..1a65d3e5f3c0 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -2785,6 +2785,12 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, */ uart_update_timeout(port, termios->c_cflag, baud); + /* + * Specify which conditions may be considered for error + * handling and the ignoring of characters. The actual + * ignoring of characters only occurs if the bit is set + * in @ignore_status_mask as well. + */ port->read_status_mask = UART_LSR_OE | UART_LSR_DR; if (termios->c_iflag & INPCK) port->read_status_mask |= UART_LSR_FE | UART_LSR_PE; -- 2.50.1 From 2c1fd53af21b8cb13878b054894d33d3383eb1f3 Mon Sep 17 00:00:00 2001 From: Miroslav Ondra Date: Sat, 21 Dec 2024 20:11:04 +0100 Subject: [PATCH 12/16] serial: amba-pl011: Fix RTS handling in RS485 mode Data loss on serial line was observed during communication through serial ports ttyAMA1 and ttyAMA2 interconnected via RS485 transcievers. Both ports are in one BCM2711 (Compute Module CM40) and they share the same interrupt line. The problem is caused by long waiting for tx queue flush in the function pl011_rs485_tx_stop. Udelay or mdelay are used to wait. The function is called from the interrupt handler. If multiple devices share a single interrupt line, late processing of pending interrupts and data loss may occur. When operation of both devices are synchronous, collisions are quite often. This rework is based on the method used in tty/serial/imx.c Use hrtimer instead of udelay and mdelay calls. Replace simple bool variable rs485_tx_started by 4-state variable rs485_tx_state. Tested-by: Lino Sanfilippo Signed-off-by: Miroslav Ondra Link: https://lore.kernel.org/r/20241221-amba-rts-v3-1-d3d444681419@faster.cz Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 126 ++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 30 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 69b7a3e1e418..04212c823a91 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -248,6 +248,13 @@ struct pl011_dmatx_data { bool queued; }; +enum pl011_rs485_tx_state { + OFF, + WAIT_AFTER_RTS, + SEND, + WAIT_AFTER_SEND, +}; + /* * We wrap our port structure around the generic uart_port. */ @@ -261,8 +268,10 @@ struct uart_amba_port { unsigned int fifosize; /* vendor-specific */ unsigned int fixed_baud; /* vendor-set fixed baud rate */ char type[12]; - bool rs485_tx_started; - unsigned int rs485_tx_drain_interval; /* usecs */ + ktime_t rs485_tx_drain_interval; /* nano */ + enum pl011_rs485_tx_state rs485_tx_state; + struct hrtimer trigger_start_tx; + struct hrtimer trigger_stop_tx; #ifdef CONFIG_DMA_ENGINE /* DMA stuff */ unsigned int dmacr; /* dma control reg */ @@ -1260,30 +1269,31 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap) static void pl011_rs485_tx_stop(struct uart_amba_port *uap) { - /* - * To be on the safe side only time out after twice as many iterations - * as fifo size. - */ - const int MAX_TX_DRAIN_ITERS = uap->port.fifosize * 2; struct uart_port *port = &uap->port; - int i = 0; u32 cr; - /* Wait until hardware tx queue is empty */ - while (!pl011_tx_empty(port)) { - if (i > MAX_TX_DRAIN_ITERS) { - dev_warn(port->dev, - "timeout while draining hardware tx queue\n"); - break; - } + if (uap->rs485_tx_state == SEND) + uap->rs485_tx_state = WAIT_AFTER_SEND; - udelay(uap->rs485_tx_drain_interval); - i++; + if (uap->rs485_tx_state == WAIT_AFTER_SEND) { + /* Schedule hrtimer if tx queue not empty */ + if (!pl011_tx_empty(port)) { + hrtimer_start(&uap->trigger_stop_tx, + uap->rs485_tx_drain_interval, + HRTIMER_MODE_REL); + return; + } + if (port->rs485.delay_rts_after_send > 0) { + hrtimer_start(&uap->trigger_stop_tx, + ms_to_ktime(port->rs485.delay_rts_after_send), + HRTIMER_MODE_REL); + return; + } + /* Continue without any delay */ + } else if (uap->rs485_tx_state == WAIT_AFTER_RTS) { + hrtimer_try_to_cancel(&uap->trigger_start_tx); } - if (port->rs485.delay_rts_after_send) - mdelay(port->rs485.delay_rts_after_send); - cr = pl011_read(uap, REG_CR); if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND) @@ -1296,7 +1306,7 @@ static void pl011_rs485_tx_stop(struct uart_amba_port *uap) cr |= UART011_CR_RXE; pl011_write(cr, uap, REG_CR); - uap->rs485_tx_started = false; + uap->rs485_tx_state = OFF; } static void pl011_stop_tx(struct uart_port *port) @@ -1304,11 +1314,18 @@ static void pl011_stop_tx(struct uart_port *port) struct uart_amba_port *uap = container_of(port, struct uart_amba_port, port); + if (port->rs485.flags & SER_RS485_ENABLED && + uap->rs485_tx_state == WAIT_AFTER_RTS) { + pl011_rs485_tx_stop(uap); + return; + } + uap->im &= ~UART011_TXIM; pl011_write(uap->im, uap, REG_IMSC); pl011_dma_tx_stop(uap); - if ((port->rs485.flags & SER_RS485_ENABLED) && uap->rs485_tx_started) + if (port->rs485.flags & SER_RS485_ENABLED && + uap->rs485_tx_state != OFF) pl011_rs485_tx_stop(uap); } @@ -1328,10 +1345,19 @@ static void pl011_rs485_tx_start(struct uart_amba_port *uap) struct uart_port *port = &uap->port; u32 cr; + if (uap->rs485_tx_state == WAIT_AFTER_RTS) { + uap->rs485_tx_state = SEND; + return; + } + if (uap->rs485_tx_state == WAIT_AFTER_SEND) { + hrtimer_try_to_cancel(&uap->trigger_stop_tx); + uap->rs485_tx_state = SEND; + return; + } + /* uap->rs485_tx_state == OFF */ /* Enable transmitter */ cr = pl011_read(uap, REG_CR); cr |= UART011_CR_TXE; - /* Disable receiver if half-duplex */ if (!(port->rs485.flags & SER_RS485_RX_DURING_TX)) cr &= ~UART011_CR_RXE; @@ -1343,10 +1369,14 @@ static void pl011_rs485_tx_start(struct uart_amba_port *uap) pl011_write(cr, uap, REG_CR); - if (port->rs485.delay_rts_before_send) - mdelay(port->rs485.delay_rts_before_send); - - uap->rs485_tx_started = true; + if (port->rs485.delay_rts_before_send > 0) { + uap->rs485_tx_state = WAIT_AFTER_RTS; + hrtimer_start(&uap->trigger_start_tx, + ms_to_ktime(port->rs485.delay_rts_before_send), + HRTIMER_MODE_REL); + } else { + uap->rs485_tx_state = SEND; + } } static void pl011_start_tx(struct uart_port *port) @@ -1355,13 +1385,44 @@ static void pl011_start_tx(struct uart_port *port) container_of(port, struct uart_amba_port, port); if ((uap->port.rs485.flags & SER_RS485_ENABLED) && - !uap->rs485_tx_started) + uap->rs485_tx_state != SEND) { pl011_rs485_tx_start(uap); + if (uap->rs485_tx_state == WAIT_AFTER_RTS) + return; + } if (!pl011_dma_tx_start(uap)) pl011_start_tx_pio(uap); } +static enum hrtimer_restart pl011_trigger_start_tx(struct hrtimer *t) +{ + struct uart_amba_port *uap = + container_of(t, struct uart_amba_port, trigger_start_tx); + unsigned long flags; + + uart_port_lock_irqsave(&uap->port, &flags); + if (uap->rs485_tx_state == WAIT_AFTER_RTS) + pl011_start_tx(&uap->port); + uart_port_unlock_irqrestore(&uap->port, flags); + + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart pl011_trigger_stop_tx(struct hrtimer *t) +{ + struct uart_amba_port *uap = + container_of(t, struct uart_amba_port, trigger_stop_tx); + unsigned long flags; + + uart_port_lock_irqsave(&uap->port, &flags); + if (uap->rs485_tx_state == WAIT_AFTER_SEND) + pl011_rs485_tx_stop(uap); + uart_port_unlock_irqrestore(&uap->port, flags); + + return HRTIMER_NORESTART; +} + static void pl011_stop_rx(struct uart_port *port) { struct uart_amba_port *uap = @@ -1953,7 +2014,7 @@ static void pl011_shutdown(struct uart_port *port) pl011_dma_shutdown(uap); - if ((port->rs485.flags & SER_RS485_ENABLED) && uap->rs485_tx_started) + if ((port->rs485.flags & SER_RS485_ENABLED && uap->rs485_tx_state != OFF)) pl011_rs485_tx_stop(uap); free_irq(uap->port.irq, uap); @@ -2098,7 +2159,7 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, * with the given baud rate. We use this as the poll interval when we * wait for the tx queue to empty. */ - uap->rs485_tx_drain_interval = DIV_ROUND_UP(bits * 1000 * 1000, baud); + uap->rs485_tx_drain_interval = ns_to_ktime(DIV_ROUND_UP(bits * NSEC_PER_SEC, baud)); pl011_setup_status_masks(port, termios); @@ -2807,6 +2868,11 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) } } + hrtimer_init(&uap->trigger_start_tx, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_init(&uap->trigger_stop_tx, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + uap->trigger_start_tx.function = pl011_trigger_start_tx; + uap->trigger_stop_tx.function = pl011_trigger_stop_tx; + ret = pl011_setup_port(&dev->dev, uap, &dev->res, portnr); if (ret) return ret; -- 2.50.1 From 79d65fda55cf1a6dac96a69913cd2294a3d8948a Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Fri, 3 Jan 2025 15:11:54 +0800 Subject: [PATCH 13/16] tty: serial: fsl_lpuart: increase maximum uart_nr to 12 Some SoCs like the i.MX943 have aliases for up to 12 UARTs, need to increase UART_NR from 8 to 12 to support lpuart9-12 to avoid initialization failures. Signed-off-by: Sherry Sun Link: https://lore.kernel.org/r/20250103071154.3070924-1-sherry.sun@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/fsl_lpuart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index 57b0632a3db6..7cb1e36fdaab 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -245,7 +245,7 @@ #define DRIVER_NAME "fsl-lpuart" #define DEV_NAME "ttyLP" -#define UART_NR 8 +#define UART_NR 12 /* IMX lpuart has four extra unused regs located at the beginning */ #define IMX_REG_OFF 0x10 -- 2.50.1 From d78a89990986a4d7d34313d3f58f8596b8ae1222 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Tue, 7 Jan 2025 15:48:34 +0800 Subject: [PATCH 14/16] tty: serial: fsl_lpuart: flush RX and TX FIFO when lpuart shutdown Need to flush UART RX and TX FIFO when lpuart is shutting down to make sure restore a clean data transfer environment. Signed-off-by: Sherry Sun Link: https://lore.kernel.org/r/20250107074834.3115230-1-sherry.sun@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/fsl_lpuart.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index 7cb1e36fdaab..c91b9d9818cd 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -1965,6 +1965,11 @@ static void lpuart32_shutdown(struct uart_port *port) UARTCTRL_TIE | UARTCTRL_TCIE | UARTCTRL_RIE | UARTCTRL_SBK); lpuart32_write(port, temp, UARTCTRL); + /* flush Rx/Tx FIFO */ + temp = lpuart32_read(port, UARTFIFO); + temp |= UARTFIFO_TXFLUSH | UARTFIFO_RXFLUSH; + lpuart32_write(port, temp, UARTFIFO); + uart_port_unlock_irqrestore(port, flags); lpuart_dma_shutdown(sport); -- 2.50.1 From e2e4025bc5461258a48cb331107c5b55b3c787d3 Mon Sep 17 00:00:00 2001 From: Ivaylo Dimitrov Date: Sat, 28 Dec 2024 17:00:59 +0200 Subject: [PATCH 15/16] tty: n_gsm: wait until channel 0 is ready Currently code does not wait for channel 0 open sequence to complete before pushing data to the other channels. Also, if userland opens tty, it will receive EL2NSYNC. Both issues result in hard to predict initialization sequence and possible userland failures. Fix that by waiting channel 0 open sequence to complete before attempting opening of the other channels. Also, if tty open() is attempted while channel 0 is opening, wait until sequence is complete before returning to userland. Signed-off-by: Ivaylo Dimitrov Signed-off-by: Tony Lindgren Link: https://lore.kernel.org/r/20241228150100.100354-2-ivo.g.dimitrov.75@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_gsm.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index 252849910588..b92480051e3d 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -2244,13 +2244,16 @@ static void gsm_dlci_t1(struct timer_list *t) break; case DLCI_OPENING: if (dlci->retries) { - dlci->retries--; - gsm_command(dlci->gsm, dlci->addr, SABM|PF); + if (!dlci->addr || !gsm->dlci[0] || + gsm->dlci[0]->state != DLCI_OPENING) { + dlci->retries--; + gsm_command(dlci->gsm, dlci->addr, SABM|PF); + } + mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); } else if (!dlci->addr && gsm->control == (DM | PF)) { if (debug & DBG_ERRORS) - pr_info("DLCI %d opening in ADM mode.\n", - dlci->addr); + pr_info("DLCI 0 opening in ADM mode.\n"); dlci->mode = DLCI_MODE_ADM; gsm_dlci_open(dlci); } else { @@ -2308,7 +2311,9 @@ static void gsm_dlci_begin_open(struct gsm_dlci *dlci) dlci->retries = gsm->n2; if (!need_pn) { dlci->state = DLCI_OPENING; - gsm_command(gsm, dlci->addr, SABM|PF); + if (!dlci->addr || !gsm->dlci[0] || + gsm->dlci[0]->state != DLCI_OPENING) + gsm_command(gsm, dlci->addr, SABM|PF); } else { /* Configure DLCI before setup */ dlci->state = DLCI_CONFIGURE; @@ -4251,7 +4256,7 @@ static const struct tty_port_operations gsm_port_ops = { static int gsmtty_install(struct tty_driver *driver, struct tty_struct *tty) { struct gsm_mux *gsm; - struct gsm_dlci *dlci; + struct gsm_dlci *dlci, *dlci0; unsigned int line = tty->index; unsigned int mux = mux_line_to_num(line); bool alloc = false; @@ -4274,10 +4279,20 @@ static int gsmtty_install(struct tty_driver *driver, struct tty_struct *tty) perspective as we don't have to worry about this if DLCI0 is lost */ mutex_lock(&gsm->mutex); - if (gsm->dlci[0] && gsm->dlci[0]->state != DLCI_OPEN) { + + dlci0 = gsm->dlci[0]; + if (dlci0 && dlci0->state != DLCI_OPEN) { mutex_unlock(&gsm->mutex); - return -EL2NSYNC; + + if (dlci0->state == DLCI_OPENING) + wait_event(gsm->event, dlci0->state != DLCI_OPENING); + + if (dlci0->state != DLCI_OPEN) + return -EL2NSYNC; + + mutex_lock(&gsm->mutex); } + dlci = gsm->dlci[line]; if (dlci == NULL) { alloc = true; -- 2.50.1 From 4a495b97b273fee77a8d77c579ebdd0d0d77f87c Mon Sep 17 00:00:00 2001 From: Ivaylo Dimitrov Date: Sat, 28 Dec 2024 17:01:00 +0200 Subject: [PATCH 16/16] tty: n_gsm: Fix control dlci ADM mode processing Currently, code retries n2 times to open control dlci in ABM mode before switching to ADM mode, but only if DM has been received. This contradicts to the comment that dlci is switched to control mode unconditionally if DLCI_OPENING retries time out. Also, it does not make sense to continue trying once DM has received. Change the logic to switch to ADM mode upon DM received. That way control channel state will change to DLCI_OPEN way faster. Fix the misleading comment while at it. Signed-off-by: Ivaylo Dimitrov Signed-off-by: Tony Lindgren Link: https://lore.kernel.org/r/20241228150100.100354-3-ivo.g.dimitrov.75@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_gsm.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index b92480051e3d..363afe11974f 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -2224,7 +2224,7 @@ static int gsm_dlci_negotiate(struct gsm_dlci *dlci) * * Some control dlci can stay in ADM mode with other dlci working just * fine. In that case we can just keep the control dlci open after the - * DLCI_OPENING retries time out. + * DLCI_OPENING receives DM. */ static void gsm_dlci_t1(struct timer_list *t) @@ -2243,7 +2243,12 @@ static void gsm_dlci_t1(struct timer_list *t) } break; case DLCI_OPENING: - if (dlci->retries) { + if (!dlci->addr && gsm->control == (DM | PF)) { + if (debug & DBG_ERRORS) + pr_info("DLCI 0 opening in ADM mode.\n"); + dlci->mode = DLCI_MODE_ADM; + gsm_dlci_open(dlci); + } else if (dlci->retries) { if (!dlci->addr || !gsm->dlci[0] || gsm->dlci[0]->state != DLCI_OPENING) { dlci->retries--; @@ -2251,11 +2256,6 @@ static void gsm_dlci_t1(struct timer_list *t) } mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); - } else if (!dlci->addr && gsm->control == (DM | PF)) { - if (debug & DBG_ERRORS) - pr_info("DLCI 0 opening in ADM mode.\n"); - dlci->mode = DLCI_MODE_ADM; - gsm_dlci_open(dlci); } else { gsm->open_error++; gsm_dlci_begin_close(dlci); /* prevent half open link */ -- 2.50.1