From 85f17e130dc7189acf03eb0f8e2455c4ea2c4f9e Mon Sep 17 00:00:00 2001 From: Ravi Patel Date: Thu, 18 Sep 2025 08:57:03 +0530 Subject: [PATCH 01/60] dt-bindings: serial: samsung: Add compatible for ARTPEC-9 SoC Add Axis ARTPEC-9 uart compatible to the bindings documentation. It is similar to the older samsung,exynos8895-uart design. Signed-off-by: Ravi Patel Acked-by: Conor Dooley Link: https://patch.msgid.link/20250918032703.8885-1-ravi.patel@samsung.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/samsung_uart.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/serial/samsung_uart.yaml b/Documentation/devicetree/bindings/serial/samsung_uart.yaml index 1a1f991d5364..d12018aaac9b 100644 --- a/Documentation/devicetree/bindings/serial/samsung_uart.yaml +++ b/Documentation/devicetree/bindings/serial/samsung_uart.yaml @@ -48,6 +48,7 @@ properties: - const: samsung,exynos850-uart - items: - enum: + - axis,artpec9-uart - samsung,exynos7870-uart - const: samsung,exynos8895-uart From fd3d4f5a62b1e2b0b4c3f16c2904b3af512648d6 Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Sun, 14 Sep 2025 16:22:01 +0300 Subject: [PATCH 02/60] dt-bindings: serial: samsung: add samsung,exynos8890-uart compatible Add dedicated samsung,exynos8890-uart compatible to the dt-schema for representing uart of the exynos8890. Like exynos8895, it has a required DT property samsung,uart-fifosize, so reuse support for it. Signed-off-by: Ivaylo Ivanov Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20250914132201.2622955-1-ivo.ivanov.ivanov1@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/samsung_uart.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/serial/samsung_uart.yaml b/Documentation/devicetree/bindings/serial/samsung_uart.yaml index d12018aaac9b..75ac2a08f257 100644 --- a/Documentation/devicetree/bindings/serial/samsung_uart.yaml +++ b/Documentation/devicetree/bindings/serial/samsung_uart.yaml @@ -50,6 +50,7 @@ properties: - enum: - axis,artpec9-uart - samsung,exynos7870-uart + - samsung,exynos8890-uart - const: samsung,exynos8895-uart reg: From 08a0dd5a465814233cf39a87bf746dc44d0ab571 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 1 Oct 2025 13:47:26 +0200 Subject: [PATCH 03/60] serial: ar933x: Add polling support KGDB requires at least the polling hooks .poll_get_char and .poll_put_char to transmit/receive character via the serial driver. Signed-off-by: Sven Eckelmann Link: https://patch.msgid.link/20251001-ar933x-kgdb-support-v1-1-5fffd9e36a01@simonwunderlich.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/ar933x_uart.c | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/drivers/tty/serial/ar933x_uart.c b/drivers/tty/serial/ar933x_uart.c index 8bb33556b312..5b491db9d2fc 100644 --- a/drivers/tty/serial/ar933x_uart.c +++ b/drivers/tty/serial/ar933x_uart.c @@ -560,6 +560,64 @@ static int ar933x_uart_verify_port(struct uart_port *port, return 0; } +#ifdef CONFIG_CONSOLE_POLL +static int ar933x_poll_get_char(struct uart_port *port) +{ + struct ar933x_uart_port *up = + container_of(port, struct ar933x_uart_port, port); + unsigned int rdata; + unsigned char ch; + u32 imr; + + /* Disable all interrupts */ + imr = ar933x_uart_read(up, AR933X_UART_INT_EN_REG); + ar933x_uart_write(up, AR933X_UART_INT_EN_REG, 0); + + rdata = ar933x_uart_read(up, AR933X_UART_DATA_REG); + if ((rdata & AR933X_UART_DATA_RX_CSR) == 0) { + /* Enable interrupts */ + ar933x_uart_write(up, AR933X_UART_INT_EN_REG, imr); + return NO_POLL_CHAR; + } + + /* remove the character from the FIFO */ + ar933x_uart_write(up, AR933X_UART_DATA_REG, + AR933X_UART_DATA_RX_CSR); + + ch = rdata & AR933X_UART_DATA_TX_RX_MASK; + + /* Enable interrupts */ + ar933x_uart_write(up, AR933X_UART_INT_EN_REG, imr); + + return ch; +} + +static void ar933x_poll_put_char(struct uart_port *port, unsigned char c) +{ + struct ar933x_uart_port *up = + container_of(port, struct ar933x_uart_port, port); + u32 imr; + + /* Disable all interrupts */ + imr = ar933x_uart_read(up, AR933X_UART_INT_EN_REG); + ar933x_uart_write(up, AR933X_UART_INT_EN_REG, 0); + + /* Wait until FIFO is empty */ + while (!(ar933x_uart_read(up, AR933X_UART_DATA_REG) & AR933X_UART_DATA_TX_CSR)) + cpu_relax(); + + /* Write a character */ + ar933x_uart_putc(up, c); + + /* Wait until FIFO is empty */ + while (!(ar933x_uart_read(up, AR933X_UART_DATA_REG) & AR933X_UART_DATA_TX_CSR)) + cpu_relax(); + + /* Enable interrupts */ + ar933x_uart_write(up, AR933X_UART_INT_EN_REG, imr); +} +#endif + static const struct uart_ops ar933x_uart_ops = { .tx_empty = ar933x_uart_tx_empty, .set_mctrl = ar933x_uart_set_mctrl, @@ -576,6 +634,10 @@ static const struct uart_ops ar933x_uart_ops = { .request_port = ar933x_uart_request_port, .config_port = ar933x_uart_config_port, .verify_port = ar933x_uart_verify_port, +#ifdef CONFIG_CONSOLE_POLL + .poll_get_char = ar933x_poll_get_char, + .poll_put_char = ar933x_poll_put_char, +#endif }; static int ar933x_config_rs485(struct uart_port *port, struct ktermios *termios, From 18bdfccf3c30b208b65b890f75ecc76fcb79e224 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 24 Sep 2025 10:56:07 +0800 Subject: [PATCH 04/60] tty: serial: fsl_lpuart: Add missing wakeup event reporting Current lpuart wakeup event would not report itself as wakeup source through sysfs. Add pm_wakeup_event() to support it. Signed-off-by: Sherry Sun Reviewed-by: Frank Li Link: https://patch.msgid.link/20250924025607.2515833-1-sherry.sun@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/fsl_lpuart.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index c9519e649e82..1bd7ec9c81ea 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -3087,6 +3087,8 @@ static int lpuart_suspend_noirq(struct device *dev) static int lpuart_resume_noirq(struct device *dev) { struct lpuart_port *sport = dev_get_drvdata(dev); + struct tty_port *port = &sport->port.state->port; + bool wake_active; u32 stat; pinctrl_pm_select_default_state(dev); @@ -3098,6 +3100,12 @@ static int lpuart_resume_noirq(struct device *dev) if (lpuart_is_32(sport)) { stat = lpuart32_read(&sport->port, UARTSTAT); lpuart32_write(&sport->port, stat, UARTSTAT); + + /* check whether lpuart wakeup was triggered */ + wake_active = stat & (UARTSTAT_RDRF | UARTSTAT_RXEDGIF); + + if (wake_active && irqd_is_wakeup_set(irq_get_irq_data(sport->port.irq))) + pm_wakeup_event(tty_port_tty_get(port)->dev, 0); } } From d55f3d2375ceeb08330d30f1e08196993c0b6583 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Thu, 2 Oct 2025 12:52:58 +0800 Subject: [PATCH 05/60] tty: serial: imx: Only configure the wake register when device is set as wakeup source Currently, the i.MX UART driver enables wake-related registers for all UART devices by default. However, this is unnecessary for devices that are not configured as wakeup sources. To address this, add a device_may_wakeup() check before configuring the UART wake-related registers. Fixes: db1a9b55004c ("tty: serial: imx: Allow UART to be a source for wakeup") Signed-off-by: Sherry Sun Reviewed-by: Frank Li Link: https://patch.msgid.link/20251002045259.2725461-2-sherry.sun@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 500dfc009d03..90e2ea1e8afe 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -2697,8 +2697,22 @@ static void imx_uart_save_context(struct imx_port *sport) /* called with irq off */ static void imx_uart_enable_wakeup(struct imx_port *sport, bool on) { + struct tty_port *port = &sport->port.state->port; + struct device *tty_dev; + bool may_wake = false; u32 ucr3; + scoped_guard(tty_port_tty, port) { + struct tty_struct *tty = scoped_tty(); + + tty_dev = tty->dev; + may_wake = tty_dev && device_may_wakeup(tty_dev); + } + + /* only configure the wake register when device set as wakeup source */ + if (!may_wake) + return; + uart_port_lock_irq(&sport->port); ucr3 = imx_uart_readl(sport, UCR3); From 0cfadf4bcd283de6d5cb06748dfb1d65e993dbf9 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Thu, 2 Oct 2025 12:52:59 +0800 Subject: [PATCH 06/60] tty: serial: imx: Add missing wakeup event reporting Current imx uart wakeup event would not report itself as wakeup source through sysfs. Add pm_wakeup_event() to support it. Signed-off-by: Sherry Sun Reviewed-by: Frank Li Link: https://patch.msgid.link/20251002045259.2725461-3-sherry.sun@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 90e2ea1e8afe..c488e5d372ff 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include "serial_mctrl_gpio.h" @@ -2699,8 +2699,8 @@ static void imx_uart_enable_wakeup(struct imx_port *sport, bool on) { struct tty_port *port = &sport->port.state->port; struct device *tty_dev; - bool may_wake = false; - u32 ucr3; + bool may_wake = false, wake_active = false; + u32 ucr3, usr1; scoped_guard(tty_port_tty, port) { struct tty_struct *tty = scoped_tty(); @@ -2715,12 +2715,14 @@ static void imx_uart_enable_wakeup(struct imx_port *sport, bool on) uart_port_lock_irq(&sport->port); + usr1 = imx_uart_readl(sport, USR1); ucr3 = imx_uart_readl(sport, UCR3); if (on) { imx_uart_writel(sport, USR1_AWAKE, USR1); ucr3 |= UCR3_AWAKEN; } else { ucr3 &= ~UCR3_AWAKEN; + wake_active = usr1 & USR1_AWAKE; } imx_uart_writel(sport, ucr3, UCR3); @@ -2731,10 +2733,14 @@ static void imx_uart_enable_wakeup(struct imx_port *sport, bool on) ucr1 |= UCR1_RTSDEN; } else { ucr1 &= ~UCR1_RTSDEN; + wake_active = wake_active || (usr1 & USR1_RTSD); } imx_uart_writel(sport, ucr1, UCR1); } + if (wake_active && irqd_is_wakeup_set(irq_get_irq_data(sport->port.irq))) + pm_wakeup_event(tty_port_tty_get(port)->dev, 0); + uart_port_unlock_irq(&sport->port); } From b7cefdb6633824fc4f2e3165974b1aefc343f3b1 Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Tue, 30 Sep 2025 09:27:43 +0200 Subject: [PATCH 07/60] serial: 8250_pcilib: Replace deprecated PCI functions When the '8250_exar' module is loaded into the kernel, a kernel trace with 'WARN_ON(legacy_iomap_table[bar])' is dumped to the console, because the old pci table mapping is still used in '8250_pcilib'. The old function have been deprecated in commit e354bb84a4c1 ("PCI: Deprecate pcim_iomap_table(), pcim_iomap_regions_request_all()"). The remapping already takes or must take place in the driver that calls the function 'serial8250_pci_setup_port()'. The remapping should only be called once via 'pcim_iomap()'. Therefore the remapping moved to the caller of 'serial8250_pci_setup_port()'. To replace the outdated/legacy iomap_table processing in '8250_pcilib' the function signature of 'serial8250_pci_setup_port()' has been extended with an already iomapped address value. So this can be used directly without io mapping again. Signed-off-by: Florian Eckert Reviewed-by: Jiri Slaby Link: https://patch.msgid.link/20250930072743.791580-1-fe@dev.tdt.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_exar.c | 4 ++-- drivers/tty/serial/8250/8250_pci.c | 10 +++++++++- drivers/tty/serial/8250/8250_pci1xxxx.c | 10 +++++----- drivers/tty/serial/8250/8250_pcilib.c | 7 ++----- drivers/tty/serial/8250/8250_pcilib.h | 2 +- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/drivers/tty/serial/8250/8250_exar.c b/drivers/tty/serial/8250/8250_exar.c index 04a0cbab02c2..3c16a849b474 100644 --- a/drivers/tty/serial/8250/8250_exar.c +++ b/drivers/tty/serial/8250/8250_exar.c @@ -503,7 +503,7 @@ static int default_setup(struct exar8250 *priv, struct pci_dev *pcidev, unsigned char status; int err; - err = serial8250_pci_setup_port(pcidev, port, 0, offset, board->reg_shift); + err = serial8250_pci_setup_port(pcidev, port, 0, offset, board->reg_shift, priv->virt); if (err) return err; @@ -831,7 +831,7 @@ static int cti_port_setup_common(struct exar8250 *priv, port->port.port_id = idx; port->port.uartclk = priv->osc_freq; - ret = serial8250_pci_setup_port(pcidev, port, 0, offset, 0); + ret = serial8250_pci_setup_port(pcidev, port, 0, offset, 0, priv->virt); if (ret) return ret; diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index 152f914c599d..f0f13fdda2df 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -165,7 +165,15 @@ static int setup_port(struct serial_private *priv, struct uart_8250_port *port, u8 bar, unsigned int offset, int regshift) { - return serial8250_pci_setup_port(priv->dev, port, bar, offset, regshift); + void __iomem *iomem = NULL; + + if (pci_resource_flags(priv->dev, bar) & IORESOURCE_MEM) { + iomem = pcim_iomap(priv->dev, bar, 0); + if (!iomem) + return -ENOMEM; + } + + return serial8250_pci_setup_port(priv->dev, port, bar, offset, regshift, iomem); } /* diff --git a/drivers/tty/serial/8250/8250_pci1xxxx.c b/drivers/tty/serial/8250/8250_pci1xxxx.c index 4c149db84692..feeede164886 100644 --- a/drivers/tty/serial/8250/8250_pci1xxxx.c +++ b/drivers/tty/serial/8250/8250_pci1xxxx.c @@ -671,7 +671,7 @@ static int pci1xxxx_resume(struct device *dev) } static int pci1xxxx_setup(struct pci_dev *pdev, - struct uart_8250_port *port, int port_idx, int rev) + struct uart_8250_port *port, int port_idx, struct pci1xxxx_8250 *priv) { int ret; @@ -698,12 +698,12 @@ static int pci1xxxx_setup(struct pci_dev *pdev, * C0 and later revisions support Burst operation. * RTS workaround in mctrl is applicable only to B0. */ - if (rev >= 0xC0) + if (priv->dev_rev >= 0xC0) port->port.handle_irq = pci1xxxx_handle_irq; - else if (rev == 0xB0) + else if (priv->dev_rev == 0xB0) port->port.set_mctrl = pci1xxxx_set_mctrl; - ret = serial8250_pci_setup_port(pdev, port, 0, PORT_OFFSET * port_idx, 0); + ret = serial8250_pci_setup_port(pdev, port, 0, PORT_OFFSET * port_idx, 0, priv->membase); if (ret < 0) return ret; @@ -821,7 +821,7 @@ static int pci1xxxx_serial_probe(struct pci_dev *pdev, else uart.port.irq = pci_irq_vector(pdev, 0); - rc = pci1xxxx_setup(pdev, &uart, port_idx, priv->dev_rev); + rc = pci1xxxx_setup(pdev, &uart, port_idx, priv); if (rc) { dev_warn(dev, "Failed to setup port %u\n", i); continue; diff --git a/drivers/tty/serial/8250/8250_pcilib.c b/drivers/tty/serial/8250/8250_pcilib.c index d8d0ae0d7238..9d5d2531a33b 100644 --- a/drivers/tty/serial/8250/8250_pcilib.c +++ b/drivers/tty/serial/8250/8250_pcilib.c @@ -22,19 +22,16 @@ int serial_8250_warn_need_ioport(struct pci_dev *dev) EXPORT_SYMBOL_NS_GPL(serial_8250_warn_need_ioport, "SERIAL_8250_PCI"); int serial8250_pci_setup_port(struct pci_dev *dev, struct uart_8250_port *port, - u8 bar, unsigned int offset, int regshift) + u8 bar, unsigned int offset, int regshift, void __iomem *iomem) { if (bar >= PCI_STD_NUM_BARS) return -EINVAL; if (pci_resource_flags(dev, bar) & IORESOURCE_MEM) { - if (!pcim_iomap(dev, bar, 0) && !pcim_iomap_table(dev)) - return -ENOMEM; - port->port.iotype = UPIO_MEM; port->port.iobase = 0; port->port.mapbase = pci_resource_start(dev, bar) + offset; - port->port.membase = pcim_iomap_table(dev)[bar] + offset; + port->port.membase = iomem + offset; port->port.regshift = regshift; } else if (IS_ENABLED(CONFIG_HAS_IOPORT)) { port->port.iotype = UPIO_PORT; diff --git a/drivers/tty/serial/8250/8250_pcilib.h b/drivers/tty/serial/8250/8250_pcilib.h index 16a274574cde..ab18de8d1355 100644 --- a/drivers/tty/serial/8250/8250_pcilib.h +++ b/drivers/tty/serial/8250/8250_pcilib.h @@ -12,6 +12,6 @@ struct pci_dev; struct uart_8250_port; int serial8250_pci_setup_port(struct pci_dev *dev, struct uart_8250_port *port, u8 bar, - unsigned int offset, int regshift); + unsigned int offset, int regshift, void __iomem *iomem); int serial_8250_warn_need_ioport(struct pci_dev *dev); From 7553f5173ec3f01d7452a80ba82bef60d3ba29b7 Mon Sep 17 00:00:00 2001 From: Abhinav Saxena Date: Wed, 3 Sep 2025 09:49:43 -0600 Subject: [PATCH 08/60] selftests/tty: add TIOCSTI test suite TIOCSTI is a TTY ioctl command that allows inserting characters into the terminal input queue, making it appear as if the user typed those characters. This functionality has behavior that varies based on system configuration and process credentials. The dev.tty.legacy_tiocsti sysctl introduced in commit 83efeeeb3d04 ("tty: Allow TIOCSTI to be disabled") controls TIOCSTI usage. When disabled, TIOCSTI requires CAP_SYS_ADMIN capability. The current implementation checks the current process's credentials via capable(CAP_SYS_ADMIN), but does not validate against the file opener's credentials stored in file->f_cred. This creates different behavior when file descriptors are passed between processes via SCM_RIGHTS. Add a test suite with 16 test variants using fixture variants to verify TIOCSTI behavior when dev.tty.legacy_tiocsti is enabled/disabled: - Basic TIOCSTI tests (8 variants): Direct testing with different capability and controlling terminal combinations - FD passing tests (8 variants): Test behavior when file descriptors are passed between processes with different capabilities The FD passing tests document this behavior - some tests show different results than expected based on file opener credentials, demonstrating that TIOCSTI uses current process credentials rather than file opener credentials. The tests validate proper enforcement of the legacy_tiocsti sysctl. Test implementation uses openpty(3) with TIOCSCTTY for isolated PTY environments. See tty_ioctl(4) for details on TIOCSTI behavior and security requirements. Signed-off-by: Abhinav Saxena Link: https://patch.msgid.link/20250903-toicsti-bug-v4-1-4894b6649ef8@gmail.com Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/tty/Makefile | 6 +- tools/testing/selftests/tty/config | 1 + .../testing/selftests/tty/tty_tiocsti_test.c | 650 ++++++++++++++++++ 3 files changed, 656 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/tty/config create mode 100644 tools/testing/selftests/tty/tty_tiocsti_test.c diff --git a/tools/testing/selftests/tty/Makefile b/tools/testing/selftests/tty/Makefile index 50d7027b2ae3..7f6fbe5a0cd5 100644 --- a/tools/testing/selftests/tty/Makefile +++ b/tools/testing/selftests/tty/Makefile @@ -1,5 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 CFLAGS = -O2 -Wall -TEST_GEN_PROGS := tty_tstamp_update +TEST_GEN_PROGS := tty_tstamp_update tty_tiocsti_test +LDLIBS += -lcap include ../lib.mk + +# Add libcap for TIOCSTI test +$(OUTPUT)/tty_tiocsti_test: LDLIBS += -lcap diff --git a/tools/testing/selftests/tty/config b/tools/testing/selftests/tty/config new file mode 100644 index 000000000000..c6373aba6636 --- /dev/null +++ b/tools/testing/selftests/tty/config @@ -0,0 +1 @@ +CONFIG_LEGACY_TIOCSTI=y diff --git a/tools/testing/selftests/tty/tty_tiocsti_test.c b/tools/testing/selftests/tty/tty_tiocsti_test.c new file mode 100644 index 000000000000..5e767e6cb3ef --- /dev/null +++ b/tools/testing/selftests/tty/tty_tiocsti_test.c @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TTY Tests - TIOCSTI + * + * Copyright © 2025 Abhinav Saxena + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" + +enum test_type { + TEST_PTY_TIOCSTI_BASIC, + TEST_PTY_TIOCSTI_FD_PASSING, + /* other tests cases such as serial may be added. */ +}; + +/* + * Test Strategy: + * - Basic tests: Use PTY with/without TIOCSCTTY (controlling terminal for + * current process) + * - FD passing tests: Child creates PTY, parent receives FD (demonstrates + * security issue) + * + * SECURITY VULNERABILITY DEMONSTRATION: + * FD passing tests show that TIOCSTI uses CURRENT process credentials, not + * opener credentials. This means privileged processes can be given FDs from + * unprivileged processes and successfully perform TIOCSTI operations that the + * unprivileged process couldn't do directly. + * + * Attack scenario: + * 1. Unprivileged process opens TTY (direct TIOCSTI fails due to lack of + * privileges) + * 2. Unprivileged process passes FD to privileged process via SCM_RIGHTS + * 3. Privileged process can use TIOCSTI on the FD (succeeds due to its + * privileges) + * 4. Result: Effective privilege escalation via file descriptor passing + * + * This matches the kernel logic in tiocsti(): + * 1. if (!tty_legacy_tiocsti && !capable(CAP_SYS_ADMIN)) return -EIO; + * 2. if ((current->signal->tty != tty) && !capable(CAP_SYS_ADMIN)) + * return -EPERM; + * Note: Both checks use capable() on CURRENT process, not FD opener! + * + * If the file credentials were also checked along with the capable() checks + * then the results for FD pass tests would be consistent with the basic tests. + */ + +FIXTURE(tiocsti) +{ + int pty_master_fd; /* PTY - for basic tests */ + int pty_slave_fd; + bool has_pty; + bool initial_cap_sys_admin; + int original_legacy_tiocsti_setting; + bool can_modify_sysctl; +}; + +FIXTURE_VARIANT(tiocsti) +{ + const enum test_type test_type; + const bool controlling_tty; /* true=current->signal->tty == tty */ + const int legacy_tiocsti; /* 0=restricted, 1=permissive */ + const bool requires_cap; /* true=with CAP_SYS_ADMIN, false=without */ + const int expected_success; /* 0=success, -EIO/-EPERM=specific error */ +}; + +/* + * Tests Controlling Terminal Variants (current->signal->tty == tty) + * + * TIOCSTI Test Matrix: + * + * | legacy_tiocsti | CAP_SYS_ADMIN | Expected Result | Error | + * |----------------|---------------|-----------------|-------| + * | 1 (permissive) | true | SUCCESS | - | + * | 1 (permissive) | false | SUCCESS | - | + * | 0 (restricted) | true | SUCCESS | - | + * | 0 (restricted) | false | FAILURE | -EIO | + */ + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tiocsti, basic_pty_permissive_withcap) { + .test_type = TEST_PTY_TIOCSTI_BASIC, + .controlling_tty = true, + .legacy_tiocsti = 1, + .requires_cap = true, + .expected_success = 0, +}; + +FIXTURE_VARIANT_ADD(tiocsti, basic_pty_permissive_nocap) { + .test_type = TEST_PTY_TIOCSTI_BASIC, + .controlling_tty = true, + .legacy_tiocsti = 1, + .requires_cap = false, + .expected_success = 0, +}; + +FIXTURE_VARIANT_ADD(tiocsti, basic_pty_restricted_withcap) { + .test_type = TEST_PTY_TIOCSTI_BASIC, + .controlling_tty = true, + .legacy_tiocsti = 0, + .requires_cap = true, + .expected_success = 0, +}; + +FIXTURE_VARIANT_ADD(tiocsti, basic_pty_restricted_nocap) { + .test_type = TEST_PTY_TIOCSTI_BASIC, + .controlling_tty = true, + .legacy_tiocsti = 0, + .requires_cap = false, + .expected_success = -EIO, /* FAILURE: legacy restriction */ +}; /* clang-format on */ + +/* + * Note for FD Passing Test Variants + * Since we're testing the scenario where an unprivileged process pass an FD + * to a privileged one, .requires_cap here means the caps of the child process. + * Not the parent; parent would always be privileged. + */ + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tiocsti, fdpass_pty_permissive_withcap) { + .test_type = TEST_PTY_TIOCSTI_FD_PASSING, + .controlling_tty = true, + .legacy_tiocsti = 1, + .requires_cap = true, + .expected_success = 0, +}; + +FIXTURE_VARIANT_ADD(tiocsti, fdpass_pty_permissive_nocap) { + .test_type = TEST_PTY_TIOCSTI_FD_PASSING, + .controlling_tty = true, + .legacy_tiocsti = 1, + .requires_cap = false, + .expected_success = 0, +}; + +FIXTURE_VARIANT_ADD(tiocsti, fdpass_pty_restricted_withcap) { + .test_type = TEST_PTY_TIOCSTI_FD_PASSING, + .controlling_tty = true, + .legacy_tiocsti = 0, + .requires_cap = true, + .expected_success = 0, +}; + +FIXTURE_VARIANT_ADD(tiocsti, fdpass_pty_restricted_nocap) { + .test_type = TEST_PTY_TIOCSTI_FD_PASSING, + .controlling_tty = true, + .legacy_tiocsti = 0, + .requires_cap = false, + .expected_success = -EIO, +}; /* clang-format on */ + +/* + * Non-Controlling Terminal Variants (current->signal->tty != tty) + * + * TIOCSTI Test Matrix: + * + * | legacy_tiocsti | CAP_SYS_ADMIN | Expected Result | Error | + * |----------------|---------------|-----------------|-------| + * | 1 (permissive) | true | SUCCESS | - | + * | 1 (permissive) | false | FAILURE | -EPERM| + * | 0 (restricted) | true | SUCCESS | - | + * | 0 (restricted) | false | FAILURE | -EIO | + */ + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tiocsti, basic_nopty_permissive_withcap) { + .test_type = TEST_PTY_TIOCSTI_BASIC, + .controlling_tty = false, + .legacy_tiocsti = 1, + .requires_cap = true, + .expected_success = 0, +}; + +FIXTURE_VARIANT_ADD(tiocsti, basic_nopty_permissive_nocap) { + .test_type = TEST_PTY_TIOCSTI_BASIC, + .controlling_tty = false, + .legacy_tiocsti = 1, + .requires_cap = false, + .expected_success = -EPERM, +}; + +FIXTURE_VARIANT_ADD(tiocsti, basic_nopty_restricted_withcap) { + .test_type = TEST_PTY_TIOCSTI_BASIC, + .controlling_tty = false, + .legacy_tiocsti = 0, + .requires_cap = true, + .expected_success = 0, +}; + +FIXTURE_VARIANT_ADD(tiocsti, basic_nopty_restricted_nocap) { + .test_type = TEST_PTY_TIOCSTI_BASIC, + .controlling_tty = false, + .legacy_tiocsti = 0, + .requires_cap = false, + .expected_success = -EIO, +}; + +FIXTURE_VARIANT_ADD(tiocsti, fdpass_nopty_permissive_withcap) { + .test_type = TEST_PTY_TIOCSTI_FD_PASSING, + .controlling_tty = false, + .legacy_tiocsti = 1, + .requires_cap = true, + .expected_success = 0, +}; + +FIXTURE_VARIANT_ADD(tiocsti, fdpass_nopty_permissive_nocap) { + .test_type = TEST_PTY_TIOCSTI_FD_PASSING, + .controlling_tty = false, + .legacy_tiocsti = 1, + .requires_cap = false, + .expected_success = -EPERM, +}; + +FIXTURE_VARIANT_ADD(tiocsti, fdpass_nopty_restricted_withcap) { + .test_type = TEST_PTY_TIOCSTI_FD_PASSING, + .controlling_tty = false, + .legacy_tiocsti = 0, + .requires_cap = true, + .expected_success = 0, +}; + +FIXTURE_VARIANT_ADD(tiocsti, fdpass_nopty_restricted_nocap) { + .test_type = TEST_PTY_TIOCSTI_FD_PASSING, + .controlling_tty = false, + .legacy_tiocsti = 0, + .requires_cap = false, + .expected_success = -EIO, +}; /* clang-format on */ + +/* Helper function to send FD via SCM_RIGHTS */ +static int send_fd_via_socket(int socket_fd, int fd_to_send) +{ + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg; + char cmsg_buf[CMSG_SPACE(sizeof(int))]; + char dummy_data = 'F'; + struct iovec iov = { .iov_base = &dummy_data, .iov_len = 1 }; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + + memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int)); + + return sendmsg(socket_fd, &msg, 0) < 0 ? -1 : 0; +} + +/* Helper function to receive FD via SCM_RIGHTS */ +static int recv_fd_via_socket(int socket_fd) +{ + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg; + char cmsg_buf[CMSG_SPACE(sizeof(int))]; + char dummy_data; + struct iovec iov = { .iov_base = &dummy_data, .iov_len = 1 }; + int received_fd = -1; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + + if (recvmsg(socket_fd, &msg, 0) < 0) + return -1; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int)); + break; + } + } + + return received_fd; +} + +static inline bool has_cap_sys_admin(void) +{ + cap_t caps = cap_get_proc(); + + if (!caps) + return false; + + cap_flag_value_t cap_val; + bool has_cap = (cap_get_flag(caps, CAP_SYS_ADMIN, CAP_EFFECTIVE, + &cap_val) == 0) && + (cap_val == CAP_SET); + + cap_free(caps); + return has_cap; +} + +/* + * Switch to non-root user and clear all capabilities + */ +static inline bool drop_all_privs(struct __test_metadata *_metadata) +{ + /* Drop supplementary groups */ + ASSERT_EQ(setgroups(0, NULL), 0); + + /* Switch to non-root user */ + ASSERT_EQ(setgid(1000), 0); + ASSERT_EQ(setuid(1000), 0); + + /* Clear all capabilities */ + cap_t empty = cap_init(); + + ASSERT_NE(empty, NULL); + ASSERT_EQ(cap_set_proc(empty), 0); + cap_free(empty); + + /* Prevent privilege regain */ + ASSERT_EQ(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), 0); + + /* Verify privilege drop */ + ASSERT_FALSE(has_cap_sys_admin()); + return true; +} + +static inline int get_legacy_tiocsti_setting(struct __test_metadata *_metadata) +{ + FILE *fp; + int value = -1; + + fp = fopen("/proc/sys/dev/tty/legacy_tiocsti", "r"); + if (!fp) { + /* legacy_tiocsti sysctl not available (kernel < 6.2) */ + return -1; + } + + if (fscanf(fp, "%d", &value) == 1 && fclose(fp) == 0) { + if (value < 0 || value > 1) + value = -1; /* Invalid value */ + } else { + value = -1; /* Failed to parse */ + } + + return value; +} + +static inline bool set_legacy_tiocsti_setting(struct __test_metadata *_metadata, + int value) +{ + FILE *fp; + bool success = false; + + /* Sanity-check the value */ + ASSERT_GE(value, 0); + ASSERT_LE(value, 1); + + /* + * Try to open for writing; if we lack permission, return false so + * the test harness will skip variants that need to change it + */ + fp = fopen("/proc/sys/dev/tty/legacy_tiocsti", "w"); + if (!fp) + return false; + + /* Write the new setting */ + if (fprintf(fp, "%d\n", value) > 0 && fclose(fp) == 0) + success = true; + else + TH_LOG("Failed to write legacy_tiocsti: %s", strerror(errno)); + + return success; +} + +/* + * TIOCSTI injection test function + * @tty_fd: TTY slave file descriptor to test TIOCSTI on + * Returns: 0 on success, -errno on failure + */ +static inline int test_tiocsti_injection(struct __test_metadata *_metadata, + int tty_fd) +{ + int ret; + char inject_char = 'V'; + + errno = 0; + ret = ioctl(tty_fd, TIOCSTI, &inject_char); + return ret == 0 ? 0 : -errno; +} + +/* + * Child process: test TIOCSTI directly with capability/controlling + * terminal setup + */ +static void run_basic_tiocsti_test(struct __test_metadata *_metadata, + FIXTURE_DATA(tiocsti) * self, + const FIXTURE_VARIANT(tiocsti) * variant) +{ + /* Handle capability requirements */ + if (self->initial_cap_sys_admin && !variant->requires_cap) + ASSERT_TRUE(drop_all_privs(_metadata)); + + if (variant->controlling_tty) { + /* + * Create new session and set PTY as + * controlling terminal + */ + pid_t sid = setsid(); + + ASSERT_GE(sid, 0); + ASSERT_EQ(ioctl(self->pty_slave_fd, TIOCSCTTY, 0), 0); + } + + /* + * Validate test environment setup and verify final + * capability state matches expectation + * after potential drop. + */ + ASSERT_TRUE(self->has_pty); + ASSERT_EQ(has_cap_sys_admin(), variant->requires_cap); + + /* Test TIOCSTI and validate result */ + int result = test_tiocsti_injection(_metadata, self->pty_slave_fd); + + /* Check against expected result from variant */ + EXPECT_EQ(result, variant->expected_success); + _exit(0); +} + +/* + * Child process: create PTY and then pass FD to parent via SCM_RIGHTS + */ +static void run_fdpass_tiocsti_test(struct __test_metadata *_metadata, + const FIXTURE_VARIANT(tiocsti) * variant, + int sockfd) +{ + signal(SIGHUP, SIG_IGN); + + /* Handle privilege dropping */ + if (!variant->requires_cap && has_cap_sys_admin()) + ASSERT_TRUE(drop_all_privs(_metadata)); + + /* Create child's PTY */ + int child_master_fd, child_slave_fd; + + ASSERT_EQ(openpty(&child_master_fd, &child_slave_fd, NULL, NULL, NULL), + 0); + + if (variant->controlling_tty) { + pid_t sid = setsid(); + + ASSERT_GE(sid, 0); + ASSERT_EQ(ioctl(child_slave_fd, TIOCSCTTY, 0), 0); + } + + /* Test child's direct TIOCSTI for reference */ + int direct_result = test_tiocsti_injection(_metadata, child_slave_fd); + + EXPECT_EQ(direct_result, variant->expected_success); + + /* Send FD to parent */ + ASSERT_EQ(send_fd_via_socket(sockfd, child_slave_fd), 0); + + /* Wait for parent completion signal */ + char sync_byte; + ssize_t bytes_read = read(sockfd, &sync_byte, 1); + + ASSERT_EQ(bytes_read, 1); + + close(child_master_fd); + close(child_slave_fd); + close(sockfd); + _exit(0); +} + +FIXTURE_SETUP(tiocsti) +{ + /* Create PTY pair for basic tests */ + self->has_pty = (openpty(&self->pty_master_fd, &self->pty_slave_fd, + NULL, NULL, NULL) == 0); + if (!self->has_pty) { + self->pty_master_fd = -1; + self->pty_slave_fd = -1; + } + + self->initial_cap_sys_admin = has_cap_sys_admin(); + self->original_legacy_tiocsti_setting = + get_legacy_tiocsti_setting(_metadata); + + if (self->original_legacy_tiocsti_setting < 0) + SKIP(return, + "legacy_tiocsti sysctl not available (kernel < 6.2)"); + + /* Common skip conditions */ + if (variant->test_type == TEST_PTY_TIOCSTI_BASIC && !self->has_pty) + SKIP(return, "PTY not available for controlling terminal test"); + + if (variant->test_type == TEST_PTY_TIOCSTI_FD_PASSING && + !self->initial_cap_sys_admin) + SKIP(return, "FD Pass tests require CAP_SYS_ADMIN"); + + if (variant->requires_cap && !self->initial_cap_sys_admin) + SKIP(return, "Test requires initial CAP_SYS_ADMIN"); + + /* Test if we can modify the sysctl (requires appropriate privileges) */ + self->can_modify_sysctl = set_legacy_tiocsti_setting( + _metadata, self->original_legacy_tiocsti_setting); + + /* Sysctl setup based on variant */ + if (self->can_modify_sysctl && + self->original_legacy_tiocsti_setting != variant->legacy_tiocsti) { + if (!set_legacy_tiocsti_setting(_metadata, + variant->legacy_tiocsti)) + SKIP(return, "Failed to set legacy_tiocsti sysctl"); + + } else if (!self->can_modify_sysctl && + self->original_legacy_tiocsti_setting != + variant->legacy_tiocsti) + SKIP(return, "legacy_tiocsti setting mismatch"); +} + +FIXTURE_TEARDOWN(tiocsti) +{ + /* + * Backup restoration - + * each test should restore its own sysctl changes + */ + if (self->can_modify_sysctl) { + int current_value = get_legacy_tiocsti_setting(_metadata); + + if (current_value != self->original_legacy_tiocsti_setting) { + TH_LOG("Backup: Restoring legacy_tiocsti from %d to %d", + current_value, + self->original_legacy_tiocsti_setting); + set_legacy_tiocsti_setting( + _metadata, + self->original_legacy_tiocsti_setting); + } + } + + if (self->has_pty) { + if (self->pty_master_fd >= 0) + close(self->pty_master_fd); + if (self->pty_slave_fd >= 0) + close(self->pty_slave_fd); + } +} + +TEST_F(tiocsti, test) +{ + int status; + pid_t child_pid; + + if (variant->test_type == TEST_PTY_TIOCSTI_BASIC) { + /* ===== BASIC TIOCSTI TEST ===== */ + child_pid = fork(); + ASSERT_GE(child_pid, 0); + + /* Perform the actual test in the child process */ + if (child_pid == 0) + run_basic_tiocsti_test(_metadata, self, variant); + + } else { + /* ===== FD PASSING SECURITY TEST ===== */ + int sockpair[2]; + + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair), 0); + + child_pid = fork(); + ASSERT_GE(child_pid, 0); + + if (child_pid == 0) { + /* Child process - create PTY and send FD */ + close(sockpair[0]); + run_fdpass_tiocsti_test(_metadata, variant, + sockpair[1]); + } + + /* Parent process - receive FD and test TIOCSTI */ + close(sockpair[1]); + + int received_fd = recv_fd_via_socket(sockpair[0]); + + ASSERT_GE(received_fd, 0); + + bool parent_has_cap = self->initial_cap_sys_admin; + + TH_LOG("=== TIOCSTI FD Passing Test Context ==="); + TH_LOG("legacy_tiocsti: %d, Parent CAP_SYS_ADMIN: %s, Child: %s", + variant->legacy_tiocsti, parent_has_cap ? "yes" : "no", + variant->requires_cap ? "kept" : "dropped"); + + /* SECURITY TEST: Try TIOCSTI with FD opened by child */ + int result = test_tiocsti_injection(_metadata, received_fd); + + /* Log security concern if demonstrated */ + if (result == 0 && !variant->requires_cap) { + TH_LOG("*** SECURITY CONCERN DEMONSTRATED ***"); + TH_LOG("Privileged parent can use TIOCSTI on FD from unprivileged child"); + TH_LOG("This shows current process credentials are used, not opener credentials"); + } + + EXPECT_EQ(result, variant->expected_success) + { + TH_LOG("FD passing: expected error %d, got %d", + variant->expected_success, result); + } + + /* Signal child completion */ + char sync_byte = 'D'; + ssize_t bytes_written = write(sockpair[0], &sync_byte, 1); + + ASSERT_EQ(bytes_written, 1); + + close(received_fd); + close(sockpair[0]); + } + + /* Common child process cleanup for both test types */ + ASSERT_EQ(waitpid(child_pid, &status, 0), child_pid); + + if (WIFSIGNALED(status)) { + TH_LOG("Child terminated by signal %d", WTERMSIG(status)); + ASSERT_FALSE(WIFSIGNALED(status)) + { + TH_LOG("Child process failed assertion"); + } + } else { + EXPECT_EQ(WEXITSTATUS(status), 0); + } +} + +TEST_HARNESS_MAIN From 02227b97a8d3ae01bcf12e5e1b5cb6cca9439875 Mon Sep 17 00:00:00 2001 From: Gopi Krishna Menon Date: Sun, 26 Oct 2025 15:30:25 +0530 Subject: [PATCH 09/60] selftests: tty: add tty_tiocsti_test to .gitignore Building the tty selftests generates the tty_tiocsti_test binary, which appears as untracked file in git. As mentioned in the kselftest documentation, all the generated objects must be placed inside .gitignore. This prevents the generated objects from accidentally getting staged and keeps the working tree clean. Add the tty_tiocsti_test binary to .gitignore to avoid accidentally staging the build artifact and maintain a clean working tree. Link: https://docs.kernel.org/dev-tools/kselftest.html#contributing-new-tests-details Fixes: 7553f5173ec3 ("selftests/tty: add TIOCSTI test suite") Suggested-by: Greg KH Suggested-by: David Hunter Signed-off-by: Gopi Krishna Menon Link: https://patch.msgid.link/20251026100104.3354-1-krishnagopi487@gmail.com Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/tty/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/tty/.gitignore b/tools/testing/selftests/tty/.gitignore index fe70462a4aad..2453685d2493 100644 --- a/tools/testing/selftests/tty/.gitignore +++ b/tools/testing/selftests/tty/.gitignore @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only +tty_tiocsti_test tty_tstamp_update From 75ae5e9119d98a03316868f7fc3c7e579268da43 Mon Sep 17 00:00:00 2001 From: Kriish Sharma Date: Sat, 25 Oct 2025 16:12:06 +0000 Subject: [PATCH 10/60] tty: document @dlci parameter in gsm_modem_send_initial_msc Add missing kernel-doc entry for the @dlci parameter in gsm_modem_send_initial_msc(), which fixes the following warning reported by kernel-doc: Warning: drivers/tty/n_gsm.c:4175 function parameter 'dlci' not described in 'gsm_modem_send_initial_msc' Signed-off-by: Kriish Sharma Link: https://patch.msgid.link/20251025161206.795784-1-kriish.sharma2006@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_gsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index 553d8c70352b..214abeb89aaa 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -4165,7 +4165,7 @@ static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk) /** * gsm_modem_send_initial_msc - Send initial modem status message * - * @dlci channel + * @dlci: channel * * Send an initial MSC message after DLCI open to set the initial * modem status lines. This is only done for basic mode. From e138428498cb733efaf1c2762d3785b802784378 Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Wed, 22 Oct 2025 00:32:09 +0200 Subject: [PATCH 11/60] dt-bindings: serial: snps-dw-apb-uart: Add support for rk3506 The uarts used in the RK3506 SoC are still the same dw-apb-uart compatible type as on the SoCs that came before, so add the RK3506 to the list of variants. Signed-off-by: Heiko Stuebner Acked-by: Conor Dooley Link: https://patch.msgid.link/20251021223209.193569-1-heiko@sntech.de Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml index cb9da6c97afc..7a71b58f880a 100644 --- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml +++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml @@ -64,6 +64,7 @@ properties: - rockchip,rk3328-uart - rockchip,rk3368-uart - rockchip,rk3399-uart + - rockchip,rk3506-uart - rockchip,rk3528-uart - rockchip,rk3562-uart - rockchip,rk3568-uart From 84d5153599f82f95f787ce4159e73ef6165d52ab Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:43 -0400 Subject: [PATCH 12/60] serial: sc16is7xx: rename LCR macros to better reflect usage There is no reference to CONF_MODE_A or CONF_MODE_B in the manufacturer's datasheet. Rename register set configuration macros for the LCR register, to better show their intended usage to select either the Special register set, or the Enhanced register set. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-2-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index c7435595dce1..330d95446f1d 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -149,10 +149,12 @@ #define SC16IS7XX_LCR_WORD_LEN_6 (0x01) #define SC16IS7XX_LCR_WORD_LEN_7 (0x02) #define SC16IS7XX_LCR_WORD_LEN_8 (0x03) -#define SC16IS7XX_LCR_CONF_MODE_A SC16IS7XX_LCR_DLAB_BIT /* Special - * reg set */ -#define SC16IS7XX_LCR_CONF_MODE_B 0xBF /* Enhanced - * reg set */ +#define SC16IS7XX_LCR_REG_SET_SPECIAL SC16IS7XX_LCR_DLAB_BIT /* Special + * reg set + */ +#define SC16IS7XX_LCR_REG_SET_ENHANCED 0xBF /* Enhanced + * reg set + */ /* MCR register bits */ #define SC16IS7XX_MCR_DTR_BIT BIT(0) /* DTR complement @@ -442,7 +444,7 @@ static void sc16is7xx_efr_lock(struct uart_port *port) one->old_lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG); /* Enable access to Enhanced register set */ - sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, SC16IS7XX_LCR_CONF_MODE_B); + sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, SC16IS7XX_LCR_REG_SET_ENHANCED); /* Disable cache updates when writing to EFR registers */ regcache_cache_bypass(one->regmap, true); @@ -598,7 +600,7 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud) /* Backup LCR and access special register set (DLL/DLH) */ lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG); sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, - SC16IS7XX_LCR_CONF_MODE_A); + SC16IS7XX_LCR_REG_SET_SPECIAL); /* Write the new divisor */ regcache_cache_bypass(one->regmap, true); @@ -1650,7 +1652,7 @@ int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype, /* Enable EFR */ sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG, - SC16IS7XX_LCR_CONF_MODE_B); + SC16IS7XX_LCR_REG_SET_ENHANCED); regcache_cache_bypass(regmaps[i], true); From d9b2d7ddbb973b981c20b21e9228581bb156f66f Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:44 -0400 Subject: [PATCH 13/60] serial: sc16is7xx: rename EFR mutex with generic name This mutex is used as a lock when accessing registers that share the same address space, not necessarily EFR registers. For example, address 0x06 is shared by MSR, TCR and XOFF1 registers, independently of EFR. Rename the mutex with a more generic name to avoid misinterpreting its usage. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-3-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 330d95446f1d..26b34f23ed5f 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -330,7 +330,7 @@ struct sc16is7xx_one_config { struct sc16is7xx_one { struct uart_port port; struct regmap *regmap; - struct mutex efr_lock; /* EFR registers access */ + struct mutex lock; /* For registers sharing same address space. */ struct kthread_work tx_work; struct kthread_work reg_work; struct kthread_delayed_work ms_work; @@ -438,7 +438,7 @@ static void sc16is7xx_efr_lock(struct uart_port *port) { struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); - mutex_lock(&one->efr_lock); + mutex_lock(&one->lock); /* Backup content of LCR. */ one->old_lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG); @@ -460,7 +460,7 @@ static void sc16is7xx_efr_unlock(struct uart_port *port) /* Restore original content of LCR */ sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, one->old_lcr); - mutex_unlock(&one->efr_lock); + mutex_unlock(&one->lock); } static void sc16is7xx_ier_clear(struct uart_port *port, u8 bit) @@ -595,7 +595,7 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud) SC16IS7XX_MCR_CLKSEL_BIT, prescaler == 1 ? 0 : SC16IS7XX_MCR_CLKSEL_BIT); - mutex_lock(&one->efr_lock); + mutex_lock(&one->lock); /* Backup LCR and access special register set (DLL/DLH) */ lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG); @@ -611,7 +611,7 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud) /* Restore LCR and access to general register set */ sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr); - mutex_unlock(&one->efr_lock); + mutex_unlock(&one->lock); return DIV_ROUND_CLOSEST((clk / prescaler) / 16, div); } @@ -758,7 +758,7 @@ static void sc16is7xx_update_mlines(struct sc16is7xx_one *one) unsigned long flags; unsigned int status, changed; - lockdep_assert_held_once(&one->efr_lock); + lockdep_assert_held_once(&one->lock); status = sc16is7xx_get_hwmctrl(port); changed = status ^ one->old_mctrl; @@ -789,7 +789,7 @@ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno) struct uart_port *port = &s->p[portno].port; struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); - mutex_lock(&one->efr_lock); + mutex_lock(&one->lock); iir = sc16is7xx_port_read(port, SC16IS7XX_IIR_REG); if (iir & SC16IS7XX_IIR_NO_INT_BIT) { @@ -836,7 +836,7 @@ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno) } out_port_irq: - mutex_unlock(&one->efr_lock); + mutex_unlock(&one->lock); return rc; } @@ -880,9 +880,9 @@ static void sc16is7xx_tx_proc(struct kthread_work *ws) (port->rs485.delay_rts_before_send > 0)) msleep(port->rs485.delay_rts_before_send); - mutex_lock(&one->efr_lock); + mutex_lock(&one->lock); sc16is7xx_handle_tx(port); - mutex_unlock(&one->efr_lock); + mutex_unlock(&one->lock); } static void sc16is7xx_reconf_rs485(struct uart_port *port) @@ -949,9 +949,9 @@ static void sc16is7xx_ms_proc(struct kthread_work *ws) struct sc16is7xx_port *s = dev_get_drvdata(one->port.dev); if (one->port.state) { - mutex_lock(&one->efr_lock); + mutex_lock(&one->lock); sc16is7xx_update_mlines(one); - mutex_unlock(&one->efr_lock); + mutex_unlock(&one->lock); kthread_queue_delayed_work(&s->kworker, &one->ms_work, HZ); } @@ -1625,7 +1625,7 @@ int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype, s->p[i].old_mctrl = 0; s->p[i].regmap = regmaps[i]; - mutex_init(&s->p[i].efr_lock); + mutex_init(&s->p[i].lock); ret = uart_get_rs485_mode(&s->p[i].port); if (ret) From bc20238ac5189683b5b941dc9276ead9ee86ef43 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:45 -0400 Subject: [PATCH 14/60] serial: sc16is7xx: define common register access function Rename lock/unlock functions to make it more generic and applicable to both the Enhanced register set and the Special register set. Use this new generic function when accessing the Special register set in sc16is7xx_set_baud(), and when accessing the Enhanced register set in sc16is7xx_set_termios() and sc16is7xx_probe(). This helps readability and also avoid to make future mistakes when accessing these obfuscated registers. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-4-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 74 ++++++++++++++-------------------- 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 26b34f23ed5f..72e4c4f80f7f 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -421,20 +421,24 @@ static void sc16is7xx_power(struct uart_port *port, int on) } /* - * In an amazing feat of design, the Enhanced Features Register (EFR) - * shares the address of the Interrupt Identification Register (IIR). - * Access to EFR is switched on by writing a magic value (0xbf) to the - * Line Control Register (LCR). Any interrupt firing during this time will - * see the EFR where it expects the IIR to be, leading to + * In an amazing feat of design, the enhanced register set shares the + * addresses 0x02 and 0x04-0x07 with the general register set. + * The special register set also shares the addresses 0x00-0x01 with the + * general register set. + * + * Access to the enhanced or special register set is enabled by writing a magic + * value to the Line Control Register (LCR). When enhanced register set access + * is enabled, for example, any interrupt firing during this time will see the + * EFR where it expects the IIR to be, leading to * "Unexpected interrupt" messages. * - * Prevent this possibility by claiming a mutex while accessing the EFR, - * and claiming the same mutex from within the interrupt handler. This is - * similar to disabling the interrupt, but that doesn't work because the - * bulk of the interrupt processing is run as a workqueue job in thread - * context. + * Prevent this possibility by claiming a mutex when access to the enhanced + * or special register set is enabled, and claiming the same mutex from within + * the interrupt handler. This is similar to disabling the interrupt, but that + * doesn't work because the bulk of the interrupt processing is run as a + * workqueue job in thread context. */ -static void sc16is7xx_efr_lock(struct uart_port *port) +static void sc16is7xx_regs_lock(struct uart_port *port, u8 register_set) { struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); @@ -443,18 +447,18 @@ static void sc16is7xx_efr_lock(struct uart_port *port) /* Backup content of LCR. */ one->old_lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG); - /* Enable access to Enhanced register set */ - sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, SC16IS7XX_LCR_REG_SET_ENHANCED); + /* Enable access to the desired register set */ + sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, register_set); - /* Disable cache updates when writing to EFR registers */ + /* Disable cache updates when writing to non-general registers */ regcache_cache_bypass(one->regmap, true); } -static void sc16is7xx_efr_unlock(struct uart_port *port) +static void sc16is7xx_regs_unlock(struct uart_port *port) { struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); - /* Re-enable cache updates when writing to normal registers */ + /* Re-enable cache updates when writing to general registers */ regcache_cache_bypass(one->regmap, false); /* Restore original content of LCR */ @@ -580,8 +584,6 @@ static bool sc16is7xx_regmap_noinc(struct device *dev, unsigned int reg) */ static int sc16is7xx_set_baud(struct uart_port *port, int baud) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); - u8 lcr; unsigned int prescaler = 1; unsigned long clk = port->uartclk, div = clk / 16 / baud; @@ -595,23 +597,15 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud) SC16IS7XX_MCR_CLKSEL_BIT, prescaler == 1 ? 0 : SC16IS7XX_MCR_CLKSEL_BIT); - mutex_lock(&one->lock); - - /* Backup LCR and access special register set (DLL/DLH) */ - lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG); - sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, - SC16IS7XX_LCR_REG_SET_SPECIAL); + /* Access special register set (DLL/DLH) */ + sc16is7xx_regs_lock(port, SC16IS7XX_LCR_REG_SET_SPECIAL); /* Write the new divisor */ - regcache_cache_bypass(one->regmap, true); sc16is7xx_port_write(port, SC16IS7XX_DLH_REG, div / 256); sc16is7xx_port_write(port, SC16IS7XX_DLL_REG, div % 256); - regcache_cache_bypass(one->regmap, false); - /* Restore LCR and access to general register set */ - sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr); - - mutex_unlock(&one->lock); + /* Restore access to general register set */ + sc16is7xx_regs_unlock(port); return DIV_ROUND_CLOSEST((clk / prescaler) / 16, div); } @@ -1108,12 +1102,12 @@ static void sc16is7xx_set_termios(struct uart_port *port, sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr); /* Update EFR registers */ - sc16is7xx_efr_lock(port); + sc16is7xx_regs_lock(port, SC16IS7XX_LCR_REG_SET_ENHANCED); sc16is7xx_port_write(port, SC16IS7XX_XON1_REG, termios->c_cc[VSTART]); sc16is7xx_port_write(port, SC16IS7XX_XOFF1_REG, termios->c_cc[VSTOP]); sc16is7xx_port_update(port, SC16IS7XX_EFR_REG, SC16IS7XX_EFR_FLOWCTRL_BITS, flow); - sc16is7xx_efr_unlock(port); + sc16is7xx_regs_unlock(port); /* Get baud rate generator configuration */ baud = uart_get_baud_rate(port, termios, old, @@ -1631,6 +1625,9 @@ int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype, if (ret) goto out_ports; + /* Enable access to general register set */ + sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG, 0x00); + /* Disable all interrupts */ sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_IER_REG, 0); /* Disable TX/RX */ @@ -1650,20 +1647,11 @@ int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype, port_registered[i] = true; - /* Enable EFR */ - sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG, - SC16IS7XX_LCR_REG_SET_ENHANCED); - - regcache_cache_bypass(regmaps[i], true); - + sc16is7xx_regs_lock(&s->p[i].port, SC16IS7XX_LCR_REG_SET_ENHANCED); /* Enable write access to enhanced features */ sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_EFR_REG, SC16IS7XX_EFR_ENABLE_BIT); - - regcache_cache_bypass(regmaps[i], false); - - /* Restore access to general registers */ - sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG, 0x00); + sc16is7xx_regs_unlock(&s->p[i].port); /* Go to suspend mode */ sc16is7xx_power(&s->p[i].port, 0); From aed482b53a866ecb5b46f8936462048584cf9b37 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:46 -0400 Subject: [PATCH 15/60] serial: sc16is7xx: remove unnecessary pointer cast There is no need to cast from a void * pointer, so remove this cast. Also remove empty line inavertently added in commit d5078509c8b0 ("serial: sc16is7xx: improve do/while loop in sc16is7xx_irq()") and change variables order to follow reversed xmas tree. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-5-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 72e4c4f80f7f..335695d0e7aa 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -837,10 +837,9 @@ out_port_irq: static irqreturn_t sc16is7xx_irq(int irq, void *dev_id) { + struct sc16is7xx_port *s = dev_id; bool keep_polling; - struct sc16is7xx_port *s = (struct sc16is7xx_port *)dev_id; - do { int i; From 0f4f88bfd7e7bf3f3293045fffdc63586b0a889f Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:47 -0400 Subject: [PATCH 16/60] serial: sc16is7xx: use guards for simple mutex locks Guards can help to make the code more readable, so use them wherever they do so. In sc16is7xx_port_irq(), labels and 'rc' locals are eliminated completely. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-6-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 335695d0e7aa..1acf5be03d48 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -11,6 +11,7 @@ #define DEFAULT_SYMBOL_NAMESPACE "SERIAL_NXP_SC16IS7XX" #include +#include #include #include #include @@ -778,18 +779,15 @@ static void sc16is7xx_update_mlines(struct sc16is7xx_one *one) static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno) { - bool rc = true; unsigned int iir, rxlen; struct uart_port *port = &s->p[portno].port; struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); - mutex_lock(&one->lock); + guard(mutex)(&one->lock); iir = sc16is7xx_port_read(port, SC16IS7XX_IIR_REG); - if (iir & SC16IS7XX_IIR_NO_INT_BIT) { - rc = false; - goto out_port_irq; - } + if (iir & SC16IS7XX_IIR_NO_INT_BIT) + return false; iir &= SC16IS7XX_IIR_ID_MASK; @@ -829,10 +827,7 @@ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno) break; } -out_port_irq: - mutex_unlock(&one->lock); - - return rc; + return true; } static irqreturn_t sc16is7xx_irq(int irq, void *dev_id) @@ -873,9 +868,8 @@ static void sc16is7xx_tx_proc(struct kthread_work *ws) (port->rs485.delay_rts_before_send > 0)) msleep(port->rs485.delay_rts_before_send); - mutex_lock(&one->lock); + guard(mutex)(&one->lock); sc16is7xx_handle_tx(port); - mutex_unlock(&one->lock); } static void sc16is7xx_reconf_rs485(struct uart_port *port) @@ -942,9 +936,8 @@ static void sc16is7xx_ms_proc(struct kthread_work *ws) struct sc16is7xx_port *s = dev_get_drvdata(one->port.dev); if (one->port.state) { - mutex_lock(&one->lock); - sc16is7xx_update_mlines(one); - mutex_unlock(&one->lock); + scoped_guard(mutex, &one->lock) + sc16is7xx_update_mlines(one); kthread_queue_delayed_work(&s->kworker, &one->ms_work, HZ); } From e0925b4a0417d32189484b19606962c15019645a Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:48 -0400 Subject: [PATCH 17/60] serial: sc16is7xx: drop -ENOMEM error message Core already prints detailed error messages on ENOMEM errors and drivers should avoid repeating it. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-7-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 1acf5be03d48..ce2a0967ffdf 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -1527,10 +1527,8 @@ int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype, /* Alloc port structure */ s = devm_kzalloc(dev, struct_size(s, p, devtype->nr_uart), GFP_KERNEL); - if (!s) { - dev_err(dev, "Error allocating port structure\n"); + if (!s) return -ENOMEM; - } /* Always ask for fixed clock rate from a property. */ device_property_read_u32(dev, "clock-frequency", &uartclk); From 983f91e5f13149ade6dbd9568053ecd0ab5e9265 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:49 -0400 Subject: [PATCH 18/60] serial: sc16is7xx: declare SPR/TLR/XOFF2 register as volatile SPR shares the same address space as TLR and XOFF2. If SPR or TLR were to be used eventually, this could lead to incorrect read value from the cache. Prevent this potential bug by declaring SPR/TLR/XOFF2 as volatile. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-8-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index ce2a0967ffdf..281cbb2274e5 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -546,6 +546,7 @@ static bool sc16is7xx_regmap_volatile(struct device *dev, unsigned int reg) case SC16IS7XX_IIR_REG: case SC16IS7XX_LSR_REG: case SC16IS7XX_MSR_REG: + case SC16IS7XX_SPR_REG: /* Shared address space with TLR & XOFF2 */ case SC16IS7XX_TXLVL_REG: case SC16IS7XX_RXLVL_REG: case SC16IS7XX_IOSTATE_REG: From a5bb146502b22fcfe1eca2c22208be451eff4357 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:50 -0400 Subject: [PATCH 19/60] serial: sc16is7xx: move port/channel init to separate function The sc16is7xx_probe() function is very long. To improve readability, move port/channel initialization to a separate function. No functional change intended. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-9-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 127 ++++++++++++++++++--------------- 1 file changed, 70 insertions(+), 57 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 281cbb2274e5..a7a6d613459d 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -1499,6 +1499,75 @@ static int sc16is7xx_reset(struct device *dev, struct regmap *regmap) return 0; } +static int sc16is7xx_setup_channel(struct sc16is7xx_one *one, int i, + bool *port_registered) +{ + struct uart_port *port = &one->port; + int ret; + + ret = ida_alloc_max(&sc16is7xx_lines, SC16IS7XX_MAX_DEVS - 1, GFP_KERNEL); + if (ret < 0) + return ret; + + port->line = ret; + + /* Initialize port data */ + port->type = PORT_SC16IS7XX; + port->fifosize = SC16IS7XX_FIFO_SIZE; + port->flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; + port->iobase = i; + /* + * Use all ones as membase to make sure uart_configure_port() in + * serial_core.c does not abort for SPI/I2C devices where the + * membase address is not applicable. + */ + port->membase = (void __iomem *)~0; + port->iotype = UPIO_PORT; + port->rs485_config = sc16is7xx_config_rs485; + port->rs485_supported = sc16is7xx_rs485_supported; + port->ops = &sc16is7xx_ops; + one->old_mctrl = 0; + + mutex_init(&one->lock); + + ret = uart_get_rs485_mode(port); + if (ret) + return ret; + + /* Enable access to general register set */ + sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, 0x00); + + /* Disable all interrupts */ + sc16is7xx_port_write(port, SC16IS7XX_IER_REG, 0); + /* Disable TX/RX */ + sc16is7xx_port_write(port, SC16IS7XX_EFCR_REG, + SC16IS7XX_EFCR_RXDISABLE_BIT | + SC16IS7XX_EFCR_TXDISABLE_BIT); + + /* Initialize kthread work structs */ + kthread_init_work(&one->tx_work, sc16is7xx_tx_proc); + kthread_init_work(&one->reg_work, sc16is7xx_reg_proc); + kthread_init_delayed_work(&one->ms_work, sc16is7xx_ms_proc); + + /* Register port */ + ret = uart_add_one_port(&sc16is7xx_uart, port); + if (ret) + return ret; + + *port_registered = true; + + sc16is7xx_regs_lock(port, SC16IS7XX_LCR_REG_SET_ENHANCED); + /* Enable write access to enhanced features */ + sc16is7xx_port_write(port, SC16IS7XX_EFR_REG, + SC16IS7XX_EFR_ENABLE_BIT); + sc16is7xx_regs_unlock(port); + + /* Go to suspend mode */ + sc16is7xx_power(port, 0); + + return 0; +} + int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype, struct regmap *regmaps[], int irq) { @@ -1582,70 +1651,14 @@ int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype, } for (i = 0; i < devtype->nr_uart; ++i) { - ret = ida_alloc_max(&sc16is7xx_lines, - SC16IS7XX_MAX_DEVS - 1, GFP_KERNEL); - if (ret < 0) - goto out_ports; - - s->p[i].port.line = ret; - - /* Initialize port data */ s->p[i].port.dev = dev; s->p[i].port.irq = irq; - s->p[i].port.type = PORT_SC16IS7XX; - s->p[i].port.fifosize = SC16IS7XX_FIFO_SIZE; - s->p[i].port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; - s->p[i].port.iobase = i; - /* - * Use all ones as membase to make sure uart_configure_port() in - * serial_core.c does not abort for SPI/I2C devices where the - * membase address is not applicable. - */ - s->p[i].port.membase = (void __iomem *)~0; - s->p[i].port.iotype = UPIO_PORT; s->p[i].port.uartclk = freq; - s->p[i].port.rs485_config = sc16is7xx_config_rs485; - s->p[i].port.rs485_supported = sc16is7xx_rs485_supported; - s->p[i].port.ops = &sc16is7xx_ops; - s->p[i].old_mctrl = 0; s->p[i].regmap = regmaps[i]; - mutex_init(&s->p[i].lock); - - ret = uart_get_rs485_mode(&s->p[i].port); + ret = sc16is7xx_setup_channel(&s->p[i], i, &port_registered[i]); if (ret) goto out_ports; - - /* Enable access to general register set */ - sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG, 0x00); - - /* Disable all interrupts */ - sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_IER_REG, 0); - /* Disable TX/RX */ - sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_EFCR_REG, - SC16IS7XX_EFCR_RXDISABLE_BIT | - SC16IS7XX_EFCR_TXDISABLE_BIT); - - /* Initialize kthread work structs */ - kthread_init_work(&s->p[i].tx_work, sc16is7xx_tx_proc); - kthread_init_work(&s->p[i].reg_work, sc16is7xx_reg_proc); - kthread_init_delayed_work(&s->p[i].ms_work, sc16is7xx_ms_proc); - - /* Register port */ - ret = uart_add_one_port(&sc16is7xx_uart, &s->p[i].port); - if (ret) - goto out_ports; - - port_registered[i] = true; - - sc16is7xx_regs_lock(&s->p[i].port, SC16IS7XX_LCR_REG_SET_ENHANCED); - /* Enable write access to enhanced features */ - sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_EFR_REG, - SC16IS7XX_EFR_ENABLE_BIT); - sc16is7xx_regs_unlock(&s->p[i].port); - - /* Go to suspend mode */ - sc16is7xx_power(&s->p[i].port, 0); } sc16is7xx_setup_irda_ports(s); From e92f83afaf4d6c7ee553f335bb3ddb53d5880446 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:51 -0400 Subject: [PATCH 20/60] serial: sc16is7xx: simplify to_sc16is7xx_one() with a single parameter Simplify macro to_sc16is7xx_one() to only take one parameter, as most of the time (19) it is called with the "port" structure name. This improves readability. For the remaining places where it is called (4), simply use container_of() locally. This is similar to what is done in other drivers (ex: max310x). For sc16is7xx_tx_proc(), first assigning "one" variable allows to simplify "port" variable init. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-10-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 48 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index a7a6d613459d..c6d4ad8d84d1 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -366,11 +366,11 @@ static struct uart_driver sc16is7xx_uart = { .nr = SC16IS7XX_MAX_DEVS, }; -#define to_sc16is7xx_one(p,e) ((container_of((p), struct sc16is7xx_one, e))) +#define to_sc16is7xx_one(p) container_of((p), struct sc16is7xx_one, port) static u8 sc16is7xx_port_read(struct uart_port *port, u8 reg) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); unsigned int val = 0; regmap_read(one->regmap, reg, &val); @@ -380,21 +380,21 @@ static u8 sc16is7xx_port_read(struct uart_port *port, u8 reg) static void sc16is7xx_port_write(struct uart_port *port, u8 reg, u8 val) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); regmap_write(one->regmap, reg, val); } static void sc16is7xx_fifo_read(struct uart_port *port, u8 *rxbuf, unsigned int rxlen) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); regmap_noinc_read(one->regmap, SC16IS7XX_RHR_REG, rxbuf, rxlen); } static void sc16is7xx_fifo_write(struct uart_port *port, u8 *txbuf, u8 to_send) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); /* * Don't send zero-length data, at least on SPI it confuses the chip @@ -409,7 +409,7 @@ static void sc16is7xx_fifo_write(struct uart_port *port, u8 *txbuf, u8 to_send) static void sc16is7xx_port_update(struct uart_port *port, u8 reg, u8 mask, u8 val) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); regmap_update_bits(one->regmap, reg, mask, val); } @@ -441,7 +441,7 @@ static void sc16is7xx_power(struct uart_port *port, int on) */ static void sc16is7xx_regs_lock(struct uart_port *port, u8 register_set) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); mutex_lock(&one->lock); @@ -457,7 +457,7 @@ static void sc16is7xx_regs_lock(struct uart_port *port, u8 register_set) static void sc16is7xx_regs_unlock(struct uart_port *port) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); /* Re-enable cache updates when writing to general registers */ regcache_cache_bypass(one->regmap, false); @@ -471,7 +471,7 @@ static void sc16is7xx_regs_unlock(struct uart_port *port) static void sc16is7xx_ier_clear(struct uart_port *port, u8 bit) { struct sc16is7xx_port *s = dev_get_drvdata(port->dev); - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); lockdep_assert_held_once(&port->lock); @@ -484,7 +484,7 @@ static void sc16is7xx_ier_clear(struct uart_port *port, u8 bit) static void sc16is7xx_ier_set(struct uart_port *port, u8 bit) { struct sc16is7xx_port *s = dev_get_drvdata(port->dev); - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); lockdep_assert_held_once(&port->lock); @@ -615,7 +615,7 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud) static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen, unsigned int iir) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); unsigned int lsr = 0, bytes_read, i; bool read_lsr = (iir == SC16IS7XX_IIR_RLSE_SRC); u8 ch, flag; @@ -782,7 +782,7 @@ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno) { unsigned int iir, rxlen; struct uart_port *port = &s->p[portno].port; - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); guard(mutex)(&one->lock); @@ -862,8 +862,8 @@ static void sc16is7xx_poll_proc(struct kthread_work *ws) static void sc16is7xx_tx_proc(struct kthread_work *ws) { - struct uart_port *port = &(to_sc16is7xx_one(ws, tx_work)->port); - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = container_of(ws, struct sc16is7xx_one, tx_work); + struct uart_port *port = &one->port; if ((port->rs485.flags & SER_RS485_ENABLED) && (port->rs485.delay_rts_before_send > 0)) @@ -895,7 +895,7 @@ static void sc16is7xx_reconf_rs485(struct uart_port *port) static void sc16is7xx_reg_proc(struct kthread_work *ws) { - struct sc16is7xx_one *one = to_sc16is7xx_one(ws, reg_work); + struct sc16is7xx_one *one = container_of(ws, struct sc16is7xx_one, reg_work); struct sc16is7xx_one_config config; unsigned long irqflags; @@ -933,7 +933,7 @@ static void sc16is7xx_reg_proc(struct kthread_work *ws) static void sc16is7xx_ms_proc(struct kthread_work *ws) { - struct sc16is7xx_one *one = to_sc16is7xx_one(ws, ms_work.work); + struct sc16is7xx_one *one = container_of(ws, struct sc16is7xx_one, ms_work.work); struct sc16is7xx_port *s = dev_get_drvdata(one->port.dev); if (one->port.state) { @@ -946,7 +946,7 @@ static void sc16is7xx_ms_proc(struct kthread_work *ws) static void sc16is7xx_enable_ms(struct uart_port *port) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); struct sc16is7xx_port *s = dev_get_drvdata(port->dev); lockdep_assert_held_once(&port->lock); @@ -957,7 +957,7 @@ static void sc16is7xx_enable_ms(struct uart_port *port) static void sc16is7xx_start_tx(struct uart_port *port) { struct sc16is7xx_port *s = dev_get_drvdata(port->dev); - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); kthread_queue_work(&s->kworker, &one->tx_work); } @@ -996,7 +996,7 @@ static unsigned int sc16is7xx_tx_empty(struct uart_port *port) static unsigned int sc16is7xx_get_mctrl(struct uart_port *port) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); /* Called with port lock taken so we can only return cached value */ return one->old_mctrl; @@ -1005,7 +1005,7 @@ static unsigned int sc16is7xx_get_mctrl(struct uart_port *port) static void sc16is7xx_set_mctrl(struct uart_port *port, unsigned int mctrl) { struct sc16is7xx_port *s = dev_get_drvdata(port->dev); - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); one->config.flags |= SC16IS7XX_RECONF_MD; kthread_queue_work(&s->kworker, &one->reg_work); @@ -1022,7 +1022,7 @@ static void sc16is7xx_set_termios(struct uart_port *port, struct ktermios *termios, const struct ktermios *old) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); unsigned int lcr, flow = 0; int baud; unsigned long flags; @@ -1125,7 +1125,7 @@ static int sc16is7xx_config_rs485(struct uart_port *port, struct ktermios *termi struct serial_rs485 *rs485) { struct sc16is7xx_port *s = dev_get_drvdata(port->dev); - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); if (rs485->flags & SER_RS485_ENABLED) { /* @@ -1145,7 +1145,7 @@ static int sc16is7xx_config_rs485(struct uart_port *port, struct ktermios *termi static int sc16is7xx_startup(struct uart_port *port) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); struct sc16is7xx_port *s = dev_get_drvdata(port->dev); unsigned int val; unsigned long flags; @@ -1209,7 +1209,7 @@ static int sc16is7xx_startup(struct uart_port *port) static void sc16is7xx_shutdown(struct uart_port *port) { struct sc16is7xx_port *s = dev_get_drvdata(port->dev); - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_one *one = to_sc16is7xx_one(port); kthread_cancel_delayed_work_sync(&one->ms_work); From 555d7b3de0057f0cf658f034ab9ee43dd35870f7 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:52 -0400 Subject: [PATCH 21/60] serial: sc16is7xx: Kconfig: allow building with COMPILE_TEST Add COMPILE_TEST as an option to allow test building the driver. Suggested-by: Geert Uytterhoeven Link: https://lore.kernel.org/all/20240604083159.d984dd08741396ea4ca46418@hugovil.com/raw Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-11-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 282116765e64..59221cce0028 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1044,7 +1044,7 @@ config SERIAL_SCCNXP_CONSOLE config SERIAL_SC16IS7XX tristate "NXP SC16IS7xx UART support" - depends on SPI_MASTER || I2C + depends on SPI_MASTER || I2C || COMPILE_TEST select SERIAL_CORE select SERIAL_SC16IS7XX_SPI if SPI_MASTER select SERIAL_SC16IS7XX_I2C if I2C From be1d3aac8fefeec4d3367ac62ea32e4693a759b5 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:53 -0400 Subject: [PATCH 22/60] serial: sc16is7xx: use KBUILD_MODNAME There is no need to redefine the driver name. Use KBUILD_MODNAME and get rid of DRV_NAME altogether. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-12-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 4 ++-- drivers/tty/serial/sc16is7xx.h | 1 - drivers/tty/serial/sc16is7xx_i2c.c | 4 ++-- drivers/tty/serial/sc16is7xx_spi.c | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index c6d4ad8d84d1..644f4e9233db 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -361,7 +361,7 @@ static DEFINE_IDA(sc16is7xx_lines); static struct uart_driver sc16is7xx_uart = { .owner = THIS_MODULE, - .driver_name = SC16IS7XX_NAME, + .driver_name = KBUILD_MODNAME, .dev_name = "ttySC", .nr = SC16IS7XX_MAX_DEVS, }; @@ -1808,4 +1808,4 @@ module_exit(sc16is7xx_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jon Ringle "); -MODULE_DESCRIPTION("SC16IS7xx tty serial core driver"); +MODULE_DESCRIPTION(KBUILD_MODNAME " tty serial core driver"); diff --git a/drivers/tty/serial/sc16is7xx.h b/drivers/tty/serial/sc16is7xx.h index afb784eaee45..9c584d6d3593 100644 --- a/drivers/tty/serial/sc16is7xx.h +++ b/drivers/tty/serial/sc16is7xx.h @@ -8,7 +8,6 @@ #include #include -#define SC16IS7XX_NAME "sc16is7xx" #define SC16IS7XX_MAX_PORTS 2 /* Maximum number of UART ports per IC. */ struct device; diff --git a/drivers/tty/serial/sc16is7xx_i2c.c b/drivers/tty/serial/sc16is7xx_i2c.c index cd7de9e057b8..699376c3b3a5 100644 --- a/drivers/tty/serial/sc16is7xx_i2c.c +++ b/drivers/tty/serial/sc16is7xx_i2c.c @@ -52,7 +52,7 @@ MODULE_DEVICE_TABLE(i2c, sc16is7xx_i2c_id_table); static struct i2c_driver sc16is7xx_i2c_driver = { .driver = { - .name = SC16IS7XX_NAME, + .name = KBUILD_MODNAME, .of_match_table = sc16is7xx_dt_ids, }, .probe = sc16is7xx_i2c_probe, @@ -63,5 +63,5 @@ static struct i2c_driver sc16is7xx_i2c_driver = { module_i2c_driver(sc16is7xx_i2c_driver); MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("SC16IS7xx I2C interface driver"); +MODULE_DESCRIPTION(KBUILD_MODNAME " interface driver"); MODULE_IMPORT_NS("SERIAL_NXP_SC16IS7XX"); diff --git a/drivers/tty/serial/sc16is7xx_spi.c b/drivers/tty/serial/sc16is7xx_spi.c index 20d736b657b1..7e76d0e38da7 100644 --- a/drivers/tty/serial/sc16is7xx_spi.c +++ b/drivers/tty/serial/sc16is7xx_spi.c @@ -75,7 +75,7 @@ MODULE_DEVICE_TABLE(spi, sc16is7xx_spi_id_table); static struct spi_driver sc16is7xx_spi_driver = { .driver = { - .name = SC16IS7XX_NAME, + .name = KBUILD_MODNAME, .of_match_table = sc16is7xx_dt_ids, }, .probe = sc16is7xx_spi_probe, @@ -86,5 +86,5 @@ static struct spi_driver sc16is7xx_spi_driver = { module_spi_driver(sc16is7xx_spi_driver); MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("SC16IS7xx SPI interface driver"); +MODULE_DESCRIPTION(KBUILD_MODNAME " interface driver"); MODULE_IMPORT_NS("SERIAL_NXP_SC16IS7XX"); From b90871cba6eda108d5df88bf9932723b9a445690 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:54 -0400 Subject: [PATCH 23/60] serial: sc16is7xx: change conditional operator indentation Move "?" conditional operator all on same line to improve readability. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-13-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 644f4e9233db..3faf821b8b89 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -1180,8 +1180,7 @@ static int sc16is7xx_startup(struct uart_port *port) /* This bit must be written with LCR[7] = 0 */ sc16is7xx_port_update(port, SC16IS7XX_MCR_REG, SC16IS7XX_MCR_IRDA_BIT, - one->irda_mode ? - SC16IS7XX_MCR_IRDA_BIT : 0); + one->irda_mode ? SC16IS7XX_MCR_IRDA_BIT : 0); /* Enable the Rx and Tx FIFO */ sc16is7xx_port_update(port, SC16IS7XX_EFCR_REG, From bee8828a7628e0056f33753ac21be10f6c969ef2 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:55 -0400 Subject: [PATCH 24/60] serial: sc16is7xx: reformat comments to improve readability Fold some multi-line comments into a single line, taking advantage of the new 100 line length limit to improve readability and to have uniform style across driver. Add missing 's' to SC16IS7XX_MCR_TCRTLR_BIT registers comments. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-14-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 121 +++++++++++---------------------- 1 file changed, 39 insertions(+), 82 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 3faf821b8b89..4898b4235d0d 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -50,18 +50,10 @@ #define SC16IS7XX_SPR_REG (0x07) /* Scratch Pad */ #define SC16IS7XX_TXLVL_REG (0x08) /* TX FIFO level */ #define SC16IS7XX_RXLVL_REG (0x09) /* RX FIFO level */ -#define SC16IS7XX_IODIR_REG (0x0a) /* I/O Direction - * - only on 75x/76x - */ -#define SC16IS7XX_IOSTATE_REG (0x0b) /* I/O State - * - only on 75x/76x - */ -#define SC16IS7XX_IOINTENA_REG (0x0c) /* I/O Interrupt Enable - * - only on 75x/76x - */ -#define SC16IS7XX_IOCONTROL_REG (0x0e) /* I/O Control - * - only on 75x/76x - */ +#define SC16IS7XX_IODIR_REG (0x0a) /* I/O Direction - only on 75x/76x */ +#define SC16IS7XX_IOSTATE_REG (0x0b) /* I/O State - only on 75x/76x */ +#define SC16IS7XX_IOINTENA_REG (0x0c) /* I/O Interrupt Enable - only on 75x/76x */ +#define SC16IS7XX_IOCONTROL_REG (0x0e) /* I/O Control - only on 75x/76x */ #define SC16IS7XX_EFCR_REG (0x0f) /* Extra Features Control */ /* TCR/TLR Register set: Only if ((MCR[2] == 1) && (EFR[4] == 1)) */ @@ -81,12 +73,9 @@ /* IER register bits */ #define SC16IS7XX_IER_RDI_BIT BIT(0) /* Enable RX data interrupt */ -#define SC16IS7XX_IER_THRI_BIT BIT(1) /* Enable TX holding register - * interrupt */ -#define SC16IS7XX_IER_RLSI_BIT BIT(2) /* Enable RX line status - * interrupt */ -#define SC16IS7XX_IER_MSI_BIT BIT(3) /* Enable Modem status - * interrupt */ +#define SC16IS7XX_IER_THRI_BIT BIT(1) /* Enable TX holding register interrupt */ +#define SC16IS7XX_IER_RLSI_BIT BIT(2) /* Enable RX line status interrupt */ +#define SC16IS7XX_IER_MSI_BIT BIT(3) /* Enable Modem status interrupt */ /* IER register bits - write only if (EFR[4] == 1) */ #define SC16IS7XX_IER_SLEEP_BIT BIT(4) /* Enable Sleep mode */ @@ -119,9 +108,8 @@ * - only on 75x/76x */ #define SC16IS7XX_IIR_XOFFI_SRC 0x10 /* Received Xoff */ -#define SC16IS7XX_IIR_CTSRTS_SRC 0x20 /* nCTS,nRTS change of state - * from active (LOW) - * to inactive (HIGH) +#define SC16IS7XX_IIR_CTSRTS_SRC 0x20 /* nCTS,nRTS change of state from active + * (LOW) to inactive (HIGH) */ /* LCR register bits */ #define SC16IS7XX_LCR_LENGTH0_BIT BIT(0) /* Word length bit 0 */ @@ -137,8 +125,7 @@ * * STOP length bit table: * 0 -> 1 stop bit - * 1 -> 1-1.5 stop bits if - * word length is 5, + * 1 -> 1-1.5 stop bits if word length is 5, * 2 stop bits otherwise */ #define SC16IS7XX_LCR_PARITY_BIT BIT(3) /* Parity bit enable */ @@ -150,31 +137,22 @@ #define SC16IS7XX_LCR_WORD_LEN_6 (0x01) #define SC16IS7XX_LCR_WORD_LEN_7 (0x02) #define SC16IS7XX_LCR_WORD_LEN_8 (0x03) -#define SC16IS7XX_LCR_REG_SET_SPECIAL SC16IS7XX_LCR_DLAB_BIT /* Special - * reg set - */ -#define SC16IS7XX_LCR_REG_SET_ENHANCED 0xBF /* Enhanced - * reg set - */ +#define SC16IS7XX_LCR_REG_SET_SPECIAL SC16IS7XX_LCR_DLAB_BIT /* Special reg set */ +#define SC16IS7XX_LCR_REG_SET_ENHANCED 0xBF /* Enhanced reg set */ /* MCR register bits */ -#define SC16IS7XX_MCR_DTR_BIT BIT(0) /* DTR complement - * - only on 75x/76x - */ +#define SC16IS7XX_MCR_DTR_BIT BIT(0) /* DTR complement - only on 75x/76x */ #define SC16IS7XX_MCR_RTS_BIT BIT(1) /* RTS complement */ -#define SC16IS7XX_MCR_TCRTLR_BIT BIT(2) /* TCR/TLR register enable */ +#define SC16IS7XX_MCR_TCRTLR_BIT BIT(2) /* TCR/TLR registers enable */ #define SC16IS7XX_MCR_LOOP_BIT BIT(4) /* Enable loopback test mode */ #define SC16IS7XX_MCR_XONANY_BIT BIT(5) /* Enable Xon Any - * - write enabled - * if (EFR[4] == 1) + * - write enabled if (EFR[4] == 1) */ #define SC16IS7XX_MCR_IRDA_BIT BIT(6) /* Enable IrDA mode - * - write enabled - * if (EFR[4] == 1) + * - write enabled if (EFR[4] == 1) */ #define SC16IS7XX_MCR_CLKSEL_BIT BIT(7) /* Divide clock by 4 - * - write enabled - * if (EFR[4] == 1) + * - write enabled if (EFR[4] == 1) */ /* LSR register bits */ @@ -195,28 +173,19 @@ /* MSR register bits */ #define SC16IS7XX_MSR_DCTS_BIT BIT(0) /* Delta CTS Clear To Send */ -#define SC16IS7XX_MSR_DDSR_BIT BIT(1) /* Delta DSR Data Set Ready - * or (IO4) +#define SC16IS7XX_MSR_DDSR_BIT BIT(1) /* Delta DSR Data Set Ready or (IO4) * - only on 75x/76x */ -#define SC16IS7XX_MSR_DRI_BIT BIT(2) /* Delta RI Ring Indicator - * or (IO7) +#define SC16IS7XX_MSR_DRI_BIT BIT(2) /* Delta RI Ring Indicator or (IO7) * - only on 75x/76x */ -#define SC16IS7XX_MSR_DCD_BIT BIT(3) /* Delta CD Carrier Detect - * or (IO6) +#define SC16IS7XX_MSR_DCD_BIT BIT(3) /* Delta CD Carrier Detect or (IO6) * - only on 75x/76x */ #define SC16IS7XX_MSR_CTS_BIT BIT(4) /* CTS */ -#define SC16IS7XX_MSR_DSR_BIT BIT(5) /* DSR (IO4) - * - only on 75x/76x - */ -#define SC16IS7XX_MSR_RI_BIT BIT(6) /* RI (IO7) - * - only on 75x/76x - */ -#define SC16IS7XX_MSR_CD_BIT BIT(7) /* CD (IO6) - * - only on 75x/76x - */ +#define SC16IS7XX_MSR_DSR_BIT BIT(5) /* DSR (IO4) - only on 75x/76x */ +#define SC16IS7XX_MSR_RI_BIT BIT(6) /* RI (IO7) - only on 75x/76x */ +#define SC16IS7XX_MSR_CD_BIT BIT(7) /* CD (IO6) - only on 75x/76x */ /* * TCR register bits @@ -255,54 +224,42 @@ #define SC16IS7XX_IOCONTROL_SRESET_BIT BIT(3) /* Software Reset */ /* EFCR register bits */ -#define SC16IS7XX_EFCR_9BIT_MODE_BIT BIT(0) /* Enable 9-bit or Multidrop - * mode (RS485) */ +#define SC16IS7XX_EFCR_9BIT_MODE_BIT BIT(0) /* Enable 9-bit or Multidrop mode (RS485) */ #define SC16IS7XX_EFCR_RXDISABLE_BIT BIT(1) /* Disable receiver */ #define SC16IS7XX_EFCR_TXDISABLE_BIT BIT(2) /* Disable transmitter */ #define SC16IS7XX_EFCR_AUTO_RS485_BIT BIT(4) /* Auto RS485 RTS direction */ #define SC16IS7XX_EFCR_RTS_INVERT_BIT BIT(5) /* RTS output inversion */ #define SC16IS7XX_EFCR_IRDA_MODE_BIT BIT(7) /* IrDA mode - * 0 = rate upto 115.2 kbit/s - * - Only 75x/76x - * 1 = rate upto 1.152 Mbit/s - * - Only 76x + * 0 = rate up to 115.2 kbit/s - Only 75x/76x + * 1 = rate up to 1.152 Mbit/s - Only 76x */ /* EFR register bits */ #define SC16IS7XX_EFR_AUTORTS_BIT BIT(6) /* Auto RTS flow ctrl enable */ #define SC16IS7XX_EFR_AUTOCTS_BIT BIT(7) /* Auto CTS flow ctrl enable */ #define SC16IS7XX_EFR_XOFF2_DETECT_BIT BIT(5) /* Enable Xoff2 detection */ -#define SC16IS7XX_EFR_ENABLE_BIT BIT(4) /* Enable enhanced functions - * and writing to IER[7:4], - * FCR[5:4], MCR[7:5] +#define SC16IS7XX_EFR_ENABLE_BIT BIT(4) /* Enable enhanced functions and writing to + * IER[7:4], FCR[5:4], MCR[7:5] */ #define SC16IS7XX_EFR_SWFLOW3_BIT BIT(3) #define SC16IS7XX_EFR_SWFLOW2_BIT BIT(2) /* * SWFLOW bits 3 & 2 table: - * 00 -> no transmitter flow - * control - * 01 -> transmitter generates - * XON2 and XOFF2 - * 10 -> transmitter generates - * XON1 and XOFF1 - * 11 -> transmitter generates - * XON1, XON2, XOFF1 and - * XOFF2 + * 00 -> no transmitter flow control + * 01 -> transmitter generates XON2 and XOFF2 + * 10 -> transmitter generates XON1 and XOFF1 + * 11 -> transmitter generates XON1, XON2, + * XOFF1 and XOFF2 */ #define SC16IS7XX_EFR_SWFLOW1_BIT BIT(1) #define SC16IS7XX_EFR_SWFLOW0_BIT BIT(0) /* * SWFLOW bits 1 & 0 table: - * 00 -> no received flow - * control - * 01 -> receiver compares - * XON2 and XOFF2 - * 10 -> receiver compares - * XON1 and XOFF1 - * 11 -> receiver compares - * XON1, XON2, XOFF1 and - * XOFF2 + * 00 -> no received flow control + * 01 -> receiver compares XON2 and XOFF2 + * 10 -> receiver compares XON1 and XOFF1 + * 11 -> receiver compares XON1, XON2, + * XOFF1 and XOFF2 */ #define SC16IS7XX_EFR_FLOWCTRL_BITS (SC16IS7XX_EFR_AUTORTS_BIT | \ SC16IS7XX_EFR_AUTOCTS_BIT | \ @@ -1152,7 +1109,7 @@ static int sc16is7xx_startup(struct uart_port *port) sc16is7xx_power(port, 1); - /* Reset FIFOs*/ + /* Reset FIFOs */ val = SC16IS7XX_FCR_RXRESET_BIT | SC16IS7XX_FCR_TXRESET_BIT; sc16is7xx_port_write(port, SC16IS7XX_FCR_REG, val); udelay(5); From 42405cb77fd35b5acc0e5a11d74eef68c2afa484 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 27 Oct 2025 10:29:56 -0400 Subject: [PATCH 25/60] serial: sc16is7xx: add comments for lock requirements Indicate why lock needs to be asserted when accessing MSR register, as this is not immediately obvious when looking at this register in the device datasheet. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20251027142957.1032073-15-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 4898b4235d0d..1fd64a47341d 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -499,10 +499,10 @@ EXPORT_SYMBOL_GPL(sc16is762_devtype); static bool sc16is7xx_regmap_volatile(struct device *dev, unsigned int reg) { switch (reg) { - case SC16IS7XX_RHR_REG: - case SC16IS7XX_IIR_REG: - case SC16IS7XX_LSR_REG: - case SC16IS7XX_MSR_REG: + case SC16IS7XX_RHR_REG: /* Shared address space with THR & DLL */ + case SC16IS7XX_IIR_REG: /* Shared address space with FCR & EFR */ + case SC16IS7XX_LSR_REG: /* Shared address space with XON2 */ + case SC16IS7XX_MSR_REG: /* Shared address space with TCR & XOFF1 */ case SC16IS7XX_SPR_REG: /* Shared address space with TLR & XOFF2 */ case SC16IS7XX_TXLVL_REG: case SC16IS7XX_RXLVL_REG: @@ -711,6 +711,7 @@ static void sc16is7xx_update_mlines(struct sc16is7xx_one *one) unsigned long flags; unsigned int status, changed; + /* Lock required as MSR address is shared with TCR and XOFF1. */ lockdep_assert_held_once(&one->lock); status = sc16is7xx_get_hwmctrl(port); From 8e2c0a9f12edb7f05b3b60f02e03dc319ba7ccbf Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Thu, 23 Oct 2025 11:43:12 +0100 Subject: [PATCH 26/60] serial: sh-sci: Sort include files alphabetically Sort the include lines alphabetically, no impact on code behavior. Signed-off-by: Lad Prabhakar Link: https://patch.msgid.link/20251023104313.210989-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sh-sci.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 62bb62b82cbe..125a56d47924 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -19,11 +19,11 @@ #include #include -#include #include +#include #include -#include #include +#include #include #include #include @@ -32,8 +32,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -50,14 +50,14 @@ #include #ifdef CONFIG_SUPERH -#include #include +#include #endif #include "rsci.h" #include "serial_mctrl_gpio.h" -#include "sh-sci.h" #include "sh-sci-common.h" +#include "sh-sci.h" #define SCIx_IRQ_IS_MUXED(port) \ ((port)->irqs[SCIx_ERI_IRQ] == \ From 719f3df3e113e03d2c8cf324827da1fd17a9bd8f Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Thu, 23 Oct 2025 11:43:13 +0100 Subject: [PATCH 27/60] serial: sh-sci: Merge sh-sci.h into sh-sci.c Inline the contents of sh-sci.h into sh-sci.c and remove the header file. The header only contained register definitions and macros used exclusively by the sh-sci driver, making the separate header unnecessary. Signed-off-by: Lad Prabhakar Link: https://patch.msgid.link/20251023104313.210989-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sh-sci.c | 176 ++++++++++++++++++++++++++++++++++- drivers/tty/serial/sh-sci.h | 178 ------------------------------------ 2 files changed, 175 insertions(+), 179 deletions(-) delete mode 100644 drivers/tty/serial/sh-sci.h diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 125a56d47924..b28711eeab71 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -17,6 +17,7 @@ */ #undef DEBUG +#include #include #include #include @@ -28,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -57,7 +60,178 @@ #include "rsci.h" #include "serial_mctrl_gpio.h" #include "sh-sci-common.h" -#include "sh-sci.h" + +#define SCI_MAJOR 204 +#define SCI_MINOR_START 8 + +/* + * SCI register subset common for all port types. + * Not all registers will exist on all parts. + */ +enum { + SCSMR, /* Serial Mode Register */ + SCBRR, /* Bit Rate Register */ + SCSCR, /* Serial Control Register */ + SCxSR, /* Serial Status Register */ + SCFCR, /* FIFO Control Register */ + SCFDR, /* FIFO Data Count Register */ + SCxTDR, /* Transmit (FIFO) Data Register */ + SCxRDR, /* Receive (FIFO) Data Register */ + SCLSR, /* Line Status Register */ + SCTFDR, /* Transmit FIFO Data Count Register */ + SCRFDR, /* Receive FIFO Data Count Register */ + SCSPTR, /* Serial Port Register */ + HSSRR, /* Sampling Rate Register */ + SCPCR, /* Serial Port Control Register */ + SCPDR, /* Serial Port Data Register */ + SCDL, /* BRG Frequency Division Register */ + SCCKS, /* BRG Clock Select Register */ + HSRTRGR, /* Rx FIFO Data Count Trigger Register */ + HSTTRGR, /* Tx FIFO Data Count Trigger Register */ + SEMR, /* Serial extended mode register */ +}; + +/* SCSMR (Serial Mode Register) */ +#define SCSMR_C_A BIT(7) /* Communication Mode */ +#define SCSMR_CSYNC BIT(7) /* - Clocked synchronous mode */ +#define SCSMR_ASYNC 0 /* - Asynchronous mode */ +#define SCSMR_CHR BIT(6) /* 7-bit Character Length */ +#define SCSMR_PE BIT(5) /* Parity Enable */ +#define SCSMR_ODD BIT(4) /* Odd Parity */ +#define SCSMR_STOP BIT(3) /* Stop Bit Length */ +#define SCSMR_CKS 0x0003 /* Clock Select */ + +/* Serial Mode Register, SCIFA/SCIFB only bits */ +#define SCSMR_CKEDG BIT(12) /* Transmit/Receive Clock Edge Select */ +#define SCSMR_SRC_MASK 0x0700 /* Sampling Control */ +#define SCSMR_SRC_16 0x0000 /* Sampling rate 1/16 */ +#define SCSMR_SRC_5 0x0100 /* Sampling rate 1/5 */ +#define SCSMR_SRC_7 0x0200 /* Sampling rate 1/7 */ +#define SCSMR_SRC_11 0x0300 /* Sampling rate 1/11 */ +#define SCSMR_SRC_13 0x0400 /* Sampling rate 1/13 */ +#define SCSMR_SRC_17 0x0500 /* Sampling rate 1/17 */ +#define SCSMR_SRC_19 0x0600 /* Sampling rate 1/19 */ +#define SCSMR_SRC_27 0x0700 /* Sampling rate 1/27 */ + +/* Serial Control Register, SCI only bits */ +#define SCSCR_TEIE BIT(2) /* Transmit End Interrupt Enable */ + +/* Serial Control Register, SCIFA/SCIFB only bits */ +#define SCSCR_TDRQE BIT(15) /* Tx Data Transfer Request Enable */ +#define SCSCR_RDRQE BIT(14) /* Rx Data Transfer Request Enable */ + +/* Serial Control Register, HSCIF-only bits */ +#define HSSCR_TOT_SHIFT 14 + +/* SCxSR (Serial Status Register) on SCI */ +#define SCI_TDRE BIT(7) /* Transmit Data Register Empty */ +#define SCI_RDRF BIT(6) /* Receive Data Register Full */ +#define SCI_ORER BIT(5) /* Overrun Error */ +#define SCI_FER BIT(4) /* Framing Error */ +#define SCI_PER BIT(3) /* Parity Error */ +#define SCI_TEND BIT(2) /* Transmit End */ +#define SCI_RESERVED 0x03 /* All reserved bits */ + +#define SCI_DEFAULT_ERROR_MASK (SCI_PER | SCI_FER) + +#define SCI_RDxF_CLEAR (u32)(~(SCI_RESERVED | SCI_RDRF)) +#define SCI_ERROR_CLEAR (u32)(~(SCI_RESERVED | SCI_PER | SCI_FER | SCI_ORER)) +#define SCI_TDxE_CLEAR (u32)(~(SCI_RESERVED | SCI_TEND | SCI_TDRE)) +#define SCI_BREAK_CLEAR (u32)(~(SCI_RESERVED | SCI_PER | SCI_FER | SCI_ORER)) + +/* SCxSR (Serial Status Register) on SCIF, SCIFA, SCIFB, HSCIF */ +#define SCIF_ER BIT(7) /* Receive Error */ +#define SCIF_TEND BIT(6) /* Transmission End */ +#define SCIF_TDFE BIT(5) /* Transmit FIFO Data Empty */ +#define SCIF_BRK BIT(4) /* Break Detect */ +#define SCIF_FER BIT(3) /* Framing Error */ +#define SCIF_PER BIT(2) /* Parity Error */ +#define SCIF_RDF BIT(1) /* Receive FIFO Data Full */ +#define SCIF_DR BIT(0) /* Receive Data Ready */ +/* SCIF only (optional) */ +#define SCIF_PERC 0xf000 /* Number of Parity Errors */ +#define SCIF_FERC 0x0f00 /* Number of Framing Errors */ +/*SCIFA/SCIFB and SCIF on SH7705/SH7720/SH7721 only */ +#define SCIFA_ORER BIT(9) /* Overrun Error */ + +#define SCIF_DEFAULT_ERROR_MASK (SCIF_PER | SCIF_FER | SCIF_BRK | SCIF_ER) + +#define SCIF_RDxF_CLEAR (u32)(~(SCIF_DR | SCIF_RDF)) +#define SCIF_ERROR_CLEAR (u32)(~(SCIF_PER | SCIF_FER | SCIF_ER)) +#define SCIF_TDxE_CLEAR (u32)(~(SCIF_TDFE)) +#define SCIF_BREAK_CLEAR (u32)(~(SCIF_PER | SCIF_FER | SCIF_BRK)) + +/* SCFCR (FIFO Control Register) */ +#define SCFCR_RTRG1 BIT(7) /* Receive FIFO Data Count Trigger */ +#define SCFCR_RTRG0 BIT(6) +#define SCFCR_TTRG1 BIT(5) /* Transmit FIFO Data Count Trigger */ +#define SCFCR_TTRG0 BIT(4) +#define SCFCR_MCE BIT(3) /* Modem Control Enable */ +#define SCFCR_TFRST BIT(2) /* Transmit FIFO Data Register Reset */ +#define SCFCR_RFRST BIT(1) /* Receive FIFO Data Register Reset */ +#define SCFCR_LOOP BIT(0) /* Loopback Test */ + +/* SCLSR (Line Status Register) on (H)SCIF */ +#define SCLSR_TO BIT(2) /* Timeout */ +#define SCLSR_ORER BIT(0) /* Overrun Error */ + +/* SCSPTR (Serial Port Register), optional */ +#define SCSPTR_RTSIO BIT(7) /* Serial Port RTS# Pin Input/Output */ +#define SCSPTR_RTSDT BIT(6) /* Serial Port RTS# Pin Data */ +#define SCSPTR_CTSIO BIT(5) /* Serial Port CTS# Pin Input/Output */ +#define SCSPTR_CTSDT BIT(4) /* Serial Port CTS# Pin Data */ +#define SCSPTR_SCKIO BIT(3) /* Serial Port Clock Pin Input/Output */ +#define SCSPTR_SCKDT BIT(2) /* Serial Port Clock Pin Data */ +#define SCSPTR_SPB2IO BIT(1) /* Serial Port Break Input/Output */ +#define SCSPTR_SPB2DT BIT(0) /* Serial Port Break Data */ + +/* HSSRR HSCIF */ +#define HSCIF_SRE BIT(15) /* Sampling Rate Register Enable */ +#define HSCIF_SRDE BIT(14) /* Sampling Point Register Enable */ + +#define HSCIF_SRHP_SHIFT 8 +#define HSCIF_SRHP_MASK 0x0f00 + +/* SCPCR (Serial Port Control Register), SCIFA/SCIFB only */ +#define SCPCR_RTSC BIT(4) /* Serial Port RTS# Pin / Output Pin */ +#define SCPCR_CTSC BIT(3) /* Serial Port CTS# Pin / Input Pin */ +#define SCPCR_SCKC BIT(2) /* Serial Port SCK Pin / Output Pin */ +#define SCPCR_RXDC BIT(1) /* Serial Port RXD Pin / Input Pin */ +#define SCPCR_TXDC BIT(0) /* Serial Port TXD Pin / Output Pin */ + +/* SCPDR (Serial Port Data Register), SCIFA/SCIFB only */ +#define SCPDR_RTSD BIT(4) /* Serial Port RTS# Output Pin Data */ +#define SCPDR_CTSD BIT(3) /* Serial Port CTS# Input Pin Data */ +#define SCPDR_SCKD BIT(2) /* Serial Port SCK Output Pin Data */ +#define SCPDR_RXDD BIT(1) /* Serial Port RXD Input Pin Data */ +#define SCPDR_TXDD BIT(0) /* Serial Port TXD Output Pin Data */ + +/* + * BRG Clock Select Register (Some SCIF and HSCIF) + * The Baud Rate Generator for external clock can provide a clock source for + * the sampling clock. It outputs either its frequency divided clock, or the + * (undivided) (H)SCK external clock. + */ +#define SCCKS_CKS BIT(15) /* Select (H)SCK (1) or divided SC_CLK (0) */ +#define SCCKS_XIN BIT(14) /* SC_CLK uses bus clock (1) or SCIF_CLK (0) */ + +#define SCxSR_TEND(port) (((port)->type == PORT_SCI) ? SCI_TEND : SCIF_TEND) +#define SCxSR_RDxF(port) (((port)->type == PORT_SCI) ? SCI_RDRF : SCIF_DR | SCIF_RDF) +#define SCxSR_TDxE(port) (((port)->type == PORT_SCI) ? SCI_TDRE : SCIF_TDFE) +#define SCxSR_FER(port) (((port)->type == PORT_SCI) ? SCI_FER : SCIF_FER) +#define SCxSR_PER(port) (((port)->type == PORT_SCI) ? SCI_PER : SCIF_PER) +#define SCxSR_BRK(port) (((port)->type == PORT_SCI) ? 0x00 : SCIF_BRK) + +#define SCxSR_ERRORS(port) (to_sci_port(port)->params->error_mask) + +#define SCxSR_RDxF_CLEAR(port) \ + (((port)->type == PORT_SCI) ? SCI_RDxF_CLEAR : SCIF_RDxF_CLEAR) +#define SCxSR_ERROR_CLEAR(port) \ + (to_sci_port(port)->params->error_clear) +#define SCxSR_TDxE_CLEAR(port) \ + (((port)->type == PORT_SCI) ? SCI_TDxE_CLEAR : SCIF_TDxE_CLEAR) +#define SCxSR_BREAK_CLEAR(port) \ + (((port)->type == PORT_SCI) ? SCI_BREAK_CLEAR : SCIF_BREAK_CLEAR) #define SCIx_IRQ_IS_MUXED(port) \ ((port)->irqs[SCIx_ERI_IRQ] == \ diff --git a/drivers/tty/serial/sh-sci.h b/drivers/tty/serial/sh-sci.h deleted file mode 100644 index 951681aba586..000000000000 --- a/drivers/tty/serial/sh-sci.h +++ /dev/null @@ -1,178 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#include -#include -#include - -#define SCI_MAJOR 204 -#define SCI_MINOR_START 8 - - -/* - * SCI register subset common for all port types. - * Not all registers will exist on all parts. - */ -enum { - SCSMR, /* Serial Mode Register */ - SCBRR, /* Bit Rate Register */ - SCSCR, /* Serial Control Register */ - SCxSR, /* Serial Status Register */ - SCFCR, /* FIFO Control Register */ - SCFDR, /* FIFO Data Count Register */ - SCxTDR, /* Transmit (FIFO) Data Register */ - SCxRDR, /* Receive (FIFO) Data Register */ - SCLSR, /* Line Status Register */ - SCTFDR, /* Transmit FIFO Data Count Register */ - SCRFDR, /* Receive FIFO Data Count Register */ - SCSPTR, /* Serial Port Register */ - HSSRR, /* Sampling Rate Register */ - SCPCR, /* Serial Port Control Register */ - SCPDR, /* Serial Port Data Register */ - SCDL, /* BRG Frequency Division Register */ - SCCKS, /* BRG Clock Select Register */ - HSRTRGR, /* Rx FIFO Data Count Trigger Register */ - HSTTRGR, /* Tx FIFO Data Count Trigger Register */ - SEMR, /* Serial extended mode register */ -}; - - -/* SCSMR (Serial Mode Register) */ -#define SCSMR_C_A BIT(7) /* Communication Mode */ -#define SCSMR_CSYNC BIT(7) /* - Clocked synchronous mode */ -#define SCSMR_ASYNC 0 /* - Asynchronous mode */ -#define SCSMR_CHR BIT(6) /* 7-bit Character Length */ -#define SCSMR_PE BIT(5) /* Parity Enable */ -#define SCSMR_ODD BIT(4) /* Odd Parity */ -#define SCSMR_STOP BIT(3) /* Stop Bit Length */ -#define SCSMR_CKS 0x0003 /* Clock Select */ - -/* Serial Mode Register, SCIFA/SCIFB only bits */ -#define SCSMR_CKEDG BIT(12) /* Transmit/Receive Clock Edge Select */ -#define SCSMR_SRC_MASK 0x0700 /* Sampling Control */ -#define SCSMR_SRC_16 0x0000 /* Sampling rate 1/16 */ -#define SCSMR_SRC_5 0x0100 /* Sampling rate 1/5 */ -#define SCSMR_SRC_7 0x0200 /* Sampling rate 1/7 */ -#define SCSMR_SRC_11 0x0300 /* Sampling rate 1/11 */ -#define SCSMR_SRC_13 0x0400 /* Sampling rate 1/13 */ -#define SCSMR_SRC_17 0x0500 /* Sampling rate 1/17 */ -#define SCSMR_SRC_19 0x0600 /* Sampling rate 1/19 */ -#define SCSMR_SRC_27 0x0700 /* Sampling rate 1/27 */ - -/* Serial Control Register, SCI only bits */ -#define SCSCR_TEIE BIT(2) /* Transmit End Interrupt Enable */ - -/* Serial Control Register, SCIFA/SCIFB only bits */ -#define SCSCR_TDRQE BIT(15) /* Tx Data Transfer Request Enable */ -#define SCSCR_RDRQE BIT(14) /* Rx Data Transfer Request Enable */ - -/* Serial Control Register, HSCIF-only bits */ -#define HSSCR_TOT_SHIFT 14 - -/* SCxSR (Serial Status Register) on SCI */ -#define SCI_TDRE BIT(7) /* Transmit Data Register Empty */ -#define SCI_RDRF BIT(6) /* Receive Data Register Full */ -#define SCI_ORER BIT(5) /* Overrun Error */ -#define SCI_FER BIT(4) /* Framing Error */ -#define SCI_PER BIT(3) /* Parity Error */ -#define SCI_TEND BIT(2) /* Transmit End */ -#define SCI_RESERVED 0x03 /* All reserved bits */ - -#define SCI_DEFAULT_ERROR_MASK (SCI_PER | SCI_FER) - -#define SCI_RDxF_CLEAR (u32)(~(SCI_RESERVED | SCI_RDRF)) -#define SCI_ERROR_CLEAR (u32)(~(SCI_RESERVED | SCI_PER | SCI_FER | SCI_ORER)) -#define SCI_TDxE_CLEAR (u32)(~(SCI_RESERVED | SCI_TEND | SCI_TDRE)) -#define SCI_BREAK_CLEAR (u32)(~(SCI_RESERVED | SCI_PER | SCI_FER | SCI_ORER)) - -/* SCxSR (Serial Status Register) on SCIF, SCIFA, SCIFB, HSCIF */ -#define SCIF_ER BIT(7) /* Receive Error */ -#define SCIF_TEND BIT(6) /* Transmission End */ -#define SCIF_TDFE BIT(5) /* Transmit FIFO Data Empty */ -#define SCIF_BRK BIT(4) /* Break Detect */ -#define SCIF_FER BIT(3) /* Framing Error */ -#define SCIF_PER BIT(2) /* Parity Error */ -#define SCIF_RDF BIT(1) /* Receive FIFO Data Full */ -#define SCIF_DR BIT(0) /* Receive Data Ready */ -/* SCIF only (optional) */ -#define SCIF_PERC 0xf000 /* Number of Parity Errors */ -#define SCIF_FERC 0x0f00 /* Number of Framing Errors */ -/*SCIFA/SCIFB and SCIF on SH7705/SH7720/SH7721 only */ -#define SCIFA_ORER BIT(9) /* Overrun Error */ - -#define SCIF_DEFAULT_ERROR_MASK (SCIF_PER | SCIF_FER | SCIF_BRK | SCIF_ER) - -#define SCIF_RDxF_CLEAR (u32)(~(SCIF_DR | SCIF_RDF)) -#define SCIF_ERROR_CLEAR (u32)(~(SCIF_PER | SCIF_FER | SCIF_ER)) -#define SCIF_TDxE_CLEAR (u32)(~(SCIF_TDFE)) -#define SCIF_BREAK_CLEAR (u32)(~(SCIF_PER | SCIF_FER | SCIF_BRK)) - -/* SCFCR (FIFO Control Register) */ -#define SCFCR_RTRG1 BIT(7) /* Receive FIFO Data Count Trigger */ -#define SCFCR_RTRG0 BIT(6) -#define SCFCR_TTRG1 BIT(5) /* Transmit FIFO Data Count Trigger */ -#define SCFCR_TTRG0 BIT(4) -#define SCFCR_MCE BIT(3) /* Modem Control Enable */ -#define SCFCR_TFRST BIT(2) /* Transmit FIFO Data Register Reset */ -#define SCFCR_RFRST BIT(1) /* Receive FIFO Data Register Reset */ -#define SCFCR_LOOP BIT(0) /* Loopback Test */ - -/* SCLSR (Line Status Register) on (H)SCIF */ -#define SCLSR_TO BIT(2) /* Timeout */ -#define SCLSR_ORER BIT(0) /* Overrun Error */ - -/* SCSPTR (Serial Port Register), optional */ -#define SCSPTR_RTSIO BIT(7) /* Serial Port RTS# Pin Input/Output */ -#define SCSPTR_RTSDT BIT(6) /* Serial Port RTS# Pin Data */ -#define SCSPTR_CTSIO BIT(5) /* Serial Port CTS# Pin Input/Output */ -#define SCSPTR_CTSDT BIT(4) /* Serial Port CTS# Pin Data */ -#define SCSPTR_SCKIO BIT(3) /* Serial Port Clock Pin Input/Output */ -#define SCSPTR_SCKDT BIT(2) /* Serial Port Clock Pin Data */ -#define SCSPTR_SPB2IO BIT(1) /* Serial Port Break Input/Output */ -#define SCSPTR_SPB2DT BIT(0) /* Serial Port Break Data */ - -/* HSSRR HSCIF */ -#define HSCIF_SRE BIT(15) /* Sampling Rate Register Enable */ -#define HSCIF_SRDE BIT(14) /* Sampling Point Register Enable */ - -#define HSCIF_SRHP_SHIFT 8 -#define HSCIF_SRHP_MASK 0x0f00 - -/* SCPCR (Serial Port Control Register), SCIFA/SCIFB only */ -#define SCPCR_RTSC BIT(4) /* Serial Port RTS# Pin / Output Pin */ -#define SCPCR_CTSC BIT(3) /* Serial Port CTS# Pin / Input Pin */ -#define SCPCR_SCKC BIT(2) /* Serial Port SCK Pin / Output Pin */ -#define SCPCR_RXDC BIT(1) /* Serial Port RXD Pin / Input Pin */ -#define SCPCR_TXDC BIT(0) /* Serial Port TXD Pin / Output Pin */ - -/* SCPDR (Serial Port Data Register), SCIFA/SCIFB only */ -#define SCPDR_RTSD BIT(4) /* Serial Port RTS# Output Pin Data */ -#define SCPDR_CTSD BIT(3) /* Serial Port CTS# Input Pin Data */ -#define SCPDR_SCKD BIT(2) /* Serial Port SCK Output Pin Data */ -#define SCPDR_RXDD BIT(1) /* Serial Port RXD Input Pin Data */ -#define SCPDR_TXDD BIT(0) /* Serial Port TXD Output Pin Data */ - -/* - * BRG Clock Select Register (Some SCIF and HSCIF) - * The Baud Rate Generator for external clock can provide a clock source for - * the sampling clock. It outputs either its frequency divided clock, or the - * (undivided) (H)SCK external clock. - */ -#define SCCKS_CKS BIT(15) /* Select (H)SCK (1) or divided SC_CLK (0) */ -#define SCCKS_XIN BIT(14) /* SC_CLK uses bus clock (1) or SCIF_CLK (0) */ - -#define SCxSR_TEND(port) (((port)->type == PORT_SCI) ? SCI_TEND : SCIF_TEND) -#define SCxSR_RDxF(port) (((port)->type == PORT_SCI) ? SCI_RDRF : SCIF_DR | SCIF_RDF) -#define SCxSR_TDxE(port) (((port)->type == PORT_SCI) ? SCI_TDRE : SCIF_TDFE) -#define SCxSR_FER(port) (((port)->type == PORT_SCI) ? SCI_FER : SCIF_FER) -#define SCxSR_PER(port) (((port)->type == PORT_SCI) ? SCI_PER : SCIF_PER) -#define SCxSR_BRK(port) (((port)->type == PORT_SCI) ? 0x00 : SCIF_BRK) - -#define SCxSR_ERRORS(port) (to_sci_port(port)->params->error_mask) - -#define SCxSR_RDxF_CLEAR(port) \ - (((port)->type == PORT_SCI) ? SCI_RDxF_CLEAR : SCIF_RDxF_CLEAR) -#define SCxSR_ERROR_CLEAR(port) \ - (to_sci_port(port)->params->error_clear) -#define SCxSR_TDxE_CLEAR(port) \ - (((port)->type == PORT_SCI) ? SCI_TDxE_CLEAR : SCIF_TDxE_CLEAR) -#define SCxSR_BREAK_CLEAR(port) \ - (((port)->type == PORT_SCI) ? SCI_BREAK_CLEAR : SCIF_BREAK_CLEAR) From 6c84a61ac023d581b4b9fcfa44532eaf15946f56 Mon Sep 17 00:00:00 2001 From: David Laight Date: Wed, 19 Nov 2025 22:41:23 +0000 Subject: [PATCH 28/60] drivers/tty/vt: use umin() instead of min_t(u16, ...) for row/col limits The row/column bounds (for a screen window box) are changed from 'offset one' to 'offset zero' and bound to the screen size using: v->xs = min_t(u16, v->xs - 1, vc->vc_cols - 1); This has the side effect of converting zero to the limit. A check I'm adding to min_t() reports that (u16)(v->xs - 1) (etc) discards signiticant bits (because v->xs is promoted to 'int' before the addition). If v->xs is zero (it comes from userspace) it converts -1 to 0xffff. This is then bounded to 'vc->vc_cols - 1' which will be fine. Replace with: v->xs = umin(v->xs - 1, vc->vc_cols - 1); which again converts a -1 to unsigned - this time to 0xffffffff, with the same overall effect. Whether zero is meant to mean the 'maximum size' is unknown. I can't find any documentation for the ioctl and it pre-dates git. Detected by an extra check added to min_t(). Signed-off-by: David Laight Reviewed-by: Jiri Slaby Link: https://patch.msgid.link/20251119224140.8616-28-david.laight.linux@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/selection.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c index 07d3b93975d3..13f4e48b4142 100644 --- a/drivers/tty/vt/selection.c +++ b/drivers/tty/vt/selection.c @@ -348,10 +348,11 @@ static int vc_selection(struct vc_data *vc, struct tiocl_selection *v, return 0; } - v->xs = min_t(u16, v->xs - 1, vc->vc_cols - 1); - v->ys = min_t(u16, v->ys - 1, vc->vc_rows - 1); - v->xe = min_t(u16, v->xe - 1, vc->vc_cols - 1); - v->ye = min_t(u16, v->ye - 1, vc->vc_rows - 1); + /* Historically 0 => max value */ + v->xs = umin(v->xs - 1, vc->vc_cols - 1); + v->ys = umin(v->ys - 1, vc->vc_rows - 1); + v->xe = umin(v->xe - 1, vc->vc_cols - 1); + v->ye = umin(v->ye - 1, vc->vc_rows - 1); if (mouse_reporting() && (v->sel_mode & TIOCL_SELMOUSEREPORT)) { mouse_report(tty, v->sel_mode & TIOCL_SELBUTTONMASK, v->xs, From 977e75909db72e3ee70483a21aec7d745eef02b7 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 11:01:31 +0100 Subject: [PATCH 29/60] tty: pty: use guard()s Use guards in the pty code. This improves readability, makes error handling easier, and marks locked portions of code explicit. All that while being sure the lock is unlocked. pty_set_pktmode() is handled specially -- the conditions are inverted and return called if conditions unmet. This avoid double nested 'if's. The variable is renamed to want_pktmode so it is not confused with the current state of pktmode. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119100140.830761-2-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/pty.c | 103 ++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index 8bb1a01fef2a..76188b8f3ba3 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c @@ -57,9 +57,8 @@ static void pty_close(struct tty_struct *tty, struct file *filp) set_bit(TTY_IO_ERROR, &tty->flags); wake_up_interruptible(&tty->read_wait); wake_up_interruptible(&tty->write_wait); - spin_lock_irq(&tty->ctrl.lock); - tty->ctrl.packet = false; - spin_unlock_irq(&tty->ctrl.lock); + scoped_guard(spinlock_irq, &tty->ctrl.lock) + tty->ctrl.packet = false; /* Review - krefs on tty_link ?? */ if (!tty->link) return; @@ -70,10 +69,9 @@ static void pty_close(struct tty_struct *tty, struct file *filp) set_bit(TTY_OTHER_CLOSED, &tty->flags); #ifdef CONFIG_UNIX98_PTYS if (tty->driver == ptm_driver) { - mutex_lock(&devpts_mutex); + guard(mutex)(&devpts_mutex); if (tty->link->driver_data) devpts_pty_kill(tty->link->driver_data); - mutex_unlock(&devpts_mutex); } #endif tty_vhangup(tty->link); @@ -157,21 +155,23 @@ static int pty_get_lock(struct tty_struct *tty, int __user *arg) /* Set the packet mode on a pty */ static int pty_set_pktmode(struct tty_struct *tty, int __user *arg) { - int pktmode; + int want_pktmode; - if (get_user(pktmode, arg)) + if (get_user(want_pktmode, arg)) return -EFAULT; - spin_lock_irq(&tty->ctrl.lock); - if (pktmode) { - if (!tty->ctrl.packet) { - tty->link->ctrl.pktstatus = 0; - smp_mb(); - tty->ctrl.packet = true; - } - } else + guard(spinlock_irq)(&tty->ctrl.lock); + if (!want_pktmode) { tty->ctrl.packet = false; - spin_unlock_irq(&tty->ctrl.lock); + return 0; + } + + if (tty->ctrl.packet) + return 0; + + tty->link->ctrl.pktstatus = 0; + smp_mb(); + tty->ctrl.packet = true; return 0; } @@ -210,10 +210,9 @@ static void pty_flush_buffer(struct tty_struct *tty) tty_buffer_flush(to, NULL); if (to->ctrl.packet) { - spin_lock_irq(&tty->ctrl.lock); + guard(spinlock_irq)(&tty->ctrl.lock); tty->ctrl.pktstatus |= TIOCPKT_FLUSHWRITE; wake_up_interruptible(&to->read_wait); - spin_unlock_irq(&tty->ctrl.lock); } } @@ -252,17 +251,17 @@ static void pty_set_termios(struct tty_struct *tty, STOP_CHAR(tty) == '\023' && START_CHAR(tty) == '\021'); if ((old_flow != new_flow) || extproc) { - spin_lock_irq(&tty->ctrl.lock); - if (old_flow != new_flow) { - tty->ctrl.pktstatus &= ~(TIOCPKT_DOSTOP | TIOCPKT_NOSTOP); - if (new_flow) - tty->ctrl.pktstatus |= TIOCPKT_DOSTOP; - else - tty->ctrl.pktstatus |= TIOCPKT_NOSTOP; + scoped_guard(spinlock_irq, &tty->ctrl.lock) { + if (old_flow != new_flow) { + tty->ctrl.pktstatus &= ~(TIOCPKT_DOSTOP | TIOCPKT_NOSTOP); + if (new_flow) + tty->ctrl.pktstatus |= TIOCPKT_DOSTOP; + else + tty->ctrl.pktstatus |= TIOCPKT_NOSTOP; + } + if (extproc) + tty->ctrl.pktstatus |= TIOCPKT_IOCTL; } - if (extproc) - tty->ctrl.pktstatus |= TIOCPKT_IOCTL; - spin_unlock_irq(&tty->ctrl.lock); wake_up_interruptible(&tty->link->read_wait); } } @@ -286,9 +285,9 @@ static int pty_resize(struct tty_struct *tty, struct winsize *ws) struct tty_struct *pty = tty->link; /* For a PTY we need to lock the tty side */ - mutex_lock(&tty->winsize_mutex); + guard(mutex)(&tty->winsize_mutex); if (!memcmp(ws, &tty->winsize, sizeof(*ws))) - goto done; + return 0; /* Signal the foreground process group of both ptys */ pgrp = tty_get_pgrp(tty); @@ -304,8 +303,7 @@ static int pty_resize(struct tty_struct *tty, struct winsize *ws) tty->winsize = *ws; pty->winsize = *ws; /* Never used so will go away soon */ -done: - mutex_unlock(&tty->winsize_mutex); + return 0; } @@ -321,28 +319,26 @@ done: */ static void pty_start(struct tty_struct *tty) { - unsigned long flags; + if (!tty->link || !tty->link->ctrl.packet) + return; - if (tty->link && tty->link->ctrl.packet) { - spin_lock_irqsave(&tty->ctrl.lock, flags); + scoped_guard(spinlock_irqsave, &tty->ctrl.lock) { tty->ctrl.pktstatus &= ~TIOCPKT_STOP; tty->ctrl.pktstatus |= TIOCPKT_START; - spin_unlock_irqrestore(&tty->ctrl.lock, flags); - wake_up_interruptible_poll(&tty->link->read_wait, EPOLLIN); } + wake_up_interruptible_poll(&tty->link->read_wait, EPOLLIN); } static void pty_stop(struct tty_struct *tty) { - unsigned long flags; + if (!tty->link || !tty->link->ctrl.packet) + return; - if (tty->link && tty->link->ctrl.packet) { - spin_lock_irqsave(&tty->ctrl.lock, flags); + scoped_guard(spinlock_irqsave, &tty->ctrl.lock) { tty->ctrl.pktstatus &= ~TIOCPKT_START; tty->ctrl.pktstatus |= TIOCPKT_STOP; - spin_unlock_irqrestore(&tty->ctrl.lock, flags); - wake_up_interruptible_poll(&tty->link->read_wait, EPOLLIN); } + wake_up_interruptible_poll(&tty->link->read_wait, EPOLLIN); } /** @@ -705,15 +701,9 @@ static struct tty_struct *ptm_unix98_lookup(struct tty_driver *driver, static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver, struct file *file, int idx) { - struct tty_struct *tty; - - mutex_lock(&devpts_mutex); - tty = devpts_get_priv(file->f_path.dentry); - mutex_unlock(&devpts_mutex); + guard(mutex)(&devpts_mutex); /* Master must be open before slave */ - if (!tty) - return ERR_PTR(-EIO); - return tty; + return devpts_get_priv(file->f_path.dentry) ? : ERR_PTR(-EIO); } static int pty_unix98_install(struct tty_driver *driver, struct tty_struct *tty) @@ -811,20 +801,17 @@ static int ptmx_open(struct inode *inode, struct file *filp) } /* find a device that is not in use. */ - mutex_lock(&devpts_mutex); - index = devpts_new_index(fsi); - mutex_unlock(&devpts_mutex); + scoped_guard(mutex, &devpts_mutex) + index = devpts_new_index(fsi); retval = index; if (index < 0) goto out_put_fsi; - mutex_lock(&tty_mutex); - tty = tty_init_dev(ptm_driver, index); - /* The tty returned here is locked so we can safely - drop the mutex */ - mutex_unlock(&tty_mutex); + /* The tty returned here is locked so we can safely drop the mutex */ + scoped_guard(mutex, &tty_mutex) + tty = tty_init_dev(ptm_driver, index); retval = PTR_ERR(tty); if (IS_ERR(tty)) From 2fc541e525178d6bd3c14c0d05a0e5889831da50 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 11:01:32 +0100 Subject: [PATCH 30/60] tty: n_tty: use guard()s Use guards in the n_tty code. This improves readability, makes error handling easier, and marks locked portions of code explicit. All that while being sure the lock is unlocked. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119100140.830761-3-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 107 +++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 60 deletions(-) diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index 6af3f3a0b531..e6a0f5b40d0a 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -324,12 +324,9 @@ static void reset_buffer_flags(struct n_tty_data *ldata) static void n_tty_packet_mode_flush(struct tty_struct *tty) { - unsigned long flags; - if (tty->link->ctrl.packet) { - spin_lock_irqsave(&tty->ctrl.lock, flags); - tty->ctrl.pktstatus |= TIOCPKT_FLUSHREAD; - spin_unlock_irqrestore(&tty->ctrl.lock, flags); + scoped_guard(spinlock_irqsave, &tty->ctrl.lock) + tty->ctrl.pktstatus |= TIOCPKT_FLUSHREAD; wake_up_interruptible(&tty->link->read_wait); } } @@ -349,13 +346,12 @@ static void n_tty_packet_mode_flush(struct tty_struct *tty) */ static void n_tty_flush_buffer(struct tty_struct *tty) { - down_write(&tty->termios_rwsem); + guard(rwsem_write)(&tty->termios_rwsem); reset_buffer_flags(tty->disc_data); n_tty_kick_worker(tty); if (tty->link) n_tty_packet_mode_flush(tty); - up_write(&tty->termios_rwsem); } /** @@ -737,25 +733,23 @@ static void commit_echoes(struct tty_struct *tty) size_t nr, old, echoed; size_t head; - mutex_lock(&ldata->output_lock); - head = ldata->echo_head; - ldata->echo_mark = head; - old = ldata->echo_commit - ldata->echo_tail; + scoped_guard(mutex, &ldata->output_lock) { + head = ldata->echo_head; + ldata->echo_mark = head; + old = ldata->echo_commit - ldata->echo_tail; - /* Process committed echoes if the accumulated # of bytes - * is over the threshold (and try again each time another - * block is accumulated) */ - nr = head - ldata->echo_tail; - if (nr < ECHO_COMMIT_WATERMARK || - (nr % ECHO_BLOCK > old % ECHO_BLOCK)) { - mutex_unlock(&ldata->output_lock); - return; + /* + * Process committed echoes if the accumulated # of bytes is over the threshold + * (and try again each time another block is accumulated) + */ + nr = head - ldata->echo_tail; + if (nr < ECHO_COMMIT_WATERMARK || (nr % ECHO_BLOCK > old % ECHO_BLOCK)) + return; + + ldata->echo_commit = head; + echoed = __process_echoes(tty); } - ldata->echo_commit = head; - echoed = __process_echoes(tty); - mutex_unlock(&ldata->output_lock); - if (echoed && tty->ops->flush_chars) tty->ops->flush_chars(tty); } @@ -768,10 +762,10 @@ static void process_echoes(struct tty_struct *tty) if (ldata->echo_mark == ldata->echo_tail) return; - mutex_lock(&ldata->output_lock); - ldata->echo_commit = ldata->echo_mark; - echoed = __process_echoes(tty); - mutex_unlock(&ldata->output_lock); + scoped_guard(mutex, &ldata->output_lock) { + ldata->echo_commit = ldata->echo_mark; + echoed = __process_echoes(tty); + } if (echoed && tty->ops->flush_chars) tty->ops->flush_chars(tty); @@ -786,10 +780,9 @@ static void flush_echoes(struct tty_struct *tty) ldata->echo_commit == ldata->echo_head) return; - mutex_lock(&ldata->output_lock); + guard(mutex)(&ldata->output_lock); ldata->echo_commit = ldata->echo_head; __process_echoes(tty); - mutex_unlock(&ldata->output_lock); } /** @@ -1078,18 +1071,19 @@ static void isig(int sig, struct tty_struct *tty) if (L_NOFLSH(tty)) { /* signal only */ __isig(sig, tty); + return; + } - } else { /* signal and flush */ - up_read(&tty->termios_rwsem); - down_write(&tty->termios_rwsem); - + /* signal and flush */ + up_read(&tty->termios_rwsem); + scoped_guard(rwsem_write, &tty->termios_rwsem) { __isig(sig, tty); /* clear echo buffer */ - mutex_lock(&ldata->output_lock); - ldata->echo_head = ldata->echo_tail = 0; - ldata->echo_mark = ldata->echo_commit = 0; - mutex_unlock(&ldata->output_lock); + scoped_guard(mutex, &ldata->output_lock) { + ldata->echo_head = ldata->echo_tail = 0; + ldata->echo_mark = ldata->echo_commit = 0; + } /* clear output buffer */ tty_driver_flush_buffer(tty); @@ -1100,10 +1094,8 @@ static void isig(int sig, struct tty_struct *tty) /* notify pty master of flush */ if (tty->link) n_tty_packet_mode_flush(tty); - - up_write(&tty->termios_rwsem); - down_read(&tty->termios_rwsem); } + down_read(&tty->termios_rwsem); } /** @@ -1683,7 +1675,7 @@ n_tty_receive_buf_common(struct tty_struct *tty, const u8 *cp, const u8 *fp, size_t n, rcvd = 0; int room, overflow; - down_read(&tty->termios_rwsem); + guard(rwsem_read)(&tty->termios_rwsem); do { /* @@ -1752,8 +1744,6 @@ n_tty_receive_buf_common(struct tty_struct *tty, const u8 *cp, const u8 *fp, n_tty_kick_worker(tty); } - up_read(&tty->termios_rwsem); - return rcvd; } @@ -1879,10 +1869,9 @@ static void n_tty_close(struct tty_struct *tty) if (tty->link) n_tty_packet_mode_flush(tty); - down_write(&tty->termios_rwsem); + guard(rwsem_write)(&tty->termios_rwsem); vfree(ldata); tty->disc_data = NULL; - up_write(&tty->termios_rwsem); } /** @@ -2247,10 +2236,10 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, u8 *kbuf, u8 cs; if (kb != kbuf) break; - spin_lock_irq(&tty->link->ctrl.lock); - cs = tty->link->ctrl.pktstatus; - tty->link->ctrl.pktstatus = 0; - spin_unlock_irq(&tty->link->ctrl.lock); + scoped_guard(spinlock_irq, &tty->link->ctrl.lock) { + cs = tty->link->ctrl.pktstatus; + tty->link->ctrl.pktstatus = 0; + } *kb++ = cs; nr--; break; @@ -2357,7 +2346,7 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, return retval; } - down_read(&tty->termios_rwsem); + guard(rwsem_read)(&tty->termios_rwsem); /* Write out any echoed characters that are still pending */ process_echoes(tty); @@ -2395,9 +2384,8 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, struct n_tty_data *ldata = tty->disc_data; while (nr > 0) { - mutex_lock(&ldata->output_lock); - num = tty->ops->write(tty, b, nr); - mutex_unlock(&ldata->output_lock); + scoped_guard(mutex, &ldata->output_lock) + num = tty->ops->write(tty, b, nr); if (num < 0) { retval = num; goto break_out; @@ -2424,7 +2412,7 @@ break_out: remove_wait_queue(&tty->write_wait, &wait); if (nr && tty->fasync) set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); - up_read(&tty->termios_rwsem); + return (b - buf) ? b - buf : retval; } @@ -2498,12 +2486,11 @@ static int n_tty_ioctl(struct tty_struct *tty, unsigned int cmd, case TIOCOUTQ: return put_user(tty_chars_in_buffer(tty), (int __user *) arg); case TIOCINQ: - down_write(&tty->termios_rwsem); - if (L_ICANON(tty) && !L_EXTPROC(tty)) - num = inq_canon(ldata); - else - num = read_cnt(ldata); - up_write(&tty->termios_rwsem); + scoped_guard(rwsem_write, &tty->termios_rwsem) + if (L_ICANON(tty) && !L_EXTPROC(tty)) + num = inq_canon(ldata); + else + num = read_cnt(ldata); return put_user(num, (unsigned int __user *) arg); default: return n_tty_ioctl_helper(tty, cmd, arg); From 3ae99599bf47eb99f6090c9502e32993fb331e0e Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 11:01:33 +0100 Subject: [PATCH 31/60] tty: n_hdlc: simplify return from n_hdlc_tty_ioctl() The cases in the switch() of n_hdlc_tty_ioctl() can return immediately -- no need to store into error and return later. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119100140.830761-4-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_hdlc.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c index 4a4dc58b866a..66bb8ce6ec40 100644 --- a/drivers/tty/n_hdlc.c +++ b/drivers/tty/n_hdlc.c @@ -584,7 +584,6 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { struct n_hdlc *n_hdlc = tty->disc_data; - int error = 0; int count; unsigned long flags; struct n_hdlc_buf *buf = NULL; @@ -603,8 +602,7 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, else count = 0; spin_unlock_irqrestore(&n_hdlc->rx_buf_list.spinlock, flags); - error = put_user(count, (int __user *)arg); - break; + return put_user(count, (int __user *)arg); case TIOCOUTQ: /* get the pending tx byte count in the driver */ @@ -616,8 +614,7 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, if (buf) count += buf->count; spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); - error = put_user(count, (int __user *)arg); - break; + return put_user(count, (int __user *)arg); case TCFLSH: switch (arg) { @@ -628,11 +625,8 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, fallthrough; /* to default */ default: - error = n_tty_ioctl_helper(tty, cmd, arg); - break; + return n_tty_ioctl_helper(tty, cmd, arg); } - return error; - } /* end of n_hdlc_tty_ioctl() */ /** From 8c03bfcf6b2bbb1b936b6a5dec3bd7922460cb9d Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 11:01:34 +0100 Subject: [PATCH 32/60] tty: n_hdlc: use guard()s Use guards in the n_hdlc code. This improves readability, makes error handling easier, and marks locked portions of code explicit. All that while being sure the lock is unlocked. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119100140.830761-5-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_hdlc.c | 67 +++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c index 66bb8ce6ec40..3c9dcb0928c6 100644 --- a/drivers/tty/n_hdlc.c +++ b/drivers/tty/n_hdlc.c @@ -263,21 +263,18 @@ static int n_hdlc_tty_open(struct tty_struct *tty) */ static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty) { - unsigned long flags; struct n_hdlc_buf *tbuf; ssize_t actual; check_again: - - spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags); - if (n_hdlc->tbusy) { - n_hdlc->woke_up = true; - spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); - return; + scoped_guard(spinlock_irqsave, &n_hdlc->tx_buf_list.spinlock) { + if (n_hdlc->tbusy) { + n_hdlc->woke_up = true; + return; + } + n_hdlc->tbusy = true; + n_hdlc->woke_up = false; } - n_hdlc->tbusy = true; - n_hdlc->woke_up = false; - spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list); while (tbuf) { @@ -324,9 +321,8 @@ check_again: clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); /* Clear the re-entry flag */ - spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags); - n_hdlc->tbusy = false; - spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); + scoped_guard(spinlock_irqsave, &n_hdlc->tx_buf_list.spinlock) + n_hdlc->tbusy = false; if (n_hdlc->woke_up) goto check_again; @@ -585,7 +581,6 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, { struct n_hdlc *n_hdlc = tty->disc_data; int count; - unsigned long flags; struct n_hdlc_buf *buf = NULL; pr_debug("%s() called %d\n", __func__, cmd); @@ -594,26 +589,26 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, case FIONREAD: /* report count of read data available */ /* in next available frame (if any) */ - spin_lock_irqsave(&n_hdlc->rx_buf_list.spinlock, flags); - buf = list_first_entry_or_null(&n_hdlc->rx_buf_list.list, - struct n_hdlc_buf, list_item); - if (buf) - count = buf->count; - else - count = 0; - spin_unlock_irqrestore(&n_hdlc->rx_buf_list.spinlock, flags); + scoped_guard(spinlock_irqsave, &n_hdlc->rx_buf_list.spinlock) { + buf = list_first_entry_or_null(&n_hdlc->rx_buf_list.list, + struct n_hdlc_buf, list_item); + if (buf) + count = buf->count; + else + count = 0; + } return put_user(count, (int __user *)arg); case TIOCOUTQ: /* get the pending tx byte count in the driver */ count = tty_chars_in_buffer(tty); /* add size of next output frame in queue */ - spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags); - buf = list_first_entry_or_null(&n_hdlc->tx_buf_list.list, - struct n_hdlc_buf, list_item); - if (buf) - count += buf->count; - spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); + scoped_guard(spinlock_irqsave, &n_hdlc->tx_buf_list.spinlock) { + buf = list_first_entry_or_null(&n_hdlc->tx_buf_list.list, + struct n_hdlc_buf, list_item); + if (buf) + count += buf->count; + } return put_user(count, (int __user *)arg); case TCFLSH: @@ -720,14 +715,10 @@ static struct n_hdlc *n_hdlc_alloc(void) static void n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list, struct n_hdlc_buf *buf) { - unsigned long flags; - - spin_lock_irqsave(&buf_list->spinlock, flags); + guard(spinlock_irqsave)(&buf_list->spinlock); list_add(&buf->list_item, &buf_list->list); buf_list->count++; - - spin_unlock_irqrestore(&buf_list->spinlock, flags); } /** @@ -738,14 +729,10 @@ static void n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list, static void n_hdlc_buf_put(struct n_hdlc_buf_list *buf_list, struct n_hdlc_buf *buf) { - unsigned long flags; - - spin_lock_irqsave(&buf_list->spinlock, flags); + guard(spinlock_irqsave)(&buf_list->spinlock); list_add_tail(&buf->list_item, &buf_list->list); buf_list->count++; - - spin_unlock_irqrestore(&buf_list->spinlock, flags); } /* end of n_hdlc_buf_put() */ /** @@ -758,10 +745,9 @@ static void n_hdlc_buf_put(struct n_hdlc_buf_list *buf_list, */ static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *buf_list) { - unsigned long flags; struct n_hdlc_buf *buf; - spin_lock_irqsave(&buf_list->spinlock, flags); + guard(spinlock_irqsave)(&buf_list->spinlock); buf = list_first_entry_or_null(&buf_list->list, struct n_hdlc_buf, list_item); @@ -770,7 +756,6 @@ static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *buf_list) buf_list->count--; } - spin_unlock_irqrestore(&buf_list->spinlock, flags); return buf; } /* end of n_hdlc_buf_get() */ From 1c7736dc68d7599e12e6b20c848dfbcbe0d04cdb Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 11:01:35 +0100 Subject: [PATCH 33/60] tty: moxa: use guard()s Use guards in the moxa code. This improves readability, makes error handling easier, and marks locked portions of code explicit. All that while being sure the lock is unlocked. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119100140.830761-6-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/moxa.c | 165 +++++++++++++++++++-------------------------- 1 file changed, 71 insertions(+), 94 deletions(-) diff --git a/drivers/tty/moxa.c b/drivers/tty/moxa.c index 329b30fac8fc..1bb2376af85c 100644 --- a/drivers/tty/moxa.c +++ b/drivers/tty/moxa.c @@ -487,25 +487,20 @@ static void moxa_wait_finish(void __iomem *ofsAddr) static void moxafunc(void __iomem *ofsAddr, u16 cmd, u16 arg) { - unsigned long flags; - spin_lock_irqsave(&moxafunc_lock, flags); + guard(spinlock_irqsave)(&moxafunc_lock); writew(arg, ofsAddr + FuncArg); writew(cmd, ofsAddr + FuncCode); moxa_wait_finish(ofsAddr); - spin_unlock_irqrestore(&moxafunc_lock, flags); } static int moxafuncret(void __iomem *ofsAddr, u16 cmd, u16 arg) { - unsigned long flags; - u16 ret; - spin_lock_irqsave(&moxafunc_lock, flags); + guard(spinlock_irqsave)(&moxafunc_lock); writew(arg, ofsAddr + FuncArg); writew(cmd, ofsAddr + FuncCode); moxa_wait_finish(ofsAddr); - ret = readw(ofsAddr + FuncArg); - spin_unlock_irqrestore(&moxafunc_lock, flags); - return ret; + + return readw(ofsAddr + FuncArg); } static void moxa_low_water_check(void __iomem *ofsAddr) @@ -1002,11 +997,11 @@ static int moxa_init_board(struct moxa_board_conf *brd, struct device *dev) if (ret) goto err_free; - spin_lock_bh(&moxa_lock); - brd->ready = 1; - if (!timer_pending(&moxaTimer)) - mod_timer(&moxaTimer, jiffies + HZ / 50); - spin_unlock_bh(&moxa_lock); + scoped_guard(spinlock_bh, &moxa_lock) { + brd->ready = 1; + if (!timer_pending(&moxaTimer)) + mod_timer(&moxaTimer, jiffies + HZ / 50); + } first_idx = (brd - moxa_boards) * MAX_PORTS_PER_BOARD; for (i = 0; i < brd->numPorts; i++) @@ -1026,29 +1021,29 @@ static void moxa_board_deinit(struct moxa_board_conf *brd) { unsigned int a, opened, first_idx; - mutex_lock(&moxa_openlock); - spin_lock_bh(&moxa_lock); - brd->ready = 0; - spin_unlock_bh(&moxa_lock); + scoped_guard(mutex, &moxa_openlock) { + scoped_guard(spinlock_bh, &moxa_lock) + brd->ready = 0; - /* pci hot-un-plug support */ - for (a = 0; a < brd->numPorts; a++) - if (tty_port_initialized(&brd->ports[a].port)) - tty_port_tty_hangup(&brd->ports[a].port, false); - - for (a = 0; a < MAX_PORTS_PER_BOARD; a++) - tty_port_destroy(&brd->ports[a].port); - - while (1) { - opened = 0; + /* pci hot-un-plug support */ for (a = 0; a < brd->numPorts; a++) if (tty_port_initialized(&brd->ports[a].port)) - opened++; - mutex_unlock(&moxa_openlock); - if (!opened) - break; - msleep(50); - mutex_lock(&moxa_openlock); + tty_port_tty_hangup(&brd->ports[a].port, false); + + for (a = 0; a < MAX_PORTS_PER_BOARD; a++) + tty_port_destroy(&brd->ports[a].port); + + while (1) { + opened = 0; + for (a = 0; a < brd->numPorts; a++) + if (tty_port_initialized(&brd->ports[a].port)) + opened++; + if (!opened) + break; + mutex_unlock(&moxa_openlock); + msleep(50); + mutex_lock(&moxa_openlock); + } } first_idx = (brd - moxa_boards) * MAX_PORTS_PER_BOARD; @@ -1206,12 +1201,9 @@ static void moxa_shutdown(struct tty_port *port) static bool moxa_carrier_raised(struct tty_port *port) { struct moxa_port *ch = container_of(port, struct moxa_port, port); - int dcd; - spin_lock_irq(&port->lock); - dcd = ch->DCDState; - spin_unlock_irq(&port->lock); - return dcd; + guard(spinlock_irq)(&port->lock); + return ch->DCDState; } static void moxa_dtr_rts(struct tty_port *port, bool active) @@ -1225,37 +1217,31 @@ static int moxa_open(struct tty_struct *tty, struct file *filp) { struct moxa_board_conf *brd; struct moxa_port *ch; - int port; + int port = tty->index; - port = tty->index; - if (mutex_lock_interruptible(&moxa_openlock)) - return -ERESTARTSYS; - brd = &moxa_boards[port / MAX_PORTS_PER_BOARD]; - if (!brd->ready) { - mutex_unlock(&moxa_openlock); - return -ENODEV; - } + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &moxa_openlock) { + brd = &moxa_boards[port / MAX_PORTS_PER_BOARD]; + if (!brd->ready) + return -ENODEV; - if (port % MAX_PORTS_PER_BOARD >= brd->numPorts) { - mutex_unlock(&moxa_openlock); - return -ENODEV; - } + if (port % MAX_PORTS_PER_BOARD >= brd->numPorts) + return -ENODEV; - ch = &brd->ports[port % MAX_PORTS_PER_BOARD]; - ch->port.count++; - tty->driver_data = ch; - tty_port_tty_set(&ch->port, tty); - mutex_lock(&ch->port.mutex); - if (!tty_port_initialized(&ch->port)) { - ch->statusflags = 0; - moxa_set_tty_param(tty, &tty->termios); - MoxaPortLineCtrl(ch, true, true); - MoxaPortEnable(ch); - MoxaSetFifo(ch, ch->type == PORT_16550A); - tty_port_set_initialized(&ch->port, true); + ch = &brd->ports[port % MAX_PORTS_PER_BOARD]; + ch->port.count++; + tty->driver_data = ch; + tty_port_tty_set(&ch->port, tty); + + guard(mutex)(&ch->port.mutex); + if (!tty_port_initialized(&ch->port)) { + ch->statusflags = 0; + moxa_set_tty_param(tty, &tty->termios); + MoxaPortLineCtrl(ch, true, true); + MoxaPortEnable(ch); + MoxaSetFifo(ch, ch->type == PORT_16550A); + tty_port_set_initialized(&ch->port, true); + } } - mutex_unlock(&ch->port.mutex); - mutex_unlock(&moxa_openlock); return tty_port_block_til_ready(&ch->port, tty, filp); } @@ -1270,15 +1256,13 @@ static void moxa_close(struct tty_struct *tty, struct file *filp) static ssize_t moxa_write(struct tty_struct *tty, const u8 *buf, size_t count) { struct moxa_port *ch = tty->driver_data; - unsigned long flags; int len; if (ch == NULL) return 0; - spin_lock_irqsave(&moxa_lock, flags); - len = MoxaPortWriteData(tty, buf, count); - spin_unlock_irqrestore(&moxa_lock, flags); + scoped_guard(spinlock_irqsave, &moxa_lock) + len = MoxaPortWriteData(tty, buf, count); set_bit(LOWWAIT, &ch->statusflags); return len; @@ -1349,12 +1333,10 @@ static int moxa_tiocmset(struct tty_struct *tty, bool dtr_active, rts_active; struct moxa_port *ch; - mutex_lock(&moxa_openlock); + guard(mutex)(&moxa_openlock); ch = tty->driver_data; - if (!ch) { - mutex_unlock(&moxa_openlock); + if (!ch) return -EINVAL; - } MoxaPortGetLineOut(ch, &dtr_active, &rts_active); if (set & TIOCM_RTS) @@ -1366,7 +1348,7 @@ static int moxa_tiocmset(struct tty_struct *tty, if (clear & TIOCM_DTR) dtr_active = false; MoxaPortLineCtrl(ch, dtr_active, rts_active); - mutex_unlock(&moxa_openlock); + return 0; } @@ -1415,18 +1397,17 @@ static void moxa_hangup(struct tty_struct *tty) static void moxa_new_dcdstate(struct moxa_port *p, u8 dcd) { - unsigned long flags; dcd = !!dcd; - spin_lock_irqsave(&p->port.lock, flags); - if (dcd != p->DCDState) { - p->DCDState = dcd; - spin_unlock_irqrestore(&p->port.lock, flags); - if (!dcd) - tty_port_tty_hangup(&p->port, true); + scoped_guard(spinlock_irqsave, &p->port.lock) { + if (dcd == p->DCDState) + return; + + p->DCDState = dcd; } - else - spin_unlock_irqrestore(&p->port.lock, flags); + + if (!dcd) + tty_port_tty_hangup(&p->port, true); } static int moxa_poll_port(struct moxa_port *p, unsigned int handle, @@ -1494,7 +1475,7 @@ static void moxa_poll(struct timer_list *unused) u16 __iomem *ip; unsigned int card, port, served = 0; - spin_lock(&moxa_lock); + guard(spinlock)(&moxa_lock); for (card = 0; card < MAX_BOARDS; card++) { brd = &moxa_boards[card]; if (!brd->ready) @@ -1525,7 +1506,6 @@ static void moxa_poll(struct timer_list *unused) if (served) mod_timer(&moxaTimer, jiffies + HZ / 50); - spin_unlock(&moxa_lock); } /******************************************************************************/ @@ -1861,13 +1841,11 @@ static int MoxaPortSetTermio(struct moxa_port *port, struct ktermios *termio, baud = MoxaPortSetBaud(port, baud); if (termio->c_iflag & (IXON | IXOFF | IXANY)) { - spin_lock_irq(&moxafunc_lock); + guard(spinlock_irq)(&moxafunc_lock); writeb(termio->c_cc[VSTART], ofsAddr + FuncArg); writeb(termio->c_cc[VSTOP], ofsAddr + FuncArg1); writeb(FC_SetXonXoff, ofsAddr + FuncCode); moxa_wait_finish(ofsAddr); - spin_unlock_irq(&moxafunc_lock); - } return baud; } @@ -2098,13 +2076,13 @@ static int moxa_get_serial_info(struct tty_struct *tty, if (!info) return -ENODEV; - mutex_lock(&info->port.mutex); + guard(mutex)(&info->port.mutex); ss->type = info->type; ss->line = info->port.tty->index; ss->flags = info->port.flags; ss->baud_base = 921600; ss->close_delay = jiffies_to_msecs(info->port.close_delay) / 10; - mutex_unlock(&info->port.mutex); + return 0; } @@ -2120,13 +2098,12 @@ static int moxa_set_serial_info(struct tty_struct *tty, close_delay = msecs_to_jiffies(ss->close_delay * 10); - mutex_lock(&info->port.mutex); + guard(mutex)(&info->port.mutex); if (!capable(CAP_SYS_ADMIN)) { if (close_delay != info->port.close_delay || ss->type != info->type || ((ss->flags & ~ASYNC_USR_MASK) != (info->port.flags & ~ASYNC_USR_MASK))) { - mutex_unlock(&info->port.mutex); return -EPERM; } } else { @@ -2136,7 +2113,7 @@ static int moxa_set_serial_info(struct tty_struct *tty, info->type = ss->type; } - mutex_unlock(&info->port.mutex); + return 0; } From bfb24564b5fd8625ce5c007f274cabdc3b570969 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 11:01:36 +0100 Subject: [PATCH 34/60] tty: vt/keyboard: use __free() The vt/keyboard code can use __free to ensure the temporary buffers are freed. Perform the switch. And even one non-temporary in kbd_connect(). There are fail paths, so ensure the buffer is freed in them and not when returning 0 -- by retain_and_null_ptr(). Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119100140.830761-7-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/keyboard.c | 90 ++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 53 deletions(-) diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index ee1d9c448c7e..65913a137862 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -1566,10 +1566,9 @@ static bool kbd_match(struct input_handler *handler, struct input_dev *dev) static int kbd_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { - struct input_handle *handle; int error; - handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + struct input_handle __free(kfree) *handle = kzalloc(sizeof(*handle), GFP_KERNEL); if (!handle) return -ENOMEM; @@ -1579,18 +1578,18 @@ static int kbd_connect(struct input_handler *handler, struct input_dev *dev, error = input_register_handle(handle); if (error) - goto err_free_handle; + return error; error = input_open_device(handle); if (error) goto err_unregister_handle; + retain_and_null_ptr(handle); + return 0; err_unregister_handle: input_unregister_handle(handle); - err_free_handle: - kfree(handle); return error; } @@ -1683,17 +1682,15 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) { unsigned long flags; int asize; - int ret = 0; switch (cmd) { case KDGKBDIACR: { struct kbdiacrs __user *a = udp; - struct kbdiacr *dia; int i; - dia = kmalloc_array(MAX_DIACR, sizeof(struct kbdiacr), - GFP_KERNEL); + struct kbdiacr __free(kfree) *dia = kmalloc_array(MAX_DIACR, sizeof(struct kbdiacr), + GFP_KERNEL); if (!dia) return -ENOMEM; @@ -1713,20 +1710,17 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) spin_unlock_irqrestore(&kbd_event_lock, flags); if (put_user(asize, &a->kb_cnt)) - ret = -EFAULT; - else if (copy_to_user(a->kbdiacr, dia, - asize * sizeof(struct kbdiacr))) - ret = -EFAULT; - kfree(dia); - return ret; + return -EFAULT; + if (copy_to_user(a->kbdiacr, dia, asize * sizeof(struct kbdiacr))) + return -EFAULT; + return 0; } case KDGKBDIACRUC: { struct kbdiacrsuc __user *a = udp; - void *buf; - buf = kmalloc_array(MAX_DIACR, sizeof(struct kbdiacruc), - GFP_KERNEL); + void __free(kfree) *buf = kmalloc_array(MAX_DIACR, sizeof(struct kbdiacruc), + GFP_KERNEL); if (buf == NULL) return -ENOMEM; @@ -1740,18 +1734,17 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) spin_unlock_irqrestore(&kbd_event_lock, flags); if (put_user(asize, &a->kb_cnt)) - ret = -EFAULT; - else if (copy_to_user(a->kbdiacruc, buf, - asize*sizeof(struct kbdiacruc))) - ret = -EFAULT; - kfree(buf); - return ret; + return -EFAULT; + if (copy_to_user(a->kbdiacruc, buf, asize * sizeof(struct kbdiacruc))) + return -EFAULT; + + return 0; } case KDSKBDIACR: { struct kbdiacrs __user *a = udp; - struct kbdiacr *dia = NULL; + struct kbdiacr __free(kfree) *dia = NULL; unsigned int ct; int i; @@ -1780,7 +1773,7 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) conv_8bit_to_uni(dia[i].result); } spin_unlock_irqrestore(&kbd_event_lock, flags); - kfree(dia); + return 0; } @@ -1788,7 +1781,7 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) { struct kbdiacrsuc __user *a = udp; unsigned int ct; - void *buf = NULL; + void __free(kfree) *buf = NULL; if (!perm) return -EPERM; @@ -1811,11 +1804,10 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) ct * sizeof(struct kbdiacruc)); accent_table_size = ct; spin_unlock_irqrestore(&kbd_event_lock, flags); - kfree(buf); return 0; } } - return ret; + return 0; } /** @@ -1934,7 +1926,7 @@ static int vt_kdskbent(unsigned char kbdmode, unsigned char idx, unsigned char map, unsigned short val) { unsigned long flags; - unsigned short *key_map, *new_map, oldval; + unsigned short *key_map, oldval; if (!idx && val == K_NOSUCHMAP) { spin_lock_irqsave(&kbd_event_lock, flags); @@ -1965,7 +1957,7 @@ static int vt_kdskbent(unsigned char kbdmode, unsigned char idx, return 0; #endif - new_map = kmalloc(sizeof(plain_map), GFP_KERNEL); + unsigned short __free(kfree) *new_map = kmalloc(sizeof(plain_map), GFP_KERNEL); if (!new_map) return -ENOMEM; @@ -1977,17 +1969,14 @@ static int vt_kdskbent(unsigned char kbdmode, unsigned char idx, if (keymap_count >= MAX_NR_OF_USER_KEYMAPS && !capable(CAP_SYS_RESOURCE)) { spin_unlock_irqrestore(&kbd_event_lock, flags); - kfree(new_map); return -EPERM; } - key_maps[map] = new_map; - key_map = new_map; + key_map = key_maps[map] = no_free_ptr(new_map); key_map[0] = U(K_ALLOCATED); for (j = 1; j < NR_KEYS; j++) key_map[j] = U(K_HOLE); keymap_count++; - } else - kfree(new_map); + } oldval = U(key_map[idx]); if (val == oldval) @@ -2050,8 +2039,6 @@ int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm) { unsigned char kb_func; unsigned long flags; - char *kbs; - int ret; if (get_user(kb_func, &user_kdgkb->kb_func)) return -EFAULT; @@ -2063,7 +2050,7 @@ int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm) /* size should have been a struct member */ ssize_t len = sizeof(user_kdgkb->kb_string); - kbs = kmalloc(len, GFP_KERNEL); + char __free(kfree) *kbs = kmalloc(len, GFP_KERNEL); if (!kbs) return -ENOMEM; @@ -2071,20 +2058,20 @@ int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm) len = strscpy(kbs, func_table[kb_func] ? : "", len); spin_unlock_irqrestore(&func_buf_lock, flags); - if (len < 0) { - ret = -ENOSPC; - break; - } - ret = copy_to_user(user_kdgkb->kb_string, kbs, len + 1) ? - -EFAULT : 0; - break; + if (len < 0) + return -ENOSPC; + + if (copy_to_user(user_kdgkb->kb_string, kbs, len + 1)) + return -EFAULT; + + return 0; } case KDSKBSENT: if (!perm || !capable(CAP_SYS_TTY_CONFIG)) return -EPERM; - kbs = strndup_user(user_kdgkb->kb_string, - sizeof(user_kdgkb->kb_string)); + char __free(kfree) *kbs = strndup_user(user_kdgkb->kb_string, + sizeof(user_kdgkb->kb_string)); if (IS_ERR(kbs)) return PTR_ERR(kbs); @@ -2092,13 +2079,10 @@ int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm) kbs = vt_kdskbsent(kbs, kb_func); spin_unlock_irqrestore(&func_buf_lock, flags); - ret = 0; - break; + return 0; } - kfree(kbs); - - return ret; + return 0; } int vt_do_kdskled(unsigned int console, int cmd, unsigned long arg, int perm) From d139b31f86b9ca56ee2424e46f0c2b5d23f15eda Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 11:01:37 +0100 Subject: [PATCH 35/60] tty: vt/keyboard: simplify returns from vt_do_kbkeycode_ioctl() Return immediately when something goes wrong in vt_do_kbkeycode_ioctl(). This makes the code flow more obvious. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119100140.830761-8-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/keyboard.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index 65913a137862..487518a696e6 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -1879,27 +1879,27 @@ int vt_do_kdskbmeta(unsigned int console, unsigned int arg) return ret; } -int vt_do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc, - int perm) +int vt_do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc, int perm) { struct kbkeycode tmp; - int kc = 0; + int kc; if (copy_from_user(&tmp, user_kbkc, sizeof(struct kbkeycode))) return -EFAULT; + switch (cmd) { case KDGETKEYCODE: kc = getkeycode(tmp.scancode); - if (kc >= 0) - kc = put_user(kc, &user_kbkc->keycode); - break; + if (kc < 0) + return kc; + return put_user(kc, &user_kbkc->keycode); case KDSETKEYCODE: if (!perm) return -EPERM; - kc = setkeycode(tmp.scancode, tmp.keycode); - break; + return setkeycode(tmp.scancode, tmp.keycode); } - return kc; + + return 0; } static unsigned short vt_kdgkbent(unsigned char kbdmode, unsigned char idx, From dee7e10498c76490bb3bd5039c7c12d54585c26d Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 11:01:38 +0100 Subject: [PATCH 36/60] tty: vt/keyboard: use guard()s Use guards in the vt/keyboard code. This improves readability, makes error handling easier, and marks locked portions of code explicit. All that while being sure the lock is unlocked. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119100140.830761-9-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/keyboard.c | 210 ++++++++++++++------------------------ 1 file changed, 74 insertions(+), 136 deletions(-) diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index 487518a696e6..d65fc60dd7be 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -424,8 +424,6 @@ static void do_compute_shiftstate(void) /* We still have to export this method to vt.c */ void vt_set_leds_compute_shiftstate(void) { - unsigned long flags; - /* * When VT is switched, the keyboard led needs to be set once. * Ensure that after the switch is completed, the state of the @@ -434,9 +432,8 @@ void vt_set_leds_compute_shiftstate(void) vt_switch = true; set_leds(); - spin_lock_irqsave(&kbd_event_lock, flags); + guard(spinlock_irqsave)(&kbd_event_lock); do_compute_shiftstate(); - spin_unlock_irqrestore(&kbd_event_lock, flags); } /* @@ -625,13 +622,12 @@ static void fn_compose(struct vc_data *vc) static void fn_spawn_con(struct vc_data *vc) { - spin_lock(&vt_spawn_con.lock); + guard(spinlock)(&vt_spawn_con.lock); if (vt_spawn_con.pid) if (kill_pid(vt_spawn_con.pid, vt_spawn_con.sig, 1)) { put_pid(vt_spawn_con.pid); vt_spawn_con.pid = NULL; } - spin_unlock(&vt_spawn_con.lock); } static void fn_SAK(struct vc_data *vc) @@ -762,13 +758,9 @@ static void k_fn(struct vc_data *vc, unsigned char value, char up_flag) return; if ((unsigned)value < ARRAY_SIZE(func_table)) { - unsigned long flags; - - spin_lock_irqsave(&func_buf_lock, flags); + guard(spinlock_irqsave)(&func_buf_lock); if (func_table[value]) puts_queue(vc, func_table[value]); - spin_unlock_irqrestore(&func_buf_lock, flags); - } else pr_err("k_fn called with value=%d\n", value); } @@ -1140,8 +1132,7 @@ static unsigned char getledstate(void) void setledstate(struct kbd_struct *kb, unsigned int led) { - unsigned long flags; - spin_lock_irqsave(&led_lock, flags); + guard(spinlock_irqsave)(&led_lock); if (!(led & ~7)) { ledioctl = led; kb->ledmode = LED_SHOW_IOCTL; @@ -1149,7 +1140,6 @@ void setledstate(struct kbd_struct *kb, unsigned int led) kb->ledmode = LED_SHOW_FLAGS; set_leds(); - spin_unlock_irqrestore(&led_lock, flags); } static inline unsigned char getleds(void) @@ -1172,14 +1162,9 @@ static inline unsigned char getleds(void) int vt_get_leds(unsigned int console, int flag) { struct kbd_struct *kb = &kbd_table[console]; - int ret; - unsigned long flags; - spin_lock_irqsave(&led_lock, flags); - ret = vc_kbd_led(kb, flag); - spin_unlock_irqrestore(&led_lock, flags); - - return ret; + guard(spinlock_irqsave)(&led_lock); + return vc_kbd_led(kb, flag); } EXPORT_SYMBOL_GPL(vt_get_leds); @@ -1213,11 +1198,10 @@ void vt_set_led_state(unsigned int console, int leds) void vt_kbd_con_start(unsigned int console) { struct kbd_struct *kb = &kbd_table[console]; - unsigned long flags; - spin_lock_irqsave(&led_lock, flags); + + guard(spinlock_irqsave)(&led_lock); clr_vc_kbd_led(kb, VC_SCROLLOCK); set_leds(); - spin_unlock_irqrestore(&led_lock, flags); } /** @@ -1230,11 +1214,10 @@ void vt_kbd_con_start(unsigned int console) void vt_kbd_con_stop(unsigned int console) { struct kbd_struct *kb = &kbd_table[console]; - unsigned long flags; - spin_lock_irqsave(&led_lock, flags); + + guard(spinlock_irqsave)(&led_lock); set_vc_kbd_led(kb, VC_SCROLLOCK); set_leds(); - spin_unlock_irqrestore(&led_lock, flags); } /* @@ -1246,12 +1229,11 @@ void vt_kbd_con_stop(unsigned int console) static void kbd_bh(struct tasklet_struct *unused) { unsigned int leds; - unsigned long flags; - spin_lock_irqsave(&led_lock, flags); - leds = getleds(); - leds |= (unsigned int)kbd->lockstate << 8; - spin_unlock_irqrestore(&led_lock, flags); + scoped_guard(spinlock_irqsave, &led_lock) { + leds = getleds(); + leds |= (unsigned int)kbd->lockstate << 8; + } if (vt_switch) { ledstate = ~leds; @@ -1525,15 +1507,13 @@ static void kbd_event(struct input_handle *handle, unsigned int event_type, unsigned int event_code, int value) { /* We are called with interrupts disabled, just take the lock */ - spin_lock(&kbd_event_lock); - - if (event_type == EV_MSC && event_code == MSC_RAW && - kbd_is_hw_raw(handle->dev)) - kbd_rawcode(value); - if (event_type == EV_KEY && event_code <= KEY_MAX) - kbd_keycode(event_code, value, kbd_is_hw_raw(handle->dev)); - - spin_unlock(&kbd_event_lock); + scoped_guard(spinlock, &kbd_event_lock) { + if (event_type == EV_MSC && event_code == MSC_RAW && + kbd_is_hw_raw(handle->dev)) + kbd_rawcode(value); + if (event_type == EV_KEY && event_code <= KEY_MAX) + kbd_keycode(event_code, value, kbd_is_hw_raw(handle->dev)); + } tasklet_schedule(&keyboard_tasklet); do_poke_blanked_console = 1; @@ -1680,7 +1660,6 @@ int __init kbd_init(void) */ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) { - unsigned long flags; int asize; switch (cmd) { @@ -1696,18 +1675,14 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) /* Lock the diacriticals table, make a copy and then copy it after we unlock */ - spin_lock_irqsave(&kbd_event_lock, flags); - - asize = accent_table_size; - for (i = 0; i < asize; i++) { - dia[i].diacr = conv_uni_to_8bit( - accent_table[i].diacr); - dia[i].base = conv_uni_to_8bit( - accent_table[i].base); - dia[i].result = conv_uni_to_8bit( - accent_table[i].result); + scoped_guard(spinlock_irqsave, &kbd_event_lock) { + asize = accent_table_size; + for (i = 0; i < asize; i++) { + dia[i].diacr = conv_uni_to_8bit(accent_table[i].diacr); + dia[i].base = conv_uni_to_8bit(accent_table[i].base); + dia[i].result = conv_uni_to_8bit(accent_table[i].result); + } } - spin_unlock_irqrestore(&kbd_event_lock, flags); if (put_user(asize, &a->kb_cnt)) return -EFAULT; @@ -1726,12 +1701,10 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) /* Lock the diacriticals table, make a copy and then copy it after we unlock */ - spin_lock_irqsave(&kbd_event_lock, flags); - - asize = accent_table_size; - memcpy(buf, accent_table, asize * sizeof(struct kbdiacruc)); - - spin_unlock_irqrestore(&kbd_event_lock, flags); + scoped_guard(spinlock_irqsave, &kbd_event_lock) { + asize = accent_table_size; + memcpy(buf, accent_table, asize * sizeof(struct kbdiacruc)); + } if (put_user(asize, &a->kb_cnt)) return -EFAULT; @@ -1762,7 +1735,7 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) return PTR_ERR(dia); } - spin_lock_irqsave(&kbd_event_lock, flags); + guard(spinlock_irqsave)(&kbd_event_lock); accent_table_size = ct; for (i = 0; i < ct; i++) { accent_table[i].diacr = @@ -1772,7 +1745,6 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) accent_table[i].result = conv_8bit_to_uni(dia[i].result); } - spin_unlock_irqrestore(&kbd_event_lock, flags); return 0; } @@ -1797,13 +1769,12 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) ct, sizeof(struct kbdiacruc)); if (IS_ERR(buf)) return PTR_ERR(buf); - } - spin_lock_irqsave(&kbd_event_lock, flags); + } + guard(spinlock_irqsave)(&kbd_event_lock); if (ct) memcpy(accent_table, buf, ct * sizeof(struct kbdiacruc)); accent_table_size = ct; - spin_unlock_irqrestore(&kbd_event_lock, flags); return 0; } } @@ -1821,33 +1792,29 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) int vt_do_kdskbmode(unsigned int console, unsigned int arg) { struct kbd_struct *kb = &kbd_table[console]; - int ret = 0; - unsigned long flags; - spin_lock_irqsave(&kbd_event_lock, flags); + guard(spinlock_irqsave)(&kbd_event_lock); switch(arg) { case K_RAW: kb->kbdmode = VC_RAW; - break; + return 0; case K_MEDIUMRAW: kb->kbdmode = VC_MEDIUMRAW; - break; + return 0; case K_XLATE: kb->kbdmode = VC_XLATE; do_compute_shiftstate(); - break; + return 0; case K_UNICODE: kb->kbdmode = VC_UNICODE; do_compute_shiftstate(); - break; + return 0; case K_OFF: kb->kbdmode = VC_OFF; - break; + return 0; default: - ret = -EINVAL; + return -EINVAL; } - spin_unlock_irqrestore(&kbd_event_lock, flags); - return ret; } /** @@ -1861,22 +1828,18 @@ int vt_do_kdskbmode(unsigned int console, unsigned int arg) int vt_do_kdskbmeta(unsigned int console, unsigned int arg) { struct kbd_struct *kb = &kbd_table[console]; - int ret = 0; - unsigned long flags; - spin_lock_irqsave(&kbd_event_lock, flags); + guard(spinlock_irqsave)(&kbd_event_lock); switch(arg) { case K_METABIT: clr_vc_kbd_mode(kb, VC_META); - break; + return 0; case K_ESCPREFIX: set_vc_kbd_mode(kb, VC_META); - break; + return 0; default: - ret = -EINVAL; + return -EINVAL; } - spin_unlock_irqrestore(&kbd_event_lock, flags); - return ret; } int vt_do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc, int perm) @@ -1905,31 +1868,28 @@ int vt_do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc, int perm) static unsigned short vt_kdgkbent(unsigned char kbdmode, unsigned char idx, unsigned char map) { - unsigned short *key_map, val; - unsigned long flags; + unsigned short *key_map; /* Ensure another thread doesn't free it under us */ - spin_lock_irqsave(&kbd_event_lock, flags); + guard(spinlock_irqsave)(&kbd_event_lock); key_map = key_maps[map]; if (key_map) { - val = U(key_map[idx]); + unsigned short val = U(key_map[idx]); if (kbdmode != VC_UNICODE && KTYP(val) >= NR_TYPES) - val = K_HOLE; - } else - val = idx ? K_HOLE : K_NOSUCHMAP; - spin_unlock_irqrestore(&kbd_event_lock, flags); + return K_HOLE; + return val; + } - return val; + return idx ? K_HOLE : K_NOSUCHMAP; } static int vt_kdskbent(unsigned char kbdmode, unsigned char idx, unsigned char map, unsigned short val) { - unsigned long flags; unsigned short *key_map, oldval; if (!idx && val == K_NOSUCHMAP) { - spin_lock_irqsave(&kbd_event_lock, flags); + guard(spinlock_irqsave)(&kbd_event_lock); /* deallocate map */ key_map = key_maps[map]; if (map && key_map) { @@ -1939,7 +1899,6 @@ static int vt_kdskbent(unsigned char kbdmode, unsigned char idx, keymap_count--; } } - spin_unlock_irqrestore(&kbd_event_lock, flags); return 0; } @@ -1961,16 +1920,14 @@ static int vt_kdskbent(unsigned char kbdmode, unsigned char idx, if (!new_map) return -ENOMEM; - spin_lock_irqsave(&kbd_event_lock, flags); + guard(spinlock_irqsave)(&kbd_event_lock); key_map = key_maps[map]; if (key_map == NULL) { int j; - if (keymap_count >= MAX_NR_OF_USER_KEYMAPS && - !capable(CAP_SYS_RESOURCE)) { - spin_unlock_irqrestore(&kbd_event_lock, flags); + if (keymap_count >= MAX_NR_OF_USER_KEYMAPS && !capable(CAP_SYS_RESOURCE)) return -EPERM; - } + key_map = key_maps[map] = no_free_ptr(new_map); key_map[0] = U(K_ALLOCATED); for (j = 1; j < NR_KEYS; j++) @@ -1980,19 +1937,15 @@ static int vt_kdskbent(unsigned char kbdmode, unsigned char idx, oldval = U(key_map[idx]); if (val == oldval) - goto out; + return 0; /* Attention Key */ - if ((oldval == K_SAK || val == K_SAK) && !capable(CAP_SYS_ADMIN)) { - spin_unlock_irqrestore(&kbd_event_lock, flags); + if ((oldval == K_SAK || val == K_SAK) && !capable(CAP_SYS_ADMIN)) return -EPERM; - } key_map[idx] = U(val); if (!map && (KTYP(oldval) == KT_SHIFT || KTYP(val) == KT_SHIFT)) do_compute_shiftstate(); -out: - spin_unlock_irqrestore(&kbd_event_lock, flags); return 0; } @@ -2038,7 +1991,6 @@ static char *vt_kdskbsent(char *kbs, unsigned char cur) int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm) { unsigned char kb_func; - unsigned long flags; if (get_user(kb_func, &user_kdgkb->kb_func)) return -EFAULT; @@ -2054,9 +2006,8 @@ int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm) if (!kbs) return -ENOMEM; - spin_lock_irqsave(&func_buf_lock, flags); - len = strscpy(kbs, func_table[kb_func] ? : "", len); - spin_unlock_irqrestore(&func_buf_lock, flags); + scoped_guard(spinlock_irqsave, &func_buf_lock) + len = strscpy(kbs, func_table[kb_func] ? : "", len); if (len < 0) return -ENOSPC; @@ -2075,9 +2026,8 @@ int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm) if (IS_ERR(kbs)) return PTR_ERR(kbs); - spin_lock_irqsave(&func_buf_lock, flags); + guard(spinlock_irqsave)(&func_buf_lock); kbs = vt_kdskbsent(kbs, kb_func); - spin_unlock_irqrestore(&func_buf_lock, flags); return 0; } @@ -2088,16 +2038,14 @@ int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm) int vt_do_kdskled(unsigned int console, int cmd, unsigned long arg, int perm) { struct kbd_struct *kb = &kbd_table[console]; - unsigned long flags; unsigned char ucval; switch(cmd) { /* the ioctls below read/set the flags usually shown in the leds */ /* don't use them - they will go away without warning */ case KDGKBLED: - spin_lock_irqsave(&kbd_event_lock, flags); - ucval = kb->ledflagstate | (kb->default_ledflagstate << 4); - spin_unlock_irqrestore(&kbd_event_lock, flags); + scoped_guard(spinlock_irqsave, &kbd_event_lock) + ucval = kb->ledflagstate | (kb->default_ledflagstate << 4); return put_user(ucval, (char __user *)arg); case KDSKBLED: @@ -2105,11 +2053,11 @@ int vt_do_kdskled(unsigned int console, int cmd, unsigned long arg, int perm) return -EPERM; if (arg & ~0x77) return -EINVAL; - spin_lock_irqsave(&led_lock, flags); - kb->ledflagstate = (arg & 7); - kb->default_ledflagstate = ((arg >> 4) & 7); - set_leds(); - spin_unlock_irqrestore(&led_lock, flags); + scoped_guard(spinlock_irqsave, &led_lock) { + kb->ledflagstate = (arg & 7); + kb->default_ledflagstate = ((arg >> 4) & 7); + set_leds(); + } return 0; /* the ioctls below only set the lights, not the functions */ @@ -2166,11 +2114,8 @@ int vt_do_kdgkbmeta(unsigned int console) */ void vt_reset_unicode(unsigned int console) { - unsigned long flags; - - spin_lock_irqsave(&kbd_event_lock, flags); + guard(spinlock_irqsave)(&kbd_event_lock); kbd_table[console].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE; - spin_unlock_irqrestore(&kbd_event_lock, flags); } /** @@ -2195,22 +2140,19 @@ int vt_get_shift_state(void) void vt_reset_keyboard(unsigned int console) { struct kbd_struct *kb = &kbd_table[console]; - unsigned long flags; - spin_lock_irqsave(&kbd_event_lock, flags); + guard(spinlock_irqsave)(&kbd_event_lock); set_vc_kbd_mode(kb, VC_REPEAT); clr_vc_kbd_mode(kb, VC_CKMODE); clr_vc_kbd_mode(kb, VC_APPLIC); clr_vc_kbd_mode(kb, VC_CRLF); kb->lockstate = 0; kb->slockstate = 0; - spin_lock(&led_lock); + guard(spinlock)(&led_lock); kb->ledmode = LED_SHOW_FLAGS; kb->ledflagstate = kb->default_ledflagstate; - spin_unlock(&led_lock); /* do not do set_leds here because this causes an endless tasklet loop when the keyboard hasn't been initialized yet */ - spin_unlock_irqrestore(&kbd_event_lock, flags); } /** @@ -2240,11 +2182,9 @@ int vt_get_kbd_mode_bit(unsigned int console, int bit) void vt_set_kbd_mode_bit(unsigned int console, int bit) { struct kbd_struct *kb = &kbd_table[console]; - unsigned long flags; - spin_lock_irqsave(&kbd_event_lock, flags); + guard(spinlock_irqsave)(&kbd_event_lock); set_vc_kbd_mode(kb, bit); - spin_unlock_irqrestore(&kbd_event_lock, flags); } /** @@ -2259,9 +2199,7 @@ void vt_set_kbd_mode_bit(unsigned int console, int bit) void vt_clr_kbd_mode_bit(unsigned int console, int bit) { struct kbd_struct *kb = &kbd_table[console]; - unsigned long flags; - spin_lock_irqsave(&kbd_event_lock, flags); + guard(spinlock_irqsave)(&kbd_event_lock); clr_vc_kbd_mode(kb, bit); - spin_unlock_irqrestore(&kbd_event_lock, flags); } From f374a33e90e6cc17b05629636e68a012dc8347ed Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 11:01:39 +0100 Subject: [PATCH 37/60] serial: serial_core: simplify uart_ioctl() returns Neither uart_do_autoconfig(), nor uart_wait_modem_status() can return -ENOIOCTLCMD. The ENOIOCTLCMD checks are there to check if 'cmd' matched against TIOCSERCONFIG, and TIOCMIWAIT respectively. (With 0 or error in 'ret', it does not matter.) Therefore, the code can simply return from the TIOCSERCONFIG and TIOCMIWAIT spots immediately. To be more explicit, use 'if' instead of switch-case for those single values. And return without jumping to the 'out' label -- it can be removed too. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119100140.830761-10-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 35 ++++++++------------------------ 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 4757293ece8c..74018fb8a4e7 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1560,37 +1560,20 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) void __user *uarg = (void __user *)arg; int ret = -ENOIOCTLCMD; - - /* - * These ioctls don't rely on the hardware to be present. - */ - switch (cmd) { - case TIOCSERCONFIG: + /* This ioctl doesn't rely on the hardware to be present. */ + if (cmd == TIOCSERCONFIG) { down_write(&tty->termios_rwsem); ret = uart_do_autoconfig(tty, state); up_write(&tty->termios_rwsem); - break; + return ret; } - if (ret != -ENOIOCTLCMD) - goto out; + if (tty_io_error(tty)) + return -EIO; - if (tty_io_error(tty)) { - ret = -EIO; - goto out; - } - - /* - * The following should only be used when hardware is present. - */ - switch (cmd) { - case TIOCMIWAIT: - ret = uart_wait_modem_status(state, arg); - break; - } - - if (ret != -ENOIOCTLCMD) - goto out; + /* This should only be used when the hardware is present. */ + if (cmd == TIOCMIWAIT) + return uart_wait_modem_status(state, arg); /* rs485_config requires more locking than others */ if (cmd == TIOCSRS485) @@ -1638,7 +1621,7 @@ out_up: mutex_unlock(&port->mutex); if (cmd == TIOCSRS485) up_write(&tty->termios_rwsem); -out: + return ret; } From b844e63807ecc5dcfe80e9920e5d14c5a4011aa4 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 11:01:40 +0100 Subject: [PATCH 38/60] serial: serial_core: use guard()s Use guards in the serial_core code. This improves readability, makes error handling easier, and marks locked portions of code explicit. All that while being sure the lock is unlocked. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119100140.830761-11-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 137 ++++++++++++++----------------- 1 file changed, 60 insertions(+), 77 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 74018fb8a4e7..c532235f8d55 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1034,9 +1034,8 @@ static int uart_set_info_user(struct tty_struct *tty, struct serial_struct *ss) { struct uart_state *state = tty->driver_data; struct tty_port *port = &state->port; - int retval; - down_write(&tty->termios_rwsem); + guard(rwsem_write)(&tty->termios_rwsem); /* * This semaphore protects port->count. It is also * very useful to prevent opens. Also, take the @@ -1044,11 +1043,8 @@ static int uart_set_info_user(struct tty_struct *tty, struct serial_struct *ss) * module insertion/removal doesn't change anything * under us. */ - mutex_lock(&port->mutex); - retval = uart_set_info(tty, port, state, ss); - mutex_unlock(&port->mutex); - up_write(&tty->termios_rwsem); - return retval; + guard(mutex)(&port->mutex); + return uart_set_info(tty, port, state, ss); } /** @@ -1562,10 +1558,8 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) /* This ioctl doesn't rely on the hardware to be present. */ if (cmd == TIOCSERCONFIG) { - down_write(&tty->termios_rwsem); - ret = uart_do_autoconfig(tty, state); - up_write(&tty->termios_rwsem); - return ret; + guard(rwsem_write)(&tty->termios_rwsem); + return uart_do_autoconfig(tty, state); } if (tty_io_error(tty)) @@ -1579,46 +1573,46 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) if (cmd == TIOCSRS485) down_write(&tty->termios_rwsem); - mutex_lock(&port->mutex); - uport = uart_port_check(state); + scoped_guard(mutex, &port->mutex) { + uport = uart_port_check(state); - if (!uport || tty_io_error(tty)) { - ret = -EIO; - goto out_up; + if (!uport || tty_io_error(tty)) { + ret = -EIO; + break; + } + + /* + * All these rely on hardware being present and need to be + * protected against the tty being hung up. + */ + + switch (cmd) { + case TIOCSERGETLSR: /* Get line status register */ + ret = uart_get_lsr_info(tty, state, uarg); + break; + + case TIOCGRS485: + ret = uart_get_rs485_config(uport, uarg); + break; + + case TIOCSRS485: + ret = uart_set_rs485_config(tty, uport, uarg); + break; + + case TIOCSISO7816: + ret = uart_set_iso7816_config(state->uart_port, uarg); + break; + + case TIOCGISO7816: + ret = uart_get_iso7816_config(state->uart_port, uarg); + break; + default: + if (uport->ops->ioctl) + ret = uport->ops->ioctl(uport, cmd, arg); + break; + } } - /* - * All these rely on hardware being present and need to be - * protected against the tty being hung up. - */ - - switch (cmd) { - case TIOCSERGETLSR: /* Get line status register */ - ret = uart_get_lsr_info(tty, state, uarg); - break; - - case TIOCGRS485: - ret = uart_get_rs485_config(uport, uarg); - break; - - case TIOCSRS485: - ret = uart_set_rs485_config(tty, uport, uarg); - break; - - case TIOCSISO7816: - ret = uart_set_iso7816_config(state->uart_port, uarg); - break; - - case TIOCGISO7816: - ret = uart_get_iso7816_config(state->uart_port, uarg); - break; - default: - if (uport->ops->ioctl) - ret = uport->ops->ioctl(uport, cmd, arg); - break; - } -out_up: - mutex_unlock(&port->mutex); if (cmd == TIOCSRS485) up_write(&tty->termios_rwsem); @@ -1634,11 +1628,10 @@ static void uart_set_ldisc(struct tty_struct *tty) if (!tty_port_initialized(port)) return; - mutex_lock(&state->port.mutex); + guard(mutex)(&state->port.mutex); uport = uart_port_check(state); if (uport && uport->ops->set_ldisc) uport->ops->set_ldisc(uport, &tty->termios); - mutex_unlock(&state->port.mutex); } static void uart_set_termios(struct tty_struct *tty, @@ -1712,9 +1705,8 @@ static void uart_close(struct tty_struct *tty, struct file *filp) state = drv->state + tty->index; port = &state->port; - spin_lock_irq(&port->lock); + guard(spinlock_irq)(&port->lock); --port->count; - spin_unlock_irq(&port->lock); return; } @@ -1826,20 +1818,18 @@ static void uart_hangup(struct tty_struct *tty) struct uart_state *state = tty->driver_data; struct tty_port *port = &state->port; struct uart_port *uport; - unsigned long flags; pr_debug("uart_hangup(%d)\n", tty->index); - mutex_lock(&port->mutex); + guard(mutex)(&port->mutex); uport = uart_port_check(state); WARN(!uport, "hangup of detached port!\n"); if (tty_port_active(port)) { uart_flush_buffer(tty); uart_shutdown(tty, state); - spin_lock_irqsave(&port->lock, flags); - port->count = 0; - spin_unlock_irqrestore(&port->lock, flags); + scoped_guard(spinlock_irqsave, &port->lock) + port->count = 0; tty_port_set_active(port, false); tty_port_tty_set(port, NULL); if (uport && !uart_console(uport)) @@ -1847,7 +1837,6 @@ static void uart_hangup(struct tty_struct *tty) wake_up_interruptible(&port->open_wait); wake_up_interruptible(&port->delta_msr_wait); } - mutex_unlock(&port->mutex); } /* uport == NULL if uart_port has already been removed */ @@ -2952,11 +2941,11 @@ static ssize_t console_show(struct device *dev, struct uart_port *uport; bool console = false; - mutex_lock(&port->mutex); - uport = uart_port_check(state); - if (uport) - console = uart_console_registered(uport); - mutex_unlock(&port->mutex); + scoped_guard(mutex, &port->mutex) { + uport = uart_port_check(state); + if (uport) + console = uart_console_registered(uport); + } return sprintf(buf, "%c\n", console ? 'Y' : 'N'); } @@ -3141,17 +3130,14 @@ static void serial_core_remove_one_port(struct uart_driver *drv, struct tty_port *port = &state->port; struct uart_port *uart_port; - mutex_lock(&port->mutex); - uart_port = uart_port_check(state); - if (uart_port != uport) - dev_alert(uport->dev, "Removing wrong port: %p != %p\n", - uart_port, uport); + scoped_guard(mutex, &port->mutex) { + uart_port = uart_port_check(state); + if (uart_port != uport) + dev_alert(uport->dev, "Removing wrong port: %p != %p\n", uart_port, uport); - if (!uart_port) { - mutex_unlock(&port->mutex); - return; + if (!uart_port) + return; } - mutex_unlock(&port->mutex); /* * Remove the devices from the tty layer @@ -3180,11 +3166,10 @@ static void serial_core_remove_one_port(struct uart_driver *drv, uport->type = PORT_UNKNOWN; uport->port_dev = NULL; - mutex_lock(&port->mutex); + guard(mutex)(&port->mutex); WARN_ON(atomic_dec_return(&state->refcount) < 0); wait_event(state->remove_wait, !atomic_read(&state->refcount)); state->uart_port = NULL; - mutex_unlock(&port->mutex); } /** @@ -3337,7 +3322,7 @@ void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev); int ctrl_id = port->ctrl_id; - mutex_lock(&port_mutex); + guard(mutex)(&port_mutex); port->flags |= UPF_DEAD; @@ -3349,8 +3334,6 @@ void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port /* Drop the serial core controller device if no ports are using it */ if (!serial_core_ctrl_find(drv, phys_dev, ctrl_id)) serial_base_ctrl_device_remove(ctrl_dev); - - mutex_unlock(&port_mutex); } /** From 80a3471f59ef6284f7ca78ffc36d90007536ee0a Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 10:24:52 +0100 Subject: [PATCH 39/60] tty: vt: do not open code DIV_ROUND_UP() Use the designated DIV_ROUND_UP() macro instead of explicit addition with division. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119092457.826789-2-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 6e0089b85c27..59b4b5e126ba 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -4862,7 +4862,7 @@ static int con_font_get(struct vc_data *vc, struct console_font_op *op) return ret; } - c = (font.width+7)/8 * vpitch * font.charcount; + c = DIV_ROUND_UP(font.width, 8) * vpitch * font.charcount; if (op->data && font.charcount > op->charcount) return -ENOSPC; @@ -4894,7 +4894,7 @@ static int con_font_set(struct vc_data *vc, const struct console_font_op *op) return -EINVAL; if (vpitch < op->height) return -EINVAL; - size = (op->width+7)/8 * vpitch * op->charcount; + size = DIV_ROUND_UP(op->width, 8) * vpitch * op->charcount; if (size > max_font_size) return -ENOSPC; From 6d4b55bf18c6dc74221f76770c8e4862f1a2b084 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 10:24:53 +0100 Subject: [PATCH 40/60] serial: xilinx_uartps: drop cdns_uart::cdns_uart_driver Provided the uart driver is available globally, there is no need to store a pointer to it in struct cdns_uart. Instead, use the global cdns_uart_uart_driver in the code directly. Signed-off-by: Jiri Slaby (SUSE) Acked-by: Michal Simek Link: https://patch.msgid.link/20251119092457.826789-3-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/xilinx_uartps.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/tty/serial/xilinx_uartps.c b/drivers/tty/serial/xilinx_uartps.c index a66b44d21fba..c793fc74c26b 100644 --- a/drivers/tty/serial/xilinx_uartps.c +++ b/drivers/tty/serial/xilinx_uartps.c @@ -190,7 +190,6 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255"); * @port: Pointer to the UART port * @uartclk: Reference clock * @pclk: APB clock - * @cdns_uart_driver: Pointer to UART driver * @baud: Current baud rate * @clk_rate_change_nb: Notifier block for clock changes * @quirks: Flags for RXBS support. @@ -204,7 +203,6 @@ struct cdns_uart { struct uart_port *port; struct clk *uartclk; struct clk *pclk; - struct uart_driver *cdns_uart_driver; unsigned int baud; struct notifier_block clk_rate_change_nb; u32 quirks; @@ -1465,7 +1463,6 @@ static struct console cdns_uart_console = { static int cdns_uart_suspend(struct device *device) { struct uart_port *port = dev_get_drvdata(device); - struct cdns_uart *cdns_uart = port->private_data; int may_wake; may_wake = device_may_wakeup(device); @@ -1489,7 +1486,7 @@ static int cdns_uart_suspend(struct device *device) * Call the API provided in serial_core.c file which handles * the suspend. */ - return uart_suspend_port(cdns_uart->cdns_uart_driver, port); + return uart_suspend_port(&cdns_uart_uart_driver, port); } /** @@ -1550,7 +1547,7 @@ static int cdns_uart_resume(struct device *device) uart_port_unlock_irqrestore(port, flags); } - return uart_resume_port(cdns_uart->cdns_uart_driver, port); + return uart_resume_port(&cdns_uart_uart_driver, port); } #endif /* ! CONFIG_PM_SLEEP */ static int __maybe_unused cdns_runtime_suspend(struct device *dev) @@ -1686,8 +1683,6 @@ static int cdns_uart_probe(struct platform_device *pdev) } } - cdns_uart_data->cdns_uart_driver = &cdns_uart_uart_driver; - match = of_match_node(cdns_uart_of_match, pdev->dev.of_node); if (match && match->data) { const struct cdns_platform_data *data = match->data; @@ -1862,7 +1857,7 @@ err_out_clk_dis_pclk: clk_disable_unprepare(cdns_uart_data->pclk); err_out_unregister_driver: if (!instances) - uart_unregister_driver(cdns_uart_data->cdns_uart_driver); + uart_unregister_driver(&cdns_uart_uart_driver); return rc; } @@ -1880,7 +1875,7 @@ static void cdns_uart_remove(struct platform_device *pdev) clk_notifier_unregister(cdns_uart_data->uartclk, &cdns_uart_data->clk_rate_change_nb); #endif - uart_remove_one_port(cdns_uart_data->cdns_uart_driver, port); + uart_remove_one_port(&cdns_uart_uart_driver, port); port->mapbase = 0; clk_disable_unprepare(cdns_uart_data->uartclk); clk_disable_unprepare(cdns_uart_data->pclk); @@ -1896,7 +1891,7 @@ static void cdns_uart_remove(struct platform_device *pdev) reset_control_assert(cdns_uart_data->rstc); if (!--instances) - uart_unregister_driver(cdns_uart_data->cdns_uart_driver); + uart_unregister_driver(&cdns_uart_uart_driver); } static struct platform_driver cdns_uart_platform_driver = { From 37d55c92e9db3f7fd3772199ffdfe782fc753fc1 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 10:24:54 +0100 Subject: [PATCH 41/60] serial: drop SERIAL_8250_DEPRECATED_OPTIONS In 3.7, 8250 was unintentionally renamed to 8250_core. This happened in the commit 835d844d1a28 (8250_pnp: do pnp probe before legacy probe). This made 8250. module options effectively defunct. Instead, 8250_core. worked. In 3.9, the commit f2b8dfd9e480 (serial: 8250: Keep 8250. module options functional after driver rename) made the original options work again by introducing a hack. Later in 3.9, the commit 9196d8acd7f9 (TTY: 8250, revert module name change) changed the module name back to 8250 (from 8250_core). Since then, the hack was there to support the transient 8250_core. options. Those were present only in the 3.7..3.9 range. These transient options were deprecated by 9326b047e4fd (TTY: 8250, deprecated 8250_core.* options) in v3.9 too. Now, after those 12 years, it is time to get rid of this hack completely. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119092457.826789-4-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- arch/arm/configs/aspeed_g4_defconfig | 1 - arch/arm/configs/aspeed_g5_defconfig | 1 - arch/arm/configs/hisi_defconfig | 1 - arch/arm/configs/lpc18xx_defconfig | 1 - arch/arm/configs/shmobile_defconfig | 1 - arch/mips/configs/bcm47xx_defconfig | 1 - arch/mips/configs/bmips_stb_defconfig | 1 - arch/mips/configs/gcw0_defconfig | 1 - arch/nios2/configs/10m50_defconfig | 1 - arch/parisc/configs/generic-32bit_defconfig | 1 - arch/parisc/configs/generic-64bit_defconfig | 1 - arch/powerpc/configs/44x/akebono_defconfig | 1 - arch/powerpc/configs/microwatt_defconfig | 1 - arch/riscv/configs/nommu_virt_defconfig | 1 - arch/xtensa/configs/audio_kc705_defconfig | 1 - arch/xtensa/configs/generic_kc705_defconfig | 1 - arch/xtensa/configs/nommu_kc705_defconfig | 1 - arch/xtensa/configs/smp_lx200_defconfig | 1 - arch/xtensa/configs/xip_kc705_defconfig | 1 - drivers/tty/serial/8250/8250_platform.c | 27 --------------------- drivers/tty/serial/8250/8250_rsa.c | 24 ------------------ drivers/tty/serial/8250/Kconfig | 17 ------------- 22 files changed, 87 deletions(-) diff --git a/arch/arm/configs/aspeed_g4_defconfig b/arch/arm/configs/aspeed_g4_defconfig index 28b724d59e7e..45d8738abb75 100644 --- a/arch/arm/configs/aspeed_g4_defconfig +++ b/arch/arm/configs/aspeed_g4_defconfig @@ -117,7 +117,6 @@ CONFIG_KEYBOARD_GPIO_POLLED=y # CONFIG_VT is not set # CONFIG_LEGACY_PTYS is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_NR_UARTS=6 CONFIG_SERIAL_8250_RUNTIME_UARTS=6 diff --git a/arch/arm/configs/aspeed_g5_defconfig b/arch/arm/configs/aspeed_g5_defconfig index 61cee1e7ebea..46081c9a537e 100644 --- a/arch/arm/configs/aspeed_g5_defconfig +++ b/arch/arm/configs/aspeed_g5_defconfig @@ -138,7 +138,6 @@ CONFIG_SERIO_RAW=y # CONFIG_VT is not set # CONFIG_LEGACY_PTYS is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_NR_UARTS=6 CONFIG_SERIAL_8250_RUNTIME_UARTS=6 diff --git a/arch/arm/configs/hisi_defconfig b/arch/arm/configs/hisi_defconfig index e19c1039fb93..384aade1a48b 100644 --- a/arch/arm/configs/hisi_defconfig +++ b/arch/arm/configs/hisi_defconfig @@ -35,7 +35,6 @@ CONFIG_NETDEVICES=y CONFIG_HIX5HD2_GMAC=y CONFIG_HIP04_ETH=y CONFIG_SERIAL_8250=y -CONFIG_SERIAL_8250_DEPRECATED_OPTIONS=y CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_NR_UARTS=2 CONFIG_SERIAL_8250_RUNTIME_UARTS=2 diff --git a/arch/arm/configs/lpc18xx_defconfig b/arch/arm/configs/lpc18xx_defconfig index 2d489186e945..f142a6637ede 100644 --- a/arch/arm/configs/lpc18xx_defconfig +++ b/arch/arm/configs/lpc18xx_defconfig @@ -90,7 +90,6 @@ CONFIG_KEYBOARD_GPIO_POLLED=y # CONFIG_UNIX98_PTYS is not set # CONFIG_LEGACY_PTYS is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_NONSTANDARD=y diff --git a/arch/arm/configs/shmobile_defconfig b/arch/arm/configs/shmobile_defconfig index e4cb33b2bcee..e8dd9a80a64a 100644 --- a/arch/arm/configs/shmobile_defconfig +++ b/arch/arm/configs/shmobile_defconfig @@ -75,7 +75,6 @@ CONFIG_INPUT_DA9063_ONKEY=y CONFIG_INPUT_ADXL34X=y # CONFIG_LEGACY_PTYS is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set # CONFIG_SERIAL_8250_16550A_VARIANTS is not set CONFIG_SERIAL_8250_CONSOLE=y # CONFIG_SERIAL_8250_PCI is not set diff --git a/arch/mips/configs/bcm47xx_defconfig b/arch/mips/configs/bcm47xx_defconfig index f56e8db5da95..d10b3d4adbd1 100644 --- a/arch/mips/configs/bcm47xx_defconfig +++ b/arch/mips/configs/bcm47xx_defconfig @@ -51,7 +51,6 @@ CONFIG_B43LEGACY=y CONFIG_BRCMSMAC=y CONFIG_ISDN=y CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y # CONFIG_SERIAL_8250_PCI is not set CONFIG_SERIAL_8250_NR_UARTS=2 diff --git a/arch/mips/configs/bmips_stb_defconfig b/arch/mips/configs/bmips_stb_defconfig index cd0dc37c3d84..ecfa7f777efa 100644 --- a/arch/mips/configs/bmips_stb_defconfig +++ b/arch/mips/configs/bmips_stb_defconfig @@ -119,7 +119,6 @@ CONFIG_INPUT_UINPUT=y CONFIG_VT=y CONFIG_VT_HW_CONSOLE_BINDING=y CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_OF_PLATFORM=y # CONFIG_HW_RANDOM is not set diff --git a/arch/mips/configs/gcw0_defconfig b/arch/mips/configs/gcw0_defconfig index 8b7ad877e07a..fda9971bdd8d 100644 --- a/arch/mips/configs/gcw0_defconfig +++ b/arch/mips/configs/gcw0_defconfig @@ -52,7 +52,6 @@ CONFIG_INPUT_UINPUT=y CONFIG_INPUT_PWM_VIBRA=y # CONFIG_SERIO is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_INGENIC=y CONFIG_HW_RANDOM=y diff --git a/arch/nios2/configs/10m50_defconfig b/arch/nios2/configs/10m50_defconfig index 048f74e0dc6d..b7224f44d327 100644 --- a/arch/nios2/configs/10m50_defconfig +++ b/arch/nios2/configs/10m50_defconfig @@ -51,7 +51,6 @@ CONFIG_MARVELL_PHY=y # CONFIG_SERIO_SERPORT is not set # CONFIG_VT is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_ALTERA_JTAGUART=y diff --git a/arch/parisc/configs/generic-32bit_defconfig b/arch/parisc/configs/generic-32bit_defconfig index 52031bde9f17..5444ce6405f3 100644 --- a/arch/parisc/configs/generic-32bit_defconfig +++ b/arch/parisc/configs/generic-32bit_defconfig @@ -119,7 +119,6 @@ CONFIG_INPUT_MISC=y CONFIG_INPUT_UINPUT=m CONFIG_LEGACY_PTY_COUNT=64 CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_NR_UARTS=8 CONFIG_SERIAL_8250_EXTENDED=y diff --git a/arch/parisc/configs/generic-64bit_defconfig b/arch/parisc/configs/generic-64bit_defconfig index 1aec04c09d0b..ce91f9d1fdbf 100644 --- a/arch/parisc/configs/generic-64bit_defconfig +++ b/arch/parisc/configs/generic-64bit_defconfig @@ -158,7 +158,6 @@ CONFIG_SERIO_SERPORT=m CONFIG_SERIO_RAW=m # CONFIG_LEGACY_PTYS is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_NR_UARTS=8 CONFIG_SERIAL_8250_RUNTIME_UARTS=8 diff --git a/arch/powerpc/configs/44x/akebono_defconfig b/arch/powerpc/configs/44x/akebono_defconfig index 1882eb2da354..02e88648a2e6 100644 --- a/arch/powerpc/configs/44x/akebono_defconfig +++ b/arch/powerpc/configs/44x/akebono_defconfig @@ -85,7 +85,6 @@ CONFIG_IBM_EMAC=y # CONFIG_SERIO is not set # CONFIG_VT is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_EXTENDED=y CONFIG_SERIAL_8250_SHARE_IRQ=y diff --git a/arch/powerpc/configs/microwatt_defconfig b/arch/powerpc/configs/microwatt_defconfig index a64fb1ef8c75..d81989a6f59b 100644 --- a/arch/powerpc/configs/microwatt_defconfig +++ b/arch/powerpc/configs/microwatt_defconfig @@ -62,7 +62,6 @@ CONFIG_LITEX_LITEETH=y # CONFIG_VT is not set # CONFIG_LEGACY_PTYS is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_NONSTANDARD=y diff --git a/arch/riscv/configs/nommu_virt_defconfig b/arch/riscv/configs/nommu_virt_defconfig index d4b03dc3c2c0..0da5069bfbef 100644 --- a/arch/riscv/configs/nommu_virt_defconfig +++ b/arch/riscv/configs/nommu_virt_defconfig @@ -48,7 +48,6 @@ CONFIG_VIRTIO_BLK=y # CONFIG_LEGACY_PTYS is not set # CONFIG_LDISC_AUTOLOAD is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_NR_UARTS=1 CONFIG_SERIAL_8250_RUNTIME_UARTS=1 diff --git a/arch/xtensa/configs/audio_kc705_defconfig b/arch/xtensa/configs/audio_kc705_defconfig index dc942bbac69f..7b3f234b337c 100644 --- a/arch/xtensa/configs/audio_kc705_defconfig +++ b/arch/xtensa/configs/audio_kc705_defconfig @@ -81,7 +81,6 @@ CONFIG_MARVELL_PHY=y # CONFIG_INPUT_MOUSE is not set # CONFIG_SERIO is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_OF_PLATFORM=y CONFIG_HW_RANDOM=y diff --git a/arch/xtensa/configs/generic_kc705_defconfig b/arch/xtensa/configs/generic_kc705_defconfig index 3ee7e1c56556..c1b766a77a1e 100644 --- a/arch/xtensa/configs/generic_kc705_defconfig +++ b/arch/xtensa/configs/generic_kc705_defconfig @@ -79,7 +79,6 @@ CONFIG_MARVELL_PHY=y # CONFIG_INPUT_MOUSE is not set # CONFIG_SERIO is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_OF_PLATFORM=y CONFIG_HW_RANDOM=y diff --git a/arch/xtensa/configs/nommu_kc705_defconfig b/arch/xtensa/configs/nommu_kc705_defconfig index c6e96f0aa700..27b126d4e7bf 100644 --- a/arch/xtensa/configs/nommu_kc705_defconfig +++ b/arch/xtensa/configs/nommu_kc705_defconfig @@ -81,7 +81,6 @@ CONFIG_MARVELL_PHY=y # CONFIG_INPUT_MOUSE is not set # CONFIG_SERIO is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_OF_PLATFORM=y CONFIG_HW_RANDOM=y diff --git a/arch/xtensa/configs/smp_lx200_defconfig b/arch/xtensa/configs/smp_lx200_defconfig index 373d42b9e510..dfb3d921b850 100644 --- a/arch/xtensa/configs/smp_lx200_defconfig +++ b/arch/xtensa/configs/smp_lx200_defconfig @@ -83,7 +83,6 @@ CONFIG_MARVELL_PHY=y # CONFIG_INPUT_MOUSE is not set # CONFIG_SERIO is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_OF_PLATFORM=y CONFIG_HW_RANDOM=y diff --git a/arch/xtensa/configs/xip_kc705_defconfig b/arch/xtensa/configs/xip_kc705_defconfig index 5d6013ea70fc..472568b85fb9 100644 --- a/arch/xtensa/configs/xip_kc705_defconfig +++ b/arch/xtensa/configs/xip_kc705_defconfig @@ -72,7 +72,6 @@ CONFIG_MARVELL_PHY=y # CONFIG_INPUT_MOUSE is not set # CONFIG_SERIO is not set CONFIG_SERIAL_8250=y -# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_OF_PLATFORM=y # CONFIG_HWMON is not set diff --git a/drivers/tty/serial/8250/8250_platform.c b/drivers/tty/serial/8250/8250_platform.c index b27981340e76..38b158ec4725 100644 --- a/drivers/tty/serial/8250/8250_platform.c +++ b/drivers/tty/serial/8250/8250_platform.c @@ -390,30 +390,3 @@ module_param(skip_txen_test, uint, 0644); MODULE_PARM_DESC(skip_txen_test, "Skip checking for the TXEN bug at init time"); MODULE_ALIAS_CHARDEV_MAJOR(TTY_MAJOR); - -#ifdef CONFIG_SERIAL_8250_DEPRECATED_OPTIONS -#ifndef MODULE -/* - * This module was renamed to 8250_core in 3.7. Keep the old "8250" name - * working as well for the module options so we don't break people. We - * need to keep the names identical and the convenient macros will happily - * refuse to let us do that by failing the build with redefinition errors - * of global variables. So we stick them inside a dummy function to avoid - * those conflicts. The options still get parsed, and the redefined - * MODULE_PARAM_PREFIX lets us keep the "8250." syntax alive. - * - * This is hacky. I'm sorry. - */ -static void __used s8250_options(void) -{ -#undef MODULE_PARAM_PREFIX -#define MODULE_PARAM_PREFIX "8250_core." - - module_param_cb(share_irqs, ¶m_ops_uint, &share_irqs, 0644); - module_param_cb(nr_uarts, ¶m_ops_uint, &nr_uarts, 0644); - module_param_cb(skip_txen_test, ¶m_ops_uint, &skip_txen_test, 0644); -} -#else -MODULE_ALIAS("8250_core"); -#endif -#endif diff --git a/drivers/tty/serial/8250/8250_rsa.c b/drivers/tty/serial/8250/8250_rsa.c index 40a3dbd9e452..3b9c00515407 100644 --- a/drivers/tty/serial/8250/8250_rsa.c +++ b/drivers/tty/serial/8250/8250_rsa.c @@ -201,27 +201,3 @@ void rsa_reset(struct uart_8250_port *up) serial_out(up, UART_RSA_FRR, 0); } EXPORT_SYMBOL_FOR_MODULES(rsa_reset, "8250_base"); - -#ifdef CONFIG_SERIAL_8250_DEPRECATED_OPTIONS -#ifndef MODULE -/* - * Keep the old "8250" name working as well for the module options so we don't - * break people. We need to keep the names identical and the convenient macros - * will happily refuse to let us do that by failing the build with redefinition - * errors of global variables. So we stick them inside a dummy function to - * avoid those conflicts. The options still get parsed, and the redefined - * MODULE_PARAM_PREFIX lets us keep the "8250." syntax alive. - * - * This is hacky. I'm sorry. - */ -static void __used rsa8250_options(void) -{ -#undef MODULE_PARAM_PREFIX -#define MODULE_PARAM_PREFIX "8250_core." - - __module_param_call(MODULE_PARAM_PREFIX, probe_rsa, - ¶m_array_ops, .arr = &__param_arr_probe_rsa, - 0444, -1, 0); -} -#endif -#endif diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig index f64ef0819cd4..58f0142a59ff 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -34,23 +34,6 @@ config SERIAL_8250 Most people will say Y or M here, so that they can use serial mice, modems and similar devices connecting to the standard serial ports. -config SERIAL_8250_DEPRECATED_OPTIONS - bool "Support 8250_core.* kernel options (DEPRECATED)" - depends on SERIAL_8250 - default y - help - In 3.7 we renamed 8250 to 8250_core by mistake, so now we have to - accept kernel parameters in both forms like 8250_core.nr_uarts=4 and - 8250.nr_uarts=4. We now renamed the module back to 8250, but if - anybody noticed in 3.7 and changed their userspace we still have to - keep the 8250_core.* options around until they revert the changes - they already did. - - If 8250 is built as a module, this adds 8250_core alias instead. - - If you did not notice yet and/or you have userspace from pre-3.7, it - is safe (and recommended) to say N here. - config SERIAL_8250_PNP bool "8250/16550 PNP device support" if EXPERT depends on SERIAL_8250 && PNP From 9b2259633bda9875c1305309e3fc9a984691cadd Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 10:24:55 +0100 Subject: [PATCH 42/60] serial: 8250: move skip_txen_test to core 8250_core is the only place where skip_txen_test is used. And platform and core end up in 8250.ko, so there is no change in module name (param prefix). Therefore, move skip_txen_test there and make it local. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119092457.826789-5-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250.h | 1 - drivers/tty/serial/8250/8250_core.c | 4 ++++ drivers/tty/serial/8250/8250_platform.c | 5 ----- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index 58e64c4e1e3a..d8a726b355d5 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -105,7 +105,6 @@ extern unsigned int nr_uarts; #endif extern unsigned int share_irqs; -extern unsigned int skip_txen_test; #define SERIAL8250_PORT_FLAGS(_base, _irq, _flags) \ { \ diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index bfa421ab3253..0e81f78c6063 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -52,6 +52,10 @@ struct irq_info { static DEFINE_HASHTABLE(irq_lists, IRQ_HASH_BITS); static DEFINE_MUTEX(hash_mutex); /* Used to walk the hash */ +static bool skip_txen_test; +module_param(skip_txen_test, bool, 0644); +MODULE_PARM_DESC(skip_txen_test, "Skip checking for the TXEN bug at init time"); + /* * This is the serial driver's interrupt routine. * diff --git a/drivers/tty/serial/8250/8250_platform.c b/drivers/tty/serial/8250/8250_platform.c index 38b158ec4725..6f09416d4107 100644 --- a/drivers/tty/serial/8250/8250_platform.c +++ b/drivers/tty/serial/8250/8250_platform.c @@ -29,10 +29,8 @@ * Configuration: * share_irqs: Whether we pass IRQF_SHARED to request_irq(). * This option is unsafe when used on edge-triggered interrupts. - * skip_txen_test: Force skip of txen test at init time. */ unsigned int share_irqs = SERIAL8250_SHARE_IRQS; -unsigned int skip_txen_test; unsigned int nr_uarts = CONFIG_SERIAL_8250_RUNTIME_UARTS; @@ -386,7 +384,4 @@ MODULE_PARM_DESC(share_irqs, "Share IRQs with other non-8250/16x50 devices (unsa module_param(nr_uarts, uint, 0644); MODULE_PARM_DESC(nr_uarts, "Maximum number of UARTs supported. (1-" __MODULE_STRING(CONFIG_SERIAL_8250_NR_UARTS) ")"); -module_param(skip_txen_test, uint, 0644); -MODULE_PARM_DESC(skip_txen_test, "Skip checking for the TXEN bug at init time"); - MODULE_ALIAS_CHARDEV_MAJOR(TTY_MAJOR); From f9066dac8ffe237817030d8505ed3dfdae7241ae Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 10:24:56 +0100 Subject: [PATCH 43/60] serial: 8250: make share_irqs local to 8250_platform share_irqs is used solely in 8250_platform. Make it local to that file. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119092457.826789-6-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250.h | 8 -------- drivers/tty/serial/8250/8250_platform.c | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index d8a726b355d5..3cd8614e6339 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -98,14 +98,6 @@ struct serial8250_config { extern unsigned int nr_uarts; -#ifdef CONFIG_SERIAL_8250_SHARE_IRQ -#define SERIAL8250_SHARE_IRQS 1 -#else -#define SERIAL8250_SHARE_IRQS 0 -#endif - -extern unsigned int share_irqs; - #define SERIAL8250_PORT_FLAGS(_base, _irq, _flags) \ { \ .iobase = _base, \ diff --git a/drivers/tty/serial/8250/8250_platform.c b/drivers/tty/serial/8250/8250_platform.c index 6f09416d4107..4c1166a46a1f 100644 --- a/drivers/tty/serial/8250/8250_platform.c +++ b/drivers/tty/serial/8250/8250_platform.c @@ -30,7 +30,7 @@ * share_irqs: Whether we pass IRQF_SHARED to request_irq(). * This option is unsafe when used on edge-triggered interrupts. */ -unsigned int share_irqs = SERIAL8250_SHARE_IRQS; +static bool share_irqs = IS_ENABLED(CONFIG_SERIAL_8250_SHARE_IRQ); unsigned int nr_uarts = CONFIG_SERIAL_8250_RUNTIME_UARTS; @@ -378,7 +378,7 @@ module_exit(serial8250_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Generic 8250/16x50 serial platform driver"); -module_param_hw(share_irqs, uint, other, 0644); +module_param_hw(share_irqs, bool, other, 0644); MODULE_PARM_DESC(share_irqs, "Share IRQs with other non-8250/16x50 devices (unsafe)"); module_param(nr_uarts, uint, 0644); From da218406dd50e0ac96bb383de4edd208286efe70 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 19 Nov 2025 10:24:57 +0100 Subject: [PATCH 44/60] serial: 8250_platform: simplify IRQF_SHARED handling IRQF_SHARED is the only flag handled in __serial8250_isa_init_ports() and serial8250_probe_platform(). There is no need to precompute the flags. Instead, initialize port->irqflags directly in the for loop. Note the "if (bool)" is cheap and these are not hot paths anyway. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20251119092457.826789-7-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_platform.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/tty/serial/8250/8250_platform.c b/drivers/tty/serial/8250/8250_platform.c index 4c1166a46a1f..af16a36193bf 100644 --- a/drivers/tty/serial/8250/8250_platform.c +++ b/drivers/tty/serial/8250/8250_platform.c @@ -58,7 +58,7 @@ EXPORT_SYMBOL(serial8250_set_isa_configurator); static void __init __serial8250_isa_init_ports(void) { - int i, irqflag = 0; + int i; if (nr_uarts > UART_NR) nr_uarts = UART_NR; @@ -75,9 +75,6 @@ static void __init __serial8250_isa_init_ports(void) univ8250_port_ops = *univ8250_port_base_ops; univ8250_rsa_support(&univ8250_port_ops); - if (share_irqs) - irqflag = IRQF_SHARED; - for (i = 0; i < ARRAY_SIZE(old_serial_port) && i < nr_uarts; i++) { struct uart_8250_port *up = serial8250_get_port(i); struct uart_port *port = &up->port; @@ -92,7 +89,9 @@ static void __init __serial8250_isa_init_ports(void) port->iotype = old_serial_port[i].io_type; port->regshift = old_serial_port[i].iomem_reg_shift; - port->irqflags |= irqflag; + if (share_irqs) + port->irqflags |= IRQF_SHARED; + if (serial8250_isa_config != NULL) serial8250_isa_config(i, &up->port, &up->capabilities); } @@ -155,15 +154,12 @@ static int serial8250_probe_acpi(struct platform_device *pdev) static int serial8250_probe_platform(struct platform_device *dev, struct plat_serial8250_port *p) { - int ret, i, irqflag = 0; + int ret, i; struct uart_8250_port *uart __free(kfree) = kzalloc(sizeof(*uart), GFP_KERNEL); if (!uart) return -ENOMEM; - if (share_irqs) - irqflag = IRQF_SHARED; - for (i = 0; p && p->flags != 0; p++, i++) { uart->port.iobase = p->iobase; uart->port.membase = p->membase; @@ -191,7 +187,10 @@ static int serial8250_probe_platform(struct platform_device *dev, struct plat_se uart->port.get_mctrl = p->get_mctrl; uart->port.pm = p->pm; uart->port.dev = &dev->dev; - uart->port.irqflags |= irqflag; + + if (share_irqs) + uart->port.irqflags |= IRQF_SHARED; + ret = serial8250_register_8250_port(uart); if (ret < 0) { dev_err(&dev->dev, "unable to register port at index %d " From 1879c2e44651d0854d3615590a638a88c5e292ad Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Tue, 4 Nov 2025 11:54:46 +0100 Subject: [PATCH 45/60] tty: replace use of system_unbound_wq with system_dfl_wq Currently if a user enqueue a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistentcy cannot be addressed without refactoring the API. This patch continues the effort to refactor worqueue APIs, which has begun with the change introducing new workqueues and a new alloc_workqueue flag: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") system_dfl_wq should be the default workqueue so as not to enforce locality constraints for random work whenever it's not required. The old system_unbound_wq will be kept for a few release cycles. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Link: https://patch.msgid.link/20251104105446.110884-1-marco.crivellari@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_dw.c | 4 ++-- drivers/tty/tty_buffer.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 710ae4d40aec..27af83f0ff46 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -361,7 +361,7 @@ static int dw8250_clk_notifier_cb(struct notifier_block *nb, * deferred event handling complication. */ if (event == POST_RATE_CHANGE) { - queue_work(system_unbound_wq, &d->clk_work); + queue_work(system_dfl_wq, &d->clk_work); return NOTIFY_OK; } @@ -680,7 +680,7 @@ static int dw8250_probe(struct platform_device *pdev) err = clk_notifier_register(data->clk, &data->clk_notifier); if (err) return dev_err_probe(dev, err, "Failed to set the clock notifier\n"); - queue_work(system_unbound_wq, &data->clk_work); + queue_work(system_dfl_wq, &data->clk_work); } platform_set_drvdata(pdev, data); diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 67271fc0b223..1a5673acd9b1 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -76,7 +76,7 @@ void tty_buffer_unlock_exclusive(struct tty_port *port) mutex_unlock(&buf->lock); if (restart) - queue_work(system_unbound_wq, &buf->work); + queue_work(system_dfl_wq, &buf->work); } EXPORT_SYMBOL_GPL(tty_buffer_unlock_exclusive); @@ -530,7 +530,7 @@ void tty_flip_buffer_push(struct tty_port *port) struct tty_bufhead *buf = &port->buf; tty_flip_buffer_commit(buf->tail); - queue_work(system_unbound_wq, &buf->work); + queue_work(system_dfl_wq, &buf->work); } EXPORT_SYMBOL(tty_flip_buffer_push); @@ -560,7 +560,7 @@ int tty_insert_flip_string_and_push_buffer(struct tty_port *port, tty_flip_buffer_commit(buf->tail); spin_unlock_irqrestore(&port->lock, flags); - queue_work(system_unbound_wq, &buf->work); + queue_work(system_dfl_wq, &buf->work); return size; } @@ -613,7 +613,7 @@ void tty_buffer_set_lock_subclass(struct tty_port *port) bool tty_buffer_restart_work(struct tty_port *port) { - return queue_work(system_unbound_wq, &port->buf.work); + return queue_work(system_dfl_wq, &port->buf.work); } bool tty_buffer_cancel_work(struct tty_port *port) From ae333a91006c7f13a921688546a09afa9bf05236 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 6 Nov 2025 12:38:15 +0100 Subject: [PATCH 46/60] serial: mux: Fix kernel doc for mux_poll() The validator is not happy: Warning: drivers/tty/serial/mux.c:351 expecting prototype for mux_drv_poll(). Prototype was for mux_poll() instead Update the kernel-doc accordingly. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20251106113815.2302539-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/mux.c b/drivers/tty/serial/mux.c index b417faead20f..3a77a7e5c7bc 100644 --- a/drivers/tty/serial/mux.c +++ b/drivers/tty/serial/mux.c @@ -343,7 +343,7 @@ static int mux_verify_port(struct uart_port *port, struct serial_struct *ser) } /** - * mux_drv_poll - Mux poll function. + * mux_poll - Mux poll function. * @unused: Unused variable * * This function periodically polls the Serial MUX to check for new data. From 0e5a99e0e5f50353b86939ff6e424800d769c818 Mon Sep 17 00:00:00 2001 From: Magne Bruno Date: Mon, 10 Nov 2025 17:24:56 +0100 Subject: [PATCH 47/60] serial: add support of CPCI cards Addi-Data GmbH is manufacturing multi-serial ports cards supporting CompactPCI (known as CPCI). Those cards are identified with different DeviceIds. Those cards integrating standard UARTs work the same way as PCI/PCIe models already supported in the serial driver. Signed-off-by: Magne Bruno Link: https://patch.msgid.link/20251110162456.341029-1-magne.bruno@addi-data.com Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_pci.c | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index f0f13fdda2df..dbd112ac7941 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -95,6 +95,11 @@ #define PCI_DEVICE_ID_MOXA_CP138E_A 0x1381 #define PCI_DEVICE_ID_MOXA_CP168EL_A 0x1683 +#define PCI_DEVICE_ID_ADDIDATA_CPCI7500 0x7003 +#define PCI_DEVICE_ID_ADDIDATA_CPCI7500_NG 0x7024 +#define PCI_DEVICE_ID_ADDIDATA_CPCI7420_NG 0x7025 +#define PCI_DEVICE_ID_ADDIDATA_CPCI7300_NG 0x7026 + /* Unknown vendors/cards - this should not be in linux/pci_ids.h */ #define PCI_SUBDEVICE_ID_UNKNOWN_0x1584 0x1584 #define PCI_SUBDEVICE_ID_UNKNOWN_0x1588 0x1588 @@ -6004,6 +6009,38 @@ static const struct pci_device_id serial_pci_tbl[] = { 0, pbn_ADDIDATA_PCIe_8_3906250 }, + { PCI_VENDOR_ID_ADDIDATA, + PCI_DEVICE_ID_ADDIDATA_CPCI7500, + PCI_ANY_ID, + PCI_ANY_ID, + 0, + 0, + pbn_b0_4_115200 }, + + { PCI_VENDOR_ID_ADDIDATA, + PCI_DEVICE_ID_ADDIDATA_CPCI7500_NG, + PCI_ANY_ID, + PCI_ANY_ID, + 0, + 0, + pbn_b0_4_115200 }, + + { PCI_VENDOR_ID_ADDIDATA, + PCI_DEVICE_ID_ADDIDATA_CPCI7420_NG, + PCI_ANY_ID, + PCI_ANY_ID, + 0, + 0, + pbn_b0_2_115200 }, + + { PCI_VENDOR_ID_ADDIDATA, + PCI_DEVICE_ID_ADDIDATA_CPCI7300_NG, + PCI_ANY_ID, + PCI_ANY_ID, + 0, + 0, + pbn_b0_1_115200 }, + { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9835, PCI_VENDOR_ID_IBM, 0x0299, 0, 0, pbn_b0_bt_2_115200 }, From d3210c8e88ee4132a5bb316ee96b09472db90572 Mon Sep 17 00:00:00 2001 From: "jempty.liang" Date: Mon, 17 Nov 2025 03:41:17 +0000 Subject: [PATCH 48/60] serial: 8250-of: Fix style issues in 8250_of.c This patch resolves the warning "sizeof *port should be sizeof(*port)" detected by checkpatch.pl. Signed-off-by: jempty.liang Link: https://patch.msgid.link/20251117034117.55588-1-imntjempty@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_of.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_of.c b/drivers/tty/serial/8250/8250_of.c index d178b6c54ea1..9799356b65f7 100644 --- a/drivers/tty/serial/8250/8250_of.c +++ b/drivers/tty/serial/8250/8250_of.c @@ -95,7 +95,7 @@ static int of_platform_serial_setup(struct platform_device *ofdev, u32 spd; int ret; - memset(port, 0, sizeof *port); + memset(port, 0, sizeof(*port)); pm_runtime_enable(&ofdev->dev); pm_runtime_get_sync(&ofdev->dev); From 57c8794693368a5df8014e4bbb7ef4016be119ed Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 17 Nov 2025 12:07:10 +0800 Subject: [PATCH 49/60] serial: icom: Convert PCIBIOS_* return codes to errnos icom_probe() uses pci_read_config_dword() that returns PCIBIOS_* codes. The return code is returned from the probe function as is but probe functions should return normal errnos. A proper implementation can be found in drivers/leds/leds-ss4200.c Convert PCIBIOS_* return codes using pcibios_err_to_errno() into normal errno before returning them. Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251117040710.1544-1-vulab@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/icom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/icom.c b/drivers/tty/serial/icom.c index 7fb995a8490e..566c05511b0f 100644 --- a/drivers/tty/serial/icom.c +++ b/drivers/tty/serial/icom.c @@ -1723,6 +1723,7 @@ static int icom_probe(struct pci_dev *dev, retval = pci_read_config_dword(dev, PCI_COMMAND, &command_reg); if (retval) { dev_err(&dev->dev, "PCI Config read FAILED\n"); + retval = pcibios_err_to_errno(retval); goto probe_exit0; } From f0a6e936eb9ca1cfb1c58239ef22e50e761a7a06 Mon Sep 17 00:00:00 2001 From: Sam Protsenko Date: Mon, 17 Nov 2025 20:48:22 -0600 Subject: [PATCH 50/60] tty: serial: samsung: Declare earlycon for Exynos850 It makes it possible to use just "earlycon" param in kernel cmdline on Exynos850 based boards instead of "earlycon=exynos4210,0x13820000", as described in [1]: When used with no options, the early console is determined by stdout-path property in device tree's chosen node [1] Documentation/admin-guide/kernel-parameters.txt Signed-off-by: Sam Protsenko Link: https://patch.msgid.link/20251118024822.28148-1-semen.protsenko@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/samsung_tty.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c index 2fb58c626daf..c1fabad6ba1f 100644 --- a/drivers/tty/serial/samsung_tty.c +++ b/drivers/tty/serial/samsung_tty.c @@ -2830,6 +2830,8 @@ OF_EARLYCON_DECLARE(exynos4210, "samsung,exynos4210-uart", s5pv210_early_console_setup); OF_EARLYCON_DECLARE(artpec8, "axis,artpec8-uart", s5pv210_early_console_setup); +OF_EARLYCON_DECLARE(exynos850, "samsung,exynos850-uart", + s5pv210_early_console_setup); static int __init gs101_early_console_setup(struct earlycon_device *device, const char *opt) From 29e8a0c587e328ed458380a45d6028adf64d7487 Mon Sep 17 00:00:00 2001 From: Wenhua Lin Date: Wed, 22 Oct 2025 11:08:40 +0800 Subject: [PATCH 51/60] serial: sprd: Return -EPROBE_DEFER when uart clock is not ready In sprd_clk_init(), when devm_clk_get() returns -EPROBE_DEFER for either uart or source clock, we should propagate the error instead of just warning and continuing with NULL clocks. Currently the driver only emits a warning when clock acquisition fails and proceeds with NULL clock pointers. This can lead to issues later when the clocks are actually needed. More importantly, when the clock provider is not ready yet and returns -EPROBE_DEFER, we should return this error to allow deferred probing. This change adds explicit checks for -EPROBE_DEFER after both: 1. devm_clk_get(uport->dev, uart) 2. devm_clk_get(uport->dev, source) When -EPROBE_DEFER is encountered, the function now returns -EPROBE_DEFER to let the driver framework retry probing later when the clock dependencies are resolved. Signed-off-by: Wenhua Lin Link: https://patch.msgid.link/20251022030840.956589-1-Wenhua.Lin@unisoc.com Reviewed-by: Cixi Geng Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sprd_serial.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/tty/serial/sprd_serial.c b/drivers/tty/serial/sprd_serial.c index 8c9366321f8e..092755f35683 100644 --- a/drivers/tty/serial/sprd_serial.c +++ b/drivers/tty/serial/sprd_serial.c @@ -1133,6 +1133,9 @@ static int sprd_clk_init(struct uart_port *uport) clk_uart = devm_clk_get(uport->dev, "uart"); if (IS_ERR(clk_uart)) { + if (PTR_ERR(clk_uart) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(uport->dev, "uart%d can't get uart clock\n", uport->line); clk_uart = NULL; @@ -1140,6 +1143,9 @@ static int sprd_clk_init(struct uart_port *uport) clk_parent = devm_clk_get(uport->dev, "source"); if (IS_ERR(clk_parent)) { + if (PTR_ERR(clk_parent) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(uport->dev, "uart%d can't get source clock\n", uport->line); clk_parent = NULL; From 10904d725f6e382376266a679ff425af488fcbcd Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Mon, 10 Nov 2025 15:40:42 +0530 Subject: [PATCH 52/60] serial: qcom-geni: Enable PM runtime for serial driver The GENI serial driver currently handles power resource management through calls to the statically defined geni_serial_resources_on() and geni_serial_resources_off() functions. This approach reduces modularity and limits support for platforms with diverse power management mechanisms, including resource managed by firmware. Improve modularity and enable better integration with platform-specific power management, introduce support for runtime PM. Use pm_runtime_resume_and_get() and pm_runtime_put_sync() within the qcom_geni_serial_pm() callback to control resource power state transitions based on UART power state changes. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20251110101043.2108414-4-praveen.talari@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 8058b839b26c..9c820302047c 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1650,10 +1650,10 @@ static void qcom_geni_serial_pm(struct uart_port *uport, old_state = UART_PM_STATE_OFF; if (new_state == UART_PM_STATE_ON && old_state == UART_PM_STATE_OFF) - geni_serial_resources_on(uport); + pm_runtime_resume_and_get(uport->dev); else if (new_state == UART_PM_STATE_OFF && old_state == UART_PM_STATE_ON) - geni_serial_resources_off(uport); + pm_runtime_put_sync(uport->dev); } @@ -1815,6 +1815,8 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) if (ret) return ret; + devm_pm_runtime_enable(port->se.dev); + ret = uart_add_one_port(drv, uport); if (ret) return ret; @@ -1846,6 +1848,22 @@ static void qcom_geni_serial_remove(struct platform_device *pdev) uart_remove_one_port(drv, &port->uport); } +static int __maybe_unused qcom_geni_serial_runtime_suspend(struct device *dev) +{ + struct qcom_geni_serial_port *port = dev_get_drvdata(dev); + struct uart_port *uport = &port->uport; + + return geni_serial_resources_off(uport); +} + +static int __maybe_unused qcom_geni_serial_runtime_resume(struct device *dev) +{ + struct qcom_geni_serial_port *port = dev_get_drvdata(dev); + struct uart_port *uport = &port->uport; + + return geni_serial_resources_on(uport); +} + static int qcom_geni_serial_suspend(struct device *dev) { struct qcom_geni_serial_port *port = dev_get_drvdata(dev); @@ -1889,6 +1907,8 @@ static const struct qcom_geni_device_data qcom_geni_uart_data = { }; static const struct dev_pm_ops qcom_geni_serial_pm_ops = { + SET_RUNTIME_PM_OPS(qcom_geni_serial_runtime_suspend, + qcom_geni_serial_runtime_resume, NULL) SYSTEM_SLEEP_PM_OPS(qcom_geni_serial_suspend, qcom_geni_serial_resume) }; From abffd1e6c4f1c9746ffd3fb5c659668efc221714 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Mon, 10 Nov 2025 15:40:43 +0530 Subject: [PATCH 53/60] serial: qcom-geni: Enable Serial on SA8255p Qualcomm platforms The Qualcomm automotive SA8255p SoC relies on firmware to configure platform resources, including clocks, interconnects and TLMM. The driver requests resources operations over SCMI using power and performance protocols. The SCMI power protocol enables or disables resources like clocks, interconnect paths, and TLMM (GPIOs) using runtime PM framework APIs, such as resume/suspend, to control power states(on/off). The SCMI performance protocol manages UART baud rates, with each baud rate represented by a performance level. The driver uses the dev_pm_opp_set_level() API to request the desired baud rate by specifying the performance level. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20251110101043.2108414-5-praveen.talari@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 158 +++++++++++++++++++++++--- 1 file changed, 141 insertions(+), 17 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 9c820302047c..6ce6528f5c10 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -101,10 +102,16 @@ #define DMA_RX_BUF_SIZE 2048 static DEFINE_IDA(port_ida); +#define DOMAIN_IDX_POWER 0 +#define DOMAIN_IDX_PERF 1 struct qcom_geni_device_data { bool console; enum geni_se_xfer_mode mode; + struct dev_pm_domain_attach_data pd_data; + int (*resources_init)(struct uart_port *uport); + int (*set_rate)(struct uart_port *uport, unsigned int baud); + int (*power_state)(struct uart_port *uport, bool state); }; struct qcom_geni_private_data { @@ -142,6 +149,7 @@ struct qcom_geni_serial_port { struct qcom_geni_private_data private_data; const struct qcom_geni_device_data *dev_data; + struct dev_pm_domain_list *pd_list; }; static const struct uart_ops qcom_geni_console_pops; @@ -1299,6 +1307,42 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud) return 0; } +static int geni_serial_set_level(struct uart_port *uport, unsigned int baud) +{ + struct qcom_geni_serial_port *port = to_dev_port(uport); + struct device *perf_dev = port->pd_list->pd_devs[DOMAIN_IDX_PERF]; + + /* + * The performance protocol sets UART communication + * speeds by selecting different performance levels + * through the OPP framework. + * + * Supported perf levels for baudrates in firmware are below + * +---------------------+--------------------+ + * | Perf level value | Baudrate values | + * +---------------------+--------------------+ + * | 300 | 300 | + * | 1200 | 1200 | + * | 2400 | 2400 | + * | 4800 | 4800 | + * | 9600 | 9600 | + * | 19200 | 19200 | + * | 38400 | 38400 | + * | 57600 | 57600 | + * | 115200 | 115200 | + * | 230400 | 230400 | + * | 460800 | 460800 | + * | 921600 | 921600 | + * | 2000000 | 2000000 | + * | 3000000 | 3000000 | + * | 3200000 | 3200000 | + * | 4000000 | 4000000 | + * +---------------------+--------------------+ + */ + + return dev_pm_opp_set_level(perf_dev, baud); +} + static void qcom_geni_serial_set_termios(struct uart_port *uport, struct ktermios *termios, const struct ktermios *old) @@ -1317,7 +1361,7 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport, /* baud rate */ baud = uart_get_baud_rate(uport, termios, old, 300, 8000000); - ret = geni_serial_set_rate(uport, baud); + ret = port->dev_data->set_rate(uport, baud); if (ret) return; @@ -1604,8 +1648,27 @@ static int geni_serial_resources_off(struct uart_port *uport) return 0; } -static int geni_serial_resource_init(struct qcom_geni_serial_port *port) +static int geni_serial_resource_state(struct uart_port *uport, bool power_on) { + return power_on ? geni_serial_resources_on(uport) : geni_serial_resources_off(uport); +} + +static int geni_serial_pwr_init(struct uart_port *uport) +{ + struct qcom_geni_serial_port *port = to_dev_port(uport); + int ret; + + ret = dev_pm_domain_attach_list(port->se.dev, + &port->dev_data->pd_data, &port->pd_list); + if (ret <= 0) + return -EINVAL; + + return 0; +} + +static int geni_serial_resource_init(struct uart_port *uport) +{ + struct qcom_geni_serial_port *port = to_dev_port(uport); int ret; port->se.clk = devm_clk_get(port->se.dev, "se"); @@ -1756,13 +1819,16 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) port->se.dev = &pdev->dev; port->se.wrapper = dev_get_drvdata(pdev->dev.parent); - ret = geni_serial_resource_init(port); + ret = port->dev_data->resources_init(uport); if (ret) return ret; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -EINVAL; + if (!res) { + ret = -EINVAL; + goto error; + } + uport->mapbase = res->start; uport->rs485_config = qcom_geni_rs485_config; @@ -1774,19 +1840,26 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) if (!data->console) { port->rx_buf = devm_kzalloc(uport->dev, DMA_RX_BUF_SIZE, GFP_KERNEL); - if (!port->rx_buf) - return -ENOMEM; + if (!port->rx_buf) { + ret = -ENOMEM; + goto error; + } } port->name = devm_kasprintf(uport->dev, GFP_KERNEL, "qcom_geni_serial_%s%d", uart_console(uport) ? "console" : "uart", uport->line); - if (!port->name) - return -ENOMEM; + if (!port->name) { + ret = -ENOMEM; + goto error; + } irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + if (irq < 0) { + ret = irq; + goto error; + } + uport->irq = irq; uport->has_sysrq = IS_ENABLED(CONFIG_SERIAL_QCOM_GENI_CONSOLE); @@ -1808,18 +1881,18 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) IRQF_TRIGGER_HIGH, port->name, uport); if (ret) { dev_err(uport->dev, "Failed to get IRQ ret %d\n", ret); - return ret; + goto error; } ret = uart_get_rs485_mode(uport); if (ret) - return ret; + goto error; devm_pm_runtime_enable(port->se.dev); ret = uart_add_one_port(drv, uport); if (ret) - return ret; + goto error; if (port->wakeup_irq > 0) { device_init_wakeup(&pdev->dev, true); @@ -1829,11 +1902,15 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) device_init_wakeup(&pdev->dev, false); ida_free(&port_ida, uport->line); uart_remove_one_port(drv, uport); - return ret; + goto error; } } return 0; + +error: + dev_pm_domain_detach_list(port->pd_list); + return ret; } static void qcom_geni_serial_remove(struct platform_device *pdev) @@ -1846,22 +1923,31 @@ static void qcom_geni_serial_remove(struct platform_device *pdev) device_init_wakeup(&pdev->dev, false); ida_free(&port_ida, uport->line); uart_remove_one_port(drv, &port->uport); + dev_pm_domain_detach_list(port->pd_list); } static int __maybe_unused qcom_geni_serial_runtime_suspend(struct device *dev) { struct qcom_geni_serial_port *port = dev_get_drvdata(dev); struct uart_port *uport = &port->uport; + int ret = 0; - return geni_serial_resources_off(uport); + if (port->dev_data->power_state) + ret = port->dev_data->power_state(uport, false); + + return ret; } static int __maybe_unused qcom_geni_serial_runtime_resume(struct device *dev) { struct qcom_geni_serial_port *port = dev_get_drvdata(dev); struct uart_port *uport = &port->uport; + int ret = 0; - return geni_serial_resources_on(uport); + if (port->dev_data->power_state) + ret = port->dev_data->power_state(uport, true); + + return ret; } static int qcom_geni_serial_suspend(struct device *dev) @@ -1899,11 +1985,41 @@ static int qcom_geni_serial_resume(struct device *dev) static const struct qcom_geni_device_data qcom_geni_console_data = { .console = true, .mode = GENI_SE_FIFO, + .resources_init = geni_serial_resource_init, + .set_rate = geni_serial_set_rate, + .power_state = geni_serial_resource_state, }; static const struct qcom_geni_device_data qcom_geni_uart_data = { .console = false, .mode = GENI_SE_DMA, + .resources_init = geni_serial_resource_init, + .set_rate = geni_serial_set_rate, + .power_state = geni_serial_resource_state, +}; + +static const struct qcom_geni_device_data sa8255p_qcom_geni_console_data = { + .console = true, + .mode = GENI_SE_FIFO, + .pd_data = { + .pd_flags = PD_FLAG_DEV_LINK_ON, + .pd_names = (const char*[]) { "power", "perf" }, + .num_pd_names = 2, + }, + .resources_init = geni_serial_pwr_init, + .set_rate = geni_serial_set_level, +}; + +static const struct qcom_geni_device_data sa8255p_qcom_geni_uart_data = { + .console = false, + .mode = GENI_SE_DMA, + .pd_data = { + .pd_flags = PD_FLAG_DEV_LINK_ON, + .pd_names = (const char*[]) { "power", "perf" }, + .num_pd_names = 2, + }, + .resources_init = geni_serial_pwr_init, + .set_rate = geni_serial_set_level, }; static const struct dev_pm_ops qcom_geni_serial_pm_ops = { @@ -1917,10 +2033,18 @@ static const struct of_device_id qcom_geni_serial_match_table[] = { .compatible = "qcom,geni-debug-uart", .data = &qcom_geni_console_data, }, + { + .compatible = "qcom,sa8255p-geni-debug-uart", + .data = &sa8255p_qcom_geni_console_data, + }, { .compatible = "qcom,geni-uart", .data = &qcom_geni_uart_data, }, + { + .compatible = "qcom,sa8255p-geni-uart", + .data = &sa8255p_qcom_geni_uart_data, + }, {} }; MODULE_DEVICE_TABLE(of, qcom_geni_serial_match_table); From 6974711cf770557e3b56b97999724618d72a48a0 Mon Sep 17 00:00:00 2001 From: Gerhard Engleder Date: Thu, 23 Oct 2025 17:12:28 +0200 Subject: [PATCH 54/60] serial: Keep rs485 settings for devices without firmware node Commit fe7f0fa43cef ("serial: 8250: Support rs485 devicetree properties") retrieves rs485 properties for 8250 drivers. These properties are read from the firmware node of the device within uart_get_rs485_mode(). If the firmware node does not exist, then the rs485 flags are still reset. Thus, 8250 driver cannot set rs485 flags to enable a defined rs485 mode during driver loading. This is no problem so far, as no 8250 driver sets the rs485 flags. The default rs485 mode can also be set by firmware nodes. But for some devices a firmware node does not exist. E.g., for a PCIe based serial interface on x86 no device tree is available and the ACPI information of the BIOS often cannot by modified. In this case it shall be possible, that a driver works out of the box by setting a reasonable default rs485 mode. If no firmware node exists, then it should be possible for the driver to set a reasonable default rs485 mode. Therefore, reset rs485 flags only if a firmware node exists. Signed-off-by: Gerhard Engleder Cc: Lukas Wunner Reviewed-by: Lukas Wunner Link: https://patch.msgid.link/20251023151229.11774-2-gerhard@engleder-embedded.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index c532235f8d55..9930023e924c 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -3502,6 +3502,14 @@ int uart_get_rs485_mode(struct uart_port *port) if (!(port->rs485_supported.flags & SER_RS485_ENABLED)) return 0; + /* + * Retrieve properties only if a firmware node exists. If no firmware + * node exists, then don't touch rs485 config and keep initial rs485 + * properties set by driver. + */ + if (!dev_fwnode(dev)) + return 0; + ret = device_property_read_u32_array(dev, "rs485-rts-delay", rs485_delay, 2); if (!ret) { From ab9a30d6febf768c057fcde74a46597862db443e Mon Sep 17 00:00:00 2001 From: Gerhard Engleder Date: Thu, 23 Oct 2025 17:12:29 +0200 Subject: [PATCH 55/60] serial: 8250: add driver for KEBA UART The KEBA UART is found in the system FPGA of KEBA PLC devices. It is mostly 8250 compatible with extension for some UART modes. 3 different variants exist. The simpliest variant supports only RS-232 and is used for debug interfaces. The next variant supports only RS-485 and is used mostly for communication with KEBA panel devices. The third variant is able to support RS-232, RS-485 and RS-422. For this variant not only the mode of the UART is configured, also the physics and transceivers are switched according to the mode. Signed-off-by: Gerhard Engleder Tested-by: Daniel Gierlinger Reviewed-by: Lukas Wunner Link: https://patch.msgid.link/20251023151229.11774-3-gerhard@engleder-embedded.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_keba.c | 280 ++++++++++++++++++++++++++++ drivers/tty/serial/8250/Kconfig | 13 ++ drivers/tty/serial/8250/Makefile | 1 + 3 files changed, 294 insertions(+) create mode 100644 drivers/tty/serial/8250/8250_keba.c diff --git a/drivers/tty/serial/8250/8250_keba.c b/drivers/tty/serial/8250/8250_keba.c new file mode 100644 index 000000000000..c05b89551b12 --- /dev/null +++ b/drivers/tty/serial/8250/8250_keba.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 KEBA Industrial Automation GmbH + * + * Driver for KEBA UART FPGA IP core + */ + +#include +#include +#include +#include +#include + +#include "8250.h" + +#define KUART "kuart" + +/* flags */ +#define KUART_RS485 BIT(0) +#define KUART_USE_CAPABILITY BIT(1) + +/* registers */ +#define KUART_VERSION 0x0000 +#define KUART_REVISION 0x0001 +#define KUART_CAPABILITY 0x0002 +#define KUART_CONTROL 0x0004 +#define KUART_BASE 0x000C +#define KUART_REGSHIFT 2 +#define KUART_CLK 1843200 + +/* mode flags */ +enum kuart_mode { + KUART_MODE_NONE = 0, + KUART_MODE_RS485, + KUART_MODE_RS422, + KUART_MODE_RS232 +}; + +/* capability flags */ +#define KUART_CAPABILITY_NONE BIT(KUART_MODE_NONE) +#define KUART_CAPABILITY_RS485 BIT(KUART_MODE_RS485) +#define KUART_CAPABILITY_RS422 BIT(KUART_MODE_RS422) +#define KUART_CAPABILITY_RS232 BIT(KUART_MODE_RS232) +#define KUART_CAPABILITY_MASK GENMASK(3, 0) + +/* Additional Control Register DTR line configuration */ +#define UART_ACR_DTRLC_MASK 0x18 +#define UART_ACR_DTRLC_COMPAT 0x00 +#define UART_ACR_DTRLC_ENABLE_LOW 0x10 + +struct kuart { + struct keba_uart_auxdev *auxdev; + void __iomem *base; + unsigned int line; + + unsigned int flags; + u8 capability; + enum kuart_mode mode; +}; + +static void kuart_set_phy_mode(struct kuart *kuart, enum kuart_mode mode) +{ + iowrite8(mode, kuart->base + KUART_CONTROL); +} + +static void kuart_enhanced_mode(struct uart_8250_port *up, bool enable) +{ + u8 lcr, efr; + + /* backup LCR register */ + lcr = serial_in(up, UART_LCR); + + /* enable 650 compatible register set (EFR, ...) */ + serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); + + /* enable/disable enhanced mode with indexed control registers */ + efr = serial_in(up, UART_EFR); + if (enable) + efr |= UART_EFR_ECB; + else + efr &= ~UART_EFR_ECB; + serial_out(up, UART_EFR, efr); + + /* disable 650 compatible register set, restore LCR */ + serial_out(up, UART_LCR, lcr); +} + +static void kuart_dtr_line_config(struct uart_8250_port *up, u8 dtrlc) +{ + u8 acr; + + /* set index register to 0 to access ACR register */ + serial_out(up, UART_SCR, UART_ACR); + + /* set value register to 0x10 writing DTR mode (1,0) */ + acr = serial_in(up, UART_LSR); + acr &= ~UART_ACR_DTRLC_MASK; + acr |= dtrlc; + serial_out(up, UART_LSR, acr); +} + +static int kuart_rs485_config(struct uart_port *port, struct ktermios *termios, + struct serial_rs485 *rs485) +{ + struct uart_8250_port *up = up_to_u8250p(port); + struct kuart *kuart = port->private_data; + enum kuart_mode mode; + u8 dtrlc; + + if (rs485->flags & SER_RS485_ENABLED) { + if (rs485->flags & SER_RS485_MODE_RS422) + mode = KUART_MODE_RS422; + else + mode = KUART_MODE_RS485; + } else { + mode = KUART_MODE_RS232; + } + + if (mode == kuart->mode) + return 0; + + if (kuart->flags & KUART_USE_CAPABILITY) { + /* deactivate physical interface, break before make */ + kuart_set_phy_mode(kuart, KUART_MODE_NONE); + } + + if (mode == KUART_MODE_RS485) { + /* + * Set DTR line configuration of 95x UART to DTR mode (1,0). + * In this mode the DTR pin drives the active-low enable pin of + * an external RS485 buffer. The DTR pin will be forced low + * whenever the transmitter is not empty, otherwise DTR pin is + * high. + */ + dtrlc = UART_ACR_DTRLC_ENABLE_LOW; + } else { + /* + * Set DTR line configuration of 95x UART to DTR mode (0,0). + * In this mode the DTR pin is compatible with 16C450, 16C550, + * 16C650 and 16c670 (i.e. normal). + */ + dtrlc = UART_ACR_DTRLC_COMPAT; + } + + kuart_enhanced_mode(up, true); + kuart_dtr_line_config(up, dtrlc); + kuart_enhanced_mode(up, false); + + if (kuart->flags & KUART_USE_CAPABILITY) { + /* activate selected physical interface */ + kuart_set_phy_mode(kuart, mode); + } + + kuart->mode = mode; + + return 0; +} + +static int kuart_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct device *dev = &auxdev->dev; + struct uart_8250_port uart = {}; + struct resource res; + struct kuart *kuart; + int retval; + + kuart = devm_kzalloc(dev, sizeof(*kuart), GFP_KERNEL); + if (!kuart) + return -ENOMEM; + kuart->auxdev = container_of(auxdev, struct keba_uart_auxdev, auxdev); + kuart->flags = id->driver_data; + auxiliary_set_drvdata(auxdev, kuart); + + /* + * map only memory in front of UART registers, UART registers will be + * mapped by serial port + */ + res = kuart->auxdev->io; + res.end = res.start + KUART_BASE - 1; + kuart->base = devm_ioremap_resource(dev, &res); + if (IS_ERR(kuart->base)) + return PTR_ERR(kuart->base); + + if (kuart->flags & KUART_USE_CAPABILITY) { + /* + * supported modes are read from capability register, at least + * one mode other than none must be supported + */ + kuart->capability = ioread8(kuart->base + KUART_CAPABILITY) & + KUART_CAPABILITY_MASK; + if ((kuart->capability & ~KUART_CAPABILITY_NONE) == 0) + return -EIO; + } + + spin_lock_init(&uart.port.lock); + uart.port.dev = dev; + uart.port.mapbase = kuart->auxdev->io.start + KUART_BASE; + uart.port.irq = kuart->auxdev->irq; + uart.port.uartclk = KUART_CLK; + uart.port.private_data = kuart; + + /* 8 bit registers are 32 bit aligned => shift register offset */ + uart.port.iotype = UPIO_MEM32; + uart.port.regshift = KUART_REGSHIFT; + + /* + * UART mixes 16550, 16750 and 16C950 (for RS485) standard => auto + * configuration works best + */ + uart.port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_IOREMAP; + + /* + * UART supports RS485, RS422 and RS232 with switching of physical + * interface + */ + uart.port.rs485_config = kuart_rs485_config; + if (kuart->flags & KUART_RS485) { + uart.port.rs485_supported.flags = SER_RS485_ENABLED | + SER_RS485_RTS_ON_SEND; + uart.port.rs485.flags = SER_RS485_ENABLED | + SER_RS485_RTS_ON_SEND; + } + if (kuart->flags & KUART_USE_CAPABILITY) { + /* default mode priority is RS485 > RS422 > RS232 */ + if (kuart->capability & KUART_CAPABILITY_RS422) { + uart.port.rs485_supported.flags |= SER_RS485_ENABLED | + SER_RS485_RTS_ON_SEND | + SER_RS485_MODE_RS422; + uart.port.rs485.flags = SER_RS485_ENABLED | + SER_RS485_RTS_ON_SEND | + SER_RS485_MODE_RS422; + } + if (kuart->capability & KUART_CAPABILITY_RS485) { + uart.port.rs485_supported.flags |= SER_RS485_ENABLED | + SER_RS485_RTS_ON_SEND; + uart.port.rs485.flags = SER_RS485_ENABLED | + SER_RS485_RTS_ON_SEND; + } + } + + retval = serial8250_register_8250_port(&uart); + if (retval < 0) { + dev_err(&auxdev->dev, "UART registration failed!\n"); + return retval; + } + kuart->line = retval; + + return 0; +} + +static void kuart_remove(struct auxiliary_device *auxdev) +{ + struct kuart *kuart = auxiliary_get_drvdata(auxdev); + + if (kuart->flags & KUART_USE_CAPABILITY) + kuart_set_phy_mode(kuart, KUART_MODE_NONE); + + serial8250_unregister_port(kuart->line); +} + +static const struct auxiliary_device_id kuart_devtype_aux[] = { + { .name = "keba.rs485-uart", .driver_data = KUART_RS485 }, + { .name = "keba.rs232-uart", .driver_data = 0 }, + { .name = "keba.uart", .driver_data = KUART_USE_CAPABILITY }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, kuart_devtype_aux); + +static struct auxiliary_driver kuart_driver_aux = { + .name = KUART, + .id_table = kuart_devtype_aux, + .probe = kuart_probe, + .remove = kuart_remove, +}; +module_auxiliary_driver(kuart_driver_aux); + +MODULE_AUTHOR("Gerhard Engleder "); +MODULE_DESCRIPTION("KEBA 8250 serial port driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig index 58f0142a59ff..a64ff026b13b 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -413,6 +413,19 @@ config SERIAL_8250_IOC3 behind the IOC3 device on those systems. Maximum baud speed is 38400bps using this driver. +config SERIAL_8250_KEBA + tristate "Support for KEBA 8250 UART" + depends on SERIAL_8250 + depends on KEBA_CP500 + help + Selecting this option will add support for KEBA UARTs. These UARTs + are used for the serial interfaces of KEBA PLCs. + + This driver can also be built as a module. If so, the module will + be called 8250_keba. + + If unsure, say N. + config SERIAL_8250_RT288X bool "Ralink RT288x/RT305x/RT3662/RT3883 serial port support" depends on SERIAL_8250 diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile index 513a0941c284..f7a463c9860a 100644 --- a/drivers/tty/serial/8250/Makefile +++ b/drivers/tty/serial/8250/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_SERIAL_8250_HP300) += 8250_hp300.o obj-$(CONFIG_SERIAL_8250_HUB6) += 8250_hub6.o obj-$(CONFIG_SERIAL_8250_INGENIC) += 8250_ingenic.o obj-$(CONFIG_SERIAL_8250_IOC3) += 8250_ioc3.o +obj-$(CONFIG_SERIAL_8250_KEBA) += 8250_keba.o obj-$(CONFIG_SERIAL_8250_LPC18XX) += 8250_lpc18xx.o obj-$(CONFIG_SERIAL_8250_LPSS) += 8250_lpss.o obj-$(CONFIG_SERIAL_8250_MEN_MCB) += 8250_men_mcb.o From 7cf86b66e5628c55899e7b00ce5015b3f2750f35 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 11 Oct 2025 15:16:47 +0800 Subject: [PATCH 56/60] dt-bindings: serial: 8250: Add Loongson uart compatible The Loongson family have a mostly NS16550A-compatible UART and High-Speed UART hardware with the exception of custom frequency divider latch settings register. Co-developed-by: Haowei Zheng Signed-off-by: Haowei Zheng Acked-by: Conor Dooley Signed-off-by: Binbin Zhou Reviewed-by: Huacai Chen Link: https://patch.msgid.link/2d858e9303d95a3e4909aa9c1379d4abbdc52cc2.1760166651.git.zhoubinbin@loongson.cn Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/8250.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/serial/8250.yaml b/Documentation/devicetree/bindings/serial/8250.yaml index b243afa69a1a..167ddcbd8800 100644 --- a/Documentation/devicetree/bindings/serial/8250.yaml +++ b/Documentation/devicetree/bindings/serial/8250.yaml @@ -125,6 +125,8 @@ properties: - nxp,lpc1850-uart - opencores,uart16550-rtlsvn105 - ti,da830-uart + - loongson,ls2k0500-uart + - loongson,ls2k1500-uart - const: ns16550a - items: - enum: @@ -169,6 +171,18 @@ properties: - nvidia,tegra194-uart - nvidia,tegra234-uart - const: nvidia,tegra20-uart + - items: + - enum: + - loongson,ls2k1000-uart + - const: loongson,ls2k0500-uart + - const: ns16550a + - items: + - enum: + - loongson,ls3a5000-uart + - loongson,ls3a6000-uart + - loongson,ls2k2000-uart + - const: loongson,ls2k1500-uart + - const: ns16550a reg: maxItems: 1 From 25e95d763176854e961aaf0f8a76f435f2dab974 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 11 Oct 2025 15:16:48 +0800 Subject: [PATCH 57/60] serial: 8250: Add Loongson uart driver support Add the driver for on-chip UART used on Loongson family chips. The hardware is similar to NS16550A, but there are the following differences: - Some chips (such as Loongson-2K2000) have added a fractional division register to obtain the required baud rate accurately, so the {get,set}_divisor callback is overridden. - Due to hardware defects, quirk handling is required for UART_MCR/UART_MSR. Co-developed-by: Haowei Zheng Signed-off-by: Haowei Zheng Signed-off-by: Binbin Zhou Reviewed-by: Huacai Chen Link: https://patch.msgid.link/2c2a01a276b9250efea0c7aa190efecdfd6fdf5a.1760166651.git.zhoubinbin@loongson.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_loongson.c | 238 ++++++++++++++++++++++++ drivers/tty/serial/8250/Kconfig | 10 + drivers/tty/serial/8250/Makefile | 1 + 3 files changed, 249 insertions(+) create mode 100644 drivers/tty/serial/8250/8250_loongson.c diff --git a/drivers/tty/serial/8250/8250_loongson.c b/drivers/tty/serial/8250/8250_loongson.c new file mode 100644 index 000000000000..53153a116c01 --- /dev/null +++ b/drivers/tty/serial/8250/8250_loongson.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Serial Port driver for Loongson family chips + * + * Copyright (C) 2020-2025 Loongson Technology Corporation Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "8250.h" + +/* Divisor Latch Fraction Register */ +#define LOONGSON_UART_DLF 0x2 + +#define LOONGSON_QUOT_FRAC_MASK GENMASK(7, 0) +#define LOONGSON_QUOT_DIV_MASK GENMASK(15, 8) + +struct loongson_uart_ddata { + bool has_frac; + u8 mcr_invert; + u8 msr_invert; +}; + +static const struct loongson_uart_ddata ls2k0500_uart_data = { + .has_frac = false, + .mcr_invert = UART_MCR_RTS | UART_MCR_DTR, + .msr_invert = UART_MSR_CTS | UART_MSR_DSR, +}; + +static const struct loongson_uart_ddata ls2k1500_uart_data = { + .has_frac = true, + .mcr_invert = UART_MCR_RTS | UART_MCR_DTR, + .msr_invert = 0, +}; + +struct loongson_uart_priv { + int line; + struct clk *clk; + struct resource *res; + struct reset_control *rst; + const struct loongson_uart_ddata *ddata; +}; + +static u8 serial_fixup(struct uart_port *p, unsigned int offset, u8 val) +{ + struct loongson_uart_priv *priv = p->private_data; + + switch (offset) { + case UART_MCR: + return val ^ priv->ddata->mcr_invert; + case UART_MSR: + return val ^ priv->ddata->msr_invert; + default: + return val; + } +} + +static u32 loongson_serial_in(struct uart_port *p, unsigned int offset) +{ + u8 val; + + val = readb(p->membase + (offset << p->regshift)); + + return serial_fixup(p, offset, val); +} + +static void loongson_serial_out(struct uart_port *p, unsigned int offset, unsigned int value) +{ + u8 val; + + offset <<= p->regshift; + val = serial_fixup(p, offset, value); + writeb(val, p->membase + offset); +} + +static unsigned int loongson_frac_get_divisor(struct uart_port *port, unsigned int baud, + unsigned int *frac) +{ + unsigned int quot; + + quot = DIV_ROUND_CLOSEST((port->uartclk << 4), baud); + *frac = FIELD_GET(LOONGSON_QUOT_FRAC_MASK, quot); + + return FIELD_GET(LOONGSON_QUOT_DIV_MASK, quot); +} + +static void loongson_frac_set_divisor(struct uart_port *port, unsigned int baud, + unsigned int quot, unsigned int quot_frac) +{ + struct uart_8250_port *up = up_to_u8250p(port); + + serial_port_out(port, UART_LCR, up->lcr | UART_LCR_DLAB); + serial_dl_write(up, quot); + serial_port_out(port, LOONGSON_UART_DLF, quot_frac); +} + +static int loongson_uart_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct uart_8250_port uart = {}; + struct loongson_uart_priv *priv; + struct uart_port *port; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->ddata = device_get_match_data(dev); + + port = &uart.port; + spin_lock_init(&port->lock); + port->flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_IOREMAP; + port->iotype = UPIO_MEM; + port->regshift = 0; + port->dev = dev; + port->type = PORT_16550A; + port->private_data = priv; + + port->membase = devm_platform_get_and_ioremap_resource(pdev, 0, &priv->res); + if (!port->membase) + return -ENOMEM; + + port->mapbase = priv->res->start; + port->mapsize = resource_size(priv->res); + port->serial_in = loongson_serial_in; + port->serial_out = loongson_serial_out; + + if (priv->ddata->has_frac) { + port->get_divisor = loongson_frac_get_divisor; + port->set_divisor = loongson_frac_set_divisor; + } + + ret = uart_read_port_properties(port); + if (ret) + return ret; + + if (!port->uartclk) { + priv->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), + "Unable to determine clock frequency!\n"); + port->uartclk = clk_get_rate(priv->clk); + } + + priv->rst = devm_reset_control_get_optional_shared(dev, NULL); + if (IS_ERR(priv->rst)) + return PTR_ERR(priv->rst); + + ret = reset_control_deassert(priv->rst); + if (ret) + return ret; + + ret = serial8250_register_8250_port(&uart); + if (ret < 0) { + reset_control_assert(priv->rst); + return ret; + } + + priv->line = ret; + platform_set_drvdata(pdev, priv); + + return 0; +} + +static void loongson_uart_remove(struct platform_device *pdev) +{ + struct loongson_uart_priv *priv = platform_get_drvdata(pdev); + + serial8250_unregister_port(priv->line); + reset_control_assert(priv->rst); +} + +static int loongson_uart_suspend(struct device *dev) +{ + struct loongson_uart_priv *priv = dev_get_drvdata(dev); + struct uart_8250_port *up = serial8250_get_port(priv->line); + + serial8250_suspend_port(priv->line); + + if (!uart_console(&up->port) || console_suspend_enabled) + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int loongson_uart_resume(struct device *dev) +{ + struct loongson_uart_priv *priv = dev_get_drvdata(dev); + struct uart_8250_port *up = serial8250_get_port(priv->line); + int ret; + + if (!uart_console(&up->port) || console_suspend_enabled) { + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + } + + serial8250_resume_port(priv->line); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(loongson_uart_pm_ops, loongson_uart_suspend, + loongson_uart_resume); + +static const struct of_device_id loongson_uart_of_ids[] = { + { .compatible = "loongson,ls2k0500-uart", .data = &ls2k0500_uart_data }, + { .compatible = "loongson,ls2k1500-uart", .data = &ls2k1500_uart_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, loongson_uart_of_ids); + +static struct platform_driver loongson_uart_driver = { + .probe = loongson_uart_probe, + .remove = loongson_uart_remove, + .driver = { + .name = "loongson-uart", + .pm = pm_ptr(&loongson_uart_pm_ops), + .of_match_table = loongson_uart_of_ids, + }, +}; + +module_platform_driver(loongson_uart_driver); + +MODULE_DESCRIPTION("Loongson UART driver"); +MODULE_AUTHOR("Loongson Technology Corporation Limited."); +MODULE_LICENSE("GPL"); diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig index a64ff026b13b..c488ff6f2865 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -464,6 +464,16 @@ config SERIAL_8250_OMAP_TTYO_FIXUP not booting kernel because the serial console remains silent in case they forgot to update the command line. +config SERIAL_8250_LOONGSON + tristate "Loongson 8250 based serial port" + depends on SERIAL_8250 + depends on LOONGARCH || COMPILE_TEST + help + If you have a machine based on LoongArch CPU you can enable + its onboard serial ports by enabling this option. The option + is applicable to both devicetree and ACPI, say Y to this option. + If unsure, say N. + config SERIAL_8250_LPC18XX tristate "NXP LPC18xx/43xx serial port support" depends on SERIAL_8250 && OF && (ARCH_LPC18XX || COMPILE_TEST) diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile index f7a463c9860a..4b36131ddb77 100644 --- a/drivers/tty/serial/8250/Makefile +++ b/drivers/tty/serial/8250/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_SERIAL_8250_HUB6) += 8250_hub6.o obj-$(CONFIG_SERIAL_8250_INGENIC) += 8250_ingenic.o obj-$(CONFIG_SERIAL_8250_IOC3) += 8250_ioc3.o obj-$(CONFIG_SERIAL_8250_KEBA) += 8250_keba.o +obj-$(CONFIG_SERIAL_8250_LOONGSON) += 8250_loongson.o obj-$(CONFIG_SERIAL_8250_LPC18XX) += 8250_lpc18xx.o obj-$(CONFIG_SERIAL_8250_LPSS) += 8250_lpss.o obj-$(CONFIG_SERIAL_8250_MEN_MCB) += 8250_men_mcb.o From 13532b5186a7aa4dfd9885355c6af7562b75dd7f Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 11 Oct 2025 15:16:49 +0800 Subject: [PATCH 58/60] LoongArch: dts: Add uart new compatible string Add loongson,ls2k*-uart compatible string on uarts. Co-developed-by: Haowei Zheng Signed-off-by: Haowei Zheng Signed-off-by: Binbin Zhou Reviewed-by: Huacai Chen Link: https://patch.msgid.link/8e0c08459fa5bddefd898648fea28a9f2fde701a.1760166651.git.zhoubinbin@loongson.cn Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/boot/dts/loongson-2k0500.dtsi | 2 +- arch/loongarch/boot/dts/loongson-2k1000.dtsi | 2 +- arch/loongarch/boot/dts/loongson-2k2000.dtsi | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/loongarch/boot/dts/loongson-2k0500.dtsi b/arch/loongarch/boot/dts/loongson-2k0500.dtsi index 588ebc3bded4..357de4ca7555 100644 --- a/arch/loongarch/boot/dts/loongson-2k0500.dtsi +++ b/arch/loongarch/boot/dts/loongson-2k0500.dtsi @@ -380,7 +380,7 @@ }; uart0: serial@1ff40800 { - compatible = "ns16550a"; + compatible = "loongson,ls2k0500-uart", "ns16550a"; reg = <0x0 0x1ff40800 0x0 0x10>; clock-frequency = <100000000>; interrupt-parent = <&eiointc>; diff --git a/arch/loongarch/boot/dts/loongson-2k1000.dtsi b/arch/loongarch/boot/dts/loongson-2k1000.dtsi index d8e01e2534dd..60ab425f793f 100644 --- a/arch/loongarch/boot/dts/loongson-2k1000.dtsi +++ b/arch/loongarch/boot/dts/loongson-2k1000.dtsi @@ -297,7 +297,7 @@ }; uart0: serial@1fe20000 { - compatible = "ns16550a"; + compatible = "loongson,ls2k1000-uart", "loongson,ls2k0500-uart", "ns16550a"; reg = <0x0 0x1fe20000 0x0 0x10>; clock-frequency = <125000000>; interrupt-parent = <&liointc0>; diff --git a/arch/loongarch/boot/dts/loongson-2k2000.dtsi b/arch/loongarch/boot/dts/loongson-2k2000.dtsi index 00cc485b753b..6c77b86ee06c 100644 --- a/arch/loongarch/boot/dts/loongson-2k2000.dtsi +++ b/arch/loongarch/boot/dts/loongson-2k2000.dtsi @@ -250,7 +250,7 @@ }; uart0: serial@1fe001e0 { - compatible = "ns16550a"; + compatible = "loongson,ls2k2000-uart", "loongson,ls2k1500-uart", "ns16550a"; reg = <0x0 0x1fe001e0 0x0 0x10>; clock-frequency = <100000000>; interrupt-parent = <&liointc>; From a6cdfd69ad38997108b862f9aafc547891506701 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 14 Nov 2025 10:13:46 +0000 Subject: [PATCH 59/60] dt-bindings: serial: rsci: Drop "uart-has-rtscts: false" Drop "uart-has-rtscts: false" from binding as the IP supports hardware flow control on all SoCs. Cc: stable@kernel.org Fixes: 25422e8f46c1 ("dt-bindings: serial: Add compatible for Renesas RZ/T2H SoC in sci") Acked-by: Conor Dooley Reviewed-by: Geert Uytterhoeven Signed-off-by: Biju Das Link: https://patch.msgid.link/20251114101350.106699-2-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/renesas,rsci.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/devicetree/bindings/serial/renesas,rsci.yaml b/Documentation/devicetree/bindings/serial/renesas,rsci.yaml index f50d8e02f476..6b1f827a335b 100644 --- a/Documentation/devicetree/bindings/serial/renesas,rsci.yaml +++ b/Documentation/devicetree/bindings/serial/renesas,rsci.yaml @@ -54,8 +54,6 @@ properties: power-domains: maxItems: 1 - uart-has-rtscts: false - required: - compatible - reg From 75a9f4c54770f062f4b3813a83667452b326dda3 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 14 Nov 2025 10:13:47 +0000 Subject: [PATCH 60/60] serial: sh-sci: Fix deadlock during RSCI FIFO overrun error On RSCI IP, a deadlock occurs during a FIFO overrun error, as it uses a different register to clear the FIFO overrun error status. Cc: stable@kernel.org Fixes: 0666e3fe95ab ("serial: sh-sci: Add support for RZ/T2H SCI") Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20251114101350.106699-3-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sh-sci.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index b28711eeab71..53edbf1d8963 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -1198,8 +1198,16 @@ static int sci_handle_fifo_overrun(struct uart_port *port) status = s->ops->read_reg(port, s->params->overrun_reg); if (status & s->params->overrun_mask) { - status &= ~s->params->overrun_mask; - s->ops->write_reg(port, s->params->overrun_reg, status); + if (s->type == SCI_PORT_RSCI) { + /* + * All of the CFCLR_*C clearing bits match the corresponding + * CSR_*status bits. So, reuse the overrun mask for clearing. + */ + s->ops->clear_SCxSR(port, s->params->overrun_mask); + } else { + status &= ~s->params->overrun_mask; + s->ops->write_reg(port, s->params->overrun_reg, status); + } port->icount.overrun++;